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


JVector 是一个纯 Java 嵌入式矢量搜索引擎,由DataStax Astra DB和(即将推出)Apache Cassandra 使用。开源项目点击标题
什么是JVector?

  • 算法快。 JVector 使用受 DiskANN 和相关研究启发的最先进的图形算法,可提供高召回率和低延迟。
  • 实施速度快。 JVector 使用巴拿马 SIMD API 来加速索引构建和查询。
  • 内存高效。 JVector 使用乘积量化来压缩向量,以便它们可以在搜索期间保留在内存中。 (作为 PQ 实现的一部分,我们的 SIMD 加速 kmeans 类比 Apache Commons Math 中的类快 5 倍。)
  • 磁盘感知。 JVector 的磁盘布局旨在在查询时执行最少的必要 iops。
  • 同时。索引构建线性扩展至至少 32 个线程。线程加倍,构建时间减半。
  • 增加的。在构建索引时查询索引。添加矢量与在搜索结果中找到它之间没有任何延迟。
  • 易于嵌入。 API 专为在生产中使用它的人们轻松嵌入而设计。

JVector 与 Lucene 搜索 Deep100M 数据集(约 35GB 向量和 25GB 索引)性能比较:是其10倍性能

依赖:

<dependency>        
    <groupId>io.github.jbellis</groupId>          
    <artifactId>jvector</artifactId>
    <!-- Use the latest version from https://central.sonatype.com/artifact/io.github.jbellis/jvector -->
    <version>${latest-version}</version>
</dependency>

建立索引:

  • GraphIndexBuilder是构建图表的入口点。您需要实现 RandomAccessVectorValues向构建器提供向量; ListRandomAccessVectorValues是一个很好的起点。
  • 如果您的所有向量都预先位于提供程序中,您只需调用即可build(),它将在所有可用核心上并行构建。否则你可以addGraphNode在添加向量时调用;这是非阻塞的,可以从多个线程同时调用。
  • GraphIndexBuilder.cleanup添加完向量后调用。这将优化索引并使其准备好写入磁盘。 (可以随时搜索正在构建的图表;您不必 cleanup先调用。)

搜索索引:

  • GraphSearcher是搜索的入口点。结果作为包含节点 ID 和分数的对象返回SearchResult,按照与查询向量的相似度降序排列。 GraphSearcher对象是可重用的,因此除非您有一个非常简单的用例,否则您应该使用GraphSearcher.Builder它们来创建它们;GraphSearcher.search也可以使用简单的默认值,但调用它GraphSearcher每次都会实例化一个新的,因此性能会更差。
  • JVector 将索引中的向量表示为与RandomAccessVectorValues您提供的向量中的索引相对应的序数 (int)。如有必要,您可以使用 获取原始向量GraphIndex.getVector,但由于这是磁盘支持的索引,因此您应该设计应用程序以避免不必要的操作。

DiskANN 和乘积量化
JVector 实现DiskANN式搜索,这意味着可以使用乘积量化来压缩向量,以便可以使用保存在内存中的压缩表示来执行搜索。您可以通过以下步骤启用此功能:

  • VectorCompressor使用向量创建一个对象ProductQuantization.compute
  • 或者BinaryQuantization.compute。 PQ 比 BQ 更灵活且损耗更小:即使在相同的压缩大小下,在 Bench 测试的数据集中,只有维基百科数据集中的 ada002 向量足够大和/或过度参数化,足以从 BQ 中受益,同时实现与 BQ 竞争的召回率。 PQ。但是,如果您正在处理非常大的向量和/或您的召回要求不严格,您可能仍然想尝试 BQ,因为它的计算和搜索速度要快得多。
  • 使用VectorCompressor::encode或encodeAll对向量进行编码,然后调用 VectorCompressor::createCompressedVectors以创建CompressedVectors对象。
  • 调用为您的查询CompressedVectors::approximateScoreFunctionFor创建一个NeighborSimilarity.ApproximateScoreFunction使用压缩向量来加速搜索的查询,并将其传递给该GraphSearcher.search方法。

保存和加载索引
  • OnDiskGraphIndexCompressedVectors有write()方法将状态保存到磁盘。它们load()分别使用构造函数和方法从磁盘进行初始化。写入只需要一个 DataOutput,但读取需要实现RandomAccessReader并封装ReaderSupplier您首选的 i/o 类以获得最佳性能。请参阅SimpleMappedReader和SimpleMappedReaderSupplier的示例。
  • 从技术上讲,构建图表并不需要您的 RandomAccessVectorValues 对象位于内存中,但如果这样做的话,它的性能会更好。 OnDiskGraphIndex相比之下,它被设计为驻留在磁盘上并使用最少的内存。
  • 您可以选择将最常访问的节点(距离图形入口点最近的节点)封装在内存中OnDiskGraphIndex。CachingGraphIndex

高级配置
  • JVector 大量利用巴拿马矢量 API (SIMD) 进行 ANN 索引和搜索。我们已经看到过在索引和乘积量化期间内存带宽饱和的情况,并可能导致过程变慢。为了避免这种情况,索引和 PQ 构建使用 aPhysicalCoreExecutor 来限制物理核心数量的操作量。默认值是 Java 看到的处理器数量的 1/2。这在所有设置中可能并不正确(例如,无超线程或混合架构),因此如果您希望覆盖默认值,请使用该-Djvector.physical_core_count属性。