2.4 推理与采样策略:模型如何"思考"
承前:在上一节中,我们完整走过了大模型训练的全流程——从预训练阶段让模型"读万卷书"学习语言规律和世界知识,到后训练阶段通过 SFT 和 RLHF 将"续写机器"变成"有用的助手"。我们了解了训练如何通过前向传播、反向传播、梯度更新来调整模型中数千亿个参数。但训练只是故事的一半。当模型训练完毕、参数固定下来之后,我们到底怎么用这个模型来生成文本? 给它一个提示词,它是如何一个字一个字地"吐"出回答的?为什么同一个问题,每次回答可能不同?为什么有时候模型回答得很有创意,有时候又很保守?这些问题的答案,都藏在本节要讨论的主题里——推理与采样策略。
从训练到推理:模型在做什么
在正式讨论各种采样策略之前,我们先建立一个清晰的认知框架。
在训练阶段,模型做的是"学习"——前向传播计算损失,反向传播更新参数,日复一日地把数万亿 token 的知识压缩进权重矩阵里。这个过程动辄持续数周,消耗巨额算力。
而推理阶段(也叫生成阶段),模型做的是"输出"。此时所有参数已经冻结,不再更新。模型只做一件事:根据输入文本,预测下一个 token 是什么。然后把这个 token 拼到输入后面,再预测下一个,如此往复,直到生成完整的回答。
这个"一次预测一个 token"的过程,可以用下面的流程来理解:
输入: "今天天气"
↓ 模型前向传播
输出概率分布: [真: 0.4, 很: 0.3, 不: 0.2, 晴: 0.1]
↓ 选择策略决定取哪个 token
选了 "真"
↓ 拼到输入后面
新输入: "今天天气真"
↓ 重复以上过程
输出概率分布: [好: 0.5, 热: 0.3, 冷: 0.2]
↓
选了 "好"
↓
最终输出: "今天天气真好"注意模型输出的并不是一个确定的 token,而是一个概率分布——词表中每个 token 都有一个对应的概率值。比如在"今天天气"后面,"真"的概率是 0.4,"很"是 0.3,"不"是 0.2,"晴"是 0.1,剩下成千上万个 token 各自分到极小的概率。
那么问题来了:从这个概率分布中,到底选哪个 token? 这个"怎么选"的决策,就是采样策略要回答的问题。不同的选择方式,直接决定了模型输出的风格、质量和多样性。这就是本节的核心议题。
让我们逐一认识主流的几种采样策略。
Greedy 解码:永远选最高分
最简单的选择策略就是 Greedy(贪心)解码:每一步都选概率最高的那个 token。
以上面的例子来说,"今天天气"后面概率最高的是"真"(0.4),那就选"真"。接着"今天天气真"后面概率最高的是"好"(0.5),那就选"好"。最终得到"今天天气真好"。
Greedy 的逻辑非常直白:
输入: "今天天气"
↓
模型输出概率: [真:0.4, 很:0.3, 不:0.2, 晴:0.1]
↓
选择: "真" (最高概率)
↓
序列: "今天天气真"
↓
继续预测: [好:0.5, 热:0.3, 冷:0.2]
↓
选择: "好"
↓
最终: "今天天气真好"优点:速度快,结果确定性强——同样的输入永远得到同样的输出,方便调试和复现。
缺点:贪心策略天生短视。它只看眼前这一步的概率最高,却不管这条路走到后面是不是最优的。这就像下棋只看眼前一步就选最大收益的那步走,而不考虑对手后续的应对。更致命的是,Greedy 解码非常容易陷入重复循环——模型不断生成相同的内容,比如"我我我我我我"或者"这是一个很重要的问题,这是一个很重要的问题,这是一个很重要的问题"。
在早期的小模型时代,Greedy 解码用得不少。但在现代大模型应用中,它已经很少作为默认策略了。不过在代码生成、数学推理等需要确定性和逻辑性的场景中,设置极低的 temperature(如 0.0)来逼近 Greedy 效果,仍然是一种常见做法。
Beam Search:下棋推演多步
既然 Greedy 的毛病在于"只看一步",那我们能不能多看几步?Beam Search(束搜索)就是这种思路的产物。
下棋类比:如果你下过国际象棋或围棋,应该知道一个道理——一步看起来很好的棋,可能在三步后被对手将死。好的棋手不会只看眼前这一步,而是在脑子里推演多个后续局面(branch),从中选出综合最优的那条路径。Beam Search 做的就是这件事:不只考虑当前一步选什么,而是同时维护多条候选路径,在每一步扩展时保留综合概率最高的若干条,最后从这些路径中选出最优的整条序列。
具体来说,Beam Search 会设定一个 beam size(束宽),比如 beam size = 3,意味着同时维护 3 条最可能的候选序列。每一步,模型对每条候选路径都预测下一个 token 的概率分布,然后从中选出概率乘积最高的 3 条继续往下走。
我们用一个具体例子来看:
Beam Size = 2:
Step 1: "今天天气" -> Top-2 候选: ["真"(0.4), "很"(0.3)]
Step 2:
"今天天气真" -> Top-2: ["好"(0.2), "的"(0.1)]
"今天天气很" -> Top-2: ["好"(0.15), "热"(0.08)]
4 条候选序列的累积概率:
"今天天气真好" (0.4 × 0.2 = 0.080)
"今天天气真的" (0.4 × 0.1 = 0.040)
"今天天气很好" (0.3 × 0.15 = 0.045)
"今天天气很热" (0.3 × 0.08 = 0.024)
保留 Top-2:
"今天天气真好" (0.080) ✓
"今天天气很好" (0.045) ✓
Step 3: 继续扩展...可以看到,"今天天气真热"虽然单步概率尚可,但累积下来不如"今天天气很好",于是被淘汰了。这正是 Beam Search 的优势——通过全局视角避免局部最优的陷阱。
优点:比 Greedy 更优,能够生成更通顺、更连贯的文本,常用于机器翻译、文本摘要等对准确性和流畅度要求高但对多样性要求不高的任务。
缺点:
- 计算量大——每一步要扩展 beam size × vocab_size 条路径,内存和时间消耗都是 Greedy 的 beam size 倍。
- 生成文本容易重复和缺乏多样性——因为总是往概率最高的方向走,倾向于选择"安全"但平庸的表达。
- Beam Search 本质上是确定性算法(给定输入和 beam size,输出是固定的),因此无法用于需要多样化输出的场景。
在现代对话型大模型中,Beam Search 的使用已经减少了。OpenAI 的 GPT 系列和大多数主流聊天模型默认不使用 Beam Search,而是使用下面要讲的 Top-K / Top-P 采样。
Temperature 温度:调节模型的"冒险程度"
在讲 Top-K 和 Top-P 之前,我们先理解一个更基础的参数——Temperature(温度)。它是理解后续采样策略的基础。
模型对每个 token 输出的原始数值叫 logit,经过 softmax 函数转换成概率。Temperature 就是插在 softmax 之前的一个除数,用来调节概率分布的"锐度"(sharpness):
原始 logits: [2.0, 1.0, 0.5, 0.1]
↓ 除以 Temperature T
缩放后的 logits: [2.0/T, 1.0/T, 0.5/T, 0.1/T]
↓ softmax
概率分布调酒类比:想象你在一家酒吧点鸡尾酒。调酒师问你要多"冒险"的口味。
低温(temperature = 0.1):你是一个非常保守的客人。你只点菜单上最经典的酒,比如永远点 Mojito。如果菜单上 Mojito 的描述得分为 95 分,第二名是 60 分,低温会把这个差距拉得更大——你几乎 100% 会点 Mojito。低温让概率分布变得尖锐,最高概率的 token 会获得更大的优势,模型变得非常"确定"。
常温(temperature = 1.0):你按菜单上的描述来选,分数高的更可能被选,但也会考虑其他选项。概率分布保持原样不变。
高温(temperature = 1.5):你是一个喜欢冒险的客人。你想试试冷门酒款,也许会点一杯菜单上不常见的 concoction。高温让概率分布变平,原来概率很低的 token 也有相当的机会被选中,模型的输出变得更加"随机"和"有创意",但也更容易跑偏。
用数学公式来表达,Temperature 的作用是:
$$p_i = \frac{\exp(\text{logit}_i / T)}{\sum_j \exp(\text{logit}_j / T)}$$
- 当 T → 0:概率分布变得极度尖锐,最高分的 token 概率趋近于 1,效果等同于 Greedy 解码。
- 当 T = 1:不改变原始概率分布。
- 当 T → ∞:概率分布变得极度平坦,所有 token 概率趋近于相等,采样变成均匀随机。
下面这个表格汇总了不同温度区间在实际应用中的效果:
| T 值范围 | 效果 | 适用场景 |
|---|---|---|
| 0.0 ~ 0.3 | 确定性强,重复性高,输出稳定 | 代码生成、数学推理、事实问答 |
| 0.5 ~ 0.8 | 平衡创造性与准确性 | 通用对话、客服问答、日常工作助手 |
| 0.9 ~ 1.2 | 创造性高,多样性好 | 创意写作、头脑风暴、故事生成 |
| 1.5+ | 随机性强,内容可能不合理 | 极少使用,除非需要刻意制造"荒诞"效果 |
需要特别注意的是,Temperature 本身并不改变候选 token 的集合——它只是在已有概率分布上做"拉伸"或"压缩"。真正决定"考虑哪些 token"的是 Top-K 和 Top-P,我们接下来就来认识它们。
Top-K 采样:只看前 K 个
Top-K 采样是一种"截断"策略。它的思路简单明了:从词表中所有 token 的概率分布里,只保留概率最高的 K 个,其余的全部丢弃,然后在这 K 个里面按概率随机采样。
用一个具体数字来理解。假设模型的词表有 10,000 个 token(实际的大模型词表通常在 50,000 到 100,000 之间,这里简化)。模型在预测下一个 token 时,会对这 10,000 个 token 各给一个概率。排在前面的概率高,排在后面的概率极低。
当你设置 Top-K = 50 时,意味着:
- 模型先对 10,000 个 token 按概率从高到低排序。
- 只保留概率最高的前 50 个 token,其余 9,950 个 token 的概率直接设为 0(被丢弃)。
- 对这 50 个 token 的概率做重新归一化(renormalize),让它们的概率加起来等于 1。
- 从这 50 个 token 中按归一化后的概率随机采样一个。
词表: 10,000 个 token
K = 50
Step 1: 模型输出所有 token 的概率
[真:0.15, 很:0.12, 不:0.08, 好:0.07, 热:0.06, ... 第50名:0.002, 第51名:0.001, ... 第10000名:0.000001]
Step 2: 只取 Top-50
Top-50: [真:0.15, 很:0.12, 不:0.08, 好:0.07, 热:0.06, ...]
丢弃: 第51名到第10000名(概率全设为 0)
Step 3: 重新归一化
[真:0.25, 很:0.20, 不:0.13, 好:0.12, 热:0.10, ...]
(概率被放大,使总和 = 1)
Step 4: 按概率随机采样
结果: "真"(25% 概率被选中)K 值的选择很关键:
- K 太小(如 K = 1):退化为 Greedy 解码,输出单调。
- K 太大(如 K = 10000):等于不做截断,概率极低的 token 也有机会被选到,可能生成不合理的内容。
- 常用值:K = 40~50 是经验上比较好的选择,既保留了合理的多样性,又过滤掉了低概率噪声。
Top-K 的优点是简单直观,但它有一个固有的缺陷:K 是固定的。无论概率分布是"平坦"还是"尖锐",都截取同样数量的候选 token。但在实际使用中,有时候模型非常确定(分布尖锐),此时保留 50 个候选太多;有时候模型不太确定(分布平坦),此时保留 50 个又太少。Top-P 采样就是为了解决这个问题而诞生的。
Top-P(Nucleus)采样:动态调整候选池
Top-P 采样,也称为 Nucleus Sampling(核采样),是 Top-K 的"智能化版本"。它不再固定保留 K 个候选,而是根据概率分布的形状动态决定保留多少个 token。
核心思路是:按概率从高到低累加,当累积概率超过预设的阈值 P 时停止,只在这个"核"(nucleus)内采样。
用一个具体例子来看。假设 P = 0.9,模型输出排序后的概率如下:
P = 0.9
Token 概率排序:
真: 0.35 → 累积: 0.35
很: 0.25 → 累积: 0.60
不: 0.15 → 累积: 0.75
好: 0.10 → 累积: 0.85
热: 0.08 → 累积: 0.93 ← 超过 0.9,停止!
冷: 0.04 → 不选
雨: 0.02 → 不选
... → 不选
候选池: [真, 很, 不, 好, 热] ← 只有 5 个
↓ re-normalize
↓ 随机采样在这个例子中,前 5 个 token 的累积概率达到 0.93,超过了 P = 0.9,所以候选池就是这 5 个 token。
Top-P 相比 Top-K 的优势在于"自适应性":
- 当模型非常确定时(概率分布尖锐),比如"真"一个 token 就占了 0.8 的概率,那么候选池可能只有 2~3 个 token,避免引入不相关的候选。
- 当模型不太确定时(概率分布平坦),比如 100 个 token 各占 0.01 的概率,那么候选池可能扩大到 90 个 token,保证足够的多样性。
这种"根据概率分布的形状自动调整候选池大小"的能力,使得 Top-P 在大多数场景下比固定的 Top-K 表现更好。这也是为什么 OpenAI API 中 top_p 参数的默认值是 1.0(即不做截断),但在需要控制输出时推荐设为 0.9。
温度与采样策略的组合使用
在实际应用中,Temperature、Top-K、Top-P 并非二选一,而是经常组合使用。典型的流程是:
模型输出 logits
↓
除以 Temperature(调节分布锐度)
↓
Top-K 截断(限制候选数量)
↓
Top-P 截断(动态限制累积概率)
↓
Softmax 归一化
↓
随机采样这三者的作用层次不同:
- Temperature:调节概率分布的整体"温度"——尖锐还是平坦。
- Top-K:硬性限制候选数量——最多考虑 K 个。
- Top-P:动态限制候选概率——只考虑累积概率在 P 以内的。
一个常见的组合配置是 temperature=0.7, top_k=50, top_p=0.9。这个配置的含义是:先让分布稍微尖锐一点(temperature=0.7),再限制最多只看前 50 个候选(top_k=50),然后在这个基础上只保留累积概率 90% 以内的候选(top_p=0.9),最后从中采样。
值得注意的是,OpenAI API 的官方建议是不要同时调整 temperature 和 top_p——两者作用在类似的维度上(控制随机性),同时调整容易产生意想不到的效果。通常选择其中一个来调节即可。
实战代码:用 OpenAI API 控制采样参数
理解了原理之后,我们来看如何在实践中使用这些参数。以下代码使用 OpenAI 的 Python SDK 演示不同采样参数的效果:
from openai import OpenAI
client = OpenAI() # 默认从环境变量 OPENAI_API_KEY 读取密钥
# ---------- 场景一:确定性输出(代码生成 / 数学推理) ----------
# temperature=0.0 使输出几乎确定,每次得到相同结果
response = client.chat.completions.create(
model="gpt-4o", # 使用的模型
messages=[ # 对话历史
{"role": "user", "content": "用 Python 写一个快速排序函数"}
],
temperature=0.0, # 温度设为 0,输出确定性最强
top_p=1.0, # 不做 Top-P 截断(默认值)
max_tokens=500, # 最多生成 500 个 token
seed=42 # 设置随机种子,便于复现
)
print("【temperature=0.0】\n", response.choices[0].message.content)
# ---------- 场景二:平衡创意与准确(通用对话) ----------
# temperature=0.7 是大多数对话场景的好起点
response = client.chat.completions.create(
model="gpt-4o", # 使用的模型
messages=[
{"role": "user", "content": "用通俗易懂的语言解释什么是量子纠缠"}
],
temperature=0.7, # 温度适中,平衡创意和准确
top_p=1.0, # 不做 Top-P 截断
max_tokens=500 # 最多生成 500 个 token
)
print("\n【temperature=0.7】\n", response.choices[0].message.content)
# ---------- 场景三:创意写作 ----------
# 高温度 + Top-P 截断,鼓励多样性
response = client.chat.completions.create(
model="gpt-4o", # 使用的模型
messages=[
{"role": "user", "content": "写一个关于时空旅行者的短篇小说,500字以内"}
],
temperature=0.9, # 高温度,鼓励创意
top_p=0.9, # Top-P 截断,排除低概率噪声
max_tokens=800, # 给足生成空间
presence_penalty=0.5, # 轻微惩罚已出现的 token,避免重复
frequency_penalty=0.3 # 轻微降低高频词的权重
)
print("\n【temperature=0.9, top_p=0.9】\n", response.choices[0].message.content)逐行说明关键参数的含义:
| 参数 | 含义 | 代码中取值 |
|---|---|---|
model | 调用的模型名称 | "gpt-4o" |
messages | 对话上下文,包含角色和内容 | 用户消息列表 |
temperature | 温度,控制随机性 | 0.0 / 0.7 / 0.9 |
top_p | 核采样阈值,截断累积概率超过 P 的 token | 0.9 / 1.0 |
max_tokens | 生成的最大 token 数 | 500 / 800 |
seed | 随机种子,用于复现结果 | 42 |
presence_penalty | 出现过 token 的额外惩罚,鼓励引入新词 | 0.5 |
frequency_penalty | 高频词的额外惩罚,降低重复 | 0.3 |
注意:OpenAI API 不直接暴露
top_k参数。如果要实现 Top-K 效果,可以使用top_p配合temperature来近似,或者使用 HuggingFace Transformers 等框架在本地部署模型时自行控制。
从底层实现理解采样过程
为了更深入地理解采样策略的工作原理,我们可以自己用 PyTorch 实现一个完整的采样函数。这段代码涵盖了 Temperature、Top-K、Top-P 三个步骤,帮助你理解它们在底层是如何串联起来的:
import torch
import torch.nn.functional as F
def sample_token(logits, temperature=1.0, top_k=0, top_p=0.0):
"""
从 logits 中采样一个 token
参数:
logits: 模型输出的原始未归一化分数 [vocab_size]
temperature: 温度,控制概率分布的锐度
top_k: Top-K 采样的 K 值,0 表示不使用
top_p: Top-P 采样的 P 值,0.0 表示不使用
返回:
采样的 token id
"""
# 第一步:应用 Temperature
# 将 logits 除以温度,温度越低分布越尖锐
logits = logits / temperature
# 第二步:Top-K 过滤
# 只保留概率最高的 K 个 token,其余设为 -inf(softmax 后概率为 0)
if top_k > 0:
top_k_values, _ = torch.topk(logits, top_k) # 取概率最高的 K 个值
min_top_k = top_k_values[-1] # 第 K 大的值,即门槛
logits[logits < min_top_k] = float('-inf') # 低于门槛的全部置 -inf
# 第三步:Top-P 过滤
# 按概率从高到低累积,超过 P 的部分丢弃
if top_p > 0.0:
sorted_logits, sorted_indices = torch.sort(logits, descending=True) # 降序排列
cumulative_probs = torch.cumsum( # 计算累积概率
F.softmax(sorted_logits, dim=-1), dim=-1
)
# 标记需要移除的 token:累积概率超过 top_p 的
sorted_indices_to_remove = cumulative_probs > top_p
# 右移一位:保留刚好让累积概率超过 P 的那个 token
sorted_indices_to_remove[1:] = sorted_indices_to_remove[:-1].clone()
sorted_indices_to_remove[0] = False # 第一个 token 始终保留
# 将对应位置设为 -inf
indices_to_remove = sorted_indices[sorted_indices_to_remove]
logits[indices_to_remove] = float('-inf')
# 第四步:Softmax 转概率并采样
probs = F.softmax(logits, dim=-1) # 转成概率分布
return torch.multinomial(probs, num_samples=1) # 按概率随机抽取一个
# ---------- 模拟测试 ----------
# 生成 100 个 token 的随机 logits 作为示例
logits = torch.randn(100)
# 测试不同采样策略组合
strategies = [
('Greedy (T=0)', 0.01, 0, 0.0), # 近似贪心
('Top-K=50', 0.8, 50, 0.0), # Top-K 采样
('Top-P=0.9', 0.8, 0, 0.9), # Top-P 采样
('Top-K=50 + P=0.9', 0.8, 50, 0.9), # 组合策略
]
for name, temp, k, p in strategies:
tokens = [sample_token(logits, temperature=temp, top_k=k, top_p=p).item()
for _ in range(5)] # 每种策略采样 5 次
print(f"{name:20s}: {tokens}")运行这段代码,你会看到不同策略下采样结果的差异。Greedy 每次都选同一个 token,而 Top-K / Top-P 会产生不同的结果,体现了采样带来的多样性。
可视化 Temperature 的影响
为了更直观地感受 Temperature 对概率分布的影响,我们可以用 matplotlib 画一组对比图:
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
# 模拟 5 个 token 的 logits
logits = torch.tensor([2.0, 1.0, 0.5, 0.1, 0.05])
# 测试 4 个不同的温度值
temperatures = [0.2, 0.5, 1.0, 2.0]
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
for i, T in enumerate(temperatures):
# 对每个温度,计算 softmax 后的概率分布
probs = F.softmax(logits / T, dim=-1)
axes[i].bar(range(len(probs)), probs.numpy()) # 画柱状图
axes[i].set_title(f'Temperature = {T}')
axes[i].set_xlabel('Token ID')
axes[i].set_ylabel('Probability')
axes[i].set_ylim(0, 1)
plt.tight_layout()
plt.savefig('/tmp/temperature_comparison.png')
plt.show()运行后你会看到四张柱状图:
- T=0.2:第一个 token(logit=2.0)几乎占满全部概率(接近 0.9),其余四个几乎看不见。分布极度尖锐,模型只考虑"最佳选择"。
- T=0.5:第一个 token 仍占主导(约 0.7),但其余 token 开始有一些可见的概率。分布偏尖锐。
- T=1.0:原始概率分布不变,第一个 token 约占 0.5,其余按比例分布。分布适中。
- T=2.0:所有 token 的概率差距显著缩小,接近均匀分布。模型对每个 token 的偏好差不多,输出变得非常随机。
这个可视化能帮助你快速建立对 Temperature 的直觉。
推理加速技术
到目前为止我们讨论的都是"选哪个 token"的策略,但生成过程中还有另一个重要问题:怎么生成得更快。以下几种技术是现代推理引擎的标配。
KV Cache(键值缓存)
Transformer 的注意力机制在生成每个新 token 时,都需要用到之前所有 token 的 Key(K)和 Value(V)。如果不做优化,每生成一个新 token,就要重新计算所有历史 token 的 K 和 V——这显然是巨大的浪费,因为历史 token 的 K 和 V 并没有变。
KV Cache 的做法很简单:把已经计算过的 K 和 V 缓存下来,下次直接复用,只需计算新 token 的 K 和 V。
标准 Attention(无缓存):
每生成一个 token,重新计算所有历史 token 的 K、V
时间复杂度: O(n²) per token
KV Cache:
缓存历史 token 的 K、V,只计算新 token 的 K、V
时间复杂度: O(n) per token
推理速度提升约 2~10 倍KV Cache 是现代大模型推理的基础优化,几乎所有推理引擎(vLLM、TensorRT-LLM、TGI 等)都默认开启。它的代价是占用额外的显存——对于长文本生成,KV Cache 可能占用数十 GB 的显存。
Flash Attention
标准 Attention 计算中,需要频繁在 GPU 的 HBM(高带宽显存)和 SRAM(片上缓存)之间搬运数据。当序列很长时,这个"搬运"的开销甚至比计算本身还大。
Flash Attention 通过分块计算(tiling)的方式,将注意力矩阵分成小块,每次只在 SRAM 中计算一小块,避免了中间结果在 HBM 中的频繁读写。
- 速度提升 2~4 倍
- 显存节省 10~20 倍(从 O(n²) 降到 O(n))
- 已成为几乎所有主流模型的标配组件
量化推理
量化推理是将模型参数从 FP16(16 位浮点数)压缩到 INT8 或 INT4(8 位或 4 位整数)的技术。效果是:
- 模型大小减少 2~4 倍(INT8 量化后约一半,INT4 量化后约四分之一)
- 显存占用大幅降低,使得大模型可以在消费级显卡上运行
- 常用的量化方法包括 GPTQ、AWQ、GGUF 等
- 精度损失通常 < 1%,对大多数任务影响可忽略
量化推理特别适合在资源受限的环境下部署模型,比如在个人电脑上运行 7B 或 8B 参数的开源模型。
不同任务场景的推荐参数
不同的应用场景对输出特性的要求差异很大。下面是一张参考表,汇总了常见任务场景下的推荐参数配置:
| 任务场景 | Temperature | Top-K | Top-P | 其他建议 | 说明 |
|---|---|---|---|---|---|
| 代码生成 | 0.0 ~ 0.2 | 1(或不用) | 1.0 | 设 seed 便于复现 | 代码需要确定性和正确性,不需要"创意"。低温度确保每次输出一致 |
| 数学推理 | 0.0 ~ 0.1 | 1(或不用) | 1.0 | 设 seed | 与代码类似,数学推理需要严密的逻辑,随机性会引入错误 |
| 事实问答 | 0.0 ~ 0.3 | 1(或不用) | 1.0 | — | 事实性回答需要准确,不需要发挥 |
| 通用对话 | 0.5 ~ 0.8 | 40 ~ 50 | 0.9 | — | 平衡流畅性和多样性,是大多数聊天场景的好起点 |
| 客服助手 | 0.3 ~ 0.5 | 40 | 0.9 | — | 需要专业和准确,但也不能太机械 |
| 创意写作 | 0.9 ~ 1.2 | 50 ~ 100 | 0.9 ~ 0.95 | 加 presence_penalty | 鼓励多样性和新颖表达,适当惩罚重复 |
| 头脑风暴 | 1.0 ~ 1.3 | 50 ~ 100 | 0.95 | 加 frequency_penalty | 需要大量不同的想法,鼓励发散 |
| 文本摘要 | 0.3 ~ 0.5 | 40 | 0.9 | — | 摘要需要忠实于原文,不需要创造性发挥 |
| 翻译 | 0.3 ~ 0.5 | 40 | 0.9 | 考虑用 Beam Search | 翻译需要准确,Beam Search 在此场景仍有价值 |
这张表中的数值是经验性的起始参考值,实际使用时需要根据具体模型和任务进行调整。一个实用的调参策略是:先从表格推荐的温度开始,如果输出太死板就稍微调高,如果输出太发散就稍微调低,逐步收敛到满意的效果。
常见误区
在理解了各种采样策略之后,我们来澄清几个在实践中常见的误区。
误区一:"Temperature 越高越好,输出更有创意"
这是一个非常普遍的误解。高温度确实增加了输出的多样性,但多样性不等于质量。当温度过高时,模型会倾向于选择概率很低的 token,这些 token 往往是语义上不相关甚至错误的。结果就是:输出看起来"有创意",但可能逻辑混乱、语句不通、事实错误。
正确的理解是:Temperature 是一个需要在确定性和多样性之间寻找平衡的旋钮。对于大多数任务来说,0.5~0.8 是一个合理的默认区间。只有在明确需要最大化创意(如头脑风暴、自由写作)时才使用 0.9 以上的温度,并且通常配合 Top-P 来过滤不合理的低概率 token。
误区二:"Greedy 一定是最差的策略"
Greedy 的名声不太好,主要因为它容易陷入重复循环。但在某些场景下,Greedy 其实是合理甚至最优的选择:
- 代码生成:代码的确定性比多样性更重要,你不会希望每次运行得到的排序函数都不同。
- 数学推理:数学推导需要严密的逻辑,随机性只会引入错误。
- 可复现性要求:当你需要输出可复现时(如调试、测试),Greedy 或 temperature=0 是最简单的方式。
Greedy 并非"最差",而是"最确定"。是否适合取决于你的任务需要确定性还是多样性。
误区三:"Top-P 比 Top-K 好,所以不需要用 Top-K 了"
Top-P 确实在大多数情况下比 Top-K 更灵活,但两者并非互斥。很多框架允许同时设置 Top-K 和 Top-P,效果是先做 Top-K 截断,再做 Top-P 截断。这种组合可以同时获得"硬上限"(K)和"软上限"(P)的好处,进一步约束候选池。
此外,Top-K 在某些特定场景下也有优势——比如当概率分布非常平坦时,Top-P 可能会包含太多候选 token,而 Top-K 可以确保不会超出一个安全范围。
误区四:"设置了 seed 就一定能复现结果"
虽然设置随机种子(seed)可以让随机采样过程可复现,但实际上,即使 seed 相同,结果也可能不同。原因是现代大模型的推理涉及多卡并行、浮点数计算的非确定性、CUDA 核函数的执行顺序等因素。如果你需要严格复现,最可靠的方法是将 temperature 设为 0(退化为 Greedy),此时不涉及随机采样,结果完全确定。
误区五:"Temperature 和 Top-P 作用一样,调一个就行"
虽然两者都在一定程度上控制输出的随机性,但机制不同。Temperature 是全局地改变概率分布的形状(拉伸或压缩),而 Top-P 是局部地截断候选 token 的范围。在实践中,同时大幅调整两者容易产生难以预测的效果。OpenAI 官方文档也建议:"通常改变其中一个就够了,不要同时调整 temperature 和 top_p。" 推荐的策略是根据任务先固定一个,再微调另一个。
误区六:"Beam Search 总是比采样好"
Beam Search 在翻译和摘要等"有标准答案"的任务中表现不错,但在开放式文本生成(如对话、创意写作)中,Beam Search 倾向于生成通顺但平庸、重复的内容。研究论文 The Curious Case of Neural Text Degeneration(Holtzman et al., 2020)通过实验证明,Top-P(Nucleus)采样在人类评估中显著优于 Beam Search。这就是为什么现代对话型大模型几乎不使用 Beam Search。
策略对比总结
| 策略 | 原理 | 优点 | 缺点 | 最佳场景 |
|---|---|---|---|---|
| Greedy | 每步选概率最高的 token | 速度快,确定性强 | 容易重复,多样性差 | 代码生成、确定性任务 |
| Beam Search | 维护 K 条候选路径,选最优序列 | 全局更优,通顺流畅 | 计算量大,可能重复 | 翻译、摘要 |
| Top-K | 只从概率最高的 K 个 token 中采样 | 控制候选数量,简单直观 | K 值固定不够灵活 | 通用文本生成 |
| Top-P | 动态保留累积概率超过 P 的候选 | 自适应,比 Top-K 灵活 | P 值需要调优 | 创意写作、对话 |
| Temperature | 调节概率分布的锐度 | 细粒度控制创造性 vs 准确性 | 不改变候选集合 | 调节整体风格 |
本节小结
本节我们从"训练完的模型如何生成文本"这个问题出发,系统学习了推理阶段的采样策略:
推理的核心:模型每次只预测一个 token 的概率分布,然后通过采样策略决定选哪个 token。训练时参数在更新,推理时参数冻结,只做前向传播。
五种主要策略:
- Greedy:永远选概率最高的,确定但容易重复。
- Beam Search:像下棋一样推演多步,维护多条候选路径选最优,适合翻译摘要。
- Top-K:截取概率最高的 K 个 token 中采样,简单有效。
- Top-P:动态截取累积概率超过 P 的候选池,比 Top-K 更灵活。
- Temperature:调节概率分布的尖锐程度,低温保守、高温冒险。
组合使用:这些策略可以组合,典型流程是 Temperature → Top-K → Top-P → 采样。实践中 Temperature 和 Top-P 是最常用的两个调节旋钮。
推理加速:KV Cache 避免重复计算历史 token 的 Key/Value,Flash Attention 通过分块计算加速注意力运算,量化推理(INT8/INT4)压缩模型大小降低资源需求。这三项是现代推理引擎的标配。
参数选择:不同任务需要不同的参数配置。代码生成和数学推理用低温度追求确定性,通用对话用中温度平衡流畅和多样,创意写作用高温度激发创意。关键是根据任务特性在确定性和多样性之间找到平衡。
常见误区:温度不是越高越好,Greedy 不是一定最差,Temperature 和 Top-P 不要同时大幅调整,Beam Search 在开放文本生成中不如采样策略。
启后:本节我们完整学习了模型在推理阶段如何一个 token 一个 token 地生成文本,以及如何通过 Temperature、Top-K、Top-P 等参数控制生成的风格和质量。但你可能已经注意到一个隐含的假设——我们一直说模型"根据输入文本预测下一个 token",却没有讨论一个关键限制:模型一次能"看到"多少输入文本? 如果用户给了一个 10 万字的长文档,模型能一次性全部读进去吗?答案是否定的。每个模型都有一个"上下文窗口"的大小限制,超出这个窗口的内容模型就"看不见"了。这个限制对模型的使用方式有深远影响——它决定了能处理的文本长度、对话能持续多少轮、RAG 系统能检索多少内容。下一节我们就来深入探讨上下文窗口与长文本处理这个主题。
参考资料
The Curious Case of Neural Text Degeneration (Holtzman et al., 2020) — Top-P 采样原论文 https://arxiv.org/abs/1904.09751
FlashAttention: Fast and Memory-Efficient Exact Attention (Dao et al., 2022) https://arxiv.org/abs/2205.14135
OpenAI API - Text Generation Guidehttps://platform.openai.com/docs/guides/text-generation
LLM 推理优化技术综述(知乎) https://zhuanlan.zhihu.com/p/642412124
HuggingFace Generation 文档https://huggingface.co/docs/transformers/generation_strategies