梯度流
Pre-norm vs Post-norm
CS336 · 第 3 课
从 Vaswani et al. 2017 的"Attention Is All You Need"到今天的前沿模型,Transformer 架构经历了一系列关键演化。这一课拆解现代 Transformer 的每一个组件,理解每个设计选择背后的 trade-off。
Spring 2026 官方来源:Lecture 3 PDF · 课程日程
本页是中文学习导读;讲次主题、教师与材料版本以 Stanford 官方页面和讲义为准。
本讲问题:有了 token 之后,还需要决定一个 Transformer block 如何组织归一化、attention、FFN 和位置表示;这些选择会同时影响训练稳定性、参数量和计算成本。
Transformer block 数据流 → normalization / activation / position choices → attention 与 FFN shape → 参数和 FLOPs 核算 → 超参数组合成完整模型。
本课不是记组件名,而是回答:一个 token representation 怎样经过 attention、前馈网络(feed-forward network,FFN,也常称 MLP)与 residual path,为什么某些改动让梯度、位置表达或资源账本更合理?后文统一使用 FFN。先确认你会用最后一维做 linear projection,并知道 residual addition 要求两侧 shape 完全相同。
x:[B,T,d]
├─ norm → Q,K,V → causal attention → [B,T,d] ─┐
│ ├─ residual add
└─────────────────────────────────────────────┘
↓ norm → MLP d→d_ff→d → residual add → next block
architecture choice → tensor shape → parameters / FLOPs / memory
hyperparameter value → capacity / optimization / hardware efficiency
令 \(B=2,T=4,d=8,h=2\),则每头 \(d_h=4\)。Q/K/V 均从 [2,4,8] 投影并 reshape 为 [2,2,4,4];每头 score 是 [4,4],合并 heads 后回到 [2,4,8],才能与 residual 相加。一般化后 score 元素随 \(B h T^2\) 增长,这是 Exact shape;写成 \(O(T^2)\) 是 Asymptotic,并不告诉你常数或真实 kernel 时间。
Pre-Norm 不是因为 LayerNorm “更准确”,而是给梯度保留更直接的 residual path;RMSNorm 少做 mean-centering 不代表对任何模型都更优;FlashAttention 改变 IO 顺序而不近似 attention 数学。反例:若把 attention 输出宽度改成 \(2d\) 却不加 output projection,residual addition 直接 shape 不合法。
原版 Transformer 提出了一个优雅的设计。但在实际训练大规模模型时,研究者发现了很多可以改进的地方。这一课围绕几个核心问题展开:
Pre-norm vs Post-norm
ReLU → GELU → SwiGLU
Sinusoidal → RoPE → ALiBi
LayerNorm → RMSNorm
Guided refresher
用几分钟追踪 tokens 如何经过 attention、MLP 与两次 residual addition。
每个 Transformer block 包含:
Input: x (batch, seq_len, d_model)
# 1. Multi-Head Self-Attention
attention_output = MultiHeadAttention(x, x, x) # Q=K=V=x
x = LayerNorm(x + attention_output) # Post-norm + residual
# 2. Feed-Forward Network
ffn_output = FFN(x) # Linear → ReLU → Linear
x = LayerNorm(x + ffn_output) # Post-norm + residual
# 3. 重复 L 层
# 4. 最终输出
output = Linear(x) # d_model → vocab_size
| 符号 | 含义 | GPT-3 175B | Llama 2 70B |
|---|---|---|---|
| d_model | 隐藏维度 | 12288 | 8192 |
| n_layers | 层数 | 96 | 80 |
| n_heads | 注意力头数 | 96 | 64 |
| d_head | 每头维度 | 128 | 128 |
| d_ff | FFN 中间维度 | 49152 | 28672 |
| V | 词表大小 | 50257 | 32000 |
# Post-norm (Vaswani 2017)
x = LayerNorm(x + MultiHeadAttention(x))
x = LayerNorm(x + FFN(x))
# Pre-norm (GPT-2 及以后主流)
x = x + MultiHeadAttention(LayerNorm(x))
x = x + FFN(LayerNorm(x))
梯度流的关键差异:
Post-norm 的恒等残差路径会经过归一化,深层网络往往更依赖 warmup、初始化和残差缩放。
Pre-norm 让残差主干保持恒等映射,梯度可以沿主干直接传播,因此通常更容易稳定训练很深的 Transformer。
这不是“Pre-norm 在所有指标上都更好”。Post-norm 或混合方案在合适初始化与缩放下也能稳定训练,并可能有不同的表示与收敛特性。架构选择应结合深度、初始化和训练 recipe 判断。
官方 Lecture 3 特别讨论了把额外的 norm 放在残差分支输出、但不放进恒等残差主干的做法;Grok、Gemma 2、OLMo 2 等模型采用了不同组合。它们不应笼统称为“DeepNet 的 Sandwich Norm”,也不是 DeepSeek-V3 的统一配置。
# 一类示意:保留干净的 residual stream,规范化分支输入/输出
x = x + PostNorm(Attention(PreNorm(x)))
原版 Transformer 用 ReLU。GPT-2 开始用 GELU:
ReLU(x) = max(0, x)
GELU(x) = x × Φ(x) (Φ 是标准正态的 CDF)
≈ 0.5 × x × (1 + tanh(√(2/π) × (x + 0.044715 × x³)))
为什么使用 GELU? GELU 是平滑的门控式非线性,按输入大小连续缩放信号,而 ReLU 对负值直接截断。它在 GPT/BERT 系列中表现良好,但不能仅凭“0 点不可导”解释优劣——深度学习可以正常优化 ReLU,最终选择主要来自经验结果与硬件代价。
Llama 系列和 Mistral 使用 SwiGLU:
# 标准 FFN
FFN(x) = W₂ × ReLU(W₁ × x)
# SwiGLU FFN(Noam Shazeer 2020 系统研究)
FFN(x) = W_down × (Swish(W_gate × x) ⊙ (W_up × x))
# 其中 Swish(x) = x × sigmoid(x)
SwiGLU 的优势:
注意:SwiGLU 把 FFN 的中间维度从 4×d_model 改为 ~2.67×d_model(为了保持参数量一致)。Llama 的 d_ff ≈ 8/3 × d_model ≈ 2.67 × d_model。
| 激活 | 公式 | 使用模型 |
|---|---|---|
| ReLU | max(0, x) | 原版 Transformer, BERT |
| GELU | x · Φ(x) | GPT-2, GPT-3, BERT |
| SwiGLU | Swish(W_g · x) ⊙ (W_u · x) | Llama, Mistral, Qwen |
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
优势:可以外推到更长序列(理论上)。
劣势:实际表现不如可学习嵌入和 RoPE;推理时需要预先计算。
# GPT-2 风格
position_embedding = nn.Embedding(max_seq_len, d_model)
优势:简单,可学习。
劣势:不能外推到训练时未见过的位置;需要固定 max_seq_len。
RoPE 的核心思想:不直接编码位置,而是让 attention 分数自然包含相对位置信息。
RoPE 的直觉:
把 query 和 key 的每一对维度旋转一个与位置相关的角度。两个位置 p₁ 和 p₂ 的 attention score 只依赖 (p₁ - p₂)——即相对位置。
# RoPE 数学
对于维度 i (0, 2, 4, ...):
θ_i = 1 / (10000^(2i/d))
位置 m 的旋转:
R(m) = diag([e^{imθ_0}, e^{imθ_1}, ...])
# 应用到 query 和 key:
q_rotated = R(m) @ q
k_rotated = R(n) @ k
# attention score = q_rotated @ k_rotated^T
# 只依赖 (m - n) —— 相对位置!
RoPE 的优势:
# 不加位置编码,直接在 attention score 上加偏置
scores = Q @ K^T / √d
scores[i, j] -= |i - j| × slope_head_i # 距离越远,惩罚越大
ALiBi 不需要位置 embedding 表,并在原论文设置中展示了长度外推能力;RoPE 与 ALiBi 的优劣取决于训练长度、缩放方法、架构和任务,不能概括为一个方法在“大多数任务”上必然更好。
| 方法 | 编码方式 | 外推能力 | 使用模型 |
|---|---|---|---|
| Sinusoidal | 绝对位置,三角函数 | 理论好,实际差 | 原版 Transformer |
| Learned | 绝对位置,可学习 | 无 | GPT-2, BERT |
| RoPE | 相对位置,旋转 | 好(可插值) | Llama, Mistral, Qwen |
| ALiBi | 相对位置,线性偏置 | 极好 | BLOOM, MPT |
# 原版 LayerNorm
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
x_norm = (x - mean) / sqrt(var + ε)
output = γ × x_norm + β
参数量:2 × d_model(γ 和 β)
Llama 系列使用 RMSNorm——去掉 mean centering:
# RMSNorm
rms = sqrt(mean(x²) + ε)
x_norm = x / rms
output = γ × x_norm # 没有 β!
为什么去掉 mean centering?
# 常见现代配置: Pre-RMSNorm
x = x + Attention(RMSNorm(x))
x = x + FFN(RMSNorm(x))
# 有些模型在 attention 内部也做 norm (QK-norm)
# 特别是长上下文模型,防止 attention logits 爆炸
FFN(x) = W₂ × activation(W₁ × x)
d_ff = 4 × d_model (标准比例)
# 标准 GLU
GLU(x) = σ(W_gate × x) ⊙ (W_up × x) # ⊙ 是逐元素乘
FFN(x) = W_down × GLU(x)
# SwiGLU(用 Swish 替代 sigmoid)
SwiGLU(x) = Swish(W_gate × x) ⊙ (W_up × x)
# GeGLU(用 GELU 替代 sigmoid)
GeGLU(x) = GELU(W_gate × x) ⊙ (W_up × x)
为什么门控更好?
维度调整:SwiGLU 把 d_ff 从 4×d_model 改为 ~2.67×d_model(8/3),因为现在有三个矩阵(W_gate, W_up, W_down)。参数量大致相同。
# 输入: Q, K, V (batch, seq, d_model)
# 拆分成 h 个头
Q = rearrange(Q, 'b s (h d) -> b h s d', h=num_heads)
K = rearrange(K, 'b s (h d) -> b h s d', h=num_heads)
V = rearrange(V, 'b s (h d) -> b h s d', h=num_heads)
# Scaled dot-product attention
scores = Q @ K^T / √d_k
scores = causal_mask(scores) # 因果掩码
attn = softmax(scores)
output = attn @ V
# 合并头
output = rearrange(output, 'b h s d -> b s (h d)')
下一课会详细讲,这里先预览:
MHA: num_heads 个 Q 头, num_heads 个 K 头, num_heads 个 V 头
MQA: num_heads 个 Q 头, 1 个 K 头, 1 个 V 头
GQA: num_heads 个 Q 头, num_kv_heads 个 K 头, num_kv_heads 个 V 头
例如: 32 个 Q 头, 8 个 KV 头(每 4 个 Q 头共享 1 个 KV 头)
不是架构创新,而是计算方法的革命:
FlashAttention 的核心思想:不改变 attention 的数学定义,而是用 tiling 与 online softmax 避免把完整的 attention score/probability 矩阵物化到 HBM。
通过 tiling + online softmax,在 SRAM 中完成每个 block 的 attention,只把最终输出写回 HBM。
效果:attention 中间量的额外存储从 O(n²) 降到 O(n);加速比取决于序列长度、head dimension、GPU 与具体 kernel,不能固定写成 2-4×。
| 超参数 | 含义 | 典型范围 | 影响 |
|---|---|---|---|
| d_model | 隐藏维度 | 512 - 12288 | 模型容量 |
| n_layers | 层数 | 12 - 120 | 模型深度 |
| n_heads | 注意力头数 | 8 - 96 | 并行注意力 |
| d_head | 每头维度 | 64 - 128 | 注意力表达力 |
| d_ff | FFN 中间维度 | 4×d_model 或 2.67×d_model | FFN 容量 |
| V | 词表大小 | 32K - 256K | 序列效率 |
对于固定的参数量预算,应该选宽模型(大 d_model, 少层)还是深模型(小 d_model, 多层)?
# Transformer 参数量(忽略 embedding 和 final head)
# Attention(MHA 假设): 4 × d_model²
# GQA/MQA 的 K、V 投影更小,不能直接套 4d²
# FFN (SwiGLU): 3 × d_model × d_ff (W_gate, W_up, W_down)
# RMSNorm: 2 × d_model (每层两个 norm)
# 使用 SwiGLU (d_ff = 8/3 × d_model):
per_layer = 4d² + 3d × (8/3)d + 2d = 4d² + 8d² + 2d = 12d² + 2d
total_params ≈ n_layers × 12 × d_model²
Hoffmann et al. (2022) 的实验常被概括为下面这个经验预算:
D ≈ 20 × N
其中:
D = 训练 token 数
N = 模型参数量
例如: 70B 模型 → ~1.4T tokens (compute-optimal)
边界:20 tokens/parameter 是 Chinchilla 70B 配置及其量级的常用速记,不是跨架构、数据质量和推理成本都不变的物理常数。现代模型常为了摊薄推理成本而显著 overtrain。
| 模型 | d_model | n_layers | n_heads | d_head | d_ff | 激活 | Position | Norm |
|---|---|---|---|---|---|---|---|---|
| GPT-3 175B | 12288 | 96 | 96 | 128 | 49152 | GELU | Learned | Pre-LN |
| Llama 2 70B | 8192 | 80 | 64 | 128 | 28672 | SwiGLU | RoPE | Pre-RMS |
| Mistral 7B | 4096 | 32 | 32 | 128 | 14336 | SwiGLU | RoPE | Pre-RMS |
| Gemma 2 27B | 4608 | 46 | 32 Q / 16 KV | 128 | 36864(8×d_model) | GeGLU | RoPE | Pre/Post RMSNorm |
趋势:
# 目标: 让输出的方差 ≈ 输入的方差
# 对于 (input_dim, output_dim) 的权重矩阵:
std = sqrt(2 / (input_dim + output_dim)) # Xavier uniform
std = sqrt(1 / input_dim) # 简化版本(前向)
w = torch.randn(input_dim, output_dim) * std
很多现代模型对残差分支做特殊初始化:
# GPT-2 风格
# 残差投影初始化为 0.02(很小),让训练初期接近 identity
w = torch.randn(d_model, d_model) * 0.02
# T5 / PaLM 风格
# 按层深度缩放: std = base_std / sqrt(2 * n_layers)
# 深层层的残差贡献更小,保持梯度稳定
PaLM 和后续模型使用特殊的初始化来增强 ICL 能力:
问题 1:为什么现代 Transformer 用 pre-norm 而不是 post-norm?
问题 2:RoPE 的核心优势是什么?
问题 3:RMSNorm 和 LayerNorm 的区别是什么?
问题 4:使用 SwiGLU 时,d_ff 应该怎么调整?
问题 5:FlashAttention 为什么比普通 attention 更快?
输入 \(x:[3,128,512]\),attention 输出若为 [3,128,1024],能否直接 residual add?
答案:不能;逐元素加法要求 shape 可广播且这里应保持 model width。需先用 output projection 映射回 512。
若答“能,因为 token 数相同”,说明你只检查了 sequence 轴,漏掉 residual 的 feature-axis invariant。
标准 attention 与 dense MLP 的对象和成本已经明确;下一课才有资格问:共享 KV、限制 attention 范围或只激活部分 experts 时,究竟省了哪项成本、又新增了什么误差与通信。
残差绕过 LayerNorm,训练更稳定
现代 LLM 中很常见,但仍存在 GeGLU、ALiBi、NoPE 等替代方案
去掉 mean centering,更快更省
不改数学,改计算顺序,O(n) 内存