Skip to content

[ET-VK] Scale softmax workers with the row length and reduce as a tree - #22349

Open
msluszniak wants to merge 2 commits into
pytorch:mainfrom
msluszniak:ms/vulkan-softmax-scale-workers
Open

[ET-VK] Scale softmax workers with the row length and reduce as a tree#22349
msluszniak wants to merge 2 commits into
pytorch:mainfrom
msluszniak:ms/vulkan-softmax-scale-workers

Conversation

@msluszniak

@msluszniak msluszniak commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

softmax_buffer.glsl hardcodes

#define NWORKERS 4
#define MAX_NTHREADS 16

and pick_softmax_gwg() matches it with lwg_extents[reduce_dim] = 4u, so four threads normalise a row no matter how long it is. On all-MiniLM-L6-v2 at its published 254-token shape that is six dispatches of 633 us each: 21.2% of GPU time on an Adreno 840.

Widening alone only recovered a quarter of it. The aggregation after each barrier was a serial walk of all NWORKERS shared entries executed by every thread, not just thread 0, so its cost grew with the worker count and cancelled most of the benefit. Replacing both walks (max, then sum) with a shared-memory tree is the larger half of the win. The loop bound is uniform and barrier() sits outside the guard, so every thread still reaches every barrier.

Measurements

all-MiniLM-L6-v2 @254 on an Adreno 840, four interleaved order-reversed rounds, best of 20 executions each:

best
baseline 18.30 ms
+ workers scaled 17.40 ms
+ tree aggregation 15.53 ms (-15.1%)

softmax_buffer itself goes 3.80 -> 2.91 ms. Output matches the XNNPACK build to cosine 0.9999975 and is 60/60 bit-identical across runs.

Notes

reduce_dim is a WHCN/xyz index (0 = x = last dim) while size_at() counts back from the end, hence the -(reduce_dim + 1) in softmax_nworkers().

Buffer storage only. The texture path uses a different shader and grouping scheme and is left alone; it has the same hardcoded constants and is worth a follow-up.

Fixes #22351

softmax_buffer.glsl hardcoded

  #define NWORKERS 4
  #define MAX_NTHREADS 16

and pick_softmax_gwg() matched it with lwg_extents[reduce_dim] = 4u, so four
threads normalised a row no matter how long it was. On all-MiniLM-L6-v2 at its
published 254-token shape, that is six dispatches of 633 us each: 21.2% of GPU
time on an Adreno 840.

Widening alone only recovered a quarter of it, because the aggregation after
each barrier was a serial walk of all NWORKERS shared entries executed by EVERY
thread, not just thread 0 - so its cost grew with the worker count. Replacing
both walks (max, then sum) with a shared-memory tree is the larger half of the
win. The loop bound is uniform and barrier() sits outside the guard, so every
thread still reaches every barrier.

all-MiniLM-L6-v2 @254 on an Adreno 840, four interleaved order-reversed rounds,
best of 20 executions each:

  baseline                        18.30 ms
  + workers scaled                17.40 ms
  + tree aggregation              15.53 ms   (-15.1%)

softmax itself 3.80 ms -> 2.91 ms. Output matches the XNNPACK build to cosine
0.9999975 and is 60/60 bit-identical across runs.

Note that reduce_dim is a WHCN/xyz index while size_at() counts back from the
end, hence the -(reduce_dim + 1) in softmax_nworkers().

Buffer storage only; the texture path uses a different shader and grouping
scheme and is left alone.
@msluszniak
msluszniak requested a review from SS-JIA as a code owner August 31, 2026 14:28
@pytorch-bot

pytorch-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

🔗 Helpful Links

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

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

⚠️ 16 Awaiting Approval

As of commit f7f7bd5 with merge base 60cb889 (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 Aug 31, 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.

…with

Same defect as the reduction change in pytorch#22348. softmax_nworkers() feeds
both the NWORKERS specialization constant, which is baked in when the
node is built and therefore reflects the dynamic upper bound, and
pick_softmax_global_wg_size(), which runs after every resize and sees
the actual extent.

When they disagree the buffer softmax launches fewer threads than its
shared-memory tree reduction indexes over, so the tree folds in slots no
thread ever wrote. Static shapes always agree, so this only affects
dynamic shapes below the bound.

Compute the count once in the node builder and pass it through the
resize args. Only the buffer path is affected; the texture path uses a
fixed 4 that matches the shader's #define.
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ET-VK] softmax_buffer normalises each row with 4 threads and aggregates serially in every thread

2 participants