AI Infra · 第 8 章图解

推理优化

同一张卡上,单个用户每秒只收到几十个 token,所有用户合计却能上千:推理优化就是重新安排请求,让一次权重读取服务更多 token,同一份 KV 少存几份,一次目标模型计算确定更多输出。

贯穿全章的设计题Qwen3-8B 跑在一张 RTX PRO 6000(96 GB、1792 GB/s、BF16 503.8 TFLOP/s)上,本实例分到 32 GiB,其中 KV 可用 12 GiB。
16 条长请求同时到达每条输入 8192、输出 256 个 token,前 6144 个是相同的系统提示与工具定义,7 秒内必须返回。
只有一种组合过关独立保存放不下(A),共享前缀放得下但超时(B),压缩后更慢(C),共享 + 推测解码 4.56 秒(D)。
prefill · 输入处理 decode · 逐 token 生成 KV / 请求状态 权重 / 共享部分 排队 / 空闲 超限 / 浪费
页面里的图都可以操作,带 ▶ 的按时间轴播放。计算器默认值都取书中数字;为演示而补设的值会标“示意”。
8.1

推理请求的执行过程与资源需求

请求有独立的输入、输出和结束条件;batch 是同一次加速器执行里一起计算的一组 token;调度迭代是“选本轮工作、提交执行、处理结果”的一个周期。请求跨越许多迭代,每轮和它合批的请求都可能不同。

图 8-1 · 请求保持身份,batch 每轮重组

执行位置 1
执行位置 2
迭代 0
请求 A
请求 B
迭代 1
请求 A
请求 C

B 在迭代 0 结束,空出的执行位置(每轮为一条活跃请求预留的一行计算)交给 C;各请求的状态仍按身份保存。

设计条件

短对话长上下文
输入长度20488192
输出长度256256
公共前缀06144
完成时限7 s7 s

请求先进等待队列;调度器把它放进 batch 并分配 KV 空间后,模型才开始 prefill:输入已全部给定,可以同时计算多个 token 并保存各层的键和值,算完最后一个输入 token 就得到第一个输出 token。随后进入 decode:把刚生成的 token 作为下一次前向的输入,读取上下文 KV,再生成下一个。同一请求必须逐步走,不同请求的当前步骤可以一起算。

每个 token 的计算内容不变,只调整执行顺序,就可能让请求超时。一条 2K 短请求单独跑约 6.95 秒,离 7 秒时限只剩 0.05 秒;中途插进一次别人的 prefill,就变成 7.05 秒。

图 8-2 · 一条请求的时间线

TTFT = t₁ − tₐ  ITLⱼ = tⱼ₊₁ − tⱼ  Trequest = TTFT + Σj=1..G−1 ITLⱼ = 0.1 + 0.096 + 255 × 0.0265 = 6.95 s

实验 8-1 的 batch 1 实测:prefill 并返回首个 token 约 0.096 s,此后平均每 26.5 ms 一个 token。首 token 把时间线切成两段:前一段决定用户何时看到响应,后一段(255 个间隔)决定何时拿到完整答案。后面所有调度优化,都是在改其中某一段的长度。

权重、KV 与运行时缓冲:先算放不放得下

Mw + MKV + Ma + Mu ≤ C  k = 2(K,V) × 36 层 × 8 KV 头 × 128 维 × 2 bytes = 147,456 B = 144 KiB / token

12 GiB KV 能同时接纳多少条请求

每块 16 token · 接收时按生成完毕的长度预留

prefill 产出第 1 个输出 token,之后把前 255 个输出依次送回模型,所以短请求共计算 2048 + 255 = 2303 个 token,按 16 对齐要 2304 个 token 的空间(324 MiB)。只按 prefill 后的 288 MiB 算能接 42 条,但生成完要 13,608 MiB,执行到一半就会内存不足。权重 15.3 GiB 由所有请求共用;每多一条独立的长请求,就多 1.125 GiB KV,16 条只存输入就要 18 GiB。

TTFT

到达到首个 token。例子里 0.1 排队 + 0.096 prefill = 0.196 s,决定用户多早看到响应。

ITL

相邻两个输出之间的间隔。255 个间隔合计 6.76 s,均匀的间隔便于连续阅读。

吞吐

16 条 2K 请求一起跑,一轮 decode 27.44 ms 输出 16 个:16 / 0.02744 ≈ 583 token/s;单条每 26.26 ms 一个,只有约 38。

