跳转到主要内容

批处理与调度:推理服务的灵魂

从请求队列、token budget 与连续批处理理解 LLM Serving 调度器的核心合同

· 约 6 分钟阅读

LLM Serving 的调度单位不是一组等长样本,而是一批不断变化的请求:有的刚进入 Prefill,有的正在 Decode,有的已经结束。调度器要在显存、token budget 与 SLO 之间持续重组执行批次。

这篇只建立调度基础。Chunked Prefill 的算法、GDN 内部 kernel chunk 和真实 trace 诊断由后续专题页负责。

30 秒复习
  • 一句话:LLM 调度器在每个迭代里选择哪些请求、处理多少 token,并确保 KV Cache 与服务目标允许这次执行。
  • 三个判断:离线同形任务可用静态 batch;在线变长请求需要 continuous batching;遇到长 Prefill 阻塞 Decode 时,再评估 chunking、优先级或 P/D 分离。
  • 核心模型inischeduledBtoken\sum_i n_i^{\text{scheduled}}\le B_{\text{token}},同时满足 KV 容量、并发上限与优先级合同。
  • 边界:batch 变大不保证 SLO 更好;真实收益取决于输入/输出分布、kernel 形状、KV 压力、排队与抢占代价。

1. 调度器到底在决定什么

一次调度至少包含四个决定:

  1. 从等待队列接纳哪些请求;
  2. 给每个请求分配 Prefill 或 Decode token;
  3. 确认 KV block、执行槽位和并行资源足够;
  4. 在请求结束、暂停或被抢占后回收资源。

可以把单次迭代的基本约束写成:

inischeduledBtoken\sum_i n_i^{\text{scheduled}} \le B_{\text{token}}

其中 BtokenB_{\text{token}} 是本轮 token budget。生产调度器还会叠加:

MKV,used+MKV,newMKV,budgetM_{\text{KV,used}} + M_{\text{KV,new}} \le M_{\text{KV,budget}}

以及最大序列数、最大上下文、优先级、租户配额和超时等约束。不同框架的参数名不同,但合同相近。

2. 三种 batching 形态

2.1 Static Batching

先收齐固定 batch,再一起执行到全部完成。

  • 优点:实现简单、形状稳定,适合离线同形任务;
  • 问题:短请求完成后仍要等待长请求,在线利用率和尾延迟通常较差。

2.2 Dynamic Batching

在一个短等待窗口内聚合请求,再组成 batch。

  • 优点:比静态 batch 更适合在线流量;
  • 问题:组批等待直接进入 TTFT,且批次内部仍可能受最长请求拖累。

2.3 Continuous Batching

每次迭代后移除已完成请求,并让等待请求补位。批次从“固定请求集合”变成“动态活跃序列集合”。

  • 权重读取可被更多活跃请求复用;
  • 请求完成即可释放 KV 和执行槽位;
  • 调度器必须处理变长 KV、不同阶段与资源抢占。

3. 为什么 KV 管理是前置条件

连续批处理要求每条序列的 KV Cache 可以独立增长、映射和回收。若每个请求都需要预留一块连续的最大长度显存,动态加入与退出会导致浪费和碎片。

Paged KV 的价值在这里体现:调度器按 block 管理逻辑序列,执行 kernel 根据 block table 找到物理 KV。它不等于“缓存一定命中”,也不自动解决 prefix reuse。

更完整的 KV 分页、容量与复用语义见 KV Cache

4. 连续批处理 (Continuous Batching / Iteration-Level Scheduling)

一个实现无关的循环可以写成:

while running:
    finished = collect_finished_requests()
    release(finished)

    candidates = waiting + active
    plan = schedule(
        candidates,
        token_budget,
        kv_budget,
        priority_policy,
    )

    outputs = execute(plan)
    update_request_state(outputs)

关键不是循环本身,而是 schedule(...) 的策略:

策略问题偏吞吐选择偏时延选择
等待多久组批更长窗口更短窗口或立即执行
Decode 与 Prefill 冲突允许更大 Prefill保护 Decode cadence
KV 接近上限提高占用提前 admission / reject
高优先级请求到达等待当前批次抢占或预留资源

因此 Continuous Batching 是一种调度机制,不是固定性能承诺。对低并发或极短请求,调度和组批开销可能比复用收益更显著。

5. Prefill 与 Decode 如何共享预算

在线批次常同时包含两类工作:

  • Prefill 一次可处理多个输入 token,容易形成较大计算块;
  • Decode 通常每条活跃序列推进少量 token,对稳定 cadence 更敏感。

若长 Prefill 一次占满整个 budget,已有 Decode 请求可能产生 TPOT/ITL 尾部抖动。常见处理路径是:

  1. 给 Decode 保留预算或提高优先级;
  2. 将长 Prefill 切成多个 scheduler chunk;
  3. 限制单请求每轮可消耗的 token;
  4. 当资源与 workload 支持时,将 Prefill / Decode 放到独立池。

Chunk 大小、策略和实验合同见 Chunked Prefill 深入分析。不要把这里的 scheduler chunk 与模型内部 kernel chunk 混为一谈。

6. Admission、优先级与抢占

Admission Control

队列长度不等于可安全接纳的请求数。调度器应基于输入长度、预期输出、KV block、水位和 SLO 决定:

  • 接纳;
  • 延迟接纳;
  • 路由到其他实例;
  • 明确拒绝或降级。

优先级

优先级可以来自租户等级、deadline、请求年龄或阶段。需要同时防止:

  • 高优先级流量让普通请求长期饥饿;
  • 长请求一直被短请求绕过;
  • Decode 保护过强导致 Prefill 队列无法排空。

抢占

抢占不是免费操作。实现可能选择重计算、换出状态、释放再恢复,代价取决于 KV 大小、存储层级和框架能力。比较策略时,应把恢复成本和尾延迟一起测量。

7. P/D 分离何时进入讨论

P/D 分离把两阶段放进独立实例或资源池,并通过 KV transfer 衔接。它扩大了独立调优空间,也新增了队列、传输、路由和故障面。

只有在聚合式调度已经暴露稳定冲突、且 KV 传输成本可接受时,它才是合理候选。框架实现与选型合同见 Serving Stack 与框架选型

8. 最小验收矩阵

不要用单个平均吞吐量证明调度优化。至少固定并记录:

model, precision, hardware, engine/version,
input/output-length distribution, concurrency/arrival process,
token budget, max sequences, KV budget,
TTFT P50/P95/P99, TPOT P50/P95/P99,
throughput, SLO-goodput, preemption/reject count

对照实验一次只改一个策略参数,并观察:

  • 吞吐是否上升;
  • TTFT 与 TPOT 尾部是否恶化;
  • KV 水位和抢占是否变化;
  • 结论是否只在某个请求分布下成立。

相关页面

参考资料