Xenova HF Staff commited on
Commit
15dc577
·
verified ·
1 Parent(s): 8d1ebb5

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,80 @@
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.ArgMax
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 13
12
+
13
+ ## Description
14
+
15
+ Returns the index of the maximum value along an axis, choosing the first equal value unless `select_last_index` is enabled.
16
+
17
+ See the [ONNX `ArgMax` spec](https://onnx.ai/onnx/operators/onnx__ArgMax.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `data` | `x` | `T` | — | — | Values whose maximum index is selected along `axis`. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- | --- |
29
+ | `reduced` | `y` | `I` | `uint32` | derived | derived; see description | Logical int64 indices of the maximum values along the reduced axis; WebGPU stores these bounded indices as uint32. | required |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `axis` | `0` | Axis to reduce; negative values count from the back. |
38
+ | `keepdims` | `1` | Retain the reduced dimension with length one when non-zero. |
39
+ | `select_last_index` | `0` | Choose the last equal maximum instead of the first when non-zero. |
40
+
41
+ ## Type constraints
42
+
43
+ | Variable | Allowed dtypes |
44
+ | --- | --- |
45
+ | `T` | `float32`, `float16`, `int32`, `uint32`, `int16`, `int8`, `uint8` |
46
+ | `I` | `int64` |
47
+
48
+ ## Device requirements
49
+
50
+ Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
51
+
52
+ ## Files
53
+
54
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
55
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
56
+ - [`test.json`](build/webgpu/test.json) — correctness cases
57
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
58
+ - [`reduce-arg-axis-split-combine.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-combine.wgsl.jinja)
59
+ - [`reduce-arg-axis-split-reduce.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-reduce.wgsl.jinja)
60
+ - [`reduce-arg-axis-split-tiled.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-tiled.wgsl.jinja)
61
+ - [`reduce-arg-axis-tiled.wgsl.jinja`](build/webgpu/reduce-arg-axis-tiled.wgsl.jinja)
62
+ - [`reduce-arg-axis.wgsl.jinja`](build/webgpu/reduce-arg-axis.wgsl.jinja)
63
+ - [`reduce-arg-row-split.wgsl.jinja`](build/webgpu/reduce-arg-row-split.wgsl.jinja)
64
+ - [`reduce-arg-row-subgroup.wgsl.jinja`](build/webgpu/reduce-arg-row-subgroup.wgsl.jinja)
65
+
66
+ ## Use with `@huggingface/kernels`
67
+
68
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
69
+ It then allocates the result tensors automatically.
70
+
71
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
72
+
73
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
74
+
75
+ ```js
76
+ import { getKernel } from "@huggingface/kernels";
77
+
78
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.ArgMax", { version: 1 });
79
+ const { y } = await kernel({ x: { data: xData, shape: [2, 2] } });
80
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ArgMax",
3
+ "cases": [
4
+ {
5
+ "name": "argmax-axis0-f32-1024x512",
6
+ "preset": "smoke",
7
+ "vars": { "dtype": "float32", "rows": 1024, "cols": 512 },
8
+ "attrs": { "axis": 0, "keepdims": 0 },
9
+ "inputs": { "x": { "shape": [1024, 512], "dtype": "float32", "dist": "normal", "seed": 971, "scale": 1 } },
10
+ "outputs": { "y": { "shape": [512], "dtype": "uint32" } },
11
+ "bench": {
12
+ "primary": true,
13
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }]
14
+ }
15
+ },
16
+ {
17
+ "name": "logits",
18
+ "attrs": { "axis": 1, "keepdims": 0 },
19
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 32000] } },
20
+ "outputs": { "y": { "dtype": "uint32", "shape": [1] } }
21
+ },
22
+ {
23
+ "name": "argmax-axis0-large-65536x512",
24
+ "preset": "smoke",
25
+ "vars": { "dtype": "float32", "rows": 65536, "cols": 512 },
26
+ "attrs": { "axis": 0, "keepdims": 0 },
27
+ "inputs": { "x": { "shape": [65536, 512], "dtype": "float32", "dist": "normal", "seed": 211, "scale": 0.2 } },
28
+ "outputs": { "y": { "shape": [512], "dtype": "uint32" } },
29
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
30
+ },
31
+ {
32
+ "name": "argmax-lastaxis-f32-4096x4096",
33
+ "preset": "smoke",
34
+ "vars": { "rows": 4096, "cols": 4096 },
35
+ "attrs": { "axis": 1, "keepdims": 0 },
36
+ "inputs": { "x": { "shape": [4096, 4096], "dtype": "float32", "dist": "normal", "seed": 117, "scale": 0.2 } },
37
+ "outputs": { "y": { "shape": [4096], "dtype": "uint32" } },
38
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
39
+ },
40
+ {
41
+ "name": "argmax-lastaxis-f32-4096x50257",
42
+ "preset": "stress",
43
+ "provenance": { "notes": "Stress-only capacity case: declared tensors occupy 785 MiB of GPU storage." },
44
+ "vars": { "dtype": "float32", "rows": 4096, "cols": 50257 },
45
+ "attrs": { "axis": 1, "keepdims": 0 },
46
+ "inputs": { "x": { "shape": [4096, 50257], "dtype": "float32", "dist": "normal", "seed": 503, "scale": 0.2 } },
47
+ "outputs": { "y": { "shape": [4096], "dtype": "uint32" } },
48
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
49
+ },
50
+ {
51
+ "name": "argmax-logits-f32-1x131072",
52
+ "preset": "smoke",
53
+ "vars": { "dtype": "float32", "rows": 1, "cols": 131072 },
54
+ "attrs": { "axis": 1, "keepdims": 0 },
55
+ "inputs": { "x": { "shape": [1, 131072], "dtype": "float32", "dist": "normal", "seed": 733, "scale": 1 } },
56
+ "outputs": { "y": { "shape": [1], "dtype": "uint32" } },
57
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
58
+ },
59
+ {
60
+ "name": "argmax-vocab-unaligned-f32-1x50257",
61
+ "preset": "smoke",
62
+ "vars": { "dtype": "float32", "rows": 1, "cols": 50257 },
63
+ "attrs": { "axis": 1, "keepdims": 0 },
64
+ "inputs": { "x": { "shape": [1, 50257], "dtype": "float32", "dist": "normal", "seed": 739, "scale": 1 } },
65
+ "outputs": { "y": { "shape": [1], "dtype": "uint32" } },
66
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
67
+ },
68
+ {
69
+ "name": "argmax-lastaxis-f16-4096x4096",
70
+ "preset": "smoke",
71
+ "vars": { "dtype": "float16", "rows": 4096, "cols": 4096 },
72
+ "attrs": { "axis": 1, "keepdims": 0 },
73
+ "inputs": { "x": { "shape": [4096, 4096], "dtype": "float16", "dist": "normal", "seed": 617, "scale": 0.2 } },
74
+ "outputs": { "y": { "shape": [4096], "dtype": "uint32" } },
75
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
76
+ },
77
+ {
78
+ "name": "argmax-axis0-int32-65536x512",
79
+ "preset": "smoke",
80
+ "vars": { "dtype": "int32", "rows": 65536, "cols": 512 },
81
+ "attrs": { "axis": 0, "keepdims": 0 },
82
+ "inputs": { "x": { "shape": [65536, 512], "dtype": "int32", "dist": "normal", "seed": 419, "scale": 1000 } },
83
+ "outputs": { "y": { "shape": [512], "dtype": "uint32" } },
84
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
85
+ },
86
+ {
87
+ "name": "argmax-lastaxis-int8-2048x4096-serial",
88
+ "preset": "stress",
89
+ "vars": { "dtype": "int8", "rows": 2048, "cols": 4096 },
90
+ "attrs": { "axis": 1, "keepdims": 0 },
91
+ "inputs": { "x": { "shape": [2048, 4096], "dtype": "int8", "dist": "normal", "seed": 831, "scale": 50 } },
92
+ "outputs": { "y": { "shape": [2048], "dtype": "uint32" } },
93
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
94
+ },
95
+ {
96
+ "name": "argmax-axis0-narrow-starvation-131072x8",
97
+ "preset": "smoke",
98
+ "vars": { "dtype": "float32", "rows": 131072, "cols": 8 },
99
+ "attrs": { "axis": 0, "keepdims": 0 },
100
+ "inputs": { "x": { "shape": [131072, 8], "dtype": "float32", "dist": "normal", "seed": 823, "scale": 1 } },
101
+ "outputs": { "y": { "shape": [8], "dtype": "uint32" } },
102
+ "bench": {
103
+ "primary": true,
104
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }]
105
+ }
106
+ },
107
+ {
108
+ "name": "argmax-temporal-rank3-axis1-starvation-1x65536x8",
109
+ "preset": "stress",
110
+ "vars": { "dtype": "float32", "frames": 65536, "classes": 8 },
111
+ "attrs": { "axis": 1, "keepdims": 0 },
112
+ "inputs": { "x": { "shape": [1, 65536, 8], "dtype": "float32", "dist": "normal", "seed": 829, "scale": 1 } },
113
+ "outputs": { "y": { "shape": [1, 8], "dtype": "uint32" } },
114
+ "bench": {
115
+ "primary": true,
116
+ "metrics": [{ "type": "bandwidth", "value": "args.frames * args.classes * dtypeBytes(args.dtype)" }]
117
+ }
118
+ }
119
+ ],
120
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] }
121
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,519 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "ArgMax",
4
+ "sinceVersion": 13,
5
+ "description": "Returns the index of the maximum value along an axis, choosing the first equal value unless `select_last_index` is enabled.",
6
+ "inputs": [{ "role": "data", "dtype": "T", "description": "Values whose maximum index is selected along `axis`." }],
7
+ "outputs": [
8
+ {
9
+ "role": "reduced",
10
+ "dtype": "I",
11
+ "rank": "ranks.data if attrs.keepdims == 1 else ranks.data - 1",
12
+ "description": "Logical int64 indices of the maximum values along the reduced axis; WebGPU stores these bounded indices as uint32.",
13
+ "shape": "prefix(shapes.data, normalizedAxis) + ([1] if attrs.keepdims == 1 else []) + suffix(shapes.data, normalizedAxis + 1)"
14
+ }
15
+ ],
16
+ "attributes": { "axis": 0, "keepdims": 1, "select_last_index": 0 },
17
+ "attributeDescriptions": {
18
+ "axis": "Axis to reduce; negative values count from the back.",
19
+ "keepdims": "Retain the reduced dimension with length one when non-zero.",
20
+ "select_last_index": "Choose the last equal maximum instead of the first when non-zero."
21
+ },
22
+ "attributeConstraints": { "keepdims": { "values": [0, 1] }, "select_last_index": { "values": [0, 1] } },
23
+ "typeConstraints": { "T": ["float32", "float16", "int32", "uint32", "int16", "int8", "uint8"], "I": ["int64"] },
24
+ "args": {
25
+ "x": { "kind": "tensor", "semantic": "data", "role": "input" },
26
+ "y": { "kind": "tensor", "semantic": "reduced", "role": "output", "dtype": "uint32" }
27
+ },
28
+ "tunables": {
29
+ "WORKGROUP_SIZE": 256,
30
+ "TILE_COLUMNS": 16,
31
+ "TILED_MIN_AXIS": 64,
32
+ "TILED_MIN_OUTPUTS": 16,
33
+ "SPLIT_MIN_AXIS": 8192,
34
+ "SPLIT_MAX_OUTPUTS": 4096,
35
+ "SPLIT_TARGET_AXIS": 256,
36
+ "SPLIT_TILE_COLUMNS": 8,
37
+ "MAX_SPLITS": 128,
38
+ "ROW_SPLIT_MIN_AXIS": 32768,
39
+ "ROW_SPLIT_MAX_OUTPUTS": 32,
40
+ "ROW_SPLIT_TARGET_AXIS": 2048,
41
+ "ROW_SPLIT_MAX_SPLITS": 64
42
+ },
43
+ "derive": {
44
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
45
+ "foldedDispatchCapacity": "device.limits.maxComputeWorkgroupsPerDimension * device.limits.maxComputeWorkgroupsPerDimension",
46
+ "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
47
+ "normalizedAxis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.data",
48
+ "axisInRange": "ranks.data >= 1 and normalizedAxis >= 0 and normalizedAxis < ranks.data",
49
+ "axisDim": "dim(shapes.data, normalizedAxis)",
50
+ "axisInner": "inner(shapes.data, normalizedAxis)",
51
+ "outputCount": "numel(shapes.reduced)",
52
+ "outputRankOk": "(attrs.keepdims == 1 and ranks.reduced == ranks.data) or (attrs.keepdims == 0 and ranks.reduced == ranks.data - 1)",
53
+ "outputShapeOk": "outputRankOk and outputCount * axisDim == numel(shapes.data)",
54
+ "baseContract": "axisInRange and axisDim > 0 and outputShapeOk and f16Ok(dtypes.T)",
55
+ "lastAxis": "normalizedAxis == ranks.data - 1",
56
+ "nativeParallelType": "dtypes.T == \"f32\" or dtypes.T == \"i32\" or dtypes.T == \"u32\"",
57
+ "rowParallelType": "nativeParallelType or dtypes.T == \"f16\"",
58
+ "workgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
59
+ "narrowLargeAxisTilePreferred": "narrowSubgroupRange and outputCount >= workgroupSize",
60
+ "threadDispatchFits": "ceilDiv(outputCount, workgroupSize) <= foldedDispatchCapacity",
61
+ "rowDispatchFits": "outputCount <= foldedDispatchCapacity",
62
+ "rowStorageFits": "workgroupSize * 8 <= device.limits.maxComputeWorkgroupStorageSize",
63
+ "parallelRowWorthwhile": "axisDim >= 256 or outputCount < 32768",
64
+ "splitCount": "min(tunables.MAX_SPLITS, pow2ceil(ceilDiv(axisDim, tunables.SPLIT_TARGET_AXIS)))",
65
+ "splitScratchBytes": "splitCount * outputCount * 8",
66
+ "splitScratchFits": "splitScratchBytes <= device.limits.maxStorageBufferBindingSize and splitScratchBytes <= device.limits.maxBufferSize",
67
+ "splitDispatchFits": "splitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(outputCount, workgroupSize), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension",
68
+ "splitTileDispatchFits": "splitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(outputCount, tunables.SPLIT_TILE_COLUMNS) <= device.limits.maxComputeWorkgroupsPerDimension",
69
+ "rowSplitCount": "min(tunables.ROW_SPLIT_MAX_SPLITS, pow2ceil(ceilDiv(axisDim, tunables.ROW_SPLIT_TARGET_AXIS)))",
70
+ "rowSplitScratchBytes": "rowSplitCount * outputCount * 8",
71
+ "rowSplitScratchFits": "rowSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and rowSplitScratchBytes <= device.limits.maxBufferSize",
72
+ "rowSplitDispatchFits": "outputCount <= device.limits.maxComputeWorkgroupsPerDimension and rowSplitCount <= device.limits.maxComputeWorkgroupsPerDimension",
73
+ "tileDispatchFits": "ceilDiv(outputCount, tunables.TILE_COLUMNS) <= foldedDispatchCapacity"
74
+ },
75
+ "bindingSets": {
76
+ "contiguousVec4": [
77
+ {
78
+ "name": "x",
79
+ "arg": "x",
80
+ "semantic": "data",
81
+ "buffer": { "type": "read-only-storage" },
82
+ "elementType": "$vectorScalar"
83
+ },
84
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "u32" },
85
+ {
86
+ "name": "params",
87
+ "semantic": "kernel.params",
88
+ "buffer": { "type": "uniform" },
89
+ "struct": {
90
+ "name": "Params",
91
+ "fields": [
92
+ { "name": "rows", "type": "u32", "value": "outputCount" },
93
+ { "name": "chunkCount", "type": "u32", "value": "axisDim / 4" },
94
+ {
95
+ "name": "rowStride",
96
+ "type": "u32",
97
+ "value": "max(1, min(outputCount, device.limits.maxComputeWorkgroupsPerDimension))"
98
+ }
99
+ ]
100
+ }
101
+ }
102
+ ],
103
+ "contiguousScalar": [
104
+ {
105
+ "name": "x",
106
+ "arg": "x",
107
+ "semantic": "data",
108
+ "buffer": { "type": "read-only-storage" },
109
+ "elementType": "$scalar"
110
+ },
111
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "u32" },
112
+ {
113
+ "name": "params",
114
+ "semantic": "kernel.params",
115
+ "buffer": { "type": "uniform" },
116
+ "struct": {
117
+ "name": "Params",
118
+ "fields": [
119
+ { "name": "rows", "type": "u32", "value": "outputCount" },
120
+ { "name": "chunkCount", "type": "u32", "value": "axisDim" },
121
+ {
122
+ "name": "rowStride",
123
+ "type": "u32",
124
+ "value": "max(1, min(outputCount, device.limits.maxComputeWorkgroupsPerDimension))"
125
+ }
126
+ ]
127
+ }
128
+ }
129
+ ],
130
+ "axisGeometry": [
131
+ {
132
+ "name": "x",
133
+ "arg": "x",
134
+ "semantic": "data",
135
+ "buffer": { "type": "read-only-storage" },
136
+ "elementType": "$scalar"
137
+ },
138
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "u32" },
139
+ {
140
+ "name": "params",
141
+ "semantic": "kernel.params",
142
+ "buffer": { "type": "uniform" },
143
+ "struct": {
144
+ "name": "Params",
145
+ "fields": [
146
+ { "name": "axisDim", "type": "u32", "value": "axisDim" },
147
+ { "name": "outputCount", "type": "u32", "value": "outputCount" },
148
+ { "name": "innerSize", "type": "u32", "value": "axisInner" }
149
+ ]
150
+ }
151
+ }
152
+ ],
153
+ "rowSplitVec4": [
154
+ {
155
+ "name": "x",
156
+ "arg": "x",
157
+ "semantic": "data",
158
+ "buffer": { "type": "read-only-storage" },
159
+ "elementType": "$vectorScalar"
160
+ },
161
+ { "name": "partials_val", "semantic": "partials_val", "buffer": { "type": "storage" }, "elementType": "u32" },
162
+ { "name": "partials_idx", "semantic": "partials_idx", "buffer": { "type": "storage" }, "elementType": "u32" },
163
+ {
164
+ "name": "params",
165
+ "semantic": "kernel.params",
166
+ "buffer": { "type": "uniform" },
167
+ "struct": {
168
+ "name": "Params",
169
+ "fields": [
170
+ { "name": "rows", "type": "u32", "value": "outputCount" },
171
+ { "name": "chunkCount", "type": "u32", "value": "axisDim / 4" }
172
+ ]
173
+ }
174
+ }
175
+ ],
176
+ "rowSplitScalar": [
177
+ {
178
+ "name": "x",
179
+ "arg": "x",
180
+ "semantic": "data",
181
+ "buffer": { "type": "read-only-storage" },
182
+ "elementType": "$scalar"
183
+ },
184
+ { "name": "partials_val", "semantic": "partials_val", "buffer": { "type": "storage" }, "elementType": "u32" },
185
+ { "name": "partials_idx", "semantic": "partials_idx", "buffer": { "type": "storage" }, "elementType": "u32" },
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": "outputCount" },
194
+ { "name": "chunkCount", "type": "u32", "value": "axisDim" }
195
+ ]
196
+ }
197
+ }
198
+ ],
199
+ "splitReduce": [
200
+ {
201
+ "name": "x",
202
+ "arg": "x",
203
+ "semantic": "data",
204
+ "buffer": { "type": "read-only-storage" },
205
+ "elementType": "$scalar"
206
+ },
207
+ { "name": "partials_val", "semantic": "partials_val", "buffer": { "type": "storage" }, "elementType": "u32" },
208
+ { "name": "partials_idx", "semantic": "partials_idx", "buffer": { "type": "storage" }, "elementType": "u32" },
209
+ {
210
+ "name": "params",
211
+ "semantic": "kernel.params",
212
+ "buffer": { "type": "uniform" },
213
+ "struct": {
214
+ "name": "Params",
215
+ "fields": [
216
+ { "name": "axisDim", "type": "u32", "value": "axisDim" },
217
+ { "name": "outputCount", "type": "u32", "value": "outputCount" },
218
+ { "name": "innerSize", "type": "u32", "value": "axisInner" }
219
+ ]
220
+ }
221
+ }
222
+ ],
223
+ "splitCombine": [
224
+ {
225
+ "name": "partials_val",
226
+ "semantic": "partials_val",
227
+ "buffer": { "type": "read-only-storage" },
228
+ "elementType": "u32"
229
+ },
230
+ {
231
+ "name": "partials_idx",
232
+ "semantic": "partials_idx",
233
+ "buffer": { "type": "read-only-storage" },
234
+ "elementType": "u32"
235
+ },
236
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "u32" },
237
+ {
238
+ "name": "params",
239
+ "semantic": "kernel.params",
240
+ "buffer": { "type": "uniform" },
241
+ "struct": { "name": "Params", "fields": [{ "name": "outputCount", "type": "u32", "value": "outputCount" }] }
242
+ }
243
+ ]
244
+ },
245
+ "variants": [
246
+ {
247
+ "id": "last_axis_split_vec4",
248
+ "priority": 41,
249
+ "when": ["baseContract", "rowParallelType", "lastAxis", "axisDim >= tunables.ROW_SPLIT_MIN_AXIS", "axisDim % 4 == 0", "outputCount <= tunables.ROW_SPLIT_MAX_OUTPUTS", "rowSplitScratchFits", "rowSplitDispatchFits", "rowStorageFits"],
250
+ "constants": {
251
+ "scalar": "dtypes.T",
252
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
253
+ "usesF16": "dtypes.T == \"f16\"",
254
+ "selectLastIndex": "attrs.select_last_index != 0",
255
+ "workgroupSize": "workgroupSize",
256
+ "split": "rowSplitCount"
257
+ },
258
+ "intermediates": [
259
+ { "id": "partials_val", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" },
260
+ { "id": "partials_idx", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" }
261
+ ],
262
+ "passes": [
263
+ {
264
+ "id": "split_reduce",
265
+ "name": "ArgMax.LastAxisSplitVec4Reduce",
266
+ "source": {
267
+ "shader": "reduce-arg-row-split.wgsl.jinja",
268
+ "inputs": { "mode": "\"max\"", "vec4": true, "useSubgroups": "device.features.has(\"subgroups\")" }
269
+ },
270
+ "bindings": "rowSplitVec4",
271
+ "dispatch": { "x": "outputCount", "y": "rowSplitCount" }
272
+ },
273
+ {
274
+ "id": "combine",
275
+ "name": "ArgMax.LastAxisSplitCombine",
276
+ "source": { "shader": "reduce-arg-axis-split-combine.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
277
+ "bindings": "splitCombine",
278
+ "dispatch": { "threads": "outputCount", "workgroupSize": "workgroupSize" }
279
+ }
280
+ ]
281
+ },
282
+ {
283
+ "id": "last_axis_split",
284
+ "priority": 41,
285
+ "when": ["baseContract", "rowParallelType", "lastAxis", "axisDim >= tunables.ROW_SPLIT_MIN_AXIS", "axisDim % 4 != 0", "outputCount <= tunables.ROW_SPLIT_MAX_OUTPUTS", "rowSplitScratchFits", "rowSplitDispatchFits", "rowStorageFits"],
286
+ "constants": {
287
+ "scalar": "dtypes.T",
288
+ "usesF16": "dtypes.T == \"f16\"",
289
+ "selectLastIndex": "attrs.select_last_index != 0",
290
+ "workgroupSize": "workgroupSize",
291
+ "split": "rowSplitCount"
292
+ },
293
+ "intermediates": [
294
+ { "id": "partials_val", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" },
295
+ { "id": "partials_idx", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" }
296
+ ],
297
+ "passes": [
298
+ {
299
+ "id": "split_reduce",
300
+ "name": "ArgMax.LastAxisSplitReduce",
301
+ "source": {
302
+ "shader": "reduce-arg-row-split.wgsl.jinja",
303
+ "inputs": { "mode": "\"max\"", "vec4": false, "useSubgroups": "device.features.has(\"subgroups\")" }
304
+ },
305
+ "bindings": "rowSplitScalar",
306
+ "dispatch": { "x": "outputCount", "y": "rowSplitCount" }
307
+ },
308
+ {
309
+ "id": "combine",
310
+ "name": "ArgMax.LastAxisSplitCombine",
311
+ "source": { "shader": "reduce-arg-axis-split-combine.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
312
+ "bindings": "splitCombine",
313
+ "dispatch": { "threads": "outputCount", "workgroupSize": "workgroupSize" }
314
+ }
315
+ ]
316
+ },
317
+ {
318
+ "id": "axis_split_tiled_narrow",
319
+ "priority": 31,
320
+ "when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.SPLIT_MIN_AXIS", "outputCount >= tunables.SPLIT_TILE_COLUMNS", "outputCount <= 2 * tunables.SPLIT_TILE_COLUMNS", "workgroupSize % tunables.SPLIT_TILE_COLUMNS == 0", "splitScratchFits", "splitTileDispatchFits"],
321
+ "constants": {
322
+ "scalar": "dtypes.T",
323
+ "selectLastIndex": "attrs.select_last_index != 0",
324
+ "workgroupSize": "workgroupSize",
325
+ "split": "splitCount",
326
+ "tileCols": "tunables.SPLIT_TILE_COLUMNS"
327
+ },
328
+ "intermediates": [
329
+ { "id": "partials_val", "dtype": "uint32", "shape": "[splitCount * outputCount]" },
330
+ { "id": "partials_idx", "dtype": "uint32", "shape": "[splitCount * outputCount]" }
331
+ ],
332
+ "passes": [
333
+ {
334
+ "id": "split_reduce",
335
+ "name": "ArgMax.AxisSplitTiledReduce",
336
+ "source": { "shader": "reduce-arg-axis-split-tiled.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
337
+ "bindings": "splitReduce",
338
+ "dispatch": { "x": "ceilDiv(outputCount, tunables.SPLIT_TILE_COLUMNS)", "y": "splitCount" }
339
+ },
340
+ {
341
+ "id": "combine",
342
+ "name": "ArgMax.AxisSplitCombine",
343
+ "source": { "shader": "reduce-arg-axis-split-combine.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
344
+ "bindings": "splitCombine",
345
+ "dispatch": { "threads": "outputCount", "workgroupSize": "workgroupSize" }
346
+ }
347
+ ]
348
+ },
349
+ {
350
+ "id": "subgroup_min_last_axis_vec4",
351
+ "priority": 41,
352
+ "requires": { "features": ["subgroups"] },
353
+ "when": ["baseContract", "rowParallelType", "lastAxis", "axisDim >= 4", "axisDim % 4 == 0", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize >= 4", "device.adapterInfo.subgroupMinSize <= device.adapterInfo.subgroupMaxSize", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeInvocationsPerWorkgroup", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeWorkgroupSizeX", "axisDim >= device.adapterInfo.subgroupMinSize * 4", "ceilDiv(axisDim, 4) <= device.adapterInfo.subgroupMinSize * 16", "outputCount >= device.adapterInfo.subgroupMinSize * 4", "rowDispatchFits"],
354
+ "constants": {
355
+ "scalar": "dtypes.T",
356
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
357
+ "usesF16": "dtypes.T == \"f16\"",
358
+ "selectLastIndex": "attrs.select_last_index != 0",
359
+ "workgroupSize": "device.adapterInfo.subgroupMinSize"
360
+ },
361
+ "passes": [
362
+ {
363
+ "id": "main",
364
+ "name": "ArgMax.SubgroupMinLastAxisVec4",
365
+ "source": {
366
+ "shader": "reduce-arg-row-subgroup.wgsl.jinja",
367
+ "inputs": { "mode": "\"max\"", "vec4": true, "singleSubgroup": true }
368
+ },
369
+ "bindings": "contiguousVec4",
370
+ "dispatch": { "workgroups": "outputCount" }
371
+ }
372
+ ]
373
+ },
374
+ {
375
+ "id": "subgroup_min_last_axis",
376
+ "priority": 41,
377
+ "requires": { "features": ["subgroups"] },
378
+ "when": ["baseContract", "rowParallelType", "lastAxis", "axisDim % 4 != 0", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize >= 4", "device.adapterInfo.subgroupMinSize <= device.adapterInfo.subgroupMaxSize", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeInvocationsPerWorkgroup", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeWorkgroupSizeX", "axisDim >= device.adapterInfo.subgroupMinSize * 4", "axisDim <= device.adapterInfo.subgroupMinSize * 64", "outputCount >= device.adapterInfo.subgroupMinSize * 4", "rowDispatchFits"],
379
+ "constants": {
380
+ "scalar": "dtypes.T",
381
+ "usesF16": "dtypes.T == \"f16\"",
382
+ "selectLastIndex": "attrs.select_last_index != 0",
383
+ "workgroupSize": "device.adapterInfo.subgroupMinSize"
384
+ },
385
+ "passes": [
386
+ {
387
+ "id": "main",
388
+ "name": "ArgMax.SubgroupMinLastAxis",
389
+ "source": {
390
+ "shader": "reduce-arg-row-subgroup.wgsl.jinja",
391
+ "inputs": { "mode": "\"max\"", "vec4": false, "singleSubgroup": true }
392
+ },
393
+ "bindings": "contiguousScalar",
394
+ "dispatch": { "workgroups": "outputCount" }
395
+ }
396
+ ]
397
+ },
398
+ {
399
+ "id": "last_axis_vec4",
400
+ "priority": 40,
401
+ "when": ["baseContract", "rowParallelType", "lastAxis", "axisDim >= 4", "axisDim % 4 == 0", "parallelRowWorthwhile", "rowDispatchFits", "rowStorageFits"],
402
+ "constants": {
403
+ "scalar": "dtypes.T",
404
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
405
+ "usesF16": "dtypes.T == \"f16\"",
406
+ "selectLastIndex": "attrs.select_last_index != 0",
407
+ "workgroupSize": "min(workgroupSize, max(32, pow2ceil(ceilDiv(axisDim, 4))))"
408
+ },
409
+ "passes": [
410
+ {
411
+ "id": "main",
412
+ "name": "ArgMax.LastAxisVec4",
413
+ "source": {
414
+ "shader": "reduce-arg-row-subgroup.wgsl.jinja",
415
+ "inputs": { "mode": "\"max\"", "vec4": true, "useSubgroups": "device.features.has(\"subgroups\")" }
416
+ },
417
+ "bindings": "contiguousVec4",
418
+ "dispatch": { "workgroups": "outputCount" }
419
+ }
420
+ ]
421
+ },
422
+ {
423
+ "id": "last_axis",
424
+ "priority": 39,
425
+ "when": ["baseContract", "rowParallelType", "lastAxis", "axisDim % 4 != 0", "parallelRowWorthwhile", "rowDispatchFits", "rowStorageFits"],
426
+ "constants": {
427
+ "scalar": "dtypes.T",
428
+ "usesF16": "dtypes.T == \"f16\"",
429
+ "selectLastIndex": "attrs.select_last_index != 0",
430
+ "workgroupSize": "min(workgroupSize, max(32, pow2ceil(axisDim)))"
431
+ },
432
+ "passes": [
433
+ {
434
+ "id": "main",
435
+ "name": "ArgMax.LastAxis",
436
+ "source": {
437
+ "shader": "reduce-arg-row-subgroup.wgsl.jinja",
438
+ "inputs": { "mode": "\"max\"", "vec4": false, "useSubgroups": "device.features.has(\"subgroups\")" }
439
+ },
440
+ "bindings": "contiguousScalar",
441
+ "dispatch": { "workgroups": "outputCount" }
442
+ }
443
+ ]
444
+ },
445
+ {
446
+ "id": "axis_split",
447
+ "priority": 30,
448
+ "demoteWhen": ["narrowLargeAxisTilePreferred"],
449
+ "when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.SPLIT_MIN_AXIS", "outputCount >= 1", "outputCount <= tunables.SPLIT_MAX_OUTPUTS", "splitScratchFits", "splitDispatchFits", "threadDispatchFits"],
450
+ "constants": {
451
+ "scalar": "dtypes.T",
452
+ "selectLastIndex": "attrs.select_last_index != 0",
453
+ "workgroupSize": "workgroupSize",
454
+ "split": "splitCount"
455
+ },
456
+ "intermediates": [
457
+ { "id": "partials_val", "dtype": "uint32", "shape": "[splitCount * outputCount]" },
458
+ { "id": "partials_idx", "dtype": "uint32", "shape": "[splitCount * outputCount]" }
459
+ ],
460
+ "passes": [
461
+ {
462
+ "id": "split_reduce",
463
+ "name": "ArgMax.AxisSplitReduce",
464
+ "source": { "shader": "reduce-arg-axis-split-reduce.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
465
+ "bindings": "splitReduce",
466
+ "dispatch": { "threads": "outputCount", "workgroupSize": "workgroupSize", "y": "splitCount" }
467
+ },
468
+ {
469
+ "id": "combine",
470
+ "name": "ArgMax.AxisSplitCombine",
471
+ "source": { "shader": "reduce-arg-axis-split-combine.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
472
+ "bindings": "splitCombine",
473
+ "dispatch": { "threads": "outputCount", "workgroupSize": "workgroupSize" }
474
+ }
475
+ ]
476
+ },
477
+ {
478
+ "id": "axis_tiled",
479
+ "priority": 20,
480
+ "when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.TILED_MIN_AXIS", "outputCount >= tunables.TILED_MIN_OUTPUTS", "workgroupSize % tunables.TILE_COLUMNS == 0", "rowStorageFits", "tileDispatchFits"],
481
+ "constants": {
482
+ "scalar": "dtypes.T",
483
+ "selectLastIndex": "attrs.select_last_index != 0",
484
+ "workgroupSize": "workgroupSize",
485
+ "tileCols": "tunables.TILE_COLUMNS"
486
+ },
487
+ "passes": [
488
+ {
489
+ "id": "main",
490
+ "name": "ArgMax.AxisTiled",
491
+ "source": { "shader": "reduce-arg-axis-tiled.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
492
+ "bindings": "axisGeometry",
493
+ "dispatch": { "workgroups": "ceilDiv((outputCount), (tunables.TILE_COLUMNS))" }
494
+ }
495
+ ]
496
+ },
497
+ {
498
+ "id": "axis_serial",
499
+ "priority": 0,
500
+ "supersededBy": ["axis_tiled"],
501
+ "when": ["baseContract", "threadDispatchFits"],
502
+ "constants": {
503
+ "scalar": "dtypes.T",
504
+ "usesF16": "dtypes.T == \"f16\"",
505
+ "selectLastIndex": "attrs.select_last_index != 0",
506
+ "workgroupSize": "workgroupSize"
507
+ },
508
+ "passes": [
509
+ {
510
+ "id": "main",
511
+ "name": "ArgMax.AxisSerial",
512
+ "source": { "shader": "reduce-arg-axis.wgsl.jinja", "inputs": { "mode": "\"max\"" } },
513
+ "bindings": "axisGeometry",
514
+ "dispatch": { "threads": "outputCount", "workgroupSize": "workgroupSize" }
515
+ }
516
+ ]
517
+ }
518
+ ]
519
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.ArgMax",
3
+ "id": "_ai_onnx_argmax_webgpu_97b7564",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "eUX0SrelUEHbrkOOtyZUmXYTcd70fLBI6oiQOQj3Uyg=",
11
+ "manifest.json": "csrYFYXbC3J/cmgTiTUK5e78NJOJceeiFXvyUe2ZAHI=",
12
+ "reduce-arg-axis-split-combine.wgsl.jinja": "rdvAqkyd5MkxXLWQtVzpTxqqKwANhk2L1h/SMW9P2qU=",
13
+ "reduce-arg-axis-split-reduce.wgsl.jinja": "VC4pBZyx0kVu5zmny2clKWdMU2D6HWpM/qiym+b664o=",
14
+ "reduce-arg-axis-split-tiled.wgsl.jinja": "/CtqlF3QH7OIQi9q0WzSOJC6K/KDUzcH4sDHdrcGTLg=",
15
+ "reduce-arg-axis-tiled.wgsl.jinja": "382jqrusVAtzqbRnn0b++oLtQK53ezOx203+9kyWs34=",
16
+ "reduce-arg-axis.wgsl.jinja": "LzoRz8NbCeL+4FA0A6K/5xmPw6jr1n3rqnq3h8lXXFs=",
17
+ "reduce-arg-row-split.wgsl.jinja": "w5ycl8KmqAXgOJSClyPwdHpvD9scRtewwYVn9g++A7o=",
18
+ "reduce-arg-row-subgroup.wgsl.jinja": "0OjqIIUwZiSWMJ/Uu6uJem+mFZrECLPJctBSQZR+IKc=",
19
+ "test.json": "gcD6r3sktg3/lFyaKAnQC4krQaZJDHYlXhQ4WHiw3EU="
20
+ }
21
+ },
22
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
23
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.ArgMax" }
24
+ }
build/webgpu/reduce-arg-axis-split-combine.wgsl.jinja ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 2 of the split-axis arg reduction folds SPLIT (best value, best index)
2
+ // partials and emits the winning reduced-axis index. One thread handles each
3
+ // flattened output. The reduce pass bit-stored each partial value in u32
4
+ // scratch, so this pass restores the comparison type: floats compare in f32,
5
+ // while i32 and u32 compare natively.
6
+ //
7
+ // The fold compares (value, index), never segment order. Equal values keep the
8
+ // smaller index for first-index mode and the larger index for last-index mode.
9
+ // Empty segments carry SENTINEL_IDX and are skipped.
10
+ {% set cmp = ">" if source.mode == "max" else "<" %}
11
+ {% set is_int = scalar == "i32" or scalar == "u32" %}
12
+ {% set work = scalar if is_int else "f32" %}
13
+ {{ env.wgsl.resourceDeclarations }}
14
+
15
+ const WG: u32 = {{ workgroupSize }}u;
16
+ const SPLIT: u32 = {{ split }}u;
17
+ const SENTINEL_IDX: u32 = 4294967295u;
18
+
19
+ @compute @workgroup_size(WG, 1, 1)
20
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
21
+ @builtin(num_workgroups) nwg: vec3<u32>) {
22
+ let output_index = gid.x + gid.y * nwg.x * WG;
23
+ if (output_index >= params.outputCount) {
24
+ return;
25
+ }
26
+ {% if scalar == "u32" %}
27
+ var out_val = partials_val[output_index];
28
+ {% else %}
29
+ var out_val = bitcast<{{ work }}>(partials_val[output_index]);
30
+ {% endif %}
31
+ var out_idx = partials_idx[output_index];
32
+ for (var split_index = 1u; split_index < SPLIT; split_index++) {
33
+ {% if scalar == "u32" %}
34
+ let candidate_val = partials_val[split_index * params.outputCount + output_index];
35
+ {% else %}
36
+ let candidate_val = bitcast<{{ work }}>(partials_val[split_index * params.outputCount + output_index]);
37
+ {% endif %}
38
+ let candidate_idx = partials_idx[split_index * params.outputCount + output_index];
39
+ {% if selectLastIndex %}
40
+ if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx > out_idx))) {
41
+ {% else %}
42
+ if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx < out_idx))) {
43
+ {% endif %}
44
+ out_val = candidate_val;
45
+ out_idx = candidate_idx;
46
+ }
47
+ }
48
+ y[output_index] = select(out_idx, 0u, out_idx == SENTINEL_IDX);
49
+ }
build/webgpu/reduce-arg-axis-split-reduce.wgsl.jinja ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 1 of a rank-independent split-axis ArgMax/ArgMin reduction. The tensor
2
+ // is flattened as (outer, axis, inner). Each thread scans one contiguous segment
3
+ // of the reduced axis for one output position.
4
+ //
5
+ // Partial values are bit-stored in u32 scratch, so f32, i32, and u32 share one
6
+ // layout without losing integer precision. The combine pass restores the native
7
+ // comparison type. Equal values keep the lowest index by default or the highest
8
+ // index when select_last_index is enabled.
9
+ {% set cmp = ">" if source.mode == "max" else "<" %}
10
+ {% set is_int = scalar == "i32" or scalar == "u32" %}
11
+ {% set work = scalar if is_int else "f32" %}
12
+ {{ env.wgsl.resourceDeclarations }}
13
+
14
+ const WG: u32 = {{ workgroupSize }}u;
15
+ const SPLIT: u32 = {{ split }}u;
16
+ const SENTINEL_IDX: u32 = 4294967295u;
17
+
18
+ // The scan identity: the most-losing value for the mode, in the compare type.
19
+ // The float branch bitcasts through a runtime `var` so the +/-inf bit pattern
20
+ // is not folded into an unrepresentable infinity literal.
21
+ fn lane_identity() -> {{ work }} {
22
+ {% if scalar == "i32" %}
23
+ return bitcast<i32>({{ "0x80000000u" if source.mode == "max" else "0x7fffffffu" }});
24
+ {% elif scalar == "u32" %}
25
+ return {{ "0u" if source.mode == "max" else "0xffffffffu" }};
26
+ {% else %}
27
+ var bits = {{ "0xff800000u" if source.mode == "max" else "0x7f800000u" }};
28
+ return bitcast<f32>(bits);
29
+ {% endif %}
30
+ }
31
+
32
+ @compute @workgroup_size(WG, 1, 1)
33
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
34
+ @builtin(workgroup_id) wg: vec3<u32>,
35
+ @builtin(num_workgroups) nwg: vec3<u32>) {
36
+ // 2D-folded column index: wg.z carries the high bits past the maxComputeWorkgroupsPerDimension
37
+ // workgroup-per-dimension dispatch limit on the x dimension.
38
+ let output_index = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
39
+ let split_index = wg.y;
40
+ if (output_index >= params.outputCount) { return; }
41
+ let outer_index = output_index / params.innerSize;
42
+ let inner_index = output_index % params.innerSize;
43
+ let input_base = outer_index * params.axisDim * params.innerSize + inner_index;
44
+
45
+ // Even split of [0, rows) across SPLIT segments (last segment may be shorter).
46
+ let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
47
+ let axis_begin = split_index * chunk;
48
+ var axis_end = min(axis_begin + chunk, params.axisDim);
49
+
50
+ var best_val = lane_identity();
51
+ var best_idx = SENTINEL_IDX;
52
+ for (var axis_index = axis_begin; axis_index < axis_end; axis_index++) {
53
+ let v = x[input_base + axis_index * params.innerSize];
54
+ {% if selectLastIndex %}
55
+ if (v {{ cmp }} best_val || (v == best_val && (best_idx == SENTINEL_IDX || axis_index > best_idx))) {
56
+ {% else %}
57
+ if (v {{ cmp }} best_val || (v == best_val && axis_index < best_idx)) {
58
+ {% endif %}
59
+ best_val = v;
60
+ best_idx = axis_index;
61
+ }
62
+ }
63
+ {% if scalar == "u32" %}
64
+ partials_val[split_index * params.outputCount + output_index] = best_val;
65
+ {% else %}
66
+ partials_val[split_index * params.outputCount + output_index] = bitcast<u32>(best_val);
67
+ {% endif %}
68
+ partials_idx[split_index * params.outputCount + output_index] = best_idx;
69
+ }
build/webgpu/reduce-arg-axis-split-tiled.wgsl.jinja ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Split-axis arg reduction for a narrow set of output columns. The ordinary
2
+ // split pass assigns one invocation to each output column, leaving most lanes
3
+ // idle when outputCount is small. This route tiles TILE_COLS columns across the
4
+ // workgroup and uses ROW_LANES lanes to cooperatively scan each column. One
5
+ // workgroup handles each (split, column tile), preserving coalesced reads for
6
+ // flattened (outer, axis, inner) tensors.
7
+ //
8
+ // The final fold is intentionally subgroup-free: it is small, deterministic,
9
+ // and preserves identical first/last-index tie semantics on every feature tier.
10
+ {% set cmp = ">" if source.mode == "max" else "<" %}
11
+ {% set is_int = scalar == "i32" or scalar == "u32" %}
12
+ {% set work = scalar if is_int else "f32" %}
13
+ {{ env.wgsl.resourceDeclarations }}
14
+
15
+ const WG: u32 = {{ workgroupSize }}u;
16
+ const SPLIT: u32 = {{ split }}u;
17
+ const TILE_COLS: u32 = {{ tileCols }}u;
18
+ const ROW_LANES: u32 = WG / TILE_COLS;
19
+ const SENTINEL_IDX: u32 = 4294967295u;
20
+
21
+ fn lane_identity() -> {{ work }} {
22
+ {% if scalar == "i32" %}
23
+ return bitcast<i32>({{ "0x80000000u" if source.mode == "max" else "0x7fffffffu" }});
24
+ {% elif scalar == "u32" %}
25
+ return {{ "0u" if source.mode == "max" else "0xffffffffu" }};
26
+ {% else %}
27
+ var bits = {{ "0xff800000u" if source.mode == "max" else "0x7f800000u" }};
28
+ return bitcast<f32>(bits);
29
+ {% endif %}
30
+ }
31
+
32
+ var<workgroup> partial_val: array<{{ work }}, WG>;
33
+ var<workgroup> partial_idx: array<u32, WG>;
34
+
35
+ @compute @workgroup_size(WG, 1, 1)
36
+ fn main(
37
+ @builtin(workgroup_id) wg: vec3<u32>,
38
+ @builtin(local_invocation_id) lid: vec3<u32>
39
+ ) {
40
+ let tid = lid.x;
41
+ let col_lane = tid % TILE_COLS;
42
+ let row_lane = tid / TILE_COLS;
43
+ let output_index = wg.x * TILE_COLS + col_lane;
44
+ let split_index = wg.y;
45
+ let in_bounds = output_index < params.outputCount;
46
+
47
+ let outer_index = output_index / params.innerSize;
48
+ let inner_index = output_index % params.innerSize;
49
+ let input_base = outer_index * params.axisDim * params.innerSize + inner_index;
50
+ let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
51
+ let axis_begin = split_index * chunk;
52
+ let axis_end = min(axis_begin + chunk, params.axisDim);
53
+
54
+ var best_val = lane_identity();
55
+ var best_idx = SENTINEL_IDX;
56
+ if (in_bounds) {
57
+ for (var axis_index = axis_begin + row_lane; axis_index < axis_end; axis_index += ROW_LANES) {
58
+ let value = x[input_base + axis_index * params.innerSize];
59
+ {% if selectLastIndex %}
60
+ if (value {{ cmp }} best_val || (value == best_val && (best_idx == SENTINEL_IDX || axis_index > best_idx))) {
61
+ {% else %}
62
+ if (value {{ cmp }} best_val || (value == best_val && axis_index < best_idx)) {
63
+ {% endif %}
64
+ best_val = value;
65
+ best_idx = axis_index;
66
+ }
67
+ }
68
+ }
69
+ partial_val[tid] = best_val;
70
+ partial_idx[tid] = best_idx;
71
+ workgroupBarrier();
72
+
73
+ if (row_lane == 0u && in_bounds) {
74
+ var out_val = partial_val[col_lane];
75
+ var out_idx = partial_idx[col_lane];
76
+ for (var lane = 1u; lane < ROW_LANES; lane++) {
77
+ let candidate_val = partial_val[lane * TILE_COLS + col_lane];
78
+ let candidate_idx = partial_idx[lane * TILE_COLS + col_lane];
79
+ {% if selectLastIndex %}
80
+ if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx > out_idx))) {
81
+ {% else %}
82
+ if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx < out_idx))) {
83
+ {% endif %}
84
+ out_val = candidate_val;
85
+ out_idx = candidate_idx;
86
+ }
87
+ }
88
+ {% if scalar == "u32" %}
89
+ partials_val[split_index * params.outputCount + output_index] = out_val;
90
+ {% else %}
91
+ partials_val[split_index * params.outputCount + output_index] = bitcast<u32>(out_val);
92
+ {% endif %}
93
+ partials_idx[split_index * params.outputCount + output_index] = out_idx;
94
+ }
95
+ }
build/webgpu/reduce-arg-axis-tiled.wgsl.jinja ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Tiled strided-axis ArgMax/ArgMin reduction. f32 compares directly; i32 and
2
+ // u32 compare natively, so large-magnitude integers keep full precision. The
3
+ // workgroup partials carry the native comparison type without a cross-pass
4
+ // bitcast round trip. Flattening the output as (outer, inner) lets one coalesced
5
+ // column tile handle every non-contiguous axis. f16 uses the serial or
6
+ // contiguous routes because this kernel has no widened-f16 scratch path.
7
+ {% set cmp = ">" if source.mode == "max" else "<" %}
8
+ {% set is_int = scalar == "i32" or scalar == "u32" %}
9
+ {% set work = scalar if is_int else "f32" %}
10
+ {{ env.wgsl.resourceDeclarations }}
11
+
12
+ const WG: u32 = {{ workgroupSize }}u;
13
+ const TILE_COLS: u32 = {{ tileCols }}u;
14
+ const ROW_LANES: u32 = WG / TILE_COLS;
15
+ const SENTINEL_IDX: u32 = 4294967295u;
16
+
17
+ // The scan identity: the most-losing value for the mode, in the compare type.
18
+ // The float branch bitcasts through a runtime `var` so the +/-inf bit pattern
19
+ // is not folded into an unrepresentable infinity literal.
20
+ fn lane_identity() -> {{ work }} {
21
+ {% if scalar == "i32" %}
22
+ return bitcast<i32>({{ "0x80000000u" if source.mode == "max" else "0x7fffffffu" }});
23
+ {% elif scalar == "u32" %}
24
+ return {{ "0u" if source.mode == "max" else "0xffffffffu" }};
25
+ {% else %}
26
+ var bits = {{ "0xff800000u" if source.mode == "max" else "0x7f800000u" }};
27
+ return bitcast<f32>(bits);
28
+ {% endif %}
29
+ }
30
+
31
+ var<workgroup> partial_val: array<{{ work }}, WG>;
32
+ var<workgroup> partial_idx: array<u32, WG>;
33
+
34
+ @compute @workgroup_size(WG, 1, 1)
35
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>,
36
+ @builtin(num_workgroups) nwg: vec3<u32>) {
37
+ let tid = lid.x;
38
+ let col_lane = tid % TILE_COLS;
39
+ let row_lane = tid / TILE_COLS;
40
+ // 2D-folded column-tile index: wg.y carries the high bits past the maxComputeWorkgroupsPerDimension
41
+ // workgroup-per-dimension dispatch limit. Reduces to wg.x when nwg.y == 1;
42
+ // the bounds guard drops the over-dispatched tail.
43
+ let output_index = (wg.x + wg.y * nwg.x) * TILE_COLS + col_lane;
44
+ let in_bounds = output_index < params.outputCount;
45
+ let outer_index = output_index / params.innerSize;
46
+ let inner_index = output_index % params.innerSize;
47
+ let input_base = outer_index * params.axisDim * params.innerSize + inner_index;
48
+
49
+ var best_val = lane_identity();
50
+ var best_idx = SENTINEL_IDX;
51
+ if (in_bounds) {
52
+ for (var axis_index = row_lane; axis_index < params.axisDim; axis_index = axis_index + ROW_LANES) {
53
+ let v = x[input_base + axis_index * params.innerSize];
54
+ {% if selectLastIndex %}
55
+ if (v {{ cmp }} best_val || (v == best_val && (best_idx == SENTINEL_IDX || axis_index > best_idx))) {
56
+ {% else %}
57
+ if (v {{ cmp }} best_val || (v == best_val && axis_index < best_idx)) {
58
+ {% endif %}
59
+ best_val = v;
60
+ best_idx = axis_index;
61
+ }
62
+ }
63
+ }
64
+ partial_val[tid] = best_val;
65
+ partial_idx[tid] = best_idx;
66
+ workgroupBarrier();
67
+
68
+ if (row_lane == 0u && in_bounds) {
69
+ var out_val = partial_val[col_lane];
70
+ var out_idx = partial_idx[col_lane];
71
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
72
+ let candidate_val = partial_val[lane * TILE_COLS + col_lane];
73
+ let candidate_idx = partial_idx[lane * TILE_COLS + col_lane];
74
+ {% if selectLastIndex %}
75
+ if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx > out_idx))) {
76
+ {% else %}
77
+ if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx < out_idx))) {
78
+ {% endif %}
79
+ out_val = candidate_val;
80
+ out_idx = candidate_idx;
81
+ }
82
+ }
83
+ y[output_index] = select(out_idx, 0u, out_idx == SENTINEL_IDX);
84
+ }
85
+ }
build/webgpu/reduce-arg-axis.wgsl.jinja ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Rank-independent serial arg reduction for ArgMax and ArgMin. Flattening a
2
+ // tensor around the reduced axis into (outer, axis, inner) gives one
3
+ // rank-independent coordinate calculation. One thread owns one output position
4
+ // and walks the reduced axis at innerSize stride.
5
+ {% set cmp = ">" if source.mode == "max" else "<" %}
6
+ {% if usesF16 %}
7
+ enable f16;
8
+ {%- endif %}
9
+ {{ env.wgsl.resourceDeclarations }}
10
+
11
+ @compute @workgroup_size({{ workgroupSize }})
12
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
13
+ @builtin(num_workgroups) nwg: vec3<u32>) {
14
+ // Fold the output grid into x/y when one dispatch dimension is insufficient.
15
+ let output_index = gid.x + gid.y * nwg.x * {{ workgroupSize }}u;
16
+ if (output_index >= params.outputCount) {
17
+ return;
18
+ }
19
+
20
+ let outer_index = output_index / params.innerSize;
21
+ let inner_index = output_index % params.innerSize;
22
+ let input_base = outer_index * params.axisDim * params.innerSize + inner_index;
23
+
24
+ var best_index = 0u;
25
+ {%- if scalar == "i32" or scalar == "u32" %}
26
+ var best = x[input_base];
27
+ for (var axis_index = 1u; axis_index < params.axisDim; axis_index++) {
28
+ let candidate = x[input_base + axis_index * params.innerSize];
29
+ if (candidate {{ cmp }} best{% if selectLastIndex %} || candidate == best{% endif %}) {
30
+ best = candidate;
31
+ best_index = axis_index;
32
+ }
33
+ }
34
+ {%- else %}
35
+ var best = f32(x[input_base]);
36
+ for (var axis_index = 1u; axis_index < params.axisDim; axis_index++) {
37
+ let candidate = f32(x[input_base + axis_index * params.innerSize]);
38
+ if (candidate {{ cmp }} best{% if selectLastIndex %} || candidate == best{% endif %}) {
39
+ best = candidate;
40
+ best_index = axis_index;
41
+ }
42
+ }
43
+ {%- endif %}
44
+ y[output_index] = best_index;
45
+ }
build/webgpu/reduce-arg-row-split.wgsl.jinja ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% set useSubgroups = source.useSubgroups %}
2
+ {% if usesF16 %}
3
+ enable f16;
4
+ {%- endif %}
5
+ {% if useSubgroups %}
6
+ enable subgroups;
7
+ {%- endif %}
8
+ {{ env.wgsl.resourceDeclarations }}
9
+
10
+ const WG: u32 = {{ workgroupSize }}u;
11
+ const SPLIT: u32 = {{ split }}u;
12
+ const SENTINEL_IDX: u32 = 4294967295u;
13
+ {%- if scalar == "i32" %}
14
+ {% set work = "i32" %}
15
+ {% elif scalar == "u32" %}
16
+ {% set work = "u32" %}
17
+ {% else %}
18
+ {% set work = "f32" %}
19
+ {% endif %}
20
+ {% set cmp = ">" if source.mode == "max" else "<" %}
21
+
22
+ fn lane_identity() -> {{ work }} {
23
+ {%- if scalar == "i32" %}
24
+ return bitcast<i32>({{ "0x80000000u" if source.mode == "max" else "0x7fffffffu" }});
25
+ {%- elif scalar == "u32" %}
26
+ return {{ "0u" if source.mode == "max" else "0xffffffffu" }};
27
+ {%- else %}
28
+ var bits = {{ "0xff800000u" if source.mode == "max" else "0x7f800000u" }};
29
+ return bitcast<f32>(bits);
30
+ {%- endif %}
31
+ }
32
+
33
+ var<workgroup> wg_val: array<{{ work }}, WG>;
34
+ var<workgroup> wg_idx: array<u32, WG>;
35
+
36
+ @compute @workgroup_size(WG, 1, 1)
37
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
38
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
39
+ @builtin(subgroup_invocation_id) subgroup_lane: u32,
40
+ @builtin(subgroup_size) subgroup_size: u32{% endif %}) {
41
+ let row = wg.x;
42
+ let split_index = wg.y;
43
+ let tid = lid.x;
44
+ let segment_size = (params.chunkCount + SPLIT - 1u) / SPLIT;
45
+ let segment_begin = split_index * segment_size;
46
+ let segment_end = min(segment_begin + segment_size, params.chunkCount);
47
+ let base = row * params.chunkCount;
48
+
49
+ var best_val = lane_identity();
50
+ var best_idx = SENTINEL_IDX;
51
+ for (var chunk = segment_begin + tid; chunk < segment_end; chunk += WG) {
52
+ {%- if source.vec4 %}
53
+ let value4 = x[base + chunk];
54
+ {%- for component in ["x", "y", "z", "w"] %}
55
+ {
56
+ {%- if scalar == "f16" %}
57
+ let value = f32(value4.{{ component }});
58
+ {%- else %}
59
+ let value = value4.{{ component }};
60
+ {%- endif %}
61
+ let index = chunk * 4u + {{ loop.index0 }}u;
62
+ {%- if selectLastIndex %}
63
+ if (value {{ cmp }} best_val || (value == best_val && (best_idx == SENTINEL_IDX || index > best_idx))) {
64
+ {%- else %}
65
+ if (value {{ cmp }} best_val || (value == best_val && index < best_idx)) {
66
+ {%- endif %}
67
+ best_val = value;
68
+ best_idx = index;
69
+ }
70
+ }
71
+ {%- endfor %}
72
+ {% else %}
73
+ {% if scalar == "f16" %}
74
+ let value = f32(x[base + chunk]);
75
+ {%- else %}
76
+ let value = x[base + chunk];
77
+ {%- endif %}
78
+ {% if selectLastIndex %}
79
+ if (value {{ cmp }} best_val || (value == best_val && (best_idx == SENTINEL_IDX || chunk > best_idx))) {
80
+ {%- else %}
81
+ if (value {{ cmp }} best_val || (value == best_val && chunk < best_idx)) {
82
+ {%- endif %}
83
+ best_val = value;
84
+ best_idx = chunk;
85
+ }
86
+ {%- endif %}
87
+ }
88
+ {%- if useSubgroups %}
89
+ let subgroup_val = subgroupMax(best_val);{% if selectLastIndex %}
90
+ let subgroup_candidate = select(0u, best_idx, best_val == subgroup_val && best_idx != SENTINEL_IDX);
91
+ let subgroup_idx = subgroupMax(subgroup_candidate);
92
+ {%- else %}
93
+ let subgroup_candidate = select(SENTINEL_IDX, best_idx, best_val == subgroup_val);
94
+ let subgroup_idx = subgroupMin(subgroup_candidate);
95
+ {%- endif %}
96
+ let safe_subgroup_size = max(subgroup_size, 1u);
97
+ // The final subgroup may be partial on devices whose native subgroup size
98
+ // does not divide WG. Merge that leader as well.
99
+ let subgroup_count = max(1u, (WG + safe_subgroup_size - 1u) / safe_subgroup_size);
100
+ if (subgroup_lane == 0u) {
101
+ let slot = min(tid / safe_subgroup_size, WG - 1u);
102
+ wg_val[slot] = subgroup_val;
103
+ wg_idx[slot] = subgroup_idx;
104
+ }
105
+ workgroupBarrier();
106
+ if (tid == 0u) {
107
+ var output_val = wg_val[0];
108
+ var output_idx = wg_idx[0];
109
+ for (var slot = 1u; slot < subgroup_count; slot++) {
110
+ let candidate_val = wg_val[slot];
111
+ let candidate_idx = wg_idx[slot];
112
+ {%- if selectLastIndex %}
113
+ if (candidate_idx != SENTINEL_IDX && (output_idx == SENTINEL_IDX || candidate_val {{ cmp }} output_val || (candidate_val == output_val && candidate_idx > output_idx))) {
114
+ {%- else %}
115
+ if (candidate_idx != SENTINEL_IDX && (output_idx == SENTINEL_IDX || candidate_val {{ cmp }} output_val || (candidate_val == output_val && candidate_idx < output_idx))) {
116
+ {%- endif %}
117
+ output_val = candidate_val;
118
+ output_idx = candidate_idx;
119
+ }
120
+ }
121
+ let scratch_index = split_index * params.rows + row;
122
+ {%- if scalar == "u32" %}
123
+ partials_val[scratch_index] = output_val;
124
+ {%- else %}
125
+ partials_val[scratch_index] = bitcast<u32>(output_val);
126
+ {%- endif %}
127
+ partials_idx[scratch_index] = output_idx;
128
+ }
129
+ {%- else %}
130
+ wg_val[tid] = best_val;
131
+ wg_idx[tid] = best_idx;
132
+ workgroupBarrier();
133
+ for (var stride = WG / 2u; stride > 0u; stride >>= 1u) {
134
+ if (tid < stride) {
135
+ let candidate_val = wg_val[tid + stride];
136
+ let candidate_idx = wg_idx[tid + stride];
137
+ let current_idx = wg_idx[tid];
138
+ {%- if selectLastIndex %}
139
+ if (candidate_idx != SENTINEL_IDX && (current_idx == SENTINEL_IDX || candidate_val {{ cmp }} wg_val[tid] || (candidate_val == wg_val[tid] && candidate_idx > current_idx))) {
140
+ {%- else %}
141
+ if (candidate_idx != SENTINEL_IDX && (current_idx == SENTINEL_IDX || candidate_val {{ cmp }} wg_val[tid] || (candidate_val == wg_val[tid] && candidate_idx < current_idx))) {
142
+ {%- endif %}
143
+ wg_val[tid] = candidate_val;
144
+ wg_idx[tid] = candidate_idx;
145
+ }
146
+ }
147
+ workgroupBarrier();
148
+ }
149
+ if (tid == 0u) {
150
+ let scratch_index = split_index * params.rows + row;
151
+ {%- if scalar == "u32" %}
152
+ partials_val[scratch_index] = wg_val[0];
153
+ {%- else %}
154
+ partials_val[scratch_index] = bitcast<u32>(wg_val[0]);
155
+ {%- endif %}
156
+ partials_idx[scratch_index] = wg_idx[0];
157
+ }
158
+ {%- endif %}
159
+ }
build/webgpu/reduce-arg-row-subgroup.wgsl.jinja ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Subgroup (value, index) row reduction for ArgMax and ArgMin over a contiguous
2
+ // last axis, with one workgroup per output row. Each thread scans a strided
3
+ // chunk, carrying a (best value, best index) pair with ONNX tie semantics.
4
+ // Subgroup combination first reduces the value, then reduces the matching
5
+ // lanes' indices. Leaders deposit per-subgroup pairs in shared memory for a
6
+ // final fold. A one-subgroup workgroup writes its collective result directly,
7
+ // avoiding shared memory and the workgroup barrier.
8
+ //
9
+ // Float inputs compare in f32. Sentinels are runtime-bitcast infinities or the
10
+ // integer bounds; ties against the sentinel use the same index rule, so a row
11
+ // filled with the sentinel still produces the correct index. NaNs are never
12
+ // accepted, and an all-NaN row falls back to index zero like the serial scan.
13
+ {% if source.useSubgroups is defined %}{% set useSubgroups = source.useSubgroups %}{% else %}{% set useSubgroups = true %}{% endif %}
14
+ {% if usesF16 %}
15
+ enable f16;
16
+ {%- endif %}
17
+ {% if useSubgroups %}
18
+ enable subgroups;
19
+ {%- endif %}
20
+ {{ env.wgsl.resourceDeclarations }}
21
+
22
+ const WG: u32 = {{ workgroupSize }}u;
23
+ const SENTINEL_IDX: u32 = 4294967295u;
24
+ {% if source.mode == "max" and scalar != "i32" and scalar != "u32" %}
25
+
26
+ fn neg_inf_f32() -> f32 {
27
+ var bits = 0xff800000u;
28
+ return bitcast<f32>(bits);
29
+ }
30
+
31
+ {% endif %}
32
+ {%- if scalar == "i32" %}
33
+ {% set cmpType = "i32" %}
34
+ {% elif scalar == "u32" %}
35
+ {% set cmpType = "u32" %}
36
+ {% else %}
37
+ {% set cmpType = "f32" %}
38
+ {% endif %}
39
+ {% set cmp = ">" %}
40
+ {%- if not (useSubgroups and source.singleSubgroup is defined and source.singleSubgroup) %}
41
+ var<workgroup> wgVal: array<{{ cmpType }}, WG>;
42
+ var<workgroup> wgIdx: array<u32, WG>;
43
+ {%- endif %}
44
+
45
+ @compute @workgroup_size(WG, 1, 1)
46
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
47
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
48
+ @builtin(subgroup_invocation_id) sgLid: u32{% if not (source.singleSubgroup is defined and source.singleSubgroup) %},
49
+ @builtin(subgroup_size) sgSize: u32{% endif %}{% endif %}) {
50
+ let row = wg.x + wg.y * params.rowStride;
51
+ if (row >= params.rows) {
52
+ return;
53
+ }
54
+ let tid = lid.x;
55
+ let base = row * params.chunkCount;{% if cmpType == "i32" %}
56
+ var bestVal: i32 = -2147483647i - 1i;
57
+ {%- elif cmpType == "u32" %}
58
+ var bestVal: u32 = 0u;
59
+ {%- else %}
60
+ var bestVal: f32 = neg_inf_f32();
61
+ {%- endif %}
62
+ var bestIdx: u32 = SENTINEL_IDX;
63
+ {%- if source.vec4 %}
64
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
65
+ let v4 = x[base + c];
66
+ {%- for comp in ["x", "y", "z", "w"] %}
67
+ {
68
+ {%- if scalar == "f16" %}
69
+ let v = f32(v4.{{ comp }});
70
+ {%- else %}
71
+ let v = v4.{{ comp }};
72
+ {%- endif %}
73
+ let idx = c * 4u + {{ loop.index0 }}u;
74
+ {%- if selectLastIndex %}
75
+ if (v {{ cmp }} bestVal || (v == bestVal && (bestIdx == SENTINEL_IDX || idx > bestIdx))) {
76
+ {%- else %}
77
+ if (v {{ cmp }} bestVal || (v == bestVal && idx < bestIdx)) {
78
+ {%- endif %}
79
+ bestVal = v;
80
+ bestIdx = idx;
81
+ }
82
+ }
83
+ {%- endfor %}
84
+ }
85
+ {%- else %}
86
+ // Four independent, lane-coalesced loads per loop iteration. Keeping each
87
+ // unrolled plane WG elements apart preserves contiguous accesses across the
88
+ // workgroup while exposing enough ILP for long odd-width rows (where a vec4
89
+ // storage binding cannot represent row boundaries).
90
+ for (var c0 = tid; c0 < params.chunkCount; c0 = c0 + 4u * WG) {
91
+ {%- for plane in [0, 1, 2, 3] %}
92
+ {
93
+ let c = c0 + {{ plane }}u * WG;
94
+ if (c < params.chunkCount) {
95
+ {%- if scalar == "f16" %}
96
+ let v = f32(x[base + c]);
97
+ {%- else %}
98
+ let v = x[base + c];
99
+ {%- endif %}
100
+ {% if selectLastIndex %}
101
+ if (v {{ cmp }} bestVal || (v == bestVal && (bestIdx == SENTINEL_IDX || c > bestIdx))) {
102
+ {%- else %}
103
+ if (v {{ cmp }} bestVal || (v == bestVal && c < bestIdx)) {
104
+ {%- endif %}
105
+ bestVal = v;
106
+ bestIdx = c;
107
+ }
108
+ }
109
+ }
110
+ {%- endfor %}
111
+ }
112
+ {%- endif %}
113
+ {%- if useSubgroups %}
114
+ let m = subgroupMax(bestVal);{% if selectLastIndex %}
115
+ // Lanes that match the subgroup best contribute their (real) index; the
116
+ // rest contribute 0 so subgroupMax picks the highest matching index.
117
+ let cand = select(0u, bestIdx, bestVal == m && bestIdx != SENTINEL_IDX);
118
+ let sgIdx = subgroupMax(cand);
119
+ {%- else %}
120
+ // Lanes that match the subgroup best contribute their index; the rest
121
+ // contribute the sentinel so subgroupMin picks the lowest matching index.
122
+ let cand = select(SENTINEL_IDX, bestIdx, bestVal == m);
123
+ let sgIdx = subgroupMin(cand);
124
+ {%- endif %}
125
+ {% if source.singleSubgroup is defined and source.singleSubgroup %}
126
+ if (sgLid == 0u) {
127
+ y[row] = select(sgIdx, 0u, sgIdx == SENTINEL_IDX);
128
+ }
129
+ {%- else %}
130
+ let safeSg = max(sgSize, 1u);
131
+ let slotCount = max(1u, (WG + safeSg - 1u) / safeSg);
132
+ if (sgLid == 0u) {
133
+ let slot = min(tid / safeSg, WG - 1u);
134
+ wgVal[slot] = m;
135
+ wgIdx[slot] = sgIdx;
136
+ }
137
+ workgroupBarrier();
138
+ if (tid == 0u) {
139
+ var outVal = wgVal[0];
140
+ var outIdx = wgIdx[0];
141
+ for (var i = 1u; i < slotCount; i = i + 1u) {
142
+ let v = wgVal[i];
143
+ let vi = wgIdx[i];
144
+ {%- if selectLastIndex %}
145
+ if (vi != SENTINEL_IDX && (v {{ cmp }} outVal || (v == outVal && (outIdx == SENTINEL_IDX || vi > outIdx)))) {
146
+ {%- else %}
147
+ if (vi != SENTINEL_IDX && (v {{ cmp }} outVal || (v == outVal && vi < outIdx))) {
148
+ {%- endif %}
149
+ outVal = v;
150
+ outIdx = vi;
151
+ }
152
+ }
153
+ y[row] = select(outIdx, 0u, outIdx == SENTINEL_IDX);
154
+ }
155
+ {%- endif %}
156
+ {% else %}
157
+ // No-subgroup tier: workgroup barrier tree-reduction over the per-thread
158
+ // (bestVal, bestIdx) pairs. Same ONNX tie rule (lowest index by default,
159
+ // highest with select_last_index); SENTINEL_IDX lanes (short rows, WG >
160
+ // chunkCount) never win. WG is a power of two,
161
+ // so the halving tree visits every lane.
162
+ wgVal[tid] = bestVal;
163
+ wgIdx[tid] = bestIdx;
164
+ workgroupBarrier();
165
+ for (var stride = WG / 2u; stride > 0u; stride = stride >> 1u) {
166
+ if (tid < stride) {
167
+ let v = wgVal[tid + stride];
168
+ let vi = wgIdx[tid + stride];
169
+ let curIdx = wgIdx[tid];
170
+ {%- if selectLastIndex %}
171
+ if (vi != SENTINEL_IDX && (curIdx == SENTINEL_IDX || v {{ cmp }} wgVal[tid] || (v == wgVal[tid] && vi > curIdx))) {
172
+ {%- else %}
173
+ if (vi != SENTINEL_IDX && (curIdx == SENTINEL_IDX || v {{ cmp }} wgVal[tid] || (v == wgVal[tid] && vi < curIdx))) {
174
+ {%- endif %}
175
+ wgVal[tid] = v;
176
+ wgIdx[tid] = vi;
177
+ }
178
+ }
179
+ workgroupBarrier();
180
+ }
181
+ if (tid == 0u) {
182
+ y[row] = select(wgIdx[0], 0u, wgIdx[0] == SENTINEL_IDX);
183
+ }
184
+ {%- endif %}
185
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ArgMax",
3
+ "fixtureArrays": {
4
+ "onnx_backend_argmax_input_x": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564, 1.3608912229537964, 8.511932373046875, -8.579278945922852, -8.257413864135742, -9.59563159942627, 6.6523966789245605, 5.563135147094727, 7.400242805480957, 9.572366714477539, 5.983171463012695, -0.7704127430915833, 5.610583305358887],
5
+ "axis0_splitk_8192x32_ties_first_index_input_x": [1, 3, 3, 2, 1, 3, 3, 2, 1, 3, 3, 2, 1, 3, 3, 2, 1, 3, 3, 2, 1, 3, 3, 2, 1, 3, 3, 2, 1, 3, 3, 2, 1],
6
+ "subgroup_vec4_ties_first_index_input_x": [1, 5, 5, 2, 5, 0, 1, 0, 7, 7, 7, 7, 7, 7, 7, 7],
7
+ "rank4_axis0_f32_no_keepdims_input_x": [5, 1, 9, 2, 0, 7, 3, 4, 1, 6, 9, 8, 2, 7, 3, 1],
8
+ "axis0_splitk_i32_8192x16_precision_max_input_x": [16777216, 16777216, 16777216, 16777216, 16777216, 16777216, 16777216, 16777216, 16777217, 16777217, 16777217, 16777217, 16777217, 16777217, 16777217, 16777217, 16777217]
9
+ },
10
+ "cases": [
11
+ {
12
+ "name": "int16_last_axis_vec4_boundaries",
13
+ "attrs": { "axis": -1, "keepdims": 0 },
14
+ "inputs": {
15
+ "x": {
16
+ "dtype": "int16",
17
+ "shape": [2, 4],
18
+ "data": { "kind": "values", "values": [-32768, -1, 32767, 0, 32767, -32768, 32767, 1] }
19
+ }
20
+ },
21
+ "outputs": {
22
+ "y": { "dtype": "uint32", "shape": [2], "tolerance": 0, "data": { "kind": "values", "values": [2, 0] } }
23
+ }
24
+ },
25
+ {
26
+ "name": "onnx_default_axis0_omitted",
27
+ "provenance": {
28
+ "source": "https://onnx.ai/onnx/operators/onnx__ArgMax.html",
29
+ "test": "ArgMax axis default",
30
+ "notes": "Omitting axis must reduce the leading dimension (ONNX default 0), not axis 1. The rank-2 output shape and per-column indices distinguish the two semantics. ONNX int64 indices use the framework's representable uint32 slot."
31
+ },
32
+ "inputs": {
33
+ "x": {
34
+ "dtype": "float32",
35
+ "shape": [2, 3],
36
+ "data": { "kind": "values", "values": [1.0, 9.0, 3.0, 4.0, 2.0, 8.0] }
37
+ }
38
+ },
39
+ "outputs": {
40
+ "y": { "dtype": "uint32", "shape": [1, 3], "tolerance": 0, "data": { "kind": "values", "values": [1, 0, 1] } }
41
+ }
42
+ },
43
+ {
44
+ "name": "f32_positive_subnormal_beats_zero_gpu_gap",
45
+ "skipGpu": {
46
+ "category": "permanent",
47
+ "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."
48
+ },
49
+ "provenance": {
50
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
51
+ "test": "ArgMax",
52
+ "notes": "A positive subnormal is strictly greater than zero; index selection must not treat it as a tie with zero."
53
+ },
54
+ "attrs": { "axis": 1, "keepdims": 0 },
55
+ "inputs": {
56
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40] } }
57
+ },
58
+ "outputs": { "y": { "dtype": "uint32", "shape": [1], "tolerance": 0 } }
59
+ },
60
+ {
61
+ "name": "f32_positive_subnormal_beats_zero_select_last_gpu_gap",
62
+ "skipGpu": {
63
+ "category": "permanent",
64
+ "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."
65
+ },
66
+ "provenance": {
67
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
68
+ "test": "ArgMax2D_select_last",
69
+ "notes": "select_last_index must only apply to true ties; a positive subnormal at index 0 is greater than following zeros."
70
+ },
71
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
72
+ "inputs": {
73
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [1e-40, 0.0, 0.0] } }
74
+ },
75
+ "outputs": {
76
+ "y": { "dtype": "uint32", "shape": [1], "tolerance": 0, "data": { "kind": "values", "values": [0] } }
77
+ }
78
+ },
79
+ {
80
+ "name": "f32_positive_subnormal_beats_zero_axis0_gpu_gap",
81
+ "skipGpu": {
82
+ "category": "permanent",
83
+ "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."
84
+ },
85
+ "provenance": {
86
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
87
+ "test": "ArgMax",
88
+ "notes": "Axis-0 companion for finite subnormal ordering: a positive subnormal is strictly greater than zero and must not be tie-broken as flushed zero."
89
+ },
90
+ "attrs": { "axis": 0, "keepdims": 0 },
91
+ "inputs": {
92
+ "x": { "dtype": "float32", "shape": [3, 1], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40] } }
93
+ },
94
+ "outputs": { "y": { "dtype": "uint32", "shape": [1], "tolerance": 0 } }
95
+ },
96
+ {
97
+ "name": "dispatch_cliff_axis1_dim1_over_16M",
98
+ "attrs": { "axis": 1, "keepdims": 0 },
99
+ "inputs": { "x": { "dtype": "float32", "shape": [16776961, 1], "data": { "kind": "constant", "value": 1.0 } } },
100
+ "outputs": { "y": { "dtype": "uint32", "shape": [16776961], "tolerance": 0 } }
101
+ },
102
+ {
103
+ "name": "axis0",
104
+ "attrs": { "axis": 0, "keepdims": 0 },
105
+ "inputs": {
106
+ "x": {
107
+ "dtype": "float32",
108
+ "shape": [3, 4],
109
+ "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] }
110
+ }
111
+ },
112
+ "outputs": { "y": { "dtype": "uint32", "shape": [4] } }
113
+ },
114
+ {
115
+ "name": "axis0_splitk_8192x32_ties_first_index",
116
+ "attrs": { "axis": 0, "keepdims": 0 },
117
+ "inputs": {
118
+ "x": {
119
+ "dtype": "float32",
120
+ "shape": [8192, 32],
121
+ "data": {
122
+ "kind": "cycle",
123
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_8192x32_ties_first_index_input_x" }
124
+ }
125
+ }
126
+ },
127
+ "outputs": { "y": { "dtype": "uint32", "shape": [32], "tolerance": 0 } }
128
+ },
129
+ {
130
+ "name": "axis0_splitk_8192x32_ties_select_last_index",
131
+ "attrs": { "axis": 0, "keepdims": 0, "select_last_index": 1 },
132
+ "inputs": {
133
+ "x": {
134
+ "dtype": "float32",
135
+ "shape": [8192, 32],
136
+ "data": {
137
+ "kind": "cycle",
138
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_8192x32_ties_first_index_input_x" }
139
+ }
140
+ }
141
+ },
142
+ "outputs": { "y": { "dtype": "uint32", "shape": [32], "tolerance": 0 } }
143
+ },
144
+ {
145
+ "name": "axis0_splitk_8192x48_keepdims",
146
+ "attrs": { "axis": 0, "keepdims": 1 },
147
+ "inputs": {
148
+ "x": {
149
+ "dtype": "float32",
150
+ "shape": [8192, 48],
151
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.2 }
152
+ }
153
+ },
154
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 48], "tolerance": 0 } }
155
+ },
156
+ {
157
+ "name": "axis0_tiled_64x32_ties_first_index",
158
+ "attrs": { "axis": 0, "keepdims": 0 },
159
+ "inputs": {
160
+ "x": {
161
+ "dtype": "float32",
162
+ "shape": [64, 32],
163
+ "data": {
164
+ "kind": "cycle",
165
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_8192x32_ties_first_index_input_x" }
166
+ }
167
+ }
168
+ },
169
+ "outputs": { "y": { "dtype": "uint32", "shape": [32], "tolerance": 0 } }
170
+ },
171
+ {
172
+ "name": "axis0_tiled_64x32_ties_select_last_index",
173
+ "attrs": { "axis": 0, "keepdims": 0, "select_last_index": 1 },
174
+ "inputs": {
175
+ "x": {
176
+ "dtype": "float32",
177
+ "shape": [64, 32],
178
+ "data": {
179
+ "kind": "cycle",
180
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_8192x32_ties_first_index_input_x" }
181
+ }
182
+ }
183
+ },
184
+ "outputs": { "y": { "dtype": "uint32", "shape": [32], "tolerance": 0 } }
185
+ },
186
+ {
187
+ "name": "axis1_tie_first_index",
188
+ "attrs": { "axis": 1, "keepdims": 0 },
189
+ "inputs": {
190
+ "x": {
191
+ "dtype": "float32",
192
+ "shape": [2, 5],
193
+ "data": { "kind": "values", "values": [1.0, 3.0, 3.0, 2.0, 3.0, -1.0, -1.0, -2.0, -1.0, -3.0] }
194
+ }
195
+ },
196
+ "outputs": { "y": { "dtype": "uint32", "shape": [2] } }
197
+ },
198
+ {
199
+ "name": "ort_axis1_nan_first_incumbent_gpu_gap",
200
+ "skipGpu": {
201
+ "category": "todo",
202
+ "reason": "The parallel min/max reduction routes do not yet preserve the reference's first-element NaN incumbent semantics; explicit NaN and index tracking is implementable in WGSL."
203
+ },
204
+ "provenance": {
205
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
206
+ "test": "ReductionOpTest.ArgMax",
207
+ "notes": "NaN extension: ORT seeds ArgMax from the first reduced element, so a leading NaN remains the selected incumbent while later NaNs are ignored after a finite incumbent."
208
+ },
209
+ "attrs": { "axis": 1, "keepdims": 0 },
210
+ "inputs": {
211
+ "x": {
212
+ "dtype": "float32",
213
+ "shape": [4, 4],
214
+ "data": {
215
+ "kind": "values",
216
+ "values": ["NaN", 1.0, 2.0, 0.0, 1.0, "NaN", 2.0, 0.0, 1.0, 2.0, "NaN", 0.0, "NaN", "NaN", "NaN", "NaN"]
217
+ }
218
+ }
219
+ },
220
+ "outputs": { "y": { "dtype": "uint32", "shape": [4], "tolerance": 0 } }
221
+ },
222
+ {
223
+ "name": "ort_axis1_nan_select_last_index_gpu_gap",
224
+ "skipGpu": {
225
+ "category": "todo",
226
+ "reason": "The parallel min/max reduction routes do not yet preserve the reference's first-element NaN incumbent semantics; explicit NaN and index tracking is implementable in WGSL."
227
+ },
228
+ "provenance": {
229
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
230
+ "test": "ReductionOpTest.ArgMax_int32_last_index_dups",
231
+ "notes": "NaN extension: select_last_index only changes equal finite ties. A leading NaN remains the incumbent because finite values are not greater than NaN."
232
+ },
233
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
234
+ "inputs": {
235
+ "x": {
236
+ "dtype": "float32",
237
+ "shape": [4, 4],
238
+ "data": {
239
+ "kind": "values",
240
+ "values": ["NaN", 1.0, 2.0, 0.0, 1.0, "NaN", 3.0, 3.0, -1.0, -2.0, "NaN", -3.0, "NaN", "NaN", "NaN", "NaN"]
241
+ }
242
+ }
243
+ },
244
+ "outputs": {
245
+ "y": { "dtype": "uint32", "shape": [4], "data": { "kind": "values", "values": [0, 3, 0, 0] }, "tolerance": 0 }
246
+ }
247
+ },
248
+ {
249
+ "name": "axis1_f16",
250
+ "attrs": { "axis": 1, "keepdims": 0 },
251
+ "inputs": {
252
+ "x": {
253
+ "dtype": "float16",
254
+ "shape": [3, 4],
255
+ "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] }
256
+ }
257
+ },
258
+ "outputs": { "y": { "dtype": "uint32", "shape": [3] } }
259
+ },
260
+ {
261
+ "name": "axis1_select_last_index_ties",
262
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
263
+ "inputs": {
264
+ "x": {
265
+ "dtype": "float32",
266
+ "shape": [2, 5],
267
+ "data": { "kind": "values", "values": [1.0, 3.0, 3.0, 2.0, 3.0, -1.0, -1.0, -2.0, -1.0, -3.0] }
268
+ }
269
+ },
270
+ "outputs": { "y": { "dtype": "uint32", "shape": [2] } }
271
+ },
272
+ {
273
+ "name": "axis1_select_last_all_negative_infinity",
274
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
275
+ "inputs": {
276
+ "x": {
277
+ "dtype": "float32",
278
+ "shape": [2, 4],
279
+ "data": {
280
+ "kind": "values",
281
+ "values": ["-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", -5.0, "-Infinity", -5.0]
282
+ }
283
+ }
284
+ },
285
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "tolerance": 0 } }
286
+ },
287
+ {
288
+ "name": "axis_minus_one",
289
+ "attrs": { "axis": -1, "keepdims": 0 },
290
+ "inputs": {
291
+ "x": {
292
+ "dtype": "float32",
293
+ "shape": [2, 4],
294
+ "data": { "kind": "values", "values": [1.0, 2.0, 9.0, 4.0, 5.0, 8.0, 7.0, 6.0] }
295
+ }
296
+ },
297
+ "outputs": { "y": { "dtype": "uint32", "shape": [2] } }
298
+ },
299
+ {
300
+ "name": "axis1_keepdims",
301
+ "attrs": { "axis": 1, "keepdims": 1 },
302
+ "inputs": {
303
+ "x": {
304
+ "dtype": "float32",
305
+ "shape": [2, 4],
306
+ "data": { "kind": "values", "values": [1.0, 7.0, 7.0, 3.0, -2.0, -2.0, -5.0, -2.0] }
307
+ }
308
+ },
309
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 1] } }
310
+ },
311
+ {
312
+ "name": "rank3_axis2_keepdims",
313
+ "attrs": { "axis": 2, "keepdims": 1 },
314
+ "inputs": {
315
+ "x": {
316
+ "dtype": "float32",
317
+ "shape": [2, 2, 4],
318
+ "data": {
319
+ "kind": "values",
320
+ "values": [1.0, 5.0, 5.0, 2.0, -1.0, -2.0, -3.0, -4.0, 0.0, 3.0, 2.0, 1.0, 9.0, 8.0, 9.0, 7.0]
321
+ }
322
+ }
323
+ },
324
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 2, 1], "tolerance": 0 } }
325
+ },
326
+ {
327
+ "name": "rank4_axis1_channel_select_last_ties",
328
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
329
+ "inputs": {
330
+ "x": {
331
+ "dtype": "float32",
332
+ "shape": [1, 3, 2, 2],
333
+ "data": { "kind": "values", "values": [1.0, 5.0, 3.0, 5.0, 3.0, 4.0, -1.0, 7.0, 3.0, 7.0, 0.0, 7.0] }
334
+ }
335
+ },
336
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 2, 2], "tolerance": 0 } }
337
+ },
338
+ {
339
+ "name": "rank4_axis2_spatial_select_last_ties",
340
+ "attrs": { "axis": 2, "keepdims": 0, "select_last_index": 1 },
341
+ "inputs": {
342
+ "x": {
343
+ "dtype": "float32",
344
+ "shape": [1, 2, 3, 2],
345
+ "data": { "kind": "values", "values": [1.0, 4.0, 3.0, 4.0, 3.0, 2.0, -1.0, 5.0, 0.0, 5.0, 0.0, 3.0] }
346
+ }
347
+ },
348
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 2, 2], "tolerance": 0 } }
349
+ },
350
+ {
351
+ "name": "rank4_last_axis_nhwc_keepdims_select_last",
352
+ "attrs": { "axis": -1, "keepdims": 1, "select_last_index": 1 },
353
+ "inputs": {
354
+ "x": {
355
+ "dtype": "float32",
356
+ "shape": [1, 2, 2, 3],
357
+ "data": { "kind": "values", "values": [1.0, 3.0, 3.0, 5.0, 4.0, 7.0, 3.0, 3.0, 0.0, 5.0, 7.0, 7.0] }
358
+ }
359
+ },
360
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 2, 2, 1], "tolerance": 0 } }
361
+ },
362
+ {
363
+ "name": "rank1_axis0_scalar_output_select_last",
364
+ "attrs": { "axis": 0, "keepdims": 0, "select_last_index": 1 },
365
+ "inputs": {
366
+ "x": { "dtype": "float32", "shape": [5], "data": { "kind": "values", "values": [-1.0, 7.0, 3.0, 7.0, 2.0] } }
367
+ },
368
+ "outputs": { "y": { "dtype": "uint32", "shape": [], "tolerance": 0 } }
369
+ },
370
+ {
371
+ "name": "ort_int8_axis1_keepdims",
372
+ "provenance": {
373
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
374
+ "test": "ReductionOpTest.ArgMax_int8"
375
+ },
376
+ "attrs": { "axis": 1, "keepdims": 1 },
377
+ "inputs": {
378
+ "x": {
379
+ "dtype": "int8",
380
+ "shape": [3, 2, 2],
381
+ "data": { "kind": "values", "values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] }
382
+ }
383
+ },
384
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1, 2], "tolerance": 0 } }
385
+ },
386
+ {
387
+ "name": "ort_uint8_axis1_keepdims",
388
+ "provenance": {
389
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
390
+ "test": "ReductionOpTest.ArgMax_uint8"
391
+ },
392
+ "attrs": { "axis": 1, "keepdims": 1 },
393
+ "inputs": {
394
+ "x": {
395
+ "dtype": "uint8",
396
+ "shape": [3, 2, 2],
397
+ "data": { "kind": "values", "values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] }
398
+ }
399
+ },
400
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1, 2], "tolerance": 0 } }
401
+ },
402
+ {
403
+ "name": "ort_int32_axis1_keepdims",
404
+ "provenance": {
405
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
406
+ "test": "ReductionOpTest.ArgMax_int32",
407
+ "notes": "Native int32 ArgMax fixture from ORT; output indices use this framework's uint32 convention."
408
+ },
409
+ "attrs": { "axis": 1, "keepdims": 1 },
410
+ "inputs": {
411
+ "x": {
412
+ "dtype": "int32",
413
+ "shape": [3, 2, 2],
414
+ "data": { "kind": "values", "values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] }
415
+ }
416
+ },
417
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1, 2], "tolerance": 0 } }
418
+ },
419
+ {
420
+ "name": "ort_int32_axis1_keepdims_select_last_nodups",
421
+ "provenance": {
422
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
423
+ "test": "ReductionOpTest.ArgMax_int32_last_index_nodups",
424
+ "notes": "ORT select_last_index path where no ties are present; should match the regular max positions."
425
+ },
426
+ "attrs": { "axis": 1, "keepdims": 1, "select_last_index": 1 },
427
+ "inputs": {
428
+ "x": {
429
+ "dtype": "int32",
430
+ "shape": [3, 2, 2],
431
+ "data": { "kind": "values", "values": [2, 2, 3, 4, 5, 6, 7, 8, 10, 10, 11, 12] }
432
+ }
433
+ },
434
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1, 2], "tolerance": 0 } }
435
+ },
436
+ {
437
+ "name": "ort_float_first_index_infinity_deterministic",
438
+ "provenance": {
439
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
440
+ "test": "ReductionOpTest.ArgMax_float_first_index_random",
441
+ "notes": "Deterministic compact projection of ORT's random infinity-sentinel first-index test."
442
+ },
443
+ "attrs": { "axis": 0, "keepdims": 1, "select_last_index": 0 },
444
+ "inputs": {
445
+ "x": {
446
+ "dtype": "float32",
447
+ "shape": [16],
448
+ "data": {
449
+ "kind": "values",
450
+ "values": [0.0, 1.0, "Infinity", 5.0, "Infinity", -3.0, 2.0, "Infinity", 4.0, -1.0, 3.0, "Infinity", 0.0, 2.0, 1.0, 0.0]
451
+ }
452
+ }
453
+ },
454
+ "outputs": { "y": { "dtype": "uint32", "shape": [1], "tolerance": 0 } }
455
+ },
456
+ {
457
+ "name": "ort_int32_axis1_keepdims_select_last_dups",
458
+ "provenance": {
459
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
460
+ "test": "ReductionOpTest.ArgMax_int32_last_index_dups"
461
+ },
462
+ "attrs": { "axis": 1, "keepdims": 1, "select_last_index": 1 },
463
+ "inputs": {
464
+ "x": {
465
+ "dtype": "int32",
466
+ "shape": [3, 2, 2],
467
+ "data": { "kind": "values", "values": [2, 4, 3, 4, 8, 6, 7, 8, 9, 13, 11, 12] }
468
+ }
469
+ },
470
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1, 2], "tolerance": 0 } }
471
+ },
472
+ {
473
+ "name": "ort_axis1_no_keepdims_rank3_f32",
474
+ "provenance": {
475
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
476
+ "test": "ReductionOpTest.ArgMax_do_not_keepdims"
477
+ },
478
+ "attrs": { "axis": 1, "keepdims": 0 },
479
+ "inputs": {
480
+ "x": {
481
+ "dtype": "float32",
482
+ "shape": [3, 2, 2],
483
+ "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] }
484
+ }
485
+ },
486
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 2], "tolerance": 0 } }
487
+ },
488
+ {
489
+ "name": "ort_axis0_no_keepdims_rank1_scalar_f32",
490
+ "provenance": {
491
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
492
+ "test": "ReductionOpTest.ArgMax_do_not_keepdims_2"
493
+ },
494
+ "attrs": { "axis": 0, "keepdims": 0 },
495
+ "inputs": { "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, 2.0, 3.0] } } },
496
+ "outputs": { "y": { "dtype": "uint32", "shape": [], "tolerance": 0 } }
497
+ },
498
+ {
499
+ "name": "ort_negative_axis_int32_keepdims",
500
+ "provenance": {
501
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
502
+ "test": "ReductionOpTest.ArgMax_int32_neg_axis"
503
+ },
504
+ "attrs": { "axis": -2, "keepdims": 1 },
505
+ "inputs": {
506
+ "x": {
507
+ "dtype": "int32",
508
+ "shape": [3, 2, 2],
509
+ "data": { "kind": "values", "values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] }
510
+ }
511
+ },
512
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1, 2], "tolerance": 0 } }
513
+ },
514
+ {
515
+ "name": "ort_axis1_keepdims_rank3_f32",
516
+ "provenance": {
517
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
518
+ "test": "ReductionOpTest.ArgMax"
519
+ },
520
+ "attrs": { "axis": 1, "keepdims": 1 },
521
+ "inputs": {
522
+ "x": {
523
+ "dtype": "float32",
524
+ "shape": [3, 2, 2],
525
+ "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] }
526
+ }
527
+ },
528
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1, 2], "tolerance": 0 } }
529
+ },
530
+ {
531
+ "name": "ort_axis1_keepdims_rank2_f32",
532
+ "provenance": {
533
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
534
+ "test": "ReductionOpTest.ArgMax2D"
535
+ },
536
+ "attrs": { "axis": 1, "keepdims": 1 },
537
+ "inputs": {
538
+ "x": {
539
+ "dtype": "float32",
540
+ "shape": [3, 2],
541
+ "data": { "kind": "values", "values": [1.0, 2.0, 6.0, 5.0, 9.0, 10.0] }
542
+ }
543
+ },
544
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1], "tolerance": 0 } }
545
+ },
546
+ {
547
+ "name": "ort_axis1_keepdims_rank2_select_last_f32",
548
+ "provenance": {
549
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
550
+ "test": "ReductionOpTest.ArgMax2D_select_last"
551
+ },
552
+ "attrs": { "axis": 1, "keepdims": 1, "select_last_index": 1 },
553
+ "inputs": {
554
+ "x": {
555
+ "dtype": "float32",
556
+ "shape": [3, 2],
557
+ "data": { "kind": "values", "values": [1.0, 2.0, 6.0, 5.0, 9.0, 10.0] }
558
+ }
559
+ },
560
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1], "tolerance": 0 } }
561
+ },
562
+ {
563
+ "name": "ort_axis1_dim1_rank2_f32",
564
+ "provenance": {
565
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
566
+ "test": "ReductionOpTest.ArgMax2D_dim1"
567
+ },
568
+ "attrs": { "axis": 1, "keepdims": 1 },
569
+ "inputs": {
570
+ "x": { "dtype": "float32", "shape": [3, 1], "data": { "kind": "values", "values": [1.0, 6.0, 9.0] } }
571
+ },
572
+ "outputs": { "y": { "dtype": "uint32", "shape": [3, 1], "tolerance": 0 } }
573
+ },
574
+ {
575
+ "name": "int32_axis1_exact_above_float24",
576
+ "attrs": { "axis": 1, "keepdims": 0 },
577
+ "inputs": {
578
+ "x": {
579
+ "dtype": "int32",
580
+ "shape": [2, 4],
581
+ "data": { "kind": "values", "values": [16777216, 16777217, 5, -1, 123456788, 123456789, 0, -7] }
582
+ }
583
+ },
584
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "tolerance": 0 } }
585
+ },
586
+ {
587
+ "name": "onnx_backend_argmax_default_axis_example",
588
+ "provenance": {
589
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_default_axis_example",
590
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
591
+ },
592
+ "attrs": { "keepdims": 1, "axis": 0 },
593
+ "inputs": {
594
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
595
+ },
596
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 2], "tolerance": 0 } }
597
+ },
598
+ {
599
+ "name": "onnx_backend_argmax_default_axis_example_select_last_index",
600
+ "provenance": {
601
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_default_axis_example_select_last_index",
602
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
603
+ },
604
+ "attrs": { "keepdims": 1, "select_last_index": 1, "axis": 0 },
605
+ "inputs": {
606
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
607
+ },
608
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 2], "tolerance": 0 } }
609
+ },
610
+ {
611
+ "name": "onnx_backend_argmax_default_axis_random",
612
+ "provenance": {
613
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_default_axis_random",
614
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
615
+ },
616
+ "attrs": { "keepdims": 1, "axis": 0 },
617
+ "inputs": {
618
+ "x": {
619
+ "dtype": "float32",
620
+ "shape": [2, 3, 4],
621
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
622
+ }
623
+ },
624
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 3, 4], "tolerance": 0 } }
625
+ },
626
+ {
627
+ "name": "onnx_backend_argmax_default_axis_random_select_last_index",
628
+ "provenance": {
629
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_default_axis_random_select_last_index",
630
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
631
+ },
632
+ "attrs": { "keepdims": 1, "select_last_index": 1, "axis": 0 },
633
+ "inputs": {
634
+ "x": {
635
+ "dtype": "float32",
636
+ "shape": [2, 3, 4],
637
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
638
+ }
639
+ },
640
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 3, 4], "tolerance": 0 } }
641
+ },
642
+ {
643
+ "name": "onnx_backend_argmax_keepdims_example",
644
+ "provenance": {
645
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_keepdims_example",
646
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
647
+ },
648
+ "attrs": { "axis": 1, "keepdims": 1 },
649
+ "inputs": {
650
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
651
+ },
652
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 1], "tolerance": 0 } }
653
+ },
654
+ {
655
+ "name": "onnx_backend_argmax_keepdims_example_select_last_index",
656
+ "provenance": {
657
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_keepdims_example_select_last_index",
658
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
659
+ },
660
+ "attrs": { "axis": 1, "keepdims": 1, "select_last_index": 1 },
661
+ "inputs": {
662
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
663
+ },
664
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 1], "tolerance": 0 } }
665
+ },
666
+ {
667
+ "name": "onnx_backend_argmax_keepdims_random",
668
+ "provenance": {
669
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_keepdims_random",
670
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
671
+ },
672
+ "attrs": { "axis": 1, "keepdims": 1 },
673
+ "inputs": {
674
+ "x": {
675
+ "dtype": "float32",
676
+ "shape": [2, 3, 4],
677
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
678
+ }
679
+ },
680
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 1, 4], "tolerance": 0 } }
681
+ },
682
+ {
683
+ "name": "onnx_backend_argmax_keepdims_random_select_last_index",
684
+ "provenance": {
685
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_keepdims_random_select_last_index",
686
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
687
+ },
688
+ "attrs": { "axis": 1, "keepdims": 1, "select_last_index": 1 },
689
+ "inputs": {
690
+ "x": {
691
+ "dtype": "float32",
692
+ "shape": [2, 3, 4],
693
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
694
+ }
695
+ },
696
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 1, 4], "tolerance": 0 } }
697
+ },
698
+ {
699
+ "name": "onnx_backend_argmax_negative_axis_keepdims_example",
700
+ "provenance": {
701
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_negative_axis_keepdims_example",
702
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
703
+ },
704
+ "attrs": { "axis": -1, "keepdims": 1 },
705
+ "inputs": {
706
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
707
+ },
708
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 1], "tolerance": 0 } }
709
+ },
710
+ {
711
+ "name": "onnx_backend_argmax_negative_axis_keepdims_example_select_last_index",
712
+ "provenance": {
713
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_negative_axis_keepdims_example_select_last_index",
714
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
715
+ },
716
+ "attrs": { "axis": -1, "keepdims": 1, "select_last_index": 1 },
717
+ "inputs": {
718
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
719
+ },
720
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 1], "tolerance": 0 } }
721
+ },
722
+ {
723
+ "name": "onnx_backend_argmax_negative_axis_keepdims_random",
724
+ "provenance": {
725
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_negative_axis_keepdims_random",
726
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
727
+ },
728
+ "attrs": { "axis": -1, "keepdims": 1 },
729
+ "inputs": {
730
+ "x": {
731
+ "dtype": "float32",
732
+ "shape": [2, 3, 4],
733
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
734
+ }
735
+ },
736
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 3, 1], "tolerance": 0 } }
737
+ },
738
+ {
739
+ "name": "onnx_backend_argmax_negative_axis_keepdims_random_select_last_index",
740
+ "provenance": {
741
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_negative_axis_keepdims_random_select_last_index",
742
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
743
+ },
744
+ "attrs": { "axis": -1, "keepdims": 1, "select_last_index": 1 },
745
+ "inputs": {
746
+ "x": {
747
+ "dtype": "float32",
748
+ "shape": [2, 3, 4],
749
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
750
+ }
751
+ },
752
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 3, 1], "tolerance": 0 } }
753
+ },
754
+ {
755
+ "name": "onnx_backend_argmax_no_keepdims_example",
756
+ "provenance": {
757
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_no_keepdims_example",
758
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
759
+ },
760
+ "attrs": { "axis": 1, "keepdims": 0 },
761
+ "inputs": {
762
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
763
+ },
764
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "tolerance": 0 } }
765
+ },
766
+ {
767
+ "name": "onnx_backend_argmax_no_keepdims_example_select_last_index",
768
+ "provenance": {
769
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_no_keepdims_example_select_last_index",
770
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
771
+ },
772
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
773
+ "inputs": {
774
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 10.0] } }
775
+ },
776
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "tolerance": 0 } }
777
+ },
778
+ {
779
+ "name": "onnx_backend_argmax_no_keepdims_random",
780
+ "provenance": {
781
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_no_keepdims_random",
782
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
783
+ },
784
+ "attrs": { "axis": 1, "keepdims": 0 },
785
+ "inputs": {
786
+ "x": {
787
+ "dtype": "float32",
788
+ "shape": [2, 3, 4],
789
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
790
+ }
791
+ },
792
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 4], "tolerance": 0 } }
793
+ },
794
+ {
795
+ "name": "onnx_backend_argmax_no_keepdims_random_select_last_index",
796
+ "provenance": {
797
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_argmax_no_keepdims_random_select_last_index",
798
+ "notes": "ONNX arg-reduction outputs are int64; this framework uses uint32 index storage."
799
+ },
800
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
801
+ "inputs": {
802
+ "x": {
803
+ "dtype": "float32",
804
+ "shape": [2, 3, 4],
805
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_argmax_input_x" } }
806
+ }
807
+ },
808
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 4], "tolerance": 0 } }
809
+ },
810
+ {
811
+ "name": "subgroup_vec4_ties_first_index",
812
+ "attrs": { "axis": -1, "keepdims": 0 },
813
+ "inputs": {
814
+ "x": {
815
+ "dtype": "float32",
816
+ "shape": [2, 8],
817
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/subgroup_vec4_ties_first_index_input_x" } }
818
+ }
819
+ },
820
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [1, 0] } } }
821
+ },
822
+ {
823
+ "name": "subgroup_vec4_ties_select_last_index",
824
+ "attrs": { "axis": -1, "keepdims": 0, "select_last_index": 1 },
825
+ "inputs": {
826
+ "x": {
827
+ "dtype": "float32",
828
+ "shape": [2, 8],
829
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/subgroup_vec4_ties_first_index_input_x" } }
830
+ }
831
+ },
832
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [4, 7] } } }
833
+ },
834
+ {
835
+ "name": "subgroup_scalar_f16_2x5",
836
+ "attrs": { "axis": 1, "keepdims": 0 },
837
+ "inputs": {
838
+ "x": {
839
+ "dtype": "float16",
840
+ "shape": [2, 5],
841
+ "data": { "kind": "values", "values": [1.5, 2.5, 2.5, 0.5, 1.0, -1.0, -2.0, -0.5, -0.5, -3.0] }
842
+ }
843
+ },
844
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [1, 2] } } }
845
+ },
846
+ {
847
+ "name": "subgroup_vec4_uint8_full_range",
848
+ "attrs": { "axis": 1, "keepdims": 0 },
849
+ "inputs": {
850
+ "x": { "dtype": "uint8", "shape": [2, 4], "data": { "kind": "values", "values": [255, 0, 254, 1, 2, 2, 1, 0] } }
851
+ },
852
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [0, 0] } } }
853
+ },
854
+ {
855
+ "name": "subgroup_vec4_long_row_2x256",
856
+ "attrs": { "axis": 1, "keepdims": 0 },
857
+ "inputs": {
858
+ "x": {
859
+ "dtype": "float32",
860
+ "shape": [2, 256],
861
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
862
+ }
863
+ },
864
+ "outputs": { "y": { "dtype": "uint32", "shape": [2] } }
865
+ },
866
+ {
867
+ "name": "subgroup_min_vec4_rows128x256_ties_first",
868
+ "provenance": {
869
+ "notes": "Route-locks the one-minimum-subgroup vec4 path on variable-width adapters and verifies first-index tie ordering without a workgroup-memory combine."
870
+ },
871
+ "attrs": { "axis": 1, "keepdims": 0 },
872
+ "inputs": { "x": { "dtype": "float32", "shape": [128, 256], "data": { "kind": "constant", "value": 7.0 } } },
873
+ "outputs": {
874
+ "y": { "dtype": "uint32", "shape": [128], "data": { "kind": "constant", "value": 0 }, "tolerance": 0 }
875
+ }
876
+ },
877
+ {
878
+ "name": "subgroup_min_scalar_rows128x257_ties_last",
879
+ "provenance": {
880
+ "notes": "Route-locks the unaligned one-minimum-subgroup path and verifies select_last_index ordering across all lane-local chunks."
881
+ },
882
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
883
+ "inputs": { "x": { "dtype": "float32", "shape": [128, 257], "data": { "kind": "constant", "value": 7.0 } } },
884
+ "outputs": {
885
+ "y": { "dtype": "uint32", "shape": [128], "data": { "kind": "constant", "value": 256 }, "tolerance": 0 }
886
+ }
887
+ },
888
+ {
889
+ "name": "subgroup_scalar_long_row_2x65",
890
+ "attrs": { "axis": 1, "keepdims": 0 },
891
+ "inputs": {
892
+ "x": {
893
+ "dtype": "float32",
894
+ "shape": [2, 65],
895
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11 }
896
+ }
897
+ },
898
+ "outputs": { "y": { "dtype": "uint32", "shape": [2] } }
899
+ },
900
+ {
901
+ "name": "empty_zero_dim",
902
+ "attrs": { "axis": 1, "keepdims": 0 },
903
+ "inputs": { "x": { "dtype": "float32", "shape": [0, 4], "data": { "kind": "values", "values": [] } } },
904
+ "outputs": { "y": { "dtype": "uint32", "shape": [0], "tolerance": 0 } }
905
+ },
906
+ {
907
+ "name": "empty_zero_dim_f16",
908
+ "attrs": { "axis": 1, "keepdims": 0 },
909
+ "inputs": { "x": { "dtype": "float16", "shape": [0, 4], "data": { "kind": "values", "values": [] } } },
910
+ "outputs": { "y": { "dtype": "uint32", "shape": [0], "tolerance": 0 } }
911
+ },
912
+ {
913
+ "name": "empty_zero_dim_int32",
914
+ "attrs": { "axis": 1, "keepdims": 0 },
915
+ "inputs": { "x": { "dtype": "int32", "shape": [0, 4], "data": { "kind": "values", "values": [] } } },
916
+ "outputs": { "y": { "dtype": "uint32", "shape": [0], "tolerance": 0 } }
917
+ },
918
+ {
919
+ "name": "rank4_axis0_f32_no_keepdims",
920
+ "attrs": { "axis": 0, "keepdims": 0 },
921
+ "inputs": {
922
+ "x": {
923
+ "dtype": "float32",
924
+ "shape": [2, 2, 2, 2],
925
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/rank4_axis0_f32_no_keepdims_input_x" } }
926
+ }
927
+ },
928
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 2, 2], "tolerance": 0 } }
929
+ },
930
+ {
931
+ "name": "rank4_axis0_int32_keepdims",
932
+ "attrs": { "axis": 0, "keepdims": 1 },
933
+ "inputs": {
934
+ "x": {
935
+ "dtype": "int32",
936
+ "shape": [2, 2, 2, 2],
937
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/rank4_axis0_f32_no_keepdims_input_x" } }
938
+ }
939
+ },
940
+ "outputs": { "y": { "dtype": "uint32", "shape": [1, 2, 2, 2], "tolerance": 0 } }
941
+ },
942
+ {
943
+ "name": "rank4_neg_axis0_select_last",
944
+ "attrs": { "axis": -4, "keepdims": 0, "select_last_index": 1 },
945
+ "inputs": {
946
+ "x": {
947
+ "dtype": "float32",
948
+ "shape": [2, 2, 2, 2],
949
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/rank4_axis0_f32_no_keepdims_input_x" } }
950
+ }
951
+ },
952
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 2, 2], "tolerance": 0 } }
953
+ },
954
+ {
955
+ "name": "axis1_serial_fallback_rows32768_cols5_select_last",
956
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
957
+ "inputs": {
958
+ "x": {
959
+ "dtype": "float32",
960
+ "shape": [32768, 5],
961
+ "data": { "kind": "cycle", "values": [1.0, 3.0, 3.0, 2.0, 3.0, 1.0] }
962
+ }
963
+ },
964
+ "outputs": { "y": { "dtype": "uint32", "shape": [32768], "tolerance": 0 } }
965
+ },
966
+ {
967
+ "name": "axis1_serial_fallback_rows32768_cols5_first_index",
968
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 0 },
969
+ "inputs": {
970
+ "x": {
971
+ "dtype": "float32",
972
+ "shape": [32768, 5],
973
+ "data": { "kind": "cycle", "values": [1.0, 3.0, 3.0, 2.0, 3.0, 1.0] }
974
+ }
975
+ },
976
+ "outputs": { "y": { "dtype": "uint32", "shape": [32768], "tolerance": 0 } }
977
+ },
978
+ {
979
+ "name": "axis0_fallback_rows4_int32_select_last_ties",
980
+ "attrs": { "axis": 0, "keepdims": 0, "select_last_index": 1 },
981
+ "inputs": {
982
+ "x": {
983
+ "dtype": "int32",
984
+ "shape": [4, 4],
985
+ "data": { "kind": "values", "values": [5, 1, 3, 2, 5, 7, 3, 2, 4, 7, 9, 2, 4, 1, 9, 8] }
986
+ }
987
+ },
988
+ "outputs": {
989
+ "y": { "dtype": "uint32", "shape": [4], "tolerance": 0, "data": { "kind": "values", "values": [1, 2, 3, 3] } }
990
+ }
991
+ },
992
+ {
993
+ "name": "axis1_int8_serial_fallback_select_last",
994
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
995
+ "inputs": {
996
+ "x": {
997
+ "dtype": "int8",
998
+ "shape": [2, 8],
999
+ "data": {
1000
+ "kind": "values",
1001
+ "values": [-5, -5, 127, 127, 3, 3, -128, -128, 0, -128, 0, 127, 100, 100, 100, 50]
1002
+ }
1003
+ }
1004
+ },
1005
+ "outputs": { "y": { "dtype": "uint32", "shape": [2], "tolerance": 0 } }
1006
+ },
1007
+ {
1008
+ "name": "axis1_uint8_serial_fallback_boundary_values",
1009
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
1010
+ "inputs": {
1011
+ "x": {
1012
+ "dtype": "uint8",
1013
+ "shape": [3, 8],
1014
+ "data": {
1015
+ "kind": "values",
1016
+ "values": [255, 0, 255, 128, 1, 254, 255, 200, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 255, 255, 100, 100, 255, 255]
1017
+ }
1018
+ }
1019
+ },
1020
+ "outputs": { "y": { "dtype": "uint32", "shape": [3], "tolerance": 0 } }
1021
+ },
1022
+ {
1023
+ "name": "axis0_splitk_i32_8192x16_precision_max",
1024
+ "attrs": { "axis": 0, "keepdims": 0 },
1025
+ "inputs": {
1026
+ "x": {
1027
+ "dtype": "int32",
1028
+ "shape": [8192, 16],
1029
+ "data": {
1030
+ "kind": "cycle",
1031
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_i32_8192x16_precision_max_input_x" }
1032
+ }
1033
+ }
1034
+ },
1035
+ "outputs": { "y": { "dtype": "uint32", "shape": [16], "tolerance": 0 } }
1036
+ },
1037
+ {
1038
+ "name": "axis0_splitk_u32_8192x16_precision_max",
1039
+ "attrs": { "axis": 0, "keepdims": 0 },
1040
+ "inputs": {
1041
+ "x": {
1042
+ "dtype": "uint32",
1043
+ "shape": [8192, 16],
1044
+ "data": {
1045
+ "kind": "cycle",
1046
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_i32_8192x16_precision_max_input_x" }
1047
+ }
1048
+ }
1049
+ },
1050
+ "outputs": { "y": { "dtype": "uint32", "shape": [16], "tolerance": 0 } }
1051
+ },
1052
+ {
1053
+ "name": "axis0_tilecols_i32_64x16_precision_max",
1054
+ "attrs": { "axis": 0, "keepdims": 0 },
1055
+ "inputs": {
1056
+ "x": {
1057
+ "dtype": "int32",
1058
+ "shape": [64, 16],
1059
+ "data": {
1060
+ "kind": "cycle",
1061
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_i32_8192x16_precision_max_input_x" }
1062
+ }
1063
+ }
1064
+ },
1065
+ "outputs": { "y": { "dtype": "uint32", "shape": [16], "tolerance": 0 } }
1066
+ },
1067
+ {
1068
+ "name": "axis0_tilecols_i32_64x16_precision_select_last_max",
1069
+ "attrs": { "axis": 0, "keepdims": 0, "select_last_index": 1 },
1070
+ "inputs": {
1071
+ "x": {
1072
+ "dtype": "int32",
1073
+ "shape": [64, 16],
1074
+ "data": {
1075
+ "kind": "cycle",
1076
+ "values": { "$ref": "#/fixtureArrays/axis0_splitk_i32_8192x16_precision_max_input_x" }
1077
+ }
1078
+ }
1079
+ },
1080
+ "outputs": { "y": { "dtype": "uint32", "shape": [16], "tolerance": 0 } }
1081
+ },
1082
+ {
1083
+ "name": "axis0_narrow_splitk_16384x8_f32",
1084
+ "provenance": {
1085
+ "notes": "Tall narrow axis-0 (16384 rows, 8 cols) routes to the split-and-tiled kernel: all 256 lanes cooperate across eight columns instead of leaving 248 lanes idle. Cross-checks the tiled partials and combine pass against the reference."
1086
+ },
1087
+ "attrs": { "axis": 0, "keepdims": 0 },
1088
+ "inputs": {
1089
+ "x": {
1090
+ "dtype": "float32",
1091
+ "shape": [16384, 8],
1092
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
1093
+ }
1094
+ },
1095
+ "outputs": { "y": { "dtype": "uint32", "shape": [8], "tolerance": 0 } }
1096
+ },
1097
+ {
1098
+ "name": "axis0_narrow_splitk_8192x4_int32",
1099
+ "provenance": {
1100
+ "notes": "Tall narrow axis-0 (16384 rows, 4 cols): cols<16 now routes to axis0_splitk (split-K over rows) instead of the few-lane axis0 serial path (which gives one thread per column -> only 4 lanes). Cross-checks split-K against the single-lane axis0 kernel bit-for-bit; reference is the oracle. int32 exercises the native-integer split-K compare path."
1101
+ },
1102
+ "attrs": { "axis": 0, "keepdims": 0 },
1103
+ "inputs": {
1104
+ "x": {
1105
+ "dtype": "int32",
1106
+ "shape": [8192, 4],
1107
+ "data": { "kind": "cycle", "values": [-5000, 1200, -300, 4800, 700] }
1108
+ }
1109
+ },
1110
+ "outputs": { "y": { "dtype": "uint32", "shape": [4], "tolerance": 0 } }
1111
+ },
1112
+ {
1113
+ "name": "rank3_axis1_splitk_temporal_select_last",
1114
+ "provenance": {
1115
+ "notes": "A long temporal middle axis with only four output classes used to launch four serial reduction lanes. The strided split-K path must map (batch, time, class) correctly and merge equal maxima to the last temporal index."
1116
+ },
1117
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
1118
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 8192, 4], "data": { "kind": "constant", "value": 7.0 } } },
1119
+ "outputs": {
1120
+ "y": { "dtype": "uint32", "shape": [1, 4], "data": { "kind": "constant", "value": 8191 }, "tolerance": 0 }
1121
+ }
1122
+ },
1123
+ {
1124
+ "name": "last_axis_split_vec4_logits_ties_first",
1125
+ "provenance": {
1126
+ "notes": "A single 32768-element row route-locks the cooperative split-row vec4 reduction. Equal maxima span many split segments and must merge to the first global index."
1127
+ },
1128
+ "attrs": { "axis": 1, "keepdims": 0 },
1129
+ "inputs": {
1130
+ "x": { "dtype": "float32", "shape": [1, 32768], "data": { "kind": "cycle", "values": [7.0, 3.0, 7.0, 1.0] } }
1131
+ },
1132
+ "outputs": {
1133
+ "y": { "dtype": "uint32", "shape": [1], "data": { "kind": "constant", "value": 0 }, "tolerance": 0 }
1134
+ }
1135
+ },
1136
+ {
1137
+ "name": "last_axis_split_scalar_logits_ties_last",
1138
+ "provenance": {
1139
+ "notes": "The unaligned 32769-element companion route-locks the scalar split-row reduction and verifies select_last_index across split boundaries."
1140
+ },
1141
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
1142
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 32769], "data": { "kind": "constant", "value": 5.0 } } },
1143
+ "outputs": {
1144
+ "y": { "dtype": "uint32", "shape": [1], "data": { "kind": "constant", "value": 32768 }, "tolerance": 0 }
1145
+ }
1146
+ },
1147
+ {
1148
+ "name": "last_axis_split_vec4_i32_negative_rows",
1149
+ "provenance": {
1150
+ "notes": "int32 on the cooperative split-row vec4 reduction (32768 columns, two rows). Every value in the row is negative, so a lane identity of 0 instead of INT_MIN would beat the whole row and the merge would return the sentinel; this directly exercises the i32 identity and native signed compare. The period-7 cycle does not divide 32768, so the two rows are phase-shifted and their answers differ — a kernel that reduced row 0 for both rows cannot pass. ONNX ArgMax admits int32 via T and breaks ties on the first index."
1151
+ },
1152
+ "attrs": { "axis": 1, "keepdims": 0 },
1153
+ "inputs": {
1154
+ "x": {
1155
+ "dtype": "int32",
1156
+ "shape": [2, 32768],
1157
+ "data": { "kind": "cycle", "values": [-9, -5, -3, -1, -2, -4, -6] }
1158
+ }
1159
+ },
1160
+ "outputs": {
1161
+ "y": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [3, 2] }, "tolerance": 0 }
1162
+ }
1163
+ },
1164
+ {
1165
+ "name": "last_axis_split_scalar_u32_above_int32_max",
1166
+ "provenance": {
1167
+ "notes": "uint32 on the unaligned (32769-column) scalar split-row reduction. Six of the seven cycle values exceed 2^31, so a signed compare would rank them negative and answer [0, 5] instead of [3, 1]. This directly exercises the unsigned identity 0u and the partial store that must write the value through without bitcasting it. ONNX ArgMax admits uint32 via T; first index on ties."
1168
+ },
1169
+ "attrs": { "axis": 1, "keepdims": 0 },
1170
+ "inputs": {
1171
+ "x": {
1172
+ "dtype": "uint32",
1173
+ "shape": [2, 32769],
1174
+ "data": {
1175
+ "kind": "cycle",
1176
+ "values": [2147483000, 4200000000, 3000000000, 4294967290, 2500000000, 3500000000, 4000000000]
1177
+ }
1178
+ }
1179
+ },
1180
+ "outputs": {
1181
+ "y": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [3, 1] }, "tolerance": 0 }
1182
+ }
1183
+ },
1184
+ {
1185
+ "name": "last_axis_split_vec4_f16_rows",
1186
+ "provenance": {
1187
+ "notes": "float16 on the vec4 split-row reduction: each vec4 component must be widened to f32 before comparison, and the shader must enable f16. Rank 3 (1, 2, 32768) reduces the last axis exactly as the rank-2 logits case does while keeping the reference on its hoisted-decode path. All seven cycle values are exactly representable in float16, so the answer does not depend on rounding. ONNX ArgMax admits float16 via T; first index on ties."
1188
+ },
1189
+ "attrs": { "axis": 2, "keepdims": 0 },
1190
+ "inputs": {
1191
+ "x": {
1192
+ "dtype": "float16",
1193
+ "shape": [1, 2, 32768],
1194
+ "data": { "kind": "cycle", "values": [1.5, -2.0, 0.5, 3.5, -1.0, 2.25, -3.75] }
1195
+ }
1196
+ },
1197
+ "outputs": {
1198
+ "y": { "dtype": "uint32", "shape": [1, 2], "data": { "kind": "values", "values": [3, 2] }, "tolerance": 0 }
1199
+ }
1200
+ },
1201
+ {
1202
+ "name": "last_axis_split_scalar_f16_rows",
1203
+ "provenance": {
1204
+ "notes": "The unaligned (32769-column) companion: float16 on the scalar split-row reduction, where the widening happens on the plain load rather than on a vec4 component. Rank 3 keeps the reference on its hoisted-decode path; all cycle values are exact in float16. ONNX ArgMax admits float16 via T; first index on ties."
1205
+ },
1206
+ "attrs": { "axis": 2, "keepdims": 0 },
1207
+ "inputs": {
1208
+ "x": {
1209
+ "dtype": "float16",
1210
+ "shape": [1, 2, 32769],
1211
+ "data": { "kind": "cycle", "values": [1.5, -2.0, 0.5, 3.5, -1.0, 2.25, -3.75] }
1212
+ }
1213
+ },
1214
+ "outputs": {
1215
+ "y": { "dtype": "uint32", "shape": [1, 2], "data": { "kind": "values", "values": [3, 1] }, "tolerance": 0 }
1216
+ }
1217
+ },
1218
+ {
1219
+ "name": "last_axis_split_vec4_ties_select_last_index",
1220
+ "provenance": {
1221
+ "notes": "select_last_index on the vec4 split-row route checks the last-index tie rule in both the segment reduction and combine. The maximum 7.0 recurs at cycle offsets 0, 2 and 5, so every segment reports a tie; the two phase-shifted rows must return 32767 and 32766."
1222
+ },
1223
+ "attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
1224
+ "inputs": {
1225
+ "x": {
1226
+ "dtype": "float32",
1227
+ "shape": [2, 32768],
1228
+ "data": { "kind": "cycle", "values": [7.0, 3.0, 7.0, 1.0, 5.0, 7.0, 2.0] }
1229
+ }
1230
+ },
1231
+ "outputs": {
1232
+ "y": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [32767, 32766] }, "tolerance": 0 }
1233
+ }
1234
+ },
1235
+ {
1236
+ "name": "rank5_middle_axis_generic_geometry",
1237
+ "provenance": {
1238
+ "notes": "Proves the rank-independent (outer, axis, inner) fallback covers ranks beyond the former rank-4 manifest specializations."
1239
+ },
1240
+ "attrs": { "axis": 2, "keepdims": 0, "select_last_index": 1 },
1241
+ "inputs": {
1242
+ "x": {
1243
+ "dtype": "float32",
1244
+ "shape": [2, 2, 3, 2, 2],
1245
+ "data": { "kind": "cycle", "values": [-3.0, 5.0, 1.0, 5.0, 2.0, -1.0] }
1246
+ }
1247
+ },
1248
+ "outputs": { "y": { "dtype": "uint32", "shape": [2, 2, 2, 2], "tolerance": 0 } }
1249
+ },
1250
+ {
1251
+ "name": "axis0_splitk_select_last_index_ties_8192x16",
1252
+ "provenance": {
1253
+ "notes": "select_last_index=1 on the split-tiled route, which no case had ever rendered: every existing case for that geometry leaves the attribute at its default. The 17-value cycle is coprime with the 16 columns, so each column sees every value about 482 times and the maximum is a 482-way tie whose winning index differs per column -- exactly the tie-break the two arms disagree about, and one a per-lane fold can get wrong even when its lane-to-index map is monotone."
1254
+ },
1255
+ "attrs": { "axis": 0, "keepdims": 0, "select_last_index": 1 },
1256
+ "inputs": {
1257
+ "x": {
1258
+ "dtype": "int32",
1259
+ "shape": [8192, 16],
1260
+ "data": { "kind": "cycle", "values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] }
1261
+ }
1262
+ },
1263
+ "outputs": { "y": { "dtype": "uint32", "shape": [16], "tolerance": 0 } }
1264
+ }
1265
+ ]
1266
+ }