在 RAG 与 LLM Memory 中,向量检索被广泛使用,其中图索引因其高精度和高性能成为最常见的选择,HNSW 索引就是最具代表性的一种【文献 1】【文献 2】。
然而,在我们将 HNSW 应用于 RAGFlow 的实践过程中,遇到了以下两个主要瓶颈:
随着数据规模不断增长,向量数据对内存的占用非常显著。例如,十亿条 1024 维的浮点数向量,在内存中需要占用约 4TB 的空间;
在复杂数据集上构建 HNSW 索引时,其检索精度存在瓶颈。当达到某一阈值后,仅通过调整参数很难进一步提升精度。
为此,Infinity 实现了多种基于 HNSW 的改进算法,用户可以通过调整 HNSW 的索引参数选择不同的索引方案。每种 HNSW 索引变体都具备不同的特性与适用场景,使用者可以根据实际需求构建相应的索引结构。
索引方案介绍
原始的 HNSW 作为一种常用的图索引,具备良好的性能。
其结构由两部分组成:
一组原始向量数据,以及一个由跳表和邻接表共同构建的图结构。以 Python 接口为例,可以通过以下方式构建并使用该索引:
## 创建索引table_obj.create_index( "hnsw_index", index.IndexInfo( "embedding", index.IndexType.Hnsw, { "m": 16, "ef_construction": 200, "metric": "l2" }, ))## 向量检索query_builder.match_dense('embedding', [1.0, 2.0, 3.0], 'float', 'l2', 10, {'ef': 200})
针对内存占用大和精度瓶颈的问题,Infinity 提供了以下解决方案:
引入 LVQ 和 RaBitQ 量化方法,降低图搜索过程中原始向量的内存开销;
引入 LSG 策略,优化 HNSW 的图索引结构,提升其精度阈值和查询效率。
为了方便用户在本机测试不同索引的性能,Infinity 提供了配套的 benchmark 脚本。您可以按照 Infinity 在 GitHub 上的教程,安装环境并准备好数据集,然后通过 benchmark 测试不同的索引方案。
###################### 编译 benchmark ######################cmake --build cmake-build-release --target hnsw_benchmark###################### 构建索引 & 执行查询 ####################### mode : build, query# benchmark_type : sift, gist, msmarco# build_type : plain, lvq, crabitq, lsg, lvq_lsg, crabitq_lsg##############################################################benchmark_type=siftbuild_type=plain./cmake-build-release/benchmark/local_infinity/hnsw_benchmark --mode=build --benchmark_type=$benchmark_type --build_type=$build_type --thread_n=8./cmake-build-release/benchmark/local_infinity/hnsw_benchmark --mode=query --benchmark_type=$benchmark_type --build_type=$build_type --thread_n=8 --topk=10
其中,原始 HNSW 对应参数 build_type=plain。
本文对所有变种索引的查询性能进行了统一测试,所采用的实验环境配置如下:
OS: Ubuntu 24.04 LTS (Noble Numbat)
CPU: 13th Gen Intel(R) Core(TM) i5-13400
RAM: 64G
该 CPU 为 16 核心规格。为贴合大多数用户的实际设备环境,benchmark 中的并行计算参数统一设置为 8 线程。
方案一:HNSW + LVQ 量化器
LVQ是一种标量量化方法,它将原始向量中的每个 32 位浮点数压缩为一个 8 位整数【文献 3】,从而内存占用降低至原始向量的四分之一。
与简单的标量量化方法(如均值标量量化)相比,LVQ 通过统计每条向量的残差来减少误差,有效降低了量化后向量在距离计算中的信息损失。因此,LVQ 能够以仅约 30% 的内存占用,较为准确地估计原始向量之间的距离。
在 HNSW 算法中,原始向量用于距离计算,这使得 LVQ 能够直接与 HNSW 结合使用。我们将这种结合后的方法称为 HnswLvq。在 Infinity 中,用户可以通过设置 "encode": "lvq"参数来启用 LVQ 编码:
## 创建索引table_obj.create_index( "hnsw_index", index.IndexInfo( "embedding", index.IndexType.Hnsw, { "m": 16, "ef_construction": 200, "metric": "l2", "encode": "lvq" }, ))## 向量检索query_builder.match_dense('embedding', [1.0, 2.0, 3.0], 'float', 'l2', 10, {'ef': 200})
HnswLvq 的图结构与原始 HNSW 保持一致,其核心区别在于使用量化后的向量完成图中所有的距离计算。经过这一改进,HnswLvq 在索引构建和查询效率上均优于原始 HNSW 索引。
构建效率的提升源于量化后向量长度更短,从而 SIMD 指令进行距离计算的时间更短;
查询效率的提升则是因为量化带来的计算加速效果,超过了因精度损失所带来的负面影响。
综上所述,HnswLvq 在显著减少内存占用的同时,仍保持了优异的查询性能。我们推荐用户在多数场景中将其作为主力索引使用。如需复现该实验,可在 benchmark 中设置参数 build_type=lvq,具体实验结果在方案二中与 RaBitQ 量化器方案一起进行对比。
方案二:HNSW + RaBitQ 量化器
RaBitQ 是一种二进制标量量化方法,其核心思路与 LVQ 类似,都旨在用更少的编码位数替代原始向量中的 32 位浮点数。不同之处在于,RaBitQ 采用二进制标量量化,每个浮点数仅使用 1 位表示,从而实现极高的压缩比。
但这种极致的压缩也会带来更明显的向量信息损失,导致距离估计的准确性下降。为缓解这一问题,RaBitQ 在预处理阶段引入旋转矩阵处理数据集,并保留更多残差信息,从而在一定程度上减少了距离计算中的误差【文献 4】。
不过,二进制量化在精度上存在明显局限,与 LVQ 相比差距较大。直接使用 RaBitQ 编码构建的索引在查询性能上表现较差。
因此,Infinity 所实现的 HnswRabitq 方案是,先为数据集构建原始 HNSW 索引,再通过optimize方法中的compress_to_rabitq参数将其转换为 HnswRabitq 索引。
查询过程则是,先使用量化向量进行初步检索,再对用户设定的 ef 个候选结果,使用原始向量进行重新排序。
## 创建索引table_obj.create_index( "hnsw_index", index.IndexInfo( "embedding", index.IndexType.Hnsw, { "m": 16, "ef_construction": 200, "metric": "l2" }, ))## 构建 RaBitQ 编码table_obj.optimize("hnsw_index", {"compress_to_rabitq": "true"})## 向量检索query_builder.match_dense('embedding', [1.0, 2.0, 3.0], 'float', 'l2', 10, {'ef': 200})
与 LVQ 相比,RaBitQ 可进一步降低编码向量近 70% 的内存占用。在部分数据集上,HnswRabitq 的查询效率甚至优于 HnswLvq,这是因为二进制量化后的距离计算效率更高。
但也需注意,在某些数据集(如 sift1M)上,量化过程可能导致严重的精度损失,这类数据集并不适合使用 HnswRabitq。

总而言之,若用户的数据集对量化误差不敏感,采用 HnswRabitq 索引可在显著降低内存开销的同时,仍保持较好的查询性能。
在此类场景下,推荐优先使用 HnswRabitq 索引。用户可通过设置 benchmark 参数 build_type=crabitq来复现上述实验。
方案三:LSG 构图策略
LSG(局部缩放图)是一种基于图索引算法(如 HNSW、DiskANN 等)的改进构图策略【文献 5】。
该策略通过统计数据集中每个向量的局部信息——邻域半径,来缩放任意两个向量之间的距离(例如 L2 距离、内积距离等),缩放后的距离称为 LS 距离。
在图索引的构建过程中,LSG 将原本的距离度量统一替换为 LS 距离,这相当于对原始度量空间做了一次“局部缩放”。论文通过证明与实验表明,在这一缩放后的空间中构建图索引,在原始空间中能够表现出更优的查询性能。
LSG 对 HNSW 索引的优化体现在多个方面。在精度要求相对宽松(<99%)时,LSG 相较原始 HNSW 索引表现出更高的 QPS;
而在高精度场景(>99%)下,LSG 提升了图索引的质量,使得 HNSW 能够突破原有精度上限,取得原始 HNSW 索引难以达到的检索准确率。这些改进在 RAGFlow 的实际应用中,为用户带来了更快的响应速度和更精确的查询结果。
在 Infinity 中,LSG 作为 HNSW 的一个可选参数提供,用户可通过设置build_type=lsg来启用该构图策略,我们将与之对应的索引称为 HnswLsg。
## 创建索引table_obj.create_index( "hnsw_index", index.IndexInfo( "embedding", index.IndexType.Hnsw, { "m": 16, "ef_construction": 200, "metric": "l2", "build_type": "lsg" }, ))## 向量检索query_builder.match_dense('embedding', [1.0, 2.0, 3.0], 'float', 'l2', 10, {'ef': 200})
LSG 本质上是改变了索引构建过程中的度量空间,因此它不仅可用于原始 HNSW,还可与量化方法(如 LVQ 或 RaBitQ)结合使用,形成诸如 HnswLvqLsg 或 HnswRabitqLsg 等变种索引。用户接口的使用方式与 HnswLvq 和 HnswRabitq 保持一致。
LSG 能够为绝大多数图索引和数据集带来性能提升,但其代价是构图阶段需额外计算局部信息——邻域半径,因此会增加一定的构建时间。例如在 sift1M 数据集上,HnswLsg 的构建时间约为原始 HNSW 的 1.2 倍。
综上所述,若用户对索引构建时间不敏感,可放心启用 LSG 选项,它能够稳定地提升查询性能。用户可通过将 benchmark 参数设置为build_type=[lsg/lvq_lsg/crabitq_lsg]来复现上述实验。
索引性能评估
为评估 Infinity 中各类索引的性能,我们选取了三个具有代表性的数据集作为基准,其中包括在向量索引评估中被广泛使用的 sift 和 gist 数据集。
由于 Infinity 当前的使用场景常与 RAGFlow 结合,RAG 类数据集的检索效果对用户评估索引性能尤为关键。
因此,我们还引入了 msmarco 数据集。该数据集基于 Cohere Embed English v3 模型对 TREC-RAG 2024 语料库进行编码生成,包含 1.135 亿个文本段落的嵌入向量,以及来自 TREC-Deep Learning 2021-2023 的 1677 条查询指令所对应的嵌入向量。
从各数据集的测试结果可以看出,在大多数情况下,HnswRabitqLsg 能够取得最佳的综合性能。例如在 RAG 场景的 msmarco 数据集上,RaBitQ 实现了 90% 的内存节省,同时在 99% 的召回率下,其查询性能达到了原始 HNSW 的 5 倍。
基于以上实验结果,我们为 Infinity 用户提供以下实践建议:
原始 HNSW 能够达到比 HnswLvq 和 HnswRabitq 更高的精度上限,若用户对精度有极高要求,可优先选择该策略;
在精度允许的范围内,大多数数据集上可放心选用 HnswLvq 索引,而对于不易受量化影响的数据集,HnswRabitq 通常是更优选择;
LSG 策略对所有索引变体均可带来性能提升,若用户对索引构建时间不敏感,在所有场景下都建议启用该选项以提升查询效率。此外,凭借其算法特性,LSG 能够显著提升精度上限,因此若使用场景对精度要求极高(>99.9%),强烈建议开启 LSG 以优化索引表现。
Infinity 仍在持续迭代与进步
欢迎大家持续提出宝贵意见与建议

引用文献
欢迎大家持续关注和 Star Infinity
https://github.com/infiniflow/infinity