衡量服务还要看答案对不对:固定输出长度便于在同样工作量下比时间;让模型自然停止才能看正确率、回答长度和重试次数。第 8.6 节会把“正确且按期完成”合成一个指标。

8.2

批处理与请求调度

Y = XW 里,同一个权重元素参与 X 的每一行。把 decode 的请求数从 1 增到 16,输入矩阵从 1 行变 16 行,同样一次权重读取对应 16 倍的乘加:只计权重读取时,算术强度 Iweight ≈ 2b / sw。但注意力仍要分别读每条请求自己的 KV。

合批只能摊薄权重,摊不薄 KV。batch 越大,每个输出 token 的读取量越接近它自己那份 KV;上下文越长,这个“地板”越早露出来。

图 8-3 · 每生成一个 token 要读多少字节

Dw = 15,136,811,008 B · k = 144 KiB

d(b, L) = Dw / b + L·k  bKV = ⌈Dw / (L·k)⌉

两轴都是对数刻度,相邻刻度 batch 加倍,权重项每次减半。2K 上下文从 batch 1 到 64,每 token 读取从约 14.38 降到 0.50 GiB(约 1/29)。同时驻留的数据却变多:64 条 2K 请求的权重加一步 decode 后的 KV 合计约 35.7 GB。练习 8-1:bKV(2K 为 51、8K 为 13)与 8.1 节的可接纳上限(37、10)比,容量会先挡住 batch。

图 8-4 · 权重改由独立快速 ROM 提供

第 4 章 8K 示例

传统路径每输出 token 分摊的 HBM 读取为 W/B + K;权重从独立 ROM 来,HBM 只读 K。两线距离随 batch 缩小:靠 batch 摊薄权重的收益在消失,batch 和推测解码方案都要重新比较。

执行时间:算力与带宽谁先到

Tstep ≈ max( bF/P , (Dw + bLk)/β )

每多一条请求,计算加 F/P,KV 读取加 Lk/β;前者更大时计算才会追上读取。按峰值算的是物理下界,下界 ÷ 实测就是 MFU / MBU(8.6.3 节校准)。

batch 扫描实测(2K 输入、输出 256)

batch 1batch 64
总吞吐≈37 token/s1165 token/s
首 token≈96 ms3.29 s
平均输出间隔≈27 ms40 ms

每批同时提交;在线服务若还要等 batch 集满,这段也算进首 token 延迟。

多 LoRA:共用基础权重,分组算低秩部分

Qwen3-8B 的 Q、V 投影取秩 16、BF16,一套 adapter 14.6 MiB,100 套 1.43 GiB,远小于 14.1 GiB 矩阵权重。但一批请求来自不同 adapter 时,低秩计算按 adapter 分组,分得越散每组行数越少,复用越差:放几种 adapter 和放多少条请求要分开决定。容量也分开算:100 条互不共享的 8K 上下文 KV 要 112.5 GiB,adapter 宽裕不代表 KV 宽裕。

从固定 batch 到连续批处理

固定批处理整组接收、整组结束,短请求空出的位置一直闲着;连续批处理在迭代边界重排活跃请求,空出位置就接纳新请求。但新请求的 prefill 也更早占用加速器,长提示会拉长已有请求两次 decode 之间的等待。

连续批处理缩短总时间,却可能把一次输出停顿放大十几倍;分块 prefill 把长输入摊进多个输出间隔。

图 8-5 / 8-6 / 8-7 · 四条请求,三种调度

最多同时运行 2 条。r0、r1 在 0 时刻到达,各输入 2048、输出 8 和 2 个 token;r2 在 20 ms 到达,输入 8192、输出 2;r3 在 30 ms 到达,输入 2048、输出 4。每轮耗时按实验 8-1 拟合:固定 26.22 ms + 每个新 token 30.4 μs + 每对因果查询-键 3 ns(复现 2K 单独 decode 26.26 ms、prefill 94.8 ms)。固定部分包括读整份权重(按峰值带宽约 8.4 ms)和逐个提交 kernel。蓝色 prefill,绿色 decode,三角为到达,色带宽度为整轮迭代耗时。

同样大小的块,为什么越来越慢

图 8-8 / 8-9 · 注意力配对数 = 矩形 + 三角形

Npair = c·h + c(c+1)/2

蓝格 = 新 token 访问旧上下文(c·h),绿格 = 块内因果注意力(三角形),空格是未来 token。块长固定时三角不变,矩形随上下文变宽。投影和 FFN 每块都处理同样多的新 token:逐块记录里末块主干矩阵运算量比首块多约 32%,执行时间中位数从约 25.3 增至 35.1 ms(+39%)。

