Skip to content

[Core] Decide visualizers and the device once in launch_simulation - #8125

Merged
ooctipus merged 12 commits into
isaac-sim:developfrom
ooctipus:octi/launch-decide-once
Sep 30, 2026
Merged

ooctipus merged 12 commits into
isaac-sim:developfrom
ooctipus:octi/launch-decide-once

Conversation

@ooctipus

@ooctipus ooctipus commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Description

This is step 2 of the launch-consistency series, after #8111 and #8112: each run-time choice is decided once, in launch_simulation, and consumers read the resolved config instead of re-deciding it.

Visualizers

  • Resolved once. launch_simulation resolves the final visualizer list: --viz none gives none; an explicit --viz a,b gives exactly those, reusing a configured visualizer of the same type (with its settings) or else a default; with no --viz, the config's own list; livestreaming adds Kit; and --max_visible_envs applies to the result. The list is written into the config's sim.visualizer_cfgs before any launcher starts, so a missing visualizer package fails before Kit starts.
  • An explicit --viz governs everything derived from visualizers. A configured newton_rtx or Kit visualizer that --viz drops no longer starts OVRTX, trips the Kit/OVRTX conflict check, or turns on the Kit streaming view. XR auto-starts only when the run has no Kit visualizer.
  • SimulationContext reads cfg.visualizer_cfgs. Its settings-based re-resolution and the /isaaclab/visualizer/explicit and /disable_all settings are gone. Code that builds a SimulationContext directly gets its config's visualizers.
  • build_simulation_context(visualizers=...) is removed. It had no callers and on develop only set a settings key without creating the visualizers.
  • Public selection helpers. parse_visualizer_csv, resolve_visualizer_cfgs, get_visualizer_install_hint, VISUALIZER_TYPES and VISUALIZER_ALIASES live in isaaclab.visualizers.visualizer_cfg. VISUALIZER_TYPES maps each type to its default config as a "module:Class" string, and scan() returns the typed SimulationCfg it found, so launch_simulation no longer probes with getattr/hasattr.
  • Remaining fallback: a launch with no SimulationCfg to write into (a physics config or None, as in several tutorials) still passes the selection through /isaaclab/visualizer/types, which SimulationContext applies once with the same function. It goes away when those entry points launch with their real config.

The command-line convention changes (--viz none removal, no visualizer without --viz, --video [SOURCE]) are split out into the follow-up PR.

Device

  • Resolved once: --device, or this rank's GPU in a distributed run, falling back to the config's device when --device is not given, then the launcher's refinement (for example XR picks the CPU), then cuda normalized to cuda:N. The result is written once to the config and to the launcher args. validate_distributed_device and its six callers are removed, since each rank now resolves its own cuda:N.
  • Consequences: apply_env_overrides no longer writes the device (its apply_device argument is removed), and simple_agents no longer overwrites --device with the task's device.

One config scan per run

  • RL run summary: it no longer runs its own scan(). The RL backends print it inside launch_simulation, from the resolved config.
  • Mimic: generate_dataset.py launches with its real config instead of a throwaway one.
  • Two locomanipulation scripts still scan separately, because their task configs import USD and can only be parsed after Kit starts.

Follow-up in this PR

  • Top-level imports where cheap. Function-local imports move to module top wherever measuring shows they load no pxr, omni, carb, isaacsim, or usdrt before Kit starts: 100 → 71 local imports across the launch stack, SimulationContext, the RL entrypoints, and mimic. The rest stay local for a stated reason: Kit-only, an optional dependency, or kept cheap on purpose (for example isaaclab.test.utils, which many Kit-free tests import, and the benchmark dispatcher).
  • SimulationContext visualizer list. visualizer_cfgs is turned into a list once (direct users now see [] instead of None), and default visualizer configs are built without a catch-all try/except.
  • Video recording after launch. apply_video_recording is wired after launch everywhere and reads the resolved visualizer list. pre_launch_video_config is the only place that adds a headless Kit visualizer, before launch, so a Kit visualizer is never injected too late to start Kit. A config with only streaming visualizers now errors on --video instead of picking a source that cannot capture.

Bugs fixed

  • --viz none: the run summary listed the config's visualizers although none ran; it now shows none (headless).
  • Distributed runs: the summary showed the default device instead of each rank's device.

The summary now names the concrete backend (for example ovphysx) rather than physx (ovphysx), because the resolved config no longer records which automatic selector chose it.

Net: +667 / −853.

Type of change

  • Bug fix
  • Breaking change: removed the /isaaclab/visualizer/explicit and /disable_all settings, the has_kit_streaming_view key of visualizer_intent, build_simulation_context(visualizers=...), apply_env_overrides(apply_device=...) and validate_distributed_device; dropped the leading underscore from the visualizer selection helpers, and show_run_summary must be called inside the launch

Validation

All runs on GPU 0 except the multi-GPU check.

  • New tests, each failing on the base:
    • --viz none summary;
    • summary device taken from the resolved config;
    • device resolution for --device cuda:1, cuda, and cpu, and Kit starting on the config's device without --device;
    • visualizers resolved into the config for none and explicit selections, plus the fallback path;
    • an explicit --viz that drops a configured newton_rtx or Kit streaming view starts neither.
  • Kit-free tests (266): launcher and app, visualizer intent, runtime compatibility, distributed device, simulation-context visualizers, visualizers, RL entrypoints and video recording.
  • Kit tests, each in its own process: test_kit_launcher (86), test_simulation_context (26), test_simulation_context_kit (4).
  • Training: Cartpole with physics=isaacsim_physx and physics=newton_mjwarp each run 2 iterations; the summaries show the right backend and cuda:0.
  • Video: train.py ... physics=isaacsim_physx --video --video_length 20 --video_interval 20 writes two mp4 clips headless.
  • Multi-GPU: torch.distributed.run --nproc_per_node=2 train.py --task Isaac-Cartpole --distributed on GPUs 0 and 1, with physics=newton_mjwarp and physics=isaacsim_physx (Kit in each rank): both exit 0, and rank 0 and rank 1 each report and run on their own cuda:0 / cuda:1.
  • Import purity: importing the changed modules loads no pxr, omni, carb, isaacsim, or usdrt, and the env-config import-purity tests pass.
  • Not run: XR, livestream, and the imitation-learning scripts.

Release backport

  • Backport this pull request to the active release branch after it merges into develop

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective
  • I have added changelog fragments for every touched package

launch_simulation resolves the final visualizer list (--viz none, an explicit
--viz selection, the config's visualizers, livestream's Kit visualizer,
--max_visible_envs) and the device (--device or the distributed rank, then the
launcher's refinement, then cuda:N) once, and writes both into the config.
SimulationContext reads cfg.visualizer_cfgs instead of re-applying the rule from
settings, apply_env_overrides no longer writes the device, and the RL run
summary reads the resolved config after launch instead of scanning it again.
This fixes the summary listing configured visualizers under --viz none and the
default device on distributed runs.
@ooctipus
ooctipus requested a review from a team September 29, 2026 04:21
@ooctipus

Copy link
Copy Markdown
Collaborator Author

run-ci

@github-actions github-actions Bot added isaac-mimic Related to Isaac Mimic team infrastructure labels Sep 29, 2026
@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 29, 2026
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[High risk] Refactors how visualizers and device are resolved at launch time.

The PR should not merge until an explicit simulation-context builder selection is protected from a pending selection left by an earlier launch.

Findings

  1. P1 Pending visualizers override builder choice ▶

Summary

The PR moves visualizer and device resolution into launch_simulation, makes simulation contexts consume resolved visualizer configs, and prints RL run summaries after launch.

  • Mimic dataset generation now reuses its launched environment config.
  • The direct simulation-context builder now resolves its visualizers argument, but a pending selection from an earlier bare-config launch can override it.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Launch configuration] --> B[launch_simulation]
  B --> C[Resolved device and visualizer configs]
  C --> D[SimulationContext]
  B --> E[Pending settings for bare-config launches]
  E --> D
  F[build_simulation_context explicit visualizers] --> D
  D --> G[Pending settings can replace explicit selection]
Loading

Reviews (1) · Last reviewed commit: "Decide visualizers and the device once i..."

