Skip to content

26.3 推理时间缩放实验

上一节搭建了从答案提取到符号等价判定的完整评估框架。有了自动评分能力,本节正式进入推理时间缩放(Inference-Time Scaling) 的实验。核心问题是:在不重新训练模型的前提下,仅在推理阶段投入更多计算资源,能否系统性地提升模型的推理准确率?

传统的模型改进路径是训练时间缩放(Train-Time Scaling)——增大模型参数、扩充训练数据、延长训练时间。推理时间缩放则提供了一条互补路径:通过精心设计的采样策略、多轮投票和迭代改进,让一个固定的模型在推理阶段"思考得更久、更深"。

训练时间缩放与推理时间缩放的对比

本节将依次实现五种推理时间缩放方法——链式思维提示(CoT)温度缩放与 top-p 采样自一致性投票(Self-Consistency)Best-of-N 选择自改进(Self-Refinement)——并在 MATH-500 上对比它们的效果。下图给出了这些方法的全景视图:

三大类推理时间缩放方法概览


一、链式思维提示(Chain-of-Thought Prompting)

最简单也最有效的推理时间缩放方法是修改提示,引导模型在给出最终答案前先生成推理步骤。

CoT 提示:在提示末尾追加"Explain step by step"

实现非常直接——在标准提示末尾追加一句指令:

python
prompt_cot = prompt + " \n\nExplain step by step."

这一简单修改带来的效果令人惊讶。以题目"Half the value of 3x9 is x+37. What is the value of x?"为例,基座模型在标准提示下直接输出 \boxed{20}(错误),而加入 CoT 提示后,模型会逐步列出方程、消除分数、移项、求解,最终正确得到 \boxed{83}

CoT 提示之所以有效,直觉在于:模型被迫生成中间推理步骤时,每一步的输出都成为下一步的上下文,相当于在自回归生成过程中构建了一条隐式的"草稿纸"。这让模型能够解决需要多步推理的问题——而在直接给出答案时,这些中间状态无处安放。

在 MATH-500 上的实测结果显示,仅凭 CoT 提示就能将 0.6B 基座模型的准确率从 15.2% 提升到 40.6%,代价是生成时间从 10 分钟增加到约 85 分钟(因为要生成更长的推理链)。


二、温度缩放与随机采样

CoT 提示解决了"如何让模型思考"的问题,但模型的输出仍然是确定性的——在贪心解码(greedy decoding)下,同一个输入永远得到同一个输出。要实现后续的多样本投票和选择策略,首先需要让模型能够生成多样化的回答。这就引出了温度缩放(Temperature Scaling)和随机采样。

温度参数的作用

温度缩放的整体流程

回忆 LLM 的生成过程:在每一步,模型输出一个覆盖整个词表的 logit 向量 zR|V|,然后通过 softmax 转换为概率分布 P(xi)=softmax(zi)温度参数 T 的作用是在 softmax 之前对 logit 进行缩放:

P(xi)=exp(zi/T)jexp(zj/T)

不同温度对 logit 分布的影响

  • T<1 时,logit 之间的差异被放大,概率分布变得更尖锐,高概率 token 占据更大份额;
  • T>1 时,差异被压缩,分布变平,低概率 token 获得更多被选中的机会;
  • T0 时,退化为贪心解码(始终选择最高概率 token)。

代码实现非常简洁:

python
def scale_logits_by_temperature(logits, temperature):
    if temperature <= 0:
        raise ValueError("Temperature must be positive")
    return logits / temperature

从概率分布中采样

温度调整后的 logit 经过 softmax 转换为概率分布,然后通过多项式采样(multinomial sampling) 随机选取下一个 token:

从 logit 到概率再到采样

python
# 温度缩放 → softmax → 采样
rescaled_logits = scale_logits_by_temperature(logits, temperature)
probas = torch.softmax(rescaled_logits, dim=-1)
next_token = torch.multinomial(probas, num_samples=1)

温度过高时采样会过于随机("The capital of Germany is mistress"),温度过低时又缺乏多样性。实践中 T[0.5,1.0] 是一个常用范围。

Top-p 核采样

纯温度缩放有一个问题:即使在合理的温度下,词表中仍存在大量极低概率的"噪声"token。Top-p 采样(Nucleus Sampling) 通过动态截断解决了这个问题:

Top-p 采样的核心思想

算法步骤如下:

  1. 将所有 token 按概率降序排列
  2. 计算累积概率,找到使累积和首次超过阈值 p 的位置;
  3. 丢弃排在该位置之后的所有 token;
  4. 对保留的 token 重新归一化概率。

Top-p 完整采样流水线

python
def top_p_filter(probas, top_p):
    if top_p is None or top_p >= 1.0:
        return probas

    # 按概率降序排列
    sorted_probas, sorted_idx = torch.sort(probas, dim=1, descending=True)

    # 计算每个 token 之前的累积概率
    cumprobas = torch.cumsum(sorted_probas, dim=1)
    prefix = cumprobas - sorted_probas

    # 保留累积前缀 < top_p 的 token
    keep = prefix < top_p
    keep[:, 0] = True  # 至少保留一个 token

    # 截断并重新归一化
    kept_sorted = torch.where(keep, sorted_probas, torch.zeros_like(sorted_probas))
    filtered = torch.zeros_like(probas).scatter(1, sorted_idx, kept_sorted)
    return filtered / torch.sum(filtered, dim=1, keepdim=True).clamp_min(1e-12)

以"The capital of Germany is"为例,在 T=0.35 下不加 top-p 时,采样 1000 次会出现 "Berlin"(435)、""(209)、""(169) 等多个结果;加上 p=0.8 后,噪声 token 被有效过滤,只保留 "Berlin"(534)、""(249)、""(217)。


三、自一致性投票(Self-Consistency)

有了随机采样能力,就可以实现一种朴素但强大的策略——自一致性(Self-Consistency)。核心思想来自 Wang 等人 (2023) 的论文 Self-Consistency Improves Chain of Thought Reasoning in Language Models:对同一个问题多次采样,提取每次的最终答案,然后通过多数投票(Plurality Vote) 选出出现频率最高的答案。

自一致性:多次采样 + 多数投票

直觉类比:如果你对一道题不确定,独立做五次,其中三次得到 83、一次得到 22、一次得到 54,那么 83 最可能是正确答案。

自一致性的详细流程

python
from collections import Counter

def self_consistency_vote(
    model, tokenizer, prompt, device,
    num_samples=10, temperature=0.8, top_p=0.9,
    max_new_tokens=2048, seed=None
):
    short_answers = []

    # 1. 用不同随机种子采样多个回答
    for i in range(num_samples):
        if seed is not None:
            torch.manual_seed(seed + i + 1)

        answer = generate_with_top_p(
            model, tokenizer, prompt, device,
            max_new_tokens=max_new_tokens,
            temperature=temperature, top_p=top_p
        )

        # 2. 从每个回答中提取最终答案
        short = extract_final_candidate(answer, fallback="number_then_full")
        short_answers.append(short)

    # 3. 多数投票选出最频繁的答案
    counts = Counter(short_answers)
    final_answer = counts.most_common(1)[0][0]
    return final_answer, counts

在实际实验中,CoT 提示与自一致性的组合效果尤为突出。仅用 CoT 提示的准确率为 40.6%,加上 n=3 的自一致性投票后升至 42.2%n=5 时达到 48.0%n=10 时达到 52.0%——后者已经超过了推理模型在贪心解码下的 48.2%。


四、Best-of-N 选择

自一致性的投票机制虽然有效,但只利用了最终答案的信息,忽略了推理过程的质量。Best-of-N 策略引入了评分函数(Scorer),对所有生成的回答进行打分,选择得分最高的那个:

Best-of-N 与评分函数

评分函数的选择直接决定了 Best-of-N 的效果。本节介绍两类评分器:

启发式评分器

基于简单规则打分——是否包含 \boxed{} 格式、回答是否简洁等:

python
import math

def heuristic_score(answer, prompt=None, brevity_bonus=500.0):
    score = 0.0
    # 奖励包含 \boxed{} 的回答
    cand = extract_final_candidate(answer, fallback="none")
    if cand:
        score += 2.0
    # 简洁性奖励:越短的正确回答越好
    score += 1.5 * math.exp(-len(answer) / brevity_bonus)
    return score

简洁性奖励 1.5exp(len(a)/500) 是一条指数衰减曲线——短回答得到接近 1.5 的额外分数,而 2000 字符以上的回答此项几乎为零。这背后的假设是:对于数学题,能用更少步骤得出正确答案的推理路径通常更可靠

对数概率评分器

利用 token 对数概率作为模型置信度指标

更精细的方法是利用模型自身的对数概率(Log-Probability) 作为置信度指标。对于模型生成的回答序列 x1,x2,,xT,其联合对数概率为:

logP(x1,,xTW)=t=1TlogP(xtx1:t1,W)

为了避免长回答因 token 数多而被惩罚,使用平均对数概率

Score(a)=1Tt=1TlogP(xtx1:t1,W)
python
@torch.inference_mode()
def avg_logprob_score(model, tokenizer, prompt, answer, device="cpu"):
    prompt_ids = tokenizer.encode(prompt)
    answer_ids = tokenizer.encode(answer)
    full_ids = torch.tensor(prompt_ids + answer_ids, device=device)

    logits = model(full_ids.unsqueeze(0)).squeeze(0)
    logprobs = torch.log_softmax(logits, dim=-1)

    # 只对回答部分的 token 计分(排除提示部分)
    start = len(prompt_ids) - 1
    end = full_ids.shape[0] - 1
    t_idx = torch.arange(start, end, device=device)
    next_tokens = full_ids[start + 1 : end + 1]
    answer_logprobs = logprobs[t_idx, next_tokens]

    return torch.mean(answer_logprobs)

注意两个关键设计选择:(1)只对回答部分计分,排除提示 token——因为提示的 logprob 对所有候选回答都相同,不提供区分信息;(2)使用 log_softmax 而非先 softmax 再取 log,前者在数值上更稳定。

在 MATH-500 上,Best-of-N (n=3) 配合对数概率评分器达到了 43.2% 的准确率,优于启发式评分器的 40.6%。


五、自改进(Self-Refinement)

前面的方法都是独立采样——每次生成互不相关。自改进(Self-Refinement)则引入了迭代反馈机制:让模型审视自己的回答、指出问题、然后修正。

自改进的三步循环

整个流程分为三步:

第一步:生成初始回答(Draft)。用标准的 CoT 提示生成第一版回答。

第二步:生成批判(Critique)。将原始问题和初始回答组合成批判提示,要求模型找出逻辑错误、遗漏步骤或计算失误:

python
def make_critique_prompt(raw_prompt, draft):
    return (
        "You are a meticulous reviewer. Identify logical errors, "
        "missing steps, or arithmetic mistakes. If the answer seems "
        "correct, say so briefly. Then propose a concise plan to fix issues.\n\n"
        f"Question:\n{raw_prompt}\n\n"
        f"Draft answer:\n{draft}\n\n"
        "Write a short critique and bullet-point fix plan "
        "(under ~120 words).\nCritique:"
    )

第三步:生成修订版(Revised Answer)。将批判结果反馈给模型,要求基于批判修正回答:

python
def make_refine_prompt(raw_prompt, draft, critique):
    return (
        "Revise the answer using the critique. Keep it concise and "
        "end with a final boxed result: \\boxed{ANSWER}\n\n"
        f"Question:\n{raw_prompt}\n\n"
        f"Previous answer:\n{draft}\n\n"
        f"Critique:\n{critique}\n\nRevised answer:"
    )

自改进的完整流水线

完整的自改进循环将以上三步封装,并引入评分函数决定是否接受修订版:

python
def self_refinement_loop(model, tokenizer, raw_prompt, device,
                         iterations=2, score_fn=None, **kwargs):
    # 生成初始回答
    current = generate_initial_response(model, tokenizer, raw_prompt, device, **kwargs)
    current_score = score_fn(current) if score_fn else 0.0

    for it in range(iterations):
        # 生成批判
        critique = generate_critique(model, tokenizer, raw_prompt, current, device, **kwargs)
        # 生成修订版
        revised = generate_revised(model, tokenizer, raw_prompt, current, critique, device, **kwargs)
        revised_score = score_fn(revised) if score_fn else 0.0

        # 只在修订版不比当前版本差时才接受
        if revised_score >= current_score:
            current = revised
            current_score = revised_score

    return current

评分函数在此扮演"看门人"角色——没有评分函数时,模型可能在迭代中越改越差(尤其是小模型),因为批判本身也可能出错。加入评分函数后,只有改进才会被采纳。

以之前的数学题为例,初始回答 \boxed{18}(错误),经过一轮批判-修正后变为 \boxed{83}(正确),对数概率分数从 -0.855 上升到 -0.226。


六、MATH-500 综合对比

下面的表格汇总了所有推理时间缩放方法在 MATH-500(500 道竞赛级数学题)上的完整实验结果,基于 0.6B 参数量的 Qwen3 模型:

#方法模型准确率耗时
1基线(贪心解码)基座15.2%10.1 min
2基线(贪心解码)推理48.2%182.1 min
3CoT 提示基座40.6%84.5 min
4温度 + Top-p(无 CoT)基座17.8%30.7 min
5自一致性 (n=3)基座29.6%97.6 min
6自一致性 (n=5)基座27.8%116.8 min
7自一致性 (n=10)基座31.6%300.4 min
8Top-p + CoT基座33.4%129.2 min
9自一致性 (n=3) + CoT基座42.2%211.6 min
10自一致性 (n=5) + CoT基座48.0%452.9 min
11自一致性 (n=10) + CoT基座52.0%862.6 min
12自一致性 (n=3) + CoT推理55.2%544.4 min

MATH-500 上各方法的准确率对比

从这张表中可以读出几个关键结论:

1. CoT 是性价比最高的方法。仅一行提示修改就把准确率从 15.2% 提升到 40.6%,是所有方法中收益/成本比最大的。

2. 随机采样本身不提升准确率。第 4 行(17.8%)与第 1 行(15.2%)相差甚微——单纯引入随机性没有意义,关键是如何利用多样性。

3. 自一致性需要 CoT 配合。不加 CoT 时,自一致性的收益有限(第 5-7 行,最高 31.6%);加上 CoT 后效果显著提升(第 9-11 行,最高 52.0%)。原因是:没有推理链时,不同样本的错误高度相关(模型倾向于犯同一类错误),投票无法纠错。

4. 更多样本并非总是更好。第 6 行(n=5, 27.8%)反而低于第 5 行(n=3, 29.6%),说明在没有 CoT 引导时,增加样本数可能引入更多噪声。但在有 CoT 时,准确率随 n 单调递增。

5. 推理时间缩放可以弥补训练差距。基座模型 + 自一致性 (n=10) + CoT 的 52.0% 超过了推理模型贪心解码的 48.2%——这意味着适当的推理时间策略可以让一个未经推理训练的模型达到甚至超越经过专门训练的模型。

自改进方法的完整 MATH-500 结果也值得关注:

#方法评分器迭代次数模型准确率
1自改进1基座25.0%
2自改进2基座22.0%
3自改进启发式1基座21.6%
4自改进对数概率1基座21.4%
5自改进1推理56.6%
6自改进启发式1推理57.8%

自改进在基座模型上效果有限(最高 25.0%),因为 0.6B 参数量的基座模型自身的批判能力不足以可靠地发现错误。但在推理模型上,自改进 + 启发式评分将准确率从 48.2% 提升到 57.8%,证明了该方法在模型能力足够时确实有效。


七、方法选择指南

基于以上实验结果,可以总结出一套实用的方法选择策略:

计算预算极低时:优先使用 CoT 提示。零额外计算开销,仅修改提示文本,即可获得最大单次收益。

计算预算中等时:CoT + 自一致性 (n=3-5)。三到五次采样的投票在成本和准确率之间取得了良好平衡。如果存在平票情况,用对数概率评分作为 tie-breaker。

计算预算充足时:CoT + 自一致性 (n=10+) 或 Best-of-N + 对数概率评分。前者在大量样本时表现更稳健,后者在样本数较少时更高效。

模型能力较强时:可以考虑自改进,尤其是配合启发式评分函数。但对于小模型,自改进的收益不如自一致性稳定。

这些推理时间缩放技术不仅适用于数学推理——CoT 提示、自一致性和 Best-of-N 已被广泛应用于代码生成、逻辑推理、常识问答等任务。它们提供了一种"用计算换准确率"的通用框架,与下一节将介绍的 GRPO 训练形成互补:推理时间缩放改善推理阶段的输出质量,GRPO 则从根本上提升模型的推理能力。