Why initialization matters
If you initialize weights too small, signal vanishes through the network and gradients vanish back. Too large, signal explodes and gradients explode. The right scale keeps activation variance roughly constant from layer to layer, which keeps the gradient chain product well-conditioned at depth.
Xavier (a.k.a. Glorot) initialization: std = sqrt(2 / (fan_in + fan_out)). Designed for tanh/sigmoid activations. He initialization: std = sqrt(2 / fan_in). Designed for ReLU (which kills half the inputs, so you need slightly bigger weights to compensate). PyTorch's default for nn.Linear is a uniform variant of He.
The custom-init pattern
For non-default architectures, write a single init_weights function and apply it via model.apply(init_weights). This recursively walks the module tree and calls your function on every submodule. Standard practice in transformer code.
Special cases
Output layer — sometimes initialized at smaller scale (e.g., 0.02 std for transformer LM heads). Embeddings — Normal(0, 0.02) for transformers; uniform for word embeddings. BatchNorm — γ=1, β=0 by default; some recipes use γ=0 for the last BN in a residual block to make it start as identity.