Continuous Batching
连续批处理在每个生成步动态加入和移除请求,提高 GPU 在变长输出下的利用率。它用「迭代级调度」取代「请求级静态批次」,让不同长度的生成共享同一批计算资源。
这一思想最早由 Orca(OSDI 2022)系统化提出,称为 iteration-level scheduling(迭代级调度),在 TensorRT-LLM 中又称 in-flight batching。它的由来是自回归生成的一个固有矛盾:不同请求生成的 token 数不同,若沿用训练式的静态 batch,一个 batch 必须等其中最长请求生成完毕才整体结束,短请求的槽位长期闲置,GPU 利用率随长度差异增大而下降。连续批处理把「batch」从「请求集合」重新定义为「当前迭代参与计算的请求集合」,让结束的请求在当步退出、排队的请求在下步进入,从而把槽位利用率维持在高位。
快速开始¶
在真实并发压测中设置最大 batch token、排队上限和公平策略,同时看 TTFT 与吞吐。
- 配置上限:设置单步最多参与的 token 数与请求数,避免显存或算力超限,例如 vLLM 的
--max-num-batched-tokens与--max-num-seqs。 - 配置排队与公平:设置请求等待上限、抢占或优先级策略,防止长请求饥饿或短请求被无限插队。
- 压测:用变长输出负载并发压测,记录 TTFT、TPOT 与稳态吞吐。
验证成功的关键是吞吐提升的同时延迟分布可控:既要看到空闲槽位被及时填补,也要确认没有某类请求被长期饿死。
机制¶
已结束请求立即释放位置,新请求可在后续步进入批次,不需要等待整批完成。由于 decode 是逐 token 推进的,每个请求在生成结束的当步即可退出,调度器随后把空闲槽位分配给排队请求。
调度循环可以用下面的伪代码概括,其核心是「每步都重新决定本步的请求集合」:
while 有未完成请求:
让每个活跃请求生成一个 token
把已结束或触达 max_tokens 的请求移出本批
回收它们占用的 KV 空间
从等待队列按策略取出请求补入本批
本批请求一起做下一次 decode 前向
与静态 batch 的差别在于「批成员」是每步变化的:静态 batch 一次装入固定请求、整体完成后才换下一批,而连续批处理在每次迭代之间都做一次「退出 + 补入」,这也是名称中「continuous」的含义。
这带来一个关键取舍:调度器需在吞吐、短请求延迟和长请求公平之间平衡。若始终优先短请求,长请求可能饥饿;若严格按到达顺序,短请求会被长请求拖慢。常见做法是结合「先到先服务 + 抢占/优先队列」等策略。
实现上还要与显存管理配合:新请求进入时为其分配 KV 空间,退出时及时回收,否则批次虽可扩展,显存却可能成为新瓶颈。调度周期本身也有开销,需避免每步频繁重排造成收益被抵消。
案例¶
混合 10-token 与 1K-token 请求时,静态 batch 会等待长请求;连续批处理能提前补入短请求,但需防止长请求饥饿。
以「短回复 + 长文续写」混合流量为例:静态批处理下,一个批次必须等最长请求生成完才整体结束,短请求的槽位长期闲置;连续批处理在短请求结束时立刻让位,新短请求得以插入,整体吞吐与短请求延迟都改善。
代价是长请求可能持续被新请求挤占,完成时间变长。为此可设置短请求优先但长请求有保底配额,或限制连续抢占次数。观察指标应同时包含 P50/P99 延迟与每类请求的完成时间。
常见失败点是调度参数失配导致长请求几乎不推进,排查时看各请求的等待时长分布与「被抢占次数」,据此调整优先级与配额。