Skip to content

Add fuse_attention pass for torch.onnx SDPA graphs - #321

Open
take-cheeze wants to merge 4 commits into
onnx:mainfrom
onnxsim:claude/fuse-attention-torch-onnx-viw15m
Open

take-cheeze wants to merge 4 commits into
onnx:mainfrom
onnxsim:claude/fuse-attention-torch-onnx-viw15m

Conversation

@take-cheeze

Copy link
Copy Markdown
Member

Fuse the sub-graph that torch.onnx.export (the TorchScript exporter) emits for scaled_dot_product_attention into a single ONNX Attention operator (opset 23+).

The exported pattern scales Q and the transposed K by sqrt(scale), multiplies them, optionally adds an attention mask, applies Softmax over the last axis and multiplies by V. Anchored on the final MatMul(Softmax(...), V), the pass recognizes this shape and rewrites it to Attention(Q, K, V[, attn_mask]):

  • explicit scale= (constant sqrt(scale) factors) -> scale attribute set to the product of the two factors
  • default scale (the 1/sqrt(head_size) sub-graph torch derives from Shape(Q)) -> scale left implicit, matching Attention's default
  • additive / causal masks -> fed through as attn_mask

Guarded on opset >= 23 (where Attention exists) so lower-opset models are left untouched. Tests cover hand-built graphs mirroring the exporter output plus graphs produced by the real torch.onnx exporter.

Claude-Session: https://claude.ai/code/session_01CHJoCKJ1p5YFm2ZMwdwaWV

@take-cheeze
take-cheeze requested review from a team as code owners July 26, 2026 03:14
@andife
andife requested a balanced review from Copilot October 18, 2026 02:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Incomplete shape and scale validation can produce invalid models or silently change outputs.

3 open issues
What changed in this PR

Adds an optimizer pass that replaces TorchScript-exported scaled dot-product attention subgraphs with ONNX Attention.

Changes:

  • Recognizes explicit/default scales and optional masks, gated on opset 23+.
  • Registers fuse_attention and adds synthetic-graph and optional PyTorch export tests.
File Description
onnxoptimizer/​test/​optimizer_test.py Tests fusion patterns and unsupported cases.
onnxoptimizer/​passes/​fuse_attention.h Implements attention pattern matching and replacement.
onnxoptimizer/​pass_registry.h Registers the new pass.

Review effort: Balanced

Comment thread onnxoptimizer/passes/fuse_attention.h Outdated
Comment thread onnxoptimizer/passes/fuse_attention.h
Comment thread onnxoptimizer/passes/fuse_attention.h
@take-cheeze
take-cheeze force-pushed the claude/fuse-attention-torch-onnx-viw15m branch 2 times, most recently from defb8fc to 355b76b Compare October 18, 2026 20:43
take-cheeze and others added 4 commits October 18, 2026 21:57
Fuse the sub-graph that `torch.onnx.export` (the TorchScript exporter)
emits for `scaled_dot_product_attention` into a single ONNX `Attention`
operator (opset 23+).

The exported pattern scales Q and the transposed K by `sqrt(scale)`,
multiplies them, optionally adds an attention mask, applies Softmax over
the last axis and multiplies by V. Anchored on the final
`MatMul(Softmax(...), V)`, the pass recognizes this shape and rewrites it
to `Attention(Q, K, V[, attn_mask])`:

- explicit `scale=` (constant sqrt(scale) factors) -> `scale` attribute
  set to the product of the two factors
- default scale (the `1/sqrt(head_size)` sub-graph torch derives from
  `Shape(Q)`) -> `scale` left implicit, matching Attention's default
- additive / causal masks -> fed through as `attn_mask`

Guarded on opset >= 23 (where `Attention` exists) so lower-opset models
are left untouched. Tests cover hand-built graphs mirroring the exporter
output plus graphs produced by the real torch.onnx exporter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHJoCKJ1p5YFm2ZMwdwaWV
Signed-off-by: take-cheeze <takechi101010@gmail.com>
Only fuse graphs that the Attention op can represent:
- Q, K and V must be rank 4 with static, matching batch and head dims.
- The default scale must come from a Shape covering all of Q and a Slice
  that selects only the last dimension (start -1, end >= 4, axis 0/-1,
  step 1). Previously a Shape end or an unchecked Slice end/step could
  pick a different dimension and silently change the scale.
- An explicit scale must be a scalar, so a higher-rank constant cannot
  promote Q or K^T and change the output rank.

Add negative tests for the shape-selection, scale-rank and batch
mismatch cases.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPahbkPmWYWChRCBQX7osk
Signed-off-by: take-cheeze <takechi101010@gmail.com>
Fixes the ruff RUF015 lint failure reported by CI.

Signed-off-by: take-cheeze <takechi101010@gmail.com>
The explicit-scale Mul constants come from Constant nodes, which carry no
sizes on their output value, so the scalar check rejected every explicit
scale. Read the rank from the constant tensor instead.

The test also trusted torch's ONNX_MAX_OPSET, but on CI the exporter falls
back to opset 20 with a warning. Skip when the exported model is not opset
23, and drop the unused import.

Signed-off-by: take-cheeze <takechi101010@gmail.com>
@take-cheeze
take-cheeze force-pushed the claude/fuse-attention-torch-onnx-viw15m branch from 355b76b to b3d82a3 Compare October 18, 2026 21:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unchecked broadcasting in masks and default-scale numerators can change output shapes during fusion.

2 open issues
3 resolved since last review

Review effort: Balanced

Comment on lines +95 to +96
float numerator;
if (!FetchSoleValueOfTensor(n->input(0), numerator) || numerator != 1.0f) {
Comment on lines +302 to +303
if (attn_mask != nullptr) {
attention->addInput(attn_mask);

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

None yet

Development

Successfully merging this pull request may close these issues.

3 participants