5183篇文档,HNSW竟输给暴力检索

一项近期实测显示,在仅5183篇文档的数据集上,FAISS Flat精确检索比FAISS HNSW快1.36倍。结论不是HNSW失效,而是小数据集根本未必值得为近似索引付出图遍历成本。
小数据集上,复杂索引未必更快
近日,一位开发者从零实现了包含 BM25、HNSW 和 RRF 的混合检索引擎,并用 FAISS、bm25s 与 rank_bm25 做对照测试,结果出现了一个很容易被向量数据库宣传材料忽略的现象:在只有 5183 篇文档的 SciFact 数据集上,FAISS 的 Flat 精确检索延迟为 0.237 毫秒,反而比 HNSW 的 0.323 毫秒更快。
截至 2026 年 8 月 26 日,这项测试最值得开发者注意的地方,不是“手写 HNSW 输给 FAISS”,而是FAISS 自己的 HNSW 也在小规模数据集上输给了 FAISS Flat。这排除了相当一部分语言和工程实现差异,说明 HNSW 的图索引开销在数据规模不足时,确实可能盖过它少算距离所节省的时间。
HNSW 是一种基于分层小世界图的近似最近邻检索算法,全称为 Hierarchical Navigable Small World。它将向量组织成多层图:上层节点稀疏,负责快速跨越较远区域;底层节点密集,负责在局部寻找近邻,工作方式可以类比“先走高速公路,再进入城市道路”。
Flat 精确检索是将查询向量与库中全部向量逐一计算距离,再返回得分最高的 top-k 结果。它的计算量随向量数量近似线性增长,但数据连续存放、访问模式规整,特别适合由 C++ SIMD 指令或 GPU 批量执行,因此“小而规整的全量扫描”可能比“少算一些、但需要不断跳指针的图遍历”更快。

