JVector:一个纯Java嵌入式矢量搜索引擎

💡 原文中文,约2700字,阅读约需7分钟。
📝

内容提要

JVector是一个纯Java嵌入式矢量搜索引擎,使用DataStax Astra DB和Apache Cassandra。它具有快速的算法、实施速度快、内存高效、磁盘感知等特点。JVector与Lucene搜索Deep100M数据集的性能相比提高了10倍。它易于嵌入,并且可以使用乘积量化来压缩向量以提高搜索速度。JVector还提供了索引构建和搜索的高级配置选项。

🔎

延伸解读

性能优势与适用场景

JVector 在 Deep100M 数据集上的搜索性能达到 Lucene 的 10 倍,这得益于其 DiskANN 式图算法和巴拿马 SIMD 加速。对于需要处理大规模向量数据且对延迟敏感的应用,如推荐系统或语义搜索,JVector 的磁盘感知设计和乘积量化能有效降低内存占用,同时保持高召回率。但需注意,其性能优势在特定数据集上验证,实际效果可能因数据分布和硬件环境而异。

索引构建与实时搜索

JVector 支持索引构建的线性扩展,至少可扩展至 32 个线程,且构建过程中即可查询索引,新添加的向量能立即被搜索到,无延迟。这适合需要实时更新向量的场景,如动态内容推荐。GraphIndexBuilder 提供并行构建和非阻塞添加接口,但需注意,在添加完向量后调用 cleanup 可优化索引,不过不调用也不影响搜索,只是可能影响后续写入性能。

量化选择:PQ 与 BQ 的权衡

JVector 提供乘积量化(PQ)和二值量化(BQ)两种压缩方式。PQ 更灵活且损耗更小,在多数数据集上能实现更高召回率;BQ 计算和搜索速度更快,但仅适用于向量维度高或召回要求不严格的场景。例如,在维基百科数据集的 ada002 向量上,BQ 才能与 PQ 竞争召回率。选择时需根据向量大小和召回需求权衡速度与精度。

高级配置与性能调优

JVector 利用巴拿马矢量 API 进行 SIMD 加速,但可能因内存带宽饱和导致性能下降。为此,索引和 PQ 构建使用 PhysicalCoreExecutor 限制并发操作数为物理核心数的一半,默认基于 Java 可见处理器数。在无超线程或混合架构下,默认值可能不适用,可通过 -Djvector.physical_core_count 属性覆盖。调优时需关注硬件特性,避免过度并发引起资源争用。

❓

Q&A

JVector是什么?它有什么主要特点?

JVector是一个纯Java嵌入式矢量搜索引擎,由DataStax Astra DB和Apache Cassandra使用。其主要特点包括:算法快(使用受DiskANN启发的图形算法)、实施速度快(使用巴拿马SIMD API)、内存高效(使用乘积量化压缩向量)、磁盘感知(磁盘布局最小化查询时的IOPS)、支持并发索引构建(线性扩展至至少32个线程)、支持在构建索引时查询(添加向量与搜索到它之间无延迟)、易于嵌入。

JVector与Lucene在性能上相比如何?

JVector在搜索Deep100M数据集(约35GB向量和25GB索引)时,性能是Lucene的10倍。

如何使用JVector构建索引?

使用GraphIndexBuilder作为构建图表的入口点。需要实现RandomAccessVectorValues向构建器提供向量,ListRandomAccessVectorValues是一个好的起点。如果所有向量已预先提供,调用build()即可在所有可用核心上并行构建;否则可以调用addGraphNode在添加向量时非阻塞地构建,并可从多个线程同时调用。添加完向量后调用GraphIndexBuilder.cleanup来优化索引并准备写入磁盘。

JVector如何实现乘积量化来压缩向量?

JVector通过以下步骤启用乘积量化:1. 使用ProductQuantization.compute或BinaryQuantization.compute创建VectorCompressor对象;2. 使用VectorCompressor::encode或encodeAll对向量进行编码,然后调用VectorCompressor::createCompressedVectors创建CompressedVectors对象;3. 调用CompressedVectors::approximateScoreFunctionFor为查询创建一个NeighborSimilarity.ApproximateScoreFunction,并将其传递给GraphSearcher.search方法。

JVector如何保存和加载索引?

OnDiskGraphIndex和CompressedVectors都有write()方法将状态保存到磁盘,并分别使用构造函数和load()方法从磁盘初始化。写入只需要一个DataOutput,但读取需要实现RandomAccessReader并封装ReaderSupplier以获得最佳性能。可以参考SimpleMappedReader和SimpleMappedReaderSupplier的示例。

JVector的高级配置选项有哪些?

JVector大量利用巴拿马矢量API(SIMD)进行ANN索引和搜索。为了避免内存带宽饱和导致过程变慢,索引和PQ构建使用PhysicalCoreExecutor来限制操作量,默认值为Java看到的处理器数量的1/2。可以使用-Djvector.physical_core_count属性覆盖默认值。

🏷️

标签

➡️

继续阅读