refactor: split submit_sft_via_cml.sh into submit_sft.sh + submit_cml_eval.sh
- submit_sft.sh: pure SFT submission, prints JobID and exits (no embedded watcher) - submit_cml_eval.sh: pure CML eval, reads workflow_id/version from config.yaml - jupyter_runtime write_text: rewrite to use Contents API instead of terminal websocket - SKILL.md: restrict trigger to explicit UI click only - program.md: update §5.2 docs to reflect two-script workflow Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1262,7 +1262,9 @@ python skills/label-master/scripts/validate_label_output.py \
|
||||
|
||||
H1 在 §4.0.1 Step A.5 已经过 label-master 推荐覆盖,这里**不再重跑**。H2 仿写是 §4.2 GPT 新增的样本,没经过 Step A.5,必须在此补一次语义判定:
|
||||
|
||||
1. 从 `augment_<runDic>.jsonl` 抽出 `(query, output)` 对
|
||||
🚨 **覆盖率硬规则**:`label_master_review.jsonl` 里 H2 部分的行数**必须等于** `augment_<runDic>.jsonl` 的行数(1:1 全量覆盖)。**禁止抽样、禁止"前 N 条 spot-check"、禁止"5/5 pass 推 100 OK"**——§5.0 准入门会用 `wc -l` 拦。已踩坑:R17817 跑了 5 条样本就上报"100 条 pass",被 §5.0 退回。
|
||||
|
||||
1. 逐行读 `augment_<runDic>.jsonl` **全部** `(query, output)` 对(N 条就是 N 条,不省略、不抽样)
|
||||
2. **逐条**调 `Skill(skill="label-master", args=...)`——每次只传**一条** `(query, 当前 label)`:
|
||||
- 让 label-master 按 §决策流程 / §候选召回索引 / §高频混淆边界 判定该条
|
||||
- 输出格式:`{verdict: 通过 | 不通过, 推荐标签, 排除理由, 易混淆边界}`
|
||||
@@ -1280,7 +1282,7 @@ H1 在 §4.0.1 Step A.5 已经过 label-master 推荐覆盖,这里**不再重
|
||||
|
||||
**写完复核报告才允许进 Step 5。**verdict 文件不存在 / 没跑层 2 → Step 5 拒绝启动(5.0 的准入检查会查 `label_master_review.jsonl` 存在)。
|
||||
|
||||
🚨 **注意**:label-master 是"知识 + Agent 推理"型 Skill,不是黑盒分类器。批量调用时严禁要求它返回 1/0 标量;必须输出 `verdict + 推荐标签 + 排除理由` 三件套,便于人类回查。
|
||||
🚨 **注意**:label-master 是"知识 + Agent 推理"型 Skill,不是黑盒分类器。每一次调用都必须输出 `verdict + 推荐标签 + 排除理由` 三件套,便于人类回查;严禁要求它返回 1/0 标量。(批量调用本身已在第 2 步禁掉,这里再强调一次输出 schema。)
|
||||
|
||||
### 5. SFT 训练
|
||||
|
||||
@@ -1290,7 +1292,7 @@ H1 在 §4.0.1 Step A.5 已经过 label-master 推荐覆盖,这里**不再重
|
||||
|
||||
1. `echo '{"step":"sft","status":"running",...}' >> $SESSION_OUTPUT/program-state.jsonl`
|
||||
2. 跑层 1 + 层 2 label-master 复核(§4.5)
|
||||
3. 如果复核全过:在 SAME bash turn 里调 `bash scripts/submit_sft_via_cml.sh <RUNDIC> <PREV_RUNDIC>` 并立刻挂 watcher(§5.2 的 cml custom_train + workflow chain)
|
||||
3. 如果复核全过:在 SAME bash turn 里调 `bash scripts/submit_sft.sh <SFT_RUNDIC> [PREV_RUNDIC]` 并立刻挂 watcher(§5.2)。训练 `_SUCCESS` 落盘后的下一轮 step 单独用 `bash scripts/submit_cml_eval.sh <EVAL_RUNDIC>` 起评测,**不要**把评测串到 SFT bg 任务里——跨 step bg 是禁区
|
||||
|
||||
**反模式**(被反复踩坑,永远禁止):
|
||||
|
||||
@@ -1318,7 +1320,20 @@ if [ "$TOTAL" -gt 0 ] && [ $((NOT_PASS * 100 / TOTAL)) -gt 5 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. zk_trainer 仓库已 clone
|
||||
# 1b. coverage 断言:augment 行数必须被 review 完整覆盖(杜绝抽样外推)
|
||||
# review.jsonl 同时包含 §4.0.1 Step A.5 的 mislabel candidate review + §4.5 的 augment 仿写 review
|
||||
# 所以行数下界 = augment.jsonl 行数(mislabel 部分多出来的那批不影响下界)
|
||||
AUG="$AUTORESEARCH_CHAT_ROOT/ai-planning/data/train_set/zk_intent/augment_${RUNDIC}.jsonl"
|
||||
if [ -f "$AUG" ]; then
|
||||
AUG_N=$(wc -l < "$AUG")
|
||||
REV_N=$(wc -l < "$REVIEW")
|
||||
if [ "$REV_N" -lt "$AUG_N" ]; then
|
||||
echo "label-master review 行数 $REV_N < augment 条数 $AUG_N — §4.5 必须逐条覆盖,禁止抽样外推(如 5/5 pass 推 100 OK),回 §4.5 把 augment.jsonl 的每一条都 append 一行 review entry"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. zk_trainer 仓库已 clone(URL 写死,不要换命名空间,clone 失败先看 ssh key)
|
||||
cd "$AUTORESEARCH_CHAT_ROOT"
|
||||
[ -d zk_trainer ] || git clone git@git.n.xiaomi.com:wangsenhao/zk_trainer.git
|
||||
```
|
||||
@@ -1373,11 +1388,11 @@ R23-R28 期间累积了 `sft_output_r23` ~ `sft_output_r28`,多次需要回退
|
||||
|
||||
**自动评测 watcher**:
|
||||
|
||||
R29 起 SFT 走 cml custom_train submit(见 5.2),watcher 直接复用 `submit_sft_via_cml.sh` 内嵌的轮询逻辑——`cml custom_train describe` 检测 succeed → 验产出 → 自动起 `cml workflow run` 评测。本地无 PID 可监控。
|
||||
R29 起 SFT 走 cml custom_train submit(见 5.2),训练用 `submit_sft.sh` 提交、评测用 `submit_cml_eval.sh` 单独起。这两步**不再串接**——agent 用 watcher 监听 `sft_output/_SUCCESS`,watcher fire 后下一轮主动调 `submit_cml_eval.sh`。本地无 PID 可监控,全靠 watcher 串两个 step。
|
||||
|
||||
##### Claude-side 双 watcher 强制要求(R17775 经验沉淀)
|
||||
|
||||
`submit_sft_via_cml.sh` 内嵌的 bash watcher **只负责"训练完 → 起评测"**,它不会通知 Claude。Claude 如果只挂一个"等评测产物"的 watcher,训练完成事件会被漏报(R17775 训练 19:57 完成,Claude 40+min 不知道,直到用户问)。
|
||||
旧版 `submit_sft_via_cml.sh` 在脚本内嵌 watcher 自动起评测——已废弃(违反"bg 任务作用域 = 单 step"原则)。现在 `submit_sft.sh` 只负责提 SFT、立刻退出;起评测必须由 agent 在 SFT watcher fire 后**新一轮**手动调 `submit_cml_eval.sh`。Claude 如果只挂一个"等评测产物"的 watcher,训练完成事件会被漏报(R17775 训练 19:57 完成,Claude 40+min 不知道,直到用户问)。
|
||||
|
||||
**Claude 调用 submit 脚本后,必须立刻用 `Bash run_in_background` 起两个 watcher task**(一个都不能少):
|
||||
|
||||
@@ -1476,26 +1491,33 @@ R23-R28 期间用本地 `nohup python3 prepare_and_train_sft.py train ...` 启
|
||||
|
||||
##### 一键提交脚本
|
||||
|
||||
⚠️ **runDic 不许手敲**:先 `eval` 一下 `resolve_run_ids.sh` 拿到这一轮的 `$SFT_RUNDIC` / `$EVAL_RUNDIC`,再原样传给 `submit_sft_via_cml.sh`。R2 踩过的坑就是手算 runDic 时把 SFT yaml 的命名也跟着 +1,导致 `data_clean_<EVAL_RUNDIC>/` 跳过了 R1 命名空间。
|
||||
⚠️ **runDic 不许手敲**:先 `eval` 一下 `resolve_run_ids.sh` 拿到这一轮的 `$SFT_RUNDIC` / `$EVAL_RUNDIC`,再原样传给两个独立脚本。R2 踩过的坑就是手算 runDic 时把 SFT yaml 的命名也跟着 +1,导致 `data_clean_<EVAL_RUNDIC>/` 跳过了 R1 命名空间。
|
||||
|
||||
提交流程拆成两步(**两个独立脚本,两个 step**):
|
||||
|
||||
```bash
|
||||
cd "$AUTORESEARCH_CHAT_ROOT"
|
||||
eval "$(./scripts/resolve_run_ids.sh)" # 解析出 $SFT_RUNDIC / $EVAL_RUNDIC
|
||||
./scripts/submit_sft_via_cml.sh "$SFT_RUNDIC" "$EVAL_RUNDIC"
|
||||
|
||||
# 等价示例(仅供阅读,**实际调用一律走 eval 上面那行**):
|
||||
# R29 上一轮已落盘的 workflow 是 17755,因此 SFT_RUNDIC=17755, EVAL_RUNDIC=17756
|
||||
# ./scripts/submit_sft_via_cml.sh 17755 17756
|
||||
# Step 5:提交 SFT 训练(不附带任何评测逻辑)
|
||||
./scripts/submit_sft.sh "$SFT_RUNDIC"
|
||||
|
||||
# 训练 _SUCCESS 落盘后的下一轮 step 才起评测:
|
||||
./scripts/submit_cml_eval.sh "$EVAL_RUNDIC"
|
||||
```
|
||||
|
||||
`submit_sft_via_cml.sh` 接受三个位置参数:`<SFT_RUNDIC> <EVAL_RUNDIC> [PREV_RUNDIC]`。`PREV_RUNDIC` 默认 `SFT_RUNDIC-1`,仅用作上一轮 `sft_output_r<PREV_RUNDIC>` 改名。脚本内部断言 `EVAL_RUNDIC > SFT_RUNDIC`,否则直接报错退出。
|
||||
`submit_sft.sh <SFT_RUNDIC> [PREV_RUNDIC]`:
|
||||
|
||||
脚本做的事:
|
||||
1. 渲染 `scripts/sft_train_job.yaml.tpl` 为本轮 yaml(替换 `{RUNDIC}` → `$SFT_RUNDIC`、`{PREV_RUNDIC}` → `$PREV_RUNDIC`)
|
||||
2. `cml custom_train submit --filename <yaml>` 提交任务,拿 `JobID`
|
||||
3. 后台 watcher 进程:每 60s `cml custom_train describe` 轮询任务状态
|
||||
4. 任务 succeed → 验证 `sft_output/_SUCCESS` 存在 → 自动起 cml workflow run,**评测的 `runDic=$EVAL_RUNDIC`**(这是整条流水线唯一一次 +1)
|
||||
5. 任务 failed/killed → 写日志报警
|
||||
2. `cml custom_train submit --filename <yaml>` 提交任务,打印 `JobID`,立刻退出
|
||||
3. **不再内嵌 watcher**——agent 自己用 watcher 监听 `$AUTORESEARCH_CHAT_ROOT/sft_output/_SUCCESS`,watcher fire 后下一轮再调 `submit_cml_eval.sh`
|
||||
|
||||
`submit_cml_eval.sh <EVAL_RUNDIC> [MODEL_NEW] [MODEL_OLD]`:
|
||||
|
||||
1. 从 `skills/model-iteration/assets/config.yaml` 读 `cml_eval.workflow_id` 和 `cml_eval.version`(版本不再硬编码,调版本就改 config.yaml)
|
||||
2. `MODEL_NEW` 默认 `$AUTORESEARCH_ROOT/sft_output`、`MODEL_OLD` 默认旧基线(也可以通过 env / 参数覆盖)
|
||||
3. `cml workflow run --workflow_id ... --version ... --global_inputs runDic=$EVAL_RUNDIC ...` 起评测,**评测 runDic=$EVAL_RUNDIC**(整条流水线唯一一次 +1)
|
||||
4. 立刻退出,UI 卡片状态走 program-state.jsonl 的 watcher 通道
|
||||
|
||||
##### yaml 模板要点(`scripts/sft_train_job.yaml.tpl`)
|
||||
|
||||
@@ -1521,8 +1543,7 @@ cml custom_train logs <JOB_ID> --follow
|
||||
# 停任务
|
||||
cml custom_train kill <JOB_ID>
|
||||
|
||||
# 查 watcher(自动评测)日志
|
||||
tail -f /tmp/r<RUNDIC>_logs/cml_watcher.log
|
||||
# SFT 不再内嵌 watcher,自动评测改成 agent watcher → 单独调 submit_cml_eval.sh
|
||||
```
|
||||
|
||||
##### 与 nohup 路径的对比
|
||||
@@ -1534,7 +1555,7 @@ tail -f /tmp/r<RUNDIC>_logs/cml_watcher.log
|
||||
| 节点故障重试 | 无 | 自动 2 次 |
|
||||
| 失败告警 | 无 | 飞书 P2 |
|
||||
| 任务历史 | 仅本地 log | cml 平台可查 |
|
||||
| 启动复杂度 | 单行 nohup | 单行 submit_sft_via_cml.sh |
|
||||
| 启动复杂度 | 单行 nohup | submit_sft.sh + watcher + submit_cml_eval.sh |
|
||||
|
||||
##### 何时仍用 nohup 本地训
|
||||
|
||||
|
||||
Reference in New Issue
Block a user