跳到主要内容

数据截至 (上游 commit 090253dac668)

04 · 训练中途的评测:域困惑度与下游任务

这一章讲什么: olmo/eval/ 怎么把评测做成训练循环的一部分(in-loop evaluation),以及「多选题用 loglikelihood 对比来答」这套标准 ICL 评测的具体实现。


1. 它要解决的小问题

预训练一跑几个月,不能等训完才知道模型行不行。评测必须是训练循环里周期性发生的一步,而且要和训练指标画在同一张图上,才能回答「loss 降了,下游能力涨没涨」。

OLMo 的 in-loop 评测分两条线:

线回答的问题数据来源
LM 评测EvaluatorType.lm模型在各类 held-out 文本上的困惑度多少?与训练数据同格式的 memmap .npy 验证集
下游评测EvaluatorType.downstream模型做 piqa/hellaswag/MMLU 这类选择题的正确率多少?HF datasets / oe-eval 任务包

两条线共享同一个 Evaluator 外壳(olmo/eval/evaluator.py:14)和同一个驱动循环 Trainer.evalolmo/train.py:1011)。


2. 顶层结构:一次评测周期

Trainer.fit() 每 eval_interval 步


Trainer.eval() (olmo/train.py:1011)
│ zero_grad → model.eval()

for evaluator in self.evaluators: ← 配置文件里每个条目一个
│ evaluator.reset_metrics()
│ for batch in evaluator.eval_loader (可截到 subset_num_batches):
│ eval_step(): 前向 → evaluator.update_metrics(batch, ce_loss, logits)
│ evaluator.compute_metrics() → 指标 dict

wandb.log(eval_metrics) → torch.compiler.reset() → model.train() 继续训练

评测器清单在训练配置里逐条列出。configs/official-1124/OLMo2-7B-stage1.yaml:101-215 配了 27 个下游任务(piqa、hellaswag、winogrande、boolq、arc、MMLU 各切片……),外加一段被注释掉的 11 域困惑度评测——注释也是真相的一部分:正式 run 里这批 ppl 评测被移到了别处做。

装配入口 build_evaluatorsolmo/eval/__init__.py:109):按 type 分流到 LM 或 downstream 构建器(build_evaluator:71)。


3. 核心机制一:按数据域算困惑度

小问题: 「验证集困惑度」一个数太粗——模型在代码上学了,在书上呢?要按域分开看。

机制出奇地简单,是第 2 章数据设计的副产品:

  1. 验证集也组织成 memmap .npy,用 data.datasets 字典按域给路径(如 c4_en-validationpile-validation……)。
  2. build_memmap_dataset 给每个实例打上 metadata: {"label": 域名}olmo/data/__init__.py:33-38)。
  3. 评测时 build_evaluator 为每个域建一个 MeanMetricolmo/eval/__init__.py:87-96)。
  4. Evaluator.update_metrics 按实例的 metadata["label"] 把 per-instance CE loss 路由进对应域的 metric(olmo/eval/evaluator.py:76-83)。

也就是说:域标签是顺着数据管线一路带下来的 metadata,评测端零特殊处理。最终指标就是 eval/<label>/CrossEntropyLossPerplexitycompute_metrics:29-63)。

分布式评测用 PyTorch 自带的 DistributedSamplerolmo/data/__init__.py:106-113)按 rank 切分,torchmetrics 负责跨 rank 同步。


4. 核心机制二:下游多选任务——不生成,只打分

小问题: 预训练中的 base model 不会「回答问题」,怎么测 MMLU?

答案是标准的 loglikelihood 多选:把问题和每个候选答案分别拼成序列,让模型前向一遍,算每个候选 continuation 的 log 概率,谁的概率高就选谁。全程没有生成,只有打分——这正是 base model 的天然接口。

4.1 数据侧:一道题变成多条样本

ICLMultiChoiceTaskDataset.prep_examplesolmo/eval/downstream.py:217)把每道题展开:每个候选答案一条样本,内容是 ctx(问题)+ cont(候选),并打上 (doc_id, cont_id) 二元组——同一题的各候选共享 doc_id,这是后面聚合的钥匙。

4.2 指标侧:ICLMetric 怎么记分

ICLMetric.updateolmo/eval/downstream.py:47-101)对每条样本:

