Part 1 · Chapter 7

Training vs inference

Two loops, one function: what moves, what is frozen, and what each costs. Capstone for Part 1.

Where we are

This is the last chapter of Part 1 and it adds no new component. Everything is built: tokenizer, embedding, blocks, unembedding, loss, gradient, update. What remains is to see the two ways the machine is used, with all the pieces in place, and to feel the asymmetry between them. Then the capstone: one complete training step traced by hand through a toy transformer, and the first working version of Dispatch.

The question this chapter answers: when a frontier model is trained and when it answers you, what exactly is happening, what is different, and why does that difference shape everything in Parts 2 and 4?

Picture this

An orchestra in rehearsal. The conductor stops them every few bars: "violas, you were late; brass, too loud". Every section adjusts at once, from one hearing of one passage. The score is fixed; the players change. They play the passage again, better, and the conductor stops them again. Weeks of this.

Then the concert. The conductor says nothing. The players do not change. They play the piece from the beginning to the end, one bar at a time, each bar following from the last, and if a bar goes wrong there is no going back: the next bar simply follows from the wrong one. The audience hears only the concert. They never see the rehearsal, but everything they hear was decided in it.

Rehearsal is training: the score (the data) is fixed, the players (the parameters) adjust, and the whole passage is corrected at once from one hearing. The concert is inference: the players are frozen, the music unfolds one bar at a time, and every bar depends on the ones already played. The one thing this analogy gets slightly wrong is important enough to say now: in rehearsal, the orchestra hears the correct passage at every bar, not its own mistakes. That is teacher forcing, and it is the first thing this chapter explains.

Map it
In the pictureIn the machineThe word we will use
RehearsalForward on real text, loss at every position, backward, updatetraining step
Playing to the written score, not to your own last barEvery position's prediction is conditioned on the true preceding tokensteacher forcing
Correcting all sections from one hearingAll T positions billed and backpropagated in one passparallel training
The concertFrozen θ, one token appended per passinference, autoregressive decoding
The upbeat before the first noteProcessing the whole prompt in one parallel passprefill
Each bar following the lastOne forward pass per generated tokendecode
Not re-reading the score from the top each barKeep every past token's keys and values instead of recomputing themKV cache
The soloist's freedom within the scoreHow a token is chosen from the distributionsampling, temperature, top-k, top-p

7.1The training step, in full

Take a batch: B sequences of T tokens each, drawn from the corpus, shape [B, T]. Run one forward pass. Because of the causal mask (Chapter 3), the output at position i depends only on positions ≤ i, so the model's prediction for "what comes after position i" is legitimate at every i simultaneously. The pass produces B·T distributions, and the true next token is known at every position: it is just the text shifted by one. Bill all B·T predictions, average, backpropagate once, update once.

Static view of the widget. Training: "The cat sat on the mat" enters as one sequence; the pass yields six distributions, p(·|The), p(·|The cat), …, each billed against the actual next word; one backward pass, one update. Generation: starting from "The", six passes each sample one token and append it.

The consequence to absorb is that a sequence of 8,000 tokens is 8,000 training examples for the price of one forward-backward pass, and the model never sees its own output during pretraining. At position i it is conditioned on the true tokens 1 … i, whatever it would have predicted for them. This is teacher forcing. It is why training parallelises so well, and it has a cost that surfaces later: a model that has only ever seen correct prefixes can drift when, at inference, its own errors become its context. Chapters 14 to 16 are partly about training on the model's own outputs to close that gap.

one training step at frontier scale (Llama 3 405B, public):

  batch          [B, T]  =  up to 16M tokens, e.g. 2,048 sequences × 8,192 tokens
  forward        16M distributions over 128k tokens                 ≈ 2N × 16M  =  1.3 × 10¹⁹ ops
  loss           16M bills, averaged
  backward       gradient for all 405B parameters                   ≈ 4N × 16M  =  2.6 × 10¹⁹ ops
  update         Adam, every parameter, once
  ≈ 1 million such steps                                             ≈ 3.8 × 10²⁵ ops total

7.2The inference loop, in full

Now the concert. A prompt arrives: the system prompt, the conversation so far, the new message, tokenized to P ids. Two phases follow, and they are different enough that serving systems treat them as different workloads (Chapter 19).

Prefill. All P prompt tokens go through the model in one parallel pass, exactly like a training forward pass without the loss. This produces the distribution at the last position, from which the first output token is sampled, and, as a by-product, the keys and values of every prompt token at every layer.

Decode. The sampled token is appended. Now the model needs the distribution at position P+1. Naively that is a fresh forward pass over P+1 tokens. But the first P tokens have not changed, θ has not changed, and the causal mask means their keys and values do not depend on anything after them. So their K and V at every layer are exactly what they were during prefill. Keep them. Each decode step then processes one token: compute its q, k, v, attend over the stored keys and values plus its own, run the MLP, unembed, sample, append its k and v to the store. That store is the KV cache.

Static view of the widget. A 4-token prompt then 8 decode steps. With the cache, each step computes one new key and value and reads the rest: 12 computations total. Without it, each step recomputes K and V for the whole context: 4 + 5 + 6 + … + 12 = 68 computations, and the per-step cost keeps growing.

The cache turns decoding from quadratic in the reply length to linear, and it is universal: no serving system generates without one. It also creates the memory problem that dominates serving. Every token in every live conversation holds 2 × L × (KV heads) × d numbers in the cache, and a busy server holds thousands of conversations.

Static view of the widget. Llama 3 70B: 80 layers × 8 KV heads × 128 × 2 bytes × 2 = 320 KB per token, 42 GB for a 128k-token context, so an 80 GB GPU holds one such conversation's cache and nothing else. Without grouped-query attention (64 KV heads) it would be 2.6 MB per token and 335 GB.
Back of the envelope

Why is decode slow even though it does little arithmetic? One decode step for one conversation is one token through the model: 2N ≈ 0.8 × 10¹² operations for the 405B, which a GPU does in about a millisecond. But to do them, every one of the 405 billion weights has to be read from memory once, because each token touches every matrix.

weights to read per decode step        810 GB   (405B × 2 bytes)
GPU memory bandwidth (top, 2025)       ≈ 5–8 TB/s per GPU; the weights span ≥ 5 GPUs
time to stream the weights once        ≈ 810 / (5 × 6,000)  ≈  27 ms          → at most ≈ 37 tokens/s for ONE conversation
arithmetic time for that token         ≈ 0.8 × 10¹² / (5 × 10¹⁵)  ≈ 0.2 ms     → the GPUs are 99% idle

Decode is memory-bound: the weights are read far more slowly than they are used. The fix is to serve many conversations at once, reading the weights once per step for all of them; that is batching, and it is why a provider's throughput and your personal latency are different numbers. Prefill, by contrast, pushes thousands of tokens through the same weights in one read, and is compute-bound. Chapter 19 is built on this contrast.

7.3The sampler's knobs

The model returns a distribution; something has to pick. Chapter 1 introduced greedy and sampling and the temperature; here is the full set of standard knobs, all of which act on the distribution after the model has produced it.

Static view of the widget. A twelve-token distribution for "…was still in the": room 45%, house 20%, building 11%, area 6%, … At T = 0.5 room takes 75%. Top-k = 3 keeps room, house, building and renormalises them to 59/26/15%. Top-p = 0.7 keeps room and house only. Sampling 200 times shows the tally following whichever distribution the sampler was left with.

None of these change the model. Two consequences. First, "the model is creative / repetitive / random" is often a statement about the sampler settings, and changing them is free. Second, the newest frontier APIs increasingly fix these knobs on the provider side public: the lab has tuned sampling as part of post-training and does not expose it. The distribution is still there; you just no longer choose how to draw from it.

7.4What is fixed and what moves

Training stepInference (one conversation)
Parameters θchange every stepfrozen
Inputreal text, all positions at onceprompt, then the model's own tokens one at a time
What each position seesthe true preceding tokens (teacher forcing)the prompt plus what was sampled so far
Passesone forward, one backward, per batchone forward per generated token, no backward
Kept between passesnothing (activations are freed after backward)the KV cache
Memory per parameter≈ 16 bytes (weights, gradients, Adam state)2 bytes (or 1, quantised; Chapter 27)
Cost per token≈ 6N operations≈ 2N operations, plus reading all weights once per step
Randomnesswhich batch, data order, dropout if anythe sampler
Bottleneckcompute, and communication between GPUsmemory bandwidth, and cache size
What changes the model's behaviourthe data and the lossonly the context

The last row is the one most people get wrong. Nothing you type in a conversation changes θ. The assistant that "remembers" your name from earlier in the chat is attending, through the KV cache, to tokens that are still in its context. Close the conversation and it is gone. When a provider says a model has been updated, that means someone re-entered the left column: new data, new bills, new θ. Part 3 is about the specific left-column loops that turn a pretrained model into an assistant. Fine-tuning, in any of its forms, is nothing more than re-entering the left column with different data and, usually, a lower learning rate.

θ (405 B numbers)tokenizer + W_E + 126 blocks + W_U TRAINING · Parts 2–3 corpus batch [B, T], shifted targets T losses per sequence, averaged backward, Adam: θ ← θ − η·step INFERENCE · Part 4 prompt → prefill (parallel) sample from the last row's p append; decode next with the KV cache θ is read every step and never written θ is written every step; nothing else persists
Where does the arrow that changes the model live? Only on the left. The inference loop reads θ on every step and never writes it; conversations change the context, never the parameters.

7.5Capstone 1, part A: one training step on paper

Everything from Chapters 1 to 5 in one place, small enough to check. The toy model has V = 6 tokens, width C = 4, one block with a single head of width 2 and an MLP of hidden width 4. The sequence is "the cat sat on" and the targets are "cat sat on mat". The script code/ch07/paper_trace.py prints every intermediate; the walkthrough below is its output with the chapter that explains each step.

By hand, guided
$ python code/ch07/paper_trace.py
StepWhat the script printsWhere it was explained
1 · embedx0 = W_E[ids], a [4, 4] matrix: four tokens, four coordinates eachCh 2 §2.3, a lookup
2 · attendattention weights, lower-triangular, rows summing to 1: [1, 0, 0, 0], [.564, .436, 0, 0], …Ch 3: scores, √d, mask, softmax
3 · addx1 = x0 + attention: the stream after its first noteCh 4 §4.2, the residual
4 · MLPthe ReLU gate matrix, e.g. [0 1 1 0] for token 1: two of four detectors firedCh 2 §2.6
5 · add againx2 = x1 + mlpCh 4
6 · unembed, softmaxlogits [4, 6], then p with each row summing to 1Ch 1 §1.2–1.3
7 · billloss = mean −ln p[target] = 2.0911; uniform would be ln 6 = 1.79, so the random model is slightly worse than guessingCh 1 §1.4
8 · gradient on logits(p − onehot)/T: every row has one negative entry (the target) and small positives elsewhereCh 5 §5.4
9 · backward through everythinggradient norms for all eight matrices; the two residual adds pass the gradient straight throughCh 5 §5.3–5.4
10 · checknumeric dL/dW_U[1,2] = 0.04930 analytic = 0.04930Ch 5, the ε nudge
11 · update, re-runloss 2.0911 → 1.7221Ch 5 §5.2
attention weights (rows = queries)
 [[1.    0.    0.    0.   ]
 [0.564 0.436 0.    0.   ]
 [0.485 0.206 0.31  0.   ]
 [0.519 0.153 0.223 0.104]]
…
loss = mean −ln p[target] = 2.0911
gradient on the logits (p − onehot)/T
 [[ 0.019 -0.234  0.026  0.004  0.075  0.111]
 [ 0.017  0.016 -0.216  0.01   0.093  0.081]
 [ 0.012  0.007  0.068 -0.229  0.075  0.067]
 [ 0.015  0.01   0.038  0.01  -0.17   0.097]]
gradient norms per matrix: {'W_E': 1.964, 'W_Q': 0.856, 'W_K': 0.716, 'W_V': 0.345, 'W_O': 0.31, 'W_in': 0.212, 'W_out': 0.842, 'W_U': 0.934}
numeric dL/dW_U[1,2] = 0.04930   analytic = 0.04930

after one step (lr=0.5): loss 2.0911 → 1.7221

The exercise is to read the script top to bottom with a pen, reproducing at least the attention weights for the second row ([.564, .436, 0, 0]) and the gradient on the logits for the first row from the printed p. If you can do that, you can trace a frontier model's step in your head; the only differences are the shapes and the number of blocks.

7.6Capstone 1, part B: Dispatch v0

The builder spine gets its first real program: a chat loop. It is fifteen lines, and every line is one of the concepts of this chapter made concrete: the conversation history is the context, the API streams tokens as the decode loop produces them, the usage numbers separate prefill from decode, and nothing you type reaches θ.

# code/ch07/dispatch_chat.py
import anthropic
client = anthropic.Anthropic()
SYSTEM = """You are Dispatch, the on-call assistant for Postbox, a notification delivery
service. Answer briefly and concretely. If you do not know something about Postbox, say so."""

history = []                      # the conversation: the only thing that changes between turns
total_in = total_out = 0
while True:
    user = input("\nyou > ").strip()
    if user.lower() in {"quit", "exit"}: break
    history.append({"role": "user", "content": user})
    with client.messages.stream(model="claude-opus-5", max_tokens=1024, system=SYSTEM, messages=history) as stream:
        print("dispatch > ", end="", flush=True)
        for text in stream.text_stream:          # tokens arrive one decode step at a time
            print(text, end="", flush=True)
        response = stream.get_final_message()
    history.append({"role": "assistant", "content": response.content})
    total_in += response.usage.input_tokens; total_out += response.usage.output_tokens
    print(f"\n   [prefill {response.usage.input_tokens} tokens · decode {response.usage.output_tokens} tokens"
          f" · session so far: {total_in} in, {total_out} out]")
(example session — your text will differ; the token accounting is the point)
you > Pager: push-queue depth > 50k. Where do I start?
dispatch > Three checks, in order: (1) is depth growing or draining over the last 15 min;
(2) push-worker error rate and restarts; (3) inbound event rate for a spike. Tell me which
you can see.
   [prefill 78 tokens · decode 61 tokens · session so far: 78 in, 61 out]

you > Depth is growing. Workers look healthy.
dispatch > Then consumers are fine and producers are ahead of them. Check inbound rate
first; if it spiked, that's the cause and you need to scale workers or shed load.
   [prefill 158 tokens · decode 43 tokens · session so far: 236 in, 104 out]

you > What did I say the pager was about?
dispatch > A push-queue depth alert above 50k.
   [prefill 218 tokens · decode 12 tokens · session so far: 454 in, 116 out]

Read the prefill numbers. Turn two's prefill is turn one's prompt plus turn one's reply plus the new message: the context grows by everything said. Turn three "remembers" the pager because the pager is still in the context, at prefill cost, every turn. A provider can avoid re-running the prefill on the unchanged prefix by keeping its KV cache between calls (prompt caching, Chapter 20); the model itself remembers nothing. Dispatch will gain tools in Chapter 20, retrieval in Chapter 21, and an agent loop in Chapter 22, and every one of those additions is more context, never a change to θ.

Break it

Train without teacher forcing: feed the model its own samples as context. Positions can no longer be computed in parallel, because position i+1's input is not known until position i has been sampled. Training becomes sequential like decoding, thousands of times slower. Some post-training methods do exactly this on purpose, for small amounts of data, to correct the drift teacher forcing leaves behind (Chapter 16); nobody does it for pretraining.

Decode without the KV cache. Each token costs a forward pass over the entire context. A 1,000-token reply after a 10,000-token prompt costs about 10.5 million token-passes instead of 11 thousand. Every serving system would be a thousand times slower.

Serve one conversation per GPU. Decode reads 810 GB of weights per token to do 0.2 ms of arithmetic; the GPUs are idle 99% of the time. Batching a hundred conversations per step reads the weights once for all of them; throughput rises a hundredfold for nearly the same time per step. Chapter 19 makes this the central fact of serving.

Let the chat client change θ after each turn. Every user's conversation would nudge the shared model; a bad conversation would degrade it for everyone; the model would forget its training in favour of whatever was said most recently; and reproducibility would be gone. The wall between the two loops is a feature. Personalisation is done in the context, or by training a separate copy under controlled conditions.

Rebuild the model · all of Part 1

Say back the whole machine. Text is split by a learned merge list into tokens, and a token is an integer. The integer selects a row of the embedding table, a vector of C numbers in a space where direction means something. That vector is the residual stream. It passes through L blocks. In each block, a normalised copy is read by H attention heads, each of which projects every token into a query, a key, and a value, scores each query against every earlier key, softmaxes the scaled scores into weights, and blends the values; the heads are concatenated, mixed by W_O, and added to the stream. Then a normalised copy is read by the MLP, which runs thousands of learned detectors and adds their write-backs to the stream. Positions enter as rotations of the queries and keys. After the last block, a final norm and the unembedding turn the stream into V logits; softmax turns those into a distribution over the next token. That distribution is the model's only output.

In training, a batch of real text goes through once, every position's distribution is billed by −ln p(true next token), the bills are averaged, and the chain rule walks backwards through the same graph delivering each matrix a gradient of its own shape: p − onehot at the logits, outer products at the matmuls, gates at the ReLUs, straight through at the residual adds. Adam nudges every parameter against its gradient, a million times, on fifteen trillion tokens. In inference, the parameters are frozen; the prompt is prefilled in one pass, then one token per pass is sampled and appended, with the keys and values of the past kept in a cache so nothing is recomputed. Decode is bound by reading the weights, not by arithmetic. Beacon is this machine with 405 billion numbers, and Dispatch is a loop that sends it context and prints what it samples.

textch 6 ids → W_Ech 1, 2 × L blocks · ch 3, 4 x += W_O·heads(norm x) [RoPE inside] x += W_out·act(W_in·norm x) the residual stream, never erased norm · W_Uch 4 softmax → pch 1 samplech 7 inference: append, decode with the KV cache (ch 7) training: −ln p(target) → chain rule → Adam on every matrix (ch 5) Part 2 asks: how is this machine built at 405B parameters and 15T tokens? Part 3: how is it made into an assistant? Part 4: how is it served and built upon?
What is all of Part 1 in one picture? The forward path along the middle, with chapter numbers; the inference loop below; the training loop above. Every later part is about doing one of these at frontier scale.
Exercises
  1. By hand. Using the printed p for the second row of the paper trace ([0.068, 0.065, 0.135, 0.038, 0.371, 0.323]) and its target index 2, compute the gradient on that row's logits, divide by T = 4, and confirm it matches the script. Then compute the loss contribution of that position and state whether it is above or below the mean.
  2. Calculation. A server holds Llama 3 70B in 2-byte weights on 2 GPUs of 80 GB. How much memory is left for KV cache? At 320 KB per token, how many total context tokens can be live at once? If each conversation averages 4,000 tokens of context, how many concurrent conversations is that, and what happens to that number if the model had 64 KV heads instead of 8?
  3. Code. Run dispatch_chat.py for four turns. Record the prefill count each turn and fit it to "previous prefill + previous decode + new message". Then add a max_tokens=20 limit and observe the stop_reason. Finally, modify the loop to drop the oldest turn once the history exceeds 2,000 tokens and note what Dispatch forgets.
Further reading