Skip to content

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 时,意味着:

  1. 模型先对 10,000 个 token 按概率从高到低排序。
  2. 只保留概率最高的前 50 个 token,其余 9,950 个 token 的概率直接设为 0(被丢弃)。
  3. 对这 50 个 token 的概率做重新归一化(renormalize),让它们的概率加起来等于 1。
  4. 从这 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 的官方建议是不要同时调整 temperaturetop_p——两者作用在类似的维度上(控制随机性),同时调整容易产生意想不到的效果。通常选择其中一个来调节即可。


实战代码:用 OpenAI API 控制采样参数

理解了原理之后,我们来看如何在实践中使用这些参数。以下代码使用 OpenAI 的 Python SDK 演示不同采样参数的效果:

python
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 的 token0.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 三个步骤,帮助你理解它们在底层是如何串联起来的:

python
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 画一组对比图:

python
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 参数的开源模型。


不同任务场景的推荐参数

不同的应用场景对输出特性的要求差异很大。下面是一张参考表,汇总了常见任务场景下的推荐参数配置:

任务场景TemperatureTop-KTop-P其他建议说明
代码生成0.0 ~ 0.21(或不用)1.0设 seed 便于复现代码需要确定性和正确性,不需要"创意"。低温度确保每次输出一致
数学推理0.0 ~ 0.11(或不用)1.0设 seed与代码类似,数学推理需要严密的逻辑,随机性会引入错误
事实问答0.0 ~ 0.31(或不用)1.0事实性回答需要准确,不需要发挥
通用对话0.5 ~ 0.840 ~ 500.9平衡流畅性和多样性,是大多数聊天场景的好起点
客服助手0.3 ~ 0.5400.9需要专业和准确,但也不能太机械
创意写作0.9 ~ 1.250 ~ 1000.9 ~ 0.95加 presence_penalty鼓励多样性和新颖表达,适当惩罚重复
头脑风暴1.0 ~ 1.350 ~ 1000.95加 frequency_penalty需要大量不同的想法,鼓励发散
文本摘要0.3 ~ 0.5400.9摘要需要忠实于原文,不需要创造性发挥
翻译0.3 ~ 0.5400.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 准确性不改变候选集合调节整体风格

本节小结

本节我们从"训练完的模型如何生成文本"这个问题出发,系统学习了推理阶段的采样策略:

  1. 推理的核心:模型每次只预测一个 token 的概率分布,然后通过采样策略决定选哪个 token。训练时参数在更新,推理时参数冻结,只做前向传播。

  2. 五种主要策略

    • Greedy:永远选概率最高的,确定但容易重复。
    • Beam Search:像下棋一样推演多步,维护多条候选路径选最优,适合翻译摘要。
    • Top-K:截取概率最高的 K 个 token 中采样,简单有效。
    • Top-P:动态截取累积概率超过 P 的候选池,比 Top-K 更灵活。
    • Temperature:调节概率分布的尖锐程度,低温保守、高温冒险。
  3. 组合使用:这些策略可以组合,典型流程是 Temperature → Top-K → Top-P → 采样。实践中 Temperature 和 Top-P 是最常用的两个调节旋钮。

  4. 推理加速:KV Cache 避免重复计算历史 token 的 Key/Value,Flash Attention 通过分块计算加速注意力运算,量化推理(INT8/INT4)压缩模型大小降低资源需求。这三项是现代推理引擎的标配。

  5. 参数选择:不同任务需要不同的参数配置。代码生成和数学推理用低温度追求确定性,通用对话用中温度平衡流畅和多样,创意写作用高温度激发创意。关键是根据任务特性在确定性和多样性之间找到平衡。

  6. 常见误区:温度不是越高越好,Greedy 不是一定最差,Temperature 和 Top-P 不要同时大幅调整,Beam Search 在开放文本生成中不如采样策略。


启后:本节我们完整学习了模型在推理阶段如何一个 token 一个 token 地生成文本,以及如何通过 Temperature、Top-K、Top-P 等参数控制生成的风格和质量。但你可能已经注意到一个隐含的假设——我们一直说模型"根据输入文本预测下一个 token",却没有讨论一个关键限制:模型一次能"看到"多少输入文本? 如果用户给了一个 10 万字的长文档,模型能一次性全部读进去吗?答案是否定的。每个模型都有一个"上下文窗口"的大小限制,超出这个窗口的内容模型就"看不见"了。这个限制对模型的使用方式有深远影响——它决定了能处理的文本长度、对话能持续多少轮、RAG 系统能检索多少内容。下一节我们就来深入探讨上下文窗口与长文本处理这个主题。


参考资料

  1. The Curious Case of Neural Text Degeneration (Holtzman et al., 2020) — Top-P 采样原论文 https://arxiv.org/abs/1904.09751

  2. FlashAttention: Fast and Memory-Efficient Exact Attention (Dao et al., 2022) https://arxiv.org/abs/2205.14135

  3. OpenAI API - Text Generation Guidehttps://platform.openai.com/docs/guides/text-generation

  4. LLM 推理优化技术综述(知乎) https://zhuanlan.zhihu.com/p/642412124

  5. HuggingFace Generation 文档https://huggingface.co/docs/transformers/generation_strategies