本文是《高效推理与压缩(算法侧):解码、投机、量化与 KV》系列的第 1 篇(共六篇)。下一篇:投机解码:草稿、接受率与树

模型的一次前向给出的不是一个 token,是一个 \(V\) 维的分布。从这个分布里取一个 token 的规则——greedy、beam、temperature、top-k、top-p、min-p、重复惩罚、语法约束——统称解码策略。它不改变模型的任何参数,不改变一次前向的成本,但决定了用户看到的每一个字、决定了评测报出的每一个数字、也决定了 RL 训练时策略探索到什么。同一个模型在 GSM8K 上,greedy 报 78、temperature 0.6 采样报 76、多次采样投票报 85——三个数字都”对”,说的是不同的事。

这一篇放在系列开头,是因为后面五篇都要回答”这个方法让输出分布变了多少”,而采样参数本身就在改分布。不先把”从分布里怎么取”说清楚,量化前后的比较、投机解码的接受率、KV 驱逐的退化,都混进了采样的噪声。

本篇要回答的核心问题是:

同一个模型,temperature 从 0.6 调到 1.0,pass@1 与 pass@64 各怎么变?为什么方向相反?

一、总览:解码是在分布上做什么

1. 一张分类表

所有解码策略都在做三件事中的一件或几件:变形(改变分布的形状)、截断(把一部分 token 的概率置零)、搜索(在多步之间做选择)。

策略 做什么 改变分布吗 成本 典型用途
greedy 取 argmax 退化为 one-hot 一次前向一个 token 确定性任务、评测的默认(有争议)
beam search 维持 \(B\) 条候选序列,按累计对数概率保留前 \(B\) 近似最大化序列概率 \(B\) 倍前向 翻译、摘要一类短输出;开放生成上失效
temperature logits 除以 \(T\) 变形:改变熵 控制多样性
top-k 只保留概率最大的 \(k\) 个 截断 一次排序 老式的默认(\(k = 40\) / \(50\))
top-p(nucleus) 保留累计概率达 \(p\) 的最小集合 截断,自适应 一次排序 + 前缀和 当前最常用(\(p = 0.9\) / \(0.95\))
min-p 保留概率 \(\ge p_{\min} \cdot p_{\max}\) 的 token 截断,相对于最大概率 一次比较 高温度下的稳定采样
重复惩罚 对已出现的 token 降低 logits 变形,依赖历史 一次查表 抑制循环
约束解码 只允许语法合法的 token 截断,依赖状态机 状态机转移 + mask 结构化输出(JSON、代码、工具调用)
多次采样 + 选择 采 \(n\) 条,按投票 / 验证器 / RM 选 序列级的重加权 \(n\) 倍 self-consistency、best-of-n、test-time compute

前七行在单步上操作,最后两行涉及多步。单步策略的组合顺序也重要——先 temperature 再 top-p,与先 top-p 再 temperature,结果不同(第三章第五节)。

2. 先说答案

温度从 0.6 升到 1.0,单次采样的准确率(pass@1)下降,因为分布变平、采到错误 token 的概率变大;多次采样中至少一次正确的概率(pass@64)通常上升,因为 64 次采样的多样性变大、覆盖了更多解法。两个指标对温度的最优点不同——pass@1 在低温、pass@k 在中高温——这是”温度调多少”没有单一答案的原因,也是 RL 训练(需要探索,用高温)与部署(需要单次准确,用低温)的采样参数不同的原因。第七章展开。

3. 本文的章节安排

主题 内容
搜索 greedy 的短视;beam search 的长度偏差与重复;为什么 LLM 上几乎不用 beam
变形与截断 temperature 与熵的推导;top-k / top-p / min-p 各裁掉什么;组合顺序
重复惩罚 三种惩罚的数学;副作用;为什么推理模型要关掉它
约束解码 语法 → 状态机 → token mask;两种编译策略;预处理成本;对分布的影响
多步:多次采样与选择 self-consistency、best-of-n、pass@k 的无偏估计;test-time compute
采样参数与评测 pass@1 与 pass@k 对温度的相反响应;推理模型为什么 greedy 变差;RL 与推理的温度一致性
成本 采样本身的成本;约束解码的开销;多次采样的账
动手(建议) 一张温度 × 截断的扫描表
本文小结  

1. greedy 的短视

