WIP: DR Legs#5962
Conversation
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This WIP PR adds support for the Disney DR Legs closed-loop biped robot (36 joints: 12 actuated, 24 passive linkage) to Isaac Lab using the Kamino solver. Key additions:
ClosedLoopView— a duck-typed replacement forArticulationViewthat provides strided views into Newton's global arrays for robots with cyclic kinematic loops (noArticulationRootAPI).- Kamino environment reset fixes — per-env scoped FK invalidation and dual-state sync to prevent cross-env leakage and post-reset velocity spikes.
route_torque_toactuator routing — newClassVaronActuatorBaseto route torques toControl.joint_act(Kamino implicit constraint) instead ofControl.joint_f.Isaac-DrLegs-HoldPose-v0task — a manager-based RL hold-pose task with full reward/obs/termination/event config.newton_kaminopresets — added across ~10 existing locomotion/manipulation env configs.
Findings
🔴 Critical / Correctness
-
manager_based_rl_env.py— unconditionalwrite_data_to_sim()+sim.forward()+scene.update()after reset (line ~255)
This affects all environments, not just Kamino/DR-Legs. The comment says "for others this is a no-op", butwrite_data_to_sim()will flush any dirty buffers for PhysX/MJWarp envs too, andsim.forward()is never free — it re-evaluates the sim state. This should be guarded by a check (e.g.if self.cfg.sim.physics.solver_cfg.solver_type == "kamino"or a flag on the physics manager) to avoid performance regression on the ~20+ existing PhysX/MJWarp tasks. At minimum, this needs benchmarking. -
ClosedLoopView._get_strided_view— raw pointer arithmetic without bounds checking
The method computesbase_ptr = int(attrib.ptr) + val_offset * int(value_stride)and constructs awp.arrayfrom it. Ifval_offset >= items_per_world(misconfigured frequency lookup), this silently reads out-of-bounds GPU memory. An assertionassert val_offset < items_per_worldwould catch misconfigurations early. -
ClosedLoopViewassumes uniform distribution of bodies/joints/shapes across worlds (model.body_count // nwetc.)
This assumption breaks if any world has a different number of bodies (e.g. debug prims added to world 0). The code should validatemodel.body_count % nw == 0at construction time and raise if not. -
_forward_kamino—.numpy()calls on GPU tensors in the hot path (lines ~50-70 of kamino_manager.py)
_model.joint_type.numpy()[0]and_model.joint_child.numpy()do GPU→CPU synchronization on every reset. These values are model constants — they should be computed once at solver construction time and cached.
🟡 Design Concerns
-
_prim_has_closed_kinematic_loopscalled inside_initialize_implfor EVERY articulation
This iterates the full USD prim subtree on every env. For large scenes with many articulations, this is potentially expensive. Consider caching the result per prim path or moving the check to builder time. -
ClosedLoopView.articulation_idsreturns a freshly-allocatedwp.zeros(...)every access
This is a@propertythat allocates on every call. If any code calls it in a loop, it creates garbage. Should be cached asself._articulation_ids. -
_joint_act_target_simbuffer is never zeroed between steps
_joint_effort_target_simappears to be zeroed/overwritten each step by_apply_actuator_model, but if an actuator switchesroute_torque_todynamically (or if not all joint indices are covered), the_joint_act_target_simbuffer may accumulate stale values. Ensure it's zeroed at the start of eachwrite_data_to_sim. -
ActionRate2L2.resetusesenv_idsas a tensor slice but also acceptsNone
Whenenv_idsisNone,slice(None)correctly zeros everything. But the__call__method updates_prev_action/_prev_prev_actionunconditionally — after a partial reset, the history for non-reset envs is correct, but the just-reset envs get the current action stored into their history. This means the first reward computation after reset computes jerk against zero (correct), but the second step computes jerk relative to the action taken during the reset step (also correct). This is fine — just noting it's a design choice.
🟢 Minor / Nits
-
DR_LEGS_JOINT_ORDERhardcodes 36 joint names — if the USD changes, this silently diverges. A runtime validation (asserting discovered joint names match the list) would be defensive. -
hold_pose_env_cfg.pyimportsmdp.JointPositionActionCfgvia the wildcard re-export — this works but makes it non-obvious whereJointPositionActionCfgoriginates. -
Several reward terms (
base_lin_vel_l2,base_ang_vel_l2) shadow stock Isaac Lab MDP terms with identical names — since the module doesfrom isaaclab.envs.mdp import *thenfrom .rewards import *, the local definitions override the stock ones silently. This is intentional (per the module docstring) but should be documented with a comment at the override site. -
gait_phaseobservation function is defined but never used inObservationsCfg— dead code for the hold-pose task (presumably for the upcoming walk task).
Verdict
Significant concerns
The ClosedLoopView adapter and Kamino reset logic are architecturally sound for a WIP, but the unconditional post-reset write_data_to_sim()/sim.forward() in manager_based_rl_env.py (item 1) has regression risk for all existing envs and needs to be gated. The .numpy() calls in the reset hot path (item 4) will also cause performance issues at scale. These should be addressed before merging.
Update (fa8efb0): Reviewed incremental diff (300 files changed since 0c28922). This is a large commit that touches the PR's DR Legs work and sweeps across the broader codebase (lazy pxr imports, test renames, install CI refactor, version bumps to 6.3.0, etc.).
Changes relevant to this PR
-
manager_based_rl_env.py— unconditional post-resetwrite_data_to_sim()/sim.forward()/scene.update()is now committed (Finding #1 from original review).- The comment says "for others this is a no-op", which is inaccurate —
write_data_to_sim()+sim.forward()are never free (they flush Fabric/PhysX state). ⚠️ Status: Still unguarded. Should be conditioned on solver type or at least benchmarked on PhysX tasks before merging out of WIP.
- The comment says "for others this is a no-op", which is inaccurate —
-
EventTermCfg.resample_interval_on_resetadded — newboolfield (defaultTrue) lets interval event terms preserve their counter across resets. Clean design, well-documented, tested in both the stable and experimental event managers. ✅ Good. -
dr_legs.pyasset config added — the 36-joint split (12 actuated / 24 passive) is cleanly documented with canonical joint ordering and explicit zero-PD for passive linkages. Uses localdata/directory for USD asset (with TODO to switch to Nucleus). ✅ -
Lazy
pxrimports acrossisaaclab.sim— massive refactor moving top-levelfrom pxr import ...into function-level imports behindTYPE_CHECKING. This is unrelated to DR Legs but significantly changes theisaaclab.simmodule. Well-structured: runtime imports in function bodies, type stubs preserved. -
NewtonSDFCollisionPropertiesCfgadded to lazy-forwarding sets inisaaclab.sim.__init__andisaaclab.sim.schemas.__init__. Test coverage added intest_schemas_shim.py. ✅ -
isaaclab_experimentallazy exports —envs,envs.mdp,managerspackages converted to lazy export with.pyistubs. This avoids eager backend imports at config-load time. ✅ Good for Warp task configs. -
Gravity kernel fix in
isaaclab_experimentalobservations/rewards: now reads per-envgravity_w[i]and normalizes, instead of always readinggravity_w[0]. Fixes per-env gravity randomization on Newton. ✅
Previous findings status
| # | Finding | Status |
|---|---|---|
| 1 | 🔴 Unconditional post-reset write_data_to_sim()/sim.forward() | Still present, now committed — needs gating |
| 2 | 🔴 .numpy() GPU→CPU sync in Kamino reset | Not addressed in this diff |
| 3 | 🔴 ClosedLoopView uniform distribution assumption | Not addressed in this diff |
| 4 | 🟡 articulation_ids allocates on every access | Not addressed |
| 5 | 🟡 _joint_act_target_sim never zeroed | Not addressed |
New observations
-
from_files.py—RigidBodyMaterialCfgreplaced withPhysxRigidBodyMaterialCfg(direct import fromisaaclab_physx). This tightens the coupling to PhysX for compliant contact materials inspawn_from_usd_with_compliant_contact_material. Intentional since compliant contacts are PhysX-only, but breaks the shim pattern used elsewhere. -
Version bump to 6.3.0 (from 6.1.0) with
pin-pinkanddaqppinned to exact versions (==3.1.0,==0.8.5) to avoid breaking changes. Reasonable defensive pinning. -
modify_fixed_tendon_propertiesnow handles MjcTendon prims — clean branching onprim_type != "MjcTendon". No issues.
Verdict
The PR continues to mature. The DR Legs asset config is clean and well-documented. The biggest outstanding concern remains Finding #1 (unconditional post-reset physics step for all backends). The other critical findings (#2, #3) from the Kamino/ClosedLoopView code remain unaddressed but may be in a future commit.
Update (51ed92e): Incremental diff contains only directory renames (moving dr_legs from manager_based/locomotion/ to contrib/) and a corresponding import path fix in hold_pose_env_cfg.py. No substantive code changes. Previous findings remain unaddressed (as expected for a reorganization commit).
Update (16734e4): Single-file incremental change — adds a tendon_count property to ClosedLoopView (returns hardcoded 0). This satisfies the ArticulationView duck-type interface so that ArticulationData can allocate empty tendon bindings without crashing. Clean, well-documented docstring explaining why closed-loop assets don't expose MuJoCo-style fixed tendons. ✅ No issues.
Previous critical findings (#1–#3) remain unaddressed.
Update (f6ed606): Single-file incremental change — renames state_out= keyword argument to state= in three cls._solver.reset() calls in kamino_manager.py. This aligns with an upstream Kamino solver API rename. No functional change. ✅ No issues.
Previous critical findings (#1–#3) remain unaddressed.
Update (2f9d208): Incremental diff adds the velocity-tracking walk task, Kamino contact force aggregation, and a wp.bool type cleanup for _world_reset_mask.
Changes reviewed:
kamino_manager.py— AddsContactAggregationinit in_build_solver(deferred import, cleaned inclear()). ✅newton_manager.py—_world_reset_maskdtype changed fromwp.int32→wp.boolconsistently across all kernels and allocations. ✅ Clean type-correctness refactor.contact_sensor.py/contact_sensor_kernels.py— New Kamino-specific path reads per-body net forces viagather_body_net_forces_kernel. Lazy body-index init, properenv_mask/timestampguards. ✅walk_env_cfg.py— New walk env config extending hold-pose with velocity commands, gait-phase obs, contact sensor, and reward suite. Well-structured. ✅mdp/rewards.py—feet_flatadded (penalizes foot roll/pitch),feet_collisionremoved (incompatible with Kamino aggregation-only contacts). ✅__init__.py— RegistersIsaac-DrLegs-Walk-v0. ✅
Previous findings status:
- Finding #12 (gait_phase dead code): ✅ Resolved — now used in
WalkObservationsCfg. - Findings #1–#3 (unconditional post-reset sim step,
.numpy()in hot path, uniform body assumption): Still unaddressed (expected for WIP).
No new issues introduced in this increment.
Update (4e636bd): Incremental diff is dominated by base-branch changes (locomotion task restructuring into core/locomotion/{ant,humanoid}, reach/ant/humanoid Gym ID renames dropping -v0, preset CLI fold_preset_tokens removal, typed selector validation, visualizer fixes, version bumps). These are unrelated to DR Legs.
DR Legs-specific changes:
rsl_rl_ppo_cfg.pyrenamedHumanoidPPORunnerCfg→DrLegsHoldPosePPORunnerCfg/DrLegsWalkPPORunnerCfgwith proper hyperparameters (512×3 network,obs_groupsfor asymmetric critic, entropy 0.001, LR 1e-3 adaptive). Clean. ✅- Walk task
DrLegsWalkPPORunnerCfginherits hold-pose and overrides only experiment name/max_iterations via__post_init__. ✅ - No changes to
ClosedLoopView,kamino_manager.py, ormanager_based_rl_env.pyin this increment.
Previous findings status — unchanged:
- Findings #1–#3 (unconditional post-reset sim step,
.numpy()in hot path, uniform body assumption): Still unaddressed (expected for WIP). - No new issues introduced in the DR Legs changes.
Update (be83521): Incremental diff removes the Kamino-specific ContactAggregation path from contact_sensor.py and kamino_manager.py, replacing it with a single unified copy_from_newton_kernel path for all solver backends. The comment explains that Newton's SensorContact now accumulates per-contact forces from Contacts.force (which both MuJoCo-Warp and Kamino solvers populate in their update_contacts conversions). This is a clean simplification — one code path instead of two, with the aggregation responsibility pushed upstream into Newton. ✅ No issues.
Previous findings status — unchanged:
…pdated various environment configurations to utilize the new Kamino solver settings.
…ate environment configurations to use newton_kamino naming convention for clarity.
…oach for all solver backends.
DR Legs closed-loop hold-pose and walk task
Opening early for visibility. Parts of this PR are moving into digestible sub-PRs #6026 #6027 #6028, leaving the pure DR Legs task here.
Isaac-DrLegs-HoldPose-v0hold-pose task as well asIsaac-DrLegs-Walk-v0walking task.ClosedLoopView: a duck-typedArticulationViewfor robots with cyclic joint graphs / noArticulationRootAPI(DR Legs can't form a tree). Open question is this adapter the right direction, or should closed-loop support be handled differently? Happy to discuss.