Skip to content

[Warp] Part 6/8: Run commands and Rough velocity tasks on the Warp managers and apply term changes under capture - #8228

Draft
hujc7 wants to merge 82 commits into
isaac-sim:mainfrom
hujc7:jichuanh/warp-frontend-managers
Draft

hujc7 wants to merge 82 commits into
isaac-sim:mainfrom
hujc7:jichuanh/warp-frontend-managers

Conversation

@hujc7

@hujc7 hujc7 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Review Map

PR Status Depends on Exact changes
#8821 Part 1/8: shared id and mask helpers, mask writers without host sync — —
#8824 Part 2/8: PhysX tendon mask setters through the masks #8821 link
#6646 Part 3/8: mask-correct partial resets #8821 link
#8783 Part 4/8: shared Warp RNG state #6646 link
#6607 Part 5/8: per-term capture, stable stepping parity #8783 link
📌 #8228 Part 6/8: Warp commands, Rough tasks, live term changes #6607 link
#6582 Part 7/8: Shadow Warp twin, Allegro twin fixes #6607 link
#7012 Part 8/8: --video on the Warp frontend — —
---
config:
  look: neo
  flowchart:
    curve: basis
---
flowchart TD
  P1("<b>#8821</b> · Part 1<br/>id/mask helpers")
  P2("<b>#8824</b> · Part 2<br/>PhysX tendon masks")
  P3("<b>#6646</b> · Part 3<br/>masked resets")
  P4("<b>#8783</b> · Part 4<br/>WarpRng")
  P5("<b>#6607</b> · Part 5<br/>per-term capture")
  P6("📌 <b>#8228</b> · Part 6<br/>commands, Rough")
  P7("<b>#6582</b> · Part 7<br/>Shadow twin")
  P8("<b>#7012</b> · Part 8<br/>--video, independent")
  P1 --> P2
  P1 --> P3 --> P4 --> P5
  P5 --> P6
  P5 --> P7
  style P6 stroke-width:3px
Loading

Summary

--frontend warp now runs command terms on the Warp command manager and supports the Rough velocity tasks, a second Warp velocity environment builds in the same process (main: RecursionError), and curriculum changes through modify_term_cfg take effect under CUDA graph capture.
Eager and captured runs give bitwise-equal observations, rewards and dones on Reach, Go2 Flat and Go2 Rough.

Description

Update (2026-10-26): merged #6607 and, through it, #8821 and main dae8b799146. _prepare_term and _collect_class_instances of the Warp observation manager use main's tensor_processors names (#8693). Its own diff is otherwise unchanged.

Update (2026-10-22): merged #6607 after #8783 (the shared Warp RNG state) was split out of #6646; this PR's own diff is unchanged.

Update (2026-10-20): merged #6607 and, through it, main 2dbb6146d75 (the default branch, renamed from develop; "main" below means main without this PR, measured at aa9bafef8a0). #8388 folded the Warp migration guide into warp_environments.rst; this PR's documentation (capture contract, command terms, terms from other packages, Rough tasks) now edits that page. After the merge, the experimental unit tests (94), the frontend, external-term, scene, capture, commands, curriculum, decimation, final-obs, log and reset tests pass, and Cartpole, Reach and Go2 Rough Warp runs step 300 times.

Update (2026-10-04): ported onto #6607's per-call capture (@captured, eager(), reset_captured_stages) and merged develop aa9bafef8a0 through #6607; stages are recorded methods (e.g. RewardManager.compute), and a set term configuration records again only when the term's function, parameters or capturability change.