块太小也有代价

把 256 token 的块拆成两个 128,多一次调度机会,但矩阵行数减半、每块都要用同一份权重。以一层 FFN 的 288 MiB 权重为例,两次分别从显存读取,读取量就从 288 → 576 MiB。应选能满足输出间隔要求的较大块。

token 上限:队列 vs 停顿

六条请求每隔 80 ms 到达的回放:每轮 token 上限从 512 提到 8192,后到请求在引擎里的排队从约 149 ms 降到不足 0.1 ms,最长输出间隔却从约 32 增至 183 ms。先按允许的输出间隔定一轮最多执行多久,再结合上下文长度和预设形状换算成新 token 数。

动态形状:少一个 token 不一定少一份计算

预先捕获 16 行与 32 行两种 CUDA Graph

17–32 行的输入都填充到 32 行:从 18 减到 17 仍执行 32 行图,从 17 减到 16 才换小图。为 17 行另捕获一个图能省 15 行填充,但捕获要准备时间,重放缓冲还要长期占显存,选预设形状时要计入 8.1 节的运行时内存。

8.3

KV 缓存的分配、复用与释放

按最大输出长度整段预留,请求提前结束时尾部就一直空着;要求连续又会在邻接区域被占时搬数据或等待。分页分配像活页册:逻辑块按 token 位置编号(页码),物理块是实际存 KV 的内存,每条请求的块表记录逻辑块在哪个物理块。注意力按块表找 KV,物理块可以分散存放。

分页省掉的是“预留未用”,共享省掉的是“重复保存”,两者节省的是不同部分的内存。

图 8-10 · 块表恢复逻辑顺序

设每块 p 个 token、长度 L,需 ⌈L/p⌉ 块,只有最后一块有空槽:浪费 p⌈L/p⌉ − L ≤ p − 1。块越小浪费越少,块表项和分配次数越多。2023 年 vLLM 的 PagedAttention 把操作系统分页的思路用到 KV 缓存;FlashAttention 则在算子内部组织分块计算,两者分别作用于状态分配和计算访存。

图 8-15 · 写时复制

每块 4 个 token 的尾块已写入共同的 a、b、c,两个分支要分别写 x、y:写到原块就会争用第 4 个槽,所以先复制这三个 token 各自追加;此前已填满的只读块继续共享。

图 8-11 / 8-12 / 8-13 · 四条请求的 KV 分配

A 9 · B 13 · C 5 · D 15 · 每条最多 16

蓝 = 已用,紫 = 与 A 共用的块(B 只引用不另存),灰 = 已分配未用。p = 4 时整段预留 64 → 分页 52 → 共享 44,实际保存的始终是 42 个 token。练习 8-3:切到 p = 2、8 看未用槽与块表项此消彼长;p = 16 时 8 个共同 token 凑不满一块,共享不了。

分页块的分配与释放(动画)

p = 4 · 16 个物理块 · 空闲池顺序打乱(示意)

每步四条请求各追加一个 token,需要新块时从空闲池取一个并写进块表。共享块的引用数为 2:A 结束后降到 1,B 仍可用;B 也结束、且加速器已不再访问它,块才回到空闲池(图 8-14)。

多分支共享:引用 ≠ 物理块

四分支实验:4 × (95 + 9) = 416 次引用,物理块只有 95 + 4 × 9 = 131,少约 69%。节省来自公共前缀去重,私有尾块仍随分支数增长。

章首 16 条长请求共享前缀后

前 6144 token 的 KV 共 864 MiB,存一份;每条私有 8192 − 6144 + 255 = 2303 个 token,按 16 对齐 324 MiB。MKV = 864 + 324b,上限从 10 条独立请求增至 35 条。

图 8-16 · 取消返回了,块还没释放

释放要同时满足两个条件:没有使用者再持有引用,已提交的加速器操作也不再访问该块。一次观察中取消调用约 1.6 ms 返回,对应的 104 个块约 31 ms 才释放,后续请求在此之后使用这些块,就不会覆盖旧请求仍在读的数据。内存不足时还可以抢占:暂停一条请求、释放它的 KV,之后按已保存的输入输出重算。用 1 GiB KV 池时发生一次抢占,多调度 1805 个 token;2 GiB 池则没有。

对话与智能体:沿前缀连续复用

