Xenova HF Staff commited on
Commit
036cd69
·
verified ·
1 Parent(s): e256620

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,19 +18,19 @@ See the [ONNX `Mean` spec](https://onnx.ai/onnx/operators/onnx__Mean.html) for t
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `A` | `a` | `T` | — | — | First input tensor. | required |
24
- | `B` | `b` | `T` | — | — | Second input tensor, broadcast-compatible with A. | optional |
25
- | `C` | `c` | `T` | — | — | Third input tensor, broadcast-compatible with A and B. | optional |
26
- | `D` | `d` | `T` | — | — | Fourth input tensor, broadcast-compatible with A, B, and C. | optional |
27
- | `E` | `e` | `T` | — | — | Fifth input tensor, broadcast-compatible with all other inputs. | optional |
28
 
29
  ## Outputs
30
 
31
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
32
  | --- | --- | --- | --- | --- | --- | --- |
33
- | `mean` | `y` | `T` | derived | derived; see description | Elementwise mean of all provided input tensors. | required |
34
 
35
  ## Type constraints
36
 
@@ -40,7 +40,7 @@ See the [ONNX `Mean` spec](https://onnx.ai/onnx/operators/onnx__Mean.html) for t
40
 
41
  ## Files
42
 
43
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
44
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
45
  - [`test.json`](build/webgpu/test.json) — correctness cases
46
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -50,10 +50,14 @@ See the [ONNX `Mean` spec](https://onnx.ai/onnx/operators/onnx__Mean.html) for t
50
 
51
  ## Use with `@huggingface/kernels`
52
 
53
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
54
- It then allocates the result tensors automatically.
 
 
 
55
 
56
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
57
 
58
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
59
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `a` | `A` | `T` | — | — | First input tensor. | required |
24
+ | `b` | `B` | `T` | — | — | Second input tensor, broadcast-compatible with A. | optional |
25
+ | `c` | `C` | `T` | — | — | Third input tensor, broadcast-compatible with A and B. | optional |
26
+ | `d` | `D` | `T` | — | — | Fourth input tensor, broadcast-compatible with A, B, and C. | optional |
27
+ | `e` | `E` | `T` | — | — | Fifth input tensor, broadcast-compatible with all other inputs. | optional |
28
 
29
  ## Outputs
30
 
31
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
32
  | --- | --- | --- | --- | --- | --- | --- |
33
+ | `y` | `mean` | `T` | derived | derived | Elementwise mean of all provided input tensors. | required |
34
 
35
  ## Type constraints
36
 
 
40
 
41
  ## Files
42
 
43
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
44
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
45
  - [`test.json`](build/webgpu/test.json) — correctness cases
46
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
50
 
51
  ## Use with `@huggingface/kernels`
52
 
53
+ ```sh
54
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
55
+ ```
56
+
57
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
58
 
59
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
60
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
61
 
62
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
63
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Mean",
3
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
  "cases": [
5
  {
 
1
  {
 
2
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
3
  "cases": [
4
  {
build/webgpu/datamove-elementwise-copy.wgsl.jinja CHANGED
@@ -1,6 +1,3 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
build/webgpu/manifest.json CHANGED
@@ -2,432 +2,181 @@
2
  "domain": "ai.onnx",
3
  "name": "Mean",
4
  "sinceVersion": 13,
5
- "description": "Computes the elementwise mean of one or more input tensors with multidirectional (NumPy-style) broadcasting. All inputs and the output share the same data type.",
6
- "inputs": [
7
- { "role": "A", "dtype": "T", "description": "First input tensor." },
8
- { "role": "B", "dtype": "T", "optional": true, "description": "Second input tensor, broadcast-compatible with A." },
9
- {
10
- "role": "C",
11
- "dtype": "T",
12
- "optional": true,
13
- "description": "Third input tensor, broadcast-compatible with A and B."
14
- },
15
- {
16
- "role": "D",
17
- "dtype": "T",
18
- "optional": true,
19
- "description": "Fourth input tensor, broadcast-compatible with A, B, and C."
20
- },
21
- {
22
- "role": "E",
23
- "dtype": "T",
24
- "optional": true,
25
- "description": "Fifth input tensor, broadcast-compatible with all other inputs."
26
- }
27
- ],
28
- "outputs": [
29
- {
30
- "role": "mean",
31
  "dtype": "T",
32
- "rank": "max(ranks.A, ranks.B if present.b else 0, ranks.C if present.b and present.c else 0, ranks.D if present.b and present.c and present.d else 0, ranks.E if present.b and present.c and present.d and present.e else 0)",
33
- "shape": "variadicShape",
34
- "description": "Elementwise mean of all provided input tensors."
35
  }
36
- ],
37
- "typeConstraints": { "T": ["float32", "float16"] },
38
- "args": {
39
- "a": { "kind": "tensor", "semantic": "A", "role": "input" },
40
- "b": { "kind": "tensor", "semantic": "B", "role": "input", "required": false },
41
- "c": { "kind": "tensor", "semantic": "C", "role": "input2", "required": false },
42
- "y": { "kind": "tensor", "semantic": "mean", "role": "output" },
43
- "d": { "kind": "tensor", "semantic": "D", "role": "input3", "required": false },
44
- "e": { "kind": "tensor", "semantic": "E", "role": "input4", "required": false }
45
  },
46
- "tunables": { "WORKGROUP_SIZE": 256 },
 
47
  "derive": {
48
  "variadicInputCount": "1 + (1 if present.b else 0) + (1 if (present.b and present.c) else 0) + (1 if (present.b and present.c and present.d) else 0) + (1 if (present.b and present.c and present.d and present.e) else 0)",
49
- "variadicShape": "broadcastShape(broadcastShape(broadcastShape(broadcastShape(shapes.A, shapes.B), shapes.C), shapes.D), shapes.E) if present.b and present.c and present.d and present.e else (broadcastShape(broadcastShape(broadcastShape(shapes.A, shapes.B), shapes.C), shapes.D) if present.b and present.c and present.d else (broadcastShape(broadcastShape(shapes.A, shapes.B), shapes.C) if present.b and present.c else (broadcastShape(shapes.A, shapes.B) if present.b else shapes.A)))",
50
  "flatVec4OutputOk": "numel(shapes.y) > 0 and numel(shapes.y) % 4 == 0 and f16Ok(dtypes.T)",
51
  "broadcastOutputOk": "f16Ok(dtypes.T)",
52
  "broadcastVec4OutputOk": "ranks.y >= 1 and dim(shapes.y, ranks.y - 1) > 0 and dim(shapes.y, ranks.y - 1) % 4 == 0 and f16Ok(dtypes.T)",
53
- "aBroadcastVec4Ok": "ranks.A <= ranks.y and (ranks.A == 0 or dim(shapes.A, ranks.A - 1) == 1 or dim(shapes.A, ranks.A - 1) == dim(shapes.y, ranks.y - 1))",
54
- "bBroadcastVec4Ok": "not present.b or (ranks.B <= ranks.y and (ranks.B == 0 or dim(shapes.B, ranks.B - 1) == 1 or dim(shapes.B, ranks.B - 1) == dim(shapes.y, ranks.y - 1)))",
55
- "cBroadcastVec4Ok": "not present.c or (ranks.C <= ranks.y and (ranks.C == 0 or dim(shapes.C, ranks.C - 1) == 1 or dim(shapes.C, ranks.C - 1) == dim(shapes.y, ranks.y - 1)))",
56
- "dBroadcastVec4Ok": "not present.d or (ranks.D <= ranks.y and (ranks.D == 0 or dim(shapes.D, ranks.D - 1) == 1 or dim(shapes.D, ranks.D - 1) == dim(shapes.y, ranks.y - 1)))"
 
 
57
  },
58
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
59
- "bindingSets": {
60
- "identity": [
61
- { "name": "x", "arg": "a", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
62
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
63
- {
64
- "name": "params",
65
- "semantic": "kernel.params",
66
- "buffer": { "type": "uniform" },
67
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
68
- }
69
- ],
70
- "vec4Two": [
71
- {
72
- "name": "a",
73
- "arg": "a",
74
- "semantic": "A",
75
- "buffer": { "type": "read-only-storage" },
76
- "elementType": "$vectorScalar"
77
- },
78
- {
79
- "name": "b",
80
- "arg": "b",
81
- "semantic": "B",
82
- "buffer": { "type": "read-only-storage" },
83
- "elementType": "$vectorScalar"
84
- },
85
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
86
- {
87
- "name": "params",
88
- "semantic": "kernel.params",
89
- "buffer": { "type": "uniform" },
90
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
91
- }
92
- ],
93
- "vec4Three": [
94
- {
95
- "name": "a",
96
- "arg": "a",
97
- "semantic": "A",
98
- "buffer": { "type": "read-only-storage" },
99
- "elementType": "$vectorScalar"
100
- },
101
- {
102
- "name": "b",
103
- "arg": "b",
104
- "semantic": "B",
105
- "buffer": { "type": "read-only-storage" },
106
- "elementType": "$vectorScalar"
107
- },
108
- {
109
- "name": "c",
110
- "arg": "c",
111
- "semantic": "C",
112
- "buffer": { "type": "read-only-storage" },
113
- "elementType": "$vectorScalar"
114
- },
115
- { "name": "y", "arg": "y", "semantic": "mean", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
116
- {
117
- "name": "params",
118
- "semantic": "kernel.params",
119
- "buffer": { "type": "uniform" },
120
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
121
- }
122
- ],
123
- "vec4Four": [
124
- {
125
- "name": "a",
126
- "arg": "a",
127
- "semantic": "A",
128
- "buffer": { "type": "read-only-storage" },
129
- "elementType": "$vectorScalar"
130
- },
131
- {
132
- "name": "b",
133
- "arg": "b",
134
- "semantic": "B",
135
- "buffer": { "type": "read-only-storage" },
136
- "elementType": "$vectorScalar"
137
- },
138
- {
139
- "name": "c",
140
- "arg": "c",
141
- "semantic": "C",
142
- "buffer": { "type": "read-only-storage" },
143
- "elementType": "$vectorScalar"
144
- },
145
- {
146
- "name": "d",
147
- "arg": "d",
148
- "semantic": "D",
149
- "buffer": { "type": "read-only-storage" },
150
- "elementType": "$vectorScalar"
151
- },
152
- { "name": "y", "arg": "y", "semantic": "mean", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
153
- {
154
- "name": "params",
155
- "semantic": "kernel.params",
156
- "buffer": { "type": "uniform" },
157
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
158
- }
159
- ],
160
- "scalarTwo": [
161
- { "name": "a", "arg": "a", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
162
- { "name": "b", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
163
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
164
- {
165
- "name": "params",
166
- "semantic": "kernel.params",
167
- "buffer": { "type": "uniform" },
168
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
169
- }
170
- ],
171
- "scalarThree": [
172
- { "name": "a", "arg": "a", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
173
- { "name": "b", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
174
- { "name": "c", "arg": "c", "semantic": "C", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
175
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
176
- {
177
- "name": "params",
178
- "semantic": "kernel.params",
179
- "buffer": { "type": "uniform" },
180
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
181
- }
182
- ],
183
- "scalarFour": [
184
- { "name": "a", "arg": "a", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
185
- { "name": "b", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
186
- { "name": "c", "arg": "c", "semantic": "C", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
187
- { "name": "d", "arg": "d", "semantic": "D", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
188
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
189
- {
190
- "name": "params",
191
- "semantic": "kernel.params",
192
- "buffer": { "type": "uniform" },
193
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
194
- }
195
- ],
196
- "vec4Five": [
197
- {
198
- "name": "a",
199
- "arg": "a",
200
- "semantic": "A",
201
- "buffer": { "type": "read-only-storage" },
202
- "elementType": "$vectorScalar"
203
- },
204
- {
205
- "name": "b",
206
- "arg": "b",
207
- "semantic": "B",
208
- "buffer": { "type": "read-only-storage" },
209
- "elementType": "$vectorScalar"
210
- },
211
- {
212
- "name": "c",
213
- "arg": "c",
214
- "semantic": "C",
215
- "buffer": { "type": "read-only-storage" },
216
- "elementType": "$vectorScalar"
217
- },
218
- {
219
- "name": "d",
220
- "arg": "d",
221
- "semantic": "D",
222
- "buffer": { "type": "read-only-storage" },
223
- "elementType": "$vectorScalar"
224
- },
225
- {
226
- "name": "e",
227
- "arg": "e",
228
- "semantic": "E",
229
- "buffer": { "type": "read-only-storage" },
230
- "elementType": "$vectorScalar"
231
- },
232
- { "name": "y", "arg": "y", "semantic": "mean", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
233
- {
234
- "name": "params",
235
- "semantic": "kernel.params",
236
- "buffer": { "type": "uniform" },
237
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
238
- }
239
- ],
240
- "scalarFive": [
241
- { "name": "a", "arg": "a", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
242
- { "name": "b", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
243
- { "name": "c", "arg": "c", "semantic": "C", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
244
- { "name": "d", "arg": "d", "semantic": "D", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
245
- { "name": "e", "arg": "e", "semantic": "E", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
246
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
247
- {
248
- "name": "params",
249
- "semantic": "kernel.params",
250
- "buffer": { "type": "uniform" },
251
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
252
- }
253
- ],
254
- "vec4BroadcastThree": [
255
- {
256
- "name": "a",
257
- "arg": "a",
258
- "semantic": "A",
259
- "buffer": { "type": "read-only-storage" },
260
- "elementType": "$aElement"
261
- },
262
- {
263
- "name": "b",
264
- "arg": "b",
265
- "semantic": "B",
266
- "buffer": { "type": "read-only-storage" },
267
- "elementType": "$bElement"
268
- },
269
- {
270
- "name": "c",
271
- "arg": "c",
272
- "semantic": "C",
273
- "buffer": { "type": "read-only-storage" },
274
- "elementType": "$cElement"
275
- },
276
- { "name": "y", "arg": "y", "semantic": "mean", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
277
- {
278
- "name": "params",
279
- "semantic": "kernel.params",
280
- "buffer": { "type": "uniform" },
281
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
282
- }
283
- ],
284
- "vec4BroadcastFour": [
285
- {
286
- "name": "a",
287
- "arg": "a",
288
- "semantic": "A",
289
- "buffer": { "type": "read-only-storage" },
290
- "elementType": "$aElement"
291
- },
292
- {
293
- "name": "b",
294
- "arg": "b",
295
- "semantic": "B",
296
- "buffer": { "type": "read-only-storage" },
297
- "elementType": "$bElement"
298
- },
299
- {
300
- "name": "c",
301
- "arg": "c",
302
- "semantic": "C",
303
- "buffer": { "type": "read-only-storage" },
304
- "elementType": "$cElement"
305
- },
306
- {
307
- "name": "d",
308
- "arg": "d",
309
- "semantic": "D",
310
- "buffer": { "type": "read-only-storage" },
311
- "elementType": "$dElement"
312
- },
313
- { "name": "y", "arg": "y", "semantic": "mean", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
314
- {
315
- "name": "params",
316
- "semantic": "kernel.params",
317
- "buffer": { "type": "uniform" },
318
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
319
- }
320
- ]
321
  },
322
  "variants": [
323
  {
324
  "id": "single_input_identity",
325
  "priority": 30,
326
- "when": ["variadicInputCount == 1", "ranks.A == ranks.y", "numel(shapes.A) == numel(shapes.y)", "f16Ok(dtypes.T)"],
327
  "passes": [
328
  {
329
  "id": "main",
330
  "name": "Mean",
331
  "shader": "datamove-elementwise-copy.wgsl.jinja",
332
- "bindings": "identity",
333
- "dispatch": { "gridStride": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
334
  }
335
  ]
336
  },
337
  {
338
  "id": "same_shape_vec4_two_input",
339
  "priority": 20,
340
- "when": ["variadicInputCount == 2", "sameShape(shapes.A, shapes.y)", "sameShape(shapes.B, shapes.y)", "flatVec4OutputOk"],
341
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
342
  "passes": [
343
  {
344
  "id": "main",
345
  "name": "Mean.vec4",
346
- "source": { "shader": "summean-vec4.wgsl.jinja", "inputs": { "op": "\"mean\"", "hasC": "false" } },
347
- "bindings": "vec4Two",
348
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
349
  }
350
  ]
351
  },
352
  {
353
  "id": "same_shape_vec4_three_input",
354
  "priority": 25,
355
- "when": ["variadicInputCount == 3", "sameShape(shapes.A, shapes.y)", "sameShape(shapes.B, shapes.y)", "sameShape(shapes.C, shapes.y)", "flatVec4OutputOk"],
356
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
357
  "passes": [
358
  {
359
  "id": "main",
360
  "name": "Mean.vec4_3",
361
- "source": { "shader": "summean-vec4.wgsl.jinja", "inputs": { "op": "\"mean\"", "hasC": "true" } },
362
- "bindings": "vec4Three",
363
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
364
  }
365
  ]
366
  },
367
  {
368
  "id": "same_shape_vec4_four_input",
369
  "priority": 27,
370
- "when": ["variadicInputCount == 4", "sameShape(shapes.A, shapes.y)", "sameShape(shapes.B, shapes.y)", "sameShape(shapes.C, shapes.y)", "sameShape(shapes.D, shapes.y)", "flatVec4OutputOk"],
371
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
372
  "passes": [
373
  {
374
  "id": "main",
375
  "name": "Mean.vec4_3",
376
- "source": {
377
- "shader": "summean-vec4.wgsl.jinja",
378
- "inputs": { "op": "\"mean\"", "hasC": "true", "hasD": "true" }
379
- },
380
- "bindings": "vec4Four",
381
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
382
  }
383
  ]
384
  },
385
  {
386
  "id": "same_shape_vec4_five_input",
387
  "priority": 28,
388
- "when": ["variadicInputCount == 5", "sameShape(shapes.A, shapes.y)", "sameShape(shapes.B, shapes.y)", "sameShape(shapes.C, shapes.y)", "sameShape(shapes.D, shapes.y)", "sameShape(shapes.E, shapes.y)", "flatVec4OutputOk"],
389
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
390
  "passes": [
391
  {
392
  "id": "main",
393
  "name": "Mean.vec4_35",
394
- "source": {
395
- "shader": "summean-vec4.wgsl.jinja",
396
- "inputs": {
397
- "op": "\"mean\"",
398
- "hasC": "true",
399
- "hasD": "true",
400
- "hasE": "true",
401
- "extraInputs": "[\"c\", \"d\", \"e\"]"
402
- }
403
  },
404
- "bindings": "vec4Five",
405
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
406
  }
407
  ]
408
  },
409
  {
410
  "id": "broadcast_two_input",
411
- "when": ["variadicInputCount == 2", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "broadcastOutputOk"],
412
  "passes": [
413
  {
414
  "id": "main",
415
  "name": "Mean",
416
- "source": {
417
- "shader": "summean-broadcast.wgsl.jinja",
418
- "inputs": {
419
- "aShape": "shapes.A",
420
- "bShape": "shapes.B",
421
- "yShape": "shapes.y",
422
- "aRank": "ranks.A",
423
- "bRank": "ranks.B",
424
- "yRank": "ranks.y",
425
- "hasC": "false",
426
- "op": "\"mean\""
427
- }
428
  },
429
- "bindings": "scalarTwo",
430
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
431
  }
432
  ]
433
  },
@@ -435,65 +184,69 @@
435
  "id": "broadcast_three_input_vec4",
436
  "priority": 24,
437
  "when": ["variadicInputCount == 3", "broadcastVec4OutputOk", "aBroadcastVec4Ok", "bBroadcastVec4Ok", "cBroadcastVec4Ok"],
438
- "constants": {
439
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
440
- "aElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.A > 0 and dim(shapes.A, ranks.A - 1) != 1 else dtypes.T",
441
- "bElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.B > 0 and dim(shapes.B, ranks.B - 1) != 1 else dtypes.T",
442
- "cElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.C > 0 and dim(shapes.C, ranks.C - 1) != 1 else dtypes.T"
443
  },
444
  "passes": [
445
  {
446
  "id": "main",
447
  "name": "Mean.Broadcast3Vec4",
448
- "source": {
449
- "shader": "summean-broadcast.wgsl.jinja",
450
- "inputs": {
451
- "aShape": "shapes.A",
452
- "bShape": "shapes.B",
453
- "cShape": "shapes.C",
454
- "yShape": "shapes.y",
455
- "aRank": "ranks.A",
456
- "bRank": "ranks.B",
457
- "cRank": "ranks.C",
458
- "yRank": "ranks.y",
459
- "aVector": "ranks.A > 0 and dim(shapes.A, ranks.A - 1) != 1",
460
- "bVector": "ranks.B > 0 and dim(shapes.B, ranks.B - 1) != 1",
461
- "cVector": "ranks.C > 0 and dim(shapes.C, ranks.C - 1) != 1",
462
- "hasC": true,
463
- "vectorized": true,
464
- "op": "\"mean\""
465
- }
466
  },
467
- "bindings": "vec4BroadcastThree",
468
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
469
  }
470
  ]
471
  },
472
  {
473
  "id": "broadcast_three_input",
474
  "priority": 20,
475
- "when": ["variadicInputCount == 3", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "ranks.C <= ranks.y", "broadcastOutputOk"],
476
  "passes": [
477
  {
478
  "id": "main",
479
  "name": "Mean",
480
- "source": {
481
- "shader": "summean-broadcast.wgsl.jinja",
482
- "inputs": {
483
- "aShape": "shapes.A",
484
- "bShape": "shapes.B",
485
- "cShape": "shapes.C",
486
- "yShape": "shapes.y",
487
- "aRank": "ranks.A",
488
- "bRank": "ranks.B",
489
- "cRank": "ranks.C",
490
- "yRank": "ranks.y",
491
- "hasC": "true",
492
- "op": "\"mean\""
493
- }
494
  },
495
- "bindings": "scalarThree",
496
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
497
  }
498
  ]
499
  },
@@ -501,108 +254,114 @@
501
  "id": "broadcast_four_input_vec4",
502
  "priority": 26,
503
  "when": ["variadicInputCount == 4", "broadcastVec4OutputOk", "aBroadcastVec4Ok", "bBroadcastVec4Ok", "cBroadcastVec4Ok", "dBroadcastVec4Ok"],
504
- "constants": {
505
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
506
- "aElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.A > 0 and dim(shapes.A, ranks.A - 1) != 1 else dtypes.T",
507
- "bElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.B > 0 and dim(shapes.B, ranks.B - 1) != 1 else dtypes.T",
508
- "cElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.C > 0 and dim(shapes.C, ranks.C - 1) != 1 else dtypes.T",
509
- "dElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.D > 0 and dim(shapes.D, ranks.D - 1) != 1 else dtypes.T"
510
  },
511
  "passes": [
512
  {
513
  "id": "main",
514
  "name": "Mean.Broadcast4Vec4",
515
- "source": {
516
- "shader": "summean-broadcast.wgsl.jinja",
517
- "inputs": {
518
- "aShape": "shapes.A",
519
- "bShape": "shapes.B",
520
- "cShape": "shapes.C",
521
- "dShape": "shapes.D",
522
- "yShape": "shapes.y",
523
- "aRank": "ranks.A",
524
- "bRank": "ranks.B",
525
- "cRank": "ranks.C",
526
- "dRank": "ranks.D",
527
- "yRank": "ranks.y",
528
- "aVector": "ranks.A > 0 and dim(shapes.A, ranks.A - 1) != 1",
529
- "bVector": "ranks.B > 0 and dim(shapes.B, ranks.B - 1) != 1",
530
- "cVector": "ranks.C > 0 and dim(shapes.C, ranks.C - 1) != 1",
531
- "dVector": "ranks.D > 0 and dim(shapes.D, ranks.D - 1) != 1",
532
- "hasC": true,
533
- "hasD": true,
534
- "vectorized": true,
535
- "op": "\"mean\""
536
- }
537
  },
538
- "bindings": "vec4BroadcastFour",
539
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
540
  }
541
  ]
542
  },
543
  {
544
  "id": "broadcast_four_input",
545
  "priority": 22,
546
- "when": ["variadicInputCount == 4", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "ranks.C <= ranks.y", "ranks.D <= ranks.y", "broadcastOutputOk"],
547
  "passes": [
548
  {
549
  "id": "main",
550
  "name": "Mean",
551
- "source": {
552
- "shader": "summean-broadcast.wgsl.jinja",
553
- "inputs": {
554
- "aShape": "shapes.A",
555
- "bShape": "shapes.B",
556
- "cShape": "shapes.C",
557
- "yShape": "shapes.y",
558
- "aRank": "ranks.A",
559
- "bRank": "ranks.B",
560
- "cRank": "ranks.C",
561
- "yRank": "ranks.y",
562
- "hasC": "true",
563
- "op": "\"mean\"",
564
- "dShape": "shapes.D",
565
- "dRank": "ranks.D",
566
- "hasD": "true"
567
- }
568
  },
569
- "bindings": "scalarFour",
570
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
571
  }
572
  ]
573
  },
574
  {
575
  "id": "broadcast_five_input",
576
  "priority": 23,
577
- "when": ["variadicInputCount == 5", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "ranks.C <= ranks.y", "ranks.D <= ranks.y", "ranks.E <= ranks.y", "broadcastOutputOk"],
578
  "passes": [
579
  {
580
  "id": "main",
581
  "name": "Mean5",
582
- "source": {
583
- "shader": "summean-broadcast.wgsl.jinja",
584
- "inputs": {
585
- "aShape": "shapes.A",
586
- "bShape": "shapes.B",
587
- "cShape": "shapes.C",
588
- "yShape": "shapes.y",
589
- "aRank": "ranks.A",
590
- "bRank": "ranks.B",
591
- "cRank": "ranks.C",
592
- "yRank": "ranks.y",
593
- "hasC": "true",
594
- "op": "\"mean\"",
595
- "dShape": "shapes.D",
596
- "dRank": "ranks.D",
597
- "hasD": "true",
598
- "hasE": "true",
599
- "extraInputs": "[\"c\", \"d\", \"e\"]",
600
- "eShape": "shapes.E",
601
- "eRank": "ranks.E"
602
- }
603
  },
604
- "bindings": "scalarFive",
605
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
606
  }
607
  ]
608
  }
 
2
  "domain": "ai.onnx",
3
  "name": "Mean",
4
  "sinceVersion": 13,
5
+ "inputs": {
6
+ "a": { "onnx": "A", "dtype": "T" },
7
+ "b": { "onnx": "B", "dtype": "T", "optional": true },
8
+ "c": { "onnx": "C", "dtype": "T", "optional": true },
9
+ "d": { "onnx": "D", "dtype": "T", "optional": true },
10
+ "e": { "onnx": "E", "dtype": "T", "optional": true }
11
+ },
12
+ "outputs": {
13
+ "y": {
14
+ "onnx": "mean",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  "dtype": "T",
16
+ "rank": "max(ranks.a, ranks.b if present.b else 0, ranks.c if present.b and present.c else 0, ranks.d if present.b and present.c and present.d else 0, ranks.e if present.b and present.c and present.d and present.e else 0)",
17
+ "shape": "variadicShape"
 
18
  }
 
 
 
 
 
 
 
 
 
19
  },
20
+ "typeConstraints": { "T": ["float32", "float16"] },
21
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
22
  "derive": {
23
  "variadicInputCount": "1 + (1 if present.b else 0) + (1 if (present.b and present.c) else 0) + (1 if (present.b and present.c and present.d) else 0) + (1 if (present.b and present.c and present.d and present.e) else 0)",
24
+ "variadicShape": "broadcastShape(broadcastShape(broadcastShape(broadcastShape(shapes.a, shapes.b), shapes.c), shapes.d), shapes.e) if present.b and present.c and present.d and present.e else (broadcastShape(broadcastShape(broadcastShape(shapes.a, shapes.b), shapes.c), shapes.d) if present.b and present.c and present.d else (broadcastShape(broadcastShape(shapes.a, shapes.b), shapes.c) if present.b and present.c else (broadcastShape(shapes.a, shapes.b) if present.b else shapes.a)))",
25
  "flatVec4OutputOk": "numel(shapes.y) > 0 and numel(shapes.y) % 4 == 0 and f16Ok(dtypes.T)",
26
  "broadcastOutputOk": "f16Ok(dtypes.T)",
27
  "broadcastVec4OutputOk": "ranks.y >= 1 and dim(shapes.y, ranks.y - 1) > 0 and dim(shapes.y, ranks.y - 1) % 4 == 0 and f16Ok(dtypes.T)",
28
+ "aBroadcastVec4Ok": "ranks.a <= ranks.y and (ranks.a == 0 or dim(shapes.a, ranks.a - 1) == 1 or dim(shapes.a, ranks.a - 1) == dim(shapes.y, ranks.y - 1))",
29
+ "bBroadcastVec4Ok": "not present.b or (ranks.b <= ranks.y and (ranks.b == 0 or dim(shapes.b, ranks.b - 1) == 1 or dim(shapes.b, ranks.b - 1) == dim(shapes.y, ranks.y - 1)))",
30
+ "cBroadcastVec4Ok": "not present.c or (ranks.c <= ranks.y and (ranks.c == 0 or dim(shapes.c, ranks.c - 1) == 1 or dim(shapes.c, ranks.c - 1) == dim(shapes.y, ranks.y - 1)))",
31
+ "dBroadcastVec4Ok": "not present.d or (ranks.d <= ranks.y and (ranks.d == 0 or dim(shapes.d, ranks.d - 1) == 1 or dim(shapes.d, ranks.d - 1) == dim(shapes.y, ranks.y - 1)))",
32
+ "scalar": "dtypes.T",
33
+ "usesF16": "dtypes.T == \"f16\""
34
  },
35
+ "bindings": {
36
+ "params": { "buffer": "uniform", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] },
37
+ "a": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
38
+ "b": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
39
+ "y_2": { "name": "y", "buffer": "storage", "elementType": "$vectorScalar" },
40
+ "params_2": {
41
+ "name": "params",
42
+ "buffer": "uniform",
43
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }]
44
+ },
45
+ "c": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
46
+ "d": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
47
+ "a_3": { "name": "a", "buffer": "read-only-storage", "elementType": "$aElement" },
48
+ "b_3": { "name": "b", "buffer": "read-only-storage", "elementType": "$bElement" },
49
+ "c_2": { "name": "c", "buffer": "read-only-storage", "elementType": "$cElement" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  },
51
  "variants": [
52
  {
53
  "id": "single_input_identity",
54
  "priority": 30,
55
+ "when": ["variadicInputCount == 1", "ranks.a == ranks.y", "numel(shapes.a) == numel(shapes.y)", "f16Ok(dtypes.T)"],
56
  "passes": [
57
  {
58
  "id": "main",
59
  "name": "Mean",
60
  "shader": "datamove-elementwise-copy.wgsl.jinja",
61
+ "bindings": [{ "arg": "a", "name": "x", "elementType": "$scalar" }, "y", "params"],
62
+ "dispatch": {
63
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
64
+ "y": 1,
65
+ "z": 1
66
+ }
67
  }
68
  ]
69
  },
70
  {
71
  "id": "same_shape_vec4_two_input",
72
  "priority": 20,
73
+ "when": ["variadicInputCount == 2", "sameShape(shapes.a, shapes.y)", "sameShape(shapes.b, shapes.y)", "flatVec4OutputOk"],
74
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
75
  "passes": [
76
  {
77
  "id": "main",
78
  "name": "Mean.vec4",
79
+ "shader": "summean-vec4.wgsl.jinja",
80
+ "derive": { "op": "\"mean\"", "hasC": "false" },
81
+ "bindings": ["a", "b", "y_2", "params_2"],
82
+ "dispatch": {
83
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
84
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
85
+ "z": 1
86
+ }
87
  }
88
  ]
89
  },
90
  {
91
  "id": "same_shape_vec4_three_input",
92
  "priority": 25,
93
+ "when": ["variadicInputCount == 3", "sameShape(shapes.a, shapes.y)", "sameShape(shapes.b, shapes.y)", "sameShape(shapes.c, shapes.y)", "flatVec4OutputOk"],
94
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
95
  "passes": [
96
  {
97
  "id": "main",
98
  "name": "Mean.vec4_3",
99
+ "shader": "summean-vec4.wgsl.jinja",
100
+ "derive": { "op": "\"mean\"", "hasC": "true" },
101
+ "bindings": ["a", "b", "c", "y_2", "params_2"],
102
+ "dispatch": {
103
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
104
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
105
+ "z": 1
106
+ }
107
  }
108
  ]
109
  },
110
  {
111
  "id": "same_shape_vec4_four_input",
112
  "priority": 27,
113
+ "when": ["variadicInputCount == 4", "sameShape(shapes.a, shapes.y)", "sameShape(shapes.b, shapes.y)", "sameShape(shapes.c, shapes.y)", "sameShape(shapes.d, shapes.y)", "flatVec4OutputOk"],
114
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
115
  "passes": [
116
  {
117
  "id": "main",
118
  "name": "Mean.vec4_3",
119
+ "shader": "summean-vec4.wgsl.jinja",
120
+ "derive": { "op": "\"mean\"", "hasC": "true", "hasD": "true" },
121
+ "bindings": ["a", "b", "c", "d", "y_2", "params_2"],
122
+ "dispatch": {
123
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
124
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
125
+ "z": 1
126
+ }
127
  }
128
  ]
129
  },
130
  {
131
  "id": "same_shape_vec4_five_input",
132
  "priority": 28,
133
+ "when": ["variadicInputCount == 5", "sameShape(shapes.a, shapes.y)", "sameShape(shapes.b, shapes.y)", "sameShape(shapes.c, shapes.y)", "sameShape(shapes.d, shapes.y)", "sameShape(shapes.e, shapes.y)", "flatVec4OutputOk"],
134
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
135
  "passes": [
136
  {
137
  "id": "main",
138
  "name": "Mean.vec4_35",
139
+ "shader": "summean-vec4.wgsl.jinja",
140
+ "derive": {
141
+ "op": "\"mean\"",
142
+ "hasC": "true",
143
+ "hasD": "true",
144
+ "hasE": "true",
145
+ "extraInputs": "[\"c\", \"d\", \"e\"]"
 
 
146
  },
147
+ "bindings": ["a", "b", "c", "d", { "arg": "e", "elementType": "$vectorScalar" }, "y_2", "params_2"],
148
+ "dispatch": {
149
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
150
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
151
+ "z": 1
152
+ }
153
  }
154
  ]
155
  },
156
  {
157
  "id": "broadcast_two_input",
158
+ "when": ["variadicInputCount == 2", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "broadcastOutputOk"],
159
  "passes": [
160
  {
161
  "id": "main",
162
  "name": "Mean",
163
+ "shader": "summean-broadcast.wgsl.jinja",
164
+ "derive": {
165
+ "aShape": "shapes.a",
166
+ "bShape": "shapes.b",
167
+ "yShape": "shapes.y",
168
+ "aRank": "ranks.a",
169
+ "bRank": "ranks.b",
170
+ "yRank": "ranks.y",
171
+ "hasC": "false",
172
+ "op": "\"mean\""
 
 
173
  },
174
+ "bindings": [{ "arg": "a" }, { "arg": "b" }, "y", "params"],
175
+ "dispatch": {
176
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
177
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
178
+ "z": 1
179
+ }
180
  }
181
  ]
182
  },
 
184
  "id": "broadcast_three_input_vec4",
185
  "priority": 24,
186
  "when": ["variadicInputCount == 3", "broadcastVec4OutputOk", "aBroadcastVec4Ok", "bBroadcastVec4Ok", "cBroadcastVec4Ok"],
187
+ "derive": {
188
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
189
+ "aElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.a > 0 and dim(shapes.a, ranks.a - 1) != 1 else dtypes.T",
190
+ "bElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.b > 0 and dim(shapes.b, ranks.b - 1) != 1 else dtypes.T",
191
+ "cElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.c > 0 and dim(shapes.c, ranks.c - 1) != 1 else dtypes.T"
192
  },
193
  "passes": [
194
  {
195
  "id": "main",
196
  "name": "Mean.Broadcast3Vec4",
197
+ "shader": "summean-broadcast.wgsl.jinja",
198
+ "derive": {
199
+ "aShape": "shapes.a",
200
+ "bShape": "shapes.b",
201
+ "cShape": "shapes.c",
202
+ "yShape": "shapes.y",
203
+ "aRank": "ranks.a",
204
+ "bRank": "ranks.b",
205
+ "cRank": "ranks.c",
206
+ "yRank": "ranks.y",
207
+ "aVector": "ranks.a > 0 and dim(shapes.a, ranks.a - 1) != 1",
208
+ "bVector": "ranks.b > 0 and dim(shapes.b, ranks.b - 1) != 1",
209
+ "cVector": "ranks.c > 0 and dim(shapes.c, ranks.c - 1) != 1",
210
+ "hasC": true,
211
+ "vectorizedSpec": true,
212
+ "op": "\"mean\""
 
 
213
  },
214
+ "bindings": ["a_3", "b_3", "c_2", "y_2", "params_2"],
215
+ "dispatch": {
216
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
217
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
218
+ "z": 1
219
+ }
220
  }
221
  ]
222
  },
223
  {
224
  "id": "broadcast_three_input",
225
  "priority": 20,
226
+ "when": ["variadicInputCount == 3", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "ranks.c <= ranks.y", "broadcastOutputOk"],
227
  "passes": [
228
  {
229
  "id": "main",
230
  "name": "Mean",
231
+ "shader": "summean-broadcast.wgsl.jinja",
232
+ "derive": {
233
+ "aShape": "shapes.a",
234
+ "bShape": "shapes.b",
235
+ "cShape": "shapes.c",
236
+ "yShape": "shapes.y",
237
+ "aRank": "ranks.a",
238
+ "bRank": "ranks.b",
239
+ "cRank": "ranks.c",
240
+ "yRank": "ranks.y",
241
+ "hasC": "true",
242
+ "op": "\"mean\""
 
 
243
  },
244
+ "bindings": [{ "arg": "a" }, { "arg": "b" }, { "arg": "c" }, "y", "params"],
245
+ "dispatch": {
246
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
247
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
248
+ "z": 1
249
+ }
250
  }
251
  ]
252
  },
 
254
  "id": "broadcast_four_input_vec4",
255
  "priority": 26,
256
  "when": ["variadicInputCount == 4", "broadcastVec4OutputOk", "aBroadcastVec4Ok", "bBroadcastVec4Ok", "cBroadcastVec4Ok", "dBroadcastVec4Ok"],
257
+ "derive": {
258
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
259
+ "aElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.a > 0 and dim(shapes.a, ranks.a - 1) != 1 else dtypes.T",
260
+ "bElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.b > 0 and dim(shapes.b, ranks.b - 1) != 1 else dtypes.T",
261
+ "cElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.c > 0 and dim(shapes.c, ranks.c - 1) != 1 else dtypes.T",
262
+ "dElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if ranks.d > 0 and dim(shapes.d, ranks.d - 1) != 1 else dtypes.T"
263
  },
264
  "passes": [
265
  {
266
  "id": "main",
267
  "name": "Mean.Broadcast4Vec4",
268
+ "shader": "summean-broadcast.wgsl.jinja",
269
+ "derive": {
270
+ "aShape": "shapes.a",
271
+ "bShape": "shapes.b",
272
+ "cShape": "shapes.c",
273
+ "dShape": "shapes.d",
274
+ "yShape": "shapes.y",
275
+ "aRank": "ranks.a",
276
+ "bRank": "ranks.b",
277
+ "cRank": "ranks.c",
278
+ "dRank": "ranks.d",
279
+ "yRank": "ranks.y",
280
+ "aVector": "ranks.a > 0 and dim(shapes.a, ranks.a - 1) != 1",
281
+ "bVector": "ranks.b > 0 and dim(shapes.b, ranks.b - 1) != 1",
282
+ "cVector": "ranks.c > 0 and dim(shapes.c, ranks.c - 1) != 1",
283
+ "dVector": "ranks.d > 0 and dim(shapes.d, ranks.d - 1) != 1",
284
+ "hasC": true,
285
+ "hasD": true,
286
+ "vectorizedSpec": true,
287
+ "op": "\"mean\""
 
 
288
  },
289
+ "bindings": ["a_3", "b_3", "c_2", { "arg": "d", "elementType": "$dElement" }, "y_2", "params_2"],
290
+ "dispatch": {
291
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
292
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
293
+ "z": 1
294
+ }
295
  }
296
  ]
297
  },
298
  {
299
  "id": "broadcast_four_input",
300
  "priority": 22,
301
+ "when": ["variadicInputCount == 4", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "ranks.c <= ranks.y", "ranks.d <= ranks.y", "broadcastOutputOk"],
302
  "passes": [
303
  {
304
  "id": "main",
305
  "name": "Mean",
306
+ "shader": "summean-broadcast.wgsl.jinja",
307
+ "derive": {
308
+ "aShape": "shapes.a",
309
+ "bShape": "shapes.b",
310
+ "cShape": "shapes.c",
311
+ "yShape": "shapes.y",
312
+ "aRank": "ranks.a",
313
+ "bRank": "ranks.b",
314
+ "cRank": "ranks.c",
315
+ "yRank": "ranks.y",
316
+ "hasC": "true",
317
+ "op": "\"mean\"",
318
+ "dShape": "shapes.d",
319
+ "dRank": "ranks.d",
320
+ "hasD": "true"
 
 
321
  },
322
+ "bindings": [{ "arg": "a" }, { "arg": "b" }, { "arg": "c" }, { "arg": "d" }, "y", "params"],
323
+ "dispatch": {
324
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
325
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
326
+ "z": 1
327
+ }
328
  }
329
  ]
330
  },
331
  {
332
  "id": "broadcast_five_input",
333
  "priority": 23,
334
+ "when": ["variadicInputCount == 5", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "ranks.c <= ranks.y", "ranks.d <= ranks.y", "ranks.e <= ranks.y", "broadcastOutputOk"],
335
  "passes": [
336
  {
337
  "id": "main",
338
  "name": "Mean5",
339
+ "shader": "summean-broadcast.wgsl.jinja",
340
+ "derive": {
341
+ "aShape": "shapes.a",
342
+ "bShape": "shapes.b",
343
+ "cShape": "shapes.c",
344
+ "yShape": "shapes.y",
345
+ "aRank": "ranks.a",
346
+ "bRank": "ranks.b",
347
+ "cRank": "ranks.c",
348
+ "yRank": "ranks.y",
349
+ "hasC": "true",
350
+ "op": "\"mean\"",
351
+ "dShape": "shapes.d",
352
+ "dRank": "ranks.d",
353
+ "hasD": "true",
354
+ "hasE": "true",
355
+ "extraInputs": "[\"c\", \"d\", \"e\"]",
356
+ "eShape": "shapes.e",
357
+ "eRank": "ranks.e"
 
 
358
  },
359
+ "bindings": [{ "arg": "a" }, { "arg": "b" }, { "arg": "c" }, { "arg": "d" }, "e", "y", "params"],
360
+ "dispatch": {
361
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
362
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
363
+ "z": 1
364
+ }
365
  }
366
  ]
367
  }
build/webgpu/metadata.json CHANGED
@@ -1,20 +1,35 @@
1
  {
2
  "name": "ai.onnx.Mean",
3
- "id": "_ai_onnx_mean_webgpu_6e7e02e",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "PZuRYEh9H7m59RtG7mKg5vKX61mxb+faVyXw45NmJkg=",
11
- "datamove-elementwise-copy.wgsl.jinja": "J5yC2bAddPiP+odXLgVGS3TJ9jeNsfRTvedKrj/fhZg=",
12
- "manifest.json": "VktTxdT7xm0Gpy3RAjtJt7o1jS2zDQaXet8yeeO27bY=",
13
- "summean-broadcast.wgsl.jinja": "JV9W3CZ+lxla2C9yTyminEV2qBzBys8/q1rwdBcSTkA=",
14
- "summean-vec4.wgsl.jinja": "mZSVsrneREFgo5NJjDSgjU5zmMhMD91OA39Z2i+5xlM=",
15
- "test.json": "mpFM+ziMgNaNEpbiAD6n5ZN5MWRx1JiuISOPJsgfeLw="
16
  }
17
  },
18
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
19
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Mean" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
 
1
  {
2
  "name": "ai.onnx.Mean",
3
+ "id": "_ai_onnx_mean_webgpu_1ff9069",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "OCGno3NWR0wjZ5xDoYp8Q1ELrQ2KCph+dNK9rQYSZzk=",
11
+ "datamove-elementwise-copy.wgsl.jinja": "Q1WuZCqcDf9b6cbT4rLilpeWj6327t/bSFmKr6IVBoQ=",
12
+ "manifest.json": "07k7OHtxzThVNzb5aKOjXpdsovnIZCCdqNqyHtzJDl8=",
13
+ "summean-broadcast.wgsl.jinja": "WPpxIr6ztCcEDvyREMrLJVVWnu3GKvhH8tzIteHMKGU=",
14
+ "summean-vec4.wgsl.jinja": "oNlvKJctx7Gn2D6aHpTcVsf+ZyoYEadtFgyVQZV+8So=",
15
+ "test.json": "/isBt5ibLwpR8jqM0J4B1cqABoi9FeFoLOE7YxZwRgg="
16
  }
17
  },
18
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
19
+ "webgpu": {
20
+ "manifestSpec": "2.0",
21
+ "variants": {
22
+ "single_input_identity": ["datamove-elementwise-copy.wgsl.jinja"],
23
+ "same_shape_vec4_two_input": ["summean-vec4.wgsl.jinja"],
24
+ "same_shape_vec4_three_input": ["summean-vec4.wgsl.jinja"],
25
+ "same_shape_vec4_four_input": ["summean-vec4.wgsl.jinja"],
26
+ "same_shape_vec4_five_input": ["summean-vec4.wgsl.jinja"],
27
+ "broadcast_two_input": ["summean-broadcast.wgsl.jinja"],
28
+ "broadcast_three_input_vec4": ["summean-broadcast.wgsl.jinja"],
29
+ "broadcast_three_input": ["summean-broadcast.wgsl.jinja"],
30
+ "broadcast_four_input_vec4": ["summean-broadcast.wgsl.jinja"],
31
+ "broadcast_four_input": ["summean-broadcast.wgsl.jinja"],
32
+ "broadcast_five_input": ["summean-broadcast.wgsl.jinja"]
33
+ }
34
+ }
35
  }
build/webgpu/summean-broadcast.wgsl.jinja CHANGED
@@ -1,8 +1,5 @@
1
- {% set allInputs = ["a", "b"] + (source.extraInputs if source.extraInputs is defined else (["c"] if source.hasC else []) + (["d"] if source.hasD else [])) %}
2
- {% set extraInputs = source.extraInputs if source.extraInputs is defined else (["c"] if source.hasC else []) + (["d"] if source.hasD else []) %}
3
- {% if usesF16 %}
4
- enable f16;
5
- {% endif %}
6
  {{ env.wgsl.resourceDeclarations }}
7
  {% macro offset_fn(fn_name, opShape, opRank, op_same, op_numel, outShape, outRank, out_numel) %}
8
  fn {{ fn_name }}({% if out_numel != 0 and op_numel != 1 %}out_index: u32{% endif %}) -> u32 {
@@ -68,34 +65,34 @@ fn {{ fn_name }}({% if out_numel != 0 and op_numel != 1 %}out_index: u32{% endif
68
 
69
 
70
 
71
- {{ broadcast_offset_fn("a_offset", source.aShape, source.aRank, source.yShape, source.yRank) }}
72
 
73
- {{ broadcast_offset_fn("b_offset", source.bShape, source.bRank, source.yShape, source.yRank) }}
74
 
75
  {% for n in extraInputs %}
76
- {{ broadcast_offset_fn(n ~ "_offset", source[n ~ "Shape"], source[n ~ "Rank"], source.yShape, source.yRank) }}
77
 
78
  {% endfor %}
79
 
80
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
81
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
82
  // 2D-folded flat index: gid.y carries the high bits past the
83
- // maxComputeWorkgroupsPerDimension limit.
84
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
85
  if (i >= params.count) {
86
  return;
87
  }
88
- {% if source.vectorized %}
89
  // The output's innermost dimension is four-aligned. Each input either keeps
90
  // that dimension (one aligned vec4 load) or broadcasts it (one scalar splat).
91
  // Offset folding is therefore paid once per four outputs without changing
92
  // multidirectional broadcast semantics on any outer dimension.
93
  let base = i * 4u;
94
  {% for n in allInputs %}
95
- {% if source[n ~ "Vector"] %}
96
- let {{ n }}v = vec4<f32>({{ n }}[{{ broadcast_offset_call(n ~ "_offset", source[n ~ "Shape"], source.yShape, "base") }} / 4u]);
97
  {% else %}
98
- let {{ n }}v = vec4<f32>(f32({{ n }}[{{ broadcast_offset_call(n ~ "_offset", source[n ~ "Shape"], source.yShape, "base") }}]));
99
  {% endif %}
100
  {% endfor %}
101
  let total = {% for n in allInputs %}{{ n }}v{% if not loop.last %} + {% endif %}{% endfor %};
@@ -105,7 +102,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
105
  y[i] = {{ vectorScalar }}(total / {{ allInputs | length }}.0);
106
  {% endif %}
107
  {% else %}
108
- let total = {% for n in allInputs %}f32({{ n }}[{{ broadcast_offset_call(n ~ "_offset", source[n ~ "Shape"], source.yShape, "i") }}]){% if not loop.last %} + {% endif %}{% endfor %};
109
  {% if allInputs | length == 2 %}
110
  y[i] = {{ scalar }}(0.5 * total);
111
  {% else %}
 
1
+ {% set allInputs = ["a", "b"] + (extraInputs if extraInputs is defined else (["c"] if hasC else []) + (["d"] if hasD else [])) %}
2
+ {% set extraInputs = extraInputs if extraInputs is defined else (["c"] if hasC else []) + (["d"] if hasD else []) %}
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
  {% macro offset_fn(fn_name, opShape, opRank, op_same, op_numel, outShape, outRank, out_numel) %}
5
  fn {{ fn_name }}({% if out_numel != 0 and op_numel != 1 %}out_index: u32{% endif %}) -> u32 {
 
65
 
66
 
67
 
68
+ {{ broadcast_offset_fn("a_offset", aShape, aRank, yShape, yRank) }}
69
 
70
+ {{ broadcast_offset_fn("b_offset", bShape, bRank, yShape, yRank) }}
71
 
72
  {% for n in extraInputs %}
73
+ {{ broadcast_offset_fn(n ~ "_offset", derive[n ~ "Shape"], derive[n ~ "Rank"], yShape, yRank) }}
74
 
75
  {% endfor %}
76
 
77
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
78
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
79
  // 2D-folded flat index: gid.y carries the high bits past the
80
+ // per-axis dispatch fold width.
81
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
82
  if (i >= params.count) {
83
  return;
84
  }
85
+ {% if vectorizedSpec %}
86
  // The output's innermost dimension is four-aligned. Each input either keeps
87
  // that dimension (one aligned vec4 load) or broadcasts it (one scalar splat).
88
  // Offset folding is therefore paid once per four outputs without changing
89
  // multidirectional broadcast semantics on any outer dimension.
90
  let base = i * 4u;
91
  {% for n in allInputs %}
92
+ {% if derive[n ~ "Vector"] %}
93
+ let {{ n }}v = vec4<f32>({{ n }}[{{ broadcast_offset_call(n ~ "_offset", derive[n ~ "Shape"], yShape, "base") }} / 4u]);
94
  {% else %}
95
+ let {{ n }}v = vec4<f32>(f32({{ n }}[{{ broadcast_offset_call(n ~ "_offset", derive[n ~ "Shape"], yShape, "base") }}]));
96
  {% endif %}
97
  {% endfor %}
98
  let total = {% for n in allInputs %}{{ n }}v{% if not loop.last %} + {% endif %}{% endfor %};
 
102
  y[i] = {{ vectorScalar }}(total / {{ allInputs | length }}.0);
103
  {% endif %}
104
  {% else %}
105
+ let total = {% for n in allInputs %}f32({{ n }}[{{ broadcast_offset_call(n ~ "_offset", derive[n ~ "Shape"], yShape, "i") }}]){% if not loop.last %} + {% endif %}{% endfor %};
106
  {% if allInputs | length == 2 %}
107
  y[i] = {{ scalar }}(0.5 * total);
108
  {% else %}
build/webgpu/summean-vec4.wgsl.jinja CHANGED
@@ -1,15 +1,12 @@
1
- {% set extraInputs = source.extraInputs if source.extraInputs is defined else (["c"] if source.hasC else []) + (["d"] if source.hasD else []) %}
2
  {% set arity = 2 + extraInputs | length %}
3
- {% if usesF16 %}
4
- enable f16;
5
- {% endif %}
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
9
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
10
  // 2D-folded flat index: gid.y carries the high bits past the
11
- // maxComputeWorkgroupsPerDimension limit.
12
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
13
  if (i >= params.count) {
14
  return;
15
  }
 
1
+ {% set extraInputs = extraInputs if extraInputs is defined else (["c"] if hasC else []) + (["d"] if hasD else []) %}
2
  {% set arity = 2 + extraInputs | length %}
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
6
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
7
  // 2D-folded flat index: gid.y carries the high bits past the
8
+ // per-axis dispatch fold width.
9
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
10
  if (i >= params.count) {
11
  return;
12
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Mean",
3
  "cases": [
4
  {
5
  "name": "max_arity_float16_positions",
@@ -71,7 +70,7 @@
71
  "provenance": {
72
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
73
  "test": "MathOpTest.Mean_6",
74
- "notes": "Vec4 companion: the mean of equal finite subnormal values should remain subnormal in the vectorized path."
75
  },
76
  "inputs": {
77
  "a": {
@@ -151,7 +150,7 @@
151
  {
152
  "name": "float16_vec4_three_input",
153
  "provenance": {
154
- "notes": "Route lock for the f16 three-input vec4 path. the summean-vec4 kernel has a separate f16 branch that widens each operand to f32, accumulates, then narrows on store; until now only two-input f16 renders existed, so on that branch neither the extra-operand loop that folds C in nor the non-arity-2 divisor (total / 3.0, as opposed to the 0.5 * total shortcut the two-input case takes) was ever emitted. Both numerator and denominator are therefore checked here: with C dropped, or with the 0.5 shortcut applied to three operands, every lane moves. Every operand, every partial sum and every quotient is exactly representable in float16 (each total is 3x a dyadic value), so the expected output is the exact (a + b + c) / 3 and needs no tolerance."
155
  },
156
  "inputs": {
157
  "a": {
@@ -233,7 +232,7 @@
233
  "provenance": {
234
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
235
  "test": "MathOpTest.Mean_6",
236
- "notes": "Extends ORT's same-shape Mean coverage to a valid four-input ONNX variadic node."
237
  },
238
  "inputs": {
239
  "a": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "constant", "value": 1.0 } },
@@ -248,7 +247,7 @@
248
  {
249
  "name": "four_input_same_shape_vec4_reference_generated",
250
  "provenance": {
251
- "notes": "Reference-generated four-operand coverage. Mean was doubly exposed: reducing only A..C also divided by three, so both the numerator and the denominator were wrong. Every element moves if either is, which makes this a total detector rather than a partial one."
252
  },
253
  "inputs": {
254
  "a": {
@@ -279,7 +278,7 @@
279
  "provenance": {
280
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
281
  "test": "MathOpTest.Mean_6",
282
- "notes": "Extends ORT's variadic Mean coverage with a fourth input that exposes NaN propagation and inf-minus-inf cancellation."
283
  },
284
  "inputs": {
285
  "a": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, "Infinity", 1.0, 8.0] } },
@@ -317,7 +316,7 @@
317
  {
318
  "name": "three_input_broadcast_vec4_mean",
319
  "provenance": {
320
- "notes": "Reaches broadcast_three_input_vec4. The output's innermost dimension is four-aligned, `a` keeps it (one aligned vec4 load) and `b` and `c` broadcast it (scalar splats), so the variant's three per-input element types are not all the same -- which is what the vec4 broadcast binding set exists for. Its four-input sibling was already reachable from four_input_scalar_d_broadcast_mean; this one had nothing."
321
  },
322
  "inputs": {
323
  "a": {
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "max_arity_float16_positions",
 
70
  "provenance": {
71
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
72
  "test": "MathOpTest.Mean_6",
73
+ "notes": "On the vec4 path, the mean of equal finite subnormal values must remain subnormal."
74
  },
75
  "inputs": {
76
  "a": {
 
150
  {
151
  "name": "float16_vec4_three_input",
152
  "provenance": {
153
+ "notes": "Three float16 vec4 inputs exercise widening each operand to float32, folding the third input, dividing by three, and narrowing on store. Dyadic inputs make every operand, partial sum, and quotient exactly representable in float16, so the expected result is exactly (a+b+c)/3."
154
  },
155
  "inputs": {
156
  "a": {
 
232
  "provenance": {
233
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
234
  "test": "MathOpTest.Mean_6",
235
+ "notes": "Four same-shaped inputs exercise the maximum supported arity of this variadic Mean package."
236
  },
237
  "inputs": {
238
  "a": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "constant", "value": 1.0 } },
 
247
  {
248
  "name": "four_input_same_shape_vec4_reference_generated",
249
  "provenance": {
250
+ "notes": "Four distinct operands make both the numerator and divisor observable: omitting the fourth input or dividing by three changes every output element."
251
  },
252
  "inputs": {
253
  "a": {
 
278
  "provenance": {
279
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
280
  "test": "MathOpTest.Mean_6",
281
+ "notes": "A fourth input exposes NaN propagation and positive-Infinity plus negative-Infinity cancellation."
282
  },
283
  "inputs": {
284
  "a": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, "Infinity", 1.0, 8.0] } },
 
316
  {
317
  "name": "three_input_broadcast_vec4_mean",
318
  "provenance": {
319
+ "notes": "The four-aligned innermost output dimension permits a vec4 load from a while b and c broadcast as scalar splats, exercising three-input vectorized broadcasting with mixed binding element types."
320
  },
321
  "inputs": {
322
  "a": {