自回归生成的目标序列是使联合概率最大的那条:

\[y^* = \arg\max_y \prod_{t=1}^{T} p(y_t \mid y_{<t}, x) = \arg\max_y \sum_{t=1}^{T} \log p(y_t \mid y_{<t}, x)\]

精确求解需要遍历 \(V^T\) 条序列,不可能。greedy 每步取当前最大的 token,是这个问题最粗的近似:它保证每一步局部最优,但一个当前概率 0.4 的 token 后面可能接着一串低概率的延续,而当前 0.35 的 token 后面是高概率的延续。greedy 看不到第二步。

greedy 的另一个问题是重复。在开放生成中,一旦模型进入一个循环(”我认为我认为我认为”),每一步的 argmax 都倾向于延续循环——因为已出现的 n-gram 会提高它再次出现的条件概率(Holtzman 等 2020 展示的”退化”现象)。这在预训练模型上尤其明显,在经过后训练的模型上缓和很多,但在长输出上仍会出现。

2. beam search:维持 B 条候选

beam search 每步维持 \(B\) 条候选序列(beam),对每条扩展所有 \(V\) 个 token,从 \(B \times V\) 个延续里按累计对数概率保留前 \(B\) 条。它在翻译上是几十年的默认,\(B = 4\) 或 \(5\) 通常比 greedy 好 1–2 BLEU。

它在 LLM 的开放生成上失效,有两个结构性原因:

长度偏差。累计对数概率是负数的和,序列越长和越小。beam search 系统性地偏好短序列——它会尽早选择 EOS。常用的修正是长度归一化 \(\frac{1}{T^\alpha} \sum_t \log p(y_t \mid \cdot)\)(\(\alpha \approx 0.6\)–\(1.0\)),但这引入了一个与任务相关的超参数,且只是缓解。

高概率不等于高质量。Holtzman 等 2020 的观察:人写的文本的每 token 概率在模型看来是波动的、经常落在低概率区,而 beam search 找到的高概率序列是平淡的、重复的。原因是语言本身有高熵——一句话之后有很多种合理的下一句,每一种的概率都不高;beam search 挑的是”每一步都最不令人惊讶”的路径,而这条路径恰恰不像人写的。最大化序列概率不是生成的正确目标,这是从 beam 转向采样的根本原因。

beam search 的成本是 \(B\) 倍的前向(可以 batch 成一次),加上每步的 \(B \times V\) 排序与 KV cache 的重排(被淘汰的 beam 的 KV 要被存活 beam 的 KV 覆盖——在 PagedAttention 下这是 block 指针的复制)。在多数推理引擎里 beam search 是二等公民:vLLM 曾经支持又移除又以另一种形式加回,因为它与 continuous batching 的调度不兼容。

3. beam 还在哪里用

短输出、有明确正确答案、且答案空间小的任务:翻译、语音识别的解码、约束下的结构化输出。以及一类特殊用法——推理模型的搜索(第六章):在推理链的步骤级别做 beam search,用过程奖励模型(PRM)给每条 beam 打分,这时”最大化概率”被”最大化奖励”替代,长度偏差与平淡的问题不再出现。

三、变形与截断:temperature、top-k、top-p、min-p

1. temperature:改变熵

模型输出 logits \(z \in \mathbb{R}^V\),温度 \(T\) 下的分布是

\[p_T(i) = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}\]

\(T = 1\) 是模型的原始分布;\(T \to 0\) 退化为 argmax(greedy);\(T \to \infty\) 趋向均匀。温度对分布熵的影响可以精确地写出来。记 \(H(T)\) 为 \(p_T\) 的熵,

\[\frac{dH}{dT} = \frac{1}{T^3} \text{Var}_{p_T}(z)\]

推导:\(H = -\sum_i p_i \log p_i\),\(\log p_i = z_i / T - \log Z\)。对 \(T\) 求导时用 \(\partial p_i / \partial T = -\frac{p_i}{T^2}(z_i - \mathbb{E}[z])\),代入整理得到上式(中间要用到 \(\sum_i p_i (z_i - \mathbb{E}[z]) = 0\))。方差非负,所以熵随温度单调增;logits 的方差越大(模型越”确定”),同样的温度变化对熵的影响越大。

