长请求进来以后,短请求为什么也在等?
你已经让模型装进显存,单条请求也能正常生成。下一步把服务交给多人使用:A 的文字刚开始输出,L 带着很长的输入进入队列,S 随后发来一句短问题。监控上的 tokens/s 没有明显下降,A 却停顿了很久,S 的首字也迟迟没有出现。你现在要解释的是请求之间如何争用一次执行机会,而不是重新计算模型能不能装下。
prefill 处理输入,decode 利用已有状态继续生成。一个调度周期可以为新请求执行 prefill,也可以让已有请求生成下一步;设备计算、KV 容量、token 预算和等待策略共同限制这个决定。先后次序会改变每个用户看到的时间线。总输出数相同,等待分布仍可以相差很大。
先记住观察对象:A 的首 token 已经到达,后来的长 prefill 会影响 A 的后续 ITL,不能倒改它已经发生的 TTFT。S 尚未得到首字,入队到首 token 的等待属于 S 的 TTFT。把两者混成“生成变慢”,就很难判断该验证哪种机制。
三种机制,各解决一个不同问题
连续批处理在请求完成后允许其他请求加入后续批次,不必等整批最长的请求结束。它减少空出来的执行位置,但不自动保证短请求优先,也不承诺所有用户的延迟都会下降。一个位置空出来后,仍需要决定接纳哪个请求,以及剩余 KV 是否足够。
分块 prefill把长输入拆成多次可调度的计算,为 decode 留出间隔。chunk 太大,已有请求可能等待更久;chunk 太小,长输入完成需要更多周期,额外提交和调度开销也可能增多。改变 token budget 是改变时间分配,不能只盯一个吞吐数字。
分页 KV改变缓存的分配和寻址,减少大块连续预留造成的浪费,并可支持符合条件的共享。它不是把 prefill 计算凭空省掉,也不是直接把 FIFO 队列变成短作业优先。第 06 课的张量容量账仍要保留;实际块大小、共享前提、抢占与重算也应记录。
先用一条可手算的时间线比较
离线实验有一个刻意简单的虚拟设备:一个 prefill token 花 1ms,一次 decode batch 花 1ms,每次提交额外 0.25ms;prefill 完成时发出首 token。decode batch 的时间在此模型中不随活跃请求数变化。没有真实 GPU、KV 容量、抢占或通信。这里的数字用来隔离调度次序,不能换成某个引擎的性能承诺。
| 请求 | 到达 | 输入 tokens | 输出 tokens |
|---|---|---|---|
| A | 0ms | 2 | 12 |
| L | 2ms | 12 | 2 |
| S | 4ms | 1 | 4 |
先看“完整 prefill 优先”,命令中的名字是 fcfs:等待 prefill 按到达先后处理,有等待项就先完整执行;之后再 decode 活跃请求。这里 FIFO 描述 prefill 队列次序,并不是把每条请求从头到尾串行执行。
首 token 和下一次输出是两个时间点
- A 从 0 到 2.25ms 完成 prefill,首 token 到达,因此 A 的 TTFT=2.25ms。
- L 已在 2ms 到达,从 2.25 到 14.50ms 执行完整 prefill。L 的 TTFT=14.50−2=12.50ms。
- S 从 14.50 到 15.75ms 执行,TTFT=15.75−4=11.75ms。
- 第一次 decode batch 到 17ms 结束,A 得到第二个 token。A 的第一个 ITL=17−2.25=14.75ms。
现在使用 chunk=2:先为活跃 decode 提交一个 batch,再执行队首 prefill 的最多 2 tokens。A 的最长间隔变为 1.25+2.25=3.50ms;它的 TTFT 仍为 2.25ms。L 和 S 仍按 FIFO 等待,而 L 被拆成更多轮次,因此它们的首字更晚。不能从 A 的改善推断所有请求都改善。
把预测和真实模拟输出放在一起
下载完整离线实验 ZIP,解压进入 infra-lab。先写下你预测改善的指标,再运行;无需 GPU 或 pip。实验说明包含规则、故障开关与验收,原始调度输入可以逐条查看。
python3 simulate.py --trace mixed --policy both --chunk 2 --output runs/mixed.json
python3 simulate.py --trace prefill-only --policy both --chunk 2 --output runs/prefill-only.json
| mixed 策略 | A 最大 ITL | L TTFT | S TTFT | 全批结束 |
|---|---|---|---|---|
| fcfs | 14.75ms | 12.50ms | 11.75ms | 29.50ms |
| chunk=2 | 3.50ms | 21.25ms | 21.75ms | 30.75ms |
反例也要保留。prefill-only 只有一个输入 12、输出 1 的请求:完整策略为 12+0.25=12.25ms;chunk=2 提交六次,为 12+6×0.25=13.50ms。没有已有 decode 需要保护,切块在这个模型中只增加提交成本。将 --overhead-ms 0 加到该命令后,两种策略都是 12ms。这给出了可复测的解释,而不是一句“分块总是更慢”。
打开输出 JSON,找到 A 的 tokenTimesMs 与执行 events。用相邻时刻相减重算最大 ITL,再检查 S 的 arrival、firstToken 和 TTFT。若打印表与原始时序对不上,先修日志和统计,不要急着调参数。
从模拟迁移到真实引擎,还缺哪些证据?
真实 GPU 上 prefill 与 decode 的时间受 batch、shape、精度和 kernel 影响,并不按本实验的固定成本运行。引擎还可能做前缀缓存、抢占与重算;容量紧张时,请求即便有执行优先级,也未必能被接纳。分页、分块和优先级共同变化时,必须分开记录配置和缓存状态,不能用本模拟的排名直接选生产参数。
| 现象 | 需要的证据 | 一项受控改动 |
|---|---|---|
| 首字很晚 | 原到达、入队、开始 prefill、首输出 | 固定长度,仅改 token budget |
| 输出中途停顿 | 后续输出事件、抢占、长 prefill | 固定负载,仅开启或关闭分块 |
| 并发升高后尾延迟突增 | KV 块水位、接纳量、重算计数 | 保留每次失败,仅降低并发上限 |
若流式事件一次携带多个 token,事件间隔与真正每 token 的间隔不等价。报告应写清引擎和客户端的定义,并同时记录输出 token 数与结束原因。本课一事件一 token 的假设不能默默沿用到推测解码场景。
交付一份含反例的调度对照
- 重算 mixed。提交三条请求的到达、首字、结束和 A 最大 ITL;用事件解释 A 为什么改善、S 为什么变差。
- 检验反例。对 prefill-only 分别设 overhead=0.25 与 0。保留预测、输出和六次提交的计数,区分调度次序与提交开销。
- 陌生负载。先预测
unseen中 U2 的首字和 U1 的最长停顿,再用python3 simulate.py --trace unseen --policy both --chunk 4 --output runs/unseen.json验证。写出一个改变之后需要重新排名的条件。
参考与完成标准
mixed 的关键数值见上表;A 第一字已经发生,因此两策略 A TTFT 相同。单请求零提交开销均为 12ms,非零时 12.25 对 13.50ms。陌生负载没有沿用 mixed 的答案:应从新 arrival/token 序列重新计算,保留原始 JSON。长度比例、到达过程、KV 容量、优先级或提交成本改变,都构成复测触发条件。
完成标准:定义完整;至少一项逐 token 复算;至少一个失败反例;能说出模拟未覆盖的两个真实机制;结论限定到当前 trace。只写“chunk 更好”未通过。
带着时间线读原始资料
vLLM · Optimization and Tuning:找到 chunked prefill、decode 优先和 token budget。读前预测缩小预算会牺牲什么;读后用本课的 A/L/S 指出真实实现还需核对的版本和容量条件。PagedAttention 论文:重点读缓存碎片与共享的机制,用一段话解释为什么它与调度次序不是同一个问题。核对日期 2026-10-04;参数以安装版本为准。
下一步:如何把这些时序变成一个可接受的运行点?
你会解释某条请求为何等待,但不能仅凭一个 trace 推断服务值得上线。第 08 课会固定实验协议,保留全部失败,再同时验收质量和时延;最后在可行档位里比较合格请求的成本。
留下本课的产出
本课产出:调度对照、失败反例与原始 trace。写下条件、证据、结果和仍不确定的事。保存不代表自动通过,下面的状态由你按验收条件自检。
记录只保存在当前浏览器。