LM 评估框架


文档摘要

LM 评估框架 本节摘要:一个在你说不清的任务上表现好的模型,是碰巧表现好。框架是任务定义、指标、运行器、排行榜,装进一个短、可换的形状。每周来一个新语言模型,营销声称它表现好;诚实的问题是「好在什么」,诚实的回答是你自己写的排行榜。无框架你凭感觉比两个模型;有框架你在固定任务集、固定指标上比、在可 diff 的 JSON 上比。本节把任务定义为带 / / /可选 的 JSONL 文件,实现五个指标(精确匹配、rouge-l F1、可执行检查、多选、子串包含),建按任务批并派发给可换模型适配器的运行器,发带每任务分、延迟、可复现总均的排行榜 JSON。 对应原课程:Phase 19 · Lesson 49 · (原英文 )。本节属「预训练/分布式」赛道第八节。

LM 评估框架

本节摘要:一个在你说不清的任务上表现好的模型,是碰巧表现好。框架是任务定义、指标、运行器、排行榜,装进一个短、可换的形状。每周来一个新语言模型,营销声称它表现好;诚实的问题是「好在什么」,诚实的回答是你自己写的排行榜。无框架你凭感觉比两个模型;有框架你在固定任务集、固定指标上比、在可 diff 的 JSON 上比。本节把任务定义为带 prompt/targets/metric/可选 extras 的 JSONL 文件,实现五个指标(精确匹配、rouge-l F1、可执行检查、多选、子串包含),建按任务批并派发给可换模型适配器的运行器,发带每任务分、延迟、可复现总均的排行榜 JSON。

对应原课程:Phase 19 · Lesson 49 · lm-eval-harness(原英文 phases/19-capstone-projects/49-lm-eval-harness/docs/en.md)。本节属「预训练/分布式」赛道第八节。

学习目标

阅读完本节,你应当能够:

  1. 把任务定义为 JSONL 文件,每例带 prompttargetsmetric、可选 extras
  2. 实现五个指标:精确匹配、rouge-l F1、可执行检查、多选、子串包含。
  3. 建按任务批并派发给可换模型适配器的运行器。
  4. 发带每任务分、延迟、可复现总均的排行榜 JSON。

一、问题与直觉

框架是昨天运行与今天运行的契约。无它,回归就发布。陷阱是把框架过拟合到单一模型;修复是同一陷阱的反面:框架小到 15 分钟读完、任务小到可进仓库、指标从零写使同事能审计、适配器是唯一放模型特定代码的地方。换适配器,排行榜动;换任务,排行榜动;别的都不该动。

任务规范

每例是一行 JSONL:

{"id": "arith-00", "prompt": "compute: 2 + 2", "targets": ["4"], "metric": "exact_match"}

需打分助手的指标,extras 带侧负载:

{"id": "code-00", "prompt": "python: write a function f that doubles its input", "targets": ["ok"], "metric": "code_exec", "extras": {"io_pairs": [[1, 2], [3, 6]]}}

任务是 outputs/tasks/ 下的 .jsonl,文件名是任务名,同文件所有例共享指标。

五个夹具任务

任务 指标 测什么
arithmetic exact_match 确定答案的 token 级正确性
summary rouge_l 对一行参考摘要的最长公共子序列 F1
code-exec code_exec 可执行测试:预测函数须满足一组输入输出对
multiple-choice multiple_choice 预测首字母须匹配允许字母
generation substring_contains 自由文本须含至少一个目标子串

指标契约

每指标是从 (prediction, targets, extras) -> float in [0.0, 1.0] 的函数。框架对每例分求均得任务分,再对任务分求均得总分。指标函数很小:

  • exact_match:小写、折叠空白、相等。
  • substring_contains:同归一化、子串测。
  • multiple_choice:首字符大写。
  • rouge_l:LCS 长除预测与参考长度,精确率与召回的 F1。
  • code_exec:在受限命名空间执行预测,对每输入输出对调 f(x),数匹配。

code_exec 在剥离的 builtins 命名空间跑预测。本节测试断言 import os 爆掉(因 os 不在命名空间),你无法从代码预测够到文件系统。

二、从零实现

code/main.py 五步。

模型适配器是接缝:

class ModelAdapter(Protocol): def generate(self, prompts: Sequence[str]) -> List[str]: ... @property def name(self) -> str: ...

本节发 ToyAdapter,确定性模式匹配器,对五个夹具任务的每提示返正确答案;真实适配器调模型返其输出,框架不在乎是哪个。

指标各自是小函数带单测:

def exact_match(prediction, targets, extras=None): pred = " ".join(prediction.lower().split()) return 1.0 if pred in [" ".join(t.lower().split()) for t in targets] else 0.0 def code_exec(prediction, targets, extras): safe_ns = {"__builtins__": {}} # 剥离 builtins, 防够文件系统 try: exec(prediction, safe_ns) except Exception: return 0.0 f = safe_ns.get("f") if f is None: return 0.0 ok = sum(1 for x, y in extras["io_pairs"] if f(x) == y) return ok / len(extras["io_pairs"]) METRIC_FNS = {"exact_match": exact_match, "code_exec": code_exec, "rouge_l": rouge_l, "multiple_choice": multiple_choice, "substring_contains": substring_contains}

运行器:

def run_task(task_examples, adapter, metric_fn, batch_size=8): scores = [] for i in range(0, len(task_examples), batch_size): batch = task_examples[i:i+batch_size] preds = adapter.generate([e["prompt"] for e in batch]) # 派发给适配器 for e, p in zip(batch, preds): scores.append(metric_fn(p, e["targets"], e.get("extras"))) return TaskResult(score=sum(scores)/len(scores), correct=int(sum(scores)), total=len(scores), latency=...) def run_leaderboard(tasks, adapter): results = {name: run_task(exs, adapter, METRIC_FNS[exs[0]["metric"]]) for name, exs in tasks.items()} overall = mean(r.score for r in results.values()) return Leaderboard(tasks=results, overall=overall)

write_leaderboard 序列化,带 schema 字符串使未来格式变不静默破仪表盘;--include-per-example dump 每例记录,使分动时可 diff 预测与上次运行。

设计要点:适配器是唯一模型特定处——换模型只动适配器,框架不动。指标从零写、小到可审计,是防过拟合的根:vendor 的排行榜是他们调过的,你的从零写的排行榜是你能读的。code_exec 的剥离 builtins 是安全的关键——无它代码预测可 import os 够文件系统。schema 字符串是升级钩子,迁移在它上分支。

三、框架对比

EleutherAI 的 lm-evaluation-harness 是行业标准:数百个基准(MMLU、HellaSwag、ARC 等)、统一接口、可插拔模型。HuggingFace lighteval 同思路,与 transformers 深度集成。OpenAI evals、BIG-bench 各有侧重。本节手写五个指标的目的是让你看清任务规范、指标契约、适配器接缝——这些在用大框架时是黑盒。真实评测还处理:少样本提示模板、生成 vs 对数似然两种任务类、按任务重试与缓存、对抗数据污染(测试例是否混进训练集)。本节的框架小到 15 分钟读完,是这些复杂性的起点。

四、可复用产物

code/main.py + 五个夹具任务 .jsonl + 13 例单测(覆盖归一化、部分重叠、代码执行、不安全代码拒绝)。demo 首运行 seed 夹具、用 ToyAdapter 打分(每夹具全对)、写 outputs/leaderboard.json,总分 1.0;test_main.py 的 stub 适配器测显示同框架在适配器答不出时产 0.0。换真实适配器(OpenAI/Anthropic/本地模型),排行榜原样工作。

五、练习

  1. 加指标:实现 BLEU-4(第 61 节多模态 eval 会用),加 generation 任务的变体。
  2. 真实适配器:把 ToyAdapter 换成真实 API 调用,对五个夹具任务打分,对比 ToyAdapter 的 1.0。
  3. 少样本模板:给每任务的 prompt 加 2~3 个少样本例,重测,对比零样本。
  4. 数据污染:写脚本检测测试 prompt 是否出现在某训练语料里,报污染率。
  5. A/B diff:给两次运行的 leaderboard.json,写 diff 工具按任务报升降、dump 预测变化的例。

本节要点回顾

  1. 框架是契约:昨天与今天运行的契约,无它回归就发布。
  2. 任务即 JSONL:每例带 prompt/targets/metric/extras,文件名是任务名。
  3. 五个指标:精确匹配/rouge-l/code_exec/多选/子串,各 (pred, targets, extras) -> [0,1]
  4. 适配器是接缝:换模型只动适配器,框架不动。
  5. 防过拟合:框架小到 15 分钟读、任务小到进仓库、指标从零写可审计。
  6. code_exec 剥离 builtins:防代码预测够文件系统,安全的关键。

下一节,我们离开分布式赛道进入 AI Scientist 赛道,做「假设生成器」——温度爬坡采样 + 新颖度过滤,产出有深度的排好序假设队列。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U