Theme
下面这两讲的核心不是“新模型结构”,而是一个统一问题:LLM 已经足够强,但推理太贵、太慢、太占显存。因此 Lecture 20 主要讲“推理时 attention/KV cache/长上下文服务优化”,Lecture 21 主要讲“模型压缩:量化、剪枝、蒸馏”。
Lecture 20:LLM Inference 的内存墙与 KV Cache 优化
0. 本章进化树
核心目标:
在自回归生成中,如何减少重复计算、降低 KV cache 显存压力、提高 batch 服务吞吐,并支持更长上下文?
逻辑演进:
text
Naive autoregressive decoding
↓ 问题:每生成一个 token 都重新计算历史 K,V
KV Cache
↓ 问题:节省计算,但 KV cache 线性增长,decode 阶段变成 memory-bandwidth bound
Prefill / Decode 分阶段理解
↓ 问题:decode 每步只算一个 token,GPU 算力吃不满
FlashAttention
↓ 问题:减少 attention 中间矩阵写回,但 KV cache 本身仍大
MQA / GQA
↓ 问题:减少 KV head 数,牺牲少量表达能力换显存/带宽
PagedAttention / vLLM
↓ 问题:服务多请求时 KV cache 内外碎片严重
Chunked Prefill + Piggybacked Decode
↓ 问题:prefill 和 decode 调度不均导致 GPU bubble
StreamingLLM / Attention Sink
↓ 问题:长上下文 OOM 或滑窗丢掉初始 token 后性能崩溃1. Naive Decoding 为什么浪费?【重要程度:★★★★★】
自回归生成的概率分解为:
每一步生成新 token 时,Transformer 都需要对当前上下文做 self-attention。标准 attention 是:
其中:
符号定义:
| 符号 | 含义 |
|---|---|
| 当前上下文 token 的 hidden states | |
| 当前上下文长度 | |
| query/key/value 投影矩阵 | |
| query、key、value 矩阵 | |
| 每个 attention head 的 key/query 维度 | |
| 每个 query 对所有 key 的相似度 | |
| 缩放项,防止 dot product 过大导致 softmax 饱和 |
Naive decoding 的问题是:第 步生成 token 时,历史 token 的 其实已经算过,但 naive 实现会重新计算所有历史 。课件强调:为了预测一个新 token 反复重算整个历史上下文,是 massive compute waste。
严格地说,如果生成 个 token,每步上下文长度从 增长到 ,重算历史投影的总量近似为:
课件中写 更像是笔误或夸张表达;从 Transformer KV 重算角度,更合理的是 级别的历史重复计算。
2. KV Cache:用显存换时间【重要程度:★★★★★】
KV cache 的思想是:
历史 token 的 不会因为新 token 到来而改变,所以只需要保存,不需要重算。
第 步时,只计算最新 token 的:
然后把新的 append 到 cache:
生成第 个 token 时的 attention 变为:
直觉:query 是“当前 token 要找什么信息”,key 是“历史 token 提供什么索引”,value 是“历史 token 真正携带的信息”。KV cache 就是把历史索引和内容提前存好。
KV cache 的收益:把每步重复计算历史 变成只计算当前 token 的 ,所以生成阶段的投影计算从重复的 降为增量的 。但注意,attention 对历史 cache 的读取仍然随上下文长度增长。
3. Prefill vs Decode:LLM 推理的两个阶段【重要程度:★★★★★】
LLM 推理分为两个阶段。课件明确指出,第一个输出 token 依赖所有 prompt token;第二个输出 token 依赖 prompt 加上第一个输出 token,以此类推。
3.1 Prefill
Prefill 是处理输入 prompt 的阶段:
特点:
| 项目 | Prefill |
|---|---|
| 输入 | 全部 prompt token |
| 并行性 | 高,可以跨 token 并行 |
| GPU 利用率 | 高 |
| 瓶颈 | compute-bound |
| 指标 | TTFT, Time To First Token |
课件强调:prefill 阶段所有输入 token 的计算可以并行执行,但不能跨层并行,因为第 层依赖第 层输出。
3.2 Decode
Decode 是逐 token 生成阶段:
特点:
| 项目 | Decode |
|---|---|
| 输入 | 每步 1 个 token |
| 并行性 | 低 |
| GPU 利用率 | 低 |
| 瓶颈 | memory-bandwidth-bound |
| 指标 | TPOT, Time Per Output Token |
Decode 阶段每步都要从 HBM 读取整个 KV cache。课件指出,生成一个新 token 会迫使系统重新读取历史 KV cache;在 Llama-3 8B、seq=4096 的例子中,每步大约要加载 1GB 数据,导致 compute starvation。
4. KV Cache 显存大小公式【重要程度:★★★★★】
课件给出 FP16 KV cache 大小:
符号定义:
| 符号 | 含义 |
|---|---|
| batch size,同时服务的序列数 | |
| sequence length / context length | |
| cache 两个矩阵: 和 | |
| Transformer 层数 | |
| attention heads 数 | |
| 每个 head 的维度 | |
| bytes | FP16 每个数占 2 bytes |
结论:
所以 KV cache 随序列长度 线性增长。课件特别强调:长文档任务 OOM 的元凶经常不是模型权重,而是 KV cache。
5. FlashAttention:减少 attention 中间矩阵写回【重要程度:★★★★☆】
标准 attention 会显式构造:
其中 。问题是 很大,写入/读取 HBM 很慢。
FlashAttention 的核心:
不把完整 attention matrix 存到 HBM,而是 block-by-block 计算,并在线维护 softmax 的归一化项。
对于 logits:
softmax 权重为:
其中:
输出:
在线计算时维护:
直觉:softmax 的难点是 denominator 需要看完所有 logits。FlashAttention 的关键是用在线归一化精确维护 denominator,而不是近似 softmax。课件 quiz 也强调:“exact online computation of softmax is impossible” 是 False。
6. MHA → MQA → GQA:减少 KV head【重要程度:★★★★☆】
6.1 MHA
Multi-Head Attention 中,每个 query head 都有自己的 key/value head:
问题:每个 head 都存自己的 ,KV cache 随 head 数线性增长。
6.2 MQA
Multi-Query Attention 让所有 query heads 共享同一组 :
优点:KV cache 极大压缩。
缺点:所有 heads 共享同一套 memory,表达能力下降,生成质量可能受损。
6.3 GQA
Grouped Query Attention 是 MHA 和 MQA 的折中。把 query heads 分成若干组,每组共享一组 。
如果共有 个 query heads, 个 KV heads,则压缩比例约为:
例如 ,KV cache 约减少到原来的 。
| 方法 | KV 共享方式 | 显存节省 | 表达能力 |
|---|---|---|---|
| MHA | 每个 head 独立 K,V | 低 | 高 |
| MQA | 所有 heads 共享 K,V | 最高 | 较低 |
| GQA | 每组 heads 共享 K,V | 中高 | 中高 |
7. PagedAttention:解决 KV cache 碎片【重要程度:★★★★☆】
PagedAttention 借鉴操作系统虚拟内存。课件描述:一个 request 类似一个 process;logical memory 类似 virtual memory;block table 把 logical blocks 映射到 physical blocks;physical blocks 位于 GPU memory。
问题来源:
| 碎片类型 | 原因 |
|---|---|
| Internal fragmentation | 每个请求预留最大长度,比如 2048 tokens,但实际只用 50/300/700 tokens |
| External fragmentation | 不同请求的物理显存不连续,中间空洞太小无法复用 |
PagedAttention 的公式化理解:
它不要求每个 sequence 的 KV cache 在物理显存中连续存放,因此可以更灵活地复用显存块。
8. Continuous Batching 与 Chunked Prefill【重要程度:★★★☆☆】
多请求服务中,prefill 和 decode 的计算特性不同:
| 阶段 | 计算特征 |
|---|---|
| Prefill | 大矩阵计算,compute-bound |
| Decode | 小步读取 KV cache,memory-bound |
问题:如果一个请求在 prefill,其他请求的 decode 可能要等待,形成 GPU bubbles。课件称这是 iteration-level scheduling 导致的 GPU underutilization。
Chunked Prefill 的做法:
然后把 decode 插入 prefill chunk 的空隙中:
text
Prefill chunk + Decode piggyback直觉:不要让一个超长 prompt 的 prefill 独占 GPU,而是切块后让 decode 请求“搭便车”。
9. StreamingLLM 与 Attention Sink【重要程度:★★★★☆】
长上下文问题:
- 完整保留 KV cache:显存爆炸,OOM。
- 只保留最近窗口:一旦丢掉最初 tokens,性能崩溃。
- 每次重算:太慢,接近 。
StreamingLLM 的观察:
初始几个 token 虽然语义信息不强,但会稳定吸收大量 attention,形成 attention sink。
原因:自回归训练中,初始位置被最多后续 token 看到;softmax 归一化也需要某些位置承接冗余注意力。课件指出,保留 attention sink 加最近窗口,可以让 KV cache 固定大小并保持较低 PPL。
StreamingLLM 的 cache 结构:
即:
其中 是滑动窗口大小。
Lecture 21:模型压缩:Quantization, Pruning, Distillation
0. 本章进化树
核心目标:
在尽量不损失精度的情况下,把大模型变得更小、更快、更便宜。
逻辑演进:
text
Scaling law vs hardware gap
↓ 问题:参数越大越强,但显存/带宽/成本跟不上
Numeric format compression
↓ FP32/FP16 → INT8/INT4
Quantization mapping
↓ 问题:离散化造成误差,低 bit 出现 precision cliff
PTQ vs QAT
↓ PTQ 快但脆弱;QAT 准但贵
Emergent outliers
↓ LLM activation outlier 破坏普通 INT8 量化
LLM.int8
↓ outlier 留 FP16,小值走 INT8
SmoothQuant
↓ 把 activation outlier 压到 weights 中
Pruning
↓ 删除冗余参数/结构
WANDA / OBS / iterative pruning
↓ 同时考虑权重和 activation,减少性能损失
2:4 structured sparsity
↓ 为 GPU sparse tensor core 设计的半结构化稀疏
Distillation
↓ 从 teacher 向 student 迁移行为和 dark knowledge1. 为什么需要压缩?【重要程度:★★★★★】
课件开头的核心矛盾是:
LLM 的 memory 和 compute 需求增长速度超过硬件能力增长。
例如 70B 模型用 FP16 存储权重需要:
课件指出,LLaMA-2 70B FP16 需要多张 A100 80GB;如果 INT4 量化,可以大幅降低到消费级 GPU 级别,这是压缩技术要解决的 gap。
2. 数值格式:FP16、INT8、INT4【重要程度:★★★★☆】
若 cache 容量为 bytes,每个参数使用 bits,则可存储参数个数为:
例如 bytes:
结论:bit width 越低,同样显存能放更多参数,但数值精度下降。
3. Quantization 数学映射【重要程度:★★★★★】
量化把连续实数 映射成离散整数 。
通用形式:
反量化:
符号定义:
| 符号 | 含义 |
|---|---|
| 原始 FP32/FP16 实数 | |
| 量化后的整数 | |
| scale,控制实数到整数的缩放比例 | |
| zero-point,整数空间中对应实数 0 的偏移 | |
| 整数量化范围 | |
| 反量化后的近似值 |
Absmax symmetric quantization
用于 INT8 对称量化,通常映射到:
Zero-point quantization
或者更一般地:
课件例子:
反量化:
误差:
4. Precision Cliff:为什么 4-bit 常常好,3-bit 开始崩?【重要程度:★★★★☆】
课件观点:
在固定显存预算下,4-bit 大模型往往比 8-bit/16-bit 小模型更准;但 3-bit 以下误差太大,增加参数也难以弥补。
这是模型规模收益和量化噪声之间的 trade-off:
当从 16-bit 到 4-bit 时,参数数量可显著增加,规模收益大于噪声损失。但到 3-bit 时,离散 levels 太少,量化误差主导性能下降。
5. PTQ vs QAT【重要程度:★★★★★】
| 方法 | 全称 | 核心做法 | 优点 | 缺点 |
|---|---|---|---|---|
| PTQ | Post-Training Quantization | 训练完后直接量化 | 快、便宜、不需要训练 | 低 bit 容易掉点 |
| QAT | Quantization-Aware Training | 训练时模拟量化误差 | 精度恢复好 | 贵,需要数据和训练 |
课件明确指出:PTQ 是不做进一步 weight update/backprop 的量化;QAT 则在 forward path 中使用 fake quantization,使模型适应量化误差。
6. QAT 与 STE【重要程度:★★★★★】
QAT 的难点是:
不可导,且其导数几乎处处为 0:
如果直接反向传播:
梯度消失,权重无法更新。
STE,即 Straight-Through Estimator,做法是 forward 用 round,backward 假装 round 是 identity:
Forward:
Backward:
因此:
QAT 的训练流程:
text
PTQ initialization
→ 用 calibration data 初始化 S 和 Z
Fake quantization forward
→ 在 forward 中注入 rounding error
STE backward
→ 绕过 round 的不可导节点
FP32 shadow weights update
→ 用 FP32 权重累积梯度,forward 时再 fake quantize7. LLM 中的 activation outlier【重要程度:★★★★★】
普通量化在 LLM 上失败的主要原因之一是 activation outlier。课件定义:activation outlier 是 hidden vector 中某些维度,其数值至少达到 6.0,并且出现在至少 25% 层、至少 6% token 中。
问题来自 absmax scale:
如果某个 outlier 比普通值大 100 倍,那么 被 outlier 拉大。普通值 可能变成:
于是:
结果:大量非 outlier 信息被压成 0,模型性能崩溃。
关键直觉:outlier 虽然稀少,但对 perplexity 和模型拟合很重要;这正是 mixed precision 的机会。
8. Compression Efficiency 与 Huffman Coding 类比【重要程度:★★★☆☆】
信息论中,若第 个符号出现概率为 ,分配码长为 ,期望长度为:
目标是最小化 。
直觉:
| 事件 | 编码策略 |
|---|---|
| 高频普通值 | 短码 / 低精度 |
| 低频重要 outlier | 长码 / 高精度 |
这对应 mixed precision quantization:大多数普通 activation 用 INT8,少数 outlier 用 FP16。
9. LLM.int8:outlier 单独保留高精度【重要程度:★★★★☆】
矩阵乘法:
可分解成 row-column dot products:
LLM.int8 的核心:
- 普通 activation 和 weights 用 INT8。
- outlier activation dimensions 保留 FP16。
- 最后把 INT8 core result 和 FP16 outlier correction 相加。
可以抽象为:
其中:
走 INT8 路径;
走 FP16 路径。
课件强调,outlier activation 可比其他维度大 20 倍,但只占总量很小比例,所以保留 FP16 的成本可控。
10. SmoothQuant:把 activation outlier “迁移”到 weight【重要程度:★★★★☆】
观察:
| 张量 | 特征 |
|---|---|
| Activation | outlier 大,难量化 |
| Weight | 分布平坦,容易量化 |
SmoothQuant 的核心等价变换:
插入一个 per-channel smoothing factor :
定义:
于是:
数学上输出不变,但数值分布改变:
| 变量 | 变化 |
|---|---|
| activation 被 smoothing,outlier 被压低 | |
| weight 变 sharper,吸收 outlier 难度 | |
| 保持不变 |
直觉:activation 很难量化,weight 比较好量化,所以把量化难度从 activation 转移到 weight。课件称这是“migrate quantization bandwidth”。
11. Pruning:删除冗余【重要程度:★★★★☆】
Pruning 的目标是删除“不重要”的参数或结构。
| 类型 | 删除对象 | 优点 | 缺点 |
|---|---|---|---|
| Unstructured pruning | 单个 weight | 参数量减少 | GPU 难加速 |
| Structured pruning | neuron/channel/head/block | 真实加速 | 更容易损失精度 |
课件强调,unstructured pruning 会产生散乱零值,GPU 很难跳过;structured pruning 让剩余部分形成更小的 dense matrix,因此有真实 wall-clock speedup。
12. Activation Pruning 与 WANDA【重要程度:★★★★☆】
只看 weight magnitude 不够。例子:
内积:
虽然 ,但第一维贡献更大:
所以 pruning 重要性应该同时考虑 weight 和 activation。
WANDA score:
符号定义:
| 符号 | 含义 |
|---|---|
| 第 个输出通道、第 个输入维度的权重 | |
| calibration data 上第 个 activation dimension | |
| 第 个 activation dimension 的统计强度 | |
| 权重 的重要性分数 |
直觉:一个 weight 本身大不一定重要;它连接的 activation 经常大,才重要。
13. OBS:Optimal Brain Surgeon【重要程度:★★★☆☆】
OBS 试图在删掉某个 weight 后,调整其他 weight 来最小化 loss 增加。
课件给出目标:
约束:
含义是:第 个权重要被剪到 0。
符号定义:
| 符号 | 含义 |
|---|---|
| 删除第 个 weight 后,对所有权重的补偿更新 | |
| Hessian 矩阵,描述 loss 曲率 | |
| 拉格朗日乘子 | |
| 第 个坐标的 one-hot 向量 | |
| 被剪掉的权重 |
OBS 给出的代价:
最优删除对象:
补偿更新:
直觉:剪掉一个参数会影响其他参数;OBS 不只是把它置零,还用 Hessian 估计怎么调整剩余参数来最小化损失。
14. Iterative Pruning【重要程度:★★★☆☆】
一次性剪太多会严重掉点,所以 iterative pruning 采用循环:
text
计算重要性分数
→ 剪掉最低分 blocks
→ fine-tune/retrain 恢复性能
→ 重复直到达到压缩目标若把权重矩阵分成 block,第 个 block 的重要性可用 L2 norm:
分数低的 block 被 mask 成 0。
15. 2:4 Structured Sparsity【重要程度:★★★★☆】
随机稀疏对 GPU 不友好;完全 structured pruning 又太粗糙。因此 2:4 sparsity 是折中:
每连续 4 个 horizontal elements 中,至少 2 个为 0。
例如:
保留两个最大或最重要的元素。
压缩格式:
| 组成 | 作用 |
|---|---|
| Data matrix | 只存每 4 个元素中的 2 个非零值 |
| Indices metadata | 用 2-bit index 记录非零位置 |
优点:NVIDIA Ampere Sparse Tensor Core 可以利用这种固定 pattern 跳过零值,实现更高吞吐。课件强调其优点包括 efficient memory access、low-overhead compressed format、2x math throughput。
16. Distillation:从 Teacher 到 Student【重要程度:★★★★☆】
蒸馏目标:
用小模型 student 模仿大模型 teacher 的行为。
Teacher-student 框架:
| 角色 | 含义 |
|---|---|
| Teacher | 大而强的预训练模型 |
| Student | 小而快的模型 |
| Knowledge | 输入-输出关系、概率分布、内部行为 |
为什么需要蒸馏?课件指出,pruning 往往难以保留 LLM 的复杂推理能力,而蒸馏是一种 knowledge transfer。
Hard label vs Soft label
Hard label:
只告诉 student 正确类别。
Soft label:
包含 teacher 对其他类别的相似性判断,即 dark knowledge。
例如 horse 对 deer 有小概率,因为二者共享四足、体型等结构相似性;但 car 的概率接近 0。
Temperature Softmax
普通 softmax:
带 temperature:
其中:
| 符号 | 含义 |
|---|---|
| 第 类 logit | |
| temperature | |
| 温度缩放后的概率 |
当 时,logits 被压小,分布更平滑,entropy 更高:
dominant class 不再过度接近 1,非目标类别的概率差异更容易被 student 学到。
常见蒸馏 loss:
其中:
| 符号 | 含义 |
|---|---|
| student 对真实标签的交叉熵 | |
| teacher/student soft distribution 的 KL divergence | |
| teacher temperature distribution | |
| student temperature distribution | |
| hard label loss 和 distillation loss 的权重 |
Distillation 优点:student 可以是完全不同架构;例如 DistilBERT 更小更快,同时保留大部分 BERT 性能。缺点:需要训练 student,且 teacher 训练期间要可用。
总结对比表
| 技术 | 解决的问题 | 核心方法 | 代价/风险 | 重要程度 |
|---|---|---|---|---|
| KV Cache | 重复计算历史 K,V | 缓存历史 K,V | 显存线性增长 | ★★★★★ |
| FlashAttention | attention matrix I/O 太大 | block-wise online softmax | 不直接解决 KV cache 大小 | ★★★★☆ |
| MQA/GQA | KV heads 太多 | 多个 query heads 共享 K,V | 表达能力可能下降 | ★★★★☆ |
| PagedAttention | KV cache 碎片 | block table 虚拟化管理 | 系统实现复杂 | ★★★★☆ |
| Chunked Prefill | GPU bubbles | prefill 切块,decode piggyback | 调度复杂 | ★★★☆☆ |
| StreamingLLM | 长上下文 OOM | sink tokens + recent window | 远距离语义仍可能丢失 | ★★★★☆ |
| PTQ | 快速压缩模型 | 训练后量化 | 低 bit 掉点 | ★★★★★ |
| QAT | 低 bit 精度恢复 | fake quant + STE | 训练成本高 | ★★★★★ |
| LLM.int8 | outlier 破坏 INT8 | outlier FP16,普通值 INT8 | mixed precision 实现复杂 | ★★★★☆ |
| SmoothQuant | activation outlier 难量化 | 把难度迁移到 weight | 需要校准和平滑参数 | ★★★★☆ |
| Pruning | 参数冗余 | 删除不重要结构/权重 | 可能损伤能力 | ★★★★☆ |
| 2:4 Sparsity | 稀疏难加速 | 固定 pattern 稀疏 | 硬件相关 | ★★★★☆ |
| Distillation | 小模型保留大模型能力 | teacher-student 学习 | 需要训练和 teacher | ★★★★☆ |
期末高频考点
最该背熟的是这些:
- Attention 公式:
- KV cache 大小:
- Quantization:
- STE:
- WANDA:
- SmoothQuant:
- Distillation softmax with temperature:
这两讲可以用一句话串起来:Lecture 20 是“推理系统层面怎么让 LLM 跑得动”,Lecture 21 是“模型表示层面怎么让 LLM 变小”。