实验运行器


文档摘要

实验运行器 本节摘要:循环只如其测量般诚实。构建运行器:取 spec,在沙盒子进程里执行,发评估器可信任的 json 指标 blob。研究循环跑不信任的代码——假设来自采样器,实验脚本来自同路径,把任一当进程内安全是请一个把编排器拖垮的崩溃。子进程是语言自带的隔离:独立进程、独立地址空间、父侧的信号句柄。本节运行器不实现完整沙盒(无 cgroup、无 seccomp、无 namespace 重映射),但有硬墙时超时、内存增长的轮询循环、超限杀进程的路径。这是每个更精心沙盒扩展的运行时契约,本节把契约缩到一次能读完。 对应原课程:Phase 19 · Lesson 52 · (原英文 )。本节属「AI Scientist」赛道第三节。

实验运行器

本节摘要:循环只如其测量般诚实。构建运行器:取 spec,在沙盒子进程里执行,发评估器可信任的 json 指标 blob。研究循环跑不信任的代码——假设来自采样器,实验脚本来自同路径,把任一当进程内安全是请一个把编排器拖垮的崩溃。子进程是语言自带的隔离:独立进程、独立地址空间、父侧的信号句柄。本节运行器不实现完整沙盒(无 cgroup、无 seccomp、无 namespace 重映射),但有硬墙时超时、内存增长的轮询循环、超限杀进程的路径。这是每个更精心沙盒扩展的运行时契约,本节把契约缩到一次能读完。

对应原课程:Phase 19 · Lesson 52 · experiment-runner(原英文 phases/19-capstone-projects/52-experiment-runner/docs/en.md)。本节属「AI Scientist」赛道第三节。

学习目标

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

  1. 把实验编成类型化 spec,运行器可序列化到子进程。
  2. 用硬墙时超时与软内存上限启子进程,把两者作终止条件暴露。
  3. 把 stdout、stderr、结构化指标 blob 捕获进单一结果记录。
  4. 构建在固定基础 spec 上一次扫一个配置旋钮的消融表。
  5. 给定种子保每结果确定,使评估器跨运行见同数字。

一、问题与直觉

为什么子进程

研究循环跑不信任的代码。假设来自采样器,实验脚本来自同路径,把任一当进程内安全是请一个把编排器拖垮的崩溃。子进程是最简隔离:独立进程、独立地址空间、父侧信号句柄。

ExperimentSpec 的形状

ExperimentSpec spec_id : str (稳定 id, "exp_001") hypothesis_id : int (链回第 48 节队列) script_path : str (要跑的 python 脚本路径) config : dict (作为一个 json 参数传给脚本) seed : int (实验的确定性种子) wall_timeout_s : float (硬超时, 超即杀) memory_cap_mb : int (软上限, 轮询; 超即杀) metric_keys : list[str] (评估器将读哪些字段)

脚本在磁盘上;运行器把 config 写到脚本读的临时文件路径。脚本须在 stdout 打单行 json,其键是 metric_keys 的超集。stdout 其余被捕获但指标解析器忽略。

为什么软内存上限

硬内存上限需 resource.setrlimit,只在 POSIX 工作。本节发可移植方法:轮询平台常驻集大小,超上限杀子进程。上限是软的,因轮询有非零间隔;进程可在两次轮询间尖峰超上限又落回。运行器记最大观测 RSS 使评估器看见运行多近上限。无进程检视支持的系统上,轮询器打一次性警告并禁用,墙时超时仍适用。

捕获 stdout 与 stderr

运行器在完成后排空两管道。stdout 逐行扫;最后一行解析为 json 且含全部所需 metric_keys 的取为指标 blob。更早 json 行作 intermediate_metrics 留在结果(评估器可作学习曲线)。stderr 逐字捕获。运行器永不在非零退出码上抛,而是记码;任何非零退出标 "crash",即便脚本打了指标,使评估器默认把部分跑当失败。

消融表

给定基础 spec 与旋钮名,助手返每值一个 spec(config[knob] 覆写),每 spec 得派生 spec_idAblationRunner 按序跑返按旋钮值键的 AblationTable。一次一个旋钮——全因子扫指数炸、产评估器无法解的结果;一次一旋钮产评估器可绘的干净轴。多旋钮扫只作重复单旋钮消融,由调用方组合。

确定性

每 spec 带种子。运行器经 config 字典把种子转发给脚本(config["__seed"] = spec.seed)。code/experiments/ 的 mock 实验脚本尊种子、跨运行产相同指标。第 51 节评估器依赖此;无确定性,「回归」可能是不同随机初始化。

二、从零实现

code/main.py 定义 ExperimentSpecExperimentResultExperimentRunnerAblationRunner 与确定性 demo。子进程管理一类,内存轮询小线程,消融助手一函数。code/experiments/sparsity_experiment.py 是 mock 实验(读 config 路径、numpy 随机过小训练、打 json 指标行),尊 sleep_s(测超时)与 allocate_mb(测内存轮询)。

@dataclass class ExperimentResult: spec_id: str; hypothesis_id: int; exit_code: int terminal: str # "ok" | "timeout" | "oom" | "crash" wall_time_s: float; peak_rss_mb: float | None metrics: dict; intermediate_metrics: list stdout_tail: str; stderr_tail: str def run(spec: ExperimentSpec) -> ExperimentResult: cfg_path = write_temp_json(spec.config | {"__seed": spec.seed}) proc = subprocess.Popen([sys.executable, spec.script_path, cfg_path], stdout=PIPE, stderr=PIPE) poller = MemoryPoller(proc.pid, spec.memory_cap_mb) # 软上限轮询 try: out, err = proc.communicate(timeout=spec.wall_timeout_s) terminal = "crash" if proc.returncode else "ok" except subprocess.TimeoutExpired: proc.kill(); out, err = proc.communicate() terminal = "timeout" if poller.exceeded: terminal = "oom" metrics, interm = parse_metrics(out, spec.metric_keys) # 末 json 行 + 中间 return ExperimentResult(spec.spec_id, spec.hypothesis_id, proc.returncode, terminal, ..., poller.peak_rss, metrics, interm, ...) def ablate(base, knob, values): return [replace(base, spec_id=f"{base.spec_id}_{knob}_{v}", config={**base.config, knob: v}) for v in values]

设计要点:terminal 字段是评估器的第一读——非 "ok" 则评估器判定自动为失败,指标不经显著性检验。intermediate_metrics 留学习曲线(评估器可看收敛)。消融一次一旋钮产干净轴,全因子炸。种子经 config 转发使确定性跨运行,无它「回归」可能是随机初始化噪声。

三、框架对比

真实研究代理(AI-Scientist、MLAgentBench)用 Docker 容器或 Kubernetes job 做更强隔离,加 GPU 资源限额、日志聚合、结果存储。本节子进程 + psutil 是最小可行沙盒——够隔离崩溃、够测超时与 OOM,但不够防恶意代码(无文件系统隔离、无网络限制)。产品化加 Docker(文件系统 + 网络 + 资源)、Firejail(轻量沙盒)、或云函数(毫秒计费、完全隔离)。WandB/MLflow 是指标存储层,与运行器正交。本节的 ExperimentSpec/ExperimentResult 契约是这些产品化封装的底座。

四、可复用产物

code/main.py + code/experiments/sparsity_experiment.py + code/tests/test_runner.py(覆盖成功、超时、崩溃、消融表、确定性)。ExperimentRunner.run(spec) 是编排器入口,产 ExperimentResult 供第 51 节评估器消费。ablate 函数与 AblationRunner 可独立复用——任何「一次一旋钮」扫场景。换真实验脚本(导真模型),spec/result/runner 不动。

五、练习

  1. 内存注入:用 allocate_mb 让 mock 实验分配超上限,确认 terminal="oom"、RSS 被记。
  2. 超时注入:用 sleep_s 让 mock 实验睡超时,确认 terminal="timeout"
  3. 崩溃路径:让 mock 实验非零退出,确认 terminal="crash"、即便打了指标也当失败。
  4. 多旋钮:用 ablate 跑两旋钮各三值,组合成 9 spec,讨论全因子 vs 单旋钮的可解读性。
  5. 确定性断言:同 spec 跑两次,断言指标逐位相同。

本节要点回顾

  1. 子进程隔离:不信任代码须独立进程,防崩溃拖垮编排器。
  2. 硬超时 + 软内存:墙时硬杀、RSS 轮询软杀,无进程检视则退化为只墙时。
  3. terminal 字段:评估器第一读,非 ok 判定自动失败,不经显著性检验。
  4. 消融一次一旋钮:干净轴可绘,全因子炸不可解。
  5. 种子经 config 转发:确定性跨运行,无它「回归」是随机噪声。
  6. 中间指标留学习曲线:intermediate_metrics 供评估器看收敛。

下一节,我们做「结果评估器」——把指标转成一行结论,用从零配对 t 检验判改进、回归、还是噪声。


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