旧请求留下的前缀 KV,能让后来的请求免去同一段输入的计算,前提是前缀完全相同,且模型、位置编码规则、adapter 和状态格式一致。章首长请求命中 6144 token 前缀后,只需处理剩下的 2048 个输入。复用沿前缀连续发生:前 100 个相同、第 101 个不同,后面文字再相同也没用了。

图 8-20 · 同一段历史,三种更新方式

长度以 K token 示意

图 8-17 · 代码 Agent 前四轮的前缀树

根部 206 个共同 token,边上是新增数量,分叉表示后续内容不同。每轮在末尾追加能保留已有前缀,但上下文越长,KV 占用和后续读取也越大。

图 8-19 · 混合注意力:从最近快照恢复

KDA · TP=8 · 每卡每份快照约 53.6 MiB

全注意力保留逐 token KV,递推模型通常只留当前状态。文本匹配到第 10,752 个 token,快照在 4096、8192,就从 8192 恢复再算 2560 个。快照越密,重算越少,保留的状态越多(2 份约 107 MiB,32 份约 1714 MiB)。

图 8-21 · DeepSeek V4.1:两类局部状态分开处理

点选命中情况
三条路径都继续:取完整提示末尾最多 128 个 token 的编码器输出,经过 20 层解码器,近似构建解码器 SWA → 开始生成

短窗口重放得到的是近似状态:每层只看最近 128 个 token,但多层叠加仍会间接依赖更早的输入。容量上,DeepSeek V4 的工作负载下 SWA 约占长期缓存一半;V4.1 把它移出持久化缓存,再把全局 KV 压到约 1/4,长期存储约为原来的 1/2 × 1/4 = 1/8。编码器 SWA 短期放主机 DRAM,解码器 SWA 只在本轮生成时驻留加速器。

缓存准入与淘汰:值不值得留,先留谁

Vh = ph(Tr − Tf) − Tm  保留有利:ph > Tm / (Tr − Tf)

图 8-22 · 864 MiB 缓存:留一个 A,还是三个 B

重算按 BF16 峰值 62% · 维护 = 一次换出

A = 6144 token 前缀(864 MiB、重算 307.9 ms);B = 2048 token(288 MiB、重算 94.7 ms)。重算随长度超线性增长(3.25 倍),所以同样的内存放长前缀收益更高:A 每 MiB 约 0.154 ms,B 约 0.140 ms。准入决定留不留,淘汰决定先删谁,换出决定挪到哪一级,重计算决定重做多少 token。

容量不够,前缀留不到下一轮

12 轮 Agent 输入回放共 19,556 个输入 token,相同干扰请求下:6 GiB 池命中 16,304 个、只重处理 3252 个;1 GiB 池没有命中。相邻两轮之间多等 0.2 秒,命中数不变:加容量能保住前缀,单纯延后请求不能。

8.4

压缩与卸载

没有相同前缀时,还可以用量化减少每个值的字节:分组编码,每组存低位宽码值和 scale 等元数据,计算时恢复近似值。另一条路是只让当前要用的数据驻留,其余用前再搬入。

压缩省的是空间,代价是转换时间和可能改变的答案;卸载省的是显存,代价是每轮 decode 都要重新搬一遍。

图 8-23 · 同一组 32 个值占多少字节

BF16 64 B;q8_0 = 32 B 码值 + 2 B scale = 34 B(8.5 bit/值);q4_0 = 16 + 2 = 18 B(4.5 bit/值)。GGML 的 Q2_K 每 256 值:64 B 码值 + 20 B 元数据 = 84 B,平均 2.625 bit,元数据占约 24%:位宽越低,元数据比重越高。

KV 量化后的容量

一条 8K 输入 BF16 KV 1152 MiB → q8_0 612 / q4_0 324 MiB;生成完的 1188 MiB → q8_0 631.125 MiB,16 条合计约 9.86 GiB,放得进 12 GiB。上下文表示也能改每 token 存几个值:同一 8K 上下文的紧凑 MLA 状态只有 549 MiB(第 9.2.2 节)。

235B 模型的 Q2_K 文件放进 M2 Max

统一内存 96 GB = 89.41 GiB · 预留 8 GiB 给系统

Qwen3-235B-A22B 的 Q2_K 变体:码值与浮点数据约 64.43 GiB + 量化元数据约 15.37 GiB + 文件头与填充。两个文件只差 2.16 GiB,就足以决定能否容纳一条请求。统一内存里文件映射后,访问到的页才驻留;执行要访问的总量超过内存,就会不断换页。

压缩 KV 何时真的更快

ΔT = ΔD/β − Tc

8K 上下文 BF16 → q8_0 少读 540 MiB;有效带宽取读取受限时约 1.16 TB/s(batch 64 实测,峰值的 65%),省约 0.487 ms。Tc = 0.8 ms 时反而慢约 0.313 ms。

FP8 KV 的交点:约 7000 token

H100 · Llama-3.1-8B · 早期评估拟合

混合注意力的状态容量

8K 上下文、全局历史 + 有效 SWA:DeepSeek V4.1 为 6.953 + 2.578 = 9.531 MiB,V4 合计 30.521 MiB,约为 3.20 倍。局部窗口占固定空间,所以短上下文下差距小于全局历史本身的 3.95 倍。

权重卸载:腾出显存,换来每步搬移

图 8-24 / 8-25 · 每 4 层卸载 1 层 FFN

一层 FFN 三个 BF16 矩阵共 288 MiB。卸载 9 份释放 2592 MiB;一组预取缓冲占 288 MiB(净省 2304),两组占 576 MiB(净省 2016)。按 1152 MiB 的 8K 输入能多放 2 或 1 条,按 1188 MiB 的完整请求都只多 1 条。每轮复制 ≥ 2.72 GB / 64 GB/s ≈ 42.5 ms,超过 batch 1 一轮 decode 的 26.26 ms;255 轮仅搬移就约 10.8 秒,过不了 7 秒时限。时间线按每层 26.26/36 ms 计算、复制按标称带宽,调度顺序是示意。

先压缩再搬:解压器要多快

r S / Blink + S / Rdec < S / Blink ⇔ Rdec > Blink / (1 − r)

质量约束:答对数相同,不代表答对的是同一批题

图 8-26 / 8-27 / 8-28 · 八道固定任务 × 并发 1、4 各两次

Qwen3-8B · BF16 权重

任务 1–4 来自 128 条六位数字记录的文档(n128-r0…r3),任务 5–8 来自 512 条(n512-r0…r3)。黑框标出与 BF16 基线结果不同的格子。只把 Q 恢复成 BF16 就改变了结果,说明 KV 存储格式和 Q 的计算精度要分开考察。错误还要花时间:两次重试修正了错误,额外增加约 2.0 秒生成时间和 92 个 token。

8.5

推测解码

合批让一次权重读取服务更多请求;推测解码换个方向:先为同一条请求生成一段草稿,再让目标模型一次验证多个位置,接受几个就一次确定几个输出,生成 256 个 token 就不必走 256 轮。

验证几乎不增加一轮时间,即使草稿大多被拒,每轮也至少得到一个 token;收益由“每轮平均输出数”与“草稿开销”共同决定。

图 8-29 · 贪心验证:第一个不一致处截断

第 4 个草稿 token 是在错误的第 3 个之后生成的,验证结果作废。全部匹配时,目标模型还能在其后再生成一个 token。验证后 KV 有效长度回退到已确定位置,分页器按确定长度更新块引用,采样器和语法检查器也恢复到对应状态。

图 8-30 / 8-31 · 随机采样:接受 min(1, p/q),拒绝后按正差额重采

两符号目标分布

从 q 采到 x 并直接接受的概率是 q(x)·α(x) = min(p(x), q(x)),还差 [p(x) − q(x)]+,由拒绝后的重采补上,所以最终输出严格服从 p。草稿恒为 A 时接受率 1/4,恒为 B 时 3/4:分布相同,效率不同。抽样用固定种子的伪随机数。

接受率与加速比计算器

RTX PRO 6000 · batch 1 · 2K 上下文

E[N] = 1 + a + a² + … + am  t̄spec = Σ Tr / Σ Nr = (Tverify + Tdraft) / E[N]

验证 5 个位置时读取的权重和 KV 与普通 decode 相同,多出的 4 行约 65 GFLOP,按峰值 62% 约 0.2 ms,所以一轮仍按 26.26 ms 计(实验 8-5 的 DFlash 也类似:验证 8 个位置 15.2 ms,普通一步 16.9 ms)。草稿 AAAA(a = 1/4)每轮约 1.33 个、19.8 ms/token;BBBB(a = 3/4)约 3.05 个、8.64 ms/token,都快于普通 decode 的 26.26。图中横轴是每轮草稿查询耗时,与普通 decode 的交点分别约 8.7 和 53.9 ms。DFlash 权重 1.95 GiB(2.10 GB),按 batch 1 有效带宽约 588 GB/s,前向一次约 3.57 ms。

