AI 快讯5183篇文档,HNSW竟输给暴力检索
开发心得

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

2026-08-26T20:04:13.406Z
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 批量执行,因此“小而规整的全量扫描”可能比“少算一些、但需要不断跳指针的图遍历”更快。

FAISS Flat全量扫描与HNSW分层图搜索路径对比示意图

四组数据把问题说清楚了

这次测试覆盖 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”直接列成两个互斥竞品并不准确,更合理的比较对象是 IndexFlatIndexHNSWFlat,前者负责精确全量扫描,后者在保留原始向量的同时使用 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 应该成为基准线,而不是被预先淘汰的“低级方案”。

更稳妥的选型流程应该包含以下几步:

  1. 先固定召回质量。 用 Flat 生成精确 top-k,作为评估 HNSW Recall@k 的基准答案。
  2. 同时测 p50、p95 和 p99。 单看中位数无法发现图遍历的尾延迟和运行波动。
  3. 把索引构建时间算进去。 数据频繁更新时,查询快 0.1 毫秒未必能抵消持续维护图索引的成本。
  4. 记录常驻内存。 HNSW 除了向量本体还需要保存邻接关系,内存压力通常高于 Flat。
  5. 逐级扫描 efSearch。 例如从 16、32、64、128 到 256,绘制延迟—召回率曲线,而不是只测一个参数点。
  6. 使用真实查询分布。 随机向量、公开数据集查询和线上用户问题,可能对应完全不同的图搜索难度。
  7. 把端到端延迟单独统计。 向量生成、网络传输、重排模型和大模型推理经常比亚毫秒级检索慢几个数量级。

端到端视角尤其重要。如果一次 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 更专业。

参考来源

相关推荐

查看全部