Build GPT

Can you trace one token from text to prediction?

About 50 minutes · open lesson · LLM engineering

Can you follow one token through a GPT you assembled yourself?

Type “What is a cat?” into a GPT, and a fraction of a second later it has chosen the next token. In between, the text goes through a fixed sequence of steps. The tokenizer turns it into token ids. The embedding table turns each id into a vector. A stack of Transformer blocks lets those vectors read from each other through masked, multi-head attention. Out of the other end comes the next token, which the generation loop appends to the text before going round again.

You have built each of those steps separately. Joined together, they make a GPT with the same wiring as GPT-2, in about 100 lines of model code. Two exact questions are worth answering about the result.

First, what happens to one token, from the moment it is typed to the moment the next one is chosen? At every step you should be able to say what shape the data has. Second, how many parameters does the whole model have, and where do they sit? For your tiny GPT and for GPT-2 small you will be able to count them exactly, by hand.

Pause and predict After the last block, every token is a context-aware vector. Is that the next token yet?

What is still missing?

A stack of blocks takes (T, D) and returns (T, D): one context-aware vector per token. That is not yet a prediction.

To generate text you need to know which character comes next. Two small gaps remain between the output of the blocks and that answer.

Pause and predict The last block gives D numbers per position. How do you get a score for every token in the vocabulary?

The second gap is the loop around the model. A GPT predicts one token. To write a sentence, you sample a token, append it to the input, and run the whole model again.

You built that loop in Predicting the next token. Here it gets a real model inside.

One practical worry: this lesson’s code is in PyTorch, where earlier lessons used NumPy. Is the framework hiding some extra machinery?

A mental model

When you wire up a model, draw the data rather than the boxes. Here is the whole machine in one picture, with the data drawn as grids, one row per token.

Follow the numbers 1 to 9 and watch the shape at each stage. Every stage is a lesson you have done.

Pause and predict Compare the grid that leaves the last block with the grid that went into the first one. It is…

The head (language-model head)
The last layer: one matrix multiply that turns a token’s D-number vector into V raw scores, one for every token in the vocabulary.
For example: D = 128, V = 65: the vector [0.3, −1.2, …] (128 numbers) becomes 65 scores, such as “e”: 2.1, “t”: 1.7, “z”: −3.0.

A GPT is these 6 numbers

The top of the file holds the entire configuration of the model. In its wiring, GPT-2 is the same code with bigger numbers.

Five of them fix the architecture. The sixth, dropout, only matters during training (it randomly zeroes some numbers so the model cannot lean on any single one).

Every module, and where you built it

in tiny_gpt.pywhat it isyou built it in
tok_embembedding lookupEmbeddings
pos_emba vector per slotThe Transformer block
CausalSelfAttentionmasked multi-head attentionAttention, Masks and heads
FeedForwardthe MLPNeurons and layers
Block, LayerNorm, x + …residuals and normalisationThe Transformer block
F.softmax, F.cross_entropyscores → probabilities, and the lossSoftmax, Next token
loss.backward()backpropagation, automatedBackpropagation
opt.step()the training loopGradient descent
generate()the autoregressive loopNext token

Assembling a GPT is like wiring up services you have already written and tested: the new work is checking that each output shape matches the next input. Unlike services, these have no hand-written logic. Each is a bag of numbers, and all of them are tuned jointly by one loss at the very end.

Let's see the numbers

Count the repository’s tiny GPT by hand: V = 65 characters, context 64, D = 128, 4 blocks.

Pause and predict Before the table: which part of this tiny GPT holds the most parameters?

The math

One line for the whole model, reading inside-out:

For any model of serious size you can forget everything except 12 · N · D². GPT-2 small: 12 × 12 × 768² ≈ 85M of its 124M.

What you should remember

  1. GPT = tokenizer → token + position embeddings → N Transformer blocks → final LayerNorm → linear head → logits → softmax → sample → append → repeat. Shapes: (T) → (T, D) → … → (T, D) → (T, V), and generation uses only the last row.
  2. A GPT is a handful of config numbers. GPT-2 is the same code with bigger ones. Parameters ≈ 12 · N · D² plus embeddings; most live in the MLPs.
  3. The model can only see context_len tokens. Older tokens are cropped away and have no influence.
  4. PyTorch automates two things you did by hand: recording the forward pass and replaying the backward rules. An untrained GPT is a correct pipeline filled with the wrong numbers.

Where this appears in a real LLM

How far is our model from the real ones? In structure it is close to them. In size it is very far away.

our tiny GPTGPT-2 small (2019)Llama 3 70B (dense, 2024)DeepSeek-V3 (mixture-of-experts, December 2024)
blocks (n_layer)4128061
vector width (n_embd)1287688,1927,168
attention heads41264 (sharing 8 key/value heads)128 (keys and values rebuilt from one cached vector of 512 numbers)
context window641,0248,192 (128K in version 3.1)128K
parameters0.81M124M≈ 70,000M, all used for every token≈ 671,000M in total, ≈ 37,000M used per token
tokenizer65 charactersBPE, 50,257 tokensBPE, ≈ 128,000 tokensBPE, 129,280 tokens
positions / norm / MLPlearned table / LayerNorm / GELURoPE / RMSNorm / SwiGLURoPE / RMSNorm / SwiGLU, split into 256 small experts plus 1 shared, 8 of the 256 used per token
In this lesson
  • Character-level, trained on 1 MB of Shakespeare
  • Runs on a laptop CPU in minutes
  • Every line readable: about 100 lines of model code
  • The browser tracer: untrained, D = 8, 2 blocks
In a real system
  • Sub-word tokens, trained on trillions of tokens
  • Thousands of GPUs for weeks; the model is split across many devices
  • The same pipeline: embed, N blocks, norm, head, softmax, sample
  • Changed details inside the boxes: RoPE, RMSNorm, SwiGLU, grouped-query attention, no biases

Below is GPT-2 small itself, with the weights OpenAI trained, running in your browser. Every stage is a box you built in this part, and each one names the lesson that built it. It also has a page of its own, with more room.

It has the same wiring as tiny_gpt.py. The difference is the numbers inside, and those come only from training.

One difference: the wiring matches, but the starting numbers do not. tiny_gpt.py keeps PyTorch’s defaults, which fill the embedding table with numbers of size about 1. GPT-2 starts every weight small (standard deviation 0.02) and shrinks the layers that write into the residual stream by a further 1/√(2N) for N blocks. You will see in the next lesson why that choice matters on the very first step.

Before its first training step, tiny_gpt.py reports params 0.81M and writes a line of gibberish. The pipeline is correct and the numbers in it are wrong. The next lesson is about finding the right numbers.

Open the interactive lesson

The interactive lesson adds the predictions with their answers, the labs, and graded practice with spaced review.