Xenova HF Staff commited on
Commit
ba08609
·
verified ·
1 Parent(s): f3f43cb

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -51,7 +51,7 @@ Default values (overridable per request):
51
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
52
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
53
  - [`test.json`](build/webgpu/test.json) — correctness cases
54
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
55
  - [`norm-row-stats.wgsl.jinja`](build/webgpu/norm-row-stats.wgsl.jinja)
56
  - [`rms-normalization-splitk-normalize.wgsl.jinja`](build/webgpu/rms-normalization-splitk-normalize.wgsl.jinja)
57
  - [`rms-normalization-splitk-partials.wgsl.jinja`](build/webgpu/rms-normalization-splitk-partials.wgsl.jinja)
@@ -61,7 +61,7 @@ Default values (overridable per request):
61
  ## Use with `@huggingface/kernels`
62
 
63
  ```sh
64
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
65
  ```
66
 
67
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
51
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
52
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
53
  - [`test.json`](build/webgpu/test.json) — correctness cases
54
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
55
  - [`norm-row-stats.wgsl.jinja`](build/webgpu/norm-row-stats.wgsl.jinja)
56
  - [`rms-normalization-splitk-normalize.wgsl.jinja`](build/webgpu/rms-normalization-splitk-normalize.wgsl.jinja)
57
  - [`rms-normalization-splitk-partials.wgsl.jinja`](build/webgpu/rms-normalization-splitk-partials.wgsl.jinja)
 
61
  ## Use with `@huggingface/kernels`
62
 
63
  ```sh
64
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
65
  ```
66
 
67
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
build/webgpu/manifest.json CHANGED
@@ -18,6 +18,7 @@
18
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
19
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
20
  "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
 
21
  "normMaxWorkgroup": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
22
  "hasSubgroupId": "device.features.has(\"subgroups\") and device.wgslLanguageFeatures.has(\"subgroup_id\")",
23
  "axisNorm": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x",
@@ -32,11 +33,10 @@
32
  "suffixAxisOk": "baseOk and ranks.x >= 2 and not (attrs.axis == -1 or attrs.axis == ranks.x - 1)"
33
  },
34
  "bindings": {
35
- "x": { "buffer": "read-only-storage", "elementType": "$xElement" },
36
- "scale": { "buffer": "read-only-storage", "elementType": "$ioElement" },
37
- "y": { "buffer": "storage", "elementType": "$ioElement" },
38
  "params": {
39
- "buffer": "uniform",
40
  "struct": [
41
  { "name": "rows", "type": "u32", "value": "normalizedRows" },
42
  {
@@ -46,9 +46,8 @@
46
  }
47
  ]
48
  },
49
- "params_2": {
50
  "name": "params",
51
- "buffer": "uniform",
52
  "struct": [
53
  { "name": "rows", "type": "u32", "value": "splitRows" },
54
  {
@@ -68,7 +67,6 @@
68
  "scalar": "dtypes.V",
69
  "xElement": "dtypes.T",
70
  "ioElement": "dtypes.V",
71
- "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
72
  "hiddenSize": "normalizedHidden",
73
  "epsilon": "attrs.epsilon"
74
  },
@@ -95,8 +93,8 @@
95
  {
96
  "id": "suffix_axis_splitk",
97
  "priority": 15,
98
- "when": ["baseOk", "ranks.x >= 2", "normalizedRows <= tunables.SPLIT_MAX_ROWS", "normalizedHidden >= tunables.SPLIT_MIN_HIDDEN", "min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(normalizedHidden, tunables.SPLIT_TARGET_ELEMENTS))) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "normalizedRows * min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(normalizedHidden, tunables.SPLIT_TARGET_ELEMENTS))) * 4 <= device.limits.maxStorageBufferBindingSize", "normalizedRows * min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(normalizedHidden, tunables.SPLIT_TARGET_ELEMENTS))) * 4 <= device.limits.maxBufferSize"],
99
- "demoteWhen": ["reportedNonWave32Adapter"],
100
  "derive": {
101
  "splitRows": "normalizedRows",
102
  "splitHidden": "normalizedHidden",
@@ -104,7 +102,6 @@
104
  "scalar": "dtypes.V",
105
  "xElement": "dtypes.T",
106
  "ioElement": "dtypes.V",
107
- "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
108
  "hiddenSize": "splitHidden",
109
  "workgroupSize": "normMaxWorkgroup",
110
  "epsilon": "attrs.epsilon",
@@ -116,7 +113,7 @@
116
  "id": "partials",
117
  "name": "RMSNormalization.SplitKPartials",
118
  "shader": "rms-normalization-splitk-partials.wgsl.jinja",
119
- "bindings": ["x", { "name": "partials", "buffer": "storage", "elementType": "f32" }, "params_2"],
120
  "dispatch": {
121
  "x": "min(splitRows, DISPATCH_FOLD_WIDTH)",
122
  "y": "ceilDiv(splitRows, DISPATCH_FOLD_WIDTH)",
@@ -132,8 +129,6 @@
132
  "scaleShape": "shapes.scale",
133
  "xRank": "ranks.x",
134
  "scaleRank": "ranks.scale",
135
- "writeStats": false,
136
- "rmsScaleAfterCast": true,
137
  "normalizeBlocks": "max(split, min(min(device.limits.maxComputeWorkgroupsPerDimension, 65535), ceilDiv(workgroupSize, max(1, normalizeRows)), ceilDiv(hiddenSize, workgroupSize * 4)))",
138
  "normalizeChunk": "ceilDiv(hiddenSize, normalizeBlocks)"
139
  },
@@ -142,7 +137,7 @@
142
  "scale",
143
  { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
144
  "y",
145
- "params_2"
146
  ],
147
  "dispatch": {
148
  "x": "min(splitRows, DISPATCH_FOLD_WIDTH)",
@@ -159,7 +154,6 @@
159
  "derive": {
160
  "scalar": "dtypes.V",
161
  "xElement": "dtypes.T",
162
- "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
163
  "ioElement": "dtypes.V",
164
  "hiddenSize": "normalizedHidden",
165
  "workgroupSize": "min(normMaxWorkgroup, pow2ceil(normalizedWorkgroupHidden))",
@@ -171,12 +165,11 @@
171
  "name": "RMSNormalization",
172
  "shader": "rms-normalization.wgsl.jinja",
173
  "derive": {
 
174
  "xShape": "shapes.x",
175
  "scaleShape": "shapes.scale",
176
  "xRank": "ranks.x",
177
- "scaleRank": "ranks.scale",
178
- "writeStats": false,
179
- "rmsScaleAfterCast": true
180
  },
181
  "bindings": ["x", "scale", "y", "params"],
182
  "dispatch": {
@@ -194,7 +187,6 @@
194
  "derive": {
195
  "scalar": "dtypes.V",
196
  "xElement": "dtypes.T",
197
- "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
198
  "ioElement": "dtypes.V",
199
  "hiddenSize": "normalizedHidden",
200
  "workgroupSize": "min(normMaxWorkgroup, pow2ceil(normalizedWorkgroupHidden))",
@@ -206,12 +198,11 @@
206
  "name": "RMSNormalization.SuffixAxis",
207
  "shader": "rms-normalization.wgsl.jinja",
208
  "derive": {
 
209
  "xShape": "shapes.x",
210
  "scaleShape": "shapes.scale",
211
  "xRank": "ranks.x",
212
- "scaleRank": "ranks.scale",
213
- "writeStats": false,
214
- "rmsScaleAfterCast": true
215
  },
216
  "bindings": ["x", "scale", "y", "params"],
217
  "dispatch": {
@@ -233,12 +224,9 @@
233
  "name": "RMSNormalization.LastAxisRow",
234
  "shader": "norm-row-stats.wgsl.jinja",
235
  "derive": {
236
- "modeSpec": "\"rms\"",
237
  "vec4": true,
238
- "writeStats": false,
239
- "rmsScaleAfterCast": true,
240
  "scalar": "dtypes.T",
241
- "usesF16Spec": "dtypes.T == \"f16\"",
242
  "hidden": "dim(shapes.x, -1)",
243
  "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.x, -1) / 4)))",
244
  "epsilon": "attrs.epsilon",
@@ -251,8 +239,7 @@
251
  "x": "min(normalizedDispatchRows, 65535)",
252
  "y": "ceilDiv(normalizedDispatchRows, 65535)",
253
  "z": 1
254
- },
255
- "subgroupCollectivesWidth": "portable"
256
  }
257
  ]
258
  },
@@ -267,12 +254,9 @@
267
  "name": "RMSNormalization.LastAxisRow",
268
  "shader": "norm-row-stats.wgsl.jinja",
269
  "derive": {
270
- "modeSpec": "\"rms\"",
271
  "vec4": false,
272
- "writeStats": false,
273
- "rmsScaleAfterCast": true,
274
  "scalar": "dtypes.T",
275
- "usesF16Spec": "dtypes.T == \"f16\"",
276
  "hidden": "dim(shapes.x, -1)",
277
  "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.x, -1))))",
278
  "epsilon": "attrs.epsilon",
@@ -285,8 +269,7 @@
285
  "x": "min(normalizedDispatchRows, 65535)",
286
  "y": "ceilDiv(normalizedDispatchRows, 65535)",
287
  "z": 1
288
- },
289
- "subgroupCollectivesWidth": "portable"
290
  }
291
  ]
292
  }
 
18
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
19
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
20
  "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
21
+ "variableSubgroup16To32": "device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 16 and device.adapterInfo.subgroupMaxSize == 32",
22
  "normMaxWorkgroup": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
23
  "hasSubgroupId": "device.features.has(\"subgroups\") and device.wgslLanguageFeatures.has(\"subgroup_id\")",
24
  "axisNorm": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x",
 
33
  "suffixAxisOk": "baseOk and ranks.x >= 2 and not (attrs.axis == -1 or attrs.axis == ranks.x - 1)"
34
  },
