Skip to content

[ExecuTorch][WebGPU] Tests for the q4gsw_train WebGPU ops#21085

Merged
JCNTH merged 16 commits into
gh/JCNTH/70/origfrom
gh/JCNTH/71/orig
Jul 21, 2026
Merged

[ExecuTorch][WebGPU] Tests for the q4gsw_train WebGPU ops#21085
JCNTH merged 16 commits into
gh/JCNTH/70/origfrom
gh/JCNTH/71/orig

Conversation

@pytorchbot

Copy link
Copy Markdown
Collaborator

This PR was created by the merge bot to help merge the original PR into the main branch.
ghstack PR number: #20932 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/71/base
ghstack PR head: https://github.com/pytorch/executorch/tree/gh/JCNTH/71/head
Merge bot PR base: https://github.com/pytorch/executorch/tree/gh/JCNTH/70/orig
Merge bot PR head: https://github.com/pytorch/executorch/tree/gh/JCNTH/71/orig

@diff-train-skip-merge

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/)
@pytorch-bot

pytorch-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🔗 Helpful Links

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

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

❗ 1 Active SEVs

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

@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 Jul 21, 2026
JCNTH added 15 commits July 21, 2026 09:45
…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/)
@JCNTH
JCNTH merged commit ef92165 into gh/JCNTH/70/orig Jul 21, 2026
10 checks passed
@JCNTH
JCNTH deleted the gh/JCNTH/71/orig branch July 21, 2026 16:45
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.

2 participants