From 86f8ea2f2d71f971dbceb77d6e2377bccd6bb35d Mon Sep 17 00:00:00 2001 From: jichuanh Date: Sat, 30 May 2026 21:13:56 +0000 Subject: [PATCH 1/3] Honor device kwarg over sim_cfg.device in build_simulation_context Most test callers pass both ``sim_cfg=`` and ``device=`` to :func:`isaaclab.sim.build_simulation_context`, implicitly expecting the ``device`` kwarg to win. The helper previously dropped the kwarg silently when ``sim_cfg`` was provided, causing warp kernel-launch device mismatches on non-default GPUs: the test fixture allocated ``env_ids`` on the requested device while the articulation's ``self.device`` resolved from the untouched ``sim_cfg`` default (``cuda:0``), and ``wp.launch(..., device=self.device)`` failed with:: RuntimeError: Error launching kernel 'set_root_link_pose_to_sim_index', trying to launch on device='cuda:0', but input array for argument 'env_ids' is on device=cuda:2. Change ``device``'s default to ``None`` (sentinel) and apply it as an override after sim_cfg construction in both branches. The one test that asserted the old "sim_cfg overrides everything" contract is updated to cover the new override semantics. --- .../jichuanh-fix-build-sim-context-device.rst | 11 ++++++++++ .../isaaclab/sim/simulation_context.py | 20 ++++++++++++++++--- .../test_build_simulation_context_headless.py | 19 +++++++++++++++--- ...st_build_simulation_context_nonheadless.py | 20 +++++++++++++++---- 4 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 source/isaaclab/changelog.d/jichuanh-fix-build-sim-context-device.rst diff --git a/source/isaaclab/changelog.d/jichuanh-fix-build-sim-context-device.rst b/source/isaaclab/changelog.d/jichuanh-fix-build-sim-context-device.rst new file mode 100644 index 000000000000..89e774bd26d7 --- /dev/null +++ b/source/isaaclab/changelog.d/jichuanh-fix-build-sim-context-device.rst @@ -0,0 +1,11 @@ +Fixed +^^^^^ + +* Fixed :func:`isaaclab.sim.build_simulation_context` silently ignoring the + ``device`` kwarg when ``sim_cfg`` is also provided. Most test callers pass + both kwargs together; the helper now applies the explicit ``device`` over + ``sim_cfg.device`` so the caller's choice wins. Without this, warp kernel + launches in :mod:`isaaclab_newton.assets.articulation` raised device + mismatch errors on non-default GPUs (``env_ids`` allocated on the test's + device while the articulation's resolved device came from the untouched + ``sim_cfg`` default ``cuda:0``). diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index c068469ec599..fae7d4d1f510 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -958,7 +958,7 @@ def _predicate(prim: Usd.Prim) -> bool: def build_simulation_context( create_new_stage: bool = True, gravity_enabled: bool = True, - device: str = "cuda:0", + device: str | None = None, dt: float = 0.01, sim_cfg: SimulationCfg | None = None, add_ground_plane: bool = False, @@ -971,7 +971,11 @@ def build_simulation_context( Args: create_new_stage: Whether to create a new stage. Defaults to True. gravity_enabled: Whether to enable gravity. Defaults to True. - device: Device to run the simulation on. Defaults to "cuda:0". + device: Device to run the simulation on. When given alongside ``sim_cfg``, + overrides ``sim_cfg.device`` so the caller's explicit choice wins + (most test callers pass both, expecting this behavior). Defaults to + ``None``, meaning ``sim_cfg.device`` is left untouched and a freshly + built ``sim_cfg`` uses :class:`SimulationCfg`'s default device. dt: Time step for the simulation. Defaults to 0.01. sim_cfg: SimulationCfg to use. Defaults to None. add_ground_plane: Whether to add a ground plane. Defaults to False. @@ -993,7 +997,17 @@ def build_simulation_context( if sim_cfg is None: gravity = (0.0, 0.0, -9.81) if gravity_enabled else (0.0, 0.0, 0.0) - sim_cfg = SimulationCfg(device=device, dt=dt, gravity=gravity) + sim_cfg = SimulationCfg(dt=dt, gravity=gravity) + if device is not None: + # Honor the explicit device kwarg in both branches: when sim_cfg is + # freshly built, this picks the device; when sim_cfg is passed in, + # this overrides its (possibly default) device. Without the override, + # callers passing both ``sim_cfg=`` and + # ``device=cuda:N`` silently got sim_cfg's device, causing warp + # kernel-launch mismatches when test fixtures allocated tensors on + # the requested device while assets resolved their device from the + # untouched sim_cfg. + sim_cfg.device = device sim = SimulationContext(sim_cfg) diff --git a/source/isaaclab/test/sim/test_build_simulation_context_headless.py b/source/isaaclab/test/sim/test_build_simulation_context_headless.py index 8f13d79041e7..4ceae87b9878 100644 --- a/source/isaaclab/test/sim/test_build_simulation_context_headless.py +++ b/source/isaaclab/test/sim/test_build_simulation_context_headless.py @@ -75,7 +75,14 @@ def test_build_simulation_context_auto_add_lighting(add_lighting, auto_add_light @pytest.mark.isaacsim_ci def test_build_simulation_context_cfg(): - """Test that the simulation context is built with the correct cfg and values don't get overridden.""" + """Test that the simulation context honors sim_cfg's values, with an explicit + device override winning when both ``sim_cfg`` and ``device`` are passed. + + Most test callers pass both kwargs together expecting the device kwarg to + win; the override branch in :func:`build_simulation_context` exists for + that case. ``gravity`` and ``dt`` are not overridable by the helper's + kwargs (only sim_cfg's values are used). + """ dt = 0.001 # Non-standard gravity gravity = (0.0, 0.0, -1.81) @@ -87,8 +94,14 @@ def test_build_simulation_context_cfg(): dt=dt, ) - with build_simulation_context(sim_cfg=cfg, gravity_enabled=False, dt=0.01, device="cpu") as sim: - # Values from sim_cfg should not be overridden by build_simulation_context args + # Pass only sim_cfg: gravity, device, dt all come from sim_cfg (kwargs ignored). + with build_simulation_context(sim_cfg=cfg, gravity_enabled=False, dt=0.01) as sim: assert sim.cfg.gravity == gravity assert sim.cfg.device == device assert sim.cfg.dt == dt + + # Pass sim_cfg and an explicit device override: device kwarg wins. + with build_simulation_context(sim_cfg=cfg, device="cpu") as sim: + assert sim.cfg.gravity == gravity + assert sim.cfg.device == "cpu" + assert sim.cfg.dt == dt diff --git a/source/isaaclab/test/sim/test_build_simulation_context_nonheadless.py b/source/isaaclab/test/sim/test_build_simulation_context_nonheadless.py index 8c053bc51cda..09bfe3091839 100644 --- a/source/isaaclab/test/sim/test_build_simulation_context_nonheadless.py +++ b/source/isaaclab/test/sim/test_build_simulation_context_nonheadless.py @@ -70,8 +70,14 @@ def test_build_simulation_context_auto_add_lighting(add_lighting, auto_add_light def test_build_simulation_context_cfg(): - """Test that the simulation context is built with the correct cfg and values don't get overridden.""" - + """Test that the simulation context honors sim_cfg's values, with an explicit + device override winning when both ``sim_cfg`` and ``device`` are passed. + + Most test callers pass both kwargs together expecting the device kwarg to + win; the override branch in :func:`build_simulation_context` exists for + that case. ``gravity`` and ``dt`` are not overridable by the helper's + kwargs (only sim_cfg's values are used). + """ dt = 0.001 # Non-standard gravity gravity = (0.0, 0.0, -1.81) @@ -83,8 +89,14 @@ def test_build_simulation_context_cfg(): dt=dt, ) - with build_simulation_context(sim_cfg=cfg, gravity_enabled=False, dt=0.01, device="cpu") as sim: - # Values from sim_cfg should not be overridden by build_simulation_context args + # Pass only sim_cfg: gravity, device, dt all come from sim_cfg (kwargs ignored). + with build_simulation_context(sim_cfg=cfg, gravity_enabled=False, dt=0.01) as sim: assert sim.cfg.gravity == gravity assert sim.cfg.device == device assert sim.cfg.dt == dt + + # Pass sim_cfg and an explicit device override: device kwarg wins. + with build_simulation_context(sim_cfg=cfg, device="cpu") as sim: + assert sim.cfg.gravity == gravity + assert sim.cfg.device == "cpu" + assert sim.cfg.dt == dt From 799192a04f59ed4c417303db270af5ff9b6a7081 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Fri, 5 Jun 2026 21:24:50 +0000 Subject: [PATCH 2/3] Avoid mutating caller's sim_cfg when overriding device Use SimulationCfg.replace(device=...) instead of assigning sim_cfg.device in place, so a caller-owned config passed into build_simulation_context is not permanently mutated and stays reusable across subsequent calls. --- source/isaaclab/isaaclab/sim/simulation_context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index fae7d4d1f510..ef5d4e704bce 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -1006,8 +1006,9 @@ def build_simulation_context( # ``device=cuda:N`` silently got sim_cfg's device, causing warp # kernel-launch mismatches when test fixtures allocated tensors on # the requested device while assets resolved their device from the - # untouched sim_cfg. - sim_cfg.device = device + # untouched sim_cfg. ``replace`` returns a copy so a caller-owned + # sim_cfg is not mutated in-place and stays reusable across calls. + sim_cfg = sim_cfg.replace(device=device) sim = SimulationContext(sim_cfg) From 7d3db804a2e8049a3cc8e02197675306369e81b5 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Tue, 9 Jun 2026 09:07:23 +0000 Subject: [PATCH 3/3] Use in-place device override in build_simulation_context Revert the device override from sim_cfg.replace(device=device) to a plain in-place assignment. replace() reconstructs the whole config (re-running the configclass post-init tree walk, ~50-76us vs ~0.03us for a single attribute set), and the copy it produced guarded against a reuse hazard that no caller hits: build_simulation_context is a test/benchmark helper, the only call sites that reuse a SimulationCfg object already mutate it in-place themselves before passing it, and SimulationContext mutates the cfg it receives regardless. --- source/isaaclab/isaaclab/sim/simulation_context.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index ef5d4e704bce..fae7d4d1f510 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -1006,9 +1006,8 @@ def build_simulation_context( # ``device=cuda:N`` silently got sim_cfg's device, causing warp # kernel-launch mismatches when test fixtures allocated tensors on # the requested device while assets resolved their device from the - # untouched sim_cfg. ``replace`` returns a copy so a caller-owned - # sim_cfg is not mutated in-place and stays reusable across calls. - sim_cfg = sim_cfg.replace(device=device) + # untouched sim_cfg. + sim_cfg.device = device sim = SimulationContext(sim_cfg)