Concepts / Sampling Random Tokens with torch.multinomial

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.

  • Programming

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?

  • Yes, the seed fixes the random number generator so the sampling step repeats

  • No, sampling always produces different text on every run

  • It depends on whether temperature is 0.0

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

Logits for laststepTop-k filteringTemperature greaterthan 0.0?Divide logits bytemperatureSoftmax toprobabilitiestorch.multinomialsamples tokenAppend token tosequenceToken equals eos_id?Break and returnsequencetorch.argmax pickshighest logitNext iteration untilmax_new_tokensyesno, 0.0 or loweryesno
Given the logits for one step, which operations run in what order, and where does the code branch between greedy selection and torch.multinomial sampling?

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.

Worked example

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.

  1. 1
    Rank the logits

    From largest to smallest the logits are 3.2, 2.0, 1.1, 0.5, and -1.0.

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

  3. 3
    Mask the rest

    The logits 0.5, -1.0, and 1.1 are below the cutoff, so each is replaced with negative infinity.

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

Result

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.

Token A logit 2.0Token A logit 2.0Token B logit 0.5Token B -infToken C logit -1.0Token C -infToken D logit 3.2Token D logit 3.2Token E logit 1.1Token E -infkeptmaskedmaskedkeptmasked
Which logits survive top-k filtering with k = 2, which positions are set to negative infinity, and how does the candidate set change?

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.

Formula

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.

python
Notes on the code
  • 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.

Logit A 2.0Logit A 4.0Logit B 1.0Logit B 2.0divide by 0.5divide by 0.5
How does dividing logits by a temperature below 1 change the gap between two candidate tokens before softmax?

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.

python
Notes on the code
  • 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.

Same input andsettingsTemperature 0.0 orlower?Greedy argmax pathSame token every runSame token for sameseedSampling path withtorch.multinomialSeed fixed?Runs can divergeyesnoyesno
Under which settings does the same input always produce the same token, and at which branch can two runs diverge?
MEDIUM

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 fails

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

    Read top-k as a mask that blocks sampling, not as deletion.

  • Common mistake

    Assuming temperature always divides the logits

    Why it fails

    The division runs only when temperature is greater than 0.0. At 0.0 or lower, the function takes the torch.argmax path.

    Do this instead

    Check the temperature value first to decide which branch runs.

  • Common mistake

    Expecting identical text from every sampling run

    Why it fails

    Sampling draws from the probabilities, so outcomes can differ across runs unless the random number generator is fixed.

    Do this instead

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

    The source states that the end-of-sequence token can stop generation early.

    Do this instead
    Treat 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.

Related concepts

More concepts starting with S