Xenova HF Staff commited on
Commit
5c58519
·
verified ·
1 Parent(s): 459b738

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,70 @@
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.Hardmax
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 13
12
+
13
+ ## Description
14
+
15
+ Computes the hardmax of the input along a single axis: sets the position of the first maximum value along `axis` to 1 and all other positions to 0. The output has the same shape as the input.
16
+
17
+ See the [ONNX `Hardmax` spec](https://onnx.ai/onnx/operators/onnx__Hardmax.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `input` | `x` | `T` | — | — | Input tensor with rank at least 1. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- |
29
+ | `output` | `y` | `T` | same as `input` | same as `input` | The output tensor with the same shape as the input, containing hardmax values. | required |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `axis` | `-1` | The dimension along which hardmax is computed. Negative values count from the back; accepted range is `[-r, r-1]` where `r` is the rank of the input. |
38
+
39
+ ## Type constraints
40
+
41
+ | Variable | Allowed dtypes |
42
+ | --- | --- |
43
+ | `T` | `float32`, `float16` |
44
+
45
+ ## Files
46
+
47
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
48
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
49
+ - [`test.json`](build/webgpu/test.json) — correctness cases
50
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
51
+ - [`hardmax-axis-tree.wgsl.jinja`](build/webgpu/hardmax-axis-tree.wgsl.jinja)
52
+ - [`hardmax-last-axis-subgroup.wgsl.jinja`](build/webgpu/hardmax-last-axis-subgroup.wgsl.jinja)
53
+ - [`hardmax-last-axis-vec4.wgsl.jinja`](build/webgpu/hardmax-last-axis-vec4.wgsl.jinja)
54
+ - [`hardmax.wgsl.jinja`](build/webgpu/hardmax.wgsl.jinja)
55
+
56
+ ## Use with `@huggingface/kernels`
57
+
58
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
59
+ It then allocates the result tensors automatically.
60
+
61
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
62
+
63
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
64
+
65
+ ```js
66
+ import { getKernel } from "@huggingface/kernels";
67
+
68
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.Hardmax", { version: 1 });
69
+ const { y } = await kernel({ x: { data: xData, shape: [1, 3] } });
70
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Hardmax",
3
+ "cases": [
4
+ {
5
+ "name": "rows_4096_cols_128",
6
+ "vars": { "rows": 4096, "cols": 128, "dtype": "float32" },
7
+ "inputs": { "x": { "dtype": "float32", "shape": [4096, 128] } },
8
+ "outputs": { "y": { "dtype": "float32", "shape": [4096, 128] } },
9
+ "bench": {
10
+ "primary": true,
11
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }]
12
+ },
13
+ "preset": "smoke"
14
+ },
15
+ {
16
+ "name": "rows_4096_cols_512",
17
+ "vars": { "rows": 4096, "cols": 512, "dtype": "float32" },
18
+ "inputs": { "x": { "dtype": "float32", "shape": [4096, 512] } },
19
+ "outputs": { "y": { "dtype": "float32", "shape": [4096, 512] } },
20
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
21
+ },
22
+ {
23
+ "name": "rows_4096_cols_128_f16",
24
+ "vars": { "rows": 4096, "cols": 128, "dtype": "float16" },
25
+ "inputs": { "x": { "dtype": "float16", "shape": [4096, 128] } },
26
+ "outputs": { "y": { "dtype": "float16", "shape": [4096, 128] } },
27
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
28
+ },
29
+ {
30
+ "name": "rows_1024_cols_1025",
31
+ "vars": { "rows": 1024, "cols": 1025, "dtype": "float32" },
32
+ "inputs": { "x": { "dtype": "float32", "shape": [1024, 1025] } },
33
+ "outputs": { "y": { "dtype": "float32", "shape": [1024, 1025] } },
34
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
35
+ },
36
+ {
37
+ "name": "rank3_axis1_scalar_fallback",
38
+ "vars": { "outer": 1024, "axisDim": 1024, "inner": 8, "dtype": "float32" },
39
+ "inputs": { "x": { "dtype": "float32", "shape": [1024, 1024, 8] } },
40
+ "outputs": { "y": { "dtype": "float32", "shape": [1024, 1024, 8] } },
41
+ "bench": {
42
+ "metrics": [
43
+ { "type": "bandwidth", "value": "args.outer * args.axisDim * args.inner * dtypeBytes(args.dtype) * 2" }
44
+ ]
45
+ }
46
+ },
47
+ {
48
+ "name": "unaligned_last_axis_127_scalar_cliff",
49
+ "vars": { "rows": 16384, "cols": 127, "dtype": "float32" },
50
+ "inputs": { "x": { "dtype": "float32", "shape": [16384, 127] } },
51
+ "outputs": { "y": { "dtype": "float32", "shape": [16384, 127] } },
52
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
53
+ },
54
+ {
55
+ "name": "rows_1024_cols_1025_f16_subgroup",
56
+ "vars": { "rows": 1024, "cols": 1025, "dtype": "float16" },
57
+ "inputs": { "x": { "dtype": "float16", "shape": [1024, 1025] } },
58
+ "outputs": { "y": { "dtype": "float16", "shape": [1024, 1025] } },
59
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
60
+ },
61
+ {
62
+ "name": "axis0_huge_axis_few_threads_launch_poor",
63
+ "preset": "stress",
64
+ "vars": { "outer": 1, "axisDim": 65536, "inner": 64, "dtype": "float32" },
65
+ "attrs": { "axis": 0 },
66
+ "inputs": { "x": { "dtype": "float32", "shape": [65536, 64], "dist": "normal", "seed": 1301, "scale": 2 } },
67
+ "outputs": { "y": { "dtype": "float32", "shape": [65536, 64], "dist": "empty" } },
68
+ "bench": {
69
+ "metrics": [{ "type": "bandwidth", "value": "args.axisDim * args.inner * dtypeBytes(args.dtype) * 2" }]
70
+ }
71
+ },
72
+ {
73
+ "name": "unaligned_last_axis_1023_scalar_row_cliff",
74
+ "preset": "edge",
75
+ "vars": { "rows": 16384, "cols": 1023, "dtype": "float32" },
76
+ "attrs": { "axis": -1 },
77
+ "inputs": { "x": { "dtype": "float32", "shape": [16384, 1023], "dist": "normal", "seed": 2207, "scale": 2 } },
78
+ "outputs": { "y": { "dtype": "float32", "shape": [16384, 1023], "dist": "empty" } },
79
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
80
+ }
81
+ ]
82
+ }
build/webgpu/hardmax-axis-tree.wgsl.jinja ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ const AXIS_DIM: u32 = {{ axisDim }}u;
7
+ const INNER: u32 = {{ inner }}u;
8
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
9
+ const NEG_INF: f32 = -3.4028234663852886e38;
10
+ var<workgroup> values: array<f32, WG>;
11
+ var<workgroup> indices: array<u32, WG>;
12
+
13
+ fn is_nan_f32(value: f32) -> bool {
14
+ let bits = bitcast<u32>(value);
15
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
16
+ }
17
+
18
+
19
+ @compute @workgroup_size(WG, 1, 1)
20
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
21
+ let row = wg.x + wg.y * params.rowStride;
22
+ if (row >= params.rows) { return; }
23
+ let tid = lid.x;
24
+ let outer = row / INNER;
25
+ let inner_index = row % INNER;
26
+ let base = outer * AXIS_DIM * INNER + inner_index;
27
+ let first = f32(x[base]);
28
+
29
+ var best_value = NEG_INF;
30
+ var best_axis = 0u;
31
+ for (var a = tid; a < AXIS_DIM; a = a + WG) {
32
+ let value = f32(x[base + a * INNER]);
33
+ if (!is_nan_f32(value) && (value > best_value || (value == best_value && a < best_axis))) {
34
+ best_value = value;
35
+ best_axis = a;
36
+ }
37
+ }
38
+ values[tid] = best_value;
39
+ indices[tid] = best_axis;
40
+ workgroupBarrier();
41
+ for (var stride = WG >> 1u; stride > 0u; stride = stride >> 1u) {
42
+ if (tid < stride) {
43
+ let rv = values[tid + stride];
44
+ let ri = indices[tid + stride];
45
+ if (rv > values[tid] || (rv == values[tid] && ri < indices[tid])) {
46
+ values[tid] = rv;
47
+ indices[tid] = ri;
48
+ }
49
+ }
50
+ workgroupBarrier();
51
+ }
52
+ // The serial definition seeds from element zero: a NaN there wins forever;
53
+ // later NaNs never satisfy `value > best_value` and are ignored. It then
54
+ // marks where the value EQUALS the winner, which a NaN winner never does, so
55
+ // the reduced row is all zeros — an out-of-range index says exactly that.
56
+ let winner = select(indices[0], AXIS_DIM, is_nan_f32(first));
57
+ for (var a = tid; a < AXIS_DIM; a = a + WG) {
58
+ y[base + a * INNER] = {{ scalar }}(select(0.0, 1.0, a == winner));
59
+ }
60
+ }
build/webgpu/hardmax-last-axis-subgroup.wgsl.jinja ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {% if useSubgroups %}
5
+ enable subgroups;
6
+ {% endif %}
7
+ {{ env.wgsl.resourceDeclarations }}
8
+
9
+ const WG: u32 = {{ workgroupSize }}u;
10
+ const SENTINEL_IDX: u32 = 4294967295u;
11
+
12
+ fn neg_inf_f32() -> f32 {
13
+ var bits = 0xff800000u;
14
+ return bitcast<f32>(bits);
15
+ }
16
+
17
+ fn is_nan_f32(value: f32) -> bool {
18
+ let bits = bitcast<u32>(value);
19
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
20
+ }
21
+
22
+
23
+ var<workgroup> wgVal: array<f32, WG>;
24
+ var<workgroup> wgIdx: array<u32, WG>;
25
+ var<workgroup> rowBestIdx: u32;
26
+
27
+ @compute @workgroup_size(WG, 1, 1)
28
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
29
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
30
+ @builtin(subgroup_invocation_id) sgLid: u32,
31
+ @builtin(subgroup_size) sgSize: u32{% endif %}) {
32
+ let row = wg.x + wg.y * params.rowStride;
33
+ if (row >= params.rows) {
34
+ return;
35
+ }
36
+
37
+ let tid = lid.x;
38
+ let base = row * params.cols;
39
+
40
+ let firstValue = f32(x[base]);
41
+ var bestVal = neg_inf_f32();
42
+ var bestIdx = SENTINEL_IDX;
43
+
44
+ for (var c = tid; c < params.cols; c = c + WG) {
45
+ let v = f32(x[base + c]);
46
+ if (!is_nan_f32(v) && (v > bestVal || (v == bestVal && c < bestIdx))) {
47
+ bestVal = v;
48
+ bestIdx = c;
49
+ }
50
+ }
51
+
52
+ {% if useSubgroups %}
53
+ let m = subgroupMax(bestVal);
54
+ let cand = select(SENTINEL_IDX, bestIdx, bestVal == m);
55
+ let sgIdx = subgroupMin(cand);
56
+ let safeSg = max(sgSize, 1u);
57
+ let slotCount = max(1u, WG / safeSg);
58
+ if (sgLid == 0u) {
59
+ let slot = min(tid / safeSg, WG - 1u);
60
+ wgVal[slot] = m;
61
+ wgIdx[slot] = sgIdx;
62
+ }
63
+ workgroupBarrier();
64
+
65
+ if (tid == 0u) {
66
+ var outVal = wgVal[0];
67
+ var outIdx = wgIdx[0];
68
+ for (var i = 1u; i < slotCount; i = i + 1u) {
69
+ let v = wgVal[i];
70
+ let vi = wgIdx[i];
71
+ if (vi != SENTINEL_IDX && (v > outVal || (v == outVal && vi < outIdx))) {
72
+ outVal = v;
73
+ outIdx = vi;
74
+ }
75
+ }
76
+ // Match the scalar shader: if x[row, 0] is NaN no later comparison can
77
+ // replace it, and an all-NaN row selects nothing. The operator writes 1 only
78
+ // where the value equals the winner, so both cases emit an all-zero row; an index
79
+ // past params.cols is how that is expressed here.
80
+ if (is_nan_f32(firstValue) || outIdx == SENTINEL_IDX) {
81
+ outIdx = params.cols;
82
+ }
83
+ rowBestIdx = outIdx;
84
+ }
85
+ workgroupBarrier();
86
+ {% else %}
87
+ // No-subgroup tier: workgroup barrier tree-reduction over the per-thread
88
+ // (bestVal, bestIdx) pairs with the same lowest-index tie rule. WG is a power
89
+ // of two (the specialized pow2ceil value); SENTINEL_IDX lanes never win.
90
+ wgVal[tid] = bestVal;
91
+ wgIdx[tid] = bestIdx;
92
+ workgroupBarrier();
93
+ for (var stride = WG / 2u; stride > 0u; stride = stride >> 1u) {
94
+ if (tid < stride) {
95
+ let v = wgVal[tid + stride];
96
+ let vi = wgIdx[tid + stride];
97
+ let curIdx = wgIdx[tid];
98
+ if (vi != SENTINEL_IDX && (curIdx == SENTINEL_IDX || v > wgVal[tid] || (v == wgVal[tid] && vi < curIdx))) {
99
+ wgVal[tid] = v;
100
+ wgIdx[tid] = vi;
101
+ }
102
+ }
103
+ workgroupBarrier();
104
+ }
105
+ if (tid == 0u) {
106
+ var outIdx = wgIdx[0];
107
+ if (is_nan_f32(firstValue) || outIdx == SENTINEL_IDX) {
108
+ outIdx = params.cols;
109
+ }
110
+ rowBestIdx = outIdx;
111
+ }
112
+ workgroupBarrier();
113
+ {% endif %}
114
+
115
+ let outIdx = rowBestIdx;
116
+ for (var c = tid; c < params.cols; c = c + WG) {
117
+ y[base + c] = {{ scalar }}(select(0.0, 1.0, c == outIdx));
118
+ }
119
+ }
build/webgpu/hardmax-last-axis-vec4.wgsl.jinja ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ const COLS_VEC: u32 = {{ colsVec }}u;
7
+
8
+ fn is_nan_f32(value: f32) -> bool {
9
+ let bits = bitcast<u32>(value);
10
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
11
+ }
12
+
13
+
14
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
15
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
16
+ // 2D-folded flat index: gid.y carries the high bits past the
17
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
18
+ let row = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
19
+ if (row >= params.rows) {
20
+ return;
21
+ }
22
+
23
+ let base = row * COLS_VEC;
24
+ let first4 = x[base];
25
+ let firstValue = f32(first4.x);
26
+ var bestValue = firstValue;
27
+ var bestAxis = 0u;
28
+
29
+ if (!is_nan_f32(firstValue)) {
30
+ for (var c4 = 0u; c4 < COLS_VEC; c4 = c4 + 1u) {
31
+ let v4 = x[base + c4];
32
+ let axisBase = c4 * 4u;
33
+
34
+ {
35
+ let value = f32(v4.x);
36
+ if (!is_nan_f32(value) && value > bestValue) {
37
+ bestValue = value;
38
+ bestAxis = axisBase;
39
+ }
40
+ }
41
+ {
42
+ let value = f32(v4.y);
43
+ if (!is_nan_f32(value) && value > bestValue) {
44
+ bestValue = value;
45
+ bestAxis = axisBase + 1u;
46
+ }
47
+ }
48
+ {
49
+ let value = f32(v4.z);
50
+ if (!is_nan_f32(value) && value > bestValue) {
51
+ bestValue = value;
52
+ bestAxis = axisBase + 2u;
53
+ }
54
+ }
55
+ {
56
+ let value = f32(v4.w);
57
+ if (!is_nan_f32(value) && value > bestValue) {
58
+ bestValue = value;
59
+ bestAxis = axisBase + 3u;
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ // A NaN at element zero wins the ordered scan and then matches nothing on the
66
+ // equality write, so the row comes out all zeros. COLS_VEC * 4 is past every
67
+ // position written below.
68
+ if (is_nan_f32(firstValue)) {
69
+ bestAxis = COLS_VEC * 4u;
70
+ }
71
+
72
+ let zero = {{ scalar }}(0.0);
73
+ let one = {{ scalar }}(1.0);
74
+ for (var c4 = 0u; c4 < COLS_VEC; c4 = c4 + 1u) {
75
+ let axisBase = c4 * 4u;
76
+ y[base + c4] = vec4<{{ scalar }}>(
77
+ select(zero, one, axisBase == bestAxis),
78
+ select(zero, one, axisBase + 1u == bestAxis),
79
+ select(zero, one, axisBase + 2u == bestAxis),
80
+ select(zero, one, axisBase + 3u == bestAxis)
81
+ );
82
+ }
83
+ }
build/webgpu/hardmax.wgsl.jinja ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ fn is_nan_f32(value: f32) -> bool {
6
+ let bits = bitcast<u32>(value);
7
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
8
+ }
9
+
10
+
11
+ const AXIS_DIM: u32 = {{ axisDim }}u;
12
+ const INNER: u32 = {{ inner }}u;
13
+
14
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
15
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
16
+ // 2D-folded flat index: gid.y carries the high bits past the
17
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
18
+ let row = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
19
+ if (row >= params.rows) {
20
+ return;
21
+ }
22
+
23
+ let outer = row / INNER;
24
+ let inner_index = row % INNER;
25
+ let base = outer * AXIS_DIM * INNER + inner_index;
26
+
27
+ var best_axis = 0u;
28
+ var best_value = f32(x[base]);
29
+ for (var a = 1u; a < AXIS_DIM; a = a + 1u) {
30
+ let value = f32(x[base + a * INNER]);
31
+ if (value > best_value) {
32
+ best_value = value;
33
+ best_axis = a;
34
+ }
35
+ }
36
+
37
+ // The operator seeds the running best at element zero and only replaces it
38
+ // on a strict `>`, so a NaN there wins forever — and it then writes 1 where
39
+ // the value EQUALS the winner, which NaN never does. The row is therefore all
40
+ // zeros, not a 1 in slot 0. Steering the winning index out of range is how
41
+ // that falls out here.
42
+ // Use a bitcast test because no-NaN optimization can fold `v != v` to false.
43
+ if (is_nan_f32(best_value)) {
44
+ best_axis = AXIS_DIM;
45
+ }
46
+
47
+ for (var a = 0u; a < AXIS_DIM; a = a + 1u) {
48
+ y[base + a * INNER] = {{ scalar }}(select(0.0, 1.0, a == best_axis));
49
+ }
50
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "Hardmax",
4
+ "sinceVersion": 13,
5
+ "description": "Computes the hardmax of the input along a single axis: sets the position of the first maximum value along `axis` to 1 and all other positions to 0. The output has the same shape as the input.",
6
+ "inputs": [{ "role": "input", "dtype": "T", "description": "Input tensor with rank at least 1." }],
7
+ "outputs": [
8
+ {
9
+ "role": "output",
10
+ "dtype": "T",
11
+ "rank": "ranks.input",
12
+ "description": "The output tensor with the same shape as the input, containing hardmax values.",
13
+ "shape": "shapes.input"
14
+ }
15
+ ],
16
+ "attributes": { "axis": -1 },
17
+ "attributeDescriptions": {
18
+ "axis": "The dimension along which hardmax is computed. Negative values count from the back; accepted range is `[-r, r-1]` where `r` is the rank of the input."
19
+ },
20
+ "typeConstraints": { "T": ["float32", "float16"] },
21
+ "args": {
22
+ "x": { "kind": "tensor", "semantic": "input", "role": "input" },
23
+ "y": { "kind": "tensor", "semantic": "output", "role": "output" }
24
+ },
25
+ "tunables": { "WORKGROUP_SIZE": 256 },
26
+ "bindingSets": {
27
+ "subgroupRow": [
28
+ {
29
+ "name": "x",
30
+ "arg": "x",
31
+ "semantic": "input",
32
+ "buffer": { "type": "read-only-storage" },
33
+ "elementType": "$scalar"
34
+ },
35
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$scalar" },
36
+ {
37
+ "name": "params",
38
+ "semantic": "kernel.params",
39
+ "buffer": { "type": "uniform" },
40
+ "struct": {
41
+ "name": "Params",
42
+ "fields": [
43
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x) / dim(shapes.x, ranks.x - 1)" },
44
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" },
45
+ {
46
+ "name": "rowStride",
47
+ "type": "u32",
48
+ "value": "max(1, min(numel(shapes.x) / dim(shapes.x, ranks.x - 1), device.limits.maxComputeWorkgroupsPerDimension))"
49
+ }
50
+ ]
51
+ }
52
+ }
53
+ ]
54
+ },
55
+ "constants": { "usesF16": "dtypes.T == \"f16\"", "scalar": "dtypes.T" },
56
+ "variants": [
57
+ {
58
+ "id": "last_axis_row",
59
+ "priority": 20,
60
+ "when": ["ranks.x >= 1", "ranks.y == ranks.x", "numel(shapes.x) == numel(shapes.y)", "(attrs.axis == -1 or attrs.axis == ranks.x - 1)", "dim(shapes.x, ranks.x - 1) >= 1024", "f16Ok(dtypes.T)"],
61
+ "constants": {
62
+ "workgroupSize": "min(tunables.WORKGROUP_SIZE, max(32, pow2ceil(dim(shapes.x, ranks.x - 1))))",
63
+ "useSubgroups": "device.features.has(\"subgroups\")"
64
+ },
65
+ "passes": [
66
+ {
67
+ "id": "main",
68
+ "name": "Hardmax.LastAxisRow",
69
+ "shader": "hardmax-last-axis-subgroup.wgsl.jinja",
70
+ "bindings": "subgroupRow",
71
+ "dispatch": { "workgroups": "numel(shapes.x) / dim(shapes.x, ranks.x - 1)" }
72
+ }
73
+ ]
74
+ },
75
+ {
76
+ "id": "last_axis_vec4",
77
+ "priority": 30,
78
+ "when": ["ranks.x >= 1", "ranks.y == ranks.x", "numel(shapes.x) == numel(shapes.y)", "(attrs.axis == -1 or attrs.axis == ranks.x - 1)", "dim(shapes.x, ranks.x - 1) >= 4", "dim(shapes.x, ranks.x - 1) % 4 == 0", "f16Ok(dtypes.T)"],
79
+ "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"", "colsVec": "dim(shapes.x, ranks.x - 1) / 4" },
80
+ "passes": [
81
+ {
82
+ "id": "main",
83
+ "name": "Hardmax.LastAxisVec4",
84
+ "shader": "hardmax-last-axis-vec4.wgsl.jinja",
85
+ "bindings": [
86
+ {
87
+ "name": "x",
88
+ "arg": "x",
89
+ "semantic": "input",
90
+ "buffer": { "type": "read-only-storage" },
91
+ "elementType": "$vectorScalar"
92
+ },
93
+ {
94
+ "name": "y",
95
+ "arg": "y",
96
+ "semantic": "output",
97
+ "buffer": { "type": "storage" },
98
+ "elementType": "$vectorScalar"
99
+ },
100
+ {
101
+ "name": "params",
102
+ "semantic": "kernel.params",
103
+ "buffer": { "type": "uniform" },
104
+ "struct": {
105
+ "name": "Params",
106
+ "fields": [{ "name": "rows", "type": "u32", "value": "numel(shapes.x) / dim(shapes.x, ranks.x - 1)" }]
107
+ }
108
+ }
109
+ ],
110
+ "dispatch": {
111
+ "threads": "numel(shapes.x) / dim(shapes.x, ranks.x - 1)",
112
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
113
+ }
114
+ }
115
+ ]
116
+ },
117
+ {
118
+ "id": "axis_tree",
119
+ "priority": 21,
120
+ "demoteWhen": ["device.features.has(\"subgroups\") and (attrs.axis == -1 or attrs.axis == ranks.x - 1)"],
121
+ "when": ["ranks.x >= 1", "ranks.y == ranks.x", "sameShape(shapes.y, shapes.x)", "attrs.axis + ranks.x >= 0", "attrs.axis < ranks.x", "dim(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x) >= 1024", "rows(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x) <= device.limits.maxComputeWorkgroupsPerDimension * device.limits.maxComputeWorkgroupsPerDimension", "f16Ok(dtypes.T)"],
122
+ "constants": {
123
+ "axisDim": "dim(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)",
124
+ "inner": "inner(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)"
125
+ },
126
+ "passes": [
127
+ {
128
+ "id": "main",
129
+ "name": "Hardmax.AxisTree",
130
+ "shader": "hardmax-axis-tree.wgsl.jinja",
131
+ "bindings": [
132
+ {
133
+ "name": "x",
134
+ "arg": "x",
135
+ "semantic": "input",
136
+ "buffer": { "type": "read-only-storage" },
137
+ "elementType": "$scalar"
138
+ },
139
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$scalar" },
140
+ {
141
+ "name": "params",
142
+ "semantic": "kernel.params",
143
+ "buffer": { "type": "uniform" },
144
+ "struct": {
145
+ "name": "Params",
146
+ "fields": [
147
+ {
148
+ "name": "rows",
149
+ "type": "u32",
150
+ "value": "rows(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)"
151
+ },
152
+ {
153
+ "name": "rowStride",
154
+ "type": "u32",
155
+ "value": "max(1, min(rows(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x), device.limits.maxComputeWorkgroupsPerDimension))"
156
+ }
157
+ ]
158
+ }
159
+ }
160
+ ],
161
+ "dispatch": { "workgroups": "rows(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)" }
162
+ }
163
+ ]
164
+ },
165
+ {
166
+ "id": "axis",
167
+ "when": ["ranks.x >= 1", "ranks.y == ranks.x", "numel(shapes.x) == numel(shapes.y)", "attrs.axis + ranks.x >= 0", "attrs.axis < ranks.x", "f16Ok(dtypes.T)"],
168
+ "constants": {
169
+ "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x",
170
+ "axisDim": "dim(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)",
171
+ "inner": "inner(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)"
172
+ },
173
+ "passes": [
174
+ {
175
+ "id": "main",
176
+ "name": "Hardmax",
177
+ "shader": "hardmax.wgsl.jinja",
178
+ "bindings": [
179
+ {
180
+ "name": "x",
181
+ "arg": "x",
182
+ "semantic": "input",
183
+ "buffer": { "type": "read-only-storage" },
184
+ "elementType": "$scalar"
185
+ },
186
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$scalar" },
187
+ {
188
+ "name": "params",
189
+ "semantic": "kernel.params",
190
+ "buffer": { "type": "uniform" },
191
+ "struct": {
192
+ "name": "Params",
193
+ "fields": [
194
+ {
195
+ "name": "rows",
196
+ "type": "u32",
197
+ "value": "rows(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)"
198
+ }
199
+ ]
200
+ }
201
+ }
202
+ ],
203
+ "dispatch": {
204
+ "threads": "rows(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x)",
205
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
206
+ }
207
+ }
208
+ ]
209
+ }
210
+ ]
211
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.Hardmax",
3
+ "id": "_ai_onnx_hardmax_webgpu_0d3207a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "bZefItlvlTUgPlW5qCO0jHoXmOmOqQo8vZMsx7mgh9A=",
11
+ "hardmax-axis-tree.wgsl.jinja": "1Qd9u20cZKcxIcoVUn8UpI27dye5BPfXCtyLSIpmNq8=",
12
+ "hardmax-last-axis-subgroup.wgsl.jinja": "YSX63Lf95J690RQT40ZWuwbBXDLOWNZp87ngY9T0UWE=",
13
+ "hardmax-last-axis-vec4.wgsl.jinja": "4S/FaIFjlrXr1LuWMV5X+3MgBQ6FA4JMZioHRoCjVfQ=",
14
+ "hardmax.wgsl.jinja": "xRSmORDzxQisVCQ78ZE+txGsYp+E/TBp8et510dk1mI=",
15
+ "manifest.json": "bbiRLG6GXQQCiUUgEJEgl943Ec7p4oPPgR5QiXLmT2c=",
16
+ "test.json": "CZ8WfjN4jnNK9CE1kbRfFuJeCyntQ40ql17EsKp87JI="
17
+ }
18
+ },
19
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
20
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Hardmax" }
21
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,613 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Hardmax",
3
+ "fixtureArrays": {
4
+ "ort_three_dims_input_x": [1.0856307, 0.99734545, 0.2829785, 1.5062947, 0.5786002, 1.6514366, 2.4266791, 0.42891264, 1.2659363, 0.8667404, 0.6788862, 0.09470897, 1.4913896, 0.638902, 0.44398195, 0.43435127, 2.20593, 2.1867862, 1.004054, 0.3861864, 0.7373686, 1.4907321, 0.9358339, 1.175829, 1.2538806, 0.6377515, 0.9071052, 1.4286807, 0.14006872, 0.8617549, 0.25561938, 2.798589, 1.7715331, 0.69987726, 0.92746246, 0.17363568, 0.002845916, 0.6882227, 0.87953633, 0.28362733, 0.8053665, 1.7276695, 0.3908998, 0.57380587, 0.33858904, 0.011830495, 2.3923652, 0.41291216, 0.978736, 2.2381434, 1.2940853, 1.0387882, 1.7437122, 0.79806274, 0.02968323, 1.0693159, 0.8907064, 1.7548862, 1.4956441, 1.0693927],
5
+ "onnx_backend_hardmax_input_x": [1.764052391052246, 0.40015721321105957, 0.978738009929657, 2.2408931255340576, 1.8675580024719238, -0.9772778749465942, 0.9500884413719177, -0.15135720372200012, -0.10321885347366333, 0.4105985164642334, 0.14404356479644775, 1.4542734622955322, 0.7610377073287964, 0.12167501449584961, 0.44386324286460876, 0.3336743414402008, 1.4940791130065918, -0.2051582634449005, 0.3130677044391632, -0.8540957570075989, -2.5529897212982178, 0.653618574142456, 0.8644362092018127, -0.7421650290489197, 2.269754648208618, -1.4543657302856445, 0.04575851559638977, -0.18718385696411133, 1.5327792167663574, 1.4693588018417358, 0.154947429895401, 0.37816253304481506, -0.8877857327461243, -1.980796456336975, -0.34791216254234314, 0.15634897351264954, 1.2302906513214111, 1.202379822731018, -0.38732680678367615, -0.302302747964859, -1.0485529899597168, -1.420017957687378, -1.7062702178955078, 1.950775384902954, -0.5096521973609924, -0.4380742907524109, -1.2527953386306763, 0.7774903774261475, -1.6138978004455566, -0.21274028718471527, -0.8954665660858154, 0.38690251111984253, -0.5108051300048828, -1.18063223361969, -0.02818222902715206, 0.4283318817615509, 0.06651721894741058, 0.30247190594673157, -0.6343221068382263, -0.3627411723136902],
6
+ "onnx_backend_example_4x4_input_x": [3, 0, 1, 2, 2, 5, 1, 0, 0, 1, 3, 2, 0, 1, 2, 3]
7
+ },
8
+ "cases": [
9
+ {
10
+ "name": "f32_positive_subnormal_beats_zero_gpu_gap",
11
+ "skipGpu": {
12
+ "category": "permanent",
13
+ "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."
14
+ },
15
+ "provenance": {
16
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
17
+ "test": "HardmaxOperator.Simple",
18
+ "notes": "A positive subnormal is strictly greater than zero; Hardmax should put the one-hot at that element rather than tie-breaking on a flushed zero."
19
+ },
20
+ "attrs": { "axis": -1 },
21
+ "inputs": {
22
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40] } }
23
+ },
24
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3], "tolerance": 0 } }
25
+ },
26
+ {
27
+ "name": "f32_positive_subnormal_beats_zero_axis0_gpu_gap",
28
+ "skipGpu": {
29
+ "category": "permanent",
30
+ "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."
31
+ },
32
+ "provenance": {
33
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
34
+ "test": "HardmaxOperator.Simple",
35
+ "notes": "Axis-0 companion for finite subnormal ordering: a positive subnormal is strictly greater than zero and should receive the one-hot."
36
+ },
37
+ "attrs": { "axis": 0 },
38
+ "inputs": {
39
+ "x": { "dtype": "float32", "shape": [3, 1], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40] } }
40
+ },
41
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1], "tolerance": 0 } }
42
+ },
43
+ {
44
+ "name": "f32_positive_subnormal_beats_zero_last_axis_vec4_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/math/hardmax_test.cc",
51
+ "test": "HardmaxOperator.Simple",
52
+ "notes": "Vec4 last-axis companion: a positive subnormal is strictly greater than zero and should win over zero-valued lanes."
53
+ },
54
+ "attrs": { "axis": -1 },
55
+ "inputs": {
56
+ "x": { "dtype": "float32", "shape": [1, 4], "data": { "kind": "values", "values": [0.0, 1e-40, 0.0, -1e-40] } }
57
+ },
58
+ "outputs": {
59
+ "y": {
60
+ "dtype": "float32",
61
+ "shape": [1, 4],
62
+ "tolerance": 0,
63
+ "data": { "kind": "values", "values": [0.0, 1.0, 0.0, 0.0] }
64
+ }
65
+ }
66
+ },
67
+ {
68
+ "name": "f32_positive_subnormal_beats_zero_last_axis_subgroup_gpu_gap",
69
+ "skipGpu": {
70
+ "category": "permanent",
71
+ "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."
72
+ },
73
+ "provenance": {
74
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
75
+ "test": "HardmaxOperator.LargeNumber",
76
+ "notes": "Subgroup last-axis companion: width 1025 bypasses the vec4 specialization, and a positive subnormal must still beat zero-valued lanes."
77
+ },
78
+ "attrs": { "axis": -1 },
79
+ "inputs": {
80
+ "x": {
81
+ "dtype": "float32",
82
+ "shape": [1, 1025],
83
+ "data": { "kind": "cycle", "values": [0.0, 1e-40, 0.0, -1e-40] }
84
+ }
85
+ },
86
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1025], "tolerance": 0 } }
87
+ },
88
+ {
89
+ "name": "dispatch_cliff_axis_last_dim1",
90
+ "attrs": { "axis": -1 },
91
+ "inputs": { "x": { "dtype": "float32", "shape": [16776961, 1], "data": { "kind": "constant", "value": 1.0 } } },
92
+ "outputs": { "y": { "dtype": "float32", "shape": [16776961, 1], "tolerance": 0 } }
93
+ },
94
+ {
95
+ "name": "simple_last_axis",
96
+ "provenance": {
97
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
98
+ "test": "HardmaxOperator.Simple"
99
+ },
100
+ "inputs": {
101
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [-1.0, 0.0, 1.0] } }
102
+ },
103
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3] } }
104
+ },
105
+ {
106
+ "name": "onnx_backend_example_4x4",
107
+ "provenance": {
108
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_example",
109
+ "test": "test_hardmax_example"
110
+ },
111
+ "inputs": {
112
+ "x": {
113
+ "dtype": "float32",
114
+ "shape": [4, 4],
115
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_example_4x4_input_x" } }
116
+ }
117
+ },
118
+ "outputs": { "y": { "dtype": "float32", "shape": [4, 4], "tolerance": 0 } }
119
+ },
120
+ {
121
+ "name": "onnx_backend_one_hot_tie_first",
122
+ "provenance": {
123
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_one_hot",
124
+ "test": "test_hardmax_one_hot"
125
+ },
126
+ "inputs": {
127
+ "x": { "dtype": "float32", "shape": [1, 4], "data": { "kind": "values", "values": [3.0, 3.0, 3.0, 1.0] } }
128
+ },
129
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4], "tolerance": 0 } }
130
+ },
131
+ {
132
+ "name": "large_values",
133
+ "provenance": {
134
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
135
+ "test": "HardmaxOperator.LargeNumber"
136
+ },
137
+ "inputs": {
138
+ "x": {
139
+ "dtype": "float32",
140
+ "shape": [2, 4],
141
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 10000.0, 10001.0, 10002.0, 10003.0] }
142
+ }
143
+ },
144
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4] } }
145
+ },
146
+ {
147
+ "name": "ort_compact_axis0_f32",
148
+ "provenance": {
149
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
150
+ "test": "HardmaxOperator.ThreeDimsAxis0",
151
+ "notes": "Compact axis-0 variant covering the same non-last-axis behavior without the full ORT random tensor."
152
+ },
153
+ "attrs": { "axis": 0 },
154
+ "inputs": {
155
+ "x": {
156
+ "dtype": "float32",
157
+ "shape": [3, 2],
158
+ "data": { "kind": "values", "values": [1.0, 5.0, 9.0, 4.0, 7.0, 6.0] }
159
+ }
160
+ },
161
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2], "tolerance": 0 } }
162
+ },
163
+ {
164
+ "name": "axis1_rank3",
165
+ "attrs": { "axis": 1 },
166
+ "inputs": {
167
+ "x": {
168
+ "dtype": "float32",
169
+ "shape": [2, 3, 2],
170
+ "data": { "kind": "values", "values": [1.0, 9.0, 7.0, 4.0, 5.0, 6.0, 3.0, 2.0, 8.0, 1.0, 4.0, 5.0] }
171
+ }
172
+ },
173
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 2] } }
174
+ },
175
+ {
176
+ "name": "negative_axis",
177
+ "attrs": { "axis": -2 },
178
+ "inputs": {
179
+ "x": {
180
+ "dtype": "float32",
181
+ "shape": [2, 3, 2],
182
+ "data": { "kind": "values", "values": [1.0, 9.0, 7.0, 4.0, 5.0, 6.0, 3.0, 2.0, 8.0, 1.0, 4.0, 5.0] }
183
+ }
184
+ },
185
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 2] } }
186
+ },
187
+ {
188
+ "name": "ties_choose_first",
189
+ "inputs": {
190
+ "x": {
191
+ "dtype": "float32",
192
+ "shape": [2, 4],
193
+ "data": { "kind": "values", "values": [1.0, 3.0, 3.0, 2.0, 5.0, 5.0, 1.0, 5.0] }
194
+ }
195
+ },
196
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4] } }
197
+ },
198
+ {
199
+ "name": "ort_axis1_nan_rows",
200
+ "provenance": {
201
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
202
+ "test": "HardmaxOperator.Simple",
203
+ "notes": "Compatibility NaN behavior on the last axis: a leading NaN or all-NaN row produces all zeros, while a NaN after a finite incumbent does not replace the selected maximum."
204
+ },
205
+ "attrs": { "axis": 1 },
206
+ "inputs": {
207
+ "x": {
208
+ "dtype": "float32",
209
+ "shape": [4, 4],
210
+ "data": {
211
+ "kind": "values",
212
+ "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"]
213
+ }
214
+ }
215
+ },
216
+ "outputs": {
217
+ "y": {
218
+ "dtype": "float32",
219
+ "shape": [4, 4],
220
+ "tolerance": 0,
221
+ "data": {
222
+ "kind": "values",
223
+ "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
224
+ }
225
+ }
226
+ }
227
+ },
228
+ {
229
+ "name": "ort_rank3_axis1_nan_columns",
230
+ "provenance": {
231
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
232
+ "test": "HardmaxOperator.ThreeDimsAxis1_opset13",
233
+ "notes": "Compatibility NaN behavior on a non-last axis: leading-NaN and all-NaN reduced columns produce all zeros, while later NaNs after a finite value are ignored."
234
+ },
235
+ "attrs": { "axis": 1 },
236
+ "inputs": {
237
+ "x": {
238
+ "dtype": "float32",
239
+ "shape": [2, 3, 2],
240
+ "data": {
241
+ "kind": "values",
242
+ "values": ["NaN", 1.0, 1.0, "NaN", 2.0, 2.0, 1.0, "NaN", 2.0, "NaN", "NaN", "NaN"]
243
+ }
244
+ }
245
+ },
246
+ "outputs": {
247
+ "y": {
248
+ "dtype": "float32",
249
+ "shape": [2, 3, 2],
250
+ "tolerance": 0,
251
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0] }
252
+ }
253
+ }
254
+ },
255
+ {
256
+ "name": "float16_axis0",
257
+ "attrs": { "axis": 0 },
258
+ "inputs": {
259
+ "x": {
260
+ "dtype": "float16",
261
+ "shape": [3, 2],
262
+ "data": { "kind": "values", "values": [1.0, 5.0, 4.0, 2.0, 3.0, 6.0] }
263
+ }
264
+ },
265
+ "outputs": { "y": { "dtype": "float16", "shape": [3, 2], "tolerance": 0.001 } }
266
+ },
267
+ {
268
+ "name": "rank4_axis2_ties_choose_first",
269
+ "attrs": { "axis": 2 },
270
+ "inputs": {
271
+ "x": {
272
+ "dtype": "float32",
273
+ "shape": [1, 2, 3, 2],
274
+ "data": { "kind": "values", "values": [1.0, 9.0, 3.0, 9.0, 3.0, 8.0, 5.0, 0.0, 5.0, 1.0, 4.0, 1.0] }
275
+ }
276
+ },
277
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 3, 2], "tolerance": 0 } }
278
+ },
279
+ {
280
+ "name": "float16_ties_choose_first",
281
+ "inputs": {
282
+ "x": {
283
+ "dtype": "float16",
284
+ "shape": [2, 4],
285
+ "data": { "kind": "values", "values": [1.0, 3.0, 3.0, 2.0, 5.0, 5.0, 1.0, 5.0] }
286
+ }
287
+ },
288
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 4], "tolerance": 0 } }
289
+ },
290
+ {
291
+ "name": "last_axis_64_tie_first_f32",
292
+ "inputs": {
293
+ "x": {
294
+ "dtype": "float32",
295
+ "shape": [1, 64],
296
+ "data": {
297
+ "kind": "values",
298
+ "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 100.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 100.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0]
299
+ }
300
+ }
301
+ },
302
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64], "tolerance": 0 } }
303
+ },
304
+ {
305
+ "name": "last_axis_128_f16",
306
+ "inputs": {
307
+ "x": {
308
+ "dtype": "float16",
309
+ "shape": [2, 128],
310
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
311
+ }
312
+ },
313
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 128], "tolerance": 0 } }
314
+ },
315
+ {
316
+ "name": "last_axis_1025_f32",
317
+ "inputs": {
318
+ "x": {
319
+ "dtype": "float32",
320
+ "shape": [1, 1025],
321
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.19 }
322
+ }
323
+ },
324
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1025], "tolerance": 0 } }
325
+ },
326
+ {
327
+ "name": "ort_three_dims_axis1_opset13",
328
+ "provenance": {
329
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
330
+ "test": "HardmaxOperator.ThreeDimsAxis1_opset13"
331
+ },
332
+ "attrs": { "axis": 1 },
333
+ "inputs": {
334
+ "x": {
335
+ "dtype": "float32",
336
+ "shape": [3, 4, 5],
337
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_three_dims_input_x" } }
338
+ }
339
+ },
340
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
341
+ },
342
+ {
343
+ "name": "ort_three_dims_default_axis_opset13",
344
+ "provenance": {
345
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
346
+ "test": "HardmaxOperator.ThreeDimsDefaultAxis_opset13"
347
+ },
348
+ "inputs": {
349
+ "x": {
350
+ "dtype": "float32",
351
+ "shape": [3, 4, 5],
352
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_three_dims_input_x" } }
353
+ }
354
+ },
355
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
356
+ },
357
+ {
358
+ "name": "ort_three_dims_axis2_opset13",
359
+ "provenance": {
360
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
361
+ "test": "HardmaxOperator.ThreeDimsAxis2_opset13"
362
+ },
363
+ "attrs": { "axis": 2 },
364
+ "inputs": {
365
+ "x": {
366
+ "dtype": "float32",
367
+ "shape": [3, 4, 5],
368
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_three_dims_input_x" } }
369
+ }
370
+ },
371
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
372
+ },
373
+ {
374
+ "name": "ort_three_dims_negative_axis_last",
375
+ "provenance": {
376
+ "source": "onnxruntime/test/providers/cpu/math/hardmax_test.cc",
377
+ "test": "HardmaxOperator.ThreeDimsNegAxis2",
378
+ "notes": "Axis=-1 maps to the last dimension; this matches opset-13 last-axis semantics."
379
+ },
380
+ "attrs": { "axis": -1 },
381
+ "inputs": {
382
+ "x": {
383
+ "dtype": "float32",
384
+ "shape": [3, 4, 5],
385
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_three_dims_input_x" } }
386
+ }
387
+ },
388
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
389
+ },
390
+ {
391
+ "name": "onnx_backend_hardmax_axis_0",
392
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_axis_0" },
393
+ "attrs": { "axis": 0 },
394
+ "inputs": {
395
+ "x": {
396
+ "dtype": "float32",
397
+ "shape": [3, 4, 5],
398
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_hardmax_input_x" } }
399
+ }
400
+ },
401
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
402
+ },
403
+ {
404
+ "name": "onnx_backend_hardmax_axis_1",
405
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_axis_1" },
406
+ "attrs": { "axis": 1 },
407
+ "inputs": {
408
+ "x": {
409
+ "dtype": "float32",
410
+ "shape": [3, 4, 5],
411
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_hardmax_input_x" } }
412
+ }
413
+ },
414
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
415
+ },
416
+ {
417
+ "name": "onnx_backend_hardmax_axis_2",
418
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_axis_2" },
419
+ "attrs": { "axis": 2 },
420
+ "inputs": {
421
+ "x": {
422
+ "dtype": "float32",
423
+ "shape": [3, 4, 5],
424
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_hardmax_input_x" } }
425
+ }
426
+ },
427
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
428
+ },
429
+ {
430
+ "name": "onnx_backend_hardmax_default_axis",
431
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_default_axis" },
432
+ "inputs": {
433
+ "x": {
434
+ "dtype": "float32",
435
+ "shape": [3, 4, 5],
436
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_hardmax_input_x" } }
437
+ }
438
+ },
439
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
440
+ },
441
+ {
442
+ "name": "onnx_backend_hardmax_negative_axis",
443
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_negative_axis" },
444
+ "attrs": { "axis": -1 },
445
+ "inputs": {
446
+ "x": {
447
+ "dtype": "float32",
448
+ "shape": [3, 4, 5],
449
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_hardmax_input_x" } }
450
+ }
451
+ },
452
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0 } }
453
+ },
454
+ {
455
+ "name": "onnx_backend_hardmax_example",
456
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_example" },
457
+ "inputs": {
458
+ "x": {
459
+ "dtype": "float32",
460
+ "shape": [4, 4],
461
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_example_4x4_input_x" } }
462
+ }
463
+ },
464
+ "outputs": { "y": { "dtype": "float32", "shape": [4, 4], "tolerance": 0.0001 } }
465
+ },
466
+ {
467
+ "name": "onnx_backend_hardmax_one_hot",
468
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hardmax_one_hot" },
469
+ "inputs": {
470
+ "x": { "dtype": "float32", "shape": [1, 4], "data": { "kind": "values", "values": [3.0, 3.0, 3.0, 1.0] } }
471
+ },
472
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4], "tolerance": 0.0001 } }
473
+ },
474
+ {
475
+ "name": "empty_input_zero_dim",
476
+ "inputs": { "x": { "dtype": "float32", "shape": [0, 3], "data": { "kind": "values", "values": [] } } },
477
+ "outputs": { "y": { "dtype": "float32", "shape": [0, 3], "tolerance": 0 } }
478
+ },
479
+ {
480
+ "name": "rank7_last_axis",
481
+ "provenance": {
482
+ "source": "ONNX spec: Hardmax permits arbitrary rank; onnxruntime CPU coerces dims [0,axis) to rows and [axis,end) to cols with no rank cap.",
483
+ "notes": "Rank-7 last-axis coverage for the generalized last_axis_vec4 path. ORT and WebGPU both compute the normal one-hot result."
484
+ },
485
+ "attrs": { "axis": -1 },
486
+ "inputs": {
487
+ "x": {
488
+ "dtype": "float32",
489
+ "shape": [2, 1, 1, 1, 1, 1, 4],
490
+ "data": { "kind": "values", "values": [3.0, 1.0, 4.0, 2.0, 5.0, 9.0, 1.0, 6.0] }
491
+ }
492
+ },
493
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 1, 1, 1, 1, 4], "tolerance": 0 } }
494
+ },
495
+ {
496
+ "name": "rank7_axis0",
497
+ "provenance": {
498
+ "source": "ONNX spec: Hardmax permits arbitrary rank; onnxruntime CPU reduces over the requested axis at any rank.",
499
+ "notes": "Rank-7 non-last-axis coverage for the generic axis path. ORT and WebGPU reduce over axis 0 per inner column."
500
+ },
501
+ "attrs": { "axis": 0 },
502
+ "inputs": {
503
+ "x": {
504
+ "dtype": "float32",
505
+ "shape": [3, 1, 1, 1, 1, 1, 2],
506
+ "data": { "kind": "values", "values": [1.0, 5.0, 9.0, 4.0, 7.0, 6.0] }
507
+ }
508
+ },
509
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 1, 1, 1, 1, 2], "tolerance": 0 } }
510
+ },
511
+ {
512
+ "name": "f16_last_axis_subgroup_1025",
513
+ "provenance": {
514
+ "source": "Clean coverage: the adaptive f16 last-axis row path (dim(last) >= 1024, f16Ok) was correctness-untested (only f16 vec4 cols=128 and f16 axis-0 rank2 existed).",
515
+ "notes": "Last dim 1025 (>=1024, not %4==0) skips last_axis_vec4 and selects last_axis_row with f16. linspace 0..1025 gives exact distinct f16 integers (representable to 2048) so argmax is unambiguous at the final lane."
516
+ },
517
+ "attrs": { "axis": -1 },
518
+ "inputs": {
519
+ "x": { "dtype": "float16", "shape": [1, 1025], "data": { "kind": "linspace", "start": 0.0, "end": 1025.0 } }
520
+ },
521
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1025], "tolerance": 0 } }
522
+ },
523
+ {
524
+ "name": "f16_axis1_rank3",
525
+ "provenance": {
526
+ "source": "Clean coverage: f16 'axis' scalar fallback on a non-last axis (existing f16 fixtures only cover axis-0 rank2 and last-axis rank2).",
527
+ "notes": "Rank-3 axis=1 f16 selects the generic 'axis' variant with usesF16. Distinct per-column values avoid ties."
528
+ },
529
+ "attrs": { "axis": 1 },
530
+ "inputs": {
531
+ "x": {
532
+ "dtype": "float16",
533
+ "shape": [2, 3, 2],
534
+ "data": { "kind": "values", "values": [1.0, 9.0, 7.0, 4.0, 5.0, 6.0, 3.0, 2.0, 8.0, 1.0, 4.0, 5.0] }
535
+ }
536
+ },
537
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 3, 2], "tolerance": 0 } }
538
+ },
539
+ {
540
+ "name": "vec4_lone_finite_among_neg_inf",
541
+ "attrs": { "axis": -1 },
542
+ "provenance": {
543
+ "source": "Coverage gap: vec4 last-axis path with -Infinity seed. Existing vec4 tests use finite fillFloat32/values; a row of all -Infinity except one finite element is untested.",
544
+ "notes": "cols=8 selects last_axis_vec4. Only index 6 is finite (3.0); every other lane is -Infinity, so the one-hot must land at index 6."
545
+ },
546
+ "inputs": {
547
+ "x": {
548
+ "dtype": "float32",
549
+ "shape": [1, 8],
550
+ "data": {
551
+ "kind": "values",
552
+ "values": ["-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", 3.0, "-Infinity"]
553
+ }
554
+ }
555
+ },
556
+ "outputs": {
557
+ "y": {
558
+ "dtype": "float32",
559
+ "shape": [1, 8],
560
+ "tolerance": 0,
561
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0] }
562
+ }
563
+ }
564
+ },
565
+ {
566
+ "name": "subgroup_cross_slot_lowest_index_tie_1025",
567
+ "attrs": { "axis": -1 },
568
+ "provenance": {
569
+ "source": "Coverage gap: subgroup last-axis lowest-index tie reduction. Existing 1025-wide subgroup tests use unique-max linspace, so cross-subgroup/slot ties are untested.",
570
+ "notes": "cols=1025 (>=1024, %4!=0) selects last_axis_row, whose capability-adaptive reduction uses subgroup or portable execution. Alternating 0/1 makes 1.0 the max at every odd index; first occurrence is index 1, so the one-hot must land at index 1 despite ties spanning multiple reduction slots."
571
+ },
572
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1025], "data": { "kind": "cycle", "values": [0.0, 1.0] } } },
573
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1025], "tolerance": 0 } }
574
+ },
575
+ {
576
+ "name": "f16_subgroup_all_equal_tie_index0_1025",
577
+ "attrs": { "axis": -1 },
578
+ "provenance": {
579
+ "source": "Coverage gap: the adaptive f16 last-axis row path under a full tie. The existing f16_last_axis_subgroup_1025 fixture uses distinct linspace values, so an all-equal f16 row is untested.",
580
+ "notes": "cols=1025 (>=1024, %4!=0) with f16 selects the adaptive last_axis_row path. All values equal (1.0) => first max is index 0, so the one-hot must land at index 0."
581
+ },
582
+ "inputs": { "x": { "dtype": "float16", "shape": [1, 1025], "data": { "kind": "constant", "value": 1.0 } } },
583
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1025], "tolerance": 0 } }
584
+ },
585
+ {
586
+ "name": "axis_tree_axis0_1024x2",
587
+ "attrs": { "axis": 0 },
588
+ "inputs": {
589
+ "x": {
590
+ "dtype": "float32",
591
+ "shape": [1024, 2],
592
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 2.0 }
593
+ }
594
+ },
595
+ "outputs": { "y": { "dtype": "float32", "shape": [1024, 2], "tolerance": 0 } }
596
+ },
597
+ {
598
+ "name": "rank8_last_axis",
599
+ "attrs": { "axis": -1 },
600
+ "inputs": {
601
+ "x": {
602
+ "dtype": "float32",
603
+ "shape": [2, 1, 1, 1, 1, 1, 2, 4],
604
+ "data": {
605
+ "kind": "values",
606
+ "values": [3.0, 1.0, 4.0, 2.0, 5.0, 9.0, 1.0, 6.0, 2.0, 7.0, 1.0, 8.0, 0.5, 0.25, 3.5, 1.25]
607
+ }
608
+ }
609
+ },
610
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 1, 1, 1, 1, 2, 4], "tolerance": 0 } }
611
+ }
612
+ ]
613
+ }