One layer of a transformer: attention and a position-wise FFN, each wrapped in a residual connection with normalization.

Equation

Post-norm (original):

Pre-norm (modern):

Pre-norm leaves an unnormalized identity path (the residual stream) from input to logits, so gradients reach early layers without warmup. FFN with SwiGLU:

From scratch

import torch, torch.nn as nn, torch.nn.functional as F
 
class SwiGLU(nn.Module):
    def __init__(self, d, mult=8/3):            # 8/3 keeps params ~= 4d^2 of 2-matrix FFN
        super().__init__()
        h = int(mult * d) // 64 * 64            # round hidden dim for kernel efficiency
        self.wg, self.wu = nn.Linear(d, h, bias=False), nn.Linear(d, h, bias=False)
        self.wd = nn.Linear(h, d, bias=False)
    def forward(self, x):                       # (b, n, d)
        return self.wd(F.silu(self.wg(x)) * self.wu(x))
 
class Block(nn.Module):
    def __init__(self, d, n_heads):
        super().__init__()
        self.n1, self.n2 = nn.RMSNorm(d), nn.RMSNorm(d)
        self.attn, self.ffn = MHA(d, n_heads), SwiGLU(d)
    def forward(self, x):                       # (b, n, d) -> (b, n, d)
        x = x + self.attn(self.n1(x))           # pre-norm residual
        return x + self.ffn(self.n2(x))

Notes

  • Params per block : attention, FFN (expansion 4, or 8/3 with three SwiGLU matrices).
  • Post-norm needs LR warmup and careful init; pre-norm trains stably but the residual stream variance grows with depth, so scale output projections by at init.
  • Sandwich/QK norm (extra norm on attention output or on q/k) is used to stop logit blowup at large scale.
  • Attention moves information between positions, FFN processes each position independently; that division is the whole architecture.

Related: Transformers, Multi-Head Attention, LayerNorm, MoE Routing