1. Changes

  1. Commands on the Warp command manager: twins of UniformVelocityCommand, UniformPoseCommand (sticky success from position and orientation thresholds, Metrics/success_rate) and NullCommand. Debug visualization is shared with the stable terms through _debug_vis.py, keeping marker_pos_offset and environment_ids. Terms read CommandManager.get_command_wp on every call; main caches the first environment's command on a function attribute.
  2. Rough velocity tasks (9 added to the Warp support list): a Warp height_scan twin and base_height_l2 with a height scanner run eagerly, because the Newton ray caster refreshes on the host (captured base_height_l2 failed with CUDA error 900). terrain_out_of_bounds reads its own environment's terrain bounds.
  3. External Warp terms: subclasses of the Warp ManagerTermBase and @WarpCapturable functions defined outside Isaac Lab are used as configured; an external Torch term sharing a built-in name fails fast instead of being replaced by the built-in.
  4. Term changes under capture:
    • The Warp termination, command and observation managers gain set_term_cfg / get_term_cfg (observation terms are named "<group>/<term>"), and the stable modify_term_cfg hands the changed config to the manager's set_term_cfg.
    • Stages are recorded again only when a term's function, parameters or capturability change, so curricula that set an unchanged config on every reset keep the recorded graphs.
    • A reward weight that turns zero or nonzero records the reward stage again (zero-weight terms stay out of the graph, as in [Warp] Part 5/8: Capture Warp stages per term without warm-up and step like the stable environments #6607); other weight changes apply without recording again.
  5. Docs: the capture contract (per-term capture, predicates, setters; direct config writes are unsupported under capture) and Rough task support in the Warp guides.

2. Evidence (MEASURED at 76fb382019b, desk RTX PRO 6000, 64 envs, deterministic, eager and captured in separate processes)

Task Steps Resets Recorded stages Eager vs captured
Isaac-Reach-Franka, curriculum active 400 64 13 obs, rewards, dones bitwise equal; logged metrics bitwise equal in 1 of 2 captured runs (the other: orientation_error 5.96e-7 from step 359)
Isaac-Velocity-Flat-UnitreeGo2 300 36 14 bitwise equal
Isaac-Velocity-Rough-UnitreeGo2 300 39 14 bitwise equal
  • The Reach metrics difference matches the one two eager runs showed on an earlier commit (INFERRED: physics nondeterminism, not capture).
  • isaaclab_experimental suite: 25 files, 151 passed, 1 skipped; the Kit-lane test_modify_env_param_curr_term.py passes.
  • Measured before the sync: the two-environment test fails with main's command caches (RecursionError); capture-then-mutate tests for a reward weight 0 → 2, a termination threshold, a command range and observation noise each fail without their re-recording; on Reach, once modify_reward_weight passes its step threshold, the reward stage is recorded once instead of after every reset (4 recordings in the test window).

3. Related PRs

4. Out of scope / follow-ups

  • Curricula stay on the stable manager and run eagerly: 12.5 µs host time per reset step on Reach, about 0.2% of a step.
  • A command term that cannot be recorded runs eagerly in place; one class annotation covers both its call and its reset, because both resample.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Release backport

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

Screenshots

Not applicable.

Checklist

Docker and GPU tests run on demand. Push the commits you want tested, then
comment run-ci on the pull request.

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with uv run isaaclab --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have validated the changed behavior with focused coverage and applied the test-audit criteria to any test changes
  • 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)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

A mask-only Articulation.reset called ActuatorCollection.reset(None), which
cleared delay buffers, network history, and Newton-native actuator state for
every environment. Newton rigid objects and collections dropped the mask
before resetting their wrench composers, clearing every environment's
persistent wrench.

ActuatorCollection.reset now takes env_mask. Isaac Lab actuator models still
reset by index, so the collection converts the mask to indices once for all
groups, and skips the conversion when no group carries reset state. Native
actuator state resets from the mask through a new
ActuatorControl.reset_native_actuators_mask hook; Newton and OVPhysX pass the
mask to the Newton actuator adapter, and the base default converts to indices
so backends that only implement the index hook stay correct.
InteractiveSceneWarp.reset passed env_ids=None to deformable objects, surface
grippers, and sensors on a masked reset, so every environment's sensor state
was cleared on each partial reset, and it never reset cable objects.

