1. 归一化的必要性
深层神经网络中,各层输入的分布会随训练过程不断变化(Internal Covariate Shift),导致:训练不稳定、梯度消失/爆炸、收敛慢。归一化通过将激活值约束到合理范围来缓解这些问题。
2. Batch Normalization 为何不适用于 NLP?
BatchNorm 在 CV 中广泛使用,但在 NLP 中效果差:
- 变长序列:不同序列长度不同,Batch 维度统计不一致
- 推理与训练不一致:训练用 batch 统计量,推理用移动平均,序列长度变化时偏差大
- batch size 敏感:NLP 训练常用小 batch + 梯度累积,BatchNorm 统计量不可靠
3. Layer Normalization
3.1 公式
对每个样本的每个 Token 独立归一化,沿特征维度计算统计量:
$$ \mu = \frac{1}{d} \sum_{i=1}^{d} x_i $$
$$ \sigma^2 = \frac{1}{d} \sum_{i=1}^{d} (x_i - \mu)^2 $$
$$ \hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}} $$
$$ y_i = \gamma_i \hat{x}_i + \beta_i $$
其中 $\gamma, \beta \in \mathbb{R}^d$ 是可学习的缩放和平移参数。
3.2 实现
import torch
import torch.nn as nn
class LayerNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-6):
super().__init__()
self.gamma = nn.Parameter(torch.ones(hidden_size))
self.beta = nn.Parameter(torch.zeros(hidden_size))
self.eps = eps
def forward(self, x):
# x: (batch, seq_len, hidden_size)
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
x_norm = (x - mean) / torch.sqrt(var + self.eps)
return self.gamma * x_norm + self.beta
3.3 计算开销
LayerNorm 需要计算均值和方差两次遍历,加上可学习参数 $\gamma$ 和 $\beta$,每个 Token 增加 $4d$ 次运算($d$ 为隐藏维度)。
4. RMSNorm
4.1 动机
Zhang & Sennrich(2019)观察到:LayerNorm 的去均值操作和缩放/平移功能中,去均值对模型性能贡献不大,主要起作用的是方差缩放。RMSNorm 去掉均值计算和 $\beta$ 偏移:
4.2 公式
$$ \text{RMS}(x) = \sqrt{\frac{1}{d} \sum_{i=1}^{d} x_i^2} $$
$$ \hat{x}_i = \frac{x_i}{\text{RMS}(x) + \epsilon} $$
$$ y_i = \gamma_i \hat{x}_i $$
没有减均值、没有 $\beta$,计算更简单。
4.3 实现
class RMSNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-6):
super().__init__()
self.gamma = nn.Parameter(torch.ones(hidden_size))
self.eps = eps
def forward(self, x):
rms = torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
return self.gamma * x * rms
4.4 性能对比
| 指标 | LayerNorm | RMSNorm |
|---|---|---|
| 均值计算 | 需要 | 不需要 |
| $\beta$ 参数 | 有 | 无 |
| 参数量 | $2d$ | $d$ |
| 计算量 | $4d$ | $2d$ |
| 前向速度 | 基准 | 快 7-64% |
| 模型质量 | 基准 | 相当或略优 |
LLaMA、Qwen、Mistral 等现代 LLM 普遍采用 RMSNorm。
5. Pre-Norm vs Post-Norm
5.1 结构差异
Post-Norm(原版 Transformer):
x_out = LayerNorm(x + SubLayer(x))
Pre-Norm(GPT-2, LLaMA 等):
x_out = x + SubLayer(LayerNorm(x))
5.2 梯度分析
Post-Norm 的梯度路径经过归一化层,深层模型中梯度容易消失或爆炸。Pre-Norm 的残差连接不经过归一化,梯度可以无损地直接回传:
$$ \frac{\partial \text{loss}}{\partial x_l} = \frac{\partial \text{loss}}{\partial x_L} \left(1 + \sum_{l’=l}^{L-1} \frac{\partial \text{SubLayer}{l’}}{\partial x{l’}}\right) $$
Pre-Norm 保证了梯度中有直通的 $\frac{\partial x_L}{\partial x_l} = 1$ 项。
5.3 实验结论
- Post-Norm 在浅层模型中效果略好(更好的正则化)
- Pre-Norm 在深层模型(>12层)中训练更稳定
- Pre-Norm 的实际深度感受弱于 Post-Norm(因为残差路径未经归一化,深度被"稀释")
6. DeepNorm
6.1 动机
Microsoft(2022)提出 DeepNorm,试图让 Post-Norm 也能训练超深模型(1000+层)。
6.2 方法
修改残差连接为:
$$ x_{l+1} = \text{LayerNorm}\left(\alpha \cdot x_l + \text{SubLayer}(x_l)\right) $$
其中 $\alpha$ 是一个常数,满足 $\alpha > 1$,使得残差路径的权重更大。初始化时对 SubLayer 的权重做缩放:
- FFN: $W \leftarrow \frac{W}{\sqrt{N}}$
- Attention: $W_V, W_{out} \leftarrow \frac{W}{\sqrt{2N}}$
其中 $N$ 是 Transformer 层数。
6.3 效果
DeepNorm 可以训练 1000 层的 Transformer,且性能优于 Pre-Norm 同深度模型。
7. 数值稳定性
7.1 溢出问题
FP16/BF16 训练中,LayerNorm 的中间值可能溢出:
x.mean()和x.var()在 FP16 下可能不准确- 解决方案:在 FP32 下计算统计量
def stable_rmsnorm(x, gamma, eps):
# 转为 FP32 计算统计量
x_fp32 = x.float()
rms = torch.rsqrt(x_fp32.pow(2).mean(dim=-1, keepdim=True) + eps)
return (gamma * x * rms.to(x.dtype))
7.2 梯度爆炸
当 $\sigma^2 + \epsilon$ 很小时,$\frac{1}{\sqrt{\sigma^2 + \epsilon}}$ 会很大,导致梯度爆炸。RMSNorm 因为不计算均值,数值更稳定。
8. 归一化在 LLM 中的位置
典型 LLaMA 结构:
x → RMSNorm → Attention → +x → RMSNorm → FFN → +x → output
每次 Attention 和 FFN 之前各有一个 RMSNorm(Pre-Norm)。
8.1 Final Norm
模型最后一层之后通常还有一次 RMSNorm(model.norm),确保输出分布稳定。这个 Norm 不可省略,否则输出值范围会不可控。
9. 总结
| 选择 | 推荐 |
|---|---|
| 浅层模型 (<12层) | LayerNorm + Post-Norm |
| 深层 LLM | RMSNorm + Pre-Norm |
| 超深模型实验 | DeepNorm |
| 低精度训练 | RMSNorm (数值更稳定) |
RMSNorm + Pre-Norm 已成为现代 LLM 的事实标准。减少计算开销、提升数值稳定性、保持模型质量,三者兼得。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
