从 RAG 检索效率瓶颈,到 HNSW 算法的深度解密

引言:好久不见,聊聊一次群聊引发的思考

嗨,大家好!好久没写博客了,最近一直在忙项目,但我的学习热情可没有减退。

前几天,在技术交流群里,大家讨论起了 ElasticsearchHNSW 算法的优化问题。这一聊,瞬间把我拉回了去年做的 RAG(检索增强生成)POC 项目。当时,为了实现多路召回,我选择了 Elasticsearch 8.0+,因为它既支持关键词检索,又能做向量检索,免去了引入过多组件的麻烦。

然而,在测试阶段,我很快遇到了一个效率上的瓶颈。当向量数据量逐步增长时,KNN(K-Nearest Neighbor)检索的性能开始急剧下降。我意识到,要解决这个难题,我必须深入理解 Elasticsearch 背后使用的核心算法——HNSW(Hierarchical Navigable Small World)。

这次群里的讨论让我觉得,这段经历很有分享的价值。所以,我整理了当时的学习笔记和实践心得,希望能帮助到同样在做向量检索优化的朋友们。


HNSW 核心思想:从“暴力检索”到“分层导航”

在深入细节之前,让我们先理解 HNSW 的核心思想。

传统的 KNN 检索,就像是在一个巨大的图书馆里,一本本、一排排地寻找你想要的书,这被称为“暴力检索”。当数据量巨大时,这种方法几乎无法使用。

而 HNSW 算法就像是给这个图书馆创建了一个高效的“索引系统”

  1. “小世界”网络: HNSW 建立了一个网络,让任意两点之间都能通过很少的步骤连接起来,就像社交网络中的“六度分隔理论”一样。
  2. “分层”结构: 这个网络不是平铺的,而是分层的。你可以把它想象成一个城市的地图:最高层是高速公路网,中间是主干道,最底层是具体的街道。

当我们需要检索时,我们从最高层开始,快速缩小搜索范围,然后逐层向下,最终在最底层的“街道”上找到最精确的结果。通过这种方式,HNSW 实现了在保证高准确性的同时,极大地提升了搜索速度。


深入 HNSW 内部:图的构建与查询

如果你和我一样好奇,HNSW 的“分层导航”到底是如何实现的?那这个部分就是为你准备的。我们将揭开 HNSW 图构建和查询的神秘面纱。

新节点如何加入图?

当你向 Elasticsearch 索引一个新文档(即一个新向量)时,HNSW 图的构建过程就开始了。这个过程就像是给社区添一个新邻居:

  1. 确定“楼层”: 算法会给新节点随机分配一个最高层级。这个分配是基于概率的,保证越低的层级,节点数量越多。低层级是容纳所有数据的“街道”,而高层级是数量稀少的“高速公路”。
  2. 寻找“最佳邻居”: 从图的最高层开始,算法会进行搜索,找到新节点在当前层级的最佳邻居。这个搜索的广度由 efConstruction 参数控制。
  3. 建立“社交圈”: 找到最佳邻居后,算法会与最近的 M 个邻居建立双向连接。这个过程会逐层重复,直到到达新节点被分配的最高层。

通过这个过程,新节点被高效地插入到图中,并建立起“捷径”,确保它能被快速找到。

一次查询是如何进行的?

当你提交一个查询向量时,HNSW 会利用已建好的图,以极快的速度找到最相似的结果。这个过程可以被分解成几个关键步骤:

  1. 确定“出发点”: 算法会从一个固定的、预先确定的入口点开始搜索。这个入口点是整个图的“起点”。
  2. 逐层“导航”: 算法会从最高层开始,利用查询向量与节点的距离,进行贪心搜索。它会从当前位置的邻居中,找到最接近查询向量的节点,并一步步朝目标方向移动。
  3. “深入小巷”: 这个过程会逐层向下进行。在每一层,算法都会找到一个更接近目标的节点,并把它作为下一层的搜索入口。这个搜索的广度由 efSearch 参数控制。
  4. 找到“宝藏”: 最终,当搜索到达最底层的“街道”时,算法会返回与查询向量最相似的 K 个结果。

通过这种“层层递进”的导航方式,HNSW 避免了暴力检索的巨大开销,实现了高效的近似最近邻搜索。


解密 HNSW 的三个关键参数

在 Elasticsearch 中,HNSW 的性能和准确性主要由三个关键参数决定。理解它们,你就掌握了调优的“钥匙”。

  • M:每个节点的最大连接数
    • 作用: M 控制了每个节点在构建图时可以连接的邻居数量。
    • 权衡: M 值越大,图的连接越密集,搜索路径选择越多,准确性越高;但同时,索引占用的内存和构建时间也越大
  • efConstruction:构建时的候选池大小
    • 作用: efConstruction 控制了在索引构建时,算法寻找最佳邻居的广度。
    • 权衡: efConstruction 值越大,构建时的搜索越彻底,生成的图质量越高,搜索准确性越高;但相应地,索引构建时间也会显著增加
  • efSearch:搜索时的候选池大小
    • 作用: efSearch 控制了在搜索查询时,算法在每一层进行贪心搜索的广度。
    • 权衡: efSearch 值越大,搜索的广度越大,召回率(准确性)越高;但搜索时间也会相应增加

我的实践心得与调优之旅

理解了这些参数后,我开始对我的 Elasticsearch 集群进行调优。

最初,为了追求快速索引,我把 MefConstruction 设置得比较小。结果是索引速度很快,但查询的召回率很低,很多相关的结果都未能被检索到。

后来,我根据业务需求重新进行了权衡:我的 RAG 系统对语义检索的准确性要求很高,宁可牺牲一些索引速度,也要保证检索质量。于是,我逐步增大了 MefConstruction 的值。

这个权衡过程,让我联想到了在其他业务场景中的案例:

  • 电商商品检索: 在电商平台中,我们需要根据用户的模糊查询(如“红色复古连衣裙”)快速找到最相似的商品。这对搜索的召回率和准确性要求很高。在这种场景下,我们通常会设置相对较大的 MefConstruction,以保证搜索结果的相关性,即使这意味着更长的索引构建时间。
  • 视频推荐系统: 对于像 YouTube 这样的视频平台,每天都有海量的新视频上传,实时索引的效率至关重要。同时,用户对推荐的“相关性”也有很高要求。这种场景下,我们通常会找到一个平衡点,可能会略微牺牲一些召回率,将 efConstruction 设置得适中,以确保索引速度能够跟上数据增长。

最终,在经过多次测试后,我找到了一个平衡点。在保证搜索延迟在可接受范围内的同时,我的召回率得到了显著提升,RAG 系统的效果也随之改善。

这个过程让我深刻体会到:没有完美的参数配置,只有最适合你业务需求的权衡


结语:HNSW 不仅仅是算法,更是工程智慧

通过这次深入学习,我不仅解决了 RAG 项目中的一个关键效率问题,更对高维向量检索技术有了全新的认识。HNSW 算法本身就是一种工程智慧的结晶,它巧妙地在速度、准确性和资源消耗之间找到了平衡。

希望我的这段经历也能对你有所启发。如果你也在使用向量检索,不妨花点时间去了解它背后的算法原理,相信这会让你对自己的系统有更深的掌控。