Forward the mask to every entity family in the stable scene's order. Surface
grippers use their reset_mask method. Cable objects take env_mask like the
other scene assets, so the scene does not convert the mask for a reset that
holds no state.
RigidObjectCollection.write_body_*_to_sim_mask converted masks to indices
with torch.nonzero before delegating to the index writers, so the mask
writers synchronized the host and could not be captured in a CUDA graph,
although their docstrings name them as the graph-capable path. They now
launch masked kernels over the full environment-body grid, and body_ids
selections resolve to a mask through a preallocated scratch buffer.

BaseRayCaster.reset compacted a mask with nonzero before resampling drift.
A masked reset now resamples every environment and keeps the new values
where the mask is set; index and slice selections are unchanged.
isaac-sim#8079 added ObservationGroupCfg.history_order and taught the stable
observation manager to skip it as a group setting. The Warp manager's skip
list was not updated, so it parsed the field as a term and raised a
TypeError for every observation group, and no manager-based Warp
environment could be constructed.

Skip history_order like the other group settings. The Warp manager raises
NotImplementedError for any term with history, and history_order only
takes effect with history, so a time-ordered group cannot be silently
reordered wrong.
The fixed-tendon set_*_mask methods and
write_fixed_tendon_properties_to_sim_mask converted masks to indices with
torch.nonzero and delegated to the index writers, so they synchronized the
host and could not be captured in a CUDA graph.

Write the masked cells in-kernel instead: scalar and full data go into the
staging buffers through masked kernels, and the property writer copies the
masked cells of each staging buffer into its simulation binding. The mask
compaction helpers have no remaining callers and are removed.
A masked Articulation.reset on the Newton-native actuator path allocated a
per-DOF mask for every actuator on each call, and a masked ray-caster
reset sampled drift with torch ops and uploaded its ranges, so neither
could run inside a CUDA graph captured on Warp's stream.

NewtonActuatorAdapter preallocates its per-DOF reset masks. BaseRayCaster
resamples masked drift in a Warp kernel from per-environment random states
seeded from torch's global generator at initialization, with the ranges
passed as launch arguments; index and slice resets keep the torch path.
The Newton fixed-tendon position-target setters raised a host-side dirty
flag, and write_data_to_sim scattered the buffered targets into MuJoCo's
controls only while it was set. A setter recorded in a CUDA graph raised
the flag once, at capture time, so later replays updated the target buffer
on the device but never reached the solver.

Write the buffered targets on every step whenever the articulation has
tendon actuators, and drop the flag from the Newton setters. The write
costs about 60 us per call on Shadow Hand; gating it on a device-side flag
would need Newton's control-array mapping re-implemented in a kernel.
Replace ManagerCallSwitch with an environment-owned WarpGraphCache that
follows the Newton core capture model: a stage is recorded through
NewtonQueries.capture_graph with no eager warm-up, so the first
execution is the launch of the new graph; recording starts at the first
step; graphs are keyed by the pointers and scalar values of the stage
arguments; and every graph is dropped when the physics backend rebinds
its buffers or stops, so the next step records against the new buffers.

Managers with a non-capturable term keep their stages eager through
register_capturability. ISAACLAB_WARP_CAPTURE=0 runs every stage
eagerly and ISAACLAB_SYNC_DEBUG=1 traps hidden host synchronizations in
eager stages. The direct environment uses the same cache in place of
capture_or_replay.
The manager-based Warp environments now reset through one mask-first
pipeline: every reset selection is copied into an environment-owned mask
that the recorded reset stages read, the scene and the Warp managers
reset from that mask, and the episode-length buffer is cleared by a
masked kernel. The mask is compacted to environment indices once per
reset, and only when a stable command, curriculum or recorder manager has
active terms. ManagerBasedEnvWarp.reset accepts env_mask.

Each step makes one host check to decide whether any environment
terminated: the index compaction when a manager needs indices, otherwise
reset_buf.any(). DirectRLEnvWarp moves its reset out of the end-of-step
graph into its own stage behind the same predicate.

