GPU 调度是一个排队问题,而不是资源配置问题
当一个团队的推理端点开始无法达到延迟目标时,他们做的第一件事通常是购买更多的 GPU。一个月后,他们会发现第二件事:p99 延迟几乎没变,而账单却翻了一倍。显卡利用率徘徊在 40%,而尾部延迟依然很难看。有人添加了自动扩缩容(autoscaler)。自动扩缩容频繁震荡。现在有了更多的显卡,更高的成本,以及来自同一批用户的同样抱怨。
错误在于将推理缓慢视为容量不足。事实几乎从未如此。你面临的是一个披着容量问题外衣的排队问题。LLM 端点的尾部延迟取决于大小迥异的请求如何随时间共享固定的计算池——这是典型的调度问题,而非资源配置问题。在你理解服务栈实际运行的排队规则(queue discipline)之前,你添加的每一块 GPU 都只是更昂贵的变慢方式。
这很不直观,因为它违背了我们对几乎所有其他后端服务的认知。对于无状态的 Web 层,延迟确实主要是一个容量问题:添加副本、分散负载,大功告成。LLM 服务打破了这种直觉,原因很特殊——工作单元是不统一的,其大小无法预知,并且在整个生命周期内都会占用昂贵的内存。这三个特性使一队高速加速器变成了一个吞吐量由排队动态决定的系统,瓶颈会根据刚刚进入的请求而不断移动。
为什么更多的显卡不能减少尾部延迟
首先看 GPU 在推理过程中实际在做什么。一个 LLM 请求有两个具有相反资源特性的阶段。预填充 (Prefill) 通过一次密集的前向传播摄取提示词(prompt),其中每个 token 都会关注其他所有 token——这是计算密集型的,受限于原始 FLOPS。解码 (Decode) 随后一次生成一个输出 token,每一步都必须从内存中为之前所有 token 重新加载键值缓存(KV cache)——这是受内存带宽限制的。GPU 在解码过程中大部分时间都在加载 KV 张量,而不是在计算。
因此,单块显卡无法以任何简单的方式被“利用”,因为这两个阶段给芯片的不同部分带来了压力。当你增加显卡时,你按固定比例增加了两种能力——但你工作负载中预填充和解码的比例每秒都在变化。结果是总利用率保持在低位(因为相对于另一个资源,总有一个资源是空闲的),而针对刚刚到达的请求的瓶颈资源却已经饱和。你为不平衡、随时间变化的需求购买了平衡数量的计算力。
更深层的问题是线头阻塞(head-of-line blocking)。假设一个带有 32,000 个 token 提示词的请求落在了一个正忙于为其他 50 个用户进行解码的批次中。该预填充是一个庞大的、计算密集型的工作单元,当 GPU 苦苦处理它时,那 50 个用户的每个解码步骤都会停滞。 他们的 token 间延迟(inter-token latency)会激增。他们谁也没做错什么;他们只是排在一个“巨鲸”请求后面。除非那个巨鲸落在了 另一块 显卡上,否则增加 GPU 对他们毫无帮助——而在幼稚的负载均衡下,这就像抛硬币一样随机。你增加了一倍的成本来提高避免停滞的概率,但这并不等同于修复停滞。
这就是为什么利用率与尾部延迟的悖论如此普遍。仪表盘上显示集群利用率不足,恰恰是因为重要的请求都在 等待 而不是在 运行。空闲的硅片和糟糕的 p99 并不矛盾。它们是调度失败的标志。
真正提升吞吐量的三个旋钮
如果增加资源不是杠杆,那么什么才是?三个排队规则机制决定了你的实际吞吐量,它们都关乎工作如何准入、排序和打包,而不是存在多少硬件。
- https://www.anyscale.com/blog/continuous-batching-llm-inference
- https://www.runpod.io/articles/guides/vllm-pagedattention-continuous-batching
- https://www.spheron.network/blog/prefill-decode-disaggregation-gpu-cloud/
- https://arxiv.org/html/2507.06608v4
- https://arxiv.org/html/2602.16603v1
- https://arxiv.org/html/2408.15792v1
- https://haoailab.com/blogs/vllm-ltr/
- https://arxiv.org/html/2504.20068
- https://arxiv.org/html/2510.03243
- https://docs.vllm.ai/en/v0.8.0/performance/optimization.html
