HNSW 深度解析:从图构建、搜索机制到企业级向量检索实践

0 阅读6分钟

一、先明确问题:HNSW 到底要解决什么?

在 RAG 系统中,文档通常先被切分成 Chunk,再由 Embedding 模型转换为向量。查询到来后,系统需要从大量文档向量中找出与查询最相关的 Top-K 个候选。

设数据库中有 (N) 个向量,每个向量维度为 (d)。最直接的方案是 Flat Search(暴力精确检索):计算查询向量与所有 (N) 个向量的距离,再选出最小的 K 个。

距离计算工作量大致随 O(N×d)\mathcal{O}(N \times d) 增长。它并非"无用的老方案":Flat Search 结构简单、结果精确,适合小数据集、批量矩阵计算、GPU 检索,以及作为近似检索的评测基准。但当数据量很大、查询延迟预算有限时,逐一比较所有向量可能过于昂贵。

HNSW 属于 近似最近邻搜索(Approximate Nearest Neighbor,ANN) 算法。它提前构建一张由向量节点组成的多层图,查询时只探索其中一部分节点,尽可能快速地找到真正近邻。

这里有一个必须先记住的区别:

  • 精确检索:保证返回目标距离度量下真正的 Top-K,但可能需要检查大量甚至全部向量。
  • 近似检索:试图用较少的距离计算找到足够好的 Top-K,通常能大幅降低搜索成本,但不保证每次都找到精确答案。

因此,HNSW 的目标不是"用数学保证把复杂度变成对数级",而是利用可导航的图结构,在实际数据上减少搜索工作量,并通过参数控制搜索范围与召回率。

二、先建立直觉:为什么用图,而不是只按距离排序?

2.1 把向量看成地图上的地点

可以把每个向量想象成地图上的一个地点。两个向量距离越近,表示它们在选定距离度量下越相似。HNSW 为部分地点建立道路,查询时从一个入口出发,沿道路寻找更接近目标的地点。

不过,这里有两个重要限制:

  1. 向量空间通常是高维空间,不是人能直接看见的二维地图。
  2. "相似的点之间有道路"不等于"只走最近的一条路就一定能到达全局最近点"。

如果所有道路都只连接极近的邻居,图可能形成许多局部紧密的小团块,跨区域导航会很困难。如果道路连接过少,图又可能不够连通或不够容易导航。因此,图的连接质量是 HNSW 性能的关键,而不仅仅是层数。

2.2 小世界:不是"最多六跳"

"小世界"通常用来描述网络中节点之间能够通过相对较短的路径连接。HNSW 借助邻居连接与分层导航,尝试让搜索能从图的一处快速移动到目标附近。

社会学中的"六度分隔"可以帮助理解这个概念,但它不是 HNSW 的算法定理。HNSW 没有"最多六跳就能找到答案"的固定保证。实际需要访问多少节点,取决于数据分布、图质量、搜索参数和实现细节。

三、HNSW 的整体结构:底层负责覆盖,高层负责导航

HNSW 全称为 Hierarchical Navigable Small World,即分层可导航小世界图。

它由多层图组成:

  • 第 0 层(底层):包含所有向量节点,承担最终的近邻搜索。
  • 第 1 层及更高层:只包含被随机分配到这些层的部分节点,越高层通常越稀疏。
  • 入口节点(entry point):查询通常从当前索引的最高层入口开始。
  • 边(edge):连接节点的图边,表示搜索可以从一个节点跳到另一个节点。边并不一定意味着两份文本在语义上"互相理解",它首先是一种服务于导航的索引结构。

可以把它想象成地图中的多级道路:

  • 底层像街道,覆盖所有地点,便于在目标附近精细搜索;
  • 高层像快速路和高速公路,只经过少数节点,帮助跨越较远区域;
  • 从高层到低层时,搜索位置逐步细化。

1.png

读图要点: 高层节点是底层节点的子集;查询从稀疏的高层开始导航,再逐层进入包含更多节点的底层。图中连线只用于展示层级与连接概念,不代表真实数据中的边分布。

这个比喻能解释设计意图,但不要把它理解为每次查询都能精准"飞抵目标区域"。高层只是提供更有效的起点和导航路径,仍可能把搜索带到并非最优的区域。

四、核心之一:节点为什么要随机分层?

4.1 随机层级分配

插入新向量时,HNSW 通常会按某种指数衰减分布随机决定它能出现的最高层级。经典形式可写为:

L=⌊−ln⁡(U)⋅mL⌋L = \lfloor -\ln(U) \cdot m_L \rfloor