串行草稿 vs 并行草稿

每轮验证 26.26 ms

四个草稿 token 逐个生成要前向 4 次(14.28 ms),并行只需 1 次(3.57 ms)。都平均输出 3 个时并行更快;若并行草稿接受率较低、平均降到 2 个,(3.57 + 26.26)/2 ≈ 14.9 ms,反而慢于串行。

草稿准备时间要计入

BBBB 每个输出 token 约省 26.26 − 8.64 ≈ 17.6 ms,约 114 个 token 抵消 2 秒索引。256 个 token 普通 decode 约 6.72 秒,先建索引再用 BBBB 期望约 4.2 秒(书中给 4.22)。索引能提前建并被多个请求共用,开销还能摊得更薄。

练习 8-7 · 多长的草稿最好

a = 3/4 · Tm = 26.3 + 3.6m ms

加长一位有利:ΔT / ΔN < T / N,ΔN = Πi≤m ai

越靠后的草稿 token 保留概率越低(逐项相乘),加长越难回本。执行形状也会让 ΔT 跳变:验证 4、5 个都补齐到 8 行图时多一个几乎不花钱;从 8 个增到 9 个要换 16 行图,计算量突增。实测 Qwen3-8B / DFlash 短提取任务(并发 1):普通 90.0 ms,K7 30.6 ms,K15 29.4 ms,从 K7 到 K15 只再省 1.2 ms;还出现过两种块长都只接受 1 个草稿 token 的轮次,K15 白白丢掉 14 个。高负载时普通批处理已经充分共享权重,草稿和验证还要和其他请求争用加速器与内存。

8.6

请求负载下的性能与配置选择

在线服务里请求不断到达:一条还没完成,下一条已经进队列;一条在等工具返回,计算暂停,KV 却还占着。系统内平均有多少请求,由 Little 定律给出:n̄ = λ·T̄。

Little 定律

每秒 4 条、每条停留 0.5 秒,系统内平均 2 条;停留增到 2 秒就是 8 条。处理速度不再随到达率增长时,新请求在队列里累积。准入控制在接收前检查 KV 空间与截止时间:8.1 节的短请求已用掉 6.95 秒,再多排队 0.1 秒就超时,应提前拒绝、转交或换更快的配置。

有效吞吐:只数正确且按期的

Rgood = Σ QiDi / Tobs

Qwen3-8B 检索实验,3 秒时限,共 336 次回答:294 次正确,只有 155 次同时按期,139 次正确回答因为太晚而作废。

图 8-33 / 8-34 · 完成吞吐 vs 有效吞吐

每条件三个 16 请求窗口的中位数 · 只画书中给出数值的条件

到达率 1/s 时两种在线策略的有效吞吐都约 0.90。4/s 时逐个接纳完成 1.22、有效只剩 0.31;连续批处理完成 2.33、有效 1.02。连续模式推到 16/s,完成吞吐只到 2.38,有效吞吐反降到 0.60(−41%):更多到达主要增加了排队。先找出有效吞吐开始下降的到达率,再限制新接收的请求数。逐个接纳时,要从请求原定的到达时刻计时,把引擎外的等待也算进去。

把实测换成处理速度

实验 8-1 的效率表(MBU / MFU)

2K 上下文 · 无共享前缀 · CUDA Graph 关闭

decode 效率随 batch 上升,并不是读取变快了:读取量从 15.44 增到 34.46 GB,一轮只从 26.26 增到 29.63 ms,那段几乎固定的时间被更多字节分摊。batch 1 比 8.62 ms 的读取下界多出约 17.6 ms,且基本不随读取量变化:这是逐个提交 kernel、主机侧调度与同步的开销,CUDA Graph 和更好的调度可以去掉(第 5.5.1 节)。prefill 稳定在峰值的 58%–62%;8K 输入、前 6K 已缓存时为 52%–58%。

tGPU = L / rP + (G − 1) / rD = 2048 / 16,059 + 255 / 585 ≈ 0.563 GPU 秒 / 条 (8K、前 6K 已缓存、batch 16)

综合算例:16 条长请求,选哪种配置

先查内存够不够,再查能否按期,最后才比每个合格结果占用的 GPU 时间。在这张卡上一轮 decode 几乎不随 batch 和上下文变,只有减少轮数,才能明显缩短生成阶段。

配置设计台 · 图 8-35

16 条 · 输入 8192(前 6144 相同)

A BF16 独立上下文:整批 2137.5 TFLOP,按 58% 算力约 7.34 s,每轮读 34.46 GB 取表中 29.63 ms。B BF16 共享前缀:实验 8-1 实测 prefill 2.04 s(rP ≈ 16,059 新 token/s),每轮 27.35 ms,总 9.01 s 与实测中位数 9.02 s 一致。C q8_0 独立:每轮读 25.40 GB,在表中插值约 28.26 ms(未计格式转换)。D 共享 + DFlash 推测:显存峰值多约 3188 MiB(其中草稿权重约 1.95 GiB),prefill 多 9.4% 约 2.23 s;每轮 16 条各验证 8 个位置约 2.56 TFLOP、8.8 ms,短于 27.35 ms,取较大者。E(练习 8-9):从上下文查找草稿,索引在主机内存,不占显存不加 prefill,每轮平均 2 个 token,每轮 27.35 + 0.1 ms。输出长度、时限、内存等滑块按书中同样的公式外推;每轮输出数为题设假设。

图 8-36 · Amdahl:整个任务能快多少

Stask = 1 / ((1 − f) + f / s)

练习 8-8 · 草稿准备吃掉多少收益

100 s = prefill 20 + decode 50 + 工具与等待 30

每个合格结果的能耗

实验 8-9 读取 GPU 与 CPU package 的能量计数。16/s 时连续批处理的完成吞吐与 4/s 相近,迟到的回答却更多,每个合格结果的能耗高约 66%。答案错了或迟了,已经消耗的能量也要算进总成本。

常见误区

五个容易下错的结论

batch 越大越好

2K 上下文到 batch 51、8K 到 13,KV 读取就超过分摊的权重;12 GiB 只能接 37 条短请求、10 条长请求。batch 从 1 到 64,首 token 也从 96 ms 推到 3.29 s。

按 prefill 后的 KV 估容量

2K 请求 prefill 后 288 MiB,能接 42 条;生成完 324 MiB,42 条要 13,608 MiB。接收时按最终长度预留,才不会执行到一半内存不足。

量化 KV 一定更快、答对数相同就等价

少读 540 MiB 只省约 0.487 ms,转换多花 0.8 ms 就变慢。BF16 KV 与 “FP8 KV + BF16 Q” 都是 28/32,答错的却是不同的题。

预取能把卸载完全藏住

预取只让搬移和计算重叠,字节数不变:经 PCIe Gen5 每轮仍要 42.5 ms,比 26.26 ms 的 decode 还长;换 NVLink-C2C 才降到 6.0 ms。

完成吞吐高就是服务好

连续批处理在 16/s 下完成 2.38 req/s,有效吞吐只有 0.60,低于 4/s 时的 1.02;每个合格结果的能耗也高约 66%。

推测解码总能显著加速

输出缩到 16 个 token 时,B 2.45 s、D 2.37 s,草稿模型多做的 0.19 s prefill 几乎抵消了少跑 10 轮 decode 的收益。

本章小结

三笔账:内存、每轮耗时、每轮输出数

一、权重共用,KV 各算各的

合批摊薄每个输出 token 的权重读取,但每条请求仍读自己的上下文。权重换成更快的独立存储后,合批不再摊薄权重,batch 与推测方案都要重新比。连续批处理及时用上空出的位置,分块 prefill 减少长输入对生成的干扰。

二、KV 管理决定并发

分页按需分配,共享只存一份公共前缀,前缀缓存还省掉重复计算;追加、改写和总结会改变复用机会。压缩减少每个值的字节,卸载把权重移出加速器、代价是反复搬移。推测解码增加草稿和验证开销,换来一次计算确定多个输出。

三、先可行,再按期,再比成本

前缀共享让 16 条长请求放得下,但逐 token 生成 256 个仍超时,推测解码减少了 decode 轮数才按期。内存减到 6 GiB 时没有可行方案;输出缩短时推测优势几乎消失。要记录:能同时保存多少条状态、prefill 与 decode 各多快、延迟如何随 batch 和到达率变化。

下一章以这些结果为基础,决定各类加速器承担什么工作、需要多少执行单元,以及 KV 如何传输和共享:批处理决定已有资源如何处理请求,部署方式决定这些资源如何分工。

练习与实验

动手验证

练习 8-1 · 计算:batch 增大时的读取量变化

