Skip to content

Qualcomm: decide graph-output publishing per output, not per node - #22011

Open
psiddh wants to merge 1 commit into
pytorch:mainfrom
psiddh:qnn-graph-output-per-index
Open

Qualcomm: decide graph-output publishing per output, not per node#22011
psiddh wants to merge 1 commit into
pytorch:mainfrom
psiddh:qnn-graph-output-per-index

Conversation

@psiddh

@psiddh psiddh commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

is_graph_output() took only a node, so for a multi-output op it answered one question -- "does anything from this op leave the partition?" -- and applied that single answer to every output. One escaping output published all of them as QNN_TENSOR_TYPE_APP_READ, including outputs with no consumer at all.

The ExecuTorch partitioner meanwhile wires up only the getitems that have live users, so the two halves of the same export disagreed about how many outputs the graph has, and nothing cross-checked them. The QNN graph then declares more outputs than ExecuTorch binds, and the two are paired by position, so the extra entries shift everything after them.

Take an optional output index and, for a multi-output node, follow only that index's getitem. Callers passing no index keep the previous behaviour, so the remaining call sites are unaffected. define_tensor already has wrapper_idx and already passes it to get_tensor_name, so thread it into get_tensor_type as well.

op_custom_op already asks for QNN_TENSOR_TYPE_NATIVE on every output; get_tensor_type was overriding it.

Also make is_graph_output tolerate users whose target has no name -- call_module targets are plain strings, and the previous form raised AttributeError on them.

Verified on x86 with a six-output custom op at a partition boundary with indices 0, 1 and 4 consumed: APP_READ goes from 6 to 3, matching the consumed set exactly. On a model returning those outputs out of order the unfixed version segfaults, so this is a crash fix rather than only a correctness one.

cc @cbilgin

Copilot AI lite review requested due to automatic review settings August 21, 2026 08:37
@pytorch-bot

pytorch-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

🔗 Helpful Links

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

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

❌ 6 New Failures, 30 Unrelated Failures

As of commit 6463f20 with merge base baafd7e (image):

NEW FAILURES - The following jobs have failed:

FLAKY - The following jobs failed but were likely due to flakiness present on trunk:

BROKEN TRUNK - The following jobs failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

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

@meta-cla meta-cla Bot 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 Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@psiddh psiddh added the module: qnn Issues related to Qualcomm's QNN delegate and code under backends/qualcomm/ label Aug 21, 2026
@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.

is_graph_output() took only a node, so for a multi-output op it answered one
question -- "does anything from this op leave the partition?" -- and applied
that single answer to every output. One escaping output published all of
them as QNN_TENSOR_TYPE_APP_READ, including outputs with no consumer at all.

The ExecuTorch partitioner meanwhile wires up only the getitems that have
live users, so the two halves of the same export disagreed about how many
outputs the graph has, and nothing cross-checked them. The QNN graph then
declares more outputs than ExecuTorch binds, and the two are paired by
position, so the extra entries shift everything after them.

Take an optional output index and, for a multi-output node, follow only that
index's getitem. Callers passing no index keep the previous behaviour, so
the remaining call sites are unaffected. define_tensor already has
wrapper_idx and already passes it to get_tensor_name, so thread it into
get_tensor_type as well.

op_custom_op already asks for QNN_TENSOR_TYPE_NATIVE on every output;
get_tensor_type was overriding it.

Also make is_graph_output tolerate users whose target has no __name__ --
call_module targets are plain strings, and the previous form raised
AttributeError on them.

Verified on x86 with a six-output custom op at a partition boundary with
indices 0, 1 and 4 consumed: APP_READ goes from 6 to 3, matching the
consumed set exactly. On a model returning those outputs out of order the
unfixed version segfaults, so this is a crash fix rather than only a
correctness one.

Diagnosed by Min Guo, who is carrying an equivalent local patch; this makes
it a first-class fix so any multi-output op benefits.

Authored with assistance from Claude (Claude Code).
@psiddh
psiddh force-pushed the qnn-graph-output-per-index branch from 076d84b to 6463f20 Compare August 25, 2026 16:27
Copilot AI review requested due to automatic review settings August 25, 2026 16:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@qti-horodnic qti-horodnic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the change, LGTM. Since this changes output publication for every multi-output builder using wrapper_idx, do you mind running one of the on-device tests in test_qnn_delegate (e.g. topK) to make sure there are no regressions?

Left a couple of other comments, one is minor, feel free to address the other one in a separate PR if you want to keep this one contained.

Comment on lines 134 to 138
any(
user.op == "output"
or user.target.__name__ == "getitem"
and is_graph_output(user)
for user in tensor.users.keys()

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.

Might be a bit of a scope creep, but since this method has the same issue as the one fixed above, can we apply the same getattr(user.target, "__name__", "") change here while you're modifying the file?

node, self.edge_program
)
is_output = is_graph_output(node)
is_output = is_graph_output(node, wrapper_idx)

@qti-horodnic qti-horodnic Aug 25, 2026

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.

Minor: wrapper_idx is not necessarily always an output index (e.g. in op_scatter_elements it's just a scratch variable). Should we add a check to pass it here only if an output index? Or we can add a comment stating the assumption

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. module: qnn Issues related to Qualcomm's QNN delegate and code under backends/qualcomm/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants