Amd/solver convergence early exit - #5
Open
zhihuidu-amd wants to merge 6 commits into
Open
Conversation
Makes wp.ScopedCapture() + mjw.step() + wp.capture_launch() work correctly
on AMD MI300X/MI325X (ROCm 7.x) without any environment variables.
Three changes enable zero-overhead graph replay:
1. solver.py: cache solver context on Data._solver_ctx on first call.
On HIP/ROCm, temporarily disable memory pool during allocation so buffers
use hipMalloc (stable addresses) not hipMallocAsync. hipMallocAsync
pointers appear as memAlloc nodes inside a hipGraph and re-execute on every
replay, adding ~5ms overhead per step. On CUDA this code path has no effect.
2. smooth.py: same fix for tendon scratch buffers (ten_Jdot, ten_bias_coef).
Both are wp.zeros() calls that fire inside graph capture on HIP, adding
two more memAlloc/memFree node pairs. Cached on Data with stable hipMalloc.
3. forward.py + io.py: optional multi-stream parallelism in fwd_position.
put_data() pre-creates two dedicated streams (stream_collision,
stream_secondary). fwd_position() uses them to run collision detection
and mass-matrix kinematics concurrently via fork-join.
The join uses stream.record_event / stream.wait_event (GPU-side events,
capturable as graph dependency edges) rather than synchronize_stream
(CPU-blocking, raises RuntimeError inside graph capture).
Falls back to sequential execution when sleep is enabled or streams
are unavailable.
Measured on AMD MI325X (gfx942, ROCm 7.2), humanoid model, 256 worlds:
Before: graph replay 7.1 ms/step (0.33x slower than eager 2.4 ms)
After: graph replay 1.4 ms/step (1.7x faster than eager 2.4 ms)
Usage (unchanged from upstream convention):
with wp.ScopedCapture() as cap:
mjw.step(model, data)
wp.capture_launch(cap.graph) # 1.7x faster on AMD ROCm
Provides a mempool-aware wrapper around wp.ScopedCapture() that works
correctly with all Warp versions on AMD ROCm:
with mjw.hip_graph_capture() as cap:
mjw.step(model, data)
wp.capture_launch(cap.graph) # ~1.7x faster on AMD ROCm
On HIP/ROCm: auto-enables mempool before ScopedCapture (required for
hipGraph capture) and restores pool state after. Some Warp versions
disable mempool globally in put_data() for ROCm 7.2 stability; this
helper ensures capture always has a compatible memory state.
On CUDA: behaves identically to wp.ScopedCapture() (no-op wrapper).
Note: wp.ScopedCapture() also works directly if the caller manages
mempool state manually.
Takes model+data, runs warmup_steps (default 3) eager steps before capture to trigger all lazy wp.zeros/wp.empty calls (solver context, tendon scratch, RK4 buffers, collision structures, etc.). After warmup these are cached on Data and do not fire during capture, ensuring the graph has zero memAlloc nodes and replays at full speed. Also enables mempool on HIP before capture and restores state after. Updated signature: mjw.hip_graph_capture(model, data, device=None, warmup_steps=3)
…m graph Multi-stream fork-join benefits eager execution but adds event-record/wait nodes to the captured graph. On ROCm 7.2, these event nodes add overhead during replay. During ScopedCapture (wp.get_device().is_capturing=True), use the sequential single-stream path to produce a minimal graph with only kernel nodes. Multi-stream continues to benefit eager execution.
Amd/hip graph support
… for ROCm Two solver optimizations for AMD HIP/ROCm devices: **Opt B: Convergence-based Newton early exit (solver.py)** On CUDA, wp.capture_while() implements conditional graph nodes that exit the Newton loop early when all worlds converge. This is not available on HIP/ROCm. We implement the equivalent by sampling nsolving (active world count) every N_CHECK=3 iterations and breaking when all worlds converge. D2H sync cost is ~2us; N_CHECK=3 amortizes this while catching convergence within a few extra iterations of the true convergence point. **Opt C: Linesearch step count reduction (io.py)** The MuJoCo default ls_iterations=50 was designed for CPU float64 precision. On GPU float32, 10 candidates on a log scale gives equivalent physics quality for warmstarted humanoid/quadruped workloads at 5x lower linesearch compute. Guidance included: this is workload-dependent and may not help (or may hurt) at large environment counts where GPU is compute-bound. Users can override via model.opt.ls_iterations after put_model(). Tested on AMD MI325X (gfx942), ROCm 7.2, Unitree G1. AIOSS-5858.
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.
PR Description: Opt B + C (solver convergence exit + linesearch reduction)
Use for: zhihuidu-amd PR#2, AMD-Ecosystem new PR, google-deepmind new PR
Title:
feat(hip): convergence-based Newton early exit + linesearch reduction for AMD ROCm (Opt B+C)Summary
Two solver optimizations for AMD HIP/ROCm that together provide up to 2.27× throughput improvement over the AMD upstream baseline, enabling AMD MI325X to surpass NVIDIA H200 by 37% on Unitree G1 locomotion training.
Measured on AMD MI325X (gfx942, ROCm 7.2), Unitree G1, 16,384 environments:
Opt B (convergence exit) is the dominant contributor — alone it reaches 227% of the AMD baseline and 144% of H200.
Opt B: Convergence-based Newton Early Exit (
solver.py)Problem
On NVIDIA CUDA,
wp.capture_while()exits the Newton loop early when all simulation worlds converge, using CUDA 12.4+ conditional graph nodes. This feature is not available on HIP/ROCm.Without early exit, the solver always runs the full
m.opt.iterationsNewton steps even when worlds converge in 1–2 iterations on a warmstarted humanoid, wasting compute.Fix
We implement the equivalent convergence check by sampling
nsolving(count of still-solving worlds) everyN_CHECK=3iterations with a CPU-GPU sync (~2µs) and breaking when all worlds converge:The
_in_captureguard correctly skips the D2H sync during hipGraph capture (synchronize_streamis forbidden during HIP graph capture).Opt C: Linesearch Step Count Reduction (
io.py)Problem
The MuJoCo default
ls_iterations=50was designed for CPU float64 precision. On GPU float32, the parallel linesearch evaluates all step-size candidates simultaneously — 10 candidates on a log scale gives equivalent physics quality for most warmstarted humanoid/quadruped workloads at 5× less linesearch compute cost.Fix
Important Guidance: When to Use Opt C
This optimization is workload-dependent. Full ablation results:
On the G1 humanoid at large env counts, the GPU is compute-bound and degraded step quality from fewer linesearch candidates requires additional Newton iterations, slightly reducing net throughput. Opt C benefits workloads where linesearch compute dominates and step quality has slack.
Users can always override after
put_model():The code includes comments explaining the trade-off.
Full Ablation Results Across All Environment Counts
*True upstream baseline from
amd-integrationbranch (no AMD optimizations).Related