
RAG 响应变慢时,很多人的第一反应是把 Flat 换成 HNSW;检索不准时,又回头调大模型和 Prompt。其实向量索引同时影响召回率、延迟、内存和构建成本,脱离数据规模谈“哪种最好”没有意义。正确的做法是先建立精确检索基线,再看每一种近似方案到底牺牲了什么。
IndexFlatL2 会把查询向量和库中每个向量逐一比较,不做近似。它的优势是结果确定、无需训练、实现简单;缺点也直接:数据量上来后,计算量随向量数线性增长。
index = faiss.IndexFlatL2(dimension)
index.add(embeddings)
distances, ids = index.search(query_vector, 5)几十万向量以内、延迟又可接受时,Flat 往往就是最省心的选择。即使准备上近似索引,也应该保留 Flat 结果作为 Recall@K 的对照答案,否则“快了多少”很清楚,“丢了多少好文档”却没人知道。
IVF 先用聚类把向量空间划成 nlist 个区域,查询时只搜索最接近的 nprobe 个区域,而不是扫描全库。
quantizer = faiss.IndexFlatL2(dimension)
index = faiss.IndexIVFFlat(quantizer, dimension, 128)
index.train(embeddings) # 必须先训练
index.add(embeddings)
index.nprobe = 8 # 查得越多,召回越高、延迟也越高调参重点不是背一个固定数字,而是理解两个旋钮:nlist 影响分桶粒度和训练成本,nprobe 影响在线查询。线上排障时,如果相关文档恰好落在邻近桶而没被搜索,增大 nprobe 通常比换 Embedding 更直接。
LSH 的思路是让相似向量更可能进入相同哈希桶:
index = faiss.IndexLSH(dimension, 128)
index.add(embeddings)它轻量、容易理解,但 nbits 并不是越大越好。位数太少,大量不相关向量挤在一起;位数太多,真正相近的向量又可能被分开。对于语义 Embedding,LSH 未必能稳定保留模型学到的邻域结构,因此尤其需要用真实问题集验证召回,而不能只看检索耗时。
HNSW 把向量组织成分层图:上层稀疏,像高速公路一样快速接近目标;下层密集,负责局部精细搜索。
index = faiss.IndexHNSWFlat(dimension, 32) # M:每个节点的连接规模
index.hnsw.efConstruction = 200 # 构建时的搜索宽度
index.hnsw.efSearch = 64 # 查询时的搜索宽度
index.add(embeddings)M 增大通常提升图质量和内存占用;efConstruction 影响构建时间与图质量;efSearch 是线上召回和延迟的重要旋钮。HNSW 经常是大数据量下的均衡解,但“查询快”不代表整体成本最低——索引构建、更新和内存都要算进去。
索引 | 检索方式 | 主要优势 | 主要代价 |
|---|
Flat | 全量精确扫描 | 召回基准、无需训练 | 大数据量延迟高 |
|---|
IVF Flat | 聚类分桶后局部搜索 | 大规模下易调召回与速度 | 需训练,可能漏桶 |
|---|
LSH | 哈希分桶 | 轻量近似搜索 | 邻域保持依赖数据与位数 |
|---|
HNSW | 分层图搜索 | 召回、延迟通常较均衡 | 构建慢、内存开销高 |
|---|
靠谱的对比实验应固定文档、切分方式、Embedding 模型、查询集和生成模型,只替换索引。至少记录四项:P50/P95 检索延迟、相对 Flat 的 Recall@K、索引构建时间、进程内存。最后再观察最终答案,但不要只用“看起来差不多”评价召回。
还有一个边界要认清:FAISS 是向量索引库,不是完整向量数据库。元数据过滤、持久化、分布式部署、权限和文档生命周期仍需外围系统承担。选型时先确认自己需要的是底层检索能力,还是一套可运维的数据服务,再决定是否为 FAISS 补这些设施。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。