Lesson 23 of 23 · Capstone

Walk one block without losing a dimension.

Your win: narrate every operation in a pre-norm causal Transformer block with RoPE, then map that readable block onto MiniMind's cache-aware implementation.

25 minutesNeeds: Lessons 1–22Outcome: teach and extend the block
Final mission: start with \(X^{(\ell)}\), finish with \(X^{(\ell+1)}\), and explain exactly where position, comparison, probability, content, and residual updates enter.

MINIMINDBLOCK.FORWARD · THE BIG PICTURE

Two updates. One continuous state.

Attention shares information across tokens. The MLP reshapes features within each token.

MiniMindBlock: mix tokens, then mix features A residual stream receives two updates. After RMSNorm, causal attention mixes earlier tokens. After a second RMSNorm, the MLP transforms each token independently. Both updates preserve shape. RESIDUAL STREAM Carry X Carry Y input state
\(X^{(\ell)}\in\mathbb{R}^{B\times T\times D}\)
RMSNorm
\(\bar X=\operatorname{RMSNorm}(X)\)
RMSNorm
\(U=\operatorname{RMSNorm}(Y)\)
The cat sat . query: sat
\(O_{\mathrm{attn}}=\operatorname{Concat}(O_1,\ldots,O_H)W_O\)
1 SHARE CONTEXT Mix token information. BUILD VIEWS
\(Q,K,V=\bar XW_{Q,K,V}\)
\(Q',K'=\operatorname{RoPE}(Q,K)\)
COMPARE POSITIONS
\([B,T,D]\to[B,H,T,d_h]\)
\(S=\frac{Q'K'^{\mathsf T}}{\sqrt{d_h}}+M_{\mathrm{causal}}\)
\(A=\operatorname{softmax}(S)\)
WRITE CONTEXT
\(O_{\mathrm{attn}}=A V\)
\(F=\operatorname{SwiGLU}(U)\)
2 REFINE FEATURES Refine each token. One MLP is applied to every row.
\(U=\operatorname{RMSNorm}(Y)\)
\(F=\bigl(\operatorname{SiLU}(UW_g)\odot UW_u\bigr)W_d\)
\(F\in\mathbb{R}^{B\times T\times D}\)
+
\(Y=X^{(\ell)}+O_{\mathrm{attn}}\)
+
\(X^{(\ell+1)}=Y+F\)
Same shape. Richer state. 3 RETURN STATE + MEMORY
\(X^{(\ell+1)}\in\mathbb{R}^{B\times T\times D}\)
cache for the next token
\(\operatorname{cache}=(K_{\le t},V_{\le t})\)

Toy walkthrough: \(B=1,T=2,d_{\text{model}}=2,h=1\)

Use RMSNorm with unit scale, identity projection matrices, positions \(m=0,1\), RoPE angle step \(\theta=\pi/2\), and

\[ X^{(\ell)}=\begin{bmatrix}1&1\\1&-1\end{bmatrix}. \]

Each row has RMS \(1\), so \(\bar X=X^{(\ell)}\). Identity projections give \(Q=K=V=\bar X\).

At position 0, \((1,1)\) stays \((1,1)\). At position 1,

\[ R_{\pi/2}\begin{bmatrix}1\\-1\end{bmatrix} =\begin{bmatrix}0&-1\\1&0\end{bmatrix} \begin{bmatrix}1\\-1\end{bmatrix} =\begin{bmatrix}1\\1\end{bmatrix}. \]

Thus \(Q'=K'=\begin{bmatrix}1&1\\1&1\end{bmatrix}\). The scaled score and masked score matrices are

\[ S=\frac{Q'K'^{\mathsf T}}{\sqrt2} =\begin{bmatrix}\sqrt2&\sqrt2\\\sqrt2&\sqrt2\end{bmatrix}, \qquad A=\begin{bmatrix}\sqrt2&-\infty\\\sqrt2&\sqrt2\end{bmatrix}. \]

Row-wise softmax gives \(P=\begin{bmatrix}1&0\\0.5&0.5\end{bmatrix}\). Therefore

\[ O=PV =\begin{bmatrix}1&0\\0.5&0.5\end{bmatrix} \begin{bmatrix}1&1\\1&-1\end{bmatrix} =\begin{bmatrix}1&1\\1&0\end{bmatrix}. \] \[ Y=X^{(\ell)}+O =\begin{bmatrix}2&2\\2&-1\end{bmatrix}. \]

After the second normalization, suppose the learned SwiGLU produces the toy update \(F=\begin{bmatrix}0.1&-0.2\\0.3&0.1\end{bmatrix}\). The block output is

\[ X^{(\ell+1)}=Y+F =\begin{bmatrix}2.1&1.8\\2.3&-0.9\end{bmatrix}. \]

The chosen \(F\) stands in for the SwiGLU calculation from Lesson 22; every attention-stage number above is calculated explicitly.

Code checkpoint · assemble the TransformerModel

The block pieces from Lessons 15–22 now form a decoder-only language model. Every block preserves \([B,T,D]\); the language-model head converts the final features into one logit per vocabulary item.

Show the final assembly
class TransformerModel(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.token_embedding = nn.Embedding(config.vocab_size, config.hidden_size)
        self.layers = nn.ModuleList(
            TransformerBlock(config) for _ in range(config.num_layers)
        )
        self.final_norm = RMSNorm(config.hidden_size, config.norm_eps)
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
        self.lm_head.weight = self.token_embedding.weight

    def forward(self, input_ids):
        x = self.token_embedding(input_ids)  # [B, T] -> [B, T, D]
        for layer in self.layers:
            x = layer(x)                    # [B, T, D] -> [B, T, D]
        return self.lm_head(self.final_norm(x))  # [B, T, vocab]

The complete assembly stays in this lesson so the shape ledger, block trace, and implementation can be read together. You can also download transformer_model.py.

Trace it: Starting from input_ids, name the final axis at each of the three comments.

Engineering bridge · grow this into MiniMind

The course TransformerModel is intentionally readable. The full target at model/model_minimind.py in the MiniMind checkout keeps the same pre-norm block equation, then adds runtime contracts around it. Use the table as an implementation order; each row has a shape or numerical check that catches regressions early.

StageMiniMind detail to addCheck before moving on
1 · ConfigMiniMindConfig owns \(H\), \(H_{\mathrm{kv}}\), \(D_h\), RoPE limits, optional YaRN rope_scaling, dropout, and the use_moe switch.Reject incompatible head counts and odd \(D_h\); print a shape ledger.
2 · AttentionUse GQA projections, per-head q_norm/k_norm, RoPE, and repeat_kv. Precompute cosine/sine tables with precompute_freqs_cis.Expanded K/V agree with Q on \(H\); output returns \([B,T,D]\).
3 · DecodeConcatenate past_key_value along tokens and slice RoPE tables from start_pos.One-token cached logits match full-prefix logits within tolerance.
4 · RuntimeSelect Flash Attention for eligible prefixes; otherwise apply causal and padding masks explicitly with FP32 softmax.Test both branches with a padding mask and with a non-empty cache.
5 · Feed-forwardKeep dense SwiGLU; optionally swap in top-k MOEFeedForward and accumulate its auxiliary loss.Every route returns \([B,T,D]\); the dense path remains the baseline.
6 · Model wrapperAdd final RMSNorm, tied embedding/lm-head weights, shifted label loss, and generation sampling.Logits are \([B,T,V]\); tied weights are the same parameter object.

RoPE lifecycle: MiniMind registers the precomputed tables as non-persistent buffers so they move with the model without bloating checkpoints. Its forward path also rebuilds them on the hidden-state device if meta-device initialization left zeroed tables.

Show the MiniMind block contract
residual = hidden_states
hidden_states, present_key_value = self.self_attn(
    self.input_layernorm(hidden_states), position_embeddings,
    past_key_value, use_cache, attention_mask
)
hidden_states += residual
hidden_states = hidden_states + self.mlp(
    self.post_attention_layernorm(hidden_states)
)
return hidden_states, present_key_value

Wrapper contract: the Hugging Face model returns logits, shifted-label loss, auxiliary MoE loss, and past_key_values. Its generation loop feeds only the uncached suffix, then applies temperature, top-k, and top-p sampling.

Suggested build sequence: port the dense block, add GQA, add cache-aware decoding, verify Flash/fallback parity, then add MoE and the Hugging Face wrapper. Keep one small deterministic input fixed for every comparison.

Retrieval check

Answer once before reopening the code; use the feedback to correct your explanation.

Where does RoPE occur in this block?

STAGE 4 · PROVE IT

Capstone studio · narrate → build → verify

  1. Without looking above, write the eight stages of the block in order.
  2. For \(B=2,T=5,d_{\text{model}}=12,h=3\), make a shape ledger for split Q, one head's score matrix, concatenated output, and final block output.
  3. Repeat the toy multiplication \(PV\) and residual addition \(X^{(\ell)}+O\).
  4. Explain why \(R_m^{\mathsf T}R_n=R_{n-m}\) matters specifically inside Step 4.
  5. Name the two places where information is mixed across features and the place where it is mixed across tokens.
  6. Teach the block aloud in under two minutes without saying “it just learns it.” Name the input and output of every stage.
Check solutions
  1. Normalize; project Q/K/V; apply RoPE to Q/K; score and causally mask; softmax and blend V; concatenate heads and apply \(W_O\); first residual; normalize, SwiGLU, and second residual.
  2. Split Q: \(2\times3\times5\times4\); one head score per batch: \(5\times5\); concatenated output: \(2\times5\times12\); final output: \(2\times5\times12\).
  3. \(O=\begin{bmatrix}1&1\\1&0\end{bmatrix}\); \(Y=\begin{bmatrix}2&2\\2&-1\end{bmatrix}\).
  4. It turns the rotated query-key dot product into a comparison that depends on relative displacement \(n-m\).
  5. Projection/output matrices and the FFN mix features; attention's \(PV\) mixes information across allowed token positions.
  6. Use the reference sheet to check omissions after speaking, not during the first attempt.

Primary sources: Vaswani et al.; Su et al., RoFormer; Touvron et al., LLaMA.