如何将 Better Binary Quantization ( BBQ )应用到你的用例中,以及为什么你应该这样做
2025年5月16日 | by mebius
作者:来自 ElasticSachin Frayne及Jessica Garson
探讨为什么你会在你的用例中实现 Better Binary Quantization ( BBQ )以及如何实现它。
更好的二进制量化(BBQ):从字节到 BBQ,更优向量搜索的秘密
通过这个自定进度的 Search AI 实践学习,你可以亲自尝试向量搜索。你现在可以开始免费的云端试用,或者在本地机器上试用 Elastic。
向量搜索为实现文本语义搜索或图像、视频、音频的相似度搜索提供了基础。使用向量搜索时,向量是数据的数学表示,通常体积庞大且处理缓慢。Better Binary Quantization(以下简称 BBQ)作为一种向量压缩方法,可以在保持匹配准确性的同时缩小向量体积,从而加快搜索和处理速度。本文将介绍 BBQ 以及 rescore_vector
字段,这是一个仅在量化索引中可用的字段,用于自动对向量重新打分。
本文中提到的所有完整查询和输tgcode出都可以在我们的 Elasticsearch Labs 代码仓库中找到。
为什么要在你的用例中实现这个功能?
注:如果你想深入了解 BBQ 背后的数学原理,请查看下方的 “进一步学习” 部分。本文的重点是实现方式。
虽然这些数学原理很有趣,而且对你理解向量搜索为何依然精确非常重要,但最终这其实都是关于压缩的。因为目前的向量搜索算法主要受限于数据读取速度。如果你能把所有数据都放入内存中,相比从存储中读取,速度会显著提升(内存比 SSD 快大约 200 倍)。
有几点需要注意:
-
基于图的索引(如 HNSW)在向量检索中是最快的。
-
HNSW:一种近似最近邻搜索算法,构建多层图结构,以实现高效的高维相似度搜索。
- HNSW 的速度从根本上受限于从内存读取数据的速度,或者在最坏情况下,是从存储中读取的速度。
- 理想情况下,最好能将所有存储的向量加载到内存中。
- Embedding 模型通常生成的向量是 float32 精度的,每个浮点数占用 4 个字节。
- 最后,根据你拥有的向量数量和/或维度数量,你可能会很快耗尽内存,无法存储所有的向量。
在这个前提下,你会发现,一旦开始摄入数百万甚至数十亿个向量,每个向量可能有几百甚至几千个维度,问题就会迅速出现。标题为 “ 关于压缩率的近似数值” 的部分提供了一些大致的数字。
你需要什么来开始?
要开始,你需要以下内容:
- 如果你使用的是 Elastic Cloud 或本地部署版本,你需要一个高于 8.18 的 Elasticsearch 版本。虽然 BBQ 是在 8.16 中引入的,但在本文中,你将使用的是 vector_rescore,它是在 8.18 中引入的。
- 此外,你还需要确保你的集群中有一个机器学习(ML)节点。(注意:加载模型至少需要一个 4GB 的 ML 节点,但在实际生产环境中你可能需要更大的节点。)
- 如果你使用的是 Serverless,你需要选择一个为向量优化的实例。
- 你还需要具备基础的向量数据库知识。如果你还不熟悉 Elastic 中的向量搜索概念,你可能需要先查看以下资源:
实现
为了让这篇博客保持简单,当有内置函数可用时,你将使用它们。在这个例子中,你会使用 .multilingual-e5-small 向量嵌入模型,它将在 Elasticsearch 的机器学习节点中直接运行。请注意,你可以将 text_embedding 模型替换为你选择的 embedder(如 OpenAI、Google AI Studio、Cohere 等等。如果你喜欢的模型还未集成,也可以使用你自己的密集向量嵌入)。
首先,你需要创建一个推理端点,用于为给定文本生成向量。你将在 Kibana Dev Tools 控制台中运行所有这些命令。这个命令将下载 .multilingual-e5-small。如果该模型尚不存在,它会设置你的端点;这可能需要一些时间。你可以在 Outputs 文件夹中的 01-create-an-inference-endpoint-output.json 文件tgcode中查看预期的输出结果。
PUT _inference/text_embedding/my_e5_model
{
"service": "elasticsearch",
"service_settings": {
"num_threads": 1,
"model_id": ".multilingual-e5-small",
"adaptive_allocations": {
"enabled": true,
"min_number_of_allocations": 1
}
}
}
一旦该操作返回,你的模型就已设置完成,你可以使用以下命令测试模型是否按预期工作。你可以在 Outputs 文件夹中的 02-embed-text-output.json 文件中查看预期的输出结果。
POST _inference/text_embedding/my_e5_model
{
"input": "my awesome piece of text"
}
如果你遇到已训练模型未分配到任何节点的问题,可能需要手动启动你的模型。
POST _ml/trained_models/.multilingual-e5-small/deployment/_start
现在我们来创建一个新的映射,其中包含两个属性:一个标准的文本字段( my_field)和一个密集向量字段( my_vector),其维度为 384,以匹配嵌入模型的输出。你还将覆盖 index_options.type 设置为 bbq_hnsw。你可以在 Outputs 文件夹中的 03-create-byte-qauntized-index-output.json 文件中查看预期的输出结果。
PUT bbq-my-byte-quantized-index
{
"mappings": {
"properties": {
"my_field": {
"type": "text"
},
"my_vector": {
"type": "dense_vector",
"dims": 384,
"index_options": {
"type": "bbq_hnsw"
}
}
}
}
}
为了确保 Elasticsearch 生成你的向量,你可以使用 Ingest Pipeline。这个 pipeline 需要三个要素:endpoint(model_id)、你想为其创建向量的 input_field,以及用于存储这些向量的 output_field。下面的第一个命令将创建一个 inference ingest pipeline,它在底层使用的是 inference 服务,第二个命令将用于测试该 pipeline 是否正常工作。你可以在 Outputs 文件夹中的 04-create-and-simulate-ingest-pipeline-output.json 文件中查看预期的输出结果。
PUT _ingest/pipeline/my_inference_pipeline
{
"processors": [
{
"inference": {
"model_id": "my_e5_model",
"input_output": [
{
"input_field": "my_field",
"output_field": "my_vector"
}
]
}
}
]
}
POST _ingest/pipeline/my_inference_pipeline/_simulate
{
"docs": [
{
"_source": {
"my_field": "my awesome text field"
}
}
]
}
现在你可以使用下面的前两个命令添加一些文档,并使用第三个命令测试你的搜索是否正常工作。你可以在 Outputs 文件夹中的 05-bbq-index-output.json 文件中查看预期的输出结果。
PUT bbq-my-byte-quantized-index/_doc/1?pipeline=my_inference_pipeline
{
"my_field": "my awesome text field"
}
PUT bbq-my-byte-quantized-index/_doc/2?pipeline=my_inference_pipeline
{
"my_field": "some other sentence"
}
GET bbq-my-byte-quantized-index/_search
{
"query": {
"bool": {
"must": [
{
"knn": {
tgcode "field": "my_vector",
"query_vector_builder": {
"text_embedding": {
"model_id": "my_e5_model",
"model_text": "my awesome search field"
}
},
"k": 10,
"num_candidates": 100
}
}
]
}
},
"_source": [
"my_field"
]
}
正如本文所建议的,当你扩展到大量数据时,建议使用重排序(rescoring)和过采样(oversampling),因为它们可以在享受压缩优势的同时保持较高的召回准确率。从 Elasticsearch 8.18 版本开始,你可以使用 rescore_vector 以这种方式实现。预期的输出可在 Outputs 文件夹中的 06-bbq-search-8-18-output.json 文件中查看。
GET bbq-my-byte-quantized-index/_search
{
"query": {
"bool": {
"must": [
{
"knn": {
"field": "my_vector",
"query_vector_builder": {
"text_embedding": {
"model_id": "my_e5_model",
"model_text": "my awesome search field"
}
},
"rescore_vector": {
"oversample": 3
},
"k": 10,
"num_candidates": 100
}
}
]
}
},
"_source": [
"my_field"
]
}
这些得分与原始数据的得分相比如何?如果你使用 index_options.type: hnsw 重新执行上述所有操作,你会发现这些得分非常接近。你可以在 Outputs 文件夹中的 07-raw-vector-output.json 文件中查看预期的输出结果。
PUT my-raw-vector-index
{
"mappings": {
"properties": {
"my_field": {
"type": "text"
},
"my_vector": {
"type": "dense_vector",
"dims": 384,
"index_options": {
"type": "hnsw"
}
}
}
}
}
PUT my-raw-vector-index/_doc/1?pipeline=my_inference_pipeline
{
"my_field": "my awesome text field"
}
PUT my-raw-vector-index/_doc/2?pipeline=my_inference_pipeline
{
"my_field": "some other sentence"
}
GET my-raw-vector-index/_search
{
"query": {
"bool": {
"must": [
{
"knn": {
"field": "my_vector",
"query_vector_builder": {
"text_embedding": {
"model_id": "my_e5_model",
"model_text": "my awesome search field"
}
},
"k": 10,
"num_candidates": 100
}
}
]
}
},
"_source": [
"my_field"
]
}
关于压缩率的近似数值
在进行向量搜索时,存储和内存需求很快就会成为一个重要的挑战。以下的分析展示了不同量化技术是如何显著减少向量数据内存占用的。
Vectors (V) | Dimensions (D) | raw (V x D x 4) | int8 (V x (D x 1 + 4)) | int4 (V x (D x 0.5 + 4)) | bbq (V x (D x 0.125 + 4)) |
---|---|---|---|---|---|
10,000,000 | 384 | 14.31GB | 3.61GB | 1.83GB | 0.58GB |
50,000,000 | 384 | 71.53GB | 18.07GB | 9.13GB | 2.89GB |
100,00,0000 | 384 | 143.05GB | 36.14GB | 18.25GB | 5.77GB |
结论
BBQ 是一种可以应用于你的向量数据的压缩优化方式,它不会牺牲准确性。它通过将向量转换为比特来工作,从而实现高效搜索,并助你扩展 AI 工作流,以加速搜索并优化数据存储。
进一步学习
如果你想了解更多关于 BBQ 的内容,可以查看以下资源:
文章来源于互联网:如何将 Better Binary Quantization ( BBQ )应用到你的用例中,以及为什么你应该这样做