RLinf v0.4 在Libero上进行 Cosmos3 监督微调
概述
Rlinf 是一个面向 Embodied AI(具身智能) 和 Agentic AI(智能体 AI) 的开源强化学习基础框架。
Cosmos3-Nano 是 NVIDIA 发布的 OmniMoT 视频世界模型。RLinf v0.4 支持以 Cosmos3-Nano 为基座,按照 LIBERO 的动作空间做监督微调(SFT),得到一个可以直接在 LIBERO 仿真器中评测的 Cosmos3 策略模型。训练从基座热启动,冻结理解塔,只训练生成分支与动作头。
通过百舸快速开始,您可以基于预置镜像创建开发机,完成「确认环境 → 准备基座模型与数据 → 启动 SFT 训练 → 转换 checkpoint」的完整流程,并通过 TensorBoard、训练日志和 GPU 指标监控训练进展。转换得到的 model_diffusers 目录可直接用于 Cosmos3 SGLang 评测。
适用场景
- 以 Cosmos3-Nano 为基座,在 LIBERO 全部 task suite(
libero_all)或单独libero_10上做动作头 SFT。 - 把 diffusers 格式的 Cosmos3-Nano 转成训练所需的 DCP 格式基座权重。
- 把 SFT 产出的 FSDP2 checkpoint 转成 diffusers 组件目录,交给 SGLang 做 LIBERO 仿真评测。
- 在单机开发机上排查初始化 OOM、动作头 shape 不匹配、HuggingFace 拉取卡死、动作归一化错位等问题。
- 不适合:Cosmos3 的强化学习训练、非 LIBERO 数据集。当前 RLinf 中 Cosmos3 的数据加载只支持 LIBERO。
训练规格参考 & 环境配置
推荐训练配置如下:
| 项目 | 推荐配置 |
|---|---|
| GPU | 单机 8 卡,官方验证为 8 × A800 80GB;实测 8 × 183GB 卡上默认配置每卡约 76–79GB |
| CPU | 建议 64 核以上(数据加载默认每 rank 4 个 worker) |
| 容器内存 | 建议 1TiB 以上。初始化时模型先留在 CPU(net bf16 约 27GB + net_ema fp32 约 54GB),再由 FSDP2 分片上卡,8 个 rank 同时构建时内存峰值较高 |
| 存储空间 | 训练前资产约 90GB(HF 缓存 ~22GB、Cosmos3-Nano 原始权重 ~33GB、DCP 基座 ~29GB、LIBERO 数据 ~4GB);每个 checkpoint 约 196GB,默认 5000 步存 10 份约 2TB;每次 checkpoint 转换的产物约 230GB |
项目目录配置如下表所示:
| 项目 | 推荐配置 |
|---|---|
| CUDA / 驱动 | 使用镜像自带的 CUDA(torch 2.11 + CUDA 13.0)与平台默认驱动,不要单独升级 |
| 工作目录 | /root/workspace/RLinf(镜像预置) |
| Python 环境 | /root/workspace/RLinf/.venv(镜像预置,cosmos3 + libero 依赖) |
| cosmos-framework | /root/workspace/cosmos-framework(镜像预置,commit 0460be81,已 editable 装入上面的 venv) |
| 训练入口 | /root/workspace/RLinf/examples/sft/train_vla_sft.py |
| 训练配置 | /root/workspace/RLinf/examples/sft/config/libero_sft_cosmos3.yaml |
| 模型目录 | /root/workspace/models |
| 数据目录 | /root/workspace/datasets/LIBERO_LeRobot_v3 |
| HF 缓存 | /root/workspace/hf-cache |
| 日志目录 | /root/workspace/logs |
训练产出与结果口径
训练完成后会留下以下产物:
| 产物 | 路径 |
|---|---|
| 主训练日志 | <LOG_DIR>/run_embodiment.log |
| TensorBoard event | <LOG_DIR>/tensorboard/ |
| SFT checkpoint(续训) | <LOG_DIR>/libero_sft_cosmos3/checkpoints/global_step_<N>/actor/dcp_checkpoint/(约 111GB) |
| SFT checkpoint(转换输入) | <LOG_DIR>/libero_sft_cosmos3/checkpoints/global_step_<N>/actor/model_state_dict/full_weights.pt(约 85GB) |
| 评测用模型 | 转换后的 <OUT>/model_diffusers(约 30GB) |
其中 <LOG_DIR> 在启动命令里通过 runner.logger.log_path 指定,libero_sft_cosmos3 是 runner.logger.experiment_name。checkpoint 每 runner.save_interval 步(默认 500)保存一次,每份约 196GB。
官方文档未给出 SFT 后的 LIBERO 成功率参考值,本文也不预置。成功率需要把 checkpoint 转换后,按 Cosmos3 SGLang 评测流程跑出来,以评测日志中的 eval/success_once 为准。
准备工作
如果您所在网络环境访问 HuggingFace 较慢,建议提前配置 HuggingFace 镜像源,或配置代理加速。
huggingface 镜像配置:
1export HF_ENDPOINT=https://hf-mirror.com
2export HF_HUB_ENABLE_HF_TRANSFER=1
hf-mirror 只适用于模型仓库:对 datasets 接口会返回 308 重定向,下载 LIBERO 数据集时需要 unset HF_ENDPOINT 直连 huggingface.co(见步骤四)。
百舸平台已提供以下模型的 BOS 地址(目录路径必须带结尾 /),可以直接从 BOS 拉取,避免依赖外部网络。LIBERO LeRobot 数据集目前没有 BOS 副本。
1bos:/aihc-models-bj/nvidia/Cosmos3-Nano/
2bos:/aihc-models-bj/Qwen/Qwen3-VL-8B-Instruct/
3bos:/aihc-models-bj/Wan-AI/Wan2.2-TI2V-5B/
4bos:/aihc-models-bj/Wan-AI/Wan2.2-TI2V-5B-Diffusers/
环境说明
说明:本文中的路径均以百舸开发机内的
/root/workspace为例。/root/workspace常是 100GB 左右的盘,放不下 checkpoint;建议用df -h找一块大容量挂载(如/mnt/pfs),把下文的models/、datasets/、hf-cache/、logs/统一换到该目录下,RLinf代码与.venv可以留在原处。
当前使用以下目录:
1/root/workspace/RLinf
2/root/workspace/RLinf/.venv
3/root/workspace/cosmos-framework
4/root/workspace/hf-cli-venv
5/root/workspace/models/Cosmos3-Nano
6/root/workspace/models/Cosmos3-Nano-DCP
7/root/workspace/datasets/LIBERO_LeRobot_v3
8/root/workspace/hf-cache
9/root/workspace/logs
步骤一:确认预置环境
创建开发机时选择推荐镜像 ccr-registry.baidubce.com/aihc/rlinf:agentic-rlinf0.4-maniskill_libero_cosmos_torch211_cuda130_20261021。镜像已预置 RLinf 代码、cosmos3 + libero 的 venv 和 cosmos-framework,无需执行 git clone 或 install.sh,本步骤只做检查。检查失败说明镜像或 shell 环境不对,不要在 venv 里自行 pip install 升降级。
说明:镜像登录 shell 默认激活的是
/opt/venv/openvla,不是本文所用的环境。后续每个 shell 和启动脚本都要先source /root/workspace/RLinf/.venv/bin/activate。
1unset PYTHONPATH
2source /root/workspace/RLinf/.venv/bin/activate
3cd /root/workspace/RLinf
4# 先确认 LIBERO 配置存在,否则下面 import libero 会交互询问路径
5test -f /root/.libero/config.yaml && echo "libero config OK" || echo "libero config MISSING"
6
7which python; python --version
8python - <<'PY'
9import importlib, typing
10for name in ["torch", "transformers", "diffusers", "lerobot", "ray", "rlinf", "cosmos_framework", "libero"]:
11 try:
12 m = importlib.import_module(name)
13 print(f"OK {name:18s} {getattr(m, '__version__', '-')}")
14 except Exception as e:
15 print(f"FAIL {name:18s} {type(e).__name__}: {e}")
16import torch
17print("cuda", torch.cuda.is_available(), "devices", torch.cuda.device_count())
18print("typing.override", hasattr(typing, "override"))
19from diffusers import Cosmos3OmniTransformer # noqa: F401
20print("Cosmos3OmniTransformer OK")
21PY
22python -c 'import cosmos_framework, os; print(os.path.dirname(os.path.dirname(cosmos_framework.__file__)))'
23git -C /root/workspace/cosmos-framework rev-parse HEAD
24command -v uv hf
25nvidia-smi --query-gpu=index,name,driver_version,memory.total --format=csv,noheader
判定标准:
| 检查项 | 期望 | 不满足时 |
|---|---|---|
| 各包 import | 全部 OK |
当前 shell 不是 /root/workspace/RLinf/.venv,重新 source;仍失败说明镜像不对 |
| CUDA | cuda True devices 8 |
检查开发机 GPU 规格与驱动 |
typing.override |
True |
见下文 sitecustomize.py 说明 |
Cosmos3OmniTransformer |
OK |
训练不受影响;步骤八转换需要它 |
| cosmos-framework 位置与 commit | /root/workspace/cosmos-framework,0460be81f16883aa380e716dc6f58c1189481172 |
commit 不一致时,训练用的 experiment 与统计文件可能不同,以 RLinf 验证过的版本为准 |
uv、hf |
均有路径 | 缺 uv 时 cosmos hf_cli 无法启动;缺 hf 只影响 HF 下载,可改走 BOS |
/root/.libero/config.yaml |
存在 | 首次 import LIBERO 会交互询问路径,在 tmux 中会卡住 |
venv 是 Python 3.11,镜像在 site-packages 里放了一个 sitecustomize.py,补齐 3.12 才有的 typing.override,否则 cosmos_framework 无法导入。cosmos-framework 仓库根目录也有一个同名的 sitecustomize.py,把该目录加进 PYTHONPATH 会遮蔽 venv 里的那个,导致 cannot import name 'override' from 'typing'。不要把 cosmos-framework 根目录加进 PYTHONPATH,它已经以 editable 方式装进 venv,可以直接导入。
cosmos-framework 的 hf_cli 会以 uv run --locked 启动一个独立环境去读 HF 资产。百舸开发机全局设置了 UV_DEFAULT_INDEX(内部 PyPI 源),它与 cosmos 自带 uv.lock 的 registry 不一致,会报 uv.lock needs to be updated, but --locked。在后续所有步骤的 shell 中执行:
1unset UV_DEFAULT_INDEX
2# 复用一个持久的 hf_cli 环境;首次使用时由 uv 自动创建(约 29MB),之后直接复用
3export IMAGINAIRE_HF_CLI_ENVIRONMENT=/root/workspace/hf-cli-venv
步骤二:离线缓存 Qwen3-VL-8B-Instruct 和 Wan2.2 模型
SFT 与基座转换会从 HuggingFace 拉取三个模型资产。建议先全部下进 HF 本地缓存,再开离线模式(HF_HUB_OFFLINE=1),让 from_pretrained / hf download 直接读缓存,否则 worker 可能在网络重试处长时间卡住:
| 模型 | 用途 |
|---|---|
Qwen/Qwen3-VL-8B-Instruct(revision 0c351dd01ed87e9c1b53cbc748cba10e6187ff3b) |
tokenizer / VLM 配置;SFT 启动与 convert_model_to_dcp 重建模型时都会用到 |
Wan-AI/Wan2.2-TI2V-5B(Wan2.2_VAE.pth,revision 921dbaf3f1674a56f47e83fb80a34bac8a8f203e) |
Wan2.2 VAE;convert_model_to_dcp 重建模型时读取,SFT 通过 wan_vae_path 引用 |
Wan-AI/Wan2.2-TI2V-5B-Diffusers(vae/*) |
diffusers 格式 VAE;基座转换与 checkpoint 转换时使用 |
cosmos 的 hf_cli 按上表的固定 revision 解析 Qwen 与 Wan VAE。离线时缓存里必须存在以该 commit 命名的 snapshot 目录,refs/main 指向其他 commit 也找不到。
方式一:通过 HuggingFace 下载
直接下载进 HF 缓存(缓存布局自动正确),建议放入 tmux:
1mkdir -p /root/workspace/hf-cache /root/workspace/logs
2SESSION=hf_cache_download
3LOG=/root/workspace/logs/hf_cache_download.log
4
5cat > /tmp/download_hf_cache.sh <<'EOF'
6#!/usr/bin/env bash
7set -euo pipefail
8source /root/workspace/RLinf/.venv/bin/activate
9export HF_HOME=/root/workspace/hf-cache
10export HF_ENDPOINT=https://hf-mirror.com
11export HF_HUB_ENABLE_HF_TRANSFER=1
12{
13 date
14 hf download Qwen/Qwen3-VL-8B-Instruct \
15 --revision 0c351dd01ed87e9c1b53cbc748cba10e6187ff3b
16 hf download Wan-AI/Wan2.2-TI2V-5B Wan2.2_VAE.pth \
17 --revision 921dbaf3f1674a56f47e83fb80a34bac8a8f203e
18 hf download Wan-AI/Wan2.2-TI2V-5B-Diffusers --include "vae/*"
19 echo "download done"
20 date
21} 2>&1 | tee -a /root/workspace/logs/hf_cache_download.log
22EOF
23chmod +x /tmp/download_hf_cache.sh
24
25tmux new-session -d -s "$SESSION" /tmp/download_hf_cache.sh
hf download 的文件名参数必须紧跟仓库名,--revision 放在后面;顺序写反时会被当成另一个仓库,报找不到。
方式二:通过 BOS 下载
从 BOS 拷贝时,必须手工摆成 HF 缓存布局,否则离线解析不到:Qwen 与 Wan VAE 放进固定 revision 的 snapshot 目录;Wan Diffusers 只需 model_index.json 与 vae/,snapshot 用占位 commit;每个仓库都写 refs/main。
1mkdir -p /root/workspace/logs
2cat > /tmp/download_hf_cache_bos.sh <<'EOF'
3#!/usr/bin/env bash
4set -euo pipefail
5HUB=/root/workspace/hf-cache/hub
6QWEN_REV=0c351dd01ed87e9c1b53cbc748cba10e6187ff3b
7WAN_REV=921dbaf3f1674a56f47e83fb80a34bac8a8f203e
8FAKE_REV=0000000000000000000000000000000000000000
9QWEN_SNAP=$HUB/models--Qwen--Qwen3-VL-8B-Instruct/snapshots/$QWEN_REV
10WAN_SNAP=$HUB/models--Wan-AI--Wan2.2-TI2V-5B/snapshots/$WAN_REV
11WAND_SNAP=$HUB/models--Wan-AI--Wan2.2-TI2V-5B-Diffusers/snapshots/$FAKE_REV
12mkdir -p "$QWEN_SNAP" "$WAN_SNAP" "$WAND_SNAP/vae"
13for d in Qwen--Qwen3-VL-8B-Instruct:$QWEN_REV Wan-AI--Wan2.2-TI2V-5B:$WAN_REV Wan-AI--Wan2.2-TI2V-5B-Diffusers:$FAKE_REV; do
14 mkdir -p "$HUB/models--${d%%:*}/refs"
15 echo -n "${d##*:}" > "$HUB/models--${d%%:*}/refs/main"
16done
17B=bos:/aihc-models-bj
18{
19 date
20 bcecmd bos cp -r $B/Qwen/Qwen3-VL-8B-Instruct/ "$QWEN_SNAP/"
21 bcecmd bos cp $B/Wan-AI/Wan2.2-TI2V-5B/Wan2.2_VAE.pth "$WAN_SNAP/Wan2.2_VAE.pth"
22 bcecmd bos cp $B/Wan-AI/Wan2.2-TI2V-5B-Diffusers/model_index.json "$WAND_SNAP/model_index.json"
23 bcecmd bos cp -r $B/Wan-AI/Wan2.2-TI2V-5B-Diffusers/vae/ "$WAND_SNAP/vae/"
24 echo "download done"
25 date
26} 2>&1 | tee -a /root/workspace/logs/hf_cache_download.log
27EOF
28chmod +x /tmp/download_hf_cache_bos.sh
29
30tmux new-session -d -s hf_cache_download /tmp/download_hf_cache_bos.sh
如果 bcecmd 报 Object don't exist,先 bcecmd bos ls bos:/aihc-models-bj/<上级目录>/ 确认真实路径与结尾 /。
查看下载进度:
1tail -50 /root/workspace/logs/hf_cache_download.log
2du -sh /root/workspace/hf-cache/hub/* 2>/dev/null || true
验证缓存
下载结束(日志出现 download done)后,在离线模式下验证三项资产能被解析:
1source /root/workspace/RLinf/.venv/bin/activate
2export HF_HOME=/root/workspace/hf-cache
3export HF_HUB_OFFLINE=1
4hf download Qwen/Qwen3-VL-8B-Instruct --revision 0c351dd01ed87e9c1b53cbc748cba10e6187ff3b >/dev/null && echo "OK Qwen"
5hf download Wan-AI/Wan2.2-TI2V-5B Wan2.2_VAE.pth --revision 921dbaf3f1674a56f47e83fb80a34bac8a8f203e >/dev/null && echo "OK Wan VAE"
6hf download Wan-AI/Wan2.2-TI2V-5B-Diffusers --include "vae/*" >/dev/null && echo "OK Wan Diffusers"
三行都输出 OK 再继续;缺哪一项,只对该项改用另一种方式补齐。
Wan2.2_VAE.pth 固定了 revision,因此它在缓存中的路径是确定的,后面训练配置里的 wan_vae_path 直接指向这个文件:
1WAN_VAE=/root/workspace/hf-cache/hub/models--Wan-AI--Wan2.2-TI2V-5B/snapshots/921dbaf3f1674a56f47e83fb80a34bac8a8f203e/Wan2.2_VAE.pth
2ls -lh "$WAN_VAE"
如果 ls 找不到文件,用 find /root/workspace/hf-cache -name Wan2.2_VAE.pth 确认实际路径。
步骤三:准备基座 DCP 权重
Cosmos3 SFT 从基座模型 Cosmos3-Nano 热启动,只训练动作头及生成分支。actor.model.model_path 必须指向一个 DCP 格式 的基座权重目录。
3.1 下载 Cosmos3-Nano(diffusers 格式)
HuggingFace 上的 nvidia/Cosmos3-Nano 是 diffusers/safetensors 格式(model_index.json + transformer/*.safetensors 分片 + vae/ + text_tokenizer/ 等),不是 DCP,SFT 不能直接加载。基座下载到普通目录即可:
1source /root/workspace/RLinf/.venv/bin/activate
2export HF_ENDPOINT=https://hf-mirror.com
3export HF_HUB_ENABLE_HF_TRANSFER=1
4mkdir -p /root/workspace/models
5
6hf download nvidia/Cosmos3-Nano \
7 --local-dir /root/workspace/models/Cosmos3-Nano
也可以从 BOS 拉取(约 33GB):
1bcecmd bos cp -r bos:/aihc-models-bj/nvidia/Cosmos3-Nano/ /root/workspace/models/Cosmos3-Nano/
验证目录:
1ls /root/workspace/models/Cosmos3-Nano
2ls /root/workspace/models/Cosmos3-Nano/transformer | head
3.2 转成 DCP
为什么必须转换:cosmos3 的基座加载器 _load_base_weights 通过 _is_safetensors_checkpoint 判断格式,它只检查路径顶层是否有 *.safetensors 文件(非递归)。diffusers 目录把分片放在 transformer/ 子目录,顶层没有,于是被当成 DCP 去加载,而它又不是 DCP,加载失败。
本步会重建整个 OmniMoTModel,期间经 cosmos 的 hf_cli 读取 Qwen3-VL-8B-Instruct(tokenizer)和 Wan2.2 VAE。步骤二已经把它们下进缓存,这里开离线模式运行:
1source /root/workspace/RLinf/.venv/bin/activate # 必须是训练用的同一个 venv
2export HF_HOME=/root/workspace/hf-cache
3export HF_HUB_OFFLINE=1
4export TRANSFORMERS_OFFLINE=1
5unset UV_DEFAULT_INDEX
6export IMAGINAIRE_HF_CLI_ENVIRONMENT=/root/workspace/hf-cli-venv
7
8python -m cosmos_framework.scripts.convert_model_to_dcp \
9 --checkpoint-path /root/workspace/models/Cosmos3-Nano \
10 --no-use-ema-weights \
11 -o /root/workspace/models/Cosmos3-Nano-DCP 2>&1 | tee /root/workspace/logs/convert_base_dcp.log
注意事项:
--checkpoint-path既接受模型名(触发 HF 下载),也接受本地 diffusers 目录。离线机器请指向本地路径。- DCP 的
.metadata是 pickle 序列化的,与保存时的 Python 版本绑定。convert_model_to_dcp必须在与训练相同的 venv 里运行,产出的 DCP 才能在训练时加载。可以在目录名上带 Python 版本后缀(如Cosmos3-Nano-DCP-py311)以免混用。 - 报
uv.lock needs to be updated, but --locked时,确认已unset UV_DEFAULT_INDEX(见步骤一)。 - 报
LocalEntryNotFoundError、OfflineModeIsEnabled或 tokenizervocab_file为 None,说明 HF 缓存缺资产或 snapshot 名不是固定 revision,回到步骤二的「验证缓存」。
验证 DCP 目录:
1ls -la /root/workspace/models/Cosmos3-Nano-DCP | head
2find /root/workspace/models/Cosmos3-Nano-DCP -maxdepth 2 -name .metadata
3du -sh /root/workspace/models/Cosmos3-Nano-DCP
能找到 .metadata 且目录下有 *.distcp 分片(实测约 29GB),说明转换成功。如果 .metadata 不在顶层而在某个子目录中,model_path 应指向包含 .metadata 的那一级目录。
步骤四:准备 LIBERO 数据
训练数据使用原始 LeRobot v3 布局(含 meta/、data/)的 LIBERO 数据集,通过 data.train_data_paths 指定。可从 HuggingFace 数据集 nvidia/LIBERO_LeRobot_v3(20 FPS,与 cosmos-framework 自带的归一化统计对应)下载。hf-mirror 对 datasets 接口返回 308,下载时需要直连 huggingface.co:
1source /root/workspace/RLinf/.venv/bin/activate
2unset HF_ENDPOINT
3mkdir -p /root/workspace/datasets/LIBERO_LeRobot_v3
4tmux new-session -d -s libero_data \
5 'hf download nvidia/LIBERO_LeRobot_v3 --repo-type dataset \
6 --local-dir /root/workspace/datasets/LIBERO_LeRobot_v3 \
7 2>&1 | tee -a /root/workspace/logs/libero_data_download.log'
数据集还包含 libero_90,libero_all 训练只用 spatial / object / goal / 10 四个 suite;四个目录齐全后即可开始训练,libero_90 可以继续在后台下载。连接中断时重跑同一条命令,已完成的文件会跳过。
data.data_type 决定使用哪个 cosmos-framework experiment:
| data_type | cosmos experiment | 说明 |
|---|---|---|
libero_all |
action_policy_libero_all_nano |
LIBERO 全部 task suite,默认配置 |
libero_10 |
action_policy_libero_nano |
只训练 libero_10 |
data_type 不填时会按目录结构自动判断:train_data_paths 下同时存在 libero_spatial、libero_object、libero_goal、libero_10 四个子目录时识别为 libero_all,否则按 libero_10 处理。建议在配置中显式写明,避免目录不全时被静默降级成 libero_10。
libero_all 的推荐目录布局:
1/root/workspace/datasets/LIBERO_LeRobot_v3
2├── libero_spatial/ # 每个子目录都是 LeRobot v3 布局
3│ ├── meta/
4│ └── data/
5├── libero_object/
6├── libero_goal/
7└── libero_10/
检查数据目录:
1DATA=/root/workspace/datasets/LIBERO_LeRobot_v3
2for SUITE in libero_spatial libero_object libero_goal libero_10; do
3 echo "===== $SUITE ====="
4 ls "$DATA/$SUITE" 2>/dev/null || echo "MISSING: $DATA/$SUITE"
5done
动作转换不需要预处理数据集。rlinf/data/datasets/cosmos3/dataloader.py 调用 cosmos-framework 的 experiment,加载时在线把原始 7 维动作按 frame_wise_relative + rot6d 转成 10 维,并做 quantile_rot 归一化。dataloader 会把 LIBERO_ROOT 环境变量设为 train_data_paths,训练时无需手动导出。
归一化统计文件 libero_native_frame_wise_relative_rot6d.json 由 cosmos-framework 预先算好,评测侧通过 action_stats_path 引用。训练与评测必须使用同一套 recipe 和同一个 stats 文件,否则去归一化会错位,表现为动作混乱。
步骤五:确认训练配置
examples/sft/config/libero_sft_cosmos3.yaml 的核心配置如下:
1defaults:
2 - hybrid_engines/fsdp@actor.fsdp_config
3 - model/cosmos3@actor.model # 引入 examples/sft/config/model/cosmos3.yaml
4
5cluster:
6 num_nodes: 1
7 component_placement:
8 actor: all
9
10runner:
11 task_type: sft
12 max_steps: 5000
13 save_interval: 500
14 log_interval: 50
15 resume_dir: null
16
17data:
18 train_data_paths: path/to/LIBERO_LeRobot_v3
19 data_type: "libero_all"
20 num_workers: 4
21 prefetch_factor: 4
22 val_ratio: 0.01
23
24actor:
25 training_backend: "fsdp"
26 micro_batch_size: 32
27 global_batch_size: 2048
28 model:
29 precision: bf16
30 load_to_device: false
31 ema_enabled: true
32 model_path: path/to/Cosmos3-Nano-DCP
33 wan_vae_path: path/to/Wan2.2-TI2V-5B/Wan2.2_VAE.pth
34 max_samples_per_batch: 32
35 optim:
36 lr: 5.0e-05
37 lr_scheduler: "lambda_linear"
38 lr_warmup_steps: 500
39 clip_grad: 1.0
40 weight_decay: 0.05
41 fsdp_config:
42 strategy: "fsdp2"
43 gradient_checkpointing: False
44 save_full_model_weights: True
原始 YAML 中三个路径仍是占位符:
1data.train_data_paths: path/to/LIBERO_LeRobot_v3
2actor.model.model_path: path/to/Cosmos3-Nano-DCP
3actor.model.wan_vae_path: path/to/Wan2.2-TI2V-5B/Wan2.2_VAE.pth
实际启动时通过 Hydra CLI override 注入真实路径:
1data.train_data_paths=/root/workspace/datasets/LIBERO_LeRobot_v3
2actor.model.model_path=/root/workspace/models/Cosmos3-Nano-DCP
3actor.model.wan_vae_path=${WAN_VAE}
官方入口 examples/sft/run_vla_sft.sh 只接收配置名一个参数,不透传额外的 Hydra override,因此本文使用直接 Python 入口启动。如果坚持用 run_vla_sft.sh,需要先把上面三个路径改进 YAML。
关键字段说明
大多数字段照抄 libero_sft_cosmos3.yaml 即可,真正需要关注的如下:
| 字段 | 说明 |
|---|---|
actor.model.model_path |
Cosmos3-Nano 基座权重目录(DCP) |
actor.model.wan_vae_path |
Wan2.2 VAE 权重文件(Wan2.2_VAE.pth) |
actor.model.load_to_device |
必须为 false。net(bf16 约 27GB)+ net_ema(fp32 约 54GB)≈ 81GB,若先 model.to(device),单张 80GB 卡在 FSDP2 fully_shard 分片之前就会 OOM |
actor.model.ema_enabled |
Cosmos3 power-EMA(rate=0.1),每个优化步更新 net_ema,占一份 fp32 网络显存;仍 OOM 时可临时设 false |
actor.model.max_samples_per_batch |
cosmos PackingDataLoader 每个 packed batch 的样本数(recipe 默认 128,RLinf 配置为 32) |
actor.micro_batch_size / global_batch_size |
每卡微批与全局批,默认 32 / 2048 |
actor.optim.lr / lr_warmup_steps |
学习率与 warmup,默认 5e-5 / 500。动作头另有 5 倍 LR 倍率(model/cosmos3.yaml 的 lr_multipliers) |
runner.max_steps / save_interval |
训练步数与存档间隔,默认 5000 / 500 |
runner.resume_dir |
断点续训时指向某个 global_step_<N> 目录 |
model/cosmos3.yaml 中与热启动相关的默认值(一般无需修改):
keys_to_skip_loading:跳过net_ema.和基座自带的 DROID 8 维动作头(action2llm、llm2action、action_modality_embed、action_pos_embed),这些参数重新初始化,以适配 LIBERO 的 10 维动作。keys_to_select:只训练moe_gen、time_embedder、vae2llm、llm2vae和动作头,理解塔冻结。disable_cosmos_parallelism: true:关闭 cosmos 内部的 FSDP/CP/CFGP,由 RLinf 的 FSDP2 统一分片。
步骤六:启动训练
建议使用 tmux 启动训练,避免 SSH 断开导致任务中止。
1cd /root/workspace/RLinf
2
3SESSION=cosmos3_sft
4CONFIG=libero_sft_cosmos3
5MODEL=/root/workspace/models/Cosmos3-Nano-DCP
6DATA=/root/workspace/datasets/LIBERO_LeRobot_v3
7WAN_VAE=/root/workspace/hf-cache/hub/models--Wan-AI--Wan2.2-TI2V-5B/snapshots/921dbaf3f1674a56f47e83fb80a34bac8a8f203e/Wan2.2_VAE.pth
8START_SCRIPT=/tmp/start_cosmos3_sft.sh
9
10LOG_DIR=/root/workspace/logs/$(date +%Y%m%d-%H:%M:%S)-${CONFIG}
11LOG_FILE=${LOG_DIR}/run_embodiment.log
12mkdir -p "$LOG_DIR"
13
14cat > "$START_SCRIPT" <<EOF
15#!/usr/bin/env bash
16set -e
17cd /root/workspace/RLinf
18source /root/workspace/RLinf/.venv/bin/activate
19export EMBODIED_PATH=/root/workspace/RLinf/examples/sft
20export REPO_PATH=/root/workspace/RLinf
21export PYTHONPATH=/root/workspace/RLinf
22export MUJOCO_GL=egl
23export PYOPENGL_PLATFORM=egl
24export HF_HOME=/root/workspace/hf-cache
25export HF_HUB_OFFLINE=1
26export TRANSFORMERS_OFFLINE=1
27export TOKENIZERS_PARALLELISM=false
28unset UV_DEFAULT_INDEX
29export IMAGINAIRE_HF_CLI_ENVIRONMENT=/root/workspace/hf-cli-venv
30CMD="python /root/workspace/RLinf/examples/sft/train_vla_sft.py --config-path /root/workspace/RLinf/examples/sft/config/ --config-name ${CONFIG} runner.logger.log_path=${LOG_DIR} data.train_data_paths=${DATA} actor.model.model_path=${MODEL} actor.model.wan_vae_path=${WAN_VAE}"
31echo "\$CMD" > ${LOG_FILE}
32echo "LOG_DIR=${LOG_DIR}" >> ${LOG_FILE}
33echo "MODEL=${MODEL}" >> ${LOG_FILE}
34\$CMD 2>&1 | tee -a ${LOG_FILE}
35EOF
36
37chmod +x "$START_SCRIPT"
38
39if tmux has-session -t "$SESSION" 2>/dev/null; then
40 tmux kill-session -t "$SESSION"
41fi
42
43tmux new-session -d -s "$SESSION" "$START_SCRIPT"
44printf "session=%s\nlog=%s\n" "$SESSION" "$LOG_FILE"
训练流程:读取 libero_sft_cosmos3.yaml,在每张 GPU 上用 FSDP2 分片 Cosmos3 模型并训练;checkpoint 每 save_interval 步存入 ${LOG_DIR}/libero_sft_cosmos3/checkpoints/global_step_<N>/。
如需只训练 libero_10,在 CMD 末尾追加 data.data_type=libero_10,并把 DATA 指向 libero_10 的 LeRobot 数据目录。
首次运行建议先做一次冒烟:在 CMD 末尾追加 runner.max_steps=10 runner.save_interval=10 runner.log_interval=1,约 15 分钟即可走完「加载基座 → 训练 → 存档」全链路,并得到一份可用于验证转换流程的 global_step_10 checkpoint。
启动后约 8 分钟验证(基座 DCP 加载在启动后约 6 分钟才出现,此前是 Ray 启动和 8 个 rank 在 CPU 上建模型):
tmux ls中存在cosmos3_sft。- 日志第一行是完整启动命令,Hydra 打印的配置中三个路径已替换为真实路径。
- Ray 启动并识别 1 node / 8 accelerators。
- 日志中出现
Cosmos3: loading DCP base into cosmos3_model from <MODEL>。 - 日志中出现
Cosmos3 SFT dataloader (cosmos PackingDataLoader): data_type='libero_all' -> experiment='action_policy_libero_all_nano'。各 rank 打印的 dataloader 长度不同属正常。 - 出现
Global Step进度条。 - 无
Traceback、CUDA out of memory、worker died,也没有长时间停在 HuggingFace 请求上。
断点续训
设置 runner.resume_dir 指向某个 global_step_<N> 目录后重跑,global_step 会从目录名解析:
1runner.resume_dir=/root/workspace/logs/<run-dir>/libero_sft_cosmos3/checkpoints/global_step_1000
停止训练
优先通过 tmux 停止,再确认残留进程:
1if tmux has-session -t cosmos3_sft 2>/dev/null; then
2 tmux kill-session -t cosmos3_sft
3fi
4sleep 8
5
6ps -eo pid,stat,pcpu,pmem,rss,args --sort=-pcpu | \
7 grep -E 'train_vla_sft.py|ray::FSDPVlaSftWorker' | grep -v grep || true
如果仍有残留的 Ray worker,在确认本机没有其他用户的 Ray 任务(ray status)后再执行 ray stop。
步骤七:监控训练
查看主日志:
1tail -f /root/workspace/logs/<run-dir>/run_embodiment.log
提取关键进展:
1grep -nE 'Cosmos3: loading|PackingDataLoader|Global Step|Traceback|Exception|out of memory|worker died' \
2 /root/workspace/logs/<run-dir>/run_embodiment.log | tail -100
Global Step 是 tqdm 进度条,postfix 中带 time/step 与 train/* 指标。tqdm 用 \r 刷新,单行可能很长,只看最后几次刷新:
1tr '\r' '\n' < /root/workspace/logs/<run-dir>/run_embodiment.log | grep 'Global Step' | tail -3
查看 GPU:
1watch -n 2 'nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits'
初始化阶段模型留在 CPU,显存几乎不增长、容器内存快速上涨属于正常现象;FSDP2 分片上卡后各卡显存会同步上升。可以同时观察 cgroup 内存:
1watch -n 2 'cat /sys/fs/cgroup/memory.current; cat /sys/fs/cgroup/memory.max; cat /sys/fs/cgroup/memory.events'
查看已保存的 checkpoint:
1ls /root/workspace/logs/<run-dir>/libero_sft_cosmos3/checkpoints/
2du -sh /root/workspace/logs/<run-dir>/libero_sft_cosmos3/checkpoints/* 2>/dev/null
每个 global_step_<N> 约 196GB:actor/dcp_checkpoint/(约 111GB,含优化器状态,用于续训)与 actor/model_state_dict/full_weights.pt(约 85GB,用于转换评测)。存档前用 df -h 确认剩余空间,空间紧张时调大 runner.save_interval。
步骤八:转换 Checkpoint
SFT 产出的 full_weights.pt 是 FSDP2 格式,不能直接评测。Cosmos3 评测默认使用 diffusers 组件目录 model_diffusers(含 model_index.json / transformer/ / vae/ / text_tokenizer / scheduler/)。toolkits/cosmos3/convert_checkpoint.py 依次完成四步转换:
| 步骤 | 输入 → 输出 | 说明 | 实测占用 |
|---|---|---|---|
| 1 | full_weights.pt → model_safetensors |
去掉 omni. 前缀 |
~85GB |
| 2 | model_safetensors → model_dcp |
存成 DCP(torch.distributed.checkpoint) |
~85GB |
| 3 | model_dcp → model_hf |
调用 cosmos_framework.scripts.export_model |
~30GB |
| 4 | model_hf → model_diffusers |
调用 cosmos_framework.scripts.convert_model_to_diffusers,拆成 diffusers 组件并修正 _class_name |
~30GB |
四步产物合计约 230GB,$OUT 所在盘需预留足够空间;脚本会把整份权重读进内存。实测整个转换约 8 分钟(步骤 1–2 约 1.5 分钟,步骤 3–4 约 4 分钟,其余为加载)。
转换须在 cosmos-framework 环境下进行:transformers 4.57.x + diffusers 0.39.0(含 Cosmos3OmniTransformer)。更高版本的 transformers 会在 save_pretrained 处崩溃,且其配套 diffusers 缺少 cosmos3 相关类。先确认当前环境版本:
1python -c 'import transformers, diffusers; print(transformers.__version__, diffusers.__version__)'
2python -c 'from diffusers import Cosmos3OmniTransformer; print("Cosmos3OmniTransformer ok")'
版本不符时,请单独准备一个满足上述版本的 cosmos-framework 环境执行转换,不要直接在训练 venv 中升降级。使用推荐镜像时,训练 venv 已满足要求,实测可直接在其中完成转换。
执行转换。脚本默认的 --config-file cosmos_framework/configs/base/config.py 是相对 cosmos-framework 根目录的路径,因此要在该目录下运行;PYTHONPATH 只放 RLinf,不放 cosmos-framework 根目录(原因见步骤一的 sitecustomize.py 说明):
1source /root/workspace/RLinf/.venv/bin/activate
2COSMOS_ROOT=$(python -c 'import cosmos_framework, pathlib; print(pathlib.Path(cosmos_framework.__file__).resolve().parent.parent)')
3
4# SFT 产出的 RLinf checkpoint(omni.net.* 前缀)
5export SRC=/root/workspace/logs/<run-dir>/libero_sft_cosmos3/checkpoints/global_step_5000/actor/model_state_dict/full_weights.pt
6# 转换工作目录,四步的中间产物都写在这里
7export OUT=/root/workspace/models/cosmos3-libero-sft-step5000
8# export_model 需要读取 LIBERO 数据集路径
9export LIBERO_ROOT=/root/workspace/datasets/LIBERO_LeRobot_v3
10export HF_HOME=/root/workspace/hf-cache
11export HF_HUB_OFFLINE=1
12export TRANSFORMERS_OFFLINE=1
13export PYTHONPATH=/root/workspace/RLinf
14unset UV_DEFAULT_INDEX
15mkdir -p "$OUT"
16
17cd "$COSMOS_ROOT"
18python /root/workspace/RLinf/toolkits/cosmos3/convert_checkpoint.py --src "$SRC" --out "$OUT" \
19 2>&1 | tee /root/workspace/logs/convert_cosmos3_ckpt.log
参数说明:
--use-ema-weights:保留 EMA 权重,默认丢弃。--experiment:默认action_policy_libero_all_nano,对应data_type=libero_all。如果训练时用的是libero_10,应改为--experiment action_policy_libero_nano,与训练 recipe 保持一致。- 完整参数见
python toolkits/cosmos3/convert_checkpoint.py --help。
脚本结束时会打印 Done. model_diffusers -> <OUT>/model_diffusers。验证产物:
1ls "$OUT"
2ls "$OUT/model_diffusers"
3cat "$OUT/model_diffusers/model_index.json"
model_diffusers 下应有 model_index.json、transformer/、vae/、text_tokenizer、scheduler/,model_index.json 的 _class_name 为 Cosmos3OmniDiffusersPipeline。
步骤 3 或 4 失败、而 $OUT/model_dcp/model/.metadata 已经存在时,修好原因后无需重读 85GB 的 full_weights.pt,可以只续跑后两步。先删除失败留下的 $OUT/model_hf / $OUT/model_diffusers(如有),然后在与上面相同的环境变量和 cd "$COSMOS_ROOT" 下执行:
1python - <<'PY'
2import os
3import sys
4
5sys.path.insert(0, "/root/workspace/RLinf/toolkits/cosmos3")
6import convert_checkpoint as cc
7
8out = os.environ["OUT"]
9dcp_dir = os.path.join(out, "model_dcp", "model")
10assert os.path.exists(os.path.join(dcp_dir, ".metadata")), f"expected DCP metadata under {dcp_dir}"
11# 最后一个参数 True 表示丢弃 EMA,与默认行为一致;libero_10 训练改用 "action_policy_libero_nano"
12hf_dir = cc.step3_export_to_hf(dcp_dir, out, cc.DEFAULT_CONFIG, cc.DEFAULT_EXPERIMENT, True)
13print(f"Done. model_diffusers -> {cc.step4_convert_to_diffusers(hf_dir, out)}")
14PY
转换成功后,model_safetensors、model_dcp、model_hf 三个中间目录(约 200GB)可以按需删除,只保留 model_diffusers。
转换得到的 model_diffusers 即 Cosmos3 SGLang 评测 中的 rollout.model.model_path。评测侧的 action_stats_path 必须指向与训练相同的 libero_native_frame_wise_relative_rot6d.json。
步骤九:启动 TensorBoard(可选)
启动 TensorBoard:
1cd /root/workspace/RLinf
2source /root/workspace/RLinf/.venv/bin/activate
3tmux new-session -d -s tensorboard_rlinf \
4 'cd /root/workspace/RLinf && tensorboard --logdir /root/workspace/logs --port 6006 --host 127.0.0.1'
本地访问:
1ssh -p <port> -L 6006:127.0.0.1:6006 -L 18265:127.0.0.1:8265 root@<host>
浏览器打开:
1http://127.0.0.1:6006
Ray Dashboard 远端默认监听 127.0.0.1:8265,按上面的端口转发后在本地打开 http://127.0.0.1:18265,可查看 SFT worker 状态与 CPU/GPU 使用情况。远端验证:
1curl -s http://127.0.0.1:8265/api/version
步骤十:解析 TensorBoard 指标
使用脚本读取 TensorBoard scalar:
1cd /root/workspace/RLinf
2source /root/workspace/RLinf/.venv/bin/activate
3export TF_CPP_MIN_LOG_LEVEL=3
4
5python - <<'PY'
6from pathlib import Path
7from tensorboard.backend.event_processing.event_accumulator import EventAccumulator
8run = Path('/root/workspace/logs/<run-dir>')
9for event in sorted((run / 'tensorboard').rglob('events.out.tfevents.*')):
10 print(f'EVENT {event.name} size={event.stat().st_size}')
11 ea = EventAccumulator(str(event), size_guidance={'scalars': 0})
12 ea.Reload()
13 for tag in sorted(ea.Tags().get('scalars', [])):
14 vals = ea.Scalars(tag)
15 if vals:
16 v = vals[-1]
17 print(f'{tag}\tcount={len(vals)}\tstep={v.step}\tvalue={v.value}')
18PY
重点关注:
| 指标 | 含义 |
|---|---|
train/loss |
SFT 训练损失,应随步数稳定下降 |
train/grad_norm |
梯度范数(clip_grad=1.0),持续偏大或出现 NaN 需检查数据与学习率 |
train/learning_rate |
当前学习率,前 500 步 warmup 后按 lambda_linear 变化 |
time/training |
每个 step 的训练耗时 |
官方文档未给出 loss 曲线和耗时的参考值。下表是 8 × 183GB GPU、libero_all、默认批大小下 10 步冒烟的实测值,只用于判断量级是否正常,不代表收敛结果:
| 指标 | 实测值 |
|---|---|
train/loss |
15.7 → 15.5 |
train/flow_matching_loss_action |
1.42 → 1.41 |
train/flow_matching_loss_vision |
约 0.144 |
train/grad_norm |
约 14.8 |
train/learning_rate |
约 1e-6(warmup 初期) |
time/step |
首步约 110 秒,稳态约 28.5 秒 |
| 显存 | 每卡 76–79GB |
| 启动到首步 | 约 7 分钟(基座 DCP 加载约 6 分钟) |
按稳态 28.5 秒/步估算,默认 5000 步约需 40 小时(不含存档)。日志中参数组学习率打印为 [5e-05, 0.00025] 属正常。指标含义可参考 训练指标。
特殊说明:action 转换 rot6d 10 维 vs axis-angle 7 维
Cosmos3 内部用 10 维 rot6d 表示动作,LIBERO 环境用 7 维 axis-angle,这是理解整条训练与评测链路的关键:
| 表示 | 维度 | 组成 |
|---|---|---|
raw_action_dim(模型侧) |
10 | 3 平移 + 6 rot6d + 1 夹爪 |
action_dim(环境侧) |
7 | 3 平移 + 3 axis-angle + 1 夹爪 |
使用 rot6d 是因为 axis-angle 在 ±π 处不连续,扩散模型难以回归;rot6d(旋转矩阵前两列)连续,更容易学习。
训练时 cosmos 的数据加载器在线把 LIBERO 的 7 维动作转成 10 维 rot6d(见步骤四);推理时再把模型输出的 10 维 rot6d 转回 7 维交给环境执行。基座 Cosmos3-Nano 自带的是 DROID 8 维动作头,因此训练时通过 keys_to_skip_loading 跳过这部分参数并重新初始化。
常见问题处理
| 问题 | 解决方案 |
|---|---|
路径仍是 path/to/... |
启动命令必须追加 data.train_data_paths=... actor.model.model_path=... actor.model.wan_vae_path=...;run_vla_sft.sh 不透传 override |
| 初始化 OOM(单卡 80GB 也爆) | 确认 actor.model.load_to_device: false;仍 OOM 时可临时设 actor.model.ema_enabled=false |
| 初始化阶段容器内存被打满 | 8 个 rank 同时在 CPU 上构建约 81GB 的模型,检查 /sys/fs/cgroup/memory.max 与 memory.events,优先调大容器内存 |
| 基座加载失败 / 格式判断错误 | model_path 指向的是 diffusers 目录而非 DCP;按步骤三转换后再使用 |
| DCP 加载报 pickle / 反序列化错误 | DCP 在另一个 Python 版本的环境中生成;在训练 venv 中重新执行 convert_model_to_dcp |
| 加载报动作头 shape 不匹配 | 确认 keys_to_skip_loading 跳过了基座 DROID 8 维动作头(model/cosmos3.yaml 默认已配置) |
| HuggingFace 拉取卡住 | 离线 + 本地缓存:HF_HUB_OFFLINE=1、TRANSFORMERS_OFFLINE=1,HF_HOME 指向已下载好的缓存目录 |
cannot import name 'override' from 'typing' |
多半是 PYTHONPATH 含 cosmos-framework 根目录,其自带的 sitecustomize.py 遮蔽了 venv 里的补丁;也可能是当前 shell 没激活 /root/workspace/RLinf/.venv。先把 PYTHONPATH 只设为 RLinf 目录重试。checkpoint 转换在步骤 3 报此错时,按步骤八只续跑后两步 |
uv.lock needs to be updated, but --locked |
全局 UV_DEFAULT_INDEX 与 cosmos hf_cli 的 lock 不一致;unset UV_DEFAULT_INDEX 后重跑 |
离线模式下找不到 Qwen3-VL / Wan VAE,或 tokenizer vocab_file 为 None |
缓存 snapshot 目录名不是固定 revision(常见于 BOS 拷贝成普通目录);按步骤二方式二的布局重放,并用「验证缓存」确认 |
hf download 探测单个文件报找不到 |
参数顺序写反;文件名紧跟仓库名,--revision 放后面 |
| 下载 LIBERO 数据集报 308 | hf-mirror 不支持 datasets 接口;unset HF_ENDPOINT 后直连 huggingface.co |
存档时 No space left on device |
每个 checkpoint 约 196GB;调大 runner.save_interval、清理无用的旧 step,或把日志目录放到大容量盘 |
数据被识别为 libero_10 |
train_data_paths 下缺少某个 suite 子目录;补齐数据或显式设置 data.data_type |
转换在 save_pretrained 处崩溃 |
transformers 版本过高;切到 transformers 4.57.x + diffusers 0.39.0 的 cosmos-framework 环境 |
| 评测动作混乱 / loss 异常 | 核对训练与评测两侧 action_normalization(quantile_rot)与统计文件同源,转换时的 --experiment 与训练 data_type 一致 |
评价此篇文章
