批处理与调度:推理服务的灵魂
从请求队列、token budget 与连续批处理理解 LLM Serving 调度器的核心合同
LLM Serving 的调度单位不是一组等长样本,而是一批不断变化的请求:有的刚进入 Prefill,有的正在 Decode,有的已经结束。调度器要在显存、token budget 与 SLO 之间持续重组执行批次。
这篇只建立调度基础。Chunked Prefill 的算法、GDN 内部 kernel chunk 和真实 trace 诊断由后续专题页负责。
- 一句话:LLM 调度器在每个迭代里选择哪些请求、处理多少 token,并确保 KV Cache 与服务目标允许这次执行。
- 三个判断:离线同形任务可用静态 batch;在线变长请求需要 continuous batching;遇到长 Prefill 阻塞 Decode 时,再评估 chunking、优先级或 P/D 分离。
- 核心模型:,同时满足 KV 容量、并发上限与优先级合同。
- 边界:batch 变大不保证 SLO 更好;真实收益取决于输入/输出分布、kernel 形状、KV 压力、排队与抢占代价。
1. 调度器到底在决定什么
一次调度至少包含四个决定:
- 从等待队列接纳哪些请求;
- 给每个请求分配 Prefill 或 Decode token;
- 确认 KV block、执行槽位和并行资源足够;
- 在请求结束、暂停或被抢占后回收资源。
可以把单次迭代的基本约束写成:
其中 是本轮 token 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 尾部抖动。常见处理路径是:
- 给 Decode 保留预算或提高优先级;
- 将长 Prefill 切成多个 scheduler chunk;
- 限制单请求每轮可消耗的 token;
- 当资源与 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 水位和抢占是否变化;
- 结论是否只在某个请求分布下成立。
相关页面
- vLLM Async Scheduling 与投机解码
- Chunked Prefill 深入分析
- GDN 与 Chunked Prefill
- Prefill Trace 诊断
- KV Cache
- Compute-bound vs Memory-bound
- Serving Stack 与框架选型
参考资料
← 被以下页面引用(11)
- 推理框架对比 2026:从 Engine 到 Serving Stackai-systems · synthesis
- Chunked Prefill 深入分析:调度、Chunk Size 与 Attention 形状ai-systems · synthesis
- LLM 推理系统全栈地图ai-systems · synthesis
- vLLM Async Scheduling:三态配置、投机解码与状态提交ai-systems · synthesis
- 推理 Kernel / Runtime 优化:少搬、少启、少等ai-systems · concept
修改历史7 次提交
- docs: explain async speculative schedulingxiaocheng··
55e093e - docs: refine LLM inference knowledge systemxiaocheng··
f8756b9 - feat(wiki): strengthen discovery and content lifecyclexiaocheng··
6e0862f - docs(wiki): publish July inference researchxiaocheng··
0a9b76b - fix(wiki): clean all lint errors to enable strict CI (PR-3)xiaocheng··
9acd1f2 - docs(ai-systems): add comprehensive LLM inference documentationxiaocheng··
dfe9ab1