Xenova HF Staff commited on
Commit
60ad31b
·
verified ·
1 Parent(s): f8fa7e6

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,19 +18,19 @@ See the [ONNX `Min` spec](https://onnx.ai/onnx/operators/onnx__Min.html) for the
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 the other inputs. | optional |
25
- | `C` | `c` | `T` | — | — | Third input tensor; broadcast-compatible with the other inputs. | optional |
26
- | `D` | `d` | `T` | — | — | Fourth input tensor; broadcast-compatible with the other inputs. | 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
- | `min` | `y` | `T` | derived | derived; see description | Elementwise minimum of all input tensors, broadcast to the output shape. | required |
34
 
35
  ## Type constraints
36
 
@@ -40,7 +40,7 @@ See the [ONNX `Min` spec](https://onnx.ai/onnx/operators/onnx__Min.html) for the
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 `Min` spec](https://onnx.ai/onnx/operators/onnx__Min.html) for the
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 the other inputs. | optional |
25
+ | `c` | `C` | `T` | — | — | Third input tensor; broadcast-compatible with the other inputs. | optional |
26
+ | `d` | `D` | `T` | — | — | Fourth input tensor; broadcast-compatible with the other inputs. | 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` | `min` | `T` | derived | derived | Elementwise minimum of all input tensors, broadcast to the output shape. | 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.Min",
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,277 +2,76 @@
2
  "domain": "ai.onnx",
3
  "name": "Min",
4
  "sinceVersion": 13,
5
- "description": "Computes the elementwise minimum across one or more input tensors with NumPy-style multidirectional broadcasting. All inputs and the output share the same data type.",
6
- "inputs": [
7
- { "role": "A", "dtype": "T", "description": "First input tensor." },
8
- {
9
- "role": "B",
10
- "dtype": "T",
11
- "optional": true,
12
- "description": "Second input tensor; broadcast-compatible with the other inputs."
13
- },
14
- {
15
- "role": "C",
16
- "dtype": "T",
17
- "optional": true,
18
- "description": "Third input tensor; broadcast-compatible with the other inputs."
19
- },
20
- {
21
- "role": "D",
22
- "dtype": "T",
23
- "optional": true,
24
- "description": "Fourth input tensor; broadcast-compatible with the other inputs."
25
- },
26
- {
27
- "role": "E",
28
- "dtype": "T",
29
- "optional": true,
30
- "description": "Fifth input tensor, broadcast-compatible with all other inputs."
31
- }
32
- ],
33
- "outputs": [
34
- {
35
- "role": "min",
36
  "dtype": "T",
37
- "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)",
38
- "shape": "variadicShape",
39
- "description": "Elementwise minimum of all input tensors, broadcast to the output shape."
40
  }
41
- ],
42
- "typeConstraints": { "T": ["float32", "float16", "int32", "uint32", "int16", "int8", "uint8"] },
43
- "args": {
44
- "a": { "kind": "tensor", "semantic": "A", "role": "input" },
45
- "b": { "kind": "tensor", "semantic": "B", "role": "input", "required": false },
46
- "c": { "kind": "tensor", "semantic": "C", "role": "input2", "required": false },
47
- "y": { "kind": "tensor", "semantic": "min", "role": "output" },
48
- "d": { "kind": "tensor", "semantic": "D", "role": "input3", "required": false },
49
- "e": { "kind": "tensor", "semantic": "E", "role": "input4", "required": false }
50
  },
51
- "tunables": { "WORKGROUP_SIZE": 256 },
 
52
  "derive": {
53
  "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)",
54
- "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)))",
55
  "flatVec4OutputOk": "numel(shapes.y) > 0 and numel(shapes.y) % 4 == 0 and f16Ok(dtypes.T)",
56
  "broadcastOutputOk": "f16Ok(dtypes.T)",
57
  "narrowNoSubgroupExecution": "not device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < 32 and device.adapterInfo.subgroupMaxSize <= 32"
58
  },
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": "min", "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": "min", "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": "min", "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
  },
255
  "variants": [
256
  {
257
  "id": "single_input_identity",
258
  "priority": 30,
259
- "when": ["variadicInputCount == 1", "ranks.A == ranks.y", "numel(shapes.A) == numel(shapes.y)", "f16Ok(dtypes.T)"],
260
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
261
  "passes": [
262
  {
263
  "id": "main",
264
  "name": "Min.Identity",
265
- "source": { "shader": "datamove-elementwise-copy.wgsl.jinja" },
266
- "bindings": "identity",
267
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
268
  }
269
  ]
270
  },
271
  {
272
  "id": "same_shape_vec4_two_input",
273
  "priority": 20,
274
- "when": ["variadicInputCount == 2", "sameShape(shapes.A, shapes.y)", "sameShape(shapes.B, shapes.y)", "flatVec4OutputOk", "not narrowNoSubgroupExecution"],
275
- "constants": {
276
  "scalar": "dtypes.T",
277
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
278
  "usesF16": "dtypes.T == \"f16\""
@@ -281,17 +80,22 @@
281
  {
282
  "id": "main",
283
  "name": "Min.vec4",
284
- "source": { "shader": "minmax-vec4.wgsl.jinja", "inputs": { "op": "\"min\"", "hasC": "false" } },
285
- "bindings": "vec4Two",
286
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
287
  }
288
  ]
289
  },
290
  {
291
  "id": "same_shape_vec4_three_input",
292
  "priority": 25,
293
- "when": ["variadicInputCount == 3", "sameShape(shapes.A, shapes.y)", "sameShape(shapes.B, shapes.y)", "sameShape(shapes.C, shapes.y)", "flatVec4OutputOk"],
294
- "constants": {
295
  "scalar": "dtypes.T",
296
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
297
  "usesF16": "dtypes.T == \"f16\""
@@ -300,17 +104,22 @@
300
  {
301
  "id": "main",
302
  "name": "Min.vec4_3",
303
- "source": { "shader": "minmax-vec4.wgsl.jinja", "inputs": { "op": "\"min\"", "hasC": "true" } },
304
- "bindings": "vec4Three",
305
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
306
  }
307
  ]
308
  },
309
  {
310
  "id": "same_shape_vec4_four_input",
311
  "priority": 27,
312
- "when": ["variadicInputCount == 4", "sameShape(shapes.A, shapes.y)", "sameShape(shapes.B, shapes.y)", "sameShape(shapes.C, shapes.y)", "sameShape(shapes.D, shapes.y)", "flatVec4OutputOk"],
313
- "constants": {
314
  "scalar": "dtypes.T",
315
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
316
  "usesF16": "dtypes.T == \"f16\""
@@ -319,20 +128,22 @@
319
  {
320
  "id": "main",
321
  "name": "Min.vec4_3",
322
- "source": {
323
- "shader": "minmax-vec4.wgsl.jinja",
324
- "inputs": { "op": "\"min\"", "hasC": "true", "hasD": "true" }
325
- },
326
- "bindings": "vec4Four",
327
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
328
  }
329
  ]
330
  },
331
  {
332
  "id": "same_shape_vec4_five_input",
333
  "priority": 28,
334
- "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"],
335
- "constants": {
336
  "scalar": "dtypes.T",
337
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
338
  "usesF16": "dtypes.T == \"f16\""
@@ -341,145 +152,155 @@
341
  {
342
  "id": "main",
343
  "name": "Min.vec4_4",
344
- "source": {
345
- "shader": "minmax-vec4.wgsl.jinja",
346
- "inputs": {
347
- "op": "\"min\"",
348
- "hasC": "true",
349
- "hasD": "true",
350
- "hasE": "true",
351
- "extraInputs": "[\"c\", \"d\", \"e\"]"
352
- }
353
  },
354
- "bindings": "vec4Five",
355
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
356
  }
357
  ]
358
  },
359
  {
360
  "id": "broadcast_two_input",
361
- "when": ["variadicInputCount == 2", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "broadcastOutputOk"],
362
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
363
  "passes": [
364
  {
365
  "id": "main",
366
  "name": "Min",
367
- "source": {
368
- "shader": "minmax-broadcast.wgsl.jinja",
369
- "inputs": {
370
- "aShape": "shapes.A",
371
- "bShape": "shapes.B",
372
- "yShape": "shapes.y",
373
- "aRank": "ranks.A",
374
- "bRank": "ranks.B",
375
- "yRank": "ranks.y",
376
- "hasC": "false",
377
- "op": "\"min\"",
378
- "itemsPerInvocation": 4
379
- }
380
  },
381
- "bindings": "scalarTwo",
382
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
383
  }
384
  ]
385
  },
386
  {
387
  "id": "broadcast_three_input",
388
  "priority": 20,
389
- "when": ["variadicInputCount == 3", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "ranks.C <= ranks.y", "broadcastOutputOk"],
390
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
391
  "passes": [
392
  {
393
  "id": "main",
394
  "name": "Min",
395
- "source": {
396
- "shader": "minmax-broadcast.wgsl.jinja",
397
- "inputs": {
398
- "aShape": "shapes.A",
399
- "bShape": "shapes.B",
400
- "cShape": "shapes.C",
401
- "yShape": "shapes.y",
402
- "aRank": "ranks.A",
403
- "bRank": "ranks.B",
404
- "cRank": "ranks.C",
405
- "yRank": "ranks.y",
406
- "hasC": "true",
407
- "op": "\"min\"",
408
- "itemsPerInvocation": 4
409
- }
410
  },
411
- "bindings": "scalarThree",
412
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
413
  }
414
  ]
415
  },
416
  {
417
  "id": "broadcast_four_input",
418
  "priority": 22,
419
- "when": ["variadicInputCount == 4", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "ranks.C <= ranks.y", "ranks.D <= ranks.y", "broadcastOutputOk"],
420
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
421
  "passes": [
422
  {
423
  "id": "main",
424
  "name": "Min",
425
- "source": {
426
- "shader": "minmax-broadcast.wgsl.jinja",
427
- "inputs": {
428
- "aShape": "shapes.A",
429
- "bShape": "shapes.B",
430
- "cShape": "shapes.C",
431
- "yShape": "shapes.y",
432
- "aRank": "ranks.A",
433
- "bRank": "ranks.B",
434
- "cRank": "ranks.C",
435
- "yRank": "ranks.y",
436
- "hasC": "true",
437
- "op": "\"min\"",
438
- "dShape": "shapes.D",
439
- "dRank": "ranks.D",
440
- "hasD": "true",
441
- "itemsPerInvocation": 4
442
- }
443
  },
444
- "bindings": "scalarFour",
445
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
446
  }
447
  ]
448
  },
449
  {
450
  "id": "broadcast_five_input",
451
  "priority": 23,
452
- "when": ["variadicInputCount == 5", "ranks.A <= ranks.y", "ranks.B <= ranks.y", "ranks.C <= ranks.y", "ranks.D <= ranks.y", "ranks.E <= ranks.y", "broadcastOutputOk"],
453
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
454
  "passes": [
455
  {
456
  "id": "main",
457
  "name": "Min",
458
- "source": {
459
- "shader": "minmax-broadcast.wgsl.jinja",
460
- "inputs": {
461
- "aShape": "shapes.A",
462
- "bShape": "shapes.B",
463
- "cShape": "shapes.C",
464
- "yShape": "shapes.y",
465
- "aRank": "ranks.A",
466
- "bRank": "ranks.B",
467
- "cRank": "ranks.C",
468
- "yRank": "ranks.y",
469
- "hasC": "true",
470
- "op": "\"min\"",
471
- "dShape": "shapes.D",
472
- "dRank": "ranks.D",
473
- "hasD": "true",
474
- "hasE": "true",
475
- "extraInputs": "[\"c\", \"d\", \"e\"]",
476
- "eRank": "ranks.E",
477
- "eShape": "shapes.E",
478
- "itemsPerInvocation": 4
479
- }
480
  },
481
- "bindings": "scalarFive",
482
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
483
  }
484
  ]
485
  }
 
2
  "domain": "ai.onnx",
3
  "name": "Min",
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": "min",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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", "int32", "uint32", "int16", "int8", "uint8"] },
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
  "narrowNoSubgroupExecution": "not device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < 32 and device.adapterInfo.subgroupMaxSize <= 32"
28
  },
29
+ "bindings": {
30
+ "x": { "arg": "a", "buffer": "read-only-storage", "elementType": "$scalar" },
31
+ "y": { "buffer": "storage", "elementType": "$scalar" },
32
+ "params": { "buffer": "uniform", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] },
33
+ "a": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
34
+ "b": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
35
+ "y_2": { "name": "y", "buffer": "storage", "elementType": "$vectorScalar" },
36
+ "params_2": {
37
+ "name": "params",
38
+ "buffer": "uniform",
39
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }]
40
+ },
41
+ "c": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
42
+ "d": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
43
+ "e": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
44
+ "a_2": { "name": "a", "buffer": "read-only-storage", "elementType": "$scalar" },
45
+ "b_2": { "name": "b", "buffer": "read-only-storage", "elementType": "$scalar" },
46
+ "c_2": { "name": "c", "buffer": "read-only-storage", "elementType": "$scalar" },
47
+ "d_2": { "name": "d", "buffer": "read-only-storage", "elementType": "$scalar" },
48
+ "e_2": { "name": "e", "buffer": "read-only-storage", "elementType": "$scalar" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  },
50
  "variants": [
51
  {
52
  "id": "single_input_identity",
53
  "priority": 30,
54
+ "when": ["variadicInputCount == 1", "ranks.a == ranks.y", "numel(shapes.a) == numel(shapes.y)", "f16Ok(dtypes.T)"],
55
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
56
  "passes": [
57
  {
58
  "id": "main",
59
  "name": "Min.Identity",
60
+ "shader": "datamove-elementwise-copy.wgsl.jinja",
61
+ "bindings": ["x", "y", "params"],
62
+ "dispatch": {
63
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
64
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
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", "not narrowNoSubgroupExecution"],
74
+ "derive": {
75
  "scalar": "dtypes.T",
76
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
77
  "usesF16": "dtypes.T == \"f16\""
 
80
  {
81
  "id": "main",
82
  "name": "Min.vec4",
83
+ "shader": "minmax-vec4.wgsl.jinja",
84
+ "derive": { "op": "\"min\"", "hasC": "false" },
85
+ "bindings": ["a", "b", "y_2", "params_2"],
86
+ "dispatch": {
87
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
88
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
89
+ "z": 1
90
+ }
91
  }
92
  ]
93
  },
94
  {
95
  "id": "same_shape_vec4_three_input",
96
  "priority": 25,
97
+ "when": ["variadicInputCount == 3", "sameShape(shapes.a, shapes.y)", "sameShape(shapes.b, shapes.y)", "sameShape(shapes.c, shapes.y)", "flatVec4OutputOk"],
98
+ "derive": {
99
  "scalar": "dtypes.T",
100
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
101
  "usesF16": "dtypes.T == \"f16\""
 
104
  {
105
  "id": "main",
106
  "name": "Min.vec4_3",
107
+ "shader": "minmax-vec4.wgsl.jinja",
108
+ "derive": { "op": "\"min\"", "hasC": "true" },
109
+ "bindings": ["a", "b", "c", "y_2", "params_2"],
110
+ "dispatch": {
111
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
112
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
113
+ "z": 1
114
+ }
115
  }
116
  ]
117
  },
118
  {
119
  "id": "same_shape_vec4_four_input",
120
  "priority": 27,
121
+ "when": ["variadicInputCount == 4", "sameShape(shapes.a, shapes.y)", "sameShape(shapes.b, shapes.y)", "sameShape(shapes.c, shapes.y)", "sameShape(shapes.d, shapes.y)", "flatVec4OutputOk"],
122
+ "derive": {
123
  "scalar": "dtypes.T",
124
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
125
  "usesF16": "dtypes.T == \"f16\""
 
128
  {
129
  "id": "main",
130
  "name": "Min.vec4_3",
131
+ "shader": "minmax-vec4.wgsl.jinja",
132
+ "derive": { "op": "\"min\"", "hasC": "true", "hasD": "true" },
133
+ "bindings": ["a", "b", "c", "d", "y_2", "params_2"],
134
+ "dispatch": {
135
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
136
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
137
+ "z": 1
138
+ }
139
  }
140
  ]
141
  },
142
  {
143
  "id": "same_shape_vec4_five_input",
144
  "priority": 28,
145
+ "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"],
146
+ "derive": {
147
  "scalar": "dtypes.T",
148
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
149
  "usesF16": "dtypes.T == \"f16\""
 
152
  {
153
  "id": "main",
154
  "name": "Min.vec4_4",
155
+ "shader": "minmax-vec4.wgsl.jinja",
156
+ "derive": {
157
+ "op": "\"min\"",
158
+ "hasC": "true",
159
+ "hasD": "true",
160
+ "hasE": "true",
161
+ "extraInputs": "[\"c\", \"d\", \"e\"]"
 
 
162
  },
163
+ "bindings": ["a", "b", "c", "d", "e", "y_2", "params_2"],
164
+ "dispatch": {
165
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
166
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
167
+ "z": 1
168
+ }
169
  }
170
  ]
171
  },
172
  {
173
  "id": "broadcast_two_input",
174
+ "when": ["variadicInputCount == 2", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "broadcastOutputOk"],
175
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
176
  "passes": [
177
  {
178
  "id": "main",
179
  "name": "Min",
180
+ "shader": "minmax-broadcast.wgsl.jinja",
181
+ "derive": {
182
+ "aShape": "shapes.a",
183
+ "bShape": "shapes.b",
184
+ "yShape": "shapes.y",
185
+ "aRank": "ranks.a",
186
+ "bRank": "ranks.b",
187
+ "yRank": "ranks.y",
188
+ "hasC": "false",
189
+ "op": "\"min\"",
190
+ "itemsPerInvocation": 4
 
 
191
  },
192
+ "bindings": ["a_2", "b_2", "y", "params"],
193
+ "dispatch": {
194
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
195
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
196
+ "z": 1
197
+ }
198
  }
199
  ]
200
  },
201
  {
202
  "id": "broadcast_three_input",
203
  "priority": 20,
204
+ "when": ["variadicInputCount == 3", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "ranks.c <= ranks.y", "broadcastOutputOk"],
205
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
206
  "passes": [
207
  {
208
  "id": "main",
209
  "name": "Min",
210
+ "shader": "minmax-broadcast.wgsl.jinja",
211
+ "derive": {
212
+ "aShape": "shapes.a",
213
+ "bShape": "shapes.b",
214
+ "cShape": "shapes.c",
215
+ "yShape": "shapes.y",
216
+ "aRank": "ranks.a",
217
+ "bRank": "ranks.b",
218
+ "cRank": "ranks.c",
219
+ "yRank": "ranks.y",
220
+ "hasC": "true",
221
+ "op": "\"min\"",
222
+ "itemsPerInvocation": 4
 
 
223
  },
224
+ "bindings": ["a_2", "b_2", "c_2", "y", "params"],
225
+ "dispatch": {
226
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
227
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
228
+ "z": 1
229
+ }
230
  }
231
  ]
232
  },
233
  {
234
  "id": "broadcast_four_input",
235
  "priority": 22,
236
+ "when": ["variadicInputCount == 4", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "ranks.c <= ranks.y", "ranks.d <= ranks.y", "broadcastOutputOk"],
237
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
238
  "passes": [
239
  {
240
  "id": "main",
241
  "name": "Min",
242
+ "shader": "minmax-broadcast.wgsl.jinja",
243
+ "derive": {
244
+ "aShape": "shapes.a",
245
+ "bShape": "shapes.b",
246
+ "cShape": "shapes.c",
247
+ "yShape": "shapes.y",
248
+ "aRank": "ranks.a",
249
+ "bRank": "ranks.b",
250
+ "cRank": "ranks.c",
251
+ "yRank": "ranks.y",
252
+ "hasC": "true",
253
+ "op": "\"min\"",
254
+ "dShape": "shapes.d",
255
+ "dRank": "ranks.d",
256
+ "hasD": "true",
257
+ "itemsPerInvocation": 4
 
 
258
  },
259
+ "bindings": ["a_2", "b_2", "c_2", "d_2", "y", "params"],
260
+ "dispatch": {
261
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
262
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
263
+ "z": 1
264
+ }
265
  }
266
  ]
267
  },
268
  {
269
  "id": "broadcast_five_input",
270
  "priority": 23,
271
+ "when": ["variadicInputCount == 5", "ranks.a <= ranks.y", "ranks.b <= ranks.y", "ranks.c <= ranks.y", "ranks.d <= ranks.y", "ranks.e <= ranks.y", "broadcastOutputOk"],
272
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
273
  "passes": [
274
  {
275
  "id": "main",
276
  "name": "Min",
277
+ "shader": "minmax-broadcast.wgsl.jinja",
278
+ "derive": {
279
+ "aShape": "shapes.a",
280
+ "bShape": "shapes.b",
281
+ "cShape": "shapes.c",
282
+ "yShape": "shapes.y",
283
+ "aRank": "ranks.a",
284
+ "bRank": "ranks.b",
285
+ "cRank": "ranks.c",
286
+ "yRank": "ranks.y",
287
+ "hasC": "true",
288
+ "op": "\"min\"",
289
+ "dShape": "shapes.d",
290
+ "dRank": "ranks.d",
291
+ "hasD": "true",
292
+ "hasE": "true",
293
+ "extraInputs": "[\"c\", \"d\", \"e\"]",
294
+ "eRank": "ranks.e",
295
+ "eShape": "shapes.e",
296
+ "itemsPerInvocation": 4
 
 
297
  },
298
+ "bindings": ["a_2", "b_2", "c_2", "d_2", "e_2", "y", "params"],
299
+ "dispatch": {
300
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
301
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
302
+ "z": 1
303
+ }
304
  }
305
  ]
306
  }
build/webgpu/metadata.json CHANGED
@@ -1,20 +1,33 @@
1
  {
2
  "name": "ai.onnx.Min",
3
- "id": "_ai_onnx_min_webgpu_30c0530",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "4AfCZQh/G8SMpQs4IgS9e2U8PUeiDMUzt8TWOgNvQsg=",
11
- "datamove-elementwise-copy.wgsl.jinja": "J5yC2bAddPiP+odXLgVGS3TJ9jeNsfRTvedKrj/fhZg=",
12
- "manifest.json": "cnSm6aRoRIHenuDqHLRwEXoPyRnT/7qmAEYYEBDzkAY=",
13
- "minmax-broadcast.wgsl.jinja": "dDLFWoUuV4Q+I/R6Ee+zGCbeStKZ2h/ObmLzlK1XdP0=",
14
- "minmax-vec4.wgsl.jinja": "oS72DBVc9P7XoADR4T16+G9FALJyQzFR4hxnagD8VB4=",
15
- "test.json": "pZblIGVry/Tin/efCEfKrchJ7gk9kdu/yLZO93hefBg="
16
  }
17
  },
18
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
19
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Min" }
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
 
1
  {
2
  "name": "ai.onnx.Min",
3
+ "id": "_ai_onnx_min_webgpu_25bdece",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "11Avk6oIAzN8DKPeDG+sjNh7o3JWi7VQRkjfOrFX1Ns=",
11
+ "datamove-elementwise-copy.wgsl.jinja": "Q1WuZCqcDf9b6cbT4rLilpeWj6327t/bSFmKr6IVBoQ=",
12
+ "manifest.json": "tpGUvXih1rgUtp9pH1559keeuj97lh9IHo+AmGSf+uE=",
13
+ "minmax-broadcast.wgsl.jinja": "VfDVtRI1nLx4PtZ05JccODRFVvZXCjfe9T/TYUKJzsw=",
14
+ "minmax-vec4.wgsl.jinja": "ykNWo2M5sTSFRXgwTWuULv4PPZOHiRtkqR9OuJEgu5s=",
15
+ "test.json": "5BzaEWeLO5YgM4RltLBpE2RH76bJHtEjlxB4dx+NLgA="
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": ["minmax-vec4.wgsl.jinja"],
24
+ "same_shape_vec4_three_input": ["minmax-vec4.wgsl.jinja"],
25
+ "same_shape_vec4_four_input": ["minmax-vec4.wgsl.jinja"],
26
+ "same_shape_vec4_five_input": ["minmax-vec4.wgsl.jinja"],
27
+ "broadcast_two_input": ["minmax-broadcast.wgsl.jinja"],
28
+ "broadcast_three_input": ["minmax-broadcast.wgsl.jinja"],
29
+ "broadcast_four_input": ["minmax-broadcast.wgsl.jinja"],
30
+ "broadcast_five_input": ["minmax-broadcast.wgsl.jinja"]
31
+ }
32
+ }
33
  }
build/webgpu/minmax-broadcast.wgsl.jinja CHANGED
@@ -1,23 +1,21 @@
1
  {% macro flat_tail_open() %}
2
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
4
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
- // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
6
- let invocation = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
7
  // Tail-safe scalar x4 keeps vector-like dispatch density without requiring
8
  // the logical tensor length (or its storage binding) to be vec4 aligned.
9
- let begin = invocation * {{ source.itemsPerInvocation }}u;
10
- let end = min(begin + {{ source.itemsPerInvocation }}u, params.count);
 
11
  for (var i = begin; i < end; i = i + 1u) {
12
  {%- endmacro %}
13
  {% macro flat_tail_close() %}
14
  }
15
  {% endmacro %}
16
 
17
- {% set extraInputs = source.extraInputs if source.extraInputs is defined else (["c"] if source.hasC else []) + (["d"] if source.hasD else []) %}
18
- {% if usesF16 %}
19
- enable f16;
20
- {% endif %}
21
  {{ env.wgsl.resourceDeclarations }}
22
  {% if scalar != "i32" and scalar != "u32" %}
23
 
@@ -91,34 +89,34 @@ fn {{ fn_name }}({% if out_numel != 0 and op_numel != 1 %}out_index: u32{% endif
91
 
92
 
93
 
94
- {{ broadcast_offset_fn("a_offset", source.aShape, source.aRank, source.yShape, source.yRank) }}
95
 
96
- {{ broadcast_offset_fn("b_offset", source.bShape, source.bRank, source.yShape, source.yRank) }}
97
 
98
  {% for n in extraInputs %}
99
- {{ broadcast_offset_fn(n ~ "_offset", source[n ~ "Shape"], source[n ~ "Rank"], source.yShape, source.yRank) }}
100
 
101
  {% endfor %}
102
 
103
 
104
  {{ flat_tail_open() }}
105
  {% if scalar == "i32" or scalar == "u32" %}
106
- let av = a[{{ broadcast_offset_call("a_offset", source.aShape, source.yShape, "i") }}];
107
- let bv = b[{{ broadcast_offset_call("b_offset", source.bShape, source.yShape, "i") }}];
108
  var out = select(bv, av, av < bv);
109
  {% for n in extraInputs %}
110
- let {{ n }}v = {{ n }}[{{ broadcast_offset_call(n ~ "_offset", source[n ~ "Shape"], source.yShape, "i") }}];
111
  out = select({{ n }}v, out, out < {{ n }}v);
112
  {% endfor %}
113
  y[i] = out;
114
  {% else %}
115
- let av = f32(a[{{ broadcast_offset_call("a_offset", source.aShape, source.yShape, "i") }}]);
116
- let bv = f32(b[{{ broadcast_offset_call("b_offset", source.bShape, source.yShape, "i") }}]);
117
  var out = min(av, bv);
118
  out = select(out, av, is_nan_f32(av));
119
  out = select(out, bv, is_nan_f32(bv));
120
  {% for n in extraInputs %}
121
- let {{ n }}v = f32({{ n }}[{{ broadcast_offset_call(n ~ "_offset", source[n ~ "Shape"], source.yShape, "i") }}]);
122
  let m{{ n }} = min(out, {{ n }}v);
123
  {% if loop.first %}
124
  // Built-in min/max may drop a NaN operand. Re-inject {{ n }}v's NaN and
 
1
  {% macro flat_tail_open() %}
2
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
4
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
+ // dispatch's per-axis workgroup fold width (the dispatch caps x and spills the rest into y).
6
+ let invocation = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
7
  // Tail-safe scalar x4 keeps vector-like dispatch density without requiring
8
  // the logical tensor length (or its storage binding) to be vec4 aligned.
9
+ {% set itemsPerInvocation = itemsPerInvocation if itemsPerInvocation is defined else 4 %}
10
+ let begin = invocation * {{ itemsPerInvocation }}u;
11
+ let end = min(begin + {{ itemsPerInvocation }}u, params.count);
12
  for (var i = begin; i < end; i = i + 1u) {
13
  {%- endmacro %}
14
  {% macro flat_tail_close() %}
15
  }
16
  {% endmacro %}
17
 
18
+ {% set extraInputs = extraInputs if extraInputs is defined else (["c"] if hasC else []) + (["d"] if hasD else []) %}
 
 
 
19
  {{ env.wgsl.resourceDeclarations }}
20
  {% if scalar != "i32" and scalar != "u32" %}
21
 
 
89
 
90
 
91
 
92
+ {{ broadcast_offset_fn("a_offset", aShape, aRank, yShape, yRank) }}
93
 
94
+ {{ broadcast_offset_fn("b_offset", bShape, bRank, yShape, yRank) }}
95
 
96
  {% for n in extraInputs %}
97
+ {{ broadcast_offset_fn(n ~ "_offset", derive[n ~ "Shape"], derive[n ~ "Rank"], yShape, yRank) }}
98
 
99
  {% endfor %}
100
 
101
 
102
  {{ flat_tail_open() }}
103
  {% if scalar == "i32" or scalar == "u32" %}
104
+ let av = a[{{ broadcast_offset_call("a_offset", aShape, yShape, "i") }}];
105
+ let bv = b[{{ broadcast_offset_call("b_offset", bShape, yShape, "i") }}];
106
  var out = select(bv, av, av < bv);
107
  {% for n in extraInputs %}
108
+ let {{ n }}v = {{ n }}[{{ broadcast_offset_call(n ~ "_offset", derive[n ~ "Shape"], yShape, "i") }}];
109
  out = select({{ n }}v, out, out < {{ n }}v);
110
  {% endfor %}
111
  y[i] = out;
112
  {% else %}
113
+ let av = f32(a[{{ broadcast_offset_call("a_offset", aShape, yShape, "i") }}]);
114
+ let bv = f32(b[{{ broadcast_offset_call("b_offset", bShape, yShape, "i") }}]);
115
  var out = min(av, bv);
116
  out = select(out, av, is_nan_f32(av));
117
  out = select(out, bv, is_nan_f32(bv));
118
  {% for n in extraInputs %}
119
+ let {{ n }}v = f32({{ n }}[{{ broadcast_offset_call(n ~ "_offset", derive[n ~ "Shape"], yShape, "i") }}]);
120
  let m{{ n }} = min(out, {{ n }}v);
121
  {% if loop.first %}
122
  // Built-in min/max may drop a NaN operand. Re-inject {{ n }}v's NaN and
build/webgpu/minmax-vec4.wgsl.jinja CHANGED
@@ -1,7 +1,4 @@
1
- {% set extraInputs = source.extraInputs if source.extraInputs is defined else (["c"] if source.hasC else []) + (["d"] if source.hasD else []) %}
2
- {% if usesF16 %}
3
- enable f16;
4
- {% endif %}
5
  {{ env.wgsl.resourceDeclarations }}
6
  {% if scalar != "i32" and scalar != "u32" %}
7
 
@@ -22,10 +19,10 @@ fn is_nan_vec4(value: vec4<f32>) -> vec4<bool> {
22
 
23
  {% endif %}
24
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
25
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
26
  // 2D-folded flat index: gid.y carries the high bits past the
27
- // maxComputeWorkgroupsPerDimension limit.
28
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
29
  if (i >= params.count) {
30
  return;
31
  }
 
1
+ {% set extraInputs = extraInputs if extraInputs is defined else (["c"] if hasC else []) + (["d"] if hasD else []) %}
 
 
 
2
  {{ env.wgsl.resourceDeclarations }}
3
  {% if scalar != "i32" and scalar != "u32" %}
4
 
 
19
 
20
  {% endif %}
21
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
22
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
23
  // 2D-folded flat index: gid.y carries the high bits past the
24
+ // per-axis dispatch fold width.
25
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
26
  if (i >= params.count) {
27
  return;
28
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Min",
3
  "cases": [
4
  {
5
  "name": "int16_max_arity_boundaries",
@@ -147,7 +146,7 @@
147
  "provenance": {
148
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
149
  "test": "MathOpTest.Min_8",
150
- "notes": "Scalar broadcast companion: negative subnormal inputs remain less than zero."
151
  },
152
  "inputs": {
153
  "a": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40] } },
@@ -284,7 +283,7 @@
284
  "provenance": {
285
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
286
  "test": "MathOpTest.Min_8",
287
- "notes": "Extends ORT's same-shape Min coverage to a valid four-input ONNX variadic node."
288
  },
289
  "inputs": {
290
  "a": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "constant", "value": -1.0 } },
@@ -299,7 +298,7 @@
299
  {
300
  "name": "four_input_same_shape_vec4_reference_generated",
301
  "provenance": {
302
- "notes": "Reference-generated four-operand coverage; see the Max sibling. Equal-amplitude interleaved sinusoids mean D supplies the minimum on a substantial share of lanes, so a reference that stops at C is visible here."
303
  },
304
  "inputs": {
305
  "a": {
@@ -330,7 +329,7 @@
330
  "provenance": {
331
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
332
  "test": "MathOpTest.Min_8",
333
- "notes": "Extends ORT's variadic Min coverage with a fourth input that carries NaNs; any NaN input should propagate at that element."
334
  },
335
  "inputs": {
336
  "a": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, "NaN", 5.0, -4.0] } },
@@ -457,10 +456,11 @@
457
  "name": "ort_float_scalar_nan_broadcast",
458
  "provenance": {
459
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
460
- "test": "MathOpTest.Min_12_Float_with_scalar_Nan"
 
461
  },
462
  "inputs": {
463
- "a": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [0.25, -0.25, -0.5, 0.5] } },
464
  "b": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": ["NaN"] } }
465
  },
466
  "outputs": { "y": { "dtype": "float32", "shape": [2, 2], "tolerance": 0.000001, "allowNaN": true } }
@@ -525,10 +525,11 @@
525
  "name": "ort_f16_scalar_nan_broadcast",
526
  "provenance": {
527
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
528
- "test": "MathOpTest.Min_13_Float16_with_scalar_Nan"
 
529
  },
530
  "inputs": {
531
- "a": { "dtype": "float16", "shape": [3, 1], "data": { "kind": "values", "values": [-0.5, 1.0, 1.5] } },
532
  "b": { "dtype": "float16", "shape": [1], "data": { "kind": "values", "values": ["NaN"] } }
533
  },
534
  "outputs": { "y": { "dtype": "float16", "shape": [3, 1], "tolerance": 0.002, "allowNaN": true } }
@@ -577,12 +578,13 @@
577
  "name": "ort_f16_scalar1_three_input",
578
  "provenance": {
579
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
580
- "test": "MathOpTest.Min_12_MLFloat16_Scalar1"
 
581
  },
582
  "inputs": {
583
- "a": { "dtype": "float16", "shape": [1, 3], "data": { "kind": "values", "values": [2.0, 3.0, 4.0] } },
584
  "b": { "dtype": "float16", "shape": [], "data": { "kind": "values", "values": [-10.0] } },
585
- "c": { "dtype": "float16", "shape": [1, 3], "data": { "kind": "values", "values": [3.0, 2.0, -3.0] } }
586
  },
587
  "outputs": {
588
  "y": {
@@ -972,7 +974,7 @@
972
  {
973
  "name": "f16_four_input_same_shape_vec4",
974
  "provenance": {
975
- "notes": "float16 at arity 4 on the flat vec4 same-shape path (same_shape_vec4_four_input, numel 8). Two extra-operand folds in the f16 lane of minmax-vec4, so the second fold consumes the first fold's result and the loop.first-guarded comment renders on the first pass only. Valid ONNX Min-13 for T = float16 with four same-shape operands. Every value is exactly representable in float16 and each of a, b, c, d supplies the minimum on exactly two lanes, so no operand can be dropped."
976
  },
977
  "inputs": {
978
  "a": {
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "int16_max_arity_boundaries",
 
146
  "provenance": {
147
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
148
  "test": "MathOpTest.Min_8",
149
+ "notes": "On the scalar broadcast path, negative subnormal inputs remain less than zero."
150
  },
151
  "inputs": {
152
  "a": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40] } },
 
283
  "provenance": {
284
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
285
  "test": "MathOpTest.Min_8",
286
+ "notes": "Four same-shaped inputs exercise the valid maximum arity of this variadic Min package."
287
  },
288
  "inputs": {
289
  "a": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "constant", "value": -1.0 } },
 
298
  {
299
  "name": "four_input_same_shape_vec4_reference_generated",
300
  "provenance": {
301
+ "notes": "Four equal-amplitude interleaved sinusoids make the fourth operand supply the minimum on many lanes, so omitting any operand changes the output well beyond tolerance."
302
  },
303
  "inputs": {
304
  "a": {
 
329
  "provenance": {
330
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
331
  "test": "MathOpTest.Min_8",
332
+ "notes": "A fourth input carrying NaNs verifies that any NaN operand propagates at that element."
333
  },
334
  "inputs": {
335
  "a": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, "NaN", 5.0, -4.0] } },
 
456
  "name": "ort_float_scalar_nan_broadcast",
457
  "provenance": {
458
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
459
+ "test": "MathOpTest.Min_12_Float_with_scalar_Nan",
460
+ "notes": "Diverges from the upstream test's inputs (inputs.a values [0.25, -0.25, -0.5, 0.5] -> constant 0.25); the expected output is recomputed by the CPU reference for the new inputs. A NaN scalar operand propagates to every element whatever the other operand holds, so the tensor operand is uniform: any finite result fails."
461
  },
462
  "inputs": {
463
+ "a": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "constant", "value": 0.25 } },
464
  "b": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": ["NaN"] } }
465
  },
466
  "outputs": { "y": { "dtype": "float32", "shape": [2, 2], "tolerance": 0.000001, "allowNaN": true } }
 
525
  "name": "ort_f16_scalar_nan_broadcast",
526
  "provenance": {
527
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
528
+ "test": "MathOpTest.Min_13_Float16_with_scalar_Nan",
529
+ "notes": "Diverges from the upstream test's inputs (inputs.a values [-0.5, 1.0, 1.5] -> constant 1.0); the expected output is recomputed by the CPU reference for the new inputs. A NaN scalar operand propagates to every element whatever the other operand holds, so the tensor operand is uniform: any finite result fails."
530
  },
531
  "inputs": {
532
+ "a": { "dtype": "float16", "shape": [3, 1], "data": { "kind": "constant", "value": 1.0 } },
533
  "b": { "dtype": "float16", "shape": [1], "data": { "kind": "values", "values": ["NaN"] } }
534
  },
535
  "outputs": { "y": { "dtype": "float16", "shape": [3, 1], "tolerance": 0.002, "allowNaN": true } }
 
578
  "name": "ort_f16_scalar1_three_input",
579
  "provenance": {
580
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
581
+ "test": "MathOpTest.Min_12_MLFloat16_Scalar1",
582
+ "notes": "Diverges from the upstream test's inputs (inputs.a values [2.0, 3.0, 4.0] -> constant 2.0; inputs.c values [3.0, 2.0, -3.0] -> constant 3.0); the expected output is recomputed by the CPU reference for the new inputs. The rank-0 operand dominates all three inputs, so the tensor operands are uniform: a variadic fold that drops the scalar returns the tensor value instead."
583
  },
584
  "inputs": {
585
+ "a": { "dtype": "float16", "shape": [1, 3], "data": { "kind": "constant", "value": 2.0 } },
586
  "b": { "dtype": "float16", "shape": [], "data": { "kind": "values", "values": [-10.0] } },
587
+ "c": { "dtype": "float16", "shape": [1, 3], "data": { "kind": "constant", "value": 3.0 } }
588
  },
589
  "outputs": {
590
  "y": {
 
974
  {
975
  "name": "f16_four_input_same_shape_vec4",
976
  "provenance": {
977
+ "notes": "Four same-shaped float16 inputs require two successive minimum folds on the vectorized path. Every value is exactly representable, and each operand supplies the minimum on exactly two lanes."
978
  },
979
  "inputs": {
980
  "a": {