大模型评测与基准测试
大模型评测的做法是把题集和提示词模板固定下来,让每个被测模型在相同条件下把每道题各答几遍:有标准答案的题用规则判分,开放式回答交给另一家厂商的评审模型按评分细则打分,再统计每个模型的得分、耗时和花费。一把 API Key 就能调用全部被测模型。按每月完整跑 2 轮、每轮 1,000 道题、两个被测模型每题各答 3 遍估算,用各模型最低价分组的实时价格计算,模型费用每月约 ¥776,每轮约 ¥388。
价格更新于 2026年11月13日 18:03
月成本估算
假设:每月完整跑 2 轮;每轮 1,000 道题,两个被测模型每题各答 3 遍,每次输入约 800 tokens、输出约 1,200 tokens(含推理模型的思考 tokens);其中一半是开放式题目,评审模型逐个回答打分,每次输入约 2,000 tokens(评分细则、题目、参考答案和回答),输出约 200 tokens。
业务问题
选模型、写论文、做产品选型,都要在同一套题上比较几家模型的质量、成本和速度。公开榜单的题目和你的任务不一样,分数也是别人用各自的提示词和参数测出来的,很难直接拿来用。
自己跑评测,难在细节:各家接口和参数不一样,推理模型往往不接受 temperature;同一个模型名背后的版本会更新;几千个请求一起发出去会遇到限流;开放式回答还要有人或评审模型打分,而评审打出的分数本身也要经得起检验。
用一个 OpenAI 兼容接口调用全部被测模型,同一份脚本只改 model 参数。给每轮评测单独建 API Key,这一轮花了多少钱、每次请求的 tokens 和耗时,在控制台的操作日志里按 Key 一查就知道。
推荐模型组合
推荐模型按实时目录自动选取,价格取各模型最低价分组,更新于 2026年11月13日 18:03。点模型名可查看全部分组价格。
被测模型:旗舰与推理模型
gpt-5.5OpenAI
输入 ¥10 / 输出 ¥80 每百万 tokens标准分组
各家的旗舰和推理模型,常作为论文里的强基线。单价高,推理模型的思考 tokens 计入输出,通常是整轮评测里花费最大的一项;正式开跑前先用几十道题试跑,按实际 usage 估算。
被测模型:主力与轻量模型
deepseek-v4.1-flashDeepSeek
输入 ¥2.8 / 输出 ¥11.2 每百万 tokens国产模型分组
便宜很多的模型在你的题目上能做到什么程度,往往是选型时最关心的问题。备选里有国产和海外的主力、轻量模型,同一份脚本加一个 model 参数就能放进评测。
评审模型(LLM-as-a-judge)
Claude Sonnet 5.5Anthropic
输入 ¥3.2 / 输出 ¥16 每百万 tokensClaude 经济分组
只给没有标准答案的开放式回答打分。选一家不在被测名单里的厂商,减少评审偏向自家模型的可能;被测名单里有它的同厂模型时,换成备选里的模型。评审打分要先和人工评分对照,校验过再用。
参考架构
- 1
冻结题集
题目、标准答案或评分细则、提示词模板整理成带编号的 JSONL,和脚本一起纳入版本管理,评测期间不再改动。公开基准的题目可能出现在模型的训练数据里,最好再加一部分自己新写、没有公开过的题。
- 2
固定调用条件
所有模型用同一份系统提示词;支持的模型设 temperature 0 和固定 seed,不支持的去掉并在记录里写明。给这一轮单独建 API Key:额度上限设成这一轮的预算,模型限制只放行这一轮要调用的模型。分组决定线路和价格,每个模型在整轮里固定用同一个分组,跑完在操作日志里核对每条记录的分组。
- 3
逐条记录
每次调用记下请求的模型 ID、响应里返回的模型名、实际发送的参数、调用时间、耗时、finish_reason、usage 和原始输出。开跑前用 /v1/models 存一份这把 Key 可调用的模型列表。
- 4
规则判分在先
选择题、数值题和能精确匹配的短答案,用程序对照标准答案判分,结果稳定、可复现,也不用额外花钱。
- 5
评审模型打分
开放式回答按书面评分细则和参考答案打分,隐去被测模型的名字;两两比较时把两个回答交换顺序各评一次。另取 100 条左右由人工独立打分,和评审模型的分数对照,一致性达到你所在领域的要求再使用。
- 6
控制并发并汇总
用信号量限制同时在跑的请求数,从 8 个开始,看 429 的比例再调整;SDK 会退避重试,仍然失败的题单独重跑,不要从统计里直接去掉。最后按模型统计得分的均值和波动、耗时和 tokens;这一轮的总花费,在操作日志里按这一轮的 Key 名称筛选,看「筛选时段消耗」。
成本估算
假设:每月完整跑 2 轮;每轮 1,000 道题,两个被测模型每题各答 3 遍,每次输入约 800 tokens、输出约 1,200 tokens(含推理模型的思考 tokens);其中一半是开放式题目,评审模型逐个回答打分,每次输入约 2,000 tokens(评分细则、题目、参考答案和回答),输出约 200 tokens。
| 项目 | 模型 | 用量假设 | 费用 |
|---|---|---|---|
| 被测:旗舰模型 | gpt-5.5 | 6,000 次 × 输入 800 + 输出 1,200 tokens | ¥624 |
| 被测:主力与轻量模型 | deepseek-v4.1-flash | 6,000 次 × 输入 800 + 输出 1,200 tokens | ¥94.08 |
| 评审模型打分 | Claude Sonnet 5.5 | 6,000 次 × 输入 2,000 + 输出 200 tokens | ¥57.6 |
| 合计(每月) | ¥775.68 | ||
按各模型最低价分组的实时价格计算,价格更新于 2026年11月13日 18:03。
只含模型调用费用,不含人工评分。每多测一个模型,按它的单价再加一行;题目数量或重复次数翻倍,费用大致翻倍。各模型的思考 tokens 差别很大,正式开跑前先用 50 道题试跑,按实际 usage 推算。这里按各模型最低价分组估算,其中可能包含混合渠道;评测结果要写进论文、需要说清线路来源的,选标着「官方转发」的分组,单价更高,见各模型详情页的分组价格。
示例代码
用信号量控制并发,每个模型每题各答 3 遍;有标准答案的按规则判分,开放式回答隐去模型名后交给评审模型打分。不接受 temperature 的模型自动去掉这个参数,实际发送的参数和失败的题都写进结果文件。
import asyncio
import json
import os
import re
import time
from openai import AsyncOpenAI, BadRequestError
client = AsyncOpenAI(base_url="https://www.tutujin.com/v1", api_key=os.environ["TUTUJIN_API_KEY"], max_retries=4)
MODELS = ["gpt-5.5", "deepseek-v4.1-flash"]
JUDGE = "claude-sonnet-5-5" # from a vendor that is not under test
REPEATS = 3
SYSTEM = "Answer the question. End with a last line of the form 'Answer: <answer>'."
limit = asyncio.Semaphore(8) # requests in flight; raise it slowly while watching for 429
async def ask(model, messages, params):
async with limit:
started = time.time()
try:
resp = await client.chat.completions.create(model=model, messages=messages, **params)
except BadRequestError:
# many reasoning models reject temperature: retry without it and keep what was really sent
params = {k: v for k, v in params.items() if k != "temperature"}
resp = await client.chat.completions.create(model=model, messages=messages, **params)
return resp, params, round(time.time() - started, 2)
async def judge(item, answer):
"""Open-ended answers: 0-5 against a written rubric, without the name of the model that wrote them."""
prompt = (
f"Question:\n{item['prompt']}\n\nReference answer:\n{item['reference']}\n\nRubric:\n{item['rubric']}\n\n"
f"Answer to score:\n{answer}\n\n"
'Score the answer from 0 to 5 by the rubric only. Reply with JSON only: {"score": 0, "reason": ""}'
)
resp, _, _ = await ask(JUDGE, [{"role": "user", "content": prompt}], {"temperature": 0})
raw = resp.choices[0].message.content
return json.loads(raw[raw.find("{"): raw.rfind("}") + 1])["score"] / 5
async def run_one(model, item, rep):
messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": item["prompt"]}]
resp, params, latency = await ask(model, messages, {"temperature": 0, "seed": 7})
text = resp.choices[0].message.content or ""
if "answer" in item: # questions with a key are scored by rule
m = re.search(r"Answer:\s*(.+?)\s*$", text)
score = float(bool(m) and m.group(1).strip(" *.").lower() == item["answer"].lower())
else:
score = await judge(item, text)
return {
"item": item["id"], "repeat": rep, "model": model, "returned_model": resp.model, "params": params,
"score": score, "scored_by": "rule" if "answer" in item else JUDGE, "latency_s": latency,
"finish_reason": resp.choices[0].finish_reason, "usage": resp.usage.model_dump() if resp.usage else None,
"called_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "output": text,
}
async def main(path="testset.jsonl"):
items = [json.loads(line) for line in open(path, encoding="utf-8")]
jobs = [(m, it, r) for m in MODELS for it in items for r in range(REPEATS)]
results = await asyncio.gather(*(run_one(*j) for j in jobs), return_exceptions=True)
with open("results.jsonl", "a", encoding="utf-8") as f:
for (m, it, r), res in zip(jobs, results):
row = res if isinstance(res, dict) else {"item": it["id"], "repeat": r, "model": m, "error": repr(res)}
f.write(json.dumps(row, ensure_ascii=False) + "\n")
for m in MODELS:
ok = [x for x in results if isinstance(x, dict) and x["model"] == m]
failed = sum(1 for (jm, _, _), x in zip(jobs, results) if jm == m and not isinstance(x, dict))
print(m, "mean score", round(sum(x["score"] for x in ok) / max(len(ok), 1), 3),
"| mean latency", round(sum(x["latency_s"] for x in ok) / max(len(ok), 1), 2), "s",
"| failed, rerun these:", failed)
asyncio.run(main())常见问题
跑一轮完整评测要多少钱?
按每轮 1,000 道题、两个被测模型每题各答 3 遍、一半题目由评审模型打分估算,用最低价分组计算每轮约 ¥388。费用主要取决于题目数 × 模型数 × 重复次数 × 输出长度,旗舰推理模型的思考 tokens 往往是大头。
评审模型的打分可靠吗?
不能直接当真,要先校验。常见的偏差有:偏爱更长的回答,受回答出现的先后顺序影响,可能偏向同一厂商的模型,在专业领域也会判错。所以评分细则要写具体,隐去模型名,两两比较时交换顺序各评一次,并用一批人工打分的样本检验一致性;能用规则判分的题,不要交给评审模型。
设了 temperature 0 和 seed,结果就能完全复现吗?
不能。seed 只能让相同输入的输出尽量接近,不保证完全一致;推理模型通常不支持 temperature。记录实际发送的参数,每题多跑几遍,报告均值和波动,而不是单次结果。
并发开多少合适,会被限流吗?
从 8 个并发开始,看 429 的比例逐步调高;收到 429 时退避重试,仍然失败的题记下来单独重跑,不要从统计里直接去掉。不同分组走不同线路,限流情况也可能不同。长期需要更高的并发,可以加客服微信 JsnonoChat,说明模型、分组和预计的请求规模,我们评估后答复。
论文里怎么写清楚用的是哪个模型?
写完整的模型 ID(带日期后缀的通常指向固定的版本快照)、调用的日期区间、所用分组和参数、提示词模板、每题重复次数和评分方式,响应里返回的模型名也一并保存。同一个模型名背后的版本可能更新,隔一段时间重跑要重新记录。
模型对比页能代替评测吗?
不能。模型对比页列的是价格、输入输出类型、可用接口和上线日期这些事实,不包含质量评分;质量要用你自己的题集来测。