取 Dw = 15,136,811,008 bytes、k = 144 KiB,算 2K 与 8K 上下文在 batch 1、4、16、64 时每个输出 token 的读取量,并求 KV 读取首次达到或超过分摊权重读取的最小整数 batch。再用 12 GiB、每块 16 token、256 个输出,求两种长度下最多接纳多少条独立请求,判断容量是否先挡住 batch。(图 8-3 与 8.1 节容量计算器可直接核对。)

练习 8-2〔核心〕· 分析:由输出间隔限制确定 prefill 分块大小

用 8.2.2 节四条请求和执行时间模型,手算 r0、r1 的初次 prefill 与下一步 decode 各要多久,再算连续批处理接纳 r2 时该轮的耗时,解释最长输出间隔的来源。固定 c = 512,分别算 h = 0 与 h = 7680 的注意力配对数。最后分析六条请求回放,在“最长间隔 < 50 ms”和“后到请求尽快接纳”两种目标下选每轮 token 上限,指出哪一项耗时决定了选择。(调度演示可切块长与上限。)

练习 8-3 · 计算:KV 块大小与前缀共享如何影响容量

把长度 9、13、5、15 的四条序列分别按 2、4、8 个 token 分页,求各自尾块的未用槽位、总未用槽位与块表项数。再按章首算例算 8、16、32 条长请求在独立与共享下的完整容量。保持每条总长度不变,把一个原本私有的完整块改为公共前缀的一部分,求 b 条请求合计少分配多少物理块。

练习 8-4〔核心〕· 分析:有限缓存应保留哪些前缀

缓存 864 MiB,A 前缀 6144 token、864 MiB;三个独立 B 前缀各 2048 token、288 MiB。按 BF16 峰值 503.8 TFLOP/s 的 62% 求重算时间,按 PCIe Gen5 x16 每方向 64 GB/s 求取回时间,维护取一次换出,命中概率都是 50%,比较两种保留方式。B 命中概率不变,A 低于多少时应改留三个 B?换成 PCIe Gen4 x16 再比。数据部分读配套 12 轮回放,算共同前缀比例与实际命中比例,解释其他请求占缓存为何影响命中。

练习 8-5 · 计算:权重卸载节省的容量与增加的搬移

卸载 9 份 288 MiB 的 FFN 权重,每组预取缓冲容纳一份,求一组、两组缓冲时净省的显存,以及能多容纳几份 1152 MiB 输入状态或 1188 MiB 完整状态。再分别取 64 GB/s 与 450 GB/s,求每步与累计 255 步复制的下界;若留给全部复制的总时间是 1 秒,求最低单向带宽。

练习 8-6 · 数据分析:KV 压缩如何影响容量、正确性与重试成本

每组 32 个值另存 2 字节 scale,复算 8K 输入与完整长请求在 q8_0、q4_0 下的 KV 容量。读三种 KV / Q 精度配置的逐题结果,列出 BF16 KV 与“FP8 KV + BF16 Q”中正确性改变的题目。最后读重试记录,对每个成功任务合计首答与重试时间,并与只计最后一次成功生成的结果比较。

练习 8-7 · 推导:多长的草稿能让平均每 token 耗时最短

a = 3/4,求块长 1、2、4、8 的每轮平均输出数;按 Tm = 26.3 + 3.6m ms 求平均每 token 耗时。再设草稿按每组 4 个位置执行、不足一组按整组计时,Tm = 26.3 + 3.6 × 4⌈m/4⌉,比较块长 4 与 5,用边际条件解释。(上面的草稿长度图可勾选“按组执行”。)

练习 8-8 · 计算:阶段加速与草稿准备如何改变任务总时间

100 秒任务由 prefill 20、decode 50、工具与等待 30 秒组成。分别把三部分单独减半,求总耗时。再把 decode 降为四分之一并增加 8 秒草稿准备,求净加速比,以及准备时间达到多少时这项改动不再省时间。

练习 8-9〔核心〕· 综合设计:在容量与时限内减少每个合格结果的 GPU 时间

以 B 为基础构造方案 E:共享 BF16 前缀,草稿从上下文查找,索引放主机内存、不占显存也不增加 prefill;每轮平均 2 个 token,后续 128 轮,整批每轮 27.35 + 0.1 ms。求 E 的显存、总耗时和每个合格结果的 GPU 秒,与 B、D 比;再把可用内存改成 6 GiB、输出改成 16 个 token,重新选择。开放实验:用相同题集和到达轨迹跑两种配置,记录到达、首输出、结束、正确性与状态峰值,按本节公式算有效吞吐。(设计台勾选“方案 E”。)