两个模型在 GPQA Diamond 上分别得 72% 和 69%,新闻标题写”A 超越 B”。可这个 benchmark 只有 198 道题,换一批同样难的题,排名很可能就倒过来。评估看起来是最简单的一步:写 prompt,拿回答,算准确率。真正难的是这一步之前和之后:你想测的”会推理""好用”是一个抽象的东西,必须落到一个具体的数上;而这个数又会被题目泄漏、裁判偏好、题量不足一点点扭曲。这篇沿着评估方式的演变,看每一种在哪里坏掉。
最直接的尺子:困惑度
语言模型就是 token 序列上的概率分布,最直接的评估是看它给一份没见过的文本分配了多少概率。概率连乘会小到没法看,所以按 token 数取几何平均再取倒数,这就是困惑度(perplexity):
也就是平均每 token 交叉熵的指数。它有一个好记的含义:PPL 为 k,相当于模型每一步都在 k 个等可能的词里犹豫。
import math
probs = [0.5, 0.25, 0.5, 0.125]
nll = -sum(math.log(p) for p in probs) / len(probs)
print(f"平均交叉熵 {nll:.4f} nats perplexity {math.exp(nll):.3f}")
print("均匀猜 32000 词表的 perplexity:", math.exp(-math.log(1/32000)))平均交叉熵 1.2130 nats perplexity 3.364
均匀猜 32000 词表的 perplexity: 32000.000000000007四个 token 的概率乘起来是 1/128,开四次方再取倒数约 3.36。一个什么都没学的模型在 32000 词表上 PPL 就是 32000,这给了一个上限参照。
困惑度的优点是平滑:它随模型规模、训练 token 数连续变化,几乎没有噪声,所以拟合 资源核算与 Scaling Laws 时用的都是 loss 或 PPL,而不是 benchmark 准确率。GPT-2 时代还有一个更大胆的信念:真实分布 t 的熵是 PPL 的下限,只在模型 p 等于 t 时取到;如果 p = t,那么 p(答案 | 问题) 就能解所有任务,所以一直压低 PPL 就能通向通用智能。这更像一种信仰,但它确实推动了很多人坚持做 scaling。
困惑度坏在两个地方。第一,它管得太多。“Stanford was founded in 1885”这句话里,1885 考的是世界知识,句首的 Stanford 能不能猜中和你想测的能力无关,PPL 却对每个 token 一视同仁。修正是只对关心的部分计分,算条件困惑度 p(response | prompt)。很多经典 benchmark 本质就是这个:LAMBADA 预测一段话的最后一个词,HellaSwag 比较几个候选补全的概率哪个最高。
第二,它需要信任对方。PPL 排行榜只能拿到对方报上来的 log 概率。如果一个”模型”对任何 token 都返回 log 概率 0(概率 1),PPL 就是理论最低的 1,因为没人检查这些概率在整个词表上加起来是否等于 1。下游任务的排行榜只需要 回答 = 模型(prompt),自己算对错,就没有这个漏洞。不同分词器的模型之间 PPL 也不能直接比,因为分母 |D| 的 token 数不同;要比就换成每字节或每字符的 bits。
换算很直接:总交叉熵(以 nats 计)是”平均每 token 交叉熵 × token 数”,除以 ln 2 变成 bits,再除以文本的字节数,就得到 bits-per-byte。分母从 token 数换成字节数之后,一个把文本切得更碎的分词器不再能靠”每个 token 更容易猜”占便宜。举个数:同一段 1000 字节的文本,分词器甲切成 250 个 token、平均每 token 2.0 nats,分词器乙切成 400 个 token、平均每 token 1.4 nats。按 PPL 看乙更低(约 4.1 对 7.4),按 bits-per-byte 看甲是 250 × 2.0 / ln2 / 1000 ≈ 0.72,乙是 400 × 1.4 / ln2 / 1000 ≈ 0.81,甲反而更好。
关于困惑度作为评估指标,下面哪些说法正确?
平滑是它在开发阶段被大量使用的原因;"管太多"可以用条件困惑度修正;合法性要求概率在词表上加起来等于 1,外人看不到。
分词器不同,每个 token 的含义和 token 总数都不同,PPL 不可比,要换成每字节的 bits;PPL 为 1 意味着每个 token 概率都是 1,对自然语言几乎只可能是作弊或测试集被背下来。
考试:有标准答案的 benchmark
既然 PPL 离”会不会做事”太远,自然的下一步是出题考它。考试能控制科目和难度,还能设计成有唯一答案、容易判分。
MMLU(2020)是 57 个科目的四选一题,从初中数学到美国法律。名字叫”语言理解”,实际测的主要是知识。GPT-3 当年小模型约 25%(等于随机猜),最大的约 44%。后来的 MMLU-Pro 去掉了噪声题和太简单的题,选项从 4 个扩到 10 个,随机猜的基线从 25% 降到 10%,模型准确率普遍掉了 16 到 33 个百分点。
GPQA(2023)把”难”做到了极致:61 位博士出题,经过专家两轮审核后,再交给能用 Google 查 30 分钟的非专家去做。博士专家正确率 65%,非专家 34%,所以叫 “Google-Proof”。两位专家都答对、多数非专家答错的 198 道题组成 GPQA Diamond。HLE(Humanity’s Last Exam,2025)更进一步:先用前沿模型筛掉它们能答对的题,7 万次投稿最后留下 2500 道公开题,外加一份私有集。发布时主流模型都不到 10%。
同一份考卷,怎么”交卷”也会改变分数。早期的做法是比较概率:把 A/B/C/D 四个选项分别接在题目后面,看模型给哪个的 log 概率最高,或者只看下一个 token 是哪个字母的概率最大。这对 base model 很自然,也不需要生成。现在更常见的是让模型先写一段推理(chain of thought),再从输出里用正则或另一个模型抽出最终答案。抽取这一步本身就会出错:模型写了”答案是 B,不过 C 也说得通”,算哪个?这就是为什么同一个模型在不同评测框架下的 MMLU 分数能差好几个点。few-shot 示例放几个、选项顺序怎么排、prompt 用什么措辞,都会影响结果。报告一个 benchmark 分数时,评测方式和分数一样是结论的一部分。
另一个容易忽略的参照是随机基线。四选一的随机猜是 25%,所以 MMLU 上 30% 几乎等于什么都没学会;MMLU-Pro 换成十个选项,基线降到 10%,同样的分数含义完全不同。小模型在多选题上的曲线常常长期贴着随机基线,到某个规模才突然起飞,这种”涌现”一部分来自指标本身的不连续:准确率只认最终答案对不对,而模型对正确答案的概率可能早就在平滑上升,只是还没超过其他选项。换成困惑度或正确选项的概率去看,曲线往往是连续的。
一个 1B 模型在 MMLU(四选一)上得 27%,在 MMLU-Pro(十选一)上得 12%。下面哪个解读最合理?
多选题的分数要先和随机猜比:四选一的下限是 25%,十选一是 10%,27% 和 12% 都贴着基线。
直接比较两个原始分数忽略了基线不同;"12% 不错"同样没扣掉 10% 的随机部分;任何多选题瞎猜都有正的期望分数。
考试类 benchmark 的命运是饱和:MMLU、GPQA 在前沿模型上已接近满分,HLE 一两年内也被大幅推高。饱和的 benchmark 分不出前沿模型谁强,但开发小模型时仍然有用。更根本的局限是:多选题可以做得任意难,却限制了能问的东西。没人会拿四选一去问助手怎么做甜菜沙拉。
开放式回答:人来评,还是模型来评
开放式回答没有标准答案,不能 exact match。最直接的办法是让人比较。Chatbot Arena 让任何人输入一个 prompt,拿到两个匿名模型的回答,选哪个更好,然后给每个模型拟合一个 Elo 分数:
分差 0 胜率 0.5,分差 100 约 0.64,分差 400 约 0.91。拟合方法是 Bradley–Terry 模型:把 Elo 当参数,对所有比较结果做 logistic 回归。它有一个关键好处:不需要每对模型都直接比过,只要比较关系连成一张连通图,就能推出所有人的相对位置。下面三个模型只比过 0 对 1、1 对 2,从没让 0 和 2 直接交手:
import numpy as np
rng = np.random.default_rng(1)
true = np.array([1100., 1000., 950.]) # 三个模型的"真实" Elo
def p_win(ra, rb): return 1 / (1 + 10 ** ((rb - ra) / 400))
games = []
for i, j in [(0, 1), (1, 2)]:
for _ in range(2000):
games.append((i, j, rng.random() < p_win(true[i], true[j])))
r = np.full(3, 1000.)
for _ in range(2000): # 对数似然梯度上升 = logistic 回归
g = np.zeros(3)
for i, j, w in games:
e = p_win(r[i], r[j]); g[i] += w - e; g[j] -= w - e
r += 20 * g / len(games)
r -= r.mean() - true.mean() # Elo 只有差值有意义,固定一个平移
print("拟合 Elo:", r.round(0), " 预测 0 胜 2 的概率:", round(p_win(r[0], r[2]), 3))拟合 Elo: [1104. 1001. 945.] 预测 0 胜 2 的概率: 0.714真实的 0 胜 2 概率是 0.703,模型从没见过这对组合,却通过中间人 1 推了出来。实际排行榜还会用 bootstrap 给每个分数一个置信区间;分差 50 只对应约 57% 的胜率,相邻名次的区间经常重叠。
Arena 的 prompt 是真实的,用户有动机认真用,新模型随时加入。问题也随之而来:用户是谁、有没有人刷分?二元偏好把风格和正确性混在一起,而提问者往往不知道正确答案,判断不了哪个对,于是容易偏爱讨好、自信、更长的回答。
人工评估贵且慢,于是有了 LLM-as-a-judge:让一个强模型当裁判。AlpacaEval 用 805 条指令,统计被测模型对基线模型的胜率,裁判是 GPT-4。它和 Arena 排名高度相关,可以在部署前快速估计。但它很快被刷了:很多微调模型只是把回答写得更长就拿到高分。LLM 裁判有三类典型偏差:
- 位置偏差:偏爱放在前面(或后面)的回答。缓解办法是交换 A/B 各评一次,结论不一致就记平局。
- 冗长偏差:偏爱长回答。AlpacaEval 2.0 用回归把长度的影响扣掉,得到 length-controlled 胜率,和 Arena 的 Spearman 相关约 0.98。
- 自我偏好:偏爱和自己风格接近的回答,裁判和被测模型同族时尤其要小心。
WildBench 的改进是给裁判一份针对每个 prompt 生成的 checklist,相当于给评分标准。不论人还是模型,有 rubric 比没有可靠得多;两两比较也比给单个回答打绝对分(7 分还是 8 分)信号更强。裁判本身也要被评估:常用的检查是拿一小批人工标注过的两两比较,看裁判和人类多数意见的一致率,再和人类标注员彼此之间的一致率对比。如果裁判和人的一致率已经接近人与人之间的一致率,再追求更高就没有意义,因为剩下的分歧本来就是主观的。还要注意一个循环:新的自动评估都在用”和 Arena 的相关性”证明自己,可 Arena 本身也不是真值。
一个微调模型只是把回答写得更长,就在 LLM 裁判的胜率榜上排名大涨。下面哪些措施能缓解?
长度控制直接针对冗长偏差;rubric 让裁判按内容打分;多裁判和异族裁判减轻自我偏好。
绝对打分的信号比两两比较更弱,冗长偏差同样存在;交换位置治的是位置偏差,对长度偏差无效,两次评估都会偏向长的那个。
从说到做:agent benchmark
前面评的都是模型说了什么,agent benchmark 评它做了什么。SWE-bench 给一个真实 Python 仓库和一个 GitHub issue,让模型提交补丁,用仓库自己的单元测试判分:原来失败的测试要通过,原来通过的不能弄坏。Terminal-Bench 把任务放进一个终端环境,CyBench 用 CTF 夺旗题,MLE-bench 让 agent 去打 Kaggle 比赛。
agent 由两部分组成:语言模型,加上决定怎么调用它的 scaffold(规划、调用工具、管理上下文的那层逻辑)。同一个模型配不同 scaffold,Terminal-Bench 上的分数能差好几个百分点。所以评估一个 agent 等于同时评估模型和 scaffold,排行榜上的一行数字不能只归功于模型。scaffold 怎么设计见 RAG 与 Agent。
agent benchmark 也更难保证自己是对的。SWE-bench 原版有不少单元测试不严、题目描述不清的问题,OpenAI 人工审核后发布了 SWE-bench Verified;有研究发现某些 agent 任务里,一个什么都不做的 agent 也能”通过”。环境越复杂,测试越可能有漏洞。
agent 评估还多了几个维度。一是随机性:一条轨迹几十步,每步都有采样,同一个任务跑两遍可能一次成功一次失败,所以要报告多次运行的平均,或者区分 pass@1(一次就做对的比例)和 pass@k(k 次里至少一次做对的比例)。pass@k 随 k 单调上升,k 大时它衡量的是”能力上限”,而用户真正体验到的通常是 pass@1。二是成本:一个任务可能消耗几十万 token、跑上几十分钟,两个 agent 分数相同而成本差十倍,不能算打平。三是过程:通过了测试不代表解法正确,agent 可能改掉了测试本身,或者从 git 历史里翻出了答案,所以要像审代码一样去读轨迹,Transluce 的 Docent 就是用 LLM 批量检查 agent 轨迹、找出这类问题的工具。
另一个方向是 ARC-AGI:给几组网格图的输入输出,推出变换规则。它刻意不需要语言和世界知识,每题都不同,背题没用。2020 到 2023 年的预训练模型在上面几乎都是 0,推理模型出现后分数才快速上升。它是目前”把推理和知识分开测”最认真的尝试。不过它的曲线也提醒了一点:推理模型本身是建立在预训练之上的,ARC-AGI 上看不出预训练的贡献,不等于预训练不重要;它只限定在人类能做的推理范围内,测不了 IMO 金牌那种超出常人的能力。
题目泄露了怎么办:污染
机器学习最基本的规则是不在测试集上训练。ImageNet 时代有明确的 train/test 划分,大家都守规矩。现在的模型在整个互联网上训练,而且不公开数据。benchmark 题目本身、它的解答、讨论它的博客,都可能被爬进训练集。多数污染不是有人故意拿测试集训练,而是题目从别的渠道流到了网上。
训练数据不公开时,有几条路:
| 路线 | 做法 | 局限 |
|---|---|---|
| 从模型推断 | benchmark 题目顺序本可随意打乱;如果模型对原始顺序的 log 概率明显高于打乱后的,说明它见过原文 | 只能查出整份按原样进入训练的情况 |
| 用新数据 | 只用模型训练截止日期之后的题(LiveCodeBench) | 新内容可能复制自旧内容 |
| 用私有数据 | 保留不公开的测试集(HLE 的私有集),或拿自己从未发布的文本测 PPL | 评测时题目仍要发给模型,只能希望不被拿去训练 |
| 定报告规范 | 要求模型提供方报告训练集与测试集的重叠 | 靠自觉 |
如果你能看到训练数据,就可以主动去污染:用 预训练数据 里讲的 n-gram 匹配或 MinHash 去重,把和测试集重叠的文档从训练集删掉。方向要注意:以测试集为基准去查训练集,并且在最终数据切分之后做,免得处理顺序让泄漏残留。
除了污染,题目本身也会错。研究者给 GSM8K、MMLU 做了人工复核的 “Platinum” 版本,发现有些题根本没法从给出的文本推出答案。无论开发还是使用 benchmark,都要真的去看模型的输出,确认测到的是你以为在测的东西。
一个模型对某 benchmark 按原始题目顺序拼接时的 log 概率,明显高于把题目随机打乱后的 log 概率。最合理的推断是什么?
benchmark 里题目之间是可交换的,没有理由让某个特定顺序更"自然";模型偏爱原始顺序,最可能是它背过这个顺序的原文。
打乱的是题与题之间的顺序,每道题内容不变;难度和推理能力解释不了"顺序本身"带来的概率差。
差三分算不算赢:统计功效
回到开头的问题。准确率 p 是对 n 道题的抽样估计,标准误是 ,95% 置信区间约 ±1.96 SE。p = 0.7 时,500 道题的区间约 ±4 个百分点,198 道题约 ±6.4 个百分点。两个模型差 3 个点,各自的区间大面积重叠,单看这个说明不了谁更强。
但”区间重叠”其实太保守了。两个模型做的是同一批题,难题对两者都难,易题对两者都易,它们的对错高度相关。比较时应该看配对差值:同一批题一起重采样,算差值的分布。
import numpy as np
rng = np.random.default_rng(0)
n = 198
skill = rng.uniform(0, 1, n)
a = skill < 0.72
b = a.copy(); flip = rng.choice(n, 12, replace=False); b[flip] = ~b[flip]
pa, pb = a.mean(), b.mean()
for name, p in [("A", pa), ("B", pb)]:
se = np.sqrt(p * (1 - p) / n)
print(f"{name}: 准确率 {p:.3f} 95% CI ±{1.96*se:.3f}")
# 配对 bootstrap:同一批题一起重采样,差值里扣掉了"题目本身难易"的共同部分
diffs = []
for _ in range(10000):
idx = rng.integers(0, n, n)
diffs.append(a[idx].mean() - b[idx].mean())
lo, hi = np.percentile(diffs, [2.5, 97.5])
print(f"A-B 差 {pa-pb:+.3f} 配对 bootstrap 95% CI [{lo:+.3f}, {hi:+.3f}]")
print("两模型结论不同的题数:", int((a != b).sum()))A: 准确率 0.657 95% CI ±0.066
B: 准确率 0.636 95% CI ±0.067
A-B 差 +0.020 配对 bootstrap 95% CI [-0.015, +0.056]
两模型结论不同的题数: 12单独看,每个模型的区间宽达 ±6.6 个点;配对后差值的区间缩到约 ±3.5 个点,因为真正提供信息的只有两者结论不同的那 12 道题。即便如此,这个 2 个点的差距的区间仍然跨过 0。结论:小 benchmark 上几个点的差距要么配对检验,要么多采样几次取平均(降低采样温度带来的方差),要么换更大的题集。
模型 A 在 198 道题上得 72%,模型 B 得 69%。下面哪个结论最合理?
p 约 0.7、n 为 198 时,每个准确率的 95% 区间约 ±6.4 个点,3 个点的差落在噪声范围内。配对比较利用两者在同一题上对错相关,比独立比较更灵敏。
"分不出"不等于"一样强",只是证据不够;差距小于标准误也不会让结论反向。
评估到底在评什么
最后退一步。一套评估要回答三个问题:测的是不是你声称的能力(构念效度,MMLU 测的是知识而不是”理解”);测出来的结果能不能代表真实使用(生态效度);数字本身可不可信(污染、题目质量、统计功效)。
生态效度是最容易被忽略的。考试题离真实使用很远,Arena 的 prompt 是真人写的,但分布不受控。OpenAI 的 GDPval 换了个思路:按美国 GDP 选出 44 种职业,请平均 14 年经验的从业者出真实工作任务;MedHELM 从临床医生那里收集临床任务,而不是用医学考试题。真实使用数据最理想,但涉及隐私,Anthropic 的 Clio 用模型分析真实对话、只公开聚合规律,是一种折中。
落到自己手上,最可靠的评估往往是一份私有的小测试集:从你真实的使用场景里收集几十到几百个例子,写清楚什么算对,必要时配一份 rubric 让 LLM 裁判按条打分,每次换模型、改 prompt、改检索策略都跑一遍。它不会被任何人拿去训练,分布就是你的分布,缺点只是题量小,所以比较时更要用配对的方法,并且每次都抽几条输出亲自读一读。公开排行榜回答的是”平均而言谁更强”,私有测试集回答的是”对我这件事谁更好”,后者通常才是你真正要做的决定。
安全评估同理,要两个方向一起测:该拒绝的有没有拒绝(HarmBench 收集了 510 种有害行为),不该拒绝的有没有误拒。只测前者,一个什么都拒绝的模型就是满分。jailbreak 研究(如 GCG 在开放模型上优化出的乱码后缀,能迁移到闭源模型)说明,安全评估的对象是一个会被主动攻击的系统,而不是一份静态试卷。这和 后训练 里违规率与误拒率的取舍是同一件事。
不同的人对”好”的定义也不同:Artificial Analysis 把多个 benchmark 合成一个智能指数,加上成本就有了”又好又便宜”;Arena 看人更喜欢谁;OpenRouter 的调用量看人真正在用、在付钱给谁。没有哪个数是”真相”,每个都是一个具体问题的答案。读到任何一个排行榜数字(尤其是厂商自己报告、评测设置又语焉不详的数字),先问它回答的是哪个问题,再决定它对你有多大参考价值,以及要不要在自己的数据上亲手重新跑一遍。
记住这三件事
- 困惑度平滑、便宜,适合开发和 scaling law;但它对所有 token 计分、要信任对方的概率,跨分词器不可比,最终还要靠下游任务。
- 开放式评估用两两比较加 Bradley–Terry 拟合;LLM 裁判有位置、冗长、自我偏好三种偏差,用交换位置、长度控制、rubric 缓解。
- 每个分数都要问三件事:题目有没有泄露进训练集、题目本身对不对、题量够不够分出差距(配对比较比独立比较灵敏得多)。
延伸阅读
- Stanford CS336 Lecture 12(评估)
- Chiang et al. 2024, Chatbot Arena: An Open Platform for Evaluating LLMs by Human Preference
- Zheng et al. 2023, Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena
- Dubois et al. 2024, Length-Controlled AlpacaEval
- Oren et al. 2023, Proving Test Set Contamination in Black Box Language Models
- Miller 2024, Adding Error Bars to Evals