☰
RAG原理-PQ乘积量化
2026/9/27 22:21:07 网站建设 项目流程

RAG 原理:PQ 乘积量化

当向量数量达到百万、千万甚至更大规模时,瓶颈不仅是“搜索范围太大”,还包括原始向量占用内存过高、距离计算成本过大。PQ(Product Quantization,乘积量化)的核心目标,就是用很短的离散编码近似表示高维向量。

一、为什么需要 PQ?

假设知识库中有NNN个ddd维向量,每个元素采用float32存储,则原始向量大约占用:

Memory=N×d×4 Bytes Memory = N \times d \times 4\ BytesMemory=N×d×4Bytes

例如,1 亿个 768 维向量仅保存原始数据就需要约:

100,000,000×768×4≈286 GiB 100,000,000 \times 768 \times 4 \approx 286\ GiB100,000,000×768×4≈286GiB

全量保存和逐维计算都会带来很高的内存、带宽与计算开销。PQ 通过“分段 + 聚类 + 编码”压缩向量,并利用查表完成近似距离计算。

PQ 不是传统意义上的降维:向量仍然对应原来的ddd维空间,只是每一段不再保存完整浮点数,而是保存最接近的聚类中心编号。

二、PQ 的核心思想

设原始向量x∈Rdx \in \mathbb{R}^{d}x∈Rd,将它平均切分为mmm个子向量:

x=[x(1),x(2),…,x(m)] x = [x^{(1)}, x^{(2)}, \ldots, x^{(m)}]x=[x(1),x(2),…,x(m)]

每个子向量的维度为:

dsub=dm d_{sub} = \frac{d}{m}dsub​=md​

因此必须满足:

d mod m=0 d \bmod m = 0dmodm=0

对第jjj个子空间单独执行 K-Means,训练出KKK个聚类中心,形成子码本:

Cj={cj,1,cj,2,…,cj,K} C_j = \{c_{j,1}, c_{j,2}, \ldots, c_{j,K}\}Cj​={cj,1​,cj,2​,…,cj,K​}

编码时,只需找到子向量最近的聚类中心,并保存它的编号:

ij=arg⁡min⁡k∈[1,K]∥x(j)−cj,k∥22 i_j = \arg\min_{k \in [1,K]} \left\|x^{(j)} - c_{j,k}\right\|_2^2ij​=argk∈[1,K]min​​x(j)−cj,k​​22​

最终,一个高维向量被压缩为:

PQ(x)=[i1,i2,…,im] PQ(x) = [i_1, i_2, \ldots, i_m]PQ(x)=[i1​,i2​,…,im​]

直观流程

原始 d 维向量 │ ├── 子向量 1 ──> 子码本 1 ──> 中心编号 i1 ├── 子向量 2 ──> 子码本 2 ──> 中心编号 i2 ├── ... └── 子向量 m ──> 子码本 m ──> 中心编号 im │ └── PQ Code = [i1, i2, ..., im]

三、为什么叫“乘积量化”?

每个子空间都有一个包含KKK个中心的码本,mmm个子码本组合起来,可表达的中心组合数量为:

Km K^mKm

完整码本空间相当于多个子码本的笛卡尔积(Cartesian Product),因此称为Product Quantization。

这种方法避免了直接在高维空间训练一个巨大码本:高维问题被拆成多个低维聚类问题,训练和存储成本都更可控。

四、PQ 如何压缩内存?

通常令:

K=2nbits K = 2^{nbits}K=2nbits

当nbits = 8时,每个子空间有 256 个聚类中心,一个中心编号只需 1 字节。

若原始向量维度为 128,切成m = 8段:

表示方式单向量大小
原始float32128×4=512128 \times 4 = 512128×4=512字节
PQ 编码8×1=88 \times 1 = 88×1=8字节

忽略码本等额外开销时,压缩倍数约为:

5128=64 \frac{512}{8} = 648512​=64

压缩率越高,量化误差通常越大。工程上需要结合召回率、内存和延迟共同调参。

五、PQ 如何进行距离计算?

PQ 不必先把数据库中的每个向量完整还原,再执行逐维距离计算。

对查询向量qqq同样切成mmm段,预先计算每个查询子向量到对应子码本所有中心的距离,得到m×Km \times Km×K的距离表:

查询子向量 q(1) ──> 到子码本 1 的 K 个中心距离 查询子向量 q(2) ──> 到子码本 2 的 K 个中心距离 ... 查询子向量 q(m) ──> 到子码本 m 的 K 个中心距离

数据库向量只保存[i1, i2, ..., im],所以它与查询向量的近似距离可通过查表并求和得到:

d~(q,x)=∑j=1m∥q(j)−cj,ij∥22 \tilde{d}(q,x) = \sum_{j=1}^{m} \left\|q^{(j)} - c_{j,i_j}\right\|_2^2d~(q,x)=j=1∑m​​q(j)−cj,ij​​​22​

原本复杂的浮点距离计算,被转换为少量查表与加法操作。

ADC 与 SDC

方式查询向量数据库向量特点
ADC(非对称距离)保留原始向量使用 PQ 编码精度通常更高,ANN 检索中更常见
SDC(对称距离)也进行量化使用 PQ 编码速度更快,但查询侧也引入量化误差

六、PQ 的完整构建与查询流程

