Repository navigation
[Mac OS] [RL] Run RL agents on the simulation device and raise without CUDA - #8188
Conversation
|
| device, | ||
| env.device, | ||
| ) | ||
| device = str(env.device) |
There was a problem hiding this comment.
Saved agent device is inaccurate
When CUDA is unavailable, this fallback can build the runner on the environment’s CPU device, but it does not update agent_cfg.device. Training later saves that unchanged configuration to params/agent.yaml, so a CPU run records cuda:0 as its agent device. This makes the saved configuration misleading when reproducing the run; please record the effective device.
There was a problem hiding this comment.
Isaac Lab Review Bot
The CPU-only fallback is correctly placed at the shared RSL-RL runner factory and has focused regression coverage, but the runner now receives conflicting device values: the explicit constructor argument uses the resolved environment device while the configuration dictionary still contains the unavailable configured CUDA device.
- Design and architecture: Resolving the fallback in
create_rsl_rl_runnerappropriately covers training, playback, and export. However, the factory now passes two disagreeing device authorities into the runner:devicecontains the resolved fallback, whileto_dict(agent_cfg)retainsagent_cfg.device. Resolve the device once and ensure the dictionary passed to both runner types records the same value. - API: The public function signature, return types, supported runner classes, and unsupported-class
ValueErrorremain unchanged. The new behavior is internal device resolution, but the configuration representation passed across the RSL-RL boundary should be kept consistent with the explicit device argument. - Compatibility and deprecation: Breaking changes: none identified. The fallback activates only when a configured CUDA device is unavailable, while CUDA-capable hosts retain the configured behavior. No public signature, default, configuration key, or export surface was removed or renamed.
- Implementation: Both
OnPolicyRunnerandDistillationRunneruse the resolved localdevice, and the warning identifies the configured and fallback devices. The remaining issue is that both calls still passto_dict(agent_cfg)unchanged, leavingcuda:0in the runner configuration when the explicit constructor device is CPU; update the dictionary before construction so runtime and stored configuration agree. - Style consistency: The module-level imports, logger placement, naming, warning construction, docstring update, and changelog fragment follow adjacent repository patterns. No separate style issue was identified.
- Test quality: The new test is a justified owner-boundary regression: it constructs a real
OnPolicyRunner, verifies CPU policy placement, and checks the warning on the no-CUDA path, which existing configuration-migration tests do not cover. It does not currently check that the configuration passed into the runner records the resolved device, so the conflicting-device state remains undetected.
Minor fixes needed. Posted 1 actionable finding inline.
Automated review; human maintainers own approval decisions.
| device = str(env.device) | ||
| if agent_cfg.class_name == "OnPolicyRunner": | ||
| return OnPolicyRunner(env, to_dict(agent_cfg), log_dir=log_dir, device=agent_cfg.device) | ||
| return OnPolicyRunner(env, to_dict(agent_cfg), log_dir=log_dir, device=device) |
There was a problem hiding this comment.
🔵 Suggestion · Design Architecture — Runner receives two conflicting devices
The fallback only changes the local variable, so to_dict(agent_cfg) passed as the runner's train config still records cuda:0 while the runner is constructed on cpu. The runner stores and serializes that config, so dumped and resumed agent configs keep the unavailable device. Resolve the device once and keep the config consistent, for example by updating the device entry in the dictionary handed to the runner.
| ) -> OnPolicyRunner | DistillationRunner: | ||
| """Instantiate the RSL-RL runner selected by ``agent_cfg.class_name``. | ||
|
|
||
| The runner uses ``agent_cfg.device``, or the environment's device when that is a CUDA device and |
There was a problem hiding this comment.
We can remove this docstring addition.
| ValueError: If the configured runner class is not supported. | ||
| """ | ||
| device = agent_cfg.device | ||
| if device.startswith("cuda") and not torch.cuda.is_available(): |
There was a problem hiding this comment.
This looks fine to me, but maybe the problem should be caught earlier that agent_cfg.device != env_cfg.device, and if either == cuda and cuda is not available then they've picked wrong?
There was a problem hiding this comment.
yeah I have an issue to track this: #8193 it's a real problem that I would fix separately.
| @@ -0,0 +1,73 @@ | |||
| # Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). | |||
|
Heads-up from #8125 (now merged), as @StafaH asked: |
…l-runner-cpu-fallback
…l-runner-cpu-fallback # Conflicts: # source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_play_rsl_rl.py # source/isaaclab_rl/isaaclab_rl/entrypoints/backends/export_rsl_rl.py # source/isaaclab_rl/isaaclab_rl/entrypoints/backends/play_rsl_rl.py
| device = args.get("device") | ||
| for launcher in launchers: | ||
| device = launcher.device or device | ||
| if device is not None and device.startswith("cuda") and not torch.cuda.is_available(): |
There was a problem hiding this comment.
id rather raise an error than fall back if device startwith("cuda")
ooctipus
left a comment
There was a problem hiding this comment.
Can we set the agent device as part of shared RL setup after launch? Right now each workflow has to remember to do it, including the benchmarks. This should happen before the agent is built and its config is saved. I've marked the copies below. I wouldn't move the RL-specific config handling into the simulation launcher, or add a one-line setter that still has to be called from every workflow.
| if args_cli.max_iterations is not None: | ||
| agent_cfg.max_iterations = args_cli.max_iterations | ||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg.device = env_cfg.sim.device |
There was a problem hiding this comment.
We already call update_rsl_rl_cfg() a few lines above. Can the resolved device be applied there with the other agent settings, and have the other RSL-RL workflows use the same setup?
| show_run_summary(screen, args_cli, env_cfg, library="rsl_rl", action="play") | ||
| agent_cfg = cli_args.update_rsl_rl_cfg(agent_cfg, args_cli) | ||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg.device = env_cfg.sim.device |
There was a problem hiding this comment.
Same as training: this comes straight after update_rsl_rl_cfg(). Can that call handle the resolved device too?
| if args_cli.experiment_name is not None: | ||
| agent_cfg.experiment_name = args_cli.experiment_name | ||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg.device = env_cfg.sim.device |
There was a problem hiding this comment.
Can export go through the same RSL-RL config setup as train/play? Otherwise it's another place we have to keep this assignment in sync.
| env_cfg.seed = agent_cfg.seed | ||
|
|
||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg.device = env_cfg.sim.device |
There was a problem hiding this comment.
This already uses update_rsl_rl_cfg() too. Can it pick up the device there so regular training and the benchmark share the same setup?
| env_cfg.seed = agent_cfg.seed | ||
|
|
||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg.device = env_cfg.sim.device |
There was a problem hiding this comment.
Can benchmark play use the same RSL-RL config setup as regular play? The device should already be set by the time we get here.
|
|
||
| # Read rl_games device / clipping config. | ||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg["params"]["config"]["device"] = agent_cfg["params"]["config"]["device_name"] = env_cfg.sim.device |
There was a problem hiding this comment.
This repeats the device setup from regular play. Can both paths get a config with the device already set?
| agent_cfg["n_timesteps"] = args_cli.max_iterations * agent_cfg["n_steps"] * env_cfg.scene.num_envs | ||
| env_cfg.seed = agent_cfg["seed"] | ||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg["device"] = env_cfg.sim.device |
There was a problem hiding this comment.
Can we set this during shared SB3 setup, before saving agent_cfg and constructing PPO? The training benchmark repeats the same assignment.
| agent_cfg["seed"] = args_cli.seed if args_cli.seed is not None else agent_cfg.get("seed", 0) | ||
| env_cfg.seed = agent_cfg["seed"] | ||
| # the agent runs on the device launch_simulation resolved for the simulation | ||
| agent_cfg["device"] = env_cfg.sim.device |
There was a problem hiding this comment.
Same device assignment as regular SB3 training. Can both use the same config setup?
|
|
||
| print(f"Loading checkpoint from: {checkpoint_path}") | ||
| agent = PPO.load(checkpoint_path, env, print_system_info=True) | ||
| agent = PPO.load(checkpoint_path, env, device=env_cfg.sim.device, print_system_info=True) |
There was a problem hiding this comment.
Keep this explicit device argument when consolidating SB3 setup; changing agent_cfg alone won't affect PPO.load().
|
|
||
| # Load the trained policy. | ||
| agent = PPO.load(resume_path, env, print_system_info=True) | ||
| agent = PPO.load(resume_path, env, device=env_cfg.sim.device, print_system_info=True) |
There was a problem hiding this comment.
Same point as regular SB3 play: the shared setup needs to cover checkpoint loading too. This explicit device argument is needed, so we shouldn't just remove it.
…l-runner-cpu-fallback
# Description `--device` defaults to `cuda:0`. On macOS, which has no CUDA, every command without an explicit `--device cpu` fails at simulation start (`Torch not compiled with CUDA enabled` in `SimulationContext._init_usd_physics_scene`). The default is now `cpu` on macOS and stays `cuda:0` elsewhere. It checks `sys.platform` rather than `torch.cuda.is_available()` because the launcher defers importing torch until after Kit starts (see the comment in `_resolve_device_settings`). An explicit `--device` still wins, and `device_explicit` is untouched, so the XR device logic is unaffected. `launch_simulation` logs a warning when it applies this default (`No --device given; using 'cpu' because macOS has no CUDA.`), so the switch is visible. Passing `--device` explicitly suppresses it. Hosts that request a CUDA device without having CUDA are handled separately by #8188, which falls back to the CPU with its own warning. Part of the experimental macOS (Apple silicon, CPU-only) support series. ## Type of change - New feature (non-breaking change which adds functionality) ## Validation - On macOS arm64, rebased on `develop` with #8125: `isaaclab train --rl_library rsl_rl --task Isaac-Cartpole-Direct physics=newton_mjwarp --max_iterations 2` (no `--device`) logs the warning, reports `Device cpu`, and trains to completion. This also needs #8188. - With `--device cpu`, no warning is logged. ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `uv run isaaclab --format` (all hooks pass except `check-git-lfs-pointers`, which needs git-lfs installed locally) - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have validated the changed behavior with focused coverage and applied the [test-audit criteria](https://github.com/isaac-sim/IsaacLab/blob/develop/skills/developer/test-audit/SKILL.md) to any test changes - [x] I have added a changelog fragment under `source/<pkg>/changelog.d/` for every touched package (do **not** edit `CHANGELOG.rst` or bump package versions — CI handles that) - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there
|
run-ci |
Description
Hosts without CUDA, such as macOS, could not train with RSL-RL even with
--device cpu. The agent configuration's device (cuda:0by default) was used to build the policy, so the run failed withAssertionError: Torch not compiled with CUDA enabled.This PR uses the device
launch_simulationalready resolves (since #8125), and sets it in each RL library's shared agent-config setup:Clear error without CUDA.
_resolve_deviceinisaaclab.app.sim_launcherraisesRuntimeError: Device 'cuda:0' requires CUDA, which is not available; pass --device cpu.when the resolved device is a CUDA device and CUDA is unavailable. Before, the run failed later inside PyTorch. On macOS, [Mac OS] Default --device to cpu on macOS #8190 defaults--devicetocpu.The agent runs on the resolved device, set in one shared setup per library. Each library's config setup takes the device (
args_cli.device, whichlaunch_simulationresolves) as an explicit argument. Every workflow calls it after launch, before the agent is built or its config is saved. This is unconditional, not only for--distributed:update_rsl_rl_cfg(agent_cfg, args_cli)setsagent_cfg.device = args_cli.device, for train, play, and benchmark train. Export and benchmark play, whose CLIs don't define the RSL-RL training arguments, set the device directly.update_rl_games_cfg(agent_cfg, args_cli)mirrorsupdate_rsl_rl_cfg: it setsdevice/device_nameand the seed for train, play, export, and both benchmarks. RL-Games export gains--seed, matching RSL-RL export, so every RL-Games workflow can call it.update_sb3_cfg(agent_cfg, args_cli)mirrorsupdate_rsl_rl_cfg: it sets the device and seed for train, play, and both benchmarks. BothPPO.load()calls passdevice=agent_cfg["device"], so checkpoint loading follows the same setup.skrl and TorchRL already used the simulation's device. The saved agent config records the device actually used, which addresses the bot findings.
Closes #8193.
Part of the experimental macOS (Apple silicon, CPU-only) support series.
Type of change
Breaking: outside distributed runs, the RSL-RL, RL-Games and SB3 CLI workflows used to ignore
--devicefor the agent:--device cpucuda:0--device cuda:1The agent configuration's
deviceis now overwritten by these workflows. The wrappers still support running the simulation and the agent on different devices for custom scripts;source/isaaclab_tasks/test/core/test_rl_device_separation.pycovers that and is unaffected.isaaclab_rlgets a.majorfragment.Validation
On macOS arm64, CPU-only:
--device cpu--device cpuUsing cpu device)--device cpu(512 envs)--device cuda:0RuntimeError: Device 'cuda:0' requires CUDA, which is not available; pass --device cpu.source/isaaclab_rl/test/test_entrypoints.py,test_entrypoints_common.py, andsource/isaaclab/test/app/test_launch_simulation_require_kit.py: 83 passed. The one failure istest_skrl_training_parser_leaves_algorithm_implicit, which needsskrl, not installed locally.test_run_summary_reports_the_launch_resolved_visualizers_and_devicenow launches oncpu: it only usedcuda:0as a label and still checks the device resolved after launch.Checklist
pre-commitchecks withuv run isaaclab --format(all hooks pass exceptcheck-git-lfs-pointers, which needs git-lfs installed locally)source/<pkg>/changelog.d/for every touched package (do not editCHANGELOG.rstor bump package versions — CI handles that)CONTRIBUTORS.mdor my name already exists there