← All courses ← Course home

Lesson 03 of 12 · Phase 2 Model Internals

Build a transformer from scratch (TinyGPT)

Attention is matmuls plus a causal mask. Write TinyGPT until you can explain every tensor without hand-waving.

Agent brief (llms.md)

Attention Is All You Need is the paper title you should know. The replica you will ship is TinyGPT: embeddings, causal self-attention, MLP, residuals, layer norm, and a tied or untied language-model head.

Do not wave your hands at multi-head attention. Write the shapes. B batch, T time, C width, H heads, D = C / H head dim. Q, K, V are produced by one linear map and split. Scores are QKT / sqrt(D). The causal mask forbids looking forward. Softmax is over keys. The output is a weighted sum of V, then a projection back to C.

Coding: CausalSelfAttention

This is the starter block. Type it. Do not paste it into a repo you never run.


import math
import torch
import torch.nn as nn
import torch.nn.functional as F

class CausalSelfAttention(nn.Module):
    def __init__(self, n_embd: int, n_head: int, block_size: int, dropout: float = 0.0):
        super().__init__()
        assert n_embd % n_head == 0
        self.n_head = n_head
        self.n_embd = n_embd
        self.c_attn = nn.Linear(n_embd, 3 * n_embd)
        self.c_proj = nn.Linear(n_embd, n_embd)
        self.resid_dropout = nn.Dropout(dropout)
        self.register_buffer(
            "bias",
            torch.tril(torch.ones(block_size, block_size)).view(1, 1, block_size, block_size),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        B, T, C = x.size()
        q, k, v = self.c_attn(x).split(self.n_embd, dim=2)
        q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
        k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
        v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
        att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
        att = att.masked_fill(self.bias[:, :, :T, :T] == 0, float("-inf"))
        att = F.softmax(att, dim=-1)
        y = att @ v
        y = y.transpose(1, 2).contiguous().view(B, T, C)
        return self.resid_dropout(self.c_proj(y))

Then wrap it in a block: norm, attention, residual, norm, MLP, residual. MLP is usually 4C wide with GELU or SiLU. Positional information can be a learned table of length block_size. That is enough to train on Shakespeare or a tiny code dump.

What you must be able to say out loud

Assignment

Opinion checkpoint

Write this down. If you cannot redraw CausalSelfAttention from memory, you do not understand a decoder. Frameworks will not save you in an incident review.

Core project 2: TinyGPT that trains on a tiny corpus.

Next: Scaling laws, data, and synthetic data.

Key concepts

  • A decoder block is norm, causal attention, residual, norm, MLP, residual.
  • QKV are linear maps. The mask enforces the next-token contract.
  • Attention scores scale with 1/sqrt(head dim) to keep softmax stable.
  • TinyGPT is the replica. 800B is the same diagram with logistics.

Takeaways

  • Type CausalSelfAttention and train it on a tiny corpus.
  • Fail the causal mask on purpose so you know the cheat.
  • Name the paper by title: Attention Is All You Need. Then ship the code.