在 AIStudio 训练任务中使用 RLinf 复现 EmbodiChain CartPole
如果您第一次在 AIStudio 上运行 RLinf,可以从 EmbodiChain CartPole 开始。这个场景使用低维状态输入和 MLP actor-critic,不需要提前准备预训练模型或静态训练数据,适合先熟悉镜像、共享存储、启动命令和训练结果之间的关系。
本文先准备镜像、固定版本的 RLinf 代码和 CartPole 资源,再创建一个单 Worker、单 GPU 的 PPO 训练任务。任务结束后,通过完整轨迹、PPO 更新和共享存储中的输出判断训练是否正常完成。
实战目标
本文会在 1 张 GPU 上运行一个简短的 CartPole PPO 训练任务。运行结束后,检查是否完成至少 2 条轨迹、一次 PPO 更新,并确认输出已写入共享存储。
以下结果全部出现时,说明任务运行成功:
- AIStudio 任务和启动日志记录相同的完整镜像 tag,RLinf 提交和资源 SHA-256 与准备记录一致。
- 启动命令从 Worker 检测驱动并输出且只输出一条
NVIDIA_DRIVER_VERSION=<version>;Worker 只看到 1 张 GPU,/dev/shm不小于 32 GiB。 CartPole和SimResources从固定共享存储目录加载,任务运行期间不下载代码或资源。- RLinf 在一个 Worker 内启动本地 Ray,actor、rollout 和 environment 组件均放置到逻辑 GPU
0。 num_trajectories的最大值不小于2,并且至少完成一次 PPO 优化器更新;TensorBoard 中的env/、rollout/和train/标量为有限值。- 任务退出码为
0,任务结束后仍可从共享存储读取运行清单、启动日志、Hydra 配置和 TensorBoard 文件。
场景信息
创建任务时使用以下设置。
示例可用区:宁夏 B。您可以选择其他可用区,但镜像、GPU 资源和共享存储必须位于同一可用区。
RLinf:
- commit:
3cb54c2eb2e355378ba049d50f7ec0cb8acc2da0 - 配置:
tests/e2e_tests/embodied/embodichain_ppo_cart_pole.yaml - 入口:
examples/embodiment/train_embodied_agent.py - 要求:检出指定 commit,并保持代码不变。
- commit:
上游镜像:
rlinf/rlinf:agentic-rlinf0.4-embodichain,来自 Docker Hub。AIStudio 任务镜像:
cr.infini-ai.com/<tenant-id>/rlinf:agentic-rlinf0.4-embodichain。先把上游镜像上传或导入自己的租户镜像仓库。仿真资产:
CartPole和SimResources,以及快照元数据和 SHA-256 清单。每次任务校验同一固定目录,不在 Worker 中下载。资源来源:Spot 资源。中断后使用新
RUN_ID完整重跑,不恢复进度。分布式框架:单机;RLinf 在 Worker 内启动本地 Ray,不使用 AIStudio 托管 Ray。
Worker 拓扑:1 个 Worker、1 个节点、1 张 A100-SXM4-80GB。其他 GPU SKU 需要单独验证。
共享存储:目标可用区内的共享高性能存储;在实际挂载点下使用
rlinf-reproduction/工作目录。固定输入只读使用,runs/写入持久化输出。训练范围:2 个训练环境、2 个评估环境、每轮最多 8 步、2 个 PPO 更新轮次。此范围用于快速检查运行链路,不评估训练收敛。
本场景在 AIStudio 中怎样运行
AIStudio 创建 1 个 Worker、挂载共享存储并执行启动命令。RLinf 在这个 Worker 内启动本地 Ray,并运行 actor、rollout 和 environment 组件。
- 单机(本场景选择):AIStudio 创建 1 个 Worker,不创建托管 Ray 集群。RLinf 在这个 Worker 内启动本地 Ray。Worker 数量为
1时,创建页只提供“单机”选项。 - Ray(本场景不选择):AIStudio 创建托管 Ray Head 和 Worker,启动命令在 Head 中运行。该模式用于多 Worker 场景,并会改变启动入口、网络检查和日志位置。
RLinf 使用本地 Ray 不要求 AIStudio 选择 Ray。本场景的实际拓扑如下:
AIStudio 单机训练任务
└── Worker 0 / GPU 0
└── RLinf 本地 Ray
├── Driver
├── ActorGroup
├── RolloutGroup
└── EnvGroup / EmbodiChain CartPoleWorker 从共享存储读取固定版本的代码和资源,并把日志、Hydra 配置和 TensorBoard 文件写入本次 RUN_ID 的输出目录。AIStudio 分布式框架的选择方法见使用 Spot 资源提交训练任务。
开始前准备
创建 GPU 任务前,使用 AICoder 或开发机准备镜像、RLinf 代码、EmbodiChain 资源和启动脚本。本文使用宁夏 B;您也可以选择其他可用区,但镜像、任务和共享存储需要位于同一可用区。
在目标可用区准备容器镜像
本场景使用 Docker Hub 镜像 rlinf/rlinf:agentic-rlinf0.4-embodichain。
重要
请先上传到自己的镜像仓库
下面的地址属于本示例验证时使用的私有租户,不是公共镜像地址,不能直接用于其他租户。请先把上游镜像上传或导入自己所选可用区的镜像仓库,再将地址中的 te-b905754427352261 替换为自己的 <tenant-id>。
cr.infini-ai.com/te-b905754427352261/rlinf:agentic-rlinf0.4-embodichain在镜像中心确认自己的 tag 位于任务所在可用区,并且“可用服务”包含任务。本文不展开镜像下载、导入或上传步骤;具体操作请查看准备并验证容器镜像。
在共享存储中准备固定版本的 RLinf 代码
在已挂载共享存储的 AICoder 或开发机中执行以下命令。先把 STORAGE_MOUNT 替换为页面显示的容器内路径,例如 /mnt/user_dir/your-user-name。命令不会覆盖已有的代码目录。
set -euo pipefail
export STORAGE_MOUNT=/mnt/user_dir/your-user-name
export WORK_ROOT="$STORAGE_MOUNT/rlinf-reproduction"
export RLINF_COMMIT=3cb54c2eb2e355378ba049d50f7ec0cb8acc2da0
export RLINF_ROOT="$WORK_ROOT/code/$RLINF_COMMIT"
mkdir -p "$WORK_ROOT"
findmnt -T "$WORK_ROOT"
df -hT "$WORK_ROOT"
test -w "$WORK_ROOT"
mkdir -p "$WORK_ROOT/code"
test ! -e "$RLINF_ROOT" || {
echo "RLinf target already exists: $RLINF_ROOT" >&2
exit 1
}
git clone https://github.com/RLinf/RLinf.git "$RLINF_ROOT"
git -C "$RLINF_ROOT" checkout --detach "$RLINF_COMMIT"
test "$(git -C "$RLINF_ROOT" rev-parse HEAD)" = "$RLINF_COMMIT"
test -z "$(git -C "$RLINF_ROOT" status --porcelain)"
test -r "$RLINF_ROOT/tests/e2e_tests/embodied/embodichain_ppo_cart_pole.yaml"训练任务不会自动同步代码。后续 GPU 启动命令只读取并校验这个固定版本,不执行 git clone、git fetch 或网络下载。代码、数据和输出目录的一般准备方法见准备代码、数据与输出目录。
固定 CartPole 和 SimResources
使用同一镜像创建一个临时开发机,挂载同一共享存储,然后下载 EmbodiChain 资源并生成校验清单。
set -euo pipefail
export STORAGE_MOUNT=/mnt/user_dir/your-user-name
export WORK_ROOT="$STORAGE_MOUNT/rlinf-reproduction"
export ASSET_ROOT="$WORK_ROOT/assets/embodichain/cartpole-v1"
export EMBODICHAIN_DATA_ROOT="$ASSET_ROOT"
set +u
source /opt/venv/embodichain/bin/activate
set -u
test ! -e "$ASSET_ROOT" || {
echo "Asset target already exists: $ASSET_ROOT" >&2
exit 1
}
mkdir -p "$ASSET_ROOT"
python -m embodichain.data download --name CartPole
python -m embodichain.data download --name SimResources
python -m pip show embodichain > "$ASSET_ROOT/embodichain-package.txt"
(
cd "$ASSET_ROOT"
find . -type f \
! -name SHA256SUMS \
! -name SHA256SUMS.sha256 \
! -name SNAPSHOT.env \
-print0 \
| sort -z \
| xargs -0 sha256sum > SHA256SUMS
ASSET_MANIFEST_SHA256="$(sha256sum SHA256SUMS | awk '{ print $1 }')"
EMBODICHAIN_VERSION="$(
python -c 'import importlib.metadata; print(importlib.metadata.version("embodichain"))'
)"
{
printf 'ASSET_SNAPSHOT_ID=cartpole-v1\n'
printf 'ASSET_NAMES=CartPole,SimResources\n'
printf 'EMBODICHAIN_VERSION=%s\n' "$EMBODICHAIN_VERSION"
printf 'ASSET_MANIFEST_SHA256=%s\n' "$ASSET_MANIFEST_SHA256"
} > SNAPSHOT.env
printf '%s SHA256SUMS\n' "$ASSET_MANIFEST_SHA256" > SHA256SUMS.sha256
)
test -s "$ASSET_ROOT/SHA256SUMS"
test -s "$ASSET_ROOT/SHA256SUMS.sha256"
test -s "$ASSET_ROOT/SNAPSHOT.env"
(
cd "$ASSET_ROOT"
sha256sum -c SHA256SUMS.sha256
sha256sum -c SHA256SUMS
cat SNAPSHOT.env
)SHA256SUMS 中的每个文件都返回 OK 后,资源便已保存到 ASSET_ROOT。后续任务会读取同一目录并再次校验清单。
准备短时训练配置、启动脚本和输出目录
本文读取固定版本中的 tests/e2e_tests/embodied/embodichain_ppo_cart_pole.yaml,并在运行时使用以下参数:
- train 和 eval 各使用 2 个环境;
- train 和 eval 的
max_steps_per_rollout_epoch均为8,与max_episode_steps: 8对齐; - PPO 使用 2 次 update epoch;
rollout.enable_cuda_graph=false;- 输出写入
$WORK_ROOT/runs/cartpole/<RUN_ID>/; - 启动脚本保存为
$WORK_ROOT/fixtures/cartpole/bounded-v1/launch.sh。
RUN_ID 必须由字母、数字、点、下划线或连字符组成,并且每次运行使用新值。启动脚本会拒绝复用已有输出目录。
固定 Hydra 工作目录和持久化输出
Hydra 配置和 RLinf 训练输出使用不同目录,但都保存在本次 RUN_ID 对应的共享存储路径中。
| 设置 | 本场景值 | 结果 |
|---|---|---|
hydra.job.chdir | false | 保持 RLinf 进程工作目录稳定 |
hydra.run.dir | $OUTPUT_DIR/hydra | 保存 Hydra 日志和本次运行信息 |
hydra.output_subdir | .hydra | 保留 config.yaml、hydra.yaml 和 overrides.yaml |
runner.logger.log_path | $OUTPUT_DIR | 把 TensorBoard 和 RLinf 解析后配置写入持久化目录 |
确认运行条件
本文使用宁夏 B 的 A100-SXM4-80GB。请在自己的账号中选择可用且符合以下要求的资源:
- 任务可以挂载已准备的共享存储,并能通过实际
WORK_ROOT读取代码和资源、写入runs/; - Worker 可以提供至少 32 GiB
/dev/shm; - 镜像中存在
/opt/venv/embodichain; - 镜像、任务和共享存储位于同一可用区。
运行这个场景
按以下步骤校验输入、创建任务并检查结果。
Step 1 使用 AICoder 校验共享存储中的固定输入
分配 GPU 前,在挂载同一共享存储的 AICoder 或开发机中执行以下命令:
set -euo pipefail
export STORAGE_MOUNT=/mnt/user_dir/your-user-name
export WORK_ROOT="$STORAGE_MOUNT/rlinf-reproduction"
export RLINF_COMMIT=3cb54c2eb2e355378ba049d50f7ec0cb8acc2da0
export RLINF_ROOT="$WORK_ROOT/code/$RLINF_COMMIT"
export ASSET_ROOT="$WORK_ROOT/assets/embodichain/cartpole-v1"
test "$(git -C "$RLINF_ROOT" rev-parse HEAD)" = "$RLINF_COMMIT"
test -z "$(git -C "$RLINF_ROOT" status --porcelain)"
test -r "$RLINF_ROOT/tests/e2e_tests/embodied/embodichain_ppo_cart_pole.yaml"
test -s "$ASSET_ROOT/SHA256SUMS"
test -s "$ASSET_ROOT/SHA256SUMS.sha256"
test -s "$ASSET_ROOT/SNAPSHOT.env"
(
cd "$ASSET_ROOT"
sha256sum -c SHA256SUMS.sha256
sha256sum -c SHA256SUMS
cat SNAPSHOT.env
)所有命令退出码为 0,并且资源清单中的每个文件都返回 OK 后,继续创建任务。检查失败时,在 AICoder 或开发机中重新准备资源。GPU 启动命令不会克隆代码或下载资源。
Step 2 创建 1 Worker 单 GPU Spot 训练任务
按使用 Spot 资源提交训练任务打开任务创建流程,并填写以下设置。
| AIStudio 字段 | 本场景设置 |
|---|---|
| 任务类型 | 训练任务 |
| 资源类型 | Spot 资源 |
| 可用区和 GPU | 本文使用宁夏 B、1 张 A100-SXM4-80GB;请在自己的账号中选择符合要求的资源 |
| 分布式框架 | 单机 |
| Worker 数量 | 1 |
| 镜像 | 上传或导入到自己租户后的 cr.infini-ai.com/<tenant-id>/rlinf:agentic-rlinf0.4-embodichain |
| 自动排队和自动重启 | 不启用 |
选择完成后,页面的资源摘要应显示所选可用区、Spot、1 个 Worker、1 张 GPU 和单机框架。
Step 3 挂载共享存储并设置唯一 RUN_ID
在任务创建页中完成以下配置:
- 选择已经保存固定输入的共享高性能存储,并记录页面显示的系统挂载路径;
- 添加环境变量
WORK_ROOT,值为该系统挂载路径下的rlinf-reproduction目录,例如/mnt/user_dir/your-user-name/rlinf-reproduction; - 添加环境变量
RUN_ID,例如cartpole-20260820-01; - 把 TensorBoard 路径设置为
${WORK_ROOT}/runs/cartpole/${RUN_ID}/tensorboard。
${WORK_ROOT} 和 ${RUN_ID} 来自上面添加的任务环境变量,平台会在启动 TensorBoard 前替换它们。具体用法见使用环境变量同步 TensorBoard 日志路径。同一存储卷在 AICoder 和训练任务中的挂载路径可能不同,但两个路径需要指向同一个 rlinf-reproduction/ 目录。每次运行只需设置新的 RUN_ID,无需再修改 TensorBoard 路径。
Step 4 设置包含输入、驱动和 Hydra 检查的启动命令
将以下脚本保存为 $WORK_ROOT/fixtures/cartpole/bounded-v1/launch.sh 并赋予可执行权限。脚本在启动 RLinf 前校验固定输入、实际 NVIDIA 驱动、CUDA、GPU 数量、/dev/shm 和本地 Ray 状态;RLinf 结束后再校验轨迹和持久化文件。
#!/bin/bash
set -euo pipefail
: "${WORK_ROOT:?Set WORK_ROOT to the prepared shared-storage directory}"
: "${RUN_ID:?Set RUN_ID in the AIStudio task}"
WORK_ROOT="${WORK_ROOT%/}"
[[ "$WORK_ROOT" = /* && "$WORK_ROOT" != "/" ]] || {
echo "WORK_ROOT must be an absolute non-root path" >&2
exit 1
}
[[ "$RUN_ID" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
echo "RUN_ID contains unsupported characters" >&2
exit 1
}
export WORK_ROOT
# 导入镜像后,把以下 URL 中的租户 ID 替换为自己的 <tenant-id>。
export IMAGE_TAG=cr.infini-ai.com/te-b905754427352261/rlinf:agentic-rlinf0.4-embodichain
export RLINF_COMMIT=3cb54c2eb2e355378ba049d50f7ec0cb8acc2da0
export RLINF_ROOT="$WORK_ROOT/code/$RLINF_COMMIT"
export ASSET_ROOT="$WORK_ROOT/assets/embodichain/cartpole-v1"
export EMBODICHAIN_DATA_ROOT="$ASSET_ROOT"
export OUTPUT_DIR="$WORK_ROOT/runs/cartpole/$RUN_ID"
export HYDRA_DIR="$OUTPUT_DIR/hydra"
export RUN_MANIFEST="$OUTPUT_DIR/run-manifest.txt"
export REPO_PATH="$RLINF_ROOT"
export PYTHONPATH="$RLINF_ROOT:${PYTHONPATH:-}"
export RAY_DEDUP_LOGS=0
export TOKENIZERS_PARALLELISM=false
export PYTHONHASHSEED=1234
export ACTOR_SEED=1234
export TRAIN_ENV_SEED=0
export EVAL_ENV_SEED=10000
export MIN_NUM_TRAJECTORIES=2
test ! -e "$OUTPUT_DIR" || {
echo "Refusing to reuse OUTPUT_DIR: $OUTPUT_DIR" >&2
exit 1
}
mkdir -p "$OUTPUT_DIR"
if ! NVIDIA_DRIVER_VERSIONS="$(
nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits |
sed '/^[[:space:]]*$/d' |
sort -u
)"; then
echo "Unable to detect the NVIDIA driver version" >&2
exit 1
fi
NVIDIA_DRIVER_VERSION_COUNT="$(
printf '%s\n' "$NVIDIA_DRIVER_VERSIONS" | wc -l | tr -d '[:space:]'
)"
if [[ -z "$NVIDIA_DRIVER_VERSIONS" || "$NVIDIA_DRIVER_VERSION_COUNT" -ne 1 ]]; then
printf 'Expected one NVIDIA driver version, detected: %s\n' \
"${NVIDIA_DRIVER_VERSIONS:-<none>}" >&2
exit 1
fi
{
printf 'SCENARIO=embodichain-cartpole-bounded\n'
printf 'PURPOSE=bounded-functional-validation\n'
printf 'FIXTURE_REVISION=bounded-v1\n'
printf 'RUN_SCOPE=bounded\n'
printf 'NVIDIA_DRIVER_VERSION=%s\n' "$NVIDIA_DRIVER_VERSIONS"
printf 'IMAGE_TAG=%s\n' "$IMAGE_TAG"
printf 'RLINF_REMOTE=%s\n' 'https://github.com/RLinf/RLinf.git'
printf 'RLINF_COMMIT=%s\n' "$RLINF_COMMIT"
printf 'RLINF_CONFIG=%s\n' 'tests/e2e_tests/embodied/embodichain_ppo_cart_pole.yaml'
printf 'RLINF_LAUNCHER=%s\n' 'examples/embodiment/train_embodied_agent.py'
printf 'RUN_ID=%s\n' "$RUN_ID"
printf 'WORKER_COUNT=1\n'
printf 'NODE_COUNT=1\n'
printf 'VISIBLE_GPU_COUNT=1\n'
printf 'ROLE_PLACEMENT=actor,env,rollout:0\n'
printf 'ACTOR_SEED=%s\n' "$ACTOR_SEED"
printf 'TRAIN_ENV_SEED=%s\n' "$TRAIN_ENV_SEED"
printf 'EVAL_ENV_SEED=%s\n' "$EVAL_ENV_SEED"
printf 'OUTPUT_DIR=%s\n' "$OUTPUT_DIR"
printf 'HYDRA_DIR=%s\n' "$HYDRA_DIR"
printf 'TENSORBOARD_DIR=%s\n' "$OUTPUT_DIR/tensorboard"
printf 'CHECKPOINT_POLICY=disabled\n'
} | tee "$OUTPUT_DIR/driver.log" "$RUN_MANIFEST"
test "$(git -C "$RLINF_ROOT" rev-parse HEAD)" = "$RLINF_COMMIT"
test -z "$(git -C "$RLINF_ROOT" status --porcelain)"
test -r "$RLINF_ROOT/tests/e2e_tests/embodied/embodichain_ppo_cart_pole.yaml"
test -s "$ASSET_ROOT/SHA256SUMS.sha256"
test -s "$ASSET_ROOT/SNAPSHOT.env"
(
cd "$ASSET_ROOT"
sha256sum -c SHA256SUMS.sha256
sha256sum -c SHA256SUMS
)
cat "$ASSET_ROOT/SNAPSHOT.env" \
| tee -ia "$OUTPUT_DIR/driver.log" "$RUN_MANIFEST"
set +u
source /opt/venv/embodichain/bin/activate
set -u
export LD_LIBRARY_PATH="$(python -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR"))'):${LD_LIBRARY_PATH:-}"
if [[ -n "${RAY_ADDRESS:-}" ]]; then
echo "Unexpected RAY_ADDRESS in an AIStudio single-machine task" >&2
exit 1
fi
if ray status >/dev/null 2>&1; then
echo "Unexpected pre-existing Ray runtime" >&2
exit 1
fi
python - <<'PY' | tee -ia "$OUTPUT_DIR/driver.log" "$RUN_MANIFEST"
import importlib.metadata
import math
import os
import torch
assert torch.cuda.is_available(), "CUDA is unavailable"
assert torch.cuda.device_count() == 1, torch.cuda.device_count()
value = torch.tensor([2.0], device="cuda").square().item()
assert math.isfinite(value) and value == 4.0
stats = os.statvfs("/dev/shm")
shm_bytes = stats.f_frsize * stats.f_blocks
assert shm_bytes >= 32 * 1024**3, shm_bytes
print("TORCH_VERSION=" + importlib.metadata.version("torch"))
print("RAY_VERSION=" + importlib.metadata.version("ray"))
print("EMBODICHAIN_RUNTIME_VERSION=" + importlib.metadata.version("embodichain"))
print("CUDA_VERSION=" + str(torch.version.cuda))
print("GPU_NAME=" + torch.cuda.get_device_name(0))
print("DEV_SHM_BYTES=" + str(shm_bytes))
PY
{
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' "$OUTPUT_DIR"
printf 'actor.seed=%s\n' "$ACTOR_SEED"
printf 'env.train.seed=%s\n' "$TRAIN_ENV_SEED"
printf 'env.eval.seed=%s\n' "$EVAL_ENV_SEED"
printf 'env.train.max_steps_per_rollout_epoch=8\n'
printf 'env.eval.max_steps_per_rollout_epoch=8\n'
printf 'rollout.enable_cuda_graph=false\n'
} > "$OUTPUT_DIR/overrides.txt"
set +e
python "$RLINF_ROOT/examples/embodiment/train_embodied_agent.py" \
--config-path "$RLINF_ROOT/tests/e2e_tests/embodied" \
--config-name embodichain_ppo_cart_pole \
"hydra.job.chdir=false" \
"hydra.run.dir=$HYDRA_DIR" \
"hydra.output_subdir=.hydra" \
"runner.logger.log_path=$OUTPUT_DIR" \
"actor.seed=$ACTOR_SEED" \
"env.train.seed=$TRAIN_ENV_SEED" \
"env.eval.seed=$EVAL_ENV_SEED" \
"env.train.max_steps_per_rollout_epoch=8" \
"env.eval.max_steps_per_rollout_epoch=8" \
"rollout.enable_cuda_graph=false" \
2>&1 | tee -ia "$OUTPUT_DIR/driver.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 - "$OUTPUT_DIR/driver.log" "$MIN_NUM_TRAJECTORIES" <<'PY' \
| tee -ia "$OUTPUT_DIR/driver.log" "$RUN_MANIFEST"
import re
import sys
from pathlib import Path
log_path = Path(sys.argv[1])
minimum = int(sys.argv[2])
values = [
int(value)
for value in re.findall(r"\bnum_trajectories=(\d+)\b", log_path.read_text())
]
if not values:
raise SystemExit("No num_trajectories metric was logged")
observed = max(values)
print(f"NUM_TRAJECTORIES_MAX={observed}")
if observed < minimum:
raise SystemExit(
f"Expected num_trajectories >= {minimum}, observed maximum {observed}"
)
PY
test -s "$OUTPUT_DIR/overrides.txt"
test -s "$RUN_MANIFEST"
test -s "$HYDRA_DIR/.hydra/config.yaml"
test -s "$HYDRA_DIR/.hydra/hydra.yaml"
test -s "$HYDRA_DIR/.hydra/overrides.yaml"
test -s "$OUTPUT_DIR/tensorboard/config.yaml"
find "$OUTPUT_DIR/tensorboard" -type f -name 'events.out.tfevents.*' -size +0 -print -quit \
| grep -q .
{
printf 'RLINF_EXIT_CODE=0\n'
printf 'PERSISTED_OUTPUT_READY=1\n'
} | tee -ia "$OUTPUT_DIR/driver.log" "$RUN_MANIFEST"在任务创建页的启动命令中填写:
exec /bin/bash "$WORK_ROOT/fixtures/cartpole/bounded-v1/launch.sh"RLinf 正常退出、num_trajectories >= 2 且输出文件写入成功时,脚本返回 0。
Step 5 确认配置并创建任务
在最终摘要中确认 1 个 Worker、1 张 GPU、单机框架、自己的租户镜像、共享存储路径和 bounded-v1/launch.sh 启动命令,然后点击 确认创建。
Step 6 确认 Worker 通过预检并启动 RLinf 组件
任务进入运行中后,在任务日志中依次确认:
IMAGE_TAG、RLINF_COMMIT、RUN_ID、OUTPUT_DIR和HYDRA_DIR与任务记录一致。- 日志中只有一条
NVIDIA_DRIVER_VERSION=<version>,并记录 1 张目标 GPU、CUDA 版本和至少 32 GiB/dev/shm。 - 固定版本的代码和资源通过 SHA-256 检查,日志中没有克隆或下载行为。
- RLinf 启动的本地 Ray 显示 1 个节点和 1 张 GPU。
- ActorGroup、RolloutGroup 和 EnvGroup 均已创建,并使用 GPU
0。
这些检查都通过后,等待训练结束。
Step 7 确认至少两条轨迹和一次 PPO 更新
任务结束前,在任务日志和 TensorBoard 中核对:
- 日志出现
NUM_TRAJECTORIES_MAX=<number>,且该值不小于2; train/actor/grad_norm以及其他train/损失或优化器标量存在且为有限值;- 日志中出现
PERSISTED_OUTPUT_READY=1; - 训练进程退出码为
0,任务最终显示运行成功。
页面显示运行成功后,仍需确认轨迹数和 PPO 更新结果。
Step 8 Worker 结束后检查共享存储中的输出
任务结束后,在挂载同一共享存储的 AICoder 或开发机中执行:
set -euo pipefail
export RUN_ID=cartpole-20260820-01
export WORK_ROOT=/mnt/user_dir/your-user-name/rlinf-reproduction
export EXPECTED_IMAGE_TAG=cr.infini-ai.com/your-tenant-id/rlinf:agentic-rlinf0.4-embodichain
: "${RUN_ID:?Set the completed run ID}"
: "${WORK_ROOT:?Set WORK_ROOT to the completed run directory on shared storage}"
: "${EXPECTED_IMAGE_TAG:?Set the complete image tag used by the task}"
OUTPUT_DIR="${WORK_ROOT%/}/runs/cartpole/$RUN_ID"
RUN_MANIFEST="$OUTPUT_DIR/run-manifest.txt"
EXPECTED_RLINF_COMMIT=3cb54c2eb2e355378ba049d50f7ec0cb8acc2da0
test -s "$OUTPUT_DIR/driver.log"
test -s "$RUN_MANIFEST"
test -s "$OUTPUT_DIR/overrides.txt"
test -s "$OUTPUT_DIR/hydra/.hydra/config.yaml"
test -s "$OUTPUT_DIR/hydra/.hydra/hydra.yaml"
test -s "$OUTPUT_DIR/hydra/.hydra/overrides.yaml"
test -s "$OUTPUT_DIR/tensorboard/config.yaml"
find "$OUTPUT_DIR/tensorboard" -type f -name 'events.out.tfevents.*' -size +0 -print -quit \
| grep -q .
grep -aFx "IMAGE_TAG=$EXPECTED_IMAGE_TAG" "$OUTPUT_DIR/driver.log"
grep -aFx "RLINF_COMMIT=$EXPECTED_RLINF_COMMIT" "$OUTPUT_DIR/driver.log"
test "$(grep -aEc '^NVIDIA_DRIVER_VERSION=[^[:space:]]+$' "$OUTPUT_DIR/driver.log")" -eq 1
grep -aFx 'PERSISTED_OUTPUT_READY=1' "$OUTPUT_DIR/driver.log"
grep -aF 'NUM_TRAJECTORIES_MAX=' "$OUTPUT_DIR/driver.log" | tail -1 \
| awk -F= '$2 >= 2 { found=1 } END { exit !found }'
find "$OUTPUT_DIR" -maxdepth 4 -type f -printf '%P\t%s\n' | sort所有命令退出码为 0 时,说明训练日志、配置和 TensorBoard 文件在任务结束后仍可读取。
重新运行失败任务
启动脚本检测到输入、GPU、共享内存、Ray、轨迹或输出异常时会返回非 0。保留本次输出目录以便排查,然后使用新的 RUN_ID 创建任务。通用问题请按训练故障处理检查任务事件、日志和存储挂载。
处理本场景特有故障
以下问题与本场景的本地 Ray、短轨迹或固定资源直接相关。其他 AIStudio 任务问题使用通用故障处理文档。
num_trajectories 始终为 0
在 overrides.txt 和解析后配置中确认 train/eval 的 max_steps_per_rollout_epoch 均为 8。值为 1 时,环境可能无法在 max_episode_steps: 8 内完成一条轨迹。修正后使用新 RUN_ID 重跑;日志出现 NUM_TRAJECTORIES_MAX>=2 表示问题已解决。
启动脚本检测到已有 Ray 运行时
本场景需要 RLinf 自己启动本地 Ray。检查任务环境变量中是否设置了 RAY_ADDRESS,并移除启动命令前的 ray start 或 ray job submit。重新创建任务后,RLinf 应启动一个本地节点。
CartPole 资源无法离线读取
在 AICoder 或开发机中重新执行 sha256sum -c SHA256SUMS,并确认资源路径与 EMBODICHAIN_DATA_ROOT 一致。资源缺失或损坏时,重新下载到新目录并生成校验清单。
Hydra 或 TensorBoard 文件没有写入共享存储
检查启动日志中的 OUTPUT_DIR 和 HYDRA_DIR,再核对 hydra.run.dir、hydra.output_subdir 和 runner.logger.log_path 的有效值。Hydra 元数据应位于 $OUTPUT_DIR/hydra/.hydra/,TensorBoard 文件应位于 $OUTPUT_DIR/tensorboard/。路径指向 Worker 本地目录时,修正启动脚本并使用新 RUN_ID 重跑。
确认本示例的使用范围
本文使用宁夏 B、1 个 Worker 和 1 张 A100-SXM4-80GB。您也可以在自己的账号中选择其他符合要求的资源,但镜像、任务和共享存储需要位于同一可用区。
- 任务中断后需要重新运行:本配置不生成 checkpoint。Spot 资源中断后,使用新的
RUN_ID从头运行。需要恢复训练进度时,请改用支持 checkpoint 保存和加载的配置。 - 训练指标可能不同:EmbodiChain 日志可能显示
Environment seed : None。比较多次运行时,以至少 2 条完整轨迹、PPO 更新和退出码为准,不要求每个指标完全相同。 - 本示例不用于性能测试:
rollout.enable_cuda_graph=false时可能出现 CUDA stream 警告。轨迹、PPO 更新和退出码正常即可完成本示例,但该结果不代表完整训练的性能、稳定性或收敛情况。