在 AIStudio 训练任务中使用 RLinf 复现 ManiSkill PPO MLP
本教程使用一个三层 MLP,根据机械臂关节、末端执行器和方块位置等低维状态控制 Panda 机械臂完成 PickCube-v1 任务。MLP 从随机权重开始训练,轨迹由仿真环境在线生成,因此不需要下载预训练模型或静态数据集。
EmbodiChain CartPole 适合用来熟悉 RLinf 的基本任务流程;这个场景进一步把 GPU 机器人仿真加入训练链路。相比 ManiSkill PPO OpenVLA 场景,它不需要准备大模型,也不涉及视觉输入和多 GPU 放置,因此更容易单独判断 SAPIEN、Vulkan/EGL、在线采样和 PPO 更新是否正常。
实战目标
您将创建一个单 Worker、单 GPU 的训练任务。任务使用两个并行环境采集短轨迹,完成一次 PPO 更新,再运行一次短时评估。
本教程聚焦 AIStudio 的 GPU 机器人仿真运行时,是引入大模型和多 GPU 前的基础检查。
以下结果全部出现时,说明本次短时验证通过:
PickCube-v1在 GPU 上创建并完成一次预检 step,状态向量和动作向量的末维分别为42和8。- RLinf 在一个 Worker 内启动本地 Ray,并把 environment、rollout 和 actor 放到 GPU
0。 - 两个训练环境各运行最多 8 个时间步,日志中的
num_trajectories不小于2。 - PPO 完成一个 global step,
actor/grad_norm为有限值。 - 两个评估环境完成短时评估,并在共享存储中生成评估视频。
- 任务正常退出,Hydra 配置、训练日志、TensorBoard 文件、评估视频和验收结果都可以从共享存储回读。
本次短时验证只检查仿真和训练流程。随机初始化的 MLP 可能取得 0 成功率;策略收敛需要更长时间的训练和独立评估。
场景信息
创建任务时使用以下设置。
示例可用区:宁夏 B。您可以选择其他可用区,但镜像、GPU 资源和共享存储必须位于同一可用区。
RLinf:
- commit:
6f46f36ad790fec0de1efa86aaa8f9cd3e10d3f4 - 配置:
examples/embodiment/config/maniskill_ppo_mlp.yaml - 入口:
examples/embodiment/train_embodied_agent.py
- commit:
镜像:将 Docker Hub 镜像
rlinf/rlinf:agentic-rlinf0.4-maniskill_libero导入自己的租户镜像仓库。模型和数据:MLP 从头训练,不需要预训练模型或静态训练数据。轨迹由
PickCube-v1在任务运行期间生成。仿真资产:使用镜像内置的 ManiSkill 和 SAPIEN 资产;任务启动时只建立资产链接,不下载文件。
资源来源:Spot 资源。
分布式框架:单机;RLinf 在 Worker 内启动本地 Ray,不使用 AIStudio 托管 Ray。
Worker 拓扑:1 个 Worker、1 个节点、1 张 A100-SXM4-80GB。其他 GPU SKU 需要重新验证。
共享存储:挂载到
/mnt/rlinf-reproduction;代码和工具作为固定输入,运行结果写入runs/maniskill-ppo-mlp/。运行范围:2 个训练环境和 2 个评估环境,每个环境最多运行 8 个时间步,完成 1 个 PPO global step 和 2 个 update epoch,不保存 checkpoint。本文先用缩减后的配置检查仿真和训练流程,再决定是否扩大规模。
本场景在 AIStudio 中怎样运行
本场景选择“单机”,因为一个 Worker 和一张 GPU 已能容纳全部组件。AIStudio 创建 Worker、注入 GPU 驱动并挂载共享存储;RLinf 在 Worker 中启动本地 Ray,再运行 ManiSkill 环境、MLP rollout 和 PPO actor。
AIStudio 单机训练任务
└── Worker 0 / GPU 0
└── RLinf 本地 Ray
├── EnvGroup / ManiSkill PickCube-v1
├── RolloutGroup / MLP policy
└── ActorGroup / PPO actor-critic状态、动作、奖励和轨迹都在任务运行期间产生,不需要从共享存储读取训练数据。Worker 从共享存储读取固定版本的 RLinf 代码,并把日志、Hydra 配置、TensorBoard 文件和评估视频写入本次 RUN_ID 的输出目录。
开始前准备
创建 GPU 任务前,使用挂载同一共享存储的 AICoder 或开发机准备镜像、RLinf checkout 和辅助脚本。本文使用宁夏 B;选择其他可用区时,也需要把这些内容准备到任务所在可用区。
在目标可用区准备容器镜像
本场景使用 Docker Hub 镜像 rlinf/rlinf:agentic-rlinf0.4-maniskill_libero。
重要
请使用自己的镜像地址
下面的地址属于本示例使用的私有租户,不是公共镜像地址,不能直接用于其他租户。请先把上游镜像上传或导入自己所选可用区的镜像仓库,再将地址中的 te-b905754427352261 替换为自己的 <tenant-id>。
cr.infini-ai.com/te-b905754427352261/rlinf:agentic-rlinf0.4-maniskill_libero在镜像中心确认自己的 tag 位于任务所在可用区,并且“可用服务”包含任务。本文不展开镜像下载、导入或上传操作;具体方法见准备并验证容器镜像。
在共享存储中准备固定版本的 RLinf 代码
在挂载目标共享存储的 AICoder 或开发机中执行:
set -euo pipefail
export WORK_ROOT=/mnt/rlinf-reproduction
export RLINF_COMMIT=6f46f36ad790fec0de1efa86aaa8f9cd3e10d3f4
export RLINF_ROOT="$WORK_ROOT/code/$RLINF_COMMIT"
findmnt -T "$WORK_ROOT"
test -w "$WORK_ROOT"
mkdir -p "$WORK_ROOT/code"
if [ ! -e "$RLINF_ROOT" ]; then
git clone https://github.com/RLinf/RLinf.git "$RLINF_ROOT"
git -C "$RLINF_ROOT" checkout --detach "$RLINF_COMMIT"
fi
test "$(git -C "$RLINF_ROOT" rev-parse HEAD)" = "$RLINF_COMMIT"
test -z "$(git -C "$RLINF_ROOT" status --porcelain)"
test -r "$RLINF_ROOT/examples/embodiment/config/maniskill_ppo_mlp.yaml"
test -r "$RLINF_ROOT/examples/embodiment/train_embodied_agent.py"训练任务只读取并校验这个 checkout,不执行 git clone、git fetch 或网络补齐。目录已经存在时,以上命令会直接检查 commit、工作树和 recipe。
使用镜像内置的仿真资产
这个场景从随机初始化的 MLP 开始训练,PickCube-v1 会在线生成状态、动作、奖励和轨迹。因此,无需准备模型目录或数据集目录。
镜像已经包含 ManiSkill、SAPIEN PhysX 和运行 PickCube-v1 所需的基础资产。启动脚本调用 link_assets 后,以离线模式创建 PickCube-v1 仿真环境;资产缺失时,预检会直接失败,不会在 GPU Worker 中下载文件。
在共享存储中创建预检和启动脚本
本教程使用三个独立脚本:GPU 仿真预检脚本、训练结果校验脚本和训练启动脚本。先创建目录:
mkdir -p /mnt/rlinf-reproduction/tools/maniskill-ppo-mlp然后使用 AICoder 编辑器创建以下文件。AIStudio 的启动命令只执行这些文件,不在 GPU Worker 中临时生成 Python 或 Shell 脚本。
创建 GPU 仿真预检脚本
创建 /mnt/rlinf-reproduction/tools/maniskill-ppo-mlp/preflight-v1.py,写入:
#!/usr/bin/env python3
"""Create and step the bounded PickCube environment on one GPU."""
import os
import sys
import gymnasium as gym
import torch
import rlinf.envs.maniskill # noqa: F401
def main() -> None:
env = gym.make(
"PickCube-v1",
num_envs=2,
obs_mode="state",
sim_backend="gpu",
render_mode="all",
max_episode_steps=8,
sensor_configs={"shader_pack": "default"},
)
try:
observation, _ = env.reset(seed=0)
device = str(env.unwrapped.device)
observation_shape = tuple(observation.shape)
action_shape = tuple(env.action_space.shape)
if not device.startswith("cuda"):
raise RuntimeError(f"ManiSkill is not using CUDA: {device}")
if observation_shape[-1] != 42:
raise RuntimeError(
f"Expected observation width 42, found {observation_shape}"
)
if action_shape[-1] != 8:
raise RuntimeError(f"Expected action width 8, found {action_shape}")
next_observation, reward, _, _, _ = env.step(env.action_space.sample())
if tuple(next_observation.shape)[-1] != 42:
raise RuntimeError("Unexpected observation shape after env.step")
if not torch.isfinite(reward).all():
raise RuntimeError("ManiSkill returned a non-finite reward")
if env.render() is None:
raise RuntimeError("ManiSkill did not return a rendered frame")
print(f"MANISKILL_DEVICE={device}")
print(f"OBSERVATION_SHAPE={observation_shape}")
print(f"ACTION_SHAPE={action_shape}")
print("MANISKILL_PREFLIGHT=passed")
finally:
env.close()
# End the isolated preflight after closing the native simulator and
# flushing its result.
sys.stdout.flush()
sys.stderr.flush()
os._exit(0)
if __name__ == "__main__":
main()创建训练结果校验脚本
创建 /mnt/rlinf-reproduction/tools/maniskill-ppo-mlp/validate-run-v1.py,写入:
#!/usr/bin/env python3
"""Require the bounded ManiSkill PPO MLP completion signals."""
import argparse
import json
import math
import re
from pathlib import Path
NUMBER = r"[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?"
FATAL_MARKERS = (
"Traceback (most recent call last):",
"RayTaskError(",
"Exception in thread",
"Segmentation fault (core dumped)",
"terminate called after throwing an instance of",
"INTERNAL ASSERT FAILED",
)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("training_log", type=Path)
parser.add_argument("video_root", type=Path)
parser.add_argument("acceptance_json", type=Path)
args = parser.parse_args()
text = args.training_log.read_text(encoding="utf-8", errors="replace")
clean = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", text)
if any(marker in clean for marker in FATAL_MARKERS):
raise RuntimeError("The training log contains a fatal exception marker")
if not re.search(r"Global Step:\s*1/1", clean):
raise RuntimeError("Global Step 1/1 was not found")
trajectories = [
float(value)
for value in re.findall(rf"num_trajectories=({NUMBER})", clean)
]
if not trajectories or max(trajectories) < 2:
raise RuntimeError("At least 2 trajectories were not observed")
grad_norms = [
float(value)
for value in re.findall(rf"actor/grad_norm=({NUMBER})", clean)
]
if not grad_norms or not math.isfinite(grad_norms[-1]):
raise RuntimeError("A finite actor/grad_norm was not observed")
if " Evaluation " not in clean:
raise RuntimeError("The bounded evaluation section was not found")
success_values = [
float(value) for value in re.findall(rf"success_once=({NUMBER})", clean)
]
if not success_values or not all(math.isfinite(value) for value in success_values):
raise RuntimeError("A finite success_once metric was not observed")
videos = sorted(
path for path in args.video_root.rglob("*.mp4") if path.stat().st_size > 0
)
if not videos:
raise RuntimeError("No non-empty evaluation video was found")
result = {
"global_step": 1,
"num_trajectories": max(trajectories),
"actor_grad_norm": grad_norms[-1],
"success_once_values": success_values,
"evaluation_videos": [str(path) for path in videos],
"accepted": True,
}
args.acceptance_json.write_text(
json.dumps(result, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
print("MANISKILL_PPO_MLP_ACCEPTANCE=passed")
if __name__ == "__main__":
main()创建训练启动脚本
创建 /mnt/rlinf-reproduction/tools/maniskill-ppo-mlp/run-maniskill-ppo-mlp-v1.sh,写入:
#!/usr/bin/env bash
set -euo pipefail
: "${RUN_ID:?Set RUN_ID in the task environment}"
: "${IMAGE_TAG:?Set IMAGE_TAG to the selected task image}"
[[ "$RUN_ID" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ ]] || {
echo "RUN_ID must use 1-64 letters, numbers, dots, underscores, or hyphens" >&2
exit 1
}
readonly WORK_ROOT=/mnt/rlinf-reproduction
readonly RLINF_COMMIT=6f46f36ad790fec0de1efa86aaa8f9cd3e10d3f4
readonly RLINF_ROOT="$WORK_ROOT/code/$RLINF_COMMIT"
readonly TOOL_ROOT="$WORK_ROOT/tools/maniskill-ppo-mlp"
readonly OUTPUT_DIR="$WORK_ROOT/runs/maniskill-ppo-mlp/$RUN_ID"
readonly HYDRA_DIR="$OUTPUT_DIR/hydra"
readonly RLINF_OUTPUT_DIR="$OUTPUT_DIR/rlinf"
readonly TRAINING_LOG="$OUTPUT_DIR/training.log"
readonly PYTHON=/opt/venv/openvla/bin/python
readonly RAY_CLI=/opt/venv/openvla/bin/ray
test ! -e "$OUTPUT_DIR" || {
echo "Refusing to reuse OUTPUT_DIR: $OUTPUT_DIR" >&2
exit 1
}
mkdir -p "$OUTPUT_DIR"
exec > >(tee "$OUTPUT_DIR/startup.log") 2>&1
printf 'IMAGE_TAG=%s\n' "$IMAGE_TAG"
printf 'RUN_ID=%s\n' "$RUN_ID"
printf 'RLINF_COMMIT=%s\n' "$RLINF_COMMIT"
test -d "$RLINF_ROOT"
test -s "$RLINF_ROOT/examples/embodiment/config/maniskill_ppo_mlp.yaml"
test -s "$RLINF_ROOT/examples/embodiment/train_embodied_agent.py"
test -s "$TOOL_ROOT/preflight-v1.py"
test -s "$TOOL_ROOT/validate-run-v1.py"
test -x "$PYTHON"
test -x "$RAY_CLI"
test "$(git -C "$RLINF_ROOT" rev-parse HEAD)" = "$RLINF_COMMIT"
test -z "$(git -C "$RLINF_ROOT" status --porcelain)"
mapfile -t DRIVER_VERSIONS < <(
nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits \
| sed '/^[[:space:]]*$/d' \
| sort -u
)
test "${#DRIVER_VERSIONS[@]}" -eq 1
printf 'NVIDIA_DRIVER_VERSION=%s\n' "${DRIVER_VERSIONS[0]}"
nvidia-smi -L | tee "$OUTPUT_DIR/gpus.txt"
test "$(wc -l < "$OUTPUT_DIR/gpus.txt")" -eq 1
printf 'GPU_COUNT=1\n'
SHM_KIB="$(df --output=size -k /dev/shm | tail -n 1 | tr -d ' ')"
test "$SHM_KIB" -ge 33554432
printf 'SHM_KIB=%s\n' "$SHM_KIB"
test "${NVIDIA_DRIVER_CAPABILITIES:-}" = all
test -r /etc/vulkan/icd.d/nvidia_icd.json
test -r /usr/share/glvnd/egl_vendor.d/10_nvidia.json
ldconfig -p > "$OUTPUT_DIR/ldconfig.txt"
grep -q 'libEGL_nvidia.so' "$OUTPUT_DIR/ldconfig.txt"
grep -q 'libGLX_nvidia.so' "$OUTPUT_DIR/ldconfig.txt"
command -v link_assets >/dev/null
link_assets
test -d "$HOME/.maniskill"
test -d "$HOME/.sapien"
if [[ -n "${RAY_ADDRESS:-}" ]]; then
echo "Unexpected RAY_ADDRESS in an AIStudio single-machine task" >&2
exit 1
fi
if "$RAY_CLI" status >/dev/null 2>&1; then
echo "Unexpected pre-existing Ray runtime" >&2
exit 1
fi
export VIRTUAL_ENV=/opt/venv/openvla
export PATH="$VIRTUAL_ENV/bin:$PATH"
export EMBODIED_PATH="$RLINF_ROOT/examples/embodiment"
export PYTHONPATH="$RLINF_ROOT${PYTHONPATH:+:$PYTHONPATH}"
export HF_HUB_OFFLINE=1
export TRANSFORMERS_OFFLINE=1
export MUJOCO_GL=egl
export PYOPENGL_PLATFORM=egl
export VK_DRIVER_FILES=/etc/vulkan/icd.d/nvidia_icd.json
export VK_ICD_FILENAMES=/etc/vulkan/icd.d/nvidia_icd.json
export RAY_DEDUP_LOGS=0
"$PYTHON" -c \
'import torch; assert torch.cuda.device_count() == 1; print(torch.__version__, torch.cuda.get_device_name(0))'
{
printf 'IMAGE_TAG=%s\n' "$IMAGE_TAG"
printf 'RUN_ID=%s\n' "$RUN_ID"
printf 'RLINF_COMMIT=%s\n' "$RLINF_COMMIT"
printf 'NVIDIA_DRIVER_VERSION=%s\n' "${DRIVER_VERSIONS[0]}"
printf 'GPU_COUNT=1\n'
printf 'SHM_KIB=%s\n' "$SHM_KIB"
} > "$OUTPUT_DIR/run-manifest.env"
{
printf 'hydra.job.chdir=false\n'
printf 'hydra.run.dir=%s\n' "$HYDRA_DIR"
printf 'hydra.output_subdir=.hydra\n'
printf 'runner.logger.log_path=%s\n' "$RLINF_OUTPUT_DIR"
printf 'runner.max_steps=1\n'
printf 'runner.val_check_interval=1\n'
printf 'env.train.total_num_envs=2\n'
printf 'env.eval.total_num_envs=2\n'
printf 'env.train.max_steps_per_rollout_epoch=8\n'
printf 'env.eval.max_steps_per_rollout_epoch=8\n'
printf 'algorithm.update_epoch=2\n'
printf 'actor.global_batch_size=2\n'
printf 'actor.micro_batch_size=2\n'
} > "$OUTPUT_DIR/overrides.txt"
CUDA_VISIBLE_DEVICES=0 "$PYTHON" "$TOOL_ROOT/preflight-v1.py"
set +e
"$PYTHON" -u \
"$RLINF_ROOT/examples/embodiment/train_embodied_agent.py" \
--config-path "$RLINF_ROOT/examples/embodiment/config" \
--config-name maniskill_ppo_mlp \
"hydra.job.chdir=false" \
"hydra.run.dir=$HYDRA_DIR" \
"hydra.output_subdir=.hydra" \
"runner.logger.log_path=$RLINF_OUTPUT_DIR" \
runner.max_steps=1 \
runner.max_epochs=1 \
runner.val_check_interval=1 \
runner.save_interval=-1 \
algorithm.update_epoch=2 \
env.train.total_num_envs=2 \
env.train.rollout_epoch=1 \
env.train.max_episode_steps=8 \
env.train.max_steps_per_rollout_epoch=8 \
env.train.seed=0 \
env.train.video_cfg.save_video=false \
env.eval.total_num_envs=2 \
env.eval.rollout_epoch=1 \
env.eval.max_episode_steps=8 \
env.eval.max_steps_per_rollout_epoch=8 \
env.eval.seed=1 \
env.eval.video_cfg.save_video=true \
actor.global_batch_size=2 \
actor.micro_batch_size=2 \
actor.seed=1234 \
rollout.enable_torch_compile=false \
rollout.enable_cuda_graph=false \
2>&1 | tee "$TRAINING_LOG"
PIPE_STATUSES=("${PIPESTATUS[@]}")
set -e
if (( PIPE_STATUSES[0] != 0 || PIPE_STATUSES[1] != 0 )); then
printf 'RLinf or log persistence failed: rlinf=%s tee=%s\n' \
"${PIPE_STATUSES[0]}" "${PIPE_STATUSES[1]}" >&2
exit 1
fi
"$PYTHON" "$TOOL_ROOT/validate-run-v1.py" \
"$TRAINING_LOG" \
"$RLINF_OUTPUT_DIR/video/eval" \
"$OUTPUT_DIR/acceptance.json"
test -s "$OUTPUT_DIR/acceptance.json"
test -s "$OUTPUT_DIR/run-manifest.env"
test -s "$OUTPUT_DIR/overrides.txt"
test -s "$HYDRA_DIR/.hydra/config.yaml"
test -s "$HYDRA_DIR/.hydra/hydra.yaml"
test -s "$HYDRA_DIR/.hydra/overrides.yaml"
find "$RLINF_OUTPUT_DIR/tensorboard" \
-type f -name 'events.out.tfevents.*' -size +0 -print -quit | grep -q .
printf 'RLINF_TRAINING_COMPLETE=1\n'固定 Hydra 和训练输出目录
启动脚本为每个 RUN_ID 创建一个独立输出目录,并分别保存 Hydra 元数据和 RLinf 训练结果。
| 设置 | 本场景值 | 结果 |
|---|---|---|
hydra.job.chdir | false | 保持训练进程的工作目录稳定 |
hydra.run.dir | $OUTPUT_DIR/hydra | 保存 Hydra 日志和本次运行信息 |
hydra.output_subdir | .hydra | 保留解析后的配置和 overrides |
runner.logger.log_path | $OUTPUT_DIR/rlinf | 保存 TensorBoard 和评估视频 |
确认 GPU 仿真运行条件
本文使用宁夏 B 的 A100-SXM4-80GB。请在自己的账号中选择符合以下要求的资源:
- 1 张与镜像兼容的 NVIDIA GPU;
- 任务可以把准备好的共享存储挂载到
/mnt/rlinf-reproduction; - Worker 可以提供至少 32 GiB
/dev/shm; - 任务环境变量可以设置
NVIDIA_DRIVER_CAPABILITIES=all。
Worker 启动后,脚本会自动检查 Python 环境、仿真资产、NVIDIA 驱动以及 Vulkan/EGL 图形库。
运行这个场景
以下步骤先检查固定输入,再分配 GPU 并判断仿真、训练和持久化结果是否全部通过。
Step 1 使用 AICoder 校验共享存储中的代码和脚本
创建 GPU 任务前,在挂载同一共享存储的 AICoder 或开发机中执行:
set -euo pipefail
export WORK_ROOT=/mnt/rlinf-reproduction
export RLINF_COMMIT=6f46f36ad790fec0de1efa86aaa8f9cd3e10d3f4
export RLINF_ROOT="$WORK_ROOT/code/$RLINF_COMMIT"
export TOOL_ROOT="$WORK_ROOT/tools/maniskill-ppo-mlp"
findmnt -T "$WORK_ROOT"
test -w "$WORK_ROOT"
test "$(git -C "$RLINF_ROOT" rev-parse HEAD)" = "$RLINF_COMMIT"
test -z "$(git -C "$RLINF_ROOT" status --porcelain)"
test -s "$RLINF_ROOT/examples/embodiment/config/maniskill_ppo_mlp.yaml"
python3 -m py_compile \
"$TOOL_ROOT/preflight-v1.py" \
"$TOOL_ROOT/validate-run-v1.py"
bash -n "$TOOL_ROOT/run-maniskill-ppo-mlp-v1.sh"
chmod 755 \
"$TOOL_ROOT/preflight-v1.py" \
"$TOOL_ROOT/validate-run-v1.py" \
"$TOOL_ROOT/run-maniskill-ppo-mlp-v1.sh"所有命令退出码为 0 后再创建任务。检查失败时,在 AICoder 或开发机中修复固定输入;GPU 启动脚本不会克隆代码、下载资产或修改 checkout。
Step 2 创建 1 Worker 单 GPU Spot 训练任务
按使用 Spot 资源提交训练任务打开任务创建流程,并填写以下设置。
| AIStudio 字段 | 本场景设置 |
|---|---|
| 任务类型 | 训练任务 |
| 资源类型 | Spot 资源 |
| 可用区和 GPU | 本文使用宁夏 B、1 张 A100-SXM4-80GB;请在自己的账号中选择符合要求的资源 |
| 分布式框架 | 单机 |
| Worker 数量 | 1 |
| RDMA 配置 | 不开启 |
| 镜像 | 自己租户中的 agentic-rlinf0.4-maniskill_libero 完整 tag |
| 共享内存 | 至少 32 GiB |
| 任务可视化 | 开启;日志路径指向本次运行的 RLinf 输出目录 |
一个 Worker 已能运行本场景的本地 Ray 和三个 RLinf 组件,因此不需要选择 AIStudio 的 Ray 分布式框架或开启 RDMA。
Step 3 挂载共享存储并设置唯一运行参数
把包含 rlinf-reproduction 内容的共享存储挂载到容器内的 /mnt/rlinf-reproduction。系统挂载路径必须和脚本中的 WORK_ROOT 完全一致。
在任务环境变量中设置:
RUN_ID=maniskill-ppo-mlp-step1-<date>-01
IMAGE_TAG=cr.infini-ai.com/<tenant-id>/rlinf:agentic-rlinf0.4-maniskill_libero
NVIDIA_DRIVER_CAPABILITIES=all开启任务可视化,并把日志路径设置为:
/mnt/rlinf-reproduction/runs/maniskill-ppo-mlp/${RUN_ID}/rlinf${RUN_ID} 来自上面添加的任务环境变量,平台会在启动 TensorBoard 前替换它。具体用法见使用环境变量同步 TensorBoard 日志路径。每次运行只需设置新的 RUN_ID,无需再修改任务可视化路径。启动脚本会拒绝复用已有输出目录,防止旧日志和本次结果混在一起。
Step 4 设置执行版本化脚本的启动命令
在“启动命令”中填写:
exec /bin/bash /mnt/rlinf-reproduction/tools/maniskill-ppo-mlp/run-maniskill-ppo-mlp-v1.sh启动命令保持简短,具体预检、Hydra overrides、训练和验收逻辑保存在共享存储中的版本化脚本里,便于运行前检查和失败后定位。
Step 5 核对任务配置后确认创建
确认 1 个 Worker、1 张 GPU、单机框架、自己的租户镜像、/mnt/rlinf-reproduction 系统挂载路径、至少 32 GiB 共享内存和三个环境变量均已设置。挂载路径必须以页面最终显示的值为准,不能根据存储卷名称推断。
第一次验证时关闭自动重启。自动重启会复用同一个 RUN_ID,而本教程的脚本会保留首次失败输出并拒绝覆盖。配置一致后,点击 确认创建。
Step 6 确认 PickCube GPU 预检和本地 Ray 启动
任务开始运行后,在日志中依次确认:
NVIDIA_DRIVER_VERSION=<version>
GPU_COUNT=1
MANISKILL_DEVICE=cuda
OBSERVATION_SHAPE=(2, 42)
ACTION_SHAPE=(2, 8)
MANISKILL_PREFLIGHT=passed随后确认 RLinf 创建 EnvGroup、RolloutGroup 和 ActorGroup,并在一个本地 Ray 节点上使用 GPU 0。以 MANISKILL_PREFLIGHT=passed 作为 GPU 仿真预检通过的标志;任务进入“运行中”只表示 Worker 已启动。
Step 7 确认两条轨迹、一次 PPO 更新和短时评估
训练完成后,日志应包含:
Global Step: 1/1
num_trajectories=<不小于 2 的数值>
actor/grad_norm=<有限数值>
MANISKILL_PPO_MLP_ACCEPTANCE=passed
RLINF_TRAINING_COMPLETE=1指标表还应包含 Evaluation 部分,且 success_once 为有限值。本次任务只需确认评估流程完成;随机初始化策略的成功率可以为 0。
Step 8 Worker 结束后回读训练结果和评估视频
任务结束后,在挂载同一共享存储的 AICoder 或开发机中执行:
set -euo pipefail
export RUN_ID=maniskill-ppo-mlp-step1-<date>-01
export WORK_ROOT=/mnt/rlinf-reproduction
export OUTPUT_DIR="$WORK_ROOT/runs/maniskill-ppo-mlp/$RUN_ID"
test -s "$OUTPUT_DIR/startup.log"
test -s "$OUTPUT_DIR/training.log"
test -s "$OUTPUT_DIR/acceptance.json"
test -s "$OUTPUT_DIR/hydra/.hydra/config.yaml"
find "$OUTPUT_DIR/rlinf/tensorboard" \
-type f -name 'events.out.tfevents.*' -size +0 -print -quit | grep -q .
find "$OUTPUT_DIR/rlinf/video/eval" \
-type f -name '*.mp4' -size +0 -print -quit | grep -q .
grep -aFx 'MANISKILL_PPO_MLP_ACCEPTANCE=passed' "$OUTPUT_DIR/startup.log"
grep -aFx 'RLINF_TRAINING_COMPLETE=1' "$OUTPUT_DIR/startup.log"
cat "$OUTPUT_DIR/acceptance.json"所有命令退出码为 0 时,说明验收结果、Hydra 配置、TensorBoard 文件和评估视频在 Worker 回收后仍然可读。
延长训练并保存 checkpoint
短时验证通过后,可以复制启动脚本并使用新的版本名逐步增加 runner.max_steps。先增加 global step 数,再根据显存和仿真速度调整并行环境数。每次只调整一类参数,便于在出现问题时定位原因。
上游 recipe 默认不限制训练 step,并设置 env.train.total_num_envs=1024、env.eval.total_num_envs=32、actor.global_batch_size=6400 和 algorithm.update_epoch=8。这些值可作为扩大规模时的参考,但仍需根据所选 GPU 和目标运行时间逐项调整。
需要保存策略时,把 runner.save_interval 设置为正整数。checkpoint 会写入 runner.logger.log_path 下的实验目录。恢复训练前,先确认 checkpoint 文件完整,再通过 runner.resume_dir=<checkpoint-directory> 启动新的任务和新的 RUN_ID。
延长运行时,请根据目标 step 重新规划存储空间、Spot 中断恢复和 checkpoint 周期,并单独评估吞吐、收敛和超参数。
停止或重新运行任务
预检、训练或结果校验失败时,脚本会返回非 0。保留本次输出,先检查 startup.log、training.log 和任务事件;修复问题后使用新的 RUN_ID 重跑。
通用问题请按训练故障处理检查任务事件、日志和共享存储挂载。
处理本场景特有故障
找不到 ManiSkill 或 SAPIEN 资产
如果 link_assets 之后仍找不到 $HOME/.maniskill 或 $HOME/.sapien,先确认任务选择了本文指定的镜像 tag,并检查 /opt/assets 是否存在。仿真资产应随镜像提供;缺失时请修正镜像或挂载。
Vulkan、EGL 或 GPU 仿真预检失败
如果缺少 NVIDIA Vulkan ICD、EGL 库,或者 MANISKILL_DEVICE 不是 CUDA 设备,说明当前 Worker 无法运行 GPU 仿真。确认任务使用预期镜像、NVIDIA_DRIVER_CAPABILITIES=all 已设置,并重新选择符合要求的 GPU 资源。
已有 Ray 进程阻止任务启动
本场景由 RLinf 在单个 Worker 内启动本地 Ray。启动前检测到 RAY_ADDRESS 或已有 Ray 运行时时,保留日志并使用干净 Worker 重新运行。
任务显示运行成功但缺少 PPO 完成信号
如果任务页面显示运行成功,但没有 MANISKILL_PPO_MLP_ACCEPTANCE=passed 或 RLINF_TRAINING_COMPLETE=1,检查 training.log 中的异常标记、轨迹数和 actor/grad_norm。只有结果校验脚本通过并且持久化文件完整时,才把本次运行记为通过。
评估完成但没有生成视频
检查解析后的 Hydra 配置中 env.eval.video_cfg.save_video 是否为 true,并确认 runner.logger.log_path 指向共享存储。随后检查 Vulkan/EGL 预检和评估日志。评估视频是本教程的验收项;训练更新完成后仍需确认视频已写入共享存储。