2Day 2

RMSNorm against LayerNorm

RMSNorm drops LayerNorm's mean subtraction and its bias. On Day 2 it matched LayerNorm's quality inside the noise and ran slower, for a reason that had nothing to do with arithmetic.

LayerNorm does two things to a vector. It subtracts the mean, and it divides by the standard deviation. The RMSNorm paper asked whether the first is needed. LLaMA, Qwen and most current decoders answer no, and use RMSNorm. EXP-010 swapped it into the Day 1 baseline and changed nothing else.

Definition

Divide by the root mean square, keep a gain

RMSNorm divides each vector by its root mean square and multiplies by a learned gain gg:

RMSNorm⁡(x)=g⊙x1C∑ixi2+ϵ\operatorname{RMSNorm}(x) = g \odot \frac{x}{\sqrt{\tfrac{1}{C}\sum_i x_i^2 + \epsilon}}

Compare LayerNorm, γ⊙(x−μ)/σ+β\gamma \odot (x - \mu)/\sigma + \beta. RMSNorm has no μ\mu and no β\beta. If the mean happens to be 0, the root mean square equals the standard deviation and the two agree exactly, apart from the bias.

The paper's argument is that LayerNorm's benefit comes from the rescaling, which makes a layer's output independent of how large its input is. Re-centering is the part it can drop.

Playground
Show
input x
1.20
−0.40
2.10
0.30
−1.00
0.80
LayerNorm(x), gain 1, bias 0
0.69
−0.88
1.57
−0.20
−1.47
0.29
RMSNorm(x), gain 1
1.06
−0.35
1.85
0.26
−0.88
0.70
mean(x)0.50LayerNorm subtracts it
std(x)1.02LayerNorm divides by it
rms(x)1.14RMSNorm divides by it
mean of RMSNorm(x)0.44not forced to 0
The same six-number vector through both norms, gain 1, no bias. Use the buttons to shift or scale every entry.

What happened?

Diagram
LayerNormmean μ = Σx / dcenter x − μmean of squares√(· + ε), divide× gain g+ bias bRMSNormskippedskippedmean of squares√(· + ε), divide× gain gskipped

Orange steps are the ones RMSNorm removes: the mean, the centering, and the bias. Without centering, the mean of squares is the RMS instead of the variance. Each norm is one pass over the vector either way, so the saving is a reduction and a subtraction per vector, small next to the matrix multiplies around it.

The operations each norm applies to one vector, in order.
In octlm

Eight lines, statistic in float32

octlm/model.pyline 135
class RMSNorm(nn.Module):
    def __init__(self, width: int, epsilon: float = 1e-6) -> None:
        super().__init__()
        self.weight = nn.Parameter(torch.ones(width))
        self.epsilon = epsilon

    def forward(self, x: Tensor) -> Tensor:
        scale = torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.epsilon)
        return (x.float() * scale).type_as(x) * self.weight

rsqrt is one over the square root. The statistic is computed in float32 even when the activations are half precision, because squaring small half-precision numbers loses too much. The gain starts at 1, and make_norm picks LayerNorm or RMSNorm from the config's norm switch.

The paper keeps an optional bias term. LLaMA's version has a gain only, and octlm follows LLaMA. The note records that the bias variant was not tested.

EXP-010

Same quality, 8.9 percent slower

Hypothesis: RMSNorm matches LayerNorm's quality and is faster. The paper reports speedups from 7 to 64 percent across models and 6.9 to 9.3 percent on a Transformer.

The test is one row of the Day 2 variant grid, with 3.3 million parameters, 400 steps, three seeds per variant, on a Colab T4. Reading the grid explains the grid and its noise.

Measured
Held-out split
baseline seed spread2.803.003.203.403.603.804.00code bits per byte, lower is betterbaselinermsnormswiglupost-normgqa-4gqa-2mqa-1modern
Hover a row to read its numbers.
Mean bits per byte over three seeds. The bar is the spread across seeds, drawn centered on the mean. The gray band is the baseline's own seed spread: a gap inside it is noise at this budget. Hover a row for its numbers.

What happened?

The slowdown is the opposite of the paper's result, and the note explains it. PyTorch's LayerNorm is one fused GPU kernel. octlm's RMSNorm is a chain of separate tensor operations (square, mean, reciprocal square root, two multiplies), and each one reads and writes the whole activation. At width 256 the norm is a noticeable share of each block, so the extra memory traffic shows. The paper counts arithmetic saved. The measurement counts which kernels the framework provides. Both are true, and the second one decides what a config should use.

Decision: no standalone keep. RMSNorm rides inside the modern stack, the combination Day 4 trains, where it matches the Qwen architecture Day 6 has to load. Re-measure its speed at width 512 and above before calling the slowdown general.

Skipped

What we did not build, and why