Step Newton with one program and one capture policy#6614
Draft
ooctipus wants to merge 1 commit into
Draft
Conversation
NewtonManager ran two parallel step programs (_simulate_full, _simulate_physics_only) and a capture decision forked three ways (init-time wp.ScopedCapture, RTX deferral, Newton-actuator skip). The skip arm relied on set_decimation to capture later, so callers that never set a decimation - plain SimulationContext loops - ran eager forever (measured 5.06 ms vs 0.91 ms per tick for the same workload). Collapse both forks: - _simulate() is the only step program, composed of _iteration_stages (collide -> actuators -> post-actuator hooks -> substeps) and _tail_stages (post-step hooks -> sensors). It runs the decimation loop when the manager owns it and exactly one sub-step otherwise; mixed scenes step the adapter eagerly and replay a per-iteration graph (_iteration_graph) inside the loop. - handles_decimation() now follows the Newton actuator path (loop ownership), no longer aliased to graph-safety, so mixed scenes fold the decimation loop into one step() call as well. - Decision sites (initialize_solver, set_decimation, hard reset) only call _invalidate_graph(): drop the graphs, owe a re-capture. The sole capture site is step()'s pending-consume block, and the one capture routine _capture_relaxed_graph(device, warmup, capture) works both headless and with RTX active. Its warmup runs _simulate eagerly and counts as the capturing tick's step: the execute branch is skipped and sim_time advances at a single site, so the first step never double-advances. The Kamino replay-once-to-pin lives inside the capture routine instead of being duplicated at two sites. The never-captured-scenes bug is fixed as a corollary: decision sites only invalidate and the first step captures, on every path. Exactly one capture per invalidation also makes the back-to-back capture CUDA faults of the contact + actuator pipeline structurally impossible. The regression test (test_deferred_graph_capture.py) was verified to fail against the original skip-instead-of-defer behavior.
ooctipus
force-pushed
the
zhengyuz/fix-deferred-graph-capture
branch
from
July 20, 2026 01:44
74ab0f3 to
ff6344c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
One step program and one capture policy for the Newton physics manager.
_simulate_full/_simulate_physics_onlyare replaced by ONE program:_simulate()runssteps x (collide -> actuator stage -> post-actuator hooks -> solver substeps)and a tail (post-step hooks, sensors).stepsisdecimationwhen the manager owns the loop and1when the environment does — loop ownership is data, not a second code path.set_decimation, hard reset) calls_invalidate_graph(); the firststep()afterwards captures. The init-timeScopedCapturebranch, the RTX-vs-headless selector,set_decimation's inline recapture, and the duplicated Kamino pin-replay are deleted.handles_decimation()reports loop ownership instead of aliasing kernel graph-safety.Why
Scenes whose callers never set a decimation (plain
SimulationContextloops) previously never got their CUDA graph and ran eager forever; fixing that minimally would have added a third arm to an already-forked capture decision. With one program and one policy, both historical failure modes — never-captured and back-to-back re-capture faults — are structurally unrepresentable: there is exactly one capture per invalidation and exactly one program to capture.Evidence
test_deferred_graph_capture.py): a fast-path scene that never sets a decimation gets its graph on the first step; verified to fail on the old code.runtime.py --task Isaac-Velocity-Flat-AnymalD --num_envs 1024 --num_frames 300 --seed 7, RTX 5090): base 12.70 ms/iter, this PR 12.86 ms/iter (within run noise).Test plan
🤖 Generated with Claude Code