LLM Workshop Lab
Chapter 1 · Build Your Own LLM

Next-word prediction and sampling

An LLM does one thing. Given the tokens so far, it outputs a probability distribution over the next token. Generation repeats that prediction in a loop and selects one token each time. Temperature, top-K and top-P change how that selection works.

§1The autoregressive loop

"Autoregressive" means the model's own output is fed back in as input. One forward pass predicts one token; generation repeats this loop until the model emits the special <eos> (end-of-sequence) token or hits a length cap.

Step 1

Context in

The prompt plus every token generated so far, e.g. the cat sat on the

Step 2

Model forward pass

One pass through the network produces a score (logit) for every token in the vocabulary — ~50,000 for GPT-2.

Step 3

Softmax → probabilities

Logits become a probability distribution that sums to 1. Most mass sits on a few tokens; the rest is a long tail.

Step 4

Sample one token

Greedy takes the argmax. Sampling draws randomly — shaped by temperature, top-K, and top-P.

↻ Append the chosen token to the context and repeat — until the model picks <eos> or hits max_new_tokens

§2Sampling lab

The model received the cat sat on the and produced this distribution over what comes next. Dashed outlines are the original probabilities. Solid bars are what is left after your settings rescale and filter them. Then sample from it a few hundred times and watch which tokens actually get picked.

<1 sharpens · 1 = as-is · >1 flattens toward uniform
Keep only the K most likely tokens (max = off)
Keep the smallest set covering ≥ P of the probability
No draws yet
Tokens kept
Top token prob
Entropy
Draws so far
0
original probability after temp + filters (renormalized) filtered out — can never be sampled last drawn

Try temperature 0.05, where nearly every draw is mat, and temperature 3, where low-probability tokens such as spaceship start appearing. With top-K 3, only three candidates remain. Top-P 0.9 removes the long tail while preserving the relative probabilities of the remaining tokens.

§2.2Top-K and top-P: what each cutoff counts

Both filters work on the same list, sorted most likely first, and both throw away everything below a line. They differ in what the line is measured in. Top-K counts rows. It keeps the first K of them and does not look at the numbers. Top-P adds up probability. It walks down the list adding each row's probability to a running total, and it stops at the first row that brings the total to P or above. That row stays in. Every row after it is cut. So top-P 0.7 does not mean "tokens with more than 70%". It means "enough of the top tokens to cover 70% between them".

The number of survivors under top-P is not a setting. It falls out of how the probability is spread at that step, which is another way of saying how confident the model is. When one token holds most of the probability, the running total crosses P almost at once and one or two tokens survive. When the probability is spread thin, the walk has to go a long way before the total reaches P, and dozens of tokens survive. Pick a context below and watch the running total column.

Keep the first K rows
Keep rows until the running total reaches P
Top-K keeps
Top-K covers
Top-P keeps
Top-P covers
this row's probability running total so far the P line the tail: thousands of tokens shown as one row

Same K and P, three different steps. Top-K returns the same count every time. Top-P moves with the shape of the distribution. Click a row to load it above. I made up these three distributions to show the cases. A real model produces numbers like these at different steps of the same sentence. Some steps have one obvious continuation and others have many.

The first row always survivesEven at P = 0.05 the walk cannot stop before it has added one row, so the most likely token is always kept. Top-P never leaves the draw with nothing to pick from.
P = 1 is offThe running total only reaches 1 after the last row, so nothing is cut. Top-K set to the vocabulary size is off in the same way.
Top-K can keep junkOn a confident step, top-K 5 keeps four tokens that together hold a few percent. They are rarely drawn, but each one can be. Top-P would have cut them.
Top-K can cut good optionsOn a spread-out step, top-K 5 drops tokens that are nearly as likely as the ones it kept. Top-P keeps walking until the budget is spent.
Both at oncemodel.generate applies top-K first, then top-P to what is left, then renormalizes. The pipeline strip in §2 shows the count after each stage.
One sort and one running totalIn code, top-P is a descending sort, torch.cumsum, and a comparison against P. §4 has the lines.

§2.5The last step: rolling a weighted die

Temperature, top-K and top-P only decide which tokens are still in the running. None of them picks the token. The pick is a weighted random draw over whatever survived. Lay the survivors end to end along a line from 0 to 1, each owning a slice as wide as its probability, then draw a random number and see where it lands. A token holding 47% of the line gets hit 47% of the time. torch.multinomial performs exactly this draw, and do_sample=True is what switches it on.

This line contains the tokens left by the §2 filters. Move a slider to update it.
0.00.250.50.751.0
0 rolls

There is no slider for this step. do_sample is the only control, and it is binary. With False, generation takes the argmax and ignores temperature, top-K and top-P entirely. With True, you get this draw. Argmax is still reachable from inside sampling — top-K 1 leaves one survivor owning the whole line, and temperature near 0 stretches the leader across almost all of it. For repeatable output without giving up sampling, set the random seed instead. set_seed(42) makes the same prompt and settings produce the same rolls every run.

§3Generation playground

This browser-based bigram model is counted from a small corpus about cats, dogs and mice. It predicts the next word from only the current word. Its autoregressive loop, EOS stopping and sampling steps also appear in GPT-2, which uses a much larger vocabulary and context.

Set temperature to 0.05 (close to greedy) and auto-run. It comes out identical every run and gets stuck repeating "the cat sat on the cat sat on…". This is greedy degeneration.

§4The same knobs in code

Hugging Face exposes these controls through model.generate(), as used in the Colab exercise.

from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed

tok   = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")

ids = tok("The cat sat on the", return_tensors="pt").input_ids

set_seed(42)  # same seed + same settings = same rolls, every run (§2.5)

out = model.generate(
    ids,
    max_new_tokens=40,        # cap on the autoregressive loop (§1)
    do_sample=True,           # False → greedy argmax every step
    temperature=0.8,          # <1 sharper, >1 flatter (§2 slider 1)
    top_k=50,                 # keep the 50 most likely tokens (slider 2)
    top_p=0.95,               # nucleus: smallest set covering 95% (slider 3)
    eos_token_id=tok.eos_token_id,  # the stop token
)
print(tok.decode(out[0]))

generate() hides the actual sampling work. Written out by hand, one sampling step is a handful of tensor calls. The method names below keep coming back in later chapters, so they are worth recognizing now.

# one hand-rolled sampling step (top-K variant)
logits = model(ids).logits[0, -1]                # a score for every token (§1, step 2)
probs  = torch.softmax(logits / 0.8, dim=-1)    # temperature, then scores → probabilities
vals, idx = torch.topk(probs, k=50)             # keep the 50 most likely (top-K)
vals   = vals / vals.sum()                      # renormalize the survivors
pick   = torch.multinomial(vals, num_samples=1) # the weighted draw from §2.5
next_id = idx[pick]

# for top-P, replace the topk lines with:
#   sort descending, cum = torch.cumsum(sorted_probs, dim=-1),
#   and cut where cum passes P
torch.softmaxTurns raw scores (logits) into probabilities that sum to 1. Step 3 of the loop in §1. Chapter 14 of this lab covers it in full.
torch.topkReturns the k largest values and their positions. Those positions define the top-K filter.
torch.cumsumRunning total along a list. Sort the probabilities, take the cumulative sum, and cut where the total passes P. That is top-P.
torch.multinomialDraws an index at random, weighted by the values you give it. The 0-to-1 line from §2.5 as a single call, and the thing do_sample=True switches on. It samples with replacement, meaning every draw starts fresh from the same distribution.

§5Test yourself

Questions on sampling, generation and the weighted draw. Each answer includes an explanation.

§6Key takeaways

Next-token predictionAn LLM predicts a probability distribution over the next token. Generation predicts, samples, appends the token and repeats until <eos>.
Greedy decodingTaking the argmax at every step produces repetitive, degenerate text. Sampling adds controlled variation.
Temperature reshapesIt divides the logits before softmax: low T exaggerates the leader, high T flattens toward uniform. It never removes tokens.
Top-K / top-P removeTop-K keeps a fixed count. Top-P keeps a probability budget, so its count adapts to how confident the model is. Survivors are renormalized to sum to 1.
Order of operationsTemperature rescales → top-K filters → top-P filters → renormalize → sample. That's what model.generate does under the hood.
The filters don't pickThey only decide which tokens stay in the running. The final token is a weighted random draw over the survivors — each owns a stretch of the 0–1 line as wide as its probability. do_sample=False replaces that draw with argmax and makes the other knobs no-ops. set_seed makes the draws repeatable.
The long tail is realOver 50k tokens, thousands of individually-unlikely tokens add up — that's why unfiltered high-temperature sampling goes off the rails.
The lab is complete. All 23 chapters of the workshop are available here.