Repetition Penalties › The Repetition Penalty
Language AI HandbookAll chapters Repetition Penalties › The Repetition PenaltyOne section · Sections in this chapter3 / 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
The most direct approach to preventing repetition modifies the logits for tokens that have already appeared in the generated sequence. The repetition penalty, introduced by Keskar et al. (2019) in the CTRL paper, divides the logits of previously seen tokens by a penalty factor, making them less likely to be selected.
Think of the repetition penalty as a "familiarity tax." Every token that has appeared before faces a handicap when competing for the next position. Fresh tokens, those not yet used, compete on their own merits. Used tokens start at a disadvantage proportional to the strength of the penalty. The effect is to tilt the playing field toward novelty without eliminating previously used tokens entirely.
This approach has an appealing simplicity. It requires only a single parameter, the penalty factor , which has a natural interpretation: at there is no effect, and higher values impose stronger penalties. Implementation requires only tracking which tokens have appeared, a set that is trivially maintained as generation proceeds. The computational overhead is minimal: one division or multiplication per token in the used-token set per generation step.
The repetition penalty modifies logits for tokens that appear in the existing context. For each token that has already been generated, its logit is divided by the penalty factor if positive, or multiplied by if negative. This reduces the probability of repeating tokens without eliminating them entirely.
Mathematical Formulation
To understand how repetition penalty works, we need to trace the path from model output to token selection. When a language model predicts the next token, it doesn't output probabilities directly. Instead, it produces logits: raw, unnormalized scores for each token in the vocabulary. These logits then pass through the softmax function to become probabilities, and finally, we sample from that probability distribution.
This pipeline gives us a natural intervention point. If we want to reduce the probability of certain tokens, we can modify their logits before softmax. The question is: how should we modify them?
The Core Insight
Our goal is straightforward: make previously used tokens less likely to appear again. Since softmax converts logits to probabilities, reducing a logit reduces its corresponding probability. But there's a subtlety. Logits can be positive or negative, and the operation that reduces a positive number (division) increases a negative number (makes it closer to zero). We need an operation that pushes logits in the "less likely" direction regardless of their sign.
The solution is a piecewise function that applies different operations based on the sign of the logit. Let be the logit for token and let be the set of tokens that have already appeared in the generated sequence. The repetition penalty modifies logits as follows:
where:
- : the original logit for token , the raw score from the model before softmax
- : the modified logit after applying the penalty
- : the repetition penalty factor, typically in the range
- : the set of token indices that have appeared in the context
- : notation meaning "token is a member of set " (i.e., token has appeared before)
The formula reads: for each token, check if it has appeared before. If it has and its logit is positive, divide by the penalty factor. If it has and its logit is zero or negative, multiply by the penalty factor. If it hasn't appeared, leave it unchanged.
Why does this formula make sense? Notice that in both the positive and negative cases, the modification moves the logit away from large positive values. For a positive logit, division reduces the magnitude (e.g., 4.0 becomes 2.0). For a negative logit, multiplication increases the magnitude in the negative direction (e.g., -2.0 becomes -3.0). Both operations reduce the value in the mathematical sense (the result is smaller or more negative), and because softmax is an increasing function of each logit, a smaller logit always maps to a smaller probability. The penalty is thus correctly reducing probability in both cases.
Why the Asymmetric Treatment?
This asymmetry might seem arbitrary, but it follows directly from how softmax works. Recall that softmax converts a logit to a probability:
The probability depends on , the exponential of the logit. The exponential function is strictly increasing: larger inputs produce larger outputs. So to reduce , we must reduce .
where:
- : the probability assigned to token
- : the exponential of token 's logit, which must be positive
- : the sum of exponentials across all tokens in the vocabulary, serving as the normalizing constant
For a positive logit like , dividing by gives . Since , we've reduced the logit and thus reduced the probability. Division works as intended.
For a negative logit like , the same division gives . But (it's closer to zero), so we've increased the logit and the probability. Division fails here.
The fix is multiplication. Multiplying by gives . Since , we've pushed the logit further negative, reducing the probability. Both operations, division for positive logits and multiplication for negative ones, consistently reduce probability.
Why does this asymmetry arise in the first place? The root cause is that division by a number greater than 1 is not a "reduction" operation for negative numbers. Division by 1.5 takes -2.0 to -1.33, which is closer to 0 and thus larger. Multiplication by the same factor takes -2.0 to -3.0, which is further from 0 and thus smaller. The piecewise formula selects the operation that achieves the desired effect in each case.
The Neutral Case
When , the penalty has no effect. Dividing by 1 or multiplying by 1 leaves any number unchanged. This gives us a natural "off switch" for the penalty and a baseline for comparison. As increases above 1.0, the penalty strengthens, pushing repeated tokens further toward improbability.


The visualization shows the key insight: for positive logits, division pulls values toward zero (reducing probability), while for negative logits, multiplication pushes values further negative (also reducing probability). Both transformations achieve the same goal through different arithmetic operations. The dashed line at shows the identity case where the penalty has no effect, serving as a useful reference for understanding how each higher value moves tokens further from their original probability.
Implementation
Let's implement the repetition penalty from scratch to see exactly how it works:
def apply_repetition_penalty(logits, generated_ids, penalty):
"""
Apply repetition penalty to logits for tokens that have been generated.
Args:
logits: Tensor of shape (vocab_size,) containing raw logits
generated_ids: Set of token IDs that have been generated
penalty: Penalty factor (1.0 = no penalty, >1.0 = reduce repetition)
Returns:
Modified logits with penalty applied to generated tokens
"""
modified_logits = logits.clone()
for token_id in generated_ids:
if modified_logits[token_id] > 0:
modified_logits[token_id] = modified_logits[token_id] / penalty
else:
modified_logits[token_id] = modified_logits[token_id] * penalty
return modified_logitsThe function iterates through each token that has appeared in the context and applies the appropriate modification based on the sign of its logit. This is conceptually simple but reveals an important property: the penalty treats all repeated tokens equally, whether they appeared once or a hundred times. "The" after one use faces the same penalty as "the" after fifty uses. This equality is a design choice with consequences we will revisit when comparing to frequency penalty.
# Create example logits and demonstrate the effect
vocab_size = 10
example_logits = torch.tensor(
[2.5, -1.0, 3.2, 0.5, -0.3, 1.8, -2.0, 0.0, 1.2, -0.5]
)
token_names = [
"the",
"cat",
"sat",
"on",
"mat",
"dog",
"ran",
"and",
"big",
"small",
]
# Suppose tokens 0, 2, 3 have been generated (the, sat, on)
generated_tokens = {0, 2, 3}
penalty = 1.5
modified_logits = apply_repetition_penalty(
example_logits, generated_tokens, penalty
)Effect of repetition penalty (θ = 1.5): ------------------------------------------------------------ Token Logit Modified P(orig) P(mod) ------------------------------------------------------------ the 2.50 1.67 0.241 0.194 * cat -1.00 -1.00 0.007 0.013 sat 3.20 2.13 0.485 0.309 * on 0.50 0.33 0.033 0.051 * mat -0.30 -0.30 0.015 0.027 dog 1.80 1.80 0.120 0.221 ran -2.00 -2.00 0.003 0.005 and 0.00 0.00 0.020 0.037 big 1.20 1.20 0.066 0.121 small -0.50 -0.50 0.012 0.022 ------------------------------------------------------------ * = token appeared in context (penalty applied)
The table shows how the repetition penalty redistributes probability mass. Tokens that appeared in the context (marked with *) have their probabilities reduced, while tokens that haven't appeared receive proportionally higher probabilities. Notice that "sat" (token 2), which had the highest original logit, is no longer the most likely token after applying the penalty. The probability mass that was taken from the penalized tokens flows to unpenalized tokens, effectively boosting the chances of less familiar choices.
Visualizing the Effect


Choosing the Penalty Value
The repetition penalty parameter requires careful tuning. Too low, and repetition persists. Too high, and the model avoids necessary repetition, producing awkward text that never uses "the" or "a" more than once. Finding the right value is partly a matter of matching the penalty to the type of content being generated.
The challenge is that function words like "the", "a", "is", "and" legitimately appear hundreds of times in any long document. A penalty strong enough to suppress pathological repetition of content words will, if applied uniformly, also heavily penalize these necessary function words. The repetition penalty applies to all tokens equally regardless of their type, which means tuning it always involves a trade-off between suppressing content repetition and preserving grammatical fluency. This limitation motivates the frequency and presence penalties discussed later, which can be calibrated to allow some repetition (of function words) while punishing excessive repetition (of content words).
Common values and their effects:
- : No penalty applied. Baseline behavior with full repetition potential.
- to : Light penalty. Gently discourages repetition while allowing natural patterns. Good starting point for most applications.
- to : Moderate penalty. Noticeably reduces repetition. May affect fluency for texts requiring repeated terms (technical writing, legal documents).
- : Strong penalty. Significantly suppresses any repeated token. Can produce stilted or unnatural text, especially for longer outputs.
def generate_with_penalty(model, tokenizer, prompt, penalty, max_tokens=80):
"""Generate text with specified repetition penalty."""
input_ids = tokenizer.encode(prompt, return_tensors="pt")
with torch.no_grad():
output = model.generate(
input_ids,
max_new_tokens=max_tokens,
do_sample=True,
temperature=0.8,
top_p=0.9,
repetition_penalty=penalty,
pad_token_id=tokenizer.eos_token_id,
)
return tokenizer.decode(output[0], skip_special_tokens=True)As the penalty increases, you'll notice the text becomes more varied in vocabulary but may also become less coherent. The model is forced to find alternative words even when repetition would be natural. At high penalty values, the output can read as if someone deliberately avoided using the same word twice, which is not how fluent writing works. The sweet spot depends on the specific task: creative writing benefits from more diversity, while technical writing where precision requires consistent terminology benefits from milder penalties.

The heatmap reveals an important pattern: the probability reduction depends on both the penalty strength and the token's original standing. Tokens with moderate positive logits (around 1-3) experience the largest percentage reductions because they start with meaningful probability that can be substantially reduced. Very high logits still dominate even after penalty, while very low logits have little probability to lose. This means that the most problematic tokens, the ones the model is most confident about and most likely to repeat, are also the most resistant to moderate penalties. Extremely confident predictions require strong penalties to dislodge, while less confident predictions can be steered with mild ones.