嵌入量化:二进制与标量技术,实现更快、更便宜的检索
TL;DR: Hugging Face 引入了二进制和标量(int8)嵌入量化,分别将嵌入压缩 32 倍或 4 倍,显著降低内存和存储成本,并将检索速度提升至最高 45 倍,同时保持原始性能的 96%–99%。
为什么嵌入重要以及它们的扩展方式
嵌入将文本、图像、音频等数据转换为高维向量,从而实现相似度搜索、推荐、聚类以及众多下游 NLP 任务。最先进的模型通常输出 1024 维的 float32 向量,每个维度占用 4 字节。存储 2.5 亿个此类向量约需 1 TB 内存,导致每月数千美元的云费用。博客对多种流行模型的成本进行了量化,显示在 AWS x2gd 实例上,1024 维模型的费用可超过 $3,600 / mo。
量化 vs. 降维
传统的扩展方法使用降维(例如 PCA)或 Matryoshka Representation Learning(MRL),通过截断维度来降低规模,但可能会损害性能。嵌入量化则在模型生成嵌入后降低每个维度的精度,提供了一条实现更低成本检索的互补路径。
二进制量化
二进制量化通过在零处阈值化,将每个 float32 值转换为单个位。这实现了 32 倍的存储压缩(例如,1024 维向量变为 1024 位,打包后为 128 字节)。检索使用汉明距离,只需两个 CPU 周期即可计算,带来巨大的加速。
在 Sentence‑Transformers 中的实现
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("mixedbread-ai/mxbai-embed-large-v1")
# Direct binary encoding
binary_embeddings = model.encode(
["I am driving to the lake.", "It is a beautiful day."],
precision="binary",
)
得到的 binary_embeddings 形状为 (2, 128),数据类型为 int8,占用 256 字节,而原始 float32 嵌入占用 8 192 字节。
在向量数据库中的支持
二进制索引已在 Faiss、USearch、Vespa AI、Milvus、Qdrant 和 Weaviate 中提供,可直接替换现有流水线。
标量(int8)量化
标量量化将每个维度的连续 float32 范围映射到 256 个离散的 int8 水平(‑128 至 127)。这实现了 4 倍的存储压缩,同时保留比二进制更细的粒度。需要在大规模嵌入集合上进行校准,以计算每个维度的最小/最大范围。
在 Sentence‑Transformers 中的实现
from sentence_transformers import SentenceTransformer, quantize_embeddings
from datasets import load_dataset
model = SentenceTransformer("mixedbread-ai/mxbai-embed-large-v1")
corpus = load_dataset("nq_open", split="train[:1000]") ["question"]
calibration_embeddings = model.encode(corpus)
embeddings = model.encode(["I am driving to the lake.", "It is a beautiful day."])
int8_embeddings = quantize_embeddings(
embeddings,
precision="int8",
calibration_embeddings=calibration_embeddings,
)
int8_embeddings 保持原始 1024 维的形状,但仅使用 2 048 字节。
在向量数据库中的支持
标量量化在 Faiss(IndexHNSWSQ)、USearch、Vespa AI、OpenSearch、ElasticSearch、Milvus(IVF_SQ8)和 Qdrant 中得到(直接或间接)支持。
结合二进制和标量量化
两阶段流水线可以实现两者的最佳组合:
- 使用高质量模型对查询进行编码(例如
mxbai-embed-large-v1)。 - 将查询量化为二进制并在二进制索引中搜索(约 5 GB,针对 4100 万条 Wikipedia 条目)。
- 从磁盘上存储的 int8 索引中加载前 k 个候选(约 48 GB)。
- 使用原始
float32查询对这些候选进行重新打分,针对 int8 嵌入。 - 返回最终的前 k 条结果。 该方法将内存降至约 5 GB,磁盘降至约 52 GB,而完整精度检索则需要约 200 GB。
实验结果
检索性能
| 模型 | 维度 | 存储 (250 M) | MTEB 检索 NDCG@10 | Float32 百分比 |
|---|---|---|---|---|
mxbai-embed-large-v1 (float32) |
1024 | 953.67 GB $3 623/mo | 54.39 | 100 % |
mxbai-embed-large-v1 (int8) |
1024 | 238.41 GB $905/mo | 52.79 | 97 % |
mxbai-embed-large-v1 (binary) |
1024 | 29.80 GB $113/mo | 52.46 | 96.45 % |
all-MiniLM-L6-v2 (binary) |
384 | 11.18 GB $42/mo | 39.07 | 93.79 % |
关键观察:
- Int8 量化通常保留 >94 % 的性能,同时将存储压缩 4 倍。
- 二进制量化在大维度模型上保留约 96 % 的性能,且在某些小模型(例如
all-MiniLM-L6-v2)上甚至优于 int8。 - 性能因模型而异;校准数据质量和维度塌陷会影响结果。
重打分的影响
- 二进制重打分(使用原始 float 查询对前 k 个二进制结果重新排序)将性能从基线的 92.5 % 提升至 96.5 %。
- 对于 int8,提升
rescore_multiplier(在重打分前检索更多候选)可提升保留率,在乘数为 4–5 时达到约 99 %。
检索速度
在 GCP a2-highgpu-4g CPU‑only 精确搜索:
| 量化方式 | 最小加速 | 平均加速 | 最大加速 |
|---|---|---|---|
| float32 | 1× | 1× | 1× |
| int8 | 2.99× | 3.66× | 4.8× |
| binary | 15.05× | 24.76× | 45.8× |
| 因此二进制量化可实现数量级的延迟降低。 |
权衡总结
| 指标 | float32 | int8/uint8 | binary/ubinary |
|---|---|---|---|
| 内存与索引大小 | 1× | 小 4× | 小 32× |
| 检索速度 | 1× | 最多提升 4× | 最多提升 45× |
| 性能保留 | 100 % | ~99 % | ~96 % |
演示与实用脚本
实时演示(link)展示了在 5 GB RAM 和 52 GB 磁盘下对 4100 万条 Wikipedia 条目的检索,达到了上述加速。博客还提供了三类可直接运行的脚本:
- 推荐检索 – 将二进制搜索与 int8 重打分相结合。
- 使用示例 – 展示如何使用量化嵌入调用
semantic_search_faiss或semantic_search_usearch。 - 基准测试 – 测量每种量化模式的速度和准确性。
未来方向
- 探索子 int8 量化(例如 4 位或 2 位桶)以实现更紧的压缩。
- 将量化与 Matryoshka Representation Learning 结合,先截断维度再量化,可能在质量损失有限的情况下实现 32×–256× 的加速。
- 在二进制 + int8 阶段之后加入第三阶段的 cross‑encoder 重排序器,以实现低延迟、低成本的最先进检索。
Citation
@article{shakir2024quantization,
author = {Aamir Shakir and Tom Aarsen and Sean Lee},
title = {Binary and Scalar Embedding Quantization for Significantly Faster & Cheaper Retrieval},
journal = {Hugging Face Blog},
year = {2024},
note = {https://huggingface.co/blog/embedding-quantization}
}