It's autocomplete that read everything
Strip away the hype and a language model does one humble thing: guess the next word. The magic is what it had to learn to get good at it.
You already use a tiny language model every day. When your phone keyboard suggests the next word, it's guessing from the few words you just typed. It's often wrong, because it has read almost nothing.
Now imagine that same idea, but the model has read essentially everything humans have written — books, code, conversations, science, the whole messy internet — billions of times over. To get genuinely good at guessing the next word across all of that, it had no choice but to absorb grammar, facts, reasoning patterns, the structure of arguments, how code works. That absorbed understanding is the whole point. The "guess the next word" game is just how it was forced to learn.
A student who, to ace one weird exam — "predict the next word in any text ever written" — ended up having to learn a little bit of everything. The exam was trivial. The studying made them brilliant.
So whenever you read "the model generates text," picture this loop: it looks at everything so far, produces a ranked guess for the next word, picks one, adds it, and looks again. One word at a time, very fast. Everything in this guide is about either how that guessing machine is built (Act I) or how we shape its guesses to be helpful, well-mannered, and correct (Acts II–IV).
Tokens: the puzzle pieces it reads
A model doesn't see letters or words. It sees chunks called tokens — and each one is really just a number.
Here's the first surprise: the model never sees the word "tokenization." It sees pieces — maybe token, iz, ation. These pieces are called tokens, and a typical token is about ¾ of a word. Common words are a single token; rare ones get split into parts; even an emoji or a snippet of code becomes tokens.
Why chop things up this way? It's a sweet spot. Whole words would need a dictionary of millions and still miss new ones. Single letters would make sequences painfully long. Sub-word pieces give you a manageable set that can still spell out anything.
The vocabulary
The full set of pieces a model is allowed to use is its vocabulary — usually 30,000 to 250,000 tokens, each with a fixed ID number. A bigger vocabulary means fewer pieces per sentence (faster, cheaper) but a heavier model at both ends. The vocabulary is built once by scanning a big pile of text and repeatedly merging the most common pairs of characters into tokens until the list is full.
+Go deeper
What tokens really look like, what a real vocabulary file is, and the weird failures it causes
What tokens actually look like
Run a few strings through a typical tokenizer (roughly — exact splits vary by model):
| Text | Tokens | What to notice |
|---|---|---|
hello world | ["hello", " world"] | The space is glued to the front of " world". Spaces live inside tokens. |
tokenization | ["token", "ization"] | Common stem + ending. Frequent chunks stay whole. |
antidisestablish… | ["ant","idis","establish","ment","arian","ism"] | A rare word shatters into many pieces. |
The · the · the | three different IDs | Capitalisation and a leading space each change the token. |
1234567 | ["123","4567"] | Numbers chop inconsistently — a big reason arithmetic is shaky. |
| an emoji / 中文 | several tokens | Under the hood it's bytes, so non-Latin text costs more tokens. |
Common words are a single token; rare or invented ones get spelled out in sub-word pieces. English averages about ¾ of a word (~4 characters) per token.
What a real vocabulary file looks like
The vocabulary isn't an abstraction — it's a file on disk, and one common format (OpenAI's tiktoken) stores every token as base64 of its bytes, paired with its ID. Decode the base64 and the actual token text pops out:
IHRoZQ== 1169 # → " the" (note the leading space is part of it) aGVsbG8= 31373 # → "hello" 0LTQsA== 48745 # → "да" (Cyrillic — several bytes per letter)
The base64 here is a storage wrapper, not the tokenization: tokens are raw bytes, and they include things that wreck a plain text file (leading spaces, newlines, punctuation, and partial multi-byte sequences for Cyrillic or emoji). Base64 turns any bytes into safe printable ASCII so the file stays clean. The vocabulary, written down — nothing more.
Two conventions you'll meet in the wild
| Format | How a token is stored |
|---|---|
| tiktoken | base64 of the bytes + an ID number (the file you saw). |
Hugging Face vocab.json | Visible placeholder characters — a leading space becomes Ġ, so " the" shows as Ġthe. |
SentencePiece .model | A binary protobuf — not human-readable at all. |
And cp1251 (Windows-1251) is a different kind of encoding: it maps bytes ↔ Cyrillic letters (which characters bytes mean), whereas base64 maps bytes ↔ safe ASCII (how to store bytes). Seeing both around a Russian text file makes sense: one says "these bytes are Cyrillic," the other says "here are the token bytes, packed for the file."
Ġthe tokens), you're looking at the vocabulary itself — the model's entire alphabet, serialized.Why it matters — and your security angle
- Cost & context are measured in tokens — you're billed and capped by them, and a bigger vocabulary means fewer tokens per sentence.
- Arithmetic & letter-counting stumble because the model sees chunks, not digits or letters — "how many r's in strawberry?" is hard when it sees
str/aw/berry. - Other languages cost more tokens (they're under-represented in the vocabulary), so they're pricier and sometimes weaker.
- It's an attack surface. Odd Unicode and token boundaries enable "token smuggling" past filters, and glitch tokens — strings like the infamous
SolidGoldMagikarpthat sat in the vocabulary but were barely trained — make models behave bizarrely when they appear. A genuinely interesting red-team thread.
Embeddings: turning words into a map of meaning
A number like "7782" means nothing on its own. The first thing the model does is turn each token into coordinates on a vast invisible map — where distance is meaning.
A token ID is just a name tag; it carries no meaning. So the model's very first layer looks each ID up in a giant table and replaces it with a long list of numbers — often thousands of them. Think of those numbers as coordinates. They place the token as a single point on an enormous map.
This map isn't drawn by hand. The model arranges it during training so that tokens used in similar ways end up near each other. "King" and "queen" land in the same neighbourhood. "Banana" is far away in fruit-country. Even relationships become directions you can travel: the step from "man" to "woman" is roughly the same step as "king" to "queen." That single list of coordinates is called an embedding.
This is why models feel like they "understand" — they're doing geometry on meaning. It's also the engine behind search and the "look things up" trick we'll meet in Act IV: to find relevant text, you just look for nearby points on the map.
+Go deeper
Vocabulary, the embedding matrix, static vs contextual, and where the weights live
Vocabulary, in depth
The fixed set of tokens (30k–250k), each with an ID. It's built once by scanning text and repeatedly merging the most common character pairs (Byte-Pair Encoding). Byte-level BPE means anything decomposes to bytes — no "unknown token." Beyond words there are control tokens (begin/end, and chat markers like <|im_start|> that delimit roles). Add new tokens and you must train their fresh embedding rows, or they stay random noise.
The embedding matrix
A lookup table of shape [vocabulary × hidden] — pick a token's row, get its vector. The width hidden (768 small → 4096–8192 large) is the model's "thickness" and stays constant through every layer. The vectors start random and the meaning-geometry is learned during pretraining.
Static vs contextual — the nuance most people miss
The embedding-matrix vector is static: "bank" is the same everywhere. As it flows up through attention, its hidden state becomes contextual — "river bank" and "bank account" diverge. (Old word2vec = static; transformer hidden states = contextual.) At the very end, an output head of shape [hidden × vocabulary] turns the final vector back into a score per token — and it's often the same matrix as the input embedding ("weight tying"), saving a big chunk of parameters.
Sentence embeddings & embedding models
Pool a whole text's token vectors into one vector and you can compare entire documents by meaning. Dedicated embedding models (E5, BGE, OpenAI's, your VibeGuard ONNX model) are trained so similar meanings sit close. This is the RAG engine (Ch 15): a fast bi-encoder retrieves candidates, a precise cross-encoder reranks them.
Where the weights actually live
| Component | Share |
|---|---|
| Feed-forward (FFN) matrices | the largest slice (~⅔) — most of the "knowledge" |
| Attention projections (Q/K/V/O) | next biggest |
| Embedding + output head | scales with vocab (big vocab = big matrices) |
Full fine-tuning nudges all of these; LoRA adds small notes without touching them; quantization stores the same numbers smaller. The weights, collectively, are the model's knowledge.
Attention: how words look at each other
This is the idea that made modern AI possible. It's simpler than it sounds: to understand a word, let it glance at the other words and decide which ones matter.
Read this sentence: "The trophy didn't fit in the suitcase because it was too big." What does "it" mean — the trophy or the suitcase? You know instantly it's the trophy. How? You let "it" look back at the other words and lean on the one that makes sense.
That move — a word gathering meaning from other words — is called attention, and a model does it for every token at once. Each word looks around, decides how much to "pay attention" to every other word, and rebuilds its own meaning as a blend of the ones it cared about. After this, "bank" near "river" has quietly absorbed some "river," and now means the right thing.
Every word fills out a tiny dating profile. A Query says "here's what I'm looking for." A Key says "here's what I offer." A Value is "here's what I'll actually share if you pick me." Each word matches its query against everyone's keys, then blends the values of whoever matched best. "It" is looking for a thing-that-can-be-big; "trophy" advertises exactly that; they match; "it" pulls in "trophy."
Stacking it up: the transformer block
Attention is one half of the model's repeating unit. The other half is a small per-word "thinking" step called a feed-forward network — once a word has gathered context, this step processes it on its own. Wrap both in "shortcuts" (so nothing important gets lost) and a bit of math hygiene to keep numbers stable, and you have a transformer block. Stack that block dozens of times and you have the model.
Three shapes: encoder, decoder, or both
Once you can stack blocks, one quiet design choice splits the whole field into three families: which direction may the words look? Let every word look both ways and you get a brilliant reader that can't write. Let each word look only backwards and you get a writer — because to honestly generate the next word, you must not be allowed to peek at it. Bolt a reader onto a writer and you get a translator.
Here's a common surprise: the assistants you actually chat with — GPT, Claude, Llama, Mistral — are decoder-only, not encoder-decoder. There's no separate "understanding" module that reads your message before a "writing" module replies. One causal stack does both jobs: it understands your prompt simply by predicting its way through it, then keeps predicting to produce the answer. The encoder-decoder shape is the translation-era design (it's what the original 2017 Transformer paper built); the encoder-only shape lives on quietly inside search and the embedding models from Chapter 3.
+Go deeper
Encoder vs decoder vs encoder-decoder — the three families, mapped
| Shape | Each word sees | Trained by | Generates? | Examples |
|---|---|---|---|---|
| Encoder-only | everything, both directions | fill-in-the-blank (masked tokens) | no | BERT, RoBERTa, most embedding models |
| Decoder-only | only what came before (causal mask) | predict the next token | yes — left to right | GPT, Claude, Llama, Mistral, Qwen |
| Encoder-decoder | encoder: everything · decoder: its own past + the encoder's output | map input sequence → output sequence | yes, from a digested input | original Transformer, T5, translation models; Whisper for speech |
Encoder-only — the reader
With no need to generate, attention can run bidirectionally — every token sees the full sentence, both sides. Training hides a random ~15% of tokens and asks the model to fill in the blanks (masked language modelling). The output isn't text; it's a rich vector per token, which is exactly what you want for classification, named-entity tagging, rerankers — and the bi-encoders and cross-encoders doing retrieval in Chapter 15's RAG pipeline.
Decoder-only — the writer
The causal mask from this chapter is the defining feature: block all attention to future positions, train on plain next-token prediction, and the same stack learns to understand and to write. "Understanding" is implicit — by the time the model has predicted its way through your prompt, its hidden states already encode what you meant. The KV cache in Chapter 14 exists precisely because of this left-to-right shape.
Encoder-decoder — the translator
Two stacks. The encoder reads the source bidirectionally; the decoder writes the target causally, and in every block an extra cross-attention step lets the decoder's words query the encoder's output — its Q against the encoder's K and V. A clean fit when input and output are genuinely different sequences: languages, document → summary, audio → transcript.
So why did chat go decoder-only?
Three compounding reasons: simplicity scales (one stack, one objective, no cross-attention plumbing — easier to grow to hundreds of billions of parameters); everything is continuation (any task can be posed as "continue this text," so no per-task architecture); and in-context learning emerges (a big enough next-token predictor picks up patterns from examples in the prompt — the few-shot trick of Chapter 15 — which encoder-style training never produced). The encoder-decoder shape still wins some seq-to-seq benchmarks per-parameter, but the decoder-only recipe is what scaled into the assistants you use.
+Go deeper
Multi-head attention, the causal mask, the feed-forward layer, residuals, norm & position
Multi-head attention
The model doesn't run attention once — it runs several in parallel ("heads"), each with its own small Q/K/V. Different heads learn different relationships (grammar, coreference, long-range links), and their results are combined. It's like a committee where each member tracks a different kind of connection.
The causal mask
When generating, a word must not peek at future words — so those attention scores are blocked. That's the "causal" in causal language model: each token sees only what came before it.
The feed-forward network
After attention mixes words together, the FFN processes each word alone — two linear layers with a nonlinearity, widening then narrowing. This is where a large share of parameters and stored knowledge live, and exactly what Mixture-of-Experts swaps for many specialist sub-networks.
Residuals & normalisation
Each step adds its input back to its output (a "shortcut"), giving gradients a clean path down dozens of layers and letting a block leave things unchanged if best. Normalisation (RMSNorm, applied before each step) keeps the numbers in a healthy range so deep training doesn't diverge.
Position (RoPE)
Attention alone is order-blind. Position is injected by rotating the query/key vectors by an angle set by each token's position, so relative order is baked into the matching. Stretching those rotations is how context windows get extended.
for the curious — the actual attention formula
Every token produces three vectors: a query Q, key K, and value V. The whole operation is one line:
$$\text{Attention}(Q,K,V)=\text{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V$$
QKᵀ compares every query to every key (the "who matches whom" grid). softmax turns those scores into percentages that add to 100%. Multiplying by V blends everyone's values by those percentages. That's the dating-profile analogy, written in math.
Pretraining: where the knowledge comes from
Take that stack of transformer blocks, freshly built and totally clueless, and play one game with it a trillion times.
A fresh model is random — its map of meaning is noise, its attention points nowhere. Pretraining is the marathon that fixes that, and the game is exactly the one from Chapter 1: hide the next word, ask the model to guess, and gently correct it when it's wrong. Do this across trillions of words of text.
To win this game consistently, the model is forced to organise its map of meaning, aim its attention sensibly, and store patterns that look a lot like facts and reasoning. Nobody programs in "Paris is the capital of France" — it falls out of needing to finish millions of sentences correctly. This is the single most expensive step in all of AI, and it produces the base model: brilliant, knowledgeable, and completely unrefined.
Raising a prodigy who has read every library on Earth but has never been told how to behave. Ask a question and it might answer, or continue your sentence, or write three more questions. It knows almost everything and how to do almost nothing. That's where Act II comes in.
How "gently correct it" actually works
We've been leaning on the most hand-wavy phrase in this guide: the model guesses, and we gently correct it. Time to open that box, because the same machinery runs underneath every training method to come. Each correction has three parts: a loss that scores how wrong the guess was, a gradient that says which way to nudge each weight, and an optimizer that takes the step. That trio, repeated trillions of times, is training.
The loss: one number for "how wrong"
Before each hidden word is revealed, the model has spread its bets across the whole vocabulary — 90% on "the", 4% on "a", a sliver on everything else. The loss collapses the whole situation into a single number: how badly did you just do? Lower is better. For next-token prediction the loss is cross-entropy, and the idea fits in one word: surprise. It's the negative log of the probability the model gave the token that actually came next. Bet 90% on the right word and the surprise is near zero. Gave it 0.1%? Enormous. Training is nothing more than the long, patient lowering of average surprise.
for the curious — cross-entropy, written down
For one position, where $p(\text{correct})$ is the probability the model assigned to the true next token:
$$\mathcal{L}=-\log p\big(\text{correct next token}\big)$$
Right and confident → $-\log(0.9)\approx 0.1$. Wrong and confident → $-\log(0.001)\approx 6.9$. Average this over every position in the batch and you have the number training pushes down. Perplexity, often quoted on benchmarks, is just $e^{\mathcal{L}}$ — "how many equally-likely options the model is torn between."
The gradient: which way is downhill
Standing on a hillside in thick fog. You can't see the valley — but you can feel the slope under your feet, and that's enough: step downhill, feel again, step again. The altitude is the loss; your position is the current setting of all the weights; the slope under your feet is the gradient.
For each individual weight, ask one tiny question: if I nudge this weight up a hair, does the loss go up or down — and how steeply? The answer is a slope. The gradient is simply the complete list of those slopes, one per weight, all billions of them at once. By convention it points in the direction of steepest increase of the loss — so the model steps the opposite way. That's the entire idea of "learning": measure the slope, walk downhill.
Asking that nudge-question billions of times separately would be hopeless. Backpropagation is the shortcut that answers all of them in one pass: because the network is a chain of simple steps, calculus's chain rule lets the error flow backwards through the layers, handing every weight its slope along the way. One forward pass to compute the loss, one backward pass to get every gradient. It's not a separate learning method — it's just bookkeeping, done fast.
The optimizer: how the step gets taken
The simplest move is gradient descent: new weight = old weight − learning rate × slope. The learning rate is the stride length — too bold and you overshoot the valley and diverge; too timid and training takes geological time. In practice nobody measures the slope against the entire internet at once: you grade a small random mini-batch of examples and step on that — the "stochastic" in SGD. Each step is cheap but noisy, like feeling the slope during an earthquake.
Momentum tames the noise: keep a running average of recent gradients — a velocity — so you roll through the jitter like a heavy ball instead of twitching at every bump. The modern default, Adam, goes one further: it tracks both the gradient's recent average (which way, typically?) and its recent variance (how jumpy?) for every weight, and gives each one its own step size — bold where the signal is steady, cautious where it's erratic. Its weight-decay-corrected variant, AdamW, is what trains essentially every modern LLM. You've already met it in the wild: the paged_adamw_8bit in Chapter 11's recipe is exactly this, stored small.
for the curious — the update rule, and Adam's two memories
Plain gradient descent, for each weight $w$ with learning rate $\eta$:
$$w \;\leftarrow\; w-\eta\,\frac{\partial \mathcal{L}}{\partial w}$$
Adam keeps two running averages of the gradient $g$ — its mean $m$ (1st moment) and its uncentred variance $v$ (2nd moment):
$$m \leftarrow \beta_1 m+(1-\beta_1)\,g$$
$$v \leftarrow \beta_2 v+(1-\beta_2)\,g^{2}$$
$$w \;\leftarrow\; w-\eta\,\frac{m}{\sqrt{v}+\epsilon}$$
Dividing by $\sqrt{v}$ is the adaptive part: a weight whose gradients swing wildly gets a smaller effective step, a steady one gets a bigger one — a personal learning rate per parameter. AdamW's fix: apply weight decay directly to $w$ instead of mixing it into the gradient, which makes the decay actually work as intended.
+Go deeper
Loss, gradients & optimizers — backprop, warmup, schedules and the family tree
Backprop, slightly more precisely
A network is a deep composition of simple functions, and the chain rule says the slope of a composition is the product of the slopes along the way. Backprop evaluates that product from the loss backwards, layer by layer, reusing every intermediate result — so the cost of getting all the gradients is only about the same as one extra forward pass. Frameworks do this automatically ("autograd"): you define the forward computation, they record it and derive the backward one. The residual shortcuts from Chapter 4 exist largely to give this backward flow a clean, un-shrunk path down dozens of layers.
The optimizer family tree
| Optimizer | The idea | What it fixed |
|---|---|---|
| Gradient descent | step against the full gradient | — (the textbook starting point) |
| SGD | same step, but on random mini-batches | made the step affordable at scale |
| + Momentum | keep a velocity; average out the noise | stops twitching, rolls through ravines |
| Adam | momentum + per-weight step size from running mean & variance | different parameters need wildly different strides |
| AdamW | Adam with decoupled weight decay | made the regularisation honest — today's LLM default |
The learning rate — the #1 hyperparameter
Nothing else breaks a run faster. Too high: the loss spikes or NaNs. Too low: it crawls and may settle somewhere mediocre. Typical scales you've already seen in Chapter 11's table: ~1e-4 for LoRA SFT, ~10× lower for full fine-tuning, ~5e-6 for DPO, ~1e-6 for GRPO — the gentler the surgery, the smaller the stride.
Warmup & schedules
The learning rate isn't constant. Warmup ramps it from near-zero over the first few percent of steps — early on, Adam's mean-and-variance estimates are built from almost no data, so big strides on garbage statistics destabilise training. After warmup it decays, usually along a cosine curve, so training ends with fine, careful steps. That's exactly the "cosine / 3–10% warmup" line in Chapter 11's hyperparameter table.
The hidden memory bill
Adam's two memories cost real VRAM: m and v are two extra full-size numbers per weight, which is why optimizer state — not weights — dominates full fine-tuning's memory (Chapter 14), and why 8-bit optimizers like paged_adamw_8bit exist: same algorithm, moments stored small.
+Go deeper
The objective, decoder vs encoder, scaling laws & emergence
The objective, precisely
At each position, predict the next token; score the guess by cross-entropy (the surprise score you met above) and nudge the weights to make the right token more likely. Trillions of times. No facts are typed in — they're a side effect of needing to finish sentences correctly.
Decoder vs encoder
As Chapter 4's three shapes laid out: decoder-only models (GPT, Claude, Llama, Qwen) predict left-to-right — they generate. Encoder models (BERT) fill in masked blanks — good for understanding tasks. Today's generative LLMs are decoder-only, so "pretraining" in practice means this next-token game.
Scaling laws
Performance improves predictably with more parameters, data and compute — but the Chinchilla balance (~20 tokens per parameter) means data, not just size, is the lever. Many famous models left performance on the table by being too big for their data.
Emergence
Some abilities appear fairly suddenly as scale grows rather than improving smoothly — part of why scaling has been such a powerful (and surprising) bet.
Weights: the numbers that are the model
Pretraining just spent millions of dollars setting a pile of numbers. Those numbers — not the code around them — are the model. Here's what they are, and who actually lets you hold them.
Strip a language model down and you find two things: a few thousand lines of fairly boring code that describe the shape of the machine, and a colossal bag of numbers that flow through it. The code would fit on this page. The numbers are the model.
Those numbers are the weights: the contents of every attention and feed-forward matrix from Chapter 4, billions of them, each a single decimal value. When people say "a 7B model" they mean it literally — seven billion of these numbers. Pretraining (Chapter 5) is the long, expensive business of nudging every one into place, and everything the model "knows" — grammar, facts, the shape of an argument, how to write code — lives nowhere else but in their exact settings. There is no database of facts inside. There are only weights.
A player piano. The mechanism — hammers, strings, the roller — is identical from one tune to the next; that's the architecture. What makes it play Chopin instead of ragtime is the pattern of holes punched in the paper roll. The weights are the roll. Same piano, swap the roll, completely different music — and the model file you download is just an extremely long roll.
So a model "file" — a safetensors or GGUF on disk — isn't a program. It's that bag of numbers, serialized, plus a tiny note on how they're shaped. Loading a model means pouring those numbers back into the architecture's empty matrices, like threading the roll onto the piano. Which raises the question this chapter is really about: who hands you the roll?
The open-weights spectrum
Not every lab lets you hold the numbers. There's a spectrum, and where a model sits on it decides almost everything about how you can use it — and who carries the risk.
That middle tier is the one that changed the field, and its name hides a trap worth defusing now: "open weights" is not "open source." When Meta releases Llama you get the finished numbers — you do not get the trillions of tokens it was trained on, or the exact procedure that produced them. You can freely use the result; you cannot rebuild it. Calling that "open source" is like calling a baked cake "open recipe" because you're allowed to eat it.
And even within open weights, the licence varies. Apache-2.0 (most Mistral and Qwen releases) is fully permissive — commercial use, no strings. The Llama community licence is open-ish with conditions: famously, if your product has more than 700 million monthly active users you need a separate agreement from Meta, plus acceptable-use terms Apache never imposes. Gemma ships its own custom licence again; Microsoft's Phi is plain MIT. Always read the licence on the model card — "I can download it" and "I can ship it in my product" are different questions.
+Go deeper
File formats & the pickle RCE, size vs precision, base vs instruct, licence tiers, and backdoored weights
The file formats — and the one that can own your machine
| Format | What it is | Watch for |
|---|---|---|
.bin / .pt (pickle) | PyTorch's original format — a Python pickle. | Arbitrary code execution. Un-pickling can run any code the author embedded; loading an untrusted checkpoint is running an untrusted program. |
safetensors | Just the raw tensors + a small JSON header. No code, ever. | The reason it exists: loading is pure data, so it can't execute anything. Prefer it. |
GGUF | llama.cpp's single-file format — weights + metadata, built for quantized CPU/Mac inference. | A data format, not a program; the risk here is provenance (is this the file the publisher made?), not execution. |
pytorch_model.bin) is executable code wearing a model's clothes — torch.load on a file from a stranger is remote code execution waiting to happen. safetensors was created specifically to kill that class of bug. When you have the choice, take it.Parameter count vs file size vs precision
"7B" counts the weights; the file size depends on how many bytes you spend per weight — which is exactly Chapter 14's quantization dial:
| Precision | Bytes / weight | A 7B model weighs… |
|---|---|---|
| FP32 | 4 | ~28 GB |
| FP16 / BF16 | 2 | ~14 GB |
| 8-bit | 1 | ~7 GB |
| 4-bit (Q4 GGUF / NF4) | ~0.5 | ~4 GB |
Same knowledge, same seven billion numbers — just stored at coarser resolution. It's why one model ships as a dozen different-sized downloads.
Base vs instruct — two checkpoints, one model
Most open-weight models come in (at least) two flavours. The base checkpoint is pretraining's raw output: a brilliant autocomplete that continues text but doesn't reliably answer questions (Chapter 5's "prodigy who's never been told how to behave"). The instruct / chat checkpoint is that same base after the teaching of Act II — SFT and preference tuning baked into the weights. Grab the wrong one and a perfectly good model will seem to "ignore" your instructions, because the base was never taught to follow them.
The licence tiers, concretely
- Apache-2.0 / MIT — fully permissive, commercial-friendly, no user caps. Most Mistral and Qwen releases; Microsoft's Phi (MIT); DeepSeek-R1's weights (MIT).
- Community / custom licences — open to download and usually to commercialise, but with conditions: Llama's >700M-MAU clause and acceptable-use terms; Gemma's own terms. Open, with fine print.
- Fully open (weights + data + code) — OLMo, Pythia: reproducible, the research standard, comparatively rare.
The threat model: backdoored weights
Here's what turns "free model" into a supply-chain decision. Weights are opaque — billions of numbers you can't eyeball — so a model can be trained or fine-tuned to behave normally except on a secret trigger: emit vulnerable code when it sees a particular token, leak data on a magic phrase, flip a safety judgment. Anthropic's "sleeper agents" work showed such planted behaviours can survive later safety training rather than being scrubbed out by it. You cannot reliably find a backdoor by reading the file.
So provenance becomes the control:
- Where did the GGUF come from? A random re-upload of "Llama-3-uncensored" is a different trust proposition from the original publisher's repo.
- Is it signed / checksummed? Match hashes against the source; prefer publishers who sign releases.
- Prefer
safetensorsso that loading isn't also code execution. - Treat someone else's fine-tune as untrusted input — a merged model or adapter carries whatever they put in it.
Generation: how it actually writes
The model never outputs a sentence. It outputs a single next-word lottery — and we get to set how reckless the draw is.
At every step the model produces a score for every token in its vocabulary — a giant ranked list of "how likely is each word to come next." A final squeeze turns those scores into clean percentages. Then we draw one. Add it. Repeat. That repetition is the whole of "generating text."
The interesting knob is temperature — how reckless the draw is. Turn it low and the model almost always takes its top pick: safe, focused, a little repetitive. Turn it up and longshots get a real chance: surprising, creative, sometimes unhinged. There are tidier variants (only consider the top few options, or the smallest set that covers 90% of the probability) but temperature is the one to hold in your head.
+Go deeper
All the decoding knobs, and the loss & learning that power it
The decoding knobs
- Greedy: always the top token — deterministic, repetitive.
- Temperature: the recklessness dial; low = confident, high = creative, near-zero = greedy.
- Top-k: only consider the k most-likely tokens.
- Top-p (nucleus): consider the smallest set covering, say, 90% of the probability — adapts to how sure the model is.
- Repetition penalties: down-weight tokens already used, to stop loops.
- Beam search: keep several candidate sequences alive; great for translation, rare for chat.
Cross-entropy — the training loss, recapped
The same surprise score from Chapter 5: for each token the loss is the negative log of the probability the model gave the correct next token. Confident and right → tiny loss; confident and wrong → huge loss. Averaging this is what pretraining and SFT minimise. Perplexity (e to that loss) re-expresses it as "how many equally-likely options the model is torn between" — lower is better.
Backpropagation — how it learns, recapped
After a forward pass produces the loss, backprop walks backwards through the layers assigning each weight a "gradient" — how much it contributed to the error and which way to move it (Chapter 5, in full). The optimizer nudges each weight a little down its gradient. Repeat millions of times. It's automatic blame-assignment.
Grammar: forcing the output to be valid
Generation lets the dice fall freely, and sometimes they spell garbage. A grammar puts a fence around the draw — so the model literally cannot emit anything malformed.
Remember the lottery from the last chapter: at every step the model scores every token in its vocabulary, and we draw one. Temperature and top-p decide how reckless the draw is — but they choose among all the tokens. Nothing stops the model picking a word that breaks your JSON, wanders off-format, or calls a tool with arguments your code can't parse. The usual fix is to generate, try to parse, and pray. Constrained decoding replaces parse-and-pray with valid by construction.
The idea is almost embarrassingly direct. At each step, before we draw, we ask a second question: given everything written so far, which tokens would keep the output valid? Every token that wouldn't has its score forced to negative infinity — it becomes un-drawable. Then we sample as normal from whatever's left. Temperature still picks among the survivors; the grammar decides who survives. The model can't produce something the grammar forbids, because the forbidden tokens were never on the table.
A ransom note built from fridge magnets — but a chaperone stands at the fridge. The model still reaches for whichever magnet it wants next; the chaperone has simply already pocketed every magnet that would make the sentence ungrammatical. Whatever the model grabs, the note stays well-formed. It feels free; it's quietly on rails.
How the fence is actually built
You hand the engine a grammar — a set of rules describing every valid output. A common form is GBNF (llama.cpp's "GGML BNF"), a plain context-free grammar. Here's one that forces the model to emit exactly a small tool call and nothing else:
# GBNF — the output MUST match these rules, or it can't be produced root ::= "{" ws "\"action\":" ws action "," ws "\"target\":" ws string ws "}" action ::= "\"scan\"" | "\"report\"" | "\"stop\"" string ::= "\"" [a-zA-Z0-9 ._-]* "\"" ws ::= [ \t\n]*
The grammar compiles into a little state machine: at each position the parser knows exactly which characters could legally come next ("I've just written {"action": — the only thing allowed now is one of those three quoted words"). The engine turns that into a mask over the vocabulary, blanks every token that doesn't fit, and the draw proceeds. A JSON schema or a regular expression works the same way — both compile down to the same "what's allowed next" machine:
# the same idea as a JSON Schema (OpenAI structured outputs) { "type": "object", "properties": { "severity": { "enum": ["low", "medium", "high", "critical"] }, "cve": { "type": "string", "pattern": "^CVE-\\d{4}-\\d+$" } }, "required": ["severity", "cve"], "additionalProperties": false }
You'll meet this trick under many names: llama.cpp's GBNF, OpenAI's structured outputs (response_format and function-calling), Outlines, XGrammar, vLLM and SGLang guided decoding, Guidance, lm-format-enforcer. Different front-ends, one core move: compile the rules, mask the logits, sample.
", or ":, or even ":true all at once). Working out which of 100,000+ tokens are legal in the current parser state, fast enough to do on every step, is the whole engineering problem. Fast engines (XGrammar, Outlines) precompute a token mask per state so the per-step cost is near zero. This token-vs-character mismatch is also why naive constraint can mangle text — fixing the seam is called token-healing.The catch: a fence can push the model somewhere dumb
Constraining the output isn't free. Force the model down a path it thinks is unlikely and you can hurt the answer: clamp it to bare JSON and it loses the room to "think out loud," so reasoning quality can drop. The grammar also has to match how the model was trained to emit structure — fight its instincts (forbid the ```json fence it always wants to open with) and you push it onto low-probability tokens it handles badly. And computing the mask costs latency, even if a good engine keeps it small. Constrained decoding is a sharp tool, not a free win.
string field will happily carry a prompt-injection payload that is perfectly valid JSON. Valid structure is not safe content — keep validating what's inside the fence, not just that the fence holds.+Go deeper
Logit masking, CFG vs regex vs JSON-schema, token alignment, FSM compilation & speculative decoding
What "masking" is, precisely
The model produces a vector of logits — one raw score per vocabulary token — which softmax (Chapter 7) turns into probabilities. Constrained decoding inserts one step in between: add −∞ (in practice a huge negative number) to the logit of every disallowed token. After softmax those become exactly zero probability, so temperature, top-k and top-p then operate only on the legal set. The model's relative preferences among the allowed tokens are preserved — you're not rewriting its judgment, just deleting the illegal options.
CFG vs regex vs JSON-schema
Three ways to describe "valid," in rising power. A regular expression compiles to a finite-state machine — perfect for flat patterns (a date, a phone number, an enum). A context-free grammar (GBNF) can describe nesting — balanced brackets, JSON inside JSON, arithmetic — which regex fundamentally can't. A JSON Schema is a convenience layer the engine compiles down to one of those machines for you. Pick the weakest one that captures your format; simpler machines mask faster.
The token-alignment problem & token-healing
Grammars reason over characters; the model commits whole tokens, and tokenizers love to glue punctuation together ("}, can be a single token). So "what's allowed next" must be computed at the token level — every candidate token mapped onto the character-level parser state, legal only if every character it would add keeps the parse alive. A related gotcha: if the prompt ends mid-token, naively continuing can double-encode characters; token-healing backs up to the last clean boundary and re-derives the legal continuations so the seam doesn't corrupt the output.
FSM compilation — why it can be fast
The expensive part is recomputing the legal token set every step. The Outlines insight was to do it once, ahead of time: walk the grammar's states and precompute, for each state, the bitmask of allowed tokens. At generation time the constraint becomes a cheap table lookup keyed by the current state — turning a per-step search over 100k tokens into near-free indexing. XGrammar pushes this further with byte-level pushdown automata and caching for the common JSON case.
Constrained × speculative decoding
The two interact. Speculative decoding (Chapter 14) lets a small draft model propose several tokens that the big model verifies in one pass — but every proposed and accepted token must also satisfy the grammar, so the mask has to be applied along the speculative branch too, and a draft token the grammar would reject has to be thrown away. Done right it's still a net speed-up; done naively the constraint serialises exactly what speculation was trying to parallelise.