PPO¶
PPO is the default synchronous on-policy training path. It uses
src/unilab/scripts/train_rsl_rl.py, composes from src/unilab/conf/ppo/config.yaml, and drives
upstream RSL-RL (rsl-rl-lib) directly: unilab.rl.RslRlVecEnvAdapter
adapts the env to RSL-RL’s VecEnv contract, and RSL-RL’s own OnPolicyRunner
trains rsl_rl.algorithms:PPO. The config follows the native RSL-RL v5 schema —
algo.actor: / algo.critic: model blocks (e.g. class_name: rsl_rl.models.MLPModel,
hidden_dims, obs_normalization) plus an algo.algorithm: block with
class_name: rsl_rl.algorithms:PPO.
Quick Start¶
uv run train --algo ppo --task go2_joystick_flat --sim mujoco
uv run train --algo ppo --task go2_joystick_flat --sim mujoco training.no_play=true
Common Overrides¶
uv run train --algo ppo --task go2_joystick_flat --sim mujoco \
algo.num_envs=2048 \
algo.max_iterations=300 \
training.no_play=true
Use uv run eval for checkpoint playback:
uv run eval --algo ppo --task go2_joystick_flat --sim mujoco --load-run -1
Logs are grouped by algo.algo_log_name; the default in src/unilab/conf/ppo/config.yaml
is rsl_rl_ppo.
Single-node multi-GPU training¶
CUDA_VISIBLE_DEVICES (one entry per rank) enables RSL-RL’s synchronous data-parallel PPO path:
export CUDA_VISIBLE_DEVICES=<gpu-a>,<gpu-b>
uv run train --algo ppo --task go2_joystick_flat --sim mujoco \
training.no_play=true
uv run train --algo ppo --task g1_motion_tracking --sim mujoco \
training.no_play=true
CUDA_VISIBLE_DEVICES is the sole GPU topology source. Parent entries may be
physical ordinals, UUIDs, or MIG tokens; torchrun remaps each worker to one
visible GPU, so every rank uses its local cuda:0. training.device remains
only an explicit single-device override and cannot replace rank-local
visibility.
For the IsaacGym, IsaacSim, and Genesis owners, the same topology is also
applied to the simulator environment. Genesis selects its process-wide session
before gs.init.
algo.num_envs is a per-rank count, not a global budget. For W ranks,
N configured envs, and rollout length T:
local samples / iteration = N * T
global samples / iteration = W * N * T
Each rank owns an independent env, policy copy, and rollout storage. Rollouts,
GAE, advantage normalization, and mini-batch shuffling stay rank-local.
RSL-RL broadcasts rank 0’s model state at startup and averages gradients after
every PPO mini-batch backward pass. Rank i uses algo.seed + i.
training.num_timesteps is interpreted as a global sample budget and therefore
uses W * N * T when deriving the iteration count.
Only rank 0 writes TensorBoard/W&B data, checkpoints, run_config.json, and
run_summary.json, then optionally enters playback after all ranks have closed
their process group. run_summary.json records world_size, per-rank/global
env counts, samples per iteration, and aggregate training throughput. RSL-RL’s
Perf/total_fps is also an aggregate global samples/s metric; reward and episode
statistics remain rank-0-local.
RSL-RL does not synchronize observation-normalizer buffers or environment
curriculum state after startup. Consequently, tasks with observation
normalization (algo.actor.obs_normalization, including the current Go2 flat
owner) keep rank-local statistics, and the checkpoint contains rank 0’s copy.
This is the upstream RSL-RL distributed semantic, not global-rollout PPO.
The integrated launcher is single-node only. The repository has two-GPU MuJoCo
smoke coverage for go2_joystick_flat and g1_motion_tracking; this does not
claim multi-node or Motrix multi-GPU support. On the currently validated RTX
6000D host, the launcher inherits the production NCCL compatibility defaults
NCCL_P2P_DISABLE=1 and NCCL_SHM_DISABLE=1; explicit environment values win.