Skip to content

Enable compiler-managed CUDA graphs for UMA inference - #2161

Draft
mlazos wants to merge 8 commits into
mainfrom
uma-reduce-overhead-pr2154
Draft

mlazos wants to merge 8 commits into
mainfrom
uma-reduce-overhead-pr2154

Conversation

@mlazos

@mlazos mlazos commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

This adds an opt-in compile_mode="reduce-overhead" inference setting so
torch.compile can use its built-in CUDA graph support for UMA.

Internal NVIDIA v3 neighbor generation remains outside the captured model
regions. Before the model runs, its variable-length edge list is padded to a
configurable bucket. Padded edges are moved beyond the cutoff, so they
contribute zero energy, force, and stress while keeping the compiled tensor
shape stable across nearby molecular-dynamics frames.

This uses released nvalchemiops 0.4.0; it does not require a patched NVIDIA
library or a custom outer CUDA graph runner. It depends on #2154 for the UMA
fast-GPU kernel baseline.

Performance

Measured on one H100 with:

  • UMA-S-1p2, 1,000 Cu atoms
  • energy, forces, and stress
  • FP32 model state with TF32 enabled
  • activation_checkpointing=False
  • merge_mole=True
  • compile=True, dynamic=True
  • external_graph_gen=False
  • internal_graph_gen_version=3
  • execution_mode="umas_fast_gpu"
  • edge bucket size 1,024
  • TunableOp disabled
  • PR Optimize UMA SO2 and Wigner inference kernels #2154 as the baseline

Both predictors were compiled and warmed in the same process. Ten alternating
20-step blocks used identical position sequences.

Variant Median QPS Mean QPS Median latency
Dynamic compile baseline 25.5091 25.8275 39.202 ms
reduce-overhead 27.3169 27.2558 36.607 ms

This is a 7.09% median QPS improvement and a 5.53% mean QPS improvement. There
were no timed recompiles. Inductor reported 29 recorded CUDA graph regions.

The measured frame had 78,424 real edges and was padded to 78,848, adding 424
masked edges (0.54%). Separate-process steady reserved memory was 9.285 GiB for
the baseline and 7.533-7.660 GiB with reduce-overhead.

Numerical checks

At the same displaced 1,000-atom frame, maximum absolute differences versus
the dynamic compile baseline were:

  • energy: 0.0022302
  • forces: 0.0004342
  • stress: 0.000001186

NVIDIA v3 returns the same edge multiset in nondeterministic order. A
same-order padded-versus-unpadded check stayed within the variation seen when
repeating the unpadded path. Direct eager checks on single- and two-system
batches were also within 5.96e-8 for energy, 8.38e-9 for forces, and 1.64e-11
for stress.

The added CUDA regression uses the real UMA-S-1p2 checkpoint. It compares
eager and reduce-overhead energy, forces, and stress across repeated calls,
retains an earlier output to detect replay-buffer overwrites, and crosses to a
different edge bucket before returning to the original bucket.

Limitations

  • Internal reduce-overhead currently requires CUDA and NVIDIA graph version
    3.
  • Graph-parallel inference is rejected for this path.
  • Neighbor construction itself is not captured because the released NVIDIA
    implementation allocates temporary buffers.
  • Crossing an edge bucket records another compiler-managed CUDA graph. Returning
    to a previous bucket reuses its existing compiled graph.

Test plan

PYTHONPATH="$PWD/src" pytest -q -s tests/core/graph/test_padded_nvidia_graph.py tests/core/models/uma/test_padded_edges.py tests/core/units/mlip_unit/test_inference_settings.py
PYTHONPATH="$PWD/src" pytest -q -s tests/core/units/mlip_unit/test_predict.py::test_reduce_overhead_internal_graph_predict
pre-commit run --files docs/core/common_tasks/ase_calculator.md src/fairchem/core/graph/padded_nvidia_graph.py src/fairchem/core/models/uma/escn_md.py src/fairchem/core/units/mlip_unit/api/inference.py src/fairchem/core/units/mlip_unit/predict.py tests/core/graph/test_padded_nvidia_graph.py tests/core/models/uma/test_padded_edges.py tests/core/units/mlip_unit/test_inference_settings.py tests/core/units/mlip_unit/test_predict.py

mlazos added 8 commits August 12, 2026 13:29
The fast GPU edgewise path previously materialized an [E, 9, C] message between conv1, gate activation, and conv2. Preserve the existing m-major GEMM blocks across that boundary so the gate and conv2 consume them directly, while retaining the materialized path for S2 activation.

For UMA-S-1p2 at 1000 atoms on one H100 with internal graph generation, merge_mole=True, compile dynamic shapes, the fast GPU backend, a 1024-edge CUDA graph bucket, 12 warmups, and 200 timed predictions, trajectory throughput increased from 23.8327 QPS to 25.8706 QPS across two repeats (+8.55%).

Test Plan:
```
PYTHONPATH="$PWD/src" pytest tests/core/models/uma/nn/test_so2_layers.py -c packages/fairchem-core/pyproject.toml -q
pre-commit run --files src/fairchem/core/models/uma/escn_md_block.py src/fairchem/core/models/uma/nn/activation.py src/fairchem/core/models/uma/nn/so2_layers.py tests/core/models/uma/nn/test_so2_layers.py
```

Authored with an AI coding assistant.
The fast GPU path still launched separate sigmoid, SiLU, multiply, split, and concatenate work between the packed conv1 and conv2 GEMMs. Add an FP32 Triton primitive that activates the three m-major blocks directly and provides a fused first-order VJP. Non-FP32 inference retains the packed PyTorch path.

When autograd requests a higher-order graph, the VJP switches to an algebraically equivalent differentiable PyTorch implementation so loop-based UMA Hessian inference remains supported. Tests cover empty and strided inputs, dynamic full-graph compilation at multiple edge counts, and first- and second-order parity.

For the same UMA-S-1p2 1000-atom H100 endpoint, the primitive increased trajectory throughput from 25.8706 QPS to 27.0886 QPS across two repeats (+4.71%). The exact final stack, including the following Wigner launch commit, reproduced at 27.4831 QPS over 200 timed predictions.

Test Plan:
```
PYTHONPATH="$PWD/src" pytest tests/core/models/uma/uma_fast/test_fused_edgewise.py -c packages/fairchem-core/pyproject.toml -q -k packed_gate
PYTHONPATH="$PWD/src" python /data/users/mlazos/pytorch/agent_space/bench_uma_internal_padding.py --variant captured --bucket-size 1024 --repeat 9 --warmups 12 --batches 10 --steps-per-batch 20
pre-commit run --files src/fairchem/core/models/uma/escn_md_block.py src/fairchem/core/models/uma/nn/execution_backends.py src/fairchem/core/models/uma/triton/__init__.py src/fairchem/core/models/uma/triton/kernels.py src/fairchem/core/models/uma/triton/packed_gate.py tests/core/models/uma/uma_fast/test_fused_edgewise.py
```

Authored with an AI coding assistant.
The fused Wigner kernels used a fixed 2,048-program grid and serially processed the remaining edges in each program. Size the grid from the symbolic edge count, capped at 131,072 programs, so the 78,848-edge inference bucket can expose one program per edge while retaining a grid-stride loop for larger graphs. A one-program minimum and explicit Wigner width preserve empty-graph behavior.

Across the four forward/backward kernels at 78,848 edges, the selected one-warp launch reduced aggregate kernel time from 2.8196 ms to 2.7111 ms. On the UMA-S-1p2 1000-atom endpoint, trajectory throughput increased from 27.0886 QPS to 27.4774 QPS across two repeats (+1.44%).

All five selected correctness tests reached pass status. This local development PyTorch build then aborted during interpreter teardown, after pytest reported 100%, which is a pre-existing shutdown issue for the Wigner Triton test module.

