Generating Text with a Simple Loop
The modified generate function adds top-k filtering and temperature scaling to the earlier loop.
Why the Earlier Generator Repeats Itself
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 shows how a modified version of that loop, called generate, adds two controls to introduce variety while keeping the whole mechanism inside one function.
What do you think happens?
If the earlier generator sees the same input twice, will it pick the same next token both times?
Reveal answer
Yes, it always picks the single most likely token for the same input
The earlier loop always chose the top candidate, so identical inputs produce identical choices. That fixed behavior is exactly what the modified function changes by adding sampling as an option.
The modified function is the earlier generate_text_simple loop, extended with two controls. Temperature scaling changes how random the choice is. Top-k sampling restricts the set of tokens the choice can come from. Both controls live inside the same loop, so you can read the full mechanism in one place. The best way to start is to see what the modified function produces, then trace how each line produces it.
Tracing One Pass Through the Loop
The first part of the loop body is the same 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, and that is where the added diversity comes from. Each pass through the loop follows one pipeline: top-k filtering runs first, then temperature scaling, then softmax converts the logits into probabilities, and finally torch.multinomial samples one token from those probabilities.
The order matters because each step changes the input to the next one. Top-k filtering decides which candidates remain eligible. Temperature scaling then reshapes the logits of those candidates before softmax turns them into probabilities. Sampling is the final step, and it draws from the probabilities that the earlier steps produced. Reading the code in this order, rather than line by line in isolation, makes the purpose of each control clear.
Temperature Scaling Divides the Logits
Temperature scaling works on the logits themselves. Let z stand for the logits of the candidate tokens and T stand for the temperature. The function passes the scaled logits z' to softmax, where the scaling is defined as follows.
In the code, this corresponds to the line logits = logits / temperature. Temperature is the divisor, and the scaled logits are what softmax turns into probabilities for sampling. The purpose of this division is to change how sharply the probabilities differ after softmax. The source does not show the probability values directly, so the exact effect depends on the logits at each step. A useful way to read it is that changing the divisor changes the gaps between the logits before they are converted into probabilities.
Top-k Filtering Sets the Rest to Negative Infinity
Top-k sampling restricts candidates to the k largest logits. The cutoff is the smallest of the top k logits. Any logit below that cutoff is replaced with negative infinity. Softmax is applied later in the function, and a token whose logit is negative infinity receives no probability, so sampling cannot select it. This narrows the candidate set to the top k tokens without removing the remaining tokens from the vocabulary arrays. Their positions still exist; they simply carry no probability.
In the diagram, the two largest logits, 3.0 and 2.0, survive. The cutoff is 2.0, the smaller of the two. Every other logit falls below that cutoff and is replaced with negative infinity. Those positions keep their place in the vocabulary, but they receive zero probability after softmax. The point of the toy example is the mechanism, not the specific numbers.
Choosing Between Greedy Selection and Sampling
The temperature value decides which selection path runs. With temperature greater than 0.0, the function samples a token from the probabilities. With temperature 0.0 or lower, the function skips the scaling and sampling lines and calls torch.argmax, which selects the token with the highest logit. The earlier generator always made this greedy choice, so the greedy branch preserves that earlier behavior as a special case of the modified function.
After a token is selected by either path, the function checks whether it equals eos_id, the end-of-sequence token ID. When it does, the break statement exits the loop and the function returns the sequence built so far, so generation can stop before max_new_tokens is reached. If the end-of-sequence token never appears, the loop runs for max_new_tokens iterations, and each pass appends one token with torch.cat.
Tracing a Toy Vocabulary by Hand
One sampling step with top-k 2 and temperature 1.4
A hypothetical five-token vocabulary has logits 2.0, 1.0, 0.5, -1.0, and 3.0 at positions 0 through 4. Trace one pass with top_k=2 and temperature=1.4. The numbers are invented for illustration and are not from the source model.
- 1Apply top-k filtering
The two largest logits are 3.0 at position 4 and 2.0 at position 0. The cutoff is the smaller of the two, which is 2.0. The logits at positions 1, 2, and 3 fall below the cutoff, so they are replaced with negative infinity.
- 2Apply temperature scaling
Temperature is 1.4, which is greater than 0.0, so the sampling branch runs. Dividing the surviving logits gives 3.0 / 1.4, about 2.14, and 2.0 / 1.4, about 1.43. The negative-infinity entries stay negative infinity after division.
- 3Apply softmax
Softmax gives the negative-infinity positions zero probability. Between the two survivors, the ratio of their exponentials is about e raised to 0.71, so position 4 is roughly twice as likely as position 0.
- 4Sample one token
torch.multinomial draws one token from these probabilities. Position 4 is chosen more often, but position 0 can still be chosen. Positions 1, 2, and 3 cannot be chosen at all.
The next token is drawn from positions 0 and 4 only. Position 4 has about two-thirds of the probability and position 0 about one-third, so the output is random but restricted to the top two candidates.
The example runs the full mechanism on a case small enough to check by hand. It also shows that top-k filtering is a restriction on candidates, while temperature decides how the remaining candidates are weighted before sampling.
Following a Seeded Run from Start to Finish
The source runs the modified function with torch.manual_seed(123) set first. That call fixes the random number generator, so the sampling step behaves reproducibly on that run. The call also passes top_k=25 and temperature=1.4, so both controls are active. The output is the prompt followed by up to 15 newly generated tokens. The text reads as plausible words that drift in meaning, which is the variety the modified function is designed to allow.
Read the run as a chain of branch decisions. Each pass checks temperature, which is 1.4, so the sampling path is taken on every iteration. Each pass also checks whether the chosen token equals eos_id. If that happens, the loop stops early. Otherwise, the loop continues until max_new_tokens iterations have added 15 tokens. The seed governs the random draws inside torch.multinomial, so repeating the same seeded call on the same setup should reproduce the same sequence of choices.
Predicting Whether a Run Repeats
Determinism depends on which branch runs. On the greedy path, temperature is 0.0 or lower, and the function calls torch.argmax. That selects the token with the highest logit, and given the same logits it always selects the same token. On the sampling path, temperature is greater than 0.0, and torch.multinomial draws a random token. Two runs with the same settings and no fixed seed can therefore diverge, while a run with a fixed seed reproduces its draws. Top-k filtering does not by itself introduce randomness; it only narrows which tokens the sampling step can draw from.
What do you think happens?
You call generate with temperature=0.0 and top_k=25, without changing the seed. Which function does the next-token step call?
Reveal answer
torch.argmax, because temperature is not greater than 0.0
Temperature 0.0 or lower skips the scaling and sampling lines and takes the greedy path. The top_k setting still filters the logits first, but the final selection is argmax, so the run is repeatable.
Suppose a run uses temperature=0.8 and top_k=10, with no manual seed set. Name the branch that makes its output vary between runs, and state where in the loop that branch sits relative to top-k filtering. Then describe what would change if you set torch.manual_seed to a fixed value before the call.
Hints
- Check the temperature value against the 0.0 threshold to decide which selection path runs.
- Top-k filtering happens before temperature scaling and softmax, while the random draw happens at the end.
- A fixed seed affects the random number generator used by the sampling step.
Mistakes Readers Make with the Generate Loop
Common mistake Thinking top-k filtering deletes the other tokens from the vocabulary
Why it failsThe source states that the remaining tokens are not removed from the vocabulary arrays. Their logits are set to negative infinity, so they receive no probability.
Do this insteadRead top-k as a mask on the candidates: the positions remain, but only the top k can be sampled.
Common mistake Assuming temperature scaling always runs
Why it failsThe division by temperature is conditional. It runs only when temperature is greater than 0.0.
Do this insteadCheck the temperature value first. At 0.0 or lower, the function calls torch.argmax and skips scaling and sampling.
Common mistake Expecting a run with temperature 0.0 to vary between runs
Why it failsThe greedy path selects the highest logit, so the same logits produce the same token every time.
Do this insteadUse the greedy branch when repeatable output is the goal.
Common mistake Believing a seed makes every run identical regardless of settings
Why it failsThe source says the seed fixes the random number generator so the sampling step is reproducible on that run. The seed matters only where sampling happens.
Do this insteadKeep the same seed and the same settings, then compare outputs. The seed does not change the greedy path, which has no random draw to fix.
Common mistake Forgetting that the end-of-sequence check can stop the loop before max_new_tokens is reached
Key Points for Reading the Generate Function
Key Takeaways
- The generate loop runs top-k filtering, then temperature scaling, then softmax, then sampling, and appends the chosen token to the context on each pass.
- Dividing the logits by a temperature greater than 0.0 reshapes the probabilities before sampling; at 0.0 or lower, the function takes the argmax path instead.
- Top-k filtering keeps the k largest logits and sets the rest to negative infinity, so those tokens receive zero probability without being removed from the vocabulary arrays.
- A run is repeatable on the greedy path, and on the sampling path when the random number generator is seeded, while the end-of-sequence check can stop generation early.