File size: 1,600 Bytes
4e316d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"""
LayerNorm (Layer Normalization).

The original normalization technique for transformers (Ba et al., 2016).
LayerNorm normalises each token's hidden state by subtracting the mean and
dividing by the standard deviation, then applies a learnable affine
transform:  output = scale * (x - mean) / sqrt(var + eps) + shift.

Compared to RMSNorm (which skips mean-centering and the shift parameter),
LayerNorm has slightly higher compute cost due to the extra mean subtraction
and additive bias.  In practice, modern LLMs (LLaMA, Qwen3, Gemma) prefer
RMSNorm because the mean-centering provides negligible benefit in pre-norm
architectures, while the savings add up across dozens of layers.

Included here for educational comparison with RMSNorm.

Computation is done in float32 regardless of input dtype to avoid
numerical instability in half-precision, then cast back.
"""

import torch
import torch.nn as nn


class LayerNorm(nn.Module):
    def __init__(self, emb_dim: int, eps: float = 1e-5, dtype=None):
        super().__init__()
        self.eps = eps
        self.scale = nn.Parameter(torch.ones(emb_dim, dtype=dtype))
        self.shift = nn.Parameter(torch.zeros(emb_dim, dtype=dtype))

    def forward(self, x):
        input_dtype = x.dtype
        # Force float32 for numerical stability (matches RMSNorm convention)
        x = x.to(torch.float32)
        mean = x.mean(dim=-1, keepdim=True)
        var = x.var(dim=-1, keepdim=True, unbiased=False)
        norm_x = (x - mean) / torch.sqrt(var + self.eps)
        return (self.scale.float() * norm_x + self.shift.float()).to(input_dtype)