Xenova HF Staff commited on
Commit
a56da48
·
verified ·
1 Parent(s): 3626d99

sync 91d990483a17

Browse files
README.md CHANGED
@@ -12,26 +12,26 @@ tags:
12
 
13
  ## Description
14
 
15
- Quantized mixture of experts over float32 activations and raw uint8-packed integer expert weights. The package implements 4- and 8-bit symmetric dequantization, ReLU, and interleaved SwiGLU for rank-2 or rank-3 input. Routing uses the softmax over the selected top-k logits, matching ONNX Runtime QMoE when the omitted `router_weights` input is absent. Biases, explicit zero points, FC3, sparse mixing, provider-prepacked layouts, and FP4/FP8 modes are not implemented.
16
 
17
  See the [ONNX Runtime `QMoE` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QMoE) for the reference semantics.
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `input` | `inputT` | `T` | — | — | Token activations of shape `(num_tokens, hidden_size)` or `(batch_size, sequence_length, hidden_size)`. | required |
24
- | `router_probs` | `routerT` | `T` | `2` | — | Routing logits of shape `(num_tokens, num_experts)`, where `num_tokens` is the product of the leading input dimensions. | required |
25
- | `fc1_experts_weights` | `fc1T` | `T1` | `3` | — | Raw packed FC1 weights of shape `(num_experts, fusion_size * inter_size, hidden_size / pack_size)`, where `fusion_size` is 2 only for interleaved SwiGLU and `pack_size` is 2 for 4-bit or 1 for 8-bit weights. | required |
26
- | `fc1_scales` | `fc1ScalesT` | `T2` | — | — | Required FC1 dequantization scales: rank 2 `(num_experts, fusion_size * inter_size)` for column-wise quantization, or rank 3 with a trailing `hidden_size / block_size` axis. | required |
27
- | `fc2_experts_weights` | `fc2T` | `T1` | `3` | — | Raw packed FC2 weights of shape `(num_experts, hidden_size, inter_size / pack_size)`. | required |
28
- | `fc2_scales` | `fc2ScalesT` | `T2` | — | — | Required FC2 dequantization scales: rank 2 `(num_experts, hidden_size)` for column-wise quantization, or rank 3 with a trailing `inter_size / block_size` axis. | required |
29
 
30
  ## Outputs
31
 
32
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
33
  | --- | --- | --- | --- | --- | --- | --- |
34
- | `output` | `outputT` | `T` | same as `input` | same as `input` | Routed expert output with the same shape as `input`. | required |
35
 
36
  ## Attributes
37
 
@@ -42,15 +42,15 @@ Attributes and default values (overridable per request):
42
  | `activation_alpha` | `1` | Alpha used by SwiGLU; the exact standard default is 1. |
43
  | `activation_beta` | `0` | Beta added to the SwiGLU linear branch; the exact standard default is 0. |
44
  | `activation_type` | `"relu"` | Activation applied after FC1. This package supports `relu` and `swiglu`; the exact standard default is `relu`. |
 
45
  | `expert_weight_bits` | `4` | Integer expert-weight bit width. This package supports 4 and 8; the exact standard default is 4. |
46
  | `k` | `1` | Number of experts selected per token; the exact standard default is 1. |
47
- | `normalize_routing_weights` | `0` | Accepted values are 0 and 1. With the separate `router_weights` input omitted, ONNX Runtime QMoE applies a softmax over the selected top-k logits for either value; the attribute only distinguishes the unsupported separate-weight path. |
48
  | `quant_type` | `"int"` | Quantization family. This package supports only the exact standard default `int`. |
49
  | `swiglu_fusion` | `0` | SwiGLU packing mode. ReLU uses the exact standard default 0; supported SwiGLU interleaves gate/up FC1 rows with value 1. |
 
50
  | `use_sparse_mixer` | `0` | Whether to use sparse-mixer routing. The exact standard default and only supported value is 0. |
51
  | `weights_prepacked` | `-1` | Provider weight-layout selector. Values -1 and 0 both consume the public raw packed tensor layout; provider-specific prepacked value 1 is not portable and is rejected. |
52
- | `block_size` | — | Optional quantization block size along the reduction dimension. Omission selects column-wise scaling; a supplied value must be a power of two of at least 16 and divide both `hidden_size` and `inter_size`. |
53
- | `swiglu_limit` | — | Optional SwiGLU clamp limit. Omission means no finite clamp. |
54
 
55
  ## Type constraints
56
 
@@ -60,13 +60,19 @@ Attributes and default values (overridable per request):
60
  | `T1` | `uint8` |
61
  | `T2` | `float32` |
62
 
 
 
 
 
 
 
63
  ## Device requirements
64
 
65
  Some implementation variants require `subgroup-matrix` and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
66
 
67
  ## Files
68
 
69
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
70
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
71
  - [`test.json`](build/webgpu/test.json) — correctness cases
72
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -85,10 +91,14 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
85
 
86
  ## Use with `@huggingface/kernels`
87
 
88
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
89
- It then allocates the result tensors automatically.
 
 
 
90
 
91
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
92
 
93
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
94
 
 
12
 
13
  ## Description
14
 
15
+ Quantized mixture of experts over float32 activations and raw uint8-packed integer expert weights. The package implements 4- and 8-bit symmetric dequantization, ReLU, and interleaved SwiGLU for rank-2 or rank-3 input. Routing uses the softmax over the selected top-k logits with `router_weights` absent. Biases, explicit zero points, FC3, sparse mixing, provider-prepacked layouts, and FP4/FP8 modes are not implemented.
16
 
17
  See the [ONNX Runtime `QMoE` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QMoE) for the reference semantics.
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `inputT` | `input` | `T` | — | — | Token activations of shape `(num_tokens, hidden_size)` or `(batch_size, sequence_length, hidden_size)`. | required |
24
+ | `routerT` | `router_probs` | `T` | `2` | — | Routing logits of shape `(num_tokens, num_experts)`, where `num_tokens` is the product of the leading input dimensions. | required |
25
+ | `fc1T` | `fc1_experts_weights` | `T1` | `3` | — | Raw packed FC1 weights of shape `(num_experts, fusion_size * inter_size, hidden_size / pack_size)`, where `fusion_size` is 2 only for interleaved SwiGLU and `pack_size` is 2 for 4-bit or 1 for 8-bit weights. | required |
26
+ | `fc1ScalesT` | `fc1_scales` | `T2` | — | — | Required FC1 dequantization scales: rank 2 `(num_experts, fusion_size * inter_size)` for column-wise quantization, or rank 3 with a trailing `hidden_size / block_size` axis. | required |
27
+ | `fc2T` | `fc2_experts_weights` | `T1` | `3` | — | Raw packed FC2 weights of shape `(num_experts, hidden_size, inter_size / pack_size)`. | required |
28
+ | `fc2ScalesT` | `fc2_scales` | `T2` | — | — | Required FC2 dequantization scales: rank 2 `(num_experts, hidden_size)` for column-wise quantization, or rank 3 with a trailing `inter_size / block_size` axis. | required |
29
 
30
  ## Outputs
31
 
32
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
33
  | --- | --- | --- | --- | --- | --- | --- |
34
+ | `outputT` | `output` | `T` | same as `inputT` | same as `inputT` | Routed expert output with the same shape as `input`. | required |
35
 
36
  ## Attributes
37
 
 
42
  | `activation_alpha` | `1` | Alpha used by SwiGLU; the exact standard default is 1. |
43
  | `activation_beta` | `0` | Beta added to the SwiGLU linear branch; the exact standard default is 0. |
44
  | `activation_type` | `"relu"` | Activation applied after FC1. This package supports `relu` and `swiglu`; the exact standard default is `relu`. |
45
+ | `block_size` | — | Optional quantization block size along the reduction dimension. Omission selects column-wise scaling; a supplied value must be a power of two of at least 16 and divide both `hidden_size` and `inter_size`. |
46
  | `expert_weight_bits` | `4` | Integer expert-weight bit width. This package supports 4 and 8; the exact standard default is 4. |
47
  | `k` | `1` | Number of experts selected per token; the exact standard default is 1. |
48
+ | `normalize_routing_weights` | `0` | Accepted values are 0 and 1. With the separate `router_weights` input omitted, routing applies a softmax over the selected top-k logits for either value; the attribute only distinguishes the unsupported separate-weight path. |
49
  | `quant_type` | `"int"` | Quantization family. This package supports only the exact standard default `int`. |
50
  | `swiglu_fusion` | `0` | SwiGLU packing mode. ReLU uses the exact standard default 0; supported SwiGLU interleaves gate/up FC1 rows with value 1. |
51
+ | `swiglu_limit` | — | Optional SwiGLU clamp limit. Omission means no finite clamp. |
52
  | `use_sparse_mixer` | `0` | Whether to use sparse-mixer routing. The exact standard default and only supported value is 0. |
53
  | `weights_prepacked` | `-1` | Provider weight-layout selector. Values -1 and 0 both consume the public raw packed tensor layout; provider-specific prepacked value 1 is not portable and is rejected. |
 
 
54
 
55
  ## Type constraints
56
 
 
60
  | `T1` | `uint8` |
61
  | `T2` | `float32` |
62
 
63
+ ## Implementation variants
64
+
65
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
66
+
67
+ - `quant_grouped_sgmat_routed` — Expert-grouped f32 matrix multiplication with per-invocation routing slots and reuse of the input tile for output publication. The larger operand/output footprint determines workgroup storage admission from device limits.
68
+
69
  ## Device requirements
70
 
71
  Some implementation variants require `subgroup-matrix` and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
72
 
73
  ## Files
74
 
75
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
76
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
77
  - [`test.json`](build/webgpu/test.json) — correctness cases
78
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
91
 
92
  ## Use with `@huggingface/kernels`
93
 
94
+ ```sh
95
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
96
+ ```
97
+
98
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
99
 
100
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
101
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
102
 
103
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
104
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "com.microsoft.QMoE",
3
  "tunableSpace": {
4
  "workgroupSize": [64, 128, 256],
5
  "groupThreads": [8, 16],
@@ -109,7 +108,7 @@
109
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
110
  },
111
  "provenance": {
112
- "notes": "Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
113
  }
114
  },
115
  {
@@ -144,7 +143,7 @@
144
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
145
  },
146
  "provenance": {
147
- "notes": "Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
148
  }
149
  },
150
  {
@@ -180,7 +179,7 @@
180
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
181
  },
182
  "provenance": {
183
- "notes": "Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
184
  }
185
  },
186
  {
@@ -215,7 +214,7 @@
215
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
216
  },
217
  "provenance": {
218
- "notes": "Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
219
  }
220
  },
221
  {
@@ -249,7 +248,7 @@
249
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 2 * args.hidden * args.inter * args.bits / 2" }]
250
  },
251
  "provenance": {
252
- "notes": "Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
253
  }
254
  },
255
  {
@@ -419,7 +418,7 @@
419
  "name": "qmoe-q4-qwen3-moe-decode-t1-h2048-e64-k8-i768",
420
  "preset": "model",
421
  "provenance": {
422
- "notes": "Qwen3-MoE class defaults (hidden_size 2048, moe_intermediate_size 768, num_experts_per_tok 8); expert count capped at 64 of the config's 128 to keep each weight tensor under 512 MB. Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
423
  },
424
  "vars": { "tokens": 1, "hidden": 2048, "experts": 64, "inter": 768, "topK": 8, "bits": 4 },
425
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 8 },
@@ -489,7 +488,7 @@
489
  "name": "qmoe-q4-olmoe-decode-t1-h2048-e32-k8-i2048",
490
  "preset": "model",
491
  "provenance": {
492
- "notes": "OLMoE class defaults (hidden_size 2048, intermediate_size 2048, num_experts_per_tok 8); expert count capped at 32 of the config's 64. Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
493
  },
494
  "vars": { "tokens": 1, "hidden": 2048, "experts": 32, "inter": 2048, "topK": 8, "bits": 4 },
495
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 8 },
@@ -557,7 +556,7 @@
557
  "name": "qmoe-q4-gpt-oss-decode-t1-h2880-e16-k4-i2880",
558
  "preset": "model",
559
  "provenance": {
560
- "notes": "GPT-OSS class defaults (hidden_size 2880, intermediate_size 2880, num_experts_per_tok 4); expert count capped at 16 of the config's 128. Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
561
  },
562
  "vars": { "tokens": 1, "hidden": 2880, "experts": 16, "inter": 2880, "topK": 4, "bits": 4 },
563
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 4 },
@@ -625,7 +624,7 @@
625
  "name": "qmoe-q4-phimoe-decode-t1-h4096-e4-k2-i6400",
626
  "preset": "model",
627
  "provenance": {
628
- "notes": "Phi-MoE class defaults (hidden_size 4096, intermediate_size 6400, num_experts_per_tok 2); expert count capped at 4 of the config's 16 -- the widest expert in this set, so it is the case that stresses the reduction rather than the routing. Scored as bandwidth, not gflops: a decode step reads whole expert weight matrices to produce one output row, so memory is its roofline and the compute peak is the wrong denominator. The prefill cases stay on gflops. The bytes are GPU storage, not the packed blob: a uint8 tensor takes one byte per u32 element here, so a b-bit code costs b/2 bytes of traffic."
629
  },
630
  "vars": { "tokens": 1, "hidden": 4096, "experts": 4, "inter": 6400, "topK": 2, "bits": 4 },
631
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 2 },
 
1
  {
 
2
  "tunableSpace": {
3
  "workgroupSize": [64, 128, 256],
4
  "groupThreads": [8, 16],
 
108
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
109
  },
110
  "provenance": {
111
+ "notes": "The bandwidth metric counts the selected experts' three quantized weight matrices at `bits/2` bytes per logical weight in the bound storage layout."
112
  }
113
  },
114
  {
 
143
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
144
  },
145
  "provenance": {
146
+ "notes": "The bandwidth metric counts the selected experts' three quantized weight matrices at `bits/2` bytes per logical weight in the bound storage layout."
147
  }
148
  },
149
  {
 
179
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
180
  },
181
  "provenance": {
182
+ "notes": "The bandwidth metric counts the selected experts' three quantized weight matrices at `bits/2` bytes per logical weight in the bound storage layout."
183
  }
184
  },
185
  {
 
214
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 3 * args.hidden * args.inter * args.bits / 2" }]
215
  },
216
  "provenance": {
217
+ "notes": "The bandwidth metric counts the selected experts' three quantized weight matrices at `bits/2` bytes per logical weight in the bound storage layout."
218
  }
219
  },
220
  {
 
248
  "metrics": [{ "type": "bandwidth", "value": "args.topK * 2 * args.hidden * args.inter * args.bits / 2" }]
249
  },
250
  "provenance": {
251
+ "notes": "The bandwidth metric counts the selected expert's two quantized weight matrices at `bits/2` bytes per logical weight in the bound storage layout."
252
  }
253
  },
254
  {
 
418
  "name": "qmoe-q4-qwen3-moe-decode-t1-h2048-e64-k8-i768",
419
  "preset": "model",
420
  "provenance": {
421
+ "notes": "Qwen3-MoE geometry uses hidden size 2,048, intermediate size 768, top-k 8, and 64 instantiated experts to keep each weight tensor below 512 MB. The bandwidth metric counts three selected quantized matrices at `bits/2` bytes per logical weight."
422
  },
423
  "vars": { "tokens": 1, "hidden": 2048, "experts": 64, "inter": 768, "topK": 8, "bits": 4 },
424
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 8 },
 
488
  "name": "qmoe-q4-olmoe-decode-t1-h2048-e32-k8-i2048",
489
  "preset": "model",
490
  "provenance": {
491
+ "notes": "OLMoE geometry uses hidden and intermediate sizes of 2,048, top-k 8, and 32 instantiated experts. The bandwidth metric counts three selected quantized matrices at `bits/2` bytes per logical weight."
492
  },
493
  "vars": { "tokens": 1, "hidden": 2048, "experts": 32, "inter": 2048, "topK": 8, "bits": 4 },
494
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 8 },
 
556
  "name": "qmoe-q4-gpt-oss-decode-t1-h2880-e16-k4-i2880",
557
  "preset": "model",
558
  "provenance": {
559
+ "notes": "GPT-OSS geometry uses hidden and intermediate sizes of 2,880, top-k 4, and 16 instantiated experts. The bandwidth metric counts three selected quantized matrices at `bits/2` bytes per logical weight."
560
  },
561
  "vars": { "tokens": 1, "hidden": 2880, "experts": 16, "inter": 2880, "topK": 4, "bits": 4 },
562
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 4 },
 
624
  "name": "qmoe-q4-phimoe-decode-t1-h4096-e4-k2-i6400",
625
  "preset": "model",
626
  "provenance": {
627
+ "notes": "Phi-MoE geometry uses hidden size 4,096, intermediate size 6,400, top-k 2, and four instantiated experts. The bandwidth metric counts three selected quantized matrices at `bits/2` bytes per logical weight."
628
  },
629
  "vars": { "tokens": 1, "hidden": 4096, "experts": 4, "inter": 6400, "topK": 2, "bits": 4 },
630
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1, "k": 2 },
build/webgpu/manifest.json CHANGED
@@ -2,75 +2,28 @@
2
  "domain": "com.microsoft",
3
  "name": "QMoE",
4
  "sinceVersion": 1,
5
- "description": "Quantized mixture of experts over float32 activations and raw uint8-packed integer expert weights. The package implements 4- and 8-bit symmetric dequantization, ReLU, and interleaved SwiGLU for rank-2 or rank-3 input. Routing uses the softmax over the selected top-k logits, matching ONNX Runtime QMoE when the omitted `router_weights` input is absent. Biases, explicit zero points, FC3, sparse mixing, provider-prepacked layouts, and FP4/FP8 modes are not implemented.",
6
- "inputs": [
7
- {
8
- "role": "input",
9
- "dtype": "T",
10
- "description": "Token activations of shape `(num_tokens, hidden_size)` or `(batch_size, sequence_length, hidden_size)`."
11
- },
12
- {
13
- "role": "router_probs",
14
- "dtype": "T",
15
- "rank": 2,
16
- "description": "Routing logits of shape `(num_tokens, num_experts)`, where `num_tokens` is the product of the leading input dimensions."
17
- },
18
- {
19
- "role": "fc1_experts_weights",
20
- "dtype": "T1",
21
- "rank": 3,
22
- "description": "Raw packed FC1 weights of shape `(num_experts, fusion_size * inter_size, hidden_size / pack_size)`, where `fusion_size` is 2 only for interleaved SwiGLU and `pack_size` is 2 for 4-bit or 1 for 8-bit weights."
23
- },
24
- {
25
- "role": "fc1_scales",
26
- "dtype": "T2",
27
- "description": "Required FC1 dequantization scales: rank 2 `(num_experts, fusion_size * inter_size)` for column-wise quantization, or rank 3 with a trailing `hidden_size / block_size` axis."
28
- },
29
- {
30
- "role": "fc2_experts_weights",
31
- "dtype": "T1",
32
- "rank": 3,
33
- "description": "Raw packed FC2 weights of shape `(num_experts, hidden_size, inter_size / pack_size)`."
34
- },
35
- {
36
- "role": "fc2_scales",
37
- "dtype": "T2",
38
- "description": "Required FC2 dequantization scales: rank 2 `(num_experts, hidden_size)` for column-wise quantization, or rank 3 with a trailing `inter_size / block_size` axis."
39
- }
40
- ],
41
- "outputs": [
42
- {
43
- "role": "output",
44
- "dtype": "T",
45
- "shape": "shapes.input",
46
- "description": "Routed expert output with the same shape as `input`."
47
- }
48
- ],
49
- "attributes": {
50
- "activation_alpha": 1,
51
- "activation_beta": 0,
52
- "activation_type": "relu",
53
- "expert_weight_bits": 4,
54
- "k": 1,
55
- "normalize_routing_weights": 0,
56
- "quant_type": "int",
57
- "swiglu_fusion": 0,
58
- "use_sparse_mixer": 0,
59
- "weights_prepacked": -1
60
  },
61
- "attributeDescriptions": {
62
- "activation_alpha": "Alpha used by SwiGLU; the exact standard default is 1.",
63
- "activation_beta": "Beta added to the SwiGLU linear branch; the exact standard default is 0.",
64
- "activation_type": "Activation applied after FC1. This package supports `relu` and `swiglu`; the exact standard default is `relu`.",
65
- "block_size": "Optional quantization block size along the reduction dimension. Omission selects column-wise scaling; a supplied value must be a power of two of at least 16 and divide both `hidden_size` and `inter_size`.",
66
- "expert_weight_bits": "Integer expert-weight bit width. This package supports 4 and 8; the exact standard default is 4.",
67
- "k": "Number of experts selected per token; the exact standard default is 1.",
68
- "normalize_routing_weights": "Accepted values are 0 and 1. With the separate `router_weights` input omitted, ONNX Runtime QMoE applies a softmax over the selected top-k logits for either value; the attribute only distinguishes the unsupported separate-weight path.",
69
- "quant_type": "Quantization family. This package supports only the exact standard default `int`.",
70
- "swiglu_fusion": "SwiGLU packing mode. ReLU uses the exact standard default 0; supported SwiGLU interleaves gate/up FC1 rows with value 1.",
71
- "swiglu_limit": "Optional SwiGLU clamp limit. Omission means no finite clamp.",
72
- "use_sparse_mixer": "Whether to use sparse-mixer routing. The exact standard default and only supported value is 0.",
73
- "weights_prepacked": "Provider weight-layout selector. Values -1 and 0 both consume the public raw packed tensor layout; provider-specific prepacked value 1 is not portable and is rejected."
 
74
  },
75
  "attributeConstraints": {
76
  "activation_type": { "values": ["relu", "swiglu"] },
@@ -82,25 +35,16 @@
82
  "weights_prepacked": { "values": [-1, 0] }
83
  },
84
  "typeConstraints": { "T": ["float32"], "T1": ["uint8"], "T2": ["float32"] },
85
- "args": {
86
- "inputT": { "kind": "tensor", "semantic": "input", "role": "input" },
87
- "routerT": { "kind": "tensor", "semantic": "router_probs", "role": "input" },
88
- "fc1T": { "kind": "tensor", "semantic": "fc1_experts_weights", "role": "input" },
89
- "fc1ScalesT": { "kind": "tensor", "semantic": "fc1_scales", "role": "input" },
90
- "fc2T": { "kind": "tensor", "semantic": "fc2_experts_weights", "role": "input" },
91
- "fc2ScalesT": { "kind": "tensor", "semantic": "fc2_scales", "role": "input" },
92
- "outputT": { "kind": "tensor", "semantic": "output", "role": "output" }
93
- },
94
  "tunables": {
95
- "workgroupSize": 64,
96
- "decodeLanes": 32,
97
- "decodeBlockTarget": 1024,
98
- "decodeMinLaneTrips": 4,
99
- "groupThreads": 8,
100
- "groupRegM": 4,
101
- "groupRegN": 4,
102
- "groupTileK": 16,
103
- "groupRouteWorkgroup": 256
104
  },
105
  "derive": {
106
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
@@ -108,28 +52,28 @@
108
  "canPinSubgroupSize32": "device.features.has(\"subgroups\") and device.features.has(\"subgroup-size-control\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize <= 32 and device.adapterInfo.subgroupMaxSize >= 32",
109
  "pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
110
  "wave32Effective": "wave32Adapter or pinSubgroupSize32",
111
- "hiddenSize": "dim(shapes.input, ranks.input - 1)",
112
- "numTokens": "numel(shapes.input) / max(1, hiddenSize)",
113
  "topK": "attrs.k",
114
  "weightBits": "attrs.expert_weight_bits",
115
  "quantBlockSize": "attrs.block_size if has(attrs, \"block_size\") else 0",
116
  "packSize": "2 if weightBits == 4 else 1",
117
  "quantMidpoint": "8 if weightBits == 4 else 128",
118
  "fusionSize": "2 if attrs.activation_type == \"swiglu\" and attrs.swiglu_fusion == 1 else 1",
119
- "interSize": "dim(shapes.fc1_experts_weights, 1) / fusionSize",
120
- "fc1PackedCols": "dim(shapes.fc1_experts_weights, 2)",
121
- "fc2PackedCols": "dim(shapes.fc2_experts_weights, 2)",
122
  "colWiseScales": "quantBlockSize == 0",
123
  "fc1ScaleBlocks": "1 if colWiseScales else hiddenSize / max(1, quantBlockSize)",
124
  "fc2ScaleBlocks": "1 if colWiseScales else interSize / max(1, quantBlockSize)",
125
  "activationSupported": "(attrs.activation_type == \"relu\" and attrs.swiglu_fusion == 0) or (attrs.activation_type == \"swiglu\" and attrs.swiglu_fusion == 1)",
126
  "routingModeSupported": "attrs.normalize_routing_weights == 0 or attrs.normalize_routing_weights == 1",
127
  "rawWeightLayout": "attrs.weights_prepacked == -1 or attrs.weights_prepacked == 0",
128
- "inputOutputShapeOk": "((ranks.input == 2 and ranks.output == 2 and dim(shapes.output, 0) == dim(shapes.input, 0) and dim(shapes.output, 1) == dim(shapes.input, 1)) or (ranks.input == 3 and ranks.output == 3 and dim(shapes.output, 0) == dim(shapes.input, 0) and dim(shapes.output, 1) == dim(shapes.input, 1) and dim(shapes.output, 2) == dim(shapes.input, 2))) and hiddenSize > 0",
129
  "quantBlockSizeOk": "colWiseScales or (quantBlockSize >= 16 and pow2ceil(quantBlockSize) == quantBlockSize and hiddenSize % quantBlockSize == 0 and interSize % quantBlockSize == 0)",
130
- "quantScalesOk": "tensorDtypes.fc1_scales == \"float32\" and tensorDtypes.fc2_scales == \"float32\" and dim(shapes.fc1_scales, 0) == dim(shapes.router_probs, 1) and dim(shapes.fc2_scales, 0) == dim(shapes.router_probs, 1) and dim(shapes.fc1_scales, 1) == dim(shapes.fc1_experts_weights, 1) and dim(shapes.fc2_scales, 1) == hiddenSize and ((ranks.fc1_scales == 2 and ranks.fc2_scales == 2) if colWiseScales else (ranks.fc1_scales == 3 and ranks.fc2_scales == 3 and dim(shapes.fc1_scales, 2) == fc1ScaleBlocks and dim(shapes.fc2_scales, 2) == fc2ScaleBlocks))",
131
- "quantShapeOk": "inputOutputShapeOk and ranks.router_probs == 2 and ranks.fc1_experts_weights == 3 and ranks.fc2_experts_weights == 3 and dim(shapes.router_probs, 0) == numTokens and dim(shapes.fc1_experts_weights, 0) == dim(shapes.router_probs, 1) and dim(shapes.fc2_experts_weights, 0) == dim(shapes.router_probs, 1) and dim(shapes.fc1_experts_weights, 1) % fusionSize == 0 and dim(shapes.fc2_experts_weights, 1) == hiddenSize and dim(shapes.fc1_experts_weights, 2) * packSize == hiddenSize and dim(shapes.fc2_experts_weights, 2) * packSize == interSize and quantBlockSizeOk and quantScalesOk",
132
- "quantContract": "activationSupported and routingModeSupported and rawWeightLayout and quantShapeOk and topK >= 1 and topK <= dim(shapes.router_probs, 1)",
133
  "workgroupSizeOk": "tunables.workgroupSize >= 1 and tunables.workgroupSize <= deviceWorkgroupCap",
134
  "hiddenChunkFits": "topK * interSize * 4 <= device.limits.maxStorageBufferBindingSize and topK * interSize * 4 <= device.limits.maxBufferSize",
135
  "hiddenChunkTokens": "numTokens if interSize == 0 else min(numTokens, max(1, floor(min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize) / (topK * interSize * 4))))",
@@ -141,421 +85,89 @@
141
  "groupTileKVec": "ceilDiv(tunables.groupTileK, 4)",
142
  "groupThreadCount": "tunables.groupThreads * tunables.groupThreads",
143
  "groupSlots": "hiddenChunkTokens * topK",
144
- "groupMaxTiles": "ceilDiv(groupSlots, max(1, groupTileM)) + dim(shapes.router_probs, 1)",
145
  "groupSharedBytes": "(groupTileM * tunables.groupTileK + 2 * groupTileN * tunables.groupTileK + groupTileM) * 4",
146
  "groupSlotOutBytes": "hiddenChunkTokens * topK * hiddenSize * 4",
147
- "groupedDeviceOk": "groupThreadCount <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.groupThreads <= device.limits.maxComputeWorkgroupSizeX and tunables.groupThreads <= device.limits.maxComputeWorkgroupSizeY and tunables.groupRouteWorkgroup <= deviceWorkgroupCap and groupSharedBytes <= device.limits.maxComputeWorkgroupStorageSize and dim(shapes.router_probs, 1) * 8 <= device.limits.maxComputeWorkgroupStorageSize and tunables.groupTileK % 4 == 0",
148
- "groupedDispatchOk": "groupMaxTiles <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(interSize, max(1, groupTileN)) <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(hiddenSize, max(1, groupTileN)) <= device.limits.maxComputeWorkgroupsPerDimension",
149
  "groupSlotOutFits": "groupSlotOutBytes <= device.limits.maxStorageBufferBindingSize and groupSlotOutBytes <= device.limits.maxBufferSize",
150
- "groupedShapeOk": "numTokens * topK * 4 >= groupTileM * dim(shapes.router_probs, 1)",
151
  "groupedContract": "quantContract and workgroupSizeOk and hiddenChunkFits and routedScratchFits and interSize > 0 and groupedDeviceOk and groupedDispatchOk and groupSlotOutFits and groupedShapeOk",
152
  "sgmatWorkgroup": "128",
153
  "sgmatSubgroups": "4",
154
  "sgmatRowSubtiles": "2",
155
- "groupedSgmatSharedBytes": "groupTileM * 4 + groupTileM * 32 * 4 + 64 * 32 * 4 + sgmatSubgroups * 4 * 64 * 4",
156
- "groupedSgmatOk": "groupedContract and groupTileM == 32 and interSize % 32 == 0 and groupedSgmatSharedBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(hiddenSize, 64) <= device.limits.maxComputeWorkgroupsPerDimension and hiddenSize % 32 == 0",
 
157
  "decodeLanesOk": "tunables.decodeLanes >= 1 and tunables.decodeLanes <= tunables.workgroupSize and tunables.workgroupSize % tunables.decodeLanes == 0",
158
  "decodeRows": "max(1, tunables.workgroupSize / max(1, tunables.decodeLanes))",
159
  "decodeDeviceOk": "workgroupSizeOk and decodeLanesOk and tunables.decodeLanes <= device.limits.maxComputeWorkgroupSizeX and decodeRows <= device.limits.maxComputeWorkgroupSizeY and tunables.workgroupSize * 8 <= device.limits.maxComputeWorkgroupStorageSize",
160
- "decodeDispatchOk": "ceilDiv(interSize, decodeRows) <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(hiddenSize, decodeRows) <= device.limits.maxComputeWorkgroupsPerDimension and hiddenChunkTokens * topK <= device.limits.maxComputeWorkgroupsPerDimension",
161
  "decodeLaneDepth": "min(hiddenSize, interSize) / max(1, tunables.decodeLanes)",
162
  "decodeDepthOk": "decodeLaneDepth >= tunables.decodeMinLaneTrips",
163
  "splitFc1Blocks": "ceilDiv(numTokens * topK * interSize, max(1, tunables.workgroupSize))",
164
  "decodeOccupancyOk": "splitFc1Blocks <= tunables.decodeBlockTarget",
165
- "decodeContract": "quantContract and decodeDeviceOk and decodeDispatchOk and hiddenChunkFits and routedScratchFits and interSize > 0 and decodeDepthOk and (decodeOccupancyOk or not groupedContract)"
166
- },
167
- "constants": {
168
  "tokens": "numTokens",
169
  "hidden": "hiddenSize",
170
- "experts": "dim(shapes.router_probs, 1)",
171
- "fc1Rows": "dim(shapes.fc1_experts_weights, 1)",
172
  "inter": "interSize",
173
- "topK": "topK",
174
  "workgroupSize": "tunables.workgroupSize",
175
  "activationType": "attrs.activation_type",
176
  "hasSwigluLimit": "has(attrs, \"swiglu_limit\")",
177
- "swigluLimit": "attrs.swiglu_limit if has(attrs, \"swiglu_limit\") else 0",
178
- "quantMidpoint": "quantMidpoint",
179
- "quantBlockSize": "quantBlockSize",
180
- "packSize": "packSize",
181
- "fc1PackedCols": "fc1PackedCols",
182
- "fc2PackedCols": "fc2PackedCols",
183
- "colWiseScales": "colWiseScales",
184
- "fc1ScaleBlocks": "fc1ScaleBlocks",
185
- "fc2ScaleBlocks": "fc2ScaleBlocks"
186
  },
187
- "bindingSets": {
188
- "route": [
189
- {
190
- "name": "router_probs",
191
- "arg": "routerT",
192
- "semantic": "router_probs",
193
- "buffer": { "type": "read-only-storage" },
194
- "elementType": "f32"
195
- },
196
- { "name": "route_expert", "semantic": "routeExpert", "buffer": { "type": "storage" }, "elementType": "u32" },
197
- { "name": "route_mix", "semantic": "routeMix", "buffer": { "type": "storage" }, "elementType": "f32" }
198
- ],
199
- "fc1Activation": [
200
- {
201
- "name": "input",
202
- "arg": "inputT",
203
- "semantic": "input",
204
- "buffer": { "type": "read-only-storage" },
205
- "elementType": "f32"
206
- },
207
- {
208
- "name": "route_expert",
209
- "semantic": "routeExpert",
210
- "buffer": { "type": "read-only-storage" },
211
- "elementType": "u32"
212
- },
213
- {
214
- "name": "fc1_experts_weights",
215
- "arg": "fc1T",
216
- "semantic": "fc1_experts_weights",
217
- "buffer": { "type": "read-only-storage" },
218
- "elementType": "u32"
219
- },
220
- {
221
- "name": "fc1_scales",
222
- "arg": "fc1ScalesT",
223
- "semantic": "fc1_scales",
224
- "buffer": { "type": "read-only-storage" },
225
- "elementType": "f32"
226
- },
227
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "storage" }, "elementType": "f32" },
228
- {
229
- "name": "params",
230
- "semantic": "kernel.params",
231
- "buffer": { "type": "uniform" },
232
- "struct": {
233
- "name": "Params",
234
- "fields": [
235
- { "name": "activationAlpha", "type": "f32", "value": "attrs.activation_alpha" },
236
- { "name": "activationBeta", "type": "f32", "value": "attrs.activation_beta" },
237
- { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
238
- {
239
- "name": "tokenCount",
240
- "type": "u32",
241
- "value": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
242
- }
243
- ]
244
- }
245
- }
246
- ],
247
- "output": [
248
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
249
- {
250
- "name": "route_expert",
251
- "semantic": "routeExpert",
252
- "buffer": { "type": "read-only-storage" },
253
- "elementType": "u32"
254
- },
255
- { "name": "route_mix", "semantic": "routeMix", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
256
- {
257
- "name": "fc2_experts_weights",
258
- "arg": "fc2T",
259
- "semantic": "fc2_experts_weights",
260
- "buffer": { "type": "read-only-storage" },
261
- "elementType": "u32"
262
- },
263
- {
264
- "name": "fc2_scales",
265
- "arg": "fc2ScalesT",
266
- "semantic": "fc2_scales",
267
- "buffer": { "type": "read-only-storage" },
268
- "elementType": "f32"
269
- },
270
- {
271
- "name": "output",
272
- "arg": "outputT",
273
- "semantic": "output",
274
- "buffer": { "type": "storage" },
275
- "elementType": "f32"
276
- },
277
- {
278
- "name": "params",
279
- "semantic": "kernel.params",
280
- "buffer": { "type": "uniform" },
281
- "struct": {
282
- "name": "Params",
283
- "fields": [
284
- { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
285
- {
286
- "name": "tokenCount",
287
- "type": "u32",
288
- "value": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
289
- }
290
- ]
291
- }
292
- }
293
- ],
294
- "zeroInterOutput": [
295
- {
296
- "name": "output",
297
- "arg": "outputT",
298
- "semantic": "output",
299
- "buffer": { "type": "storage" },
300
- "elementType": "f32"
301
- }
302
- ],
303
- "group": [
304
- {
305
- "name": "route_expert",
306
- "semantic": "routeExpert",
307
- "buffer": { "type": "read-only-storage" },
308
- "elementType": "u32"
309
- },
310
- { "name": "slot_list", "semantic": "slotList", "buffer": { "type": "storage" }, "elementType": "u32" },
311
- { "name": "tile_meta", "semantic": "tileMeta", "buffer": { "type": "storage" }, "elementType": "u32" },
312
- {
313
- "name": "params",
314
- "semantic": "kernel.params",
315
- "buffer": { "type": "uniform" },
316
- "struct": {
317
- "name": "Params",
318
- "fields": [
319
- { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
320
- {
321
- "name": "tokenCount",
322
- "type": "u32",
323
- "value": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
324
- }
325
- ]
326
- }
327
- }
328
- ],
329
- "fc1Grouped": [
330
- {
331
- "name": "input",
332
- "arg": "inputT",
333
- "semantic": "input",
334
- "buffer": { "type": "read-only-storage" },
335
- "elementType": "f32"
336
- },
337
- { "name": "slot_list", "semantic": "slotList", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
338
- { "name": "tile_meta", "semantic": "tileMeta", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
339
- {
340
- "name": "fc1_experts_weights",
341
- "arg": "fc1T",
342
- "semantic": "fc1_experts_weights",
343
- "buffer": { "type": "read-only-storage" },
344
- "elementType": "u32"
345
- },
346
- {
347
- "name": "fc1_scales",
348
- "arg": "fc1ScalesT",
349
- "semantic": "fc1_scales",
350
- "buffer": { "type": "read-only-storage" },
351
- "elementType": "f32"
352
- },
353
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "storage" }, "elementType": "f32" },
354
- {
355
- "name": "params",
356
- "semantic": "kernel.params",
357
- "buffer": { "type": "uniform" },
358
- "struct": {
359
- "name": "Params",
360
- "fields": [
361
- { "name": "activationAlpha", "type": "f32", "value": "attrs.activation_alpha" },
362
- { "name": "activationBeta", "type": "f32", "value": "attrs.activation_beta" },
363
- { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" }
364
- ]
365
- }
366
- }
367
- ],
368
- "outputGrouped": [
369
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
370
- { "name": "slot_list", "semantic": "slotList", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
371
- { "name": "tile_meta", "semantic": "tileMeta", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
372
- {
373
- "name": "fc2_experts_weights",
374
- "arg": "fc2T",
375
- "semantic": "fc2_experts_weights",
376
- "buffer": { "type": "read-only-storage" },
377
- "elementType": "u32"
378
- },
379
- {
380
- "name": "fc2_scales",
381
- "arg": "fc2ScalesT",
382
- "semantic": "fc2_scales",
383
- "buffer": { "type": "read-only-storage" },
384
- "elementType": "f32"
385
- },
386
- { "name": "slot_out", "semantic": "slotOut", "buffer": { "type": "storage" }, "elementType": "f32" }
387
- ],
388
- "mix": [
389
- { "name": "slot_out", "semantic": "slotOut", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
390
- { "name": "route_mix", "semantic": "routeMix", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
391
- {
392
- "name": "output",
393
- "arg": "outputT",
394
- "semantic": "output",
395
- "buffer": { "type": "storage" },
396
- "elementType": "f32"
397
- },
398
- {
399
- "name": "params",
400
- "semantic": "kernel.params",
401
- "buffer": { "type": "uniform" },
402
- "struct": {
403
- "name": "Params",
404
- "fields": [
405
- { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
406
- {
407
- "name": "tokenCount",
408
- "type": "u32",
409
- "value": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
410
- }
411
- ]
412
- }
413
- }
414
- ],
415
- "fc1ActivationIo": [
416
- {
417
- "name": "input",
418
- "arg": "inputT",
419
- "semantic": "input",
420
- "buffer": { "type": "read-only-storage" },
421
- "elementType": "f32"
422
- },
423
- {
424
- "name": "route_expert",
425
- "semantic": "routeExpert",
426
- "buffer": { "type": "read-only-storage" },
427
- "elementType": "u32"
428
- },
429
- {
430
- "name": "fc1_experts_weights",
431
- "arg": "fc1T",
432
- "semantic": "fc1_experts_weights",
433
- "buffer": { "type": "read-only-storage" },
434
- "elementType": "u32"
435
- },
436
- {
437
- "name": "fc1_scales",
438
- "arg": "fc1ScalesT",
439
- "semantic": "fc1_scales",
440
- "buffer": { "type": "read-only-storage" },
441
- "elementType": "f32"
442
- },
443
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "storage" }, "elementType": "f32" }
444
- ],
445
- "fc1ActivationScheduled": [
446
- {
447
- "name": "input",
448
- "arg": "inputT",
449
- "semantic": "input",
450
- "buffer": { "type": "read-only-storage" },
451
- "elementType": "f32"
452
- },
453
- {
454
- "name": "route_expert",
455
- "semantic": "routeExpert",
456
- "buffer": { "type": "read-only-storage" },
457
- "elementType": "u32"
458
- },
459
- {
460
- "name": "fc1_experts_weights",
461
- "arg": "fc1T",
462
- "semantic": "fc1_experts_weights",
463
- "buffer": { "type": "read-only-storage" },
464
- "elementType": "u32"
465
- },
466
- {
467
- "name": "fc1_scales",
468
- "arg": "fc1ScalesT",
469
- "semantic": "fc1_scales",
470
- "buffer": { "type": "read-only-storage" },
471
- "elementType": "f32"
472
- },
473
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "storage" }, "elementType": "f32" },
474
- {
475
- "name": "params",
476
- "semantic": "kernel.params",
477
- "buffer": { "type": "uniform" },
478
- "struct": {
479
- "name": "Params",
480
- "fields": [
481
- { "name": "activationAlpha", "type": "f32", "value": "attrs.activation_alpha" },
482
- { "name": "activationBeta", "type": "f32", "value": "attrs.activation_beta" },
483
- { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" }
484
- ]
485
- }
486
- }
487
- ],
488
- "outputIo": [
489
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
490
- {
491
- "name": "route_expert",
492
- "semantic": "routeExpert",
493
- "buffer": { "type": "read-only-storage" },
494
- "elementType": "u32"
495
- },
496
- { "name": "route_mix", "semantic": "routeMix", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
497
- {
498
- "name": "fc2_experts_weights",
499
- "arg": "fc2T",
500
- "semantic": "fc2_experts_weights",
501
- "buffer": { "type": "read-only-storage" },
502
- "elementType": "u32"
503
- },
504
- {
505
- "name": "fc2_scales",
506
- "arg": "fc2ScalesT",
507
- "semantic": "fc2_scales",
508
- "buffer": { "type": "read-only-storage" },
509
- "elementType": "f32"
510
- },
511
- {
512
- "name": "output",
513
- "arg": "outputT",
514
- "semantic": "output",
515
- "buffer": { "type": "storage" },
516
- "elementType": "f32"
517
- }
518
- ],
519
- "outputScheduled": [
520
- { "name": "hidden_act", "semantic": "hiddenAct", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
521
- {
522
- "name": "route_expert",
523
- "semantic": "routeExpert",
524
- "buffer": { "type": "read-only-storage" },
525
- "elementType": "u32"
526
- },
527
- { "name": "route_mix", "semantic": "routeMix", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
528
- {
529
- "name": "fc2_experts_weights",
530
- "arg": "fc2T",
531
- "semantic": "fc2_experts_weights",
532
- "buffer": { "type": "read-only-storage" },
533
- "elementType": "u32"
534
- },
535
- {
536
- "name": "fc2_scales",
537
- "arg": "fc2ScalesT",
538
- "semantic": "fc2_scales",
539
- "buffer": { "type": "read-only-storage" },
540
- "elementType": "f32"
541
- },
542
- {
543
- "name": "output",
544
- "arg": "outputT",
545
- "semantic": "output",
546
- "buffer": { "type": "storage" },
547
- "elementType": "f32"
548
- },
549
- {
550
- "name": "params",
551
- "semantic": "kernel.params",
552
- "buffer": { "type": "uniform" },
553
- "struct": {
554
- "name": "Params",
555
- "fields": [{ "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" }]
556
  }
557
- }
558
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  },
560
  "variants": [
561
  {
@@ -566,12 +178,14 @@
566
  {
567
  "id": "output_stage",
568
  "name": "QMoE.OutputStageZeroInter",
569
- "source": {
570
- "shader": "qmoe-output-zero-inter.wgsl.jinja",
571
- "inputs": { "outputElementCount": "numel(shapes.output)" }
572
- },
573
- "bindings": "zeroInterOutput",
574
- "dispatch": { "threads": "numel(shapes.output)", "workgroupSize": "constants.workgroupSize" }
 
 
575
  }
576
  ]
577
  },
@@ -579,13 +193,11 @@
579
  "id": "quant_grouped_sgmat_routed",
580
  "priority": 32,
581
  "when": ["groupedSgmatOk", "wave32Effective"],
582
- "constants": {
583
- "groupTileM": "groupTileM",
584
- "groupRouteWorkgroup": "tunables.groupRouteWorkgroup",
585
- "sgmatWorkgroup": "sgmatWorkgroup",
586
- "sgmatSubgroups": "sgmatSubgroups",
587
- "sgmatRowSubtiles": "sgmatRowSubtiles"
588
  },
 
589
  "intermediates": [
590
  { "id": "routeExpert", "dtype": "uint32", "shape": "[numTokens * topK]" },
591
  { "id": "routeMix", "dtype": "float32", "shape": "[numTokens * topK]" },
@@ -599,8 +211,12 @@
599
  "id": "route_stage",
600
  "name": "QMoE.RouteStage",
601
  "shader": "qmoe-route-stage.wgsl.jinja",
602
- "bindings": "route",
603
- "dispatch": { "threads": "numTokens", "workgroupSize": "constants.workgroupSize" }
 
 
 
 
604
  },
605
  {
606
  "id": "token_chunks",
@@ -610,54 +226,48 @@
610
  "id": "group_stage",
611
  "name": "QMoE.GroupStage",
612
  "shader": "expert-group-slots.wgsl.jinja",
613
- "bindings": "group",
614
  "dispatch": {
615
- "threads": "constants.groupRouteWorkgroup",
616
- "workgroupSize": "constants.groupRouteWorkgroup"
 
617
  }
618
  },
619
  {
620
  "id": "fc1_activation_stage",
621
  "name": "QMoE.FC1ActivationStageGroupedSgmat",
622
  "shader": "qmoe-fc1-activation-grouped-sgmat.wgsl.jinja",
623
- "bindings": "fc1Grouped",
624
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(interSize * fusionSize, 64)" }
625
  },
626
  {
627
  "id": "output_stage",
628
  "name": "QMoE.OutputStageGroupedSgmat",
629
  "shader": "qmoe-output-grouped-sgmat.wgsl.jinja",
630
- "bindings": "outputGrouped",
631
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize, 64)" }
632
  },
633
  {
634
  "id": "mix_stage",
635
  "name": "QMoE.MixStage",
636
  "shader": "qmoe-mix-stage.wgsl.jinja",
637
- "bindings": "mix",
638
  "dispatch": {
639
- "threads": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize",
640
- "workgroupSize": "constants.workgroupSize"
 
641
  }
642
  }
643
  ]
644
  }
645
- ],
646
- "requires": {
647
- "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
648
- "limits": { "maxComputeWorkgroupStorageSize": 16512 },
649
- "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
650
- }
651
  },
652
  {
653
  "id": "quant_grouped_routed",
654
  "priority": 30,
655
  "when": ["groupedContract"],
656
- "constants": {
657
- "groupTileM": "groupTileM",
658
- "groupTileN": "groupTileN",
659
  "groupTileK": "tunables.groupTileK",
660
- "groupTileKVec": "groupTileKVec",
661
  "groupThreads": "tunables.groupThreads",
662
  "regM": "tunables.groupRegM",
663
  "regN": "tunables.groupRegN",
@@ -676,8 +286,12 @@
676
  "id": "route_stage",
677
  "name": "QMoE.RouteStage",
678
  "shader": "qmoe-route-stage.wgsl.jinja",
679
- "bindings": "route",
680
- "dispatch": { "threads": "numTokens", "workgroupSize": "constants.workgroupSize" }
 
 
 
 
681
  },
682
  {
683
  "id": "token_chunks",
@@ -687,34 +301,36 @@
687
  "id": "group_stage",
688
  "name": "QMoE.GroupStage",
689
  "shader": "expert-group-slots.wgsl.jinja",
690
- "bindings": "group",
691
  "dispatch": {
692
- "threads": "constants.groupRouteWorkgroup",
693
- "workgroupSize": "constants.groupRouteWorkgroup"
 
694
  }
695
  },
696
  {
697
  "id": "fc1_activation_stage",
698
  "name": "QMoE.FC1ActivationStageGrouped",
699
  "shader": "qmoe-fc1-activation-grouped.wgsl.jinja",
700
- "bindings": "fc1Grouped",
701
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(interSize, groupTileN)" }
702
  },
703
  {
704
  "id": "output_stage",
705
  "name": "QMoE.OutputStageGrouped",
706
  "shader": "qmoe-output-grouped.wgsl.jinja",
707
- "bindings": "outputGrouped",
708
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize, groupTileN)" }
709
  },
710
  {
711
  "id": "mix_stage",
712
  "name": "QMoE.MixStage",
713
  "shader": "qmoe-mix-stage.wgsl.jinja",
714
- "bindings": "mix",
715
  "dispatch": {
716
- "threads": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize",
717
- "workgroupSize": "constants.workgroupSize"
 
718
  }
719
  }
720
  ]
@@ -725,7 +341,7 @@
725
  "id": "quant_gemv_routed",
726
  "priority": 20,
727
  "when": ["decodeContract"],
728
- "constants": { "decodeLanes": "tunables.decodeLanes", "decodeRows": "decodeRows" },
729
  "intermediates": [
730
  { "id": "routeExpert", "dtype": "uint32", "shape": "[numTokens * topK]" },
731
  { "id": "routeMix", "dtype": "float32", "shape": "[numTokens * topK]" },
@@ -736,8 +352,12 @@
736
  "id": "route_stage",
737
  "name": "QMoE.RouteStage",
738
  "shader": "qmoe-route-stage.wgsl.jinja",
739
- "bindings": "route",
740
- "dispatch": { "threads": "numTokens", "workgroupSize": "constants.workgroupSize" }
 
 
 
 
741
  },
742
  {
743
  "id": "token_chunks",
@@ -747,7 +367,7 @@
747
  "id": "fc1_activation_stage",
748
  "name": "QMoE.FC1ActivationStageGemv",
749
  "shader": "qmoe-fc1-activation-gemv.wgsl.jinja",
750
- "bindings": "fc1ActivationScheduled",
751
  "dispatch": {
752
  "x": "ceilDiv(interSize, decodeRows)",
753
  "y": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * topK"
@@ -757,7 +377,18 @@
757
  "id": "output_stage",
758
  "name": "QMoE.OutputStageGemv",
759
  "shader": "qmoe-output-gemv.wgsl.jinja",
760
- "bindings": "outputScheduled",
 
 
 
 
 
 
 
 
 
 
 
761
  "dispatch": {
762
  "x": "ceilDiv(hiddenSize, decodeRows)",
763
  "y": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
@@ -781,8 +412,12 @@
781
  "id": "route_stage",
782
  "name": "QMoE.RouteStage",
783
  "shader": "qmoe-route-stage.wgsl.jinja",
784
- "bindings": "route",
785
- "dispatch": { "threads": "numTokens", "workgroupSize": "constants.workgroupSize" }
 
 
 
 
786
  },
787
  {
788
  "id": "token_chunks",
@@ -792,20 +427,41 @@
792
  "id": "fc1_activation_stage",
793
  "name": "QMoE.FC1ActivationStage",
794
  "shader": "qmoe-fc1-activation-stage.wgsl.jinja",
795
- "bindings": "fc1Activation",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  "dispatch": {
797
- "threads": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * topK * interSize",
798
- "workgroupSize": "constants.workgroupSize"
 
799
  }
800
  },
801
  {
802
  "id": "output_stage",
803
  "name": "QMoE.OutputStage",
804
  "shader": "qmoe-output-stage.wgsl.jinja",
805
- "bindings": "output",
806
  "dispatch": {
807
- "threads": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize",
808
- "workgroupSize": "constants.workgroupSize"
 
809
  }
810
  }
811
  ]
 
2
  "domain": "com.microsoft",
3
  "name": "QMoE",
4
  "sinceVersion": 1,
5
+ "inputs": {
6
+ "inputT": { "onnx": "input", "dtype": "T" },
7
+ "routerT": { "onnx": "router_probs", "dtype": "T", "rank": 2 },
8
+ "fc1T": { "onnx": "fc1_experts_weights", "dtype": "T1", "rank": 3 },
9
+ "fc1ScalesT": { "onnx": "fc1_scales", "dtype": "T2" },
10
+ "fc2T": { "onnx": "fc2_experts_weights", "dtype": "T1", "rank": 3 },
11
+ "fc2ScalesT": { "onnx": "fc2_scales", "dtype": "T2" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  },
13
+ "outputs": { "outputT": { "onnx": "output", "dtype": "T", "shape": "shapes.inputT" } },
14
+ "attributes": {
15
+ "activation_alpha": { "default": 1 },
16
+ "activation_beta": { "default": 0 },
17
+ "activation_type": { "default": "relu" },
18
+ "expert_weight_bits": { "default": 4 },
19
+ "k": { "default": 1 },
20
+ "normalize_routing_weights": { "default": 0 },
21
+ "quant_type": { "default": "int" },
22
+ "swiglu_fusion": { "default": 0 },
23
+ "use_sparse_mixer": { "default": 0 },
24
+ "weights_prepacked": { "default": -1 },
25
+ "block_size": {},
26
+ "swiglu_limit": {}
27
  },
28
  "attributeConstraints": {
29
  "activation_type": { "values": ["relu", "swiglu"] },
 
35
  "weights_prepacked": { "values": [-1, 0] }
36
  },
37
  "typeConstraints": { "T": ["float32"], "T1": ["uint8"], "T2": ["float32"] },
 
 
 
 
 
 
 
 
 
38
  "tunables": {
39
+ "workgroupSize": { "default": 64 },
40
+ "decodeLanes": { "default": 32 },
41
+ "decodeBlockTarget": { "default": 1024 },
42
+ "decodeMinLaneTrips": { "default": 4 },
43
+ "groupThreads": { "default": 8 },
44
+ "groupRegM": { "default": 4 },
45
+ "groupRegN": { "default": 4 },
46
+ "groupTileK": { "default": 16 },
47
+ "groupRouteWorkgroup": { "default": 256 }
48
  },
49
  "derive": {
50
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
 
52
  "canPinSubgroupSize32": "device.features.has(\"subgroups\") and device.features.has(\"subgroup-size-control\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize <= 32 and device.adapterInfo.subgroupMaxSize >= 32",
53
  "pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
54
  "wave32Effective": "wave32Adapter or pinSubgroupSize32",
55
+ "hiddenSize": "dim(shapes.inputT, ranks.inputT - 1)",
56
+ "numTokens": "numel(shapes.inputT) / max(1, hiddenSize)",
57
  "topK": "attrs.k",
58
  "weightBits": "attrs.expert_weight_bits",
59
  "quantBlockSize": "attrs.block_size if has(attrs, \"block_size\") else 0",
60
  "packSize": "2 if weightBits == 4 else 1",
61
  "quantMidpoint": "8 if weightBits == 4 else 128",
62
  "fusionSize": "2 if attrs.activation_type == \"swiglu\" and attrs.swiglu_fusion == 1 else 1",
63
+ "interSize": "dim(shapes.fc1T, 1) / fusionSize",
64
+ "fc1PackedCols": "dim(shapes.fc1T, 2)",
65
+ "fc2PackedCols": "dim(shapes.fc2T, 2)",
66
  "colWiseScales": "quantBlockSize == 0",
67
  "fc1ScaleBlocks": "1 if colWiseScales else hiddenSize / max(1, quantBlockSize)",
68
  "fc2ScaleBlocks": "1 if colWiseScales else interSize / max(1, quantBlockSize)",
69
  "activationSupported": "(attrs.activation_type == \"relu\" and attrs.swiglu_fusion == 0) or (attrs.activation_type == \"swiglu\" and attrs.swiglu_fusion == 1)",
70
  "routingModeSupported": "attrs.normalize_routing_weights == 0 or attrs.normalize_routing_weights == 1",
71
  "rawWeightLayout": "attrs.weights_prepacked == -1 or attrs.weights_prepacked == 0",
72
+ "inputOutputShapeOk": "((ranks.inputT == 2 and ranks.outputT == 2 and dim(shapes.outputT, 0) == dim(shapes.inputT, 0) and dim(shapes.outputT, 1) == dim(shapes.inputT, 1)) or (ranks.inputT == 3 and ranks.outputT == 3 and dim(shapes.outputT, 0) == dim(shapes.inputT, 0) and dim(shapes.outputT, 1) == dim(shapes.inputT, 1) and dim(shapes.outputT, 2) == dim(shapes.inputT, 2))) and hiddenSize > 0",
73
  "quantBlockSizeOk": "colWiseScales or (quantBlockSize >= 16 and pow2ceil(quantBlockSize) == quantBlockSize and hiddenSize % quantBlockSize == 0 and interSize % quantBlockSize == 0)",
74
+ "quantScalesOk": "tensorDtypes.fc1ScalesT == \"float32\" and tensorDtypes.fc2ScalesT == \"float32\" and dim(shapes.fc1ScalesT, 0) == dim(shapes.routerT, 1) and dim(shapes.fc2ScalesT, 0) == dim(shapes.routerT, 1) and dim(shapes.fc1ScalesT, 1) == dim(shapes.fc1T, 1) and dim(shapes.fc2ScalesT, 1) == hiddenSize and ((ranks.fc1ScalesT == 2 and ranks.fc2ScalesT == 2) if colWiseScales else (ranks.fc1ScalesT == 3 and ranks.fc2ScalesT == 3 and dim(shapes.fc1ScalesT, 2) == fc1ScaleBlocks and dim(shapes.fc2ScalesT, 2) == fc2ScaleBlocks))",
75
+ "quantShapeOk": "inputOutputShapeOk and ranks.routerT == 2 and ranks.fc1T == 3 and ranks.fc2T == 3 and dim(shapes.routerT, 0) == numTokens and dim(shapes.fc1T, 0) == dim(shapes.routerT, 1) and dim(shapes.fc2T, 0) == dim(shapes.routerT, 1) and dim(shapes.fc1T, 1) % fusionSize == 0 and dim(shapes.fc2T, 1) == hiddenSize and dim(shapes.fc1T, 2) * packSize == hiddenSize and dim(shapes.fc2T, 2) * packSize == interSize and quantBlockSizeOk and quantScalesOk",
76
+ "quantContract": "activationSupported and routingModeSupported and rawWeightLayout and quantShapeOk and topK >= 1 and topK <= dim(shapes.routerT, 1)",
77
  "workgroupSizeOk": "tunables.workgroupSize >= 1 and tunables.workgroupSize <= deviceWorkgroupCap",
78
  "hiddenChunkFits": "topK * interSize * 4 <= device.limits.maxStorageBufferBindingSize and topK * interSize * 4 <= device.limits.maxBufferSize",
79
  "hiddenChunkTokens": "numTokens if interSize == 0 else min(numTokens, max(1, floor(min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize) / (topK * interSize * 4))))",
 
85
  "groupTileKVec": "ceilDiv(tunables.groupTileK, 4)",
86
  "groupThreadCount": "tunables.groupThreads * tunables.groupThreads",
87
  "groupSlots": "hiddenChunkTokens * topK",
88
+ "groupMaxTiles": "ceilDiv(groupSlots, max(1, groupTileM)) + dim(shapes.routerT, 1)",
89
  "groupSharedBytes": "(groupTileM * tunables.groupTileK + 2 * groupTileN * tunables.groupTileK + groupTileM) * 4",
90
  "groupSlotOutBytes": "hiddenChunkTokens * topK * hiddenSize * 4",
91
+ "groupedDeviceOk": "groupThreadCount <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.groupThreads <= device.limits.maxComputeWorkgroupSizeX and tunables.groupThreads <= device.limits.maxComputeWorkgroupSizeY and tunables.groupRouteWorkgroup <= deviceWorkgroupCap and groupSharedBytes <= device.limits.maxComputeWorkgroupStorageSize and dim(shapes.routerT, 1) * 8 <= device.limits.maxComputeWorkgroupStorageSize and tunables.groupTileK % 4 == 0",
92
+ "groupedDispatchOk": "groupMaxTiles <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(interSize, max(1, groupTileN)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(hiddenSize, max(1, groupTileN)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
93
  "groupSlotOutFits": "groupSlotOutBytes <= device.limits.maxStorageBufferBindingSize and groupSlotOutBytes <= device.limits.maxBufferSize",
94
+ "groupedShapeOk": "numTokens * topK * 4 >= groupTileM * dim(shapes.routerT, 1)",
95
  "groupedContract": "quantContract and workgroupSizeOk and hiddenChunkFits and routedScratchFits and interSize > 0 and groupedDeviceOk and groupedDispatchOk and groupSlotOutFits and groupedShapeOk",
96
  "sgmatWorkgroup": "128",
97
  "sgmatSubgroups": "4",
98
  "sgmatRowSubtiles": "2",
99
+ "groupedSgmatStageElements": "max(groupTileM * 32, sgmatSubgroups * 4 * 64)",
100
+ "groupedSgmatSharedBytes": "(groupedSgmatStageElements + 64 * 32) * 4",
101
+ "groupedSgmatOk": "groupedContract and groupTileM == 32 and interSize % 32 == 0 and groupedSgmatSharedBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(hiddenSize, 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and hiddenSize % 32 == 0",
102
  "decodeLanesOk": "tunables.decodeLanes >= 1 and tunables.decodeLanes <= tunables.workgroupSize and tunables.workgroupSize % tunables.decodeLanes == 0",
103
  "decodeRows": "max(1, tunables.workgroupSize / max(1, tunables.decodeLanes))",
104
  "decodeDeviceOk": "workgroupSizeOk and decodeLanesOk and tunables.decodeLanes <= device.limits.maxComputeWorkgroupSizeX and decodeRows <= device.limits.maxComputeWorkgroupSizeY and tunables.workgroupSize * 8 <= device.limits.maxComputeWorkgroupStorageSize",
105
+ "decodeDispatchOk": "ceilDiv(interSize, decodeRows) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(hiddenSize, decodeRows) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and hiddenChunkTokens * topK <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
106
  "decodeLaneDepth": "min(hiddenSize, interSize) / max(1, tunables.decodeLanes)",
107
  "decodeDepthOk": "decodeLaneDepth >= tunables.decodeMinLaneTrips",
108
  "splitFc1Blocks": "ceilDiv(numTokens * topK * interSize, max(1, tunables.workgroupSize))",
109
  "decodeOccupancyOk": "splitFc1Blocks <= tunables.decodeBlockTarget",
110
+ "decodeContract": "quantContract and decodeDeviceOk and decodeDispatchOk and hiddenChunkFits and routedScratchFits and interSize > 0 and decodeDepthOk and (decodeOccupancyOk or not groupedContract)",
 
 
111
  "tokens": "numTokens",
112
  "hidden": "hiddenSize",
113
+ "experts": "dim(shapes.routerT, 1)",
114
+ "fc1Rows": "dim(shapes.fc1T, 1)",
115
  "inter": "interSize",
 
116
  "workgroupSize": "tunables.workgroupSize",
117
  "activationType": "attrs.activation_type",
118
  "hasSwigluLimit": "has(attrs, \"swiglu_limit\")",
119
+ "swigluLimit": "attrs.swiglu_limit if has(attrs, \"swiglu_limit\") else 0"
 
 
 
 
 
 
 
 
120
  },
121
+ "bindings": {
122
+ "output": { "arg": "outputT", "buffer": "storage", "elementType": "f32" },
123
+ "router_probs": { "arg": "routerT", "buffer": "read-only-storage", "elementType": "f32" },
124
+ "route_expert": { "scratch": "routeExpert", "buffer": "storage", "elementType": "u32" },
125
+ "route_mix": { "scratch": "routeMix", "buffer": "storage", "elementType": "f32" },
126
+ "route_expert_2": {
127
+ "scratch": "routeExpert",
128
+ "name": "route_expert",
129
+ "buffer": "read-only-storage",
130
+ "elementType": "u32"
131
+ },
132
+ "slot_list": { "scratch": "slotList", "buffer": "storage", "elementType": "u32" },
133
+ "tile_meta": { "scratch": "tileMeta", "buffer": "storage", "elementType": "u32" },
134
+ "params": {
135
+ "buffer": "uniform",
136
+ "struct": [
137
+ { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
138
+ {
139
+ "name": "tokenCount",
140
+ "type": "u32",
141
+ "value": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
+ ]
144
+ },
145
+ "input": { "arg": "inputT", "buffer": "read-only-storage", "elementType": "f32" },
146
+ "slot_list_2": { "scratch": "slotList", "name": "slot_list", "buffer": "read-only-storage", "elementType": "u32" },
147
+ "tile_meta_2": { "scratch": "tileMeta", "name": "tile_meta", "buffer": "read-only-storage", "elementType": "u32" },
148
+ "fc1_experts_weights": { "arg": "fc1T", "buffer": "read-only-storage", "elementType": "u32" },
149
+ "fc1_scales": { "arg": "fc1ScalesT", "buffer": "read-only-storage", "elementType": "f32" },
150
+ "hidden_act": { "scratch": "hiddenAct", "buffer": "storage", "elementType": "f32" },
151
+ "params_2": {
152
+ "name": "params",
153
+ "buffer": "uniform",
154
+ "struct": [
155
+ { "name": "activationAlpha", "type": "f32", "value": "attrs.activation_alpha" },
156
+ { "name": "activationBeta", "type": "f32", "value": "attrs.activation_beta" },
157
+ { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" }
158
+ ]
159
+ },
160
+ "hidden_act_2": {
161
+ "scratch": "hiddenAct",
162
+ "name": "hidden_act",
163
+ "buffer": "read-only-storage",
164
+ "elementType": "f32"
165
+ },
166
+ "fc2_experts_weights": { "arg": "fc2T", "buffer": "read-only-storage", "elementType": "u32" },
167
+ "fc2_scales": { "arg": "fc2ScalesT", "buffer": "read-only-storage", "elementType": "f32" },
168
+ "slot_out": { "scratch": "slotOut", "buffer": "storage", "elementType": "f32" },
169
+ "slot_out_2": { "scratch": "slotOut", "name": "slot_out", "buffer": "read-only-storage", "elementType": "f32" },
170
+ "route_mix_2": { "scratch": "routeMix", "name": "route_mix", "buffer": "read-only-storage", "elementType": "f32" }
171
  },
172
  "variants": [
173
  {
 
178
  {
179
  "id": "output_stage",
180
  "name": "QMoE.OutputStageZeroInter",
181
+ "shader": "qmoe-output-zero-inter.wgsl.jinja",
182
+ "derive": { "outputElementCount": "numel(shapes.outputT)" },
183
+ "bindings": ["output"],
184
+ "dispatch": {
185
+ "x": "min(ceilDiv((numel(shapes.outputT)), (workgroupSize)), 65535)",
186
+ "y": "ceilDiv(ceilDiv((numel(shapes.outputT)), (workgroupSize)), 65535)",
187
+ "z": 1
188
+ }
189
  }
190
  ]
191
  },
 
193
  "id": "quant_grouped_sgmat_routed",
194
  "priority": 32,
195
  "when": ["groupedSgmatOk", "wave32Effective"],
196
+ "requires": {
197
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
198
+ "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
 
 
 
199
  },
200
+ "derive": { "groupRouteWorkgroup": "tunables.groupRouteWorkgroup" },
201
  "intermediates": [
202
  { "id": "routeExpert", "dtype": "uint32", "shape": "[numTokens * topK]" },
203
  { "id": "routeMix", "dtype": "float32", "shape": "[numTokens * topK]" },
 
211
  "id": "route_stage",
212
  "name": "QMoE.RouteStage",
213
  "shader": "qmoe-route-stage.wgsl.jinja",
214
+ "bindings": ["router_probs", "route_expert", "route_mix"],
215
+ "dispatch": {
216
+ "x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
217
+ "y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
218
+ "z": 1
219
+ }
220
  },
221
  {
222
  "id": "token_chunks",
 
226
  "id": "group_stage",
227
  "name": "QMoE.GroupStage",
228
  "shader": "expert-group-slots.wgsl.jinja",
229
+ "bindings": ["route_expert_2", "slot_list", "tile_meta", "params"],
230
  "dispatch": {
231
+ "x": "min(ceilDiv((groupRouteWorkgroup), (groupRouteWorkgroup)), 65535)",
232
+ "y": "ceilDiv(ceilDiv((groupRouteWorkgroup), (groupRouteWorkgroup)), 65535)",
233
+ "z": 1
234
  }
235
  },
236
  {
237
  "id": "fc1_activation_stage",
238
  "name": "QMoE.FC1ActivationStageGroupedSgmat",
239
  "shader": "qmoe-fc1-activation-grouped-sgmat.wgsl.jinja",
240
+ "bindings": ["input", "slot_list_2", "tile_meta_2", "fc1_experts_weights", "fc1_scales", "hidden_act", "params_2"],
241
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(interSize * fusionSize, 64)" }
242
  },
243
  {
244
  "id": "output_stage",
245
  "name": "QMoE.OutputStageGroupedSgmat",
246
  "shader": "qmoe-output-grouped-sgmat.wgsl.jinja",
247
+ "bindings": ["hidden_act_2", "slot_list_2", "tile_meta_2", "fc2_experts_weights", "fc2_scales", "slot_out"],
248
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize, 64)" }
249
  },
250
  {
251
  "id": "mix_stage",
252
  "name": "QMoE.MixStage",
253
  "shader": "qmoe-mix-stage.wgsl.jinja",
254
+ "bindings": ["slot_out_2", "route_mix_2", "output", "params"],
255
  "dispatch": {
256
+ "x": "min(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
257
+ "y": "ceilDiv(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
258
+ "z": 1
259
  }
260
  }
261
  ]
262
  }
263
+ ]
 
 
 
 
 
264
  },
265
  {
266
  "id": "quant_grouped_routed",
267
  "priority": 30,
268
  "when": ["groupedContract"],
269
+ "derive": {
 
 
270
  "groupTileK": "tunables.groupTileK",
 
271
  "groupThreads": "tunables.groupThreads",
272
  "regM": "tunables.groupRegM",
273
  "regN": "tunables.groupRegN",
 
286
  "id": "route_stage",
287
  "name": "QMoE.RouteStage",
288
  "shader": "qmoe-route-stage.wgsl.jinja",
289
+ "bindings": ["router_probs", "route_expert", "route_mix"],
290
+ "dispatch": {
291
+ "x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
292
+ "y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
293
+ "z": 1
294
+ }
295
  },
296
  {
297
  "id": "token_chunks",
 
301
  "id": "group_stage",
302
  "name": "QMoE.GroupStage",
303
  "shader": "expert-group-slots.wgsl.jinja",
304
+ "bindings": ["route_expert_2", "slot_list", "tile_meta", "params"],
305
  "dispatch": {
306
+ "x": "min(ceilDiv((groupRouteWorkgroup), (groupRouteWorkgroup)), 65535)",
307
+ "y": "ceilDiv(ceilDiv((groupRouteWorkgroup), (groupRouteWorkgroup)), 65535)",
308
+ "z": 1
309
  }
310
  },
311
  {
312
  "id": "fc1_activation_stage",
313
  "name": "QMoE.FC1ActivationStageGrouped",
314
  "shader": "qmoe-fc1-activation-grouped.wgsl.jinja",
315
+ "bindings": ["input", "slot_list_2", "tile_meta_2", "fc1_experts_weights", "fc1_scales", "hidden_act", "params_2"],
316
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(interSize, groupTileN)" }
317
  },
318
  {
319
  "id": "output_stage",
320
  "name": "QMoE.OutputStageGrouped",
321
  "shader": "qmoe-output-grouped.wgsl.jinja",
322
+ "bindings": ["hidden_act_2", "slot_list_2", "tile_meta_2", "fc2_experts_weights", "fc2_scales", "slot_out"],
323
  "dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize, groupTileN)" }
324
  },
325
  {
326
  "id": "mix_stage",
327
  "name": "QMoE.MixStage",
328
  "shader": "qmoe-mix-stage.wgsl.jinja",
329
+ "bindings": ["slot_out_2", "route_mix_2", "output", "params"],
330
  "dispatch": {
331
+ "x": "min(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
332
+ "y": "ceilDiv(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
333
+ "z": 1
334
  }
335
  }
336
  ]
 
341
  "id": "quant_gemv_routed",
342
  "priority": 20,
343
  "when": ["decodeContract"],
344
+ "derive": { "decodeLanes": "tunables.decodeLanes" },
345
  "intermediates": [
346
  { "id": "routeExpert", "dtype": "uint32", "shape": "[numTokens * topK]" },
347
  { "id": "routeMix", "dtype": "float32", "shape": "[numTokens * topK]" },
 
352
  "id": "route_stage",
353
  "name": "QMoE.RouteStage",
354
  "shader": "qmoe-route-stage.wgsl.jinja",
355
+ "bindings": ["router_probs", "route_expert", "route_mix"],
356
+ "dispatch": {
357
+ "x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
358
+ "y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
359
+ "z": 1
360
+ }
361
  },
362
  {
363
  "id": "token_chunks",
 
367
  "id": "fc1_activation_stage",
368
  "name": "QMoE.FC1ActivationStageGemv",
369
  "shader": "qmoe-fc1-activation-gemv.wgsl.jinja",
370
+ "bindings": ["input", "route_expert_2", "fc1_experts_weights", "fc1_scales", "hidden_act", "params_2"],
371
  "dispatch": {
372
  "x": "ceilDiv(interSize, decodeRows)",
373
  "y": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * topK"
 
377
  "id": "output_stage",
378
  "name": "QMoE.OutputStageGemv",
379
  "shader": "qmoe-output-gemv.wgsl.jinja",
380
+ "bindings": [
381
+ "hidden_act_2",
382
+ "route_expert_2",
383
+ "route_mix_2",
384
+ "fc2_experts_weights",
385
+ "fc2_scales",
386
+ "output",
387
+ {
388
+ "name": "params",
389
+ "struct": [{ "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" }]
390
+ }
391
+ ],
392
  "dispatch": {
393
  "x": "ceilDiv(hiddenSize, decodeRows)",
394
  "y": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
 
412
  "id": "route_stage",
413
  "name": "QMoE.RouteStage",
414
  "shader": "qmoe-route-stage.wgsl.jinja",
415
+ "bindings": ["router_probs", "route_expert", "route_mix"],
416
+ "dispatch": {
417
+ "x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
418
+ "y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
419
+ "z": 1
420
+ }
421
  },
422
  {
423
  "id": "token_chunks",
 
427
  "id": "fc1_activation_stage",
428
  "name": "QMoE.FC1ActivationStage",
429
  "shader": "qmoe-fc1-activation-stage.wgsl.jinja",
430
+ "bindings": [
431
+ "input",
432
+ "route_expert_2",
433
+ "fc1_experts_weights",
434
+ "fc1_scales",
435
+ "hidden_act",
436
+ {
437
+ "name": "params",
438
+ "struct": [
439
+ { "name": "activationAlpha", "type": "f32", "value": "attrs.activation_alpha" },
440
+ { "name": "activationBeta", "type": "f32", "value": "attrs.activation_beta" },
441
+ { "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
442
+ {
443
+ "name": "tokenCount",
444
+ "type": "u32",
445
+ "value": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens)"
446
+ }
447
+ ]
448
+ }
449
+ ],
450
  "dispatch": {
451
+ "x": "min(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * topK * interSize), (workgroupSize)), 65535)",
452
+ "y": "ceilDiv(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * topK * interSize), (workgroupSize)), 65535)",
453
+ "z": 1
454
  }
455
  },
456
  {
457
  "id": "output_stage",
458
  "name": "QMoE.OutputStage",
459
  "shader": "qmoe-output-stage.wgsl.jinja",
460
+ "bindings": ["hidden_act_2", "route_expert_2", "route_mix_2", "fc2_experts_weights", "fc2_scales", "output", "params"],
461
  "dispatch": {
462
+ "x": "min(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
463
+ "y": "ceilDiv(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
464
+ "z": 1
465
  }
466
  }
467
  ]
build/webgpu/metadata.json CHANGED
@@ -1,29 +1,38 @@
1
  {
2
  "name": "com.microsoft.QMoE",
3
- "id": "_com_microsoft_qmoe_webgpu_6caed83",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "zBVupnayA0Ggc4y737P9tzqyM/q/N8F4j+FEJXz6Wks=",
11
  "expert-group-slots.wgsl.jinja": "Ta+3H2FA1qRRzksdkwgKLZiO+AokmuM+B9JVN8cv1EQ=",
12
- "manifest.json": "F9s2iIz0nNI2vJ+2zAFr55u8jaKjAEe5qvfmGxSc6mE=",
13
  "qmoe-fc1-activation-gemv.wgsl.jinja": "UvP8Vyx1E9+/+I09w/z9iB6h6MHrSrOmjKfIelyIS0M=",
14
- "qmoe-fc1-activation-grouped-sgmat.wgsl.jinja": "r/vlFM8WL1WfjkX7P2/DjKxrMDRKBmI+PtV/qQVLgCc=",
15
- "qmoe-fc1-activation-grouped.wgsl.jinja": "y2vGeNeXE5YmNrM2WrXogyF/fTvQ/nqYR4uWBTDvpPY=",
16
- "qmoe-fc1-activation-stage.wgsl.jinja": "Hx7fYjr8CwB2jXeTnh6G6IyavWrLwyOdtUsJwVEpgGk=",
17
- "qmoe-mix-stage.wgsl.jinja": "+deuFyuBn04UeIX8Hk2/ntCHNfM1vgkNDTcsh3uXVdI=",
18
  "qmoe-output-gemv.wgsl.jinja": "OJLoDDWMYE5yaTeDGY7qUeuf6AepJL7ZZLx8ko6OTEQ=",
19
- "qmoe-output-grouped-sgmat.wgsl.jinja": "aoBOyGPOM2MXOpjvVCppacvWra4bVBewQEbjL2MPe2k=",
20
- "qmoe-output-grouped.wgsl.jinja": "mHkrsubKwd0YWYo4TMpl4HWdbWheoeiAsUU2WsUON8E=",
21
- "qmoe-output-stage.wgsl.jinja": "f9CWE9XR/5RTULNXdTHwA4gFsOL1QRg0H/rT3dD32Gs=",
22
- "qmoe-output-zero-inter.wgsl.jinja": "6iBjk5YqxSSoXufDo/aJJRS4v8ZOp7wF2aRRXU4m1SA=",
23
- "qmoe-route-stage.wgsl.jinja": "cGnJJB0bEdlcPFOKm8s42byI9ePixYfmfK9nK05L7V4=",
24
- "test.json": "3i3FSregL3rNfwD/Wi7X2Ix296NNB1zIV+Ap9TrpMeU="
25
  }
26
  },
27
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
28
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.QMoE" }
 
 
 
 
 
 
 
 
 
29
  }
 
1
  {
2
  "name": "com.microsoft.QMoE",
3
+ "id": "_com_microsoft_qmoe_webgpu_b3768d9",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "YuXSEWa4U+6Ek0E0ZkIeDrzqQ672IelokTilKJuKHf0=",
11
  "expert-group-slots.wgsl.jinja": "Ta+3H2FA1qRRzksdkwgKLZiO+AokmuM+B9JVN8cv1EQ=",
12
+ "manifest.json": "r3eaOyzncaumYYrgh5XHHfvd9mHdvVPR3cl5xbiwDDA=",
13
  "qmoe-fc1-activation-gemv.wgsl.jinja": "UvP8Vyx1E9+/+I09w/z9iB6h6MHrSrOmjKfIelyIS0M=",
14
+ "qmoe-fc1-activation-grouped-sgmat.wgsl.jinja": "uWNSXPm0htntxHKCNBzDIo5qFmROP9wDF3mjBGQo+Wo=",
15
+ "qmoe-fc1-activation-grouped.wgsl.jinja": "6NEYMHvSNgCp6KHJveI4LELrTqr8u/lxIJjSkRX68V4=",
16
+ "qmoe-fc1-activation-stage.wgsl.jinja": "C9DvNGQ2u4eBAAceyEdbGX13goQFJbxSunkXXhYN604=",
17
+ "qmoe-mix-stage.wgsl.jinja": "+xEc6boXboFbx+4AJ2/KWElM04LRuufmiIKfEK51/oo=",
18
  "qmoe-output-gemv.wgsl.jinja": "OJLoDDWMYE5yaTeDGY7qUeuf6AepJL7ZZLx8ko6OTEQ=",
19
+ "qmoe-output-grouped-sgmat.wgsl.jinja": "jopTCpUPoIw9MBRhrmjQuZioTbJJIygwBvbwRyhHvUY=",
20
+ "qmoe-output-grouped.wgsl.jinja": "dOoFq8c0+yBCwOae+QVKBQyodJUK4vwam1RQb46oBNU=",
21
+ "qmoe-output-stage.wgsl.jinja": "yuSg3G05eKQOunyR5lcCEaJ5y5lXoT73RzT9rq6ahJQ=",
22
+ "qmoe-output-zero-inter.wgsl.jinja": "6jHYpVxU5+9WMflhwMste4v17hq7WWA1BDDS4q4DLS4=",
23
+ "qmoe-route-stage.wgsl.jinja": "SF5VN6d0zp/ssCGnTwCWccgbnOkZ0mptZiwIbMX3gXA=",
24
+ "test.json": "/ffqKBtjk2jseUZPswe8gOyYZfhNJu9aOZg8GNlB+9A="
25
  }
26
  },
27
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
28
+ "webgpu": {
29
+ "manifestSpec": "2.0",
30
+ "variants": {
31
+ "quant_zero_inter": ["qmoe-output-zero-inter.wgsl.jinja"],
32
+ "quant_grouped_sgmat_routed": ["expert-group-slots.wgsl.jinja", "qmoe-fc1-activation-grouped-sgmat.wgsl.jinja", "qmoe-mix-stage.wgsl.jinja", "qmoe-output-grouped-sgmat.wgsl.jinja", "qmoe-route-stage.wgsl.jinja"],
33
+ "quant_grouped_routed": ["expert-group-slots.wgsl.jinja", "qmoe-fc1-activation-grouped.wgsl.jinja", "qmoe-mix-stage.wgsl.jinja", "qmoe-output-grouped.wgsl.jinja", "qmoe-route-stage.wgsl.jinja"],
34
+ "quant_gemv_routed": ["qmoe-fc1-activation-gemv.wgsl.jinja", "qmoe-output-gemv.wgsl.jinja", "qmoe-route-stage.wgsl.jinja"],
35
+ "quant_split_routed": ["qmoe-fc1-activation-stage.wgsl.jinja", "qmoe-output-stage.wgsl.jinja", "qmoe-route-stage.wgsl.jinja"]
36
+ }
37
+ }
38
  }
build/webgpu/qmoe-fc1-activation-grouped-sgmat.wgsl.jinja CHANGED
@@ -5,14 +5,13 @@ enable subgroup_size_control;
5
  enable chromium_experimental_subgroup_matrix;
6
  diagnostic(off, chromium.subgroup_matrix_uniformity);
7
 
 
8
  {{ env.wgsl.resourceDeclarations }}
9
 
10
- // com.microsoft.QMoE, expert-grouped FC1 + activation stage on the subgroup-matrix tier.
11
- //
12
- // Same decomposition as the scalar grouped stage -- routed slots arrive sorted by expert,
13
- // so a tile shares one expert and its weight tile is dequantized once and reused down the
14
- // tile -- with the inner product on the matrix units and the packed weights dequantized
15
- // straight into the shared B operand.
16
  //
17
  // A fused SwiGLU takes its gate from weight row 2c and its up from row 2c+1. Those are
18
  // staged as adjacent columns of one B tile rather than as two tiles. The result columns a
@@ -21,7 +20,6 @@ const HIDDEN: u32 = {{ hidden }}u;
21
  const FC1_ROWS: u32 = {{ fc1Rows }}u;
22
  const INTER: u32 = {{ inter }}u;
23
  const TOP_K: u32 = {{ topK }}u;
24
- const TILE_ROWS: u32 = {{ groupTileM }}u;
25
  const TILE_COLS: u32 = 64u;
26
  const TILE_K: u32 = 32u;
27
  const SUB_COLS: u32 = 32u;
@@ -43,7 +41,7 @@ const FC1_BLOCKS: u32 = {{ fc1ScaleBlocks }}u;
43
 
44
  // Decode the four-code group consumed by the staging loop. The aligned codes
45
  // share one quantization scale: blocks are powers of two of at least 16, and
46
- // the caller guarantees `col` is a multiple of four.
47
  fn fc1_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
48
  let base = expert * FC1_ROWS + row;
49
  {% if packSize == 2 %}
@@ -86,16 +84,16 @@ fn activate(value: f32) -> f32 {
86
  {% endif %}
87
 
88
 
89
- var<workgroup> row_slot: array<u32, {{ groupTileM }}>;
90
- var<workgroup> tile_A: array<f32, {{ groupTileM }} * 32>;
 
91
  var<workgroup> tile_B: array<f32, 64 * 32>;
92
- var<workgroup> scratch: array<array<array<f32, 64>, 4>, {{ sgmatSubgroups }}>;
93
 
94
  // Rows past the tile's end stage zero and are dropped at the store, so a short final
95
  // tile needs no separate arm.
96
- fn loadSHMA(k_idx: u32, rows: u32, row: u32, c_idx: u32) {
97
  let col = c_idx * 8u;
98
- let base = (params.tokenOffset + row_slot[row] / TOP_K) * HIDDEN + k_idx + col;
99
  if (row < rows) {
100
  for (var i = 0u; i < 8u; i = i + 1u) {
101
  tile_A[row * TILE_K + col + i] = f32(input[base + i]);
@@ -143,10 +141,7 @@ fn main(
143
  let rows = tile_meta[1u + tile * 3u + 2u];
144
  let weight_base = wid.y * TILE_COLS;
145
 
146
- for (var m = local_idx; m < TILE_ROWS; m = m + {{ sgmatWorkgroup }}u) {
147
- row_slot[m] = select(0u, slot_list[slice_base + m], m < rows);
148
- }
149
- workgroupBarrier();
150
 
151
  let subtile_id = local_idx / sg_size;
152
  let subtile_idx = subtile_id / {{ sgmatRowSubtiles }}u;
@@ -159,20 +154,21 @@ fn main(
159
  {% endfor %}{% endfor %}
160
 
161
  for (var kidx = 0u; kidx < HIDDEN; kidx = kidx + TILE_K) {
162
- loadSHMA(kidx, rows, local_idx / 4u, local_idx % 4u);
163
  loadSHMB(expert, weight_base, kidx, local_idx / 2u, local_idx % 2u);
164
  workgroupBarrier();
165
 
166
  for (var step = 0u; step < TILE_K; step = step + 8u) {
167
- {% set operandScalar = "f32" %} let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
168
- var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset, TILE_K);
169
- var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset + 8u * TILE_K, TILE_K);
 
 
170
 
171
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
172
- var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset, TILE_K);
173
- var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 8u * TILE_K, TILE_K);
174
- var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 16u * TILE_K, TILE_K);
175
- var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 24u * TILE_K, TILE_K);
176
 
177
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
178
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
@@ -197,20 +193,20 @@ fn main(
197
  // The scratch banks are shared by both row groups, so the readback is fenced on both
198
  // sides of each collective store.
199
  {% for c in [0, 1, 2, 3] %}
200
- subgroupMatrixStore<row_major>(&scratch[subtile_id][{{ c }}], 0u, matC{{ group }}{{ c }}, 8u);
201
  {% endfor %}
202
  workgroupBarrier();
203
  {
204
  let m = base_A + {{ 8 * group }}u + row;
205
  if (m < rows) {
206
- let out_base = row_slot[m] * INTER;
207
  {% for c in [0, 1, 2, 3] %}
208
  {% if isSwiglu %}
209
  {
210
  let out_col = (out_weight_base + col + {{ 8 * c }}u) / 2u;
211
  if (out_col < INTER) {
212
- hidden_act[out_base + out_col] = activate(scratch[subtile_id][{{ c }}][row * 8u + col],
213
- scratch[subtile_id][{{ c }}][row * 8u + col2]);
214
  }
215
  }
216
  {% else %}
@@ -218,7 +214,7 @@ fn main(
218
  {
219
  let out_col = out_weight_base + {{ half }} + {{ 8 * c }}u;
220
  if (out_col < INTER) {
221
- hidden_act[out_base + out_col] = activate(scratch[subtile_id][{{ c }}][row * 8u + {{ half }}]);
222
  }
223
  }
224
  {% endfor %}
 
5
  enable chromium_experimental_subgroup_matrix;
6
  diagnostic(off, chromium.subgroup_matrix_uniformity);
7
 
8
+
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
+ // Expert-grouped FC1 and activation stage using subgroup-matrix operations.
12
+ // Routed slots arrive sorted by expert, so each tile shares one expert and
13
+ // reuses its dequantized weight tile across rows. Packed weights are
14
+ // dequantized directly into the shared B operand.
 
 
15
  //
16
  // A fused SwiGLU takes its gate from weight row 2c and its up from row 2c+1. Those are
17
  // staged as adjacent columns of one B tile rather than as two tiles. The result columns a
 
20
  const FC1_ROWS: u32 = {{ fc1Rows }}u;
21
  const INTER: u32 = {{ inter }}u;
22
  const TOP_K: u32 = {{ topK }}u;
 
23
  const TILE_COLS: u32 = 64u;
24
  const TILE_K: u32 = 32u;
25
  const SUB_COLS: u32 = 32u;
 
41
 
42
  // Decode the four-code group consumed by the staging loop. The aligned codes
43
  // share one quantization scale: blocks are powers of two of at least 16, and
44
+ // this helper requires `col` to be a multiple of four.
45
  fn fc1_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
46
  let base = expert * FC1_ROWS + row;
47
  {% if packSize == 2 %}
 
84
  {% endif %}
85
 
86
 
87
+ // The final K-loop barrier ends the A operand's lifetime. Its storage then
88
+ // holds four 8x8 output banks per subgroup, fenced by the epilogue barriers.
89
+ var<workgroup> tile_A: array<f32, {{ groupedSgmatStageElements }}>;
90
  var<workgroup> tile_B: array<f32, 64 * 32>;
 
91
 
92
  // Rows past the tile's end stage zero and are dropped at the store, so a short final
93
  // tile needs no separate arm.
94
+ fn loadSHMA(k_idx: u32, rows: u32, row: u32, c_idx: u32, a_slot: u32) {
95
  let col = c_idx * 8u;
96
+ let base = (params.tokenOffset + a_slot / TOP_K) * HIDDEN + k_idx + col;
97
  if (row < rows) {
98
  for (var i = 0u; i < 8u; i = i + 1u) {
99
  tile_A[row * TILE_K + col + i] = f32(input[base + i]);
 
141
  let rows = tile_meta[1u + tile * 3u + 2u];
142
  let weight_base = wid.y * TILE_COLS;
143
 
144
+ let a_slot = select(0u, slot_list[slice_base + local_idx / 4u], local_idx / 4u < rows);
 
 
 
145
 
146
  let subtile_id = local_idx / sg_size;
147
  let subtile_idx = subtile_id / {{ sgmatRowSubtiles }}u;
 
154
  {% endfor %}{% endfor %}
155
 
156
  for (var kidx = 0u; kidx < HIDDEN; kidx = kidx + TILE_K) {
157
+ loadSHMA(kidx, rows, local_idx / 4u, local_idx % 4u, a_slot);
158
  loadSHMB(expert, weight_base, kidx, local_idx / 2u, local_idx % 2u);
159
  workgroupBarrier();
160
 
161
  for (var step = 0u; step < TILE_K; step = step + 8u) {
162
+ {% set operandScalar = "f32" %}{% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
163
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
164
+ {% for r in range(2) %}
165
+ var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
166
+ {% endfor %}
167
 
168
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
169
+ {% for c in range(4) %}
170
+ var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u * TILE_K{% endif %}, {{ "N" if directInputs else "TILE_K" }});
171
+ {% endfor %}
 
172
 
173
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
174
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
 
193
  // The scratch banks are shared by both row groups, so the readback is fenced on both
194
  // sides of each collective store.
195
  {% for c in [0, 1, 2, 3] %}
196
+ subgroupMatrixStore<row_major>(&tile_A, (subtile_id * 4u + {{ c }}u) * 64u, matC{{ group }}{{ c }}, 8u);
197
  {% endfor %}
198
  workgroupBarrier();
199
  {
200
  let m = base_A + {{ 8 * group }}u + row;
201
  if (m < rows) {
202
+ let out_base = slot_list[slice_base + m] * INTER;
203
  {% for c in [0, 1, 2, 3] %}
204
  {% if isSwiglu %}
205
  {
206
  let out_col = (out_weight_base + col + {{ 8 * c }}u) / 2u;
207
  if (out_col < INTER) {
208
+ hidden_act[out_base + out_col] = activate(tile_A[(subtile_id * 4u + {{ c }}u) * 64u + row * 8u + col],
209
+ tile_A[(subtile_id * 4u + {{ c }}u) * 64u + row * 8u + col2]);
210
  }
211
  }
212
  {% else %}
 
214
  {
215
  let out_col = out_weight_base + {{ half }} + {{ 8 * c }}u;
216
  if (out_col < INTER) {
217
+ hidden_act[out_base + out_col] = activate(tile_A[(subtile_id * 4u + {{ c }}u) * 64u + row * 8u + {{ half }}]);
218
  }
219
  }
220
  {% endfor %}
build/webgpu/qmoe-fc1-activation-grouped.wgsl.jinja CHANGED
@@ -43,7 +43,7 @@ fn fc1_weight(expert: u32, row: u32, col: u32) -> f32 {
43
  }
44
  // Decode the four-code group consumed by the staging loop. The aligned codes
45
  // share one quantization scale: blocks are powers of two of at least 16, and
46
- // the caller guarantees `col` is a multiple of four.
47
  fn fc1_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
48
  let base = expert * FC1_ROWS + row;
49
  {% if packSize == 2 %}
 
43
  }
44
  // Decode the four-code group consumed by the staging loop. The aligned codes
45
  // share one quantization scale: blocks are powers of two of at least 16, and
46
+ // this helper requires `col` to be a multiple of four.
47
  fn fc1_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
48
  let base = expert * FC1_ROWS + row;
49
  {% if packSize == 2 %}
build/webgpu/qmoe-fc1-activation-stage.wgsl.jinja CHANGED
@@ -60,11 +60,10 @@ fn activate(value: f32) -> f32 {
60
 
61
 
62
  @compute @workgroup_size(WG, 1, 1)
63
- fn main(@builtin(global_invocation_id) gid: vec3<u32>,
64
- @builtin(num_workgroups) nwg: vec3<u32>) {
65
- // 2D-folded flat index: gid.y carries the high bits past the maxComputeWorkgroupsPerDimension
66
- // workgroup-per-dimension dispatch limit. Reduces to gid.x when nwg.y == 1.
67
- let index = gid.x + gid.y * nwg.x * WG;
68
  {% if topK > 1 %}
69
  // One activation row per routed slot rather than per token: the grid is
70
  // (token, slot, inter) and hidden_act is addressed by the same flat index.
 
60
 
61
 
62
  @compute @workgroup_size(WG, 1, 1)
63
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
64
+ // 2D-folded flat index: gid.y carries the high bits past the per-axis dispatch fold width.
65
+ // Reduces to gid.x when the dispatch does not fold.
66
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
 
67
  {% if topK > 1 %}
68
  // One activation row per routed slot rather than per token: the grid is
69
  // (token, slot, inter) and hidden_act is addressed by the same flat index.
build/webgpu/qmoe-mix-stage.wgsl.jinja CHANGED
@@ -10,11 +10,10 @@ const TOP_K: u32 = {{ topK }}u;
10
  const WG: u32 = {{ workgroupSize }}u;
11
 
12
  @compute @workgroup_size(WG, 1, 1)
13
- fn main(@builtin(global_invocation_id) gid: vec3<u32>,
14
- @builtin(num_workgroups) nwg: vec3<u32>) {
15
- // 2D-folded flat index: gid.y carries the high bits past the maxComputeWorkgroupsPerDimension
16
- // workgroup-per-dimension dispatch limit. Reduces to gid.x when nwg.y == 1.
17
- let index = gid.x + gid.y * nwg.x * WG;
18
  let total = params.tokenCount * HIDDEN;
19
  if (index >= total) {
20
  return;
 
10
  const WG: u32 = {{ workgroupSize }}u;
11
 
12
  @compute @workgroup_size(WG, 1, 1)
13
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
14
+ // 2D-folded flat index: gid.y carries the high bits past the per-axis dispatch fold width.
15
+ // Reduces to gid.x when the dispatch does not fold.
16
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
 
17
  let total = params.tokenCount * HIDDEN;
18
  if (index >= total) {
19
  return;
build/webgpu/qmoe-output-grouped-sgmat.wgsl.jinja CHANGED
@@ -5,22 +5,18 @@ enable subgroup_size_control;
5
  enable chromium_experimental_subgroup_matrix;
6
  diagnostic(off, chromium.subgroup_matrix_uniformity);
7
 
 
8
  {{ env.wgsl.resourceDeclarations }}
9
 
10
- // com.microsoft.QMoE, expert-grouped FC2 stage on the subgroup-matrix tier.
11
- //
12
- // Same decomposition as the scalar grouped stage -- the group stage has sorted routed
13
- // slots by expert, so one tile shares an expert and its weight tile is dequantized once
14
- // and reused across the tile's rows -- but the inner product runs on the matrix units.
15
- // The packed weights are dequantized straight into the shared B tile, so no float weight
16
- // matrix is ever materialized, and the accumulate stays f32, which is exact for a 4-bit
17
- // code times its scale.
18
  //
19
  // The M axis is the tile's routed-slot slice rather than a contiguous row range, so the
20
  // A loader gathers through `slot_list` and the store scatters back through it.
21
  const HIDDEN: u32 = {{ hidden }}u;
22
  const INTER: u32 = {{ inter }}u;
23
- const TILE_ROWS: u32 = {{ groupTileM }}u;
24
  const TILE_COLS: u32 = 64u;
25
  const TILE_K: u32 = 32u;
26
  const SUB_COLS: u32 = 32u;
@@ -39,7 +35,7 @@ const FC2_BLOCKS: u32 = {{ fc2ScaleBlocks }}u;
39
 
40
  // Decode the four-code group consumed by the staging loop. The aligned codes
41
  // share one quantization scale: blocks are powers of two of at least 16, and
42
- // the caller guarantees `col` is a multiple of four.
43
  fn fc2_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
44
  let base = expert * HIDDEN + row;
45
  {% if packSize == 2 %}
@@ -62,16 +58,16 @@ fn fc2_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
62
  }
63
 
64
 
65
- var<workgroup> row_slot: array<u32, {{ groupTileM }}>;
66
- var<workgroup> tile_A: array<f32, {{ groupTileM }} * 32>;
 
67
  var<workgroup> tile_B: array<f32, 64 * 32>;
68
- var<workgroup> scratch: array<array<array<f32, 64>, 4>, {{ sgmatSubgroups }}>;
69
 
70
  // Rows past the tile's end stage zero and are dropped at the store, so a short final
71
  // tile needs no separate arm.
72
- fn loadSHMA(k_idx: u32, rows: u32, row: u32, c_idx: u32) {
73
  let col = c_idx * 8u;
74
- let base = row_slot[row] * INTER + k_idx + col;
75
  if (row < rows) {
76
  for (var i = 0u; i < 8u; i = i + 1u) {
77
  tile_A[row * TILE_K + col + i] = hidden_act[base + i];
@@ -105,15 +101,15 @@ fn loadSHMB(expert: u32, col_base: u32, k_idx: u32, row: u32, c_idx: u32) {
105
  fn storeCol(col_base: u32, row: u32, cc: u32, slot: u32, src_slot: u32, out_slot: u32) {
106
  let c = cc + slot * 8u;
107
  if (col_base + c < HIDDEN) {
108
- slot_out[out_slot * HIDDEN + col_base + c] = scratch[src_slot][slot][row * 8u + cc];
109
  }
110
  }
111
 
112
- fn storeOutput(col_base: u32, row: u32, col: u32, src_slot: u32, row_base: u32, rows: u32) {
113
  let m = row_base + row;
114
  if (m < rows) {
115
  let col2 = col + 1u;
116
- let out_slot = row_slot[m];
117
  {% for blockCol in [0, 1, 2, 3] %}
118
  storeCol(col_base, row, col, {{ blockCol }}u, src_slot, out_slot);
119
  storeCol(col_base, row, col2, {{ blockCol }}u, src_slot, out_slot);
@@ -137,10 +133,7 @@ fn main(
137
  let rows = tile_meta[1u + tile * 3u + 2u];
138
  let col_base = wid.y * TILE_COLS;
139
 
140
- for (var m = local_idx; m < TILE_ROWS; m = m + {{ sgmatWorkgroup }}u) {
141
- row_slot[m] = select(0u, slot_list[slice_base + m], m < rows);
142
- }
143
- workgroupBarrier();
144
 
145
  let subtile_id = local_idx / sg_size;
146
  let subtile_idx = subtile_id / {{ sgmatRowSubtiles }}u;
@@ -153,20 +146,21 @@ fn main(
153
  {% endfor %}{% endfor %}
154
 
155
  for (var kidx = 0u; kidx < INTER; kidx = kidx + TILE_K) {
156
- loadSHMA(kidx, rows, local_idx / 4u, local_idx % 4u);
157
  loadSHMB(expert, col_base, kidx, local_idx / 2u, local_idx % 2u);
158
  workgroupBarrier();
159
 
160
  for (var step = 0u; step < TILE_K; step = step + 8u) {
161
- {% set operandScalar = "f32" %} let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
162
- var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset, TILE_K);
163
- var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset + 8u * TILE_K, TILE_K);
 
 
164
 
165
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
166
- var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset, TILE_K);
167
- var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 8u * TILE_K, TILE_K);
168
- var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 16u * TILE_K, TILE_K);
169
- var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 24u * TILE_K, TILE_K);
170
 
171
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
172
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
@@ -183,17 +177,17 @@ fn main(
183
  // Each scratch bank is written by a collective store and then read across lanes, and
184
  // the two row groups reuse the same banks, so both barriers are load-bearing.
185
  {% for col in [0, 1, 2, 3] %}
186
- subgroupMatrixStore<row_major>(&scratch[subtile_id][{{ col }}], 0u, matC0{{ col }}, 8u);
187
  {% endfor %}
188
  workgroupBarrier();
189
  let row = sg_id / 4u;
190
  let col = (sg_id % 4u) * 2u;
191
- storeOutput(col_base + base_B, row, col, subtile_id, base_A, rows);
192
  workgroupBarrier();
193
 
194
  {% for col in [0, 1, 2, 3] %}
195
- subgroupMatrixStore<row_major>(&scratch[subtile_id][{{ col }}], 0u, matC1{{ col }}, 8u);
196
  {% endfor %}
197
  workgroupBarrier();
198
- storeOutput(col_base + base_B, row, col, subtile_id, base_A + 8u, rows);
199
  }
 
5
  enable chromium_experimental_subgroup_matrix;
6
  diagnostic(off, chromium.subgroup_matrix_uniformity);
7
 
8
+
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
+ // Expert-grouped FC2 stage using subgroup-matrix operations. Routed slots are
12
+ // sorted by expert, so each tile shares one expert and reuses its dequantized
13
+ // weight tile across rows. Packed weights are dequantized directly into the
14
+ // shared B tile, and accumulation remains in f32.
 
 
 
 
15
  //
16
  // The M axis is the tile's routed-slot slice rather than a contiguous row range, so the
17
  // A loader gathers through `slot_list` and the store scatters back through it.
18
  const HIDDEN: u32 = {{ hidden }}u;
19
  const INTER: u32 = {{ inter }}u;
 
20
  const TILE_COLS: u32 = 64u;
21
  const TILE_K: u32 = 32u;
22
  const SUB_COLS: u32 = 32u;
 
35
 
36
  // Decode the four-code group consumed by the staging loop. The aligned codes
37
  // share one quantization scale: blocks are powers of two of at least 16, and
38
+ // this helper requires `col` to be a multiple of four.
39
  fn fc2_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
40
  let base = expert * HIDDEN + row;
41
  {% if packSize == 2 %}
 
58
  }
59
 
60
 
61
+ // The final K-loop barrier ends the A operand's lifetime. Its storage then
62
+ // holds four 8x8 output banks per subgroup, fenced by the epilogue barriers.
63
+ var<workgroup> tile_A: array<f32, {{ groupedSgmatStageElements }}>;
64
  var<workgroup> tile_B: array<f32, 64 * 32>;
 
65
 
66
  // Rows past the tile's end stage zero and are dropped at the store, so a short final
67
  // tile needs no separate arm.
68
+ fn loadSHMA(k_idx: u32, rows: u32, row: u32, c_idx: u32, a_slot: u32) {
69
  let col = c_idx * 8u;
70
+ let base = a_slot * INTER + k_idx + col;
71
  if (row < rows) {
72
  for (var i = 0u; i < 8u; i = i + 1u) {
73
  tile_A[row * TILE_K + col + i] = hidden_act[base + i];
 
101
  fn storeCol(col_base: u32, row: u32, cc: u32, slot: u32, src_slot: u32, out_slot: u32) {
102
  let c = cc + slot * 8u;
103
  if (col_base + c < HIDDEN) {
104
+ slot_out[out_slot * HIDDEN + col_base + c] = tile_A[(src_slot * 4u + slot) * 64u + row * 8u + cc];
105
  }
106
  }
107
 
108
+ fn storeOutput(col_base: u32, row: u32, col: u32, src_slot: u32, row_base: u32, rows: u32, slice_base: u32) {
109
  let m = row_base + row;
110
  if (m < rows) {
111
  let col2 = col + 1u;
112
+ let out_slot = slot_list[slice_base + m];
113
  {% for blockCol in [0, 1, 2, 3] %}
114
  storeCol(col_base, row, col, {{ blockCol }}u, src_slot, out_slot);
115
  storeCol(col_base, row, col2, {{ blockCol }}u, src_slot, out_slot);
 
133
  let rows = tile_meta[1u + tile * 3u + 2u];
134
  let col_base = wid.y * TILE_COLS;
135
 
136
+ let a_slot = select(0u, slot_list[slice_base + local_idx / 4u], local_idx / 4u < rows);
 
 
 
137
 
138
  let subtile_id = local_idx / sg_size;
139
  let subtile_idx = subtile_id / {{ sgmatRowSubtiles }}u;
 
146
  {% endfor %}{% endfor %}
147
 
148
  for (var kidx = 0u; kidx < INTER; kidx = kidx + TILE_K) {
149
+ loadSHMA(kidx, rows, local_idx / 4u, local_idx % 4u, a_slot);
150
  loadSHMB(expert, col_base, kidx, local_idx / 2u, local_idx % 2u);
151
  workgroupBarrier();
152
 
153
  for (var step = 0u; step < TILE_K; step = step + 8u) {
154
+ {% set operandScalar = "f32" %}{% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
155
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
156
+ {% for r in range(2) %}
157
+ var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
158
+ {% endfor %}
159
 
160
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
161
+ {% for c in range(4) %}
162
+ var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u * TILE_K{% endif %}, {{ "N" if directInputs else "TILE_K" }});
163
+ {% endfor %}
 
164
 
165
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
166
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
 
177
  // Each scratch bank is written by a collective store and then read across lanes, and
178
  // the two row groups reuse the same banks, so both barriers are load-bearing.
179
  {% for col in [0, 1, 2, 3] %}
180
+ subgroupMatrixStore<row_major>(&tile_A, (subtile_id * 4u + {{ col }}u) * 64u, matC0{{ col }}, 8u);
181
  {% endfor %}
182
  workgroupBarrier();
183
  let row = sg_id / 4u;
184
  let col = (sg_id % 4u) * 2u;
185
+ storeOutput(col_base + base_B, row, col, subtile_id, base_A, rows, slice_base);
186
  workgroupBarrier();
187
 
188
  {% for col in [0, 1, 2, 3] %}
189
+ subgroupMatrixStore<row_major>(&tile_A, (subtile_id * 4u + {{ col }}u) * 64u, matC1{{ col }}, 8u);
190
  {% endfor %}
191
  workgroupBarrier();
192
+ storeOutput(col_base + base_B, row, col, subtile_id, base_A + 8u, rows, slice_base);
193
  }
build/webgpu/qmoe-output-grouped.wgsl.jinja CHANGED
@@ -41,7 +41,7 @@ fn fc2_weight(expert: u32, row: u32, col: u32) -> f32 {
41
  }
42
  // Decode the four-code group consumed by the staging loop. The aligned codes
43
  // share one quantization scale: blocks are powers of two of at least 16, and
44
- // the caller guarantees `col` is a multiple of four.
45
  fn fc2_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
46
  let base = expert * HIDDEN + row;
47
  {% if packSize == 2 %}
 
41
  }
42
  // Decode the four-code group consumed by the staging loop. The aligned codes
43
  // share one quantization scale: blocks are powers of two of at least 16, and
44
+ // this helper requires `col` to be a multiple of four.
45
  fn fc2_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
46
  let base = expert * HIDDEN + row;
47
  {% if packSize == 2 %}
build/webgpu/qmoe-output-stage.wgsl.jinja CHANGED
@@ -35,11 +35,10 @@ fn fc2_weight(expert: u32, row: u32, col: u32) -> f32 {
35
 
36
 
37
  @compute @workgroup_size(WG, 1, 1)
38
- fn main(@builtin(global_invocation_id) gid: vec3<u32>,
39
- @builtin(num_workgroups) nwg: vec3<u32>) {
40
- // 2D-folded flat index: gid.y carries the high bits past the maxComputeWorkgroupsPerDimension
41
- // workgroup-per-dimension dispatch limit. Reduces to gid.x when nwg.y == 1.
42
- let index = gid.x + gid.y * nwg.x * WG;
43
  let total = params.tokenCount * HIDDEN;
44
  if (index >= total) {
45
  return;
 
35
 
36
 
37
  @compute @workgroup_size(WG, 1, 1)
38
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
39
+ // 2D-folded flat index: gid.y carries the high bits past the per-axis dispatch fold width.
40
+ // Reduces to gid.x when the dispatch does not fold.
41
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
 
42
  let total = params.tokenCount * HIDDEN;
43
  if (index >= total) {
44
  return;
build/webgpu/qmoe-output-zero-inter.wgsl.jinja CHANGED
@@ -3,10 +3,9 @@
3
  const WG: u32 = {{ workgroupSize }}u;
4
 
5
  @compute @workgroup_size(WG, 1, 1)
6
- fn main(@builtin(global_invocation_id) gid: vec3<u32>,
7
- @builtin(num_workgroups) nwg: vec3<u32>) {
8
- let index = gid.x + gid.y * nwg.x * WG;
9
- if (index < {{ source.outputElementCount }}u) {
10
  output[index] = 0.0;
11
  }
12
  }
 
3
  const WG: u32 = {{ workgroupSize }}u;
4
 
5
  @compute @workgroup_size(WG, 1, 1)
6
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
7
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
8
+ if (index < {{ outputElementCount }}u) {
 
9
  output[index] = 0.0;
10
  }
11
  }
build/webgpu/qmoe-route-stage.wgsl.jinja CHANGED
@@ -79,11 +79,10 @@ fn top1_route(token: u32) -> Top1Route {
79
  {% endif %}
80
 
81
  @compute @workgroup_size(WG, 1, 1)
82
- fn main(@builtin(global_invocation_id) gid: vec3<u32>,
83
- @builtin(num_workgroups) nwg: vec3<u32>) {
84
- // 2D-folded flat index: gid.y carries the high bits past the maxComputeWorkgroupsPerDimension
85
- // workgroup-per-dimension dispatch limit. Reduces to gid.x when nwg.y == 1.
86
- let token = gid.x + gid.y * nwg.x * WG;
87
  if (token >= TOKENS) {
88
  return;
89
  }
 
79
  {% endif %}
80
 
81
  @compute @workgroup_size(WG, 1, 1)
82
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
83
+ // 2D-folded flat index: gid.y carries the high bits past the per-axis dispatch fold width.
84
+ // Reduces to gid.x when the dispatch does not fold.
85
+ let token = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
 
86
  if (token >= TOKENS) {
87
  return;
88
  }
build/webgpu/test.json CHANGED
@@ -1,10 +1,9 @@
1
  {
2
- "op": "com.microsoft.QMoE",
3
  "cases": [
4
  {
5
  "name": "q4_colwise_default_relu",
6
  "provenance": {
7
- "notes": "Exercises every exact default at once: 4-bit integer weights, k=1, ReLU, column-wise scales, dense routing, and raw provider layout."
8
  },
9
  "inputs": {
10
  "inputT": {
@@ -39,7 +38,7 @@
39
  {
40
  "name": "q4_top1_tie_selects_higher_expert",
41
  "provenance": {
42
- "notes": "Pins ONNX Runtime's descending `(logit, expert_index)` ordering with selected-weight normalization: equal logits select expert 1, whose hand-computed output is [16, 32], rather than the all-zero expert 0."
43
  },
44
  "attrs": { "normalize_routing_weights": 1 },
45
  "inputs": {
@@ -227,28 +226,16 @@
227
  {
228
  "name": "q4_zero_inter",
229
  "provenance": {
230
- "notes": "A zero intermediate dimension has an exact zero result without binding empty weight buffers in a shader."
231
  },
232
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1 },
233
  "inputs": {
234
- "inputT": {
235
- "dtype": "float32",
236
- "shape": [3, 4],
237
- "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.3 }
238
- },
239
- "routerT": {
240
- "dtype": "float32",
241
- "shape": [3, 3],
242
- "data": { "kind": "fillFloat32", "sinStep": 0.37, "cosStep": 0.19, "scale": 0.7 }
243
- },
244
  "fc1T": { "dtype": "uint8", "shape": [3, 0, 2], "data": { "kind": "values", "values": [] } },
245
  "fc1ScalesT": { "dtype": "float32", "shape": [3, 0], "data": { "kind": "values", "values": [] } },
246
  "fc2T": { "dtype": "uint8", "shape": [3, 4, 0], "data": { "kind": "values", "values": [] } },
247
- "fc2ScalesT": {
248
- "dtype": "float32",
249
- "shape": [3, 4],
250
- "data": { "kind": "linspace", "start": 0.008, "end": 0.037 }
251
- }
252
  },
253
  "outputs": {
254
  "outputT": { "dtype": "float32", "shape": [3, 4], "data": { "kind": "constant", "value": 0.0 }, "tolerance": 0 }
@@ -274,7 +261,7 @@
274
  {
275
  "name": "q4_colwise_relu_deep_reduction",
276
  "provenance": {
277
- "notes": "A 128-long reduction on both projections, deep enough that splitting it across lanes is worthwhile; the shallow cases above keep the one-thread-per-column schedule."
278
  },
279
  "attrs": { "weights_prepacked": 0 },
280
  "inputs": {
@@ -527,7 +514,7 @@
527
  {
528
  "name": "q4_grouped_prefill_partial_tiles",
529
  "provenance": {
530
- "notes": "Half the routed slots the grouped schedule used to demand: 3 experts share 64 slots against 32-row tiles, so most of every tile is padding and the store guard drops more rows than it keeps."
531
  },
532
  "attrs": {
533
  "activation_type": "swiglu",
@@ -573,7 +560,7 @@
573
  {
574
  "name": "q8_grouped_prefill_swiglu_blockwise",
575
  "provenance": {
576
- "notes": "Enough routed slots per expert to fill grouped tiles, so one dequantized weight tile is reused across a tile of slots. The tiled reduction sums in a different order than the reference, so the tolerance carries that f32 regrouping; outputs are order 0.4, which a 10% error would move by 0.04."
577
  },
578
  "attrs": {
579
  "activation_type": "swiglu",
@@ -617,6 +604,196 @@
617
  }
618
  },
619
  "outputs": { "outputT": { "dtype": "float32", "shape": [48, 64], "tolerance": 0.0003 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  }
621
  ]
622
  }
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "q4_colwise_default_relu",
5
  "provenance": {
6
+ "notes": "Exercises every exact default at once: 4-bit integer weights, k=1, ReLU, column-wise scales, dense routing, and the raw uint8-packed weight layout."
7
  },
8
  "inputs": {
9
  "inputT": {
 
38
  {
39
  "name": "q4_top1_tie_selects_higher_expert",
40
  "provenance": {
41
+ "notes": "Descending `(logit, expert_index)` ordering with selected-weight normalization: equal logits select expert 1, whose output is [16, 32], rather than the all-zero expert 0."
42
  },
43
  "attrs": { "normalize_routing_weights": 1 },
44
  "inputs": {
 
226
  {
227
  "name": "q4_zero_inter",
228
  "provenance": {
229
+ "notes": "A zero intermediate dimension has an exact zero result without binding empty weight buffers in a shader. No token, routing or scale value can reach the result once the intermediate width is zero, so the fill values are uniform."
230
  },
231
  "attrs": { "activation_type": "swiglu", "swiglu_fusion": 1 },
232
  "inputs": {
233
+ "inputT": { "dtype": "float32", "shape": [3, 4], "data": { "kind": "constant", "value": 0.3 } },
234
+ "routerT": { "dtype": "float32", "shape": [3, 3], "data": { "kind": "constant", "value": 0.7 } },
 
 
 
 
 
 
 
 
235
  "fc1T": { "dtype": "uint8", "shape": [3, 0, 2], "data": { "kind": "values", "values": [] } },
236
  "fc1ScalesT": { "dtype": "float32", "shape": [3, 0], "data": { "kind": "values", "values": [] } },
237
  "fc2T": { "dtype": "uint8", "shape": [3, 4, 0], "data": { "kind": "values", "values": [] } },
238
+ "fc2ScalesT": { "dtype": "float32", "shape": [3, 4], "data": { "kind": "constant", "value": 0.02 } }
 
 
 
 
239
  },
240
  "outputs": {
241
  "outputT": { "dtype": "float32", "shape": [3, 4], "data": { "kind": "constant", "value": 0.0 }, "tolerance": 0 }
 
261
  {
262
  "name": "q4_colwise_relu_deep_reduction",
263
  "provenance": {
264
+ "notes": "A 128-element reduction on both projections exercises the cooperative multi-lane schedule for q4 weights."
265
  },
266
  "attrs": { "weights_prepacked": 0 },
267
  "inputs": {
 
514
  {
515
  "name": "q4_grouped_prefill_partial_tiles",
516
  "provenance": {
517
+ "notes": "Three experts share 64 routed slots across 32-row tiles, so most lanes address padding and the store guard discards more rows than it writes."
518
  },
519
  "attrs": {
520
  "activation_type": "swiglu",
 
560
  {
561
  "name": "q8_grouped_prefill_swiglu_blockwise",
562
  "provenance": {
563
+ "notes": "Enough routed slots per expert fill grouped tiles, allowing each dequantized weight tile to serve several slots. The tolerance covers f32 reassociation in the tiled reduction while remaining far below a 10% error on outputs near 0.4."
564
  },
565
  "attrs": {
566
  "activation_type": "swiglu",
 
604
  }
605
  },
606
  "outputs": { "outputT": { "dtype": "float32", "shape": [48, 64], "tolerance": 0.0003 } }
607
+ },
608
+ {
609
+ "name": "q4_grouped_stage_t33_h96_i96_block0_swiglu",
610
+ "attrs": {
611
+ "activation_type": "swiglu",
612
+ "expert_weight_bits": 4,
613
+ "block_size": 0,
614
+ "k": 2,
615
+ "normalize_routing_weights": 1,
616
+ "weights_prepacked": 0,
617
+ "swiglu_fusion": 1
618
+ },
619
+ "inputs": {
620
+ "inputT": {
621
+ "dtype": "float32",
622
+ "shape": [33, 96],
623
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.17, "scale": 0.05 }
624
+ },
625
+ "routerT": {
626
+ "dtype": "float32",
627
+ "shape": [33, 3],
628
+ "data": { "kind": "fillFloat32", "sinStep": 0.44, "cosStep": 0.12, "scale": 0.6 }
629
+ },
630
+ "fc1T": {
631
+ "dtype": "uint8",
632
+ "shape": [3, 192, 48],
633
+ "data": { "kind": "cycle", "values": [97, 182, 45, 213, 131, 68, 159, 22, 241] }
634
+ },
635
+ "fc2T": {
636
+ "dtype": "uint8",
637
+ "shape": [3, 96, 48],
638
+ "data": { "kind": "cycle", "values": [241, 22, 159, 68, 131, 213, 45, 182, 97] }
639
+ },
640
+ "fc1ScalesT": {
641
+ "dtype": "float32",
642
+ "shape": [3, 192],
643
+ "data": { "kind": "linspace", "start": 0.002, "end": 0.02 }
644
+ },
645
+ "fc2ScalesT": {
646
+ "dtype": "float32",
647
+ "shape": [3, 96],
648
+ "data": { "kind": "linspace", "start": 0.003, "end": 0.018 }
649
+ }
650
+ },
651
+ "outputs": { "outputT": { "dtype": "float32", "shape": [33, 96], "tolerance": 0.000002 } },
652
+ "provenance": {
653
+ "notes": "Cooperative quantized weight staging across partial 64-column output tiles and routed row tails, with 4/8-bit codes and column/block scales. Small deterministic inputs keep the existing strict f32 tolerance meaningful."
654
+ }
655
+ },
656
+ {
657
+ "name": "q4_grouped_stage_t31_h96_i32_block16_relu",
658
+ "attrs": {
659
+ "activation_type": "relu",
660
+ "expert_weight_bits": 4,
661
+ "block_size": 16,
662
+ "k": 2,
663
+ "normalize_routing_weights": 1,
664
+ "weights_prepacked": 0
665
+ },
666
+ "inputs": {
667
+ "inputT": {
668
+ "dtype": "float32",
669
+ "shape": [31, 96],
670
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.17, "scale": 0.05 }
671
+ },
672
+ "routerT": {
673
+ "dtype": "float32",
674
+ "shape": [31, 3],
675
+ "data": { "kind": "fillFloat32", "sinStep": 0.44, "cosStep": 0.12, "scale": 0.6 }
676
+ },
677
+ "fc1T": {
678
+ "dtype": "uint8",
679
+ "shape": [3, 32, 48],
680
+ "data": { "kind": "cycle", "values": [97, 182, 45, 213, 131, 68, 159, 22, 241] }
681
+ },
682
+ "fc2T": {
683
+ "dtype": "uint8",
684
+ "shape": [3, 96, 16],
685
+ "data": { "kind": "cycle", "values": [241, 22, 159, 68, 131, 213, 45, 182, 97] }
686
+ },
687
+ "fc1ScalesT": {
688
+ "dtype": "float32",
689
+ "shape": [3, 32, 6],
690
+ "data": { "kind": "linspace", "start": 0.002, "end": 0.02 }
691
+ },
692
+ "fc2ScalesT": {
693
+ "dtype": "float32",
694
+ "shape": [3, 96, 2],
695
+ "data": { "kind": "linspace", "start": 0.003, "end": 0.018 }
696
+ }
697
+ },
698
+ "outputs": { "outputT": { "dtype": "float32", "shape": [31, 96], "tolerance": 0.00005 } },
699
+ "provenance": {
700
+ "notes": "Cooperative quantized weight staging across partial 64-column output tiles and routed row tails, with 4/8-bit codes and column/block scales. Small deterministic inputs keep the existing strict f32 tolerance meaningful."
701
+ }
702
+ },
703
+ {
704
+ "name": "q8_grouped_stage_t65_h128_i96_block32_swiglu",
705
+ "attrs": {
706
+ "activation_type": "swiglu",
707
+ "expert_weight_bits": 8,
708
+ "block_size": 32,
709
+ "k": 2,
710
+ "normalize_routing_weights": 1,
711
+ "weights_prepacked": 0,
712
+ "swiglu_fusion": 1
713
+ },
714
+ "inputs": {
715
+ "inputT": {
716
+ "dtype": "float32",
717
+ "shape": [65, 128],
718
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.17, "scale": 0.05 }
719
+ },
720
+ "routerT": {
721
+ "dtype": "float32",
722
+ "shape": [65, 4],
723
+ "data": { "kind": "fillFloat32", "sinStep": 0.44, "cosStep": 0.12, "scale": 0.6 }
724
+ },
725
+ "fc1T": {
726
+ "dtype": "uint8",
727
+ "shape": [4, 192, 128],
728
+ "data": { "kind": "cycle", "values": [97, 182, 45, 213, 131, 68, 159, 22, 241] }
729
+ },
730
+ "fc2T": {
731
+ "dtype": "uint8",
732
+ "shape": [4, 128, 96],
733
+ "data": { "kind": "cycle", "values": [241, 22, 159, 68, 131, 213, 45, 182, 97] }
734
+ },
735
+ "fc1ScalesT": {
736
+ "dtype": "float32",
737
+ "shape": [4, 192, 4],
738
+ "data": { "kind": "linspace", "start": 0.002, "end": 0.02 }
739
+ },
740
+ "fc2ScalesT": {
741
+ "dtype": "float32",
742
+ "shape": [4, 128, 3],
743
+ "data": { "kind": "linspace", "start": 0.003, "end": 0.018 }
744
+ }
745
+ },
746
+ "outputs": { "outputT": { "dtype": "float32", "shape": [65, 128], "tolerance": 0.00005 } },
747
+ "provenance": {
748
+ "notes": "Cooperative quantized weight staging across partial 64-column output tiles and routed row tails, with 4/8-bit codes and column/block scales. Small deterministic inputs keep the existing strict f32 tolerance meaningful."
749
+ }
750
+ },
751
+ {
752
+ "name": "q8_grouped_stage_t32_h96_i160_block0_relu",
753
+ "attrs": {
754
+ "activation_type": "relu",
755
+ "expert_weight_bits": 8,
756
+ "block_size": 0,
757
+ "k": 1,
758
+ "normalize_routing_weights": 1,
759
+ "weights_prepacked": 0
760
+ },
761
+ "inputs": {
762
+ "inputT": {
763
+ "dtype": "float32",
764
+ "shape": [32, 96],
765
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.17, "scale": 0.05 }
766
+ },
767
+ "routerT": {
768
+ "dtype": "float32",
769
+ "shape": [32, 3],
770
+ "data": { "kind": "fillFloat32", "sinStep": 0.44, "cosStep": 0.12, "scale": 0.6 }
771
+ },
772
+ "fc1T": {
773
+ "dtype": "uint8",
774
+ "shape": [3, 160, 96],
775
+ "data": { "kind": "cycle", "values": [97, 182, 45, 213, 131, 68, 159, 22, 241] }
776
+ },
777
+ "fc2T": {
778
+ "dtype": "uint8",
779
+ "shape": [3, 96, 160],
780
+ "data": { "kind": "cycle", "values": [241, 22, 159, 68, 131, 213, 45, 182, 97] }
781
+ },
782
+ "fc1ScalesT": {
783
+ "dtype": "float32",
784
+ "shape": [3, 160],
785
+ "data": { "kind": "linspace", "start": 0.002, "end": 0.02 }
786
+ },
787
+ "fc2ScalesT": {
788
+ "dtype": "float32",
789
+ "shape": [3, 96],
790
+ "data": { "kind": "linspace", "start": 0.003, "end": 0.018 }
791
+ }
792
+ },
793
+ "outputs": { "outputT": { "dtype": "float32", "shape": [32, 96], "tolerance": 0.00005 } },
794
+ "provenance": {
795
+ "notes": "Cooperative quantized weight staging across partial 64-column output tiles and routed row tails, with 4/8-bit codes and column/block scales. Small deterministic inputs keep the existing strict f32 tolerance meaningful."
796
+ }
797
  }
798
  ]
799
  }