§ 8.4 · Section

采样参数

Sampling Parameters · Temperature / Top-p / Top-k

§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.586.7%11.7%1.6%拉尖:强者更强,几乎只选 A
T = 1.066.5%24.5%9.0%原始分布,不做改动
T = 2.050.6%30.7%18.6%压平:差距缩小,B、C 有机会了
T = 5.039.7%32.5%26.6%更平:三个几乎势均力敌

看清这个规律:T 变小 → 除法把差距放大 → 分布更尖 → 更保守;T 变大 → 除法把差距压缩 → 分布更平 → 更随机。

两个极端值值得单独记:

Analogy · 音响的音量旋钮,不是选曲键

好比你在调一台老收音机。台上正在放的曲子是什么,是电台决定的(那是模型的 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-kTop-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

打个比方就清楚了。假设你在一场会议上发言,主持人有两种不同的限制方式:

注意那个负数范围。设成负值等于"鼓励重复",听起来没用,其实在某些场景(比如要求严格遵守某个固定格式、反复输出同一个标签)确实有人用。

★ 但这里有一个非常重要的提醒:Anthropic 的 Claude API 不提供这两个参数。Claude 只有 temperaturetop_ptop_k。所以千万不要写出"所有 LLM 都有 frequency_penalty"这种话,也不要在跨厂商的代码里无条件传这两个字段——会直接报错。这是一个真实的踩坑点。

参数OpenAI 兼容接口Anthropic Claude
temperature
top_p
top_k部分实现有
frequency_penalty没有
presence_penalty没有

★ 「temperature=0 就完全可复现」并不成立

这是一条流传极广的误解,值得单独讲。逻辑上看它很有道理:T=0 就是 argmax,永远挑概率最高的那个,没有随机性,那同一个 prompt 应该永远得到同一个答案——可实践中并非如此。

下面几条标注为「实践观察」,是工程上被反复报告的现象:

换成大白话: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 还有一些不那么常用但偶尔救命的参数。知道它们的存在,比临时抱佛脚查文档强。

关于最后一条,值得补一句实践经验:"温度调到 1.3 求一条好答案"和"温度 0.7 生成 5 条挑一条最好的",后者几乎总是赢。相当于招聘时你是"降低标准招一个奇才",还是"按正常标准面五个人挑最好的"——后者的期望质量明显更高,成本也可控。

为什么"平淡且诡异地重复"会发生

Top-p 论文里那个说法值得单独展开,因为它揭示了一个很深的机制:为什么"永远挑概率最高的那个"反而会写出病态的文本?

关键在于概率高不等于内容好。模型学到的是"在人类写的文本里,这个词后面最常跟什么"。而人类语言里最常出现的搭配,恰好是那些最没信息量的:连接词、套话、客套。你让它永远挑最常见的,它就一路挑出最平庸的路径。

更糟的是正反馈循环。这个循环特别值得看清:

模型写下一句 "这一点非常重要。"
     ↓
现在上下文里出现了 "这一点非常重要。"
     ↓
模型看到自己刚说过这句话
——而在训练语料里,一句话出现过之后,
   它再次出现的概率其实是升高的
   (因为人类文本里确实有重复强调的现象)
     ↓
于是 "这" 的概率略微上升
     ↓
写下 "这一点非常重要。"
     ↓
现在上下文里这句话出现了两次
     ↓
概率再升高 ……
     ↓
死循环:一直重复到 max_tokens 用完

这就像一个人在空教室里说话产生的回声:他说了一句,回声传回来,他觉得"刚才好像是这么说的",又照着说一遍,回声更强……这不是模型坏了,是"贪心 + 自回归"两个机制叠加出的结构性陷阱。

知道机制之后,三种解法就都能想通了:一、加随机性(提高温度或用 top-p),让它有机会跳出那条路径;二、加重复惩罚,直接对已出现的 token 扣分,从数值上打断循环;三、改提示词,明确要求不要重复。三种手段作用在不同层面,可以叠加使用。

调参的正确姿势:一套可复用的流程

最后给一套实操流程。这套东西的价值不在多聪明,而在它能让你不再瞎猜

整个流程可以类比成调一台老收音机:第一步是先确认天线插好了(提示词),第二步是找一个已知的电台当基准(评测集),第三步是慢慢转旋钮听哪里最清楚(扫温度),第四步是只在有杂音时才动降噪(惩罚项)。最常见的错误恰恰是天线没插就开始猛转旋钮。

各种任务该怎么调:一张实用配置表

下面这张表是工程实践中的常见起点。请注意这些是经验起点而不是权威定论,具体还要看你的模型和任务微调。

任务temperaturetop_p为什么
写代码 / 改 bug0 ~ 0.20.95 或不设语法只有一种对的写法,创意等于错误
数据抽取 / 结构化输出 JSON0不设要求格式严格一致,一点偏差就解析失败
事实问答 / 知识检索0 ~ 0.30.9要准,不要发挥
翻译0.30.9要准,但允许一点行文上的灵活
日常聊天 / 客服0.70.9自然、不呆板,但别跑偏
写文案 / 起标题 / 头脑风暴0.9 ~ 1.10.95要多样性,愿意接受一些不那么"标准"的表达
写诗 / 小说 / 剧本1.0 ~ 1.20.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 通常直接崩坏。要多样性应该用"多生成几条再挑"
Recap · 收束

采样发生在整条流水线的最末端,它不改模型一个字节,只改"从约十万个候选里怎么挑一个"——所以改一个数字下次请求就生效。

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 年震动了整个领域。

☰ 主页
学海无涯 · 智能篇 · § 8.4