跳到正文
土土金API

大模型评测与基准测试

大模型评测的做法是把题集和提示词模板固定下来,让每个被测模型在相同条件下把每道题各答几遍:有标准答案的题用规则判分,开放式回答交给另一家厂商的评审模型按评分细则打分,再统计每个模型的得分、耗时和花费。一把 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。

¥775.68/ 月

  • 被测:旗舰模型¥624
  • 被测:主力与轻量模型¥94.08
  • 评审模型打分¥57.6
看用量假设

业务问题

选模型、写论文、做产品选型,都要在同一套题上比较几家模型的质量、成本和速度。公开榜单的题目和你的任务不一样,分数也是别人用各自的提示词和参数测出来的,很难直接拿来用。

自己跑评测,难在细节:各家接口和参数不一样,推理模型往往不接受 temperature;同一个模型名背后的版本会更新;几千个请求一起发出去会遇到限流;开放式回答还要有人或评审模型打分,而评审打出的分数本身也要经得起检验。

用一个 OpenAI 兼容接口调用全部被测模型,同一份脚本只改 model 参数。给每轮评测单独建 API Key,这一轮花了多少钱、每次请求的 tokens 和耗时,在控制台的操作日志里按 Key 一查就知道。

推荐模型组合

推荐模型按实时目录自动选取,价格取各模型最低价分组,更新于 2026年11月13日 18:03。点模型名可查看全部分组价格。

参考架构

  1. 1

    冻结题集

    题目、标准答案或评分细则、提示词模板整理成带编号的 JSONL,和脚本一起纳入版本管理,评测期间不再改动。公开基准的题目可能出现在模型的训练数据里,最好再加一部分自己新写、没有公开过的题。

  2. 2

    固定调用条件

    所有模型用同一份系统提示词;支持的模型设 temperature 0 和固定 seed,不支持的去掉并在记录里写明。给这一轮单独建 API Key:额度上限设成这一轮的预算,模型限制只放行这一轮要调用的模型。分组决定线路和价格,每个模型在整轮里固定用同一个分组,跑完在操作日志里核对每条记录的分组。

  3. 3

    逐条记录

    每次调用记下请求的模型 ID、响应里返回的模型名、实际发送的参数、调用时间、耗时、finish_reason、usage 和原始输出。开跑前用 /v1/models 存一份这把 Key 可调用的模型列表。

  4. 4

    规则判分在先

    选择题、数值题和能精确匹配的短答案,用程序对照标准答案判分,结果稳定、可复现,也不用额外花钱。

  5. 5

    评审模型打分

    开放式回答按书面评分细则和参考答案打分,隐去被测模型的名字;两两比较时把两个回答交换顺序各评一次。另取 100 条左右由人工独立打分,和评审模型的分数对照,一致性达到你所在领域的要求再使用。

  6. 6

    控制并发并汇总

    用信号量限制同时在跑的请求数,从 8 个开始,看 429 的比例再调整;SDK 会退避重试,仍然失败的题单独重跑,不要从统计里直接去掉。最后按模型统计得分的均值和波动、耗时和 tokens;这一轮的总花费,在操作日志里按这一轮的 Key 名称筛选,看「筛选时段消耗」。

成本估算

假设:每月完整跑 2 轮;每轮 1,000 道题,两个被测模型每题各答 3 遍,每次输入约 800 tokens、输出约 1,200 tokens(含推理模型的思考 tokens);其中一半是开放式题目,评审模型逐个回答打分,每次输入约 2,000 tokens(评分细则、题目、参考答案和回答),输出约 200 tokens。

项目模型用量假设费用
被测:旗舰模型gpt-5.56,000 次 × 输入 800 + 输出 1,200 tokens¥624
被测:主力与轻量模型deepseek-v4.1-flash6,000 次 × 输入 800 + 输出 1,200 tokens¥94.08
评审模型打分Claude Sonnet 5.56,000 次 × 输入 2,000 + 输出 200 tokens¥57.6
合计(每月)¥775.68

按各模型最低价分组的实时价格计算,价格更新于 2026年11月13日 18:03。

只含模型调用费用,不含人工评分。每多测一个模型,按它的单价再加一行;题目数量或重复次数翻倍,费用大致翻倍。各模型的思考 tokens 差别很大,正式开跑前先用 50 道题试跑,按实际 usage 推算。这里按各模型最低价分组估算,其中可能包含混合渠道;评测结果要写进论文、需要说清线路来源的,选标着「官方转发」的分组,单价更高,见各模型详情页的分组价格。

示例代码

用信号量控制并发,每个模型每题各答 3 遍;有标准答案的按规则判分,开放式回答隐去模型名后交给评审模型打分。不接受 temperature 的模型自动去掉这个参数,实际发送的参数和失败的题都写进结果文件。

示例代码(Python,并发评测、规则判分与评审打分)
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())

用一把 Key 试试这个方案

注册后在控制台创建 API Key,把上面代码里的 base_url 和 Key 换成你的就能运行。企业采购、开发票可以加客服微信 JsnonoChat。

土土金客服微信二维码
微信JsnonoChat

扫码或搜索微信号添加客服

常见问题

跑一轮完整评测要多少钱?

按每轮 1,000 道题、两个被测模型每题各答 3 遍、一半题目由评审模型打分估算,用最低价分组计算每轮约 ¥388。费用主要取决于题目数 × 模型数 × 重复次数 × 输出长度,旗舰推理模型的思考 tokens 往往是大头。

评审模型的打分可靠吗?

不能直接当真,要先校验。常见的偏差有:偏爱更长的回答,受回答出现的先后顺序影响,可能偏向同一厂商的模型,在专业领域也会判错。所以评分细则要写具体,隐去模型名,两两比较时交换顺序各评一次,并用一批人工打分的样本检验一致性;能用规则判分的题,不要交给评审模型。

设了 temperature 0 和 seed,结果就能完全复现吗?

不能。seed 只能让相同输入的输出尽量接近,不保证完全一致;推理模型通常不支持 temperature。记录实际发送的参数,每题多跑几遍,报告均值和波动,而不是单次结果。

并发开多少合适,会被限流吗?

从 8 个并发开始,看 429 的比例逐步调高;收到 429 时退避重试,仍然失败的题记下来单独重跑,不要从统计里直接去掉。不同分组走不同线路,限流情况也可能不同。长期需要更高的并发,可以加客服微信 JsnonoChat,说明模型、分组和预计的请求规模,我们评估后答复。

论文里怎么写清楚用的是哪个模型?

写完整的模型 ID(带日期后缀的通常指向固定的版本快照)、调用的日期区间、所用分组和参数、提示词模板、每题重复次数和评分方式,响应里返回的模型名也一并保存。同一个模型名背后的版本可能更新,隔一段时间重跑要重新记录。

模型对比页能代替评测吗?

不能。模型对比页列的是价格、输入输出类型、可用接口和上线日期这些事实,不包含质量评分;质量要用你自己的题集来测。