Zvec Logo

乘积量化(PQ)

为 HNSW 和 IVF 索引配置 PQ4、PQ8、OPQ 与 IVF FastScan。

乘积量化(Product Quantization,PQ)将每个向量划分为 num_chunk 个子向量,再用训练得到的码本中的编码表示每个子向量。与逐维压缩的标量量化不同,PQ 对一组维度进行联合压缩。

Zvec 的 PQ 支持 HNSW 和 IVF 索引、稠密 FP32 向量以及 L2、IP、COSINE 距离度量。此功能随 0.8.1 版本提供。以下示例使用 Python API。

编码大小与精度

  • PQ8:每个子向量使用 8 位编码,码本包含 256 个条目。
  • PQ4:每个子向量使用 4 位编码,码本包含 16 个条目。
  • OPQ:在 PQ 训练前学习旋转变换,以减小量化误差。
  • FastScan:加速 IVF 中紧凑 PQ4 编码的扫描。

每个向量的编码大小约为 num_chunk × num_bits / 8 字节。例如,768 维 FP32 向量的原始大小为 3,072 字节;划分为 96 个子向量后,PQ8 编码占 96 字节,PQ4 编码占 48 字节。这些数字不包含码本、索引结构、元数据及保留的原始向量,不代表整个 Collection 的内存或磁盘占用。

增加子向量数量或编码位数通常可以保留更多信息,但也会增加存储和计算开销。请使用实际数据评估召回率与延迟。

配置参数

在 HnswIndexParam 或 IVFIndexParam 中设置 quantize_type=zvec.QuantizeType.PQ,并通过 zvec.QuantizerParam 传入量化参数。

参数默认值含义与约束
num_chunk8子向量数量,必须为正数且不大于向量维度。使用 PQ4 时,向量维度必须能被该值整除。
num_bits8每个子向量的编码位数,只支持 4 或 8。
enable_rotateFalse为 PQ 启用学习得到的 OPQ 旋转,与 INT8/INT4 标量量化使用的随机旋转不同。
fast_scanFalse启用 FastScan,仅支持 IVF + PQ4;HNSW 和 PQ8 不支持此选项。
opq_iter5OPQ 训练迭代次数,启用 OPQ 时必须为正数。
opq_pq_iter4每次 OPQ 迭代中的 PQ 训练迭代次数,启用 OPQ 时必须为正数。

构建 HNSW-PQ 索引

以下示例创建启用 OPQ 的 HNSW-PQ8 Collection。请使用尚不存在的 Collection 路径。

import random
import zvec

dimension = 128
schema = zvec.CollectionSchema(
    name="hnsw_pq",
    vectors=[
        zvec.VectorSchema(
            name="embedding",
            data_type=zvec.DataType.VECTOR_FP32,
            dimension=dimension,
            index_param=zvec.HnswIndexParam(
                metric_type=zvec.MetricType.L2,
                quantize_type=zvec.QuantizeType.PQ,
                quantizer_param=zvec.QuantizerParam(
                    num_chunk=16,
                    num_bits=8,
                    enable_rotate=True,
                ),
            ),
        )
    ],
)
collection = zvec.create_and_open("./hnsw_pq", schema)

# 示例使用合成数据;实际使用时请导入有代表性的嵌入向量。
rng = random.Random(42)
vectors = [
    [rng.uniform(-1, 1) for _ in range(dimension)]
    for _ in range(1024)
]
for start in range(0, len(vectors), 128):
    statuses = collection.insert([
        zvec.Doc(id=str(i), vectors={"embedding": vectors[i]})
        for i in range(start, min(start + 128, len(vectors)))
    ])
    assert all(status.ok() for status in statuses)

# 训练码本并构建 PQ 索引。
collection.optimize()

PQ 在 Collection.optimize() 时训练。优化前,未训练的数据仍可通过原始 Flat 索引查询。应先写入有代表性的数据再执行优化;仅创建 PQ Schema 不会得到已训练的压缩索引。

关闭并重新打开 Collection 后,训练好的码本与量化配置会保留。以可写方式打开的 Collection 也支持后续更新;随着数据变化,可以通过优化训练和重建索引。

使用 IVF-PQ4 与 FastScan

如需使用 IVF,将上述 Schema 中的 index_param 替换为:

index_param=zvec.IVFIndexParam(
    metric_type=zvec.MetricType.L2,
    n_list=16,
    n_iters=10,
    quantize_type=zvec.QuantizeType.PQ,
    quantizer_param=zvec.QuantizerParam(
        num_chunk=16,
        num_bits=4,
        fast_scan=True,
        enable_rotate=True,
    ),
)

这里的 128 维向量可以被 16 个子向量整除。OPQ 是可选项,可以独立于 FastScan 关闭。使用普通 IVF-PQ4 时,设置 fast_scan=False;使用 IVF-PQ8 时,还需设置 num_bits=8。

查询与精排

PQ 检索使用近似距离。启用 is_using_refiner 后,会使用参考向量重新计算候选结果的分数。scale_factor 控制候选扩展倍数:设为 4 时,会在精排前请求约 topk 四倍的候选结果。

对于上述 HNSW Collection:

results = collection.query(
    zvec.Query(
        field_name="embedding",
        vector=vectors[0],
        param=zvec.HnswQueryParam(
            ef=100,
            is_using_refiner=True,
            scale_factor=4,
        ),
    ),
    topk=10,
)
collection.close()

对于 IVF Collection,使用匹配的查询参数类型:

param=zvec.IVFQueryParam(
    nprobe=8,
    is_using_refiner=True,
    scale_factor=4,
)

精排会增加参考向量读取和打分开销。它可以改善已召回候选的排序,但无法找回未进入候选集的近邻。请结合 HNSW 的 ef 或 IVF 的 nprobe 调整 scale_factor,同时评估召回率和延迟。

索引生命周期的更多说明,参见 HNSW、IVF 和 Collection 优化。

本页目录