Skip to content

Vulkan: support index.Tensor with the index on any dimension - #22406

Open
msluszniak wants to merge 1 commit into
pytorch:mainfrom
msluszniak:ms/vulkan-index-tensor-any-dim
Open

Vulkan: support index.Tensor with the index on any dimension#22406
msluszniak wants to merge 1 commit into
pytorch:mainfrom
msluszniak:ms/vulkan-index-tensor-any-dim

Conversation

@msluszniak

@msluszniak msluszniak commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

x[:, :, idx] reaches the edge dialect as index.Tensor(x, [None, None, idx]). The partitioner only accepted the index on dim 0, so every other gather fell back to the CPU.

That is not just slower than it needs to be. An unsupported node in the middle of a graph splits the delegate in two, and a model that gathers once per block ends up with one delegate call per block. A TTS vector estimator I was lowering came out as 29 delegate calls, 28 of them created by this single op. On a Galaxy S26 Ultra each of those boundaries costs about 3 ms in staging and submission, measured with a synthetic 4-block A/B.

Gathering along a dimension is exactly what index_select does, and the Vulkan implementation of it already handles every dimension. So dispatch there when the index does not sit on dim 0, and leave the index_tensor shader, which gathers along the leading dim, alone.

Two things were in the way of simply accepting the node

The dimension. It is recoverable at runtime: the serialized indices list keeps a null for every dimension that is not indexed, so the position of the tensor entry is the dimension. The list pointer has to be released before add_scalar(), which may reallocate the graph's value store (check_no_active_value_ptrs catches this).

The index tensor never got a representation. It lives inside a list whose other entries are None, and both is_tensor_arg_node() and is_non_constant_tensor_node() only recognise lists that are entirely tensors, so the whole argument was treated as a non-tensor and skipped by TagMemoryMetaPass. index_select reads channels-packed textures and asserts on anything else.

This adds tensor_nodes_in_arg(), which returns the tensor nodes of an argument and skips the Nones, and uses it everywhere the tagging pass walks a list argument. Lists that are entirely tensors, which is every other operator today, behave exactly as before.

Test plan

New test_vulkan_backend_index_tensor_non_leading_dim in backends/vulkan/test/test_vulkan_delegate.py, alongside the existing dim-0 test.

Verified on a Galaxy S26 Ultra (Adreno 840). A minimal x[:, :, idx] * 2 model aborts on check_index_select_args before this change and matches its CPU reference after (max abs diff 6e-4, fp16). The TTS model that motivated it goes from 29 delegate calls to 2 with unchanged outputs:

sub-model delegate calls cosine vs CPU
duration_predictor 11 -> 6 1.000000
text_encoder 15 -> 10 0.999414
vector_estimator 29 -> 2 0.999994
vocoder 14 -> 3 0.999977

End to end that is 11% faster on device, measured with the arms alternated forward and reverse inside each round to cancel thermal drift.

Depends on nothing, but note that lowering this model also needs #22403; without it FuseViewCopyTransform trips over the larger partition.

cc @SS-JIA @manuelcandales @digantdesai @cbilgin

`x[:, :, idx]` reaches the edge dialect as
`index.Tensor(x, [None, None, idx])`. The partitioner only accepted the index
on dim 0, so every other gather fell back to the CPU. That is not just slower
than it needs to be: an unsupported node in the middle of a graph splits the
delegate in two, and a model that gathers once per block ends up with one
delegate call per block. A TTS vector estimator here lowered to 29 delegate
calls, 28 of them created by this single op.

Gathering along a dimension is exactly what `index_select` does, and the
Vulkan implementation of it already handles every dimension, so dispatch
there when the index does not sit on dim 0 and leave the index_tensor shader,
which gathers along the leading dim, alone.

Two things were in the way of simply accepting the node:

- The dimension is recoverable at runtime, because the indices list keeps a
  null for every dimension that is not indexed, but the list pointer has to be
  released before `add_scalar()` may reallocate the graph's value store.

- Nothing assigned a representation to the index tensor. It lives inside a
  list whose other entries are None, and both `is_tensor_arg_node()` and
  `is_non_constant_tensor_node()` only recognise lists that are entirely
  tensors, so the whole argument was treated as a non-tensor and skipped.
  `index_select` reads channels-packed textures and asserts on anything else.

Add `tensor_nodes_in_arg()`, which returns the tensor nodes of an argument and
skips the Nones, and use it everywhere the tagging pass walks a list argument.
Lists that are entirely tensors, which is every other operator today, behave
exactly as before.

With this the same model lowers to 2 delegate calls, and its four sub-models
keep their outputs: cosine 1.000000, 0.999414, 0.999994 and 0.999977 against
their CPU references on a Galaxy S26 Ultra.
@msluszniak
msluszniak requested a review from SS-JIA as a code owner September 1, 2026 10:52
@pytorch-bot pytorch-bot Bot added the module: vulkan Issues related to the Vulkan delegate and code under backends/vulkan/ label Sep 1, 2026
@pytorch-bot

pytorch-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🔗 Helpful Links

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

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

⚠️ 15 Awaiting Approval

As of commit 7436c03 with merge base 5428092 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

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 Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

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.

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: vulkan Issues related to the Vulkan delegate and code under backends/vulkan/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants