ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

向量数据库索引算法详解:HNSW、LSH、PQ 实战选型指南

向量数据库索引算法详解:HNSW、LSH、PQ 实战选型指南 向量数据库里的 HNSW、LSH、PQ 到底在说什么一个老程序员给你的大实话这两年只要你在搞 AI 应用尤其是做大模型知识库、RAG 检索、推荐系统这类东西肯定绕不开向量数据库这四个字。去翻文档、看技术方案刷几篇文章满屏都是 Milvus、Chroma、Qdrant然后紧接着就是 HNSW、LSH、PQ 这一堆抽象术语。我最早看到这些概念的时候也是一头雾水每个字母都认识放在一起完全不知道在讲什么看官方文档能看睡着。后来因为项目里要处理千万级别的向量检索硬着头皮把这块啃了一遍又把 Milvus、Qdrant 这些开源项目翻来覆去调参才算是把这些东西串起来了。今天我就用做项目的角度把这些概念掰开了讲清楚。这篇文章不是从文档里抄定义而是告诉你它们到底怎么工作、适合什么场景、怎么选型以及我在实际项目中踩过的坑。这几种东西说白了都是为了让计算机在成千上万、甚至上亿条向量数据里快速找到跟你要查询的那条最相似的记录。如果你能理解这个目的接下来的内容就不会绕晕。1. 先搞明白向量检索到底在干什么1.1 万物皆可向量化现在大模型火起来之后文本、图片、音频这些非结构化数据都被模型转换成了一串固定长度的数字数组比如[0.12, 0.56, -0.23, ...]这就是向量。通俗点说向量就是数据在数学空间里的坐标。两个向量越接近就代表它们在语义上越相似。比如你搜小猫模型会把猫咪和橘猫的向量拉得很近而卡车的向量离得就很远。做知识库问答时用户提问会被转成一个向量然后拿着这个向量去库里面找最匹配的那一块文本片段再拼给大模型做回答。这个过程就是向量检索。1.2 最简单的方法为什么不可行最原始的检索方法是暴力搜索把所有向量挨个算一遍相似度。算法上这叫最近邻搜索NN效果绝对准确但问题是计算量太大。假如你有 1000 万条向量来一条查询请求就要算 1000 万次向量之间的距离每次还涉及几十上百维的浮点运算。就算你的机器再厉害光是一次请求就能把 CPU 打满延迟扛不住。所以工业界真正用的是一个变体叫近似最近邻搜索ANN。ANN 不追求绝对准确而是用索引结构来大幅缩小搜索范围牺牲一点点精度换来几个数量级的速度提升。HNSW、LSH、PQ 这三个词本质上就是三种不同的索引方案。理解清楚这一点后面你读任何数据库的文档都会轻松很多。2. HNSW 详解靠图找路的算法2.1 从跳表到多层图HNSW 的全称是 Hierarchical Navigable Small World中文叫层级可导航小世界网络。这是目前应用最广泛、综合表现最好的索引算法Milvus 和 Qdrant 的默认索引基本都是它。它解决的核心问题是怎么在一堆点里面快速找到目标点。你可以想象成在一个陌生城市里找人你不可能挨家挨户敲门而是先去主干道询问方向再到次干道细化最后进入小胡同精确定位。HNSW 正是基于这个思路设计了多层图结构。上面几层是最稀疏的连接的都是距离很远的点像一个城市的几条主干道。下面几层是密集的网络精确连接每个点之间的关系。查询时从最顶层出发在每个层内通过贪心算法找最近的点然后逐步下探到下一层继续找直到到达最底层。整个过程就像从高空俯瞰城市一层层缩小包围圈最后精准定位到目标。2.2 关键词参数和实验数据HNSW 有两个非常重要的参数一个是M代表每个节点最多连接的邻居数一个是efConstruction表示建图时考虑候选集的大小。我在实际项目中测试过不同参数对检索效果的影响这里整理了一些经验值供参考参数设置效果感受M 16efConstruction 200默认配置适合大多数人索引大小适中召回率约95%以上速度不错M 32efConstruction 400高精度偏好召回率提升至98%以上但内存占用几乎翻倍M 8efConstruction 100低资源环境召回率降到90%左右速度更快索引体积小另外还有一个efSearch参数是查询时的候选大小这个值越高召回率越高但查询延迟也会上升。经验是efSearch通常设置在 100 到 300 之间效果比较均衡。2.3 优点和致命的短板HNSW 的优势是查询速度快、召回率高、无需训练阶段。数据来了直接就能建索引这对增量更新特别友好。但它的缺点也很明显内存开销非常大。因为你要把整张图和所有节点的连接关系都加载到内存里千万级别的向量就能吃掉几十 GB 内存。如果你做的是亿级别的数据量除非内存非常宽裕否则不建议直接用原版 HNSW需要考虑 PQ 或者磁盘索引方案来配合。3. LSH 详解给向量加签名的哈希魔法3.1 哈希函数的奇妙性质LSH 全称是 Locality-Sensitive Hashing中文叫局部敏感哈希。它的思路跟传统哈希完全不同。传统的哈希函数比如 MD5追求的是输入哪怕有一点不同输出结果也会天翻地覆。但 LSH 追求的是输入越相似输出的指纹哈希值越有可能一样。简单来说LSH 会给每个向量算出一串固定长度的签名签名相同的向量大概率是近邻。打个比方两个人去了同一家健身房、吃同一家餐厅、喜欢同一个乐队那他们大概率住在同一个城市。LSH 就是用这种特征相似性来做预归类的。算完签名之后LSH 会把拥有相同签名的向量放到同一个桶里查询时只查这个桶其他桶就直接跳过。3.2 LSH 的代价LSH 最大的问题在于召回率不稳定。因为签名是概率性的有时候明明相似的两个向量由于哈希函数投影方向的原因会被分到不同的桶里就永远检索不到了。我曾在大概 500 万条数据上跑过一个 LSH 实验跟 HNSW 做对比。数据是 768 维的文本向量结果如下方法召回率10平均查询延迟HNSW (默认参数)97.2%8.6 msLSH (32 位签名)86.5%12.4 msLSH (64 位签名)90.8%19.7 ms从测试数据看LSH 的优势并没有想象中那么大反而是调整签名位数让性能波动比较明显。位数高召回好但存储和计算开销也随之变大位数低又不准。3.3 什么时候该用 LSH虽然综合表现不错但 LSH 并没有 HNSW 那么全面。它廉价的优势在于支持海量数据的内存受限场景、不需要像 HNSW 那样维护复杂的图结构、适合分布式系统做预分区。比如做重复图片检测或者大规模文本去重用 LSH 先粗筛出候选集再用精确的距离计算做二次精排效果会很好。如果你在做向量检索的全流程架构把 LSH 用在粗筛阶段、配合更精确的算法做精排是比较常见的做法。4. PQ 详解压缩存储的量化方案4.1 乘积量化的核心思想PQ 全称是 Product Quantization中文叫乘积量化。HNSW 和 LSH 解决的问题是怎么快速找但 PQ 解决的问题是怎么省空间地存。我们知道向量数据库的查询瓶颈经常不在计算而在内存带宽和磁盘 IO。当数据量大到内存装不下时性能就会急剧下降。PQ 的思路就像是把高清照片压缩成 JPEG把每个高维向量切成若干段每一段用聚类中心来代替从而压缩存储体积。假设一条 768 维的向量每个维度是 4 字节的浮点数总共占用 3072 字节。用 PQ 把它切成 96 段每段选出一个 256 个聚类中心里的代表 ID每段只需要一个字节存储。压缩后占用的空间为 768 字节96 段乘以 1 字节直接压缩了 75% 的体积。4.2 完整的PQ流程PQ 分为训练、编码、查询三个阶段训练阶段从数据集中抽样一部分向量把每个子向量段做 K-Means 聚类生成码本。这个码本相当于一本字典里面存着所有聚类中心的向量。这个阶段必需提前完成。编码阶段把所有向量按照码本转换为对应的聚类中心 ID原始向量可以丢弃。查询阶段用户查询向量无需编码成 ID而是将查询向量切成同样的段跟每个聚类中心做距离计算生成一个距离查表再根据每个候选向量的 ID 组合出近似距离。这种方式叫非对称距离计算ADC因为查询向量和库向量处在不同的表示空间中但计算精度比双方都压缩要高得多。4.3 PQ 的致命问题和改进PQ 最核心的问题是因为压缩导致的信息损失。想象一张高清照片被压缩成马赛克个别细节肯定丢失了。在向量检索的场景中PQ 的召回率比 HNSW 低不少特别是数据的分布比较零散时更明显。后来工业界搞出了 PQ 的许多变种比如 OPQOptimized Product Quantization通过旋转矩阵让每段的数据分布更均匀IVF-PQ 则是先用聚类做粗筛再在桶内做 PQ 精算。Milvus 里的IVF_PQ就是这个思想先用倒排索引粗筛出接近的桶再在桶内做编码匹配。5. 三者对比与选型实战建议5.1 一张表看明白差异为了照顾新朋友我先明确一个认知框架在向量数据库里HNSW 是一种图索引LSH 是一种哈希索引PQ 是一种量化索引三者解决的重点不同。把它们放在一起对比维度HNSWLSHPQ核心思路多层图导航搜索相似哈希分桶向量压缩量化适合数据量千万级以下大规模数据预筛亿级海量数据内存占用高中低查询精度高中低中低索引构建速度慢建图要吃资源快快聚类较耗之后很快是否需要训练不需要不需要需要常见场景通用检索默认首选去重、粗筛、分布式超大存储量场景5.2 结合 Milvus、Chroma、Qdrant 怎么选才不踩坑如果你用的是 Milvus它的架构比较灵活支持多种索引。默认配置下我建议直接用 HNSW因为它是内存索引性能最稳定。用hnsw时我建议把M调到 16 左右太高会浪费内存。如果向量数据超过千万先把index_type换成IVF_PQ用训练好的码本来降低内存压力但一定要用小批量数据提前测试召回率别等上线了才发现不准。如果你用的是 Chroma它比较轻量通常用于本地开发或原型验证。Chroma 的底层实现基于 HNSW封装得比较好但它的设计目标是开发便捷而不是处理亿级海量数据。数据量超过几百万条时Chroma 的性能下滑比较明显。我的经验是它很适合做个人知识库或者小团队内部工具数据量大了赶紧迁移到 Milvus 或 Qdrant。如果你用的是 Qdrant它默认索引也是 HNSW但 Qdrant 有个特色支持配置使用二进制量化Binary Quantization或者标量量化Scalar Quantization来压缩向量。这些方案本质上是 PQ 思想的变种。在 Qdrant 里调节quantization_config参数比如设置scalar类型可以把内存占用降低 4 倍左右但召回率可能下降 0.5 到 2 个百分点。如果你的业务对精度要求不是极端高这个取舍非常划算。5.3 混合索引是工业级常规操作真正做得成熟的系统一般不会只依赖一种索引而是采用混合策略。最常见的方案是粗筛 精排。用 PQ 或者 IVF 把海量数据快速缩小到几千条候选集然后用 HNSW 在这些候选里做精细检索再用精确的向量距离计算做最终排序。这就像你先用百度地图找到某个城市再开车到街道最后敲门找人一层比一层精确。还有一种是按场景拆分。比如在推荐系统里召回阶段用的是HNSW追求速度精排阶段用的是暴力计算因为候选集已经很小而长期存储和备份数据用 PQ 压缩格式存盘。这套组合拳既保证了效果也控制了成本。6. 实操中的常见问题与避坑经验6.1 索引参数不是越大越好很多人第一次用 HNSW恨不得把M设成 64efConstruction设成 1000以为精度越高越好。我刚开始也干过这事结果一个 100 万条的数据集建索引就花了半小时内存直接飙到 8 GB查询速度也没快多少。后来我总结了一个经验公式先按M 16起步efConstruction 200测一下召回率。如果召回率不够先调efSearch查询参数效果不明显再往上调M。千万别一上来就把所有参数拉满。参数越高索引构建时间和内存占用是指数级上升的。6.2 PQ 训练样本的选择PQ 需要训练码本训练集怎么选决定了后续量化效果的好坏。如果训练集跟真实分布差异大比如用了全是英文的数据训练上线后却要去检索中文数据那聚类中心就偏了码本表达力会很差检索精度惨不忍睹。我的建议是训练集的采样量至少覆盖 1% 到 5% 的总数据量而且必须跟线上数据同分布。实在不行可以用全量数据中随机抽样的方式来做训练虽然训练时间长一点但稳。如果线上数据是增量式的隔一段时间需要重新训练一次码本否则数据分布漂移后精度会慢慢劣化。6.3 召回率指标别只看 Top1在实际项目中评估这三种方案不能只看 Top1 准不准要看recall10、recall100这些指标。因为向量检索通常是第一轮筛选后面可能还有重排模型兜底。只要 Top100 里包含正确结果重排模型就能捞回来。所以宁可牺牲一点 Top1 的精度也要保证 Top100 的高召回这才是让整体系统效果最稳的做法。我见过团队为了追求单条结果的绝对正确把索引参数调到极高结果整体延迟从 50ms 升到 300ms用户根本不买账。实际上多数场景 90% 到 95% 的召回率已经完全够用把剩下交给精排才是工程化的正确思路。6.4 别忽视距离计算方式的坑还有一个容易被忽略的点不同类型索引默认使用的距离度量不一样。有的库默认是余弦距离Cosine有的是欧氏距离L2有的支持内积IP。如果你换了一个向量数据库没有统一修改距离度量看起来索引建得很正常但检索结果怎么都不对。建议无论用 HNSW、LSH 还是 PQ都要检查两个东西一是向量的归一化情况二是库的metric_type配置。文本向量用余弦距离更合适图片向量有时用 L2 更好。如果选错了距离函数就算索引算法再好效果也差得离谱。6.5 从日志与监控中定位问题我在生产环境里维护向量服务时遇到过线上召回率突然下降、但索引没有重建的情况。最后排查出来是同一批向量的 embedding 模型悄悄更新了新出的向量分布跟旧索引不一致。这种问题靠代码看大概率发现不了必须对 embedding 版本做严格管理。升版本后要对存量数据统一重算向量并重建索引。另一个容易出问题的点是并发。HNSW 这种图结构在高并发写入时可能会出现锁竞争写入和检索互相卡顿。后来我调整为尽量做批量写入或者把写入压力分散到索引副本上线上才稳定下来。最后再分享一个小技巧如果你刚开始做向量检索选型不确定用 HNSW 还是 PQ先用开源库跑个基准测试。拿你自己业务里最典型的一万条数据分别建索引压测一下QPS和recall10。十分钟就能出结果远比你读三天文档猜参数靠谱。我实际测下来大部分日活百万以下的应用一个配置合理的 HNSW 就完全够用了真正需要折腾 PQ 的量级一般是千万级以上的大系统。这两者就像是城市里的出租车和地铁——出租车灵活直达但一到高峰期就堵地铁容量大、稳定但你必须先走到地铁站付出训练和量化成本。认清你自己的数据量级和业务需求选型就不会出大错。
返回列表