Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

大语言模型 / Large Language Models

1. LLM 前置知识学习路线 / A Prerequisite Learning Path for Large Language Models

2. 神经网络基础 - 从零理解 AI 的"计算单元" / Neural Network Fundamentals from Artificial Neurons

3. Token 与上下文窗口 - LLM 的计费与记忆单位 / Tokens and Context Windows as the Units of LLM Cost and Memory

4. 解码策略 - 控制 LLM 输出的艺术 / Decoding Strategies for Controlling LLM Output

5. 消息角色 - 构建 Agent 对话的基础 / Message Roles as the Foundation of Agent Conversations

6. 流式输出 - 实时交互的体验优化 / Streaming Output for Responsive Interaction

7. Prompt 工程基础 - 与 LLM 高效对话的技巧 / Prompt Engineering Fundamentals for Effective LLM Interaction

8. LLM 进化史 - 从词向量到 Transformer / The Evolution of LLMs from Word Embeddings to Transformers

9. Transformer 手动计算:从 Attention 到 Encoder-Decoder 完整数据流

10. Transformer 核心原理 - 现代 LLM 的基石 / Transformer Fundamentals Behind Modern LLMs

11. Decoder-Only LLM 深度解析:为什么扔掉 Encoder,以及 KV Cache 如何工作

12. 训练 vs 推理:同一个 Transformer,两条完全不同的执行路径

13. Transformer 训练阶段计算详解 - 手算每一行矩阵 / Transformer Training Computation Matrix by Matrix

14. Transformer 推理阶段详解 — 模型如何"思考"并生成回答 / Transformer Inference and Autoregressive Generation

15. 训练基础扫盲 - 理解 Fine-tune 在做什么 / A Training Primer for Understanding Fine-Tuning

16. LLM 预训练全景:数据管道、Scaling Laws 与训练稳定性

17. 训练基础设施 - 从单卡到千卡集群 / Training Infrastructure from One GPU to Thousand-GPU Clusters

18. Post-Training Pipeline - 从 Base Model 到可用助手 / The Post-Training Pipeline from Base Model to Assistant

19. SFT 深度解析:从 Base Model 到指令跟随——后训练第一步 / SFT Deep Dive: Teaching Base Models to Follow Instructions

20. RLHF 深度解析:从 Reward Model 到 PPO 的完整对齐流程

21. DPO 与对齐方法:从 RLHF 复杂度到直接偏好优化

22. 研究视角:DL/RL 理论到 LLM 训练的完整映射

本页目录

Decoder-Only LLM 深度解析:为什么扔掉 Encoder,以及 KV Cache 如何工作 ​

📅 创建时间:2026-07-29 🏷️ 标签:#DecoderOnly #GPT #KVCache #CausalAttention #AutoRegressive #Inference 📚 前置知识:[[08-transformer-by-hand]](理解了 Q、K、V、Self-Attention、Encoder-Decoder 数据流)


📋 本章目标 ​

  • 理解 GPT/Claude/DeepSeek 为什么不需要 Encoder——以及 Encoder 到底"多余"在哪里
  • 掌握 Causal (Masked) Self-Attention 如何让一个句子变成 N 个训练样本
  • 理解训练时的一次前向传播如何并行产出所有位置的预测
  • 掌握 KV Cache 的完整原理——为什么推理时不需要重新计算过去 token 的 K 和 V
  • 手算 KV Cache 的内存占用,理解为什么 2048 token 的上下文需要 2GB 显存
  • 理解 Decoder-Only 架构为什么在 Scaling 和 In-Context Learning 上碾压 Encoder-Decoder
  • 从此看任何 LLM 架构图都能一眼认出"这是 Decoder-Only 还是 Encoder-Decoder"

第0部分:回顾——Encoder-Decoder 干了什么,GPT 要干什么完全不同的事 ​

0.1 上篇文章的核心回顾 ​

在 [[08-transformer-by-hand]] 中,我们用具体数字走完了一个完整的 Encoder-Decoder Transformer:

┌─────────────────────────────────────────────────────────────┐
│        Encoder-Decoder 做了什么(翻译 "我爱你" → "I love you")│
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Encoder(只读源语言):                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ "我""爱""你" → Embedding → Self-Attn → FFN          │   │
│  │   → 输出:每个源 token 融合了上下文信息的向量        │   │
│  │   → 这是"要被翻译的内容"的完整表示                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                            ↓                                │
│  Decoder(逐词生成目标语言):                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 1. Masked Self-Attn:看已经生成的词("<s>" → "I")  │   │
│  │ 2. Cross-Attention:                                │   │
│  │    Q 来自 Decoder("我现在要生成什么?")            │   │
│  │    K,V 来自 Encoder("源语言提供了什么信息?")      │   │
│  │    → Decoder 从 Encoder 输出中"检索"翻译线索        │   │
│  │ 3. FFN → 预测下一个词                               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  关键依赖:Encoder 和 Decoder 是两个独立的栈。              │
│  Cross-Attention 是它们唯一的交互点。                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

这个架构天然适合 seq2seq 任务——输入和输出是两个不同的序列(源语言 → 目标语言)。

0.2 GPT 要干什么? ​

GPT(Generative Pre-trained Transformer)的任务不是翻译。它是:

用户输入(Prompt):"1 + 1 = ?"
模型输出(Response):"2"
1
2

或者更复杂的:

用户输入:"请解释一下光合作用。"
模型输出:"光合作用是植物利用光能……(一段长文本)"
1
2

GPT 没有"源语言"和"目标语言"之分。 输入和输出是同一种语言的连续文本。用户的问题和模型的回答,本质上是同一个序列的不同部分。

0.3 根本问题:Encoder 在这里是多余的 ​

┌─────────────────────────────────────────────────────────────┐
│         如果强行用 Encoder-Decoder 做 GPT 的任务              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入:"1 + 1 = ?"                                          │
│  期望输出:"2"                                               │
│                                                             │
│  如果使用 Encoder-Decoder:                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Encoder 读入 "1 + 1 = ?"                            │   │
│  │   → 输出 4 个 token 的上下文表示                    │   │
│  │                                                     │   │
│  │ Decoder 从 "<s>" 开始生成                           │   │
│  │   → 通过 Cross-Attention 查询 Encoder 输出          │   │
│  │   → 生成 "2"                                        │   │
│  │                                                     │   │
│  │ 问题:                                              │   │
│  │ 1. Encoder 单独处理输入 → 额外参数、额外计算        │   │
│  │ 2. Cross-Attention 连接两个栈 → 额外参数            │   │
│  │ 3. "1+1=?" 和 "2" 本质上是同一序列——为什么要       │   │
│  │    分两个栈处理?                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  核心洞察:                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 在 "1+1=?2" 这个连续文本中,                         │   │
│  │ "1+1=?" 和 "2" 是同一个序列的前缀和后缀。            │   │
│  │                                                     │   │
│  │ 如果模型能在序列内部建立依赖关系(Causal Attention),│   │
│  │ 那 Encoder 和 Cross-Attention 都是冗余的。           │   │
│  │                                                     │   │
│  │ GPT 的答案:把整个序列放进一个栈,用 Causal          │   │
│  │ Self-Attention 让每个 token 关注前面所有 token。     │   │
│  │ 这就是 Decoder-Only。                                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

第1部分:Decoder-Only 架构——把 Encoder 扔掉,只剩一个栈 ​

1.1 两张架构图:Encoder-Decoder vs Decoder-Only ​

┌─────────────────────────────────────────────────────────────┐
│              Encoder-Decoder(原始 Transformer)              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌───────────┐         ┌───────────┐                        │
│  │  ENCODER  │         │  DECODER  │                        │
│  │  ┌─────┐  │         │  ┌─────┐  │                        │
│  │  │Self │  │         │  │Mask │  │                        │
│  │  │Attn │  │         │  │Self │  │                        │
│  │  │     │  │         │  │Attn │  │                        │
│  │  └──┬──┘  │         │  └──┬──┘  │                        │
│  │     │     │         │     │     │                        │
│  │  ┌──┴──┐  │         │  ┌──┴──┐  │                        │
│  │  │ FFN │  │         │  │Cross│  │← K,V from Encoder      │
│  │  │     │  │         │  │Attn │  │                        │
│  │  └─────┘  │         │  └──┬──┘  │                        │
│  │           │         │     │     │                        │
│  │  × N 层   │         │  ┌──┴──┐  │                        │
│  │           │         │  │ FFN │  │                        │
│  │           │         │  │     │  │                        │
│  │           │         │  └─────┘  │                        │
│  │           │         │           │                        │
│  │           │         │  × N 层   │                        │
│  └───────────┘         └───────────┘                        │
│                                                             │
│  两个独立的 Transformer 栈,通过 Cross-Attention 连接。      │
│  总参数量 = Encoder参数 + Decoder参数 + Cross-Attn参数      │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│              Decoder-Only(GPT / Claude / DeepSeek)         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │               DECODER-ONLY STACK                     │   │
│  │  ┌──────────────────────┐                            │   │
│  │  │ Masked Self-Attention│  ← 只有这一种 Attention!  │   │
│  │  │ (Causal Attention)   │    没有 Cross-Attention    │   │
│  │  └──────────┬───────────┘                            │   │
│  │             ↓                                        │   │
│  │  ┌──────────────────────┐                            │   │
│  │  │ Feed-Forward Network │                            │   │
│  │  └──────────────────────┘                            │   │
│  │                                                      │   │
│  │  × L 层(GPT-3: 96层, LLaMA-7B: 32层)              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  只有一个栈。每个 token 通过 Causal Attention              │
│  看到前面所有 token。前缀自己就是"上下文"。                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

1.2 Decoder-Only 的核心:Causal Self-Attention ​

在 Decoder-Only 中,唯一的 Attention 机制就是 Masked Self-Attention(也叫 Causal Attention)。原因是:没有 Encoder,所以没有 Cross-Attention 的 K 和 V 来源。

┌─────────────────────────────────────────────────────────────┐
│     Causal Self-Attention:每个 token 只能看到过去           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入序列:"1 + 1 = ?"(假设已分词为 5 个 token)            │
│                                                             │
│  Token 0 "1"                                                 │
│    可以看到:[1]                           ← 只有自己        │
│    用于预测:下一个 token "+"                                │
│                                                             │
│  Token 1 "+"                                                 │
│    可以看到:[1, +]                        ← 自己和之前的    │
│    用于预测:下一个 token "1"                                │
│                                                             │
│  Token 2 "1"                                                 │
│    可以看到:[1, +, 1]                     ← 前三个          │
│    用于预测:下一个 token "="                                │
│                                                             │
│  Token 3 "="                                                 │
│    可以看到:[1, +, 1, =]                  ← 前四个          │
│    用于预测:下一个 token "?"                                │
│                                                             │
│  Token 4 "?"                                                 │
│    可以看到:[1, +, 1, =, ?]              ← 全部五个         │
│    用于预测:下一个 token "2"                                │
│                                                             │
│  规律:位置 i 的 token 只能 attend 位置 0,1,...,i。          │
│  这是通过 Causal Mask(下三角矩阵)实现的。                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

1.3 Causal Mask 的矩阵形式 ​

┌─────────────────────────────────────────────────────────────┐
│            Causal Attention Mask(5 个 token 的例子)         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Attention 分数矩阵(Softmax 之前,加上 Mask 之后):        │
│                                                             │
│          Token0   Token1   Token2   Token3   Token4          │
│  Token0 [  s₀₀  │  -∞   │  -∞   │  -∞   │  -∞   ]          │
│  Token1 [  s₁₀  │  s₁₁  │  -∞   │  -∞   │  -∞   ]          │
│  Token2 [  s₂₀  │  s₂₁  │  s₂₂  │  -∞   │  -∞   ]          │
│  Token3 [  s₃₀  │  s₃₁  │  s₃₂  │  s₃₃  │  -∞   ]          │
│  Token4 [  s₄₀  │  s₄₁  │  s₄₂  │  s₄₃  │  s₄₄  ]          │
│                                                             │
│  上三角 = -∞ → Softmax 后变为 0                             │
│  下三角(含对角线)= 正常分数 → Softmax 后非零               │
│                                                             │
│  结果:Attention Weights 矩阵也是下三角的。                  │
│  每一行的有效权重之和 = 1.0(只在允许的位置上分配)。        │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 核心认知:Causal Mask 不是"可有可无的技巧"——          │   │
│  │ 它是 Decoder-Only 的根基。                            │   │
│  │                                                     │   │
│  │ 训练时:防止模型"作弊"看到答案                        │   │
│  │ 推理时:自然地只能看到已生成的 token                  │   │
│  │ 同一个 Mask,同时服务于训练和推理!                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

1.4 前缀即上下文——Decoder-Only 的核心哲学 ​

┌─────────────────────────────────────────────────────────────┐
│          Decoder-Only 的"上下文"就是序列的前缀               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  在 Encoder-Decoder 中:                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ "上下文" = Encoder 的输出(一个独立的表示)          │   │
│  │ 上下文和生成的 token 是分开处理的                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  在 Decoder-Only 中:                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ "上下文" = 当前 token 之前的所有 token               │   │
│  │ 上下文和生成的 token 在同一个序列中                  │   │
│  │                                                     │   │
│  │ 输入:"请翻译成英文:我爱你"                         │   │
│  │ 模型将其视为一个序列,生成续写:                     │   │
│  │   "请翻译成英文:我爱你" → "I" → "love" → "you"     │   │
│  │                                                     │   │
│  │ 用户的指令("请翻译…")和模型的回答("I love you")  │   │
│  │ 在模型中走的是同一个栈——它们没有任何架构上的区别。   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  这就是为什么 Few-Shot Prompting 天然有效:                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ "把下面的词翻译成英文:                              │   │
│  │  猫 → cat                                           │   │
│  │  狗 → dog                                           │   │
│  │  鸟 → "                                             │   │
│  │                                                     │   │
│  │ 前面的 "猫→cat" 和 "狗→dog" 通过 Causal Attention   │   │
│  │ 影响最后 "鸟→" 位置的表示——这就是 In-Context         │   │
│  │ Learning 的机制。不需要更新参数,前面的 token 的     │   │
│  │ K 和 V 直接在 Attention 中影响后面的预测。           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

第2部分:GPT 的训练——一句话提供 N 个训练样本 ​

2.1 Decoder-Only 的训练效率是 Encoder-Decoder 的杀手锏 ​

┌─────────────────────────────────────────────────────────────┐
│      一句话 "1+1=2" 如何变成 4 个训练样本(一次前向传播)     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入序列(Token IDs):[1, +, 1, =, 2]                      │
│  目标序列(shifted right):[+, 1, =, 2, <eos>]              │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                     │   │
│  │ Position 0: 看到 [1]                                │   │
│  │   → 模型预测下一个 token → 目标:"+"               │   │
│  │   → Loss₀ = CrossEntropy(预测, "+")                │   │
│  │                                                     │   │
│  │ Position 1: 看到 [1, +]                             │   │
│  │   → 模型预测下一个 token → 目标:"1"               │   │
│  │   → Loss₁ = CrossEntropy(预测, "1")                │   │
│  │                                                     │   │
│  │ Position 2: 看到 [1, +, 1]                          │   │
│  │   → 模型预测下一个 token → 目标:"="               │   │
│  │   → Loss₂ = CrossEntropy(预测, "=")                │   │
│  │                                                     │   │
│  │ Position 3: 看到 [1, +, 1, =]                       │   │
│  │   → 模型预测下一个 token → 目标:"2"               │   │
│  │   → Loss₃ = CrossEntropy(预测, "2")                │   │
│  │                                                     │   │
│  │ Position 4: 看到 [1, +, 1, =, 2]                    │   │
│  │   → 模型预测下一个 token → 目标:"<eos>"           │   │
│  │   → Loss₄ = CrossEntropy(预测, "<eos>")            │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  总 Loss = (Loss₀ + Loss₁ + Loss₂ + Loss₃ + Loss₄) / 5    │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 关键:这 5 个位置的预测是并行的!                    │   │
│  │                                                     │   │
│  │ 一次前向传播 → 所有位置的 hidden states →            │   │
│  │ 所有位置的预测分布 → 所有位置的 Loss                 │   │
│  │                                                     │   │
│  │ 因为有 Causal Mask,位置 i 的计算只依赖位置 0~i。    │   │
│  │ 这个依赖关系在 Attention 矩阵中自然满足——            │   │
│  │ 不需要串行计算!矩阵乘法一步到位!                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

2.2 和 Encoder-Decoder 的训练效率对比 ​

┌─────────────────────────────────────────────────────────────┐
│          训练效率:Decoder-Only vs Encoder-Decoder           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Encoder-Decoder(翻译任务):                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 需要"配对数据":每个训练样本 = (源句子, 目标句子)    │   │
│  │ 例如:("我爱你", "I love you")                      │   │
│  │                                                     │   │
│  │ 源句子 → Encoder(一次前向)                        │   │
│  │ 目标句子 → Decoder(一次前向,Teacher Forcing)     │   │
│  │                                                     │   │
│  │ 一个样本对 → 目标句子长度 N 个训练信号               │   │
│  │ 需要人工标注的平行语料(昂贵)                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Decoder-Only(GPT 风格):                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 只需要"原始文本":任何文本就是训练数据!             │   │
│  │ 例如:"1+1=2" 本身就是训练样本                      │   │
│  │                                                     │   │
│  │ 序列 → Decoder(一次前向,Causal Mask)             │   │
│  │                                                     │   │
│  │ 一条长度为 N 的文本 → N-1 个训练信号                 │   │
│  │ 数据来源:整个互联网(几乎免费)                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  实际影响:                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ GPT-3 的训练数据:~500B tokens(来自网页、书籍等)   │   │
│  │ 如果用 Encoder-Decoder:需要 500B 个"输入-输出"对   │   │
│  │ → 根本不可能标注这么多配对数据。                     │   │
│  │                                                     │   │
│  │ Decoder-Only 的一个关键优势:                        │   │
│  │ 互联网上的每一段文本,无需任何人工标注,              │   │
│  │ 就是 N 个"next token prediction"训练样本。          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

2.3 训练的具体流程(用数字说话) ​

┌─────────────────────────────────────────────────────────────┐
│         GPT 训练的一次 Step(手算级别的具体数字)             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  假设:                                                      │
│  • 序列长度 N = 2048 tokens(GPT-3 的上下文窗口)           │
│  • 词表大小 V = 50257(GPT-2/3 的词表)                     │
│  • d_model = 4096(GPT-3 的隐藏维度,不过实际是 12288)     │
│  • 层数 L = 96(GPT-3 175B)                                │
│                                                             │
│  一步训练的计算量:                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 1. Embedding: 2048 × 4096 → 查表,O(N × d_model)    │   │
│  │ 2. 每层 Self-Attention:                             │   │
│  │    Q=XWq, K=XWk, V=XWv: 3 × (2048×4096)×(4096×4096)│   │
│  │    QK^T: (2048×4096) × (4096×2048)                  │   │
│  │    Attn×V: (2048×2048) × (2048×4096)                │   │
│  │    → O(N² × d_model) per layer                      │   │
│  │ 3. FFN: 2048 × 4096 × (4×4096) × 4096              │   │
│  │    → O(N × d_model²) per layer                      │   │
│  │ 4. 最后一层 → Linear(V) → Softmax → CrossEntropy   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  关键观察:Self-Attention 的 O(N² × d_model) 是瓶颈。       │
│  但训练时 N² 没法避免——每个 token 需要 attend 所有 token。  │
│                                                             │
│  不过,CUDA 的矩阵乘法高度并行化,2048² 的矩阵乘法           │
│  在现代 GPU 上很快(几毫秒)。真正的瓶颈在推理阶段。         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

第3部分:KV Cache——推理时不需要重新计算过去 ​

这是整个 Decoder-Only LLM 推理优化的核心。如果你只记住一个概念,记这个。

3.1 问题:没有 KV Cache,推理会慢到什么程度? ​

┌─────────────────────────────────────────────────────────────┐
│         Auto-Regressive 生成:为什么 O(N²) 的暴力方案不可行  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  假设模型已经训练好了。现在用户输入 "1+1=?",模型要生成 "2"。│
│                                                             │
│  输入序列:[1, +, 1, =, ?]          (5 个 token)            │
│                                                             │
│  Step 1: 前向传播整个序列 [1, +, 1, =, ?]                   │
│    → 计算所有 5 个 token 的 Q, K, V                         │
│    → 最后一个位置的输出 → 预测 token "2"                    │
│    → Attention 计算量:5² = 25 个点积                       │
│                                                             │
│  Step 2: 拼上 "2",输入变为 [1, +, 1, =, ?, 2] (6 token)    │
│    → 重新计算所有 6 个 token 的 Q, K, V                     │
│    → 最后一个位置的输出 → 预测 <eos>                        │
│    → Attention 计算量:6² = 36 个点积                       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 问题:Step 1 已经算过前 5 个 token 的 K 和 V 了!   │   │
│  │ Step 2 又算了一遍——完全一样的计算!                 │   │
│  │                                                     │   │
│  │ 对于生成长度为 M 的序列(M 可以到几千):            │   │
│  │                                                     │   │
│  │   总 Attention 计算量 ≈ Σ(i²) for i=1..M             │   │
│  │                      ≈ M³ / 3                        │   │
│  │                                                     │   │
│  │  生成 2048 token:需要 ~2.86 亿次点积               │   │
│  │  相当于在推理时走了一个 O(M³) 的算法                 │   │
│  │  → 完全不可接受!                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

