Sampling Random Tokens with torch.multinomial
After a token is selected, the function checks whether it equals eos_id, the end-of-sequence token ID.
Why Always Picking the Top Token Falls Short
Earlier in this module, the text generation loop produced each new token by looking only at the last time step of the model's output and choosing the single most likely token. That approach is simple and predictable, but it always makes the same choice when given the same input. A generator that always picks the top candidate tends to produce repetitive and narrow text. This article covers the modified version of that loop, which gives the generator two controls over its choice: temperature scaling, which changes how random the choice is, and top-k sampling, which restricts the set of tokens the choice can come from.
The source modifies the earlier function, called generate_text_simple, into a new function named generate. Both controls are added inside the same loop, so the whole mechanism can be read in one function. The plan for this article is to run the modified function first, then trace the mechanism step by step.
What do you think happens?
The modified generate call sets torch.manual_seed(123) and uses temperature=1.4 with top_k=25. If you run the same call twice in a fresh session with the same prompt, will you get the same generated text?
Reveal answer
Yes, the same seed with the same settings and the same input reproduces the sampling step on that run.
The source states that torch.manual_seed(123) fixes the random number generator so the sampling step behaves reproducibly on that run. Temperature 1.4 is greater than 0.0, so the sampling branch is used, and that branch is the one the seed controls.
Tracing One Pass Through the Loop
The loop body starts the same way as before. It crops the context to context_size, runs the model, and keeps only the logits for the last time step. Everything after that line is new. On each pass, the data moves through a single pipeline: top-k filtering runs first, then temperature scaling runs, then softmax converts the logits into probabilities, and finally torch.multinomial samples one token from those probabilities.
The temperature step is conditional, so it does not always run. The branch that decides between the two selection paths depends on the temperature value. When temperature is greater than 0.0, the function divides the logits by the temperature and samples a token from the resulting probabilities. When temperature is 0.0 or lower, the code skips the scaling and sampling lines and calls torch.argmax instead, which selects the token with the highest logit. The earlier generator always made that greedy choice, so this branch preserves the earlier behavior as a special case.
After a token is selected, the function checks whether it equals eos_id, the end-of-sequence token ID. The source states that generation can stop early when that token appears. The break statement exits the loop and the function returns the sequence built so far. If no end-of-sequence token appears, the loop runs for max_new_tokens iterations, and each pass appends one token with torch.cat. In the source's example, the limit is 15 newly generated tokens.
Top-k Filtering and the Negative Infinity Mask
Top-k sampling restricts candidates to the k largest logits. The function finds the cutoff, which is the smallest of the top k logits. Any logit below that cutoff is replaced with negative infinity. Because the probabilities are produced by softmax later in the function, a token whose logit is negative infinity receives no probability and cannot be selected by sampling. The function narrows the candidate set to the top k tokens without removing the remaining tokens from the vocabulary arrays.
Applying top-k with k equal to 2
Five candidate tokens have the logits 2.0, 0.5, -1.0, 3.2, and 1.1. Apply top-k filtering with k = 2 and determine which tokens can still be sampled.
- 1Rank the logits
From largest to smallest the logits are 3.2, 2.0, 1.1, 0.5, and -1.0.
- 2Find the cutoff
The two largest are 3.2 and 2.0, so the smallest of the top k logits is 2.0. That is the cutoff.
- 3Mask the rest
The logits 0.5, -1.0, and 1.1 are below the cutoff, so each is replaced with negative infinity.
- 4Check the effect after softmax
Softmax gives negative infinity a probability of zero, so only the tokens with logits 3.2 and 2.0 can be selected by sampling.
Only the tokens with logits 3.2 and 2.0 remain candidates. The other three tokens are still in the arrays but have zero probability.
Temperature Scaling Before Softmax
Temperature scaling divides the logits by a temperature value before they reach softmax. In the formula, $z$ stands for the logits for the candidate tokens, $T$ stands for the temperature, and $z'$ is the scaled logits that the function passes to softmax.
In the code, this corresponds to the line logits = logits / temperature. The source's example uses temperature=1.4, a value greater than 0.0, so this division runs and the sampling branch is used. The temperature value is the divisor, and the scaled logits are what softmax turns into probabilities for sampling.
- logits holds the raw scores for the candidate tokens at the last time step
- temperature is the value that controls how random the choice is
- The result is passed on toward softmax and then to torch.multinomial
The purpose of this division is to change how sharply the probabilities differ after softmax. The source does not show the probability values, so the exact effect depends on the logits at each step. The direction of the effect follows from the arithmetic: dividing by a value below 1 makes the scaled logits further apart, while dividing by a value above 1 pulls them closer together. Closer logits produce a flatter distribution, which gives lower-ranked tokens a better chance of being sampled.
Choosing Between Greedy and Sampling Paths
The two selection paths are both controlled by the temperature value. With temperature greater than 0.0, the function samples a token from the probabilities using torch.multinomial. Sampling picks one token at random according to the probabilities, so a lower-ranked token can be chosen. With temperature 0.0 or lower, the function calls torch.argmax and always takes the token with the highest logit. That is the greedy path, and it matches the behavior of the earlier generator.
- torch.manual_seed fixes the random number generator for reproducible sampling
- The division by temperature belongs to the sampling branch only
- torch.argmax is the greedy path used when temperature is 0.0 or lower
Reading Determinism from the Settings
Whether a run is deterministic depends on the branch it takes. On the greedy path, torch.argmax selects the highest logit, so the same input gives the same token. On the sampling path, torch.multinomial draws from the probabilities, and the source notes that torch.manual_seed fixes the random number generator so the sampling step behaves reproducibly on that run. The divergence point is therefore the sampling call: two runs with different seeds, or with the seed unset, can choose different tokens from the same probabilities.
A run uses top_k=25 and temperature=0.0 on a fixed prompt. Another run uses top_k=25 and temperature=1.4 with torch.manual_seed(123). Predict which run is deterministic, and name the line that is responsible for any divergence from expected output.
Hints
- Check which branch the temperature value selects
- The greedy branch calls torch.argmax, while the sampling branch calls torch.multinomial
- Consider what torch.manual_seed controls
Mistakes Learners Make with These Controls
Common mistake Thinking top-k removes tokens from the vocabulary
Why it failsTop-k sets the non-top-k logits to negative infinity. The arrays keep every token, but those tokens receive zero probability after softmax.
Do this insteadRead top-k as a mask that blocks sampling, not as deletion.
Common mistake Assuming temperature always divides the logits
Why it failsThe division runs only when temperature is greater than 0.0. At 0.0 or lower, the function takes the torch.argmax path.
Do this insteadCheck the temperature value first to decide which branch runs.
Common mistake Expecting identical text from every sampling run
Why it failsSampling draws from the probabilities, so outcomes can differ across runs unless the random number generator is fixed.
Do this insteadUse torch.manual_seed when you need reproducible sampling, and remember the seed controls only the sampling step.
Common mistake Treating the end-of-sequence token as an error rather than a stop signal
Common mistake Expecting exactly max_new_tokens every time
Why it failsThe source states that the end-of-sequence token can stop generation early.
Do this insteadTreat max_new_tokens as an upper limit.
Summary of the Generation Controls
- Top-k filtering keeps the k largest logits and sets the rest to negative infinity, so those tokens get zero probability.
- Temperature scaling divides the logits by the temperature when it is greater than 0.0, before softmax.
- When temperature is 0.0 or lower, the function calls torch.argmax and takes the highest logit, which is the earlier greedy behavior.
- Sampling runs are reproducible on a given run when torch.manual_seed is fixed, and generation can stop early at the end-of-sequence token.
At a glance
- The modified generate function applies top-k filtering, then temperature scaling, then softmax, then torch.multinomial sampling on each pass.
- Top-k sampling sets logits below the k-th largest to negative infinity, so only the top k tokens can be sampled.
- Temperature greater than 0.0 divides the logits before softmax; temperature 0.0 or lower takes the torch.argmax greedy path.
- A run is deterministic on the greedy path, while the sampling path is reproducible only when the random number generator is fixed.