嵌入量化:二进制与标量技术,实现更快、更便宜的检索

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 中得到(直接或间接)支持。

结合二进制和标量量化

两阶段流水线可以实现两者的最佳组合:

  1. 使用高质量模型对查询进行编码(例如 mxbai-embed-large-v1)。
  2. 将查询量化为二进制并在二进制索引中搜索(约 5 GB,针对 4100 万条 Wikipedia 条目)。
  3. 从磁盘上存储的 int8 索引中加载前 k 个候选(约 48 GB)。
  4. 使用原始 float32 查询对这些候选进行重新打分,针对 int8 嵌入。
  5. 返回最终的前 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
int8 2.99× 3.66× 4.8×
binary 15.05× 24.76× 45.8×
因此二进制量化可实现数量级的延迟降低。

权衡总结

指标 float32 int8/uint8 binary/ubinary
内存与索引大小 小 4× 小 32×
检索速度 最多提升 4× 最多提升 45×
性能保留 100 % ~99 % ~96 %

演示与实用脚本

实时演示(link)展示了在 5 GB RAM 和 52 GB 磁盘下对 4100 万条 Wikipedia 条目的检索,达到了上述加速。博客还提供了三类可直接运行的脚本:

  • 推荐检索 – 将二进制搜索与 int8 重打分相结合。
  • 使用示例 – 展示如何使用量化嵌入调用 semantic_search_faisssemantic_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}
}

Sources