RLinf RECAP (pi0.5) 昇腾910B适配简记

发表于 17 小时前  17 次阅读


## 一、背景

RLinf 是一个基于 Ray 的机器人强化学习框架。其核心算法 RECAP 通过四阶段流水线实现 classifier-free guidance (CFG) 策略优化:先计算轨迹回报(Step 1),再训练价值模型(Step 2),然后计算优势标签(Step 3),最后 CFG 训练策略模型(Step 4)。整个过程涉及 VLM backbone(SigLIP + Gemma3)、8 卡 FSDP 分布式训练,目标是把这套 pipeline 完整跑在华为昇腾 910B 上。

环境:Docker 镜像 `rlinf/rlinf:agentic-rlinf0.2-libero-cann9.0`,自建 `.venv`(install.sh embodied --model openpi --env libero --platform ascend),torch 2.6.0+cpu + torch-npu 2.6.0,CANN 9.0。

数据集:RLinf/RECAP-Libero10-Task0-48succ-Data。路径:
- `libero10_task0_sft`:48 条成功轨迹(8005 帧),Step 2 训练集
- `libero10_task0_train`:4096 条成败混合的 rollout,Step 2 eval + Step 1 输入
- `libero10_task0_eval`:验证集

## 二、Step 1 — Compute Returns(通过)

纯 CPU 计算,无 NPU 依赖。对 8005 帧 SFT 数据和 4096 条 rollout 轨迹分别计算折扣回报(`G_t = r_t + γ·G_{t+1}`,γ=1.0),生成 `meta/returns.parquet`。全程顺畅。

```bash
python examples/recap/process/compute_returns.py --config-name=compute_returns
```

## 三、Step 2 — Value Model SFT(8 卡 FSDP 训练)

这是整个适配过程中踩坑最密集的阶段。

### 3.1 transfer_to_npu 的 Segfault

`torch_npu.contrib.transfer_to_npu` 是 torch-npu 的 monkey-patch 模块,作用是把所有 `torch.cuda.*` 调用自动替换为 `torch.npu.*`。但它在遍历 torch 模块树时触发了 transformers 的 lazy import→image_transforms→tensorflow→`pywrap_tensorflow.py→self_check.py:63`,TF 的 C 扩展在 NumPy 2.x 下直接 segfault。

**修复**:注释掉 `train_value.py` 里的 `from torch_npu.contrib import transfer_to_npu`,然后 `pip uninstall tensorflow`。

但实际上 Ray worker 文件 `rlinf/workers/sft/fsdp_value_sft_worker.py` 也 import 了它,所以每个 NPU worker fork 时还是会触发。关键是要确保环境中没有 tensorflow。

### 3.2 LeRobot Dataset 离线加载

Step 2 的 config 里指定了 `eval_data_paths` 指向本地 `libero10_task0_train`。LeRobot 的 `LeRobotDataset.__init__` 会调用 `get_safe_version()`,该方法内部 `get_repo_versions()` 去 hf-mirror 查数据集版本信息,但这个本地数据集不在 HuggingFace 上——404。

**修复**:直接 patch LeRobot 源码 `lerobot/common/datasets/utils.py`:

```python
# get_repo_versions — 捕捉异常返回空列表
def get_repo_versions(repo_id: str):
try:
return _get_repo_versions_impl(repo_id)
except Exception:
return []

# get_safe_version — hub_versions 为空时直接用本地版本
def get_safe_version(repo_id, version):
hub_versions = get_repo_versions(repo_id)
if not hub_versions:
return f"v{target_version}" # 用本地 info.json 的 _version_
...
```

`.pth` 文件和入口脚本 monkey-patch 都不生效(Ray workers 独立 fork,不继承父进程 monkey-patch),必须改源文件。

### 3.3 Ray Timer Crash

训练跑 4 步没问题,第 5 步触发 save+eval 时崩:

```
ValueError: Timer 'run_eval' has not been recorded.
```

根因:`transfer_to_npu` monkey-patch 了 `torch.distributed.init_process_group`(替换 backend 为 hccl),干扰了 Ray 内部 `@Worker.timer` 装饰器的计时机制。

**修复**:把 `pop_execution_time` 的 crash 改成 no-op:

```python
# rlinf/scheduler/worker/worker.py:1295
if tag not in self._timer_metrics:
return 0.0 # patched: Ray timer broken by transfer_to_npu
```

### 3.4 训练指标

8 卡 FSDP,micro_batch=16×8=128 global_batch,8005 样本,约 62 步/epoch。

前 4 步训练指标(模型尚未收敛,正常):
- loss ~6.0,MAE ~0.43
- predicted_value_std ~0.01(模型输出近乎常数,预期中)
- value_spearman ~-0.3 逐步改善(step 4 到 -0.02)

## 四、已完成修改清单

| 文件 | 修改 | 原因 |
|------|------|------|
| `examples/recap/value/train_value.py:26` | import transfer_to_npu(保留) | NPU cuda→npu 映射 |
| `rlinf/workers/sft/fsdp_value_sft_worker.py:30` | import transfer_to_npu(保留) | Ray worker 需要 |
| `rlinf/scheduler/worker/worker.py:1295` | ValueError → return 0.0 | Ray timer crash |
| `lerobot/utils.py:get_repo_versions` | try/except 包裹 | 离线数据集 404 |
| `lerobot/utils.py:get_safe_version` | hub_versions 空时用本地版 | 同上 |
| `examples/recap/value/config/libero_sft_value.yaml` | val_check_interval=50, save_interval=50 | 控制验证频率 |

备份目录:`/RLinf/backups_adapt/20260630_101234/`

## 五、剩余步骤

Step 3(Compute Advantages)和 Step 4(CFG Training)尚未运行。Step 4 同样涉及 8 卡 FSDP 训练,预计会遇到类似的 transfer_to_npu / LeRobot / Ray timer 问题,前述 patch 应已覆盖。

简记。


scanz个人博客