Skip to content

实验记录:terminal-rl Qwen3-4B GRPO outcome-only 单节点 8×H200(10h,wandb lpurziy1) #1

@HansBug

Description

@HansBug

TL;DR

在单节点 8× H200(143 GB/卡)上跑了一次 terminal-rl GRPO outcome-only(dense pass-rate reward,无 PRM、无 process reward)训练 10 小时,模型为 Qwen3-4B,数据集为 seta_env(1376 个 Linux 终端任务)。

  • W&B runhttps://wandb.ai/hansbug/openclaw-terminal-rl/runs/lpurziy1
  • 训练步数:~270 step(对应 ~135 个 rollout round,每个 rollout batch=16 × n_samples=8 = 128 样本)
  • 结果terminal/accuracy 从 baseline 0.19 爬到 peak 0.385(25 步桶均值)/ 0.60(单 batch 最高),随后在 0.32–0.38 plateau;后期 KL 漂移到 0.54 时主动停下
  • 7 处 OpenClaw-RL 本仓库源码修改 + 1 处 mbridge site-packages 补丁,见下文"源码修改清单",每处都附 diff、必要性、不打补丁的后果、潜在副作用

1 期望训练结果 —— 我查过的所有官方 / 半官方来源

这一段是想说清楚"官方到底有没有给出 terminal-rl 的期望指标",答案是 没有,所以我们这次跑的结果没有直接可比的目标值。下面是我为了确认这一点翻过的所有位置。

1.1 Tech Report PDF

  • arxiv 链接:https://arxiv.org/abs/2603.10165

  • Section 5.4 General Agents: Unified RL Across Terminal, GUI, SWE, and Tool-Call(page 12)——只提到 terminal-rl 的基础设施描述("128 parallel environments for terminal agents"),没有训练曲线或精度数字

  • Table 4 给出 outcome-only vs integrated(outcome+PRM) 的对比:

    Setting Integrated reward Outcome only
    Tool-call 0.30 0.17 (train 250 steps)
    GUI 0.33 0.31 (train 120 steps)

    terminal 这一行是空的——paper 没给数字。

  • Table 3(Personal-Agent 作业场景实验):

    Method Updated 8 steps Updated 16 steps
    Binary RL 0.25 0.23
    OPD 0.25 0.72
    Combined 0.76 0.81

    这一张是 personal-agent 的,不是 terminal-rl;而且 paper 里 personal-agent 的 "Binary RL" 是真 0/1 reward,跟本工作 fraction-of-pass 的 dense reward 形态不一样。但作者自己这里就说明了 Binary RL 在 16 步就 plateau 甚至 0.25→0.23 略降,同属 outcome-only / 无 critic / 无 process reward 一族,plateau pattern 有参考意义。

1.2 Blog 系列

  • Track 1 blog(Personal Agent):https://yinjjiew.github.io/projects/openclawrl1/ —— 只有 Table 3 的 personal-agent 数据
  • Track 2 blog(General Agents):https://yinjjiew.github.io/projects/openclawrl2/ —— 专门讲 terminal/GUI/SWE/tool-call 4 种 general agent,但数字表格仍然只有 tool-call + GUI(抄自 Table 4),terminal 明确不在表里
  • Blog 结尾 roadmap 自己承认:"plan to ... identify stronger optimization recipes through large-scale experiments",意味着terminal-rl 的 optimization recipe 作者自己也还在找

1.3 伴生论文 RLAnything

  • arxiv:https://arxiv.org/abs/2602.02488
  • 39 页 PDF 全文搜 "terminal",没有带数字 / baseline 的上下文。该论文讲 GUI + LLM agent + coding,terminal 不在实验范围内

1.4 GitHub issues(main repo 全文搜 "terminal",10 条)

我读了相关的几条(Gen-Verse#59Gen-Verse#62Gen-Verse#68Gen-Verse#69Gen-Verse#87Gen-Verse#88),作者都没给出训练曲线数字;Gen-Verse#62 有个用户问 retool_qwen3_4b_rl(和我们同等级的 4B agent RL)的预期训练时间,至今 OPEN、没有权威回复。

1.5 HuggingFace Papers 讨论区

https://huggingface.co/papers/2603.10165 —— 只有一条和 baseline 无关的排版疑问,没有训练数字讨论。

1.6 仓库 assets

assets/openclawrl1performance.png —— 是 personal-agent 的 Figure 2(学生/老师 OpenClaw 对话模拟),不是 terminal-rl 曲线。

1.7 结论

terminal-rl 没有官方公开的"预期训练到多少"数字。 唯一有指导性的是 Table 4 中 tool-call outcome-only 基线 0.17 @ 250 步、GUI outcome-only 0.31 @ 120 步。把我们的 terminal accuracy 对照这两条:

  • 我们 peak 0.385(25 步桶均值),高于 tool-call outcome-only 基线(0.17)
  • 介于 GUI outcome-only(0.31)和 integrated(0.33)之间
  • 但这两个 setting 都不是 terminal,只能说明我们的量级合理,不能说"达标"或"不达标"

2 启动顺序和命令

完整可运行的启动脚本在 run_terminal_rl.sh (attachment);该脚本基于仓库里官方提供的 terminal-rl/terminal-rl_qwen3-8b.sh 改的,结构完全同构(cleanup_prev → check_gpus → detect_nvlink → start_ray_head → submit_job),只做了针对 4B + 单机的最小改动,下面列清楚。

2.1 前置依赖

# 在 /nfs/terminal-rl-workspace/ 下
conda env 'tbench-rl'  (Python 3.12)
  torch 2.9.1+cu128
  sglang 0.5.10.post1
  ray 2.55.1
  mbridge 0.15.1
  transformer-engine 2.13.0   (prebuilt cu12 wheel)
  flash-attn 2.8.3            (prebuilt cu12torch2.9 wheel)
  + slime -e .                (editable install)

.env 文件(在 /nfs/terminal-rl-workspace/.env):
  WANDB_API_KEY=<key>
  WANDB_PROJECT=openclaw-terminal-rl
  WANDB_ORG=hansbug
  WANDB_MODE=online

checkpoint 准备:

# HF 权重下载(用 xet 会在 NFS 上 perm denied)
HF_HUB_DISABLE_XET=1 hf download Qwen/Qwen3-4B --local-dir /nfs/models/Qwen3-4B

# HF → Megatron torch_dist(用 TE impl 做,不要加 --transformer-impl local)
cd slime && source scripts/models/qwen3-4B.sh
python tools/convert_hf_to_torch_dist.py \
    "${MODEL_ARGS[@]}" \
    --hf-checkpoint /nfs/models/Qwen3-4B \
    --rotary-base 1000000 \
    --no-gradient-accumulation-fusion \
    --save /nfs/models/Qwen3-4B_torch_dist

2.2 Pool server(和训练分离、独立 Docker 容器)

docker run -d --name openclaw_pool_server --restart unless-stopped \
    -p 18081:18081 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /nfs/terminal-rl-workspace/OpenClaw-RL:/nfs/terminal-rl-workspace/OpenClaw-RL \
    -e DATASET_DIR=/nfs/terminal-rl-workspace/OpenClaw-RL/terminal-rl/dataset \
    -e TBENCH_OUTPUT_ROOT=/nfs/terminal-rl-workspace/OpenClaw-RL/terminal-rl/build_outputs \
    -e TBENCH_DOCKER_IMAGE_SOURCE=build \
    -e ENV_SERVER_PORT=18081 \
    openclaw_pool_server:v1 \
    bash -c "cd /nfs/terminal-rl-workspace/OpenClaw-RL && \
        python -m terminal-rl.remote.pool_server \
            --host 0.0.0.0 --port 18081 \
            --max-tasks 8 --max-runs-per-task 4 \
            --output-root /nfs/terminal-rl-workspace/OpenClaw-RL/terminal-rl/build_outputs"

# 健康检查:curl http://127.0.0.1:18081/healthz  → {"ok":true}

2.3 启动训练

cd /nfs/terminal-rl-workspace/OpenClaw-RL
source /home/ubuntu/miniconda3/etc/profile.d/conda.sh && conda activate tbench-rl
: > terminal-rl/logs/training.log
nohup bash terminal-rl/run_terminal_rl.sh > terminal-rl/logs/training.log 2>&1 &

脚本内部流程:

  1. set +u 保护 conda activate tbench-rl(conda activate 脚本引用 unset 变量)
  2. source ../.env 注入 wandb 凭证
  3. 拼接 LD_LIBRARY_PATH 指向 $CONDA_PREFIX/lib/python3.12/site-packages/nvidia/{cudnn,nvtx,cusparse,nccl,...}/lib(TE 运行时需要)
  4. source slime/scripts/models/qwen3-4B.sh 加载 MODEL_ARGS
  5. cleanup_prevpkill sglang/ray/python
  6. ray start --head --num-gpus 8 ...
  7. ray job submit --runtime-env-json=...train_async.py + 全部 args 提交

完整参数 list 见 attachment 里的脚本。

2.4 ckpt 保留守护(单独进程)

slime 原生没有 --save-max-to-keep,每个 iter_* 是 ~50 GB(4B 模型 dist_checkpoint),几小时下来 NFS 就能吃掉 2 TB。我写了个简单的 polling daemon:

nohup bash terminal-rl/ckpt_retention.sh \
    > terminal-rl/logs/ckpt_retention.log 2>&1 &

脚本逻辑:每 5 分钟扫一次 iter_* 目录,只保留最新 KEEP_N=2 个,且只动 mtime > 3 分钟前的目录(避免和 in-flight save 打架)。


3 和官方 terminal-rl_qwen3-8b.sh 的配置差异

Arg 官方 8B 本次 4B 为什么改
模型脚本 qwen3-8B.sh qwen3-4B.sh 用 4B 模型
ACTOR_GPUS / TP 4 / 4 2 / 2 4B + TP=2 足够,省卡给 rollout
ROLLOUT_GPUS 4 6 actor 少了 2 卡,给 rollout
--rollout-num-gpus-per-engine 2 2 3 engines × 2 GPUs
--no-gradient-accumulation-fusion ❌(官方假设装了 Apex) 我们没装 Apex,原配置会跑到 Megatron 内部 gradient_accumulation_fusion=True 然后崩:ColumnParallelLinear was called with gradient_accumulation_fusion set to True but the custom CUDA extension fused_weight_gradient_mlp_cuda module is not found
ENV_SERVER_URL router_server.py 在 :18080 上 proxy WORKER_URLS 直接 http://127.0.0.1:18081 单机 + 单 pool_server,不需要多 worker 的 proxy
--prm-enable ❌(默认 8B 脚本本来就没有,PRM 在另一个 _prm_2nodes.sh 保持 outcome-only(无 process reward)
--use-slime-router ❌(默认 sglang_router 不踩 --use-slime-router 自带的 h11 Content-Length bug

4 源码修改清单(共 7 处 repo 内 + 1 处 site-packages)

下面每一项都给出 diff + 为什么需要 + 不打补丁的后果 + 潜在副作用 / 风险。这些修改都是运行环境和仓库上游假设不一致造成的,不是"我觉得代码写得不好所以改了"。

git diff --stat 汇总:

 Megatron-LM/megatron/core/utils.py                 |  7 +++++--
 slime/slime/backends/megatron_utils/initialize.py  |  4 +++-
 slime/slime/backends/megatron_utils/megatron_to_hf/qwen2.py   | 11 +++++++++++
 slime/slime/backends/megatron_utils/update_weight/update_weight_from_distributed.py | 22 ++++++++++++++++++----
 slime/slime/backends/sglang_utils/sglang_engine.py |  6 ++++++
 terminal-rl/remote/docker_compose_utils.py         |  5 ++++-
 terminal-rl/remote/terminal_env.py                 |  5 ++++-
 7 files changed, 51 insertions(+), 9 deletions(-)

4.1 Megatron-LM/megatron/core/utils.py — TE 未装时 is_te_min_version 的安全回退

@@ -346,9 +346,12 @@ def is_te_min_version(version, check_equality=True):
             "packaging is not installed. Please install it with `pip install packaging`."
         )
 
+    te_version = get_te_version()
+    if te_version is None:
+        return False  # TE not installed
     if check_equality:
-        return get_te_version() >= PkgVersion(version)
-    return get_te_version() > PkgVersion(version)
+        return te_version >= PkgVersion(version)
+    return te_version > PkgVersion(version)
  • 为什么需要:我们最早没装 transformer_engine(走的是 --transformer-impl local 路线),get_te_version() 返回 None,原代码 None >= PkgVersion(...) 直接 TypeError 崩。
  • 不打的后果:Megatron 模块加载期 TypeError。
  • 当前状态是否还需要不严格需要——我们后来装了 TE 2.13,这条 if te_version is None 在当前跑法里永远不进入。但保留不影响正常路径,且让没 TE 的 dry-run 也能 import Megatron,所以留着。
  • 副作用:零。原路径(TE 已装)走 te_version = get_te_version(); if check_equality: ... 和原来一字不差。

4.2 slime/slime/backends/megatron_utils/initialize.py — numpy 2.x 硬 assert 降为 warning

@@ -63,7 +63,9 @@ def init(args):
     _initialize_distributed(args)
 
     # https://github.com/NVIDIA/Megatron-LM/issues/1563
-    assert np.__version__.startswith("1."), "Megatron does not support numpy 2.x"
+    if not np.__version__.startswith("1."):
+        import warnings
+        warnings.warn(f"Megatron was designed for numpy 1.x, got {np.__version__}. Continuing anyway.")
  • 为什么需要:sglang ≥ 0.5 的依赖树把 numpy 拉到 2.3.5(pandas/ml_dtypes/...)。这个 assert 必炸,训练进程刚初始化就挂。
  • 不打的后果AssertionError: Megatron does not support numpy 2.x 训练进程 0 步就死。
  • 副作用 / 风险
    • 上游那条 assert 是防御性的,numpy 1.x → 2.x API 少数地方不兼容(最常见 np.float_ 被删、np.infty 被删、np.asfarray 被删等)。Megatron 用到的 numpy API 在 2.x 里大部分仍然是稳定的;这次 270 步训练没触发。但如果未来 Megatron 改动走到某条 numpy 2.x 缺的 API,会在那个调用点报错,错误信息不像 assert 那样好看。
    • 理想方案应该是 pin numpy<2 到上游;这里只是"凑合能跑"的权宜。

4.3 slime/slime/backends/megatron_utils/megatron_to_hf/qwen2.py — 补 local-impl 命名

@@ -68,4 +68,15 @@ def convert_qwen2_to_hf(args, name, param):
         elif rest == "self_attention.k_layernorm.weight":
             return [(f"model.layers.{layer_idx}.self_attn.k_norm.weight", param)]
 
+        # local (non-TE) impl: standalone layernorm modules
+        elif rest == "input_layernorm.weight":
+            return [(f"model.layers.{layer_idx}.input_layernorm.weight", param)]
+        elif rest in ("pre_mlp_layernorm.weight", "post_attention_layernorm.weight"):
+            return [(f"model.layers.{layer_idx}.post_attention_layernorm.weight", param)]
+        elif rest == "mlp.router.weight":
+            return [(f"model.layers.{layer_idx}.mlp.gate.weight", param)]
+        elif rest == "self_attention.core_attention.rotary_emb.inv_freq":
+            # rotary embeddings are not saved in HF format
+            return []
+
     raise ValueError(f"Unknown parameter name: {name}")
  • 为什么需要:当使用 --transformer-impl local(TE 未装时的退路)时,Megatron 模型里出现的是 input_layernorm.weight / pre_mlp_layernorm.weight 这类"独立 layernorm 模块"的名字,而不是 TE fused 的 self_attention.linear_qkv.layer_norm_weight。原 convert_qwen2_to_hf 只认 TE 名字,遇到 local 名字直接 raise ValueError(Unknown parameter name: ...) 把 Megatron→HF 的 save_checkpoint 打断。
  • 不打的后果:如果走 --transformer-impl local 路径(例如机器上装不了 TE),Megatron 保存 HF 格式 ckpt 时崩。
  • 当前是否需要不严格需要——我们最终用 TE impl 训,导出时走不到这几条 elif。但保留对 "没 TE 的环境" 是一层兼容。
  • 副作用:纯新增的 elif 分支,不改动现有分支逻辑;零副作用。

4.4 slime/slime/backends/megatron_utils/update_weight/update_weight_from_distributed.py — NCCL 重复 GPU 错误的软失败

     handles = []
-    for _, param in converted_named_tensors:
-        handles.append(dist.broadcast(param.data, 0, group=group, async_op=True))
-    for handle in handles:
-        handle.wait()
+    try:
+        for _, param in converted_named_tensors:
+            handles.append(dist.broadcast(param.data, 0, group=group, async_op=True))
+        for handle in handles:
+            handle.wait()
+    except Exception as e:
+        if "Duplicate GPU" in str(e) or "ncclInvalidUsage" in str(e):
+            import logging
+            logging.getLogger(__name__).warning(
+                "NCCL weight broadcast skipped (single-GPU demo, duplicate GPU): %s", str(e)[:200]
+            )
+            for h in handles:
+                try: h.wait()
+                except Exception: pass
+        else:
+            raise
  • 为什么需要:用于1-GPU hackCUDA_VISIBLE_DEVICES=0 + Ray --num-gpus 3 模拟 3 卡,全落在 GPU 0)的场景。NCCL 在同一张物理卡上建 broadcast group 会 Duplicate GPU detected 报错。这个 try/except 让 1-GPU demo 能跑完(权重不会在 rollout 间同步,但至少训练步能走)。
  • 不打的后果:1-GPU demo 模式训练进程第一次 weight sync 就 crash。
  • 当前是否需要当前 8-GPU 跑法不触发。但保留给未来想用 1-GPU debug 的人。
  • 副作用 / 风险有真风险——如果 NCCL 真的在多卡下出了 Duplicate GPU / ncclInvalidUsage 这类错(硬件故障、错误配置),这个 catch 会把它变成 warning 吞掉,训练继续但权重根本没同步。生产环境应该移掉或加一个 env var 开关来决定是否吞。

4.5 slime/slime/backends/sglang_utils/sglang_engine.py — SGLang HTTP 端同样的软失败

         try:
             response.raise_for_status()
         except requests.exceptions.HTTPError as e:
+            if "Duplicate GPU detected" in response.text or "ncclInvalidUsage" in response.text:
+                logger.warning(
+                    "Weight sync skipped (NCCL duplicate GPU on single-GPU setup): %s",
+                    response.text[:200],
+                )
+                return {"success": True, "skipped": True}
             e.add_note(f"{response.text=}")
             raise
  • 为什么需要:同 4.4,但这里是 actor 通过 HTTP 调 SGLang 的 /update_weights_from_distributed 端点,NCCL 错从 HTTP 响应 text 里传回来。1-GPU hack 用。
  • 不打的后果:同 4.4,1-GPU demo 挂。
  • 当前是否需要当前 8-GPU 跑法不触发
  • 副作用 / 风险:和 4.4 完全对称。生产环境应该移掉。

4.6 terminal-rl/remote/docker_compose_utils.pyDockerComposeManager.build(timeout=…) 兼容

-    compose_manager.build(timeout=timeout)
+    try:
+        compose_manager.build(timeout=timeout)
+    except TypeError:
+        compose_manager.build()
  • 为什么需要terminal-bench 本次我们装的版本 0.2.18DockerComposeManager.build() 方法不接受 timeout 关键字参数。上游 docker_compose_utils.py 里写死了 build(timeout=…)

    /nfs/terminal-rl-workspace/OpenClaw-RL/terminal-rl/remote/README.mdterminal-rl/README.md 都没固定 terminal-bench 版本。用 pip install git+https://github.com/laude-institute/terminal-bench.git 默认装 main 分支 → 我装到的 0.2.18 里已经没这个 kwarg。

  • 不打的后果:pool_server 第一个 /resetTypeError: build() got an unexpected keyword argument 'timeout',500 返回给 RolloutManager,样本全标 FAILED。

  • 副作用:fallback 到无 timeout 的 build(),意味着build 本身可能挂死而不是 timeout 触发。实际运行下 build 都能几秒内完成,没观察到挂死。

  • 理想方案:上游 pin 一个 terminal-bench 版本(或检测 signature 自适应),我这里只是凑合跑。

4.7 terminal-rl/remote/terminal_env.pyTerminal.start(timeout=…) 兼容

             else:
-                self._terminal.start(timeout=self._timeouts.reset_session)
+                try:
+                    self._terminal.start(timeout=self._timeouts.reset_session)
+                except TypeError:
+                    self._terminal.start()
                 try:
                     from .docker_compose_utils import (
                         _DEFAULT_CONTAINER_MEMORY_LIMIT,
  • 为什么需要:同 4.6,terminal-bench 0.2.18 的 Terminal.start() 不接受 timeout kwarg
  • 不打的后果:pool server 一律 500,agent 一个都跑不起来,和我们最初 9 小时白训(agent 全失败、grad=0)的根因之一。
  • 副作用:fallback 不传 timeout,依赖 terminal-bench 内部默认。实际运行无观察到问题。
  • 理想方案:同 4.6,pin 版本或动态自适应。

4.8 mbridge/models/qwen2.py(site-packages,非 git 追踪)

不在仓库里,但是 HF → torch_dist 转换时必需的外部依赖补丁。上游 mbridge==0.15.1Qwen2Bridge 只认 TE fused 的 self_attention.linear_qkv.layer_norm_weight 类命名。走 --transformer-impl local 转换时,Megatron 会产生 input_layernorm.weight / pre_mlp_layernorm.weight 这种 local-impl 专属的参数名,mbridge 找不到映射直接 NotImplementedError: Unsupported parameter name: decoder.layers.0.input_layernorm.weight 转换崩。

修改内容(加到 _MLP_MAPPING 和新建的 _OTHER_MAPPING):

_MLP_MAPPING = {
    # ... 原有映射 ...
    # local (non-TE) impl: standalone pre_mlp_layernorm goes here because its
    # name contains "mlp" so routing sends it to _MLP_MAPPING.
    "pre_mlp_layernorm.weight": [
        "model.layers.{layer_number}.post_attention_layernorm.weight"
    ],
}
# local (non-TE) transformer_impl: standalone layernorm modules whose names
# do not contain "self_attention" or "mlp", so they land in _OTHER_MAPPING.
_OTHER_MAPPING = {
    "input_layernorm.weight": [
        "model.layers.{layer_number}.input_layernorm.weight"
    ],
    "pre_mlp_layernorm.weight": [
        "model.layers.{layer_number}.post_attention_layernorm.weight"
    ],
    "post_attention_layernorm.weight": [
        "model.layers.{layer_number}.post_attention_layernorm.weight"
    ],
}
  • 为什么需要:只有在用 --transformer-impl local 做 HF → torch_dist 转换时才会踩。
  • 不打的后果python tools/convert_hf_to_torch_dist.py ... --transformer-impl local 直接 NotImplementedError
  • 当前是否需要不严格需要——我们最终重做了转换,用 TE impl(不传 --transformer-impl local)。但装补丁对 TE 路径 0 影响(新增的 key 永远不匹配)。
  • 副作用:纯新增键;对 TE 路径 0 影响;对 local 路径是 bug fix。
  • 最佳处理:写到 mbridge 上游。

4.9 小结:必要性分类

修改 当前 8-GPU + TE 跑法严格必需 其他路径有用
4.1 utils.py(TE None safe) ✅ 无 TE 场景
4.2 initialize.py(numpy 2.x warning)
4.3 slime qwen2.py mapping ✅ local-impl 导出
4.4 update_weight NCCL dup catch ✅ 1-GPU hack
4.5 sglang_engine NCCL dup catch ✅ 1-GPU hack
4.6 docker_compose build(timeout)
4.7 terminal_env start(timeout)
4.8 mbridge qwen2 mapping ✅ local-impl 转换

当前跑法下严格必需的只有 #4.2、#4.6、#4.7 三处。 其他五处是沿着外部 setup guide 一起打的兼容补丁,当前 TE + 8-GPU 的正路不触发,但也不会有反作用,所以没删。


5 关键指标曲线

dashboard

原图 + 单指标图(均附 MA(11) 平滑):

指标
terminal/accuracy(pytest 通过率)
terminal/reward_mean(= 2·accuracy − 1)
rollout/raw_reward(训练 batch 均值)
train/grad_norm
train/kl_loss
train/entropy_loss
terminal/non_trainable_ratio
rollout/response_len/mean(agent 输出 token 数)

6 训练阶段小结(25 步桶均值)

                      accuracy    raw_reward    kl_loss
steps   0-24  (cold)   0.211     -0.412         0.007
steps  25-49           0.303     -0.225         0.049
steps  50-74           0.344     -0.117         0.162
steps  60-79  (PEAK)   0.385     -0.058         0.182
steps 100-124          0.337     -0.224         0.067
steps 125-149 (spike)  噪声      噪声           0.296   ← grad_norm max=909 @ step 266,GRPO clip 把策略拉回
steps 150-174 (rec)    0.349     -0.176         0.108
steps 200-224 (plat)   0.334     -0.115         0.139
steps 275-297 (drift)     —         —           0.540   ← 我在这里停了

完整 JSON 在 summary_stats.json (attachment)

观察:

  1. 冷启动顺利:grad 0→1.5,accuracy 0.19→0.28 in 20 步
  2. 爬坡:到 step 60–79 达到峰 0.385,和 paper Table 3 personal-agent Binary RL "16 步 plateau 0.25" 的规律同族(reward 形态不同,但都是 outcome-only 无 critic) —— 爬升阶段短
  3. Plateau:0.32–0.38 震荡约 150 步
  4. Outlier:step 266 grad_norm=909, kl=5,下一步立即被 GRPO ratio clip + KL 压回 grad=0.2, kl=0.24。不是发散
  5. 晚期 KL 漂移:step 275–297 桶 kl mean 从 0.14 慢慢爬到 0.54,accuracy 没跟着涨 —— 这是 policy 在无收益地远离 ref,典型过度训练信号,所以主动停

7 Attachments(均走 gh attach session-token 路径,挂在 refs/uploads/issues/1 下,不产生 commit / branch)


8 给上游的建议(按优先级)

  1. 文档里给出 terminal-rl 的一条参考 wandb 曲线或一个 Table(即使是一张作者内部训的截图),省掉"我这是否在 work"的排查时间
  2. pin terminal-bench 版本,或者在 terminal-rl/remote/docker_compose_utils.pyterminal-rl/remote/terminal_env.py 里用 inspect.signature(...) 动态判断 kwarg 支持情况
  3. slime 加 --save-max-to-keep N;每 50 GB 的 ckpt 不清理几小时就把盘打爆
  4. --kl-loss-coef 0.01 配合 k3 对晚期 KL 漂移保护不足,建议 README 标注"建议 step > 200 观察 KL,必要时加大 coef 或早停"
  5. 单机部署可跳过 router_server.py,让 ENV_SERVER_URL 直连 pool_server;可以作为"single-node"模式写到 README

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions