news 2026/9/11 22:28:13

向量数据集放不下内存时,faiss 怎么把 IVF 倒排列表存到磁盘并搜索?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
向量数据集放不下内存时,faiss 怎么把 IVF 倒排列表存到磁盘并搜索?

向量数据集放不下内存时,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 依赖faissfaiss.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.indexblock_0..3.indexmerged_index.ivfdatapopulated.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 4

demos/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 5

merge_ondisk内部逻辑(见 contrib/ondisk.py)值得注意的几点:

  1. 各分片索引用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。
  2. 目标索引必须为空:assert index.ntotal == 0, "works only on empty index"。所以阶段 5 读的是阶段 0 写的trained.index,而不是任何已 add 数据的索引。
  3. 新建faiss.OnDiskInvertedLists(nlist, code_size, ivfdata_fname),磁盘文件为merged_index.ivfdata,然后用merge_from_multiple把 4 个分片的倒排列表合并进去,最后replace_invlists(invlists, True)替换进索引。
  4. 每个分片设置index_ivf.own_invlists = False,避免 invlists 随分片索引析构被释放。
  5. 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 22:26:44

从菜鸟到高手:Python开发者必备的20个库,你都用过几个?

从刚接触Python时只会用print()和if-else&#xff0c;到如今能熟练搭建生产级应用&#xff0c;回头看看这些年&#xff0c;真正让我从“能写代码”蜕变为“能写好代码”的&#xff0c;是那些在实战中反复打磨的库。它们不是锦上添花&#xff0c;是吃饭的家伙。结合JetBrains 20…

作者头像 李华
网站建设 2026/9/11 22:20:27

用Python Requests爬数据?14个案例从入门到实战,新手也能看懂

要去爬取网页的数据, 还要调用 API 的接口, 对此能够堪称“神器”的库是存在的。其特点是一行代码就能够发起请求, 新手也能够快速上手。在今天整理出了 14 个实操案例, 这些案例覆盖了从基础到实战的全方面内容, 代码是直接复制就可以使用的, 所以要赶紧将其收藏起来&#xff…

作者头像 李华
网站建设 2026/9/11 22:19:56

从 TaskRegistry 到大厂架构:Python 高并发流式任务调度实战

如何在一个 Python Web 服务中&#xff0c;优雅地管理成千上万个流式任务&#xff0c;并实现精准的取消、断点恢复与分布式扩展。一、一个真实的业务场景 假设你在开发一个 AI 深度研报系统&#xff1a;用户提交一个复杂的研究课题&#xff08;如“分析2026年全球大模型开源生态…

作者头像 李华
网站建设 2026/9/11 22:19:25

Python在工业自动化领域的应用详解 , python和硬件结合可以做什么

围绕本篇文章, 来给大家说一说, 在工业自动化范畴里的应用详尽解析, 以及&#xff0c;跟硬件相互结合能够达成些什么样的事情, 所对应的重点知识领悟, 期望对各位能产生有益的协助, 可千万别忘记收藏下本网站呢。这是服务于不一样的应用场景的情况。三者具有相同之点, 即是都属…

作者头像 李华
网站建设 2026/9/11 22:19:22

后端技术栈演进:我亲手拆过的七个“过度设计”

做了这么多年后端开发&#xff0c;我拆过的“过度设计”比写过的业务代码还多。每一次拆除&#xff0c;都是一次认知升级。以下是我亲手拆过的七个典型“过度设计”&#xff0c;每一个都曾让我夜不能寐。一、为“微服务”而微服务三个人的团队&#xff0c;把项目拆成六个微服务…

作者头像 李华