Constrained Decoding › Constrained Sampling Strategies

Michael BrenndoerferUpdated July 31, 20254 min read
Language AI HandbookAll chapters
Part 28 of 67GPT Architecture
Chapter 206Constrained Decoding › Constrained Sampling Strategies
Reading section 8 of 13 · Constrained Sampling Strategies · 4 minSection 8 of 13 · 4 min

Book contents

Section 8 of 13 · 4 min read

Constrained Decoding › Constrained Sampling StrategiesOne section · Sections in this chapter8 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Beyond masking invalid tokens, we can combine constraints with sampling strategies like temperature, top-k, and nucleus sampling.

Temperature and Constraints

Temperature scaling adjusts the "sharpness" of the distribution. With constraints, we apply temperature before masking to maintain proper probability ratios among valid tokens.

In[28]:
Code
import numpy as np


def constrained_sample_with_temperature(
    logits: np.ndarray, valid_token_ids: set[int], temperature: float = 1.0
) -> int:
    """
    Sample with both temperature and constraints.

    Args:
        logits: Raw model logits
        valid_token_ids: Set of valid token indices
        temperature: Sampling temperature (>1 = more random, <1 = more deterministic)

    Returns:
        Sampled token ID
    """
    # Apply temperature first
    scaled_logits = logits / temperature

    # Mask invalid tokens
    masked_logits = np.full_like(scaled_logits, float("-inf"))
    for tid in valid_token_ids:
        masked_logits[tid] = scaled_logits[tid]

    # Convert to probabilities
    max_logit = np.max(masked_logits[masked_logits > float("-inf")])
    exp_logits = np.exp(masked_logits - max_logit)
    probs = exp_logits / exp_logits.sum()

    return np.random.choice(len(probs), p=probs)
Out[29]:
Console
Effect of temperature on constrained sampling:

Valid tokens: a, b, c, d, e (X is excluded)
Original logits: a=2.0, b=1.5, c=1.0, d=0.5, X=3.5(invalid), e=0.2

Temperature 0.5:
  a: ████████████████████████████████ 64.0%
  b: ██████████ 21.0%
  c: █████ 10.4%
  d: █ 2.5%
  e: █ 2.1%

Temperature 1.0:
  a: ████████████████████ 40.0%
  b: █████████████ 27.2%
  c: ████████ 16.6%
  d: ████ 8.7%
  e: ███ 7.5%

Temperature 2.0:
  a: ███████████████ 31.3%
  b: ███████████ 23.4%
  c: █████████ 18.8%
  d: ███████ 14.5%
  e: ██████ 12.0%

Lower temperatures make the sampler more deterministic within the valid set, while higher temperatures spread probability more evenly across valid options. The constraint always prevents invalid tokens regardless of temperature.

Out[30]:
Visualization
Grouped bar chart showing probability of five valid tokens (a, b, c, d, e) under four temperature settings (0.3, 0.5, 1.0, 2.0). At low temperature, token 'a' dominates; at high temperature, probabilities become more uniform across all valid tokens.
Effect of temperature on constrained sampling. Lower temperatures concentrate probability on the highest-probability valid token, while higher temperatures spread probability more evenly.

Combining Top-k with Constraints

Top-k sampling and constraints interact in an important way: we should apply constraints first, then take top-k among valid tokens.

In[31]:
Code
import numpy as np


def constrained_top_k_sample(
    logits: np.ndarray,
    valid_token_ids: set[int],
    k: int = 5,
    temperature: float = 1.0,
) -> int:
    """
    Top-k sampling within constraint-valid tokens.

    Args:
        logits: Raw model logits
        valid_token_ids: Set of valid token indices
        k: Number of top tokens to consider
        temperature: Sampling temperature

    Returns:
        Sampled token ID
    """
    # First, mask to valid tokens only
    valid_logits = []
    valid_ids_list = []
    for tid in valid_token_ids:
        valid_logits.append(logits[tid])
        valid_ids_list.append(tid)

    valid_logits = np.array(valid_logits)

    # Apply temperature
    valid_logits = valid_logits / temperature

    # Get top-k among valid tokens
    if len(valid_logits) > k:
        top_k_indices = np.argsort(valid_logits)[-k:]
        top_k_logits = valid_logits[top_k_indices]
        top_k_ids = [valid_ids_list[i] for i in top_k_indices]
    else:
        top_k_logits = valid_logits
        top_k_ids = valid_ids_list

    # Convert to probabilities and sample
    exp_logits = np.exp(top_k_logits - np.max(top_k_logits))
    probs = exp_logits / exp_logits.sum()

    selected_idx = np.random.choice(len(top_k_ids), p=probs)
    return top_k_ids[selected_idx]
Out[32]:
Console
Top-k within constrained tokens:

Logits: run=2.5, walk=2.0, jump=1.8, skip=1.5, hop=1.0, crawl=0.5, roll=0.2
Invalid: FLY=5.0 (highest logit but excluded)

With k=3, sampling only from top-3 valid tokens:

  run: 47.3%
  walk: 28.1%
  jump: 24.6%

Only 'run', 'walk', 'jump' are sampled (top-3 among valid).