LLM 流式推理的自适应调度引擎:背压传播、批处理优化与过载保护
在 LLM 推理服务中,Streaming(流式)输出带来了极致的用户体验——用户可以逐字看到生成结果。但这也给服务端带来了独特的调度难题:请求的生命周期不可预测、Token 生成速率差异巨大、GPU 显存随时可能爆满。本文将深入探讨如何在流式推理场景下构建一个具备背压感知、自适应批处理和过载保护的生产级调度系统。
一、流式推理的调度困境
传统微服务中,请求的生命周期是确定的:到达、处理、返回。但 LLM 流式推理完全不同——
非对称的生命周期:一个请求的"完成时间"取决于 max_tokens 参数或模型何时生成 EOS token。一个请求可能只需 5 个 token(几毫秒),也可能生成 4096 个 token(数十秒)。客户端连接保持着 SSE(Server-Sent Events)或 WebSocket 连接,随时接收增量输出。
GPU 显存的动态绑定:每个活跃请求都持有 KV Cache 空间。max_tokens 越大,该请求占用的显存就越多。当显存逼近上限,新请求无法被调度,已运行请求可能在 decode 中途因显存不足而崩溃。
Token 生成速率的剧烈波动:Prefill 阶段(处理输入 prompt)的计算量与 prompt 长度成正比,是计算密集型的;Decode 阶段(逐 token 生成)是带宽密集型的,受 GPU 显存带宽限制。Prefill 和 Decode 混合执行时,Prefill 会"吃掉"大量计算资源,导致所有请求的 decode 延迟飙升。
这三个特性叠加,构成了流式推理调度的核心挑战。
二、背压传播模型
在控制理论中,Backpressure(背压)是指下游处理能力不足时将压力向上游传导,使整个系统自动降速。对于流式推理系统,我们需要三个层次的背压感知:
2.1 GPU 显存预算器
┌─────────────────────────────────────────────────────┐
│ GPU Memory Budget │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Request A │ │Request B │ │Request C │ ← Active │
│ │ 512 slots│ │ 768 slots│ │ 256 slots│ Requests │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Reserved for Prefill │ ← Static │
│ │ (max_prompt_len * │ Reserve │
│ │ n_layers * d_model * 2) │ │
│ └──────────────────────────────────────┘ │
│ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░ │
│ Used: 1536 slots Free: 2464 slots │
│ Watermark: 90% → Reject new requests │
└─────────────────────────────────────────────────────┘
```
核心思路是设一个显存使用水位线。当显存使用超过 90%,调度器拒绝新请求(返回 429 Too Many Requests),而非冒险接受新请求导致所有在途请求失败。
2.2 队列深度的传导
当调度器开始拒绝请求时,这个信号需要传导到上游:
class BackpressureAwareScheduler:
def __init__(self, max_queue_depth=100, watermark_ratio=0.9):
self.queue = deque()
self.max_queue = max_queue_depth
self.watermark = int(max_queue * watermark_ratio)
self.gpu_mem_usage = 0
self.gpu_mem_limit = 0
def admit_request(self, request) -

发表评论 取消回复