← Hussain Abuwala

LLM from scratch

A language model built step by step, from counting to a small Transformer. 3 of 14 episodes published.

Each episode has a theory video worked by hand, a build video, a notebook, and tests that fail if the concept is implemented wrongly. The number worked by hand in the theory video and the number printed by the code must match. The series ends with a small decoder-only Transformer, trained on a laptop.

Python · NumPy · Jupyter · Matplotlib · unittest

#EpisodeCode
Part 1 · Count
1The smallest language modelPython
2Trigrams and the sparsity wallPython
Bridge: counting to learning—
Part 2 · Learn
3From counts to weightsNumPy
4Gradients, by handNumPy
5Autograd from scratchPython
6Enter PyTorchPyTorch
7Embeddings and the MLP modelPyTorch
8Making training workPyTorch
Part 3 · Attend
9Why fixed context breaksPyTorch
10Self-attention from scratchPyTorch
11Multi-head attention and the blockPyTorch
12Build the GPTPyTorch
13Tokenization for realPyTorch
14Train it and make it talkPyTorch

1. The smallest language model

A character bigram model predicts the next character from the current one, using counts of which character follows which. The episode builds the same model twice: once by hand on two names, then in code on a real dataset.

anvendstart1000a0¼¼½n½½00v1000Next-character probabilities from anna and ava. The path for ana is in red.By hand, on two names. Trained on anna and ava, small enough to count on paper, the model gives the unseen name ana probability 1 · ¼ · ½ · ½ = 1/16, a loss of ln 2 ≈ 0.693 nats per prediction. Try it with this two-name model:

Heatmap of how often each character follows each other character in the training namesHow often each character follows each other one in the training names (darker is more). This table is the whole model.In code, on real names. The same model in Python, trained on 32,033 US first names (Social Security data for 2018, from Andrej Karpathy’s makemore). Removing repeated spellings leaves 29,494 names, split 80/10/10 into training, validation and test. Loss on the 21,053 test predictions, in nats per prediction:

ModelTest loss
Uniform: every next character equally likely3.296
Unigram: letter frequencies, ignoring context2.819
Bigram, add-k smoothing (k = 0.3)2.460

Smoothing adds a small count k to every pair, so a pair never seen in training no longer gets probability zero. k was chosen on the validation names.

2. Trigrams and the sparsity wall

The same 29,494 US first names and the same training, validation and test split as episode 1, but the model now looks at more than one previous character: 2 (a trigram), then 3, 4 and 5. Training loss keeps falling as the context grows. Loss on held-out names is lowest at three characters and rises after that.Smoothing k was chosen on the validation names; the frozen settings were then scored once on test. The result holds for this dataset and add-k smoothing, not for every n-gram method.

Loss by context length: training loss falls from 2.45 to under 1.8, validation loss is lowest at 3 characters and rises to 2.39 at 5
Loss by number of context characters. Training keeps improving; held-out prediction gets worse after 3.

The reason is thin evidence. With longer contexts, more of the contexts seen in training appear only once, and more held-out predictions need a context that never appeared in training at all.

Left: share of training contexts seen only once rises from 0% at 1 character to 59.1% at 5. Right: share of validation predictions with an unseen context rises from 0% to 13.0%
Left: training contexts seen only once. Right: held-out predictions whose context never appeared in training.

Test loss for every context length, with the number of the 21,053 test predictions whose context was never seen in training:

ContextModelkTest lossUnseen-context predictions
1Bigram0.32.4600
2Trigram0.32.2396
34-gram0.12.130148
45-gram0.12.1961,114
56-gram0.12.3872,676

Bridge: counting to learning

What count models can and cannot do, including backoff and interpolation, and the three pieces the next part adds: adjustable weights, training, and a structure that reuses weights. Video, 15 min.

3. From counts to weights

The same bigram task with a table of adjustable scores instead of counts: look up the score row for the current character, turn it into probabilities with softmax, and score the target with negative log-likelihood. Written in NumPy.

One score, changed by hand. To see what a single weight does, the episode changes one score manually on the anna and ava example. This is not training; it only shows how one score moves the probabilities and the loss. With every score at zero, each of the four next characters has probability ¼, and the loss over the nine training targets is ln 4 ≈ 1.386. Setting one score, W[a, end], to ln 2 lowers the loss to 1.331. Setting it to ln 100 overshoots and raises it to 1.807. Finding good values automatically is what training does, starting with gradients in episode 4.