其中:

  • (U) 是取值在 ((0,1)) 内的均匀随机变量;
  • (mLm_L) 是控制层级分布的参数;
  • (L) 是新节点被分配的最高层级。

这里的 (mLm_L) 与连接参数 M 不是一回事。具体实现可能使用不同的参数约定或等价形式,应以对应实现为准。

这种分布的关键性质是:层级越高,节点越少,而且数量大致按指数速度下降。 在上述连续分布近似下:

P(L≥k)≈e−k/mLP(L \ge k) \approx e^{-k/m_L}

因此,较高层节点会越来越稀疏,形成层级结构。最高层的期望高度通常随节点总量呈对数级增长。

4.2 为什么不直接把最重要的节点放到高层?

HNSW 一般不需要先理解每个向量的语义重要性,也不必全局聚类后再决定谁是"枢纽"。随机分层可以用较低的维护成本形成稀疏导航层。

注意:高层节点并不必然是业务上最重要、最热门或最具代表性的文档。它们是为了索引导航而被随机选中的节点。

4.3 随机分层不等于性能保证

对数级的层高描述的是层级结构的统计性质,并不能单独推出整个查询过程严格为 (O(log⁡N)O(\log N))。查询还要在每层访问多个节点;数据分布、连接质量和搜索宽度都会影响实际开销。

应避免的误解:"节点分层后,数据量增加 100 倍,查询延迟也一定只增加固定的几毫秒。"这不是 HNSW 的通用保证,必须通过目标数据和硬件上的基准测试验证。

五、核心之二:HNSW 如何插入一个新节点?

插入过程不是把新节点直接扔进某一层,然后只连接附近几个点。经典流程可以拆成以下步骤。

2.png

5.1 第一步:决定新节点的最高层

算法先为新节点随机抽取最高层级 (L)。假设新节点的最高层是第 2 层,那么它会出现在第 2、1、0 层,不会出现在第 3 层及以上。

5.2 第二步:从入口开始逐层导航

从当前索引的最高层入口出发,算法在高于新节点最高层的层级上进行搜索,找到更适合进入下一层的候选位置。

例如,新节点只存在于第 0 层,而索引最高层是第 4 层,那么算法可以先在第 4、3、2、1 层进行导航,再进入第 0 层进行邻居搜索。

在高层,经典 HNSW 插入流程通常使用较窄的贪心搜索:反复检查当前节点的邻居,如果发现距离新节点更近的节点,就移动过去;当邻居中没有更近的候选时,停止这一层的移动,并把得到的位置带到下一层。

这就是"上一层领路,下一层接着找"的来源。但更准确的说法是:上一层给下一层提供搜索起点,而不是把新节点准确定位到了最终位置。

5.3 第三步:在新节点所在层及以下层寻找候选邻居

当搜索到新节点最高层 (L) 后,算法开始在 (L) 到第 0 层寻找可连接的邻居。与高层导航不同,这一阶段通常会使用更宽的候选搜索,其候选规模由 efConstruction 等参数控制。

候选搜索不是只沿着单一路径前进。它会维护待探索的候选节点,并持续从中选择当前较有希望的节点,检查其邻居,直到满足停止条件。

可以把它想象成在地图上寻找附近的道路接入点:

  • 只看眼前一个地点,容易错过另一条更合适的路线;
  • 适度扩大候选范围,可以发现更好的接入点;
  • 候选范围越大,通常越有机会构建高质量的图,但建索引所需的距离计算和时间也会增加。

5.4 第四步:选择邻居,而不是机械地取最近的 M 个点

这是理解 HNSW 时特别重要、也经常被科普文章略过的部分。

image_175490713758485.png

假设新节点周围有以下候选:

  • A:离新节点最近;
  • B:与 A 几乎在同一个方向,距离也很近;
  • C:稍远一些,但位于另一个方向;
  • D:更远一些,能通往另一个区域。

如果只取距离最近的 M 个节点,最终连接可能大量集中在 A、B 这样的局部区域。虽然每条边都很短,但图未必容易从其他区域导航过来。

HNSW 的经典邻居选择启发式会考虑候选之间的相对距离,尽量避免所有连接都指向相互高度冗余的近邻。在一些常见实现中,候选点与新节点之间的距离,以及候选点与已选邻居之间的距离,都会影响是否保留这条连接。

直觉上,这有点像选道路接入点:不仅要考虑"离我近不近",还要考虑"这些道路是否都通往同一个方向"。保留一定的方向多样性,可能帮助搜索更容易跨越局部区域。

这只是对启发式目标的解释,并不意味着 HNSW 能保证图的全局最优。具体邻居选择规则应以论文和实现源码为准。

5.5 第五步:建立连接,并可能调整旧节点的邻接表

新节点被选入邻居集合后,算法会建立相应的图连接。经典实现中通常会把新节点与选中邻居连接,并在邻居一侧更新连接关系。

但一个节点的邻接表有容量上限。加入新连接后,旧节点可能超出该层允许的连接数,因此算法可能需要重新筛选其邻居,移除部分旧边。

所以,下面这种说法不准确:

新节点绝不会打扰或重新排布其他老节点。

更准确的是:

HNSW 插入通常只修改局部图结构,不需要对整个索引全局重建;但为了满足连接上限和邻居选择规则,局部旧节点的邻接关系可能被调整。

5.6 M 为什么会影响图质量?

M 控制每个节点可保留的邻接连接规模,但具体含义依实现而异。常见实现可能对底层与上层使用不同的连接上限,例如底层上限与 2M 相关、上层上限与 M 相关;不能将其视为所有数据库都统一采用的规则。

一般来说:

  • M 较小:图占用内存较少、构建可能更快,但连接不足可能影响可导航性和召回率。
  • M 较大:图拥有更多连接,可能改善搜索质量,但会增加图内存、构建成本和查询时需要检查的邻居数量。

M 不是越大越好。最佳值依数据集、距离度量、召回目标、内存预算和引擎实现而定。

六、核心之三:查询时究竟发生了什么?

理解查询过程,需要区分高层的贪心导航与底层的候选集搜索。

4.png

6.1 高层:贪心导航

给定查询向量 (q),算法从最高层入口节点开始。在当前层,它计算当前节点邻居与 (q) 的距离:

  • 如果某个邻居比当前节点更接近 (q),就移动到该邻居;
  • 如果没有邻居能进一步改善距离,就结束当前层搜索;
  • 把当前得到的节点作为下一层的起点。

这种策略简单、快速,适合在稀疏高层上逐步靠近查询区域。

但贪心搜索有局限:它只接受眼前能改善距离的移动,可能停在局部最优点。如果通往真正最近邻的路径需要先经过一个暂时更远的节点,单纯贪心就可能错过它。

6.2 底层:为什么不能只沿着一条路走?

第 0 层包含全部节点,最终 Top-K 通常在这一层搜索得到。经典 HNSW 的底层搜索一般会维护几个逻辑集合:

  1. 待探索候选集:已经发现、但其邻居还可能值得继续检查的节点。
  2. 当前结果集:当前最好的候选结果,通常限制在一定规模内。
  3. 已访问集合:避免反复访问同一个节点。

算法不断从待探索候选中取出当前距离查询向量较近的节点,检查它的邻居,并把有希望的节点加入候选与结果集合。当候选中最值得探索的节点已经不可能改善当前结果集时,或者达到实现定义的停止条件时,搜索结束。

这里的"结果集"在搜索过程中不一定就是最终返回给用户的 Top-K。它可能保留比 K 更多的候选,用来避免过早丢弃有价值的搜索路径;结束后再从结果中选出需要的 K 个结果。

6.3 efSearch:决定搜索视野有多宽的关键参数

efSearch 通常用于控制查询阶段的搜索宽度或候选规模。具体实现可能在细节上有所不同,但调优方向通常是:

  • 较小的 efSearch:检查节点较少,通常更快、资源消耗更低,但可能错过真正近邻。
  • 较大的 efSearch:探索更多候选,通常有机会提高召回率,但会增加距离计算、CPU 消耗和查询延迟。

efSearch 与最终返回的 K 不是同一个参数。比如请求 Top-10,内部搜索可以探索远多于 10 个候选节点。若搜索过程中只保留 10 个节点,可能过早丢弃有价值的替代路径。

可以用"侦察范围"来理解它:K 是最终要带回多少个结果,efSearch 是搜索过程中愿意调查多大范围。两者相关,但不能互相替代。

6.4 为什么 HNSW 会漏掉真正的最近邻?

可能原因包括:

  • 图的连接质量不足,通往目标区域的路线不理想;
  • efConstruction 较小,构建时没有充分探索候选,导致图质量受限;
  • M 较小,连接数不足;
  • efSearch 较小,查询时探索不够;
  • 数据分布、过滤条件或实现细节使导航更困难。

因此,HNSW 返回的是基于当前图和搜索配置找到的近似结果。它不会因为使用了分层结构就自动保证精确 Top-K。

七、三个参数如何一起影响结果?

HNSW 的常见核心参数可以按作用阶段理解:

参数主要作用阶段调大后的常见影响主要成本
M图构建与图结构连接更丰富,可能改善可导航性与召回率图内存、构建与查询开销
efConstruction插入建图邻居候选探索更充分,可能形成更好的图建索引更慢、构建计算量更大
efSearch查询搜索更广,通常有机会提高召回率查询延迟与 CPU 消耗增加

三者并不是可以简单互换的旋钮:

  • M 决定图结构能保留多少连接;
  • efConstruction 决定建图时有多认真地寻找连接对象;
  • efSearch 决定查询时愿意探索多少候选。

如果图本身质量较差,只把 efSearch 调得很大可能仍无法完全弥补。反过来,构建一个高质量图,也不意味着可以在极低搜索宽度下始终获得高召回。

常见的调优方法是先固定数据集、距离度量与评测集,再分阶段调参,而不是同时改变三个参数后凭感觉判断。

5.png 注意: 上图只是说明常见趋势的概念示意,不是任何数据库的实测结果。实际曲线可能因数据、硬件和实现不同而明显变化。

八、如何正确评估 HNSW:速度快不等于检索好

8.1 用 Flat Search 建立精确基准

要评估近似检索的召回效果,需要在相同数据集、向量、距离度量、过滤条件与查询集上,用精确检索获得基准 Top-K。然后比较 HNSW 返回的结果。

常见指标是 Recall@K:

Recall@K=∣AK∩EK∣K\mathrm{Recall@K} = \frac{\vert{}A_K \cap E_K\vert{}}{K}

其中:

  • (AKA_K) 是 ANN 返回的 K 个结果;
  • (EKE_K) 是精确检索返回的 K 个结果。

例如,精确检索的 Top-10 与 HNSW 的 Top-10 有 9 个结果重合,则该查询的 Recall@10 为 0.9。实际评测通常需要在大量代表性查询上统计平均值或分布,而不是只看单个样本。

Recall@K 衡量的是结果与精确近邻集合的重合程度,不等于业务语义相关性本身。一个系统即使准确复现了向量距离排名,也可能因为 Embedding 模型、文本切片或数据质量的问题而返回不相关内容。

8.2 不要只报告平均延迟

企业级检索至少应该关注:

  • Recall@K:近似结果与精确基准的重合程度;
  • P50 / P95 / P99 延迟:典型及尾部请求耗时;
  • QPS / 吞吐量:给定并发和硬件下每秒可处理的查询量;
  • 索引构建耗时:初次建图与增量写入的成本;
  • 内存占用:向量、图连接、元数据和运行时开销;
  • 更新可见性与恢复能力:写入多久可查、进程重启后如何恢复。

如果只报告"毫秒级",读者无法知道这是 1 毫秒还是 90 毫秒,也不知道它对应多少数据、多少并发、怎样的召回率。

8.3 建议的基准测试流程

  1. 固定一份代表性数据集和查询集,记录向量维度、数量、距离度量及数据来源。
  2. 用 Flat Search 为每条查询生成精确 Top-K。
  3. 构建 HNSW,逐步调整 M 与 efConstruction,记录构建时间和索引内存。
  4. 对每一种索引配置,改变 efSearch,统计 Recall@K、P50、P95、P99 和 QPS。
  5. 在目标并发量下重新测试,避免把单请求延迟当成生产吞吐能力。
  6. 加入真实的元数据过滤、权限约束、更新与删除操作,检查线上工作负载是否改变结果。
  7. 选取满足召回、延迟和内存约束的配置,而不是盲目追求单项最优。

九、企业知识库落地:HNSW 只是检索链路的一部分

9.1 内存预算不能只计算向量

假设有 100 万条向量,每条 1536 维,采用 float32,每个维度占 4 字节:

1,000,000×1536×4=6,144,000,000 bytes1{,}000{,}000 \times 1536 \times 4 = 6{,}144{,}000{,}000 \text{ bytes}

这约为 6.14 GB(十进制)或 5.72 GiB(二进制) 的原始向量数据。这个数字还没有包括图连接、节点元数据、原始文本、ID 映射、分配器开销、缓存以及数据库自身的内存开销。

因此,不能直接用"向量数 × 维度 × 4 字节"当成 HNSW 的完整内存预算。实际容量应通过目标引擎、目标参数和目标数据集测量,并为峰值构建、合并、缓存及并发请求预留空间。

9.2 PB 级数据不等于单个 HNSW 图可以轻松装下

PB 级通常是整个系统的数据存储规模,不意味着单个 HNSW 索引能把全部向量放入内存并轻松完成查询。超大规模系统通常需要考虑分片、数据分区、冷热分层、分布式路由和多阶段召回。

分片可以降低单个索引的规模,但也带来额外问题:查询要访问哪些分片、如何合并各分片结果、如何平衡热点,以及全局 Top-K 的质量如何评估。不能只凭 HNSW 单机算法就推导出 PB 级系统的整体性能。

9.3 元数据过滤与权限隔离

企业知识库往往需要按租户、部门、文档类型、时间或访问权限过滤。过滤和 ANN 的组合方式会影响召回率与延迟:

  • 有些系统会在 ANN 搜索过程中应用过滤条件;
  • 有些场景可能先做近似召回,再过滤候选;
  • 也可能需要分区、预过滤或其他混合策略。

如果先取少量近邻再过滤,可能出现大量候选被过滤掉,最终不足 K 条结果;如果为了补足结果不断扩大搜索范围,延迟又可能上升。

权限控制必须由可靠的数据访问机制保障,不能把"向量相似"当作用户有权查看文档的依据。过滤策略要用真实权限分布进行测试,不能只用无过滤的基准数据推断线上表现。

9.4 RAG 不只有向量检索

HNSW 负责的是向量近邻搜索,并不直接解决以下问题:

  • 文档切片是否合理;
  • Embedding 是否能表达业务语义;
  • 关键词、精确实体和编号是否需要额外检索;
  • 重排模型是否能把真正有用的证据排在前面;
  • 生成模型是否忠实使用检索证据。

因此,实际 RAG 链路可能组合向量召回、关键词召回、元数据过滤和 Rerank。最终答案的可靠性还取决于数据质量、检索覆盖率、证据组织与生成策略。HNSW 能提高检索效率,但不能保证"零幻觉"。

9.5 写入、刷新与持久化要看具体引擎

不能笼统地认为 Bulk 写入、异步写入或调整刷新间隔对所有向量引擎都具有相同效果。不同系统的索引构建、事务、可见性、后台合并、磁盘持久化和并发控制机制可能差别很大。

工程验证时应明确:

  • 使用哪个引擎及版本;
  • 是批量初始导入还是持续增量写入;
  • 写入后多久必须可查询;
  • 查询和写入同时发生时,延迟如何变化;
  • 删除、更新、重启与故障恢复如何处理;
  • 索引重建期间是否需要额外内存与磁盘空间。

对于 Elasticsearch、Milvus、Infinity 等不同系统,具体配置名称、默认值和行为都应以对应版本的官方文档为准,不能把某个引擎的参数定义直接套到另一个引擎上。

十、常见误区总结

误区 1:HNSW 查询复杂度一定是 (O(log N))。
层级高度通常随数据规模对数增长,但实际搜索还取决于每层访问节点数、图结构和搜索参数。应报告实测性能,而不是给出没有适用条件的复杂度保证。

误区 2:HNSW 总能找到真正的 Top-K。
它是近似最近邻算法,可能漏掉精确近邻。应使用 Recall@K 对照精确基准评估。

误区 3:M 越大,召回率一定越高。
更大的连接规模可能改善图质量,但效果取决于数据和实现,同时会增加内存及计算成本。

误区 4:efSearch 就是最终返回的 K。
K 是输出结果数量,efSearch 通常控制查询阶段的搜索宽度,两者作用不同。

误区 5:每个节点只需要连接最近的 M 个节点。
经典 HNSW 会使用邻居选择启发式,避免连接完全退化为局部、冗余的近邻集合。

误区 6:插入新节点不会影响旧节点。
插入通常只修改局部图结构,但可能调整邻居的连接关系。

误区 7:HNSW 解决了 RAG 的准确性问题。
HNSW 解决的是向量近似检索效率问题。语义相关性、权限隔离、证据质量和生成可靠性仍需要整条系统链路共同保障。

结语

HNSW 的关键不只是"把向量连成图",而是把三个机制结合起来:

  1. 随机分层:形成节点逐层稀疏的导航结构;
  2. 邻居选择与局部连接维护:让图在连接规模有限的条件下尽量保持可导航性;
  3. 分层导航与底层候选搜索:在有限计算预算内探索尽可能有价值的候选节点。

它的工程价值来自可调节的速度、召回率与资源之间的权衡,而不是无条件保证对数复杂度、亚毫秒延迟或精确 Top-K。真正的企业级落地,必须把算法理解、参数实验、容量规划、过滤与权限策略、并发写入和故障恢复放在同一个系统视角下评估。