CS336 · 从零构建语言模型
Lecture 10 · 推理
源文件:lecture-10.md
Lecture 10 · 推理
CS336: Language Modeling from Scratch · Stanford · Spring 2025
📅 May 1 · 💻 可执行讲义 · 讲者 Percy Liang
承上启下
上一讲(Lecture 9 · 缩放定律 I:基础):讨论的是训练前的预算分配问题——给定计算预算 $C$,如何在参数量 $N$ 和数据量 $D$ 之间分配才能最小化训练 loss。训练侧的核心指标是 loss、FLOPs、batch size、学习率与 scaling curve。
本讲(推理,inference):视角从「训练一个模型花多少钱」切换到「用一个模型花多少钱」。训练是一次性成本,推理是随每次调用反复发生的成本——产品一旦跑起来,推理账单往往远超训练账单。更关键的是,推理 workload 的性质与训练完全不同:训练可以在整个序列维度上并行,把计算组织成大矩阵乘法;而自回归生成必须一个 token 一个 token 地串行进行,瓶颈从「训练算力」转为「生成阶段的内存带宽(memory bandwidth)+ 动态调度」。分析工具正是 Lecture 5 · GPU 的 roofline 模型与 Lecture 2 · PyTorch 与资源核算 的 FLOPs 核算方法。
与后续的关联:Lecture 12 · 评估 中跑 benchmark、Lecture 16 · 对齐 II:RLVR 中生成 RL rollout,本质上都是大规模推理 workload,推理效率直接决定评估与后训练的成本。下一讲 Lecture 11 · 缩放定律 II:细节 则回到训练侧,看工业界如何在实践中拟合与使用 scaling laws。
摘要
推理分为 compute-bound 的 prefill 和 memory-bound 的 generation 两个阶段。KV cache 让生成的计算量从 $O(T^3)$ 降到 $O(T^2)$,但也把整个服务系统变成「KV cache 显存管理问题」;GQA/MLA/CLA/局部注意力、量化/剪枝/蒸馏、投机解码、continuous batching 与 PagedAttention,全都是围绕这个瓶颈做文章。
1. 推理 workload:训练之后的长期账单
推理无处不在,而且形态各异:
- 聊天机器人:对延迟极其敏感,用户盯着屏幕等第一个字出现。
- 代码补全:要求比聊天更低的延迟——补全必须在程序员打下一个字符之前出来。
- 批量数据处理:如用 LLM 清洗/标注海量文档,不在乎单条延迟,只在乎总吞吐和总成本。
- 模型评估:跑一遍 Lecture 12 · 评估 里的 benchmark 就是一次大规模批量推理。
- Test-time compute:o1/R1 式「先思考再回答」用更长的生成换更好的答案,直接把推理成本乘了一个数量级。
- RL 训练中的 rollout 生成:Lecture 16 · 对齐 II:RLVR 中策略模型要不断采样输出算 reward,推理慢会直接卡住训练循环。
这些场景对「快」的定义并不相同,因此需要先把指标拆清楚。
1.1 三个指标
| 指标 | 含义 | 关注场景 |
|---|---|---|
| TTFT(time-to-first-token) | 用户等到第一个 token 的时间,由 prefill 决定 | 交互体验 |
| Latency(每 token 延迟) | 生成阶段每个 token 的间隔,由 decode 决定 | 聊天/代码补全 |
| Throughput(吞吐) | 系统整体每秒产出的 tokens/s | 批处理、平台服务 |
三者经常互相冲突:
- 小 batch:单请求延迟低,但 GPU 利用率差,每 token 成本高;
- 大 batch:吞吐高、成本低,但请求要排队,TTFT 和每 token 延迟都变差;
- prefill 与 decode 两个阶段的最优 batch 策略也不同(见第 4 节)。
提示
推理优化没有「唯一的快」。聊天产品优化的是 TTFT 和每 token 延迟(人的阅读速度约 10 tokens/s,比这快就够用),批处理平台优化的是每百万 token 的成本。同一套硬件、同一个模型,为这两类目标设计出的 serving 系统完全不同——所以先问「为谁优化」,再谈技术。
2. 推理的算术强度
2.1 训练 vs 生成
回顾 Lecture 5 · GPU:算术强度(arithmetic intensity)定义为每从显存搬运 1 字节数据所做的浮点运算数:
$$\text{intensity} = \frac{\text{FLOPs}}{\text{bytes transferred}}$$
硬件也有一个对应的比值。以 H100 为例:bf16 算力峰值约 $989\ \text{TFLOP/s}$,HBM 带宽约 $3.35\ \text{TB/s}$,两者之比约
$$\frac{989 \times 10^{12}}{3.35 \times 10^{12}} \approx 295\ \text{FLOPs/byte}$$
这就是 roofline 的「屋脊点」:算术强度高于 295 时是 compute-bound(算力受限),低于 295 时是 memory-bound(带宽受限),此时算力再强也只能空转等数据。
训练:输入完整序列,可以在序列维并行,前向/反向都是大矩阵乘法,算术强度轻松超过屋脊点,是 compute-bound。
生成:每步只产出一个新 token,且必须等上一个 token 出来才能算下一个。大量操作退化为 matrix-vector 乘法,读权重和 KV cache 的搬运成本主导一切,是 memory-bound。
2.2 Matrix-vector 的问题
考察 MLP 层的一次矩阵乘 $Y = XW$,其中 $X \in \mathbb{R}^{B \times D}$($B$ 为 batch 中的 token 数,$D$ 为隐藏维度),$W \in \mathbb{R}^{D \times F}$($F$ 为 MLP 中间维度):
- FLOPs:$2BDF$(每个输出元素一次乘加);
- 数据搬运(bf16,每元素 2 字节):$2(BD + DF + BF)$ 字节。
当 $D, F \gg B$ 时,分母由权重 $DF$ 主导,于是
$$\text{intensity} = \frac{2BDF}{2(BD + DF + BF)} \approx B$$
算术强度约等于 batch 中的 token 数。生成时若 $B = 1$:为了产出一个 token 的计算,要把整个 $W$ 从 HBM 读一遍,算术强度约为 1 FLOP/byte,离屋脊点 295 差了两个数量级——GPU 的算力利用率不到 1%。
提示
把权重从 HBM 搬到片上的成本是固定的「过路费」。batch 里的 token 越多,这笔过路费被摊得越薄;$B=1$ 时一个 token 独自付全款,自然亏到极点。这就是为什么「换更快的 GPU」不一定让单请求生成按比例变快——瓶颈不是算力,而是把参数和 KV cache 从 HBM 搬出来的带宽。
2.3 Attention 的算术强度:batch 帮不上忙
MLP 的强度随 $B$ 增长,但 attention 不同。设一次前向处理 $T$ 个 query token、历史共 $S$ 个 KV token,单头维度 $H$:
- FLOPs:$QK^\top$ 与 $AV$ 两次矩阵乘,约 $4STH$;
- 搬运:读 $K, V$(各 $S \times H$)与 $Q$($T \times H$)、写输出,约 $4H(S+T)$ 字节量级。
$$\text{intensity}_{\text{attn}} \approx \frac{4STH}{4H(S+T)} = \frac{ST}{S+T}$$
代入两个阶段:
- Prefill($T = S$,整段 prompt 一起算):强度 $\approx S/2$。$S = 1024$ 时约 512,超过屋脊点,compute-bound;
- Decode($T = 1$,一次一个 token):强度 $\approx S/(S+1) < 1$,深度 memory-bound。
注意这个公式里没有 $B$:每条序列都有自己私有的 KV cache,batch 内不同请求之间无法共享读取。增大 batch 能摊薄 MLP 读权重的成本,却对 attention 读 KV cache 毫无帮助。
注意
「加大 batch 就能解决 memory-bound」只对 MLP/权重部分成立。长上下文场景下 KV cache 的读取量随 $B \times S$ 线性增长,batch 越大总搬运量越大——attention 部分的算术强度始终小于 1。这也是长上下文推理格外昂贵的根本原因。
3. KV Cache:复用前缀计算
3.1 朴素自回归推理
生成第 $t$ 个 token 时,如果把前面所有 token 都重新喂进 Transformer 完整算一遍前向:第 $t$ 步 attention 计算量为 $O(t^2)$,累加 $T$ 步:
$$\sum_{t=1}^{T} O(t^2) = O(T^3)$$
生成 $T$ 个 token 要 $O(T^3)$ FLOPs——大量计算在重复:前缀 token 的 K/V 表示每一步都被原样重算了一遍。
3.2 KV Cache
KV cache 的做法:对每层、每个 KV 头、每个历史 token,把算过的 key/value 向量缓存下来:
$$\text{KV cache 形状} \approx [\,B,\ S,\ L,\ K,\ H\,]$$
其中 $B$ 为 batch size,$S$ 为序列长度,$L$ 为层数,$K$ 为 KV 头数,$H$ 为单头维度。生成新 token 时:
- 新 token 只计算自己的 Q/K/V,并把 K/V 追加进 cache;
- 旧 token 的 K/V 直接从 cache 读,不再重算;
- attention 用新 Q attend 到全部历史 K/V。
于是每步 attention 降为 $O(t)$,总量降为 $O(T^2)$;MLP 每步只处理 1 个 token,总量 $O(T)$。代价是显存里多了一块随序列增长的状态。
由此推理天然分成两个阶段:
| 阶段 | 输入 | 特征 | 瓶颈 |
|---|---|---|---|
| Prefill | prompt 全部 token | 序列内并行,一次前向填满 cache | compute-bound |
| Generation / Decode | 每次 1 个 token | 自回归串行 | memory-bound |
提示
KV cache 是「用空间换时间」的教科书案例:把 $O(T^3)$ 的重复计算换成 $O(T^2)$ 计算 + $O(T)$ 的显存状态。但这笔交易把问题的性质变了——推理系统的核心资源从「FLOPs」变成「装 KV cache 的显存 + 读 KV cache 的带宽」,后面所有优化(GQA、MLA、PagedAttention)都是在管理这块状态。
3.3 KV Cache 的内存成本
每个请求的 KV cache 随上下文长度线性增长:
$$\text{bytes} = \underbrace{B}_{\text{batch}} \times \underbrace{S}_{\text{序列长}} \times \underbrace{L}_{\text{层数}} \times \underbrace{K}_{\text{KV 头数}} \times \underbrace{H}_{\text{头维度}} \times \underbrace{2}_{K\text{ 与 }V} \times \underbrace{2}_{\text{bf16 字节数}}$$
例子
配置:$L=40$ 层,$K=40$ 个 KV 头(标准 MHA),$H=128$,$D=5120$,$F=13824$,词表 32000。
- 每 token 的 KV cache:$40 \times 40 \times 128 \times 2 \times 2 = 819{,}200$ 字节 $\approx 0.8\ \text{MB}$;
- $S=1024$ 时每条序列约 $0.8\ \text{GB}$;
- $B=64$ 时 KV cache 共约 $54\ \text{GB}$,加上 bf16 权重 $26\ \text{GB}$,恰好逼近 H100 的 80 GB 显存上限;
- $B=256$ 直接 OOM。限制 batch size(也就限制了吞吐)的不是算力,而是 KV cache 的显存占用。
4. Throughput 与 Latency 的权衡
batch size 增大时:
- MLP 权重被 batch 内所有请求共享读取,每 token 的搬运成本被摊薄,throughput 提升;
- 但单个请求要与更多请求共享算力和带宽,且可能排队,latency 和 TTFT 变差;
- KV cache 总量随 batch 线性增长,显存上限反过来限制 batch 的天花板(见 3.3 的算例)。
常见策略:
- prefill 与 decode 分离调度:prefill 是 compute-bound 的大矩阵乘,decode 是 memory-bound 的小步快跑,两者混在一个 batch 里会互相拖累(一个长 prompt 的 prefill 会卡住所有人的 decode)。分开调度甚至分开部署(disaggregated serving)能让各自都跑在合适的 regime;
- decode 用尽量大的 batch:提高吞吐,直到 KV cache 吃满显存;
- 多副本部署:复制 $M$ 份模型实例,latency 不变,throughput 乘 $M$——用钱换吞吐,简单有效;
- 模型/KV cache 分片:结合 Lecture 7 · 并行 I:基础 的张量并行等手段把模型切到多卡,更复杂,但能服务单卡装不下的大模型,也顺带扩了 KV cache 的可用显存。
提示
Throughput 和 latency 的冲突本质上是「公交车 vs 出租车」:公交车(大 batch)人均成本低但要等发车,出租车(小 batch)即叫即走但贵。serving 系统的工作就是根据 SLA 在两者之间找运营方案,而 KV cache 显存决定了「公交车最多能坐多少人」。
5. 降低 KV Cache:架构捷径
以下都是有损(lossy)捷径:改动模型架构来缩小 KV cache,可能付出一点准确率代价,换取更大的 batch 与更快的生成。
5.1 GQA / MQA
标准 MHA(multi-head attention)中 KV 头数等于 query 头数($K = N$);MQA(multi-query attention)走到另一个极端,所有 query 头共享 1 组 KV($K = 1$);GQA(grouped-query attention)取中间值,让每组 query 头共享一组 KV($1 < K < N$)。
KV cache 的缩减比例正是头数之比:
$$\text{缩减倍数} = \frac{N}{K}$$
例子
Llama 2 13B 若把 $K = 40$ 改为 $K = 8$(每 5 个 query 头共享一组 KV),KV cache 缩小 5 倍:$B=64$ 时从约 54 GB 降到约 11 GB,于是 batch 可以开到 256,吞吐大幅提升。Llama 3 系列已默认采用 GQA(见 Lecture 3 · 架构与超参数)。
GQA 是当前主流折中:实验表明适度分组几乎不掉点,比 MQA 表达力好,比 MHA 省数倍 cache。
5.2 MLA(Multi-head Latent Attention)
DeepSeek-V2/V3 使用的思路:不直接缓存完整的 K/V,而是训练一个低秩压缩——把每个 token 的隐状态投影到一个低维 latent 向量 $c_t$ 缓存起来,需要时再用上投影矩阵恢复出各头的 K/V。
例子
128 头 × 128 维的完整 K/V 每 token 每层本需 $2 \times 16384$ 维;MLA 只缓存 $c_t \in \mathbb{R}^{512}$ 外加 64 维解耦的 RoPE key,共 576 维——缓存量降到约 1/57,与只分 2~3 组的 GQA 相当,但论文报告效果反而优于 MHA。
优点:
- KV cache 极大降低,generation 阶段要读的字节数同步降低;
- 因为「读 cache」正是 decode 的瓶颈,这直接转化为更大的 batch 和更高的吞吐,对 MoE 大模型(参见 Lecture 4 · 混合专家模型 MoE)的推理尤其重要。
难点:
- 与 RoPE 的兼容需要特殊处理:MLA 的效率依赖「把 K 的上投影矩阵吸收进 Q 的投影」这一代数技巧,而 RoPE 给 K 乘上了随位置变化的旋转矩阵,夹在中间导致无法吸收。DeepSeek 的解法是解耦(decoupled)RoPE:额外留出 64 维专门携带位置信息,其余维度不做旋转;
- 架构复杂度明显上升,且必须从预训练阶段就采用,无法像量化那样对现成模型事后加装。
5.3 Cross-Layer Attention / Local Attention
CLA(cross-layer attention):相邻的多层共享同一份 K/V——GQA 是「跨头共享」,CLA 是「跨层共享」,两者正交、可叠加。实验显示它能改善「准确率 vs KV cache 大小」的帕累托前沿。
Local attention(滑动窗口注意力):每个 token 只 attend 最近 $W$ 个 token,KV cache 只需保留窗口内的条目,与总上下文长度无关。代价是长程信息会丢失、准确率受损,因此实践中与少数全局 attention 层交错使用——例如 Character.AI 的部署是每 6 层局部才配 1 层全局。
提示
5.1–5.3 的所有花样共享同一个目标函数:在尽量不掉准确率的前提下,减少生成每个 token 时必须从 HBM 读出的 KV 字节数。方向有三个正交的轴——跨头共享(GQA/MQA)、跨层共享(CLA)、压缩表示(MLA)、截断历史(local attention)——工业模型往往组合使用。
6. Transformer 之外:为推理重设架构
如果把「KV cache 随 $T$ 线性增长」看作 Transformer 的原罪,那么更激进的路线是换掉架构本身。
6.1 State Space Models
SSM(state space model)的核心思路:用一个固定大小的状态向量递归地总结全部历史,生成时只更新这个状态,而不是维护 $O(T)$ 的 KV cache——推理成本与上下文长度解耦。
代表工作:
- S4:结构化状态空间模型的开山之作,线性时不变系统,可用卷积并行训练;
- Mamba:引入输入依赖(selective)的状态更新参数,弥补了 SSM 在内容选择上的短板,在 1B 规模上与 Transformer 打平;
- Jamba:AI21 的混合架构,Transformer 层与 Mamba 层按 1:7 交错,再叠加 MoE——用少量 attention 层保住检索能力,用大量 Mamba 层省 cache;
- BASED / MiniMax-01 等线性注意力变体:线性注意力 + 局部注意力,再保留少量完整 attention 层。
优势是 $O(1)$ 的状态大小,长上下文推理内存和带宽都极省。问题在于固定大小的状态是有损压缩:associative recall(如「前文提过的某个电话号码是多少」这类精确检索)任务上 Transformer 仍显著更强。因此纯替代不容易,混合架构是当前的务实答案。
6.2 Diffusion LM
扩散式文本生成不做自回归:从一个全是噪声/掩码的序列出发,并行地对整个序列做多步迭代精化(refine)。理论上每步都能同时更新所有位置的 token,摆脱「一步一个 token」的串行瓶颈——Inception Labs 的 Mercury 声称在代码生成上比同档自回归模型快数倍。
挑战:离散文本上的扩散在数学上比连续图像更别扭,生成质量、可控性与训练配方都还在探索期,尚未撼动自回归的主流地位。
提示
SSM 与 diffusion 分别攻击自回归 Transformer 的两个软肋:SSM 砍掉 $O(T)$ 的 KV cache(状态太大),diffusion 砍掉逐 token 的串行依赖(步数太多)。它们都是「推理成本倒逼架构设计」的例证——架构选择越来越不由训练效果单独决定。
7. 量化、剪枝与蒸馏
另一类有损捷径:不改架构,直接压缩模型本身。生成是 memory-bound 的,权重字节数减半 ≈ 读权重的时间减半,所以压缩直接换来速度。
7.1 Quantization
| 格式 | 字节 | 用途 |
|---|---|---|
| FP32 | 4 | 训练中的 optimizer state |
| BF16 / FP16 | 2 | 常规训练与推理默认 |
| FP8 | 1 | 高效推理;训练「艺高人胆大」也可用 |
| INT8 | 1 | 高效推理 |
| INT4 | 0.5 | 极限压缩 |
PTQ(post-training quantization,训练后量化):用少量校准数据估计每个通道/分组的 scale 与 zero-point,把权重映射到低比特整数网格,不需要重新训练。QAT(quantization-aware training,量化感知训练):训练时就模拟量化误差,效果更好但要动训练流程,大规模场景不总现实。
LLM.int8() 的关键发现:模型规模超过约 6.7B 后,激活中会系统性出现少数离群(outlier)特征通道,其数值大到让普通 int8 量化的网格完全失真。解法是混合精度分解——把约 0.1% 的 outlier 通道单独用 fp16 计算,其余走 int8。代价是推理反而比 fp16 慢 15~23%(分解本身有开销),它换来的是显存减半而非速度。
AWQ(activation-aware weight quantization):观察到权重的重要性由激活分布决定,保护约 0.1%~1% 的关键权重(通过按通道缩放而非真的保留 fp16),其余压到 int3/int4。fp16 → int3 可得约 4 倍显存缩减与 3.2 倍实测加速。
注意
量化的评估不能只看 perplexity——PPL 对少数关键能力的退化不敏感(这与 Lecture 12 · 评估 讲的「PPL 不等于下游能力」是同一件事)。低比特量化后应在下游任务上复测,尤其是长推理链任务,误差会沿生成步数累积。
7.2 Pruning + Distillation
剪枝(pruning):按重要性评分删除整个层、attention 头或隐藏维度,直接得到一个更小、更快的模型(结构化剪枝,硬件友好)。剪完的模型通常明显掉点。
蒸馏(distillation):用原模型(teacher)的输出分布做监督信号,训练/修复剪枝后的小模型(student),把丢掉的能力找补回来。
两种得到小模型的 recipe:
| 路线 | 步骤 |
|---|---|
| From scratch | 设计快架构 → 从头预训练 |
| Prune + Distill | 取大模型 → 重要性评分并剪枝 → 用 teacher 蒸馏修复 |
NVIDIA 的 Minitron 系列与 Llama 3.2 的 1B/3B 都走第二条路线:其总成本可比从头训练低一个数量级,因为大模型已经付过的「学费」通过蒸馏被继承了下来。
8. Speculative Decoding:用小模型猜,大模型验
这是无损(lossless)加速:输出分布与目标模型逐 token 精确一致。
出发点是 decode 阶段的一个不对称性:生成慢,验证快。生成 $K$ 个 token 要 $K$ 次串行前向;但给定 $K$ 个候选 token,用一次并行前向就能算出它们每个位置的概率(像 prefill 一样并行)。又因为 decode 是 memory-bound,一次前向处理 1 个还是 $K$ 个 token,读权重的时间几乎一样——验证近乎免费。
投机采样(speculative sampling)流程(讲义记号:draft 模型为 $p$,target 模型为 $q$):
- 便宜的 draft 模型自回归猜 $K$ 个 token(如 $K=4$);
- target 模型一次并行前向算出这些位置上的概率;
- 对每个候选 token $x \sim p$,以概率
$$\alpha(x) = \min\!\left(1,\ \frac{q(x)}{p(x)}\right)$$
接受;一旦拒绝,从残差分布
$$r(x) = \frac{\max\bigl(0,\ q(x) - p(x)\bigr)}{\sum_{x'} \max\bigl(0,\ q(x') - p(x')\bigr)}$$
中重新采样一个 token 并停止本轮;
4. 这个修正的 rejection sampling 保证最终每个 token 都精确服从 $q$。
例子
设词表只有 $\{A, B\}$,且 draft 高估了 $A$:$p(A) > q(A)$(于是 $p(B) < q(B)$)。
- 输出 $A$ 的概率 $= p(A) \cdot \frac{q(A)}{p(A)} = q(A)$ ✓
- 输出 $B$ 的概率 $= \underbrace{p(B) \cdot 1}_{\text{直接接受}} + \underbrace{p(A)\bigl(1 - \tfrac{q(A)}{p(A)}\bigr)}_{\text{拒绝 }A\text{ 后重采样,残差全在 }B} = p(B) + p(A) - q(A) = 1 - q(A) = q(B)$ ✓
无论 draft 猜得多差,输出分布都严格等于 target——draft 质量只影响速度(接受率),不影响正确性。
sequenceDiagram
participant Draft as "Draft 模型 p"
participant Target as "Target 模型 q"
Draft->>Draft: 串行猜 token1..token4
Draft->>Target: 提交候选序列
Target->>Target: 一次前向并行算各位置概率
Target-->>Draft: 按 min(1, q/p) 接受前 k 个,拒绝处从残差重采样
关键性质与实践:
- exact sampling:与直接从 target 采样不可区分,无需任何质量评估;
- draft 越接近 target,接受率越高、加速越明显。典型搭配是同家族小模型:70B target + 8B draft、8B target + 1B draft;
- draft 与 target 必须共用同一个 tokenizer(参见 Lecture 1 · 概览与分词),否则「逐 token 验证」无从谈起。
扩展:
- Medusa:不用独立 draft 模型,在 target 模型上加多个解码头一次并行猜多个后续 token,省掉 draft 的前向;
- EAGLE:用 target 模型最后一层的 hidden features(而非 token)做自回归 draft,猜测质量更高、接受率更好。
提示
投机解码把 memory-bound 阶段「算力大量闲置」这个坏消息变成好消息:反正读一遍权重的时间是固定的,不如让这次前向多验证几个 token,把闲置的 FLOPs 用起来。它在概念上与 rejection sampling 完全同源——用一个便宜的 proposal 分布逼近昂贵的目标分布,错了就修正。
9. 动态请求:Continuous Batching 与 PagedAttention
前面假设 batch 是静态给定的;线上真实流量是 ragged workload(参差不齐的负载):
- 请求到达时间不同;
- prompt 长度不同;
- 生成长度不同且事先未知(有人生成 10 个 token 就停,有人生成 1000 个);
- 大量请求共享同一个 system prompt 前缀;
- 同一 prompt 可能要采样多个候选(best-of-n)。
静态 batching 下,整个 batch 必须等最慢的请求生成完才能换人,GPU 大量空转。
9.1 Continuous Batching
Orca 提出两个核心机制:
- Iteration-level scheduling(迭代级调度):调度的粒度不是「请求」而是「一步 decode」。每个 decode step 结束后重新组 batch——完成的请求立即腾位,新请求随时插入,不必等整个 batch 收工;
- Selective batching(选择性批处理):不同请求序列长度不同,无法拼成规整张量。解法是把所有序列的 token 拼接(flatten)成一个大 batch 跑非 attention 算子(MLP、LayerNorm 等对 token 独立的运算),只在 attention 处按序列拆开分别计算——因为只有 attention 关心序列边界。
9.2 PagedAttention
传统 KV cache 分配按「最坏情况」给每个请求预留最大长度的连续显存,造成严重浪费:内部碎片(预留了但没用到)加外部碎片(空闲块不连续没法用),实测有效利用率可能只有 20%~40%。
PagedAttention(vLLM 的核心)把操作系统的虚拟内存分页思想搬进 KV cache 管理:
- 把每个请求的 KV cache 切成固定大小的 block(如每块 16 个 token),按需分配;
- 逻辑上连续、物理上不连续,靠 block table 做「页表」翻译;
- 天然支持 prefix sharing:共享同一 system prompt 的请求映射到同一批物理 block,只存一份;
- 分叉采样(一个 prompt 出多个候选)用 copy-on-write:共享的 block 被写入时才复制。
graph LR
REQ["请求的逻辑 KV cache"] --> B1["block 1"]
REQ --> B2["block 2"]
REQ --> B3["block 3"]
B1 -.物理映射.-> HBM1["HBM page A"]
B2 -.物理映射.-> HBM2["HBM page Q"]
B3 -.物理映射.-> HBM3["HBM page C"]
vLLM 还叠加了一系列系统优化:把 block 读取融合进 attention kernel(fused kernel)、FlashAttention / FlashDecoding(参见 Lecture 6 · Kernel 与 Triton)、CUDA graphs 消减 kernel launch 开销。综合下来相对此前系统有 2~4 倍吞吐提升。
提示
KV cache 管理面对的问题——变长、动态到达、共享前缀、碎片化——和操作系统管理进程内存的问题一模一样,所以答案也一样:分页 + 页表 + 写时复制。Serving 的进步很多不是 ML 创新,而是把 OS/数据库社区五十年的老智慧搬进 GPU 显存。
总结
mindmap
root((推理))
Metrics
TTFT
latency
throughput
Workload
prefill compute-bound
generation memory-bound
KV cache
Architecture
GQA
MLA
CLA
local attention
SSM
Compression
quantization
pruning
distillation
Speedups
speculative decoding
Medusa
EAGLE
Serving
continuous batching
PagedAttention
prefix sharing
关键要点:
-
推理和训练是两种 workload
- 训练可在序列维并行、compute-bound;生成串行、算术强度约等于 batch size,深度 memory-bound。H100 屋脊点约 295 FLOPs/byte,$B=1$ 的生成只有约 1。 -
KV cache 是推理系统的核心状态
- 它把 $O(T^3)$ 计算降到 $O(T^2)$,但吃显存和带宽;限制 batch(吞吐)的往往是 KV cache 而非算力。 -
Prefill 和 generation 要分开优化
- prefill 像训练(大矩阵乘),generation 像反复搬运权重和 cache;两阶段混合调度会互相拖累。 -
GQA/MLA/CLA/local attention 都是在减 KV cache
- 跨头共享、压缩表示、跨层共享、截断历史四条正交的轴;架构设计越来越受推理成本驱动,SSM 与 diffusion 是更激进的重设。 -
量化/剪枝/蒸馏用少量精度换字节数
- memory-bound 下权重减半近似读取时间减半;outlier 是量化的头号敌人(LLM.int8()、AWQ 都在处理它)。 -
Speculative decoding 是 lossless shortcut
- 小模型猜、大模型并行验,修正的 rejection sampling 保证输出分布精确等于 target;draft 质量只影响速度不影响正确性。 -
Serving 是系统问题
- continuous batching(迭代级调度 + 选择性批处理)和 PagedAttention(分页 + 前缀共享 + 写时复制)是把 OS 思想搬进 LLM serving。
下一讲预告(Lecture 11 · 缩放定律 II:细节):
推理说明了模型训练后的成本;下一讲回到训练前的模型设计,深入看 CerebrasGPT、MiniCPM、DeepSeek 等模型如何在实践中拟合 scaling laws、选择 batch size 和学习率,并讨论 muP 是否真能让超参数跨规模迁移。
复习自测
题目
MLP 层矩阵乘的算术强度约等于 batch 中的 token 数,$B=1$ 时约为 1 FLOP/byte;而 H100 的屋脊点是 $989\ \text{TFLOP/s} \div 3.35\ \text{TB/s} \approx 295$ FLOPs/byte。1 远小于 295,说明时间花在从 HBM 搬权重上,算力利用率不足 1%——瓶颈是带宽而非算力。
题目
每 token:$40 \times 40 \times 128 \times 2 \times 2 \approx 0.8$ MB;每序列(1024 token)约 0.8 GB;$B=64$ 时约 54 GB。加上 26 GB 权重正好逼近 H100 的 80 GB。说明限制 batch size(进而限制吞吐)的是 KV cache 显存,这正是 GQA/MLA 等「减 cache」架构的动机。
题目
MLP 的权重 $W$ 是所有请求共享的,batch 越大,读一遍 $W$ 的固定成本被摊得越薄(强度 $\approx B$)。而每条序列的 KV cache 是私有的,batch 内无法共享读取——attention 的算术强度 $\approx ST/(S+T)$,公式里根本没有 $B$;decode 时($T=1$)恒小于 1,始终 memory-bound。
题目
无损性来自修正的 rejection sampling:对 draft 提议的 $x \sim p$ 以 $\min(1, q(x)/p(x))$ 接受,拒绝时从残差分布 $r(x) \propto \max(0, q(x)-p(x))$ 重采样,可证输出边际分布精确等于 $q$。验证便宜有两个原因:K 个候选位置的概率可以在一次前向中并行算出(类似 prefill);且 decode 是 memory-bound,一次前向的耗时由读权重主导,处理 1 个还是 $K$ 个 token 时间几乎相同。
题目
MLA 的推理效率依赖把 K 的上投影矩阵「吸收」进 Q 的投影,从而只需缓存低维 latent。但 RoPE 在 K 上乘了随位置变化的旋转矩阵,卡在两个投影之间使吸收不再成立。DeepSeek 的解法是解耦 RoPE:额外留出 64 维专门做旋转、携带位置信息,其余 512 维 latent 不做旋转,两部分拼接后参与 attention。
参考资料
- 💻 lecture_10.py(官方可执行讲义)
- 📖 Scaling Book: Inference
- 📄 GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints
- 📄 DeepSeek-V2
- 📄 Reducing Transformer Key-Value Cache Size with Cross-Layer Attention
- 📄 Mamba: Linear-Time Sequence Modeling with Selective State Spaces
- 📄 LLM.int8()
- 📄 AWQ
- 📄 Compact Language Models via Pruning and Knowledge Distillation (Minitron)
- 📄 Fast Inference from Transformers via Speculative Decoding
- 📄 Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads
- 📄 EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty
- 📄 Orca: A Distributed Serving System for Transformer-Based Generative Models
- 📄 Efficient Memory Management for Large Language Model Serving with PagedAttention
- 🧰 vLLM