ARTICLE DETAIL

资讯详情

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

矢量数据库核心原理与实践:从语义检索到HNSW调优

矢量数据库核心原理与实践:从语义检索到HNSW调优 1. 为什么传统数据库搞不定语义相近这件事先从一个最直观的场景说起。你去电商网站搜能跑马拉松的鞋传统搜索引擎会老老实实匹配马拉松鞋这些字结果可能把马拉松周边纪念品也搜出来。但你想表达的其实是轻量、缓震好、适合长距离的路跑鞋。如果换成矢量检索系统会把这句话投射到一个高维空间里的坐标点然后在这个空间里找到语义上靠近轻量缓震路跑鞋的那些商品向量——哪怕商品标题里一个字都没出现马拉松。这就是矢量数据库存在的根本理由它处理的不是字面匹配而是语义近似。传统数据库的看家本领是精确匹配和范围查询。SQL里一个WHERE name 张三能精确命中WHERE age BETWEEN 20 AND 30能圈定范围。但现实世界的大量需求是找相似的找接近的按语义去重的这些需求没法用一个等号或者B树的区间扫描解决。你当然可以给每条文本做关键词分词再建倒排索引但词与词之间的同义、上下义关系是词法索引表达不了的。苹果和iPhone看起来毫无交集在语义空间里它们却可能相距不远。这个痛点在我自己接触RAG检索增强生成项目时体会更深。最初团队用ES做知识库召回_query_里写一堆布尔组合、同义词扩展效果始终差强人意。后来把文档切成段落用Embedding模型转成向量存进矢量数据库召回质量立刻上了一个台阶。不是ES不好而是它做的是字面命中矢量数据库做的是含义命中两者根本不在一个维度上干活。所以理解矢量数据库第一条就是要扭转思维数据不再是一行行的记录、一条条的文本而是一组组带坐标的高维向量。2. 嵌入向量到底在存什么2.1 从词向量到多模态向量向量听起来很抽象实际上就是一组浮点数。比如把马拉松这个单词喂给某个Embedding模型输出可能是一个384维或者1024维的数组[0.112, -0.034, 0.507, ...]。这些数字本身没有物理意义但它们组合在一起就在高维空间里定义了马拉松的位置。现代Embedding模型的进化路线很有意思。早期是Word2Vec、GloVe这类词向量每个词一个向量但解决不了一词多义——苹果在水果和手机语境里是同一个向量。后来BERT、Sentence-BERT这类上下文模型出现同一个词在不同句子里可以拥有不同向量。再往后CLIP这类多模态模型直接把图片和文本映射到同一个语义空间图片和描述它的文字会在空间里靠近。这也是为什么矢量数据库能同时处理图文检索一切能向量化的模态都可以进同一个坐标系进行比较。2.2 为什么行业里总看到384、768、1536这些数字第一次接触矢量数据库的人都会问向量维度为什么总是一些奇怪的数字384小模型如all-MiniLM-L6-v2的常见输出维度句子Transformer里常用的MiniLM模型就是384维。768BERT-base的隐藏层维度很多开源Embedding模型沿用这一设置。1536OpenAI的text-embedding-3-small的输出维度。维度本质上是模型认为描述语义所需的独立特征数。维度越高理论上能区分的语义细节越多但存储和计算成本也越高。一个1536维的float向量占6KB内存一亿条向量就是600GB单机很难扛住。所以工程上经常做降维或者用Product QuantizationPQ乘积量化压缩后面会细说。2.3 语义空间里的位置关系把大量文档切段向量化之后把这些向量放进一个坐标系超出三维但道理一样你会观察到一种现象语义相近的文本聚集在一起不同主题的文本在空间中形成一个个簇。比如所有关于数据库故障排查的段落聚在一个区域所有关于前端框架的段落聚在另一个区域。查询的时候把用户的提问也转成向量放进这个空间距离它最近的几个向量就是最相关的文档。这里有个关键认知矢量数据库不理解你的数据它只是在数学上维护好这个空间结构然后用几何距离帮你找邻居。语义之所以能被几何化完全是Embedding模型的功劳不是数据库的本事。换句话说矢量数据库是空间管理者Embedding模型才是语义翻译器。这两个角色搞混了的人往往会在项目里踩很大的坑——模型选得不好换再高级的数据库也救不了召回质量。3. 相似度度量余弦、内积、欧氏距离的取舍向量存好了怎么判断两个向量像不像主流矢量数据库基本支持三种度量方式余弦相似度Cosine Similarity、内积Dot Product、欧氏距离Euclidean Distance。余弦相似度看的是两个向量的方向是否一致公式是两个向量的内积除以各自模长之积。它只关心方向不关心长度。这在文本语义场景里很好用因为文本向量的模长受句子长度和语气影响很大方向才能体现真正的语义倾向。取值范围在[-1, 1]越大越相似。内积就是对应维度相乘再求和它同时把方向、长度都算了进去。在推荐系统里经常用内积因为用户向量和物品向量的模长本身就携带了热度或偏好强度的信息。两个向量即使方向一致如果模长差很多内积也不会很高。欧氏距离是计算高维空间里两点的直线距离越小越相似。它适合对距离数值敏感的场景比如图像特征或者经过归一化处理的向量。度量方式关注点常用场景数值语义余弦相似度方向文本语义检索、RAG越大越相似内积方向长度推荐排序、个性化越大越相似欧氏距离绝对距离图像特征、归一化向量越小越相似工程选择上有个非常重要的细节如果选余弦相似度向量在入库前最好先做L2归一化把模长变成1。归一化之后余弦相似度、内积、欧氏距离三者之间存在转换关系余弦相似度 1 - 平方(欧氏距离)/2且归一化后内积 余弦相似度。很多数据库比如Milvus允许你在建Collection时指定metric_type但底层如果用了某些索引类型对度量方式有兼容性限制比如某些版本的HNSW索引对内积和欧氏距离支持得更好余弦需要在应用层先归一化再转内积。这一点我建议所有人在建索引之前先翻一遍所选数据库的官方文档Metric compatibility部分否则上线前才发现度量方式不支持迁移成本极高。选度量的核心逻辑其实就一句话先确定你的Embedding分布和业务语义偏好方向再选度量最后看索引兼容性。没有人会用余弦相似度去算用户和商品的点击距离也没人会用内积去算纯文本语义相似度除非你的向量已经做了归一化。4. 近似最近邻为什么精确搜索在向量世界走不通4.1 精确KNN的时间复杂度有多恐怖如果有10亿条128维向量要在里面找与某个查询向量最近的10条精确做法是计算查询向量与全部10亿条向量的距离然后排序取前10。一次计算10亿次浮点运算看起来量级不小但也不算致命问题是线上每秒有几百上千个查询每个查询都全量扫一遍服务器直接原地爆炸。更关键的是性能随数据量线性增长数据翻倍耗时翻倍这不是一个可持续扩展的架构。所以矢量数据库几乎清一色采用ANNApproximate Nearest Neighbor近似最近邻算法。核心思路是我不保证100%找到全局最近的点但我用比精确搜索快几个数量级的代价找到大概率是最近的那一批候选。召回率Recall是ANN的关键KPI比如Recall1095%意思是查询结果的10个邻居里平均有9.5个与精确搜索结果一致。4.2 HNSW跳表思想在高维空间的延伸HNSWHierarchical Navigable Small World分层可导航小世界图是目前落地最广的ANN算法很多数据库都把它作为默认索引。它的灵感来自一个很著名的图论现象六度分隔——社交网络里看似毫无关联的两个人通过少量中间人就能建立连接。HNSW把数据点组织成多层图底层包含全部数据点上一层是抽样子集越往上点越少。查询时从顶层开始贪婪地沿着边走向与查询点最接近的节点逐层下探到底层找到一批候选邻居。HNSW的优点很突出召回率高查询延迟稳定不需要像IVF那样依赖聚类质量。缺点是索引结构是图内存占用偏高且每次插入新点需要维护图结构大批量写入时构建速度比IVF慢。4.3 IVF先分区再细查IVFInverted File Index倒排文件索引的思路更简单粗暴先把全部向量用KMeans聚成N个桶每个桶有一个聚类中心。查询时算出查询点离哪些聚类中心最近只在这些桶内部做精确扫描而不是全库扫描。调参的关键是nlist桶的数量和nprobe查询时扫描的桶数。nprobe越大候选范围越大召回率越高但延迟也越高。IVF的优点是内存比HNSW小构建速度快缺点是召回率对聚类质量敏感数据分布如果极度不均衡会出现某些桶数据量过大拖慢查询。实际项目中常见做法是IVF和HNSW结合场景使用海量数据、允许一定延迟的离线批量场景用IVF在线低延迟高召回场景用HNSW。4.4 PQ用压缩换速度和内存Product Quantization乘积量化是另一个绕不开的算法它解决的是向量太占内存的问题。做法是把高维向量切成若干段每段单独做聚类用聚类中心的编号代替原始浮点数。比如把128维向量切成8段每段聚类256个中心那么每个向量只需要8个编号每编号1字节总共8字节相比原始512字节128维float压缩了64倍。PQ的硬核之处在于查询时不需要存原始向量只要用查表法在压缩后的编码上估算距离速度极快。代价是精度损失明显所以工程上常把它和倒排结构结合成IVF-PQ或者用PQ做压缩、在最后一步用原始向量做重排序平衡召回率和资源开销。4.5 为什么精确KNN并没有完全退出虽然ANN是业界主流但我仍然会在一些中小规模项目里坚持用暴力精确检索Brute Force。当数据量在百万级以下精确KNN的延迟也就几十到几百毫秒对内部工具类应用完全可以接受。这时候用精确搜索的好处是结果100%可解释、无需调参、省去索引构建时间。矢量数据库通常都保留强制扫全量的接口比如Milvus里的FLAT索引本质上就是不做任何加速的精确计算。在大规模系统里做ANN但不迷信ANN这是我在选型上的一条原则。5. 一个向量检索请求的完整旅程把前面几节串在一起就能还原一次矢量数据库查询的完整链路。假设你的RAG系统收到用户问题如何在K8s里排查Pod启动失败处理流程如下5.1 查询入口过滤条件怎么处理用户的提问先经过Embedding模型转成查询向量同时还可能带着一些结构化过滤条件比如WHERE 时间 2024-01-01 AND 标签 运维。矢量数据库的问题在于向量检索和标量过滤怎么协作业界基本有两种路线先过滤再检索Filter-then-Search和先检索再过滤Search-then-Filter。先过滤再检索会先用标量条件把候选集缩小到合理范围再对这个子集做向量近邻查找。这样查询准确但如果过滤后的候选集非常小召回结果可能不够。先检索再过滤会先按向量相似度取出Top-N再套标量过滤。速度快但可能漏掉标量条件命中但向量排名靠后的正确结果。现代矢量数据库普遍在做的是结合用标量索引先粗筛出候选集再在候选集内做向量检索Milvus 2.x 把这套逻辑封装成标量过滤向量检索的统一计划器。我实际用下来的体会是过滤字段一定要建标量索引否则过滤本身会变成全表扫描。不少人只盯着向量索引调参忽略标量索引结果检索链路整体延迟居高不下。5.2 候选集的生成拿到过滤后的数据子集查询向量会被送入HNSW图的顶层。算法从顶层节点出发在当前层寻找与查询向量距离最近的节点找到后进入下一层继续找直到最底层。在底层会维护一个固定大小的动态列表范围取决于ef参数里面存当前遇到的距离最近的若干候选节点。遍历完所有可触达的边之后这个列表就是候选集。这一步非常考验图的连通质量和ef参数的平衡。ef太小候选集不够容易漏掉真正的近邻ef太大遍历的节点过多延迟上去。线上调优的时候我一般先把ef设到k * 10k为最终返回条数再根据延迟和Recall的实测曲线微调。5.3 重排序与返回候选集里可能混入一些距离较远但侥幸进入列表的节点。如果底层用的是PQ压缩向量每个节点存的是压缩编码距离本身是估算而非精确值。这时候最后一步会做一个精排把候选集对应的原始向量如果有存储捞出来用精确距离公式重算按真实距离排序取最终Top-K返回。这一步是整个链路里最容易被忽略的优化点。重排序的候选数量通常设置为ef或nprobe结果的数倍比如k10时取ef100用100条候选里选出10条精确最优。对海量数据场景只把Top-K的原始向量放到内存里做精确计算开销极小但召回率提升明显。这也是为什么很多高端配置都会强调保留原始向量用于重排。6. 索引参数调优M、efConstruction、ef到底怎么设HNSW作为默认王牌索引参数理解透了基本就掌握了大半调优技能。核心参数有三个M构建图时每个节点的最大连接数。M越大图越稠密查询时路径越短召回率越高但内存占用和构建时间也越大。常见的起步值是16或32。我自己的经验M设为16在千万级数据上通常能平衡好延迟和召回如果内存充足且延迟敏感可以考虑提到32。efConstruction构建索引时使用的动态候选列表大小。它越大构建期间会花更多力气寻找合适的邻居图的质量越高连接更接近真近邻构建时间也越长。一般取值在100到500之间。这里有个容易踩的坑efConstruction和查询时的ef没有直接关系但增大了efConstruction并不能让查询更快它只会让图更可靠。很多人把两个参数搞混调了半天查询性能没变化原因就在这。ef查询参数查询时动态候选列表大小这个值是查询时可以实时调整的。ef越大候选越充分召回越高延迟也越高。开箱推荐值是2*k到10*k上线压测时再用如recall10作为指标逐档测试。调参方法论我总结为四步先固定数据量级和一个可接受延迟上限比如P95 50ms。用召回率脚本打一批已知近邻的查询测试不同M和efConstruction的离线索引质量。固定M和efConstruction扫描ef从20到200画出延迟-Recall曲线。选出曲线拐点的值作为线上配置并在查询接口留一个ef的动态参数方便后续按流量调整。内存估算也是必做的功课。HNSW每个向量的内存开销大约为维度 × 4字节 × (1 M)你在设计容量时按这个公式先粗算。比如128维、M16每个向量约8.7KB1000万条就是87GB单机扛不住就得考虑分片或用PQ压缩。千万级数据上线前不算内存等你创建Collection发现OOM再回头改配置那个过程相当痛苦。删除与更新更是隐蔽的坑。矢量数据库的删除普遍是墓碑标记标记删除后向量并不会立刻从索引结构里移除而是等后台Compaction真正清理。查询时墓碑会被跳过但索引体积不会立刻减小写入性能也可能被Compaction任务短暂拖累。如果你的业务频繁更新向量比如商品特征每天变更务必选择支持异步Compaction并且可手动触发Compaction的数据库。我见过有团队频繁执行delete insert来更新向量结果索引体积疯涨查询延迟飙升最后只能全量重建索引才能恢复。7. 矢量数据库与传统数据库的架构差异7.1 存储引擎向量和标量各居其位传统关系数据库的存储引擎围绕B树设计数据按主键有序组织适合范围扫描与点查。矢量数据库的存储体系则通常分为两部分向量数据走专门的索引结构HNSW图、IVF倒排标量元数据走类LSM或B树的索引。两者并行维护查询时通过计划器协同工作。这种双轨制意味着矢量数据库的写入链路比传统数据库复杂一条记录既要写标量存储又要更新向量索引事务和一致性模型因此天然比传统数据库弱。7.2 扩展能力分片和分区策略水平扩展上传统数据库常用分库分表按主键或区间划分数据。矢量数据库的分片逻辑通常按哈希或者按业务分区键把不同向量集合分到不同节点。更关键的是它没有全局索引查询时要么广播到所有分片做并行ANN搜索再合并结果要么通过分区做裁剪把查询定位到少量分片。前者实现简单但放大查询开销后者省资源但对业务数据分布要求高。用Milvus的时候给每个租户设计独立的Collection或者按租户ID做分区就是在用分区裁剪换取隔离性和查询性能。7.3 混合搜索矢量不是万能的说句公道话矢量检索在精确条件上弱得离谱。你没法用向量直接表达价格严格小于100元状态等于已发布这些逻辑还得靠标量过滤。所以成熟系统里矢量数据库几乎从来不是单打独斗它跟前置的过滤条件、后置的规则引擎、甚至传统数据库协同工作。RAG系统尤其如此先从矢量数据库召回Top-K候选再用规则或重排序模型精排最后交给大模型生成。矢量和标量是互补关系不是替代关系。想用矢量数据库取代业务系统的MySQL方向就错了。8. 矢量数据库实际解决的核心问题聊完底层机制回到真实的业务场景。我从自己的项目经验里挑四个典型问题说明矢量数据库到底在哪些地方不可替代。8.1 语义搜索与文档召回这是最主流的应用也是我最早接触矢量数据库的契机。传统关键词搜索遇到用户用口语化描述、同义改写、跨语言提问时基本失灵。语义搜索把查询和文档都向量化用户的怎么让服务器别老宕机能命中文档里的高可用架构与故障恢复哪怕字面毫无交集。文本切分粒度是这里最影响效果的因素切太碎丢上下文切太长引入噪音常用策略是300-500字一段加上相邻段落的重叠窗口。8.2 RAG检索增强生成RAG是大模型应用落地的主力架构。大模型的参数知识有截止日期、容易幻觉RAG通过把外部知识库的相关片段注入上下文让模型生成时有所依据。矢量数据库是RAG的召回底座知识库切片向量化入库用户提问向量化去检索把Top-K片段拼进Prompt。这里的核心指标不只是延迟还有检索结果的集中度——如果Top-5结果来自相互矛盾的文档大模型会生成一段自相矛盾的内容。我最近在做的项目里就加了一道结果去重话题一致性校验比单纯调索引参数有效得多。8.3 多模态检索借助CLIP这类多模态模型图片、音频、视频都能编码进同一语义空间。上传一张红色跑鞋的图能检索出所有红色跑鞋的图片不依赖图片的命名或标签。这在商品推荐、内容去重、素材管理的场景里是刚需。8.4 推荐系统与大规模去重推荐系统里用户向量和物品向量做内积排序是实现个性化召回的高效路径。内容平台上做相似稿件去重把文本、图片向量化后找高相似对也能显著节省人工审核成本。这类场景对写入吞吐有要求矢量数据库流式写入的能力就比离线批处理方案有优势。矢量数据库远不是什么万能银弹它的定位很清楚为高维向量提供高效存储、索引和检索的专用基础设施。选型时先想清楚自己的数据能否向量化、Embedding模型是否足够可靠再考虑用哪种索引、什么硬件的预算。我在实操里的体会是它真正吃透价值的场景永远是那些语义相关、海量数据、延迟敏感的检索系统。把这三点想透了你就能判断该不该上矢量数据库、该在什么环节引入它而不会把它变成又一个吃内存的玩具。
返回列表