Repository navigation
[WARP] Cleanup Part 1: Add frontend bridge for stable manager-based tasks - #5504
Conversation
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This PR introduces a WarpFrontend adapter that allows stable manager-based RL task configs to run on the experimental warp runtime without requiring parallel -Warp-v0 task registrations. The implementation mutates configs in-place to swap stable MDP functions with warp twins, upgrades SceneEntityCfg instances, and drops unsupported sensors. The approach is architecturally sound for reducing task duplication, but there are several correctness issues that need attention before shipping.
Architecture Impact
The changes are relatively self-contained within isaaclab_experimental, with the main integration point being rsl_rl/train.py. The adapter operates at config mutation time before env construction, so it doesn't change the warp runtime itself. However:
- The
manager_term_cfg.pychange from custom classes to re-exports affects any code doingisinstancechecks against the warp term cfg types - The
--manager=warpflag is only added torsl_rl/train.py, not other trainers (skrl, rl_games) — documented as out of scope but creates API inconsistency - The in-place
__class__mutation pattern in_upgrade_scene_entity_cfgsis fragile if the warpSceneEntityCfgever diverges structurally from stable
Implementation Verdict
Minor fixes needed — The core design is solid, but there are edge cases and correctness issues that should be addressed.
Test Coverage
Insufficient. The PR adds no automated tests. The validation is entirely manual (described in PR description). Given the complexity of the adapter logic (module resolution, class swapping, term iteration), this needs at minimum:
- Unit tests for
WarpFrontend.adapt()with mock configs - Regression test for the visualizer fix in
manager_based_env_warp.py - Integration test that the
--manager=warpflag correctly invokes the adapter
CI Status
No CI checks available yet — cannot assess whether existing tests pass.
Findings
🔴 Critical: warp_frontend.py:278-287 — In-place __class__ mutation is unsafe without verifying inheritance
The code does obj.__class__ = _WarpSE after checking isinstance(obj, _StableSE), but if _WarpSE doesn't actually inherit from _StableSE (which the code assumes but doesn't verify), this will corrupt the object. The comment claims "warp inherits stable" but there's no runtime assertion. If someone refactors the warp SceneEntityCfg to not inherit from stable, this silently corrupts configs.
# Should verify at import time:
if not issubclass(_WarpSE, _StableSE):
raise TypeError("WarpFrontend requires warp SceneEntityCfg to inherit from stable")🔴 Critical: warp_frontend.py:95-97 — Iterating over dir(group) while mutating attributes
The apply_to method iterates over dir(group) and calls setattr(group, name, None) to drop terms. While this doesn't crash because the iteration is over a snapshot from dir(), it means subsequent code that relies on attribute presence (like the warp manager's term resolution) will see None values rather than missing attributes. The warp managers need to handle None term entries, or this should use delattr.
🟡 Warning: warp_frontend.py:316-329 — Module resolution swallows all exceptions
try:
import gymnasium as gym
entry = gym.spec(task_id).kwargs.get("env_cfg_entry_point")
except Exception:
entry = NoneThis catches all exceptions including KeyboardInterrupt and SystemExit. Should be except (ImportError, KeyError, AttributeError): or similar to avoid masking real errors.
🟡 Warning: train.py:100-101 — Preset injection doesn't check for conflicting explicit presets
if args_cli.manager == "warp" and not any(a.startswith("presets=") for a in remaining_args):
remaining_args.append("presets=newton")If user passes presets=physx, this silently does nothing (doesn't append newton), but the warp adapter may still try to run and fail confusingly later. Should warn when --manager=warp is used with a non-newton preset.
🟡 Warning: warp_frontend.py:215-217 — Physics preset extraction logic is redundant
The comment says "by the time we run, Hydra's resolve_presets has already collapsed any PresetCfg wrapper" — but then the code checks hasattr(physics, "newton") anyway. If the preset is already resolved, this condition will always be False. This is dead code that adds confusion.
🔵 Improvement: warp_frontend.py:170-177 — DEFAULT_TERM_GROUPS misses commands group
The default term groups don't include ("commands",), which is a manager group in the stable env. If a task has command terms with SceneEntityCfg params, they won't be upgraded. Either add it or document why it's excluded.
🔵 Improvement: manager_term_cfg.py — Re-export change may break type annotations
The change from explicit @configclass definitions to from ... import * means type checkers can no longer see what's exported from this module. Code doing from isaaclab_experimental.managers.manager_term_cfg import RewardTermCfg will work at runtime but may fail type checking. Consider adding explicit __all__ or type stub.
🔵 Improvement: warp_frontend.py:256-261 — build() ignores the adapt report
The build() method calls adapt() but doesn't return or expose the report. If the caller wants to inspect what was dropped/missing, they have to call adapt() separately first. Consider returning a tuple (env, report) or attaching the report to the env.
Greptile SummaryThis PR introduces a Confidence Score: 4/5Safe to merge with the two robustness fixes applied; the warp adapter path is gated behind an optional import so the default torch path is unaffected. The source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py — Important Files Changed
Reviews (3): Last reviewed commit: "Rename CLI preset hint to newton_mjwarp" | Re-trigger Greptile |
| def build(self, cfg: Any, task_id: str): | ||
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | ||
|
|
||
| Always logs the missing-twin report (if any). With ``strict=True``, | ||
| any missing twin raises before the env is constructed. | ||
| """ | ||
| # Lazy: this is the first warp-lib load. Caller must already be inside | ||
| # the SimulationApp context, i.e. inside ``launch_simulation``. | ||
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | ||
|
|
||
| self.adapt(cfg, task_id) | ||
| return ManagerBasedRLEnvWarp(cfg=cfg) |
There was a problem hiding this comment.
WarpFrontend.build always constructs ManagerBasedRLEnvWarp with render_mode=None, but ManagerBasedRLEnvWarp.__init__ accepts render_mode and stores it. When --video is combined with --manager=warp, gym.wrappers.RecordVideo is applied to the returned env, but because render_mode was never set to "rgb_array", gymnasium will raise an error. The warp path silently drops the flag that the stable path (gym.make(..., render_mode="rgb_array" if args_cli.video else None)) correctly sets.
| def build(self, cfg: Any, task_id: str): | |
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | |
| Always logs the missing-twin report (if any). With ``strict=True``, | |
| any missing twin raises before the env is constructed. | |
| """ | |
| # Lazy: this is the first warp-lib load. Caller must already be inside | |
| # the SimulationApp context, i.e. inside ``launch_simulation``. | |
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | |
| self.adapt(cfg, task_id) | |
| return ManagerBasedRLEnvWarp(cfg=cfg) | |
| def build(self, cfg: Any, task_id: str, render_mode: str | None = None): | |
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | |
| Always logs the missing-twin report (if any). With ``strict=True``, | |
| any missing twin raises before the env is constructed. | |
| """ | |
| # Lazy: this is the first warp-lib load. Caller must already be inside | |
| # the SimulationApp context, i.e. inside ``launch_simulation``. | |
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | |
| self.adapt(cfg, task_id) | |
| return ManagerBasedRLEnvWarp(cfg=cfg, render_mode=render_mode) |
| # SimulationApp is already alive avoids racing pxr extension init. | ||
| from isaaclab_experimental.envs.warp_frontend import WarpFrontend | ||
|
|
||
| env = WarpFrontend().build(env_cfg, task_id=args_cli.task) |
There was a problem hiding this comment.
The warp env is built without
render_mode, so gym.wrappers.RecordVideo applied on line 222 will fail when --video --manager=warp is used. Pass the same render_mode the stable path uses.
| env = WarpFrontend().build(env_cfg, task_id=args_cli.task) | |
| env = WarpFrontend().build(env_cfg, task_id=args_cli.task, render_mode="rgb_array" if args_cli.video else None) |
| def build(self, cfg: Any, task_id: str): | ||
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | ||
|
|
||
| Always logs the missing-twin report (if any). With ``strict=True``, | ||
| any missing twin raises before the env is constructed. | ||
| """ | ||
| # Lazy: this is the first warp-lib load. Caller must already be inside | ||
| # the SimulationApp context, i.e. inside ``launch_simulation``. | ||
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | ||
|
|
||
| self.adapt(cfg, task_id) | ||
| return ManagerBasedRLEnvWarp(cfg=cfg) |
There was a problem hiding this comment.
build() discards the WarpAdaptReport returned by adapt(), so callers have no programmatic access to what was dropped. In the default non-strict mode, silently-dropped terms cause the env to train with a smaller effective term set than expected. A caller who wants to enforce a "fail if anything was dropped" policy must call adapt() separately, which then re-mutates the already-mutated cfg when build() calls it again internally.
| modules: list[ModuleType] = [] | ||
| try: | ||
| import gymnasium as gym | ||
|
|
||
| entry = gym.spec(task_id).kwargs.get("env_cfg_entry_point") | ||
| except Exception: | ||
| entry = None |
There was a problem hiding this comment.
The bare
except Exception swallows any error from gymnasium.spec(task_id) — including gymnasium.error.NameNotFound if the task ID is misspelled and real programming errors. When this fires, entry is set to None and only the fallback mdp module is used, so no task-specific warp twins are discovered, causing every term to go missing without any indication of why.
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot — Re-review after Updates
Summary
This PR introduces a well-designed frontend framework (isaaclab_experimental.envs.frontend) that enables runtime backend selection for IsaacLab tasks via --frontend torch|warp. The architecture is clean: a Frontend base class with pluggable CompatRule pipelines, proper separation between task resolution (TaskResolver) and env construction, and clear error reporting via Report/Issue/Change records.
Since the initial review (May 5), the author has addressed all critical findings through commits 8934fe649a60 ("Tighten frontend framework against review findings") and 67050a863d5e ("Tighten frontend framework against second review pass"). The code quality has improved significantly.
Architecture Assessment
Strengths:
- Clean plugin architecture: new compatibility rules are easy to add without modifying core framework
- Proper error surfacing via structured
Issuerecords with severity levels - Good idempotency handling in
SwapMdpFunctions(skips already-warp terms) - Train script integration is backwards-compatible (falls back to
gym.makewhenisaaclab_experimentalisn't installed) - The
manager_term_cfg.pysimplification (re-exporting stable classes) correctly fixes theisinstancecheck issue
Design Decisions (reasonable but worth noting):
- In-place cfg mutation rather than copy — documented and appropriate for the use case
frontend_reportis set as a dynamic attribute onenv.unwrapped— works but isn't declared on the env class
Implementation Verdict
Ship it ✅ — The previous critical issues have been resolved. The remaining items are minor improvements that don't block merging.
Resolved Issues from Previous Review
✅ issubclass(Warp, Stable) check now present (line 200)
✅ TypeError from __class__ promotion is caught and surfaced as blocking issue (line 225)
✅ Non-newton presets now emit a warning (lines 508-509)
✅ commands group added to both PromoteSceneEntityCfg and SwapMdpFunctions
✅ Module import errors now properly distinguish "module doesn't exist" from "module has internal error" (lines 338-343)
Remaining Findings
🔵 Improvement: No automated tests for the frontend framework
The PR adds substantial logic (rule pipeline, task resolution, cfg mutation) but no unit tests. The manual validation in the PR description is thorough, but automated tests would prevent regressions. Consider adding in a follow-up:
- Unit tests for
TaskResolver.resolve()with mock specs - Unit tests for each
CompatRulewith mock configs - Integration test for
WarpFrontend.build()happy path
🔵 Improvement: base.py:365 — Consider declaring frontend_report attribute
The frontend_report attribute is set dynamically:
env.unwrapped.frontend_report = reportWhile this works, type checkers and IDE autocompletion won't know about it. Consider adding a type stub or documenting that users should expect this attribute.
🔵 Improvement: warp.py:165 — DropUnsupportedSensors could emit an Issue
Currently drops sensors silently (only emits a Change). A WARNING severity Issue might be more appropriate since dropping a sensor affects task behavior and users should be clearly informed.
CI Status
Checks are still pending. Recommend waiting for CI green before merge.
Test Coverage
The PR description includes thorough manual validation (Cartpole, Anymal-D Flat parity tests, negative cases). However, no automated tests are included for the new framework code. This is acceptable for an experimental package but should be addressed in a follow-up.
Re-reviewed at commit 429794db61ad after the refactoring commits addressed previous findings.
🤖 Update (e2cb744)
Major architectural simplification — The plugin-based CompatRule framework has been replaced with a much simpler functional approach:
Changes Summary
-
Framework consolidation: ~1000+ lines across 4 files → ~373 lines in a single
frontend.pymodule- Deleted:
frontend/__init__.py,frontend/base.py,frontend/torch.py,frontend/warp.py - Added:
frontend.py(flat module with direct functions)
- Deleted:
-
Behavioral changes:
presets=newtonauto-injection removed — user must explicitly pass it (documented in changelog)- Missing MDP twins are now a hard failure (was WARNING/drop in non-strict mode) — this is safer
SceneEntityCfg.from_stable()classmethod replaces the__class__reassignment hack
-
Train script simplified: Direct
build(frontend, env_cfg, task_id, render_mode=...)call
Previous Findings Status
✅ "No automated tests" — Now addressed! Comprehensive test_frontend.py added covering:
- Enum surface tests
SceneEntityCfg.from_stablefield copy_require_newton_physicshard-check_promote_scene_entity_cfgswalks term params_swap_mdpfunc/class_type swap + missing twin error paths- Direct workflow guard tests
✅ frontend_report attribute — No longer applies (removed with the Report/Issue/Change system)
✅ DropUnsupportedSensors silent drop — Rule removed entirely; the new approach requires all twins to exist
Assessment
The simplification is a good architectural decision:
- Easier to understand and maintain
- Harder to misuse (fail-fast on missing twins)
- Tests now included
- The plugin extensibility wasn't being used and added complexity
Ship it ✅ — No new issues found in this update.
🤖 Update (bd27af4)
Dynamic term discovery — This update replaces hardcoded term path lists with recursive type-based discovery:
Changes Summary
-
_walk_terms()refactoring: New recursive walker that discoversManagerTermBaseCfginstances by type viaisinstance(), not by hardcoded attribute names like("observations", "policy"). This makes the framework future-proof — new observation groups, nested cfgs, or custom term containers are picked up automatically. -
Deleted
manager_term_cfg.py: The experimental package no longer re-exports stable term cfgs; managers import directly fromisaaclab.managers.manager_term_cfg. Cleaner dependency graph. -
Type hint updates:
funccallables inObservationTermCfg,RewardTermCfg,TerminationTermCfgnow allow| Nonereturn types, supporting warp-style out-parameter functions. -
Test improvements: Tests now use real
@configclassfixtures instead ofSimpleNamespacemocks, and explicitly verify that arbitrarily-named observation groups (e.g.,perception) are discovered. -
Naming cleanup:
_require_direct_is_warp_task→_assert_direct_warp_registration(clearer intent).
Assessment
This is a solid incremental improvement:
- Dynamic discovery is more robust than hardcoded paths
- Better test fixtures catch real configclass edge cases
- Import simplification reduces surface area
Ship it ✅ — No new issues introduced.
🤖 Update (21e8bba)
Changelog housekeeping — Documentation-only commit:
- Compressed
isaaclab_experimentalchangelog fragment from 47 lines to 12 lines (follows terse-fragment convention) - Added
isaaclab/changelog.d/warp-manager-bridge.rstdocumenting theCallable[..., torch.Tensor | None]type relaxation - Added
isaaclab_tasks_experimental/changelog.d/warp-manager-bridge.skip(internal humanoid import redirect)
No code changes. Previous review findings unchanged.
Ship it ✅
🤖 Update (b932897)
Warp env API alignment — Syncs warp experimental envs with stable API changes:
Changes Summary
-
direct_rl_env_warp.py:- Enabled
ViewportCameraController(was commented out) — now initializes when Kit viewport exists - Added
has_kit()guard to skip ViewportCameraController in kitless Newton-only runs - Replaced manual
get_setting("/isaaclab/has_gui")calls withself.sim.has_gui/self.sim.has_offscreen_renderproperties
- Enabled
-
manager_based_rl_env_warp.py:- Same render-check modernization:
get_setting()→ cachedSimulationContextproperties
- Same render-check modernization:
These changes align with upstream stable PRs #4646 (cached render properties) and #5103 (visualizer API update).
Assessment
Clean sync with stable — No new issues. The inline comments from previous reviews (render_mode passthrough, exception handling) remain open but are in separate files not touched by this commit.
Ship it ✅
🤖 Update (d33a9bd)
Merge from develop + preset rename — Two branch-specific changes since b932897d:
-
0f344754— Renames CLI preset hint frompresets=newtontopresets=newton_mjwarpin error messages, comments, and the corresponding test assertion. Cosmetic only; aligns with an upstream rename of the preset identifier. -
d33a9bdf— Merge oforigin/developinto the feature branch (200+ upstream commits). No merge conflicts with the branch's own files; the diff is entirely upstream changes flowing in.
Previous Findings Status
All previous findings remain addressed. No regressions introduced.
Assessment
No new issues. The branch-specific delta is a trivial string rename that keeps documentation/error messages consistent with upstream preset naming. The develop merge is clean.
Ship it ✅
🤖 Update (d33a9bd, second pass)
✅ P1 fix confirmed: The ModuleNotFoundError check in _warp_mdp_modules (line 351) now uses warp_mod.startswith(exc.name) instead of the previous exc.name == target. This correctly handles the case where a parent package (e.g. isaaclab_tasks_experimental) is missing, not just the leaf module. The graceful fallback path now works as intended.
All inline comments from my previous review have been addressed.
🤖 Update (855588f)
Merge from develop (clean) — The new HEAD is a merge of origin/develop into the feature branch, incorporating ~50+ upstream commits (OvPhysX assets/sensors, Newton deformables, teleop replay MCAP migration, preset CLI improvements, visualizer fixes, packaging cleanup, etc.). No merge conflicts with the PR's own files. The branch-specific delta since d33a9bdf (last reviewed) is zero new feature code — the preset rename to newton_mjwarp (commit 0f344754) was already noted in the previous update.
Previous Findings Status
All previous findings remain addressed. No regressions introduced by the merge.
Assessment
No new issues. The branch is a clean forward-merge of upstream develop with no changes to the PR's own experimental frontend framework code.
Ship it ✅
|
@greptileai review |
| for depth in range(len(parts), 0, -1): | ||
| target = ".".join(parts[:depth] + ["mdp"]) | ||
| try: | ||
| modules.append(importlib.import_module(target)) | ||
| break | ||
| except ModuleNotFoundError as exc: | ||
| # Only swallow "this candidate doesn't exist" — a real | ||
| # import error inside an existing module is a bug. | ||
| if exc.name == target: | ||
| continue | ||
| raise |
There was a problem hiding this comment.
exc.name == target check is too strict — crashes when parent package is absent
ModuleNotFoundError.name is set to the specific module that was missing, which may be a parent package rather than the leaf target. For example, importing "isaaclab_tasks_experimental.locomotion.velocity.mdp" when isaaclab_tasks_experimental is not installed raises ModuleNotFoundError(name="isaaclab_tasks_experimental"). Since "isaaclab_tasks_experimental" != "isaaclab_tasks_experimental.locomotion.velocity.mdp", the condition is False and the exception is re-raised unconditionally — every depth in the walk fails the same way, so _warp_mdp_modules throws instead of falling back to the generic module. This turns the graceful "no task-specific twin module found" path into a hard crash any time isaaclab_tasks_experimental is not installed.
Change the guard to if exc.name and target.startswith(exc.name). This keeps the intent ("only swallow 'this candidate doesn't exist'") while correctly handling the case where a parent package is the missing link.
AntoineRichard
left a comment
There was a problem hiding this comment.
High level review, i don't have a issue with the mechanical changes introduced in this PR. I think task owners will need to refactor these so they match their new tasks.
There was a problem hiding this comment.
Cartpole got completely rewritten by @StafaH we would need to match the new env exactly.
There was a problem hiding this comment.
Cartpole got completely rewritten by @StafaH we would need to match the new env exactly.
Let the warp frontend adapt a stable manager-based configuration onto the warp runtime without duplicating scene-entity configs.
67bcc4e to
00ae695
Compare
Introduce the WarpFrontend adapter that runs stable task ids on the warp runtime: it adapts a stable manager-based cfg in place (swapping each MDP term for its warp twin, resolved by name on the mirrored experimental package), resolves direct env classes by name-based mirror, and converts observation-noise cfgs to their warp-native twins. Adds the experimental warp managers, environments, and noise utilities.
Lay out isaaclab_tasks_experimental as a mirror of isaaclab_tasks.core and reuse the stable task configurations under --frontend warp. Direct warp envs follow the <task>_warp_env / <Name>WarpEnv convention and resolve the per-physics-backend joint gears; the retired -Warp-v0 registrations are removed.
Add --frontend {torch,warp} to the shared reinforcement-learning training
and play entrypoints and construct environments through the frontend
factory.
69ba95d to
06072c5
Compare
| @@ -640,8 +607,8 @@ Environments based on legged locomotion tasks. | |||
| .. |dr-legs-hold-pose-link| replace:: :isaaclab-source:`IsaacContrib-DrLegs-HoldPose <source/isaaclab_tasks/isaaclab_tasks/contrib/dr_legs/hold_pose_env_cfg.py>` | |||
| .. |dr-legs-walk-link| replace:: :isaaclab-source:`IsaacContrib-DrLegs-Walk <source/isaaclab_tasks/isaaclab_tasks/contrib/dr_legs/walk_env_cfg.py>` | |||
|
|
|||
| .. |velocity-flat-digit-link| replace:: :isaaclab-source:`IsaacContrib-Velocity-Flat-Digit <source/isaaclab_tasks/isaaclab_tasks/contrib/velocity/config/digit/flat_env_cfg.py>` | |||
| .. |velocity-rough-digit-link| replace:: :isaaclab-source:`IsaacContrib-Velocity-Rough-Digit <source/isaaclab_tasks/isaaclab_tasks/contrib/velocity/config/digit/rough_env_cfg.py>` | |||
| .. |velocity-flat-digit-link| replace:: :isaaclab-source:`Isaac-Velocity-Flat-Digit <source/isaaclab_tasks/isaaclab_tasks/core/velocity/config/digit/flat_env_cfg.py>` | |||
There was a problem hiding this comment.
why was this change reverted?
| @@ -147,8 +127,8 @@ Classic environments that are based on IsaacGymEnvs implementation of MuJoCo-sty | |||
| | | | | ``newton_kamino`` | | |||
| +------------------+-----------------------------+-------------------------------------------------------------------------+------------------------------+ | |||
| | |cartpole| | |cartpole-camera-presets| | Move the cart to keep the pole upwards in the classic cartpole control | **physics=** | | |||
| | | | and perceptive inputs. Select data type via ``presets=``. | ``physx``, | | |||
| | | | | ``newton_mjwarp``, | | |||
| | | | and perceptive inputs. Select data type via ``presets=``. Requires | ``physx``, | | |||
There was a problem hiding this comment.
--enable_cameras are removed
| To run inference on a trained task, pass its task name to ``play.py``. The play script applies the environment | ||
| configuration's ``play_mode`` overrides automatically, providing configurations more suitable for inferencing, | ||
| including disabling runtime perturbations used for training. | ||
| For environments that have a different task name listed under ``Inference Task Name``, please use the Inference Task Name |
There was a problem hiding this comment.
inference task names are no longer applicable
06072c5 to
3fdef8b
Compare
Document convention-based direct environment lookup and keep the optional override and migration guidance consistent.
A stable direct env class can be shared by several tasks (e.g. ReorientDirectEnv serves both the Allegro and Shadow hands via different cfgs). Name-based resolution keys on that shared env class, so it mirrored every sharing task to the single ReorientDirectWarpEnv twin -- which implements only the Allegro variant -- silently running the wrong MDP for Shadow under --frontend warp. Have _resolve_direct_warp_class verify the name-mirrored twin implements the task's cfg (via the twin's `cfg` annotation) before returning it; a mismatch is a hard error. Explicit warp_entry_point overrides stay trusted. The 4 supported direct tasks are unaffected; the 3 Shadow reorient tasks now report unsupported instead of mis-resolving.
The warp reward/event managers accept a manager group expressed as a
{name: term} dict as well as a configclass, but _walk_terms stopped at any
non-configclass node -- so a dict group's terms were never discovered and
never swapped to their warp twins, leaving torch-signature callables that
fail under the warp managers.
Recurse into mapping values in _walk_terms so both group forms are covered.
No warp-supported task uses dict groups today; this closes the gap before
one does.
…asks (isaac-sim#5504) ## Review Map - A stacked PR’s page shows the cumulative diff of its dependency chain; the "Exact changes" link pins that PR’s own commit range. - SHA links can go stale after a branch update; the copy on isaac-sim#5504 is refreshed first. | PR | Status | Depends on | Exact changes | |---|---|---|---| | 📌 isaac-sim#5504 Part 1: frontend bridge (stable ids on the warp runtime) |  | — | — | | isaac-sim#6607 Part 2: mask-first frontend (draft) |  | isaac-sim#5504 | [link](https://github.com/isaac-sim/IsaacLab/pull/6607/changes/00ae695ac63e67ca678b91a837d3737529d2ec91..f68d03f40854b31a36063c5068f2933d32ea5db9) | | isaac-sim#6646 Part 3: scene reset mask-native + capture-ready (draft, base rewritten — pending rebase onto isaac-sim#6607) |  | isaac-sim#6607 | [link](https://github.com/isaac-sim/IsaacLab/pull/6646/changes/4360622bde446c52c3fc85924639f9d1119d6e95..6d49e32f11e15894a4a0e49161a356ec79679ac6) | | isaac-sim#6580 Part 4: OVPhysX backend (draft) |  | isaac-sim#5504 | [link](https://github.com/isaac-sim/IsaacLab/pull/6580/changes/fc37fa65ad95d1ac6e3f32e721329ef715899d37..716cef20979ca6c578a6e595812291c7b1a282fc) | | isaac-sim#6611 Part 5: persistent-launch speedups (draft, absorbs isaac-sim#6551) |  | isaac-sim#6646 | [link](https://github.com/isaac-sim/IsaacLab/pull/6611/changes/6d49e32f11e15894a4a0e49161a356ec79679ac6..6e3da794215da03745a9bc8d35790e22262b099a) | ## 1. Summary - `--frontend {torch,warp}` on the shared RL CLI (train **and play**) runs **stable** task ids on the warp runtime — no duplicated configs, no parallel warp task registrations at all (end state pinned by test). The `rlinf` integration constructs environments inside the external framework and is not frontend-routable (known limitation). - **1.24–1.70× faster end-to-end** than torch on the same tasks, identical Newton physics (§3.1). - Manager-based: `WarpFrontend` adapts the stable cfg in place; MDP twins resolve by name on the mirrored experimental package tree — deterministic, registration-free, missing twins reported in one aggregated failure. - Direct: the stable registration declares its warp class via `warp_entry_point`; only the env class swaps, the cfg is shared. - Stable observation-noise cfgs (constant/uniform/gaussian) swap to warp-native twins during adaptation; cfgs without a twin (class-based noise models, customized funcs, tensor-valued params) are a hard error instead of silently dropping noise. The warp observation manager rejects non-warp noise cfgs and plumbs the shared per-env RNG state. - Plus: warp adapters for the material/mass randomization startup events; `isaaclab_tasks_experimental` mirrors the stable `core/<task>` layout; `Metrics/success_rate` restored under warp. ## 2. Migration map | Retired | Replacement | |---|---| | `Isaac-{Cartpole,Humanoid,Ant}-Warp-v0`, `Isaac-Reach-Franka-Warp-v0` (+`-Play`) | stable id + `--frontend warp presets=newton_mjwarp` | | `Isaac-Velocity-Flat-<Robot>-Warp-v0` (+`-Play`), all robots | stable id + `--frontend warp presets=newton_mjwarp` | | `Isaac-{Cartpole,Ant,Humanoid}-Direct-Warp-v0`, `Isaac-Reorient-Cube-Allegro-Direct-Warp-v0` | stable id + `--frontend warp presets=newton_mjwarp` (via `warp_entry_point`) | ## 3. Test plan - [x] Merged current `develop`; `./isaaclab.sh -f` clean on all files. - [x] Re-merged develop (task-id catalog update isaac-sim#6606, entrypoints refactor); environments catalog regenerated; conversion sweep + warp smokes (`Isaac-Velocity-Flat-UnitreeGo2`, `Isaac-Cartpole-Direct`) re-run green; pre-commit clean on all files. - [x] 142 focused tests: frontend units, cfg-conversion sweep over every covered stable id, no-warp-registrations end-state pin, shared-CLI tests. - [x] 5-iteration RSL-RL trainings exit clean (`Training time:` present, no traceback): `Isaac-Cartpole` (torch); `Isaac-Cartpole`, `Isaac-Humanoid`, `Isaac-Cartpole-Direct`, `Isaac-Ant-Direct`, `Isaac-Velocity-Flat-AnymalD`, `Isaac-Velocity-Flat-H1` (warp; randomization events active); `Isaac-Reorient-Cube-Allegro-Direct` (warp). `Metrics/success_rate` reported under warp. ### 3.1 Frontend A/B benchmark Same stable task id and cfg on both sides, `presets=newton_mjwarp` on **both** (identical Newton physics — only `--frontend` differs); rsl_rl, 4096 envs, 300 iterations, headless, one L40 per run, single run per cell. Metric: rsl_rl `Training time:` — learn-loop wall-clock (env stepping + PPO update; warp includes its one-time CUDA-graph capture). PPO-update cost is common to both, so the env-pipeline gain exceeds the end-to-end ratio. | Task | torch | warp | end-to-end | final reward (torch / warp) | |---|---|---|---|---| | `Isaac-Cartpole` | 58.2 s | 39.7 s | 1.46× | 4.95 / 4.95 | | `Isaac-Humanoid` | 162.0 s | 110.5 s | 1.47× | 149.1 / 179.8 | | `Isaac-Ant` | 109.8 s | 64.5 s | 1.70× | 82.1 / 77.1 | | `Isaac-Velocity-Flat-AnymalD` | 162.4 s | 130.7 s | 1.24× | 20.3 / 21.4 | ## 4. Out of scope / follow-ups - No video-recorder plumbing on warp envs; rough terrain blocked on Warp `TerrainImporter` APIs; no twins for IK/OSC actions, `height_scan`, or Digit/Spot-specific terms. - OVPhysX support for the warp frontend prepared as a follow-up PR (physics gate widening + backend data-parity test). - Candidate refactor: fold the ~300 lines of converged plumbing shared by `DirectRLEnvWarp` and stable `DirectRLEnv` into a common base. --------- Co-authored-by: Kelly Guo <kellyguo123@hotmail.com>
…et in CI (#6900) ## 1. Summary - `--frontend warp` fails at env construction for `Isaac-Reach-Franka` and `Isaac-Reach-UR10`: `rewards.success` and `terminations.success` resolve to `is_terminated_term` and `pose_command_success`, which had no warp twins. Adds both, matched to the stable terms and validated by training. - The warp-supported task list was hand-maintained and had already drifted — sweeping the registry finds 4 contrib velocity tasks that adapt but were never listed (10 → 14). It is now derived, not declared. - Adds a **CUDA-graph capture-safety gate** for warp MDP terms. Managers run captured by default, so capture safety is the normal path; the gate captures a term, overwrites its inputs in place, replays, and compares against stable on the new data. - `source/isaaclab_experimental/` had no CI job at all: all 9 of its test files were collected by nothing. Test files matched by no job: 13 → 4. - Also restores `Isaac-Ant` / `Isaac-Humanoid`, which lost warp support on develop when the locomotion rewards gained `terminated_penalty` and `survival_success_rate` without twins — the sweep in this PR is what surfaced it. - Package suite: 203 passed, 1 skipped, ~10 s. NVBug 6558734. ## 2. Why the existing gate did not catch it `test_frontend_cfg_conversion.py` already asserted both Reach tasks adapt, and has since #5504 — it just never ran. Jobs either name a package with a positive `filter-pattern`, or pass `not isaaclab_`, which `run_tests.sh` converts into an exclude on `isaaclab_`. This package matched neither form. ## 3. Twin semantics `pose_command_success` recomputes the pose error in a kernel rather than copying the stable result, whose buffer is reallocated per call and would go stale under graph replay. Three points where a naive twin diverges from the stable term, each with a regression test: | Behaviour | Stable | Naive twin | |---|---|---| | Sticky per-episode success | ORed inside `compute_success()`, before reset | never set; `reset()` reads and clears first, so `Metrics/success_rate` reads 0 | | Non-finite error | `error < bound` is false → denies success | reject form `error >= bound` is false → **reports success** | | Negative threshold | configured (`is not None`) → denies success | read as "unset" → ignored | The sticky write happens inside the kernel, not after the launch: host-side work between launches is not replayed under capture. Thresholds are resolved at init. A curriculum mutating them at runtime is not honoured, and cannot be while the manager is captured — a kernel scalar is baked into the graph. Recorded in the class docstring. ## 4. Capture-safety gate Terms are discovered from the shared warp MDP modules **and every per-task mirror** under `isaaclab_tasks_experimental`, rather than listed, so a new term without a declaration fails the gate. Enumerating separated three groups convention had blurred: terms already covered by the hand-written mutation classes, three observation terms with no capture coverage anywhere, and eight event terms that cannot be captured at all (variable `env_ids` launch dim at reset cadence). Capture-and-replay alone does not prove safety — replaying against unchanged memory re-reads the same valid bytes. Injecting a stale-view bug (`wp.clone` instead of a zero-copy view) fails only `test_term_is_capture_safe[pose_command_success]`; all 17 other tests pass. ## 5. Test plan - [x] `pytest source/isaaclab_experimental/test/` — 201 passed, 1 skipped - [x] Reverting the twins fails the sweep, naming both tasks and both missing symbols - [x] Mutation checks: stale view → only the capture test fails; reject-form predicate → only the NaN test; dropped sticky write → the threshold tests; wxyz quaternion → only the orientation cases - [x] Training, 4096 envs / 150 iters / `--frontend warp presets=newton_mjwarp`: | Task | Training time | Reward start → end | `Metrics/success_rate` | |---|---|---|---| | `Isaac-Reach-Franka` | 91.76 s | −0.63 → −0.49 | 0.78 | | `Isaac-Reach-UR10` | 72.25 s | −2.43 → 0.03 | 0.99 | With the sticky write reverted, Franka trains identically (episode length still falls 360 → 252, so terminations fire) but `Metrics/success_rate` stays 0.0000 for all 150 iterations — the metric bug in isolation. - [x] `uv run isaaclab -f` clean ## 6. Out of scope - `source/isaaclab_ppisp/` has the same CI hole (4 uncollected test files). - Three observation terms have no capture coverage yet: `joint_pos_rel`, `joint_vel_rel`, `body_incoming_wrench`. - `isaaclab_tasks_experimental` has no test directory, so its twenty pre-existing per-task twins have no parity or capture coverage anywhere. They are recorded in the gate's backlog rather than assumed correct. - Warp orientation-error twins use `2*acos(|w|)` where the stable path normalizes first; the two differ by ~7e-4 rad at float32, far below any configured threshold. Pre-existing in `orientation_command_error`; left consistent rather than changed in one place.
Review Map
1. Summary
--frontend {torch,warp}on the shared RL CLI (train and play) runs stable task ids on the warp runtime — no duplicated configs, no parallel warp task registrations at all (end state pinned by test). Therlinfintegration constructs environments inside the external framework and is not frontend-routable (known limitation).WarpFrontendadapts the stable cfg in place; MDP twins resolve by name on the mirrored experimental package tree — deterministic, registration-free, missing twins reported in one aggregated failure.warp_entry_point; only the env class swaps, the cfg is shared.isaaclab_tasks_experimentalmirrors the stablecore/<task>layout;Metrics/success_raterestored under warp.2. Migration map
Isaac-{Cartpole,Humanoid,Ant}-Warp-v0,Isaac-Reach-Franka-Warp-v0(+-Play)--frontend warp presets=newton_mjwarpIsaac-Velocity-Flat-<Robot>-Warp-v0(+-Play), all robots--frontend warp presets=newton_mjwarpIsaac-{Cartpole,Ant,Humanoid}-Direct-Warp-v0,Isaac-Reorient-Cube-Allegro-Direct-Warp-v0--frontend warp presets=newton_mjwarp(viawarp_entry_point)3. Test plan
develop;./isaaclab.sh -fclean on all files.Isaac-Velocity-Flat-UnitreeGo2,Isaac-Cartpole-Direct) re-run green; pre-commit clean on all files.Training time:present, no traceback):Isaac-Cartpole(torch);Isaac-Cartpole,Isaac-Humanoid,Isaac-Cartpole-Direct,Isaac-Ant-Direct,Isaac-Velocity-Flat-AnymalD,Isaac-Velocity-Flat-H1(warp; randomization events active);Isaac-Reorient-Cube-Allegro-Direct(warp).Metrics/success_ratereported under warp.3.1 Frontend A/B benchmark
Same stable task id and cfg on both sides,
presets=newton_mjwarpon both (identical Newton physics — only--frontenddiffers); rsl_rl, 4096 envs, 300 iterations, headless, one L40 per run, single run per cell. Metric: rsl_rlTraining time:— learn-loop wall-clock (env stepping + PPO update; warp includes its one-time CUDA-graph capture). PPO-update cost is common to both, so the env-pipeline gain exceeds the end-to-end ratio.Isaac-CartpoleIsaac-HumanoidIsaac-AntIsaac-Velocity-Flat-AnymalD4. Out of scope / follow-ups
TerrainImporterAPIs; no twins for IK/OSC actions,height_scan, or Digit/Spot-specific terms.DirectRLEnvWarpand stableDirectRLEnvinto a common base.