AI/ML 详解:解码与采样数学

Day 45 · 2026-07-01
面向:有编程经验的非 AI 方向工程师

模型 forward 一次,输出的不是一个词,而是词表上几万个 token 的一整张概率分布。从这张分布里挑出下一个词,就是「解码(decoding)」。同一个模型、同一段 prompt,换一种解码方式,输出可以从「呆板重复」变成「天马行空」——甚至从「事实准确」变成「一本正经胡说」。今天讲清楚这最后一步的数学:确定性搜索、随机采样的分布重塑、以及两个漂亮的对照机制。

贪心与束搜索Greedy & Beam Search

确定性解码搜索
一句话类比

Greedy 就是贪心算法——每一步只挑当前概率最高的 token,像找零钱时每次都拿面值最大的硬币,简单但容易陷入局部最优。Beam search 则是查询优化器的思路:不敢只押一条执行计划,而是维护一个固定容量为 k 的候选队列(bounded priority queue),每步让 k 条路径各自展开、按累积得分剪枝、只留 top-k 继续。它用 k 倍算力买一点全局视野。

它解决什么问题 + 工作机制

生成一整句话,本质是找一个联合概率最大的 token 序列。理论上要枚举所有序列——词表 5 万、句子 20 词就是 5万²⁰ 种,绝无可能。Greedy 是最粗暴的近似:逐词取 argmax。问题在于局部最优 ≠ 全局最优——这一步概率最高的词,可能把后面逼进死胡同。

Beam search 缓解这点:保留 k 条最优前缀,每步把每条前缀 × 词表全部候选算出,再按对数概率之和取全局 top-k。

score(序列) = Σᵢ log P(tokenᵢ | 前文)

用 log 相加而非概率相乘,是为了避免几十个小数连乘下溢到 0(和你在后端算长链路概率时取对数是同一招)。beam 宽度 k=1 就退化成 greedy。

Beam search,宽度 k=2(每步只留 2 条最优路径)

起点 The
├→ cat logP=-0.9 ✓保留
├→ dog logP=-1.2 ✓保留
└→ sky logP=-3.1 ✗剪枝
↑ 每层把 2×词表 条候选排序,只带走累积 logP 最高的 2 条进入下一层

核心反直觉(Holtzman et al. 2019 揭示):对开放式生成(写作、对话),追求「最高概率序列」反而产出枯燥且诡异重复的文本。因为人类真实语言里充满了「不那么高概率但更有信息量」的选择;一味最大化概率,模型会收敛到「the the the」式的安全废话。这就是为什么聊天模型几乎从不用纯 beam search——它适合翻译、摘要这类「答案接近唯一」的任务,不适合创造。

代码示例
from transformers import AutoModelForCausalLM, AutoTokenizer
tok = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")
ids = tok("The future of AI is", return_tensors="pt").input_ids

# 贪心:每步 argmax,完全确定性
greedy = model.generate(ids, max_new_tokens=30, do_sample=False)

# 束搜索:保留 5 条路径,选累积 logP 最高的整句
beam = model.generate(ids, max_new_tokens=30, num_beams=5,
                      early_stopping=True)
print(tok.decode(beam[0], skip_special_tokens=True))
常见误区 + 实践场景
❌ 误区:「概率最高的序列 = 质量最好的文本」。这正是 Holtzman 论文推翻的直觉——最大化似然会滑向重复退化。「好文本」并不住在概率分布的最高峰,而在一个略低、更宽的区域。这也是后面三个概念存在的根本理由。
📌 超个体场景:当你需要模型可复现、可审计的输出(如从财报抽取结构化字段、跑同一个 prompt 对比不同版本),用确定性解码(temperature=0,本质就是 greedy);当你要它写作、发散,就该切到采样——先分清任务属于「搜索」还是「创造」。
Takeaway + 思考题
💡 解码是一道搜索题,但「最优解」对开放式生成是个陷阱——高概率不等于高质量。
🤔 你的哪些 AI 任务真正需要「唯一正确答案」,哪些其实需要「多样但合理」?这个区分决定了该用搜索还是采样。

温度与截断采样Temperature · Top-k · Top-p · Min-p

随机采样概率重塑
一句话类比

如果 greedy 是「永远读缓存里最热的那一行」,采样就是「按热度加权随机抽一行」。Temperature 是控制这个随机性的总阀门——像给 softmax 的分布做「锐化 / 模糊」;而 top-k / top-p / min-p 是三种把长尾垃圾候选先砍掉的截断策略,防止小概率的荒唐 token 被抽中。

它解决什么问题 + 工作机制

Temperature(温度 T)在 softmax 里给每个 logit 除以 T:

Pᵢ = exp(zᵢ / T) / Σⱼ exp(zⱼ / T)

zᵢ 是第 i 个 token 的原始 logit。直觉:T < 1(如 0.3)放大了 logit 之间的差距 → 分布更尖 → 高概率词更霸道(趋近 greedy);T > 1(如 1.5)压平差距 → 分布更均匀 → 冷门词更有机会;T → 0 就是 argmax,T → ∞ 就是完全均匀乱选。关键:temperature 不改变模型「知道」什么,只重塑它已经算出的那张分布的锐度

同一组 logits,不同温度下的概率分布

T=0.5 (尖锐/保守)
  
T=1.0 (原始)
  
T=1.6 (平坦/发散)
  
↑ 温度越高,分布越平,冷门词被抽中的概率越大

光调温度不够:高温下长尾的荒唐词也被抬起了概率。三种截断先把尾巴砍掉,再在剩下的候选里按概率归一化采样:

  • Top-k——只留概率最高的 k 个 token。缺点:k 是固定的,分布很尖时还是塞进 k 个候选,分布很平时又砍掉了合理选项;
  • Top-p / 核采样(Nucleus)(Holtzman 2019)——留下累积概率刚好 ≥ p 的最小候选集。它动态调整候选数:模型很确定时集合小,很犹豫时集合大。类比:只保留「命中累积热度 90% 的热点行」;
  • Min-p(Nguyen 2024)——阈值 = 最高概率 × min_p,砍掉所有低于它的 token。当模型很确定(最高概率 0.9)时截断极狠,只留最优;不确定(最高概率 0.2)时放宽,保留多样。在高温下比 top-p 更稳。
代码示例
# HuggingFace:三种截断可叠加,采样在剩余候选里进行
out = model.generate(
    ids, max_new_tokens=40,
    do_sample=True,       # 打开随机采样(否则忽略下面所有参数)
    temperature=0.8,      # 分布锐度:<1 保守,>1 发散
    top_k=50,             # 只在概率最高的 50 个里采
    top_p=0.9,            # 且累积概率 ≥0.9 的核内采(动态候选数)
    min_p=0.05,           # 且概率 ≥ 0.05×最高概率 的才保留
)
print(tok.decode(out[0], skip_special_tokens=True))
# 通常不会同时用满三种;理解各自的「动态 vs 固定」差异是关键
常见误区 + 实践场景
❌ 误区:「temperature 是模型的『创造力旋钮』,调高就更聪明」。错。温度不注入任何新信息,它只是重新分配模型本已算出的概率质量。高温不是「更有创意」,而是「更愿意赌小概率」——赌对了叫创意,赌错了叫幻觉。创造力来自模型的知识,不来自这个除法。
📌 超个体场景:理解「你调的到底是什么物理量」——事实核查/代码生成用低温(让分布集中在它最确信的答案),跨学科头脑风暴用高温 + top-p(放宽核,允许模型跳到相邻概念)。你不是在调「聪明度」,而是在选「愿意离开高峰多远」。
Takeaway + 思考题
💡 采样 = 温度重塑分布 + 截断砍掉长尾。核心分野是「固定候选数(top-k)」还是「随模型信心动态伸缩(top-p / min-p)」。
🤔 如果模型对下一个词极度确定(一个词 0.99),高温还有意义吗?(提示:想想 min-p 为什么此时几乎不放行别的词。)

对比解码Contrastive Decoding

对照机制质量
一句话类比

做 diff / 控制变量法:拿一个「业余(弱小)模型」当对照组,用「专家(强大)模型」的对数概率减去它。两个模型共有的廉价坏毛病——重复、语法套话、无脑高频词——会在相减中被抵消掉,只留下强模型独有的知识和判断。不是让两个模型投票求和,而是用弱模型做减法

它解决什么问题 + 工作机制

接续概念 1 的难题:greedy 太呆、纯采样又可能跑偏。对比解码(Li et al. 2022)换个思路——坏文本的共性是什么?答案:那些无论大小模型都爱犯的错(重复、通用废话)。一个 GPT-2 small 和一个 GPT-2 XL 都会给「the」很高概率;但只有大模型能给出真正贴切的罕见词。于是打分函数变成两者之差:

score = log P专家(token) − λ · log P业余(token)

λ 控制减法力度。直觉:一个 token 如果专家和业余都爱(如「the」),相减后优势被抹平;如果专家爱、业余不爱(专家凭知识才选的词),相减后脱颖而出。但纯做减法有个坑:业余模型极度厌恶的合理词会被过度奖励,产出乱码。所以加一条合理性约束(plausibility constraint):只在专家模型概率足够高(≥ α × 最高概率,思路和 min-p 同源)的候选里做对比,先划定「专家认可的安全区」,再在区内用业余模型排座次。

对比解码:用「专家 − 业余」凸显真正的知识信号

候选词 "the":专家 0.30 · 业余 0.28 差值 ≈0(共有俗套,压制)
候选词 "photosynthesis":专家 0.20 · 业余 0.02 差值 大(专家独有,奖励)
↑ 先用合理性约束圈出专家认可的候选,再按差值排序

效果:兼得 greedy 的连贯和采样的丰富,且更少重复退化。后续工作(Contrastive Decoding Improves Reasoning,2023)还发现它能提升推理准确率——因为「业余模型的错误直觉」正是要被减掉的东西。

代码示例
import torch, torch.nn.functional as F
expert  = AutoModelForCausalLM.from_pretrained("gpt2-large")
amateur = AutoModelForCausalLM.from_pretrained("gpt2")  # 小=业余

lp_e = F.log_softmax(expert(ids).logits[:, -1], dim=-1)
lp_a = F.log_softmax(amateur(ids).logits[:, -1], dim=-1)

# 合理性约束:只保留专家概率 ≥ α×最高 的候选,其余置 -inf
alpha, lam = 0.1, 1.0
mask = lp_e < (lp_e.max() + torch.log(torch.tensor(alpha)))
score = lp_e - lam * lp_a          # 核心:专家 − λ×业余
score[mask] = -float("inf")      # 砍掉专家都不认可的词
next_id = score.argmax(-1)          # 在安全区内取对比最优
常见误区 + 实践场景
❌ 误区:「对比解码就是集成两个模型让它们投票(求和/平均)」。恰恰相反——它是减法。集成是「三个臭皮匠」,对比是「用臭皮匠当反面教材」:业余模型的价值不在于它对,而在于它代表了「不需要真知识就能犯的错」,把它减掉剩下的才是真本事。
📌 超个体场景:这个「用弱基线做减法」的思想可迁移到人机协同——评估一个 AI 方案时,先问「一个啥都不懂的人靠套路也能给出的答案是什么」,把这部分从模型输出里心理上「减掉」,剩下的才是它真正贡献的信息增量。
Takeaway + 思考题
💡 对比解码:坏毛病是大小模型的共性,用弱模型做减法就能滤掉共性、留住强模型独有的知识。
🤔 「共有的错误」这个概念很深——你能想到别的领域里,「用一个弱基线去抵消系统性偏差」的例子吗?(提示:因果推断里的对照组、金融里的对冲。)

推测解码Speculative Decoding

加速机制无损
一句话类比

这是 CPU 的投机执行(speculative execution)+ 分支预测,或数据库的乐观并发控制(OCC)搬到了解码上:先让一个便宜的小模型乐观地猜一串 token,再让昂贵的大模型一次性并行验证这串猜测。猜对的直接采纳(省下了逐个生成的时间),猜错的从那里回滚重来。关键是——最终输出和「大模型自己逐字生成」在数学上完全一致,是无损加速。

它解决什么问题 + 工作机制

自回归生成的痛点:逐 token 串行。生成 100 个词就要 100 次大模型 forward,每次都得把整个巨型模型从显存过一遍——GPU 算力闲置,瓶颈在访存带宽而非计算。Leviathan et al.(2022)的洞察:很多 token 是「容易的」(「the」「of」「.」这种),小模型也能猜对,何必动用大模型?

流程:草稿模型 q 先自回归生成 γ 个 token;大模型 p 把这 γ 个 token 一次并行 forward(一次算完 γ 个位置的分布,这是关键——验证是并行的,生成才是串行的)。然后逐个用拒绝采样决定接受与否:

接受 token x 的概率 = min(1, P(x) / q(x))

直觉:小模型对 x 的信心若不超过大模型(q ≤ p),无条件接受;若小模型过度自信(q > p),则以 p/q 的概率接受。一旦某个 token 被拒,就丢弃它之后的所有草稿,并从修正分布 norm(max(0, p − q)) 里重采一个——数学上可证明,这套接受 / 重采规则让最终分布严格等于大模型 p 的分布。所以它不牺牲任何质量,只把「一串容易 token」的成本从「γ 次大模型」压到「1 次大模型 + γ 次小模型」。

推测解码一轮(γ=4 草稿)

小模型草稿 isanewera (串行,但便宜)
大模型验证 一次 forward 并行算 4 个位置的分布
isanewera 拒于此,重采正确词
↑ 接受 3 + 重采 1 = 一轮拿到 4 个 token,只花 1 次大模型 forward

草稿模型越接近大模型,接受率越高、加速越大;论文在 T5-XXL 上报告约 2–3 倍加速且输出逐字不变。DeepMind 同期的 speculative sampling(Chen et al. 2023)给出了等价的独立推导。

代码示例
# HuggingFace 内置:传一个小的 assistant_model 即启用推测解码
big   = AutoModelForCausalLM.from_pretrained("gpt2-xl")   # 目标(慢/准)
draft = AutoModelForCausalLM.from_pretrained("gpt2")      # 草稿(快)

out = big.generate(
    ids, max_new_tokens=60,
    assistant_model=draft,   # ← 小模型猜、大模型验,输出分布不变
    do_sample=True, temperature=0.7,
)
print(tok.decode(out[0], skip_special_tokens=True))
# 结果与不加 assistant_model 时分布一致,只是更快
常见误区 + 实践场景
❌ 误区:「推测解码是近似加速,会牺牲一点质量换速度」。错——它是数学上无损的。拒绝采样的接受 / 重采规则经过精心设计,保证最终 token 分布与大模型单独生成严格相同。草稿模型烂只会让接受率低(加速少),永远不会污染输出。这正是它优雅之处:速度是赌来的,正确性是证明来的。
📌 超个体场景:理解你日常用的 API 为何变快、且没变笨。更普适的是这个思维模型——「乐观地猜 + 廉价地验证 + 错了才回滚」:写代码时先让 AI 大胆生成、自己快速核验关键处;做决策时先出草案再验证假设。投机执行是通用的效率哲学。
Takeaway + 思考题
💡 推测解码 = 解码版的投机执行:小模型猜、大模型并行验、拒绝采样保证输出分布一字不改。免费的午餐(几乎)。
🤔 推测解码用「便宜的猜测 + 可证明的验证」换速度。你的工作流里,哪些环节可以「先乐观执行,再廉价验证」,而不是「小心翼翼逐步确认」?

深入资源Further Reading

深入思考Deep Questions

1. 四种方法里,哪些改变模型的「输出分布」,哪些不改变?为什么这个区分很重要?
把它们分成两类是理解今天全部内容的钥匙。改变分布的:greedy(塌缩成一个点)、temperature(锐化/压平)、top-k/p/min-p(截断长尾)、对比解码(专家减业余,彻底重塑打分)——这些都在「主动改写模型说什么」。不改变分布的:只有推测解码——它是纯粹的工程加速,输出与大模型逐字生成严格相同。这个区分的意义在于责任归属:如果模型输出了幻觉,前四种是「你选择了让它离高峰更远」的结果(可调),而推测解码永远无责(它只是更快地复现了大模型本来就会说的话)。当你调试一个「模型突然胡说」的问题,先看是不是采样参数(temperature 太高、top-p 太宽)而非模型本身——这是最常见、最容易忽略的一层。
2. Top-p、min-p、对比解码的合理性约束,三者的数学形式不同,但「精神」是一致的——是什么?
三者都在回答同一个问题:「该保留多少候选词才算合理?」而且答案都是动态的、随模型信心自适应的,而非固定阈值。top-p 用「累积概率 ≥ p」——模型越确定,达到 p 需要的候选越少。min-p 用「≥ 最高概率 × 比例」——直接锚定在 top-1 的信心上。对比解码的合理性约束「≥ α × 最高概率」几乎和 min-p 是同一个式子。共同精神:模型很确定时收窄(相信它)、很犹豫时放宽(给它余地)。这比 top-k 的固定 k 更符合直觉——因为「合理候选的数量」本就不该是常数,它取决于当前这一步模型有多确定。这也呼应了概念 1 的洞察:好文本住在一个「随上下文伸缩」的区域里,而不是固定大小的邻域。
3. 推测解码「小模型猜 + 大模型验」为什么能无损?如果换成「小模型和大模型输出加权平均」会怎样?
无损的关键是拒绝采样(rejection sampling)这个数学工具,而非「平均」。接受概率 min(1, p/q) 加上「被拒时从 norm(max(0,p−q)) 重采」这两条规则,经代数展开可证明:任意一个 token 最终被输出的概率恰好等于大模型 p。直觉上,小模型只是提供了一个提议分布(proposal),拒绝采样把它「校正」回目标分布 p——小模型猜得准只影响接受率(效率),不影响最终分布(正确性)。而如果改成「加权平均」(如 0.5p+0.5q),输出分布就变成了一个全新的、既不是 p 也不是 q 的分布——那是对比解码那一类「主动改写输出」的做法,会牺牲/改变质量,不再无损。同样是「两个模型协作」,推测解码是「用小模型加速、用数学保正确」,对比解码是「用小模型的错误当反面教材改写输出」——目标截然不同,别混为一谈。
4. 概念 1 说「最高概率序列反而是坏文本」,这对「概率越大越好」的朴素直觉是个冲击。它揭示了语言模型什么本质?
它揭示了一个深刻的错位:模型被训练去「最大化似然」,但人类语言的价值并不在似然的最高峰。信息论上,一个完全可预测(高概率)的词几乎不携带信息(自信息 = −log p,p 越大信息越少)。Holtzman 论文的观察正是:人类文本的 token 概率是起伏的,而 beam search 会把生成拉向「一条持续高概率的平滑曲线」,结果枯燥重复。这也解释了为什么采样对开放式生成是必需而非「加点噪声凑合」。更哲学一点:这和复杂性科学里「有趣的系统活在有序与混沌的边缘」遥相呼应——好的生成也活在 greedy(过度有序)与高温乱采(过度混沌)之间那条窄带上。
5. 如果把「解码策略」类比成人类的「思维模式」,四种方法分别对应什么认知状态?这对你使用 AI 有何启发?
这是个有趣的映射。Greedy ≈ 直觉快答,永远说最顺口的第一反应——高效但容易陷入思维定式(局部最优)。Beam search ≈ 深思熟虑权衡多个方案再定,适合有明确最优解的问题,但对开放创作会「想太多反而平庸」。高温采样 ≈ 头脑风暴、自由联想,允许跳跃但可能离题。对比解码 ≈ 批判性思维——「一个平庸的我会怎么答?把那部分剔除」,主动对抗自己的套路。推测解码 ≈ 先快速打草稿再回头精修,而非逐字雕琢。启发在于:你和 AI 协作时,其实也在为每个任务隐式选择一种「认知温度」。真正的「超个体」不是永远用同一种模式,而是能有意识地为当前任务切换解码策略——无论对 AI 还是对自己的大脑。