LM 评估框架 本节摘要:一个在你说不清的任务上表现好的模型,是碰巧表现好。框架是任务定义、指标、运行器、排行榜,装进一个短、可换的形状。每周来一个新语言模型,营销声称它表现好;诚实的问题是「好在什么」,诚实的回答是你自己写的排行榜。无框架你凭感觉比两个模型;有框架你在固定任务集、固定指标上比、在可 diff 的 JSON 上比。本节把任务定义为带 / / /可选 的 JSONL 文件,实现五个指标(精确匹配、rouge-l F1、可执行检查、多选、子串包含),建按任务批并派发给可换模型适配器的运行器,发带每任务分、延迟、可复现总均的排行榜 JSON。 对应原课程:Phase 19 · Lesson 49 · (原英文 )。本节属「预训练/分布式」赛道第八节。
本节摘要:一个在你说不清的任务上表现好的模型,是碰巧表现好。框架是任务定义、指标、运行器、排行榜,装进一个短、可换的形状。每周来一个新语言模型,营销声称它表现好;诚实的问题是「好在什么」,诚实的回答是你自己写的排行榜。无框架你凭感觉比两个模型;有框架你在固定任务集、固定指标上比、在可 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)。本节属「预训练/分布式」赛道第八节。
阅读完本节,你应当能够:
prompt、targets、metric、可选 extras。框架是昨天运行与今天运行的契约。无它,回归就发布。陷阱是把框架过拟合到单一模型;修复是同一陷阱的反面:框架小到 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/本地模型),排行榜原样工作。
generation 任务的变体。ToyAdapter 换成真实 API 调用,对五个夹具任务打分,对比 ToyAdapter 的 1.0。leaderboard.json,写 diff 工具按任务报升降、dump 预测变化的例。prompt/targets/metric/extras,文件名是任务名。(pred, targets, extras) -> [0,1]。下一节,我们离开分布式赛道进入 AI Scientist 赛道,做「假设生成器」——温度爬坡采样 + 新颖度过滤,产出有深度的排好序假设队列。