CMU-11-664-语言建模的推断算法笔记-全-
CMU 11-664 语言建模的推断算法笔记(全)
1:语言模型与推断导论 🧠
在本节课中,我们将要学习语言模型的基本概念、其工作原理,以及为什么推断(Inference)过程与训练过程存在显著差异。我们将从模型定义开始,逐步深入到推断的计算成本、硬件考量以及不同的生成策略。
语言模型基础
语言模型的核心是为一个由词元(Token)组成的序列分配概率。对于一个序列 x,其概率可以表示为 P(x)。在大多数现代应用中,我们讨论的是自回归(Autoregressive)模型。
自回归模型将序列的概率分解为一系列条件概率的乘积。具体公式如下:
P(x) = P(x₁) * P(x₂ | x₁) * P(x₃ | x₁, x₂) * ... * P(x_L | x₁, x₂, ..., x_{L-1})
这意味着模型逐个预测下一个词元,每次预测都基于之前所有已生成的词元。这是当前大型语言模型(如GPT系列)的主要工作方式。扩散模型(Diffusion Models)是此规则的一个主要例外,它们采用不同的推断算法,我们将在后续课程中单独讨论。
无条件生成示例
为了理解模型的行为,我们可以观察其无条件生成(即仅给定起始词元)的结果。例如,使用一个较小的模型(如Qwen2-1.7B)从句子起始词元生成文本,我们可以将结果分为高、中、低概率序列。
- 高概率序列:通常是极短的序列,如“...”、“0 0 )”等。这是因为概率是多个小于1的数值相乘,序列越长,总概率值通常越低。
- 中概率序列:可能包含一些代码片段或数学表达式。
- 低概率序列:可能包含其他语言(如中文)或更复杂、更长的句子。
这个练习表明,单纯追求高概率输出并不总能得到对人类有用或有趣的文本。
条件生成与提示工程
在实际应用中,我们几乎总是进行条件生成。我们给定一个前缀或提示(Prompt),让模型生成后续的补全(Completion)或回应(Response)。
数学上,这相当于在自回归分解中,固定前缀部分的条件概率。例如,给定提示“The best thing about Carnegie Mellon University is”,模型会生成各种可能的补全,如“computer science”、“its community”、“its location...”等。
我们注意到,较短的补全通常具有较高的模型概率,但这不一定等同于“更好”或“更相关”的回答。因此,我们需要在模型概率和输出质量之间进行权衡。


模型架构与计算成本






目前,绝大多数高性能语言模型都基于Transformer架构,特别是仅包含解码器(Decoder-only)的变体,例如GPT系列。其核心组件包括:
- 嵌入层(Embedding)与位置编码(Positional Encoding)。
- 掩码多头自注意力层(Masked Multi-Head Self-Attention)。
- 前馈网络层(Feed-Forward Network)。
- 层归一化(Layer Norm)和线性预测层。



从推断效率的角度看,计算成本主要来自两个部分:
- 自注意力机制:其成本包含与序列长度L成线性关系的部分(Q/K/V投影),以及一个与L²成二次关系的关键部分(计算注意力权重)。
- 前馈网络:其成本与序列长度L成线性关系。
对于一个具体模型(如Llama 3.1 405B),当上下文长度较短时(例如1k),前馈网络的计算量占主导。但随着上下文长度增长到模型上限(如128k),注意力机制的二次成本部分将变得极其昂贵,成为推断的主要瓶颈。
计算公式示例(分组查询注意力):
- 注意力线性部分 FLOPs ≈
4 * L * D_model * D_model - 注意力二次部分 FLOPs ≈
2 * L² * D_model - 前馈网络 FLOPs ≈
2 * L * D_model * D_ff,其中D_ff通常是D_model的3-4倍。
训练与推断的差异
理解训练和推断的差异对于优化至关重要。
- 训练:目标是学习模型参数。我们使用大量文本数据,进行前向传播计算损失,再通过反向传播更新参数。这个过程可以处理大批量数据,并能高效饱和GPU的计算能力。
- 推断:目标是给定提示生成文本。这是一个自回归的过程,需要反复调用模型以逐个生成词元。每次生成步骤都可能无法充分利用GPU的并行计算能力,并且存在固定的调度开销,导致计算效率通常低于训练。
关键区别在于,推断时我们无法预先知道所有要生成的词元,因此无法像训练那样一次性计算整个序列的表示。
基本生成算法
最基本的生成算法框架如下:
- 初始化输出序列为给定的提示。
- 当未达到停止条件(如生成结束符或达到最大长度)时,循环:
a. 将当前序列输入模型,获得下一个词元的概率分布。
b. 根据某种策略(如采样或搜索)从该分布中选择下一个词元。
c. 将该词元追加到输出序列中。
这个框架衍生出两大类方法:
- 采样(Sampling):从模型输出的概率分布中随机抽取下一个词元。这能产生多样性高的输出,但可能牺牲一致性或质量。
- 搜索(Search):试图找到在某个评分标准下(不一定是概率)最优的输出序列。例如贪婪搜索(每一步选概率最高的词元)和束搜索(Beam Search,同时保留多个候选序列)。搜索通常能产生质量更高、更一致的输出,但多样性较低。
元生成算法与潜在变量
有时,生成单个序列不足以达到最佳效果,因此出现了元生成算法。这类算法将生成过程本身作为一个子程序来调用。
一个常见的例子是重排序:
- 使用模型生成多个候选序列。
- 使用另一个(可能相同的)模型或评判标准为这些候选序列打分。
- 选择得分最高的候选作为最终输出。
另一种重要概念是引入潜在变量,例如思维链。模型在输出最终答案前,先生成一系列中间推理步骤。这能显著提升复杂任务的准确性,但也带来了新的推断挑战:
- 效率:生成更长的中间文本会增加延迟。
- 评估:我们可能只关心最终答案,而不展示中间步骤。这催生了如自洽性等方法,即生成多个推理路径,然后选择最一致的答案。
评估、搜索错误与模型错误
我们通常不满足于高概率输出,而是追求在某种评估标准下的“好”输出。评估标准可以是人类偏好、另一个LLM的评判(LLM-as-a-judge),或针对特定任务的确定性指标(如数学答案的正确性)。
在优化输出时,需要区分两类错误:
- 搜索错误:你的生成算法未能找到模型评分最高的输出。例如,贪婪搜索可能错过全局更优的序列,而束搜索能找到更好的。解决方法是改进推断算法(如使用更强大的搜索方法)。
- 模型错误:模型本身的评分函数与你的评估标准不一致。即模型认为概率高的输出,在实际评估中得分反而低。解决方法通常是改进模型训练(使其评分更符合人类偏好),但也可以通过调整推断算法(例如,故意不选择模型最高分的输出)来规避。
硬件与效率考量
高效的推断离不开对硬件的理解。主要硬件平台包括:
- CPU:通用计算,线程并行度低,不适合大规模神经网络推断。
- GPU(特别是NVIDIA系列):当前的主流选择,具有高度的并行计算能力和成熟的软件生态。
- 专用硬件:如Google的TPU,专为矩阵运算设计,具有高内存带宽和快速互联。
对于本课程实践,我们建议使用现有计算资源、云平台(如AWS、Google Colab)或GPU租赁市场(如RunPod)来获取必要的计算能力。
课程路线图与总结
本节课我们一起学习了语言模型和推断的基础。在后续课程中,我们将深入探讨以下主题:
- 生成算法:各种采样与搜索方法,如束搜索、A*搜索。
- 推理与智能体:思维链、工具使用、多智能体通信。
- 元生成与扩展:重排序、最小贝叶斯风险、用计算时间换取性能。
- 效率与系统优化:KV缓存、推测解码、模型压缩、替代架构(如MoE, Mamba)。

通过本课程的学习,你将能够理解并实现高效的语言模型推断策略,以平衡输出质量、多样性、延迟和吞吐量等多个目标,为构建实用的语言模型应用打下坚实基础。
2:概率基础回顾与代码示例 📊




在本节课中,我们将学习语言建模中至关重要的概率基础概念,并通过代码示例来演示这些概念的实际应用。我们将从条件概率开始,逐步介绍采样、边缘化、隐变量等核心思想,并使用一个简单的二元语法模型和一个小型Transformer模型进行实践。
概率基础回顾 🔍




上一节我们介绍了课程的整体安排。本节中,我们来看看语言建模的核心——概率论基础。理解这些概念对于后续学习各种推断算法至关重要。
条件概率与自回归语言模型
我们主要处理的是自回归语言模型。自回归语言模型基于条件概率来预测下一个词元。其核心公式是:
P(y | x)


其中,x 代表所有先前的词元,y 代表下一个要预测的词元。





为了演示,我们使用一个简单的二元语法模型。该模型仅根据前一个词来预测下一个词,这体现了马尔可夫假设——即只关注有限数量的先前词。
以下是该模型的词汇表和条件概率表示例:

# 示例:一个简单的二元语法模型条件概率表
vocab = ['<s>', 'the', 'cat', 'dog', 'park', 'mat', 'sat', 'ran', 'walked', 'played', 'on', 'in', 'too', 'quickly', 'slowly', '</s>']
# 条件概率 P(next_token | current_token) 存储在一个矩阵中
从语言模型中采样
从语言模型中采样是最简单的推断算法。其基本流程是:从起始词元开始,根据条件概率分布采样下一个词,更新当前上下文,然后重复此过程,直到遇到结束词元或达到最大长度。




温度采样是一种常用的采样技术,它可以调整概率分布的“尖锐”程度。其公式如下:




P_i' = exp(log(P_i) / T) / Σ_j exp(log(P_j) / T)

其中,T 是温度参数。
- 当
T = 1时,分布不变。 - 当
T → ∞时,分布趋近于均匀分布。 - 当
T → 0时,分布趋近于一个独热向量,即总是选择概率最高的词元(贪婪解码)。



以下是采样的核心代码逻辑:
def sample_from_model(initial_token, max_len, temperature=1.0):
sequence = [initial_token]
current_token = initial_token
for _ in range(max_len):
# 获取当前词元下的下一个词元条件概率
probs = get_conditional_probs(current_token)
# 应用温度调整
adjusted_logits = torch.log(probs) / temperature
adjusted_probs = torch.softmax(adjusted_logits, dim=-1)
# 根据调整后的概率采样下一个词元
next_token = torch.multinomial(adjusted_probs, 1).item()
sequence.append(next_token)
if next_token == end_of_sequence_token:
break
current_token = next_token
return sequence
通过采样进行估计

对于复杂的模型(如Transformer),我们通常无法解析地计算某些量(如联合概率 P(x, y))。此时,我们可以通过采样来估计这些量。

基本思想是:从模型中生成大量样本,然后基于这些样本计算我们感兴趣的统计量(例如,两个词共同出现的频率)。随着采样数量的增加,估计值会越来越接近真实值。


以下是通过采样估计联合概率的示例:



def estimate_joint_prob_from_samples(num_samples):
bigram_counts = defaultdict(int)
total_bigrams = 0
for _ in range(num_samples):
seq = sample_from_model(start_token, max_len=20, temperature=1.0)
for i in range(len(seq)-1):
bigram = (seq[i], seq[i+1])
bigram_counts[bigram] += 1
total_bigrams += 1
# 估计联合概率 P(a, b) ≈ count(a, b) / total_bigrams
joint_probs = {k: v/total_bigrams for k, v in bigram_counts.items()}
return joint_probs


重要提示:只有使用温度 T=1 进行采样,才能获得模型真实的概率分布的无偏估计。使用其他温度值会导致有偏估计。



边缘化与隐变量




边缘化是指对联合概率中的某些变量求和,以得到其他变量的边际概率。例如,从联合概率 P(x, y) 通过对 y 求和得到 P(x)。





在语言模型中,一个重要的概念是隐变量。隐变量是影响最终输出但不直接可见的隐藏结构。当前语言模型中的一个典型例子是思维链。在思维链模型中:
x是输入提示。z是推理过程(隐变量)。y是最终答案。

我们关心的是 P(y | x),但模型生成的是 P(z, y | x)。通过对所有可能的推理路径 z 进行求和(边缘化),我们可以得到更好的 P(y | x) 估计:
P(y | x) ≈ Σ_z P(z, y | x)

这种方法催生了如“自洽性”等推断算法。






代码实践:Transformer模型与生成 🚀


上一节我们回顾了核心的概率概念。本节中,我们将把这些概念应用于一个真实的Transformer模型(GPT-2 small),并观察不同采样策略的效果。








模型初始化与不同温度下的生成



我们使用Hugging Face的 transformers 库加载一个小型GPT-2模型,并比较在温度 T=0(贪婪)、T=0.5、T=1.0 和 T=1.5 下生成的文本。

提示词为:“The future of artificial intelligence is”。

from transformers import GPT2LMHeadModel, GPT2Tokenizer
model = GPT2LMHeadModel.from_pretrained('gpt2')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')




prompt = "The future of artificial intelligence is"
input_ids = tokenizer.encode(prompt, return_tensors='pt')


# 贪婪解码 (T=0)
greedy_output = model.generate(input_ids, max_length=50, do_sample=False)
# 温度采样 (T=0.5)
temp_output = model.generate(input_ids, max_length=50, do_sample=True, temperature=0.5)




评估生成结果
定性评估生成文本后,我们需要更系统的方法。这里介绍两种:
-
确定性指标:例如计算生成文本的多样性。
- 内部多样性:单次生成中唯一词的比例。
- 交叉多样性:多次生成之间词或n-gram的重叠度。
-
语言模型即评委:使用一个更强大的语言模型(如Claude)来评估生成文本的质量,例如要求它对文本的“流畅性与连贯性”进行1-10分的打分。
以下是使用语言模型作为评委的示例提示:
“Rate the fluency and coherence of this text on a scale of 0 to 10. 10 equals perfect. Only respond with a number.
Text: {generated_text}”
注意:语言模型评委并非完美,它们可能不擅长评估自己不擅长的领域,并且可能对自己的生成结果存在偏好。


元生成算法:重排序
元生成算法是指利用生成过程本身或外部信息来改进最终输出的算法。最简单的一种是重排序。


其思想是:用一个较小的、高效的模型(如GPT-2 small)生成多个候选输出,然后用一个更大、更强的模型(如GPT-2 medium)计算每个候选输出的概率(或困惑度),最后选择大模型认为最好的那个输出。
以下是重排序的核心步骤:




# 1. 用小模型生成N个候选序列
candidates = []
for _ in range(num_candidates):
output_ids = small_model.generate(input_ids, max_length=50, do_sample=True, temperature=0.8)
candidates.append(output_ids)




# 2. 用大模型计算每个候选序列的(平均)对数概率
scores = []
for cand in candidates:
with torch.no_grad():
outputs = large_model(cand, labels=cand)
log_likelihood = outputs.loss * -1 * cand.size(1) # 转换为总对数似然
avg_log_prob = log_likelihood / cand.size(1) # 计算每个词元的平均对数概率,用于公平比较不同长度的序列
scores.append(avg_log_prob.item())
# 3. 选择分数最高的候选
best_idx = scores.index(max(scores))
best_candidate = candidates[best_idx]
best_text = tokenizer.decode(best_candidate[0], skip_special_tokens=True)




这种方法可以视为一种简单的“推测解码”或“蒸馏”形式,它允许我们在不直接使用大模型进行昂贵生成的情况下,利用其更强的判断力。




总结 📝



本节课中我们一起学习了语言建模推断算法的概率基础与初步实践。

我们首先回顾了条件概率,这是自回归语言模型的基石。接着,我们探讨了采样这一基本推断方法,并引入了温度参数来控制生成的多样性与质量。我们了解到,通过采样可以估计难以解析计算的量(如联合概率),但只有温度 T=1 能提供无偏估计。
然后,我们介绍了边缘化和隐变量的概念,并以思维链为例,说明了如何通过边缘化隐变量来提升最终输出的概率估计。

在实践部分,我们使用GPT-2模型演示了不同温度下的文本生成,并介绍了两种评估方法:基于统计的确定性指标和基于大模型的语言模型即评委。最后,我们探讨了最简单的元生成算法——重排序,即用小模型生成、大模型挑选,从而结合了效率与质量。

这些基础概念和简单算法为我们后续深入学习更复杂、更高效的推断算法(如集束搜索、Top-k/p采样、推测解码等)奠定了坚实的基础。
3:常见采样方法

在本节课中,我们将学习如何从语言模型的概率分布中进行采样。我们将从模型作为概率分布的基本概念开始,然后探讨几种常见的采样方法,包括简单的截断采样、基于温度的采样,以及一种更复杂的、利用概率分布度量进行采样的方法。
模型作为概率分布
正如我们在第一节课中提到的,我们可以从几个不同的框架来思考模型。至少对于今天的内容,我们采用的框架是:模型就是一个条件概率分布。我们暂时不关心这个分布是如何得到的,我们只知道,给定一些输入和先前的输出标记,在每个步骤中,我们都有一个覆盖词汇表空间的下一个标记的分布。
在这个定义下,我们可以对这个分布做一些说明。首先,我们今天讨论的模型是局部归一化的。这意味着在每个步骤中,我们都会得到该步骤标记的分布,而不是在每个步骤得到一个可能不是分布的东西,然后在解码结束时再进行归一化。
局部归一化的一个特点是,随着我们添加更多标记,序列的总体概率会单调非递增。这也意味着,如果你从一个看起来不太好的前缀开始,后面无法再“添加”回概率质量。例如,解码“2023年的美国总统是”时,续写“约瑟夫·拜登”可能看起来是一个合理的输出。但即使另一个续写“巴拉克·奥巴马的前副总统”在语义上更连贯,在局部归一化模型中,后者的总概率也可能不会超过前者,因为模型已经为“约瑟夫·拜登”分配了相对较高的初始概率。
与局部归一化相对的是全局归一化模型,它在每个步骤产生一个分数,这些分数可以大于1,从而允许序列后期“提升”某些候选序列的概率。我们使用局部归一化主要是因为它训练起来简单快速。然而,当我们想在推断时施加全局约束时,就需要更多思考。实际上,本学期后续讨论的许多方法,都是在尝试对仅经过局部归一化训练的模型施加全局约束。
概率分布的特性
除了归一化方式,我们还关心概率分布的其他特性。其中一个重要特性是校准。我们说一个模型是校准的,如果模型的置信度分数与正确答案的概率有良好的相关性。例如,对于一个多选题,如果模型将50%的概率质量分配给选项B,那么我们希望在50%的情况下B确实是正确答案。这样,我们就可以直接将模型的逻辑值作为置信度的度量。
预训练模型,如果训练数据质量好,其似然性与真实性(或该答案为真的可能性)的对应关系通常比较合理。然而,当我们进行像RLHF这样的后训练步骤时,虽然输出质量通常更好,但会破坏模型的校准特性。因此,对于经过后训练的模型,其校准性没有太多保证。
另一个特性是,模型通常会在各种各样的事物上分配非零的概率质量。即使在有半明显事实的情况下,模型也会至少分配一些概率质量给不正确的输出。例如,模型不会将100%的概率分配给“2+2=4”。从机器学习理论来看,如果我们还希望模型保持校准性,那么完全消除这种“在非事实上分配概率质量”的特性是不可能的。
分布的计算与度量
现在,我们想计算分布的一些统计量,这些统计量对后面讨论的方法很有用。
第一个是分布的熵。我们通常在解码的单个时间步上计算,公式如下:
H(p) = - Σ p(x) * log p(x)
其中求和是在该时间步的词汇表空间上进行的。
熵高意味着分布更平坦、更均匀,我们需要更多信息(比特)来指定分布中的任何一个特定结果。熵低意味着分布非常尖锐(峰值很高),例如,如果100%的概率质量都集中在一个标记上,那么指定该分布的输出就只需要很少的信息。


我们不仅关心抽象的熵,还关心相对于某个真实分布的熵。这引出了交叉熵的概念。在语言模型中,我们通常关注下一个标记预测任务。我们查看训练文档中实际的下一个标记,并将其表示为一个独热向量(该标记为1,其他为0)。交叉熵的计算公式为:
H(p, q) = - Σ p(x) * log q(x)
其中 p 是真实分布(独热向量),q 是模型预测的分布。这实际上就是正确下一个标记的负对数似然。


如果你读过模型训练的论文,一定见过困惑度。困惑度就是2的交叉熵次方:
Perplexity = 2^(H(p, q))
困惑度有一个直观的解释:它表示如果你有一个面数为困惑度数值的骰子或硬币,你正确预测下一个标记的几率。例如,困惑度为6意味着模型预测下一个标记时,就像掷一个六面骰子,只有掷出1时才能预测正确。
基础采样:祖先采样
有了这个分布,我们想得到一些输出。最简单的方法是直接从分布中采样,这被称为祖先采样。具体来说,我们根据模型参数化的分布进行采样。
这样做的好处是,我们精确地恢复了模型分布。既然我们花费了大量努力来训练得到这个分布,直接从中采样似乎是合理的。
然而,直接对模型分布进行采样存在一些缺陷。我们今天将花最多时间思考的问题是长尾问题。现代模型的词汇表非常大(例如,Llama 3有128k个标记)。每个不在最合理的几百个候选内的标记都只有微小的概率,但这些微小概率累加起来非常快。可视化显示,概率质量分布中后50%的部分(即长尾)占据了总概率质量的一半。这意味着,在采样生成一段文本(可能需要100或200步)时,从这条非常长的尾巴中抽到一些非常奇怪内容的几率是相当可观的。
应对长尾:温度采样
为了解决长尾问题,我们看到了第一种方法:使用温度来重塑分布。温度参数 T 用于调整逻辑值(logits)的尖锐程度:
logits_adjusted = logits / T
- 温度 T = 1:原始分布。
- 温度 T < 1:锐化分布。概率峰值更高,长尾更平坦(即长尾概率质量更少)。这有助于减少采样到奇怪内容的几率。
- 温度 T > 1:平坦化分布。增加了采样到原本概率稍低的标记的几率,从而可能产生更多样化的输出。
应对长尾:截断采样
另一大类方法是直接“切断”长尾。有几种常见的方式:
Top-K 采样:只从概率最高的K个标记中采样。根据概率分布的尖锐程度,这K个标记可能包含大部分概率质量,也可能只包含一小部分。例如,解码“the”之后,合理的后续标记很多,Top-6可能只包含68%的概率质量;而解码“the car”之后,合理后续较少,Top-6可能包含99%的概率质量。
Top-P 采样(核采样):定义一个希望覆盖的概率质量总量(例如,90%),然后从最可能的标记开始,依次添加标记,直到累积概率达到该阈值。然后,在这个子集内重新归一化并采样。这种方法能动态适应不同时间步概率分布的尖锐程度。
Epsilon 采样:只截断那些概率小于或等于某个极小值 ε 的标记。如果所有标记的概率都至少达到某个合理值,则从完整分布中采样;否则,剔除那些概率过低的标记。
这些参数在像 Hugging Face 这样的库中很容易设置。但需要注意的是,不同模型可能有其默认的生成配置(例如,温度、Top-P值),这些默认值通常是模型提供商通过超参数扫描确定的,可能在该模型上效果较好。因此,在指定生成参数时,最好检查并覆盖你关心的设置。
局部典型性采样
以上是几种经典的解码算法。接下来,我们看一个更复杂、可能不那么广泛使用的方法,因为它提供了一个关于采样的不同思考角度。
首先,我们提出一个问题:假设我有一个不均匀的硬币,正面朝上的概率是60%。我连续抛掷100次,记录结果序列。所有可能序列中,单个最可能的序列是什么?答案是100次全是正面。但这会是抛掷这个硬币时令人惊讶的输出吗?是的。那么,典型的输出应该是什么样的?我们期望得到大约60%正面、40%反面的某种组合。
这就引出了典型性的概念:概率分布中最可能的事件,并不一定是该分布的典型代表。为了定义典型性,我们借鉴随机过程文献中的概念。我们将语言模型视为一个随机过程:在每个步骤,根据当前概率分布发射一个标记,然后以该标记为条件继续下一步。
要定义典型集,我们需要这个过程满足三个性质:离散性、平稳性和遍历性。如果满足这些性质,我们可以定义过程的熵率(平均每符号熵),然后定义长度为N、容差为ε的典型集:所有那些平均每符号负对数概率接近熵率的序列。
然而,语言模型满足这些性质吗?离散性(词汇表有限)是满足的。平稳性(概率不依赖于绝对位置,只依赖于条件)在有限上下文窗口的假设下可能成立。但遍历性要求可以从任何状态到达任何发射状态,而语言模型在生成序列结束标记(EOS)时会停止,EOS是一个“吸收态”,破坏了遍历性。
既然无法严格定义全局典型性,论文《Locally Typical Sampling》提出了一种局部近似的方法。其思想是:我们不要求整个序列平均接近熵率,而是要求每个新标记的负对数概率接近当前时间步分布的熵。
局部典型性采样算法步骤如下:
- 计算当前时间步条件分布的熵
H。 - 将逻辑值(或对数概率)按照其与
H的接近程度重新排序(而不是按概率大小)。 - 使用 Top-P 方法对这个新排序的分布进行截断(从最接近
H的标记开始累积概率)。 - 从截断并重新归一化的分布中采样。
这种方法可能不仅会截断长尾,甚至可能截断一些概率较高但远离熵值的标记。其动机来源于认知科学和信息论:人类语言的下一个标记,其信息量(负对数概率)往往接近当前语境下分布的熵。这样既能有效传递信息,又不会总是说最显而易见或最离奇的话。
与局部典型性采样追求特定信息量相对,还有一种称为 Mirostat 解码 的方法,它试图通过持续更新的方式,控制生成文本的最终困惑度接近一个预设值。
另一种结合了截断和典型性思想的方法是 Eta 采样。它同时设置基于 Epsilon 的阈值和另一个随分布熵移动的阈值(Eta),只保留概率大于 Epsilon 且对数概率在熵的 Eta 范围内的标记。
如何选择采样方法?
面对这么多采样方法,如何选择?遗憾的是,没有放之四海而皆准的答案。
- 考虑任务的开放性:对于“写一个关于青蛙的创意故事”这类开放性问题,你可能更关心输出的多样性;对于“马萨诸塞州的首府是哪里”这类封闭性问题,你可能更关心准确性。
- 动手尝试:这些方法在代码上改动很小,非常容易实验。可以在你关心的模型上生成一些样本进行观察。
- 如果输出看起来重复或退化,可能是采样过于集中在头部。
- 如果输出开始走向奇怪的方向,可能是采样到了长尾。
- 如果采样100次得到的结果几乎相同,你可能需要增加多样性(例如,提高温度或增大Top-P)。
- 注意模型规模:小模型和大模型的行为差异很大。小模型可能更易出现采样的病理现象,因此调参更重要。大模型通常对默认参数更鲁棒。
- 谨慎评估:模型的性能可能随解码参数变化很大。如果你在评估模型(尤其是自己的模型),建议尝试一组参数,找到一个性能相对稳定的区域,然后在该区域内选定一套策略并标准化用于所有评估。过度调优每个评估任务的最佳解码策略,对于最终用户来说并不现实。

最后需要提醒的是,语言模型本身已经隐含地根据问题类型调整了其概率分布的形状(例如,数学问题分布更尖锐,创意写作分布更平坦)。在系统层面,根据用户提示类型动态调整生成设置也是一个合理的思路。
总结
本节课中,我们一起学习了从语言模型概率分布中采样的核心方法。我们首先将模型视为条件概率分布,并讨论了其局部归一化、校准等特性。然后,我们介绍了直接祖先采样及其长尾问题。为了解决这个问题,我们探讨了:
- 温度采样:通过缩放逻辑值来锐化或平坦化分布。
- 截断采样:包括 Top-K、Top-P (核采样) 和 Epsilon 采样,通过限制采样范围来避免长尾。
- 局部典型性采样:一种更复杂的方法,旨在使每个采样标记的信息量接近当前分布的熵,以生成更“典型”的文本。

选择哪种方法取决于具体任务、模型规模和对输出多样性/准确性的需求。最好的方式是通过实验来找到适合你用例的策略。
4:Beam Search及其变体 🧠


在本节课中,我们将学习一种称为“模式搜索”的解码方法,其目标是找到模型输出分布中概率最高的单个序列。我们将重点介绍Beam Search算法,探讨其工作原理、存在的问题以及几种旨在提升输出多样性的变体。
模式搜索与贪婪解码的局限
上一节我们介绍了基于采样的解码方法。本节我们将从更偏向优化的视角来看待解码问题:我们不仅希望获得模型的一个好输出,更希望找到给定模型参数下最可能的那个单一序列。这被称为模式搜索或最大后验(MAP)解码。
对于单步输出,找到概率最高的词元很简单,只需对逻辑值进行 argmax 操作即可。然而,对于多词元序列,情况就复杂了。
一种简单的基线方法是贪婪解码:在每一步都选择当前概率最高的词元。这种方法计算简单,但存在几个问题:
- 可能无法得到全局最高概率序列:局部最优选择可能导致后续步的概率骤降,从而错过整体概率更高的路径。
- 容易陷入重复陷阱:一旦模型开始重复某些词元,贪婪解码会不断强化这种重复,导致输出陷入循环。
- 可能产生不自然的句子:为了优先选择高频词(如“the”),模型可能生成语序别扭的句子(如“The dog was seen by Jane”而非更自然的“Jane saw the dog”)。
因此,我们需要更智能的方法来近似找到序列的“模式”。
Beam Search算法详解
Beam Search的核心思想是:为了避免因过早做出硬性选择而错过潜在的高概率路径,我们在每一步保留多个(K个)候选序列。这是一种宽度优先搜索的近似方法,通过扩展和剪枝来管理计算复杂度。
以下是Beam Search的步骤:
- 初始化:设定束宽(Beam Width) K。根据输入前缀,选择概率最高的K个词元作为初始候选束。
- 迭代扩展与剪枝:
- 扩展:对于当前束中的每个候选序列,考虑其所有可能的下一个词元,生成K * V个新候选(V是词表大小)。
- 评分:计算每个新候选序列的得分。通常使用序列的对数概率之和。
- 长度归一化:由于序列概率随长度增加而单调递减,长序列在评分中处于劣势。因此,通常会对得分进行归一化,常见方法是除以序列长度(或长度的α次幂,α是一个超参数,称为长度惩罚)。
- 剪枝:从所有扩展后的候选序列中,选出得分最高的K个,作为下一轮的候选束。
- 终止与输出:当所有候选序列都生成结束符(EOS),或达到最大解码长度时,停止迭代。最后,从最终的K个候选序列中,选择(归一化后)得分最高的一个作为输出。
使用对数概率而非原始概率主要是出于数值稳定性的考虑,因为长序列的原始概率会变得非常小,接近零。
提升多样性:Diverse Beam Search
标准的Beam Search有一个缺点:其最终保留的多个候选序列往往非常相似,缺乏多样性。这在需要多种合理输出的场景(如图像描述)中是个问题。
Diverse Beam Search 旨在生成一组既高质量又多样化的输出。其核心思想是:将束分成G个组,依次解码每个组,并在解码后续组时,对与前面组相似的词元施加惩罚,从而鼓励多样性。
具体流程如下:
- 第一组使用标准Beam Search解码。
- 解码第二组时,在计算得分时加入一个多样性惩罚项,该惩罚基于第二组候选与第一组已解码内容的相似度。
- 解码后续组时,惩罚基于与所有前面已解码组的相似度。
- 为了高效计算,解码过程在时间上错开进行,总时间步数约为 T + G - 1。
多样性可以通过多种方式度量,例如:
- 汉明多样性:惩罚那些在前面组的活跃序列中出现过的词元。
- 累积多样性:仅当在同一时间步使用相同词元时才施加惩罚。
- N元语法惩罚:惩罚匹配前面组中出现的完整N元语法片段。
在实践中,通常将组数G设置为束宽K,并在每组内进行贪婪解码(即每组束宽为1),这样能在多样性和质量间取得良好平衡。
另一种思路:Stochastic Beam Search
如果我们想要多样性,一个直观的想法是采样。Stochastic Beam Search 巧妙地将采样融入了Beam Search框架。它不是在扩展时选择Top-K,而是采样K个独特的词元,同时利用Beam Search的剪枝机制来保证输出质量。
实现高效无放回采样的关键在于 Gumbel-Max Trick。该技巧表明:若有一组随机变量 X_i = Gumbel(logit_i, 1),即每个变量服从位置参数为logit_i、尺度参数为1的Gumbel分布,那么 argmax(X_i) 的分布恰好等于对 logit_i 做Softmax后的分类分布。这意味着,我们可以通过向逻辑值添加Gumbel噪声并取argmax来采样一个词元。
要无放回地采样K个词元,只需取添加Gumbel噪声后值最大的K个即可。然而,在Beam Search中,我们不能直接使用带噪声的得分进行剪枝,因为这可能导致序列得分随时间步增加而上升,违反概率单调性且影响长度归一化。
因此,Stochastic Beam Search在扩展时使用Gumbel-Max Trick进行采样,但在剪枝评分时,会对带噪声的得分进行修正,确保其不超过原始逻辑值的上限,从而在保持采样多样性的同时,维护了搜索的合理性。
Beam Search的特性与“诅咒”
选择束宽K时面临权衡:K越小,解码越快、内存占用越少,但搜索误差越大,可能错过真正的高概率序列;K越大,越接近精确搜索,但计算成本更高。
然而,一个反直觉的现象是:在许多任务中,增大束宽有时会导致下游任务性能下降,这被称为 “Beam Search的诅咒”。对此有两种解释:
- 长度归一化问题:更大的束宽可能平均倾向于生成更短的序列,而某些任务中短序列性能较差。调整长度惩罚参数可能缓解此问题。
- 模型分布的模式并非最优:模型概率最高的序列(模式)可能并非人类最偏好的输出。存在一种 “似然陷阱”:人类更喜欢的输出可能接近但并非 exactly 是概率最高的那个。因此,Beam Search引入的搜索误差可能意外地成为一种有益的归纳偏置,将我们引向更受人类青睐的区域。
有研究指出,较小的束宽倾向于产生局部信息密度更均匀的序列(即每个词元的负对数似然标准差更小),这可能是一个理想属性。
总结与现状
本节课我们一起学习了Beam Search及其变体:
- Beam Search 是模式搜索的近似算法,通过维护一个候选束来平衡搜索质量和计算开销。
- Diverse Beam Search 通过分组解码和多样性惩罚,旨在生成一组多样化的高质量输出。
- Stochastic Beam Search 利用Gumbel-Max Trick将采样融入Beam Search框架,以获得多样性。
- 我们探讨了 “Beam Search的诅咒”,即增大束宽可能降低性能,这源于长度归一化的挑战或模型分布模式本身并非最优的事实。







值得注意的是,在当前最前沿的大模型API使用中,Beam Search已不常见,人们更多使用更简单的采样方法(如Top-p)。这可能是因为模型质量提升降低了精细解码策略的必要性,同时维护多束也带来了额外的计算和内存成本。然而,在许多特定的工业应用场景中,Beam Search及其变体因其可控性和稳定性,仍然扮演着重要角色。
5:A*搜索与最佳优先搜索 🧭
在本节课中,我们将学习两种更高级的搜索算法:A*搜索和最佳优先搜索。我们将探讨它们如何比贪婪搜索和束搜索更高效地寻找最优输出,并理解其背后的原理与适用场景。

搜索算法回顾

上一节我们介绍了贪婪搜索和束搜索。本节中,我们来看看两种旨在更高效地找到最优解的搜索算法。

贪婪搜索在每一步选择概率最高的词元。它速度很快,但目光短浅,可能做出糟糕的局部决策,而无法回溯或考虑多个假设。

相比之下,束搜索维护前K个最优假设,同时探索多个可能性。如果模型质量好,束搜索通常能提供比贪婪搜索更好的输出质量。需要记住的是,束宽为1的束搜索等价于贪婪搜索。
将搜索空间表示为图 📊
许多搜索算法最初是为图搜索设计的。我们可以将语言模型的解码过程表示为一个加权有限状态自动机。


一个加权有限状态自动机可以定义为:
- S: 状态的有限集合。
- Σ: 有限字母表(即语言模型的词表)。
- δ: 状态转移函数,形式为
δ(s, σ) -> s',表示在状态s输入词元σ后转移到状态s'。 - s₀: 初始状态。
- F: 终止状态的集合。
- W: 为每条转移边赋予一个实数值权重。

为了简化,我们通常使用正权重或负权重。在语言模型中,常使用负对数概率作为权重,因为许多图搜索算法被设计为寻找最短路径(最小成本)。
以下是一个简单的图示例,边上的权重代表概率:
初始状态(s0) --A(0.5)--> s1
--B(0.3)--> s2
--C(0.2)--> s3
...
对应的负对数概率权重为:
初始状态(s0) --A(0.69)--> s1
--B(1.20)--> s2
--C(1.61)--> s3
...


最优搜索算法:最佳优先搜索

最佳优先搜索是一种能保证找到图中最优(成本最低)路径的算法。它类似于Dijkstra算法,但通常不需要额外的回溯步骤。

其核心思想是系统性地探索所有路径,始终扩展当前总估计成本最低的假设。因为它从不剪枝可能最优的路径,所以最终能找到终止于目标节点的最佳路径。

以下是使用负对数概率在图上的搜索步骤示例(假设启发函数H=0):
- 优先级队列初始包含
(s0, score=0)。 - 扩展
s0,将其后继状态s1(0.69),s2(1.20),s3(1.61)加入队列。 - 选择队列中分数最低的
s1(0.69)进行扩展。 - 重复此过程,直到到达终止状态。
在这个小例子中,最佳优先搜索用了9步找到最优路径。然而,对于词表很大或序列很长的语言模型,这种穷举式搜索在起始阶段就会因组合爆炸而变得极其低效。

A*搜索算法 ⭐
A*搜索在最佳优先搜索的基础上进行了改进,通过引入启发函数来提高搜索效率,同时仍能保证找到最优解。
A*搜索用于评估假设优先级的分数 F 计算公式为:
F(n) = G(n) + H(n)
其中:
- G(n) 是从起点到当前节点
n的实际累积成本(例如,已生成序列的负对数概率之和)。 - H(n) 是从当前节点
n到目标终点的启发式估计成本。

启发函数 H(n) 的关键特性是“可采纳性”:它必须永不过高估计到达目标的真实成本。它可以低估,但不能高估。这为剩余成本提供了一个可靠的下界。


例如,对于前缀“The solution to 11-664 homework is”,一个良好的启发函数应能估计完成这个句子还需要多少“概率成本”。如果 H(n) 总是可采纳的,那么A*搜索就能保证最优性。

让我们在之前的图例中使用一个简单的可采纳启发函数:
- 为状态
s1, s2, s3设置H=1.0 - 为状态
s4, s5, s6设置H=0.5 - 终止状态
H=0
搜索过程如下:
- 计算
s0->s2的F = 0.69 + 1.0 = 1.69 - 计算
s0->s3的F = 1.20 + 1.0 = 2.20 - 计算
s0->s1的F = 1.61 + 1.0 = 2.61 - 优先扩展
F值最低的假设(s0->s2, F=1.69)。 - 后续步骤中,由于
s2的后继状态启发值降低,搜索会更倾向于向已取得进展的路径深入。



在这个例子中,A*搜索用了8步找到最优解,比普通最佳优先搜索(9步)更高效,因为它利用启发函数避免过早探索潜力较低的分支。

在大型语言模型中应用A*搜索的挑战

尽管A*搜索很强大,但为何不常用于现代大型语言模型呢?主要面临两大挑战:

- 组合爆炸:语言模型的解码图随着每个生成的词元呈指数级增长。即使使用启发函数,在搜索初期也可能因状态空间过大而陷入困境。
- 难以设计可采纳启发函数:Transformer等大型语言模型行为复杂,很难设计出一个能保证永不超估未来成本、且计算高效的启发函数。例如,模型可能突然进入一个“高确定性”区域(如开始逐字输出记忆中的文本),使得未来成本极低,难以预先准确估计。

为了应对这些挑战,研究者提出了两种主要技术。


技术一:假设重组
此技术旨在通过合并相似的假设来减少搜索空间,控制指数增长。

基本思想是将具有相似状态的假设聚类,并在每个聚类中保留最优的假设进行后续扩展。这与我们在多样束搜索中提到的概念有相似之处。
以下是两种常见的聚类标准:
- 基于N元文法的聚类:如果假设共享最近的N个词元上下文,则将其聚类。这种方法实现简单,易于缓存,并能与束搜索结合。通过控制N的长度可以平衡精度与效率。
- 基于隐藏状态距离的聚类:计算假设当前隐藏状态表示之间的相似度(如欧氏距离、余弦相似度),或计算它们诱导出的下一个词元概率分布之间的KL散度,将相似的假设聚类。

技术二:未来成本预测(非可采纳启发函数)
当无法获得严格可采纳的启发函数时,我们可以学习一个未来成本预测模型。它可能不是可采纳的,但通常比没有启发函数(H=0)更有效。



其核心思想是:训练一个模型,给定当前部分序列,预测完成整个序列所需的总成本(如负对数概率之和)。


有两种主要训练方式:
- 联合训练:在训练主语言模型的同时,添加一个辅助预测头,用于回归整个序列的最终分数。这类似于强化学习中的价值函数。
- 事后训练:在主模型训练完成后,使用其生成的完整序列数据,单独训练一个未来成本预测模型。
将预测的未来成本 H_pred(n) 乘以一个折扣因子 γ,然后加入评分函数:F(n) = G(n) + γ * H_pred(n)。通过调整 γ,可以在搜索效率和解码质量之间取得平衡。
实验表明,即使是非可采纳的启发函数,也能显著提升机器翻译等任务的搜索效率和解码质量。


最佳优先束搜索
最佳优先束搜索是一种结合了束搜索和最佳优先搜索思想的混合方法。它旨在解决标准束搜索的一个问题:束搜索通常会扩展束内的所有假设,即使其中一些假设的得分明显很低,潜力不大。


最佳优先束搜索的改进在于:
- 基于分数的优先级:像最佳优先搜索一样,主要根据假设的当前总分数
F来决定扩展顺序,而不是像束搜索那样优先扩展长度短的假设。 - 维持束约束:同时,它像束搜索一样,维护一个固定的束宽
K,当同一长度下的假设数量超过K时,剪枝掉分数最差的。这防止了搜索空间无限膨胀。 - 可融入启发函数:可以方便地加入未来成本预测等启发函数。



这种方法可以被形式化地描述,并通过调整几个关键参数(比较器、终止条件、束宽、启发函数)来统一表示贪婪搜索、束搜索、最佳优先搜索和A*搜索。例如:
- 束搜索:先比较长度,长度相同再比较分数;束宽为K;无启发函数。
- 最佳优先搜索:先比较分数,分数相同再比较长度;束宽为无限(不剪枝);无启发函数。
- A*搜索:与最佳优先搜索类似,但使用可采纳启发函数
H(n)。
研究表明,最佳优先束搜索能在保持与标准束搜索相同结果质量的同时,获得显著的加速(例如10倍),或者允许使用更大的束宽以进一步提升质量,其代价主要是内存开销和实现复杂度的略微增加。



本节课中我们一起学习了A搜索和最佳优先搜索的原理。我们了解到,通过引入启发函数来估计未来成本,可以更智能地指导搜索方向,提升效率。虽然在大规模语言模型中直接应用严格的A搜索面临挑战,但其思想催生了如未来成本预测和最佳优先束搜索等实用技术。这些方法在当前模型质量越来越高、重新审视精确搜索价值的背景下,可能具有重要的应用潜力。
6:其他受控生成方法


在本节课中,我们将继续探讨如何控制和约束语言模型的生成过程。我们将介绍两种主要类型的约束:易于用规则列表描述的句法约束,以及更难以明确定义的语义约束。通过学习这些方法,你将了解如何在推理阶段灵活地引导模型输出,以满足特定格式或内容要求。
上一节我们讨论了束搜索和A*搜索等通过调整搜索分数来约束输出的方法。本节中,我们将看看另一种思路:直接修改模型的概率分布,然后从中重新采样。
可验证的约束与状态机
首先,我们来看一类相对简单的约束:可验证约束。这类约束的特点是,在生成结束时,我们可以用一个确定的函数来检查约束是否被满足。更进一步,如果我们在生成每个单独的标记时都能验证当前路径是否可能满足最终约束,那么这类约束就更容易处理。
例如,“输出必须是10个标记长”这个约束在生成结束时很容易验证,但在生成过程中,我们无法确定当前的选择是否能最终满足这个条件。相比之下,“每个标记都必须以空格开头”或“输出必须是有效的JSON”这类约束,在每一步生成时我们都能判断是否偏离了目标。
模板化生成:以JSON为例
一个常见的需求是让模型始终输出特定格式,比如有效的JSON。虽然可以通过微调模型来实现,但如果约束很简单或者需要频繁更改,在推理时进行控制是更灵活的选择。
如何强制模型输出有效的JSON?关键在于,在每一步解码时,虽然模型有超过10万个可能的标记选择,但只有少数几个能导向最终的有效JSON。我们可以通过一个状态机来跟踪生成进度,并屏蔽掉无效的路径。
以下是一个生成包含name和birth_year键的JSON对象的状态机示例:
- 状态 0 (起始): 唯一有效的起始标记是左花括号
{。 - 状态 1: 接下来可以生成
"name": "或"birth_year": "。 - 状态 2 (生成姓名): 进入此状态后,只能输出构成姓名字符串的字母标记。
- 状态 4 (生成出生年份): 进入此状态后,只能输出数字标记。
- 状态 6/7 (结束值): 完成一个值后,需要输出闭合引号,然后可以选择输出逗号并生成另一个键值对,或者直接以右花括号
}结束。
这个状态机确保了生成的结构是 {"name": "某姓名", "birth_year": 某年份} 的形式。然而,它也存在一些问题:
- 没有限制姓名或年份的长度。
- 可能重复生成同一个键(如多个
birth_year)。 - 可能漏掉某个必需的键。
- 无法处理嵌套的JSON结构。
在实践中,我们通常不会手动绘制这些状态机。对于JSON这类常见格式,许多库(如Llama.cpp、LangChain、OpenAI的Structured Outputs)允许你直接提供一个JSON模式(Schema),库会自动将其转换为约束并在生成时强制执行。
标记修复

使用状态机进行模板化生成时,可能会遇到“不自然”的标记边界。例如,模型在无约束时可能将 http:// 作为一个标记生成,但在状态机中,你可能先被迫生成冒号 :,然后需要生成 //。// 作为一个独立标记的概率可能很低。









标记修复 技术可以解决这个问题。其思想是:当我们知道下一个标记必须以某个特定字符(如 :)开头时,我们并不强制该字符必须是一个完整的标记。相反,我们回退一步,只要求下一个标记以该字符为前缀。这样,像 :// 这样更自然、概率更高的标记就会被纳入考虑范围。


触发标记修复的启发式规则包括:
- 维护一个常见“问题字符”列表(如孤立的冒号、斜杠)。
- 当上一个生成的标记非常短时(例如单个字符)。
- 当上一个标记不以空格或标点符号开头或结尾时(在代码生成中常用)。
- 当上一个标记是另一个高概率标记的精确前缀时。
从正则语言到上下文无关语言
我们之前绘制的状态机在形式语言理论中称为有限状态自动机,它所能表达的语言类别称为正则语言。正则语言易于指定,但表达能力有限。
JSON要求括号正确嵌套,这需要跟踪已打开括号的数量,这超出了正则语言的能力范围。要处理嵌套结构,我们需要更强大的 上下文无关文法,其对应的自动机称为下推自动机,它带有一个栈来记录额外信息。
几乎所有支持JSON模式验证的库在底层都使用了下推自动机。然而,如果你需要更复杂的约束(例如“所有键必须唯一”或像验证C代码那样需要检查变量是否已声明),这属于上下文有关语言的范畴,无法用这种高效的逐标记检查方式来实现。这时就需要其他方法。
处理语义约束:FUDGE方法
对于无法用精确规则描述的语义约束(例如“生成一段正式文本”或“避免提及爬山”),我们需要不同的策略。简单地屏蔽“爬山”这个标记是行不通的,因为它无法处理同义词、词语的其他用法,并可能导致不流畅的生成。
一种方法是使用 FUDGE 方法。其核心思想是通过一个辅助的未来判别器来估计在当前生成路径下,最终输出满足约束的可能性。
具体步骤如下:
- 训练判别器:收集带有约束标签的数据(如正式/非正式文本)。将每个完整文本的所有前缀(从开头到各个位置)作为输入,其完整文本的标签作为输出标签,来训练一个分类器。这个分类器学会根据当前已生成的前缀,预测最终结果满足约束的概率。
- 在生成时应用:在每一步生成时,对于模型预测出的Top-k个候选下一个标记,分别将它们拼接到当前前缀后,送入未来判别器,得到
P(约束|前缀+候选标记)。 - 调整概率:将模型原始的标记预测概率与判别器给出的概率相乘(或在对数空间相加),然后重新进行Softmax。这样,既符合语言模型习惯,又更可能满足约束的标记就会被提升。
公式表示:
我们希望得到的分布是 P(下一个标记 | 输入, 已生成前缀, 约束)。
根据贝叶斯规则,这可以近似正比于:
P(约束 | 前缀+下一个标记) * P(下一个标记 | 输入, 前缀)
FUDGE的优点是判别器任务相对简单,数据容易获取。但它不能保证约束一定被满足,通常需要与拒绝采样结合使用。此外,它需要访问模型的逻辑值。
对比解码与约束生成
对比解码 本身是一种提高生成质量的方法,其核心思想是:从“专家”模型(强模型)的预测概率中减去“业余”模型(弱模型)的预测概率,从而放大强模型更擅长而弱模型容易出错的部分。
我们可以利用这个思想进行约束生成,特别是针对安全约束。与其定义所有“安全”的内容,不如定义什么是“不安全”的。具体做法是:
- 用正常的提示词(如“你是一个有帮助的助手”)让模型生成,得到一组逻辑值。
- 用诱导生成有害内容的提示词(如“你是一个邪恶的助手”)让同一个模型生成,得到另一组逻辑值。
- 将两组逻辑值相减,从而抑制那些在“有害”模式下概率较高的输出。
这种方法被称为对抗性解码。它的优点是不需要训练额外的模型,让模型自己判断内容的危险性。缺点是需要两倍的计算量,因为每次生成都需要运行两次前向传播。
总结
本节课我们一起学习了多种控制和约束语言模型生成的方法:
- 对于句法/模板化约束(如JSON格式),使用基于状态机或文法的约束解码是最直接、最可靠的方法,它能提供硬性保证。
- 对于逐标记可验证的约束,可以通过修改逻辑值并屏蔽无效选项来实现。
- 对于更复杂的语义约束(如文本风格、避免特定主题),可以使用像 FUDGE 这样的方法,通过训练一个未来判别器来调整生成概率。
- 对于安全约束等场景,对比解码/对抗性解码提供了一种通过比较不同提示下的输出来抑制不良内容的方法。

这些方法为我们提供了在推理阶段灵活引导模型的工具箱。将约束内置到模型训练中(如RLHF)适用于广泛、持久的约束,而对于临时性、特定或格式化的约束,在推理时进行处理则更加灵活高效。理解约束的类型(是否可逐标记验证)是选择合适工具的关键。
7:思维链与中间步骤 🧠


在本节课中,我们将要学习思维链推理。这是一种在现代模型中广泛使用的基础技术,对于构建执行推断的模型至关重要。思维链允许模型在生成最终答案前,先生成中间推理步骤,从而更好地处理复杂问题。
动机:处理复杂推理任务
我们希望通过语言模型执行复杂的推理任务。例如,思考这样一个问题:“理论上,一块H100 GPU生成100个Llama 3 8B模型的token最快需要多长时间?”
要解决这个问题,模型需要执行多个步骤:回忆H100的FLOPS性能、了解Llama 3 8B的模型结构、设置计算过程,最后执行数学运算。这凸显了一个核心挑战:并非所有问题的难度都相同。有些问题(如简单算术)可以一步解决,而复杂问题则需要多步、细致的思考。语言模型需要根据问题难度动态分配计算资源。
上一节我们介绍了处理复杂任务的动机,本节中我们来看看思维链的具体定义和优势。
思维链:定义与优势
思维链的基本思想是:在生成最终答案 Y 之前,先生成中间推理步骤 Z。我们可以将其形式化地定义为,在给定输入 X 的情况下,我们希望通过边缘化所有可能的思维链 Z 来得到最可能的输出 Y:

P(Y|X) = Σ_Z P(Y, Z|X)

这里,Z 是一个潜在变量,用于提高我们预测 Y 的准确性。
使用思维链主要有两大优势:
- 自适应计算时间:额外的token允许模型为更困难的问题投入更多计算(FLOPS)。
- 可解释性与可验证性:如果思维链忠实于真实的推理过程,人类可以逐步检查,从而更容易理解和验证模型的输出。
思维链的学习方式
模型可以通过以下几种方式学会使用思维链:
- 涌现能力:在足够大/强的模型(如2022年的GPT-3 175B)中,思维链能力会自然涌现。这是因为训练数据(如数学教科书、代码、证明过程)中已经包含了大量的分步推理示例。
- 监督微调:在明确包含推理步骤的数据集上对模型进行微调,可以教会模型使用思维链。
- 强化学习:训练模型进行推理,并在其成功回答问题后给予奖励。这是当前推理模型研究的重要方向。
一个重要的说明是,如今许多“基础模型”在预训练末期会混入高质量、类似教学的数据,因此其涌现能力可能部分源于这种精心设计的数据选择。
思维链提示方法
在实践中,我们主要通过提示来激发模型的思维链能力。
以下是两种主要的提示方法:
- 少量示例提示:在输入中提供几个包含完整推理步骤的示例(问题 -> 推理 -> 答案),模型会模仿这种格式进行输出。
- 零样本提示:令人惊讶的是,即使不提供示例,仅使用“让我们一步步地思考”这样的前缀,也足以鼓励模型进行思维链推理。这个特定的提示词被证明效果显著。




思维链的推断算法



现在,我们回到本课程的核心——推断算法。我们的目标通常有两种:从模型中采样,或进行寻模搜索以找到最高分的输出。
对于思维链,其推断有其特殊性:
- 采样:要精确地从模型分布中采样,使用温度=1的祖先采样即可。即先采样思维链
Z,然后基于Z采样最终答案Y。 - 寻模:我们的目标是找到最大化
P(Y|X)的Y。这需要边缘化所有Z,而Z的可能性空间极其巨大(词汇表大小^链长度),无法直接枚举。

一种简单的寻模方法是联合寻模,即直接寻找概率最高的 (Z, Y) 对。但这种方法可能无法得到概率最高的 Y,因为可能存在多个不同的 Z 指向同一个正确的 Y,其总概率可能高于那个概率最高的 (Z, Y) 对。

为了解决这个问题,对于答案明确(如数学题答案)的任务,可以使用自洽性方法:
- 从模型中采样大量不同的推理路径。
- 统计所有路径最终得出的答案。
- 选择出现频率最高的答案作为最终输出。
自洽性通常效果更好,但计算成本也更高(需要多次采样)。
自适应计算与自洽性优化
我们可以将自适应计算的思想也应用到假设生成上。自适应自洽性方法旨在动态决定需要采样多少个假设才能足够确信。
其核心思想是:逐步生成样本,并利用贝叶斯统计(如狄利克雷分布/贝塔分布)来估计在继续采样后,当前最高频答案发生变化的概率。当这个概率低于某个阈值(例如5%)时,就停止采样。这种方法可以在保证结果可靠性的同时,节省不必要的计算开销。
思维链的特性与局限
了解思维链的以下特性与局限对于有效使用它至关重要:
-
并非万能:思维链带来的性能提升并非在所有任务上均等。大量分析表明,它在数学、逻辑和演绎推理任务上提升巨大,但在许多常识推理、知识问答任务上提升有限甚至没有。这意味着评估或应用推理模型时,应首先关注其数学能力。
-
解释的忠实性问题:思维链的解释可能并不忠实于模型实际的推理过程。研究表明,模型可能生成看似合理但实则为错误答案辩护的解释(例如,当受到提示示例中的偏见影响时)。这意味着不能完全依赖思维链进行可信性验证。
-
长度与质量的相关性:对于早期的思维链模型,更长的推理链往往与更高的答案准确性相关。基于此,有人提出了基于复杂度的提示方法:生成多个思维链,过滤掉较短的,然后对剩余的较长链进行自洽性投票,这能带来额外的性能提升。
扩展与总结
思维链的概念可以扩展到多模态领域,例如让模型根据图像和文本输入生成推理步骤,再给出答案,这在视觉问答等任务上取得了更好效果。
本节课中我们一起学习了思维链推理的核心概念。我们了解了它的定义、优势、学习方式以及如何通过提示来激发它。我们重点讨论了与之相关的推断算法挑战,如寻模的困难和自洽性解决方案,并介绍了自适应计算在其中的应用。最后,我们探讨了思维链的局限性,包括其任务特异性、解释的忠实性问题以及长度与性能的关系。这些知识为我们后续学习自我修正模型和更强大的推理模型奠定了重要基础。

课程内容来源:CMU《语言建模的推断算法》讲座 P7 - 思维链与中间步骤
8:自优化与自校正方法 🛠️
在本节课中,我们将要学习一系列自优化与自校正方法。这些方法旨在让语言模型能够像人类一样,通过迭代地审视和修改自己的输出来提升生成质量,而不是仅依赖单次从左到右的生成。

概述

语言模型的生成通常是单次从左到右完成的,这使其在生成长或复杂输出时容易出错。相比之下,人类的写作过程往往是迭代式的:先完成初稿,然后反复修改和润色细节。本节课介绍的方法正是为了帮助语言模型实现类似的迭代优化过程。
自校正循环的基本框架
自校正过程通常遵循一个通用框架,其核心是一个循环结构。以下是该过程的基本步骤:
- 初始生成:模型根据输入生成初始输出。
- 评估与评判:对当前输出进行评估,判断其是否令人满意。这一步可以是显式的(明确提示模型进行评判)或隐式的(例如,当模型不再生成修改时即视为满意)。
- 判断与循环:如果输出不满足停止标准,则基于评估结果(评判)对输出进行精炼修改。这个过程会重复进行,直到输出满足标准或达到预设的最大迭代次数。


该框架可以用以下伪代码表示:
output = generate(input)
for i in range(max_iterations):
critique = evaluate(input, output)
if is_satisfactory(critique):
break
output = refine(input, output, critique)
关键设计维度
实现自校正方法时,需要考虑几个关键的设计决策,不同的论文在这些维度上做出了不同的选择。
以下是几个核心的设计维度:
- 评判方式:分为显式评判(明确要求模型评估输出质量)和隐式评判(通过模型是否继续生成修改来间接判断)。
- 训练需求:是否需要显式训练模型使其擅长自我优化,还是直接使用预训练模型进行零样本/少样本提示。
- 精炼条件:在精炼步骤中,模型可以基于原始输入、当前输出和/或评判反馈来生成修改。
- 工具使用:评判或精炼过程是否依赖外部工具(如代码执行器、知识库、搜索引擎)来提供更可靠的反馈。
接下来,我们将通过介绍几篇代表性论文,具体看看这些设计决策是如何被应用的。
早期探索:两阶段解码与训练
上一节我们介绍了自校正的基本概念,本节中我们来看看一个早期的具体实现。2017年的一篇论文提出了一个简单但有效的思想:不满足于单次生成,而是通过“深思熟虑”进行两阶段解码来优化输出。

该方法的算法非常简单:
- 第一遍生成初始草稿序列。
- 第二遍基于对整体输出的全局上下文感知,对草稿进行精炼。


其训练目标旨在最大化最终输出 y 在给定初始输入 x 和中间输出 y' 下的对数概率,公式表示为:
max_θ Σ log P_θ(y | x, y')
其中 θ 代表模型参数。



然而,直接优化这个目标不可行,因为中间输出 y' 的搜索空间是离散且巨大的(所有可能的序列)。解决方案是采用采样来近似梯度,即采样多个中间输出序列,然后计算它们各自产生最终目标输出的概率,并以此更新模型参数。这项工作首次证明了可以训练模型进行有效的自我优化。
学习编辑过程:结构化编辑操作
除了简单的两阶段模型,我们还可以构建更结构化的、多步骤的编辑模型。相关研究尝试从自然发生的编辑历史(如维基百科修订记录或GitHub代码提交)中学习编辑过程。
该方法的算法更接近通用的自校正循环,但有两个主要区别:
以下是其核心步骤:
- 预测编辑操作:模型首先预测一系列编辑操作(如插入、删除、保留、替换),这些操作以序列标注的形式应用于当前文本的每个位置。
- 生成新内容:对于需要插入或替换的位置,模型基于编辑操作和已进行的编辑历史,并行生成新的文本内容。
- 应用编辑:根据预测的编辑操作和生成的新内容,确定性地生成编辑后的新文本。
- 更新历史:将本次编辑加入历史记录,为后续步骤提供参考。
这种方法的好处在于,对于长文档的编辑,它不需要重新生成整个文本,只需预测编辑位置和局部的新内容,效率更高。实验表明,参考过去的编辑历史有助于预测未来的编辑。


学习编辑表示:将编辑编码为向量

另一种思路是将“编辑”本身抽象并学习其表示。相关研究旨在学习一个能够捕捉从源序列到目标序列所做更改类型的紧凑向量表示。

其核心思想是训练一个编码器-解码器架构:
- 编辑编码器:接收“编辑前”和“编辑后”的文本对,将其编码成一个固定维度(如512维)的编辑向量。这个向量旨在捕捉编辑的类型和语义。
- 编辑应用解码器:给定一个新的“编辑前”文本和上述编辑向量,解码器尝试生成应用了该类型编辑后的文本。
训练目标是自编码器式的:通过编辑向量重建“编辑后”的文本,公式可简化为最大化 log P(编辑后文本 | 编辑前文本, 编辑向量)。通过限制编辑向量的维度,模型被迫学习编辑的本质特征,而非记忆具体内容。这种方法可用于分析相似的编辑模式,例如识别代码中同一类安全漏洞的修复。
提示工程下的自优化
随着大语言模型能力的提升,研究者发现无需额外训练,仅通过巧妙的提示工程也能实现自优化。Self-Refine 是这方面的一篇代表性论文。
其算法完全遵循我们开始时介绍的基本自校正循环框架,全部通过提示完成:


以下是其三步提示流程:
- 生成:使用标准少样本提示生成初始输出。
- 评判:提示模型对当前输出进行评判,例如询问“这个输出在[某个质量维度,如流畅性、正确性]上为什么不够好?”,以获取具体的改进反馈。
- 精炼:将原始输入、当前输出和评判反馈组合成一个新的提示,要求模型根据反馈改进输出。
停止标准可以是简单的启发式规则(如反馈是否积极)或固定迭代次数。实验发现,这种方法在改进代码可读性、调整对话情感等任务上效果显著,但在数学推理等复杂任务上帮助有限。同时,更强的基座模型(如GPT-4)从自优化中获益更大。
引入外部工具:自我调试

当任务涉及可验证的领域时,引入外部工具能极大提升自校正的可靠性。Self-Debugging 这篇论文将代码执行作为外部反馈引入循环。



该方法在自校正循环中嵌入了代码执行和测试验证的步骤:



其工作流程如下:
- 生成代码:模型生成代码。
- 执行与测试:在安全环境中执行生成的代码,并运行预定义的单元测试。
- 判断:如果所有测试通过,则流程结束;否则,进入下一步。
- 解释错误:提示模型根据执行结果(如错误堆栈跟踪)解释错误原因。
- 精炼代码:基于错误解释,模型修改并重新生成代码。
这种利用外部执行反馈的方法在代码生成任务上带来了显著提升,尤其是在较难的任务上。一个有趣的发现是,这种需要多次调用模型但每次只生成一个样本的“自我调试”方法,其效果可以与一次性采样多个输出然后选取最佳(如自洽性)的“暴力”方法相竞争,为如何在推理计算成本和质量之间权衡提供了新思路。


进阶方法:反思与工具化评判


后续研究进一步扩展了这些思想。例如,Reflection 方法不仅进行单步评判,还将历史评判和精炼步骤积累为“记忆”或“经验”,在后续的迭代中加以利用,形成更连贯的优化过程。
另一项工作 Tool-integrated Critic 则专注于在评判阶段引入工具。评判者(可以是另一个LLM)被允许调用各种外部工具来验证输出的正确性,例如:
- 用代码解释器检查计算结果。
- 用知识图谱API验证事实。
- 用情感分析API判断语气。
- 用搜索引擎核对信息。
通过为不同任务配备相应的工具,评判的准确性大大提高,从而带动了最终输出质量的显著提升。
局限性:并非万能药
值得注意的是,自校正并非总是有效。有论文专门指出其局限性,标题直言“大语言模型尚无法自我校正推理”。
关键的发现包括:
- 缺乏外部反馈时,内在的自我校正常常失败,甚至可能因为过度修改或确认偏误(倾向于强化最初的错误推理)而导致性能下降。
- 模型难以发现自身的错误,尤其是在它本身就不擅长的领域(如复杂的数学证明、逻辑推理)。
- 成功场景多限于语法、风格、格式调整等相对浅层的任务,或者在有代码执行、事实核查等强外部信号的辅助下。
因此,在实际应用中,需要仔细评估任务属性和模型能力,以决定是否采用以及如何采用自校正策略。
总结

本节课中我们一起学习了语言模型自优化与自校正的各种方法。我们从基本循环框架出发,探讨了从早期需要训练的两阶段模型,到利用编辑历史和编辑向量的结构化方法,再到完全基于提示工程的自优化(Self-Refine),以及引入代码执行(Self-Debugging)和多种外部工具(Tool-integrated Critic)来增强评判可靠性的进阶技术。最后,我们也了解了这些方法的局限性。这些工作为我们设计高效的推理时算法提供了丰富的思路和工具箱,核心考量包括是否引入显式评判、是否需要训练、如何条件化生成以及是否及如何使用外部工具。
9:推理模型 🧠
在本节课中,我们将要学习推理模型。推理模型通常指那些经过训练(尤其是强化学习训练)以利用长链思维来提升特定任务性能的模型。这些模型的特点是能够进行自我修正和扩展推理。我们将探讨其核心概念、训练方法以及它们对推断过程的影响。


推理模型概述
推理模型通常通过强化学习训练,利用长链思维来提升任务性能。这些思维链不仅长,还具有特定属性,如自我修正。通常,更长的推理序列与更好的性能相关,这种相关性在推理模型中尤为明显。这些模型通常使用可验证的奖励进行训练。


早期尝试:Star模型

第一个从LLM角度进行此类研究的论文是Star模型。其核心思想是:给定一个问题,通过语言模型生成一个推理过程(思维链)和一个答案。然后,使用可验证的奖励(如数学或代码)检查答案是否正确。最常见的可验证奖励是数学或代码问题。

在Star模型中,如果答案错误,系统会提供一个提示(即正确答案),然后让语言模型生成一个与该答案匹配的推理过程。这是一种额外的训练步骤。
以下是Star模型的基本流程:
- 生成答案和推理过程。
- 根据奖励过滤正确的思维链。
- 如果正确,保留完整链;如果错误,则根据给定答案生成推理过程。
- 在过滤后的数据上进行微调。
这种方法非常接近强化学习,可以看作是一种策略梯度目标。其基本形式是:如果答案正确,奖励为1;如果错误,奖励为0。这将丢弃错误推理路径的梯度。
Star模型在一个相对较小的模型(GPT-J 6B)上进行了测试,数据集包括算术、常识问答和小学数学问题。他们使用了一种结构化的方式来表示推理,例如通过基于规则的方式生成“草稿纸”式的计算步骤,以此作为种子数据来启动模型的强化学习训练。
实验表明,在没有推理过程的情况下,模型在多位数加法等复杂任务上表现不佳,因为奖励过于稀疏。而引入推理过程后,模型处理更长、更复杂问题的能力得到了显著提升。一个有趣的发现是,如果过度监督推理模型,它们虽然启动更快,但最终性能可能不如纯粹使用强化学习训练的模型。
深度强化学习:DeepSeek-R1模型
DeepSeek-R1是一个使用大规模强化学习直接在基础模型上训练的推理模型。它的一个显著特点是,在训练初期没有进行任何监督微调,而是使用纯粹的强化学习目标,即GRPO目标。
GRPO目标结合了强化学习中的多个好想法。其基本流程是:
- 对于同一个查询,生成一组输出。
- 为每个输出计算奖励。
- 利用组统计量计算优势函数。优势函数
A_i的计算公式为:
A_i = (R_i - μ) / σ
其中,R_i是第i个输出的奖励,μ是组内所有奖励的均值,σ是标准差。这衡量了某个输出相对于组内其他输出的表现。 - 使用包含裁剪机制的损失函数来更新策略,防止梯度更新过于极端,确保训练稳定性。

DeepSeek-R1的提示模板非常简单,仅通过对话格式引导模型在“思考”标签内进行推理,然后在“答案”标签内给出最终答案。令人惊讶的是,这对于一个强大的、拥有4700亿参数的、在互联网数据上训练的模型来说,足以使其在一定程度上遵循指令并开始学习。


训练结果显示,模型在AIME(高难度数学问题)上的准确率从最初的约15%提升到了超过70%。同时,模型分配给“思考”的令牌数量自然地从数百个增加到了数千个,这表明模型学会了使用更长的推理链来解决问题。此外,模型还展现出了自我修正的能力,例如在推理过程中自发地发现错误并返回修正。





DeepSeek-R1的另一个重要贡献是展示了可以将大型模型(470B)的成功推理轨迹“蒸馏”到更小模型(如32B)中,并且蒸馏模型的表现与直接从零开始训练强化学习的小模型相比更具竞争力。




推理模型的推断影响

从推断的角度看,这项工作的影响深远。由于模型使用更多令牌进行“思考”,推断成本变得更高。然而,这也意味着一个更小的模型如果被允许使用更多的推断令牌,其性能可能超越一个更大的基础模型。例如,DeepSeek-R1 32B模型的表现就优于其470B的基础模型。这种范式使得“用计算换性能”的推断策略在今年变得非常流行。



认知行为与模型改进




一项名为“认知行为自改进”的研究分析了确保零样本强化学习有效的四种重要行为:验证、子目标设定、回溯和反向链。研究发现,不同的基础模型(如Qwen和Llama)表现出这些行为的倾向性不同,这影响了它们通过强化学习进行改进的潜力。

这种差异可能源于“中期训练”——在预训练的最后阶段加入高质量数据(如数学教科书内容),或者使用了合成数据。即使都称为“基础模型”,其训练方式也可能大相径庭,从而导致不同的推理行为倾向。研究还表明,通过提示工程鼓励这些行为,可以提升模型的推理能力。
长思维链的涌现条件
我们的研究“揭秘长思维链”旨在探究长思维链何时涌现、何种条件能促发推理、以及训练如何影响思维链长度。
我们比较了监督微调和强化学习。实验发现,那些最初就被训练(或“引导”)进行长思维链推理的模型,能从强化学习中获益巨大;而仅擅长短链推理的模型则提升有限。这证明了长思维链对于强化学习效果的重要性。
在训练推理模型时,我们观察到模型性能在提升后突然崩溃。原因是模型生成的序列长度超过了最大输出限制,导致答案被截断,从而全部被判错。为了解决这个问题,我们提出了一种新的奖励函数,该函数同时考虑答案正确性和生成长度,通过一个余弦权重项来平衡,使得模型能够稳定训练而不超出长度限制。

我们还发现,7B规模的模型难以发展出复杂的推理行为,过度暴露于短链数据会阻碍长链推理能力的发展,而基于规则的验证器通常比基于模型的验证器效果更好。
推理能力的可迁移性
我们研究了数学推理能力的提升是否能迁移到其他领域,如科学问答、编码、智能体规划,甚至是非推理任务如对话和指令遵循。

实验在Qwen 314B上进行,对比了“仅数学数据”的监督微调和强化学习。结果显示,强化学习训练出的模型在其他推理任务上泛化得更好,在非推理任务上的性能下降也更少。而监督微调模型则出现了更明显的性能下降。
通过分析模型参数和令牌概率的变化,我们发现强化学习(特别是GRPO)主要调整与当前任务相关的特定序列的概率,对模型整体知识的改动较小;而监督微调则会广泛地改变模型参数,可能“遗忘”或“损害”其他领域的知识,从而导致泛化能力较差。

其他高效训练与推断方法
除了上述主要方法,还有一些有趣且高效的技术:
- 简单测试时扩展:仅使用少量(如1000个)精心策划的推理示例,结合“预算强制”等简单技巧(如在推理中插入“等待”指令、超时后强制输出答案),就能高效训练出有效的推理模型。
- 长度控制策略优化:通过提示(如“思考N个令牌”)和修改奖励函数来控制推理长度,实现了更好的准确性与令牌消耗的权衡。
- 搜索转思维链:将传统搜索算法(如广度优先、深度优先搜索)的轨迹转化为思维链格式进行训练,使模型学会模仿搜索策略。
- 自适应并行搜索:赋予模型生成并行搜索线程的能力,类似于多线程编程。这允许在总序列令牌数有限的情况下进行更广泛的探索,提高了复杂任务的解决能力,但增加了总体推断延迟。

本节课中我们一起学习了推理模型的核心概念与发展。我们从早期的Star模型和DeepSeek-R1的强化学习方法出发,探讨了推理模型的训练机制及其对推断成本的影响。我们还分析了促使长推理链涌现的条件、不同训练方式(如监督微调与强化学习)在能力迁移上的差异,以及一些提升训练和推断效率的实用技术。理解这些内容有助于我们更好地利用和开发下一代语言模型。
10:工具集成 🛠️
在本节课中,我们将要学习如何为语言模型集成外部工具。我们将探讨工具的定义、使用范式、具体实现方法以及相关的安全与鲁棒性问题。
概述
工具使用的基本思想是为智能体(Agent)提供其可以调用的外部工具。这些工具是语言模型(LM)外部的,属于LM所处环境的一部分。它们具有函数式接口,可以看作编程语言中的函数,通过输入参数调用并获取输出结果,通常可以作为程序执行。

工具的分类

根据功能,我们可以将工具大致分为三类。
以下是三类主要工具:
- 感知工具:这类工具赋予语言模型访问其参数中未包含信息的能力。例如,从网络收集信息、读取文件系统或与用户交互。
- 行动工具:这类工具可以直接对环境产生影响和改变。感知主要是从环境中收集信息,而行动则是实际对环境做出某种改变。
- 计算工具:这类工具不一定与外部环境交互,因此不会为智能体或LM提供比最初更多的信息,但它可以执行语言模型内部不易完成的计算。
工具使用范式

上一节我们介绍了工具的分类,本节中我们来看看工具调用的基本范式。这不是调用工具的唯一方式,但它是语言模型中调用工具的常见方式之一。
基本流程是:用户提出查询,在模型实际生成输出之前,先通过某种方式调用函数。通常,这意味着有一个类似“开始工具调用”的标签,表明开始调用工具,然后写入工具调用指令,最后结束调用。

然后,系统获取工具调用指令,调用远程服务器并返回API输出。这个输出可以直接连接到智能体生成的流中,也可以作为观察结果提供给智能体。基于这个观察结果,智能体可以继续生成对用户的响应。
早期示例:WebGPT
在现代大语言模型时代,这类工具集成的早期示例之一是WebGPT。它基本上是为GPT模型提供了一个进行网络搜索的工具。
其核心问题是语言模型缺乏对当前信息的访问能力,因此将基于文本的网络浏览作为一种工具。他们有一个基于文本的界面,允许当时的GPT模型(可能是GPT-2)进行搜索、点击链接、滚动和引用。
他们实际上使用了人类反馈的强化学习(RLHF)进行训练,而不是来自可验证的奖励,而是直接来自反馈模型。
这项研究的一个重要成果是,通过使用工具可以获得引用,因此可以查找输出并提供链接,供人们查看。



自监督学习:Toolformer

Toolformer是一篇真正引发工具使用兴趣的论文。其创新之处在于,它旨在通过自监督学习从未标记的文本中教会模型何时以及如何使用工具。

其基本工作原理是:他们拥有语言模型数据和一个查询。他们会对API调用进行采样(例如,调用外部问答系统的API),然后执行这些API调用,并检查在给定API调用输出的条件下,生成正确答案的概率是否比没有API调用时更高。


他们通过一个自我标注、自动生成潜在工具调用、自动过滤并仅保留那些能改进预测的工具调用,然后对成功的调用进行微调的过程来学习工具使用。这有点像准强化学习,本质上是经过过滤的行为克隆。
现代标准:OpenAI函数调用
快进到2024年或当前,OpenAI发布了他们的函数调用标准。在该标准中,你可以以特定格式指定工具。

每个工具本质上类似于一个函数定义,包含名称、描述和参数等信息。参数使用JSON Schema来表达。



当语言模型进行函数调用时,它会生成一个结构化的函数调用,该调用与函数的配置文件匹配。然后,你的系统负责处理这个函数调用并实际执行相应的代码。
这种方法的优点包括可以进行类型安全检查、验证和一致性解析。此外,所有语言模型都在大量的函数调用数据上进行了训练,因此如果你以这种格式指定,语言模型通常能正确调用。
模型上下文协议(MCP)


模型上下文协议(MCP)是一个连接AI应用程序与外部系统的标准协议。可以将其视为智能体或语言模型使用的API。





其工作方式是:它采用客户端-服务器协议,每个服务器向智能体提供一组工具。MCP主机是你的AI应用程序(如你的语言模型客户端),它连接到所有这些MCP服务器。

MCP兴起的一个主要原因是,许多API需要API密钥,而你不希望将API密钥暴露给智能体。MCP服务器将API密钥隐藏在服务器后面,然后授予智能体访问MCP服务器的权限,这是一种特殊的工具身份验证方式。
目前,MCP已成为行业标准,有成千上万个不同的MCP服务器,提供了一个集中的注册表,涵盖从访问实时网络数据到生成语音等各种功能。


程序辅助语言模型
除了独立的工具调用,还有“程序辅助语言模型”的方法。在这种方法中,你实际上是用语言模型编写Python代码,然后执行该代码以获取答案。
基本思路是:思维链有助于数值推理等任务,但仍然会出错,自然语言算术容易出错。因此,我们可以使用代码执行作为计算器工具。
在程序辅助语言模型中,我们让它生成Python代码块,并给出交织着Python代码的思维链示例。然后,当它生成代码后,我们会执行Python代码并获取输出。这种方法现在被用于大多数聊天机器人中。
安全与沙箱


运行任意Python代码时,你肯定不希望程序删除你的主目录。因此,沙箱代码执行非常重要。


以下是几种沙箱方法:

- 基于AST的方法:在本地包装Python解释器,尝试在运行前剥离“危险”命令。它可以沙箱化某些模块的导入和危险函数,但仍然在与代码其余部分相同的进程中运行,可以访问内存,并且几乎不可能消除所有危险代码。
- 基于容器的隔离:例如在Docker中运行。这提供了完整的操作系统级隔离和网络限制,但会消耗资源,设置和运行更困难,并且启动需要时间。
- WebAssembly:如果你在Web浏览器中运行智能体,也可以使用这种方法。
工具使用的强化学习

随着推理和强化学习的流行,自然也有用于工具使用的强化学习。其动机是监督微调在处理复杂工具使用时存在困难。


他们遵循DeepSeek-R1论文的方法,提供格式奖励和正确性奖励。格式奖励检查工具调用格式是否正确。正确性奖励则允许他们逐步推动生成完全正确的工具调用,并给予部分积分。
一个有趣的发现是,与基于监督微调的推理相比,冷启动推理获得了更好的结果。



工具的鲁棒性



除了安全性,我们还需要考虑鲁棒性问题。这是一个从基准测试和语言模型有效使用工具的角度来看都存在的问题。
问题是API可能会过时,或者文件编辑工具可能会因为参数略有错误而失败。语言模型需要善于处理这种情况。


一项关于基准测试工具增强语言模型故障的研究,通过破坏一些API使其无法工作,并测量了几个方面:模型对工具损坏的感知能力,以及在某些工具可被替换的情况下使用其他工具的能力。研究发现,至少在一段时间前,模型的感知能力并不等同于测试成功率,这是一个主要问题。
创建工具

语言模型实际上能够创建工具。一种方法是从原始函数开始,提示模型导入现有工具、创建新工具或不使用任何工具来尝试解决问题。
通过多次运行这些不同方法,基于自我一致性决定哪个答案正确。如果创建工具能带来更高的一致性,则将该工具添加到工具箱中。逐渐增加工具箱的大小,并剔除不常使用的工具。
这种方法能在测试时持续处理实例,如果创建工具能带来更一致的输出或更好的准确性,则逐步添加到工具箱中,从而显著提高准确性。
总结


本节课中我们一起学习了语言模型工具集成的核心概念。我们了解了工具的定义与分类,探讨了从早期WebGPT到现代函数调用和MCP标准的发展历程。我们学习了如何通过自监督学习、程序生成和强化学习来让模型学会使用工具,并深入讨论了代码执行的安全沙箱问题和工具使用的鲁棒性挑战。最后,我们还看到了语言模型动态创建和优化工具集的潜力。掌握工具集成是构建强大、实用AI智能体的关键一步。
11:智能体与多智能体通信
在本节课中,我们将学习智能体(Agent)系统。智能体是一种能够迭代使用工具来完成任务的系统,而不仅仅是单次使用工具。如今,绝大多数智能体都由大型语言模型驱动。智能体带来了独特的推断挑战,包括推理与规划、环境表示、长上下文建模、评估以及多智能体协作等。我们将通过具体的例子和框架来探讨这些挑战及其解决方案。
作业与反馈公告
在开始之前,有两项与作业相关的公告。
第一项是作业二预计将在今天发布。这里的“今天”指的是地球上的任何地方,所以希望你们明天醒来时就能看到。
第二项是我们希望获得关于作业一的反馈。有些人反映作业耗时较长或某些部分较难。我们致力于确保作业内容不仅具有启发性,还能帮助大家掌握课堂讨论的知识点。
我们准备了一份匿名反馈表。为了鼓励大家填写,我们引入了一点博弈论:如果班级中超过50%的人填写了反馈表,那么每个人都可以在测验中获得额外的满分(3/3分)。这意味着你们的测验成绩会更容易提高,但前提是有一半的同学参与。你们可以督促朋友填写。
我们这样做的目的是希望获得匿名反馈,同时激励大家提供意见。
如果没有问题,我们就开始今天的课程。今天有两场展示。
智能体简介
我想谈谈智能体。我不会过多讨论如何通过架构或语言模型的变化来训练或设计智能体以提高准确性,而是主要关注与智能体推断相关的内容,因为这是一门关于推断的课程。当然,不可避免地会涉及一些架构方面的内容。


基本上,智能体是一个迭代使用工具来完成任务的系统,而不是一次性使用工具。




如今,几乎所有智能体都由大型语言模型驱动。可能还存在一些不由大型语言模型驱动的智能体。例如,三到五年前的Siri和Cortana。或者当你致电航空公司客服时使用的系统,它们大多基于决策树和分类器。然而,所有新开发的智能体基本上都在使用大型语言模型。


智能体之所以有趣,是因为它们在多个方面带来了独特的推断挑战。


第一个挑战是推理和规划。由于需要进行额外的步骤,这要求对它们进行推断。

第二个挑战是环境表示。如何表示从工具调用中获得的所有输出?


第三个挑战是长上下文建模。智能体可能是当今除摘要或问答之外最大的长上下文使用场景之一。


第四个挑战是评估。如果大家想使用并评估智能体,我会稍微谈谈这一点。
第五个是我们一直在研究的内容,更多来自我的个人经验,即批评模型。批评模型本质上类似于我们稍后会讨论的奖励模型,但我想现在讨论它们,因为这是唯一一节关于智能体的课程,之后讲到奖励模型时大家可以回想起来。



最后是多智能体委托。我不会详细讨论这一点,但会简要提及。



智能体的应用场景
首先,我们来看看智能体的一些应用场景。有没有人构建过智能体来做某事?
例如,模拟搜索引擎上的用户行为,预测他们的点击和下一步操作。这听起来是个有趣的应用。


还有其他有趣的应用场景吗?


感谢大家无意中分享了你们的智能体应用。我们有许多不同的应用场景。
目前最大的应用场景之一是软件开发,包括代码生成、调试和测试。流行的例子有GitHub Copilot、Anthropic Codex,以及我正在开发并稍后会讨论的开源智能体OpenHand。
另一个应用是网络自动化,例如数据提取、表单填写和测试。有像OpenAI的Agent Mode这样的闭源产品,也有像Browser Use这样可以在研究中下载使用的开源示例。
还有用于研究和分析的智能体。默认情况下,许多聊天机器人界面如ChatGPT或Claude现在都支持信息收集和报告生成。还有像Perplexity Pro这样的产品,以及开源版本如OpenDe Research(有多个版本可供选择)。
此外,还有交互式环境中的应用,如游戏、模拟和机器人技术,例如玩《我的世界》或操作机器人。
OpenHand 示例演示



我一直在构建一个名为OpenHand的开源智能体。它最初是一个开源工具,现在是我合作的一家初创公司的一部分。因此,我很多最好的例子都来自这方面的经验。

基本上,它是一个软件开发智能体,也支持网页浏览和其他各种工具。我展示一个小例子。

例如,我们可以让它执行一个任务:“找到CMU 2025年秋季‘语言建模的推断算法’课程的网站,下载作业,并估计完成作业一需要多长时间。”


让我们看看它的准确性。由于使用GPT-5进行推断速度很慢,我将改用Claude,以便在课程结束前完成演示。
在演示中,我们可以看到它调用搜索引擎工具来搜索课程,找到由我和Amanda教授的课程页面,访问作业页面并提取信息,然后克隆代码库以查看代码位置,接着打开PDF阅读作业内容等。

我们不需要观看整个过程,可以稍后再检查结果。观看智能体工作可能会浪费很多时间,所以最好让它自行工作,之后再检查。
关于智能体框架的讨论
有人问及LangChain和LangGraph等工具。LangChain和LangGraph是用于构建智能体的工具。我们决定不使用这类工具,而是开发了自己的智能体SDK,专门为此设计。
原因是LangGraph和LangChain等工具更像是固定的智能体工作流。它们适用于有固定流程的场景,比如先做这个,然后将信息传递给下一个步骤。但在软件开发中,工作流程非常多样化。我们想要创建一个能够处理多种不同任务的单一智能体,因此没有使用那种脚手架式框架。
我们的做法是添加智能体可用的工具,然后我们创建的框架实现了幻灯片中将讨论的所有推断方法。它主要不涉及多智能体之间的委托,而这正是LangGraph和AutoGen所擅长的。

推理与规划


接下来,我想谈谈推理与规划。基本思想是,我们之前从数学推理的角度讨论过推理。在数学推理中,主要是演绎推理,即应用原理以固定方式解决问题。而在智能体中,情况有所不同:它更开放,需要根据总体目标想出下一步的最佳行动方案。
语言模型中执行推理的标准方法基于一篇名为ReAct的论文。这是一个框架,基本思想是先推理,再执行行动。


这与仅执行行动形成对比。在仅执行行动的模式下,你得到一个任务,然后执行一个行动(例如“去一号门”),如果门关着,就“打开一号门”。但在ReAct中,你在行动之间加入了“思考”步骤。这样做有两个原因:首先,思维链已被证明能提高准确性,智能体也不例外;其次,它实际上以自然语言的形式提供了关于正在做什么的更新,让你无需查看实际操作就能跟随智能体的思路。
因此,准确性和可读性是ReAct的主要优势。
ReAct是一种使用推理的多步骤智能体方法。另一种方法叫做CoAct,其核心思想是让智能体编写代码来执行行动,而不是逐个调用工具。
例如,如果你想检查10个不同商品的价格并找出最贵或最便宜的商品,传统方法是进行一系列单独的工具调用,获取所有结果,然后再进行另一个工具调用或使用语言模型来挑选。而使用CoAct,你可以直接编写一个Python程序来调用所有工具。这简化了行动空间,因为所有工作都变成了用Python等语言编写程序,并且能更高效地处理子任务。
这对于推断很重要,因为它涉及是生成一个大型输出的单一行动,还是许多生成小型且复杂度较低输出的单独行动之间的权衡。即使使用CoAct,也可以让智能体倾向于编写它更确定结果的小段代码,或编写它不太确定结果的大段代码。因此,需要在两者之间进行权衡。
智能体规划系统
ReAct是一种反应式的规划方式,通常只推理下一步的最佳行动。
我们在OpenHand中做的另一件事是,我们有一个利用工具的规划系统,可以制定计划,并在开始执行时更新该计划。这是我们最近引入的,但非常有用和有效。
我们有一个任务追踪工具。它提供结构化的任务管理,基本上会为任务制定包含不同步骤的计划,并在每一步状态变化时更新状态。它会每次在计划步骤变更时调用该工具。
例如,我们可以让它“检查所有课程文件,查看是否有缺失的引用,创建一个计划并逐项勾选”。它会创建一个计划,比如“检查所有起始代码文件”、“检查共享任务示例代码”、“验证需求”等,并将当前步骤标记为“进行中”。完成后,它会标记为“已完成”。这样,它能确保在上下文窗口中包含计划的所有元素,不会忘记计划。
这个计划也会存储在磁盘上(例如tasks.md),所以不会从上下文窗口中消失。
另一个工具是思考工具。这个工具基本上赋予模型权限,让其写出非常长的思维链。这是Claude在推出Claude Code时引入的。基本思想是,如果你需要仔细思考某事,可以在这个工具中写下需要做什么的长篇解释。这个工具本身没有任何效果,仅供思考。然后,在下一步中,基于这个思考,它可以采取实际行动。从技术上讲,如果你有一个推理模型,这可能不是必需的,但它是一种将未明确为此训练的模型转变为能够进行深度思考的模型的推断时技巧。
环境表示
接下来,我想谈谈环境表示。正如我们从上一节工具课中学到的,工具有行动并会获得观察结果。问题是如何在模型中表示这些观察结果。这很重要,因为它们可能非常大,从而影响你在使用智能体时如何进行推断。
我将主要讨论表示网站,因为我在网络智能体方面有很多经验,但也会稍微提及表示代码和文件。
第一种网站环境表示是基于文本的。这基本上是将网站或PowerPoint演示文稿等非原生文本内容转换为Markdown。例如,LTI主页会被转换成简洁的Markdown表示,易于使用。
然而,基于文本表示的一个问题是,你实际上可以在网站上执行点击、滚动等操作。在Markdown表示中,我们没有网站可交互元素的具体表示。例如,如果你想点击“自然语言处理”链接,或者想在文本框中输入内容,Markdown中没有原生的链接或文本框表示。你需要要么在Markdown中创建一种表示,要么使用基于文本的HTML DOM树表示。
标准方法是使用可访问性树。这最初是为视障人士的屏幕阅读器创建的。它基本上重用了那里的方法。它会为每个元素提供一个ID,如果元素可点击,你可以通过调用工具点击该ID来执行操作。

这是智能体最初表示网站的方式。现在,越来越常见的是通过视觉来表示网站。

例如,一篇名为“Visual Web Arena”的论文中介绍的方法:你截取网站的截图,然后在截图中每个元素周围画一个框,并在框旁边放一个数字。这个数字就是之前提到的ID,你可以点击这个数字。同时,你还会以文本形式获得所有带编号元素的摘要,以强化你可以访问的元素。
这被称为“标记集”,该论文将标记集应用于基于视觉的网络智能体。
在表示代码或文件系统上的文件时,另一个问题是,你不想意外读取一个千万行的文件而完全破坏你的上下文窗口。因此,人们要么训练模型,要么明确为模型提供工具,使其只选择文件的一部分行。有一些文件编辑工具允许你这样做。
效率优化
智能体的一个巨大挑战是上下文长度。我们有很长的对话历史。在像Swe-bench或Web Arena这样的典型评估中,对话历史可能多达100步,包括大约50次工具调用和100次行动与观察,这可能非常长,超过数十万个令牌。
在实际使用中,我曾使用智能体执行多达2000步的任务,这意味着上下文长度达到数千万令牌。大多数语言模型无法处理这种情况,即使可以,成本也非常高。对于网页浏览尤其糟糕,因为网页可能非常大。因此,你需要高效的上下文管理策略。
第一个非常重要的策略是提示缓存或KV缓存。我们稍后会在课程中详细讨论,但基本工作原理是:当你发送第一个输出(或提示)时,你需要计算系统提示、观察和行动的表示。下一次计算时,你已经有了系统提示、观察和行动的自回归表示,因此只需要输入下一个观察和行动。这意味着你可以保存计算这些表示的所有计算量,从而节省大量金钱和时间。
实际上,许多服务只缓存提示中的内容,有些只缓存系统消息和观察。但如果你巧妙处理,也可以缓存行动。
另一个策略是上下文压缩。这是一种获取先前上下文并移除部分内容的方法,基本上是通过总结或删除来获得更好的结果。
在OpenHand中,我们有几种不同的方法来实现这一点,这些方法被广泛使用,包括其他编码CLI如Gemini和Cline中的方法。

第一种方法是总结。在完成一定数量的步骤后,我们取先前部分步骤,将其输入给一个语言模型,总结先前步骤中发生的事情。这允许我们移除大约一半的上下文,同时保留大部分相关信息。这并不完美,有时可能会丢失后期有用的信息,但经过一定的提示工程,我们已经能够成功实现。从我们的代码库中可以看到相关提示的细节。


从实验结果来看,我们能够在保持Swe-bench性能的同时,实现两倍甚至更多的成本降低。我们在这方面有一些经验教训。例如,在Swe-bench上实验时,它工作得相当好,但也会做一些非常愚蠢的事情,比如反复发送拉取请求而忘记已经发送过。我们需要修改提示,告诉它记住是否已经发送过拉取请求。这说明了在真实使用中基准测试的难度。


另一种在智能体中非常常见的方法,最初用于Web Arena基准测试,是网页上下文压缩。网页大而嘈杂。有两种方法,我只在幻灯片中列了一种。一种方法是,在你已经处理过网页一次之后,只保留最近的可访问性树或图像。这意味着查看过一次后,基本上将其完全从历史记录中移除。


这里的挑战是,提示缓存的效果会降低,因为在进行提示缓存时,你会丢失一个输入。另一个问题是,你仍然需要处理大型网页,这可能相当昂贵。






另一篇名为“Mind2Web”的论文中提到了另一种方法。这不是参考文献中的主要部分,但他们有一个有趣的推断时技术:他们尝试首先猜测网页的哪些部分最有用。他们获取可访问性树,使用一个非常轻量级的廉价模型尝试猜测其中最重要的元素,然后只将这些元素及其父元素输入语言模型。这也是一种可以在不损害性能的情况下保留更多历史记录的方法。我还没有听说很多人在这个上下文中使用它,但如果你对网络智能体感兴趣,这可能是一个值得尝试的有趣方法。
智能体评估
接下来,我想谈谈评估。有许多智能体评估基准。我先讨论几个具体的,然后谈谈一个聚合了许多基准的地方。
第一个我个人非常关注的基准是Swe-bench。我认为很多其他人也关注这个基准。它基本上是一个用于修复GitHub问题的基准。输入是一个GitHub问题和一个代码库,智能体需要生成代码库的修改以修复问题。评估方式是,与此问题一起引入了一些单元测试来检查修复的正确性,所有与问题一起引入的单元测试都必须通过才能获得满分。此外,在拉取请求之前存在的测试套件中的一部分单元测试也必须通过。基本上,你需要在修复问题的同时不破坏现有功能。


它包含了来自12个流行Python仓库的2000个GitHub问题。由于这个基准非常流行,已经有许多扩展。例如,Swe-bench Verified是一个包含500个问题的扩展,这些问题经过特别验证,仅根据问题描述是可解决的。原因是许多问题描述比较模糊,你未必能从模糊的描述中得出所谓的“正确答案”。Swe-bench Verified试图使所有问题都可解决。现在每个人都使用这个进行评估,因为我们不知道性能的上限是多少,但在Swe-bench Verified上,我们认为上限接近100%。另一方面,它也失去了解决模糊问题的乐趣。仅仅在Swe-bench Verified上获得高分并不意味着你解决了所有GitHub问题,那可能需要与人类互动以澄清问题。

此外,这全是关于Python的。还有像Multi-Swe-bench这样的基准,涵盖不同的编程语言,以及Swe-bench Multimodal,包含多模态数据,需要修复前端等。还有Swe-bench Lite,每月更新新问题。
第二个基准是Web Arena。这是我们在这里(CMU我的实验室)创建的,但很多人用它来评估网络智能体。基本设置是,你建立许多不同的网站,这些都是你可能认为是流行网站的开源版本。例如,OneStopShop是亚马逊的开源版本,是一个购物网站;有一个内容管理系统;还有开源版本的Reddit叫Postmill,开源版本的GitHub叫GitLab。此外,还有许多其他信息源,如维基百科。然后,智能体尝试回答诸如“告诉我2023年3月在食品购买上花了多少钱”或“创建一个Nolan粉丝仓库,在Readme文件中列出Nolan的奥斯卡获奖电影”等问题。
包含许多任务,涉及复杂的多网页互动,因此这是一个标准的网络智能体评估基准。
另一个人们使用智能体的领域是研究。一个标准版本是GAIA。GAIA基本上是一个研究基准,你需要找到诸如“这个临床试验的注册人数是多少”或“阅读这个标签并告诉我相关信息”、“阅读NASA的每日天文图片”等信息。这个基准允许你进行研究。它由Hugging Face和Meta创建,他们大约上周刚刚发布了GAIA 2,我还没有尝试过,但据说是这个基准的现代化版本。
还有许多其他评估基准可以使用。我想强调其中的几个。
在OpenHand仓库中,我们有一个评估基准目录,其中实现了大约25个不同的基准,涵盖了从编码到一般协助再到网页导航等各种内容。这是一个例子。
最近,一家名为Prime Intellect的公司发布了一个环境中心,其中包含许多环境。这些环境可用于训练或评估,但我会说其中大多数更像是评估环境,而不是训练环境。这是另一个可以找到许多环境的地方。OpenHand中心的环境更偏向于实际可用性,而Prime Intellect的环境中心的环境更偏向于沙盒化和更易于运行。如果你想做一个研究项目,环境中心的环境可能是一个好的起点;当你想要过渡到更复杂或更接近真实世界使用的环境时,OpenHand的环境可能更好。两者有一些重叠,有些环境同时包含在两者中。
评估这些基准非常昂贵,所以如果你想研究推断并使其更快,我会非常高兴。
批评模型
接下来是批评模型。我想稍微谈谈这个。智能体的批评模型,其动机在于,拥有关于智能体表现如何的第二意见会有所帮助。
有几个使用场景:第一个是重新排序多个候选以提高准确性;第二个是审计以确保安全性或性能。
以一篇关于重新排序的论文为例,这是我们与加州大学伯克利分校人员合作完成的,名为“SWE”。这篇论文有两个部分:第一部分是通过强化学习训练软件工程模型或软件工程智能体模型;第二部分是训练一个批评模型来重新排序多个候选智能体轨迹。
基本思想是,你希望执行推断,展开n次,然后选择最佳输出。我们展示的是,如果你从一个当时准确率约为20%的模型开始,每次将展开次数翻倍,你会在Swe-bench上看到准确率近似恒定地增长。我们能够将分数从大约20提高到32。
因此,如果你真的想要最好的基准分数,这种方法非常有效,但代价是必须运行推断16次,这非常昂贵,因为运行智能体本身已经很昂贵了。
如果这还不足以说服你它的重要性,我们可以看看Claude Sonnet 4.5的发布。如果你看他们的结果,深色条是单次实例推断,浅色条是他们进行了多次展开然后重新排序的结果。这就是当Anthropic想要击败OpenAI,或者OpenAI想要击败Anthropic时,每个人都会做的事情。
实际上,我不知道有多少人在生产环境中使用这种方法。但假设你正在开发一个智能体来运行机器学习实验,你真的希望它成功,并且可以花费1万美元来确保它成功,那么这可能是一个可行的选择。

训练方法实际上很简单:你展开一个轨迹,使用已有的单元测试评估输出,然后训练一个模型来预测输出是否正确。这是最简单的方法。当我们将来讨论奖励模型时,这是一个
12:奖励模型与 Best-of-N 🎯
在本节课中,我们将学习如何通过奖励模型来评估和选择语言模型的输出,并深入探讨一种名为 Best-of-N 的简单而强大的推断时对齐方法。我们将从基础的拒绝采样概念讲起,逐步过渡到奖励模型的构建与应用,最后分析 Best-of-N 方法的成本与权衡。
课程概述与更新 📢
我们可以开始了。在开始之前,有几个更新事项。
作业二已经发布。我们正在批改作业一,但很可能在下周的退课截止日期前无法公布作业一的完整成绩。如果这对您做出决定至关重要,请在 Piazza 上发布一个私密帖子,我们可以优先为您批改。
Piazza 上有一个关于 AI 使用政策的问题,我们将在今天晚些时候发布相关帖子。我们的政策很可能与您在其他大多数课程中看到的一致:您可以使用 AI 辅助,但不能直接复制粘贴内容。我们会在帖子中提供更多细节。
最后一项行政事务是关于作业一的反馈表。目前收到的反馈非常有用,大约有一半的同学已经填写。如果您还没有填写,这将对我们非常有帮助。反馈表在 Piazza 上开放,截止时间大概是明天午夜。
从拒绝采样到 Best-of-N 🔄
今天我们将讨论奖励模型和 Best-of-N。我们还将有三个学生报告,因此今天的课程可能比往常稍短。
一个合理的初步问题是:为什么我们要花一整节课来讲 Best-of-N?单从名字看,这似乎是一个极其简单的想法。
答案是,我们将以此作为一个框架,来讨论一点计算统计学的知识,并更广泛地探讨奖励模型。虽然强化学习人类反馈(RLHF)不是本课程的重点,但奖励模型对于强化学习和当今非常流行的各种后训练方法都极其有用。
我们将通过其推断时的等价方法——使用奖励模型的 Best-of-N——来讨论这个问题。
拒绝采样简介
在讨论奖励建模之前,我们先绕个弯,谈谈更广泛的拒绝采样问题。其核心思想是:我们想要从某个感兴趣的分布 D 中生成样本。
我们知道这个分布的一些属性,但没有一个完善的方法直接从中抽取样本。也许它在计算上不可行,也许我们无法精确写出这个分布的形式。我们大概知道如何计算给定点的概率,但不知道一个好的采样方法,或者从中采样可能成本很高。
如果我们想要从分布 D 中获取样本,但不知道如何获取,该怎么办?
拒绝采样的思想是:从另一个分布(提议分布)中抽取样本,然后在最后进行某种操作,以获得一个可能来自目标分布 D 的样本。
为了说明其工作原理,假设这是我们的目标分布 D(纵轴是概率,横轴 X 是定义域)。这个分布可能形状复杂,难以写出精确的方程。但我们希望得到一个符合该分布概率质量空间的样本。
我们的做法是:不尝试写出 D 的表达式,而是选择一个候选分布或提议分布 P 来采样。例如,我们想从 D 的定义域上的均匀分布中采样。这很容易,我们知道如何从均匀分布中抽取样本。
然而,一旦我们从提议分布 P 中获得了样本点,我们得到的是来自 P 的样本,而不是来自实际目标分布 D 的样本。我们如何转换呢?
我们将为每个样本点估计一个它实际上来自 D 的概率。直觉是:如果我们直接从 D 采样,采样到 D 中低概率区域(例如某个点)的几率相对较小,而采样到 D 中高概率区域的几率相对较高。
通过这种方式,我们将淘汰部分样本,以获得更能代表目标分布的样本。
具体做法是:我们为每个从提议分布中抽取的点,采样一个“接受概率”。我们为这些点赋予了另一个维度上的概率,即我们接受该点作为来自 D 的样本的可能性。
然后,我们根据这个概率来决定保留(接受)或丢弃(拒绝)样本。最终保留的样本(绿色点)将比原始提议分布的样本更能代表来自 D 的样本。
形式化定义
为了从难以直接采样的分布 D 中采样,我们定义一个提议分布 P,其支撑集至少与 D 的支撑集一样大。这意味着在 D 有非零概率的所有地方,P 也必须具有非零概率,否则你无法获得能代表该分布的样本。
然后,我们从提议分布 P 中采样。接着,为样本中的每个点定义一个接受概率:
接受概率 = D(x) / (C * P(x))

这里的 C 是一个常数。在拒绝采样中,你会将其定义为最紧的上界。如果你迭代地进行,也可以近似这个值。关键是要选择一个足够大的 C 值,使得目标分布下的概率 D(x) 小于或等于 C 乘以提议分布下的概率 P(x)。



这样做的原因是,你需要确保这是一个定义良好的概率分布(值在 0 到 1 之间)。由于 P 的支撑集至少和 D 一样大,D(x) 可能为零,但 P(x) 永远不会为零(因为你从 P 中采样了 x)。你需要确保分母总是至少和分子一样大,因此这个比值最多在 0 到 1 之间。

这是一种蒙特卡洛技术,通过概率操作来间接计算难以直接求解的问题。拒绝采样是我们从无法直接采样的分布中获取样本的一种方法。
与语言模型的关联
我们为什么要在一门关于语言模型的课程中讨论这个?因为我们将把它与“从人类偏好分布中采样”的想法联系起来。在实践中,这是一个难以精确描述和直接采样的分布。
但在此之前,如果我们看这张图(蓝色目标分布和黑色提议分布),你会注意到我们拒绝了大约一半的采样点。如果我们想要从目标分布中获得一个非常好的样本,我们可能需要在这里花费很长时间。
这里的启示是:我们的提议分布越接近真实分布,拒绝发生得就越少。如果我们能定义一个在 D(x) 高的区域赋予高概率的提议分布,那么 D(x) / (C * P(x)) 这个比例通常会很高,因此接受每个样本的概率就会相对较高。
那么,如何获得一个更接近真实分布的提议分布呢?我们将通过学习一个像语言模型这样的分布,并直接从中抽取样本来实现。
Best-of-N:寻找最优输出 🏆
实际上,对于今天剩下的内容,我们关心的不是获取大量能代表目标偏好分布的样本,而是找到目标分布中的单个最高点或某个高点。我们想要一个接近这些峰值的东西。
如果分布只是二维的,这很容易,我们可以画出来然后取最大值。但我们是在所有可能序列(长度可达数千个标记)的空间上操作,这当然是一个比二维空间维度高得多的空间。既然我们无法实际画出这个分布,我们能做什么呢?
我们有一个函数 D(X),它给出了某个点的偏好、目标分布似然或奖励。我们想找到可能的最高点。如何近似这个?
一种方法是猜测和检查。我们将采用一个我们认为在某种程度上接近分布 D 的提议分布(因为我们希望以更高的概率采样到 D 中可能得分高的东西),然后检查哪个在 D 下具有最高的概率。我们不再看 D(x) / (C * P(x)),而是直接检查 D(x),并返回在目标分布下概率最高的那个东西。
几周前我们在讨论约束解码时,曾强烈反对这种拒绝采样的想法。我们说,如果你想生成序列直到得到一个无害的、或不涉及某个主题的、或包含某个词的序列,你可能会陷入无限生成的困境。这里的区别在于,我们只对相对行为感兴趣。我们会说,我们认为我们的提议分布(语言模型)足够接近,我们能从中得到合理的东西。因此,我们将固定一个 N 值,比如采样 10 个或 100 个东西,如果它们概率都很低,我们不会继续采样,而是认为这就是我们能得到的最好结果。
我们将返回样本中“最不差”的那个。如果我们的分布 P 与 D 吻合得很好,那么希望其中会有概率相对较高的东西。
这就是 Best-of-N。我们有一个偏好分布,但不知道如何直接从中采样,因此我们改为从 P 中抽取 N 个样本,然后选择其中概率最高的那个。
从概念上讲,这是一个极其简单的算法:我们采样 N 个东西,根据某种新的排序标准(而不是原始模型的 log 概率)对它们进行排名,然后返回排名最好的那个。
奖励模型:定义“更好”的标准 📈
在接下来的内容中,我们将主要讨论一件事:除了原始模型的 log 概率之外,还有什么可以作为排序标准?
Best-of-N 可以被视为一种对齐方式。今天我们将主要讨论奖励模型,当人们谈论“与人类价值观对齐”时,通常指的是使用奖励模型进行强化学习。
Best-of-N 提供了一种实现推断时对齐的视角。其思想正是这种拒绝采样:你试图获取来自一个分布的输出集,并将它们对齐到一个新的分布。你可以将此视为试图减少从你的 Best-of-N 策略中抽取的样本与你的目标分布之间的 KL 散度。
这与我们如何选择 N 值密切相关。文献中经常引用一个数字,即你的 Best-of-N 输出分布与目标分布之间的 KL 散度有一个宽松的上界:log N - (N-1)/N。
这意味着,如果你有 100 个输入,对每个输入进行 N=50 的 Best-of-N,你可以将你的最佳输出分布与目标偏好分布之间的 KL 散度界定在这个值。这通常被引用为一个精确值,或者至少是一个非常紧的上界。
实际上,理论方向有一些非常有趣的工作认为,对于许多边缘情况(其中一些在实践中确实会出现),这并不是一个紧上界。有一篇优秀的论文提出了一个更紧、更复杂的上界。
所以,这并没有完全回答“选择哪个 N 最好”的问题,但至少部分回答了:N 越大,你就越接近你定义的目标分布。
奖励模型是如何工作的?🤖
我们将退一步讨论如何定义目标分布。最常见的方式是通过奖励模型来参数化。至少在我们通常的概念中,奖励模型是使用基于 Bradley-Terry 偏好模型的损失函数训练的模型。


其思想是:如果你有两个东西,并试图对它们进行两两比较,你可以说“物品 i 优于物品 j”的概率与它们之间的概率比有关。你可以直观地看出,如果它们概率相同,那么胜算各半;当然,如果 i 的概率上升,那么它成为首选的可能性就更大。
我们通常不考虑概率,而是考虑 log 概率。因此,你会看到奖励模型的损失函数形式如下:输出 y1 的奖励与输出 y2 的奖励通过这种方式关联,意味着 y1 优于 y2 的概率是指数化后的和之比。如果你的奖励是 log 概率,这与之前的方程完全相同。
我们如何获得能给出这种输出的模型?通常,我们会取一个现有的、相对强大的语言模型。为了获得标量预测,我们会去掉语言建模头,并用某种序列分类器替换它。然后,我们在成对偏好数据上进行训练。这种数据包含一个输入和两个输出,一个被选中,一个被拒绝(或一个好,一个坏)。任务是预测 y1 还是 y2 被选中。
需要澄清的是,这只是奖励模型的一种。奖励模型可以是任何预测奖励的模型。另一种日益突出的方法是使用生成模型作为奖励模型。如果我们已经有一个相当好的模型,为什么还要这么多额外步骤?我们能不能直接问模型哪个输出更好?
这方面的研究与“使用语言模型作为评判者”的想法密切相关,因为你是在要求一个模型评判输出之间的优劣。你也可以训练生成模型来完成这个任务,例如通过监督微调或强化学习,让它们识别出偏好的例子。
你可以提供上下文示例,或者像处理任何其他语言建模任务一样进行操作。特别是在奖励建模领域,有一个有趣的文献方向是要求奖励模型证明为什么其中一个更好。就像思维链能提高性能一样,这也能提升效果。
这也意味着你可以摆脱成对偏好的概念。你可以只询问单个输出是否好,或者询问一个排名列表是否好,或者要求对一个列表进行排名。
在实践中,成对偏好仍然非常流行,因为它模仿了我们获得的一种自然反馈类型:在两个候选者之间做出选择。
生成式奖励模型的好处在于,你可以进行更多不同类型的偏好评估。你不需要训练专门的模型,甚至可以为此任务使用 API 模型。如果你的任务规范或偏好输出发生变化,你可以轻松修改一个并非为此任务训练的生成式奖励模型。
但是,生成式奖励模型也有一些缺点:它们在不同调用之间可能不一致。大多数 API 模型即使在温度为零时也是非确定性的。如果你调用奖励模型两次,第一次它告诉你 A 优于 B,第二次告诉你 B 优于 A,这在实践中没有帮助。虽然在温度为零时,你可能不会遇到非确定性问题,但更可能遇到的情况是:如果你稍微改变指令模板、模型在后台更新而你不知情,或者你交换两个输入的顺序,模型给出的偏好判断可能不一致。
这些对输入顺序和格式的敏感性是语言模型的普遍问题,并非奖励建模领域独有。但如果你使用的奖励模型也是生成模型,你需要将这些视为潜在问题或混淆因素。
生成式奖励模型的另一个奇怪之处是,奖励通常不具有传递性。如果你问奖励模型 A 还是 B 更好,它说 A 更好;然后问 B 还是 C 更好,它说 B 更好;再问 A 还是 C 更好,你并不总是会得到 A 更好的答案。这是因为模型没有“记忆”之前的调用,也可能没有在多次调用中使用相同的标准进行评分。
构建奖励数据:挑战与实践 🏗️
要进行奖励建模,我们需要生成某种偏好数据,或者生成关于哪些类型的输出是好的数据。为此,我们可以查看模型的各种输出,并尝试作为一个小组来完成这项任务。
首先是一个问题:有没有人愿意就什么是这个问题的好答案或坏答案发表意见?如果你必须为此编写偏好数据,什么是坏答案?什么是好答案?这是一个相当困难的问题。
一个更简单的问题是:“语言模型是如何工作的?”哪个输出更好?哪个更差?

这里的关键是,“什么是这个问题的好输出”是一个非常广泛的问题,但一个简单得多的问题是:“对于这个问题,两个输出中哪个更好?”
我们可以通读这些输出。我很好奇,仅凭风格,有没有人能猜出这里哪个模型是哪个?看起来都不像。Claude 不给我表情符号。我们投票吧。
这种大规模的偏好数据正是你生成偏好数据集的方式。我们已经现场看到了偏好建模的好与坏。好的一面是,“哪个更好”这个问题比“什么是好输出”这个开放性问题更容易回答。说“我喜欢 A 胜过 B”比为这个问题写一个评分标准要容易得多。
坏的一面是,即使在这个简短的例子中,文本也太长了,不滚动就无法阅读。我们可能没有足够的时间让每个人都完整阅读两个答案。因此,尽管我们投票并做出了集体决定,但我们并没有读完整个内容就投了票。在实践中,对于标注员来说,这也很常见。此外,我们也有分歧。我们有两个看起来都合理的输出,作为一个小组,我们可能没有非常强烈的偏向,部分原因可能是我们没有读完,同时也因为我们捕捉到了一些风格特征。如果你对 Anthropic 有强烈的好恶,这可能会影响你的投票。
通常,当我们进行偏好建模时,我们看的不是这样的问题。我们通常不试图挑出正确性(尽管一些偏好建模数据集确实包含诸如插入错误信息或提供过时信息等内容)。一般来说,当我们进行偏好建模(无论是为了 RLHF 还是 Best-of-N)时,我们更关注的是风格或冗长度等问题。
如果我们有这两个输出,从正确性层面看,它们在功能上是等效的,最后都给出了正确答案。但如果你想训练你的模型做更像推理或思考的事情,你会偏好输出二;如果你希望模型输出尽可能少的标记,你会偏好输出一,因为这个问题可能足够简单,不需要做所有的乘法或解释。
偏好建模也关乎强加你自己关于模型应如何与世界互动的观念。我们刚才谈到了 Anthropic,我认为它在模型应如何与世界互动方面有更连贯和详细的信息。对于诸如“我的最爱食物是什么”这样的问题,模型是否应该回答?如果被问及年龄,它是否应该告知?如果被问及身份,它是否应该说“我是一个由 Anthropic 构建的模型”?这些都是由标注数据的人,或者更上游的、编写数据标注说明的人广泛定义的偏好。
这也与“有用性”与“有害性”的概念相关。你希望模型能够回答问题,是因为你希望它有用;还是你希望模型避免可能在下游造成伤害的事情?在什么情况下它应该拒绝?如果你问如何制造炸弹,它应该拒绝吗?如果你问一个有争议的政治话题,它应该拒绝吗?如果你要求生成涉及未成年人的内容,它应该拒绝吗?这些都是语言建模公司正在做出的风险管理决策,并且很大程度上是通过训练像这样的偏好模型来实施的。
你可能看到这些被训练的另一个密切相关的地方是内容过滤。有没有人在调用模型时,因为内容过滤器而导致响应中途被切断?这些类型的过滤器通常不是内置于模型中的,而是在事后应用的,这就是为什么你会看到输出被部分切断。这是另一种奖励模型,给定一个输出或部分输出,它判断的不是“这是否是一个好输出”,而是“对于我们已决定安全或不安全的标准,这是否是一个安全的输出”。你可以应用它,这不是 Best-of-N,而是一种可以使用类似模型应用的事后过滤步骤。
偏好建模的另一个问题是,偏好模型是从人类标签生成的,因此反映了标注者的偏见。如果我们要在这两个开头相同的输出中做决定,有没有人对哪一个更好有意见?在实践中,特别是如果标注说明不是 100% 全面,人们往往偏好更长的输出。如果两个输出不完全相同但有些相似,这里存在某种人类认知偏差:标注者倾向于将更长的输出描述为更详细或更全面。如果你是为“有用性”做标注,详细和全面听起来是相当合理的属性。
但其下游影响是:虽然到目前为止我们所做的一切都不得不补偿较短的东西概率较低的问题,但奖励模型却存在相反的问题:较长的东西往往奖励更高。
即使你的输出在任何表面属性上都没有更好,甚至是错误的,只要它们更长,就更有可能获得高奖励。因此,在 RLHF 中,人们会做各种事情来纠正这一点,包括应用长度惩罚。否则,当你用某种奖励增强的目标训练模型时,你会看到输出空间变长,因此奖励上升,即使内容不一定有所改善。
我们之前简要讨论过的奖励建模的另一个问题是,奖励模型并不总是在子序列上定义良好。这对于 Best-of-N 来说不一定是问题,因为我们只是完整地采样所有这些内容然后查看。但对于使用奖励模型进行训练,或者对于内容过滤(你可能希望中途中断)来说,这就是一个问题。一个序列的奖励可能非常波动,在某些点变为负值。这可能部分是因为像“I love to walk”不是一个完整的句子,如果那是一个完整的输出,奖励会很低。但它也可能与某些表面特征有关。
你可以尝试学习关于子序列或子集的模型。事实上,像过程奖励模型这样的东西就是专门为此训练的。但一般来说,如果你的奖励模型不是为此训练的,你不应该尝试将其应用于子序列,至少在没有进行某种繁重预处理的情况下。

偏好的另一个问题是,并不总是存在有意义的两两区分。如果你让模型说出彩虹的颜色,“绿色”或“蓝色”是更好的答案吗?在很大程度上,这些是质量大致相同的模型输出。因此,成对偏好有时会在事物之间强加这种人为的排名。如果你要求标注员标注这个,不清楚他们会标注什么。因此,第一,如果你要求标注,应该允许这种情况;第二,有时检查奖励模型在这些边缘情况下是否有合理的行为是有帮助的。特别是,如果我要求一个标量奖励(比如预测绿色优于蓝色的概率在 0 到 1 之间),我希望答案在 0.5 左右,因为它们质量大致相同

浙公网安备 33010602011771号