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

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,17 +18,17 @@ See the [ONNX `InstanceNormalization` spec](https://onnx.ai/onnx/operators/onnx_
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
 
@@ -50,7 +50,7 @@ Some implementation variants require `subgroups`. These are route-specific capab
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
@@ -62,10 +62,14 @@ Some implementation variants require `subgroups`. These are route-specific capab
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
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `input` | | `T` | — | — | Input tensor of shape `(N x C x D1 x ... x Dn)`; at least 3-D. | required |
24
+ | `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 | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- |
31
+ | `output` | `T` | same as `input` | same as `input` | Normalized output tensor; same shape as the input. | required |
32
 
33
  ## Attributes
34
 
 
50
 
51
  ## Files
52
 
53
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, 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
 
62
 
63
  ## Use with `@huggingface/kernels`
64
 
65
+ ```sh
66
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
67
+ ```
68
+
69
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
70
 
71
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
72
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
73
 
74
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
75
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.InstanceNormalization",
3
  "cases": [
4
  {
5
  "name": "nchw_4x64x128x128",
@@ -209,7 +208,7 @@
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 },
@@ -233,7 +232,7 @@
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 },
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "nchw_4x64x128x128",
 
208
  "name": "splitk-priority-cliff-c256-256x256",
209
  "preset": "stress",
210
  "provenance": {
211
+ "source": "synthetic benchmark",
212
  "notes": "Realistic 16.8M-element feature map that pins the selector boundary between plane_subgroup_vec4 and plane_splitk."
213
  },
214
  "vars": { "dtype": "float32", "batch": 1, "channels": 256, "spatial": 65536 },
 
232
  "name": "splitk-priority-cliff-c32-512x512",
233
  "preset": "stress",
234
  "provenance": {
235
+ "source": "synthetic benchmark",
236
  "notes": "Realistic 8.4M-element high-resolution feature map that pins the selector boundary between plane_subgroup_vec4 and plane_splitk."
237
  },
238
  "vars": { "dtype": "float32", "batch": 1, "channels": 32, "spatial": 262144 },
build/webgpu/instance-normalization-apply.wgsl.jinja CHANGED
@@ -2,9 +2,6 @@
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 "" %}
@@ -16,11 +13,11 @@ enable f16;
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
  }
 
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
  {% set LOAD_OPEN = "vec4<f32>(" if usesF16 else "" %}
6
  {% set LOAD_CLOSE = ")" if usesF16 else "" %}
7
  {% set STORE_OPEN = "vec4<f16>(" if usesF16 else "" %}
 
13
  const WG: u32 = {{ applyWorkgroupSize }}u;
14
 
15
  @compute @workgroup_size(WG, 1, 1)
16
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
17
  {% if not vectorized %}
18
  // 2D-folded flat index: gid.y carries the high bits after dispatch folding.
19
  {% endif %}
20
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
21
  if (index >= params.count) {
22
  return;
23
  }
build/webgpu/instance-normalization-batched-planes-vec4.wgsl.jinja CHANGED
@@ -1,6 +1,3 @@
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
@@ -19,13 +16,12 @@ var<workgroup> plane_shift: array<f32, PLANES_PER_WG>;
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;
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // A power-of-two lane cohort reduces one plane while several cohorts share a
 
16
  @compute @workgroup_size(WG, 1, 1)
17
  fn main(
18
  @builtin(workgroup_id) workgroup: vec3<u32>,
 
19
  @builtin(local_invocation_id) local: vec3<u32>
20
  ) {
21
  let tid = local.x;
22
  let plane_in_workgroup = tid / LANES;
23
  let lane = tid % LANES;
24
+ let group = workgroup.x + workgroup.y * {{ DISPATCH_FOLD_WIDTH }}u;
25
  let row = group * PLANES_PER_WG + plane_in_workgroup;
26
  let is_active = row < params.rows;
27
  let base = row * HIDDEN_V4;
build/webgpu/instance-normalization-splitk-combine.wgsl.jinja CHANGED
@@ -1,14 +1,15 @@
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
  }
@@ -20,8 +21,16 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
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
  }
 
1
  // Fold SPLIT per-plane (sum, sum-of-squares) partials into mean and inverse
2
+ // standard deviation. One thread handles each plane. The partials are centred on
3
+ // the plane's first element, so E[y^2] - E[y]^2 keeps the variance a raw second
4
+ // moment would cancel away; max(value, 0) guards against negative rounding residue.
5
  {{ env.wgsl.resourceDeclarations }}
6
 
7
  const SPLIT: u32 = {{ split }}u;
8
  const COMBINE_WG: u32 = {{ combineWorkgroupSize }}u;
9
 
10
  @compute @workgroup_size(COMBINE_WG, 1, 1)
11
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
12
+ let plane = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * COMBINE_WG;
13
  if (plane >= params.planes) {
14
  return;
15
  }
 
21
  total_sq = total_sq + partials[(b + k) * 2u + 1u];
22
  }
23
  let n = f32(params.spatial);
24
+ // The partials are accumulated around the plane's first element; undo the shift
25
+ // on the mean and leave the variance, which the shift does not change.
26
+ {% if vectorizedSpec %}
27
+ let shift = f32(input[plane * (params.spatial / 4u)].x);
28
+ {% else %}
29
+ let shift = f32(input[plane * params.spatial]);
30
+ {% endif %}
31
+ let centred_mean = total / n;
32
+ let mean = shift + centred_mean;
33
+ let variance = max(total_sq / n - centred_mean * centred_mean, 0.0);
34
  stats[plane * 2u] = mean;
35
  stats[plane * 2u + 1u] = inverseSqrt(variance + params.epsilon);
36
  }
build/webgpu/instance-normalization-splitk-partials.wgsl.jinja CHANGED
@@ -51,9 +51,6 @@
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 %}
@@ -74,12 +71,11 @@ 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
  }
@@ -96,17 +92,27 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
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 %}
 
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 useSubgroups %}
55
  enable subgroups;
56
  {% endif %}
 
71
  {% endif %}
72
 
73
  @compute @workgroup_size(WG, 1, 1)
74
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
 
75
  @builtin(subgroup_invocation_id) subgroup_lane: u32,
76
  @builtin(subgroup_id) subgroup_id: u32,
77
  @builtin(num_subgroups) num_subgroups: u32{% endif %}) {
78
+ let plane = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
79
  if (plane >= params.planes) {
80
  return;
81
  }
 
92
  if (end > spatial) { end = spatial; }
93
  let base = plane * spatial;
94
 
95
+ // Raw second moments cancel: a plane centred on 8192 with unit variance loses
96
+ // the variance entirely in E[x^2] - E[x]^2, and the combine's max(.,0) then
97
+ // reports zero. Both passes accumulate around the plane's first element, which
98
+ // costs one broadcast load and leaves the squared term holding the residual.
99
+ {% if vectorized %}
100
+ let shift = f32({{ LOAD_OPEN }}input[base]{{ LOAD_CLOSE }}.x);
101
+ let shift4 = vec4<f32>(shift);
102
+ {% else %}
103
+ let shift = f32(input[base]);
104
+ {% endif %}
105
  var s = 0.0;
106
  var sq = 0.0;
107
  var i = start + tid;
108
  loop {
109
  if (i >= end) { break; }
110
  {% if vectorized %}
111
+ let v = {{ LOAD_OPEN }}input[base + i]{{ LOAD_CLOSE }} - shift4;
112
  s = s + v.x + v.y + v.z + v.w;
113
  sq = sq + dot(v, v);
114
  {% else %}
115
+ let v = f32(input[base + i]) - shift;
116
  s = s + v;
117
  sq = sq + v * v;
118
  {% endif %}
build/webgpu/manifest.json CHANGED
@@ -2,51 +2,28 @@
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)",
@@ -65,226 +42,48 @@
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 ~ \">\"",
@@ -301,103 +100,158 @@
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
  },
@@ -405,7 +259,7 @@
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 ~ \">\"",
@@ -425,23 +279,58 @@
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
  },
@@ -449,7 +338,7 @@
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",
@@ -467,22 +356,57 @@
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
  }
 
2
  "domain": "ai.onnx",
3
  "name": "InstanceNormalization",
4
  "sinceVersion": 6,
5
+ "inputs": {
6
+ "input": { "dtype": "T" },
7
+ "scale": { "dtype": "T", "rank": 1 },
8
+ "b": { "onnx": "B", "dtype": "T", "rank": 1 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  },
10
+ "outputs": { "output": { "dtype": "T", "rank": "ranks.input", "shape": "shapes.input" } },
11
+ "attributes": { "epsilon": { "default": 0.00001 } },
12
  "typeConstraints": { "T": ["float32", "float16"] },
 
 
 
 
 
 
13
  "tunables": {
14
+ "WORKGROUP_SIZE": { "default": 256 },
15
+ "MAX_STATS_SPLITS": { "default": 256 },
16
+ "STATS_VALUES_PER_SPLIT": { "default": 2048 },
17
+ "SPLIT_STATS_MIN_SPATIAL": { "default": 65536 },
18
+ "SPLIT_STATS_MAX_PLANES": { "default": 256 },
19
+ "COMBINE_WORKGROUP_SIZE": { "default": 64 },
20
+ "BATCHED_MIN_PLANES_PER_WORKGROUP": { "default": 8 }
21
  },
22
  "derive": {
23
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
24
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
25
  "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
26
+ "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)",
27
  "instancePlanes": "dim(shapes.input, 0) * dim(shapes.input, 1)",
28
  "instanceSpatial": "inner(shapes.input, 1)",
29
  "normDeviceWorkgroupCap": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
 
42
  "instanceStatsBytes": "instancePlanes * 2 * 4",
43
  "instanceStatsFits": "instanceStatsBytes <= device.limits.maxStorageBufferBindingSize and instanceStatsBytes <= device.limits.maxBufferSize",
44
  "instanceRowCovered": "instanceContractOk and instanceRowWorkgroupBytes <= device.limits.maxComputeWorkgroupStorageSize",
45
+ "instanceSplitCount": "min(tunables.MAX_STATS_SPLITS, min(device.limits.maxComputeWorkgroupsPerDimension, 65535), pow2ceil(ceilDiv(instanceSpatial, tunables.STATS_VALUES_PER_SPLIT)))",
46
  "instancePartialBytes": "instancePlanes * instanceSplitCount * 2 * 4",
47
+ "splitStatsCovered": "instanceRowCovered and instanceStatsFits and instancePlanes <= tunables.SPLIT_STATS_MAX_PLANES and instancePlanes <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and instanceSpatial >= tunables.SPLIT_STATS_MIN_SPATIAL and instancePartialBytes <= device.limits.maxStorageBufferBindingSize and instancePartialBytes <= device.limits.maxBufferSize",
48
  "splitStatsPreferred": "splitStatsCovered and instancePlanes < normSubgroupMax"
49
  },
50
+ "bindings": {
51
+ "x": { "arg": "input", "buffer": "read-only-storage", "elementType": "$ioElement" },
52
+ "scale": { "buffer": "read-only-storage", "elementType": "$T" },
53
+ "bias": { "arg": "b", "buffer": "read-only-storage", "elementType": "$T" },
54
+ "y": { "arg": "output", "buffer": "storage", "elementType": "$ioElement" },
55
+ "input": { "buffer": "read-only-storage", "elementType": "$splitInputElement" },
56
+ "input_2": { "name": "input", "buffer": "read-only-storage", "elementType": "$vectorScalar" },
57
+ "stats_2": { "name": "stats", "buffer": "read-only-storage", "elementType": "f32" },
58
+ "output": { "buffer": "storage", "elementType": "$vectorScalar" },
59
+ "params_5": {
60
+ "name": "params",
61
+ "buffer": "uniform",
62
+ "struct": [
63
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / 4" },
64
+ { "name": "channels", "type": "u32", "value": "dim(shapes.input, 1)" },
65
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" }
66
+ ]
67
+ },
68
+ "input_3": { "name": "input", "buffer": "read-only-storage", "elementType": "$T" },
69
+ "output_2": { "name": "output", "buffer": "storage", "elementType": "$T" },
70
+ "params_6": {
71
+ "name": "params",
72
+ "buffer": "uniform",
73
+ "struct": [
74
+ { "name": "count", "type": "u32", "value": "numel(shapes.output)" },
75
+ { "name": "channels", "type": "u32", "value": "dim(shapes.input, 1)" },
76
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" }
77
+ ]
78
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  },
80
  "variants": [
81
  {
82
  "id": "plane_batched_vec4",
83
  "priority": 115,
84
  "when": ["instanceRowCovered", "instanceSpatial % 4 == 0", "instanceSpatial >= 4", "instancePlanes >= normWorkgroupCap", "instanceBatchedVec4PlanesPerWorkgroup >= tunables.BATCHED_MIN_PLANES_PER_WORKGROUP", "instanceBatchedVec4StorageBytes <= device.limits.maxComputeWorkgroupStorageSize"],
85
+ "demoteWhen": ["reportedNonWave32Adapter and instancePlanes <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
86
+ "derive": {
87
  "usesF16": "dtypes.T == \"f16\"",
88
  "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
89
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
 
100
  "id": "main",
101
  "name": "InstanceNormalization.PlaneBatchedVec4",
102
  "shader": "instance-normalization-batched-planes-vec4.wgsl.jinja",
103
+ "bindings": [
104
+ "x",
105
+ "scale",
106
+ "bias",
107
+ "y",
108
+ {
109
+ "name": "params",
110
+ "struct": [{ "name": "rows", "type": "u32", "value": "dim(shapes.input, 0) * dim(shapes.input, 1)" }]
111
+ }
112
+ ],
113
+ "dispatch": {
114
+ "x": "min(instanceBatchedVec4Workgroups, 65535)",
115
+ "y": "ceilDiv(instanceBatchedVec4Workgroups, 65535)",
116
+ "z": 1
117
+ }
118
  }
119
  ]
120
  },
121
  {
122
  "id": "plane_subgroup_vec4",
123
  "priority": 110,
 
124
  "when": ["instanceRowCovered", "inner(shapes.input, 1) % 4 == 0", "instanceVec4SubgroupEfficient"],
125
+ "requires": { "features": [] },
126
+ "derive": { "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
127
  "passes": [
128
  {
129
  "id": "main",
130
  "name": "InstanceNormalization.plane_subgroup_vec4",
131
+ "shader": "norm-row-stats.wgsl.jinja",
132
+ "derive": {
133
+ "modeSpec": "\"instance\"",
134
+ "vec4": true,
135
+ "scalar": "dtypes.T",
136
+ "usesF16Spec": "dtypes.T == \"f16\"",
137
+ "hidden": "instanceSpatial",
138
+ "wg": "instanceVec4Workgroup",
139
+ "epsilon": "attrs.epsilon",
140
+ "channels": "dim(shapes.input, 1) if dim(shapes.input, 1) > 0 else 1",
141
+ "hiddenVec": "instanceSpatial / 4",
142
+ "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
143
+ "combineSubgroups": "hasSubgroupId"
 
 
144
  },
145
+ "bindings": [
146
+ "x",
147
+ "scale",
148
+ "bias",
149
+ "y",
150
+ {
151
+ "name": "params",
152
+ "struct": [
153
+ { "name": "rows", "type": "u32", "value": "dim(shapes.input, 0) * dim(shapes.input, 1)" },
154
+ {
155
+ "name": "rowStride",
156
+ "type": "u32",
157
+ "value": "max(1, min(dim(shapes.input, 0) * dim(shapes.input, 1), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)))"
158
+ }
159
+ ]
160
+ }
161
+ ],
162
+ "dispatch": { "x": "min(instancePlanes, 65535)", "y": "ceilDiv(instancePlanes, 65535)", "z": 1 },
163
+ "subgroupCollectivesWidth": "portable"
164
  }
165
  ]
166
  },
167
  {
168
  "id": "plane_subgroup_vec4_scalar_io",
169
  "priority": 111,
 
170
  "when": ["dtypes.T == \"f32\"", "wave32Adapter", "device.wgslLanguageFeatures.has(\"subgroup_id\")", "instanceRowCovered", "instanceSpatial % 4 == 0", "instanceVec4SubgroupEfficient"],
171
+ "requires": { "features": ["subgroups"] },
172
+ "derive": { "ioElement": "dtypes.T" },
173
  "passes": [
174
  {
175
  "id": "main",
176
  "name": "InstanceNormalization.plane_subgroup_vec4_scalar_io",
177
+ "shader": "norm-row-stats.wgsl.jinja",
178
+ "derive": {
179
+ "modeSpec": "\"instance\"",
180
+ "vec4": true,
181
+ "scalarIo": true,
182
+ "scalar": "dtypes.T",
183
+ "usesF16Spec": false,
184
+ "hidden": "instanceSpatial",
185
+ "wg": "instanceVec4Workgroup",
186
+ "epsilon": "attrs.epsilon",
187
+ "channels": "dim(shapes.input, 1) if dim(shapes.input, 1) > 0 else 1",
188
+ "hiddenVec": "instanceSpatial / 4",
189
+ "vecType": "\"vec4<f32>\"",
190
+ "combineSubgroups": true
 
 
191
  },
192
+ "bindings": [
193
+ "x",
194
+ "scale",
195
+ "bias",
196
+ "y",
197
+ {
198
+ "name": "params",
199
+ "struct": [
200
+ { "name": "rows", "type": "u32", "value": "dim(shapes.input, 0) * dim(shapes.input, 1)" },
201
+ {
202
+ "name": "rowStride",
203
+ "type": "u32",
204
+ "value": "max(1, min(dim(shapes.input, 0) * dim(shapes.input, 1), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)))"
205
+ }
206
+ ]
207
+ }
208
+ ],
209
+ "dispatch": { "x": "min(instancePlanes, 65535)", "y": "ceilDiv(instancePlanes, 65535)", "z": 1 },
210
+ "subgroupCollectivesWidth": "portable"
211
  }
212
  ]
213
  },
214
  {
215
  "id": "plane_subgroup",
216
  "priority": 100,
 
217
  "when": ["instanceRowCovered"],
218
+ "requires": { "features": [] },
219
+ "derive": { "ioElement": "dtypes.T" },
220
  "passes": [
221
  {
222
  "id": "main",
223
  "name": "InstanceNormalization.plane_subgroup",
224
+ "shader": "norm-row-stats.wgsl.jinja",
225
+ "derive": {
226
+ "modeSpec": "\"instance\"",
227
+ "vec4": false,
228
+ "scalar": "dtypes.T",
229
+ "usesF16Spec": "dtypes.T == \"f16\"",
230
+ "hidden": "instanceSpatial",
231
+ "wg": "instanceScalarWorkgroup",
232
+ "epsilon": "attrs.epsilon",
233
+ "channels": "dim(shapes.input, 1) if dim(shapes.input, 1) > 0 else 1",
234
+ "combineSubgroups": "hasSubgroupId"
 
 
235
  },
236
+ "bindings": [
237
+ "x",
238
+ "scale",
239
+ "bias",
240
+ "y",
241
+ {
242
+ "name": "params",
243
+ "struct": [
244
+ { "name": "rows", "type": "u32", "value": "dim(shapes.input, 0) * dim(shapes.input, 1)" },
245
+ {
246
+ "name": "rowStride",
247
+ "type": "u32",
248
+ "value": "max(1, min(dim(shapes.input, 0) * dim(shapes.input, 1), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)))"
249
+ }
250
+ ]
251
+ }
252
+ ],
253
+ "dispatch": { "x": "min(instancePlanes, 65535)", "y": "ceilDiv(instancePlanes, 65535)", "z": 1 },
254
+ "subgroupCollectivesWidth": "portable"
255
  }
256
  ]
257
  },
 
259
  "id": "plane_splitk_vec4",
260
  "priority": 121,
261
  "when": ["splitStatsPreferred", "instanceSpatial % 4 == 0"],
262
+ "derive": {
263
  "vectorized": true,
264
  "usesF16": "dtypes.T == \"f16\"",
265
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
 
279
  "id": "partials",
280
  "name": "InstanceNormalization.SplitKPartialsVec4",
281
  "shader": "instance-normalization-splitk-partials.wgsl.jinja",
282
+ "bindings": [
283
+ "input",
284
+ { "name": "partials", "buffer": "storage", "elementType": "f32" },
285
+ {
286
+ "name": "params",
287
+ "struct": [
288
+ { "name": "planes", "type": "u32", "value": "instancePlanes" },
289
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" }
290
+ ]
291
+ }
292
+ ],
293
+ "dispatch": {
294
+ "x": "min(instancePlanes, DISPATCH_FOLD_WIDTH)",
295
+ "y": "ceilDiv(instancePlanes, DISPATCH_FOLD_WIDTH)",
296
+ "z": "instanceSplitCount"
297
+ },
298
+ "subgroupCollectivesWidth": "portable"
299
  },
300
  {
301
  "id": "combine",
302
  "name": "InstanceNormalization.SplitKCombine",
303
  "shader": "instance-normalization-splitk-combine.wgsl.jinja",
304
+ "derive": { "vectorizedSpec": true },
305
+ "bindings": [
306
+ "input",
307
+ { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
308
+ { "name": "stats", "buffer": "storage", "elementType": "f32" },
309
+ {
310
+ "name": "params",
311
+ "struct": [
312
+ { "name": "planes", "type": "u32", "value": "instancePlanes" },
313
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" },
314
+ { "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
315
+ ]
316
+ }
317
+ ],
318
+ "dispatch": {
319
+ "x": "min(ceilDiv((instancePlanes), (combineWorkgroupSize)), 65535)",
320
+ "y": "ceilDiv(ceilDiv((instancePlanes), (combineWorkgroupSize)), 65535)",
321
+ "z": 1
322
+ }
323
  },
324
  {
325
  "id": "apply",
326
  "name": "InstanceNormalization.ApplyVec4",
327
  "shader": "instance-normalization-apply.wgsl.jinja",
328
+ "bindings": ["input_2", "stats_2", "scale", "bias", "output", "params_5"],
329
+ "dispatch": {
330
+ "x": "min(ceilDiv((numel(shapes.output) / 4), (applyWorkgroupSize)), 65535)",
331
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / 4), (applyWorkgroupSize)), 65535)",
332
+ "z": 1
333
+ }
334
  }
335
  ]
336
  },
 
338
  "id": "plane_splitk",
339
  "priority": 120,
340
  "when": ["splitStatsPreferred"],
341
+ "derive": {
342
  "scalar": "dtypes.T",
343
  "usesF16": "dtypes.T == \"f16\"",
344
  "splitInputElement": "dtypes.T",
 
356
  "id": "partials",
357
  "name": "InstanceNormalization.SplitKPartials",
358
  "shader": "instance-normalization-splitk-partials.wgsl.jinja",
359
+ "bindings": [
360
+ "input",
361
+ { "name": "partials", "buffer": "storage", "elementType": "f32" },
362
+ {
363
+ "name": "params",
364
+ "struct": [
365
+ { "name": "planes", "type": "u32", "value": "instancePlanes" },
366
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" }
367
+ ]
368
+ }
369
+ ],
370
+ "dispatch": {
371
+ "x": "min(instancePlanes, DISPATCH_FOLD_WIDTH)",
372
+ "y": "ceilDiv(instancePlanes, DISPATCH_FOLD_WIDTH)",
373
+ "z": "instanceSplitCount"
374
+ }
375
  },
376
  {
377
  "id": "combine",
378
  "name": "InstanceNormalization.SplitKCombine",
379
  "shader": "instance-normalization-splitk-combine.wgsl.jinja",
380
+ "derive": { "vectorizedSpec": false },
381
+ "bindings": [
382
+ "input",
383
+ { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
384
+ { "name": "stats", "buffer": "storage", "elementType": "f32" },
385
+ {
386
+ "name": "params",
387
+ "struct": [
388
+ { "name": "planes", "type": "u32", "value": "instancePlanes" },
389
+ { "name": "spatial", "type": "u32", "value": "instanceSpatial" },
390
+ { "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
391
+ ]
392
+ }
393
+ ],
394
+ "dispatch": {
395
+ "x": "min(ceilDiv((instancePlanes), (combineWorkgroupSize)), 65535)",
396
+ "y": "ceilDiv(ceilDiv((instancePlanes), (combineWorkgroupSize)), 65535)",
397
+ "z": 1
398
+ }
399
  },
400
  {
401
  "id": "apply",
402
  "name": "InstanceNormalization.Apply",
403
  "shader": "instance-normalization-apply.wgsl.jinja",
404
+ "bindings": ["input_3", "stats_2", "scale", "bias", "output_2", "params_6"],
405
+ "dispatch": {
406
+ "x": "min(ceilDiv((numel(shapes.output)), (applyWorkgroupSize)), 65535)",
407
+ "y": "ceilDiv(ceilDiv((numel(shapes.output)), (applyWorkgroupSize)), 65535)",
408
+ "z": 1
409
+ }
410
  }
411
  ]
412
  }
build/webgpu/metadata.json CHANGED
@@ -1,22 +1,32 @@
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
  }
 
1
  {
2
  "name": "ai.onnx.InstanceNormalization",
3
+ "id": "_ai_onnx_instancenormalization_webgpu_16b3576",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "HBqAImIaa6N6gGqA53oRQ/ZvaCAFNJPKltDsBnjF5wQ=",
11
+ "instance-normalization-apply.wgsl.jinja": "ss27/JnTr5XXoUd0lj8yyYcXfYImAO5jPB9BdkneLwU=",
12
+ "instance-normalization-batched-planes-vec4.wgsl.jinja": "cLkDhQOaM/T+im43mRMyLa+kEoIHfm8i31IdkiyeMcI=",
13
+ "instance-normalization-splitk-combine.wgsl.jinja": "z2uqoUYCw3foyDxmSSD9i8fjH3Z6HFpzVhBlNCV11BM=",
14
+ "instance-normalization-splitk-partials.wgsl.jinja": "SymImIFJ0cyKgoJZtsuTtPAEy2m8CtCKWn8ylnuBQJE=",
15
+ "manifest.json": "Eq6DFSvT5bXhoGN7gsrUL106yaDyqRNzf+r4t2e/Nh0=",
16
+ "norm-row-stats.wgsl.jinja": "pIQ85rOY7wxOVoImYTZF8yUjKpdvCKAH4h2EBmD1Kvc=",
17
+ "test.json": "AIj6JyZjhnniT+w2T4DZkIjVYrltvKmjY6cFQcNm+kY="
18
  }
19
  },
20
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
21
+ "webgpu": {
22
+ "manifestSpec": "2.0",
23
+ "variants": {
24
+ "plane_batched_vec4": ["instance-normalization-batched-planes-vec4.wgsl.jinja"],
25
+ "plane_subgroup_vec4": ["norm-row-stats.wgsl.jinja"],
26
+ "plane_subgroup_vec4_scalar_io": ["norm-row-stats.wgsl.jinja"],
27
+ "plane_subgroup": ["norm-row-stats.wgsl.jinja"],
28
+ "plane_splitk_vec4": ["instance-normalization-apply.wgsl.jinja", "instance-normalization-splitk-combine.wgsl.jinja", "instance-normalization-splitk-partials.wgsl.jinja"],
29
+ "plane_splitk": ["instance-normalization-apply.wgsl.jinja", "instance-normalization-splitk-combine.wgsl.jinja", "instance-normalization-splitk-partials.wgsl.jinja"]
30
+ }
31
+ }
32
  }
build/webgpu/norm-row-stats.wgsl.jinja CHANGED
@@ -1,8 +1,15 @@
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"
@@ -22,16 +29,54 @@ enable subgroups;
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
  }
@@ -94,13 +139,20 @@ fn main(
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 %}
@@ -111,9 +163,12 @@ fn main(
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]);
@@ -124,7 +179,12 @@ fn main(
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;
@@ -141,9 +201,15 @@ fn main(
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 %}
@@ -157,14 +223,33 @@ fn main(
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
  }
 
1
+ {% if usesF16Spec %}
2
  enable f16;
3
  {% endif %}
4
+ {% set combineSubgroups = combineSubgroups %}
5
+ {% set scalarIo = scalarIo if scalarIo is defined else false %}
6
+ {% set packedBf16Embedding = packedBf16Embedding if packedBf16Embedding is defined else false %}
7
+ {% set rmsChainNorm = rmsChainNorm if rmsChainNorm is defined else false %}
8
+ {% set hiddenPairs = hiddenPairs | default(0) %}
9
+ {% set numRows = numRows | default(0) %}
10
+ {% set epsilon = epsilon | default("0.0") %}
11
+ {% set epsilon2 = epsilon2 | default("0.0") %}
12
+ {% set channels = channels | default(0) %}
13
  {% set reduceThreadParameters = ", sg_lane: u32, sg_id: u32, num_sg: u32"
14
  if combineSubgroups else ", tid: u32" %}
15
  {% set reduceThreadArguments = ", sg_lane, sg_id, num_sg"
 
29
  //
30
  // Shifted moments avoid cancellation from a large common offset; clamp the
31
  // variance to zero before adding EPSILON and taking inverseSqrt.
32
+ const HIDDEN: u32 = {{ hidden }}u;
33
+ {% if vec4 %}
34
+ const HIDDEN_V: u32 = {{ hiddenVec }}u;
35
+ {% endif %}
36
+ {% if packedBf16Embedding %}
37
+ const HIDDEN_PAIRS: u32 = {{ hiddenPairs }}u;
38
+ const NUM_ROWS: u32 = {{ numRows }}u;
39
+ {% endif %}
40
+ const WG: u32 = {{ wg }}u;
41
+ const EPSILON: f32 = {{ epsilon }};
42
+ {% if rmsChainNorm %}
43
+ const EPSILON2: f32 = {{ epsilon2 }};
44
+ {% endif %}
45
+ const CHANNELS: u32 = {{ channels }}u;
46
+
47
+ {% if packedBf16Embedding %}
48
+ {% if vec4 %}
49
+ fn unpack_bf16_pair(word: u32) -> vec2<f32> {
50
+ let bits = vec2<u32>(word & 0xffffu, word >> 16u);
51
+ return bitcast<vec2<f32>>(bits << vec2<u32>(16u));
52
+ }
53
+ {% endif %}
54
+
55
+ {% if not vec4 %}
56
+ fn embedding_scalar(source_row: u32, hidden: u32) -> f32 {
57
+ if (source_row >= NUM_ROWS) {
58
+ return 0.0;
59
+ }
60
+ let word = x[source_row * HIDDEN_PAIRS + (hidden >> 1u)];
61
+ let bits = select(word & 0xffffu, word >> 16u, (hidden & 1u) != 0u);
62
+ return bitcast<f32>(bits << 16u);
63
+ }
64
  {% endif %}
 
 
 
65
 
66
+ {% if vec4 %}
67
+ fn embedding_vec4(source_row: u32, hidden_vec: u32) -> vec4<f32> {
68
+ if (source_row >= NUM_ROWS) {
69
+ return vec4<f32>(0.0);
70
+ }
71
+ let base = source_row * HIDDEN_PAIRS + hidden_vec * 2u;
72
+ let low = unpack_bf16_pair(x[base]);
73
+ let high = unpack_bf16_pair(x[base + 1u]);
74
+ return vec4<f32>(low, high);
75
+ }
76
+ {% endif %}
77
+ {% endif %}
78
 
79
+ {% if vec4 and scalarIo %}
80
  fn load_vec4(index: u32) -> vec4<f32> {
81
  return vec4<f32>(x[index], x[index + 1u], x[index + 2u], x[index + 3u]);
82
  }
 
139
  return;
140
  }
141
  let tid = lid.x;
142
+ {% if packedBf16Embedding %}
143
+ let source_row = indices[row];
144
+ {% if vec4 %}
145
+ let base = row * HIDDEN_V;
146
+ {% else %}
147
+ let base = row * HIDDEN;
148
+ {% endif %}
149
+ {% elif vec4 and not scalarIo %}
150
  let base = row * HIDDEN_V;
151
  {% else %}
152
  let base = row * HIDDEN;
153
  {% endif %}
154
 
155
+ {% if vec4 %}
156
  {% if scalarIo %}
157
  let shift = f32(x[base]);
158
  {% else %}
 
163
  {% endif %}
164
 
165
  var acc = vec2<f32>(0.0, 0.0);
166
+ {% if vec4 %}
167
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
168
+ {% if packedBf16Embedding %}
169
+ let v = embedding_vec4(source_row, i);
170
+ embedding_out[base + i] = v;
171
+ {% elif scalarIo %}
172
  let v = load_vec4(base + i * 4u);
173
  {% else %}
174
  let v = vec4<f32>(x[base + i]);
 
179
  }
180
  {% else %}
181
  for (var i = tid; i < HIDDEN; i = i + WG) {
182
+ {% if packedBf16Embedding %}
183
+ let v = embedding_scalar(source_row, i);
184
+ embedding_out[base + i] = v;
185
+ {% else %}
186
  let v = f32(x[base + i]);
187
+ {% endif %}
188
  let d = v - shift;
189
  acc.x = acc.x + d;
190
  acc.y = acc.y + d * d;
 
201
  let ch_scale = f32(scale[c]);
202
  let ch_bias = f32(bias[c]);
203
 
204
+ {% if rmsChainNorm %}
205
+ var acc2 = 0.0;
206
+ {% endif %}
207
+ {% if vec4 %}
208
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
209
+ {% if packedBf16Embedding %}
210
+ let idx = base + i;
211
+ let v = embedding_vec4(source_row, i);
212
+ {% elif scalarIo %}
213
  let idx = base + i * 4u;
214
  let v = load_vec4(idx);
215
  {% else %}
 
223
  y[idx + 2u] = value.z;
224
  y[idx + 3u] = value.w;
225
  {% else %}
226
+ y[idx] = {{ vecType }}((v - vec4<f32>(row_mean)) * inv * vec4<f32>(ch_scale) + vec4<f32>(ch_bias));
227
  {% endif %}
228
  }
229
+ {% if rmsChainNorm %}
230
+
231
+ // The chained second norm reads the residual row this loop just stored. This
232
+ // barrier completes those stores and any preceding shared-scratch use before
233
+ // the next reduction reuses its scratch; each lane then re-reads only the
234
+ // elements it wrote itself.
235
+ workgroupBarrier();
236
+ let total2 = reduce_scalar(acc2{{ reduceThreadArguments }});
237
+ let inv2 = inverseSqrt(total2 / f32(HIDDEN) + EPSILON2);
238
+ for (var i = tid; i < HIDDEN_V; i = i + WG) {
239
+ let idx = base + i;
240
+ let hv = vec4<f32>(y[idx]);
241
+ normed2[idx] = {{ vecType }}(hv * inv2 * vec4<f32>(scale2[i]));
242
+ }
243
+ {% endif %}
244
  {% else %}
245
  for (var i = tid; i < HIDDEN; i = i + WG) {
246
  let idx = base + i;
247
+ {% if packedBf16Embedding %}
248
+ let v = embedding_scalar(source_row, i);
249
+ {% else %}
250
  let v = f32(x[idx]);
251
+ {% endif %}
252
+ y[idx] = {{ scalar }}((v - row_mean) * inv * ch_scale + ch_bias);
253
  }
254
  {% endif %}
255
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
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
  },
@@ -65,7 +64,7 @@
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": {
@@ -183,7 +182,7 @@
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": {
@@ -670,7 +669,7 @@
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": {
@@ -700,6 +699,25 @@
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
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "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]
4
  },
 
64
  {
65
  "name": "f32_yfold_many_channels_1x70000x4",
66
  "provenance": {
67
+ "notes": "More than 65,535 channels exercise folded Y dispatch with a compact four-element spatial plane."
68
  },
69
  "attrs": { "epsilon": 0.00001 },
70
  "inputs": {
 
182
  "provenance": {
183
  "source": "onnxruntime/test/providers/cpu/nn/instance_norm_op_test.cc",
184
  "test": "InstanceNormalizationOpTest.InstanceNormBatch1",
185
+ "notes": "The vec4 spatial path must preserve valid outputs produced by a subnormal scale."
186
  },
187
  "attrs": { "epsilon": 0.00001 },
188
  "inputs": {
 
669
  {
670
  "name": "split_stats_spatial_65536_f16",
671
  "provenance": {
672
+ "notes": "A float16 input with spatial extent 65,536 exercises split statistics, widened accumulation, and narrowed output storage. The extent is divisible by four, allowing both packed and scalar split-statistics variants."
673
  },
674
  "attrs": { "epsilon": 0.00001 },
675
  "inputs": {
 
699
  "b": { "dtype": "float16", "shape": [128], "data": { "kind": "constant", "value": 0.0 } }
700
  },
701
  "outputs": { "output": { "dtype": "float16", "shape": [2, 128, 64], "tolerance": 0.03 } }
702
+ },
703
+ {
704
+ "name": "splitk_large_offset_unit_variance",
705
+ "provenance": {
706
+ "notes": "A plane centred on 8192 alternating by one unit: the true variance is 1, but raw second moments cancel it away at this magnitude, the combine's max(.,0) clamps it to zero, and every output becomes +/-1/sqrt(epsilon). The split-K partials centre on the plane's first element instead."
707
+ },
708
+ "attrs": { "epsilon": 0.00001 },
709
+ "inputs": {
710
+ "input": {
711
+ "dtype": "float32",
712
+ "shape": [1, 1, 256, 256],
713
+ "data": { "kind": "cycle", "values": [8191.0, 8193.0] }
714
+ },
715
+ "scale": { "dtype": "float32", "shape": [1], "data": { "kind": "constant", "value": 1.0 } },
716
+ "b": { "dtype": "float32", "shape": [1], "data": { "kind": "constant", "value": 0.0 } }
717
+ },
718
+ "outputs": {
719
+ "output": { "dtype": "float32", "shape": [1, 1, 256, 256], "tolerance": 0.002, "relTolerance": 0.002 }
720
+ }
721
  }
722
  ]
723
  }