Skip to content

Commit 8d778f4

Browse files
committed
Set Warp's global determinism mode under --deterministic
Newton's solvers take a deterministic argument and apply it as a per-module option, so the solver kernels already honoured PhysicsCfg.deterministic. Its sensor and geometry kernels take no such argument and fall back to warp.config.deterministic, which stayed at NOT_GUARANTEED. The scene BVH is built over a shape list compacted with wp.atomic_add, so its primitive order varied between processes. Ray queries then broke ties differently and a tiled camera rendered a few pixels differently from bit-identical simulation state, which was enough to make an image-observation policy diverge. Warp reads the setting at module build time, so it is requested in apply_env_overrides, before the environment is created. An explicitly chosen mode is left alone.
1 parent 1a986c9 commit 8d778f4

4 files changed

Lines changed: 86 additions & 1 deletion

File tree

docs/source/features/reproducibility.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,18 @@ for **RL-Games**, **skrl**, **RSL-RL**, and **Stable-Baselines3**: each calls
3535
so library initialization is not disturbed, then training proceeds with the requested global RNG and
3636
optional PyTorch deterministic algorithms. Whether the **rendering** half of the flag matters depends
3737
on the workload: **physics-only** simulation does not render at all; **RTX** rendering (non-minimal
38-
mode) needs it for reproducible imagery; **Newton** rendering is already deterministic.
38+
mode) needs it for reproducible imagery; **Newton** rendering needs Warp's global determinism mode,
39+
which the flag sets (see :ref:`below <reproducibility-warp-determinism>`).
40+
41+
.. _reproducibility-warp-determinism:
42+
43+
**Warp determinism.** Newton's solvers accept a ``deterministic`` argument that Isaac Lab supplies
44+
from :attr:`~isaaclab.physics.PhysicsCfg.deterministic`, and they apply it as a per-module option.
45+
Its sensor and geometry kernels take no such argument and fall back to ``warp.config.deterministic``,
46+
so ``--deterministic`` sets that global to ``RUN_TO_RUN`` as well. Without it the scene BVH is built
47+
over an atomically compacted shape list whose order varies between processes, and a tiled camera can
48+
render a few pixels differently from identical simulation state. An explicitly chosen mode, such as
49+
``GPU_TO_GPU``, is left untouched.
3950

4051
.. note::
4152

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed ``--deterministic`` not making camera observations reproducible. The flag configured the
5+
physics solver but left :attr:`warp.config.deterministic` at ``NOT_GUARANTEED``, and Newton's
6+
sensor and geometry kernels -- unlike its solvers -- take no per-module determinism option and
7+
fall back to that global. The scene BVH is built over an atomically compacted shape list, so its
8+
primitive order varied between processes and a tiled camera rendered a few pixels differently
9+
from identical simulation state, which was enough to make image-observation training diverge.

source/isaaclab_rl/isaaclab_rl/entrypoints/common.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,29 @@ def apply_env_overrides(args_cli: argparse.Namespace, env_cfg: Any, *, apply_dev
497497
physics_cfg = getattr(getattr(env_cfg, "sim", None), "physics", None)
498498
if physics_cfg is not None:
499499
physics_cfg.deterministic = True
500+
request_warp_determinism()
501+
502+
503+
def request_warp_determinism() -> None:
504+
"""Ask Warp for run-to-run deterministic atomics process-wide.
505+
506+
Newton's solvers take a ``deterministic`` argument and apply it as a per-module option, so a
507+
solver-level request already covers the physics kernels. Its sensor and geometry modules take
508+
no such argument and fall back to ``warp.config.deterministic``, which defaults to
509+
``NOT_GUARANTEED``. One of them, the BVH shape compaction in ``newton._src.geometry.bvh``,
510+
claims output slots with ``wp.atomic_add``, so the enabled-shape order -- and with it the
511+
order of the primitives the scene BVH is built over -- varies between processes. Ray queries
512+
then break ties differently and a tiled camera renders a handful of pixels differently from
513+
identical simulation state, which is enough to make an image-observation policy diverge.
514+
515+
Warp reads this at module build time, so it must be set before the first kernel launch;
516+
:func:`apply_env_overrides` runs before the environment is created. An explicit setting is
517+
left alone so a caller can still opt into ``GPU_TO_GPU``.
518+
"""
519+
import warp as wp
520+
521+
if wp.config.deterministic == wp.DeterministicMode.NOT_GUARANTEED:
522+
wp.config.deterministic = wp.DeterministicMode.RUN_TO_RUN
500523

501524

502525
def validate_distributed_device(args_cli: argparse.Namespace) -> None:

source/isaaclab_rl/test/test_entrypoints_common.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,48 @@ def test_apply_env_overrides_leaves_physics_alone_without_the_flag(monkeypatch:
420420
assert env_cfg.sim.physics.deterministic is False
421421

422422

423+
def test_apply_env_overrides_requests_warp_determinism(monkeypatch: pytest.MonkeyPatch) -> None:
424+
"""The request reaches Warp's global too, which is all that covers Newton's sensor kernels."""
425+
import warp as wp
426+
427+
monkeypatch.setattr(wp.config, "deterministic", wp.DeterministicMode.NOT_GUARANTEED)
428+
physics = _fake_physics_cfg("NewtonCfg")
429+
env_cfg = SimpleNamespace(sim=SimpleNamespace(physics=physics))
430+
431+
args_cli = argparse.Namespace(num_envs=None, device=None, deterministic=True)
432+
_rl_common.apply_env_overrides(args_cli, env_cfg, apply_device=False)
433+
434+
assert wp.config.deterministic == wp.DeterministicMode.RUN_TO_RUN
435+
436+
437+
def test_apply_env_overrides_keeps_an_explicit_warp_mode(monkeypatch: pytest.MonkeyPatch) -> None:
438+
"""A caller that already chose a stronger guarantee keeps it."""
439+
import warp as wp
440+
441+
monkeypatch.setattr(wp.config, "deterministic", wp.DeterministicMode.GPU_TO_GPU)
442+
physics = _fake_physics_cfg("NewtonCfg")
443+
env_cfg = SimpleNamespace(sim=SimpleNamespace(physics=physics))
444+
445+
args_cli = argparse.Namespace(num_envs=None, device=None, deterministic=True)
446+
_rl_common.apply_env_overrides(args_cli, env_cfg, apply_device=False)
447+
448+
assert wp.config.deterministic == wp.DeterministicMode.GPU_TO_GPU
449+
450+
451+
def test_apply_env_overrides_leaves_warp_alone_without_the_flag(monkeypatch: pytest.MonkeyPatch) -> None:
452+
"""Without ``--deterministic`` Warp keeps its default, so no run pays for determinism."""
453+
import warp as wp
454+
455+
monkeypatch.setattr(wp.config, "deterministic", wp.DeterministicMode.NOT_GUARANTEED)
456+
physics = _fake_physics_cfg("NewtonCfg")
457+
env_cfg = SimpleNamespace(sim=SimpleNamespace(physics=physics))
458+
459+
args_cli = argparse.Namespace(num_envs=None, device=None, deterministic=False)
460+
_rl_common.apply_env_overrides(args_cli, env_cfg, apply_device=False)
461+
462+
assert wp.config.deterministic == wp.DeterministicMode.NOT_GUARANTEED
463+
464+
423465
@pytest.mark.parametrize("class_name", ["PhysxCfg", "OvPhysxCfg", "NewtonCfg", "SomeFutureBackendCfg"])
424466
def test_apply_env_overrides_records_the_request_for_every_backend(class_name: str) -> None:
425467
"""The request is backend-agnostic, so the entrypoint needs no per-backend knowledge."""

0 commit comments

Comments
 (0)