登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

Sentence Transformers 怎样用二进制量化保存嵌入向量

来源:17golang原创

时间:2026-10-29 16:43:28 150浏览 收藏

Sentence Transformers 可以在生成嵌入时直接使用二进制量化,也可以先生成 float32 向量,再调用 quantize_embeddings 转换。二进制量化把每个维度压成 1 bit,官方实现会把这些 bit 打包进字节,因此 1024 维向量通常变成形状为 (1024 / 8) = 128 的 int8 或 uint8 数组。

官方地址:https://www.sbert.net/

如果目标是降低向量索引的内存和磁盘占用,最小做法是让 encode 使用 precision="binary",保存返回数组及其模型、维度和 dtype 元数据;如果还要保证召回质量,则把二进制结果用于初筛,再保留较高精度的重排数据。

先确定二进制量化放在哪一层

最直接的入口是 SentenceTransformer.encode 的 precision 参数。它适合从文本开始就按二进制格式生成结果;如果程序已经缓存了默认的浮点嵌入,则可以在后面调用 quantize_embeddings,避免改动前面的编码流程。

from sentence_transformers import SentenceTransformer
from sentence_transformers.quantization import quantize_embeddings

# 加载同一个模型,文档向量和查询向量必须保持模型一致
model = SentenceTransformer("mixedbread-ai/mxbai-embed-large-v1")
texts = ["如何降低向量索引的存储占用", "二进制向量怎样参与召回"]

# 方案一:编码阶段直接得到二进制数组
binary_embeddings = model.encode(texts, precision="binary")

# 方案二:已有 float32 结果时再量化,便于保留原始向量做重排
float_embeddings = model.encode(texts)
binary_from_float = quantize_embeddings(float_embeddings, precision="binary")

print(binary_embeddings.shape, binary_embeddings.dtype)
print(binary_from_float.shape, binary_from_float.dtype)

两种入口的量化目标相同,但数据生命周期不同:直接量化可以少保留一份中间结果;后置量化则适合已经有浮点缓存、还需要用原始向量做重排的检索系统。不要只保存一个没有元数据的二进制文件,否则后续很难判断它对应哪个模型和维度。

Sentence Transformers 从文本到 float32 嵌入、二进制量化和字节数组保存的静态关系说明图
图1:结构说明图,展示编码结果、二进制量化、bit 打包和数组保存之间的关系,不是运行截图。

保存时重点记录 shape 和 dtype

二进制量化不是把数组简单改成更小的浮点类型。官方说明是先按零阈值把归一化嵌入映射为 0/1,再用 np.packbits 把 8 个 bit 收进一个字节。因此一个 1024 维的 float32 向量原本需要 4096 字节,打包后的二进制数组通常只有 128 字节,形状也从 (1024,) 变成 (128,)。

import json
import numpy as np

# 保存数组与描述文件,恢复时必须按同一 dtype 读取
np.save("doc_embeddings.binary.npy", binary_embeddings)
metadata = {
    "model": "mixedbread-ai/mxbai-embed-large-v1",
    "precision": "binary",
    "shape": list(binary_embeddings.shape),
    "dtype": str(binary_embeddings.dtype),
    "metric": "hamming"
}
with open("doc_embeddings.binary.json", "w", encoding="utf-8") as fp:
    # ensure_ascii=False 让中文元数据保持可读
    json.dump(metadata, fp, ensure_ascii=False, indent=2)

# 恢复时先读元数据,再检查数组的形状和类型
restored = np.load("doc_embeddings.binary.npy")
assert tuple(metadata["shape"]) == restored.shape
assert metadata["dtype"] == str(restored.dtype)

如果向量库需要无符号字节格式,可以把精度指定为 "ubinary";二者不要在没有转换规则的情况下混用。实际项目还应把模型名、量化精度、维度、dtype 和相似度度量一起写进索引元数据,避免查询向量和文档向量使用了不同的编码协议。

二进制适合召回,重排负责精度

二进制向量的优势是占用小、Hamming 距离计算快,但压缩后不应被当作完整的浮点相似度替代品。一个稳妥的链路是:先把查询向量也量化为 binary,在二进制索引中取出较大的候选集,再用 float32 查询向量和更高精度的文档向量进行重排,最后返回较小的 top-k。

例如目标结果是 10 条,可以先召回 rescore_multiplier * 10 条候选,再对候选做 dot-product 重排。这样二进制索引负责缩小搜索范围,重排索引负责恢复排序质量;两者的模型、向量维度和文档 ID 必须一一对应。

二进制索引召回与高精度向量重排以及模型元数据关联的静态结构说明图
图2:关系说明图,展示二进制索引、候选文档、高精度重排和元数据之间的静态边界,不是检索结果截图。

几个容易混淆的边界

  • 不要把 128 当成模型输出维度。它是 1024 个 bit 打包成字节后的数组长度,原始语义维度仍是 1024。
  • 不要只保存二进制文件。模型标识、precision、shape、dtype 和度量方式缺一项,都可能导致恢复或查询不匹配。
  • 不要用很小的样本估计 int8 标定范围。本文讨论的是 binary;如果改用 int8,官方建议使用更大的校准嵌入集合或明确的 min/max 范围。
  • 不要把二进制召回误写成质量不变。最终效果取决于模型、数据分布、召回倍率和重排策略,应按自己的检索集评估。

常见问题

二进制量化后为什么 dtype 还是 int8?因为实现把 bit 打包进字节,数组元素承载的是字节,不是 Python 层面的单 bit 类型;需要无符号格式时使用 ubinary。

可以只用二进制向量做最终排序吗?可以,但要接受排序精度和模型数据相关的取舍。对质量敏感的检索,通常使用二进制初筛加高精度重排更稳妥。

总结:从 encode(precision="binary") 开始,保存数组和完整元数据;用 Hamming 距离做快速候选召回,再根据需求保留 float32 或 int8 数据做重排。这样才能同时得到较低的存储成本和可控的检索质量。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>