两个推论。第一,温度的效果依赖 logits 的尺度。两个模型在同一个 \(T = 0.7\) 下的行为可能完全不同——一个 logits 尺度大的模型(训练时 z-loss 或 logit soft-capping 会影响它)在 0.7 下已经接近 greedy,另一个还在广泛采样。跨模型比较温度没有意义;同一模型的不同 checkpoint 之间也未必可比。第二,温度改变的是相对概率的比值:\(p_T(i) / p_T(j) = (p_1(i) / p_1(j))^{1/T}\)。\(T = 0.5\) 时,原本 2 倍的概率比变成 4 倍;原本 10 倍变成 100 倍。低温放大头部、压缩尾部,但不会把任何非零概率变成零——尾部的 token 仍然可能被采到,只是概率极小。这是截断策略存在的原因。

2. 为什么需要截断:尾部的总质量

一个 \(V = 128K\) 的词表,即使每个尾部 token 的概率只有 \(10^{-6}\),十万个这样的 token 加起来是 0.1——采到”尾部某一个”的概率是 10%。而尾部 token 几乎全是错的(一个不通的字、一个不相关的词)。一旦采到一个,它进入上下文,后续的分布被污染,错误级联。Holtzman 等 2020 的核心论点是:模型对尾部的概率估计是不可靠的(训练数据里没见过足够多的样本去校准 \(10^{-6}\) 这个量级),把它们截掉比信任它们更好。

温度不能解决这个问题——\(T = 0.7\) 把尾部总质量从 0.1 降到也许 0.02,仍然不是零。截断是把它们置零再归一化。

3. top-k:固定个数

保留概率最大的 \(k\) 个 token,其余置零,归一化。\(k = 40\)(GPT-2 时代)或 \(50\)。问题是 \(k\) 与分布形状无关:在一个模型很确定的位置(第一个 token 概率 0.95),\(k = 40\) 保留了 39 个几乎不该被采的 token;在一个开放的位置(”我今天想吃”后面),\(k = 40\) 可能截掉了合理的选项。

4. top-p(nucleus):固定质量

保留累计概率达到 \(p\) 的最小 token 集合:把 token 按概率降序排列,取前缀直到累计概率 \(\ge p\)。\(p = 0.9\) 或 \(0.95\)。它自适应分布的形状——尖的分布保留少数几个,平的分布保留很多。

top-p 的一个不直观的缺陷在高温度下出现。温度升高时分布变平,头部 token 的概率下降,累计到 \(p\) 需要更多 token;这些新进入的 token 在原始分布里可能只有 \(10^{-4}\),被温度抬高到 \(10^{-3}\) 之后进入了 nucleus。结果是高温度 + top-p 会采到很多”本来不该采”的 token——top-p 在它最需要起作用的时候(高温)保护最弱。

另一个缺陷是平坦分布上的截断过于激进:当分布真的接近均匀(比如生成随机数、或多个同义词),top-p 0.9 会截掉 10% 的合理选项,而这些选项与被保留的选项没有本质区别。

5. min-p:相对于最大概率

min-p(Nguyen 等 2024)保留概率 \(\ge p_{\min} \cdot p_{\max}\) 的 token,\(p_{\max}\) 是当前分布的最大概率。\(p_{\min} = 0.05\) 或 \(0.1\)。逻辑是:一个 token 值不值得被采,取决于它相对于最好选项有多差。\(p_{\max} = 0.9\) 时阈值是 0.09,几乎只剩头部;\(p_{\max} = 0.1\)(平坦分布)时阈值是 0.01,保留很多。

min-p 在高温度下比 top-p 稳定,原因是它对每个 token 独立判断。温度升高时,一个尾部 token 与最大概率的比值 \(p_T(i)/p_T(\max) = (p_1(i)/p_1(\max))^{1/T}\) 确实变大——原本 \(10^{-4}\) 的比值在 \(T = 2\) 下变成 \(10^{-2}\),仍低于 0.05 的阈值,被截掉;而 top-p 是累计判断,头部一变平,几十上百个这样的尾部 token 一起被放进 nucleus。换句话说,min-p 要求每个入选 token 单独达标,top-p 允许一群不达标的 token 凑数。论文的实验是 \(T = 2\)–\(3\) 下 min-p 仍能生成连贯文本而 top-p 已经崩掉。

三种截断在同一个分布上裁掉的部分:

