Continuous Batching
Continuous Batching(连续批处理)是一种按推理迭代重新组批的调度方式:请求完成后立即离开批次,等待队列中的新请求随即补入,而不必等整批请求全部结束。它主要解决生成长度不一致造成的算力空洞,是在线 LLM 服务提升吞吐量和降低排队时间的基础能力。
静态批处理的问题
传统静态批处理会先凑齐一组请求,再让它们共同运行到全部结束。由于不同请求的输出长度不同,短请求结束后占用的位置无法及时复用:
| 推理阶段 | 静态批处理 | Continuous Batching |
|---|---|---|
| 请求完成 | 等待同批其他请求 | 立即移出批次 |
| 新请求到达 | 等当前批次全部完成 | 下一次可调度迭代即可加入 |
| 批次大小 | 一批内基本固定 | 随请求状态动态变化 |
Continuous Batching 也被称为 iteration-level scheduling 或 in-flight batching。它与“去掉 Padding”不是一回事:前者改变请求的调度生命周期,后者只减少一个批次内部的无效计算。
核心原理
设第
调度器通常在每次模型前向计算前执行以下操作:
- 接收新请求,并放入等待队列。
- 移除已完成或被中止的请求,释放其请求槽位和 KV Cache。
- 根据显存、Token 预算和优先级,从等待队列接纳新请求。
- 选择新的 Prefill 请求或继续运行 Decode 请求;支持混合执行时,也可把两者组成同一批次。
其中,Decode 请求每轮通常只输入一个新 Token,历史上下文保存在 KV Cache 中。因此,请求可以跨越多个批次持续生成,批次成员也可以在每轮发生变化。
调度时真正受限的资源
Continuous Batching 不是简单地“有空位就塞请求”。调度器至少需要同时考虑:
- 请求槽位:并发请求数不能超过运行上限。
- KV Cache 容量:要为已有上下文和后续生成预留空间。
- 单轮 Token 预算:Prefill 的输入 Token 数远大于 Decode,可能让一次迭代耗时突然增加。
- 公平性与优先级:持续到来的短请求不应让长请求无限等待。
如果直接把一个超长 Prompt 整体插入正在 Decode 的批次,虽然实现了连续接纳,却可能让所有 Decode 请求等待一次很长的 Prefill 计算。因此调度器还需要限制单轮工作量,在吞吐量与延迟之间取舍。
优势与代价
| 方面 | 作用 |
|---|---|
| GPU 利用率 | 及时填补已完成请求留下的空位,减少空转 |
| 吞吐量 | 在相同硬件上并发服务更多请求 |
| 排队时间 | 新请求不必等待最慢的同批请求结束 |
| 延迟稳定性 | 仍受长 Prefill、抢占和显存压力影响,需要额外调度策略 |
它优化的是系统整体效率,但不保证每个请求都更快。批次越大,吞吐量通常越高;同时单轮计算时间和排队竞争也可能增加。因此应同时观察:
- TTFT(Time To First Token):请求到首个输出 Token 的时间。
- TPOT(Time Per Output Token):相邻输出 Token 的平均间隔。
- Goodput:满足延迟目标的有效吞吐量,而不只是每秒处理的总 Token 数。
SGLang 中的实现
SGLang 的调度循环会维护等待队列与运行中的 Decode 批次,在每轮前向计算后过滤已完成请求,再尝试接纳新的 Prefill 请求。接纳过程同时检查请求槽位、KV Cache 和 Prefill Token 等预算。
相关入口: