# 基于 slime 的训练链路

当前实现把 proposal 中的快、慢两层时间尺度分开：slime 负责同一 checkpoint 内的 GRPO rollout/update；Trace2Skill failure analysis 与 randomized validation 在一个有限训练 round 结束后运行。两条链路共享冻结、带哈希的 curriculum / candidate-pool / assignment 状态，通过 slime 的扩展点接入。

本次已发布实验为四轮、80 个 rollout step，最终 checkpoint 为 Iter80；结果与局限见 [实验报告](experiment_report.md)。以下操作面向新的训练运行，历史运行目录不应覆盖。

## 1. 数据与初始 curriculum

训练只读取项目冻结的 persona `train` task IDs：

```powershell
python scripts\run_shopsimrl.py training prepare-data `
  ShopSimulator\shop_env\configs\persona_splits.v1.json `
  train data\shopsim_train.jsonl
```

本次运行使用已发布的 10-chunk S₀：`artifacts/cold-start/selected_skillbank.json`，与 `artifacts/cold-start/curriculum.json` 配套。默认 q=0.20、rho 范围 [0.20, 0.90]；首轮离线检查可以直接使用这对冻结输入。S₀ 来自历史冻结上下文恢复，未使用测试 reward 重新选择，也没有伪装为新完成的 Gate A，见 [来源说明](provenance.md)。

需要为新实验改变 q/rho 时，显式冻结新的 curriculum 并同步配置路径：

```powershell
python scripts/run_shopsimrl.py training build-curriculum `
  artifacts/cold-start/selected_skillbank.json `
  runs/new-experiment/curriculum.json `
  --round-id round-000 --skill-free-probability 0.20 `
  --rho-min 0.20 --rho-max 0.90
```

默认 rho 映射为 clipped linear mapping；不传 `--contribution-scale` 时，本轮最大正贡献映射到 `rho_max`。本次 q 固定不变，没有运行自动 q controller。Gate 独占 chunk admission/retirement 权限，curriculum builder 不会重新接纳非正贡献内容。

每个 curriculum JSON 同时嵌入 active chunks、contribution、rho、q、来源 SkillBank hash 与映射参数。rollout worker 会校验 `state_id`，文件内容被修改但 hash 未更新时直接失败。

`training prepare-data` 生成 3726 个冻结 train task。`data/` 和 `runs/` 被 Git 忽略，换机器时需要重建任务数据，并配置模型权重与服务；发布的初始 SkillBank/curriculum 已随仓库提供。

## 2. slime rollout 与 GRPO 语义

训练入口是：

```bash
export HF_CHECKPOINT=/path/to/Qwen3.5-4B
export TRAIN_LOAD=/path/to/qwen35-4b-actor-torch-dist
export TRAIN_SAVE=/path/to/output
bash scripts/run_shopsimrl_slime.sh
```

训练 TP 由 `TP_SIZE` 控制（默认 2），推理引擎 TP 由 `ROLLOUT_TP_SIZE` 控制（默认 1）。
`NUM_GPUS` 必须同时被两者整除；`TP_SIZE>1` 时训练打开 sequence parallel。
当前默认 `NUM_GPUS=4 TP_SIZE=2 ROLLOUT_TP_SIZE=1`：训练切开词表 logits，rollout / val 仍是 4 个独立引擎。
训练打包 `MAX_TOKENS_PER_GPU=16384`，单条 Sample 由 `ROLLOUT_MAX_CONTEXT_LEN=24576` 封顶；二者不要当成同一件事，见 [oom_qwen35_4b.md](archive/oom_qwen35_4b.md)。
`ROLLOUT_BATCH_SIZE` 默认 16，即每步留下 `16×8=128` 条 episode。`run_full_train.sh` 另把 `OVER_SAMPLING_BATCH_SIZE` 设为 32、`GLOBAL_BATCH_SIZE` 设为 64：先多采再过滤，留下的 128 条切成两次优化器更新。波次规则见 [dynamic_sampling_filter.md](dynamic_sampling_filter.md)。
1 卡用 `NUM_GPUS=1 TP_SIZE=1`。
少于 8 卡时不能只修改 Ray 的可见 GPU 数而保留 slime 默认的 `num_gpus_per_node=8`。

运行前按 ShopSimulator 文档启动环境服务，并按机器拓扑审核 GPU、batch、context 和 checkpoint 路径；脚本会在启动 Ray 前检查 checkpoint、task data、slime checkout 和 custom config，并执行以下无 GPU、无 API 的输入校验。自定义参数位于 `configs/slime_shopsimrl.yaml`；其中 `shopsim_skillbank_path` 与 `shopsim_curriculum_path` 必须对应同一版本：