35
  "bindings": {
36
+ "x": { "elementType": "$xElement" },
37
+ "scale": { "elementType": "$ioElement" },
38
+ "y": { "elementType": "$ioElement" },
39
  "params": {
 
40
  "struct": [
41
  { "name": "rows", "type": "u32", "value": "normalizedRows" },
42
  {
 
46
  }
47
  ]
48
  },
49
+ "params__uniform": {
50
  "name": "params",
 
51
  "struct": [
52
  { "name": "rows", "type": "u32", "value": "splitRows" },
53
  {
 
67
  "scalar": "dtypes.V",
68
  "xElement": "dtypes.T",
69
  "ioElement": "dtypes.V",
 
70
  "hiddenSize": "normalizedHidden",
71
  "epsilon": "attrs.epsilon"
72
  },
 
93
  {
94
  "id": "suffix_axis_splitk",
95
  "priority": 15,
96
+ "when": ["baseOk", "ranks.x >= 2", "normalizedRows <= tunables.SPLIT_MAX_ROWS", "normalizedHidden >= tunables.SPLIT_MIN_HIDDEN", "min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(normalizedHidden, tunables.SPLIT_TARGET_ELEMENTS))) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "normalizedRows * min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(normalizedHidden, tunables.SPLIT_TARGET_ELEMENTS))) * 4 <= device.limits.maxStorageBufferBindingSize", "normalizedRows * min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(normalizedHidden, tunables.SPLIT_TARGET_ELEMENTS))) * 4 <= device.limits.maxBufferSize", "not (variableSubgroup16To32 and dtypes.T == \"f16\" and dtypes.V == \"f32\")"],
97
+ "demoteWhen": ["reportedNonWave32Adapter and not variableSubgroup16To32"],
98
  "derive": {
99
  "splitRows": "normalizedRows",
100
  "splitHidden": "normalizedHidden",
 
102
  "scalar": "dtypes.V",
103
  "xElement": "dtypes.T",
104
  "ioElement": "dtypes.V",
 
105
  "hiddenSize": "splitHidden",
106
  "workgroupSize": "normMaxWorkgroup",
107
  "epsilon": "attrs.epsilon",
 
113
  "id": "partials",
114
  "name": "RMSNormalization.SplitKPartials",
115
  "shader": "rms-normalization-splitk-partials.wgsl.jinja",
116
+ "bindings": ["x", { "name": "partials", "elementType": "f32" }, "params__uniform"],
117
  "dispatch": {
118
  "x": "min(splitRows, DISPATCH_FOLD_WIDTH)",
119
  "y": "ceilDiv(splitRows, DISPATCH_FOLD_WIDTH)",
 
129
  "scaleShape": "shapes.scale",
130
  "xRank": "ranks.x",
131
  "scaleRank": "ranks.scale",
 
 
132
  "normalizeBlocks": "max(split, min(min(device.limits.maxComputeWorkgroupsPerDimension, 65535), ceilDiv(workgroupSize, max(1, normalizeRows)), ceilDiv(hiddenSize, workgroupSize * 4)))",
133
  "normalizeChunk": "ceilDiv(hiddenSize, normalizeBlocks)"
134
  },
 
137
  "scale",
138
  { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
139
  "y",
140
+ "params__uniform"
141
  ],
142
  "dispatch": {
143
  "x": "min(splitRows, DISPATCH_FOLD_WIDTH)",
 
154
  "derive": {
155
  "scalar": "dtypes.V",
156
  "xElement": "dtypes.T",
 
157
  "ioElement": "dtypes.V",
158
  "hiddenSize": "normalizedHidden",
159
  "workgroupSize": "min(normMaxWorkgroup, pow2ceil(normalizedWorkgroupHidden))",
 
165
  "name": "RMSNormalization",
166
  "shader": "rms-normalization.wgsl.jinja",
167
  "derive": {
168
+ "compensateHalfStats": true,
169
  "xShape": "shapes.x",
170
  "scaleShape": "shapes.scale",
171
  "xRank": "ranks.x",
172
+ "scaleRank": "ranks.scale"
 
 
173
  },
174
  "bindings": ["x", "scale", "y", "params"],
175
  "dispatch": {
 
187
  "derive": {
188
  "scalar": "dtypes.V",
189
  "xElement": "dtypes.T",
 
190
  "ioElement": "dtypes.V",
191
  "hiddenSize": "normalizedHidden",
192
  "workgroupSize": "min(normMaxWorkgroup, pow2ceil(normalizedWorkgroupHidden))",
 
198
  "name": "RMSNormalization.SuffixAxis",
199
  "shader": "rms-normalization.wgsl.jinja",
200
  "derive": {
201
+ "compensateHalfStats": true,
202
  "xShape": "shapes.x",
203
  "scaleShape": "shapes.scale",
204
  "xRank": "ranks.x",
205
+ "scaleRank": "ranks.scale"
 
 
206
  },
207
  "bindings": ["x", "scale", "y", "params"],
208
  "dispatch": {
 
224
  "name": "RMSNormalization.LastAxisRow",
225
  "shader": "norm-row-stats.wgsl.jinja",
226
  "derive": {
227
+ "compensateHalfStats": true,
228
  "vec4": true,
 
 
229
  "scalar": "dtypes.T",
 
230
  "hidden": "dim(shapes.x, -1)",
231
  "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.x, -1) / 4)))",
232
  "epsilon": "attrs.epsilon",
 
239
  "x": "min(normalizedDispatchRows, 65535)",
240
  "y": "ceilDiv(normalizedDispatchRows, 65535)",
241
  "z": 1
242
+ }
 
243
  }
244
  ]
245
  },
 
254
  "name": "RMSNormalization.LastAxisRow",
255
  "shader": "norm-row-stats.wgsl.jinja",
256
  "derive": {
257
+ "compensateHalfStats": true,
258
  "vec4": false,
 
 
259
  "scalar": "dtypes.T",
 
260
  "hidden": "dim(shapes.x, -1)",
261
  "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.x, -1))))",
262
  "epsilon": "attrs.epsilon",
 
269
  "x": "min(normalizedDispatchRows, 65535)",
270
  "y": "ceilDiv(normalizedDispatchRows, 65535)",
271
  "z": 1
272
+ }
 
273
  }
274
  ]
275
  }
build/webgpu/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "ai.onnx.RMSNormalization",
3
- "id": "_ai_onnx_rmsnormalization_webgpu_9f92cf5",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
@@ -8,18 +8,18 @@
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "aQq6UMoOU5W6oOnS6tWV1CjBpS0e4r93laCsON1jvqk=",
11
- "manifest.json": "ayMwG0Hl94mrw8rLqrQus65vGITfIySQBSRfgp+6dK0=",
12
- "norm-row-stats.wgsl.jinja": "eRBO50QnNhvyqRW/Wdw6rzfJRgVlRT0P6oWVqcDPf5w=",
13
- "rms-normalization-splitk-normalize.wgsl.jinja": "Vs708VdMX/fAhYyv9Q7EZbVRNllO6XVhaUaxt+aSJGg=",
14
- "rms-normalization-splitk-partials.wgsl.jinja": "Vs4HNsa9ZbLPsW/ZOunRg64qFmbg7WfJ/uE6gqALmSQ=",
15
- "rms-normalization-stash-f16-serial.wgsl.jinja": "stNcILDu/EW/WEVtmhYEcIpgE0vOVp0XdvomibMkXBU=",
16
- "rms-normalization.wgsl.jinja": "5+i/fHAcHlyZ+Co5hpTNZFHHw1YO+QdqsGg2har/Pa4=",
17
- "test.json": "B1fCbyIY2ig58fLsPyM9j4x2gyi1uUuD0TTRN6cUesQ="
18
  }
19
  },
20
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
21
  "webgpu": {
22
- "manifestSpec": "2.0",
23
  "variants": {
24
  "stash_f16_serial": ["rms-normalization-stash-f16-serial.wgsl.jinja"],
25
  "suffix_axis_splitk": ["rms-normalization-splitk-normalize.wgsl.jinja", "rms-normalization-splitk-partials.wgsl.jinja"],
 
1
  {
2
  "name": "ai.onnx.RMSNormalization",
3
+ "id": "_ai_onnx_rmsnormalization_webgpu_79e305f",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
 
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "aQq6UMoOU5W6oOnS6tWV1CjBpS0e4r93laCsON1jvqk=",
11
+ "manifest.json": "yeH/EnaDGr2egsF8QVQs3KXw6ntjk/KVHH0h44Mdr58=",
12
+ "norm-row-stats.wgsl.jinja": "z9fPI5NfWmJ207kdnob9/md3Cc2yVtW3Uui89r8JKDs=",
13
+ "rms-normalization-splitk-normalize.wgsl.jinja": "LZOLQQd6JVX8rA5YOlcl3cw3cyaAvRYKh6h1lvVEiu4=",
14
+ "rms-normalization-splitk-partials.wgsl.jinja": "KnvI9/ZZe4zPBS+v4ieFHw7RTg/2c/MqVHH1Zj3T/F4=",
15
+ "rms-normalization-stash-f16-serial.wgsl.jinja": "7ZVEC2w1SegkxBz3V2T1DKhGKzmf7q32dqAfKXYr35Q=",
16
+ "rms-normalization.wgsl.jinja": "qpO/TCUizsL9TYtnpqNh1rwf3ZYy+Y5EboJ4j4jZqNc=",
17
+ "test.json": "cXXefTiwUMuJYGZSdIBjsMAyw0EO31uh2QXxrbZBNmU="
18
  }
19
  },
20
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
21
  "webgpu": {
22
+ "manifestSpec": "2.1",
23
  "variants": {
24
  "stash_f16_serial": ["rms-normalization-stash-f16-serial.wgsl.jinja"],
25
  "suffix_axis_splitk": ["rms-normalization-splitk-normalize.wgsl.jinja", "rms-normalization-splitk-partials.wgsl.jinja"],
build/webgpu/norm-row-stats.wgsl.jinja CHANGED
@@ -1,25 +1,7 @@
1
- {% if usesF16Spec %}
2
- enable f16;
3
- {% endif %}
4
- {% set combineSubgroups = combineSubgroups %}
5
- {% set scalarIo = scalarIo if scalarIo is defined else false %}
6
- {% set packedBf16Embedding = packedBf16Embedding if packedBf16Embedding is defined else false %}
7
- {% set writeStats = writeStats if writeStats is defined else false %}
8
- {% set rmsWeightOffset = rmsWeightOffset if rmsWeightOffset is defined else false %}
9
- {% set rmsScaleAfterCast = rmsScaleAfterCast if rmsScaleAfterCast is defined else false %}
10
- {% set rmsResidualAdd = rmsResidualAdd if rmsResidualAdd is defined else false %}
11
- {% set rmsChainNorm = rmsChainNorm if rmsChainNorm is defined else false %}
12
- {% set hiddenPairs = hiddenPairs | default(0) %}
13
- {% set numRows = numRows | default(0) %}
14
- {% set epsilon = epsilon | default("0.0") %}
15
- {% set epsilon2 = epsilon2 | default("0.0") %}
16
- {% if rmsWeightOffset %}
17
- {% set rmsScaleVec = "(vec4<f32>(1.0) + vec4<f32>(scale[i]))" %}
18
- {% set rmsScaleScalar = "(1.0 + f32(scale[i]))" %}
19
- {% else %}
20
  {% set rmsScaleVec = "vec4<f32>(scale[i])" %}
21
  {% set rmsScaleScalar = "f32(scale[i])" %}
22
- {% endif %}
23
  {% set reduceThreadParameters = ", sg_lane: u32, sg_id: u32, num_sg: u32"
24
  if combineSubgroups else ", tid: u32" %}
25
  {% set reduceThreadArguments = ", sg_lane, sg_id, num_sg"
@@ -28,67 +10,251 @@ enable f16;
28
  enable subgroups;
29
  {% endif %}
30
  {{ env.wgsl.resourceDeclarations }}
 
 
 
 
 
 
 
 
31
 
32
- // Workgroup-parallel single-pass row statistics + fused normalize/affine.
33
- //
34
- // One workgroup owns one contiguous normalization span ("row": a last-axis
35
- // row, an instance plane, or a channel group). Threads stride the row once,
36
- // accumulating (sum, sum_sq) simultaneously. Partials are reduced either with
37
- // subgroupAdd plus a shared-memory combine or with a portable shared-memory
38
- // tree, then every thread applies the fused normalize + affine write.
39
- //
40
- // RMS mode uses sum_sq / HIDDEN without computing or subtracting a mean.
41
- const HIDDEN: u32 = {{ hidden }}u;
42
- {% if vec4 %}
43
- const HIDDEN_V: u32 = {{ hiddenVec }}u;
44
- {% endif %}
45
- {% if packedBf16Embedding %}
46
- const HIDDEN_PAIRS: u32 = {{ hiddenPairs }}u;
47
- const NUM_ROWS: u32 = {{ numRows }}u;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  {% endif %}
 
 
 
 
 
 
49
  const WG: u32 = {{ wg }}u;
50
  const EPSILON: f32 = {{ epsilon }};
51
- {% if rmsChainNorm %}
52
- const EPSILON2: f32 = {{ epsilon2 }};
53
- {% endif %}
54
 
55
- {% if packedBf16Embedding %}
56
- {% if vec4 %}
57
- fn unpack_bf16_pair(word: u32) -> vec2<f32> {
58
- let bits = vec2<u32>(word & 0xffffu, word >> 16u);
59
- return bitcast<vec2<f32>>(bits << vec2<u32>(16u));
 
 
 
 
 
 
 
 
60
  }
61
- {% endif %}
62
 
63
- {% if not vec4 %}
64
- fn embedding_scalar(source_row: u32, hidden: u32) -> f32 {
65
- if (source_row >= NUM_ROWS) {
66
- return 0.0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
- let word = x[source_row * HIDDEN_PAIRS + (hidden >> 1u)];
69
- let bits = select(word & 0xffffu, word >> 16u, (hidden & 1u) != 0u);
70
- return bitcast<f32>(bits << 16u);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
  {% endif %}
 
 
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  {% if vec4 %}
75
- fn embedding_vec4(source_row: u32, hidden_vec: u32) -> vec4<f32> {
76
- if (source_row >= NUM_ROWS) {
77
- return vec4<f32>(0.0);
 
 
 
 
 
 
 
 
78
  }
79
- let base = source_row * HIDDEN_PAIRS + hidden_vec * 2u;
80
- let low = unpack_bf16_pair(x[base]);
81
- let high = unpack_bf16_pair(x[base + 1u]);
82
- return vec4<f32>(low, high);
83
- }
84
  {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  {% endif %}
86
-
87
- {% if vec4 and scalarIo %}
88
- fn load_vec4(index: u32) -> vec4<f32> {
89
- return vec4<f32>(x[index], x[index + 1u], x[index + 2u], x[index + 3u]);
90
  }
 
 
 
 
 
91
  {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  {% if combineSubgroups %}
94
  var<workgroup> sg_partials: array<f32, WG>;
@@ -142,41 +308,21 @@ fn main(
142
  return;
143
  }
144
  let tid = lid.x;
145
- {% if packedBf16Embedding %}
146
- let source_row = indices[row];
147
- {% if vec4 %}
148
- let base = row * HIDDEN_V;
149
- {% else %}
150
- let base = row * HIDDEN;
151
- {% endif %}
152
- {% elif vec4 and not scalarIo %}
153
  let base = row * HIDDEN_V;
154
  {% else %}
155
  let base = row * HIDDEN;
156
  {% endif %}
157
 
158
-
159
  var acc = 0.0;
160
  {% if vec4 %}
161
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
162
- {% if packedBf16Embedding %}
163
- let v = embedding_vec4(source_row, i);
164
- embedding_out[base + i] = v;
165
- {% elif scalarIo %}
166
- let v = load_vec4(base + i * 4u);
167
- {% else %}
168
- let v = vec4<f32>(x[base + i]);
169
- {% endif %}
170
  acc = acc + dot(v, v);
171
  }
172
  {% else %}
173
  for (var i = tid; i < HIDDEN; i = i + WG) {
174
- {% if packedBf16Embedding %}
175
- let v = embedding_scalar(source_row, i);
176
- embedding_out[base + i] = v;
177
- {% else %}
178
  let v = f32(x[base + i]);
179
- {% endif %}
180
  acc = acc + v * v;
181
  }
182
  {% endif %}
@@ -184,70 +330,19 @@ fn main(
184
  let total = reduce_scalar(acc{{ reduceThreadArguments }});
185
 
186
  let inv = inverseSqrt(total / f32(HIDDEN) + EPSILON);
187
- {% if writeStats %}
188
- if (tid == 0u) {
189
- inv_std_out[row] = inv;
190
- }
191
- {% endif %}
192
 
193
- {% if rmsChainNorm %}
194
- var acc2 = 0.0;
195
- {% endif %}
196
  {% if vec4 %}
197
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
198
- {% if packedBf16Embedding %}
199
- let idx = base + i;
200
- let v = embedding_vec4(source_row, i);
201
- {% elif scalarIo %}
202
- let idx = base + i * 4u;
203
- let v = load_vec4(idx);
204
- {% else %}
205
  let idx = base + i;
206
- let v = vec4<f32>(x[idx]);
207
- {% endif %}
208
- {% if rmsScaleAfterCast %}
209
  y[idx] = {{ vecType }}(v * inv) * {{ vecType }}({{ rmsScaleVec }});
210
- {% elif rmsChainNorm %}
211
- // fma(a, b, 0.0) rounds the weighted product exactly as the decomposed pair's store does,
212
- // and prevents the compiler from re-contracting it into the residual add.
213
- let hv = y[idx] + fma(v * inv, {{ rmsScaleVec }}, vec4<f32>(0.0));
214
- y[idx] = hv;
215
- acc2 = acc2 + dot(hv, hv);
216
- {% elif rmsResidualAdd %}
217
- // See the chained branch: fma(a, b, 0.0) pins the pre-add rounding of the decomposed pair.
218
- y[idx] = y[idx] + fma(v * inv, {{ rmsScaleVec }}, vec4<f32>(0.0));
219
- {% else %}
220
- y[idx] = {{ vecType }}(v * inv * {{ rmsScaleVec }});
221
- {% endif %}
222
  }
223
- {% if rmsChainNorm %}
224
-
225
- // The chained second norm reads the residual row this loop just stored. This
226
- // barrier completes those stores and any preceding shared-scratch use before
227
- // the next reduction reuses its scratch; each lane then re-reads only the
228
- // elements it wrote itself.
229
- workgroupBarrier();
230
- let total2 = reduce_scalar(acc2{{ reduceThreadArguments }});
231
- let inv2 = inverseSqrt(total2 / f32(HIDDEN) + EPSILON2);
232
- for (var i = tid; i < HIDDEN_V; i = i + WG) {
233
- let idx = base + i;
234
- let hv = vec4<f32>(y[idx]);
235
- normed2[idx] = {{ vecType }}(hv * inv2 * vec4<f32>(scale2[i]));
236
- }
237
- {% endif %}
238
  {% else %}
239
  for (var i = tid; i < HIDDEN; i = i + WG) {
240
  let idx = base + i;
241
- {% if packedBf16Embedding %}
242
- let v = embedding_scalar(source_row, i);
243
- {% else %}
244
  let v = f32(x[idx]);
245
- {% endif %}
246
- {% if rmsScaleAfterCast %}
247
  y[idx] = {{ scalar }}(v * inv) * {{ scalar }}({{ rmsScaleScalar }});
248
- {% else %}
249
- y[idx] = {{ scalar }}(v * inv * {{ rmsScaleScalar }});
250
- {% endif %}
251
  }
252
  {% endif %}
253
  }
 
 
1
+ {% set scalarIo = false %}
2
+ {% set packedF32 = "vec4<f32>" %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  {% set rmsScaleVec = "vec4<f32>(scale[i])" %}
4
  {% set rmsScaleScalar = "f32(scale[i])" %}
 
5
  {% set reduceThreadParameters = ", sg_lane: u32, sg_id: u32, num_sg: u32"
6
  if combineSubgroups else ", tid: u32" %}
7
  {% set reduceThreadArguments = ", sg_lane, sg_id, num_sg"
 
10
  enable subgroups;
11
  {% endif %}
12
  {{ env.wgsl.resourceDeclarations }}
13
+ {% if scalar == "f16" and compensateHalfStats is defined and compensateHalfStats %}
14
+ {% set halfOutputScalar = "f16" %}
15
+ {% set halfStageVector = vec4 %}
16
+ fn round_f16_bits_rte(value: f32) -> u32 {
17
+ let bits = bitcast<u32>(value);
18
+ let sign = (bits >> 16u) & 0x8000u;
19
+ let exponent_f32 = (bits >> 23u) & 0xffu;
20
+ let mantissa_f32 = bits & 0x7fffffu;
21
 
22
+ if (exponent_f32 == 0xffu) {
23
+ if (mantissa_f32 != 0u) {
24
+ return 0x7e00u;
25
+ }
26
+ return sign | 0x7c00u;
27
+ }
28
+
29
+ var exponent_f16 = i32(exponent_f32) - 127 + 15;
30
+ if (exponent_f16 >= 0x1f) {
31
+ return sign | 0x7c00u;
32
+ }
33
+
34
+ if (exponent_f16 <= 0) {
35
+ if (exponent_f16 < -10) {
36
+ return sign;
37
+ }
38
+ let significand = mantissa_f32 | 0x800000u;
39
+ let shift = u32(14 - exponent_f16);
40
+ let halfway = 1u << (shift - 1u);
41
+ let discarded = significand & ((1u << shift) - 1u);
42
+ var fraction = significand >> shift;
43
+ if (discarded > halfway || (discarded == halfway && (fraction & 1u) == 1u)) {
44
+ fraction = fraction + 1u;
45
+ }
46
+ return sign | fraction;
47
+ }
48
+
49
+ let halfway = 1u << 12u;
50
+ let discarded = mantissa_f32 & 0x1fffu;
51
+ var mantissa_f16 = mantissa_f32 >> 13u;
52
+ if (discarded > halfway || (discarded == halfway && (mantissa_f16 & 1u) == 1u)) {
53
+ mantissa_f16 = mantissa_f16 + 1u;
54
+ if (mantissa_f16 == 0x400u) {
55
+ mantissa_f16 = 0u;
56
+ exponent_f16 = exponent_f16 + 1;
57
+ }
58
+ }
59
+ if (exponent_f16 >= 0x1f) {
60
+ return sign | 0x7c00u;
61
+ }
62
+ return sign | (u32(exponent_f16) << 10u) | mantissa_f16;
63
+ }
64
+
65
+ fn widen_f16_bits(value: u32) -> f32 {
66
+ return unpack2x16float(value & 0xffffu).x;
67
+ }
68
+
69
+ // Typed ONNX edges must survive arithmetic fusion and narrow/wide casts.
70
+ // Integer rounding also fixes the ties-to-even rule independently of the
71
+ // implementation's floating-point conversion rounding mode.
72
+ fn half_stage(value: f32) -> f32 {
73
+ return widen_f16_bits(round_f16_bits_rte(value));
74
+ }
75
+ {% if halfStageVector | default(false) %}
76
+
77
+ fn half_stage4(value: vec4<f32>) -> vec4<f32> {
78
+ return vec4<f32>(half_stage(value.x), half_stage(value.y),
79
+ half_stage(value.z), half_stage(value.w));
80
+ }
81
  {% endif %}
82
+
83
+ // Half output magnifies statistics errors at rounding midpoints. Keep a low
84
+ // residual through the reduction and normalization, then round the typed
85
+ // float32-normalized/half-scale/half-bias edges explicitly. This is the standard ONNX half path;
86
+ // other normalization contracts retain their existing arithmetic.
87
+ const HIDDEN: u32 = {{ hidden }}u;
88
  const WG: u32 = {{ wg }}u;
89
  const EPSILON: f32 = {{ epsilon }};
 
 
 
90
 
91
+ fn pair_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {
92
+ let s = fma(a.x, 1.0, b.x);
93
+ // Materialize each rounded subtraction in the error-free transform. Plain
94
+ // cancellation expressions do not preserve the intended evaluation tree on
95
+ // every shader backend.
96
+ let bv = fma(-1.0, a.x, s);
97
+ let av = fma(-1.0, bv, s);
98
+ let a_error = fma(-1.0, av, a.x);
99
+ let b_error = fma(-1.0, bv, b.x);
100
+ let error = fma(a_error, 1.0, b_error);
101
+ let e = fma(fma(error, 1.0, a.y), 1.0, b.y);
102
+ let hi = fma(s, 1.0, e);
103
+ return vec2<f32>(hi, fma(-1.0, fma(-1.0, s, hi), e));
104
  }
 
105
 
106
+ fn pair_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {
107
+ let p = fma(a.x, b.x, 0.0);
108
+ let error = fma(a.x, b.x, -p);
109
+ return pair_add(vec2<f32>(p, 0.0), vec2<f32>(error + (a.x * b.y + b.x * a.y), 0.0));
110
+ }
111
+
112
+ fn pair_div(a: vec2<f32>, b: f32) -> vec2<f32> {
113
+ let q = a.x / b;
114
+ let residual = pair_add(a, -pair_mul(vec2<f32>(q, 0.0), vec2<f32>(b, 0.0)));
115
+ return pair_add(vec2<f32>(q, 0.0), vec2<f32>((residual.x + residual.y) / b, 0.0));
116
+ }
117
+
118
+ fn pair_inverse_sqrt(a: vec2<f32>) -> vec2<f32> {
119
+ let r = inverseSqrt(a.x);
120
+ let rr = pair_mul(vec2<f32>(r, 0.0), vec2<f32>(r, 0.0));
121
+ let residual = pair_add(vec2<f32>(1.0, 0.0), -pair_mul(a, rr));
122
+ return pair_add(vec2<f32>(r, 0.0), vec2<f32>((0.5 * r) * (residual.x + residual.y), 0.0));
123
+ }
124
+
125
+ fn half_normalized(value: vec2<f32>) -> f32 {
126
+ // stash_type=1 materializes Normalized as float32 before its cast to half.
127
+ // Collapse the compensated residual at that typed edge; rounding the pair
128
+ // directly to half can choose a different result at a float32 midpoint.
129
+ return widen_f16_bits(round_f16_bits_rte(fma(value.x, 1.0, value.y)));
130
+ }
131
+
132
+ {% if combineSubgroups %}
133
+ var<workgroup> sg_partials: array<vec2<f32>, WG>;
134
+ fn reduce_pair(value: vec2<f32>, sg_lane: u32, sg_id: u32, num_sg: u32, sg_size: u32) -> vec2<f32> {
135
+ var s = value;
136
+ let active_lanes = subgroupBallot(true);
137
+ let counts = countOneBits(active_lanes);
138
+ if (counts.x + counts.y + counts.z + counts.w == sg_size) {
139
+ for (var step = sg_size / 2u; step > 0u; step /= 2u) {
140
+ let other = subgroupShuffleDown(s, step);
141
+ if (sg_lane + step < sg_size) { s = pair_add(s, other); }
142
+ }
143
+ s = subgroupBroadcastFirst(s);
144
+ } else {
145
+ // Small workgroups need not fill a subgroup. Enumerate its actual active
146
+ // lanes instead of adding indeterminate shuffle results from inactive lanes.
147
+ s = vec2<f32>(0.0);
148
+ for (var word = 0u; word < 4u; word++) {
149
+ var mask = active_lanes[word];
150
+ while (mask != 0u) {
151
+ let lane = word * 32u + firstTrailingBit(mask);
152
+ s = pair_add(s, subgroupShuffle(value, lane));
153
+ mask &= mask - 1u;
154
+ }
155
+ }
156
  }
157
+ if (num_sg == 1u) { return s; }
158
+ if (subgroupElect()) { sg_partials[sg_id] = s; }
159
+ workgroupBarrier();
160
+ var total = vec2<f32>(0.0);
161
+ for (var i = 0u; i < num_sg; i++) { total = pair_add(total, sg_partials[i]); }
162
+ // Another reduction may immediately reuse the same storage.
163
+ workgroupBarrier();
164
+ return total;
165
+ }
166
+ {% else %}
167
+ var<workgroup> partial: array<vec2<f32>, WG>;
168
+ fn reduce_pair(value: vec2<f32>, tid: u32) -> vec2<f32> {
169
+ partial[tid] = value;
170
+ workgroupBarrier();
171
+ for (var step = WG / 2u; step > 0u; step /= 2u) {
172
+ if (tid < step) { partial[tid] = pair_add(partial[tid], partial[tid + step]); }
173
+ workgroupBarrier();
174
+ }
175
+ let total = partial[0];
176
+ workgroupBarrier();
177
+ return total;
178
  }
179
  {% endif %}
180
+ {% set reduceArgs = "sg_lane, sg_id, num_sg, sg_size" if combineSubgroups else "tid" %}
181
+ {% if not vec4 %}
182
 
183
+ fn load_value(index: u32) -> f32 {
184
+ return f32(x[index]);
185
+ }
186
+ {% endif %}
187
+
188
+ fn normalize_half_row(row: u32, tid: u32
189
+ {% if combineSubgroups %}
190
+ , sg_lane: u32, sg_id: u32, num_sg: u32, sg_size: u32
191
+ {% endif %}
192
+ ) {
193
+ if (row >= params.rows) { return; }
194
+ let base = row * HIDDEN;
195
+ let mean = vec2<f32>(0.0);
196
+ var local_square = vec2<f32>(0.0);
197
  {% if vec4 %}
198
+ for (var i = tid; i < HIDDEN / 4u; i += WG) {
199
+ let v = vec4<f32>(x[base / 4u + i]);
200
+ {% for component in ["x", "y", "z", "w"] %}
201
+ let centered_{{ component }} = pair_add(vec2<f32>(v.{{ component }}, 0.0), -mean);
202
+ local_square = pair_add(local_square, pair_mul(centered_{{ component }}, centered_{{ component }}));
203
+ {% endfor %}
204
+ }
205
+ {% else %}
206
+ for (var i = tid; i < HIDDEN; i += WG) {
207
+ let centered = pair_add(vec2<f32>(load_value(base + i), 0.0), -mean);
208
+ local_square = pair_add(local_square, pair_mul(centered, centered));
209
  }
 
 
 
 
 
210
  {% endif %}
211
+ let variance = pair_div(reduce_pair(local_square, {{ reduceArgs }}), f32(HIDDEN));
212
+ let inv = pair_inverse_sqrt(pair_add(variance, vec2<f32>(EPSILON, 0.0)));
213
+ {% if vec4 %}
214
+ for (var i = tid; i < HIDDEN / 4u; i += WG) {
215
+ let v = vec4<f32>(x[base / 4u + i]);
216
+ var normalized: vec4<f32>;
217
+ for (var component = 0u; component < 4u; component++) {
218
+ normalized[component] = half_normalized(pair_mul(pair_add(vec2<f32>(v[component], 0.0), -mean), inv));
219
+ }
220
+ var value = half_stage4(normalized * vec4<f32>(scale[i]));
221
+ y[base / 4u + i] = vec4<f16>(value);
222
+ }
223
+ {% else %}
224
+ for (var i = tid; i < HIDDEN; i += WG) {
225
+ let normalized = half_normalized(pair_mul(pair_add(vec2<f32>(load_value(base + i), 0.0), -mean), inv));
226
+ var value = half_stage(normalized * f32(scale[{{ halfScaleOffset | default("i") }}]));
227
+ y[base + i] = {{ halfOutputScalar }}(value);
228
+ }
229
  {% endif %}
 
 
 
 
230
  }
231
+ @compute @workgroup_size(WG, 1, 1)
232
+ fn main(@builtin(workgroup_id) wg_id: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>
233
+ {% if combineSubgroups %}
234
+ , @builtin(subgroup_invocation_id) sg_lane: u32, @builtin(subgroup_id) sg_id: u32,
235
+ @builtin(num_subgroups) num_sg: u32, @builtin(subgroup_size) sg_size: u32
236
  {% endif %}
237
+ ) {
238
+ let row = wg_id.x + wg_id.y * params.rowStride;
239
+ normalize_half_row(row, lid.x{% if combineSubgroups %}, sg_lane, sg_id, num_sg, sg_size{% endif %});
240
+ }
241
+ {% else %}
242
+
243
+ // Workgroup-parallel single-pass row statistics + fused normalize/affine.
244
+ //
245
+ // One workgroup owns one contiguous normalization span ("row": a last-axis
246
+ // row, an instance plane, or a channel group). Threads stride the row once,
247
+ // accumulating (sum, sum_sq) simultaneously. Partials are reduced either with
248
+ // subgroupAdd plus a shared-memory combine or with a portable shared-memory
249
+ // tree, then every thread applies the fused normalize + affine write.
250
+ //
251
+ // RMS mode uses sum_sq / HIDDEN without computing or subtracting a mean.
252
+ const HIDDEN: u32 = {{ hidden }}u;
253
+ {% if vec4 %}
254
+ const HIDDEN_V: u32 = {{ hiddenVec }}u;
255
+ {% endif %}
256
+ const WG: u32 = {{ wg }}u;
257
+ const EPSILON: f32 = {{ epsilon }};
258
 
259
  {% if combineSubgroups %}
260
  var<workgroup> sg_partials: array<f32, WG>;
 
308
  return;
309
  }
310
  let tid = lid.x;
311
+ {% if vec4 and not scalarIo %}
 
 
 
 
 
 
 
312
  let base = row * HIDDEN_V;
313
  {% else %}
314
  let base = row * HIDDEN;
315
  {% endif %}
316
 
 
317
  var acc = 0.0;
318
  {% if vec4 %}
319
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
320
+ let v = {{ packedF32 }}(x[base + i]);
 
 
 
 
 
 
 
321
  acc = acc + dot(v, v);
322
  }
323
  {% else %}
324
  for (var i = tid; i < HIDDEN; i = i + WG) {
 
 
 
 
325
  let v = f32(x[base + i]);
 
326
  acc = acc + v * v;
327
  }
328
  {% endif %}
 
330
  let total = reduce_scalar(acc{{ reduceThreadArguments }});
331
 
332
  let inv = inverseSqrt(total / f32(HIDDEN) + EPSILON);
 
 
 
 
 
333
 
 
 
 
334
  {% if vec4 %}
335
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
 
 
 
 
 
 
 
336
  let idx = base + i;
337
+ let v = {{ packedF32 }}(x[idx]);
 
 
338
  y[idx] = {{ vecType }}(v * inv) * {{ vecType }}({{ rmsScaleVec }});
 
 
 
 
 
 
 
 
 
 
 
 
339
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  {% else %}
341
  for (var i = tid; i < HIDDEN; i = i + WG) {
342
  let idx = base + i;
 
 
 
343
  let v = f32(x[idx]);
 
 
344
  y[idx] = {{ scalar }}(v * inv) * {{ scalar }}({{ rmsScaleScalar }});
 
 
 
345
  }
346
  {% endif %}
347
  }
348
+ {% endif %}
build/webgpu/rms-normalization-splitk-normalize.wgsl.jinja CHANGED
@@ -54,7 +54,6 @@ fn scale_offset({% if scaleRank > 0 %}out_index: u32{% endif %}) -> u32 {
54
  {% endif %}
55
  }
56
 
57
-
58
  var<workgroup> shared_inv: f32;
59
 
60
  @compute @workgroup_size(WG, 1, 1)
 
54
  {% endif %}
55
  }
56
 
 
57
  var<workgroup> shared_inv: f32;
58
 
59
  @compute @workgroup_size(WG, 1, 1)
build/webgpu/rms-normalization-splitk-partials.wgsl.jinja CHANGED
@@ -1,49 +1,15 @@
1
  {% macro wgsl_tree_fold_stmt(a, op, idx, svar) %}
2
- {% if op == "max" %}
3
- {{ a }}[{{ idx }}] = max({{ a }}[{{ idx }}], {{ a }}[{{ idx }} + {{ svar }}]);
4
- {%- else %}
5
- {{ a }}[{{ idx }}] = {{ a }}[{{ idx }}] + {{ a }}[{{ idx }} + {{ svar }}];
6
- {%- endif %}
7
- {% endmacro %}
8
- {% macro wgsl_tree_fold(arrays, op="add", idx="lid", wg="WORKGROUP_SIZE", svar="stride", typed=false, form="tail", breakInline=false, bodyInline=false, barrierFirst=false) %}
9
  var {{ svar }}{{ ": u32 " if typed else " " }}= {{ wg }} / 2u;
10
  loop {
11
- {% if form == "head" %}
12
- {% if breakInline %}
13
  if ({{ svar }} == 0u) { break; }
14
- {% else %}
15
- if ({{ svar }} == 0u) {
16
- break;
17
- }
18
- {% endif %}
19
- {% endif %}
20
- {% if bodyInline %}
21
  if ({{ idx }} < {{ svar }}) { {{ wgsl_tree_fold_stmt(arrays[0], op, idx, svar) }} }
22
- {% else %}
23
- if ({{ idx }} < {{ svar }}) {
24
- {% for a in arrays %}
25
- {{ wgsl_tree_fold_stmt(a, op, idx, svar) }}
26
- {% endfor %}
27
- }
28
- {% endif %}
29
- {% if form == "head" %}
30
- {% if barrierFirst %}
31
- workgroupBarrier();
32
- {{ svar }} = {{ svar }} / 2u;
33
- {% else %}
34
  {{ svar }} = {{ svar }} / 2u;
35
  workgroupBarrier();
36
- {% endif %}
37
- {% else %}
38
- workgroupBarrier();
39
- if ({{ svar }} == 1u) {
40
- break;
41
- }
42
- {{ svar }} = {{ svar }} / 2u;
43
- {% endif %}
44
- }
45
- {%- endmacro %}
46
-
47
  /* Split-K partial sum-of-squares for tensors with few rows and a large hidden
48
  dimension. A workgroup-per-row kernel exposes too little parallelism in this
49
  regime, so this pass splits each row across SPLIT workgroups
 
1
  {% macro wgsl_tree_fold_stmt(a, op, idx, svar) %}
2
+ {% if op == "max" or op == "min" %}
3
+ {{ a }}[{{ idx }}] = {{ op }}({{ a }}[{{ idx }}], {{ a }}[{{ idx }} + {{ svar }}]);{% else %}
4
+ {{ a }}[{{ idx }}] = {{ a }}[{{ idx }}] {{ "*" if op == "prod" else "+" }} {{ a }}[{{ idx }} + {{ svar }}];{% endif %}{% endmacro %}
5
+ {% macro wgsl_tree_fold(arrays, op="add", idx="lid", wg="WORKGROUP_SIZE", svar="stride", typed=false, form="tail", breakInline=false, bodyInline=false, barrierFirst=false, reuse=false) %}
 
 
 
6
  var {{ svar }}{{ ": u32 " if typed else " " }}= {{ wg }} / 2u;
7
  loop {
 
 
8
  if ({{ svar }} == 0u) { break; }
 
 
 
 
 
 
 
9
  if ({{ idx }} < {{ svar }}) { {{ wgsl_tree_fold_stmt(arrays[0], op, idx, svar) }} }
 
 
 
 
 
 
 
 
 
 
 
 
10
  {{ svar }} = {{ svar }} / 2u;
11
  workgroupBarrier();
12
+ }{% endmacro %}
 
 
 
 
 
 
 
 
 
 
13
  /* Split-K partial sum-of-squares for tensors with few rows and a large hidden
14
  dimension. A workgroup-per-row kernel exposes too little parallelism in this
15
  regime, so this pass splits each row across SPLIT workgroups
build/webgpu/rms-normalization-stash-f16-serial.wgsl.jinja CHANGED
@@ -1,5 +1,3 @@
1
- {% if usesF16 %}enable f16;
2
- {% endif %}
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
  const HIDDEN: u32 = {{ hiddenSize }}u;
@@ -58,7 +56,6 @@ fn widen_f16_bits(value: u32) -> f32 {
58
  return unpack2x16float(value & 0xffffu).x;
59
  }
60
 
61
-
62
  {% if scaleRank > 0 %}
63
  const X_RANK: u32 = {{ xRank }}u;
64
  const SCALE_RANK: u32 = {{ scaleRank }}u;
@@ -104,7 +101,6 @@ fn scale_offset({% if scaleRank > 0 %}out_index: u32{% endif %}) -> u32 {
104
  {% endif %}
105
  }
106
 
107
-
108
  // ONNX RMSNormalization-23 expresses the stash_type=FLOAT16 stage as a graph
109
  // of f16 tensor operators. Keeping one normalization row on one invocation
110
  // preserves f16 accumulation and every stage boundary.
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  const HIDDEN: u32 = {{ hiddenSize }}u;
 
56
  return unpack2x16float(value & 0xffffu).x;
57
  }
58
 
 
59
  {% if scaleRank > 0 %}
60
  const X_RANK: u32 = {{ xRank }}u;
61
  const SCALE_RANK: u32 = {{ scaleRank }}u;
 
101
  {% endif %}
102
  }
103
 
 
104
  // ONNX RMSNormalization-23 expresses the stash_type=FLOAT16 stage as a graph
105
  // of f16 tensor operators. Keeping one normalization row on one invocation
106
  // preserves f16 accumulation and every stage boundary.
build/webgpu/rms-normalization.wgsl.jinja CHANGED
@@ -4,8 +4,6 @@ const HIDDEN: u32 = {{ hiddenSize }}u;
4
  const EPSILON: f32 = {{ epsilon }};
5
  const WG: u32 = {{ workgroupSize }}u;
6
 
7
- var<workgroup> partial: array<f32, WG>;
8
-
9
  {% if scaleRank > 0 %}
10
  const X_RANK: u32 = {{ xRank }}u;
11
  const SCALE_RANK: u32 = {{ scaleRank }}u;
@@ -51,70 +49,245 @@ fn scale_offset({% if scaleRank > 0 %}out_index: u32{% endif %}) -> u32 {
51
  {% endif %}
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  {% macro wgsl_tree_fold_stmt(a, op, idx, svar) %}
56
- {% if op == "max" %}
57
- {{ a }}[{{ idx }}] = max({{ a }}[{{ idx }}], {{ a }}[{{ idx }} + {{ svar }}]);
58
- {%- else %}
59
- {{ a }}[{{ idx }}] = {{ a }}[{{ idx }}] + {{ a }}[{{ idx }} + {{ svar }}];
60
- {%- endif %}
61
- {% endmacro %}
62
- {% macro wgsl_tree_fold(arrays, op="add", idx="lid", wg="WORKGROUP_SIZE", svar="stride", typed=false, form="tail", breakInline=false, bodyInline=false, barrierFirst=false) %}
63
  var {{ svar }}{{ ": u32 " if typed else " " }}= {{ wg }} / 2u;
64
  loop {
65
- {% if form == "head" %}
66
- {% if breakInline %}
67
- if ({{ svar }} == 0u) { break; }
68
- {% else %}
69
  if ({{ svar }} == 0u) {
70
  break;
71
  }
72
- {% endif %}
73
- {% endif %}
74
- {% if bodyInline %}
75
- if ({{ idx }} < {{ svar }}) { {{ wgsl_tree_fold_stmt(arrays[0], op, idx, svar) }} }
76
- {% else %}
77
  if ({{ idx }} < {{ svar }}) {
78
  {% for a in arrays %}
79
  {{ wgsl_tree_fold_stmt(a, op, idx, svar) }}
80
  {% endfor %}
81
  }
82
- {% endif %}
83
- {% if form == "head" %}
84
- {% if barrierFirst %}
85
- workgroupBarrier();
86
  {{ svar }} = {{ svar }} / 2u;
87
- {% else %}
88
- {{ svar }} = {{ svar }} / 2u;
89
- workgroupBarrier();
90
- {% endif %}
91
- {% else %}
92
  workgroupBarrier();
93
- if ({{ svar }} == 1u) {
94
- break;
95
- }
96
- {{ svar }} = {{ svar }} / 2u;
97
- {% endif %}
98
- }
99
- {%- endmacro %}
100
-
101
  // Reusing partial after this reduction requires a barrier between the read of
102
  // partial[0] and the next write, or the next round can race the prior readers.
103
- {% set trailingBarrier = trailingBarrier is defined and trailingBarrier %}
104
  fn reduce_sum(value: f32, tid: u32) -> f32 {
105
  partial[tid] = value;
106
  workgroupBarrier();
107
  {{ wgsl_tree_fold(["partial"], idx="tid", wg="WG", form="head") }}
108
- {% if trailingBarrier %}
109
- let total = partial[0];
110
- workgroupBarrier();
111
- return total;
112
- {% else %}
113
  return partial[0];
114
- {% endif %}
115
  }
116
 
117
-
118
  @compute @workgroup_size(WG, 1, 1)
119
  fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
120
  let row = wg.x + wg.y * params.rowStride;
@@ -139,3 +312,4 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
139
  y[base + d] = {{ scalar }}(value);
140
  }
141
  }
 
 
4
  const EPSILON: f32 = {{ epsilon }};
5
  const WG: u32 = {{ workgroupSize }}u;
6
 
 
 
7
  {% if scaleRank > 0 %}
8
  const X_RANK: u32 = {{ xRank }}u;
9
  const SCALE_RANK: u32 = {{ scaleRank }}u;
 
49
  {% endif %}
50
  }
51
 
52
+ {% if compensateHalfStats is defined and compensateHalfStats and xElement == "f16" %}
53
+ {% set halfOutputScalar = scalar %}
54
+ {% set halfScaleOffset = "scale_offset(base + i)" if scaleRank > 0 else "scale_offset()" %}
55
+ {% set halfOutputScalar = halfOutputScalar | default("f16") %}
56
+ {% if halfOutputScalar == "f16" %}
57
+ fn round_f16_bits_rte(value: f32) -> u32 {
58
+ let bits = bitcast<u32>(value);
59
+ let sign = (bits >> 16u) & 0x8000u;
60
+ let exponent_f32 = (bits >> 23u) & 0xffu;
61
+ let mantissa_f32 = bits & 0x7fffffu;
62
+
63
+ if (exponent_f32 == 0xffu) {
64
+ if (mantissa_f32 != 0u) {
65
+ return 0x7e00u;
66
+ }
67
+ return sign | 0x7c00u;
68
+ }
69
+
70
+ var exponent_f16 = i32(exponent_f32) - 127 + 15;
71
+ if (exponent_f16 >= 0x1f) {
72
+ return sign | 0x7c00u;
73
+ }
74
+
75
+ if (exponent_f16 <= 0) {
76
+ if (exponent_f16 < -10) {
77
+ return sign;
78
+ }
79
+ let significand = mantissa_f32 | 0x800000u;
80
+ let shift = u32(14 - exponent_f16);
81
+ let halfway = 1u << (shift - 1u);
82
+ let discarded = significand & ((1u << shift) - 1u);
83
+ var fraction = significand >> shift;
84
+ if (discarded > halfway || (discarded == halfway && (fraction & 1u) == 1u)) {
85
+ fraction = fraction + 1u;
86
+ }
87
+ return sign | fraction;
88
+ }
89
+
90
+ let halfway = 1u << 12u;
91
+ let discarded = mantissa_f32 & 0x1fffu;
92
+ var mantissa_f16 = mantissa_f32 >> 13u;
93
+ if (discarded > halfway || (discarded == halfway && (mantissa_f16 & 1u) == 1u)) {
94
+ mantissa_f16 = mantissa_f16 + 1u;
95
+ if (mantissa_f16 == 0x400u) {
96
+ mantissa_f16 = 0u;
97
+ exponent_f16 = exponent_f16 + 1;
98
+ }
99
+ }
100
+ if (exponent_f16 >= 0x1f) {
101
+ return sign | 0x7c00u;
102
+ }
103
+ return sign | (u32(exponent_f16) << 10u) | mantissa_f16;
104
+ }
105
+
106
+ fn widen_f16_bits(value: u32) -> f32 {
107
+ return unpack2x16float(value & 0xffffu).x;
108
+ }
109
+
110
+ // Typed ONNX edges must survive arithmetic fusion and narrow/wide casts.
111
+ // Integer rounding also fixes the ties-to-even rule independently of the
112
+ // implementation's floating-point conversion rounding mode.
113
+ fn half_stage(value: f32) -> f32 {
114
+ return widen_f16_bits(round_f16_bits_rte(value));
115
+ }
116
+ {% else %}
117
+ fn round_f16_bits_rte(value: f32) -> u32 {
118
+ let bits = bitcast<u32>(value);
119
+ let sign = (bits >> 16u) & 0x8000u;
120
+ let exponent_f32 = (bits >> 23u) & 0xffu;
121
+ let mantissa_f32 = bits & 0x7fffffu;
122
+
123
+ if (exponent_f32 == 0xffu) {
124
+ if (mantissa_f32 != 0u) {
125
+ return 0x7e00u;
126
+ }
127
+ return sign | 0x7c00u;
128
+ }
129
+
130
+ var exponent_f16 = i32(exponent_f32) - 127 + 15;
131
+ if (exponent_f16 >= 0x1f) {
132
+ return sign | 0x7c00u;
133
+ }
134
+
135
+ if (exponent_f16 <= 0) {
136
+ if (exponent_f16 < -10) {
137
+ return sign;
138
+ }
139
+ let significand = mantissa_f32 | 0x800000u;
140
+ let shift = u32(14 - exponent_f16);
141
+ let halfway = 1u << (shift - 1u);
142
+ let discarded = significand & ((1u << shift) - 1u);
143
+ var fraction = significand >> shift;
144
+ if (discarded > halfway || (discarded == halfway && (fraction & 1u) == 1u)) {
145
+ fraction = fraction + 1u;
146
+ }
147
+ return sign | fraction;
148
+ }
149
+
150
+ let halfway = 1u << 12u;
151
+ let discarded = mantissa_f32 & 0x1fffu;
152
+ var mantissa_f16 = mantissa_f32 >> 13u;
153
+ if (discarded > halfway || (discarded == halfway && (mantissa_f16 & 1u) == 1u)) {
154
+ mantissa_f16 = mantissa_f16 + 1u;
155
+ if (mantissa_f16 == 0x400u) {
156
+ mantissa_f16 = 0u;
157
+ exponent_f16 = exponent_f16 + 1;
158
+ }
159
+ }
160
+ if (exponent_f16 >= 0x1f) {
161
+ return sign | 0x7c00u;
162
+ }
163
+ return sign | (u32(exponent_f16) << 10u) | mantissa_f16;
164
+ }
165
+
166
+ fn widen_f16_bits(value: u32) -> f32 {
167
+ return unpack2x16float(value & 0xffffu).x;
168
+ }
169
+ {% endif %}
170
+
171
+ // Half output magnifies statistics errors at rounding midpoints. Keep a low
172
+ // residual through the reduction and normalization, then round the typed
173
+ // float32-normalized/half-scale/half-bias edges explicitly. This is the standard ONNX half path;
174
+ // other normalization contracts retain their existing arithmetic.
175
+
176
+ fn pair_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {
177
+ let s = fma(a.x, 1.0, b.x);
178
+ // Materialize each rounded subtraction in the error-free transform. Plain
179
+ // cancellation expressions do not preserve the intended evaluation tree on
180
+ // every shader backend.
181
+ let bv = fma(-1.0, a.x, s);
182
+ let av = fma(-1.0, bv, s);
183
+ let a_error = fma(-1.0, av, a.x);
184
+ let b_error = fma(-1.0, bv, b.x);
185
+ let error = fma(a_error, 1.0, b_error);
186
+ let e = fma(fma(error, 1.0, a.y), 1.0, b.y);
187
+ let hi = fma(s, 1.0, e);
188
+ return vec2<f32>(hi, fma(-1.0, fma(-1.0, s, hi), e));
189
+ }
190
+
191
+ fn pair_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {
192
+ let p = fma(a.x, b.x, 0.0);
193
+ let error = fma(a.x, b.x, -p);
194
+ return pair_add(vec2<f32>(p, 0.0), vec2<f32>(error + (a.x * b.y + b.x * a.y), 0.0));
195
+ }
196
+
197
+ fn pair_div(a: vec2<f32>, b: f32) -> vec2<f32> {
198
+ let q = a.x / b;
199
+ let residual = pair_add(a, -pair_mul(vec2<f32>(q, 0.0), vec2<f32>(b, 0.0)));
200
+ return pair_add(vec2<f32>(q, 0.0), vec2<f32>((residual.x + residual.y) / b, 0.0));
201
+ }
202
+
203
+ fn pair_inverse_sqrt(a: vec2<f32>) -> vec2<f32> {
204
+ let r = inverseSqrt(a.x);
205
+ let rr = pair_mul(vec2<f32>(r, 0.0), vec2<f32>(r, 0.0));
206
+ let residual = pair_add(vec2<f32>(1.0, 0.0), -pair_mul(a, rr));
207
+ return pair_add(vec2<f32>(r, 0.0), vec2<f32>((0.5 * r) * (residual.x + residual.y), 0.0));
208
+ }
209
+
210
+ fn half_normalized(value: vec2<f32>) -> f32 {
211
+ // stash_type=1 materializes Normalized as float32 before its cast to half.
212
+ // Collapse the compensated residual at that typed edge; rounding the pair
213
+ // directly to half can choose a different result at a float32 midpoint.
214
+ return widen_f16_bits(round_f16_bits_rte(fma(value.x, 1.0, value.y)));
215
+ }
216
+
217
+ var<workgroup> partial: array<vec2<f32>, WG>;
218
+ fn reduce_pair(value: vec2<f32>, tid: u32) -> vec2<f32> {
219
+ partial[tid] = value;
220
+ workgroupBarrier();
221
+ for (var step = WG / 2u; step > 0u; step /= 2u) {
222
+ if (tid < step) { partial[tid] = pair_add(partial[tid], partial[tid + step]); }
223
+ workgroupBarrier();
224
+ }
225
+ let total = partial[0];
226
+ workgroupBarrier();
227
+ return total;
228
+ }
229
+ {% set reduceArgs = "tid" %}
230
+
231
+ fn load_value(index: u32) -> f32 {
232
+ return f32(x[index]);
233
+ }
234
 
235
+ fn normalize_half_row(row: u32, tid: u32
236
+ ) {
237
+ if (row >= params.rows) { return; }
238
+ let base = row * HIDDEN;
239
+ let mean = vec2<f32>(0.0);
240
+ var local_square = vec2<f32>(0.0);
241
+ for (var i = tid; i < HIDDEN; i += WG) {
242
+ let centered = pair_add(vec2<f32>(load_value(base + i), 0.0), -mean);
243
+ local_square = pair_add(local_square, pair_mul(centered, centered));
244
+ }
245
+ let variance = pair_div(reduce_pair(local_square, {{ reduceArgs }}), f32(HIDDEN));
246
+ let inv = pair_inverse_sqrt(pair_add(variance, vec2<f32>(EPSILON, 0.0)));
247
+ for (var i = tid; i < HIDDEN; i += WG) {
248
+ let normalized = half_normalized(pair_mul(pair_add(vec2<f32>(load_value(base + i), 0.0), -mean), inv));
249
+ {% if halfOutputScalar == "f16" %}
250
+ var value = half_stage(normalized * f32(scale[{{ halfScaleOffset | default("i") }}]));
251
+ {% else %}
252
+ var value = normalized * f32(scale[{{ halfScaleOffset | default("i") }}]);
253
+ {% endif %}
254
+ y[base + i] = {{ halfOutputScalar }}(value);
255
+ }
256
+ }
257
+ @compute @workgroup_size(WG, 1, 1)
258
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
259
+ let row = wg.x + wg.y * params.rowStride;
260
+ normalize_half_row(row, lid.x);
261
+ }
262
+ {% else %}
263
+ var<workgroup> partial: array<f32, WG>;
264
  {% macro wgsl_tree_fold_stmt(a, op, idx, svar) %}
265
+ {% if op == "max" or op == "min" %}
266
+ {{ a }}[{{ idx }}] = {{ op }}({{ a }}[{{ idx }}], {{ a }}[{{ idx }} + {{ svar }}]);{% else %}
267
+ {{ a }}[{{ idx }}] = {{ a }}[{{ idx }}] {{ "*" if op == "prod" else "+" }} {{ a }}[{{ idx }} + {{ svar }}];{% endif %}{% endmacro %}
268
+ {% macro wgsl_tree_fold(arrays, op="add", idx="lid", wg="WORKGROUP_SIZE", svar="stride", typed=false, form="tail", breakInline=false, bodyInline=false, barrierFirst=false, reuse=false) %}
 
 
 
269
  var {{ svar }}{{ ": u32 " if typed else " " }}= {{ wg }} / 2u;
270
  loop {
 
 
 
 
271
  if ({{ svar }} == 0u) {
272
  break;
273
  }
 
 
 
 
 
274
  if ({{ idx }} < {{ svar }}) {
275
  {% for a in arrays %}
276
  {{ wgsl_tree_fold_stmt(a, op, idx, svar) }}
277
  {% endfor %}
278
  }
 
 
 
 
279
  {{ svar }} = {{ svar }} / 2u;
 
 
 
 
 
280
  workgroupBarrier();
281
+ }{% endmacro %}
 
 
 
 
 
 
 
282
  // Reusing partial after this reduction requires a barrier between the read of
283
  // partial[0] and the next write, or the next round can race the prior readers.
 
284
  fn reduce_sum(value: f32, tid: u32) -> f32 {
285
  partial[tid] = value;
286
  workgroupBarrier();
287
  {{ wgsl_tree_fold(["partial"], idx="tid", wg="WG", form="head") }}
 
 
 
 
 
288
  return partial[0];
 
289
  }
290
 
 
291
  @compute @workgroup_size(WG, 1, 1)
292
  fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
293
  let row = wg.x + wg.y * params.rowStride;
 
312
  y[base + d] = {{ scalar }}(value);
313
  }
314
  }
315
+ {% endif %}
build/webgpu/test.json CHANGED
@@ -9,6 +9,80 @@
9
  "ort_f16_axis1_outer_inner_broadcast_scale_input_x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
10
  },
11
  "cases": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  {
13
  "name": "subgroup_vec4_2x512",
14
  "attrs": { "epsilon": 0.000001, "axis": -1 },
@@ -1391,7 +1465,7 @@
1391
  {
1392
  "name": "onnx23_f16_cast_before_scale_row_vec4_exact",
1393
  "provenance": {
1394
- "notes": "ONNX RMSNormalization-23 stage one casts Normalized back to X's dtype before Scale. This vector-scale case selects the optimized vec4 row kernel and differs by one f16 ULP if the cast is deferred until after Scale."
1395
  },
1396
  "requires": { "features": ["shader-f16"] },
1397
  "attrs": { "epsilon": 0.00001, "axis": -1 },
@@ -1449,7 +1523,7 @@
1449
  {
1450
  "name": "onnx23_f16_cast_before_scale_splitk_exact",
1451
  "provenance": {
1452
- "notes": "Forces the split-K shared kernel on the same exact f16 boundary case, pinning the standardized cast of Normalized back to X before Scale."
1453
  },
1454
  "requires": { "features": ["shader-f16"] },
1455
  "tunables": { "SPLIT_MIN_HIDDEN": 1, "SPLIT_TARGET_ELEMENTS": 1 },
 
9
  "ort_f16_axis1_outer_inner_broadcast_scale_input_x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
10
  },
11
  "cases": [
12
+ {
13
+ "name": "half_normalized_midpoint_scalar",
14
+ "attrs": { "axis": -1, "epsilon": 0.000001 },
15
+ "inputs": {
16
+ "x": {
17
+ "dtype": "float16",
18
+ "shape": [1, 7],
19
+ "data": {
20
+ "kind": "values",
21
+ "values": [0.333984375, -0.82177734375, 0.35009765625, -1.189453125, -0.8701171875, 0.043914794921875, -0.35595703125]
22
+ }
23
+ },
24
+ "scale": {
25
+ "dtype": "float16",
26
+ "shape": [7],
27
+ "data": {
28
+ "kind": "values",
29
+ "values": [1.1455078125, 1.0283203125, 0.92626953125, 1.0771484375, 1.232421875, 0.89990234375, 1.060546875]
30
+ }
31
+ }
32
+ },
33
+ "outputs": {
34
+ "y": {
35
+ "dtype": "float16",
36
+ "shape": [1, 7],
37
+ "data": {
38
+ "kind": "values",
39
+ "values": [0.56494140625, -1.248046875, 0.47900390625, -1.892578125, -1.583984375, 0.058319091796875, -0.5576171875]
40
+ },
41
+ "tolerance": 0.000002
42
+ }
43
+ },
44
+ "provenance": {
45
+ "source": "ONNX RMSNormalization typed normalization and scale stages",
46
+ "notes": "Float32 Normalized midpoint followed by cast to half and affine half rounding. The CPU reference and independent PyTorch float32 typed stages agree; rounding float64 Normalized directly to half gives a different result."
47
+ }
48
+ },
49
+ {
50
+ "name": "half_normalized_midpoint_vec4",
51
+ "attrs": { "axis": -1, "epsilon": 0.000001 },
52
+ "inputs": {
53
+ "x": {
54
+ "dtype": "float16",
55
+ "shape": [1, 28],
56
+ "data": {
57
+ "kind": "values",
58
+ "values": [0.333984375, -0.82177734375, 0.35009765625, -1.189453125, -0.8701171875, 0.043914794921875, -0.35595703125, 0.333984375, -0.82177734375, 0.35009765625, -1.189453125, -0.8701171875, 0.043914794921875, -0.35595703125, 0.333984375, -0.82177734375, 0.35009765625, -1.189453125, -0.8701171875, 0.043914794921875, -0.35595703125, 0.333984375, -0.82177734375, 0.35009765625, -1.189453125, -0.8701171875, 0.043914794921875, -0.35595703125]
59
+ }
60
+ },
61
+ "scale": {
62
+ "dtype": "float16",
63
+ "shape": [28],
64
+ "data": {
65
+ "kind": "values",
66
+ "values": [1.1455078125, 1.0283203125, 0.92626953125, 1.0771484375, 1.232421875, 0.89990234375, 1.060546875, 1.1455078125, 1.0283203125, 0.92626953125, 1.0771484375, 1.232421875, 0.89990234375, 1.060546875, 1.1455078125, 1.0283203125, 0.92626953125, 1.0771484375, 1.232421875, 0.89990234375, 1.060546875, 1.1455078125, 1.0283203125, 0.92626953125, 1.0771484375, 1.232421875, 0.89990234375, 1.060546875]
67
+ }
68
+ }
69
+ },
70
+ "outputs": {
71
+ "y": {
72
+ "dtype": "float16",
73
+ "shape": [1, 28],
74
+ "data": {
75
+ "kind": "values",
76
+ "values": [0.56494140625, -1.248046875, 0.47900390625, -1.892578125, -1.583984375, 0.058319091796875, -0.5576171875, 0.56494140625, -1.248046875, 0.47900390625, -1.892578125, -1.583984375, 0.058319091796875, -0.5576171875, 0.56494140625, -1.248046875, 0.47900390625, -1.892578125, -1.583984375, 0.058319091796875, -0.5576171875, 0.56494140625, -1.248046875, 0.47900390625, -1.892578125, -1.583984375, 0.058319091796875, -0.5576171875]
77
+ },
78
+ "tolerance": 0.000002
79
+ }
80
+ },
81
+ "provenance": {
82
+ "source": "ONNX RMSNormalization typed normalization and scale stages",
83
+ "notes": "Float32 Normalized midpoint followed by cast to half and affine half rounding. The CPU reference and independent PyTorch float32 typed stages agree; rounding float64 Normalized directly to half gives a different result."
84
+ }
85
+ },
86
  {
87
  "name": "subgroup_vec4_2x512",
88
  "attrs": { "epsilon": 0.000001, "axis": -1 },
 
1465
  {
1466
  "name": "onnx23_f16_cast_before_scale_row_vec4_exact",
1467
  "provenance": {
1468
+ "notes": "ONNX RMSNormalization-23 casts Normalized back to X's dtype before Scale. This vector-scale case differs by one float16 ULP if the cast is deferred."
1469
  },
1470
  "requires": { "features": ["shader-f16"] },
1471
  "attrs": { "epsilon": 0.00001, "axis": -1 },
 
1523
  {
1524
  "name": "onnx23_f16_cast_before_scale_splitk_exact",
1525
  "provenance": {
1526
+ "notes": "A four-element float16 row with a scalar Scale checks the standardized cast of Normalized back to X's dtype before Scale."
1527
  },
1528
  "requires": { "features": ["shader-f16"] },
1529
  "tunables": { "SPLIT_MIN_HIDDEN": 1, "SPLIT_TARGET_ELEMENTS": 1 },