Repository navigation
Optimize UMA SO2 and Wigner inference kernels - #2154
Merged
Merged
Conversation
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.
bkmi
reviewed
Aug 20, 2026
bkmi
reviewed
Aug 25, 2026
| return torch.cat(out, dim=1) | ||
|
|
||
| def gemms_to_buffers( | ||
| def gemms_from_blocks( |
Contributor
There was a problem hiding this comment.
is there any backwards compatibility we should think about here?
Contributor
Author
There was a problem hiding this comment.
is the only usage, so I think we are good!
bkmi
approved these changes
Aug 25, 2026
bkmi
left a comment
Contributor
There was a problem hiding this comment.
seems good to me... maybe it's worth having someone else look too.
abagusetty
added a commit
to abagusetty/fairchem
that referenced
this pull request
Aug 28, 2026
…kernels Resolves the merge in favour of the accelerator-agnostic device layer, then carries that treatment to the Triton work upstream added in facebookresearch#2154: packed_gate required CUDA tensors outright, and the new tests hard-coded CUDA devices. Both now follow whatever accelerator is present. Upstream's execution_backends.py is kept verbatim apart from dropping the XPU Triton opt-in; an earlier resolution had discarded its _dense_l2_wigner and pack_blocks helpers, which broke the compact Wigner layout its kernels expect. Verified on Intel Data Center GPU Max: the gpu-marked suite is 93 passed, 0 failed, including the packed-gate and compact-Wigner tests that compare against the materialized reference implementations and the torch.compile dynamic-shape paths.
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 this changes
UMA spends much of its inference time moving edge data between GPU operations. This PR removes several large temporary tensors and combines adjacent operations so the GPU reads and writes less memory.
In plain terms, the six optimization commits do the following:
On a 1000-atom UMA-S-1p2 inference workload, the complete stack improves GPU throughput from 19.722 to 25.881 QPS (+31.22%) and reduces steady peak allocated memory by 11.18%. CUDA graphs were not enabled for this comparison.
How this relates to #2073
This work builds on #2073, which is already included in the base revision.
#2073 added GPU kernels that combine the initial node-to-edge rotation and the final edge rotation. However, the model still created large temporary edge tensors around those kernels. This PR removes more of those temporary tensors and reduces the amount of rotation data stored for every edge.
A simplified view is:
The six optimizations
1. Avoid an unnecessary format conversion
The first convolution already produces three feature blocks in the format expected by the second convolution. Previously, UMA joined these blocks into one
[edges, 9, channels]tensor for the gate activation and then split the tensor back into blocks.The model now keeps the blocks in their original format across the gate. This removes a full-tensor join, split, and memory round trip.
For the representative 78,848-edge graph with 128 channels, one FP32
[edges, 9, channels]tensor occupies about 346.5 MiB.Forward and backward results were compared with the old path using:
2. Run the gate as one GPU operation
The gate previously launched separate operations for sigmoid, SiLU, multiplication, splitting, and joining. The new Triton kernel performs those steps together and writes the block-form output directly.
The fused operation supports normal force inference and its first derivative. When a caller requests second derivatives through this gate, it uses a differentiable PyTorch implementation rather than the specialized first-derivative kernel. This fallback applies to the gate itself; the other existing
umas_fast_gpuTriton operations retain the backend's existing higher-order derivative restrictions.Measured agreement with the original gate:
Compiled and uncompiled executions of the new kernel matched exactly in the dynamic-shape tests.
3. Process more edges in parallel
The existing Wigner kernels always launched 2,048 GPU programs. A graph with 78,848 edges therefore made each program process many edges one after another.
The launch size now follows the number of edges, up to a limit of 131,072 programs. For the representative graph, this allows roughly one program per edge.
This does not change the formula or combine results from different edges; it only exposes more independent work to the GPU. The standard forward, backward, empty-graph, and dynamic-shape correctness tests pass. Across the four affected kernels at 78,848 edges, combined kernel time decreased from 2.8196 ms to 2.7111 ms.
4. Write input gradients directly to atoms
Backward previously created an
[edges, 9, 2 * channels]tensor and then ran two additional reductions to add its values to source and target atoms.The new kernel writes those contributions directly into the atom-gradient tensor. At 78,848 edges and 128 channels, this avoids an approximately 693.1 MiB temporary tensor. The isolated backward operation improved from 2.361 ms to 0.935 ms.
Because multiple edges may update the same atom concurrently, the order of FP32 additions can change. The measured numerical effect was:
Here, relative L2 means the total error magnitude divided by the reference result magnitude.
When deterministic algorithms are enabled, UMA keeps the previous reduction path. Repeated forward and backward executions of that path are bitwise identical.
5. Rotate and add edge messages in one operation
Previously, UMA wrote an inverse-rotated
[edges, 9, channels]tensor and then read it again to add edge messages to their target atoms. Backward similarly gathered atom gradients into another edge tensor.The new kernel performs the rotation and atom update together. The isolated forward operation improved from 0.565 ms to 0.201 ms.
This operation also changes the order of some FP32 additions:
When deterministic algorithms are enabled, UMA uses the previous materialized reduction. Its forward and backward results are bitwise repeatable.
6. Store only the nonzero rotation values
UMA-S uses a 9 by 9 block-diagonal rotation matrix. Only three diagonal blocks can contain nonzero values:
The old representation stored all 81 values, including 46 values that are always zero. The new representation stores only the 35 useful values. For 78,848 edges, this saves approximately 13.8 MiB per rotation tensor, or 27.7 MiB for the forward and inverse tensors before counting gradients.
The compact rotation generator is bitwise equal to the corresponding blocks of the dense generator, including its position gradients. The edge-degree output and input gradient are also bitwise equal. Double-precision radial and rotation gradient comparisons were within
2e-15and1e-14relative tolerance respectively.Legacy entry points still accept compact rotations by reconstructing the dense matrix when needed.
Independent review follow-up
An independent review found two compatibility gaps, fixed in follow-up commit
802979b9:[edges, 9, 9]rotation input. They now pack dense block-diagonal inputs before custom autograd, preserving the public input shape, dense gradient shape, and zero off-block gradients.The added tests cover 96-channel FP32 fallback, 128-channel BF16 fallback, dynamic full-graph compilation, dense forward and backward compatibility, and changing edge counts. An independent re-review found no remaining blocking issues.
The fixed branch measured 25.946 QPS with a 38.541 ms GPU median and 5.956 GiB steady peak allocation under the same configuration below. This is effectively unchanged from the published 25.881 QPS result and produced no timed recompilations.
When these optimizations run
These changes are used by the
umas_fast_gpubackend. That backend is selected explicitly with:It can also be selected automatically when
execution_mode=Noneand all of the following are true:lmax=2andmmax=2merge_mole=TrueThe optimized kernels are not selected by
execution_mode="general".Additional fallbacks remain available:
CUDA graphs are not required to use these kernels.
End-to-end performance
Standalone H100 measurements compare clean detached checkouts of base
a3fa357c3and optimization head522ce9df. Follow-up head802979b9contains the compatibility fixes described above and has unchanged performance.Configuration:
Each process performed 24 warmup predictions followed by eight blocks of 100 timed predictions. Base and head processes were interleaved in base/head/head/base order, and each process used a fresh TorchInductor cache. No recompilation occurred during timed blocks. QPS is
1000 / median_latency_ms.End-to-end numerical comparison
Five frames used deterministic position displacements of -0.02, -0.003, 0, 0.003, and 0.02. The table compares the complete optimization stack with the base. The final column shows the normal difference observed when the same revision is run twice.
The complete-stack differences are comparable to the run-to-run variation already observed from the same revision. The two operations that intentionally change FP32 addition order were also tested directly, producing relative L2 differences of 2.97e-7 and 2.15e-7.
Suggested review order
The first six commits follow the model's execution order:
The seventh commit contains the independent-review compatibility fixes and their regression tests.
Test plan
All selected correctness tests and all 21 fused-edgewise tests reached pass status. The final review-focused run passed four gate/backend cases and three dense/compact dynamic Wigner cases. In this local custom PyTorch/Triton environment, one Triton-heavy test process subsequently hit a known interpreter teardown abort after pytest reported 100% passed.
Authored with assistance from an AI coding tool.