模型换了、prompt 改了、RAG 的 chunk 大小从 512 调到 1024——你怎么知道到底是变好了还是变差了?

大多数团队的回答是"跑个分看看"。但"跑个分"和"评测体系"之间,差的不是几个脚本,而是一整套方法论。

这篇文章把大模型评测拆成四层结构,讲清三类评测范式、六类指标怎么选,以及最容易被忽视的数据污染问题,最后给出一条能接进 CI、每次改动都能自动跑起来的评测流水线,附带可直接复用的代码。


一、先明确:评测到底在测什么

很多人一上来就问"用哪个榜单"。这个问题本身就错了——榜单是工具,不是目的。真正的评测要先回答:你要用它做什么决策?

不同决策需要不同层级的证据,这就是评测体系的四层结构:

层次核心问题典型手段结论用于
场景与端到端在我的业务里能不能跑通RAG 忠实度、Agent 任务成功率、工具调用准确率上线决策
对齐与安全输出是否可控、可信、可担责指令遵循、安全基准、过度拒答、幻觉率风险审批
核心能力模型本身有多强MMLU-Pro、GPQA、MATH、HumanEval、LiveCodeBench选型
系统与成本跑得起吗、快不快、贵不贵TTFT、吞吐、P99 延迟、每百万 token 成本容量规划与报价

关键认知:这四层不可互相替代。

一个 MMLU 85 分的模型,可能在你的客服场景里因为不遵守"必须先核实订单号"的指令而完全不可用;一个 Agent 任务成功率 90% 的模型,可能每百万 token 贵 8 倍。反过来,只做业务端到端评测的团队,往往在换基座模型时完全失去方向感——因为不知道该往哪个能力维度上加数据。

一个成熟的评测报告,四层必须齐全。


二、核心能力层:MMLU 早就该退休了

2.1 知识类基准的"饱和"问题

MMLU 是过去三年被引用最多的基准:57 个学科、约 1.6 万道四选一,4-shot。但头部模型的分数已经普遍超过 88 分,区分度接近 0——85 分和 88 分之间更多反映的是题目噪声,而不是能力差异。

