大模型响应慢,先拆解请求耗时
单一请求总耗时无法判断性能瓶颈,应分别追踪排队、检索、预填充和解码等阶段。文章以生产环境中的检索增强应用为例,说明如何为扩容、缩短提示词或优化检索提供依据。
AI 深度解读
一次 AI 问答花了 11.4 秒,先换模型还是先加 GPU?作者 Ahmed Adawy 的答案是:先弄清时间花在哪里。认证、检索、提示词构造、推理和流式转发,都可能让用户等得更久,一个总耗时无法告诉你该改哪一层。
这篇文章提供的是一套排查方法:统一计时边界,把请求串成可追踪的执行路径,再用受控实验验证原因。它没有生产实测或性能提升数据,但对正在做知识库问答、接入模型 API 的程序员,有直接的参考价值。
首字慢,得先问从哪里开始计时
检索增强生成(RAG)应用收到问题后,可能依次经过 API 网关、认证、查询向量化、检索、重排、提示词构造、推理服务、响应流转发和客户端渲染。用户只看到一次问答,服务端却完成了多段工作。
request_duration 能描述用户遇到的症状,却不足以选择优化手段。
连「首 token 时间」也可能存在口径差异:推理服务从收到请求开始计时,后端从调用模型开始计时,浏览器则可能从用户提交问题开始计时。三张图都叫首 token 延迟,实际测的却是三段不同的路。
作者建议先记录这些时间点:
t0:应用收到请求。t1、t2:检索开始、检索结束。t3:模型请求发出。t4:收到模型服务的第一个内容块。t5:第一个内容块转发给客户端。t6:最后一个响应块转发完成。t7:客户端首次渲染回答内容,需要客户端埋点。
下面这组定义解决的是指标命名与实际测量范围不一致的问题:
application_first_content_latency = t5 - t0
model_call_first_content_latency = t4 - t3
retrieval_duration = t2 - t1
stream_forwarding_delay = t5 - t4
重点看最后一行:后端已经收到内容,却迟迟没转发出去,这段延迟需要单独检查转发链路,不能直接归到模型头上。
这里还有两个边界。第一,网络传输中的一个内容块不一定对应一个模型 token,不能直接拿它计算逐 token 延迟。第二,不同机器的时钟未必对齐,跨机器时间戳不能随意相减。应优先记录本地经过时间,再通过分布式追踪表达跨服务执行关系。
把请求串起来,才能区分等待和执行
假设模型调用过了 2.3 秒才返回首个内容块,里面可能包含网络传输、准入等待、执行资源排队、未缓存提示词的预填充(prefill),以及初始输出的生成和返回。
只看到这 2.3 秒,无法知道哪一项变慢。
自己运营推理服务,可以尝试暴露内部阶段耗时;使用托管 API,则要接受内部信息可能不可见。此时能确认的是「模型调用首内容延迟升高」,不能直接写成「prefill 变慢」。解码(decode)也只是推理生命周期的一部分,解释不了周边所有等待。
组件监控还存在另一个缺口:数据库慢,不代表某次失败请求真的访问了数据库。它可能命中了缓存,也可能走了回退路径。因此,作者建议用 OpenTelemetry 传播追踪上下文,让日志带上 trace ID 和 span ID,把记录关联到同一次请求。
一个起步用的调用树可以是:
POST /answer
├── authenticate
├── embed_query
├── retrieve_documents
├── rerank_documents
├── construct_prompt
└── generate_answer
├── wait_for_first_content
└── consume_response_stream
要留意 generate_answer 下的两个子节点:等首个内容与持续消费响应流,是不同阶段。这个结构仍然只代表应用能观察到的过程;推理服务是否提供内部 span,要看它是否埋点并正确传播上下文。
追踪还需要记录版本和工作量。作者建议考虑 service.version、deployment.environment、model.identifier、prompt.template_version、retrieval.index_version,以及 input.token_count、output.token_count、retrieval.document_count。
这些是建议字段,并非文章要求遵守的统一命名标准。关键是回答:变慢时,服务版本、提示词、检索索引、输入输出规模,有没有一起变化?
请求 ID 适合放在日志和追踪里定位单次执行,不适合成为全站延迟指标的分组标签。原始私密提示词、密钥和认证头,也不应默认写入遥测数据。
检索改完变慢,为什么还不能直接回滚
作者构造了一个排查场景:检索配置调整后,数据库没有明显变慢,模型也没换,但送给模型的输入更大,首内容延迟也升高了。
合理假设是:检索变化增加了提示词处理成本。
相关变化能提供线索,受控比较才能检验原因。
这里必须交代证据强度:作者明确说这是虚构的工程示例,不是基准测试,也不是客户生产事故。所给原文提到了前后对比的指标,但没有提供具体数值,不能据此计算提升或退化比例。
下一步实验应使用相同的评测问题,固定模型选择和输出上限,只比较新旧检索策略;同时控制或记录并发,在可观察时记录缓存条件。除了延迟,还要测回答质量。少检索几份文档可能更快,也可能恰好删掉回答所需的证据。
线上比较也要按模型或路由目标、提示词模板版本、请求类别适度分组。短问题变成长文档问答,即使系统没有退化,聚合延迟也可能上涨。
作者还建议为 AI 排障助手规定输出结构,分别列出观察、假设、支持证据、缺失证据、建议实验和操作风险。例如,提示词模板从版本 11 升到版本 12 后,输入 token 和首内容延迟一起增长,助手应建议在匹配负载下比较两个版本,并注明内部排队时间不可见。
直接宣布「根因是提示词太大,马上回滚」,则跳过了验证环节。作者也没有声称 LLM 已能从任意遥测数据中可靠识别根因。
我的判断
我认同这篇文章把测量边界放在优化之前。对应用开发者而言,模型调用往往是一个黑盒,很容易把所有等待都归给它。将检索、等待首内容、消费响应流、转发和渲染拆开,至少能让后续行动有明确对象。
它的局限同样清楚:这是一套诊断建议,没有给出落地后的成本、排障效率或性能收益对照。文中提到的异步服务模拟器也是教学工具,不能充当生产基准。
完整追踪还依赖上下文传播和埋点覆盖,内部排队信息尤其取决于服务提供方。一般团队未必能拿到所有数据,也没必要一开始收集全部字段。先围绕眼前的问题补足证据,更符合文章的思路。
和直接缩短提示词、增加副本、替换模型相比,这套方法多了一步验证,但有助于避免改错位置。它并不保证 GPU 没问题,而是要求在决定扩容之前,先证明瓶颈与容量有关。
能拿来做什么
- 给 Node 问答接口记录收到请求、检索完成、模型请求发出、首内容到达和首次转发的本地耗时,先分清应用等待与模型调用等待。
- 给前端增加首次回答内容渲染埋点,结合后端转发记录检查流式链路;如果后端已收到内容而页面仍在等,就继续核查代理和客户端时序。
- 修改检索策略或提示词模板时记录版本,用同一组问题比较新旧配置,同时检查输入 token、首内容延迟和回答质量,并记录并发与可见的缓存条件。
- 让 AI 排障助手按观察、假设、证据、缺口、实验和风险输出结论,对缺少内部计时支持的判断保留不确定性。