```powershell
python scripts\run_shopsimrl.py training check configs\slime_shopsimrl.yaml
```

检查覆盖 curriculum 内容 hash、source bank、chunk 文本/系数、完整且不重复的 frozen train IDs、persona 和并发配置。`ready` 只表示离线输入通过，不表示 GPU、SGLang 或环境服务已验证。脚本使用 slime 原生扩展点：

- `shopsimrl.slime_runtime.generate`：多轮 function-tool agent rollout；
- `shopsimrl.slime_runtime.normalize_grpo_by_prompt_and_rollout`：按 prompt group、unique rollout 做 GRPO centering；
- `Sample.tokens` / `rollout_log_probs` 来自 SGLang 实际采样，不从文本重新分词恢复；
- 环境 observation、tool result 和 prompt token 的 loss mask 为 0，模型生成 token 的 loss mask 为 1；
- 动态工具 schema 造成 token-contiguous trajectory 分段时，siblings 共享 `rollout_id`。reward normalization 先按 unique rollout 计算，再向 siblings 广播，不会让长轨迹在组均值中被重复计数。
- 适配器明确使用 `fork_threshold_tokens=0`：slime 默认会在短 assistant reasoning 回显或 token drift 时丢弃先前 response 的训练信号；本实现保留每一轮实际生成的 token/logprob，必要时独立分段。
- 任一 rollout 因环境、协议运行时或空 trajectory 而不可评分时，动态采样过滤器丢弃整个 prompt group 并补采；技术失败不会作为 reward=0 样本污染 GRPO。连续失败达到配置阈值时直接终止，避免环境服务宕机后无限补采。zero-std（全对/全错）带 `keep_when_insufficient`，波次与凑批规则见 [dynamic_sampling_filter.md](dynamic_sampling_filter.md)。
- SGLang 的 `abort` 及 rollout 停止状态属于技术中止；停止后不再启动新的 turn/retry 请求。达到生成长度上限仍是可评分的 policy failure。当前页面未提供的工具与 evaluator 一样返回 `unavailable_tool` feedback，不执行环境动作。

脚本给 slime dataset loader 开启 `--apply-chat-template`，兼容 Qwen3.5 checkpoint 附带 processor 时必须使用 message-list 的输入要求；实际购物 prompt 仍由 runtime 根据 `metadata.task_id` 从环境生成。`run_shopsimrl_slime.sh` 里 `GLOBAL_BATCH_SIZE` 默认等于 `ROLLOUT_BATCH_SIZE * N_SAMPLES_PER_PROMPT`；`run_full_train.sh` 覆盖为 64，必须整除一轮留下的 episode 数，且是 `N_SAMPLES_PER_PROMPT` 的倍数，避免训练侧丢弃尾部 rollouts 或把同一个 prompt group 拆进两次更新。累计 `NUM_ROLLOUT` 变长时必须 `--override-opt-param-scheduler`，否则 Megatron 会因 checkpoint 里的 `lr_decay_steps` 与新 job 不一致而拒绝 load（constant lr 下这个总步数本来就不影响学习率）。

当前入口固定 `top_p=1.0, top_k=-1`。这是 runtime contract，而不是随意的解码偏好：slime 的普通 rollout 能在 `top_p<1` 时携带截断分布 replay 元数据，但当前多轮 `TrajectoryManager` 只保存 exact token/logprob，不能把该 ragged 元数据跨 turn/fan-out 无损传到训练侧。验证配置使用同一采样分布；若未来扩展 manager 的 replay contract，再联合修改训练和 validation，不能只改脚本参数。

`shopsim_episode_concurrency`（正整数，默认 16）限制每个 rollout worker 中同时执行的完整 episode，普通 rollout 与 full-skill retry 共用名额。超额任务在异步 semaphore 上等待，不进入阻塞 `reset` 的线程池；名额覆盖 reset、交互和 close，在 group 协调前释放。该值不能超过分配给此 worker 的环境槽位数；多个 worker 或评测进程共享服务时，需要分别预算，不能各自按服务总槽位数配置。

### Group-consistent 双层 mask

同一个 prompt 的 `n_samples_per_prompt` 个样本共享 slime `group_index`。runtime 先用 `(curriculum state_id, group_index)` 对 q 做一次确定性采样：

- 命中 skill-free：整个 group 不注入任何 chunk；
- 否则进入 assisted，再按每个 chunk 的 rho 采样一次联合 mask。

