Phase 1 · Weeks 1–14 · Qwen2.5-7B on A100 80GB

推理引擎剖面

一次 LLM 推理,GPU 的时间到底花在哪里。五个动画,对应你 Wk 3–8 亲手测出的五组数字—— 每一项优化都在动同一个东西:prefill 是算力密集,decode 是带宽密集, 而这两者的矛盾是整个推理系统设计的起点。

01

两个阶段,两种瓶颈

Wk 3

一次请求分两段。Prefill 把整个 prompt 一次算完——几百个 token 并行进 GPU,算力吃满。 Decode 之后每次只出 1 个 token,但每一步都要把 14 GB 权重完整读一遍。 算力闲着,带宽跑满。整个 Phase 1 的优化,本质都在处理这个不对称。

待机
← 一次 forward pass = 一个格子 →
算力利用率 0%
显存带宽 0%
Prefill — 150 tokens 一次算完 Decode — 每步 1 token

你的实测

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)还快。小模型没把张量核心喂饱, 算力和带宽都在空转。

02

连续批处理买的是等待,不是吞吐

Wk 5–6

请求不会同时到达。静态批处理要凑够一批才开工——先到的请求干等。 连续批处理让请求随到随进、完事就走。注意看两边的总吞吐几乎一样,差的全在延迟。

t = 0.0s

静态批处理 — 等齐了再一起跑

连续批处理 — 到一个跑一个

排队等待 静态执行 连续执行
静态 P50
—
本演示
连续 P50
—
本演示

你的实测 · 32 请求,泊松到达

P50 延迟 6.36 s → 2.41 s,P99 8.18 s → 3.91 s。 而端到端吞吐 717 → 705 tok/s——基本没变。

结论要说准:连续批处理的价值不是提高 GPU 利用率,是消除本来就不必要的排队。 GPU 做的功一样多,只是不让人白等。

03

PagedAttention:把显存当虚拟内存用

Wk 4

每条请求的 KV cache 要多大?生成完之前不知道。传统做法按最大长度预留连续显存—— 实际用 40 个 token,却占着 512 个的位置。PagedAttention 改成按 16-token 的块按需分配, 物理上可以散落各处,靠块表映射。这就是操作系统分页。

4 条请求正在生成

连续预分配

每条请求预留 16 格(按最大可能长度)。用不到的格子锁死在那里,别人也用不了。

分页分配

从共享池按需取块。请求长一格就多拿一块,物理位置无所谓。

预留但浪费 请求 A 请求 B 请求 C 请求 D
连续 · 剩余可分配
—
空格子被预留锁死
分页 · 剩余可分配
—
没用到的格子还能收新请求

论文数字 · 你的实测

碎片率从 60–80% 降到 <4%。省下的显存直接变成更大的 batch。

你 Wk 4 的 batch sweep 就是这个的直接后果:batch 1 → 32,吞吐 80 → 2,408 tok/s 近乎线性, 而墙上延迟只从 1.25 s 动到 1.33 s。每多一条请求几乎不要钱——因为 decode 本来就在等带宽, 顺手多算几条是白赚。

04

前缀缓存:算过的不再算

Wk 7

32 条请求带着同一份 500-token 文档(RAG、system prompt、多轮历史都是这个形状)。 关掉缓存,这 500 个 token 的 prefill 要跑 32 遍。打开,第一条算完存进 KV cache, 后面 31 条按块哈希命中,直接跳过。

Prefix cache
共享前缀 prefill(500 tok) 缓存命中 — 跳过 各自的问题(~10 tok) Decode
TTFT P50
829 ms
每条都重算前缀
吞吐
835 tok/s
算力耗在重复 prefill

你的实测

TTFT P50 829 → 156 ms(5.3x),吞吐 835 → 1,412 tok/s(1.7x)。

吞吐也涨了 1.7x,因为省下的 prefill 算力全部转给了 decode——GPU 不再重复做同一份功。

命中是按块哈希的,前缀必须逐 token 相同。所以生产上要做前缀感知路由:同一个用户/会话 始终打同一台机器,否则 cache 永远 miss。

05

投机解码:把 decode 变回 prefill

Wk 8

既然 decode 时算力大量空闲,就先猜 5 个 token,让大模型用一次 forward pass 并行验证。 causal mask 保证每个位置的预测不受后面 token 影响,所以验证结果和逐个解码逐位相同——不是近似。 猜中就白赚,猜错就丢弃。

任务形态
已确定
draft 猜测
7B 验证结果
已提交 验证中 接受 拒绝 — 之后全部作废
本轮产出
—
tokens / 一次 forward
命中率
—
接受数 / 猜测数

你的实测 · 引擎不变,只改了 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。

06

一张表看完 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)