← All Posts
Deep Learning · Popular Videos · Umar Jamil· Part 3 · Chapter 5

Transformer Assembly & Weight Initialization

Depth changes the rules. A two-layer network tolerates almost any sane initialization. An eighty-layer residual network does not: get the residual-branch scale wrong and the activation variance grows linearly with depth, which shows up as a loss curve that will not descend and is frequently misdiagnosed as a bad learning rate.

The residual stream

The most useful way to read a modern transformer is not as a stack of layers but as a single residual stream that every block reads from and writes to:

$$\mathbf x\leftarrow\mathbf x+\text{Attn}\big(\text{Norm}(\mathbf x)\big),\qquad \mathbf x\leftarrow\mathbf x+\text{FFN}\big(\text{Norm}(\mathbf x)\big).$$

Two properties matter. The stream is never transformed in place, only added to, so there is an unbroken identity path from the embedding to the final norm. And each block sees a normalized copy of the stream while writing an unnormalized increment back into it.

Pre-norm versus post-norm

Post-norm (original)

$$\mathbf x\leftarrow\text{Norm}\big(\mathbf x+F(\mathbf x)\big)$$

The normalization sits on the residual path, so every backward pass is multiplied by the norm's Jacobian. Over dozens of layers those factors compound and the gradient reaching early layers is unreliable. Training deep post-norm stacks requires a learning-rate warmup and is still fragile.

Pre-norm (universal today)

$$\mathbf x\leftarrow\mathbf x+F\big(\text{Norm}(\mathbf x)\big)$$

The residual path is a clean identity. Differentiating gives $\partial\mathbf x_{\text{out}}/\partial\mathbf x=I+\partial F/\partial\mathbf x$, so the gradient always has an unattenuated route to every layer. This is what makes 80-layer stacks trainable without heroics.

Pre-norm has one wart: the stream itself is never normalized, so its magnitude grows with depth. That is why a final Norm is applied after the last block, before the output projection.

RMSNorm

LayerNorm centres and rescales:

$$\text{LN}(\mathbf x)=\frac{\mathbf x-\mu}{\sqrt{\sigma^2+\epsilon}}\odot\mathbf g+\mathbf b.$$

RMSNorm drops the mean subtraction and the bias, keeping only the scale:

$$\boxed{\text{RMSNorm}(\mathbf x)=\frac{\mathbf x}{\sqrt{\frac1d\sum_i x_i^2+\epsilon}}\odot\mathbf g.}$$

Why it is enough

The empirical finding is that the re-centring contributes little; the benefit comes from constraining the magnitude. Removing it loses no measurable quality.

Why it is better

One reduction pass instead of two, no mean to store for the backward pass, and $d$ fewer parameters per norm. On a memory-bound elementwise kernel that is a real saving.

Compute the norm in FP32. Summing $d$ squared values in BF16 loses precision quickly and, for large activations, can overflow. Every serious implementation upcasts, reduces, normalizes, then casts back.

The gated feed-forward network

The classic FFN is $W_2\,\phi(W_1\mathbf x)$. The gated variant splits the first projection into two and uses one branch to modulate the other:

$$\text{SwiGLU}(\mathbf x)=W_{\text{down}}\Big(\text{SiLU}(W_{\text{gate}}\mathbf x)\odot W_{\text{up}}\mathbf x\Big),\qquad \text{SiLU}(z)=z\,\sigma(z).$$

The gate lets the layer suppress or pass each hidden unit as a function of the input, rather than applying a fixed pointwise nonlinearity. It reliably beats an ungated FFN at equal parameter count — which is why $d_{\text{ff}}$ shrinks from $4d$ to $\tfrac83 d$ when the third matrix is added, keeping the budget fixed.

Why naive initialization fails with depth

Here is the failure mode, made quantitative. Model the residual stream as a random vector and each block's output as an independent contribution. If the stream enters a block with variance $v$ and the branch contributes variance $\sigma^2$, then because the two are roughly uncorrelated,

$$v_{\text{out}}=v+\sigma^2.$$

A block contains two residual additions, so an $L$-layer model performs $2L$ of them. Starting from $v_0=1$:

$$v_{2L}=1+2L\,\sigma^2.$$
The variance grows linearly in depth. With $\sigma^2\approx1$ and $L=80$, the stream arrives at the output with variance around 161, roughly a 13-fold growth in magnitude. Every normalization then has to divide by a large factor, the effective contribution of each block shrinks, and the logits saturate before training begins.