A static gate pins the sanctioned host synchronizations of the mask-first
path, which also removes the device read from CircularBuffer.max_length.
The Episode_Termination metric keeps averaging over every environment,
as the stable manager does.
push_by_setting_velocity, apply_external_force_torque,
reset_root_state_uniform and randomize_rigid_body_com kept scratch
arrays and parsed ranges as attributes of the module-level function. The
first call allocated them, which inside a recorded stage becomes
graph-owned memory; the parsed ranges froze the first configuration; and
the state was shared by every environment in the process.

They are now ManagerTermBase classes that allocate in __init__ and read
their ranges on every call. randomize_rigid_body_com offsets the center of
mass it finds on its first call instead of accumulating, and
apply_external_force_torque with zero ranges leaves the permanent wrench
composer untouched.

The material and mass adapters reject modes other than startup and are
marked non-capturable. Startup terms no longer keep the event stages
eager, since they never run inside one, and set_term_cfg re-resolves the
term and drops the recorded event stages.
A manager with one non-capturable term used to run every stage eagerly.
Managers now describe each stage as ordered steps, one per term plus
their own setup and finalize kernels, and WarpGraphCache.call_steps
records each run of consecutive capturable steps into one graph while
the terms marked @WarpCapturable(False) run eagerly in place.

Reward and termination terms write their own output columns and the
finalize kernel combines the columns in a fixed order, so these stages
evaluate the capturable terms first and need at most one eager group.
Observation noise and events draw from the shared per-environment random
state, and action terms are applied in order, so those stages keep the
configured term order and split into contiguous runs.

Observation terms with history run as eager steps instead of raising
inside capture. Replacing a reward term rebuilds its steps and drops the
recorded reward stages.
A term can be capture-safe for one configuration and not another: a
reward that reads a ray-caster refreshes the sensor on the host and
launches its query graph, which cannot run inside a recorded stage,
while the same reward without a sensor can.

WarpCapturable accepts a predicate over the term's parameters, and its
guard raises only for the parameters the predicate rejects. The Warp
managers resolve capturability with each term's parameters, and a class
term may set _warp_capturable in __init__ to decide per instance. The
sync gate's opt-out inventory also lists predicate annotations.
Stable environments tell the physics backend their decimation and, when the
backend runs the whole decimation loop, apply actions, step the simulation and
update the scene once per environment step. The Warp environments still looped
once per physics step, so contact sensors sampled four times as often on Go2
and the contact history, air times and the rewards and terminations reading
them differed from the stable environment with the same configuration.

Mirror the stable loop in the manager-based and direct Warp environments. The
per-physics-step loop remains for backends that do not run the decimation loop.
The stable environments honor compute_final_obs: a step that resets
environments stores the observations from before the reset in
extras["final_obs"] and declares Same-Step autoreset in the metadata. The
Warp environments ignored the flag, so torchrl, which sets it, bootstrapped
time-outs from the post-reset observation.

Compute the observations before the reset stage and keep a copy, because the
reset overwrites the persistent observation buffers. Declare Same-Step
autoreset in both Warp environment classes.
Warp managers and tasks log persistent buffers that later steps overwrite.
Loggers such as the rsl_rl logger keep extras["log"] of every step and average
it at the end of an iteration, so every entry showed the latest value. The
stable environments log fresh tensors.

The manager-based environment copies the tensor values when it builds the
reset log. The direct environment keeps the log the task writes and returns a
copy of it from every step, so task code that is recorded into a CUDA graph
keeps refreshing its own buffers.
ManagerBasedEnvWarp built its reset log from the same persistent manager
buffers as the RL environment, so a logged value changed with the next reset.
Copy the tensor values like the RL environment does.
Warp environments now build the experimental CommandManager and record its
compute and reset stages. The frontend swaps command term class_types for
their Warp twins (commands join WARP_MANAGED_GROUPS); the term configs stay
the stable ones.

Add Warp twins of UniformVelocityCommand, UniformPoseCommand and
NullCommand. The pose twin keeps the per-episode sticky success flag that
the pose_command_success termination writes, checks both the position and
the orientation thresholds, and logs Metrics/success_rate like the stable
term. The null twin never resamples, so its time_left stays finite.

The debug visualization of the stable velocity and pose commands moves into
private mixins shared with the twins, keeping marker_pos_offset and
environment_ids.

Warp reward and observation terms read the command through
CommandManager.get_command_wp on every call instead of caching the first
environment's buffer in a function attribute.
The Warp observation manager infers the output size of a ray-caster term
from the sensor's ray count, so height_scan needs no IO descriptor. The
Newton ray caster refreshes through host-side timestamps and scene
transforms, so height_scan is marked non-capturable and runs eagerly.

terrain_out_of_bounds reads the terrain bounds from the environment on
every call instead of caching the first environment's in function
attributes.

The rough velocity tasks keep the stable terrain curriculum and now adapt
under --frontend warp.
A term defined outside the mirrored packages has no twin. The frontend now
uses it unchanged when it is a Warp term: a class term that subclasses the
Warp ManagerTermBase, or a function term that declares its capture behavior
with WarpCapturable. Any other such term is reported as not a Warp term
instead of being replaced by an unrelated built-in twin of the same name.
Reading a Newton ray caster refreshes it on the host and launches its own
query graph, which fails inside a recorded reward stage: Go2 Rough stopped
at its first captured step with CUDA error 900. base_height_l2 now declares
itself capturable only without a sensor, so the reward stage records its
other terms and runs this one eagerly; Go2 Flat keeps it recorded.

The height-scanner case of the capture harness becomes an eager parity
test, since that configuration is never recorded.
A recorded stage keeps the scalars a term read while it recorded, so a
configuration change reaches a captured stage only through a setter that
drops the recorded graphs. The Warp termination and command managers gain
set_term_cfg and get_term_cfg that do so, matching the reward and event
managers.

The stable modify_term_cfg curriculum writes into a manager's term
configuration directly. After writing a value it now hands the term
configuration back to the manager's set_term_cfg when the manager has one,
which is a no-op for the stable managers and records the Warp stages again.

Capture-then-mutate tests cover a reward weight raised from zero, a replaced
termination threshold, and a command range changed by modify_term_cfg.
The migration guide describes the stable-config frontend, the command
terms, the author contract for recorded stages and Warp terms from other
packages. The environment overview lists the rough velocity tasks and the
stages that stay eager.
A recorded observation stage keeps the noise parameters its terms read
while it recorded, so an observation noise curriculum written through
modify_term_cfg, like the noise ADR terms of the Lift task, changed
nothing once the stage was captured.

The Warp observation manager gains set_term_cfg and get_term_cfg, naming
a term "<group>/<term>". Setting a term prepares it again and drops the
recorded observation graphs. The per-term preparation moves out of
_prepare_terms so both paths share it; an existing noise model keeps its
state. modify_term_cfg hands observation terms back under that name.

A capture-then-mutate test changes a noise bias through modify_term_cfg
after the observation stage recorded; it fails without the setter.
Curricula such as modify_reward_weight set their term on every reset past
their threshold, and set_term_cfg dropped the manager's graphs on every call,
so the reward stage was recorded again on every reset step.

The reward stage now checks zero weights on the device: while recording, each
term runs under a conditional graph node on an int32 flag kept beside the
weights, so any weight change, including to and from zero, is a device write.
Without conditional node support (CUDA < 12.4) the record-time check stays and
a zero crossing records again.

The reward, event, termination, command and observation setters compare the
term against the values taken when the steps were built, since curricula
change the stored configuration in place, and drop the graphs only when what
the recorded steps read differs: the function, parameters and capturability,
an event term's trigger settings, and the whole configuration of command and
observation terms. An unchanged event or observation term is no longer
resolved again, which reallocated its Warp buffers.
hujc7 added 10 commits October 20, 2026 09:52
…tend-managers

# Conflicts:
#	docs/source/concepts/warp_environment_migration.rst
#	docs/source/concepts/warp_environments.rst
Revert 6ac6061 and 9328d98: the process-wide WarpRng state moves to its own pull request stacked on this one. The ray caster, Warp environments, MDP terms, managers, noise models and Warp tasks keep their own RNG arrays here, as on main.
Environments initialize WarpRng once at construction and configure_seed reseeds it in place. The ray caster, experimental managers, events, noise models and Warp tasks read WarpRng.state instead of keeping their own copies; rng_state_wp is removed.

Re-applies 6ac6061 and 9328d98, split out of the masked-reset pull request.
@hujc7 hujc7 changed the title [Warp] Part 3/5: Run commands and Rough velocity tasks on the Warp managers and apply term changes under capture [Warp] Part 4/6: Run commands and Rough velocity tasks on the Warp managers and apply term changes under capture Oct 22, 2026
hujc7 added 11 commits October 26, 2026 02:48
…pers

resolve_ids resolves ids or a boolean mask to ids on a device, as resolve_1d_mask does to a mask. resolve_1d_mask takes an optional scratch mask, copies a Warp mask on another device, and accepts a boolean torch mask on its device inside a graph capture. The experimental package now re-exports it instead of keeping a copy.
…red helpers

SensorBase, Camera and WrenchComposer resolve env_ids, env_mask and body_ids through resolve_ids and resolve_1d_mask instead of their own copies. The test doubles that build these objects without their initializer get the env masks a real sensor creates.
The nodal mask writers of BaseDeformableObject, which the OVPhysX deformable object inherits, and of the PhysX deformable object called wp.nonzero on a Warp array, which raised a RuntimeError. They now resolve the mask to ids with resolve_ids and call the index writers.
…sk writers through masks

The Newton assets resolve ids and masks through resolve_ids and resolve_1d_mask. The RigidObjectCollection mask writers and the fixed-tendon mask setters write through the masks instead of converting them to ids, which waited for the GPU on every call; the joint-coordinate writes reuse a scratch mask; one generic masked 2-D write kernel replaces two copies.
The OVPhysX assets resolve env, body, joint and tendon selections through resolve_ids and resolve_1d_mask instead of their own copies.
The PhysX assets resolve env, body, joint and tendon selections through resolve_ids and resolve_1d_mask instead of their own copies. The sensor test doubles get the env masks a real sensor creates.
…-native

isaac-sim#8821 owns the Newton mask writers: the collection write_body_*_to_sim_mask methods,
the fixed-tendon mask setters and their shared masked-copy kernel. This branch takes
its version of those and keeps only the reset-mask plumbing and the tendon-target
capture fix in the shared files.
…-shared

# Conflicts:
#	source/isaaclab/isaaclab/sensors/ray_caster/base_ray_caster.py
…leanup

# Conflicts:
#	source/isaaclab_experimental/isaaclab_experimental/managers/observation_manager.py
#	source/isaaclab_experimental/isaaclab_experimental/utils/__init__.pyi
#	source/isaaclab_experimental/isaaclab_experimental/utils/warp/utils.py
…tend-managers

# Conflicts:
#	source/isaaclab_experimental/isaaclab_experimental/managers/observation_manager.py
Writing the buffered targets every step cost 2-4% physics step time for every
articulation with tendon actuators. The only Warp caller, the Shadow Hand twin,
sets its targets outside the captured stage, so the host flag still sees them.
@hujc7 hujc7 changed the title [Warp] Part 4/6: Run commands and Rough velocity tasks on the Warp managers and apply term changes under capture [Warp] Part 6/8: Run commands and Rough velocity tasks on the Warp managers and apply term changes under capture Oct 26, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request documentation Improvements or additions to documentation infrastructure isaac-lab Related to Isaac Lab team isaac-mimic Related to Isaac Mimic team isaac-sim Related to Isaac Sim team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant