TurboQuant 量化技术原理与 Qdrant 实践指南
为什么 TurboQuant 填补了向量压缩的空白
向量搜索系统需要将大量嵌入向量常驻内存以响应查询。当集合规模扩大,以 float32 存储每个坐标(4 字节)的成本迅速上升。例如,100 万个 1024 维向量占用约 4.1 GB。若能用更少比特表示每个坐标,存储开销可大幅降低。
但压缩会引入误差,可能改变最近邻的排序结果。现有方法各有局限:标量量化(如 SQ8)通常只能做到 4 倍压缩;二值量化虽可达 32 倍压缩,但召回率对嵌入分布敏感;乘积量化(PQ)则需为每个数据集训练专用码本,增加复杂度。
TurboQuant 提供了一种折中方案。它支持 4 位、2 位、1.5 位和 1 位四种模式,无需训练即可使用。TQ4 比 SQ8 节省一半空间,而 TQ1/TQ2 在相同比特预算下通常比二值量化保留更多语义信息。当你发现向量本身成为内存或存储瓶颈时,TurboQuant 值得一试。
TurboQuant 的工作原理
嵌入向量的坐标值往往分布不均——某些维度变化剧烈,另一些则接近常数。直接对原始坐标做低位量化会丢失关键信息。
TurboQuant 的第一步是施加一个随机正交旋转。这个操作混合了所有坐标,但保持向量长度、点积和 L2 距离不变。对于已归一化的向量,旋转后每个坐标的分布趋近于高斯分布(在高维下由 Beta 分布近似)。
接着,每个旋转后的坐标被映射到一个固定码本中最接近的代表值。这个码本基于 Lloyd-Max 算法预先计算,代表值在概率密度高的区域更密集,从而最小化平均重构误差。由于旋转后的分布已知,码本无需针对具体数据集训练。
最终,系统只存储代表值的索引(如 4 位对应 16 个索引),而非原始浮点数。查询时同样先旋转,再与压缩后的索引比较。虽然仍有信息损失,但论文提出的 MSE 变体优化了重构精度,PROD 变体则用额外 1 比特校正内积偏差。
Qdrant 对 TurboQuant 的增强实现
Qdrant 从 1.18 版本起集成 TurboQuant 作为可选量化层。它不仅采用 TurboQuant 的固定码本,还融合了 RaBitQ 论文中的两项改进:
- 长度重归一化:量化会使向量变短,Qdrant 利用额外存储的原始 L2 范数在打分时恢复长度,使点积和欧氏距离成为有效度量(而不局限于余弦相似度)。
- 逐坐标校准:调整每个坐标的缩放,使其与固定码本对齐,进一步减少打分误差。
这些校准参数和范数共用 4 字节存储。更重要的是,Qdrant 将码本和查询向量都转为整数,利用 SIMD 指令并行处理坐标块。这使得单次查询能高效比对数百万压缩向量。
值得注意的是,Qdrant 默认保留原始 float32 向量用于重打分(rescoring)。搜索分两阶段:先用压缩向量快速召回候选集,再用原始向量精确重排。大部分召回率损失在此阶段被弥补。
三种量化方案的实测对比
我们在 BEIR 基准的三个数据集上测试:SciFact(科学事实验证)、ArguAna(论点检索)和 NFCorpus(医学信息检索)。所有嵌入均来自 Cohere 的 embed-english-v3.0 模型(1024 维),确保公平比较。

测试配置包括:
- F32:无压缩基线
- SQ8:8 位标量量化(4 倍压缩)
- BQ1/BQ2:1/2 位二值量化
- TQ1/TQ4:1/4 位 TurboQuant

HNSW 参数统一设为 m=16, ef_construct=128。评估指标采用 Precision@10、Recall@10 和 nDCG@10。

结果表明:
- TQ4 在 8 倍压缩下,效果接近 SQ8(后者仅 4 倍压缩)
- TQ1 在 32 倍压缩下,nDCG 普遍优于 BQ1,尤其在 SciFact 上提升显著
- 二值量化在 ArguAna 表现尚可,但在 NFCorpus 上召回率骤降

Qdrant 官方在更大规模数据(如 arXiv、LAION)上的测试也显示,TurboQuant 在相同存储预算下比二值量化高 10–20 个百分点的召回率。

在 Qdrant 中启用 TurboQuant
连接到 Qdrant Cloud
首先安装客户端并配置环境变量:
pip install "qdrant-client[fastembed]"import os
from qdrant_client import QdrantClient, models
from fastembed import TextEmbedding
client = QdrantClient(
url=os.environ["QDRANT_URL"],
api_key=os.environ["QDRANT_API_KEY"],
)
embedder = TextEmbedding("jinaai/jina-embeddings-v2-base-en")创建带 TurboQuant 配置的集合
量化策略在集合创建时指定,后续不可更改:
client.create_collection(
collection_name="catalog",
vectors_config=models.VectorParams(
size=768, # 需匹配嵌入模型维度
distance=models.Distance.COSINE,
),
quantization_config=models.TurboQuantization(
turbo=models.TurboQuantQuantizationConfig(
bits=models.TurboQuantBitSize.BITS4, # 可选 BITS2/BITS1_5/BITS1
),
),
)
插入文档
写入过程与未量化时完全一致。Qdrant 在索引时自动压缩:
products = [
"waterproof hiking boots with ankle support",
"lightweight running shoes for marathon training",
# ... 其他商品描述
]
vectors = list(embedder.embed(products))
client.upsert(
collection_name="catalog",
points=[
models.PointStruct(
id=i,
vector=v.tolist(),
payload={"text": text}
)
for i, (v, text) in enumerate(zip(vectors, products))
],
)带重打分的查询
启用 rescore=True 可触发两阶段搜索。oversampling=2.0 表示先召回 6 个候选(limit×2),再用原始向量重排返回前 3:
query = next(embedder.embed(["shoes for walking long distances outdoors"]))
hits = client.query_points(
collection_name="catalog",
query=query.tolist(),
limit=3,
search_params=models.SearchParams(
quantization=models.QuantizationSearchParams(
rescore=True,
oversampling=2.0,
),
),
).points

for hit in hits:
print(round(hit.score, 4), hit.payload["text"])输出示例:
0.8215 waterproof hiking boots with ankle support
0.7982 lightweight running shoes for marathon training
0.7641 collapsible trekking poles with cork grips何时该用 TurboQuant
- 如果你当前用标量量化且存储压力大,可尝试 TQ4。它在减半存储的同时保持相近效果。
- 如果你用二值量化,在相同比特数下(如 1 位)测试 TQ1。多数场景下召回率更高,但需实测延迟是否可接受。
- 当向量占据主要内存/存储成本,且你的嵌入维度较高(≥512)时,TurboQuant 的旋转优势更明显。
不过,如果应用对吞吐量极度敏感,或召回率提升对你业务影响甚微,则维持现有方案可能更稳妥。毕竟,没有免费的压缩——只是 TurboQuant 让代价变得更可控。