Prhub

#1906 Snapshot the launchers that build their own command line

原始 PR 作者 fzyzcjy 合并时间 2026-08-09 18:46 文件变更 25 提交数 13 评论 1 代码增减 +2574 / -0

执行摘要

为自构建命令的启动器新增快照测试

本 PR 是跟踪 issue #1837“refactoring and enhancements”中的一环(PR body 注明 Part of #1837)。在整理启动脚本时发现,p2p_weight_transfer/run.py、formal_math/run_minimal.py 这类启动器不经过 command_utils,而是自己构建完整命令行并直接调用 subprocess 或 shell 脚本,既有快照测试(针对 shell 启动器和 python 启动器)无法覆盖。测试 docstring 明确指出“These launchers build their whole command line by hand, so only a snapshot pins it”,即只有快照能把这类手写命令固定下来,防止后续重构悄悄改变训练启动命令。

值得精读。重点看 install_shell_recorder 的冻结策略(PID、sleep、makedirs)和 iter_self_executing_launchers 的“按行为发现”设计,它把‘哪些启动器需要保护’变成了可断言的测试。对负责启动脚本重构的工程师,这套快照是正确的第一道防线;后续 PR #1907-#1911 都建立在此基础上。

讨论亮点

本 PR 没有人审 review 评论(review_comments_count 为 0),GitHub 上仅有一条 Gemini Code Assist 的停用提示,不构成技术讨论。真正有价值的“讨论”藏在测试代码的 docstring 里,例如“这些启动器完全手写命令行,只有快照能把它钉住”“启动器会把自身 PID 嵌进清理命令,所以快照只有在冻结 PID 后才会稳定”,这些是测试设计约束的自我说明。

实现拆解

  1. 扩展测试基架(tests/fast/launch_scripts/py_harness.py):新增 iter_self_executing_launchers,以“不属于 scripts/run_*.py 约定且源码文本包含 ray job submit”的行为特征发现自执行启动器;新增 install_shell_recorder,通过 monkeypatch subprocess.runtime.sleepos.makedirsos.getpid/os.getppid 录制命令并冻结不确定性来源,使录制结果可复现。
  2. 新增快照测试(tests/fast/launch_scripts/test_self_executing_launchers.py):定义 LauncherCase 与参数化 fixture recorded,为 p2p_weight_transfer 的 11 个模型 profile × p2p/broadcast 模式以及 formal_math 的 import 模式共 23 个 case 执行启动器入口并把命令录制结果与快照比较;另含重跑一致性、最终必须 ray job submit、发现覆盖三个维度断言。
  3. 生成 23 个快照文件(tests/snapshots/launch_scripts/self_executing/):按“启动器路径/场景名.txt”的结构存放完整录制(含环境清理、ray 启动、ray job submit 携带的完整训练参数),作为 CI 比对基线;这些文件同时充当手写命令的“文档”。
  4. 配套约束TestDiscovery 保证“按行为发现”与“已覆盖 case”完全一致,新增手写启动器会立刻被测试点名;同时显式声明无法被沙箱化的 cmd_prepare 入口点并校验其硬编码 /root/models 路径,避免测试盲区被掩盖。
文件 模块 状态 重要度
tests/fast/launch_scripts/test_self_executing_launchers.py 启动器 added 7.19
tests/fast/launch_scripts/py_harness.py 测试基架 modified 5.97
tests/snapshots/launch_scripts/self_executing/examples/infra_features/p2p_weight_transfer/run.py/run/GLM-5/p2p.txt 快照 added 3.82
tests/snapshots/launch_scripts/self_executing/examples/infra_features/p2p_weight_transfer/run.py/run/GLM-5/broadcast.txt 快照 added 3.81

关键符号

iter_self_executing_launchers install_shell_recorder fake_run recorded test_commands_match_snapshot test_reruns_produce_identical_recordings test_the_launcher_submits_a_ray_job test_every_self_executing_launcher_has_at_least_one_case test_the_uncovered_entrypoint_is_named_and_still_uncoverable

关键源码片段

tests/fast/launch_scripts/py_harness.py test-coverage

新增录制器与发现函数,是快照测试的基架。

# 自执行启动器:不满足 scripts/run_*.py 约定,但源码里包含 ray job submit 的 Python 文件
def iter_self_executing_launchers() -> list[Path]:
    """Launchers that reach the shell themselves rather than through command_utils."""
    roots = [REPO_ROOT / root for root in ("scripts", "examples", "tools")]
    convention = {script.path for script in iter_py_launch_scripts()}
    return sorted(
        path
        for root in roots
        for path in root.rglob("*.py")
        if path not in convention and "ray job submit" in path.read_text(errors="replace")
    )
​
​
# 这类启动器持有自己的 subprocess 句柄,不会经过 command_utils 的录制器,因此单独拦截 subprocess.run
def install_shell_recorder(monkeypatch, sandbox: Path) -> Recording:
    """A launcher holding its own subprocess handle never touches the recorded command_utils helpers."""
    recording = Recording(commands=[], pseudo_files=[])
​
    # fake_run 记录命令并返回一个足够大的 GPU 数量,让启动器里的等待循环立即通过
    def fake_run(command, *args, **kwargs):
        recording.commands.append(command if isinstance(command, str) else " ".join(command))
        return subprocess.CompletedProcess(
            args=command, returncode=0, stdout=_GPU_COUNT_ANY_WAIT_LOOP_ACCEPTS, stderr=""
        )
​
    monkeypatch.setenv("MILES_LOG_DIR", str(sandbox))
    monkeypatch.setattr(subprocess, "run", fake_run)
    monkeypatch.setattr(time, "sleep", lambda seconds: None)
    monkeypatch.setattr(os, "makedirs", lambda path, **kwargs: None)
    monkeypatch.setattr(os, "getpid", lambda: _FROZEN_PID)
    monkeypatch.setattr(os, "getppid", lambda: _FROZEN_PPID)
​
    return recording

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

快照与模型脚本强耦合:23 个快照记录了 p2p_weight_transfer 针对 11 个模型 profile 的完整参数,任何参数默认值、模型脚本路径或配置 key 变化都会让 CI 失败,维护成本集中在 tests/snapshots/launch_scripts/self_executing/ 下。录制有盲区:install_shell_recorder 只拦截 subprocess.run,若启动器改用 os.systemsubprocess.Popen 等路径则录不到;fake_run 固定返回 GPU 数量,依赖真实命令输出的启动器可能被“假阳性”通过。发现机制依赖文本匹配:iter_self_executing_launchers 靠源码里出现 ray job submit 字符串判断,注释或文档字符串误出现可能造成误报/漏报,目前依赖 TestDiscovery 兜底。此外 cmd_prepare 因硬编码 /root/models 无法沙箱化,该入口点没有被录制覆盖。

开发者修改任何自执行启动器(尤其是 p2p_weight_transfer 与 formal_math 示例)时必须同步更新对应快照,否则 CI 会失败;CI 新增 23 个快照断言,运行时间略有增加。对团队而言,这套基线把长尾的手写启动命令显式化,使 #1837 系列后续重构(如 #1907 修复 rotary_base、#1910 用 Python 替换 shell 配置)有明确的回归网。对生产系统无影响(纯测试变更)。

快照维护成本高 环境敏感测试 文本发现机制 monkeypatch 依赖 存在无法覆盖的入口点

关联 Issue

#1837 Tracking issue for refactoring and enhancements

完整报告

参与讨论