三步搭建 lm-evaluation-harness 自定义评估循环:从复现官方分数到接入自家模型一次到位 三步搭建 lm-evaluation-harness 自定义评估循环从复现官方分数到接入自家模型一次到位【免费下载链接】lm-evaluation-harnessA framework for few-shot evaluation of language models.项目地址: https://gitcode.com/GitHub_Trending/lm/lm-evaluation-harness你是否有过这样的困惑用simple_evaluate跑出的分数和论文表格对不上想加一个自定义指标却不知道往哪塞换一个非 HuggingFace 格式的模型更是无从下手。lm-evaluation-harness 的所有魔法其实都藏在lm_eval/evaluator.py的simple_evaluate和evaluate两个函数里本文就用一个内部模型评测的真实场景带你从拆开默认流程开始一步步组装出属于自己的评估循环顺带解决自定义指标和异形模型接入这两个老大难问题。一、先看默认流程一次评估到底发生了什么我们常说跑一下 lm_eval 看看分但simple_evaluate内部其实串了三个独立阶段。理解它是你敢动它的前提。1.1 从模型名到任务对象两层注册表在背后工作当你写下modelhf时框架会去lm_eval/models/__init__.py的模型注册表里找对应类当你写下tasks[arc_easy]时lm_eval/tasks目录下数千个 YAML 文件会被索引成任务对象。你通常感受不到这两层查找是因为TaskManager在第一次调用时悄悄把它们全部加载好了。1.2 simple_evaluate 的三步流水线simple_evaluate大致可以拆成三步对应lm_eval/evaluator.py中的关键逻辑每一步都有对应的自定义入口第一步你可以传入自己的Task实例第二步可以改gen_kwargs第三步可以注册新指标。但如果你想精确控制加载哪些样例、用几个 few-shot、跳过哪些聚合simple_evaluate这个全家桶就不够用了。1.3 evaluate 才是那个裸引擎evaluate(lm, task_dict, ...)的参数更少、更底层它接收一个已经实例化的lm对象和一个已经构建好的task_dict直接进入构建请求 → 推理 → 算指标的核心循环。你可以把它理解为拆掉外壳的引擎simple_evaluate只是给这个引擎接上了模型名解析和任务加载的管道。下一节我们就把这个裸引擎接上自己的管道实现只评测指定编号的样例。二、场景实战只评测你关心的那 50 条样本你可能会遇到一个真实需求线上出错的样例编号是[3, 17, 42]你想复现这些样本上的表现而不是把整个测试集重跑一遍。simple_evaluate的limit只能取前 N 条或前 N%做不到精确定位这时候就该自己组装循环了。2.1 用 evaluate 精确控制评测对象evaluate支持samples参数可以直接指定每个任务要评测的样例索引参考lm_eval/evaluator.py中simple_evaluate和evaluate的签名samples形如{task_name: [0, 3, 6]}。完整代码如下适用于 lm-evaluation-harness 0.4.x 及以上版本# 自定义评估循环只评测指定样本v0.4.x from lm_eval import evaluator, tasks # 1. 构造任务字典内部完成 YAML 索引与 Task 对象构建 task_manager tasks.TaskManager() task_dict task_manager.load([arc_easy]) # 2. 实例化模型这里直接传对象绕开字符串解析 from lm_eval.models.huggingface import HFLM lm HFLM(pretrainedEleutherAI/pythia-70m) # 3. 只评测这 3 条样本 results evaluator.evaluate( lmlm, task_dicttask_dict, samples{arc_easy: [3, 17, 42]}, # 精确指定样本编号 bootstrap_iters1000, # 样本少bootstrap 迭代也相应减少 ) print(results[results][arc_easy])这里有个值得注意的细节evaluate不会帮你自动加载模型字符串model_args之类的解析全部被跳过你必须自己完成HFLM(...)的实例化。多了一步但也多了一层自由度——你可以对模型做任何包装再传进去。2.2 顺带认识 Task 对象的生命周期task_dict里的每个Task都持有test数据集和fewshot_docs。evaluate内部会对它们做两件事先用build_all_requests()把每一条数据转成推理请求loglikelihood或generate_until类型推理完成后用process_results()逐条算分数。如果你想知道某条样本的 prompt 长什么样可以设置write_outTrue框架会把文档和模型输入一起打印出来核对。2.3 验证结果是否可信跑完你会发现指定 3 条样本得到的分数波动很大——这很正常样本越少标准差越大。所以真实工作中建议用samples做回归复现用limit做快速冒烟用全量数据出正式报告三者不要混用。下一步我们给自己加一个指标这才是自定义评估循环的灵魂。三、造一个自己的指标注册表比你想的更简单内置的acc、f1、bleu都有明确的使用场景但业务上你大概率需要宽松匹配这类自己的口径。好消息是lm-evaluation-harness 的指标体系是注册表驱动的两行装饰器就能挂进去。3.1 先搞清指标和聚合的分工看lm_eval/api/metrics.py的源码你会发现每个 metric 都被拆成了两部分指标函数接收(doc, results)对单条样本打分产出数值或字典聚合函数接收所有样本的分数列表产出最终报告值比如acc的聚合就是简单的mean而f1这类需要跨样本计算的指标则依赖把(gold, pred)打包传入的专用聚合逻辑。你在 YAML 里写的metric_list其实就是在声明用哪个指标 用哪个聚合 数值是否越大越好这三元组。3.2 注册一个宽松准确率下面这段代码在项目里新建my_metrics.py后直接运行即可0.4.x它注册了一个忽略首尾空白、忽略大小写的宽松匹配指标# my_metrics.py — 注册自定义指标与聚合 from lm_eval.api.registry import register_metric, register_aggregation register_aggregation(my_mean) def my_mean(items): 对每条样本的分数取平均空列表返回 0 而非报错 return sum(items) / len(items) if items else 0.0 register_metric(metricrelaxed_acc, aggregationmy_mean, higher_is_betterTrue) def relaxed_acc(doc, results): 宽松匹配归一化后比较模型输出与参考答案 pred, ref str(results[0]).strip().lower(), str(doc[answer]).strip().lower() return 1.0 if pred ref else 0.0register_metric的三个关键字参数缺一不可metric是名称aggregation指定聚合函数higher_is_better决定报告里箭头方向。注册完成后这个指标会进入metric_registry和aggregation_registry两个注册表源码在lm_eval/api/registry.py的register_metric定义中可以看到这层逻辑。3.3 在自己的循环里用上它# 复用第二节的自定义循环加上新指标 from my_metrics import relaxed_acc # noqa: F401 确保装饰器执行 # 构造一个只含 metric_list 的 dict 配置也能被 TaskManager 接受 task_dict task_manager.load([ { task: arc_easy_custom, dataset_path: allenai/ai2_arc, dataset_name: ARC-Easy, output_type: multiple_choice, test_split: test, doc_to_text: Question: {{question}}\nAnswer:, doc_to_target: {{choices.label.index(answerKey)}}, doc_to_choice: {{choices.text}}, metric_list: [ {metric: relaxed_acc, aggregation: my_mean, higher_is_better: True} ], } ]) results evaluator.evaluate(lmlm, task_dicttask_dict, samples{arc_easy_custom: [0, 1, 2]})注意import my_metrics这行不能省——装饰器是在导入时执行的不导入就不会注册你会直接撞上Could not find registered metric的报错。指标能注册任务自然也能注册下一节看看怎么把整个新任务固化成一个 YAML 文件。四、从 YAML 到任务不用写一行 Python 就定义新评测lm_eval/tasks/arc/arc_easy.yaml这类文件只有十几行却是整个评测体系的入口。新任务完全可以复制它的骨架改造成自己的。4.1 一个 YAML 最少需要哪些字段字段作用你的任务里该填什么task任务名用于 CLI 和load()唯一、小写下划线命名dataset_path/dataset_nameHuggingFace 数据集定位你的数据集仓库与子集output_typemultiple_choice/loglikelihood/generate_until按任务形态选择test_split评测用哪个 split通常是testdoc_to_text/doc_to_target/doc_to_choice用 Jinja2 模板从样本里抽取 prompt、答案、候选直接写{{字段名}}metric_list指标三元组列表用内置的acc起步表格之外的training_split、validation_split、metadata.version都是可选项但建议补上metadata.version——它参与结果文件的版本命名缺了它后续对比结果容易混乱。4.2 字段顺序和缩进会坑你YAML 解析是顺序敏感的task必须出现在最前面dataset_*在output_type之前doc_to_*在一起metric_list在最后。如果metric_list里的aggregation写成mean而你的指标没注册过mean运行时会静默退化为跳过该指标而不是报错——这种安静失败最隐蔽排查时先查聚合函数名拼写。4.3 模板写法决定评测质量doc_to_text里的{{ }}是 Jinja2 变量插值{{choices.label.index(answerKey)}}这种写法说明它支持完整 Python 表达式。你可能会踩的坑是字段名与数据集实际列名不一致先在 Python 里datasets.load_dataset(your/dataset)[test][0]打印一条样本确认字段名再写模板能省下大量调试时间。任务搞定了下一个难题通常是模型——如果你的模型不是 HuggingFace 格式怎么办五、接入异形模型只实现两个方法的 LM 接口框架对模型的抽象收敛到LM基类的两个核心方法上_loglikelihood_tokens算分任务和_generate_until生成任务。只要你把模型包装成符合这两个方法语义的对象就能进评估循环。5.1 两种思路按你的工程量选轻量路线推荐先试你的模型能转成 HuggingFace 接口就用适配器包装examples/transformer-lens.py就是现成范本——它用一个HFLikeModelAdapter把 TransformerLens 的HookedTransformer包装成有.logits属性的 nn.Module然后直接交给HFLM(pretrainedmodel, tokenizer...)使用。重型路线直接继承LM基类自己实现_loglikelihood_tokens和_generate_until适合推理服务化、远端 API 这类无法本地加载的情况参考lm_eval/models/api_models.py的实现方式。5.2 适配器思路的最小骨架# 极简适配器骨架参考 examples/transformer-lens.py0.4.x import torch.nn as nn from lm_eval.models.huggingface import HFLM class MyAdapter(nn.Module): 让自定义模型具备 HuggingFace 接口的外形 def __init__(self, my_model, tokenizer): super().__init__() self.model my_model self.tokenizer tokenizer self.config my_model.config self.device my_model.device def forward(self, input_idsNone, attention_maskNone, **kwargs): out self.model(input_idsinput_ids, attention_maskattention_mask, **kwargs) # 关键lm-eval 依赖 output.logits 取分数 if not hasattr(out, logits) and isinstance(out, torch.Tensor): out.logits out return out def to(self, *args, **kwargs): return self.model.to(*args, **kwargs) def eval(self): self.model.eval() return self lm HFLM(pretrainedMyAdapter(my_model, tokenizer), tokenizertokenizer)5.3 别忽略 tokenizer 一致性_loglikelihood_tokens按 token 计算概率generate_until按 token 生成打分口径完全取决于 tokenizer。如果你包装的模型 tokenizer 和HFLM拿到的 tokenizer 不一致loglikelihood 分数会莫名其妙地系统性偏低。适配时优先让HFLM直接使用模型自带的 tokenizer而不是另配一个。模型和任务都就位了最后把调试阶段常见的坑一次性讲清楚省得你逐个踩。六、避坑指南我实际撞过的 6 个报错这里整理的是 0.4.x 版本下高频出现的真实报错按出现频率排序。1.Could not find registered metric xxx现象指标名明明在metric_list里运行时报找不到。 原因自定义指标的模块没有被导入装饰器从未执行。 解决在入口脚本顶部import 你的指标模块或用--include_path指向你的指标文件目录。2.KeyError: test或 dataset 列名不存在现象任务加载时数据集字段对不上模板。 解决先打印一条样本核对字段名确认dataset_name参数是否真的对应你想要的子集比如 ARC 的ARC-Easy与ARC-Challenge是两个不同子集。3. 分数和官方报告对不上原因few-shot 数量不同、apply_chat_template默认关闭、num_fewshot未显式设置。 解决复现时务必显式传num_fewshot并检查官方报告是否启用了 chat 模板。注意apply_chat_templateTrue会改变输入格式直接影响 loglikelihood 类指标——对比时保持两边一致。4.ValueError: batch size too large或 OOM原因batch_sizeauto探测出的最大值超显存。 解决显式max_batch_size8之类上限或固定batch_size4起步。5.generate_until任务生成内容过长导致超时/截断原因gen_kwargs里没设until和max_gen_toks。 解决在任务 YAML 的generation_kwargs中显式声明until: [\n\n]、max_gen_toks: 128YAML 中必须用双引号包裹\n这类转义字符。6. 同一任务重复运行结果不完全一致原因few-shot 样例的采样带随机性fewshot_random_seed。 解决设置--seed 1234固定随机种子正式对比时保持种子一致。下面进入收尾给你一个 10 分钟就能跑完的最小实验把本文内容串起来。七、下一步行动10 分钟跑通你自己的评估循环现在动起手来把这个最小实验跑通前置条件已安装 lm-evaluation-harness 0.4.x网络可访问 HuggingFace 数据集# 1. 准备一个最小自定义任务保存为 my_task.yaml # task: my_demo # dataset_path: allenai/ai2_arc # dataset_name: ARC-Easy # output_type: multiple_choice # test_split: test # doc_to_text: Question: {{question}}\nAnswer: # doc_to_target: {{choices.label.index(answerKey)}} # doc_to_choice: {{choices.text}} # metric_list: # - metric: acc # aggregation: mean # higher_is_better: true # 2. 用 CLI 跑通先取 5 条验证配置 lm_eval --model hf \ --model_args pretrainedEleutherAI/pythia-70m \ --tasks my_demo \ --include_path ./my_task.yaml \ --limit 5 \ --num_fewshot 0 # 3. 跑通后把 my_metrics.py 里自定义的 relaxed_acc 加进 metric_list 再跑一次跑通之后建议按这个顺序继续深入通读 docs/new_task_guide.md把 YAML 模板里的可选项全部摸一遍看 docs/task_guide.md 理解evaluate与simple_evaluate的完整参数语义精读lm_eval/evaluator.py中evaluate函数从请求构建到结果聚会的 300 行核心代码想给社区贡献任务时参照 docs/CONTRIBUTING.md 的提交流程如果你已经能独立组装评估循环接下来值得研究的方向是用predict_onlyTrue把模型输出导出成 JSON 再做离线分析或者用EvaluationTracker把每次实验配置自动归档——这两招能让你的评测结果真正做到可复现、可追溯。【免费下载链接】lm-evaluation-harnessA framework for few-shot evaluation of language models.项目地址: https://gitcode.com/GitHub_Trending/lm/lm-evaluation-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考