Skip to content

Keep the output Reshape when fusing MultiHeadAttention into a non-3D output - #3157

Open
Sankur Kundu (Shakhtar-Sankur) wants to merge 1 commit into
microsoft:mainfrom
Shakhtar-Sankur:mha-keep-output-reshape
Open

Sankur Kundu (Shakhtar-Sankur) wants to merge 1 commit into
microsoft:mainfrom
Shakhtar-Sankur:mha-keep-output-reshape

Conversation

@Shakhtar-Sankur

@Shakhtar-Sankur Sankur Kundu (Shakhtar-Sankur) commented Nov 4, 2026 •

Copy link
Copy Markdown

optimize_for_ort turns GPT-2 into a model ONNX Runtime can't load:

Node (node_Gemm_57) Op (Gemm) [ShapeInferenceError] Input 0 expected to have rank 2 but has rank 3

The MultiHeadAttention rule (ort_fusions/mha.py) matches ... -> SDPA -> Transpose -> Reshape and assumes the final Reshape restores (B, S, D), the shape MultiHeadAttention returns (the code has a # TODO: verify Reshapes). GPT-2's export reshapes the attention output straight to (B*S, D) for the Conv1D Gemm, so the fusion replaces a 2D value with a 3D one. Applying optimize_for_ort's steps one at a time on hf-internal-testing/tiny-random-GPT2LMHeadModel confirms that every step before fuse_mha2 leaves the model valid and matching, and fuse_mha2 breaks it.

SmolLM (test_smollm) hits the same pattern after two Reshapes are folded into one (B*S, D) Reshape, but its value feeds a MatMul, which happens to accept the 3D tensor, so it works by chance.

This PR keeps the fusion and, when the matched output isn't 3D, reshapes the MultiHeadAttention output back to the original target shape, so consumers see exactly the shape they saw before. It only does this when the target shape is a constant without zeros: a 0 copies a dimension of the Reshape's input, and that input changes from 4D to 3D. Otherwise the rule doesn't match. The past/present variant reshapes only the attention output.

Results (CPU, onnxruntime 1.31.1, main at 9112df0)

Model, optimize_for_ort main this PR
tiny-random GPT-2 fails to load in ORT loads, max |diff| vs unoptimized 1.2e-7, MHA fused ×5
tiny-random Llama, BERT, Mistral, Phi load and match identical outputs and identical fusion counts
  • pytest onnxscript/rewriter/ort_fusions: 143 passed on main, 144 passed with this PR (0 failed).
  • New test_output_reshaped_to_2d_keeps_reshape in mha_unit_test.py: fails on main, passes with this PR.
  • ruff check and ruff format pass on the changed files.
  • Run log: https://www.kaggle.com/code/sankurkundu/notebook006b434ee6

…output

The MHA pattern ends with a Reshape that is assumed to restore (B, S, D),
the shape MultiHeadAttention returns. GPT-2 exports reshape the attention
output straight to (B*S, D) for the Conv1D Gemm instead, so the fusion
replaced a 2D value with a 3D one and optimize_for_ort produced a model
ONNX Runtime rejects (Gemm: input 0 expected to have rank 2 but has rank
3). SmolLM hits the same pattern after Reshape folding, but its 2D value
feeds a MatMul, which happens to accept the 3D tensor.

When the matched output isn't 3D, reshape the fused output back to the
original target shape, so consumers see the same shape as before. Only
do so for constant target shapes without zeros, since a 0 would copy a
dimension of the Reshape's input, which changes from 4D to 3D.
@Shakhtar-Sankur Sankur Kundu (Shakhtar-Sankur) changed the title optimize_for_ort turns GPT-2 into a model ONNX Runtime can't load: Keep the output Reshape when fusing MultiHeadAttention into a non-3D output Nov 4, 2026
@Shakhtar-Sankur

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

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

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant