评估流水线


文档摘要

评估流水线 本节摘要:训练是你能用损失曲线监控的部分;评估是你必须设计的部分。本节构建统一评估流水线:接收任意训练好的语言模型,在其上跑四个异质评估,把结果聚合成每任务报告,并附一个本地 mock「LLM 当判官」(LLM-as-judge),使整个循环无需联网即可运行。四个评估覆盖每个要发布的模型都需要的维度:语言建模(困惑度)、短形式正确性(精确匹配)、开放形式相似度(token F1)、定性打分(判官)。单一指标从不描述一个语言模型——困惑度只说模型多贴合语言分布、不说它能否答问;精确匹配只说是否产出黄金串、惩罚正确改写;token F1 宽恕改写但被词汇重叠骗;判官抓定性维度但贵且随机。四个一起,每个评估覆盖其余漏掉的维度。

评估流水线

本节摘要:训练是你能用损失曲线监控的部分;评估是你必须设计的部分。本节构建统一评估流水线:接收任意训练好的语言模型,在其上跑四个异质评估,把结果聚合成每任务报告,并附一个本地 mock「LLM 当判官」(LLM-as-judge),使整个循环无需联网即可运行。四个评估覆盖每个要发布的模型都需要的维度:语言建模(困惑度)、短形式正确性(精确匹配)、开放形式相似度(token F1)、定性打分(判官)。单一指标从不描述一个语言模型——困惑度只说模型多贴合语言分布、不说它能否答问;精确匹配只说是否产出黄金串、惩罚正确改写;token F1 宽恕改写但被词汇重叠骗;判官抓定性维度但贵且随机。四个一起,每个评估覆盖其余漏掉的维度。

对应原课程:Phase 19 · Lesson 41 · eval-pipeline(原英文 phases/19-capstone-projects/41-eval-pipeline/docs/en.md)。本节属「从零构建 GPT」赛道第十二节。

学习目标

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

  1. 在微型 transformer 上用掩码 token 计数算留出集困惑度(perplexity)。
  2. 在短形式事实提示上跑精确匹配(exact-match)评估。
  3. 在预测与参考串之间算带归一化的 token 级 F1
  4. 构建本地 mock LLM-as-judge,在 1-5 分制上给模型输出打分。
  5. 把四个评估聚合成单一加权报告,带每任务细分。

一、问题与直觉

你想要的流水线是四个都有。每个评估覆盖其余漏掉的维度,各自跑在为该指标整形的不同留出子集上。最终报告把每任务数字并排放、加一个聚合,让评审一眼看出模型在做哪些权衡。

每个评估是从 (model, dataset) -> EvalResult 的函数。结果携带指标值、供检视的每例细节、与供聚合用的名字。流水线用一个配置说跑哪些评估、怎么加权,把它们组合起来。

困惑度,正确计数

困惑度是 exp(每 token 平均负对数似然)。实现有两个陷阱:

  • 均值必须在实际 token 位上算,而非在 batch*sequence 上算。填充 token 必须从分母排除,否则困惑度看起来比实际好。
  • 模型预测下一 token,故位 i 的 logits 预测位 i+1 的 token。这里的差一错是静默的:损失仍训,但指标变无意义。

评估在非填充位上算每批 -log p(token) 之和与每批 token 计数,末尾再除。这比每批困惑度取平均(低估短序列权重)数值更稳,且匹配教科书定义。

精确匹配、token F1、判官

精确匹配:预测串归一化(小写、去空白、折叠双空格)后与同样归一化的参考比,每例 1/0。token F1:预测与参考都小写切词,算共享 token 的 F1——宽恕改写,但被词汇重叠骗(错内容若词汇重叠高也会高分)。判官:本节用 mock 实现,按启发式(回答长度、是否含参考关键词、是否完整句)给 1-5 分,使循环无需联网;真实管线接 GPT-4/Claude 当判官,接口相同。

二、从零实现

