资讯动态

测试时扩展与推理大模型评测:可复现性工程实践

发布时间:2026/8/27 15:14:42 来源:尧图企业网站定制
最近在对比推理大模型reasoning LLM的不同评测方式时被一个很现实的问题卡住模型效果波动不小同一个 prompt 跑两次答案可能完全不同换一个推理采样配置分数能差好几个点。如果评测工具、推理参数、随机种子都没固定下来最后很难说清楚模型能力提升究竟是模型变了还是只是测试方式变了。这篇文章围绕 Test-Time Scaling测试时扩展这一主题结合 Reasoning LLMs 的推理行为、Inference Regimes推理配置、第三方评估工具接入以及可复现性工程实践梳理一套完整可落地的评测思路。内容偏实操代码都是可以直接在本地跑的最小示例适合正在做 LLM 效果评测、想接入第三方评估工具或者研究采样策略对模型分数影响的读者。1. 背景与核心概念1.1 什么是 Test-Time Scaling传统的大模型能力提升主要靠训练阶段更多数据、更大模型、更长时间的训练。但对于推理任务比如数学题、逻辑判断、代码调试模型在训练完成之后仍然可以通过“多算一会儿、多想几步”来获得更高质量的回答。这种在推理阶段增加计算量、改善生成质量的做法就是 Test-Time Scaling。一个最简单的例子是数学题问题一个农场里有 3 只鸡和 2 只狗总共有多少条腿不做扩展时模型可能直接输出3 * 2 2 * 4 14。如果做测试时扩展模型可能会先自言自语鸡有 2 条腿3 只鸡是 6 条腿。 狗有 4 条腿2 只狗是 8 条腿。 总腿数是 6 8 14。当推理过程变长、中间检查变多错误概率会下降。更进一步的扩展可以让模型生成多条候选答案再用投票或验证器选出最可靠的结果。这种现象在 OpenAI 的 o1 / o3 系列模型中比较典型也让“测试时计算Test-Time Compute”这个概念成为研究热点。与之相似的还有 self-consistency自洽性、best-of-n 采样、多数投票、轻量级验证器筛选等方法。1.2 为什么 Inference Regimes 会影响评测结果Inference Regimes可以理解为“模型推理阶段的运行配置组合”。它决定了模型在给定同一个 prompt 时到底如何生成内容。常见维度包括配置项影响temperature控制采样随机性越高越随机top_p / top_k控制候选 token 范围max_tokens / max_completion_tokens限制输出长度采样次数 n生成多少条候选答案多数投票轮数对多条答案如何聚合是否使用验证器是否用额外模型排序候选答案prompt 模板是否加入思考引导种子 seed固定随机数生成器状态同一个模型temperature0 和 temperature0.7n1 和 n10最终评测分数可能差异不小。因此如果你在论文或项目报告中写“本模型准确率达到 85%”却不描述推理阶段参数那这个结果就很难复现。Inference Regimes本质上是评测报告里必须交代清楚的实验条件。1.3 评测与可复现性的关系评测Evaluation回答的是“模型效果到底怎么样”。可复现性Reproducibility回答的是“别人能不能按同样的设置得到相同结果”。评测结果可信的前提是可复现。但在真实项目中数据集版本会在更新、评估脚本会被调整、模型服务可能做了量化或 batch 推理这些都会影响最终指标。常见的评测完整性问题包括只记录准确率不记录 prompt 模板和模型版本。为了效果对比临时把 temperature 调低却没写在报告里。评测数据集做完清洗后没有保存清洗脚本。多个评测工具混用指标口径不一致。随机性没有固定实验重复两次结果相差很大。所以评测工程中需要引入稳定的第三方评估工具并且让工具能够调用自建的推理 API使用自定义评估标准。这也是本文后半部分的实战重点。2. 环境准备与第三方评估工具选型2.1 基础环境本文示例以 Python 3.10 为基准需要准备Python 3.10 或更高版本pip 包管理工具OpenAI SDK用于调用兼容接口FastAPI uvicorn用于模拟自建推理服务promptfoo 或 DeepEval第三方评估工具如果你已经有可用的推理 API就不用搭 mock 服务直接把 base_url 和 API Key 换成真实服务即可。版本说明不同工具的配置格式差异较大本文以常见版本为主。实际使用时请以官方文档和pip show输出的版本为准。安装依赖的命令pip install openai fastapi uvicorn promptfoo deep-eval如果你只需要其中某一个工具可以分开安装。例如pip install deep-eval # npm 安装 promptfoo npm install -g promptfoo2.2 主流第三方评估工具目前社区常用评估方案大致分两类。一类是离线评估框架适合批量跑 benchmark比如lm-evaluation-harnessEleutherAI 出品支持大量公开数据集OpenCompass上海人工智能实验室开源支持中英文评估另一类是面向业务的自定义评估工具适合对接自建 API、编写自定义指标比如promptfoo类似单元测试的评测工具用 YAML 配置测试用例DeepEvalPytest 风格的 LLM 评估框架可以自定义 metricsOpenAI EvalsOpenAI 开源的评估框架可注册自定义 eval如果你需要“调用自己写的 API根据自己定义的评价标准”来做评估promptfoo 和 DeepEval 是更灵活的选择。2.3 本文实战场景我设计的示例场景如下有一个自建推理服务接口兼容 OpenAI API 格式。需要评测模型在高斯数学题上的准确率。每次生成不只跑 1 条答案而是先采样 N 条候选再用多数投票得出最终答案。评测脚本需要记录温度、种子、模型版本、prompt 版本保证实验可复现。在代码实现上我会先用 FastAPI 写一个 mock 推理服务再分别演示 promptfoo 和 DeepEval 的接入方式。这样你不需要真实模型也能跑通流程。3. 核心概念拆解3.1 Test-Time Scaling 的常见策略Test-Time Scaling 并不单指“让模型生成更多内容”而是包含多个层面的扩展手段。3.1.1 多数投票 / Self-Consistency对同一个问题生成 N 个答案然后统计答案中出现次数最多的那个作为最终输出。这是最直观的扩展方式效果稳定实现简单。import collections answers [14, 14, 14, 15, 14, 13] final_answer collections.Counter(answers).most_common(1)[0][0] print(final_answer)输出14对于有唯一正确答案的数学题多数投票能显著提升准确率但代价是多次调用模型推理成本变为原来的 N 倍。3.1.2 Best-of-N 采样 验证器生成 N 个候选答案再使用一个验证器reward model / verifier对候选答案排序选出得分最高的。这种方式适合“答案没有单一标准但可以判断好坏”的任务比如代码、开放式问答。3.1.3 长思维链 / 隐式搜索像 o1 这类模型会在内部生成较长的推理轨迹再输出最终答案。这种方式不需要显式采样多轮而是把更多计算放在单次生成内。3.2 Inference Regimes 的关键参数参数说明对评测的影响temperature采样温度默认为 1温度越低输出越确定温度为 0 时多数采样可能失效n每个 prompt 生成的候选数影响多数投票、Best-of-N 的基数max_tokens最大生成 token 数太短会导致推理过程被截断seed随机种子控制可复现性stop停止符可能影响结构化输出response_format输出格式约束影响解析难度reasoning_effort推理难度部分模型支持 low / medium / high需要注意的是temperature0在技术上并不是完全确定性不同框架、不同硬件下仍有微弱差异。要保证严格可复现最好固定 seed并保留完整参数快照。3.3 评估流程的组成一次完整的评测流程通常包含数据准备测试用例的加载与清洗。推理调用调用自建 API设置 Inference Regimes。答案解析从模型输出中提取最终答案。指标计算根据自定义标准评分。结果记录保存原始输出、参数和指标到本地文件。在这套流程里数据、参数、代码、运行环境四者耦合在一起哪一环没记录复现都会出问题。4. 实战用第三方评估工具评测自定义 API下面进入代码实战部分。我会给出一个可以直接复制的完整流程。4.1 准备一个兼容 OpenAI 的自建推理服务先写一个 FastAPI 服务。为了演示方便这个服务不会真的加载大模型而是返回一个模拟推理结果从几个预置答案中随机抽一个。# 文件路径server.py import random import uvicorn from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class ChatRequest(BaseModel): model: str mock-reasoner messages: list temperature: float 0.7 max_tokens: int 128 seed: int | None None class Choice(BaseModel): index: int message: dict finish_reason: str stop class ChatResponse(BaseModel): id: str object: str chat.completion choices: list[Choice] usage: dict ANSWERS [ The answer is 14., Final answer: 14., 14, Let me think carefully. 3 chickens have 6 legs. 2 dogs have 8 legs. Total is 14., ] app.post(/v1/chat/completions, response_modelChatResponse) def chat_completion(req: ChatRequest): if req.seed is not None: random.seed(req.seed) content random.choice(ANSWERS) return ChatResponse( idchatcmpl-mock, choices[ Choice( index0, message{role: assistant, content: content}, finish_reasonstop, ) ], usage{prompt_tokens: 20, completion_tokens: len(content), total_tokens: 20 len(content)}, ) if __name__ __main__: uvicorn.run(app, host0.0.0.0, port8000)启动服务python server.py注意这只是一个 mock 服务用来验证评估流程。真实项目中你需要把/v1/chat/completions转发到自己的推理引擎比如 vLLM、TGI或者你内网部署的模型服务。4.2 使用 promptfoo 进行评测promptfoo 是一个命令行评测工具可以用 YAML 定义 prompt 和测试用例也支持自定义 API 地址。4.2.1 初始化配置文件在项目目录下创建一个promptfooconfig.yaml# 文件路径promptfooconfig.yaml prompts: - | 请回答下面的数学题只输出最终答案数字。 题目一个农场里有 3 只鸡和 2 只狗总共有多少条腿 providers: - id: openai:chat:mock-reasoner config: apiBaseUrl: http://localhost:8000/v1 apiKey: dummy-key temperature: 0.7 max_tokens: 128 tests: - vars: question: 一个农场里有 3 只鸡和 2 只狗总共有多少条腿 assert: - type: contains value: 14这里apiBaseUrl指向自建服务apiKey填任意非空字符串即可openai:chat:mock-reasoner表示调用 OpenAI 兼容接口。4.2.2 运行评测promptfoo eval如果希望输出完整报告promptfoo eval -o report.htmlpromptfoo 的断言机制非常灵活包括contains输出包含某个文本。equals输出完全等于某个文本。javascript执行自定义 JS 判断。python调用自定义 Python 脚本。model-graded用另一个 LLM 打分。这种方式非常适合“自己定义评价标准”。4.3 使用 DeepEval 进行评测DeepEval 是 Python 生态的评估库风格接近 pytest适合在代码里精细控制评估流程。4.3.1 编写自定义评估用例# 文件路径test_eval.py import os from deepeval import assert_test from deepeval.test_case import LLMTestCase from deepeval.metrics import AnswerRelevancyMetric def call_my_api(question: str, seed: int 42) - str: # 这里直接调用自建 API from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keydummy-key) response client.chat.completions.create( modelmock-reasoner, messages[ {role: user, content: question} ], temperature0.7, max_tokens128, seedseed, ) return response.choices[0].message.content def test_math_answer(): question 一个农场里有 3 只鸡和 2 只狗总共有多少条腿 output call_my_api(question) # 自定义判断答案里必须包含 14 assert 14 in output, f模型输出不包含 14实际输出{output} test_case LLMTestCase( inputquestion, actual_outputoutput, expected_output14, ) metric AnswerRelevancyMetric( threshold0.5, modelgpt-4o-mini ) assert_test(test_case, [metric])这里有一个关键点AnswerRelevancyMetric本身通常需要调用 OpenAI 模型来判断相关性。如果不想引入另一个模型可以只用简单的assert做规则判断或者自定义一个 Metric 类。DeepEval 的自定义 Metric 示例# 文件路径custom_metric.py from deepeval.metrics import BaseMetric from deepeval.scorer import Scorer class ContainsAnswerMetric(BaseMetric): def __init__(self, expected: str): self.expected expected self.threshold 1.0 def measure(self, test_case) - float: if self.expected in test_case.actual_output: self.success True return 1.0 self.success False return 0.0 def is_successful(self) - bool: return self.success property def __name__(self): return ContainsAnswerMetric然后用这个自定义指标跑测试# 文件路径test_with_custom_metric.py from deepeval.test_case import LLMTestCase from custom_metric import ContainsAnswerMetric test_case LLMTestCase( input一个农场里有 3 只鸡和 2 只狗总共有多少条腿, actual_outputThe answer is 14., ) metric ContainsAnswerMetric(expected14) score metric.measure(test_case) print(fScore: {score}, Success: {metric.is_successful()})这种方式的好处是评估标准完全由你定义不依赖其他模型判断。4.4 实现多数投票评估要评估 Test-Time Scaling 的效果需要对比不同采样数量 n 下的准确率。下面是一个完整的 Python 脚本。它会调用自建 API N 次提取答案并投票。# 文件路径majority_vote_eval.py import collections import random from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keydummy-key) QUESTIONS [ 一个农场里有 3 只鸡和 2 只狗总共有多少条腿, 小明有 5 个苹果给了小红 2 个还剩几个, 12 7 等于多少, ] CORRECT_ANSWERS [14, 3, 19] def extract_answer(text: str) - str: # 简单提取数字真实项目需要更严谨的解析 import re numbers re.findall(r\d, text) if not numbers: return return numbers[-1] def generate_with_api(question: str, temperature: float, seed: int) - str: response client.chat.completions.create( modelmock-reasoner, messages[{role: user, content: question}], temperaturetemperature, max_tokens128, seedseed, ) return response.choices[0].message.content def majority_vote(question: str, n: int, temperature: float, seed: int) - str: answers [] for i in range(n): raw generate_with_api(question, temperature, seed i) answer extract_answer(raw) answers.append(answer) counter collections.Counter(answers) final_answer, _ counter.most_common(1)[0] return final_answer, answers def evaluate(temperature: float 0.7, n: int 5): correct 0 for i, question in enumerate(QUESTIONS): final_answer, all_answers majority_vote(question, nn, temperaturetemperature, seed42 i) is_correct final_answer CORRECT_ANSWERS[i] correct int(is_correct) print(fQuestion {i 1}: final{final_answer}, expected{CORRECT_ANSWERS[i]}, correct{is_correct}) print(f candidates: {all_answers}) accuracy correct / len(QUESTIONS) print(fAccuracy: {accuracy:.2%}) if __name__ __main__: evaluate(temperature0.7, n5)运行效果Question 1: final14, expected14, correctTrue candidates: [14, 14, 14, 14, 14] Question 2: final3, expected3, correctTrue candidates: [3, 3, 3, 3, 3] Question 3: final19, expected19, correctTrue candidates: [19, 19, 19, 19, 19] Accuracy: 100.00%当前 mock 服务只返回固定答案所以准确率为 100%。真实场景中候选答案会有波动投票机制才会发挥明显作用。5. 可复现性实践5.1 固定随机种子与参数大多数推理框架都支持 seed 参数。评测脚本里建议对每次请求传入不同的 seed 组合比如seed 1000 question_index * 10 sample_index这样即使多轮运行同一位置的采样结果不会变化。同时把所有推理参数记录到 JSON 文件中# 文件路径save_config.py import json config { model: mock-reasoner, temperature: 0.7, top_p: 1.0, max_tokens: 128, seed: 42, n_answers: 5, voting: majority, prompt_version: v1.0, } with open(inference_config.json, w, encodingutf-8) as f: json.dump(config, f, ensure_asciiFalse, indent2)5.2 保存完整实验记录推荐每个实验一个目录experiments/ run_20250101_1200/ inference_config.json dataset.csv raw_outputs.jsonl metrics.json prompt_template.txt eval_script.py保留原始输出到 JSONL 文件可以随时复盘# 文件路径save_raw_outputs.py import json with open(raw_outputs.jsonl, a, encodingutf-8) as f: record { question: question, candidates: all_answers, final_answer: final_answer, expected: CORRECT_ANSWERS[i], config: config, } f.write(json.dumps(record, ensure_asciiFalse) \n)5.3 版本锁定不管是用 lm-evaluation-harness、promptfoo 还是 DeepEval都要锁定工具版本。pip freeze requirements.txt如果使用 npm 安装的 promptfoo则提交package-lock.json到仓库。模型版本也很关键。如果你的模型服务支持多个版本评测时要显式指定避免默认版本悄悄变化。5.4 随机性与不确定性的处理即使固定 seed在不同硬件或不同 batch 大小下结果也可能有差异。可复现性实践并不是追求“绝对相同”而是追求“在合理误差范围内一致”。更严谨的做法是多次运行评测报告平均值和标准差而不是单次结果。6. 常见问题与排查思路问题现象常见原因解决思路promptfoo 连不上自建 APIapiBaseUrl 配置错误或服务未启动先 curl 测试接口再检查 YAML 中的 URL 和路径报错 OpenAI API Key 无效自建服务不校验 key但 SDK 要求非空设置任意非空字符串如dummy-key评测结果不稳定温度较高或没有固定 seed固定 seed、降低温度、多次运行取平均多数投票效果不明显mock 服务输出单一或数据量太少更换真实模型增大 N或使用更难题库DeepEval 调用外部模型时慢AnswerRelevancyMetric依赖另一个 LLM改用自定义规则 metric或只在部分样本上使用模型打分输出答案带推理过程解析不到数字正则提取规则太简单根据模型输出格式设计更健壮的解析器实验记录不完整依赖版本或参数未保存按实验目录保存 config、dataset、raw outputs 和脚本换了评估工具后分数差异大指标口径或 prompt 模板不一致统一 prompt 模板和答案解析逻辑先在小数据集上对齐两个工具7. 最佳实践与工程建议7.1 评测数据集与 prompt 分离不要用临时写在脚本里的字符串作为测试集。把测试数据和 prompt 模板分开管理推荐使用文件组织data/questions.jsonl只存题目和标准答案。prompts/math_cot.txt存 prompt 模板包含变量占位符。这样修改 prompt 时不需要改动代码也方便对比不同 prompt 版本。7.2 评估 Metrics 尽量可解释在 Test-Time Scaling 实验中单纯看准确率有时会掩盖问题。建议同时观察平均生成 token 数。单次推理耗时。候选答案多样性。投票后正确率 vs 单次正确率。这些指标能帮你判断扩展策略是否真的有效。7.3 区分模型能力与采样策略收益如果你发现 n10 的多数投票比 n1 准确率高这不代表模型更强而是采样策略带来增益。报告里最好分开描述基础准确率n1 时的表现。扩展后准确率n10 时的表现。增益量扩展带来的提升幅度。这样能避免“刷分式评测”带来的误导。7.4 安全与授权提醒如果你要评估的是线上模型注意评测请求量不要超过服务配额。不要在评测脚本里明文存储生产环境 API Key建议使用环境变量。如果要跑大量 Prompt先在小范围测试避免触发服务端限流或安全策略。7.5 自动化与 CI 集成对业务关键指标可以把评测流程接入 CI。比如每次模型版本更新后自动运行 100 条冒烟测试用例失败则阻止发布。promptfoo 支持直接作为命令行工具集成到 CIpromptfoo eval --max-concurrency 4 promptfoo shareDeepEval 也支持与 pytest 配合直接作为测试套件运行。8. 总结与下一步学习路线本文从 Test-Time Scaling 的背景出发梳理了推理阶段扩展的核心思路重点解决了三个工程问题如何用第三方评估工具接入自建推理 API。如何自定义评估标准比如多数投票和答案包含判断。如何提升评测结果的可复现性。如果你正在做推理大模型的效果评测下一步可以优先做这几件事把现有的评测脚本改成“数据集 Prompt 模板 推理配置 评估脚本”分离的结构。引入 promptfoo 或 DeepEval 中的任意一个先把自建 API 的冒烟评测跑通。在固定 seed 和参数记录的前提下对比 n1 和 n5 的多数投票效果差异。尝试用更复杂的数据集比如 GSM8K 或 MATH 的子集观察 Test-Time Scaling 在不同难度上的表现差异。评测这件事投入产出比很高。把评估流程标准化之后后面每一次模型迭代、参数调整都能得到清晰可信的结论。希望这篇内容对你正在做的推理模型评测项目有帮助。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价