Step 4 · The transformer block
Attention plus a small feed-forward network plus two normalisations plus two residual connections. Repeat N times. That is the entire model.
In 60 seconds
Step 4 · The transformer block
Attention plus a small feed-forward network plus two normalisations plus two residual connections. Repeat N times. That is the entire model.
The feed-forward network
class SwiGLU(nn.Module):
def __init__(self, d_model, hidden=None):
super().__init__()
# 8/3 rather than 4x, because the gate adds a third matrix
hidden = hidden or int(8 * d_model / 3 / 64) * 64
self.up = nn.Linear(d_model, hidden, bias=False)
self.gate = nn.Linear(d_model, hidden, bias=False)
self.down = nn.Linear(hidden, d_model, bias=False)
def forward(self, x):
return self.down(F.silu(self.gate(x)) * self.up(x))Residuals and normalisation: the two things that make depth possible
- 1
Residual connections
x = x + block(x). Each block writes a correction onto a running stream rather than replacing it. Gradients flow straight down the addition path, which is what allows 80-layer networks to train at all. - 2
Pre-normalisation
Normalise before each sub-layer, not after. Original transformers did it after and needed careful learning-rate warmup to avoid diverging. Pre-norm is dramatically more stable and is now universal. - 3
RMSNorm over LayerNorm
Skip the mean-subtraction and the bias; just divide by the root-mean-square and scale. Slightly cheaper, works just as well, so modern models use it. - 4
No biases
Most current models drop bias terms in linear layers entirely. Fewer parameters, no measured loss, slightly better stability.
class RMSNorm(nn.Module):
def __init__(self, d, eps=1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(d))
def forward(self, x):
rms = x.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
return x * rms * self.weight
class Block(nn.Module):
def __init__(self, d_model, n_head):
super().__init__()
self.n1 = RMSNorm(d_model)
self.attn = CausalSelfAttention(d_model, n_head)
self.n2 = RMSNorm(d_model)
self.ff = SwiGLU(d_model)
def forward(self, x):
x = x + self.attn(self.n1(x)) # mix across positions
x = x + self.ff(self.n2(x)) # think within each position
return xWatch and read more
Lab
An ablation study of the modern transformer block.
The problem
You are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1Post-norm diverges without careful warmup; pre-norm does not. Explain the mechanism.Reveal
Questions people ask
Where is knowledge stored?
Evidence points mostly at the feed-forward layers, which behave somewhat like key-value memories. Attention decides what to retrieve and route; the feed-forward network holds much of what gets retrieved. It is not a clean separation.
Why 4x expansion in the FFN?
Empirical. It has been near-optimal across many scales. Gated variants use about 8/3 because they have three matrices instead of two, keeping the parameter count comparable.
How deep should a model be?
There is a rough relationship between width and depth that works well: a 7B model is typically around 32 layers with d_model 4096. Very deep and thin, or very wide and shallow, both underperform at a fixed parameter budget.
Is anything actually new since 2017?
At the block level: RoPE, RMSNorm, gated FFNs, grouped-query attention, and mixture-of-experts routing. Real improvements, all incremental. The transformative changes have been in scale, data quality and post-training.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.
Sign in with your phone number to take the test and save your progress