Xenova HF Staff commited on
Commit
0af9165
·
verified ·
1 Parent(s): 5205f7a

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,81 @@
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.InstanceNormalization
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 6
12
+
13
+ ## Description
14
+
15
+ Applies instance normalization to the input: `y = scale * (x - mean) / sqrt(variance + epsilon) + B`, where `mean` and `variance` are computed per instance per channel over the spatial dimensions. Equivalent to batch normalization with a batch size of one per channel.
16
+
17
+ See the [ONNX `InstanceNormalization` spec](https://onnx.ai/onnx/operators/onnx__InstanceNormalization.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `input` | `input` | `T` | — | — | Input tensor of shape `(N x C x D1 x ... x Dn)`; at least 3-D. | required |
24
+ | `scale` | `scale` | `T` | `1` | — | 1-D scale tensor of size C, one scale factor per channel. | required |
25
+ | `B` | `b` | `T` | `1` | — | 1-D bias tensor of size C, one bias value per channel. | required |
26
+
27
+ ## Outputs
28
+
29
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- | --- |
31
+ | `output` | `output` | `T` | same as `input` | same as `input` | Normalized output tensor; same shape as the input. | required |
32
+
33
+ ## Attributes
34
+
35
+ Default values (overridable per request):
36
+
37
+ | Attribute | Default | Description |
38
+ | --- | --- | --- |
39
+ | `epsilon` | `0.00001` | Small constant added to the variance before taking the square root to avoid division by zero. |
40
+
41
+ ## Type constraints
42
+
43
+ | Variable | Allowed dtypes |
44
+ | --- | --- |
45
+ | `T` | `float32`, `float16` |
46
+
47
+ ## Device requirements
48
+
49
+ Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
50
+
51
+ ## Files
52
+
53
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
54
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
+ - [`test.json`](build/webgpu/test.json) — correctness cases
56
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
57
+ - [`instance-normalization-apply.wgsl.jinja`](build/webgpu/instance-normalization-apply.wgsl.jinja)
58
+ - [`instance-normalization-batched-planes-vec4.wgsl.jinja`](build/webgpu/instance-normalization-batched-planes-vec4.wgsl.jinja)
59
+ - [`instance-normalization-splitk-combine.wgsl.jinja`](build/webgpu/instance-normalization-splitk-combine.wgsl.jinja)
60
+ - [`instance-normalization-splitk-partials.wgsl.jinja`](build/webgpu/instance-normalization-splitk-partials.wgsl.jinja)
61
+ - [`norm-row-stats.wgsl.jinja`](build/webgpu/norm-row-stats.wgsl.jinja)
62
+
63
+ ## Use with `@huggingface/kernels`
64
+
65
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
66
+ It then allocates the result tensors automatically.
67
+
68
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
69
+
70
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
71
+
72
+ ```js
73
+ import { getKernel } from "@huggingface/kernels";
74
+
75
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.InstanceNormalization", { version: 1 });
76
+ const { output } = await kernel({
77
+ input: { data: inputData, shape: [1, 2, 1, 3] },
78
+ scale: { data: scaleData, shape: [2] },
79
+ b: { data: bData, shape: [2] },
80
+ });
81
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.InstanceNormalization",
3
+ "cases": [
4
+ {
5
+ "name": "nchw_4x64x128x128",
6
+ "preset": "smoke",
7
+ "vars": { "dtype": "float32", "batch": 4, "channels": 64, "spatial": 16384 },
8
+ "inputs": {
9
+ "input": { "dtype": "float32", "shape": [4, 64, 128, 128], "dist": "normal", "seed": 750, "scale": 0.5 },
10
+ "scale": { "dtype": "float32", "shape": [64], "dist": "uniform", "seed": 751, "scale": 0.25, "offset": 1 },
11
+ "b": { "dtype": "float32", "shape": [64], "dist": "normal", "seed": 752, "scale": 0.1 }
12
+ },
13
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 64, 128, 128] } },
14
+ "bench": {
15
+ "primary": true,
16
+ "metrics": [
17
+ {
18
+ "type": "bandwidth",
19
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
20
+ }
21
+ ]
22
+ }
23
+ },
24
+ {
25
+ "name": "nchw_f16_4x64x128x128",
26
+ "preset": "smoke",
27
+ "vars": { "dtype": "float16", "batch": 4, "channels": 64, "spatial": 16384 },
28
+ "inputs": {
29
+ "input": { "dtype": "float16", "shape": [4, 64, 128, 128], "dist": "normal", "seed": 750, "scale": 0.5 },
30
+ "scale": { "dtype": "float16", "shape": [64], "dist": "uniform", "seed": 751, "scale": 0.25, "offset": 1 },
31
+ "b": { "dtype": "float16", "shape": [64], "dist": "normal", "seed": 752, "scale": 0.1 }
32
+ },
33
+ "outputs": { "output": { "dtype": "float16", "shape": [4, 64, 128, 128] } },
34
+ "bench": {
35
+ "primary": true,
36
+ "metrics": [
37
+ {
38
+ "type": "bandwidth",
39
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
40
+ }
41
+ ]
42
+ }
43
+ },
44
+ {
45
+ "name": "nchw_1x64x56x56",
46
+ "preset": "smoke",
47
+ "inputs": {
48
+ "input": { "dtype": "float32", "shape": [1, 64, 56, 56] },
49
+ "scale": { "dtype": "float32", "shape": [64] },
50
+ "b": { "dtype": "float32", "shape": [64] }
51
+ },
52
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 64, 56, 56] } }
53
+ },
54
+ {
55
+ "name": "alignment_cliff_rank3_2x64x4095_unaligned",
56
+ "preset": "smoke",
57
+ "vars": { "dtype": "float32", "batch": 2, "channels": 64, "spatial": 4095 },
58
+ "inputs": {
59
+ "input": { "dtype": "float32", "shape": [2, 64, 4095], "dist": "normal", "seed": 810, "scale": 0.5 },
60
+ "scale": { "dtype": "float32", "shape": [64], "dist": "uniform", "seed": 811, "scale": 0.25, "offset": 1 },
61
+ "b": { "dtype": "float32", "shape": [64], "dist": "normal", "seed": 812, "scale": 0.1 }
62
+ },
63
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 64, 4095] } },
64
+ "bench": {
65
+ "metrics": [
66
+ {
67
+ "type": "bandwidth",
68
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
69
+ }
70
+ ]
71
+ }
72
+ },
73
+ {
74
+ "name": "alignment_healthy_rank3_2x64x4096_aligned",
75
+ "preset": "smoke",
76
+ "vars": { "dtype": "float32", "batch": 2, "channels": 64, "spatial": 4096 },
77
+ "inputs": {
78
+ "input": { "dtype": "float32", "shape": [2, 64, 4096], "dist": "normal", "seed": 810, "scale": 0.5 },
79
+ "scale": { "dtype": "float32", "shape": [64], "dist": "uniform", "seed": 811, "scale": 0.25, "offset": 1 },
80
+ "b": { "dtype": "float32", "shape": [64], "dist": "normal", "seed": 812, "scale": 0.1 }
81
+ },
82
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 64, 4096] } },
83
+ "bench": {
84
+ "metrics": [
85
+ {
86
+ "type": "bandwidth",
87
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
88
+ }
89
+ ]
90
+ }
91
+ },
92
+ {
93
+ "name": "dispatch_cliff_rows70000_yfold",
94
+ "preset": "smoke",
95
+ "vars": { "dtype": "float32", "batch": 1, "channels": 70000, "spatial": 64 },
96
+ "inputs": {
97
+ "input": { "dtype": "float32", "shape": [1, 70000, 64], "dist": "normal", "seed": 820, "scale": 0.5 },
98
+ "scale": { "dtype": "float32", "shape": [70000], "dist": "uniform", "seed": 821, "scale": 0.25, "offset": 1 },
99
+ "b": { "dtype": "float32", "shape": [70000], "dist": "normal", "seed": 822, "scale": 0.1 }
100
+ },
101
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 70000, 64] } },
102
+ "bench": {
103
+ "metrics": [
104
+ {
105
+ "type": "bandwidth",
106
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
107
+ }
108
+ ]
109
+ }
110
+ },
111
+ {
112
+ "name": "dispatch_healthy_rows60000_under_cap",
113
+ "preset": "smoke",
114
+ "vars": { "dtype": "float32", "batch": 1, "channels": 60000, "spatial": 64 },
115
+ "inputs": {
116
+ "input": { "dtype": "float32", "shape": [1, 60000, 64], "dist": "normal", "seed": 820, "scale": 0.5 },
117
+ "scale": { "dtype": "float32", "shape": [60000], "dist": "uniform", "seed": 821, "scale": 0.25, "offset": 1 },
118
+ "b": { "dtype": "float32", "shape": [60000], "dist": "normal", "seed": 822, "scale": 0.1 }
119
+ },
120
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 60000, 64] } },
121
+ "bench": {
122
+ "metrics": [
123
+ {
124
+ "type": "bandwidth",
125
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
126
+ }
127
+ ]
128
+ }
129
+ },
130
+ {
131
+ "name": "single_huge_instance_1x1x4194304_onewg",
132
+ "preset": "smoke",
133
+ "vars": { "dtype": "float32", "batch": 1, "channels": 1, "spatial": 4194304 },
134
+ "inputs": {
135
+ "input": { "dtype": "float32", "shape": [1, 1, 4194304], "dist": "normal", "seed": 830, "scale": 0.5 },
136
+ "scale": { "dtype": "float32", "shape": [1], "dist": "uniform", "seed": 831, "scale": 0.25, "offset": 1 },
137
+ "b": { "dtype": "float32", "shape": [1], "dist": "normal", "seed": 832, "scale": 0.1 }
138
+ },
139
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 1, 4194304] } },
140
+ "bench": {
141
+ "metrics": [
142
+ {
143
+ "type": "bandwidth",
144
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
145
+ }
146
+ ]
147
+ }
148
+ },
149
+ {
150
+ "name": "onewg_per_plane_tiny_spatial_launchbound_1x65280x3x1",
151
+ "preset": "stress",
152
+ "vars": { "dtype": "float32", "batch": 1, "channels": 65280, "spatial": 3 },
153
+ "attrs": { "epsilon": 0.00001 },
154
+ "inputs": {
155
+ "input": { "dtype": "float32", "shape": [1, 65280, 3, 1], "dist": "normal", "seed": 901, "scale": 0.5 },
156
+ "scale": { "dtype": "float32", "shape": [65280], "dist": "uniform", "seed": 902, "scale": 0.25, "offset": 1 },
157
+ "b": { "dtype": "float32", "shape": [65280], "dist": "normal", "seed": 903, "scale": 0.1 }
158
+ },
159
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 65280, 3, 1] } },
160
+ "bench": {
161
+ "metrics": [
162
+ {
163
+ "type": "bandwidth",
164
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
165
+ }
166
+ ]
167
+ }
168
+ },
169
+ {
170
+ "name": "onewg_per_plane_tiny_spatial_launchbound_rank3_1x49152x6",
171
+ "preset": "stress",
172
+ "vars": { "dtype": "float32", "batch": 1, "channels": 49152, "spatial": 6 },
173
+ "attrs": { "epsilon": 0.00001 },
174
+ "inputs": {
175
+ "input": { "dtype": "float32", "shape": [1, 49152, 6], "dist": "normal", "seed": 911, "scale": 0.5 },
176
+ "scale": { "dtype": "float32", "shape": [49152], "dist": "uniform", "seed": 912, "scale": 0.25, "offset": 1 },
177
+ "b": { "dtype": "float32", "shape": [49152], "dist": "normal", "seed": 913, "scale": 0.1 }
178
+ },
179
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 49152, 6] } },
180
+ "bench": {
181
+ "metrics": [
182
+ {
183
+ "type": "bandwidth",
184
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
185
+ }
186
+ ]
187
+ }
188
+ },
189
+ {
190
+ "name": "rank7_spatial_flatten_gpu_gap",
191
+ "vars": { "dtype": "float32", "batch": 1, "channels": 2, "spatial": 32 },
192
+ "attrs": { "epsilon": 0.00001 },
193
+ "inputs": {
194
+ "input": { "dtype": "float32", "shape": [1, 2, 2, 2, 2, 2, 2], "dist": "normal", "seed": 931, "scale": 0.5 },
195
+ "scale": { "dtype": "float32", "shape": [2], "dist": "uniform", "seed": 932, "scale": 0.25, "offset": 1 },
196
+ "b": { "dtype": "float32", "shape": [2], "dist": "normal", "seed": 933, "scale": 0.1 }
197
+ },
198
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2, 2, 2, 2] } },
199
+ "bench": {
200
+ "metrics": [
201
+ {
202
+ "type": "bandwidth",
203
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
204
+ }
205
+ ]
206
+ }
207
+ },
208
+ {
209
+ "name": "splitk-priority-cliff-c256-256x256",
210
+ "preset": "stress",
211
+ "provenance": {
212
+ "source": "authored for variant coverage",
213
+ "notes": "Realistic 16.8M-element feature map that pins the selector boundary between plane_subgroup_vec4 and plane_splitk."
214
+ },
215
+ "vars": { "dtype": "float32", "batch": 1, "channels": 256, "spatial": 65536 },
216
+ "attrs": { "epsilon": 0.00001 },
217
+ "inputs": {
218
+ "input": { "dtype": "float32", "shape": [1, 256, 256, 256], "dist": "normal", "seed": 991, "scale": 0.5 },
219
+ "scale": { "dtype": "float32", "shape": [256], "dist": "uniform", "seed": 992, "scale": 0.25, "offset": 1 },
220
+ "b": { "dtype": "float32", "shape": [256], "dist": "normal", "seed": 993, "scale": 0.1 }
221
+ },
222
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 256, 256, 256], "dist": "empty" } },
223
+ "bench": {
224
+ "metrics": [
225
+ {
226
+ "type": "bandwidth",
227
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
228
+ }
229
+ ]
230
+ }
231
+ },
232
+ {
233
+ "name": "splitk-priority-cliff-c32-512x512",
234
+ "preset": "stress",
235
+ "provenance": {
236
+ "source": "authored for variant coverage",
237
+ "notes": "Realistic 8.4M-element high-resolution feature map that pins the selector boundary between plane_subgroup_vec4 and plane_splitk."
238
+ },
239
+ "vars": { "dtype": "float32", "batch": 1, "channels": 32, "spatial": 262144 },
240
+ "attrs": { "epsilon": 0.00001 },
241
+ "inputs": {
242
+ "input": { "dtype": "float32", "shape": [1, 32, 512, 512], "dist": "normal", "seed": 994, "scale": 0.5 },
243
+ "scale": { "dtype": "float32", "shape": [32], "dist": "uniform", "seed": 995, "scale": 0.25, "offset": 1 },
244
+ "b": { "dtype": "float32", "shape": [32], "dist": "normal", "seed": 996, "scale": 0.1 }
245
+ },
246
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 32, 512, 512], "dist": "empty" } },
247
+ "bench": {
248
+ "metrics": [
249
+ {
250
+ "type": "bandwidth",
251
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
252
+ }
253
+ ]
254
+ }
255
+ }
256
+ ]
257
+ }
build/webgpu/instance-normalization-apply.wgsl.jinja ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Apply per-plane statistics: y = (x - mean) * inverseStddev * scale + bias.
2
+ // The vectorized route packs four adjacent spatial values per invocation; each
3
+ // packed load and store remains within one plane.
4
+ {% set vectorized = vectorized if vectorized is defined else false %}
5
+ {% if usesF16 %}
6
+ enable f16;
7
+ {% endif %}
8
+ {% set LOAD_OPEN = "vec4<f32>(" if usesF16 else "" %}
9
+ {% set LOAD_CLOSE = ")" if usesF16 else "" %}
10
+ {% set STORE_OPEN = "vec4<f16>(" if usesF16 else "" %}
11
+ {% set STORE_CLOSE = ")" if usesF16 else "" %}
12
+ {% set CHAN_OPEN = "f32(" if usesF16 else "" %}
13
+ {% set CHAN_CLOSE = ")" if usesF16 else "" %}
14
+ {{ env.wgsl.resourceDeclarations }}
15
+
16
+ const WG: u32 = {{ applyWorkgroupSize }}u;
17
+
18
+ @compute @workgroup_size(WG, 1, 1)
19
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
20
+ {% if not vectorized %}
21
+ // 2D-folded flat index: gid.y carries the high bits after dispatch folding.
22
+ {% endif %}
23
+ let index = gid.x + gid.y * nwg.x * WG;
24
+ if (index >= params.count) {
25
+ return;
26
+ }
27
+ {% if vectorized %}
28
+ // The vectorized path requires each plane to contain a multiple of four
29
+ // values, so a packed load/store never crosses an instance boundary.
30
+ let plane = (index * 4u) / params.spatial;
31
+ {% else %}
32
+ let plane = index / params.spatial;
33
+ {% endif %}
34
+ let channel = plane % params.channels;
35
+ let mean = stats[plane * 2u];
36
+ let inv_std = stats[plane * 2u + 1u];
37
+ {% if vectorized %}
38
+ let value = {{ LOAD_OPEN }}input[index]{{ LOAD_CLOSE }};
39
+ output[index] = {{ STORE_OPEN }}(value - vec4<f32>(mean)) * vec4<f32>(inv_std * {{ CHAN_OPEN }}scale[channel]{{ CHAN_CLOSE }}) + vec4<f32>({{ CHAN_OPEN }}bias[channel]{{ CHAN_CLOSE }}){{ STORE_CLOSE }};
40
+ {% else %}
41
+ output[index] = {{ scalar }}((f32(input[index]) - mean) * inv_std * f32(scale[channel]) + f32(bias[channel]));
42
+ {% endif %}
43
+ }
build/webgpu/instance-normalization-batched-planes-vec4.wgsl.jinja ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // A power-of-two lane cohort reduces one plane while several cohorts share a
7
+ // workgroup.
8
+ const HIDDEN: u32 = {{ hidden }}u;
9
+ const HIDDEN_V4: u32 = {{ hiddenVec }}u;
10
+ const CHANNELS: u32 = {{ channels }}u;
11
+ const EPSILON: f32 = {{ epsilon }};
12
+ const WG: u32 = {{ workgroupSize }}u;
13
+ const LANES: u32 = {{ lanesPerPlane }}u;
14
+ const PLANES_PER_WG: u32 = {{ planesPerWorkgroup }}u;
15
+
16
+ var<workgroup> shifted_moments: array<vec2<f32>, WG>;
17
+ var<workgroup> plane_shift: array<f32, PLANES_PER_WG>;
18
+
19
+ @compute @workgroup_size(WG, 1, 1)
20
+ fn main(
21
+ @builtin(workgroup_id) workgroup: vec3<u32>,
22
+ @builtin(num_workgroups) workgroup_count: vec3<u32>,
23
+ @builtin(local_invocation_id) local: vec3<u32>
24
+ ) {
25
+ let tid = local.x;
26
+ let plane_in_workgroup = tid / LANES;
27
+ let lane = tid % LANES;
28
+ let group = workgroup.x + workgroup.y * workgroup_count.x;
29
+ let row = group * PLANES_PER_WG + plane_in_workgroup;
30
+ let is_active = row < params.rows;
31
+ let base = row * HIDDEN_V4;
32
+
33
+ if (is_active && lane == 0u) {
34
+ plane_shift[plane_in_workgroup] = f32(x[base].x);
35
+ }
36
+ workgroupBarrier();
37
+ let shift = plane_shift[plane_in_workgroup];
38
+
39
+ var moments = vec2<f32>(0.0);
40
+ if (is_active) {
41
+ for (var i = lane; i < HIDDEN_V4; i = i + LANES) {
42
+ let value = vec4<f32>(x[base + i]);
43
+ let delta = value - vec4<f32>(shift);
44
+ moments.x = moments.x + delta.x + delta.y + delta.z + delta.w;
45
+ moments.y = moments.y + dot(delta, delta);
46
+ }
47
+ }
48
+ shifted_moments[tid] = moments;
49
+ workgroupBarrier();
50
+
51
+ var stride = LANES / 2u;
52
+ loop {
53
+ if (stride == 0u) { break; }
54
+ if (lane < stride) {
55
+ shifted_moments[tid] = shifted_moments[tid] + shifted_moments[tid + stride];
56
+ }
57
+ stride = stride / 2u;
58
+ workgroupBarrier();
59
+ }
60
+
61
+ if (is_active) {
62
+ let total = shifted_moments[plane_in_workgroup * LANES];
63
+ let mean_delta = total.x / f32(HIDDEN);
64
+ let variance = max(total.y / f32(HIDDEN) - mean_delta * mean_delta, 0.0);
65
+ let mean = shift + mean_delta;
66
+ let inv_std = inverseSqrt(variance + EPSILON);
67
+ let channel = row % CHANNELS;
68
+ let affine_scale = inv_std * f32(scale[channel]);
69
+ let affine_bias = f32(bias[channel]);
70
+ for (var i = lane; i < HIDDEN_V4; i = i + LANES) {
71
+ let value = vec4<f32>(x[base + i]);
72
+ y[base + i] = {{ vectorScalar }}((value - vec4<f32>(mean)) * vec4<f32>(affine_scale) + vec4<f32>(affine_bias));
73
+ }
74
+ }
75
+ }
build/webgpu/instance-normalization-splitk-combine.wgsl.jinja ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Fold SPLIT per-plane (sum, sum-of-squares) partials into mean and inverse
2
+ // standard deviation. One thread handles each plane. Variance uses
3
+ // E[x^2] - E[x]^2; max(value, 0) guards against negative rounding residue.
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ const SPLIT: u32 = {{ split }}u;
7
+ const COMBINE_WG: u32 = {{ combineWorkgroupSize }}u;
8
+
9
+ @compute @workgroup_size(COMBINE_WG, 1, 1)
10
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
11
+ let plane = gid.x + gid.y * nwg.x * COMBINE_WG;
12
+ if (plane >= params.planes) {
13
+ return;
14
+ }
15
+ var total = 0.0;
16
+ var total_sq = 0.0;
17
+ let b = plane * SPLIT;
18
+ for (var k = 0u; k < SPLIT; k = k + 1u) {
19
+ total = total + partials[(b + k) * 2u];
20
+ total_sq = total_sq + partials[(b + k) * 2u + 1u];
21
+ }
22
+ let n = f32(params.spatial);
23
+ let mean = total / n;
24
+ let variance = max(total_sq / n - mean * mean, 0.0);
25
+ stats[plane * 2u] = mean;
26
+ stats[plane * 2u + 1u] = inverseSqrt(variance + params.epsilon);
27
+ }
build/webgpu/instance-normalization-splitk-partials.wgsl.jinja ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 sums for tensors with few planes and a large spatial extent.
48
+ A workgroup-per-plane kernel exposes too little parallelism, so this pass
49
+ splits each plane across SPLIT workgroups. Each accumulates a raw sum and
50
+ sum-of-squares over its slice. The combine pass produces mean and inverse
51
+ standard deviation, and the apply pass normalizes. */
52
+ {% set vectorized = vectorized if vectorized is defined else false %}
53
+ {% set useSubgroups = useSubgroups if useSubgroups is defined else false %}
54
+ {% if usesF16 %}
55
+ enable f16;
56
+ {% endif %}
57
+ {% if useSubgroups %}
58
+ enable subgroups;
59
+ {% endif %}
60
+ {% set LOAD_OPEN = "vec4<f32>(" if usesF16 else "" %}
61
+ {% set LOAD_CLOSE = ")" if usesF16 else "" %}
62
+ {{ env.wgsl.resourceDeclarations }}
63
+
64
+ const WG: u32 = {{ workgroupSize }}u;
65
+ const SPLIT: u32 = {{ split }}u;
66
+
67
+ {% if useSubgroups %}
68
+ // One slot per possible subgroup avoids assuming any mapping from local
69
+ // invocation IDs to subgroup membership.
70
+ var<workgroup> subgroup_partials: array<vec2<f32>, WG>;
71
+ {% else %}
72
+ var<workgroup> red_sum: array<f32, WG>;
73
+ var<workgroup> red_sq: array<f32, WG>;
74
+ {% endif %}
75
+
76
+ @compute @workgroup_size(WG, 1, 1)
77
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>,
78
+ @builtin(num_workgroups) nwg: vec3<u32>{% if useSubgroups %},
79
+ @builtin(subgroup_invocation_id) subgroup_lane: u32,
80
+ @builtin(subgroup_id) subgroup_id: u32,
81
+ @builtin(num_subgroups) num_subgroups: u32{% endif %}) {
82
+ let plane = wg.x + wg.y * nwg.x;
83
+ if (plane >= params.planes) {
84
+ return;
85
+ }
86
+ let k = wg.z;
87
+ let tid = lid.x;
88
+ {% if vectorized %}
89
+ let spatial = params.spatial / 4u;
90
+ {% else %}
91
+ let spatial = params.spatial;
92
+ {% endif %}
93
+ let chunk = (spatial + SPLIT - 1u) / SPLIT;
94
+ let start = k * chunk;
95
+ var end = start + chunk;
96
+ if (end > spatial) { end = spatial; }
97
+ let base = plane * spatial;
98
+
99
+ var s = 0.0;
100
+ var sq = 0.0;
101
+ var i = start + tid;
102
+ loop {
103
+ if (i >= end) { break; }
104
+ {% if vectorized %}
105
+ let v = {{ LOAD_OPEN }}input[base + i]{{ LOAD_CLOSE }};
106
+ s = s + v.x + v.y + v.z + v.w;
107
+ sq = sq + dot(v, v);
108
+ {% else %}
109
+ let v = f32(input[base + i]);
110
+ s = s + v;
111
+ sq = sq + v * v;
112
+ {% endif %}
113
+ i = i + WG;
114
+ }
115
+ {% if useSubgroups %}
116
+ let subgroup_total = vec2<f32>(subgroupAdd(s), subgroupAdd(sq));
117
+ if (subgroup_lane == 0u) {
118
+ subgroup_partials[subgroup_id] = subgroup_total;
119
+ }
120
+ workgroupBarrier();
121
+ if (tid == 0u) {
122
+ var total = vec2<f32>(0.0);
123
+ for (var subgroup = 0u; subgroup < num_subgroups; subgroup = subgroup + 1u) {
124
+ total = total + subgroup_partials[subgroup];
125
+ }
126
+ let idx = (plane * SPLIT + k) * 2u;
127
+ partials[idx] = total.x;
128
+ partials[idx + 1u] = total.y;
129
+ }
130
+ {% else %}
131
+ red_sum[tid] = s;
132
+ red_sq[tid] = sq;
133
+ workgroupBarrier();
134
+ {{ wgsl_tree_fold(["red_sum", "red_sq"], idx="tid", wg="WG", typed=true, form="head", breakInline=true) }}
135
+ if (tid == 0u) {
136
+ let idx = (plane * SPLIT + k) * 2u;
137
+ partials[idx] = red_sum[0];
138
+ partials[idx + 1u] = red_sq[0];
139
+ }
140
+ {% endif %}
141
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,490 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "InstanceNormalization",
4
+ "sinceVersion": 6,
5
+ "description": "Applies instance normalization to the input: `y = scale * (x - mean) / sqrt(variance + epsilon) + B`, where `mean` and `variance` are computed per instance per channel over the spatial dimensions. Equivalent to batch normalization with a batch size of one per channel.",
6
+ "inputs": [
7
+ { "role": "input", "dtype": "T", "description": "Input tensor of shape `(N x C x D1 x ... x Dn)`; at least 3-D." },
8
+ {
9
+ "role": "scale",
10
+ "dtype": "T",
11
+ "rank": 1,
12
+ "description": "1-D scale tensor of size C, one scale factor per channel."
13
+ },
14
+ { "role": "B", "dtype": "T", "rank": 1, "description": "1-D bias tensor of size C, one bias value per channel." }
15
+ ],
16
+ "outputs": [
17
+ {
18
+ "role": "output",
19
+ "dtype": "T",
20
+ "rank": "ranks.input",
21
+ "description": "Normalized output tensor; same shape as the input.",
22
+ "shape": "shapes.input"
23
+ }
24
+ ],
25
+ "attributes": { "epsilon": 0.00001 },
26
+ "attributeDescriptions": {
27
+ "epsilon": "Small constant added to the variance before taking the square root to avoid division by zero."
28
+ },
29
+ "typeConstraints": { "T": ["float32", "float16"] },
30
+ "args": {
31
+ "input": { "kind": "tensor", "semantic": "input", "role": "input" },
32
+ "scale": { "kind": "tensor", "semantic": "scale", "role": "input" },
33
+ "b": { "kind": "tensor", "semantic": "B", "role": "input" },
34
+ "output": { "kind": "tensor", "semantic": "output", "role": "output" }
35
+ },
36
+ "tunables": {
37
+ "WORKGROUP_SIZE": 256,
38
+ "MAX_STATS_SPLITS": 256,
39
+ "STATS_VALUES_PER_SPLIT": 2048,
40
+ "SPLIT_STATS_MIN_SPATIAL": 65536,
41
+ "SPLIT_STATS_MAX_PLANES": 256,
42
+ "COMBINE_WORKGROUP_SIZE": 64,
43
+ "BATCHED_MIN_PLANES_PER_WORKGROUP": 8
44
+ },
45
+ "derive": {
46
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
47
+ "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
48
+ "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
49
+ "instanceContractOk": "f16Ok(dtypes.T) and ranks.input >= 3 and ranks.output == ranks.input and sameShape(shapes.output, shapes.input) and ranks.scale == 1 and ranks.B == 1 and dim(shapes.scale, 0) == dim(shapes.input, 1) and dim(shapes.B, 0) == dim(shapes.input, 1)",
50
+ "instancePlanes": "dim(shapes.input, 0) * dim(shapes.input, 1)",
51
+ "instanceSpatial": "inner(shapes.input, 1)",
52
+ "normDeviceWorkgroupCap": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
53
+ "normWorkgroupCap": "max(1, pow2ceil(normDeviceWorkgroupCap + 1) / 2)",
54
+ "normSubgroupMin": "device.adapterInfo.subgroupMinSize if has(device.adapterInfo, \"subgroupMinSize\") else 1",
55
+ "normSubgroupMax": "device.adapterInfo.subgroupMaxSize if has(device.adapterInfo, \"subgroupMaxSize\") else 32",
56
+ "hasSubgroupId": "device.features.has(\"subgroups\") and device.wgslLanguageFeatures.has(\"subgroup_id\")",
57
+ "instanceScalarWorkgroup": "min(normWorkgroupCap, pow2ceil(instanceSpatial))",
58
+ "instanceVec4Workgroup": "min(normWorkgroupCap, pow2ceil(instanceSpatial / 4))",
59
+ "instanceVec4SubgroupEfficient": "not hasSubgroupId or instanceVec4Workgroup >= normSubgroupMin",
60
+ "instanceBatchedVec4Lanes": "instanceVec4Workgroup",
61
+ "instanceBatchedVec4PlanesPerWorkgroup": "max(1, floor(normWorkgroupCap / instanceBatchedVec4Lanes))",
62
+ "instanceBatchedVec4Workgroups": "ceilDiv(instancePlanes, instanceBatchedVec4PlanesPerWorkgroup)",
63
+ "instanceBatchedVec4StorageBytes": "normWorkgroupCap * 2 * 4 + instanceBatchedVec4PlanesPerWorkgroup * 4",
64
+ "instanceRowWorkgroupBytes": "normWorkgroupCap * 2 * 4",
65
+ "instanceStatsBytes": "instancePlanes * 2 * 4",
66
+ "instanceStatsFits": "instanceStatsBytes <= device.limits.maxStorageBufferBindingSize and instanceStatsBytes <= device.limits.maxBufferSize",
67
+ "instanceRowCovered": "instanceContractOk and instanceRowWorkgroupBytes <= device.limits.maxComputeWorkgroupStorageSize",
68
+ "instanceSplitCount": "min(tunables.MAX_STATS_SPLITS, device.limits.maxComputeWorkgroupsPerDimension, pow2ceil(ceilDiv(instanceSpatial, tunables.STATS_VALUES_PER_SPLIT)))",
69
+ "instancePartialBytes": "instancePlanes * instanceSplitCount * 2 * 4",
70
+ "splitStatsCovered": "instanceRowCovered and instanceStatsFits and instancePlanes <= tunables.SPLIT_STATS_MAX_PLANES and instancePlanes <= device.limits.maxComputeWorkgroupsPerDimension and instanceSpatial >= tunables.SPLIT_STATS_MIN_SPATIAL and instancePartialBytes <= device.limits.maxStorageBufferBindingSize and instancePartialBytes <= device.limits.maxBufferSize",
71
+ "splitStatsPreferred": "splitStatsCovered and instancePlanes < normSubgroupMax"
72
+ },
73
+ "bindingSets": {
74
+ "planeIo": [
75
+ {
76
+ "name": "x",
77
+ "arg": "input",
78
+ "semantic": "input",
79
+ "buffer": { "type": "read-only-storage" },
80
+ "elementType": "$ioElement"
81
+ },
82
+ {
83
+ "name": "scale",
84
+ "arg": "scale",
85
+ "semantic": "scale",
86
+ "buffer": { "type": "read-only-storage" },
87
+ "elementType": "$T"
88
+ },
89
+ { "name": "bias", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
90
+ {
91
+ "name": "y",
92
+ "arg": "output",
93
+ "semantic": "output",
94
+ "buffer": { "type": "storage" },
95
+ "elementType": "$ioElement"
96
+ }
97
+ ],
98
+ "plane": [
99
+ {
100
+ "name": "x",
101
+ "arg": "input",
102
+ "semantic": "input",
103
+ "buffer": { "type": "read-only-storage" },
104
+ "elementType": "$ioElement"
105
+ },
106
+ {
107
+ "name": "scale",
108
+ "arg": "scale",
109
+ "semantic": "scale",
110
+ "buffer": { "type": "read-only-storage" },
111
+ "elementType": "$T"
112
+ },
113
+ { "name": "bias", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
114
+ {
115
+ "name": "y",
116
+ "arg": "output",
117
+ "semantic": "output",
118
+ "buffer": { "type": "storage" },
119
+ "elementType": "$ioElement"
120
+ },
121
+ {
122
+ "name": "params",
123
+ "semantic": "kernel.params",
124
+ "buffer": { "type": "uniform" },
125
+ "struct": {
126
+ "name": "Params",
127
+ "fields": [
128
+ { "name": "rows", "type": "u32", "value": "dim(shapes.input, 0) * dim(shapes.input, 1)" },
129
+ {
130
+ "name": "rowStride",
131
+ "type": "u32",
132
+ "value": "max(1, min(dim(shapes.input, 0) * dim(shapes.input, 1), device.limits.maxComputeWorkgroupsPerDimension))"
133
+ }
134
+ ]
135
+ }
136
+ }
137
+ ],
138
+ "planeBatched": [
139
+ {
140
+ "name": "x",
141
+ "arg": "input",
142
+ "semantic": "input",
143
+ "buffer": { "type": "read-only-storage" },
144
+ "elementType": "$ioElement"
145
+ },
146
+ {
147
+ "name": "scale",
148
+ "arg": "scale",
149
+ "semantic": "scale",
150
+ "buffer": { "type": "read-only-storage" },
151
+ "elementType": "$T"
152
+ },
153
+ { "name": "bias", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
154
+ {
155
+ "name": "y",
156
+ "arg": "output",
157
+ "semantic": "output",
158
+ "buffer": { "type": "storage" },
159
+ "elementType": "$ioElement"
160
+ },
161
+ {
162
+ "name": "params",
163
+ "semantic": "kernel.params",
164
+ "buffer": { "type": "uniform" },
165
+ "struct": {
166
+ "name": "Params",
167
+ "fields": [{ "name": "rows", "type": "u32", "value": "dim(shapes.input, 0) * dim(shapes.input, 1)" }]
168
+ }
169
+ }
170
+ ],
171
+ "applyScalar": [
172
+ {
173
+ "name": "input",
174
+ "arg": "input",
175
+ "semantic": "input",
176
+ "buffer": { "type": "read-only-storage" },
177
+ "elementType": "$T"
178
+ },
179
+ { "name": "stats", "semantic": "stats", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
180
+ {
181
+ "name": "scale",
182
+ "arg": "scale",
183
+ "semantic": "scale",
184
+ "buffer": { "type": "read-only-storage" },
185
+ "elementType": "$T"
186
+ },
187
+ { "name": "bias", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
188
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$T" },
189
+ {
190
+ "name": "params",
191
+ "semantic": "kernel.params",
192
+ "buffer": { "type": "uniform" },
193
+ "struct": {
194
+ "name": "Params",
195
+ "fields": [
196
+ { "name": "count", "type": "u32", "value": "numel(shapes.output)" },
197
+ { "name": "channels", "type": "u32", "value": "dim(shapes.input, 1)" },
198
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" }
199
+ ]
200
+ }
201
+ }
202
+ ],
203
+ "applyVec4": [
204
+ {
205
+ "name": "input",
206
+ "arg": "input",
207
+ "semantic": "input",
208
+ "buffer": { "type": "read-only-storage" },
209
+ "elementType": "$vectorScalar"
210
+ },
211
+ { "name": "stats", "semantic": "stats", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
212
+ {
213
+ "name": "scale",
214
+ "arg": "scale",
215
+ "semantic": "scale",
216
+ "buffer": { "type": "read-only-storage" },
217
+ "elementType": "$T"
218
+ },
219
+ { "name": "bias", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
220
+ {
221
+ "name": "output",
222
+ "arg": "output",
223
+ "semantic": "output",
224
+ "buffer": { "type": "storage" },
225
+ "elementType": "$vectorScalar"
226
+ },
227
+ {
228
+ "name": "params",
229
+ "semantic": "kernel.params",
230
+ "buffer": { "type": "uniform" },
231
+ "struct": {
232
+ "name": "Params",
233
+ "fields": [
234
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / 4" },
235
+ { "name": "channels", "type": "u32", "value": "dim(shapes.input, 1)" },
236
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" }
237
+ ]
238
+ }
239
+ }
240
+ ],
241
+ "splitPartials": [
242
+ {
243
+ "name": "input",
244
+ "arg": "input",
245
+ "semantic": "input",
246
+ "buffer": { "type": "read-only-storage" },
247
+ "elementType": "$splitInputElement"
248
+ },
249
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "f32" },
250
+ {
251
+ "name": "params",
252
+ "semantic": "kernel.params",
253
+ "buffer": { "type": "uniform" },
254
+ "struct": {
255
+ "name": "Params",
256
+ "fields": [
257
+ { "name": "planes", "type": "u32", "value": "instancePlanes" },
258
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" }
259
+ ]
260
+ }
261
+ }
262
+ ],
263
+ "splitCombine": [
264
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
265
+ { "name": "stats", "semantic": "stats", "buffer": { "type": "storage" }, "elementType": "f32" },
266
+ {
267
+ "name": "params",
268
+ "semantic": "kernel.params",
269
+ "buffer": { "type": "uniform" },
270
+ "struct": {
271
+ "name": "Params",
272
+ "fields": [
273
+ { "name": "planes", "type": "u32", "value": "instancePlanes" },
274
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" },
275
+ { "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
276
+ ]
277
+ }
278
+ }
279
+ ]
280
+ },
281
+ "variants": [
282
+ {
283
+ "id": "plane_batched_vec4",
284
+ "priority": 115,
285
+ "when": ["instanceRowCovered", "instanceSpatial % 4 == 0", "instanceSpatial >= 4", "instancePlanes >= normWorkgroupCap", "instanceBatchedVec4PlanesPerWorkgroup >= tunables.BATCHED_MIN_PLANES_PER_WORKGROUP", "instanceBatchedVec4StorageBytes <= device.limits.maxComputeWorkgroupStorageSize"],
286
+ "demoteWhen": ["reportedNonWave32Adapter and instancePlanes <= device.limits.maxComputeWorkgroupsPerDimension"],
287
+ "constants": {
288
+ "usesF16": "dtypes.T == \"f16\"",
289
+ "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
290
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
291
+ "hidden": "instanceSpatial",
292
+ "hiddenVec": "instanceSpatial / 4",
293
+ "channels": "dim(shapes.input, 1) if dim(shapes.input, 1) > 0 else 1",
294
+ "epsilon": "attrs.epsilon",
295
+ "workgroupSize": "normWorkgroupCap",
296
+ "lanesPerPlane": "instanceBatchedVec4Lanes",
297
+ "planesPerWorkgroup": "instanceBatchedVec4PlanesPerWorkgroup"
298
+ },
299
+ "passes": [
300
+ {
301
+ "id": "main",
302
+ "name": "InstanceNormalization.PlaneBatchedVec4",
303
+ "shader": "instance-normalization-batched-planes-vec4.wgsl.jinja",
304
+ "bindings": "planeBatched",
305
+ "dispatch": { "workgroups": "instanceBatchedVec4Workgroups" }
306
+ }
307
+ ]
308
+ },
309
+ {
310
+ "id": "plane_subgroup_vec4",
311
+ "priority": 110,
312
+ "requires": { "features": [] },
313
+ "when": ["instanceRowCovered", "inner(shapes.input, 1) % 4 == 0", "instanceVec4SubgroupEfficient"],
314
+ "constants": { "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
315
+ "passes": [
316
+ {
317
+ "id": "main",
318
+ "name": "InstanceNormalization.plane_subgroup_vec4",
319
+ "source": {
320
+ "shader": "norm-row-stats.wgsl.jinja",
321
+ "inputs": {
322
+ "mode": "\"instance\"",
323
+ "vec4": true,
324
+ "scalar": "dtypes.T",
325
+ "usesF16": "dtypes.T == \"f16\"",
326
+ "hidden": "instanceSpatial",
327
+ "wg": "instanceVec4Workgroup",
328
+ "epsilon": "attrs.epsilon",
329
+ "channels": "dim(shapes.input, 1) if dim(shapes.input, 1) > 0 else 1",
330
+ "hiddenVec": "instanceSpatial / 4",
331
+ "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
332
+ "combineSubgroups": "hasSubgroupId"
333
+ }
334
+ },
335
+ "subgroupCollectivesWidth": "portable",
336
+ "bindings": "plane",
337
+ "dispatch": { "workgroups": "instancePlanes" }
338
+ }
339
+ ]
340
+ },
341
+ {
342
+ "id": "plane_subgroup_vec4_scalar_io",
343
+ "priority": 111,
344
+ "requires": { "features": ["subgroups"] },
345
+ "when": ["dtypes.T == \"f32\"", "wave32Adapter", "device.wgslLanguageFeatures.has(\"subgroup_id\")", "instanceRowCovered", "instanceSpatial % 4 == 0", "instanceVec4SubgroupEfficient"],
346
+ "constants": { "ioElement": "dtypes.T" },
347
+ "passes": [
348
+ {
349
+ "id": "main",
350
+ "name": "InstanceNormalization.plane_subgroup_vec4_scalar_io",
351
+ "source": {
352
+ "shader": "norm-row-stats.wgsl.jinja",
353
+ "inputs": {
354
+ "mode": "\"instance\"",
355
+ "vec4": true,
356
+ "scalarIo": true,
357
+ "scalar": "dtypes.T",
358
+ "usesF16": false,
359
+ "hidden": "instanceSpatial",
360
+ "wg": "instanceVec4Workgroup",
361
+ "epsilon": "attrs.epsilon",
362
+ "channels": "dim(shapes.input, 1) if dim(shapes.input, 1) > 0 else 1",
363
+ "hiddenVec": "instanceSpatial / 4",
364
+ "vecType": "\"vec4<f32>\"",
365
+ "combineSubgroups": true
366
+ }
367
+ },
368
+ "subgroupCollectivesWidth": "portable",
369
+ "bindings": "plane",
370
+ "dispatch": { "workgroups": "instancePlanes" }
371
+ }
372
+ ]
373
+ },
374
+ {
375
+ "id": "plane_subgroup",
376
+ "priority": 100,
377
+ "requires": { "features": [] },
378
+ "when": ["instanceRowCovered"],
379
+ "constants": { "ioElement": "dtypes.T" },
380
+ "passes": [
381
+ {
382
+ "id": "main",
383
+ "name": "InstanceNormalization.plane_subgroup",
384
+ "source": {
385
+ "shader": "norm-row-stats.wgsl.jinja",
386
+ "inputs": {
387
+ "mode": "\"instance\"",
388
+ "vec4": false,
389
+ "scalar": "dtypes.T",
390
+ "usesF16": "dtypes.T == \"f16\"",
391
+ "hidden": "instanceSpatial",
392
+ "wg": "instanceScalarWorkgroup",
393
+ "epsilon": "attrs.epsilon",
394
+ "channels": "dim(shapes.input, 1) if dim(shapes.input, 1) > 0 else 1",
395
+ "combineSubgroups": "hasSubgroupId"
396
+ }
397
+ },
398
+ "subgroupCollectivesWidth": "portable",
399
+ "bindings": "plane",
400
+ "dispatch": { "workgroups": "instancePlanes" }
401
+ }
402
+ ]
403
+ },
404
+ {
405
+ "id": "plane_splitk_vec4",
406
+ "priority": 121,
407
+ "when": ["splitStatsPreferred", "instanceSpatial % 4 == 0"],
408
+ "constants": {
409
+ "vectorized": true,
410
+ "usesF16": "dtypes.T == \"f16\"",
411
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
412
+ "useSubgroups": "hasSubgroupId",
413
+ "splitInputElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
414
+ "workgroupSize": "normWorkgroupCap",
415
+ "split": "instanceSplitCount",
416
+ "combineWorkgroupSize": "min(tunables.COMBINE_WORKGROUP_SIZE, normWorkgroupCap)",
417
+ "applyWorkgroupSize": "normWorkgroupCap"
418
+ },
419
+ "intermediates": [
420
+ { "id": "partials", "dtype": "float32", "shape": "[instancePlanes * instanceSplitCount, 2]" },
421
+ { "id": "stats", "dtype": "float32", "shape": "[instancePlanes, 2]" }
422
+ ],
423
+ "passes": [
424
+ {
425
+ "id": "partials",
426
+ "name": "InstanceNormalization.SplitKPartialsVec4",
427
+ "shader": "instance-normalization-splitk-partials.wgsl.jinja",
428
+ "subgroupCollectivesWidth": "portable",
429
+ "bindings": "splitPartials",
430
+ "dispatch": { "workgroups": "instancePlanes", "z": "instanceSplitCount" }
431
+ },
432
+ {
433
+ "id": "combine",
434
+ "name": "InstanceNormalization.SplitKCombine",
435
+ "shader": "instance-normalization-splitk-combine.wgsl.jinja",
436
+ "bindings": "splitCombine",
437
+ "dispatch": { "threads": "instancePlanes", "workgroupSize": "constants.combineWorkgroupSize" }
438
+ },
439
+ {
440
+ "id": "apply",
441
+ "name": "InstanceNormalization.ApplyVec4",
442
+ "shader": "instance-normalization-apply.wgsl.jinja",
443
+ "bindings": "applyVec4",
444
+ "dispatch": { "threads": "numel(shapes.output) / 4", "workgroupSize": "constants.applyWorkgroupSize" }
445
+ }
446
+ ]
447
+ },
448
+ {
449
+ "id": "plane_splitk",
450
+ "priority": 120,
451
+ "when": ["splitStatsPreferred"],
452
+ "constants": {
453
+ "scalar": "dtypes.T",
454
+ "usesF16": "dtypes.T == \"f16\"",
455
+ "splitInputElement": "dtypes.T",
456
+ "workgroupSize": "normWorkgroupCap",
457
+ "split": "instanceSplitCount",
458
+ "combineWorkgroupSize": "min(tunables.COMBINE_WORKGROUP_SIZE, normWorkgroupCap)",
459
+ "applyWorkgroupSize": "normWorkgroupCap"
460
+ },
461
+ "intermediates": [
462
+ { "id": "partials", "dtype": "float32", "shape": "[instancePlanes * instanceSplitCount, 2]" },
463
+ { "id": "stats", "dtype": "float32", "shape": "[instancePlanes, 2]" }
464
+ ],
465
+ "passes": [
466
+ {
467
+ "id": "partials",
468
+ "name": "InstanceNormalization.SplitKPartials",
469
+ "shader": "instance-normalization-splitk-partials.wgsl.jinja",
470
+ "bindings": "splitPartials",
471
+ "dispatch": { "workgroups": "instancePlanes", "z": "instanceSplitCount" }
472
+ },
473
+ {
474
+ "id": "combine",
475
+ "name": "InstanceNormalization.SplitKCombine",
476
+ "shader": "instance-normalization-splitk-combine.wgsl.jinja",
477
+ "bindings": "splitCombine",
478
+ "dispatch": { "threads": "instancePlanes", "workgroupSize": "constants.combineWorkgroupSize" }
479
+ },
480
+ {
481
+ "id": "apply",
482
+ "name": "InstanceNormalization.Apply",
483
+ "shader": "instance-normalization-apply.wgsl.jinja",
484
+ "bindings": "applyScalar",
485
+ "dispatch": { "threads": "numel(shapes.output)", "workgroupSize": "constants.applyWorkgroupSize" }
486
+ }
487
+ ]
488
+ }
489
+ ]
490
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.InstanceNormalization",
3
+ "id": "_ai_onnx_instancenormalization_webgpu_1aea375",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "GgyPTi2dmoXyw7KRjHv05H93qU5QjSYSvl7Sq6uxIQ8=",
11
+ "instance-normalization-apply.wgsl.jinja": "xg8Pb+LQ5uMHQJNjPgxQAC9te2uiz1hfhLiKSxqD6WQ=",
12
+ "instance-normalization-batched-planes-vec4.wgsl.jinja": "fj7ftnvxAyb54O/TFqt5sezNAX0zVCyQNZfWZ34EiLE=",
13
+ "instance-normalization-splitk-combine.wgsl.jinja": "faaEHRq8I1cCglDAVTTpxsfaUtjDKjQOgDF6cobZWhY=",
14
+ "instance-normalization-splitk-partials.wgsl.jinja": "EPM/k6ud99fto6j+fbpwVBzJbg65sv3aiO4Cg6X9lLw=",
15
+ "manifest.json": "bM48AIoFxoG9TXkEwvxB57O6GrOiJMrH/MTEU39Fplk=",
16
+ "norm-row-stats.wgsl.jinja": "ZK1Wy+fDkHUODnGtjaF3FoacpqZidO/r2VxqrHtpBCg=",
17
+ "test.json": "TTDhrrElGsd6piLKBRdf7VIHBRzb1DwvWSgc5xB3iyk="
18
+ }
19
+ },
20
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
21
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.InstanceNormalization" }
22
+ }
build/webgpu/norm-row-stats.wgsl.jinja ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 reduceThreadParameters = ", sg_lane: u32, sg_id: u32, num_sg: u32"
7
+ if combineSubgroups else ", tid: u32" %}
8
+ {% set reduceThreadArguments = ", sg_lane, sg_id, num_sg"
9
+ if combineSubgroups else ", tid" %}
10
+ {% if combineSubgroups %}
11
+ enable subgroups;
12
+ {% endif %}
13
+ {{ env.wgsl.resourceDeclarations }}
14
+
15
+ // Workgroup-parallel single-pass row statistics + fused normalize/affine.
16
+ //
17
+ // One workgroup owns one contiguous normalization span ("row": a last-axis
18
+ // row, an instance plane, or a channel group). Threads stride the row once,
19
+ // accumulating (sum, sum_sq) simultaneously. Partials are reduced either with
20
+ // subgroupAdd plus a shared-memory combine or with a portable shared-memory
21
+ // tree, then every thread applies the fused normalize + affine write.
22
+ //
23
+ // Shifted moments avoid cancellation from a large common offset; clamp the
24
+ // variance to zero before adding EPSILON and taking inverseSqrt.
25
+ const HIDDEN: u32 = {{ source.hidden }}u;
26
+ {% if source.vec4 %}
27
+ const HIDDEN_V: u32 = {{ source.hiddenVec }}u;
28
+ {% endif %}
29
+ const WG: u32 = {{ source.wg }}u;
30
+ const EPSILON: f32 = {{ source.epsilon }};
31
+ const CHANNELS: u32 = {{ source.channels }}u;
32
+
33
+
34
+ {% if source.vec4 and scalarIo %}
35
+ fn load_vec4(index: u32) -> vec4<f32> {
36
+ return vec4<f32>(x[index], x[index + 1u], x[index + 2u], x[index + 3u]);
37
+ }
38
+ {% endif %}
39
+
40
+ {% if combineSubgroups %}
41
+ var<workgroup> sg_partials: array<vec2<f32>, WG>;
42
+
43
+ fn reduce_pair(value: vec2<f32>{{ reduceThreadParameters }}) -> vec2<f32> {
44
+ let s = vec2<f32>(subgroupAdd(value.x), subgroupAdd(value.y));
45
+ if (num_sg == 1u) {
46
+ return s;
47
+ }
48
+ if (sg_lane == 0u) {
49
+ sg_partials[sg_id] = s;
50
+ }
51
+ workgroupBarrier();
52
+ var total = vec2<f32>(0.0);
53
+ for (var i = 0u; i < num_sg; i++) {
54
+ total += sg_partials[i];
55
+ }
56
+ return total;
57
+ }
58
+ {% else %}
59
+ // Each shared-memory tree reduction deliberately ends with a barrier. It keeps
60
+ // lanes that have read the result from starting a later reduction and
61
+ // overwriting scratch while slower lanes are still reading it.
62
+ var<workgroup> tr0: array<f32, WG>;
63
+ var<workgroup> tr1: array<f32, WG>;
64
+ fn reduce_pair(value: vec2<f32>, tid: u32) -> vec2<f32> {
65
+ tr0[tid] = value.x;
66
+ tr1[tid] = value.y;
67
+ workgroupBarrier();
68
+ var stride: u32 = WG / 2u;
69
+ loop {
70
+ if (stride == 0u) { break; }
71
+ if (tid < stride) {
72
+ tr0[tid] = tr0[tid] + tr0[tid + stride];
73
+ tr1[tid] = tr1[tid] + tr1[tid + stride];
74
+ }
75
+ stride = stride / 2u;
76
+ workgroupBarrier();
77
+ }
78
+ let reduced = vec2<f32>(tr0[0], tr1[0]);
79
+ workgroupBarrier();
80
+ return reduced;
81
+ }
82
+ {% endif %}
83
+
84
+ @compute @workgroup_size(WG, 1, 1)
85
+ fn main(
86
+ @builtin(workgroup_id) wg_id: vec3<u32>,
87
+ @builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups %},
88
+ @builtin(subgroup_invocation_id) sg_lane: u32,
89
+ @builtin(subgroup_id) sg_id: u32,
90
+ @builtin(num_subgroups) num_sg: u32{% endif %}
91
+ ) {
92
+ let row = wg_id.x + wg_id.y * params.rowStride;
93
+ if (row >= params.rows) {
94
+ return;
95
+ }
96
+ let tid = lid.x;
97
+ {% if source.vec4 and not scalarIo %}
98
+ let base = row * HIDDEN_V;
99
+ {% else %}
100
+ let base = row * HIDDEN;
101
+ {% endif %}
102
+
103
+ {% if source.vec4 %}
104
+ {% if scalarIo %}
105
+ let shift = f32(x[base]);
106
+ {% else %}
107
+ let shift = f32(x[base].x);
108
+ {% endif %}
109
+ {% else %}
110
+ let shift = f32(x[base]);
111
+ {% endif %}
112
+
113
+ var acc = vec2<f32>(0.0, 0.0);
114
+ {% if source.vec4 %}
115
+ for (var i = tid; i < HIDDEN_V; i = i + WG) {
116
+ {% if scalarIo %}
117
+ let v = load_vec4(base + i * 4u);
118
+ {% else %}
119
+ let v = vec4<f32>(x[base + i]);
120
+ {% endif %}
121
+ let d = v - vec4<f32>(shift);
122
+ acc.x = acc.x + d.x + d.y + d.z + d.w;
123
+ acc.y = acc.y + dot(d, d);
124
+ }
125
+ {% else %}
126
+ for (var i = tid; i < HIDDEN; i = i + WG) {
127
+ let v = f32(x[base + i]);
128
+ let d = v - shift;
129
+ acc.x = acc.x + d;
130
+ acc.y = acc.y + d * d;
131
+ }
132
+ {% endif %}
133
+
134
+ let totals = reduce_pair(acc{{ reduceThreadArguments }});
135
+
136
+ let mean_d = totals.x / f32(HIDDEN);
137
+ let variance = max(totals.y / f32(HIDDEN) - mean_d * mean_d, 0.0);
138
+ let inv = inverseSqrt(variance + EPSILON);
139
+ let row_mean = shift + mean_d;
140
+ let c = row % CHANNELS;
141
+ let ch_scale = f32(scale[c]);
142
+ let ch_bias = f32(bias[c]);
143
+
144
+ {% if source.vec4 %}
145
+ for (var i = tid; i < HIDDEN_V; i = i + WG) {
146
+ {% if scalarIo %}
147
+ let idx = base + i * 4u;
148
+ let v = load_vec4(idx);
149
+ {% else %}
150
+ let idx = base + i;
151
+ let v = vec4<f32>(x[idx]);
152
+ {% endif %}
153
+ {% if scalarIo %}
154
+ let value = (v - vec4<f32>(row_mean)) * inv * vec4<f32>(ch_scale) + vec4<f32>(ch_bias);
155
+ y[idx] = value.x;
156
+ y[idx + 1u] = value.y;
157
+ y[idx + 2u] = value.z;
158
+ y[idx + 3u] = value.w;
159
+ {% else %}
160
+ y[idx] = {{ source.vecType }}((v - vec4<f32>(row_mean)) * inv * vec4<f32>(ch_scale) + vec4<f32>(ch_bias));
161
+ {% endif %}
162
+ }
163
+ {% else %}
164
+ for (var i = tid; i < HIDDEN; i = i + WG) {
165
+ let idx = base + i;
166
+ let v = f32(x[idx]);
167
+ y[idx] = {{ source.scalar }}((v - row_mean) * inv * ch_scale + ch_bias);
168
+ }
169
+ {% endif %}
170
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,705 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.InstanceNormalization",
3
+ "fixtureArrays": {
4
+ "ort_rank3_batch2_repeated_epsilon_point3_input_input": [3.1513367, 9.283596, 1.4546119, 5.4617004, 8.519701, 1.2382338, 1.7930176, 5.1099434, 7.9195533, 7.638727, 8.065445, 3.8082376, 3.1513367, 9.283596, 1.4546119, 5.4617004, 8.519701, 1.2382338, 1.7930176, 5.1099434, 7.9195533, 7.638727, 8.065445, 3.8082376]
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "dispatch_cliff_ncl_16777216",
9
+ "attrs": { "epsilon": 0.00001 },
10
+ "inputs": {
11
+ "input": {
12
+ "dtype": "float32",
13
+ "shape": [1, 1, 16777217],
14
+ "data": { "kind": "cycle", "values": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] }
15
+ },
16
+ "scale": { "dtype": "float32", "shape": [1], "data": { "kind": "constant", "value": 1.0 } },
17
+ "b": { "dtype": "float32", "shape": [1], "data": { "kind": "constant", "value": 0.0 } }
18
+ },
19
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 1, 16777217], "tolerance": 0.002 } }
20
+ },
21
+ {
22
+ "name": "subgroup_vec4_2x4x32x32",
23
+ "attrs": { "epsilon": 0.00001 },
24
+ "inputs": {
25
+ "input": {
26
+ "dtype": "float32",
27
+ "shape": [2, 4, 32, 32],
28
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21 }
29
+ },
30
+ "scale": {
31
+ "dtype": "float32",
32
+ "shape": [4],
33
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.11, "scale": 0.5 }
34
+ },
35
+ "b": {
36
+ "dtype": "float32",
37
+ "shape": [4],
38
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.19, "scale": 0.25 }
39
+ }
40
+ },
41
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 4, 32, 32], "tolerance": 0.000002 } }
42
+ },
43
+ {
44
+ "name": "subgroup_scalar_1x2x13x5",
45
+ "attrs": { "epsilon": 0.00001 },
46
+ "inputs": {
47
+ "input": {
48
+ "dtype": "float32",
49
+ "shape": [1, 2, 13, 5],
50
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.23 }
51
+ },
52
+ "scale": {
53
+ "dtype": "float32",
54
+ "shape": [2],
55
+ "data": { "kind": "fillFloat32", "sinStep": 0.31, "cosStep": 0.07, "scale": 0.4 }
56
+ },
57
+ "b": {
58
+ "dtype": "float32",
59
+ "shape": [2],
60
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13, "scale": 0.2 }
61
+ }
62
+ },
63
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 13, 5], "tolerance": 0.000002 } }
64
+ },
65
+ {
66
+ "name": "f32_yfold_many_channels_1x70000x4",
67
+ "provenance": {
68
+ "notes": "Compact sibling for the y-fold InstanceNormalization benchmark; preserves C>65535 with much smaller spatial work."
69
+ },
70
+ "attrs": { "epsilon": 0.00001 },
71
+ "inputs": {
72
+ "input": {
73
+ "dtype": "float32",
74
+ "shape": [1, 70000, 4],
75
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 0.5 }
76
+ },
77
+ "scale": {
78
+ "dtype": "float32",
79
+ "shape": [70000],
80
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.023, "scale": 0.25, "offset": 1.0 }
81
+ },
82
+ "b": {
83
+ "dtype": "float32",
84
+ "shape": [70000],
85
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.029, "scale": 0.1 }
86
+ }
87
+ },
88
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 70000, 4], "tolerance": 0.001, "relTolerance": 0.001 } }
89
+ },
90
+ {
91
+ "name": "f32_batched_planes_1x257x64",
92
+ "provenance": {
93
+ "notes": "Compact correctness lock for the feature-independent lane-cohort plane batching used by the 70,000-row dispatch-cliff benchmark."
94
+ },
95
+ "attrs": { "epsilon": 0.00001 },
96
+ "inputs": {
97
+ "input": {
98
+ "dtype": "float32",
99
+ "shape": [1, 257, 64],
100
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.031, "scale": 0.5 }
101
+ },
102
+ "scale": {
103
+ "dtype": "float32",
104
+ "shape": [257],
105
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.25, "offset": 1.0 }
106
+ },
107
+ "b": {
108
+ "dtype": "float32",
109
+ "shape": [257],
110
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.023, "scale": 0.1 }
111
+ }
112
+ },
113
+ "outputs": {
114
+ "output": { "dtype": "float32", "shape": [1, 257, 64], "tolerance": 0.00001, "relTolerance": 0.00001 }
115
+ }
116
+ },
117
+ {
118
+ "name": "nchw",
119
+ "attrs": { "epsilon": 0.00001 },
120
+ "inputs": {
121
+ "input": {
122
+ "dtype": "float32",
123
+ "shape": [1, 2, 2, 3],
124
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, -1.0, -2.0, -3.0, -4.0, -5.0, -6.0] }
125
+ },
126
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 0.5] } },
127
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1.0] } }
128
+ },
129
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 3], "tolerance": 0.000001 } }
130
+ },
131
+ {
132
+ "name": "f32_tiny_variance_epsilon_zero_gpu_gap",
133
+ "skipGpu": {
134
+ "category": "permanent",
135
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; the subnormal per-instance variance collapses to zero so normalization is non-finite."
136
+ },
137
+ "provenance": {
138
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
139
+ "test": "InstanceNormalizationOpTest.InstanceNormNCHW",
140
+ "notes": "Valid epsilon=0 edge: normal inputs produce subnormal per-instance variance but finite order-one normalized outputs."
141
+ },
142
+ "attrs": { "epsilon": 0 },
143
+ "inputs": {
144
+ "input": {
145
+ "dtype": "float32",
146
+ "shape": [1, 2, 2],
147
+ "data": { "kind": "values", "values": [1e-20, -1e-20, 2e-20, -2e-20] }
148
+ },
149
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.0] } },
150
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 0.0] } }
151
+ },
152
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2], "tolerance": 0.00001 } }
153
+ },
154
+ {
155
+ "name": "f32_subnormal_scale_rank3_gpu_gap",
156
+ "skipGpu": {
157
+ "category": "permanent",
158
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; the subnormal affine scale collapses to zero, losing the tiny output it should preserve."
159
+ },
160
+ "provenance": {
161
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
162
+ "test": "InstanceNormalizationOpTest.InstanceNorm",
163
+ "notes": "Subnormal scale is a valid affine parameter; the normalized output should preserve tiny values."
164
+ },
165
+ "attrs": { "epsilon": 0.00001 },
166
+ "inputs": {
167
+ "input": {
168
+ "dtype": "float32",
169
+ "shape": [1, 2, 2],
170
+ "data": { "kind": "values", "values": [-1.0, 1.0, -2.0, 2.0] }
171
+ },
172
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1e-40, -2e-40] } },
173
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 0.0] } }
174
+ },
175
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2], "tolerance": 1e-44 } }
176
+ },
177
+ {
178
+ "name": "f32_subnormal_scale_rank4_vec4_gpu_gap",
179
+ "skipGpu": {
180
+ "category": "permanent",
181
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; the subnormal affine scale collapses to zero (vec4 path)."
182
+ },
183
+ "provenance": {
184
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
185
+ "test": "InstanceNormalizationOpTest.InstanceNormBatch1",
186
+ "notes": "Vec4 spatial companion for valid subnormal scale outputs."
187
+ },
188
+ "attrs": { "epsilon": 0.00001 },
189
+ "inputs": {
190
+ "input": {
191
+ "dtype": "float32",
192
+ "shape": [1, 2, 2, 2],
193
+ "data": { "kind": "values", "values": [-3.0, -1.0, 1.0, 3.0, 4.0, 2.0, 0.0, -2.0] }
194
+ },
195
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1e-40, -2e-40] } },
196
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 0.0] } }
197
+ },
198
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2], "tolerance": 1e-44 } }
199
+ },
200
+ {
201
+ "name": "ncdhw_rank5",
202
+ "attrs": { "epsilon": 0.00001 },
203
+ "inputs": {
204
+ "input": {
205
+ "dtype": "float32",
206
+ "shape": [1, 2, 2, 2, 2],
207
+ "data": {
208
+ "kind": "values",
209
+ "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, -1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0]
210
+ }
211
+ },
212
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 0.5] } },
213
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1.0] } }
214
+ },
215
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2, 2], "tolerance": 0.000001 } }
216
+ },
217
+ {
218
+ "name": "large_mean_small_variance_one_pass_cancellation",
219
+ "attrs": { "epsilon": 0.00001 },
220
+ "inputs": {
221
+ "input": {
222
+ "dtype": "float32",
223
+ "shape": [1, 2, 2, 3],
224
+ "data": {
225
+ "kind": "values",
226
+ "values": [8192.0, 8193.0, 8194.0, 8195.0, 8196.0, 8197.0, -8192.0, -8193.0, -8194.0, -8195.0, -8196.0, -8197.0]
227
+ }
228
+ },
229
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 0.5] } },
230
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1.0] } }
231
+ },
232
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 3], "tolerance": 0.001 } }
233
+ },
234
+ {
235
+ "name": "zero_variance_bias_only",
236
+ "attrs": { "epsilon": 0.001 },
237
+ "inputs": {
238
+ "input": {
239
+ "dtype": "float32",
240
+ "shape": [1, 3, 2, 2],
241
+ "data": { "kind": "values", "values": [7.0, 7.0, 7.0, 7.0, -3.0, -3.0, -3.0, -3.0, 0.0, 0.0, 0.0, 0.0] }
242
+ },
243
+ "scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [10.0, -2.0, 4.0] } },
244
+ "b": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.5, -1.0, 2.0] } }
245
+ },
246
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 3, 2, 2], "tolerance": 0.000001 } }
247
+ },
248
+ {
249
+ "name": "large_epsilon_damps_output",
250
+ "attrs": { "epsilon": 0.5 },
251
+ "inputs": {
252
+ "input": {
253
+ "dtype": "float32",
254
+ "shape": [1, 2, 1, 4],
255
+ "data": { "kind": "values", "values": [1.0, 2.0, 4.0, 8.0, -1.0, -2.0, -4.0, -8.0] }
256
+ },
257
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.5, -0.5] } },
258
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, 2.0] } }
259
+ },
260
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 1, 4], "tolerance": 0.000001 } }
261
+ },
262
+ {
263
+ "name": "ort_webgpu_nchw_zero_variance_channel",
264
+ "provenance": {
265
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
266
+ "test": "InstanceNormalizationOpTest.InstanceNormNCHW_webgpu"
267
+ },
268
+ "attrs": { "epsilon": 0.009999999776482582 },
269
+ "inputs": {
270
+ "input": {
271
+ "dtype": "float32",
272
+ "shape": [1, 2, 1, 3],
273
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 2.0, 2.0, 2.0] }
274
+ },
275
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.0] } },
276
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 2.0] } }
277
+ },
278
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 1, 3], "tolerance": 0.00001 } }
279
+ },
280
+ {
281
+ "name": "ort_webgpu_nchw_mixed_zero_variance",
282
+ "provenance": {
283
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
284
+ "test": "InstanceNormalizationOpTest.InstanceNormNCHW_webgpu_2"
285
+ },
286
+ "attrs": { "epsilon": 0.009999999776482582 },
287
+ "inputs": {
288
+ "input": {
289
+ "dtype": "float32",
290
+ "shape": [1, 2, 2, 2],
291
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 2.0, 2.0, 2.0, 2.0, 2.0] }
292
+ },
293
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.0] } },
294
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 2.0] } }
295
+ },
296
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2], "tolerance": 0.00001 } }
297
+ },
298
+ {
299
+ "name": "ort_rank3_batch1_epsilon_point3",
300
+ "provenance": {
301
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
302
+ "test": "InstanceNormalizationOpTest.InstanceNormBatch1"
303
+ },
304
+ "attrs": { "epsilon": 0.3 },
305
+ "inputs": {
306
+ "input": {
307
+ "dtype": "float32",
308
+ "shape": [1, 3, 4],
309
+ "data": {
310
+ "kind": "values",
311
+ "values": [3.1513367, 9.283596, 1.4546119, 5.4617004, 8.519701, 1.2382338, 1.7930176, 5.1099434, 7.9195533, 7.638727, 8.065445, 3.8082376]
312
+ }
313
+ },
314
+ "scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, 1.0, 1.0] } },
315
+ "b": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
316
+ },
317
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 3, 4], "tolerance": 0.00001 } }
318
+ },
319
+ {
320
+ "name": "ort_rank3_batch2_distinct_epsilon_point3",
321
+ "provenance": {
322
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
323
+ "test": "InstanceNormalizationOpTest.InstanceNorm"
324
+ },
325
+ "attrs": { "epsilon": 0.3 },
326
+ "inputs": {
327
+ "input": {
328
+ "dtype": "float32",
329
+ "shape": [2, 3, 4],
330
+ "data": {
331
+ "kind": "values",
332
+ "values": [3.1513367, 9.283596, 1.4546119, 5.4617004, 8.519701, 1.2382338, 1.7930176, 5.1099434, 7.9195533, 7.638727, 8.065445, 3.8082376, 2.3667817, 2.8248506, 3.7754705, 5.861325, 5.058735, 3.2787242, 3.6843839, 9.755121, 2.7902672, 7.3974323, 8.283609, 8.488337]
333
+ }
334
+ },
335
+ "scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, 1.0, 1.0] } },
336
+ "b": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
337
+ },
338
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 3, 4], "tolerance": 0.00001 } }
339
+ },
340
+ {
341
+ "name": "ort_rank3_batch2_repeated_epsilon_point3",
342
+ "provenance": {
343
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
344
+ "test": "InstanceNormalizationOpTest.InstanceNormBatch2"
345
+ },
346
+ "attrs": { "epsilon": 0.3 },
347
+ "inputs": {
348
+ "input": {
349
+ "dtype": "float32",
350
+ "shape": [2, 3, 4],
351
+ "data": {
352
+ "kind": "values",
353
+ "values": { "$ref": "#/fixtureArrays/ort_rank3_batch2_repeated_epsilon_point3_input_input" }
354
+ }
355
+ },
356
+ "scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, 1.0, 1.0] } },
357
+ "b": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
358
+ },
359
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 3, 4], "tolerance": 0.00001 } }
360
+ },
361
+ {
362
+ "name": "ort_rank3_batch1_fp16",
363
+ "provenance": {
364
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
365
+ "test": "InstanceNormalizationOpTest.InstanceNormBatch1_fp16",
366
+ "notes": "Valid fp16 InstanceNormalization coverage for a rank-3 single-batch input."
367
+ },
368
+ "attrs": { "epsilon": 0.3 },
369
+ "inputs": {
370
+ "input": {
371
+ "dtype": "float16",
372
+ "shape": [1, 3, 4],
373
+ "data": {
374
+ "kind": "values",
375
+ "values": [3.1513367, 9.283596, 1.4546119, 5.4617004, 8.519701, 1.2382338, 1.7930176, 5.1099434, 7.9195533, 7.638727, 8.065445, 3.8082376]
376
+ }
377
+ },
378
+ "scale": { "dtype": "float16", "shape": [3], "data": { "kind": "values", "values": [1.0, 1.0, 1.0] } },
379
+ "b": { "dtype": "float16", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
380
+ },
381
+ "outputs": { "output": { "dtype": "float16", "shape": [1, 3, 4], "tolerance": 0.005, "relTolerance": 0.005 } }
382
+ },
383
+ {
384
+ "name": "ort_rank3_batch2_fp16",
385
+ "provenance": {
386
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
387
+ "test": "InstanceNormalizationOpTest.InstanceNormBatch2_fp16",
388
+ "notes": "Valid fp16 InstanceNormalization coverage for a rank-3 two-batch input."
389
+ },
390
+ "attrs": { "epsilon": 0.3 },
391
+ "inputs": {
392
+ "input": {
393
+ "dtype": "float16",
394
+ "shape": [2, 3, 4],
395
+ "data": {
396
+ "kind": "values",
397
+ "values": { "$ref": "#/fixtureArrays/ort_rank3_batch2_repeated_epsilon_point3_input_input" }
398
+ }
399
+ },
400
+ "scale": { "dtype": "float16", "shape": [3], "data": { "kind": "values", "values": [1.0, 1.0, 1.0] } },
401
+ "b": { "dtype": "float16", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
402
+ },
403
+ "outputs": { "output": { "dtype": "float16", "shape": [2, 3, 4], "tolerance": 0.005, "relTolerance": 0.005 } }
404
+ },
405
+ {
406
+ "name": "ort_rank5_instance_norm_2",
407
+ "provenance": {
408
+ "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
409
+ "test": "InstanceNormalizationOpTest.InstanceNorm_2"
410
+ },
411
+ "attrs": { "epsilon": 0.3 },
412
+ "inputs": {
413
+ "input": {
414
+ "dtype": "float32",
415
+ "shape": [2, 3, 2, 1, 3],
416
+ "data": {
417
+ "kind": "values",
418
+ "values": [2.676342, 4.1100464, 4.570907, 5.8493505, 4.772751, 7.1669755, 2.8400702, 8.903057, 1.2464883, 7.034208, 4.755743, 6.0282083, 2.2634823, 2.7829134, 8.206701, 9.7143545, 3.8208177, 7.2309036, 8.887503, 9.05146, 1.7653979, 1.351493, 2.5284739, 8.903282, 1.8851215, 4.7899685, 9.621006, 5.7984877, 7.226894, 3.8396406, 7.1785083, 8.511631, 1.1645945, 7.751299, 9.89975, 7.733491]
419
+ }
420
+ },
421
+ "scale": {
422
+ "dtype": "float32",
423
+ "shape": [3],
424
+ "data": { "kind": "values", "values": [4.753198, 7.4829206, 1.0010294] }
425
+ },
426
+ "b": {
427
+ "dtype": "float32",
428
+ "shape": [3],
429
+ "data": { "kind": "values", "values": [3.720993, 2.320803, 1.8310473] }
430
+ }
431
+ },
432
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 3, 2, 1, 3], "tolerance": 0.00001 } }
433
+ },
434
+ {
435
+ "name": "onnx_backend_example_1x2x1x3",
436
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_instancenorm_example" },
437
+ "inputs": {
438
+ "input": {
439
+ "dtype": "float32",
440
+ "shape": [1, 2, 1, 3],
441
+ "data": { "kind": "values", "values": [-1.0, 0.0, 1.0, 2.0, 3.0, 4.0] }
442
+ },
443
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.5] } },
444
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1.0] } }
445
+ },
446
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 1, 3], "tolerance": 0.00001 } }
447
+ },
448
+ {
449
+ "name": "onnx_backend_instancenorm_epsilon",
450
+ "attrs": { "epsilon": 0.009999999776482582 },
451
+ "inputs": {
452
+ "input": {
453
+ "dtype": "float32",
454
+ "shape": [2, 3, 4, 5],
455
+ "data": {
456
+ "kind": "values",
457
+ "values": [1.764052391052246, 0.40015721321105957, 0.978738009929657, 2.2408931255340576, 1.8675580024719238, -0.9772778749465942, 0.9500884413719177, -0.15135720372200012, -0.10321885347366333, 0.4105985164642334, 0.14404356479644775, 1.4542734622955322, 0.7610377073287964, 0.12167501449584961, 0.44386324286460876, 0.3336743414402008, 1.4940791130065918, -0.2051582634449005, 0.3130677044391632, -0.8540957570075989, -2.5529897212982178, 0.653618574142456, 0.8644362092018127, -0.7421650290489197, 2.269754648208618, -1.4543657302856445, 0.04575851559638977, -0.18718385696411133, 1.5327792167663574, 1.4693588018417358, 0.154947429895401, 0.37816253304481506, -0.8877857327461243, -1.980796456336975, -0.34791216254234314, 0.15634897351264954, 1.2302906513214111, 1.202379822731018, -0.38732680678367615, -0.302302747964859, -1.0485529899597168, -1.420017957687378, -1.7062702178955078, 1.950775384902954, -0.5096521973609924, -0.4380742907524109, -1.2527953386306763, 0.7774903774261475, -1.6138978004455566, -0.21274028718471527, -0.8954665660858154, 0.38690251111984253, -0.5108051300048828, -1.18063223361969, -0.02818222902715206, 0.4283318817615509, 0.06651721894741058, 0.30247190594673157, -0.6343221068382263, -0.3627411723136902, -0.6724604368209839, -0.35955315828323364, -0.8131462931632996, -1.7262825965881348, 0.17742614448070526, -0.4017809331417084, -1.630198359489441, 0.46278226375579834, -0.9072983860969543, 0.05194539576768875, 0.7290905714035034, 0.12898291647434235, 1.1394007205963135, -1.234825849533081, 0.4023416340351105, -0.6848101019859314, -0.8707971572875977, -0.5788496732711792, -0.3115525245666504, 0.056165341287851334, -1.1651498079299927, 0.9008265137672424, 0.4656624495983124, -1.5362436771392822, 1.4882521629333496, 1.895889163017273, 1.1787796020507812, -0.1799248307943344, -1.0707526206970215, 1.0544517040252686, -0.4031769335269928, 1.222445011138916, 0.2082749754190445, 0.9766390323638916, 0.3563663959503174, 0.7065731883049011, 0.01050002034753561, 1.7858705520629883, 0.12691208720207214, 0.4019893705844879, 1.8831506967544556, -1.3477590084075928, -1.2704850435256958, 0.969396710395813, -1.1731233596801758, 1.9436211585998535, -0.4136189818382263, -0.747454822063446, 1.922942042350769, 1.4805147647857666, 1.8675589561462402, 0.9060446619987488, -0.8612256646156311, 1.910064935684204, -0.26800337433815, 0.8024563789367676, 0.9472519755363464, -0.15501008927822113, 0.6140793561935425, 0.922206699848175]
458
+ }
459
+ },
460
+ "scale": {
461
+ "dtype": "float32",
462
+ "shape": [3],
463
+ "data": { "kind": "values", "values": [0.37642553448677063, -1.0994007587432861, 0.29823818802833557] }
464
+ },
465
+ "b": {
466
+ "dtype": "float32",
467
+ "shape": [3],
468
+ "data": { "kind": "values", "values": [1.3263858556747437, -0.694567859172821, -0.14963454008102417] }
469
+ }
470
+ },
471
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 3, 4, 5] } },
472
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_instancenorm_epsilon" }
473
+ },
474
+ {
475
+ "name": "empty_zero_dim",
476
+ "attrs": { "epsilon": 0.00001 },
477
+ "inputs": {
478
+ "input": { "dtype": "float32", "shape": [0, 4, 32, 32], "data": { "kind": "values", "values": [] } },
479
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "constant", "value": 1.0 } },
480
+ "b": { "dtype": "float32", "shape": [4], "data": { "kind": "constant", "value": 0.0 } }
481
+ },
482
+ "outputs": { "output": { "dtype": "float32", "shape": [0, 4, 32, 32], "tolerance": 0 } }
483
+ },
484
+ {
485
+ "name": "f16_vec4_cnn_1x32x64x64",
486
+ "attrs": { "epsilon": 0.00001 },
487
+ "inputs": {
488
+ "input": {
489
+ "dtype": "float16",
490
+ "shape": [1, 32, 64, 64],
491
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.19, "scale": 0.5 }
492
+ },
493
+ "scale": {
494
+ "dtype": "float16",
495
+ "shape": [32],
496
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.4, "offset": 1.0 }
497
+ },
498
+ "b": {
499
+ "dtype": "float16",
500
+ "shape": [32],
501
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.17, "scale": 0.25 }
502
+ }
503
+ },
504
+ "outputs": {
505
+ "output": { "dtype": "float16", "shape": [1, 32, 64, 64], "tolerance": 0.005, "relTolerance": 0.005 }
506
+ }
507
+ },
508
+ {
509
+ "name": "f16_scalar_oddspatial_1x64x15x17",
510
+ "attrs": { "epsilon": 0.00001 },
511
+ "inputs": {
512
+ "input": {
513
+ "dtype": "float16",
514
+ "shape": [1, 64, 15, 17],
515
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.29, "scale": 0.5 }
516
+ },
517
+ "scale": {
518
+ "dtype": "float16",
519
+ "shape": [64],
520
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 0.4, "offset": 1.0 }
521
+ },
522
+ "b": {
523
+ "dtype": "float16",
524
+ "shape": [64],
525
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.23, "scale": 0.25 }
526
+ }
527
+ },
528
+ "outputs": {
529
+ "output": { "dtype": "float16", "shape": [1, 64, 15, 17], "tolerance": 0.006, "relTolerance": 0.006 }
530
+ }
531
+ },
532
+ {
533
+ "name": "f16_vec4_rank3_2x32x1024_eps1e3",
534
+ "attrs": { "epsilon": 0.001 },
535
+ "inputs": {
536
+ "input": {
537
+ "dtype": "float16",
538
+ "shape": [2, 32, 1024],
539
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21, "scale": 0.5 }
540
+ },
541
+ "scale": {
542
+ "dtype": "float16",
543
+ "shape": [32],
544
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.11, "scale": 0.4, "offset": 1.0 }
545
+ },
546
+ "b": {
547
+ "dtype": "float16",
548
+ "shape": [32],
549
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.19, "scale": 0.25 }
550
+ }
551
+ },
552
+ "outputs": { "output": { "dtype": "float16", "shape": [2, 32, 1024], "tolerance": 0.005, "relTolerance": 0.005 } }
553
+ },
554
+ {
555
+ "name": "f32_vec4_negscale_largeeps_1x8x16x16",
556
+ "attrs": { "epsilon": 0.2 },
557
+ "inputs": {
558
+ "input": {
559
+ "dtype": "float32",
560
+ "shape": [1, 8, 16, 16],
561
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21, "scale": 0.7 }
562
+ },
563
+ "scale": {
564
+ "dtype": "float32",
565
+ "shape": [8],
566
+ "data": { "kind": "values", "values": [1.5, -0.5, 2.0, -1.0, 0.25, -2.5, 1.0, -0.75] }
567
+ },
568
+ "b": {
569
+ "dtype": "float32",
570
+ "shape": [8],
571
+ "data": { "kind": "values", "values": [0.0, 1.0, -1.0, 0.5, -0.25, 2.0, -2.0, 0.75] }
572
+ }
573
+ },
574
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 8, 16, 16], "tolerance": 0.00001 } }
575
+ },
576
+ {
577
+ "name": "f32_splitk_2plane_huge_1x2x131072",
578
+ "attrs": { "epsilon": 0.000001 },
579
+ "inputs": {
580
+ "input": {
581
+ "dtype": "float32",
582
+ "shape": [1, 2, 131072],
583
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.023, "scale": 0.5 }
584
+ },
585
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.5, -0.75] } },
586
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
587
+ },
588
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 131072], "tolerance": 0.0001 } }
589
+ },
590
+ {
591
+ "name": "empty_zero_spatial_dim_1x4x3x0",
592
+ "attrs": { "epsilon": 0.00001 },
593
+ "inputs": {
594
+ "input": { "dtype": "float32", "shape": [1, 4, 3, 0], "data": { "kind": "values", "values": [] } },
595
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "constant", "value": 1.0 } },
596
+ "b": { "dtype": "float32", "shape": [4], "data": { "kind": "constant", "value": 0.0 } }
597
+ },
598
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 3, 0], "tolerance": 0 } }
599
+ },
600
+ {
601
+ "name": "empty_zero_channels",
602
+ "attrs": { "epsilon": 0.00001 },
603
+ "inputs": {
604
+ "input": { "dtype": "float32", "shape": [2, 0, 4, 4], "data": { "kind": "values", "values": [] } },
605
+ "scale": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } },
606
+ "b": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
607
+ },
608
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 0, 4, 4], "tolerance": 0 } }
609
+ },
610
+ {
611
+ "name": "rank6_vec4_general_spatial_flatten_1x2x2x2x2x2",
612
+ "attrs": { "epsilon": 0.00001 },
613
+ "inputs": {
614
+ "input": {
615
+ "dtype": "float32",
616
+ "shape": [1, 2, 2, 2, 2, 2],
617
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21 }
618
+ },
619
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.5, -0.5] } },
620
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, 2.0] } }
621
+ },
622
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2, 2, 2], "tolerance": 0.000002 } }
623
+ },
624
+ {
625
+ "name": "rank7_spatial_flatten",
626
+ "provenance": {
627
+ "source": "onnx spec: InstanceNormalization accepts N x C x D1 x ... x Dn",
628
+ "notes": "Exercises instance normalization over a rank-7 tensor using the flattened spatial extent for each batch/channel row."
629
+ },
630
+ "attrs": { "epsilon": 0.00001 },
631
+ "inputs": {
632
+ "input": {
633
+ "dtype": "float32",
634
+ "shape": [1, 2, 2, 2, 2, 2, 2],
635
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21 }
636
+ },
637
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.5, -0.5] } },
638
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, 2.0] } }
639
+ },
640
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2, 2, 2, 2], "tolerance": 0.000002 } }
641
+ },
642
+ {
643
+ "name": "rank5_scalar_nosg_oddspatial_2x3x3x3x3",
644
+ "attrs": { "epsilon": 0.001 },
645
+ "inputs": {
646
+ "input": {
647
+ "dtype": "float32",
648
+ "shape": [2, 3, 3, 3, 3],
649
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.23 }
650
+ },
651
+ "scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, -2.0, 0.5] } },
652
+ "b": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.5, -1.0, 2.0] } }
653
+ },
654
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 3, 3, 3, 3], "tolerance": 0.000004 } }
655
+ },
656
+ {
657
+ "name": "rank8_spatial_flatten",
658
+ "attrs": { "epsilon": 0.00001 },
659
+ "inputs": {
660
+ "input": {
661
+ "dtype": "float32",
662
+ "shape": [1, 2, 2, 2, 2, 2, 2, 2],
663
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27 }
664
+ },
665
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.5, -0.5] } },
666
+ "b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, -1.0] } }
667
+ },
668
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2, 2, 2, 2, 2], "tolerance": 0.000002 } }
669
+ },
670
+ {
671
+ "name": "split_stats_spatial_65536_f16",
672
+ "provenance": {
673
+ "notes": "float16 on the split-statistics rungs, which were gated to float32 until the partials pass learned to widen its packed load and the apply pass to narrow its packed store. A spatial extent of SPLIT_STATS_MIN_SPATIAL that is also a multiple of four leaves both the vec4 and the scalar rung eligible, so one case renders both."
674
+ },
675
+ "attrs": { "epsilon": 0.00001 },
676
+ "inputs": {
677
+ "input": {
678
+ "dtype": "float16",
679
+ "shape": [1, 1, 65536],
680
+ "data": { "kind": "cycle", "values": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] }
681
+ },
682
+ "scale": { "dtype": "float16", "shape": [1], "data": { "kind": "constant", "value": 1.0 } },
683
+ "b": { "dtype": "float16", "shape": [1], "data": { "kind": "constant", "value": 0.0 } }
684
+ },
685
+ "outputs": { "output": { "dtype": "float16", "shape": [1, 1, 65536], "tolerance": 0.03 } }
686
+ },
687
+ {
688
+ "name": "batched_planes_vec4_p256_s64_f16",
689
+ "provenance": {
690
+ "notes": "float16 on the batched-planes path uses 2x128 planes of 64 elements, giving 16 lanes per plane and 16 planes per workgroup. This checks half-precision storage across the batched normalization and apply stages."
691
+ },
692
+ "attrs": { "epsilon": 0.00001 },
693
+ "inputs": {
694
+ "input": {
695
+ "dtype": "float16",
696
+ "shape": [2, 128, 64],
697
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 0.5 }
698
+ },
699
+ "scale": { "dtype": "float16", "shape": [128], "data": { "kind": "constant", "value": 1.0 } },
700
+ "b": { "dtype": "float16", "shape": [128], "data": { "kind": "constant", "value": 0.0 } }
701
+ },
702
+ "outputs": { "output": { "dtype": "float16", "shape": [2, 128, 64], "tolerance": 0.03 } }
703
+ }
704
+ ]
705
+ }