摘要
推理服务可以每秒输出更多 token,同时让某位用户等得更久。原因在于请求长度不同、到达时间不同,且预填充与解码争用同一组资源。本文基于已有的迭代级调度思想,以原创四请求时间表解释连续批处理,再构造同时考虑 token 与缓存容量的调度草图。重点是建立可比较的吞吐和延迟口径。
01 / 两种阶段,共享一张设备
预填充处理一段输入,解码则反复为活跃请求生成后续 token。长文档预填充通常有较大的矩阵运算,单步解码往往更受权重及 KV Cache 读取影响;具体瓶颈还取决于模型、上下文和批量大小。
以研究助手为例,一位用户上传长报告时,另一位用户可能正在等待一句解释继续输出。如果长预填充独占一个大步骤,后者会看到生成间隔拉长;如果每轮都只照顾已开始生成的回答,新报告又会迟迟没有首个结果。调度面对的是两种等待,需要分别计量。
02 / 将调度边界缩小到迭代
请求级批处理在一批请求开始后,通常保持成员不变,直到这批任务结束。短请求先完成,其位置仍可能等待同批长请求释放。连续批处理在迭代边界重新选择成员:完成的退出,等待中的进入,下一轮运行新的组合。
Yu 等人在 Orca 论文中提出迭代级调度,并结合选择性批处理构建生成模型服务。本文沿用“每轮更新成员”的基本思想;实际执行还涉及注意力状态和算子组织,不能仅靠改变队列就获得相同系统效果。
03 / 四个请求的时间表
为了只观察成员替换,假设每轮固定耗时 1 个时间单位,最多并行处理两条请求,每条每轮生成 1 个 token。以下到达时间是已经完成预填充、可以加入解码的时刻。A、B 在 0 时刻到达,分别还需 4、2 个 token;C 在 1 时刻到达,还需 2 个;D 在 2 时刻到达,还需 1 个。等待请求按到达顺序进入。
| 时间区间 | 固定成员批次 | 连续批处理 |
|---|---|---|
| [0, 1) | A · B | A · B |
| [1, 2) | A · B | A · B |
| [2, 3) | A · 空位 | A · C |
| [3, 4) | A · 空位 | A · C |
| [4, 5) | C · D | D · 空位 |
| [5, 6) | C · 空位 | 已全部完成 |
固定成员方案中,B 在时刻 2 完成,但其空位到时刻 4 才能重新使用。连续方案在时刻 2 加入 C,因此 C 的完成时刻从 6 提前到 4;A、B、D 的完成时刻分别仍为 4、2、5。九个剩余 token 全部完成的时刻由 6 变为 5。
这个改善来自减少空位,没有让任意一次模型运算加速。D 仍需等待,说明局部效率提高不等于所有请求都更快。真实设备的一轮耗时会随批量、上下文和预填充份额变化,不能把此表换算成毫秒或声称固定的吞吐提升比例。
04 / 区分吞吐与等待
测量时先固定时间边界。本文从客户端发出请求到收到 token 计时;服务端排队和执行耗时另列。vLLM 的指标文档提供了请求、生成 token、缓存与延迟指标,可用于补充服务端观察。
| 指标 | 本文定义 | 说明 |
|---|---|---|
| TTFT | 首 token 到达时间 − 请求发出时间 | 包含排队、预填充及传输等待。 |
| TPOT | (末 token 时间 − 首 token 时间) / (输出 token 数 − 1) | 每请求的平均后续生成间隔;至少有两个输出 token。 |
| p95 延迟 | 指定延迟记录的第 95 百分位 | 须说明取 TTFT、TPOT 还是总耗时,不能混为同一指标。 |
| 输出吞吐 | 观测窗口内输出 token 总数 / 窗口秒数 | 与请求数每秒、输入加输出 token 每秒分别报告。 |
平均 TPOT 会平滑回答中间的一次长停顿。若产品强调流式连贯性,应额外记录逐 token 间隔的分布。不同长度请求也应分组比较,否则长输出占比变化可能抬高吞吐,却掩盖短问答的首字等待。
05 / 给解码和预填充分配预算
分块预填充将长输入拆成若干段,使一轮内能够混合解码与输入计算。vLLM 的调优文档说明了先安排解码、再用剩余 token 预算处理预填充的方式。较小预算通常减少单轮预填充对生成间隔的影响,但新请求可能需要更多轮才得到首个 token;更大预算有助于推进输入,也可能延长本轮。
def select_iteration(active, waiting, budget, chunk_cap, kv):
plan = []
for request in active:
if budget == 0:
break
if kv.reserve(request, 1):
plan.append((request, "decode", 1))
budget -= 1
for request in waiting:
count = min(request.prefill_left, chunk_cap, budget,
kv.available_token_slots(request))
if count == 0:
continue
kv.reserve(request, count)
plan.append((request, "prefill", count))
budget -= count
return plan
这里假定单个调度器顺序预留缓存,未启用推测解码。token 预算是工作量代理,并不保证每轮等时。应用可按等待时长调整候选顺序,并限制同时活跃的解码请求数量,为新输入留出进展空间。真正需要调的是“允许一轮做多少工作”,而不仅是“同时存在多少请求”。
06 / 用真实负载比较取舍
实验应固定模型、设备、推理精度、输出限制和请求数据,单独改变批量或 token 预算。记录输入长度、输出长度、前缀复用比例和到达过程;长短混合负载比全部等长请求更能暴露排队问题。预热后再统计稳定区间,冷启动时间另列。
容量曲线可使用预先给定的到达时间持续提交,逐步增加到达率;不要因为上一条变慢就自动减少发送量,否则会低估拥塞。固定并发压测适合模拟有限用户,也应注明请求完成后才补发的机制。两种测试回答的问题不同,需要分别展示。
每个配置同时报告 TTFT、TPOT 与总耗时的分位数、输出吞吐和积压请求数。最终选择应满足产品可接受的等待水平,再比较可服务负载。研究助手的长报告分析与即时交互可以采用不同预算,让调度策略对应真实的阅读和操作节奏。
参考文献
- Yu et al. — Orca: A Distributed Serving System for Transformer-Based Generative Models,OSDI 2022:迭代级调度与选择性批处理的原始研究。
- vLLM — Optimization and Tuning / Chunked Prefill:分块预填充与 token 预算的取舍。
- vLLM — Production Metrics:推理服务指标定义与观察入口。
← 返回研究论文