四组数据把问题说清楚了
这次测试覆盖 NFCorpus 和 SciFact 两个小型语料集,文档数量分别为 3633 篇和 5183 篇。测试者对比了 FAISS Flat、FAISS HNSW、自研暴力检索 mini-brute,以及纯 Python 实现的 mini-hnsw,公布的单次查询中位延迟如下。
| 系统 | 检索类型 | 关键配置 | NFCorpus:3633篇 | SciFact:5183篇 | |---|---|---:|---:|---:| | FAISS Flat | 精确检索 | 全量扫描 | 0.153 ms | 0.237 ms | | FAISS HNSW | 近似检索 | M=16,efSearch=256 | 0.135 ms | 0.323 ms | | mini-brute | 精确检索 | 自研实现 | 0.295 ms | 0.410 ms | | mini-hnsw | 近似检索 | M=16,efSearch=256 | 3.227 ms | 7.517 ms |
最显眼的差距来自纯 Python 图遍历。在 NFCorpus 上,mini-hnsw 的 3.227 毫秒是 mini-brute 0.295 毫秒的 10.9 倍;在 SciFact 上,mini-hnsw 的 7.517 毫秒是 mini-brute 0.410 毫秒的 18.3 倍。
纯 Python 的结果不能直接用来否定 HNSW,因为它首先暴露的是解释器执行图算法的成本。HNSW 查询需要维护候选集合、访问集合和优先队列,还要频繁读取不连续的邻接节点;这些操作在 Python 中会叠加对象分配、哈希查询、函数调用和动态类型检查,而暴力检索更容易被改写成连续数组上的向量化计算。
真正有价值的对照来自同一套 FAISS 实现。在 SciFact 上,FAISS Flat 的 0.237 毫秒比 FAISS HNSW 的 0.323 毫秒低 0.086 毫秒,换算下来,HNSW 延迟是 Flat 的 1.36 倍,或者说 Flat 的延迟低约 26.6%。
NFCorpus 的结果则没有形成足够稳定的领先优势。FAISS HNSW 的中位延迟为 0.135 毫秒,表面上比 Flat 的 0.153 毫秒快约 11.8%,但 HNSW 多次运行之间的波动达到 0.023 毫秒,已经超过两者均值约 0.019 毫秒的差距;两者 p95 延迟也只有 0.207 毫秒与 0.209 毫秒之差。
这意味着在 3633 篇文档上争论谁快 0.02 毫秒,工程意义非常有限。如此小的差距可能被 CPU 频率变化、缓存冷热、线程调度和测量框架开销轻易淹没,而 HNSW 还需要额外承担索引构建、图结构存储和参数调优成本。
这不是“HNSW不行”,而是规模还没到
这项测试不能推出“HNSW 总是比暴力检索慢”,它只能说明 5183 篇文档不足以保证 HNSW 获胜。算法复杂度描述的是规模增长趋势,并不代表复杂算法从第一个数据点开始就更快。
HNSW 的核心收益是减少需要计算距离的候选数量。在数十万、数百万甚至更多向量上,全量扫描的距离计算会随数据规模持续增长,而 HNSW 通常只探索图中的一部分节点,因此能把高召回率检索控制在更低延迟内;但在 5183 个向量附近,一次连续扫描本身已经能在亚毫秒内完成,图搜索节省下来的计算量很难抵消随机内存访问和候选队列维护。
HNSW 常被概括为具有接近对数级的实际搜索表现,但这并不是对所有数据分布都成立的严格最坏情况保证。向量维度、数据聚类程度、距离度量、M、efConstruction、efSearch、目标召回率以及硬件缓存都会改变最终结果,仅用文档数量判断切换点并不可靠。
FAISS 是 Meta 开源的高性能向量相似度搜索与聚类库,而 HNSW 是 FAISS 支持的一类索引算法。把“FAISS 和 HNSW”直接列成两个互斥竞品并不准确,更合理的比较对象是 IndexFlat 与 IndexHNSWFlat,前者负责精确全量扫描,后者在保留原始向量的同时使用 HNSW 图进行候选导航。
M=16、efSearch=256意味着什么
M 是 HNSW 图中每个节点连接规模的核心参数,它影响图的连通性、构建成本、内存占用和搜索效果。M 越大,图通常越容易找到高质量近邻,但每个节点需要保存更多边,查询时可能检查的邻居也更多。
efSearch 是 HNSW 在查询阶段探索候选节点数量的参数,它直接控制速度与召回率之间的交换。efSearch 越高,搜索越接近精确结果,但需要访问更多节点;efSearch 越低,延迟通常越小,却更可能漏掉真实近邻。
本次 FAISS HNSW 使用的是 M=16、efSearch=256,这是一组明显偏向高召回率的配置。对于只有 5183 个数据点的 SciFact,efSearch=256 意味着搜索候选规模已经相当于数据集的约 4.9%,再叠加图遍历、去重和优先队列操作,HNSW 的优势自然被压缩。
参数偏保守并不代表测试没有价值,因为生产 RAG 系统往往不能只追求最低延迟。若把 efSearch 从 256 降到 32,HNSW 可能明显提速,但 Recall@k 也可能下降;如果测试只报告延迟、不报告召回率,就可能用“漏掉更多正确结果”换来一个看起来更漂亮的数字。
| 维度 | FAISS Flat | FAISS HNSW | |---|---|---| | 检索性质 | 精确最近邻 | 近似最近邻 | | 查询方式 | 扫描全部向量 | 沿分层图探索候选节点 | | 主要参数 | 距离类型、top-k | M、efConstruction、efSearch | | 召回率 | 在同一距离定义下为精确结果 | 取决于参数和数据分布 | | 构建成本 | 低,通常无需训练 | 需要构建邻接图 | | 额外内存 | 主要是原始向量 | 原始向量加图连接 | | 增量插入 | 可追加,但查询仍全量扫描 | 通常支持动态插入 | | 更适合的规模 | 小型语料、强精确需求 | 中大型语料、低延迟高召回需求 |
文档数量不是唯一尺度,切块后可能完全不同
“5183 篇文档”不必然等于“5183 个向量”。RAG 是通过检索外部知识片段,为生成模型提供上下文的系统架构;在真实 RAG 中,一篇长文档往往会被拆成多个 chunk,每个 chunk 独立生成向量。
切块策略会直接改变索引规模。假设 5183 篇文档平均拆成 10 个片段,向量数量就会变成 51830;若每篇拆成 50 个片段,则会达到 259150 个向量,此时 Flat 与 HNSW 的延迟关系很可能重新洗牌。
向量维度同样会改变分界点。扫描 5183 个 384 维向量,与扫描 5183 个 3072 维向量不是同一工作量;维度增加 8 倍,单次精确距离计算的数据读取量和乘加操作也会显著增加,而 HNSW 如果能减少实际访问节点数,就更容易体现价值。
批量查询也可能让 Flat 获得额外优势。Flat 可以把多个查询组织成矩阵运算,充分利用 SIMD、BLAS 或 GPU 吞吐;HNSW 的每个查询路径具有数据依赖和分支跳转,批处理效率通常没有连续矩阵乘法那么理想。
对RAG开发者的直接建议:先测Flat,再谈索引
小型知识库默认上 HNSW,往往是一种没有经过测量的过度设计。对于内部制度库、产品手册、代码仓库摘要和小型客服知识库,如果切块后只有数千到数万个向量,FAISS Flat 应该成为基准线,而不是被预先淘汰的“低级方案”。
更稳妥的选型流程应该包含以下几步:
- 先固定召回质量。 用 Flat 生成精确 top-k,作为评估 HNSW Recall@k 的基准答案。
- 同时测 p50、p95 和 p99。 单看中位数无法发现图遍历的尾延迟和运行波动。
- 把索引构建时间算进去。 数据频繁更新时,查询快 0.1 毫秒未必能抵消持续维护图索引的成本。
- 记录常驻内存。 HNSW 除了向量本体还需要保存邻接关系,内存压力通常高于 Flat。
- 逐级扫描 efSearch。 例如从 16、32、64、128 到 256,绘制延迟—召回率曲线,而不是只测一个参数点。
- 使用真实查询分布。 随机向量、公开数据集查询和线上用户问题,可能对应完全不同的图搜索难度。
- 把端到端延迟单独统计。 向量生成、网络传输、重排模型和大模型推理经常比亚毫秒级检索慢几个数量级。
端到端视角尤其重要。如果一次 RAG 请求需要 30 毫秒生成查询向量、20 毫秒访问服务、80 毫秒进行重排,再等待数百毫秒获得模型首 token,那么把向量检索从 0.237 毫秒优化到 0.135 毫秒,对用户体验几乎没有可感知影响。
混合检索的价值,可能比换索引更大
BM25 是一种基于词频、逆文档频率和文档长度归一化的经典关键词相关性算法。它擅长处理产品型号、报错字符串、人名和精确术语,而纯向量检索更擅长语义改写和近义表达。
RRF 是一种通过结果排名而非原始分数融合多个检索列表的方法,全称为 Reciprocal Rank Fusion。它能绕开 BM25 分数与向量相似度不在同一尺度的问题,让关键词结果和语义结果以相对稳定的方式合并。
这位开发者从零实现的不只是 HNSW,还包括手写倒排索引上的 BM25 和用于融合的 RRF。对于只有数千篇文档的知识库,这种“BM25 + Flat + RRF”的简单组合,可能比“只用 HNSW 向量检索”更值得优先尝试:它保留精确向量召回,又能补上专有名词和字面匹配,同时避免维护复杂图索引。
这次实测仍有几个边界需要补齐
这组数字适合用来反驳“HNSW 在任何规模都更快”,但还不足以给出一条通用的 5183 文档分界线。原帖摘要没有完整列出硬件型号、向量维度、距离度量、查询数量、线程设置、预热方式和 Recall@k,这些因素都可能影响亚毫秒级结果。
不同实现之间也不能只按算法名称横向比较。mini-hnsw 是纯 Python 图遍历,FAISS HNSW 的核心执行路径则是经过优化的 C++;mini-brute 与 FAISS Flat 同为精确检索,但后者在连续内存布局、SIMD 和底层循环优化方面更成熟,因此 FAISS Flat 分别比 mini-brute 快约 1.93 倍和 1.73 倍并不意外。
更完整的后续测试应当固定 Recall@10 或 Recall@100,再比较不同 efSearch 下的延迟,并把向量规模从 5000 扩展到 1 万、5 万、10 万、100 万。只有画出不同硬件与维度下的交叉曲线,才能回答“从多少向量开始 HNSW 值得用”,而这个答案很可能不是一个固定数字。
判断:Flat不是落后方案,而是小数据集的理性默认
这次测试给出的最实用结论是:**索引越复杂,并不代表检索越快;在数据足够小的时候,暴力扫描反而最接近硬件喜欢的工作方式。**连续数组、批量计算和极少的控制分支,让 Flat 可以把“算法上多做的工作”变成“硬件上更容易做的工作”。
HNSW 仍然是 CPU 近似向量搜索中成熟而强力的方案,尤其适合向量规模持续增长、需要动态插入、又要求较高召回率的场景。但在只有几千个向量时,它可能只是给亚毫秒查询增加一套图结构、几个参数和更多内存占用。
开发者真正应该问的不是“HNSW 和 FAISS 谁更强”,而是三个更具体的问题:当前到底有多少个向量、目标 Recall@k 是多少、端到端瓶颈究竟在哪里。若这三个问题还没有数据支撑,先用 FAISS Flat 建立精确、简单、可复现的基线,通常比直接堆上 HNSW 更专业。
参考来源
- Reddit:HNSW from scratch, benchmarked against FAISS:本次测试的原始讨论,包含 NFCorpus、SciFact 与四种实现的中位延迟数据。
- GitHub:Meta FAISS 官方仓库:FAISS 的官方源代码、索引实现与文档入口。
- GitHub:FAISS indexes 说明:介绍 IndexFlat、HNSW 等索引结构及其主要特征。
- GitHub:hnswlib 官方仓库:常用 HNSW C++ 实现,可用于理解 M、efConstruction 和 efSearch 等参数。
- 知乎:HNSW算法实战:补充介绍 HNSW 的图结构、参数调优和中大型数据集使用场景。



