Repository navigation
[WebGPU] Add block-scaled FP8 QMoE - #32794
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Direct expert indexing and large-buffer bindings are incorrect, with additional validation, documentation, and decode-test gaps.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (6)
Double-offset expert-sliced BufferViews · New Missing uint8 validation for integer quantization · New Backing entries duplicate segmented buffer bindings · New Generated operator documentation is outdated · New Block-FP8 dimension documentation contradicts partial-tile support · New Missing coverage for indirect single-token expert dispatch · New
What changed in this PR
Adds WebGPU QMoE support for 128×128 block-scaled FP8 expert weights.
Changes:
- Adds FP8 validation, dispatch, and dequantization shader logic.
- Extends the QMoE schema and adds WebGPU coverage.
- Cleans up Engram gate declarations.
| File | Description |
|---|---|
onnxruntime/contrib_ops/webgpu/moe/qmoe.cc |
Implements block-FP8 execution paths. |
onnxruntime/contrib_ops/webgpu/moe/qmoe.h |
Adds FP8 mode configuration. |
onnxruntime/contrib_ops/webgpu/moe/block_fp8_expert_matmul.wgsl.template |
Adds the FP8 expert matmul shader. |
onnxruntime/core/graph/contrib_ops/contrib_defs.cc |
Documents the new QMoE contract. |
onnxruntime/test/providers/webgpu/contrib_ops/moe_test.cc |
Adds block-FP8 prefill coverage. |
onnxruntime/contrib_ops/webgpu/bert/engram_gate.h |
Removes obsolete and duplicate declarations. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
77b82df to
a56d082
Compare
Address review feedback on expert-relative views, integer weights, block-FP8 documentation and one-token coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align per-expert weight views to packed words and account for leading bytes in the shader. Test 3x3 expert boundaries and all four 128x128 scale-tile quadrants on WebGPU. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
a56d082 to
b4ec969
Compare
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
Review effort: Lite
Findings: 1
Open (3)
WhenDISABLE_FLOAT8_TYPESis defined, this function still returns WGSL declaring `array<u32,… · New The shader loadsscalefrom storage on every inner iteration even though it only changes once per… · New Building the FP8 dequant LUT as a large WGSL string viaostringstreamand a 256-iteration loop… · New
Resolved since last review (6)
Missing uint8 validation for integer quantization Double-offset expert-sliced BufferViews Backing entries duplicate segmented buffer bindings Missing coverage for indirect single-token expert dispatch Block-FP8 dimension documentation contradicts partial-tile support Generated operator documentation is outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit a valid LUT and reject block-FP8 QMoE explicitly in float8-disabled builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Test indirect selection and broadcast on one-token SwiGLU with per-expert FC1, FC2, and FC3 scales. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # onnxruntime/contrib_ops/webgpu/moe/qmoe.cc
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kunal-vaishnavi
left a comment
There was a problem hiding this comment.
From Copilot:
Beyond the already-raised FP32 coverage gap, could we add focused block-FP8 coverage for these execution combinations?
- Fused SwiGLU with
swiglu_fusion=1and2, distinct gate/up values and scales, and non-default alpha/beta/limit. - Multiple tokens routed to the same expert, so the new direct matmul actually executes with
rows > 1; include nonzero expert biases. - A rectangular scale grid with unequal N-block and K-block counts, such as 129x257.
- Top-k selection from more than k experts with unequal mixing weights, plus a single-token case with explicit
router_weightsto exercise the direct fallback.
The current successful multi-token FP8 fixtures assign one token to each expert, the scale-boundary fixture is square, and the k=2 fixtures select both experts with equal mixing weights. Also, the separate-FC3 test uses activation_type="silu", so it does not cover the fused SwiGLU layouts or the parameterized/clipped SwiGLU branch.
Preserve FP16 numerical coverage, exercise FP32 shader variants, and reject dispatch normalization and 32-bit expert-offset overflow before launch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align generated operator docs with the block-FP8 schema and cover orphan FC3 scale validation on WebGPU. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fail explicitly for all four optional activation-scale inputs and cover each input with a GPU-only regression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
CI follow-up for head |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary - Replace the single-token QMoE gate full expert bubble sort with a stable parallel TopK reduction for normalized routing. Preserve descending-logit/index-ascending ties, softmax weight order, and the existing full sort when routing weights are not normalized (the denominator then needs all experts). - Cover boundary ties in 3-, 5-, 6-, and 512-expert pools with nonzero expert outputs. Skip the 512-expert case when either adapter workgroup limit is below 512. - Assert the selected gate indices directly for FP16/FP32 negative-infinity logits (`[0, -inf, -inf]` selects `[0, 1]`) and for all-equal logits with `k == num_experts` (`[0, 1, 2, 3]`). The negative-infinity assertion fails with the original comparator (it selects the invalid sentinel index 3). - Exercise FP16/FP32 nonnormalized routing with an excluded expert contributing appreciably to the all-expert softmax denominator. ## Validation - On head `45037061757c`, merged `origin/main` at `9f6baac7319a` (including #33168) without rebasing or gate conflicts. Rebuilt both the static test-all and split provider-test targets; all 28 focused WebGPU MoE tests passed on H200, including FP16/FP32 gate-index readback. Checked free GPU memory before the run; fresh CI for this head is in progress. - Main-based static WebGPU build and all 22 focused WebGPU QMoE tests passed on H200 after merging `main` at `aac791ef2408a266d8ee59dd5fa0ce11d1a5a0bb`, including the new tie, gate-index, and nonnormalized-routing regressions. - Qwen3.8-Flash-Next INT4 RTN/INT8-head export, 512 experts, k=10, FP16 I/O, one-token chunks, H200: 16-token prefill 11.64 s -> 0.90 s and steady decode ~0.674 s -> ~0.045 s/token. Baseline versus optimized logits were bit-identical (248,320 logits), and the first five generated token IDs matched. This full-model comparison used a QSA-enabled experimental build carrying the same single-token gate change because the export also requires the not-yet-merged SparseAttentionIndexer kernel; the isolated main-based plugin cannot load that export today. The PR itself does not depend on the QSA changes. - This PR changes only the single-token gate. Separate GPU-only multi-token prefill experiments are intentionally excluded pending model-quality validation. ## Main updates - October 5: merged `main` and added the follow-up requested on merged #32878: two experts use distinct INT2/INT4 packed weights as well as scales, with matching FP16-aware numerical references. The fixture explicitly rejects unsupported 8-bit encodings. - October 6: merged `main` at `aac791ef2408a266d8ee59dd5fa0ce11d1a5a0bb`; added direct gate-index assertions, 5-/6-expert pool tests, adapter-limit skip, and the full-denominator regression. That head was superseded by the fixture alignment fix below. - October 6 follow-up: pad the direct gate-index test fixture buffers to 4-byte boundaries for odd FP16 expert counts. On the prior head, macOS Dawn rejected a six-byte storage buffer and an eight-byte staging copy. The prior-head Windows job reported a lost device at the same gate-index test (0 ms), followed by cascading failures; its log does not establish whether the fixture caused the device loss. - October 7 follow-up: current-head arm64 Debug reproduced a real test-fixture readback error: `BufferManager::Download` rounds the copy to 16 bytes, but the `k=2` indices buffer was only 8 bytes. Align that buffer to 16 bytes. Windows novcpkg lost the WebGPU device at the same gate-index test and subsequently failed 1,538 unrelated tests; the static vcpkg aggregate reported the dependent test failure despite successful compilation. All 28 focused WebGPU MoE tests pass after the fix on H200. - October 7 main refresh: merged `main` at `9f6baac7319a` (including #33168), rebuilt the split static WebGPU provider-test target, and passed all 28 focused H200 WebGPU MoE tests on head `45037061757c`. Prior-head CI failures do not describe this new head; current-head checks are running. ## October 8 main refresh Merged `origin/main` at `864d87126864c64651f58a9b51d9006ad71b2c78` (including #33203) into the gate branch, resolving the test include conflict by retaining both sets of includes. Updated the direct gate-buffer test to call upstream `WebGpuDevice(0)` after the context-ID API change. On head `813f7999e55de5a8d29a7a02dfc869d514f327b4`, the static WebGPU provider-test build succeeded and all 34 focused H200/Vulkan `MoETest.QMoETest_WebGPU*` cases passed. Fresh CI is queued; no new-head CI result is claimed yet. ## Matched synthetic whole-inference benchmark (October 8) H200/Vulkan ONNX Runtime `perf_test`, synthetic single-token block-FP8 QMoE with 256 experts and top-4 routing, three 100-run trial averages: merged #32794 baseline **7.09 ms/run** median-trial mean versus the integrated gate branch **5.56 ms/run** (~**1.28x**). Individual trial averages ranged **5.71–7.12 ms** for baseline and **5.46–5.89 ms** for the integrated branch; these overlapping ranges warrant a variance caveat. This measures whole inference, **not** the isolated gate kernel or a full checkpoint. The separate combined branch passed **52/52** focused tests; that result is not claimed as an isolated #33058-head test run. ## October 8 documentation follow-up Added [WebGPU QMoE single-token gate documentation](https://github.com/sayanshaw24/onnxruntime/blob/8d38e0e31be0216e7d3b937578efa113fccee4ca/docs/contrib_ops/webgpu/qmoe_gate.md) on head `8d38e0e31b`, covering normalized top-k, tie and -inf handling, nonnormalized fallback, adapter limits, and benchmark scope. The previous-head Windows documentation-build job stopped while compiling unrelated CUDA sources before document generation, so its dependent validation failure does not establish a generated-doc mismatch. The previous-head Android job failed during NDK setup with a corrupt downloaded archive. Fresh-head CI is running. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Sayan Shaw <sayanshaw@microsoft.com>
## Summary Follow up on #32794 with an optimized **routed expert** matmul for block-scaled FP8 QMoE. This is separate from the dense MatMul kernel in #33074. - Stage selected-expert E4M3 weights and FP16 activations into shared tiles and use supported 16x16x16 FP16-input subgroup-matrix instructions with FP32 accumulation. FP8 is decoded before the matrix instruction; this is **not native FP8 matrix hardware**. - Handle per-128x128 scales, signed FP8 codes, FC1/FC2 bias, odd-byte expert boundaries, and incomplete matrix/scale tiles. - Only dispatch the matrix path for direct expert batches with at least 128 rows and 256 input/output channels on adapters exposing the required subgroup configuration. Preserve the original scalar path for single-token decode, smaller routed groups, and other adapters. A 16-row synthetic case regressed with the matrix path, so it is deliberately excluded. ## Validation - Linux Release static WebGPU provider, provider tests, and Python WebGPU runtime built successfully. - All 17 focused WebGPU QMoE tests passed on NVIDIA H200/Vulkan, including a new 128-row, 257-channel case covering signed FP8 values, distinct 128x128 block scales, bias, tail tiles, and non-word-aligned expert offset. - Synthetic end-to-end QMoE with 128 rows, 2560 hidden channels, 640 intermediate channels, two experts, and 30 repeated calls: matrix path 4.4–5.1 ms/call versus original scalar 6.17 ms/call in this harness (about 1.2–1.4x). This includes routing and other kernel work; it is **not isolated matrix-kernel timing**. At 16 rows the original scalar path remains enabled (about 3.7 ms/call). - `clang-format --dry-run --Werror` and `git diff --check` passed. No official block-FP8 checkpoint or end-to-end model-quality claims: the local checkpoint is BF16. Stack base is #32794 for review; merge the PRs individually when ready. ## Stack update (October 5) Restacked onto #32794 after its separate-FC3 regression was added. The review diff remains limited to the three QMoE optimization files. All 18 focused stacked WebGPU QMoE tests passed on H200/Vulkan; CI is rerunning for the updated base. ## Current main update (October 5) Merged the latest #32794 base, which itself now includes current `main` and merged INT2/mixed-width QMoE support. The review diff remains the three tiled-FP8 files. All 15 focused H200 block-FP8 and merged INT2 compatibility tests pass; new-head CI is running. ## Review follow-up (October 6) - Apply FP32 block scales to FP32 subgroup-matrix partial sums, not FP16 staged weights; a +448/-448 cancellation test with scale 200 checks finite output. - Keep matrix workgroup dimensions within the adapter limit, falling back to scalar when its dispatch is legal; reject oversized scalar dispatch through the updated #32794 base. - Add mixed 128+4 row routing with distinct expert FC1/FC2/FC3 weights, scales, and nonzero biases, an independent SwiGLU reference, and an adapter-aware assertion that all three eligible projections actually dispatch the matrix path. - After merging #32794 head `40cebdeeef`, built the Linux Release provider tests and passed all 30 focused WebGPU QMoE tests on H200/Vulkan; formatting and `git diff --check` passed. This does not imply other platform CI has completed. ## October 6 base update Merged updated FP8 QMoE base 09737a3, inheriting orphan FC3-scale validation and synchronized operator docs that address the Windows GPU documentation failure (stacked head 23922ce). All 31 focused H200 WebGPU QMoE tests pass; fresh CI is pending. Previously drafted replies to three review threads have now been published. ## October 7 base update Merged updated #32794 base eb51a28 to inherit explicit rejection of unused FP8 activation-scale inputs. The new four-case regression and all 32 focused H200 WebGPU QMoE tests pass on stacked head 158c7d0. Fresh CI is pending. ## October 7 CI stack update Merged current main and updated #32794 (541e5c7), including the already-merged upstream CUDA GQA `is_int8` build fix #33179. The static WebGPU provider tests build and all 32 focused H200 routed-QMoE tests pass. New-head CI is pending. ## October 7 upstream CI fix merged Restacked on updated #32794 at 79873ae, including merged upstream #33168 ONNX node-test type conversion. Static WebGPU provider tests build and all 32 focused H200 routed-QMoE cases pass. Fresh CI is pending; previous-head failures are not evidence for this head. ## October 8 merged-base refresh and paired performance Merged current main after #32794 landed. The review diff remains the QMoE matrix-path implementation and its tests. In an integrated branch with #33058 and #33074, 52/52 H200 WebGPU QMoE and dense FP8 regressions passed. For a synthetic FP16-input, 2-expert block-FP8 QMoE (128 tokens, hidden 2560, intermediate 640), ONNX Runtime perf_test on H200/Vulkan measured 7.86 ms/run on the merged #32794 implementation versus 6.05 ms/run with the tiled path (median of three 100-run trial averages, ~1.30x). These are whole-session inference latencies, not isolated kernel timings or an official-FP8-checkpoint result. Current-head CI is pending. ## October 9 main refresh and subgroup ownership fix Merged main at 9a6089e, including merged #33058 and its gate tests/documentation. The PR now targets the merged FP8 QMoE implementation on main; #32794 is no longer an unmerged stack prerequisite. Addressed the new subgroup-ownership review in 7567ecd: the shared shader entry point exposes the actual `@builtin(subgroup_id)` as `subgroup_index`, and the QMoE matrix path uses it for accumulator initialization, left-operand loading, and result storage. Cooperative tile transfers still use local invocation indices. This matches the dense FP8 implementation in #33074. Validation on the updated head: - Linux Release WebGPU provider-test build succeeded. - 38/38 focused H200/Vulkan WebGPU QMoE tests passed, including the matrix-capability-aware mixed-routing/separate-FC3 regression and the merged gate cases. - 89/89 WGSL-template tests passed. - Captured GPU shaders use the subgroup-ID builtin for all three matrix tile offsets; no `lane / 32u` offset remains. - `clang-format --dry-run --Werror` and `git diff --check` passed. The subgroup-mapping fix was executed on H200, not an adapter with a noncontiguous subgroup mapping. Fresh GitHub CI is pending; the local results are not a claim that all platform CI has completed. --------- Co-authored-by: Sayan Shaw <sayanshaw@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>



Summary
Adds direct WebGPU W8A16 support for QMoE expert weights stored as FP8 E4M3 with 128x128 FP32 block scales, matching the Qwen 3.8 Flash Next MTP expert checkpoint format without requantization or dense weight materialization.
Changes
quant_type="fp8"QMoE mode:block_size=128consumesfc*_scalesshaped[E, ceil(N/128), ceil(K/128)]; omitted block size retains the existing CUDA global-scale FP8 contract.Historical validation (initial Windows run)
onnxruntime_providers_webgpubuilds successfully.onnxruntime_provider_testbuilds successfully.MoETest.QMoETest_WebGPU_BlockFp8_128x128passes locally on D3D12.clang-format --dry-run --Werrorandgit diff --checkpass.Target / follow-up
maindirectly; the WebGPU MoE/QMoE prerequisite from Add WebGPU MoE and expand QMoE feature support #32447 is merged. This PR's review diff is limited to six FP8-related files, independent of the Qwen integration branch.Historical validation (earlier main restack)
main.MoETest.QMoETest_WebGPU_*tests passed on NVIDIA H200/Vulkan, including single-token decode, a 3x3 non-word-aligned expert-boundary case, a 129x129 four-scale-tile case, and negative integer-weight validation.clang-format --dry-run --Werrorandgit diff --checkpassed.Historical review follow-up (October 5)
Added a separate-FC3 block-FP8 single-token test covering k=2 indirect expert routing, broadcast input, SwiGLU, and distinct per-expert FC1/FC2/FC3 scales. All 17 focused QMoE WebGPU tests passed on H200/Vulkan. All review threads have replies and are resolved; CI is rerunning for the new commit.
Historical main update (October 5)
Merged current
mainafter #32878 landed and reconciled block-FP8 routing with its INT2/mixed-width QMoE logic. The review diff remains limited to the FP8 QMoE files. An isolated Linux static WebGPU provider/test build and all 14 focused block-FP8 plus INT2 compatibility tests passed on H200; the stacked tiled follow-up also passed 15 focused H200 tests. New-head CI is running.Latest validation (October 6,
40cebdeeef)mainataac791ef2408, resolved the QMoE schema/documentation conflicts, and confirmed GitHub reports this head as mergeable.onnxruntime_provider_testbuild passed; all 26 focusedMoETest.QMoETest_WebGPU_*tests passed on NVIDIA H200/Vulkan. The numerical block-FP8 reference now covers both FP16 and FP32 inputs/outputs, including FP8 decoding, accumulation, and biases.October 6 follow-up
Reject orphan block-FP8 FC3 scales instead of silently ignoring them (09737a3); 27 focused H200 WebGPU QMoE tests pass. Refreshed generated ContribOperators.md to match the schema and address Windows GPU documentation CI. Fresh CI is pending.
October 7 review follow-up
Reject all four optional activation-scale inputs (17–20) in WebGPU block-FP8 mode instead of silently ignoring them. The GPU-only negative regression supplies each input separately. Updated the schema and generated operator docs to state that activation scales are not accepted. All 28 focused H200 WebGPU QMoE tests pass on eb51a28; new-head CI is pending.
October 7 CI follow-up
Merged current main (6030cc6), including upstream CUDA GQA fix #33179: the previous Linux/Windows CUDA and TensorRT builds failed because
is_int8was used before its declaration in upstream CUDA code, not in this PR's WebGPU changes. The updated Linux static WebGPU provider test builds and all 28 focused H200 QMoE tests pass. Previous-head Python 3.14 and Windows QNN jobs failed ONNX materialized-node tests; Android minimal binary size exceeded its threshold by 1,494 bytes. New-head CI is running; these failures are not yet claimed fixed until the fresh checks report.October 7 upstream CI fix merged
#33168 merged to main and corrects the ONNX wire-type to C-API-type conversion behind the old-head Windows QNN/Python 3.14 materialized-node failures. Merged main at fdf7f9d; static WebGPU tests build and all 28 focused H200 QMoE cases pass. The old-head Android binary-size check passed. New-head CI is running; no claim of CI green until the fresh checks finish.