AI 应用的端到端延迟预算工程 2026:从 P50 切片到 SLO 反向约束的统一架构
把端到端延迟切成 prefill、decode、检索、网络四个阶段并分配独立预算,把总延迟当作硬约束而非优化目标,叠加流式输出、prefix caching、speculative decoding 与容量规划四项核心工程动作,把 500ms 总预算下的 P99 从开盲盒变成可承诺的 SLO。
约 22 分钟阅读6,504 字13 次阅读博主

把端到端延迟切成 prefill、decode、检索、网络四个阶段并分配独立预算,把总延迟当作硬约束而非优化目标,叠加流式输出、prefix caching、speculative decoding 与容量规划四项核心工程动作,把 500ms 总预算下的 P99 从开盲盒变成可承诺的 SLO。

一句话摘要:当 LLM 应用的端到端延迟超过 500ms,对话就从"自然"变成"工具"——本文从延迟预算的四元组切片出发,给出 TTFT/ITL/P99 的反向 SLO 约束、prefill/decode/检索/网络四个阶段的预算分配、以及 5 条可立即执行的工程落地项,让 100ms 级别的增量不再需要靠赌运气。
如果你在过去六个月里用过任何一个 LLM 应用——无论是 AI 写作助手、AI 客服、还是代码 Copilot——你大概率经历过一种"明明模型不卡、网速不慢,但用户还是嫌慢"的体验。背后的原因很简单:人机对话对延迟的容忍度,比传统 API 严苛得多。神经科学给出的经验值是,人类对话中的停顿若超过 200ms,对方就会感知到"卡";超过 500ms,对话就从"自然流动"降级为"工具调用";超过 1.5s,用户会下意识打开另一件事做,再回头时已经忘了自己在等什么。这个数值是经过二十年语音通信与即时通讯工程验证的"人类感知门槛",并非凭空想象。
但 LLM 应用的端到端延迟由六个独立环节串联而成:客户端网络、网关鉴权、请求队列、prefill(提示编码)、decode(逐 token 生成)、后处理与流式回传。任何一个环节出现 200ms 抖动,最终用户看到的 P99 就是 1.2s——距离"自然对话"已经有 700ms 的鸿沟。过去 14 天 tag 23 下 60+ 篇已发表的文章讨论了"评估/缓存/引用/灰度/协作/可观测性"等"做什么",本文聚焦一个从未被单独成文的角度:在 500ms 内把所有事做完的反向约束工程。
要回答"500ms 内做完所有事",我们需要回答三个层层递进的问题:(1) 500ms 应该怎么切分到四个阶段?(2) 每个阶段在不牺牲质量的前提下,最快能跑到多少?(3) 当某个阶段无法再压缩时,业务侧应该让出什么?这三个问题在传统 web 工程中各有答案(CDN、worker pool、async I/O),但 LLM 应用有一个独特约束:decode 阶段是串行自回归——无论你投入多少 GPU,单个 token 的生成时间存在物理下限。这个下限决定了"延迟预算"在 LLM 应用里不是优化目标,而是工程边界。
本文明确定义四个术语,全文沿用:
后文所有切片都按这四个量纲展开。
把端到端延迟形式化为一个四元组 Σ = (B_total, B_prefill, B_decode, B_io),其中 B_total 是用户容忍的上限,B_prefill 是 TTFT 阶段预算,B_decode 是 ITL × 输出 token 数,B_io 是网络/序列化/解析开销。任何一项工程动作的目标,都是把实际耗时 t_x 控制在 B_x 之内。
不等式 1:分配相容性。必须有 B_prefill + B_decode + B_io ≤ B_total - B_safety,其中 B_safety 是抖动余量,经验值取 B_total 的 15%——对应 P95→P99 的尾部放大系数。如果 B_safety 取 10%,看似"省"了 5% 预算,实际生产中尾部 SLA 会被 P99 抖动频繁击穿。
不等式 2:尾部约束。总延迟的 P99 必须满足:t_total(P99) ≤ B_total - tail_reserve,其中 tail_reserve 是大模型推理特有的"长尾放大"——decode 阶段偶发的 GPU 调度抖动(其他租户的 kernel 抢占、显存换页、HBM 带宽争抢)会让单 token 延迟从 30ms 跳到 80ms 再回落,叠加 200 token 的输出就是 100ms × 200 = 20s 的极端尾部。生产经验:把 tail_reserve 设为 P50 的 2 倍,是不会反复被告警的底线。
不等式 3:质量-成本-延迟的不可能三角。在固定硬件成本下,加速延迟的代价是降低质量(更小的模型 / 更激进的量化 / 更短的上下文),加速质量的代价是更长的延迟(更大的模型 / 更长的思维链),加速成本的代价是更长的延迟(不堆机器就只能排队)。任何"又快又好又便宜"的承诺,要么在某个变量上放宽,要么在统计意义上不成立——这是延迟工程的第一性约束,没有银弹。
给出三个推论,方便后文引用:
Prefill 阶段处理用户输入的 prompt,将其编码为 KV cache。对一个 7B 模型、4K 上下文,prefill 在 H100 上的理论耗时约 50-80ms;在消费级 GPU(M2 / 4090)上则是 200-400ms。这个阶段的复杂度是 O(n²),n 是 prompt 长度——8K prompt 比 4K 慢 4 倍,而非 2 倍。
预算分配建议:实时语音/对话场景,B_prefill ≤ 150ms(P95);流式 copilot 场景,B_prefill ≤ 300ms;离线批处理场景,B_prefill ≤ 5s。P99 应该再放宽 1.5-2 倍,因为 prompt 长度的尾部比 token 数尾部更难预测(用户可能粘贴超长 PDF 文本)。
并发解耦(continuous batching):传统 static batching 把一批请求打包到 GPU 上跑,prefill + decode 同步进行,导致"长 prompt 拖累短 prompt"的队头阻塞。Continuous batching 允许新请求在旧请求 decode 中途插入 prefill,vLLM、TGI、SGLang 三大推理框架在 2024-2025 全部默认开启这个特性。实测:一个 8 卡推理集群,启用 continuous batching 后 P99 延迟从 1.8s 降到 1.1s,吞吐量提升 2-3 倍——这是过去一年 LLM 推理工程最大的单一性能改进。
Chunked prefill:把超长 prompt 切成小块(典型 512-2048 token 一块),分多次 prefill 插入 decode 流中,避免单次 prefill 阻塞整批。代价是 KV cache 写入次数增加,显存带宽压力上升;但对于 prompt 长度方差大的应用(PDF 解析、长代码库),P99 改善可达 40%。
Disaggregated prefill-decode(PD 分离):2025 年下半年开始流行的架构,prefill 节点和 decode 节点分开部署,prefill 完成后把 KV cache 通过 RDMA 推送到 decode 节点。优点是两类工作负载的资源需求不同(prefill 吃算力、decode 吃显存带宽),分开调度可以分别优化;缺点是工程复杂度高、需要 NVLink/IB 网络,且对 prompt 长度有 ROI 阈值(<1K token 不划算)。本文定位的"应用层"通常不需要自己实现 PD 分离,但调用云厂商 API 时(如 Fireworks、Together、Anyscale)应当确认他们是否默认开启。
工程指标:P50 prefill_ms、P99 prefill_ms、排队长度(queue depth)、chunked_prefill_hit_ratio(前缀缓存命中率——若 prompt 共享长 system prompt,前缀缓存可以让 4K system 的 prefill 等价于 100 token)。
Decode 是 LLM 推理最特殊的环节:每生成一个 token 必须等前一个完成,串行自回归。单个 token 的延迟存在物理下限——KV cache 读取 + 一次前向 + 采样 overhead,在 H100 上 7B 模型约 8-15ms/token,在消费级 GPU 上约 30-50ms/token。这意味着一个 500 token 的输出,理论上最少需要 4s(即使其他环节全部为零)——但用户感知的是首 token + 后续 token 滚动渲染,所以 500ms 的"自然对话"是可能的,前提是总输出控制在 50 token 以内。
预算分配建议:B_decode = ITL_P99 × output_token_count。实时对话场景,ITL_P99 ≤ 30ms、output_token_count ≤ 50 → B_decode ≈ 1.5s;流式 copilot 场景,ITL_P99 ≤ 80ms、output_token_count ≤ 200 → B_decode ≈ 16s(但用户此时已经在滚动阅读,所以感知延迟 = TTFT + 5 × ITL ≈ 1s)。
KV cache 命中率与续写延迟:多轮对话中,前几轮的 KV cache 可以保留在显存中,新一轮只需要 prefill 用户的新输入。若 cache 命中,ITL 几乎不变、TTFT 大幅缩短——这就是为什么"会话级缓存"是延迟工程的第一优先级。Cache 命中率 70% 时,平均 TTFT 降低 35-45%;命中率 90% 时,平均 TTFT 降低 60-70%。但 cache 命中率受显存容量制约:7B 模型 4K 上下文的 KV cache 约 1GB/会话,并发 100 会话就是 100GB——这正是 8 卡 H100 集群的容量边界。
Speculative decoding(投机解码):用一个小模型(draft model)预测 4-8 个 token,再让大模型一次性验证。如果接受率 ≥ 60%,整体 ITL 等效降低 2-3x;如果接受率 < 40%,反而因为 draft 模型的额外开销而变慢。投机解码适用于"输出结构性强、可预测"的场景(翻译、代码补全、模板填充),对"开放式创意写作"(诗歌、营销文案)效果有限。生产建议:仅在 ITL 是 P99 主因时启用,且 draft model 选用目标模型 1/10 参数量的蒸馏版。
反压信号:当 GPU 显存饱和或计算单元排队时,decode 延迟会出现"阶梯式上升"——从 30ms 跳到 80ms、再跳到 150ms。这是反压到上游排队的征兆,此时应该:(1) 降低并发上限;(2) 启用 prefix caching 淘汰;(3) 把部分请求路由到备用集群。反压不可怕,可怕的是反压不可观测——很多应用的 P99 抖动来自"没设上限",请求堆积到 OOM 才被回收。
工程指标:ITL P50、ITL P99、TPOT P99、KV cache hit ratio、speculative acceptance rate、batch size(连续 batching 下的动态 batch)。
RAG 应用的延迟预算被一个常被忽视的事实支配:检索链路的耗时与生成链路相当。一个典型的 RAG 请求:query embedding(30-80ms)+ 向量检索(20-50ms)+ 重排序(80-200ms)+ 上下文拼接(5-10ms)= 150-350ms。这还没有算 LLM 调用——若 LLM 的 TTFT 是 200ms,那 RAG 总 TTFT 就是 350-550ms,已经接近"自然对话"的边界。
预算分配建议:B_retrieval ≤ 200ms(P95);B_rerank ≤ 150ms(P95);两者合计不应超过总 B_prefill 的 60%,给 LLM prefill 留出至少 40%。
RAG 链路的延迟瓶颈通常在 rerank:cross-encoder rerank 比 bi-encoder embedding 慢 5-10 倍,但精度高 15-25%。生产中常见的折衷是"bi-encoder 召回 50 个候选 → cross-encoder 重排 top 5",把 rerank 的输入限制在小批量。如果 rerank 仍占延迟大头,可以:(1) 用蒸馏过的 miniLM 做轻量 rerank;(2) 启用 rerank 结果缓存(同一 query 的不同 prefix 命中);(3) 把 rerank 异步化(先返回生成结果,再后台更新 top-k)。
流式 RAG(retrieval-augmented streaming):传统 RAG 是"先检索完所有上下文、再生成";流式 RAG 把检索分成两段——首 token 之前检索 top-1 上下文(足以回答开头),生成过程中并行检索补充上下文。这种架构可以把 TTFT 降低 30-50%,但要求检索系统支持增量查询。LangChain 的 streaming_final_only=False 模式、LlamaIndex 的 StreamingResponse 都支持这种模式,但需要底层 vector DB 支持 query_partial 接口。
文档解析的预算分离:PDF 解析、表格抽取、图像 OCR 这些重操作通常被放在离线阶段——上传时就解析好、缓存到对象存储、在线阶段只读取缓存**。**如果在线阶段还跑解析,P99 会被文档大小方差严重污染(一个 50MB 的 PDF 可能耗时 30s)。硬规则:在线路径不允许运行超过 100ms 的同步解析操作,必须离线预算或异步回调。
工程指标:retrieval_ms P50/P99、rerank_ms P50/P99、context_assembly_ms、streaming_overlap_ratio(检索与生成的并行时长占比)、cache_hit_ratio(query cache / embedding cache)。
网络延迟是"在最后一公里"经常被低估的环节。一个 LLM 推理可能 200ms 完成,但用户感知 800ms——剩下的 600ms 都在网络与序列化上。
TLS handshake + HTTP/2:每个新连接首次握手需要 100-200ms(TLS 1.3 可降到 50-100ms)。流式响应必须复用长连接,否则每条消息都付出 100ms 握手成本。生产建议:(1) 启用 keep-alive + 连接池;(2) 客户端预热一个长连接到网关;(4) 评估 HTTP/3 / QUIC 在移动端的延迟收益。
首字节与流式 chunked transfer:流式响应使用 chunked transfer encoding,每个 chunk 是几十到几百字节。每个 chunk 离开服务端 → 经过负载均衡 → CDN 边缘 → 用户浏览器 → JS 引擎解析 → DOM 更新,每个环节都贡献几毫秒到几十毫秒。首字节延迟(Network First Byte, NFB)是流式 UX 的关键指标——它决定了用户何时看到"AI 在打字"的视觉反馈。
CDN 边缘推理 / 区域路由:如果用户分布在全球,单纯靠中心化推理会被光速限制——北京到洛杉矶的单向 RTT 就有 150ms。解决方案:(1) 多区域部署(us-east、eu-west、ap-southeast);(2) 边缘推理(Cloudflare Workers AI、Vercel Edge Functions);(3) anycast + geo-DNS。代价是多区域状态同步的复杂度——KV cache 不能跨区域共享,每个区域都要预热独立的 prefix cache。
WebSocket vs SSE vs 长轮询:流式响应有三种传输方式。WebSocket 是双向长连接,首字节延迟最低,但握手需要一次 HTTP upgrade;SSE(Server-Sent Events)是单向流,兼容 HTTP/2,可在 CDN 边缘缓存;长轮询每次请求-响应循环,延迟最差但最简单。LLM 应用几乎都选 SSE 或 WebSocket——OpenAI 的 streaming API 默认 SSE,Anthropic 的 Messages API 默认 SSE,Anyscale 与 Fireworks 都支持两种。
工程指标:network_ms P50/P99、first_byte_ms(首字节)、tls_handshake_ms、cdn_hit_ratio、region_routing_accuracy(用户请求被路由到最近区域的比例)。
前六节谈了"每个阶段怎么快",本节谈"在业务侧约束下,怎么反向分配预算"。这是延迟工程与传统性能优化的根本区别:传统优化是先有系统、再测性能、再调优;延迟工程是"先有 SLO、再反向设计系统"。
步骤 1:定义业务侧感知阈值。语音助手/实时对话:500ms 总预算。流式 Copilot / 文档助手:1500-2500ms。批处理 / 报告生成:30-60s。搜索增强问答:1500ms。这些数字不是凭空拍的——背后是用户体验研究、竞品对标(A 人为 500ms、B 助手为 800ms)、用户留存数据(每慢 100ms 留存下降 X%)。
步骤 2:分配到四元组。以 500ms 总预算为例,B_safety=75ms(15%),剩余 425ms。按经验分配:B_prefill=100ms(含 30ms 网络 + 70ms prefill)、B_io=75ms(首字节 + 序列化)、B_decode=100ms(含首 token + 5 个滚动 token 的 ITL × 20ms)。剩下 150ms 给 tail_reserve 和突发抖动。
步骤 3:定义 P50 / P95 / P99 SLO。SLO 不是单一数字——必须是分布上的承诺。常见定义:
步骤 4:容量规划。Little 定律:L = λ × W。若 λ=10 req/s(平均到达率)、W=400ms,则稳态并发 L=4。但 W 是 P95 而非 P50——实际并发会围绕 L=4 波动,P99 可能到 6-8。所以集群容量应按 L_max=10 设计(即 P99 容量 = 2.5× P50 容量)。
步骤 5:5 条可立即执行的工程项:
启用流式响应。把"等完整响应"改成"边生成边推送"。即使总耗时不变,感知延迟从总预算降到 TTFT——500ms 预算在流式下感受为 200-300ms。OpenAI / Anthropic / vLLM 全部默认支持,启用只需要在请求参数里加 "stream": true。
开启 prefix caching / prompt caching。Claude 的 prompt caching(4 cache breakpoints,命中后降本 90% + 提速 80%)、GPT-4o 的自动 prompt cache、vLLM 的 enable_prefix_caching=True——只要 system prompt 是固定的,开启后 P50 TTFT 降低 30-60%。这是延迟工程 ROI 最高的单一动作,没有之一。
选小模型做分类/路由,保留大模型给真正需要的地方。意图分类用 1B 模型(20ms),复杂生成用 70B 模型(300ms),比"全部用 70B"快 5-10 倍。GPT-4o-mini、Claude Haiku、Llama-3.1-8B 都是这个梯队的代表。
关闭思考链除非必要。Chain-of-thought 把 ITL 拉长 3-10 倍(每 token 都被"思考"占用)。如果用户场景是"短答案 + 快响应",关闭 CoT 或用 ReAct 替代。DeepSeek-R1、o1/o3 的 reasoning mode 是慢的代名词——只用在需要推理的场景。
配置合理的并发上限 + 自动扩缩容。K8s HPA 基于 GPU 利用率(70%)扩缩,而非 CPU;预留 20% 容量应对 burst;启用 connection draining 让旧请求不被打断。反模式:无上限接收请求直到 OOM——这是 P99 抖动的最大来源。
步骤 6:监控 + 自动降级。设 SLO 后必须配自动降级——当 P95 接近 800ms(总预算 1.6×)时,自动降级到小模型 / 关闭 CoT / 启用缓存旁路。这比"被动等用户投诉"好 100 倍。
SLO 定义完了,工程实现完了,最后一道关卡是"运行时守护"——确保线上实际延迟持续符合 SLO,而不是只在压测时漂亮。
三层埋点:
enable_timing=True、TGI 的 metrics endpoint、Together 的 trace API)记录 prefill_ms、decode_ms、ITL、batch_size。这是定位瓶颈的根因数据。三层数据通过 trace ID 关联,可以从"用户报慢"反查到"哪一段耗时最长"。
SLO burn rate 报警:Google SRE Workbook 推荐的 burn rate 报警——当 P99 延迟的滑动窗口均值超过 SLO 的 N 倍时触发。常见配置:
三层覆盖可避免"小问题被快速报警淹掉、大问题被快速报警错过"。
回归门禁:每个发版前跑回归测试集(golden prompts),对比新旧版本的 P50/P95/P99 延迟基线。若新版本 P95 退化 > 10%,门禁拒绝合并。这是 vLLM、Together 等推理服务商的标配——他们每个 PR 都跑 latency regression。应用层也应该这样做:维护 100-200 条代表用户行为的 golden prompts,每次发版前跑一遍。
抖动诊断工具:当 P99 突然飙升(从 500ms 到 2s)时,需要快速定位是哪个切片变慢。常用工具:
经验:80% 的抖动来自队列堆积(排队不是延迟的优化目标,而是延迟的另一个名字)——先看排队,再看别的。
抖动隔离:当 P99 抖动发生时,第一动作是"打开 fast path 旁路"——把 5% 的请求路由到一个独立的"无队列"集群,对比 P99。若旁路集群 P99 正常,主集群 P99 飙升 → 主集群排队问题;若两集群都飙升 → 下游问题(数据库、向量库、网络)。
如果你是负责 LLM 应用上线与运维的工程师,本节给出 5 步入门指南,按顺序执行:
常见误区:
推荐的工程模式:
写到这里你会发现,LLM 应用的延迟工程没有银弹,但有清晰的工程范式:先有 SLO、再反向设计、四元组切片、流式优先、运行时守护。这五条原则的工程价值,远比"买更多 GPU"或"换更大的模型"更持久——因为它们在硬件和模型迭代中保持稳定,是延迟工程的"不变项"。
Conversation
0 条