在 RAG 检索链路中,向量数据库的索引层直接决定一件事:给定 query 向量,能否在可接受的延迟内把真正相关的 chunk 找回来。HNSW(Hierarchical Navigable Small World)是目前最常用的近似最近邻(ANN)索引之一,它把向量组织成一张多层图:检索从稀疏的上层开始快速逼近目标区域,再在稠密的下层做贪心搜索。所谓“调参”,本质是在构建成本、内存占用、查询延迟和召回率之间做取舍。
以下内容是算法层面的通用工程分析,不引用任何厂商的默认值与实现细节;实际可调参数名、默认值和取值范围,请以你所使用的向量数据库官方文档为准。
三个核心参数各管什么
M——图的结构宽度,主要影响内存与召回上限。 M 决定每个节点保留多少条邻居连接。M 越大,图的连通性越好,长距离跳转能力越强,相同 efSearch 下通常能拿到更高召回;代价是索引变大(图结构的内存开销大致与 M 成正比)、构建时间变长。在 HNSW 的原始设计中,除最底层外每层最多保留 M 个邻居,最底层保留更多(常见约定为 2M),底层连接数是内存占用的主要来源。M 属于一次性投入:索引建好后要改 M,只能重建。
efConstruction——构建期的候选队列长度,主要影响索引质量与构建耗时。 插入一个向量时,算法维护一个大小为 efConstruction 的动态候选列表,从中挑选要连接的邻居。efConstruction 越大,选出的邻居越接近真实最近邻,图的质量越好,后续查询能达到的召回上限越高;代价是构建更慢、构建期内存峰值更高。它不影响索引建好后的查询延迟。如果发现召回上不去,先确认 efConstruction 是否偏低,而不是一味调 efSearch。
efSearch——查询期的候选队列长度,主要影响召回率与查询延迟。 它不改变索引结构,只在每次查询时生效:候选列表越大,越不容易漏掉真正的近邻,但需要计算的距离次数更多,延迟随之上行。注意 efSearch 不能小于要返回的条数 k。实践中它是最常用的在线旋钮:可以在不改索引的前提下,按业务对召回和延迟的偏好调整,甚至按查询类型动态设置。
距离度量与维度:先定对,再谈调参
度量方式选错,参数怎么调都是错的。常见选择是欧氏距离(L2)、内积(IP)和余弦相似度。余弦与“对向量做 L2 归一化后的内积”等价;对已归一化的向量,L2 距离与余弦排序单调对应。关键原则是:距离度量必须与 embedding 模型的训练目标一致,否则“最近邻”不等于语义最近邻。
维度从两方面影响配置:一是内存,float32 下原始向量约占 N×d×4 字节,维度翻倍则向量部分内存翻倍;二是召回难度,维度越高,距离分布越集中,近邻与远邻的差距被压缩,达到同样召回往往需要更大的 M 或 efSearch。若内存紧张且模型支持降维输出,可以考虑降维后再建索引,但要重新测一遍召回。
内存受限时怎么配
先把内存账算清楚:总占用约等于向量本体(N×d×4 字节)加图结构(大致与 N×M 成正比),图结构往往是被忽略的大头。压缩顺序建议是:先降 M,它对内存的杠杆比 efSearch 大得多;再考虑对向量本体做量化压缩,收益直接但会损失召回,需要重新评估。efSearch 不占常驻内存,只影响延迟,所以内存紧张时优先牺牲 M 与量化精度,把 efSearch 保留为在线调节手段。
数据量增长时怎么配
数据量从十万级涨到百万级,即使参数不变,召回通常也会下降——图变大后,同样的 efSearch 覆盖的候选比例变小。应对方式是:M 适度调大以保证图的连通性,efConstruction 保持较高值(构建是一次性的,这里省不划算),efSearch 随数据量上调。如果删除和更新非常频繁,需要注意许多 HNSW 实现采用标记删除,长期运行后图结构会退化、召回下降,此时应规划定期重建或使用支持合并压缩的方案。
QPS 变化时怎么配
延迟预算等于单次查询的距离计算量除以可用算力。QPS 上升时,要么降 efSearch(牺牲召回换吞吐),要么提高并行度、增加副本。多副本读扩散是常见的横向扩展方式:每个副本持有完整索引,查询分散到各副本,efSearch 可以维持较高水平。反过来,如果 QPS 很低而召回不满意,直接拉高 efSearch 就是最廉价的优化。
一套可执行的调参顺序
- 固定距离度量,与 embedding 模型对齐,这一步不做妥协。
- 构建期先用较大的 M 和 efConstruction 建一版高质量基线,摸清召回上限。
- 用真实业务 query 集测 recall@k,同时记录 p95/p99 延迟,画出 efSearch 从低到高的召回—延迟曲线。
- 在曲线上选满足召回要求的最小 efSearch,用它的延迟判断是否达标;不达标再回头降 M 重建,换取更低内存和更快查询。
- 上线后按查询类型分档设置 efSearch,把延迟预算花在真正需要高召回的查询上。
参数没有全局最优解,只有针对你的数据分布、维度和延迟预算的局部最优。每次只改一个变量,用同一套评估集对比,才能知道改动值不值。