Skip to content

04 · Scheduler:连续批处理的决策中心

04 · Scheduler:连续批处理的决策中心

先建立直觉:短请求为什么还在等?

当一个短请求已经完成、却仍有新请求在等待时,GPU 的空位是怎样产生的?

先观看下面这段动画。它把 Scheduler 的核心决策拆成可观察的状态迁移:固定批处理的空等、每轮重新组装的连续批处理、token 预算的分配、Prefill 与 Decode 的不同节奏,以及 schedule() 附近应当阅读的代码线索。

观看时只抓住一个结论:Batch 不是一份固定名单。每个调度轮次结束后,完成的请求应立即离场;Scheduler 再依据阶段、队列与可用 token 预算重新选择工作。

Scheduler 每轮只回答一个问题

在资源有限的情况下,这一轮让哪些 Sequence 处理多少 token?

它同时受到三类约束:

  • max_num_seqs:一个批次最多容纳多少序列。
  • max_num_batched_tokens:本轮最多处理多少 token。
  • KV Cache block 是否足够。

Prefill 阶段

Scheduler 先查看 waiting 队列。对队首请求,它会询问 BlockManager 能否分配并返回可命中的缓存 block,计算仍需执行的 prompt token,检查本轮 token 预算,然后分配 block、设置 num_scheduled_tokens,最后把请求移到 running。

当一个超长 prompt 超过剩余预算时,nano-vLLM 允许第一个请求做 Chunked Prefill,即只处理一部分 prompt;但若本轮已经选了其他请求,就不会再塞入一个无法完整容纳的长请求,以保持逻辑简单。

Decode 阶段

没有可调度 Prefill 后,Scheduler 从 running 队列选择请求。Decode 中每个序列通常只需要处理最后一个 token,但可能需要新 block。如果空闲 block 不足,调度器会回退或抢占序列,释放其缓存,之后让它重新进入等待。

为什么要连续批处理

静态 batching 必须等待整批请求一起结束;连续批处理允许某个请求完成后立即腾出位置,引入新请求。这样 GPU 不必被最慢序列拖住。

公平性与吞吐的取舍

Prefill 优先能让新请求尽快进入系统,但超长 prompt 可能占用大量 token 预算;Decode 批次越大,总吞吐通常越高,但单个用户的每 token 延迟可能变差。生产系统会加入更复杂的优先级、预算和延迟目标,nano-vLLM 保留了最小但清晰的核心。

动手验证:预测 → 运行 → 解释

不要直接点击按钮。先在脑中扮演一次 Scheduler,再用实验验证自己的解释。

步骤你要做什么你应该观察什么
1. 预测加入长度为 8、24、60 token 的三个请求,先把预算设为 32。写下你认为本轮会完整处理谁、谁会被 Chunked Prefill。token 总预算而不是请求数量决定本轮能接纳多少 prompt。
2. 运行点击 执行 schedule();随后把预算改为 64,点击“装载 / 重置对比任务”恢复同一批请求,再运行一轮。Waiting 与 Running 的变化、本轮已用 token,以及日志中的完整 Prefill 或 chunk。
3. 解释比较两次运行:预算从 32 变为 64 后,哪一个请求改变了命运?为什么?用“队列、预算、阶段、状态迁移”四个词解释差异,而不是只报告结果。
HTML INTERACTIVE LAB04 · Scheduler:连续批处理的决策中心
单独打开 ↗
课后习题等待完成

为什么 `max_num_batched_tokens` 比“最大 batch size”更适合约束 Prefill?

动手任务

先完成上面的预算 32 与 64 对比,再用一句话解释:你改了什么,Scheduler 为什么这样选,下一轮会发生什么。

下一节:分页 KV Cache

社区教程,与 nano-vLLM 上游项目无官方隶属关系。