group 内每个 rollout 都校验 `state_id` 和完整 `skill_ids` 相同。worker 调度、并发完成顺序和断点恢复不会改变 treatment。`assisted_empty` 被显式记录，表示进入 assisted 分支后所有 Bernoulli chunk 均未命中；它不会被伪装成另一次 q 命中。

## 3. all-wrong full-skill retry 与 error analysis

每个训练 rollout 都写入：

```text
runs/slime-training/<round-id>/group-XXXXXXXXX/
├── rollout-<sample-index>.json
├── triage.json
└── full-skill-retry.json      # 只在 all-wrong 时存在
```

只有当 group 中所有 rollout 都是正常、可评分终局且 `r_success=0` 时，runtime 才使用完整 current skill 额外生成一条诊断 trajectory。该 retry：

- 不返回给 slime trainer；
- 不参与原 group 的 GRPO advantage；
- 成功时标为 `model_internalization_deficit`；
- 仍失败时只标为 `full_skill_failure_pending_analysis`，不会自动宣判 skill deficit；
- 原 group 含环境/runtime 技术错误时不触发 skill 分析。

round 结束后复制并修改在线分析示例配置。外循环在 slime 训练期间启动 `training watch-analyze`，对着同一台 ShopSimulator 并发调用百炼 Failure Analyst，把 card 追加到 `failure_cards.jsonl`；训练 round 结束后再跑 `training analyze`，补上剩余 retry 并由 compiler 一次汇总：

```powershell
Copy-Item configs\archive\trace2skill_online_analysis.example.yaml `
  configs\trace2skill_online_analysis.yaml
python scripts\run_shopsimrl.py training watch-analyze `
  configs\trace2skill_online_analysis.yaml
# slime 结束后：
python scripts\run_shopsimrl.py training analyze `
  configs\trace2skill_online_analysis.yaml
```

Analyst 只消费失败的 full-skill retry trajectory。ADD 必须有真实成功 counterfactual trial；REWRITE 的 target 必须是 current active chunk；gold firewall 与 cold start 相同；Analyst 仍可返回 `NO_PROPOSAL`。Compiler 在 round 结束时对所有 eligible cards 做一次 many-to-one 合并，近义机制去重后每轮最多输出 **6 条 ADD/REWRITE candidates**（`max_candidates` 默认值及当前配置均为 6）。单条 trajectory card 不会直接成为 validation factor。第二轮起可在配置中指定上一轮 `proposal_ledger.jsonl`；compiler 只把它作为审计/去重状态，不注入 policy prompt，gate 输出会累计历史并更新本轮候选结果。watch-analyze 与训练共享环境服务，并发沿用 Trace2Skill 配置（当前为 2），不要把 analyst 塞进 slime 进程。

主要产物：

- `failure_cards.jsonl`：privileged audit 与 deployable abstraction 分离；
- `candidate_pool.json`：current chunks + validation-ready ADD/REWRITE；
- `proposal_ledger.jsonl`：等待 validation 的候选；
- `analysis_summary.json`：覆盖和候选统计。

## 4. 当前 checkpoint 的 randomized validation

在线 gate 使用固定 `val` 400 tasks，每个 task 一条 bare 加一条 masked rollout，统一拟合 \(R_i-B_i=C_i^\top\beta+\epsilon_i\)。先将当前 checkpoint 以 OpenAI-compatible endpoint 暴露；训练循环里用 4 卡 `tp=1 dp=4` 起 sglang，`configs/qwen35_4b_val_slime.yaml` 仍连 `http://127.0.0.1:30000/v1`，并发 32。每轮更新 `experiment.name` 与 `model.checkpoint_id` 为实际冻结权重的唯一标识，并保持服务在 bare/masked/补测期间不换权重。

先用原始 evaluator 取得 bare validation score；已有相同 checkpoint、同协议的完整 run 时直接复用这批 traces，不重复调用模型。`online_gate.bare_run_dir` 指向该 run，`experiment_config` 引用同一份 bare YAML：

```powershell
python scripts\run_shopsimrl.py run configs\qwen35_4b_val_slime.yaml
# 已有匹配的完整 bare run 时跳过上一条，并设置 bare_run_dir。
Copy-Item configs\archive\trace2skill_online_gate.example.yaml `
  configs\trace2skill_online_gate.yaml
python scripts\run_shopsimrl.py training online-gate-plan `
  configs\trace2skill_online_gate.yaml
python scripts\run_shopsimrl.py training online-gate `
  configs\trace2skill_online_gate.yaml
```

普通 current/ADD slot 使用 `absent/present` 两个等概率状态。存在 rewrite family 时，一个逻辑 slot 使用 `absent/old/new1/...` 外生互斥状态；old 和 new 不会同时出现在 prompt。所有版本 dummy columns 与其他 slots 一起进入 **paired-delta 无截距 main-effect OLS**，因变量是同任务的 masked reward 减 bare reward，各 `r_*` 分量同样逐项相减。绝不减全局 bare 均值、不另外估计截距、不复用上一 checkpoint 的基线。

共享数据契约、完整性/provenance 校验和解释边界见 [paired_validation.md](./paired_validation.md)。训练中的 skill-free group 或 full-skill retry 不能替代固定 val 的 bare run。`run_full_train.sh` 每 20 个 slime step 停下来跑同一 checkpoint 的 bare val 与 masked gate，这不是 slime `--eval-interval`。

选择顺序严格固定：

1. 每个 rewrite family 内按 reward coefficient 选唯一 winner；相等时保留 old；
2. loser 在全局排名前废弃；
3. unchanged、ADD 与 rewrite winner 合成 survivor pool；
4. 只对 coefficient `> 0` 的 survivors 做全局 top-K；
5. current chunk 的非正或 budget-excluded 结果记为 retired；
6. new rewrite winner 进入 active bank 时沿用 target 的逻辑 `skill_id`，避免每轮改写破坏 slot 身份。

原始 bare run 的 `summary.json` 继续作为训练汇报的 skill-free validation score；gate 的 `summary.json` 则是 masked 原始分数。`contributions.json` 单独保存差值、系数和 baseline 来源/hash，不覆盖两组绝对指标。符号用于当前选择规则，不单独证明正面/负面因果作用。

Gate 完成后输出 `mask_assignments.json`、通用 evaluator traces/summary、`contributions.json`、带最终 validation result 的 `proposal_ledger.jsonl`、`selected_skillbank.json`、`selected_skill.md` 和 `online_gate_manifest.json`。任何 coverage 缺失都会保留 `incomplete`，不会冻结 selection。

## 5. 下一轮

用新 gate 的 `selected_skillbank.json` 显式选择下一轮 q/rho 参数并构建新的 curriculum，然后让 slime 从对应 checkpoint 继续一个有限 round。

注意 slime 的 `NUM_ROLLOUT` 是累计结束位置，不是本轮增量。当前默认及本次完整主实验为 4 段、每段 20 step：`NUM_ROLLOUT` 依次为 21、41、61、81（转换后的 ckpt 从 `start_rollout_id=1` 起）。早期脚本默认五轮、100 step；若要重用这一计划可显式设置 `NUM_ROUNDS=5`。每段结束后从已保存 checkpoint 继续，并更新 SkillBank / curriculum / `round_id` 与评测的 checkpoint identity。仍填上一段的结束值会使循环为空。`run_shopsimrl_slime.sh` 打开 `--override-opt-param-scheduler`，允许后一段用更大的 `NUM_ROLLOUT` 覆盖 checkpoint 里的 scheduler 总步数。

完整循环为：

```text
validated SkillBank + explicit q/rho
        → frozen curriculum state
        → slime GRPO round
        → all-wrong full-skill retry queue
        → gold-aware online analysis + many-to-one candidates
        → same-checkpoint bare val score + reusable task traces
        → frozen-checkpoint paired-delta randomized val gate
        → replacement winner + positive top-K
        → next validated SkillBank
```

Test split 不参与上述任何更新。最终 checkpoint 只在 test 上分别报告 full-skill paired performance 与 skill-free performance，二者差值作为 internalization gap。

## 6. 无 GPU 链路回归

```powershell
python -m pytest tests/test_slime_runtime_integration.py tests/test_training.py tests/test_episode_admission.py tests/test_training_preflight.py tests/test_paired_validation.py -q
```

## 7. 训练监控

W&B 的登录、run/group 组织、完整指标口径、Analyst/gate Table 与 Artifact、
offline sync、建议面板和异常排查见 [training_monitoring.md](./training_monitoring.md)。

集成测试直接使用当前 slime 的 OpenAIAdapter、TrajectoryManager 和 Sample，以本地替身提供模型输出与环境，验证多轮 token/logprob/loss mask、工具修复、中止、group reward、diagnostic retry 不进入 trainer、失败 retry 到 online analysis/candidate pool、在线 gate 到下一轮 curriculum。未启动 GPU 更新或真实 Analyst/Compiler API。真实 SGLang parser/服务、环境联调和 Megatron forward/backward 留给少量卡 smoke。