3.2 KV Cache 的核心思想:过去的 K 和 V 不会变 ​

┌─────────────────────────────────────────────────────────────┐
│    为什么过去 token 的 K 和 V 永远不会变?(关键洞察)        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  回忆 Attention 的计算:                                     │
│                                                             │
│    对于位置 i:                                              │
│      Q_i = X'_i × Wq                                       │
│      K_j = X'_j × Wk    (所有位置 j)                      │
│      V_j = X'_j × Wv    (所有位置 j)                      │
│      Score_i = Q_i × [K_0, K_1, ..., K_i]^T                │
│      Output_i = softmax(Score_i) × [V_0, V_1, ..., V_i]    │
│                                                             │
│  关键:在 Causal Attention 下,                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                     │   │
│  │  位置 j 的 K_j 和 V_j 只取决于 X'_j                 │   │
│  │  而 X'_j 只取决于输入序列 0~j 位置                   │   │
│  │                                                     │   │
│  │  当我们在 Step 2 追加了新 token 后:                 │   │
│  │  • 新 token 会影响 Q_k(k > j)                    │   │
│  │  • 但新 token 不会影响旧的 K_j 和 V_j              │   │
│  │  • 因为 Causal Attention:后面 token 不影响前面     │   │
│  │                                                     │   │
│  │  所以:旧的 K 和 V → 永远不变!                     │   │
│  │  只需计算一次,然后缓存起来。                        │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  这和 Encoder-Decoder 的关键区别:                           │
│  • Encoder-Decoder 中,Decoder 的所有位置之间也有 Causal    │
│    Mask,所以同样的推理也适用。                              │
│  • 但 Encoder 中的 K,V 也需要缓存(因为每次生成新 token     │
│    时要重新做 Cross-Attention)。KV Cache 不是 Decoder-     │
│    Only 独有的,但在 Decoder-Only 中对性能至关重要。        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

3.3 KV Cache 的图解——有 Cache 和没 Cache 的天壤之别 ​

┌─────────────────────────────────────────────────────────────┐
│              没有 KV Cache:每一步都重新计算一切              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Step 1: 输入 [1, +, 1, =, ?]                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 计算 Q₀K₀ Q₀K₁ Q₀K₂ Q₀K₃ Q₀K₄  ← 5 个 K 都是新算的 │   │
│  │ 计算 Q₁K₀ Q₁K₁ Q₁K₂ Q₁K₃ Q₁K₄                       │   │
│  │ 计算 Q₂K₀ Q₂K₁ Q₂K₂ Q₂K₃ Q₂K₄                       │   │
│  │ 计算 Q₃K₀ Q₃K₁ Q₃K₂ Q₃K₃ Q₃K₄                       │   │
│  │ 计算 Q₄K₀ Q₄K₁ Q₄K₂ Q₄K₃ Q₄K₄  ← 25 次点积          │   │
│  │ → 输出 token "2"                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Step 2: 输入 [1, +, 1, =, ?, 2]                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 计算 Q₀K₀ ... Q₀K₅  ← K₀~K₄ 又算了一遍(浪费!)    │   │
│  │ 计算 Q₁K₀ ... Q₁K₅  ← K₀~K₄ 又算了一遍              │   │
│  │ 计算 Q₂K₀ ... Q₂K₅  ← K₀~K₄ 又算了一遍              │   │
│  │ 计算 Q₃K₀ ... Q₃K₅  ← K₀~K₄ 又算了一遍              │   │
│  │ 计算 Q₄K₀ ... Q₄K₅  ← K₀~K₄ 又算了一遍              │   │
│  │ 计算 Q₅K₀ Q₅K₁ Q₅K₂ Q₅K₃ Q₅K₄ Q₅K₅  ← 36 次点积   │   │
│  │ → 输出 <eos>                                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  总点积次数(仅 Attention):25 + 36 = 61                    │
│  生成 M token 需要 O(M²) 次点积                             │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│              有 KV Cache:只计算新 token 的 Q,K,V            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Step 1 (Prefill): 输入 [1, +, 1, =, ?]                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 计算所有 5 个 token 的 K,V → 存入 Cache              │   │
│  │ Cache = {K₀,V₀, K₁,V₁, K₂,V₂, K₃,V₃, K₄,V₄}        │   │
│  │                                                     │   │
│  │ 计算 Q₄ × [K₀,K₁,K₂,K₃,K₄]^T(只取最后一行的 Q)    │   │
│  │ → 输出 token "2"                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Step 2 (Decode): 输入只有新 token "2"                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 只计算新 token "2" 的 Q₅, K₅, V₅                    │   │
│  │ 新 K₅,V₅ 追加到 Cache                               │   │
│  │ Cache = {..., K₄,V₄, K₅,V₅}                         │   │
│  │                                                     │   │
│  │ 计算 Q₅ × [K₀,K₁,K₂,K₃,K₄,K₅]^T(只 1 个 Q!)     │   │
│  │ → 使用 Cache 中的 K₀~K₄(不重新计算!)             │   │
│  │ → 只有 5+1=6 次点积                                 │   │
│  │ → 输出 <eos>                                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 总点积次数:25 (prefill) + 6 (per decode step)     │   │
│  │                                                     │   │
│  │ 无 KV Cache:O(M²) per step → 总 O(M³)             │   │
│  │ 有 KV Cache:O(1) per step  → 总 O(M²)             │   │
│  │                                                     │   │
│  │ 生成 2048 token:                                    │   │
│  │   无 Cache → ~286M 点积                              │   │
│  │   有 Cache → ~4M 点积                                │   │
│  │   → 加速 70 倍!                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

3.4 KV Cache 的内存占用——为什么上下文长度是瓶颈 ​

