GPU 饥饿:某个租户的推理提示词如何导致你的共享推理端点停滞
你的仪表盘显示 GPU 状态健康。利用率维持在 80% 左右,每秒生成的 token 吞吐量看起来很正常,冷启动很少见,而且模型也是你要求的那个。然而,你的报警器响了,因为 p99 延迟翻了三倍,少数用户遇到了超时,支持工单都在描述同一件事:“应用冻结了 20 秒,然后又恢复了。” 你调取了一个追踪(trace),发现一个毫不相关的客户发送的 28,000 个 token 的推理请求,正与每一个停滞的调用处在同一个批次(batch)中。某个租户的深度思考提示词刚刚抢走了其他所有人的机会。
这就是队头阻塞(head-of-line blocking),它是推理模型进入流量组合后,破坏共享 LLM 推理的典型故障模式。这种模式并不新鲜 —— 存储系统和网络栈已经与之斗争了几十年 —— 但由于连续批次(continuous batching)和 KV 缓存固定(KV-cache pinning)的工作方式,它在 GPU 上呈现出一种特定的形态。大多数团队针对平均负载进行设计,却太晚才发现,一旦请求大小不再相似,“共享推理更便宜”就不再成立了。
使这个问 题如此微妙的原因在于,你的标准指标中没有一个能显示出问题。GPU 利用率很高,这看起来是件好事。整个端点的每秒生成 token 数也很可观。没有单个请求是“慢”的 —— 推理提示词完成所需的时间与它单独运行的时间一致。遭受苦难的是那些在巨头之后等待的短请求,而你的指标从未按请求类别进行拆分,无法向你展示这一点。
为什么连续批次为了吞吐量而牺牲了队头阻塞
现代 LLM 服务器(如 vLLM、TGI 和 SGLang)运行一个调度程序循环,该循环选取一组工作序列,在 GPU 上对整个批次运行一次 token 生成迭代,然后立即重新评估。完成的序列离开批次,新的序列加入,GPU 永远不会因为等待最慢的成员完成而闲置。这被称为连续批次或迭代级批次(iteration-level batching),也是自 2023 年以来推理吞吐量增长如此之快的最大单一原因。
问题在于,吞吐量不等于延迟。连续批次假设批次中的序列具有大致相当的形状。当批次中的一个序列具有必须在开始生成之前进行预填充(prefill)的 30,000 个 token 的提示词时,该批次中的所有其他序列都会等待该预填充完成。预填充阶段是计算受限的,一次可能消耗整个 GPU 数百毫秒 —— 这段时间足以在任何人收到响应 token 之前耗尽你的 p99 预算。
推理模型从两个方面加剧了这一问题。它们的提示词往往很长,因为用户会粘贴代码、文档或之前的对话轮次。它们的输出也很长 —— 几千个 token 的思维链(chain-of-thought)追踪是很正常的。单个推理请求占用的 KV 缓存槽位和调度程序注意力,比典型的聊天补全长一个数量级。将其与一系列短分类调用分在一个批次中,你会得到两者的最坏情况:短调用被预填充阻塞,而长调用保持其 KV 缓存固定,导致没有任何东西可以干净利落地抢占它。
没人观察的诊断信号
遇到此问题的团队首先会注意到用户报告的卡顿。当他们打开仪表盘时,平均值已经平滑了峰值。需要观察的信号不是平均延迟或利用率 —— 而是按请求类别分桶的请求形状和 p99 延迟的联合分布。
具体来说:将你的流量分成大小桶(提示词长度、预期输出长度、max-tokens 设置),并绘制每个桶随时间变化的 p99 延迟。一个健康的端点显示短请求桶的 p99 保持稳定,无论长请求桶在做什么。不健康的端点则显示短请求桶的 p99 紧跟长请求桶 —— 短请求在为碰巧与其分在同一批次的长请求买单。这种相关性就是队头阻塞的指纹,它永远不会出现在单个聚合延迟图表中。
还有两个信号有助于确认诊断。调度程序的队列等待时间(与 GPU 上的时间分开)可以告诉你请求是卡在准入阶段还是卡在执行阶段。此外,KV 缓存占用率占容量的百分比可以告诉你是否受到抢占限制 —— 当占用率接近 100% 时,服务器正在发生抖动(thrashing),逐出并重新计算前缀,延迟峰值即将呈非线性增长。
四种缓解措施,按侵入性排序
一旦看清这种模式,修复方法取决于你对技术栈的控制程度。按造成干扰的程度粗略排序:
- https://arxiv.org/html/2504.20828v2
- https://docs.vllm.ai/en/stable/configuration/optimization/
- https://blog.vllm.ai/2025/09/05/anatomy-of-vllm.html
- https://huggingface.co/blog/continuous_batching
- https://huggingface.co/blog/tngtech/llm-performance-prefill-decode-concurrent-requests
- https://huggingface.co/blog/tngtech/llm-performance-request-queueing
- https://www.usenix.org/system/files/osdi24-zhong-yinmin.pdf
- https://arxiv.org/pdf/2511.04791
- https://www.usenix.org/system/files/osdi24-fu.pdf
- https://ennanzhai.github.io/pub/sosp25-aegaeon.pdf
- https://arxiv.org/html/2602.16603
