Roadmap / Neural Network Layers

✓

LayerNorm Forward & Backward

mediumlayersgradientsnormalization

The problem

LayerNorm (layer normalization) keeps the numbers inside a network at a steady scale. It takes each token's vector of DD numbers on its own, shifts it to mean 0, rescales it to variance 1, then applies a learned gain γ\gamma and bias β\beta (both vectors of DD numbers). The theory page explains why this helps.

You will write the forward pass and the backward pass.

Forward

First the mean and the variance of the vector:

μ=1D∑jxj\mu = \frac{1}{D}\sum_j x_jσ2=1D∑j(xj−μ)2\sigma^2 = \frac{1}{D}\sum_j (x_j - \mu)^2

Then normalize, scale and shift:

x^=x−μσ2+ϵ,y=γ⊙x^+β\hat x = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}, \qquad y = \gamma \odot \hat x + \beta

where:

  • xx is one token's vector and xjx_j its entry jj;
  • μ\mu (mu) is its mean and σ2\sigma^2 (sigma squared) its variance, the average squared distance from the mean;
  • x^\hat x ("x hat") is the normalized vector, with mean 0 and variance 1;
  • ϵ\epsilon (epsilon) is eps, which avoids dividing by zero;
  • ⊙\odot is elementwise multiplication.

Example. x=(1,2,3,4)x = (1, 2, 3, 4), γ=(2,2,2,2)\gamma = (2, 2, 2, 2), β=(1,1,1,1)\beta = (1, 1, 1, 1).

  1. μ=2.5\mu = 2.5. Distances from the mean (−1.5,−0.5,0.5,1.5)(-1.5, -0.5, 0.5, 1.5), squared (2.25,0.25,0.25,2.25)(2.25, 0.25, 0.25, 2.25), so σ2=1.25\sigma^2 = 1.25.
  2. x^=(x−2.5)/1.25≈(−1.34,−0.45,0.45,1.34)\hat x = (x - 2.5) / \sqrt{1.25} \approx (-1.34, -0.45, 0.45, 1.34).
  3. y=2x^+1≈(−1.68,0.11,1.89,3.68)y = 2 \hat x + 1 \approx (-1.68, 0.11, 1.89, 3.68).

Backward

The backward pass receives grad_y, the gradient of the loss with respect to yy, and returns three gradients.

For β\beta and γ\gamma: βj\beta_j is added to entry jj of every token's output, and γj\gamma_j multiplies x^j\hat x_j of every token. So:

  • the gradient of β\beta is grad_y summed over all tokens;
  • the gradient of γ\gamma is grad_y * x_hat summed over all tokens.

Example. Two tokens with grad_y = [[1, 0, 0, 0], [1, 0, 0, 0]]: the gradient of β\beta is [2, 0, 0, 0]. If both tokens had the x^\hat x above, the gradient of γ\gamma would be 1⋅(−1.34)+1⋅(−1.34)=−2.681 \cdot (-1.34) + 1 \cdot (-1.34) = -2.68 in its first entry, and 0 elsewhere.

For xx: every entry of x^\hat x depends on every entry of xx, through μ\mu and σ\sigma. The theory page derives the result. With g=γ⊙g = \gamma \odot grad_y and s=σ2+ϵs = \sqrt{\sigma^2 + \epsilon}:

∂L∂x=1s(g−g‾−x^ g⊙x^‾)\frac{\partial \mathcal{L}}{\partial x} = \frac{1}{s}\left(g - \overline{g} - \hat x \, \overline{g \odot \hat x}\right)

where:

  • a bar means the mean over the DD entries of the token;
  • g‾\overline{g} removes the part of the gradient that would only shift all entries together (normalization undoes any shift);
  • x^ g⊙x^‾\hat x \, \overline{g \odot \hat x} removes the part that would only stretch the vector (normalization undoes any stretch).

What to implement

  • layernorm_forward(x, gamma, beta, eps=1e-5): x has shape (…,D)(\dots, D). Return (y, cache), where cache is anything you need for the backward pass (for example x^\hat x, ss and γ\gamma).
  • layernorm_backward(grad_y, cache): return (dx, dgamma, dbeta), with the shapes of x, gamma and beta.

Requirements

  • Match PyTorch's LayerNorm: the variance divides by DD (np.var does this by default), and eps is added inside the square root.
  • x can have any number of leading axes: (N,D)(N, D), (B,T,D)(B, T, D), ... Normalize over the last axis only. dgamma and dbeta sum over all the other axes.
  • No Python loops.

Hints

  • Use keepdims=True for the means and the variance over the last axis, so they line up with x.
  • To sum over every axis except the last, pass a tuple: axis=tuple(range(x.ndim - 1)).
solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.