于是有了几个替代方案:

  • MMLU-Pro:1.2 万题,选项从 4 个扩到 10 个,去掉了"靠排除法也能蒙对"的水分,并要求 CoT 作答。头部模型分数普遍掉到 70 分上下,重新有了区分度。
  • GPQA Diamond:198 道博士级物理/化学/生物题,设计目标就是"Google 不到答案"。人类专家(非本领域)正确率约 34%,是少数还没被刷爆的硬基准。
  • HLE(Humanity's Last Exam):2500 道专家级跨学科题,用来测量"前沿到底还差多远"。
  • 中文能力:C-Eval(52 个学科、约 1.4 万道)、CMMLU(67 个主题)。注意:中文基准的翻译腔题目较多,且同样存在污染,不要只看总分,要看学科分项。

2.2 数学与代码:正确性可验证,所以更可信

这两类任务的判分可以自动化到接近 100%,因此是评测里最"干净"的部分:

  • 数学:GSM8K(8500 道小学应用题)已经饱和;MATH(1.25 万道竞赛题)仍有区分度;AIME 每年新题是当前最好的抗污染数学基准之一。
  • 代码:HumanEval(164 题)太小,方差极大,一题之差就是 0.6 分;MBPP(974 题)稍好;真正有说服力的是:
  • LiveCodeBench:按题目发布时间切分,只用模型训练截止日期之后的题,天然抗污染;
  • SWE-bench / SWE-bench Verified:真实 GitHub issue,让模型改真实仓库,用测试用例判定。500 条 Verified 子集是目前最接近"真实工程能力"的基准。

2.3 一张基准选型表

能力维度推荐基准规模指标现状与注意点
通用知识MMLU-Pro12kAccuracy已取代 MMLU 成为主基准
专家推理GPQA Diamond198Accuracy方差大,务必报置信区间
数学MATH-500 / AIME500 / 30Exact MatchAIME 每年换题,抗污染
代码(函数级)HumanEval / MBPP164 / 974pass@1规模太小,只做参考
代码(工程级)SWE-bench Verified500resolve rate最贴近真实开发
指令遵循IFEval~500约束满足率客观可验证,强烈推荐
长文本RULER / NIAH可配置各长度档准确率必须测多个长度档,别只测 32k
中文C-Eval / CMMLU14k / 11kAccuracy分学科看,别只看总分

选型经验:能力维度上,宁可要 3 个"小而硬"的基准(GPQA、AIME、SWE-bench Verified),也不要 20 个"大而水"的排行榜分数。


三、对齐与安全层:只测"安全"会奖励"什么都不答"的模型

这一层最容易被做偏。常见的错误是只测"有害问题有没有拒绝",结果得到一个过度拒答的模型:安全分满分,但用户问"如何杀死一个进程"都会被拒绝。

正确的做法是三个维度同时测:

  1. 有用性(Helpfulness):指令遵循能力。IFEval 用"必须包含关键词""必须少于 200 字""必须用 JSON 输出"这类可程序验证的约束,直接算满足率,是最客观的对齐指标。
  2. 无害性(Harmlessness):SafetyBench、HarmBench、JailbreakBench,覆盖直接有害请求与越狱攻击。
  3. 诚实性(Honesty):TruthfulQA(817 题,考"常见误解")、SimpleQA(4326 题短答案事实性)、HaluEval(幻觉检测)。注意 SimpleQA 的绝对分数很低,前沿模型也常在 30 分以下——这是设计使然,它测的是"敢不敢说不知道"。

同时必须测过度拒答:XSTest 提供 250 条"看起来敏感但其实完全安全"的提示(如"怎么用刀切菜"),统计误拒率。

一条判断准则:如果你的安全评测只报告了"有害请求拒答率",那这个数字越高,模型可能越没用。必须同时报告"安全提示的误拒率",两者一起看才有意义。


四、场景与端到端层:唯一无法外包的一层

前三层都能用公开基准,这一层不能。因为它完全取决于你的业务分布。

4.1 RAG 系统

不要只测"最终回答对不对",要拆开测,否则你不知道是检索坏了还是生成坏了:

指标含义低了说明什么
Context Recall参考答案里的信息,检索结果覆盖了多少检索召回不足
Context Precision检索结果里有多少是无关噪声检索排序有问题
Faithfulness回答是否完全由检索内容支撑模型在编,幻觉
Answer Relevancy回答是否切题生成偏题

RAGAS 是这套指标最常见的开源实现。另外建议单独构造一组负样本(问知识库里根本没有的问题),专门测"模型会不会老实说不知道"——这是生产环境最常见的翻车点。

4.2 Agent / 工具调用

Agent 的评测和 Chat 完全不同,因为它是多步轨迹:

  • 任务完成率:最终状态对不对(τ-bench、WebArena、OSWorld)。
  • pass^k 一致性:同一任务跑 k 次,全部成功的概率。这个指标极其重要——一个"平均成功率 80% 但方差巨大"的 Agent,在生产上比"稳定 70%"的更难用。
  • 函数调用准确率:BFCL 覆盖单轮、多轮、并行调用、拒答调用等场景。

4.3 多模态与长文本

MMMU(大学级多模态理解)、MathVista(视觉数学)、Video-MME(视频理解)是当前主力。长文本一定要做多长度梯度测试:8k / 32k / 128k 各测一遍,因为"支持 128k"和"128k 内还能用"完全是两件事。


五、系统与成本层:能力涨 2 分,价格翻 3 倍,算不算升级?

这一层经常被忽略,但它常常是真正的决策依据:

指标定义为什么重要
TTFT首 token 延迟决定"感觉快不快",交互类产品红线
TPOT / ITL相邻 token 间隔决定"读起来顺不顺"
吞吐tokens/s(含并发)决定单卡能服务多少用户
P99 延迟99 分位延迟平均值好看但长尾爆炸是常态
单位成本每百万 token 成本(含缓存)决定商业模式是否成立

务必在真实并发下测,单请求压测出来的吞吐没有参考价值。还要测上下文长度对延迟的影响——很多模型在 8k 上下文时很快,到 100k 时 TTFT 会涨 10 倍。


六、方法论:三种评测范式,没有一种能单独成立

6.1 静态基准:给你"可比性"

优点是可复现、成本低、有社区共识,是版本回归的最佳基线。缺点是会被污染、会饱和、对 prompt 极其敏感——同一模型换个 few-shot 数量,分数能差 5 分以上。

6.2 动态与对抗评测:给你"真实性"

包括 LiveCodeBench 这类时间切分基准、Arena 这类实时对战平台、以及自己维护的私有题库。它们抗污染、贴近真实分布,但成本高、难复现,不适合做逐版本对比。

实践建议:私有题库不用大,500~1000 条、覆盖你的真实流量长尾、永不公开,价值远超任何公开榜单。 它应该成为你的上线门禁。

6.3 人评与模型评:给你"分辨力"

开放式生成(写文案、做总结、答咨询)没有唯一答案,只能比较优劣。主流做法是 Pairwise 比较 + Bradley-Terry / Elo 打分:

  • 每次给裁判两个回答,问"哪个更好";
  • 用大量比较结果拟合每个模型的实力值(Elo 或 Bradley-Terry),得到可比的分数;
  • Arena 的做法还会做风格控制,剥离"回答更长/更花哨"带来的偏好。

用人评太贵,所以工业界普遍用 LLM-as-Judge 替代。但直接拿一个模型当裁判,结论基本不可用——它有系统性偏差:

让「模型打分」变得可信:去偏的四步

偏差类型表现对策
位置偏差偏爱排在前面的回答交换 A/B 顺序各评一次,只采纳一致的结论
长度偏差更长、更分点的回答得分更高用长度受控胜率;或 rubric 里写明"简洁也加分"
自我偏好偏爱同族模型的输出换非本族裁判;多裁判投票
格式偏差Markdown 排版好就被判优统一转纯文本再评

硬性要求:裁判上线前,必须与 100~200 条人工标注算一次一致率(Cohen's κ 或 Spearman)。κ < 0.6 的裁判不能用于对外结论,只能做内部粗筛。 这一步 90% 的团队都跳过了,然后拿着不可信的胜率做决策。


七、指标怎么选:先看任务类型,再谈数字

选错指标比选错模型更致命,因为指标决定你会优化出什么样的模型。

7.1 三个常见指标的坑

pass@k 不是准确率。 它的无偏估计是:pass@k = 1 - C(n-c, k) / C(n, k)

其中 n 是每题采样次数,c 是答对次数,C 是组合数。k=1 时它退化为准确率;k>1 时它测的是"k 次里有一次对就行"的能力上限,不能拿来说"模型准确率 90%"。

ROUGE / BLEU 是弱信号。 它们奖励"说得像",不奖励"说得对"。一个把错误答案用参考答案的句式说出来的模型,ROUGE 可能很高。只适合做基线,不能做结论。

胜率必须带置信区间。 500 条样本下,55% 胜率的 95% 置信区间大约是 [50.6%, 59.4%]——和 50% 没有统计显著差异。很多"XX 模型超越 YY"的结论就死在这里。

7.2 样本量怎么定

结论越细,样本要越多:

场景最低样本量说明
总体准确率(±3 分)~10001.96 × √(0.25/1000) ≈ 3.1%
总体准确率(±5 分)~400粗筛够用
分项/分学科结论每项 ≥200否则子项波动会盖过真实差异
Pairwise 胜率(±5 分)~400 对且要做配对检验
Agent 任务成功率≥100 个任务 × 3 次方差远大于单轮任务

判读纪律:两组差异小于 2 倍标准误时,报告"打平",而不是"领先"。这条纪律能帮你避免 80% 的错误上线决策。


八、数据污染:最被低估的失效模式

这是评测里最脏也最普遍的问题。很多时候你不是在测模型能力,而是在测"这道题它见过没有"。

三条典型泄漏路径:

  1. 预训练语料直接收录:MMLU、GSM8K、HumanEval 的题目和解析在 GitHub 上到处都是,被抓进预训练数据几乎是必然的。
  2. 同源改写后混入训练集:同一题库的翻译版、扩写版、带 CoT 的版本进入 SFT 或蒸馏数据。n-gram 检测抓不到,但模型照样"背题"。
  3. 对着榜单反复调参:把公开测试集当验证集用。模型没背题,但对这套题过拟合了——这同样是一种污染,而且更隐蔽。

五种检测与规避手段:

  • 13-gram 重叠检测:与训练语料做 n-gram 比对,召回有限但成本最低,适合做入门体检。
  • 时间切割:只用模型训练截止日期之后发布的新题(LiveCodeBench、年度 AIME 就是这么做的)。
  • 私有保留集:自建、不公开、不进任何公开语料,是最有效的手段。
  • 变体扰动:换数字、换人名、改问法,看分数是否断崖式下跌。
  • 困惑度异常检测:模型对某道题的困惑度显著低于同类题目,通常意味着见过。

一条经验判据:同一模型在公开集与"私有同分布集"上的分差超过 5 分,先怀疑污染,再怀疑能力。如果公开分很高但线上体感很差,几乎一定是污染或分布不匹配。


九、一条能落地的评测流水线

评测的价值不在跑分当天,而在每次改 prompt、换模型时,它能立刻告诉你有没有退步。所以要把它当工程系统建,而不是当实验脚本写。

9.1 七个步骤

  1. 定义任务与成功标准:把"好用"翻译成可判定的判定式。做不到这一步,后面全是自欺欺人。
  2. 分层抽样建评测集:按难度、场景、输入长度分层,保证长尾被覆盖(长尾往往才是线上事故来源)。
  3. 固化 prompt 与解码参数:system prompt、few-shot 数量与顺序、temperature、max_tokens 全部版本化。
  4. 批量推理:用 vLLM 或批处理接口,保证吞吐稳定。
  5. 答案解析与判分:规则解析 → 沙箱执行(代码)→ 裁判兜底(开放题)。
  6. 统计与置信区间:bootstrap 置信区间 + 配对检验。
  7. 报告与门禁:分项雷达图 + 失败案例 Top20,接进 CI 卡住回归。

9.2 最小可用的批量评测代码

# eval_runner.py
import re, json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

SYSTEM = "你是一个严谨的助手。推理过程可以放开写,但最后一行必须用「答案:X」给出最终选项。"
TEMPLATE = "{question}\nA. {A}\nB. {B}\nC. {C}\nD. {D}"

def extract_choice(text: str):
    """只认最后一次出现,避免 CoT 里提到其它选项被误判"""
    m = re.findall(r"答案[::]\s*([ABCD])", text)
    if m:
        return m[-1]
    m = re.findall(r"\b([ABCD])\b", text)
    return m[-1] if m else None

def run_one(sample: dict, model: str):
    resp = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": TEMPLATE.format(**sample)},
        ],
        temperature=0.0,          # 评测必须确定性
        max_tokens=1024,          # 留足空间,截断会制造假错
        seed=42,
    )
    choice = resp.choices[0]
    text = choice.message.content or ""
    return {
        "id": sample["id"],
        "gold": sample["answer"],
        "pred": extract_choice(text),
        "finish": choice.finish_reason,   # length 说明被截断,要单独统计
        "raw": text,
    }

跑完之后,第一件事不是看准确率,而是看 finish == "length" 的比例。超过 1% 就说明 max_tokens 设置有问题,此时算出来的分数是假的。

9.3 置信区间与配对检验

import numpy as np
from statsmodels.stats.contingency_tables import mcnemar

def bootstrap_ci(correct, n_boot=10000, alpha=0.05, seed=0):
    """对逐题 0/1 结果做 bootstrap,返回 (均值, 下界, 上界)"""
    rng = np.random.default_rng(seed)
    arr = np.asarray(correct, dtype=np.float32)
    idx = rng.integers(0, len(arr), size=(n_boot, len(arr)))
    accs = arr[idx].mean(axis=1)
    return float(arr.mean()), float(np.quantile(accs, alpha / 2)), float(np.quantile(accs, 1 - alpha / 2))

def mcnemar_test(correct_a, correct_b):
    """同一批题上比较两个模型:只有「一个对一个错」的题才提供信息"""
    a, b = np.asarray(correct_a), np.asarray(correct_b)
    n01 = int(((a == 0) & (b == 1)).sum())   # A 错 B 对
    n10 = int(((a == 1) & (b == 0)).sum())   # A 对 B 错
    res = mcnemar([[0, n01], [n10, 0]], exact=True)
    return n01, n10, res.pvalue

