采样参数
§8.2 讲过,模型每一步吐出的不是一个字,而是词表里约十万个候选各自的概率。真正决定屏幕上出现哪个字的,是紧接着那个"怎么挑"的动作——它叫采样。采样参数不改模型一个字节的权重,它只改"从这堆候选里怎么挑一个"。但就是这几个旋钮,决定了同一个模型是写出严谨的代码,还是编出天马行空的小说,还是陷入车轱辘话来回说的死循环。这一节把 temperature、top-p、top-k、两种惩罚项一个一个拆开,还有一个能亲手拖的滑块等着你。
一家餐厅的菜单上有一百道菜,每道菜旁边标着"过去一年被点过多少次"——这就是概率分布。现在四个人来点菜:
老张每次都点点单量第一的那道,从不变。稳,永远不会踩雷,但吃了十年都是同一道。——这是 temperature = 0。
老李按点单量的比例随机抽:第一名有 35% 概率被他抽中,第二名 22%,第八十名 0.01% 也有可能被抽中。整体靠谱,偶尔出个新鲜。——这是 temperature = 1。
小王无视点单量,一百道菜等概率乱抽,包括那道谁都不点的"糖醋钥匙"。——这是 temperature 调到很高。
老陈的规矩最有意思:他先划掉那些"点单量加起来只占最后 10% 的冷门菜",然后在剩下的热门菜里按比例随机抽。既有新鲜感,又不会抽到离谱的东西。——这是 top-p 核采样。
先把这一步的位置搞清楚
采样发生在整条流水线的最末端,在模型全部算完之后。这一点很重要,因为它意味着:你调采样参数,不需要重新训练、不需要重新加载模型,改一个数字下次请求就生效。
模型内部(改不了,除非重训)
┌──────────────────────────────────────────┐
│ Transformer 一层层算完 │
│ ↓ │
│ 最后一层输出:logits │
│ ——词表里每个 token 一个「原始分数」 │
│ "很" → 3.2 │
│ "怎么" → 2.8 │
│ "不" → 2.4 │
│ ...(约 10 万个) │
└──────────────────────────────────────────┘
↓ ↓ ↓ 这条线以下才是采样参数的地盘
┌──────────────────────────────────────────┐
│ ① logits 各除以 temperature T │
│ ② 加上 frequency / presence penalty │
│ ③ 过 softmax → 变成概率(加起来 = 1) │
│ ④ 按 top-k 砍掉排名靠后的 │
│ ⑤ 按 top-p 砍掉累积概率尾巴 │
│ ⑥ 在剩下的候选里按概率随机抽一个 │
└──────────────────────────────────────────┘
↓
这一个 token 上屏
先解释一个必须懂的词。logits(读作"洛吉茨")说白了就是模型给每个候选打的"原始分数"——它可以是任意实数,可以是负数,加起来也不等于 1,不能直接当概率用。要变成概率,得过一道叫 softmax 的手续。
softmax 这个词听着玄,它干的活儿相当于把一堆乱七八糟的分数换算成百分比:先把每个分数拿去做指数运算(放大差距),再各自除以总和(归一化),结果就是一组加起来正好等于 1 的概率。好比班里考试,原始分是 92、85、78,你想知道"每人拿到多少份奖金比例",就得把分数换算成占总分的百分比——softmax 干的就是这件事,只是它在换算前先做了一次指数放大,让高分的优势更明显。
★ Temperature:把分布压平还是拉尖
这是最重要、也最常被调的一个参数。它的定义极其简洁——在进 softmax 之前,把所有 logits 都除以一个数 T。
标准 softmax:
P(xi) = exp(zi) / Σ exp(zj)
带 temperature 的 softmax:
P(xi) = exp(zi / T) / Σ exp(zj / T)
↑
就多了这一个除法
zi = 第 i 个 token 的原始分数(logit)
T = temperature
Σ = 对词表里所有 token 求和
就这一个除法,效果却是决定性的。用具体数字走一遍你就明白了。假设三个候选的 logits 是 3.0 / 2.0 / 1.0:
| T 的值 | 候选 A(logit 3.0) | 候选 B(logit 2.0) | 候选 C(logit 1.0) | 效果 |
|---|---|---|---|---|
| T = 0.5 | 86.7% | 11.7% | 1.6% | 拉尖:强者更强,几乎只选 A |
| T = 1.0 | 66.5% | 24.5% | 9.0% | 原始分布,不做改动 |
| T = 2.0 | 50.6% | 30.7% | 18.6% | 压平:差距缩小,B、C 有机会了 |
| T = 5.0 | 39.7% | 32.5% | 26.6% | 更平:三个几乎势均力敌 |
看清这个规律:T 变小 → 除法把差距放大 → 分布更尖 → 更保守;T 变大 → 除法把差距压缩 → 分布更平 → 更随机。
两个极端值值得单独记:
- T → 0退化为贪心解码(greedy decoding),也就是
argmax——永远只挑概率最高的那一个,完全不随机。注意:数学上除以 0 是没定义的,所以实现层面通常是"T 设为 0 就直接切换到 argmax 分支",而不是真的做那个除法。 - T = 1原始分布,不加任何修改。这是模型训练时学到的"真实"概率。
- T → ∞所有 logits 都被除成接近 0,指数运算后彼此相等,分布趋于词表上的均匀分布——等于在十万个候选里完全乱抽,输出会是彻底的乱码。
好比你在调一台老收音机。台上正在放的曲子是什么,是电台决定的(那是模型的 logits,你改不了)。你手里那个旋钮只能调"音量对比"——把响的更响、轻的更轻,还是把大家都拉到差不多的音量。
T 调小,相当于把对比拉到极致:最响的那一声压过一切,其他声音你完全听不见,于是你每次听到的都是同一个音。
T 调大,相当于把所有声音的音量拉齐:主旋律和背景杂音一样响,你会听到很多平时听不到的东西——有时候是有趣的和声,有时候就是纯粹的噪音。
关键在于:旋钮从来没有换过曲子。模型的知识、判断、偏好全都在 logits 里定好了;temperature 只是决定你要多严格地遵守这份偏好。这就是为什么"把温度调高能让模型更有创意"这句话只对了一半——它不会让模型变聪明,只是让它更愿意说出那些它本来觉得不太可能的话。
下面这个组件让你亲手拖动温度滑块,实时看概率条形图怎么被压平和拉尖。拖到最左边,你会看到一根独苗;拖到最右边,你会看到一片平原。建议在两个极端各停一下,这个画面比任何公式都好记。
★ Top-k:固定砍到只剩 k 个
Top-k 的规则简单到一句话:按概率从高到低排序,只保留前 k 个,其余全部作废,然后在这 k 个里按概率重新归一化后随机抽。
假设 top_k = 3,模型给出的分布是:
"很" 35% ✓ 保留
"怎么" 22% ✓ 保留
"不" 15% ✓ 保留
────────────────── 第 3 名之后全砍
"好" 10% ✗ 砍掉
"真" 8% ✗ 砍掉
...(其余约 10 万个) ✗ 全砍
剩下三个重新算比例(35+22+15 = 72):
"很" → 48.6%
"怎么" → 30.6%
"不" → 20.8%
在这三个里随机抽一个。
它的好处是杜绝了"从十万个候选的长尾里抽到一个荒谬词"的可能。缺点是那个 k 是死的,而这就是问题所在:
| 情形 | 分布长什么样 | top_k = 50 的后果 |
|---|---|---|
| 写代码,下一个 token 几乎唯一确定 | 第一名 99%,其余全是零点几 | 太松:把 49 个概率极低的垃圾也放进候选池,虽然抽中概率小,但确实有可能出错 |
| 写小说的开头,下一个词有几百种合理选择 | 前 300 名概率都在 0.2%~0.5% 之间,很平均 | 太紧:硬生生砍掉 250 个同样合理的词,文风被人为收窄 |
换成大白话:top-k 就相当于规定"不管什么情况,都只从前 50 名里挑"。可现实是有时候第一名就该独占鳌头,有时候前三百名难分高下。用一个固定的数字去应付变化极大的分布,这是 top-k 的先天缺陷。
★ Top-p 核采样:砍掉的是"尾巴"而不是"名次"
为了解决上面那个缺陷,Holtzman 等人提出了 Top-p,也叫核采样(Nucleus Sampling)。它的规则是:按概率从高到低累加,累加到刚好超过 p 就停,只保留这一批。
这里有个有趣的细节值得提:这篇论文的题目是《The Curious Case of Neural Text Degeneration》("神经文本退化的怪案"),标题里根本没有 Top-p 或 Nucleus Sampling 的字样——方法名是在正文里给的。论文是 arXiv 1904.09751,2019 年提出、2020 年发表于 ICLR。
它的动机说得非常直白:最大似然解码(也就是永远挑概率最高的那个)产生的文本"平淡且诡异地重复";解决办法是从一个动态的"核"中采样,以截断那条不可靠的长尾。
假设 top_p = 0.9
【场景 A · 模型很确定的时候】
"=" 95% 累积 95% ← 已经超过 90%,就此打住
──────────────────── 只保留 1 个候选!
其余全砍
【场景 B · 模型很不确定的时候】
"夜晚" 8% 累积 8%
"清晨" 7% 累积 15%
"雨天" 6% 累积 21%
"远方" 5% 累积 26%
...(一路累加)
"码头" 0.4% 累积 89.7%
"灯塔" 0.4% 累积 90.1% ← 刚超过 90%,打住
──────────────────── 保留了 60 多个候选!
同一个 top_p = 0.9:
确定时自动收紧到 1 个
不确定时自动放宽到 60 多个
★ 候选数量是「自己变」的,不用你操心
这就是 top-k 和 top-p 的本质区别,务必记牢:
| Top-k | Top-p(核采样) | |
|---|---|---|
| 你规定的是 | 固定的个数(保留几个) | 固定的累积概率质量(保留多少把握) |
| 候选数量 | 永远是 k 个,死的 | 动态变化,从 1 个到几百个 |
| 模型很确定时 | 仍然放 k 个进候选池(偏松) | 自动收到 1~2 个(正好) |
| 模型很不确定时 | 硬砍到 k 个(偏紧) | 自动放宽到几百个(正好) |
| 论文 | 更早期的常规做法 | Holtzman et al., arXiv 1904.09751, ICLR 2020 |
用生活场景说清这个区别:好比医院门诊分诊。Top-k 的规矩是"不管来什么病人,每次都往候诊名单上放 50 个";Top-p 的规矩是"往名单上放人,放到把 90% 的可能病因都覆盖住为止"——明显是感冒就只放内科一位医生,症状复杂就把六十位专家都叫来。后者显然更合理。
这也是为什么今天绝大多数场景的默认推荐是用 top-p 而不是 top-k。顺便提一句常见的困惑:top-k 和 top-p 是可以同时开的,多数实现是先按 k 砍、再按 p 砍。但一般不建议同时精调两个——先固定 top_p,只动 temperature,是最省心的做法。
两种重复惩罚:frequency 与 presence
如果你见过模型陷入"车轱辘话来回说"的死循环("这很重要。这很重要。这很重要。……"),那两个惩罚参数就是给这种情况准备的。它们的区别只有一个字,但很关键:
| 参数 | 惩罚规则 | 效果 | 范围 |
|---|---|---|---|
frequency_penalty | 按已出现的次数成正比惩罚——出现越多,罚得越狠 | 压制高频重复,但不完全禁止 | −2.0 ~ 2.0 |
presence_penalty | 只要出现过,就施加一个固定的惩罚,不管出现了几次 | 鼓励换新话题、扩大用词范围 | −2.0 ~ 2.0 |
打个比方就清楚了。假设你在一场会议上发言,主持人有两种不同的限制方式:
- frequency 式主持人"你这个词说第一遍不扣分,第二遍扣 1 分,第三遍扣 2 分,第十遍扣 9 分。"——你还可以偶尔用,但绝不敢反复念。
- presence 式主持人"你这个词只要说过一次,之后每次都固定扣 1 分,说十遍也是每次扣 1 分。"——你会倾向于换新词,但不会被"越说越重罚"逼着彻底放弃。
注意那个负数范围。设成负值等于"鼓励重复",听起来没用,其实在某些场景(比如要求严格遵守某个固定格式、反复输出同一个标签)确实有人用。
★ 但这里有一个非常重要的提醒:Anthropic 的 Claude API 不提供这两个参数。Claude 只有 temperature、top_p、top_k。所以千万不要写出"所有 LLM 都有 frequency_penalty"这种话,也不要在跨厂商的代码里无条件传这两个字段——会直接报错。这是一个真实的踩坑点。
| 参数 | OpenAI 兼容接口 | Anthropic Claude |
|---|---|---|
temperature | 有 | 有 |
top_p | 有 | 有 |
top_k | 部分实现有 | 有 |
frequency_penalty | 有 | 没有 |
presence_penalty | 有 | 没有 |
★ 「temperature=0 就完全可复现」并不成立
这是一条流传极广的误解,值得单独讲。逻辑上看它很有道理:T=0 就是 argmax,永远挑概率最高的那个,没有随机性,那同一个 prompt 应该永远得到同一个答案——可实践中并非如此。
下面几条标注为「实践观察」,是工程上被反复报告的现象:
- 浮点运算的非确定性GPU 上大量数字并行相加,而浮点加法不满足结合律——(a+b)+c 和 a+(b+c) 的结果可能在最后一位小数上不同。当两个候选的概率极其接近(比如 0.4999 和 0.5001),这点误差足以让 argmax 挑出另一个。而一旦第一个 token 不同,后面整段话就全岔开了(回想 §8.2 的"无法回头修改")。
- MoE 的路由混合专家模型(第 7 章讲过)每一步要决定"把这个 token 交给哪几个专家处理"。这个路由决策在某些实现里会受到同一批次里其他请求的影响,而你无法控制别人什么时候发请求。
- 批处理的大小服务端会把多个用户的请求攒成一批一起算。批的大小不同,底层调用的矩阵乘法算子实现可能不同,数值结果就有细微差别。而批大小取决于当时有多少人在用,你完全控制不了。
换成大白话:T=0 保证的是"决策规则不带随机",而不是"输入到输出的整条链路上一个比特都不变"。这就像两个人用同一台银行点钞机数同一叠钱:规则完全一样,但机器的传送速度、纸币的湿度、当时的温度都会影响个别钞票的识别,绝大多数时候结果相同,偶尔就是会差一张。
所以在工程上:需要"完全可复现"的场景(比如自动化测试的断言、需要审计留痕的业务),不要指望 T=0,而要把模型的输出结果本身存下来。有些厂商提供 seed 参数能提高复现概率,但通常也只承诺"尽力而为"(best-effort),不是保证。
贪心解码 vs 束搜索:还有第三种挑法
前面讲的几种都属于"随机采样"这一大类。历史上还有一条完全不同的路线,叫束搜索(Beam Search),今天在翻译和语音识别里仍然常用,理解它能让你更明白采样的取舍。
贪心解码(也就是 T=0)的问题是只看眼前一步。它在第 1 步挑了概率最高的词,可这个词可能把后面整句话逼进死胡同——就像下棋只算一步,第一步吃个小卒很划算,结果第二步丢了车。
束搜索的做法是同时保留几条候选路径("束宽",beam width),走几步之后再看哪条整体概率最高:
假设束宽 = 3,要生成 3 个 token
第 1 步 从概率最高的 3 个开头各开一条路
路径A: "今" 路径B: "明" 路径C: "昨"
第 2 步 每条路各往下扩展 3 个候选 → 共 9 条
算出每条的「累积概率」,只留总分最高的 3 条
路径A: "今天" 路径B: "明天" 路径C: "今晚"
("昨" 那条被淘汰了,因为它后续都不太行)
第 3 步 再扩展、再淘汰,保持只留 3 条
最后:从 3 条完整路径里挑累积概率最高的那条输出。
束搜索在机器翻译这类"答案基本唯一"的任务上表现很好,因为它确实找到了整体概率更高的句子。但用在开放式写作上,它反而是灾难——正是 Top-p 那篇论文批评的对象。因为"整体概率最高的句子"往往是最平淡、最模板化的那句,而且极其容易陷入重复。好比招聘时只按"最符合平均标准"的算法选人,选出来的一定是最不出错也最没意思的那位。
| 方法 | 确定性 | 擅长 | 短板 |
|---|---|---|---|
| 贪心(T=0) | 确定 | 代码、结构化输出 | 只看一步,容易走进死胡同;容易重复 |
| 束搜索 | 确定 | 翻译、语音识别 | 输出平淡模板化;计算量是束宽的倍数 |
| Top-p 采样 | 随机 | 对话、创作、通用场景 | 不可复现;偶尔会抽到不太好的词 |
还有几个你可能会遇到的旋钮
除了主要那几个,各家 API 还有一些不那么常用但偶尔救命的参数。知道它们的存在,比临时抱佛脚查文档强。
- min_p一种较新的截断方式:只保留概率不低于"最高概率 × min_p"的候选。换成大白话就是"必须至少有冠军的百分之几的实力才有资格进候选"。它比 top-p 对分布形状更敏感,在部分开源推理框架里能用。
- repetition_penalty开源社区(如 Hugging Face、vLLM)常见的参数,和 OpenAI 的两种 penalty 思路类似但公式不同——它是直接对已出现 token 的 logit 做除法而不是减法。注意:跨框架时同名不同义,1.1 在这里是"轻微惩罚",别照搬 OpenAI 那套 −2~2 的取值直觉。
- logit_bias手动指定某些 token 的分数加减多少。最典型的用法是硬性禁用某些词(设一个极大的负值)或者强行提高某个词的概率。做内容过滤和格式约束时很实用。
- seed随机数种子。设了它能提高复现概率,但如前所述,多数厂商只承诺"尽力而为",不是保证。别把它当契约。
- n / best_of一次请求生成多条候选。想要多样性又不想把温度调爆,用这个更靠谱——生成 5 条,自己挑或者让另一个模型评一遍。代价是按全部输出 token 计费,所以 n=5 就是 5 倍输出成本。
关于最后一条,值得补一句实践经验:"温度调到 1.3 求一条好答案"和"温度 0.7 生成 5 条挑一条最好的",后者几乎总是赢。相当于招聘时你是"降低标准招一个奇才",还是"按正常标准面五个人挑最好的"——后者的期望质量明显更高,成本也可控。
为什么"平淡且诡异地重复"会发生
Top-p 论文里那个说法值得单独展开,因为它揭示了一个很深的机制:为什么"永远挑概率最高的那个"反而会写出病态的文本?
关键在于概率高不等于内容好。模型学到的是"在人类写的文本里,这个词后面最常跟什么"。而人类语言里最常出现的搭配,恰好是那些最没信息量的:连接词、套话、客套。你让它永远挑最常见的,它就一路挑出最平庸的路径。
更糟的是正反馈循环。这个循环特别值得看清:
模型写下一句 "这一点非常重要。"
↓
现在上下文里出现了 "这一点非常重要。"
↓
模型看到自己刚说过这句话
——而在训练语料里,一句话出现过之后,
它再次出现的概率其实是升高的
(因为人类文本里确实有重复强调的现象)
↓
于是 "这" 的概率略微上升
↓
写下 "这一点非常重要。"
↓
现在上下文里这句话出现了两次
↓
概率再升高 ……
↓
死循环:一直重复到 max_tokens 用完
这就像一个人在空教室里说话产生的回声:他说了一句,回声传回来,他觉得"刚才好像是这么说的",又照着说一遍,回声更强……这不是模型坏了,是"贪心 + 自回归"两个机制叠加出的结构性陷阱。
知道机制之后,三种解法就都能想通了:一、加随机性(提高温度或用 top-p),让它有机会跳出那条路径;二、加重复惩罚,直接对已出现的 token 扣分,从数值上打断循环;三、改提示词,明确要求不要重复。三种手段作用在不同层面,可以叠加使用。
调参的正确姿势:一套可复用的流程
最后给一套实操流程。这套东西的价值不在多聪明,而在它能让你不再瞎猜。
- 第一步 · 先别调参,改提示词这是最容易被跳过的一步。十次里有八次,问题出在提示词写得不清楚,而不是温度不对。把要求写明确、给两三个示例,效果提升通常比调参大得多,而且免费。
- 第二步 · 建一个小评测集找 20~30 个真实输入,人工标好"什么样的输出算合格"。没有这个集合,你所有的调参都是凭感觉,换个例子结论就翻。这一步花两小时,能省后面二十小时。
- 第三步 · 只动 temperature,扫一遍固定 top_p = 0.95,让 temperature 从 0 到 1.2 每隔 0.2 跑一遍评测集,把合格率画成一条曲线。你会看到一个明显的最优区间,而且往往比你凭直觉挑的值好不少。
- 第四步 · 只在有重复问题时才加 penalty别一上来就把两个惩罚项都设成 0.5。先确认真的出现了重复,再从 0.1~0.3 这种小值开始加。惩罚项加大了会让模型刻意回避某些必要的词,反而伤害准确性——比如写代码时它会为了"不重复"而换掉本该重复的变量名。
- 第五步 · 把最终配置写进代码并加注释注明"为什么是这个值、在哪个评测集上验证过"。半年后你自己也会忘,而这行注释能救命。
整个流程可以类比成调一台老收音机:第一步是先确认天线插好了(提示词),第二步是找一个已知的电台当基准(评测集),第三步是慢慢转旋钮听哪里最清楚(扫温度),第四步是只在有杂音时才动降噪(惩罚项)。最常见的错误恰恰是天线没插就开始猛转旋钮。
各种任务该怎么调:一张实用配置表
下面这张表是工程实践中的常见起点。请注意这些是经验起点而不是权威定论,具体还要看你的模型和任务微调。
| 任务 | temperature | top_p | 为什么 |
|---|---|---|---|
| 写代码 / 改 bug | 0 ~ 0.2 | 0.95 或不设 | 语法只有一种对的写法,创意等于错误 |
| 数据抽取 / 结构化输出 JSON | 0 | 不设 | 要求格式严格一致,一点偏差就解析失败 |
| 事实问答 / 知识检索 | 0 ~ 0.3 | 0.9 | 要准,不要发挥 |
| 翻译 | 0.3 | 0.9 | 要准,但允许一点行文上的灵活 |
| 日常聊天 / 客服 | 0.7 | 0.9 | 自然、不呆板,但别跑偏 |
| 写文案 / 起标题 / 头脑风暴 | 0.9 ~ 1.1 | 0.95 | 要多样性,愿意接受一些不那么"标准"的表达 |
| 写诗 / 小说 / 剧本 | 1.0 ~ 1.2 | 0.95 | 意外性本身就是价值 |
三条通用建议:
- 只动一个旋钮先把 top_p 固定在 0.9 或 0.95,只调 temperature。同时调两个会让你完全说不清是哪个在起作用。
- 别把温度调过 1.5超过这个值,输出质量通常会明显崩坏——不是"更有创意",是开始说不成话。想要更多样性,宁可换成"多生成几条,从里面挑"。
- 推理模型可能不吃这一套部分推理模型(§8.6)会锁定或建议特定的采样参数。比如 DeepSeek-R1 官方 benchmark 的评测设置就是明确的 temperature 0.6、top-p 0.95——这类模型你自己乱调,反而会掉分。
代码:把这几个旋钮真正拧一遍
光看表不如自己试。下面这段代码用同一个提示词、不同的温度各跑三次,你会亲眼看到差异:
# pip install openai
from openai import OpenAI
client = OpenAI()
prompt = "用一句话形容春天的傍晚。"
for T in [0.0, 0.7, 1.3]:
print(f"\n===== temperature = {T} =====")
for i in range(3):
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=T,
top_p=0.95,
max_tokens=60,
# 注意:下面两个参数 Anthropic Claude 不支持!
frequency_penalty=0.0,
presence_penalty=0.0,
)
print(f" [{i+1}] {r.choices[0].message.content.strip()}")
print(f" 用量: in={r.usage.prompt_tokens} out={r.usage.completion_tokens}")
# 你会观察到:
# T=0.0 三次答案几乎相同(但不保证 100% 一字不差!)
# T=0.7 三次答案句式不同,但都还算得体
# T=1.3 开始出现奇特的比喻,偶尔有一次读起来不太通顺
特别留意最后那行注释。跑十次,你有可能碰到一次 T=0 的输出跟其他九次不一样——那不是你的代码写错了,正是上面讲的浮点非确定性在起作用。亲眼看到这一次不一致,比读十遍解释都有说服力。
常见误解一次澄清
| 常听到的说法 | 实际情况 |
|---|---|
| "温度调高模型就变聪明了" | 不对。温度不改模型知识,只改"多严格遵守它自己的偏好"。调高只是更愿意说出它本来认为不太可能的话 |
| "temperature=0 就完全可复现" | 不成立。浮点非确定性、MoE 路由、批处理大小都会导致同一 prompt 输出不同(实践观察) |
| "top-k 和 top-p 差不多" | 本质不同。top-k 固定个数,top-p 固定累积概率质量、候选数动态变化 |
| "所有 LLM 都有 frequency_penalty" | 不对。Anthropic Claude API 只有 temperature / top_p / top_k,没有这两个惩罚参数 |
| "Top-p 论文的名字里有 Nucleus Sampling" | 不对。论文叫《The Curious Case of Neural Text Degeneration》,方法名在正文里 |
| "两个 penalty 是一个东西" | 不是。frequency 按出现次数成正比加罚,presence 是出现过就固定加罚 |
| "温度越高越有创意,1.5 以上更好" | 过了 1.5 通常直接崩坏。要多样性应该用"多生成几条再挑" |
采样发生在整条流水线的最末端,它不改模型一个字节,只改"从约十万个候选里怎么挑一个"——所以改一个数字下次请求就生效。
Temperature 的定义就一个除法:P(xi) = exp(zi/T) / Σexp(zj/T),进 softmax 前把 logits 除以 T。T→0 退化为贪心/argmax;T=1 是原始分布;T→∞ 趋于词表上的均匀分布(纯乱码)。它像音量对比旋钮,从不换曲子。
Top-k 与 Top-p 的本质区别务必记牢:前者固定「个数」,后者固定「累积概率质量」、候选数动态变化。Top-p 来自 Holtzman et al.《The Curious Case of Neural Text Degeneration》(arXiv 1904.09751,2019 年提出、2020 年 ICLR),动机是最大似然解码会产出"平淡且诡异地重复"的文本,办法是从动态核中采样以截断不可靠长尾。
两个惩罚项:frequency_penalty 按已出现次数成正比加罚,presence_penalty 出现过就固定加罚,范围都是 −2.0 ~ 2.0。但 Anthropic Claude API 不提供这两个参数,只有 temperature / top_p / top_k。另外,「T=0 就完全可复现」并不成立——浮点非确定性、MoE 路由、批处理都会插一手(实践观察)。
下一节我们换个角度:不再调旋钮,而是改提示词——只加一句"让我们一步一步想",正确率就从一成跳到四成。这件事在 2022 年震动了整个领域。