上一章节在 Sophia 的层结构中看到了 RMSNorm——每个注意力模块和前馈网络模块之前各有一个。为什么需要它?
数值漂移的问题
想象一个 28 层的网络。数据从第一层流到第二十八层,每一层都做矩阵乘法和激活函数。即使每一层只让向量的值稍微变大或变小一点,经过 28 层之后,累积效应可能很可观。
如果向量的分量值越变越大,到后面几层时数字可能变成几千甚至几万——这会导致数值溢出(超出计算机能表示的范围)。如果值越变越小,可能变成接近零的数字——有用的信号被淹没在数值噪声中。
更麻烦的是,不同的训练样本可能让向量的数值漂移到不同的范围。同一个层要处理的有些向量分量在 0.1 附近,有些在 100 附近——权重矩阵很难找到一组参数能同时适配这些不同量级的输入。
归一化的作用就是在每一层的入口把向量的数值「校准」到一个稳定的范围,让后续的计算有一个可预期的数值基础。
RMSNorm 的计算
RMSNorm(Root Mean Square Normalization)是一种简单高效的归一化方法。对一个 1,536 维的向量 x,它的计算分两步:
第一步:计算 RMS(均方根)。
RMS = √(x₁² + x₂² + … + x₁₅₃₆²) / √1536
也就是所有分量的平方的平均值,再开方。这个数字衡量了向量分量的「典型大小」。
第二步:除以 RMS,再乘以可学习的缩放因子。
输出ᵢ = (xᵢ / RMS) × γᵢ
除以 RMS 之后,向量的分量被归一化到大约 ±1 的范围(准确地说,分量平方的均值变成了 1)。
γ 是一个 1,536 维的可学习参数向量——模型在训练中自己调整每个维度的缩放比例。这让模型可以选择性地放大或缩小某些维度,而不是被迫接受均匀的归一化。
用 Python 写出来:
import math
def rms_norm(x, gamma):
# 第一步:计算 RMS
sum_of_squares = sum(xi ** 2 for xi in x)
rms = math.sqrt(sum_of_squares / len(x))
# 第二步:归一化并缩放
output = []
for i in range(len(x)):
output.append((x[i] / rms) * gamma[i])
return output
为什么用 RMSNorm 而不是 LayerNorm
另一种常见的归一化叫 LayerNorm,比 RMSNorm 多一步:先减去均值(centering),然后除以标准差。
LayerNorm: 输出 = (x - 均值) / 标准差 × γ + β
RMSNorm: 输出 = x / RMS × γ
LayerNorm 需要算均值、标准差,以及一个额外的偏置参数 β。RMSNorm 只需要算 RMS 和一个 γ——计算更少,参数更少,而效果几乎一样。
实验表明,减去均值(centering)这一步对 Transformer 的性能贡献很小。去掉它之后,训练速度更快,效果不受影响。这就是为什么现代的 Transformer 模型(包括 LLaMA、Sophia)普遍使用 RMSNorm 而不是 LayerNorm。
Pre-Norm 还是 Post-Norm
归一化放在模块之前还是之后,效果是不同的。
Post-Norm(归一化放在残差连接之后):
输出 = Norm(F(输入) + 输入)
这是最早的 Transformer 论文(2017)使用的方式。归一化在残差连接之后,作用于「原始信息 + 新增信息」的混合体。
Pre-Norm(归一化放在模块计算之前):
输出 = F(Norm(输入)) + 输入
后来的研究发现 Pre-Norm 训练更稳定——因为残差连接的直通路径不经过归一化,梯度的传播完全畅通无阻。Sophia 使用的就是 Pre-Norm。
一层的完整结构
现在可以画出 Sophia 一层的完整计算流程了:
输入 x
↓
RMSNorm(x) → 注意力模块 → + x ← 第一个残差连接
↓
得到 x'
↓
RMSNorm(x') → 前馈网络模块 → + x' ← 第二个残差连接
↓
输出
这个结构重复 28 次,就是 Sophia 的全部 Transformer 层。
下一章节来看这个结构中还没有展开的部分:前馈网络模块——以及 Sophia 使用的一种特殊的前馈网络设计:SiTU-GLU。