┌─────────────────────────────────────────────────────────────┐
│              KV Cache 内存计算(手算级别)                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  对于一层、一个头:                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 每个 token 需要存储:                                │   │
│  │   K: 一个长度为 d_k 的向量                           │   │
│  │   V: 一个长度为 d_k 的向量                           │   │
│  │                                                     │   │
│  │  对于序列长度 n:                                    │   │
│  │    一层一个头的 KV Cache = n × 2 × d_k 个浮点数     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  扩展到所有层、所有头:                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ KV Cache 大小 = 2 × L × n × h × d_k × bytes_per_val│   │
│  │                                                     │   │
│  │  其中:                                              │   │
│  │    L = 层数                                         │   │
│  │    n = 序列长度                                      │   │
│  │    h = 每层的注意力头数                              │   │
│  │    d_k = 每个头的 K/V 维度                          │   │
│  │                                                     │   │
│  │  注意:h × d_k = d_model(头的总维度 = 模型维度)   │   │
│  │  所以可以简化为:                                    │   │
│  │    KV Cache 大小 = 2 × L × n × d_model × bytes     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  以 LLaMA-7B 为例(FP16 推理):                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ L = 32 层                                           │   │
│  │ d_model = 4096                                      │   │
│  │ n = 2048 tokens                                     │   │
│  │ bytes_per_val = 2(FP16)                           │   │
│  │                                                     │   │
│  │ KV Cache = 2 × 32 × 2048 × 4096 × 2                │   │
│  │          = 2 × 32 × 2048 × 4096 × 2                │   │
│  │          = 1,073,741,824 bytes                      │   │
│  │          ≈ 1.0 GB                                   │   │
│  │                                                     │   │
│  │ n = 4096 → ~2.0 GB                                  │   │
│  │ n = 8192 → ~4.0 GB                                  │   │
│  │ n = 32768 → ~16 GB                                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  以 LLaMA-70B 为例(FP16,d_model=8192, L=80):                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ n = 2048 → 2 × 80 × 2048 × 8192 × 2                │   │
│  │          ≈ 5.4 GB                                   │   │
│  │                                                     │   │
│  │ n = 4096 → ~10.7 GB                                 │   │
│  │ → 这就是为什么长上下文推理需要大显存 GPU!           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 关键认知:                                           │   │
│  │                                                     │   │
│  │ KV Cache 的大小随序列长度 n 线性增长。               │   │
│  │ 这看起来还好——但加上模型参数本身占用的显存后,        │   │
│  │ 内存很容易成为瓶颈。                                 │   │
│  │                                                     │   │
│  │ LLaMA-7B 模型参数(FP16):~14 GB                    │   │
│  │ + KV Cache (n=2048):~1 GB                           │   │
│  │ → 总计 ~15 GB,刚好放进一张 24GB 的 RTX 4090         │   │
│  │                                                     │   │
│  │ 但如果 n=8192:KV Cache ~4 GB,总计 ~18 GB           │   │
│  │ → 仍然可行,但余量变小                               │   │
│  │                                                     │   │
│  │ 这就是 GQA(Grouped Query Attention)和 MQA          │   │
│  │ (Multi-Query Attention)存在的意义——减少 KV Cache   │   │
│  │ 的大小!                                             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

3.5 Prefill 和 Decode——KV Cache 的两个阶段 ​

┌─────────────────────────────────────────────────────────────┐
│            Prefill 阶段 vs Decode 阶段                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Prefill(预填充)阶段:                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 输入:用户的完整 Prompt(可能很长,如 2000 token)   │   │
│  │                                                     │   │
│  │ 操作:                                               │   │
│  │  1. 一次性前向传播整个 Prompt                       │   │
│  │  2. 所有 token 的 K,V 存入 Cache                    │   │
│  │  3. 拿到最后一个位置的 hidden state                 │   │
│  │  4. 预测第一个输出 token                            │   │
│  │                                                     │   │
│  │ 特点:                                               │   │
│  │  • 计算量大(处理 N 个 token 并行)                  │   │
│  │  • 受 Compute Bound 限制(GPU 算力瓶颈)            │   │
│  │  • 但只做一次!                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Decode(逐 token 生成)阶段:                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 输入:每次只有 1 个新 token                          │   │
│  │                                                     │   │
│  │ 操作:                                               │   │
│  │  1. 只计算这个 token 的 Q, K, V                     │   │
│  │  2. 新 K,V 追加到 Cache                             │   │
│  │  3. Attention:新 Q × 所有缓存的 K                   │   │
│  │  4. 预测下一个 token                                │   │
│  │                                                     │   │
│  │ 特点:                                               │   │
│  │  • 计算量小(每次只处理 1 个 token)                 │   │
│  │  • 受 Memory Bandwidth Bound 限制                   │   │
│  │    (大部分时间在从显存读 KV Cache!)               │   │
│  │  • 重复 M 次(M = 生成长度)                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  速度分析:                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Prefill:2000 token → 一次前向,约 50-200ms(GPU)  │   │
│  │ Decode:每 token 约 10-50ms                         │   │
│  │                                                     │   │
│  │ 生成 500 token 的回答:                              │   │
│  │   Prefill: 100ms                                     │   │
│  │   Decode:  500 × 20ms = 10,000ms                    │   │
│  │   总计:    ~10 秒                                    │   │
│  │                                                     │   │
│  │ → 生成阶段(Decode)占总时间的 ~99%!                │   │
│  │ → "为什么 ChatGPT 输出这么慢"的答案就在这里          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

3.6 GQA 和 MQA——减少 KV Cache 的工业级优化 ​

┌─────────────────────────────────────────────────────────────┐
│     MHA vs MQA vs GQA:KV Cache 大小的权衡                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  MHA (Multi-Head Attention):标准的多头注意力                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 每个头有独立的 K,V → h 组 K,V 要缓存                │   │
│  │ KV Cache = 2 × L × n × h × d_k                     │   │
│  │                                                     │   │
│  │ 示例:LLaMA-7B, h=32, d_k=128                      │   │
│  │ → 每层每个 token: 32 × 128 × 2 = 8192 个 float     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  MQA (Multi-Query Attention):所有头共享同一组 K,V           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 只有 1 组 K,V 要缓存                                │   │
│  │ KV Cache = 2 × L × n × 1 × d_k                     │   │
│  │                                                     │   │
│  │ 节省:h 倍!                                         │   │
│  │ 代价:表达能力有所下降                               │   │
│  │                                                     │   │
│  │ 使用者:PaLM                                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  GQA (Grouped Query Attention):折中方案                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 把 h 个头分成 g 组,每组共享 K,V                     │   │
│  │ KV Cache = 2 × L × n × g × d_k                     │   │
│  │                                                     │   │
│  │ 示例:LLaMA-2 70B, h=64, g=8                        │   │
│  │ → 节省 8 倍 KV Cache!                               │   │
│  │                                                     │   │
│  │ 使用者:LLaMA-2, LLaMA-3, Mistral, DeepSeek-V2      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  对比图:                                            │   │
│  │                                                     │   │
│  │  MHA: Q₀→K₀V₀  Q₁→K₁V₁  Q₂→K₂V₂  Q₃→K₃V₃         │   │
│  │        独立的 4 组 K,V 缓存                         │   │
│  │                                                     │   │
│  │  MQA: Q₀→┐                                          │   │
│  │        Q₁→├─K₀V₀                                    │   │
│  │        Q₂→┤   所有 Q 共享 1 组 K,V                  │   │
│  │        Q₃→┘                                          │   │
│  │                                                     │   │
│  │  GQA: Q₀→┐                                          │   │
│  │        Q₁→├─K₀V₀  (Group 0: 2 个头共享)            │   │
│  │        Q₂→┐                                          │   │
│  │        Q₃→├─K₁V₁  (Group 1: 2 个头共享)            │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

第4部分:Decoder-Only 为什么赢了?——四条护城河 ​

4.1 护城河一:训练效率——互联网就是训练数据 ​

┌─────────────────────────────────────────────────────────────┐
│           训练效率:数据利用率的天壤之别                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Encoder-Decoder(以翻译为例):                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ "我爱你" → "I love you"  这是一个训练样本           │   │
│  │ 需要:源语言句子 + 目标语言句子(人工标注的平行语料)│   │
│  │ 训练信号数:3(目标句子的每个位置是一个预测目标)    │   │
│  │ 数据成本:高(需要双语标注员)                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Decoder-Only(GPT 风格):                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 任何网页、任何书、任何代码——只要是人类写的文本       │   │
│  │ → "1+1=2" 就是训练样本                              │   │
│  │ → "光合作用是植物利用光能..." 就是训练样本           │   │
│  │                                                     │   │
│  │ 训练信号数:N-1(序列中每个 token 的下一 token)     │   │
│  │ 数据成本:几乎零(互联网上取之不尽)                 │   │
│  │                                                     │   │
│  │ GPT-3 (175B) 训练数据:~500B tokens                 │   │
│  │ → 约 500B 个训练信号                                │   │
│  │                                                     │   │
│  │ 如果用 Encoder-Decoder 得到等量训练信号:            │   │
│  │ 需要 ~500B 个配对样本 → 不可能                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

4.2 护城河二:Scaling——一个架构,从 1B 到 1T ​

┌─────────────────────────────────────────────────────────────┐
│            Decoder-Only 的 Scaling 极其平滑                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  同一个架构,只改超参数:                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 模型        参数量    L     d_model  训练数据        │   │
│  │ GPT-2 Small 124M     12     768     40GB 文本       │   │
│  │ GPT-2 Medium 355M    24    1024     ...             │   │
│  │ GPT-2 Large  774M    36    1280     ...             │   │
│  │ GPT-2 XL    1.5B     48    1600     ...             │   │
│  │ GPT-3 Small  125M    12     768     ...             │   │
│  │ GPT-3 Medium 350M    24    1024     ...             │   │
│  │ GPT-3 Large  760M    24    1536     ...             │   │
│  │ GPT-3 XL     1.3B    24    2048     ...             │   │
│  │ GPT-3 6.7B    32     4096     ...                   │   │
│  │ GPT-3 13B     40     5140     ...                   │   │
│  │ GPT-3 175B    96    12288    570GB 文本             │   │
│  │                                                     │   │
│  │ 都是 Decoder-Only + Causal Self-Attention + FFN!   │   │
│  │ 架构完全不变,只是把矩阵变大、层数变多。             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  为什么 Scaling 友好?                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 1. 只有一种 Attention 机制 → 超参数少、调参简单     │   │
│  │ 2. Causal Mask 是确定性的 → 不需要调 Mask 策略      │   │
│  │ 3. 没有 Cross-Attention → 不需要平衡两个栈的大小    │   │
│  │ 4. 所有位置的计算是对称的 → 负载均衡,GPU 利用率高  │   │
│  │                                                     │   │
│  │ 相比之下,Encoder-Decoder 需要决定:                 │   │
│  │ • Encoder 几层?Decoder 几层?                      │   │
│  │ • Cross-Attention 在哪里?每层都有还是只有几层?    │   │
│  │ • Encoder 的 d_model 和 Decoder 一样吗?            │   │
│  │ → 超参数空间更大,Scaling 时不确定性更高            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

4.3 护城河三:In-Context Learning 自然涌现 ​

┌─────────────────────────────────────────────────────────────┐
│      In-Context Learning 是 Decoder-Only 的特有超能力        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  什么是 In-Context Learning(ICL)?                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 给模型几个示例(在 prompt 里),不更新任何参数,     │   │
│  │ 模型就能学会做对应的任务。                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  为什么 Decoder-Only 天然支持 ICL?                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                     │   │
│  │  Prompt:                                            │   │
│  │  "请把以下词翻译成英文:                            │   │
│  │   猫 → cat                                         │   │
│  │   狗 → dog                                         │   │
│  │   鸟 → "                                           │   │
│  │                                                     │   │
│  │  在 Causal Attention 中:                           │   │
│  │                                                     │   │
│  │  位置 "鸟" 的 Q 会 attend 到:                      │   │
│  │  • "猫 → cat" 的 K,V(知道"猫"的翻译模式)          │   │
│  │  • "狗 → dog" 的 K,V(知道"狗"的翻译模式)          │   │
│  │                                                     │   │
│  │  通过 Attention,示例中的模式被自动"提取"出来,     │   │
│  │  用于影响 "鸟" 位置的预测。                         │   │
│  │                                                     │   │
│  │  这是 Attention 的自然特性:                         │   │
│  │  output = Σ (attention_weight_j × V_j)              │   │
│  │                                                     │   │
│  │  如果"猫→cat"的 V 包含了对翻译模式有用的信息,      │   │
│  │  "鸟"位置的 Q 会自动给予它高 attention 权重。        │   │
│  │                                                     │   │
│  │  不需要特殊机制——这就是 Attention 本身在干活。       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Encoder-Decoder 为什么在这方面吃亏?                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 在 Encoder-Decoder 中:                              │   │
│  │                                                     │   │
│  │ 示例 "猫→cat" 和 "狗→dog" 必须通过 Cross-Attention │   │
│  │ 影响 Decoder。但 Cross-Attention 的 K,V 来自 Encoder│   │
│  │ 的固定输出,而 Encoder 是双向 Self-Attention——      │   │
│  │ 它处理的是完整的源序列。                            │   │
│  │                                                     │   │
│  │ 指令("请翻译…")和示例("猫→cat")在 Encoder 中    │   │
│  │ 被混在一起编码。Decoder 只能通过 Cross-Attention    │   │
│  │ 间接获取这些信息——信息传递路径更长、更间接。        │   │
│  │                                                     │   │
│  │ 在 Decoder-Only 中,一切都是一个序列:               │   │
│  │ 指令 → 示例1 → 示例2 → 预测位置                     │   │
│  │ Causal Attention 让信息沿着序列单向流动,            │   │
│  │ 示例的信息直接通过 Self-Attention 流入预测位置。     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

4.4 护城河四:架构简单,工程友好 ​

