Xenova HF Staff commited on
Commit
48220c7
·
verified ·
1 Parent(s): 554ff82

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,75 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.SimplifiedLayerNormalization
10
+
11
+ `ai.onnx` · ONNX Runtime compatibility operator · default-domain since_version 1
12
+
13
+ ## Description
14
+
15
+ Implements ONNX Runtime's legacy RMS normalization for models that serialize `SimplifiedLayerNormalization` in the default ONNX domain: `Y = (X / sqrt(mean(X^2) + epsilon)) * scale`. It shares kernels with [ONNX `RMSNormalization`](https://onnx.ai/onnx/operators/onnx__RMSNormalization.html), but retains scale-before-output-cast ordering and optional `inv_std_var`. See the [compatibility discussion](https://github.com/onnx/onnx/issues/6582#issuecomment-3591862327). Supports float16/float32, float32 statistics, and a nonempty normalization suffix.
16
+
17
+ ## Inputs
18
+
19
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
20
+ | --- | --- | --- | --- | --- | --- | --- |
21
+ | `X` | `x` | `T` | — | — | Input tensor; the mean square is taken over the suffix dimensions starting at `axis`. | required |
22
+ | `scale` | `scale` | `V` | — | — | Scale tensor, unidirectionally broadcastable to `X`; output `Y` has this tensor's dtype. | required |
23
+
24
+ ## Outputs
25
+
26
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
27
+ | --- | --- | --- | --- | --- | --- | --- |
28
+ | `Y` | `y` | `V` | same as `X` | same as `X` | Normalized and scaled output; same shape as `X` and same dtype as `scale`. | required |
29
+ | `inv_std_var` | `invStdVar` | `U` | same as `X` | derived; see description | Optional inverse RMS statistic `1 / sqrt(mean(X^2) + epsilon)`; same rank as `X`, with the dimensions from `axis` onward set to one. | optional |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `axis` | `-1` | The first dimension of the normalization suffix; negative values count from the end, so the default `-1` normalizes only the last dimension. |
38
+ | `epsilon` | `0.00001` | Small constant added to the mean square before taking the square root to avoid division by zero. |
39
+ | `stash_type` | `1` | TensorProto element type of `inv_std_var`; this package implements the legacy float32 value `1`. |
40
+ | `keep_dims` | `1` | Legacy compatibility attribute accepted and ignored by ONNX Runtime. The supported value `1` records the runtime's fixed behavior: optional statistics retain the input rank with normalized dimensions set to one. |
41
+
42
+ ## Type constraints
43
+
44
+ | Variable | Allowed dtypes |
45
+ | --- | --- |
46
+ | `T` | `float32`, `float16` |
47
+ | `V` | `float32`, `float16` |
48
+ | `U` | `float32` |
49
+
50
+ ## Files
51
+
52
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
53
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
54
+ - [`test.json`](build/webgpu/test.json) — correctness cases
55
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
56
+ - [`norm-row-stats.wgsl.jinja`](build/webgpu/norm-row-stats.wgsl.jinja)
57
+ - [`rms-normalization-splitk-normalize.wgsl.jinja`](build/webgpu/rms-normalization-splitk-normalize.wgsl.jinja)
58
+ - [`rms-normalization-splitk-partials.wgsl.jinja`](build/webgpu/rms-normalization-splitk-partials.wgsl.jinja)
59
+ - [`rms-normalization.wgsl.jinja`](build/webgpu/rms-normalization.wgsl.jinja)
60
+
61
+ ## Use with `@huggingface/kernels`
62
+
63
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
64
+ It then allocates the result tensors automatically.
65
+
66
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
67
+
68
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
69
+
70
+ ```js
71
+ import { getKernel } from "@huggingface/kernels";
72
+
73
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.SimplifiedLayerNormalization", { version: 1 });
74
+ const { y } = await kernel({ x: { data: xData, shape: [8] }, scale: { data: scaleData, shape: [8] } });
75
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.SimplifiedLayerNormalization",
3
+ "cases": [
4
+ {
5
+ "name": "simplifiedln-f32-4096x4096",
6
+ "preset": "smoke",
7
+ "vars": { "dtype": "float32", "rows": 4096, "dim": 4096 },
8
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
9
+ "inputs": {
10
+ "x": { "shape": [4096, 4096], "dtype": "float32", "dist": "normal", "seed": 720, "scale": 0.5 },
11
+ "scale": { "shape": [4096], "dtype": "float32", "dist": "uniform", "seed": 721, "scale": 0.25, "offset": 1 }
12
+ },
13
+ "outputs": { "y": { "shape": [4096, 4096], "dtype": "float32" } },
14
+ "bench": {
15
+ "primary": true,
16
+ "metrics": [{ "type": "bandwidth", "value": "(args.rows * args.dim * 2 + args.dim) * dtypeBytes(args.dtype)" }]
17
+ }
18
+ },
19
+ {
20
+ "name": "simplifiedln-f32-4096x4096-stats",
21
+ "preset": "smoke",
22
+ "vars": { "dtype": "float32", "rows": 4096, "dim": 4096 },
23
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
24
+ "inputs": {
25
+ "x": { "shape": [4096, 4096], "dtype": "float32", "dist": "normal", "seed": 722, "scale": 0.5 },
26
+ "scale": { "shape": [4096], "dtype": "float32", "dist": "uniform", "seed": 723, "scale": 0.25, "offset": 1 }
27
+ },
28
+ "outputs": {
29
+ "y": { "shape": [4096, 4096], "dtype": "float32" },
30
+ "invStdVar": { "shape": [4096, 1], "dtype": "float32" }
31
+ },
32
+ "bench": {
33
+ "primary": true,
34
+ "metrics": [
35
+ {
36
+ "type": "bandwidth",
37
+ "value": "(args.rows * args.dim * 2 + args.dim) * dtypeBytes(args.dtype) + args.rows * 4"
38
+ }
39
+ ]
40
+ }
41
+ },
42
+ {
43
+ "name": "simplifiedln-f16-8192x3072-stats",
44
+ "preset": "smoke",
45
+ "vars": { "dtype": "float16", "rows": 8192, "dim": 3072 },
46
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
47
+ "inputs": {
48
+ "x": { "shape": [8192, 3072], "dtype": "float16", "dist": "normal", "seed": 724, "scale": 0.5 },
49
+ "scale": { "shape": [3072], "dtype": "float16", "dist": "uniform", "seed": 725, "scale": 0.25, "offset": 1 }
50
+ },
51
+ "outputs": {
52
+ "y": { "shape": [8192, 3072], "dtype": "float16" },
53
+ "invStdVar": { "shape": [8192, 1], "dtype": "float32" }
54
+ },
55
+ "bench": {
56
+ "primary": true,
57
+ "metrics": [
58
+ {
59
+ "type": "bandwidth",
60
+ "value": "(args.rows * args.dim * 2 + args.dim) * dtypeBytes(args.dtype) + args.rows * 4"
61
+ }
62
+ ]
63
+ }
64
+ }
65
+ ]
66
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,591 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "SimplifiedLayerNormalization",
4
+ "conformance": "legacy-default-domain",
5
+ "sinceVersion": 1,
6
+ "description": "Implements ONNX Runtime's legacy RMS normalization for models that serialize `SimplifiedLayerNormalization` in the default ONNX domain: `Y = (X / sqrt(mean(X^2) + epsilon)) * scale`. It shares kernels with [ONNX `RMSNormalization`](https://onnx.ai/onnx/operators/onnx__RMSNormalization.html), but retains scale-before-output-cast ordering and optional `inv_std_var`. See the [compatibility discussion](https://github.com/onnx/onnx/issues/6582#issuecomment-3591862327). Supports float16/float32, float32 statistics, and a nonempty normalization suffix.",
7
+ "inputs": [
8
+ {
9
+ "role": "X",
10
+ "dtype": "T",
11
+ "description": "Input tensor; the mean square is taken over the suffix dimensions starting at `axis`."
12
+ },
13
+ {
14
+ "role": "scale",
15
+ "dtype": "V",
16
+ "description": "Scale tensor, unidirectionally broadcastable to `X`; output `Y` has this tensor's dtype."
17
+ }
18
+ ],
19
+ "outputs": [
20
+ {
21
+ "role": "Y",
22
+ "dtype": "V",
23
+ "rank": "ranks.X",
24
+ "shape": "shapes.X",
25
+ "description": "Normalized and scaled output; same shape as `X` and same dtype as `scale`."
26
+ },
27
+ {
28
+ "role": "inv_std_var",
29
+ "dtype": "U",
30
+ "rank": "ranks.X",
31
+ "shape": "prefix(shapes.X, axisNorm) + fill(1, ranks.X - axisNorm)",
32
+ "optional": true,
33
+ "description": "Optional inverse RMS statistic `1 / sqrt(mean(X^2) + epsilon)`; same rank as `X`, with the dimensions from `axis` onward set to one."
34
+ }
35
+ ],
36
+ "attributes": { "axis": -1, "epsilon": 0.00001, "stash_type": 1, "keep_dims": 1 },
37
+ "attributeDescriptions": {
38
+ "axis": "The first dimension of the normalization suffix; negative values count from the end, so the default `-1` normalizes only the last dimension.",
39
+ "epsilon": "Small constant added to the mean square before taking the square root to avoid division by zero.",
40
+ "stash_type": "TensorProto element type of `inv_std_var`; this package implements the legacy float32 value `1`.",
41
+ "keep_dims": "Legacy compatibility attribute accepted and ignored by ONNX Runtime. The supported value `1` records the runtime's fixed behavior: optional statistics retain the input rank with normalized dimensions set to one."
42
+ },
43
+ "attributeConstraints": { "stash_type": { "values": [1] }, "keep_dims": { "values": [1] } },
44
+ "typeConstraints": { "T": ["float32", "float16"], "V": ["float32", "float16"], "U": ["float32"] },
45
+ "args": {
46
+ "x": { "kind": "tensor", "semantic": "X", "role": "input" },
47
+ "scale": { "kind": "tensor", "semantic": "scale", "role": "input" },
48
+ "y": { "kind": "tensor", "semantic": "Y", "role": "output" },
49
+ "invStdVar": { "kind": "tensor", "semantic": "inv_std_var", "role": "output", "required": false }
50
+ },
51
+ "tunables": {
52
+ "WORKGROUP_SIZE": 256,
53
+ "SPLIT_MAX_ROWS": 256,
54
+ "SPLIT_MIN_HIDDEN": 16384,
55
+ "SPLIT_TARGET_ELEMENTS": 4096,
56
+ "MAX_SPLITS": 64
57
+ },
58
+ "derive": {
59
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
60
+ "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
61
+ "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
62
+ "normMaxWorkgroup": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
63
+ "hasSubgroupId": "device.features.has(\"subgroups\") and device.wgslLanguageFeatures.has(\"subgroup_id\")",
64
+ "axisNorm": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.X",
65
+ "normRows": "outer(shapes.X, axisNorm)",
66
+ "normHidden": "dim(shapes.X, axisNorm) * inner(shapes.X, axisNorm)",
67
+ "normRowStride": "max(1, min(normRows, device.limits.maxComputeWorkgroupsPerDimension))",
68
+ "rowWg": "min(normMaxWorkgroup, pow2ceil(max(1, normHidden)))",
69
+ "baseOk": "ranks.X >= 1 and sameShape(shapes.Y, shapes.X) and ranks.scale >= 0 and ranks.scale <= ranks.X and broadcastable(shapes.scale, shapes.X) and attrs.axis + ranks.X >= 0 and attrs.axis < ranks.X and normHidden > 0 and attrs.stash_type == onnxDtypeCode(\"float32\") and f16Ok(dtypes.T) and f16Ok(dtypes.V)",
70
+ "lastAxisOk": "baseOk and (attrs.axis == -1 or attrs.axis == ranks.X - 1)",
71
+ "suffixAxisOk": "baseOk and ranks.X >= 2 and not (attrs.axis == -1 or attrs.axis == ranks.X - 1)",
72
+ "noStats": "not present.invStdVar",
73
+ "statsOk": "present.invStdVar and ranks.inv_std_var == ranks.X and sameShape(prefix(shapes.inv_std_var, axisNorm), prefix(shapes.X, axisNorm)) and numel(suffix(shapes.inv_std_var, axisNorm)) == 1",
74
+ "sameDtype": "dtypes.T == dtypes.V",
75
+ "splitCount": "min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(normHidden, tunables.SPLIT_TARGET_ELEMENTS)))",
76
+ "splitScratchBytes": "normRows * splitCount * 4",
77
+ "splitFits": "normRows <= tunables.SPLIT_MAX_ROWS and splitCount <= device.limits.maxComputeWorkgroupsPerDimension and splitScratchBytes <= device.limits.maxStorageBufferBindingSize and splitScratchBytes <= device.limits.maxBufferSize"
78
+ },
79
+ "bindingSets": {
80
+ "rows": [
81
+ {
82
+ "name": "x",
83
+ "arg": "x",
84
+ "semantic": "X",
85
+ "buffer": { "type": "read-only-storage" },
86
+ "elementType": "$xElement"
87
+ },
88
+ {
89
+ "name": "scale",
90
+ "arg": "scale",
91
+ "semantic": "scale",
92
+ "buffer": { "type": "read-only-storage" },
93
+ "elementType": "$ioElement"
94
+ },
95
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$ioElement" },
96
+ {
97
+ "name": "params",
98
+ "semantic": "kernel.params",
99
+ "buffer": { "type": "uniform" },
100
+ "struct": {
101
+ "name": "Params",
102
+ "fields": [
103
+ { "name": "rows", "type": "u32", "value": "normRows" },
104
+ { "name": "rowStride", "type": "u32", "value": "normRowStride" }
105
+ ]
106
+ }
107
+ }
108
+ ],
109
+ "rowsStats": [
110
+ {
111
+ "name": "x",
112
+ "arg": "x",
113
+ "semantic": "X",
114
+ "buffer": { "type": "read-only-storage" },
115
+ "elementType": "$xElement"
116
+ },
117
+ {
118
+ "name": "scale",
119
+ "arg": "scale",
120
+ "semantic": "scale",
121
+ "buffer": { "type": "read-only-storage" },
122
+ "elementType": "$ioElement"
123
+ },
124
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$ioElement" },
125
+ {
126
+ "name": "inv_std_out",
127
+ "arg": "invStdVar",
128
+ "semantic": "inv_std_var",
129
+ "buffer": { "type": "storage" },
130
+ "elementType": "f32"
131
+ },
132
+ {
133
+ "name": "params",
134
+ "semantic": "kernel.params",
135
+ "buffer": { "type": "uniform" },
136
+ "struct": {
137
+ "name": "Params",
138
+ "fields": [
139
+ { "name": "rows", "type": "u32", "value": "normRows" },
140
+ { "name": "rowStride", "type": "u32", "value": "normRowStride" }
141
+ ]
142
+ }
143
+ }
144
+ ],
145
+ "splitPartials": [
146
+ {
147
+ "name": "x",
148
+ "arg": "x",
149
+ "semantic": "X",
150
+ "buffer": { "type": "read-only-storage" },
151
+ "elementType": "$xElement"
152
+ },
153
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "f32" },
154
+ {
155
+ "name": "params",
156
+ "semantic": "kernel.params",
157
+ "buffer": { "type": "uniform" },
158
+ "struct": {
159
+ "name": "Params",
160
+ "fields": [
161
+ { "name": "rows", "type": "u32", "value": "normRows" },
162
+ { "name": "rowStride", "type": "u32", "value": "normRowStride" }
163
+ ]
164
+ }
165
+ }
166
+ ],
167
+ "splitNormalize": [
168
+ {
169
+ "name": "x",
170
+ "arg": "x",
171
+ "semantic": "X",
172
+ "buffer": { "type": "read-only-storage" },
173
+ "elementType": "$xElement"
174
+ },
175
+ {
176
+ "name": "scale",
177
+ "arg": "scale",
178
+ "semantic": "scale",
179
+ "buffer": { "type": "read-only-storage" },
180
+ "elementType": "$ioElement"
181
+ },
182
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
183
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$ioElement" },
184
+ {
185
+ "name": "params",
186
+ "semantic": "kernel.params",
187
+ "buffer": { "type": "uniform" },
188
+ "struct": {
189
+ "name": "Params",
190
+ "fields": [
191
+ { "name": "rows", "type": "u32", "value": "normRows" },
192
+ { "name": "rowStride", "type": "u32", "value": "normRowStride" }
193
+ ]
194
+ }
195
+ }
196
+ ],
197
+ "splitNormalizeStats": [
198
+ {
199
+ "name": "x",
200
+ "arg": "x",
201
+ "semantic": "X",
202
+ "buffer": { "type": "read-only-storage" },
203
+ "elementType": "$xElement"
204
+ },
205
+ {
206
+ "name": "scale",
207
+ "arg": "scale",
208
+ "semantic": "scale",
209
+ "buffer": { "type": "read-only-storage" },
210
+ "elementType": "$ioElement"
211
+ },
212
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
213
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$ioElement" },
214
+ {
215
+ "name": "inv_std_out",
216
+ "arg": "invStdVar",
217
+ "semantic": "inv_std_var",
218
+ "buffer": { "type": "storage" },
219
+ "elementType": "f32"
220
+ },
221
+ {
222
+ "name": "params",
223
+ "semantic": "kernel.params",
224
+ "buffer": { "type": "uniform" },
225
+ "struct": {
226
+ "name": "Params",
227
+ "fields": [
228
+ { "name": "rows", "type": "u32", "value": "normRows" },
229
+ { "name": "rowStride", "type": "u32", "value": "normRowStride" }
230
+ ]
231
+ }
232
+ }
233
+ ]
234
+ },
235
+ "variants": [
236
+ {
237
+ "id": "last_axis",
238
+ "priority": 1,
239
+ "when": ["lastAxisOk", "noStats"],
240
+ "constants": {
241
+ "scalar": "dtypes.V",
242
+ "xElement": "dtypes.T",
243
+ "ioElement": "dtypes.V",
244
+ "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
245
+ "hiddenSize": "normHidden",
246
+ "workgroupSize": "rowWg",
247
+ "epsilon": "attrs.epsilon"
248
+ },
249
+ "passes": [
250
+ {
251
+ "id": "main",
252
+ "name": "SimplifiedLayerNormalization.Row",
253
+ "source": {
254
+ "shader": "rms-normalization.wgsl.jinja",
255
+ "inputs": {
256
+ "xShape": "shapes.X",
257
+ "scaleShape": "shapes.scale",
258
+ "xRank": "ranks.X",
259
+ "scaleRank": "ranks.scale",
260
+ "writeStats": false,
261
+ "rmsScaleAfterCast": false
262
+ }
263
+ },
264
+ "bindings": "rows",
265
+ "dispatch": { "workgroups": "normRows" }
266
+ }
267
+ ]
268
+ },
269
+ {
270
+ "id": "last_axis_stats",
271
+ "priority": 2,
272
+ "when": ["lastAxisOk", "statsOk"],
273
+ "constants": {
274
+ "scalar": "dtypes.V",
275
+ "xElement": "dtypes.T",
276
+ "ioElement": "dtypes.V",
277
+ "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
278
+ "hiddenSize": "normHidden",
279
+ "workgroupSize": "rowWg",
280
+ "epsilon": "attrs.epsilon"
281
+ },
282
+ "passes": [
283
+ {
284
+ "id": "main",
285
+ "name": "SimplifiedLayerNormalization.Row",
286
+ "source": {
287
+ "shader": "rms-normalization.wgsl.jinja",
288
+ "inputs": {
289
+ "xShape": "shapes.X",
290
+ "scaleShape": "shapes.scale",
291
+ "xRank": "ranks.X",
292
+ "scaleRank": "ranks.scale",
293
+ "writeStats": true,
294
+ "rmsScaleAfterCast": false
295
+ }
296
+ },
297
+ "bindings": "rowsStats",
298
+ "dispatch": { "workgroups": "normRows" }
299
+ }
300
+ ]
301
+ },
302
+ {
303
+ "id": "suffix_axis",
304
+ "priority": 10,
305
+ "when": ["suffixAxisOk", "noStats"],
306
+ "constants": {
307
+ "scalar": "dtypes.V",
308
+ "xElement": "dtypes.T",
309
+ "ioElement": "dtypes.V",
310
+ "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
311
+ "hiddenSize": "normHidden",
312
+ "workgroupSize": "rowWg",
313
+ "epsilon": "attrs.epsilon"
314
+ },
315
+ "passes": [
316
+ {
317
+ "id": "main",
318
+ "name": "SimplifiedLayerNormalization.Row",
319
+ "source": {
320
+ "shader": "rms-normalization.wgsl.jinja",
321
+ "inputs": {
322
+ "xShape": "shapes.X",
323
+ "scaleShape": "shapes.scale",
324
+ "xRank": "ranks.X",
325
+ "scaleRank": "ranks.scale",
326
+ "writeStats": false,
327
+ "rmsScaleAfterCast": false
328
+ }
329
+ },
330
+ "bindings": "rows",
331
+ "dispatch": { "workgroups": "normRows" }
332
+ }
333
+ ]
334
+ },
335
+ {
336
+ "id": "suffix_axis_stats",
337
+ "priority": 11,
338
+ "when": ["suffixAxisOk", "statsOk"],
339
+ "constants": {
340
+ "scalar": "dtypes.V",
341
+ "xElement": "dtypes.T",
342
+ "ioElement": "dtypes.V",
343
+ "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
344
+ "hiddenSize": "normHidden",
345
+ "workgroupSize": "rowWg",
346
+ "epsilon": "attrs.epsilon"
347
+ },
348
+ "passes": [
349
+ {
350
+ "id": "main",
351
+ "name": "SimplifiedLayerNormalization.Row",
352
+ "source": {
353
+ "shader": "rms-normalization.wgsl.jinja",
354
+ "inputs": {
355
+ "xShape": "shapes.X",
356
+ "scaleShape": "shapes.scale",
357
+ "xRank": "ranks.X",
358
+ "scaleRank": "ranks.scale",
359
+ "writeStats": true,
360
+ "rmsScaleAfterCast": false
361
+ }
362
+ },
363
+ "bindings": "rowsStats",
364
+ "dispatch": { "workgroups": "normRows" }
365
+ }
366
+ ]
367
+ },
368
+ {
369
+ "id": "suffix_axis_splitk",
370
+ "priority": 15,
371
+ "when": ["baseOk", "ranks.X >= 2", "noStats", "splitFits"],
372
+ "demoteWhen": ["reportedNonWave32Adapter", "normHidden < tunables.SPLIT_MIN_HIDDEN"],
373
+ "constants": {
374
+ "scalar": "dtypes.V",
375
+ "xElement": "dtypes.T",
376
+ "ioElement": "dtypes.V",
377
+ "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
378
+ "hiddenSize": "normHidden",
379
+ "workgroupSize": "normMaxWorkgroup",
380
+ "split": "splitCount",
381
+ "epsilon": "attrs.epsilon"
382
+ },
383
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[normRows * splitCount]" }],
384
+ "passes": [
385
+ {
386
+ "id": "partials",
387
+ "name": "SimplifiedLayerNormalization.SplitKPartials",
388
+ "source": { "shader": "rms-normalization-splitk-partials.wgsl.jinja", "inputs": {} },
389
+ "bindings": "splitPartials",
390
+ "dispatch": { "workgroups": "normRows", "z": "splitCount" }
391
+ },
392
+ {
393
+ "id": "normalize",
394
+ "name": "SimplifiedLayerNormalization.SplitKNormalize",
395
+ "source": {
396
+ "shader": "rms-normalization-splitk-normalize.wgsl.jinja",
397
+ "inputs": {
398
+ "xShape": "shapes.X",
399
+ "scaleShape": "shapes.scale",
400
+ "xRank": "ranks.X",
401
+ "scaleRank": "ranks.scale",
402
+ "writeStats": false,
403
+ "rmsScaleAfterCast": false
404
+ }
405
+ },
406
+ "bindings": "splitNormalize",
407
+ "dispatch": { "workgroups": "normRows", "z": "splitCount" }
408
+ }
409
+ ]
410
+ },
411
+ {
412
+ "id": "suffix_axis_splitk_stats",
413
+ "priority": 16,
414
+ "when": ["baseOk", "ranks.X >= 2", "statsOk", "splitFits"],
415
+ "demoteWhen": ["reportedNonWave32Adapter", "normHidden < tunables.SPLIT_MIN_HIDDEN"],
416
+ "constants": {
417
+ "scalar": "dtypes.V",
418
+ "xElement": "dtypes.T",
419
+ "ioElement": "dtypes.V",
420
+ "usesF16": "dtypes.T == \"f16\" or dtypes.V == \"f16\"",
421
+ "hiddenSize": "normHidden",
422
+ "workgroupSize": "normMaxWorkgroup",
423
+ "split": "splitCount",
424
+ "epsilon": "attrs.epsilon"
425
+ },
426
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[normRows * splitCount]" }],
427
+ "passes": [
428
+ {
429
+ "id": "partials",
430
+ "name": "SimplifiedLayerNormalization.SplitKPartials",
431
+ "source": { "shader": "rms-normalization-splitk-partials.wgsl.jinja", "inputs": {} },
432
+ "bindings": "splitPartials",
433
+ "dispatch": { "workgroups": "normRows", "z": "splitCount" }
434
+ },
435
+ {
436
+ "id": "normalize",
437
+ "name": "SimplifiedLayerNormalization.SplitKNormalize",
438
+ "source": {
439
+ "shader": "rms-normalization-splitk-normalize.wgsl.jinja",
440
+ "inputs": {
441
+ "xShape": "shapes.X",
442
+ "scaleShape": "shapes.scale",
443
+ "xRank": "ranks.X",
444
+ "scaleRank": "ranks.scale",
445
+ "writeStats": true,
446
+ "rmsScaleAfterCast": false
447
+ }
448
+ },
449
+ "bindings": "splitNormalizeStats",
450
+ "dispatch": { "workgroups": "normRows", "z": "splitCount" }
451
+ }
452
+ ]
453
+ },
454
+ {
455
+ "id": "last_axis_row_vec4",
456
+ "priority": 110,
457
+ "when": ["lastAxisOk", "sameDtype", "noStats", "ranks.scale >= 1", "numel(shapes.scale) == dim(shapes.X, -1)", "dim(shapes.scale, -1) == dim(shapes.X, -1)", "dim(shapes.X, -1) % 4 == 0"],
458
+ "constants": {
459
+ "scalar": "dtypes.T",
460
+ "xElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
461
+ "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\""
462
+ },
463
+ "passes": [
464
+ {
465
+ "id": "main",
466
+ "name": "SimplifiedLayerNormalization.LastAxisRow",
467
+ "source": {
468
+ "shader": "norm-row-stats.wgsl.jinja",
469
+ "inputs": {
470
+ "mode": "\"rms\"",
471
+ "vec4": true,
472
+ "writeStats": false,
473
+ "rmsScaleAfterCast": false,
474
+ "scalar": "dtypes.T",
475
+ "usesF16": "dtypes.T == \"f16\"",
476
+ "hidden": "dim(shapes.X, -1)",
477
+ "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.X, -1) / 4)))",
478
+ "epsilon": "attrs.epsilon",
479
+ "hiddenVec": "dim(shapes.X, -1) / 4",
480
+ "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
481
+ "combineSubgroups": "hasSubgroupId"
482
+ }
483
+ },
484
+ "subgroupCollectivesWidth": "portable",
485
+ "bindings": "rows",
486
+ "dispatch": { "workgroups": "normRows" }
487
+ }
488
+ ]
489
+ },
490
+ {
491
+ "id": "last_axis_row",
492
+ "priority": 100,
493
+ "when": ["lastAxisOk", "sameDtype", "noStats", "ranks.scale >= 1", "numel(shapes.scale) == dim(shapes.X, -1)", "dim(shapes.scale, -1) == dim(shapes.X, -1)"],
494
+ "constants": { "scalar": "dtypes.T", "xElement": "dtypes.T", "ioElement": "dtypes.T" },
495
+ "passes": [
496
+ {
497
+ "id": "main",
498
+ "name": "SimplifiedLayerNormalization.LastAxisRow",
499
+ "source": {
500
+ "shader": "norm-row-stats.wgsl.jinja",
501
+ "inputs": {
502
+ "mode": "\"rms\"",
503
+ "vec4": false,
504
+ "writeStats": false,
505
+ "rmsScaleAfterCast": false,
506
+ "scalar": "dtypes.T",
507
+ "usesF16": "dtypes.T == \"f16\"",
508
+ "hidden": "dim(shapes.X, -1)",
509
+ "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.X, -1))))",
510
+ "epsilon": "attrs.epsilon",
511
+ "hiddenVec": 1,
512
+ "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
513
+ "combineSubgroups": "hasSubgroupId"
514
+ }
515
+ },
516
+ "subgroupCollectivesWidth": "portable",
517
+ "bindings": "rows",
518
+ "dispatch": { "workgroups": "normRows" }
519
+ }
520
+ ]
521
+ },
522
+ {
523
+ "id": "last_axis_row_vec4_stats",
524
+ "priority": 112,
525
+ "when": ["lastAxisOk", "sameDtype", "statsOk", "ranks.scale >= 1", "numel(shapes.scale) == dim(shapes.X, -1)", "dim(shapes.scale, -1) == dim(shapes.X, -1)", "dim(shapes.X, -1) % 4 == 0"],
526
+ "constants": {
527
+ "scalar": "dtypes.T",
528
+ "xElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
529
+ "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\""
530
+ },
531
+ "passes": [
532
+ {
533
+ "id": "main",
534
+ "name": "SimplifiedLayerNormalization.LastAxisRow",
535
+ "source": {
536
+ "shader": "norm-row-stats.wgsl.jinja",
537
+ "inputs": {
538
+ "mode": "\"rms\"",
539
+ "vec4": true,
540
+ "writeStats": true,
541
+ "rmsScaleAfterCast": false,
542
+ "scalar": "dtypes.T",
543
+ "usesF16": "dtypes.T == \"f16\"",
544
+ "hidden": "dim(shapes.X, -1)",
545
+ "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.X, -1) / 4)))",
546
+ "epsilon": "attrs.epsilon",
547
+ "hiddenVec": "dim(shapes.X, -1) / 4",
548
+ "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
549
+ "combineSubgroups": "hasSubgroupId"
550
+ }
551
+ },
552
+ "subgroupCollectivesWidth": "portable",
553
+ "bindings": "rowsStats",
554
+ "dispatch": { "workgroups": "normRows" }
555
+ }
556
+ ]
557
+ },
558
+ {
559
+ "id": "last_axis_row_stats",
560
+ "priority": 102,
561
+ "when": ["lastAxisOk", "sameDtype", "statsOk", "ranks.scale >= 1", "numel(shapes.scale) == dim(shapes.X, -1)", "dim(shapes.scale, -1) == dim(shapes.X, -1)"],
562
+ "constants": { "scalar": "dtypes.T", "xElement": "dtypes.T", "ioElement": "dtypes.T" },
563
+ "passes": [
564
+ {
565
+ "id": "main",
566
+ "name": "SimplifiedLayerNormalization.LastAxisRow",
567
+ "source": {
568
+ "shader": "norm-row-stats.wgsl.jinja",
569
+ "inputs": {
570
+ "mode": "\"rms\"",
571
+ "vec4": false,
572
+ "writeStats": true,
573
+ "rmsScaleAfterCast": false,
574
+ "scalar": "dtypes.T",
575
+ "usesF16": "dtypes.T == \"f16\"",
576
+ "hidden": "dim(shapes.X, -1)",
577
+ "wg": "min(normMaxWorkgroup, pow2ceil(max(1, dim(shapes.X, -1))))",
578
+ "epsilon": "attrs.epsilon",
579
+ "hiddenVec": 1,
580
+ "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
581
+ "combineSubgroups": "hasSubgroupId"
582
+ }
583
+ },
584
+ "subgroupCollectivesWidth": "portable",
585
+ "bindings": "rowsStats",
586
+ "dispatch": { "workgroups": "normRows" }
587
+ }
588
+ ]
589
+ }
590
+ ]
591
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.SimplifiedLayerNormalization",
3
+ "id": "_ai_onnx_simplifiedlayernormalization_webgpu_e7aa570",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "pvZ/eyxjfpTLTo48nnQhDaKG61UPA6L0EPXu4QnH7MY=",
11
+ "manifest.json": "hfKp3YN8J74f7KzVbFXsOBq5wNbZm2A8QMJ3ww5MAdw=",
12
+ "norm-row-stats.wgsl.jinja": "GopNOsand0HoXzNtQnzGdvJKmHbCv96No/7heLcS9VA=",
13
+ "rms-normalization-splitk-normalize.wgsl.jinja": "YklXSmW/XGI1M5KVzfayKp0EQ+3iAYHeOnTAMA4UW4c=",
14
+ "rms-normalization-splitk-partials.wgsl.jinja": "GbYu7Cj/LLO9y2Rvhw30RJAUAduI0xfN5YS3H/4Z1k8=",
15
+ "rms-normalization.wgsl.jinja": "wNOsrtTIc7DmBifozpAm2fZ3iWznPJ04EhFMkOq6GqA=",
16
+ "test.json": "ycI7oQ1MKS87yo6QXLI36xLz840eYS69Y/anRQHQkUY="
17
+ }
18
+ },
19
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
20
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.SimplifiedLayerNormalization" }
21
+ }
build/webgpu/norm-row-stats.wgsl.jinja ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if source.usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {% set combineSubgroups = source.combineSubgroups %}
5
+ {% set scalarIo = source.scalarIo if source.scalarIo is defined else false %}
6
+ {% set writeStats = source.writeStats if source.writeStats is defined else false %}
7
+ {% set rmsScaleVec = "vec4<f32>(scale[i])" %}
8
+ {% set rmsScaleScalar = "f32(scale[i])" %}
9
+ {% set reduceThreadParameters = ", sg_lane: u32, sg_id: u32, num_sg: u32"
10
+ if combineSubgroups else ", tid: u32" %}
11
+ {% set reduceThreadArguments = ", sg_lane, sg_id, num_sg"
12
+ if combineSubgroups else ", tid" %}
13
+ {% if combineSubgroups %}
14
+ enable subgroups;
15
+ {% endif %}
16
+ {{ env.wgsl.resourceDeclarations }}
17
+
18
+ // Workgroup-parallel single-pass row statistics + fused normalize/affine.
19
+ //
20
+ // One workgroup owns one contiguous normalization span ("row": a last-axis
21
+ // row, an instance plane, or a channel group). Threads stride the row once,
22
+ // accumulating (sum, sum_sq) simultaneously. Partials are reduced either with
23
+ // subgroupAdd plus a shared-memory combine or with a portable shared-memory
24
+ // tree, then every thread applies the fused normalize + affine write.
25
+ //
26
+ // RMS mode uses sum_sq / HIDDEN without computing or subtracting a mean.
27
+ const HIDDEN: u32 = {{ source.hidden }}u;
28
+ {% if source.vec4 %}
29
+ const HIDDEN_V: u32 = {{ source.hiddenVec }}u;
30
+ {% endif %}
31
+ const WG: u32 = {{ source.wg }}u;
32
+ const EPSILON: f32 = {{ source.epsilon }};
33
+
34
+
35
+
36
+ {% if combineSubgroups %}
37
+ var<workgroup> sg_partials: array<f32, WG>;
38
+
39
+ fn reduce_scalar(value: f32{{ reduceThreadParameters }}) -> f32 {
40
+ let s = subgroupAdd(value);
41
+ if (num_sg == 1u) {
42
+ return s;
43
+ }
44
+ if (sg_lane == 0u) {
45
+ sg_partials[sg_id] = s;
46
+ }
47
+ workgroupBarrier();
48
+ var total = 0.0;
49
+ for (var i = 0u; i < num_sg; i++) {
50
+ total += sg_partials[i];
51
+ }
52
+ return total;
53
+ }
54
+ {% else %}
55
+ // Each shared-memory tree reduction deliberately ends with a barrier. It keeps
56
+ // lanes that have read the result from starting a later reduction and
57
+ // overwriting scratch while slower lanes are still reading it.
58
+ var<workgroup> tr0: array<f32, WG>;
59
+ fn reduce_scalar(value: f32, tid: u32) -> f32 {
60
+ tr0[tid] = value;
61
+ workgroupBarrier();
62
+ var stride: u32 = WG / 2u;
63
+ loop {
64
+ if (stride == 0u) { break; }
65
+ if (tid < stride) { tr0[tid] = tr0[tid] + tr0[tid + stride]; }
66
+ stride = stride / 2u;
67
+ workgroupBarrier();
68
+ }
69
+ let reduced = tr0[0];
70
+ workgroupBarrier();
71
+ return reduced;
72
+ }
73
+ {% endif %}
74
+
75
+ @compute @workgroup_size(WG, 1, 1)
76
+ fn main(
77
+ @builtin(workgroup_id) wg_id: vec3<u32>,
78
+ @builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups %},
79
+ @builtin(subgroup_invocation_id) sg_lane: u32,
80
+ @builtin(subgroup_id) sg_id: u32,
81
+ @builtin(num_subgroups) num_sg: u32{% endif %}
82
+ ) {
83
+ let row = wg_id.x + wg_id.y * params.rowStride;
84
+ if (row >= params.rows) {
85
+ return;
86
+ }
87
+ let tid = lid.x;
88
+ {% if source.vec4 and not scalarIo %}
89
+ let base = row * HIDDEN_V;
90
+ {% else %}
91
+ let base = row * HIDDEN;
92
+ {% endif %}
93
+
94
+
95
+ var acc = 0.0;
96
+ {% if source.vec4 %}
97
+ for (var i = tid; i < HIDDEN_V; i = i + WG) {
98
+ let v = vec4<f32>(x[base + i]);
99
+ acc = acc + dot(v, v);
100
+ }
101
+ {% else %}
102
+ for (var i = tid; i < HIDDEN; i = i + WG) {
103
+ let v = f32(x[base + i]);
104
+ acc = acc + v * v;
105
+ }
106
+ {% endif %}
107
+
108
+ let total = reduce_scalar(acc{{ reduceThreadArguments }});
109
+
110
+ let inv = inverseSqrt(total / f32(HIDDEN) + EPSILON);
111
+ {% if writeStats %}
112
+ if (tid == 0u) {
113
+ inv_std_out[row] = inv;
114
+ }
115
+ {% endif %}
116
+
117
+ {% if source.vec4 %}
118
+ for (var i = tid; i < HIDDEN_V; i = i + WG) {
119
+ let idx = base + i;
120
+ let v = vec4<f32>(x[idx]);
121
+ y[idx] = {{ source.vecType }}(v * inv * {{ rmsScaleVec }});
122
+ }
123
+ {% else %}
124
+ for (var i = tid; i < HIDDEN; i = i + WG) {
125
+ let idx = base + i;
126
+ let v = f32(x[idx]);
127
+ y[idx] = {{ source.scalar }}(v * inv * {{ rmsScaleScalar }});
128
+ }
129
+ {% endif %}
130
+ }
build/webgpu/rms-normalization-splitk-normalize.wgsl.jinja ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Split-K normalize pass. Each workgroup (row = wg.x, split index = wg.z)
2
+ // folds the SPLIT per-row partial sums of squares into the RMS scale, then
3
+ // normalizes its HIDDEN/SPLIT slice. SPLIT is small (<=64), so the serial fold
4
+ // avoids a third combine pass. Scale offsets follow the suffix-axis broadcast
5
+ // contract.
6
+ {% if usesF16 %}
7
+ enable f16;
8
+ {% endif %}
9
+ {{ env.wgsl.resourceDeclarations }}
10
+
11
+ const HIDDEN: u32 = {{ hiddenSize }}u;
12
+ const EPSILON: f32 = {{ epsilon }};
13
+ const WG: u32 = {{ workgroupSize }}u;
14
+ const SPLIT: u32 = {{ split }}u;
15
+
16
+ {% if source.scaleRank > 0 %}
17
+ const X_RANK: u32 = {{ source.xRank }}u;
18
+ const SCALE_RANK: u32 = {{ source.scaleRank }}u;
19
+ const X_SHAPE: array<u32, {{ source.xRank }}> = array<u32, {{ source.xRank }}>({% for d in source.xShape %}{{ d }}u{% if not loop.last %}, {% endif %}{% endfor %});
20
+ const SCALE_SHAPE: array<u32, {{ source.scaleRank }}> = array<u32, {{ source.scaleRank }}>({% for d in source.scaleShape %}{{ d }}u{% if not loop.last %}, {% endif %}{% endfor %});
21
+
22
+ fn x_stride(axis: u32) -> u32 {
23
+ var stride = 1u;
24
+ for (var i = axis + 1u; i < X_RANK; i += 1u) {
25
+ stride *= X_SHAPE[i];
26
+ }
27
+ return stride;
28
+ }
29
+
30
+ fn scale_stride(axis: u32) -> u32 {
31
+ var stride = 1u;
32
+ for (var i = axis + 1u; i < SCALE_RANK; i += 1u) {
33
+ stride *= SCALE_SHAPE[i];
34
+ }
35
+ return stride;
36
+ }
37
+
38
+ {% endif %}
39
+ fn scale_offset({% if source.scaleRank > 0 %}out_index: u32{% endif %}) -> u32 {
40
+ {% if source.scaleRank == 0 %}
41
+ return 0u;
42
+ {% else %}
43
+ var rem = out_index;
44
+ var offset = 0u;
45
+ for (var axis = 0u; axis < X_RANK; axis += 1u) {
46
+ let stride = x_stride(axis);
47
+ let coord = rem / stride;
48
+ rem %= stride;
49
+ let scale_axis = i32(axis) - i32(X_RANK - SCALE_RANK);
50
+ if (scale_axis >= 0) {
51
+ let s_axis = u32(scale_axis);
52
+ if (SCALE_SHAPE[s_axis] != 1u) {
53
+ offset += coord * scale_stride(s_axis);
54
+ }
55
+ }
56
+ }
57
+ return offset;
58
+ {% endif %}
59
+ }
60
+
61
+
62
+ @compute @workgroup_size(WG, 1, 1)
63
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
64
+ let row = wg.x + wg.y * params.rowStride;
65
+ if (row >= params.rows) {
66
+ return;
67
+ }
68
+ let k = wg.z;
69
+ let tid = lid.x;
70
+
71
+ var total = 0.0;
72
+ for (var i = 0u; i < SPLIT; i = i + 1u) {
73
+ total = total + partials[row * SPLIT + i];
74
+ }
75
+ let inv = inverseSqrt(total / f32(HIDDEN) + EPSILON);
76
+ {% if source.writeStats %}
77
+ // Every split workgroup folds the same partials, so one designated
78
+ // workgroup writes the row statistic.
79
+ if (k == 0u && tid == 0u) {
80
+ inv_std_out[row] = inv;
81
+ }
82
+
83
+ {% endif %}
84
+ let chunk = (HIDDEN + SPLIT - 1u) / SPLIT;
85
+ let start = k * chunk;
86
+ var end = start + chunk;
87
+ if (end > HIDDEN) { end = HIDDEN; }
88
+ let base = row * HIDDEN;
89
+
90
+ var d = start + tid;
91
+ loop {
92
+ if (d >= end) { break; }
93
+ let index = base + d;
94
+ let value = f32(x[index]) * inv * f32(scale[scale_offset({% if source.scaleRank > 0 %}index{% endif %})]);
95
+ y[index] = {{ scalar }}(value);
96
+ d = d + WG;
97
+ }
98
+ }
build/webgpu/rms-normalization-splitk-partials.wgsl.jinja ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
50
+ (row = wg.x, split index = wg.z). Each workgroup accumulates a partial
51
+ sum-of-squares over its HIDDEN/SPLIT slice and writes one partial to scratch.
52
+ The normalize pass folds the SPLIT partials per row. Split-K reassociates the
53
+ f32 sum, so this route is not bit-identical to the unsplit reduction. */
54
+ {% if usesF16 %}
55
+ enable f16;
56
+ {% endif %}
57
+ {{ env.wgsl.resourceDeclarations }}
58
+
59
+ const HIDDEN: u32 = {{ hiddenSize }}u;
60
+ const WG: u32 = {{ workgroupSize }}u;
61
+ const SPLIT: u32 = {{ split }}u;
62
+
63
+ var<workgroup> red: array<f32, WG>;
64
+
65
+ @compute @workgroup_size(WG, 1, 1)
66
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
67
+ let row = wg.x + wg.y * params.rowStride;
68
+ if (row >= params.rows) {
69
+ return;
70
+ }
71
+ let k = wg.z;
72
+ let tid = lid.x;
73
+ let chunk = (HIDDEN + SPLIT - 1u) / SPLIT;
74
+ let start = k * chunk;
75
+ var end = start + chunk;
76
+ if (end > HIDDEN) { end = HIDDEN; }
77
+ let base = row * HIDDEN;
78
+
79
+ var acc = 0.0;
80
+ var d = start + tid;
81
+ loop {
82
+ if (d >= end) { break; }
83
+ let v = f32(x[base + d]);
84
+ acc = acc + v * v;
85
+ d = d + WG;
86
+ }
87
+ red[tid] = acc;
88
+ workgroupBarrier();
89
+ {{ wgsl_tree_fold(["red"], idx="tid", wg="WG", typed=true, form="head", breakInline=true, bodyInline=true) }}
90
+ if (tid == 0u) {
91
+ partials[row * SPLIT + k] = red[0];
92
+ }
93
+ }
build/webgpu/rms-normalization.wgsl.jinja ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ const HIDDEN: u32 = {{ hiddenSize }}u;
7
+ const EPSILON: f32 = {{ epsilon }};
8
+ const WG: u32 = {{ workgroupSize }}u;
9
+
10
+ var<workgroup> partial: array<f32, WG>;
11
+
12
+ {% if source.scaleRank > 0 %}
13
+ const X_RANK: u32 = {{ source.xRank }}u;
14
+ const SCALE_RANK: u32 = {{ source.scaleRank }}u;
15
+ const X_SHAPE: array<u32, {{ source.xRank }}> = array<u32, {{ source.xRank }}>({% for d in source.xShape %}{{ d }}u{% if not loop.last %}, {% endif %}{% endfor %});
16
+ const SCALE_SHAPE: array<u32, {{ source.scaleRank }}> = array<u32, {{ source.scaleRank }}>({% for d in source.scaleShape %}{{ d }}u{% if not loop.last %}, {% endif %}{% endfor %});
17
+
18
+ fn x_stride(axis: u32) -> u32 {
19
+ var stride = 1u;
20
+ for (var i = axis + 1u; i < X_RANK; i += 1u) {
21
+ stride *= X_SHAPE[i];
22
+ }
23
+ return stride;
24
+ }
25
+
26
+ fn scale_stride(axis: u32) -> u32 {
27
+ var stride = 1u;
28
+ for (var i = axis + 1u; i < SCALE_RANK; i += 1u) {
29
+ stride *= SCALE_SHAPE[i];
30
+ }
31
+ return stride;
32
+ }
33
+
34
+ {% endif %}
35
+ fn scale_offset({% if source.scaleRank > 0 %}out_index: u32{% endif %}) -> u32 {
36
+ {% if source.scaleRank == 0 %}
37
+ return 0u;
38
+ {% else %}
39
+ var rem = out_index;
40
+ var offset = 0u;
41
+ for (var axis = 0u; axis < X_RANK; axis += 1u) {
42
+ let stride = x_stride(axis);
43
+ let coord = rem / stride;
44
+ rem %= stride;
45
+ let scale_axis = i32(axis) - i32(X_RANK - SCALE_RANK);
46
+ if (scale_axis >= 0) {
47
+ let s_axis = u32(scale_axis);
48
+ if (SCALE_SHAPE[s_axis] != 1u) {
49
+ offset += coord * scale_stride(s_axis);
50
+ }
51
+ }
52
+ }
53
+ return offset;
54
+ {% endif %}
55
+ }
56
+
57
+
58
+ {% macro wgsl_tree_fold_stmt(a, op, idx, svar) %}
59
+ {% if op == "max" %}
60
+ {{ a }}[{{ idx }}] = max({{ a }}[{{ idx }}], {{ a }}[{{ idx }} + {{ svar }}]);
61
+ {%- else %}
62
+ {{ a }}[{{ idx }}] = {{ a }}[{{ idx }}] + {{ a }}[{{ idx }} + {{ svar }}];
63
+ {%- endif %}
64
+ {% endmacro %}
65
+ {% macro wgsl_tree_fold(arrays, op="add", idx="lid", wg="WORKGROUP_SIZE", svar="stride", typed=false, form="tail", breakInline=false, bodyInline=false, barrierFirst=false) %}
66
+ var {{ svar }}{{ ": u32 " if typed else " " }}= {{ wg }} / 2u;
67
+ loop {
68
+ {% if form == "head" %}
69
+ {% if breakInline %}
70
+ if ({{ svar }} == 0u) { break; }
71
+ {% else %}
72
+ if ({{ svar }} == 0u) {
73
+ break;
74
+ }
75
+ {% endif %}
76
+ {% endif %}
77
+ {% if bodyInline %}
78
+ if ({{ idx }} < {{ svar }}) { {{ wgsl_tree_fold_stmt(arrays[0], op, idx, svar) }} }
79
+ {% else %}
80
+ if ({{ idx }} < {{ svar }}) {
81
+ {% for a in arrays %}
82
+ {{ wgsl_tree_fold_stmt(a, op, idx, svar) }}
83
+ {% endfor %}
84
+ }
85
+ {% endif %}
86
+ {% if form == "head" %}
87
+ {% if barrierFirst %}
88
+ workgroupBarrier();
89
+ {{ svar }} = {{ svar }} / 2u;
90
+ {% else %}
91
+ {{ svar }} = {{ svar }} / 2u;
92
+ workgroupBarrier();
93
+ {% endif %}
94
+ {% else %}
95
+ workgroupBarrier();
96
+ if ({{ svar }} == 1u) {
97
+ break;
98
+ }
99
+ {{ svar }} = {{ svar }} / 2u;
100
+ {% endif %}
101
+ }
102
+ {%- endmacro %}
103
+
104
+ // Reusing partial after this reduction requires a barrier between the read of
105
+ // partial[0] and the next write, or the next round can race the prior readers.
106
+ {% set trailingBarrier = trailingBarrier is defined and trailingBarrier %}
107
+ fn reduce_sum(value: f32, tid: u32) -> f32 {
108
+ partial[tid] = value;
109
+ workgroupBarrier();
110
+ {{ wgsl_tree_fold(["partial"], idx="tid", wg="WG", form="head") }}
111
+ {% if trailingBarrier %}
112
+ let total = partial[0];
113
+ workgroupBarrier();
114
+ return total;
115
+ {% else %}
116
+ return partial[0];
117
+ {% endif %}
118
+ }
119
+
120
+
121
+ @compute @workgroup_size(WG, 1, 1)
122
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
123
+ let row = wg.x + wg.y * params.rowStride;
124
+ if (row >= params.rows) {
125
+ return;
126
+ }
127
+ let tid = lid.x;
128
+ let base = row * HIDDEN;
129
+
130
+ var local_sq = 0.0;
131
+ for (var d = tid; d < HIDDEN; d = d + WG) {
132
+ let value = f32(x[base + d]);
133
+ local_sq = local_sq + value * value;
134
+ }
135
+ let inv = inverseSqrt(reduce_sum(local_sq, tid) / f32(HIDDEN) + EPSILON);
136
+ {% if source.writeStats %}
137
+ if (tid == 0u) {
138
+ inv_std_out[row] = inv;
139
+ }
140
+
141
+ {% endif %}
142
+ for (var d = tid; d < HIDDEN; d = d + WG) {
143
+ let index = base + d;
144
+ let value = f32(x[index]) * inv * f32(scale[scale_offset({% if source.scaleRank > 0 %}index{% endif %})]);
145
+ y[base + d] = {{ scalar }}(value);
146
+ }
147
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.SimplifiedLayerNormalization",
3
+ "fixtureArrays": {
4
+ "f16_scalar_cast_x": [-1.1103515625, 2.982421875, 1.248046875, -1.8544921875],
5
+ "f16_scalar_cast_scale": [2.015625],
6
+ "f16_scalar_cast_y": [-1.1513671875, 3.091796875, 1.2939453125, -1.921875]
7
+ },
8
+ "cases": [
9
+ {
10
+ "name": "legacy_last_axis_vec4_no_stats_2x8",
11
+ "attrs": { "epsilon": 0.000001, "axis": -1 },
12
+ "inputs": {
13
+ "x": {
14
+ "dtype": "float32",
15
+ "shape": [2, 8],
16
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07 }
17
+ },
18
+ "scale": {
19
+ "dtype": "float32",
20
+ "shape": [8],
21
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.19, "scale": 0.5 }
22
+ }
23
+ },
24
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 8], "tolerance": 0.000002 } }
25
+ },
26
+ {
27
+ "name": "legacy_last_axis_scalar_no_stats_2x5",
28
+ "attrs": { "epsilon": 0.000001, "axis": -1 },
29
+ "inputs": {
30
+ "x": {
31
+ "dtype": "float32",
32
+ "shape": [2, 5],
33
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07 }
34
+ },
35
+ "scale": {
36
+ "dtype": "float32",
37
+ "shape": [5],
38
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.19, "scale": 0.5 }
39
+ }
40
+ },
41
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 5], "tolerance": 0.000002 } }
42
+ },
43
+ {
44
+ "name": "legacy_last_axis_vec4_stats_2x8",
45
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
46
+ "inputs": {
47
+ "x": {
48
+ "dtype": "float32",
49
+ "shape": [2, 8],
50
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21 }
51
+ },
52
+ "scale": {
53
+ "dtype": "float32",
54
+ "shape": [8],
55
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.11, "scale": 0.5 }
56
+ }
57
+ },
58
+ "outputs": {
59
+ "y": { "dtype": "float32", "shape": [2, 8], "tolerance": 0.000002 },
60
+ "invStdVar": { "dtype": "float32", "shape": [2, 1], "tolerance": 0.00001 }
61
+ }
62
+ },
63
+ {
64
+ "name": "legacy_last_axis_scalar_stats_2x33",
65
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
66
+ "inputs": {
67
+ "x": {
68
+ "dtype": "float32",
69
+ "shape": [2, 33],
70
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.27 }
71
+ },
72
+ "scale": {
73
+ "dtype": "float32",
74
+ "shape": [33],
75
+ "data": { "kind": "fillFloat32", "sinStep": 0.21, "cosStep": 0.13, "scale": 0.5 }
76
+ }
77
+ },
78
+ "outputs": {
79
+ "y": { "dtype": "float32", "shape": [2, 33], "tolerance": 0.000002 },
80
+ "invStdVar": { "dtype": "float32", "shape": [2, 1], "tolerance": 0.00001 }
81
+ }
82
+ },
83
+ {
84
+ "name": "legacy_scalar_scale_generic_stats_2x8",
85
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
86
+ "inputs": {
87
+ "x": {
88
+ "dtype": "float32",
89
+ "shape": [2, 8],
90
+ "data": { "kind": "fillFloat32", "sinStep": 0.31, "cosStep": 0.09 }
91
+ },
92
+ "scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.75] } }
93
+ },
94
+ "outputs": {
95
+ "y": { "dtype": "float32", "shape": [2, 8], "tolerance": 0.000002 },
96
+ "invStdVar": { "dtype": "float32", "shape": [2, 1], "tolerance": 0.00001 }
97
+ }
98
+ },
99
+ {
100
+ "name": "legacy_suffix_axis1_stats_2x3x4",
101
+ "attrs": { "epsilon": 0.00001, "axis": 1 },
102
+ "inputs": {
103
+ "x": {
104
+ "dtype": "float32",
105
+ "shape": [2, 3, 4],
106
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23 }
107
+ },
108
+ "scale": {
109
+ "dtype": "float32",
110
+ "shape": [3, 4],
111
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.29, "scale": 0.5 }
112
+ }
113
+ },
114
+ "outputs": {
115
+ "y": { "dtype": "float32", "shape": [2, 3, 4], "tolerance": 0.000002 },
116
+ "invStdVar": { "dtype": "float32", "shape": [2, 1, 1], "tolerance": 0.00001 }
117
+ }
118
+ },
119
+ {
120
+ "name": "legacy_mixed_f16_x_f32_scale_stats_2x4",
121
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
122
+ "inputs": {
123
+ "x": {
124
+ "dtype": "float16",
125
+ "shape": [2, 4],
126
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21 }
127
+ },
128
+ "scale": {
129
+ "dtype": "float32",
130
+ "shape": [4],
131
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.11, "scale": 0.5 }
132
+ }
133
+ },
134
+ "outputs": {
135
+ "y": { "dtype": "float32", "shape": [2, 4], "tolerance": 0.002 },
136
+ "invStdVar": { "dtype": "float32", "shape": [2, 1], "tolerance": 0.002 }
137
+ }
138
+ },
139
+ {
140
+ "name": "legacy_mixed_f32_x_f16_scale_2x4",
141
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
142
+ "inputs": {
143
+ "x": {
144
+ "dtype": "float32",
145
+ "shape": [2, 4],
146
+ "data": { "kind": "fillFloat32", "sinStep": 0.15, "cosStep": 0.25 }
147
+ },
148
+ "scale": {
149
+ "dtype": "float16",
150
+ "shape": [4],
151
+ "data": { "kind": "fillFloat32", "sinStep": 0.27, "cosStep": 0.09, "scale": 0.5 }
152
+ }
153
+ },
154
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 4], "tolerance": 0.003 } }
155
+ },
156
+ {
157
+ "name": "legacy_f16_vec4_stats_4x32",
158
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
159
+ "inputs": {
160
+ "x": {
161
+ "dtype": "float16",
162
+ "shape": [4, 32],
163
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.27 }
164
+ },
165
+ "scale": {
166
+ "dtype": "float16",
167
+ "shape": [32],
168
+ "data": { "kind": "fillFloat32", "sinStep": 0.21, "cosStep": 0.13, "scale": 0.5 }
169
+ }
170
+ },
171
+ "outputs": {
172
+ "y": { "dtype": "float16", "shape": [4, 32], "tolerance": 0.005 },
173
+ "invStdVar": { "dtype": "float32", "shape": [4, 1], "tolerance": 0.002 }
174
+ }
175
+ },
176
+ {
177
+ "name": "legacy_suffix_splitk_stats_2x64x512",
178
+ "attrs": { "epsilon": 0.00001, "axis": 1 },
179
+ "inputs": {
180
+ "x": {
181
+ "dtype": "float32",
182
+ "shape": [2, 64, 512],
183
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017 }
184
+ },
185
+ "scale": {
186
+ "dtype": "float32",
187
+ "shape": [64, 512],
188
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.007, "scale": 0.5 }
189
+ }
190
+ },
191
+ "outputs": {
192
+ "y": { "dtype": "float32", "shape": [2, 64, 512], "tolerance": 0.00001 },
193
+ "invStdVar": { "dtype": "float32", "shape": [2, 1, 1], "tolerance": 0.00001 }
194
+ }
195
+ },
196
+ {
197
+ "name": "legacy_suffix_splitk_no_stats_1x32x512",
198
+ "attrs": { "epsilon": 0.00001, "axis": 1 },
199
+ "inputs": {
200
+ "x": {
201
+ "dtype": "float32",
202
+ "shape": [1, 32, 512],
203
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.023 }
204
+ },
205
+ "scale": {
206
+ "dtype": "float32",
207
+ "shape": [32, 512],
208
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.019, "scale": 0.5 }
209
+ }
210
+ },
211
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 512], "tolerance": 0.00001 } }
212
+ },
213
+ {
214
+ "name": "legacy_suffix_splitk_scalar_scale_no_stats_1x32x512",
215
+ "attrs": { "epsilon": 0.00001, "axis": 1 },
216
+ "inputs": {
217
+ "x": {
218
+ "dtype": "float32",
219
+ "shape": [1, 32, 512],
220
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013 }
221
+ },
222
+ "scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.75] } }
223
+ },
224
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 512], "tolerance": 0.00001 } }
225
+ },
226
+ {
227
+ "name": "legacy_f16_scale_before_output_cast_vec4_exact",
228
+ "provenance": {
229
+ "source": "onnxruntime/core/providers/cpu/nn/layer_norm_impl.cc",
230
+ "test": "SimplifiedLayerNormalization legacy f16 cast boundary",
231
+ "notes": "The legacy op applies scale before its single output cast. The first value differs by one f16 ULP from standardized RMSNormalization, which casts the normalized stage before scale."
232
+ },
233
+ "requires": { "features": ["shader-f16"] },
234
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
235
+ "inputs": {
236
+ "x": {
237
+ "dtype": "float16",
238
+ "shape": [1, 4],
239
+ "data": { "kind": "values", "values": [-1.4765625, -1.861328125, 1.4697265625, 1.0703125] }
240
+ },
241
+ "scale": {
242
+ "dtype": "float16",
243
+ "shape": [4],
244
+ "data": { "kind": "values", "values": [0.316162109375, -2.791015625, -0.0877685546875, 2.0859375] }
245
+ }
246
+ },
247
+ "outputs": {
248
+ "y": {
249
+ "dtype": "float16",
250
+ "shape": [1, 4],
251
+ "tolerance": 0,
252
+ "relTolerance": 0,
253
+ "data": { "kind": "values", "values": [-0.31201171875, 3.47265625, -0.08624267578125, 1.4921875] }
254
+ }
255
+ }
256
+ },
257
+ {
258
+ "name": "legacy_f16_scale_before_output_cast_generic_exact",
259
+ "provenance": {
260
+ "source": "onnxruntime/core/providers/cpu/nn/layer_norm_impl.cc",
261
+ "test": "SimplifiedLayerNormalization scalar-scale cast boundary",
262
+ "notes": "A scalar scale forces the generic shared template and pins the same legacy cast ordering independently of the optimized row path."
263
+ },
264
+ "requires": { "features": ["shader-f16"] },
265
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
266
+ "inputs": {
267
+ "x": {
268
+ "dtype": "float16",
269
+ "shape": [1, 4],
270
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_scalar_cast_x" } }
271
+ },
272
+ "scale": {
273
+ "dtype": "float16",
274
+ "shape": [],
275
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_scalar_cast_scale" } }
276
+ }
277
+ },
278
+ "outputs": {
279
+ "y": {
280
+ "dtype": "float16",
281
+ "shape": [1, 4],
282
+ "tolerance": 0,
283
+ "relTolerance": 0,
284
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_scalar_cast_y" } }
285
+ }
286
+ }
287
+ },
288
+ {
289
+ "name": "legacy_f16_scale_before_output_cast_splitk_exact",
290
+ "provenance": {
291
+ "source": "onnxruntime/core/providers/cpu/nn/layer_norm_impl.cc",
292
+ "test": "SimplifiedLayerNormalization split-K f16 cast boundary",
293
+ "notes": "Forces the split-K shared kernel on the exact scalar-scale boundary and pins the legacy scale-before-output-cast ordering."
294
+ },
295
+ "requires": { "features": ["shader-f16"] },
296
+ "tunables": { "SPLIT_MIN_HIDDEN": 1, "SPLIT_TARGET_ELEMENTS": 1 },
297
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
298
+ "inputs": {
299
+ "x": {
300
+ "dtype": "float16",
301
+ "shape": [1, 4],
302
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_scalar_cast_x" } }
303
+ },
304
+ "scale": {
305
+ "dtype": "float16",
306
+ "shape": [],
307
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_scalar_cast_scale" } }
308
+ }
309
+ },
310
+ "outputs": {
311
+ "y": {
312
+ "dtype": "float16",
313
+ "shape": [1, 4],
314
+ "tolerance": 0,
315
+ "relTolerance": 0,
316
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_scalar_cast_y" } }
317
+ }
318
+ }
319
+ },
320
+ {
321
+ "name": "legacy_rank1_default_attrs_8",
322
+ "attrs": {},
323
+ "inputs": {
324
+ "x": { "dtype": "float32", "shape": [8], "data": { "kind": "fillFloat32", "sinStep": 0.37, "cosStep": 0.03 } },
325
+ "scale": {
326
+ "dtype": "float32",
327
+ "shape": [8],
328
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.41, "scale": 0.5 }
329
+ }
330
+ },
331
+ "outputs": { "y": { "dtype": "float32", "shape": [8], "tolerance": 0.000002 } }
332
+ },
333
+ {
334
+ "name": "legacy_explicit_stash_type_float32",
335
+ "attrs": { "epsilon": 0.00001, "axis": -1, "stash_type": 1 },
336
+ "inputs": {
337
+ "x": {
338
+ "dtype": "float32",
339
+ "shape": [2, 8],
340
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.13 }
341
+ },
342
+ "scale": {
343
+ "dtype": "float32",
344
+ "shape": [8],
345
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.37, "scale": 0.5 }
346
+ }
347
+ },
348
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 8], "tolerance": 0.000002 } }
349
+ },
350
+ {
351
+ "name": "legacy_keep_dims_compatibility_attribute",
352
+ "provenance": {
353
+ "source": "onnxruntime/test/contrib_ops/layer_norm_test.cc",
354
+ "test": "SimplifiedLayerNormalization keep_dims=1",
355
+ "notes": "ONNX Runtime's legacy schema permits unchecked attributes and its own compatibility test emits keep_dims=1. The kernel ignores the attribute and always retains reduced dimensions for optional statistics."
356
+ },
357
+ "attrs": { "epsilon": 0.00001, "axis": 1, "keep_dims": 1 },
358
+ "inputs": {
359
+ "x": {
360
+ "dtype": "float32",
361
+ "shape": [2, 3, 4],
362
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
363
+ },
364
+ "scale": {
365
+ "dtype": "float32",
366
+ "shape": [3, 4],
367
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.09, "scale": 0.5 }
368
+ }
369
+ },
370
+ "outputs": {
371
+ "y": { "dtype": "float32", "shape": [2, 3, 4], "tolerance": 0.000002 },
372
+ "invStdVar": { "dtype": "float32", "shape": [2, 1, 1], "tolerance": 0.00001 }
373
+ }
374
+ },
375
+ {
376
+ "name": "legacy_zero_outer_rows_stats",
377
+ "attrs": { "epsilon": 0.00001, "axis": -1 },
378
+ "inputs": {
379
+ "x": { "dtype": "float32", "shape": [0, 4], "data": { "kind": "values", "values": [] } },
380
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "constant", "value": 1.0 } }
381
+ },
382
+ "outputs": {
383
+ "y": { "dtype": "float32", "shape": [0, 4], "tolerance": 0.000002 },
384
+ "invStdVar": { "dtype": "float32", "shape": [0, 1], "tolerance": 0.00001 }
385
+ }
386
+ }
387
+ ]
388
+ }