
Test-Time Scaling 在推理阶段通过增加计算量换取更好的答案质量这一点在 OpenAI o1、DeepSeek-R1 等模型的讨论中已经被验证过。但在工程落地时真正难的问题不是“让模型多生成几次”而是“生成很多候选答案之后如何在没有独立验证器的情况下挑出正确答案”。Verifier-Free 的策略正好指向这个痛点不额外训练奖励模型或验证器只靠多次采样、一致性、置信度、输出结构等信号完成选择和排序。Consilience 在这里代表一个非常朴素但有效的思想多个独立证据都指向同一个结论时即使每个证据本身都不够强综合判断也比单次生成更可信。这篇文章先解释 Test-Time Scaling 和 Verifier-Free 的关系再给出一个可复现的最小实验设计用 Python 实现从采样、信号提取到共识选择的完整链路最后讨论评测指标、常见排查路径和工程落地建议。你会看到无验证器测试时扩展并不是“不做任何验证”而是把验证从“单独训练的模型”迁移到“模型自身输出的多信号共识”上。1. 理解 Test-Time Scaling 与 Verifier-Free 的核心命题1.1 Test-Time Scaling 到底在扩展什么传统的大模型能力提升主要发生在训练阶段增大参数量、扩充数据、优化目标函数、提高训练步数。这套逻辑在预训练阶段非常有效但进入后训练阶段后梯度消耗和人工数据标注的成本越来越高单纯扩大训练计算量的边际收益开始下降。Test-Time Scaling 把一部分计算量从训练阶段转移到推理阶段。常见手段包括多次采样生成多个候选答案再选择其中一个延长思维链让模型在输出最终答案前做更多推导使用搜索算法在步骤空间里做 Beam Search、MCTS 或 Best-of-N 搜索引入验证器或奖励模型为候选答案排序。这里最关键的一点是Test-Time Scaling 并不是直接提升单次生成质量而是通过增加“试错次数”或“搜索深度”来提高最终答案的置信度。它与训练阶段扩展的差别可以用一个类比说明一个学生可以在考试前通过大量刷题来提高水平也可以在考试中做完全部题目后再反向代入验算几遍或者写出两种解法互相印证。前者是训练阶段扩展后者是测试时扩展。在语言模型的语境下一个最简单的 Test-Time Scaling 策略是 Self-Consistency让模型在同一个问题上生成多个推理路径然后对最终答案做多数投票。这个策略不依赖额外模型也不修改生成概率因此它是 Verifier-Free 甚至是“无训练”的。容易误解的地方在于Test-Time Scaling 并不保证每次都能提升准确率。当模型自身存在系统性偏差时采样再多次也只会把同一个错误答案反复出现当候选答案缺少可靠的选择信号时扩展反而会放大错误。因此研究 Test-Time Scaling 本质上是在研究两个问题如何生成多样化候选以及如何从候选中选出正确答案。前者靠采样策略后者靠选择信号。1.2 Verifier-Based 与 Verifier-Free 的差异Verifier-Based 的做法很直观额外训练一个模型输入问题、候选答案或推理步骤输出一个分数然后根据分数选择最终答案。奖励模型Reward Model、结果验证器Outcome Reward Model、过程验证器Process Reward Model都属于这一类。Verifier-Free 的做法则完全不同不训练任何额外模型而是从基座模型自身的生成行为中提取信号。典型信号包括多个候选答案的重复程度每个候选答案的归一化生成概率或对数似然模型输出中是否包含完整步骤、边界符号或清晰结论对输入做轻微改写后答案是否保持稳定同一候选答案在多次生成中是否稳定复现。下面用表格对比两类方法维度Verifier-BasedVerifier-Free选择信号来源独立训练的打分模型基座模型输出中提取的一致性、概率、结构特征额外训练成本需要标注数据、训练验证器基本不需要主要增加采样次数推理开销主模型生成 验证器打分主模型多次生成 聚合计算任务迁移能力验证器需要重新训练或微调聚合策略通常可以直接跨任务使用主要风险验证器偏差、过拟合、分布外失效信号不可靠时会把错误答案误判为正确答案典型使用场景对精度要求高、预算充足的离线任务快速验证、低成本部署、缺乏标注数据的场景两者不是互斥关系。实际工程中可以用 Verifier-Free 信号做初筛再用一个轻量验证器对少量候选做精排既降低训练成本也避免验证器在分布外场景中直接崩溃。Verifier-Free 之所以重要是因为奖励模型和验证器的训练数据很难标注。尤其在数学、代码、复杂推理任务上标注一个“答案是否正确”容易但标注“每一步是否正确”非常昂贵而且标注员之间的一致性也有限。去掉独立验证器意味着去掉了这部分标注和训练成本代价是必须设计更可靠的选择信号。1.3 Consilience 的含义多个信号汇聚成共识Consilience 原本是科学哲学中的术语指的是不同来源、不同类型、相互独立的证据汇聚到同一个结论时这个结论的可信度会显著上升。地质学中化石记录、地层结构、同位素测年、磁极倒转序列分别独立地支持同一套地质年代框架就是 Consilience 的典型例子。在无验证器测试时扩展中Consilience 的思想非常契合。我们不再依赖一个强验证模型而是收集多个弱信号让它们互相印证答案一致性多个采样结果里同一个答案出现次数越多越有可能是正确答案概率信号模型在生成该答案时给出的平均对数似然越高说明模型内部对该答案的“把握”越大结构完整性包含完整推导步骤的候选答案往往比直接给结论的答案更可验证稳定性对问题做轻微改写后答案仍然不变说明该候选不是偶然生成的内部一致性推理步骤之间没有矛盾过程和结论对得上。把这些信号融合起来而不是只依赖其中某一个是 Consilience 引入工程实践后的核心价值。单一信号都有明显缺陷多数投票会被系统性错误带偏对数似然会偏爱短答案结构完整性会让废话多的模型占便宜。但当多个独立信号同时指向同一个候选答案时这些噪音叠加的概率会下降。需要强调的是Consilience 聚合不是简单地把分数相加。不同信号之间存在相关性和尺度差异设计聚合器时需要先做归一化、去相关再考虑加权或排序。否则一致性信号很容易和别的信号重复计算导致聚合结果等同于只是“投票”。2. 从任务选型到最小实验设计无验证器扩展的完整链路2.1 为什么选自然语言推理或数学题作为验证任务无验证器测试时扩展并不适合所有任务。要观察“采样次数和选择策略对答案质量的影响”首先要保证任务有明确可判定的正确答案。否则你无法区分“模型没答对”和“我们不知道答案是否正确”。下面三类任务适合做验证数学题GSM8K、MATH 等数据集的答案通常是一个数字、一个表达式或一个简短字符串方便自动判定。自然语言推理给定前提和假设输出 entailment、contradiction 或 neutral答案空间有限。多选常识推理答案在 A/B/C/D 之间选择准确率统计非常直接。数学题是最常用的选择因为它同时具备两个特点第一答案可精确比较第二推理链可以很长模型可能在最终结果正确但中间步骤混乱这有助于观察“选择信号是否只看结果”。在实验中我会默认使用类似 GSM8K 的数学应用题格式。你完全可以用其他数据集替换只需要保证能对最终答案做归一化比较。下面是实验设计目标固定一个基座模型对每个测试问题采样 N 个候选答案设计不同的选择策略包括贪心、多数投票、置信度加权、Consilience 组合统计各策略的选择准确率和每个问题消耗的平均 token 数。2.2 设定采样预算与信号收集采样预算直接决定实验成本。需要设置的参数包括候选数 N、采样温度、top-p、最大输出长度和并发数。采样温度是影响候选多样性的关键参数温度接近 0输出接近 greedy 解码多样性低多数投票退化为单次采样温度在 0.7 到 1.0 之间输出多样性明显但错误答案也会增多温度超过 1.2输出可能变得混乱候选答案之间的一致性大幅下降。在实际项目中不要只用一个温度采样。可以尝试“温度退火”策略前一部分采样用 0.8 到 1.0 得到多样性后一部分用 0.3 到 0.5 保留更稳定的高质量候选。这个策略能缓解“一致性票数被低质量候选主导”的问题。在生成每个候选答案时除了文本本身还需要收集以下元信息{ question_id: demo_0001, candidates: [ { index: 0, text: Let x be the number... Therefore, the answer is 42., answer: 42, logprob_sum: -18.32, token_count: 86, has_step_structure: true, self_consistency_count: 3 } ] }字段含义answer从文本中解析出的最终答案必须经过归一化否则“42”和“42.0”会被当作不同答案logprob_sum生成该候选时所有 token 对数似然之和用于计算平均对数似然token_count生成 token 数量用于归一化长度偏差has_step_structure是否包含解题步骤这里可以用是否出现换行、“步骤”“因此”等结构符号判断也可以按业务规则自定义self_consistency_count该答案在 N 次采样中出现的次数。2.3 Consilience 聚合器的设计思路Consilience 聚合器的任务是把多个信号组合成一个最终选择分数。下面给出一个逐步实现思路。第一步答案归一化。数学任务的答案要统一格式比如把$42$、42.0、42都映射为同一字符串。自然语言任务可以先用语义相似度做近义判断但代价更高不推荐在第一版实验中使用。第二步计算一致性分数。最简单的方式是统计每个归一化答案的出现次数除以总采样数。这个分数天然在 0 到 1 之间不需要额外归一化。第三步计算置信度分数。取每个候选答案的 token 平均对数似然。为了避免长度偏好可以只对答案部分 token 计算也可以对所有 token 平均后做 min-max 归一化。第四步计算结构完整性分数。根据业务规则判断候选是否包含推理步骤。这个分数只是 0 或 1但在长文本生成任务中很有效。第五步把多个分数组合成最终排序。最简单的组合方式是加权和final_score ( alpha * consistency_score beta * normalized_logprob_score gamma * structure_score )alpha、beta、gamma 需要在一个小的验证集上离线调整不要直接在生产环境中动态调。此外如果候选答案数量较多还可以先按答案聚类再把投票数和置信度放到聚类级别做聚合。这样能避免同一道题里有 10 个文本不同但语义相同的答案被拆成多个簇导致票数分散的问题。2.4 实验矩阵与对照设计无验证器测试时扩展最容易犯的错误是只跑一种策略然后直接下结论。科学做法是固定所有条件只改变“选择策略”这一个变量。推荐至少跑以下四组对照Greedy温度设为 0只生成一次作为基线多数投票采样 N 次对归一化答案做硬投票置信度加权投票采样 N 次按每个答案的平均对数似然加权投票Consilience 聚合采样 N 次综合一致性、置信度、结构完整性三个信号再选择。每个策略都在同一个测试集、同一个基座模型、同一组随机种子下运行。记录以下信息选择准确率平均生成 token 数总耗时每个问题被选中的答案分布。如果多个策略准确率接近应该优先选择实现简单、可解释性强、运行成本低的方案。不要为了“炫效果”引入复杂聚合器除非它带来了明确的准确率提升。3. 用 Python 实现最小闭环从采样到共识选择3.1 环境依赖这一节给出一个参考实现。它不绑定特定云平台也不依赖特定模型 API。你可以把它改造成 transformers、vLLM、OpenAI 兼容接口或其他推理服务的调用方式。import json import random import numpy as np from collections import defaultdict如果使用本地 Hugging Face 模型需要安装依赖pip install transformers torch如果使用兼容 OpenAI 的远程推理服务则安装pip install openai不同推理服务返回 logprobs 的字段不同落地前先确认接口文档。下面示例假设已经完成了模型加载并封装了一个generate_candidate函数。3.2 实现采样函数采样函数需要接受问题文本、采样参数和候选编号返回文本和元信息。def sample_single_answer( model, tokenizer, question, temperature0.8, top_p0.9, max_new_tokens512 ): messages [ { role: user, content: ( Solve the following problem step by step. At the end, write ANSWER: result.\n\n fProblem: {question} ) } ] inputs tokenizer.apply_chat_template( messages, return_tensorspt, add_generation_promptTrue ) outputs model.generate( inputs, temperaturetemperature, top_ptop_p, max_new_tokensmax_new_tokens, do_sampletemperature 0, return_dict_in_generateTrue, output_scoresTrue, renormalize_logitsTrue, ) generated_ids outputs.sequences[0][inputs.shape[-1]:] generated_text tokenizer.decode(generated_ids, skip_special_tokensTrue) # 这里只做大致的对数似然统计实际应按 token id 逐项计算 logprob_sum 0.0 token_count len(generated_ids) return { text: generated_text, logprob_sum: logprob_sum, token_count: token_count, }上面的代码里logprob_sum留空了因为在不同生成 API 中获取逐 token 概率的方式差异很大。学习阶段可以先只记录 token_count用“是否完整输出”和“投票一致性”做信号进入生产环境后再根据推理服务返回的 logprobs 字段补全。注意do_sample在 temperature 大于 0 时才会随机采样。如果你想保留一个温度 0 的 greedy 基线需要显式设为 False并保证输出确定。3.3 实现答案解析与信号提取答案解析是所有后续信号的基础。如果这一层写不好后面的投票和相似度计算都是错的。import re def extract_answer(text): match re.search(rANSWER:\s*(.)$, text.strip(), re.MULTILINE) if match: return normalize_answer(match.group(1)) return None def normalize_answer(raw): if raw is None: return None value raw.strip() # 去掉千分位逗号、多余空格、货币符号 value value.replace(,, ).replace($, ).strip() # 统一小数点和整数形式 try: numeric float(value) if numeric.is_integer(): return str(int(numeric)) return f{numeric:.6f}.rstrip(0).rstrip(.) except ValueError: pass return valueextract_answer的核心是要求模型在推理结束后给出一个固定格式的ANSWER:标记。这能大幅降低解析难度。如果没有这个标记比如模型只输出了一段话就需要用规则继续提取但可靠性会明显下降。结构完整性信号可以这样定义def has_step_structure(text): structural_indicators [ \n, Step, 步骤, 首先, 因此, 所以, Let, Since, Then, Therefore ] score sum(1 for indicator in structural_indicators if indicator in text) return min(1.0, score / 3.0)这个分数虽然是启发式的但在工程上可以作为“模型是否认真推导”的粗粒度代理。3.4 实现 Consilience 聚合器聚合器的输入是多个候选答案的信号列表输出是最终选择的答案。def aggregate_by_consilience(records): # 按归一化答案分组 groups defaultdict(list) for rec in records: answer rec.get(answer) if answer is None: continue groups[answer].append(rec) # 对每个答案计算三组分数 group_scores [] for answer, items in groups.items(): consistency len(items) / len(records) avg_logprob np.mean([r.get(avg_logprob, 0) for r in items]) structure np.mean([r.get(structure_score, 0) for r in items]) group_scores.append({ answer: answer, consistency: consistency, avg_logprob: avg_logprob, structure: structure, count: len(items), }) # 对 logprob 做 0-1 归一化 logprobs [g[avg_logprob] for g in group_scores] min_lp, max_lp min(logprobs), max(logprobs) for g in group_scores: if max_lp min_lp: g[norm_logprob] (g[avg_logprob] - min_lp) / (max_lp - min_lp) else: g[norm_logprob] 0.5 # 加权组合 alpha, beta, gamma 0.6, 0.3, 0.1 for g in group_scores: g[final_score] ( alpha * g[consistency] beta * g[norm_logprob] gamma * g[structure] ) group_scores.sort(keylambda x: (x[final_score], x[count]), reverseTrue) return group_scores[0][answer]这段代码的思路是先用一致性票数作为主要信号然后用归一化的对数似然修正“票数相近但平均把握不同”的情况最后用结构完整度给有清晰推导步骤的答案额外加分。alpha0.6, beta0.3, gamma0.1只是起始值必须根据数据集离线调整。如果你的实验中发现多数投票已经很好可以先跑一次alpha1, beta0, gamma0作为下限再逐步加入其他信号记录每次变化带来的准确率差异。3.5 运行与验证下面是完整流程的入口函数def run_evaluation(questions, num_samples8, temperature0.8): all_results [] for question in questions: records [] for idx in range(num_samples): sample sample_single_answer( model, tokenizer, question, temperaturetemperature ) answer extract_answer(sample[text]) records.append({ index: idx, answer: answer, avg_logprob: ( sample[logprob_sum] / sample[token_count] if sample[token_count] 0 else 0 ), structure_score: has_step_structure(sample[text]), }) final_answer aggregate_by_consilience(records) all_results.append({ question: question, final_answer: final_answer, records: records, }) return all_results运行命令示例python run_tts_consilience.py \ --dataset demo.jsonl \ --num_samples 8 \ --temperature 0.8 \ --output results.jsonl输出结果应当包含每个问题的最终答案和每个候选答案的信号明细方便后面分析。一个正常的结果示意图Strategy Accuracy Avg tokens per question greedy 72.0% 138 majority vote 78.5% 1096 confidence weighted vote 80.0% 1096 consilience aggregation 81.5% 1096示例中的准确率只是用来展示实验报告的格式不代表某个真实模型在某个真实数据集上的结果。你在复现时需要关注的是Consilience 聚合是否比单信号策略更高以及准确率提升是否超过了采样成本带来的“算力红利”。4. 评测指标与实验设计陷阱4.1 用哪些指标观察收益评估无验证器测试时扩展时不能只看最终准确率。下面几个指标可以组合使用指标计算方式观察目的选择准确率被选中答案中正确的比例策略的最终效果Passk任意一个候选答案正确的比例候选池本身的质量上限覆盖率正确答案在候选池中出现的频率采样策略是否足够多样性平均 token 数所有候选 token 长度的均值衡量采样成本选择信号可靠性选择准确率与 Passk 的比值选择策略是否有效利用候选池其中“选择信号可靠性”非常关键。如果 Passk 是 90%但最终选择准确率只有 70%说明候选池里存在正确答案但选择信号没有筛出来。这时候优化聚合器的空间比继续增加采样次数更大。4.2 计算量扩展曲线的读法建议把实验结果画成曲线横轴是候选数或总生成 token 数纵轴是选择准确率。观察曲线形态单调递增且趋于平台说明当前信号够用扩增量可以继续小幅提升先升后降说明候选池质量下降选择信号被低质量候选干扰一开始就不升说明任务难度分布不适合无验证器策略或者答案归一化与解析有问题。“扩展曲线拐点”很重要。当你发现从 16 个候选增加到 32 个候选准确率只提升 0.5%而耗时翻倍就应该停止继续扩展。这在实际部署中直接对应成本控制策略。4.3 策略失效的三种典型信号第一种多数投票变差。这说明模型的生成分布有系统性偏差比如某个错误答案因为常见误解而高频出现。这种情况下一致性信号不再可靠。第二种置信度与正确率负相关。高 logprob 的答案反而是错误答案。常见原因是模型对“流畅但错误”的生成给出高概率尤其在训练数据里经常出现类似错误模式时。第三种各信号相互冲突。比如投票最多的答案结构分数低置信度最高的答案票数少。这种冲突说明信号之间并非独立需要检查是否在计算时重复使用了同一信息。遇到这些情况不要盲目调整权重而是先画散点图分析各信号与正确率的关系。必要时减少信号种类只保留与正确率相关性较高且彼此独立的信号。5. 排查路径与常见坑5.1 多次采样后答案不提升反而更差现象采样数从 1 增加到 8准确率没有上升甚至下降。常见原因答案归一化失败同一个正确答案被拆成多个文本形式导致投票分散温度设置过高候选答案大部分是噪声模型本身能力不足所有正确候选都被淹没在错误答案中。检查方式打印每个问题的候选答案列表确认归一化后的答案分布计算 Passk如果 Passk 高但选择准确率低问题在选择信号如果 Passk 本身就低问题在采样策略或模型能力。解决方案提高解析和归一化质量降低温度到 0.6 到 0.8或改用温度退火改用更强的基座模型。5.2 一致性信号被“空泛文本”欺骗现象模型多次生成同一段文字但这段文字根本没有给出明确答案却被投票选中。原因extract_answer没有匹配到ANSWER:标记返回 None或者多个候选都输出了相同但无实质内容的推理过程。检查方式grep answer: null results.jsonl | head处理建议解析不到答案时不要直接丢弃而是记录为无效候选对 None 占比设一个上限比如超过 30% 时报警在 prompt 里再次强调必须输出ANSWER:标记必要时用 few-shot 示例约束。5.3 置信度信号容易偏高或偏低现象置信度加权投票不如普通多数投票。原因平均对数似然会被长序列稀释长度更短的候选即使答案错误也可能获得更高平均分。检查方式统计正确与错误候选的平均 token 数量看是否存在显著差异。处理建议只对答案部分 token 计算对数似然而不是对整个生成序列加入长度惩罚项在离线验证集上重新拟合加权系数。5.4 计算成本失控现象候选数从 8 增加到 16准确率只涨了 0.5%但 GPU 时间翻倍。处理建议设置扩展停止条件准确率增量小于阈值时停止控制并发和批量大小对困难问题动态分配采样次数简单问题用少数候选生产环境设置总 token 预算上限超出后直接返回当前最优答案。5.5 数据泄露导致的假提升现象同一条问题在训练数据中出现过模型记住了答案无论用哪种策略都能答对。检查方式随机抽 20 个测试问题改为相似但数值不同的变体重新评估观察模型是否直接输出记忆中的答案而不是推导过程。处理建议使用公开数据集的官方测试集划分在使用测试集前先做去重对于学术实验尽量选与训练分布差异更大的测试集。6. 最佳实践与扩展方向6.1 无验证器 TTS 的工程落地清单把实验从“跑通”提升到“可上线”建议按以下清单检查答案归一化方案是否覆盖了同一问题的所有合理表达采样参数是否固定随机种子是否可控多个信号是否都有离线验证过的相关性记录聚合权重是否在留出集上调整过而不是拍脑袋设定无效候选answer 为 None的比例是否被监控每个问题的 token 预算是否有上限是否记录了 Passk 与选择准确率的差值用于判断信号质量是否在模型版本变化后重新评估策略而不是沿用旧权重生产环境是否具备日志采样能力能回放任一问题被拒绝或选择失败的过程。6.2 从无验证器到轻量验证器的过渡路径如果你发现 Consilience 信号已经无法再提升准确率可以考虑引入轻量验证器。推荐路径是先用无验证器策略生成大量候选答案和标签把选择结果作为弱标签训练一个小型排序模型用这个小模型替代聚合器中的部分信号或者作为最后一道精排。这样做的好处是小模型只在候选集上运行推理成本远低于让大模型再生成一次答案。而且由于训练数据来自真实生成分布分布外问题比直接使用公开奖励模型要少。6.3 可以继续深入的几个方向步骤级一致性把答案一致性从“最终答案”扩展到“推理步骤”通过步骤对齐判断多个候选是否在过程上一致过程奖励模型蒸馏将 Consilience 信号作为软标签训练一个过程打分模型自适应采样根据问题难度动态调整候选数简单题少采样、难题多采样更强的信号融合例如将文本向量、代码执行结果、工具返回结果都纳入 Consilience 体系与在线强化学习结合把 test-time 选择信号作为 reward训练模型在推理阶段更主动地给出可验证的中间步骤。回到本文最核心的判断Verifier-Free Test-Time Scaling 的可行性建立在“模型自身输出中包含大量可用于验证的弱信号”这个前提上。Consilience 的价值不是替代所有验证器而是先善用这些免费信号。对于大多数中小团队和缺乏标注数据的业务场景这通常是最快、最稳妥的推理增强路径。建议你从一个开源模型、一个小规模数学或推理数据集开始先复现多数投票的提升效果再逐步叠加置信度、结构完整度和稳定性信号最后形成适合自己业务的选择策略。