1. 构建阶段

  1. 准备具有代表性的训练向量;
  2. 将每个向量切分为mmm个子向量;
  3. 在每个子空间中独立执行 K-Means;
  4. 得到mmm个子码本;
  5. 用最近中心的编号编码全部数据库向量。

2. 查询阶段

  1. 将查询向量按相同方式切成mmm段;
  2. 计算查询子向量到各子码本中心的距离表;
  3. 根据每个数据库向量保存的中心编号查表;
  4. 累加各子空间距离;
  5. 返回近似距离最小的 Top K 结果。

七、PQ 与 IVF-PQ

单独使用 PQ 解决的是“每个向量太大、距离计算太贵”的问题,但仍可能需要扫描大量 PQ 编码。

IVF-PQ 将两种优化组合起来:

IVF:先通过粗聚类定位少量候选簇 ↓ PQ:再用压缩编码快速估算候选向量距离 ↓ 可选:取较大的候选集,用原始向量或 Reranker 精排
组件主要作用
IVF减少需要扫描的向量数量
PQ压缩向量,并降低单次距离计算成本
精排降低量化误差对最终 Top K 的影响

在 FAISS 的IndexIVFPQ中,通常会先计算向量与粗聚类中心之间的残差,再对残差进行 PQ 编码,从而提高近似精度。

八、FAISS:IndexPQ 示例

pipinstallfaiss-cpu numpy
importfaissimportnumpyasnp rng=np.random.default_rng(42)d=128m=8nbits=8database_size=100_000top_k=5database_vectors=rng.random((database_size,d),dtype=np.float32)query_vectors=rng.random((5,d),dtype=np.float32)# d 必须能被 m 整除assertd%m==0# 创建 PQ 索引:128 维向量分成 8 段,每段使用 8 bit 编码index=faiss.IndexPQ(d,m,nbits)# PQ 必须先训练各子空间码本index.train(database_vectors)index.add(database_vectors)distances,ids=index.search(query_vectors,top_k)print("近邻 ID:")print(ids)print("近似 L2 距离:")print(distances)# 查看独立 PQ 编码大小codes=index.sa_encode(database_vectors)print("原始向量字节数:",database_vectors.nbytes)print("PQ 编码字节数:",codes.nbytes)print("单向量编码字节数:",index.code_size)

九、FAISS:IndexIVFPQ 示例

importfaissimportnumpyasnp rng=np.random.default_rng(42)d=128nlist=256m=8nbits=8top_k=5database_vectors=rng.random((100_000,d),dtype=np.float32)query_vectors=rng.random((5,d),dtype=np.float32)# IVF 使用的粗量化器quantizer=faiss.IndexFlatL2(d)index=faiss.IndexIVFPQ(quantizer,d,nlist,m,nbits,)# IVF 和 PQ 都需要训练index.train(database_vectors)index.add(database_vectors)# 查询时探测的粗聚类数量index.nprobe=16distances,ids=index.search(query_vectors,top_k)print(ids)print(distances)

关键参数:

参数含义影响
m子空间数量越大通常越精确,但编码更长、查表次数更多
nbits每段编码位数越大码本越细,误差更小,但训练和码本成本更高
nlistIVF 粗聚类数量决定倒排列表的粒度
nprobe查询探测簇数量越大召回率越高,但查询更慢
top_k最终返回数量应结合业务召回和后续重排需求设置

十、误差来源与调优思路

PQ 用聚类中心近似真实子向量,因此必然存在量化误差:

Error=∥x−x^∥22 Error = \left\|x - \hat{x}\right\|_2^2Error=∥x−x^∥22​

主要误差来源包括:

  • 子空间划分后,不同分段之间的相关性被弱化;
  • 每个子码本的聚类中心数量有限;
  • 训练样本不能代表真实数据分布;
  • 压缩率过高,导致多个差异较大的向量共享相同编码。

常见优化方法:

  • 增大m或nbits,用更长编码换取更低误差;
  • 使用足够大且有代表性的训练集;
  • 使用 OPQ 先旋转向量空间,让各子空间的信息分布更均衡;
  • 使用 IVF-PQ 先缩小候选集,再执行 PQ 距离计算;
  • 先召回更多候选,再使用原始向量或重排序模型精排。

十一、如何评估 PQ?

不要只比较压缩倍数,还应同时观察:

  • Recall@K:与精确 Flat 搜索结果的重合比例;
  • 查询延迟与 QPS:是否达到线上性能目标;
  • 内存占用:PQ 编码、码本与索引的总成本;
  • 索引训练时间:训练集增大后会明显上升;
  • 重建误差:原向量与量化重建向量之间的距离。

推荐使用精确索引生成基准答案,再对不同的m、nbits、nlist、nprobe组合进行对比。

十二、小结

PQ 的本质可以概括为四步:

  1. 分段:把高维向量切成多个低维子向量;
  2. 聚类:为每个子空间训练独立码本;
  3. 编码:用聚类中心编号代替浮点子向量;
  4. 查表:用预计算距离表快速估算向量距离。

它以可控的精度损失换取显著的内存压缩和检索加速。实际 RAG 系统中,PQ 常与 IVF 组合成 IVF-PQ,并配合候选扩召回和精排,在召回率、延迟、内存与成本之间取得平衡。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询