IVF 与 PQ 是什么
两者是互补的向量压缩手段:IVF 先把向量聚成若干桶,查询时只扫距离最近的几个桶,用少量遍历换速度;PQ 把向量切成若干段、每段各自量化成编码,用少量精度换内存。叠加使用可在千万级规模把常驻内存压下一个数量级,代价是量化误差导致召回变差,工程上通常再配一层重排把精度补回来。
基本信息
| IVF 结构 | nlist 个聚类中心,查询只扫其中 nprobe 个桶 |
|---|---|
| PQ 结构 | 维度切段后每段独立聚类成码本,向量存为编码 |
| 参数重心 | nlist 定分桶粒度,nprobe 定速度与召回的平衡点 |
| 精度代价 | 量化压缩有信息损失,需用原始向量重排兜底 |
| 使用顺序 | 必须先 train 训练聚类与码本,再 add 入库 |
| 实现参考 | FAISS 的 index_factory 可以一行声明组合索引 |
典型使用场景
- 千万级知识库常驻内存吃紧,需要把向量占用压到单机可承载
- 成本敏感的多租户共享向量集群,内存是主要计费项
- 离线批量检索任务:允许一次粗排加一次重排,追求吞吐而非单次精度
- 边缘或私有化部署环境,机器规格受限但数据量不小
- 作为两级检索的粗排层,先扩候选再用原始向量或重排模型精挑
FAISS 组合索引示例
FAISS 组合索引示例
import faiss
dim = 768
index = faiss.index_factory(dim, "IVF4096,PQ96") # 768 能被 96 整除,PQ 段数需整除维度
res = faiss.StandardGpuResources() # 需装 GPU 版 FAISS 才能用
index = faiss.index_cpu_to_gpu(res, 0, index) # 把索引放显存,省主机内存
index.train(xb) # 先训练聚类中心与量化码本,训练集要显著大于桶数
index.add(xb) # 训练完成后才能入库,未训练直接 add 会报错
index.nprobe = 32 # 查询扫描桶数,越大越准也越慢,需用评测集扫
D, I = index.search(xq, 10) # D 是距离、I 是索引编号,供后续重排
ids = [meta[i] for i in I[0]] # 用编号回查原始分块与元数据参数与取舍
| 参数 | 含义 | 调大调小的影响 |
|---|---|---|
| nlist | 聚类桶数量 | 桶多则每桶样本少,召回上升但训练成本高 |
| nprobe | 查询扫描的桶数 | 调大召回上升、延迟线性增长 |
| PQ 段数 | 向量被切成几段量化 | 段数越多压缩比越低、精度越高,须整除维度 |
| 训练样本量 | 用于 train 的向量数 | 过少则聚类中心不可靠,召回明显下降 |
| 是否重排 | 对粗排候选再精排 | 开启可补回量化损失,代价是一次额外计算 |
量化索引基本都要配重排:先粗排扩大候选规模(例如取前 100 条),再用原始向量或重排模型精排,否则压缩带来的漏检会直接体现在答案缺失上。
nprobe 不要凭经验拍值:它和 nlist、数据分布强相关,正确做法是固定数据集扫几组取值,画出召回率与延迟曲线后选取折中。