Two tiny transformers
I spent the last week working through Karpathy’s “Let’s build GPT” and typing out a character-level transformer from scratch: embeddings, positional encodings, the attention blocks, the training loop. Nothing about it is novel — that’s the point. There’s a real difference between having watched someone explain multi-head attention and having debugged your own.
The model is tiny — 128-dimensional embeddings, a context of 32 characters — and it trains on the ~1MB tiny Shakespeare dataset. At initialization it produces noise:
l me son?dEiP ,ydh,cGPkaF ?SV
kkdQOi'
After training, this:
ISABELLA:
Let, and wilts; let see of you'l come thou,
Which which'd computs fallain, unly mrobe noisur
It’s nonsense, but it’s Shakespeare-shaped nonsense: character names in caps with a colon, line breaks in the right places, words that feel almost Elizabethan. Everything from “what a letter is” upward was learned from scratch. For calibration, the notebook also has a bigram baseline — seeing how much better the transformer gets from the same data is most of the lesson.
Then I pointed the same architecture at a different problem: arithmetic on
6-digit numbers, one character at a time. Instead of prose the training data
is generated equations like 483920+7=, and the model has to complete the
answer. One trick that matters: the answer is written back to front, so the
model produces the least significant digit first — the same order you’d do
it on paper, which means the carry it needs is always already in context. I
like this task because, unlike Shakespeare, there is an exact right answer —
the model can’t hide behind style. It has to learn something like an
algorithm, carries and all, purely from next-character prediction.
Notebooks: nanogpt.ipynb and addition_lm.ipynb.