
测试时扩展Test-Time Scaling最近在推理大模型里讨论度很高。它的核心思路很简单训练阶段不动推理阶段多给模型一些计算量通过更充分的搜索、采样或修订来提升答案质量。这个方向之所以关键是因为它不改变权重却能显著拉高数学推理、代码生成这类任务的正确率而且实现路径非常工程化。这次我们就把 Test-Time Scaling 的几个核心问题拆开看推理模式Inference Regimes有哪些、评估该怎么设计、为什么很多人在复现时结果对不上。文章最后会给出一个可以照着跑的评估流程包括自己写评估 API、批量调用模型、复现关键参数记录的全过程。如果你正在做推理大模型的效果优化、评测体系搭建或者想搞明白为什么同样的方法在不同框架下结果不一样这篇文章可以直接收藏。1. Test-Time Scaling 核心概念速览能力项说明核心目标在推理阶段增加计算量提升模型在数学、逻辑、代码等任务上的准确率主要手段并行采样、多数投票、Best-of-N、顺序修订、过程奖励模型、树搜索典型应用任务MATH、AIME、GPQA、HumanEval、代码生成、复杂多步推理不改变的内容模型权重、训练数据、微调流程关键代价推理延迟上升、Token 消耗增加、显存峰值可能翻倍评估重点Accuracy、Passk、Consistencyk、平均 Token 数、计算效率可复现性风险采样参数、种子、量化、框架实现差异都会影响结果适用场景离线评测、高质量答案生成、需要稳定推理结果的业务系统从材料来看Test-Time Scaling 不是一个单一的算法而是一类推理策略的总称。它解决的核心问题是当模型一次采样无法得到正确答案时如何利用额外的推理预算逼近正确答案。与训练时扩展Train-Time Scaling相比它更灵活适合在部署阶段按需调节推理质量。2. 三种核心推理模式Inference Regimes怎么选Test-Time Scaling 的实验设计通常会对比几种推理模式。这里的推理模式指的是在给定相同的测试时计算预算下使用哪种采样和筛选策略来获得最终答案。2.1 并行采样 Self-Consistency这是最简单的扩展方式。模型对同一个问题独立采样 N 次然后对答案进行投票选出现次数最多的作为最终结果。Self-Consistency 在数学推理任务上已经被广泛验证能明显提升准确率。实现时要特别处理的是答案归一化同一个答案可能表述不同需要先做规则归一化再投票。采样温度一般取 0.6 到 0.8温度过低会导致采样结果趋同。采样次数N 通常取 8、16、32但从收益递减的角度看不一定越大越好。2.2 Best-of-NBest-of-N 是指采样多个输出后用奖励模型或过程奖励模型给每个完整答案打分选得分最高的输出。和 Self-Consistency 相比它不依赖答案之间的投票而是依赖一个额外的评分器。打分器的质量直接决定 Best-of-N 的上限。如果没有好的奖励模型这种方法可能不如 Self-Consistency。2.3 顺序修订Sequential Revision顺序修订让模型先给出一版答案然后根据反馈逐步修订。反馈来源可以是验证器自动检查、模型自我反思、外部工具执行结果。这种方式在代码生成任务里效果明显因为编译器和测试用例提供了可靠反馈。但在纯文本推理任务里模型自我反思的收益不稳定可能出现来回修改但最终答案不变的情况。2.4 过程奖励模型 树搜索这是目前测试时扩展中上限最高的方向之一。过程奖励模型Process Reward Model, PRM不再对完整答案打分而是对推理过程中的每一步打分。树搜索在此基础上维护多个推理分支每一步扩展时优先走得分高的路径。在 AIME 这类竞赛级数学任务上PRM 搜索通常能带来明显提升但工程复杂度也最高。需要维护搜索树、节点状态、回滚策略和预算控制。2.5 推理模式对比推理模式核心机制额外组件计算开销适用任务Self-Consistency多次采样 投票无低通用推理、数学Best-of-N采样 奖励模型打分奖励模型中需要质量排序的场景Sequential Revision迭代修订验证器或反思模块中代码生成、工具调用PRM Tree Search逐步打分 搜索过程奖励模型 搜索器高竞赛数学、复杂推理选择哪个模式取决于手头有什么组件。没有奖励模型就先用 Self-Consistency有可靠的验证器就优先做 Sequential Revision想做 SOTA 再考虑 PRM 和树搜索。3. 评估方法论怎么量化“推理变强了”评估在 Test-Time Scaling 研究中容易被低估。很多人跑完实验只看 Accuracy 有没有涨但忽略了评估设计本身对结论的影响。3.1 评估集怎么选不是所有 Benchmark 都适合用来观察测试时扩展效果。以下几点比较关键任务必须有多步推理空间。简单事实问答无论怎么扩展都不会有提升。题目需要有标准答案或可自动判定的判据。题目难度要覆盖从简单到竞赛级的范围否则容易饱和。数学类推荐看 AIME、MATH Level 5科学类看 GPQA Diamond代码类看 LiveCodeBench、HumanEval。3.2 核心指标单个 Accuracy 指标不够用建议四个指标一起看Accuracy / Pass1单次采样的直接正确率代表模型基础能力。Passkk 次采样中至少有一次正确的比例代表模型潜在能力。Consistencykk 次采样中答案一致的占比代表稳定性。平均 Token 数 / 平均采样次数代表达到该准确率付出的推理成本。Passk 的计算公式常用如下使用时注意 k 和 n 的关系def pass_at_k(n: int, c: int, k: int) - float: n: 总采样次数 c: 正确次数 k: 采样数量上限 if n - c k: return 1.0 import math return 1.0 - math.comb(n - c, k) / math.comb(n, k) # 示例 print(pass_at_k(n64, c10, k16))3.3 评估流程设计一个严格的 Test-Time Scaling 评估流程至少包含固定样本子集避免每次实验都换不同题目。统一 prompt 模板不同推理模式必须使用同一个初始 prompt。记录每个样本的采样参数包括温度、top_p、max_tokens。记录每个样本实际消耗的 token 数和耗时。输出原始答案和最终答案便于回溯。评估结果最终应该能回答三个问题准确率提升了多少、消耗了多少额外计算、稳定性是否下降。4. 第三方评估工具与自定义评估 API评估工具在 Test-Time Scaling 项目中容易被低估。实际落地时除了现成的开源评测框架还经常需要接入自己的评测标准尤其是当业务里的“正确答案”不是唯一标准答案而是需要规则判定或外部工具校验时。4.1 现成评估工具怎么选常见的开源评估工具有以下几种各有偏重工具特点适合场景lm-evaluation-harness基准评测覆盖广支持多种生成参数标准 Benchmark 测评OpenCompass中文生态好支持多模型对比大模型横向评测vLLM 自建脚本推理服务 自定义判分逻辑需要定制打分逻辑时自定义 FastAPI完全掌控输入输出和评分标准接入业务指标时如果只是跑标准评测集直接用 lm-evaluation-harness 或 OpenCompass 就够了。但如果需要“调用自己写的 API根据自己定义的评测标准打分”就需要自己搭一个轻量评估服务。4.2 自定义评估 API 示例下面这个示例实现了一个基于 FastAPI 的评估服务它接收模型输出和参考答案返回评测结果。你可以把这里的打分规则替换成自己的业务规则比如检查 JSON 结构、调用外部解释器、做语义相似度计算等。# evaluation_api.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI(titleCustom Evaluation API) class EvalRequest(BaseModel): task_id: str model_output: str reference: str task_type: str math # math/code/json/similarity class EvalResponse(BaseModel): task_id: str score: float 0.0 passed: bool False detail: str app.post(/evaluate, response_modelEvalResponse) def evaluate(req: EvalRequest): if not req.model_output.strip(): raise HTTPException(status_code400, detailmodel_output is empty) if req.task_type math: # 数学题归一化后比较答案字符串 norm_output req.model_output.strip().replace(\n, ) norm_ref req.reference.strip().replace(\n, ) passed norm_output norm_ref score 1.0 if passed else 0.0 detail math exact match return EvalResponse(task_idreq.task_id, scorescore, passedpassed, detaildetail) elif req.task_type code: # 代码题这里可以调用本地执行沙箱做单元测试 # 示例中仅判断是否包含 main 函数 passed def main in req.model_output score 1.0 if passed else 0.0 detail code structure check return EvalResponse(task_idreq.task_id, scorescore, passedpassed, detaildetail) elif req.task_type json: # JSON 结构校验 import json try: parsed json.loads(req.model_output) passed isinstance(parsed, dict) score 1.0 if passed else 0.0 detail json valid if passed else json invalid except Exception as e: passed False score 0.0 detail fjson parse error: {str(e)} return EvalResponse(task_idreq.task_id, scorescore, passedpassed, detaildetail) else: raise HTTPException(status_code400, detailfunknown task_type: {req.task_type}) if __name__ __main__: import uvicorn uvicorn.run(app, host127.0.0.1, port8765)启动评估服务pip install fastapi uvicorn python evaluation_api.py服务启动后用客户端调用评测接口。这里用 Python 的 requests 来模拟# eval_client.py import requests url http://127.0.0.1:8765/evaluate cases [ { task_id: case_001, model_output: 42, reference: 42, task_type: math }, { task_id: case_002, model_output: def main():\n return 1, reference: , task_type: code }, { task_id: case_003, model_output: {answer: 1}, reference: , task_type: json }, ] for case in cases: resp requests.post(url, jsoncase, timeout10) print(case[task_id], resp.json())这种方式的好处是当你需要批量评估多个模型输出时可以将模型输出写入文件然后逐条 POST 到评估 API。评估逻辑和生成逻辑解耦可以独立扩展。接下来给出批量评估时的脚本骨架import json import requests from concurrent.futures import ThreadPoolExecutor, as_completed EVAL_URL http://127.0.0.1:8765/evaluate def load_outputs(path: str): with open(path, r, encodingutf-8) as f: return json.load(f) def evaluate_one(item: dict) - dict: resp requests.post(EVAL_URL, jsonitem, timeout15) return resp.json() def batch_evaluate(input_path: str, output_path: str, max_workers: int 8): items load_outputs(input_path) results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: futures [executor.submit(evaluate_one, item) for item in items] for fut in as_completed(futures): results.append(fut.result()) with open(output_path, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) passed sum(r[passed] for r in results) print(fpass rate: {passed / len(results):.2f}) if __name__ __main__: batch_evaluate(./model_outputs.json, ./eval_results.json)5. 可复现性为什么两次实验对不上可复现性是 Test-Time Scaling 研究里最容易翻车的地方。同一个模型、同一个采样策略两次实验结果可能差好几个点。这不是玄学主要原因集中在这几个层面。5.1 采样参数没有完全固定temperature、top_p、top_k、max_tokens、seed 都会影响输出。对比实验时所有推理模式必须使用相同的基础采样参数只有模式本身的变量可以不同。seed 固定不等于结果完全确定。在 GPU 上某些算子如 FlashAttention本身是非确定性的即使固定 seed多次运行也可能存在微小差异。测试时扩展涉及多次采样这种差异会被放大最终导致投票结果变化。5.2 模型权重的来源和量化方式不同同一个开源模型不同来源的权重可能存在差异。量化后的模型如 AWQ、GPTQ 量化在推理质量上与原版权重有偏差。如果实验要求精确复现使用原始未量化权重最稳妥。5.3 推理框架实现差异vLLM、SGLang、HuggingFace Transformers、LitGPT 等框架在采样实现上存在细微差异包括 logits 后处理、重复惩罚实现、采样算法选择。这意味着在 vLLM 下跑的 Self-Consistency 结果不一定能在 Transformers 下完全复现。5.4 提示词模板不一致模型对 prompt 格式很敏感。不同推理模式之间如果使用了不同的 prompt 模板比如加了“Lets think step by step”或在 system prompt 里加了规则实验结论就不干净了。5.5 实验配置记录清单建议每次实验都记录下面这些配置项这样即使结果对不上也能快速定位差异# experiment_config.yaml model: name: your-model-name revision: main quantization: null dtype: bfloat16 sampling: temperature: 0.7 top_p: 0.95 top_k: -1 max_tokens: 4096 seed: 42 task: dataset: math_500 prompt_template_version: v2 max_samples: 100 n_samples_per_task: 16 inference_regime: self_consistency framework: vllm cuda_version: 12.16. 端到端验证流程下面给出一套从环境准备到结果统计的通用流程。这里的命令不是某个项目特有的而是适用于大部分基于 vLLM 或 Transformers 的本地推理评测。6.1 环境准备基础环境需要Linux 或 Windows WSL2。Python 3.10 及以上。PyTorch 2.x。vLLM 或 SGLang用于高效推理。显存需要根据模型大小决定推理大模型建议 24G 以上如果是 7B 到 14B 模型16G 以上更稳妥。磁盘空间预留至少能放下模型权重和评估结果文件。安装推理框架pip install vllm pip install transformers datasets6.2 启动 vLLM 推理服务以 vLLM 为例启动一个兼容 OpenAI API 的服务python -m vllm.entrypoints.openai.api_server \ --model /path/to/model \ --served-model-name test-scale-model \ --dtype bfloat16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --port 8000如果模型本身没有经过 instruction tuning你可能需要在 prompt 里加上 few-shot 示例否则模型不会按要求输出推理过程。6.3 执行多轮采样并保存结果下面是调用 vLLM 服务进行多轮采样的示例脚本。它会从评测数据集中读取题目对每个问题采样 n 次并把结果保存为 JSONL 文件。import json import random from openai import OpenAI client OpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY ) def load_questions(path: str, max_samples: int 20): with open(path, r, encodingutf-8) as f: data json.load(f) return data[:max_samples] def generate_samples(question: str, n: int 8, temperature: float 0.7): results [] for _ in range(n): response client.chat.completions.create( modeltest-scale-model, messages[ {role: system, content: You are a helpful assistant.}, {role: user, content: question} ], temperaturetemperature, max_tokens2048, seed42, ) results.append(response.choices[0].message.content) return results def main(): questions load_questions(./math_test.json) records [] for idx, item in enumerate(questions): question item[question] candidates generate_samples(question, n8) records.append({ task_id: ftask_{idx:04d}, question: question, reference: item.get(answer, ), candidates: candidates }) with open(./model_outputs.json, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2) print(fgenerated {len(records)} tasks) if __name__ __main__: main()6.4 提取最终答案并投票多轮采样的输出通常包含推理过程需要先提取最终答案再做一致性投票。提取规则根据任务类型而定数学题可以提取boxed{}内容或“答案是 X”之后的文本。import json import re from collections import Counter def extract_final_answer(text: str) - str: 提取数学答案的简单规则示例 # 尝试提取 \boxed{...} m re.search(r\\boxed\{([^}])\}, text) if m: return m.group(1).strip() # 尝试提取 answer is X m re.search(ranswer\sis\s([^.\n]), text, re.IGNORECASE) if m: return m.group(1).strip() # 尝试最后一个等式后的值 lines [line for line in text.splitlines() if in line] if lines: return lines[-1].split()[-1].strip() return text.strip()[-80:] def majority_vote(answers): counter Counter(answers) most_common counter.most_common(1)[0][0] return most_common, counter[most_common] / len(answers) def process_records(input_path: str, output_path: str): with open(input_path, r, encodingutf-8) as f: records json.load(f) results [] for rec in records: extracted [extract_final_answer(c) for c in rec[candidates]] final_answer, consistency majority_vote(extracted) results.append({ task_id: rec[task_id], reference: rec[reference], final_answer: final_answer, consistency: consistency, all_extracted: extracted }) with open(output_path, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) print(fprocessed {len(results)} records) if __name__ __main__: process_records(./model_outputs.json, ./extracted_results.json)6.5 评估并统计将提取后的结果 POST 到第 4 节里的评估 API得到 pass1、pass8、consistency 等指标。要计算 Passk可以在统计脚本里按每个任务的候选答案序列来算import json def evaluate_batch(input_path: str, eval_api_url: str): import requests with open(input_path, r, encodingutf-8) as f: records json.load(f) total 0 correct_any 0 consistency_sum 0.0 for rec in records: total 1 eval_items [] for ans in rec[all_extracted]: eval_items.append({ task_id: rec[task_id], model_output: ans, reference: rec[reference], task_type: math }) passed_any False passed_count 0 for item in eval_items: resp requests.post(eval_api_url, jsonitem, timeout10).json() if resp[passed]: passed_any True passed_count 1 if passed_any: correct_any 1 consistency_sum rec[consistency] print(fPass1: {correct_any / total:.3f}) print(fAverage Consistency: {consistency_sum / total:.3f}) if __name__ __main__: from evaluation_api import pass_at_k print(pass8 example:, pass_at_k(8, 5, 8))7. 资源占用与性能观察Test-Time Scaling 的核心成本不是训练而是推理时的计算量放大。观察资源占用时重点关注以下几点。7.1 显存占用多轮采样会同时占用显存用于 KV Cache尤其是 max_tokens 设置过大时。观察方式watch -n 1 nvidia-smi如果出现 Out of Memory优先降低--max-model-len或减小批处理大小。另一种方式是使用 vLLM 的--gpu-memory-utilization参数控制显存分配比例。7.2 采样次数与总延迟Self-Consistency 的延迟近似等于单次采样延迟乘以采样次数。Best-of-N 还要额外加上奖励模型评分的时间。树搜索的延迟波动最大取决于搜索深度和分支数。建议在实验前估算总预算单个任务预算上限 max_tokens x 采样次数 x 搜索/修订轮次例如一次采样 2048 token、采样 8 次、无修订单任务的最高输出 Token 预算就是 16384 个 token。实际业务中要考虑成本是否能接受。7.3 降低资源占用的思路使用更小的采样次数先用 8 次观察趋势再决定是否增加。降低 max_tokens答案不必要太长。使用 vLLM 的连续批处理提升吞吐。对候选答案先做规则去重再进入投票或评分环节。8. 常见问题与排查问题现象可能原因排查方式解决方案多次采样结果完全一样温度过低或 seed 固定后采样策略无随机性检查 sampling 参数调高 temperature或检查框架是否启用了 greedy 模式投票结果比单次采样还差答案归一化不正确查看提取后的答案列表检查提取规则统一答案格式两次实验准确率差异明显采样参数或 prompt 模板未固定对照实验配置记录固定 seed、temperature、prompt 模板评估服务返回 400请求格式不符合 API 定义查看服务日志检查 JSON 字段名和 task_type批量任务卡住请求并发过高或服务内存不足查看日志和资源占用降低并发数增加超时时间增加队列重试OOM 显存不足并发请求过多或 max_tokens 过大查看 nvidia-smi减少并发降低 max_tokens开启 vLLM 显存限制模型输出无法解析提取规则与任务不匹配查看原始输出样本针对任务类型设计更鲁棒的提取规则API 调用超时模型推理太慢或网络超时设置过短查看单次请求耗时增大 timeout 参数或改用异步批处理9. 最佳实践与合规建议Test-Time Scaling 方法本身是模型推理阶段的策略但在实际应用时要注意以下几点。第一次实验先用小数据集、低采样次数跑通流程再扩大规模。保存一份最小可运行配置包括 prompt 模板、采样参数、评估 API方便后续复现。模型权重、输入数据、评估结果分目录管理避免混在一起。批量任务要加日志和失败重试机制防止一个超时请求影响整批结果。接口服务如果部署在服务器上要限制访问来源避免被外部随意调用。如果数据集来自公开 benchmark如 AIME、GPQA注意使用最新版本避免数据泄漏到模型训练集中造成虚高结果。在隐私和版权方面如果评测数据包含非公开内容、人脸信息、声音或受版权保护的文本必须确认使用授权不能直接用于评测和对外发布。用虚假日志、广告域名等方式做隐蔽内容是不被允许的评测和生成过程中任何输出都应保证来源可追溯。需要额外提醒的是评测用例本身也可能存在泄漏风险。如果评测集出现在模型训练数据里Test-Time Scaling 带来的提升可能被高估。稳妥的做法是使用距离模型发布时间较晚的评测集或者自己构建未见过的测试集。10. 总结Test-Time Scaling 真正值得关注的地方在于它不改变模型权重只是通过推理阶段的采样、投票、修订和搜索来换取更高准确率。推荐的入手路径是先做 Self-Consistency用最少的外部组件建立基线然后逐步尝试 Best-of-N 和顺序修订最后再考虑 PRM 树搜索这类复杂方案。在评估侧建议把自定义评估 API 和现成评测工具结合使用。标准评测集用 OpenCompass 或 lm-evaluation-harness业务定制标准交给自己的评估服务这样整个实验链路更完整。最容易踩的坑有两个一是并行采样时的答案归一化很多模型输出格式不统一导致投票结果失真二是可复现性采样参数、prompt 模板、推理框架不一致时两次实验能差出好几个点。建议把实验配置完整保存下来每个实验至少跑两次确认论文或报告里的结论不是偶然结果。后续可以继续扩展的方向包括把过程奖励模型接入评测流程、用树搜索替代多数投票、以及把评估 API 接到自动化的批量评测平台上让整个 Test-Time Scaling 实验变成一条可重复跑的流水线。