Test Plan:
```
PYTHONPATH="$PWD/src" python /data/users/mlazos/pytorch/agent_space/bench_rebased_wigner_launch.py
PYTHONPATH="$PWD/src" pytest tests/core/models/uma/uma_fast/test_fused_edgewise.py -c packages/fairchem-core/pyproject.toml -q -k "matches_pytorch or empty_graph"
PYTHONPATH="$PWD/src" python /data/users/mlazos/pytorch/agent_space/bench_uma_internal_padding.py --variant captured --bucket-size 1024 --repeat 9 --warmups 12 --batches 10 --steps-per-batch 20
pre-commit run --files src/fairchem/core/models/uma/triton/constants.py src/fairchem/core/models/uma/triton/fused_wigner.py tests/core/models/uma/uma_fast/test_fused_edgewise.py
```

Authored with an AI coding assistant.
The fused Wigner conv1 backward previously materialized an [E, 9, 2C]
gradient buffer and launched two index_add reductions to return gradients to
the source and target nodes. Accumulate those contributions directly into the
[N, 9, C] node-gradient tensor in the producer kernel when deterministic
algorithms are disabled. When deterministic algorithms are enabled, specialize
the same kernel to retain the per-edge buffer and established index_add path.

On UMA-S-1p2 with 1000 atoms, internal NVIDIA graph generation v3,
external_graph_gen=False, merge_mole=True, dynamic torch.compile, fast math,
the umas_fast_gpu backend, a 1024-edge bucket, and full outer CUDA graph
capture, three trajectory repeats average 29.575 QPS versus 27.466 QPS for the
committed predecessor (+7.68%). Reserved memory falls from 8.135 GiB to 7.908
GiB. An isolated backward measurement improves from 2.361 ms to 0.935 ms
(2.53x). The direct reduction differs from the prior atomic reduction by
2.97e-7 relative L2 in grad_x; grad_wigner and grad_radial are bitwise equal.
The deterministic compiled fallback is bitwise repeatable.

Test Plan:

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py::test_wigner_conv1_fused_deterministic_backward -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py::test_wigner_conv1_fused_dynamic_compile -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py::test_wigner_conv1_fused_gradcheck -c packages/fairchem-core/pyproject.toml
```

Both gradcheck parameterizations pass before the local Triton runtime aborts
during interpreter teardown.

```bash
PYTHONPATH="$PWD/src:/data/users/mlazos/pytorch/agent_space:$PYTHONPATH" CUDA_VISIBLE_DEVICES=0 python /data/users/mlazos/pytorch/agent_space/bench_uma_internal_padding.py --variant captured --bucket-size 1024 --repeat 12
```

```bash
pre-commit run --files src/fairchem/core/models/uma/triton/fused_wigner.py src/fairchem/core/models/uma/triton/kernels.py tests/core/models/uma/uma_fast/test_fused_edgewise.py
```

This commit was authored with assistance from Codex.
The fast GPU consumer previously wrote an [E, 9, C] inverse-rotated edge
tensor, reread it in index_add to produce node embeddings, and gathered node
gradients back into another edge tensor during backward. Add a compile-safe
custom operation that accumulates the inverse rotation directly into target
nodes and gathers node gradients inside the existing backward kernel.

The default path uses relaxed FP32 atomics. When deterministic algorithms are
enabled, it retains the materialized inverse rotation and PyTorch index_add so
the deterministic contract is preserved. Graph-parallel target remapping stays
an explicit input to the fused operation.

On UMA-S-1p2 with 1000 atoms, internal NVIDIA graph generation v3,
external_graph_gen=False, merge_mole=True, dynamic torch.compile, fast math,
the umas_fast_gpu backend, a 1024-edge bucket, and full outer CUDA graph
capture, two trajectory repeats average 32.066 QPS versus 29.575 QPS for the
committed predecessor (+8.42%). Latency falls from 33.813 ms to 31.186 ms and
reserved memory from 7.908 GiB to 7.169 GiB. An isolated forward measurement
improves from 0.565 ms to 0.201 ms (2.81x). The fused output differs from the
materialized atomic reduction by 2.15e-7 relative L2, while input and Wigner
gradients are bitwise equal. The deterministic compiled forward and backward
are bitwise repeatable.

Test Plan:

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py::test_wigner_inv_conv2_scatter_matches_materialized tests/core/models/uma/uma_fast/test_fused_edgewise.py::test_fused_edgewise_empty_graph -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py::test_wigner_inv_conv2_scatter_dynamic_compile -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py::test_wigner_inv_conv2_scatter_deterministic -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_execution_backends.py::test_umas_fast_gpu_forces_match_baseline_pbc tests/core/models/uma/uma_fast/test_execution_backends.py::test_umas_fast_gpu_forces_match_baseline_no_pbc -c packages/fairchem-core/pyproject.toml
```

