On this page

For a long time ChatGPT felt like magic to me. I used it every day and still couldn't explain what happens between pressing Enter and the first word appearing. So I did what I do with every piece of tech that bugs me: I started taking it apart.

This is the first part of Diving into LLMs, a series where I write down what I learn as I go, the way I'd tell a friend over coffee. No maths beyond multiplying, and every animation runs live in your browser. If one is too busy, press Pause.

Based on Andrej Karpathy's course. This whole series follows Neural Networks: Zero to Hero by Andrej Karpathy, a free video course that builds a language model from nothing, one small step at a time. The path, the order and the ideas behind the code (micrograd, makemore, nanoGPT) are his, and his videos explain them far better than I can. What I add is a beginner's view: my notes, my JavaScript version that I can pause in Chrome's debugger, and the animations. If this post makes you curious, go watch the course.

Where I am. I've finished stage 1 of the course (micrograd, a tiny engine that learns) and I'm starting stages 2 and 3 (makemore and nanoGPT), which will be the next parts of this series. The first half of this post is what I built and measured myself. The LLM section at the end is the bigger picture, explained as simply as I can.

The one idea behind every LLM

Here it is: an LLM predicts the next word. It reads the text so far, gives every possible next word a probability, picks one, adds it to the text, and does it again.

Next-word prediction
The cat sat on the mat and purred.
Sentence finished. Press play to watch it again.
  • “.”53%
  • softly22%
  • loudly10%
  • at9%
  • happily7%
Try it yourself
The model gives every possible next word a probability, one word is picked and added, and the whole thing repeats. The scores here are made up; a real model scores every token in a vocabulary of tens of thousands.

Answering questions, writing code, translating: it's all this one move, repeated very fast. When ChatGPT is "typing", it is writing one token, then the next, then the next.

Play with the temperature slider. At a low temperature the model almost always takes the most likely word: safe and a bit boring. Turn it up and unlikely words get a real chance: more creative, and sometimes nonsense. That's the same "temperature" setting you find in AI tools.

The hard part, of course, is how a pile of numbers learns to give good probabilities. That's where I started.

Part 1: how a machine learns anything

A number that remembers where it came from

The heart of Karpathy's micrograd is a tiny class called Value. It holds a number, but it also remembers which numbers it was made from, and how. Write y = a × b + c with Values and you don't just get 4. You get a little recipe you can walk backwards.

Forward, then backward
ainputdata 2grad −3binputdata −3grad 2cinputdata 10grad 1da × bdata −6grad 1yd + cdata 4grad 1
Backwarda.grad = −3 means: nudge a up by 0.01 and y moves by about −0.03. That is all a gradient is.
Try it yourself
y = a × b + c = 4
The first thing micrograd taught me. Values flow forward through the recipe, gradients flow back through the same boxes. Each gradient says how much the output would move if that one number moved.

Walking backwards gives every number a gradient: how much the final result would move if you nudged that one number a little. a has a gradient of −3, so if you nudge a up by 0.01, y goes down by about 0.03. That's the whole concept.

There's no magic in the backward walk either. Each box knows one small rule (a plus passes the gradient straight through, a times multiplies it by the other input), and you apply the rules from the end back to the start. Mathematicians call this the chain rule. The code calls it backward().

Rolling downhill

To learn, the network needs one number that says how wrong it is right now. That's the loss: bigger is worse. Now picture the loss as a landscape and the network's settings as a ball. The gradient tells you which way is downhill, so you take a small step that way, and repeat.

Rolling downhill
weight →↑ loss (how wrong)best weight
step 8slope −0.15almost flat: done
Try it yourself
Training in one picture. The curve is “how wrong am I” for every value of one weight. The slope at the ball is the gradient: it says which way is downhill. Each step moves against the slope, by the slope times the learning rate, so steps get smaller near the bottom. Push the learning rate past 1.8 and watch it fly off.

The size of the step is the learning rate. Too small and training takes forever. Too big and you jump right over the valley. Real networks do this with billions of weights at once, which is impossible to picture, but the move for each single weight is exactly this one.

The training loop, with real numbers

Put the pieces together and you get the loop every neural network uses, from a toy to GPT:

  1. Guess: run the examples through the network.
  2. Measure: turn all the mistakes into one number, the loss.
  3. Backward: work out the gradient of every weight.
  4. Nudge: move every weight a little downhill.

micrograd's demo trains a network with 337 weights to tell which of 100 dots lie inside a circle. These are the real numbers from my run:

The training loop, real numbers
GuessMeasureBackwardNudgestep900.00.40.81.2losstraining step →
Done (demo.py prints every 10th step). Same four moves every time.loss 0.0687accuracy 98%
Four moves, repeated. These are the real numbers from micrograd’s demo: 337 weights learning which of 100 dots lie inside a circle. Each lap of the ring stands for 10 training steps. The loss falls from 1.13 to 0.07 and accuracy climbs from 52% to 98%.

The first time I stepped into backward() in the debugger and watched 65,818 little Values hand their gradients back to each other, for one single training step, the magic went away. In a good way. It's just a lot of very simple arithmetic.

A small rabbit hole I don't regret: I wanted my JavaScript version to print exactly the same numbers as the Python one. That meant porting Python's random number generator (the Mersenne Twister) to JavaScript, so both start from identical random weights. Now python3 demo.py and my browser console agree to the last digit, which makes comparing the two very relaxing.

Part 2: from numbers to words

So far the network sorts dots. How does that become language?

The simplest language model: counting letters