取 continuation 对应的 logits 区段
→ gather 出 continuation 每个 token 的 log 概率,求和
→ 按 metric_type 归一化/变换
→ 存 (doc_id, cont_id, loglikelihood) 三元组

metric_type 决定怎么把 log 概率变成分数(:63-93):

类型规则适用
acc直接比 continuation 总 log 概率短答案任务
len_norm除以字符数答案长短不一时公平化
pmi_dc除以「只给领域上下文」时的概率消除领域先验
ce_loss / bpb取负 / 再除字节数乘 log₂e困惑度口径的任务

compute:103-165)做聚合:按 doc_id 把候选分数排好,argmax == label 记对。两个防御细节

  • 候选没凑齐的题直接跳过:135-145):分布式采样下 drop_last=False 会制造重复样本,重排序后一道题的候选可能没在同一个评测窗口里收齐,宁可跳过也不误判。
  • 指标状态只 append 张量三元组,因为 torchmetrics 的分布式状态同步不认嵌套结构(:95-96 注释)。

f1 是个特例:连续值是 yes/no/neither 三分类,用 sklearn 算(:151-161)。

4.3 任务注册表与 oe-eval 对齐

任务名 → 任务类的映射是 label_to_task_mapolmo/eval/downstream.py:2373),它由两代表合并而成:老牌手写任务类(PIQA、HellaSwag 等,:1611)+ 新的 label_to_task_map_new:1881,统一走 OEEvalTask 读 oe-eval 格式的任务包)。METRIC_FROM_OE_EVAL:19-24)把 oe-eval 的指标名(acc_per_char 等)翻译成本地口径(len_norm 等)——评测口径与 AI2 的外部评测套件保持一致。


5. 真实实现坐标

步骤位置符号
训练循环触发评测olmo/train.py:1312-1325fit 内的 eval 分支
评测主循环olmo/train.py:1011-1055Trainer.eval
单 batcholmo/train.py:914-931eval_batch / eval_steploss_reduction="none" 拿逐实例 loss)
下游评测器装配olmo/eval/__init__.py:24-68build_downstream_evaluator
指标更新/计算olmo/eval/evaluator.py:67:29update_metrics / compute_metrics
ICL 打分olmo/eval/downstream.py:28ICLMetric
任务展开olmo/eval/downstream.py:168:217ICLMultiChoiceTaskDatasetprep_examples

6. 坑与边界

  • 只支持 zero-shot。 ICLMultiChoiceTaskDataset 类 docstring 首行就是「Only supports zero-shot for now」(olmo/eval/downstream.py:169)。few-shot、生成式评测在仓库外的 olmes / OLMo-eval。
  • 评测完要重置编译器。 eval 会编译出另一批图,导致回到训练时性能变差,所以 Trainer.eval 末尾 torch.compiler.reset()olmo/train.py:1051-1053)。
  • MeanMetric 空跑保护。 一个评测器含多个域、而当前窗口某域没跑到时,compute_metrics 先给它 update 一个权重 0 的假值避免 NaN(olmo/eval/evaluator.py:48-54)。
  • 下游评测的 batch 内容依赖 tokenizer 截断方向TokenizerConfig.truncate_directionolmo/config.py:686):超长样本从右边截断,问题部分可能截丢——评测有效性隐含依赖「截断别吃掉关键内容」。
  • in-loop 数字是监控口径,不是论文口径。 正式报告的分数以训后离线评测为准(README 指向 olmes);in-loop 的价值是曲线形状和相对趋势(inferred:in-loop 用 zero-shot 与固定子集,本来就为监控而非榜单设计)。

7. 代码地图

主题文件路径符号名
评测器外壳olmo/eval/evaluator.py:14Evaluator
装配olmo/eval/__init__.py:71:109build_evaluatorbuild_evaluators
LM 困惑度olmo/eval/evaluator.py:76-83update_metrics 的 lm 分支
下游多选指标olmo/eval/downstream.py:28ICLMetric
任务数据集olmo/eval/downstream.py:168ICLMultiChoiceTaskDataset
任务注册表olmo/eval/downstream.py:2373label_to_task_map
oe-eval 口径翻译olmo/eval/downstream.py:19METRIC_FROM_OE_EVAL
训练侧驱动olmo/train.py:1011Trainer.eval
真实评测清单configs/official-1124/OLMo2-7B-stage1.yaml:101-215(YAML evaluators: 段)