[ExecuTorch][WebGPU] Add elementwise binary ops (div, sub) to the WebGPU backend#21074
Merged
Conversation
…GPU backend Pull Request resolved: #20921 Add broadcast-aware `aten.div.Tensor` + `aten.sub.Tensor` elementwise handlers for the training tail. Key changes: - `runtime/ops/{div,sub}/` — broadcast elementwise WGSL kernels + handlers - `CMakeLists.txt` `WEBGPU_SRCS` — wire the sources Reuses the shared Vulkan partitioner (`aten.div.Tensor`/`sub.Tensor` already registered); WebGPU kernels only. Co-authored-with: Claude Code. ghstack-source-id: 405026052 @exported-using-ghexport Differential Revision: [D111755128](https://our.internmc.facebook.com/intern/diff/D111755128/)
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21074
Note: Links to docs will display an error until the docs builds have been completed. ❗ 1 Active SEVsThere are 1 currently active SEVs. If your PR is affected, please view them below: This comment was automatically generated by Dr. CI and updates every 15 minutes. |
Pull Request resolved: #20922 Export-delegation + fp64 golden tests for the binary (`div`, `sub`) ops. Key changes: - `test/ops/{test_div,test_sub}.py` — fp64 goldens (incl. broadcast) + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026054 @exported-using-ghexport Differential Revision: [D111755114](https://our.internmc.facebook.com/intern/diff/D111755114/)
Pull Request resolved: #20923 Add an `aten.sum.dim_IntList` / `aten.mean.dim` reduction handler for the training tail. Key changes: - `runtime/ops/reduce/` — per-row reduction WGSL kernel + handler - `CMakeLists.txt` `WEBGPU_SRCS` — wire the source Reuses the shared Vulkan partitioner (`aten.sum.dim_IntList`/`mean.dim` already registered); WebGPU kernel only. Co-authored-with: Claude Code. ghstack-source-id: 405026057 @exported-using-ghexport Differential Revision: [D111755133](https://our.internmc.facebook.com/intern/diff/D111755133/)
Pull Request resolved: #20924 Export-delegation + fp64 golden tests for the reduce (`sum`/`mean`) ops. Key changes: - `test/ops/test_reduce.py` — fp64 goldens (dim variants) + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026059 @exported-using-ghexport Differential Revision: [D111755115](https://our.internmc.facebook.com/intern/diff/D111755115/)
…logical_not) to the WebGPU backend Pull Request resolved: #20925 Add `aten.where.self`, scalar comparisons (`aten.{eq,ne,le,ge,lt,gt}.Scalar`), and `aten.logical_not.default` handlers for the training tail. The scalar compares and `logical_not` are generated from ONE templated byte-packed-bool shader (`runtime/ops/boolean_op/`) rather than per-op hand-written kernels — mirroring the `binary_op` codegen and Vulkan's grouping of comparison ops as `${OPERATOR}` variants (`backends/vulkan/runtime/graph/ops/glsl/binary_op_buffer.yaml`). Key changes: - `runtime/ops/boolean_op/` — one `boolean_op.wgsl` template + `boolean_op.yaml` (variants `compare_{eq,ne,le,ge,lt,gt}` + `logical_not`) expand to the per-variant `*_wgsl.h`; `BooleanOp.cpp` holds one shared `dispatch_bool_op` helper (pack 4 bools per `u32` word, one thread per word) plus the seven `WEBGPU_REGISTER_OP`s. - `runtime/ops/where/` — ternary select, kept as its own kernel/handler (non-bool-family, as in Vulkan). - `op_registry.py` — register the net-new `aten.{ne,lt,le,ge,gt}.Scalar` + `aten.logical_not.default` `OpFeatures` (`eq.Scalar` already on master). - `CMakeLists.txt` `WEBGPU_SRCS` — wire `boolean_op/BooleanOp.cpp`. `aten.where.self` already had Vulkan `OpFeatures`; the scalar-compare + `logical_not` registrations are additive to the shared Vulkan registry. Co-authored-with: Claude Code. ghstack-source-id: 405026062 @exported-using-ghexport Differential Revision: [D111755122](https://our.internmc.facebook.com/intern/diff/D111755122/)
Pull Request resolved: #20926 Export-delegation + fp64 golden tests for the select/bool (`where`, scalar compares, `logical_not`) ops. Key changes: - `test/ops/{test_where,test_compare,test_logical_not}.py` — fp64 goldens + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026072 @exported-using-ghexport Differential Revision: [D111755124](https://our.internmc.facebook.com/intern/diff/D111755124/)
Pull Request resolved: #20927 Add `aten.gather.default` (row-gather) + `aten.embedding.default` handlers for the training tail. Key changes: - `runtime/ops/{gather,embedding}/` — gather / row-gather WGSL kernels + handlers - `CMakeLists.txt` `WEBGPU_SRCS` — wire the sources Reuses the shared Vulkan partitioner (`aten.gather`/`embedding` already registered); WebGPU kernels only. Co-authored-with: Claude Code. ghstack-source-id: 405026068 @exported-using-ghexport Differential Revision: [D111755125](https://our.internmc.facebook.com/intern/diff/D111755125/)
Pull Request resolved: #20928 Export-delegation + fp64 golden tests for the gather (`gather`, `embedding`) ops. Key changes: - `test/ops/{test_gather,test_embedding}.py` — fp64 goldens + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026067 @exported-using-ghexport Differential Revision: [D111755129](https://our.internmc.facebook.com/intern/diff/D111755129/)
…) to the WebGPU backend Pull Request resolved: #20929 Add `_clone_dim_order` (DMA copy), `aten.expand_copy` (broadcast), and `fill` (`aten.full`/`full_like`/`scalar_tensor`) handlers for the training tail. Key changes: - `runtime/ops/{dim_order,expand_copy,fill}/` — WGSL kernels + handlers (dim_order is a native buffer DMA copy) - `CMakeLists.txt` `WEBGPU_SRCS` — wire the sources Reuses the shared Vulkan partitioner (`_clone_dim_order`/`expand_copy`/`full`* already registered); WebGPU kernels only. Co-authored-with: Claude Code. ghstack-source-id: 405026073 @exported-using-ghexport Differential Revision: [D111755131](https://our.internmc.facebook.com/intern/diff/D111755131/)
Pull Request resolved: #20930 Export-delegation + fp64 golden tests for the shape/copy (`dim_order`, `expand_copy`, `fill`) ops. Key changes: - `test/ops/{test_dim_order,test_expand_copy,test_fill}.py` — fp64 goldens + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026074 @exported-using-ghexport Differential Revision: [D111755126](https://our.internmc.facebook.com/intern/diff/D111755126/)
…d, requant) to the WebGPU backend Pull Request resolved: #20931 Add the 4-bit fine-tuning weight-path custom ops — `et_vk.linear_q4gsw_backward` (input grad dX), `et_vk.linear_dW` (weight grad dW — a generic fp32 weight-gradient matmul, not q4gsw-specific), and `et_vk.q4gsw_requant` (STE re-quant of fp32 latents → 4-bit codes) — that close the on-device training loop through a frozen 4-bit base. Key changes: - `runtime/ops/quantized_linear/{QuantizedLinearBackward,QuantizedLinearRequant}.cpp` + `LinearDw.cpp` + `q4gsw_{backward,requant}.wgsl` + `linear_dW.wgsl` — the three training kernels - `custom_ops_lib.py` — 3 custom-op defs (`linear_q4gsw_backward` + `register_autograd` on `linear_q4gsw`, `linear_dW`, `q4gsw_requant`) - `op_registry.py` — 3 `OpFeatures` - `CMakeLists.txt` `WEBGPU_SRCS` — wire the sources Training-only, WebGPU-only custom ops registered under the shared Vulkan partitioner; Vulkan has no kernels for them yet (a separate Vulkan-kernel track covers that). Guards mirror the sibling `Dw`/`Requant` ops (scales-rank + `K==0`). Co-authored-with: Claude Code. ghstack-source-id: 405026076 @exported-using-ghexport Differential Revision: [D111755130](https://our.internmc.facebook.com/intern/diff/D111755130/)
Pull Request resolved: #20932 Export-delegation + fp64 golden tests for the q4gsw training (`backward`, `dw`, `requant`) ops. Key changes: - `test/ops/{test_quantized_linear_backward,test_linear_q4gsw_dw,test_q4gsw_requant}.py` — fp64 dequant-matmul / weight-grad / re-quant goldens + a round-trip + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026079 @exported-using-ghexport Differential Revision: [D111755121](https://our.internmc.facebook.com/intern/diff/D111755121/)
…WebGPU backend Pull Request resolved: #20933 Add `et_vk.fused_ce` — a fused cross-entropy (loss + dlogits in one op) for the on-device training tail. Key changes: - `runtime/ops/fused_ce/` — fused-CE WGSL kernel (+ the `reduce` shader it depends on) + handler - `custom_ops_lib.py` — `fused_ce` custom-op def + `register_autograd` - `op_registry.py` — `OpFeatures` - `CMakeLists.txt` `WEBGPU_SRCS` — wire the source Training-only, WebGPU-only custom op registered under the shared Vulkan partitioner; no Vulkan kernel yet. Co-authored-with: Claude Code. ghstack-source-id: 405026078 @exported-using-ghexport Differential Revision: [D111755132](https://our.internmc.facebook.com/intern/diff/D111755132/)
Pull Request resolved: #20934 Export-delegation + fp64 golden tests for the fused_ce ops. Key changes: - `test/ops/test_fused_ce.py` — fp64 loss + dlogits goldens (incl. masked-label) + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026080 @exported-using-ghexport Differential Revision: [D111755119](https://our.internmc.facebook.com/intern/diff/D111755119/)
…p) to the WebGPU backend Pull Request resolved: #20935 Add `et_vk.adamw_step` — an on-GPU AdamW optimizer step (delegated in-place parameter update) for on-device training. Key changes: - `runtime/ops/adamw/` — elementwise AdamW-update WGSL kernel + handler - `custom_ops_lib.py` — `adamw_step` custom-op def (mutating, `Tensor(a!)`) - `op_registry.py` — `OpFeatures` (string key `et_vk::adamw_step`) - `CMakeLists.txt` `WEBGPU_SRCS` — wire the source Delegating the optimizer step to the GPU is new to ExecuTorch — its optimizers are host-side C++ (`extension/training/optimizer/adamw.cpp`). Training-only WebGPU custom op under the shared Vulkan partitioner; no Vulkan kernel yet. Co-authored-with: Claude Code. ghstack-source-id: 405026081 @exported-using-ghexport Differential Revision: [D111755116](https://our.internmc.facebook.com/intern/diff/D111755116/)
Pull Request resolved: #20936 Export-delegation + fp64 golden tests for the adamw (`adamw_step`) ops. Key changes: - `test/ops/test_adamw.py` — fp64 in-place param/m/v goldens (incl. non-zero weight-decay) + delegation checks — fp64 library-reference goldens + delegation checks (≥2 cases/op). Co-authored-with: Claude Code. ghstack-source-id: 405026082 @exported-using-ghexport Differential Revision: [D111755127](https://our.internmc.facebook.com/intern/diff/D111755127/)
…zer step) Pull Request resolved: #20937 Vulkan GLSL kernel + handler for the training custom op `et_vk.adamw_step` (elementwise in-place AdamW parameter update). Mirrors `impl/BinaryOp.cpp` elementwise structure + `unary_op.glsl` push-constant scalars; param/m/v are read-modify-write (`kReadWrite`). AOT registration already exists under the shared Vulkan partitioner. ghstack-source-id: 405026087 @exported-using-ghexport Differential Revision: [D111761777](https://our.internmc.facebook.com/intern/diff/D111761777/)
Pull Request resolved: #20938 Vulkan op-test golden for `et_vk.adamw_step` (ATen library-computed reference of the CPU-eager update; in-place param/m/v vs the kernel). 3 cases incl. non-zero weight-decay + a large 1-D tail. Wires `adamw_step_test` in `targets.bzl` + `CMakeLists.txt`. ghstack-source-id: 405030805 @exported-using-ghexport Differential Revision: [D111761775](https://our.internmc.facebook.com/intern/diff/D111761775/)
…ad GEMM) Pull Request resolved: #20939 Vulkan GLSL kernel + handler for the training custom op `et_vk.linear_q4gsw_dw` — a dense fp32 GEMM `dW[N,K] = d_out^T @ x` (contracts over M; NOT quantized despite the name). Mirrors `impl/Matmul.cpp` `add_matmul_tiled_node` structure + `sizes_ubo` for dynamic shapes; the M-contraction tile math is transcribed from the golden-verified reference. AOT registration already exists under the shared Vulkan partitioner. ghstack-source-id: 405026089 @exported-using-ghexport Differential Revision: [D111761779](https://our.internmc.facebook.com/intern/diff/D111761779/)
Pull Request resolved: #20940 Vulkan op-test golden for `et_vk.linear_q4gsw_dw` (ATen `d_out.reshape(-1,N).t() @ x.reshape(-1,K)` reference). 3 cases: tile-aligned, non-tile-multiple (partial-tile clamp), and >2D leading-dim flatten. Wires `linear_q4gsw_dw_test` in `targets.bzl` + `CMakeLists.txt`. ghstack-source-id: 405026090 @exported-using-ghexport Differential Revision: [D111761776](https://our.internmc.facebook.com/intern/diff/D111761776/)
…loss + dlogits) Pull Request resolved: #20941 Vulkan GLSL kernels + handler for the training custom op `et_vk.fused_ce` (fused CE: per-row online-softmax loss + dlogits, then an N->1 loss sum). Per-row shared-memory reduction structured like `glsl/reduce_per_row_buffer.glsl` (`NWORKERS=64`, one workgroup/row, barrier tree-combine); two dispatch nodes share `loss_partial`, ordered by the runtime per-`vTensor` barrier (same pattern as `impl/SDPA.cpp` QK->softmax->AV). AOT registration already exists under the shared Vulkan partitioner. ghstack-source-id: 405059103 @exported-using-ghexport Differential Revision: [D111761780](https://our.internmc.facebook.com/intern/diff/D111761780/)
Pull Request resolved: #20942 Vulkan op-test golden for `et_vk.fused_ce` (ATen transcription of the CPU-eager loss/dlogits reference). 3 cases: all-valid, a masked label (`label<0`), and vocab >> workers (strided per-row reduce). Wires `fused_ce_test` in `targets.bzl` + `CMakeLists.txt`. ghstack-source-id: 405059109 @exported-using-ghexport Differential Revision: [D111761774](https://our.internmc.facebook.com/intern/diff/D111761774/)
…put-grad) Pull Request resolved: #20943 **Adds the Vulkan `et_vk.linear_q4gsw_backward` kernel** — the input-gradient of the frozen 4-bit `linear_q4gsw` base, for on-device adapter training. Computes `d_x[M, K] = d_out[M, N] @ dequant(W)[N, K]`, contracting over N. **Problem:** `et_vk.linear_q4gsw_backward` is registered in the shared Vulkan partitioner (`custom_ops_lib.py` + `op_registry.py`) but Vulkan had no runtime kernel, so the op could not run. **Solution:** a 4M x 4K register-tiled GLSL kernel that reads the SAME W_4X8 block-packed weight the forward reads, re-deriving the nibble/scale addressing so the training loop stays consistent with the forward with no re-pack. `dequant(W[n, k]) = (code - 8) * scale`. Key changes: - `glsl/q4gsw_backward.{glsl,yaml}` — buffer x float; W_4X8 nibble unpack mirroring `glsl/q4gsw_linear_gemm__w_4x8.glsl` (even-N low nibble, odd-N high, `N4_padded` ivec4 stride, `[num_groups, N]` scales). - `impl/QuantizedLinearBackward.cpp` — reuses `prepack_q4_w_4x8_nc_buffer` + `prepack_q4_scales` from the forward; 1D tile dispatch `ceil(M/4) * ceil(K/4)` with a workgroup-count guard; `group_size` specialization constant. **Constraints:** buffer storage, fp32; `N % 4 == 0`, `K % 4 == 0`, `group_size % 4 == 0` (matches the forward prepack). Weight/scale layout identical to the forward. ghstack-source-id: 405059111 @exported-using-ghexport Differential Revision: [D111797529](https://our.internmc.facebook.com/intern/diff/D111797529/)
Pull Request resolved: #20944 **Correctness tests for the Vulkan `et_vk.linear_q4gsw_backward` kernel** (stacked above the op diff). **Coverage:** the golden `d_x = d_out @ dequant(W)` is computed with ATen (`matmul` over the library-dequantized weight), mirroring the CPU-eager `linear_q4gsw_backward_impl` in `custom_ops_lib.py` — never a hand-rolled matmul. The packed weight is fed as a constant `tensorref` so the op exercises the real `prepack_q4_w_4x8_nc_buffer` W_4X8 path. Cases: - `test_tile_aligned` — single group, tile-aligned M/N/K. - `test_grouped` — multiple quantization groups along K. - `test_odd_n4_partial_m` — `N % 8 != 0` (odd N4 -> padded W_4X8 stride) plus a partial-M tile (exercises the `min()` clamps). Also wires `quantized_linear_backward_test` into `targets.bzl` + `CMakeLists.txt` (mirrors the sibling `linear_q4gsw_dw_test`). ghstack-source-id: 405059117 @exported-using-ghexport Differential Revision: [D111797530](https://our.internmc.facebook.com/intern/diff/D111797530/)
…W_4X8) Pull Request resolved: #20945 **Adds the Vulkan `et_vk.q4gsw_requant` kernel** — straight-through re-quant of fp32 latent weights into the frozen-scale 4-bit codes, for on-device weight training. Writes the codes directly in the forward's W_4X8 layout so no per-step re-pack is needed. **Problem:** `et_vk.q4gsw_requant` is registered in the shared Vulkan partitioner but Vulkan had no runtime kernel. **Solution:** a GLSL kernel that quantizes `round(latent / scale)` clamped to `[-8, 7]` and packs the codes in the W_4X8 block layout the forward reads, reusing the exact byte-pair convention of `glsl/pack_q4_linear_weight__w_4x8.glsl` (even-N low nibble, odd-N high; one ivec4 per 4K x 8N block at `(k4, n8)`; OOB upper tile = the bias-zero `0x88888888`). A zero scale yields code 8 (no divide-by-zero), matching the eager reference. Key changes: - `glsl/q4gsw_requant.{glsl,yaml}` — buffer x float; 2D dispatch over `(k4, n8)`. - `impl/QuantizedLinearRequant.cpp` — prepacks the constant scales; output is the W_4X8 int buffer `[K4 * N4_padded * 2]`; `group_size` spec constant; dispatch-grid guard. **Design note (for review):** per the layout decision, requant writes W_4X8 to match the Vulkan forward. Today the forward and backward each prepack their own flat `[N, K/2]` weight internally, so nothing yet consumes an externally-produced W_4X8 buffer — closing the training loop needs a forward path that reads a mutable pre-packed weight (the weight-lifecycle follow-up). The op's AOT meta in `custom_ops_lib.py` still describes the flat `[N, K/2]` output and should be reconciled with this W_4X8 output. **Constraints:** buffer storage, fp32 latent/scales; `N % 4 == 0`, `K % 4 == 0`, `group_size % 4 == 0`. ghstack-source-id: 405059118 @exported-using-ghexport Differential Revision: [D111797527](https://our.internmc.facebook.com/intern/diff/D111797527/)
Pull Request resolved: #20946 **Correctness tests for the Vulkan `et_vk.q4gsw_requant` kernel** (stacked above the op diff). **Coverage:** the golden codes are computed with ATen (`round`/`clamp`, zero-scale -> code 8), mirroring `quant_nibble`, then packed into the expected W_4X8 int buffer with a small bit-packing reference (data-reshaping only, no hand-rolled math). The kernel output is compared int-for-int against that buffer, which locks the exact byte layout the forward reads. The latent is built as `code * scale` so `round()` is unambiguous (no `.5` tie-break divergence). Cases: - `test_tile_aligned` — single group, tile-aligned N/K. - `test_grouped` — multiple quantization groups along K. - `test_odd_n4` — `N % 8 != 0` (odd N4 -> padded stride + bias-zero OOB tile). - `test_zero_scale` — a zero scale must yield code 8, not a divide-by-zero. Also wires `q4gsw_requant_test` into `targets.bzl` + `CMakeLists.txt`. ghstack-source-id: 405059122 @exported-using-ghexport Differential Revision: [D111797526](https://our.internmc.facebook.com/intern/diff/D111797526/)
Pull Request resolved: #21005 Replace the hand-maintained explicit `WEBGPU_SRCS` op-handler list with `file(GLOB WEBGPU_OP_SRCS CONFIGURE_DEPENDS runtime/ops/*/*.cpp)` so adding a new op no longer requires editing this file (addresses review feedback). The five `runtime/*.cpp` sources and `runtime/ops/OperatorRegistry.cpp` (which sits directly under `ops/`, not a per-op subdir) stay explicit. `CONFIGURE_DEPENDS` re-globs at build time when op sources are added or removed. The glob resolves to exactly the 40 op handlers the explicit list enumerated (verified set-equal) — no op added or dropped, and static-init registration is order-independent under `--whole-archive`. Co-authored-with: Claude Code. ghstack-source-id: 405059133 @exported-using-ghexport Differential Revision: [D112482039](https://our.internmc.facebook.com/intern/diff/D112482039/)
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.
This PR was created by the merge bot to help merge the original PR into the main branch.
ghstack PR number: #20921 by @JCNTH
^ Please use this as the source of truth for the PR details, comments, and reviews
ghstack PR base: https://github.com/pytorch/executorch/tree/gh/JCNTH/60/base
ghstack PR head: https://github.com/pytorch/executorch/tree/gh/JCNTH/60/head
Merge bot PR base: https://github.com/pytorch/executorch/tree/gh/JCNTH/59/orig
Merge bot PR head: https://github.com/pytorch/executorch/tree/gh/JCNTH/60/orig
@diff-train-skip-merge