Both force tests pass before the local Triton runtime aborts during interpreter
teardown.

```bash
PYTHONPATH="$PWD/src:/data/users/mlazos/pytorch/agent_space:$PYTHONPATH" CUDA_VISIBLE_DEVICES=0 python /data/users/mlazos/pytorch/agent_space/bench_uma_internal_padding.py --variant captured --bucket-size 1024 --repeat 13
```

```bash
PYTHONPATH="$PWD/src:/data/users/mlazos/pytorch/agent_space:$PYTHONPATH" CUDA_VISIBLE_DEVICES=0 python /data/users/mlazos/pytorch/agent_space/bench_uma_internal_padding.py --variant captured --bucket-size 1024 --repeat 14
```

```bash
pre-commit run --files src/fairchem/core/models/uma/nn/execution_backends.py src/fairchem/core/models/uma/triton/__init__.py src/fairchem/core/models/uma/triton/fused_wigner.py src/fairchem/core/models/uma/triton/kernels.py tests/core/models/uma/uma_fast/test_fused_edgewise.py
```

This commit was authored with assistance from Codex.
UMA-S lmax=2 Wigner matrices are block diagonal, but the fast GPU path materializes and carries 81 values per edge and direction while only 35 can be nonzero. Generate the three blocks directly, keep the compact layout through edge-degree and the fused Triton producer and consumer kernels, and reconstruct dense matrices only at legacy backend entry points.

This reduces Wigner traffic and gradient storage without changing precision. Dynamic compiled generator and fused operation tests cover changing edge counts, deterministic fallbacks retain their behavior, and PBC energy, forces, and stress remain within the existing fast-backend tolerance.

On UMA-S-1p2 with 1000 atoms, NVIDIA internal graph generation v3, external_graph_gen=False, merge_mole=True, dynamic torch.compile, fast math, the umas_fast_gpu backend, a 1024-edge bucket, and full outer CUDA graph capture, two trajectory repeats average 33.815 QPS versus 32.066 QPS for the committed predecessor (+5.45%). Reserved memory falls from 7.169 GiB to 7.096 GiB.