分布形状 top-k = 40 top-p = 0.9 min-p = 0.05
尖(\(p_{\max} = 0.95\)) 保留 40 个,其中 39 个总质量 0.05 保留 1 个 保留 1 个(阈值 0.0475)
中(\(p_{\max} = 0.3\),头部 5 个占 0.85) 保留 40 个 保留约 6–7 个 保留约 8–10 个(阈值 0.015)
平(\(p_{\max} = 0.05\),30 个几乎等概率) 保留 40 个 保留约 27 个,截掉 3 个同等合理的 保留全部 30 个(阈值 0.0025)
高温后的中等分布(\(p_{\max} = 0.12\)) 保留 40 个 累计到 0.9 需要几十甚至上百个 保留约 15–20 个(阈值 0.006)

6. 组合顺序

实践中常同时开 temperature、top-k、top-p(有时加 min-p)。顺序在不同实现里不同,且结果不同。Hugging Face generate 的默认顺序是 temperature → top-k → top-p(作为 logits processor 依次作用);vLLM 的采样器是 temperature → top-k → top-p → min-p(实现细节随版本变)。先温度再截断意味着截断作用在温度变形后的分布上——这是 top-p 在高温下失效的原因(上一节)。先截断再温度则是在原始分布上决定保留谁,再对保留的部分变形——高温下更安全,但多数库不是这个顺序。

min-p 的论文建议的顺序是 temperature → min-p,让阈值在变形后的分布上计算;但因为它的相对性,两种顺序的差别比 top-p 小得多。

一个实用的规则:不要同时开三个截断。top-k 与 top-p 同时开时,实际起作用的几乎总是更严的那个,另一个只增加困惑。当前多数模型的推荐配置是 temperature + top-p(Llama、Qwen 的默认)或 temperature + min-p(社区在高温创意写作上的选择),推理模型的推荐配置在第七章。

四、重复惩罚

1. 三种形式

重复惩罚在采样之前修改已出现 token 的 logits。三种常见形式:

名字 公式 特点
repetition penalty(Keskar 等 2019,CTRL) \(z_i \leftarrow z_i / r\) 若 \(z_i > 0\),\(z_i \leftarrow z_i \cdot r\) 若 \(z_i < 0\);\(r > 1\) 乘法;对正负 logits 处理不同;不看出现次数
presence penalty(OpenAI API) \(z_i \leftarrow z_i - \alpha \cdot \mathbb{1}[i \text{ 已出现}]\) 加法;出现过就罚固定量
frequency penalty(OpenAI API) \(z_i \leftarrow z_i - \beta \cdot \text{count}(i)\) 加法;按出现次数线性增加

repetition penalty 的乘法形式有一个古怪之处:它假设 logits 的符号有意义(正的压小、负的压得更负),但 logits 的绝对值没有意义——softmax 对整体平移不变,同一个分布可以由全正、全负、或正负混合的 logits 表示。不同模型的 logits 偏置不同,同一个 \(r = 1.2\) 在不同模型上的效果不同。加法形式(presence / frequency)是平移不变的,更规范。

2. 副作用

重复惩罚对所有已出现的 token 生效,包括那些必须重复的:标点、”的”、”the”、代码里的变量名、JSON 的引号与括号、数学推导里反复出现的符号。一个 \(r = 1.3\) 的 repetition penalty 会让模型在写了三次 return 之后开始回避第四次——用别的写法绕开,或者干脆写错。在代码与数学任务上,重复惩罚几乎总是有害的。

frequency penalty 的线性增长在长输出上尤其危险:一个出现了 50 次的常用词被罚 \(50\beta\),\(\beta = 0.1\) 时是 5 个 logit 单位——概率被压到 \(e^{-5} \approx 0.7\%\) 倍,等于被禁用。

3. 推理模型为什么要关掉它

推理模型的思维链有大量结构性重复:”让我检查一下”、”所以”、”等式两边”、重新计算同一个中间量。DeepSeek-R1、Qwen3 的推荐采样参数里都没有重复惩罚(或明确设为 1.0),推荐的是 temperature 0.6、top-p 0.95。开了重复惩罚的推理模型会在验算时因为不能重复之前的式子而出错,或者被迫换一种表述从而引入错误。

重复的根本解法不在解码,在训练:后训练充分的模型很少陷入循环;RL 训练里的长度惩罚与格式奖励也抑制它。解码侧的重复惩罚是对训练不足的补丁,应当作为最后手段、以最小的力度使用。

五、约束解码:结构化输出

1. 问题

工具调用、JSON 输出、代码生成、选择题作答,都要求输出满足某种形式约束。让模型”尽量”输出合法 JSON 的做法(prompt 里给例子、事后修复)在 95% 的情况下可行,剩下 5% 需要重试或失败。约束解码把合法性变成 100%:每一步只允许采样能使输出继续合法的 token

2. 从语法到 token mask

约束的表达方式从简到繁:正则表达式、JSON Schema、上下文无关语法(CFG)。执行的方式都一样:维护一个状态(当前在语法的哪个位置),每步计算”从这个状态出发,哪些 token 是合法的下一步”,把不合法的 token 的 logits 置 \(-\infty\),然后照常采样。

难点在token 与字符的不对齐。语法定义在字符上({"、数字),而模型输出 token——一个 token 可能是 {"": "123,跨越了多个语法状态。所以”从状态 \(s\) 出发合法的 token 集合”需要对每个 token 模拟它的全部字符能否被语法接受、接受后到达哪个状态。词表 128K、状态数几千到几万,朴素地每步计算是 \(O(V \times L)\)(\(L\) 是 token 的平均字符数),每步几十毫秒——比一次前向还慢。

两种编译策略解决它:

预编译(outlines 的做法):把正则表达式编译成有限状态机(FSM),对 FSM 的每个状态预计算合法 token 集合,存成一张 状态 × 词表 的表。生成时每步查表,\(O(1)\)。代价是预编译:状态数 × 词表大小的模拟,对复杂 schema 可能几秒到几十秒,且每个(schema, tokenizer)对要编译一次并缓存。JSON Schema 先转成正则再编译;CFG 需要下推自动机,状态无穷,只能部分预编译。

运行时 + 缓存(xgrammar、llguidance 的做法):把词表按 token 的字符序列组织成前缀树;对 CFG 用下推自动机,运行时对当前栈状态计算合法 token,但利用两个观察大幅剪枝——多数状态下合法 token 集合要么很小(只允许 ,})、要么几乎是全集(在字符串内部),可以分别用”允许列表”和”禁止列表”表示;以及上下文无关的部分(不依赖栈的状态)可以预计算。xgrammar 报告的每 token 开销在几十微秒,与前向重叠后接近零。

3. 约束改变了分布

约束解码的输出不是“模型在合法输出上的条件分布”。它是逐 token 的贪心约束:每步在合法 token 上重新归一化。两者的差别是:条件分布 \(p(y \mid y \in \mathcal{L})\) 要求对整条序列归一化,而逐 token 约束只对每步归一化,它给了那些”前缀合法但没有合法延续”的路径过高的概率——虽然 FSM 保证了任何合法前缀都有合法延续,但延续的概率质量可能极小,模型被迫在几个它都不想选的 token 里挑一个。

实际影响:在 schema 与模型自然输出格式接近时(模型本来就会写 JSON),约束几乎不改变分布,只是把 5% 的失败修正了;在 schema 与模型习惯差异大时(要求特定的字段顺序、特殊的键名),约束把模型逼进它不熟悉的区域,内容质量下降——”格式对了,答案错了”。这是约束解码在评测中有时降低任务准确率的原因(Tam 等 2024 报告过这个现象)。缓解办法:让 schema 尽量贴近模型的自然格式;先让模型自由生成再用约束解码做第二遍”格式化”;或在后训练时用目标格式的数据 SFT。

4. 与 token healing 的关系

约束解码的一个副产品是 token 边界问题。prompt 以 {"name": " 结尾时,模型想输出的下一个 token 可能是 "Alice" 这种带引号的整体,但引号已经在 prompt 里了。token healing(04 系列第九篇)回退最后一个 token 让模型重新选择边界;约束解码的 FSM 在处理 prompt 尾部时也要做同样的回退,否则合法 token 集合会被错误地缩小。

六、多步:多次采样与选择

1. self-consistency:采样 n 次投票

Wang 等 2022 的观察:对同一个问题采样 \(n\) 条推理链,取最终答案的多数投票,比 greedy 的单条链准确率高很多——GSM8K 上从 56.5% 到 74.4%(PaLM 540B,\(n = 40\))。原因是推理链有多条正确路径、更多条错误路径,但错误路径的答案是分散的,正确路径的答案是集中的;投票把分散的错误抵消掉。

它要求答案可以比较(数字、选项、短字符串),且需要温度 > 0——greedy 采 40 次是同一条链。这正是温度对 pass@k 有正向作用的机制:多样性让投票有东西可投。

2. best-of-n:用奖励模型或验证器选

采 \(n\) 条,用 RM(L5 第二篇)或验证器(L5 第五篇)选最好的一条。它比投票更通用(不要求答案可比较),但依赖选择器的质量:RM 有过优化问题——\(n\) 越大,选出来的越可能是”骗过 RM”的那条(L5 第二篇算过 BoN 的 KL 是 \(\log n - (n-1)/n\));验证器(单元测试、答案匹配)没有这个问题,但只在可验证的任务上有。

3. pass@k 的无偏估计

pass@k 是”采 \(k\) 次至少一次正确”的概率。直接采 \(k\) 次看有没有对的,方差很大。Chen 等 2021(Codex)的无偏估计:对每题采 \(n \ge k\) 次,数出 \(c\) 次正确,

\[\text{pass@}k = 1 - \frac{\binom{n - c}{k}}{\binom{n}{k}}\]

即从 \(n\) 条里随机抽 \(k\) 条全错的概率的补。\(n = 200\)、\(k = 1, 10, 100\) 是 HumanEval 的标准协议。L5 第八篇讲了它的方差;这里补一点:pass@k 随 \(k\) 的增长曲线是模型”覆盖率”的度量——pass@1 低但 pass@100 高的模型知道怎么解但不稳定,pass@1 与 pass@100 接近的模型要么很稳要么根本不会。RLVR 训练(L5 第五篇)的一个争论就是它提高 pass@1 但不提高甚至降低 pass@k——它把基座已有的覆盖率变成了稳定性,没有增加覆盖率(Yue 等 2025)。

4. test-time compute:搜索的回归

推理模型让”多花推理算力换准确率”成为一条正式的 scaling 轴(L5 第五篇)。解码层面它有三种形态:更长的单条链(模型自己在链里反思、验算——这是 R1 类模型的主要形态,不需要解码侧做什么)、并行多条链 + 选择(self-consistency / best-of-n)、步骤级搜索(beam search 或 MCTS 在推理步骤上,用 PRM 打分——Snell 等 2024 的比较是:简单问题上并行采样 + 投票最有效,难问题上步骤级搜索更好,但 PRM 的质量决定一切)。

三者的成本都是线性的(\(n\) 倍 token),但对系统的形态不同:并行多条链是 batch 维度的扩展,prefix 共享让 prompt 只算一次;步骤级搜索需要频繁的 fork 与剪枝,KV 管理复杂。这是 vLLM 系列里 prefix caching 与 fork 语义的算法侧来源。

七、采样参数与评测

1. pass@1 与 pass@k 对温度的相反响应

回到核心问题。记模型在某题上的正确 token 序列集合为 \(\mathcal{C}\),单次采样正确的概率 \(q(T) = \sum_{y \in \mathcal{C}} p_T(y)\)。温度升高时分布变平,如果正确序列在低温下已经是高概率的(模型”会”这道题),\(q(T)\) 随 \(T\) 下降;如果正确序列在低温下概率很低(模型”不太会”),\(q(T)\) 可能随 \(T\) 先升后降。对整个评测集平均,pass@1 通常随 \(T\) 单调下降或在很低的 \(T\) 处有个平缓的峰。

pass@k 是 \(1 - (1 - q)^k\)。当 \(q\) 小时(难题),\(1 - (1-q)^k \approx kq\),与 \(q\) 线性;当 \(q\) 大时(易题)它已接近 1,对 \(q\) 不敏感。所以 pass@k 主要由难题决定,而难题上 \(q(T)\) 可能随温度上升——这就是 pass@k 随温度升高而上升的机制。更重要的是 \(k\) 次采样之间的多样性:低温下 \(k\) 次采样高度相关(都是同一条链的微小变化),有效样本数远小于 \(k\);高温下它们更独立。上面的公式假设了独立性,低温下实际的 pass@k 比公式低得多。

实证上,代码生成的经验(Codex 论文):pass@1 最优温度约 0.2,pass@100 最优温度约 0.8。数学推理类似。所以:报告 pass@1 用低温或 greedy,报告 pass@k 用中高温,两者不能用同一组参数——一份评测报告如果只给一个温度下的 pass@1 与 pass@100,其中一个必然不是最优。

2. 推理模型为什么在 greedy 下变差

DeepSeek-R1 的模型卡明确建议不用 greedy:会出现重复与死循环。原因有两层。其一,推理模型的思维链很长(几千到几万 token),greedy 的重复退化在长输出上被放大——任何一处进入循环就再出不来。其二,RL 训练时策略是在 \(T = 1\)(或接近)下采样的,模型学到的分布是”在采样下有效”的分布;greedy 取的 argmax 路径是训练时几乎没走过的路径——训练分布与推理分布不一致。Qwen3 的推荐同样是思考模式 \(T = 0.6\)、top-p 0.95、不要 greedy。

这引出一个评测方法论的问题:推理模型的评测必须用采样,因此必须多次采样报均值(L5 第八篇),且温度要与模型卡一致。用 greedy 评一个推理模型,得到的是一个偏低且不稳定的数字。

3. RL 训练的温度与推理的温度

RL 的 rollout 需要探索——温度太低时组内样本几乎一样,GRPO 的组内优势接近零,没有学习信号(L5 第三篇)。所以训练时 \(T = 1.0\) 是常见默认。但训练时的采样温度会影响模型学到的分布形状:策略梯度在 \(T = 1\) 下优化的是 \(p_1\),部署时若用 \(T = 0.6\),等于在一个没被直接优化过的分布上工作。多数情况下这没问题——低温只是让已有的偏好更尖锐——但两个已知的现象与此相关:RL 后的模型熵坍缩(分布变得过尖,\(T = 1\) 下也接近确定性,探索能力丧失——L5 第五篇提到 DAPO 的 clip-higher 是对策之一);以及模型卡推荐的温度往往就是训练时验证过的那个,偏离它的效果未经验证。

4. 评测协议里的采样参数

L5 第八篇讲过协议的每个细节都改变分数;采样参数是其中最大的一项。一份可复现的评测报告要写明:temperature、top-p / top-k / min-p、重复惩罚、最大输出长度、采样次数 \(n\)、报告的是 pass@1 的均值还是 pass@k、是否有约束解码(选择题的答案抽取是靠约束还是靠正则)。一个常见的不一致来源:选择题用约束解码强制输出 A/B/C/D,与让模型自由回答再抽取答案,分数可以差好几个点——前者剥夺了模型”先推理再回答”的机会。

八、成本

1. 采样本身

一次前向之后,采样要在 \(V = 128K\) 的 logits 上做:温度(一次除法)、top-k(部分排序,\(O(V \log k)\))、top-p(全排序 \(O(V \log V)\) + 前缀和)、min-p(一次 max + 一次比较,\(O(V)\))、归一化与采样。对 batch 256 是 \(256 \times 128K = 33M\) 个元素,在 GPU 上几十微秒到几百微秒——与一次 decode 前向的几毫秒到几十毫秒相比通常可以忽略,但有两个例外:top-p 的排序在大 batch 下不可忽略(vLLM 曾经用近似的 top-p 换速度);惩罚项需要历史 token 的统计,在 continuous batching 下每个请求的历史长度不同,实现上是一个 gather,也不算便宜。

logits 本身的字节:\(V \times 4\) 字节(FP32)= 512 KB 每个序列,batch 256 是 128 MB,这在 04 系列第九篇里算过——词表大小对采样成本的影响比对前向的影响更直接。

2. 约束解码

预编译策略:一次编译几秒到几十秒(复杂 schema),运行时每步一次查表 + 一次 mask(\(O(V)\) 的写)。运行时策略:每步几十微秒。两者的 mask 应用(把 \(V\) 个 logits 中的一部分置 \(-\infty\))在 GPU 上是一个 kernel。对大多数负载,约束解码的开销小于 5%;对超短输出(只输出一个选项字母)、超大 batch、或每个请求 schema 都不同(编译不能复用)的负载,开销会显现。

3. 多次采样

\(n\) 条链的成本是 \(n\) 倍的解码 token,prefill 通过 prefix 共享只算一次。对推理模型,一条链几千 token,\(n = 64\) 就是几十万 token——一道题的成本超过一美元的量级(按公开 API 价格)。这就是 test-time compute 的账:pass@64 的高分数是花 64 倍成本买来的,与训练一个更好的模型比,哪个便宜取决于查询量。Snell 等 2024 的结论是:在中等难度问题上,14 倍的 test-time compute 可以让小模型追上 14 倍大的模型;但在最难的问题上小模型加再多采样也不行——覆盖率为零时 \(k\) 再大 pass@k 也是零。

九、动手(建议)

一个温度 × 截断的扫描表,用现成工具能在几小时内跑完:

  • 模型:Qwen2.5-7B-Instruct 或 Llama-3.1-8B-Instruct;推理模型对照用 DeepSeek-R1-Distill-Qwen-7B。
  • 任务:GSM8K 测试集的 500 题子集(省时);HumanEval 全部 164 题。
  • 扫描:temperature ∈ {0(greedy), 0.2, 0.6, 1.0, 1.4} × 截断 ∈ {无, top-p 0.95, min-p 0.05};每个配置对每题采 \(n = 16\) 次(vLLM 的 SamplingParams(n=16),prefix 共享让成本可接受)。
  • 指标:pass@1(16 次的均值)、pass@16(无偏估计)、输出长度均值、重复率(输出里重复 4-gram 的比例)。

该看的:pass@1 与 pass@16 的最优温度是否不同;\(T = 1.4\) 下 top-p 与 min-p 的重复率与 pass@1 差多少;推理模型在 greedy 下的重复率与输出长度(预期:长度爆炸、重复率高);开 repetition penalty 1.2 后 HumanEval 的 pass@1 变化(预期:下降)。

再做一个约束解码的对照:GSM8K 用 outlines 或 xgrammar 约束输出为”推理 + 数字答案”的 JSON schema,与自由生成 + 正则抽取比 pass@1 与格式失败率。

不引用任何未跑过的数字;上述”预期”是文献与机制的推断,实验的意义正是验证它们。

十、本文小结

规则 / 公式 备注
目标 最大化序列概率不是生成的正确目标 beam 在开放生成上失效:长度偏差、平淡与重复
temperature \(p_T \propto \exp(z/T)\);\(dH/dT = \text{Var}(z)/T^3\) 效果依赖 logits 尺度,跨模型不可比;不把任何概率置零
截断 top-k 固定个数;top-p 固定质量;min-p 相对于 \(p_{\max}\) 尾部总质量可达 10%;top-p 在高温下失效;min-p 更稳
顺序 多数库:temperature → top-k → top-p → min-p 不要同时开多个截断
重复惩罚 乘法(CTRL)/ presence / frequency 伤代码、数学与推理;推理模型关掉
约束解码 语法 → FSM / PDA → token mask;预编译或运行时 逐 token 约束 ≠ 条件分布;schema 远离自然格式时降质量
多次采样 self-consistency 投票;best-of-n;pass@k \(= 1 - \binom{n-c}{k}/\binom{n}{k}\) RLVR 提高 pass@1 不提高 pass@k
温度与评测 pass@1 最优低温(~0.2);pass@k 最优中高温(~0.8) 两者不能用同一组参数报告
推理模型 不用 greedy;\(T = 0.6\)、top-p 0.95、无惩罚(R1 / Qwen3 模型卡) 训练时 \(T = 1\) 采样,greedy 路径未被训练
成本 采样几十到几百微秒;约束 < 5%;\(n\) 次采样 \(n\) 倍 token test-time compute 用 \(n\) 倍成本换 pass@n

核心问题的答案:温度从 0.6 升到 1.0,分布变平,单次采样落到正确序列上的概率下降,pass@1 下降;但 64 次采样之间的多样性上升——低温下 64 条链高度相关、有效样本数远小于 64,高温下它们接近独立——对难题(正确序列在低温下概率很低)来说被覆盖到的机会增加,pass@64 上升。两个指标的最优温度不同,是因为一个奖励”稳定地走最可能的路”,另一个奖励”至少一次走到正确的路”;RL 训练用高温探索、部署用低温利用,也是同一个权衡。任何一份只报一个温度的评测,都在这两个指标中至少牺牲了一个。

下一篇讲唯一不改变输出分布的加速方法:投机解码——从 04 系列第七篇的证明出发,讨论怎么把接受率提上去。

下一篇

投机解码:草稿、接受率与树

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/decoding-strategies-sampling-and-constrained-generation.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×