这道题其实是在问,当我们要在海量向量里做[[近似最近邻]]搜索时,怎么在检索质量、查询延迟和内存占用这三者之间做取舍。说白了,没有银弹,每一步都是权衡。
先说数据准备阶段,很多人一上来就选算法,其实数据准备决定了后续的天花板。我一般会先做两件事:一是向量归一化,如果场景用余弦相似度,就先做L2归一化,这样点积和余弦等价,能省一次计算;二是做数据采样,看看向量分布,比如是不是有长尾、密度是否均匀,这些会直接影响算法选型。如果数据分布极不均匀,比如头部商品占了80%的查询,那我会考虑按业务维度分片,比如按类目或商家,这样每个子空间的分布会更规整。
接下来是算法选型,这是最核心的trade-off环节。业界主流是[[HNSW]]、[[IVF]]和LSH。HNSW是图索引,查询极快,召回率高,但内存开销大,适合百万到千万级、对实时性要求高的场景,比如电商的商品检索,用户搜一个商品你得毫秒级返回结果。IVF是倒排加聚类,查询要调nprobe;IVF-Flat仍保存全精度向量并增加ID与中心开销,省内存主要依赖PQ/SQ等编码,质量不能笼统排在HNSW之后,适合十亿级以上、内存受限的场景,比如离线检索或者冷数据。LSH有理论保证,但实际召回偏低,我很少用。真正落地时,很少只用一种算法,例如Faiss的IVF-HNSW组合可用HNSW搜索粗聚类中心,再扫描选中的倒排桶;若对候选重排,应明确使用精确距离或Cross-Encoder,不能把HNSW粗量化器说成候选精排。
参数调优是另一个容易踩坑的地方。以HNSW为例,M参数控制邻居数,一般设16到64,M越大召回越高,但内存和构建时间也涨;efConstruction设100到500,影响建图质量。IVF的nlist与nprobe联合扫描,4倍根号N只能作某些配置的起点,不能预设nprobe一定取几十到几百。还有量化压缩,比如[[Product Quantization]],不是把32维变成4位:PQ把d维向量分为M段,每段用nbits位码字,裸编码长度约ceil(M×nbits/8)字节。例如768维FP32是3072字节,M=96、nbits=8的PQ编码为96字节,码本、ID和图开销另计;压缩比和质量变化不固定。这个取舍要看业务容忍度,例如可比较不同SQ/PQ码长,不能预设某种量化必然损失更小;如果是粗筛环节,用PQ把内存打下来更划算。
构建部署这部分,以[[Faiss]]为例,我会用index_factory一行代码搭出混合索引,比如"IVF4096_HNSW128,PQ32",其中HNSW是粗中心索引,PQ32默认32段、每段8位;维度要能被32整除,训练样本与metric要核对,再训练、添加与持久化。上线前我会特别关注索引的更新策略。增量更新是个坑,HNSW虽然支持动态插入,但频繁操作会导致图结构退化,召回率慢慢往下掉。所以我会把更新分成两类:新增数据直接增量插入,但修改和删除我不动原索引,而是用软删除加后台异步重建。具体做法是维护一个base索引和一个delta索引,查询时合并结果,再按版本过滤。重建完成后,用一组固定query回放,对比Recall@K和延迟,通过才原子切换,不通过就回滚。
说到这里,还有个容易被忽略的点:当索引规模大到单机放不下时,分布式部署会引入网络开销和一致性挑战,比如节点间向量分布不均匀会导致查询热点。这个场景下,我倾向用[[IVF-PQ]]配合分片路由,把每个分片独立建HNSW,查询时先路由到少数几个分片,再在分片内做精搜。
所以,我更倾向于把ANN索引构建看成一套持续迭代的工程体系,而不是一次性的建索引任务。核心是理解业务对延迟和精度的真实要求,然后在数据、算法、参数和更新策略上做针对性取舍,同时用回放和兜底机制控制风险。