Xenova HF Staff commited on
Commit
758a8fa
·
verified ·
1 Parent(s): a76bbb1

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,89 @@
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.ReduceMean
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 18
12
+
13
+ ## Description
14
+
15
+ Computes the mean of the input tensor's elements along the specified axes. The output rank matches the input if `keepdims` is 1; otherwise the reduced dimensions are pruned. When `axes` is empty, behavior is controlled by `noop_with_empty_axes`: reduce over all axes (default) or act as a no-op.
16
+
17
+ See the [ONNX `ReduceMean` spec](https://onnx.ai/onnx/operators/onnx__ReduceMean.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `data` | `x` | `T` | — | — | Input tensor to reduce. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- |
29
+ | `reduced` | `y` | `T` | derived | — | Reduced output tensor with mean values along the specified axes. | required |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `keepdims` | `1` | If 1, retains the reduced dimensions with size 1 in the output shape; if 0, the reduced dimensions are removed. |
38
+ | `noop_with_empty_axes` | `0` | When `axes` is empty, `0` (the default) reduces over all axes and `1` skips reduction as a no-op. |
39
+ | `axes` | `[]` | Values of the optional ONNX `axes` tensor input, supplied through this request attribute; an empty list follows `noop_with_empty_axes`. |
40
+
41
+ ## Type constraints
42
+
43
+ | Variable | Allowed dtypes |
44
+ | --- | --- |
45
+ | `T` | `float32`, `float16`, `int32` |
46
+
47
+ ## Device requirements
48
+
49
+ Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
50
+
51
+ ## Files
52
+
53
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
54
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
+ - [`test.json`](build/webgpu/test.json) — correctness cases
56
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
57
+ - [`reduce-axis-split-reduce.wgsl.jinja`](build/webgpu/reduce-axis-split-reduce.wgsl.jinja)
58
+ - [`reduce-axis0-splitk-combine.wgsl.jinja`](build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja)
59
+ - [`reduce-axis0-splitk-reduce.wgsl.jinja`](build/webgpu/reduce-axis0-splitk-reduce.wgsl.jinja)
60
+ - [`reduce-axis0-tilecols.wgsl.jinja`](build/webgpu/reduce-axis0-tilecols.wgsl.jinja)
61
+ - [`reduce-flat-partial.wgsl.jinja`](build/webgpu/reduce-flat-partial.wgsl.jinja)
62
+ - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
63
+ - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
64
+ - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
65
+ - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
66
+
67
+ ## Use with `@huggingface/kernels`
68
+
69
+ The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
70
+
71
+ The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
72
+
73
+ - `y`
74
+
75
+ Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
76
+
77
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
78
+
79
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
80
+
81
+ ```js
82
+ import { getKernel } from "@huggingface/kernels";
83
+
84
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.ReduceMean", { version: 1 });
85
+ // Explicit destinations request optional results or supply metadata that cannot be inferred.
86
+ const { y } = await kernel({ x: { data: xData, shape: [] } }, {
87
+ outputs: { y: { shape: [], dtype: "float32" } },
88
+ });
89
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ReduceMean",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256], "MIDDLE_AXIS_TILE_COLS": [16, 32, 64] },
4
+ "cases": [
5
+ {
6
+ "name": "reduce-mean-axis0-1024x512",
7
+ "preset": "smoke",
8
+ "vars": { "dtype": "float32", "rows": 1024, "cols": 512 },
9
+ "attrs": { "axes": [0], "keepdims": 0 },
10
+ "inputs": { "x": { "shape": [1024, 512], "dtype": "float32", "dist": "normal", "seed": 881, "scale": 1 } },
11
+ "outputs": { "y": { "shape": [512], "dtype": "float32" } },
12
+ "bench": {
13
+ "primary": true,
14
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }]
15
+ }
16
+ },
17
+ {
18
+ "name": "reduce-mean-axis1-1024x4096",
19
+ "preset": "smoke",
20
+ "vars": { "dtype": "float32", "rows": 1024, "cols": 4096 },
21
+ "attrs": { "axes": [1], "keepdims": 0 },
22
+ "inputs": { "x": { "shape": [1024, 4096], "dtype": "float32", "dist": "normal", "seed": 880, "scale": 1 } },
23
+ "outputs": { "y": { "shape": [1024], "dtype": "float32" } },
24
+ "bench": {
25
+ "primary": true,
26
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }]
27
+ }
28
+ },
29
+ {
30
+ "name": "reducemean-lastaxis-f32-4096x4096",
31
+ "preset": "smoke",
32
+ "vars": { "rows": 4096, "cols": 4096 },
33
+ "attrs": { "axes": [1], "keepdims": 0 },
34
+ "inputs": { "x": { "shape": [4096, 4096], "dtype": "float32", "dist": "normal", "seed": 117, "scale": 0.2 } },
35
+ "outputs": { "y": { "shape": [4096], "dtype": "float32" } },
36
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
37
+ },
38
+ {
39
+ "name": "reducemean-rank3-lastaxis-f32-64x64x4096",
40
+ "preset": "smoke",
41
+ "vars": { "rows": 4096, "cols": 4096 },
42
+ "attrs": { "axes": [2], "keepdims": 0 },
43
+ "inputs": { "x": { "shape": [64, 64, 4096], "dtype": "float32", "dist": "normal", "seed": 218, "scale": 0.2 } },
44
+ "outputs": { "y": { "shape": [64, 64], "dtype": "float32" } },
45
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
46
+ },
47
+ {
48
+ "name": "reducemean-rank3-lastaxis-smallcols-f32-1024x32x16",
49
+ "preset": "smoke",
50
+ "vars": { "rows": 32768, "cols": 16 },
51
+ "attrs": { "axes": [2], "keepdims": 0 },
52
+ "inputs": { "x": { "shape": [1024, 32, 16], "dtype": "float32", "dist": "normal", "seed": 412, "scale": 0.2 } },
53
+ "outputs": { "y": { "shape": [1024, 32], "dtype": "float32" } },
54
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
55
+ },
56
+ {
57
+ "name": "axis0-large-65536x512",
58
+ "preset": "smoke",
59
+ "vars": { "rows": 65536, "cols": 512 },
60
+ "attrs": { "axes": [0], "keepdims": 0 },
61
+ "inputs": { "x": { "shape": [65536, 512], "dtype": "float32", "dist": "normal", "scale": 0.2, "seed": 311 } },
62
+ "outputs": { "y": { "shape": [512], "dtype": "float32" } },
63
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
64
+ },
65
+ {
66
+ "name": "axis0-tall-16384x1024",
67
+ "preset": "smoke",
68
+ "vars": { "rows": 16384, "cols": 1024 },
69
+ "attrs": { "axes": [0], "keepdims": 0 },
70
+ "inputs": { "x": { "shape": [16384, 1024], "dtype": "float32", "dist": "normal", "scale": 0.2, "seed": 312 } },
71
+ "outputs": { "y": { "shape": [1024], "dtype": "float32" } },
72
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
73
+ },
74
+ {
75
+ "name": "reducemean-rank4-axis1-serial-32x256x28x28",
76
+ "preset": "smoke",
77
+ "vars": { "dtype": "float32" },
78
+ "attrs": { "axes": [1], "keepdims": 0 },
79
+ "inputs": {
80
+ "x": { "shape": [32, 256, 28, 28], "dtype": "float32", "dist": "normal", "scale": 0.2, "seed": 5511 }
81
+ },
82
+ "outputs": { "y": { "shape": [32, 28, 28], "dtype": "float32" } },
83
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "32 * 256 * 28 * 28 * dtypeBytes(args.dtype)" }] }
84
+ },
85
+ {
86
+ "name": "reducemean-rank3-axis1-serial-8x1024x768",
87
+ "preset": "smoke",
88
+ "vars": { "dtype": "float32" },
89
+ "attrs": { "axes": [1], "keepdims": 0 },
90
+ "inputs": { "x": { "shape": [8, 1024, 768], "dtype": "float32", "dist": "normal", "scale": 0.2, "seed": 5512 } },
91
+ "outputs": { "y": { "shape": [8, 768], "dtype": "float32" } },
92
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "8 * 1024 * 768 * dtypeBytes(args.dtype)" }] }
93
+ },
94
+ {
95
+ "name": "reducemean-lastaxis-scalar-subgroup-cols-unaligned-4096x4095",
96
+ "preset": "smoke",
97
+ "vars": { "dtype": "float32", "rows": 4096, "cols": 4095 },
98
+ "attrs": { "axes": [1], "keepdims": 0 },
99
+ "inputs": { "x": { "shape": [4096, 4095], "dtype": "float32", "dist": "normal", "scale": 0.2, "seed": 5513 } },
100
+ "outputs": { "y": { "shape": [4096], "dtype": "float32" } },
101
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
102
+ },
103
+ {
104
+ "name": "reducemean-fullreduce-serial-numel-not-mul4-r3",
105
+ "preset": "stress",
106
+ "attrs": { "keepdims": 0 },
107
+ "inputs": { "x": { "shape": [1001, 1001, 1], "dtype": "float32", "dist": "normal", "seed": 601, "scale": 1 } },
108
+ "outputs": { "y": { "shape": [], "dtype": "float32" } },
109
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "1001 * 1001 * 1 * 4" }] }
110
+ },
111
+ {
112
+ "name": "reducemean-int32-lastaxis-serial-4096x1024",
113
+ "preset": "stress",
114
+ "attrs": { "axes": [1], "keepdims": 0 },
115
+ "inputs": {
116
+ "x": { "shape": [4096, 1024], "dtype": "int32", "dist": "randint", "seed": 602, "min": -1000, "max": 1000 }
117
+ },
118
+ "outputs": { "y": { "shape": [4096], "dtype": "int32" } },
119
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4096 * 1024 * 4" }] }
120
+ },
121
+ {
122
+ "name": "reducemean-axis0-f32-131072x15-narrow-splitk-boundary",
123
+ "preset": "stress",
124
+ "vars": { "rows": 131072, "cols": 15 },
125
+ "attrs": { "axes": [0], "keepdims": 0 },
126
+ "inputs": { "x": { "shape": [131072, 15], "dtype": "float32", "dist": "normal", "seed": 603, "scale": 0.2 } },
127
+ "outputs": { "y": { "shape": [15], "dtype": "float32", "dist": "empty" } },
128
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * 4" }] }
129
+ },
130
+ {
131
+ "name": "reducemean-spatial-axes23-f32-2x64x256x256-globalpool-pattern",
132
+ "preset": "stress",
133
+ "provenance": {
134
+ "source": "authored for variant coverage",
135
+ "notes": "Rank-4 contiguous-suffix reduction that verifies the vec4 subgroup reducer and portable workgroup-tree fallback."
136
+ },
137
+ "vars": { "batch": 2, "channels": 64, "height": 256, "width": 256 },
138
+ "attrs": { "axes": [2, 3], "keepdims": 1 },
139
+ "inputs": { "x": { "shape": [2, 64, 256, 256], "dtype": "float32", "dist": "normal", "seed": 909, "scale": 0.2 } },
140
+ "outputs": { "y": { "shape": [2, 64, 1, 1], "dtype": "float32", "dist": "empty" } },
141
+ "bench": {
142
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.height * args.width * 4" }]
143
+ }
144
+ },
145
+ {
146
+ "name": "reducemean-rank4-axis1-largeaxis-2x131072x1x8",
147
+ "preset": "smoke",
148
+ "vars": { "numel": 2097152 },
149
+ "attrs": { "axes": [1], "keepdims": 0 },
150
+ "inputs": { "x": { "shape": [2, 131072, 1, 8], "dtype": "float32", "dist": "normal", "seed": 120, "scale": 0.2 } },
151
+ "outputs": { "y": { "shape": [2, 1, 8], "dtype": "float32" } },
152
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.numel * 4" }] }
153
+ }
154
+ ]
155
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,1085 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "ReduceMean",
4
+ "sinceVersion": 18,
5
+ "description": "Computes the mean of the input tensor's elements along the specified axes. The output rank matches the input if `keepdims` is 1; otherwise the reduced dimensions are pruned. When `axes` is empty, behavior is controlled by `noop_with_empty_axes`: reduce over all axes (default) or act as a no-op.",
6
+ "inputs": [{ "role": "data", "dtype": "T", "description": "Input tensor to reduce." }],
7
+ "outputs": [
8
+ {
9
+ "role": "reduced",
10
+ "dtype": "T",
11
+ "rank": "ranks.data if attrs.keepdims == 1 or ((attrs.axes | length) == 0 and attrs.noop_with_empty_axes == 1) else (ranks.data - (attrs.axes | length) if (attrs.axes | length) > 0 else 0)",
12
+ "description": "Reduced output tensor with mean values along the specified axes."
13
+ }
14
+ ],
15
+ "attributes": { "keepdims": 1, "noop_with_empty_axes": 0, "axes": [] },
16
+ "attributeDescriptions": {
17
+ "keepdims": "If 1, retains the reduced dimensions with size 1 in the output shape; if 0, the reduced dimensions are removed.",
18
+ "noop_with_empty_axes": "When `axes` is empty, `0` (the default) reduces over all axes and `1` skips reduction as a no-op.",
19
+ "axes": "Values of the optional ONNX `axes` tensor input, supplied through this request attribute; an empty list follows `noop_with_empty_axes`."
20
+ },
21
+ "attributeConstraints": { "keepdims": { "values": [0, 1] }, "noop_with_empty_axes": { "values": [0, 1] } },
22
+ "typeConstraints": { "T": ["float32", "float16", "int32"] },
23
+ "args": {
24
+ "x": { "kind": "tensor", "semantic": "data", "role": "input" },
25
+ "y": { "kind": "tensor", "semantic": "reduced", "role": "output" }
26
+ },
27
+ "derive": {
28
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
29
+ "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
30
+ "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
31
+ "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
32
+ "treeWorkgroupOk": "reduceWorkgroupSize > 0 and pow2ceil(reduceWorkgroupSize) == reduceWorkgroupSize and reduceWorkgroupSize * dtypeBytes(\"float32\") <= device.limits.maxComputeWorkgroupStorageSize",
33
+ "subgroupWorkgroupFloor": "min(reduceWorkgroupSize, max(1, device.adapterInfo.subgroupMaxSize))",
34
+ "lastAxisRows": "rows(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
35
+ "lastAxisCols": "dim(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
36
+ "rowSerialPreferred": "lastAxisRows >= tunables.ROW_SERIAL_MIN_ROWS and lastAxisCols <= tunables.ROW_SERIAL_MAX_COLS",
37
+ "axis0Rows": "dim(shapes.data, 0) if ranks.data >= 2 else 0",
38
+ "axis0Cols": "dim(shapes.data, 1) if ranks.data >= 2 else 0",
39
+ "axis0SplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axis0Rows, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
40
+ "axis0SplitScratchBytes": "axis0SplitCount * axis0Cols * dtypeBytes(\"float32\")",
41
+ "axis0SplitPathFits": "axis0SplitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(axis0Cols, reduceWorkgroupSize), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension and axis0SplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axis0SplitScratchBytes <= device.limits.maxBufferSize",
42
+ "reduceAxis": "(attrs.axes[0] + ranks.data if attrs.axes[0] < 0 else attrs.axes[0]) if ((attrs.axes | length) == 1 and isUniqueIntList(attrs.axes, 0 - ranks.data, ranks.data, 1)) else ranks.data",
43
+ "axisSplitDim": "dim(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 0",
44
+ "axisSplitInner": "inner(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 1",
45
+ "axisSplitOutputs": "numel(shapes.reduced)",
46
+ "axisSplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axisSplitDim, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
47
+ "axisSplitScratchBytes": "axisSplitCount * axisSplitOutputs * 4",
48
+ "axisSplitPathFits": "axisSplitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(axisSplitOutputs, reduceWorkgroupSize), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension and axisSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axisSplitScratchBytes <= device.limits.maxBufferSize",
49
+ "axis0TilePathFits": "treeWorkgroupOk and tunables.AXIS0_TILE_COLS > 0 and tunables.AXIS0_TILE_COLS <= reduceWorkgroupSize and reduceWorkgroupSize % tunables.AXIS0_TILE_COLS == 0",
50
+ "flatItems": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)",
51
+ "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
52
+ "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
53
+ "flatPathFits": "treeWorkgroupOk and flatSplitCount <= device.limits.maxComputeWorkgroupsPerDimension and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
54
+ "flatParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.reduced) == 1 and numel(shapes.data) >= tunables.FULL_REDUCE_MIN_ELEMENTS and flatPathFits",
55
+ "contiguousSuffixParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.reduced) > 0 and numel(shapes.data) % numel(shapes.reduced) == 0 and numel(shapes.data) / numel(shapes.reduced) >= tunables.CONTIGUOUS_SUFFIX_MIN_COLS and ((ranks.data == 3 and hasAxis(attrs.axes, 0, 3) == false and hasAxis(attrs.axes, 1, 3) and hasAxis(attrs.axes, 2, 3) and numel(shapes.reduced) == dim(shapes.data, 0)) or (ranks.data == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) == false and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.reduced) == dim(shapes.data, 0) * dim(shapes.data, 1)) or (ranks.data == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.reduced) == dim(shapes.data, 0)))"
56
+ },
57
+ "tunables": {
58
+ "WORKGROUP_SIZE": 256,
59
+ "VECTOR_WIDTH": 4,
60
+ "ROW_PARALLEL_MIN_COLS": 64,
61
+ "SUBGROUP_MIN_COLS": 256,
62
+ "SUBGROUP_SMALL_ROW_LIMIT": 32768,
63
+ "AXIS0_SPLIT_MIN_ROWS": 8192,
64
+ "AXIS0_SPLIT_TARGET_ROWS": 256,
65
+ "AXIS0_MAX_SPLITS": 128,
66
+ "AXIS0_TILE_MIN_ROWS": 64,
67
+ "AXIS0_TILE_MIN_COLS": 16,
68
+ "AXIS0_TILE_COLS": 16,
69
+ "AXIS_SPLIT_TILE_COLS": 8,
70
+ "MIDDLE_AXIS_TILE_COLS": 16,
71
+ "FULL_REDUCE_MIN_ELEMENTS": 8192,
72
+ "FULL_REDUCE_MAX_SPLITS": 256,
73
+ "CONTIGUOUS_SUFFIX_MIN_COLS": 256,
74
+ "ROW_SERIAL_MIN_ROWS": 8192,
75
+ "ROW_SERIAL_MAX_COLS": 1024
76
+ },
77
+ "bindingSets": {
78
+ "elementwise": [
79
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
80
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
81
+ {
82
+ "name": "params",
83
+ "semantic": "kernel.params",
84
+ "buffer": { "type": "uniform" },
85
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.reduced)" }] }
86
+ }
87
+ ],
88
+ "lastAxisVec4": [
89
+ {
90
+ "name": "x",
91
+ "arg": "x",
92
+ "semantic": "data",
93
+ "buffer": { "type": "read-only-storage" },
94
+ "elementType": "$vectorScalar"
95
+ },
96
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
97
+ {
98
+ "name": "params",
99
+ "semantic": "kernel.params",
100
+ "buffer": { "type": "uniform" },
101
+ "struct": {
102
+ "name": "Params",
103
+ "fields": [
104
+ { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
105
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" },
106
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1) / tunables.VECTOR_WIDTH" }
107
+ ]
108
+ }
109
+ }
110
+ ],
111
+ "suffixVec4": [
112
+ {
113
+ "name": "x",
114
+ "arg": "x",
115
+ "semantic": "data",
116
+ "buffer": { "type": "read-only-storage" },
117
+ "elementType": "$vectorScalar"
118
+ },
119
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
120
+ {
121
+ "name": "params",
122
+ "semantic": "kernel.params",
123
+ "buffer": { "type": "uniform" },
124
+ "struct": {
125
+ "name": "Params",
126
+ "fields": [
127
+ { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
128
+ { "name": "cols", "type": "u32", "value": "numel(shapes.data) / numel(shapes.reduced)" },
129
+ {
130
+ "name": "chunkCount",
131
+ "type": "u32",
132
+ "value": "numel(shapes.data) / numel(shapes.reduced) / tunables.VECTOR_WIDTH"
133
+ }
134
+ ]
135
+ }
136
+ }
137
+ ],
138
+ "suffixScalar": [
139
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
140
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
141
+ {
142
+ "name": "params",
143
+ "semantic": "kernel.params",
144
+ "buffer": { "type": "uniform" },
145
+ "struct": {
146
+ "name": "Params",
147
+ "fields": [
148
+ { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
149
+ { "name": "cols", "type": "u32", "value": "numel(shapes.data) / numel(shapes.reduced)" }
150
+ ]
151
+ }
152
+ }
153
+ ],
154
+ "lastAxisScalar": [
155
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
156
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
157
+ {
158
+ "name": "params",
159
+ "semantic": "kernel.params",
160
+ "buffer": { "type": "uniform" },
161
+ "struct": {
162
+ "name": "Params",
163
+ "fields": [
164
+ { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
165
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
166
+ ]
167
+ }
168
+ }
169
+ ],
170
+ "lastAxisScalarSubgroup": [
171
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
172
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
173
+ {
174
+ "name": "params",
175
+ "semantic": "kernel.params",
176
+ "buffer": { "type": "uniform" },
177
+ "struct": {
178
+ "name": "Params",
179
+ "fields": [
180
+ { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
181
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" },
182
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
183
+ ]
184
+ }
185
+ }
186
+ ],
187
+ "scalar": [
188
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
189
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
190
+ {
191
+ "name": "params",
192
+ "semantic": "kernel.params",
193
+ "buffer": { "type": "uniform" },
194
+ "struct": {
195
+ "name": "Params",
196
+ "fields": [
197
+ { "name": "rows", "type": "u32", "value": "1" },
198
+ { "name": "cols", "type": "u32", "value": "1" },
199
+ { "name": "outCount", "type": "u32", "value": "1" }
200
+ ]
201
+ }
202
+ }
203
+ ],
204
+ "rank1Axis0": [
205
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
206
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
207
+ {
208
+ "name": "params",
209
+ "semantic": "kernel.params",
210
+ "buffer": { "type": "uniform" },
211
+ "struct": {
212
+ "name": "Params",
213
+ "fields": [
214
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
215
+ { "name": "cols", "type": "u32", "value": "1" },
216
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
217
+ ]
218
+ }
219
+ }
220
+ ],
221
+ "rank2Serial": [
222
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
223
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
224
+ {
225
+ "name": "params",
226
+ "semantic": "kernel.params",
227
+ "buffer": { "type": "uniform" },
228
+ "struct": {
229
+ "name": "Params",
230
+ "fields": [
231
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
232
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
233
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
234
+ ]
235
+ }
236
+ }
237
+ ],
238
+ "rank2SerialAxis1": [
239
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
240
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
241
+ {
242
+ "name": "params",
243
+ "semantic": "kernel.params",
244
+ "buffer": { "type": "uniform" },
245
+ "struct": {
246
+ "name": "Params",
247
+ "fields": [
248
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
249
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
250
+ ]
251
+ }
252
+ }
253
+ ],
254
+ "axis0Parallel": [
255
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
256
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
257
+ {
258
+ "name": "params",
259
+ "semantic": "kernel.params",
260
+ "buffer": { "type": "uniform" },
261
+ "struct": {
262
+ "name": "Params",
263
+ "fields": [
264
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
265
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
266
+ ]
267
+ }
268
+ }
269
+ ],
270
+ "fullReduceSerial": [
271
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
272
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
273
+ {
274
+ "name": "params",
275
+ "semantic": "kernel.params",
276
+ "buffer": { "type": "uniform" },
277
+ "struct": {
278
+ "name": "Params",
279
+ "fields": [
280
+ { "name": "rows", "type": "u32", "value": "numel(shapes.data)" },
281
+ { "name": "cols", "type": "u32", "value": "1" },
282
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
283
+ ]
284
+ }
285
+ }
286
+ ],
287
+ "axisSplitReduce": [
288
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
289
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
290
+ {
291
+ "name": "params",
292
+ "semantic": "kernel.params",
293
+ "buffer": { "type": "uniform" },
294
+ "struct": {
295
+ "name": "Params",
296
+ "fields": [
297
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
298
+ { "name": "inner", "type": "u32", "value": "axisSplitInner" },
299
+ { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
300
+ ]
301
+ }
302
+ }
303
+ ],
304
+ "axisSplitCombine": [
305
+ {
306
+ "name": "partials",
307
+ "semantic": "partials",
308
+ "buffer": { "type": "read-only-storage" },
309
+ "elementType": "$partialElement"
310
+ },
311
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
312
+ {
313
+ "name": "params",
314
+ "semantic": "kernel.params",
315
+ "buffer": { "type": "uniform" },
316
+ "struct": {
317
+ "name": "Params",
318
+ "fields": [
319
+ { "name": "rows", "type": "u32", "value": "axisSplitDim" },
320
+ { "name": "cols", "type": "u32", "value": "axisSplitOutputs" }
321
+ ]
322
+ }
323
+ }
324
+ ],
325
+ "axis0SplitReduce": [
326
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
327
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
328
+ {
329
+ "name": "params",
330
+ "semantic": "kernel.params",
331
+ "buffer": { "type": "uniform" },
332
+ "struct": {
333
+ "name": "Params",
334
+ "fields": [
335
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
336
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
337
+ ]
338
+ }
339
+ }
340
+ ],
341
+ "axis0SplitCombine": [
342
+ {
343
+ "name": "partials",
344
+ "semantic": "partials",
345
+ "buffer": { "type": "read-only-storage" },
346
+ "elementType": "$partialElement"
347
+ },
348
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
349
+ {
350
+ "name": "params",
351
+ "semantic": "kernel.params",
352
+ "buffer": { "type": "uniform" },
353
+ "struct": {
354
+ "name": "Params",
355
+ "fields": [
356
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
357
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
358
+ ]
359
+ }
360
+ }
361
+ ],
362
+ "rankNAxis": [
363
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
364
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
365
+ {
366
+ "name": "params",
367
+ "semantic": "kernel.params",
368
+ "buffer": { "type": "uniform" },
369
+ "struct": {
370
+ "name": "Params",
371
+ "fields": [
372
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
373
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
374
+ ]
375
+ }
376
+ }
377
+ ],
378
+ "rank3Axis1Tiled": [
379
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
380
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "f32" },
381
+ {
382
+ "name": "params",
383
+ "semantic": "kernel.params",
384
+ "buffer": { "type": "uniform" },
385
+ "struct": {
386
+ "name": "Params",
387
+ "fields": [
388
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 1)" },
389
+ { "name": "cols", "type": "u32", "value": "dim(shapes.data, 2)" },
390
+ {
391
+ "name": "colTiles",
392
+ "type": "u32",
393
+ "value": "ceilDiv(dim(shapes.data, 2), tunables.MIDDLE_AXIS_TILE_COLS)"
394
+ }
395
+ ]
396
+ }
397
+ }
398
+ ],
399
+ "flatPartialF32": [
400
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
401
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "f32" },
402
+ {
403
+ "name": "params",
404
+ "semantic": "kernel.params",
405
+ "buffer": { "type": "uniform" },
406
+ "struct": {
407
+ "name": "Params",
408
+ "fields": [
409
+ { "name": "count4", "type": "u32", "value": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)" },
410
+ { "name": "numel", "type": "u32", "value": "numel(shapes.data)" }
411
+ ]
412
+ }
413
+ }
414
+ ],
415
+ "flatCombineF32": [
416
+ { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
417
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
418
+ {
419
+ "name": "params",
420
+ "semantic": "kernel.params",
421
+ "buffer": { "type": "uniform" },
422
+ "struct": {
423
+ "name": "Params",
424
+ "fields": [
425
+ { "name": "rows", "type": "u32", "value": "numel(shapes.data)" },
426
+ { "name": "cols", "type": "u32", "value": "1" }
427
+ ]
428
+ }
429
+ }
430
+ ],
431
+ "multiAxis": [
432
+ { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
433
+ { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
434
+ {
435
+ "name": "params",
436
+ "semantic": "kernel.params",
437
+ "buffer": { "type": "uniform" },
438
+ "struct": {
439
+ "name": "Params",
440
+ "fields": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }]
441
+ }
442
+ }
443
+ ]
444
+ },
445
+ "variants": [
446
+ {
447
+ "id": "contiguous_suffix_subgroup_vec4",
448
+ "priority": 30,
449
+ "requires": { "features": ["subgroups"] },
450
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0"],
451
+ "constants": {
452
+ "scalar": "dtypes.T",
453
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
454
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH))))"
455
+ },
456
+ "passes": [
457
+ {
458
+ "id": "main",
459
+ "name": "ReduceMean.ContiguousSuffixSubgroupVec4",
460
+ "source": {
461
+ "shader": "reduce-row-subgroup.wgsl.jinja",
462
+ "inputs": {
463
+ "op": "\"mean\"",
464
+ "vec4": true,
465
+ "castF32": "dtypes.T == \"f16\"",
466
+ "usesF16": "dtypes.T == \"f16\""
467
+ }
468
+ },
469
+ "subgroupCollectivesWidth": "portable",
470
+ "bindings": "suffixVec4",
471
+ "dispatch": { "workgroups": "numel(shapes.reduced)" }
472
+ }
473
+ ]
474
+ },
475
+ {
476
+ "id": "contiguous_suffix_tree_vec4",
477
+ "priority": 22,
478
+ "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
479
+ "constants": {
480
+ "scalar": "dtypes.T",
481
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
482
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH)))"
483
+ },
484
+ "passes": [
485
+ {
486
+ "id": "main",
487
+ "name": "ReduceMean.ContiguousSuffixTreeVec4",
488
+ "source": {
489
+ "shader": "reduce-row-tree.wgsl.jinja",
490
+ "inputs": {
491
+ "op": "\"mean\"",
492
+ "vec4": true,
493
+ "castF32": "dtypes.T == \"f16\"",
494
+ "usesF16": "dtypes.T == \"f16\""
495
+ }
496
+ },
497
+ "bindings": "suffixVec4",
498
+ "dispatch": { "workgroups": "numel(shapes.reduced)" }
499
+ }
500
+ ]
501
+ },
502
+ {
503
+ "id": "contiguous_suffix_tree",
504
+ "priority": 21,
505
+ "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
506
+ "constants": {
507
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.data) / numel(shapes.reduced)))",
508
+ "scalar": "dtypes.T"
509
+ },
510
+ "passes": [
511
+ {
512
+ "id": "main",
513
+ "name": "ReduceMean.ContiguousSuffixTree",
514
+ "source": {
515
+ "shader": "reduce-row-tree.wgsl.jinja",
516
+ "inputs": { "op": "\"mean\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
517
+ },
518
+ "bindings": "suffixScalar",
519
+ "dispatch": { "workgroups": "numel(shapes.reduced)" }
520
+ }
521
+ ]
522
+ },
523
+ {
524
+ "id": "noop_empty_axes",
525
+ "priority": 40,
526
+ "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.data, shapes.reduced)"],
527
+ "passes": [
528
+ {
529
+ "id": "main",
530
+ "name": "ReduceMean.NoopEmptyAxes",
531
+ "source": { "shader": "reduce-noop-empty-axes.wgsl.jinja" },
532
+ "bindings": "elementwise",
533
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
534
+ }
535
+ ]
536
+ },
537
+ {
538
+ "id": "multi_axis_rank3",
539
+ "priority": 8,
540
+ "when": ["not flatParallelCovered", "not contiguousSuffixParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 3", "(attrs.keepdims == 1 and ranks.reduced == 3 and (dim(shapes.reduced, 0) == 1 if hasAxis(attrs.axes, 0, 3) else dim(shapes.reduced, 0) == dim(shapes.data, 0)) and (dim(shapes.reduced, 1) == 1 if hasAxis(attrs.axes, 1, 3) else dim(shapes.reduced, 1) == dim(shapes.data, 1)) and (dim(shapes.reduced, 2) == 1 if hasAxis(attrs.axes, 2, 3) else dim(shapes.reduced, 2) == dim(shapes.data, 2))) or (attrs.keepdims == 0 and ranks.reduced == 1)"],
541
+ "passes": [
542
+ {
543
+ "id": "main",
544
+ "name": "ReduceMean.MultiAxisRank3",
545
+ "source": {
546
+ "shader": "reduce-serial-axis.wgsl.jinja",
547
+ "inputs": {
548
+ "op": "\"mean\"",
549
+ "indexing": "\"multiaxis\"",
550
+ "rank": 3,
551
+ "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
552
+ "dataShape": "shapes.data",
553
+ "outputShape": "shapes.reduced",
554
+ "outputRank": "ranks.reduced",
555
+ "keepDims": "attrs.keepdims != 0",
556
+ "intMode": "dtypes.T == \"i32\"",
557
+ "castF32": "dtypes.T == \"f16\"",
558
+ "usesF16": "dtypes.T == \"f16\""
559
+ }
560
+ },
561
+ "bindings": "multiAxis",
562
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
563
+ }
564
+ ],
565
+ "constants": { "scalar": "dtypes.T" }
566
+ },
567
+ {
568
+ "id": "multi_axis_rank4",
569
+ "priority": 8,
570
+ "when": ["not flatParallelCovered", "not contiguousSuffixParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 4", "attrs.noop_with_empty_axes == 0", "numel(shapes.reduced) == (1 if hasAxis(attrs.axes, 0, 4) else dim(shapes.data, 0)) * (1 if hasAxis(attrs.axes, 1, 4) else dim(shapes.data, 1)) * (1 if hasAxis(attrs.axes, 2, 4) else dim(shapes.data, 2)) * (1 if hasAxis(attrs.axes, 3, 4) else dim(shapes.data, 3))", "((attrs.keepdims == 1 and ranks.reduced == 4) or (attrs.keepdims == 0 and ranks.reduced < 4))"],
571
+ "passes": [
572
+ {
573
+ "id": "main",
574
+ "name": "ReduceMean.MultiAxisRank4",
575
+ "source": {
576
+ "shader": "reduce-serial-axis.wgsl.jinja",
577
+ "inputs": {
578
+ "op": "\"mean\"",
579
+ "indexing": "\"multiaxis\"",
580
+ "rank": 4,
581
+ "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
582
+ "dataShape": "shapes.data",
583
+ "outputShape": "shapes.reduced",
584
+ "outputRank": "ranks.reduced",
585
+ "keepDims": "attrs.keepdims != 0",
586
+ "intMode": "dtypes.T == \"i32\"",
587
+ "castF32": "dtypes.T == \"f16\"",
588
+ "usesF16": "dtypes.T == \"f16\""
589
+ }
590
+ },
591
+ "bindings": "multiAxis",
592
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
593
+ }
594
+ ],
595
+ "constants": { "scalar": "dtypes.T" }
596
+ },
597
+ {
598
+ "id": "tree_last_axis_vec4",
599
+ "priority": 23,
600
+ "demoteWhen": ["rowSerialPreferred"],
601
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "attrs.noop_with_empty_axes == 0", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "lastAxisCols % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
602
+ "constants": {
603
+ "scalar": "dtypes.T",
604
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
605
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
606
+ },
607
+ "passes": [
608
+ {
609
+ "id": "main",
610
+ "name": "ReduceMean.TreeRowVec4",
611
+ "source": {
612
+ "shader": "reduce-row-tree.wgsl.jinja",
613
+ "inputs": {
614
+ "op": "\"mean\"",
615
+ "vec4": true,
616
+ "castF32": "dtypes.T == \"f16\"",
617
+ "usesF16": "dtypes.T == \"f16\""
618
+ }
619
+ },
620
+ "bindings": "lastAxisVec4",
621
+ "dispatch": { "workgroups": "lastAxisRows" }
622
+ }
623
+ ]
624
+ },
625
+ {
626
+ "id": "rank0_scalar",
627
+ "priority": 40,
628
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
629
+ "when": ["f16Ok(dtypes.T)", "ranks.data == 0", "ranks.reduced == 0"],
630
+ "passes": [
631
+ {
632
+ "id": "main",
633
+ "name": "ReduceMean.Rank0Scalar",
634
+ "source": {
635
+ "shader": "reduce-serial-axis.wgsl.jinja",
636
+ "inputs": {
637
+ "op": "\"mean\"",
638
+ "indexing": "\"axis2d\"",
639
+ "intMode": "dtypes.T == \"i32\"",
640
+ "castF32": "dtypes.T == \"f16\"",
641
+ "usesF16": "dtypes.T == \"f16\"",
642
+ "logicalBool": "tensorDtypes.data == \"bool\""
643
+ }
644
+ },
645
+ "bindings": "scalar",
646
+ "dispatch": { "x": 1 }
647
+ }
648
+ ]
649
+ },
650
+ {
651
+ "id": "rank1_axis0",
652
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
653
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 1", "reduceAxis == 0", "((attrs.keepdims == 0 and ranks.reduced == 0) or (attrs.keepdims == 1 and ranks.reduced == 1 and dim(shapes.reduced, 0) == 1))"],
654
+ "passes": [
655
+ {
656
+ "id": "main",
657
+ "name": "ReduceMean.Rank1Axis0",
658
+ "source": {
659
+ "shader": "reduce-serial-axis.wgsl.jinja",
660
+ "inputs": {
661
+ "op": "\"mean\"",
662
+ "indexing": "\"axis2d\"",
663
+ "intMode": "dtypes.T == \"i32\"",
664
+ "castF32": "dtypes.T == \"f16\"",
665
+ "usesF16": "dtypes.T == \"f16\"",
666
+ "logicalBool": "tensorDtypes.data == \"bool\""
667
+ }
668
+ },
669
+ "bindings": "rank1Axis0",
670
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
671
+ }
672
+ ]
673
+ },
674
+ {
675
+ "id": "axis1_parallel",
676
+ "priority": 20,
677
+ "demoteWhen": ["rowSerialPreferred"],
678
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 2", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "treeWorkgroupOk"],
679
+ "constants": { "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.data, ranks.data - 1)))" },
680
+ "passes": [
681
+ {
682
+ "id": "main",
683
+ "name": "ReduceMean.Axis1Parallel",
684
+ "source": {
685
+ "shader": "reduce-row-tree.wgsl.jinja",
686
+ "inputs": { "op": "\"mean\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
687
+ },
688
+ "bindings": "lastAxisScalar",
689
+ "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
690
+ }
691
+ ]
692
+ },
693
+ {
694
+ "id": "axis_split",
695
+ "priority": 24,
696
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "attrs.noop_with_empty_axes == 0", "ranks.data >= 2", "reduceAxis < ranks.data - 1", "not (ranks.data == 2 and reduceAxis == 0)", "axisSplitDim >= tunables.AXIS0_SPLIT_MIN_ROWS", "axisSplitOutputs >= 1", "axisSplitOutputs <= 4096", "axisSplitOutputs == rows(shapes.data, reduceAxis)", "axisSplitPathFits"],
697
+ "derive": { "splitCount": "axisSplitCount" },
698
+ "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
699
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
700
+ "passes": [
701
+ {
702
+ "id": "split_reduce",
703
+ "name": "ReduceMean.AxisSplitReduce",
704
+ "source": {
705
+ "shader": "reduce-axis-split-reduce.wgsl.jinja",
706
+ "inputs": {
707
+ "op": "\"mean\"",
708
+ "workgroupSize": "reduceWorkgroupSize",
709
+ "split": "splitCount",
710
+ "castF32": "dtypes.T == \"f16\"",
711
+ "usesF16": "dtypes.T == \"f16\""
712
+ }
713
+ },
714
+ "bindings": "axisSplitReduce",
715
+ "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
716
+ },
717
+ {
718
+ "id": "combine",
719
+ "name": "ReduceMean.AxisSplitCombine",
720
+ "source": {
721
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
722
+ "inputs": {
723
+ "op": "\"mean\"",
724
+ "workgroupSize": "reduceWorkgroupSize",
725
+ "split": "splitCount",
726
+ "outputF16": "dtypes.T == \"f16\""
727
+ }
728
+ },
729
+ "bindings": "axisSplitCombine",
730
+ "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
731
+ }
732
+ ]
733
+ },
734
+ {
735
+ "id": "axis_split_tiled_narrow",
736
+ "priority": 25,
737
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "attrs.noop_with_empty_axes == 0", "ranks.data >= 2", "reduceAxis < ranks.data - 1", "axisSplitDim >= tunables.AXIS0_SPLIT_MIN_ROWS", "axisSplitOutputs >= 1", "axisSplitOutputs <= 2 * tunables.AXIS_SPLIT_TILE_COLS", "reduceWorkgroupSize % tunables.AXIS_SPLIT_TILE_COLS == 0", "axisSplitOutputs == rows(shapes.data, reduceAxis)", "axisSplitPathFits"],
738
+ "derive": { "splitCount": "axisSplitCount" },
739
+ "constants": {
740
+ "partialElement": "\"f32\"",
741
+ "scalar": "dtypes.T",
742
+ "workgroupSize": "reduceWorkgroupSize",
743
+ "split": "splitCount",
744
+ "tileCols": "tunables.AXIS_SPLIT_TILE_COLS"
745
+ },
746
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
747
+ "passes": [
748
+ {
749
+ "id": "split_reduce",
750
+ "name": "ReduceMean.AxisSplitTiledReduce",
751
+ "source": {
752
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
753
+ "inputs": {
754
+ "op": "\"mean\"",
755
+ "workgroupSize": "reduceWorkgroupSize",
756
+ "split": "splitCount",
757
+ "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
758
+ "castF32": "dtypes.T == \"f16\"",
759
+ "usesF16": "dtypes.T == \"f16\""
760
+ }
761
+ },
762
+ "bindings": "axisSplitReduce",
763
+ "dispatch": { "workgroups": "ceilDiv((axisSplitOutputs), (constants.tileCols))", "y": "splitCount" }
764
+ },
765
+ {
766
+ "id": "combine",
767
+ "name": "ReduceMean.AxisSplitCombine",
768
+ "source": {
769
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
770
+ "inputs": {
771
+ "op": "\"mean\"",
772
+ "workgroupSize": "reduceWorkgroupSize",
773
+ "split": "splitCount",
774
+ "outputF16": "dtypes.T == \"f16\""
775
+ }
776
+ },
777
+ "bindings": "axisSplitCombine",
778
+ "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
779
+ }
780
+ ]
781
+ },
782
+ {
783
+ "id": "axis0_splitk",
784
+ "priority": 22,
785
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_SPLIT_MIN_ROWS", "dim(shapes.data, 1) > 0", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 1)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == dim(shapes.data, 1)))", "axis0SplitPathFits"],
786
+ "derive": { "splitCount": "axis0SplitCount" },
787
+ "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
788
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * dim(shapes.data, 1)]" }],
789
+ "passes": [
790
+ {
791
+ "id": "split_reduce",
792
+ "name": "ReduceMean.Axis0SplitKReduce",
793
+ "source": {
794
+ "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
795
+ "inputs": {
796
+ "op": "\"mean\"",
797
+ "workgroupSize": "reduceWorkgroupSize",
798
+ "split": "splitCount",
799
+ "castF32": "dtypes.T == \"f16\"",
800
+ "usesF16": "dtypes.T == \"f16\""
801
+ }
802
+ },
803
+ "bindings": "axis0SplitReduce",
804
+ "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
805
+ },
806
+ {
807
+ "id": "combine",
808
+ "name": "ReduceMean.Axis0SplitKCombine",
809
+ "source": {
810
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
811
+ "inputs": {
812
+ "op": "\"mean\"",
813
+ "workgroupSize": "reduceWorkgroupSize",
814
+ "split": "splitCount",
815
+ "outputF16": "dtypes.T == \"f16\""
816
+ }
817
+ },
818
+ "bindings": "axis0SplitCombine",
819
+ "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize" }
820
+ }
821
+ ]
822
+ },
823
+ {
824
+ "id": "axis0_tilecols",
825
+ "priority": 20,
826
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_TILE_MIN_ROWS", "axis0Cols >= tunables.AXIS0_TILE_MIN_COLS", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 1)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == dim(shapes.data, 1)))", "axis0TilePathFits"],
827
+ "constants": {
828
+ "scalar": "dtypes.T",
829
+ "workgroupSize": "reduceWorkgroupSize",
830
+ "tileCols": "tunables.AXIS0_TILE_COLS"
831
+ },
832
+ "passes": [
833
+ {
834
+ "id": "main",
835
+ "name": "ReduceMean.Axis0TileCols",
836
+ "source": {
837
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
838
+ "inputs": {
839
+ "op": "\"mean\"",
840
+ "intMode": "dtypes.T == \"i32\"",
841
+ "castF32": "dtypes.T == \"f16\"",
842
+ "usesF16": "dtypes.T == \"f16\""
843
+ }
844
+ },
845
+ "bindings": "axis0Parallel",
846
+ "dispatch": { "workgroups": "ceilDiv((dim(shapes.data, 1)), (constants.tileCols))" }
847
+ }
848
+ ]
849
+ },
850
+ {
851
+ "id": "all_axes_flat",
852
+ "priority": 31,
853
+ "constants": { "scalar": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
854
+ "when": ["flatParallelCovered"],
855
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
856
+ "passes": [
857
+ {
858
+ "id": "flat_partial",
859
+ "name": "ReduceMean.AllAxesFlatPartial",
860
+ "source": {
861
+ "shader": "reduce-flat-partial.wgsl.jinja",
862
+ "inputs": { "op": "\"mean\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
863
+ },
864
+ "bindings": "flatPartialF32",
865
+ "dispatch": { "x": "flatSplitCount" }
866
+ },
867
+ {
868
+ "id": "combine",
869
+ "name": "ReduceMean.AllAxesFlatCombine",
870
+ "source": {
871
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
872
+ "inputs": { "op": "\"mean\"", "outputF16": "dtypes.T == \"f16\"" }
873
+ },
874
+ "bindings": "flatCombineF32",
875
+ "dispatch": { "x": 1 }
876
+ }
877
+ ]
878
+ },
879
+ {
880
+ "id": "rank3_axis1_tiled",
881
+ "priority": 23,
882
+ "demoteWhen": ["reportedNonWave32Adapter"],
883
+ "when": ["not flatParallelCovered", "dtypes.T == \"f32\"", "ranks.data == 3", "reduceAxis == 1", "dim(shapes.data, 1) >= tunables.AXIS0_TILE_MIN_ROWS", "dim(shapes.data, 2) >= tunables.MIDDLE_AXIS_TILE_COLS", "tunables.MIDDLE_AXIS_TILE_COLS > 0", "tunables.MIDDLE_AXIS_TILE_COLS <= reduceWorkgroupSize", "reduceWorkgroupSize % tunables.MIDDLE_AXIS_TILE_COLS == 0", "treeWorkgroupOk", "((attrs.keepdims == 0 and ranks.reduced == 2 and dim(shapes.reduced, 0) == dim(shapes.data, 0) and dim(shapes.reduced, 1) == dim(shapes.data, 2)) or (attrs.keepdims == 1 and ranks.reduced == 3 and dim(shapes.reduced, 0) == dim(shapes.data, 0) and dim(shapes.reduced, 1) == 1 and dim(shapes.reduced, 2) == dim(shapes.data, 2)))"],
884
+ "constants": { "workgroupSize": "reduceWorkgroupSize", "tileCols": "tunables.MIDDLE_AXIS_TILE_COLS" },
885
+ "passes": [
886
+ {
887
+ "id": "main",
888
+ "name": "ReduceMean.Rank3Axis1Tiled",
889
+ "source": { "shader": "reduce-axis0-tilecols.wgsl.jinja", "inputs": { "op": "\"mean\"", "batched": true } },
890
+ "bindings": "rank3Axis1Tiled",
891
+ "dispatch": {
892
+ "workgroups": "dim(shapes.data, 0) * ceilDiv(dim(shapes.data, 2), tunables.MIDDLE_AXIS_TILE_COLS)"
893
+ }
894
+ }
895
+ ]
896
+ },
897
+ {
898
+ "id": "rankn_single_axis_generic",
899
+ "priority": 12,
900
+ "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4", "rank3_axis1_tiled"],
901
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.noop_with_empty_axes == 0", "reduceAxis < ranks.data", "numel(shapes.reduced) == rows(shapes.data, reduceAxis)", "((attrs.keepdims == 0 and ranks.reduced == ranks.data - 1) or (attrs.keepdims == 1 and ranks.reduced == ranks.data and dim(shapes.reduced, reduceAxis) == 1))"],
902
+ "passes": [
903
+ {
904
+ "id": "main",
905
+ "name": "ReduceMean.RankNSingleAxisGeneric",
906
+ "source": {
907
+ "shader": "reduce-serial-axis.wgsl.jinja",
908
+ "inputs": {
909
+ "op": "\"mean\"",
910
+ "indexing": "\"rankn\"",
911
+ "rank": "ranks.data",
912
+ "axis": "reduceAxis",
913
+ "dataShape": "shapes.data",
914
+ "outputShape": "shapes.reduced",
915
+ "outputRank": "ranks.reduced",
916
+ "keepDims": "attrs.keepdims != 0",
917
+ "intMode": "dtypes.T == \"i32\"",
918
+ "castF32": "dtypes.T == \"f16\"",
919
+ "usesF16": "dtypes.T == \"f16\""
920
+ }
921
+ },
922
+ "bindings": "rankNAxis",
923
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
924
+ }
925
+ ],
926
+ "constants": { "scalar": "dtypes.T" }
927
+ },
928
+ {
929
+ "id": "subgroup_last_axis_vec4",
930
+ "priority": 25,
931
+ "requires": { "features": ["subgroups"] },
932
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "dim(shapes.data, ranks.data - 1) >= 4", "dim(shapes.data, ranks.data - 1) % tunables.VECTOR_WIDTH == 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "not rowSerialPreferred"],
933
+ "constants": {
934
+ "scalar": "dtypes.T",
935
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
936
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
937
+ },
938
+ "passes": [
939
+ {
940
+ "id": "main",
941
+ "name": "ReduceMean.SubgroupRowVec4",
942
+ "source": {
943
+ "shader": "reduce-row-subgroup.wgsl.jinja",
944
+ "inputs": {
945
+ "op": "\"mean\"",
946
+ "vec4": true,
947
+ "castF32": "dtypes.T == \"f16\"",
948
+ "usesF16": "dtypes.T == \"f16\""
949
+ }
950
+ },
951
+ "subgroupCollectivesWidth": "portable",
952
+ "bindings": "lastAxisVec4",
953
+ "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
954
+ }
955
+ ]
956
+ },
957
+ {
958
+ "id": "subgroup_last_axis",
959
+ "priority": 24,
960
+ "requires": { "features": ["subgroups"] },
961
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "dim(shapes.data, ranks.data - 1) > 0", "dim(shapes.data, ranks.data - 1) % tunables.VECTOR_WIDTH != 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "not rowSerialPreferred"],
962
+ "constants": {
963
+ "scalar": "dtypes.T",
964
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
965
+ },
966
+ "passes": [
967
+ {
968
+ "id": "main",
969
+ "name": "ReduceMean.SubgroupRow",
970
+ "source": {
971
+ "shader": "reduce-row-subgroup.wgsl.jinja",
972
+ "inputs": {
973
+ "op": "\"mean\"",
974
+ "vec4": false,
975
+ "castF32": "dtypes.T == \"f16\"",
976
+ "usesF16": "dtypes.T == \"f16\""
977
+ }
978
+ },
979
+ "subgroupCollectivesWidth": "portable",
980
+ "bindings": "lastAxisScalarSubgroup",
981
+ "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
982
+ }
983
+ ]
984
+ },
985
+ {
986
+ "id": "axis0",
987
+ "priority": 0,
988
+ "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
989
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
990
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 0", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 1)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == dim(shapes.data, 1)))"],
991
+ "passes": [
992
+ {
993
+ "id": "main",
994
+ "name": "axis0",
995
+ "source": {
996
+ "shader": "reduce-serial-axis.wgsl.jinja",
997
+ "inputs": {
998
+ "op": "\"mean\"",
999
+ "indexing": "\"axis2d\"",
1000
+ "intMode": "dtypes.T == \"i32\"",
1001
+ "castF32": "dtypes.T == \"f16\"",
1002
+ "usesF16": "dtypes.T == \"f16\""
1003
+ }
1004
+ },
1005
+ "bindings": "rank2Serial",
1006
+ "constants": { "axis": 0 },
1007
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
1008
+ }
1009
+ ]
1010
+ },
1011
+ {
1012
+ "id": "axis1",
1013
+ "priority": 0,
1014
+ "constants": { "axis": 1, "scalar": "dtypes.T" },
1015
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 1", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 0)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == dim(shapes.data, 0) and dim(shapes.reduced, 1) == 1))"],
1016
+ "passes": [
1017
+ {
1018
+ "id": "main",
1019
+ "name": "axis1",
1020
+ "source": {
1021
+ "shader": "reduce-serial-axis.wgsl.jinja",
1022
+ "inputs": {
1023
+ "op": "\"mean\"",
1024
+ "indexing": "\"axis2d\"",
1025
+ "intMode": "dtypes.T == \"i32\"",
1026
+ "castF32": "dtypes.T == \"f16\"",
1027
+ "usesF16": "dtypes.T == \"f16\""
1028
+ }
1029
+ },
1030
+ "bindings": "rank2SerialAxis1",
1031
+ "constants": { "axis": 1 },
1032
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
1033
+ }
1034
+ ]
1035
+ },
1036
+ {
1037
+ "id": "all_axes_no_keepdims",
1038
+ "priority": 30,
1039
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
1040
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 0 and ranks.reduced == 0"],
1041
+ "passes": [
1042
+ {
1043
+ "id": "main",
1044
+ "name": "ReduceMean.Rank3AllAxesNoKeepdims",
1045
+ "source": {
1046
+ "shader": "reduce-serial-axis.wgsl.jinja",
1047
+ "inputs": {
1048
+ "op": "\"mean\"",
1049
+ "indexing": "\"axis2d\"",
1050
+ "intMode": "dtypes.T == \"i32\"",
1051
+ "castF32": "dtypes.T == \"f16\"",
1052
+ "usesF16": "dtypes.T == \"f16\""
1053
+ }
1054
+ },
1055
+ "bindings": "fullReduceSerial",
1056
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
1057
+ }
1058
+ ]
1059
+ },
1060
+ {
1061
+ "id": "all_axes_keepdims",
1062
+ "priority": 30,
1063
+ "constants": { "axis": 0, "scalar": "dtypes.T" },
1064
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 1 and ranks.reduced == ranks.data and numel(shapes.reduced) == 1"],
1065
+ "passes": [
1066
+ {
1067
+ "id": "main",
1068
+ "name": "ReduceMean.Rank3AllAxesKeepdims",
1069
+ "source": {
1070
+ "shader": "reduce-serial-axis.wgsl.jinja",
1071
+ "inputs": {
1072
+ "op": "\"mean\"",
1073
+ "indexing": "\"axis2d\"",
1074
+ "intMode": "dtypes.T == \"i32\"",
1075
+ "castF32": "dtypes.T == \"f16\"",
1076
+ "usesF16": "dtypes.T == \"f16\""
1077
+ }
1078
+ },
1079
+ "bindings": "fullReduceSerial",
1080
+ "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
1081
+ }
1082
+ ]
1083
+ }
1084
+ ]
1085
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.ReduceMean",
3
+ "id": "_ai_onnx_reducemean_webgpu_01e9079",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "/K+s8cJOdBn/VX7RsBXRL096sIV729idGwO0eC0jDFY=",
11
+ "manifest.json": "bJgZfjv4JjiLNg4BxmMQFWswJgqkmUgNaFdwrJGd07A=",
12
+ "reduce-axis-split-reduce.wgsl.jinja": "Q50AhcbpYvCADMPrbGtPXKbmfT8f6LmQY+6+/CEX224=",
13
+ "reduce-axis0-splitk-combine.wgsl.jinja": "ybuwLy7He8M/Lkwk+LD6l9m4QI1b2d5pHzDUp/hmDPY=",
14
+ "reduce-axis0-splitk-reduce.wgsl.jinja": "jc7OBcuREabFLa+kwdZs2oEI3W/BWfHh8nyeUQnIVyY=",
15
+ "reduce-axis0-tilecols.wgsl.jinja": "mgYY0RTgZxPka4Q7qze7veEiPTC3jL1K7kYZWk/b2f4=",
16
+ "reduce-flat-partial.wgsl.jinja": "Jz5ZMpVLRJ702gjunfgrUEwqOase9mEBr5SRHlwA0zU=",
17
+ "reduce-noop-empty-axes.wgsl.jinja": "IO2MEzyBGJr3Z8AVP7ELJXqfhwcG/vtBfa0wCdkbuwo=",
18
+ "reduce-row-subgroup.wgsl.jinja": "fMRk3pR/DtSfjeRScsPXSY3hG9l13La8Xqyhjj46MyA=",
19
+ "reduce-row-tree.wgsl.jinja": "/ofIrGSbKtQbrDSfclyWqXfO2lRS2PJaE5Ws8IJqBJ0=",
20
+ "reduce-serial-axis.wgsl.jinja": "aUda1ztZ4NwVPwicDZz5P1sIhLT/LT9HrQnjpkdDmp4=",
21
+ "test.json": "RgkWmqzv4eLtCd+VbotifqQ1COTLqkSZ2gJ65sPQQaQ="
22
+ }
23
+ },
24
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
25
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.ReduceMean" }
26
+ }
build/webgpu/reduce-axis-split-reduce.wgsl.jinja ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 1 of a split-K reduction for an (outer, axis, inner) flattening. Each
2
+ // (output, segment) invocation reduces one axis slice at stride inner and writes
3
+ // partials[segment * outputs + output]. Adjacent output threads read adjacent
4
+ // inner-axis elements. The combine pass folds the segments and finalizes the
5
+ // selected reduction.
6
+ //
7
+ // logsumexp writes three partial planes per output and segment: the segment
8
+ // maximum, the sum of exp(x - maximum), and a packed NaN marker.
9
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
10
+ {% set xa = "f32(" if castF32 else "" %}
11
+ {% set ax = ")" if castF32 else "" %}
12
+ {% if source.usesF16 is defined and source.usesF16 %}
13
+ enable f16;
14
+ {% endif %}
15
+ {{ env.wgsl.resourceDeclarations }}
16
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
17
+ * evaluation, so f32 identities are constructed at runtime from their
18
+ * IEEE-754 bit patterns. */
19
+
20
+
21
+ const WG: u32 = {{ workgroupSize }}u;
22
+ const SPLIT: u32 = {{ split }}u;
23
+
24
+ @compute @workgroup_size(WG, 1, 1)
25
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
26
+ @builtin(workgroup_id) wg: vec3<u32>,
27
+ @builtin(num_workgroups) nwg: vec3<u32>) {
28
+ // 2D-folded output index: wg.z carries the high bits past the
29
+ // per-dimension dispatch limit on the x dimension.
30
+ let output_index = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
31
+ let seg = wg.y;
32
+ if (output_index >= params.outputs) { return; }
33
+
34
+ let outer_index = output_index / params.inner;
35
+ let inner_index = output_index % params.inner;
36
+ let input_base = outer_index * params.axisDim * params.inner + inner_index;
37
+
38
+ // Even split of [0, axisDim) across SPLIT segments (last may be shorter).
39
+ let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
40
+ let a0 = seg * chunk;
41
+ var a1 = a0 + chunk;
42
+ if (a1 > params.axisDim) { a1 = params.axisDim; }
43
+
44
+ var acc = 0.0;
45
+ for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
46
+ acc = acc + {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
47
+ }
48
+ partials[seg * params.outputs + output_index] = acc;
49
+ }
build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 2 of the split-K column-wise reduction. One invocation per output column
2
+ // folds the segment partials and applies the selected reduction's final step.
3
+ // Segments are folded in ascending order for deterministic results. This order
4
+ // differs from the single-pass reduction but remains within the f32 tolerance.
5
+ {% set yv = "f16(" if source.outputF16 else "" %}
6
+ {% set vy = ")" if source.outputF16 else "" %}
7
+ {% if source.outputF16 %}
8
+ enable f16;
9
+ {% endif %}
10
+ {{ env.wgsl.resourceDeclarations }}
11
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
12
+ * evaluation, so f32 identities are constructed at runtime from their
13
+ * IEEE-754 bit patterns. */
14
+
15
+
16
+ const WG: u32 = {{ workgroupSize }}u;
17
+ const SPLIT: u32 = {{ split }}u;
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 stride = nwg.x * WG;
23
+ let start = (gid.y * nwg.x * WG) + gid.x;
24
+ for (var col = start; col < params.cols; col = col + stride) {
25
+ var total = 0.0;
26
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
27
+ let p = partials[seg * params.cols + col];
28
+ total = total + p;
29
+ }
30
+ y[col] = {{ yv }}total / f32(params.rows){{ vy }};
31
+ }
32
+ }
build/webgpu/reduce-axis0-splitk-reduce.wgsl.jinja ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 1 of a split-K column-wise reduction. Splitting rows across workgroup
2
+ // segments increases residency for tall matrices. Each (column, segment)
3
+ // invocation reduces one row slice and writes partials[segment * columns +
4
+ // column]. Adjacent column threads keep row reads coalesced.
5
+ //
6
+ // logsumexp writes the segment maximum and sum of exp(x - maximum) as separate
7
+ // partial planes; the combine pass merges them stably and handles NaN and +Inf.
8
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
9
+ {% set xa = "f32(" if castF32 else "" %}
10
+ {% set ax = ")" if castF32 else "" %}
11
+ {% if source.usesF16 is defined and source.usesF16 %}
12
+ enable f16;
13
+ {% endif %}
14
+ {{ env.wgsl.resourceDeclarations }}
15
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
16
+ * evaluation, so f32 identities are constructed at runtime from their
17
+ * IEEE-754 bit patterns. */
18
+
19
+
20
+ const WG: u32 = {{ workgroupSize }}u;
21
+ const SPLIT: u32 = {{ split }}u;
22
+
23
+ @compute @workgroup_size(WG, 1, 1)
24
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
25
+ @builtin(workgroup_id) wg: vec3<u32>,
26
+ @builtin(num_workgroups) nwg: vec3<u32>) {
27
+ // 2D-folded column index: wg.z carries the high bits past the maxComputeWorkgroupsPerDimension
28
+ // workgroup-per-dimension dispatch limit on the x dimension.
29
+ let col = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
30
+ let seg = wg.y;
31
+ if (col >= params.cols) { return; }
32
+
33
+ // Even split of [0, rows) across SPLIT segments (last segment may be shorter).
34
+ let chunk = (params.rows + SPLIT - 1u) / SPLIT;
35
+ let r0 = seg * chunk;
36
+ var r1 = r0 + chunk;
37
+ if (r1 > params.rows) { r1 = params.rows; }
38
+
39
+ var acc = 0.0;
40
+ for (var row = r0; row < r1; row = row + 1u) {
41
+ acc = acc + {{ xa }}x[row * params.cols + col]{{ ax }};
42
+ }
43
+ partials[seg * params.cols + col] = acc;
44
+ }
build/webgpu/reduce-axis0-tilecols.wgsl.jinja ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Tiled column-wise reduction. Each workgroup owns TILE_COLS columns;
2
+ // ROW_LANES threads stride the rows of one column, then lane 0 folds their
3
+ // partials. In split mode, workgroup y selects an axis segment and finalization
4
+ // is deferred to the combine pass. Cooperative row lanes preserve occupancy
5
+ // when the flattened output has only a few elements.
6
+ {% set splitMode = source.split is defined %}
7
+ {% if splitMode %}
8
+ {% set rowBegin = "row_begin + row_lane" %}
9
+ {% set rowEnd = "row_end" %}
10
+ {% set elem = "x[input_base + row * params.inner]" %}
11
+ {% else %}
12
+ {% set rowBegin = "row_lane" %}
13
+ {% set rowEnd = "params.rows" %}
14
+ {% set elem = "x[inputBase + row * params.cols + col]" %}
15
+ {% endif %}
16
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
17
+ {% set intMode = source.intMode is defined and source.intMode %}
18
+ {% set scalar = "f32" if castF32 else scalar %}
19
+ {% if castF32 %}
20
+ {% set elem = "f32(" ~ elem ~ ")" %}
21
+ {% endif %}
22
+ {% set yv = "f16(" if castF32 else "" %}
23
+ {% set vy = ")" if castF32 else "" %}
24
+ {% if source.usesF16 is defined and source.usesF16 %}
25
+ enable f16;
26
+ {% endif %}
27
+ {{ env.wgsl.resourceDeclarations }}
28
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
29
+ * evaluation, so f32 identities are constructed at runtime from their
30
+ * IEEE-754 bit patterns. */
31
+
32
+
33
+ const WG: u32 = {{ workgroupSize }}u;
34
+ const TILE_COLS: u32 = {{ tileCols }}u;
35
+ const ROW_LANES: u32 = WG / TILE_COLS;
36
+ {% if splitMode %}
37
+ const SPLIT: u32 = {{ source.split }}u;
38
+ {% endif %}
39
+
40
+ var<workgroup> partial: array<{{ scalar if (source.op == "max" or source.op == "min" or intMode) else "f32" }}, WG>;
41
+ @compute @workgroup_size(WG, 1, 1)
42
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>{% if not splitMode %},
43
+ @builtin(num_workgroups) nwg: vec3<u32>{% endif %}) {
44
+ let tid = lid.x;
45
+ let col_lane = tid % TILE_COLS;
46
+ let row_lane = tid / TILE_COLS;
47
+ {% if splitMode %}
48
+ // Narrow outputs: wg.x covers every column tile, wg.y is the axis segment.
49
+ let col = wg.x * TILE_COLS + col_lane;
50
+ let outputIndex = col;
51
+ let in_bounds = col < params.outputs;
52
+ let seg = wg.y;
53
+ let outer_index = col / params.inner;
54
+ let inner_index = col % params.inner;
55
+ let input_base = outer_index * params.axisDim * params.inner + inner_index;
56
+ // Even split of [0, axisDim) across SPLIT segments (last may be shorter).
57
+ let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
58
+ let row_begin = seg * chunk;
59
+ let row_end = min(row_begin + chunk, params.axisDim);
60
+ {% else %}
61
+ // 2D-folded tile index: wg.y carries the high bits past the dispatch limit.
62
+ // The batched form reuses this same coalesced axis-0 reduction for a middle
63
+ // axis by assigning consecutive tiles to each outer slice.
64
+ let tile = wg.x + wg.y * nwg.x;
65
+ {% if source.batched is defined and source.batched %}
66
+ let outer = tile / params.colTiles;
67
+ let col = (tile % params.colTiles) * TILE_COLS + col_lane;
68
+ let inputBase = outer * params.rows * params.cols;
69
+ let outputIndex = outer * params.cols + col;
70
+ {% else %}
71
+ let col = tile * TILE_COLS + col_lane;
72
+ let inputBase = 0u;
73
+ let outputIndex = col;
74
+ {% endif %}
75
+ let in_bounds = col < params.cols;
76
+ {% endif %}
77
+
78
+ var acc = {% if intMode %}{{ scalar }}(0){% else %}0.0{% endif %};
79
+ if (in_bounds) {
80
+ for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
81
+ acc = acc + {{ elem }};
82
+ }
83
+ }
84
+ partial[tid] = acc;
85
+ workgroupBarrier();
86
+
87
+ if (row_lane == 0u && in_bounds) {
88
+ var total = partial[col_lane];
89
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
90
+ total = total + partial[lane * TILE_COLS + col_lane];
91
+ }
92
+ {% if splitMode %}
93
+ partials[seg * params.outputs + outputIndex] = total;
94
+ {% else %}
95
+ {% if source.op == "mean" and intMode %}
96
+ // Integer division truncates toward zero.
97
+ y[outputIndex] = total / {{ scalar }}(params.rows);
98
+ {% else %}
99
+ y[outputIndex] = {{ yv }}total / f32(params.rows){{ vy }};
100
+ {% endif %}
101
+ {% endif %}
102
+ }
103
+ }
build/webgpu/reduce-flat-partial.wgsl.jinja ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pass 1 of a flat reduction to one scalar. Instead of assigning the whole
2
+ // tensor to one invocation, SPLIT workgroups traverse the flat input, accumulate
3
+ // in registers, reduce within each workgroup, and write one partial each. A
4
+ // following combine pass folds the partials and applies the operation finalizer.
5
+ //
6
+ // Scalar f32 bindings keep arbitrary element counts legal. The grid-stride loop
7
+ // manually assembles full vec4 groups from contiguous scalars, and one global
8
+ // thread folds the final zero-to-three scalar elements exactly once.
9
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
10
+ {% set xa = "f32(" if castF32 else "" %}
11
+ {% set ax = ")" if castF32 else "" %}
12
+ {% if source.usesF16 is defined and source.usesF16 %}
13
+ enable f16;
14
+ {% endif %}
15
+ {{ env.wgsl.resourceDeclarations }}
16
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
17
+ * evaluation, so f32 identities are constructed at runtime from their
18
+ * IEEE-754 bit patterns. */
19
+
20
+
21
+ const WG: u32 = {{ workgroupSize }}u;
22
+ var<workgroup> red: array<{{ "i32" if source.intMode else "f32" }}, WG>;
23
+ @compute @workgroup_size(WG)
24
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
25
+ @builtin(local_invocation_id) lid: vec3<u32>,
26
+ @builtin(workgroup_id) wg: vec3<u32>,
27
+ @builtin(num_workgroups) nwg: vec3<u32>) {
28
+ let tid = lid.x;
29
+ let gstride = nwg.x * WG;
30
+ var acc = 0.0;
31
+ // Grid-stride over the flat vec4 groups (params.count4 = numel / 4, floored).
32
+ for (var i = gid.x; i < params.count4; i = i + gstride) {
33
+ let b = 4u * i;
34
+ let v = vec4<{{ "i32" if source.intMode else "f32" }}>({{ xa }}x[b]{{ ax }}, {{ xa }}x[b + 1u]{{ ax }}, {{ xa }}x[b + 2u]{{ ax }}, {{ xa }}x[b + 3u]{{ ax }});
35
+ acc = acc + v.x + v.y + v.z + v.w;
36
+ }
37
+ // Scalar tail (the 0..3 elements past the last full vec4). One global thread
38
+ // folds it so it is counted exactly once; the count is tiny so serializing it
39
+ // is negligible. numel % 4 == 0 makes this loop empty.
40
+ if (gid.x == 0u) {
41
+ for (var i = 4u * params.count4; i < params.numel; i = i + 1u) {
42
+ let s = {{ xa }}x[i]{{ ax }};
43
+ acc = acc + s;
44
+ }
45
+ }
46
+ red[tid] = acc;
47
+ workgroupBarrier();
48
+ var stride: u32 = WG / 2u;
49
+ loop {
50
+ if (stride == 0u) { break; }
51
+ if (tid < stride) {
52
+ red[tid] = red[tid] + red[tid + stride];
53
+ }
54
+ stride = stride / 2u;
55
+ workgroupBarrier();
56
+ }
57
+ if (tid == 0u) {
58
+ partials[wg.x] = red[0];
59
+ }
60
+ }
build/webgpu/reduce-noop-empty-axes.wgsl.jinja ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+
3
+ @compute @workgroup_size({{ reduceWorkgroupSize }})
4
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
5
+ // 2D-folded flat index: gid.y carries the high bits past the
6
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
7
+ let i = gid.x + gid.y * nwg.x * {{ reduceWorkgroupSize }}u;
8
+ if (i >= params.count) {
9
+ return;
10
+ }
11
+ let v = x[i];
12
+ y[i] = v;
13
+ }
build/webgpu/reduce-row-subgroup.wgsl.jinja ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Subgroup row reduction for a contiguous last axis. One workgroup owns each
2
+ // output row. Threads reduce strided chunks, subgroup leaders deposit their
3
+ // partials in workgroup memory, and thread 0 folds those slots and finalizes.
4
+ //
5
+ // Accumulators are f32 except for integer max/min. Mean, l2, and logsum apply
6
+ // division, square root, or logarithm only at finalization. logsumexp uses
7
+ // max-subtraction, bit-exact NaN detection, NaN propagation, and a +Inf
8
+ // short-circuit. Runtime bitcasts provide max/min infinity identities because
9
+ // WGSL constant evaluation rejects infinite constants. f16 storage is widened
10
+ // before accumulation and narrowed only for the final store.
11
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
12
+ {% set scalar = "f32" if castF32 else scalar %}
13
+ {% set xv = ("vec4<f32>(" if source.vec4 else "f32(") if castF32 else "" %}
14
+ {% set vx = ")" if castF32 else "" %}
15
+ {% set yv = "f16(" if castF32 else "" %}
16
+ {% set vy = ")" if castF32 else "" %}
17
+ enable subgroups;
18
+ {% if source.usesF16 is defined and source.usesF16 %}
19
+ enable f16;
20
+ {% endif %}
21
+ {{ env.wgsl.resourceDeclarations }}
22
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
23
+ * evaluation, so f32 identities are constructed at runtime from their
24
+ * IEEE-754 bit patterns. */
25
+
26
+
27
+ const WG: u32 = {{ workgroupSize }}u;
28
+ var<workgroup> wgPartial: array<{{ scalar }}, WG>;
29
+
30
+ {% macro emit_reduce(name, collective, combine) %}
31
+ fn {{ name }}(value: {{ scalar }}, sgLid: u32, sgId: u32, numSg: u32) -> {{ scalar }} {
32
+ let sgVal = {{ collective }}(value);
33
+ if (numSg == 1u) {
34
+ return sgVal;
35
+ }
36
+ if (sgLid == 0u) {
37
+ wgPartial[sgId] = sgVal;
38
+ }
39
+ workgroupBarrier();
40
+ var total = wgPartial[0];
41
+ for (var i = 1u; i < numSg; i = i + 1u) {
42
+ {{ combine }}
43
+ }
44
+ workgroupBarrier();
45
+ return total;
46
+ }
47
+ {%- endmacro %}{{ emit_reduce("reduce_row", "subgroupAdd", "total = total + wgPartial[i];") }}
48
+ @compute @workgroup_size(WG, 1, 1)
49
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
50
+ @builtin(num_workgroups) nwg: vec3<u32>,
51
+ @builtin(local_invocation_id) lid: vec3<u32>,
52
+ @builtin(subgroup_invocation_id) sgLid: u32,
53
+ @builtin(subgroup_id) sgId: u32,
54
+ @builtin(num_subgroups) numSg: u32) {
55
+ let row = wg.x + wg.y * nwg.x;
56
+ if (row >= params.rows) {
57
+ return;
58
+ }
59
+ let tid = lid.x;
60
+ let base = row * params.chunkCount; let INIT: f32 = 0.0;{% if source.vec4 %}
61
+ var acc4 = vec4<{{ scalar }}>(INIT);
62
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
63
+ let v = {{ xv }}x[base + c]{{ vx }}; acc4 = acc4 + v; } let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);{% else %}
64
+ var acc = INIT;
65
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
66
+ let v = {{ xv }}x[base + c]{{ vx }}; acc = acc + v; }
67
+ {%- endif %}
68
+ let total = reduce_row(acc, sgLid, sgId, numSg);
69
+ if (tid == 0u) { y[row] = {{ yv }}total / f32(params.cols){{ vy }}; }}
build/webgpu/reduce-row-tree.wgsl.jinja ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Portable one-workgroup-per-row reduction for the Reduce value family.
2
+ // Threads stride a contiguous row, accumulate locally, and fold their values
3
+ // through a shared-memory tree without relying on subgroups.
4
+ //
5
+ // Max, min, and product use the native f32/i32/u32 value type. Additive
6
+ // transforms accumulate float inputs in f32 but retain integer accumulation
7
+ // for sum, L1, and sum-of-squares. Mean, L2, log-sum, and log-sum-exp are
8
+ // f32-only because their finalizers divide, take a square root, or take a log.
9
+ // f16 storage widens through f32 for both accumulation and the shared tree,
10
+ // then narrows only at the final store.
11
+ {% set isVec4 = source.vec4 is defined and source.vec4 %}
12
+ {% set rowIsEmpty = "params.chunkCount == 0u" if isVec4 else "params.cols == 0u" %}
13
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
14
+ {% set scalar = "f32" if castF32 else scalar %}
15
+ {% set xv = ("vec4<f32>(" if source.vec4 else "f32(") if castF32 else "" %}
16
+ {% set vx = ")" if castF32 else "" %}
17
+ {% set yv = "f16(" if castF32 else "" %}
18
+ {% set vy = ")" if castF32 else "" %}
19
+ {% if source.usesF16 is defined and source.usesF16 %}
20
+ enable f16;
21
+ {% endif %}
22
+ {{ env.wgsl.resourceDeclarations }}
23
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
24
+ * evaluation, so f32 identities are constructed at runtime from their
25
+ * IEEE-754 bit patterns. */
26
+
27
+
28
+ const WG: u32 = {{ workgroupSize }}u;
29
+
30
+ {% set is_int = scalar == "i32" or scalar == "u32" %}
31
+ {% set intAdditive = is_int and (source.op == "sum" or source.op == "l1" or source.op == "sumsquare") %}
32
+ {% set accType = scalar if source.op == "max" or source.op == "min" or source.op == "prod" or intAdditive else "f32" %}
33
+ fn identity() -> {{ accType }} { return {{ accType }}(0); }
34
+
35
+ fn combine(a: {{ accType }}, b: {{ accType }}) -> {{ accType }} {
36
+ return a + b;
37
+ }
38
+
39
+ var<workgroup> partial: array<{{ accType }}, WG>;
40
+
41
+ @compute @workgroup_size(WG, 1, 1)
42
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
43
+ @builtin(num_workgroups) nwg: vec3<u32>,
44
+ @builtin(local_invocation_id) lid: vec3<u32>) {
45
+ let row = wg.x + wg.y * nwg.x;
46
+ if (row >= params.rows) {
47
+ return;
48
+ }
49
+ let tid = lid.x;
50
+ {% if source.vec4 %}
51
+ let base = row * params.chunkCount;
52
+ {% else %}
53
+ let base = row * params.cols;
54
+ {% endif %}
55
+
56
+ if ({{ rowIsEmpty }}) {
57
+ if (tid == 0u) { y[row] = {{ yv }}0.0{{ vy }}; }
58
+ return;
59
+ }
60
+
61
+ {% if source.vec4 %}
62
+ var acc4 = vec4<{{ accType }}>(identity());
63
+ for (var col = tid; col < params.chunkCount; col = col + WG) {
64
+ let value = {{ xv }}x[base + col]{{ vx }};
65
+ acc4 = acc4 + value;
66
+ }
67
+ let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
68
+ {% else %}
69
+ var acc = identity();
70
+ for (var col = tid; col < params.cols; col = col + WG) {
71
+ let value = {{ xv }}x[base + col]{{ vx }};
72
+ acc = combine(acc, value);
73
+ }
74
+ {% endif %}
75
+ partial[tid] = acc;
76
+ workgroupBarrier();
77
+
78
+ for (var step = WG >> 1u; step > 0u; step = step >> 1u) {
79
+ if (tid < step) {
80
+ partial[tid] = combine(partial[tid], partial[tid + step]);
81
+ }
82
+ workgroupBarrier();
83
+ }
84
+
85
+ if (tid == 0u) {
86
+ let total = partial[0];
87
+ y[row] = {{ yv }}total / f32(params.cols){{ vy }};
88
+ }
89
+ }
build/webgpu/reduce-serial-axis.wgsl.jinja ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Serial one-thread-per-output reduction for the no-feature tier. f16 storage
2
+ // is widened before every accumulation and narrowed only for the final store.
3
+ {% set castF32 = source.castF32 is defined and source.castF32 %}
4
+ {% set intMode = source.intMode is defined and source.intMode %}
5
+ {% set yv = "f16(" if castF32 else "" %}
6
+ {% set vy = ")" if castF32 else "" %}
7
+ {% if source.usesF16 is defined and source.usesF16 %}
8
+ enable f16;
9
+ {% endif %}
10
+ {{ env.wgsl.resourceDeclarations }}
11
+ /* Exact max/min reduction identities. WGSL rejects infinity during constant
12
+ * evaluation, so f32 identities are constructed at runtime from their
13
+ * IEEE-754 bit patterns. */
14
+
15
+ {% if source.indexing == "rankn" %}
16
+
17
+ fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
18
+ var rem = out_index;
19
+ {% for axis in range(source.outputRank) %}
20
+ {% set out_stride = namespace(value=1) %}
21
+ {% for j in range(axis + 1, source.outputRank) %}
22
+ {% set out_stride.value = out_stride.value * source.outputShape[j] %}
23
+ {% endfor %}
24
+ {% set safe_out_stride = 1 if out_stride.value == 0 else out_stride.value %}
25
+ {% if not source.keepDims or axis != source.axis %}
26
+ let out_coord{{ axis }} = rem / {{ safe_out_stride }}u;
27
+ {% endif %}
28
+ rem = rem % {{ safe_out_stride }}u;
29
+ {% endfor %}
30
+ {% for axis in range(source.rank) %}
31
+ {% if axis == source.axis %}
32
+ let coord{{ axis }} = reduce_index;
33
+ {% elif source.keepDims %}
34
+ let coord{{ axis }} = out_coord{{ axis }};
35
+ {% elif axis < source.axis %}
36
+ let coord{{ axis }} = out_coord{{ axis }};
37
+ {% else %}
38
+ let coord{{ axis }} = out_coord{{ axis - 1 }};
39
+ {% endif %}
40
+ {% endfor %}
41
+ {% set src = namespace(value="coord0") %}
42
+ {% for axis in range(1, source.rank) %}
43
+ {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[axis] ~ "u + coord" ~ axis ~ ")" %}
44
+ {% endfor %}
45
+ return {{ src.value }};
46
+ }
47
+ {% endif %}
48
+ {% if source.indexing == "multiaxis" %}
49
+ {% set hasReducedAxis = namespace(value=false) %}
50
+ {% for a in range(source.rank) %}{% if source.reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
51
+
52
+ // One thread per output element walks the Cartesian product of the reduced axes,
53
+ // linearized as reduce_linear. Specialized shapes make every input offset a sum
54
+ // of coordinate-times-constant terms.
55
+ fn input_offset(out_index: u32{% if hasReducedAxis.value %}, reduce_linear: u32{% endif %}) -> u32 {
56
+ var rem = out_index;
57
+ {% for oaxis in range(source.outputRank) %}
58
+ {% set ostride = namespace(value=1) %}
59
+ {% for j in range(oaxis + 1, source.outputRank) %}
60
+ {% set ostride.value = ostride.value * source.outputShape[j] %}
61
+ {% endfor %}
62
+ {% set osafe = 1 if ostride.value == 0 else ostride.value %}
63
+ {% if not source.keepDims or not source.reduce[oaxis] %}
64
+ let out_coord{{ oaxis }} = rem / {{ osafe }}u;
65
+ {% endif %}
66
+ rem = rem % {{ osafe }}u;
67
+ {% endfor %}
68
+ {% if hasReducedAxis.value %}
69
+ var rrem = reduce_linear;
70
+ {% endif %}
71
+ {% for a in range(source.rank) if source.reduce[a] %}
72
+ {% set rstride = namespace(value=1) %}
73
+ {% for b in range(a + 1, source.rank) if source.reduce[b] %}
74
+ {% set rstride.value = rstride.value * source.dataShape[b] %}
75
+ {% endfor %}
76
+ {% set rsafe = 1 if rstride.value == 0 else rstride.value %}
77
+ let red_coord{{ a }} = rrem / {{ rsafe }}u;
78
+ rrem = rrem % {{ rsafe }}u;
79
+ {% endfor %}
80
+ {% set oc = namespace(i=0) %}
81
+ {% for a in range(source.rank) %}
82
+ {% if source.reduce[a] %}
83
+ let coord{{ a }} = red_coord{{ a }};
84
+ {% elif source.keepDims %}
85
+ let coord{{ a }} = out_coord{{ a }};
86
+ {% else %}
87
+ let coord{{ a }} = out_coord{{ oc.i }};
88
+ {% set oc.i = oc.i + 1 %}
89
+ {% endif %}
90
+ {% endfor %}
91
+ {% set src = namespace(value="coord0") %}
92
+ {% for a in range(1, source.rank) %}
93
+ {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[a] ~ "u + coord" ~ a ~ ")" %}
94
+ {% endfor %}
95
+ return {{ src.value }};
96
+ }
97
+ {% endif %}
98
+ {% if source.indexing == "multiaxis" %}
99
+ {% set mcount = namespace(value=1) %}
100
+ {% for a in range(source.rank) if source.reduce[a] %}
101
+ {% set mcount.value = mcount.value * source.dataShape[a] %}
102
+ {% endfor %}
103
+ {% set count = mcount.value ~ "u" %}
104
+ {% if hasReducedAxis.value %}
105
+ {% set at = "x[input_offset(i, r)]" %}
106
+ {% else %}
107
+ {% set at = "x[input_offset(i)]" %}
108
+ {% endif %}
109
+ {% elif source.indexing == "rankn" %}
110
+ {% set count = "params.axisDim" %}
111
+ {% set at = "x[input_offset(i, r)]" %}
112
+ {% elif axis == 0 %}
113
+ {% set count = "params.rows" %}
114
+ {% set at = "x[r * params.cols + i]" %}
115
+ {% else %}
116
+ {% set count = "params.cols" %}
117
+ {% set at = "x[i * params.cols + r]" %}
118
+ {% endif %}
119
+ {% if castF32 %}
120
+ {% set at = "f32(" ~ at ~ ")" %}
121
+ {% endif %}
122
+
123
+ @compute @workgroup_size({{ reduceWorkgroupSize }})
124
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
125
+ // 2D-folded flat index: gid.y carries the high bits past the
126
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
127
+ let i = gid.x + gid.y * nwg.x * {{ reduceWorkgroupSize }}u;
128
+ if (i >= params.outCount) {
129
+ return;
130
+ }
131
+ if ({{ count }} == 0u) {
132
+ {% if intMode %}
133
+ y[i] = {{ scalar }}(0);
134
+ {% else %}
135
+ y[i] = {{ yv }}0.0{{ vy }};
136
+ {% endif %}
137
+ return;
138
+ }
139
+ {% if intMode %}
140
+ // Integer reduction accumulates in the output type. sum/prod/l1/sumsquare stay
141
+ // in integer arithmetic; l2/logsum widen for sqrt/log and truncate afterward.
142
+ var acc = {{ scalar }}({{ "1" if source.op == "prod" else "0" }});
143
+ {% else %}
144
+ var acc = {{ "1.0" if source.op == "prod" else "0.0" }};
145
+ {% endif %}
146
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
147
+ acc = acc + {{ at }};
148
+ }
149
+ {% if intMode %}
150
+ y[i] = acc / {{ scalar }}({{ count }});
151
+ {% else %}
152
+ y[i] = {{ yv }}acc / f32({{ count }}){{ vy }};
153
+ {% endif %}
154
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ReduceMean",
3
+ "fixtureArrays": {
4
+ "rank3_axis2_last_keepdims_input_x": [1, 2, 3, 4, -1, -2, -3, -4, 0.5, 1.5, 2.5, 3.5, 10, 20, 30, 40, -10, -20, -30, -40, 2, 4, 6, 8]
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "contiguous_suffix_axes23_parallel",
9
+ "attrs": { "axes": [2, 3], "keepdims": 1 },
10
+ "inputs": {
11
+ "x": {
12
+ "dtype": "float32",
13
+ "shape": [2, 3, 16, 16],
14
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.07, "scale": 0.2 }
15
+ }
16
+ },
17
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 1, 1], "tolerance": 0.00001 } }
18
+ },
19
+ {
20
+ "name": "all_axes_flat_rank1_boundary_8192",
21
+ "provenance": {
22
+ "notes": "The parallel full-reduction threshold must supersede the rank1 serial and row-reduction fallbacks."
23
+ },
24
+ "attrs": { "axes": [0], "keepdims": 0 },
25
+ "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
26
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0 } }
27
+ },
28
+ {
29
+ "name": "all_axes_flat_fullreduce_32x32x32_keepdims",
30
+ "attrs": { "keepdims": 1 },
31
+ "inputs": {
32
+ "x": {
33
+ "dtype": "float32",
34
+ "shape": [32, 32, 32],
35
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5 }
36
+ }
37
+ },
38
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1], "tolerance": 0.01, "relTolerance": 0.0001 } }
39
+ },
40
+ {
41
+ "name": "all_axes_flat_fullreduce_32x32x32_keepdims_offset_mean_scale_lock",
42
+ "provenance": {
43
+ "notes": "The all_axes_flat route reduces 32768 elements through an f32 partial pass and a single-workgroup combine; the zero-mean sibling produces 4.5e-4 against a 0.01 absolute tolerance (min detectable uniform scale error 22), so the combine's divide by numel is unobservable. Offsetting x about 1.0 makes the full-reduce mean O(1)."
44
+ },
45
+ "attrs": { "keepdims": 1 },
46
+ "inputs": {
47
+ "x": {
48
+ "dtype": "float32",
49
+ "shape": [32, 32, 32],
50
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5, "offset": 1.0 }
51
+ }
52
+ },
53
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1], "tolerance": 0.0001, "relTolerance": 0.00001 } }
54
+ },
55
+ {
56
+ "name": "dispatch_cliff_axis1_rank2",
57
+ "attrs": { "axes": [1], "keepdims": 0 },
58
+ "inputs": {
59
+ "x": { "dtype": "float32", "shape": [16776961, 1], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } }
60
+ },
61
+ "outputs": { "y": { "dtype": "float32", "shape": [16776961], "tolerance": 0.0001 } }
62
+ },
63
+ {
64
+ "name": "axis0",
65
+ "attrs": { "axes": [0], "keepdims": 0 },
66
+ "inputs": {
67
+ "x": {
68
+ "dtype": "float32",
69
+ "shape": [3, 4],
70
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11 }
71
+ }
72
+ },
73
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 0.000001 } }
74
+ },
75
+ {
76
+ "name": "axis0_tiled_64x32",
77
+ "attrs": { "axes": [0], "keepdims": 0 },
78
+ "inputs": {
79
+ "x": {
80
+ "dtype": "float32",
81
+ "shape": [64, 32],
82
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
83
+ }
84
+ },
85
+ "outputs": { "y": { "dtype": "float32", "shape": [32], "tolerance": 0.000001 } }
86
+ },
87
+ {
88
+ "name": "f32_subnormal_axis0_tilecols_mean_gpu_gap",
89
+ "skipGpu": {
90
+ "category": "permanent",
91
+ "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."
92
+ },
93
+ "provenance": {
94
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
95
+ "test": "ReductionOpTest.ReduceMean",
96
+ "notes": "Tiled axis-0 companion: the column mean of equal finite subnormal values remains subnormal."
97
+ },
98
+ "attrs": { "axes": [0], "keepdims": 0 },
99
+ "inputs": { "x": { "dtype": "float32", "shape": [64, 16], "data": { "kind": "constant", "value": 1e-40 } } },
100
+ "outputs": {
101
+ "y": { "dtype": "float32", "shape": [16], "tolerance": 2e-45, "data": { "kind": "constant", "value": 1e-40 } }
102
+ }
103
+ },
104
+ {
105
+ "name": "axis1",
106
+ "attrs": { "axes": [1], "keepdims": 0 },
107
+ "inputs": {
108
+ "x": {
109
+ "dtype": "float32",
110
+ "shape": [3, 4],
111
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11 }
112
+ }
113
+ },
114
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0.000001 } }
115
+ },
116
+ {
117
+ "name": "f32_axis1_parallel_cancellation_order_gpu_gap",
118
+ "skipGpu": {
119
+ "category": "todo",
120
+ "reason": "The current parallel reduction changes the fixture's required sequential evaluation order, so f32 rounding is not bit-exact. An order-preserving reduction route can implement this behavior."
121
+ },
122
+ "provenance": {
123
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
124
+ "test": "ReductionOpTest.ReduceMean",
125
+ "notes": "Mean inherits the same cancellation-order trap as ReduceSum: serial float32 summation yields 0, while the parallel row tree can preserve the small lane terms before division."
126
+ },
127
+ "attrs": { "axes": [1], "keepdims": 0 },
128
+ "inputs": {
129
+ "x": {
130
+ "dtype": "float32",
131
+ "shape": [1, 1024],
132
+ "data": { "kind": "cycle", "values": [100000000000000000000.0, 1.0, -100000000000000000000.0, 0.0] }
133
+ }
134
+ },
135
+ "outputs": { "y": { "dtype": "float32", "shape": [1], "tolerance": 0 } }
136
+ },
137
+ {
138
+ "name": "f32_subnormal_axis1_mean_gpu_gap",
139
+ "skipGpu": {
140
+ "category": "permanent",
141
+ "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."
142
+ },
143
+ "provenance": {
144
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
145
+ "test": "ReductionOpTest.ReduceMean",
146
+ "notes": "A row mean over equal finite subnormal values remains subnormal; reduction kernels must not flush the input or final quotient to zero."
147
+ },
148
+ "attrs": { "axes": [1], "keepdims": 0 },
149
+ "inputs": {
150
+ "x": {
151
+ "dtype": "float32",
152
+ "shape": [2, 3],
153
+ "data": { "kind": "values", "values": [1e-40, 1e-40, 1e-40, -1e-40, -1e-40, -1e-40] }
154
+ }
155
+ },
156
+ "outputs": {
157
+ "y": {
158
+ "dtype": "float32",
159
+ "shape": [2],
160
+ "tolerance": 2e-45,
161
+ "data": { "kind": "values", "values": [1e-40, -1e-40] }
162
+ }
163
+ }
164
+ },
165
+ {
166
+ "name": "f32_subnormal_axis0_mean_gpu_gap",
167
+ "skipGpu": {
168
+ "category": "permanent",
169
+ "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."
170
+ },
171
+ "provenance": {
172
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
173
+ "test": "ReductionOpTest.ReduceMean",
174
+ "notes": "Axis-0 companion: the mean of equal finite subnormal column values remains subnormal."
175
+ },
176
+ "attrs": { "axes": [0], "keepdims": 0 },
177
+ "inputs": {
178
+ "x": {
179
+ "dtype": "float32",
180
+ "shape": [3, 2],
181
+ "data": { "kind": "values", "values": [1e-40, -1e-40, 1e-40, -1e-40, 1e-40, -1e-40] }
182
+ }
183
+ },
184
+ "outputs": {
185
+ "y": {
186
+ "dtype": "float32",
187
+ "shape": [2],
188
+ "tolerance": 2e-45,
189
+ "data": { "kind": "values", "values": [1e-40, -1e-40] }
190
+ }
191
+ }
192
+ },
193
+ {
194
+ "name": "f32_subnormal_last_axis_vec4_mean_gpu_gap",
195
+ "skipGpu": {
196
+ "category": "permanent",
197
+ "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."
198
+ },
199
+ "provenance": {
200
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
201
+ "test": "ReductionOpTest.ReduceMean",
202
+ "notes": "Vec4 last-axis companion: the mean of equal finite subnormal values should remain subnormal."
203
+ },
204
+ "attrs": { "axes": [-1], "keepdims": 0 },
205
+ "inputs": {
206
+ "x": {
207
+ "dtype": "float32",
208
+ "shape": [2, 4],
209
+ "data": { "kind": "values", "values": [1e-40, 1e-40, 1e-40, 1e-40, -1e-40, -1e-40, -1e-40, -1e-40] }
210
+ }
211
+ },
212
+ "outputs": {
213
+ "y": {
214
+ "dtype": "float32",
215
+ "shape": [2],
216
+ "tolerance": 2e-45,
217
+ "data": { "kind": "values", "values": [1e-40, -1e-40] }
218
+ }
219
+ }
220
+ },
221
+ {
222
+ "name": "f32_subnormal_last_axis_odd_mean_gpu_gap",
223
+ "skipGpu": {
224
+ "category": "permanent",
225
+ "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."
226
+ },
227
+ "provenance": {
228
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
229
+ "test": "ReductionOpTest.ReduceMean",
230
+ "notes": "Odd-width last-axis companion: the mean of equal finite subnormal values should remain subnormal in the non-vec4 subgroup reducer."
231
+ },
232
+ "attrs": { "axes": [-1], "keepdims": 0 },
233
+ "inputs": {
234
+ "x": {
235
+ "dtype": "float32",
236
+ "shape": [2, 3],
237
+ "data": { "kind": "values", "values": [1e-40, 1e-40, 1e-40, -1e-40, -1e-40, -1e-40] }
238
+ }
239
+ },
240
+ "outputs": {
241
+ "y": {
242
+ "dtype": "float32",
243
+ "shape": [2],
244
+ "tolerance": 2e-45,
245
+ "data": { "kind": "values", "values": [1e-40, -1e-40] }
246
+ }
247
+ }
248
+ },
249
+ {
250
+ "name": "f32_subnormal_rank3_axis1_mean_gpu_gap",
251
+ "skipGpu": {
252
+ "category": "permanent",
253
+ "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."
254
+ },
255
+ "provenance": {
256
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
257
+ "test": "ReductionOpTest.ReduceMean",
258
+ "notes": "Rank-3 axis-1 companion: the mean of equal finite subnormal values remains subnormal through middle-axis indexing."
259
+ },
260
+ "attrs": { "axes": [1], "keepdims": 0 },
261
+ "inputs": {
262
+ "x": {
263
+ "dtype": "float32",
264
+ "shape": [2, 3, 2],
265
+ "data": {
266
+ "kind": "values",
267
+ "values": [1e-40, -1e-40, 1e-40, -1e-40, 1e-40, -1e-40, -1e-40, 1e-40, -1e-40, 1e-40, -1e-40, 1e-40]
268
+ }
269
+ }
270
+ },
271
+ "outputs": {
272
+ "y": {
273
+ "dtype": "float32",
274
+ "shape": [2, 2],
275
+ "tolerance": 2e-45,
276
+ "data": { "kind": "values", "values": [1e-40, -1e-40, -1e-40, 1e-40] }
277
+ }
278
+ }
279
+ },
280
+ {
281
+ "name": "f32_subnormal_rank3_all_axes_mean_scalar_gpu_gap",
282
+ "skipGpu": {
283
+ "category": "permanent",
284
+ "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."
285
+ },
286
+ "provenance": {
287
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
288
+ "test": "ReductionOpTest.ReduceMean_default_axes_do_not_keep_dims",
289
+ "notes": "Rank-3 default-axes companion: the mean of equal finite subnormal values should remain subnormal in scalar output form."
290
+ },
291
+ "attrs": { "keepdims": 0 },
292
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 3, 2], "data": { "kind": "constant", "value": 1e-40 } } },
293
+ "outputs": {
294
+ "y": { "dtype": "float32", "shape": [], "tolerance": 2e-45, "data": { "kind": "values", "values": [1e-40] } }
295
+ }
296
+ },
297
+ {
298
+ "name": "f32_subnormal_rank3_all_axes_keepdims_mean_gpu_gap",
299
+ "skipGpu": {
300
+ "category": "permanent",
301
+ "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."
302
+ },
303
+ "provenance": {
304
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
305
+ "test": "ReductionOpTest.ReduceMean_default_axes_keepdims",
306
+ "notes": "Rank-3 default-axes keepdims companion: the mean of equal finite subnormals should remain subnormal in shape [1,1,1]."
307
+ },
308
+ "attrs": { "keepdims": 1 },
309
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 3, 2], "data": { "kind": "constant", "value": 1e-40 } } },
310
+ "outputs": {
311
+ "y": {
312
+ "dtype": "float32",
313
+ "shape": [1, 1, 1],
314
+ "tolerance": 2e-45,
315
+ "data": { "kind": "values", "values": [1e-40] }
316
+ }
317
+ }
318
+ },
319
+ {
320
+ "name": "axis1_empty_cols_identity_zero",
321
+ "attrs": { "axes": [1], "keepdims": 0 },
322
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 0], "data": { "kind": "values", "values": [] } } },
323
+ "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0 } }
324
+ },
325
+ {
326
+ "name": "axis0_empty_rows_identity_zero",
327
+ "attrs": { "axes": [0], "keepdims": 0 },
328
+ "inputs": { "x": { "dtype": "float32", "shape": [0, 3], "data": { "kind": "values", "values": [] } } },
329
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0 } }
330
+ },
331
+ {
332
+ "name": "axis1_zero_rows_noop",
333
+ "attrs": { "axes": [1], "keepdims": 0 },
334
+ "inputs": { "x": { "dtype": "float32", "shape": [0, 3], "data": { "kind": "values", "values": [] } } },
335
+ "outputs": { "y": { "dtype": "float32", "shape": [0], "tolerance": 0 } }
336
+ },
337
+ {
338
+ "name": "axis_minus_one",
339
+ "attrs": { "axes": [-1], "keepdims": 0 },
340
+ "inputs": {
341
+ "x": {
342
+ "dtype": "float32",
343
+ "shape": [3, 4],
344
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, -1.0, -2.0, -3.0, -4.0, 0.5, 1.5, 2.5, 3.5] }
345
+ }
346
+ },
347
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0.000001 } }
348
+ },
349
+ {
350
+ "name": "rank3_axis2_last_keepdims",
351
+ "attrs": { "axes": [2], "keepdims": 1 },
352
+ "inputs": {
353
+ "x": {
354
+ "dtype": "float32",
355
+ "shape": [2, 3, 4],
356
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/rank3_axis2_last_keepdims_input_x" } }
357
+ }
358
+ },
359
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 1], "tolerance": 0.000001 } }
360
+ },
361
+ {
362
+ "name": "rank4_axis1_channel_no_keepdims",
363
+ "attrs": { "axes": [1], "keepdims": 0 },
364
+ "inputs": {
365
+ "x": {
366
+ "dtype": "float32",
367
+ "shape": [2, 3, 2, 2],
368
+ "data": {
369
+ "kind": "values",
370
+ "values": [1.0, -2.0, 3.0, -4.0, 10.0, 20.0, -30.0, -40.0, 0.25, -0.5, 0.75, -1.0, -5.0, 6.0, -7.0, 8.0, 0.0, 0.0, 1.5, -1.5, 100.0, -200.0, 300.0, -400.0]
371
+ }
372
+ }
373
+ },
374
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 2], "tolerance": 0.00001 } }
375
+ },
376
+ {
377
+ "name": "rank1_axis0_scalar_output",
378
+ "attrs": { "axes": [0], "keepdims": 0 },
379
+ "inputs": {
380
+ "x": {
381
+ "dtype": "float32",
382
+ "shape": [6],
383
+ "data": { "kind": "values", "values": [1.0, -2.0, 3.5, 4.5, -1.0, 0.0] }
384
+ }
385
+ },
386
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.000001 } }
387
+ },
388
+ {
389
+ "name": "rank3_axis2_empty_axis_identity_zero",
390
+ "attrs": { "axes": [2], "keepdims": 1 },
391
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 3, 0], "data": { "kind": "values", "values": [] } } },
392
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 1], "tolerance": 0 } }
393
+ },
394
+ {
395
+ "name": "rank4_axis1_empty_axis_identity_zero",
396
+ "attrs": { "axes": [1], "keepdims": 0 },
397
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 0, 2, 2], "data": { "kind": "values", "values": [] } } },
398
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 2], "tolerance": 0 } }
399
+ },
400
+ {
401
+ "name": "ort_axis1_rank3_no_keepdims",
402
+ "provenance": {
403
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
404
+ "test": "ReductionOpTest.ReduceMean_do_not_keepdims"
405
+ },
406
+ "attrs": { "axes": [1], "keepdims": 0 },
407
+ "inputs": {
408
+ "x": {
409
+ "dtype": "float32",
410
+ "shape": [3, 2, 2],
411
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
412
+ }
413
+ },
414
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2], "tolerance": 0.000001 } }
415
+ },
416
+ {
417
+ "name": "ort_axis1_rank3_keepdims",
418
+ "provenance": {
419
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
420
+ "test": "ReductionOpTest.ReduceMean_keepdims"
421
+ },
422
+ "attrs": { "axes": [1], "keepdims": 1 },
423
+ "inputs": {
424
+ "x": {
425
+ "dtype": "float32",
426
+ "shape": [3, 2, 2],
427
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
428
+ }
429
+ },
430
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2], "tolerance": 0.000001 } }
431
+ },
432
+ {
433
+ "name": "ort_axis0_rank1_scalar",
434
+ "provenance": {
435
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
436
+ "test": "ReductionOpTest.ReduceMean_do_not_keepdims_2"
437
+ },
438
+ "attrs": { "axes": [0], "keepdims": 0 },
439
+ "inputs": { "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, 2.0, 3.0] } } },
440
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.000001 } }
441
+ },
442
+ {
443
+ "name": "ort_rank0_scalar",
444
+ "provenance": {
445
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
446
+ "test": "ReductionOpTest.ReduceMean0DTensor"
447
+ },
448
+ "inputs": { "x": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [2.0] } } },
449
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0 } }
450
+ },
451
+ {
452
+ "name": "ort_axis0_singleton_keepdims_noop",
453
+ "provenance": {
454
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
455
+ "test": "ReductionOpTest.ReduceMean_keepdims_results_in_noop"
456
+ },
457
+ "attrs": { "axes": [0], "keepdims": 1 },
458
+ "inputs": {
459
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [1.0, 2.0, 3.0] } }
460
+ },
461
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3], "tolerance": 0.000001 } }
462
+ },
463
+ {
464
+ "name": "ort_axis0_singleton_no_keepdims_shape_change",
465
+ "provenance": {
466
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
467
+ "test": "ReductionOpTest.ReduceMean_keepdims_results_in_shape_change"
468
+ },
469
+ "attrs": { "axes": [0], "keepdims": 0 },
470
+ "inputs": {
471
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [1.0, 2.0, 3.0] } }
472
+ },
473
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0.000001 } }
474
+ },
475
+ {
476
+ "name": "ort_default_axes_rank3_no_keepdims_scalar",
477
+ "provenance": {
478
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
479
+ "test": "ReductionOpTest.ReduceMean_default_axes_do_not_keep_dims",
480
+ "notes": "Default axes reduce all input dimensions to a rank-0 scalar when keepdims=0."
481
+ },
482
+ "attrs": { "keepdims": 0 },
483
+ "inputs": {
484
+ "x": {
485
+ "dtype": "float32",
486
+ "shape": [3, 2, 2],
487
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
488
+ }
489
+ },
490
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.000001 } }
491
+ },
492
+ {
493
+ "name": "onnx_backend_reduce_mean_do_not_keepdims_example",
494
+ "attrs": { "keepdims": 0, "axes": [1] },
495
+ "inputs": {
496
+ "x": {
497
+ "dtype": "float32",
498
+ "shape": [3, 2, 2],
499
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
500
+ }
501
+ },
502
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2] } },
503
+ "provenance": {
504
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_do_not_keepdims_example",
505
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
506
+ }
507
+ },
508
+ {
509
+ "name": "onnx_backend_reduce_mean_do_not_keepdims_random",
510
+ "attrs": { "keepdims": 0, "axes": [1] },
511
+ "inputs": {
512
+ "x": {
513
+ "dtype": "float32",
514
+ "shape": [3, 2, 2],
515
+ "data": {
516
+ "kind": "values",
517
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
518
+ }
519
+ }
520
+ },
521
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2] } },
522
+ "provenance": {
523
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_do_not_keepdims_random",
524
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
525
+ }
526
+ },
527
+ {
528
+ "name": "onnx_backend_reduce_mean_keepdims_example",
529
+ "attrs": { "keepdims": 1, "axes": [1] },
530
+ "inputs": {
531
+ "x": {
532
+ "dtype": "float32",
533
+ "shape": [3, 2, 2],
534
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
535
+ }
536
+ },
537
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
538
+ "provenance": {
539
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_keepdims_example",
540
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
541
+ }
542
+ },
543
+ {
544
+ "name": "onnx_backend_reduce_mean_keepdims_random",
545
+ "attrs": { "keepdims": 1, "axes": [1] },
546
+ "inputs": {
547
+ "x": {
548
+ "dtype": "float32",
549
+ "shape": [3, 2, 2],
550
+ "data": {
551
+ "kind": "values",
552
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
553
+ }
554
+ }
555
+ },
556
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
557
+ "provenance": {
558
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_keepdims_random",
559
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
560
+ }
561
+ },
562
+ {
563
+ "name": "onnx_backend_reduce_mean_negative_axes_keepdims_example",
564
+ "attrs": { "keepdims": 1, "axes": [-2] },
565
+ "inputs": {
566
+ "x": {
567
+ "dtype": "float32",
568
+ "shape": [3, 2, 2],
569
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
570
+ }
571
+ },
572
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
573
+ "provenance": {
574
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_negative_axes_keepdims_example",
575
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
576
+ }
577
+ },
578
+ {
579
+ "name": "onnx_backend_reduce_mean_negative_axes_keepdims_random",
580
+ "attrs": { "keepdims": 1, "axes": [-2] },
581
+ "inputs": {
582
+ "x": {
583
+ "dtype": "float32",
584
+ "shape": [3, 2, 2],
585
+ "data": {
586
+ "kind": "values",
587
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
588
+ }
589
+ }
590
+ },
591
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 2] } },
592
+ "provenance": {
593
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_negative_axes_keepdims_random",
594
+ "notes": "The ONNX int64 axes input is materialized as this compile-time axes list."
595
+ }
596
+ },
597
+ {
598
+ "name": "onnx_backend_reduce_mean_default_axes_keepdims_example",
599
+ "provenance": {
600
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_default_axes_keepdims_example"
601
+ },
602
+ "attrs": { "keepdims": 1 },
603
+ "inputs": {
604
+ "x": {
605
+ "dtype": "float32",
606
+ "shape": [3, 2, 2],
607
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
608
+ }
609
+ },
610
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
611
+ },
612
+ {
613
+ "name": "ort_default_axes_keepdims_all_rank3",
614
+ "provenance": {
615
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
616
+ "test": "ReductionOpTest.ReduceMean_default_axes_keepdims"
617
+ },
618
+ "attrs": { "keepdims": 1 },
619
+ "inputs": {
620
+ "x": {
621
+ "dtype": "float32",
622
+ "shape": [3, 2, 2],
623
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
624
+ }
625
+ },
626
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1], "tolerance": 0.000001 } }
627
+ },
628
+ {
629
+ "name": "onnx_backend_reduce_mean_default_axes_keepdims_random",
630
+ "provenance": {
631
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_mean_default_axes_keepdims_random"
632
+ },
633
+ "attrs": { "keepdims": 1 },
634
+ "inputs": {
635
+ "x": {
636
+ "dtype": "float32",
637
+ "shape": [3, 2, 2],
638
+ "data": {
639
+ "kind": "values",
640
+ "values": [0.9762700796127319, 4.3037872314453125, 2.055267572402954, 0.8976636528968811, -1.5269039869308472, 2.917882204055786, -1.248255729675293, 7.835460186004639, 9.273255348205566, -2.331169605255127, 5.834500789642334, 0.577898383140564]
641
+ }
642
+ }
643
+ },
644
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
645
+ },
646
+ {
647
+ "name": "subgroup_vec4_last_axis_2x256",
648
+ "attrs": { "axes": [-1], "keepdims": 0 },
649
+ "inputs": {
650
+ "x": {
651
+ "dtype": "float32",
652
+ "shape": [2, 256],
653
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
654
+ }
655
+ },
656
+ "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0.0002, "relTolerance": 0.0001 } }
657
+ },
658
+ {
659
+ "name": "subgroup_scalar_last_axis_2x65",
660
+ "attrs": { "axes": [1], "keepdims": 0 },
661
+ "inputs": {
662
+ "x": {
663
+ "dtype": "float32",
664
+ "shape": [2, 65],
665
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11 }
666
+ }
667
+ },
668
+ "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0.0002, "relTolerance": 0.0001 } }
669
+ },
670
+ {
671
+ "name": "ort_int32_large_values_no_overflow_gpu_gap",
672
+ "skipGpu": {
673
+ "category": "todo",
674
+ "reason": "The current integer reduction route uses an i32 accumulator, so the fixture's 6e9 intermediate sum overflows before division. A portable multiword accumulator can implement this behavior."
675
+ },
676
+ "provenance": {
677
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
678
+ "test": "ReductionOpTest.ReduceMean_int32_LargeValues_NoOverflow"
679
+ },
680
+ "attrs": { "axes": [0], "keepdims": 1 },
681
+ "inputs": {
682
+ "x": {
683
+ "dtype": "int32",
684
+ "shape": [3],
685
+ "data": { "kind": "values", "values": [2000000000, 2000000000, 2000000000] }
686
+ }
687
+ },
688
+ "outputs": {
689
+ "y": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2000000000] }, "tolerance": 0 }
690
+ }
691
+ },
692
+ {
693
+ "name": "ort_noop_empty_axes_identity",
694
+ "provenance": {
695
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
696
+ "test": "ReductionOpTest.ReduceMean_noop_axes_input_initializer_opset_18",
697
+ "notes": "The omitted axes input exercises empty-axes behavior."
698
+ },
699
+ "attrs": { "keepdims": 0, "noop_with_empty_axes": 1 },
700
+ "inputs": {
701
+ "x": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } }
702
+ },
703
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 2], "tolerance": 0 } }
704
+ },
705
+ {
706
+ "name": "ort_int32_multi_axis_keepdims",
707
+ "provenance": {
708
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
709
+ "test": "ReductionOpTest.ReduceMean_int32"
710
+ },
711
+ "attrs": { "axes": [0, 2], "keepdims": 1 },
712
+ "inputs": {
713
+ "x": {
714
+ "dtype": "int32",
715
+ "shape": [3, 2, 2],
716
+ "data": { "kind": "values", "values": [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120] }
717
+ }
718
+ },
719
+ "outputs": {
720
+ "y": { "dtype": "int32", "shape": [1, 2, 1], "data": { "kind": "values", "values": [55, 75] }, "tolerance": 0 }
721
+ }
722
+ },
723
+ {
724
+ "name": "ort_float_multi_axis_keepdims",
725
+ "provenance": {
726
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
727
+ "test": "ReductionOpTest.ReduceMean"
728
+ },
729
+ "attrs": { "axes": [0, 2], "keepdims": 1 },
730
+ "inputs": {
731
+ "x": {
732
+ "dtype": "float32",
733
+ "shape": [3, 2, 2],
734
+ "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] }
735
+ }
736
+ },
737
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 1], "tolerance": 0.000001 } }
738
+ },
739
+ {
740
+ "name": "rank3_lastaxis_cols1024_tree_nosubgroup",
741
+ "attrs": { "axes": [2], "keepdims": 0 },
742
+ "inputs": {
743
+ "x": {
744
+ "dtype": "float32",
745
+ "shape": [2, 2, 1024],
746
+ "data": {
747
+ "kind": "cycle",
748
+ "values": [1.0, -2.0, 0.5, 3.25, -1.5, 2.0, -0.75, 4.0, -3.5, 1.25, 0.0, -2.25, 5.0, -4.0, 2.75, -1.0]
749
+ }
750
+ }
751
+ },
752
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2], "tolerance": 0.00001 } }
753
+ },
754
+ {
755
+ "name": "axis0_splitk_8192x32",
756
+ "attrs": { "axes": [0], "keepdims": 0 },
757
+ "inputs": {
758
+ "x": {
759
+ "dtype": "float32",
760
+ "shape": [8192, 32],
761
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
762
+ }
763
+ },
764
+ "outputs": { "y": { "dtype": "float32", "shape": [32], "tolerance": 0.0001 } }
765
+ },
766
+ {
767
+ "name": "axis0_splitk_8192x32_offset_mean_scale_lock",
768
+ "provenance": {
769
+ "notes": "axis0_splitk_8192x32 draws x from a zero-mean sinusoid, so the mean over 8192 rows cancels to 7e-5 while the absolute tolerance is 1e-4: the whole output fits inside the tolerance and any multiplicative error passes (min detectable uniform scale error 1.43). Offsetting x to oscillate about 1.0 makes each column mean O(1), so the axis0_splitk divisor is under test - a per-split partial that divides by the split height instead of the axis length, a missing final divide, or a combine that double-counts a partial all fail here."
770
+ },
771
+ "attrs": { "axes": [0], "keepdims": 0 },
772
+ "inputs": {
773
+ "x": {
774
+ "dtype": "float32",
775
+ "shape": [8192, 32],
776
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
777
+ }
778
+ },
779
+ "outputs": { "y": { "dtype": "float32", "shape": [32], "tolerance": 0.0001, "relTolerance": 0.00001 } }
780
+ },
781
+ {
782
+ "name": "axis0_splitk_8192x48_keepdims",
783
+ "attrs": { "axes": [0], "keepdims": 1 },
784
+ "inputs": {
785
+ "x": {
786
+ "dtype": "float32",
787
+ "shape": [8192, 48],
788
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
789
+ }
790
+ },
791
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 48], "tolerance": 0.0001 } }
792
+ },
793
+ {
794
+ "name": "axis0_splitk_8192x48_keepdims_offset_mean_scale_lock",
795
+ "provenance": {
796
+ "notes": "The keepdims sibling axis0_splitk_8192x48_keepdims averages zero-mean data to 1e-5 against a 1e-4 absolute tolerance (min detectable uniform scale error 10.0), so the keepdims axis0_splitk combine is only checked for shape, never for magnitude. Offsetting x about 1.0 makes the mean O(1) so the divisor on the keepdims path is checked too."
797
+ },
798
+ "attrs": { "axes": [0], "keepdims": 1 },
799
+ "inputs": {
800
+ "x": {
801
+ "dtype": "float32",
802
+ "shape": [8192, 48],
803
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
804
+ }
805
+ },
806
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 48], "tolerance": 0.0001, "relTolerance": 0.00001 } }
807
+ },
808
+ {
809
+ "name": "f32_rank4_axis2_no_keepdims",
810
+ "provenance": {
811
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
812
+ "test": "ReductionOpTest.ReduceMean",
813
+ "notes": "Rank-4 single-axis reduce over a middle (non-last, non-axis1) dimension."
814
+ },
815
+ "attrs": { "axes": [2], "keepdims": 0 },
816
+ "inputs": {
817
+ "x": {
818
+ "dtype": "float32",
819
+ "shape": [2, 3, 4, 2],
820
+ "data": { "kind": "fillFloat32", "sinStep": 0.37, "cosStep": 0.13, "scale": 0.5 }
821
+ }
822
+ },
823
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 2], "tolerance": 0.00001 } }
824
+ },
825
+ {
826
+ "name": "f32_rank4_multi_axis_23_keepdims",
827
+ "provenance": {
828
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
829
+ "test": "ReductionOpTest.ReduceMean",
830
+ "notes": "Rank-4 multi-axis reduce over the trailing spatial axes [2,3]."
831
+ },
832
+ "attrs": { "axes": [2, 3], "keepdims": 1 },
833
+ "inputs": {
834
+ "x": {
835
+ "dtype": "float32",
836
+ "shape": [2, 3, 4, 2],
837
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.17, "scale": 0.5 }
838
+ }
839
+ },
840
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 1, 1], "tolerance": 0.00001 } }
841
+ },
842
+ {
843
+ "name": "f32_rank4_default_all_axes_scalar",
844
+ "provenance": {
845
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
846
+ "test": "ReductionOpTest.ReduceMean_default_axes_do_not_keep_dims",
847
+ "notes": "Reduces every axis of a rank-4 float32 tensor to a rank-0 scalar."
848
+ },
849
+ "attrs": { "keepdims": 0 },
850
+ "inputs": {
851
+ "x": {
852
+ "dtype": "float32",
853
+ "shape": [2, 3, 4, 2],
854
+ "data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.19, "scale": 0.5 }
855
+ }
856
+ },
857
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.00001 } }
858
+ },
859
+ {
860
+ "name": "f32_last_axis_inf_nan_propagation",
861
+ "provenance": {
862
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
863
+ "test": "ReductionOpTest.ReduceMean",
864
+ "notes": "inf/NaN propagation through the subgroup vec4 last-axis reducer (cols%4==0): row means of +inf -> inf, mixed +inf/-inf -> NaN, any NaN -> NaN. Not subnormal, so Metal FTZ does not apply."
865
+ },
866
+ "attrs": { "axes": [-1], "keepdims": 0 },
867
+ "inputs": {
868
+ "x": {
869
+ "dtype": "float32",
870
+ "shape": [4, 4],
871
+ "data": {
872
+ "kind": "values",
873
+ "values": [1.0, 2.0, 3.0, 4.0, "Infinity", 1.0, 2.0, 3.0, "Infinity", "-Infinity", 1.0, 1.0, "NaN", 1.0, 2.0, 3.0]
874
+ }
875
+ }
876
+ },
877
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 0.000001, "allowNaN": true } }
878
+ },
879
+ {
880
+ "name": "rank3_multi_axes_12_keepdims",
881
+ "provenance": {
882
+ "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
883
+ "test": "ReductionOpTest.ReduceMean",
884
+ "notes": "Multi-axis rank3 reduce over axes=[1,2] with keepdims=1, exercising the multi_axis_rank3 mask branch beyond the existing [0,2] case."
885
+ },
886
+ "attrs": { "axes": [1, 2], "keepdims": 1 },
887
+ "inputs": {
888
+ "x": {
889
+ "dtype": "float32",
890
+ "shape": [2, 3, 4],
891
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/rank3_axis2_last_keepdims_input_x" } }
892
+ }
893
+ },
894
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 1], "tolerance": 0.00001 } }
895
+ },
896
+ {
897
+ "name": "int32_mean_truncation_toward_zero",
898
+ "attrs": { "axes": [0], "keepdims": 0 },
899
+ "inputs": {
900
+ "x": { "dtype": "int32", "shape": [2, 4], "data": { "kind": "values", "values": [-3, 5, -7, 9, -4, 2, -8, 2] } }
901
+ },
902
+ "outputs": {
903
+ "y": { "dtype": "int32", "shape": [4], "data": { "kind": "values", "values": [-3, 3, -7, 5] }, "tolerance": 0 }
904
+ }
905
+ },
906
+ {
907
+ "name": "reduce_size1_axis_returns_input_value",
908
+ "attrs": { "axes": [1], "keepdims": 0 },
909
+ "inputs": {
910
+ "x": {
911
+ "dtype": "float32",
912
+ "shape": [2, 1, 4],
913
+ "data": { "kind": "values", "values": [1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0] }
914
+ }
915
+ },
916
+ "outputs": {
917
+ "y": {
918
+ "dtype": "float32",
919
+ "shape": [2, 4],
920
+ "data": { "kind": "values", "values": [1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0] },
921
+ "tolerance": 0
922
+ }
923
+ }
924
+ },
925
+ {
926
+ "name": "all_axes_flat_numel_boundary_8192",
927
+ "attrs": { "keepdims": 0 },
928
+ "inputs": {
929
+ "x": {
930
+ "dtype": "float32",
931
+ "shape": [1, 8192],
932
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.02, "scale": 1.0 }
933
+ }
934
+ },
935
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.001, "relTolerance": 0.0001 } }
936
+ },
937
+ {
938
+ "name": "all_axes_flat_numel_boundary_8192_offset_mean_scale_lock",
939
+ "provenance": {
940
+ "notes": "At exactly FULL_REDUCE_MIN_ELEMENTS the split count is at its low boundary, which is where a combine that divides by the partial count instead of numel would first show; the zero-mean sibling averages to 2.2e-3 against a 1e-3 tolerance (min detectable uniform scale error 0.45) and cannot show it. Offsetting x about 1.0 makes the mean O(1) at the same boundary."
941
+ },
942
+ "attrs": { "keepdims": 0 },
943
+ "inputs": {
944
+ "x": {
945
+ "dtype": "float32",
946
+ "shape": [1, 8192],
947
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.02, "scale": 1.0, "offset": 1.0 }
948
+ }
949
+ },
950
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.0001, "relTolerance": 0.00001 } }
951
+ },
952
+ {
953
+ "name": "axis0_narrow_f32_8192x3_splitk_guard_lock",
954
+ "provenance": {
955
+ "notes": "Compact lock below the historical 16-column split-K guard. Constant ones validate that mean finalization remains exactly one on the split-K route."
956
+ },
957
+ "attrs": { "axes": [0], "keepdims": 0 },
958
+ "inputs": { "x": { "dtype": "float32", "shape": [8192, 3], "data": { "kind": "constant", "value": 1.0 } } },
959
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0 } }
960
+ },
961
+ {
962
+ "name": "contiguous_suffix_axes12_parallel",
963
+ "provenance": {
964
+ "notes": "Contiguous axes {1,2} exercise the shared cooperative suffix reduction instead of one serial lane per output."
965
+ },
966
+ "attrs": { "axes": [1, 2], "keepdims": 1 },
967
+ "inputs": {
968
+ "x": {
969
+ "dtype": "float32",
970
+ "shape": [3, 16, 16],
971
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.07, "scale": 0.2 }
972
+ }
973
+ },
974
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 1, 1], "tolerance": 0.00001 } }
975
+ },
976
+ {
977
+ "name": "rank3_axis1_tiled_middle_reduction",
978
+ "provenance": {
979
+ "notes": "Compact route fixture for the coalesced multi-lane middle-axis reduction used by the 8x1024x768 case."
980
+ },
981
+ "attrs": { "axes": [1], "keepdims": 0 },
982
+ "inputs": {
983
+ "x": {
984
+ "dtype": "float32",
985
+ "shape": [2, 64, 64],
986
+ "data": { "kind": "fillFloat32", "sinStep": 0.037, "cosStep": 0.061, "scale": 0.7 }
987
+ }
988
+ },
989
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 64], "tolerance": 0.00002 } }
990
+ },
991
+ {
992
+ "name": "axis_split_rank3_axis1_2x8192x4",
993
+ "attrs": { "axes": [1], "keepdims": 0 },
994
+ "inputs": {
995
+ "x": {
996
+ "dtype": "float32",
997
+ "shape": [2, 8192, 4],
998
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
999
+ }
1000
+ },
1001
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4], "tolerance": 0.0001 } }
1002
+ },
1003
+ {
1004
+ "name": "axis_split_rank3_axis1_2x8192x4_offset_mean_scale_lock",
1005
+ "provenance": {
1006
+ "notes": "axis_split_rank3_axis1_2x8192x4 selects axis_split_tiled_narrow, whose reduce pass writes per-split partials and whose combine performs the divide; its zero-mean data averages to 9e-5 against a 1e-4 tolerance (min detectable uniform scale error 1.08), so neither the partial nor the combine divisor is observable. Offsetting x about 1.0 puts the mean at O(1)."
1007
+ },
1008
+ "attrs": { "axes": [1], "keepdims": 0 },
1009
+ "inputs": {
1010
+ "x": {
1011
+ "dtype": "float32",
1012
+ "shape": [2, 8192, 4],
1013
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1014
+ }
1015
+ },
1016
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4], "tolerance": 0.0001, "relTolerance": 0.00001 } }
1017
+ },
1018
+ {
1019
+ "name": "f16_axis_split_tiled_narrow_2x8192x4",
1020
+ "attrs": { "axes": [1], "keepdims": 0 },
1021
+ "inputs": {
1022
+ "x": {
1023
+ "dtype": "float16",
1024
+ "shape": [2, 8192, 4],
1025
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1026
+ }
1027
+ },
1028
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 4], "tolerance": 0.05, "relTolerance": 0.002 } }
1029
+ },
1030
+ {
1031
+ "name": "f16_axis_split_tiled_narrow_2x8192x4_offset_mean_scale_lock",
1032
+ "provenance": {
1033
+ "notes": "Offsetting the input around 1.0 keeps the mean at O(1), so the tightened tolerance detects errors in the float16 partial representation and the axis-split combine divisor at roughly two float16 ulps."
1034
+ },
1035
+ "attrs": { "axes": [1], "keepdims": 0 },
1036
+ "inputs": {
1037
+ "x": {
1038
+ "dtype": "float16",
1039
+ "shape": [2, 8192, 4],
1040
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1041
+ }
1042
+ },
1043
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 4], "tolerance": 0.002, "relTolerance": 0.002 } }
1044
+ },
1045
+ {
1046
+ "name": "axis_split_rank3_axis1_wide_2x8192x32",
1047
+ "attrs": { "axes": [1], "keepdims": 0 },
1048
+ "inputs": {
1049
+ "x": {
1050
+ "dtype": "float32",
1051
+ "shape": [2, 8192, 32],
1052
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1053
+ }
1054
+ },
1055
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 32], "tolerance": 0.0001 } }
1056
+ },
1057
+ {
1058
+ "name": "axis_split_rank3_axis1_wide_2x8192x32_offset_mean_scale_lock",
1059
+ "provenance": {
1060
+ "notes": "The wide axis_split route (inner 32, not the tiled-narrow path) averages zero-mean data to 7e-5 against a 1e-4 tolerance (min detectable uniform scale error 1.43). Offsetting x about 1.0 makes the mean O(1) so the wide route's split count, partial stride, and final divide are all under test."
1061
+ },
1062
+ "attrs": { "axes": [1], "keepdims": 0 },
1063
+ "inputs": {
1064
+ "x": {
1065
+ "dtype": "float32",
1066
+ "shape": [2, 8192, 32],
1067
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1068
+ }
1069
+ },
1070
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 32], "tolerance": 0.0001, "relTolerance": 0.00001 } }
1071
+ },
1072
+ {
1073
+ "name": "f16_axis_split_wide_2x8192x32",
1074
+ "attrs": { "axes": [1], "keepdims": 0 },
1075
+ "inputs": {
1076
+ "x": {
1077
+ "dtype": "float16",
1078
+ "shape": [2, 8192, 32],
1079
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1080
+ }
1081
+ },
1082
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 32], "tolerance": 0.05, "relTolerance": 0.002 } }
1083
+ },
1084
+ {
1085
+ "name": "f16_axis_split_wide_2x8192x32_offset_mean_scale_lock",
1086
+ "provenance": {
1087
+ "notes": "f16 wide axis_split: 0.05 absolute tolerance against a 7e-5 mean is a min detectable uniform scale error of 710, so nothing multiplicative is observable on this route. Offsetting x about 1.0 makes the mean O(1) with an f16-resolution tolerance."
1088
+ },
1089
+ "attrs": { "axes": [1], "keepdims": 0 },
1090
+ "inputs": {
1091
+ "x": {
1092
+ "dtype": "float16",
1093
+ "shape": [2, 8192, 32],
1094
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1095
+ }
1096
+ },
1097
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 32], "tolerance": 0.002, "relTolerance": 0.002 } }
1098
+ },
1099
+ {
1100
+ "name": "f16_rank3_axis1_serial",
1101
+ "attrs": { "axes": [1], "keepdims": 0 },
1102
+ "inputs": {
1103
+ "x": {
1104
+ "dtype": "float16",
1105
+ "shape": [3, 2, 2],
1106
+ "data": { "kind": "values", "values": [5.0, 1.0, 20.0, 2.0, 30.0, 1.0, 40.0, 2.0, 55.0, 1.0, 60.0, 2.0] }
1107
+ }
1108
+ },
1109
+ "outputs": { "y": { "dtype": "float16", "shape": [3, 2], "tolerance": 0.02 } }
1110
+ },
1111
+ {
1112
+ "name": "f16_last_axis_serial_fallback",
1113
+ "attrs": { "axes": [1], "keepdims": 0 },
1114
+ "inputs": {
1115
+ "x": {
1116
+ "dtype": "float16",
1117
+ "shape": [2, 65],
1118
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11 }
1119
+ }
1120
+ },
1121
+ "outputs": { "y": { "dtype": "float16", "shape": [2], "tolerance": 0.02, "relTolerance": 0.0001 } }
1122
+ },
1123
+ {
1124
+ "name": "f16_last_axis_serial_fallback_offset_mean_scale_lock",
1125
+ "provenance": {
1126
+ "notes": "The 65-column f16 row uses a different workgroup geometry (pow2ceil(65)=128) from the 1023-column scalar case, and its zero-mean sibling averages to 0.054 against a 0.02 absolute tolerance (min detectable uniform scale error 0.37) - still above the 0.1 blindness threshold. Offsetting x about 1.0 makes the mean O(1) at this geometry."
1127
+ },
1128
+ "attrs": { "axes": [1], "keepdims": 0 },
1129
+ "inputs": {
1130
+ "x": {
1131
+ "dtype": "float16",
1132
+ "shape": [2, 65],
1133
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11, "offset": 1.0 }
1134
+ }
1135
+ },
1136
+ "outputs": { "y": { "dtype": "float16", "shape": [2], "tolerance": 0.002, "relTolerance": 0.002 } }
1137
+ },
1138
+ {
1139
+ "name": "f16_all_axes",
1140
+ "attrs": { "axes": [0], "keepdims": 0 },
1141
+ "inputs": { "x": { "dtype": "float16", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
1142
+ "outputs": { "y": { "dtype": "float16", "shape": [], "tolerance": 0.02 } }
1143
+ },
1144
+ {
1145
+ "name": "f16_axis0_splitk_8192x8",
1146
+ "attrs": { "axes": [0], "keepdims": 0 },
1147
+ "inputs": {
1148
+ "x": {
1149
+ "dtype": "float16",
1150
+ "shape": [8192, 8],
1151
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1152
+ }
1153
+ },
1154
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.002 } }
1155
+ },
1156
+ {
1157
+ "name": "f16_axis0_splitk_8192x8_offset_mean_scale_lock",
1158
+ "provenance": {
1159
+ "notes": "The most tolerance-blind fixture in this op: f16_axis0_splitk_8192x8 has a min detectable uniform scale error of 1483 (0.05 absolute tolerance, 3.4e-5 max output), so halving, doubling, or dividing by the split height instead of 8192 all pass. Same rank-2 axis-0 f16 shape with x offset to oscillate about 1.0, so the mean is O(1) and the divisor is checked."
1160
+ },
1161
+ "attrs": { "axes": [0], "keepdims": 0 },
1162
+ "inputs": {
1163
+ "x": {
1164
+ "dtype": "float16",
1165
+ "shape": [8192, 8],
1166
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1167
+ }
1168
+ },
1169
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.002, "relTolerance": 0.002 } }
1170
+ },
1171
+ {
1172
+ "name": "f16_last_axis_vec4_8x1024",
1173
+ "attrs": { "axes": [1], "keepdims": 0 },
1174
+ "inputs": {
1175
+ "x": {
1176
+ "dtype": "float16",
1177
+ "shape": [8, 1024],
1178
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1179
+ }
1180
+ },
1181
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.002 } }
1182
+ },
1183
+ {
1184
+ "name": "f16_last_axis_vec4_8x1024_offset_mean_scale_lock",
1185
+ "provenance": {
1186
+ "notes": "Last-axis vec4 (tree_last_axis_vec4 on the baseline tier, subgroup_last_axis_vec4 with subgroups): the zero-mean sibling averages 1024 columns to 4.8e-3 against a 0.05 absolute tolerance (min detectable uniform scale error 10.5), so a divide by the vec4 count rather than the column count is invisible. Offsetting x about 1.0 makes each row mean O(1)."
1187
+ },
1188
+ "attrs": { "axes": [1], "keepdims": 0 },
1189
+ "inputs": {
1190
+ "x": {
1191
+ "dtype": "float16",
1192
+ "shape": [8, 1024],
1193
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1194
+ }
1195
+ },
1196
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.002, "relTolerance": 0.002 } }
1197
+ },
1198
+ {
1199
+ "name": "f16_last_axis_scalar_8x1023",
1200
+ "attrs": { "axes": [1], "keepdims": 0 },
1201
+ "inputs": {
1202
+ "x": {
1203
+ "dtype": "float16",
1204
+ "shape": [8, 1023],
1205
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1206
+ }
1207
+ },
1208
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.002 } }
1209
+ },
1210
+ {
1211
+ "name": "f16_last_axis_scalar_8x1023_offset_mean_scale_lock",
1212
+ "provenance": {
1213
+ "notes": "The scalar last-axis route at 1023 columns (workgroup floor 256, so each lane walks a strided span with a ragged tail); the zero-mean sibling leaves 4.7e-3 against a 0.05 absolute tolerance (min detectable uniform scale error 10.6). Offsetting x about 1.0 makes the mean O(1) so a divisor of 1024 instead of 1023, or a lane span miscount, fails."
1214
+ },
1215
+ "attrs": { "axes": [1], "keepdims": 0 },
1216
+ "inputs": {
1217
+ "x": {
1218
+ "dtype": "float16",
1219
+ "shape": [8, 1023],
1220
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1221
+ }
1222
+ },
1223
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.002, "relTolerance": 0.002 } }
1224
+ },
1225
+ {
1226
+ "name": "f16_all_axes_flat_65543",
1227
+ "attrs": { "keepdims": 0 },
1228
+ "inputs": {
1229
+ "x": {
1230
+ "dtype": "float16",
1231
+ "shape": [65543],
1232
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1233
+ }
1234
+ },
1235
+ "outputs": { "y": { "dtype": "float16", "shape": [], "tolerance": 0.05, "relTolerance": 0.002 } }
1236
+ },
1237
+ {
1238
+ "name": "f16_all_axes_flat_65543_offset_mean_scale_lock",
1239
+ "provenance": {
1240
+ "notes": "65543 is prime to the workgroup span, so the all_axes_flat partial pass runs a ragged tail; the zero-mean sibling reduces it to 5.4e-5 against a 0.05 absolute tolerance (min detectable uniform scale error 924), so a tail element counted twice or a divide by the padded length passes. Offsetting x about 1.0 makes the mean O(1) and any miscount of the element total shows up directly."
1241
+ },
1242
+ "attrs": { "keepdims": 0 },
1243
+ "inputs": {
1244
+ "x": {
1245
+ "dtype": "float16",
1246
+ "shape": [65543],
1247
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1248
+ }
1249
+ },
1250
+ "outputs": { "y": { "dtype": "float16", "shape": [], "tolerance": 0.002, "relTolerance": 0.002 } }
1251
+ },
1252
+ {
1253
+ "name": "f16_suffix_vec4_4x8x128",
1254
+ "attrs": { "axes": [1, 2], "keepdims": 0 },
1255
+ "inputs": {
1256
+ "x": {
1257
+ "dtype": "float16",
1258
+ "shape": [4, 8, 128],
1259
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1260
+ }
1261
+ },
1262
+ "outputs": { "y": { "dtype": "float16", "shape": [4], "tolerance": 0.05, "relTolerance": 0.002 } }
1263
+ },
1264
+ {
1265
+ "name": "f16_suffix_vec4_4x8x128_offset_mean_scale_lock",
1266
+ "provenance": {
1267
+ "notes": "The contiguous-suffix vec4 route (tree on the baseline tier, subgroup on the subgroup tiers) reduces 1024 suffix elements per row; the zero-mean sibling leaves 3.3e-3 against a 0.05 absolute tolerance (min detectable uniform scale error 15.3), so the suffix element count used as the divisor is unchecked. Offsetting x about 1.0 makes the suffix mean O(1) on both tiers."
1268
+ },
1269
+ "attrs": { "axes": [1, 2], "keepdims": 0 },
1270
+ "inputs": {
1271
+ "x": {
1272
+ "dtype": "float16",
1273
+ "shape": [4, 8, 128],
1274
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1275
+ }
1276
+ },
1277
+ "outputs": { "y": { "dtype": "float16", "shape": [4], "tolerance": 0.002, "relTolerance": 0.002 } }
1278
+ },
1279
+ {
1280
+ "name": "f16_suffix_scalar_4x7x37",
1281
+ "attrs": { "axes": [1, 2], "keepdims": 0 },
1282
+ "inputs": {
1283
+ "x": {
1284
+ "dtype": "float16",
1285
+ "shape": [4, 7, 37],
1286
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1287
+ }
1288
+ },
1289
+ "outputs": { "y": { "dtype": "float16", "shape": [4], "tolerance": 0.05, "relTolerance": 0.002 } }
1290
+ },
1291
+ {
1292
+ "name": "f16_suffix_scalar_4x7x37_offset_mean_scale_lock",
1293
+ "provenance": {
1294
+ "notes": "The scalar contiguous-suffix route with a non-vectorizable suffix of 259: the zero-mean sibling leaves 5.1e-3 against a 0.05 absolute tolerance (min detectable uniform scale error 9.8). Offsetting x about 1.0 makes the mean O(1), so a divisor taken from a padded or rounded suffix length rather than 7*37 fails."
1295
+ },
1296
+ "attrs": { "axes": [1, 2], "keepdims": 0 },
1297
+ "inputs": {
1298
+ "x": {
1299
+ "dtype": "float16",
1300
+ "shape": [4, 7, 37],
1301
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1302
+ }
1303
+ },
1304
+ "outputs": { "y": { "dtype": "float16", "shape": [4], "tolerance": 0.002, "relTolerance": 0.002 } }
1305
+ },
1306
+ {
1307
+ "name": "f16_axis0_tilecols_4096x64",
1308
+ "attrs": { "axes": [0], "keepdims": 0 },
1309
+ "inputs": {
1310
+ "x": {
1311
+ "dtype": "float16",
1312
+ "shape": [4096, 64],
1313
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
1314
+ }
1315
+ },
1316
+ "outputs": { "y": { "dtype": "float16", "shape": [64], "tolerance": 0.05, "relTolerance": 0.002 } }
1317
+ },
1318
+ {
1319
+ "name": "f16_axis0_tilecols_4096x64_offset_mean_scale_lock",
1320
+ "provenance": {
1321
+ "notes": "axis0_tilecols reduces 4096 rows per column tile in one workgroup; the zero-mean sibling averages to 1e-4 against a 0.05 absolute tolerance (min detectable uniform scale error 509), so a divide by the tile column count rather than the row count is invisible. Offsetting x about 1.0 makes the column means O(1)."
1322
+ },
1323
+ "attrs": { "axes": [0], "keepdims": 0 },
1324
+ "inputs": {
1325
+ "x": {
1326
+ "dtype": "float16",
1327
+ "shape": [4096, 64],
1328
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 1.0 }
1329
+ }
1330
+ },
1331
+ "outputs": { "y": { "dtype": "float16", "shape": [64], "tolerance": 0.002, "relTolerance": 0.002 } }
1332
+ },
1333
+ {
1334
+ "name": "int32_axis0_tiled_64x32",
1335
+ "attrs": { "axes": [0], "keepdims": 0 },
1336
+ "inputs": {
1337
+ "x": {
1338
+ "dtype": "int32",
1339
+ "shape": [64, 32],
1340
+ "data": { "kind": "cycle", "values": [16777217, 3, -5, 16777219, 7, -11, 2] }
1341
+ }
1342
+ },
1343
+ "outputs": { "y": { "dtype": "int32", "shape": [32], "tolerance": 0 } }
1344
+ }
1345
+ ]
1346
+ }