Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OpenClaw-RL on Ascend 910B — Top-K Select Hybrid (GRPO + OPD) 训练全套

在华为 Ascend 910B(Atlas A2)集群上跑 OpenClaw-RL 的 Hybrid Top-K Select 训练 (GRPO 在线 RL + On-Policy Distillation,对齐论文 arXiv:2603.10165), 以 GSM8K 个人助手多轮对话为场景,student/TA/teacher 三角色由模拟器驱动。

本仓库是从生产集群工作目录 /home/openclaw-ascend-work 整理出来的可复现版本: 剔除了模型权重、checkpoint、Node.js 运行时、镜像 tar 包和调试残留,保留全部代码与脚本。

📘 完整运行指导书见 docs/RUNBOOK.md:从裸机到训练出 loss 的 逐步操作手册,含每步验证方法、启动时序、参数手册和故障排查。本 README 只做概览。


1. 总体架构

┌─ 训练节点 (MASTER_ADDR, 8×910B) ──────────────────────────────┐
│  docker: openclaw-slime-ascend:a2-cann85-home                 │
│  ├─ Ray head (:6379)                                          │
│  ├─ actor 训练     4 NPU  Megatron-LM (TP2×DP2)               │
│  ├─ rollout 推理   2 NPU  SGLang → :30000 (OpenAI 兼容 API)    │
│  └─ teacher 参考   2 NPU  (--prm-teacher-load, 冻结)           │
│                                                               │
│  docker: openclaw-feeder(同一镜像)                            │
│  └─ sim_joint.py → openclaw agent --local → :30000            │
│     (student/TA/teacher 三角色,外部模拟器扮演用户)              │
└───────────────────────────────────────────────────────────────┘
┌─ PRM 节点 (PRM_NODE, 8×910B) ─────────────────────────────────┐
│  docker: 同一镜像 → run_ray_prm_worker_inner.sh               │
│  └─ ray worker 加入 MASTER:6379,注册自定义资源 prm_gpu×8       │
│     PRM SGLang 引擎(打分 + 生成 hint)被调度到这里              │
└───────────────────────────────────────────────────────────────┘

数据流:feeder 通过 openclaw CLI 向 :30000 发多轮对话 → 训练侧 openclaw_combine_api_server 抓取为 rollout 样本 → PRM 打分并生成 hint → openclaw_topk_select_loss 计算 GRPO + Top-K OPD 混合 loss → Megatron 训一步 → SGLang 同步新权重 → 下一轮 rollout(严格 on-policy)。

关键铁律:各节点文件系统完全独立,没有共享存储。 本仓库要在 每个参与节点 部署一份到相同路径 /home/openclaw-ascend-work。 改训练代码必须改「训练容器所在节点」的那份——容器挂载的是本机目录。

2. 仓库结构

├── Dockerfile.slime-ascend-a2-openclaw   # 镜像构建(见 §4)
├── run_qwen3_4b_openclaw_combine_topk_ascend_2nodes.sh   # ★4B 2节点外层启动
├── run_qwen3_32b_openclaw_combine_topk_ascend_8nodes.sh  # 32B 8节点外层启动
├── convert_qwen3_4b_hf_to_torch_dist_ascend.sh           # HF→torch_dist 转换
├── run_openclaw_container.sh             # 手动进容器调试用
├── check_openclaw_env.sh                 # NPU 环境自检
├── scripts/                              # 容器内执行的 inner 脚本
│   ├── run_qwen3_4b_openclaw_combine_topk_ascend_2nodes_inner.sh  # ★训练参数都在这
│   ├── run_ray_prm_worker_inner.sh       # PRM 节点入口
│   └── ...(32B/1节点等其他变体)
├── OpenClaw-RL/                          # 算法代码(上游 fork,只保留用到的部分)
│   ├── openclaw-combine/                 # ★Top-K Select Hybrid:rollout/loss/API server
│   ├── openclaw-opd/                     # OPD 蒸馏:opd api server(rep_penalty 注入点)
│   ├── openclaw-rl/                      # 基础 RL:api server / rollout / oel eval
│   ├── Megatron-LM/                      # vendor 的 Megatron(已含 slime NPU 补丁 + NaN-skip 修复)
│   └── slime/                            # 上游自带 slime(镜像构建时 pip install,运行时不用)
├── slime-ascend-openclaw-full/           # ★SLIME_ROOT:训练框架(train_async.py 入口)
│   ├── slime/backends/megatron_utils/    # Megatron 后端适配层(LSE 修复在 loss.py)
│   ├── slime/backends/sglang_utils/      # SGLang 引擎适配层
│   └── docker/npu_patch/                 # Ascend 补丁集(构建镜像时 git am)
├── patches/                              # MindSpeed 运行时兼容补丁
├── sim_joint.py                          # ★GSM8K joint feeder(三角色模拟)
├── feed_forever_joint_sim.sh             # feeder 常驻循环
├── make_gsm8k.py / GSM8K.json            # 数据集(7473 题 train split)
└── .openclaw/                            # openclaw CLI 配置
    ├── openclaw.json                     # agent 指向 :30000
    └── extensions/rl-training-headers/   # 注入 X-Session-Id/X-Turn-Type 的插件

三个「Megatron」的关系(容易混淆):

位置 是什么 运行时是否生效
slime-ascend-openclaw-full/slime/backends/megatron_utils/ slime 的后端胶水层,不是 Megatron 本体 ✅
OpenClaw-RL/Megatron-LM/ vendor 的完整 Megatron-LM,已含静态 NPU 补丁 ✅ PYTHONPATH 优先,实际 import 的是它
镜像内 /workspace/Megatron-LM 构建时 pip install -e 的一份 ❌ 被 PYTHONPATH 遮蔽,仅兜底

真正的 Ascend 适配主力是 MindSpeed 运行时猴子补丁: megatron_utils 入口处 import mindspeed.megatron_adaptor 会把当前 import 到的 megatron 包的 CUDA 实现动态替换为 NPU 实现。

3. 硬件与前置条件

  • 2 台(4B)或 8 台(32B)Atlas A2 节点,每台 8×910B,aarch64
  • 宿主机已装 Ascend 驱动/固件(/usr/local/Ascend/driver、npu-smi 可用)
  • Docker,节点间内网互通,launch 机到各节点 root SSH 免密(外层脚本靠 ssh 编排)
  • 每节点磁盘预留:镜像 ~15G + 模型 ~10G + ckpt(4B 每份 ~50G,注意 save-interval)

4. 构建镜像

镜像 openclaw-slime-ascend:a2-cann85-home,基于华为云 swr.cn-south-1.myhuaweicloud.com/ascendhub/cann:8.5.0-910b-ubuntu22.04-py3.11。

# build context 需包含 OpenClaw-RL/(Dockerfile 里 COPY 它)
cd /home/openclaw-ascend-work
docker build -f Dockerfile.slime-ascend-a2-openclaw \
  -t openclaw-slime-ascend:a2-cann85-home \
  --build-arg HTTP_PROXY=... --build-arg HTTPS_PROXY=... .   # 无代理可不传

构建内容(都钉了版本,不要随意升级): torch 2.8.0 + torch_npu 2.8.0.post2 → SGLang v0.5.10(NPU 版)→ sgl-kernel-npu 预编译 wheel → mbridge/Megatron-Bridge(dev_rl)/Megatron-LM(3714d81d)/ MindSpeed(fc63de5c) → slime → 对五个仓库 git am 应用 slime-ascend-openclaw-full/docker/npu_patch/v0.2.4/ 补丁。

分发到其余节点(构建一次,到处 load):

docker save openclaw-slime-ascend:a2-cann85-home | zstd -T0 > openclaw-slime-ascend-a2-cann85-home.tar.zst
scp *.tar.zst <node>:/home/openclaw-ascend-work/
ssh <node> 'zstd -dc /home/openclaw-ascend-work/*.tar.zst | docker load'

5. 部署代码与模型

在每个参与节点上:

# 1. 代码放到固定路径(外层/inner 脚本都默认这个 WORK_ROOT)
git clone <本仓库> /home/openclaw-ascend-work

# 2. 下载模型(训练节点 + PRM 节点都要,路径必须一致)
#    4B: Qwen/Qwen3-4B-Thinking-2507   32B: Qwen/Qwen3-32B
huggingface-cli download Qwen/Qwen3-4B-Thinking-2507 \
  --local-dir /home/openclaw-ascend-work/models/Qwen3-4B-Thinking-2507

# 3. HF → Megatron torch_dist 转换(只需在训练节点做一次)
HF_CKPT=/home/openclaw-ascend-work/models/Qwen3-4B-Thinking-2507 \
  bash /home/openclaw-ascend-work/convert_qwen3_4b_hf_to_torch_dist_ascend.sh
# 产出 models/Qwen3-4B-Thinking-2507_torch_dist(inner 脚本的 REF_LOAD 默认值)

# 4. 环境自检:期望 npu_available True / npu_count 8
bash /home/openclaw-ascend-work/check_openclaw_env.sh

6. 启动训练(4B 2 节点)

在 launch 机(能 ssh 到两个节点即可,不占 NPU)执行:

cd /home/openclaw-ascend-work
CONTEXT_LENGTH=65536 ROLLOUT_MAX_CONTEXT_LEN=65536 \
ROLLOUT_TEMPERATURE=1.0 \
MASTER_ADDR=<训练节点IP> PRM_NODE=<PRM节点IP> \
HF_CKPT=/home/openclaw-ascend-work/models/Qwen3-4B-Thinking-2507 \
nohup bash run_qwen3_4b_openclaw_combine_topk_ascend_2nodes.sh \
  > /tmp/openclaw_4b_topk_2nodes.log 2>&1 &

外层脚本做的事:docker rm -f 清两节点旧容器 → PRM 节点起 ray worker 容器 → 训练节点起 master 容器跑 inner 脚本(ray head + 提交训练 job)。 启动后约 15–20 分钟 :30000 才可用(SGLang 加载 + NPU 图捕获 + Megatron 读 ckpt)。

关键参数(外层 -e 透传,inner 脚本给默认值):

参数 默认 说明
OPENCLAW_TOPK_K 4 Top-K 蒸馏候选数(论文 k)
OPENCLAW_TOPK_MAX_CAND / PRM_M 3 hint 候选数(论文 M)
OPENCLAW_TOPK_ADV_DIFF_CLIP 1.0 OPD 优势差裁剪 C
OPENCLAW_TOPK_W_RL / W_OPD 1.0 / 1.0 混合权重
OPENCLAW_TOPK_HINT_SELECTION sequence_optimal hint 选择策略
OPENCLAW_ROLLOUT_REP_PENALTY 1.1 rollout 重复惩罚(掐断 thinking 死循环,必开)
kl-loss-coef 0(inner 硬编码) 当前 KL 关闭;要开改 inner 脚本第 158 行
ROLLOUT_BATCH_SIZE 16 每步训练的 turn 数
NUM_ROLLOUT — 总步数

注意:rollout-max-response-len、rollout-batch-size 在外层脚本有 -e 覆盖, 改 inner 默认值不够,外层也要改。

断点恢复

当前 inner 脚本没有 --load:重启即 fresh(从 REF_LOAD 基座重新开始),不自动续训。 要续训需在 inner 脚本 CKPT_ARGS 加 --load "${SAVE_CKPT}",Megatron 会从 ckpt/qwen3-4b-openclaw-topk-select-ascend/ 的 latest iteration 恢复(详见 RUNBOOK §8.3)。

停止训练(⚠️ 按顺序)

# 1. 用容器名删,不要 pkill -f 脚本名(模式会匹配到自己的 shell → 自杀 exit 255)
ssh <训练节点> 'docker rm -f openclaw-rl-qwen3-4b-topk-2nodes'
ssh <PRM节点>  'docker rm -f openclaw-rl-qwen3-4b-topk-2nodes'
# 2. 检查 NPU 孤儿进程(docker rm 杀容器但进程可能泄漏,会占死 NPU)
ssh <节点> 'npu-smi info | grep python'   # 应为空,残留则 kill -9
ssh <节点> 'pgrep -f train_async'          # 应为空

7. 部署 Feeder(数据供给)

feeder 在训练节点上跑一个同镜像的常驻容器,内部用 openclaw CLI 造多轮会话。

7.1 一次性环境准备(容器外目录,挂载进去)

cd /home/openclaw-ascend-work
# Node.js ≥ 20 解压到 node/(aarch64 版),feeder 脚本用 $WORK/node/bin
wget https://npmmirror.com/mirrors/node/v22.11.0/node-v22.11.0-linux-arm64.tar.xz
tar xf node-*.tar.xz && mv node-v22.11.0-linux-arm64 node

# openclaw CLI 必须 2026.3.31 版(依赖 globalThis.fetch 行为与 --json 输出格式,勿 npm update)
# 国内环境用 npmmirror 源
./node/bin/npm config set registry https://registry.npmmirror.com
./node/bin/npm install -g openclaw@2026.3.31

# .openclaw/ 已在仓库里:openclaw.json 定义 rl-agent + provider(127.0.0.1:30000),
# extensions/rl-training-headers 插件负责注入 X-Session-Id / X-Turn-Type。
# 启动 feeder 容器时直接挂载为 /root/.openclaw(见下),无需手动拷贝。

7.2 配置模拟器 API key(唯一需要的密钥)

sim_joint.py 用外部大模型(火山方舟,glm-5.1)扮演学生/助教/老师三个 persona, key 从环境变量读:

export ARK_API_KEY=<你的火山方舟 key>

7.3 启动 feeder(等 :30000 能推理之后再启动)

