对HNSW索引的一些理解

📅 2026/7/22 4:59:56 👁️ 阅读次数 📝 编程学习
对HNSW索引的一些理解

在学习milvus的过程中,遇到了可以通过HNSW索引方式进行collection的创建。

检索流程:

最高层入口节点 ↓ 在当前层寻找更接近 query_vector 的节点 ↓ 当前层找不到更近的节点时,停止当前层搜索 ↓ 把当前找到的最近节点作为下一层入口 ↓ 继续向下一层搜索 ↓ 最终进入第0层 ↓ 扩大候选范围,选出最终 Top K

为什么一定要进入最底层?
HNSW 的高层只包含部分向量节点,用来快速定位查询向量大致位于哪个区域。
第 0 层才包含 Collection 中的全部向量节点。
高层:节点较少,用于快速导航
底层:包含全部向量,用于产生最终 Top K
即使高层找到了一个相似度很高的节点,它周围可能还有更相似的向量,但这些向量只存在于更低层。

扩大候选范围是什么意思?

扩大候选范围指的是:进入第 0 层后,不再只沿着“当前最优的一个节点”向前移动,而是维护一组可能成为最终结果的候选节点,并继续探索这些候选节点周围的邻居。

还是有点抽象

为什么到了第0层还要继续探索,我直接从全部的collection中筛选候选范围,再在候选范围中返回top k不就好了吗?

第 0 层确实包含 Collection 中的全部向量节点,但 HNSW 到达第 0 层后,并不会立刻读取或比较全部节点。
然而
第 0 层的全部节点 ≠ 当前已经发现的候选节点

第 0 层包含全部向量
假设 Collection 有 100 万条向量:
第 0 层:包含全部 100 万个向量节点
第 1 层:包含其中一部分节点
第 2 层:包含更少的节点
但是这些节点通过图的边相互连接。HNSW 到达第 0 层时,只是落在其中一个入口节点附近:
第 0 层全部节点:100 万个
当前入口节点:A
此时算法并不知道全部 100 万个节点分别离查询向量多远。

HNSW 只能通过当前节点的邻接边发现其他节点。
例如第 0 层是:

A ─ B ─ C │ │ │ D ─ E ─ F │ │ │ G ─ H ─ I (这些横线和竖线表示为图的边)

从上层进入第 0 层时,入口可能是 A。
首先只能看到:
当前节点:A
A 的邻居:B、D
比较后发现 B 比较接近查询向量,于是继续查看 B 的邻居:
B 的邻居:A、C、E
再发现 E 更接近,于是继续查看:
E 的邻居:B、D、F、H
这里的“探索”具体指:
沿着第 0 层图中的邻接边,不断访问尚未检查的向量节点,并计算这些节点与 query_vector 的距离或相似度。

虽然 F、H、I 等节点都在第 0 层,但在沿图走到它们之前,算法还没有访问它们。
候选节点集合只是第 0 层的子集
假设第 0 层有 100 万个节点,搜索过程中可能只访问:
A、B、D、C、E、F、H、I……
共几百个节点。
这些已发现且可能成为最终结果的节点,叫作候选节点。
第 0 层全部节点:50 万个
搜索实际访问节点:例如 300 个
保留的候选节点:例如 ef=70 控制的一批较优节点
最终返回:例如 limit=10(top k k=10)

因此,更准确的流程是(进入第0层后):
进入包含全部向量节点的第 0 层

从上层给出的入口节点开始

沿邻接边发现附近节点

计算已发现节点与 query_vector 的距离

保留较优候选节点,并继续探索其邻居

继续探索难以改善结果时停止

返回 Top K
如果比较第 0 层全部节点会怎样
如果到达第 0 层后,把全部 50 万个向量都拿出来计算距离:
query_vector ↔ 第 1 个向量
query_vector ↔ 第 2 个向量
……
query_vector ↔ 第 50 万个向量
这就接近 FLAT 全量搜索,而不是 HNSW 的近似搜索了。
(FLAT 全量搜索 接近于对全文进行搜索)
HNSW 的价值恰恰在于:
第 0 层虽然包含全部向量
但查询只沿图访问其中一小部分
从而用较少的距离计算,近似找到真正的 Top K。

上层是否需要停止搜索,如何停止搜索?
HNSW 在较高层通常使用贪心搜索。
假设当前节点为 A:
query_vector 与 A 的相似度:0.80
A 的邻居 B:0.85
A 的邻居 C:0.70
因为 B 更接近查询向量,所以移动到 B。
接着比较 B 的邻居:
当前节点 B:0.85
邻居 D:0.82
邻居 E:0.79
没有邻居比 B 更接近查询向量,于是停止当前层搜索。

贪心搜索的目的,让搜索效率更快,在搜索至相邻节点未出现相似度更高的节点即停止搜索。

HNSW索引的一个关键参数:ef 候选范围最大值

ef的选择对检索的精确程度也起到了至关重要的作用。

总结:
HNSW索引检索时进入最底层的目的是在上层的入口节点为起始节点探索遍历ef范围内所有的与问题匹配的相似度作为候选范围,再从候选范围选择top k作为最接近我们预期的答案。