add lightweight model training skill
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
---
|
||||
name: model-training-lite
|
||||
name_zh: 轻量模型训练
|
||||
description: 从 Codex、Claude Code 或其他 Agent 通过用户提供的 Jupyter 环境和数据版本发起一次模型训练,聚焦“准备远端工作区、同步数据、提交 CML SFT、返回 JobID”,不包含完整自主迭代闭环。
|
||||
when_to_use: 当用户已经准备好训练数据,想让 Agent 使用 Jupyter/CloudML 发起一次模型训练、复用当前仓库的训练脚本和模板、或询问如何把模型训练流程沉淀成轻量 skill 时使用。
|
||||
aliases: jupyter-model-training, sft-submit, 轻量训练, 模型训练提交
|
||||
examples:
|
||||
- 用这个 Jupyter 和 ai-planning 当前分支重新发起训练
|
||||
- 数据已经提交到 autoresearch-v1,帮我提交一次 SFT
|
||||
- 帮我把 Codex 通过 Jupyter 发起模型训练的流程跑起来
|
||||
- 检查一下训练工作区和 CML 环境,然后提交训练任务
|
||||
allowed_tools: read_file, write_file, grep_search, glob_search, ask_user_question, python_exec, bash
|
||||
---
|
||||
|
||||
# 轻量模型训练
|
||||
|
||||
本 skill 只负责一次训练提交:
|
||||
|
||||
```text
|
||||
Jupyter 授权环境
|
||||
-> 创建/复用远端 kernel
|
||||
-> 准备隔离训练工作区
|
||||
-> 同步训练脚本与数据版本
|
||||
-> 检查 CML / 数据 / zk_trainer
|
||||
-> 提交 SFT
|
||||
-> 返回 JobID、产物路径、下一步评测入口
|
||||
```
|
||||
|
||||
不要把它扩展成 `model-iteration` 那种完整“baseline -> 分析 -> 增强 -> 训练 -> 评测 -> 再分析”的自主循环。
|
||||
|
||||
## 必要输入
|
||||
|
||||
开始前必须拿到:
|
||||
|
||||
- **Jupyter 地址和授权方式**:URL;密码、token、cookie/session,或说明当前环境已有可用登录态。
|
||||
- **数据版本**:git repo + branch + commit,或远端工作区中已存在的数据路径。
|
||||
- **训练配方**:如果是当前 ZK 中控 SFT,默认复用 `skills/model-iteration/scripts/`;如果不是,必须让用户提供训练脚本、模板或命令。
|
||||
|
||||
通常用户只需要显式给 `Jupyter 地址` 和 `数据版本`。当前项目的默认训练配方可从本仓库继承,不必每次追问。
|
||||
|
||||
可选但推荐确认:
|
||||
|
||||
- `owner`:远端输出目录使用的用户前缀,例如 `wuyang6`。
|
||||
- `run_name`:本次训练工作区名,例如 `manual_YYYYMMDD_zk_intent_xxx`。
|
||||
- 是否训练后立刻发起评测。默认只提交训练,不自动串接评测。
|
||||
|
||||
## 默认 ZK SFT 配方
|
||||
|
||||
当前 ZK 中控 SFT 复用这些文件:
|
||||
|
||||
```text
|
||||
skills/model-iteration/scripts/
|
||||
prepare_and_train_sft.py
|
||||
resolve_run_ids.sh
|
||||
submit_sft.sh
|
||||
submit_cml_eval.sh
|
||||
sft_train_job.yaml.tpl
|
||||
skills/model-iteration/assets/config.yaml
|
||||
```
|
||||
|
||||
默认基模和训练模板见 [references/zk_sft_defaults.md](references/zk_sft_defaults.md)。只有用户明确要求或训练任务不是 ZK 中控 SFT 时,才修改这些默认值。
|
||||
|
||||
## 推荐流程
|
||||
|
||||
### 1. 先生成提交计划
|
||||
|
||||
优先执行 portable script:
|
||||
|
||||
```bash
|
||||
python skills/model-training-lite/scripts/render_sft_submission_plan.py --input input.json
|
||||
```
|
||||
|
||||
输入示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"jupyter_url": "https://.../lab?",
|
||||
"data_repo": "git@git.n.xiaomi.com:ai-service/ai-planning.git",
|
||||
"data_branch": "autoresearch-v1",
|
||||
"data_commit": "b92be709",
|
||||
"owner": "wuyang6",
|
||||
"run_name": "manual_20260528_zk_intent_clean_train",
|
||||
"recipe": "zk_sft"
|
||||
}
|
||||
```
|
||||
|
||||
脚本只生成计划和命令草案,不连接 Jupyter,不提交训练。
|
||||
|
||||
### 2. 连接 Jupyter
|
||||
|
||||
如果已经有 session/cookie,可直接调用 Jupyter REST API:
|
||||
|
||||
- `GET /api/kernels` 检查登录态。
|
||||
- `POST /api/kernels` 创建 kernel;POST 需要 `X-XSRFToken`。
|
||||
- websocket 连接 `/api/kernels/<kernel_id>/channels` 执行 Python/Bash。
|
||||
|
||||
如果没有登录态,用用户提供的密码/token 登录;不要把密码写入仓库、日志或最终报告。
|
||||
|
||||
### 3. 准备远端工作区
|
||||
|
||||
远端路径默认:
|
||||
|
||||
```text
|
||||
/mnt/wangsenhao/autoresearch-zk-users/<owner>/<run_name>
|
||||
```
|
||||
|
||||
工作区内必须有:
|
||||
|
||||
```text
|
||||
scripts/
|
||||
assets/
|
||||
results/
|
||||
output/
|
||||
ai-planning/
|
||||
zk_trainer/
|
||||
```
|
||||
|
||||
把默认 ZK SFT 配方里的 `scripts/` 和 `config.yaml` 同步到远端工作区。再 clone 或更新数据仓库,并 checkout 到用户指定 commit。
|
||||
|
||||
### 4. 提交前检查
|
||||
|
||||
提交训练前必须输出:
|
||||
|
||||
- `ai-planning` 当前 commit。
|
||||
- 训练 JSONL 文件数量和总行数。
|
||||
- 关键增量文件行数。
|
||||
- 目标评测集行数,如果用户指定了评测集。
|
||||
- `source ~/.cloudml-cli/.profile` 后 `cml config show` 可用。
|
||||
- `zk_trainer` 已存在或 clone 成功。
|
||||
|
||||
如果 `cml` 不在 PATH,先检查 `~/.cloudml-cli/.profile`。不要因为 `which cml` 为空就直接判定不可用。
|
||||
|
||||
### 5. 提交 SFT
|
||||
|
||||
ZK SFT 提交命令:
|
||||
|
||||
```bash
|
||||
source ~/.cloudml-cli/.profile
|
||||
export AUTORESEARCH_CHAT_ROOT=<remote_workspace>
|
||||
export AUTORESEARCH_ROOT="$AUTORESEARCH_CHAT_ROOT"
|
||||
cd "$AUTORESEARCH_CHAT_ROOT"
|
||||
eval "$(./scripts/resolve_run_ids.sh)"
|
||||
./scripts/submit_sft.sh "$SFT_RUNDIC"
|
||||
```
|
||||
|
||||
注意:`resolve_run_ids.sh` 只输出 `SFT_RUNDIC` 和 `EVAL_RUNDIC`,不要读取不存在的 `RUNDIC`。
|
||||
|
||||
### 6. 成功判定
|
||||
|
||||
提交成功后必须回报:
|
||||
|
||||
- CloudML JobID。
|
||||
- CloudML 链接。
|
||||
- `SFT_RUNDIC` 和 `EVAL_RUNDIC`。
|
||||
- 远端训练工作区。
|
||||
- 成功标记路径:`<remote_workspace>/sft_output/_SUCCESS`。
|
||||
- 查看状态命令:`cml custom_train describe <JOB_ID>`。
|
||||
|
||||
再查一次 `cml custom_train describe <JOB_ID>`,确认状态不是提交后立即失败。
|
||||
|
||||
## 边界
|
||||
|
||||
- 不自动修改训练数据。
|
||||
- 不自动做错误归因。
|
||||
- 不自动生成增强样本。
|
||||
- 不默认串接评测;训练成功后是否评测由用户或后续明确指令决定。
|
||||
- 不把 Jupyter 密码、CloudML key、cookie 写进产物或最终回复。
|
||||
@@ -0,0 +1,80 @@
|
||||
# ZK SFT 默认训练配方
|
||||
|
||||
当前轻量训练默认复用 `model-iteration` 的 SFT 提交流程,但只使用其中的训练提交能力。
|
||||
|
||||
## 训练脚本
|
||||
|
||||
```text
|
||||
skills/model-iteration/scripts/prepare_and_train_sft.py
|
||||
skills/model-iteration/scripts/resolve_run_ids.sh
|
||||
skills/model-iteration/scripts/submit_sft.sh
|
||||
skills/model-iteration/scripts/submit_cml_eval.sh
|
||||
skills/model-iteration/scripts/sft_train_job.yaml.tpl
|
||||
skills/model-iteration/assets/config.yaml
|
||||
```
|
||||
|
||||
## 默认基模
|
||||
|
||||
```text
|
||||
/mnt/wangsenhao/verl_zk/Qwen3-4B-Instruct-2507
|
||||
```
|
||||
|
||||
`sft_train_job.yaml.tpl` 内固定从该基模启动 SFT。不要从上一轮 `sft_output` 续训,除非用户明确改变训练策略。
|
||||
|
||||
## 默认数据仓库
|
||||
|
||||
```text
|
||||
git@git.n.xiaomi.com:ai-service/ai-planning.git
|
||||
branch: autoresearch-v1
|
||||
```
|
||||
|
||||
训练脚本会扫描:
|
||||
|
||||
```text
|
||||
ai-planning/data/train_set/*/*.jsonl
|
||||
```
|
||||
|
||||
其中 `*_valid.jsonl` 会作为验证数据,其余训练 jsonl 进入训练集。
|
||||
|
||||
## 默认远端工作区
|
||||
|
||||
```text
|
||||
/mnt/wangsenhao/autoresearch-zk-users/<owner>/<run_name>
|
||||
```
|
||||
|
||||
训练产物:
|
||||
|
||||
```text
|
||||
<workspace>/sft_output/
|
||||
<workspace>/sft_output/_SUCCESS
|
||||
```
|
||||
|
||||
## CML 环境
|
||||
|
||||
CloudML CLI 通常位于:
|
||||
|
||||
```bash
|
||||
source ~/.cloudml-cli/.profile
|
||||
```
|
||||
|
||||
该 profile 会设置:
|
||||
|
||||
```bash
|
||||
PATH=$HOME/.cloudml-cli/bin:$PATH
|
||||
CLOUDML_CONFIG=$HOME/.config/cloudml/config.yaml
|
||||
```
|
||||
|
||||
检查命令:
|
||||
|
||||
```bash
|
||||
cml config show
|
||||
cml custom_train describe <JOB_ID>
|
||||
```
|
||||
|
||||
## 常见坑
|
||||
|
||||
- Jupyter POST 请求缺少 `_xsrf`:从 cookie 取 `_xsrf`,请求头带 `X-XSRFToken`。
|
||||
- websocket 证书校验失败:内网证书场景可在客户端禁用证书校验,但不要降低服务端安全配置。
|
||||
- `which cml` 为空:先 `source ~/.cloudml-cli/.profile`。
|
||||
- `resolve_run_ids.sh` 没有 `RUNDIC`:只使用 `SFT_RUNDIC` / `EVAL_RUNDIC`。
|
||||
- git clone 看似卡住:先检查目标目录是否已经完整、是否存在残留进程,不要重复提交训练。
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_DATA_REPO = "git@git.n.xiaomi.com:ai-service/ai-planning.git"
|
||||
DEFAULT_BRANCH = "autoresearch-v1"
|
||||
DEFAULT_RECIPE = "zk_sft"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Render a lightweight SFT submission plan.")
|
||||
parser.add_argument("--input", "-i", help="JSON input file. Reads stdin when omitted.")
|
||||
args = parser.parse_args()
|
||||
text = open(args.input, encoding="utf-8").read() if args.input else sys.stdin.read()
|
||||
payload = json.loads(text or "{}")
|
||||
result = render_plan(payload)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
def render_plan(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
missing: list[str] = []
|
||||
jupyter_url = clean(payload.get("jupyter_url"))
|
||||
data_commit = clean(payload.get("data_commit") or payload.get("commit"))
|
||||
data_path = clean(payload.get("data_path"))
|
||||
if not jupyter_url:
|
||||
missing.append("jupyter_url")
|
||||
if not data_commit and not data_path:
|
||||
missing.append("data_commit or data_path")
|
||||
|
||||
owner = clean(payload.get("owner")) or "wuyang6"
|
||||
run_name = clean(payload.get("run_name")) or default_run_name(data_commit or data_path or "manual")
|
||||
workspace = clean(payload.get("workspace")) or str(
|
||||
PurePosixPath("/mnt/wangsenhao/autoresearch-zk-users") / owner / run_name
|
||||
)
|
||||
data_repo = clean(payload.get("data_repo")) or DEFAULT_DATA_REPO
|
||||
data_branch = clean(payload.get("data_branch")) or DEFAULT_BRANCH
|
||||
recipe = clean(payload.get("recipe")) or DEFAULT_RECIPE
|
||||
|
||||
commands = {
|
||||
"prepare_workspace": [
|
||||
f"mkdir -p {workspace}/{{scripts,assets,results,output}}",
|
||||
f"cd {workspace}",
|
||||
],
|
||||
"sync_data": [
|
||||
f"[ -d ai-planning/.git ] || git clone -b {data_branch} {data_repo} ai-planning",
|
||||
f"git -C ai-planning fetch origin {data_branch}",
|
||||
f"git -C ai-planning checkout {data_branch}",
|
||||
f"git -C ai-planning reset --hard {data_commit}" if data_commit else f"# use existing data_path: {data_path}",
|
||||
],
|
||||
"submit_sft": [
|
||||
"source ~/.cloudml-cli/.profile",
|
||||
f"export AUTORESEARCH_CHAT_ROOT={workspace}",
|
||||
'export AUTORESEARCH_ROOT="$AUTORESEARCH_CHAT_ROOT"',
|
||||
f"cd {workspace}",
|
||||
'eval "$(./scripts/resolve_run_ids.sh)"',
|
||||
'./scripts/submit_sft.sh "$SFT_RUNDIC"',
|
||||
],
|
||||
"check_status": [
|
||||
"cml custom_train describe <JOB_ID>",
|
||||
f"test -f {workspace}/sft_output/_SUCCESS",
|
||||
],
|
||||
}
|
||||
return {
|
||||
"ok": not missing,
|
||||
"missing": missing,
|
||||
"plan": {
|
||||
"jupyter_url": jupyter_url,
|
||||
"recipe": recipe,
|
||||
"workspace": workspace,
|
||||
"data_repo": data_repo,
|
||||
"data_branch": data_branch,
|
||||
"data_commit": data_commit,
|
||||
"data_path": data_path,
|
||||
"success_marker": f"{workspace}/sft_output/_SUCCESS",
|
||||
},
|
||||
"commands": commands,
|
||||
"required_outputs": [
|
||||
"CloudML JobID",
|
||||
"CloudML task URL",
|
||||
"SFT_RUNDIC",
|
||||
"EVAL_RUNDIC",
|
||||
"workspace",
|
||||
"success_marker",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def clean(value: Any) -> str:
|
||||
return str(value or "").strip()
|
||||
|
||||
|
||||
def default_run_name(seed: str) -> str:
|
||||
slug = re.sub(r"[^a-zA-Z0-9_-]+", "_", seed).strip("_")[:32] or "manual"
|
||||
return f"manual_sft_{slug}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user