Faiss 项目调研
调研日期:2026-09-17。本文基于官方仓库、Wiki、API 和论文,侧重技术选型与实现理解。尚未执行 Faiss 性能实验,容量估算和实验参数不代表实测结果。正式复现时应固定 release 或 commit,并记录编译选项。
1. 调研目标与初步判断
本次调研关注三个问题:Faiss 解决什么问题,主要索引如何取舍,以及怎样验证它是否适合实际业务与硬件。
Faiss 是由 Meta 主导开发的稠密向量相似度检索与聚类库,核心使用 C++,提供 Python/NumPy 接口,采用 MIT 许可证。它提供多种索引,允许在检索质量、速度、存储与构建成本之间取舍。官方仓库
本文的初步判断是:Faiss 适合作为向量检索算法基线、单机检索组件和底层性能研究对象。用于线上服务时,还需要围绕它设计数据管理、服务接口与运维机制。最终选型应由真实数据上的实验决定。
2. 在检索系统中的位置
以文档检索为例,可以将应用设计为以下流程:
离线:文档 → 切分 → embedding 模型 → 向量 → Faiss 索引
文档内容与业务 ID → 元数据存储
在线:查询 → 同一 embedding 模型 → Faiss top-k → ID 回查 → 重排 → 返回结果
这里的流程是应用架构建议。Faiss 接收向量并返回邻居 ID 与距离或分数;embedding 生成、原文存储和业务相关性判断需要其他组件承担。向量检索准确并不意味着 embedding 能正确表达业务语义,二者需要分别评估。官方简介
3. 距离度量与核心接口
| 检索目标 | 常见选择 | 注意事项 |
|---|---|---|
| 欧氏距离最近 | IndexFlatL2 |
返回平方 L2 距离,越小越近 |
| 内积最大 | IndexFlatIP |
返回内积分数,越大越相似 |
| 余弦相似度最大 | 归一化后使用内积索引 | 库向量和查询向量都要归一化 |
对非零单位向量,平方 L2 距离等于 2 - 2 × 内积,因此精确检索排序等价。未经归一化时,内积会受到库向量模长影响,不能直接当作余弦相似度。距离定义
核心生命周期是 train → add → search:训练阶段学习聚类中心、量化码本等索引参数,添加阶段写入向量,查询阶段返回近邻。Flat 不需要学习码本,IVF、PQ 通常需要先训练。索引训练与 embedding 模型训练是两个独立过程。入门教程
阅读源码时应留意 d、ntotal、is_trained、metric_type 等状态,以及不同子类对显式 ID、重建和删除的支持;基类存在某个接口不意味着所有实现均支持对应操作。Index 基类
4. 主要索引路线
| 索引 | 核心思路 | 主要代价 | 关注参数 |
|---|---|---|---|
| Flat | 比较全部原始向量 | 扫描量大 | 距离度量、批大小 |
| IVFFlat | 分桶后只扫描部分桶,桶内比较原始向量 | 可能漏掉未访问桶中的近邻 | nlist、nprobe |
| PQ | 将子向量编码为码本编号 | 量化导致距离误差 | 子空间数 M、每段位数 nbits |
| IVFPQ | IVF 缩小候选集,再使用 PQ 编码计算 | 候选遗漏与量化误差叠加 | nlist、nprobe、M、nbits |
| HNSWFlat | 沿分层近邻图探索候选 | 额外图内存与构建成本 | M、efConstruction、efSearch |
IVF 的 nlist 决定分桶数量,nprobe 决定查询访问多少桶;PQ 的 M 和 HNSW 的 M 含义不同。Flat 可作为精确结果基线;扫描所有压缩编码的 PQ 仍可能产生近似结果。索引说明
建议先跑 Flat,确认数据与指标正确,再按瓶颈选择候选:内存充足、需要 CPU 低延迟时评估 HNSW;希望调节扫描范围时评估 IVF;内存紧张时评估量化方案。数据规模本身不足以决定索引,还需要考虑查询次数、训练成本与召回目标。官方选型指南
5. 容量估算
以下只计算主要向量载荷,不包含分配器、训练工作区、码本、图结构和服务进程开销:
| 表示方式 | 每条向量主要存储量 |
|---|---|
| float32 Flat | 4 × d 字节 |
| IVFFlat | 4 × d + 8 字节,包含存储的 ID |
| IVFPQ | ceil(M × nbits / 8) + 8 字节 |
公式依据官方索引表。存储量说明
例如,一千万条 768 维向量的 float32 载荷为 10^7 × 768 × 4 = 30.72 GB,约 28.61 GiB。若使用 M=96、nbits=8 的 IVFPQ,编码及 ID 约为 10^7 × (96+8) = 1.04 GB,约 0.97 GiB。这是理论计算,不能直接当作进程内存峰值。
若需要原始向量精排,还必须计算原始向量的保留或外部读取成本。压缩索引变小,并不等于整个检索系统按相同比例缩小。
6. CPU/GPU 与性能关注点
CPU 距离计算可采用直接计算或 BLAS 路径。性能分析应分别观察距离计算、候选访问和 top-k 选择,并记录批大小及线程配置。实现说明
GPU 索引可接受主机或设备数据,并提供 CPU/GPU 索引转换接口。输入与索引位于同一 GPU 时可避免输入跨设备复制,但 CPU 与 GPU 索引的功能并非完全一致,应按目标版本核对具体 API。GPU 文档
当前官方仓库列出 CUDA、AMD ROCm 和可选 NVIDIA cuVS 后端;实际可用性取决于平台和构建配置。不能从“支持 GPU”推导出任意加速卡都能直接运行。构建与后端说明
针对性能研究或硬件移植,本文建议依次测量:
- Flat:分离距离计算与 top-k,建立正确性和吞吐基线。
- IVFFlat:加入粗量化、列表访问与候选合并,观察不规则访存。
- IVFPQ:测量查表、编码读取及精度损失。
- 端到端流程:加入数据搬运、批处理等待和结果回查,评估实际收益。
这是后续研究路线,不代表已经完成移植或获得加速结果。
7. 工程集成边界
| 问题 | 接入时需要确定的事项 |
|---|---|
| ID 管理 | 顺序 ID 如何映射业务主键,是否需要显式 ID |
| 更新与删除 | 目标索引支持哪些操作,外部映射如何维持一致 |
| 并发 | 区分并发只读与修改索引,单独设计 GPU 资源共享 |
| 持久化 | 索引文件、embedding 版本、业务映射和预处理参数一起管理 |
| 过滤 | 后过滤可能导致不足 k 条结果,需验证目标索引的过滤能力 |
官方 FAQ 说明 CPU 支持并发搜索,但并发搜索与添加、并发添加需要调用方同步;不要将该结论直接推广到 GPU。FAQ
Faiss 提供索引读写与克隆接口,GPU 索引写盘需要先转回 CPU。业务层仍需要设计一致性、版本切换与恢复流程。索引 IO
本文建议将 Faiss 作为服务中的检索组件评估。如果需求包含复杂元数据查询、权限、多副本和集群管理,应将外围系统的开发与维护成本一并计入选型。
8. 最小 API 示例
以下示例展示归一化后的内积精确检索。它是教学代码,本次未在文档构建环境中执行;运行时需另行安装 NumPy 和兼容的 Faiss CPU 包。Faiss 不属于本站构建依赖。
import numpy as np
import faiss
rng = np.random.default_rng(42)
xb = np.ascontiguousarray(rng.normal(size=(10_000, 128)), dtype=np.float32)
xq = np.ascontiguousarray(rng.normal(size=(100, 128)), dtype=np.float32)
# 实际数据应先检查 NaN、Inf 和零向量。
faiss.normalize_L2(xb)
faiss.normalize_L2(xq)
index = faiss.IndexFlatIP(xb.shape[1])
index.add(xb)
scores, ids = index.search(xq, 10)
assert ids.shape == (100, 10)
assert np.all((ids >= 0) & (ids < len(xb)))
print(ids[0], scores[0])
示例遵循官方 NumPy 输入及 add/search 用法。随机向量适合验证 API,不足以评估真实语义检索效果。入门教程
9. 可复现的实验方案
数据与环境
使用业务 embedding,分别准备训练集、入库集与查询集;存在时间分布变化时,保留较新的查询作评估。明确维度、距离、归一化、重复向量比例和过滤条件。
记录 Faiss 版本或 commit、硬件、线程数、BLAS、编译选项、数据摘要、随机种子和批大小。比较 CPU/GPU 时保持召回目标一致,并分别记录纯检索耗时和包含搬运的端到端耗时。
指标
本文将 Recall@k 定义为:对每条查询,近似结果与精确 top-k 集合的交集大小除以 k,再对查询取平均。参考集合由相同预处理及度量下的 Flat 生成,并统一并列距离的处理规则。
同时记录 QPS、请求延迟 p50/p95/p99、训练时间、入库时间、常驻及峰值内存、索引文件大小。批量总耗时除以查询数是摊销耗时,不能冒充单请求尾延迟。
首轮参数
以下是待验证的起点,需按数据与资源调整,不是通用最佳值。
| 方案 | 首轮参数 |
|---|---|
| Flat | batch = 1、32、256 |
| IVFFlat | nlist = 256、1024;nprobe = 1、4、16、64 |
| IVFPQ | 在 IVF 参数上选择能整除维度的 M;先用 nbits = 8 |
| HNSWFlat | M = 16、32;固定并记录 efConstruction,扫描 efSearch = 32、64、128 |
训练样本应足以支撑码本训练,并具有代表性。先以小规模验证流程,再扩大规模。建议比较固定内存预算下达到目标 Recall@k 的最低 p95,再观察吞吐。
| 索引及参数 | Recall@10 | p95 请求延迟 | QPS | 峰值内存 | 训练 / 入库时间 |
|---|---|---|---|---|---|
| Flat 基线 | 待测 | 待测 | 待测 | 待测 | 待测 |
| IVFFlat | 待测 | 待测 | 待测 | 待测 | 待测 |
| IVFPQ | 待测 | 待测 | 待测 | 待测 | 待测 |
| HNSWFlat | 待测 | 待测 | 待测 | 待测 | 待测 |
10. 源码阅读与后续工作
建议先从官方 tutorial 复现最小流程,再以 Index.h 为入口,沿具体索引的 train/add/search 实现阅读。随后结合 Implementation notes 定位计算路径,最后阅读 GPU 资源与索引实现。
整体设计可配合项目作者的论文 The Faiss library 阅读。源码笔记应固定 commit,并针对具体索引记录调用链。
后续优先完成三项工作:确认数据规模和延迟目标;跑通 Flat 与一种近似索引的召回对比;使用 profiler 确认瓶颈后,再决定 GPU 优化或其他硬件适配方向。本文目前完成了资料调研和实验设计,尚不能给出特定业务下的性能优胜结论。