小林的产品上线前夜,AI 作词机交出的 demo 歌词是“爱你爱你爱你爱不完”,会议室笑成一片,产品总监却笑不出来。这个场景就像 KTV 里卡了带的复读机——点歌台主持人小美有个死板的原则:每次都从热歌榜里挑热度最高的歌接着唱。上一首是《爱你》,下一首大概率还是《爱你》,因为“人气第一”的设定永远不会变。 大模型的“复读机”问题正是如此:模型逐词生成时,刚说过的词往往因为“人气高”而再次被选中,于是陷入单曲循环。Repetition Penalty(重复惩罚)就是小美的新规则——每首唱过的歌,话筒音量调低一点:把已出现过的词的分数除以一个惩罚值(通常 1.1~1.3),让其他词有机会登场。这篇文章会从“复读机成因”讲到“惩罚的数学原理”,再到参数怎么调、如何与 temperature 和 top_p 配合,最后聊工程落地和失效边界。

岗位关联

对大模型应用开发者来说,重复惩罚是成本最低、见效最快的文本质量开关——一行参数,就能让客服、写作、代码生成类产品告别“车轱辘话”,显著提升用户体验。

目录
  1. 可视化演示
  2. 复读机从何而来
  3. 惩罚机制内幕
  4. 调优黄金区间
  5. 采样参数共舞
  6. 工程化落地实践
  7. 边界与失效场景
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示重复惩罚如何降低已出现token的采样概率、Temperature与TopP协同过滤、不同惩罚值的输出效果对比,切换模式掌握解码参数调优核心。

本节配有交互动效(进入视口后自动加载,约 3KB)。

重复惩罚概率抑制机制 已出现token的logits除以惩罚因子(>1)降低其概率,未出现token保持原始分布 惩罚因子1.0=无惩罚,1.1-1.3为推荐范围,过高(>1.5)导致语义不连贯 p\'(t) = p(t) / penalty (if t in history) 重复惩罚通过概率缩放实现已生成token的去重抑制

复读机从何而来

小林的产品上线前夜,AI 作词机交出的 demo 歌词是“爱你爱你爱你爱不完”,整个会议室笑成一片。这个现象像极了 KTV 里的“单曲循环”——点歌台主持人小美有个死板的原则:每次都从热歌榜挑热度最高的歌接着唱。上一首是《爱你》,下一首大概率还是《爱你》,因为“人气第一”的设定永远不会变。

大模型的“点歌”过程在技术上叫自回归生成(autoregressive generation):预测下一个词(token,可以粗略理解为“词”)时,模型把整个词表里的候选词都打一个热度分(logits,分数越高越可能被选中),然后从中挑一个。问题在于,刚生成的词进入上下文之后,模型看它的眼神反而更“亲切”——上下文里刚出现过“爱”,模型就倾向继续预测与它接近的“爱”,热度不降反升。

如果再叠加贪心解码(每次都选分数最高的词),正反馈会被无限放大:“爱”的分数本来就高,出现后语境更偏向它,于是它被选中,再出现,语境更偏向……最终形成复读回路。这不是模型“笨”,而是概率自举的必然结果。长文本生成时,回路的惯性还会累积,导致后半段越来越像复读机。

小美很快意识到,光靠“挑榜一”的规则,再好的歌单也会被循环到天荒地老。要破局,得在规则里加一条:唱过的歌,音量该调小一点。

惩罚机制内幕

小美的新规则很简单:每首被唱过的歌,话筒音量调低一档——除以惩罚值 θ。注意,不是把《爱你》从歌单里删掉,万一观众就是想听呢?只是它再想被选中,得靠比别的歌更响亮的声音。θ 越大,音量调得越低。

技术上的对应物叫 logits 处理器(LogitsProcessor)。在采样之前,模型会先检查“已唱清单”——上下文中出现过的所有 token。凡是清单里的候选词,原始分数统一除以惩罚值 θ;没出现过的词分数不动。之后 softmax 把分数换算成概率,此消彼长:已现词的概率下降了多少,未现词的概率就上升多少。

为什么要用除法而不是减法?除法是“按比例”衰减——分数越高的词被压掉的量越大,能快速打破“人气王越唱越热”的回路;而减法对高低分一视同仁,热门词依然可能霸榜。小美把规则写进操作手册:“唱过的歌,音量自动衰减。舞台留给还没唱过的歌。”复读机的第一道锁,就这样挂上了。

z^i={zi÷θ,i∈Sseenzi,i∉SseenP(wi)=ez^i∑j=1Vez^j\displaystyle \hat{z}_i = \begin{cases} z_i \div \theta, & i \in S_{\text{seen}} \\ z_i, & i \notin S_{\text{seen}} \end{cases} \qquad\qquad \displaystyle P(w_i) = \frac{e^{\hat{z}_i}}{\sum_{j=1}^{V} e^{\hat{z}_j}}
通俗地说:唱过的歌,话筒音量先打个折,再按各自的音量排队抽签。
  1. 拿到原始热度分模型为词表里每个候选词输出一个实数分数,分数越高越可能被选中,这个分数叫 logits。
    zi∈R,i=1,2,…,Vz_i \in \mathbb{R}, \quad i = 1, 2, \dots, V
  2. 圈出“唱过的歌”把上下文中出现过的 token 收集成一个集合,等待处理。
    Sseen={i∣tokeni 已出现在上下文中}S_{\text{seen}} = \{ i \mid \text{token}_i \text{ 已出现在上下文中} \}
  3. 除数打折集合里的分数集体除以惩罚值 θ,θ>1 时分数变小,θ 越大压得越狠。
    z^i=zi÷θ,i∈Sseen\hat{z}_i = z_i \div \theta, \quad i \in S_{\text{seen}}
  4. Softmax 重新归一把所有调整后的分数换算成概率,让总和为 1,方便随机采样。
    P(wi)=ez^i∑j=1Vez^jP(w_i) = \frac{e^{\hat{z}_i}}{\sum_{j=1}^{V} e^{\hat{z}_j}}
  5. 按新概率选词被压低的词中选概率显著下降,复读被抑制,其他词的机会变大。
    wt+1∼P(wi),概率越大越可能被选中w_{t+1} \sim P(w_i), \quad \text{概率越大越可能被选中}

调优黄金区间

音量该调低多少?小美做了三次实验。调 1.0(等于没调),《爱你》依旧无限循环;调 2.0,歌手被迫唱出一堆生僻词,歌词变成“爱汝之瞳影映月”,观众直呼听不懂;调到 1.1~1.3,循环被打破,歌词依然顺口——这就是经验里的黄金区间。

为什么区间这么窄?因为 softmax 里概率和分数的关系是非线性的。分数除以 θ 之后,所有候选词的概率分布会朝“均匀”方向摊平:热门词的占比被削薄,冷门词的机会被放大。θ 越大,摊平越狠。超过 1.5 后,原本合理的常用搭配(“因为……所以……”)也会被拆散,文本生硬、逻辑断裂。

不同任务的甜点位不同:歌词、小说追求文风多样,可以用 1.2~1.3;客服问答、代码生成追求稳定准确,1.05~1.15 就够;上下文很长时建议更保守。小美在手册上画了一条刻度:“旋钮拧到 1.1~1.3,既打散循环,又不吓跑观众。”这半格的差别,就是产品体验的分水岭。

Pnew(wi)=(Pold(wi))1/θ∑j(Pold(wj))1/θ\displaystyle P_{\text{new}}(w_i) = \frac{\big(P_{\text{old}}(w_i)\big)^{1/\theta}}{\sum_j \big(P_{\text{old}}(w_j)\big)^{1/\theta}}
通俗地说:惩罚本质上把“热歌榜”的票数分布抹平了一点,让第一名不再一家独大,但抹得太平,每首歌都没了特色。
  1. 回顾旧概率在惩罚之前,每个候选词原本有一个被选中的概率 P_old。
    Pold(wi)=ezi∑jezjP_{\text{old}}(w_i) = \frac{e^{z_i}}{\sum_j e^{z_j}}
  2. 取 1/θ 次方θ>1 时指数小于 1,热门词的大概率被拉小,冷门词的小概率被抬高。
    p~i=(Pold(wi))1/θ\tilde{p}_i = \big(P_{\text{old}}(w_i)\big)^{1/\theta}
  3. 重新归一化把变形后的值再缩放,恢复成概率和为 1 的分布。
    Pnew(wi)=p~i∑jp~jP_{\text{new}}(w_i) = \frac{\tilde{p}_i}{\sum_j \tilde{p}_j}
  4. 观察摊平效果θ=1.3 时指数约为 0.77,第一名占比被明显压缩,分布更平缓。
    θ=1.3  ⇒  Pnew(wi)=Pold(wi)0.77∑jPold(wj)0.77\theta = 1.3 \;\Rightarrow\; P_{\text{new}}(w_i) = \frac{P_{\text{old}}(w_i)^{0.77}}{\sum_j P_{\text{old}}(w_j)^{0.77}}

采样参数共舞

光有惩罚还不够。小美发现,即使音量调低了,主持人如果还是死守“榜一”,偶尔还是会回到《爱你》——因为唱过的歌只是变弱了,没有消失。她决定给点歌流程加两个旋钮:temperature 和 top_p。

temperature 管“观众冒险程度”:数值越高,冷门歌被点中的概率越大;top_p 管“候选池规模”:只留下累计得票率达到 p 的那批歌,垫底的直接出局;top_k 则限制候选数量,让主持人不必在几千首歌里大海捞针。三者搭配,热歌榜既不会卡在一首,也不会乱跳到离谱。

三者的分工很清晰:重复惩罚管“别再说旧词”,temperature 管“整体有多随机”,top_p 管“在多大的池子里抽签”。常见组合是 repetition_penalty=1.1~1.15、temperature=0.7、top_p=0.9。注意不要和另一种参数 frequency penalty 混淆——后者用减法按出现次数扣分,前者用除法按是否出现打折。小美的点歌台终于凑齐了三件套:音量旋钮、冒险旋钮、聚焦灯。单曲循环被拆散,冷门好歌有了出头之日,整个包间都活跃起来。

P(wi)=exp⁡(zi/τ)∑j∈V(p)exp⁡(zj/τ),V(p)=累计概率达到 p 的最小候选集\displaystyle P(w_i) = \frac{\exp\big(z_i / \tau\big)}{\sum_{j \in \mathcal{V}^{(p)}} \exp\big(z_j / \tau\big)}, \qquad \mathcal{V}^{(p)} = \text{累计概率达到 } p \text{ 的最小候选集}
通俗地说:先旋温度让大家的音量重新洗牌,再用聚焦灯把垫底的歌请出榜单,最后在剩下的歌里抽签。
  1. 温度缩放把所有 logits 除以温度 τ,τ 越大,分数差异越被压缩,分布越平。
    zi→zi/τz_i \to z_i / \tau
  2. 概率化缩放后做 softmax,得到每个词的临时概率。
    πi=ezi/τ∑jezj/τ\pi_i = \frac{e^{z_i / \tau}}{\sum_j e^{z_j / \tau}}
  3. 按概率排序从高到低给所有候选词排队。
    π(1)≥π(2)≥⋯≥π(V)\pi_{(1)} \ge \pi_{(2)} \ge \dots \ge \pi_{(V)}
  4. top_p 裁剪从第一名开始累加,直到概率和达到 p,只保留这批词。
    V(p)={(1),…,(k)},s.t.∑m=1kπ(m)≥p\mathcal{V}^{(p)} = \{(1), \dots, (k)\}, \quad \text{s.t.} \sum_{m=1}^{k} \pi_{(m)} \ge p
  5. 在裁剪后的集合里重新归一最终概率只在这些留下的候选里分配。
    P(wi)=πi∑j∈V(p)πjP(w_i) = \frac{\pi_i}{\sum_{j \in \mathcal{V}^{(p)}} \pi_j}

工程化落地实践

小美把三件套写进《点歌台操作手册》,希望每个分店都能一键使用。好消息是,主流框架早已内置:在 transformers 的 generate 里传一个 repetition_penalty 参数即可,比如 model.generate(input_ids, repetition_penalty=1.15, temperature=0.7, top_p=0.9)。

如果默认行为不够用,还可以注册自定义 logits 处理器,在采样前直接修改分数。适合“对某些词额外惩罚”的场景,比如屏蔽敏感词、禁止特定句式、对固定开场白单独扣分。原理和正文公式完全一致:维护已生成序列,对其中每个 token 的分数执行除法。

实际调参建议走评测流程:先设一个保守值(1.1),再逐步加大,在评测集上对比不同 θ 下的重复率(如 n-gram 重复占比)与人工流畅度评分,找到“重复明显下降、流畅度还没受损”的拐点。上线后也要持续监控,因为提示词模板一变,最优惩罚值可能漂移。

手册的最后一页写着:“旋钮很小,效果很大——一行参数,治好了全公司的复读机。”但小美心里清楚,这套规则还不是终点。

边界与失效场景

小美以为问题解决了,直到收到两个投诉:AI 写“活动流程”时,“第一步……第二步……第三步……”照旧重复;写数学证明时,循环论证又回来了。

结构化的重复(列表序号、固定开场白、模板句式)往往来自训练数据的强烈先验,单一的分数惩罚压不动它们;数学推导的重复步骤同理。更长的生成里,惩罚效果会随长度衰减——“已现词”越来越多,分母越来越大,惩罚被摊薄,高频词仍可能回潮。换句话说,重复惩罚更像是“音量旋钮”,而不是“禁播名单”;真要禁播,得改曲库。

应对策略是组合拳:惩罚配合更短的 max_new_tokens 分多次生成、在数据侧清洗模板句式、甚至自定义处理器对固定句式直接打分。每种策略都有自己的边界,叠加起来才能覆盖大部分复读场景。

小美在手册最后补了一行小字:“音量旋钮能治单曲循环,治不了节目单本身的重复。规则之外,还要看看歌单里到底有没有足够的歌。”

AI 应用场景

智能客服告别“车轱辘话”

阶段:推理与生成阶段(文本生成)

解决的问题:传统客服机器人在多轮对话里反复使用相同话术,“请问还有什么可以帮您”能连说三遍,用户体验很差。AI 方案是在生成调用里加 repetition_penalty=1.15,并配合 temperature=0.7、top_p=0.9,让每一轮的开场白都更自然。

具体案例:某电商客服助手上线初期,用户投诉“机器人像复读机”;工程师在推理服务中设置重复惩罚后,同一话术在相邻三轮对话中的出现率下降约 40%。

长篇小说与剧本生成

阶段:内容生成与后处理阶段

解决的问题:AIGC 写小说时,角色口头禅会高频复读,破坏文风与沉浸感。利用 repetition_penalty=1.2~1.3 配合较长上下文,让词汇表达更丰富。

具体案例:某网文创作工具把默认惩罚值从 1.0 调到 1.25,编辑抽检的“相邻段落词汇重复率”从 18% 降到 9%。

代码注释与文档生成

阶段:代码生成阶段

解决的问题:AI 编程助手生成注释时总写“例如……注意……”这类模板句,信息量低。设置 repetition_penalty=1.05~1.1,在不过度干扰代码 token 的前提下,让注释内容更有干货。

具体案例:某 IDE 插件在生成 SQL 注释时加入重复惩罚,模板句“例如:”的重复出现率从每段 2.4 次降到 0.8 次。

Python 演示

python43 行
import math # simulate a tiny language model: given previous token, output next-token scoresLOGITS = {    "我": {"爱": 3.4, "你": 3.2, "的": 3.0, "人": 2.8, "会": 2.6, "想": 2.4},} def softmax(logits):    exps = [math.exp(x) for x in logits]    total = sum(exps)    return [e / total for e in exps] def apply_penalty(scores, output, penalty, per_occurrence=True):    # presence mode divides once; per-occurrence mode divides by repeat count    for w in scores:        count = output.count(w)        if count > 0:            times = count if per_occurrence else 1            scores[w] = scores[w] / (penalty ** times)    return scores def generate(penalty=1.0, length=12, per_occurrence=True):    output = ["我"]    for _ in range(length):        scores = dict(LOGITS["我"])        scores = apply_penalty(scores, output, penalty, per_occurrence)        best = max(scores, key=scores.get)  # greedy: pick the highest score        output.append(best)    return "".join(output) def show_probs(penalty, output):    scores = dict(LOGITS["我"])    scores = apply_penalty(scores, output, penalty, True)    cands = list(scores.keys())    probs = softmax(list(scores.values()))    pairs = sorted(zip(cands, probs), key=lambda x: -x[1])    return " ".join("%s=%.2f" % (w, p) for w, p in pairs) if __name__ == "__main__":    print("no penalty  :", generate(1.0))    print("by occur    :", generate(1.8, per_occurrence=True))    print("by presence :", generate(1.8, per_occurrence=False))    print("prob after first word (theta=1.8):", show_probs(1.8, ["我", "爱"]))
目的:用 6 个词的“迷你词表”模拟贪心解码,直观对比无惩罚、按次数惩罚、按是否出现过惩罚三种情况下的输出差异。
  1. LOGITS = {用手写字典模拟“迷你模型”:给定上一个词,输出所有候选词的热度分(logits)。
  2. scores = apply_penalty(scores, output, penalty, per_occurrence)采样前先对已出现词做除法打折,再参与竞争。
  3. count = output.count(w)数一下这个词在已生成内容里出现过几次。
  4. times = count if per_occurrence else 1按次数累计就打折几次;只看是否出现过就只除一次。
  5. scores[w] = scores[w] / (penalty ** times)除法打折,次数越多分数被压得越狠。
  6. print("no penalty :", generate(1.0))无惩罚时“我爱爱爱……”复读机现形。
  7. print("by presence :", generate(1.8, per_occurrence=False))只按“是否出现”除一次,一轮之后仍会回潮重复。

常见误区

误区一遇到重复就把参数拉满:“重复这么严重,惩罚调大点,最好调到 2.0、3.0,数字越大肯定越不重复!”

正解惩罚的本质是把概率分布“摊平”,调得过大,常用搭配和自然语气都会被压垮,模型会强行选生僻词,输出生硬、逻辑断裂。主流经验是 1.1~1.3,超过 1.5 就要逐条人工评测。就像 KTV 里音量调太低,歌手只能硬唱完全没把握的歌,观众反而更难受。

为何常见:因为“惩罚越大、重复越少”在指标上立竿见影,n-gram 重复率确实下降了;而流畅度的损失需要逐句细读才能发现,容易被指标掩盖。

误区“一个词出现了三次,就应该被除以三倍的惩罚,出现越多压得越狠才对。”

正解标准实现(如 transformers 的 RepetitionPenaltyLogitsProcessor)通常是“出现即除一次”,按出现次数连除是另一类实现方式,使用时务必先查文档确认行为。这就像有的主持人每唱过一次就调低一档音量,有的则对所有“唱过的歌”统一调低一档,规则不同,效果也不同。

为何常见:很多教程把“除法打折”口语化成“出现越多越打折”,把除法机制与频次扣分混为一谈,导致初学者默认所有实现都按次数累计。

误区“反正 temperature 调大一点也能减少重复,那重复惩罚还有什么用?不如直接用 temperature。”

正解temperature 调大只是让采样更随机,可能变成胡言乱语;它不区分“旧词”和“新词”。重复惩罚专门针对已现词压分,两者方向不同,工程上通常是叠加使用而不是二选一。加大随机性等于让主持人闭眼乱点,点得再散也可能点到同一首金曲;惩罚才是专门绕过唱过的歌。

为何常见:很多 demo 里“调高 temperature 后文本确实没那么重复了”,但这是随机性带来的副作用,不是机制上的针对性,容易让人误判两者等价。

误区“只要设了 repetition_penalty,长文本就再也不会出现复读了。”

正解结构性重复(列表序号、固定模板、数学推导步骤)有很强的数据先验,单靠分数衰减压不动;超长生成里,“已现词”越来越多,惩罚被摊薄,高频词仍可能回潮。需要配合 max_new_tokens、数据清洗和自定义处理器一起兜底。

为何常见:短文本上惩罚效果显著,让人觉得它是万能开关;长文本里的回潮需要专门跑长序列评测才能发现,普通测试样本根本暴露不了。

面试题

Q1. 重复惩罚最核心的作用是什么?

单选题 · easy

  • A.降低已出现词的再次选中概率← 正确答案
  • B.提高所有生僻词的出现概率
  • C.让模型输出更简短的答案
  • D.加快模型推理速度提升效率

答案:A。重复惩罚把上下文中已出现 token 的分数除以惩罚值,从而降低其被再次选中的概率,防止复读。B 只提到生僻词,片面;C 与输出长度无关;D 与推理速度无关。

Q2. 标准重复惩罚对已出现词的分值做什么操作?

单选题 · easy

  • A.在原分值上加上常数
  • B.在原分值上减去常数
  • C.原分值除以惩罚数值← 正确答案
  • D.原分值乘以惩罚数值

答案:C。标准实现对已出现 token 的 logit 除以惩罚值 θ(θ>1),分数下降。加法会加分,效果相反;减法属于频率惩罚的常见风格;乘以大于 1 的数同样会加分,都不对。

Q3. 经验上重复惩罚参数通常设为多少?

单选题 · easy

  • A.0.8 到 1.0 之间
  • B.1.1 到 1.3 之间← 正确答案
  • C.1.5 到 2.0 之间
  • D.2.0 到 3.0 之间

答案:B。1.1~1.3 是实践中的黄金区间,既能抑制复读又保持自然。低于 1 等于没有惩罚;太高会让文本生硬、逻辑断裂,不推荐。

Q4. 当重复惩罚值设得过大时,最可能出现的后果是?

单选题 · mid

  • A.输出速度会明显变慢
  • B.文本变得生硬不自然← 正确答案
  • C.模型的词表被扩大
  • D.上下文窗口会变长

答案:B。惩罚太大把高频自然搭配的词都压了下去,模型被迫选生僻词,流畅度和逻辑受损。速度和词表大小与惩罚无关,上下文窗口也不受它影响。

Q5. 重复惩罚与 temperature 的关系是?

单选题 · mid

  • A.两参数作用完全相同
  • B.惩罚压重复温度控随机← 正确答案
  • C.温度调高后惩罚失效
  • D.惩罚会取代温度作用

答案:B。两者分工不同:重复惩罚针对已现词压低分数,temperature 缩放整体 logits 控制随机程度。工程上可叠加使用,温度并不会让惩罚失效,惩罚也不会取代温度。

Q6. 与重复惩罚配合使用时,top_p 负责什么?

单选题 · mid

  • A.把已出现的词直接删掉
  • B.只保留累计概率达 p 的词← 正确答案
  • C.把全部词的概率抹平
  • D.提升高频词的选中权重

答案:B。top_p 按概率从高到低累加,保留累计概率达到 p 的最小词集再采样,控制候选范围。它不删词、不抹平,也不提升高频词权重。

Q7. 标准实现中,惩罚是对什么做什么操作?

单选题 · hard

  • A.对已出现token的logit做除法← 正确答案
  • B.对未出现token的logit做除法
  • C.对已出现token的logit做减法
  • D.对全部token的logit做乘法

答案:A。transformers 的 RepetitionPenaltyLogitsProcessor 对生成序列中出现过的 token 的 logit 除以惩罚值 θ;未出现词不受影响。减法属于频率惩罚风格,乘法会让分数变大,均错误。

Q8. 哪些手段可以缓解大模型文本复读问题?

多选题 · mid

  • A.设置合理重复惩罚值← 正确答案
  • B.调高温度增加随机性← 正确答案
  • C.用 top_p 控制候选范围← 正确答案
  • D.强制模型输出最短答案

答案:ABC。重复惩罚直接压已现词,temperature 增加多样性,top_p 收缩候选范围,三者都是有效手段。强制短输出只是掩盖问题,不是缓解复读的通用方案。

Q9. 关于重复惩罚与 frequency penalty 的区别,说法正确的有?

多选题 · hard

  • A.前者多用除法后者多减法← 正确答案
  • B.两者作用的对象完全相同
  • C.前者压已现词后者按次数扣← 正确答案
  • D.两者属于同一种采样方式

答案:AC。重复惩罚常见实现是“出现即除一次”,frequency penalty 常见实现是按出现次数减去固定值,所以 A、C 正确。两者作用对象和扣分方式并不完全相同,也不属于同一种采样方式。

Q10. 重复惩罚在哪些场景下容易失效?

多选题 · extreme

  • A.生成长文本超过一定轮次← 正确答案
  • B.结构化列表输出与模板← 正确答案
  • C.单轮简单短问答生成中
  • D.数学推导中的循环步骤← 正确答案

答案:ABD。超长生成中惩罚被“已现词过多”摊薄,结构化模板和数学推导的固定句式有强先验,都是惩罚的失效边界。短问答重复本身不严重,不属于失效场景。