Test Plan:

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/test_quaternion_wigner.py -k "compact_l2_matches_dense" -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src" CUDA_VISIBLE_DEVICES=0 pytest -q tests/core/models/uma/uma_fast/test_execution_backends.py::test_compact_edge_degree_matches_dense tests/core/models/uma/uma_fast/test_execution_backends.py::test_legacy_backend_rotations_accept_compact_wigner tests/core/models/uma/uma_fast/test_execution_backends.py::test_umas_fast_gpu_forces_match_baseline_pbc tests/core/models/uma/uma_fast/test_execution_backends.py::test_umas_fast_gpu_forces_match_baseline_no_pbc -c packages/fairchem-core/pyproject.toml
```

```bash
PYTHONPATH="$PWD/src:/data/users/mlazos/pytorch/agent_space:$PYTHONPATH" CUDA_VISIBLE_DEVICES=0 python /data/users/mlazos/pytorch/agent_space/bench_uma_internal_padding.py --variant captured --bucket-size 1024 --repeat 16
PYTHONPATH="$PWD/src:/data/users/mlazos/pytorch/agent_space:$PYTHONPATH" CUDA_VISIBLE_DEVICES=0 python /data/users/mlazos/pytorch/agent_space/bench_uma_internal_padding.py --variant captured --bucket-size 1024 --repeat 17
```

```bash
pre-commit run --files src/fairchem/core/models/uma/common/quaternion/wigner_d_hybrid.py src/fairchem/core/models/uma/escn_md.py src/fairchem/core/models/uma/nn/execution_backends.py src/fairchem/core/models/uma/triton/fused_wigner.py src/fairchem/core/models/uma/triton/kernels.py tests/core/models/uma/test_quaternion_wigner.py tests/core/models/uma/uma_fast/test_execution_backends.py tests/core/models/uma/uma_fast/test_fused_edgewise.py tests/core/models/uma/uma_fast/triton_test_utils.py
```

The Triton-heavy pytest processes report all selected tests passed, then hit the existing local interpreter teardown abort. The compact generator and legacy fallback tests exit cleanly.

This commit was authored with assistance from Codex.
The fast GPU backend can be selected for channel widths and autocast dtypes that the packed FP32 Triton gate does not support. Keep the packed GEMM outputs, but route their activation through the existing PyTorch block implementation unless the actual outputs are FP32 with a power-of-two channel width. This avoids rejecting valid fast-backend configurations without repeating the GEMMs.

Compact Wigner storage also made the exported fused operations reject their prior dense [E, 9, 9] inputs. Pack dense block-diagonal inputs before entering custom autograd so the public interface, dense gradient shape, and zero off-block gradients are preserved.

Test Plan:
```bash
PYTHONPATH="$PWD/src" pytest -q tests/core/models/uma/uma_fast/test_execution_backends.py -k "gate_activation_fallback or compact_edge_degree_matches_dense"
PYTHONPATH="$PWD/src" pytest -q tests/core/models/uma/uma_fast/test_fused_edgewise.py -k "exported_fused_ops_accept_dense_wigner or wigner_conv1_fused_dynamic_compile"
pre-commit run --files src/fairchem/core/models/uma/escn_md_block.py src/fairchem/core/models/uma/nn/execution_backends.py src/fairchem/core/models/uma/triton/fused_wigner.py tests/core/models/uma/uma_fast/test_execution_backends.py tests/core/models/uma/uma_fast/test_fused_edgewise.py
```

Authored with assistance from an AI coding tool.
Torch compile can capture UMA compute regions, but internal neighbor generation changes the exact edge shape from one frame to the next. Those shape changes prevent reliable CUDA graph reuse even when the atom count is fixed.

Expose reduce-overhead as an inference compile mode and mark prediction boundaries for CUDA graph trees. For internal NVIDIA v3 graphs, generate the neighbor list before model execution and pad its edges to a configurable bucket. Padded edges are moved beyond the cutoff so they contribute zero energy, force, and stress while keeping the compiled input shape stable. Neighbor construction remains outside capture because the released NVIDIA implementation allocates temporary buffers.

The internal path is limited to CUDA, graph version 3, and non-distributed inference. External graphs can use reduce-overhead without padding.

Test Plan:

```
PYTHONPATH="$PWD/src" pytest -q -s tests/core/graph/test_padded_nvidia_graph.py tests/core/models/uma/test_padded_edges.py tests/core/units/mlip_unit/test_inference_settings.py
PYTHONPATH="$PWD/src" pytest -q -s tests/core/units/mlip_unit/test_predict.py::test_reduce_overhead_internal_graph_predict
pre-commit run --files docs/core/common_tasks/ase_calculator.md src/fairchem/core/graph/padded_nvidia_graph.py src/fairchem/core/models/uma/escn_md.py src/fairchem/core/units/mlip_unit/api/inference.py src/fairchem/core/units/mlip_unit/predict.py tests/core/graph/test_padded_nvidia_graph.py tests/core/models/uma/test_padded_edges.py tests/core/units/mlip_unit/test_inference_settings.py tests/core/units/mlip_unit/test_predict.py
```
@meta-cla meta-cla Bot added the cla signed label Aug 13, 2026
@mlazos mlazos added enhancement New feature or request minor Minor version release labels Aug 13, 2026
Base automatically changed from uma-so2-wigner-fusions to main August 25, 2026 23:36
@github-actions

Copy link
Copy Markdown

This PR has been marked as stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale label Sep 25, 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

cla signed enhancement New feature or request minor Minor version release stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant