两个阶段,两种瓶颈
Wk 3一次请求分两段。Prefill 把整个 prompt 一次算完——几百个 token 并行进 GPU,算力吃满。 Decode 之后每次只出 1 个 token,但每一步都要把 14 GB 权重完整读一遍。 算力闲着,带宽跑满。整个 Phase 1 的优化,本质都在处理这个不对称。
你的实测
vLLM 单请求 80.6 tok/s,即 TPOT 12.4 ms。每 12.4 ms 就要把 14 GB 权重搬一趟—— A100 的 HBM 带宽约 2 TB/s,光搬运就要 7 ms。超过一半的时间在等内存,不在算。
这也解释了 Wk 2 那个反直觉的结果:7B(40 tok/s)比 0.5B(31 tok/s)还快。小模型没把张量核心喂饱, 算力和带宽都在空转。
连续批处理买的是等待,不是吞吐
Wk 5–6请求不会同时到达。静态批处理要凑够一批才开工——先到的请求干等。 连续批处理让请求随到随进、完事就走。注意看两边的总吞吐几乎一样,差的全在延迟。
静态批处理 — 等齐了再一起跑
连续批处理 — 到一个跑一个
你的实测 · 32 请求,泊松到达
P50 延迟 6.36 s → 2.41 s,P99 8.18 s → 3.91 s。 而端到端吞吐 717 → 705 tok/s——基本没变。
结论要说准:连续批处理的价值不是提高 GPU 利用率,是消除本来就不必要的排队。 GPU 做的功一样多,只是不让人白等。
PagedAttention:把显存当虚拟内存用
Wk 4每条请求的 KV cache 要多大?生成完之前不知道。传统做法按最大长度预留连续显存—— 实际用 40 个 token,却占着 512 个的位置。PagedAttention 改成按 16-token 的块按需分配, 物理上可以散落各处,靠块表映射。这就是操作系统分页。
连续预分配
每条请求预留 16 格(按最大可能长度)。用不到的格子锁死在那里,别人也用不了。
分页分配
从共享池按需取块。请求长一格就多拿一块,物理位置无所谓。
论文数字 · 你的实测
碎片率从 60–80% 降到 <4%。省下的显存直接变成更大的 batch。
你 Wk 4 的 batch sweep 就是这个的直接后果:batch 1 → 32,吞吐 80 → 2,408 tok/s 近乎线性, 而墙上延迟只从 1.25 s 动到 1.33 s。每多一条请求几乎不要钱——因为 decode 本来就在等带宽, 顺手多算几条是白赚。
前缀缓存:算过的不再算
Wk 732 条请求带着同一份 500-token 文档(RAG、system prompt、多轮历史都是这个形状)。 关掉缓存,这 500 个 token 的 prefill 要跑 32 遍。打开,第一条算完存进 KV cache, 后面 31 条按块哈希命中,直接跳过。
你的实测
TTFT P50 829 → 156 ms(5.3x),吞吐 835 → 1,412 tok/s(1.7x)。
吞吐也涨了 1.7x,因为省下的 prefill 算力全部转给了 decode——GPU 不再重复做同一份功。
命中是按块哈希的,前缀必须逐 token 相同。所以生产上要做前缀感知路由:同一个用户/会话 始终打同一台机器,否则 cache 永远 miss。
投机解码:把 decode 变回 prefill
Wk 8既然 decode 时算力大量空闲,就先猜 5 个 token,让大模型用一次 forward pass 并行验证。 causal mask 保证每个位置的预测不受后面 token 影响,所以验证结果和逐个解码逐位相同——不是近似。 猜中就白赚,猜错就丢弃。
你的实测 · 引擎不变,只改了 prompt 里一句指令
batch = 1 | 逐字复制 2.78x | 自由改写 1.13x
batch = 16 | 逐字复制 2.38x | 自由改写 0.85x(净亏)
baseline 在两种任务上都是 80.7 tok/s(三位有效数字完全相同), 所以提速全部来自被接受的猜测,跟任务难度无关。
batch size 是放大器,不是决定因素。高并发下 GPU 已经 compute-bound, 多验证的 6 倍算力不再免费——命中率低就净亏 15%,命中率高照样赚 2.38x。
一张表看完 Phase 1
每一项优化,都可以问同一个问题:它在动 prefill 还是 decode?省的是算力、带宽,还是显存?
| 优化 | 动的阶段 | 省下什么 | 你的实测 |
|---|---|---|---|
| 连续批处理 | decode | 排队等待(不是算力) | P50 6.36 → 2.41 s |
| PagedAttention | decode | 显存碎片 → 换更大 batch | batch 32 → 2,408 tok/s |
| 前缀缓存 | prefill | 重复的前缀计算 | TTFT 829 → 156 ms |
| 投机解码 | decode | 把空闲算力换成 token | 2.78x / 0.85x(看命中率) |
| 张量并行 Wk 9 | prefill decode | 每卡权重减半 → 带宽压力减半,代价是 56 次 all-reduce | TP=2 1.55x / TP=4 2.16x |
| MoE / 专家并行 Wk 10 | decode | 每 token 只读激活的权重,但 GEMM 被打散 | batch=1 4.19x / batch=32 仅 1.23x |
| 量化 Wk 11–12 | decode | 权重体积 → 砍带宽开销,前提是有高效 kernel 承接 | FP8 带宽区 1.5x/算力区 0.84x 净亏;INT8 几乎零加速 (1.0x) |