Concepts / Generating Text with a Simple Loop

Generating Text with a Simple Loop

The modified generate function adds top-k filtering and temperature scaling to the earlier loop.

  • Programming

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?

  • Yes, it always picks the single most likely token for the same input

  • No, it picks a token at random each time

  • It depends on the temperature value

Reveal answer
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.

Raw logitslast time step onlyTop-k filterkeep k largestTemperature scaledivide by T when T > 0.0Softmaxlogits to probabilitiesSample one tokentorch.multinomialAppend to contexttorch.cat1st2nd3rd4th5th
What is the order of operations from raw logits through top-k filtering, temperature scaling, selection, and appending the token back into the context?

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.

Formula

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.

Token 02.0 keptToken 1-infToken 2-infToken 3-infToken 43.0 keptcutoff is 2.0
Which logits survive top-k filtering, which become negative infinity, and how do the surviving candidates line up with their vocabulary positions? The values are a hypothetical toy example, not output from the source model.

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.

Filtered logitsafter top-kTemperature greaterthan 0.0?Divide logits bytemperaturesampling pathSoftmax probabilitiestorch.multinomialsamplerandom choiceNext token selectedToken equals eos_id?Break and returnsequenceearly stoptorch.argmaxgreedy pathAppend token withtorch.catnext iterationyesnoyesno
After filtering and scaling, when does the generate function take the argmax path versus the sampling path, and what does each path produce?

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

Worked example

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.

  1. 1
    Apply 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.

  2. 2
    Apply 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.

  3. 3
    Apply 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.

  4. 4
    Sample 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.

Result

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?

  • torch.multinomial, because top_k is set

  • torch.argmax, because temperature is not greater than 0.0

  • Both are called on every pass

Reveal answer
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.

MEDIUM

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 fails

    The 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 instead

    Read 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 fails

    The division by temperature is conditional. It runs only when temperature is greater than 0.0.

    Do this instead

    Check 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 fails

    The greedy path selects the highest logit, so the same logits produce the same token every time.

    Do this instead

    Use the greedy branch when repeatable output is the goal.

  • Common mistake

    Believing a seed makes every run identical regardless of settings

    Why it fails

    The 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 instead

    Keep 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.

Related concepts

More concepts starting with G