Before any neural network, you can build a language model by counting. Take a list of names, count which letter follows which, and turn the counts into probabilities. Then write new names one letter at a time, rolling the dice at every step. This is where Karpathy's makemore starts, and here it is running on the 325 names in my practice folder:

A language model that only counts
Writing a name:
mint
Picked “end”: the name is finished.
lfuoenlthaurcamjojuaneconcriselanedkbertiademint
  • h16%
  • end15%
  • o12%
  • e10%
  • t10%
  • i7%
Try it yourself
The simplest language model there is, running on the 325 names in my practice folder. It counted which letter follows which, and now writes new names one letter at a time. It only ever looks at the previous letter, which is why it makes “juan” and “mint” but also “lthaurcamjo”.

It gets surprisingly far for something that only counts: "juan", "mint", "crisel". But it only ever sees one letter back, so it has no idea it's halfway through a name, and you also get "lthaurcamjo". Every better model is an answer to one question: how do we let it see more of the past?

Words become lists of numbers

Neural networks only work with numbers, so every token gets a list of numbers, called an embedding. Nobody writes these by hand. They start out random and get nudged by training like every other weight. The surprising part is what comes out: tokens that are used in similar ways end up with similar lists. "cat" ends up close to "dog" and far from "carburetor", without anyone ever telling the model what a cat is.

Attention: letting words look at each other

The big idea behind modern LLMs is attention (it's the heart of the Transformer, the "T" in GPT). For every word, the model works out how much each earlier word matters for understanding it, and mixes in information from those words.

Attention: looking back
  • The2%
  • animal55%
  • didn’t3%
  • cross6%
  • the2%
  • street20%
  • because4%
  • it8%
  • washidden
  • toohidden
  • tiredhidden
Reading “it”“it” has to work out what “it” means. It looks back hardest at “animal”, a little at “street”.
For every word, the model works out how much each earlier word matters for it, then mixes in information from those words. Later words are hidden, because when the model writes, they don’t exist yet. The percentages are made up to show the idea; a real model learns them.

Take "it" in that sentence. To continue well, the model has to know that "it" means the animal, not the street. Attention is what lets it look back and connect the two. It never looks forward: when the model writes, the future words don't exist yet.

The LLM section: how ChatGPT-style models work

Now everything comes together. A large language model is these same pieces, stacked up and scaled up enormously.

One token, start to finish
  1. 1. Text
    The cat sat on
  2. 2. Tokens
    The cat sat
  3. 3. Numbers
  4. 4. Transformer
    × dozens of blocks
  5. 5. Next token
    on down still
RepeatThe picked token is glued onto the text, and everything runs again for the next token. That loop is the whole “writing”.
Everything a chat model does when it writes, one token at a time. The numbers are only for illustration; real models use vocabularies of tens of thousands of tokens and embeddings with hundreds or thousands of numbers each.

The whole journey in five steps

  1. Tokens. Your text is cut into tokens: whole words, pieces of words, punctuation.
  2. Embeddings. Each token becomes a long list of numbers.
  3. Transformer blocks. The lists go through a stack of identical blocks. In each one, attention mixes in information from earlier tokens, then a small network (like the one in micrograd) reworks each token. Large models stack dozens of these blocks.
  4. Probabilities. At the end, the model scores every token in its vocabulary and turns the scores into probabilities.
  5. Pick and repeat. One token is picked (this is where temperature comes in), glued onto the text, and everything runs again.

How it learns to do that

  • Pre-training. The model reads an enormous amount of text and plays one game, over and over: guess the next token, measure how wrong it was, backward, nudge. It's the exact loop from my 100 dots, just unimaginably bigger.
  • Fine-tuning. A model that has only read the internet continues text; it doesn't answer you. So it's trained further on example conversations, which teaches it to behave like an assistant.
  • Human feedback. People compare two answers and pick the better one, and the model is tuned towards the answers people prefer. A lot of the helpful, polite tone comes from here.

What an LLM is not

Knowing this changed how I use these tools:

  • It's not a database. It doesn't look facts up (unless the app gives it a search tool). Everything it "knows" is squeezed into its weights.
  • It can be confidently wrong. It's trained to write plausible text, and a wrong answer can look just as plausible as a right one. That's what people call hallucination.
  • It doesn't learn from your chat. The weights are fixed once training is over. What it remembers during a conversation is simply the text in front of it, its context window.

A sense of scale

ModelWeights (parameters)
My micrograd network337
GPT-2 (2019)1.5 billion
GPT-3 (2020)175 billion

Same four-step loop, same kind of arithmetic. Mostly, the numbers got bigger.

What surprised me most

  • It's the same loop all the way up. Guess, measure, backward, nudge. I expected something cleverer at the top, and there isn't. There's just a lot more of it.
  • Nobody writes the rules. Nowhere in the code does it say "names often end in a". The model finds that on its own, because finding it lowers the loss.
  • A gradient is just "nudge it and see", computed cleverly for millions of numbers at once.
  • Debugging beats reading. Pausing on one line and looking at real values taught me more than any diagram, including the ones in this post.

What's next in the series

  • Part 2: makemore. The counting model from above, then a real neural network that writes names. This is where letters get their own embeddings, and where I'll find out whether my names get better than "lthaurcamjo".
  • Part 3: nanoGPT. A small GPT built from scratch, with attention and the Transformer, trained to write text in the style of Shakespeare.

I'll publish each part when I've built it myself, not before.

If you want to dive in with me, start where I started: