Continuous Batching

一句话解释

连续批处理在迭代边界动态加入新请求并移除已完成请求,提高推理设备利用率。

它解决什么问题

自回归请求需要的 decode 步数不同。静态批处理若把一组请求绑定到全部完成,短请求结束后留下空槽,新请求仍要等待最长请求退出;若为了低延迟减小 batch,又浪费设备并行能力。

连续批处理把调度单位从“整个请求”下沉到“本轮模型迭代”。每轮结束时,scheduler 重新决定活跃集合:完成或取消的请求退出,等待请求进入,未完成请求继续生成。

1
2
3
时刻 t0:A B C 执行一轮
时刻 t1:B 完成,D 进入;A C 继续
时刻 t2:A 完成,E 的 prefill 进入;C D 继续

它改变的是跨请求的工作编排,不改变单个请求的自回归语义。每个请求仍然只能在自己的前缀之后生成下一个 token。

为什么它与 KV Cache 耦合

请求能否进入下一轮,不只看计算槽位,还要看新增 token 所需的 KV Cache 容量。调度器通常需要同时处理:

  • prefill 与 decode 的计算形状不同,混合比例会影响 TTFT 与 TPOT;
  • 长上下文请求持续增长 KV,占用会压缩可并发请求数;
  • 内存不足时要等待、抢占、重算或卸载,策略会直接进入尾延迟;
  • PagedAttention 降低分配浪费,但不能消除容量上限。

因此“连续”不等于“所有请求都立刻进入”。它只是允许每轮重组批次;admission、优先级和容量约束仍决定谁被调度。

收益与代价

收益是减少静态 batch 的尾部空洞,使 GPU 在长度不同的请求间保持更高利用率。代价是 scheduler 更复杂,而且吞吐最大化可能伤害单请求延迟或公平性。大 prefill 若一次吞入,可能阻塞正在 decode 的请求;过度偏向 decode 又会让新请求 TTFT 变差。Chunked Prefill(待建节点)正是缓和这一冲突的一类机制。

怎样验证

不要只跑固定长度合成请求。至少改变到达率、输入/输出长度分布、突发强度与并发上限,同时记录吞吐、TTFT/TPOT 的 P50/P95/P99、排队时间、抢占/重算和 KV 使用率。

与其他节点的关系

  • 向上:vLLMSGLang IMPLEMENT 各自的迭代级调度策略。
  • 同层:continuous batching CONTRASTS-WITH static batching,并与 prefix caching、chunked prefill 等策略共同影响调度。
  • 向下:它 DEPENDS-ON KV Cache 容量与 PagedAttention 等内存管理机制。
  • 评测:其效果 EVALUATED-BY 延迟分布、吞吐、公平性和真实请求轨迹,而非单一 token/s。

引擎之间如何取舍这些目标仍需在 vLLM 和 SGLang 的调度器区别是什么 中做同负载验证。

调度器实际要做的预算

每一轮调度至少同时受三个预算约束:本轮可执行的 token 数、可分配的 KV block 数、以及请求级的公平/优先级。只按“当前 batch 还有空槽”接入请求,会在长 prompt 或长输出到来后迅速耗尽 KV;只按 KV 空间接入,又可能让 GPU 因 batch 太小而空转。一个可解释的 admission 决策应把新请求的输入 token、预计输出上限、已有请求的剩余 KV 和优先级写进日志。

1
2
3
4
5
等待队列
→ 估算新增 prefill token + decode token 的计算预算
→ 预留可增长的 KV block
→ 与正在 decode 的请求比较 TPOT/P99 风险
→ 接入、延后、chunk 或拒绝

公平性是吞吐之外的真实取舍

最长请求优先可能减少频繁重排,但会让短请求的 TTFT/P99 变差;短请求优先可以改善交互体验,却可能让长上下文请求饥饿。突发流量下还要区分“排队等待”和“执行等待”:连续 batching 只能消除静态 batch 的空槽,不能创造 GPU 容量。评测应按输入/输出长度分桶,报告每桶的等待、TTFT、TPOT、完成率和被抢占/重算次数,而不是只给全局平均。

chunked prefill 的 chunk 大小就是一个实际旋钮:太大仍会打断 decode,太小会增加调度与 KV bookkeeping。应在目标模型和硬件上用固定流量回放搜索,而不是把某个默认值当成普适最佳值。

参考资料