Skip to content

17.3 采样多样性控制

上一节介绍了思维链提示如何通过"让模型一步一步地思考"来提升推理准确率。然而,要让 CoT 与自一致性(Self-Consistency)等技术真正发挥威力,有一个不可或缺的前提:模型必须能够对同一个问题生成多条不同的推理路径。 如果每次生成的答案都完全相同——如贪心解码(Greedy Decoding)所做的那样——就无法通过多数投票来筛选最优答案。

这就引出了本节的核心主题:采样多样性控制。我们将依次讨论温度缩放(Temperature Scaling)、Top-k 采样和 Top-p(Nucleus)采样三种技术,理解它们如何在"确定性"与"多样性"之间精确调节,以及如何将它们组合应用于推理场景。


17.3.1 从贪心到随机:理解 Token 选择过程

在深入采样策略之前,先回顾一下模型是如何选择下一个 Token 的。给定输入序列,语言模型的最后一层会输出一个与词表大小相同的向量,称为 logits(对数值)。每个元素对应词表中某个 Token 的"得分"——得分越高,模型认为该 Token 出现在此位置的可能性越大。

下一个 Token 的选择流程:从输入到 logits 到 argmax

图 17-5:下一个 Token 的选择流程。模型接收输入序列,输出词表大小的 logits 向量,通过 argmax 选择得分最高的 Token 作为下一个输出。

贪心解码(Greedy Decoding) 是最简单的策略:直接取 logits 中最大值对应的 Token。这种策略是确定性的——相同输入永远产出相同输出。对于事实性问答等场景,这种确定性是优势;但对于需要多样化输出的推理场景,它成了障碍。

python
import torch

# 假设 logits 是模型对 9 个候选 Token 的得分
logits = torch.tensor([4.51, 0.89, -1.90, 6.75, 1.63, -1.62, -1.89, 6.28, 1.79])
vocab = ["closer", "every", "effort", "forward", "inches", "moves", "pizza", "toward", "you"]

# 贪心解码:总是选择得分最高的 Token
greedy_token = vocab[torch.argmax(logits).item()]
print(f"贪心解码选择: {greedy_token}")  # 输出: forward

随机采样(Stochastic Sampling) 则将 logits 转换为概率分布,然后按概率抽样。这引入了随机性,使每次生成的结果可能不同:

python
# 示意代码(承接上方代码块中的 logits 和 vocab)
probas = torch.softmax(logits, dim=0)

# 随机采样:按概率抽取
torch.manual_seed(42)
sampled_token = vocab[torch.multinomial(probas, num_samples=1).item()]
print(f"随机采样选择: {sampled_token}")  # 可能输出: forward 或 toward

在这个例子中,"forward""toward" 的概率分别约为 57.8% 和 36.1%,合计超过 93%。随机采样大概率选中它们之一,但也有小概率选到 "pizza" 这样的低概率 Token——这就是纯随机采样的问题:它允许非常不合理的 Token 被选中。温度缩放和 Top-k/Top-p 采样正是为了解决这一问题。


17.3.2 温度缩放(Temperature Scaling)

温度缩放的本质极为简单:在将 logits 传入 softmax 之前,先除以一个正数 T(温度参数)。 数学表达为:

pi=exp(zi/T)jexp(zj/T)

其中 zi 是第 i 个 Token 的 logit 值,T>0 为温度参数。

温度缩放对 logits 分布的影响

图 17-6:温度缩放的效果。低温(T=0.5)使 logits 差异放大,分布更尖锐;高温(T=5.0)使 logits 差异缩小,分布更平坦。

温度参数的直觉可以用"信心调节器"来理解:

温度效果类比
T0概率集中在最高分 Token,趋近贪心解码极其自信,只选"最好的"
T=1保持原始 logits 不变原样输出模型的判断
T>1概率分布变平坦,低分 Token 也有机会被选中放宽标准,愿意尝试
T趋近均匀分布,完全随机选择完全放弃偏好,随机抽签

下面通过代码可视化温度对采样分布的影响:

python
import torch
import matplotlib.pyplot as plt

def softmax_with_temperature(logits, temperature):
    """对 logits 进行温度缩放后计算 softmax"""
    return torch.softmax(logits / temperature, dim=0)

