端到端评估运行器:五节课的管道,一次粘合


文档摘要

端到端评估运行器:五节课的管道,一次粘合 本节摘要:五节课的管道,一节课把它们粘起来。运行器读 70 节的任务规格,经适配器调模型,用 71 与 72 节打分,附上 73 节的校准报告,发出 74 节的排行榜。配套一个 接口(mock、本地、API 任模型都能用小方法面满足),用线程池并行跑任务,单遍同时组合指标层与校准层,发出每模型 喂进排行榜聚合器,输出 JSON 报告加 markdown 表;demo 自终止,干净跑退出码 0,校验或运行失败非零。读完本节,你交付的不是一个脚本,而是一个指标无关、提供商无关、可接真实模型的评估系统。 对应原课程:Phase 19 · Lesson 75 · (原英文 )。本节属第 20 章「毕业项目」的评估赛道,是本赛道的收官。

端到端评估运行器:五节课的管道,一次粘合

本节摘要:五节课的管道,一节课把它们粘起来。运行器读 70 节的任务规格,经适配器调模型,用 71 与 72 节打分,附上 73 节的校准报告,发出 74 节的排行榜。配套一个 ModelAdapter 接口(mock、本地、API 任模型都能用小方法面满足),用线程池并行跑任务,单遍同时组合指标层与校准层,发出每模型 EvalRun 喂进排行榜聚合器,输出 JSON 报告加 markdown 表;demo 自终止,干净跑退出码 0,校验或运行失败非零。读完本节,你交付的不是一个脚本,而是一个指标无关、提供商无关、可接真实模型的评估系统。

对应原课程:Phase 19 · Lesson 75 · end-to-end-eval-runner(原英文 phases/19-capstone-projects/75-end-to-end-eval-runner/docs/en.md)。本节属第 20 章「毕业项目」的评估赛道,是本赛道的收官。

学习目标

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

  1. 定义一个 ModelAdapter 接口,让任模型(mock、本地、API)用小方法面满足。
  2. 在固定 JSONL 文件上跨线程池并行跑评估。
  3. 把指标层(exact_match、F1、BLEU-4、ROUGE-L、code_exec)与校准层一次跑通
  4. 发出每模型 EvalRun 记录,直喂排行榜聚合器
  5. 同时输出 JSON 报告与 markdown 表;干净跑退出码 0,校验或运行失败非零。

一、问题与直觉

70~74 节每节拥有一个模块,运行器是集成点,它导入而非复制这些模块的逻辑。运行器是评估赛道的「端到端系统」对应物:前面五节课的部件单独成立什么也不证明,只有粘成一条流水线、跑同一固定集、出统一报告,才算交付。

二、从零实现

适配器接口

适配器是运行器与任模型之间的缝,接口故意很小:

class ModelAdapter: model_id: str def generate(self, prompt: str, task: TaskSpec) -> Generation: ...

Generation 是 dataclass,含:

  • text:模型的自由格式输出。
  • confidence:[0,1] 的 float,表示模型对该答案的自报概率。
  • token_nll:可选,生成 token 上的负对数似然之和。
  • token_count:可选,生成 token 数。

运行器里的 mock 适配器提供三种风味:RuleBasedAdapter(确定、近乎完美)、NoisyAdapter(过自信、常错)、BiasedAdapter(某类好、另类差)。demo 在 70 节固定集上跑全部三种。

并行执行

运行器用 concurrent.futures.ThreadPoolExecutor 按模型并行跑任务,工作数默认取 8 与任务数的较小者。线程够用,因为真实模型调用的瓶颈是网络 I/O;code_exec 路径在任务内起自己的子进程,执行器只调度等待。确定性测试下,运行器暴露 run_eval(adapters, tasks, parallel=False) 让测试钉死执行顺序。

单遍打分循环

每任务:

  1. 渲染提示(few-shot 前缀加 prompt 体)。
  2. 调适配器并计时。
  3. 按任务规则后处理生成。
  4. 分派到指标层。
  5. 构建带分数与指标元数据的 EvalRun 记录。
  6. (置信度, 正确) 对追加进校准缓冲。

正确 信号对 exact_match 风格指标(exact_matchaccuracycode_exec)是 score >= 1.0,对分级指标是 score >= 0.5。阈值在 _correct_from_score,运行器不暴露公开覆盖。

def _score_one(adapter, task): prompt = render(task) gen = adapter.generate(prompt, task) norm = post_process(gen.text, task["post_process"]) s = score(task["metric_name"], norm, task["targets"]) return EvalRun(model_id=adapter.model_id, task_id=task["task_id"], metric_name=task["metric_name"], score=s, category=task["category"]), (gen.confidence, _correct(s))

聚合

每任务有结果后,运行器调 74 节的 aggregatepairwise_diffs、73 节的 CalibrationReport.from_predictions,输出单 JSON 信封:

{ "leaderboard": [...], "pairwise": [...], "calibration": { "model_id_a": {"ece": 0.04, "brier": 0.10, "populated_bins": 8}, "...": {} }, "summary": {"tasks": 10, "models": 3, "wall_seconds": 1.2} }

运行器还把 markdown 表写到 stdout,让用户能把结果贴进 PR 评审。

自终止 demo

demo 在 70 节的十条固定任务上跑三个 mock 适配器,墙钟应低于 10 秒,干净跑退出码 0。干净标准:每任务在 70 节下校验通过;每任务在 71、72 节下打分;73 节校准报告无错聚合;排行榜把规则适配器严格排在随机适配器之上。任一破裂,运行器非零退出并在 JSON 信封里带结构化错误。

三、框架对比

维度 本节运行器 lm-eval-harness HELM OpenCompass
任务格式 70 节 JSONL YAML/Py 类 严格 JSON Py 类
适配器 ModelAdapter.generate LM 抽象类 Service APITokenizer
并行 线程池 线程/进程 进程 线程/分布式
指标分派 单字段 metric_name 注册表 中央矩阵 注册表
校准 内建(73 节) 部分 内建 部分

业界共识与本节一致:适配器是缝,运行器对指标与提供商都无关。HELM 的中央指标矩阵对应本节的封闭 metric_name 词表;lm-eval 的 LM 抽象对应本节 ModelAdapter。差别在本节单遍同时算指标与校准,而多数框架分两趟。

四、可复用产物

  • run_eval(adapters, tasks):本节是评估赛道的集成点;70~74 节每节拥有一个模块,运行器导入而非复制。
  • ModelAdapter + Generation:任何模型只需写约三十行胶水(挑一个有免费额度的提供商),排行榜就亮起来;加第二个提供商,框架做剩下的活。
  • JSON 信封 + markdown 表:CI 评论与结果存档的共用形状。

main.py 是集成,通过一个小的 _load_sibling 助手按相对路径解析导入其他五课模块;GenerationEvalReportModelAdapter 在本地定义,mock 适配器在文件底部。从顶到底读:略读导入,看 run_eval,再看 _score_one,再看适配器,末尾 demo 是入口。code/tests/test_runner.py 钉死适配器接口、单遍循环、并行等于顺序、校准缓冲、JSON 信封形状。

五、本节不做的事

本节不调真实模型、不实现 API key 流或限流处理、不做流式或部分生成(适配器每次返回一个生成)、不做重试或缓存——那些关切在适配器层;运行器对指标与提供商都无关。

💡 这个运行器是地基。生产评估系统再加:按 (task_id, model_id, model_version) 键的结果缓存、跟踪每运行美元与 token 的成本账本、限流退避层、pass-at-k 任务的采样策略、长套件的流式输出格式。每一个都是单一关切,包裹运行器而不改指标或聚合层——这种分离正是契约的意义

六、练习

  1. 接真实提供商:挑一个有免费额度的,写三十行 ModelAdapter 胶水,在固定集上看排行榜亮起来。
  2. 加第二个提供商:让框架做剩下的活,对比两模型的两两差 CI。
  3. 加结果缓存:按 (task_id, model_id, model_version) 键,重跑时命中缓存,报告墙钟节省。
  4. 加成本账本:每模型跟踪 token 数与估算美元,加进 summary
  5. 加 pass-at-k 采样策略:对 code_exec 任务每模型采 n=10,接 72 节 pass_at_k,在排行榜里报 pass-at-1/5/10。

本节要点回顾

  1. 运行器是集成点:70~74 节每节拥有一个模块,运行器导入而非复制——指标无关、提供商无关。
  2. 适配器是缝:ModelAdapter.generate(prompt, task) -> Generation(text, confidence, token_nll, token_count),mock/本地/API 任模型用小方法面满足。
  3. 线程池并行:瓶颈是网络 I/O,线程够用;code_exec 在任务内起子进程。
  4. 单遍同时算指标与校准:(置信度, 正确) 对进缓冲,正确阈值 exact_match 类 >=1.0、分级类 >=0.5。
  5. 输出 JSON 信封 + markdown 表:排行榜、两两差、校准、小结;markdown 可贴 CI 评论。
  6. 自终止 demo:三条干净标准(校验/打分/校准无错、规则适配器严格胜随机),破裂则非零退出带结构化错误。
  7. 地基先稳:缓存、成本、退避、采样、流式都是包裹运行器的单一关切,不改指标或聚合层——这是契约的意义。

下一节,我们将切换到分布式训练赛道(Track H),从「集合通信从零」开始——用纯 Python 模拟 all-reduce、all-gather、broadcast 等集体通信原语,为后续 DDP、ZeRO、流水线并行打地基。


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