详细解释
频率惩罚(Frequency Penalty) 与 存在惩罚 Presence Penalty 是孪生兄弟,都在 Logits 层面对已出现过的 Token 减分。区别只在于衰减函数:
- Presence Penalty:出现过 1 次就减固定分(
-p),出现 100 次还是减-p; - Frequency Penalty:出现 N 次就减
- f × N,出现越多减越多,线性累积。
所以 Frequency Penalty 是专治”复读机”——模型一旦陷入”谢谢、谢谢、谢谢、谢谢”或者 <p>段落</p><p>段落</p><p>段落</p> 无限套娃,Frequency Penalty 会让第 4 个”谢谢”的分数比第 1 个低 4 倍,从而打断正反馈链条。
惩罚数值的数学形式(vLLM / OpenAI 兼容实现通用)
每生成新的一步 Token,对词表中每个候选词 i 执行:
logits[i] -= frequency_penalty * count[i] # count[i] = 到目前为止这个词出现了几次
logits[i] -= presence_penalty * (1 if count[i] > 0 else 0)
然后再走 Softmax → Sampling。
由此直接推出几个重要结论:
- 只要出现过 1 次,Presence 和 Frequency 都生效;出现越多,Frequency 项越大。
- 如果
f=1.0、一个词出现了 3 次,相当于减 3 分。对 logits 尺度来说 3 分是非常大的量(logits 差 1 分对应概率差 2.7×),所以数值很小就很有效果。 - 两者相加最大值建议不要超过 2.0,否则几乎所有词都被砍到没法正常说话。
常见场景推荐配置
| 场景 | frequency_penalty | presence_penalty | 其他配合 |
|---|---|---|---|
| 开放对话(闲聊/客服) | 0.1 – 0.3 | 0.2 – 0.6 | temperature=0.7–1.0 |
| 创意/营销文案 | 0.2 – 0.5 | 0.8 – 1.2 | 配合 Top-P=0.9 |
| 代码生成 | 0.3 – 0.8 | 0.1 – 0.3 | 加 Stop Sequence 防止输出额外注释 |
| 结构化 JSON/数据抽取 | 0.0 – 0.1 | 0.0 | 用 结构化输出 + temperature=0.2 |
| RAG 问答/摘要 | 0.1 – 0.3 | 0.2 – 0.5 | 避免车轱辘话复述原文 |
| 多语言翻译 | 0.0 | 0.0 | 翻译不需要”新意”,不要开惩罚 |
唯元智创 默认 presence_penalty=0, frequency_penalty=0,你可以按上表按场景选择。控制台 Playground 也提供了这两个参数的滑块实时预览效果。
常见问题
模型输出到一半开始重复同一句话,我把 Frequency 调到 2.0 为什么反而更糟?
2.0 是上限,此时所有已经出现过的正常词(如”的、是、你、我”)都被巨量减分,模型反而只能选完全没出现过的生僻词或乱码。正确处理方式:先检查是不是 max_output_tokens 设太大导致模型”没话可说被迫复读”,然后 Frequency 回到 0.3–0.8 + Presence 0.2 组合即可;再不济加 Stop Sequence 比如检测到连续重复 3 次同样的 n-gram 就停止。
Presence=0 Frequency=0 就一定安全吗?
不是。大模型生成长文本有”吸引子效应”:采样是正反馈过程,同一个词出现一次之后条件概率就升高,越到后面越容易陷入局部短语循环。现代模型(GPT-4o、Claude 3.5)比 2023 年模型抗复读好很多,但在超长输出(长度 > 4K Token)、或者 Temperature 很低(小于 0.3)的情况下仍会偶然触发。建议只要输出长度在 1K Token 以上,就至少开 Frequency=0.2 以防万一。
为什么翻译任务反而要把两个惩罚都设 0?
因为目标语言里有些词(比如法律文本中的”本合同”、“甲方”)本来就应该反复出现。如果开了惩罚,模型会为了避开重复词而换词——翻译术语的一致性就会被破坏。类似的道理:代码生成中如果用了同一个变量名,也要控制 Frequency 不要太高,否则模型会为了不重复而瞎改变量名。