The fix is to make each branch's contribution shrink with depth. Scale the weights of the layers that write into the stream — the attention output projection $W_O$ and the FFN down projection $W_{\text{down}}$ — by

$$\boxed{\frac{1}{\sqrt{2L}}.}$$

Then $\sigma^2\mapsto\sigma^2/2L$ and the total becomes

$$v_{2L}=1+2L\cdot\frac{\sigma^2}{2L}=1+\sigma^2,$$

which is independent of $L$. This is the GPT-2 initialization rule, and it is why the same recipe transfers unchanged from a 12-layer model to an 80-layer one.

variance at the output
magnitude growth
branch weight scale
Each block performs two residual additions, so the horizontal axis runs to $2L$. The unscaled curve is a straight line; the scaled one is flat by construction, at any depth.

The full recipe

TensorInitializationReason
$W_Q,W_K,W_V$$\mathcal N(0,\sigma^2)$ with $\sigma=0.02$, or $\sigma=1/\sqrt d$Preserve activation scale through the projection.
$W_O$, $W_{\text{down}}$same, then multiplied by $1/\sqrt{2L}$These write into the residual stream; the depth scaling keeps its variance constant.
$W_{\text{gate}},W_{\text{up}}$$\mathcal N(0,\sigma^2)$They feed the nonlinearity, not the stream.
RMSNorm gain $\mathbf g$all onesStart as an identity so the block begins as a pure pass-through.
Embedding table$\mathcal N(0,\sigma^2)$Sets $v_0\approx1$ entering the stack.
Biases, if presentzeroNo reason to break symmetry twice.
Scale the right matrices. Applying $1/\sqrt{2L}$ to every weight in the model, rather than only to the two that write into the residual stream, shrinks the attention logits and the FFN pre-activations as well. The stream stays stable but the model starts far too close to a constant function.

Tied embeddings

The input embedding maps token ids to vectors; the output head maps vectors back to logits. Reusing one matrix for both is weight tying:

$$\text{logits}=\mathbf x\,E^\top,\qquad E\in\mathbb R^{V\times d}.$$

It saves $Vd$ parameters, which for a small model with a 128k vocabulary is a large fraction of the total. It also acts as a regularizer, since a token's input and output representations are forced to agree. Large models increasingly untie the two, because $Vd$ stops being a meaningful share of the budget and the extra freedom helps slightly.

Tying changes the gradient structure. A tied table receives gradients from two places — the embedding lookup and the output projection — which by the fan-out rule must be summed. Frameworks handle this automatically, but it is worth knowing when the embedding gradient looks larger than expected.

The assembled block

One decoder block, forward
1$\mathbf h\leftarrow\text{RMSNorm}_1(\mathbf x)$
2$\mathbf q,\mathbf k,\mathbf v\leftarrow W_Q\mathbf h,\;W_K\mathbf h,\;W_V\mathbf h$
3apply RoPE to $\mathbf q$ and $\mathbf k$ only
4$\mathbf a\leftarrow W_O\,\text{softmax}\!\big(\mathbf q\mathbf k^\top/\sqrt{d_h}+\text{mask}\big)\mathbf v$
5$\mathbf x\leftarrow\mathbf x+\mathbf a$
6$\mathbf h\leftarrow\text{RMSNorm}_2(\mathbf x)$
7$\mathbf x\leftarrow\mathbf x+W_{\text{down}}\big(\text{SiLU}(W_{\text{gate}}\mathbf h)\odot W_{\text{up}}\mathbf h\big)$

After $L$ of these, a final RMSNorm and the output projection produce logits. The $1/\sqrt{d_h}$ inside the softmax is the same variance argument in miniature: a dot product of two $d_h$-dimensional unit-variance vectors has variance $d_h$, and dividing by $\sqrt{d_h}$ brings the logits back to order one so the softmax does not saturate.

Takeaway

Pre-norm gives the gradient a clean path, RMSNorm makes normalization cheap, gating makes the FFN more expressive per parameter, and scaling the two stream-writing projections by $1/\sqrt{2L}$ makes the whole recipe depth-independent. That last item is the one that silently breaks when you scale up.

Check yourself