Comment on lines +946 to +948
sim_cfg.visualizer_cfgs = _resolve_visualizer_cfgs(
sim_cfg.visualizer_cfgs, _parse_visualizer_csv(visualizers)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pending visualizers override builder choice A launch with a bare physics config leaves its visualizer selection in process-wide settings. If build_simulation_context(visualizers=["kit"]) runs afterward, this code selects Kit, but SimulationContext.__init__ then reapplies the earlier selection. The builder can therefore create the wrong visualizer instead of the one explicitly requested.

Knowledge Base Used: Core simulation framework

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isaac Lab Review Bot

Reviewed the centralization of visualizer and device resolution in launch_simulation, including the SimulationCfg mutation path, the fallback for launches without a simulation config, RL summary ordering, and mimic config reuse. No candidate findings were supplied or supported as merge-blocking issues.

  • Design and architecture: The patch establishes launch_simulation as the primary owner of resolved device and visualizer state while retaining a documented settings-based compatibility path for callers that construct SimulationCfg after launch. The producer and consumer paths are internally consistent, though the process-global fallback remains a transitional state-management tradeoff.
  • API: The documented breaking removals and call-order requirement are reflected in changelog fragments, and the shown in-repository callers are migrated. The additive setup_env_config(env_cfg=...) parameter preserves the existing parse-on-None behavior.
  • Implementation: Traced device precedence through CLI, distributed rank, config fallback, launcher refinement, and CUDA index normalization. Also checked visualizer list normalization, stale-setting clearing for config-owning launches, summary reads after resolution, and the mimic hand-off of the launched config. The fallback settings path and pre-launch optional-backend imports remain areas worth monitoring, but the supplied patch does not demonstrate a concrete failure requiring action.

No blocking issues. No inline issue met the actionable-evidence threshold; the assessment above records the review feedback.

Automated review; human maintainers own approval decisions.

- Move function-local imports to module top wherever measuring shows they load
  no pxr/omni/carb/isaacsim/usdrt before Kit starts; the rest stay local with
  the reason (Kit-only, optional dependency, or kept cheap on purpose, such as
  isaaclab.test.utils, which many Kit-free tests import).
- SimulationContext turns visualizer_cfgs into a list once, drops the helper
  and getattr guards around it, and builds default visualizer configs without
  a catch-all try/except.
- Video recording is wired after launch everywhere and reads the resolved
  visualizer list; pre_launch_video_config is the only place that adds a
  headless Kit visualizer, before launch.
@ooctipus

Copy link
Copy Markdown
Collaborator Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 29, 2026
Core must not import a backend package at module level, and sim_launcher loads
every backend's configs, so both imports stay inside the functions that need
them. The PhysX articulation kernel tests stub isaaclab_physx.physics and
failed on the module-level import.
@ooctipus

Copy link
Copy Markdown
Collaborator Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 29, 2026
launch_simulation writes the two visualizer settings itself instead of through a
single-caller helper, and the export backends stop re-applying a device the
launch already set.
from ..utils.string import string_to_callable
from ..visualizers.visualizer_cfg import _VISUALIZER_ALIASES, _VISUALIZER_TYPES, VisualizerCfg
from ..visualizers.visualizer_cfg import (
_VISUALIZER_ALIASES,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid importing _ prefixes into another module, _ means its private to that file

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ad03700: the --viz parser, resolve_visualizer_cfgs, the install hint and the type table are now public names in isaaclab.visualizers.visualizer_cfg, and sim_launcher imports them from there.

if max_visible_envs is not None and int(max_visible_envs) < 0:
raise ValueError(f"Invalid value for --max_visible_envs: {max_visible_envs}. Expected non-negative int.")
visualizers = args.get("visualizer")
def _make_visualizer_cfg(visualizer_type: str) -> VisualizerCfg:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be useful to users outside this file? Maybe we can expose and remove the _?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

visualizers = args.get("visualizer")
def _make_visualizer_cfg(visualizer_type: str) -> VisualizerCfg:
"""Construct the default config of a visualizer type, importing only its backend package."""
module = {"newton_gl": "newton", "newton_rtx": "newton"}.get(visualizer_type, visualizer_type)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the aliasing right? this is not looking good like this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ad03700: replaced the two dicts with one VISUALIZER_TYPES table of "module:Class" strings. It is also the list of valid types, so the separate tuple is gone.

"viser": "ViserVisualizerCfg",
}[visualizer_type]
try:
return getattr(importlib.import_module(f"isaaclab_visualizers.{module}"), class_name)()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove getattr? (in this case it might be better to keep it but always want to consider the alternative)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in ad03700: the lookup now goes through string_to_callable(VISUALIZER_TYPES[visualizer_type]).

_resolve_distributed_device(effective_cfg, args)
# The SimulationCfg the simulation is built from: an env config holds it in ``sim``; a physics config or
# None holds none.
sim_cfg = getattr(effective_cfg, "sim", effective_cfg if hasattr(effective_cfg, "visualizer_cfgs") else None)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getattr

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in ad03700: scan() now records the first SimulationCfg it walks (Scan.sim_cfg), so launch_simulation reads a typed config instead of probing with getattr.

args["device"] = sim_cfg.device

# Decide the visualizers once, into the SimulationCfg.
has_visualizer_cfgs = hasattr(sim_cfg, "visualizer_cfgs")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasattr

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in ad03700: this is now if sim_cfg is not None: on the typed Scan.sim_cfg.

from ..utils.version import has_kit
from ..visualizers.base_visualizer import BaseVisualizer
from ..visualizers.visualizer_cfg import _VISUALIZER_TYPES, _get_visualizer_install_hint
from ..visualizers.visualizer_cfg import _get_visualizer_install_hint

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more _ stuff being imported

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ad03700: SimulationContext imports the public helpers from visualizer_cfg, which depends only on isaaclab.utils, so its two local imports from sim_launcher are gone too.

device = getattr(args_cli, "device", None)
if device is not None:
env_cfg.sim.device = device
if getattr(args_cli, "disable_fabric", False):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more get_attr

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partly done in ad03700. Every parser that reaches this function defines --num_envs, so it's plain attribute access now. The other two stay as getattr on purpose: --disable_fabric exists only on the play parsers, and --export_io_descriptors only on the train parsers, so each parser lacks one of them. The docstring now says this.

- A newton_rtx or Kit visualizer that an explicit --viz selection drops no longer
  starts OVRTX, trips the Kit/OVRTX conflict check, or turns on the Kit streaming
  view.
- XR auto-starts only when the run has no Kit visualizer from the config or --viz.
- Drop validate_distributed_device, which the per-rank cuda:N device made dead,
  and the unused caller-supplied has_kit_streaming_view intent.
@ooctipus

Copy link
Copy Markdown
Collaborator Author

run-ci

@ooctipus

Copy link
Copy Markdown
Collaborator Author

Split for review: this PR now covers deciding visualizers and the device once (the commits Mustafa reviewed, plus the device-test fix and the build_simulation_context(visualizers=) removal). The command-line convention changes (--viz none removal, no visualizer without --viz, --video [SOURCE]) moved to #8198, which is stacked on this one.

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 30, 2026
return list(dict.fromkeys(names))


def _normalize_launcher_args(args: dict) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function name normalize does not give too much info on what this function does, would _resolve be better to match the other _resolve_xxx functions?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to _resolve_launcher_args in d3435b7.

)


def _resolve_device(sim_cfg, args: dict, launchers: list[SimulationLauncher]) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so device is getting resolved, see this other PR: #8188. If we know device from the initial resolution, then it should be passed to all further configs in the argument. (please leave a comment in that PR once this PR is merged)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Once this merges, the launch-resolved device is always on env_cfg.sim.device, so the agent/runner can take it from there instead of agent_cfg.device. I'll leave that note on #8188 after this PR merges.

_VISUALIZER_TYPES = ("kit", "newton_gl", "newton_rtx", "rerun", "viser")
"""Canonical visualizer type names, for ``--visualizer`` and :attr:`VisualizerCfg.visualizer_type`."""
VISUALIZER_TYPES = {
"kit": "isaaclab_visualizers.kit:KitVisualizerCfg",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the goal here is to not import from the subpackages, still it looks a bit sad when written this way

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortened in d3435b7: the entries are now "kit:KitVisualizerCfg" etc., and the isaaclab_visualizers. prefix is added in the one place that resolves them. The string form stays because core isaaclab must not import isaaclab_visualizers subpackages until one is requested. A lazy top-level export in isaaclab_visualizers (lazy_export()) would also work, but it needs a .pyi stub, so I kept this PR to the table.

_MISSING = object()
_PHYSICS_BACKEND_NAMES = {"PhysxCfg": "isaacsim_physx", "OvPhysxCfg": "ovphysx", "PhysxAutoCfg": "physx"}
_RENDERER_BACKEND_NAMES = {"isaac_rtx": "isaacsim_rtx", "newton_warp": "newton_renderer", "auto_rtx": "rtx"}
_PHYSICS_BACKEND_NAMES = {"PhysxCfg": "isaacsim_physx", "OvPhysxCfg": "ovphysx"}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems some backends are missing here, is this correct? is it used for another purpose?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is correct. The table only maps names that can't be derived from the class. Newton configs get theirs from the class plus the solver (NewtonCfg + MJWarpSolverCfg becomes newton_mjwarp) in _physics_backend_name, so they aren't listed. It's only used for the run summary.

_PHYSICS_BACKEND_NAMES = {"PhysxCfg": "isaacsim_physx", "OvPhysxCfg": "ovphysx", "PhysxAutoCfg": "physx"}
_RENDERER_BACKEND_NAMES = {"isaac_rtx": "isaacsim_rtx", "newton_warp": "newton_renderer", "auto_rtx": "rtx"}
_PHYSICS_BACKEND_NAMES = {"PhysxCfg": "isaacsim_physx", "OvPhysxCfg": "ovphysx"}
_RENDERER_BACKEND_NAMES = {"isaac_rtx": "isaacsim_rtx", "newton_warp": "newton_renderer"}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should make the available backends something global/universal, is it defined like this elsewhere in the repo?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet. These two maps predate this PR, and there are similar ones in benchmark/capture.py (renderer names) and preset_target.py (renderer aliases). The next PR in the series turns --physics into physics= presets, which is the natural single home for backend preset names, so I'd consolidate them there rather than here.

assert env_cfg.sim.device == "cuda:1"
mock_set_device.assert_called_once_with("cuda:1")

@pytest.mark.parametrize("device, expected", [("cuda:1", "cuda:1"), ("cuda", "cuda:0"), ("cpu", "cpu")])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these tests be combined with other tests?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. In 50f95cd the file's 6 classes and 13 test functions (342 lines) became two parametrized tests (164 lines) with the same 15 cases: one table for _resolve_distributed_device (multi-GPU, restricted devices, JAX rank, dict args, missing env, multi-node, two no-op cases) and one for launch_simulation device precedence (Kit refines, Kit gets the config device, kitless distributed rank 0/1, --device cuda:1/cuda/cpu). I also dropped the spy on the internal call and now assert the resulting device and set_cuda_device instead.

@ooctipus

Copy link
Copy Markdown
Collaborator Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 30, 2026
@ooctipus

Copy link
Copy Markdown
Collaborator Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 30, 2026
@ooctipus

Copy link
Copy Markdown
Collaborator Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 30, 2026
@ooctipus
ooctipus merged commit f091028 into isaac-sim:develop Sep 30, 2026
55 of 56 checks passed
ooctipus added a commit that referenced this pull request Oct 1, 2026
## Description

Fixes the Kit startup crash introduced by #8125 when MoviePy is
installed. Importing the RL entrypoints pulled in the environment
runtime and `moviepy.editor`, including unrelated editing dependencies,
before Kit launched.

- Import the MARL wrapper only when constructing the wrapped
environment, after simulation launch.
- Import `ImageSequenceClip` directly in the recorder instead of loading
`moviepy.editor`. This preserves recording without importing SciPy,
OpenCV, or matplotlib.
- Add one case to the existing subprocess import test. It catches eager
environment imports even without MoviePy installed.

There are no launcher or distributed-device changes. Torch remains
available before Kit; moving one import did not change that boundary.

## Validation

- 121 tests passed across the entrypoint, distributed-device, and
video-recorder suites, with MoviePy installed.
- The added regression failed when the original eager MARL import was
restored and passed with the fix.
- The recorder wrote and decoded a three-frame MP4 using the direct
import.
- GPU 0: `Isaac-Cartpole-Direct`, Isaac PhysX, 16 environments, one
RSL-RL training iteration completed. A second run with the
environment/video recorder deliberately imported before Kit also
completed.
- Formatting, lint, and changelog checks passed.

## Release backport

- [ ] <!-- backport-active-release --> Backport this pull request to the
active release branch after it merges into `develop`

## Checklist

- [x] I have read and understood the contribution guidelines
- [x] I have run the pre-commit checks
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective
- [x] I have added changelog fragments for every touched package
ooctipus added a commit that referenced this pull request Oct 1, 2026
## Description

Follows #8125. **`--visualizer` decides which visualizers run; the
config only holds their settings. `--video` says what to record.**

### `--visualizer`

| Command line | Before | After |
|---|---|---|
| no flag | the config's visualizers run | **nothing runs** |
| `--viz kit` | Kit | Kit, using the config's Kit settings (e.g. camera)
if it lists one |

- Tasks and tools (`zero_agent`, `random_agent`, `run_cartpole_rl_env`,
...) no longer open a viewer by default. Demos, examples and visualizer
tutorials keep theirs via `parser.set_defaults(visualizer=[...])`.
- Kit opens a window only when `--viz kit` is selected (`HEADLESS=1`
still forces headless). The internal `headless` and `visualizer_intent`
launcher arguments are removed.

### `--video [SOURCE]`

| Command line | Records from |
|---|---|
| `--video` | the `--viz` window, else a headless newton_gl |
| `--video viz:newton_rtx` | newton_rtx (headless unless `--viz` opened
it) |
| `--video sensor:wrist_camera` | that camera sensor |

- `VideoRecorderCfg.source` uses the same format; `visualizer:kit` and
`viz:kit` are equivalent.
- `--video presets=newton_mjwarp` still applies the preset: `--video`
never takes a Hydra override as its value.

### Migration

| Before | Now |
|---|---|
| config lists a visualizer, no flag | add `--viz <type>` |
| `zero_agent.py --task X` to watch | add `--viz newton_gl` |
| `--video` recording from headless Kit | `--video viz:kit` |

## Type of change

- Breaking change (see Migration)

## Validation

- Unit tests for the tables above (selection, recorder source, headless
flags, which launchers start, Hydra overrides).
- Cartpole training on Newton and Isaac Sim PhysX without `--viz`:
headless, no Kit on Newton.
- `--video` on Newton and PhysX, `--video viz:kit`, and `--video
presets=newton_mjwarp`: each writes clips.
- Not run: windowed cases (no display on the test machine; unit-tested),
XR, livestream.

## Release backport

- [ ] <!-- backport-active-release --> Backport this pull request to the
active release branch after it merges into `develop`

## Checklist

- [x] I have read and understood the contribution guidelines
- [x] I have run the pre-commit checks
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective
- [x] I have added changelog fragments for every touched package
AntoineRichard added a commit that referenced this pull request Oct 5, 2026
# 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
AntoineRichard added a commit that referenced this pull request Oct 9, 2026
…t CUDA (#8188)

# Description

Hosts without CUDA, such as macOS, could not train with RSL-RL even with
`--device cpu`. The agent configuration's device (`cuda:0` by default)
was used to build the policy, so the run failed with `AssertionError:
Torch not compiled with CUDA enabled`.

This PR uses the device `launch_simulation` already resolves (since
#8125), and sets it in each RL library's shared agent-config setup:

- **Clear error without CUDA.** `_resolve_device` in
`isaaclab.app.sim_launcher` raises `RuntimeError: 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, #8190
defaults `--device` to `cpu`.
- **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`, which `launch_simulation` resolves) 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`:
- **RSL-RL:** the existing `update_rsl_rl_cfg(agent_cfg, args_cli)` sets
`agent_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.
- **RL-Games:** the new `update_rl_games_cfg(agent_cfg, args_cli)`
mirrors `update_rsl_rl_cfg`: it sets `device` / `device_name` and 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.
- **Stable-Baselines3:** the new `update_sb3_cfg(agent_cfg, args_cli)`
mirrors `update_rsl_rl_cfg`: it sets the device and seed for train,
play, and both benchmarks. Both `PPO.load()` calls pass
`device=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

- Bug fix (non-breaking change which fixes an issue)
- Breaking change (existing functionality will not work without user
modification)

**Breaking:** outside distributed runs, the RSL-RL, RL-Games and SB3 CLI
workflows used to ignore `--device` for the agent:

| Command | Before | After |
|---|---|---|
| `--device cpu` | Simulation on CPU, agent on `cuda:0` | Both on the
CPU |
| `--device cuda:1` | Simulation on GPU 1, agent on GPU 0 (see
#2148) | Both on GPU 1 |

The agent configuration's `device` is 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.py` covers
that and is unaffected. `isaaclab_rl` gets a `.major` fragment.

## Validation

On macOS arm64, CPU-only:

| Run | Result |
|---|---|
| RSL-RL train, `--device cpu` | Trains |
| SB3 train, `--device cpu` | Trains (`Using cpu device`) |
| RL-Games train, `--device cpu` (512 envs) | Completes 2 epochs |
| RSL-RL train, `--device cuda:0` | Stops with `RuntimeError: Device
'cuda:0' requires CUDA, which is not available; pass --device cpu.` |

`source/isaaclab_rl/test/test_entrypoints.py`,
`test_entrypoints_common.py`, and
`source/isaaclab/test/app/test_launch_simulation_require_kit.py`: 83
passed. The one failure is
`test_skrl_training_parser_leaves_algorithm_implicit`, which needs
`skrl`, not installed locally.
`test_run_summary_reports_the_launch_resolved_visualizers_and_device`
now launches on `cpu`: it only used `cuda:0` as a label and still checks
the device resolved after launch.

## 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
- [ ] 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
@ooctipus
ooctipus deleted the octi/launch-decide-once branch October 14, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation infrastructure isaac-mimic Related to Isaac Mimic team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants