Prhub

#35018 Clean up playground scripts and add PR babysitter launcher

原始 PR 作者 merrymercy 合并时间 2026-08-17 05:43 文件变更 3 提交数 3 评论 0 代码增减 +332 / -27

执行摘要

新增 PR babysitter 启动器并清理 playground 脚本

PR body 明确列出三点目标:把 DeepSeek NextN 导出辅助脚本移到 scripts/playground、删除过时的空测试排序辅助脚本、新增可复用的 tmux 启动器,让一个 yolo2 Codex babysitter 在独立 worktree 中跟盯一个 PR 的 CI。目的是为 #35015 引入的 babysit-pr-to-pass-ci skill 提供配套命令行入口,把 PR CI 看护从手工操作变成可并行、可复现的脚本流程。

建议快速浏览 scripts/playground/launch_pr_babysitters.sh,重点看两类设计:fetch 后 hash 校验 + 3 次重试(应对 PR head 漂移),以及 worktree 幂等创建/复用(worktree listshow-refmerge --ff-only 环节)。若团队有自动化 CI 值班需求,这套“skill + tmux + worktree”组合可直接复用。导出脚本移动本身无评审价值。

讨论亮点

本 PR 未产生任何公开 review 评论或 issue 讨论(comments_count、review_comments_count 均为 0),因此没有可提炼的多轮交锋。唯一可参考的是 PR body 中的验证清单:移动脚本源/目标 blob ID 一致、bash -nshellcheckast.parsepre-commit,以及手工演练 help、缺参、重复 PR、已有 session 等异常路径。这是作者在没有 reviewer 的情况下自证改动安全的主要证据。

实现拆解

  1. 目录收敛(commit bd8a78c):将 scripts/export_deepseek_nextn.py 移动为 scripts/playground/export_deepseek_nextn.py。PR body 声明移动前后 Git blob ID 完全一致,即纯重命名、零内容变化,历史可追溯。该脚本用于把 DeepSeek-V3/R1 的 NextN 层参数导出为独立 checkpoint,供 speculative decoding 使用。
  2. 删除空转脚本(commit 2251f27):删除 scripts/sort_testcases_alphabetically.py。该脚本名为“按字母序排序测试用例”,但 suites = {} 恒为空,TestFile dataclass 只有定义没有实际使用,属于早已过时的半成品,直接移除 27 行。
  3. 新增 PR babysitter 启动器(commit f8d5dd0):新增 scripts/playground/launch_pr_babysitters.sh,由多个 bash 函数组成:validate_args 校验 PR 编号格式与去重;ensure_remote 为 fork 来源 PR 注册 pr-<num> remote 并核验 URL;fetch_pr_headgh pr view 获取 state、isDraft、head hash 等信息,fetch 后做 rev-parse 与 gh 报告 hash 的对齐校验,最多重试 3 次;prepare_worktree 在每个 PR 下创建或复用 pr-<num> worktree,检查分支名、工作区干净状态、upstream 指向,最后走 merge --ff-only 并严格比对 HEADPR_HEAD_SHAprompt_for_prlaunch_window 在每个 PR 的独立 tmux window 中用交互式 bash -ic 执行 yolo2 alias,注入的 prompt 指向 SKILL.md 并限定只看 lint.ymlpr-test.yml、不 merge、不动其他 worktree。
  4. 验证配套:本 PR 没有新增自动化单测(纯 shell 工具),作者通过 bash -nshellcheckast.parsepre-commit 以及手工演练 help、缺参、重复 PR、既有 session 等路径完成验证。
文件 模块 状态 重要度
scripts/playground/launch_pr_babysitters.sh 看护器 added 5.46
scripts/playground/export_deepseek_nextn.py 导出工具 renamed 3.95
scripts/sort_testcases_alphabetically.py 脚本清理 removed 5.92

关键符号

get_nextn_layer_id update_and_save_config copy_non_safetensors_files export_nextn_layer_parameters usage die validate_args require_command normalize_github_repo worktree_for_branch ensure_remote fetch_pr_head prepare_worktree prompt_for_pr launch_window

关键源码片段

scripts/playground/export_deepseek_nextn.py rename-or-move

DeepSeek NextN 导出工具从 `scripts` 根目录迁入 `scripts/playground`,blob ID 完全一致,是纯路径变更,保持历史可追溯。

# export_deepseek_nextn.py 是纯文件移动,逻辑没有变化。
# 核心函数负责把 NextN 层(layer id 等于 config 里的 num_hidden_layers)
# 从 DeepSeek-V3/R1 的 safetensors 权重中抽取出来,导出成独立权重文件,
# 供 speculative decoding 加载 NextN 层def export_nextn_layer_parameters(input_dir, output_dir, nextn_layer_id):
    # NextN 层权重统一带 model.layers.<nextn_layer_id> 前缀
    prefix = f"model.layers.{nextn_layer_id}"
    output_path = os.path.join(output_dir, "nextn_layer_parameters.safetensors")
    params = {}
​
    # 遍历输入目录下的所有 safetensors 分片,筛选属于 NextN 层的 tensor
    for filename in os.listdir(input_dir):
        if not filename.endswith(".safetensors"):
            continue
​
        file_path = os.path.join(input_dir, filename)
        print(f"Processing: {filename}")
​
        try:
            with safe_open(file_path, framework="pt") as f:
                matching_keys = [k for k in f.keys() if k.startswith(prefix)]
​
                if not matching_keys:
                    print(f"  No parameters starting with '{prefix}' found")
                    continue
​
                for key in matching_keys:
                    # embed_tokens 与 shared head 仍由原模型持有,不加入 NextN 导出集
                    if "embed_tokens" in key or "shared_head.head" in key:
                        continue
                    # 将 model.layers.<nextn_id> 重映射为 model.layers.0,
                    # 使推理侧可以复用第一层的参数命名路径
                    new_key = key.replace(prefix, "model.layers.0")
                    params[new_key] = f.get_tensor(key)
        #(后续分片处理与落盘逻辑保持原样,此处省略)

评论区精华

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

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

风险与影响

  1. 启动器会修改主仓库的 Git 配置(注册/校验 pr-<num> remote),并在 WORKTREE_ROOT 下创建 worktree 与本地分支;这些分支不在脚本内自动清理,长期运行会留下残留。
  2. launch_window 依赖交互式 yolo2 alias 存在,若 alias 未定义,tmux window 打开后会立即失败,且缺少前置检查。
  3. worktree 逻辑假定工作区干净(status --porcelain 为空)且分支未被其他 worktree 占用,任何违规都会触发 die 退出,属于保护性失败,不会损坏数据。
  4. 删除 scripts/sort_testcases_alphabetically.py 与移动 export_deepseek_nextn.py 都存在“旧路径引用失效”的低概率风险,需留意是否有文档或 CI 脚本引用旧路径;两者均为开发者工具脚本,对运行时无影响。

对线上服务和用户零影响(纯开发者脚本目录)。对团队工作流的影响:新增 launch_pr_babysitters.sh 后,多 PR 并行 CI 看护成为可能——每个 PR 有独立 worktree 和 tmux window,babysitter 只监看 lint.ymlpr-test.yml。同时 scripts/playground 成为存放实验/维护脚本的约定位置,删除空脚本也消除了对新同学的误导。影响程度低到中,主要集中在开发工作流与文档引用层面。

Git 环境副作用 依赖交互式 alias worktree/ 分支残留 旧路径引用失效

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论