本站内容由 AI 生成,可能存在错误。如发现问题,欢迎到 GitHub Issues 反馈。

调度与抢占:推理引擎的 Scheduler

调度与抢占:推理引擎的 Scheduler

更新于 2026-04-05

Scheduler:推理引擎的大脑

上一篇 中我们解决了内存管理问题(PagedAttention)和批处理问题(Continuous Batching)。但还有一个关键问题:当 GPU 资源不够同时服务所有请求时,谁先谁后?谁该被暂停?

这就是 Scheduler 的职责。它是推理引擎的”大脑”,每一个 decode iteration 都做一次决策:哪些请求继续运行、哪些新请求可以加入、哪些正在运行的请求需要被抢占让路。

请求状态机

每个请求在 Scheduler 中有四种状态:

Scheduler 请求状态机点击下方按钮触发状态转换GPU slot 空闲生成 EOS显存不足swap 完成WaitingRunningSwappedFinishedGPU slot 空闲Running
  • Waiting:请求到达,排队等待 GPU slot
  • Running:正在 GPU 上执行 prefill 或 decode
  • Swapped:被抢占,KV Cache 搬到 CPU 或已丢弃,等待恢复
  • Finished:生成了 EOS token 或达到 max_length,释放所有资源

核心转换:Waiting → Running(调度)、Running → Swapped(抢占)、Swapped → Running(恢复)。每次 iteration 结束后 Scheduler 重新评估所有请求的状态。

调度策略

最基本的策略是 FCFS(先到先服务),但它不考虑请求的重要性。生产环境中通常需要更复杂的策略:

调度策略对比:同一批请求的执行顺序切换策略查看甘特图变化FCFS优先级短作业优先Slot 0Slot 10123456789R1R2VIPR3R4平均等待时间: 1.0 | 平均完成时间: 4.5 FCFS:先到先服务,简单但 VIP 请求可能等待过久

FCFS:简单公平,但 VIP 用户的请求可能被大量普通请求堵在后面。

优先级调度:为不同请求分配优先级(如 VIP 用户、付费用户、免费用户),高优先级请求优先获得 GPU slot。缺点是低优先级请求可能被”饿死”。

短作业优先 (SJF):预估请求长度,短请求优先执行,最小化平均完成时间。但长请求可能被不断推迟。

抢占机制

当显存不够时,Scheduler 必须从 Running 请求中选择一个或多个进行抢占 (preemption),释放其 KV Cache 占用的显存。有两种策略:

触发抢占:显存不足
场景:新高优先级请求到达,GPU 显存不足Running 请求 AKV Cache: 2GB (20 blocks)已生成 80% tokens新请求 B (VIP)需要 KV Cache: 3GBGPU 显存不足!Scheduler 必须抢占请求 A 来腾出显存 — 但 A 的 KV Cache 不能丢?两种策略:Swap(搬到 CPU)vs Recompute(丢弃重算)

如何选择? vLLM 的默认策略是:如果请求已经生成了很多 token(KV Cache 很大),优先用 Swap(搬运成本高但不浪费计算);如果请求刚开始(KV Cache 较小),优先用 Recompute(重算成本低且避免 PCIe 传输)。

Chunked Prefill

长 prompt 会带来另一个问题:prefill 阶段需要一次性处理数千个 token,这期间 GPU 被独占,正在 decode 的请求全部被阻塞。用户体验直接表现为”卡顿”——已经开始流式输出的对话突然停顿。

Chunked Prefill (Sarathi, 2023) 的解决方案是将长 prompt 切成固定大小的 chunk,每个 chunk 只占用一个 iteration,剩余 iteration 留给 decode 请求:

Chunked Prefill:长 Prompt 分块调度不分块:Prefill 独占 GPU新请求Long Prefill (8 iteration 独占)Decode A被阻塞 — TTFT 飙升!Decode B被阻塞 — 用户感知卡顿!分块:Prefill 与 Decode 交替执行新请求P1P2P3P4Decode ADDDDDecode BDDDDChunked Prefill 将长 prompt 切成小块,与 decode 交替执行 — Decode 请求不再被阻塞

Sarathi-Serve 进一步优化:在每个 iteration 中,将 prefill chunk 和 decode 请求混合执行 (piggybacking)——利用 prefill 是 compute-bound、decode 是 memory-bound 的互补特性,两者混合可以同时最大化 GPU 的计算单元和显存带宽利用率。

调度的 Trade-off

吞吐量、延迟和公平性——三者不可兼得。Scheduler 的配置本质上是在这个三角形中选择一个位置:

调度 Trade-off:吞吐 vs 延迟 vs 公平性选择优化目标,查看对应策略和效果吞吐优先延迟优先公平优先策略: 大 batch size + 延迟 preemption最大化 GPU 利用率,适合离线批处理任务吞吐量95延迟40公平性50

实际系统中,这些参数通常可以通过配置调整:

  • max_num_seqs:最大并发请求数(大 → 高吞吐,小 → 低延迟)
  • enable_chunked_prefill:是否启用分块 prefill(开 → 低延迟,关 → 高吞吐)
  • preemption_mode:抢占策略选择(swap / recompute)
  • scheduling_policy:调度算法选择

总结

Scheduler 是推理引擎中最”工程化”的部分——没有统一最优解,需要根据场景调参。但核心原则清晰:状态机管理请求生命周期,抢占机制处理资源不足,chunked prefill 消除长 prompt 阻塞,trade-off 三角引导配置选择

延伸阅读