生产环境中的 LLM 延迟:哪些手段真正能见效
大多数 LLM 延迟建议往往会陷入以下两种失败模式之一:要么关注错误的指标,要么推荐的优化过于依赖特定硬件,除非你运行自己的推理集群,否则难以应用。如果你是基于托管 API 或受管推理提供商进行构建,那么这类建议中的大部分都是噪音。
本文专注于真正能产生影响的因素 —— 无论你是否控制整个技术栈,这些技术都适用,且基于生产数据而非基准测试的实验室条件。
延迟包含两个截然不同的问题
在进行任何优化之前,你需要了解 LLM 推理包含两个截然不同的阶段,它们具有本质上不同的瓶颈。
Prefill(预填充)阶段处理你的输入提示词。模型会读取你发送的每一个 token,并构建开始生成所需的上下文。这个阶段是计算密集型的(compute-bound)—— GPU 在并行进行繁重的矩阵乘法,并在单次前向传播中完成。其持续时间直接对应于 Time to First Token (TTFT):即用户在看到任何内容之前等待的时间。
Decode(解码)阶段逐个生成输出 token。每一步都需要从 GPU 显存中加载整个模型的 Key-Value (KV) 缓存。这是受内存带宽限制的(memory-bandwidth-bound),而非计算能力。这就是为什么即使是最强大的 GPU 每一秒也只能输出有限数量的 token。H100 SXM5 拥有 3.35 TB/s 的内存带宽;A6000 拥有 768 GB/s —— 这种差异对解码速度的影响远大于原始计算规格。
这是两个不同的问题。优化其中一个并不会自动改善另一个。团队往往投入大量精力降低 TTFT,却忽视了 token 间的延迟(inter-token latency),然后疑惑为什么长文本输出仍然感觉缓慢。
流式传输:你尚未利用的最廉价的优化方案
如果你的应用程序目前在显示任何内容之前都在等待完整的响应,那么修复这一点就是你能做的杠杆率最高的改变。流式传输(Streaming)会增量地输出 token,对感知延迟的影响是巨大的 —— 通常在用户感知响应速度方面会有 10 到 100 倍的提升。
原因在于心理层面而非技术层面:人类在阅读时会同时处理文本。一个在 400 毫秒内开始出现并持续流式传输 3 秒的响应,感觉上比一个在 3.4 秒后才交付完整文本的响应要快得多。代码补全工具设定了更严格的标准 —— 低于 100 毫秒的 TTFT 是让建议感觉像是编辑器原生功能而非干扰的门槛。
各类用例的 TTFT 基准目标:
- 聊天机器人:500 毫秒以下
- 代码补全:100 毫秒以下
- 批处理流水线:最多 30 秒也是可以接受的
流式传输并不会减少总生成时间 —— 实际上它会增加一点点额外开销。但对于任何交互式用例,它是首先要实现的优化,且除了在 API 调用中启用流式传输标志(streaming flag)外,不需要任何额外操作。
KV Cache:多层级的优化策略
Key-Value (KV) 缓存存储了中间注意力计算结果,因此模型不需要在每个 token 生成步骤中重新计算它们。这是让解码阶段变得可行的核心。但是,KV 缓存有几种不同的策略层层叠加,混淆它们会导致错失优化机会。
单次请求 KV 缓存(Per-request KV cache) 是基础 —— 每个推理引擎都会这样做。它在单个请求的解码阶段将注意力键(key)和值(value)保留在 GPU 显存中。其内存占用相当可观:对于标准精度的 Llama-3-70B,一个 4,096 token 的上下文大约消耗 1.3 GB 的显存(VRAM)。
前缀缓存(Prefix caching) 将这种机制扩展到了多个请求。当多个请求共享相同的提示词前缀时 —— 比如所有调用都使用相同的系统提示词 —— 推理引擎可以计算该前缀一次,并在所有请求中复用其 KV 状态。在实践中,这能为命中缓存的请求降低 60-80% 的 TTFT。在代理工作流(agentic workflows)中,数百个请求都以相同的工具定义和系统上下文开头,这种优化的意义非常重大。
如果你使用的 是托管 API 而非运行自己的推理引擎,你仍然可以通过 API 级提示词缓存(prompt caching) 从前缀缓存中受益。关键在于结构化你的请求,使静态内容(系统指令、背景上下文、工具定义)位于动态内容(用户消息)之前,并在静态与动态内容的边界处设置缓存断点。常见的错误是标记了错误的区块 —— 如果你的缓存断点随每个请求而移动,你将永远无法命中。
一个实用的放置规则:缓存断点应该标记在跨请求保持一致的内容末尾,而不是最后一条消息的末尾。通过监控响应中的 cache_read_input_tokens 来确认你是否真的命中了缓存。
语义缓存(Semantic caching) 则完全是另一个层级。它不是缓存精确的提示词前缀,而是对查询进行嵌入(embedding)处理,并将语义相似的请求与向量缓存进行匹配。如果有人问 “退货政策是什么?” 而另一个用户问 “我如何退货?” —— 意图相同,措辞不同 —— 语义缓存可以为两者提供相同的缓存响应。研究表明,在具有重复查询模式的生产部署中,这可以减少高达 90% 的计算量。这在面向客户的应用程序中价值最高,因为许多用户会询问相同问题的变体。
投机解码:长输出场景下的制胜法宝
投机解码(Speculative decoding)解决了顺序生成 token 的根本瓶颈。一个轻量、快速的“草稿”模型并行生成几个候选 token(通常为 3-12 个)。随后,较大的目标模型在一次前向传播中验证所有候选 token——批量接受正确的 token 并拒绝第一个错误的 token。当草稿模型预测正确时,你只需支付约一次目标模型推理步骤的成本,即可获得多个 token。
在生成任务沉重的负载下,2-3 倍的现实加速效果已有据可查。问题在于接受率(目标模型同意草稿模型的频率)波动很大。在草稿模型见过类似文本的特定领域任务中,接受率往往能达到 70-90%。而在通用或高度多变的任务中,得分则较低。
如果你正在运行自己的推理栈,请监控推理引擎中的 spec_decode_draft_acceptance_length。如果每个步骤的接受率低于 0.5 个 token,则表明草稿-目标模型配对不佳,投机解码可能不值得增加额外的复杂性。如果你使用的是托管推理提供商,支持的模型配对通常会自动处理这一过程。
量化:基础设施的解锁关键
模型量化(Quantization)——将权重精度从 16 位降低到 8 位或 4 位——其核心价值不在于降低延迟,而在于基础设施的经济性,从而间接实现延迟优化。
数据非常惊人。Llama-3-70B 在 BF16 精度下占用约 140GB 显存,需要两块 H100 80GB GPU,每小时成本约为 2.69 美元。同样模型在 4-bit AWQ 量化下,可以放入总显存 96GB 的双 RTX A6000 中,每块 GPU 每小时成本约 0.49 美元——在大多数任务质量损失极小的情况下,节省了超过 80% 的成本。
与延迟的联系在于:将模型部署在更少、更便宜的 GPU 上,意味着你可以在相同的预算下运行更多副本,从而减少排队时间。H100 上的 FP8 量化可减少约 50% 的显存占用,在生 成密集型任务中吞吐量最高可提升 1.6 倍。释放出更多显存后,你还可以持有更大的 KV 缓存,从而提高缓存命中率。
对于生产部署:在投入昂贵的硬件之前,先进行量化。对于推理任务,质量权衡通常是可以接受的,而基础设施的灵活性提升却是巨大的。
批处理:单租户思维如何扼杀性能
静态批处理(Static batching)——等待填满整个批次后再处理——会引入不必要的队头阻塞(head-of-line blocking)。一个长请求会阻塞其后所有的短请求。虽然吞吐量上升了,但尾部延迟(tail latency)会飙升。
持续批处理(Continuous batching,也称为 in-flight batching)通过在处理槽位空闲时立即将新请求插入解码循环来解决此问题。当一个序列生成结束,新序列会立即补位,而不是等待整个批次完成。生产数据显示,在持续批处理下,稳定流量时的 GPU 利用率可达 60-85%,而原始的静态服务利用率通常较低。
批次大小对延迟的影响不容小觑:一项基准测试显示,由于 GPU 利用率提高,延迟从批次大小为 1 时的 976ms 下降到批次大小为 8 时的 126ms。当批次非常大时,随着排队压力增大,延迟会再次攀升。动态批处理(根据当前队列长度和延迟目标进行缩放)是生产环境的模式,而非固定设置。
如果你没有运行自己的服务栈,这由推理提供商处理。相关的核心问题变成了提供商是否使用持续批处理,以及他们如何处理队列管理——这会在负载下显著影响你的 P99 延迟。
衡量指标:哪些该关注,哪些该忽略
平均延迟是生产环境中最没用的指标。它掩盖了决定 P95 或 P99 用户体验是否糟糕的尾部延迟。一个平均延迟 300ms 的 TTFT 系统,其 P99 延迟可能高达 4 秒——这种离群值会引发支持工单和用户流失。
请追踪以下指标:
- P50 TTFT:典型用户的体验
- P95 TTFT:接近延迟服务水平目标(SLO)的最坏情况
- P99 TTFT:真正的尾部延迟——测试 SLA 的关键数值
- Token 间延迟(Inter-token latency):对于长文本生成尤为重要
- 每个输出 token 的时间 (TPOT):
(总延迟 - TTFT) / (输出 token 数 - 1),解码阶段的衡量指标
如果你使用 vLLM 进行自建托管,请监控 vllm:gpu_cache_usage_perc 和 vllm:num_requests_waiting,以区分你是达到了 KV 缓存限制(受显存约束)还是计算限制。这两者指向不同的修复方案:缓存压力需要进行量化或 KV 缓存优化,而计算压力则需要更好的批处理或增加副本。
避免针对固定提示/输出长度的合成基准测试进行优化。生产流量在这两个维度上都有很大的方差,固定长度的基准测试会系统性地低估尾部延迟。
实际优先级排序
考虑到工程带宽有限,以下是按投入产出比排序的建议:
- 启用流式输出(Streaming)——即时的用户体验提升,代码改动极小
- 正确实现前缀/提示词缓存(Prefix/Prompt caching)——结构化你的提示词,以最大限度提高静态内容的缓存命中率
- 如果自建托管,请量化你的模型——在购买更多 GPU 之前解锁基础设施的灵活性
- 建立基于百分位数的监控——你无法改进你没有正确衡量的内容
- 在推理引擎中评估持续批处理——智能体/结构化输出工作负载使用 SGLang,通用聊天使用 vLLM
- 添加投机解码——在生成密集型工作负载上收益很高,但需要仔细选择草稿模型
投机解码和高级批处理通常排在最后,因为它们需要更多的基础设施投入和调优。无论你是否控制推理栈,流式输出和缓存都是非常有效的手段。
总结
LLM 延迟不是一个可以优化的单一数字 —— 它是两个独立的问题(TTFT 和 decode),需要不同的干预措施,且其衡量方式是大多数监控设置难以妥善处理的。那些能够持续交付快速 AI 应用的团队,并不一定运行着最奇特的推理基础设施。他们启用了流式传输,构造了提示词以命中缓存,并构建了能够展现尾部延迟而非平均值的监控体系。在你需要考虑 speculative decoding 或自定义 serving engines 之前,这些基础工作已经解决了大部分用户感知中的“快”与“慢”。
- https://www.clarifai.com/blog/llm-inference-optimization/
- https://www.runpod.io/blog/llm-inference-optimization-techniques-reduce-latency-cost
- https://bentoml.com/llm/inference-optimization/llm-inference-metrics
- https://www.baseten.co/blog/how-we-built-production-ready-speculative-decoding-with-tensorrt-llm/
- https://platform.claude.com/docs/en/docs/build-with-claude/prompt-caching
- https://research.aimultiple.com/llm-latency-benchmark/
- https://mljourney.com/latency-optimization-techniques-for-real-time-llm-inference/
- https://docs.anyscale.com/llm/serving/benchmarking/metrics
