Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix op decomposition issue when multiple partitioners with conflicting expectations are run - #14458

Merged
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479
Sep 23, 2025
Merged

Fix op decomposition issue when multiple partitioners with conflicting expectations are run#14458
abhinaykukkadapu merged 1 commit into
pytorch:mainfrom
abhinaykukkadapu:export-D82936479

Conversation

@abhinaykukkadapu

@abhinaykukkadapuabhinaykukkadapu commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Differential Revision: D82936479

Context:
This diff enables sequential recipes targeting multiple backends (for example: CoreML.FP32 + XNNPack.FP32, with XNNPack as a fallback). I think, we had not tested lowering models to multiple backends, so this edge case was unhandled.

While lowering the Vision Transformer (ViT) model, I encountered issues similar to those previously seen with the SDPA op (discussion). Although a fix exists, it did not account for scenarios with multiple partitioners having conflicting decomposition requirements and op filtering for the no-decomp namespace.

Error scenarios

The core problem: if two partitioners request to preserve different ops (ex: XNNPack wants to preserve aten.max_pool2d, but QNN does not support it), the current logic unions all ops to preserve, causing errors if a backend cannot handle an op.

QNN + XNNPack

[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'

CoreML + XNNPack

[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)

Note: Lowering to a single backend (CoreML, XNNPack, or QNN) works as expected, the issue is only hit when there are backend combinations with conflicting expectations.

Changes:

  • Decomposition skipping and filtering are now handled per partitioner, rather than globally.
  • Refactored code for readability by removing too many boolean + if/else checks which are hard to follow.

@pytorch-bot

pytorch-botBot commented Sep 22, 2025

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/14458

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure

As of commit 04f413a with merge base 99e4fbe (image):

NEW FAILURE - The following job has failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 22, 2025
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary: Pull Request resolved: pytorch#14458
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

@abhinaykukkadapu
abhinaykukkadapu marked this pull request as ready for review September 22, 2025 18:43
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

abhinaykukkadapu added a commit to abhinaykukkadapu/executorch that referenced this pull request Sep 22, 2025
…g expectations are run (pytorch#14458)
Summary:
Pull Request resolved: pytorch#14458
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error (using both coreml + xnnpack)
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
**Note**: Lowering to single backend works with both coreml or xnnpack, it is the combination that give this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
…g expectations are run (pytorch#14458)
Summary:
Context: I'm trying to enable sequential recipes targeting multiple backends (such as `CoreML.FP32 + XNNPack.FP32`, here xnnpack will be a fallback for the ops) and i don't think we've ever tested lowering a model to multiple backends or this edge case has never been hit.
I've hit this case when i tried to lower vision transformer model (VIT)
I've seen a similar problem occurred with SDPA op (discussed [here](https://fb.workplace.com/groups/pytorch.edge.users/permalink/1796069037930048/)) and has been fixed and i think the fix works but it didn't considered when there multiple partitioners in mind with *conflicting decomposition requirements and filter for op no decomp namespace*.
## Error with coreml + xnnpack
```
[2025-09-22T11:01:47.263-07:00] ValueError: Cannot view a tensor with shape torch.Size([197, 1, 12, 64]) and strides (64, 151296, 12608, 1) as a tensor with shape (197, 768)!
[2025-09-22T11:01:47.263-07:00]
[2025-09-22T11:01:47.263-07:00] While executing %view_8 : [num_users=1] = call_function[target=torch.ops.aten.view.default](args = (%permute_6, [197, 768]), kwargs = {})
[2025-09-22T11:01:47.263-07:00] Original traceback:
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 298, in forward
[2025-09-22T11:01:47.263-07:00] x = self.encoder(x)
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 157, in forward
[2025-09-22T11:01:47.263-07:00] return self.ln(self.layers(self.dropout(input)))
[2025-09-22T11:01:47.263-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torchvision/models/vision_transformer.py", line 113, in forward
[2025-09-22T11:01:47.263-07:00] x, _ = self.self_attention(x, x, x, need_weights=False)
```
## Error with QNN + XNNPACK
The core problem here is that if there are two partitioners, Backend A partitioner (asking to preserve ops x, y) and Backend B partitioner (asking to preserve ops z), assume Backend A doesn't understand Z and want to decompose, currently we `union` all the ops to preserve from multiple partitioner and it errors out
In this specific case, xnnpack asks to preserve `aten.max_pool2d` but QNN doesn't understand it.
```
[2025-09-22T12:18:33.640-07:00] partition_list = capability_partitioner.propose_partitions()
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 226, in propose_partitions
[2025-09-22T12:18:33.640-07:00] if self._is_node_supported(node) and node not in assignment:
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/torch/fx/passes/infra/partitioner.py", line 87, in _is_node_supported
[2025-09-22T12:18:33.640-07:00] return self.operator_support.is_node_supported(
[2025-09-22T12:18:33.640-07:00] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] File "/data/users/abhinayk/fbsource/buck-out/v2/gen/fbcode/afd2a63214a057a8/executorch/export/tests/__test_target_recipes__/test_target_recipes#link-tree/executorch/backends/qualcomm/partition/qnn_partitioner.py", line 100, in is_node_supported
[2025-09-22T12:18:33.640-07:00] op_wrapper = self.node_visitors[node.target.__name__].define_node(
[2025-09-22T12:18:33.640-07:00] ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
[2025-09-22T12:18:33.640-07:00] KeyError: 'aten.max_pool2d.default'
```
**Note**: Lowering to single backend works with both coreml or xnnpack or QNN, it is the combinations that hits this error.
Changes:
- The fix is to run decomposition filtering and skipping per partitioner rather than maintaining same rule in global scope.
- I've additionally refactored the code to make it more readable by removing multiple boolean checks.
Differential Revision: D82936479
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@abhinaykukkadapu has exported this pull request. If you are a Meta employee, you can view the originating diff in D82936479.

if not can_skip_using_EDGE_DO_NOT_DECOMP:
program = program.run_decompositions(_default_decomposition_table())
_restore_transformed_ops_to_aten_ops(program)
if can_skip_using_edge_do_not_decomp:

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.

LGTM, but there is still a logical bug in ET's AOT code with the EDGE_DO_NOT_DECOMP namespace / preservation when it comes to SDPA (and maybe other ops).

For CoreML, we get around it by skipping that path, but other backends (e.g., XNNPACK or QNN) will run into it if they preserve SDPA.

Perhaps the runtime time could take a look at this issue? cc @JacobSzwejbka@larryliu0820

@abhinaykukkadapu
abhinaykukkadapu merged commit b991271 into pytorch:mainSep 23, 2025
129 of 132 checks passed
@abhinaykukkadapu
abhinaykukkadapu deleted the export-D82936479 branch September 23, 2025 18:25
jirioc pushed a commit to nxp-upstream/executorch that referenced this pull request Dec 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@abhinaykukkadapu@facebook-github-bot@metascroy