|
1 | | -# WebGPU Backend |
| 1 | +# ExecuTorch WebGPU Backend |
2 | 2 |
|
3 | | -Run ExecuTorch models on the GPU via [WebGPU](https://www.w3.org/TR/webgpu/). The backend compiles delegated subgraphs into WGSL compute shaders executed natively through [Dawn](https://dawn.googlesource.com/dawn), whose Tint compiler is the reference WGSL implementation (Metal on macOS, Vulkan on Linux/Windows). |
| 3 | +Run ExecuTorch models on the GPU through |
| 4 | +[WebGPU](https://www.w3.org/TR/webgpu/). The backend compiles delegated |
| 5 | +subgraphs into WGSL compute shaders and executes them through |
| 6 | +[Dawn](https://dawn.googlesource.com/dawn) and its Tint WGSL compiler. The same |
| 7 | +graph execution design targets Metal and Vulkan in native builds. Browser |
| 8 | +builds compile the same execution path with Emscripten and `emdawnwebgpu`. |
4 | 9 |
|
5 | | -> **Status: Prototype, under active development.** The backend runs the core of transformer inference today — `add`, `rms_norm`, fused scaled-dot-product attention with KV cache, and 4-bit weight-only quantized linear — plus quantized embedding, rotary embedding, and constant prepacking. See [Progress](#progress) for shipped milestones. |
| 10 | +> **Status: under active development.** The backend has more than 100 |
| 11 | +> registered operator symbols and has demonstrated browser artifacts and |
| 12 | +> workflows across language, vision, retrieval, audio, and on-device training. |
6 | 13 |
|
7 | | -## Progress |
| 14 | +## Performance |
8 | 15 |
|
9 | | -Milestones landed on `main`: |
| 16 | +Measured in July 2026 on an Apple M4 Pro in Chrome Canary, using WebGPU |
| 17 | +GPU-timestamp queries and a hash-pinned, optimized dynamic-shape 4-bit Llama |
| 18 | +3.2 1B artifact. Headline values are medians from three measured runs per cell |
| 19 | +after three untimed prefill and decode warmups. |
10 | 20 |
|
11 | | -| Date | Milestone | Pull Request | |
12 | | -|---|---|---| |
13 | | -| 2026-04 | Made it possible to run ExecuTorch models on the GPU through WebGPU — built the backend from the ground up, including the runtime delegate that builds the GPU graph (buffers, pipelines, bind groups) and runs the model on Metal and Vulkan | [#18808](https://github.com/pytorch/executorch/pull/18808) | |
14 | | -| 2026-06 | Grew model support beyond element-wise operators — added the root-mean-square normalization operator (`rms_norm`) and named-data weight loading | [#19963](https://github.com/pytorch/executorch/pull/19963) | |
15 | | -| 2026-06 | Made sure every change is automatically tested — added WebGPU to ExecuTorch's standard backend test suite, running on Linux/x86 in CI | [#19964](https://github.com/pytorch/executorch/pull/19964) | |
16 | | -| 2026-06 | Removed a class of bugs and manual upkeep — the WGSL shaders are now generated automatically, with a build-time check that fails the build on shader/source drift | [#19981](https://github.com/pytorch/executorch/pull/19981) | |
17 | | -| 2026-06 | Got the test suite to actually run work on the GPU — added operator-allowlist delegation (unsupported operations fall back to the CPU) and a process-wide GPU device context, so models execute on the GPU during testing | [#20036](https://github.com/pytorch/executorch/pull/20036) | |
18 | | -| 2026-06 | Made testing match the WebGPU standard exactly — switched the native runtime and tests to Google's Dawn shader compiler (Tint, the source-of-truth WGSL implementation) running on SwiftShader for headless GPU execution | [#20079](https://github.com/pytorch/executorch/pull/20079) | |
19 | | -| 2026-06 | Strengthened correctness for models that run in several GPU passes — added dispatch-ordering and scratch-buffer (temporary GPU memory) support and tests | [#20080](https://github.com/pytorch/executorch/pull/20080) | |
20 | | -| 2026-06 | Added the attention core of transformer inference — fused scaled-dot-product attention (`sdpa_with_kv_cache`) with an `update_cache` operator for autoregressive decode | [#20086](https://github.com/pytorch/executorch/pull/20086), [#20087](https://github.com/pytorch/executorch/pull/20087) | |
21 | | -| 2026-06 | Added on-GPU kernel timing via WebGPU timestamp queries, for true GPU-side profiling | [#20201](https://github.com/pytorch/executorch/pull/20201) | |
22 | | -| 2026-06 | Added the dominant compute in quantized LLMs — 4-bit weight-only quantized linear (`linear_q4gsw`), a dequantize-and-matmul kernel | [#20226](https://github.com/pytorch/executorch/pull/20226), [#20227](https://github.com/pytorch/executorch/pull/20227) | |
| 21 | +| Context / prompt tokens | Prefill | Decode | |
| 22 | +|---:|---:|---:| |
| 23 | +| 128 | 1,881.5 tokens/s | 188.3 tokens/s | |
| 24 | +| 512 | 2,291.0 tokens/s | 179.5 tokens/s | |
| 25 | +| 1,024 | 2,209.7 tokens/s | 167.2 tokens/s | |
| 26 | +| 2,048 | 1,950.7 tokens/s | 152.9 tokens/s | |
| 27 | +| 4,096 | 1,598.4 tokens/s | 132.8 tokens/s | |
| 28 | +| 8,192 | 1,164.2 tokens/s | 96.2 tokens/s | |
23 | 29 |
|
24 | | -In review: |
| 30 | +These figures describe this artifact and device, not the performance of every |
| 31 | +model or hardware configuration. |
25 | 32 |
|
26 | | -| Milestone | Pull Request | |
| 33 | +## Optimization Techniques |
| 34 | + |
| 35 | +| Category | Optimizations | |
| 36 | +|---|---| |
| 37 | +| Prefill | Shared-memory tiled "Steel" GEMM with fp16 multiplication and fp32 accumulation, shape-routed kernel selection, scoped output suppression, and QKV fusion | |
| 38 | +| Decode | Cooperative GEMV with bi-column batching, FlashDecoding with split KV, and register-tiled SDPA | |
| 39 | +| SDPA | Register-tiled QK and AV, coalesced KV reads, causal-tile skipping, and aligned loads | |
| 40 | +| Quantization | Packed-word dequantization, 4-bit weight-only kernels, dynamic 8da4w quantization, and int8 q8ta kernels | |
| 41 | +| Memory | f16 KV cache and scratch-pool reuse | |
| 42 | +| Dispatch | Two-dimensional folded dispatch, runtime-configurable workgroup sizes, and SwiGLU fusion | |
| 43 | + |
| 44 | +## Demonstrated Tasks |
| 45 | + |
| 46 | +| Task | Capability | |
27 | 47 | |---|---| |
28 | | -| Adds 4-bit quantized embedding (`embedding_q4gsw`) | [#20263](https://github.com/pytorch/executorch/pull/20263) | |
29 | | -| Adds rotary position embedding / RoPE (`apply_rotary_emb`) | [#20264](https://github.com/pytorch/executorch/pull/20264) | |
30 | | -| Adds constant prepacking (`prepack`) for end-to-end model weight handling | [#20265](https://github.com/pytorch/executorch/pull/20265) | |
| 48 | +| On-device chat | Streaming local language-model generation | |
| 49 | +| Retrieval-augmented generation | Semantic retrieval and grounded generation over user documents | |
| 50 | +| Image segmentation | Prompted object grounding and segmentation | |
| 51 | +| Vision-language understanding | Image question answering and captioning | |
| 52 | +| Depth estimation | Per-pixel depth estimation from a single image | |
| 53 | +| Scene understanding | Scene classification and shoppable-product detection | |
| 54 | +| Object detection | Image object detection and labeling | |
| 55 | +| Super-resolution | 4x image upscaling | |
| 56 | +| Background removal | Portrait matting and background removal | |
| 57 | +| Speech-to-text | Microphone and uploaded-audio transcription | |
| 58 | +| On-device fine-tuning | GPU-resident adapter training followed by personalized generation | |
| 59 | + |
| 60 | +These rows describe demonstrated artifacts and workflows. The registered |
| 61 | +operator surface is broader, but registry coverage alone does not guarantee |
| 62 | +that an arbitrary model will run end to end. |
| 63 | + |
| 64 | +## Operator Support |
| 65 | + |
| 66 | +The backend registers more than 100 operator symbols. Representative groups |
| 67 | +are listed below; this is not an exhaustive registry listing. |
| 68 | + |
| 69 | +| Category | Representative operators | |
| 70 | +|---|---| |
| 71 | +| Quantized linear and embedding | `linear_q4gsw`, `embedding_q4gsw`, `linear_qcs4w`, `linear_dq8ca_q4gsw`, `choose_qparams_affine` | |
| 72 | +| Int8 quantized (q8ta) | `q8ta_linear`, `q8ta_conv2d`, `q8ta_add`, `q8ta_relu`, `q8ta_pixel_shuffle`, per-tensor quantize/dequantize | |
| 73 | +| Training primitives | `adamw_step`, `fused_ce`, `linear_q4gsw_backward`, `linear_dW`, `q4gsw_requant` | |
| 74 | +| Attention and position | `sdpa_with_kv_cache`, `update_cache`, `apply_rotary_emb` (default, interleaved, and Hugging Face layouts) | |
| 75 | +| Elementwise | `add`, `mul`, `sub`, `div`, `pow`, `minimum`, `sigmoid`, `relu`, `gelu`, `tanh`, `abs`, `neg`, `exp`, `sqrt`, `rsqrt`, `sin`, `cos`, `clamp` | |
| 76 | +| Comparison and boolean | `eq`, `ne`, `le`, `ge`, `lt`, `gt`, `where`, logical and bitwise operators | |
| 77 | +| Shape and memory | `view_copy`, `slice_copy`, `select_copy`, `cat`, `permute_copy`, `squeeze_copy`, `unsqueeze_copy`, `clone`, `expand_copy`, `fill`, `repeat`, `flip`, `pixel_shuffle` | |
| 78 | +| Reduction and normalization | `rms_norm`, `layer_norm`, `batch_norm`, `group_norm`, `softmax`, `log_softmax`, `sum`, `mean`, `argmax`, `argmin`, `amax`, `amin`, pooling | |
| 79 | +| Convolution and spatial | `conv1d`, `conv_with_clamp`, `grid_sampler_2d`, `grid_priors`, `upsample_bilinear2d` | |
| 80 | +| Matrix multiplication | `mm`, `bmm`, `linear` | |
| 81 | +| Indexing | `index.Tensor`, `gather`, `embedding`, `index_select` | |
| 82 | +| Infrastructure | `prepack`, `select_as_symint`, `_to_copy` | |
31 | 83 |
|
32 | 84 | ## Architecture |
33 | 85 |
|
34 | | -``` |
| 86 | +```text |
35 | 87 | PyTorch model |
36 | 88 | │ torch.export |
37 | 89 | ▼ |
38 | 90 | Exported Program |
39 | | - │ VulkanPartitioner (tags supported fp32 ops) |
| 91 | + │ VulkanPartitioner (tags supported ops) |
40 | 92 | ▼ |
41 | 93 | Edge Dialect IR |
42 | | - │ VulkanBackend.preprocess (builds Vulkan FlatBuffer, buffer-only storage) |
| 94 | + │ VulkanBackend.preprocess (builds a Vulkan FlatBuffer) |
43 | 95 | ▼ |
44 | 96 | .pte file (with VH00/VK00 delegate blob) |
45 | 97 | │ |
46 | | - ▼ |
47 | | -Native runtime (Dawn/Tint → Metal / Vulkan) |
48 | | - │ WebGPUGraph::build → creates GPU buffers, pipelines, bind groups |
49 | | - │ WebGPUGraph::execute → encodes + submits compute passes |
50 | | - ▼ |
51 | | -GPU output (mapped back to CPU via wgpuDevicePoll) |
| 98 | + ├── Native runtime (Dawn/Tint → Metal or Vulkan) |
| 99 | + │ └── WebGPUGraph::build → creates GPU buffers, pipelines, bind groups |
| 100 | + │ WebGPUGraph::execute → encodes and submits compute passes |
| 101 | + │ |
| 102 | + └── Browser runtime (Emscripten + emdawnwebgpu) |
| 103 | + └── Same graph execution path, compiled to WebAssembly |
52 | 104 | ``` |
53 | 105 |
|
54 | 106 | Key design choices: |
55 | | -- **Reuses Vulkan serialization** — the delegate blob is a Vulkan FlatBuffer (`VK00`) with a `VH00` header. All tensor storage is forced to `BUFFER` (WebGPU has no 3D storage textures). |
56 | | -- **Built-in WGSL shaders** — shader source is compiled as C++ string constants. Future work will embed fused shaders in the FlatBuffer for compile-time mega-kernel fusion. |
57 | | -- **No Python AOT code** — directly consumes .pte files exported via `VulkanPartitioner`. |
58 | | - |
59 | | -## Operator Support |
60 | 107 |
|
61 | | -| Operator | WGSL Shader | Notes | |
62 | | -|---|---|---| |
63 | | -| `aten.add.Tensor` | `binary_add.wgsl` | Element-wise with alpha: `out = in1 + alpha * in2` | |
64 | | -| `et_vk.rms_norm.default` | `rms_norm.wgsl` | Root-mean-square normalization | |
65 | | -| `sdpa_with_kv_cache.default` | `sdpa_compute_attn_weights.wgsl`, `sdpa_softmax.wgsl`, `sdpa_compute_out.wgsl` | Fused scaled-dot-product attention (QK / softmax / AV) with KV cache | |
66 | | -| `llama.update_cache.default` | `update_cache.wgsl` | In-place KV cache update for autoregressive decode | |
67 | | -| `et_vk.linear_q4gsw.default` | `q4gsw_linear.wgsl` | 4-bit weight-only quantized linear (dequantize + matmul) | |
68 | | - |
69 | | -**In review:** quantized embedding (`embedding_q4gsw`), rotary embedding (`apply_rotary_emb`), and constant prepacking (`prepack`). |
70 | | - |
71 | | -**Planned:** `mul`, `sigmoid`, shape ops (`view`, `permute`, `slice`, `select`, `cat`, `squeeze`/`unsqueeze`), and `index` — the remaining ops needed for end-to-end Llama 3.2 1B. |
72 | | - |
73 | | -## Quick Start |
74 | | - |
75 | | -### 1. Setup |
76 | | - |
77 | | -```bash |
78 | | -bash backends/webgpu/scripts/setup-wgpu-native.sh |
79 | | -``` |
80 | | - |
81 | | -This downloads prebuilt wgpu-native binaries for your platform. |
82 | | - |
83 | | -### 2. Export a model |
| 108 | +- **Reuses Vulkan serialization.** The delegate blob is a Vulkan FlatBuffer |
| 109 | + (`VK00`) with a `VH00` header. The WebGPU runtime ignores Vulkan texture |
| 110 | + storage annotations and allocates tensors as buffers. |
| 111 | +- **Built-in WGSL shaders.** Shader source is embedded as C++ string constants, |
| 112 | + with build-time code generation and drift validation. |
| 113 | +- **No Python AOT layer.** The backend directly consumes `.pte` files exported |
| 114 | + with `VulkanPartitioner`. |
| 115 | +- **Dynamic shapes.** Tensors allocate at their maximum shape, with SymInt |
| 116 | + arithmetic and per-operator resize hooks for runtime dimensions. |
| 117 | +- **Shape-routed dispatch.** Runtime tensor dimensions select specialized |
| 118 | + kernels, such as tiled GEMM for prefill and cooperative GEMV for decode. |
| 119 | + |
| 120 | +## Infrastructure and Testing |
| 121 | + |
| 122 | +- **CI:** Dawn/Tint and SwiftShader provide headless GPU execution on Linux/x86. |
| 123 | +- **Operator tests:** Python operator-test modules exercise export and |
| 124 | + delegation. The code-generated op-test catalog and native runners compare |
| 125 | + GPU output with eager-generated goldens. |
| 126 | +- **GPU profiling:** WebGPU timestamp queries provide per-kernel GPU timings on |
| 127 | + devices that expose the feature. |
| 128 | +- **Shader code generation:** `gen_wgsl_headers.py` generates embedded |
| 129 | + `*_wgsl.h` headers; source/header drift fails validation. |
| 130 | + |
| 131 | +## Linux Native Quick Start |
| 132 | + |
| 133 | +The `test_build_webgpu.sh` flow sources |
| 134 | +`.ci/scripts/setup-webgpu-linux-deps.sh` to install Dawn and SwiftShader |
| 135 | +prebuilts on Linux. On macOS, provide a configured Dawn installation instead |
| 136 | +of using this script. |
| 137 | + |
| 138 | +### 1. Export an illustrative model |
84 | 139 |
|
85 | 140 | ```python |
86 | 141 | import torch |
87 | | -from executorch.backends.vulkan import VulkanPartitioner |
| 142 | + |
| 143 | +from executorch.backends.vulkan.partitioner.vulkan_partitioner import ( |
| 144 | + VulkanPartitioner, |
| 145 | +) |
88 | 146 | from executorch.exir import to_edge_transform_and_lower |
89 | 147 |
|
90 | | -class AddModule(torch.nn.Module): |
91 | | - def forward(self, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor: |
92 | | - return a + b |
93 | 148 |
|
94 | | -ep = torch.export.export(AddModule(), (torch.randn(4, 4), torch.randn(4, 4))) |
| 149 | +class AddOne(torch.nn.Module): |
| 150 | + def forward(self, x: torch.Tensor) -> torch.Tensor: |
| 151 | + return x + 1.0 |
| 152 | + |
| 153 | + |
| 154 | +ep = torch.export.export(AddOne(), (torch.randn(4, 4),)) |
95 | 155 | et_program = to_edge_transform_and_lower( |
96 | 156 | ep, partitioner=[VulkanPartitioner()] |
97 | 157 | ).to_executorch() |
98 | 158 |
|
99 | | -with open("add.pte", "wb") as f: |
100 | | - f.write(et_program.buffer) |
| 159 | +with open("model.pte", "wb") as file: |
| 160 | + file.write(et_program.buffer) |
101 | 161 | ``` |
102 | 162 |
|
103 | | -### 3. Build and run |
| 163 | +This snippet demonstrates the export path. The validation script below exports |
| 164 | +and runs its own native reference models; it does not consume `model.pte`. |
| 165 | + |
| 166 | +### 2. Build and run native validation |
104 | 167 |
|
105 | 168 | ```bash |
106 | 169 | bash backends/webgpu/test/test_build_webgpu.sh |
107 | 170 | ``` |
108 | 171 |
|
109 | | -This runs Python export tests, exports a .pte, builds the native runtime, and validates GPU output. |
| 172 | +The script exports a `.pte`, builds the native runtime, and validates GPU |
| 173 | +output. |
110 | 174 |
|
111 | 175 | ## Directory Structure |
112 | 176 |
|
113 | | -``` |
| 177 | +```text |
114 | 178 | backends/webgpu/ |
115 | 179 | ├── CMakeLists.txt |
116 | 180 | ├── README.md |
117 | 181 | ├── runtime/ |
118 | | -│ ├── WebGPUBackend.h/cpp # BackendInterface (init/execute) |
119 | | -│ ├── WebGPUGraph.h/cpp # GPU graph: buffers, pipelines, dispatch |
120 | | -│ ├── WebGPUDelegateHeader.h/cpp # VH00 header parser |
121 | | -│ ├── WebGPUDevice.h/cpp # Dawn device abstraction |
122 | | -│ ├── WebGPUUtils.h # Workgroup-size helpers |
123 | | -│ └── ops/ |
124 | | -│ ├── OperatorRegistry.h/cpp # Op dispatch table |
| 182 | +│ ├── WebGPUBackend.h/cpp # BackendInterface (init/execute) |
| 183 | +│ ├── WebGPUGraph.h/cpp # GPU graph: buffers, pipelines, dispatch |
| 184 | +│ ├── WebGPUDelegateHeader.h/cpp # VH00 header parser |
| 185 | +│ ├── WebGPUDevice.h/cpp # Dawn device abstraction |
| 186 | +│ ├── WebGPUUtils.h # Workgroup-size helpers |
| 187 | +│ └── ops/ # Operator implementations |
| 188 | +│ ├── OperatorRegistry.h/cpp # Operator dispatch table |
125 | 189 | │ ├── add/ |
126 | | -│ │ ├── BinaryOp.cpp # aten.add.Tensor implementation |
127 | | -│ │ ├── binary_add.wgsl # WGSL shader source |
128 | | -│ │ └── binary_add_wgsl.h # Shader as C++ string constant |
129 | | -│ └── rms_norm/ |
130 | | -│ ├── RmsNorm.cpp # et_vk.rms_norm implementation |
131 | | -│ ├── rms_norm.wgsl # WGSL shader source |
132 | | -│ └── rms_norm_wgsl.h # Shader as C++ string constant |
| 190 | +│ │ ├── BinaryOp.cpp |
| 191 | +│ │ ├── binary_add.wgsl |
| 192 | +│ │ └── binary_add_wgsl.h |
| 193 | +│ └── ... |
133 | 194 | ├── scripts/ |
134 | | -│ ├── setup-wgpu-native.sh # Download wgpu-native binaries |
135 | | -│ └── gen_wgsl_headers.py # Generate the embedded *_wgsl.h shader headers |
| 195 | +│ ├── gen_wgsl_headers.py # Generate embedded WGSL headers |
| 196 | +│ └── test_webgpu_native_ci.sh # CI entry point for native tests |
136 | 197 | └── test/ |
137 | 198 | ├── conftest.py |
138 | | - ├── tester.py # Partitioner stages + supported-op list |
139 | | - ├── test_build_webgpu.sh # End-to-end build + test |
140 | | - ├── test_webgpu_native.cpp # C++ native test runner |
141 | | - ├── test_wgsl_codegen.py # Shader codegen check |
142 | | - ├── native/ # C++ operator tests |
143 | | - └── ops/ # Python op test suites (flat: test_<op>.py) |
144 | | - ├── test_add.py |
145 | | - ├── test_rms_norm.py |
146 | | - └── ... # one test_<op>.py per op |
| 199 | + ├── tester.py # Partitioner and supported-op list |
| 200 | + ├── test_build_webgpu.sh # End-to-end build and test |
| 201 | + ├── test_webgpu_native.cpp # Native C++ test runner |
| 202 | + ├── test_wgsl_codegen.py # Shader drift check |
| 203 | + ├── native/ # Native C++ operator tests |
| 204 | + ├── op_tests/ # Codegen case catalog and generator |
| 205 | + └── ops/ # Python operator-test modules |
147 | 206 | ``` |
148 | 207 |
|
149 | 208 | ## Requirements |
150 | 209 |
|
151 | | -- **macOS**: Metal-capable GPU |
152 | | -- **Linux**: Vulkan-capable GPU + drivers |
153 | | -- **Build**: CMake 3.19+, conda environment with ExecuTorch installed |
| 210 | +- **macOS:** Metal-capable GPU |
| 211 | +- **Linux:** Vulkan-capable GPU and drivers |
| 212 | +- **Browser:** A WebGPU-enabled browser; the benchmark harness uses Chrome |
| 213 | + Canary |
| 214 | +- **Build:** CMake 3.19+ and a Python environment with ExecuTorch installed |
0 commit comments