[ET-VK] Lower eligible conv1d as conv2d over a singleton height dim - #22330
Open
msluszniak wants to merge 1 commit into
Open
[ET-VK] Lower eligible conv1d as conv2d over a singleton height dim#22330msluszniak wants to merge 1 commit into
msluszniak wants to merge 1 commit into
Conversation
conv1d.glsl computes one output element per invocation with no tiling and no reuse between invocations, and its work grid packs texels along the batch dim, wasting three of every four lanes at batch 1. On an Adreno 840 it reaches about 30 GFLOP/s where the tiled linear reaches 900 GFLOP/s in the same graph. The conv2d im2col + GEMM path already solves this, so rewrite 1-D convolutions that are certain to reach it into a 2-D convolution over a singleton height dim. Only convs with groups 1, unit dilation, kernel > 1, batch 1 and out_channels >= kIm2colMinCOut are rewritten, so nothing moves onto a shader that has not been compared against conv1d. The Whisper-tiny encoder goes from 153.2 ms to 88.8 ms, from 0.84x XNNPACK to 1.44x. Fixes pytorch#22329
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22330
Note: Links to docs will display an error until the docs builds have been completed.
|
This PR needs a
|
This was referenced Aug 30, 2026
msluszniak
added a commit
to software-mansion-labs/executorch
that referenced
this pull request
Aug 30, 2026
conv1d.glsl has no tiling and no reuse between invocations and packs its texels along the batch dim, so it runs at roughly 3 percent of the throughput the same GPU reaches on a matmul of equal MAC count. Route eligible 1-D convolutions through the existing conv2d im2col + GEMM path instead. Also backports set_param_tensor from upstream, which the pass uses to reshape the weight in place. Upstream: pytorch/executorch#22329, pytorch/executorch#22330
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #22329.
conv1d.glslcomputes one output element per invocation with no tiling, no shared memory and no register blocking, and every tap re-fetches both a weight texel and an input texel, so nothing is reused between invocations. On top of thatconv1d_gwgsetsz = div_up_4(batch), so the texel's four lanes span the batch dimension and three of every four are dead at batch 1.Measured inside a single Whisper-tiny encoder execution on an Adreno 840, using the shader query pool:
conv1d_float, 80 -> 384, k=3, L=3000conv1d_float, 384 -> 384, k=3, s=2, L=1500linear_vec_bias, M=1500, N=1536, K=384linear_vec_bias, M=1500, N=384, K=1536Same GPU, same run. Those two conv1d dispatches were 43% of the encoder's runtime.
conv2d_im2col.glsl+conv2d_gemm.glslalready solve this for 2-D convolutions. This pass rewrites 1-D convolutions into a 2-D convolution over a singleton height dim so the existing conv2d machinery (im2col selection, memory layout tagging, weight prepacking) handles them with no runtime changes.The weight is reshaped in place through
set_param_tensorrather than with aview_copynode, becauseadd_conv2d_nodereads it as a constantTensorRefand a runtime view would hand it aTENSORinstead.Eligibility
Only convolutions that are certain to reach the im2col path are rewritten, so nothing silently moves onto a shader that has not been compared against conv1d:
groups == 1and unit dilation, matchingshould_use_conv2d_im2colout_channels >= kIm2colMinCOut(128), so the im2col path is selected on every vendor rather than only on Malikernel_size > 1, leaving pointwise convs onconv1d_pwResults
Whisper-tiny encoder, Samsung Galaxy S26 Ultra (Adreno 840), fp32, interleaved A/B with the order reversed each round, warm-up iteration discarded, 60 timed iterations per arm. Both Vulkan arms include #22328, without which the encoder is not deterministic on this GPU at all.
Cosine against the CPU reference is 0.99999684 after the change.
Isolated, just the encoder's two convs plus gelu, GPU time from the query pool:
20x on the frontend, and the output is unchanged at cosine 1.00000000, max abs diff 4.9e-04 against the CPU reference in both.
Routing and correctness matrix
Eight conv1d configurations exported and run on device, with the dispatched shader read back from the query pool:
conv2d_im2col+conv2d_gemmconv2d_im2col+conv2d_gemmconv2d_im2col+conv2d_gemmconv1d_floatconv1d_pw_bias_textureconv1d_dw_bias_textureconv1d_floatconv1d_floatEvery ineligible case keeps its original shader and every case matches the CPU reference.
test_vulkan_backend_conv1d_as_conv2dcovers the rewritten path in CI; the two existing conv1d tests are grouped and depthwise, so they only exercise the untouched path.Depends on #22328
Moving the conv frontend onto the im2col + GEMM path exposes a pre-existing Adreno defect in
conv2d_gemm, which dispatches with the same near-square local work group aslinear. Distinct outputs over 60 executions on the Adreno 840, withLinear.cppalready fixed:This is not caused by the rewrite.
conv2d_gemmis already reached today by any conv2d without_channels >= 128, and it is nondeterministic there for the same reason. #22328 now fixes the sharedpick_xy_square_lwghelper rather than one caller, and with it every model above is 1 / 60.The 88.8 ms figure above was measured with only
Linear.cppfixed. With #22328 applied in full the encoder is 91.4 ms, still 1.34x XNNPACK, and correct on every run.