做模型迭代的人大概都遇过这种场面自己改了版微调权重拿几道题手测感觉「明显变强了」结果同事用另一套题一测说「退步了」两个人谁也说服不了谁。问题不在模型在评测过程本身没有纪律——题目不一样、提示词不一样、抽答案的规则不一样、解码参数不一样任何一处变动都足以让分数飘出好几个百分点。OpenCompass 这类评测框架存在的意义就是把这堆变量全部固化成配置文件让「同一个模型、同一套题、同一套规则」变成可复现的事实而不是口头结论。这篇内容面向的是已经能跑通推理、准备给模型做系统性体检的开发者尤其是刚接触大模型评测、手里有几张卡但不知道从哪下手的人。我会按实际动手的顺序讲环境怎么算账、三份配置怎么写、分数不对时怎么逐层往回查、主观评测和自有数据怎么接进去最后是几个我踩过之后写进 checklist 的细节。1. 先搞清楚评测链路里哪些环节会偷走你的分数很多人把评测当成一个「跑个脚本出个数」的动作实际上从模型权重到最终那一行数字中间至少要经过六七个环节每个环节都能独立地让分数偏移。OpenCompass 的设计思路是把这些环节全部显式化代价是你得知道每个环节在哪、干什么。1.1 分数不可比的四个隐形来源第一个来源是提示词模板。同一个模型few-shot 从 0 改到 5中文选择题的准确率能差出 3 到 8 个百分点这不是模型不稳定是模板变了任务难度就变了。第二个是答案抽取规则。模型输出「答案是 B」正则提取([A-D])可能拿到B也可能因为前面有个「A、B、C、D 四个选项中」而错误命中A。第三个是解码参数温度、top_p、max_out_len 任何一个变动都会影响长答案题型的完整性。第四个最容易被忽略chat template。用基座模型的模板去套对话微调模型或者反过来损失是隐性的你可能只是觉得「这次分数有点低」。OpenCompass 把这四类东西分别放进了数据集配置的infer_cfg、后处理函数、模型配置的解码参数和 tokenizer 加载逻辑里。理解这个划分比记住任何一个命令都重要因为出问题时你就知道该翻哪个文件。1.2 框架在整条链路上承担的角色粗粒度地看OpenCompass 干四件事读配置、拼 prompt、批量推理、抽答案打分。它自己不训练模型也不负责部署服务所以它对模型的接入方式是「本地路径或 API 地址」两种。推理后端可以换HuggingFace 原生、vLLM、LMDeploy 都支持换后端不改变评测规则这也是它比手写脚本靠谱的地方——你可以用 vLLM 快速刷一遍看趋势再用 HuggingFace 原生跑一遍做最终确认两次的题目和规则完全一致。中间产物全部落盘predictions/存模型原始输出results/存结构化评分summary/存汇总表。这个设计是排查问题的地基后面第 4 节会重点用它。2. 开跑之前先把算力和时间的账算明白我见过太多人一头扎进去配好文件就python run.py跑了六个小时发现显存不够或者数据集没下全。这一步花的半小时能省掉后面半天。2.1 依赖安装的两个版本陷阱OpenCompass 依赖mmengine的配置系统这套东西对版本比较敏感。我的建议是老老实实建独立环境用官方给的requirements装不要往已有的训练环境里塞。常见的第一个坑是transformers版本过高或过低导致某些模型的trust_remote_code加载失败表现是加载时报一个跟模型结构无关的AttributeError第二个坑是datasets库版本与缓存格式不兼容报的是数据文件读取错误但实际是库版本问题。判断方法很简单新建一个空环境只装框架依赖跑一个最小 demo 配置能出分再往里加东西。别一上来就在大环境里装装完发现有冲突你连是哪个包引起的都定位不到。另外算力上有个容易被忽略的点评测是纯推理不需要梯度所以显存占用主要来自权重 KV cache跟训练完全是两个量级。一个 7B 模型在 FP16 下权重约 14GB配 batch_size8、输出长度 1024 的话24GB 卡基本能压住但如果你把 max_out_len 调到 4096同样的 batch 就会 OOM。这个关系要心里有数。2.2 时间成本的粗略估算方法评测时间 ≈ 样本数 × 平均输出长度 / 吞吐。以中文选择题为例样本通常几千条输出短基本十几个 token主要开销在 prefillvLLM 下一个 7B 模型单卡能跑到每秒几十条。但换成代码生成题输出动辄几百上千 token吞吐会掉一个数量级这时候max_out_len设成 512 还是 2048时间差好几倍。我的习惯是先用--debug或者只挂一个数据集跑通看日志里打印的每秒处理条数再乘以总样本数估总时长。别信「大概半小时」代码类评测跑一整天是常事。提示先用小样本确认链路通再放大规模。全量跑之前务必确认predictions/里已经有内容否则你可能在等一个根本不会产出结果的任务。3. 三份配置撑起一次完整评测模型、数据集、运行参数OpenCompass 的配置文件本质是 Python 脚本用read_base把官方预置的配置引入再覆盖。这个设计一开始会让人不适应但用熟之后非常灵活——你可以在配置里写条件判断、循环生成多个模型条目这是纯 YAML 做不到的。3.1 模型配置什么时候该切到 vLLM模型配置的核心是四个字段用哪个后端类、模型路径、解码参数、卡数分配。以对话模型为例原生后端的写法大致是引入HuggingFacewithChatTemplate指定path为本地目录max_out_len给个合理值batch_size按显存调run_cfg里写num_gpus。切到 vLLM 只换类名和几个参数加tensor_parallel_size其余不变。什么时候该切我的经验是数据量超过一万条样本或者输出长度超过 512 token就切 vLLM。原生后端优势在于兼容性最好冷门模型结构、自定义 tokenizer 都能跑适合做最终确认vLLM 优势是吞吐适合大批量刷分。但 vLLM 有个前提模型结构得被它支持某些魔改过的模型会加载失败这时候退回原生后端别硬刚。有个细节值得单独说abbr字段决定结果目录名也决定汇总表里的模型名。评测不同版本时abbr 里一定要带版本号比如my-model-v3-sft否则-r复用缓存的时候旧结果会被当成新结果用你看到的分数其实是上一版模型的。这个坑我栽过一次白跑了两轮。3.2 数据集的 reader / infer / eval 三段式一个数据集配置拆成三段理解这三段就理解了一切。reader_cfg管「从原始文件里读什么」。指定输入列、输出列、训练划分和测试划分。中文选择题的原始数据可能是question、A、B、C、D、answer六列input_columns里把前五列拼起来喂给模型output_column指定answer作为标准答案。infer_cfg管「怎么问」。prompt_template定义 few-shot 示例和提问格式retriever决定示例从哪来固定、随机还是按相似度检索inferencer决定生成式还是困惑度式。选择题一般用生成式让模型直接吐选项字母某些基座模型对生成式不敏感用困惑度式对比各选项的似然反而更稳。eval_cfg管「怎么判分」。这里挂evaluator和可选的pred_postprocessor。后处理函数就是把模型输出清洗成标准答案的关键一环比如把「我认为正确选项是B」规整成B。我的建议是先看一遍数据集原始文件长什么样再去读对应配置。很多人跳过这步直接抄配置结果字段名对不上报错信息还特别晦涩。3.3 分片、缓存与并行度运行时的几个参数值得单独拎出来。分片参数控制把任务切成多少份并发跑理论上越大越快但切得太碎会让日志难以阅读、汇总阶段变慢一般设成卡数的整数倍比较合适。缓存复用参数在调试阶段是救命的——改个后处理规则重跑前面的推理结果直接复用几秒钟出分但它同时也是污染源改提示词、改模型路径之后必须清掉旧结果或换输出目录。还有一个常被忽略的是评估细节落盘开关打开之后每个样本的判定细节都会写进结果文件包括模型输出、抽取到的答案、是否正确。排查问题的时候这个开关必须开。4. 分数明显不对时从 predictions 往上倒推的排查链路这是整篇内容里我最想讲清楚的部分。评测出问题的时候绝大多数人的第一反应是「模型不行」但十次里有七八次是链路问题。下面这条链路是按信息量从大到小排的照着走基本能定位。4.1 第一步永远是打开 predictions 原文predictions/目录下是模型逐条原始输出格式是每行一个 JSON 对象带样本 id 和输出文本。先抽十条看。看一眼你就知道问题在哪如果输出是空字符串那是推理阶段的问题max_out_len 太小、stop token 配置错、生成被提前截断如果输出是一段完整的分析但没说选项那是提示词要求不明确如果输出格式工整但答案明显是错的那才是模型能力问题。这个动作三十秒能做完但能挡掉后面一小时的瞎猜。我现在的习惯是任何一个数据集出分先眼睛扫十条 predictions再去看 summary。4.2 答案抽取失败的三种典型形态第一种过度抽取。模型输出「A 选项描述了……所以答案是 B」正则贪婪匹配第一个[A-D]拿到A。修法是收紧后处理逻辑优先匹配「答案是」后面的字母匹配不到再退回全文查找。第二种格式错位。模型输出小写b或者带括号的(B)而判分器只认大写裸字母导致大量样本判为未命中。表现是准确率低得离谱比如只有随机猜测水平左右。第三种空抽取。模型输出太长或者措辞完全偏离预期正则一条都没命中。这时候去看未命中样本的分布如果集中在某几个主题上可能是那部分题目的提示格式有问题。import re def postprocess(text: str) - str: # 优先匹配显式结论避免被题干里的选项字母干扰 m re.search(r答案[是为:\s]*([A-Da-d]), text) if m: return m.group(1).upper() # 退一步取结尾附近出现的最后一个孤立字母 tail text.strip()[-40:] m re.search(r([A-Da-d])\s*[。.]?$, tail) return m.group(1).upper() if m else X这段只是个示意实际项目里要拿着你自己的 predictions 反复调调到未命中率降到百分之一以下再开始看分数否则分数没有意义。4.3 模板错配与 tokenizer 问题如果 predictions 里出现了明显的乱码、重复片段、或者每一条都在同一个位置被切断八成是模板或 tokenizer 问题。对话模型用错了 chat template表现通常是模型把整段 prompt 当普通文本续写输出风格突变tokenizer 版本不匹配表现是特殊 token 变成奇怪符号。还有一种隐蔽情况模型的eos_token配置与生成时的 stop token 列表不一致导致模型生成完答案之后还在自说自话一直说到 max_out_len 才停。这种情况不会报错只会让输出变长、时间变长、后处理变难。看一眼平均输出长度就能发现异常。5. 主观题和自有数据把评测从「通用榜」拉到「自己的业务」公开榜单的分数只能说明模型在公开题上的表现真正决定上线与否的是它在你业务场景里的表现。这部分要解决的问题是开放题怎么判、自己的数据怎么接。5.1 用模型当裁判的三个偏见开放题没有标准答案常见做法是让一个能力更强的模型当裁判打分。这个方案能跑但有三个偏见必须知道。位置偏见两个答案 A 和 B 比较时把顺序换一下裁判可能给出相反结论解决办法是做双向对比取一致结果。长度偏见裁判倾向于给更长的答案更高分即使内容空洞解决办法是在评分提示里显式约束「不因长度加分」。自我偏好同源模型打分时会偏向自己家族的输出解决办法是选一个来源不同的裁判模型或者混用两个裁判取平均。还有一个工程细节裁判模型的调用是逐条串行的几千条样本的裁判调用成本和时间都不低实际做的时候我会先抽 200 条看评分分布是否合理合理了再全量跑。5.2 自有数据的接入路径把自己的数据接进去核心是三件事转成框架认识的格式、写 reader 配置、选对判分方式。格式上最省事的是 JSONL每行一个对象字段自己定但必须有一个标准答案字段。转换的时候注意编码统一用 UTF-8中文乱码在评测里会变成「模型输出完全对不上」查半天以为是模型问题。reader 配置里指定输入列和标准答案列划分直接给测试集。判分方式分三类有精确答案的数字、选项、实体名用精确匹配加归一化答案表述多样的同义词、不同说法用包含匹配或者用裁判模型代码类用执行测试用例的方式。这里有个经验自有数据的第一版评测答案最好能够精确匹配。哪怕你手工把开放题改造成选择题也比一上来就上裁判模型靠谱。等链路稳了再逐步引入主观评分。我见过太多人第一版就上裁判分数波动巨大最后连模型到底有没有进步都判断不了。6. 几个踩过之后写进 checklist 的细节最后这部分是我个人认为最值钱的部分都是文档里不太会写、但实际会咬人的东西。6.1 缓存复用是双刃剑复用参数在调试阶段非常香改后处理规则重跑几秒出结果。但它的判断依据是任务标识只要你没改模型路径和数据集它就认为可以复用。问题在于你改提示词模板了它不一定认。表现是「我明明改了 prompt分数一点没变」。判断方法很简单看日志里有没有「加载已有结果」之类的字样。我的做法是分三个输出目录debug-前缀的随便复用run-前缀的每次全量final-前缀的跑完立刻归档。听起来笨但能避免「用了旧结果做了上线决策」这种事故。6.2 数据污染的识别与自查公开评测集在训练语料里出现是分数虚高的最常见原因。自查方法有两类一是扰动测试把题目里的选项顺序打乱、把数字改掉如果分数断崖式下跌说明模型记住了原题二是对比测试同一能力用公开集和一个临时构造的、肯定没被收录的小集各测一遍差距超过十个点就要警惕。还有一个反向的坑某些评测集的题目本身有标注错误模型答对了但被判错。这种情况在中文数据里不算罕见做错题分析的时候如果发现某道题的「标准答案」你自己都不认同别急着调模型先去看数据。6.3 结果要落到一张能讲清楚的表评测的最终产出不是一堆 JSON而是一张能拿给团队看的对比表。我通常只保留四类信息模型版本、数据集名、核心指标、样本数。指标一定要带样本数因为样本量小的数据集波动极大只看分数不看样本数就是耍流氓。另外给个实操建议同一个模型在同一数据集上跑两遍看分数波动有多大。解码温度设为 0 理论上可复现但不同的并行度、不同的推理后端仍然可能带来细微差异。知道你自己这套流程的噪声幅度你才知道「涨了两个点」到底算不算涨。这个数我个人经验是在 0.5 到 1.5 个百分点之间具体取决于题型。最后分享一个我一直保留的习惯每次评测完随机抽二十道错题人工看一眼模型到底错在哪——是审题错、推理错、还是格式错。这一步花不了十五分钟但它给你的信息量远超所有汇总数字因为汇总数字告诉你「差多少」错题分析才告诉你「差在哪」。评测这件事真正的价值从来不在于把模型排出个一二三名而在于让你知道下一次该往哪个方向改。