5.3 索引参数优化


文档摘要

5.3 索引参数优化 执行流程与重排就位后,本节处理被问得最多的问题:索引参数到底怎么调。承接第三章的参数语义(倒排的聚类数与探测数、图的连接度与候选队列)与 5.5 将要建立的评估方法,本节给出的是一套流程而不是一张参数表——参数表会过时,流程不会。读完你应当能把"探测宽度怎么选、候选队列设多大"这类问题,变成一条带数据回路的决策过程。 先分清结构参数与运行参数 第三章划过这条线,调参时它决定你的自由度:结构参数(倒排的聚类数、图的连接度、量化码本大小)在构建期定死,改动等于重建,决策依据是内存预算与数据规模,一年调不了几次;运行参数(倒排的探测数、图的候选队列长度、量化重排的候选倍数)每次查询可带,是日常调优的全部空间。

5.3 索引参数优化

执行流程与重排就位后,本节处理被问得最多的问题:索引参数到底怎么调。承接第三章的参数语义(倒排的聚类数与探测数、图的连接度与候选队列)与 5.5 将要建立的评估方法,本节给出的是一套流程而不是一张参数表——参数表会过时,流程不会。读完你应当能把"探测宽度怎么选、候选队列设多大"这类问题,变成一条带数据回路的决策过程。

先分清结构参数与运行参数

第三章划过这条线,调参时它决定你的自由度:结构参数(倒排的聚类数、图的连接度、量化码本大小)在构建期定死,改动等于重建,决策依据是内存预算与数据规模,一年调不了几次;运行参数(倒排的探测数、图的候选队列长度、量化重排的候选倍数)每次查询可带,是日常调优的全部空间。把两者分开的另一个理由是事故域不同:运行参数调错,最坏是延迟毛刺,改回来即恢复;结构参数调错(比如聚类数严重偏离数据规模),可能要全量重建数小时才能恢复。所以调优顺序永远先动运行参数,结构参数只在运行参数穷尽后按重建流程(3.3 的双索引切换)处理。

权衡象限:每个参数住在哪个象限

图 5-2 索引参数的权衡象限

图 5-2 索引参数的权衡象限

象限图回答"每个参数的代价是什么",但"调到多少"必须由数据回答。常见业务锚点是:召回率目标九成五到九成九之间(按业务对漏检的容忍度定),尾延迟目标按上游接口预算倒推。两者定下后,调参就变成了找满足双约束的最低成本配置——不追求最高召回,也不追求最低延迟,追求达标线上的最省配置。

一套带回路的调参流程

流程分四步。第一步备料:固定一批探针查询(从真实查询日志采样数百到数千条),预先用精确检索算好每条的标准答案,这份"答案卷"是所有后续测量的基准,3.1 说过平面基线的价值正在于此。第二步测起点:当前配置下跑一遍探针,记录召回率、平均延迟、尾延迟三个数。第三步单变量扫描:每次只动一个参数,按倍数阶梯取值,记录同三个数。第四步选点:在召回达标的前提下取延迟最低的点,留出一成余量防抖动。整个过程可以全自动:

import numpy as np, time def evaluate(index, probes, truths, ef): index.hnsw.efSearch = ef # 只动这一个旋钮 hits, latency = 0, [] for q, truth in zip(probes, truths): t0 = time.perf_counter() ids = index.search(q, top_k=10) latency.append(time.perf_counter() - t0) hits += len(set(ids) & set(truth)) # 与精确答案求交集 recall = hits / (len(probes) * 10) p99 = float(np.percentile(latency, 99)) return recall, p99 for ef in [16, 32, 64, 128, 256]: r, p = evaluate(idx, probes, answers, ef) print(f"候选队列 {ef:>4} 召回 {r:.3f} 尾延迟 {p*1000:6.1f} ms") # 选召回首过 0.95 的最小档,再留一成余量

这段扫描脚本有两个容易被忽略的讲究:探针查询必须来自真实流量而不是随机向量——随机向量的距离分布与真实业务完全不同,测出的召回率没有意义;延迟要看尾延迟不要只看平均——检索是扇出架构,个别慢请求的比例决定了用户体验的下限,5.5 会把尾延迟讲透。

三个高频误区

调参数年的人大致都会踩过同一批坑,提前列出。误区一:在数据集上调、在数据分布变化后不重调。数据量翻倍后最优参数会移动,把参数扫描做成季度例行任务比一次性调优靠谱。误区二:只调索引不问上游。很多时候延迟大头在嵌入服务的编码耗时或网络往返,索引参数扫到天上去也无济于事——先做延迟分解(5.1 的路径分段计时)再动手。误区三:用召回率替代业务指标。召回达标不等于用户满意,最终还要回到点击率、答案采纳率这类业务指标上做闭环,重排参数(5.2)同理。

常用参数速查与初值

调参流程需要起点,下表给出主流参数的语义与经验初值(初值只是起点,最终以扫描结果为准):

参数 层面 语义 经验初值
聚类数 nlist 结构 倒排簇的个数 约为条数平方根的四倍附近
探测数 nprobe 运行 每次查询访问的簇数 从十六扫到二百五十六
连接度 M 结构 图中每点连边数 十六到四十八
候选队列 ef 运行 每层保留的候选宽度 从六十四扫到五百一十二
量化子段数 结构 码本切分粒度 每子段八到十六维
精修倍数 运行 精修候选相对 K 的倍数 四到十六倍

表中值得点破的是运行参数与结构参数的扫描成本差异:运行参数扫描只需要改请求参数跑基准,一轮实验以分钟计;结构参数每档都要重建索引,一轮以小时计。所以扫描永远先穷尽运行参数,把"同样结构下的最优运行点"找到,再用 7.3 的四问评估是否真的需要动结构。

问题:参数在数据量增长后要跟着调吗?

要,且变化有方向可循。数据量翻倍后,同样的探测数或候选队列覆盖的数据比例减半,召回率会缓慢下滑——这是"参数的相对性":它们多数是比例敏感而非绝对值敏感的量。工程上的对策是把参数与规模挂钩:数据每上一个数量级,重跑一轮运行参数扫描;规模在数量级内小幅增长时,用召回代理指标(6.3 的定时探针)盯住下滑趋势,跌破阈值即触发复扫。把"规模变化触发复扫"写进运维手册,参数就不会在无声中过期。

问题:多租户共用索引时,参数要分租户调吗?

理想与现实之间要找平衡。参数在索引层面是全局的,不同租户的查询分布却可能差异巨大——有的租户全是长文本模糊查询,有的全是型号精确查找,同一组参数对一家最优、对另一家平庸。可行的缓解按投入递增:监控按租户拆分召回代理指标,发现某租户显著掉队再定向处理;对差异极大的头部租户单独建集合(用空间换参数自由);把租户间的分布差异纳入建库前的容量与隔离评审。别追求让所有租户共享一套"平均最优"参数——平均最优往往意味着每家都不最优。

本节要点回顾

  • 结构参数管内存与召回上限,运行参数管日常调优,先动后者。
  • 调参四步:备答案卷、测基线、单变量扫描、达标线上取最省配置。
  • 探针查询必须采样自真实流量,随机向量测不出有效召回。
  • 延迟看尾延迟,召回看与精确答案的交集,两者都要留余量。
  • 数据分布变化后要重调,动索引前先做延迟分解排除上游瓶颈。

参数的软杠杆用尽了,下一节上硬杠杆:硬件与缓存。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U