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) -                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论