Repository navigation
dynamo: decompose fused transformer ops so they lower to MIGraphX (#255) - #297
Merged
shivadbhavsar merged 1 commit intoOct 28, 2026
Merged
Conversation
In inference, nn.TransformerEncoderLayer takes a fast path that dynamo records as one aten._transformer_encoder_layer_fwd node, and code that calls the fast path of nn.MultiheadAttention records aten._native_multi_head_attention. Neither has a converter or a torch decomposition, so every encoder layer stayed in torch and split the model into separate MIGraphX partitions. Add decompositions for both ops (packed qkv projection, heads, SDPA or an explicit softmax when attention weights are requested, output projection; layer norms, residuals and FFN for the encoder layer) and pass them to the AOT retrace in both backends. The graph that reaches the converters then holds linear, layer_norm and scaled_dot_product_attention, which already lower. On torch 2.13, vit_b_16 (the example in ROCm#255) no longer emits the fused MHA op; the encoder layer is where it still appears. tests/dynamo/test_decompositions_dynamo.py checks each decomposition against the real aten op on CPU (mask types 0/1/2, bool and float masks, need_weights, average, norm_first, relu/gelu): 42 passed. tests/dynamo/test_fused_attention_dynamo.py compiles the encoder layer and the direct MHA op in fp16 with use_aot on and off, compares with eager and asserts nothing is left to torch: 20 passed on gfx1100, 4 strict xfails, all fail on master. The xfails are the encoder layer with a padding mask on the non-AOT path: F._canonical_mask's in-place masked_fill_ is rejected by aot_export_joint_simple, also on master. tests/dynamo: 436 passed, 10 skipped, 4 xfailed; the 2 failures are test_quant_LLM (transformers DynamicCache), failing on master too.
shivadbhavsar
approved these changes
Oct 28, 2026
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.
Motivation
Closes #255.
In inference,
nn.TransformerEncoderLayertakes a fast path that dynamo records as oneaten._transformer_encoder_layer_fwdnode, and code callingnn.MultiheadAttention's fast path recordsaten._native_multi_head_attention(#255). Neither has a converter or a torch decomposition, so every encoder layer stays in torch and the model is split into separate MIGraphX partitions.On torch 2.13 the #255 example (
vit_b_16) no longer emits the fused MHA op (it reaches the backend as SDPA with the fast path on or off); the encoder layer is where it still appears. The suggestion on #255,torch.backends.mha.set_fastpath_enabled(False), acts at dynamo trace time, so inside the backend it is too late.Technical Details
dynamo/decompositions.py: decompositions for both ops (packed qkv projection, heads, SDPA, or an explicit softmax when attention weights are requested, output projection; layer norms, residuals and FFN for the encoder layer).backends.py: passed to the AOT retrace in both backends (aot_export_joint_simpleandaot_autogradtakedecompositionssince torch 2.1, the oldest torch the dynamo backend loads on).linear,layer_normandscaled_dot_product_attention, which already lower.Test Plan
tests/dynamo/test_decompositions_dynamo.py(CPU): each decomposition against the real aten op over mask types 0/1/2, bool and float masks, need_weights, average, norm_first, relu/gelu.tests/dynamo/test_fused_attention_dynamo.py(GPU): encoder layer and the direct MHA op in fp16,use_aoton and off, compared with eager, asserting nothing is left to torch. Run on master as the control.torch.compileofvit_b_16and a 6-layernn.TransformerEncoder, fp16, both backends, vs eager.tests/dynamo; latency of 6 encoder layers vs eager.Setup: RX 7900 XTX (gfx1100), ROCm 7.2, torch 2.13, MIGraphX develop.
Test Result
use_aotfails to compile (AttributeError: 'GraphModule' object has no attribute '_frozen_param0').F._canonical_mask's in-placemasked_fill_is rejected byaot_export_joint_simple, also on master (separate issue).tests/(fx + dynamo): 906 passed, 3 failed, 14 skipped, 6 xfailed (run withpytest-xdist -n 8). 2 failures aretest_quant_LLM(transformersDynamicCache), same on master, fixed in Fix 0-d new_zeros/new_ones in remove_const_ops; run test_quant_LLM without the KV cache #296. 1 is an encoder case that read~/.cache/migraphx/problem.jsonwhile another worker was writing it; run alone it passes.Performance
Median ms per forward, 6x
TransformerEncoderLayer(E512, 8 heads, FFN 2048), fp16, RX 7900 XTX, default MIGraphX settings. On master the encoder layers stay in torch, so master runs at about eager speed.From 2k tokens this PR is slower than eager on gfx1100. That is MIGraphX's attention on gfx11, not the decomposition: MIGraphX fuses the decomposed pattern, but its MLIR attention is on by default only on gfx94/gfx95, so gfx11 materialises the full score matrix (tracked in ROCm/AMDMIGraphX#5396).
Submission Checklist