Chapter 05 · 调度与吞吐:batching、continuous batching、paged attention
你已经知道单个请求如何活着,现在问题变成:一批请求同时进来时,系统怎么排队、怎么组 batch、怎么尽量把 GPU 吃满、又怎么避免缓存管理失控。这一章就是从单请求视角正式切到多请求系统视角。
你已经知道单个请求如何活着,现在问题变成:一批请求同时进来时,系统怎么排队、怎么组 batch、怎么尽量把 GPU 吃满、又怎么避免缓存管理失控。这一章就是从单请求视角正式切到多请求系统视角。
在线推理服务最难的地方,往往不是单次执行,而是“很多不同长度、不同状态、不同优先级的请求一起进来时,怎样不把硬件浪费掉”。这一章讨论的 batching、continuous batching 和 paged attention,本质上都在回答这个问题。
如何在吞吐、延迟、显存和公平性之间做调度权衡。
批处理不是把请求“攒一攒”这么简单,而是和请求状态机、缓存管理一起设计。
先抓住“请求何时进入 batch、何时退出、缓存何时分配与回收”这三条主线。
单个请求往往无法充分利用 GPU 资源,所以系统会尽量把多个请求组织在一起执行。这就是 batch 的基本动机:提高硬件利用率,把更多 token 处理放进同一个执行窗口里。
但在线服务不是离线训练。离线场景里你可以等一批样本凑齐再统一跑,在线服务里用户在等待响应。于是 batch 从一开始就不是纯粹的“吞吐工具”,而是一个需要在等待时间和利用率之间平衡的调度工具。
传统固定 batch 的思路,是等一批请求凑好后一起执行,跑完再接下一批。这在离线处理里很自然,但在线场景中会带来明显空档:有的请求已经完成,有的新请求却还在门口等下一轮开车。设备利用率和等待时间都会受到影响。
更麻烦的是,请求长度往往不同。有的 prompt 很短,有的输出很长。如果所有请求都被绑在固定批次里,短请求会被长请求拖着走,系统的“平均效率”就会掩盖很多局部浪费。
continuous batching 的核心思想,是让系统尽量持续吸收新请求进入运行窗口,而不是等整批完成再开下一批。这样做可以减少批次之间的空洞,提高在线服务中的设备利用率。
但收益的另一面是复杂度上升。系统需要持续判断哪些请求在 decode、哪些刚完成 prefill、哪些该结束回收缓存、哪些新请求可以插入。它本质上要求调度器随时面对“一批状态不同的活跃请求”。
如果 KV Cache 只能按大块连续空间管理,那么高并发、长上下文、频繁插入和释放请求时,很容易出现碎片化和分配压力。paged attention 用分页思路组织缓存,目的就是让缓存管理更灵活、浪费更小、请求共存更可控。
这意味着 paged attention 不只是“一个 attention 优化技巧”,更是调度和内存管理的基础设施。它让调度器在面对动态请求流时,有更现实的缓存组织手段。
只看吞吐不够。一个调度策略至少要同时接受四种审视:吞吐是否提高、延迟是否可接受、显存是否可控、请求之间是否公平。如果一个系统为了吞吐让短请求全部排长队,对某些在线产品来说就可能完全不可用。
后面你看框架对比时,应该带着这些问题去问:它更偏吞吐还是更偏时延?它对长短请求混跑友好吗?它的缓存组织能不能支撑动态调度?
真实产品里,请求往往不是完全平等的。交互式请求、内部高优工单、批量离线任务,容忍的等待时间都不同。调度器如果只按“尽量把 GPU 填满”设计,就可能把最需要快速响应的请求也拖进长队列。
这意味着调度策略最终要同时服务两种目标:一类是硬件利用率,一类是产品侧的服务等级要求。很多框架和平台能力的差异,也正体现在是否允许你把这种业务约束带进调度器。
如果用户在等结果,固定 batch 的很多简化假设就不再成立,你更需要关注等待时间和批次空洞。
当请求长度差异很大时,平均吞吐数字常常掩盖短请求被长请求拖慢的问题。
如果缓存分配和回收太僵硬,再好的调度策略也很难在动态请求流里稳定落地。
如果系统一味追求大 batch,短问答请求可能会被长报告任务拖住,用户会直接感觉“怎么问个小问题也这么慢”。
这时调度器就不能只按统一 FIFO 思路工作,而要把业务优先级带入排队和插队策略中。
把固定 batch 和 continuous batching 放到同一视图里,差异会比文字描述更直接。
左边是整批进入、整批退出;右边是动态插入、动态退出。两者真正的分水岭,在于状态和缓存能不能跟上。
更像一班一班发车,适合规则负载,但在线场景容易形成空档。
更像一条持续吸收请求的流水线,吞吐更高,但调度状态明显更复杂。
| 机制 | 主要目标 | 带来的收益 | 代价 / 难点 |
|---|---|---|---|
| 固定 batch | 把请求聚合成统一执行单元 | 实现简单,适合离线或规则负载 | 在线服务空档多、等待成本高 |
| continuous batching | 持续吸收新请求进入执行窗口 | 在线吞吐更高,利用率更好 | 调度状态更复杂 |
| paged attention | 更灵活地组织 KV Cache | 减少碎片化和内存浪费 | 需要更精细的缓存管理设计 |
吞吐优化的前提是状态管理做得住。没有稳定的请求状态和缓存组织,调度策略很难成立。
一个好的调度策略不只是“让 GPU 更忙”,还要回答“让谁先等、谁先走、代价是否可接受”。
至少比较吞吐、等待时间、实现复杂度、适用场景。最低完成标准是:你能说明为什么在线服务更偏好后者。
例如长上下文、长输出、高优先级请求。最低完成标准是:每条都要写出它为什么会增加调度难度。
最低完成标准是:答案里必须出现“缓存组织”或“内存管理”,而不是只说“更快”。
要同时提到首 token latency、吞吐、公平性三件事。最低完成标准是:不是只写“尽量快”。
一张两类 batching 方案对比表。
一份调度难点清单。
一段关于 paged attention 作用的工程化解释。
如果你能把等待时间和请求长度差异讲出来,说明理解到位。
关键不在 API,而在持续面对状态不同的活跃请求。
如果你能把答案说到“缓存更灵活,调度才更现实”,就已经抓住重点。
这是所有调度策略都绕不开的权衡。
直接对应本章主题,适合理解分页缓存为何是服务吞吐优化的重要基础。
适合把吞吐、时延和服务压测放回真实 benchmarking 语境。
适合整体理解动态请求、缓存管理和高吞吐 serving 是如何被统一设计的。
适合继续找调度器、请求状态和缓存布局相关实现。
适合比较另一条高性能 serving 路线在调度和能力暴露上的取舍。
后面结合框架实战时,再回来看 continuous batching、serving 和 benchmark 模块会更有效率。
在线服务里的 batch 是调度系统的一部分,不只是聚合操作。
它更关键的价值在缓存组织和内存管理,而这正是服务系统的核心痛点。
对在线产品来说,这会直接破坏真实用户体验。
现在你已经知道系统为什么要调度、怎样组 batch、缓存管理为什么重要。下一章会把这套系统问题往下继续压成“优化工具箱”:量化、算子、编译、内存和通信各自在解决什么瓶颈。