# 9 个候选词的 logits
logits = torch.tensor([4.51, 0.89, -1.90, 6.75, 1.63, -1.62, -1.89, 6.28, 1.79])
vocab = ["closer", "every", "effort", "forward", "inches", "moves", "pizza", "toward", "you"]

# 三种温度下的概率分布
temperatures = [0.1, 1.0, 5.0]
fig, axes = plt.subplots(1, 3, figsize=(14, 4), sharey=True)

for ax, T in zip(axes, temperatures):
    probas = softmax_with_temperature(logits, T)
    bars = ax.bar(range(len(vocab)), probas.numpy())
    ax.set_title(f"Temperature = {T}", fontsize=13)
    ax.set_xticks(range(len(vocab)))
    ax.set_xticklabels(vocab, rotation=45, ha="right")
    ax.set_ylim(0, 1.05)
    # 高亮最大概率的柱子
    max_idx = torch.argmax(probas).item()
    bars[max_idx].set_color("darkorange")

axes[0].set_ylabel("Probability")
plt.tight_layout()
plt.show()

运行上述代码可以直观观察到:当 T=0.1 时,"forward" 的概率接近 1.0(其他词几乎为 0);当 T=1.0 时,"forward""toward" 各占约 58% 和 36%;当 T=5.0 时,9 个词的概率趋于均匀,甚至 "pizza" 也有约 3% 的机会被选中。

推理场景中温度的选择。 在推理模型和数学推理任务中,温度的选择需要在准确性和多样性之间权衡。DeepSeek-R1 等推理模型的推荐温度为 T=0.6,这是一个经过实践验证的平衡点。在自一致性投票实验中,通常使用 T=0.8 左右——足够产生多样化的推理路径,又不至于引入过多噪声。


17.3.3 Top-k 采样

温度缩放调节了概率分布的"陡峭程度",但即使在适中的温度下,词表中数十万个 Token 中的极低概率 Token 仍有被选中的可能。Top-k 采样 通过一种简单粗暴的方式解决这个问题:只保留概率最高的 k 个 Token,将其余所有 Token 的概率置零,然后在这 k 个候选中重新归一化并采样。

Top-k 采样的流程:保留前 k 个概率最高的 Token

图 17-7:Top-k 采样示意图。对 logits 排序后只保留前 k 个,其余置为 ,经 softmax 后在缩减的候选集中采样。

python
import torch

def top_k_filter(logits, k):
    """Top-k 过滤:只保留前 k 个最大 logit,其余设为 -inf"""
    top_values, _ = torch.topk(logits, k)
    min_top_value = top_values[-1]  # 第 k 大的值
    # 将低于阈值的 logit 置为 -inf
    filtered = torch.where(
        logits < min_top_value,
        torch.tensor(float("-inf")),
        logits
    )
    return filtered

# 示例
logits = torch.tensor([4.51, 0.89, -1.90, 6.75, 1.63, -1.62, -1.89, 6.28, 1.79])

filtered_logits = top_k_filter(logits, k=3)
print("过滤后的 logits:", filtered_logits)
# 输出: tensor([4.5100,   -inf,   -inf, 6.7500,   -inf,   -inf,   -inf, 6.2800,   -inf])

probas = torch.softmax(filtered_logits, dim=0)
print("归一化概率:", probas)
# 输出: tensor([0.0615, 0.0000, 0.0000, 0.5775, 0.0000, 0.0000, 0.0000, 0.3610, 0.0000])

Top-k 的优点是实现简单、计算开销极低。但它有一个内在缺陷:k 是固定的,不会随上下文自适应调整。 当模型对下一个 Token 非常确定时(比如 "中华人民共和___" 后几乎必定接 "国"),取 k=50 会引入 49 个不必要的噪声候选;反过来,当模型面临高度模糊的上下文时(比如 "今天我想吃___"),仅取 k=3 又可能把合理选项排除在外。


17.3.4 Top-p 采样(Nucleus Sampling)

Top-p 采样(又称核采样,Nucleus Sampling)由 Holtzman et al. (2020) 在论文 "The Curious Case of Neural Text Degeneration" 中提出,优雅地解决了 Top-k 的固定候选集问题。其核心思想是:不固定候选 Token 的数量,而是固定候选 Token 的累积概率质量。

Top-p 采样的概念:按概率排序后,保留累积概率刚好超过 p 的最小 Token 集合

图 17-8:Top-p 采样的核心思想。将 Token 按概率从高到低排序,依次累加,保留累积概率首次超过阈值 p 时所包含的所有 Token。

具体步骤如下:

  1. 排序:将所有 Token 的概率从高到低排列。
  2. 累积求和:依次累加概率值。
  3. 截断:保留使累积概率首次达到或超过 p 的最小 Token 集合。
  4. 重新归一化:在保留的候选集中重新归一化概率,然后采样。

Top-p 采样的完整流程

图 17-9:Top-p 采样的完整流程。从温度缩放后的 logits 出发,经 softmax → 排序 → 累积概率截断 → 重新归一化 → 采样。

下面给出一个完整的、自包含的 Top-p 采样实现:

python
import torch

def top_p_sample(logits, temperature=1.0, top_p=0.9):
    """
    温度缩放 + Top-p 采样的完整实现。

    参数:
        logits: 模型输出的原始 logits,形状 [vocab_size]
        temperature: 温度参数,> 0
        top_p: 累积概率阈值,范围 (0, 1]
    返回:
        采样得到的 Token 索引
    """
    # Step 1: 温度缩放
    scaled_logits = logits / temperature

    # Step 2: 转换为概率
    probas = torch.softmax(scaled_logits, dim=-1)

    # Step 3: 按概率降序排列
    sorted_probas, sorted_indices = torch.sort(probas, descending=True)

    # Step 4: 计算累积概率
    cumulative_probas = torch.cumsum(sorted_probas, dim=-1)

    # Step 5: 找到累积概率超过 top_p 的截断点
    # 保留"前缀累积概率 < top_p"的所有 Token,以及恰好越过阈值的那个
    prefix_cumsum = cumulative_probas - sorted_probas  # 每个 Token 之前的累积概率
    keep_mask = prefix_cumsum < top_p
    keep_mask[0] = True  # 始终保留概率最高的 Token

    # Step 6: 将被过滤掉的 Token 概率置零
    sorted_probas[~keep_mask] = 0.0

    # Step 7: 映射回原始词表顺序
    filtered_probas = torch.zeros_like(probas)
    filtered_probas.scatter_(0, sorted_indices, sorted_probas)

    # Step 8: 重新归一化
    filtered_probas = filtered_probas / filtered_probas.sum().clamp(min=1e-12)

    # Step 9: 按概率采样
    return torch.multinomial(filtered_probas, num_samples=1).item()


# 使用示例
logits = torch.tensor([4.51, 0.89, -1.90, 6.75, 1.63, -1.62, -1.89, 6.28, 1.79])
vocab = ["closer", "every", "effort", "forward", "inches", "moves", "pizza", "toward", "you"]

torch.manual_seed(42)
for i in range(5):
    idx = top_p_sample(logits, temperature=1.0, top_p=0.9)
    print(f"第 {i+1} 次采样: {vocab[idx]}")

Top-p 的自适应特性。 Top-p 的核心优势在于候选集大小会随上下文自动调整。当模型对某个位置非常确定(如概率 0.95 集中在一个 Token 上)时,Top-p=0.9 会只保留那 1 个 Token,等效于贪心解码。当模型面临高度不确定的选择(概率分散在数十个 Token 上)时,候选集自动扩大。这种自适应行为使得 Top-p 在实践中比固定的 Top-k 更为稳健。


17.3.5 温度与 Top-p 的组合策略

在实际推理系统中,温度缩放和 Top-p 通常组合使用。它们各自控制不同层面的多样性:

  • 温度控制的是概率分布的"形状"——多尖锐还是多平坦。
  • Top-p控制的是候选集的"范围"——允许多少个 Token 参与采样。

两者的组合可以精细调控输出的确定性-多样性光谱:

组合策略温度Top-p效果适用场景
高确定性0.0-0.30.5-0.7极少变化,近似贪心事实性问答、翻译
适度多样性0.5-0.80.8-0.95生成多样但连贯推理任务自一致性投票
高创造性1.0-1.50.95-1.0高度多样,偶有惊喜创意写作、头脑风暴

在推理模型的典型使用中,常见的参数组合为 T=0.8,top_p=0.9——足够产生多条不同的推理路径,同时保持每条路径的内在逻辑一致性。

下面的代码展示了不同参数组合对采样分布的影响:

python
import torch
from collections import Counter

def sample_with_config(logits, temperature, top_p, num_samples=1000):
    """给定参数组合,统计 1000 次采样的 Token 频率"""
    counts = Counter()
    for _ in range(num_samples):
        # 温度缩放
        scaled = logits / temperature if temperature > 0 else logits
        probas = torch.softmax(scaled, dim=-1)

        # Top-p 过滤
        sorted_p, sorted_idx = torch.sort(probas, descending=True)
        cum_p = torch.cumsum(sorted_p, dim=-1)
        prefix = cum_p - sorted_p
        keep = prefix < top_p
        keep[0] = True
        sorted_p[~keep] = 0.0
        filtered = torch.zeros_like(probas).scatter_(0, sorted_idx, sorted_p)
        filtered = filtered / filtered.sum().clamp(min=1e-12)

        token_id = torch.multinomial(filtered, num_samples=1).item()
        counts[token_id] += 1
    return counts

logits = torch.tensor([4.51, 0.89, -1.90, 6.75, 1.63, -1.62, -1.89, 6.28, 1.79])
vocab = ["closer", "every", "effort", "forward", "inches", "moves", "pizza", "toward", "you"]

configs = [
    (0.3, 0.7, "低温+窄范围"),
    (0.8, 0.9, "适中(推理常用)"),
    (1.5, 0.95, "高温+宽范围"),
]

for T, p, label in configs:
    torch.manual_seed(42)
    counts = sample_with_config(logits, T, p, num_samples=1000)
    top3 = counts.most_common(3)
    summary = ", ".join(f"{vocab[tid]}={cnt}" for tid, cnt in top3)
    print(f"[{label}] T={T}, top_p={p}: {summary}")

17.3.6 推理场景中的采样实践

将上述采样策略应用于推理模型时,有几个值得注意的实践要点。

自一致性投票依赖采样多样性。 在 §17.2 中介绍的自一致性(Self-Consistency)方法,其核心前提是能通过随机采样生成多条不同的推理路径。下表展示了采样参数对 MATH-500 基准测试准确率的影响:

方法采样参数准确率耗时
贪心解码(无采样)T=015.2%10.1 min
温度+Top-p(单次)T=0.8,p=0.917.8%30.7 min
温度+Top-p + CoT + 自一致性 (n=5)T=0.8,p=0.948.0%452.9 min
温度+Top-p + CoT + 自一致性 (n=10)T=0.8,p=0.952.0%862.6 min

表 17-3:采样策略与推理准确率的关系。数据来源:Raschka (2025) 使用 Qwen3-0.6B 基座模型在 DGX Spark 上的实验。

一个重要的观察是:单次随机采样(17.8%)甚至不如贪心解码的 CoT 版本(40.6%),但当采样与 CoT 和多数投票组合时,准确率飙升至 52.0%。 这表明采样的价值不在于单次生成更好的答案,而在于通过多次采样产生多样化的候选答案,让投票机制筛选出最可靠的结果。

温度过高的风险。 如果温度设置过高(如 T>1.5),推理路径中可能出现逻辑跳跃或计算错误,导致多数投票时正确答案反而无法获得多数票。实践中的经验法则是:

  • 对需要精确计算的数学/代码任务:T[0.5,0.8]
  • 对需要创意推理的开放式任务:T[0.8,1.2]
  • Top-p 通常固定在 [0.8,0.95] 范围内

与 Top-k 的关系。 虽然 Top-k 和 Top-p 解决的是同一个问题(缩小候选集),但在现代推理系统中 Top-p 更为常用,因为它的自适应特性更适合推理过程中上下文不断变化的场景。OpenAI、DeepSeek 等主流 API 默认提供的也是 Top-p(而非 Top-k)参数。不过,两者也可以组合使用——先用 Top-k 做粗过滤,再用 Top-p 做精细截断。


本节总结

采样多样性控制是推理时间缩放的技术基础。温度缩放通过调整 softmax 前的 logits 比例来控制概率分布的尖锐程度;Top-k 采样通过保留固定数量的最高概率 Token 来排除噪声候选;Top-p 采样通过动态控制累积概率阈值实现自适应的候选集大小。在推理模型的实际应用中,温度与 Top-p 通常组合使用(如 T=0.8,p=0.9),为自一致性投票等多路径推理方法提供必要的输出多样性。下一节将深入讨论自一致性投票的完整实现和理论分析。