Xenova HF Staff commited on
Commit
96d2c8a
·
verified ·
1 Parent(s): a76bbb1

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,91 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.ReduceMin
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 20
12
+
13
+ ## Description
14
+
15
+ Computes the minimum of input tensor elements along the specified axes. The output rank matches the input when `keepdims` is 1; otherwise reduced dimensions are pruned. Reduction over an empty set yields positive infinity when the dtype supports it, or the dtype's maximum value otherwise. For Boolean inputs, `false` is less than `true`.
16
+
17
+ See the [ONNX `ReduceMin` spec](https://onnx.ai/onnx/operators/onnx__ReduceMin.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `data` | `x` | `T` | — | — | The input tensor to reduce. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- |
29
+ | `reduced` | `y` | `T` | derived | — | The reduced output tensor containing minimum values. | required |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `keepdims` | `1` | If 1, retains the reduced dimensions with size 1 in the output; if 0, those dimensions are pruned. |
38
+ | `noop_with_empty_axes` | `0` | When axes is empty: if 0 (default) reduces over all axes; if 1 the op acts as an identity (no-op reduction). |
39
+ | `axes` | `[]` | Values of the optional ONNX `axes` tensor input, supplied through this request attribute; an empty list follows `noop_with_empty_axes`. |
40
+
41
+ ## Type constraints
42
+
43
+ | Variable | Allowed dtypes |
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.
50
+
51
+ ## Files
52
+
53
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
54
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
+ - [`test.json`](build/webgpu/test.json) — correctness cases
56
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
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)
60
+ - [`reduce-axis0-splitk-reduce.wgsl.jinja`](build/webgpu/reduce-axis0-splitk-reduce.wgsl.jinja)
61
+ - [`reduce-axis0-tilecols.wgsl.jinja`](build/webgpu/reduce-axis0-tilecols.wgsl.jinja)
62
+ - [`reduce-flat-partial.wgsl.jinja`](build/webgpu/reduce-flat-partial.wgsl.jinja)
63
+ - [`reduce-narrow-empty-identity.wgsl.jinja`](build/webgpu/reduce-narrow-empty-identity.wgsl.jinja)
64
+ - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
65
+ - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
66
+ - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
67
+ - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
68
+
69
+ ## Use with `@huggingface/kernels`
70
+
71
+ The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
72
+
73
+ The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
74
+
75
+ - `y`
76
+
77
+ Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
78
+
79
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
80
+
81
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
82
+
83
+ ```js
84
+ import { getKernel } from "@huggingface/kernels";
85
+
86
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.ReduceMin", { version: 1 });
87
+ // Explicit destinations request optional results or supply metadata that cannot be inferred.
88
+ const { y } = await kernel({ x: { data: xData, shape: [] } }, {
89
+ outputs: { y: { shape: [], dtype: "float32" } },
90
+ });
91
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ReduceMin",
3
+ "cases": [
4
+ {
5
+ "name": "reduce-min-axis0-1024x512",
6
+ "preset": "smoke",
7
+ "vars": { "dtype": "float32", "rows": 1024, "cols": 512 },
8
+ "attrs": { "axes": [0], "keepdims": 0 },
9
+ "inputs": { "x": { "shape": [1024, 512], "dtype": "float32", "dist": "normal", "seed": 953, "scale": 1 } },
10
+ "outputs": { "y": { "shape": [512], "dtype": "float32" } },
11
+ "bench": {
12
+ "primary": true,
13
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }]
14
+ }
15
+ },
16
+ {
17
+ "name": "rows_1024_cols_768",
18
+ "attrs": { "axes": [1], "keepdims": 0 },
19
+ "inputs": { "x": { "dtype": "float32", "shape": [1024, 768] } },
20
+ "outputs": { "y": { "dtype": "float32", "shape": [1024] } }
21
+ },
22
+ {
23
+ "name": "reducemin-lastaxis-f32-4096x4096",
24
+ "preset": "smoke",
25
+ "vars": { "rows": 4096, "cols": 4096 },
26
+ "attrs": { "axes": [1], "keepdims": 0 },
27
+ "inputs": { "x": { "shape": [4096, 4096], "dtype": "float32", "dist": "normal", "seed": 117, "scale": 0.2 } },
28
+ "outputs": { "y": { "shape": [4096], "dtype": "float32" } },
29
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
30
+ },
31
+ {
32
+ "name": "reducemin-r3-last-64x64x4096",
33
+ "preset": "smoke",
34
+ "vars": { "rows": 4096, "cols": 4096 },
35
+ "attrs": { "axes": [2], "keepdims": 0 },
36
+ "inputs": { "x": { "shape": [64, 64, 4096], "dtype": "float32", "dist": "normal", "seed": 72, "scale": 0.2 } },
37
+ "outputs": { "y": { "shape": [64, 64], "dtype": "float32" } },
38
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
39
+ },
40
+ {
41
+ "name": "reducemin-lastaxis-f32-4096x4095-scalar-subgroup",
42
+ "vars": { "dtype": "float32", "rows": 4096, "cols": 4095 },
43
+ "attrs": { "axes": [1], "keepdims": 0 },
44
+ "inputs": { "x": { "shape": [4096, 4095], "dtype": "float32", "dist": "normal", "seed": 414, "scale": 0.2 } },
45
+ "outputs": { "y": { "shape": [4096], "dtype": "float32" } },
46
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
47
+ },
48
+ {
49
+ "name": "reducemin-axis0-splitk-f32-8192x512",
50
+ "vars": { "dtype": "float32", "rows": 8192, "cols": 512 },
51
+ "attrs": { "axes": [0], "keepdims": 0 },
52
+ "inputs": { "x": { "shape": [8192, 512], "dtype": "float32", "dist": "normal", "seed": 818, "scale": 0.2 } },
53
+ "outputs": { "y": { "shape": [512], "dtype": "float32" } },
54
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
55
+ },
56
+ {
57
+ "name": "reducemin-axis1-f32-16776961x1-degenerate-row",
58
+ "vars": { "dtype": "float32", "rows": 16776961, "cols": 1 },
59
+ "attrs": { "axes": [1], "keepdims": 0 },
60
+ "inputs": { "x": { "shape": [16776961, 1], "dtype": "float32", "dist": "normal", "seed": 167, "scale": 0.2 } },
61
+ "outputs": { "y": { "shape": [16776961], "dtype": "float32" } },
62
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
63
+ },
64
+ {
65
+ "name": "reducemin-all-axes-flat-miss-single-thread-2M",
66
+ "preset": "stress",
67
+ "vars": { "rows": 2146689, "cols": 1 },
68
+ "attrs": { "keepdims": 0 },
69
+ "inputs": { "x": { "shape": [129, 129, 129], "dtype": "float32", "dist": "normal", "seed": 722, "scale": 1 } },
70
+ "outputs": { "y": { "shape": [], "dtype": "float32", "dist": "empty" } },
71
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "129 * 129 * 129 * 4" }] }
72
+ },
73
+ {
74
+ "name": "reducemin-rank3-spatial-axes12-f32-128x256x256-pathology",
75
+ "preset": "stress",
76
+ "provenance": {
77
+ "source": "authored for branch coverage",
78
+ "notes": "Rank-3 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane."
79
+ },
80
+ "attrs": { "axes": [1, 2], "keepdims": 1 },
81
+ "inputs": { "x": { "shape": [128, 256, 256], "dtype": "float32", "dist": "normal", "seed": 824, "scale": 0.2 } },
82
+ "outputs": { "y": { "shape": [128, 1, 1], "dtype": "float32", "dist": "empty" } },
83
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "128 * 256 * 256 * 4" }] }
84
+ },
85
+ {
86
+ "name": "reducemin-rank4-spatial-axes23-f32-2x64x256x256-pathology",
87
+ "preset": "stress",
88
+ "provenance": {
89
+ "source": "authored for branch coverage",
90
+ "notes": "Rank-4 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane."
91
+ },
92
+ "attrs": { "axes": [2, 3], "keepdims": 1 },
93
+ "inputs": { "x": { "shape": [2, 64, 256, 256], "dtype": "float32", "dist": "normal", "seed": 825, "scale": 0.2 } },
94
+ "outputs": { "y": { "shape": [2, 64, 1, 1], "dtype": "float32", "dist": "empty" } },
95
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "2 * 64 * 256 * 256 * 4" }] }
96
+ },
97
+ {
98
+ "name": "reducemin-axis0-cols3-ultra-low-parallelism",
99
+ "preset": "stress",
100
+ "vars": { "rows": 65536, "cols": 3 },
101
+ "attrs": { "axes": [0], "keepdims": 0 },
102
+ "inputs": { "x": { "shape": [65536, 3], "dtype": "float32", "dist": "normal", "seed": 823, "scale": 1 } },
103
+ "outputs": { "y": { "shape": [3], "dtype": "float32", "dist": "empty" } },
104
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "65536 * 3 * 4" }] }
105
+ }
106
+ ],
107
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] }
108
+ }
build/webgpu/datamove-flat-copy-x4-tail.wgsl.jinja ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // One-dispatch flat copy that coarsens four scalar elements per invocation and
2
+ // handles a final one-to-three-element tail in the same kernel. Scalar bindings
3
+ // keep an arbitrary element count legal; the four explicit adjacent accesses
4
+ // let the backend combine full groups into wide transactions.
5
+ {{ env.wgsl.resourceDeclarations }}
6
+
7
+ const COUNT: u32 = {{ source.count }}u;
8
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
9
+
10
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
11
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
12
+ @builtin(num_workgroups) nwg: vec3<u32>) {
13
+ let q = gid.x + gid.y * nwg.x * WG;
14
+ let i = q * 4u;
15
+ if (i >= COUNT) {
16
+ return;
17
+ }
18
+
19
+ if (i + 3u < COUNT) {
20
+ let v = vec4<{{ scalar }}>(src[i], src[i + 1u], src[i + 2u], src[i + 3u]);
21
+ dst[i] = v.x;
22
+ dst[i + 1u] = v.y;
23
+ dst[i + 2u] = v.z;
24
+ dst[i + 3u] = v.w;
25
+ return;
26
+ }
27
+
28
+ for (var j = i; j < COUNT; j = j + 1u) {
29
+ dst[j] = src[j];
30
+ }
31
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,1074 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "ReduceMin",
4
+ "sinceVersion": 20,
5
+ "description": "Computes the minimum of input tensor elements along the specified axes. The output rank matches the input when `keepdims` is 1; otherwise reduced dimensions are pruned. Reduction over an empty set yields positive infinity when the dtype supports it, or the dtype's maximum value otherwise. For Boolean inputs, `false` is less than `true`.",
6
+ "inputs": [{ "role": "data", "dtype": "T", "description": "The input tensor to reduce." }],
7
+ "outputs": [
8
+ {
9
+ "role": "reduced",
10
+ "dtype": "T",
11
+ "rank": "ranks.data if attrs.keepdims == 1 or ((attrs.axes | length) == 0 and attrs.noop_with_empty_axes == 1) else (ranks.data - (attrs.axes | length) if (attrs.axes | length) > 0 else 0)",
12
+ "description": "The reduced output tensor containing minimum values."
13
+ }
14
+ ],
15
+ "attributes": { "keepdims": 1, "noop_with_empty_axes": 0, "axes": [] },
16
+ "attributeDescriptions": {
17
+ "keepdims": "If 1, retains the reduced dimensions with size 1 in the output; if 0, those dimensions are pruned.",
18
+ "noop_with_empty_axes": "When axes is empty: if 0 (default) reduces over all axes; if 1 the op acts as an identity (no-op reduction).",
19
+ "axes": "Values of the optional ONNX `axes` tensor input, supplied through this request attribute; an empty list follows `noop_with_empty_axes`."
20
+ },
21
+ "attributeConstraints": { "keepdims": { "values": [0, 1] }, "noop_with_empty_axes": { "values": [0, 1] } },
22
+ "typeConstraints": { "T": ["float32", "float16", "int32", "uint32", "int8", "uint8", "bool"] },
23
+ "args": {
24
+ "x": { "kind": "tensor", "semantic": "data", "role": "input" },
25
+ "y": { "kind": "tensor", "semantic": "reduced", "role": "output" }
26
+ },
27
+ "derive": {
28
+ "emptyNarrowInputOk": "numel(shapes.data) > 0 or (tensorDtypes.data != \"int8\" and tensorDtypes.data != \"uint8\")",
29
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
30
+ "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
31
+ "treeWorkgroupOk": "reduceWorkgroupSize > 0 and pow2ceil(reduceWorkgroupSize) == reduceWorkgroupSize and reduceWorkgroupSize * dtypeBytes(\"float32\") <= device.limits.maxComputeWorkgroupStorageSize",
32
+ "subgroupWorkgroupFloor": "min(reduceWorkgroupSize, max(1, device.adapterInfo.subgroupMaxSize))",
33
+ "lastAxisRows": "rows(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
34
+ "lastAxisCols": "dim(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
35
+ "rowSerialPreferred": "lastAxisRows >= tunables.ROW_SERIAL_MIN_ROWS and lastAxisCols <= tunables.ROW_SERIAL_MAX_COLS",
36
+ "axis0Rows": "dim(shapes.data, 0) if ranks.data >= 2 else 0",
37
+ "axis0Cols": "dim(shapes.data, 1) if ranks.data >= 2 else 0",
38
+ "axis0SplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axis0Rows, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
39
+ "axis0SplitScratchBytes": "axis0SplitCount * axis0Cols * dtypeBytes(\"float32\")",
40
+ "axis0SplitPathFits": "axis0SplitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(axis0Cols, reduceWorkgroupSize), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension and axis0SplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axis0SplitScratchBytes <= device.limits.maxBufferSize",
41
+ "reduceAxis": "(attrs.axes[0] + ranks.data if attrs.axes[0] < 0 else attrs.axes[0]) if ((attrs.axes | length) == 1 and isUniqueIntList(attrs.axes, 0 - ranks.data, ranks.data, 1)) else ranks.data",
42
+ "axisSplitDim": "dim(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 0",
43
+ "axisSplitInner": "inner(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 1",
44
+ "axisSplitOutputs": "numel(shapes.reduced)",
45
+ "axisSplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axisSplitDim, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
46
+ "axisSplitScratchBytes": "axisSplitCount * axisSplitOutputs * 4",
47
+ "axisSplitPathFits": "axisSplitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(axisSplitOutputs, reduceWorkgroupSize), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension and axisSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axisSplitScratchBytes <= device.limits.maxBufferSize",
48
+ "axis0TilePathFits": "treeWorkgroupOk and tunables.AXIS0_TILE_COLS > 0 and tunables.AXIS0_TILE_COLS <= reduceWorkgroupSize and reduceWorkgroupSize % tunables.AXIS0_TILE_COLS == 0",
49
+ "flatItems": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)",
50
+ "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
51
+ "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
52
+ "flatPathFits": "treeWorkgroupOk and flatSplitCount <= device.limits.maxComputeWorkgroupsPerDimension and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
53
+ "flatParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.reduced) == 1 and numel(shapes.data) >= tunables.FULL_REDUCE_MIN_ELEMENTS and flatPathFits",
54
+ "contiguousSuffixParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.reduced) > 0 and numel(shapes.data) % numel(shapes.reduced) == 0 and numel(shapes.data) / numel(shapes.reduced) >= tunables.CONTIGUOUS_SUFFIX_MIN_COLS and ((ranks.data == 3 and hasAxis(attrs.axes, 0, 3) == false and hasAxis(attrs.axes, 1, 3) and hasAxis(attrs.axes, 2, 3) and numel(shapes.reduced) == dim(shapes.data, 0)) or (ranks.data == 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.reduced) == dim(shapes.data, 0) * dim(shapes.data, 1)) or (ranks.data == 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.reduced) == dim(shapes.data, 0)))"
55
+ },
56
+ "tunables": {
57
+ "WORKGROUP_SIZE": 256,
58
+ "VECTOR_WIDTH": 4,
59
+ "ROW_PARALLEL_MIN_COLS": 64,
60
+ "SUBGROUP_MIN_COLS": 256,
61
+ "SUBGROUP_SMALL_ROW_LIMIT": 32768,
62
+ "AXIS0_SPLIT_MIN_ROWS": 8192,
63
+ "AXIS0_SPLIT_TARGET_ROWS": 256,
64
+ "AXIS0_MAX_SPLITS": 128,
65
+ "AXIS0_TILE_MIN_ROWS": 64,
66
+ "AXIS0_TILE_MIN_COLS": 16,
67
+ "AXIS0_TILE_COLS": 16,
68
+ "AXIS_SPLIT_TILE_COLS": 8,
69
+ "FULL_REDUCE_MIN_ELEMENTS": 8192,
70
+ "FULL_REDUCE_MAX_SPLITS": 256,
71
+ "CONTIGUOUS_SUFFIX_MIN_COLS": 256,
72
+ "ROW_SERIAL_MIN_ROWS": 8192,
73
+ "ROW_SERIAL_MAX_COLS": 1024
74
+ },
75
+ "bindingSets": {
76
+ "narrowEmpty": [
77
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$scalar" },
78
+ {
79
+ "name": "params",
80
+ "semantic": "kernel.params",
81
+ "buffer": { "type": "uniform" },
82
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.reduced)" }] }
83
+ }
84
+ ],
85
+ "elementwise": [
86
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
87
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
88
+ {
89
+ "name": "params",
90
+ "semantic": "kernel.params",
91
+ "buffer": { "type": "uniform" },
92
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.reduced)" }] }
93
+ }
94
+ ],
95
+ "lastAxisVec4": [
96
+ {
97
+ "name": "x",
98
+ "arg": "x",
99
+ "semantic": "data",
100
+ "buffer": { "type": "read-only-storage" },
101
+ "elementType": "$vectorScalar"
102
+ },
103
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
104
+ {
105
+ "name": "params",
106
+ "semantic": "kernel.params",
107
+ "buffer": { "type": "uniform" },
108
+ "struct": {
109
+ "name": "Params",
110
+ "fields": [
111
+ { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
112
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1) / tunables.VECTOR_WIDTH" }
113
+ ]
114
+ }
115
+ }
116
+ ],
117
+ "lastAxisScalar": [
118
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
119
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
120
+ {
121
+ "name": "params",
122
+ "semantic": "kernel.params",
123
+ "buffer": { "type": "uniform" },
124
+ "struct": {
125
+ "name": "Params",
126
+ "fields": [
127
+ { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
128
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
129
+ ]
130
+ }
131
+ }
132
+ ],
133
+ "lastAxisScalarSubgroup": [
134
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
135
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
136
+ {
137
+ "name": "params",
138
+ "semantic": "kernel.params",
139
+ "buffer": { "type": "uniform" },
140
+ "struct": {
141
+ "name": "Params",
142
+ "fields": [
143
+ { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
144
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
145
+ ]
146
+ }
147
+ }
148
+ ],
149
+ "scalar": [
150
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
151
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
152
+ {
153
+ "name": "params",
154
+ "semantic": "kernel.params",
155
+ "buffer": { "type": "uniform" },
156
+ "struct": {
157
+ "name": "Params",
158
+ "fields": [
159
+ { "name": "rows", "type": "u32", "value": "1" },
160
+ { "name": "cols", "type": "u32", "value": "1" },
161
+ { "name": "outCount", "type": "u32", "value": "1" }
162
+ ]
163
+ }
164
+ }
165
+ ],
166
+ "rank1Axis0": [
167
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
168
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
169
+ {
170
+ "name": "params",
171
+ "semantic": "kernel.params",
172
+ "buffer": { "type": "uniform" },
173
+ "struct": {
174
+ "name": "Params",
175
+ "fields": [
176
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
177
+ { "name": "cols", "type": "u32", "value": "1" },
178
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
179
+ ]
180
+ }
181
+ }
182
+ ],
183
+ "rank2Serial": [
184
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
185
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
186
+ {
187
+ "name": "params",
188
+ "semantic": "kernel.params",
189
+ "buffer": { "type": "uniform" },
190
+ "struct": {
191
+ "name": "Params",
192
+ "fields": [
193
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
194
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
195
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
196
+ ]
197
+ }
198
+ }
199
+ ],
200
+ "rank2SerialAxis1": [
201
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
202
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
203
+ {
204
+ "name": "params",
205
+ "semantic": "kernel.params",
206
+ "buffer": { "type": "uniform" },
207
+ "struct": {
208
+ "name": "Params",
209
+ "fields": [
210
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
211
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
212
+ ]
213
+ }
214
+ }
215
+ ],
216
+ "axis0Parallel": [
217
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
218
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
219
+ {
220
+ "name": "params",
221
+ "semantic": "kernel.params",
222
+ "buffer": { "type": "uniform" },
223
+ "struct": {
224
+ "name": "Params",
225
+ "fields": [
226
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
227
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
228
+ ]
229
+ }
230
+ }
231
+ ],
232
+ "fullReduceSerial": [
233
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
234
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
235
+ {
236
+ "name": "params",
237
+ "semantic": "kernel.params",
238
+ "buffer": { "type": "uniform" },
239
+ "struct": {
240
+ "name": "Params",
241
+ "fields": [
242
+ { "name": "rows", "type": "u32", "value": "numel(shapes.data)" },
243
+ { "name": "cols", "type": "u32", "value": "1" },
244
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
245
+ ]
246
+ }
247
+ }
248
+ ],
249
+ "axisSplitReduce": [
250
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
251
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
252
+ {
253
+ "name": "params",
254
+ "semantic": "kernel.params",
255
+ "buffer": { "type": "uniform" },
256
+ "struct": {
257
+ "name": "Params",
258
+ "fields": [
259
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
260
+ { "name": "inner", "type": "u32", "value": "axisSplitInner" },
261
+ { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
262
+ ]
263
+ }
264
+ }
265
+ ],
266
+ "axisSplitCombine": [
267
+ {
268
+ "name": "partials",
269
+ "semantic": "partials",
270
+ "buffer": { "type": "read-only-storage" },
271
+ "elementType": "$partialElement"
272
+ },
273
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
274
+ {
275
+ "name": "params",
276
+ "semantic": "kernel.params",
277
+ "buffer": { "type": "uniform" },
278
+ "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }] }
279
+ }
280
+ ],
281
+ "axis0SplitReduce": [
282
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
283
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
284
+ {
285
+ "name": "params",
286
+ "semantic": "kernel.params",
287
+ "buffer": { "type": "uniform" },
288
+ "struct": {
289
+ "name": "Params",
290
+ "fields": [
291
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
292
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
293
+ ]
294
+ }
295
+ }
296
+ ],
297
+ "axis0SplitCombine": [
298
+ {
299
+ "name": "partials",
300
+ "semantic": "partials",
301
+ "buffer": { "type": "read-only-storage" },
302
+ "elementType": "$partialElement"
303
+ },
304
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
305
+ {
306
+ "name": "params",
307
+ "semantic": "kernel.params",
308
+ "buffer": { "type": "uniform" },
309
+ "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }] }
310
+ }
311
+ ],
312
+ "rankNAxis": [
313
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
314
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
315
+ {
316
+ "name": "params",
317
+ "semantic": "kernel.params",
318
+ "buffer": { "type": "uniform" },
319
+ "struct": {
320
+ "name": "Params",
321
+ "fields": [
322
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
323
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
324
+ ]
325
+ }
326
+ }
327
+ ],
328
+ "copyX4Tail": [
329
+ {
330
+ "name": "src",
331
+ "arg": "x",
332
+ "semantic": "data",
333
+ "buffer": { "type": "read-only-storage" },
334
+ "elementType": "$scalar"
335
+ },
336
+ { "name": "dst", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$scalar" }
337
+ ],
338
+ "flatPartialF32": [
339
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
340
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "f32" },
341
+ {
342
+ "name": "params",
343
+ "semantic": "kernel.params",
344
+ "buffer": { "type": "uniform" },
345
+ "struct": {
346
+ "name": "Params",
347
+ "fields": [
348
+ { "name": "count4", "type": "u32", "value": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)" },
349
+ { "name": "numel", "type": "u32", "value": "numel(shapes.data)" }
350
+ ]
351
+ }
352
+ }
353
+ ],
354
+ "flatCombineF32": [
355
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
356
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
357
+ {
358
+ "name": "params",
359
+ "semantic": "kernel.params",
360
+ "buffer": { "type": "uniform" },
361
+ "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "1" }] }
362
+ }
363
+ ],
364
+ "suffixVec4": [
365
+ {
366
+ "name": "x",
367
+ "arg": "x",
368
+ "semantic": "data",
369
+ "buffer": { "type": "read-only-storage" },
370
+ "elementType": "$vectorScalar"
371
+ },
372
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
373
+ {
374
+ "name": "params",
375
+ "semantic": "kernel.params",
376
+ "buffer": { "type": "uniform" },
377
+ "struct": {
378
+ "name": "Params",
379
+ "fields": [
380
+ { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
381
+ {
382
+ "name": "chunkCount",
383
+ "type": "u32",
384
+ "value": "numel(shapes.data) / numel(shapes.reduced) / tunables.VECTOR_WIDTH"
385
+ }
386
+ ]
387
+ }
388
+ }
389
+ ],
390
+ "suffixScalar": [
391
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
392
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
393
+ {
394
+ "name": "params",
395
+ "semantic": "kernel.params",
396
+ "buffer": { "type": "uniform" },
397
+ "struct": {
398
+ "name": "Params",
399
+ "fields": [
400
+ { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
401
+ { "name": "cols", "type": "u32", "value": "numel(shapes.data) / numel(shapes.reduced)" }
402
+ ]
403
+ }
404
+ }
405
+ ],
406
+ "multiAxis": [
407
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
408
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
409
+ {
410
+ "name": "params",
411
+ "semantic": "kernel.params",
412
+ "buffer": { "type": "uniform" },
413
+ "struct": {
414
+ "name": "Params",
415
+ "fields": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }]
416
+ }
417
+ }
418
+ ]
419
+ },
420
+ "variants": [
421
+ {
422
+ "id": "narrow_empty_identity",
423
+ "priority": 100,
424
+ "when": ["numel(shapes.data) == 0", "numel(shapes.reduced) > 0", "tensorDtypes.data == \"int8\" or tensorDtypes.data == \"uint8\" or tensorDtypes.data == \"bool\""],
425
+ "constants": { "scalar": "dtypes.T" },
426
+ "passes": [
427
+ {
428
+ "id": "main",
429
+ "name": "ReduceMin.NarrowEmptyIdentity",
430
+ "source": {
431
+ "shader": "reduce-narrow-empty-identity.wgsl.jinja",
432
+ "inputs": {
433
+ "identity": "1 if tensorDtypes.data == \"bool\" else 127 if tensorDtypes.data == \"int8\" else 255"
434
+ }
435
+ },
436
+ "bindings": "narrowEmpty",
437
+ "dispatch": { "gridStride": "numel(shapes.reduced)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
438
+ }
439
+ ]
440
+ },
441
+ {
442
+ "id": "contiguous_suffix_subgroup_vec4",
443
+ "priority": 30,
444
+ "requires": { "features": ["subgroups"] },
445
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0"],
446
+ "constants": {
447
+ "scalar": "dtypes.T",
448
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
449
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH))))"
450
+ },
451
+ "passes": [
452
+ {
453
+ "id": "main",
454
+ "name": "ReduceMin.ContiguousSuffixSubgroupVec4",
455
+ "source": {
456
+ "shader": "reduce-row-subgroup.wgsl.jinja",
457
+ "inputs": {
458
+ "op": "\"min\"",
459
+ "vec4": true,
460
+ "castF32": "dtypes.T == \"f16\"",
461
+ "usesF16": "dtypes.T == \"f16\""
462
+ }
463
+ },
464
+ "subgroupCollectivesWidth": "portable",
465
+ "bindings": "suffixVec4",
466
+ "dispatch": { "workgroups": "numel(shapes.reduced)" }
467
+ }
468
+ ]
469
+ },
470
+ {
471
+ "id": "contiguous_suffix_tree_vec4",
472
+ "priority": 22,
473
+ "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
474
+ "constants": {
475
+ "scalar": "dtypes.T",
476
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
477
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH)))"
478
+ },
479
+ "passes": [
480
+ {
481
+ "id": "main",
482
+ "name": "ReduceMin.ContiguousSuffixTreeVec4",
483
+ "source": {
484
+ "shader": "reduce-row-tree.wgsl.jinja",
485
+ "inputs": {
486
+ "op": "\"min\"",
487
+ "vec4": true,
488
+ "castF32": "dtypes.T == \"f16\"",
489
+ "usesF16": "dtypes.T == \"f16\""
490
+ }
491
+ },
492
+ "bindings": "suffixVec4",
493
+ "dispatch": { "workgroups": "numel(shapes.reduced)" }
494
+ }
495
+ ]
496
+ },
497
+ {
498
+ "id": "contiguous_suffix_tree",
499
+ "priority": 21,
500
+ "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
501
+ "constants": {
502
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.data) / numel(shapes.reduced)))",
503
+ "scalar": "dtypes.T"
504
+ },
505
+ "passes": [
506
+ {
507
+ "id": "main",
508
+ "name": "ReduceMin.ContiguousSuffixTree",
509
+ "source": {
510
+ "shader": "reduce-row-tree.wgsl.jinja",
511
+ "inputs": { "op": "\"min\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
512
+ },
513
+ "bindings": "suffixScalar",
514
+ "dispatch": { "workgroups": "numel(shapes.reduced)" }
515
+ }
516
+ ]
517
+ },
518
+ {
519
+ "id": "degenerate_copy_x4_tail",
520
+ "priority": 46,
521
+ "when": ["(dtypes.T == \"f32\" or dtypes.T == \"i32\" or dtypes.T == \"u32\")", "numel(shapes.data) >= 4", "numel(shapes.data) == numel(shapes.reduced)"],
522
+ "constants": { "scalar": "dtypes.T", "usesF16": false },
523
+ "passes": [
524
+ {
525
+ "id": "main",
526
+ "name": "ReduceMin.DegenerateCopyX4Tail",
527
+ "source": {
528
+ "shader": "datamove-flat-copy-x4-tail.wgsl.jinja",
529
+ "inputs": { "count": "numel(shapes.reduced)" }
530
+ },
531
+ "bindings": "copyX4Tail",
532
+ "dispatch": { "threads": "ceilDiv(numel(shapes.reduced), 4)", "workgroupSize": "reduceWorkgroupSize" },
533
+ "viewAlias": [{ "input": "src", "output": "dst" }]
534
+ }
535
+ ]
536
+ },
537
+ {
538
+ "id": "noop_empty_axes",
539
+ "priority": 40,
540
+ "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.data, shapes.reduced)"],
541
+ "passes": [
542
+ {
543
+ "id": "main",
544
+ "name": "ReduceMin.NoopEmptyAxes",
545
+ "source": { "shader": "reduce-noop-empty-axes.wgsl.jinja" },
546
+ "bindings": "elementwise",
547
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
548
+ }
549
+ ]
550
+ },
551
+ {
552
+ "id": "multi_axis_rank3",
553
+ "priority": 8,
554
+ "constants": { "scalar": "dtypes.T" },
555
+ "when": ["not flatParallelCovered", "not contiguousSuffixParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data == 3", "(attrs.keepdims == 1 and ranks.reduced == 3 and (dim(shapes.reduced, 0) == 1 if hasAxis(attrs.axes, 0, 3) else dim(shapes.reduced, 0) == dim(shapes.data, 0)) and (dim(shapes.reduced, 1) == 1 if hasAxis(attrs.axes, 1, 3) else dim(shapes.reduced, 1) == dim(shapes.data, 1)) and (dim(shapes.reduced, 2) == 1 if hasAxis(attrs.axes, 2, 3) else dim(shapes.reduced, 2) == dim(shapes.data, 2))) or (attrs.keepdims == 0 and ranks.reduced == 1)"],
556
+ "passes": [
557
+ {
558
+ "id": "main",
559
+ "name": "ReduceMin.MultiAxisRank3",
560
+ "source": {
561
+ "shader": "reduce-serial-axis.wgsl.jinja",
562
+ "inputs": {
563
+ "op": "\"min\"",
564
+ "indexing": "\"multiaxis\"",
565
+ "castF32": "dtypes.T == \"f16\"",
566
+ "usesF16": "dtypes.T == \"f16\"",
567
+ "rank": 3,
568
+ "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
569
+ "dataShape": "shapes.data",
570
+ "outputShape": "shapes.reduced",
571
+ "outputRank": "ranks.reduced",
572
+ "keepDims": "attrs.keepdims != 0",
573
+ "logicalBool": "tensorDtypes.data == \"bool\""
574
+ }
575
+ },
576
+ "bindings": "multiAxis",
577
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
578
+ }
579
+ ]
580
+ },
581
+ {
582
+ "id": "multi_axis_rank4",
583
+ "priority": 8,
584
+ "when": ["not flatParallelCovered", "not contiguousSuffixParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data == 4", "attrs.noop_with_empty_axes == 0", "numel(shapes.reduced) == (1 if hasAxis(attrs.axes, 0, 4) else dim(shapes.data, 0)) * (1 if hasAxis(attrs.axes, 1, 4) else dim(shapes.data, 1)) * (1 if hasAxis(attrs.axes, 2, 4) else dim(shapes.data, 2)) * (1 if hasAxis(attrs.axes, 3, 4) else dim(shapes.data, 3))", "((attrs.keepdims == 1 and ranks.reduced == 4) or (attrs.keepdims == 0 and ranks.reduced < 4))"],
585
+ "passes": [
586
+ {
587
+ "id": "main",
588
+ "name": "ReduceMin.MultiAxisRank4",
589
+ "source": {
590
+ "shader": "reduce-serial-axis.wgsl.jinja",
591
+ "inputs": {
592
+ "op": "\"min\"",
593
+ "indexing": "\"multiaxis\"",
594
+ "castF32": "dtypes.T == \"f16\"",
595
+ "usesF16": "dtypes.T == \"f16\"",
596
+ "rank": 4,
597
+ "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
598
+ "dataShape": "shapes.data",
599
+ "outputShape": "shapes.reduced",
600
+ "outputRank": "ranks.reduced",
601
+ "keepDims": "attrs.keepdims != 0",
602
+ "logicalBool": "tensorDtypes.data == \"bool\""
603
+ }
604
+ },
605
+ "bindings": "multiAxis",
606
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
607
+ }
608
+ ],
609
+ "constants": { "scalar": "dtypes.T" }
610
+ },
611
+ {
612
+ "id": "tree_last_axis",
613
+ "priority": 20,
614
+ "demoteWhen": ["rowSerialPreferred"],
615
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"i32\" or dtypes.T == \"u32\")", "ranks.data >= 2", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "treeWorkgroupOk"],
616
+ "constants": {
617
+ "scalar": "dtypes.T",
618
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.data, ranks.data - 1)))"
619
+ },
620
+ "passes": [
621
+ {
622
+ "id": "main",
623
+ "name": "ReduceMin.TreeLastAxis",
624
+ "source": { "shader": "reduce-row-tree.wgsl.jinja", "inputs": { "op": "\"min\"" } },
625
+ "bindings": "lastAxisScalar",
626
+ "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
627
+ }
628
+ ]
629
+ },
630
+ {
631
+ "id": "tree_last_axis_vec4",
632
+ "priority": 23,
633
+ "demoteWhen": ["rowSerialPreferred"],
634
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "attrs.noop_with_empty_axes == 0", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "lastAxisCols % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
635
+ "constants": {
636
+ "scalar": "dtypes.T",
637
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
638
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
639
+ },
640
+ "passes": [
641
+ {
642
+ "id": "main",
643
+ "name": "ReduceMin.TreeRowVec4",
644
+ "source": {
645
+ "shader": "reduce-row-tree.wgsl.jinja",
646
+ "inputs": {
647
+ "op": "\"min\"",
648
+ "vec4": true,
649
+ "castF32": "dtypes.T == \"f16\"",
650
+ "usesF16": "dtypes.T == \"f16\""
651
+ }
652
+ },
653
+ "bindings": "lastAxisVec4",
654
+ "dispatch": { "workgroups": "lastAxisRows" }
655
+ }
656
+ ]
657
+ },
658
+ {
659
+ "id": "rank0_scalar",
660
+ "priority": 40,
661
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
662
+ "when": ["emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data == 0", "ranks.reduced == 0"],
663
+ "passes": [
664
+ {
665
+ "id": "main",
666
+ "name": "ReduceMin.Rank0Scalar",
667
+ "source": {
668
+ "shader": "reduce-serial-axis.wgsl.jinja",
669
+ "inputs": {
670
+ "op": "\"min\"",
671
+ "indexing": "\"axis2d\"",
672
+ "castF32": "dtypes.T == \"f16\"",
673
+ "usesF16": "dtypes.T == \"f16\"",
674
+ "logicalBool": "tensorDtypes.data == \"bool\""
675
+ }
676
+ },
677
+ "bindings": "scalar",
678
+ "dispatch": { "x": 1 }
679
+ }
680
+ ]
681
+ },
682
+ {
683
+ "id": "rank1_axis0",
684
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
685
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data == 1", "reduceAxis == 0", "((attrs.keepdims == 0 and ranks.reduced == 0) or (attrs.keepdims == 1 and ranks.reduced == 1 and dim(shapes.reduced, 0) == 1))"],
686
+ "passes": [
687
+ {
688
+ "id": "main",
689
+ "name": "ReduceMin.Rank1Axis0",
690
+ "source": {
691
+ "shader": "reduce-serial-axis.wgsl.jinja",
692
+ "inputs": {
693
+ "op": "\"min\"",
694
+ "indexing": "\"axis2d\"",
695
+ "castF32": "dtypes.T == \"f16\"",
696
+ "usesF16": "dtypes.T == \"f16\"",
697
+ "logicalBool": "tensorDtypes.data == \"bool\""
698
+ }
699
+ },
700
+ "bindings": "rank1Axis0",
701
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
702
+ }
703
+ ]
704
+ },
705
+ {
706
+ "id": "axis_split",
707
+ "priority": 24,
708
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "attrs.noop_with_empty_axes == 0", "ranks.data >= 2", "reduceAxis < ranks.data - 1", "not (ranks.data == 2 and reduceAxis == 0)", "axisSplitDim >= tunables.AXIS0_SPLIT_MIN_ROWS", "axisSplitOutputs >= 1", "axisSplitOutputs <= 4096", "axisSplitOutputs == rows(shapes.data, reduceAxis)", "axisSplitPathFits"],
709
+ "derive": { "splitCount": "axisSplitCount" },
710
+ "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
711
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
712
+ "passes": [
713
+ {
714
+ "id": "split_reduce",
715
+ "name": "ReduceMin.AxisSplitReduce",
716
+ "source": {
717
+ "shader": "reduce-axis-split-reduce.wgsl.jinja",
718
+ "inputs": {
719
+ "op": "\"min\"",
720
+ "workgroupSize": "reduceWorkgroupSize",
721
+ "split": "splitCount",
722
+ "castF32": "dtypes.T == \"f16\"",
723
+ "usesF16": "dtypes.T == \"f16\""
724
+ }
725
+ },
726
+ "bindings": "axisSplitReduce",
727
+ "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
728
+ },
729
+ {
730
+ "id": "combine",
731
+ "name": "ReduceMin.AxisSplitCombine",
732
+ "source": {
733
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
734
+ "inputs": {
735
+ "op": "\"min\"",
736
+ "workgroupSize": "reduceWorkgroupSize",
737
+ "split": "splitCount",
738
+ "outputF16": "dtypes.T == \"f16\""
739
+ }
740
+ },
741
+ "bindings": "axisSplitCombine",
742
+ "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
743
+ }
744
+ ]
745
+ },
746
+ {
747
+ "id": "axis_split_tiled_narrow",
748
+ "priority": 25,
749
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "attrs.noop_with_empty_axes == 0", "ranks.data >= 2", "reduceAxis < ranks.data - 1", "axisSplitDim >= tunables.AXIS0_SPLIT_MIN_ROWS", "axisSplitOutputs >= 1", "axisSplitOutputs <= 2 * tunables.AXIS_SPLIT_TILE_COLS", "reduceWorkgroupSize % tunables.AXIS_SPLIT_TILE_COLS == 0", "axisSplitOutputs == rows(shapes.data, reduceAxis)", "axisSplitPathFits"],
750
+ "derive": { "splitCount": "axisSplitCount" },
751
+ "constants": {
752
+ "partialElement": "\"f32\"",
753
+ "scalar": "dtypes.T",
754
+ "workgroupSize": "reduceWorkgroupSize",
755
+ "split": "splitCount",
756
+ "tileCols": "tunables.AXIS_SPLIT_TILE_COLS"
757
+ },
758
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
759
+ "passes": [
760
+ {
761
+ "id": "split_reduce",
762
+ "name": "ReduceMin.AxisSplitTiledReduce",
763
+ "source": {
764
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
765
+ "inputs": {
766
+ "op": "\"min\"",
767
+ "workgroupSize": "reduceWorkgroupSize",
768
+ "split": "splitCount",
769
+ "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
770
+ "castF32": "dtypes.T == \"f16\"",
771
+ "usesF16": "dtypes.T == \"f16\""
772
+ }
773
+ },
774
+ "bindings": "axisSplitReduce",
775
+ "dispatch": { "workgroups": "ceilDiv((axisSplitOutputs), (constants.tileCols))", "y": "splitCount" }
776
+ },
777
+ {
778
+ "id": "combine",
779
+ "name": "ReduceMin.AxisSplitCombine",
780
+ "source": {
781
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
782
+ "inputs": {
783
+ "op": "\"min\"",
784
+ "workgroupSize": "reduceWorkgroupSize",
785
+ "split": "splitCount",
786
+ "outputF16": "dtypes.T == \"f16\""
787
+ }
788
+ },
789
+ "bindings": "axisSplitCombine",
790
+ "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
791
+ }
792
+ ]
793
+ },
794
+ {
795
+ "id": "axis0_splitk",
796
+ "priority": 22,
797
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_SPLIT_MIN_ROWS", "dim(shapes.data, 1) > 0", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 1)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == dim(shapes.data, 1)))", "axis0SplitPathFits"],
798
+ "derive": { "splitCount": "axis0SplitCount" },
799
+ "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
800
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * dim(shapes.data, 1)]" }],
801
+ "passes": [
802
+ {
803
+ "id": "split_reduce",
804
+ "name": "ReduceMin.Axis0SplitKReduce",
805
+ "source": {
806
+ "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
807
+ "inputs": {
808
+ "op": "\"min\"",
809
+ "workgroupSize": "reduceWorkgroupSize",
810
+ "split": "splitCount",
811
+ "castF32": "dtypes.T == \"f16\"",
812
+ "usesF16": "dtypes.T == \"f16\""
813
+ }
814
+ },
815
+ "bindings": "axis0SplitReduce",
816
+ "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
817
+ },
818
+ {
819
+ "id": "combine",
820
+ "name": "ReduceMin.Axis0SplitKCombine",
821
+ "source": {
822
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
823
+ "inputs": {
824
+ "op": "\"min\"",
825
+ "workgroupSize": "reduceWorkgroupSize",
826
+ "split": "splitCount",
827
+ "outputF16": "dtypes.T == \"f16\""
828
+ }
829
+ },
830
+ "bindings": "axis0SplitCombine",
831
+ "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize" }
832
+ }
833
+ ]
834
+ },
835
+ {
836
+ "id": "axis0_tilecols",
837
+ "priority": 20,
838
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_TILE_MIN_ROWS", "axis0Cols >= tunables.AXIS0_TILE_MIN_COLS", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 1)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == dim(shapes.data, 1)))", "axis0TilePathFits"],
839
+ "constants": {
840
+ "scalar": "dtypes.T",
841
+ "workgroupSize": "reduceWorkgroupSize",
842
+ "tileCols": "tunables.AXIS0_TILE_COLS"
843
+ },
844
+ "passes": [
845
+ {
846
+ "id": "main",
847
+ "name": "ReduceMin.Axis0TileCols",
848
+ "source": {
849
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
850
+ "inputs": { "op": "\"min\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
851
+ },
852
+ "bindings": "axis0Parallel",
853
+ "dispatch": { "workgroups": "ceilDiv((dim(shapes.data, 1)), (constants.tileCols))" }
854
+ }
855
+ ]
856
+ },
857
+ {
858
+ "id": "all_axes_flat",
859
+ "priority": 31,
860
+ "constants": { "scalar": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
861
+ "when": ["flatParallelCovered"],
862
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
863
+ "passes": [
864
+ {
865
+ "id": "flat_partial",
866
+ "name": "ReduceMin.AllAxesFlatPartial",
867
+ "source": {
868
+ "shader": "reduce-flat-partial.wgsl.jinja",
869
+ "inputs": { "op": "\"min\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
870
+ },
871
+ "bindings": "flatPartialF32",
872
+ "dispatch": { "x": "flatSplitCount" }
873
+ },
874
+ {
875
+ "id": "combine",
876
+ "name": "ReduceMin.AllAxesFlatCombine",
877
+ "source": {
878
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
879
+ "inputs": { "op": "\"min\"", "outputF16": "dtypes.T == \"f16\"" }
880
+ },
881
+ "bindings": "flatCombineF32",
882
+ "dispatch": { "x": 1 }
883
+ }
884
+ ]
885
+ },
886
+ {
887
+ "id": "rankn_single_axis_generic",
888
+ "priority": 12,
889
+ "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4", "tree_last_axis"],
890
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.noop_with_empty_axes == 0", "reduceAxis < ranks.data", "numel(shapes.reduced) == rows(shapes.data, reduceAxis)", "((attrs.keepdims == 0 and ranks.reduced == ranks.data - 1) or (attrs.keepdims == 1 and ranks.reduced == ranks.data and dim(shapes.reduced, reduceAxis) == 1))"],
891
+ "passes": [
892
+ {
893
+ "id": "main",
894
+ "name": "ReduceMin.RankNSingleAxisGeneric",
895
+ "source": {
896
+ "shader": "reduce-serial-axis.wgsl.jinja",
897
+ "inputs": {
898
+ "op": "\"min\"",
899
+ "indexing": "\"rankn\"",
900
+ "castF32": "dtypes.T == \"f16\"",
901
+ "usesF16": "dtypes.T == \"f16\"",
902
+ "rank": "ranks.data",
903
+ "axis": "reduceAxis",
904
+ "dataShape": "shapes.data",
905
+ "outputShape": "shapes.reduced",
906
+ "outputRank": "ranks.reduced",
907
+ "keepDims": "attrs.keepdims != 0",
908
+ "logicalBool": "tensorDtypes.data == \"bool\""
909
+ }
910
+ },
911
+ "bindings": "rankNAxis",
912
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
913
+ }
914
+ ],
915
+ "constants": { "scalar": "dtypes.T" }
916
+ },
917
+ {
918
+ "id": "subgroup_last_axis_vec4",
919
+ "priority": 25,
920
+ "requires": { "features": ["subgroups"] },
921
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "dim(shapes.data, ranks.data - 1) >= 4", "dim(shapes.data, ranks.data - 1) % tunables.VECTOR_WIDTH == 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "not rowSerialPreferred"],
922
+ "constants": {
923
+ "scalar": "dtypes.T",
924
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
925
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
926
+ },
927
+ "passes": [
928
+ {
929
+ "id": "main",
930
+ "name": "ReduceMin.SubgroupRowVec4",
931
+ "source": {
932
+ "shader": "reduce-row-subgroup.wgsl.jinja",
933
+ "inputs": {
934
+ "op": "\"min\"",
935
+ "vec4": true,
936
+ "castF32": "dtypes.T == \"f16\"",
937
+ "usesF16": "dtypes.T == \"f16\""
938
+ }
939
+ },
940
+ "subgroupCollectivesWidth": "portable",
941
+ "bindings": "lastAxisVec4",
942
+ "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
943
+ }
944
+ ]
945
+ },
946
+ {
947
+ "id": "subgroup_last_axis",
948
+ "priority": 24,
949
+ "requires": { "features": ["subgroups"] },
950
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "dim(shapes.data, ranks.data - 1) > 0", "dim(shapes.data, ranks.data - 1) % tunables.VECTOR_WIDTH != 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "not rowSerialPreferred"],
951
+ "constants": {
952
+ "scalar": "dtypes.T",
953
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
954
+ },
955
+ "passes": [
956
+ {
957
+ "id": "main",
958
+ "name": "ReduceMin.SubgroupRow",
959
+ "source": {
960
+ "shader": "reduce-row-subgroup.wgsl.jinja",
961
+ "inputs": {
962
+ "op": "\"min\"",
963
+ "vec4": false,
964
+ "castF32": "dtypes.T == \"f16\"",
965
+ "usesF16": "dtypes.T == \"f16\""
966
+ }
967
+ },
968
+ "subgroupCollectivesWidth": "portable",
969
+ "bindings": "lastAxisScalarSubgroup",
970
+ "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
971
+ }
972
+ ]
973
+ },
974
+ {
975
+ "id": "axis0",
976
+ "priority": 0,
977
+ "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
978
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
979
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 0", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 1)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == dim(shapes.data, 1)))"],
980
+ "passes": [
981
+ {
982
+ "id": "main",
983
+ "name": "axis0",
984
+ "source": {
985
+ "shader": "reduce-serial-axis.wgsl.jinja",
986
+ "inputs": {
987
+ "op": "\"min\"",
988
+ "indexing": "\"axis2d\"",
989
+ "castF32": "dtypes.T == \"f16\"",
990
+ "usesF16": "dtypes.T == \"f16\"",
991
+ "logicalBool": "tensorDtypes.data == \"bool\""
992
+ }
993
+ },
994
+ "bindings": "rank2Serial",
995
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
996
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
997
+ }
998
+ ]
999
+ },
1000
+ {
1001
+ "id": "axis1",
1002
+ "priority": 0,
1003
+ "constants": { "axis": 1, "scalar": "dtypes.T" },
1004
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 1", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 0)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == dim(shapes.data, 0) and dim(shapes.reduced, 1) == 1))"],
1005
+ "passes": [
1006
+ {
1007
+ "id": "main",
1008
+ "name": "axis1",
1009
+ "source": {
1010
+ "shader": "reduce-serial-axis.wgsl.jinja",
1011
+ "inputs": {
1012
+ "op": "\"min\"",
1013
+ "indexing": "\"axis2d\"",
1014
+ "castF32": "dtypes.T == \"f16\"",
1015
+ "usesF16": "dtypes.T == \"f16\"",
1016
+ "logicalBool": "tensorDtypes.data == \"bool\""
1017
+ }
1018
+ },
1019
+ "bindings": "rank2SerialAxis1",
1020
+ "constants": { "axis": 1, "scalar": "dtypes.T" },
1021
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
1022
+ }
1023
+ ]
1024
+ },
1025
+ {
1026
+ "id": "all_axes_keepdims",
1027
+ "priority": 30,
1028
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
1029
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 1 and ranks.reduced == ranks.data and numel(shapes.reduced) == 1"],
1030
+ "passes": [
1031
+ {
1032
+ "id": "main",
1033
+ "name": "ReduceMin.Rank3AllAxesKeepdims",
1034
+ "source": {
1035
+ "shader": "reduce-serial-axis.wgsl.jinja",
1036
+ "inputs": {
1037
+ "op": "\"min\"",
1038
+ "indexing": "\"axis2d\"",
1039
+ "castF32": "dtypes.T == \"f16\"",
1040
+ "usesF16": "dtypes.T == \"f16\"",
1041
+ "logicalBool": "tensorDtypes.data == \"bool\""
1042
+ }
1043
+ },
1044
+ "bindings": "fullReduceSerial",
1045
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
1046
+ }
1047
+ ]
1048
+ },
1049
+ {
1050
+ "id": "all_axes_no_keepdims",
1051
+ "priority": 30,
1052
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
1053
+ "when": ["not flatParallelCovered", "emptyNarrowInputOk", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 0 and ranks.reduced == 0"],
1054
+ "passes": [
1055
+ {
1056
+ "id": "main",
1057
+ "name": "ReduceMin.Rank3AllAxesNoKeepdims",
1058
+ "source": {
1059
+ "shader": "reduce-serial-axis.wgsl.jinja",
1060
+ "inputs": {
1061
+ "op": "\"min\"",
1062
+ "indexing": "\"axis2d\"",
1063
+ "castF32": "dtypes.T == \"f16\"",
1064
+ "usesF16": "dtypes.T == \"f16\"",
1065
+ "logicalBool": "tensorDtypes.data == \"bool\""
1066
+ }
1067
+ },
1068
+ "bindings": "fullReduceSerial",
1069
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
1070
+ }
1071
+ ]
1072
+ }
1073
+ ]
1074
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.ReduceMin",
3
+ "id": "_ai_onnx_reducemin_webgpu_a5e3e3d",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "6KR8yCFLrmY6PdH2GnqpEOjK72FRANRAv+s5kcNoDnk=",
11
+ "datamove-flat-copy-x4-tail.wgsl.jinja": "zqQLkq4mysszKvFcAIzayrcBx0qTuJf2zavso5HN/+A=",
12
+ "manifest.json": "VUjwoC99OWimXu2iEgqL+ufHyaEZ60INXqPW3dw3Q6Q=",
13
+ "reduce-axis-split-reduce.wgsl.jinja": "rjy37ckoB9/9a88KxgKKjpZYjmIjy8vmI34yLvPzxuo=",
14
+ "reduce-axis0-splitk-combine.wgsl.jinja": "YOriZzB3yMakRsErMyOJhirJBBtgSenl+Gd4rBsHZsM=",
15
+ "reduce-axis0-splitk-reduce.wgsl.jinja": "QbHcxtUfEvqMJQRstIC89pBdlD7gDzTFZSdYH1Wldn8=",
16
+ "reduce-axis0-tilecols.wgsl.jinja": "ldhjG9yaosrcAFdq6OCglYkvJEaQWi38KdCNqx4N4Yw=",
17
+ "reduce-flat-partial.wgsl.jinja": "Esf7shfEzRgtyIkjVRYHlqU9Rc1lYCj5WqNLZgBxsSo=",
18
+ "reduce-narrow-empty-identity.wgsl.jinja": "S6UnLW+6dyCb9ciRezkgXE9mlYmqdfBCjPRO9iPm3Jk=",
19
+ "reduce-noop-empty-axes.wgsl.jinja": "IO2MEzyBGJr3Z8AVP7ELJXqfhwcG/vtBfa0wCdkbuwo=",
20
+ "reduce-row-subgroup.wgsl.jinja": "szYjtA6TUPJ/I4mbVX7WZUoXiseQ3JfulzqgGb6fdt4=",
21
+ "reduce-row-tree.wgsl.jinja": "srFhdHteraJrXxSPG56B56yK9qjUjRAzCEakQKBsuLY=",
22
+ "reduce-serial-axis.wgsl.jinja": "cLrTc0ite8YZmx/wn7PBwaxTp9dLWWUIUdQQ3tH8+4o=",
23
+ "test.json": "G8Sv1RfEOyXjQTYVWqiWogZbCuoav1pCHqswBcjcM6E="
24
+ }
25
+ },
26
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
27
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.ReduceMin" }
28
+ }
build/webgpu/reduce-axis-split-reduce.wgsl.jinja ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 1 of a split-K reduction for an (outer, axis, inner) flattening. Each
2
+ // (output, segment) invocation reduces one axis slice at stride inner and writes
3
+ // partials[segment * outputs + output]. Adjacent output threads read adjacent
4
+ // inner-axis elements. The combine pass folds the segments and finalizes the
5
+ // selected reduction.
6
+ //
7
+ // logsumexp writes three partial planes per output and segment: the segment
8
+ // maximum, the sum of exp(x - maximum), and a packed NaN marker.
9
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
10
+ {% set xa = "f32(" if castF32 else "" %}
11
+ {% set ax = ")" if castF32 else "" %}
12
+ {% if source.usesF16 is defined and source.usesF16 %}
13
+ enable f16;
14
+ {% endif %}
15
+ {{ env.wgsl.resourceDeclarations }}
16
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
17
+ * evaluation, so f32 identities are constructed at runtime from their
18
+ * IEEE-754 bit patterns. */
19
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
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
+ const SPLIT: u32 = {{ split }}u;
36
+ {{ wgsl_minmax_identity("reduction_identity", source.op) }}
37
+
38
+ @compute @workgroup_size(WG, 1, 1)
39
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
40
+ @builtin(workgroup_id) wg: vec3<u32>,
41
+ @builtin(num_workgroups) nwg: vec3<u32>) {
42
+ // 2D-folded output index: wg.z carries the high bits past the
43
+ // per-dimension dispatch limit on the x dimension.
44
+ let output_index = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
45
+ let seg = wg.y;
46
+ if (output_index >= params.outputs) { return; }
47
+
48
+ let outer_index = output_index / params.inner;
49
+ let inner_index = output_index % params.inner;
50
+ let input_base = outer_index * params.axisDim * params.inner + inner_index;
51
+
52
+ // Even split of [0, axisDim) across SPLIT segments (last may be shorter).
53
+ let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
54
+ let a0 = seg * chunk;
55
+ var a1 = a0 + chunk;
56
+ if (a1 > params.axisDim) { a1 = params.axisDim; }
57
+
58
+ var acc = reduction_identity();
59
+ for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
60
+ acc = {{ source.op }}(acc, {{ xa }}x[input_base + axis_index * params.inner]{{ ax }});
61
+ }
62
+ partials[seg * params.outputs + output_index] = acc;
63
+ }
build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 2 of the split-K column-wise reduction. One invocation per output column
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
+ {% if source.outputF16 %}
6
+ enable f16;
7
+ {% endif %}
8
+ {{ env.wgsl.resourceDeclarations }}
9
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
10
+ * evaluation, so f32 identities are constructed at runtime from their
11
+ * IEEE-754 bit patterns. */
12
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
13
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
14
+ fn {{ name }}() -> {{ scalar }} {
15
+ {% if scalar == "i32" %}
16
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
17
+ {% elif scalar == "u32" %}
18
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
19
+ {% else %}
20
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
21
+ return bitcast<f32>(bits);
22
+ {% endif %}
23
+ }
24
+ {%- endmacro %}
25
+
26
+
27
+ const WG: u32 = {{ workgroupSize }}u;
28
+ const SPLIT: u32 = {{ split }}u;
29
+ {{ wgsl_minmax_identity("reduction_identity", source.op) }}
30
+
31
+ @compute @workgroup_size(WG, 1, 1)
32
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
33
+ @builtin(num_workgroups) nwg: vec3<u32>) {
34
+ let stride = nwg.x * WG;
35
+ let start = (gid.y * nwg.x * WG) + gid.x;
36
+ for (var col = start; col < params.cols; col = col + stride) {
37
+ var total = reduction_identity();
38
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
39
+ let p = partials[seg * params.cols + col];
40
+ total = {{ source.op }}(total, p);
41
+ }
42
+ {% if source.outputF16 %}
43
+ y[col] = f16(total);
44
+ {% else %}
45
+ y[col] = total;
46
+ {% endif %}
47
+ }
48
+ }
build/webgpu/reduce-axis0-splitk-reduce.wgsl.jinja ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 1 of a split-K column-wise reduction. Splitting rows across workgroup
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
+ //
6
+ // logsumexp writes the segment maximum and sum of exp(x - maximum) as separate
7
+ // partial planes; the combine pass merges them stably and handles NaN and +Inf.
8
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
9
+ {% set xa = "f32(" if castF32 else "" %}
10
+ {% set ax = ")" if castF32 else "" %}
11
+ {% if source.usesF16 is defined and source.usesF16 %}
12
+ enable f16;
13
+ {% endif %}
14
+ {{ env.wgsl.resourceDeclarations }}
15
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
16
+ * evaluation, so f32 identities are constructed at runtime from their
17
+ * IEEE-754 bit patterns. */
18
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
19
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
20
+ fn {{ name }}() -> {{ scalar }} {
21
+ {% if scalar == "i32" %}
22
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
23
+ {% elif scalar == "u32" %}
24
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
25
+ {% else %}
26
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
27
+ return bitcast<f32>(bits);
28
+ {% endif %}
29
+ }
30
+ {%- endmacro %}
31
+
32
+
33
+ const WG: u32 = {{ workgroupSize }}u;
34
+ const SPLIT: u32 = {{ split }}u;
35
+ {{ wgsl_minmax_identity("reduction_identity", source.op) }}
36
+
37
+ @compute @workgroup_size(WG, 1, 1)
38
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
39
+ @builtin(workgroup_id) wg: vec3<u32>,
40
+ @builtin(num_workgroups) nwg: vec3<u32>) {
41
+ // 2D-folded column index: wg.z carries the high bits past the maxComputeWorkgroupsPerDimension
42
+ // workgroup-per-dimension dispatch limit on the x dimension.
43
+ let col = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
44
+ let seg = wg.y;
45
+ if (col >= params.cols) { return; }
46
+
47
+ // Even split of [0, rows) across SPLIT segments (last segment may be shorter).
48
+ let chunk = (params.rows + SPLIT - 1u) / SPLIT;
49
+ let r0 = seg * chunk;
50
+ var r1 = r0 + chunk;
51
+ if (r1 > params.rows) { r1 = params.rows; }
52
+
53
+ var acc = reduction_identity();
54
+ for (var row = r0; row < r1; row = row + 1u) {
55
+ acc = {{ source.op }}(acc, {{ xa }}x[row * params.cols + col]{{ ax }});
56
+ }
57
+ partials[seg * params.cols + col] = acc;
58
+ }
build/webgpu/reduce-axis0-tilecols.wgsl.jinja ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Tiled column-wise reduction. Each workgroup owns TILE_COLS columns;
2
+ // ROW_LANES threads stride the rows of one column, then lane 0 folds their
3
+ // partials. In split mode, workgroup y selects an axis segment and finalization
4
+ // is deferred to the combine pass. Cooperative row lanes preserve occupancy
5
+ // when the flattened output has only a few elements.
6
+ {% set splitMode = source.split is defined %}
7
+ {% if splitMode %}
8
+ {% set rowBegin = "row_begin + row_lane" %}
9
+ {% set rowEnd = "row_end" %}
10
+ {% set elem = "x[input_base + row * params.inner]" %}
11
+ {% else %}
12
+ {% set rowBegin = "row_lane" %}
13
+ {% set rowEnd = "params.rows" %}
14
+ {% set elem = "x[inputBase + row * params.cols + col]" %}
15
+ {% endif %}
16
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
17
+ {% set intMode = source.intMode is defined and source.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 source.usesF16 is defined and source.usesF16 %}
25
+ enable f16;
26
+ {% endif %}
27
+ {{ env.wgsl.resourceDeclarations }}
28
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
29
+ * evaluation, so f32 identities are constructed at runtime from their
30
+ * IEEE-754 bit patterns. */
31
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
32
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
33
+ fn {{ name }}() -> {{ scalar }} {
34
+ {% if scalar == "i32" %}
35
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
36
+ {% elif scalar == "u32" %}
37
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
38
+ {% else %}
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
+ const TILE_COLS: u32 = {{ tileCols }}u;
48
+ const ROW_LANES: u32 = WG / TILE_COLS;
49
+ {% if splitMode %}
50
+ const SPLIT: u32 = {{ source.split }}u;
51
+ {% endif %}
52
+ {{ wgsl_minmax_identity("lane_identity", source.op, scalar) }}
53
+
54
+ var<workgroup> partial: array<{{ scalar if (source.op == "max" or source.op == "min" or intMode) else "f32" }}, WG>;
55
+ @compute @workgroup_size(WG, 1, 1)
56
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>{% if not splitMode %},
57
+ @builtin(num_workgroups) nwg: vec3<u32>{% endif %}) {
58
+ let tid = lid.x;
59
+ let col_lane = tid % TILE_COLS;
60
+ let row_lane = tid / TILE_COLS;
61
+ {% if splitMode %}
62
+ // Narrow outputs: wg.x covers every column tile, wg.y is the axis segment.
63
+ let col = wg.x * TILE_COLS + col_lane;
64
+ let outputIndex = col;
65
+ let in_bounds = col < params.outputs;
66
+ let seg = wg.y;
67
+ let outer_index = col / params.inner;
68
+ let inner_index = col % params.inner;
69
+ let input_base = outer_index * params.axisDim * params.inner + inner_index;
70
+ // Even split of [0, axisDim) across SPLIT segments (last may be shorter).
71
+ let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
72
+ let row_begin = seg * chunk;
73
+ let row_end = min(row_begin + chunk, params.axisDim);
74
+ {% else %}
75
+ // 2D-folded tile index: wg.y carries the high bits past the dispatch limit.
76
+ // The batched form reuses this same coalesced axis-0 reduction for a middle
77
+ // axis by assigning consecutive tiles to each outer slice.
78
+ let tile = wg.x + wg.y * nwg.x;
79
+ let col = tile * TILE_COLS + col_lane;
80
+ let inputBase = 0u;
81
+ let outputIndex = col;
82
+ let in_bounds = col < params.cols;
83
+ {% endif %}
84
+
85
+ var acc = lane_identity();
86
+ if (in_bounds) {
87
+ for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
88
+ acc = {{ source.op }}(acc, {{ elem }});
89
+ }
90
+ }
91
+ partial[tid] = acc;
92
+ workgroupBarrier();
93
+
94
+ if (row_lane == 0u && in_bounds) {
95
+ var total = partial[col_lane];
96
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
97
+ total = {{ source.op }}(total, partial[lane * TILE_COLS + col_lane]);
98
+ }
99
+ {% if splitMode %}
100
+ partials[seg * params.outputs + outputIndex] = total;
101
+ {% else %}
102
+ y[outputIndex] = {{ yv }}total{{ vy }};
103
+ {% endif %}
104
+ }
105
+ }
build/webgpu/reduce-flat-partial.wgsl.jinja ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 1 of a flat reduction to one scalar. Instead of assigning the whole
2
+ // tensor to one invocation, SPLIT workgroups traverse the flat input, accumulate
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 castF32 = source.castF32 is defined and source.castF32 %}
10
+ {% set xa = "f32(" if castF32 else "" %}
11
+ {% set ax = ")" if castF32 else "" %}
12
+ {% if source.usesF16 is defined and source.usesF16 %}
13
+ enable f16;
14
+ {% endif %}
15
+ {{ env.wgsl.resourceDeclarations }}
16
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
17
+ * evaluation, so f32 identities are constructed at runtime from their
18
+ * IEEE-754 bit patterns. */
19
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
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 source.intMode else "f32" }}, WG>;
36
+ {{ wgsl_minmax_identity("reduction_identity", source.op) }}
37
+
38
+ @compute @workgroup_size(WG)
39
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
40
+ @builtin(local_invocation_id) lid: vec3<u32>,
41
+ @builtin(workgroup_id) wg: vec3<u32>,
42
+ @builtin(num_workgroups) nwg: vec3<u32>) {
43
+ let tid = lid.x;
44
+ let gstride = nwg.x * WG;
45
+ var acc = reduction_identity();
46
+ // Grid-stride over the flat vec4 groups (params.count4 = numel / 4, floored).
47
+ for (var i = gid.x; i < params.count4; i = i + gstride) {
48
+ let b = 4u * i;
49
+ let v = vec4<{{ "i32" if source.intMode else "f32" }}>({{ xa }}x[b]{{ ax }}, {{ xa }}x[b + 1u]{{ ax }}, {{ xa }}x[b + 2u]{{ ax }}, {{ xa }}x[b + 3u]{{ ax }});
50
+ acc = min(acc, min(min(v.x, v.y), min(v.z, v.w)));
51
+ }
52
+ // Scalar tail (the 0..3 elements past the last full vec4). One global thread
53
+ // folds it so it is counted exactly once; the count is tiny so serializing it
54
+ // is negligible. numel % 4 == 0 makes this loop empty.
55
+ if (gid.x == 0u) {
56
+ for (var i = 4u * params.count4; i < params.numel; i = i + 1u) {
57
+ let s = {{ xa }}x[i]{{ ax }};
58
+ acc = min(acc, s);
59
+ }
60
+ }
61
+ red[tid] = acc;
62
+ workgroupBarrier();
63
+ var stride: u32 = WG / 2u;
64
+ loop {
65
+ if (stride == 0u) { break; }
66
+ if (tid < stride) {
67
+ red[tid] = min(red[tid], red[tid + stride]);
68
+ }
69
+ stride = stride / 2u;
70
+ workgroupBarrier();
71
+ }
72
+ if (tid == 0u) {
73
+ partials[wg.x] = red[0];
74
+ }
75
+ }
build/webgpu/reduce-narrow-empty-identity.wgsl.jinja ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+
3
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
4
+
5
+ // Empty min/max reductions use the logical narrow dtype's bound, while their
6
+ // WebGPU storage is widened to i32/u32.
7
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
8
+ fn main(
9
+ @builtin(global_invocation_id) gid: vec3<u32>,
10
+ @builtin(num_workgroups) nwg: vec3<u32>
11
+ ) {
12
+ let stride = nwg.x * WG;
13
+ for (var i = gid.x; i < params.count; i += stride) {
14
+ y[i] = {{ scalar }}({{ source.identity }});
15
+ }
16
+ }
build/webgpu/reduce-noop-empty-axes.wgsl.jinja ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+
3
+ @compute @workgroup_size({{ reduceWorkgroupSize }})
4
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
5
+ // 2D-folded flat index: gid.y carries the high bits past the
6
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
7
+ let i = gid.x + gid.y * nwg.x * {{ reduceWorkgroupSize }}u;
8
+ if (i >= params.count) {
9
+ return;
10
+ }
11
+ let v = x[i];
12
+ y[i] = v;
13
+ }
build/webgpu/reduce-row-subgroup.wgsl.jinja ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Subgroup row reduction for a contiguous last axis. One workgroup owns each
2
+ // output row. Threads reduce strided chunks, subgroup leaders deposit their
3
+ // partials in workgroup memory, and thread 0 folds those slots and finalizes.
4
+ //
5
+ // Accumulators are f32 except for integer max/min. Mean, l2, and logsum apply
6
+ // division, square root, or logarithm only at finalization. logsumexp uses
7
+ // max-subtraction, bit-exact NaN detection, NaN propagation, and a +Inf
8
+ // short-circuit. Runtime bitcasts provide max/min infinity identities because
9
+ // WGSL constant evaluation rejects infinite constants. f16 storage is widened
10
+ // before accumulation and narrowed only for the final store.
11
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
12
+ {% set scalar = "f32" if castF32 else scalar %}
13
+ {% set xv = ("vec4<f32>(" if source.vec4 else "f32(") if castF32 else "" %}
14
+ {% set vx = ")" if castF32 else "" %}
15
+ {% set yv = "f16(" if castF32 else "" %}
16
+ {% set vy = ")" if castF32 else "" %}
17
+ enable subgroups;
18
+ {% if source.usesF16 is defined and source.usesF16 %}
19
+ enable f16;
20
+ {% endif %}
21
+ {{ env.wgsl.resourceDeclarations }}
22
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
23
+ * evaluation, so f32 identities are constructed at runtime from their
24
+ * IEEE-754 bit patterns. */
25
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
26
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
27
+ fn {{ name }}() -> {{ scalar }} {
28
+ {% if scalar == "i32" %}
29
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
30
+ {% elif scalar == "u32" %}
31
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
32
+ {% else %}
33
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
34
+ return bitcast<f32>(bits);
35
+ {% endif %}
36
+ }
37
+ {%- endmacro %}
38
+
39
+
40
+ const WG: u32 = {{ workgroupSize }}u;{{ wgsl_minmax_identity("reduction_identity", source.op, scalar) }}
41
+ var<workgroup> wgPartial: array<{{ scalar }}, WG>;
42
+
43
+ {% macro emit_reduce(name, collective, combine) %}
44
+ fn {{ name }}(value: {{ scalar }}, sgLid: u32, sgId: u32, numSg: u32) -> {{ scalar }} {
45
+ let sgVal = {{ collective }}(value);
46
+ if (numSg == 1u) {
47
+ return sgVal;
48
+ }
49
+ if (sgLid == 0u) {
50
+ wgPartial[sgId] = sgVal;
51
+ }
52
+ workgroupBarrier();
53
+ var total = wgPartial[0];
54
+ for (var i = 1u; i < numSg; i = i + 1u) {
55
+ {{ combine }}
56
+ }
57
+ workgroupBarrier();
58
+ return total;
59
+ }
60
+ {%- endmacro %}{{ emit_reduce("reduce_row", "subgroupMin", "total = min(total, wgPartial[i]);") }}
61
+ @compute @workgroup_size(WG, 1, 1)
62
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
63
+ @builtin(num_workgroups) nwg: vec3<u32>,
64
+ @builtin(local_invocation_id) lid: vec3<u32>,
65
+ @builtin(subgroup_invocation_id) sgLid: u32,
66
+ @builtin(subgroup_id) sgId: u32,
67
+ @builtin(num_subgroups) numSg: u32) {
68
+ let row = wg.x + wg.y * nwg.x;
69
+ if (row >= params.rows) {
70
+ return;
71
+ }
72
+ let tid = lid.x;
73
+ let base = row * params.chunkCount; let INIT: {{ scalar }} = reduction_identity();{% if source.vec4 %}
74
+ var acc4 = vec4<{{ scalar }}>(INIT);
75
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
76
+ let v = {{ xv }}x[base + c]{{ vx }}; acc4 = min(acc4, v); } let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));{% else %}
77
+ var acc = INIT;
78
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
79
+ let v = {{ xv }}x[base + c]{{ vx }}; acc = min(acc, v); }
80
+ {%- endif %}
81
+ let total = reduce_row(acc, sgLid, sgId, numSg);
82
+ if (tid == 0u) { y[row] = {{ yv }}total{{ vy }}; }}
build/webgpu/reduce-row-tree.wgsl.jinja ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Portable one-workgroup-per-row reduction for the Reduce value family.
2
+ // Threads stride a contiguous row, accumulate locally, and fold their values
3
+ // through a shared-memory tree without relying on subgroups.
4
+ //
5
+ // Max, min, and product use the native f32/i32/u32 value type. Additive
6
+ // transforms accumulate float inputs in f32 but retain integer accumulation
7
+ // for sum, L1, and sum-of-squares. Mean, L2, log-sum, and log-sum-exp are
8
+ // f32-only because their finalizers divide, take a square root, or take a log.
9
+ // f16 storage widens through f32 for both accumulation and the shared tree,
10
+ // then narrows only at the final store.
11
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
12
+ {% set scalar = "f32" if castF32 else scalar %}
13
+ {% set xv = ("vec4<f32>(" if source.vec4 else "f32(") if castF32 else "" %}
14
+ {% set vx = ")" if castF32 else "" %}
15
+ {% set yv = "f16(" if castF32 else "" %}
16
+ {% set vy = ")" if castF32 else "" %}
17
+ {% if source.usesF16 is defined and source.usesF16 %}
18
+ enable f16;
19
+ {% endif %}
20
+ {{ env.wgsl.resourceDeclarations }}
21
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
22
+ * evaluation, so f32 identities are constructed at runtime from their
23
+ * IEEE-754 bit patterns. */
24
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
25
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
26
+ fn {{ name }}() -> {{ scalar }} {
27
+ {% if scalar == "i32" %}
28
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
29
+ {% elif scalar == "u32" %}
30
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
31
+ {% else %}
32
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
33
+ return bitcast<f32>(bits);
34
+ {% endif %}
35
+ }
36
+ {%- endmacro %}
37
+
38
+
39
+ const WG: u32 = {{ workgroupSize }}u;
40
+
41
+ {% set is_int = scalar == "i32" or scalar == "u32" %}
42
+ {% set intAdditive = is_int and (source.op == "sum" or source.op == "l1" or source.op == "sumsquare") %}
43
+ {% set accType = scalar if source.op == "max" or source.op == "min" or source.op == "prod" or intAdditive else "f32" %}
44
+ {{ wgsl_minmax_identity("identity", source.op, accType) }}
45
+
46
+ fn combine(a: {{ accType }}, b: {{ accType }}) -> {{ accType }} {
47
+ return min(a, b);
48
+ }
49
+
50
+ var<workgroup> partial: array<{{ accType }}, WG>;
51
+
52
+ @compute @workgroup_size(WG, 1, 1)
53
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
54
+ @builtin(num_workgroups) nwg: vec3<u32>,
55
+ @builtin(local_invocation_id) lid: vec3<u32>) {
56
+ let row = wg.x + wg.y * nwg.x;
57
+ if (row >= params.rows) {
58
+ return;
59
+ }
60
+ let tid = lid.x;
61
+ {% if source.vec4 %}
62
+ let base = row * params.chunkCount;
63
+ {% else %}
64
+ let base = row * params.cols;
65
+ {% endif %}
66
+
67
+
68
+ {% if source.vec4 %}
69
+ var acc4 = vec4<{{ accType }}>(identity());
70
+ for (var col = tid; col < params.chunkCount; col = col + WG) {
71
+ let value = {{ xv }}x[base + col]{{ vx }};
72
+ acc4 = min(acc4, value);
73
+ }
74
+ let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
75
+ {% else %}
76
+ var acc = identity();
77
+ for (var col = tid; col < params.cols; col = col + WG) {
78
+ let value = {{ xv }}x[base + col]{{ vx }};
79
+ acc = combine(acc, value);
80
+ }
81
+ {% endif %}
82
+ partial[tid] = acc;
83
+ workgroupBarrier();
84
+
85
+ for (var step = WG >> 1u; step > 0u; step = step >> 1u) {
86
+ if (tid < step) {
87
+ partial[tid] = combine(partial[tid], partial[tid + step]);
88
+ }
89
+ workgroupBarrier();
90
+ }
91
+
92
+ if (tid == 0u) {
93
+ let total = partial[0];
94
+ y[row] = {{ yv }}total{{ vy }};
95
+ }
96
+ }
build/webgpu/reduce-serial-axis.wgsl.jinja ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Serial one-thread-per-output reduction for the no-feature tier. f16 storage
2
+ // is widened before every accumulation and narrowed only for the final store.
3
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
4
+ {% set logicalBool = source.logicalBool is defined and source.logicalBool %}
5
+ {% set yv = "f16(" if castF32 else "" %}
6
+ {% set vy = ")" if castF32 else "" %}
7
+ {% if source.usesF16 is defined and source.usesF16 %}
8
+ enable f16;
9
+ {% endif %}
10
+ {{ env.wgsl.resourceDeclarations }}
11
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
12
+ * evaluation, so f32 identities are constructed at runtime from their
13
+ * IEEE-754 bit patterns. */
14
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
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
+ {{ wgsl_minmax_identity("empty_value", source.op, "f32" if castF32 else scalar) }}
30
+ {% if source.indexing == "rankn" %}
31
+
32
+ fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
33
+ var rem = out_index;
34
+ {% for axis in range(source.outputRank) %}
35
+ {% set out_stride = namespace(value=1) %}
36
+ {% for j in range(axis + 1, source.outputRank) %}
37
+ {% set out_stride.value = out_stride.value * source.outputShape[j] %}
38
+ {% endfor %}
39
+ {% set safe_out_stride = 1 if out_stride.value == 0 else out_stride.value %}
40
+ {% if not source.keepDims or axis != source.axis %}
41
+ let out_coord{{ axis }} = rem / {{ safe_out_stride }}u;
42
+ {% endif %}
43
+ rem = rem % {{ safe_out_stride }}u;
44
+ {% endfor %}
45
+ {% for axis in range(source.rank) %}
46
+ {% if axis == source.axis %}
47
+ let coord{{ axis }} = reduce_index;
48
+ {% elif source.keepDims %}
49
+ let coord{{ axis }} = out_coord{{ axis }};
50
+ {% elif axis < source.axis %}
51
+ let coord{{ axis }} = out_coord{{ axis }};
52
+ {% else %}
53
+ let coord{{ axis }} = out_coord{{ axis - 1 }};
54
+ {% endif %}
55
+ {% endfor %}
56
+ {% set src = namespace(value="coord0") %}
57
+ {% for axis in range(1, source.rank) %}
58
+ {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[axis] ~ "u + coord" ~ axis ~ ")" %}
59
+ {% endfor %}
60
+ return {{ src.value }};
61
+ }
62
+ {% endif %}
63
+ {% if source.indexing == "multiaxis" %}
64
+ {% set hasReducedAxis = namespace(value=false) %}
65
+ {% for a in range(source.rank) %}{% if source.reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
66
+
67
+ // One thread per output element walks the Cartesian product of the reduced axes,
68
+ // linearized as reduce_linear. Specialized shapes make every input offset a sum
69
+ // of coordinate-times-constant terms.
70
+ fn input_offset(out_index: u32{% if hasReducedAxis.value %}, reduce_linear: u32{% endif %}) -> u32 {
71
+ var rem = out_index;
72
+ {% for oaxis in range(source.outputRank) %}
73
+ {% set ostride = namespace(value=1) %}
74
+ {% for j in range(oaxis + 1, source.outputRank) %}
75
+ {% set ostride.value = ostride.value * source.outputShape[j] %}
76
+ {% endfor %}
77
+ {% set osafe = 1 if ostride.value == 0 else ostride.value %}
78
+ {% if not source.keepDims or not source.reduce[oaxis] %}
79
+ let out_coord{{ oaxis }} = rem / {{ osafe }}u;
80
+ {% endif %}
81
+ rem = rem % {{ osafe }}u;
82
+ {% endfor %}
83
+ {% if hasReducedAxis.value %}
84
+ var rrem = reduce_linear;
85
+ {% endif %}
86
+ {% for a in range(source.rank) if source.reduce[a] %}
87
+ {% set rstride = namespace(value=1) %}
88
+ {% for b in range(a + 1, source.rank) if source.reduce[b] %}
89
+ {% set rstride.value = rstride.value * source.dataShape[b] %}
90
+ {% endfor %}
91
+ {% set rsafe = 1 if rstride.value == 0 else rstride.value %}
92
+ let red_coord{{ a }} = rrem / {{ rsafe }}u;
93
+ rrem = rrem % {{ rsafe }}u;
94
+ {% endfor %}
95
+ {% set oc = namespace(i=0) %}
96
+ {% for a in range(source.rank) %}
97
+ {% if source.reduce[a] %}
98
+ let coord{{ a }} = red_coord{{ a }};
99
+ {% elif source.keepDims %}
100
+ let coord{{ a }} = out_coord{{ a }};
101
+ {% else %}
102
+ let coord{{ a }} = out_coord{{ oc.i }};
103
+ {% set oc.i = oc.i + 1 %}
104
+ {% endif %}
105
+ {% endfor %}
106
+ {% set src = namespace(value="coord0") %}
107
+ {% for a in range(1, source.rank) %}
108
+ {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[a] ~ "u + coord" ~ a ~ ")" %}
109
+ {% endfor %}
110
+ return {{ src.value }};
111
+ }
112
+ {% endif %}
113
+ {% if source.indexing == "multiaxis" %}
114
+ {% set mcount = namespace(value=1) %}
115
+ {% for a in range(source.rank) if source.reduce[a] %}
116
+ {% set mcount.value = mcount.value * source.dataShape[a] %}
117
+ {% endfor %}
118
+ {% set count = mcount.value ~ "u" %}
119
+ {% if hasReducedAxis.value %}
120
+ {% set at = "x[input_offset(i, r)]" %}
121
+ {% set at_first = "x[input_offset(i, 0u)]" %}
122
+ {% else %}
123
+ {% set at = "x[input_offset(i)]" %}
124
+ {% set at_first = "x[input_offset(i)]" %}
125
+ {% endif %}
126
+ {% elif source.indexing == "rankn" %}
127
+ {% set count = "params.axisDim" %}
128
+ {% set at = "x[input_offset(i, r)]" %}
129
+ {% set at_first = "x[input_offset(i, 0u)]" %}
130
+ {% elif axis == 0 %}
131
+ {% set count = "params.rows" %}
132
+ {% set at = "x[r * params.cols + i]" %}
133
+ {% set at_first = "x[i]" %}
134
+ {% else %}
135
+ {% set count = "params.cols" %}
136
+ {% set at = "x[i * params.cols + r]" %}
137
+ {% set at_first = "x[i * params.cols]" %}
138
+ {% endif %}
139
+ {% if castF32 %}
140
+ {% set at = "f32(" ~ at ~ ")" %}
141
+ {% set at_first = "f32(" ~ at_first ~ ")" %}
142
+ {% endif %}
143
+
144
+ @compute @workgroup_size({{ reduceWorkgroupSize }})
145
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
146
+ // 2D-folded flat index: gid.y carries the high bits past the
147
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
148
+ let i = gid.x + gid.y * nwg.x * {{ reduceWorkgroupSize }}u;
149
+ if (i >= params.outCount) {
150
+ return;
151
+ }
152
+ if ({{ count }} == 0u) {
153
+ y[i] = {{ yv }}empty_value(){{ vy }};
154
+ return;
155
+ }
156
+ var acc = {{ at_first }};
157
+ for (var r = 1u; r < {{ count }}; r = r + 1u) {
158
+ acc = {{ source.op }}(acc, {{ at }});
159
+ }
160
+ y[i] = {{ yv }}acc{{ vy }};
161
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ReduceMin",
3
+ "fixtureArrays": {
4
+ "rank3_axis2_last_keepdims_input_x": [4, -1, 2, -1, 8, 7, 6, 5, -3, -4, -4, 2, 0, 9, -8, 3, -2, -6, 1, 10, 5, -5, -7, 4]
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "all_axes_flat_positive_infinity_identity_8192",
9
+ "provenance": {
10
+ "notes": "The parallel full-reduction threshold must supersede the rank1 serial and row-reduction fallbacks, while preserving the true +Infinity identity."
11
+ },
12
+ "attrs": { "axes": [0], "keepdims": 0 },
13
+ "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "cycle", "values": ["Infinity"] } } },
14
+ "outputs": {
15
+ "y": {
16
+ "dtype": "float32",
17
+ "shape": [],
18
+ "data": { "kind": "cycle", "values": ["Infinity"] },
19
+ "tolerance": 0,
20
+ "relTolerance": 0
21
+ }
22
+ }
23
+ },
24
+ {
25
+ "name": "all_axes_flat_fullreduce_32x32x32_keepdims",
26
+ "attrs": { "keepdims": 1 },
27
+ "inputs": {
28
+ "x": {
29
+ "dtype": "float32",
30
+ "shape": [32, 32, 32],
31
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5 }
32
+ }
33
+ },
34
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1], "tolerance": 0 } }
35
+ },
36
+ {
37
+ "name": "f32_negative_subnormal_axis1_gpu_gap",
38
+ "skipGpu": {
39
+ "category": "permanent",
40
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; bit-exact subnormal preservation is unattainable on GPU."
41
+ },
42
+ "provenance": {
43
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
44
+ "test": "ReduceMin",
45
+ "notes": "A negative subnormal is the row minimum over zero; flushing it changes the selected value."
46
+ },
47
+ "attrs": { "axes": [1], "keepdims": 0 },
48
+ "inputs": {
49
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [1e-40, 0.0, -1e-40] } }
50
+ },
51
+ "outputs": { "y": { "dtype": "float32", "shape": [1], "tolerance": 0 } }
52
+ },
53
+ {
54
+ "name": "dispatch_cliff_axis1_16776961x1",
55
+ "attrs": { "axes": [1], "keepdims": 0 },
56
+ "inputs": {
57
+ "x": { "dtype": "float32", "shape": [16776961, 1], "data": { "kind": "linspace", "start": -5.0, "end": 5.0 } }
58
+ },
59
+ "outputs": { "y": { "dtype": "float32", "shape": [16776961], "tolerance": 0 } }
60
+ },
61
+ {
62
+ "name": "degenerate_axis1_vec4_tail_17x1",
63
+ "provenance": {
64
+ "notes": "Compact correctness coverage for the vectorized singleton-axis copy plus its one-element scalar tail."
65
+ },
66
+ "attrs": { "axes": [1], "keepdims": 0 },
67
+ "inputs": {
68
+ "x": { "dtype": "float32", "shape": [17, 1], "data": { "kind": "linspace", "start": -8.0, "end": 8.0 } }
69
+ },
70
+ "outputs": { "y": { "dtype": "float32", "shape": [17], "tolerance": 0 } }
71
+ },
72
+ {
73
+ "name": "degenerate_axis1_vec4_aligned_i32_16x1",
74
+ "provenance": { "notes": "Compact native-integer coverage for the aligned vectorized singleton-axis copy." },
75
+ "attrs": { "axes": [1], "keepdims": 0 },
76
+ "inputs": {
77
+ "x": { "dtype": "int32", "shape": [16, 1], "data": { "kind": "cycle", "values": [16777217, -16777219, 7, -3] } }
78
+ },
79
+ "outputs": { "y": { "dtype": "int32", "shape": [16], "tolerance": 0 } }
80
+ },
81
+ {
82
+ "name": "axis0",
83
+ "attrs": { "axes": [0], "keepdims": 0 },
84
+ "inputs": {
85
+ "x": {
86
+ "dtype": "float32",
87
+ "shape": [3, 4],
88
+ "data": { "kind": "values", "values": [1.0, 9.0, 3.0, 4.0, -1.0, 2.0, 7.0, 8.0, 0.0, 5.0, -3.0, 6.0] }
89
+ }
90
+ },
91
+ "outputs": { "y": { "dtype": "float32", "shape": [4] } }
92
+ },
93
+ {
94
+ "name": "axis0_tilecols_positive_infinity_identity_64x16",
95
+ "attrs": { "axes": [0], "keepdims": 0 },
96
+ "inputs": { "x": { "dtype": "float32", "shape": [64, 16], "data": { "kind": "cycle", "values": ["Infinity"] } } },
97
+ "outputs": {
98
+ "y": {
99
+ "dtype": "float32",
100
+ "shape": [16],
101
+ "data": { "kind": "cycle", "values": ["Infinity"] },
102
+ "tolerance": 0,
103
+ "relTolerance": 0
104
+ }
105
+ }
106
+ },
107
+ {
108
+ "name": "int32_axis0_tiled_64x32",
109
+ "attrs": { "axes": [0], "keepdims": 0 },
110
+ "inputs": {
111
+ "x": {
112
+ "dtype": "int32",
113
+ "shape": [64, 32],
114
+ "data": {
115
+ "kind": "cycle",
116
+ "values": [-16777216, -16777245, -16777274, -16777236, -16777265, -16777227, -16777256, -16777218, -16777247, -16777276, -16777238, -16777267, -16777229, -16777258, -16777220, -16777249, -16777278, -16777240, -16777269, -16777231, -16777260, -16777222, -16777251, -16777280, -16777242, -16777271, -16777233, -16777262, -16777224, -16777253, -16777282, -16777244, -16777273, -16777235, -16777264, -16777226, -16777255, -16777217, -16777246, -16777275, -16777237, -16777266, -16777228, -16777257, -16777219, -16777248, -16777277, -16777239, -16777268, -16777230, -16777259, -16777221, -16777250, -16777279, -16777241, -16777270, -16777232, -16777261, -16777223, -16777252, -16777281, -16777243, -16777272, -16777234, -16777263, -16777225, -16777254]
117
+ }
118
+ }
119
+ },
120
+ "outputs": { "y": { "dtype": "int32", "shape": [32], "tolerance": 0 } }
121
+ },
122
+ {
123
+ "name": "axis1",
124
+ "attrs": { "axes": [1], "keepdims": 0 },
125
+ "inputs": {
126
+ "x": {
127
+ "dtype": "float32",
128
+ "shape": [3, 4],
129
+ "data": { "kind": "values", "values": [1.0, 9.0, 3.0, 4.0, -1.0, 2.0, 7.0, 8.0, 0.0, 5.0, -3.0, 6.0] }
130
+ }
131
+ },
132
+ "outputs": { "y": { "dtype": "float32", "shape": [3] } }
133
+ },
134
+ {
135
+ "name": "ort_axis1_nan_seed_comparison_gpu_gap",
136
+ "skipGpu": {
137
+ "category": "todo",
138
+ "reason": "The parallel min/max reduction routes do not yet preserve the reference's first-element NaN incumbent semantics; explicit NaN tracking is implementable in WGSL."
139
+ },
140
+ "provenance": {
141
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
142
+ "test": "ReductionOpTest.ReduceMin",
143
+ "notes": "NaN extension: ORT seeds ReduceMin from the first reduced element, so a leading NaN remains the result while later NaNs are ignored after a finite incumbent."
144
+ },
145
+ "attrs": { "axes": [1], "keepdims": 0 },
146
+ "inputs": {
147
+ "x": {
148
+ "dtype": "float32",
149
+ "shape": [3, 4],
150
+ "data": { "kind": "values", "values": ["NaN", 1.0, -2.0, 0.0, 1.0, "NaN", -2.0, 0.0, 1.0, -2.0, "NaN", 0.0] }
151
+ }
152
+ },
153
+ "outputs": {
154
+ "y": {
155
+ "dtype": "float32",
156
+ "shape": [3],
157
+ "data": { "kind": "values", "values": ["NaN", -2.0, -2.0] },
158
+ "tolerance": 0,
159
+ "allowNaN": true
160
+ }
161
+ }
162
+ },
163
+ {
164
+ "name": "axis0_empty_rows_positive_infinity_identity_f32",
165
+ "attrs": { "axes": [0], "keepdims": 0 },
166
+ "inputs": { "x": { "dtype": "float32", "shape": [0, 3], "data": { "kind": "values", "values": [] } } },
167
+ "outputs": {
168
+ "y": { "dtype": "float32", "shape": [3], "data": { "kind": "cycle", "values": ["Infinity"] }, "tolerance": 0 }
169
+ }
170
+ },
171
+ {
172
+ "name": "axis1_empty_cols_positive_infinity_identity_f32",
173
+ "attrs": { "axes": [1], "keepdims": 0 },
174
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 0], "data": { "kind": "values", "values": [] } } },
175
+ "outputs": {
176
+ "y": { "dtype": "float32", "shape": [2], "data": { "kind": "cycle", "values": ["Infinity"] }, "tolerance": 0 }
177
+ }
178
+ },
179
+ {
180
+ "name": "axis1_keepdims",
181
+ "attrs": { "axes": [1], "keepdims": 1 },
182
+ "inputs": {
183
+ "x": {
184
+ "dtype": "float32",
185
+ "shape": [3, 4],
186
+ "data": { "kind": "values", "values": [4.0, -1.0, 2.0, -1.0, 8.0, 7.0, 6.0, 5.0, -3.0, -4.0, -4.0, 2.0] }
187
+ }
188
+ },
189
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1], "tolerance": 0.000001 } }
190
+ },
191
+ {
192
+ "name": "axis1_all_positive_infinity_returns_positive_infinity",
193
+ "attrs": { "axes": [1], "keepdims": 0 },
194
+ "inputs": {
195
+ "x": {
196
+ "dtype": "float32",
197
+ "shape": [3, 4],
198
+ "data": {
199
+ "kind": "values",
200
+ "values": ["Infinity", "Infinity", "Infinity", "Infinity", "Infinity", 7.0, "Infinity", 9.0, 3.0, "Infinity", 5.0, "Infinity"]
201
+ }
202
+ }
203
+ },
204
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0 } }
205
+ },
206
+ {
207
+ "name": "ort_mixed_infinities_axis1",
208
+ "provenance": {
209
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
210
+ "test": "ReductionOpTest.ReduceInfMin"
211
+ },
212
+ "attrs": { "axes": [1], "keepdims": 0 },
213
+ "inputs": {
214
+ "x": {
215
+ "dtype": "float32",
216
+ "shape": [6, 2],
217
+ "data": {
218
+ "kind": "values",
219
+ "values": [1.0, "Infinity", "Infinity", 4.0, "Infinity", "-Infinity", "-Infinity", "Infinity", 1.0, "-Infinity", "-Infinity", 4.0]
220
+ }
221
+ }
222
+ },
223
+ "outputs": { "y": { "dtype": "float32", "shape": [6], "tolerance": 0 } }
224
+ },
225
+ {
226
+ "name": "rank3_axis2_last_keepdims",
227
+ "attrs": { "axes": [2], "keepdims": 1 },
228
+ "inputs": {
229
+ "x": {
230
+ "dtype": "float32",
231
+ "shape": [2, 3, 4],
232
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/rank3_axis2_last_keepdims_input_x" } }
233
+ }
234
+ },
235
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 1], "tolerance": 0.000001 } }
236
+ },
237
+ {
238
+ "name": "rank4_axis1_channel_no_keepdims",
239
+ "attrs": { "axes": [1], "keepdims": 0 },
240
+ "inputs": {
241
+ "x": {
242
+ "dtype": "float32",
243
+ "shape": [2, 3, 2, 2],
244
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/rank3_axis2_last_keepdims_input_x" } }
245
+ }
246
+ },
247
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 2], "tolerance": 0.000001 } }
248
+ },
249
+ {
250
+ "name": "rank1_axis0_scalar_output",
251
+ "attrs": { "axes": [0], "keepdims": 0 },
252
+ "inputs": {
253
+ "x": {
254
+ "dtype": "float32",
255
+ "shape": [7],
256
+ "data": { "kind": "values", "values": [4.0, -8.0, 2.0, -8.0, 0.0, 5.0, -1.0] }
257
+ }
258
+ },
259
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.000001 } }
260
+ },
261
+ {
262
+ "name": "ort_int32_axis0_no_keepdims",
263
+ "provenance": {
264
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
265
+ "test": "ReductionOpTest.ReduceMin_int32",
266
+ "notes": "Single-axis subset adapted from ORT's multi-axis integer reduction fixture because this framework models one reduction axis per case."
267
+ },
268
+ "attrs": { "axes": [0], "keepdims": 0 },
269
+ "inputs": {
270
+ "x": {
271
+ "dtype": "int32",
272
+ "shape": [3, 2, 2],
273
+ "data": { "kind": "values", "values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] }
274
+ }
275
+ },
276
+ "outputs": { "y": { "dtype": "int32", "shape": [2, 2], "tolerance": 0 } }
277
+ },
278
+ {
279
+ "name": "int32_axis1_exact_above_float24",
280
+ "attrs": { "axes": [1], "keepdims": 0 },
281
+ "inputs": {
282
+ "x": {
283
+ "dtype": "int32",
284
+ "shape": [2, 3],
285
+ "data": { "kind": "values", "values": [16777217, 16777216, 5, -16777216, -16777217, 3] }
286
+ }
287
+ },
288
+ "outputs": { "y": { "dtype": "int32", "shape": [2], "tolerance": 0 } }
289
+ },
290
+ {
291
+ "name": "ort_axis1_rank3_no_keepdims",
292
+ "provenance": {
293
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
294
+ "test": "ReductionOpTest.ReduceMin_do_not_keepdims"
295
+ },
296
+ "attrs": { "axes": [1], "keepdims": 0 },
297
+ "inputs": {
298
+ "x": {
299
+ "dtype": "float32",
300
+ "shape": [3, 2, 2],
301
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
302
+ }
303
+ },
304
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2], "tolerance": 0.000001 } }
305
+ },
306
+ {
307
+ "name": "ort_axis1_rank3_keepdims",
308
+ "provenance": {
309
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
310
+ "test": "ReductionOpTest.ReduceMin_keepdims"
311
+ },
312
+ "attrs": { "axes": [1], "keepdims": 1 },
313
+ "inputs": {
314
+ "x": {
315
+ "dtype": "float32",
316
+ "shape": [3, 2, 2],
317
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
318
+ }
319
+ },
320
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2], "tolerance": 0.000001 } }
321
+ },
322
+ {
323
+ "name": "ort_axis0_rank1_scalar",
324
+ "provenance": {
325
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
326
+ "test": "ReductionOpTest.ReduceMin_do_not_keepdims_2"
327
+ },
328
+ "attrs": { "axes": [0], "keepdims": 0 },
329
+ "inputs": { "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [5.0, 1.0, 20.0] } } },
330
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.000001 } }
331
+ },
332
+ {
333
+ "name": "ort_float_axes02_rank3_keepdims1",
334
+ "attrs": { "axes": [0, 2], "keepdims": 1 },
335
+ "provenance": {
336
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
337
+ "test": "ReductionOpTest.ReduceMin",
338
+ "notes": "Direct multi-axis reduction from ORT."
339
+ },
340
+ "inputs": {
341
+ "x": {
342
+ "dtype": "float32",
343
+ "shape": [3, 2, 2],
344
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0] }
345
+ }
346
+ },
347
+ "outputs": {
348
+ "y": {
349
+ "dtype": "float32",
350
+ "shape": [1, 2, 1],
351
+ "data": { "kind": "values", "values": [1.0, 3.0] },
352
+ "tolerance": 0
353
+ }
354
+ }
355
+ },
356
+ {
357
+ "name": "ort_rank0_scalar",
358
+ "provenance": {
359
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
360
+ "test": "ReductionOpTest.ReduceMin0DTensor"
361
+ },
362
+ "inputs": { "x": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [2.0] } } },
363
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0 } }
364
+ },
365
+ {
366
+ "name": "onnx_backend_reduce_min_do_not_keepdims_example",
367
+ "attrs": { "keepdims": 0, "axes": [1] },
368
+ "inputs": {
369
+ "x": {
370
+ "dtype": "float32",
371
+ "shape": [3, 2, 2],
372
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
373
+ }
374
+ },
375
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2] } },
376
+ "provenance": {
377
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_do_not_keepdims_example",
378
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
379
+ }
380
+ },
381
+ {
382
+ "name": "onnx_backend_reduce_min_do_not_keepdims_random",
383
+ "attrs": { "keepdims": 0, "axes": [1] },
384
+ "inputs": {
385
+ "x": {
386
+ "dtype": "float32",
387
+ "shape": [3, 2, 2],
388
+ "data": {
389
+ "kind": "values",
390
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
391
+ }
392
+ }
393
+ },
394
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2] } },
395
+ "provenance": {
396
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_do_not_keepdims_random",
397
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
398
+ }
399
+ },
400
+ {
401
+ "name": "onnx_backend_reduce_min_empty_set",
402
+ "attrs": { "keepdims": 1, "axes": [1] },
403
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 0, 4], "data": { "kind": "values", "values": [] } } },
404
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 4] } },
405
+ "provenance": {
406
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_empty_set",
407
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
408
+ }
409
+ },
410
+ {
411
+ "name": "onnx_backend_reduce_min_keepdims_example",
412
+ "attrs": { "keepdims": 1, "axes": [1] },
413
+ "inputs": {
414
+ "x": {
415
+ "dtype": "float32",
416
+ "shape": [3, 2, 2],
417
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
418
+ }
419
+ },
420
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
421
+ "provenance": {
422
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_keepdims_example",
423
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
424
+ }
425
+ },
426
+ {
427
+ "name": "onnx_backend_reduce_min_keepdims_random",
428
+ "attrs": { "keepdims": 1, "axes": [1] },
429
+ "inputs": {
430
+ "x": {
431
+ "dtype": "float32",
432
+ "shape": [3, 2, 2],
433
+ "data": {
434
+ "kind": "values",
435
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
436
+ }
437
+ }
438
+ },
439
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
440
+ "provenance": {
441
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_keepdims_random",
442
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
443
+ }
444
+ },
445
+ {
446
+ "name": "onnx_backend_reduce_min_negative_axes_keepdims_example",
447
+ "attrs": { "keepdims": 1, "axes": [-2] },
448
+ "inputs": {
449
+ "x": {
450
+ "dtype": "float32",
451
+ "shape": [3, 2, 2],
452
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
453
+ }
454
+ },
455
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
456
+ "provenance": {
457
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_negative_axes_keepdims_example",
458
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
459
+ }
460
+ },
461
+ {
462
+ "name": "onnx_backend_reduce_min_negative_axes_keepdims_random",
463
+ "attrs": { "keepdims": 1, "axes": [-2] },
464
+ "inputs": {
465
+ "x": {
466
+ "dtype": "float32",
467
+ "shape": [3, 2, 2],
468
+ "data": {
469
+ "kind": "values",
470
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
471
+ }
472
+ }
473
+ },
474
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
475
+ "provenance": {
476
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_negative_axes_keepdims_random",
477
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
478
+ }
479
+ },
480
+ {
481
+ "name": "ort_int32_empty_axis0_keepdims_max_identity",
482
+ "provenance": {
483
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
484
+ "test": "ReductionOpTest.ReduceMin_int32_EmptySet",
485
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
486
+ },
487
+ "attrs": { "axes": [0], "keepdims": 1 },
488
+ "inputs": { "x": { "dtype": "int32", "shape": [0, 3], "data": { "kind": "values", "values": [] } } },
489
+ "outputs": { "y": { "dtype": "int32", "shape": [1, 3], "tolerance": 0 } }
490
+ },
491
+ {
492
+ "name": "ort_empty_set_default_axes_keepdims_f32",
493
+ "provenance": {
494
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
495
+ "test": "ReductionOpTest.empty_set_ReduceMin",
496
+ "notes": "An omitted axes input reduces all dimensions when noop_with_empty_axes is 0."
497
+ },
498
+ "attrs": { "keepdims": 1 },
499
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 0, 3], "data": { "kind": "values", "values": [] } } },
500
+ "outputs": {
501
+ "y": {
502
+ "dtype": "float32",
503
+ "shape": [1, 1, 1],
504
+ "data": { "kind": "values", "values": ["Infinity"] },
505
+ "tolerance": 0
506
+ }
507
+ }
508
+ },
509
+ {
510
+ "name": "onnx_backend_reduce_min_default_axes_keepdims_example",
511
+ "provenance": {
512
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_default_axes_keepdims_example"
513
+ },
514
+ "attrs": { "keepdims": 1 },
515
+ "inputs": {
516
+ "x": {
517
+ "dtype": "float32",
518
+ "shape": [3, 2, 2],
519
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
520
+ }
521
+ },
522
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1], "tolerance": 0 } }
523
+ },
524
+ {
525
+ "name": "ort_default_axes_rank3_no_keepdims_scalar",
526
+ "provenance": {
527
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
528
+ "test": "ReductionOpTest.ReduceMin_default_axes_do_not_keep_dims",
529
+ "notes": "Default axes reduce all input dimensions to a rank-0 scalar when keepdims=0."
530
+ },
531
+ "attrs": { "keepdims": 0 },
532
+ "inputs": {
533
+ "x": {
534
+ "dtype": "float32",
535
+ "shape": [3, 2, 2],
536
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
537
+ }
538
+ },
539
+ "outputs": {
540
+ "y": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [1.0] }, "tolerance": 0 }
541
+ }
542
+ },
543
+ {
544
+ "name": "onnx_backend_reduce_min_default_axes_keepdims_random",
545
+ "provenance": {
546
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_min_default_axes_keepdims_random"
547
+ },
548
+ "attrs": { "keepdims": 1 },
549
+ "inputs": {
550
+ "x": {
551
+ "dtype": "float32",
552
+ "shape": [3, 2, 2],
553
+ "data": {
554
+ "kind": "values",
555
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
556
+ }
557
+ }
558
+ },
559
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1], "tolerance": 0 } }
560
+ },
561
+ {
562
+ "name": "subgroup_vec4_last_axis_2x256",
563
+ "attrs": { "axes": [-1], "keepdims": 0 },
564
+ "inputs": {
565
+ "x": {
566
+ "dtype": "float32",
567
+ "shape": [2, 256],
568
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
569
+ }
570
+ },
571
+ "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0 } }
572
+ },
573
+ {
574
+ "name": "subgroup_scalar_last_axis_2x65",
575
+ "attrs": { "axes": [1], "keepdims": 0 },
576
+ "inputs": {
577
+ "x": {
578
+ "dtype": "float32",
579
+ "shape": [2, 65],
580
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11 }
581
+ }
582
+ },
583
+ "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0 } }
584
+ },
585
+ {
586
+ "name": "subgroup_vec4_int32_above_float24",
587
+ "attrs": { "axes": [1], "keepdims": 0 },
588
+ "inputs": {
589
+ "x": {
590
+ "dtype": "int32",
591
+ "shape": [2, 4],
592
+ "data": {
593
+ "kind": "values",
594
+ "values": [16777217, 16777219, 16777218, 16777220, -16777217, -16777219, -16777218, -16777220]
595
+ }
596
+ }
597
+ },
598
+ "outputs": {
599
+ "y": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [16777217, -16777220] } }
600
+ }
601
+ },
602
+ {
603
+ "name": "ort_bool_axis0_keepdims0",
604
+ "attrs": { "axes": [0], "keepdims": 0 },
605
+ "provenance": {
606
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
607
+ "test": "test_bool_ReduceMin_9"
608
+ },
609
+ "inputs": {
610
+ "x": {
611
+ "dtype": "bool",
612
+ "shape": [2, 3, 2],
613
+ "data": { "kind": "values", "values": [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] }
614
+ }
615
+ },
616
+ "outputs": {
617
+ "y": { "dtype": "bool", "shape": [3, 2], "data": { "kind": "values", "values": [0, 0, 0, 1, 0, 1] } }
618
+ }
619
+ },
620
+ {
621
+ "name": "ort_bool_axis2_keepdims1",
622
+ "attrs": { "axes": [2], "keepdims": 1 },
623
+ "provenance": {
624
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
625
+ "test": "test_bool_ReduceMin_15"
626
+ },
627
+ "inputs": {
628
+ "x": {
629
+ "dtype": "bool",
630
+ "shape": [2, 3, 2],
631
+ "data": { "kind": "values", "values": [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] }
632
+ }
633
+ },
634
+ "outputs": {
635
+ "y": { "dtype": "bool", "shape": [2, 3, 1], "data": { "kind": "values", "values": [0, 1, 0, 0, 0, 0] } }
636
+ }
637
+ },
638
+ {
639
+ "name": "ort_bool_all_axes_keepdims1",
640
+ "attrs": { "keepdims": 1 },
641
+ "provenance": {
642
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
643
+ "test": "test_bool_ReduceMin_19",
644
+ "notes": "The fixture now uses the standard Boolean route introduced by ONNX ReduceMin-20."
645
+ },
646
+ "inputs": {
647
+ "x": {
648
+ "dtype": "bool",
649
+ "shape": [2, 3, 2],
650
+ "data": { "kind": "values", "values": [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] }
651
+ }
652
+ },
653
+ "outputs": {
654
+ "y": { "dtype": "bool", "shape": [1, 1, 1], "data": { "kind": "values", "values": [0] }, "tolerance": 0 }
655
+ }
656
+ },
657
+ {
658
+ "name": "ort_bool_all_axes_keepdims0_scalar",
659
+ "attrs": { "keepdims": 0 },
660
+ "provenance": {
661
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
662
+ "test": "test_bool_ReduceMin_17",
663
+ "notes": "The fixture now uses the standard Boolean route introduced by ONNX ReduceMin-20."
664
+ },
665
+ "inputs": {
666
+ "x": {
667
+ "dtype": "bool",
668
+ "shape": [2, 3, 2],
669
+ "data": { "kind": "values", "values": [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] }
670
+ }
671
+ },
672
+ "outputs": { "y": { "dtype": "bool", "shape": [], "data": { "kind": "values", "values": [0] }, "tolerance": 0 } }
673
+ },
674
+ {
675
+ "name": "ort_noop_empty_axes_identity",
676
+ "provenance": {
677
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
678
+ "test": "ReductionOpTest.ReduceMin_noop_axes_input_initializer_opset_18",
679
+ "notes": "The omitted axes input exercises empty-axes behavior."
680
+ },
681
+ "attrs": { "keepdims": 0, "noop_with_empty_axes": 1 },
682
+ "inputs": {
683
+ "x": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } }
684
+ },
685
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 2], "tolerance": 0 } }
686
+ },
687
+ {
688
+ "name": "rank3_lastaxis_cols1024_tree_f32",
689
+ "attrs": { "axes": [2], "keepdims": 0 },
690
+ "inputs": {
691
+ "x": {
692
+ "dtype": "float32",
693
+ "shape": [2, 2, 1024],
694
+ "data": {
695
+ "kind": "cycle",
696
+ "values": [1.0, -2.0, 0.5, 3.25, -1.5, 2.0, -0.75, 4.0, -3.5, 1.25, 0.0, -2.25, 5.0, -4.0, 2.75, -1.0]
697
+ }
698
+ }
699
+ },
700
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2], "tolerance": 0.00001 } }
701
+ },
702
+ {
703
+ "name": "rank3_lastaxis_cols1024_tree_i32",
704
+ "attrs": { "axes": [2], "keepdims": 0 },
705
+ "inputs": {
706
+ "x": {
707
+ "dtype": "int32",
708
+ "shape": [2, 2, 1024],
709
+ "data": { "kind": "cycle", "values": [3, -7, 11, -2, 5, -13, 8, -1, 19, -4, 6, -9, 2, -15, 10, -6] }
710
+ }
711
+ },
712
+ "outputs": { "y": { "dtype": "int32", "shape": [2, 2], "tolerance": 0.00001 } }
713
+ },
714
+ {
715
+ "name": "axis0_splitk_positive_infinity_identity_8192x2",
716
+ "attrs": { "axes": [0], "keepdims": 0 },
717
+ "inputs": { "x": { "dtype": "float32", "shape": [8192, 2], "data": { "kind": "cycle", "values": ["Infinity"] } } },
718
+ "outputs": {
719
+ "y": {
720
+ "dtype": "float32",
721
+ "shape": [2],
722
+ "data": { "kind": "cycle", "values": ["Infinity"] },
723
+ "tolerance": 0,
724
+ "relTolerance": 0
725
+ }
726
+ }
727
+ },
728
+ {
729
+ "name": "axis0_splitk_8192x48_keepdims",
730
+ "attrs": { "axes": [0], "keepdims": 1 },
731
+ "inputs": {
732
+ "x": {
733
+ "dtype": "float32",
734
+ "shape": [8192, 48],
735
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
736
+ }
737
+ },
738
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 48], "tolerance": 0.0001 } }
739
+ },
740
+ {
741
+ "name": "int32_rank4_axis0_no_keepdims",
742
+ "attrs": { "axes": [0], "keepdims": 0 },
743
+ "inputs": {
744
+ "x": {
745
+ "dtype": "int32",
746
+ "shape": [2, 3, 4, 5],
747
+ "data": {
748
+ "kind": "values",
749
+ "values": [-3, 18, -25, 17, 33, -27, 42, 7, -36, -27, 22, 39, -8, 40, -42, -11, 18, -2, -43, -6, -50, 25, 5, -44, -31, 10, -6, 13, 19, 6, -26, 5, 3, 11, 14, -16, 6, 23, 28, -12, -46, -41, 37, 49, 17, 22, 33, -2, -49, 14, -34, -19, 43, -6, 42, 21, -27, -40, -15, 14, 11, -43, -27, 42, 46, -29, -15, 47, 17, 41, 47, -31, -23, -46, -31, -17, -20, 7, 23, 43, 31, -19, -48, 33, 0, -32, -29, 17, 25, 8, -3, -34, -38, 43, -41, 26, 17, -36, -30, 3, -35, 9, -15, -2, -46, -7, 22, 40, 4, -7, 37, 27, 0, -11, -47, -21, -38, 26, 29, -21]
750
+ }
751
+ }
752
+ },
753
+ "outputs": { "y": { "dtype": "int32", "shape": [3, 4, 5], "tolerance": 0 } }
754
+ },
755
+ {
756
+ "name": "f32_rank4_axis2_mid_no_keepdims",
757
+ "attrs": { "axes": [2], "keepdims": 0 },
758
+ "inputs": {
759
+ "x": {
760
+ "dtype": "float32",
761
+ "shape": [2, 3, 4, 5],
762
+ "data": {
763
+ "kind": "values",
764
+ "values": [5.248, -0.858, -1.454, -7.96, -0.025, -0.959, -1.61, 0.946, 1.263, -3.197, -2.659, -1.427, 2.069, 1.684, -3.917, -3.358, 2.211, 4.724, -0.093, -2.05, 3.287, -0.929, 2.177, 4.647, 1.89, 0.22, 2.197, -1.928, -0.534, -1.722, -0.613, -1.459, -0.557, -1.142, 0.267, 0.191, 0.889, 4.208, -4.641, 3.887, -0.712, -3.697, -0.517, 0.276, 3.203, -3.185, 0.652, 0.353, -5.052, -3.557, 1.8, 2.087, 3.263, 1.601, 1.186, 0.369, 3.627, -2.529, -0.426, 1.156, -4.732, 3.933, -2.379, -0.215, 6.47, -2.488, -1.588, 4.685, -3.241, -1.296, 1.548, 1.366, 1.781, 1.112, 4.036, 3.048, 1.786, -2.048, -2.141, -5.725, 2.062, -5.469, 2.637, 5.539, -3.185, -2.053, -1.429, 2.491, -2.59, -0.392, -1.569, -0.754, 3.874, -2.893, 0.215, 0.815, 2.576, -3.792, 3.345, 1.304, 2.623, 0.215, -4.917, -1.942, 2.453, 0.11, -0.146, 5.375, 6.606, -0.111, 5.799, -5.981, -6.147, 2.601, -0.786, 1.737, 1.55, -0.268, 2.046, 0.452]
765
+ }
766
+ }
767
+ },
768
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 5] } }
769
+ },
770
+ {
771
+ "name": "f32_rank4_default_all_axes_no_keepdims_scalar",
772
+ "attrs": { "keepdims": 0 },
773
+ "inputs": {
774
+ "x": {
775
+ "dtype": "float32",
776
+ "shape": [2, 3, 4, 5],
777
+ "data": {
778
+ "kind": "values",
779
+ "values": [-1.425, 1.508, -0.089, 0.904, 2.69, 1.065, 2.7, 1.722, 2.957, -2.091, -1.578, -2.523, 1.126, -0.487, 1.827, 0.635, 0.255, 4.301, 1.213, -0.054, -1.968, 2.381, 1.906, -2.174, -0.29, 0.476, -3.278, -0.556, 2.798, -3.23, 0.982, 3.785, -1.242, -0.908, 0.435, 1.029, 0.794, -3.026, -1.528, 0.203, -0.635, 2.277, -0.644, -0.74, 3.699, -0.163, -1.961, -0.005, -0.465, 1.422, 0.906, -0.665, -0.769, -1.061, 2.701, 4.03, -0.115, 1.129, 0.719, -1.024, -1.046, -0.963, -3.848, -1.515, 0.907, 1.845, -0.415, -0.752, 1.28, 4.107, -1.857, -1.549, -0.874, 1.096, -1.708, 0.931, -1.442, 1.125, -0.63, -0.86, 1.63, -2.371, -1.537, 1.458, 1.532, 0.416, -4.374, 1.444, 0.698, -0.677, -3.891, 1.662, 3.498, 1.002, 2.661, -1.315, 0.37, -1.302, -2.241, -0.693, -1.823, -1.492, -1.542, -0.828, -0.584, 0.254, -2.495, 0.634, -3.637, 0.111, 0.162, -4.64, -0.682, 2.18, 0.901, -0.989, -1.093, 0.584, 0.308, 1.635]
780
+ }
781
+ }
782
+ },
783
+ "outputs": { "y": { "dtype": "float32", "shape": [] } }
784
+ },
785
+ {
786
+ "name": "rank4_multi_axis_12_keepdims",
787
+ "attrs": { "axes": [1, 2], "keepdims": 1 },
788
+ "inputs": {
789
+ "x": {
790
+ "dtype": "float32",
791
+ "shape": [2, 3, 2, 2],
792
+ "data": {
793
+ "kind": "values",
794
+ "values": [0.5, -1.0, 2.0, -0.25, 1.5, 0.75, -2.0, 1.0, 0.125, -0.5, 3.0, -1.5, 0.25, 2.5, -0.75, 1.25, -3.0, 0.5, 2.0, -1.0, 0.75, -0.25, 1.5, -2.5]
795
+ }
796
+ }
797
+ },
798
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 1, 2], "tolerance": 0 } }
799
+ },
800
+ {
801
+ "name": "uint32_axis0_tiled_64x32",
802
+ "attrs": { "axes": [0], "keepdims": 0 },
803
+ "inputs": {
804
+ "x": {
805
+ "dtype": "uint32",
806
+ "shape": [64, 32],
807
+ "data": {
808
+ "kind": "cycle",
809
+ "values": [16777216, 16777245, 16777274, 16777236, 16777265, 16777227, 16777256, 16777218, 16777247, 16777276, 16777238, 16777267, 16777229, 16777258, 16777220, 16777249, 16777278, 16777240, 16777269, 16777231, 16777260, 16777222, 16777251, 16777280, 16777242, 16777271, 16777233, 16777262, 16777224, 16777253, 16777282, 16777244, 16777273, 16777235, 16777264, 16777226, 16777255, 16777217, 16777246, 16777275, 16777237, 16777266, 16777228, 16777257, 16777219, 16777248, 16777277, 16777239, 16777268, 16777230, 16777259, 16777221, 16777250, 16777279, 16777241, 16777270, 16777232, 16777261, 16777223, 16777252, 16777281, 16777243, 16777272, 16777234, 16777263, 16777225, 16777254]
810
+ }
811
+ }
812
+ },
813
+ "outputs": { "y": { "dtype": "uint32", "shape": [32], "tolerance": 0 } }
814
+ },
815
+ {
816
+ "name": "rank3_all_axes_keepdims0_small_mod4_miss_f32",
817
+ "attrs": { "keepdims": 0 },
818
+ "inputs": {
819
+ "x": {
820
+ "dtype": "float32",
821
+ "shape": [3, 5, 7],
822
+ "data": {
823
+ "kind": "cycle",
824
+ "values": [-2.5, 1.0, 0.0, -3.5, 4.0, -1.5, 2.5, -0.5, -4.0, 0.5, -1.0, 3.0, -5.5, 1.5, -2.0, 2.0, -3.0, 5.0, -0.25, 0.25]
825
+ }
826
+ }
827
+ },
828
+ "outputs": { "y": { "dtype": "float32", "shape": [] } }
829
+ },
830
+ {
831
+ "name": "rank3_axis2_dedicated_variant_correctness",
832
+ "attrs": { "axes": [2], "keepdims": 1 },
833
+ "inputs": {
834
+ "x": {
835
+ "dtype": "float32",
836
+ "shape": [4, 3, 5],
837
+ "data": {
838
+ "kind": "values",
839
+ "values": [1.0, -2.0, 3.0, -4.0, 5.0, -1.0, 2.0, -3.0, 4.0, -5.0, 0.5, -0.5, 1.5, -1.5, 2.5, -2.5, 3.5, -3.5, 4.5, -4.5, 0.25, -0.25, 0.75, -0.75, 1.25, -1.25, 1.75, -1.75, 2.25, -2.25, 0.1, -0.1, 0.3, -0.3, 0.6, -0.6, 0.9, -0.9, 1.2, -1.2, 2.0, -2.0, 3.0, -3.0, 4.0, -4.0, 5.0, -5.0, 6.0, -6.0, 7.0, -7.0, 8.0, -8.0, 9.0, -9.0, 10.0, -10.0, 11.0, -11.0]
840
+ }
841
+ }
842
+ },
843
+ "outputs": { "y": { "dtype": "float32", "shape": [4, 3, 1] } }
844
+ },
845
+ {
846
+ "name": "axis1_rows32768_cols30_subgroup_gate_cliff_f32",
847
+ "attrs": { "axes": [1], "keepdims": 0 },
848
+ "inputs": {
849
+ "x": {
850
+ "dtype": "float32",
851
+ "shape": [32768, 30],
852
+ "data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.017, "scale": 1.5 }
853
+ }
854
+ },
855
+ "outputs": { "y": { "dtype": "float32", "shape": [32768] } }
856
+ },
857
+ {
858
+ "name": "axis0_cols15_i32_serial_uncoalesced",
859
+ "attrs": { "axes": [0], "keepdims": 0 },
860
+ "inputs": {
861
+ "x": {
862
+ "dtype": "int32",
863
+ "shape": [256, 15],
864
+ "data": {
865
+ "kind": "cycle",
866
+ "values": [2147483647, -2147483648, 0, 1, -1, 1000000, -1000000, 16777217, -16777217, 42, -42, 100, -100, 7, -7]
867
+ }
868
+ }
869
+ },
870
+ "outputs": { "y": { "dtype": "int32", "shape": [15] } }
871
+ },
872
+ {
873
+ "name": "all_axes_flat_fullreduce_101x103_nonmul4_keepdims",
874
+ "provenance": {
875
+ "notes": "numel=10403 (%4=3, >=8192) full-reduce: previously fell to the single-lane reduce-serial-axis kernel; now stays on all_axes_flat (f32 binding, manual vec4 groups + 3-element scalar tail). Cross-checks the flat path against the serial all_axes_keepdims reference — a dropped tail would change the result."
876
+ },
877
+ "attrs": { "keepdims": 1 },
878
+ "inputs": {
879
+ "x": {
880
+ "dtype": "float32",
881
+ "shape": [101, 103],
882
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5 }
883
+ }
884
+ },
885
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1], "tolerance": 0.01, "relTolerance": 0.0001 } }
886
+ },
887
+ {
888
+ "name": "contiguous_suffix_axes12_parallel",
889
+ "provenance": {
890
+ "notes": "Contiguous axes {1,2} exercise the shared cooperative suffix reduction instead of one serial lane per output."
891
+ },
892
+ "attrs": { "axes": [1, 2], "keepdims": 1 },
893
+ "inputs": {
894
+ "x": {
895
+ "dtype": "float32",
896
+ "shape": [3, 16, 16],
897
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.07, "scale": 0.2 }
898
+ }
899
+ },
900
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 1], "tolerance": 0.00001 } }
901
+ },
902
+ {
903
+ "name": "axis_split_rank3_axis1_2x8192x4",
904
+ "attrs": { "axes": [1], "keepdims": 0 },
905
+ "inputs": {
906
+ "x": { "dtype": "float32", "shape": [2, 8192, 4], "data": { "kind": "cycle", "values": ["Infinity"] } }
907
+ },
908
+ "outputs": {
909
+ "y": {
910
+ "dtype": "float32",
911
+ "shape": [2, 4],
912
+ "data": { "kind": "cycle", "values": ["Infinity"] },
913
+ "tolerance": 0,
914
+ "relTolerance": 0
915
+ }
916
+ }
917
+ },
918
+ {
919
+ "name": "f16_axis_split_tiled_narrow_2x8192x4",
920
+ "attrs": { "axes": [1], "keepdims": 0 },
921
+ "inputs": {
922
+ "x": { "dtype": "float16", "shape": [2, 8192, 4], "data": { "kind": "cycle", "values": ["Infinity"] } }
923
+ },
924
+ "outputs": {
925
+ "y": {
926
+ "dtype": "float16",
927
+ "shape": [2, 4],
928
+ "data": { "kind": "cycle", "values": ["Infinity"] },
929
+ "tolerance": 0,
930
+ "relTolerance": 0
931
+ }
932
+ }
933
+ },
934
+ {
935
+ "name": "axis_split_rank3_axis1_wide_2x8192x32",
936
+ "attrs": { "axes": [1], "keepdims": 0 },
937
+ "inputs": {
938
+ "x": { "dtype": "float32", "shape": [2, 8192, 32], "data": { "kind": "cycle", "values": ["Infinity"] } }
939
+ },
940
+ "outputs": {
941
+ "y": {
942
+ "dtype": "float32",
943
+ "shape": [2, 32],
944
+ "data": { "kind": "cycle", "values": ["Infinity"] },
945
+ "tolerance": 0,
946
+ "relTolerance": 0
947
+ }
948
+ }
949
+ },
950
+ {
951
+ "name": "f16_axis_split_wide_2x8192x32",
952
+ "attrs": { "axes": [1], "keepdims": 0 },
953
+ "inputs": {
954
+ "x": { "dtype": "float16", "shape": [2, 8192, 32], "data": { "kind": "cycle", "values": ["Infinity"] } }
955
+ },
956
+ "outputs": {
957
+ "y": {
958
+ "dtype": "float16",
959
+ "shape": [2, 32],
960
+ "data": { "kind": "cycle", "values": ["Infinity"] },
961
+ "tolerance": 0,
962
+ "relTolerance": 0
963
+ }
964
+ }
965
+ },
966
+ {
967
+ "name": "f16_rank3_axis1_serial",
968
+ "attrs": { "axes": [1], "keepdims": 0 },
969
+ "inputs": {
970
+ "x": {
971
+ "dtype": "float16",
972
+ "shape": [3, 2, 2],
973
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
974
+ }
975
+ },
976
+ "outputs": { "y": { "dtype": "float16", "shape": [3, 2], "tolerance": 0.01 } }
977
+ },
978
+ {
979
+ "name": "f16_last_axis_serial_fallback",
980
+ "attrs": { "axes": [1], "keepdims": 0 },
981
+ "inputs": {
982
+ "x": {
983
+ "dtype": "float16",
984
+ "shape": [2, 65],
985
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11 }
986
+ }
987
+ },
988
+ "outputs": { "y": { "dtype": "float16", "shape": [2], "tolerance": 0.01 } }
989
+ },
990
+ {
991
+ "name": "f16_all_axes",
992
+ "attrs": { "keepdims": 1 },
993
+ "inputs": {
994
+ "x": {
995
+ "dtype": "float16",
996
+ "shape": [32, 32, 32],
997
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5 }
998
+ }
999
+ },
1000
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1, 1], "tolerance": 0.01 } }
1001
+ },
1002
+ {
1003
+ "name": "f16_axis0_splitk_8192x8",
1004
+ "attrs": { "axes": [0], "keepdims": 0 },
1005
+ "inputs": {
1006
+ "x": {
1007
+ "dtype": "float16",
1008
+ "shape": [8192, 8],
1009
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 4.0 }
1010
+ }
1011
+ },
1012
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.002 } }
1013
+ },
1014
+ {
1015
+ "name": "f16_last_axis_vec4_8x1024",
1016
+ "attrs": { "axes": [1], "keepdims": 0 },
1017
+ "inputs": {
1018
+ "x": {
1019
+ "dtype": "float16",
1020
+ "shape": [8, 1024],
1021
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 4.0 }
1022
+ }
1023
+ },
1024
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.002 } }
1025
+ },
1026
+ {
1027
+ "name": "f16_last_axis_scalar_8x1023",
1028
+ "attrs": { "axes": [1], "keepdims": 0 },
1029
+ "inputs": {
1030
+ "x": {
1031
+ "dtype": "float16",
1032
+ "shape": [8, 1023],
1033
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 4.0 }
1034
+ }
1035
+ },
1036
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.002 } }
1037
+ },
1038
+ {
1039
+ "name": "f16_all_axes_flat_65543",
1040
+ "attrs": { "keepdims": 0 },
1041
+ "inputs": {
1042
+ "x": {
1043
+ "dtype": "float16",
1044
+ "shape": [65543],
1045
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 4.0 }
1046
+ }
1047
+ },
1048
+ "outputs": { "y": { "dtype": "float16", "shape": [], "tolerance": 0.05, "relTolerance": 0.002 } }
1049
+ },
1050
+ {
1051
+ "name": "f16_suffix_vec4_4x8x128",
1052
+ "attrs": { "axes": [1, 2], "keepdims": 0 },
1053
+ "inputs": {
1054
+ "x": {
1055
+ "dtype": "float16",
1056
+ "shape": [4, 8, 128],
1057
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 4.0 }
1058
+ }
1059
+ },
1060
+ "outputs": { "y": { "dtype": "float16", "shape": [4], "tolerance": 0.05, "relTolerance": 0.002 } }
1061
+ },
1062
+ {
1063
+ "name": "f16_suffix_scalar_4x7x37",
1064
+ "attrs": { "axes": [1, 2], "keepdims": 0 },
1065
+ "inputs": {
1066
+ "x": {
1067
+ "dtype": "float16",
1068
+ "shape": [4, 7, 37],
1069
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 4.0 }
1070
+ }
1071
+ },
1072
+ "outputs": { "y": { "dtype": "float16", "shape": [4], "tolerance": 0.05, "relTolerance": 0.002 } }
1073
+ },
1074
+ {
1075
+ "name": "f16_axis0_tilecols_4096x64",
1076
+ "attrs": { "axes": [0], "keepdims": 0 },
1077
+ "inputs": {
1078
+ "x": {
1079
+ "dtype": "float16",
1080
+ "shape": [4096, 64],
1081
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11, "scale": 4.0 }
1082
+ }
1083
+ },
1084
+ "outputs": { "y": { "dtype": "float16", "shape": [64], "tolerance": 0.05, "relTolerance": 0.002 } }
1085
+ },
1086
+ {
1087
+ "name": "noop_empty_axes_under_x4_copy_floor",
1088
+ "provenance": {
1089
+ "notes": "Identity-shaped noop_with_empty_axes reduction below the four-element floor of the vec4 degenerate copy, which otherwise outranks the noop route on every identity case (the ORT [1,2,2] fixture has exactly four elements)."
1090
+ },
1091
+ "attrs": { "keepdims": 0, "noop_with_empty_axes": 1 },
1092
+ "inputs": {
1093
+ "x": { "dtype": "float32", "shape": [1, 1, 3], "data": { "kind": "values", "values": [3.0, -1.0, 2.0] } }
1094
+ },
1095
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 3], "tolerance": 0 } }
1096
+ },
1097
+ {
1098
+ "name": "ort_int8_axis1_extrema",
1099
+ "provenance": {
1100
+ "source": "ONNX Runtime CPUExecutionProvider",
1101
+ "notes": "Covers the signed narrow-integer reduction route."
1102
+ },
1103
+ "attrs": { "axes": [1], "keepdims": 0 },
1104
+ "inputs": {
1105
+ "x": { "dtype": "int8", "shape": [2, 3], "data": { "kind": "values", "values": [-128, -5, 127, 12, -3, 11] } }
1106
+ },
1107
+ "outputs": {
1108
+ "y": { "dtype": "int8", "shape": [2], "tolerance": 0, "data": { "kind": "values", "values": [-128, -3] } }
1109
+ }
1110
+ },
1111
+ {
1112
+ "name": "ort_uint8_axis0_extrema",
1113
+ "provenance": {
1114
+ "source": "ONNX Runtime CPUExecutionProvider",
1115
+ "notes": "Covers the unsigned narrow-integer reduction route."
1116
+ },
1117
+ "attrs": { "axes": [0], "keepdims": 0 },
1118
+ "inputs": {
1119
+ "x": { "dtype": "uint8", "shape": [2, 3], "data": { "kind": "values", "values": [1, 200, 255, 250, 3, 4] } }
1120
+ },
1121
+ "outputs": {
1122
+ "y": { "dtype": "uint8", "shape": [3], "tolerance": 0, "data": { "kind": "values", "values": [1, 3, 4] } }
1123
+ }
1124
+ },
1125
+ {
1126
+ "name": "ort_int8_empty_axis1_highest_identity",
1127
+ "provenance": {
1128
+ "source": "ONNX Runtime CPUExecutionProvider",
1129
+ "notes": "An empty INT8 minimum is the logical dtype maximum (127), not the physical i32 storage maximum."
1130
+ },
1131
+ "attrs": { "axes": [1], "keepdims": 1 },
1132
+ "inputs": { "x": { "dtype": "int8", "shape": [2, 0, 3], "data": { "kind": "values", "values": [] } } },
1133
+ "outputs": {
1134
+ "y": {
1135
+ "dtype": "int8",
1136
+ "shape": [2, 1, 3],
1137
+ "tolerance": 0,
1138
+ "data": { "kind": "values", "values": [127, 127, 127, 127, 127, 127] }
1139
+ }
1140
+ }
1141
+ },
1142
+ {
1143
+ "name": "ort_uint8_empty_axis1_highest_identity",
1144
+ "provenance": {
1145
+ "source": "ONNX Runtime CPUExecutionProvider",
1146
+ "notes": "An empty UINT8 minimum is the logical dtype maximum (255), not the physical u32 storage maximum."
1147
+ },
1148
+ "attrs": { "axes": [1], "keepdims": 1 },
1149
+ "inputs": { "x": { "dtype": "uint8", "shape": [2, 0, 3], "data": { "kind": "values", "values": [] } } },
1150
+ "outputs": {
1151
+ "y": {
1152
+ "dtype": "uint8",
1153
+ "shape": [2, 1, 3],
1154
+ "tolerance": 0,
1155
+ "data": { "kind": "values", "values": [255, 255, 255, 255, 255, 255] }
1156
+ }
1157
+ }
1158
+ },
1159
+ {
1160
+ "name": "onnx_v20_bool_empty_axis1_true_identity",
1161
+ "provenance": {
1162
+ "source": "https://onnx.ai/onnx/operators/onnx__ReduceMin.html",
1163
+ "notes": "An empty Boolean minimum is true, the logical dtype maximum."
1164
+ },
1165
+ "attrs": { "axes": [1], "keepdims": 1 },
1166
+ "inputs": { "x": { "dtype": "bool", "shape": [2, 0, 3], "data": { "kind": "values", "values": [] } } },
1167
+ "outputs": {
1168
+ "y": {
1169
+ "dtype": "bool",
1170
+ "shape": [2, 1, 3],
1171
+ "tolerance": 0,
1172
+ "data": { "kind": "values", "values": [1, 1, 1, 1, 1, 1] }
1173
+ }
1174
+ }
1175
+ }
1176
+ ]
1177
+ }