Skip to content

if transpose can't be cancelled , do not push it - #32714

Closed
xiaohanAMD wants to merge 10 commits into
microsoft:rel-1.30.0from
xiaohanAMD:transposeOptimize-fix
Closed

xiaohanAMD wants to merge 10 commits into
microsoft:rel-1.30.0from
xiaohanAMD:transposeOptimize-fix

Conversation

@xiaohanAMD

Copy link
Copy Markdown

Summary
Stop the transpose optimizer from pushing a Transpose through an op when that move does not actually cancel a later Transpose.

Stock ORT treats “any downstream Transpose” as a reason to push. Two Transposes cancel only when their perms are inverses (both disappear). They merge when the perms compose into a remaining Transpose. For a shared Transpose, a merge does not remove the original node, so the graph is not simpler and QDQ layout can be broken.

Behavior
Cost model: waive the cost of a Transpose inserted on the output only if a downstream Transpose would cancel the pushed perm, or the incoming Transpose has a single consumer (merge still drops a node). A shared Transpose that would only merge is not pushed.

QDQ guard: if a Transpose already sits in a complete DQ → Transpose → Q unit, do not push it through that Q unless the relocated Transpose would cancel downstream. Dead-end branches (no Transpose) are allowed when cancellation happens on another branch, because the relocated Transpose can stay on those branches.

Test plan
TestSliceSharedTransposeCancel / TestSliceSharedTransposeNoCancel / TestSliceUnsharedTransposeNoCancel
TestQDQSharedTransposeSliceCancel / TestQDQSharedTransposeSliceNoCancel
TestQDQTransposeNotPushedThroughOwnQ
image

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

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.

Copilot review overview

🟡 Changes recommended

Several traversal and cost paths can still misclassify cancellation or bypass the QDQ safeguard.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 4 Medium severity

Open (5)
What changed in this PR

Refines transpose optimization to push shared transposes only when downstream cancellation provides a real benefit while preserving QDQ units.

Changes:

  • Adds cancellation-aware transpose cost analysis.
  • Guards complete QDQ units from unhelpful rewrites.
  • Adds shared, unshared, and QDQ regression tests.
File Description
onnxruntime/​test/​optimizer/​transpose_optimizer_test.cc Adds transpose cancellation and QDQ test scenarios.
onnxruntime/​core/​optimizer/​transpose_optimization/​onnx_transpose_optimization.cc Implements cancellation-aware cost and QDQ safeguards.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
xiaoh and others added 2 commits September 22, 2026 00:07
Apply the QDQ guard to every push decision, preview handler perms, reject incomplete consumers, cache walks, and waive output cost only when every pushed output is beneficial.

Co-authored-by: Cursor <cursoragent@cursor.com>
Only waive the inserted output Transpose cost when an output actually leads to a downstream Transpose. The sole-consumer merge exception was being applied even with no downstream Transpose, which pushed Transposes the optimizer should leave alone (TestOnlyOptimizeTowardsTranspose, MobileClip attention fusion).

Restrict the QDQ node unit guard to the default cost path again. Layout transformation returns kPushTranspose to move layout Transposes through Q/DQ on purpose and relies on FixQDQNodeUnits afterwards, so blocking that stopped layout transformation entirely (ConstantFoldTransposeAndSqueezeOutputCorrectness).

Co-authored-by: Cursor <cursoragent@cursor.com>

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.

Copilot review overview

🟡 Changes recommended

Cancellation prediction can cross infeasible paths, use incorrect output permutations, and strand relocated Transposes.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 3 Medium severity

Open (4)
Resolved since last review (5)

Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
…ow unpushable paths.

The walk is iterative, previews only handlers that preserve perm (including HandleConcat), and requires a transposable, modifiable consumer plus a rank-adjusted output perm.

Co-authored-by: Cursor <cursoragent@cursor.com>

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.

Copilot review overview

🔵 Needs a closer look

Cancellation analysis can approve pushes that never cancel or simplify the graph.

Review effort: Balanced
Findings: None

Resolved since last review (4)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Avoid cancellation predictions when downstream push is infeasible

onnxruntime/​core/​optimizer/​transpose_optimization/​onnx_transpose_optimization.cc:3292

This walk can report a cancellation through a node that the optimizer will never push through. For example, after pushing a shared Transpose through Slice, let that output feed Add(output, dynamic_same_rank) and then an inverse Transpose. PreviewPushedOutputPerm follows the Add and returns kCancel, but when Add is visited its input cost is -rank + rank == 0, so DefaultCostCheck stops and the newly inserted Transpose remains stranded (and the first rewrite can increase the Transpose count). The same false positive can also let the QDQ guard dismantle a node unit. The walk needs to include downstream push feasibility/cost, or conservatively stop at handlers whose other inputs prevent the push.

Medium severity Do not waive merge cost for partially shared output Transposes

onnxruntime/​core/​optimizer/​transpose_optimization/​onnx_transpose_optimization.cc:3435

kept_output_transposes <= 1 counts node outputs, not whether the one surviving output Transpose is shared. With an unshared incoming Transpose, a single-output op whose output fans out to a non-cancelling Transpose and a dead-end branch passes this condition. After the push, the inserted output Transpose must remain for the dead-end branch, while merging on the other branch leaves another Transpose, so the graph still has two Transposes and gains nothing. Only waive the merge cost when every consumer branch reaches a merge/cancellation (or otherwise prove the inserted output Transpose itself is removable).

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.

Copilot review overview

🟡 Changes recommended

Cancellation analysis can traverse an intermediate operator that the cost model later refuses, stranding an additional Transpose.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
A shared Transpose through Slice into Add (or Concat) with a same-rank dynamic input was treated as free, then stranded; merge cost is also not waived when the kept output fans out.

Co-authored-by: Cursor <cursoragent@cursor.com>

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.

Copilot review overview

🟡 Changes recommended

Cancellation and merge feasibility can be misclassified, causing stranded transposes or missed valid simplifications.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
Resolved since last review (1)

Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
…e cost only when kept outputs are fully covered or a unique incoming Transpose does not fan out downstream.

Co-authored-by: Cursor <cursoragent@cursor.com>
@xiaohanAMD
xiaohanAMD requested a balanced review from Copilot September 23, 2026 14:29
Copilot stopped reviewing on behalf of xiaohanAMD due to an error September 23, 2026 14:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
…st_cast at its call sites.

Guard GetValueInfo before reading a shape so a value without value info makes the preview conservative instead of crashing. Replace the string cache key with a structured key and hasher, and let PreviewPushedOutputPerm take a const NodeRef.

Co-authored-by: Cursor <cursoragent@cursor.com>

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.

Copilot review overview

🔵 Needs a closer look

Fanout traversal can approve unreachable cancellations and unproductive rewrites, while cache invalidation introduces quadratic behavior on deep graphs.

Review effort: Balanced
Findings: None

Resolved since last review (9)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Misses merge fanout across separate Split outputs

onnxruntime/​core/​optimizer/​transpose_optimization/​onnx_transpose_optimization.cc:3498

This only detects a merge/dead-end fanout when the Transpose and the other consumer share the same value. For Transpose -> Relu -> Split, with a same-perm Transpose on one Split output and a graph-output branch on the other, the two Split outputs are visited separately, so one sees only has_transpose and the other only has_other. The function returns false, CalculateCost waives Relu's output cost, and the Transpose moves even though Split subsequently refuses the merge and no node is removed. Aggregate transpose and stranded-terminal state across all descendant outputs, not just consumers of one value.

Medium severity Memo clearing causes quadratic traversal on unary chains

onnxruntime/​core/​optimizer/​transpose_optimization/​onnx_transpose_optimization.cc:3657

Clearing the entire memo after every successful push makes a long unary chain quadratic. With Transpose -> op1 -> ... -> opN -> inverse Transpose, each op performs a walk over the remaining suffix, then clears all results when the Transpose advances one node, for Θ(N²) consumer visits instead of the previous linear reverse reachability pass. Preserve unaffected downstream results or compute cancellation reachability incrementally so model initialization does not regress on deep graphs.

…os after a push.

Per-value fanout missed Transpose -> Relu -> Split with a merge on one output and a dead-end on the other, and wiping the whole memo after each rewrite made unary chains quadratic.

Co-authored-by: Cursor <cursoragent@cursor.com>
@xiaohanAMD

xiaohanAMD commented Sep 24, 2026 •

Copy link
Copy Markdown
Author
@microsoft-github-policy-service agree [company="{your company}"]

@microsoft-github-policy-service agree [company="AMD"]

@xiaohanAMD

Copy link
Copy Markdown
Author
@microsoft-github-policy-service agree [company="{your company}"]

@microsoft-github-policy-service agree [company="AMD"]
@microsoft-github-policy-service agree company="AMD"

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.

Copilot review overview

🟡 Changes recommended

The cancellation walk bypasses custom cost decisions and introduces quadratic traversal for merge chains.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread onnxruntime/core/optimizer/transpose_optimization/onnx_transpose_optimization.cc Outdated
xiaoh and others added 2 commits September 24, 2026 09:08
The fanout DFS kept its visited set local to each call, so a unique Transpose ahead of a long chain ending in a same-perm Transpose re-walked the whole suffix after every push. Track 'reaches a Transpose' and 'reaches a stranded terminal' as composable per-value flags in an OptimizerCtx memo that the existing rewrite invalidation clears.

Co-authored-by: Cursor <cursoragent@cursor.com>
The two DFS copies used the same stack, in-progress marker, and rewrite invalidation with only the combine rule different, so a third walk could go call-local again. ExpandPushWalk classifies consumers; RunCachedPushWalk owns the memo.

Co-authored-by: Cursor <cursoragent@cursor.com>

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.

Copilot review overview

🔵 Needs a closer look

The output-cost fallback can still push an unshared Transpose when the downstream Transpose is unreachable.

Review effort: Balanced
Findings: None

Resolved since last review (1)

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.

Copilot review overview

🟡 Changes recommended

The walk can waive costs across unreachable paths, and cache invalidation makes long chains quadratic.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment on lines +3516 to +3521
for (auto it = ctx.pushed_walk_cache.begin(); it != ctx.pushed_walk_cache.end();) {
if (rewired_values.find(it->first.value) != rewired_values.end()) {
it = ctx.pushed_walk_cache.erase(it);
} else {
++it;
}
@xiaohanAMD

Copy link
Copy Markdown
Author

I will close this PR. #32868 please review this new PR, Scott McKay (@skottmckay) mirounga
Edward Chen (@edgchen1)

@xiaohanAMD xiaohanAMD closed this Sep 28, 2026
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.

2 participants