测试时计算:用并行采样与验证器提升大模型推理能力
发布时间:2026/9/8 2:39:59 锦皓数字建站

之前在做 AI 智能体项目时我一直被一个问题困扰模型一旦训练完成能力边界似乎就固定了。遇到模型答不出来的题要不重新训练要不微调成本高周期长。后来接触到一个思路——不训练模型而是在推理阶段投入更多计算让模型自己“多想几步”“多试几次”效果竟然出奇地好。这个思路就是测试时计算Test-Time Compute也是斯坦福 CS329A《自我改进 AI 智能体》第二讲的核心内容。这篇文章就基于这门课第二讲的知识脉络结合并行采样与验证机制完整梳理测试时计算的核心原理、实现方式和工程落地注意事项。无论你在做 AI 智能体开发还是想提升大模型在推理任务上的表现这篇文章都值得收藏。1. 背景与核心概念1.1 为什么“不训练也能让 AI 变强”我们先想一个场景。你用大模型解一道竞赛数学题模型直接给出一个答案。这个答案可能是对的也可能是错的但模型通常表现得非常自信。更麻烦的是你让它重新算一遍它可能给出完全不同的答案而且依然自信。这说明什么说明单次推理只是一个“采样”过程。大模型本质上是根据输入概率分布来生成文本的同样的 prompt不同的随机种子、不同的采样参数得到的结果可能不一样。既然单次采样不靠谱我们能不能让模型多采样几次然后从多个结果中选出最可靠的那个这正是测试时计算要做的事。它的核心思想是在模型推理阶段通过增加计算量来提升输出质量而不是修改模型权重。换句话说模型还是那个模型但我们在使用方式上做文章。在很多测试集上这种“推理时多想几步”的方式往往能让模型在数学推理、代码生成、复杂规划等任务上的表现明显提升。相比训练一个新模型这种方式成本低、见效快且不需要额外准备训练数据。1.2 测试时计算是什么定义与常见形式测试时计算Test-Time Compute通常指在模型推理inference阶段投入额外的计算资源通过多次采样、搜索、验证、反思、修正等策略获得比单次推理更高质量的输出。它的常见形式包括并行采样Parallel Sampling让模型对同一个问题生成多个候选答案。多数投票/自一致性Self-Consistency对多个答案进行投票选出现次数最多的结果。验证器Verifier训练或利用一个评分模型对候选答案打分选出分数最高的结果。思维搜索Search over Thoughts在思维链的每一步做多种可能性的搜索类似树搜索。自我反思与修正Self-Refinement让模型自己检查错误并根据反馈重新生成答案。这篇文章重点展开并行采样与验证这是最容易落地、也最容易被忽视的两块内容。1.3 适用场景与局限测试时计算并非万能。它最适用的场景是那些存在“标准答案”或者“结果可验证”的任务比如数学题、逻辑推理、代码单元测试、SQL 查询等。这类任务的共同特点是候选答案很多但我们可以通过某种方式判断哪个答案更好。相反如果任务本身是开放式的比如“写一首诗”“总结一下这篇文章的风格”多个答案没有绝对的对错测试时计算的价值就相对有限因为验证环节很难设计。另外还要注意测试时计算是以更多算力开销换质量提升。在实际工程中需要评估延迟和成本是否能接受。2. 训练阶段计算与测试时计算的对比2.1 训练阶段计算离线、批量、权重更新先看传统方式。训练阶段计算发生在模型上线之前通过大量样本计算梯度并更新模型权重。这个过程的特点是离线进行时间跨度长。一次性投入大量 GPU 算力。结果是一组固定的权重。模型能力在训练结束后基本定型。训练阶段计算解决的是“模型学会多少知识”的问题。模型不会做某类题通常是因为训练数据里没见过或者模型容量不够。这种情况下只能通过重新训练或微调来改变。2.2 测试时计算在线、采样、选择测试时计算则完全不同。模型权重保持不动我们在推理阶段做额外的计算让模型生成多个候选结果。对候选结果进行验证或筛选。选出一个最终答案或者综合多个答案。这个过程是“在线”的每次请求都可能产生不同的计算路径。它解决的是“模型其实会但单次没发挥好”的问题。这两类计算方式并不互斥而是可以叠加。训练阶段决定了模型能力的上限测试时计算则尽量逼近这个上限。2.3 两者如何配合使用在实际项目中合理的做法是先用训练阶段的计算把模型能力提到足够高。对于仍然不稳定的任务在推理阶段引入测试时计算。当一个任务经过测试时计算仍然无法解决时才考虑重新训练或微调。这样做的好处非常明显训练一次模型成本很高而测试时计算按量付费。对于低频但高价值的任务测试时计算几乎是性价比最高的优化手段。3. 核心机制一并行采样3.1 并行的含义多个独立推理并行采样的思路很直接同一个 prompt同时让模型生成 N 个答案而不是只生成一个。这里的“并行”既可以是真正的多路同时推理也可以是同一模型多次顺序采样只要每次采样保持随机性效果类似。打个比方你问一个聪明但偶尔粗心的人一道题他答一次可能失误你让他答 10 次然后统计出现次数最多的答案正确率通常会高很多。在代码实现上并行采样通常有两种方式显式循环在代码中调用 N 次模型接口。批量输入构造 N 条相同的 prompt 一起请求。3.2 温度参数与多样性并行采样要发挥作用关键在于“样本之间的多样性”。如果采样出来的 N 个答案一模一样那投票和验证都没有意义。控制多样性的核心参数是温度temperature。温度越高模型生成时的随机性越大候选答案越多样但同时单条答案的质量可能下降温度越低输出越确定但多样性不足。实际使用中常见做法是把采样温度设置在 0.7 到 1.0 之间具体需要根据任务调节。数学推理类任务建议温度略低比如 0.7 左右创意生成类任务可以高一点比如 0.9 以上。另一个注意点是 top-p核采样它控制候选 token 的累积概率。一般情况下并行采样时适当调低 top-p 可以减少低质量候选但不要在并行采样场景把 top-p 设得过低否则多样性会受影响。3.3 并行采样的参数组合一个典型的并行采样配置看起来像这样参数说明建议值n采样次数4 到 16temperature随机性0.7 到 1.0top_p核采样概率0.9 到 1.0max_tokens最大生成长度根据任务调整stop停止符按需配置下面是一个调用 OpenAI 兼容接口进行并行采样的 Python 示例。示例思路可用于任意兼容接口实际运行需要你配置自己的 API Key 和服务地址。# 文件路径parallel_sample.py import openai client openai.OpenAI( api_keyyour-api-key, base_urlyour-base-url ) prompt 一个三角形的三个内角分别是 2x、3x 和 4x求 x 的值。 def parallel_sample(prompt: str, n: int 5) - list[str]: responses [] for i in range(n): try: resp client.chat.completions.create( modelyour-model-name, messages[ {role: system, content: 你是一个严谨的数学解题助手。}, {role: user, content: prompt} ], temperature0.7, top_p0.9, max_tokens500 ) responses.append(resp.choices[0].message.content) except Exception as e: print(f第 {i1} 次采样失败: {e}) return responses if __name__ __main__: answers parallel_sample(prompt, n5) for idx, ans in enumerate(answers, 1): print(f候选 {idx}:\n{ans}\n)这里有几个值得注意的地方每次采样都是独立请求互不影响。采样过程中的异常需要单独捕获避免单次失败导致整个流程中断。在实际工程中可以把循环改成线程池或异步任务提高吞吐。4. 核心机制二验证器与选择策略并行采样生成了很多候选答案但哪个更好这时候就需要验证器。4.1 为什么要验证采样只是产生候选采样只解决“有没有更多备选”的问题并没有解决“哪个备选更好”的问题。直接随机选一个候选正确率并不会提升多少。所以必须有一个选择策略。选择策略大致分两类无需额外训练的比如多数投票、启发式规则。需要训练的比如训练一个验证器模型对候选结果进行打分。两者各有优劣。无需训练的方案上手快适用面广训练验证器则需要额外数据标注和训练成本但通常效果更好。4.2 不需要训练的验证方案多数投票与自一致性先看多数投票Majority Voting也叫自一致性Self-Consistency。核心逻辑是如果多个独立采样得到同一个答案那么这个答案正确的概率更大。举个例子。5 次采样结果中有 3 次答案是 20 度1 次是 25 度1 次是 30 度。按照多数投票规则最终答案是 20 度。但这里有一个关键细节不能对完整文本做简单去重因为模型的措辞可能不同。更合理的做法是提取“最终答案”部分参与投票。下面是一个简单的多数投票实现# 文件路径majority_vote.py import re from collections import Counter def extract_answer(text: str) - str: # 简化版抽取规则实际项目需要根据任务定制 patterns [ r答案是[:]\s*([^\n。]), r最终答案[:]\s*([^\n。]), r\bx\s*\s*([^\n。]) ] for pattern in patterns: match re.search(pattern, text) if match: return match.group(1).strip() return text.strip() def majority_vote(answers: list[str]) - tuple[str, int]: extracted [extract_answer(ans) for ans in answers] counter Counter(extracted) best_answer, count counter.most_common(1)[0] return best_answer, count if __name__ __main__: sample_answers [ 设三个角分别为 2x、3x、4x三角形内角和为 180 度。9x 180所以 x 20。答案是 20。, 2x 3x 4x 1809x 180x 20。最终答案20。, x 25。, 内角和为 180 度。2x3x4x9x180最终 x 20。, 这道题中 x 20 度。 ] result, cnt majority_vote(sample_answers) print(f多数投票结果: {result}出现次数: {cnt})多数投票的适用前提是任务有明确的答案形式且模型大部分情况下能给出正确答案。如果模型本身能力较弱正确率低于随机水平投票也救不回来。4.3 需要训练的验证器结果验证器与过程验证器当任务答案形式复杂或者候选答案难以自动抽取时可以考虑训练一个验证器。验证器本质上是一个二分类模型或打分模型输入是“问题 候选答案”输出是一个分数表示这个答案的可信度。常见做法有两种结果验证器Outcome Reward Model, ORM只看最终答案是否正确给最终结果打分。过程验证器Process Reward Model, PRM在一步步推理中逐步打分能定位到错误步骤但训练成本更高。结果验证器适合大多数工程场景过程验证器更适合推理链路长、需要定位错误位置的任务。验证器的训练数据怎么来常见思路是用大模型为同一个问题生成多个候选答案然后通过人工标注或者用权威答案自动比对给每个候选打上正确/错误标签再用排序损失训练一个打分模型。4.4 用大模型自身做验证如果不方便训练验证器还有一种折中方案让大模型充当验证器。做法是把问题、候选答案一起给模型要求它判断这个答案是否正确或者对多个答案进行排序。这种方式无需训练效果取决于大模型自身的判断能力。示例 prompt请判断以下解题过程是否正确。如果正确请回答“正确”如果不正确请指出错误原因。 题目一个三角形的三个内角分别是 2x、3x 和 4x求 x 的值。 候选解答 2x 3x 4x 180 9x 180 x 20 请给出你的判断。这种方式的好处是灵活缺点是会额外消耗 token而且大模型可能“看不出”错误。在工程中可以先做字符串规则抽答案再用 LLM 验证没有抽到答案的候选这样成本更可控。5. 完整实战案例为数学推理任务加入采样与验证流程现在把前面讲的内容串起来实现一个“采样 → 投票/验证 → 最终输出”的完整流程。示例任务仍然是数学题但这套流程可以扩展到代码生成、SQL 生成、日志分析等任务。5.1 流程设计整体流程分四步接收用户问题。并行采样生成 N 个候选答案。对候选答案做归一化和抽取。用多数投票筛选最终答案若投票无法收敛则用 LLM 验证。5.2 完整代码# 文件路径test_time_pipeline.py import re from collections import Counter import openai client openai.OpenAI( api_keyyour-api-key, base_urlyour-base-url ) SYSTEM_PROMPT 你是一个严谨的数学解题助手。请分步推理并在最后单独一行输出最终答案。 def generate_answer(question: str, temperature: float 0.7) - str: resp client.chat.completions.create( modelyour-model-name, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: question} ], temperaturetemperature, max_tokens500 ) return resp.choices[0].message.content def extract_answer(text: str) - str: # 优先抽取“最终答案”后面的内容 patterns [ r最终答案[:]\s*([^\n。]), r答案是[:]\s*([^\n。]), rx\s*\s*([^\n。]), r([-]?\d(?:\.\d)?) ] for pattern in patterns: match re.search(pattern, text) if match: return match.group(1).strip() return text.strip() def majority_vote(answers: list[str]) - tuple[str | None, int]: extracted [extract_answer(ans) for ans in answers] counter Counter(extracted) answer, count counter.most_common(1)[0] if count 1: return None, count return answer, count def llm_verify(question: str, answer: str) - bool: verify_prompt f 请判断下面的最终答案是否正确。如果正确请回答“正确”否则回答“不正确”。 题目{question} 最终答案{answer} 你的判断 resp client.chat.completions.create( modelyour-model-name, messages[ {role: user, content: verify_prompt} ], temperature0, max_tokens100 ) content resp.choices[0].message.content return 正确 in content def solve_with_test_time_compute(question: str, n: int 5) - str: # Step 1: 并行采样 candidates [generate_answer(question) for _ in range(n)] # Step 2: 多数投票 best_answer, count majority_vote(candidates) if best_answer is not None: return f多数投票结果: {best_answer}获得 {count}/{n} 票 # Step 3: 投票不收敛时用 LLM 验证 for idx, cand in enumerate(candidates): extracted extract_answer(cand) if extracted and llm_verify(question, extracted): return fLLM 验证通过: {extracted}来自第 {idx1} 个候选 # Step 4: 兜底返回第一个候选 return f验证未通过返回第一个候选: {extract_answer(candidates[0])} if __name__ __main__: q 一个三角形的三个内角分别是 2x、3x 和 4x求 x 的值。 result solve_with_test_time_compute(q, n5) print(result)5.3 运行结果说明假设模型本身具备基础数学能力那么 5 次采样中通常会出现 3 到 4 次答案一致的情况程序会直接走“多数投票”分支并输出结果。如果模型能力偏弱5 次采样各自给出不同答案程序会进入 LLM 验证分支逐一验证候选答案直到找到模型自身认可的答案。如果所有候选都无法通过验证程序返回第一个候选作为兜底避免接口无输出。这种设计保证了整个流程在工程上是健壮的不会因为某一次采样异常或验证异常导致整体失败。5.4 工程改造建议上面的代码是一个串行演示版本。在实际服务中建议做以下改造用 asyncio 或线程池并发发起采样请求把 5 次采样时间从“5 倍单次延迟”压缩到“接近 1 倍单次延迟”。对候选答案做缓存相同 prompt 在短时间内的采样结果可以复用。把投票和验证逻辑封装成独立服务方便不同模块复用。增加超时和重试机制防止某个采样请求一直阻塞。6. 常见问题与排查思路测试时计算在实践中会遇到不少问题下面整理几个高频场景。问题现象常见原因解决思路采样结果千篇一律投票没有意义温度参数过低模型输出趋于确定调高 temperature适当降低 top_p多数投票选出的答案是错的模型本身能力不足大多数采样都错考虑换更强的模型或训练任务专用验证器答案抽取不准确投票失效正则规则没覆盖到模型的表述习惯先观察 20 条真实输出再完善抽取规则并行采样导致接口延迟过高所有请求串行执行用异步或线程池并发调用验证结果不稳定同样的输入有时通过有时不通过LLM 验证时温度过高随机性大验证请求设置 temperature0多次验证取多数算力成本暴增采样数量 N 设置过大先测 N4 的效果再逐步增加到 8 或 16采样结果长度过长token 消耗大没有限制 max_tokens模型输出冗长设置合理的 max_tokens并要求“只输出最终答案”这里重点说一下答案抽取。很多人做多数投票时直接对完整文本去重结果发现同样的答案因为表达方式不同被当成不同结果。比较好的办法是先让模型在 prompt 中按固定格式输出比如“最终答案xxx”。代码里先按固定标记抽取。抽不到再走正则兜底。最后才考虑用 LLM 抽取。这样做的核心原则是能用规则解决的事情不要用模型能一次抽准的事情不要二次加戏。7. 最佳实践与工程建议7.1 采样数量 N 不是越大越好N 越大效果通常会更好但收益递减非常明显。从实际项目经验看N4 到 N8 时性价比最高N 超过 16 后正确率提升趋于平缓成本和延迟却线性增长。建议上线前先做小规模实验画出“N 值-正确率”曲线再决定线上参数。7.2 温度参数要与任务匹配数学推理、代码生成建议 temperature0.7top_p0.9。开放对话、创意写作建议 temperature0.9 以上。验证环节建议 temperature0。同一套并行采样逻辑用在不同任务上需要单独调参不要追一个参数走天下。7.3 验证器要关注“错误接受率”用 LLM 做验证时最容易出问题的不是把好答案拒掉而是把错误答案放进来。比如模型对错误答案也说“正确”这时候验证就失去了意义。在工程上可以引入“多次验证取多数”或者“验证规则双重校验”来降低错误接受率。在安全敏感的领域比如代码生成、数据库 SQL 生成建议加上沙箱测试而不是只靠 LLM 判断。7.4 并行采样的资源控制并行采样会放大对底层模型的调用压力。建议在代码中加入并发数限制例如用信号量控制最大并发请求数避免短时间大量请求打爆服务。下面是一个简单的并发控制示例# 文件路径concurrency_control.py import asyncio import openai client openai.OpenAI( api_keyyour-api-key, base_urlyour-base-url ) semaphore asyncio.Semaphore(3) async def guarded_generate(prompt: str) - str: async with semaphore: loop asyncio.get_event_loop() def sync_call(): resp client.chat.completions.create( modelyour-model-name, messages[{role: user, content: prompt}], temperature0.7, max_tokens300 ) return resp.choices[0].message.content return await loop.run_in_executor(None, sync_call) async def parallel_generate(prompt: str, n: int 5) - list[str]: tasks [guarded_generate(prompt) for _ in range(n)] return await asyncio.gather(*tasks, return_exceptionsTrue) if __name__ __main__: results asyncio.run(parallel_generate(解释一下什么是测试时计算。, n5)) for idx, res in enumerate(results, 1): if isinstance(res, Exception): print(f候选 {idx} 异常: {res}) else: print(f候选 {idx}: {res})7.5 日志与可复现性测试时计算引入了随机性会导致同一个请求在不同时间得到不同的结果。这在调试时非常痛苦。建议在日志中记录每个请求的prompt 版本温度和 top_p 参数采样次数每个候选答案及其抽取结果最终采用的候选编号同时在采样时为每次请求生成一个随机种子方便复现问题。7.6 安全与权限边界在真实项目中如果测试时计算用于生产环境涉及 SQL 生成、命令执行、代码生成等场景必须严格限制最终结果的执行权限。建议所有生成结果默认不自动执行先经过人工确认。代码生成结果必须在沙箱环境中进行测试。数据库 SQL 生成结果必须经过只读账号验证并在测试库执行。在安全敏感操作中加入审批流程。测试时计算只是提升模型输出质量的手段不应该绕过任何已有的安全边界。7.7 评估线上效果时要注意评估测试时计算的效果不能只看正确率。需要同时观察延迟P95 延迟是否可接受。成本单次请求的 token 消耗和 API 费用。错误模式是多数投票能解决还是必须上验证器。回退机制验证不通过时是否有兜底策略。建议先在离线数据集上跑通全流程再小流量上线对比。8. 总结与后续学习方向测试时计算的核心逻辑并不复杂既然单次采样不可靠那就多做几次再引入验证机制来筛选。它把“训练时多花算力”变成了“推理时多花算力”在没有改变模型权重的前提下提升了模型在推理任务上的可靠性。本文讲清楚了几件事什么是测试时计算以及它与训练阶段计算的本质区别。并行采样如何通过温度等参数控制多样性。验证器的两种路线无需训练的多数投票以及需要训练的验证器模型。一个完整的采样投票LLM 验证流程及其代码实现。工程落地时关于延迟、成本、护栏和评估的注意事项。下一步建议你在自己的任务上先跑一遍并行采样基线记录 N1、N4、N8 时的正确率和成本曲线。确认收益后再考虑是否引入训练验证器。如果对思维搜索和过程验证器感兴趣还可以进一步看树搜索在推理任务中的应用。如果你在做 AI 智能体开发或者正在搭建模型评估流程建议把文中的 pipeline 代码改成你自己的任务格式跑一遍。测试时计算是一个“投入小、见效快”的优化方向值得花一个下午做实验看看在你的任务上能提升多少。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。