向量数据集放不下内存时,faiss 怎么把 IVF 倒排列表存到磁盘并搜索?
【免费下载链接】faissA library for efficient similarity search and clustering of dense vectors.项目地址: https://gitcode.com/GitHub_Trending/fa/faiss
当 IVF 索引的倒排列表(inverted lists)数据量超过可用内存时,faiss 提供OnDiskInvertedLists:索引结构仍由faiss.IndexIVF管理,但每个倒排列表的 code 和 id 以 mmap 方式存放在一个磁盘文件里,搜索时按需读取。demos/demo_ondisk_ivf.py 在 sift1M 数据集上完整演示了这条路径:训练索引 → 把库切成 4 块分别 add → 合并成 on-disk 索引 → 从磁盘加载并搜索验证。本文按该 demo 的 6 个阶段(0–6)还原一遍可执行流程。
准备条件
- 已安装 Python 版 faiss(demo 依赖
faiss与faiss.contrib.ondisk)。 - 准备 sift1M 数据集:benchs/README.md 说明需从 corpus-texmex.irisa.fr 下载 ANN_SIFT1M 并解压到子目录
sift1M/,需要以下 4 个文件:sift1M/sift_learn.fvecs(训练向量)sift1M/sift_base.fvecs(数据库向量)sift1M/sift_query.fvecs(查询向量)sift1M/sift_groundtruth.ivecs(ground truth)
- demo 中这些路径是相对路径,
tmpdir硬编码为/tmp/,因此要在包含sift1M/目录下运行脚本,中间产物会写入/tmp/(trained.index、block_0..3.index、merged_index.ivfdata、populated.index),运行前确保该目录可写。
阶段 0:训练 IVF 索引并保存
demos/demo_ondisk_ivf.py 用 factory 字符串构建一个IVF4096,Flat索引(4096 个聚类中心的 IVF-Flat),用训练集 train 后写出:
index = faiss.index_factory(xt.shape[1], "IVF4096,Flat") index.train(xt) faiss.write_index(index, "/tmp/trained.index")执行:
python demos/demo_ondisk_ivf.py 0此时/tmp/trained.index是只含训练好的粗量化器、还没有任何库向量的索引。
阶段 1–4:把库切成 4 块分别 add
接下来 4 个阶段各自读取trained.index,把sift_base的 1/4 数据 add 进独立副本,再写出:
i0, i1 = int(bno * xb.shape[0] / 4), int((bno + 1) * xb.shape[0] / 4) index = faiss.read_index("/tmp/trained.index") index.add_with_ids(xb[i0:i1], np.arange(i0, i1)) faiss.write_index(index, "/tmp/block_%d.index" % bno)python demos/demo_ondisk_ivf.py 1 python demos/demo_ondisk_ivf.py 2 python demos/demo_ondisk_ivf.py 3 python demos/demo_ondisk_ivf.py 4demos/README.md 指出这一步"can be done in parallel on several machines",即 4 个 block 可以在多台机器上并行构建。add_with_ids显式保留了原始 id,这样分块构建后各块 id 不冲突。产物是/tmp/block_0.index到/tmp/block_3.index。
阶段 5:合并为 on-disk 索引
这是整个方案的核心。demo 调用 contrib/ondisk.py 中的merge_ondisk:
index = faiss.read_index("/tmp/trained.index") block_fnames = ["/tmp/block_%d.index" % bno for bno in range(4)] merge_ondisk(index, block_fnames, "/tmp/merged_index.ivfdata") faiss.write_index(index, "/tmp/populated.index")python demos/demo_ondisk_ivf.py 5merge_ondisk内部逻辑(见 contrib/ondisk.py)值得注意的几点:
- 各分片索引用
faiss.read_index(fname, faiss.IO_FLAG_MMAP)读入。源码注释说明:IO_FLAG_MMAP 是为了"avoid actually loading the data thus the total size of the inverted lists can exceed the available RAM",即倒排列表数据不实际加载进 RAM。 - 目标索引必须为空:
assert index.ntotal == 0, "works only on empty index"。所以阶段 5 读的是阶段 0 写的trained.index,而不是任何已 add 数据的索引。 - 新建
faiss.OnDiskInvertedLists(nlist, code_size, ivfdata_fname),磁盘文件为merged_index.ivfdata,然后用merge_from_multiple把 4 个分片的倒排列表合并进去,最后replace_invlists(invlists, True)替换进索引。 - 每个分片设置
index_ivf.own_invlists = False,避免 invlists 随分片索引析构被释放。 merge_ondisk开头有断言:IndexIVFPQR不支持作为 on-disk 索引。
OnDiskInvertedLists的存储格式在 faiss/invlists/OnDiskInvertedLists.h 的注释中有说明:数据存放在一个 mmapped 内存块中,每条倒排列表是codes[capacity * code_size]后跟ids[capacity]的一段内存,size <= capacity的元素有效;列表 capacity 按 2 的幂向上取整并维护空闲槽位,mmap 块按需调整大小。
阶段 6:从磁盘加载并搜索验证
合并后的索引写出为/tmp/populated.index(其中倒排列表指向merged_index.ivfdata)。搜索阶段:
index = faiss.read_index("/tmp/populated.index") index.nprobe = 16 xq = fvecs_read("sift1M/sift_query.fvecs") gt = ivecs_read("sift1M/sift_groundtruth.ivecs") D, I = index.search(xq, 5) recall_at_1 = (I[:, :1] == gt[:, :1]).sum() / float(xq.shape[0]) print("recall@1: %.3f" % recall_at_1)python demos/demo_ondisk_ivf.py 6验证方式就是对照sift_groundtruth.ivecs计算recall@1:脚本会打印一行recall@1: x.xxx(文档未给出固定预期值,以该数值判断检索精度是否符合预期)。nprobe 设为 16,即每条查询探测 16 个聚类,是 demo 给出的检索参数。
C++ 侧也有对应能力:OnDiskInvertedLists直接传给IndexIVFFlat::replace_invlists后即可 add 和 search,tests/test_ondisk_ivf.cpp 验证了 add + search 结果与内存版本一致,且write_index/read_index之后搜索结果不变。
限制与注意事项
- 增量 add 慢:faiss/invlists/OnDiskInvertedLists.h 注释明确 "Addition to the invlists is slow. For incremental add it is better to use a default ArrayInvertedLists object and convert it to an OnDisk with merge_from"。这就是 demo 先分块用普通 invlists add、再一次性 merge 的原因。
- 预取:头文件注释提到,当能预知一批将被访问的列表时,可以调用
prefetch_lists启动多个线程并行读取这些列表,降低随机 IO 延迟。 - mmap 读取依赖文件系统支持:
OnDiskInvertedLists通过 mmap 访问磁盘文件,索引文件与.ivfdata文件需要位于支持 mmap 的文件系统上。 - 搜索精度由 nprobe 等 IVF 常规参数控制,on-disk 存储本身不改变
IndexIVF的检索语义。
更大规模的延伸
demos/README.md 定位该 demo 为"stores the inverted file data on disk, eg. when it does not fit in RAM"。如果目标是 1T 向量级数据集,仓库中 benchs/distributed_ondisk/README.md 描述了分布式 on-disk 方案(多机构建垂直切片、merge_to_ondisk.py合并、combined_index.py组合查询),contrib/README.md 中的client_server.py则对应把数据集分片到多台机器的分布式索引用法,可作为 on-disk 单文件方案之后的下一步资料。
【免费下载链接】faissA library for efficient similarity search and clustering of dense vectors.项目地址: https://gitcode.com/GitHub_Trending/fa/faiss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考