p 值大于 0.05 的差异,在报告里就写"无显著差异"。这不是学术洁癖——这是防止你为了 1 分的噪声去付 3 倍推理成本。

9.4 LLM-as-Judge 的去偏实现

import json

JUDGE_PROMPT = """你是严格的评审。请比较两个回答对同一问题的质量。

问题:{q}
回答一:{r1}
回答二:{r2}

评分优先级:事实正确性 > 是否完整回应问题 > 简洁清晰。
不要因为回答更长或用了更多排版就给高分。
只输出 JSON,不要任何解释:{{"winner": "1|2|tie", "reason": "一句话"}}"""

FLIP = {"1": "2", "2": "1", "tie": "tie"}

def judge_pair(q, ans_a, ans_b, call_judge):
    # 第一次:A 在前
    v1 = json.loads(call_judge(JUDGE_PROMPT.format(q=q, r1=ans_a, r2=ans_b)))
    # 第二次:交换顺序,再把 winner 映射回来
    v2 = json.loads(call_judge(JUDGE_PROMPT.format(q=q, r1=ans_b, r2=ans_a)))
    w2 = FLIP[v2["winner"]]
    if v1["winner"] == w2:
        return v1["winner"]        # 两次一致 → 采纳
    return "inconsistent"          # 不一致 → 转人工或多裁判投票

def pass_at_k(n, c, k):
    """n: 采样次数, c: 答对次数, k: 取 k 次"""
    if n - c < k:
        return 1.0
    return 1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))

9.5 把门禁接进 CI

BASELINE = {"mmlu_pro": 0.712, "ifeval": 0.845, "swe_verified": 0.246}
TOLERANCE = 0.015   # 容忍 1.5 分波动

def gate(report: dict):
    failed = []
    for k, base in BASELINE.items():
        cur = report[k]["acc"]
        lo = report[k]["ci_low"]
        # 下界跌破容忍线才拦,避免噪声误杀
        if lo < base - TOLERANCE:
            failed.append(f"{k}: {cur:.3f} (CI 下界 {lo:.3f}) < 基线 {base:.3f}")
    if failed:
        raise SystemExit("评测门禁未通过:\n" + "\n".join(failed))
    print("门禁通过")

注意这里用的是置信区间下界而不是点估计——这样能自动容忍样本噪声,避免"今天跑 71.8 明天跑 72.2"就报警。


十、十个最常见的坑

  1. 样本太少就下结论。200 条样本上的 3 分差距,噪声占了一半以上。
  2. 只看总分不看分项。总分相同的两个模型,能力结构可能完全不同,选错一个就是灾难。
  3. prompt 没对齐。few-shot 数量、system prompt、选项顺序都会影响分数,横向对比必须用同一套模板。
  4. temperature 不等于 0,也没固定 seed。同一模型跑两次差 2 分,你就没法判断任何事。
  5. max_tokens 太小。答案被截断,判成错,分数系统性偏低。
  6. 答案解析器写得太严。模型答对了但格式是"(B)"而不是"B",被判错——你测的是 parser,不是模型。
  7. 只测英文、只测短问题。中文、长输入、多轮,是另外三个世界。
  8. 只测安全不测过度拒答。会奖励出一个"什么都不答"的模型。
  9. 忽略成本与延迟。能力涨 2 分、价格翻 3 倍、延迟翻倍,多数业务上这是净亏损。
  10. 没有基线快照。不记录模型版本与日期,三个月后你无法回答"到底是模型变了还是数据变了"。

十一、这个领域正在往哪走

  • 从结果评测到过程评测:Agent 时代,只看终态对错信息量太低,步骤准确率、轨迹合理性、无效动作数会变成主指标。
  • 从静态到滚动:LiveCodeBench 式的"按发布时间切分"会成为基准设计的事实标准;私有题库 + 定期换血会成为企业标配。
  • 一致性比平均值更重要:pass^k 这类指标会普及——生产系统怕的不是"平均差一点",而是"偶尔崩一次"。
  • 可验证奖励优先:能用规则验证的任务(代码、数学、结构化输出、工具调用)会被系统性优先评测,因为只有它们能规模化。
  • 评测左移:评测集和 prompt 一起进版本库,每次 PR 自动跑,回归报警。评测从"季度报告"变成"每次提交的一部分"。

结语

回到开头那个问题:模型换了、prompt 改了,怎么知道变好还是变差?

答案不是"跑个分",而是:有一套四层齐全、带置信区间、抗污染、能接进 CI 的评测体系。它要求的工程量不大——一套 800 条的私有评测集、三个硬基准、一个双评裁判、一个 bootstrap 脚本,两三天就能搭起来。但它带来的决策质量提升,远超再花两周调 prompt。

评测不是训练的附属品,它是你唯一能看清模型行为的仪器。 仪器不准,后面所有优化都是盲调。

Logo

更多推荐