Skip to content

[ExecuTorch][WebGPU] Add optimized linear_fp32 op#20847

Open
JCNTH wants to merge 3 commits into
gh/JCNTH/22/basefrom
gh/JCNTH/22/head
Open

[ExecuTorch][WebGPU] Add optimized linear_fp32 op#20847
JCNTH wants to merge 3 commits into
gh/JCNTH/22/basefrom
gh/JCNTH/22/head

Conversation

@JCNTH

@JCNTH JCNTH commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Stack from ghstack (oldest at bottom):

fp32 aten.linear.default now runs on the WebGPU backend as a shared-memory-tiled GEMM (with an optional bias), so the dense projections in the Florence-2 BART decoder and DaViT vision encoder execute on-device instead of falling back. aten.linear.default is the projection primitive throughout those attention stacks; without a WebGPU kernel it AOT-delegates but throws at graph load.

Problem — The WebGPU backend had no aten.linear.default kernel. The op partitions into the delegate at export time, but at runtime there was no registered handler, so any graph containing an fp32 linear could not run. A naive one-thread-per-output GEMM would also be memory-bound: every output element would re-read a full row of the input and a full column of the weight from global memory with no reuse.

Solution — Before: aten.linear.default had no runtime kernel (load-time failure). After: the handler binds in, the prepacked [N,K] weight, an optional bias, and an output, then dispatches a tiled GEMM that computes out[m,n] = sum_k in[m,k] * weight[n,k] (+ bias[n]). The 32x32 output tile is staged through workgroup shared memory: each workgroup cooperatively loads a 32x32 slab of A (input) and B (weight) into var<workgroup> arrays, workgroupBarrier-syncs, then accumulates across ceil(K/32) k-tiles so each loaded value is reused across the tile instead of re-fetched from global memory. When K % 4 == 0 the handler routes to a vec4 variant that views the input and weight buffers as array<vec4<f32>> over K and reduces with dot, halving the shared-memory traffic and issuing wide 16-byte loads.

Implementation — Shared-memory tiling: TILE = 32, @workgroup_size(8, 8, 1), each of the 64 threads owns a 4x4 register sub-tile (RPT = 4) so one 8x8 workgroup covers the full 32x32 output tile; two var<workgroup> arrays a_sub/b_sub stage the A/B tiles. The scalar tiled kernel (linear_fp32_tiled.wgsl) reads the transposed [N,K] weight via read_b at t_weight[col*K + krow]; the vec4 kernel (linear_fp32_vec4.wgsl) uses [32][8] vec4 tiles (TILE4 = 8) with a flat cooperative tile load (256 vec4 elements spread across the 64 threads) and a dot(av, b_sub[..]) inner product. Both apply the same bias epilogue v = v + t_bias[c] gated on params.has_bias. Build-time path routing: LinearFp32.cpp selects linear_fp32_vec4_wgsl when K % 4 == 0, else linear_fp32_tiled_wgsl (LinearFp32.cpp:77,100). Dispatch is a genuine 2D grid ceil(N/32) x ceil(M/32), throwing before any allocation if either dimension exceeds 65535 (no 1D-flat stride recovery needed). Uses the shared utils::make_compute_pipeline (shader/layout/pipeline/bind-group builder), utils::make_uniform (16-byte LinearParams {M,N,K,has_bias}), and utils::make_optional_binding (a 16-byte zero-filled dummy storage buffer for the None-bias binding that the shader gates off and never reads). Mirrors Vulkan backends/vulkan/runtime/graph/ops/impl/Linear.cpp (linear_packed_weight: tiled-vs-vec shader selection on width alignment, transposed prepacked weight, bias epilogue).

Constraints — fp32 only (validated by an output byte-size check); weight is the prepacked [N,K] constant copied by et_vk.prepack; bias may be None; the vec4 path requires K % 4 == 0; 2D dispatch is capped at 65535 workgroups per dimension; fixed @workgroup_size(8, 8, 1) (no override constant).

Co-authored-with: Claude Code.
@exported-using-ghexport

Differential Revision: D110836675

Differential Revision: D110836675

[ghstack-poisoned]
@pytorch-bot

pytorch-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🔗 Helpful Links

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

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

❌ 129 New Failures, 3 Unrelated Failures, 8 Unclassified Failures

As of commit 78b870f with merge base c2b273e (image):

NEW FAILURES - The following jobs have failed:

UNCLASSIFIED FAILURES - DrCI could not classify the following jobs because the workflow did not run on the merge base. The failures may be pre-existing on trunk or introduced by this PR:

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

  • pull / unittest / windows / windows-job (gh) (matched win rule in flaky-rules.json)
    Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under 'C:\actions-runner\_work\executorch\executorch\test-infra\.github\actions\teardown-windows'. Did you forget to run actions/checkout before running your local action?
  • pull / unittest-editable / windows / windows-job (gh) (matched win rule in flaky-rules.json)
    Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under 'C:\actions-runner\_work\executorch\executorch\test-infra\.github\actions\teardown-windows'. Did you forget to run actions/checkout before running your local action?

BROKEN TRUNK - The following job 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.

@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.

[ghstack-poisoned]
[ghstack-poisoned]
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. meta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants