3、调度系统概述:调度器的作用、调度策略分类、与推理性能的关系

3.1 调度器在 vLLM 推理引擎中的核心作用

在 vLLM 推理引擎中,调度器(Scheduler)是连接前端请求入口与后端 GPU 执行单元之间的“中枢神经系统”。它的核心职责并非简单地“安排谁先跑”,而是要在每一轮迭代(iteration)中,决定哪些请求的哪些 token 可以被送入 GPU 执行,以及它们以何种方式组织成批次。这一决策过程直接决定了 GPU 的利用率、请求的端到端延迟以及系统整体的吞吐量。

调度器需要解决的根本矛盾是:请求的动态到达与 GPU 静态算力之间的不匹配。推理请求的到达时间、序列长度、所需显存量各不相同,而 GPU 的显存和计算单元是有限的。调度器通过维护一个全局的等待队列(Waiting Queue)和运行中的请求集合(Running Set),在每个调度步骤中执行以下关键操作:

  • 显存预算管理:为每个请求预分配 KV Cache 空间,并确保所有运行中请求的显存总和不超过 GPU 可用上限。当显存不足时,调度器必须决定是暂停某些请求(preempt)还是拒绝新请求。
  • 连续批处理(Continuous Batching)决策:在每次前向传播前,调度器决定哪些请求可以加入当前批次,哪些请求的生成已完成需要退出,从而实现细粒度的动态批处理。
  • 抢占与恢复策略:当新请求优先级更高或显存压力过大时,调度器需要选择牺牲哪些请求(交换到 CPU 或重新计算),以保证系统整体不崩溃。

核心理解:调度器不是简单的“任务队列”,而是一个显存感知的实时决策引擎。它每时每刻都在回答三个问题:“GPU 现在能装下谁?”、“谁应该先跑?”、“谁需要被暂停?”。这三个问题的答案,直接决定了推理引擎的性能上限。

请求入口 并发用户请求 (不同序列长度) vLLM 调度器 (Scheduler) 等待队列 Waiting Queue 运行中集合 Running Set 调度决策引擎 ① 显存预算检查 ② 连续批处理组批 FCFS 策略 先来先服务 SJF / 优先级 短任务优先 抢占与恢复 (Preemption & Recovery) 交换至 CPU / 重新计算 GPU 执行单元 批量前向传播 KV Cache 读写 新请求 组装批次 显存状态 / 完成信号反馈

3.2 调度策略分类与选择逻辑

vLLM 的调度策略并非单一算法,而是围绕“何时调度”与“调度谁”两个维度展开的策略组合。理解这些分类,是后续调整 --max-num-seqs、--max-num-batched-tokens 等参数的基础。

策略维度 具体策略 核心逻辑 适用场景
请求选择策略 FCFS(先来先服务) 按到达时间排序,先到先调度 延迟敏感、公平性要求高的场景
SJF(短作业优先) 优先调度输出长度短的请求 高吞吐优先,可牺牲部分长尾延迟
批处理策略 静态批处理 固定批次大小,等待批次填满或超时 离线批量推理
连续批处理 每步动态加入/退出请求 在线服务,混合负载
抢占策略 交换(Swap) 将 KV Cache 移至 CPU 内存 显存紧张但 CPU 内存充足
重计算(Recompute) 丢弃 KV Cache,后续重新生成 CPU 内存也不足时兜底

实践提示:vLLM 默认采用 连续批处理 + FCFS 基础策略,并允许通过 --priority 参数为不同请求设置优先级。在真实部署中,不要盲目追求“短作业优先”,因为输出长度往往无法预知,SJF 可能因估计错误导致长请求被无限饿死。

3.3 调度决策如何影响推理性能

调度器的每一个决策都会在 GPU 利用率、延迟和吞吐量三个维度上产生连锁反应。以下通过一个具体案例说明调度参数与性能的关系。

案例:显存碎片化与批处理效率

假设 GPU 可用显存为 16GB,KV Cache 池大小为 12GB。当前有 4 个请求正在运行,每个请求占用了 3GB KV Cache。此时新到达一个请求,其预估需要 2GB KV Cache。调度器面临以下选择:

  1. 直接拒绝:如果 --max-num-seqs 已设为 4,则新请求必须等待。
  2. 抢占一个旧请求:将某个已运行请求的 KV Cache 交换到 CPU,腾出空间给新请求。
  3. 等待当前批次中某个请求完成:如果某个请求即将生成结束,调度器可以短暂等待,避免抢占开销。

从性能角度看,方案 2 的抢占开销最大(涉及 CPU-GPU 数据传输),方案 3 的延迟最高(新请求等待时间不可控),方案 1 则可能造成 GPU 算力空闲。实际调度器会综合 --max-num-batched-tokens(单批次最大 token 数)和当前各请求的已生成步数来动态决策。

# 调度器关键参数示例(vLLM 0.6+)
--max-num-seqs 256          # 最大并发序列数
--max-num-batched-tokens 8192  # 单批次最大 token 总数
--gpu-memory-utilization 0.9    # GPU 显存利用率上限
--swap-space 4              # CPU 交换空间大小(GB)

常见误区:很多用户认为 --max-num-seqs 设置得越大越好,认为并发越高吞吐越大。实际上,当并发序列数超过 GPU 计算能力时,每个请求的生成速度会显著下降,导致 TTFT(首 token 延迟) 和 TPOT(每输出 token 延迟) 双双恶化。调度器的作用就是在“填满 GPU”和“保证单请求响应速度”之间寻找平衡点。

3.4 调度器性能调优的复盘与误区

在实际项目中,调度器相关的问题往往不是“引擎不工作”,而是“引擎工作方式不符合业务预期”。以下是三类高频问题的复盘:

误区一:忽略“批处理 token 上限”的约束

只调大 --max-num-seqs 而不调整 --max-num-batched-tokens,会导致调度器虽然允许更多请求进入运行集合,但每步实际参与计算的 token 数被上限卡死,大量请求处于“假运行”状态(已分配显存但未参与计算),造成显存浪费。

误区二:抢占策略选择不当

在延迟敏感型服务中,如果频繁触发抢占,用户会观察到明显的“卡顿”——表现为某次生成突然停顿数秒。复盘发现,根因是 --swap-space 配置过小,导致调度器频繁使用重计算而非交换。重计算虽然节省 CPU 内存,但代价是重新执行前向传播,耗时远高于交换。

误区三:忽视“长尾请求”的饿死问题

在混合负载场景(短查询 + 长生成)下,若未设置优先级或时间片限制,短请求会持续插队,长请求可能长时间得不到调度。vLLM 的调度器虽然支持优先级,但默认关闭。生产环境建议开启基于队列权重的调度,并设置最大等待时间。

复盘结论:调度器调优的核心不是“找到一个万能参数组合”,而是先明确业务目标——是追求 P99 延迟稳定,还是追求整体吞吐最大化?目标不同,调度策略的选择方向完全相反。建议每次调整参数后,同时监控 TTFT、TPOT、GPU 利用率、排队请求数 四个指标,才能准确判断调度器是否工作在合理状态。