┌─────────────────────────────────────────────────────────────┐
│            简单就是美——Decoder-Only 的工程优势               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  一个典型的 Decoder-Only 层(伪代码):                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ def decoder_layer(x, mask):                         │   │
│  │     # 1. Causal Self-Attention                     │   │
│  │     attn_out = causal_self_attn(x, mask)           │   │
│  │     x = layer_norm(x + attn_out)                   │   │
│  │                                                     │   │
│  │     # 2. Feed-Forward Network                      │   │
│  │     ffn_out = ffn(x)                               │   │
│  │     x = layer_norm(x + ffn_out)                    │   │
│  │                                                     │   │
│  │     return x                                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  对比 Encoder-Decoder(伪代码):                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ def encoder_layer(x):                               │   │
│  │     attn_out = self_attn(x)     # 双向 Self-Attn   │   │
│  │     x = layer_norm(x + attn_out)                   │   │
│  │     ffn_out = ffn(x)                               │   │
│  │     x = layer_norm(x + ffn_out)                    │   │
│  │     return x                                        │   │
│  │                                                     │   │
│  │ def decoder_layer(x, enc_out, self_mask, cross_mask):│   │
│  │     # 1. Causal Self-Attention                     │   │
│  │     attn_out = causal_self_attn(x, self_mask)      │   │
│  │     x = layer_norm(x + attn_out)                   │   │
│  │     # 2. Cross-Attention                           │   │
│  │     cross_out = cross_attn(x, enc_out, cross_mask) │   │
│  │     x = layer_norm(x + cross_out)                  │   │
│  │     # 3. FFN                                       │   │
│  │     ffn_out = ffn(x)                               │   │
│  │     x = layer_norm(x + ffn_out)                    │   │
│  │     return x                                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  工程上的实际影响:                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 1. 实现更简单:一个 Attention 模块 vs 三种          │   │
│  │ 2. 推理代码更短:不需要维护 Encoder 的输出          │   │
│  │ 3. 分布式训练更均匀:每个 GPU 的计算量对称           │   │
│  │ 4. KV Cache 更直观:只缓存一种 Attention 的 K,V     │   │
│  │ 5. 调试更容易:问题出在哪里一目了然                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

第5部分:不是所有 LLM 都是纯 Decoder-Only——几个重要例外 ​

虽然 Decoder-Only 是绝对主流,但不是唯一的。

┌─────────────────────────────────────────────────────────────┐
│            LLM 架构的三种主要形态                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  类型 1:纯 Decoder-Only(主流中的主流)                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ GPT 系列(GPT-1/2/3/4)                             │   │
│  │ LLaMA 系列(LLaMA-1/2/3/4)                         │   │
│  │ Mistral / Mixtral                                   │   │
│  │ DeepSeek-V2 / V3                                    │   │
│  │ Claude 系列(Anthropic)                             │   │
│  │ Qwen 系列                                           │   │
│  │ Gemma                                               │   │
│  │                                                     │   │
│  │ 特点:只有 Causal Self-Attention + FFN               │   │
│  │ 比例:>95% 的现代 LLM                                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  类型 2:Encoder-Decoder(仍然在特定领域有优势)             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ T5(Google, 2019)                                  │   │
│  │ BART(Facebook, 2019)                              │   │
│  │ NLLB(Meta,多语言翻译模型,2022)                   │   │
│  │                                                     │   │
│  │ 为什么还在用?                                       │   │
│  │ • 翻译/摘要等 seq2seq 任务上效果更好                │   │
│  │ • 源序列和目标序列天然分离 → 模型结构匹配任务结构   │   │
│  │ • NLLB-200 支持 200 种语言,翻译质量仍然领先        │   │
│  │                                                     │   │
│  │ 为什么不是主流?                                     │   │
│  │ • 无法利用海量无标注文本训练                         │   │
│  │ • In-Context Learning 不如 Decoder-Only             │   │
│  │ • 训练开销更大(两个栈)                             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  类型 3:Prefix-LM(Encoder-Decoder 的变种)                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ GLM (General Language Model, 清华 & 智谱, 2022)     │   │
│  │                                                     │   │
│  │ 核心思想:                                           │   │
│  │ • 前缀部分(prompt):双向 Self-Attention           │   │
│  │ • 生成部分(response):Causal Self-Attention       │   │
│  │ • 前缀的 K,V 可以被生成部分 attend                  │   │
│  │                                                     │   │
│  │ 融合了两种注意力模式,但实现更复杂。                 │   │
│  │ ChatGLM 系列最初是 Prefix-LM,但 GLM-4 转向了       │   │
│  │ 纯 Decoder-Only——这也说明了趋势。                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  趋势判断:                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • 通用 LLM:Decoder-Only 一统天下                   │   │
│  │ • 特定 seq2seq 任务:Encoder-Decoder 仍有价值       │   │
│  │ • 未来的变体:可能融入 MoE(Mixture of Experts)    │   │
│  │   和 MLA(Multi-head Latent Attention),            │   │
│  │   但 Causal Self-Attention 的基本范式不会变。       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

5.1 DeepSeek 的特殊优化——MLA 和 KV Cache ​

┌─────────────────────────────────────────────────────────────┐
│       DeepSeek-V2/V3 的 MLA:对 KV Cache 的极致优化          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  传统的 KV Cache 问题:                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ K,V 的维度 = d_model,等于 Q 的维度                 │   │
│  │ 对于大模型(DeepSeek-V3: d_model 很大)             │   │
│  │ → KV Cache 内存占用极其庞大                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  MLA (Multi-head Latent Attention) 的解决方案:              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 核心思想:把 K 和 V 压缩到一个低维"潜在空间"        │   │
│  │                                                     │   │
│  │ 传统:K = X × Wk (d_model → d_model)                │   │
│  │       V = X × Wv (d_model → d_model)                │   │
│  │       → 缓存 d_model 维的 K,V(大)                 │   │
│  │                                                     │   │
│  │ MLA:                                               │   │
│  │       C = X × Wc (d_model → d_c, d_c << d_model)   │   │
│  │       缓存 C(很小的潜在向量)                       │   │
│  │       使用时:K = C × Wk_up (d_c → d_model)         │   │
│  │               V = C × Wv_up (d_c → d_model)         │   │
│  │                                                     │   │
│  │ 好处:只缓存 d_c 维的 C,而不是 d_model 维的 K,V!  │   │
│  │                                                     │   │
│  │ DeepSeek-V2:d_c 约是 d_model 的 1/4~1/10           │   │
│  │ → KV Cache 减少 75%~90%!                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  为什么这很重要:                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 更小的 KV Cache →                                     │   │
│  │   • 同样的显存 → 支持更长的上下文                   │   │
│  │   • 同样的上下文 → 可以用更少的显存                 │   │
│  │   • Decode 阶段更快(少读显存)                     │   │
│  │                                                     │   │
│  │ DeepSeek-V3 支持 128K 上下文,部分归功于 MLA。       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

核心总结 ​

总结1:Decoder-Only 的本质 ​

Decoder-Only 不是"砍掉了 Encoder 的 Transformer",而是重新定义了"上下文"的含义。在 Encoder-Decoder 中,上下文是一个独立的 Encoder 输出;在 Decoder-Only 中,上下文就是序列的前缀。这种设计让"指令"和"回答"在同一个序列中无缝衔接,使得 In-Context Learning 天然可能。

总结2:Causal Mask 是一切的基础 ​

Causal Mask(下三角 Mask)同时解决了两个问题:训练时防止模型"作弊"看到未来 token(Teacher Forcing),推理时自然地限制模型只能看到已生成的 token(Auto-Regressive)。一个 Mask,两种用途。

总结3:KV Cache 是推理可行性的基石 ​

没有 KV Cache,每生成一个 token 都要重新计算所有历史 token 的 K 和 V——O(M^3) 的复杂度让任何长文本生成都不现实。KV Cache 利用了 Causal Attention 的因果性(过去不受未来影响),让每个 decode step 只需要 O(1) 的计算量。KV Cache 的大小随序列长度线性增长,是限制长上下文推理的主要瓶颈。

总结4:Decoder-Only 赢在规模和数据 ​

