了解 Lucene 中的标量量化

探索 Elastic 如何将标量量化引入 Lucene,包括自动字节量化、每个分段的量化和性能洞察。

Lucene 中的自动字节量化

虽然 HNSW 是一种强大且灵活的存储和搜索向量的方式,但它确实需要大量的内存才能快速运行。例如,查询 100 万个 768 维的 float32 向量需要大约 1,000,0004(768+12)=3120000000字节3GB1,000,000 * 4 * (768 + 12) = 3120000000 bytes \approx 3GB 的 RAM。一旦你开始搜索大量的向量,这就会变得很昂贵。一种使用大约 75%75\% 更少的内存的方法是通过字节量化。Lucene 以及 Elasticsearch 一直支持索引 字节byte 向量一段时间了,但是构建这些向量一直是用户的责任。这种情况即将改变,因为我们引入了 Lucene 中的 int8int8 标量量化。

标量量化 101

所有量化技术都被认为是原始数据的有损转换。这意味着为了节省空间,会丢失一些信息。有关标量量化的深入解释,请参阅:标量量化 101。从高层次来看,标量量化是一种有损压缩技术。一些简单的数学计算可以节省大量空间,而对召回率的影响却很小。

探索架构

习惯使用 Elasticsearch 的用户可能已经熟悉这些概念了,但这里是对搜索文档分布的快速概述。

每个 Elasticsearch 索引都由多个分片组成。虽然每个分片只能分配给单个节点,但每个索引中的多个分片可以在节点之间提供计算并行性。

每个分片都由单个Lucene 索引组成。Lucene 索引由多个只读段组成。在索引过程中,文档会被缓冲并定期刷新到只读段中。当满足某些条件时,这些段可以在后台合并成更大的段。所有这些都是可配置的,并且有其自身的一套复杂性。但是,当我们谈论段和合并时,我们指的是只读 Lucene 段以及这些段的自动定期合并。这里有更深入的探讨段合并和设计决策。

Lucene 中每个分段的量化

Lucene 中的每个分段都存储以下内容:各个向量、HNSW 图索引、量化向量和计算的分位数。为简洁起见,我们将重点关注 Lucene 如何存储量化和原始向量。对于每个分段,我们都会在vecvec 文件中跟踪原始向量,在veqveq 文件中跟踪量化向量和单个校正乘数浮点数,以及vemqvemq 文件中量化的元数据。

图 1:原始向量存储文件的简化布局。占用dimension4numVectorsdimension * 4 * numVectors 的磁盘空间,因为floatfloat 值为 4 个字节。因为我们正在进行量化,所以这些在 HNSW 搜索期间不会被加载。它们仅在明确请求时(例如,通过重新评分的蛮力辅助)或在段合并期间重新量化时使用。

图 2:.veq.veq 文件的简化布局。占用(dimension+4)numVectors(dimension + 4)*numVectors 的空间,并在搜索期间加载到内存中。 +4+ 4 字节用于计算校正乘数浮点数,用于调整评分以获得更好的准确性和召回率。

图 3:元数据文件的简化布局。在这里,我们跟踪量化和向量配置,以及此段计算的分位数。

因此,对于每个段,我们不仅存储量化向量,还存储用于创建这些量化向量的分位数以及原始原始向量。但是,为什么我们 überhaupt die Rohvektoren aufbewahren?

与您一起增长的量化

由于 Lucene 定期刷新到只读段,因此每个段仅对所有数据的 部分视图。这意味着计算出的分位数仅直接适用于整个数据的该样本集。现在,如果您的样本充分代表了您的整个语料库,那么这并不是什么大问题。但是 Lucene 允许您以各种方式对索引进行排序。因此,您可能正在对以某种方式添加偏差以进行每个段分位数计算的方式进行索引数据。此外,您可以随时刷新数据!您的样本集可能很小,甚至只是一个向量。另一个难题是您可以控制合并何时发生。虽然 Elasticsearch 具有配置的默认值和定期合并,但您可以随时通过 _force_merge API 请求合并。那么,我们如何在仍然允许所有这些灵活性的情况下,提供提供良好召回率的良好量化呢?

Lucene 的向量量化将随着时间的推移自动调整。因为 Lucene 的设计采用只读段架构,所以我们可以保证每个段中的数据没有发生变化,并且代码中明确划分了可以更新内容的位置。这意味着在段合并期间,我们可以根据需要调整分位数并可能重新量化向量。

图 4:三个具有不同分位数的示例段。

但是重新量化不昂贵吗?它确实有一些开销,但 Lucene 智能地处理分位数,并且仅在必要时进行完全重新量化。让我们以图 4 中的段为例。让我们给出段AABB 1,0001,000 个文档,并给出段CC100100 个文档。Lucene 将取分位数的加权平均值,如果得到的合并分位数足够接近段的原始分位数,则我们不必重新量化该段,并将利用新合并的分位数。

图 5:合并分位数的示例,其中段AABB10001000 个文档,而 CC 只有 100100

在图 5 中可视化的场景中,我们可以看到,结果合并的分位数与 AABB 中的原始分位数非常相似。因此,它们没有理由量化向量。段 CC 似乎偏差太大。因此,CC 中的向量将使用新合并的分位数值重新量化。

确实存在合并分位数与任何原始分位数发生显着差异的极端情况。在这种情况下,我们将从每个段中提取样本并完全重新计算分位数。

量化性能和数字

那么,它速度快吗?它仍然提供良好的召回率吗?以下数字是在 c3-standard-8 GCP 实例上运行实验收集的。为了确保与float32float32 进行公平的比较,我们使用了足够大的实例来将原始向量存储在内存中。我们使用最大内积索引了 400,000400,000Cohere Wiki 向量。

图 6:量化向量与原始向量的 Recall@10。量化向量的搜索性能明显快于原始向量,并且只需收集多 5 个向量即可快速恢复召回率;这可以通过 quantized@15quantized@15 看出。

图 6 展示了这一情况。虽然存在召回率差异,这是预料之中的,但差异并不显著。而且,只需收集多 5 个向量,召回率差异就会消失。所有这些都实现了 2×2\times 更快的段合并速度,并且内存占用只有 float32float32 向量的 1/4。

结论

Lucene 为一个难题提供了独特的解决方案。量化不需要任何“训练”或“优化”步骤。在 Lucene 中,它会自动生效。如果数据发生变化,您无需担心必须“重新训练”向量索引。Lucene 会检测重大变化,并在您的数据生命周期内自动处理这些变化。期待我们何时将此功能引入 Elasticsearch!

Elasticsearch 充满了新功能,可以帮助您为您的用例构建最佳的搜索解决方案。深入了解我们的 示例笔记本 以了解更多信息,开始 免费云试用,或立即在您的 本地机器 上尝试 Elastic。

准备好构建最先进的搜索体验了吗?

充分先进的搜索并非一人之力所能及。Elasticsearch 由数据科学家、ML 运维人员、工程师以及许多其他对搜索同样充满热情的人员提供支持,就像您一样。让我们联系起来,共同努力构建神奇的搜索体验,让您获得想要的结果。

亲自尝试