做 RAG 的时候,经常遇到「单独测两路都挺快,合起来就慢」的情况。问题往往不在模型本身,而在于你对两路检索的延迟来源没拆开看,也没想清楚该串行还是并行。这篇不给你结论式的数字,只给一套拆延迟、量基线、判断串并行值不值得的顺序,让你在自己的环境里量出答案。
做 RAG 的时候常遇到一种别扭的情况:单独撸一遍向量检索,快;单独撸一遍关键词检索,也快;两路一合起来,整条链路就慢得让人想骂人。这时候多数人的第一反应是去调模型、换热库、加机器,其实方向多半是错的。问题不在某一路本身,而在于你没把两路的延迟来源拆开看,也没想清楚该串行还是并行。
这篇不给你任何结论式数字,因为每个环境的库大小、索引类型、机器位置都不一样,抄别人的数字只会误导你。只给一套动作:先把延迟拆成几段、量出自己的基线,再判断串并行到底值不值得改。全程你都能在自己机器上复现。
两路检索的延迟来源根本不是一回事
关键词检索和向量检索,名字听着像一对兄弟,延迟构成却差得远。关键词一路主要是倒排索引查表加打分,大头在索引是否常驻内存、查询词命中了多少文档;向量一路得先把查询编码成向量,再走近邻搜索,编码耗时和索引类型共同决定它有多慢。把这两路当成同一种东西去优化,基本是白费力气。
| 延迟段 | 关键词检索关注点 | 向量检索关注点 |
|---|---|---|
| 查询预处理 | 分词、改写,通常很轻 | 查询编码成向量,可能占大头 |
| 核心检索 | 倒排查表加打分,看命中量级 | 近邻搜索,看索引类型和向量条数 |
| 网络往返 | 库在哪、走不走进程 | 库在哪、编码模型在哪 |
| 结果合并 | 排序、去重、截断,纯 CPU | 同样要排序、去重、截断 |
判断标准很简单:把「查询预处理」「核心检索」「网络往返」三段分别计时,哪一段占比高,优化方向就在哪。如果连这三段都没拆过,任何调参都是在猜。什么时候不适用:如果你用的是托管服务,中间段你根本摸不到,那就只能量端到端,再去站内测速页看同类环境的参考区间,到 /tools/speed-test 核对当时的数字。
串行还是并行:先看最短那一路有多长
串行是 A 跑完再跑 B,总延迟大致等于两路相加,实现简单、逻辑可控,适合「先粗筛再精排」这种有先后依赖的场景。并行是两路同时发起再合并,理想情况下总延迟约等于较慢的那一路加上合并开销,能省掉较快那一路的时间。听起来并行稳赢,实际不一定。
- 两路一快一慢差距很大时,并行的收益仅仅等于快的那一路那点时间,值不值得改代码要算一下
- 两路共享同一个连接池、同一台机器资源时,并发会互相挤占,串行的总延迟反而可能更低
- 并发上去把某一路打到限流,延迟不降反升,这时候并行是负收益
- 有先后依赖的场景(比如一路的结果要喂给另一路)根本没法并行,别硬凑
什么时候并行不适用
如果你的两路检索跑在同一台机器、抢同一份 CPU 或同一块内存,并发带来的上下文切换和缓存抖动会把收益吃掉。判断办法:先按单线程串行量一遍,再开并发量一遍,对比尾部延迟。如果并发后 P99 反而变差,就别强行并行,老老实实串行,或者把两路拆到不同机器上再并行。
先把基线量出来,再决定怎么改
优化前必须有一份可信的基线,否则你永远不知道改动有没有效果。做法不复杂,关键是分三组测、记录分布而不是只看平均值。平均值会骗人,用户真正感知到的是尾部延迟,也就是偶尔那几次慢请求。
- 第一组:只走向量同批查询只打向量一路,记录耗时分布、超时率、失败率,作为向量单路基线。
- 第二组:只走关键词换同样这批查询只打关键词一路,同样记分布,作为关键词单路基线。
- 第三组:两路合并按你线上真实的串行或并行方式跑,记录合并后的分布,和前两组对比,差值就是合并与并发带来的额外开销。
- 验证方法三组用同一批真实查询,别用一两条短句代表全部;长查询对向量编码段的影响尤其明显,样本里要混进长短不同的查询。
记录项至少包含:单路耗时分布、合并后耗时分布、超时率、失败率。合并后如果超时明显变多,问题多半出在合并环节的等待策略,而不是某一路本身慢,下一步就该去查合并逻辑,而不是回头调检索。
合并环节和超时策略最容易被忽略
两路结果合到一起要排序、去重、截断,这部分是纯 CPU 开销,候选条数越大越明显。很多人量基线时只算了检索那两段,把合并漏掉,结果优化完发现总延迟没怎么动,因为瓶颈在合并。量的时候一定要把这段单独拉出来看。
- 等全部返回
- 总延迟由最慢那一路决定,召回最全,但最容易拖长尾
- 等固定时间
- 到点就合并,延迟可控,但可能丢结果,适合对完整性要求不高的场景
- 够数就走
- 候选凑够阈值立刻合并,响应快,牺牲一部分召回,准确性要求高的场景别用
给每一路单独设超时,不要让一路卡住把整体拖到用户放弃。超时之后是降级成单路结果、返回缓存,还是直接报错,必须在写代码之前定好,而不是等线上出问题再临时决定。什么情况下不适用「够数就走」:对准确率敏感的场景,比如合规检索、财务对账,宁可慢一点也要等完整结果,这时候别为了省时间提前截断。
具体到你的环境该怎么配,没有通用答案。站内指南中心有混合检索相关的排查清单,路径在 /guides,结合你自己量出来的基线去对照,比套用别人的参数靠谱得多。价格和模型选型的数字放在价格页和模型资料库里,需要的时候到 /price-watch 和 /models 核对当时的数字。
下一步先别改代码,去测速页把三条基线量出来,再回来看串并行到底值不值得动。有了基线,你的每一次改动才有参照,不然只是换个地方慢而已。→常见问题
为什么两路单独测都很快,合起来就慢了一大截?
常见原因是合并开销和并发挤占,而不是检索本身。先确认两路是不是在抢同一个连接池或同一台机器,再看合并排序的候选条数是不是过大。把只走向量、只走关键词、两路合并三组分开计时,差距出现在哪一组,问题就在那一段。
混合检索一定要并行吗?
不一定。并行只在两路耗时接近、且资源不互相挤占时才明显划算。如果一路明显比另一路快很多,并行的整体延迟还是由慢的那一路决定,省下的有限。资源紧张或有限流风险的场景,串行反而更稳,先量基线再定。
调参能解决检索延迟吗?
调参只对特定一段有效。向量索引参数影响近邻搜索那一段,不解决编码和网络往返;关键词检索的延迟多半在索引结构和内存占用上。先拆出三段各自的占比,再决定是调参、换索引类型,还是把检索挪到离应用更近的地方,比盲目调参省时间。