Roadmap / Attention & Transformers

✓

A LLaMA-Style Transformer Block

hardtransformerattentionlayers

The problem

Time to assemble the pieces into the block that models in the style of LLaMA (Large Language Model Meta AI) stack dozens of times. A block has two sub-layers, each wrapped the same way:

  • MHA (multi-head attention), where the tokens exchange information;
  • an MLP (multilayer perceptron) of the SwiGLU (Swish-gated linear unit) kind, where each token is processed on its own;
  • before each sub-layer, an RMSNorm (root mean square layer normalization).

The block

For x of shape (B,T,D)(B, T, D):

h=x+MHA(RMSNorm1(x))h = x + \mathrm{MHA}(\mathrm{RMSNorm}_1(x))y=h+SwiGLU(RMSNorm2(h))y = h + \mathrm{SwiGLU}(\mathrm{RMSNorm}_2(h))

where RMSNorm1\mathrm{RMSNorm}_1 and RMSNorm2\mathrm{RMSNorm}_2 have their own gains. Read each line as: take a normalized copy of the stream, run the sub-layer on it, and add the result to the stream itself (the residual connection). The stream is never replaced, only corrected.

Example. A useful check. If the output matrices of both sub-layers are zero (Wo=0W_o = 0 in attention, W2=0W_2 = 0 in the MLP), both sub-layers output zero, so h=x+0h = x + 0 and y=h+0y = h + 0: the block returns x unchanged. A block that normalizes the stream itself, instead of a copy, fails this check.

The SwiGLU MLP

SwiGLU(u)=(SiLU(uW1)⊙uW3) W2\mathrm{SwiGLU}(u) = \big(\mathrm{SiLU}(u W_1) \odot u W_3\big)\, W_2

where:

  • uu is the normalized input, W1W_1 and W3W_3 have shape (D,F)(D, F) and widen each vector to FF numbers, and W2W_2, of shape (F,D)(F, D), narrows it back;
  • SiLU(z)=z σ(z)\mathrm{SiLU}(z) = z\,\sigma(z) is the SiLU (sigmoid linear unit): zz times its sigmoid σ(z)=1/(1+e−z)\sigma(z) = 1 / (1 + e^{-z});
  • ⊙\odot is the elementwise product: the branch uW3u W_3 acts as a gate that scales each entry of the other branch.

Example. SiLU of a few numbers: SiLU(2)=2⋅0.881≈1.762\mathrm{SiLU}(2) = 2 \cdot 0.881 \approx 1.762, SiLU(0)=0\mathrm{SiLU}(0) = 0, SiLU(−2)≈−0.238\mathrm{SiLU}(-2) \approx -0.238, SiLU(20)≈20\mathrm{SiLU}(20) \approx 20, SiLU(−20)≈0\mathrm{SiLU}(-20) \approx 0. Large positive inputs pass almost unchanged, large negative ones are switched off, like ReLU (rectified linear unit) but smooth.

What to implement

  • silu(z), elementwise.
  • swiglu_mlp(u, W1, W3, W2).
  • transformer_block(x, p, n_heads), where p is a dict with "norm1", "norm2" (RMSNorm gains, shape (D,)(D,)), "Wq", "Wk", "Wv", "Wo" (shape (D,D)(D, D)) and "W1", "W3", "W2".

rmsnorm(x, gamma) and mha_forward(x, Wq, Wk, Wv, Wo, n_heads) (causal, from the earlier exercises) are given. To keep the block focused, it has no RoPE (rotary position embedding).

Requirements

  • Pre-norm: normalize the input of each sub-layer, and add the sub-layer's output to the un-normalized stream.
  • The second sub-layer reads hh, the output of the first, not xx.
  • No loops.

Hint

For a sigmoid that never overflows, σ(z)=12(1+tanh⁡(z/2))\sigma(z) = \tfrac{1}{2}\left(1 + \tanh(z/2)\right) (section 2).

solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.