Xenova HF Staff commited on
Commit
1f79789
·
verified ·
1 Parent(s): 0877677

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,80 @@
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.GroupNormalization
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 21
12
+
13
+ ## Description
14
+
15
+ Applies group normalization to the input: `y = scale * (x - mean) / sqrt(variance + epsilon) + bias`, where mean and variance are computed per instance per group of channels. The number of groups `num_groups` must divide the channel count `C` evenly; when `num_groups == C` this is equivalent to InstanceNormalization, and when `num_groups == 1` it is equivalent to LayerNormalization. The normalization stage supports TensorProto `stash_type` values `1` (float32) and `10` (float16).
16
+
17
+ See the [ONNX `GroupNormalization` spec](https://onnx.ai/onnx/operators/onnx__GroupNormalization.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `X` | `x` | `T` | — | — | Input data tensor of shape `(N x C x D1 x ... x Dn)` where `N` is batch size and `C` is the number of channels. | required |
24
+ | `scale` | `scale` | `T` | `1` | — | Scale tensor of shape `(C)`, one value per channel. | required |
25
+ | `bias` | `bias` | `T` | `1` | — | Bias tensor of shape `(C)`, one value per channel. | required |
26
+
27
+ ## Outputs
28
+
29
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- | --- |
31
+ | `Y` | `y` | `T` | same as `X` | same as `X` | Normalized output tensor of the same shape as `X`. | required |
32
+
33
+ ## Attributes
34
+
35
+ Attributes and default values (overridable per request):
36
+
37
+ | Attribute | Default | Description |
38
+ | --- | --- | --- |
39
+ | `epsilon` | `0.00001` | Small value added to the variance denominator to avoid division by zero. |
40
+ | `stash_type` | `1` | TensorProto element type used for the normalization stage: `1` computes in float32, while `10` computes in float16. Normalized values are cast back to the input type before scale and bias are applied. |
41
+ | `num_groups` | — | Required number of groups to divide the channels into; must be a divisor of `C`. |
42
+
43
+ ## Type constraints
44
+
45
+ | Variable | Allowed dtypes |
46
+ | --- | --- |
47
+ | `T` | `float32`, `float16` |
48
+
49
+ ## Files
50
+
51
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
52
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
53
+ - [`test.json`](build/webgpu/test.json) — correctness cases
54
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
55
+ - [`group-normalization-splitk-apply.wgsl.jinja`](build/webgpu/group-normalization-splitk-apply.wgsl.jinja)
56
+ - [`group-normalization-splitk-partials.wgsl.jinja`](build/webgpu/group-normalization-splitk-partials.wgsl.jinja)
57
+ - [`group-normalization-stash-f16-serial.wgsl.jinja`](build/webgpu/group-normalization-stash-f16-serial.wgsl.jinja)
58
+ - [`norm-row-stats.wgsl.jinja`](build/webgpu/norm-row-stats.wgsl.jinja)
59
+
60
+ ## Use with `@huggingface/kernels`
61
+
62
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
63
+ It then allocates the result tensors automatically.
64
+
65
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
66
+
67
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
68
+
69
+ ```js
70
+ import { getKernel } from "@huggingface/kernels";
71
+
72
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.GroupNormalization", { version: 1 });
73
+ const { y } = await kernel({
74
+ x: { data: xData, shape: [1, 2, 3] },
75
+ scale: { data: scaleData, shape: [2] },
76
+ bias: { data: biasData, shape: [2] },
77
+ }, {
78
+ attrs: { num_groups: 1 },
79
+ });
80
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.GroupNormalization",
3
+ "cases": [
4
+ {
5
+ "name": "4x64x128x128_g32",
6
+ "preset": "smoke",
7
+ "vars": { "dtype": "float32", "batch": 4, "channels": 64, "spatial": 16384 },
8
+ "attrs": { "num_groups": 32, "epsilon": 0.00001 },
9
+ "inputs": {
10
+ "x": { "dtype": "float32", "shape": [4, 64, 128, 128], "dist": "normal", "seed": 740, "scale": 0.5 },
11
+ "scale": { "dtype": "float32", "shape": [64], "dist": "uniform", "seed": 741, "scale": 0.25, "offset": 1 },
12
+ "bias": { "dtype": "float32", "shape": [64], "dist": "normal", "seed": 742, "scale": 0.1 }
13
+ },
14
+ "outputs": { "y": { "dtype": "float32", "shape": [4, 64, 128, 128] } },
15
+ "bench": {
16
+ "primary": true,
17
+ "metrics": [
18
+ {
19
+ "type": "bandwidth",
20
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ {
26
+ "name": "1x32x32x32_g8",
27
+ "attrs": { "num_groups": 8, "epsilon": 0.00001 },
28
+ "inputs": {
29
+ "x": { "dtype": "float32", "shape": [1, 32, 32, 32] },
30
+ "scale": { "dtype": "float32", "shape": [32], "data": { "kind": "constant", "value": 1.0 } },
31
+ "bias": { "dtype": "float32", "shape": [32], "data": { "kind": "constant", "value": 0.0 } }
32
+ },
33
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 32, 32] } }
34
+ },
35
+ {
36
+ "name": "f16_vec4_4x64x128x128_g32",
37
+ "preset": "smoke",
38
+ "vars": { "dtype": "float16", "batch": 4, "channels": 64, "spatial": 16384 },
39
+ "attrs": { "num_groups": 32, "epsilon": 0.00001 },
40
+ "inputs": {
41
+ "x": { "dtype": "float16", "shape": [4, 64, 128, 128], "dist": "normal", "seed": 750, "scale": 0.5 },
42
+ "scale": { "dtype": "float16", "shape": [64], "dist": "uniform", "seed": 751, "scale": 0.25, "offset": 1 },
43
+ "bias": { "dtype": "float16", "shape": [64], "dist": "normal", "seed": 752, "scale": 0.1 }
44
+ },
45
+ "outputs": { "y": { "dtype": "float16", "shape": [4, 64, 128, 128] } },
46
+ "bench": {
47
+ "metrics": [
48
+ {
49
+ "type": "bandwidth",
50
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
51
+ }
52
+ ]
53
+ }
54
+ },
55
+ {
56
+ "name": "vec4_spatial_aligned_2x128x56x56_g32",
57
+ "preset": "smoke",
58
+ "vars": { "dtype": "float32", "batch": 2, "channels": 128, "spatial": 3136 },
59
+ "attrs": { "num_groups": 32, "epsilon": 0.00001 },
60
+ "inputs": {
61
+ "x": { "dtype": "float32", "shape": [2, 128, 56, 56], "dist": "normal", "seed": 760, "scale": 0.5 },
62
+ "scale": { "dtype": "float32", "shape": [128], "dist": "uniform", "seed": 761, "scale": 0.25, "offset": 1 },
63
+ "bias": { "dtype": "float32", "shape": [128], "dist": "normal", "seed": 762, "scale": 0.1 }
64
+ },
65
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 128, 56, 56] } },
66
+ "bench": {
67
+ "metrics": [
68
+ {
69
+ "type": "bandwidth",
70
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
71
+ }
72
+ ]
73
+ }
74
+ },
75
+ {
76
+ "name": "scalar_spatial_unaligned_2x128x53x53_g32",
77
+ "preset": "smoke",
78
+ "vars": { "dtype": "float32", "batch": 2, "channels": 128, "spatial": 2809 },
79
+ "attrs": { "num_groups": 32, "epsilon": 0.00001 },
80
+ "inputs": {
81
+ "x": { "dtype": "float32", "shape": [2, 128, 53, 53], "dist": "normal", "seed": 770, "scale": 0.5 },
82
+ "scale": { "dtype": "float32", "shape": [128], "dist": "uniform", "seed": 771, "scale": 0.25, "offset": 1 },
83
+ "bias": { "dtype": "float32", "shape": [128], "dist": "normal", "seed": 772, "scale": 0.1 }
84
+ },
85
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 128, 53, 53] } },
86
+ "bench": {
87
+ "metrics": [
88
+ {
89
+ "type": "bandwidth",
90
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
91
+ }
92
+ ]
93
+ }
94
+ },
95
+ {
96
+ "name": "tiny_spatial_stem_1x32x7x7_g8",
97
+ "preset": "smoke",
98
+ "vars": { "dtype": "float32", "batch": 1, "channels": 32, "spatial": 49 },
99
+ "attrs": { "num_groups": 8, "epsilon": 0.00001 },
100
+ "inputs": {
101
+ "x": { "dtype": "float32", "shape": [1, 32, 7, 7], "dist": "normal", "seed": 780, "scale": 0.5 },
102
+ "scale": { "dtype": "float32", "shape": [32], "dist": "uniform", "seed": 781, "scale": 0.25, "offset": 1 },
103
+ "bias": { "dtype": "float32", "shape": [32], "dist": "normal", "seed": 782, "scale": 0.1 }
104
+ },
105
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 7, 7] } },
106
+ "bench": {
107
+ "metrics": [
108
+ {
109
+ "type": "bandwidth",
110
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
111
+ }
112
+ ]
113
+ }
114
+ },
115
+ {
116
+ "name": "scalar_lowocc_layernorm_equiv_2x320x65x65_g1",
117
+ "preset": "stress",
118
+ "vars": { "dtype": "float32", "batch": 2, "channels": 320, "spatial": 4225 },
119
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
120
+ "inputs": {
121
+ "x": { "dtype": "float32", "shape": [2, 320, 65, 65], "dist": "normal", "seed": 901, "scale": 0.5 },
122
+ "scale": { "dtype": "float32", "shape": [320], "dist": "uniform", "seed": 902, "scale": 0.25, "offset": 1 },
123
+ "bias": { "dtype": "float32", "shape": [320], "dist": "normal", "seed": 903, "scale": 0.1 }
124
+ },
125
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 320, 65, 65], "dist": "empty" } },
126
+ "bench": {
127
+ "metrics": [
128
+ {
129
+ "type": "bandwidth",
130
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
131
+ }
132
+ ]
133
+ }
134
+ },
135
+ {
136
+ "name": "scalar_cliff_unaligned_spatial_8x256x63x63_g32",
137
+ "preset": "stress",
138
+ "vars": { "dtype": "float32", "batch": 8, "channels": 256, "spatial": 3969 },
139
+ "attrs": { "num_groups": 32, "epsilon": 0.00001 },
140
+ "inputs": {
141
+ "x": { "dtype": "float32", "shape": [8, 256, 63, 63], "dist": "normal", "seed": 911, "scale": 0.5 },
142
+ "scale": { "dtype": "float32", "shape": [256], "dist": "uniform", "seed": 912, "scale": 0.25, "offset": 1 },
143
+ "bias": { "dtype": "float32", "shape": [256], "dist": "normal", "seed": 913, "scale": 0.1 }
144
+ },
145
+ "outputs": { "y": { "dtype": "float32", "shape": [8, 256, 63, 63], "dist": "empty" } },
146
+ "bench": {
147
+ "metrics": [
148
+ {
149
+ "type": "bandwidth",
150
+ "value": "(args.batch * args.channels * args.spatial * 2 + args.channels * 2) * dtypeBytes(args.dtype)"
151
+ }
152
+ ]
153
+ }
154
+ }
155
+ ]
156
+ }
build/webgpu/group-normalization-splitk-apply.wgsl.jinja ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ const HIDDEN: u32 = {{ hiddenSize }}u;
7
+ const SPATIAL: u32 = {{ spatial }}u;
8
+ const CPG: u32 = {{ channelsPerGroup }}u;
9
+ const GROUPS: u32 = {{ numGroups }}u;
10
+ const WG: u32 = {{ workgroupSize }}u;
11
+ const SPLIT: u32 = {{ split }}u;
12
+ const EPSILON: f32 = {{ epsilon }};
13
+
14
+ @compute @workgroup_size(WG, 1, 1)
15
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
16
+ let row = wg.x;
17
+ let part = wg.z;
18
+ if (row >= params.rows) { return; }
19
+ var pair = vec2<f32>(0.0);
20
+ for (var p = 0u; p < SPLIT; p = p + 1u) { pair = pair + partials[row * SPLIT + p]; }
21
+ let base = row * HIDDEN;
22
+ let shift = f32(x[base]);
23
+ let mean_d = pair.x / f32(HIDDEN);
24
+ let variance = max(pair.y / f32(HIDDEN) - mean_d * mean_d, 0.0);
25
+ let mean = shift + mean_d;
26
+ let inv_std = inverseSqrt(variance + EPSILON);
27
+ let group = row % GROUPS;
28
+ let chunk = (HIDDEN + SPLIT - 1u) / SPLIT;
29
+ let start = part * chunk;
30
+ let end = min(start + chunk, HIDDEN);
31
+ for (var d = start + lid.x; d < end; d = d + WG) {
32
+ let channel = group * CPG + d / SPATIAL;
33
+ let index = base + d;
34
+ y[index] = {{ scalar }}((f32(x[index]) - mean) * inv_std * f32(scale[channel]) + f32(bias[channel]));
35
+ }
36
+ }
build/webgpu/group-normalization-splitk-partials.wgsl.jinja ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ const HIDDEN: u32 = {{ hiddenSize }}u;
7
+ const WG: u32 = {{ workgroupSize }}u;
8
+ const SPLIT: u32 = {{ split }}u;
9
+ var<workgroup> reduction: array<vec2<f32>, WG>;
10
+
11
+ @compute @workgroup_size(WG, 1, 1)
12
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
13
+ let row = wg.x;
14
+ let part = wg.z;
15
+ if (row >= params.rows) { return; }
16
+ let tid = lid.x;
17
+ let chunk = (HIDDEN + SPLIT - 1u) / SPLIT;
18
+ let start = part * chunk;
19
+ let end = min(start + chunk, HIDDEN);
20
+ let base = row * HIDDEN;
21
+ let shift = f32(x[base]);
22
+ var pair = vec2<f32>(0.0);
23
+ for (var d = start + tid; d < end; d = d + WG) {
24
+ let value = f32(x[base + d]) - shift;
25
+ pair = pair + vec2<f32>(value, value * value);
26
+ }
27
+ reduction[tid] = pair;
28
+ workgroupBarrier();
29
+ for (var stride = WG >> 1u; stride > 0u; stride = stride >> 1u) {
30
+ if (tid < stride) { reduction[tid] = reduction[tid] + reduction[tid + stride]; }
31
+ workgroupBarrier();
32
+ }
33
+ if (tid == 0u) { partials[row * SPLIT + part] = reduction[0]; }
34
+ }
build/webgpu/group-normalization-stash-f16-serial.wgsl.jinja ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}enable f16;
2
+ {% endif %}
3
+ {{ env.wgsl.resourceDeclarations }}
4
+
5
+ const HIDDEN: u32 = {{ hiddenSize }}u;
6
+ const SPATIAL: u32 = {{ spatial }}u;
7
+ const CPG: u32 = {{ channelsPerGroup }}u;
8
+ const GROUPS: u32 = {{ numGroups }}u;
9
+ const EPSILON: f32 = {{ epsilon }};
10
+
11
+ fn round_f16_bits_rte(value: f32) -> u32 {
12
+ let bits = bitcast<u32>(value);
13
+ let sign = (bits >> 16u) & 0x8000u;
14
+ let exponent_f32 = (bits >> 23u) & 0xffu;
15
+ let mantissa_f32 = bits & 0x7fffffu;
16
+
17
+ if (exponent_f32 == 0xffu) {
18
+ if (mantissa_f32 != 0u) {
19
+ return 0x7e00u;
20
+ }
21
+ return sign | 0x7c00u;
22
+ }
23
+
24
+ var exponent_f16 = i32(exponent_f32) - 127 + 15;
25
+ if (exponent_f16 >= 0x1f) {
26
+ return sign | 0x7c00u;
27
+ }
28
+
29
+ if (exponent_f16 <= 0) {
30
+ if (exponent_f16 < -10) {
31
+ return sign;
32
+ }
33
+ let significand = mantissa_f32 | 0x800000u;
34
+ let shift = u32(14 - exponent_f16);
35
+ let halfway = 1u << (shift - 1u);
36
+ let discarded = significand & ((1u << shift) - 1u);
37
+ var fraction = significand >> shift;
38
+ if (discarded > halfway || (discarded == halfway && (fraction & 1u) == 1u)) {
39
+ fraction = fraction + 1u;
40
+ }
41
+ return sign | fraction;
42
+ }
43
+
44
+ let halfway = 1u << 12u;
45
+ let discarded = mantissa_f32 & 0x1fffu;
46
+ var mantissa_f16 = mantissa_f32 >> 13u;
47
+ if (discarded > halfway || (discarded == halfway && (mantissa_f16 & 1u) == 1u)) {
48
+ mantissa_f16 = mantissa_f16 + 1u;
49
+ if (mantissa_f16 == 0x400u) {
50
+ mantissa_f16 = 0u;
51
+ exponent_f16 = exponent_f16 + 1;
52
+ }
53
+ }
54
+ if (exponent_f16 >= 0x1f) {
55
+ return sign | 0x7c00u;
56
+ }
57
+ return sign | (u32(exponent_f16) << 10u) | mantissa_f16;
58
+ }
59
+
60
+ fn widen_f16_bits(value: u32) -> f32 {
61
+ return unpack2x16float(value & 0xffffu).x;
62
+ }
63
+
64
+
65
+ // ONNX GroupNormalization-21 expresses the stash_type=FLOAT16 stage as a
66
+ // graph of f16 tensor operators. One invocation owns a complete group so each
67
+ // intermediate addition and arithmetic stage remains f16.
68
+ @compute @workgroup_size(1, 1, 1)
69
+ fn main(@builtin(workgroup_id) wg: vec3<u32>) {
70
+ let row = wg.x + wg.y * params.rowStride;
71
+ if (row >= params.rows) {
72
+ return;
73
+ }
74
+ let base = row * HIDDEN;
75
+
76
+ // Keep loop-carried stage values as binary16 bits. An f32 carrier could be
77
+ // reassociated through pack/unpack and retain wider accumulation precision.
78
+ var sum = round_f16_bits_rte(0.0);
79
+ var square_sum = round_f16_bits_rte(0.0);
80
+ for (var d = 0u; d < HIDDEN; d = d + 1u) {
81
+ let value = round_f16_bits_rte(f32(x[base + d]));
82
+ sum = round_f16_bits_rte(widen_f16_bits(sum) + widen_f16_bits(value));
83
+ let squared = round_f16_bits_rte(widen_f16_bits(value) * widen_f16_bits(value));
84
+ square_sum = round_f16_bits_rte(widen_f16_bits(square_sum) + widen_f16_bits(squared));
85
+ }
86
+
87
+ let mean = round_f16_bits_rte(widen_f16_bits(sum) / f32(HIDDEN));
88
+ let mean_square = round_f16_bits_rte(widen_f16_bits(square_sum) / f32(HIDDEN));
89
+ let mean_squared = round_f16_bits_rte(widen_f16_bits(mean) * widen_f16_bits(mean));
90
+ let variance = round_f16_bits_rte(widen_f16_bits(mean_square) - widen_f16_bits(mean_squared));
91
+ let epsilon_f16 = round_f16_bits_rte(EPSILON);
92
+ let variance_epsilon = round_f16_bits_rte(widen_f16_bits(variance) + widen_f16_bits(epsilon_f16));
93
+ let stddev = round_f16_bits_rte(sqrt(widen_f16_bits(variance_epsilon)));
94
+ let group = row % GROUPS;
95
+
96
+ for (var d = 0u; d < HIDDEN; d = d + 1u) {
97
+ let index = base + d;
98
+ let channel = group * CPG + d / SPATIAL;
99
+ let value = round_f16_bits_rte(f32(x[index]));
100
+ let centered = round_f16_bits_rte(widen_f16_bits(value) - widen_f16_bits(mean));
101
+ let normalized_f16 = round_f16_bits_rte(widen_f16_bits(centered) / widen_f16_bits(stddev));
102
+ {% if scalar == "f16" %}
103
+ let scaled = round_f16_bits_rte(widen_f16_bits(normalized_f16) * f32(scale[channel]));
104
+ let biased = round_f16_bits_rte(widen_f16_bits(scaled) + f32(bias[channel]));
105
+ y[index] = f16(widen_f16_bits(biased));
106
+ {% else %}
107
+ // The function casts Normalized back to T before its T-typed affine stage.
108
+ // For float32 X, scale and bias must therefore remain float32 operations.
109
+ let normalized = widen_f16_bits(normalized_f16);
110
+ let scaled = fma(normalized, scale[channel], 0.0);
111
+ y[index] = fma(1.0, scaled, bias[channel]);
112
+ {% endif %}
113
+ }
114
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "GroupNormalization",
4
+ "sinceVersion": 21,
5
+ "description": "Applies group normalization to the input: `y = scale * (x - mean) / sqrt(variance + epsilon) + bias`, where mean and variance are computed per instance per group of channels. The number of groups `num_groups` must divide the channel count `C` evenly; when `num_groups == C` this is equivalent to InstanceNormalization, and when `num_groups == 1` it is equivalent to LayerNormalization. The normalization stage supports TensorProto `stash_type` values `1` (float32) and `10` (float16).",
6
+ "inputs": [
7
+ {
8
+ "role": "X",
9
+ "dtype": "T",
10
+ "description": "Input data tensor of shape `(N x C x D1 x ... x Dn)` where `N` is batch size and `C` is the number of channels."
11
+ },
12
+ { "role": "scale", "dtype": "T", "rank": 1, "description": "Scale tensor of shape `(C)`, one value per channel." },
13
+ { "role": "bias", "dtype": "T", "rank": 1, "description": "Bias tensor of shape `(C)`, one value per channel." }
14
+ ],
15
+ "outputs": [
16
+ {
17
+ "role": "Y",
18
+ "dtype": "T",
19
+ "rank": "ranks.X",
20
+ "description": "Normalized output tensor of the same shape as `X`.",
21
+ "shape": "shapes.X"
22
+ }
23
+ ],
24
+ "attributes": { "epsilon": 0.00001, "stash_type": 1 },
25
+ "attributeDescriptions": {
26
+ "epsilon": "Small value added to the variance denominator to avoid division by zero.",
27
+ "num_groups": "Required number of groups to divide the channels into; must be a divisor of `C`.",
28
+ "stash_type": "TensorProto element type used for the normalization stage: `1` computes in float32, while `10` computes in float16. Normalized values are cast back to the input type before scale and bias are applied."
29
+ },
30
+ "attributeConstraints": { "num_groups": { "required": true }, "stash_type": { "values": [1, 10] } },
31
+ "typeConstraints": { "T": ["float32", "float16"] },
32
+ "args": {
33
+ "x": { "kind": "tensor", "semantic": "X", "role": "input" },
34
+ "scale": { "kind": "tensor", "semantic": "scale", "role": "input" },
35
+ "bias": { "kind": "tensor", "semantic": "bias", "role": "input" },
36
+ "y": { "kind": "tensor", "semantic": "Y", "role": "output" }
37
+ },
38
+ "tunables": {
39
+ "WORKGROUP_SIZE": 256,
40
+ "MAX_STATS_SPLITS": 64,
41
+ "STATS_VALUES_PER_SPLIT": 4096,
42
+ "SPLIT_STATS_MIN_HIDDEN": 65536,
43
+ "SPLIT_STATS_MAX_ROWS": 256
44
+ },
45
+ "derive": {
46
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
47
+ "groupAttributesOk": "attrs.num_groups >= 1",
48
+ "groupShapeOk": "groupAttributesOk and f16Ok(dtypes.T) and ranks.X >= 3 and ranks.scale == 1 and ranks.bias == 1 and ranks.Y == ranks.X and sameShape(shapes.Y, shapes.X) and dim(shapes.scale, 0) == dim(shapes.X, 1) and dim(shapes.bias, 0) == dim(shapes.X, 1) and dim(shapes.X, 1) % attrs.num_groups == 0",
49
+ "groupContractOk": "groupShapeOk and attrs.stash_type == onnxDtypeCode(\"float32\")",
50
+ "groupStashF16Ok": "groupShapeOk and attrs.stash_type == onnxDtypeCode(\"float16\")",
51
+ "groupRows": "dim(shapes.X, 0) * attrs.num_groups if groupAttributesOk else 0",
52
+ "groupSpatial": "inner(shapes.X, 1)",
53
+ "groupChannelsPerGroup": "dim(shapes.X, 1) / attrs.num_groups if groupAttributesOk else 0",
54
+ "groupHidden": "groupChannelsPerGroup * groupSpatial",
55
+ "normDeviceWorkgroupCap": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
56
+ "normWorkgroupCap": "max(1, pow2ceil(normDeviceWorkgroupCap + 1) / 2)",
57
+ "groupScalarWorkgroup": "min(normWorkgroupCap, pow2ceil(groupHidden))",
58
+ "groupVec4Workgroup": "min(normWorkgroupCap, pow2ceil(groupHidden / 4))",
59
+ "hasSubgroupId": "device.features.has(\"subgroups\") and device.wgslLanguageFeatures.has(\"subgroup_id\")",
60
+ "groupRowWorkgroupBytes": "normWorkgroupCap * 2 * 4",
61
+ "groupRowCovered": "groupContractOk and groupRowWorkgroupBytes <= device.limits.maxComputeWorkgroupStorageSize",
62
+ "groupSplitCount": "min(tunables.MAX_STATS_SPLITS, device.limits.maxComputeWorkgroupsPerDimension, pow2ceil(ceilDiv(groupHidden, tunables.STATS_VALUES_PER_SPLIT)))",
63
+ "groupPartialBytes": "groupRows * groupSplitCount * 2 * 4",
64
+ "groupSplitCovered": "groupRowCovered and groupRows <= tunables.SPLIT_STATS_MAX_ROWS and groupRows <= device.limits.maxComputeWorkgroupsPerDimension and groupHidden >= tunables.SPLIT_STATS_MIN_HIDDEN and groupPartialBytes <= device.limits.maxStorageBufferBindingSize and groupPartialBytes <= device.limits.maxBufferSize"
65
+ },
66
+ "bindingSets": {
67
+ "norm": [
68
+ {
69
+ "name": "x",
70
+ "arg": "x",
71
+ "semantic": "X",
72
+ "buffer": { "type": "read-only-storage" },
73
+ "elementType": "$ioElement"
74
+ },
75
+ {
76
+ "name": "scale",
77
+ "arg": "scale",
78
+ "semantic": "scale",
79
+ "buffer": { "type": "read-only-storage" },
80
+ "elementType": "$scalar"
81
+ },
82
+ {
83
+ "name": "bias",
84
+ "arg": "bias",
85
+ "semantic": "bias",
86
+ "buffer": { "type": "read-only-storage" },
87
+ "elementType": "$scalar"
88
+ },
89
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$ioElement" },
90
+ {
91
+ "name": "params",
92
+ "semantic": "kernel.params",
93
+ "buffer": { "type": "uniform" },
94
+ "struct": {
95
+ "name": "Params",
96
+ "fields": [
97
+ { "name": "rows", "type": "u32", "value": "groupRows" },
98
+ {
99
+ "name": "rowStride",
100
+ "type": "u32",
101
+ "value": "max(1, min(groupRows, device.limits.maxComputeWorkgroupsPerDimension))"
102
+ }
103
+ ]
104
+ }
105
+ }
106
+ ],
107
+ "splitPartials": [
108
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
109
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "vec2<f32>" },
110
+ {
111
+ "name": "params",
112
+ "semantic": "kernel.params",
113
+ "buffer": { "type": "uniform" },
114
+ "struct": { "name": "Params", "fields": [{ "name": "rows", "type": "u32", "value": "groupRows" }] }
115
+ }
116
+ ],
117
+ "splitApply": [
118
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
119
+ {
120
+ "name": "scale",
121
+ "arg": "scale",
122
+ "semantic": "scale",
123
+ "buffer": { "type": "read-only-storage" },
124
+ "elementType": "$scalar"
125
+ },
126
+ {
127
+ "name": "bias",
128
+ "arg": "bias",
129
+ "semantic": "bias",
130
+ "buffer": { "type": "read-only-storage" },
131
+ "elementType": "$scalar"
132
+ },
133
+ {
134
+ "name": "partials",
135
+ "semantic": "partials",
136
+ "buffer": { "type": "read-only-storage" },
137
+ "elementType": "vec2<f32>"
138
+ },
139
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
140
+ {
141
+ "name": "params",
142
+ "semantic": "kernel.params",
143
+ "buffer": { "type": "uniform" },
144
+ "struct": { "name": "Params", "fields": [{ "name": "rows", "type": "u32", "value": "groupRows" }] }
145
+ }
146
+ ]
147
+ },
148
+ "variants": [
149
+ {
150
+ "id": "group_stash_f16_serial",
151
+ "priority": 1000,
152
+ "when": "groupStashF16Ok",
153
+ "constants": {
154
+ "scalar": "dtypes.T",
155
+ "ioElement": "dtypes.T",
156
+ "usesF16": "dtypes.T == \"f16\"",
157
+ "hiddenSize": "groupHidden",
158
+ "spatial": "groupSpatial",
159
+ "channelsPerGroup": "groupChannelsPerGroup",
160
+ "numGroups": "attrs.num_groups",
161
+ "epsilon": "attrs.epsilon"
162
+ },
163
+ "passes": [
164
+ {
165
+ "id": "main",
166
+ "name": "GroupNormalization.StashF16Serial",
167
+ "shader": "group-normalization-stash-f16-serial.wgsl.jinja",
168
+ "bindings": "norm",
169
+ "dispatch": { "workgroups": "groupRows" }
170
+ }
171
+ ]
172
+ },
173
+ {
174
+ "id": "group_splitk",
175
+ "priority": 120,
176
+ "when": ["groupSplitCovered"],
177
+ "constants": {
178
+ "scalar": "dtypes.T",
179
+ "usesF16": "dtypes.T == \"f16\"",
180
+ "hiddenSize": "groupHidden",
181
+ "spatial": "groupSpatial",
182
+ "channelsPerGroup": "groupChannelsPerGroup",
183
+ "numGroups": "attrs.num_groups",
184
+ "workgroupSize": "normWorkgroupCap",
185
+ "split": "groupSplitCount",
186
+ "epsilon": "attrs.epsilon"
187
+ },
188
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[groupRows * groupSplitCount, 2]" }],
189
+ "passes": [
190
+ {
191
+ "id": "partials",
192
+ "name": "GroupNormalization.SplitKPartials",
193
+ "shader": "group-normalization-splitk-partials.wgsl.jinja",
194
+ "bindings": "splitPartials",
195
+ "dispatch": { "workgroups": "groupRows", "z": "groupSplitCount" }
196
+ },
197
+ {
198
+ "id": "apply",
199
+ "name": "GroupNormalization.SplitKApply",
200
+ "shader": "group-normalization-splitk-apply.wgsl.jinja",
201
+ "bindings": "splitApply",
202
+ "dispatch": { "workgroups": "groupRows", "z": "groupSplitCount" }
203
+ }
204
+ ]
205
+ },
206
+ {
207
+ "id": "group_subgroup_vec4",
208
+ "priority": 110,
209
+ "when": ["groupRowCovered", "groupSpatial % 4 == 0"],
210
+ "constants": {
211
+ "scalar": "dtypes.T",
212
+ "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
213
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\""
214
+ },
215
+ "passes": [
216
+ {
217
+ "id": "main",
218
+ "name": "GroupNormalization.group_subgroup_vec4",
219
+ "source": {
220
+ "shader": "norm-row-stats.wgsl.jinja",
221
+ "inputs": {
222
+ "mode": "\"group\"",
223
+ "vec4": true,
224
+ "scalar": "dtypes.T",
225
+ "usesF16": "dtypes.T == \"f16\"",
226
+ "hidden": "groupHidden",
227
+ "wg": "groupVec4Workgroup",
228
+ "epsilon": "attrs.epsilon",
229
+ "numGroups": "attrs.num_groups",
230
+ "cpg": "groupChannelsPerGroup",
231
+ "hiddenVec": "groupHidden / 4",
232
+ "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
233
+ "spatialVec": "groupSpatial / 4",
234
+ "combineSubgroups": "hasSubgroupId"
235
+ }
236
+ },
237
+ "subgroupCollectivesWidth": "portable",
238
+ "bindings": "norm",
239
+ "dispatch": { "workgroups": "groupRows" }
240
+ }
241
+ ]
242
+ },
243
+ {
244
+ "id": "group_subgroup",
245
+ "priority": 100,
246
+ "when": ["groupRowCovered"],
247
+ "constants": { "scalar": "dtypes.T", "ioElement": "dtypes.T" },
248
+ "passes": [
249
+ {
250
+ "id": "main",
251
+ "name": "GroupNormalization.group_subgroup",
252
+ "source": {
253
+ "shader": "norm-row-stats.wgsl.jinja",
254
+ "inputs": {
255
+ "mode": "\"group\"",
256
+ "vec4": false,
257
+ "scalar": "dtypes.T",
258
+ "usesF16": "dtypes.T == \"f16\"",
259
+ "hidden": "groupHidden",
260
+ "wg": "groupScalarWorkgroup",
261
+ "epsilon": "attrs.epsilon",
262
+ "numGroups": "attrs.num_groups",
263
+ "cpg": "groupChannelsPerGroup",
264
+ "spatial": "groupSpatial",
265
+ "combineSubgroups": "hasSubgroupId"
266
+ }
267
+ },
268
+ "subgroupCollectivesWidth": "portable",
269
+ "bindings": "norm",
270
+ "dispatch": { "workgroups": "groupRows" }
271
+ }
272
+ ]
273
+ }
274
+ ]
275
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.GroupNormalization",
3
+ "id": "_ai_onnx_groupnormalization_webgpu_da50174",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "R60LEBSM13qdMqFSIdwpekWs9JTpBlgud7FNGLzPDdg=",
11
+ "group-normalization-splitk-apply.wgsl.jinja": "7Q93kvTqmCixZN4Z7GpxYdlxwaxiAUMjH7AMKHq8pfc=",
12
+ "group-normalization-splitk-partials.wgsl.jinja": "6FNPr81N83OW3DWYMjJln/B5zUsKTdinV610BRzzRvs=",
13
+ "group-normalization-stash-f16-serial.wgsl.jinja": "Wez9kqS+lzZASbm2BpWZSsHuNTT4rqsus0NiyhmGmvY=",
14
+ "manifest.json": "nWQpLNZpKQYFo8Pkd/yNT3xboHmv9HZJ5S28OKU86TE=",
15
+ "norm-row-stats.wgsl.jinja": "RCAuBcGKN1mg+npi5UTIqD4zDk0+B8DQj6u2TH+4IXU=",
16
+ "test.json": "KbF1vAUrTyrgdXUaq67pwqn41JjEAcHxnO2dImDHUiM="
17
+ }
18
+ },
19
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
20
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.GroupNormalization" }
21
+ }
build/webgpu/norm-row-stats.wgsl.jinja ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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; normalize as
24
+ // (x - mean) / sqrt(variance + EPSILON).
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 NUM_GROUPS: u32 = {{ source.numGroups }}u;
32
+ const CPG: u32 = {{ source.cpg }}u;
33
+ {% if source.vec4 %}
34
+ const SPATIAL_V: u32 = {{ source.spatialVec }}u;
35
+ {% else %}
36
+ const SPATIAL: u32 = {{ source.spatial }}u;
37
+ {% endif %}
38
+
39
+
40
+
41
+ {% if combineSubgroups %}
42
+ var<workgroup> sg_partials: array<vec2<f32>, WG>;
43
+
44
+ fn reduce_pair(value: vec2<f32>{{ reduceThreadParameters }}) -> vec2<f32> {
45
+ let s = vec2<f32>(subgroupAdd(value.x), subgroupAdd(value.y));
46
+ if (num_sg == 1u) {
47
+ return s;
48
+ }
49
+ if (sg_lane == 0u) {
50
+ sg_partials[sg_id] = s;
51
+ }
52
+ workgroupBarrier();
53
+ var total = vec2<f32>(0.0);
54
+ for (var i = 0u; i < num_sg; i++) {
55
+ total += sg_partials[i];
56
+ }
57
+ return total;
58
+ }
59
+ {% else %}
60
+ // Each shared-memory tree reduction deliberately ends with a barrier. It keeps
61
+ // lanes that have read the result from starting a later reduction and
62
+ // overwriting scratch while slower lanes are still reading it.
63
+ var<workgroup> tr0: array<f32, WG>;
64
+ var<workgroup> tr1: array<f32, WG>;
65
+ fn reduce_pair(value: vec2<f32>, tid: u32) -> vec2<f32> {
66
+ tr0[tid] = value.x;
67
+ tr1[tid] = value.y;
68
+ workgroupBarrier();
69
+ var stride: u32 = WG / 2u;
70
+ loop {
71
+ if (stride == 0u) { break; }
72
+ if (tid < stride) {
73
+ tr0[tid] = tr0[tid] + tr0[tid + stride];
74
+ tr1[tid] = tr1[tid] + tr1[tid + stride];
75
+ }
76
+ stride = stride / 2u;
77
+ workgroupBarrier();
78
+ }
79
+ let reduced = vec2<f32>(tr0[0], tr1[0]);
80
+ workgroupBarrier();
81
+ return reduced;
82
+ }
83
+ {% endif %}
84
+
85
+ @compute @workgroup_size(WG, 1, 1)
86
+ fn main(
87
+ @builtin(workgroup_id) wg_id: vec3<u32>,
88
+ @builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups %},
89
+ @builtin(subgroup_invocation_id) sg_lane: u32,
90
+ @builtin(subgroup_id) sg_id: u32,
91
+ @builtin(num_subgroups) num_sg: u32{% endif %}
92
+ ) {
93
+ let row = wg_id.x + wg_id.y * params.rowStride;
94
+ if (row >= params.rows) {
95
+ return;
96
+ }
97
+ let tid = lid.x;
98
+ {% if source.vec4 and not scalarIo %}
99
+ let base = row * HIDDEN_V;
100
+ {% else %}
101
+ let base = row * HIDDEN;
102
+ {% endif %}
103
+
104
+ {% if source.vec4 %}
105
+ let shift = f32(x[base].x);
106
+ {% else %}
107
+ let shift = f32(x[base]);
108
+ {% endif %}
109
+
110
+ var acc = vec2<f32>(0.0, 0.0);
111
+ {% if source.vec4 %}
112
+ for (var i = tid; i < HIDDEN_V; i = i + WG) {
113
+ let v = vec4<f32>(x[base + i]);
114
+ let d = v - vec4<f32>(shift);
115
+ acc.x = acc.x + d.x + d.y + d.z + d.w;
116
+ acc.y = acc.y + dot(d, d);
117
+ }
118
+ {% else %}
119
+ for (var i = tid; i < HIDDEN; i = i + WG) {
120
+ let v = f32(x[base + i]);
121
+ let d = v - shift;
122
+ acc.x = acc.x + d;
123
+ acc.y = acc.y + d * d;
124
+ }
125
+ {% endif %}
126
+
127
+ let totals = reduce_pair(acc{{ reduceThreadArguments }});
128
+
129
+ let mean_d = totals.x / f32(HIDDEN);
130
+ let variance = max(totals.y / f32(HIDDEN) - mean_d * mean_d, 0.0);
131
+ let denom = sqrt(variance + EPSILON);
132
+ let row_mean = shift + mean_d;
133
+ let g_ch_base = (row % NUM_GROUPS) * CPG;
134
+
135
+ {% if source.vec4 %}
136
+ for (var i = tid; i < HIDDEN_V; i = i + WG) {
137
+ let idx = base + i;
138
+ let v = vec4<f32>(x[idx]);
139
+ let ch = g_ch_base + i / SPATIAL_V;
140
+ let normed = (v - vec4<f32>(row_mean)) / vec4<f32>(denom);
141
+ y[idx] = {{ source.vecType }}(normed * vec4<f32>(f32(scale[ch])) + vec4<f32>(f32(bias[ch])));
142
+ }
143
+ {% else %}
144
+ for (var i = tid; i < HIDDEN; i = i + WG) {
145
+ let idx = base + i;
146
+ let v = f32(x[idx]);
147
+ let ch = g_ch_base + i / SPATIAL;
148
+ let normed = (v - row_mean) / denom;
149
+ y[idx] = {{ source.scalar }}(normed * f32(scale[ch]) + f32(bias[ch]));
150
+ }
151
+ {% endif %}
152
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,711 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.GroupNormalization",
3
+ "fixtureArrays": {
4
+ "batch2_groups2_f32_input_x": [1, 2, 3, 4, 5, 6, 7, 8, -1, -2, -3, -4, 8, 7, 6, 5],
5
+ "ort_rank3_equivalent_instance_norm_groups_equal_channels_input_x": [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],
6
+ "ort_rank3_group_size_n_input_x": [-1.1258, -1.1524, -0.2506, -0.4339, 0.8487, 0.692, -0.316, -2.1152, 0.3223, -1.2633, 0.35, 0.3081, 0.1198, 1.2377, 1.1168, -0.2473, -1.3527, -1.6959, 0.5667, 0.7935, 0.5988, -1.5551, -0.3414, 1.853, 0.7502, -0.5855, -0.1734, 0.1835, 1.3894, 1.5863, 0.9463, -0.8437, -0.6136, 0.0316, -0.4927, 0.2484, 0.4397, 0.1124, 0.6408, 0.4412, -0.1023, 0.7924, -0.2897, 0.0525, 0.5229, 2.3022, -1.4689, -1.5867],
7
+ "ort_rank3_group_size_n_output_y": [-0.759, -0.7848, 0.0914, -0.0867, 1.1595, 1.0073, 0.0278, -1.7203, 0.648, -0.8926, 0.6749, 0.6343, 0.0232, 0.9274, 0.8296, -0.2738, -1.1679, -1.4456, 0.3846, 0.5681, 0.4107, -1.3317, -0.3499, 1.4252, 0.5772, -0.8298, -0.3957, -0.0198, 1.2505, 1.458, 0.7838, -1.1017, -0.8594, -0.1798, -0.732, 0.0486, 0.2541, -0.0377, 0.4334, 0.2554, -0.2291, 0.5686, -0.3962, -0.0911, 0.3282, 1.9145, -1.4475, -1.5525],
8
+ "onnx_backend_group_normalization_example_input_x": [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]
9
+ },
10
+ "cases": [
11
+ {
12
+ "name": "subgroup_vec4_2x8x16x16_g2",
13
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
14
+ "inputs": {
15
+ "x": {
16
+ "dtype": "float32",
17
+ "shape": [2, 8, 16, 16],
18
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21 }
19
+ },
20
+ "scale": {
21
+ "dtype": "float32",
22
+ "shape": [8],
23
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.11, "scale": 0.5 }
24
+ },
25
+ "bias": {
26
+ "dtype": "float32",
27
+ "shape": [8],
28
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.19, "scale": 0.25 }
29
+ }
30
+ },
31
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 8, 16, 16], "tolerance": 0.000002 } }
32
+ },
33
+ {
34
+ "name": "subgroup_scalar_1x4x7x7_g2",
35
+ "attrs": { "num_groups": 2 },
36
+ "inputs": {
37
+ "x": {
38
+ "dtype": "float32",
39
+ "shape": [1, 4, 7, 7],
40
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.23 }
41
+ },
42
+ "scale": {
43
+ "dtype": "float32",
44
+ "shape": [4],
45
+ "data": { "kind": "fillFloat32", "sinStep": 0.31, "cosStep": 0.07, "scale": 0.4 }
46
+ },
47
+ "bias": {
48
+ "dtype": "float32",
49
+ "shape": [4],
50
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13, "scale": 0.2 }
51
+ }
52
+ },
53
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 7, 7], "tolerance": 0.000002 } }
54
+ },
55
+ {
56
+ "name": "f32_tiny_variance_epsilon_zero_gpu_gap",
57
+ "skipGpu": {
58
+ "category": "permanent",
59
+ "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; each group's subnormal variance collapses to zero so normalization is non-finite."
60
+ },
61
+ "provenance": {
62
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
63
+ "test": "GroupNormalizationOpTest.Equivalent_InstanceNorm_G_C",
64
+ "notes": "Valid epsilon=0 edge: each group has tiny normal centered values with positive subnormal variance, so normalization should be finite."
65
+ },
66
+ "attrs": { "num_groups": 2, "epsilon": 0 },
67
+ "inputs": {
68
+ "x": {
69
+ "dtype": "float32",
70
+ "shape": [1, 2, 1, 2],
71
+ "data": { "kind": "values", "values": [1e-20, -1e-20, 2e-20, -2e-20] }
72
+ },
73
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.0] } },
74
+ "bias": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 0.0] } }
75
+ },
76
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 1, 2], "tolerance": 0.00001 } }
77
+ },
78
+ {
79
+ "name": "f32_subnormal_scale_instance_groups_gpu_gap",
80
+ "skipGpu": {
81
+ "category": "permanent",
82
+ "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-channel scale collapses to zero, losing the tiny affine output."
83
+ },
84
+ "provenance": {
85
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
86
+ "test": "GroupNormalizationOpTest.Equivalent_InstanceNorm_G_C",
87
+ "notes": "Subnormal per-channel scale is valid and should preserve tiny affine outputs after otherwise ordinary normalization."
88
+ },
89
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
90
+ "inputs": {
91
+ "x": {
92
+ "dtype": "float32",
93
+ "shape": [1, 2, 1, 2],
94
+ "data": { "kind": "values", "values": [-1.0, 1.0, -2.0, 2.0] }
95
+ },
96
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1e-40, -2e-40] } },
97
+ "bias": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 0.0] } }
98
+ },
99
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 1, 2], "tolerance": 1e-44 } }
100
+ },
101
+ {
102
+ "name": "f32_subnormal_scale_grouped_spatial_vec4_gpu_gap",
103
+ "skipGpu": {
104
+ "category": "permanent",
105
+ "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-channel scale collapses to zero (grouped spatial vec4 path)."
106
+ },
107
+ "provenance": {
108
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
109
+ "test": "GroupNormalizationOpTest.GroupSize_N",
110
+ "notes": "Larger grouped companion: subnormal scales should survive across channels and spatial positions."
111
+ },
112
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
113
+ "inputs": {
114
+ "x": {
115
+ "dtype": "float32",
116
+ "shape": [1, 4, 2, 2],
117
+ "data": {
118
+ "kind": "values",
119
+ "values": [-3.0, -1.0, 1.0, 3.0, -2.0, 0.0, 2.0, 4.0, 4.0, 2.0, 0.0, -2.0, 3.0, 1.0, -1.0, -3.0]
120
+ }
121
+ },
122
+ "scale": {
123
+ "dtype": "float32",
124
+ "shape": [4],
125
+ "data": { "kind": "values", "values": [1e-40, -2e-40, 3e-40, -4e-40] }
126
+ },
127
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] } }
128
+ },
129
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 2, 2], "tolerance": 1e-44 } }
130
+ },
131
+ {
132
+ "name": "nchw_groups2_f32",
133
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
134
+ "inputs": {
135
+ "x": {
136
+ "dtype": "float32",
137
+ "shape": [1, 4, 1, 2],
138
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
139
+ },
140
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.5, 0.5, 2.0] } },
141
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.25, -0.5, 1.0] } }
142
+ },
143
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 1, 2] } },
144
+ "tolerance": 0.00001
145
+ },
146
+ {
147
+ "name": "instance_equivalent_f16",
148
+ "attrs": { "num_groups": 4, "epsilon": 0.00001 },
149
+ "inputs": {
150
+ "x": {
151
+ "dtype": "float16",
152
+ "shape": [1, 4, 1, 2],
153
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
154
+ },
155
+ "scale": { "dtype": "float16", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0] } },
156
+ "bias": { "dtype": "float16", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] } }
157
+ },
158
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 4, 1, 2] } },
159
+ "tolerance": 0.002
160
+ },
161
+ {
162
+ "name": "layernorm_equivalent_one_group_f32",
163
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
164
+ "inputs": {
165
+ "x": {
166
+ "dtype": "float32",
167
+ "shape": [1, 4, 2, 1],
168
+ "data": { "kind": "values", "values": [-3.0, -1.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0] }
169
+ },
170
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 0.5, -1.0, 2.0] } },
171
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 1.0, -2.0, 3.0] } }
172
+ },
173
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 2, 1], "tolerance": 0.00001 } }
174
+ },
175
+ {
176
+ "name": "zero_variance_bias_only",
177
+ "attrs": { "num_groups": 2, "epsilon": 0.001 },
178
+ "inputs": {
179
+ "x": {
180
+ "dtype": "float32",
181
+ "shape": [1, 4, 1, 2],
182
+ "data": { "kind": "values", "values": [7.0, 7.0, 7.0, 7.0, -3.0, -3.0, -3.0, -3.0] }
183
+ },
184
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [10.0, -2.0, 3.0, 4.0] } },
185
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.5, -1.0, 2.0, -3.0] } }
186
+ },
187
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 1, 2], "tolerance": 0.000001 } }
188
+ },
189
+ {
190
+ "name": "batch2_groups2_f32",
191
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
192
+ "inputs": {
193
+ "x": {
194
+ "dtype": "float32",
195
+ "shape": [2, 4, 1, 2],
196
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/batch2_groups2_f32_input_x" } }
197
+ },
198
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.5, 0.5, 2.0] } },
199
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.25, -0.5, 1.0] } }
200
+ },
201
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4, 1, 2], "tolerance": 0.00001 } }
202
+ },
203
+ {
204
+ "name": "rank5_ncdhw_groups2_f32",
205
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
206
+ "inputs": {
207
+ "x": {
208
+ "dtype": "float32",
209
+ "shape": [1, 4, 2, 1, 2],
210
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/batch2_groups2_f32_input_x" } }
211
+ },
212
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.5, 0.5, 2.0] } },
213
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.25, -0.5, 1.0] } }
214
+ },
215
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 2, 1, 2], "tolerance": 0.00001 } }
216
+ },
217
+ {
218
+ "name": "ort_rank3_equivalent_instance_norm_groups_equal_channels",
219
+ "provenance": {
220
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
221
+ "test": "GroupNormalizationOpTest.Equivalent_InstanceNorm_G_C"
222
+ },
223
+ "attrs": { "num_groups": 3, "epsilon": 0.3 },
224
+ "inputs": {
225
+ "x": {
226
+ "dtype": "float32",
227
+ "shape": [2, 3, 4],
228
+ "data": {
229
+ "kind": "values",
230
+ "values": { "$ref": "#/fixtureArrays/ort_rank3_equivalent_instance_norm_groups_equal_channels_input_x" }
231
+ }
232
+ },
233
+ "scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, 1.0, 1.0] } },
234
+ "bias": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
235
+ },
236
+ "outputs": {
237
+ "y": {
238
+ "dtype": "float32",
239
+ "shape": [2, 3, 4],
240
+ "tolerance": 0.0001,
241
+ "data": {
242
+ "kind": "values",
243
+ "values": [-0.56495477, 1.48930046, -1.13334329, 0.20899761, 1.46688162, -0.98600774, -0.79911913, 0.31824524, 0.57370438, 0.42193634, 0.6525492, -1.64818992, -0.92380346, -0.60808484, 0.04711878, 1.48476953, -0.14644464, -0.82262872, -0.66852817, 1.63760153, -1.65898662, 0.27618144, 0.64840618, 0.734399]
244
+ }
245
+ }
246
+ }
247
+ },
248
+ {
249
+ "name": "ort_rank3_equivalent_instance_norm_groups_equal_channels_f16",
250
+ "provenance": {
251
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
252
+ "test": "GroupNormalizationOpTest.Equivalent_InstanceNorm_G_C",
253
+ "notes": "MLFloat16 typed variant of the ORT GroupNormalization instance-normalization case."
254
+ },
255
+ "attrs": { "num_groups": 3, "epsilon": 0.3 },
256
+ "inputs": {
257
+ "x": {
258
+ "dtype": "float16",
259
+ "shape": [2, 3, 4],
260
+ "data": {
261
+ "kind": "values",
262
+ "values": { "$ref": "#/fixtureArrays/ort_rank3_equivalent_instance_norm_groups_equal_channels_input_x" }
263
+ }
264
+ },
265
+ "scale": { "dtype": "float16", "shape": [3], "data": { "kind": "values", "values": [1.0, 1.0, 1.0] } },
266
+ "bias": { "dtype": "float16", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
267
+ },
268
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 3, 4], "tolerance": 0.003 } }
269
+ },
270
+ {
271
+ "name": "ort_rank3_equivalent_layer_norm_one_group",
272
+ "provenance": {
273
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
274
+ "test": "GroupNormalizationOpTest.Equivalent_LayerNorm_G_1"
275
+ },
276
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
277
+ "inputs": {
278
+ "x": {
279
+ "dtype": "float32",
280
+ "shape": [1, 2, 3],
281
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
282
+ },
283
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.0] } },
284
+ "bias": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [2.0, 1.0] } }
285
+ },
286
+ "outputs": {
287
+ "y": {
288
+ "dtype": "float32",
289
+ "shape": [1, 2, 3],
290
+ "tolerance": 0.0001,
291
+ "data": { "kind": "values", "values": [0.5361, 1.1216, 1.7072, 1.2928, 1.8783, 2.4638] }
292
+ }
293
+ }
294
+ },
295
+ {
296
+ "name": "ort_rank3_equivalent_layer_norm_one_group_f16",
297
+ "provenance": {
298
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
299
+ "test": "GroupNormalizationOpTest.Equivalent_LayerNorm_G_1",
300
+ "notes": "MLFloat16 typed variant of ORT's GroupNormalization-as-LayerNorm case."
301
+ },
302
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
303
+ "inputs": {
304
+ "x": {
305
+ "dtype": "float16",
306
+ "shape": [1, 2, 3],
307
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
308
+ },
309
+ "scale": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.0] } },
310
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [2.0, 1.0] } }
311
+ },
312
+ "outputs": {
313
+ "y": {
314
+ "dtype": "float16",
315
+ "shape": [1, 2, 3],
316
+ "tolerance": 0.001,
317
+ "data": { "kind": "values", "values": [0.5361, 1.1216, 1.7072, 1.2928, 1.8783, 2.4638] }
318
+ }
319
+ }
320
+ },
321
+ {
322
+ "name": "ort_rank3_group_size_n",
323
+ "provenance": {
324
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
325
+ "test": "GroupNormalizationOpTest.GroupSize_N"
326
+ },
327
+ "attrs": { "num_groups": 2, "epsilon": 0.3 },
328
+ "inputs": {
329
+ "x": {
330
+ "dtype": "float32",
331
+ "shape": [2, 6, 4],
332
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_rank3_group_size_n_input_x" } }
333
+ },
334
+ "scale": { "dtype": "float32", "shape": [6], "data": { "kind": "constant", "value": 1.0 } },
335
+ "bias": { "dtype": "float32", "shape": [6], "data": { "kind": "constant", "value": 0.0 } }
336
+ },
337
+ "outputs": {
338
+ "y": {
339
+ "dtype": "float32",
340
+ "shape": [2, 6, 4],
341
+ "tolerance": 0.0001,
342
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_rank3_group_size_n_output_y" } }
343
+ }
344
+ }
345
+ },
346
+ {
347
+ "name": "ort_rank3_group_size_n_f16",
348
+ "provenance": {
349
+ "source": "onnxruntime/test/providers/cpu/nn/group_norm_op_test.cc",
350
+ "test": "GroupNormalizationOpTest.GroupSize_N",
351
+ "notes": "MLFloat16 typed variant of ORT's six-channel, two-group normalization case; ORT uses 0.005 output tolerance for the fp16 typed test."
352
+ },
353
+ "attrs": { "num_groups": 2, "epsilon": 0.3 },
354
+ "inputs": {
355
+ "x": {
356
+ "dtype": "float16",
357
+ "shape": [2, 6, 4],
358
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_rank3_group_size_n_input_x" } }
359
+ },
360
+ "scale": { "dtype": "float16", "shape": [6], "data": { "kind": "constant", "value": 1.0 } },
361
+ "bias": { "dtype": "float16", "shape": [6], "data": { "kind": "constant", "value": 0.0 } }
362
+ },
363
+ "outputs": {
364
+ "y": {
365
+ "dtype": "float16",
366
+ "shape": [2, 6, 4],
367
+ "tolerance": 0.005,
368
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_rank3_group_size_n_output_y" } }
369
+ }
370
+ }
371
+ },
372
+ {
373
+ "name": "onnx_backend_group_normalization_example",
374
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_group_normalization_example" },
375
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
376
+ "inputs": {
377
+ "x": {
378
+ "dtype": "float32",
379
+ "shape": [3, 4, 2, 2],
380
+ "data": {
381
+ "kind": "values",
382
+ "values": { "$ref": "#/fixtureArrays/onnx_backend_group_normalization_example_input_x" }
383
+ }
384
+ },
385
+ "scale": {
386
+ "dtype": "float32",
387
+ "shape": [4],
388
+ "data": {
389
+ "kind": "values",
390
+ "values": [-1.6138978004455566, -0.21274028718471527, -0.8954665660858154, 0.38690251111984253]
391
+ }
392
+ },
393
+ "bias": {
394
+ "dtype": "float32",
395
+ "shape": [4],
396
+ "data": {
397
+ "kind": "values",
398
+ "values": [-0.5108051300048828, -1.18063223361969, -0.02818222902715206, 0.4283318817615509]
399
+ }
400
+ }
401
+ },
402
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 2, 2], "tolerance": 0.00005 } }
403
+ },
404
+ {
405
+ "name": "onnx_backend_group_normalization_epsilon",
406
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_group_normalization_epsilon" },
407
+ "attrs": { "num_groups": 2, "epsilon": 0.009999999776482582 },
408
+ "inputs": {
409
+ "x": {
410
+ "dtype": "float32",
411
+ "shape": [3, 4, 2, 2],
412
+ "data": {
413
+ "kind": "values",
414
+ "values": { "$ref": "#/fixtureArrays/onnx_backend_group_normalization_example_input_x" }
415
+ }
416
+ },
417
+ "scale": {
418
+ "dtype": "float32",
419
+ "shape": [4],
420
+ "data": {
421
+ "kind": "values",
422
+ "values": [-1.6138978004455566, -0.21274028718471527, -0.8954665660858154, 0.38690251111984253]
423
+ }
424
+ },
425
+ "bias": {
426
+ "dtype": "float32",
427
+ "shape": [4],
428
+ "data": {
429
+ "kind": "values",
430
+ "values": [-0.5108051300048828, -1.18063223361969, -0.02818222902715206, 0.4283318817615509]
431
+ }
432
+ }
433
+ },
434
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 2, 2], "tolerance": 0.00005 } }
435
+ },
436
+ {
437
+ "name": "empty_zero_dim",
438
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
439
+ "inputs": {
440
+ "x": { "dtype": "float32", "shape": [0, 8, 16, 16], "data": { "kind": "values", "values": [] } },
441
+ "scale": { "dtype": "float32", "shape": [8], "data": { "kind": "constant", "value": 1.0 } },
442
+ "bias": { "dtype": "float32", "shape": [8], "data": { "kind": "constant", "value": 0.0 } }
443
+ },
444
+ "outputs": { "y": { "dtype": "float32", "shape": [0, 8, 16, 16], "tolerance": 0 } }
445
+ },
446
+ {
447
+ "name": "empty_zero_dim_f16",
448
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
449
+ "inputs": {
450
+ "x": { "dtype": "float16", "shape": [0, 8, 16, 16], "data": { "kind": "values", "values": [] } },
451
+ "scale": { "dtype": "float16", "shape": [8], "data": { "kind": "constant", "value": 1.0 } },
452
+ "bias": { "dtype": "float16", "shape": [8], "data": { "kind": "constant", "value": 0.0 } }
453
+ },
454
+ "outputs": { "y": { "dtype": "float16", "shape": [0, 8, 16, 16], "tolerance": 0 } }
455
+ },
456
+ {
457
+ "name": "empty_zero_channels",
458
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
459
+ "inputs": {
460
+ "x": { "dtype": "float32", "shape": [2, 0, 4, 4], "data": { "kind": "values", "values": [] } },
461
+ "scale": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } },
462
+ "bias": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
463
+ },
464
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 0, 4, 4], "tolerance": 0 } }
465
+ },
466
+ {
467
+ "name": "f16_instance_norm_4d_vec4_1x16x8x8_g16",
468
+ "attrs": { "num_groups": 16, "epsilon": 0.00001 },
469
+ "inputs": {
470
+ "x": {
471
+ "dtype": "float16",
472
+ "shape": [1, 16, 8, 8],
473
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.19, "scale": 1.5 }
474
+ },
475
+ "scale": {
476
+ "dtype": "float16",
477
+ "shape": [16],
478
+ "data": { "kind": "fillFloat32", "sinStep": 0.27, "cosStep": 0.13, "scale": 0.5, "offset": 1.0 }
479
+ },
480
+ "bias": {
481
+ "dtype": "float16",
482
+ "shape": [16],
483
+ "data": { "kind": "fillFloat32", "sinStep": 0.09, "cosStep": 0.17, "scale": 0.25 }
484
+ }
485
+ },
486
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 16, 8, 8], "tolerance": 0.02 } }
487
+ },
488
+ {
489
+ "name": "f16_groupnorm_4d_vec4_cpg4_1x32x14x14_g8",
490
+ "attrs": { "num_groups": 8, "epsilon": 0.00001 },
491
+ "inputs": {
492
+ "x": {
493
+ "dtype": "float16",
494
+ "shape": [1, 32, 14, 14],
495
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.23, "scale": 2.0 }
496
+ },
497
+ "scale": {
498
+ "dtype": "float16",
499
+ "shape": [32],
500
+ "data": { "kind": "fillFloat32", "sinStep": 0.31, "cosStep": 0.07, "scale": 0.4, "offset": 1.0 }
501
+ },
502
+ "bias": {
503
+ "dtype": "float16",
504
+ "shape": [32],
505
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.29, "scale": 0.3 }
506
+ }
507
+ },
508
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 32, 14, 14], "tolerance": 0.03 } }
509
+ },
510
+ {
511
+ "name": "rank6_vec4_g2_distinct_affine",
512
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
513
+ "inputs": {
514
+ "x": {
515
+ "dtype": "float32",
516
+ "shape": [1, 4, 2, 2, 2, 2],
517
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21, "scale": 1.5 }
518
+ },
519
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, -2.0, 0.5, 3.0] } },
520
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.25, -1.0, 2.0, -0.5] } }
521
+ },
522
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 2, 2, 2, 2], "tolerance": 0.00002 } }
523
+ },
524
+ {
525
+ "name": "vec4_layernorm_equiv_g1_distinct_per_channel_affine",
526
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
527
+ "inputs": {
528
+ "x": {
529
+ "dtype": "float32",
530
+ "shape": [1, 8, 4, 4],
531
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.23, "scale": 2.0 }
532
+ },
533
+ "scale": {
534
+ "dtype": "float32",
535
+ "shape": [8],
536
+ "data": { "kind": "values", "values": [1.0, -2.0, 0.5, 3.0, -1.5, 2.5, -0.5, 4.0] }
537
+ },
538
+ "bias": {
539
+ "dtype": "float32",
540
+ "shape": [8],
541
+ "data": { "kind": "values", "values": [0.0, 1.0, -0.5, 2.0, -1.0, 0.5, 3.0, -2.0] }
542
+ }
543
+ },
544
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 8, 4, 4], "tolerance": 0.00002 } }
545
+ },
546
+ {
547
+ "name": "scalar_layernorm_equiv_g1_distinct_affine_odd_spatial",
548
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
549
+ "inputs": {
550
+ "x": {
551
+ "dtype": "float32",
552
+ "shape": [1, 8, 5, 5],
553
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.29, "scale": 1.75 }
554
+ },
555
+ "scale": {
556
+ "dtype": "float32",
557
+ "shape": [8],
558
+ "data": { "kind": "values", "values": [1.0, -2.0, 0.5, 3.0, -1.5, 2.5, -0.5, 4.0] }
559
+ },
560
+ "bias": {
561
+ "dtype": "float32",
562
+ "shape": [8],
563
+ "data": { "kind": "values", "values": [0.0, 1.0, -0.5, 2.0, -1.0, 0.5, 3.0, -2.0] }
564
+ }
565
+ },
566
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 8, 5, 5], "tolerance": 0.00002 } }
567
+ },
568
+ {
569
+ "name": "vec4_instance_equiv_gEqC_distinct_scale_per_channel",
570
+ "attrs": { "num_groups": 6, "epsilon": 0.00001 },
571
+ "inputs": {
572
+ "x": {
573
+ "dtype": "float32",
574
+ "shape": [2, 6, 4, 4],
575
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.07, "scale": 1.25 }
576
+ },
577
+ "scale": {
578
+ "dtype": "float32",
579
+ "shape": [6],
580
+ "data": { "kind": "values", "values": [1.0, -2.0, 0.5, 3.0, -1.5, 2.5] }
581
+ },
582
+ "bias": {
583
+ "dtype": "float32",
584
+ "shape": [6],
585
+ "data": { "kind": "values", "values": [0.0, 1.0, -0.5, 2.0, -1.0, 0.5] }
586
+ }
587
+ },
588
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 6, 4, 4], "tolerance": 0.00002 } }
589
+ },
590
+ {
591
+ "name": "group_splitk_layernorm_equiv_65536",
592
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
593
+ "inputs": {
594
+ "x": {
595
+ "dtype": "float32",
596
+ "shape": [1, 16, 64, 64],
597
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5 }
598
+ },
599
+ "scale": { "dtype": "float32", "shape": [16], "data": { "kind": "constant", "value": 1.0 } },
600
+ "bias": { "dtype": "float32", "shape": [16], "data": { "kind": "constant", "value": 0.0 } }
601
+ },
602
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 64, 64], "tolerance": 0.0002 } }
603
+ },
604
+ {
605
+ "name": "rank7_spatial",
606
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
607
+ "inputs": {
608
+ "x": {
609
+ "dtype": "float32",
610
+ "shape": [1, 4, 1, 2, 1, 2, 2],
611
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.29, "scale": 2.0 }
612
+ },
613
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 0.5, 1.5, 2.0] } },
614
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.25, -0.5, 1.0] } }
615
+ },
616
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 1, 2, 1, 2, 2], "tolerance": 0.000002 } }
617
+ },
618
+ {
619
+ "name": "onnx21_f16_stash_float16_rounding",
620
+ "provenance": {
621
+ "source": "onnx/defs/nn/defs.cc GroupNormalization-21 function body",
622
+ "notes": "TensorProto FLOAT16 stash (10). Sequential f16 stage arithmetic produces outputs separated by multiple f16 ULPs from the default float32-stash route."
623
+ },
624
+ "requires": { "features": ["shader-f16"] },
625
+ "attrs": { "num_groups": 1, "epsilon": 0.00001, "stash_type": 10 },
626
+ "inputs": {
627
+ "x": {
628
+ "dtype": "float16",
629
+ "shape": [1, 2, 1, 2],
630
+ "data": { "kind": "values", "values": [-3.650390625, -2.51171875, -3.216796875, -2.568359375] }
631
+ },
632
+ "scale": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [1.6484375, 3.365234375] } },
633
+ "bias": {
634
+ "dtype": "float16",
635
+ "shape": [2],
636
+ "data": { "kind": "values", "values": [-0.26123046875, 1.74609375] }
637
+ }
638
+ },
639
+ "outputs": {
640
+ "y": {
641
+ "dtype": "float16",
642
+ "shape": [1, 2, 1, 2],
643
+ "tolerance": 0.002,
644
+ "relTolerance": 0,
645
+ "data": { "kind": "values", "values": [-2.59375, 1.41796875, 0.1025390625, 4.765625] }
646
+ }
647
+ }
648
+ },
649
+ {
650
+ "name": "onnx21_f32_stash_float16_rounding",
651
+ "provenance": {
652
+ "source": "onnx/defs/nn/defs.cc GroupNormalization-21 function body",
653
+ "notes": "TensorProto FLOAT16 stash (10) with float32 T. Non-f16 input and affine values expose both the cast into float16 stage one and the required cast back to float32 before scale and bias."
654
+ },
655
+ "requires": { "features": ["shader-f16"] },
656
+ "attrs": { "num_groups": 1, "epsilon": 0.00001, "stash_type": 10 },
657
+ "inputs": {
658
+ "x": {
659
+ "dtype": "float32",
660
+ "shape": [1, 2, 1, 2],
661
+ "data": { "kind": "values", "values": [-3.6501, -2.5113, -3.2172, -2.5687] }
662
+ },
663
+ "scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.6483, 3.3657] } },
664
+ "bias": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [-0.2612, 1.7463] } }
665
+ },
666
+ "outputs": {
667
+ "y": {
668
+ "dtype": "float32",
669
+ "shape": [1, 2, 1, 2],
670
+ "tolerance": 0.000001,
671
+ "relTolerance": 0,
672
+ "data": {
673
+ "kind": "values",
674
+ "values": [-2.593608856201172, 1.417683720588684, 0.10207010805606842, 4.7668843269348145]
675
+ }
676
+ }
677
+ }
678
+ },
679
+ {
680
+ "name": "rank8_spatial",
681
+ "attrs": { "num_groups": 2, "epsilon": 0.00001 },
682
+ "inputs": {
683
+ "x": {
684
+ "dtype": "float32",
685
+ "shape": [1, 4, 1, 2, 1, 2, 2, 2],
686
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27 }
687
+ },
688
+ "scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 0.5, -1.0, 2.0] } },
689
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.25, -0.5, 1.0] } }
690
+ },
691
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 1, 2, 1, 2, 2, 2], "tolerance": 0.000002 } }
692
+ },
693
+ {
694
+ "name": "group_splitk_layernorm_equiv_65536_f16",
695
+ "provenance": {
696
+ "notes": "float16 twin of the split-K group reduction. Only f32 cases reached the split-K partial and apply shaders, so neither had ever emitted its f16 declaration."
697
+ },
698
+ "attrs": { "num_groups": 1, "epsilon": 0.00001 },
699
+ "inputs": {
700
+ "x": {
701
+ "dtype": "float16",
702
+ "shape": [1, 16, 64, 64],
703
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5 }
704
+ },
705
+ "scale": { "dtype": "float16", "shape": [16], "data": { "kind": "constant", "value": 1.0 } },
706
+ "bias": { "dtype": "float16", "shape": [16], "data": { "kind": "constant", "value": 0.0 } }
707
+ },
708
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 16, 64, 64], "tolerance": 0.02 } }
709
+ }
710
+ ]
711
+ }