code/main.py 实现:

  • perplexity_eval(model, dataset):累计非填充位负对数似然与 token 数,末尾 exp(总/计数)
  • exact_match_eval(model, prompts, references):贪心生成、归一化、逐例比。
  • token_f1_eval(model, prompts, references):token 化后算 precision/recall/F1。
  • judge_eval(model, prompts, references, rubric):mock 判官按 rubric 打 1-5。
  • aggregate(results, weights):加权聚合每任务分到单一报告。

困惑度的关键骨架:

def perplexity_eval(model, loader): total_nll, total_tokens = 0.0, 0 model.eval() with torch.no_grad(): for x, y, mask in loader: # mask 标非填充位 logits = model(x) # (B, T, V) logp = F.log_softmax(logits, dim=-1) nll = -logp.gather(-1, y.unsqueeze(-1)).squeeze(-1) # (B, T) nll = (nll * mask).sum() # 只算非填充位 total_nll += nll.item() total_tokens += mask.sum().item() return {"perplexity": math.exp(total_nll / max(1, total_tokens))}

每个评估返 EvalResult(name, value, per_example);聚合按配置加权:

def aggregate(results, weights): score = sum(weights[r.name] * r.value for r in results) return {"tasks": {r.name: r.value for r in results}, "aggregate": score / sum(weights.values())}

设计要点:困惑度的「末尾再除」而非「每批困惑度取平均」是数值安全的关键——后者按批等权,短序列被高估,且每批 exp 易溢出。所有评估用同一模型适配器接口 model.generate(prompt) -> strmodel(ids) -> logits,使适配器是唯一放模型特定代码的地方。换模型只动适配器,流水线其余不动。

三、框架对比

lm-evaluation-harness(EleutherAI)是行业标准:数百个基准、统一接口、可插拔模型。HuggingFace evaluate 库提供精确匹配、BLEU、ROUGE 等单项指标。OpenAI evals、Anthropic 的内部 eval 框架思路相同:任务定义 + 指标 + 运行器 + 排行榜。本节手写四评估的目的是让你看清困惑度的掩码计数、F1 的 token 化、判官的 rubric——这些在用大框架时是黑盒。真实 LLM-as-judge 接 GPT-4/Claude API,需处理随机性(temperature=0、多投票)、位置偏置(交换候选顺序看分数是否变)、rubric 漂移(判官标准随时间变),这些是产品化评估的难点,本节的 mock 帮你先把骨架跑通。

四、可复用产物

code/main.py:四个评估函数 + 聚合器 + demo。demo 在 CPU 上几十秒跑完,把四个评估跑在一个微型预训模型上,打印每任务分与加权聚合。四个 *_eval 函数与 aggregate 可复用——换到任何有适配器的模型上,评估流水线原样工作。EvalResultper_example 字段让你能下钻看哪个例子拖了分。

五、练习

  1. 困惑度陷阱:故意把填充位算进分母,确认困惑度虚降,再修回。
  2. F1 边界:构造「预测与参考词汇高度重叠但语义相反」的例子,确认 token F1 仍给高分,讨论这是漏洞还是特性。
  3. 判官随机性:mock 判官加随机扰动,跑 5 次取均值与方差,讨论真实判官的稳定性。
  4. 真判官:把 mock 换成真实 API 调用(OpenAI/Anthropic 免费额度),对比打分。
  5. 加权扫描:扫四个评估的权重,观察聚合排名是否翻转,讨论哪个指标最该重。

本节要点回顾

  1. 单一指标不够:困惑度/精确匹配/token F1/判官各覆盖一个维度,四个一起才描述全模型。
  2. 困惑度计数陷阱:均值在实际 token 位上算、填充排除、差一错静默致命。
  3. 精确匹配最严:字符级 1/0,惩罚正确改写。
  4. token F1 宽恕改写:但被词汇重叠骗,语义错也会高分。
  5. 判官抓定性:贵且随机,本节 mock 使循环离线,真实接 API 接口相同。
  6. 适配器是唯一模型特定处:换模型只动适配器,流水线不动。

下一节,我们离开 GPT 赛道进入预训练/分布式赛道,做「大语料下载器」——流式下载、断点续传、MinHash 去重。


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