四条护城河——数据效率(互联网即训练集)、Scaling 平滑(一个架构从 1B 到 1T)、In-Context Learning(Attention 的自然产物)、工程简单(一种 Attention 模块)——共同造就了 Decoder-Only 在通用 LLM 领域的统治地位。

总结5:KV Cache 优化的前沿 ​

从 MHA 到 MQA 到 GQA 到 MLA,KV Cache 的压缩是 LLM 推理优化的核心战场。每次减少 KV Cache 的大小,都直接转化为更长的上下文支持或更快的生成速度。


章节测试 ​

测试1:Decoder-Only 架构中,为什么不需要 Cross-Attention? ​

A. 因为 Cross-Attention 太慢了,被工程优化掉了 B. 因为没有 Encoder 输出可以提供 K 和 V——整个模型只有一个栈 C. 因为 Cross-Attention 被替换成了更大的 FFN D. 因为 Decoder-Only 使用双向 Attention,Cross-Attention 是多余的

测试2:Causal Mask 在训练和推理中分别起什么作用? ​

A. 训练中防止过拟合,推理中加速计算 B. 训练中让模型并行学习所有位置的"下一 token 预测",推理中阻止模型看到未生成的 token C. 训练中减少显存使用,推理中提高准确率 D. 训练和推理中的 Causal Mask 是不同的

测试3:为什么说 Decoder-Only 的"一句话提供 N 个训练样本"? ​

A. 因为模型会把一句话复制 N 次来训练 B. 因为序列中位置 i 的 token 预测位置 i+1 的 token,长度为 N 的序列产生 N 个训练信号,所有信号在一次前向传播中并行计算 C. 因为 Decoder-Only 使用数据增强生成 N 个变体 D. 因为每个 token 需要 N 次前向传播才能训练

测试4:KV Cache 存在的根本原因是什么? ​

A. GPU 显存太便宜了,不用白不用 B. 在 Causal Attention 中,过去 token 的 K 和 V 不受未来 token 影响,所以可以安全缓存 C. 为了减少模型参数数量 D. 为了让 Attention 计算更准确

测试5:LLaMA-7B(L=32, d_model=4096)推理时序列长度为 4096,KV Cache 大约占用多少显存(FP16)? ​

A. 0.5 GB B. 2.0 GB C. 8.0 GB D. 32 GB

测试6:GQA(Grouped Query Attention)相比 MHA(Multi-Head Attention)的主要好处是什么? ​

测试7:Prefill 阶段和 Decode 阶段各受什么硬件瓶颈限制?为什么 Decode 阶段占总时间的绝大部分? ​


参考答案 ​

测试1答案 ​

答案:B。Decoder-Only 只有一个 Transformer 栈,没有 Encoder。Cross-Attention 需要 Q 来自 Decoder、K 和 V 来自 Encoder——没有 Encoder,就没有 K 和 V 的来源。所以 Decoder-Only 只有 Masked Self-Attention(Causal Attention)。

测试2答案 ​

答案:B。Causal Mask 让位置 i 只能看到位置 0~i。训练时,这意味着所有位置可以并行计算——每个位置独立预测下一个 token,互不干扰(Teacher Forcing)。推理时,模型逐 token 生成,还没有生成的 token 自然不能被看到——Causal Mask 保证了这一点。同一个 Mask 服务于两种场景。

测试3答案 ​

答案:B。在 Decoder-Only 中,输入是序列 [t_0, t_1, ..., t_N],模型通过 Causal Mask 确保位置 i 只能看到 t_0...t_i。位置 i 的输出预测 t_{i+1}。一个长度为 N 的序列有 N 个位置,每个位置的输出都被用于计算 Loss。所有 N 个预测在一次矩阵乘法中并行完成。因此一条文本提供 N 个训练样本,训练效率极高。

测试4答案 ​

答案:B。Causal Attention 的核心特性是:位置 j 的 K_j 和 V_j 只取决于输入 X'_j,而 X'_j 只受位置 0~j 的影响。当新 token 追加到序列末尾时,它不会改变已有 token 的表示(Causal Mask 确保信息单向流动)。因此已有 token 的 K 和 V 是"不变的",可以缓存起来重复使用。这是 Attention 的因果性直接推出的结论。

测试5答案 ​

答案:B(约 2.0 GB)。

计算公式:KV Cache = 2 × L × n × d_model × 2 bytes

= 2 × 32 × 4096 × 4096 × 2 = 2,147,483,648 bytes = 2.0 GB

(如果使用 GQA 且 g < h,实际会更小。)

测试6答案 ​

MHA 中每个注意力头有独立的 K 和 V,需要缓存 h 组 K,V。GQA 把 h 个头分成 g 组(g < h),每组内的头共享同一组 K,V,只需要缓存 g 组 K,V。这直接减少了 KV Cache 的大小(减少 h/g 倍),代价是每个头失去了独立的 K,V 表示能力。实践证明 g 取适当值(如 8)时,性能损失可忽略不计。LLaMA-2/3、Mistral、DeepSeek-V2 等主流模型都使用 GQA。

测试7答案 ​

Prefill 阶段:一次性处理整个 prompt(N 个 token),计算量大(N 个 token 的 Attention 并行计算),受 Compute Bound 限制(GPU 算力是瓶颈)。

Decode 阶段:每次只处理 1 个新 token,计算量很小,但需要从显存中读取整个 KV Cache(大小随序列长度线性增长)。受 Memory Bandwidth Bound 限制(显存带宽是瓶颈)。

Decode 占总时间绝大部分的原因:虽然每个 decode step 计算量小,但需要重复 M 次(M = 生成长度),而且每次都要读取不断增长的 KV Cache。对于生成 500 token 的场景,95%+ 的时间花在 Decode 阶段。


相关笔记 ​

  • [[08-transformer-by-hand]] — Encoder-Decoder 完整数据流(手算版,本文的前置知识)
  • [[07-llm-evolution]] — 从 Word2Vec 到 Transformer 的历史演进
  • [[10-training-vs-inference]] — 训练和推理完整对比(FLOPs 计算 / 显存估算)
  • [[11-llm-architectures]] — 主流 LLM 架构全景:MoE, GQA, MLA, FlashAttention

下一步学习 ​

  • [ ] 理解 KV Cache 的代码实现——用 PyTorch 写一个带 KV Cache 的 Decoder-Only 推理循环(约 60 行),对比有无 Cache 的速度差异
  • [ ] 阅读 [[10-training-vs-inference]] — 深入理解训练和推理的 FLOPs / 显存 / 延迟完整分析
  • [ ] 阅读 LLaMA 论文的架构部分——对照本文的图,找到 GQA 和 RoPE 的实际应用
  • [ ] 思考:如果让你设计一个支持 1M 上下文窗口的 Decoder-Only 模型,KV Cache 会是最大挑战吗?还有哪些技术可以突破这个限制?

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇10. Transformer 核心原理 - 现代 LLM 的基石 / Transformer Fundamentals Behind Modern LLMs
下一篇12. 训练 vs 推理:同一个 Transformer,两条完全不同的执行路径

持续记录,持续成长

Copyright © Tidenflow