← Attention, Demystified: From Dot Products to Explaining Transformers
Week 2, Part 2: The Full Model and the Explanation

Training and Generation in Practice

Explain next-token prediction with cross-entropy loss, and describe how a trained model generates text one token at a time, including the role of temperature in sampling.

Training and Generation in Practice

You now have the whole architecture: tokens → embeddings → stacked blocks of (causal self-attention + MLP) → a final linear layer. This lesson connects that machine to the two things it actually does: learn from text and produce text. They're the same forward pass used in two different loops.


1. The task: next-token prediction

The model's entire job is one function:

tokens1..t  ⟶  a probability distribution over the vocabulary for token t+1\text{tokens}_{1..t} \;\longrightarrow\; \text{a probability distribution over the vocabulary for token } t+1

The final layer (often called the unembedding or LM head) is a matrix WU∈Rdmodel×VW_U \in \mathbb{R}^{d_{\text{model}} \times V}, where VV is the vocabulary size (say 50,000). It maps the residual-stream vector at position tt into VV raw scores called logits:

z=htWU∈RVz = h_t W_U \in \mathbb{R}^{V}

Softmax turns logits into probabilities:

pi=ezi∑j=1Vezjp_i = \frac{e^{z_i}}{\sum_{j=1}^{V} e^{z_j}}

That's it. There is no separate "understanding" module. Everything the model knows about grammar, facts, and style exists because it improves this one prediction.

The trick that makes training cheap

Here's the part people usually miss when explaining transformers. Because of the causal mask, position tt can only attend to positions ≤t\le t. So a single forward pass over a 1,024-token document produces 1,024 predictions simultaneously, and every one of them is a valid training example — position 5 predicts token 6, position 6 predicts token 7, and so on. No leakage, because position 5 physically cannot see token 6.

In code, the setup is a one-line shift:

input_ids  = tokens[:-1]   # what the model sees
target_ids = tokens[1:]    # what it should predict at each position
logits = model(input_ids)  # shape: (seq_len, vocab_size)

This is called teacher forcing: during training the model always conditions on the true prefix, never on its own guesses. It's why training is one big parallel matrix computation while generation (next section) is stubbornly sequential.


2. Cross-entropy loss

For one position with target token yy, the loss is the negative log-probability the model assigned to the correct token:

L=−log⁡py=−log⁡ezy∑jezj\mathcal{L} = -\log p_y = -\log \frac{e^{z_y}}{\sum_j e^{z_j}}

Read it as: "How surprised was the model by what actually came next?" Perfect prediction (py=1p_y = 1) gives loss 00. Assigning py=0.01p_y = 0.01 gives loss ≈4.6\approx 4.6. The total loss for a batch is the mean over all positions and all sequences.

Worked example

Suppose the context is "the cat sat on the" and, for simplicity, the vocabulary has 6 tokens. The model produces these logits:

| token | mat | rug | floor | cat | banana | the | |---|---|---|---|---|---|---| | logit ziz_i | 2.0 | 1.0 | 0.5 | 0.0 | −1.0 | −2.0 |

Exponentiate: e2.0=7.389e^{2.0}=7.389, e1.0=2.718e^{1.0}=2.718, e0.5=1.649e^{0.5}=1.649, e0=1.000e^{0}=1.000, e−1=0.368e^{-1}=0.368, e−2=0.135e^{-2}=0.135. Sum =13.259= 13.259.

p=[0.557,  0.205,  0.124,  0.075,  0.028,  0.010]p = [0.557,\; 0.205,\; 0.124,\; 0.075,\; 0.028,\; 0.010]

If the true next token is mat:

L=−log⁡(0.557)=0.585\mathcal{L} = -\log(0.557) = 0.585

If it had been rug:

L=−log⁡(0.205)=1.585\mathcal{L} = -\log(0.205) = 1.585

Note that the loss depends only on the probability of the correct token — but that probability depends on all the logits, because of the normalizing sum.

Why the gradient is so clean

Differentiate cross-entropy with respect to the logits and almost everything cancels:

∂L∂zi=pi−1[i=y]\frac{\partial \mathcal{L}}{\partial z_i} = p_i - \mathbb{1}[i = y]

In our example, with target mat, the gradient is [0.557−1,  0.205,  0.124,  0.075,  0.028,  0.010]=[−0.443,  0.205,  …][0.557 - 1,\; 0.205,\; 0.124,\; 0.075,\; 0.028,\; 0.010] = [-0.443,\; 0.205,\; \ldots]. Gradient descent pushes the correct logit up and pushes every wrong logit down in proportion to how much probability it wrongly claimed. banana barely gets touched; rug gets a real shove. This signal then flows backward through the unembedding, the MLPs, and the attention weights — which is how attention heads end up learning to do things like "copy the token that followed this same bigram earlier in the document."

Perplexity

Loss values are usually reported as perplexity, PPL=eL\text{PPL} = e^{\mathcal{L}}, which you can read as "the effective number of tokens the model is choosing between." Our example loss of 0.5850.585 is a perplexity of 1.791.79 — the model was effectively picking between two options. A good modern LM on ordinary web text sits around loss 22–33, i.e. perplexity 77–2020.


3. Generation: the autoregressive loop

Training was parallel. Generation is not, because the model's output at step tt becomes its input at step t+1t+1.

tokens = tokenizer.encode(prompt)

for _ in range(max_new_tokens):
    logits = model(tokens)          # (len(tokens), vocab_size)
    next_logits = logits[-1]        # only the LAST position matters now
    probs = softmax(next_logits / temperature)
    next_token = sample(probs)      # or argmax, for greedy decoding
    tokens.append(next_token)
    if next_token == EOS:
        break

print(tokenizer.decode(tokens))

Three things worth saying out loud to a colleague:

  1. The model still computes logits at every position; we throw away all but the last. The earlier ones would just re-predict tokens we already have.
  2. This is where the KV cache comes in. Recomputing keys and values for the whole prefix at every step is wasteful, and because of the causal mask, the keys and values for token 5 never change when token 200 arrives. So implementations cache them: each new step computes a query for one new position and attends against the cached KK and VV. That turns per-step cost from O(t2)O(t^2) to O(t)O(t) and is the single biggest reason inference is affordable.
  3. The model has no plan. It has no representation of the sentence it's "about to write." Coherence over a paragraph is an emergent consequence of each step conditioning on everything already emitted.

4. Temperature

If you always take argmax (greedy decoding), the model is deterministic and tends to loop: "the best way to do that is to do that is to do that…" Sampling from pp fixes this but sometimes picks genuinely bad tokens. Temperature TT is the dial between those failure modes. It divides the logits before the softmax:

pi=ezi/T∑jezj/Tp_i = \frac{e^{z_i / T}}{\sum_j e^{z_j / T}}

Using the same six logits from before:

| token | T=0.5T = 0.5 | T=1.0T = 1.0 | T=2.0T = 2.0 | |---|---|---|---| | mat | 0.829 | 0.557 | 0.357 | | rug | 0.112 | 0.205 | 0.216 | | floor | 0.041 | 0.124 | 0.168 | | cat | 0.015 | 0.075 | 0.131 | | banana | 0.002 | 0.028 | 0.080 | | the | 0.000 | 0.010 | 0.048 |

  • T<1T < 1 sharpens: gaps between logits get amplified, the top token dominates. As T→0T \to 0 this becomes greedy decoding.
  • T=1T = 1 is the model's honest, calibrated distribution — the one it was trained to produce.
  • T>1T > 1 flattens: rare tokens become plausible. As T→∞T \to \infty it approaches uniform sampling over the entire vocabulary, i.e. noise. Notice banana went from a 1-in-500 shot to a 1-in-12 shot.

An important nuance: temperature is a monotonic transformation. It never reorders the tokens — mat is the most likely candidate at every temperature. It only changes how often you gamble on the rest of the tail.

Because the tail of a 50,000-token vocabulary contains a lot of garbage, temperature is usually combined with a truncation step:

  • Top-kk: keep only the kk highest-probability tokens, renormalize, sample.
  • Top-pp (nucleus): keep the smallest set of tokens whose probabilities sum to pp (e.g. 0.9), renormalize, sample. Adaptive — a confident step keeps 2 tokens, an ambiguous one keeps 40.

Typical practice: T≈0.7T \approx 0.7 with top-p=0.9p = 0.9 for prose; T→0T \to 0 for code, extraction, or anything where you want reproducibility.


5. The 90-second version for your colleague

The model reads a sequence and, at every position, outputs a score for every token in the vocabulary. Softmax turns those scores into probabilities. Training just means: show it real text, and penalize it by the negative log of the probability it assigned to the token that actually came next — that's cross-entropy. Because attention is masked so each position can only look leftward, one pass over a document gives you thousands of these predictions at once, which is why transformers train so efficiently.

To generate, you run the same forward pass, look only at the last position's distribution, pick a token from it, append it, and repeat. Temperature is a knob applied to the scores before the softmax: divide by a small number and the distribution sharpens toward the single best guess; divide by a large number and it flattens toward random. Nothing else about the model changes — same weights, same attention — you're just deciding how much you want it to gamble.


Try it yourself

Open a Python REPL — no ML framework needed, just numpy:

  1. Write softmax(logits, T=1.0) that returns probabilities. (Watch out for overflow: subtract logits.max() before exponentiating. This is exactly what production implementations do, and it's a nice thing to be able to explain.)
  2. Reproduce the table in Section 4 with the logits [2.0, 1.0, 0.5, 0.0, -1.0, -2.0] at T∈{0.1,0.5,1,2,10}T \in \{0.1, 0.5, 1, 2, 10\}. At what temperature does banana cross 5%?
  3. Write cross_entropy(logits, target_index) and verify that it equals −log⁡ptarget-\log p_{\text{target}} from your softmax. Then confirm numerically that the gradient of the loss with respect to the logits is p−onehot(y)p - \text{onehot}(y), by nudging one logit by ±10−5\pm 10^{-5} and taking a finite difference.
  4. Finally, say the Section 5 summary out loud without reading it, and notice which sentence you stumble on. That's the part to re-read.

Confused by anything? Highlight it and hit “Go deeper”.

Watch

Test yourself