Xenova HF Staff commited on
Commit
94c3f90
·
verified ·
1 Parent(s): b94231d

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -44,6 +44,13 @@ Default values (overridable per request):
44
  | --- | --- |
45
  | `T` | `float32`, `float16`, `int32`, `uint32`, `int8`, `uint8`, `bool` |
46
 
 
 
 
 
 
 
 
47
  ## Device requirements
48
 
49
  Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
@@ -53,7 +60,7 @@ Some implementation variants require `subgroups`. These are route-specific capab
53
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
54
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
  - [`test.json`](build/webgpu/test.json) — correctness cases
56
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
57
  - [`datamove-flat-copy-x4-tail.wgsl.jinja`](build/webgpu/datamove-flat-copy-x4-tail.wgsl.jinja)
58
  - [`reduce-axis-split-reduce.wgsl.jinja`](build/webgpu/reduce-axis-split-reduce.wgsl.jinja)
59
  - [`reduce-axis0-splitk-combine.wgsl.jinja`](build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja)
@@ -70,7 +77,7 @@ Some implementation variants require `subgroups`. These are route-specific capab
70
  ## Use with `@huggingface/kernels`
71
 
72
  ```sh
73
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
74
  ```
75
 
76
  Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
@@ -89,7 +96,7 @@ import { getKernel } from "@huggingface/kernels";
89
 
90
  const kernel = await getKernel("webgpu-kernels/ai.onnx.ReduceMin", { version: 1 });
91
  // Explicit destinations request optional results or supply metadata that cannot be inferred.
92
- const { y } = await kernel({ x: { data: xData, shape: [] } }, {
93
- outputs: { y: { shape: [], dtype: "float32" } },
94
  });
95
  ```
 
44
  | --- | --- |
45
  | `T` | `float32`, `float16`, `int32`, `uint32`, `int8`, `uint8`, `bool` |
46
 
47
+ ## Implementation variants
48
+
49
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
50
+
51
+ - `subgroup_last_axis_vec4` — Reduces each contiguous last-axis row with subgroup collectives and vec4-packed reads.
52
+ - `subgroup_last_axis` — Reduces each contiguous last-axis row with subgroup collectives and scalar reads for an unaligned row width.
53
+
54
  ## Device requirements
55
 
56
  Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
 
60
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
61
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
62
  - [`test.json`](build/webgpu/test.json) — correctness cases
63
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
64
  - [`datamove-flat-copy-x4-tail.wgsl.jinja`](build/webgpu/datamove-flat-copy-x4-tail.wgsl.jinja)
65
  - [`reduce-axis-split-reduce.wgsl.jinja`](build/webgpu/reduce-axis-split-reduce.wgsl.jinja)
66
  - [`reduce-axis0-splitk-combine.wgsl.jinja`](build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja)
 
77
  ## Use with `@huggingface/kernels`
78
 
79
  ```sh
80
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
81
  ```
82
 
83
  Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
 
96
 
97
  const kernel = await getKernel("webgpu-kernels/ai.onnx.ReduceMin", { version: 1 });
98
  // Explicit destinations request optional results or supply metadata that cannot be inferred.
99
+ const { y } = await kernel({ x: { data: xData, shape: [3, 2, 2] } }, {
100
+ outputs: { y: { shape: [1, 1, 1], dtype: "float32" } },
101
  });
102
  ```
build/webgpu/bench.json CHANGED
@@ -73,7 +73,7 @@
73
  "name": "reducemin-rank3-spatial-axes12-f32-128x256x256-pathology",
74
  "preset": "stress",
75
  "provenance": {
76
- "source": "repository-authored",
77
  "notes": "Rank-3 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane."
78
  },
79
  "attrs": { "axes": [1, 2], "keepdims": 1 },
@@ -85,7 +85,7 @@
85
  "name": "reducemin-rank4-spatial-axes23-f32-2x64x256x256-pathology",
86
  "preset": "stress",
87
  "provenance": {
88
- "source": "repository-authored",
89
  "notes": "Rank-4 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane."
90
  },
91
  "attrs": { "axes": [2, 3], "keepdims": 1 },
 
73
  "name": "reducemin-rank3-spatial-axes12-f32-128x256x256-pathology",
74
  "preset": "stress",
75
  "provenance": {
76
+ "source": "synthetic",
77
  "notes": "Rank-3 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane."
78
  },
79
  "attrs": { "axes": [1, 2], "keepdims": 1 },
 
85
  "name": "reducemin-rank4-spatial-axes23-f32-2x64x256x256-pathology",
86
  "preset": "stress",
87
  "provenance": {
88
+ "source": "synthetic",
89
  "notes": "Rank-4 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane."
90
  },
91
  "attrs": { "axes": [2, 3], "keepdims": 1 },
build/webgpu/manifest.json CHANGED
@@ -33,6 +33,7 @@
33
  "ROW_SERIAL_MAX_COLS": { "default": 1024 }
34
  },
35
  "derive": {
 
36
  "emptyNarrowInputOk": "numel(shapes.x) > 0 or (tensorDtypes.x != \"int8\" and tensorDtypes.x != \"uint8\")",
37
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
38
  "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
@@ -59,62 +60,75 @@
59
  "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
60
  "flatPathFits": "treeWorkgroupOk and flatSplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
61
  "flatParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.y) == 1 and numel(shapes.x) >= tunables.FULL_REDUCE_MIN_ELEMENTS and flatPathFits",
62
- "contiguousSuffixParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.y) > 0 and numel(shapes.x) % numel(shapes.y) == 0 and numel(shapes.x) / numel(shapes.y) >= tunables.CONTIGUOUS_SUFFIX_MIN_COLS and ((ranks.x == 3 and hasAxis(attrs.axes, 0, 3) == false and hasAxis(attrs.axes, 1, 3) and hasAxis(attrs.axes, 2, 3) and numel(shapes.y) == dim(shapes.x, 0)) or (ranks.x == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) == false and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.y) == dim(shapes.x, 0) * dim(shapes.x, 1)) or (ranks.x == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.y) == dim(shapes.x, 0)))"
 
 
63
  },
64
  "bindings": {
65
- "params": { "buffer": "uniform", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] },
66
- "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
67
- "y_2": { "name": "y", "buffer": "storage", "elementType": "$T" },
68
- "params_2": {
69
  "name": "params",
70
- "buffer": "uniform",
71
  "struct": [
72
  { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
73
  { "name": "chunkCount", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y) / tunables.VECTOR_WIDTH" }
74
  ]
75
  },
76
- "x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "$T" },
77
- "params_3": {
78
  "name": "params",
79
- "buffer": "uniform",
80
  "struct": [
81
- { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
82
- { "name": "cols", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y)" }
83
  ]
84
  },
85
- "params_4": {
86
  "name": "params",
87
- "buffer": "uniform",
88
- "struct": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }]
89
  },
90
- "params_5": {
 
 
 
 
 
 
 
91
  "name": "params",
92
- "buffer": "uniform",
93
  "struct": [
94
  { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
95
- { "name": "cols", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
96
  ]
97
  },
98
- "params_6": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  "name": "params",
100
- "buffer": "uniform",
101
  "struct": [
102
  { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
103
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1) / tunables.VECTOR_WIDTH" }
104
  ]
105
  },
106
- "params_7": {
107
  "name": "params",
108
- "buffer": "uniform",
109
  "struct": [
110
  { "name": "rows", "type": "u32", "value": "1" },
111
  { "name": "cols", "type": "u32", "value": "1" },
112
  { "name": "outCount", "type": "u32", "value": "1" }
113
  ]
114
  },
115
- "params_8": {
116
  "name": "params",
117
- "buffer": "uniform",
118
  "struct": [
119
  { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
120
  { "name": "cols", "type": "u32", "value": "1" },
@@ -122,75 +136,56 @@
122
  ]
123
  },
124
  "partials": { "buffer": "storage", "elementType": "$partialElement" },
125
- "params_9": {
126
  "name": "params",
127
- "buffer": "uniform",
128
  "struct": [
129
  { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
130
  { "name": "inner", "type": "u32", "value": "axisSplitInner" },
131
  { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
132
  ]
133
  },
134
- "partials_2": { "name": "partials", "buffer": "read-only-storage", "elementType": "$partialElement" },
135
- "params_10": {
136
  "name": "params",
137
- "buffer": "uniform",
138
- "struct": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }]
139
- },
140
- "params_11": {
141
- "name": "params",
142
- "buffer": "uniform",
143
  "struct": [
144
  { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
145
  { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }
146
  ]
147
  },
148
- "params_12": {
149
- "name": "params",
150
- "buffer": "uniform",
151
- "struct": [{ "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }]
152
- },
153
- "partials_3": { "name": "partials", "buffer": "storage", "elementType": "f32" },
154
- "params_13": {
155
  "name": "params",
156
- "buffer": "uniform",
157
  "struct": [
158
  { "name": "count4", "type": "u32", "value": "floor(numel(shapes.x) / tunables.VECTOR_WIDTH)" },
159
  { "name": "numel", "type": "u32", "value": "numel(shapes.x)" }
160
  ]
161
  },
162
- "partials_4": { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
163
- "params_14": { "name": "params", "buffer": "uniform", "struct": [{ "name": "cols", "type": "u32", "value": "1" }] },
164
- "params_15": {
165
  "name": "params",
166
- "buffer": "uniform",
167
  "struct": [
168
  { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
169
  { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
170
  ]
171
  },
172
- "params_16": {
173
  "name": "params",
174
- "buffer": "uniform",
175
  "struct": [
176
- { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
177
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
 
178
  ]
179
  },
180
- "params_17": {
181
  "name": "params",
182
- "buffer": "uniform",
183
  "struct": [
184
- { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
185
  { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
186
  { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
187
  ]
188
  },
189
- "params_18": {
190
  "name": "params",
191
- "buffer": "uniform",
192
  "struct": [
193
- { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
 
194
  { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
195
  ]
196
  }
@@ -207,7 +202,7 @@
207
  "name": "ReduceMin.NarrowEmptyIdentity",
208
  "shader": "reduce-narrow-empty-identity.wgsl.jinja",
209
  "derive": { "identity": "1 if tensorDtypes.x == \"bool\" else 127 if tensorDtypes.x == \"int8\" else 255" },
210
- "bindings": [{ "arg": "y", "elementType": "$scalar" }, "params"],
211
  "dispatch": {
212
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
213
  "y": 1,
@@ -231,15 +226,9 @@
231
  "id": "main",
232
  "name": "ReduceMin.ContiguousSuffixSubgroupVec4",
233
  "shader": "reduce-row-subgroup.wgsl.jinja",
234
- "derive": {
235
- "op": "\"min\"",
236
- "vec4": true,
237
- "castF32": "dtypes.T == \"f16\"",
238
- "usesF16Spec": "dtypes.T == \"f16\""
239
- },
240
- "bindings": ["x", "y_2", "params_2"],
241
- "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 },
242
- "subgroupCollectivesWidth": "portable"
243
  }
244
  ]
245
  },
@@ -257,13 +246,8 @@
257
  "id": "main",
258
  "name": "ReduceMin.ContiguousSuffixTreeVec4",
259
  "shader": "reduce-row-tree.wgsl.jinja",
260
- "derive": {
261
- "op": "\"min\"",
262
- "vec4": true,
263
- "castF32": "dtypes.T == \"f16\"",
264
- "usesF16Spec": "dtypes.T == \"f16\""
265
- },
266
- "bindings": ["x", "y_2", "params_2"],
267
  "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
268
  }
269
  ]
@@ -281,8 +265,7 @@
281
  "id": "main",
282
  "name": "ReduceMin.ContiguousSuffixTree",
283
  "shader": "reduce-row-tree.wgsl.jinja",
284
- "derive": { "op": "\"min\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
285
- "bindings": ["x_2", "y_2", "params_3"],
286
  "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
287
  }
288
  ]
@@ -321,7 +304,7 @@
321
  "id": "main",
322
  "name": "ReduceMin.NoopEmptyAxes",
323
  "shader": "reduce-noop-empty-axes.wgsl.jinja",
324
- "bindings": ["x_2", "y_2", "params"],
325
  "dispatch": {
326
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
327
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -342,10 +325,7 @@
342
  "shader": "reduce-serial-axis.wgsl.jinja",
343
  "derive": {
344
  "reduceWorkgroupSize": "reduceWorkgroupSize",
345
- "op": "\"min\"",
346
  "indexing": "\"multiaxis\"",
347
- "castF32": "dtypes.T == \"f16\"",
348
- "usesF16Spec": "dtypes.T == \"f16\"",
349
  "rank": 3,
350
  "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
351
  "dataShape": "shapes.x",
@@ -354,7 +334,7 @@
354
  "keepDims": "attrs.keepdims != 0",
355
  "logicalBool": "tensorDtypes.x == \"bool\""
356
  },
357
- "bindings": ["x_2", "y_2", "params_4"],
358
  "dispatch": {
359
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
360
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -375,10 +355,7 @@
375
  "shader": "reduce-serial-axis.wgsl.jinja",
376
  "derive": {
377
  "reduceWorkgroupSize": "reduceWorkgroupSize",
378
- "op": "\"min\"",
379
  "indexing": "\"multiaxis\"",
380
- "castF32": "dtypes.T == \"f16\"",
381
- "usesF16Spec": "dtypes.T == \"f16\"",
382
  "rank": 4,
383
  "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
384
  "dataShape": "shapes.x",
@@ -387,7 +364,7 @@
387
  "keepDims": "attrs.keepdims != 0",
388
  "logicalBool": "tensorDtypes.x == \"bool\""
389
  },
390
- "bindings": ["x_2", "y_2", "params_4"],
391
  "dispatch": {
392
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
393
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -410,8 +387,7 @@
410
  "id": "main",
411
  "name": "ReduceMin.TreeLastAxis",
412
  "shader": "reduce-row-tree.wgsl.jinja",
413
- "derive": { "op": "\"min\"" },
414
- "bindings": ["x_2", "y_2", "params_5"],
415
  "dispatch": {
416
  "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
417
  "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
@@ -436,14 +412,12 @@
436
  "id": "main",
437
  "name": "ReduceMin.SubgroupRowsVec4",
438
  "shader": "reduce-row-subgroup-rows.wgsl.jinja",
439
- "derive": { "op": "\"min\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
440
- "bindings": ["x", "y_2", "params_6"],
441
  "dispatch": {
442
  "x": "min(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
443
  "y": "ceilDiv(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
444
  "z": 1
445
- },
446
- "subgroupCollectivesWidth": "portable"
447
  }
448
  ]
449
  },
@@ -462,13 +436,8 @@
462
  "id": "main",
463
  "name": "ReduceMin.TreeRowVec4",
464
  "shader": "reduce-row-tree.wgsl.jinja",
465
- "derive": {
466
- "op": "\"min\"",
467
- "vec4": true,
468
- "castF32": "dtypes.T == \"f16\"",
469
- "usesF16Spec": "dtypes.T == \"f16\""
470
- },
471
- "bindings": ["x", "y_2", "params_6"],
472
  "dispatch": { "x": "min(lastAxisRows, 65535)", "y": "ceilDiv(lastAxisRows, 65535)", "z": 1 }
473
  }
474
  ]
@@ -483,14 +452,8 @@
483
  "id": "main",
484
  "name": "ReduceMin.Rank0Scalar",
485
  "shader": "reduce-serial-axis.wgsl.jinja",
486
- "derive": {
487
- "op": "\"min\"",
488
- "indexing": "\"axis2d\"",
489
- "castF32": "dtypes.T == \"f16\"",
490
- "usesF16Spec": "dtypes.T == \"f16\"",
491
- "logicalBool": "tensorDtypes.x == \"bool\""
492
- },
493
- "bindings": ["x_2", "y_2", "params_7"],
494
  "dispatch": { "x": 1 }
495
  }
496
  ]
@@ -504,14 +467,8 @@
504
  "id": "main",
505
  "name": "ReduceMin.Rank1Axis0",
506
  "shader": "reduce-serial-axis.wgsl.jinja",
507
- "derive": {
508
- "op": "\"min\"",
509
- "indexing": "\"axis2d\"",
510
- "castF32": "dtypes.T == \"f16\"",
511
- "usesF16Spec": "dtypes.T == \"f16\"",
512
- "logicalBool": "tensorDtypes.x == \"bool\""
513
- },
514
- "bindings": ["x_2", "y_2", "params_8"],
515
  "dispatch": {
516
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
517
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -536,13 +493,8 @@
536
  "id": "split_reduce",
537
  "name": "ReduceMin.AxisSplitReduce",
538
  "shader": "reduce-axis-split-reduce.wgsl.jinja",
539
- "derive": {
540
- "op": "\"min\"",
541
- "splitSpec": "splitCount",
542
- "castF32": "dtypes.T == \"f16\"",
543
- "usesF16Spec": "dtypes.T == \"f16\""
544
- },
545
- "bindings": ["x_2", "partials", "params_9"],
546
  "dispatch": {
547
  "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
548
  "y": "splitCount",
@@ -553,8 +505,8 @@
553
  "id": "combine",
554
  "name": "ReduceMin.AxisSplitCombine",
555
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
556
- "derive": { "op": "\"min\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
557
- "bindings": ["partials_2", "y_2", "params_10"],
558
  "dispatch": {
559
  "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
560
  "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
@@ -581,14 +533,8 @@
581
  "id": "split_reduce",
582
  "name": "ReduceMin.AxisSplitTiledReduce",
583
  "shader": "reduce-axis0-tilecols.wgsl.jinja",
584
- "derive": {
585
- "op": "\"min\"",
586
- "splitSpec": "splitCount",
587
- "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
588
- "castF32": "dtypes.T == \"f16\"",
589
- "usesF16Spec": "dtypes.T == \"f16\""
590
- },
591
- "bindings": ["x_2", "partials", "params_9"],
592
  "dispatch": {
593
  "x": "min(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)",
594
  "y": "splitCount",
@@ -599,8 +545,8 @@
599
  "id": "combine",
600
  "name": "ReduceMin.AxisSplitCombine",
601
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
602
- "derive": { "op": "\"min\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
603
- "bindings": ["partials_2", "y_2", "params_10"],
604
  "dispatch": {
605
  "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
606
  "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
@@ -613,6 +559,7 @@
613
  "id": "axis0_splitk",
614
  "priority": 22,
615
  "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.x == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_SPLIT_MIN_ROWS", "dim(shapes.x, 1) > 0", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 1)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == dim(shapes.x, 1)))", "axis0SplitPathFits"],
 
616
  "derive": {
617
  "splitCount": "axis0SplitCount",
618
  "partialElement": "\"f32\"",
@@ -625,13 +572,8 @@
625
  "id": "split_reduce",
626
  "name": "ReduceMin.Axis0SplitKReduce",
627
  "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
628
- "derive": {
629
- "op": "\"min\"",
630
- "splitSpec": "splitCount",
631
- "castF32": "dtypes.T == \"f16\"",
632
- "usesF16Spec": "dtypes.T == \"f16\""
633
- },
634
- "bindings": ["x_2", "partials", "params_11"],
635
  "dispatch": {
636
  "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
637
  "y": "splitCount",
@@ -642,8 +584,8 @@
642
  "id": "combine",
643
  "name": "ReduceMin.Axis0SplitKCombine",
644
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
645
- "derive": { "op": "\"min\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
646
- "bindings": ["partials_2", "y_2", "params_12"],
647
  "dispatch": {
648
  "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
649
  "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
@@ -662,8 +604,8 @@
662
  "id": "main",
663
  "name": "ReduceMin.Axis0TileCols",
664
  "shader": "reduce-axis0-tilecols.wgsl.jinja",
665
- "derive": { "op": "\"min\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
666
- "bindings": ["x_2", "y_2", "params_11"],
667
  "dispatch": {
668
  "x": "min(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
669
  "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
@@ -676,23 +618,22 @@
676
  "id": "all_axes_flat",
677
  "priority": 31,
678
  "when": ["flatParallelCovered"],
679
- "derive": { "scalar": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
680
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
681
  "passes": [
682
  {
683
  "id": "flat_partial",
684
  "name": "ReduceMin.AllAxesFlatPartial",
685
  "shader": "reduce-flat-partial.wgsl.jinja",
686
- "derive": { "op": "\"min\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
687
- "bindings": ["x_2", "partials_3", "params_13"],
688
  "dispatch": { "x": "flatSplitCount" }
689
  },
690
  {
691
  "id": "combine",
692
  "name": "ReduceMin.AllAxesFlatCombine",
693
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
694
- "derive": { "op": "\"min\"", "outputF16": "dtypes.T == \"f16\"" },
695
- "bindings": ["partials_4", "y_2", "params_14"],
696
  "dispatch": { "x": 1 }
697
  }
698
  ]
@@ -710,10 +651,7 @@
710
  "shader": "reduce-serial-axis.wgsl.jinja",
711
  "derive": {
712
  "reduceWorkgroupSize": "reduceWorkgroupSize",
713
- "op": "\"min\"",
714
  "indexing": "\"rankn\"",
715
- "castF32": "dtypes.T == \"f16\"",
716
- "usesF16Spec": "dtypes.T == \"f16\"",
717
  "rank": "ranks.x",
718
  "axisSpec": "reduceAxis",
719
  "dataShape": "shapes.x",
@@ -722,7 +660,7 @@
722
  "keepDims": "attrs.keepdims != 0",
723
  "logicalBool": "tensorDtypes.x == \"bool\""
724
  },
725
- "bindings": ["x_2", "y_2", "params_15"],
726
  "dispatch": {
727
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
728
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -746,19 +684,13 @@
746
  "id": "main",
747
  "name": "ReduceMin.SubgroupRowVec4",
748
  "shader": "reduce-row-subgroup.wgsl.jinja",
749
- "derive": {
750
- "op": "\"min\"",
751
- "vec4": true,
752
- "castF32": "dtypes.T == \"f16\"",
753
- "usesF16Spec": "dtypes.T == \"f16\""
754
- },
755
- "bindings": ["x", "y_2", "params_6"],
756
  "dispatch": {
757
  "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
758
  "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
759
  "z": 1
760
- },
761
- "subgroupCollectivesWidth": "portable"
762
  }
763
  ]
764
  },
@@ -776,19 +708,13 @@
776
  "id": "main",
777
  "name": "ReduceMin.SubgroupRow",
778
  "shader": "reduce-row-subgroup.wgsl.jinja",
779
- "derive": {
780
- "op": "\"min\"",
781
- "vec4": false,
782
- "castF32": "dtypes.T == \"f16\"",
783
- "usesF16Spec": "dtypes.T == \"f16\""
784
- },
785
- "bindings": ["x_2", "y_2", "params_16"],
786
  "dispatch": {
787
  "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
788
  "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
789
  "z": 1
790
- },
791
- "subgroupCollectivesWidth": "portable"
792
  }
793
  ]
794
  },
@@ -801,18 +727,10 @@
801
  "passes": [
802
  {
803
  "id": "main",
804
- "name": "axis0",
805
  "shader": "reduce-serial-axis.wgsl.jinja",
806
- "derive": {
807
- "axis": 0,
808
- "scalar": "dtypes.T",
809
- "op": "\"min\"",
810
- "indexing": "\"axis2d\"",
811
- "castF32": "dtypes.T == \"f16\"",
812
- "usesF16Spec": "dtypes.T == \"f16\"",
813
- "logicalBool": "tensorDtypes.x == \"bool\""
814
- },
815
- "bindings": ["x_2", "y_2", "params_17"],
816
  "dispatch": {
817
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
818
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -829,18 +747,10 @@
829
  "passes": [
830
  {
831
  "id": "main",
832
- "name": "axis1",
833
  "shader": "reduce-serial-axis.wgsl.jinja",
834
- "derive": {
835
- "axis": 1,
836
- "scalar": "dtypes.T",
837
- "op": "\"min\"",
838
- "indexing": "\"axis2d\"",
839
- "castF32": "dtypes.T == \"f16\"",
840
- "usesF16Spec": "dtypes.T == \"f16\"",
841
- "logicalBool": "tensorDtypes.x == \"bool\""
842
- },
843
- "bindings": ["x_2", "y_2", "params_18"],
844
  "dispatch": {
845
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
846
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -852,32 +762,15 @@
852
  {
853
  "id": "all_axes_keepdims",
854
  "priority": 30,
855
- "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 1 and ranks.y == ranks.x and numel(shapes.y) == 1"],
856
  "derive": { "axis": 0, "scalar": "dtypes.T" },
857
  "passes": [
858
  {
859
  "id": "main",
860
  "name": "ReduceMin.Rank3AllAxesKeepdims",
861
  "shader": "reduce-serial-axis.wgsl.jinja",
862
- "derive": {
863
- "op": "\"min\"",
864
- "indexing": "\"axis2d\"",
865
- "castF32": "dtypes.T == \"f16\"",
866
- "usesF16Spec": "dtypes.T == \"f16\"",
867
- "logicalBool": "tensorDtypes.x == \"bool\""
868
- },
869
- "bindings": [
870
- "x_2",
871
- "y_2",
872
- {
873
- "name": "params",
874
- "struct": [
875
- { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
876
- { "name": "cols", "type": "u32", "value": "1" },
877
- { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
878
- ]
879
- }
880
- ],
881
  "dispatch": {
882
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
883
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
@@ -889,32 +782,15 @@
889
  {
890
  "id": "all_axes_no_keepdims",
891
  "priority": 30,
892
- "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 0 and ranks.y == 0"],
893
  "derive": { "axis": 0, "scalar": "dtypes.T" },
894
  "passes": [
895
  {
896
  "id": "main",
897
  "name": "ReduceMin.Rank3AllAxesNoKeepdims",
898
  "shader": "reduce-serial-axis.wgsl.jinja",
899
- "derive": {
900
- "op": "\"min\"",
901
- "indexing": "\"axis2d\"",
902
- "castF32": "dtypes.T == \"f16\"",
903
- "usesF16Spec": "dtypes.T == \"f16\"",
904
- "logicalBool": "tensorDtypes.x == \"bool\""
905
- },
906
- "bindings": [
907
- "x_2",
908
- "y_2",
909
- {
910
- "name": "params",
911
- "struct": [
912
- { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
913
- { "name": "cols", "type": "u32", "value": "1" },
914
- { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
915
- ]
916
- }
917
- ],
918
  "dispatch": {
919
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
920
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
33
  "ROW_SERIAL_MAX_COLS": { "default": 1024 }
34
  },
35
  "derive": {
36
+ "reduceOp": "\"min\"",
37
  "emptyNarrowInputOk": "numel(shapes.x) > 0 or (tensorDtypes.x != \"int8\" and tensorDtypes.x != \"uint8\")",
38
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
39
  "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
 
60
  "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
61
  "flatPathFits": "treeWorkgroupOk and flatSplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
62
  "flatParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.y) == 1 and numel(shapes.x) >= tunables.FULL_REDUCE_MIN_ELEMENTS and flatPathFits",
63
+ "contiguousSuffixParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.y) > 0 and numel(shapes.x) % numel(shapes.y) == 0 and numel(shapes.x) / numel(shapes.y) >= tunables.CONTIGUOUS_SUFFIX_MIN_COLS and ((ranks.x == 3 and hasAxis(attrs.axes, 0, 3) == false and hasAxis(attrs.axes, 1, 3) and hasAxis(attrs.axes, 2, 3) and numel(shapes.y) == dim(shapes.x, 0)) or (ranks.x == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) == false and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.y) == dim(shapes.x, 0) * dim(shapes.x, 1)) or (ranks.x == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.y) == dim(shapes.x, 0)))",
64
+ "op": "reduceOp",
65
+ "castF32": "dtypes.T == \"f16\""
66
  },
67
  "bindings": {
68
+ "params_suffix_vec4": {
 
 
 
69
  "name": "params",
 
70
  "struct": [
71
  { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
72
  { "name": "chunkCount", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y) / tunables.VECTOR_WIDTH" }
73
  ]
74
  },
75
+ "params_reduce_row_tree": {
 
76
  "name": "params",
 
77
  "struct": [
78
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
79
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1) / tunables.VECTOR_WIDTH" }
80
  ]
81
  },
82
+ "params_axis_split_combine": {
83
  "name": "params",
84
+ "struct": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }]
 
85
  },
86
+ "params_axis0_combine": {
87
+ "name": "params",
88
+ "struct": [{ "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }]
89
+ },
90
+ "partials_flat": { "name": "partials", "buffer": "storage", "elementType": "f32" },
91
+ "partials_flat_read": { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
92
+ "params_all_axes_flat": { "name": "params", "struct": [{ "name": "cols", "type": "u32", "value": "1" }] },
93
+ "params_rows_chunk_count": {
94
  "name": "params",
 
95
  "struct": [
96
  { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
97
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
98
  ]
99
  },
100
+ "x": { "elementType": "$vectorScalar" },
101
+ "y": { "elementType": "$T" },
102
+ "x_t": { "name": "x", "elementType": "$T" },
103
+ "params_main": {
104
+ "name": "params",
105
+ "struct": [
106
+ { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
107
+ { "name": "cols", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y)" }
108
+ ]
109
+ },
110
+ "params_count": { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] },
111
+ "params_out_count": {
112
+ "name": "params",
113
+ "struct": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }]
114
+ },
115
+ "params_rows_cols": {
116
  "name": "params",
 
117
  "struct": [
118
  { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
119
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
120
  ]
121
  },
122
+ "params_rank0_scalar": {
123
  "name": "params",
 
124
  "struct": [
125
  { "name": "rows", "type": "u32", "value": "1" },
126
  { "name": "cols", "type": "u32", "value": "1" },
127
  { "name": "outCount", "type": "u32", "value": "1" }
128
  ]
129
  },
130
+ "params_rank1_axis0": {
131
  "name": "params",
 
132
  "struct": [
133
  { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
134
  { "name": "cols", "type": "u32", "value": "1" },
 
136
  ]
137
  },
138
  "partials": { "buffer": "storage", "elementType": "$partialElement" },
139
+ "params_axis_split": {
140
  "name": "params",
 
141
  "struct": [
142
  { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
143
  { "name": "inner", "type": "u32", "value": "axisSplitInner" },
144
  { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
145
  ]
146
  },
147
+ "partials_combine": { "name": "partials", "buffer": "read-only-storage", "elementType": "$partialElement" },
148
+ "params_split_reduce": {
149
  "name": "params",
 
 
 
 
 
 
150
  "struct": [
151
  { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
152
  { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }
153
  ]
154
  },
155
+ "params_count4_numel": {
 
 
 
 
 
 
156
  "name": "params",
 
157
  "struct": [
158
  { "name": "count4", "type": "u32", "value": "floor(numel(shapes.x) / tunables.VECTOR_WIDTH)" },
159
  { "name": "numel", "type": "u32", "value": "numel(shapes.x)" }
160
  ]
161
  },
162
+ "params_axis_dim_out_count": {
 
 
163
  "name": "params",
 
164
  "struct": [
165
  { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
166
  { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
167
  ]
168
  },
169
+ "params_axis0": {
170
  "name": "params",
 
171
  "struct": [
172
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
173
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
174
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
175
  ]
176
  },
177
+ "params_axis1": {
178
  "name": "params",
 
179
  "struct": [
 
180
  { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
181
  { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
182
  ]
183
  },
184
+ "params_all_axes": {
185
  "name": "params",
 
186
  "struct": [
187
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
188
+ { "name": "cols", "type": "u32", "value": "1" },
189
  { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
190
  ]
191
  }
 
202
  "name": "ReduceMin.NarrowEmptyIdentity",
203
  "shader": "reduce-narrow-empty-identity.wgsl.jinja",
204
  "derive": { "identity": "1 if tensorDtypes.x == \"bool\" else 127 if tensorDtypes.x == \"int8\" else 255" },
205
+ "bindings": [{ "arg": "y", "elementType": "$scalar" }, "params_count"],
206
  "dispatch": {
207
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
208
  "y": 1,
 
226
  "id": "main",
227
  "name": "ReduceMin.ContiguousSuffixSubgroupVec4",
228
  "shader": "reduce-row-subgroup.wgsl.jinja",
229
+ "derive": { "vec4": true },
230
+ "bindings": ["x", "y", "params_suffix_vec4"],
231
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
 
 
 
 
 
 
232
  }
233
  ]
234
  },
 
246
  "id": "main",
247
  "name": "ReduceMin.ContiguousSuffixTreeVec4",
248
  "shader": "reduce-row-tree.wgsl.jinja",
249
+ "derive": { "vec4": true },
250
+ "bindings": ["x", "y", "params_suffix_vec4"],
 
 
 
 
 
251
  "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
252
  }
253
  ]
 
265
  "id": "main",
266
  "name": "ReduceMin.ContiguousSuffixTree",
267
  "shader": "reduce-row-tree.wgsl.jinja",
268
+ "bindings": ["x_t", "y", "params_main"],
 
269
  "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
270
  }
271
  ]
 
304
  "id": "main",
305
  "name": "ReduceMin.NoopEmptyAxes",
306
  "shader": "reduce-noop-empty-axes.wgsl.jinja",
307
+ "bindings": ["x_t", "y", "params_count"],
308
  "dispatch": {
309
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
310
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
325
  "shader": "reduce-serial-axis.wgsl.jinja",
326
  "derive": {
327
  "reduceWorkgroupSize": "reduceWorkgroupSize",
 
328
  "indexing": "\"multiaxis\"",
 
 
329
  "rank": 3,
330
  "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
331
  "dataShape": "shapes.x",
 
334
  "keepDims": "attrs.keepdims != 0",
335
  "logicalBool": "tensorDtypes.x == \"bool\""
336
  },
337
+ "bindings": ["x_t", "y", "params_out_count"],
338
  "dispatch": {
339
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
340
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
355
  "shader": "reduce-serial-axis.wgsl.jinja",
356
  "derive": {
357
  "reduceWorkgroupSize": "reduceWorkgroupSize",
 
358
  "indexing": "\"multiaxis\"",
 
 
359
  "rank": 4,
360
  "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
361
  "dataShape": "shapes.x",
 
364
  "keepDims": "attrs.keepdims != 0",
365
  "logicalBool": "tensorDtypes.x == \"bool\""
366
  },
367
+ "bindings": ["x_t", "y", "params_out_count"],
368
  "dispatch": {
369
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
370
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
387
  "id": "main",
388
  "name": "ReduceMin.TreeLastAxis",
389
  "shader": "reduce-row-tree.wgsl.jinja",
390
+ "bindings": ["x_t", "y", "params_rows_cols"],
 
391
  "dispatch": {
392
  "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
393
  "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
 
412
  "id": "main",
413
  "name": "ReduceMin.SubgroupRowsVec4",
414
  "shader": "reduce-row-subgroup-rows.wgsl.jinja",
415
+ "bindings": ["x", "y", "params_reduce_row_tree"],
 
416
  "dispatch": {
417
  "x": "min(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
418
  "y": "ceilDiv(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
419
  "z": 1
420
+ }
 
421
  }
422
  ]
423
  },
 
436
  "id": "main",
437
  "name": "ReduceMin.TreeRowVec4",
438
  "shader": "reduce-row-tree.wgsl.jinja",
439
+ "derive": { "vec4": true },
440
+ "bindings": ["x", "y", "params_reduce_row_tree"],
 
 
 
 
 
441
  "dispatch": { "x": "min(lastAxisRows, 65535)", "y": "ceilDiv(lastAxisRows, 65535)", "z": 1 }
442
  }
443
  ]
 
452
  "id": "main",
453
  "name": "ReduceMin.Rank0Scalar",
454
  "shader": "reduce-serial-axis.wgsl.jinja",
455
+ "derive": { "indexing": "\"axis2d\"", "logicalBool": "tensorDtypes.x == \"bool\"" },
456
+ "bindings": ["x_t", "y", "params_rank0_scalar"],
 
 
 
 
 
 
457
  "dispatch": { "x": 1 }
458
  }
459
  ]
 
467
  "id": "main",
468
  "name": "ReduceMin.Rank1Axis0",
469
  "shader": "reduce-serial-axis.wgsl.jinja",
470
+ "derive": { "indexing": "\"axis2d\"", "logicalBool": "tensorDtypes.x == \"bool\"" },
471
+ "bindings": ["x_t", "y", "params_rank1_axis0"],
 
 
 
 
 
 
472
  "dispatch": {
473
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
474
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
493
  "id": "split_reduce",
494
  "name": "ReduceMin.AxisSplitReduce",
495
  "shader": "reduce-axis-split-reduce.wgsl.jinja",
496
+ "derive": { "splitSpec": "splitCount" },
497
+ "bindings": ["x_t", "partials", "params_axis_split"],
 
 
 
 
 
498
  "dispatch": {
499
  "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
500
  "y": "splitCount",
 
505
  "id": "combine",
506
  "name": "ReduceMin.AxisSplitCombine",
507
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
508
+ "derive": { "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
509
+ "bindings": ["partials_combine", "y", "params_axis_split_combine"],
510
  "dispatch": {
511
  "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
512
  "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
 
533
  "id": "split_reduce",
534
  "name": "ReduceMin.AxisSplitTiledReduce",
535
  "shader": "reduce-axis0-tilecols.wgsl.jinja",
536
+ "derive": { "splitSpec": "splitCount" },
537
+ "bindings": ["x_t", "partials", "params_axis_split"],
 
 
 
 
 
 
538
  "dispatch": {
539
  "x": "min(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)",
540
  "y": "splitCount",
 
545
  "id": "combine",
546
  "name": "ReduceMin.AxisSplitCombine",
547
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
548
+ "derive": { "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
549
+ "bindings": ["partials_combine", "y", "params_axis_split_combine"],
550
  "dispatch": {
551
  "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
552
  "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
 
559
  "id": "axis0_splitk",
560
  "priority": 22,
561
  "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.x == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_SPLIT_MIN_ROWS", "dim(shapes.x, 1) > 0", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 1)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == dim(shapes.x, 1)))", "axis0SplitPathFits"],
562
+ "demoteWhen": ["dtypes.T == \"f32\" and device.adapterInfo.subgroupMinSize == 16 and device.adapterInfo.subgroupMaxSize == 32 and axis0Rows >= 2 * tunables.AXIS0_SPLIT_MIN_ROWS and axis0Cols >= tunables.AXIS0_TILE_MIN_COLS and axis0TilePathFits and ceilDiv(axis0Cols, tunables.AXIS0_TILE_COLS) >= axis0SplitCount"],
563
  "derive": {
564
  "splitCount": "axis0SplitCount",
565
  "partialElement": "\"f32\"",
 
572
  "id": "split_reduce",
573
  "name": "ReduceMin.Axis0SplitKReduce",
574
  "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
575
+ "derive": { "splitSpec": "splitCount" },
576
+ "bindings": ["x_t", "partials", "params_split_reduce"],
 
 
 
 
 
577
  "dispatch": {
578
  "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
579
  "y": "splitCount",
 
584
  "id": "combine",
585
  "name": "ReduceMin.Axis0SplitKCombine",
586
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
587
+ "derive": { "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
588
+ "bindings": ["partials_combine", "y", "params_axis0_combine"],
589
  "dispatch": {
590
  "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
591
  "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
 
604
  "id": "main",
605
  "name": "ReduceMin.Axis0TileCols",
606
  "shader": "reduce-axis0-tilecols.wgsl.jinja",
607
+ "derive": {},
608
+ "bindings": ["x_t", "y", "params_split_reduce"],
609
  "dispatch": {
610
  "x": "min(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
611
  "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
 
618
  "id": "all_axes_flat",
619
  "priority": 31,
620
  "when": ["flatParallelCovered"],
621
+ "derive": { "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
622
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
623
  "passes": [
624
  {
625
  "id": "flat_partial",
626
  "name": "ReduceMin.AllAxesFlatPartial",
627
  "shader": "reduce-flat-partial.wgsl.jinja",
628
+ "bindings": ["x_t", "partials_flat", "params_count4_numel"],
 
629
  "dispatch": { "x": "flatSplitCount" }
630
  },
631
  {
632
  "id": "combine",
633
  "name": "ReduceMin.AllAxesFlatCombine",
634
  "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
635
+ "derive": { "outputF16": "dtypes.T == \"f16\"" },
636
+ "bindings": ["partials_flat_read", "y", "params_all_axes_flat"],
637
  "dispatch": { "x": 1 }
638
  }
639
  ]
 
651
  "shader": "reduce-serial-axis.wgsl.jinja",
652
  "derive": {
653
  "reduceWorkgroupSize": "reduceWorkgroupSize",
 
654
  "indexing": "\"rankn\"",
 
 
655
  "rank": "ranks.x",
656
  "axisSpec": "reduceAxis",
657
  "dataShape": "shapes.x",
 
660
  "keepDims": "attrs.keepdims != 0",
661
  "logicalBool": "tensorDtypes.x == \"bool\""
662
  },
663
+ "bindings": ["x_t", "y", "params_axis_dim_out_count"],
664
  "dispatch": {
665
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
666
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
684
  "id": "main",
685
  "name": "ReduceMin.SubgroupRowVec4",
686
  "shader": "reduce-row-subgroup.wgsl.jinja",
687
+ "derive": { "vec4": true },
688
+ "bindings": ["x", "y", "params_reduce_row_tree"],
 
 
 
 
 
689
  "dispatch": {
690
  "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
691
  "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
692
  "z": 1
693
+ }
 
694
  }
695
  ]
696
  },
 
708
  "id": "main",
709
  "name": "ReduceMin.SubgroupRow",
710
  "shader": "reduce-row-subgroup.wgsl.jinja",
711
+ "derive": { "vec4": false },
712
+ "bindings": ["x_t", "y", "params_rows_chunk_count"],
 
 
 
 
 
713
  "dispatch": {
714
  "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
715
  "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
716
  "z": 1
717
+ }
 
718
  }
719
  ]
720
  },
 
727
  "passes": [
728
  {
729
  "id": "main",
730
+ "name": "ReduceMin.Axis0",
731
  "shader": "reduce-serial-axis.wgsl.jinja",
732
+ "derive": { "indexing": "\"axis2d\"", "logicalBool": "tensorDtypes.x == \"bool\"" },
733
+ "bindings": ["x_t", "y", "params_axis0"],
 
 
 
 
 
 
 
 
734
  "dispatch": {
735
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
736
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
747
  "passes": [
748
  {
749
  "id": "main",
750
+ "name": "ReduceMin.Axis1",
751
  "shader": "reduce-serial-axis.wgsl.jinja",
752
+ "derive": { "indexing": "\"axis2d\"", "logicalBool": "tensorDtypes.x == \"bool\"" },
753
+ "bindings": ["x_t", "y", "params_axis1"],
 
 
 
 
 
 
 
 
754
  "dispatch": {
755
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
756
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
762
  {
763
  "id": "all_axes_keepdims",
764
  "priority": 30,
765
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 1", "ranks.y == ranks.x", "numel(shapes.y) == 1"],
766
  "derive": { "axis": 0, "scalar": "dtypes.T" },
767
  "passes": [
768
  {
769
  "id": "main",
770
  "name": "ReduceMin.Rank3AllAxesKeepdims",
771
  "shader": "reduce-serial-axis.wgsl.jinja",
772
+ "derive": { "indexing": "\"axis2d\"", "logicalBool": "tensorDtypes.x == \"bool\"" },
773
+ "bindings": ["x_t", "y", "params_all_axes"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
774
  "dispatch": {
775
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
776
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
 
782
  {
783
  "id": "all_axes_no_keepdims",
784
  "priority": 30,
785
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 0", "ranks.y == 0"],
786
  "derive": { "axis": 0, "scalar": "dtypes.T" },
787
  "passes": [
788
  {
789
  "id": "main",
790
  "name": "ReduceMin.Rank3AllAxesNoKeepdims",
791
  "shader": "reduce-serial-axis.wgsl.jinja",
792
+ "derive": { "indexing": "\"axis2d\"", "logicalBool": "tensorDtypes.x == \"bool\"" },
793
+ "bindings": ["x_t", "y", "params_all_axes"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
794
  "dispatch": {
795
  "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
796
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
build/webgpu/metadata.json CHANGED
@@ -1,32 +1,32 @@
1
  {
2
  "name": "ai.onnx.ReduceMin",
3
- "id": "_ai_onnx_reducemin_webgpu_56651f5",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "9HSUFWy2zOka0uaIlARjO0hKvsiIkYJlVTOuwy7LlrU=",
11
  "datamove-flat-copy-x4-tail.wgsl.jinja": "1u3sJCuYJ8lM8QU7/oOPB6Q6A7vkcOxCR03RGiakHWo=",
12
- "manifest.json": "Cwk7CFWuFgbjlIYAuOaGJieBrpOhc28p44FXo8+v7F4=",
13
- "reduce-axis-split-reduce.wgsl.jinja": "4+ep9xH4pHZOfaZ8abJhA8SW5M4mUhDC+y1CtDz6vjY=",
14
- "reduce-axis0-splitk-combine.wgsl.jinja": "Yz1hjK55R/kndUrw3ugPqgPaOBwKmYupqZoVdJTHO5Q=",
15
- "reduce-axis0-splitk-reduce.wgsl.jinja": "iLqQIC+Sa+EUzZG4Z3BqZOT2XA1TQEuRcFR2JbkEa30=",
16
- "reduce-axis0-tilecols.wgsl.jinja": "PjYkEUQJBeG70td3W2xxmexH9x6XNIfeSzXBY47HbaU=",
17
- "reduce-flat-partial.wgsl.jinja": "+qToL+wFi9QOxvY887aBAEwZK6Xu/eLQkucKvJYNwSk=",
18
  "reduce-narrow-empty-identity.wgsl.jinja": "XmLNK4zp0ru14DEm8TL5CAL74RVu7aVPrFo3WSJX3wo=",
19
- "reduce-noop-empty-axes.wgsl.jinja": "5bKufrcfYt3BagZ6WxbUvqwKKKVMU7d+SUshgUFWTDI=",
20
- "reduce-row-subgroup-rows.wgsl.jinja": "76u7rAvFoZZKrFDs2A2jk0vkB0uNrPL6twdOBE9b+v8=",
21
- "reduce-row-subgroup.wgsl.jinja": "2mu9LEsk8HfaLvucBCfcB1/ENpXkD6ELiCtRt+5UqiU=",
22
- "reduce-row-tree.wgsl.jinja": "Bwa5xcI0bTmKXb4r9Cc1bfVbM5rNqqpQVrWWVqcb8xA=",
23
- "reduce-serial-axis.wgsl.jinja": "fvUV9htqzKzt4Pg05pYtRmup/5QIHYaUGQHJZnthXKo=",
24
- "test.json": "rOIoQuLOtT5w5RcBzucPfj30IkoeNSd+2TuyiE4cbk8="
25
  }
26
  },
27
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
28
  "webgpu": {
29
- "manifestSpec": "2.0",
30
  "variants": {
31
  "narrow_empty_identity": ["reduce-narrow-empty-identity.wgsl.jinja"],
32
  "contiguous_suffix_subgroup_vec4": ["reduce-row-subgroup.wgsl.jinja"],
 
1
  {
2
  "name": "ai.onnx.ReduceMin",
3
+ "id": "_ai_onnx_reducemin_webgpu_6673c73",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "t2isnzkULzGR9Ki2/j7Gg6PyxV3TDPc+ovEKMuVvYWI=",
11
  "datamove-flat-copy-x4-tail.wgsl.jinja": "1u3sJCuYJ8lM8QU7/oOPB6Q6A7vkcOxCR03RGiakHWo=",
12
+ "manifest.json": "vZJEw1TU0yAeZ9SzFGoIujiecANsX96IVN1/QXbvqTQ=",
13
+ "reduce-axis-split-reduce.wgsl.jinja": "xo4jwtx/MeHIZkh2KU31ZCR+Ak58xOe4Cg3HWrcvdqo=",
14
+ "reduce-axis0-splitk-combine.wgsl.jinja": "Zf7tz8nrapi2KMZIbv8cT2f4pliJoJHj4iwS4hc/6ms=",
15
+ "reduce-axis0-splitk-reduce.wgsl.jinja": "RVy0BuBzgen99cwoM0KNiZ6OPnc8ColMSsME3IZKLI4=",
16
+ "reduce-axis0-tilecols.wgsl.jinja": "bxt1WvxNRg3bgU4R+s5JUlHZjqH0THZZic++EJ7X+yA=",
17
+ "reduce-flat-partial.wgsl.jinja": "S573jG+5ZoOcPEzKnD8h3nvPNYOuM8+ku15d+15ypBA=",
18
  "reduce-narrow-empty-identity.wgsl.jinja": "XmLNK4zp0ru14DEm8TL5CAL74RVu7aVPrFo3WSJX3wo=",
19
+ "reduce-noop-empty-axes.wgsl.jinja": "MMZEXHeACtGwvDaH0nAyvMal+tAIjYrh2yxDuLfh1Lw=",
20
+ "reduce-row-subgroup-rows.wgsl.jinja": "5cMGE1dmrcawwHdoxQD8kPSurwA1wNHyoAQfxAOE7vU=",
21
+ "reduce-row-subgroup.wgsl.jinja": "7bRBWIQ7oHOq/E54hGrmXIzLznzVH9oTfYaf1jmBzV8=",
22
+ "reduce-row-tree.wgsl.jinja": "a+iEV/cBkrlsVz6NS0GY/TAsUSX5rO4N14wY1dKfDi0=",
23
+ "reduce-serial-axis.wgsl.jinja": "ZZ1kKKhlLr3cMnRDRBhWm0ZDMluJwcoqmpRmL9ukuYM=",
24
+ "test.json": "9zz9YIFSmjUPFmYqqDm3yRVIjl4dJQpSTgGyxTIeN7I="
25
  }
26
  },
27
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
28
  "webgpu": {
29
+ "manifestSpec": "2.1",
30
  "variants": {
31
  "narrow_empty_identity": ["reduce-narrow-empty-identity.wgsl.jinja"],
32
  "contiguous_suffix_subgroup_vec4": ["reduce-row-subgroup.wgsl.jinja"],
build/webgpu/reduce-axis-split-reduce.wgsl.jinja CHANGED
@@ -8,29 +8,16 @@
8
  // Each output segment writes three partial planes: its maximum, the sum of
9
  // exp(x - maximum), and a packed NaN marker.
10
  {% endif %}
11
- {% set castF32 = castF32 is defined and castF32 %}
12
- {% set xa = "f32(" if castF32 else "" %}
13
  {% set ax = ")" if castF32 else "" %}
14
- {% if usesF16Spec is defined and usesF16Spec %}
15
- enable f16;
16
- {% endif %}
17
  {{ env.wgsl.resourceDeclarations }}
18
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
19
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
20
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
21
- {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
22
  fn {{ name }}() -> {{ scalar }} {
23
- {% if scalar == "i32" %}
24
- return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
25
- {% elif scalar == "u32" %}
26
- return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
27
- {% else %}
28
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
29
  return bitcast<f32>(bits);
30
- {% endif %}
31
- }
32
- {%- endmacro %}
33
-
34
 
35
  const WG: u32 = {{ workgroupSize }}u;
36
  const SPLIT: u32 = {{ split }}u;
@@ -42,6 +29,7 @@ fn is_nan_f32(value: f32) -> bool {
42
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
43
  }
44
  {% elif op == "max" or op == "min" %}
 
45
  {{ wgsl_minmax_identity("reduction_identity", op) }}
46
  {% endif %}
47
 
 
8
  // Each output segment writes three partial planes: its maximum, the sum of
9
  // exp(x - maximum), and a packed NaN marker.
10
  {% endif %}
11
+ {% set xa = ("vec" ~ columnVectorWidth ~ "<f32>(" if columnVectorWidth is defined else "f32(") if castF32 else "" %}
 
12
  {% set ax = ")" if castF32 else "" %}
 
 
 
13
  {{ env.wgsl.resourceDeclarations }}
14
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
15
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
16
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
 
17
  fn {{ name }}() -> {{ scalar }} {
 
 
 
 
 
18
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
19
  return bitcast<f32>(bits);
20
+ }{% endmacro %}
 
 
 
21
 
22
  const WG: u32 = {{ workgroupSize }}u;
23
  const SPLIT: u32 = {{ split }}u;
 
29
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
30
  }
31
  {% elif op == "max" or op == "min" %}
32
+
33
  {{ wgsl_minmax_identity("reduction_identity", op) }}
34
  {% endif %}
35
 
build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja CHANGED
@@ -2,37 +2,19 @@
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
5
- {% set addBias = addBias is defined and addBias %}
6
- {% set biasCols = biasCols | default(0) %}
7
- {% set intMode = intMode is defined and intMode %}
8
  {% set yv = "f16(" if outputF16 else "" %}
9
  {% set vy = ")" if outputF16 else "" %}
10
- {% if outputF16 %}
11
- enable f16;
12
- {% endif %}
13
  {{ env.wgsl.resourceDeclarations }}
14
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
15
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
16
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
17
- {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
18
  fn {{ name }}() -> {{ scalar }} {
19
- {% if scalar == "i32" %}
20
- return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
21
- {% elif scalar == "u32" %}
22
- return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
23
- {% else %}
24
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
25
  return bitcast<f32>(bits);
26
- {% endif %}
27
- }
28
- {%- endmacro %}
29
-
30
 
31
  const WG: u32 = {{ workgroupSize }}u;
32
  const SPLIT: u32 = {{ split }}u;
33
- {% if addBias %}
34
- const BIAS_COLS: u32 = {{ biasCols }}u;
35
- {% endif %}
36
  {% if op == "logsumexp" %}
37
  const F32_MIN: f32 = -3.4028234663852886e38;
38
  const F32_MAX: f32 = 3.4028234663852886e38;
@@ -48,7 +30,9 @@ fn is_nan_f32(value: f32) -> bool {
48
  @compute @workgroup_size(WG, 1, 1)
49
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
  @builtin(num_workgroups) nwg: vec3<u32>) {
51
- let stride = nwg.x * WG;
 
 
52
  let start = (gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG) + gid.x;
53
  for (var col = start; col < params.cols; col = col + stride) {
54
  {% if op == "logsumexp" %}
@@ -74,13 +58,6 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
74
  let finite_or_inf = select(global_max + log(sum), global_max, has_positive_inf);
75
  y[col] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
76
  {% else %}
77
- {% if intMode %}
78
- {% if op == "prod" %}
79
- var total = 1i;
80
- {% else %}
81
- var total = 0i;
82
- {% endif %}
83
- {% else %}
84
  {% if op == "max" %}
85
  var total = reduction_identity();
86
  {% elif op == "min" %}
@@ -89,7 +66,6 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
89
  var total = 1.0;
90
  {% else %}
91
  var total = 0.0;
92
- {% endif %}
93
  {% endif %}
94
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
95
  let p = partials[seg * params.cols + col];
@@ -101,9 +77,6 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
101
  total = total + p;
102
  {% endif %}
103
  }
104
- {% if addBias %}
105
- total = total + f32(bias[col % BIAS_COLS]);
106
- {% endif %}
107
  {% if op == "l2" %}
108
  y[col] = {{ yv }}sqrt(total){{ vy }};
109
  {% elif op == "logsum" %}
 
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
 
 
 
5
  {% set yv = "f16(" if outputF16 else "" %}
6
  {% set vy = ")" if outputF16 else "" %}
 
 
 
7
  {{ env.wgsl.resourceDeclarations }}
8
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
9
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
10
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
 
11
  fn {{ name }}() -> {{ scalar }} {
 
 
 
 
 
12
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
13
  return bitcast<f32>(bits);
14
+ }{% endmacro %}
 
 
 
15
 
16
  const WG: u32 = {{ workgroupSize }}u;
17
  const SPLIT: u32 = {{ split }}u;
 
 
 
18
  {% if op == "logsumexp" %}
19
  const F32_MIN: f32 = -3.4028234663852886e38;
20
  const F32_MAX: f32 = 3.4028234663852886e38;
 
30
  @compute @workgroup_size(WG, 1, 1)
31
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
32
  @builtin(num_workgroups) nwg: vec3<u32>) {
33
+ // The start already folds gid.y in, so the stride must span every y row too;
34
+ // an x-only stride would send y = 0 lanes over columns the y >= 1 rows own.
35
+ let stride = nwg.x * nwg.y * WG;
36
  let start = (gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG) + gid.x;
37
  for (var col = start; col < params.cols; col = col + stride) {
38
  {% if op == "logsumexp" %}
 
58
  let finite_or_inf = select(global_max + log(sum), global_max, has_positive_inf);
59
  y[col] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
60
  {% else %}
 
 
 
 
 
 
 
61
  {% if op == "max" %}
62
  var total = reduction_identity();
63
  {% elif op == "min" %}
 
66
  var total = 1.0;
67
  {% else %}
68
  var total = 0.0;
 
69
  {% endif %}
70
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
71
  let p = partials[seg * params.cols + col];
 
77
  total = total + p;
78
  {% endif %}
79
  }
 
 
 
80
  {% if op == "l2" %}
81
  y[col] = {{ yv }}sqrt(total){{ vy }};
82
  {% elif op == "logsum" %}
build/webgpu/reduce-axis0-splitk-reduce.wgsl.jinja CHANGED
@@ -2,29 +2,16 @@
2
  // segments increases residency for tall matrices. Each (column, segment)
3
  // invocation reduces one row slice and writes partials[segment * columns +
4
  // column]. Adjacent column threads keep row reads coalesced.
5
- {% set castF32 = castF32 is defined and castF32 %}
6
  {% set xa = "f32(" if castF32 else "" %}
7
  {% set ax = ")" if castF32 else "" %}
8
- {% if usesF16Spec is defined and usesF16Spec %}
9
- enable f16;
10
- {% endif %}
11
  {{ env.wgsl.resourceDeclarations }}
12
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
13
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
14
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
15
- {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
16
  fn {{ name }}() -> {{ scalar }} {
17
- {% if scalar == "i32" %}
18
- return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
19
- {% elif scalar == "u32" %}
20
- return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
21
- {% else %}
22
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
23
  return bitcast<f32>(bits);
24
- {% endif %}
25
- }
26
- {%- endmacro %}
27
-
28
 
29
  const WG: u32 = {{ workgroupSize }}u;
30
  const SPLIT: u32 = {{ split }}u;
 
2
  // segments increases residency for tall matrices. Each (column, segment)
3
  // invocation reduces one row slice and writes partials[segment * columns +
4
  // column]. Adjacent column threads keep row reads coalesced.
 
5
  {% set xa = "f32(" if castF32 else "" %}
6
  {% set ax = ")" if castF32 else "" %}
 
 
 
7
  {{ env.wgsl.resourceDeclarations }}
8
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
9
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
10
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
 
11
  fn {{ name }}() -> {{ scalar }} {
 
 
 
 
 
12
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
13
  return bitcast<f32>(bits);
14
+ }{% endmacro %}
 
 
 
15
 
16
  const WG: u32 = {{ workgroupSize }}u;
17
  const SPLIT: u32 = {{ split }}u;
build/webgpu/reduce-axis0-tilecols.wgsl.jinja CHANGED
@@ -13,17 +13,13 @@
13
  {% set rowEnd = "params.rows" %}
14
  {% set elem = "x[inputBase + row * params.cols + col]" %}
15
  {% endif %}
16
- {% set castF32 = castF32 is defined and castF32 %}
17
- {% set intMode = intMode is defined and intMode %}
18
  {% set scalar = "f32" if castF32 else scalar %}
19
  {% if castF32 %}
20
  {% set elem = "f32(" ~ elem ~ ")" %}
21
  {% endif %}
22
  {% set yv = "f16(" if castF32 else "" %}
23
  {% set vy = ")" if castF32 else "" %}
24
- {% if usesF16Spec is defined and usesF16Spec %}
25
- enable f16;
26
- {% endif %}
27
  {{ env.wgsl.resourceDeclarations }}
28
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
29
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
@@ -38,15 +34,12 @@ fn {{ name }}() -> {{ scalar }} {
38
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
39
  return bitcast<f32>(bits);
40
  {% endif %}
41
- }
42
- {%- endmacro %}
43
-
44
  {% if not splitMode and not intMode and (op == "logsum" or op == "logsumexp") %}
45
  fn negative_infinity() -> f32 {
46
  var bits = 0xff800000u;
47
  return bitcast<f32>(bits);
48
  }
49
-
50
  {% endif %}
51
 
52
  const WG: u32 = {{ workgroupSize }}u;
@@ -69,7 +62,6 @@ fn is_nan_f32(value: f32) -> bool {
69
  let bits = bitcast<u32>(value);
70
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
71
  }
72
-
73
  {% endif %}
74
  @compute @workgroup_size(WG, 1, 1)
75
  fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
@@ -77,8 +69,9 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
77
  let col_lane = tid % TILE_COLS;
78
  let row_lane = tid / TILE_COLS;
79
  {% if splitMode %}
80
- // Narrow outputs: wg.x covers every column tile, wg.y is the axis segment.
81
- let col = wg.x * TILE_COLS + col_lane;
 
82
  let outputIndex = col;
83
  let in_bounds = col < params.outputs;
84
  let seg = wg.y;
@@ -203,9 +196,9 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
203
  {% if op == "max" or op == "min" %}
204
  var acc = lane_identity();
205
  {% elif op == "prod" %}
206
- var acc = {% if intMode %}{{ scalar }}(1){% else %}1.0{% endif %};
207
  {% else %}
208
- var acc = {% if intMode %}{{ scalar }}(0){% else %}0.0{% endif %};
209
  {% endif %}
210
  if (in_bounds) {
211
  for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
@@ -240,15 +233,10 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
240
  {% if splitMode %}
241
  partials[seg * params.outputs + outputIndex] = total;
242
  {% else %}
243
- {% if op == "l2" and intMode %}
244
- y[outputIndex] = {{ scalar }}(sqrt(f32(total)));
245
- {% elif op == "l2" %}
246
  y[outputIndex] = {{ yv }}sqrt(total){{ vy }};
247
  {% elif op == "logsum" %}
248
  y[outputIndex] = {{ yv }}log(total){{ vy }};
249
- {% elif op == "mean" and intMode %}
250
- // Integer division truncates toward zero.
251
- y[outputIndex] = total / {{ scalar }}(params.rows);
252
  {% elif op == "mean" %}
253
  y[outputIndex] = {{ yv }}total / f32(params.rows){{ vy }};
254
  {% else %}
 
13
  {% set rowEnd = "params.rows" %}
14
  {% set elem = "x[inputBase + row * params.cols + col]" %}
15
  {% endif %}
16
+ {% set intMode = false %}
 
17
  {% set scalar = "f32" if castF32 else scalar %}
18
  {% if castF32 %}
19
  {% set elem = "f32(" ~ elem ~ ")" %}
20
  {% endif %}
21
  {% set yv = "f16(" if castF32 else "" %}
22
  {% set vy = ")" if castF32 else "" %}
 
 
 
23
  {{ env.wgsl.resourceDeclarations }}
24
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
25
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
 
34
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
35
  return bitcast<f32>(bits);
36
  {% endif %}
37
+ }{% endmacro %}
 
 
38
  {% if not splitMode and not intMode and (op == "logsum" or op == "logsumexp") %}
39
  fn negative_infinity() -> f32 {
40
  var bits = 0xff800000u;
41
  return bitcast<f32>(bits);
42
  }
 
43
  {% endif %}
44
 
45
  const WG: u32 = {{ workgroupSize }}u;
 
62
  let bits = bitcast<u32>(value);
63
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
64
  }
 
65
  {% endif %}
66
  @compute @workgroup_size(WG, 1, 1)
67
  fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
 
69
  let col_lane = tid % TILE_COLS;
70
  let row_lane = tid / TILE_COLS;
71
  {% if splitMode %}
72
+ // Narrow outputs: wg.x (with wg.z carrying its fold overflow) covers every
73
+ // column tile, wg.y is the axis segment.
74
+ let col = (wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u) * TILE_COLS + col_lane;
75
  let outputIndex = col;
76
  let in_bounds = col < params.outputs;
77
  let seg = wg.y;
 
196
  {% if op == "max" or op == "min" %}
197
  var acc = lane_identity();
198
  {% elif op == "prod" %}
199
+ var acc = 1.0;
200
  {% else %}
201
+ var acc = 0.0;
202
  {% endif %}
203
  if (in_bounds) {
204
  for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
 
233
  {% if splitMode %}
234
  partials[seg * params.outputs + outputIndex] = total;
235
  {% else %}
236
+ {% if op == "l2" %}
 
 
237
  y[outputIndex] = {{ yv }}sqrt(total){{ vy }};
238
  {% elif op == "logsum" %}
239
  y[outputIndex] = {{ yv }}log(total){{ vy }};
 
 
 
240
  {% elif op == "mean" %}
241
  y[outputIndex] = {{ yv }}total / f32(params.rows){{ vy }};
242
  {% else %}
build/webgpu/reduce-flat-partial.wgsl.jinja CHANGED
@@ -3,33 +3,20 @@
3
  // in registers, reduce within each workgroup, and write one partial each. A
4
  // following combine pass folds the partials and applies the operation finalizer.
5
  //
6
- // Scalar f32 bindings keep arbitrary element counts legal. The grid-stride loop
7
- // manually assembles full vec4 groups from contiguous scalars, and one global
8
- // thread folds the final zero-to-three scalar elements exactly once.
9
- {% set intMode = intMode is defined and intMode %}
10
- {% set castF32 = castF32 is defined and castF32 %}
11
  {% set xa = "f32(" if castF32 else "" %}
12
  {% set ax = ")" if castF32 else "" %}
13
- {% if usesF16Spec is defined and usesF16Spec %}
14
- enable f16;
15
- {% endif %}
16
  {{ env.wgsl.resourceDeclarations }}
17
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
18
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
19
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
20
- {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
21
  fn {{ name }}() -> {{ scalar }} {
22
- {% if scalar == "i32" %}
23
- return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
24
- {% elif scalar == "u32" %}
25
- return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
26
- {% else %}
27
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
28
  return bitcast<f32>(bits);
29
- {% endif %}
30
- }
31
- {%- endmacro %}
32
-
33
 
34
  const WG: u32 = {{ workgroupSize }}u;
35
  var<workgroup> red: array<{{ "i32" if intMode else "f32" }}, WG>;
@@ -44,13 +31,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
44
  @builtin(num_workgroups) nwg: vec3<u32>) {
45
  let tid = lid.x;
46
  let gstride = nwg.x * WG;
47
- {% if intMode %}
48
- {% if op == "prod" %}
49
- var acc = 1i;
50
- {% else %}
51
- var acc = 0i;
52
- {% endif %}
53
- {% elif op == "max" %}
54
  var acc = reduction_identity();
55
  {% elif op == "min" %}
56
  var acc = reduction_identity();
@@ -100,23 +81,23 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
100
  }
101
  red[tid] = acc;
102
  workgroupBarrier();
103
- var stride: u32 = WG / 2u;
 
 
 
 
 
104
  loop {
105
- if (stride == 0u) { break; }
106
- if (tid < stride) {
107
- {% if op == "max" %}
108
- red[tid] = max(red[tid], red[tid + stride]);
109
- {% elif op == "min" %}
110
- red[tid] = min(red[tid], red[tid + stride]);
111
- {% elif op == "prod" %}
112
- red[tid] = red[tid] * red[tid + stride];
113
- {% else %}
114
- red[tid] = red[tid] + red[tid + stride];
115
- {% endif %}
116
  }
117
- stride = stride / 2u;
118
  workgroupBarrier();
119
- }
 
120
  if (tid == 0u) {
121
  partials[wg.x] = red[0];
122
  }
 
3
  // in registers, reduce within each workgroup, and write one partial each. A
4
  // following combine pass folds the partials and applies the operation finalizer.
5
  //
6
+ // Scalar bindings support arbitrary element counts and a zero-to-three element
7
+ // tail. An aligned packed binding loads each group directly, preserving the
8
+ // component accumulation order and widening half storage before arithmetic.
9
+ {% set intMode = false %}
 
10
  {% set xa = "f32(" if castF32 else "" %}
11
  {% set ax = ")" if castF32 else "" %}
 
 
 
12
  {{ env.wgsl.resourceDeclarations }}
13
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
14
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
15
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
 
16
  fn {{ name }}() -> {{ scalar }} {
 
 
 
 
 
17
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
18
  return bitcast<f32>(bits);
19
+ }{% endmacro %}
 
 
 
20
 
21
  const WG: u32 = {{ workgroupSize }}u;
22
  var<workgroup> red: array<{{ "i32" if intMode else "f32" }}, WG>;
 
31
  @builtin(num_workgroups) nwg: vec3<u32>) {
32
  let tid = lid.x;
33
  let gstride = nwg.x * WG;
34
+ {% if op == "max" %}
 
 
 
 
 
 
35
  var acc = reduction_identity();
36
  {% elif op == "min" %}
37
  var acc = reduction_identity();
 
81
  }
82
  red[tid] = acc;
83
  workgroupBarrier();
84
+ {% macro wgsl_tree_fold_stmt(a, op, idx, svar) %}
85
+ {% if op == "max" or op == "min" %}
86
+ {{ a }}[{{ idx }}] = {{ op }}({{ a }}[{{ idx }}], {{ a }}[{{ idx }} + {{ svar }}]);{% else %}
87
+ {{ a }}[{{ idx }}] = {{ a }}[{{ idx }}] {{ "*" if op == "prod" else "+" }} {{ a }}[{{ idx }} + {{ svar }}];{% endif %}{% endmacro %}
88
+ {% macro wgsl_tree_fold(arrays, op="add", idx="lid", wg="WORKGROUP_SIZE", svar="stride", typed=false, form="tail", breakInline=false, bodyInline=false, barrierFirst=false, reuse=false) %}
89
+ var {{ svar }}{{ ": u32 " if typed else " " }}= {{ wg }} / 2u;
90
  loop {
91
+ if ({{ svar }} == 0u) { break; }
92
+ if ({{ idx }} < {{ svar }}) {
93
+ {% for a in arrays %}
94
+ {{ wgsl_tree_fold_stmt(a, op, idx, svar) }}
95
+ {% endfor %}
 
 
 
 
 
 
96
  }
97
+ {{ svar }} = {{ svar }} / 2u;
98
  workgroupBarrier();
99
+ }{% endmacro %}
100
+ {{ wgsl_tree_fold(["red"], op=op, idx="tid", wg="WG", typed=true, form="head", breakInline=true) }}
101
  if (tid == 0u) {
102
  partials[wg.x] = red[0];
103
  }
build/webgpu/reduce-noop-empty-axes.wgsl.jinja CHANGED
@@ -1,13 +1,16 @@
 
 
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  @compute @workgroup_size({{ reduceWorkgroupSize }})
4
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
5
- // 2D-folded flat index: gid.y carries the high bits past the
6
- // per-axis dispatch fold width (outputs > 16.7M elements).
7
- let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ reduceWorkgroupSize }}u;
8
- if (i >= params.count) {
9
- return;
10
- }
11
  let v = x[i];
12
  {% if op == "abs" %}
13
  y[i] = abs(v);
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
+ // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
6
+ if ({{ name }} >= {{ bound }}) {
7
+ return;
8
+ }{% endmacro %}
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
  @compute @workgroup_size({{ reduceWorkgroupSize }})
12
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
13
+ {{ flat_index_2d(reduceWorkgroupSize) }}
 
 
 
 
 
14
  let v = x[i];
15
  {% if op == "abs" %}
16
  y[i] = abs(v);
build/webgpu/reduce-row-subgroup-rows.wgsl.jinja CHANGED
@@ -15,18 +15,12 @@
15
  // The finalizer takes the logarithm of the sum.
16
  {% endif %}
17
  // f16 storage widens before accumulation and narrows only at the final store.
18
- {% set castF32 = castF32 is defined and castF32 %}
19
  {% set isInt = scalar == "i32" or scalar == "u32" %}
20
  {% set accScalar = scalar if isInt else "f32" %}
21
  {% set xv = "vec4<f32>(" if castF32 else "" %}
22
  {% set vx = ")" if castF32 else "" %}
23
  {% set yv = "f16(" if castF32 else "" %}
24
  {% set vy = ")" if castF32 else "" %}
25
- enable subgroups;
26
- {% if usesF16Spec is defined and usesF16Spec %}
27
- enable f16;
28
- {% endif %}
29
- {{ env.wgsl.resourceDeclarations }}
30
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
31
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
32
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
@@ -40,15 +34,15 @@ fn {{ name }}() -> {{ scalar }} {
40
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
41
  return bitcast<f32>(bits);
42
  {% endif %}
43
- }
44
- {%- endmacro %}
45
-
46
 
47
  const WG: u32 = {{ workgroupSize }}u;
48
- {%- if op == "max" or op == "min" %}
 
49
  {{ wgsl_minmax_identity("reduction_identity", op, accScalar) }}
50
- {%- endif %}
51
- {%- if op == "logsumexp" %}
52
 
53
  const F32_MIN: f32 = -3.4028234663852886e38;
54
  const F32_MAX: f32 = 3.4028234663852886e38;
@@ -57,7 +51,7 @@ fn is_nan_f32(value: f32) -> bool {
57
  let bits = bitcast<u32>(value);
58
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
59
  }
60
- {%- endif %}
61
 
62
  @compute @workgroup_size(WG, 1, 1)
63
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
@@ -110,58 +104,58 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
110
  let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
111
  y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
112
  }
113
- {%- else %}
114
  {% if op == "max" or op == "min" %}
115
  let INIT: {{ accScalar }} = reduction_identity();
116
- {%- elif op == "prod" %}
117
  let INIT: {{ accScalar }} = {{ "1.0" if not isInt else accScalar ~ "(1)" }};
118
- {%- else %}
119
  let INIT: {{ accScalar }} = {{ "0.0" if not isInt else accScalar ~ "(0)" }};
120
- {%- endif %}
121
  var acc4 = vec4<{{ accScalar }}>(INIT);
122
  for (var c = sgLane; c < chunkLimit; c = c + sgSize) {
123
  let v = {{ xv }}x[base + c]{{ vx }};
124
- {%- if op == "max" %}
125
  acc4 = max(acc4, v);
126
- {%- elif op == "min" %}
127
  acc4 = min(acc4, v);
128
- {%- elif op == "prod" %}
129
  acc4 = acc4 * v;
130
- {%- elif op == "l1" %}
131
  acc4 = acc4 + abs(v);
132
- {%- elif op == "l2" or op == "sumsquare" %}
133
  acc4 = acc4 + v * v;
134
- {%- else %}
135
  acc4 = acc4 + v;
136
- {%- endif %}
137
  }
138
- {%- if op == "max" %}
139
  let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
140
  let total = subgroupMax(acc);
141
- {%- elif op == "min" %}
142
  let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
143
  let total = subgroupMin(acc);
144
- {%- elif op == "prod" %}
145
  let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
146
  let total = subgroupMul(acc);
147
- {%- else %}
148
  let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
149
  let total = subgroupAdd(acc);
150
- {%- endif %}
151
  if (rowValid && sgLane == 0u) {
152
- {%- if op == "mean" and isInt %}
153
  y[row] = total / {{ accScalar }}(params.chunkCount * 4u);
154
- {%- elif op == "mean" %}
155
  y[row] = {{ yv }}total / f32(params.chunkCount * 4u){{ vy }};
156
- {%- elif op == "l2" and isInt %}
157
  y[row] = {{ accScalar }}(sqrt(f32(total)));
158
- {%- elif op == "l2" %}
159
  y[row] = {{ yv }}sqrt(total){{ vy }};
160
- {%- elif op == "logsum" %}
161
  y[row] = {{ yv }}log(total){{ vy }};
162
- {%- else %}
163
  y[row] = {{ yv }}total{{ vy }};
164
- {%- endif %}
165
  }
166
- {%- endif %}
167
  }
 
15
  // The finalizer takes the logarithm of the sum.
16
  {% endif %}
17
  // f16 storage widens before accumulation and narrows only at the final store.
 
18
  {% set isInt = scalar == "i32" or scalar == "u32" %}
19
  {% set accScalar = scalar if isInt else "f32" %}
20
  {% set xv = "vec4<f32>(" if castF32 else "" %}
21
  {% set vx = ")" if castF32 else "" %}
22
  {% set yv = "f16(" if castF32 else "" %}
23
  {% set vy = ")" if castF32 else "" %}
 
 
 
 
 
24
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
25
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
26
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
 
34
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
35
  return bitcast<f32>(bits);
36
  {% endif %}
37
+ }{% endmacro %}
38
+ enable subgroups;
39
+ {{ env.wgsl.resourceDeclarations }}
40
 
41
  const WG: u32 = {{ workgroupSize }}u;
42
+ {% if op == "max" or op == "min" %}
43
+
44
  {{ wgsl_minmax_identity("reduction_identity", op, accScalar) }}
45
+ {% elif op == "logsumexp" %}
 
46
 
47
  const F32_MIN: f32 = -3.4028234663852886e38;
48
  const F32_MAX: f32 = 3.4028234663852886e38;
 
51
  let bits = bitcast<u32>(value);
52
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
53
  }
54
+ {% endif %}
55
 
56
  @compute @workgroup_size(WG, 1, 1)
57
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
 
104
  let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
105
  y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
106
  }
107
+ {% else %}
108
  {% if op == "max" or op == "min" %}
109
  let INIT: {{ accScalar }} = reduction_identity();
110
+ {% elif op == "prod" %}
111
  let INIT: {{ accScalar }} = {{ "1.0" if not isInt else accScalar ~ "(1)" }};
112
+ {% else %}
113
  let INIT: {{ accScalar }} = {{ "0.0" if not isInt else accScalar ~ "(0)" }};
114
+ {% endif %}
115
  var acc4 = vec4<{{ accScalar }}>(INIT);
116
  for (var c = sgLane; c < chunkLimit; c = c + sgSize) {
117
  let v = {{ xv }}x[base + c]{{ vx }};
118
+ {% if op == "max" %}
119
  acc4 = max(acc4, v);
120
+ {% elif op == "min" %}
121
  acc4 = min(acc4, v);
122
+ {% elif op == "prod" %}
123
  acc4 = acc4 * v;
124
+ {% elif op == "l1" %}
125
  acc4 = acc4 + abs(v);
126
+ {% elif op == "l2" or op == "sumsquare" %}
127
  acc4 = acc4 + v * v;
128
+ {% else %}
129
  acc4 = acc4 + v;
130
+ {% endif %}
131
  }
132
+ {% if op == "max" %}
133
  let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
134
  let total = subgroupMax(acc);
135
+ {% elif op == "min" %}
136
  let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
137
  let total = subgroupMin(acc);
138
+ {% elif op == "prod" %}
139
  let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
140
  let total = subgroupMul(acc);
141
+ {% else %}
142
  let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
143
  let total = subgroupAdd(acc);
144
+ {% endif %}
145
  if (rowValid && sgLane == 0u) {
146
+ {% if op == "mean" and isInt %}
147
  y[row] = total / {{ accScalar }}(params.chunkCount * 4u);
148
+ {% elif op == "mean" %}
149
  y[row] = {{ yv }}total / f32(params.chunkCount * 4u){{ vy }};
150
+ {% elif op == "l2" and isInt %}
151
  y[row] = {{ accScalar }}(sqrt(f32(total)));
152
+ {% elif op == "l2" %}
153
  y[row] = {{ yv }}sqrt(total){{ vy }};
154
+ {% elif op == "logsum" %}
155
  y[row] = {{ yv }}log(total){{ vy }};
156
+ {% else %}
157
  y[row] = {{ yv }}total{{ vy }};
158
+ {% endif %}
159
  }
160
+ {% endif %}
161
  }
build/webgpu/reduce-row-subgroup.wgsl.jinja CHANGED
@@ -15,17 +15,11 @@
15
  // IEEE-754 bit patterns because WGSL rejects infinite constants.
16
  {% endif %}
17
  // f16 storage widens before accumulation and narrows only at the final store.
18
- {% set castF32 = castF32 is defined and castF32 %}
19
  {% set scalar = "f32" if castF32 else scalar %}
20
  {% set xv = ("vec4<f32>(" if vec4 else "f32(") if castF32 else "" %}
21
  {% set vx = ")" if castF32 else "" %}
22
  {% set yv = "f16(" if castF32 else "" %}
23
  {% set vy = ")" if castF32 else "" %}
24
- enable subgroups;
25
- {% if usesF16Spec is defined and usesF16Spec %}
26
- enable f16;
27
- {% endif %}
28
- {{ env.wgsl.resourceDeclarations }}
29
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
30
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
31
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
@@ -39,15 +33,15 @@ fn {{ name }}() -> {{ scalar }} {
39
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
40
  return bitcast<f32>(bits);
41
  {% endif %}
42
- }
43
- {%- endmacro %}
44
-
45
 
46
  const WG: u32 = {{ workgroupSize }}u;
47
- {%- if op == "max" or op == "min" %}
 
48
  {{ wgsl_minmax_identity("reduction_identity", op, scalar) }}
49
- {%- endif %}
50
- {%- if op == "logsumexp" %}
51
 
52
  const F32_MIN: f32 = -3.4028234663852886e38;
53
  const F32_MAX: f32 = 3.4028234663852886e38;
@@ -56,7 +50,7 @@ fn is_nan_f32(value: f32) -> bool {
56
  let bits = bitcast<u32>(value);
57
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
58
  }
59
- {%- endif %}
60
 
61
  var<workgroup> wgPartial: array<{{ scalar }}, WG>;
62
 
@@ -77,20 +71,19 @@ fn {{ name }}(value: {{ scalar }}, sgLid: u32, sgId: u32, numSg: u32) -> {{ scal
77
  workgroupBarrier();
78
  return total;
79
  }
80
- {%- endmacro %}
81
- {%- if op == "max" %}
82
  {{ emit_reduce("reduce_row", "subgroupMax", "total = max(total, wgPartial[i]);") }}
83
- {%- elif op == "min" %}
84
  {{ emit_reduce("reduce_row", "subgroupMin", "total = min(total, wgPartial[i]);") }}
85
- {%- elif op == "prod" %}
86
  {{ emit_reduce("reduce_row", "subgroupMul", "total = total * wgPartial[i];") }}
87
- {%- elif op == "logsumexp" %}
88
  {{ emit_reduce("reduce_row_add", "subgroupAdd", "total = total + wgPartial[i];") }}
89
  {{ emit_reduce("reduce_row_max", "subgroupMax", "total = max(total, wgPartial[i]);") }}
90
- {%- else %}
91
  {{ emit_reduce("reduce_row", "subgroupAdd", "total = total + wgPartial[i];") }}
92
- {%- endif %}
93
-
94
  @compute @workgroup_size(WG, 1, 1)
95
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
96
  @builtin(local_invocation_id) lid: vec3<u32>,
@@ -103,13 +96,13 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
103
  }
104
  let tid = lid.x;
105
  let base = row * params.chunkCount;
106
- {%- if op == "logsumexp" %}
107
  var localMax = F32_MIN;
108
  var localNan = 0.0;
109
  var localNanValue = 0.0;
110
  for (var c = tid; c < params.chunkCount; c = c + WG) {
111
  let v = {{ xv }}x[base + c]{{ vx }};
112
- {%- if vec4 %}
113
  {% for comp in ["x", "y", "z", "w"] %}
114
  if (is_nan_f32(v.{{ comp }})) {
115
  localNan = 1.0;
@@ -117,7 +110,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
117
  } else {
118
  localMax = max(localMax, v.{{ comp }});
119
  }
120
- {%- endfor %}
121
  {% else %}
122
  if (is_nan_f32(v)) {
123
  localNan = 1.0;
@@ -125,7 +118,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
125
  } else {
126
  localMax = max(localMax, v);
127
  }
128
- {%- endif %}
129
  }
130
  let rowMax = reduce_row_max(localMax, sgLid, sgId, numSg);
131
  let nanCount = reduce_row_add(localNan, sgLid, sgId, numSg);
@@ -135,83 +128,83 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
135
  var acc = 0.0;
136
  for (var c = tid; c < params.chunkCount; c = c + WG) {
137
  let v = {{ xv }}x[base + c]{{ vx }};
138
- {%- if vec4 %}
139
  let e = select(exp(v - vec4<f32>(rowMax)), vec4<f32>(0.0), hasPositiveInf || hasNan);
140
  acc = acc + ((e.x + e.y) + (e.z + e.w));
141
- {%- else %}
142
  acc = acc + select(exp(v - rowMax), 0.0, hasPositiveInf || hasNan);
143
- {%- endif %}
144
  }
145
  let sum = reduce_row_add(acc, sgLid, sgId, numSg);
146
  if (tid == 0u) {
147
  let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
148
  y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
149
  }
150
- {%- else %}
151
  {% if op == "max" or op == "min" %}
152
  let INIT: {{ scalar }} = reduction_identity();
153
- {%- elif op == "prod" %}
154
  let INIT: f32 = 1.0;
155
- {%- else %}
156
  let INIT: f32 = 0.0;
157
- {%- endif %}
158
  {% if vec4 %}
159
  var acc4 = vec4<{{ scalar }}>(INIT);
160
  for (var c = tid; c < params.chunkCount; c = c + WG) {
161
  let v = {{ xv }}x[base + c]{{ vx }};
162
- {%- if op == "max" %}
163
  acc4 = max(acc4, v);
164
- {%- elif op == "min" %}
165
  acc4 = min(acc4, v);
166
- {%- elif op == "prod" %}
167
  acc4 = acc4 * v;
168
- {%- elif op == "l1" %}
169
  acc4 = acc4 + abs(v);
170
- {%- elif op == "l2" or op == "sumsquare" %}
171
  acc4 = acc4 + v * v;
172
- {%- else %}
173
  acc4 = acc4 + v;
174
- {%- endif %}
175
  }
176
- {%- if op == "max" %}
177
  let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
178
- {%- elif op == "min" %}
179
  let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
180
- {%- elif op == "prod" %}
181
  let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
182
- {%- else %}
183
  let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
184
- {%- endif %}
185
  {% else %}
186
  var acc = INIT;
187
  for (var c = tid; c < params.chunkCount; c = c + WG) {
188
  let v = {{ xv }}x[base + c]{{ vx }};
189
- {%- if op == "max" %}
190
  acc = max(acc, v);
191
- {%- elif op == "min" %}
192
  acc = min(acc, v);
193
- {%- elif op == "prod" %}
194
  acc = acc * v;
195
- {%- elif op == "l1" %}
196
  acc = acc + abs(v);
197
- {%- elif op == "l2" or op == "sumsquare" %}
198
  acc = acc + v * v;
199
- {%- else %}
200
  acc = acc + v;
201
- {%- endif %}
202
  }
203
- {%- endif %}
204
  let total = reduce_row(acc, sgLid, sgId, numSg);
205
  if (tid == 0u) {
206
- {%- if op == "mean" %}
207
  y[row] = {{ yv }}total / f32(params.cols){{ vy }};
208
- {%- elif op == "l2" %}
209
  y[row] = {{ yv }}sqrt(total){{ vy }};
210
- {%- elif op == "logsum" %}
211
  y[row] = {{ yv }}log(total){{ vy }};
212
- {%- else %}
213
  y[row] = {{ yv }}total{{ vy }};
214
- {%- endif %}
215
  }
216
- {%- endif %}
217
  }
 
15
  // IEEE-754 bit patterns because WGSL rejects infinite constants.
16
  {% endif %}
17
  // f16 storage widens before accumulation and narrows only at the final store.
 
18
  {% set scalar = "f32" if castF32 else scalar %}
19
  {% set xv = ("vec4<f32>(" if vec4 else "f32(") if castF32 else "" %}
20
  {% set vx = ")" if castF32 else "" %}
21
  {% set yv = "f16(" if castF32 else "" %}
22
  {% set vy = ")" if castF32 else "" %}
 
 
 
 
 
23
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
24
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
25
  * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
 
33
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
34
  return bitcast<f32>(bits);
35
  {% endif %}
36
+ }{% endmacro %}
37
+ enable subgroups;
38
+ {{ env.wgsl.resourceDeclarations }}
39
 
40
  const WG: u32 = {{ workgroupSize }}u;
41
+ {% if op == "max" or op == "min" %}
42
+
43
  {{ wgsl_minmax_identity("reduction_identity", op, scalar) }}
44
+ {% elif op == "logsumexp" %}
 
45
 
46
  const F32_MIN: f32 = -3.4028234663852886e38;
47
  const F32_MAX: f32 = 3.4028234663852886e38;
 
50
  let bits = bitcast<u32>(value);
51
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
52
  }
53
+ {% endif %}
54
 
55
  var<workgroup> wgPartial: array<{{ scalar }}, WG>;
56
 
 
71
  workgroupBarrier();
72
  return total;
73
  }
74
+ {% endmacro %}
75
+ {% if op == "max" %}
76
  {{ emit_reduce("reduce_row", "subgroupMax", "total = max(total, wgPartial[i]);") }}
77
+ {% elif op == "min" %}
78
  {{ emit_reduce("reduce_row", "subgroupMin", "total = min(total, wgPartial[i]);") }}
79
+ {% elif op == "prod" %}
80
  {{ emit_reduce("reduce_row", "subgroupMul", "total = total * wgPartial[i];") }}
81
+ {% elif op == "logsumexp" %}
82
  {{ emit_reduce("reduce_row_add", "subgroupAdd", "total = total + wgPartial[i];") }}
83
  {{ emit_reduce("reduce_row_max", "subgroupMax", "total = max(total, wgPartial[i]);") }}
84
+ {% else %}
85
  {{ emit_reduce("reduce_row", "subgroupAdd", "total = total + wgPartial[i];") }}
86
+ {% endif %}
 
87
  @compute @workgroup_size(WG, 1, 1)
88
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
89
  @builtin(local_invocation_id) lid: vec3<u32>,
 
96
  }
97
  let tid = lid.x;
98
  let base = row * params.chunkCount;
99
+ {% if op == "logsumexp" %}
100
  var localMax = F32_MIN;
101
  var localNan = 0.0;
102
  var localNanValue = 0.0;
103
  for (var c = tid; c < params.chunkCount; c = c + WG) {
104
  let v = {{ xv }}x[base + c]{{ vx }};
105
+ {% if vec4 %}
106
  {% for comp in ["x", "y", "z", "w"] %}
107
  if (is_nan_f32(v.{{ comp }})) {
108
  localNan = 1.0;
 
110
  } else {
111
  localMax = max(localMax, v.{{ comp }});
112
  }
113
+ {% endfor %}
114
  {% else %}
115
  if (is_nan_f32(v)) {
116
  localNan = 1.0;
 
118
  } else {
119
  localMax = max(localMax, v);
120
  }
121
+ {% endif %}
122
  }
123
  let rowMax = reduce_row_max(localMax, sgLid, sgId, numSg);
124
  let nanCount = reduce_row_add(localNan, sgLid, sgId, numSg);
 
128
  var acc = 0.0;
129
  for (var c = tid; c < params.chunkCount; c = c + WG) {
130
  let v = {{ xv }}x[base + c]{{ vx }};
131
+ {% if vec4 %}
132
  let e = select(exp(v - vec4<f32>(rowMax)), vec4<f32>(0.0), hasPositiveInf || hasNan);
133
  acc = acc + ((e.x + e.y) + (e.z + e.w));
134
+ {% else %}
135
  acc = acc + select(exp(v - rowMax), 0.0, hasPositiveInf || hasNan);
136
+ {% endif %}
137
  }
138
  let sum = reduce_row_add(acc, sgLid, sgId, numSg);
139
  if (tid == 0u) {
140
  let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
141
  y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
142
  }
143
+ {% else %}
144
  {% if op == "max" or op == "min" %}
145
  let INIT: {{ scalar }} = reduction_identity();
146
+ {% elif op == "prod" %}
147
  let INIT: f32 = 1.0;
148
+ {% else %}
149
  let INIT: f32 = 0.0;
150
+ {% endif %}
151
  {% if vec4 %}
152
  var acc4 = vec4<{{ scalar }}>(INIT);
153
  for (var c = tid; c < params.chunkCount; c = c + WG) {
154
  let v = {{ xv }}x[base + c]{{ vx }};
155
+ {% if op == "max" %}
156
  acc4 = max(acc4, v);
157
+ {% elif op == "min" %}
158
  acc4 = min(acc4, v);
159
+ {% elif op == "prod" %}
160
  acc4 = acc4 * v;
161
+ {% elif op == "l1" %}
162
  acc4 = acc4 + abs(v);
163
+ {% elif op == "l2" or op == "sumsquare" %}
164
  acc4 = acc4 + v * v;
165
+ {% else %}
166
  acc4 = acc4 + v;
167
+ {% endif %}
168
  }
169
+ {% if op == "max" %}
170
  let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
171
+ {% elif op == "min" %}
172
  let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
173
+ {% elif op == "prod" %}
174
  let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
175
+ {% else %}
176
  let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
177
+ {% endif %}
178
  {% else %}
179
  var acc = INIT;
180
  for (var c = tid; c < params.chunkCount; c = c + WG) {
181
  let v = {{ xv }}x[base + c]{{ vx }};
182
+ {% if op == "max" %}
183
  acc = max(acc, v);
184
+ {% elif op == "min" %}
185
  acc = min(acc, v);
186
+ {% elif op == "prod" %}
187
  acc = acc * v;
188
+ {% elif op == "l1" %}
189
  acc = acc + abs(v);
190
+ {% elif op == "l2" or op == "sumsquare" %}
191
  acc = acc + v * v;
192
+ {% else %}
193
  acc = acc + v;
194
+ {% endif %}
195
  }
196
+ {% endif %}
197
  let total = reduce_row(acc, sgLid, sgId, numSg);
198
  if (tid == 0u) {
199
+ {% if op == "mean" %}
200
  y[row] = {{ yv }}total / f32(params.cols){{ vy }};
201
+ {% elif op == "l2" %}
202
  y[row] = {{ yv }}sqrt(total){{ vy }};
203
+ {% elif op == "logsum" %}
204
  y[row] = {{ yv }}log(total){{ vy }};
205
+ {% else %}
206
  y[row] = {{ yv }}total{{ vy }};
207
+ {% endif %}
208
  }
209
+ {% endif %}
210
  }
build/webgpu/reduce-row-tree.wgsl.jinja CHANGED
@@ -16,15 +16,11 @@
16
  // then narrows only at the final store.
17
  {% set isVec4 = vec4 is defined and vec4 %}
18
  {% set rowIsEmpty = "params.chunkCount == 0u" if isVec4 else "params.cols == 0u" %}
19
- {% set castF32 = castF32 is defined and castF32 %}
20
  {% set scalar = "f32" if castF32 else scalar %}
21
  {% set xv = ("vec4<f32>(" if vec4 else "f32(") if castF32 else "" %}
22
  {% set vx = ")" if castF32 else "" %}
23
  {% set yv = "f16(" if castF32 else "" %}
24
  {% set vy = ")" if castF32 else "" %}
25
- {% if usesF16Spec is defined and usesF16Spec %}
26
- enable f16;
27
- {% endif %}
28
  {{ env.wgsl.resourceDeclarations }}
29
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
30
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
@@ -39,15 +35,12 @@ fn {{ name }}() -> {{ scalar }} {
39
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
40
  return bitcast<f32>(bits);
41
  {% endif %}
42
- }
43
- {%- endmacro %}
44
-
45
  {% if scalar != "i32" and scalar != "u32" and (op == "logsum" or op == "logsumexp") %}
46
  fn negative_infinity() -> f32 {
47
  var bits = 0xff800000u;
48
  return bitcast<f32>(bits);
49
  }
50
-
51
  {% endif %}
52
 
53
  const WG: u32 = {{ workgroupSize }}u;
@@ -57,8 +50,8 @@ const F32_MIN: f32 = -3.4028234663852886e38;
57
  const F32_MAX: f32 = 3.4028234663852886e38;
58
 
59
  var<workgroup> partial: array<f32, WG>;
60
- {% macro wgsl_tree_reduce_f32(name, mode, buffer="partial", wg="WG", trailingBarrier=true) %}
61
- fn {{ name }}(value: f32, tid: u32) -> f32 {
62
  {{ buffer }}[tid] = value;
63
  workgroupBarrier();
64
  // Ceil-halving keeps every lane when the workgroup size is not a power of
@@ -84,20 +77,15 @@ fn {{ name }}(value: f32, tid: u32) -> f32 {
84
  // slot 0 here, so the next call's first store must not run until all lanes have read it.
85
  // `trailingBarrier=false` is safe only when the buffer is never written again before kernel exit.
86
  let reduced = {{ buffer }}[0];
87
- {% if trailingBarrier %}
88
  workgroupBarrier();
89
- {% endif %}
90
  return reduced;
91
  }
92
  {% endmacro %}
93
-
94
  {{ wgsl_tree_reduce_f32("reduce_sum", "add", "partial", "WG") }}
95
  {{ wgsl_tree_reduce_f32("reduce_max", "max", "partial", "WG") }}
96
-
97
  fn is_nan_f32(value: f32) -> bool {
98
  let bits = bitcast<u32>(value);
99
- return (bits & 0x7f800000u) == 0x7f800000u
100
- && (bits & 0x007fffffu) != 0u;
101
  }
102
  {% else %}
103
  {% set is_int = scalar == "i32" or scalar == "u32" %}
@@ -216,13 +204,14 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
216
  {% endif %}
217
  return;
218
  }
 
219
  {% elif op == "logsum" %}
220
  if ({{ rowIsEmpty }}) {
221
  if (tid == 0u) { y[row] = {{ yv }}negative_infinity(){{ vy }}; }
222
  return;
223
  }
224
- {% endif %}
225
 
 
226
  {% if vec4 %}
227
  var acc4 = vec4<{{ accType }}>(identity());
228
  for (var col = tid; col < params.chunkCount; col = col + WG) {
 
16
  // then narrows only at the final store.
17
  {% set isVec4 = vec4 is defined and vec4 %}
18
  {% set rowIsEmpty = "params.chunkCount == 0u" if isVec4 else "params.cols == 0u" %}
 
19
  {% set scalar = "f32" if castF32 else scalar %}
20
  {% set xv = ("vec4<f32>(" if vec4 else "f32(") if castF32 else "" %}
21
  {% set vx = ")" if castF32 else "" %}
22
  {% set yv = "f16(" if castF32 else "" %}
23
  {% set vy = ")" if castF32 else "" %}
 
 
 
24
  {{ env.wgsl.resourceDeclarations }}
25
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
26
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
 
35
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
36
  return bitcast<f32>(bits);
37
  {% endif %}
38
+ }{% endmacro %}
 
 
39
  {% if scalar != "i32" and scalar != "u32" and (op == "logsum" or op == "logsumexp") %}
40
  fn negative_infinity() -> f32 {
41
  var bits = 0xff800000u;
42
  return bitcast<f32>(bits);
43
  }
 
44
  {% endif %}
45
 
46
  const WG: u32 = {{ workgroupSize }}u;
 
50
  const F32_MAX: f32 = 3.4028234663852886e38;
51
 
52
  var<workgroup> partial: array<f32, WG>;
53
+ {% macro wgsl_tree_reduce_f32(name, mode, buffer="partial", wg="WG", trailingBarrier=true, valueType="f32") %}
54
+ fn {{ name }}(value: {{ valueType }}, tid: u32) -> {{ valueType }} {
55
  {{ buffer }}[tid] = value;
56
  workgroupBarrier();
57
  // Ceil-halving keeps every lane when the workgroup size is not a power of
 
77
  // slot 0 here, so the next call's first store must not run until all lanes have read it.
78
  // `trailingBarrier=false` is safe only when the buffer is never written again before kernel exit.
79
  let reduced = {{ buffer }}[0];
 
80
  workgroupBarrier();
 
81
  return reduced;
82
  }
83
  {% endmacro %}
 
84
  {{ wgsl_tree_reduce_f32("reduce_sum", "add", "partial", "WG") }}
85
  {{ wgsl_tree_reduce_f32("reduce_max", "max", "partial", "WG") }}
 
86
  fn is_nan_f32(value: f32) -> bool {
87
  let bits = bitcast<u32>(value);
88
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
 
89
  }
90
  {% else %}
91
  {% set is_int = scalar == "i32" or scalar == "u32" %}
 
204
  {% endif %}
205
  return;
206
  }
207
+
208
  {% elif op == "logsum" %}
209
  if ({{ rowIsEmpty }}) {
210
  if (tid == 0u) { y[row] = {{ yv }}negative_infinity(){{ vy }}; }
211
  return;
212
  }
 
213
 
214
+ {% endif %}
215
  {% if vec4 %}
216
  var acc4 = vec4<{{ accType }}>(identity());
217
  for (var col = tid; col < params.chunkCount; col = col + WG) {
build/webgpu/reduce-serial-axis.wgsl.jinja CHANGED
@@ -1,5 +1,4 @@
1
  {% macro reduce_multi_axis_offset(hasReduced) %}
2
-
3
  // One thread per output element walks the Cartesian product of the reduced axes,
4
  // linearized as reduce_linear. Specialized shapes make every input offset a sum
5
  // of coordinate-times-constant terms.
@@ -44,18 +43,20 @@ fn input_offset(out_index: u32{% if hasReduced %}, reduce_linear: u32{% endif %}
44
  {% set src.value = "(" ~ src.value ~ " * " ~ dataShape[a] ~ "u + coord" ~ a ~ ")" %}
45
  {% endfor %}
46
  return {{ src.value }};
47
- }
48
- {%- endmacro %}
49
  // Serial one-thread-per-output reduction for the no-feature tier. f16 storage
50
  // is widened before every accumulation and narrowed only for the final store.
51
- {% set castF32 = castF32 is defined and castF32 %}
52
- {% set logicalBool = logicalBool is defined and logicalBool %}
53
- {% set intMode = intMode is defined and intMode %}
54
  {% set yv = "f16(" if castF32 else "" %}
55
  {% set vy = ")" if castF32 else "" %}
56
- {% if usesF16Spec is defined and usesF16Spec %}
57
- enable f16;
58
- {% endif %}
 
 
 
 
 
59
  {{ env.wgsl.resourceDeclarations }}
60
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
61
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
@@ -70,15 +71,12 @@ fn {{ name }}() -> {{ scalar }} {
70
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
71
  return bitcast<f32>(bits);
72
  {% endif %}
73
- }
74
- {%- endmacro %}
75
-
76
  {% if not intMode and (op == "logsum" or op == "logsumexp") %}
77
  fn negative_infinity() -> f32 {
78
  var bits = 0xff800000u;
79
  return bitcast<f32>(bits);
80
  }
81
-
82
  {% endif %}
83
  {% if op == "max" or op == "min" %}
84
 
@@ -86,7 +84,6 @@ fn negative_infinity() -> f32 {
86
  {% elif op == "logsumexp" %}
87
 
88
  const F32_MIN: f32 = -3.4028234663852886e38;
89
- {% if not intMode %}
90
  const F32_MAX: f32 = 3.4028234663852886e38;
91
 
92
  fn is_nan_f32(value: f32) -> bool {
@@ -94,7 +91,6 @@ fn is_nan_f32(value: f32) -> bool {
94
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
95
  }
96
  {% endif %}
97
- {% endif %}
98
  {% if indexing == "rankn" %}
99
 
100
  fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
@@ -131,7 +127,8 @@ fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
131
  {% if indexing == "multiaxis" %}
132
  {% set hasReducedAxis = namespace(value=false) %}
133
  {% for a in range(rank) %}{% if reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
134
- {{- reduce_multi_axis_offset(hasReducedAxis.value) }}
 
135
  {% endif %}
136
  {% if indexing == "multiaxis" %}
137
  {% set mcount = namespace(value=1) %}
@@ -166,31 +163,8 @@ fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
166
 
167
  @compute @workgroup_size({{ reduceWorkgroupSize }})
168
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
169
- // 2D-folded flat index: gid.y carries the high bits past the
170
- // per-axis dispatch fold width (outputs > 16.7M elements).
171
- let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ reduceWorkgroupSize }}u;
172
- if (i >= params.outCount) {
173
- return;
174
- }
175
  {% if op == "logsumexp" %}
176
- {% if intMode %}
177
- // Integer logsumexp widens each element for exp/log, then truncates the result
178
- // back to the integer output type.
179
- // Integers are never NaN, so the f32 NaN-propagation path is unnecessary here.
180
- if ({{ count }} == 0u) {
181
- y[i] = {{ scalar }}(F32_MIN);
182
- return;
183
- }
184
- var m = F32_MIN;
185
- for (var r = 0u; r < {{ count }}; r = r + 1u) {
186
- m = max(m, f32({{ at }}));
187
- }
188
- var acc = 0.0;
189
- for (var r = 0u; r < {{ count }}; r = r + 1u) {
190
- acc = acc + exp(f32({{ at }}) - m);
191
- }
192
- y[i] = {{ scalar }}(m + log(acc));
193
- {% else %}
194
  if ({{ count }} == 0u) {
195
  y[i] = {{ yv }}negative_infinity(){{ vy }};
196
  return;
@@ -220,7 +194,6 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
220
  acc = acc + exp({{ at }} - m);
221
  }
222
  y[i] = {{ yv }}m + log(acc){{ vy }};
223
- {% endif %}
224
  {% elif op == "max" or op == "min" %}
225
  if ({{ count }} == 0u) {
226
  y[i] = {{ yv }}empty_value(){{ vy }};
@@ -234,11 +207,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
234
  {% else %}
235
  {% if op == "mean" %}
236
  if ({{ count }} == 0u) {
237
- {% if intMode %}
238
- y[i] = {{ scalar }}(0);
239
- {% else %}
240
  y[i] = {{ yv }}0.0{{ vy }};
241
- {% endif %}
242
  return;
243
  }
244
  {% elif op == "logsum" %}
@@ -247,13 +216,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
247
  return;
248
  }
249
  {% endif %}
250
- {% if intMode %}
251
- // Integer reduction accumulates in the output type. sum/prod/l1/sumsquare stay
252
- // in integer arithmetic; l2/logsum widen for sqrt/log and truncate afterward.
253
- var acc = {{ scalar }}({{ "1" if op == "prod" else "0" }});
254
- {% else %}
255
  var acc = {{ "1.0" if op == "prod" else "0.0" }};
256
- {% endif %}
257
  for (var r = 0u; r < {{ count }}; r = r + 1u) {
258
  {% if op == "prod" %}
259
  acc = acc * {{ at }};
@@ -267,19 +230,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
267
  {% endif %}
268
  }
269
  {% if op == "l2" %}
270
- {% if intMode %}
271
- y[i] = {{ scalar }}(sqrt(f32(acc)));
272
- {% else %}
273
  y[i] = {{ yv }}sqrt(acc){{ vy }};
274
- {% endif %}
275
  {% elif op == "logsum" %}
276
  y[i] = {{ yv }}log(acc){{ vy }};
277
  {% elif op == "mean" %}
278
- {% if intMode %}
279
- y[i] = acc / {{ scalar }}({{ count }});
280
- {% else %}
281
  y[i] = {{ yv }}acc / f32({{ count }}){{ vy }};
282
- {% endif %}
283
  {% else %}
284
  y[i] = {{ yv }}acc{{ vy }};
285
  {% endif %}
 
1
  {% macro reduce_multi_axis_offset(hasReduced) %}
 
2
  // One thread per output element walks the Cartesian product of the reduced axes,
3
  // linearized as reduce_linear. Specialized shapes make every input offset a sum
4
  // of coordinate-times-constant terms.
 
43
  {% set src.value = "(" ~ src.value ~ " * " ~ dataShape[a] ~ "u + coord" ~ a ~ ")" %}
44
  {% endfor %}
45
  return {{ src.value }};
46
+ }{% endmacro %}
 
47
  // Serial one-thread-per-output reduction for the no-feature tier. f16 storage
48
  // is widened before every accumulation and narrowed only for the final store.
49
+ {% set intMode = false %}
 
 
50
  {% set yv = "f16(" if castF32 else "" %}
51
  {% set vy = ")" if castF32 else "" %}
52
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
53
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
54
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
55
+ // per-axis workgroup fold width.
56
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
57
+ if ({{ name }} >= {{ bound }}) {
58
+ return;
59
+ }{% endmacro %}
60
  {{ env.wgsl.resourceDeclarations }}
61
  {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
62
  /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
 
71
  var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
72
  return bitcast<f32>(bits);
73
  {% endif %}
74
+ }{% endmacro %}
 
 
75
  {% if not intMode and (op == "logsum" or op == "logsumexp") %}
76
  fn negative_infinity() -> f32 {
77
  var bits = 0xff800000u;
78
  return bitcast<f32>(bits);
79
  }
 
80
  {% endif %}
81
  {% if op == "max" or op == "min" %}
82
 
 
84
  {% elif op == "logsumexp" %}
85
 
86
  const F32_MIN: f32 = -3.4028234663852886e38;
 
87
  const F32_MAX: f32 = 3.4028234663852886e38;
88
 
89
  fn is_nan_f32(value: f32) -> bool {
 
91
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
92
  }
93
  {% endif %}
 
94
  {% if indexing == "rankn" %}
95
 
96
  fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
 
127
  {% if indexing == "multiaxis" %}
128
  {% set hasReducedAxis = namespace(value=false) %}
129
  {% for a in range(rank) %}{% if reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
130
+
131
+ {{ reduce_multi_axis_offset(hasReducedAxis.value) }}
132
  {% endif %}
133
  {% if indexing == "multiaxis" %}
134
  {% set mcount = namespace(value=1) %}
 
163
 
164
  @compute @workgroup_size({{ reduceWorkgroupSize }})
165
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
166
+ {{ flat_index_2d(reduceWorkgroupSize, bound="params.outCount") }}
 
 
 
 
 
167
  {% if op == "logsumexp" %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  if ({{ count }} == 0u) {
169
  y[i] = {{ yv }}negative_infinity(){{ vy }};
170
  return;
 
194
  acc = acc + exp({{ at }} - m);
195
  }
196
  y[i] = {{ yv }}m + log(acc){{ vy }};
 
197
  {% elif op == "max" or op == "min" %}
198
  if ({{ count }} == 0u) {
199
  y[i] = {{ yv }}empty_value(){{ vy }};
 
207
  {% else %}
208
  {% if op == "mean" %}
209
  if ({{ count }} == 0u) {
 
 
 
210
  y[i] = {{ yv }}0.0{{ vy }};
 
211
  return;
212
  }
213
  {% elif op == "logsum" %}
 
216
  return;
217
  }
218
  {% endif %}
 
 
 
 
 
219
  var acc = {{ "1.0" if op == "prod" else "0.0" }};
 
220
  for (var r = 0u; r < {{ count }}; r = r + 1u) {
221
  {% if op == "prod" %}
222
  acc = acc * {{ at }};
 
230
  {% endif %}
231
  }
232
  {% if op == "l2" %}
 
 
 
233
  y[i] = {{ yv }}sqrt(acc){{ vy }};
 
234
  {% elif op == "logsum" %}
235
  y[i] = {{ yv }}log(acc){{ vy }};
236
  {% elif op == "mean" %}
 
 
 
237
  y[i] = {{ yv }}acc / f32({{ count }}){{ vy }};
 
238
  {% else %}
239
  y[i] = {{ yv }}acc{{ vy }};
240
  {% endif %}
build/webgpu/test.json CHANGED
@@ -1107,7 +1107,7 @@
1107
  {
1108
  "name": "ort_int8_axis1_extrema",
1109
  "provenance": {
1110
- "source": "ONNX Runtime CPUExecutionProvider",
1111
  "notes": "Covers the signed narrow-integer reduction route."
1112
  },
1113
  "attrs": { "axes": [1], "keepdims": 0 },
@@ -1121,7 +1121,7 @@
1121
  {
1122
  "name": "ort_uint8_axis0_extrema",
1123
  "provenance": {
1124
- "source": "ONNX Runtime CPUExecutionProvider",
1125
  "notes": "Covers the unsigned narrow-integer reduction route."
1126
  },
1127
  "attrs": { "axes": [0], "keepdims": 0 },
@@ -1135,7 +1135,7 @@
1135
  {
1136
  "name": "ort_int8_empty_axis1_highest_identity",
1137
  "provenance": {
1138
- "source": "ONNX Runtime CPUExecutionProvider",
1139
  "notes": "An empty INT8 minimum is the logical dtype maximum (127), not the physical i32 storage maximum."
1140
  },
1141
  "attrs": { "axes": [1], "keepdims": 1 },
@@ -1152,7 +1152,7 @@
1152
  {
1153
  "name": "ort_uint8_empty_axis1_highest_identity",
1154
  "provenance": {
1155
- "source": "ONNX Runtime CPUExecutionProvider",
1156
  "notes": "An empty UINT8 minimum is the logical dtype maximum (255), not the physical u32 storage maximum."
1157
  },
1158
  "attrs": { "axes": [1], "keepdims": 1 },
 
1107
  {
1108
  "name": "ort_int8_axis1_extrema",
1109
  "provenance": {
1110
+ "source": "ONNX Runtime's CPU provider",
1111
  "notes": "Covers the signed narrow-integer reduction route."
1112
  },
1113
  "attrs": { "axes": [1], "keepdims": 0 },
 
1121
  {
1122
  "name": "ort_uint8_axis0_extrema",
1123
  "provenance": {
1124
+ "source": "ONNX Runtime's CPU provider",
1125
  "notes": "Covers the unsigned narrow-integer reduction route."
1126
  },
1127
  "attrs": { "axes": [0], "keepdims": 0 },
 
1135
  {
1136
  "name": "ort_int8_empty_axis1_highest_identity",
1137
  "provenance": {
1138
+ "source": "ONNX Runtime's CPU provider",
1139
  "notes": "An empty INT8 minimum is the logical dtype maximum (127), not the physical i32 storage maximum."
1140
  },
1141
  "attrs": { "axes": [1], "keepdims": 1 },
 
1152
  {
1153
  "name": "ort_uint8_empty_axis1_highest_identity",
1154
  "provenance": {
1155
+ "source": "ONNX Runtime's CPU provider",
1156
  "notes": "An empty UINT8 minimum is the logical dtype maximum (255), not the physical u32 storage maximum."
1157
  },
1158
  "attrs": { "axes": [1], "keepdims": 1 },