Layer Normalization › Gradient Flow Through Layer Normalization
Language AI HandbookAll chapters Layer Normalization › Gradient Flow Through Layer NormalizationOne section · Sections in this chapter8 / 16 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Understanding how gradients flow through layer normalization is essential for debugging training issues and understanding why normalization stabilizes training.
The backward pass through layer normalization is more complex than a simple element-wise operation because each output depends on all inputs through the mean and variance computation. When we change a single input , it affects its own normalized output, the mean , and the variance , which in turn affects every output element. This coupling makes the gradient computation more involved.
Given the loss and the upstream gradient (the gradient flowing back from later layers), we need to compute three gradients: for backpropagation, and and for updating the learnable parameters.
Gradients for Learnable Parameters
The output of layer normalization is , where is the normalized input. Since this is a simple affine transformation, the gradients follow directly from the chain rule.
For the scale parameter :
where:
- : the gradient of the loss with respect to the -th scale parameter
- : an index over all tokens (across batch and sequence dimensions)
- : the upstream gradient for the -th feature of the -th token
- : the normalized value of the -th feature for the -th token
Intuitively, this sums up how much each token's normalized value contributed to the loss through this scale parameter.
For the shift parameter :
where:
- : the gradient of the loss with respect to the -th shift parameter
This is simply the sum of upstream gradients, since adds directly to the output.
Gradient for Input
The gradient with respect to input is more involved because depends on in three ways: directly, through the mean , and through the variance . Applying the chain rule carefully yields:
where:
- : the gradient of the loss with respect to the -th input element
- : the learned scale parameter for the -th feature
- : the standard deviation (with epsilon for stability)
- : the feature dimension (number of elements in the input vector)
- : the normalized input, equal to
Let's break down the three terms inside the parentheses:
-
Direct contribution : The gradient that would flow if normalization were a simple scaling operation.
-
Mean correction : Accounts for how changing affects , which affects all outputs. This term subtracts the average gradient, centering the gradient distribution.
-
Variance correction : Accounts for how changing affects , which scales all outputs. This term is proportional to the normalized value , meaning inputs far from the mean get larger corrections.
This formula reveals something important: the gradient for each input element depends on the gradients of all other elements through the mean and variance terms. This coupling helps distribute gradient information across features, which can improve training stability.
The mean correction term subtracts a constant from every gradient. The effect is similar to centering the gradients, preventing them from all shifting in the same direction. If the loss pushes all features to increase, the mean correction tempers this. This ensures the relative ordering of features matters more than their absolute magnitude. This is analogous to how the forward pass removes the mean from the input; the backward pass removes the mean from the gradient.
The variance correction term is more subtle. It is proportional to , the normalized value at position . Features that are far from the normalized mean (large ) receive a larger variance correction. This makes sense: those features have a larger influence on the variance computation, so their gradient needs a larger adjustment to account for that influence. In practice, this term prevents individual features from receiving gradients that are systematically larger simply because they happen to have larger normalized values.
def layer_norm_backward(dout, cache):
"""
Layer normalization backward pass.
Args:
dout: Upstream gradient of shape (batch, seq_len, hidden_dim)
cache: Values from forward pass
Returns:
dx: Gradient with respect to input
dgamma: Gradient with respect to scale
dbeta: Gradient with respect to shift
"""
x, x_norm, mu, var, gamma, eps = cache
d = x.shape[-1]
# Gradients for learnable parameters (sum over batch and sequence)
dgamma = (dout * x_norm).sum(dim=(0, 1))
dbeta = dout.sum(dim=(0, 1))
# Gradient for normalized input
dx_norm = dout * gamma
# Gradient for input (the complex part)
std = torch.sqrt(var + eps)
# Three terms in the gradient
term1 = dx_norm / std
term2 = dx_norm.mean(dim=-1, keepdim=True) / std
term3 = (dx_norm * x_norm).mean(dim=-1, keepdim=True) * x_norm / std
dx = term1 - term2 - term3
return dx, dgamma, dbeta# Verify against PyTorch autograd
x_test = torch.randn(2, 4, 8, requires_grad=True)
gamma_test = torch.ones(8, requires_grad=True)
beta_test = torch.zeros(8, requires_grad=True)
# Forward pass with our implementation
output, cache = layer_norm_forward(x_test, gamma_test, beta_test)
# Create fake upstream gradient
dout = torch.randn_like(output)
# Our backward pass
dx_ours, dgamma_ours, dbeta_ours = layer_norm_backward(dout, cache)
# PyTorch autograd backward
output.backward(dout)Gradient comparison with PyTorch autograd: dx max difference: 7.15e-07 dgamma max difference: 0.00e+00 dbeta max difference: 0.00e+00
The differences are on the order of or smaller, well within floating-point precision. This confirms our manual backward pass implementation correctly computes the gradients that PyTorch's autograd produces automatically.