softmax 的反向干净得反常,LayerNorm 则相反:每个输出分量通过均值 $\mu$ 和方差 $\sigma^2$ 依赖所有输入分量,雅可比不再稀疏,梯度公式也多出两项不太直觉的修正。很多手写反向传播的 bug 就出在这里——只写 $\gamma/\sigma$ 那一项,把耦合项丢了。这篇用微分法完整推一遍,并给三项各自一个几何解释。
前向定义与记号
对单个样本的特征向量 $x\in\mathbb{R}^d$(LayerNorm 在特征维上归一化,batch 内各样本独立,所以只需推单样本):
$$ \mu=\frac{1}{d}\sum_j x_j,\qquad \sigma^2=\frac{1}{d}\sum_j (x_j-\mu)^2,\qquad \hat x_i=\frac{x_i-\mu}{\sigma},\qquad y_i=\gamma_i\hat x_i+\beta_i. $$
其中 $\sigma=\sqrt{\sigma^2+\varepsilon}$,$\varepsilon$ 是数值稳定小量。前向缓存 $x$(或 $\hat x$)、$\sigma$。上游传来 $\bar y=\partial\ell/\partial y$,目标是 $\bar x$、$\bar\gamma$、$\bar\beta$。
两个参数梯度是顺手就能拿的:$y_i=\gamma_i\hat x_i+\beta_i$ 逐分量,
$$ \bar\gamma=\bar y\odot\hat x,\qquad \bar\beta=\bar y. $$
(对 batch 还要沿 batch 维求和归约——广播梯度归约,前面雅可比那篇说过原因。)难的是 $\bar x$,因为 $x$ 通过三条路影响输出:直接进分子、进 $\mu$、进 $\sigma$。
微分法主推 $\bar x$
令 $g=\bar y\odot\gamma$(把仿射的 $\gamma$ 先吸收进上游梯度),则 $d\ell=g^\top d\hat x$,问题化归为求 $\hat x$ 对 $x$ 的 VJP。
第一步:$\hat x$ 的微分。 $\hat x=(x-\mu\mathbf{1})/\sigma$,对它用商法则:
$$ d\hat x=\frac{dx-\mathbf{1}\,d\mu}{\sigma}-\frac{(x-\mu\mathbf{1})}{\sigma^2}d\sigma=\frac{dx-\mathbf{1}\,d\mu-\hat x\,d\sigma}{\sigma}. $$
最后一步用了 $(x-\mu\mathbf{1})/\sigma=\hat x$ 把 $d\sigma$ 的系数改写——这是整个推导最妙的一步替换,让后面所有项都能用 $\hat x$ 表达。
第二步:$d\mu$ 与 $d\sigma$。 均值部分简单:$d\mu=\frac{1}{d}\mathbf{1}^\top dx$。方差部分:$\sigma^2=\frac{1}{d}\|x-\mu\mathbf{1}\|^2$,微分得
$$ 2\sigma\,d\sigma=\frac{2}{d}(x-\mu\mathbf{1})^\top(dx-\mathbf{1}\,d\mu)=\frac{2}{d}(x-\mu\mathbf{1})^\top dx, $$
第二个等号因为 $(x-\mu\mathbf{1})^\top\mathbf{1}=\sum_j(x_j-\mu)=0$——去均值向量与全 1 向量正交,$d\mu$ 项自动消失。两边除以 $\sigma$($d\sigma=d(\sigma^2)/(2\sigma)$):
$$ d\sigma=\frac{1}{d\sigma}(x-\mu\mathbf{1})^\top dx=\frac{1}{d}\,\hat x^\top dx. $$
$d\sigma$ 与 $dx$ 的关系干净得出人意料:它是 $\hat x$ 方向上的投影系数除以 $d$。
第三步:组装 VJP。 把 $d\mu$、$d\sigma$ 代回 $d\hat x$,再乘 $g^\top$:
$$ d\ell=\frac{1}{\sigma}\Big[g^\top dx-\underbrace{(g^\top\mathbf{1})}_{\sum_i g_i}\cdot\underbrace{\frac{\mathbf{1}^\top dx}{d}}_{d\mu}-(g^\top\hat x)\cdot\underbrace{\frac{\hat x^\top dx}{d}}_{d\sigma}\Big]. $$
三项都是 $dx$ 的线性型,直接读出梯度:
$$ \boxed{\;\bar x=\frac{1}{\sigma}\Big[g-\bar g\,\mathbf{1}-\big(\tfrac{1}{d}g^\top\hat x\big)\,\hat x\Big],\qquad \bar g=\frac{1}{d}\sum_i g_i,\quad g=\bar y\odot\gamma\;} $$
写成分量形式(论文和实现里常见的样子):
$$ \bar x_i=\frac{1}{\sigma}\Big[g_i-\frac{1}{d}\sum_j g_j-\frac{\hat x_i}{d}\sum_j g_j\hat x_j\Big]. $$
三项的几何解释:两次投影
把公式改写一下更有味道。定义去均值算子 $P=I-\frac{1}{d}\mathbf{1}\mathbf{1}^\top$(投影到 $\mathbf{1}$ 的正交补),则
$$ \bar x=\frac{1}{\sigma}\,P\Big[g-\frac{g^\top\hat x}{d}\hat x\Big]. $$
(验证:$P$ 作用在第一项上给出 $g-\bar g\mathbf{1}$;作用在第二项上不变,因为 $\hat x$ 本身已去均值、$P\hat x=\hat x$。)也就是说 LayerNorm 的反向做两件事:
- 去掉 $\hat x$ 方向的分量(减 $\frac{g^\top\hat x}{d}\hat x$)——$\hat x$ 方向对应改变 $\sigma$,而 LayerNorm 对输入的缩放不变($x\leftarrow cx$ 不改变 $\hat x$),所以沿 $\hat x$ 方向的扰动不改变输出,其梯度必须为零;
- 去掉均值方向的分量($P$ 的作用)——$\mathbf{1}$ 方向对应平移,LayerNorm 对平移也不变($x\leftarrow x+c\mathbf{1}$ 不改变 $\hat x$),该方向梯度同样必须为零。
两项修正不是代数意外,是 LayerNorm 两个不变性(平移不变、缩放不变)的必然要求:函数对哪些扰动不敏感,梯度就必须与哪些方向正交。这个原则值得记住,它是检查一切归一化层反向推导是否正确的零成本手段——验证 $g$ 换成 $\mathbf{1}$ 或 $\hat x$ 时 $\bar x$ 是否为零即可。
推导中的两个暗礁
一个是 $d\mu$ 在 $d\sigma$ 里的自动消失。不少推导会带着 $\partial\sigma/\partial\mu$ 项硬算,其实去均值向量与 $\mathbf{1}$ 的正交性早就把它清零了——但前提是所有统计量围绕同一个 $\mu$。如果代码里均值和方差用了不同 batch 维(某些分布式实现的坑),这个正交性就不成立,梯度静默出错。
另一个是 $\varepsilon$ 的位置。$\sigma=\sqrt{\mathrm{var}+\varepsilon}$ 时 $d\sigma=\frac{1}{2\sqrt{\mathrm{var}+\varepsilon}}\cdot d(\mathrm{var})$,上面公式里的 $\sigma$ 全部指加了 $\varepsilon$ 之后的版本。$\varepsilon$ 在方差趋零时(比如输入是常数向量)防止除零,代价是梯度公式里 $1/\sigma$ 有下界——这实际上是归一化层自带的一种梯度裁剪。输入方差极小时 $\hat x\approx0$,第三项修正也趋零,梯度退化为 $\bar x\approx P g/\sqrt{\varepsilon}$,量级由 $\varepsilon$ 控制。这也是为什么 LayerNorm 的 $\varepsilon$(通常 $10^{-5}$ 或 $10^{-6}$)不只是数值补丁,它直接参与梯度的量级设计。
与实现的对照
PyTorch 的 F.layer_norm 反向在 CUDA kernel 里就是按分量公式算的(aten/src/ATen/native/layer_norm.cpp 的 layer_norm_backward,沿特征维做两次归约求 $\sum g$ 和 $\sum g\hat x$)。自己手写验证时,用第一篇的数值梯度检验:
import torch
torch.manual_seed(0)
d = 8
x = torch.randn(d, dtype=torch.float64, requires_grad=True)
gamma = torch.randn(d, dtype=torch.float64)
w = torch.randn(d, dtype=torch.float64) # 伪造上游梯度
def f(x):
mu = x.mean()
sigma = (x.var(unbiased=False) + 1e-5).sqrt()
xhat = (x - mu) / sigma
return (w * (gamma * xhat)).sum()
f(x).backward()
g = w * gamma
xhat = (x - x.mean()) / (x.var(unbiased=False) + 1e-5).sqrt()
manual = (g - g.mean() - xhat * (g * xhat).mean()) / (x.var(unbiased=False) + 1e-5).sqrt()
assert torch.allclose(x.grad, manual, atol=1e-10)
autograd 结果与三项公式逐项吻合(相对误差在 float64 下约 $10^{-12}$)。跑通这个 30 行的检查之后,LayerNorm 的反向就不再是黑盒。
小结
LayerNorm 反向的三项结构——直连项、去均值修正、去 $\hat x$ 修正——分别对应分子的直接依赖、均值耦合、方差耦合,后两项是平移与缩放两个不变性在梯度上的投影约束。推导的关键技巧只有两招:用 $\hat x$ 替换 $d\sigma$ 的系数,以及利用去均值向量与 $\mathbf{1}$ 的正交性。掌握这个推导后,RMSNorm(去掉均值耦合,只剩两项)和 BatchNorm(统计量换到 batch 维、正交性条件改变)的反向都可以当作推论写出,不再需要从头推。下一篇把 VJP 技术用到注意力的三段结构上:$Q,K,V$ 的梯度如何穿过 softmax 回传。