评测LLM这件事,做的时间越长越觉得“量化”本身就是一个陷阱。Loss掉了,不代表模型真的变聪明了;榜单位置上去了,可能只是因为训练语料里见过题目。我自己跑过不少开源模型和微调实验,从MMLU刷分到真实业务场景的对话质量评估,中间踩过的坑比收获的教训还多。这期Training Lab聊一聊评测模型的核心问题:基准怎么选才不糊弄自己、数据污染怎么识别和避免,以及怎么搭建一套别人拿着你的描述也能复现出同结果的评测流程。
这篇文章主要面向正在微调模型、或者需要横向对比多个模型的开发者。无论你是在做领域垂直模型,还是通用对话模型,只要涉及“这个模型到底行不行”这个问题,下面的内容基本都适用。我会把多年实操中踩过的坑、验证过的方案、还有那些文档里不会写的细节全部摊开说。
1. 基准选择:别被单一榜单绑架
1.1 理解评测维度与任务类别
每次有人问我“这个模型效果咋样”,我的第一反应永远不是直接答,而是反问一句:你关心它哪方面的能力?同一个模型,综合评测排名可能很好看,但在某个特定任务场景下可能完全不能打。基准测试本质上是给模型“体检”,但你得先知道自己要检查哪个“科室”。
目前主流的开源基准大概可以分成几个维度:
| 基准 | 评测方向 | 典型数据集 | 适用场景 |
|---|---|---|---|
| 知识问答 | 事实性知识储备 | MMLU、CMMLU、CPQA | 评估模型的知识广度和领域基础 |
| 数学推理 | 符号推导与算术逻辑 | GSM8K、MATH、AIME | 判断模型的逻辑链条和量化能力 |
| 编程能力 | 代码生成与理解 | HumanEval、MBPP、LiveCodeBench | 面向代码助手场景的重要参考 |
| 通用推理 | 常识推断与多步推理 | HellaSwag、ARC、BBH、HotpotQA | 考察模型的深层推理而非表面记忆 |
| 对话体验 | 综合对话与指令跟随 | MT-Bench、AlpacaEval 2.0、IFEval | 评估真实用户体验和“对齐”程度 |
| 中文能力 | 中文理解与生成 | C-Eval、CMMLU、SuperCLUE | 中文场景下需要额外关注 |
很多人做的事情是拿一张榜单的排名去说某个模型比另一个好,但这个结论往往站不住脚。原因在于不同基准的题目难度、提问格式、判定方式差异巨大,而且榜单通常只汇报宏观平均分,掩盖了任务维度的内部差异。
举个例子,MMLU是一个多选题基准,覆盖人文、社科、理工等多个学科,它考察的更多是“记住了多少知识”。但GSM8K考的是小学数学应用题,要求模型一步步推出结果。一个模型在MMLU上拿到高分,在GSM8K上可能惨不忍睹,这完全正常,因为它的训练数据侧重不同。评估前先确认你关心的能力维度,而不是简单看一个综合分。
1.2 容易踩坑的基准选择误区
我在不同项目里看到过几个反复出现的问题,给大家提个醒:
第一,盲目追新。LLM领域基准的更新速度极快,今天刚测完的SOTA模型,下周可能就被另一个新基准重新定义排行榜。但追新不追旧是种错觉,很多“新基准”本身并没有足够的样本量和权威性背书,分数波动非常大。我一般会优先选择经过社区大量验证、有较长时间复制记录的基准做横向对比。
第二,只看分数不看评测集。有一些基准的测试集存在过时问题。比如HumanEval的题目相对简单,现在稍有能力的模型都能拿高分,题目区分度已经明显下降。如果要做代码能力对比,应该从旧基准升级到新集合,比如更难的LiveCodeBench,或者直接在特定领域的私有老题集上测试才更有说服力。
第三,任务类型与业务场景错配。客服机器人重点考察多轮对话和拒绝回答的边界,但如果你硬要用MMLU去评估,那测出来的分数没什么实际参考价值。评测的最重要原则:benchmark应该尽可能逼近你的目标形态。做垂直应用时,更推荐构建自己的“领域小评测集”,从中挑选100~300道与实际业务贴近的题目做准上线评估。
1.3 构建自己的组合评测方案
我的习惯是用“三维评测框架”:通用基准做门槛、领域评测做筛选、盲测对话做体验。
通用基准是用来筛掉不靠谱模型的,比如先跑MMLU和BBH,确保基础能力没有掉队。领域评测是拿自己攒的行业题库单独验证,这部分题目不能公开,也可能是定向生成或脱敏后的真实业务数据。盲测对话是让真人去做对比,通常采用A/B盲测,连我自己都不知道哪条回答来自哪个模型,只按流畅度、正确率、友好度打分。
真人盲测这个环节特别容易被忽略,但它恰恰是衡量体验的最真实手段。有些模型在benchmark上高歌猛进,实际对话时却废话连篇。为什么不直接把所有评估都交给自动化?因为自动化评估往往也依赖LLM本身(后面会提到LLM-as-a-Judge的问题),评估质量仍然需要人工兜底。
通用基准负责守住下限,领域评测负责确认垂直能力,真人盲测负责体验的最后把关。三者结合,才能避免被单一维度的分数误导。
2. 数据污染:最能拉高“虚胖分数”的元凶
2.1 什么是数据污染,危害有多大
数据污染是指模型的训练数据中已经包含了评测基准的测试集或其非常相似的变体。模型不是在“泛化推理”,而是在“背诵答案”。这种情况会让评测分数远远高于模型的真实能力,而且你很难通过肉眼观察判断到底是不是背出来的。
危害说直白点:你会对模型能力产生完全错误的判断,做出错误的技术选型和发布决策。我见过某个模型在公开测试集上表现惊艳,换到自建题目上直接拉胯,后来复盘发现公开题目几乎都在开源语料中大量出现过。
数据污染的来源通常有几个路径。最常见的是训练语料爬取时没有过滤掉评测数据,尤其是某些通用爬虫抓取了GitHub、笔记本、题库网站的内容,里面可能就嵌着MMLU的题目。其次是社区反复传播的数据集被混合进了训练数据,有些开源训练数据集本身含有基准测试的测试集,但从名称上完全看不出问题。还有一种隐蔽的:你用来做指令微调的对话数据中,可能已经被别人间接注入了某个评测集的题型。
数据污染不等于“作弊”。大多数情况下是无意的,但你要知道它对你评估工作的影响有多大。就算你完全自训模型,只要训练语料的来源不够干净,同样可能踩中污染。
2.2 污染检测的实操方法
怎么判断一个模型是否在“背答案”?实操中我有几个可落地的检测手段,不需要特别复杂的工具。
第一,字符串重叠检测。把你的评测集切成若干连续片段(比如8-token的ngram),去和训练语料做精确匹配。这个方案最直接,也能定位到具体污染数据来源。先跑一遍ngram频率统计,记录测试题中同时出现在训练语料里的句子或片段数量。高重叠率是强烈的污染信号。当然这需要你已经有一定的语料检索基础设施,但哪怕只能抽样检查一部分,也比较有价值。
第二,句子困惑度检测。有些笔记里也提到用模型对测试题本身计算Loss或困惑度,然后和一组“从未出现在训练语料中的同类题目”做对比。如果模型面对“从没见过”的题目时困惑度明显高于测试集题目的困惑度,那很可能存在数据泄漏。这个方法尤其适合在模型已完成训练、拿不到训练语料清单时使用。
第三,时间线交叉验证。有些基准会记录数据构建的时间节点,如果你能确认某条测试样本构建时间晚于模型训练数据的截止时间,那理论上模型不该见过它。实际操作里时间信息很可能不完整,这里主要作为一个辅助验证手段。
第四,针对性改写再测。把测试题换个说法(比如JSON转成对话形式、英文换中文意译、数字换掉、角色名换掉),然后对比原题和改写后正确率的变化。如果原题得分极高而改写后得分骤降,说明模型很大程度上是在套模板或记忆,而不是真正掌握了解题方法。
这四种方法实操下来,第一种最直接,第四种最常用,第二种最严谨但条件要求高,第三种看运气。我自己通常结合一、四两种做快速筛查。
2.3 应对数据污染的实际策略
应对污染可以从两个层面做。一个是训练侧防:建立训练集的黑名单过滤机制,把公开基准测试集全部列为“永不入训”的名单。对于开源语料,也要做模糊匹配,因为测试题可能会被加了噪声、翻译或者被套上了不同的答案选项。
另一个是评测侧防:尽量选用动态更新的基准,也就是那些会持续加入新题的评测项目,例如LiveCodeBench、FreshQA这种采用时间截止点动态出题的基准。条件允许的话再留一份私有评测集,这部分数据永远不公开,从根本上杜绝被污染的可能性。很多企业团队在内部就是这么做的,跑榜单、汇报、内部对比时用公开基准,但正式决策时只看私有题集的成绩。
还有个很多团队会忽略的点:评测报告的公开代码和数据版本要标注明确。如果未来发现评测集有污染,具备审计能力的人可以回溯你的评测过程和得分。现在很多比赛的评审已经要求参赛队伍提交“评测代码+随机数种子+采样配置”来尽量保证可信度。
3. 可复现的评测流程:把“感觉还行”变成可回放的结论
3.1 评测流程的现状与问题
我见过太多“评测过就没有下文”的项目。当时测完觉得模型不错,等到下一次想复现这个结论时,发现啥也找不到了——用了哪个版本的模型、温度采样设了多少、提示词写的啥、有没有用约束解码,全都变成了模糊记忆。
做AI评测,最最重要的原则是“可复现”。评测结果不能靠“我记得当时效果不错”这种话支撑,必须有完整的记录可供回放。尤其做技术报告、论文、开源模型对比的时候,评测的不透明会被直接打上“不可信”的标签。
一条可复现的评测流程至少需要固定这几个东西:模型权重版本(含精确的checkpoint或commit hash)、评测框架版本、数据集版本、解码参数、提示词模板、请求并发、随机种子。任何一项变了,结果都可能不一样,尤其是采样温度较高的时候,两次跑的结果都能差出一大截。
3.2 建立可复现评测管线的四步法
下面我分享一下个人常用的四条步骤,按顺序做完,基本能让评测流程达到“别人照着你的描述也能跑出相似结果”的状态。
第一步,固定环境与脚本。尽量用Docker或者至少用一份requirements.txt锁定依赖版本。如果用的是目前主流的一些评测开源库,也要锁住版本号。社区里经常有这种情况:上周跑出来的分数,这周库升级后同一份代码跑出的分数不同了,原因就藏在依赖变更里。
第二步,写清楚评测配置。把模型参数、提示词、采样策略全部集中在一个配置文件里,避免“临时在代码里改个数”造成无法追溯。我习惯用YAML维护一份实验配置,里面记录着模型路径、模型加载时的dtype、max length、temperature、top_p、top_k、评测数据集、few-shot数量、任务类型映射等关键信息。
第三步,统一记录和回放。每次评测运行都生成一个带时间戳的目录,保存输入样例、模型输出、评测打分的中间结果以及最终的汇总报告。所有中间结果都保留,不要只留一个最终分数,否则一旦发现某个坑,你连复盘的数据都没有。
第四步,固定随机种子与解码配置。虽然我把种子写进配置,但我必须提醒你:LLM测评里,随机种子并不等价于真正可复现。因为GPU上的非确定性算子可能导致即使种子相同,输出也会有微小差异。但固定种子配合温度设到0,可以极大收敛差异范围。我们做指标对标时,一般把所有解决方案类的评测任务都设置为temperature=0,多次采样任务再单独说明用的是什么种子。
下面是一个接近实战的配置片段,可以参考:
# eval_config.yaml model: path: /models/my-finetuned-7b revision: "main" dtype: bfloat16 dataset: name: gsm8k split: test revision: "5f9ff4a" generation: temperature: 0.0 top_p: 1.0 max_new_tokens: 512 do_sample: false prompt: template: "Question: {question}\nAnswer:" fewshot: num: 5 output: save_dir: "./results/run_20250601" log_inputs: true log_outputs: true这份配置看起来简单,但它数字化地锁定了评测现场。之后在任何环境下重新加载模型和配置,理论上结果都应该稳定。
3.3 判卷标准与自动评估工具的选择
评测不仅要保证模型输出能够被回放,还要解决“怎么判分”的问题。不同任务有不同的判分方式:
- 选择题类(MMLU等):直接判断选项字母。注意部分模型会在输出里写一段解释再选答案,判分时需要先把答案解析出来。
- 数学应用题(GSM8K等):目前常用的方式是对比最终答案。答案提取时尤其要注意去掉千分位逗号和货币符号,正则要覆盖到“12,000”和“12000”的等价情况。
- 代码生成(HumanEval等):用单测用例抽样执行判分,执行环境要固定Python版本和依赖包版本。
- 对话打分(MT-Bench等):既可以用规则,也可以借助LLM打诚,还可以做人工评分。如果使用LLM打分,通常参考“LLM-as-a-Judge”的路线,用大一点的通用模型做裁判输出1~10分。
我实际上手时遇到过最难缠的判分问题,是模型回答里“解释一大段+最后给答案”的形态。如果不做答案抽取而直接做字符串匹配,结果会非常难看。判分脚本要写得足够宽容,又要防止答非所问被误判成正确。正则表达式的顺序也很关键,要先提取最后出现的数值或选项字母,而不是全局匹配的第一个。
在做LLM-as-a-Judge的时候,还有一个必须警惕的问题:裁判模型也有偏好偏差。它会偏好更长的回答、更丰富的排版、甚至某些情绪化表达。这套偏差在评测中极为常见,所以做LLM裁判时不要只让裁判打分数,还要让它给出推理过程,便于人工抽检。每个任务最好重复打分几次取均值,并且把“平局/无法判断”作为一个独立选项,别硬逼着裁判做出选择。
4. 常见问题与排查技巧实录
4.1 评测结果不稳定,来回跳
很多人跑完一轮评测,隔一天再跑一遍,发现分数差了2~3个点,开始怀疑模型是不是又变“聪明”了。实际上大概率是评测流程里某个环节没锁死。
排查顺序建议是这样:
- 先确认解码参数,看看是不是用了temperature=0.7这种随机性较大的值。如果做正式对比,全部统一到temperature=0。
- 再确认是不是批次大小调了。有些框架在batch size增大的情况下,因为并行计算的浮点数累加顺序变化,结果会有微小差异。特别是混合精度训练和推理时,这种现象更明显。
- 然后看数据集有没有被shuffle。很多数据集在加载时默认打乱顺序,在few-shot评测里,few-shot示例的顺序变了分数也会变。
- 最后确认模型加载时dtype是不是一致。float16和bfloat16的结果可能差在第四位以后,但在严格复现时仍然会产生“跳分”。
以上每一项我都踩过。印象最深的一次是仅仅换了数据加载时是否使用多进程,结果就出现了两个不同版本的分数,折腾了半天才发现是多进程读取时数据集顺序发生了改变。
4.2 输出解析失败率奇高
这类问题最常见的原因是模型没按提示词里的格式走。有些模型会对Few-shot的格式学得不好,输出变成了对话流,或者在前面加一句“好的,让我算一下”。这时候你再拿正则去提取答案,往往提取不出来。
实操里的解法有几种。第一,在提示词里强制要求“只输出答案,不要额外解释”。第二,在后处理时做多级容错,先按格式抽取,抽不到就扫描全文中所有数字并做规则校验。第三,直接把解析失败也算作“回答错误”并记录日志,不去硬补。
解析失败本身也是模型质量的一个信号。如果你发现一个模型在某个任务上有大量解析失败,说明它对指令遵循能力不足,这是需要在日志里单独关注掉的指标,而不是悄悄把它解析成“错误”就完了。
4.3 稀缺评测集使用不当
许多零样本评测只做了考分,忽略了答题的统计置信度。比如只有100道题的评测集,一个样本的波动就能让分数跳1个百分点。这种情况下最好对结果给出置信区间。用Bootstrap法(自助抽样法)对预测结果做重采样,可以粗略估算分数的方差。尤其在杀入榜单最后几名竞争的时候,没有置信区间的分数差异基本没有说服力。
我的做法是把测试集先按类别分层统计,再输出子任务的分数和整体分数。这样不会出现“总分看起来没变,实际上一类能力已经废了”的情况。评测集多小都不怕,但报告里要写清楚样本量,让读者自己判断可信度。
4.4 验证集还是测试集,别用混
很多人的评测习惯是拿验证集做调参,拿测试集做最终结论。这个理论上没有大问题,但实操中常出现“验证集和测试集的差异过大”。尤其当你用公开数据集的默认split时,要确认这个split到底是什么。
建议尽量用官方脚本runner里定义好的数据集使用方式,不要自己做切分。如果非要自建验证集,建议和测试集完全独立,最好来源不同。最忌讳的就是原本做评测的题目,被加进训练集去“提升表现”,那样唯一的赢家只有刷分体验。
5. 评测过程中的额外心得
5.1 大模型做裁判的正确打开方式
LLM-as-a-Judge已经是个主流玩法,但用之前你得清楚它到底在评判什么。它是用语义相似和理解的方式做打分,不是像编译器那样能给出确定性结论。所以评测对话质量、回答安全性、创意写作这类开放任务时,LLM裁判的效果还可以;但评测数学答案、代码正确性这类任务时,用LLM裁判就要特别小心,因为它的数值敏感度并不稳定。
做LLM裁判时我的偏好是用比被评测模型更强的模型来打标,两者能力差距太小,裁判本身也会犯低级错误。此外,我会把裁判的prompt写成一个详细的打分卡模式,明确给出“1分代表什么,5分代表什么”的维度定义,并要求输出打分理由。省时间的同时也保留可审计性。
5.2 要构建评测集建议从“小而精”开始
自建领域评测集听上去很美好,但大而全的构建往往半途夭折。我的建议是先做100道题,覆盖核心场景,然后边用边补。每道题人工标注预期答案或评分标准,不对的地方随时修订。评测集本身也需要版本管理,一次修订一个版本号。
100道题对应到量化上面,波动幅度大概在0.5~1分左右,用来做快速pass/fail判断够用了。真要精确对比,那就需要加大样本量到几百道甚至上千道题。但很多团队就是死在“想一步到位”上——评测集永远没有完全准备好的一天,不如先用小规模版本跑起来,边跑边迭代,评测集本身也是在“使用中成熟”的。
5.3 评测流程同样需要测试
你自己写的判分脚本,测试过吗?我见过很多评测管线里带着悄悄的错误,比如待比较的模型答案text被做了lower-case,但蓝本答案是大小写敏感;比如代码执行环境里缺了依赖包,导致某道题全部超时。这些错误不会让代码直接报错,而是默默污染你的整个结论。
所以跑正式评测之前,先拿一小组“答案已知”的数据做管线验证,确认模型输出、判分脚本、汇总逻辑全都符合预期。一个简单的做法是把测试集缩小到只有10道题,人工把得分算一遍,然后跑完整管线,对比脚本输出和人工结果是否一致。这个习惯每次都能帮我拦下至少一个低级错误,强烈推荐大家都这样做。
结束语
评测这件事,真正的价值不在排行榜那个总分数上,而在整个流程能不能经受住追问和复现。基准选择要匹配场景而不是追逐热度,数据污染要主动排查而不是心怀侥幸,评测流程要像实验室记录一样规范而不是凭感觉留档。每次跑完一个评测,我都建议顺手把配置、输出、判分脚本、模型版本归档到一起。表面上看是多了几步操作,实际上是在为自己的判断兜底。
这套方法论帮我挡掉过至少三次“看起来高分、实则不可靠”的决策陷阱。你对模型越好,你对它的评估就越要严格——因为这决定着你接下来要投入的算力、时间还有对外承诺的边界。下次再做模型对比,试着问问自己:这份成绩,能经得起一个月后的自己质疑吗?