用户点下发送以后,页面空了很久,随后答案一口气开始出现。
遇到这种现象,一种常见判断是大模型太慢,需要换一个更快的模型。
但一条 RAG 请求在模型生成第一个字之前,已经经过 Query 处理、Query Embedding、关键词与向量检索、候选融合、Rerank 和上下文构建。真正的等待可能发生在其中任何一段。
从调用依赖关系看,模型要等检索证据准备好,才能开始基于证据生成。因此,如果向量检索阶段很慢,流式输出只能改变第一个 Token 之后的展示,不能缩短前面的检索等待。这是链路顺序决定的。同样,如果 Trace 显示模型服务正在排队,继续调 HNSW 参数也无法触及这个阶段。
由此可以引出一道 RAG 工程面试题:
RAG 首字响应慢,怎样逐段定位瓶颈?Embedding、检索、Rerank、缓存和流式输出分别能解决什么问题?
只回答“加 Redis、开异步、上流式”通常不够。面试官会继续问:
- 首字响应和完整答案耗时有什么区别?
- 两路检索能不能并行,哪些步骤存在前后依赖?
- 缓存命中以后,权限和知识更新怎样保证?
- 把 ANN 参数调快了,召回质量下降怎么办?
首字响应和总耗时不是一回事
首字响应常用 TTFT 表示,关注从用户发出请求到看到第一个 Token 的时间。
总耗时关注从请求开始到完整答案生成结束。
两者会受到不同因素影响。
如果前置检索很慢、模型生成很快,TTFT 很差,总答案出来后却可能迅速结束。如果前置检索很快、答案很长,TTFT 不错,总耗时仍然很长。
要看清首字响应,可以先把链路拆成 Query Embedding、向量检索、Prompt 构建和网络、队列、I/O 等系统开销。一条完整的线上时间线还要加上路由、关键词召回、融合、Rerank、模型排队、预填充和第一个 Token。

TTFT 只到第一个 Token,总耗时还包含后续生成。优化前要先确认用户抱怨的是哪一段等待。
如果所有阶段只记一个 response_time,你只能知道“整条请求慢”,却不知道慢在哪里。
更有用的记录方式是给每次请求分配 Trace ID,每个阶段写开始时间、结束时间、状态和关键配置。检索还要记录候选数量、是否命中缓存、使用了哪个索引和模型版本。
平均值也不够。
少量特别慢的请求可能被平均值掩盖。至少要区分中位数、P95、P99,并同时看错误率。高峰期排队、第一次加载模型、缓存未命中和日常请求也应该分组。
一条请求在首字前经过什么?
可以按下面的顺序理解。
第一步,网关和应用服务接收请求,完成身份、租户和知识库范围校验。
第二步,进行 Query 处理。如果是多轮问题,还可能做话题判断和 Query 改写。
第三步,计算 Query Embedding。文档 Embedding 应在离线入库阶段完成,在线阶段通常只计算用户问题。
第四步,执行关键词召回和向量召回。两路之间没有结果依赖时,可以并行。
第五步,融合候选并做 Rerank,然后按门槛和上下文预算截取证据。
第六步,构建 Prompt,发送到模型服务。模型可能还要排队、加载上下文并完成预填充,之后才产生第一个 Token。
第七步,通过流式协议把 Token 送到前端。

只有给每个阶段独立计时,才能知道优化应该落在缓存、检索、排序还是模型服务。
这些步骤不是都能并行。
检索需要 Query 或改写后的 Query,向量检索需要 Query Embedding,模型生成又需要最终证据。它们存在明确依赖。
但 BM25 与向量召回可以在准备好 Query 后并行,多个独立子查询也可以并发。并行的前提是它们互不依赖,而且下游资源能承受并发,不是把所有函数都改成 async 就会自动变快。
Embedding 阶段慢,怎么优化?
先确认在线请求有没有重复计算文档向量。
文档 Embedding 属于离线入库工作。在线问答通常只需要计算 Query 向量。如果每次问答都重新向量化整份资料,架构边界已经错了。
重复 Query 可以缓存 Embedding。这里的缓存只是一种可选工程策略:先规范化文本,再生成缓存键,命中后复用向量。是否值得采用,要看真实 Query 的重复程度和缓存维护成本。
但一个生产缓存键不能只有 Query 文本。
Embedding 模型升级后,同一句话对应的向量空间已经变化。缓存键至少要区分模型和版本。多租户系统还要考虑租户与数据边界,不能让一次缓存命中绕开访问控制。
批处理适合离线文档入库,或者多个请求积累后一起计算。它能摊薄网络与调度开销,却会引入等待批次形成的时间。吞吐量提高,不代表单个请求的 P99 一定降低。
所以批大小和等待窗口要在目标负载下一起测试吞吐、P95 和 P99,不能只看单次调用时间。
并发请求同样受服务端限流、连接池和队列影响。压测时可以从低并发逐步增加负载:并发不足时,部分资源可能没有被利用;继续提高并发,也可能只是在上游形成更长排队。通过队列长度、资源使用率、吞吐与尾延迟的同步变化,才能找到拐点。
检索阶段慢,先看索引还是先扩容?
检索优化里常见的 ANN 索引、分区、过滤、批量查询和并发连接,解决的问题并不相同。
ANN 索引用近似搜索换取速度。HNSW、IVF 等索引都有搜索宽度和构建参数,调得更激进通常会影响召回。
因此索引优化必须和固定评测集绑定。每次改变参数,都要检查正确证据是否仍进入前 K 条。查询快了但 Recall@K 下降,首字改善可能换来错误答案。
过滤是在搜索前缩小有效范围。
如果问题只允许查询某个知识库、某些文档或当前有效版本,先过滤再搜索既是权限要求,也可能减少候选范围。过滤条件应直接进入查询,让未授权和已失效内容不参与候选排序,而不是搜完全库后再二次删除。
BM25 和向量检索可以并行执行。串行写法会先等完一路再跑另一路,并行则等待较慢的一路完成后进入融合。
不过,并行会同时占用搜索引擎、连接池和 CPU。单请求变快的同时,也可能降低系统在高并发下的稳定性。要用并发压测验证,而不是只在本机发一个请求。
Rerank 为什么容易成为新瓶颈?
粗召回通常可以快速从大规模候选中找出一批可能相关的内容。Rerank 则要更细地比较 Query 和每个候选,成本会随着候选数量增长。
候选越多,召回覆盖可能越高,Rerank 工作量也越大。无限扩大 Top N 不是稳妥策略。
一条合理的排序链路会先做粗召回,再调用 rerank 或 rerank_by_model 对有限候选做更精细的比较。昂贵排序应该作用于有限候选,而不是作用于整个知识库。候选数量由召回曲线和耗时曲线共同决定,不是越多越好。
选择候选规模时,先画出两条曲线。
一条是候选数量增加后 Recall@K 怎样变化。另一条是 Rerank 耗时和内存怎样变化。当召回增长已经很小,继续扩大候选却显著增加延迟,就应该停下来。
批量推理可以一次处理多组 Query 与文档对,减少调度开销。模型量化或更小的 Rerank 模型也可能降低耗时,但都必须重新验证排序质量,不能只看速度。
缓存能放在哪几层?
为了看清收益与失效边界,可以按缓存所处的位置把它分成三类。
第一类是 Embedding 缓存,复用重复文本的向量。
第二类是检索结果缓存,复用某个 Query 的候选文档。
第三类是完整答案缓存,对高度重复且答案稳定的问题直接返回结果。
越靠后,缓存收益可能越大,失效风险也越高。
Embedding 缓存依赖模型版本。检索缓存依赖知识库、文档版本、过滤条件、检索配置和权限。答案缓存还依赖生成模型、Prompt、引用和时效。

缓存位置越接近最终答案,需要纳入缓存键和失效策略的上下文越多。
假设同一句问题在两个知识库里答案不同,缓存键却只有 Query。第一个用户的检索结果可能被第二个用户复用,这已经不是性能小问题,而是数据越界。
文档更新后,旧缓存也要失效。可以把知识库版本写入缓存键,或者在发布新版本时主动清理相关条目。
缓存命中率要按场景统计。如果大多数问题都不重复,维护复杂失效逻辑可能不划算。缓存不是必选组件,而是用真实重复度换取的工程策略。
流式输出能解决什么,不能解决什么?
流式输出让模型一产生 Token 就发送给前端,用户不用等完整答案生成完再看到内容。
它主要改善感知等待和完整答案的展示过程。
模型开始生成之前的 Query 处理、Embedding、检索、Rerank 和 Prompt 构建仍然存在。如果 TTFT 主要耗在这些前置阶段,改成流式不会让第一个字更早出现。
流式服务还需要处理断线、取消和资源释放。
用户关闭页面后,后端如果仍然生成完整答案,会继续占用模型和网络资源。超时和取消信号应该沿链路传播,让尚未开始或已经无意义的任务尽快停止。
前端也要区分“正在检索”和“正在生成”。只放一个旋转图标,用户不知道系统卡在何处;阶段状态既有助于体验,也有助于排查。
Trace 里应该记录哪些信息?
只记录每段耗时仍然不够,因为同样的慢可能由不同输入触发。
请求入口要记录 Trace ID、租户、知识库范围、请求时间和是否为多轮问题。敏感 Query 可以做受控存储或脱敏,但不能完全失去问题类型信息。
Query 处理阶段记录是否改写、改写耗时和回退状态。Embedding 阶段记录模型版本、输入长度、缓存是否命中和上游排队时间。
检索阶段记录使用的索引、过滤范围、两路候选数量、搜索参数、各自耗时和是否超时。融合与 Rerank 记录输入候选数、输出候选数、模型版本和批次等待。
Prompt 构建阶段记录最终证据数量和上下文长度。模型阶段记录请求进入队列、开始推理、第一个 Token 和结束时间,才能区分服务排队、预填充和持续生成。
客户端阶段还要记录连接建立、首个数据包和用户取消。如果服务端已经产生 Token,前端很晚才显示,问题可能在代理缓冲、网络或渲染,而不在模型。
这些字段需要使用统一时钟和统一阶段名。不同服务各写一套日志,却没有共同 Trace ID,最后仍然拼不出时间线。
监控面板可以显示分位数,但排查具体慢请求时必须能下钻到单条 Trace。看到某个阶段 P99 上升以后,继续比较输入长度、候选数量、缓存状态和队列长度,才能形成可执行结论。
出于数据安全边界的考虑,工程上还应给 Trace 设置保留周期和访问权限。它可能包含用户问题、文档标题和模型上下文,不能为了性能排查无限期保存敏感内容。保留时长、可查字段和脱敏方式都应在发布前确定。
分段计时还要避免重复计算和遗漏。
如果外层接口记录了整个检索时间,内层又把 BM25、向量和 Rerank 分别上报,面板不能把四个数字简单相加,否则并行阶段会被重复计算。应该用时间线展示重叠关系,并明确每个阶段是墙钟耗时还是 CPU 时间。
跨服务调用还要记录上游等待和实际执行。请求在队列里等了很久,模型推理本身却很快,只看模型服务函数耗时会把瓶颈藏掉。
一条 Trace 能解释单次请求,指标聚合则负责发现趋势。两者结合,才既能看到整体变慢,也能打开具体慢请求找到原因。
五个 bad case,为什么越优化越慢?
第一种,只看平均值
多数请求很快,少量请求因为冷启动或排队特别慢,平均值仍然好看。
应该按冷启动、缓存命中、缓存未命中和高峰流量分组,同时看 P95、P99 和错误率。
第二种,为了加速 ANN,召回掉了
检索时间下降,正确证据却不再进入候选,最终答案开始出错。
每次调整索引与搜索参数,都要在同一评测集上回归 Recall@K、MRR 和最终答案,不允许只过性能门槛。
第三种,Rerank 候选不断扩大
团队担心漏召回,把候选从一批增加到更大一批,Rerank 耗时持续增长,正确证据覆盖却几乎不变。
应同时观察候选规模、Recall 曲线和重排耗时,找到收益开始变小的位置。
第四种,缓存没有版本和租户
速度看起来非常快,却返回旧答案或其他知识库的结果。
先停用风险缓存,再补齐模型、知识库、文档版本、权限范围和检索配置,不能用性能收益掩盖正确性问题。
第五种,批处理提高吞吐,尾延迟反而上升
请求需要等待同批数据凑齐,低流量或突发流量下,某些请求等待更久。
要同时报告吞吐与单请求延迟,并设置最大等待时间。批处理是资源利用策略,不是无条件的低延迟策略。
怎么验证一次性能优化?
首先建立端到端 Trace,固定知识库、问题集和检索配置,采集每个阶段的耗时、错误、候选和模型版本。
然后准备多种负载:单请求、稳定并发、突发流量、冷启动、缓存命中和缓存未命中。不同场景使用同一套统计口径。
性能指标至少包括各阶段耗时、TTFT、总耗时、P95、P99、吞吐、超时和错误率。
质量指标至少包括 Recall@K、MRR、无答案错误和最终答案的证据一致性。
每次只改变一个主要因素,比如索引参数、候选数量、缓存层或模型版本。记录改动前后的性能和质量,不用一个模糊的“明显变快”替代数据。

性能、质量、稳定性和数据边界必须一起通过,才算完成一次优化。
线上还要持续监控队列长度、连接池、模型加载、缓存命中和各阶段分位数。测试环境里没有排队,线上慢的原因可能完全不同。
把性能追问分成四类
下面四个问题是用于检查机制理解的教学设计,不对应某次真实面试的逐字记录。
为什么不先换更快的大模型?
先看 Trace。模型排队和首 Token 如果不是最长阶段,换模型不会解决主要等待,还可能改变答案质量和成本。
BM25 和向量检索一定能并行吗?
当两路使用同一个已准备好的 Query 且没有结果依赖时可以并行。如果向量路线依赖额外改写或意图路由,要先完成对应前置步骤。
缓存 Query Embedding 会不会串数据?
向量本身不直接包含检索结果,但缓存仍要区分模型版本和必要边界。检索结果与答案缓存必须额外包含租户、知识库、版本和配置。
流式输出是不是能降低 TTFT?
它能避免等待完整答案,但无法跳过检索前置链路。只有模型已经开始生成却被服务端缓冲时,正确流式传输才会直接改善首字。
只看 P99 会不会过度优化极端请求?
需要结合请求量、错误类型和业务影响。重点是知道慢请求属于冷启动、超时重试、特定文档还是资源饱和,再决定是否值得优化,而不是盲目追一个分位数。
现场回答:先定位最长阶段,再做联合验收
这道题最适合用“先定位,再优化,最后联合验收”三步作答:
RAG 首字响应慢时,我会先用 Trace 拆出网关排队、Query 处理、Embedding、BM25 和向量召回、融合、Rerank、上下文构建、模型排队与首 Token 的耗时,并区分冷启动、缓存命中和高峰请求。TTFT 和完整答案耗时分开统计,同时看 P95、P99 与错误率。
定位后再按阶段优化。文档向量放在离线阶段,重复 Query 可以做带模型版本的 Embedding 缓存;BM25 与向量召回在没有依赖时并行;向量索引和过滤缩小搜索范围;Rerank 用有限候选并批量处理;生成端用流式返回并支持取消。检索结果和答案缓存还必须包含租户、知识库和文档版本,更新后及时失效。
每次加速都会和质量一起验收。改变索引参数、候选数量或模型后,用同一评测集回归 Recall@K、MRR、无答案控制和答案证据,同时做并发、突发、冷启动和缓存分组压测。这样才能证明是缩短了等待,而不是把正确性和数据边界换掉了。
面试官最终会沿三件事判断这次性能优化是否成立:有没有找到真正的最长阶段,能不能解释每项优化的代价,以及速度改善后,召回、权限和答案质量是否仍然守住。列出多少性能组件并不重要,定位与验证证据才重要。