sync 6fdf6301e2bb
Browse files- README.md +3 -3
- build/webgpu/{qmoe-mix-stage.wgsl.jinja → expert-slot-mix.wgsl.jinja} +6 -3
- build/webgpu/manifest.json +69 -81
- build/webgpu/metadata.json +17 -17
- build/webgpu/qmoe-fc1-activation-gemv.wgsl.jinja +22 -22
- build/webgpu/qmoe-fc1-activation-grouped-sgmat.wgsl.jinja +25 -21
- build/webgpu/qmoe-fc1-activation-grouped.wgsl.jinja +25 -26
- build/webgpu/qmoe-fc1-activation-stage.wgsl.jinja +48 -21
- build/webgpu/qmoe-output-gemv.wgsl.jinja +22 -21
- build/webgpu/qmoe-output-grouped-sgmat.wgsl.jinja +25 -20
- build/webgpu/qmoe-output-grouped.wgsl.jinja +25 -34
- build/webgpu/qmoe-output-stage.wgsl.jinja +47 -19
- build/webgpu/qmoe-route-stage.wgsl.jinja +9 -9
- build/webgpu/test.json +0 -0
README.md
CHANGED
|
@@ -75,13 +75,13 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
|
|
| 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
|
| 79 |
- [`expert-group-slots.wgsl.jinja`](build/webgpu/expert-group-slots.wgsl.jinja)
|
|
|
|
| 80 |
- [`qmoe-fc1-activation-gemv.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-gemv.wgsl.jinja)
|
| 81 |
- [`qmoe-fc1-activation-grouped-sgmat.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-grouped-sgmat.wgsl.jinja)
|
| 82 |
- [`qmoe-fc1-activation-grouped.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-grouped.wgsl.jinja)
|
| 83 |
- [`qmoe-fc1-activation-stage.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-stage.wgsl.jinja)
|
| 84 |
-
- [`qmoe-mix-stage.wgsl.jinja`](build/webgpu/qmoe-mix-stage.wgsl.jinja)
|
| 85 |
- [`qmoe-output-gemv.wgsl.jinja`](build/webgpu/qmoe-output-gemv.wgsl.jinja)
|
| 86 |
- [`qmoe-output-grouped-sgmat.wgsl.jinja`](build/webgpu/qmoe-output-grouped-sgmat.wgsl.jinja)
|
| 87 |
- [`qmoe-output-grouped.wgsl.jinja`](build/webgpu/qmoe-output-grouped.wgsl.jinja)
|
|
@@ -92,7 +92,7 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
|
|
| 92 |
## Use with `@huggingface/kernels`
|
| 93 |
|
| 94 |
```sh
|
| 95 |
-
npm install --save-exact @huggingface/kernels@0.0.1-preview.
|
| 96 |
```
|
| 97 |
|
| 98 |
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
|
|
|
| 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 cases
|
| 79 |
- [`expert-group-slots.wgsl.jinja`](build/webgpu/expert-group-slots.wgsl.jinja)
|
| 80 |
+
- [`expert-slot-mix.wgsl.jinja`](build/webgpu/expert-slot-mix.wgsl.jinja)
|
| 81 |
- [`qmoe-fc1-activation-gemv.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-gemv.wgsl.jinja)
|
| 82 |
- [`qmoe-fc1-activation-grouped-sgmat.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-grouped-sgmat.wgsl.jinja)
|
| 83 |
- [`qmoe-fc1-activation-grouped.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-grouped.wgsl.jinja)
|
| 84 |
- [`qmoe-fc1-activation-stage.wgsl.jinja`](build/webgpu/qmoe-fc1-activation-stage.wgsl.jinja)
|
|
|
|
| 85 |
- [`qmoe-output-gemv.wgsl.jinja`](build/webgpu/qmoe-output-gemv.wgsl.jinja)
|
| 86 |
- [`qmoe-output-grouped-sgmat.wgsl.jinja`](build/webgpu/qmoe-output-grouped-sgmat.wgsl.jinja)
|
| 87 |
- [`qmoe-output-grouped.wgsl.jinja`](build/webgpu/qmoe-output-grouped.wgsl.jinja)
|
|
|
|
| 92 |
## Use with `@huggingface/kernels`
|
| 93 |
|
| 94 |
```sh
|
| 95 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.3
|
| 96 |
```
|
| 97 |
|
| 98 |
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
build/webgpu/{qmoe-mix-stage.wgsl.jinja → expert-slot-mix.wgsl.jinja}
RENAMED
|
@@ -1,3 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
|
| 3 |
// Routed sum for the grouped schedule. The grouped FC2 stage produces one projected row per
|
|
@@ -11,9 +16,7 @@ const WG: u32 = {{ workgroupSize }}u;
|
|
| 11 |
|
| 12 |
@compute @workgroup_size(WG, 1, 1)
|
| 13 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 14 |
-
|
| 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;
|
|
|
|
| 1 |
+
{% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
|
| 2 |
+
{% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
|
| 3 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 4 |
+
// per-axis workgroup fold width.
|
| 5 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};{% endmacro %}
|
| 6 |
{{ env.wgsl.resourceDeclarations }}
|
| 7 |
|
| 8 |
// Routed sum for the grouped schedule. The grouped FC2 stage produces one projected row per
|
|
|
|
| 16 |
|
| 17 |
@compute @workgroup_size(WG, 1, 1)
|
| 18 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 19 |
+
{{ flat_index_2d("WG", "index", "") }}
|
|
|
|
|
|
|
| 20 |
let total = params.tokenCount * HIDDEN;
|
| 21 |
if (index >= total) {
|
| 22 |
return;
|
build/webgpu/manifest.json
CHANGED
|
@@ -52,9 +52,28 @@
|
|
| 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",
|
|
@@ -74,19 +93,13 @@
|
|
| 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))))",
|
| 80 |
"hiddenChunkCount": "max(1, ceilDiv(numTokens, max(1, hiddenChunkTokens)))",
|
| 81 |
"routeScratchBytes": "numTokens * topK * 4",
|
| 82 |
"routedScratchFits": "routeScratchBytes <= device.limits.maxStorageBufferBindingSize and routeScratchBytes <= device.limits.maxBufferSize",
|
| 83 |
-
"groupTileM": "tunables.groupThreads * tunables.groupRegM",
|
| 84 |
-
"groupTileN": "tunables.groupThreads * tunables.groupRegN",
|
| 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)",
|
|
@@ -96,43 +109,35 @@
|
|
| 96 |
"sgmatWorkgroup": "128",
|
| 97 |
"sgmatSubgroups": "4",
|
| 98 |
"sgmatRowSubtiles": "2",
|
|
|
|
| 99 |
"groupedSgmatStageElements": "max(groupTileM * 32, sgmatSubgroups * 4 * 64)",
|
| 100 |
-
"groupedSgmatSharedBytes": "(groupedSgmatStageElements +
|
| 101 |
-
"groupedSgmatOk": "groupedContract and groupTileM == 32 and interSize % 32 == 0 and groupedSgmatSharedBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(hiddenSize,
|
| 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 |
-
"
|
| 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", "
|
| 123 |
-
"router_probs": { "arg": "routerT", "
|
| 124 |
-
"route_expert": { "scratch": "routeExpert", "
|
| 125 |
-
"route_mix": { "scratch": "routeMix", "
|
| 126 |
-
"
|
| 127 |
"scratch": "routeExpert",
|
| 128 |
"name": "route_expert",
|
| 129 |
"buffer": "read-only-storage",
|
| 130 |
"elementType": "u32"
|
| 131 |
},
|
| 132 |
-
"slot_list": { "scratch": "slotList", "
|
| 133 |
-
"tile_meta": { "scratch": "tileMeta", "
|
|
|
|
| 134 |
"params": {
|
| 135 |
-
"buffer": "uniform",
|
| 136 |
"struct": [
|
| 137 |
{ "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
|
| 138 |
{
|
|
@@ -142,32 +147,31 @@
|
|
| 142 |
}
|
| 143 |
]
|
| 144 |
},
|
| 145 |
-
"input": { "arg": "inputT", "
|
| 146 |
-
"
|
| 147 |
-
"
|
| 148 |
-
"fc1_experts_weights": { "arg": "fc1T", "
|
| 149 |
-
"fc1_scales": { "arg": "fc1ScalesT", "
|
| 150 |
-
"hidden_act": { "scratch": "hiddenAct", "
|
| 151 |
-
"
|
| 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 |
-
"
|
| 161 |
"scratch": "hiddenAct",
|
| 162 |
"name": "hidden_act",
|
| 163 |
"buffer": "read-only-storage",
|
| 164 |
"elementType": "f32"
|
| 165 |
},
|
| 166 |
-
"fc2_experts_weights": { "arg": "fc2T", "
|
| 167 |
-
"fc2_scales": { "arg": "fc2ScalesT", "
|
| 168 |
-
"slot_out": { "scratch": "slotOut", "
|
| 169 |
-
"
|
| 170 |
-
"
|
| 171 |
},
|
| 172 |
"variants": [
|
| 173 |
{
|
|
@@ -197,7 +201,6 @@
|
|
| 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,7 +214,7 @@
|
|
| 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)",
|
|
@@ -226,32 +229,28 @@
|
|
| 226 |
"id": "group_stage",
|
| 227 |
"name": "QMoE.GroupStage",
|
| 228 |
"shader": "expert-group-slots.wgsl.jinja",
|
| 229 |
-
"bindings": ["
|
| 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", "
|
| 241 |
-
"dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(interSize * fusionSize,
|
| 242 |
},
|
| 243 |
{
|
| 244 |
"id": "output_stage",
|
| 245 |
"name": "QMoE.OutputStageGroupedSgmat",
|
| 246 |
"shader": "qmoe-output-grouped-sgmat.wgsl.jinja",
|
| 247 |
-
"bindings": ["
|
| 248 |
-
"dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize,
|
| 249 |
},
|
| 250 |
{
|
| 251 |
"id": "mix_stage",
|
| 252 |
"name": "QMoE.MixStage",
|
| 253 |
-
"shader": "
|
| 254 |
-
"bindings": ["
|
| 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)",
|
|
@@ -266,13 +265,6 @@
|
|
| 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",
|
| 274 |
-
"groupRouteWorkgroup": "tunables.groupRouteWorkgroup"
|
| 275 |
-
},
|
| 276 |
"intermediates": [
|
| 277 |
{ "id": "routeExpert", "dtype": "uint32", "shape": "[numTokens * topK]" },
|
| 278 |
{ "id": "routeMix", "dtype": "float32", "shape": "[numTokens * topK]" },
|
|
@@ -286,7 +278,7 @@
|
|
| 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)",
|
|
@@ -301,32 +293,28 @@
|
|
| 301 |
"id": "group_stage",
|
| 302 |
"name": "QMoE.GroupStage",
|
| 303 |
"shader": "expert-group-slots.wgsl.jinja",
|
| 304 |
-
"bindings": ["
|
| 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", "
|
| 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": ["
|
| 323 |
"dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize, groupTileN)" }
|
| 324 |
},
|
| 325 |
{
|
| 326 |
"id": "mix_stage",
|
| 327 |
"name": "QMoE.MixStage",
|
| 328 |
-
"shader": "
|
| 329 |
-
"bindings": ["
|
| 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)",
|
|
@@ -352,7 +340,7 @@
|
|
| 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)",
|
|
@@ -367,7 +355,7 @@
|
|
| 367 |
"id": "fc1_activation_stage",
|
| 368 |
"name": "QMoE.FC1ActivationStageGemv",
|
| 369 |
"shader": "qmoe-fc1-activation-gemv.wgsl.jinja",
|
| 370 |
-
"bindings": ["input", "
|
| 371 |
"dispatch": {
|
| 372 |
"x": "ceilDiv(interSize, decodeRows)",
|
| 373 |
"y": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * topK"
|
|
@@ -378,9 +366,9 @@
|
|
| 378 |
"name": "QMoE.OutputStageGemv",
|
| 379 |
"shader": "qmoe-output-gemv.wgsl.jinja",
|
| 380 |
"bindings": [
|
| 381 |
-
"
|
| 382 |
-
"
|
| 383 |
-
"
|
| 384 |
"fc2_experts_weights",
|
| 385 |
"fc2_scales",
|
| 386 |
"output",
|
|
@@ -412,7 +400,7 @@
|
|
| 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)",
|
|
@@ -429,7 +417,7 @@
|
|
| 429 |
"shader": "qmoe-fc1-activation-stage.wgsl.jinja",
|
| 430 |
"bindings": [
|
| 431 |
"input",
|
| 432 |
-
"
|
| 433 |
"fc1_experts_weights",
|
| 434 |
"fc1_scales",
|
| 435 |
"hidden_act",
|
|
@@ -457,7 +445,7 @@
|
|
| 457 |
"id": "output_stage",
|
| 458 |
"name": "QMoE.OutputStage",
|
| 459 |
"shader": "qmoe-output-stage.wgsl.jinja",
|
| 460 |
-
"bindings": ["
|
| 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)",
|
|
|
|
| 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 |
+
"groupTileK": "tunables.groupTileK",
|
| 56 |
+
"groupThreads": "tunables.groupThreads",
|
| 57 |
+
"regM": "tunables.groupRegM",
|
| 58 |
+
"regN": "tunables.groupRegN",
|
| 59 |
+
"groupRouteWorkgroup": "tunables.groupRouteWorkgroup",
|
| 60 |
+
"groupTileM": "tunables.groupThreads * tunables.groupRegM",
|
| 61 |
+
"groupTileN": "tunables.groupThreads * tunables.groupRegN",
|
| 62 |
+
"groupTileKVec": "ceilDiv(tunables.groupTileK, 4)",
|
| 63 |
+
"groupThreadCount": "tunables.groupThreads * tunables.groupThreads",
|
| 64 |
+
"groupSharedBytes": "(groupTileM * tunables.groupTileK + 2 * groupTileN * tunables.groupTileK + groupTileM) * 4",
|
| 65 |
+
"topK": "attrs.k",
|
| 66 |
+
"activationType": "attrs.activation_type",
|
| 67 |
+
"workgroupSizeOk": "tunables.workgroupSize >= 1 and tunables.workgroupSize <= deviceWorkgroupCap",
|
| 68 |
+
"decodeLanesOk": "tunables.decodeLanes >= 1 and tunables.decodeLanes <= tunables.workgroupSize and tunables.workgroupSize % tunables.decodeLanes == 0",
|
| 69 |
+
"decodeRows": "max(1, tunables.workgroupSize / max(1, tunables.decodeLanes))",
|
| 70 |
+
"decodeDeviceOk": "workgroupSizeOk and decodeLanesOk and tunables.decodeLanes <= device.limits.maxComputeWorkgroupSizeX and decodeRows <= device.limits.maxComputeWorkgroupSizeY and tunables.workgroupSize * 8 <= device.limits.maxComputeWorkgroupStorageSize",
|
| 71 |
+
"fc1Rows": "dim(shapes.fc1T, 1)",
|
| 72 |
+
"hasSwigluLimit": "has(attrs, \"swiglu_limit\")",
|
| 73 |
+
"swigluLimit": "attrs.swiglu_limit if has(attrs, \"swiglu_limit\") else 0",
|
| 74 |
+
"workgroupSize": "tunables.workgroupSize",
|
| 75 |
"hiddenSize": "dim(shapes.inputT, ranks.inputT - 1)",
|
| 76 |
"numTokens": "numel(shapes.inputT) / max(1, hiddenSize)",
|
|
|
|
| 77 |
"weightBits": "attrs.expert_weight_bits",
|
| 78 |
"quantBlockSize": "attrs.block_size if has(attrs, \"block_size\") else 0",
|
| 79 |
"packSize": "2 if weightBits == 4 else 1",
|
|
|
|
| 93 |
"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))",
|
| 94 |
"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",
|
| 95 |
"quantContract": "activationSupported and routingModeSupported and rawWeightLayout and quantShapeOk and topK >= 1 and topK <= dim(shapes.routerT, 1)",
|
|
|
|
| 96 |
"hiddenChunkFits": "topK * interSize * 4 <= device.limits.maxStorageBufferBindingSize and topK * interSize * 4 <= device.limits.maxBufferSize",
|
| 97 |
"hiddenChunkTokens": "numTokens if interSize == 0 else min(numTokens, max(1, floor(min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize) / (topK * interSize * 4))))",
|
| 98 |
"hiddenChunkCount": "max(1, ceilDiv(numTokens, max(1, hiddenChunkTokens)))",
|
| 99 |
"routeScratchBytes": "numTokens * topK * 4",
|
| 100 |
"routedScratchFits": "routeScratchBytes <= device.limits.maxStorageBufferBindingSize and routeScratchBytes <= device.limits.maxBufferSize",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
"groupSlots": "hiddenChunkTokens * topK",
|
| 102 |
"groupMaxTiles": "ceilDiv(groupSlots, max(1, groupTileM)) + dim(shapes.routerT, 1)",
|
|
|
|
| 103 |
"groupSlotOutBytes": "hiddenChunkTokens * topK * hiddenSize * 4",
|
| 104 |
"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",
|
| 105 |
"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)",
|
|
|
|
| 109 |
"sgmatWorkgroup": "128",
|
| 110 |
"sgmatSubgroups": "4",
|
| 111 |
"sgmatRowSubtiles": "2",
|
| 112 |
+
"sgmatTileCols": "64",
|
| 113 |
"groupedSgmatStageElements": "max(groupTileM * 32, sgmatSubgroups * 4 * 64)",
|
| 114 |
+
"groupedSgmatSharedBytes": "(groupedSgmatStageElements + sgmatTileCols * 32) * 4",
|
| 115 |
+
"groupedSgmatOk": "groupedContract and groupTileM == 32 and interSize % 32 == 0 and groupedSgmatSharedBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(hiddenSize, sgmatTileCols) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and hiddenSize % 32 == 0",
|
|
|
|
|
|
|
|
|
|
| 116 |
"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)",
|
| 117 |
"decodeLaneDepth": "min(hiddenSize, interSize) / max(1, tunables.decodeLanes)",
|
| 118 |
"decodeDepthOk": "decodeLaneDepth >= tunables.decodeMinLaneTrips",
|
| 119 |
"splitFc1Blocks": "ceilDiv(numTokens * topK * interSize, max(1, tunables.workgroupSize))",
|
| 120 |
"decodeOccupancyOk": "splitFc1Blocks <= tunables.decodeBlockTarget",
|
| 121 |
"decodeContract": "quantContract and decodeDeviceOk and decodeDispatchOk and hiddenChunkFits and routedScratchFits and interSize > 0 and decodeDepthOk and (decodeOccupancyOk or not groupedContract)",
|
|
|
|
| 122 |
"hidden": "hiddenSize",
|
| 123 |
"experts": "dim(shapes.routerT, 1)",
|
| 124 |
+
"inter": "interSize"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
},
|
| 126 |
"bindings": {
|
| 127 |
+
"output": { "arg": "outputT", "elementType": "f32" },
|
| 128 |
+
"router_probs": { "arg": "routerT", "elementType": "f32" },
|
| 129 |
+
"route_expert": { "scratch": "routeExpert", "elementType": "u32" },
|
| 130 |
+
"route_mix": { "scratch": "routeMix", "elementType": "f32" },
|
| 131 |
+
"route_expert_u32": {
|
| 132 |
"scratch": "routeExpert",
|
| 133 |
"name": "route_expert",
|
| 134 |
"buffer": "read-only-storage",
|
| 135 |
"elementType": "u32"
|
| 136 |
},
|
| 137 |
+
"slot_list": { "scratch": "slotList", "elementType": "u32" },
|
| 138 |
+
"tile_meta": { "scratch": "tileMeta", "elementType": "u32" },
|
| 139 |
+
"params_route": { "name": "params", "struct": [{ "name": "tokenCount", "type": "u32", "value": "numTokens" }] },
|
| 140 |
"params": {
|
|
|
|
| 141 |
"struct": [
|
| 142 |
{ "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" },
|
| 143 |
{
|
|
|
|
| 147 |
}
|
| 148 |
]
|
| 149 |
},
|
| 150 |
+
"input": { "arg": "inputT", "elementType": "f32" },
|
| 151 |
+
"slot_list_u32": { "scratch": "slotList", "name": "slot_list", "buffer": "read-only-storage", "elementType": "u32" },
|
| 152 |
+
"tile_meta_u32": { "scratch": "tileMeta", "name": "tile_meta", "buffer": "read-only-storage", "elementType": "u32" },
|
| 153 |
+
"fc1_experts_weights": { "arg": "fc1T", "elementType": "u32" },
|
| 154 |
+
"fc1_scales": { "arg": "fc1ScalesT", "elementType": "f32" },
|
| 155 |
+
"hidden_act": { "scratch": "hiddenAct", "elementType": "f32" },
|
| 156 |
+
"params__uniform": {
|
| 157 |
"name": "params",
|
|
|
|
| 158 |
"struct": [
|
| 159 |
{ "name": "activationAlpha", "type": "f32", "value": "attrs.activation_alpha" },
|
| 160 |
{ "name": "activationBeta", "type": "f32", "value": "attrs.activation_beta" },
|
| 161 |
{ "name": "tokenOffset", "type": "u32", "value": "repeat.chunk * hiddenChunkTokens" }
|
| 162 |
]
|
| 163 |
},
|
| 164 |
+
"hidden_act_f32": {
|
| 165 |
"scratch": "hiddenAct",
|
| 166 |
"name": "hidden_act",
|
| 167 |
"buffer": "read-only-storage",
|
| 168 |
"elementType": "f32"
|
| 169 |
},
|
| 170 |
+
"fc2_experts_weights": { "arg": "fc2T", "elementType": "u32" },
|
| 171 |
+
"fc2_scales": { "arg": "fc2ScalesT", "elementType": "f32" },
|
| 172 |
+
"slot_out": { "scratch": "slotOut", "elementType": "f32" },
|
| 173 |
+
"slot_out_f32": { "scratch": "slotOut", "name": "slot_out", "buffer": "read-only-storage", "elementType": "f32" },
|
| 174 |
+
"route_mix_f32": { "scratch": "routeMix", "name": "route_mix", "buffer": "read-only-storage", "elementType": "f32" }
|
| 175 |
},
|
| 176 |
"variants": [
|
| 177 |
{
|
|
|
|
| 201 |
"features": ["subgroups", "chromium-experimental-subgroup-matrix"],
|
| 202 |
"subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
|
| 203 |
},
|
|
|
|
| 204 |
"intermediates": [
|
| 205 |
{ "id": "routeExpert", "dtype": "uint32", "shape": "[numTokens * topK]" },
|
| 206 |
{ "id": "routeMix", "dtype": "float32", "shape": "[numTokens * topK]" },
|
|
|
|
| 214 |
"id": "route_stage",
|
| 215 |
"name": "QMoE.RouteStage",
|
| 216 |
"shader": "qmoe-route-stage.wgsl.jinja",
|
| 217 |
+
"bindings": ["router_probs", "route_expert", "route_mix", "params_route"],
|
| 218 |
"dispatch": {
|
| 219 |
"x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
| 220 |
"y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
|
|
|
| 229 |
"id": "group_stage",
|
| 230 |
"name": "QMoE.GroupStage",
|
| 231 |
"shader": "expert-group-slots.wgsl.jinja",
|
| 232 |
+
"bindings": ["route_expert_u32", "slot_list", "tile_meta", "params"],
|
| 233 |
+
"dispatch": { "x": 1 }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
},
|
| 235 |
{
|
| 236 |
"id": "fc1_activation_stage",
|
| 237 |
"name": "QMoE.FC1ActivationStageGroupedSgmat",
|
| 238 |
"shader": "qmoe-fc1-activation-grouped-sgmat.wgsl.jinja",
|
| 239 |
+
"bindings": ["input", "slot_list_u32", "tile_meta_u32", "fc1_experts_weights", "fc1_scales", "hidden_act", "params__uniform"],
|
| 240 |
+
"dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(interSize * fusionSize, sgmatTileCols)" }
|
| 241 |
},
|
| 242 |
{
|
| 243 |
"id": "output_stage",
|
| 244 |
"name": "QMoE.OutputStageGroupedSgmat",
|
| 245 |
"shader": "qmoe-output-grouped-sgmat.wgsl.jinja",
|
| 246 |
+
"bindings": ["hidden_act_f32", "slot_list_u32", "tile_meta_u32", "fc2_experts_weights", "fc2_scales", "slot_out"],
|
| 247 |
+
"dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize, sgmatTileCols)" }
|
| 248 |
},
|
| 249 |
{
|
| 250 |
"id": "mix_stage",
|
| 251 |
"name": "QMoE.MixStage",
|
| 252 |
+
"shader": "expert-slot-mix.wgsl.jinja",
|
| 253 |
+
"bindings": ["slot_out_f32", "route_mix_f32", "output", "params"],
|
| 254 |
"dispatch": {
|
| 255 |
"x": "min(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
|
| 256 |
"y": "ceilDiv(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
|
|
|
|
| 265 |
"id": "quant_grouped_routed",
|
| 266 |
"priority": 30,
|
| 267 |
"when": ["groupedContract"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
"intermediates": [
|
| 269 |
{ "id": "routeExpert", "dtype": "uint32", "shape": "[numTokens * topK]" },
|
| 270 |
{ "id": "routeMix", "dtype": "float32", "shape": "[numTokens * topK]" },
|
|
|
|
| 278 |
"id": "route_stage",
|
| 279 |
"name": "QMoE.RouteStage",
|
| 280 |
"shader": "qmoe-route-stage.wgsl.jinja",
|
| 281 |
+
"bindings": ["router_probs", "route_expert", "route_mix", "params_route"],
|
| 282 |
"dispatch": {
|
| 283 |
"x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
| 284 |
"y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
|
|
|
| 293 |
"id": "group_stage",
|
| 294 |
"name": "QMoE.GroupStage",
|
| 295 |
"shader": "expert-group-slots.wgsl.jinja",
|
| 296 |
+
"bindings": ["route_expert_u32", "slot_list", "tile_meta", "params"],
|
| 297 |
+
"dispatch": { "x": 1 }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
},
|
| 299 |
{
|
| 300 |
"id": "fc1_activation_stage",
|
| 301 |
"name": "QMoE.FC1ActivationStageGrouped",
|
| 302 |
"shader": "qmoe-fc1-activation-grouped.wgsl.jinja",
|
| 303 |
+
"bindings": ["input", "slot_list_u32", "tile_meta_u32", "fc1_experts_weights", "fc1_scales", "hidden_act", "params__uniform"],
|
| 304 |
"dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(interSize, groupTileN)" }
|
| 305 |
},
|
| 306 |
{
|
| 307 |
"id": "output_stage",
|
| 308 |
"name": "QMoE.OutputStageGrouped",
|
| 309 |
"shader": "qmoe-output-grouped.wgsl.jinja",
|
| 310 |
+
"bindings": ["hidden_act_f32", "slot_list_u32", "tile_meta_u32", "fc2_experts_weights", "fc2_scales", "slot_out"],
|
| 311 |
"dispatch": { "x": "groupMaxTiles", "y": "ceilDiv(hiddenSize, groupTileN)" }
|
| 312 |
},
|
| 313 |
{
|
| 314 |
"id": "mix_stage",
|
| 315 |
"name": "QMoE.MixStage",
|
| 316 |
+
"shader": "expert-slot-mix.wgsl.jinja",
|
| 317 |
+
"bindings": ["slot_out_f32", "route_mix_f32", "output", "params"],
|
| 318 |
"dispatch": {
|
| 319 |
"x": "min(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
|
| 320 |
"y": "ceilDiv(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
|
|
|
|
| 340 |
"id": "route_stage",
|
| 341 |
"name": "QMoE.RouteStage",
|
| 342 |
"shader": "qmoe-route-stage.wgsl.jinja",
|
| 343 |
+
"bindings": ["router_probs", "route_expert", "route_mix", "params_route"],
|
| 344 |
"dispatch": {
|
| 345 |
"x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
| 346 |
"y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
|
|
|
| 355 |
"id": "fc1_activation_stage",
|
| 356 |
"name": "QMoE.FC1ActivationStageGemv",
|
| 357 |
"shader": "qmoe-fc1-activation-gemv.wgsl.jinja",
|
| 358 |
+
"bindings": ["input", "route_expert_u32", "fc1_experts_weights", "fc1_scales", "hidden_act", "params__uniform"],
|
| 359 |
"dispatch": {
|
| 360 |
"x": "ceilDiv(interSize, decodeRows)",
|
| 361 |
"y": "min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * topK"
|
|
|
|
| 366 |
"name": "QMoE.OutputStageGemv",
|
| 367 |
"shader": "qmoe-output-gemv.wgsl.jinja",
|
| 368 |
"bindings": [
|
| 369 |
+
"hidden_act_f32",
|
| 370 |
+
"route_expert_u32",
|
| 371 |
+
"route_mix_f32",
|
| 372 |
"fc2_experts_weights",
|
| 373 |
"fc2_scales",
|
| 374 |
"output",
|
|
|
|
| 400 |
"id": "route_stage",
|
| 401 |
"name": "QMoE.RouteStage",
|
| 402 |
"shader": "qmoe-route-stage.wgsl.jinja",
|
| 403 |
+
"bindings": ["router_probs", "route_expert", "route_mix", "params_route"],
|
| 404 |
"dispatch": {
|
| 405 |
"x": "min(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
| 406 |
"y": "ceilDiv(ceilDiv((numTokens), (workgroupSize)), 65535)",
|
|
|
|
| 417 |
"shader": "qmoe-fc1-activation-stage.wgsl.jinja",
|
| 418 |
"bindings": [
|
| 419 |
"input",
|
| 420 |
+
"route_expert_u32",
|
| 421 |
"fc1_experts_weights",
|
| 422 |
"fc1_scales",
|
| 423 |
"hidden_act",
|
|
|
|
| 445 |
"id": "output_stage",
|
| 446 |
"name": "QMoE.OutputStage",
|
| 447 |
"shader": "qmoe-output-stage.wgsl.jinja",
|
| 448 |
+
"bindings": ["hidden_act_f32", "route_expert_u32", "route_mix_f32", "fc2_experts_weights", "fc2_scales", "output", "params"],
|
| 449 |
"dispatch": {
|
| 450 |
"x": "min(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
|
| 451 |
"y": "ceilDiv(ceilDiv((min(hiddenChunkTokens, numTokens - repeat.chunk * hiddenChunkTokens) * hiddenSize), (workgroupSize)), 65535)",
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.QMoE",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
|
@@ -9,28 +9,28 @@
|
|
| 9 |
"files": {
|
| 10 |
"bench.json": "YuXSEWa4U+6Ek0E0ZkIeDrzqQ672IelokTilKJuKHf0=",
|
| 11 |
"expert-group-slots.wgsl.jinja": "Ta+3H2FA1qRRzksdkwgKLZiO+AokmuM+B9JVN8cv1EQ=",
|
| 12 |
-
"
|
| 13 |
-
"
|
| 14 |
-
"qmoe-fc1-activation-
|
| 15 |
-
"qmoe-fc1-activation-grouped.wgsl.jinja": "
|
| 16 |
-
"qmoe-fc1-activation-
|
| 17 |
-
"qmoe-
|
| 18 |
-
"qmoe-output-gemv.wgsl.jinja": "
|
| 19 |
-
"qmoe-output-grouped-sgmat.wgsl.jinja": "
|
| 20 |
-
"qmoe-output-grouped.wgsl.jinja": "
|
| 21 |
-
"qmoe-output-stage.wgsl.jinja": "
|
| 22 |
"qmoe-output-zero-inter.wgsl.jinja": "6jHYpVxU5+9WMflhwMste4v17hq7WWA1BDDS4q4DLS4=",
|
| 23 |
-
"qmoe-route-stage.wgsl.jinja": "
|
| 24 |
-
"test.json": "
|
| 25 |
}
|
| 26 |
},
|
| 27 |
-
"provenance": { "kernel": { "sha": "
|
| 28 |
"webgpu": {
|
| 29 |
-
"manifestSpec": "2.
|
| 30 |
"variants": {
|
| 31 |
"quant_zero_inter": ["qmoe-output-zero-inter.wgsl.jinja"],
|
| 32 |
-
"quant_grouped_sgmat_routed": ["expert-group-slots.wgsl.jinja", "
|
| 33 |
-
"quant_grouped_routed": ["expert-group-slots.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 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.QMoE",
|
| 3 |
+
"id": "_com_microsoft_qmoe_webgpu_58337b0",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
|
|
|
| 9 |
"files": {
|
| 10 |
"bench.json": "YuXSEWa4U+6Ek0E0ZkIeDrzqQ672IelokTilKJuKHf0=",
|
| 11 |
"expert-group-slots.wgsl.jinja": "Ta+3H2FA1qRRzksdkwgKLZiO+AokmuM+B9JVN8cv1EQ=",
|
| 12 |
+
"expert-slot-mix.wgsl.jinja": "VwX7A1f2r+lxMzG/I5r0KUpGr0Y5gW/CrI/wdwx0Ah4=",
|
| 13 |
+
"manifest.json": "9eXT25hIM0qWJhCsJjnAodd/OgG9+nO+AvuscEpzk5U=",
|
| 14 |
+
"qmoe-fc1-activation-gemv.wgsl.jinja": "ZYXWDSbc10LldI8RICBHK6Z/9qrrXbuYfLpVnsa0ON4=",
|
| 15 |
+
"qmoe-fc1-activation-grouped-sgmat.wgsl.jinja": "emJWdN2BOxm610l2YstemK7OkegF96pKvhnpRSNQ5LY=",
|
| 16 |
+
"qmoe-fc1-activation-grouped.wgsl.jinja": "TXmpZXCGJhq/T26VJpnp9OcBu14jg4oJCnAvnqY7uF4=",
|
| 17 |
+
"qmoe-fc1-activation-stage.wgsl.jinja": "zpkM+RhDGOwHtlQBDa6k30BgDLJl2MzkCQDH1+fEFGk=",
|
| 18 |
+
"qmoe-output-gemv.wgsl.jinja": "aB5licSktZYGWKyHBnL+K4a7ogTZCG5b0IewORHZTVs=",
|
| 19 |
+
"qmoe-output-grouped-sgmat.wgsl.jinja": "NZocGQlY7dInufxtEwMxylKXDqspt3MyJqUftZ/M6Uk=",
|
| 20 |
+
"qmoe-output-grouped.wgsl.jinja": "P7AAth2FTgq4p67WQpt6rWMdWs5PsAEn0HURW44wIRQ=",
|
| 21 |
+
"qmoe-output-stage.wgsl.jinja": "x1VLEwM3N45620tcpvbRcD94PSrt/gxDUjXsYBDQJAw=",
|
| 22 |
"qmoe-output-zero-inter.wgsl.jinja": "6jHYpVxU5+9WMflhwMste4v17hq7WWA1BDDS4q4DLS4=",
|
| 23 |
+
"qmoe-route-stage.wgsl.jinja": "wi5Q1NwN1ZugT/otZryovayihC1gP6EdLO9yw1skhtc=",
|
| 24 |
+
"test.json": "6Rt3peYHm7WQraXPO0uYfIdb8VlBXwFwgy9pAvQwiac="
|
| 25 |
}
|
| 26 |
},
|
| 27 |
+
"provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
|
| 28 |
"webgpu": {
|
| 29 |
+
"manifestSpec": "2.1",
|
| 30 |
"variants": {
|
| 31 |
"quant_zero_inter": ["qmoe-output-zero-inter.wgsl.jinja"],
|
| 32 |
+
"quant_grouped_sgmat_routed": ["expert-group-slots.wgsl.jinja", "expert-slot-mix.wgsl.jinja", "qmoe-fc1-activation-grouped-sgmat.wgsl.jinja", "qmoe-output-grouped-sgmat.wgsl.jinja", "qmoe-route-stage.wgsl.jinja"],
|
| 33 |
+
"quant_grouped_routed": ["expert-group-slots.wgsl.jinja", "expert-slot-mix.wgsl.jinja", "qmoe-fc1-activation-grouped.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 |
}
|
build/webgpu/qmoe-fc1-activation-gemv.wgsl.jinja
CHANGED
|
@@ -10,54 +10,57 @@ const TOP_K: u32 = {{ topK }}u;
|
|
| 10 |
// reduction, which is LANES times the invocations for the same arithmetic.
|
| 11 |
const LANES: u32 = {{ decodeLanes }}u;
|
| 12 |
const ROWS: u32 = {{ decodeRows }}u;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 14 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 15 |
// first along the reduction axis, two codes per stored word.
|
| 16 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 17 |
-
const
|
| 18 |
{% if not colWiseScales %}
|
| 19 |
-
// Block-wise scales (
|
| 20 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 21 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 22 |
-
const
|
| 23 |
{% endif %}
|
| 24 |
|
| 25 |
-
fn
|
| 26 |
-
let base = expert *
|
| 27 |
{% if packSize == 2 %}
|
| 28 |
-
let word =
|
| 29 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 30 |
{% else %}
|
| 31 |
-
let code = f32(
|
| 32 |
{% endif %}
|
| 33 |
{% if not colWiseScales %}
|
| 34 |
-
return (code - MIDPOINT) *
|
| 35 |
{% else %}
|
| 36 |
-
return (code - MIDPOINT) *
|
| 37 |
{% endif %}
|
| 38 |
}
|
| 39 |
// Decode two consecutive reduction-axis codes together. Four-bit codes share a
|
| 40 |
// stored word; eight-bit codes occupy adjacent words.
|
| 41 |
-
fn
|
| 42 |
-
let base = expert *
|
| 43 |
{% if packSize == 2 %}
|
| 44 |
-
let word =
|
| 45 |
let lo = f32(word & 15u) - MIDPOINT;
|
| 46 |
let hi = f32((word >> 4u) & 15u) - MIDPOINT;
|
| 47 |
{% else %}
|
| 48 |
-
let lo = f32(
|
| 49 |
-
let hi = f32(
|
| 50 |
{% endif %}
|
| 51 |
{% if not colWiseScales %}
|
| 52 |
-
return vec2<f32>(lo *
|
| 53 |
-
hi *
|
| 54 |
{% else %}
|
| 55 |
-
let scale =
|
| 56 |
return vec2<f32>(lo * scale, hi * scale);
|
| 57 |
{% endif %}
|
| 58 |
}
|
| 59 |
|
| 60 |
-
|
| 61 |
{% if activationType == "swiglu" %}
|
| 62 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 63 |
{% if hasSwigluLimit %}
|
|
@@ -77,7 +80,6 @@ fn activate(value: f32) -> f32 {
|
|
| 77 |
}
|
| 78 |
{% endif %}
|
| 79 |
|
| 80 |
-
|
| 81 |
{% macro rowlane_fold(arrays, lanes="LANES", lane="lane", slot="slot") %}
|
| 82 |
var n = {{ lanes }};
|
| 83 |
while (n > 1u) {
|
|
@@ -89,9 +91,7 @@ fn activate(value: f32) -> f32 {
|
|
| 89 |
}
|
| 90 |
workgroupBarrier();
|
| 91 |
n = half;
|
| 92 |
-
}
|
| 93 |
-
{%- endmacro %}
|
| 94 |
-
|
| 95 |
var<workgroup> gate_partial: array<f32, {{ decodeLanes * decodeRows }}>;
|
| 96 |
{% if activationType == "swiglu" %}
|
| 97 |
// The gate and up rows of an interleaved SwiGLU pair share the same activation
|
|
|
|
| 10 |
// reduction, which is LANES times the invocations for the same arithmetic.
|
| 11 |
const LANES: u32 = {{ decodeLanes }}u;
|
| 12 |
const ROWS: u32 = {{ decodeRows }}u;
|
| 13 |
+
{% set qw = "fc1" %}
|
| 14 |
+
{% set qwRows = "FC1_ROWS" %}
|
| 15 |
+
{% set qwPackedCols = fc1PackedCols %}
|
| 16 |
+
{% set qwBlocks = fc1ScaleBlocks %}
|
| 17 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 18 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 19 |
// first along the reduction axis, two codes per stored word.
|
| 20 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 21 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 22 |
{% if not colWiseScales %}
|
| 23 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 24 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 25 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 26 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 27 |
{% endif %}
|
| 28 |
|
| 29 |
+
fn {{ qw }}_weight(expert: u32, row: u32, col: u32) -> f32 {
|
| 30 |
+
let base = expert * {{ qwRows }} + row;
|
| 31 |
{% if packSize == 2 %}
|
| 32 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 33 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 34 |
{% else %}
|
| 35 |
+
let code = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u);
|
| 36 |
{% endif %}
|
| 37 |
{% if not colWiseScales %}
|
| 38 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 39 |
{% else %}
|
| 40 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base];
|
| 41 |
{% endif %}
|
| 42 |
}
|
| 43 |
// Decode two consecutive reduction-axis codes together. Four-bit codes share a
|
| 44 |
// stored word; eight-bit codes occupy adjacent words.
|
| 45 |
+
fn {{ qw }}_weight_pair(expert: u32, row: u32, col: u32) -> vec2<f32> {
|
| 46 |
+
let base = expert * {{ qwRows }} + row;
|
| 47 |
{% if packSize == 2 %}
|
| 48 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 49 |
let lo = f32(word & 15u) - MIDPOINT;
|
| 50 |
let hi = f32((word >> 4u) & 15u) - MIDPOINT;
|
| 51 |
{% else %}
|
| 52 |
+
let lo = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u) - MIDPOINT;
|
| 53 |
+
let hi = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col + 1u] & 255u) - MIDPOINT;
|
| 54 |
{% endif %}
|
| 55 |
{% if not colWiseScales %}
|
| 56 |
+
return vec2<f32>(lo * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK],
|
| 57 |
+
hi * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + (col + 1u) / QUANT_BLOCK]);
|
| 58 |
{% else %}
|
| 59 |
+
let scale = {{ qw }}_scales[base];
|
| 60 |
return vec2<f32>(lo * scale, hi * scale);
|
| 61 |
{% endif %}
|
| 62 |
}
|
| 63 |
|
|
|
|
| 64 |
{% if activationType == "swiglu" %}
|
| 65 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 66 |
{% if hasSwigluLimit %}
|
|
|
|
| 80 |
}
|
| 81 |
{% endif %}
|
| 82 |
|
|
|
|
| 83 |
{% macro rowlane_fold(arrays, lanes="LANES", lane="lane", slot="slot") %}
|
| 84 |
var n = {{ lanes }};
|
| 85 |
while (n > 1u) {
|
|
|
|
| 91 |
}
|
| 92 |
workgroupBarrier();
|
| 93 |
n = half;
|
| 94 |
+
}{% endmacro %}
|
|
|
|
|
|
|
| 95 |
var<workgroup> gate_partial: array<f32, {{ decodeLanes * decodeRows }}>;
|
| 96 |
{% if activationType == "swiglu" %}
|
| 97 |
// The gate and up rows of an interleaved SwiGLU pair share the same activation
|
build/webgpu/qmoe-fc1-activation-grouped-sgmat.wgsl.jinja
CHANGED
|
@@ -5,7 +5,6 @@ enable subgroup_size_control;
|
|
| 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.
|
|
@@ -20,50 +19,53 @@ const HIDDEN: u32 = {{ hidden }}u;
|
|
| 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 =
|
| 24 |
const TILE_K: u32 = 32u;
|
| 25 |
const SUB_COLS: u32 = 32u;
|
| 26 |
const SUB_ROWS: u32 = 16u;
|
| 27 |
{% set isSwiglu = activationType == "swiglu" %}
|
| 28 |
// Weight rows per output column: the fused gate/up pair, or one.
|
| 29 |
const FUSION: u32 = {{ 2 if isSwiglu else 1 }}u;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 31 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 32 |
// first along the reduction axis, two codes per stored word.
|
| 33 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 34 |
-
const
|
| 35 |
{% if not colWiseScales %}
|
| 36 |
-
// Block-wise scales (
|
| 37 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 38 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 39 |
-
const
|
| 40 |
{% endif %}
|
| 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
|
| 46 |
-
let base = expert *
|
| 47 |
{% if packSize == 2 %}
|
| 48 |
-
let pair = base *
|
| 49 |
-
let w0 =
|
| 50 |
-
let w1 =
|
| 51 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 52 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 53 |
{% else %}
|
| 54 |
-
let word = base *
|
| 55 |
-
let codes = vec4<f32>(f32(
|
| 56 |
-
f32(
|
| 57 |
{% endif %}
|
| 58 |
{% if not colWiseScales %}
|
| 59 |
-
let scale =
|
| 60 |
{% else %}
|
| 61 |
-
let scale =
|
| 62 |
{% endif %}
|
| 63 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 64 |
}
|
| 65 |
|
| 66 |
-
|
| 67 |
{% if activationType == "swiglu" %}
|
| 68 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 69 |
{% if hasSwigluLimit %}
|
|
@@ -83,11 +85,10 @@ fn activate(value: f32) -> f32 {
|
|
| 83 |
}
|
| 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,
|
| 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.
|
|
@@ -149,9 +150,11 @@ fn main(
|
|
| 149 |
let base_A = subtile_idy * SUB_ROWS;
|
| 150 |
let base_B = subtile_idx * SUB_COLS;
|
| 151 |
|
| 152 |
-
{% for row in [0, 1] %}
|
|
|
|
| 153 |
var matC{{ row }}{{ col }}: subgroup_matrix_result<f32, 8, 8>;
|
| 154 |
-
{% 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);
|
|
@@ -159,7 +162,8 @@ fn main(
|
|
| 159 |
workgroupBarrier();
|
| 160 |
|
| 161 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 162 |
-
{% set operandScalar = "f32" %}
|
|
|
|
| 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" }});
|
|
|
|
| 5 |
enable chromium_experimental_subgroup_matrix;
|
| 6 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 7 |
|
|
|
|
| 8 |
{{ env.wgsl.resourceDeclarations }}
|
| 9 |
|
| 10 |
// Expert-grouped FC1 and activation stage using subgroup-matrix operations.
|
|
|
|
| 19 |
const FC1_ROWS: u32 = {{ fc1Rows }}u;
|
| 20 |
const INTER: u32 = {{ inter }}u;
|
| 21 |
const TOP_K: u32 = {{ topK }}u;
|
| 22 |
+
const TILE_COLS: u32 = {{ sgmatTileCols }}u;
|
| 23 |
const TILE_K: u32 = 32u;
|
| 24 |
const SUB_COLS: u32 = 32u;
|
| 25 |
const SUB_ROWS: u32 = 16u;
|
| 26 |
{% set isSwiglu = activationType == "swiglu" %}
|
| 27 |
// Weight rows per output column: the fused gate/up pair, or one.
|
| 28 |
const FUSION: u32 = {{ 2 if isSwiglu else 1 }}u;
|
| 29 |
+
{% set qw = "fc1" %}
|
| 30 |
+
{% set qwRows = "FC1_ROWS" %}
|
| 31 |
+
{% set qwPackedCols = fc1PackedCols %}
|
| 32 |
+
{% set qwBlocks = fc1ScaleBlocks %}
|
| 33 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 34 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 35 |
// first along the reduction axis, two codes per stored word.
|
| 36 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 37 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 38 |
{% if not colWiseScales %}
|
| 39 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 40 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 41 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 42 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 43 |
{% endif %}
|
| 44 |
|
| 45 |
// Decode the four-code group consumed by the staging loop. The aligned codes
|
| 46 |
// share one quantization scale: blocks are powers of two of at least 16, and
|
| 47 |
// this helper requires `col` to be a multiple of four.
|
| 48 |
+
fn {{ qw }}_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
|
| 49 |
+
let base = expert * {{ qwRows }} + row;
|
| 50 |
{% if packSize == 2 %}
|
| 51 |
+
let pair = base * {{ qw | upper }}_PACKED_COLS + col / 2u;
|
| 52 |
+
let w0 = {{ qw }}_experts_weights[pair];
|
| 53 |
+
let w1 = {{ qw }}_experts_weights[pair + 1u];
|
| 54 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 55 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 56 |
{% else %}
|
| 57 |
+
let word = base * {{ qw | upper }}_PACKED_COLS + col;
|
| 58 |
+
let codes = vec4<f32>(f32({{ qw }}_experts_weights[word] & 255u), f32({{ qw }}_experts_weights[word + 1u] & 255u),
|
| 59 |
+
f32({{ qw }}_experts_weights[word + 2u] & 255u), f32({{ qw }}_experts_weights[word + 3u] & 255u));
|
| 60 |
{% endif %}
|
| 61 |
{% if not colWiseScales %}
|
| 62 |
+
let scale = {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 63 |
{% else %}
|
| 64 |
+
let scale = {{ qw }}_scales[base];
|
| 65 |
{% endif %}
|
| 66 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 67 |
}
|
| 68 |
|
|
|
|
| 69 |
{% if activationType == "swiglu" %}
|
| 70 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 71 |
{% if hasSwigluLimit %}
|
|
|
|
| 85 |
}
|
| 86 |
{% endif %}
|
| 87 |
|
|
|
|
| 88 |
// The final K-loop barrier ends the A operand's lifetime. Its storage then
|
| 89 |
// holds four 8x8 output banks per subgroup, fenced by the epilogue barriers.
|
| 90 |
var<workgroup> tile_A: array<f32, {{ groupedSgmatStageElements }}>;
|
| 91 |
+
var<workgroup> tile_B: array<f32, {{ sgmatTileCols }} * 32>;
|
| 92 |
|
| 93 |
// Rows past the tile's end stage zero and are dropped at the store, so a short final
|
| 94 |
// tile needs no separate arm.
|
|
|
|
| 150 |
let base_A = subtile_idy * SUB_ROWS;
|
| 151 |
let base_B = subtile_idx * SUB_COLS;
|
| 152 |
|
| 153 |
+
{% for row in [0, 1] %}
|
| 154 |
+
{% for col in [0, 1, 2, 3] %}
|
| 155 |
var matC{{ row }}{{ col }}: subgroup_matrix_result<f32, 8, 8>;
|
| 156 |
+
{% endfor %}
|
| 157 |
+
{% endfor %}
|
| 158 |
|
| 159 |
for (var kidx = 0u; kidx < HIDDEN; kidx = kidx + TILE_K) {
|
| 160 |
loadSHMA(kidx, rows, local_idx / 4u, local_idx % 4u, a_slot);
|
|
|
|
| 162 |
workgroupBarrier();
|
| 163 |
|
| 164 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 165 |
+
{% set operandScalar = "f32" %}
|
| 166 |
+
{% set directInputs = false %}
|
| 167 |
let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
|
| 168 |
{% for r in range(2) %}
|
| 169 |
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" }});
|
build/webgpu/qmoe-fc1-activation-grouped.wgsl.jinja
CHANGED
|
@@ -15,57 +15,60 @@ const THREADS_SIDE: u32 = {{ groupThreads }}u;
|
|
| 15 |
const THREADS: u32 = THREADS_SIDE * THREADS_SIDE;
|
| 16 |
{% set isSwiglu = activationType == "swiglu" %}
|
| 17 |
{% set primaryRow = "col * 2u" if isSwiglu else "col" %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 19 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 20 |
// first along the reduction axis, two codes per stored word.
|
| 21 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 22 |
-
const
|
| 23 |
{% if not colWiseScales %}
|
| 24 |
-
// Block-wise scales (
|
| 25 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 26 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 27 |
-
const
|
| 28 |
{% endif %}
|
| 29 |
|
| 30 |
-
fn
|
| 31 |
-
let base = expert *
|
| 32 |
{% if packSize == 2 %}
|
| 33 |
-
let word =
|
| 34 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 35 |
{% else %}
|
| 36 |
-
let code = f32(
|
| 37 |
{% endif %}
|
| 38 |
{% if not colWiseScales %}
|
| 39 |
-
return (code - MIDPOINT) *
|
| 40 |
{% else %}
|
| 41 |
-
return (code - MIDPOINT) *
|
| 42 |
{% endif %}
|
| 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
|
| 48 |
-
let base = expert *
|
| 49 |
{% if packSize == 2 %}
|
| 50 |
-
let pair = base *
|
| 51 |
-
let w0 =
|
| 52 |
-
let w1 =
|
| 53 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 54 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 55 |
{% else %}
|
| 56 |
-
let word = base *
|
| 57 |
-
let codes = vec4<f32>(f32(
|
| 58 |
-
f32(
|
| 59 |
{% endif %}
|
| 60 |
{% if not colWiseScales %}
|
| 61 |
-
let scale =
|
| 62 |
{% else %}
|
| 63 |
-
let scale =
|
| 64 |
{% endif %}
|
| 65 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 66 |
}
|
| 67 |
|
| 68 |
-
|
| 69 |
{% if activationType == "swiglu" %}
|
| 70 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 71 |
{% if hasSwigluLimit %}
|
|
@@ -85,7 +88,6 @@ fn activate(value: f32) -> f32 {
|
|
| 85 |
}
|
| 86 |
{% endif %}
|
| 87 |
|
| 88 |
-
|
| 89 |
const KVEC: u32 = {{ groupTileKVec }}u;
|
| 90 |
{% macro stage_group_tiles(aLoad, bLoad, b2Load, kExtent, nExtent, guarded, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 91 |
for (var idx = tid; idx < MTILE * KVEC; idx = idx + THREADS) {
|
|
@@ -138,8 +140,7 @@ const KVEC: u32 = {{ groupTileKVec }}u;
|
|
| 138 |
{% if b2Load %}
|
| 139 |
b2_tile[idx] = b2_vec;
|
| 140 |
{% endif %}
|
| 141 |
-
}
|
| 142 |
-
{%- endmacro %}
|
| 143 |
|
| 144 |
{% macro group_tile_loop(aLoad, bLoad, b2Load, kExtent, nExtent, regM, regN, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 145 |
{% for r in range(regM) %}
|
|
@@ -192,9 +193,7 @@ const KVEC: u32 = {{ groupTileKVec }}u;
|
|
| 192 |
// Orders this step's tile reads before the next step overwrites them.
|
| 193 |
workgroupBarrier();
|
| 194 |
k_base = k_base + KTILE;
|
| 195 |
-
}
|
| 196 |
-
{%- endmacro %}
|
| 197 |
-
|
| 198 |
var<workgroup> row_slot: array<u32, {{ groupTileM }}>;
|
| 199 |
var<workgroup> a_tile: array<vec4<f32>, {{ groupTileM * groupTileKVec }}>;
|
| 200 |
var<workgroup> b_tile: array<vec4<f32>, {{ groupTileN * groupTileKVec }}>;
|
|
|
|
| 15 |
const THREADS: u32 = THREADS_SIDE * THREADS_SIDE;
|
| 16 |
{% set isSwiglu = activationType == "swiglu" %}
|
| 17 |
{% set primaryRow = "col * 2u" if isSwiglu else "col" %}
|
| 18 |
+
{% set qw = "fc1" %}
|
| 19 |
+
{% set qwRows = "FC1_ROWS" %}
|
| 20 |
+
{% set qwPackedCols = fc1PackedCols %}
|
| 21 |
+
{% set qwBlocks = fc1ScaleBlocks %}
|
| 22 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 23 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 24 |
// first along the reduction axis, two codes per stored word.
|
| 25 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 26 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 27 |
{% if not colWiseScales %}
|
| 28 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 29 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 30 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 31 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 32 |
{% endif %}
|
| 33 |
|
| 34 |
+
fn {{ qw }}_weight(expert: u32, row: u32, col: u32) -> f32 {
|
| 35 |
+
let base = expert * {{ qwRows }} + row;
|
| 36 |
{% if packSize == 2 %}
|
| 37 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 38 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 39 |
{% else %}
|
| 40 |
+
let code = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u);
|
| 41 |
{% endif %}
|
| 42 |
{% if not colWiseScales %}
|
| 43 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 44 |
{% else %}
|
| 45 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base];
|
| 46 |
{% endif %}
|
| 47 |
}
|
| 48 |
// Decode the four-code group consumed by the staging loop. The aligned codes
|
| 49 |
// share one quantization scale: blocks are powers of two of at least 16, and
|
| 50 |
// this helper requires `col` to be a multiple of four.
|
| 51 |
+
fn {{ qw }}_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
|
| 52 |
+
let base = expert * {{ qwRows }} + row;
|
| 53 |
{% if packSize == 2 %}
|
| 54 |
+
let pair = base * {{ qw | upper }}_PACKED_COLS + col / 2u;
|
| 55 |
+
let w0 = {{ qw }}_experts_weights[pair];
|
| 56 |
+
let w1 = {{ qw }}_experts_weights[pair + 1u];
|
| 57 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 58 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 59 |
{% else %}
|
| 60 |
+
let word = base * {{ qw | upper }}_PACKED_COLS + col;
|
| 61 |
+
let codes = vec4<f32>(f32({{ qw }}_experts_weights[word] & 255u), f32({{ qw }}_experts_weights[word + 1u] & 255u),
|
| 62 |
+
f32({{ qw }}_experts_weights[word + 2u] & 255u), f32({{ qw }}_experts_weights[word + 3u] & 255u));
|
| 63 |
{% endif %}
|
| 64 |
{% if not colWiseScales %}
|
| 65 |
+
let scale = {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 66 |
{% else %}
|
| 67 |
+
let scale = {{ qw }}_scales[base];
|
| 68 |
{% endif %}
|
| 69 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 70 |
}
|
| 71 |
|
|
|
|
| 72 |
{% if activationType == "swiglu" %}
|
| 73 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 74 |
{% if hasSwigluLimit %}
|
|
|
|
| 88 |
}
|
| 89 |
{% endif %}
|
| 90 |
|
|
|
|
| 91 |
const KVEC: u32 = {{ groupTileKVec }}u;
|
| 92 |
{% macro stage_group_tiles(aLoad, bLoad, b2Load, kExtent, nExtent, guarded, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 93 |
for (var idx = tid; idx < MTILE * KVEC; idx = idx + THREADS) {
|
|
|
|
| 140 |
{% if b2Load %}
|
| 141 |
b2_tile[idx] = b2_vec;
|
| 142 |
{% endif %}
|
| 143 |
+
}{% endmacro %}
|
|
|
|
| 144 |
|
| 145 |
{% macro group_tile_loop(aLoad, bLoad, b2Load, kExtent, nExtent, regM, regN, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 146 |
{% for r in range(regM) %}
|
|
|
|
| 193 |
// Orders this step's tile reads before the next step overwrites them.
|
| 194 |
workgroupBarrier();
|
| 195 |
k_base = k_base + KTILE;
|
| 196 |
+
}{% endmacro %}
|
|
|
|
|
|
|
| 197 |
var<workgroup> row_slot: array<u32, {{ groupTileM }}>;
|
| 198 |
var<workgroup> a_tile: array<vec4<f32>, {{ groupTileM * groupTileKVec }}>;
|
| 199 |
var<workgroup> b_tile: array<vec4<f32>, {{ groupTileN * groupTileKVec }}>;
|
build/webgpu/qmoe-fc1-activation-stage.wgsl.jinja
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
const HIDDEN: u32 = {{ hidden }}u;
|
| 4 |
const FC1_ROWS: u32 = {{ fc1Rows }}u;
|
|
@@ -11,34 +38,37 @@ const WG: u32 = {{ workgroupSize }}u;
|
|
| 11 |
{% if topK > 1 %}
|
| 12 |
const TOP_K: u32 = {{ topK }}u;
|
| 13 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 15 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 16 |
// first along the reduction axis, two codes per stored word.
|
| 17 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 18 |
-
const
|
| 19 |
{% if not colWiseScales %}
|
| 20 |
-
// Block-wise scales (
|
| 21 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 22 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 23 |
-
const
|
| 24 |
{% endif %}
|
| 25 |
|
| 26 |
-
fn
|
| 27 |
-
let base = expert *
|
| 28 |
{% if packSize == 2 %}
|
| 29 |
-
let word =
|
| 30 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 31 |
{% else %}
|
| 32 |
-
let code = f32(
|
| 33 |
{% endif %}
|
| 34 |
{% if not colWiseScales %}
|
| 35 |
-
return (code - MIDPOINT) *
|
| 36 |
{% else %}
|
| 37 |
-
return (code - MIDPOINT) *
|
| 38 |
{% endif %}
|
| 39 |
}
|
| 40 |
|
| 41 |
-
|
| 42 |
{% if activationType == "swiglu" %}
|
| 43 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 44 |
{% if hasSwigluLimit %}
|
|
@@ -58,12 +88,9 @@ fn activate(value: f32) -> f32 {
|
|
| 58 |
}
|
| 59 |
{% endif %}
|
| 60 |
|
| 61 |
-
|
| 62 |
@compute @workgroup_size(WG, 1, 1)
|
| 63 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 64 |
-
|
| 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.
|
|
@@ -91,25 +118,25 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
| 91 |
let expert = route_expert[token];
|
| 92 |
{% endif %}
|
| 93 |
|
| 94 |
-
var gate = 0.0;
|
| 95 |
{% if activationType == "swiglu" %}
|
| 96 |
-
var up = 0.0;
|
| 97 |
{% endif %}
|
| 98 |
for (var h: u32 = 0u; h < HIDDEN; h = h + 1u) {
|
| 99 |
let x = f32(input[token * HIDDEN + h]);
|
| 100 |
{% if activationType == "swiglu" %}
|
| 101 |
-
gate = gate
|
| 102 |
{% else %}
|
| 103 |
-
gate = gate
|
| 104 |
{% endif %}
|
| 105 |
{% if activationType == "swiglu" %}
|
| 106 |
-
up = up
|
| 107 |
{% endif %}
|
| 108 |
}
|
| 109 |
|
| 110 |
{% if activationType == "swiglu" %}
|
| 111 |
-
hidden_act[index] = activate(gate, up);
|
| 112 |
{% else %}
|
| 113 |
-
hidden_act[index] = activate(gate);
|
| 114 |
{% endif %}
|
| 115 |
}
|
|
|
|
| 1 |
+
{% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
|
| 2 |
+
{% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
|
| 3 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 4 |
+
// per-axis workgroup fold width.
|
| 5 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};{% endmacro %}
|
| 6 |
{{ env.wgsl.resourceDeclarations }}
|
| 7 |
+
{% set dotType = "f32" %}
|
| 8 |
+
// Retain product and addition residuals across a dot product. Explicit fma
|
| 9 |
+
// boundaries preserve the addition error transform under reassociation.
|
| 10 |
+
struct DotAccumulator {
|
| 11 |
+
hi: {{ dotType }},
|
| 12 |
+
lo: {{ dotType }},
|
| 13 |
+
}
|
| 14 |
+
fn dot_accumulate(acc: DotAccumulator, a: {{ dotType }}, b: {{ dotType }}) -> DotAccumulator {
|
| 15 |
+
let product = fma(a, b, {{ dotType }}(0.0));
|
| 16 |
+
let productError = fma(a, b, -product);
|
| 17 |
+
let sum = fma(acc.hi, {{ dotType }}(1.0), product);
|
| 18 |
+
let bv = fma({{ dotType }}(-1.0), acc.hi, sum);
|
| 19 |
+
let av = fma({{ dotType }}(-1.0), bv, sum);
|
| 20 |
+
let ae = fma({{ dotType }}(-1.0), av, acc.hi);
|
| 21 |
+
let be = fma({{ dotType }}(-1.0), bv, product);
|
| 22 |
+
let error = fma(fma(fma(ae, {{ dotType }}(1.0), be), {{ dotType }}(1.0), acc.lo), {{ dotType }}(1.0), productError);
|
| 23 |
+
let hi = fma(sum, {{ dotType }}(1.0), error);
|
| 24 |
+
return DotAccumulator(hi, fma({{ dotType }}(-1.0), fma({{ dotType }}(-1.0), sum, hi), error));
|
| 25 |
+
}
|
| 26 |
+
fn dot_value(acc: DotAccumulator) -> {{ dotType }} {
|
| 27 |
+
return fma(acc.hi, {{ dotType }}(1.0), acc.lo);
|
| 28 |
+
}
|
| 29 |
|
| 30 |
const HIDDEN: u32 = {{ hidden }}u;
|
| 31 |
const FC1_ROWS: u32 = {{ fc1Rows }}u;
|
|
|
|
| 38 |
{% if topK > 1 %}
|
| 39 |
const TOP_K: u32 = {{ topK }}u;
|
| 40 |
{% endif %}
|
| 41 |
+
{% set qw = "fc1" %}
|
| 42 |
+
{% set qwRows = "FC1_ROWS" %}
|
| 43 |
+
{% set qwPackedCols = fc1PackedCols %}
|
| 44 |
+
{% set qwBlocks = fc1ScaleBlocks %}
|
| 45 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 46 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 47 |
// first along the reduction axis, two codes per stored word.
|
| 48 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 49 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 50 |
{% if not colWiseScales %}
|
| 51 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 52 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 53 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 54 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 55 |
{% endif %}
|
| 56 |
|
| 57 |
+
fn {{ qw }}_weight(expert: u32, row: u32, col: u32) -> f32 {
|
| 58 |
+
let base = expert * {{ qwRows }} + row;
|
| 59 |
{% if packSize == 2 %}
|
| 60 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 61 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 62 |
{% else %}
|
| 63 |
+
let code = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u);
|
| 64 |
{% endif %}
|
| 65 |
{% if not colWiseScales %}
|
| 66 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 67 |
{% else %}
|
| 68 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base];
|
| 69 |
{% endif %}
|
| 70 |
}
|
| 71 |
|
|
|
|
| 72 |
{% if activationType == "swiglu" %}
|
| 73 |
fn activate(gate_in: f32, up_in: f32) -> f32 {
|
| 74 |
{% if hasSwigluLimit %}
|
|
|
|
| 88 |
}
|
| 89 |
{% endif %}
|
| 90 |
|
|
|
|
| 91 |
@compute @workgroup_size(WG, 1, 1)
|
| 92 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 93 |
+
{{ flat_index_2d("WG", "index", "") }}
|
|
|
|
|
|
|
| 94 |
{% if topK > 1 %}
|
| 95 |
// One activation row per routed slot rather than per token: the grid is
|
| 96 |
// (token, slot, inter) and hidden_act is addressed by the same flat index.
|
|
|
|
| 118 |
let expert = route_expert[token];
|
| 119 |
{% endif %}
|
| 120 |
|
| 121 |
+
var gate = DotAccumulator(0.0, 0.0);
|
| 122 |
{% if activationType == "swiglu" %}
|
| 123 |
+
var up = DotAccumulator(0.0, 0.0);
|
| 124 |
{% endif %}
|
| 125 |
for (var h: u32 = 0u; h < HIDDEN; h = h + 1u) {
|
| 126 |
let x = f32(input[token * HIDDEN + h]);
|
| 127 |
{% if activationType == "swiglu" %}
|
| 128 |
+
gate = dot_accumulate(gate, x, fc1_weight(expert, i * 2u, h));
|
| 129 |
{% else %}
|
| 130 |
+
gate = dot_accumulate(gate, x, fc1_weight(expert, i, h));
|
| 131 |
{% endif %}
|
| 132 |
{% if activationType == "swiglu" %}
|
| 133 |
+
up = dot_accumulate(up, x, fc1_weight(expert, i * 2u + 1u, h));
|
| 134 |
{% endif %}
|
| 135 |
}
|
| 136 |
|
| 137 |
{% if activationType == "swiglu" %}
|
| 138 |
+
hidden_act[index] = activate(dot_value(gate), dot_value(up));
|
| 139 |
{% else %}
|
| 140 |
+
hidden_act[index] = activate(dot_value(gate));
|
| 141 |
{% endif %}
|
| 142 |
}
|
build/webgpu/qmoe-output-gemv.wgsl.jinja
CHANGED
|
@@ -8,54 +8,57 @@ const TOP_K: u32 = {{ topK }}u;
|
|
| 8 |
// LANES-wide cooperative reduction.
|
| 9 |
const LANES: u32 = {{ decodeLanes }}u;
|
| 10 |
const ROWS: u32 = {{ decodeRows }}u;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 12 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 13 |
// first along the reduction axis, two codes per stored word.
|
| 14 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 15 |
-
const
|
| 16 |
{% if not colWiseScales %}
|
| 17 |
-
// Block-wise scales (
|
| 18 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 19 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 20 |
-
const
|
| 21 |
{% endif %}
|
| 22 |
|
| 23 |
-
fn
|
| 24 |
-
let base = expert *
|
| 25 |
{% if packSize == 2 %}
|
| 26 |
-
let word =
|
| 27 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 28 |
{% else %}
|
| 29 |
-
let code = f32(
|
| 30 |
{% endif %}
|
| 31 |
{% if not colWiseScales %}
|
| 32 |
-
return (code - MIDPOINT) *
|
| 33 |
{% else %}
|
| 34 |
-
return (code - MIDPOINT) *
|
| 35 |
{% endif %}
|
| 36 |
}
|
| 37 |
// Decode two consecutive reduction-axis codes together. Four-bit codes share a
|
| 38 |
// stored word; eight-bit codes occupy adjacent words.
|
| 39 |
-
fn
|
| 40 |
-
let base = expert *
|
| 41 |
{% if packSize == 2 %}
|
| 42 |
-
let word =
|
| 43 |
let lo = f32(word & 15u) - MIDPOINT;
|
| 44 |
let hi = f32((word >> 4u) & 15u) - MIDPOINT;
|
| 45 |
{% else %}
|
| 46 |
-
let lo = f32(
|
| 47 |
-
let hi = f32(
|
| 48 |
{% endif %}
|
| 49 |
{% if not colWiseScales %}
|
| 50 |
-
return vec2<f32>(lo *
|
| 51 |
-
hi *
|
| 52 |
{% else %}
|
| 53 |
-
let scale =
|
| 54 |
return vec2<f32>(lo * scale, hi * scale);
|
| 55 |
{% endif %}
|
| 56 |
}
|
| 57 |
|
| 58 |
-
|
| 59 |
{% macro rowlane_fold(arrays, lanes="LANES", lane="lane", slot="slot") %}
|
| 60 |
var n = {{ lanes }};
|
| 61 |
while (n > 1u) {
|
|
@@ -67,9 +70,7 @@ fn fc2_weight_pair(expert: u32, row: u32, col: u32) -> vec2<f32> {
|
|
| 67 |
}
|
| 68 |
workgroupBarrier();
|
| 69 |
n = half;
|
| 70 |
-
}
|
| 71 |
-
{%- endmacro %}
|
| 72 |
-
|
| 73 |
var<workgroup> partial: array<f32, {{ decodeLanes * decodeRows }}>;
|
| 74 |
|
| 75 |
@compute @workgroup_size(LANES, ROWS, 1)
|
|
|
|
| 8 |
// LANES-wide cooperative reduction.
|
| 9 |
const LANES: u32 = {{ decodeLanes }}u;
|
| 10 |
const ROWS: u32 = {{ decodeRows }}u;
|
| 11 |
+
{% set qw = "fc2" %}
|
| 12 |
+
{% set qwRows = "HIDDEN" %}
|
| 13 |
+
{% set qwPackedCols = fc2PackedCols %}
|
| 14 |
+
{% set qwBlocks = fc2ScaleBlocks %}
|
| 15 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 16 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 17 |
// first along the reduction axis, two codes per stored word.
|
| 18 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 19 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 20 |
{% if not colWiseScales %}
|
| 21 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 22 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 23 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 24 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 25 |
{% endif %}
|
| 26 |
|
| 27 |
+
fn {{ qw }}_weight(expert: u32, row: u32, col: u32) -> f32 {
|
| 28 |
+
let base = expert * {{ qwRows }} + row;
|
| 29 |
{% if packSize == 2 %}
|
| 30 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 31 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 32 |
{% else %}
|
| 33 |
+
let code = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u);
|
| 34 |
{% endif %}
|
| 35 |
{% if not colWiseScales %}
|
| 36 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 37 |
{% else %}
|
| 38 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base];
|
| 39 |
{% endif %}
|
| 40 |
}
|
| 41 |
// Decode two consecutive reduction-axis codes together. Four-bit codes share a
|
| 42 |
// stored word; eight-bit codes occupy adjacent words.
|
| 43 |
+
fn {{ qw }}_weight_pair(expert: u32, row: u32, col: u32) -> vec2<f32> {
|
| 44 |
+
let base = expert * {{ qwRows }} + row;
|
| 45 |
{% if packSize == 2 %}
|
| 46 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 47 |
let lo = f32(word & 15u) - MIDPOINT;
|
| 48 |
let hi = f32((word >> 4u) & 15u) - MIDPOINT;
|
| 49 |
{% else %}
|
| 50 |
+
let lo = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u) - MIDPOINT;
|
| 51 |
+
let hi = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col + 1u] & 255u) - MIDPOINT;
|
| 52 |
{% endif %}
|
| 53 |
{% if not colWiseScales %}
|
| 54 |
+
return vec2<f32>(lo * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK],
|
| 55 |
+
hi * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + (col + 1u) / QUANT_BLOCK]);
|
| 56 |
{% else %}
|
| 57 |
+
let scale = {{ qw }}_scales[base];
|
| 58 |
return vec2<f32>(lo * scale, hi * scale);
|
| 59 |
{% endif %}
|
| 60 |
}
|
| 61 |
|
|
|
|
| 62 |
{% macro rowlane_fold(arrays, lanes="LANES", lane="lane", slot="slot") %}
|
| 63 |
var n = {{ lanes }};
|
| 64 |
while (n > 1u) {
|
|
|
|
| 70 |
}
|
| 71 |
workgroupBarrier();
|
| 72 |
n = half;
|
| 73 |
+
}{% endmacro %}
|
|
|
|
|
|
|
| 74 |
var<workgroup> partial: array<f32, {{ decodeLanes * decodeRows }}>;
|
| 75 |
|
| 76 |
@compute @workgroup_size(LANES, ROWS, 1)
|
build/webgpu/qmoe-output-grouped-sgmat.wgsl.jinja
CHANGED
|
@@ -5,7 +5,6 @@ enable subgroup_size_control;
|
|
| 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
|
|
@@ -17,51 +16,54 @@ diagnostic(off, chromium.subgroup_matrix_uniformity);
|
|
| 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 =
|
| 21 |
const TILE_K: u32 = 32u;
|
| 22 |
const SUB_COLS: u32 = 32u;
|
| 23 |
const SUB_ROWS: u32 = 16u;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 25 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 26 |
// first along the reduction axis, two codes per stored word.
|
| 27 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 28 |
-
const
|
| 29 |
{% if not colWiseScales %}
|
| 30 |
-
// Block-wise scales (
|
| 31 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 32 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 33 |
-
const
|
| 34 |
{% endif %}
|
| 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
|
| 40 |
-
let base = expert *
|
| 41 |
{% if packSize == 2 %}
|
| 42 |
-
let pair = base *
|
| 43 |
-
let w0 =
|
| 44 |
-
let w1 =
|
| 45 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 46 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 47 |
{% else %}
|
| 48 |
-
let word = base *
|
| 49 |
-
let codes = vec4<f32>(f32(
|
| 50 |
-
f32(
|
| 51 |
{% endif %}
|
| 52 |
{% if not colWiseScales %}
|
| 53 |
-
let scale =
|
| 54 |
{% else %}
|
| 55 |
-
let scale =
|
| 56 |
{% endif %}
|
| 57 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 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,
|
| 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.
|
|
@@ -141,9 +143,11 @@ fn main(
|
|
| 141 |
let base_A = subtile_idy * SUB_ROWS;
|
| 142 |
let base_B = subtile_idx * SUB_COLS;
|
| 143 |
|
| 144 |
-
{% for row in [0, 1] %}
|
|
|
|
| 145 |
var matC{{ row }}{{ col }}: subgroup_matrix_result<f32, 8, 8>;
|
| 146 |
-
{% 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);
|
|
@@ -151,7 +155,8 @@ fn main(
|
|
| 151 |
workgroupBarrier();
|
| 152 |
|
| 153 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 154 |
-
{% set operandScalar = "f32" %}
|
|
|
|
| 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" }});
|
|
|
|
| 5 |
enable chromium_experimental_subgroup_matrix;
|
| 6 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 7 |
|
|
|
|
| 8 |
{{ env.wgsl.resourceDeclarations }}
|
| 9 |
|
| 10 |
// Expert-grouped FC2 stage using subgroup-matrix operations. Routed slots are
|
|
|
|
| 16 |
// A loader gathers through `slot_list` and the store scatters back through it.
|
| 17 |
const HIDDEN: u32 = {{ hidden }}u;
|
| 18 |
const INTER: u32 = {{ inter }}u;
|
| 19 |
+
const TILE_COLS: u32 = {{ sgmatTileCols }}u;
|
| 20 |
const TILE_K: u32 = 32u;
|
| 21 |
const SUB_COLS: u32 = 32u;
|
| 22 |
const SUB_ROWS: u32 = 16u;
|
| 23 |
+
{% set qw = "fc2" %}
|
| 24 |
+
{% set qwRows = "HIDDEN" %}
|
| 25 |
+
{% set qwPackedCols = fc2PackedCols %}
|
| 26 |
+
{% set qwBlocks = fc2ScaleBlocks %}
|
| 27 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 28 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 29 |
// first along the reduction axis, two codes per stored word.
|
| 30 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 31 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 32 |
{% if not colWiseScales %}
|
| 33 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 34 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 35 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 36 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 37 |
{% endif %}
|
| 38 |
|
| 39 |
// Decode the four-code group consumed by the staging loop. The aligned codes
|
| 40 |
// share one quantization scale: blocks are powers of two of at least 16, and
|
| 41 |
// this helper requires `col` to be a multiple of four.
|
| 42 |
+
fn {{ qw }}_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
|
| 43 |
+
let base = expert * {{ qwRows }} + row;
|
| 44 |
{% if packSize == 2 %}
|
| 45 |
+
let pair = base * {{ qw | upper }}_PACKED_COLS + col / 2u;
|
| 46 |
+
let w0 = {{ qw }}_experts_weights[pair];
|
| 47 |
+
let w1 = {{ qw }}_experts_weights[pair + 1u];
|
| 48 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 49 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 50 |
{% else %}
|
| 51 |
+
let word = base * {{ qw | upper }}_PACKED_COLS + col;
|
| 52 |
+
let codes = vec4<f32>(f32({{ qw }}_experts_weights[word] & 255u), f32({{ qw }}_experts_weights[word + 1u] & 255u),
|
| 53 |
+
f32({{ qw }}_experts_weights[word + 2u] & 255u), f32({{ qw }}_experts_weights[word + 3u] & 255u));
|
| 54 |
{% endif %}
|
| 55 |
{% if not colWiseScales %}
|
| 56 |
+
let scale = {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 57 |
{% else %}
|
| 58 |
+
let scale = {{ qw }}_scales[base];
|
| 59 |
{% endif %}
|
| 60 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 61 |
}
|
| 62 |
|
|
|
|
| 63 |
// The final K-loop barrier ends the A operand's lifetime. Its storage then
|
| 64 |
// holds four 8x8 output banks per subgroup, fenced by the epilogue barriers.
|
| 65 |
var<workgroup> tile_A: array<f32, {{ groupedSgmatStageElements }}>;
|
| 66 |
+
var<workgroup> tile_B: array<f32, {{ sgmatTileCols }} * 32>;
|
| 67 |
|
| 68 |
// Rows past the tile's end stage zero and are dropped at the store, so a short final
|
| 69 |
// tile needs no separate arm.
|
|
|
|
| 143 |
let base_A = subtile_idy * SUB_ROWS;
|
| 144 |
let base_B = subtile_idx * SUB_COLS;
|
| 145 |
|
| 146 |
+
{% for row in [0, 1] %}
|
| 147 |
+
{% for col in [0, 1, 2, 3] %}
|
| 148 |
var matC{{ row }}{{ col }}: subgroup_matrix_result<f32, 8, 8>;
|
| 149 |
+
{% endfor %}
|
| 150 |
+
{% endfor %}
|
| 151 |
|
| 152 |
for (var kidx = 0u; kidx < INTER; kidx = kidx + TILE_K) {
|
| 153 |
loadSHMA(kidx, rows, local_idx / 4u, local_idx % 4u, a_slot);
|
|
|
|
| 155 |
workgroupBarrier();
|
| 156 |
|
| 157 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 158 |
+
{% set operandScalar = "f32" %}
|
| 159 |
+
{% set directInputs = false %}
|
| 160 |
let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
|
| 161 |
{% for r in range(2) %}
|
| 162 |
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" }});
|
build/webgpu/qmoe-output-grouped.wgsl.jinja
CHANGED
|
@@ -13,57 +13,60 @@ const NTILE: u32 = {{ groupTileN }}u;
|
|
| 13 |
const KTILE: u32 = {{ groupTileK }}u;
|
| 14 |
const THREADS_SIDE: u32 = {{ groupThreads }}u;
|
| 15 |
const THREADS: u32 = THREADS_SIDE * THREADS_SIDE;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 17 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 18 |
// first along the reduction axis, two codes per stored word.
|
| 19 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 20 |
-
const
|
| 21 |
{% if not colWiseScales %}
|
| 22 |
-
// Block-wise scales (
|
| 23 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 24 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 25 |
-
const
|
| 26 |
{% endif %}
|
| 27 |
|
| 28 |
-
fn
|
| 29 |
-
let base = expert *
|
| 30 |
{% if packSize == 2 %}
|
| 31 |
-
let word =
|
| 32 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 33 |
{% else %}
|
| 34 |
-
let code = f32(
|
| 35 |
{% endif %}
|
| 36 |
{% if not colWiseScales %}
|
| 37 |
-
return (code - MIDPOINT) *
|
| 38 |
{% else %}
|
| 39 |
-
return (code - MIDPOINT) *
|
| 40 |
{% endif %}
|
| 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
|
| 46 |
-
let base = expert *
|
| 47 |
{% if packSize == 2 %}
|
| 48 |
-
let pair = base *
|
| 49 |
-
let w0 =
|
| 50 |
-
let w1 =
|
| 51 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 52 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 53 |
{% else %}
|
| 54 |
-
let word = base *
|
| 55 |
-
let codes = vec4<f32>(f32(
|
| 56 |
-
f32(
|
| 57 |
{% endif %}
|
| 58 |
{% if not colWiseScales %}
|
| 59 |
-
let scale =
|
| 60 |
{% else %}
|
| 61 |
-
let scale =
|
| 62 |
{% endif %}
|
| 63 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 64 |
}
|
| 65 |
|
| 66 |
-
|
| 67 |
const KVEC: u32 = {{ groupTileKVec }}u;
|
| 68 |
{% macro stage_group_tiles(aLoad, bLoad, b2Load, kExtent, nExtent, guarded, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 69 |
for (var idx = tid; idx < MTILE * KVEC; idx = idx + THREADS) {
|
|
@@ -116,16 +119,12 @@ const KVEC: u32 = {{ groupTileKVec }}u;
|
|
| 116 |
{% if b2Load %}
|
| 117 |
b2_tile[idx] = b2_vec;
|
| 118 |
{% endif %}
|
| 119 |
-
}
|
| 120 |
-
{%- endmacro %}
|
| 121 |
|
| 122 |
{% macro group_tile_loop(aLoad, bLoad, b2Load, kExtent, nExtent, regM, regN, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 123 |
{% for r in range(regM) %}
|
| 124 |
{% for c in range(regN) %}
|
| 125 |
var acc_{{ r }}_{{ c }} = 0.0;
|
| 126 |
-
{% if b2Load %}
|
| 127 |
-
var acc2_{{ r }}_{{ c }} = 0.0;
|
| 128 |
-
{% endif %}
|
| 129 |
{% endfor %}
|
| 130 |
{% endfor %}
|
| 131 |
|
|
@@ -154,25 +153,17 @@ const KVEC: u32 = {{ groupTileKVec }}u;
|
|
| 154 |
{% endfor %}
|
| 155 |
{% for c in range(regN) %}
|
| 156 |
let b{{ c }} = b_tile[(lid.x * {{ regN }}u + {{ c }}u) * KVEC + kv];
|
| 157 |
-
{% if b2Load %}
|
| 158 |
-
let s{{ c }} = b2_tile[(lid.x * {{ regN }}u + {{ c }}u) * KVEC + kv];
|
| 159 |
-
{% endif %}
|
| 160 |
{% endfor %}
|
| 161 |
{% for r in range(regM) %}
|
| 162 |
{% for c in range(regN) %}
|
| 163 |
acc_{{ r }}_{{ c }} = acc_{{ r }}_{{ c }} + dot(a{{ r }}, b{{ c }});
|
| 164 |
-
{% if b2Load %}
|
| 165 |
-
acc2_{{ r }}_{{ c }} = acc2_{{ r }}_{{ c }} + dot(a{{ r }}, s{{ c }});
|
| 166 |
-
{% endif %}
|
| 167 |
{% endfor %}
|
| 168 |
{% endfor %}
|
| 169 |
}
|
| 170 |
// Orders this step's tile reads before the next step overwrites them.
|
| 171 |
workgroupBarrier();
|
| 172 |
k_base = k_base + KTILE;
|
| 173 |
-
}
|
| 174 |
-
{%- endmacro %}
|
| 175 |
-
|
| 176 |
var<workgroup> row_slot: array<u32, {{ groupTileM }}>;
|
| 177 |
var<workgroup> a_tile: array<vec4<f32>, {{ groupTileM * groupTileKVec }}>;
|
| 178 |
var<workgroup> b_tile: array<vec4<f32>, {{ groupTileN * groupTileKVec }}>;
|
|
|
|
| 13 |
const KTILE: u32 = {{ groupTileK }}u;
|
| 14 |
const THREADS_SIDE: u32 = {{ groupThreads }}u;
|
| 15 |
const THREADS: u32 = THREADS_SIDE * THREADS_SIDE;
|
| 16 |
+
{% set qw = "fc2" %}
|
| 17 |
+
{% set qwRows = "HIDDEN" %}
|
| 18 |
+
{% set qwPackedCols = fc2PackedCols %}
|
| 19 |
+
{% set qwBlocks = fc2ScaleBlocks %}
|
| 20 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 21 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 22 |
// first along the reduction axis, two codes per stored word.
|
| 23 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 24 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 25 |
{% if not colWiseScales %}
|
| 26 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 27 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 28 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 29 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 30 |
{% endif %}
|
| 31 |
|
| 32 |
+
fn {{ qw }}_weight(expert: u32, row: u32, col: u32) -> f32 {
|
| 33 |
+
let base = expert * {{ qwRows }} + row;
|
| 34 |
{% if packSize == 2 %}
|
| 35 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 36 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 37 |
{% else %}
|
| 38 |
+
let code = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u);
|
| 39 |
{% endif %}
|
| 40 |
{% if not colWiseScales %}
|
| 41 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 42 |
{% else %}
|
| 43 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base];
|
| 44 |
{% endif %}
|
| 45 |
}
|
| 46 |
// Decode the four-code group consumed by the staging loop. The aligned codes
|
| 47 |
// share one quantization scale: blocks are powers of two of at least 16, and
|
| 48 |
// this helper requires `col` to be a multiple of four.
|
| 49 |
+
fn {{ qw }}_weight_vec4(expert: u32, row: u32, col: u32) -> vec4<f32> {
|
| 50 |
+
let base = expert * {{ qwRows }} + row;
|
| 51 |
{% if packSize == 2 %}
|
| 52 |
+
let pair = base * {{ qw | upper }}_PACKED_COLS + col / 2u;
|
| 53 |
+
let w0 = {{ qw }}_experts_weights[pair];
|
| 54 |
+
let w1 = {{ qw }}_experts_weights[pair + 1u];
|
| 55 |
let codes = vec4<f32>(f32(w0 & 15u), f32((w0 >> 4u) & 15u),
|
| 56 |
f32(w1 & 15u), f32((w1 >> 4u) & 15u));
|
| 57 |
{% else %}
|
| 58 |
+
let word = base * {{ qw | upper }}_PACKED_COLS + col;
|
| 59 |
+
let codes = vec4<f32>(f32({{ qw }}_experts_weights[word] & 255u), f32({{ qw }}_experts_weights[word + 1u] & 255u),
|
| 60 |
+
f32({{ qw }}_experts_weights[word + 2u] & 255u), f32({{ qw }}_experts_weights[word + 3u] & 255u));
|
| 61 |
{% endif %}
|
| 62 |
{% if not colWiseScales %}
|
| 63 |
+
let scale = {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 64 |
{% else %}
|
| 65 |
+
let scale = {{ qw }}_scales[base];
|
| 66 |
{% endif %}
|
| 67 |
return (codes - vec4<f32>(MIDPOINT)) * scale;
|
| 68 |
}
|
| 69 |
|
|
|
|
| 70 |
const KVEC: u32 = {{ groupTileKVec }}u;
|
| 71 |
{% macro stage_group_tiles(aLoad, bLoad, b2Load, kExtent, nExtent, guarded, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 72 |
for (var idx = tid; idx < MTILE * KVEC; idx = idx + THREADS) {
|
|
|
|
| 119 |
{% if b2Load %}
|
| 120 |
b2_tile[idx] = b2_vec;
|
| 121 |
{% endif %}
|
| 122 |
+
}{% endmacro %}
|
|
|
|
| 123 |
|
| 124 |
{% macro group_tile_loop(aLoad, bLoad, b2Load, kExtent, nExtent, regM, regN, bLoadVec4="", b2LoadVec4="", aLoadVec4="") %}
|
| 125 |
{% for r in range(regM) %}
|
| 126 |
{% for c in range(regN) %}
|
| 127 |
var acc_{{ r }}_{{ c }} = 0.0;
|
|
|
|
|
|
|
|
|
|
| 128 |
{% endfor %}
|
| 129 |
{% endfor %}
|
| 130 |
|
|
|
|
| 153 |
{% endfor %}
|
| 154 |
{% for c in range(regN) %}
|
| 155 |
let b{{ c }} = b_tile[(lid.x * {{ regN }}u + {{ c }}u) * KVEC + kv];
|
|
|
|
|
|
|
|
|
|
| 156 |
{% endfor %}
|
| 157 |
{% for r in range(regM) %}
|
| 158 |
{% for c in range(regN) %}
|
| 159 |
acc_{{ r }}_{{ c }} = acc_{{ r }}_{{ c }} + dot(a{{ r }}, b{{ c }});
|
|
|
|
|
|
|
|
|
|
| 160 |
{% endfor %}
|
| 161 |
{% endfor %}
|
| 162 |
}
|
| 163 |
// Orders this step's tile reads before the next step overwrites them.
|
| 164 |
workgroupBarrier();
|
| 165 |
k_base = k_base + KTILE;
|
| 166 |
+
}{% endmacro %}
|
|
|
|
|
|
|
| 167 |
var<workgroup> row_slot: array<u32, {{ groupTileM }}>;
|
| 168 |
var<workgroup> a_tile: array<vec4<f32>, {{ groupTileM * groupTileKVec }}>;
|
| 169 |
var<workgroup> b_tile: array<vec4<f32>, {{ groupTileN * groupTileKVec }}>;
|
build/webgpu/qmoe-output-stage.wgsl.jinja
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
const HIDDEN: u32 = {{ hidden }}u;
|
| 4 |
const INTER: u32 = {{ inter }}u;
|
|
@@ -6,39 +33,40 @@ const WG: u32 = {{ workgroupSize }}u;
|
|
| 6 |
{% if topK > 1 %}
|
| 7 |
const TOP_K: u32 = {{ topK }}u;
|
| 8 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 10 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 11 |
// first along the reduction axis, two codes per stored word.
|
| 12 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 13 |
-
const
|
| 14 |
{% if not colWiseScales %}
|
| 15 |
-
// Block-wise scales (
|
| 16 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 17 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 18 |
-
const
|
| 19 |
{% endif %}
|
| 20 |
|
| 21 |
-
fn
|
| 22 |
-
let base = expert *
|
| 23 |
{% if packSize == 2 %}
|
| 24 |
-
let word =
|
| 25 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 26 |
{% else %}
|
| 27 |
-
let code = f32(
|
| 28 |
{% endif %}
|
| 29 |
{% if not colWiseScales %}
|
| 30 |
-
return (code - MIDPOINT) *
|
| 31 |
{% else %}
|
| 32 |
-
return (code - MIDPOINT) *
|
| 33 |
{% endif %}
|
| 34 |
}
|
| 35 |
|
| 36 |
-
|
| 37 |
@compute @workgroup_size(WG, 1, 1)
|
| 38 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 39 |
-
|
| 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;
|
|
@@ -55,11 +83,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
| 55 |
var acc = 0.0;
|
| 56 |
for (var j = 0u; j < TOP_K; j = j + 1u) {
|
| 57 |
let expert = route_expert[token * TOP_K + j];
|
| 58 |
-
var part = 0.0;
|
| 59 |
for (var i: u32 = 0u; i < INTER; i = i + 1u) {
|
| 60 |
-
part = part
|
| 61 |
}
|
| 62 |
-
acc = acc + part * route_mix[token * TOP_K + j];
|
| 63 |
}
|
| 64 |
|
| 65 |
output[token * HIDDEN + out_col] = acc;
|
|
@@ -67,11 +95,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
| 67 |
let expert = route_expert[token];
|
| 68 |
let mix = route_mix[token];
|
| 69 |
|
| 70 |
-
var acc = 0.0;
|
| 71 |
for (var i: u32 = 0u; i < INTER; i = i + 1u) {
|
| 72 |
-
acc = acc
|
| 73 |
}
|
| 74 |
|
| 75 |
-
output[token * HIDDEN + out_col] = acc * mix;
|
| 76 |
{% endif %}
|
| 77 |
}
|
|
|
|
| 1 |
+
{% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
|
| 2 |
+
{% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
|
| 3 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 4 |
+
// per-axis workgroup fold width.
|
| 5 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};{% endmacro %}
|
| 6 |
{{ env.wgsl.resourceDeclarations }}
|
| 7 |
+
{% set dotType = "f32" %}
|
| 8 |
+
// Retain product and addition residuals across a dot product. Explicit fma
|
| 9 |
+
// boundaries preserve the addition error transform under reassociation.
|
| 10 |
+
struct DotAccumulator {
|
| 11 |
+
hi: {{ dotType }},
|
| 12 |
+
lo: {{ dotType }},
|
| 13 |
+
}
|
| 14 |
+
fn dot_accumulate(acc: DotAccumulator, a: {{ dotType }}, b: {{ dotType }}) -> DotAccumulator {
|
| 15 |
+
let product = fma(a, b, {{ dotType }}(0.0));
|
| 16 |
+
let productError = fma(a, b, -product);
|
| 17 |
+
let sum = fma(acc.hi, {{ dotType }}(1.0), product);
|
| 18 |
+
let bv = fma({{ dotType }}(-1.0), acc.hi, sum);
|
| 19 |
+
let av = fma({{ dotType }}(-1.0), bv, sum);
|
| 20 |
+
let ae = fma({{ dotType }}(-1.0), av, acc.hi);
|
| 21 |
+
let be = fma({{ dotType }}(-1.0), bv, product);
|
| 22 |
+
let error = fma(fma(fma(ae, {{ dotType }}(1.0), be), {{ dotType }}(1.0), acc.lo), {{ dotType }}(1.0), productError);
|
| 23 |
+
let hi = fma(sum, {{ dotType }}(1.0), error);
|
| 24 |
+
return DotAccumulator(hi, fma({{ dotType }}(-1.0), fma({{ dotType }}(-1.0), sum, hi), error));
|
| 25 |
+
}
|
| 26 |
+
fn dot_value(acc: DotAccumulator) -> {{ dotType }} {
|
| 27 |
+
return fma(acc.hi, {{ dotType }}(1.0), acc.lo);
|
| 28 |
+
}
|
| 29 |
|
| 30 |
const HIDDEN: u32 = {{ hidden }}u;
|
| 31 |
const INTER: u32 = {{ inter }}u;
|
|
|
|
| 33 |
{% if topK > 1 %}
|
| 34 |
const TOP_K: u32 = {{ topK }}u;
|
| 35 |
{% endif %}
|
| 36 |
+
{% set qw = "fc2" %}
|
| 37 |
+
{% set qwRows = "HIDDEN" %}
|
| 38 |
+
{% set qwPackedCols = fc2PackedCols %}
|
| 39 |
+
{% set qwBlocks = fc2ScaleBlocks %}
|
| 40 |
// Quantized expert weights use unsigned codes offset by 2^(bits-1), so a
|
| 41 |
// weight is (code - MIDPOINT) * scale. Four-bit codes are packed low-nibble
|
| 42 |
// first along the reduction axis, two codes per stored word.
|
| 43 |
const MIDPOINT: f32 = f32({{ quantMidpoint }});
|
| 44 |
+
const {{ qw | upper }}_PACKED_COLS: u32 = {{ qwPackedCols }}u;
|
| 45 |
{% if not colWiseScales %}
|
| 46 |
+
// Block-wise scales ({{ qw }}_scales rank 3): one scale per QUANT_BLOCK columns of the reduction
|
| 47 |
// axis. Column-wise scales (block_size 0) are the rank-2 single-scale-per-row case below.
|
| 48 |
const QUANT_BLOCK: u32 = {{ quantBlockSize }}u;
|
| 49 |
+
const {{ qw | upper }}_BLOCKS: u32 = {{ qwBlocks }}u;
|
| 50 |
{% endif %}
|
| 51 |
|
| 52 |
+
fn {{ qw }}_weight(expert: u32, row: u32, col: u32) -> f32 {
|
| 53 |
+
let base = expert * {{ qwRows }} + row;
|
| 54 |
{% if packSize == 2 %}
|
| 55 |
+
let word = {{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col / 2u];
|
| 56 |
let code = f32((word >> ((col & 1u) * 4u)) & 15u);
|
| 57 |
{% else %}
|
| 58 |
+
let code = f32({{ qw }}_experts_weights[base * {{ qw | upper }}_PACKED_COLS + col] & 255u);
|
| 59 |
{% endif %}
|
| 60 |
{% if not colWiseScales %}
|
| 61 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base * {{ qw | upper }}_BLOCKS + col / QUANT_BLOCK];
|
| 62 |
{% else %}
|
| 63 |
+
return (code - MIDPOINT) * {{ qw }}_scales[base];
|
| 64 |
{% endif %}
|
| 65 |
}
|
| 66 |
|
|
|
|
| 67 |
@compute @workgroup_size(WG, 1, 1)
|
| 68 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 69 |
+
{{ flat_index_2d("WG", "index", "") }}
|
|
|
|
|
|
|
| 70 |
let total = params.tokenCount * HIDDEN;
|
| 71 |
if (index >= total) {
|
| 72 |
return;
|
|
|
|
| 83 |
var acc = 0.0;
|
| 84 |
for (var j = 0u; j < TOP_K; j = j + 1u) {
|
| 85 |
let expert = route_expert[token * TOP_K + j];
|
| 86 |
+
var part = DotAccumulator(0.0, 0.0);
|
| 87 |
for (var i: u32 = 0u; i < INTER; i = i + 1u) {
|
| 88 |
+
part = dot_accumulate(part, hidden_act[(local_token * TOP_K + j) * INTER + i], fc2_weight(expert, out_col, i));
|
| 89 |
}
|
| 90 |
+
acc = acc + dot_value(part) * route_mix[token * TOP_K + j];
|
| 91 |
}
|
| 92 |
|
| 93 |
output[token * HIDDEN + out_col] = acc;
|
|
|
|
| 95 |
let expert = route_expert[token];
|
| 96 |
let mix = route_mix[token];
|
| 97 |
|
| 98 |
+
var acc = DotAccumulator(0.0, 0.0);
|
| 99 |
for (var i: u32 = 0u; i < INTER; i = i + 1u) {
|
| 100 |
+
acc = dot_accumulate(acc, hidden_act[local_token * INTER + i], fc2_weight(expert, out_col, i));
|
| 101 |
}
|
| 102 |
|
| 103 |
+
output[token * HIDDEN + out_col] = dot_value(acc) * mix;
|
| 104 |
{% endif %}
|
| 105 |
}
|
build/webgpu/qmoe-route-stage.wgsl.jinja
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
|
| 3 |
-
const TOKENS: u32 = {{ tokens }}u;
|
| 4 |
const EXPERTS: u32 = {{ experts }}u;
|
| 5 |
const WG: u32 = {{ workgroupSize }}u;
|
| 6 |
{% if topK > 1 %}
|
|
@@ -54,7 +61,6 @@ fn topk_route(token: u32) -> TopKRoute {
|
|
| 54 |
}
|
| 55 |
return result;
|
| 56 |
}
|
| 57 |
-
|
| 58 |
{% else %}
|
| 59 |
// Serial top-1 selection over `(logit, expert_index)` pairs ordered descending,
|
| 60 |
// so equal logits select the higher expert index.
|
|
@@ -75,17 +81,11 @@ fn top1_route(token: u32) -> Top1Route {
|
|
| 75 |
}
|
| 76 |
return result;
|
| 77 |
}
|
| 78 |
-
|
| 79 |
{% endif %}
|
| 80 |
|
| 81 |
@compute @workgroup_size(WG, 1, 1)
|
| 82 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 83 |
-
|
| 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 |
-
}
|
| 89 |
|
| 90 |
{% if topK > 1 %}
|
| 91 |
// Slot j of each token's TOP_K-wide run holds its j-th ranked expert and
|
|
|
|
| 1 |
+
{% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
|
| 2 |
+
{% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
|
| 3 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 4 |
+
// per-axis workgroup fold width.
|
| 5 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
|
| 6 |
+
if ({{ name }} >= {{ bound }}) {
|
| 7 |
+
return;
|
| 8 |
+
}{% endmacro %}
|
| 9 |
{{ env.wgsl.resourceDeclarations }}
|
| 10 |
|
|
|
|
| 11 |
const EXPERTS: u32 = {{ experts }}u;
|
| 12 |
const WG: u32 = {{ workgroupSize }}u;
|
| 13 |
{% if topK > 1 %}
|
|
|
|
| 61 |
}
|
| 62 |
return result;
|
| 63 |
}
|
|
|
|
| 64 |
{% else %}
|
| 65 |
// Serial top-1 selection over `(logit, expert_index)` pairs ordered descending,
|
| 66 |
// so equal logits select the higher expert index.
|
|
|
|
| 81 |
}
|
| 82 |
return result;
|
| 83 |
}
|
|
|
|
| 84 |
{% endif %}
|
| 85 |
|
| 86 |
@compute @workgroup_size(WG, 1, 1)
|
| 87 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 88 |
+
{{ flat_index_2d("WG", "token", "params.tokenCount") }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
{% if topK > 1 %}
|
| 91 |
// Slot j of each token's TOP_K-wide run holds its j-th ranked expert and
|
build/webgpu/test.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|