高性能向量索引库 支持图索引和大规模搜索
JVector是一个基于图的向量索引库,采用DiskANN设计并支持可组合扩展。它实现单层图和非阻塞并发控制,具有线性扩展能力。该库提供两阶段搜索、量化压缩和大于内存的索引构建功能,有效降低内存使用并提升搜索速度。JVector主要用于大规模近似最近邻搜索,为高维向量检索提供高效方案。
在高维空间中,精确的最近邻搜索(k-最近邻或KNN)的计算成本过高,因为在二维或三维中有效的搜索空间分割方法(如四叉树或k-d树)在高维中退化为线性扫描。这是所谓的"维度灾难"的一个方面。
对于大型数据集,以对数时间获得近似答案几乎总是比以线性时间获得精确答案更有用。这被简称为ANN(近似最近邻)搜索。
ANN索引主要分为两大类:
图索引通常更容易实现且速度更快,更重要的是它们可以增量构建和更新。这使得它们比只能在预先完全指定的静态数据集上工作的分区方法更适合作为通用索引。这就是为什么所有主要的商业向量索引都使用图方法的原因。
JVector是DiskANN家族中的一个图索引。
JVector是一个基于图的索引,在DiskANN设计的基础上增加了可组合的扩展。
JVector实现了一个具有非阻塞并发控制的单层图,允许构建过程随着核心数量的增加而线性扩展:
图以每个节点的磁盘邻接表表示,内联存储额外数据以支持两遍搜索。第一遍由内存中保存的向量的有损压缩表示驱动,第二遍由从磁盘读取的更精确表示驱动。第一遍搜索可以通过以下方式执行:
第二遍搜索可以使用:
这种两遍设计减少了内存使用并降低了延迟,同时保持了准确性。
此外,JVector独特地提供了使用两遍搜索构建索引本身的能力,允许构建大于内存的索引:
这一点很重要,因为它允许您在单个索引中利用对数级搜索,而不是溢出到多个索引的线性时间合并结果。
所有代码示例都来自JVector源代码库中的SiftSmall,其中还包括siftsmall数据集。只需在IDE中导入项目并点击运行即可尝试!
首先是代码:
public static void siftInMemory(ArrayList<VectorFloat<?>> baseVectors) throws IOException { // 从第一个向量推断维度 int originalDimension = baseVectors.get(0).length(); // 将原始向量包装在RandomAccessVectorValues中 RandomAccessVectorValues ravv = new ListRandomAccessVectorValues(baseVectors, originalDimension); // 使用原始的内存中向量的分数提供者 BuildScoreProvider bsp = BuildScoreProvider.randomAccessScoreProvider(ravv, VectorSimilarityFunction.EUCLIDEAN); try (GraphIndexBuilder builder = new GraphIndexBuilder(bsp, ravv.dimension(), 16, // 图度 100, // 构建搜索深度 1.2f, // 构建过程中允许度溢出的因子 1.2f)) // 放宽邻居多样性要求的因子 { // 构建索引(在内存中) OnHeapGraphIndex index = builder.build(ravv); // 搜索随机向量 VectorFloat<?> q = randomVector(originalDimension); SearchResult sr = GraphSearcher.search(q, 10, // 结果数量 ravv, // 我们搜索的向量,用于评分 VectorSimilarityFunction.EUCLIDEAN, // 评分方式 index, Bits.ALL); // 要考虑的有效序号 for (SearchResult.NodeScore ns : sr.getNodes()) { System.out.println(ns); } } }
注释:
保持Builder不变,更新后的搜索代码如下:
// 使用GraphSearcher和SearchScoreProvider搜索随机向量 VectorFloat<?> q = randomVector(originalDimension); try (GraphSearcher searcher = new GraphSearcher(index)) { SearchScoreProvider ssp = SearchScoreProvider.exact(q, VectorSimilarityFunction.EUCLIDEAN, ravv); SearchResult sr = searcher.search(ssp, 10, Bits.ALL); for (SearchResult.NodeScore ns : sr.getNodes()) { System.out.println(ns); } }
注释:
如果一个速度极快的向量索引无法返回准确的结果,那么它就没有多大用处。作为健全性检查,SiftSmall包含一个辅助方法_testRecall_。将其连接到我们的代码主要涉及将SearchScoreProvider转换为工厂lambda:
Function<VectorFloat<?>, SearchScoreProvider> sspFactory = q -> SearchScoreProvider.exact(q, VectorSimilarityFunction.EUCLIDEAN, ravv); testRecall(index, queryVectors, groundTruth, sspFactory);
如果运行代码,您会看到每次召回率略有不同(由testRecall
打印):
(OnHeapGraphIndex) Recall: 0.9898
...
(OnHeapGraphIndex) Recall: 0.9890
考虑到所创建索引的近似性质以及build
调用的多线程并发引入的扰动,这是预期的结果。
代码:
Path indexPath = Files.createTempFile("siftsmall", ".inline"); try (GraphIndexBuilder builder = new GraphIndexBuilder(bsp, ravv.dimension(), 16, 100, 1.2f, 1.2f)) { // 构建索引(在内存中) OnHeapGraphIndex index = builder.build(ravv); // 使用默认选项将索引写入磁盘 OnDiskGraphIndex.write(index, ravv, indexPath); } // 磁盘上的索引需要一个ReaderSupplier(而不仅仅是Reader),因为我们希望它为搜索打开额外的读取器 ReaderSupplier rs = new SimpleMappedReaderSupplier(indexPath); OnDiskGraphIndex index = OnDiskGraphIndex.load(rs); // 根据(精确计算的)基准真值测量我们的召回率 Function<VectorFloat<?>, SearchScoreProvider> sspFactory = q -> SearchScoreProvider.exact(q, VectorSimilarityFunction.EUCLIDEAN, ravv); testRecall(index, queryVectors, groundTruth, sspFactory);
注释:
第5步:在搜索中使用压缩向量
使用乘积量化压缩向量的方法如下:
// 计算并将压缩向量写入磁盘 Path pqPath = Files.createTempFile("siftsmall", ".pq"); try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(pqPath)))) { // 使用PQ压缩原始向量。这代表了128 * 4 / 16 = 32倍的压缩比 ProductQuantization pq = ProductQuantization.compute(ravv, 16, // 子空间数量 256, // 每个子空间的质心数量 true); // 对数据集进行中心化 ByteSequence<?>[] compressed = pq.encodeAll(ravv); // 将压缩向量写入磁 盘 PQVectors pqv = new PQVectors(pq, compressed); pqv.write(out); }
然后我们可以通过从PQVectors获取快速ApproximateScoreFunction,并从索引View获取Reranker来连接压缩向量到两阶段搜索:
ReaderSupplier rs = new MMapReaderSupplier(indexPath); OnDiskGraphIndex index = OnDiskGraphIndex.load(rs); // 加载我们刚刚写入磁盘的PQVectors try (RandomAccessReader in = new SimpleMappedReader(pqPath)) { PQVectors pqv = PQVectors.load(in); // SearchScoreProvider首先使用加载到内存中的PQVectors进行初步筛选, // 然后使用存储在索引磁盘中的精确向量进行重新排序 Function<VectorFloat<?>, SearchScoreProvider> sspFactory = q -> { ApproximateScoreFunction asf = pqv.precomputedScoreFunctionFor(q, VectorSimilarityFunction.EUCLIDEAN); Reranker reranker = index.getView().rerankerFor(q, VectorSimilarityFunction.EUCLIDEAN); return new SearchScoreProvider(asf, reranker); }; // 对照(精确计算的)真实结果测量我们的召回率 testRecall(index, queryVectors, groundTruth, sspFactory); }
这组功能是经典的DiskANN设计。
第6步:构建大于内存的索引
JVector还可以应用PQ压缩来允许索引大于内存的数据集:只在内存中保留压缩向量。
首先,我们需要设置一个可以添加新向量的PQVectors实例,以及使用它的BuildScoreProvider:
// 计算码本,但暂不编码任何向量 ProductQuantization pq = ProductQuantization.compute(ravv, 16, 256, true); // 在构建索引时,我们将压缩新向量并将其添加到这个支持PQVectors的List中; // 这用于对构建搜索进行评分 List<ByteSequence<?>> incrementallyCompressedVectors = new ArrayList<>(); PQVectors pqv = new PQVectors(pq, incrementallyCompressedVectors); BuildScoreProvider bsp = BuildScoreProvider.pqBuildScoreProvider(VectorSimilarityFunction.EUCLIDEAN, pqv);
然后我们需要设置一个OnDiskGraphIndexWriter,以完全控制构建过程。
Path indexPath = Files.createTempFile("siftsmall", ".inline"); Path pqPath = Files.createTempFile("siftsmall", ".pq"); // Builder创建看起来大致相同 try (GraphIndexBuilder builder = new GraphIndexBuilder(bsp, ravv.dimension(), 16, 100, 1.2f, 1.2f); // 首次显式使用Writer,这是OnDiskGraphIndex.write背后的内容 OnDiskGraphIndexWriter writer = new OnDiskGraphIndexWriter.Builder(builder.getGraph(), indexPath) .with(new InlineVectors(ravv.dimension())) .withMapper(new OnDiskGraphIndexWriter.IdentityMapper()) .build(); // 压缩向量的输出 DataOutputStream pqOut = new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(pqPath))))
完成后,我们可以一次索引一个向量:
// 逐个向量构建索引(在磁盘上) for (VectorFloat<?> v : baseVectors) { // 压缩新向量并将其添加到PQVectors(通过incrementallyCompressedVectors) int ordinal = incrementallyCompressedVectors.size(); incrementallyCompressedVectors.add(pq.encode(v)); // 将完整向量写入磁盘 writer.writeInline(ordinal, Feature.singleState(FeatureId.INLINE_VECTORS, new InlineVectors.State(v))); // 现在将其添加到图中 -- 之前的步骤必须先完成,因为在addGraphNode构建过程中运行的搜索会使用PQVectors和InlineVectorValues builder.addGraphNode(ordinal, v); }
最后,我们需要运行cleanup()并将索引和PQVectors写入磁盘:
// cleanup进行最终的maxDegree强制执行,并处理其他场景,如删除的节点 // 在这里我们不需要担心这些 builder.cleanup(); // 完成索引写入(通过填充边列表)并写入我们完成的PQVectors writer.write(Map.of()); pqv.write(pqOut);
注释:
-Djvector.physical_core_count
属性,或传入您自己的ForkJoinPool实例。本项目组织为多模块Maven构建。目的是生成一个适合作为任何Java 11代码依赖的多版本jar包。当在启用Vector模块的Java 20+JVM上运行时,将使用优化的向量提供程序。一 般来说,项目结构适合用JDK 20+构建,但当JAVA_HOME设置为Java 11到Java 19时,某些构建功能仍然可用。
基础代码在jvector-base中,将为Java 11版本构建,相应地限制语言特性和API。jvector-twenty中的代码将针对Java 20语言特性/API编译,并包含在最终的多版本jar中,面向支持的JVM。jvector-multirelease将jvector-base和jvector-twenty打包为发布用的多版本jar。jvector-examples是一个额外的同级模块,使用jvector-base/jvector-twenty的reactor表示来运行示例代码。jvector-tests包含项目的测试,能够在Java 11和Java 20+ JVM上运行。
要运行测试,使用mvn test
。要在Java 20+上运行测试,使用mvn test
。要在Java 11上运行测试,使用mvn -Pjdk11 test
。要运行单个测试类,使用Maven Surefire测试过滤功能,例如,mvn -Dtest=TestNeighborArray test
。你也可以使用方法级过滤和模式,例如,mvn -Dtest=TestNeighborArray#testRetain* test
。
你可以直接运行SiftSmall
和Bench
来了解这里发生的一切。Bench
将自动下载所需的数据集到fvec
和hdf5
目录。SiftSmall
使用的文件可以在项目根目录的siftsmall目录中找到。
要运行这两个类,你可以使用Maven exec-plugin通过以下命令:
mvn compile exec:exec@bench
或者对于Sift:
mvn compile exec:exec@sift
Bench
接受一个可选的benchArgs
参数,可以设置为以空格分隔的正则表达式列表。如果提供的任何正则表 达式在数据集名称中匹配,该数据集将被包含在基准测试中。例如,要只运行glove和nytimes数据集,你可以使用:
mvn compile exec:exec@bench -DbenchArgs="glove nytimes"
要在没有JVM向量模块的情况下运行Sift/Bench,你可以使用以下命令:
mvn -Pjdk11 compile exec:exec@bench
mvn -Pjdk11 compile exec:exec@sift
... -Pjdk11
调用也适用于JAVA_HOME指向Java 11安装的情况。
要发布,请配置~/.m2/settings.xml
以指向OSSRH,然后运行mvn -Prelease clean deploy
。
AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
AI小说写作助手,一站式润色、改写、扩写
蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word, 让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号