curl -s http://127.0.0.1:30000/v1/models && \
docker run -d --name openclaw-feeder --network host \
  -v /home/openclaw-ascend-work:/home/openclaw-ascend-work \
  -v /home/openclaw-ascend-work/.openclaw:/root/.openclaw \
  -e ARK_API_KEY -e HOME=/root \
  openclaw-slime-ascend:a2-cann85-home \
  bash /home/openclaw-ascend-work/feed_forever_joint_sim.sh

feeder 设计要点(详见 sim_joint.py 头部注释):

  • 每个任务串行链 student → TA → teacher(TA 拿 student 真实答案,teacher 拿 TA 真实反馈),4 任务并发
  • 每 role-session 用独立 OPENCLAW_STATE_DIR=/tmp/ocfeed/<id> 隔离, 并 symlink extensions 进去(否则插件丢失 → X-Session-Id 不注入 → 样本全被丢弃)
  • 模拟器 看不到正确答案(不泄露 ground truth),persona 满意时输出 DONE 哨兵终止会话
  • SGLang 权重同步期间返回 503:feeder 自带指数退避重试(15s 起,×6 次),属正常现象

8. 监控

# 训练指标(reward / loss / grad_norm 等)
grep -a 'model.py:681\|rollout.py:1227\|data.py:236' /tmp/openclaw_4b_topk_2nodes.log | tail -20

# feeder
docker logs -f openclaw-feeder

健康值参考:

指标 健康区间 异常含义
train/grad_norm ~2–5 飙到数十/数百 = 发散
train/train_rollout_logprob_abs_diff 0.04–0.09 持续走高 = 策略漂移
OPD abs_mean < 1 ≡1.0 = 优势差恒被 clip,饱和(见 §9)
sel_overlap(teacher/student top-k 重叠) 稳定不单调下降 单调崩 = OPD 方向错
response_len 刷满 8192 占比 ~0 大量刷满 = thinking 死循环,查 rep_penalty
perf/step_time ~2–4 min/step(4B)

9. 已知问题与已打修复(重要)

以下修复已包含在本仓库代码里,列出来是为了防止有人"顺手还原":

  1. LSE bug(发散的压底根因):slime-ascend-openclaw-full/slime/backends/megatron_utils/loss.py _vocab_parallel_log_probs_at_indices 计算 teacher logprob 曾漏减 all-reduce 的 max 项,导致 ℓ_T 系统性偏高 ~+30–42 nat → OPD 优势差恒 +1 饱和 → 熵崩发散。 已修(return gathered − log_sum_exp − local_max)。
  2. NaN 梯度跳过:OpenClaw-RL/Megatron-LM/megatron/core/distributed/param_and_grad_buffer.py check_grads 遇非有限梯度时清零该 bucket 并 continue(跳过坏样本), 而不是走 Megatron rerun_state_machine 直接 abort 整个 job。
  3. rep_penalty 注入:OpenClaw-RL/openclaw-opd/openclaw_opd_api_server.py 对 rollout 请求 setdefault repetition_penalty(env OPENCLAW_ROLLOUT_REP_PENALTY)。 Thinking 模型在长 agentic 上下文里会在 <think> 内循环刷满 max tokens,必须开。
  4. sim_joint 成功判据:openclaw 3.31 agent --local --json 输出无顶层 status 字段,按 meta.stopReason + 非空文本判成功(曾因判 status=="ok" 导致每 turn 假失败重试 6 次、整条链路断裂)。
  5. flush_cache 重试:slime/backends/sglang_utils/sglang_engine.py 等待循环 range(300) 且非 200 响应时 sleep 在 try 块内(否则 400 空转打爆)。
  6. KL 锚:inner 脚本 KL_LOSS_COEF 默认 0.01(原 0.0,无锚易发散)。

已知未解问题:OPD 的 teacher(prm_teacher)从参考 ckpt 加载后冻结, 不随 actor 更新。论文要求 ℓ_T = π_old + hint(on-policy teacher)。直接把 gather 换到 actor 上会碰 dp_size 断言(prm_teacher DP=1 vs actor DP=2,数据分片不对齐)。 真修需要 per-rank indices 管线或每步向 teacher 广播权重,均为非平凡框架改动。 长训发散时可先降 OPENCLAW_TOPK_W_OPD 缓解。

10. 32B 8 节点

流程同 4B,用 run_qwen3_32b_openclaw_combine_topk_ascend_8nodes.sh + scripts/run_qwen3_32b_openclaw_combine_topk_ascend_8nodes_inner.sh, 模型 Qwen3-32B(转换脚本参考 4B 的,换 scripts/models/qwen3-32B.sh)。 节点角色:1 master + 3 actor worker + 2 rollout + 1 PRM + 1 teacher, 所有节点都要部署本仓库 + 镜像 + 模型到相同路径。

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages