Xenova HF Staff commited on
Commit
19afa49
·
verified ·
1 Parent(s): 1a53346

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `ReduceProd` spec](https://onnx.ai/onnx/operators/onnx__ReduceProd
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `data` | `x` | `T` | — | — | The input tensor to reduce. | required |
24
 
25
  ## Outputs
26
 
27
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
- | `reduced` | `y` | `T` | derived | — | The product-reduced output tensor. | required |
30
 
31
  ## Attributes
32
 
@@ -34,9 +34,9 @@ Default values (overridable per request):
34
 
35
  | Attribute | Default | Description |
36
  | --- | --- | --- |
 
37
  | `keepdims` | `1` | If 1, retains reduced dimensions with size 1 in the output; if 0, the reduced dimensions are removed. |
38
  | `noop_with_empty_axes` | `0` | If 0 (default), empty or absent axes trigger reduction over all axes; if 1, empty axes make the op a no-op identity pass-through. |
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
 
@@ -50,7 +50,7 @@ Some implementation variants require `subgroups`. These are route-specific capab
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
@@ -61,21 +61,25 @@ Some implementation variants require `subgroups`. These are route-specific capab
61
  - [`reduce-flat-partial.wgsl.jinja`](build/webgpu/reduce-flat-partial.wgsl.jinja)
62
  - [`reduce-i32-axes02.wgsl.jinja`](build/webgpu/reduce-i32-axes02.wgsl.jinja)
63
  - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
 
64
  - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
65
  - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
66
  - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
67
 
68
  ## Use with `@huggingface/kernels`
69
 
70
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
71
 
72
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
73
 
74
- - `y`
75
 
76
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
77
 
78
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
79
 
80
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
81
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `data` | `T` | — | — | The input tensor to reduce. | required |
24
 
25
  ## Outputs
26
 
27
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
+ | `y` | `reduced` | `T` | derived | — | The product-reduced output tensor. | required |
30
 
31
  ## Attributes
32
 
 
34
 
35
  | Attribute | Default | Description |
36
  | --- | --- | --- |
37
+ | `axes` | `[]` | Values of the optional ONNX `axes` tensor input, supplied through this request attribute; an empty list follows `noop_with_empty_axes`. |
38
  | `keepdims` | `1` | If 1, retains reduced dimensions with size 1 in the output; if 0, the reduced dimensions are removed. |
39
  | `noop_with_empty_axes` | `0` | If 0 (default), empty or absent axes trigger reduction over all axes; if 1, empty axes make the op a no-op identity pass-through. |
 
40
 
41
  ## Type constraints
42
 
 
50
 
51
  ## Files
52
 
53
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
54
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
  - [`test.json`](build/webgpu/test.json) — correctness cases
56
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
61
  - [`reduce-flat-partial.wgsl.jinja`](build/webgpu/reduce-flat-partial.wgsl.jinja)
62
  - [`reduce-i32-axes02.wgsl.jinja`](build/webgpu/reduce-i32-axes02.wgsl.jinja)
63
  - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
64
+ - [`reduce-row-subgroup-rows.wgsl.jinja`](build/webgpu/reduce-row-subgroup-rows.wgsl.jinja)
65
  - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
66
  - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
67
  - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
68
 
69
  ## Use with `@huggingface/kernels`
70
 
71
+ ```sh
72
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
73
+ ```
74
 
75
+ Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
76
 
77
+ This example supplies explicit metadata for:
78
 
79
+ - `y`
80
 
81
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
82
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
83
 
84
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
85
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.ReduceProd",
3
  "cases": [
4
  {
5
  "name": "reduce-prod-axis0-1024x512",
@@ -96,9 +95,7 @@
96
  "preset": "smoke",
97
  "vars": { "dtype": "int32", "rows": 256, "cols": 4096 },
98
  "attrs": { "axes": [1], "keepdims": 0 },
99
- "inputs": {
100
- "x": { "shape": [256, 4096], "dtype": "int32", "dist": "uniform", "seed": 423, "scale": 1, "offset": 1 }
101
- },
102
  "outputs": { "y": { "shape": [256], "dtype": "int32" } },
103
  "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
104
  },
@@ -156,7 +153,7 @@
156
  "outputs": { "y": { "shape": [4096], "dtype": "float32" } },
157
  "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] },
158
  "provenance": {
159
- "notes": "One row-count notch outside the row-serial region, where the row-per-workgroup tree wins by 1.7x. Pairs with the 8192x1024 case to bracket ROW_SERIAL_MIN_ROWS."
160
  }
161
  },
162
  {
@@ -174,7 +171,7 @@
174
  "name": "reduceprod-rank3-spatial-axes12-f32-128x256x256-pathology",
175
  "preset": "stress",
176
  "provenance": {
177
- "source": "authored for branch coverage",
178
  "notes": "Rank-3 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Inputs stay close to one so the long product remains finite."
179
  },
180
  "attrs": { "axes": [1, 2], "keepdims": 1 },
@@ -195,7 +192,7 @@
195
  "name": "reduceprod-rank4-spatial-axes23-f32-2x64x256x256-pathology",
196
  "preset": "stress",
197
  "provenance": {
198
- "source": "authored for branch coverage",
199
  "notes": "Rank-4 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Inputs stay close to one so the long product remains finite."
200
  },
201
  "attrs": { "axes": [2, 3], "keepdims": 1 },
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "reduce-prod-axis0-1024x512",
 
95
  "preset": "smoke",
96
  "vars": { "dtype": "int32", "rows": 256, "cols": 4096 },
97
  "attrs": { "axes": [1], "keepdims": 0 },
98
+ "inputs": { "x": { "shape": [256, 4096], "dtype": "int32", "dist": "uniform", "seed": 423, "min": 0, "max": 2 } },
 
 
99
  "outputs": { "y": { "shape": [256], "dtype": "int32" } },
100
  "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] }
101
  },
 
153
  "outputs": { "y": { "shape": [4096], "dtype": "float32" } },
154
  "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype)" }] },
155
  "provenance": {
156
+ "notes": "A [4096, 1024] tensor lies below ROW_SERIAL_MIN_ROWS, so each output row uses a workgroup-tree reduction. Together with `reduceprod-rowserial-boundary-8192x1024`, this brackets the row-count selector transition."
157
  }
158
  },
159
  {
 
171
  "name": "reduceprod-rank3-spatial-axes12-f32-128x256x256-pathology",
172
  "preset": "stress",
173
  "provenance": {
174
+ "source": "repository-authored",
175
  "notes": "Rank-3 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Inputs stay close to one so the long product remains finite."
176
  },
177
  "attrs": { "axes": [1, 2], "keepdims": 1 },
 
192
  "name": "reduceprod-rank4-spatial-axes23-f32-2x64x256x256-pathology",
193
  "preset": "stress",
194
  "provenance": {
195
+ "source": "repository-authored",
196
  "notes": "Rank-4 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Inputs stay close to one so the long product remains finite."
197
  },
198
  "attrs": { "axes": [2, 3], "keepdims": 1 },
build/webgpu/manifest.json CHANGED
@@ -2,478 +2,242 @@
2
  "domain": "ai.onnx",
3
  "name": "ReduceProd",
4
  "sinceVersion": 18,
5
- "description": "Computes the product of elements along specified axes of the input tensor. The output rank matches the input when `keepdims` is 1; reduced dimensions are pruned when `keepdims` is 0. Reduction over an empty set of values yields 1.",
6
- "inputs": [{ "role": "data", "dtype": "T", "description": "The input tensor to reduce." }],
7
- "outputs": [
8
- {
9
- "role": "reduced",
10
  "dtype": "T",
11
- "rank": "ranks.data if attrs.keepdims == 1 or ((attrs.axes | length) == 0 and attrs.noop_with_empty_axes == 1) else (ranks.data - (attrs.axes | length) if (attrs.axes | length) > 0 else 0)",
12
- "description": "The product-reduced output tensor."
13
  }
14
- ],
15
- "attributes": { "keepdims": 1, "noop_with_empty_axes": 0, "axes": [] },
16
- "attributeDescriptions": {
17
- "keepdims": "If 1, retains reduced dimensions with size 1 in the output; if 0, the reduced dimensions are removed.",
18
- "noop_with_empty_axes": "If 0 (default), empty or absent axes trigger reduction over all axes; if 1, empty axes make the op a no-op identity pass-through.",
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
  "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
30
  "treeWorkgroupOk": "reduceWorkgroupSize > 0 and pow2ceil(reduceWorkgroupSize) == reduceWorkgroupSize and reduceWorkgroupSize * dtypeBytes(\"float32\") <= device.limits.maxComputeWorkgroupStorageSize",
31
  "subgroupWorkgroupFloor": "min(reduceWorkgroupSize, max(1, device.adapterInfo.subgroupMaxSize))",
32
- "lastAxisRows": "rows(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
33
- "lastAxisCols": "dim(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
34
  "rowSerialPreferred": "lastAxisRows >= tunables.ROW_SERIAL_MIN_ROWS and lastAxisCols <= tunables.ROW_SERIAL_MAX_COLS",
35
- "axis0Rows": "dim(shapes.data, 0) if ranks.data >= 2 else 0",
36
- "axis0Cols": "dim(shapes.data, 1) if ranks.data >= 2 else 0",
37
  "axis0SplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axis0Rows, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
38
  "axis0SplitScratchBytes": "axis0SplitCount * axis0Cols * dtypeBytes(\"float32\")",
39
- "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",
40
- "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",
41
- "axisSplitDim": "dim(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 0",
42
- "axisSplitInner": "inner(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 1",
43
- "axisSplitOutputs": "numel(shapes.reduced)",
44
  "axisSplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axisSplitDim, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
45
  "axisSplitScratchBytes": "axisSplitCount * axisSplitOutputs * 4",
46
- "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",
47
  "axis0TilePathFits": "treeWorkgroupOk and tunables.AXIS0_TILE_COLS > 0 and tunables.AXIS0_TILE_COLS <= reduceWorkgroupSize and reduceWorkgroupSize % tunables.AXIS0_TILE_COLS == 0",
48
- "flatItems": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)",
49
  "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
50
  "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
51
- "flatPathFits": "treeWorkgroupOk and flatSplitCount <= device.limits.maxComputeWorkgroupsPerDimension and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
52
- "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",
53
- "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)))"
54
- },
55
- "tunables": {
56
- "WORKGROUP_SIZE": 256,
57
- "VECTOR_WIDTH": 4,
58
- "ROW_PARALLEL_MIN_COLS": 64,
59
- "SUBGROUP_MIN_COLS": 256,
60
- "SUBGROUP_SMALL_ROW_LIMIT": 32768,
61
- "AXIS0_SPLIT_MIN_ROWS": 8192,
62
- "AXIS0_SPLIT_TARGET_ROWS": 256,
63
- "AXIS0_MAX_SPLITS": 128,
64
- "AXIS0_TILE_MIN_ROWS": 64,
65
- "AXIS0_TILE_MIN_COLS": 16,
66
- "AXIS0_TILE_COLS": 16,
67
- "AXIS_SPLIT_TILE_COLS": 8,
68
- "FULL_REDUCE_MIN_ELEMENTS": 8192,
69
- "FULL_REDUCE_MAX_SPLITS": 256,
70
- "AXES02_WORKGROUP_SIZE": 64,
71
- "CONTIGUOUS_SUFFIX_MIN_COLS": 256,
72
- "ROW_SERIAL_MIN_ROWS": 8192,
73
- "ROW_SERIAL_MAX_COLS": 1024
74
  },
75
- "bindingSets": {
76
- "elementwise": [
77
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
78
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
79
- {
80
- "name": "params",
81
- "semantic": "kernel.params",
82
- "buffer": { "type": "uniform" },
83
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.reduced)" }] }
84
- }
85
- ],
86
- "axes02": [
87
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
88
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
89
- {
90
- "name": "params",
91
- "semantic": "kernel.params",
92
- "buffer": { "type": "uniform" },
93
- "struct": {
94
- "name": "Params",
95
- "fields": [
96
- { "name": "dim0", "type": "u32", "value": "dim(shapes.data, 0)" },
97
- { "name": "dim1", "type": "u32", "value": "dim(shapes.data, 1)" },
98
- { "name": "dim2", "type": "u32", "value": "dim(shapes.data, 2)" }
99
- ]
100
- }
101
- }
102
- ],
103
- "lastAxisVec4": [
104
- {
105
- "name": "x",
106
- "arg": "x",
107
- "semantic": "data",
108
- "buffer": { "type": "read-only-storage" },
109
- "elementType": "$vectorScalar"
110
- },
111
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
112
- {
113
- "name": "params",
114
- "semantic": "kernel.params",
115
- "buffer": { "type": "uniform" },
116
- "struct": {
117
- "name": "Params",
118
- "fields": [
119
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
120
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1) / tunables.VECTOR_WIDTH" }
121
- ]
122
- }
123
- }
124
- ],
125
- "lastAxisScalar": [
126
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
127
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
128
- {
129
- "name": "params",
130
- "semantic": "kernel.params",
131
- "buffer": { "type": "uniform" },
132
- "struct": {
133
- "name": "Params",
134
- "fields": [
135
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
136
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
137
- ]
138
- }
139
- }
140
- ],
141
- "lastAxisScalarSubgroup": [
142
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
143
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
144
- {
145
- "name": "params",
146
- "semantic": "kernel.params",
147
- "buffer": { "type": "uniform" },
148
- "struct": {
149
- "name": "Params",
150
- "fields": [
151
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
152
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
153
- ]
154
- }
155
- }
156
- ],
157
- "scalar": [
158
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
159
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
160
- {
161
- "name": "params",
162
- "semantic": "kernel.params",
163
- "buffer": { "type": "uniform" },
164
- "struct": {
165
- "name": "Params",
166
- "fields": [
167
- { "name": "rows", "type": "u32", "value": "1" },
168
- { "name": "cols", "type": "u32", "value": "1" },
169
- { "name": "outCount", "type": "u32", "value": "1" }
170
- ]
171
- }
172
- }
173
- ],
174
- "rank1Axis0": [
175
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
176
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
177
- {
178
- "name": "params",
179
- "semantic": "kernel.params",
180
- "buffer": { "type": "uniform" },
181
- "struct": {
182
- "name": "Params",
183
- "fields": [
184
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
185
- { "name": "cols", "type": "u32", "value": "1" },
186
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
187
- ]
188
- }
189
- }
190
- ],
191
- "rank2Serial": [
192
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
193
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
194
- {
195
- "name": "params",
196
- "semantic": "kernel.params",
197
- "buffer": { "type": "uniform" },
198
- "struct": {
199
- "name": "Params",
200
- "fields": [
201
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
202
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
203
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
204
- ]
205
- }
206
- }
207
- ],
208
- "rank2SerialAxis1": [
209
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
210
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
211
- {
212
- "name": "params",
213
- "semantic": "kernel.params",
214
- "buffer": { "type": "uniform" },
215
- "struct": {
216
- "name": "Params",
217
- "fields": [
218
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
219
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
220
- ]
221
- }
222
- }
223
- ],
224
- "axis0Parallel": [
225
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
226
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
227
- {
228
- "name": "params",
229
- "semantic": "kernel.params",
230
- "buffer": { "type": "uniform" },
231
- "struct": {
232
- "name": "Params",
233
- "fields": [
234
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
235
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
236
- ]
237
- }
238
- }
239
- ],
240
- "fullReduceSerial": [
241
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
242
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
243
- {
244
- "name": "params",
245
- "semantic": "kernel.params",
246
- "buffer": { "type": "uniform" },
247
- "struct": {
248
- "name": "Params",
249
- "fields": [
250
- { "name": "rows", "type": "u32", "value": "numel(shapes.data)" },
251
- { "name": "cols", "type": "u32", "value": "1" },
252
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
253
- ]
254
- }
255
- }
256
- ],
257
- "axisSplitReduce": [
258
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
259
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
260
- {
261
- "name": "params",
262
- "semantic": "kernel.params",
263
- "buffer": { "type": "uniform" },
264
- "struct": {
265
- "name": "Params",
266
- "fields": [
267
- { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
268
- { "name": "inner", "type": "u32", "value": "axisSplitInner" },
269
- { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
270
- ]
271
- }
272
- }
273
- ],
274
- "axisSplitCombine": [
275
- {
276
- "name": "partials",
277
- "semantic": "partials",
278
- "buffer": { "type": "read-only-storage" },
279
- "elementType": "$partialElement"
280
- },
281
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
282
- {
283
- "name": "params",
284
- "semantic": "kernel.params",
285
- "buffer": { "type": "uniform" },
286
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }] }
287
- }
288
- ],
289
- "axis0SplitReduce": [
290
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
291
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
292
- {
293
- "name": "params",
294
- "semantic": "kernel.params",
295
- "buffer": { "type": "uniform" },
296
- "struct": {
297
- "name": "Params",
298
- "fields": [
299
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
300
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
301
- ]
302
- }
303
- }
304
- ],
305
- "axis0SplitCombine": [
306
- {
307
- "name": "partials",
308
- "semantic": "partials",
309
- "buffer": { "type": "read-only-storage" },
310
- "elementType": "$partialElement"
311
- },
312
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
313
- {
314
- "name": "params",
315
- "semantic": "kernel.params",
316
- "buffer": { "type": "uniform" },
317
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }] }
318
- }
319
- ],
320
- "rankNAxis": [
321
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
322
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
323
- {
324
- "name": "params",
325
- "semantic": "kernel.params",
326
- "buffer": { "type": "uniform" },
327
- "struct": {
328
- "name": "Params",
329
- "fields": [
330
- { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
331
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
332
- ]
333
- }
334
- }
335
- ],
336
- "flatPartial": [
337
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
338
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
339
- {
340
- "name": "params",
341
- "semantic": "kernel.params",
342
- "buffer": { "type": "uniform" },
343
- "struct": {
344
- "name": "Params",
345
- "fields": [
346
- { "name": "count4", "type": "u32", "value": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)" },
347
- { "name": "numel", "type": "u32", "value": "numel(shapes.data)" }
348
- ]
349
- }
350
- }
351
- ],
352
- "flatCombine": [
353
- {
354
- "name": "partials",
355
- "semantic": "partials",
356
- "buffer": { "type": "read-only-storage" },
357
- "elementType": "$partialElement"
358
- },
359
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
360
- {
361
- "name": "params",
362
- "semantic": "kernel.params",
363
- "buffer": { "type": "uniform" },
364
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "1" }] }
365
- }
366
- ],
367
- "suffixVec4": [
368
- {
369
- "name": "x",
370
- "arg": "x",
371
- "semantic": "data",
372
- "buffer": { "type": "read-only-storage" },
373
- "elementType": "$vectorScalar"
374
- },
375
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
376
- {
377
- "name": "params",
378
- "semantic": "kernel.params",
379
- "buffer": { "type": "uniform" },
380
- "struct": {
381
- "name": "Params",
382
- "fields": [
383
- { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
384
- {
385
- "name": "chunkCount",
386
- "type": "u32",
387
- "value": "numel(shapes.data) / numel(shapes.reduced) / tunables.VECTOR_WIDTH"
388
- }
389
- ]
390
- }
391
- }
392
- ],
393
- "suffixScalar": [
394
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
395
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
396
- {
397
- "name": "params",
398
- "semantic": "kernel.params",
399
- "buffer": { "type": "uniform" },
400
- "struct": {
401
- "name": "Params",
402
- "fields": [
403
- { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
404
- { "name": "cols", "type": "u32", "value": "numel(shapes.data) / numel(shapes.reduced)" }
405
- ]
406
- }
407
- }
408
- ],
409
- "multiAxis": [
410
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
411
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
412
- {
413
- "name": "params",
414
- "semantic": "kernel.params",
415
- "buffer": { "type": "uniform" },
416
- "struct": {
417
- "name": "Params",
418
- "fields": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }]
419
- }
420
- }
421
- ]
422
  },
423
  "variants": [
424
  {
425
  "id": "contiguous_suffix_subgroup_vec4",
426
  "priority": 30,
 
427
  "requires": { "features": ["subgroups"] },
428
- "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0"],
429
- "constants": {
430
  "scalar": "dtypes.T",
431
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
432
- "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH))))"
433
  },
434
  "passes": [
435
  {
436
  "id": "main",
437
  "name": "ReduceProd.ContiguousSuffixSubgroupVec4",
438
- "source": {
439
- "shader": "reduce-row-subgroup.wgsl.jinja",
440
- "inputs": {
441
- "op": "\"prod\"",
442
- "vec4": true,
443
- "castF32": "dtypes.T == \"f16\"",
444
- "usesF16": "dtypes.T == \"f16\""
445
- }
446
  },
447
- "subgroupCollectivesWidth": "portable",
448
- "bindings": "suffixVec4",
449
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
450
  }
451
  ]
452
  },
453
  {
454
  "id": "contiguous_suffix_tree_vec4",
455
  "priority": 22,
456
- "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
457
- "constants": {
458
  "scalar": "dtypes.T",
459
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
460
- "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH)))"
461
  },
462
  "passes": [
463
  {
464
  "id": "main",
465
  "name": "ReduceProd.ContiguousSuffixTreeVec4",
466
- "source": {
467
- "shader": "reduce-row-tree.wgsl.jinja",
468
- "inputs": {
469
- "op": "\"prod\"",
470
- "vec4": true,
471
- "castF32": "dtypes.T == \"f16\"",
472
- "usesF16": "dtypes.T == \"f16\""
473
- }
474
  },
475
- "bindings": "suffixVec4",
476
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
477
  }
478
  ]
479
  },
@@ -481,142 +245,204 @@
481
  "id": "contiguous_suffix_tree",
482
  "priority": 21,
483
  "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
484
- "constants": {
485
- "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.data) / numel(shapes.reduced)))",
486
  "scalar": "dtypes.T"
487
  },
488
  "passes": [
489
  {
490
  "id": "main",
491
  "name": "ReduceProd.ContiguousSuffixTree",
492
- "source": {
493
- "shader": "reduce-row-tree.wgsl.jinja",
494
- "inputs": { "op": "\"prod\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
495
- },
496
- "bindings": "suffixScalar",
497
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
498
  }
499
  ]
500
  },
501
  {
502
  "id": "noop_empty_axes",
503
  "priority": 40,
504
- "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.data, shapes.reduced)"],
 
505
  "passes": [
506
  {
507
  "id": "main",
508
  "name": "ReduceProd.NoopEmptyAxes",
509
- "source": { "shader": "reduce-noop-empty-axes.wgsl.jinja" },
510
- "bindings": "elementwise",
511
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
512
  }
513
  ]
514
  },
515
  {
516
  "id": "multi_axis_rank3",
517
  "priority": 8,
518
- "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)"],
 
519
  "passes": [
520
  {
521
  "id": "main",
522
  "name": "ReduceProd.MultiAxisRank3",
523
- "source": {
524
- "shader": "reduce-serial-axis.wgsl.jinja",
525
- "inputs": {
526
- "op": "\"prod\"",
527
- "indexing": "\"multiaxis\"",
528
- "rank": 3,
529
- "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
530
- "dataShape": "shapes.data",
531
- "outputShape": "shapes.reduced",
532
- "outputRank": "ranks.reduced",
533
- "keepDims": "attrs.keepdims != 0",
534
- "intMode": "dtypes.T == \"i32\"",
535
- "castF32": "dtypes.T == \"f16\"",
536
- "usesF16": "dtypes.T == \"f16\""
537
- }
538
  },
539
- "bindings": "multiAxis",
540
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
541
  }
542
- ],
543
- "constants": { "scalar": "dtypes.T" }
544
  },
545
  {
546
  "id": "multi_axis_rank4",
547
  "priority": 8,
548
- "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))"],
 
549
  "passes": [
550
  {
551
  "id": "main",
552
  "name": "ReduceProd.MultiAxisRank4",
553
- "source": {
554
- "shader": "reduce-serial-axis.wgsl.jinja",
555
- "inputs": {
556
- "op": "\"prod\"",
557
- "indexing": "\"multiaxis\"",
558
- "rank": 4,
559
- "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
560
- "dataShape": "shapes.data",
561
- "outputShape": "shapes.reduced",
562
- "outputRank": "ranks.reduced",
563
- "keepDims": "attrs.keepdims != 0",
564
- "intMode": "dtypes.T == \"i32\"",
565
- "castF32": "dtypes.T == \"f16\"",
566
- "usesF16": "dtypes.T == \"f16\""
567
- }
568
  },
569
- "bindings": "multiAxis",
570
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
571
  }
572
- ],
573
- "constants": { "scalar": "dtypes.T" }
574
  },
575
  {
576
  "id": "int32_rank3_axes02_keepdims",
577
  "priority": 35,
578
- "when": ["dtypes.T == \"i32\"", "ranks.data == 3", "attrs.keepdims == 1", "hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 2, 3)", "hasAxis(attrs.axes, 1, 3) == false", "dim(shapes.data, 0) > 0", "dim(shapes.data, 2) > 0", "ranks.reduced == 3", "dim(shapes.reduced, 0) == 1", "dim(shapes.reduced, 1) == dim(shapes.data, 1)", "dim(shapes.reduced, 2) == 1"],
 
 
 
579
  "passes": [
580
  {
581
  "id": "main",
582
  "name": "ReduceProd.Int32Rank3Axes02Keepdims",
583
- "bindings": "axes02",
584
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "axes02WorkgroupSize" },
585
- "source": {
586
- "shader": "reduce-i32-axes02.wgsl.jinja",
587
- "inputs": { "op": "\"prod\"", "workgroupSize": "axes02WorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  }
589
  }
590
- ],
591
- "derive": {
592
- "axes02WorkgroupSize": "min(tunables.AXES02_WORKGROUP_SIZE, device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)"
593
- }
594
  },
595
  {
596
  "id": "tree_last_axis",
597
  "priority": 20,
 
598
  "demoteWhen": ["rowSerialPreferred"],
599
- "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"i32\")", "ranks.data >= 2", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "treeWorkgroupOk"],
600
- "constants": {
601
  "scalar": "dtypes.T",
602
- "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.data, ranks.data - 1)))"
603
  },
604
  "passes": [
605
  {
606
  "id": "main",
607
  "name": "ReduceProd.TreeLastAxis",
608
- "source": { "shader": "reduce-row-tree.wgsl.jinja", "inputs": { "op": "\"prod\"" } },
609
- "bindings": "lastAxisScalar",
610
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  }
612
  ]
613
  },
614
  {
615
  "id": "tree_last_axis_vec4",
616
  "priority": 23,
 
617
  "demoteWhen": ["rowSerialPreferred"],
618
- "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\" or dtypes.T == \"i32\")", "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"],
619
- "constants": {
620
  "scalar": "dtypes.T",
621
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
622
  "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
@@ -625,116 +451,116 @@
625
  {
626
  "id": "main",
627
  "name": "ReduceProd.TreeRowVec4",
628
- "source": {
629
- "shader": "reduce-row-tree.wgsl.jinja",
630
- "inputs": {
631
- "op": "\"prod\"",
632
- "vec4": true,
633
- "castF32": "dtypes.T == \"f16\"",
634
- "usesF16": "dtypes.T == \"f16\""
635
- }
636
  },
637
- "bindings": "lastAxisVec4",
638
- "dispatch": { "workgroups": "lastAxisRows" }
639
  }
640
  ]
641
  },
642
  {
643
  "id": "rank0_scalar",
644
  "priority": 40,
645
- "constants": { "axis": 0, "scalar": "dtypes.T" },
646
- "when": ["f16Ok(dtypes.T)", "ranks.data == 0", "ranks.reduced == 0"],
647
  "passes": [
648
  {
649
  "id": "main",
650
  "name": "ReduceProd.Rank0Scalar",
651
- "source": {
652
- "shader": "reduce-serial-axis.wgsl.jinja",
653
- "inputs": {
654
- "op": "\"prod\"",
655
- "indexing": "\"axis2d\"",
656
- "intMode": "dtypes.T == \"i32\"",
657
- "castF32": "dtypes.T == \"f16\"",
658
- "usesF16": "dtypes.T == \"f16\"",
659
- "logicalBool": "tensorDtypes.data == \"bool\""
660
- }
661
  },
662
- "bindings": "scalar",
663
  "dispatch": { "x": 1 }
664
  }
665
  ]
666
  },
667
  {
668
  "id": "rank1_axis0",
669
- "constants": { "axis": 0, "scalar": "dtypes.T" },
670
- "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))"],
671
  "passes": [
672
  {
673
  "id": "main",
674
  "name": "ReduceProd.Rank1Axis0",
675
- "source": {
676
- "shader": "reduce-serial-axis.wgsl.jinja",
677
- "inputs": {
678
- "op": "\"prod\"",
679
- "indexing": "\"axis2d\"",
680
- "intMode": "dtypes.T == \"i32\"",
681
- "castF32": "dtypes.T == \"f16\"",
682
- "usesF16": "dtypes.T == \"f16\"",
683
- "logicalBool": "tensorDtypes.data == \"bool\""
684
- }
685
  },
686
- "bindings": "rank1Axis0",
687
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
688
  }
689
  ]
690
  },
691
  {
692
  "id": "axis_split",
693
  "priority": 24,
694
- "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"],
695
- "derive": { "splitCount": "axisSplitCount" },
696
- "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
 
 
 
 
697
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
698
  "passes": [
699
  {
700
  "id": "split_reduce",
701
  "name": "ReduceProd.AxisSplitReduce",
702
- "source": {
703
- "shader": "reduce-axis-split-reduce.wgsl.jinja",
704
- "inputs": {
705
- "op": "\"prod\"",
706
- "workgroupSize": "reduceWorkgroupSize",
707
- "split": "splitCount",
708
- "castF32": "dtypes.T == \"f16\"",
709
- "usesF16": "dtypes.T == \"f16\""
710
- }
711
  },
712
- "bindings": "axisSplitReduce",
713
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
 
 
 
 
714
  },
715
  {
716
  "id": "combine",
717
  "name": "ReduceProd.AxisSplitCombine",
718
- "source": {
719
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
720
- "inputs": {
721
- "op": "\"prod\"",
722
- "workgroupSize": "reduceWorkgroupSize",
723
- "split": "splitCount",
724
- "outputF16": "dtypes.T == \"f16\""
725
- }
726
- },
727
- "bindings": "axisSplitCombine",
728
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
729
  }
730
  ]
731
  },
732
  {
733
  "id": "axis_split_tiled_narrow",
734
  "priority": 25,
735
- "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"],
736
- "derive": { "splitCount": "axisSplitCount" },
737
- "constants": {
738
  "partialElement": "\"f32\"",
739
  "scalar": "dtypes.T",
740
  "workgroupSize": "reduceWorkgroupSize",
@@ -746,175 +572,177 @@
746
  {
747
  "id": "split_reduce",
748
  "name": "ReduceProd.AxisSplitTiledReduce",
749
- "source": {
750
- "shader": "reduce-axis0-tilecols.wgsl.jinja",
751
- "inputs": {
752
- "op": "\"prod\"",
753
- "workgroupSize": "reduceWorkgroupSize",
754
- "split": "splitCount",
755
- "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
756
- "castF32": "dtypes.T == \"f16\"",
757
- "usesF16": "dtypes.T == \"f16\""
758
- }
759
  },
760
- "bindings": "axisSplitReduce",
761
- "dispatch": { "workgroups": "ceilDiv((axisSplitOutputs), (constants.tileCols))", "y": "splitCount" }
 
 
 
 
762
  },
763
  {
764
  "id": "combine",
765
  "name": "ReduceProd.AxisSplitCombine",
766
- "source": {
767
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
768
- "inputs": {
769
- "op": "\"prod\"",
770
- "workgroupSize": "reduceWorkgroupSize",
771
- "split": "splitCount",
772
- "outputF16": "dtypes.T == \"f16\""
773
- }
774
- },
775
- "bindings": "axisSplitCombine",
776
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
777
  }
778
  ]
779
  },
780
  {
781
  "id": "axis0_splitk",
782
  "priority": 22,
783
- "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"],
784
- "derive": { "splitCount": "axis0SplitCount" },
785
- "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
786
- "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * dim(shapes.data, 1)]" }],
 
 
 
 
787
  "passes": [
788
  {
789
  "id": "split_reduce",
790
  "name": "ReduceProd.Axis0SplitKReduce",
791
- "source": {
792
- "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
793
- "inputs": {
794
- "op": "\"prod\"",
795
- "workgroupSize": "reduceWorkgroupSize",
796
- "split": "splitCount",
797
- "castF32": "dtypes.T == \"f16\"",
798
- "usesF16": "dtypes.T == \"f16\""
799
- }
800
  },
801
- "bindings": "axis0SplitReduce",
802
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
 
 
 
 
803
  },
804
  {
805
  "id": "combine",
806
  "name": "ReduceProd.Axis0SplitKCombine",
807
- "source": {
808
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
809
- "inputs": {
810
- "op": "\"prod\"",
811
- "workgroupSize": "reduceWorkgroupSize",
812
- "split": "splitCount",
813
- "outputF16": "dtypes.T == \"f16\""
814
- }
815
- },
816
- "bindings": "axis0SplitCombine",
817
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize" }
818
  }
819
  ]
820
  },
821
  {
822
  "id": "axis0_splitk_i32",
823
  "priority": 23,
824
- "when": ["dtypes.T == \"i32\"", "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"],
825
- "derive": { "splitCount": "axis0SplitCount" },
826
- "constants": { "partialElement": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
827
- "intermediates": [{ "id": "partials", "dtype": "int32", "shape": "[splitCount * dim(shapes.data, 1)]" }],
 
 
 
 
828
  "passes": [
829
  {
830
  "id": "split_reduce",
831
  "name": "ReduceProd.Axis0SplitKReduceI32",
832
- "source": {
833
- "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
834
- "inputs": {
835
- "op": "\"prod\"",
836
- "intMode": true,
837
- "workgroupSize": "reduceWorkgroupSize",
838
- "split": "splitCount"
839
- }
840
  },
841
- "bindings": "axis0SplitReduce",
842
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
 
 
 
 
843
  },
844
  {
845
  "id": "combine",
846
  "name": "ReduceProd.Axis0SplitKCombineI32",
847
- "source": {
848
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
849
- "inputs": {
850
- "op": "\"prod\"",
851
- "intMode": true,
852
- "workgroupSize": "reduceWorkgroupSize",
853
- "split": "splitCount"
854
- }
855
  },
856
- "bindings": "axis0SplitCombine",
857
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
858
  }
859
  ]
860
  },
861
  {
862
  "id": "axis0_tilecols",
863
  "priority": 20,
864
- "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"],
865
- "constants": {
866
- "scalar": "dtypes.T",
867
- "workgroupSize": "reduceWorkgroupSize",
868
- "tileCols": "tunables.AXIS0_TILE_COLS"
869
- },
870
  "passes": [
871
  {
872
  "id": "main",
873
  "name": "ReduceProd.Axis0TileCols",
874
- "source": {
875
- "shader": "reduce-axis0-tilecols.wgsl.jinja",
876
- "inputs": {
877
- "op": "\"prod\"",
878
- "intMode": "dtypes.T == \"i32\"",
879
- "castF32": "dtypes.T == \"f16\"",
880
- "usesF16": "dtypes.T == \"f16\""
881
- }
882
  },
883
- "bindings": "axis0Parallel",
884
- "dispatch": { "workgroups": "ceilDiv((dim(shapes.data, 1)), (constants.tileCols))" }
 
 
 
 
885
  }
886
  ]
887
  },
888
  {
889
  "id": "all_axes_flat",
890
  "priority": 31,
891
- "constants": {
 
892
  "partialElement": "\"f32\"",
893
  "scalar": "dtypes.T",
894
  "workgroupSize": "reduceWorkgroupSize",
895
  "split": "flatSplitCount"
896
  },
897
- "when": ["flatParallelCovered"],
898
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
899
  "passes": [
900
  {
901
  "id": "flat_partial",
902
  "name": "ReduceProd.AllAxesFlatPartial",
903
- "source": {
904
- "shader": "reduce-flat-partial.wgsl.jinja",
905
- "inputs": { "op": "\"prod\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
906
- },
907
- "bindings": "flatPartial",
908
  "dispatch": { "x": "flatSplitCount" }
909
  },
910
  {
911
  "id": "combine",
912
  "name": "ReduceProd.AllAxesFlatCombine",
913
- "source": {
914
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
915
- "inputs": { "op": "\"prod\"", "outputF16": "dtypes.T == \"f16\"" }
916
- },
917
- "bindings": "flatCombine",
918
  "dispatch": { "x": 1 }
919
  }
920
  ]
@@ -922,25 +750,24 @@
922
  {
923
  "id": "all_axes_flat_i32",
924
  "priority": 32,
925
- "constants": { "partialElement": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
926
- "when": ["dtypes.T == \"i32\"", "numel(shapes.reduced) == 1", "numel(shapes.data) >= tunables.FULL_REDUCE_MIN_ELEMENTS", "flatPathFits"],
927
  "intermediates": [{ "id": "partials", "dtype": "int32", "shape": "[flatSplitCount]" }],
928
  "passes": [
929
  {
930
  "id": "flat_partial",
931
  "name": "ReduceProd.AllAxesFlatPartialI32",
932
- "source": { "shader": "reduce-flat-partial.wgsl.jinja", "inputs": { "op": "\"prod\"", "intMode": true } },
933
- "bindings": "flatPartial",
 
934
  "dispatch": { "x": "flatSplitCount" }
935
  },
936
  {
937
  "id": "combine",
938
  "name": "ReduceProd.AllAxesFlatCombineI32",
939
- "source": {
940
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
941
- "inputs": { "op": "\"prod\"", "intMode": true, "workgroupSize": 1, "split": "flatSplitCount" }
942
- },
943
- "bindings": "flatCombine",
944
  "dispatch": { "x": 1 }
945
  }
946
  ]
@@ -948,41 +775,43 @@
948
  {
949
  "id": "rankn_single_axis_generic",
950
  "priority": 12,
 
951
  "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4", "tree_last_axis"],
952
- "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))"],
953
  "passes": [
954
  {
955
  "id": "main",
956
  "name": "ReduceProd.RankNSingleAxisGeneric",
957
- "source": {
958
- "shader": "reduce-serial-axis.wgsl.jinja",
959
- "inputs": {
960
- "op": "\"prod\"",
961
- "indexing": "\"rankn\"",
962
- "rank": "ranks.data",
963
- "axis": "reduceAxis",
964
- "dataShape": "shapes.data",
965
- "outputShape": "shapes.reduced",
966
- "outputRank": "ranks.reduced",
967
- "keepDims": "attrs.keepdims != 0",
968
- "intMode": "dtypes.T == \"i32\"",
969
- "castF32": "dtypes.T == \"f16\"",
970
- "usesF16": "dtypes.T == \"f16\""
971
- }
972
  },
973
- "bindings": "rankNAxis",
974
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
975
  }
976
- ],
977
- "constants": { "scalar": "dtypes.T" }
978
  },
979
  {
980
  "id": "subgroup_last_axis_vec4",
981
  "priority": 25,
982
- "requires": { "features": ["subgroups"] },
983
  "demoteWhen": ["rowSerialPreferred"],
984
- "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)"],
985
- "constants": {
986
  "scalar": "dtypes.T",
987
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
988
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
@@ -991,28 +820,30 @@
991
  {
992
  "id": "main",
993
  "name": "ReduceProd.SubgroupRowVec4",
994
- "source": {
995
- "shader": "reduce-row-subgroup.wgsl.jinja",
996
- "inputs": {
997
- "op": "\"prod\"",
998
- "vec4": true,
999
- "castF32": "dtypes.T == \"f16\"",
1000
- "usesF16": "dtypes.T == \"f16\""
1001
- }
 
 
 
 
1002
  },
1003
- "subgroupCollectivesWidth": "portable",
1004
- "bindings": "lastAxisVec4",
1005
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
1006
  }
1007
  ]
1008
  },
1009
  {
1010
  "id": "subgroup_last_axis",
1011
  "priority": 24,
1012
- "requires": { "features": ["subgroups"] },
1013
  "demoteWhen": ["rowSerialPreferred"],
1014
- "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)"],
1015
- "constants": {
1016
  "scalar": "dtypes.T",
1017
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
1018
  },
@@ -1020,141 +851,186 @@
1020
  {
1021
  "id": "main",
1022
  "name": "ReduceProd.SubgroupRow",
1023
- "source": {
1024
- "shader": "reduce-row-subgroup.wgsl.jinja",
1025
- "inputs": {
1026
- "op": "\"prod\"",
1027
- "vec4": false,
1028
- "castF32": "dtypes.T == \"f16\"",
1029
- "usesF16": "dtypes.T == \"f16\""
1030
- }
 
 
 
 
1031
  },
1032
- "subgroupCollectivesWidth": "portable",
1033
- "bindings": "lastAxisScalarSubgroup",
1034
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
1035
  }
1036
  ]
1037
  },
1038
  {
1039
  "id": "axis0",
1040
  "priority": 0,
 
1041
  "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
1042
- "constants": { "axis": 0, "scalar": "dtypes.T" },
1043
- "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)))"],
1044
  "passes": [
1045
  {
1046
  "id": "main",
1047
  "name": "axis0",
1048
- "source": {
1049
- "shader": "reduce-serial-axis.wgsl.jinja",
1050
- "inputs": {
1051
- "op": "\"prod\"",
1052
- "indexing": "\"axis2d\"",
1053
- "intMode": "dtypes.T == \"i32\"",
1054
- "castF32": "dtypes.T == \"f16\"",
1055
- "usesF16": "dtypes.T == \"f16\""
1056
- }
1057
  },
1058
- "bindings": "rank2Serial",
1059
- "constants": { "axis": 0 },
1060
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
1061
  }
1062
  ]
1063
  },
1064
  {
1065
  "id": "axis1",
1066
  "priority": 0,
1067
- "constants": { "axis": 1, "scalar": "dtypes.T" },
1068
- "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))"],
1069
  "passes": [
1070
  {
1071
  "id": "main",
1072
  "name": "axis1",
1073
- "source": {
1074
- "shader": "reduce-serial-axis.wgsl.jinja",
1075
- "inputs": {
1076
- "op": "\"prod\"",
1077
- "indexing": "\"axis2d\"",
1078
- "intMode": "dtypes.T == \"i32\"",
1079
- "castF32": "dtypes.T == \"f16\"",
1080
- "usesF16": "dtypes.T == \"f16\""
1081
- }
1082
  },
1083
- "bindings": "rank2SerialAxis1",
1084
- "constants": { "axis": 1 },
1085
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
1086
  }
1087
  ]
1088
  },
1089
  {
1090
  "id": "rank2_all_axes_keepdims",
1091
  "priority": 30,
1092
- "constants": { "axis": 0, "scalar": "dtypes.T" },
1093
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 2 and attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == 1"],
1094
  "passes": [
1095
  {
1096
  "id": "main",
1097
  "name": "ReduceProd.Rank2AllAxesKeepdims",
1098
- "source": {
1099
- "shader": "reduce-serial-axis.wgsl.jinja",
1100
- "inputs": {
1101
- "op": "\"prod\"",
1102
- "indexing": "\"axis2d\"",
1103
- "intMode": "dtypes.T == \"i32\"",
1104
- "castF32": "dtypes.T == \"f16\"",
1105
- "usesF16": "dtypes.T == \"f16\""
1106
- }
1107
  },
1108
- "bindings": "fullReduceSerial",
1109
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1110
  }
1111
  ]
1112
  },
1113
  {
1114
  "id": "all_axes_keepdims",
1115
  "priority": 30,
1116
- "constants": { "axis": 0, "scalar": "dtypes.T" },
1117
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3 and attrs.keepdims == 1 and ranks.reduced == ranks.data and numel(shapes.reduced) == 1"],
1118
  "passes": [
1119
  {
1120
  "id": "main",
1121
  "name": "ReduceProd.Rank3AllAxesKeepdims",
1122
- "source": {
1123
- "shader": "reduce-serial-axis.wgsl.jinja",
1124
- "inputs": {
1125
- "op": "\"prod\"",
1126
- "indexing": "\"axis2d\"",
1127
- "intMode": "dtypes.T == \"i32\"",
1128
- "castF32": "dtypes.T == \"f16\"",
1129
- "usesF16": "dtypes.T == \"f16\""
1130
- }
1131
  },
1132
- "bindings": "fullReduceSerial",
1133
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1134
  }
1135
  ]
1136
  },
1137
  {
1138
  "id": "all_axes_no_keepdims",
1139
  "priority": 30,
1140
- "constants": { "axis": 0, "scalar": "dtypes.T" },
1141
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3 and attrs.keepdims == 0 and ranks.reduced == 0"],
1142
  "passes": [
1143
  {
1144
  "id": "main",
1145
  "name": "ReduceProd.Rank3AllAxesNoKeepdims",
1146
- "source": {
1147
- "shader": "reduce-serial-axis.wgsl.jinja",
1148
- "inputs": {
1149
- "op": "\"prod\"",
1150
- "indexing": "\"axis2d\"",
1151
- "intMode": "dtypes.T == \"i32\"",
1152
- "castF32": "dtypes.T == \"f16\"",
1153
- "usesF16": "dtypes.T == \"f16\""
1154
- }
1155
  },
1156
- "bindings": "fullReduceSerial",
1157
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1158
  }
1159
  ]
1160
  }
 
2
  "domain": "ai.onnx",
3
  "name": "ReduceProd",
4
  "sinceVersion": 18,
5
+ "inputs": { "x": { "onnx": "data", "dtype": "T" } },
6
+ "outputs": {
7
+ "y": {
8
+ "onnx": "reduced",
 
9
  "dtype": "T",
10
+ "rank": "ranks.x if attrs.keepdims == 1 or ((attrs.axes | length) == 0 and attrs.noop_with_empty_axes == 1) else (ranks.x - (attrs.axes | length) if (attrs.axes | length) > 0 else 0)"
 
11
  }
 
 
 
 
 
 
12
  },
13
+ "attributes": { "keepdims": { "default": 1 }, "noop_with_empty_axes": { "default": 0 }, "axes": { "default": [] } },
14
  "attributeConstraints": { "keepdims": { "values": [0, 1] }, "noop_with_empty_axes": { "values": [0, 1] } },
15
  "typeConstraints": { "T": ["float32", "float16", "int32"] },
16
+ "tunables": {
17
+ "WORKGROUP_SIZE": { "default": 256 },
18
+ "VECTOR_WIDTH": { "default": 4 },
19
+ "ROW_PARALLEL_MIN_COLS": { "default": 64 },
20
+ "SUBGROUP_MIN_COLS": { "default": 256 },
21
+ "SUBGROUP_SMALL_ROW_LIMIT": { "default": 32768 },
22
+ "AXIS0_SPLIT_MIN_ROWS": { "default": 8192 },
23
+ "AXIS0_SPLIT_TARGET_ROWS": { "default": 256 },
24
+ "AXIS0_MAX_SPLITS": { "default": 128 },
25
+ "AXIS0_TILE_MIN_ROWS": { "default": 64 },
26
+ "AXIS0_TILE_MIN_COLS": { "default": 16 },
27
+ "AXIS0_TILE_COLS": { "default": 16 },
28
+ "AXIS_SPLIT_TILE_COLS": { "default": 8 },
29
+ "FULL_REDUCE_MIN_ELEMENTS": { "default": 8192 },
30
+ "FULL_REDUCE_MAX_SPLITS": { "default": 256 },
31
+ "AXES02_WORKGROUP_SIZE": { "default": 64 },
32
+ "CONTIGUOUS_SUFFIX_MIN_COLS": { "default": 256 },
33
+ "ROW_SERIAL_MIN_ROWS": { "default": 8192 },
34
+ "ROW_SERIAL_MAX_COLS": { "default": 1024 }
35
  },
36
  "derive": {
37
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
38
  "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
39
  "treeWorkgroupOk": "reduceWorkgroupSize > 0 and pow2ceil(reduceWorkgroupSize) == reduceWorkgroupSize and reduceWorkgroupSize * dtypeBytes(\"float32\") <= device.limits.maxComputeWorkgroupStorageSize",
40
  "subgroupWorkgroupFloor": "min(reduceWorkgroupSize, max(1, device.adapterInfo.subgroupMaxSize))",
41
+ "lastAxisRows": "rows(shapes.x, ranks.x - 1) if ranks.x > 0 else 1",
42
+ "lastAxisCols": "dim(shapes.x, ranks.x - 1) if ranks.x > 0 else 1",
43
  "rowSerialPreferred": "lastAxisRows >= tunables.ROW_SERIAL_MIN_ROWS and lastAxisCols <= tunables.ROW_SERIAL_MAX_COLS",
44
+ "axis0Rows": "dim(shapes.x, 0) if ranks.x >= 2 else 0",
45
+ "axis0Cols": "dim(shapes.x, 1) if ranks.x >= 2 else 0",
46
  "axis0SplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axis0Rows, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
47
  "axis0SplitScratchBytes": "axis0SplitCount * axis0Cols * dtypeBytes(\"float32\")",
48
+ "axis0SplitPathFits": "axis0SplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(ceilDiv(axis0Cols, reduceWorkgroupSize), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and axis0SplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axis0SplitScratchBytes <= device.limits.maxBufferSize",
49
+ "reduceAxis": "(attrs.axes[0] + ranks.x if attrs.axes[0] < 0 else attrs.axes[0]) if ((attrs.axes | length) == 1 and isUniqueIntList(attrs.axes, 0 - ranks.x, ranks.x, 1)) else ranks.x",
50
+ "axisSplitDim": "dim(shapes.x, reduceAxis) if ranks.x >= 2 and reduceAxis < ranks.x else 0",
51
+ "axisSplitInner": "inner(shapes.x, reduceAxis) if ranks.x >= 2 and reduceAxis < ranks.x else 1",
52
+ "axisSplitOutputs": "numel(shapes.y)",
53
  "axisSplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axisSplitDim, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
54
  "axisSplitScratchBytes": "axisSplitCount * axisSplitOutputs * 4",
55
+ "axisSplitPathFits": "axisSplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(ceilDiv(axisSplitOutputs, reduceWorkgroupSize), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and axisSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axisSplitScratchBytes <= device.limits.maxBufferSize",
56
  "axis0TilePathFits": "treeWorkgroupOk and tunables.AXIS0_TILE_COLS > 0 and tunables.AXIS0_TILE_COLS <= reduceWorkgroupSize and reduceWorkgroupSize % tunables.AXIS0_TILE_COLS == 0",
57
+ "flatItems": "floor(numel(shapes.x) / tunables.VECTOR_WIDTH)",
58
  "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
59
  "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
60
+ "flatPathFits": "treeWorkgroupOk and flatSplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
61
+ "flatParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.y) == 1 and numel(shapes.x) >= tunables.FULL_REDUCE_MIN_ELEMENTS and flatPathFits",
62
+ "contiguousSuffixParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.y) > 0 and numel(shapes.x) % numel(shapes.y) == 0 and numel(shapes.x) / numel(shapes.y) >= tunables.CONTIGUOUS_SUFFIX_MIN_COLS and ((ranks.x == 3 and hasAxis(attrs.axes, 0, 3) == false and hasAxis(attrs.axes, 1, 3) and hasAxis(attrs.axes, 2, 3) and numel(shapes.y) == dim(shapes.x, 0)) or (ranks.x == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) == false and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.y) == dim(shapes.x, 0) * dim(shapes.x, 1)) or (ranks.x == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.y) == dim(shapes.x, 0)))"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  },
64
+ "bindings": {
65
+ "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
66
+ "y": { "buffer": "storage", "elementType": "$T" },
67
+ "params": {
68
+ "buffer": "uniform",
69
+ "struct": [
70
+ { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
71
+ { "name": "chunkCount", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y) / tunables.VECTOR_WIDTH" }
72
+ ]
73
+ },
74
+ "x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "$T" },
75
+ "params_2": {
76
+ "name": "params",
77
+ "buffer": "uniform",
78
+ "struct": [
79
+ { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
80
+ { "name": "cols", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y)" }
81
+ ]
82
+ },
83
+ "params_3": {
84
+ "name": "params",
85
+ "buffer": "uniform",
86
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
87
+ },
88
+ "params_4": {
89
+ "name": "params",
90
+ "buffer": "uniform",
91
+ "struct": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }]
92
+ },
93
+ "params_7": {
94
+ "name": "params",
95
+ "buffer": "uniform",
96
+ "struct": [
97
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
98
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1) / tunables.VECTOR_WIDTH" }
99
+ ]
100
+ },
101
+ "params_8": {
102
+ "name": "params",
103
+ "buffer": "uniform",
104
+ "struct": [
105
+ { "name": "rows", "type": "u32", "value": "1" },
106
+ { "name": "cols", "type": "u32", "value": "1" },
107
+ { "name": "outCount", "type": "u32", "value": "1" }
108
+ ]
109
+ },
110
+ "params_9": {
111
+ "name": "params",
112
+ "buffer": "uniform",
113
+ "struct": [
114
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
115
+ { "name": "cols", "type": "u32", "value": "1" },
116
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
117
+ ]
118
+ },
119
+ "partials": { "buffer": "storage", "elementType": "$partialElement" },
120
+ "params_10": {
121
+ "name": "params",
122
+ "buffer": "uniform",
123
+ "struct": [
124
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
125
+ { "name": "inner", "type": "u32", "value": "axisSplitInner" },
126
+ { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
127
+ ]
128
+ },
129
+ "partials_2": { "name": "partials", "buffer": "read-only-storage", "elementType": "$partialElement" },
130
+ "params_11": {
131
+ "name": "params",
132
+ "buffer": "uniform",
133
+ "struct": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }]
134
+ },
135
+ "params_12": {
136
+ "name": "params",
137
+ "buffer": "uniform",
138
+ "struct": [
139
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
140
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }
141
+ ]
142
+ },
143
+ "params_13": {
144
+ "name": "params",
145
+ "buffer": "uniform",
146
+ "struct": [{ "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }]
147
+ },
148
+ "params_14": {
149
+ "name": "params",
150
+ "buffer": "uniform",
151
+ "struct": [
152
+ { "name": "count4", "type": "u32", "value": "floor(numel(shapes.x) / tunables.VECTOR_WIDTH)" },
153
+ { "name": "numel", "type": "u32", "value": "numel(shapes.x)" }
154
+ ]
155
+ },
156
+ "params_15": { "name": "params", "buffer": "uniform", "struct": [{ "name": "cols", "type": "u32", "value": "1" }] },
157
+ "params_16": {
158
+ "name": "params",
159
+ "buffer": "uniform",
160
+ "struct": [
161
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
162
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
163
+ ]
164
+ },
165
+ "params_17": {
166
+ "name": "params",
167
+ "buffer": "uniform",
168
+ "struct": [
169
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
170
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
171
+ ]
172
+ },
173
+ "params_18": {
174
+ "name": "params",
175
+ "buffer": "uniform",
176
+ "struct": [
177
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
178
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
179
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
180
+ ]
181
+ },
182
+ "params_19": {
183
+ "name": "params",
184
+ "buffer": "uniform",
185
+ "struct": [
186
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
187
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
188
+ ]
189
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  },
191
  "variants": [
192
  {
193
  "id": "contiguous_suffix_subgroup_vec4",
194
  "priority": 30,
195
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.x) / numel(shapes.y)) % tunables.VECTOR_WIDTH == 0"],
196
  "requires": { "features": ["subgroups"] },
197
+ "derive": {
 
198
  "scalar": "dtypes.T",
199
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
200
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.x) / numel(shapes.y), tunables.VECTOR_WIDTH))))"
201
  },
202
  "passes": [
203
  {
204
  "id": "main",
205
  "name": "ReduceProd.ContiguousSuffixSubgroupVec4",
206
+ "shader": "reduce-row-subgroup.wgsl.jinja",
207
+ "derive": {
208
+ "op": "\"prod\"",
209
+ "vec4": true,
210
+ "castF32": "dtypes.T == \"f16\"",
211
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
212
  },
213
+ "bindings": ["x", "y", "params"],
214
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 },
215
+ "subgroupCollectivesWidth": "portable"
216
  }
217
  ]
218
  },
219
  {
220
  "id": "contiguous_suffix_tree_vec4",
221
  "priority": 22,
222
+ "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.x) / numel(shapes.y)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
223
+ "derive": {
224
  "scalar": "dtypes.T",
225
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
226
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.x) / numel(shapes.y), tunables.VECTOR_WIDTH)))"
227
  },
228
  "passes": [
229
  {
230
  "id": "main",
231
  "name": "ReduceProd.ContiguousSuffixTreeVec4",
232
+ "shader": "reduce-row-tree.wgsl.jinja",
233
+ "derive": {
234
+ "op": "\"prod\"",
235
+ "vec4": true,
236
+ "castF32": "dtypes.T == \"f16\"",
237
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
238
  },
239
+ "bindings": ["x", "y", "params"],
240
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
241
  }
242
  ]
243
  },
 
245
  "id": "contiguous_suffix_tree",
246
  "priority": 21,
247
  "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
248
+ "derive": {
249
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.x) / numel(shapes.y)))",
250
  "scalar": "dtypes.T"
251
  },
252
  "passes": [
253
  {
254
  "id": "main",
255
  "name": "ReduceProd.ContiguousSuffixTree",
256
+ "shader": "reduce-row-tree.wgsl.jinja",
257
+ "derive": { "op": "\"prod\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
258
+ "bindings": ["x_2", "y", "params_2"],
259
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
 
 
260
  }
261
  ]
262
  },
263
  {
264
  "id": "noop_empty_axes",
265
  "priority": 40,
266
+ "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.x, shapes.y)"],
267
+ "derive": { "reduceWorkgroupSize": "reduceWorkgroupSize" },
268
  "passes": [
269
  {
270
  "id": "main",
271
  "name": "ReduceProd.NoopEmptyAxes",
272
+ "shader": "reduce-noop-empty-axes.wgsl.jinja",
273
+ "bindings": ["x_2", "y", "params_3"],
274
+ "dispatch": {
275
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
276
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
277
+ "z": 1
278
+ }
279
  }
280
  ]
281
  },
282
  {
283
  "id": "multi_axis_rank3",
284
  "priority": 8,
285
+ "when": ["not flatParallelCovered", "not contiguousSuffixParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 3", "(attrs.keepdims == 1 and ranks.y == 3 and (dim(shapes.y, 0) == 1 if hasAxis(attrs.axes, 0, 3) else dim(shapes.y, 0) == dim(shapes.x, 0)) and (dim(shapes.y, 1) == 1 if hasAxis(attrs.axes, 1, 3) else dim(shapes.y, 1) == dim(shapes.x, 1)) and (dim(shapes.y, 2) == 1 if hasAxis(attrs.axes, 2, 3) else dim(shapes.y, 2) == dim(shapes.x, 2))) or (attrs.keepdims == 0 and ranks.y == 1)"],
286
+ "derive": { "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
287
  "passes": [
288
  {
289
  "id": "main",
290
  "name": "ReduceProd.MultiAxisRank3",
291
+ "shader": "reduce-serial-axis.wgsl.jinja",
292
+ "derive": {
293
+ "op": "\"prod\"",
294
+ "indexing": "\"multiaxis\"",
295
+ "rank": 3,
296
+ "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
297
+ "dataShape": "shapes.x",
298
+ "outputShape": "shapes.y",
299
+ "outputRank": "ranks.y",
300
+ "keepDims": "attrs.keepdims != 0",
301
+ "intMode": "dtypes.T == \"i32\"",
302
+ "castF32": "dtypes.T == \"f16\"",
303
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
304
  },
305
+ "bindings": ["x_2", "y", "params_4"],
306
+ "dispatch": {
307
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
308
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
309
+ "z": 1
310
+ }
311
  }
312
+ ]
 
313
  },
314
  {
315
  "id": "multi_axis_rank4",
316
  "priority": 8,
317
+ "when": ["not flatParallelCovered", "not contiguousSuffixParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 4", "attrs.noop_with_empty_axes == 0", "numel(shapes.y) == (1 if hasAxis(attrs.axes, 0, 4) else dim(shapes.x, 0)) * (1 if hasAxis(attrs.axes, 1, 4) else dim(shapes.x, 1)) * (1 if hasAxis(attrs.axes, 2, 4) else dim(shapes.x, 2)) * (1 if hasAxis(attrs.axes, 3, 4) else dim(shapes.x, 3))", "((attrs.keepdims == 1 and ranks.y == 4) or (attrs.keepdims == 0 and ranks.y < 4))"],
318
+ "derive": { "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
319
  "passes": [
320
  {
321
  "id": "main",
322
  "name": "ReduceProd.MultiAxisRank4",
323
+ "shader": "reduce-serial-axis.wgsl.jinja",
324
+ "derive": {
325
+ "op": "\"prod\"",
326
+ "indexing": "\"multiaxis\"",
327
+ "rank": 4,
328
+ "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
329
+ "dataShape": "shapes.x",
330
+ "outputShape": "shapes.y",
331
+ "outputRank": "ranks.y",
332
+ "keepDims": "attrs.keepdims != 0",
333
+ "intMode": "dtypes.T == \"i32\"",
334
+ "castF32": "dtypes.T == \"f16\"",
335
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
336
  },
337
+ "bindings": ["x_2", "y", "params_4"],
338
+ "dispatch": {
339
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
340
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
341
+ "z": 1
342
+ }
343
  }
344
+ ]
 
345
  },
346
  {
347
  "id": "int32_rank3_axes02_keepdims",
348
  "priority": 35,
349
+ "when": ["dtypes.T == \"i32\"", "ranks.x == 3", "attrs.keepdims == 1", "hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 2, 3)", "hasAxis(attrs.axes, 1, 3) == false", "dim(shapes.x, 0) > 0", "dim(shapes.x, 2) > 0", "ranks.y == 3", "dim(shapes.y, 0) == 1", "dim(shapes.y, 1) == dim(shapes.x, 1)", "dim(shapes.y, 2) == 1"],
350
+ "derive": {
351
+ "axes02WorkgroupSize": "min(tunables.AXES02_WORKGROUP_SIZE, device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)"
352
+ },
353
  "passes": [
354
  {
355
  "id": "main",
356
  "name": "ReduceProd.Int32Rank3Axes02Keepdims",
357
+ "shader": "reduce-i32-axes02.wgsl.jinja",
358
+ "derive": { "op": "\"prod\"", "workgroupSizeSpec": "axes02WorkgroupSize" },
359
+ "bindings": [
360
+ "x_2",
361
+ "y",
362
+ {
363
+ "name": "params",
364
+ "struct": [
365
+ { "name": "dim0", "type": "u32", "value": "dim(shapes.x, 0)" },
366
+ { "name": "dim1", "type": "u32", "value": "dim(shapes.x, 1)" },
367
+ { "name": "dim2", "type": "u32", "value": "dim(shapes.x, 2)" }
368
+ ]
369
+ }
370
+ ],
371
+ "dispatch": {
372
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (axes02WorkgroupSize)), 65535)",
373
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (axes02WorkgroupSize)), 65535)",
374
+ "z": 1
375
  }
376
  }
377
+ ]
 
 
 
378
  },
379
  {
380
  "id": "tree_last_axis",
381
  "priority": 20,
382
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"i32\")", "ranks.x >= 2", "reduceAxis == ranks.x - 1", "numel(shapes.y) == rows(shapes.x, ranks.x - 1)", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "treeWorkgroupOk"],
383
  "demoteWhen": ["rowSerialPreferred"],
384
+ "derive": {
 
385
  "scalar": "dtypes.T",
386
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.x, ranks.x - 1)))"
387
  },
388
  "passes": [
389
  {
390
  "id": "main",
391
  "name": "ReduceProd.TreeLastAxis",
392
+ "shader": "reduce-row-tree.wgsl.jinja",
393
+ "derive": { "op": "\"prod\"" },
394
+ "bindings": [
395
+ "x_2",
396
+ "y",
397
+ {
398
+ "name": "params",
399
+ "struct": [
400
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
401
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
402
+ ]
403
+ }
404
+ ],
405
+ "dispatch": {
406
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
407
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
408
+ "z": 1
409
+ }
410
+ }
411
+ ]
412
+ },
413
+ {
414
+ "id": "subgroup_rows_last_axis_vec4",
415
+ "priority": 26,
416
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\" or dtypes.T == \"i32\")", "f16Ok(dtypes.T)", "ranks.x >= 1", "reduceAxis == ranks.x - 1", "numel(shapes.y) == rows(shapes.x, ranks.x - 1)", "attrs.noop_with_empty_axes == 0", "lastAxisCols % tunables.VECTOR_WIDTH == 0", "lastAxisCols >= tunables.VECTOR_WIDTH", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize >= 16", "ceilDiv(lastAxisCols / tunables.VECTOR_WIDTH, device.adapterInfo.subgroupMinSize) <= 8", "lastAxisRows >= 64", "treeWorkgroupOk", "device.adapterInfo.subgroupMaxSize <= reduceWorkgroupSize", "(not rowSerialPreferred or lastAxisCols >= tunables.SUBGROUP_MIN_COLS)"],
417
+ "requires": { "features": ["subgroups"] },
418
+ "derive": {
419
+ "scalar": "dtypes.T",
420
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
421
+ "workgroupSize": "reduceWorkgroupSize",
422
+ "vecsPerLane": "ceilDiv(lastAxisCols / tunables.VECTOR_WIDTH, device.adapterInfo.subgroupMinSize)"
423
+ },
424
+ "passes": [
425
+ {
426
+ "id": "main",
427
+ "name": "ReduceProd.SubgroupRowsVec4",
428
+ "shader": "reduce-row-subgroup-rows.wgsl.jinja",
429
+ "derive": { "op": "\"prod\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
430
+ "bindings": ["x", "y", "params_7"],
431
+ "dispatch": {
432
+ "x": "min(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
433
+ "y": "ceilDiv(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
434
+ "z": 1
435
+ },
436
+ "subgroupCollectivesWidth": "portable"
437
  }
438
  ]
439
  },
440
  {
441
  "id": "tree_last_axis_vec4",
442
  "priority": 23,
443
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\" or dtypes.T == \"i32\")", "f16Ok(dtypes.T)", "ranks.x >= 1", "reduceAxis == ranks.x - 1", "numel(shapes.y) == rows(shapes.x, ranks.x - 1)", "attrs.noop_with_empty_axes == 0", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "lastAxisCols % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
444
  "demoteWhen": ["rowSerialPreferred"],
445
+ "derive": {
 
446
  "scalar": "dtypes.T",
447
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
448
  "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
 
451
  {
452
  "id": "main",
453
  "name": "ReduceProd.TreeRowVec4",
454
+ "shader": "reduce-row-tree.wgsl.jinja",
455
+ "derive": {
456
+ "op": "\"prod\"",
457
+ "vec4": true,
458
+ "castF32": "dtypes.T == \"f16\"",
459
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
460
  },
461
+ "bindings": ["x", "y", "params_7"],
462
+ "dispatch": { "x": "min(lastAxisRows, 65535)", "y": "ceilDiv(lastAxisRows, 65535)", "z": 1 }
463
  }
464
  ]
465
  },
466
  {
467
  "id": "rank0_scalar",
468
  "priority": 40,
469
+ "when": ["f16Ok(dtypes.T)", "ranks.x == 0", "ranks.y == 0"],
470
+ "derive": { "axis": 0, "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
471
  "passes": [
472
  {
473
  "id": "main",
474
  "name": "ReduceProd.Rank0Scalar",
475
+ "shader": "reduce-serial-axis.wgsl.jinja",
476
+ "derive": {
477
+ "op": "\"prod\"",
478
+ "indexing": "\"axis2d\"",
479
+ "intMode": "dtypes.T == \"i32\"",
480
+ "castF32": "dtypes.T == \"f16\"",
481
+ "usesF16Spec": "dtypes.T == \"f16\"",
482
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
 
483
  },
484
+ "bindings": ["x_2", "y", "params_8"],
485
  "dispatch": { "x": 1 }
486
  }
487
  ]
488
  },
489
  {
490
  "id": "rank1_axis0",
491
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 1", "reduceAxis == 0", "((attrs.keepdims == 0 and ranks.y == 0) or (attrs.keepdims == 1 and ranks.y == 1 and dim(shapes.y, 0) == 1))"],
492
+ "derive": { "axis": 0, "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
493
  "passes": [
494
  {
495
  "id": "main",
496
  "name": "ReduceProd.Rank1Axis0",
497
+ "shader": "reduce-serial-axis.wgsl.jinja",
498
+ "derive": {
499
+ "op": "\"prod\"",
500
+ "indexing": "\"axis2d\"",
501
+ "intMode": "dtypes.T == \"i32\"",
502
+ "castF32": "dtypes.T == \"f16\"",
503
+ "usesF16Spec": "dtypes.T == \"f16\"",
504
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
 
505
  },
506
+ "bindings": ["x_2", "y", "params_9"],
507
+ "dispatch": {
508
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
509
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
510
+ "z": 1
511
+ }
512
  }
513
  ]
514
  },
515
  {
516
  "id": "axis_split",
517
  "priority": 24,
518
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "attrs.noop_with_empty_axes == 0", "ranks.x >= 2", "reduceAxis < ranks.x - 1", "not (ranks.x == 2 and reduceAxis == 0)", "axisSplitDim >= tunables.AXIS0_SPLIT_MIN_ROWS", "axisSplitOutputs >= 1", "axisSplitOutputs <= 4096", "axisSplitOutputs == rows(shapes.x, reduceAxis)", "axisSplitPathFits"],
519
+ "derive": {
520
+ "splitCount": "axisSplitCount",
521
+ "partialElement": "\"f32\"",
522
+ "workgroupSize": "reduceWorkgroupSize",
523
+ "split": "splitCount"
524
+ },
525
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
526
  "passes": [
527
  {
528
  "id": "split_reduce",
529
  "name": "ReduceProd.AxisSplitReduce",
530
+ "shader": "reduce-axis-split-reduce.wgsl.jinja",
531
+ "derive": {
532
+ "op": "\"prod\"",
533
+ "splitSpec": "splitCount",
534
+ "castF32": "dtypes.T == \"f16\"",
535
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
536
  },
537
+ "bindings": ["x_2", "partials", "params_10"],
538
+ "dispatch": {
539
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
540
+ "y": "splitCount",
541
+ "z": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)"
542
+ }
543
  },
544
  {
545
  "id": "combine",
546
  "name": "ReduceProd.AxisSplitCombine",
547
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
548
+ "derive": { "op": "\"prod\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
549
+ "bindings": ["partials_2", "y", "params_11"],
550
+ "dispatch": {
551
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
552
+ "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
553
+ "z": 1
554
+ }
 
 
 
555
  }
556
  ]
557
  },
558
  {
559
  "id": "axis_split_tiled_narrow",
560
  "priority": 25,
561
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "attrs.noop_with_empty_axes == 0", "ranks.x >= 2", "reduceAxis < ranks.x - 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.x, reduceAxis)", "axisSplitPathFits"],
562
+ "derive": {
563
+ "splitCount": "axisSplitCount",
564
  "partialElement": "\"f32\"",
565
  "scalar": "dtypes.T",
566
  "workgroupSize": "reduceWorkgroupSize",
 
572
  {
573
  "id": "split_reduce",
574
  "name": "ReduceProd.AxisSplitTiledReduce",
575
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
576
+ "derive": {
577
+ "op": "\"prod\"",
578
+ "splitSpec": "splitCount",
579
+ "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
580
+ "castF32": "dtypes.T == \"f16\"",
581
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
582
  },
583
+ "bindings": ["x_2", "partials", "params_10"],
584
+ "dispatch": {
585
+ "x": "min(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)",
586
+ "y": "splitCount",
587
+ "z": "ceilDiv(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)"
588
+ }
589
  },
590
  {
591
  "id": "combine",
592
  "name": "ReduceProd.AxisSplitCombine",
593
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
594
+ "derive": { "op": "\"prod\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
595
+ "bindings": ["partials_2", "y", "params_11"],
596
+ "dispatch": {
597
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
598
+ "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
599
+ "z": 1
600
+ }
 
 
 
601
  }
602
  ]
603
  },
604
  {
605
  "id": "axis0_splitk",
606
  "priority": 22,
607
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.x == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_SPLIT_MIN_ROWS", "dim(shapes.x, 1) > 0", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 1)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == dim(shapes.x, 1)))", "axis0SplitPathFits"],
608
+ "derive": {
609
+ "splitCount": "axis0SplitCount",
610
+ "partialElement": "\"f32\"",
611
+ "workgroupSize": "reduceWorkgroupSize",
612
+ "split": "splitCount"
613
+ },
614
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * dim(shapes.x, 1)]" }],
615
  "passes": [
616
  {
617
  "id": "split_reduce",
618
  "name": "ReduceProd.Axis0SplitKReduce",
619
+ "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
620
+ "derive": {
621
+ "op": "\"prod\"",
622
+ "splitSpec": "splitCount",
623
+ "castF32": "dtypes.T == \"f16\"",
624
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
625
  },
626
+ "bindings": ["x_2", "partials", "params_12"],
627
+ "dispatch": {
628
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
629
+ "y": "splitCount",
630
+ "z": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)"
631
+ }
632
  },
633
  {
634
  "id": "combine",
635
  "name": "ReduceProd.Axis0SplitKCombine",
636
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
637
+ "derive": { "op": "\"prod\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
638
+ "bindings": ["partials_2", "y", "params_13"],
639
+ "dispatch": {
640
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
641
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
642
+ "z": 1
643
+ }
 
 
 
644
  }
645
  ]
646
  },
647
  {
648
  "id": "axis0_splitk_i32",
649
  "priority": 23,
650
+ "when": ["dtypes.T == \"i32\"", "ranks.x == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_SPLIT_MIN_ROWS", "dim(shapes.x, 1) > 0", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 1)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == dim(shapes.x, 1)))", "axis0SplitPathFits"],
651
+ "derive": {
652
+ "splitCount": "axis0SplitCount",
653
+ "partialElement": "dtypes.T",
654
+ "workgroupSize": "reduceWorkgroupSize",
655
+ "split": "splitCount"
656
+ },
657
+ "intermediates": [{ "id": "partials", "dtype": "int32", "shape": "[splitCount * dim(shapes.x, 1)]" }],
658
  "passes": [
659
  {
660
  "id": "split_reduce",
661
  "name": "ReduceProd.Axis0SplitKReduceI32",
662
+ "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
663
+ "derive": {
664
+ "op": "\"prod\"",
665
+ "intMode": true,
666
+ "workgroupSizeSpec": "reduceWorkgroupSize",
667
+ "splitSpec": "splitCount"
 
 
668
  },
669
+ "bindings": ["x_2", "partials", "params_12"],
670
+ "dispatch": {
671
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
672
+ "y": "splitCount",
673
+ "z": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)"
674
+ }
675
  },
676
  {
677
  "id": "combine",
678
  "name": "ReduceProd.Axis0SplitKCombineI32",
679
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
680
+ "derive": {
681
+ "op": "\"prod\"",
682
+ "intMode": true,
683
+ "workgroupSizeSpec": "reduceWorkgroupSize",
684
+ "splitSpec": "splitCount"
 
 
685
  },
686
+ "bindings": ["partials_2", "y", "params_13"],
687
+ "dispatch": {
688
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
689
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
690
+ "z": 1
691
+ }
692
  }
693
  ]
694
  },
695
  {
696
  "id": "axis0_tilecols",
697
  "priority": 20,
698
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_TILE_MIN_ROWS", "axis0Cols >= tunables.AXIS0_TILE_MIN_COLS", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 1)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == dim(shapes.x, 1)))", "axis0TilePathFits"],
699
+ "derive": { "scalar": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "tileCols": "tunables.AXIS0_TILE_COLS" },
 
 
 
 
700
  "passes": [
701
  {
702
  "id": "main",
703
  "name": "ReduceProd.Axis0TileCols",
704
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
705
+ "derive": {
706
+ "op": "\"prod\"",
707
+ "intMode": "dtypes.T == \"i32\"",
708
+ "castF32": "dtypes.T == \"f16\"",
709
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
710
  },
711
+ "bindings": ["x_2", "y", "params_12"],
712
+ "dispatch": {
713
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
714
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
715
+ "z": 1
716
+ }
717
  }
718
  ]
719
  },
720
  {
721
  "id": "all_axes_flat",
722
  "priority": 31,
723
+ "when": ["flatParallelCovered"],
724
+ "derive": {
725
  "partialElement": "\"f32\"",
726
  "scalar": "dtypes.T",
727
  "workgroupSize": "reduceWorkgroupSize",
728
  "split": "flatSplitCount"
729
  },
 
730
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
731
  "passes": [
732
  {
733
  "id": "flat_partial",
734
  "name": "ReduceProd.AllAxesFlatPartial",
735
+ "shader": "reduce-flat-partial.wgsl.jinja",
736
+ "derive": { "op": "\"prod\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
737
+ "bindings": ["x_2", "partials", "params_14"],
 
 
738
  "dispatch": { "x": "flatSplitCount" }
739
  },
740
  {
741
  "id": "combine",
742
  "name": "ReduceProd.AllAxesFlatCombine",
743
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
744
+ "derive": { "op": "\"prod\"", "outputF16": "dtypes.T == \"f16\"" },
745
+ "bindings": ["partials_2", "y", "params_15"],
 
 
746
  "dispatch": { "x": 1 }
747
  }
748
  ]
 
750
  {
751
  "id": "all_axes_flat_i32",
752
  "priority": 32,
753
+ "when": ["dtypes.T == \"i32\"", "numel(shapes.y) == 1", "numel(shapes.x) >= tunables.FULL_REDUCE_MIN_ELEMENTS", "flatPathFits"],
754
+ "derive": { "partialElement": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
755
  "intermediates": [{ "id": "partials", "dtype": "int32", "shape": "[flatSplitCount]" }],
756
  "passes": [
757
  {
758
  "id": "flat_partial",
759
  "name": "ReduceProd.AllAxesFlatPartialI32",
760
+ "shader": "reduce-flat-partial.wgsl.jinja",
761
+ "derive": { "op": "\"prod\"", "intMode": true },
762
+ "bindings": ["x_2", "partials", "params_14"],
763
  "dispatch": { "x": "flatSplitCount" }
764
  },
765
  {
766
  "id": "combine",
767
  "name": "ReduceProd.AllAxesFlatCombineI32",
768
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
769
+ "derive": { "op": "\"prod\"", "intMode": true, "splitSpec": "flatSplitCount" },
770
+ "bindings": ["partials_2", "y", "params_15"],
 
 
771
  "dispatch": { "x": 1 }
772
  }
773
  ]
 
775
  {
776
  "id": "rankn_single_axis_generic",
777
  "priority": 12,
778
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.noop_with_empty_axes == 0", "reduceAxis < ranks.x", "numel(shapes.y) == rows(shapes.x, reduceAxis)", "((attrs.keepdims == 0 and ranks.y == ranks.x - 1) or (attrs.keepdims == 1 and ranks.y == ranks.x and dim(shapes.y, reduceAxis) == 1))"],
779
  "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4", "tree_last_axis"],
780
+ "derive": { "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
781
  "passes": [
782
  {
783
  "id": "main",
784
  "name": "ReduceProd.RankNSingleAxisGeneric",
785
+ "shader": "reduce-serial-axis.wgsl.jinja",
786
+ "derive": {
787
+ "op": "\"prod\"",
788
+ "indexing": "\"rankn\"",
789
+ "rank": "ranks.x",
790
+ "axisSpec": "reduceAxis",
791
+ "dataShape": "shapes.x",
792
+ "outputShape": "shapes.y",
793
+ "outputRank": "ranks.y",
794
+ "keepDims": "attrs.keepdims != 0",
795
+ "intMode": "dtypes.T == \"i32\"",
796
+ "castF32": "dtypes.T == \"f16\"",
797
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
798
  },
799
+ "bindings": ["x_2", "y", "params_16"],
800
+ "dispatch": {
801
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
802
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
803
+ "z": 1
804
+ }
805
  }
806
+ ]
 
807
  },
808
  {
809
  "id": "subgroup_last_axis_vec4",
810
  "priority": 25,
811
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.x >= 1", "reduceAxis == ranks.x - 1", "numel(shapes.y) == rows(shapes.x, ranks.x - 1)", "dim(shapes.x, ranks.x - 1) >= 4", "dim(shapes.x, ranks.x - 1) % tunables.VECTOR_WIDTH == 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)"],
812
  "demoteWhen": ["rowSerialPreferred"],
813
+ "requires": { "features": ["subgroups"] },
814
+ "derive": {
815
  "scalar": "dtypes.T",
816
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
817
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
 
820
  {
821
  "id": "main",
822
  "name": "ReduceProd.SubgroupRowVec4",
823
+ "shader": "reduce-row-subgroup.wgsl.jinja",
824
+ "derive": {
825
+ "op": "\"prod\"",
826
+ "vec4": true,
827
+ "castF32": "dtypes.T == \"f16\"",
828
+ "usesF16Spec": "dtypes.T == \"f16\""
829
+ },
830
+ "bindings": ["x", "y", "params_7"],
831
+ "dispatch": {
832
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
833
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
834
+ "z": 1
835
  },
836
+ "subgroupCollectivesWidth": "portable"
 
 
837
  }
838
  ]
839
  },
840
  {
841
  "id": "subgroup_last_axis",
842
  "priority": 24,
843
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.x >= 1", "reduceAxis == ranks.x - 1", "numel(shapes.y) == rows(shapes.x, ranks.x - 1)", "dim(shapes.x, ranks.x - 1) > 0", "dim(shapes.x, ranks.x - 1) % tunables.VECTOR_WIDTH != 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)"],
844
  "demoteWhen": ["rowSerialPreferred"],
845
+ "requires": { "features": ["subgroups"] },
846
+ "derive": {
847
  "scalar": "dtypes.T",
848
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
849
  },
 
851
  {
852
  "id": "main",
853
  "name": "ReduceProd.SubgroupRow",
854
+ "shader": "reduce-row-subgroup.wgsl.jinja",
855
+ "derive": {
856
+ "op": "\"prod\"",
857
+ "vec4": false,
858
+ "castF32": "dtypes.T == \"f16\"",
859
+ "usesF16Spec": "dtypes.T == \"f16\""
860
+ },
861
+ "bindings": ["x_2", "y", "params_17"],
862
+ "dispatch": {
863
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
864
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
865
+ "z": 1
866
  },
867
+ "subgroupCollectivesWidth": "portable"
 
 
868
  }
869
  ]
870
  },
871
  {
872
  "id": "axis0",
873
  "priority": 0,
874
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 2", "reduceAxis == 0", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 1)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == dim(shapes.x, 1)))"],
875
  "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
876
+ "derive": { "axis": 0, "scalar": "dtypes.T" },
 
877
  "passes": [
878
  {
879
  "id": "main",
880
  "name": "axis0",
881
+ "shader": "reduce-serial-axis.wgsl.jinja",
882
+ "derive": {
883
+ "axis": 0,
884
+ "op": "\"prod\"",
885
+ "indexing": "\"axis2d\"",
886
+ "intMode": "dtypes.T == \"i32\"",
887
+ "castF32": "dtypes.T == \"f16\"",
888
+ "usesF16Spec": "dtypes.T == \"f16\""
 
889
  },
890
+ "bindings": ["x_2", "y", "params_18"],
891
+ "dispatch": {
892
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
893
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
894
+ "z": 1
895
+ }
896
  }
897
  ]
898
  },
899
  {
900
  "id": "axis1",
901
  "priority": 0,
902
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 2", "reduceAxis == 1", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 0)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == dim(shapes.x, 0) and dim(shapes.y, 1) == 1))"],
903
+ "derive": { "axis": 1, "scalar": "dtypes.T" },
904
  "passes": [
905
  {
906
  "id": "main",
907
  "name": "axis1",
908
+ "shader": "reduce-serial-axis.wgsl.jinja",
909
+ "derive": {
910
+ "axis": 1,
911
+ "op": "\"prod\"",
912
+ "indexing": "\"axis2d\"",
913
+ "intMode": "dtypes.T == \"i32\"",
914
+ "castF32": "dtypes.T == \"f16\"",
915
+ "usesF16Spec": "dtypes.T == \"f16\""
 
916
  },
917
+ "bindings": ["x_2", "y", "params_19"],
918
+ "dispatch": {
919
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
920
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
921
+ "z": 1
922
+ }
923
  }
924
  ]
925
  },
926
  {
927
  "id": "rank2_all_axes_keepdims",
928
  "priority": 30,
929
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 2 and attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == 1"],
930
+ "derive": { "axis": 0, "scalar": "dtypes.T" },
931
  "passes": [
932
  {
933
  "id": "main",
934
  "name": "ReduceProd.Rank2AllAxesKeepdims",
935
+ "shader": "reduce-serial-axis.wgsl.jinja",
936
+ "derive": {
937
+ "op": "\"prod\"",
938
+ "indexing": "\"axis2d\"",
939
+ "intMode": "dtypes.T == \"i32\"",
940
+ "castF32": "dtypes.T == \"f16\"",
941
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
942
  },
943
+ "bindings": [
944
+ "x_2",
945
+ "y",
946
+ {
947
+ "name": "params",
948
+ "struct": [
949
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
950
+ { "name": "cols", "type": "u32", "value": "1" },
951
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
952
+ ]
953
+ }
954
+ ],
955
+ "dispatch": {
956
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
957
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
958
+ "z": 1
959
+ }
960
  }
961
  ]
962
  },
963
  {
964
  "id": "all_axes_keepdims",
965
  "priority": 30,
966
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x >= 3 and attrs.keepdims == 1 and ranks.y == ranks.x and numel(shapes.y) == 1"],
967
+ "derive": { "axis": 0, "scalar": "dtypes.T" },
968
  "passes": [
969
  {
970
  "id": "main",
971
  "name": "ReduceProd.Rank3AllAxesKeepdims",
972
+ "shader": "reduce-serial-axis.wgsl.jinja",
973
+ "derive": {
974
+ "op": "\"prod\"",
975
+ "indexing": "\"axis2d\"",
976
+ "intMode": "dtypes.T == \"i32\"",
977
+ "castF32": "dtypes.T == \"f16\"",
978
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
979
  },
980
+ "bindings": [
981
+ "x_2",
982
+ "y",
983
+ {
984
+ "name": "params",
985
+ "struct": [
986
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
987
+ { "name": "cols", "type": "u32", "value": "1" },
988
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
989
+ ]
990
+ }
991
+ ],
992
+ "dispatch": {
993
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
994
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
995
+ "z": 1
996
+ }
997
  }
998
  ]
999
  },
1000
  {
1001
  "id": "all_axes_no_keepdims",
1002
  "priority": 30,
1003
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x >= 3 and attrs.keepdims == 0 and ranks.y == 0"],
1004
+ "derive": { "axis": 0, "scalar": "dtypes.T" },
1005
  "passes": [
1006
  {
1007
  "id": "main",
1008
  "name": "ReduceProd.Rank3AllAxesNoKeepdims",
1009
+ "shader": "reduce-serial-axis.wgsl.jinja",
1010
+ "derive": {
1011
+ "op": "\"prod\"",
1012
+ "indexing": "\"axis2d\"",
1013
+ "intMode": "dtypes.T == \"i32\"",
1014
+ "castF32": "dtypes.T == \"f16\"",
1015
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
1016
  },
1017
+ "bindings": [
1018
+ "x_2",
1019
+ "y",
1020
+ {
1021
+ "name": "params",
1022
+ "struct": [
1023
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
1024
+ { "name": "cols", "type": "u32", "value": "1" },
1025
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
1026
+ ]
1027
+ }
1028
+ ],
1029
+ "dispatch": {
1030
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
1031
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
1032
+ "z": 1
1033
+ }
1034
  }
1035
  ]
1036
  }
build/webgpu/metadata.json CHANGED
@@ -1,27 +1,59 @@
1
  {
2
  "name": "ai.onnx.ReduceProd",
3
- "id": "_ai_onnx_reduceprod_webgpu_af6578f",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "VznixYylucv/AFi4jPmrJwhC0TMoDKr+7rMdIVHsW+s=",
11
- "manifest.json": "RVeo8qczKuuOGfvWE9Qf77fp9IgC4/Vx/tCYmwo86+E=",
12
- "reduce-axis-split-reduce.wgsl.jinja": "sKtJglLU0XOIB5OZ3ayiQ3hyBozHELXbS2bRxPdDBtQ=",
13
- "reduce-axis0-splitk-combine.wgsl.jinja": "CP4QmClXgzXKKmy/ynbQdaWsMEkI8ETCiaHU0P1mW1s=",
14
- "reduce-axis0-splitk-reduce.wgsl.jinja": "qArZcLmfHk+cK8BAtnBAhfUlN85EljIXgqfGotutV0g=",
15
- "reduce-axis0-tilecols.wgsl.jinja": "If5gLW0KSm7P9fRhWiwiQUbPt9jAj+lDnVLQu98vTe8=",
16
- "reduce-flat-partial.wgsl.jinja": "iFF+WqB7whD91yu/pT/ESQegWBUlHq5r5b6DSHGEVHY=",
17
- "reduce-i32-axes02.wgsl.jinja": "lyV7HWjK6vjRO2ekdha9khkcBkeSUa7xPPOA4YyByHI=",
18
- "reduce-noop-empty-axes.wgsl.jinja": "IO2MEzyBGJr3Z8AVP7ELJXqfhwcG/vtBfa0wCdkbuwo=",
19
- "reduce-row-subgroup.wgsl.jinja": "tCCSXf8DaUkcXj2vUD4bwfhvaYxXMN3bEE8q8L90ZrE=",
20
- "reduce-row-tree.wgsl.jinja": "cLIGeHfVYpn8YH3HHQg84DuMNe/3brWgwLUi2zCFmOw=",
21
- "reduce-serial-axis.wgsl.jinja": "1GJeXytXvJYFn3JhcEk8sbpze8q8+nFVHQrf0lbSbXI=",
22
- "test.json": "w6OEmrn5Bdf1M23l36SbchwXIui/PnEzFjHVeHJUH1I="
 
23
  }
24
  },
25
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
26
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.ReduceProd" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
 
1
  {
2
  "name": "ai.onnx.ReduceProd",
3
+ "id": "_ai_onnx_reduceprod_webgpu_eba8bbf",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "agHVWGoEFusniUoQri0U27LrIk898f6FnHV0DXI6Ld8=",
11
+ "manifest.json": "MDsk7/aIS4exv9qL/BRnNp40y3k4RQB0UQzWnzSpwQw=",
12
+ "reduce-axis-split-reduce.wgsl.jinja": "4+ep9xH4pHZOfaZ8abJhA8SW5M4mUhDC+y1CtDz6vjY=",
13
+ "reduce-axis0-splitk-combine.wgsl.jinja": "Yz1hjK55R/kndUrw3ugPqgPaOBwKmYupqZoVdJTHO5Q=",
14
+ "reduce-axis0-splitk-reduce.wgsl.jinja": "jB2h58emn6rfKhd8ALzqylSDsmBbrrkEOhtIoNhcQM0=",
15
+ "reduce-axis0-tilecols.wgsl.jinja": "PjYkEUQJBeG70td3W2xxmexH9x6XNIfeSzXBY47HbaU=",
16
+ "reduce-flat-partial.wgsl.jinja": "+qToL+wFi9QOxvY887aBAEwZK6Xu/eLQkucKvJYNwSk=",
17
+ "reduce-i32-axes02.wgsl.jinja": "t6kVFEoZLe2G9Cj58xMyYVyRe267yfpbVA0vA78id7I=",
18
+ "reduce-noop-empty-axes.wgsl.jinja": "5bKufrcfYt3BagZ6WxbUvqwKKKVMU7d+SUshgUFWTDI=",
19
+ "reduce-row-subgroup-rows.wgsl.jinja": "76u7rAvFoZZKrFDs2A2jk0vkB0uNrPL6twdOBE9b+v8=",
20
+ "reduce-row-subgroup.wgsl.jinja": "2mu9LEsk8HfaLvucBCfcB1/ENpXkD6ELiCtRt+5UqiU=",
21
+ "reduce-row-tree.wgsl.jinja": "Bwa5xcI0bTmKXb4r9Cc1bfVbM5rNqqpQVrWWVqcb8xA=",
22
+ "reduce-serial-axis.wgsl.jinja": "fvUV9htqzKzt4Pg05pYtRmup/5QIHYaUGQHJZnthXKo=",
23
+ "test.json": "AAcj4UFyWhoSVnZFFqVG7DSWAadBNDMcvZQc3t3XZDk="
24
  }
25
  },
26
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
27
+ "webgpu": {
28
+ "manifestSpec": "2.0",
29
+ "variants": {
30
+ "contiguous_suffix_subgroup_vec4": ["reduce-row-subgroup.wgsl.jinja"],
31
+ "contiguous_suffix_tree_vec4": ["reduce-row-tree.wgsl.jinja"],
32
+ "contiguous_suffix_tree": ["reduce-row-tree.wgsl.jinja"],
33
+ "noop_empty_axes": ["reduce-noop-empty-axes.wgsl.jinja"],
34
+ "multi_axis_rank3": ["reduce-serial-axis.wgsl.jinja"],
35
+ "multi_axis_rank4": ["reduce-serial-axis.wgsl.jinja"],
36
+ "int32_rank3_axes02_keepdims": ["reduce-i32-axes02.wgsl.jinja"],
37
+ "tree_last_axis": ["reduce-row-tree.wgsl.jinja"],
38
+ "subgroup_rows_last_axis_vec4": ["reduce-row-subgroup-rows.wgsl.jinja"],
39
+ "tree_last_axis_vec4": ["reduce-row-tree.wgsl.jinja"],
40
+ "rank0_scalar": ["reduce-serial-axis.wgsl.jinja"],
41
+ "rank1_axis0": ["reduce-serial-axis.wgsl.jinja"],
42
+ "axis_split": ["reduce-axis-split-reduce.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
43
+ "axis_split_tiled_narrow": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-axis0-tilecols.wgsl.jinja"],
44
+ "axis0_splitk": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-axis0-splitk-reduce.wgsl.jinja"],
45
+ "axis0_splitk_i32": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-axis0-splitk-reduce.wgsl.jinja"],
46
+ "axis0_tilecols": ["reduce-axis0-tilecols.wgsl.jinja"],
47
+ "all_axes_flat": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-flat-partial.wgsl.jinja"],
48
+ "all_axes_flat_i32": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-flat-partial.wgsl.jinja"],
49
+ "rankn_single_axis_generic": ["reduce-serial-axis.wgsl.jinja"],
50
+ "subgroup_last_axis_vec4": ["reduce-row-subgroup.wgsl.jinja"],
51
+ "subgroup_last_axis": ["reduce-row-subgroup.wgsl.jinja"],
52
+ "axis0": ["reduce-serial-axis.wgsl.jinja"],
53
+ "axis1": ["reduce-serial-axis.wgsl.jinja"],
54
+ "rank2_all_axes_keepdims": ["reduce-serial-axis.wgsl.jinja"],
55
+ "all_axes_keepdims": ["reduce-serial-axis.wgsl.jinja"],
56
+ "all_axes_no_keepdims": ["reduce-serial-axis.wgsl.jinja"]
57
+ }
58
+ }
59
  }
build/webgpu/reduce-axis-split-reduce.wgsl.jinja CHANGED
@@ -4,30 +4,53 @@
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
 
@@ -41,9 +64,52 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
41
  var a1 = a0 + chunk;
42
  if (a1 > params.axisDim) { a1 = params.axisDim; }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  var acc = 1.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
  }
 
4
  // inner-axis elements. The combine pass folds the segments and finalizes the
5
  // selected reduction.
6
  //
7
+ {% if op == "logsumexp" %}
8
+ // Each output segment writes three partial planes: its maximum, the sum of
9
+ // exp(x - maximum), and a packed NaN marker.
10
+ {% endif %}
11
+ {% set castF32 = castF32 is defined and castF32 %}
12
  {% set xa = "f32(" if castF32 else "" %}
13
  {% set ax = ")" if castF32 else "" %}
14
+ {% if usesF16Spec is defined and usesF16Spec %}
15
  enable f16;
16
  {% endif %}
17
  {{ env.wgsl.resourceDeclarations }}
18
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
19
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
20
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
21
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
22
+ fn {{ name }}() -> {{ scalar }} {
23
+ {% if scalar == "i32" %}
24
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
25
+ {% elif scalar == "u32" %}
26
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
27
+ {% else %}
28
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
29
+ return bitcast<f32>(bits);
30
+ {% endif %}
31
+ }
32
+ {%- endmacro %}
33
 
34
 
35
  const WG: u32 = {{ workgroupSize }}u;
36
  const SPLIT: u32 = {{ split }}u;
37
+ {% if op == "logsumexp" %}
38
+ const F32_MIN: f32 = -3.4028234663852886e38;
39
+
40
+ fn is_nan_f32(value: f32) -> bool {
41
+ let bits = bitcast<u32>(value);
42
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
43
+ }
44
+ {% elif op == "max" or op == "min" %}
45
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
46
+ {% endif %}
47
 
48
  @compute @workgroup_size(WG, 1, 1)
49
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
+ @builtin(workgroup_id) wg: vec3<u32>) {
 
51
  // 2D-folded output index: wg.z carries the high bits past the
52
  // per-dimension dispatch limit on the x dimension.
53
+ let output_index = (wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u) * WG + (gid.x % WG);
54
  let seg = wg.y;
55
  if (output_index >= params.outputs) { return; }
56
 
 
64
  var a1 = a0 + chunk;
65
  if (a1 > params.axisDim) { a1 = params.axisDim; }
66
 
67
+ {% if op == "logsumexp" %}
68
+ var local_max = F32_MIN;
69
+ var local_nan_count = 0.0;
70
+ var local_nan_value = 0.0;
71
+ for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
72
+ let value = {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
73
+ if (is_nan_f32(value)) {
74
+ local_nan_count = local_nan_count + 1.0;
75
+ local_nan_value = value;
76
+ } else {
77
+ local_max = max(local_max, value);
78
+ }
79
+ }
80
+ var acc = 0.0;
81
+ if (local_nan_count == 0.0) {
82
+ for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
83
+ acc = acc + exp({{ xa }}x[input_base + axis_index * params.inner]{{ ax }} - local_max);
84
+ }
85
+ }
86
+ partials[seg * params.outputs + output_index] = local_max;
87
+ partials[(SPLIT + seg) * params.outputs + output_index] = acc;
88
+ partials[(2u * SPLIT + seg) * params.outputs + output_index] = select(0.0, local_nan_value, local_nan_count > 0.0);
89
+ {% else %}
90
+ {% if op == "max" %}
91
+ var acc = reduction_identity();
92
+ {% elif op == "min" %}
93
+ var acc = reduction_identity();
94
+ {% elif op == "prod" %}
95
  var acc = 1.0;
96
+ {% else %}
97
+ var acc = 0.0;
98
+ {% endif %}
99
  for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
100
+ {% if op == "max" or op == "min" %}
101
+ acc = {{ op }}(acc, {{ xa }}x[input_base + axis_index * params.inner]{{ ax }});
102
+ {% elif op == "prod" %}
103
  acc = acc * {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
104
+ {% elif op == "l1" %}
105
+ acc = acc + abs({{ xa }}x[input_base + axis_index * params.inner]{{ ax }});
106
+ {% elif op == "l2" or op == "sumsquare" %}
107
+ let value = {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
108
+ acc = acc + value * value;
109
+ {% else %}
110
+ acc = acc + {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
111
+ {% endif %}
112
  }
113
  partials[seg * params.outputs + output_index] = acc;
114
+ {% endif %}
115
  }
build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja CHANGED
@@ -2,37 +2,121 @@
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
5
- {% if source.outputF16 %}
 
 
 
 
 
6
  enable f16;
7
  {% endif %}
8
  {{ env.wgsl.resourceDeclarations }}
9
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
10
- * evaluation, so f32 identities are constructed at runtime from their
11
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
 
14
  const WG: u32 = {{ workgroupSize }}u;
15
  const SPLIT: u32 = {{ split }}u;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  @compute @workgroup_size(WG, 1, 1)
18
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
19
  @builtin(num_workgroups) nwg: vec3<u32>) {
20
  let stride = nwg.x * WG;
21
- let start = (gid.y * nwg.x * WG) + gid.x;
22
  for (var col = start; col < params.cols; col = col + stride) {
23
- {% if source.intMode %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  var total = 1i;
25
  {% else %}
 
 
 
 
 
 
 
 
26
  var total = 1.0;
 
 
 
27
  {% endif %}
28
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
29
  let p = partials[seg * params.cols + col];
 
 
 
30
  total = total * p;
 
 
 
31
  }
32
- {% if source.outputF16 %}
 
 
 
 
 
 
 
 
 
 
33
  y[col] = f16(total);
34
  {% else %}
35
  y[col] = total;
 
 
36
  {% endif %}
37
  }
38
  }
 
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
5
+ {% set addBias = addBias is defined and addBias %}
6
+ {% set biasCols = biasCols | default(0) %}
7
+ {% set intMode = intMode is defined and intMode %}
8
+ {% set yv = "f16(" if outputF16 else "" %}
9
+ {% set vy = ")" if outputF16 else "" %}
10
+ {% if outputF16 %}
11
  enable f16;
12
  {% endif %}
13
  {{ env.wgsl.resourceDeclarations }}
14
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
15
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
16
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
17
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
18
+ fn {{ name }}() -> {{ scalar }} {
19
+ {% if scalar == "i32" %}
20
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
21
+ {% elif scalar == "u32" %}
22
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
23
+ {% else %}
24
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
25
+ return bitcast<f32>(bits);
26
+ {% endif %}
27
+ }
28
+ {%- endmacro %}
29
 
30
 
31
  const WG: u32 = {{ workgroupSize }}u;
32
  const SPLIT: u32 = {{ split }}u;
33
+ {% if addBias %}
34
+ const BIAS_COLS: u32 = {{ biasCols }}u;
35
+ {% endif %}
36
+ {% if op == "logsumexp" %}
37
+ const F32_MIN: f32 = -3.4028234663852886e38;
38
+ const F32_MAX: f32 = 3.4028234663852886e38;
39
+
40
+ fn is_nan_f32(value: f32) -> bool {
41
+ let bits = bitcast<u32>(value);
42
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
43
+ }
44
+ {% elif op == "max" or op == "min" %}
45
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
46
+ {% endif %}
47
 
48
  @compute @workgroup_size(WG, 1, 1)
49
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
  @builtin(num_workgroups) nwg: vec3<u32>) {
51
  let stride = nwg.x * WG;
52
+ let start = (gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG) + gid.x;
53
  for (var col = start; col < params.cols; col = col + stride) {
54
+ {% if op == "logsumexp" %}
55
+ // Merge SPLIT (segMax, segSumExp) pairs stably; carry NaN / +Inf markers.
56
+ var nan_value = 0.0;
57
+ var has_nan = false;
58
+ var global_max = F32_MIN;
59
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
60
+ let nv = partials[(2u * SPLIT + seg) * params.cols + col];
61
+ if (nv != 0.0 || is_nan_f32(nv)) {
62
+ has_nan = true;
63
+ nan_value = nv;
64
+ }
65
+ global_max = max(global_max, partials[seg * params.cols + col]);
66
+ }
67
+ var sum = 0.0;
68
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
69
+ let seg_max = partials[seg * params.cols + col];
70
+ let seg_sum = partials[(SPLIT + seg) * params.cols + col];
71
+ sum = sum + seg_sum * exp(seg_max - global_max);
72
+ }
73
+ let has_positive_inf = global_max > F32_MAX;
74
+ let finite_or_inf = select(global_max + log(sum), global_max, has_positive_inf);
75
+ y[col] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
76
+ {% else %}
77
+ {% if intMode %}
78
+ {% if op == "prod" %}
79
  var total = 1i;
80
  {% else %}
81
+ var total = 0i;
82
+ {% endif %}
83
+ {% else %}
84
+ {% if op == "max" %}
85
+ var total = reduction_identity();
86
+ {% elif op == "min" %}
87
+ var total = reduction_identity();
88
+ {% elif op == "prod" %}
89
  var total = 1.0;
90
+ {% else %}
91
+ var total = 0.0;
92
+ {% endif %}
93
  {% endif %}
94
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
95
  let p = partials[seg * params.cols + col];
96
+ {% if op == "max" or op == "min" %}
97
+ total = {{ op }}(total, p);
98
+ {% elif op == "prod" %}
99
  total = total * p;
100
+ {% else %}
101
+ total = total + p;
102
+ {% endif %}
103
  }
104
+ {% if addBias %}
105
+ total = total + f32(bias[col % BIAS_COLS]);
106
+ {% endif %}
107
+ {% if op == "l2" %}
108
+ y[col] = {{ yv }}sqrt(total){{ vy }};
109
+ {% elif op == "logsum" %}
110
+ y[col] = {{ yv }}log(total){{ vy }};
111
+ {% elif op == "mean" %}
112
+ y[col] = {{ yv }}total / f32(params.rows){{ vy }};
113
+ {% else %}
114
+ {% if outputF16 %}
115
  y[col] = f16(total);
116
  {% else %}
117
  y[col] = total;
118
+ {% endif %}
119
+ {% endif %}
120
  {% endif %}
121
  }
122
  }
build/webgpu/reduce-axis0-splitk-reduce.wgsl.jinja CHANGED
@@ -2,31 +2,49 @@
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
 
@@ -36,13 +54,62 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
36
  var r1 = r0 + chunk;
37
  if (r1 > params.rows) { r1 = params.rows; }
38
 
39
- {% if source.intMode %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  var acc = 1i;
41
  {% else %}
 
 
 
 
 
 
 
 
42
  var acc = 1.0;
 
 
 
43
  {% endif %}
44
  for (var row = r0; row < r1; row = row + 1u) {
 
 
 
45
  acc = acc * {{ xa }}x[row * params.cols + col]{{ ax }};
 
 
 
 
 
 
 
 
46
  }
47
  partials[seg * params.cols + col] = acc;
 
48
  }
 
2
  // segments increases residency for tall matrices. Each (column, segment)
3
  // invocation reduces one row slice and writes partials[segment * columns +
4
  // column]. Adjacent column threads keep row reads coalesced.
5
+ {% set castF32 = castF32 is defined and castF32 %}
 
 
 
6
  {% set xa = "f32(" if castF32 else "" %}
7
  {% set ax = ")" if castF32 else "" %}
8
+ {% if usesF16Spec is defined and usesF16Spec %}
9
  enable f16;
10
  {% endif %}
11
  {{ env.wgsl.resourceDeclarations }}
12
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
13
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
14
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
15
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
16
+ fn {{ name }}() -> {{ scalar }} {
17
+ {% if scalar == "i32" %}
18
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
19
+ {% elif scalar == "u32" %}
20
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
21
+ {% else %}
22
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
23
+ return bitcast<f32>(bits);
24
+ {% endif %}
25
+ }
26
+ {%- endmacro %}
27
 
28
 
29
  const WG: u32 = {{ workgroupSize }}u;
30
  const SPLIT: u32 = {{ split }}u;
31
+ {% if op == "logsumexp" %}
32
+ const F32_MIN: f32 = -3.4028234663852886e38;
33
+
34
+ fn is_nan_f32(value: f32) -> bool {
35
+ let bits = bitcast<u32>(value);
36
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
37
+ }
38
+ {% elif op == "max" or op == "min" %}
39
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
40
+ {% endif %}
41
 
42
  @compute @workgroup_size(WG, 1, 1)
43
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
44
+ @builtin(workgroup_id) wg: vec3<u32>) {
45
+ // 2D-folded column index: wg.z carries the high bits past the per-axis dispatch
46
+ // fold width on the x dimension.
47
+ let col = (wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u) * WG + (gid.x % WG);
 
48
  let seg = wg.y;
49
  if (col >= params.cols) { return; }
50
 
 
54
  var r1 = r0 + chunk;
55
  if (r1 > params.rows) { r1 = params.rows; }
56
 
57
+ {% if op == "logsumexp" %}
58
+ var local_max = F32_MIN;
59
+ var local_nan_count = 0.0;
60
+ var local_nan_value = 0.0;
61
+ for (var row = r0; row < r1; row = row + 1u) {
62
+ let value = {{ xa }}x[row * params.cols + col]{{ ax }};
63
+ if (is_nan_f32(value)) {
64
+ local_nan_count = local_nan_count + 1.0;
65
+ local_nan_value = value;
66
+ } else {
67
+ local_max = max(local_max, value);
68
+ }
69
+ }
70
+ var acc = 0.0;
71
+ if (local_nan_count == 0.0) {
72
+ for (var row = r0; row < r1; row = row + 1u) {
73
+ acc = acc + exp({{ xa }}x[row * params.cols + col]{{ ax }} - local_max);
74
+ }
75
+ }
76
+ // Three partial planes: seg max, seg sumexp(x - max), and a packed nan
77
+ // marker (count in the low slot, the nan bit-pattern smuggled as f32).
78
+ partials[seg * params.cols + col] = local_max;
79
+ partials[(SPLIT + seg) * params.cols + col] = acc;
80
+ partials[(2u * SPLIT + seg) * params.cols + col] = select(0.0, local_nan_value, local_nan_count > 0.0);
81
+ {% else %}
82
+ {% if intMode | default(false) %}
83
+ {% if op == "prod" %}
84
  var acc = 1i;
85
  {% else %}
86
+ var acc = 0i;
87
+ {% endif %}
88
+ {% else %}
89
+ {% if op == "max" %}
90
+ var acc = reduction_identity();
91
+ {% elif op == "min" %}
92
+ var acc = reduction_identity();
93
+ {% elif op == "prod" %}
94
  var acc = 1.0;
95
+ {% else %}
96
+ var acc = 0.0;
97
+ {% endif %}
98
  {% endif %}
99
  for (var row = r0; row < r1; row = row + 1u) {
100
+ {% if op == "max" or op == "min" %}
101
+ acc = {{ op }}(acc, {{ xa }}x[row * params.cols + col]{{ ax }});
102
+ {% elif op == "prod" %}
103
  acc = acc * {{ xa }}x[row * params.cols + col]{{ ax }};
104
+ {% elif op == "l1" %}
105
+ acc = acc + abs({{ xa }}x[row * params.cols + col]{{ ax }});
106
+ {% elif op == "l2" or op == "sumsquare" %}
107
+ let value = {{ xa }}x[row * params.cols + col]{{ ax }};
108
+ acc = acc + value * value;
109
+ {% else %}
110
+ acc = acc + {{ xa }}x[row * params.cols + col]{{ ax }};
111
+ {% endif %}
112
  }
113
  partials[seg * params.cols + col] = acc;
114
+ {% endif %}
115
  }
build/webgpu/reduce-axis0-tilecols.wgsl.jinja CHANGED
@@ -1,9 +1,9 @@
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" %}
@@ -13,34 +13,66 @@
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;
@@ -61,17 +93,134 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
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
  let col = tile * TILE_COLS + col_lane;
66
  let inputBase = 0u;
67
  let outputIndex = col;
68
  let in_bounds = col < params.cols;
69
  {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
 
 
 
71
  var acc = {% if intMode %}{{ scalar }}(1){% else %}1.0{% endif %};
 
 
 
72
  if (in_bounds) {
73
  for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
 
 
 
74
  acc = acc * {{ elem }};
 
 
 
 
 
 
 
 
75
  }
76
  }
77
  partial[tid] = acc;
@@ -80,12 +229,32 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
80
  if (row_lane == 0u && in_bounds) {
81
  var total = partial[col_lane];
82
  for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
 
 
 
83
  total = total * partial[lane * TILE_COLS + col_lane];
 
 
 
84
  }
85
  {% if splitMode %}
86
  partials[seg * params.outputs + outputIndex] = total;
 
 
 
 
 
 
 
 
 
 
 
 
87
  {% else %}
88
  y[outputIndex] = {{ yv }}total{{ vy }};
 
89
  {% endif %}
90
  }
 
91
  }
 
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 expose independent
5
+ // reduction work when the flattened output has only a few elements.
6
+ {% set splitMode = splitSpec is defined %}
7
  {% if splitMode %}
8
  {% set rowBegin = "row_begin + row_lane" %}
9
  {% set rowEnd = "row_end" %}
 
13
  {% set rowEnd = "params.rows" %}
14
  {% set elem = "x[inputBase + row * params.cols + col]" %}
15
  {% endif %}
16
+ {% set castF32 = castF32 is defined and castF32 %}
17
+ {% set intMode = intMode is defined and intMode %}
18
  {% set scalar = "f32" if castF32 else scalar %}
19
  {% if castF32 %}
20
  {% set elem = "f32(" ~ elem ~ ")" %}
21
  {% endif %}
22
  {% set yv = "f16(" if castF32 else "" %}
23
  {% set vy = ")" if castF32 else "" %}
24
+ {% if usesF16Spec is defined and usesF16Spec %}
25
  enable f16;
26
  {% endif %}
27
  {{ env.wgsl.resourceDeclarations }}
28
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
29
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
30
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
31
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
32
+ fn {{ name }}() -> {{ scalar }} {
33
+ {% if scalar == "i32" %}
34
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
35
+ {% elif scalar == "u32" %}
36
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
37
+ {% else %}
38
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
39
+ return bitcast<f32>(bits);
40
+ {% endif %}
41
+ }
42
+ {%- endmacro %}
43
 
44
+ {% if not splitMode and not intMode and (op == "logsum" or op == "logsumexp") %}
45
+ fn negative_infinity() -> f32 {
46
+ var bits = 0xff800000u;
47
+ return bitcast<f32>(bits);
48
+ }
49
+
50
+ {% endif %}
51
 
52
  const WG: u32 = {{ workgroupSize }}u;
53
  const TILE_COLS: u32 = {{ tileCols }}u;
54
  const ROW_LANES: u32 = WG / TILE_COLS;
55
  {% if splitMode %}
56
+ const SPLIT: u32 = {{ splitSpec }}u;
57
+ {% endif %}
58
+ {% if op == "max" or op == "min" %}
59
+ {{ wgsl_minmax_identity("lane_identity", op, scalar) }}
60
+ {% elif op == "logsumexp" %}
61
+ const F32_MIN: f32 = -3.4028234663852886e38;
62
+ const F32_MAX: f32 = 3.4028234663852886e38;
63
  {% endif %}
64
 
65
+ var<workgroup> partial: array<{{ scalar if (op == "max" or op == "min" or intMode) else "f32" }}, WG>;
66
+ {% if op == "logsumexp" %}
67
+
68
+ fn is_nan_f32(value: f32) -> bool {
69
+ let bits = bitcast<u32>(value);
70
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
71
+ }
72
+
73
+ {% endif %}
74
  @compute @workgroup_size(WG, 1, 1)
75
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
 
76
  let tid = lid.x;
77
  let col_lane = tid % TILE_COLS;
78
  let row_lane = tid / TILE_COLS;
 
93
  // 2D-folded tile index: wg.y carries the high bits past the dispatch limit.
94
  // The batched form reuses this same coalesced axis-0 reduction for a middle
95
  // axis by assigning consecutive tiles to each outer slice.
96
+ let tile = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
97
  let col = tile * TILE_COLS + col_lane;
98
  let inputBase = 0u;
99
  let outputIndex = col;
100
  let in_bounds = col < params.cols;
101
  {% endif %}
102
+ {% if op == "logsumexp" %}
103
+ {% if not splitMode %}
104
+
105
+ if (params.rows == 0u) {
106
+ if (row_lane == 0u && in_bounds) {
107
+ y[outputIndex] = {{ yv }}negative_infinity(){{ vy }};
108
+ }
109
+ return;
110
+ }
111
+ {% endif %}
112
+
113
+ var local_max = F32_MIN;
114
+ var local_nan_count = 0.0;
115
+ var local_nan_value = 0.0;
116
+ if (in_bounds) {
117
+ for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
118
+ let value = {{ elem }};
119
+ if (is_nan_f32(value)) {
120
+ local_nan_count = local_nan_count + 1.0;
121
+ local_nan_value = value;
122
+ } else {
123
+ local_max = max(local_max, value);
124
+ }
125
+ }
126
+ }
127
+
128
+ partial[tid] = local_max;
129
+ workgroupBarrier();
130
+ if (row_lane == 0u && in_bounds) {
131
+ var max_lanes = partial[col_lane];
132
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
133
+ max_lanes = max(max_lanes, partial[lane * TILE_COLS + col_lane]);
134
+ }
135
+ partial[col_lane] = max_lanes;
136
+ }
137
+ workgroupBarrier();
138
+ let max_value = partial[col_lane];
139
+ workgroupBarrier();
140
+
141
+ partial[tid] = local_nan_count;
142
+ workgroupBarrier();
143
+ if (row_lane == 0u && in_bounds) {
144
+ var nan_lanes = partial[col_lane];
145
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
146
+ nan_lanes = nan_lanes + partial[lane * TILE_COLS + col_lane];
147
+ }
148
+ partial[col_lane] = nan_lanes;
149
+ }
150
+ workgroupBarrier();
151
+ let nan_count = partial[col_lane];
152
+ workgroupBarrier();
153
+
154
+ partial[tid] = local_nan_value;
155
+ workgroupBarrier();
156
+ if (row_lane == 0u && in_bounds) {
157
+ var nan_value_lanes = partial[col_lane];
158
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
159
+ nan_value_lanes = nan_value_lanes + partial[lane * TILE_COLS + col_lane];
160
+ }
161
+ partial[col_lane] = nan_value_lanes;
162
+ }
163
+ workgroupBarrier();
164
+ let nan_value = partial[col_lane];
165
+ let has_nan = nan_count > 0.0;
166
+ let has_positive_inf = max_value > F32_MAX;
167
+ workgroupBarrier();
168
+
169
+ var acc = 0.0;
170
+ if (in_bounds && !has_nan && !has_positive_inf) {
171
+ for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
172
+ acc = acc + exp({{ elem }} - max_value);
173
+ }
174
+ }
175
+ partial[tid] = acc;
176
+ workgroupBarrier();
177
+
178
+ if (row_lane == 0u && in_bounds) {
179
+ var sum = partial[col_lane];
180
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
181
+ sum = sum + partial[lane * TILE_COLS + col_lane];
182
+ }
183
+ {% if splitMode %}
184
+ partials[seg * params.outputs + outputIndex] = max_value;
185
+ partials[(SPLIT + seg) * params.outputs + outputIndex] = sum;
186
+ partials[(2u * SPLIT + seg) * params.outputs + outputIndex] = select(0.0, nan_value, has_nan);
187
+ {% else %}
188
+ let finite_or_inf = select(max_value + log(sum), max_value, has_positive_inf);
189
+ y[outputIndex] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
190
+ {% endif %}
191
+ }
192
+ {% else %}
193
+ {% if op == "logsum" and not splitMode %}
194
+
195
+ if (params.rows == 0u) {
196
+ if (row_lane == 0u && in_bounds) {
197
+ y[outputIndex] = {{ yv }}negative_infinity(){{ vy }};
198
+ }
199
+ return;
200
+ }
201
+ {% endif %}
202
 
203
+ {% if op == "max" or op == "min" %}
204
+ var acc = lane_identity();
205
+ {% elif op == "prod" %}
206
  var acc = {% if intMode %}{{ scalar }}(1){% else %}1.0{% endif %};
207
+ {% else %}
208
+ var acc = {% if intMode %}{{ scalar }}(0){% else %}0.0{% endif %};
209
+ {% endif %}
210
  if (in_bounds) {
211
  for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
212
+ {% if op == "max" or op == "min" %}
213
+ acc = {{ op }}(acc, {{ elem }});
214
+ {% elif op == "prod" %}
215
  acc = acc * {{ elem }};
216
+ {% elif op == "l1" %}
217
+ acc = acc + abs({{ elem }});
218
+ {% elif op == "l2" or op == "sumsquare" %}
219
+ let value = {{ elem }};
220
+ acc = acc + value * value;
221
+ {% else %}
222
+ acc = acc + {{ elem }};
223
+ {% endif %}
224
  }
225
  }
226
  partial[tid] = acc;
 
229
  if (row_lane == 0u && in_bounds) {
230
  var total = partial[col_lane];
231
  for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
232
+ {% if op == "max" or op == "min" %}
233
+ total = {{ op }}(total, partial[lane * TILE_COLS + col_lane]);
234
+ {% elif op == "prod" %}
235
  total = total * partial[lane * TILE_COLS + col_lane];
236
+ {% else %}
237
+ total = total + partial[lane * TILE_COLS + col_lane];
238
+ {% endif %}
239
  }
240
  {% if splitMode %}
241
  partials[seg * params.outputs + outputIndex] = total;
242
+ {% else %}
243
+ {% if op == "l2" and intMode %}
244
+ y[outputIndex] = {{ scalar }}(sqrt(f32(total)));
245
+ {% elif op == "l2" %}
246
+ y[outputIndex] = {{ yv }}sqrt(total){{ vy }};
247
+ {% elif op == "logsum" %}
248
+ y[outputIndex] = {{ yv }}log(total){{ vy }};
249
+ {% elif op == "mean" and intMode %}
250
+ // Integer division truncates toward zero.
251
+ y[outputIndex] = total / {{ scalar }}(params.rows);
252
+ {% elif op == "mean" %}
253
+ y[outputIndex] = {{ yv }}total / f32(params.rows){{ vy }};
254
  {% else %}
255
  y[outputIndex] = {{ yv }}total{{ vy }};
256
+ {% endif %}
257
  {% endif %}
258
  }
259
+ {% endif %}
260
  }
build/webgpu/reduce-flat-partial.wgsl.jinja CHANGED
@@ -6,20 +6,37 @@
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>,
@@ -27,16 +44,38 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
27
  @builtin(num_workgroups) nwg: vec3<u32>) {
28
  let tid = lid.x;
29
  let gstride = nwg.x * WG;
30
- {% if source.intMode %}
 
31
  var acc = 1i;
32
  {% else %}
 
 
 
 
 
 
 
33
  var acc = 1.0;
 
 
34
  {% endif %}
35
  // Grid-stride over the flat vec4 groups (params.count4 = numel / 4, floored).
36
  for (var i = gid.x; i < params.count4; i = i + gstride) {
37
  let b = 4u * i;
38
- 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 }});
 
 
 
 
 
39
  acc = acc * v.x * v.y * v.z * v.w;
 
 
 
 
 
 
 
40
  }
41
  // Scalar tail (the 0..3 elements past the last full vec4). One global thread
42
  // folds it so it is counted exactly once; the count is tiny so serializing it
@@ -44,7 +83,19 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
44
  if (gid.x == 0u) {
45
  for (var i = 4u * params.count4; i < params.numel; i = i + 1u) {
46
  let s = {{ xa }}x[i]{{ ax }};
 
 
 
 
 
47
  acc = acc * s;
 
 
 
 
 
 
 
48
  }
49
  }
50
  red[tid] = acc;
@@ -53,7 +104,15 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
53
  loop {
54
  if (stride == 0u) { break; }
55
  if (tid < stride) {
 
 
 
 
 
56
  red[tid] = red[tid] * red[tid + stride];
 
 
 
57
  }
58
  stride = stride / 2u;
59
  workgroupBarrier();
 
6
  // Scalar f32 bindings keep arbitrary element counts legal. The grid-stride loop
7
  // manually assembles full vec4 groups from contiguous scalars, and one global
8
  // thread folds the final zero-to-three scalar elements exactly once.
9
+ {% set intMode = intMode is defined and intMode %}
10
+ {% set castF32 = castF32 is defined and castF32 %}
11
  {% set xa = "f32(" if castF32 else "" %}
12
  {% set ax = ")" if castF32 else "" %}
13
+ {% if usesF16Spec is defined and usesF16Spec %}
14
  enable f16;
15
  {% endif %}
16
  {{ env.wgsl.resourceDeclarations }}
17
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
18
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
19
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
20
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
21
+ fn {{ name }}() -> {{ scalar }} {
22
+ {% if scalar == "i32" %}
23
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
24
+ {% elif scalar == "u32" %}
25
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
26
+ {% else %}
27
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
28
+ return bitcast<f32>(bits);
29
+ {% endif %}
30
+ }
31
+ {%- endmacro %}
32
 
33
 
34
  const WG: u32 = {{ workgroupSize }}u;
35
+ var<workgroup> red: array<{{ "i32" if intMode else "f32" }}, WG>;
36
+ {% if op == "max" or op == "min" %}
37
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
38
+
39
+ {% endif %}
40
  @compute @workgroup_size(WG)
41
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
42
  @builtin(local_invocation_id) lid: vec3<u32>,
 
44
  @builtin(num_workgroups) nwg: vec3<u32>) {
45
  let tid = lid.x;
46
  let gstride = nwg.x * WG;
47
+ {% if intMode %}
48
+ {% if op == "prod" %}
49
  var acc = 1i;
50
  {% else %}
51
+ var acc = 0i;
52
+ {% endif %}
53
+ {% elif op == "max" %}
54
+ var acc = reduction_identity();
55
+ {% elif op == "min" %}
56
+ var acc = reduction_identity();
57
+ {% elif op == "prod" %}
58
  var acc = 1.0;
59
+ {% else %}
60
+ var acc = 0.0;
61
  {% endif %}
62
  // Grid-stride over the flat vec4 groups (params.count4 = numel / 4, floored).
63
  for (var i = gid.x; i < params.count4; i = i + gstride) {
64
  let b = 4u * i;
65
+ let v = vec4<{{ "i32" if intMode else "f32" }}>({{ xa }}x[b]{{ ax }}, {{ xa }}x[b + 1u]{{ ax }}, {{ xa }}x[b + 2u]{{ ax }}, {{ xa }}x[b + 3u]{{ ax }});
66
+ {% if op == "max" %}
67
+ acc = max(acc, max(max(v.x, v.y), max(v.z, v.w)));
68
+ {% elif op == "min" %}
69
+ acc = min(acc, min(min(v.x, v.y), min(v.z, v.w)));
70
+ {% elif op == "prod" %}
71
  acc = acc * v.x * v.y * v.z * v.w;
72
+ {% elif op == "l1" %}
73
+ acc = acc + abs(v.x) + abs(v.y) + abs(v.z) + abs(v.w);
74
+ {% elif op == "l2" or op == "sumsquare" %}
75
+ acc = acc + dot(v, v);
76
+ {% else %}
77
+ acc = acc + v.x + v.y + v.z + v.w;
78
+ {% endif %}
79
  }
80
  // Scalar tail (the 0..3 elements past the last full vec4). One global thread
81
  // folds it so it is counted exactly once; the count is tiny so serializing it
 
83
  if (gid.x == 0u) {
84
  for (var i = 4u * params.count4; i < params.numel; i = i + 1u) {
85
  let s = {{ xa }}x[i]{{ ax }};
86
+ {% if op == "max" %}
87
+ acc = max(acc, s);
88
+ {% elif op == "min" %}
89
+ acc = min(acc, s);
90
+ {% elif op == "prod" %}
91
  acc = acc * s;
92
+ {% elif op == "l1" %}
93
+ acc = acc + abs(s);
94
+ {% elif op == "l2" or op == "sumsquare" %}
95
+ acc = acc + s * s;
96
+ {% else %}
97
+ acc = acc + s;
98
+ {% endif %}
99
  }
100
  }
101
  red[tid] = acc;
 
104
  loop {
105
  if (stride == 0u) { break; }
106
  if (tid < stride) {
107
+ {% if op == "max" %}
108
+ red[tid] = max(red[tid], red[tid + stride]);
109
+ {% elif op == "min" %}
110
+ red[tid] = min(red[tid], red[tid + stride]);
111
+ {% elif op == "prod" %}
112
  red[tid] = red[tid] * red[tid + stride];
113
+ {% else %}
114
+ red[tid] = red[tid] + red[tid + stride];
115
+ {% endif %}
116
  }
117
  stride = stride / 2u;
118
  workgroupBarrier();
build/webgpu/reduce-i32-axes02.wgsl.jinja CHANGED
@@ -1,12 +1,11 @@
1
  // Rank-3 i32 reduction over axes {0, 2}, leaving the middle dimension.
2
  {{ env.wgsl.resourceDeclarations }}
3
 
4
- const WG: u32 = {{ source.workgroupSize }}u;
5
 
6
  @compute @workgroup_size(WG)
7
- fn main(@builtin(global_invocation_id) gid: vec3<u32>,
8
- @builtin(num_workgroups) nwg: vec3<u32>) {
9
- let outputIndex = gid.x + gid.y * nwg.x * WG;
10
  let d0 = params.dim0;
11
  let d1 = params.dim1;
12
  let d2 = params.dim2;
 
1
  // Rank-3 i32 reduction over axes {0, 2}, leaving the middle dimension.
2
  {{ env.wgsl.resourceDeclarations }}
3
 
4
+ const WG: u32 = {{ workgroupSizeSpec }}u;
5
 
6
  @compute @workgroup_size(WG)
7
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
8
+ let outputIndex = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
 
9
  let d0 = params.dim0;
10
  let d1 = params.dim1;
11
  let d2 = params.dim2;
build/webgpu/reduce-noop-empty-axes.wgsl.jinja CHANGED
@@ -1,13 +1,21 @@
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
  }
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  @compute @workgroup_size({{ reduceWorkgroupSize }})
4
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
5
  // 2D-folded flat index: gid.y carries the high bits past the
6
+ // per-axis dispatch fold width (outputs > 16.7M elements).
7
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ reduceWorkgroupSize }}u;
8
  if (i >= params.count) {
9
  return;
10
  }
11
  let v = x[i];
12
+ {% if op == "abs" %}
13
+ y[i] = abs(v);
14
+ {% elif op == "square" %}
15
+ y[i] = v * v;
16
+ {% elif op == "log" %}
17
+ y[i] = log(v);
18
+ {% else %}
19
  y[i] = v;
20
+ {% endif %}
21
  }
build/webgpu/reduce-row-subgroup-rows.wgsl.jinja ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Subgroup-per-row reduction for the selected operation on a contiguous last axis.
2
+ // One SUBGROUP owns each output row and a workgroup carries as many rows as it
3
+ // has subgroups, so the row fold is a single subgroup collective with no
4
+ // workgroup memory or barriers. Every lane
5
+ // reads vec4 words strided by the subgroup width, keeps them in registers, and
6
+ // the row is folded by the selected collective.
7
+ {% if op == "logsumexp" %}
8
+ // The row remains in registers across the maximum, NaN census, and exponential
9
+ // sum. Max subtraction and a positive-infinity branch define the stable result.
10
+ {% elif op == "mean" %}
11
+ // The finalizer divides the accumulated sum by the row width.
12
+ {% elif op == "l2" %}
13
+ // The finalizer takes the square root of the sum of squares.
14
+ {% elif op == "logsum" %}
15
+ // The finalizer takes the logarithm of the sum.
16
+ {% endif %}
17
+ // f16 storage widens before accumulation and narrows only at the final store.
18
+ {% set castF32 = castF32 is defined and castF32 %}
19
+ {% set isInt = scalar == "i32" or scalar == "u32" %}
20
+ {% set accScalar = scalar if isInt else "f32" %}
21
+ {% set xv = "vec4<f32>(" if castF32 else "" %}
22
+ {% set vx = ")" if castF32 else "" %}
23
+ {% set yv = "f16(" if castF32 else "" %}
24
+ {% set vy = ")" if castF32 else "" %}
25
+ enable subgroups;
26
+ {% if usesF16Spec is defined and usesF16Spec %}
27
+ enable f16;
28
+ {% endif %}
29
+ {{ env.wgsl.resourceDeclarations }}
30
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
31
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
32
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
33
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
34
+ fn {{ name }}() -> {{ scalar }} {
35
+ {% if scalar == "i32" %}
36
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
37
+ {% elif scalar == "u32" %}
38
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
39
+ {% else %}
40
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
41
+ return bitcast<f32>(bits);
42
+ {% endif %}
43
+ }
44
+ {%- endmacro %}
45
+
46
+
47
+ const WG: u32 = {{ workgroupSize }}u;
48
+ {%- if op == "max" or op == "min" %}
49
+ {{ wgsl_minmax_identity("reduction_identity", op, accScalar) }}
50
+ {%- endif %}
51
+ {%- if op == "logsumexp" %}
52
+
53
+ const F32_MIN: f32 = -3.4028234663852886e38;
54
+ const F32_MAX: f32 = 3.4028234663852886e38;
55
+
56
+ fn is_nan_f32(value: f32) -> bool {
57
+ let bits = bitcast<u32>(value);
58
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
59
+ }
60
+ {%- endif %}
61
+
62
+ @compute @workgroup_size(WG, 1, 1)
63
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
64
+ @builtin(local_invocation_id) lid: vec3<u32>,
65
+ @builtin(subgroup_invocation_id) sgLane: u32,
66
+ @builtin(subgroup_size) sgSize: u32) {
67
+ // Rows tile the folded workgroup grid; the lanes of one subgroup share a row.
68
+ let rowsPerWorkgroup = WG / sgSize;
69
+ let row = (wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u) * rowsPerWorkgroup + lid.x / sgSize;
70
+ // A subgroup past the last row folds an empty row and stores nothing, so
71
+ // every collective stays in uniform control flow.
72
+ let rowValid = row < params.rows;
73
+ let chunkLimit = select(0u, params.chunkCount, rowValid);
74
+ let base = row * params.chunkCount;
75
+ {% if op == "logsumexp" %}
76
+ // The row stays in registers: the max census and the exp pass read the same
77
+ // words, so the row is fetched from memory once.
78
+ var localMax = F32_MIN;
79
+ var localNan = 0.0;
80
+ var localNanValue = 0.0;
81
+ {% for i in range(vecsPerLane) %}
82
+ var v{{ i }} = vec4<f32>(0.0);
83
+ let c{{ i }} = sgLane + {{ i }}u * sgSize;
84
+ if (c{{ i }} < chunkLimit) {
85
+ v{{ i }} = {{ xv }}x[base + c{{ i }}]{{ vx }};
86
+ {% for comp in ["x", "y", "z", "w"] %}
87
+ if (is_nan_f32(v{{ i }}.{{ comp }})) {
88
+ localNan = 1.0;
89
+ localNanValue = v{{ i }}.{{ comp }};
90
+ } else {
91
+ localMax = max(localMax, v{{ i }}.{{ comp }});
92
+ }
93
+ {% endfor %}
94
+ }
95
+ {% endfor %}
96
+ let rowMax = subgroupMax(localMax);
97
+ let nanCount = subgroupAdd(localNan);
98
+ let nanValue = subgroupAdd(localNanValue);
99
+ let hasPositiveInf = rowMax > F32_MAX;
100
+ let hasNan = nanCount > 0.0;
101
+ var acc = 0.0;
102
+ {% for i in range(vecsPerLane) %}
103
+ if (c{{ i }} < chunkLimit) {
104
+ let e{{ i }} = select(exp(v{{ i }} - vec4<f32>(rowMax)), vec4<f32>(0.0), hasPositiveInf || hasNan);
105
+ acc = acc + ((e{{ i }}.x + e{{ i }}.y) + (e{{ i }}.z + e{{ i }}.w));
106
+ }
107
+ {% endfor %}
108
+ let sum = subgroupAdd(acc);
109
+ if (rowValid && sgLane == 0u) {
110
+ let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
111
+ y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
112
+ }
113
+ {%- else %}
114
+ {% if op == "max" or op == "min" %}
115
+ let INIT: {{ accScalar }} = reduction_identity();
116
+ {%- elif op == "prod" %}
117
+ let INIT: {{ accScalar }} = {{ "1.0" if not isInt else accScalar ~ "(1)" }};
118
+ {%- else %}
119
+ let INIT: {{ accScalar }} = {{ "0.0" if not isInt else accScalar ~ "(0)" }};
120
+ {%- endif %}
121
+ var acc4 = vec4<{{ accScalar }}>(INIT);
122
+ for (var c = sgLane; c < chunkLimit; c = c + sgSize) {
123
+ let v = {{ xv }}x[base + c]{{ vx }};
124
+ {%- if op == "max" %}
125
+ acc4 = max(acc4, v);
126
+ {%- elif op == "min" %}
127
+ acc4 = min(acc4, v);
128
+ {%- elif op == "prod" %}
129
+ acc4 = acc4 * v;
130
+ {%- elif op == "l1" %}
131
+ acc4 = acc4 + abs(v);
132
+ {%- elif op == "l2" or op == "sumsquare" %}
133
+ acc4 = acc4 + v * v;
134
+ {%- else %}
135
+ acc4 = acc4 + v;
136
+ {%- endif %}
137
+ }
138
+ {%- if op == "max" %}
139
+ let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
140
+ let total = subgroupMax(acc);
141
+ {%- elif op == "min" %}
142
+ let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
143
+ let total = subgroupMin(acc);
144
+ {%- elif op == "prod" %}
145
+ let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
146
+ let total = subgroupMul(acc);
147
+ {%- else %}
148
+ let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
149
+ let total = subgroupAdd(acc);
150
+ {%- endif %}
151
+ if (rowValid && sgLane == 0u) {
152
+ {%- if op == "mean" and isInt %}
153
+ y[row] = total / {{ accScalar }}(params.chunkCount * 4u);
154
+ {%- elif op == "mean" %}
155
+ y[row] = {{ yv }}total / f32(params.chunkCount * 4u){{ vy }};
156
+ {%- elif op == "l2" and isInt %}
157
+ y[row] = {{ accScalar }}(sqrt(f32(total)));
158
+ {%- elif op == "l2" %}
159
+ y[row] = {{ yv }}sqrt(total){{ vy }};
160
+ {%- elif op == "logsum" %}
161
+ y[row] = {{ yv }}log(total){{ vy }};
162
+ {%- else %}
163
+ y[row] = {{ yv }}total{{ vy }};
164
+ {%- endif %}
165
+ }
166
+ {%- endif %}
167
+ }
build/webgpu/reduce-row-subgroup.wgsl.jinja CHANGED
@@ -1,30 +1,63 @@
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) %}
@@ -44,26 +77,141 @@ fn {{ name }}(value: {{ scalar }}, sgLid: u32, sgId: u32, numSg: u32) -> {{ scal
44
  workgroupBarrier();
45
  return total;
46
  }
47
- {%- endmacro %}{{ emit_reduce("reduce_row", "subgroupMul", "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 = 1.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{{ vy }}; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Subgroup 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
+ {% if op == "mean" %}
5
+ // The finalizer divides the f32 sum by the row width.
6
+ {% elif op == "l2" %}
7
+ // The finalizer takes the square root of the sum of squares.
8
+ {% elif op == "logsum" %}
9
+ // The finalizer takes the logarithm of the sum.
10
+ {% elif op == "logsumexp" %}
11
+ // Max subtraction, explicit NaN propagation, and a positive-infinity branch
12
+ // define the stable log-sum-exp result.
13
+ {% elif op == "max" or op == "min" %}
14
+ // Integer values retain their native type; f32 identities are constructed from
15
+ // IEEE-754 bit patterns because WGSL rejects infinite constants.
16
+ {% endif %}
17
+ // f16 storage widens before accumulation and narrows only at the final store.
18
+ {% set castF32 = castF32 is defined and castF32 %}
19
  {% set scalar = "f32" if castF32 else scalar %}
20
+ {% set xv = ("vec4<f32>(" if vec4 else "f32(") if castF32 else "" %}
21
  {% set vx = ")" if castF32 else "" %}
22
  {% set yv = "f16(" if castF32 else "" %}
23
  {% set vy = ")" if castF32 else "" %}
24
  enable subgroups;
25
+ {% if usesF16Spec is defined and usesF16Spec %}
26
  enable f16;
27
  {% endif %}
28
  {{ env.wgsl.resourceDeclarations }}
29
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
30
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
31
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
32
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
33
+ fn {{ name }}() -> {{ scalar }} {
34
+ {% if scalar == "i32" %}
35
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
36
+ {% elif scalar == "u32" %}
37
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
38
+ {% else %}
39
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
40
+ return bitcast<f32>(bits);
41
+ {% endif %}
42
+ }
43
+ {%- endmacro %}
44
 
45
 
46
  const WG: u32 = {{ workgroupSize }}u;
47
+ {%- if op == "max" or op == "min" %}
48
+ {{ wgsl_minmax_identity("reduction_identity", op, scalar) }}
49
+ {%- endif %}
50
+ {%- if op == "logsumexp" %}
51
+
52
+ const F32_MIN: f32 = -3.4028234663852886e38;
53
+ const F32_MAX: f32 = 3.4028234663852886e38;
54
+
55
+ fn is_nan_f32(value: f32) -> bool {
56
+ let bits = bitcast<u32>(value);
57
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
58
+ }
59
+ {%- endif %}
60
+
61
  var<workgroup> wgPartial: array<{{ scalar }}, WG>;
62
 
63
  {% macro emit_reduce(name, collective, combine) %}
 
77
  workgroupBarrier();
78
  return total;
79
  }
80
+ {%- endmacro %}
81
+ {%- if op == "max" %}
82
+ {{ emit_reduce("reduce_row", "subgroupMax", "total = max(total, wgPartial[i]);") }}
83
+ {%- elif op == "min" %}
84
+ {{ emit_reduce("reduce_row", "subgroupMin", "total = min(total, wgPartial[i]);") }}
85
+ {%- elif op == "prod" %}
86
+ {{ emit_reduce("reduce_row", "subgroupMul", "total = total * wgPartial[i];") }}
87
+ {%- elif op == "logsumexp" %}
88
+ {{ emit_reduce("reduce_row_add", "subgroupAdd", "total = total + wgPartial[i];") }}
89
+ {{ emit_reduce("reduce_row_max", "subgroupMax", "total = max(total, wgPartial[i]);") }}
90
+ {%- else %}
91
+ {{ emit_reduce("reduce_row", "subgroupAdd", "total = total + wgPartial[i];") }}
92
+ {%- endif %}
93
+
94
  @compute @workgroup_size(WG, 1, 1)
95
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
 
96
  @builtin(local_invocation_id) lid: vec3<u32>,
97
  @builtin(subgroup_invocation_id) sgLid: u32,
98
  @builtin(subgroup_id) sgId: u32,
99
  @builtin(num_subgroups) numSg: u32) {
100
+ let row = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
101
  if (row >= params.rows) {
102
  return;
103
  }
104
  let tid = lid.x;
105
+ let base = row * params.chunkCount;
106
+ {%- if op == "logsumexp" %}
107
+ var localMax = F32_MIN;
108
+ var localNan = 0.0;
109
+ var localNanValue = 0.0;
110
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
111
+ let v = {{ xv }}x[base + c]{{ vx }};
112
+ {%- if vec4 %}
113
+ {% for comp in ["x", "y", "z", "w"] %}
114
+ if (is_nan_f32(v.{{ comp }})) {
115
+ localNan = 1.0;
116
+ localNanValue = v.{{ comp }};
117
+ } else {
118
+ localMax = max(localMax, v.{{ comp }});
119
+ }
120
+ {%- endfor %}
121
+ {% else %}
122
+ if (is_nan_f32(v)) {
123
+ localNan = 1.0;
124
+ localNanValue = v;
125
+ } else {
126
+ localMax = max(localMax, v);
127
+ }
128
+ {%- endif %}
129
+ }
130
+ let rowMax = reduce_row_max(localMax, sgLid, sgId, numSg);
131
+ let nanCount = reduce_row_add(localNan, sgLid, sgId, numSg);
132
+ let nanValue = reduce_row_add(localNanValue, sgLid, sgId, numSg);
133
+ let hasPositiveInf = rowMax > F32_MAX;
134
+ let hasNan = nanCount > 0.0;
135
+ var acc = 0.0;
136
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
137
+ let v = {{ xv }}x[base + c]{{ vx }};
138
+ {%- if vec4 %}
139
+ let e = select(exp(v - vec4<f32>(rowMax)), vec4<f32>(0.0), hasPositiveInf || hasNan);
140
+ acc = acc + ((e.x + e.y) + (e.z + e.w));
141
+ {%- else %}
142
+ acc = acc + select(exp(v - rowMax), 0.0, hasPositiveInf || hasNan);
143
+ {%- endif %}
144
+ }
145
+ let sum = reduce_row_add(acc, sgLid, sgId, numSg);
146
+ if (tid == 0u) {
147
+ let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
148
+ y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
149
+ }
150
+ {%- else %}
151
+ {% if op == "max" or op == "min" %}
152
+ let INIT: {{ scalar }} = reduction_identity();
153
+ {%- elif op == "prod" %}
154
+ let INIT: f32 = 1.0;
155
+ {%- else %}
156
+ let INIT: f32 = 0.0;
157
+ {%- endif %}
158
+ {% if vec4 %}
159
  var acc4 = vec4<{{ scalar }}>(INIT);
160
  for (var c = tid; c < params.chunkCount; c = c + WG) {
161
+ let v = {{ xv }}x[base + c]{{ vx }};
162
+ {%- if op == "max" %}
163
+ acc4 = max(acc4, v);
164
+ {%- elif op == "min" %}
165
+ acc4 = min(acc4, v);
166
+ {%- elif op == "prod" %}
167
+ acc4 = acc4 * v;
168
+ {%- elif op == "l1" %}
169
+ acc4 = acc4 + abs(v);
170
+ {%- elif op == "l2" or op == "sumsquare" %}
171
+ acc4 = acc4 + v * v;
172
+ {%- else %}
173
+ acc4 = acc4 + v;
174
+ {%- endif %}
175
+ }
176
+ {%- if op == "max" %}
177
+ let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
178
+ {%- elif op == "min" %}
179
+ let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
180
+ {%- elif op == "prod" %}
181
+ let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
182
+ {%- else %}
183
+ let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
184
+ {%- endif %}
185
+ {% else %}
186
  var acc = INIT;
187
  for (var c = tid; c < params.chunkCount; c = c + WG) {
188
+ let v = {{ xv }}x[base + c]{{ vx }};
189
+ {%- if op == "max" %}
190
+ acc = max(acc, v);
191
+ {%- elif op == "min" %}
192
+ acc = min(acc, v);
193
+ {%- elif op == "prod" %}
194
+ acc = acc * v;
195
+ {%- elif op == "l1" %}
196
+ acc = acc + abs(v);
197
+ {%- elif op == "l2" or op == "sumsquare" %}
198
+ acc = acc + v * v;
199
+ {%- else %}
200
+ acc = acc + v;
201
+ {%- endif %}
202
+ }
203
  {%- endif %}
204
  let total = reduce_row(acc, sgLid, sgId, numSg);
205
+ if (tid == 0u) {
206
+ {%- if op == "mean" %}
207
+ y[row] = {{ yv }}total / f32(params.cols){{ vy }};
208
+ {%- elif op == "l2" %}
209
+ y[row] = {{ yv }}sqrt(total){{ vy }};
210
+ {%- elif op == "logsum" %}
211
+ y[row] = {{ yv }}log(total){{ vy }};
212
+ {%- else %}
213
+ y[row] = {{ yv }}total{{ vy }};
214
+ {%- endif %}
215
+ }
216
+ {%- endif %}
217
+ }
build/webgpu/reduce-row-tree.wgsl.jinja CHANGED
@@ -1,74 +1,266 @@
1
- // Portable one-workgroup-per-row reduction for the Reduce value family.
2
  // Threads stride a contiguous row, accumulate locally, and fold their values
3
  // through a shared-memory tree without relying on subgroups.
4
- //
5
- // Max, min, and product use the native f32/i32/u32 value type. Additive
6
- // transforms accumulate float inputs in f32 but retain integer accumulation
7
- // for sum, L1, and sum-of-squares. Mean, L2, log-sum, and log-sum-exp are
8
- // f32-only because their finalizers divide, take a square root, or take a log.
 
 
 
 
 
 
9
  // f16 storage widens through f32 for both accumulation and the shared tree,
10
  // then narrows only at the final store.
11
- {% set castF32 = source.castF32 is defined and source.castF32 %}
 
 
12
  {% set scalar = "f32" if castF32 else scalar %}
13
- {% set xv = ("vec4<f32>(" if source.vec4 else "f32(") if castF32 else "" %}
14
  {% set vx = ")" if castF32 else "" %}
15
  {% set yv = "f16(" if castF32 else "" %}
16
  {% set vy = ")" if castF32 else "" %}
17
- {% if source.usesF16 is defined and source.usesF16 %}
18
  enable f16;
19
  {% endif %}
20
  {{ env.wgsl.resourceDeclarations }}
21
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
22
- * evaluation, so f32 identities are constructed at runtime from their
23
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
24
 
 
 
 
 
 
 
 
25
 
26
  const WG: u32 = {{ workgroupSize }}u;
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  {% set is_int = scalar == "i32" or scalar == "u32" %}
29
- {% set intAdditive = is_int and (source.op == "sum" or source.op == "l1" or source.op == "sumsquare"
30
- or source.op == "l2" or source.op == "mean") %}
31
- {% set accType = scalar if source.op == "max" or source.op == "min" or source.op == "prod" or intAdditive else "f32" %}
32
- {% if source.op == "prod" and accType == "i32" %}
 
 
33
  fn identity() -> i32 { return 1i; }
34
- {% else %}
35
  fn identity() -> {{ accType }} { return {{ accType }}(1); }
 
 
36
  {% endif %}
37
 
38
  fn combine(a: {{ accType }}, b: {{ accType }}) -> {{ accType }} {
 
 
 
 
 
39
  return a * b;
 
 
 
40
  }
41
 
42
  var<workgroup> partial: array<{{ accType }}, WG>;
 
43
 
44
  @compute @workgroup_size(WG, 1, 1)
45
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
46
- @builtin(num_workgroups) nwg: vec3<u32>,
47
  @builtin(local_invocation_id) lid: vec3<u32>) {
48
- let row = wg.x + wg.y * nwg.x;
49
  if (row >= params.rows) {
50
  return;
51
  }
52
  let tid = lid.x;
53
- {% if source.vec4 %}
54
  let base = row * params.chunkCount;
55
  {% else %}
56
  let base = row * params.cols;
57
  {% endif %}
58
 
 
 
 
 
 
 
 
59
 
60
- {% if source.vec4 %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  var acc4 = vec4<{{ accType }}>(identity());
62
  for (var col = tid; col < params.chunkCount; col = col + WG) {
63
  let value = {{ xv }}x[base + col]{{ vx }};
 
 
 
 
 
64
  acc4 = acc4 * value;
 
 
 
 
 
 
 
65
  }
 
 
 
 
 
66
  let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
 
 
 
67
  {% else %}
68
  var acc = identity();
69
  for (var col = tid; col < params.cols; col = col + WG) {
70
  let value = {{ xv }}x[base + col]{{ vx }};
 
 
 
 
 
71
  acc = combine(acc, value);
 
72
  }
73
  {% endif %}
74
  partial[tid] = acc;
@@ -83,6 +275,19 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
83
 
84
  if (tid == 0u) {
85
  let total = partial[0];
 
 
 
 
 
 
 
 
 
 
 
86
  y[row] = {{ yv }}total{{ vy }};
 
87
  }
 
88
  }
 
1
+ // Portable one-workgroup-per-row reduction for the selected operation.
2
  // Threads stride a contiguous row, accumulate locally, and fold their values
3
  // through a shared-memory tree without relying on subgroups.
4
+ {% if op == "max" or op == "min" or op == "prod" %}
5
+ // Values accumulate in their native f32/i32/u32 type.
6
+ {% elif op == "mean" %}
7
+ // The tree sums values before the final division by the row width.
8
+ {% elif op == "l2" %}
9
+ // The tree sums squared values before the final square root.
10
+ {% elif op == "logsum" %}
11
+ // The tree sums values before the final logarithm.
12
+ {% elif op == "logsumexp" %}
13
+ // A max-subtracted exponential sum handles NaN and positive-infinity rows.
14
+ {% endif %}
15
  // f16 storage widens through f32 for both accumulation and the shared tree,
16
  // then narrows only at the final store.
17
+ {% set isVec4 = vec4 is defined and vec4 %}
18
+ {% set rowIsEmpty = "params.chunkCount == 0u" if isVec4 else "params.cols == 0u" %}
19
+ {% set castF32 = castF32 is defined and castF32 %}
20
  {% set scalar = "f32" if castF32 else scalar %}
21
+ {% set xv = ("vec4<f32>(" if vec4 else "f32(") if castF32 else "" %}
22
  {% set vx = ")" if castF32 else "" %}
23
  {% set yv = "f16(" if castF32 else "" %}
24
  {% set vy = ")" if castF32 else "" %}
25
+ {% if usesF16Spec is defined and usesF16Spec %}
26
  enable f16;
27
  {% endif %}
28
  {{ env.wgsl.resourceDeclarations }}
29
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
30
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
31
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
32
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
33
+ fn {{ name }}() -> {{ scalar }} {
34
+ {% if scalar == "i32" %}
35
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
36
+ {% elif scalar == "u32" %}
37
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
38
+ {% else %}
39
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
40
+ return bitcast<f32>(bits);
41
+ {% endif %}
42
+ }
43
+ {%- endmacro %}
44
 
45
+ {% if scalar != "i32" and scalar != "u32" and (op == "logsum" or op == "logsumexp") %}
46
+ fn negative_infinity() -> f32 {
47
+ var bits = 0xff800000u;
48
+ return bitcast<f32>(bits);
49
+ }
50
+
51
+ {% endif %}
52
 
53
  const WG: u32 = {{ workgroupSize }}u;
54
 
55
+ {% if op == "logsumexp" %}
56
+ const F32_MIN: f32 = -3.4028234663852886e38;
57
+ const F32_MAX: f32 = 3.4028234663852886e38;
58
+
59
+ var<workgroup> partial: array<f32, WG>;
60
+ {% macro wgsl_tree_reduce_f32(name, mode, buffer="partial", wg="WG", trailingBarrier=true) %}
61
+ fn {{ name }}(value: f32, tid: u32) -> f32 {
62
+ {{ buffer }}[tid] = value;
63
+ workgroupBarrier();
64
+ // Ceil-halving keeps every lane when the workgroup size is not a power of
65
+ // two. For even n this matches the power-of-two tree order; for odd n, lanes
66
+ // [0, n-half) fold the upper tail while the middle lane carries forward.
67
+ var n: u32 = {{ wg }};
68
+ loop {
69
+ let half = (n + 1u) / 2u;
70
+ if (tid < n - half) {
71
+ {% if mode == "max" %}
72
+ {{ buffer }}[tid] = max({{ buffer }}[tid], {{ buffer }}[tid + half]);
73
+ {% else %}
74
+ {{ buffer }}[tid] = {{ buffer }}[tid] + {{ buffer }}[tid + half];
75
+ {% endif %}
76
+ }
77
+ workgroupBarrier();
78
+ n = half;
79
+ if (n == 1u) {
80
+ break;
81
+ }
82
+ }
83
+ // The default trailing barrier makes this helper safe for back-to-back calls: every lane reads
84
+ // slot 0 here, so the next call's first store must not run until all lanes have read it.
85
+ // `trailingBarrier=false` is safe only when the buffer is never written again before kernel exit.
86
+ let reduced = {{ buffer }}[0];
87
+ {% if trailingBarrier %}
88
+ workgroupBarrier();
89
+ {% endif %}
90
+ return reduced;
91
+ }
92
+ {% endmacro %}
93
+
94
+ {{ wgsl_tree_reduce_f32("reduce_sum", "add", "partial", "WG") }}
95
+ {{ wgsl_tree_reduce_f32("reduce_max", "max", "partial", "WG") }}
96
+
97
+ fn is_nan_f32(value: f32) -> bool {
98
+ let bits = bitcast<u32>(value);
99
+ return (bits & 0x7f800000u) == 0x7f800000u
100
+ && (bits & 0x007fffffu) != 0u;
101
+ }
102
+ {% else %}
103
  {% set is_int = scalar == "i32" or scalar == "u32" %}
104
+ {% set intAdditive = is_int and (op == "sum" or op == "l1" or op == "sumsquare"
105
+ or op == "l2" or op == "mean") %}
106
+ {% set accType = scalar if op == "max" or op == "min" or op == "prod" or intAdditive else "f32" %}
107
+ {% if op == "max" or op == "min" %}
108
+ {{ wgsl_minmax_identity("identity", op, accType) }}
109
+ {% elif op == "prod" and accType == "i32" %}
110
  fn identity() -> i32 { return 1i; }
111
+ {% elif op == "prod" %}
112
  fn identity() -> {{ accType }} { return {{ accType }}(1); }
113
+ {% else %}
114
+ fn identity() -> {{ accType }} { return {{ accType }}(0); }
115
  {% endif %}
116
 
117
  fn combine(a: {{ accType }}, b: {{ accType }}) -> {{ accType }} {
118
+ {% if op == "max" %}
119
+ return max(a, b);
120
+ {% elif op == "min" %}
121
+ return min(a, b);
122
+ {% elif op == "prod" %}
123
  return a * b;
124
+ {% else %}
125
+ return a + b;
126
+ {% endif %}
127
  }
128
 
129
  var<workgroup> partial: array<{{ accType }}, WG>;
130
+ {% endif %}
131
 
132
  @compute @workgroup_size(WG, 1, 1)
133
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
 
134
  @builtin(local_invocation_id) lid: vec3<u32>) {
135
+ let row = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
136
  if (row >= params.rows) {
137
  return;
138
  }
139
  let tid = lid.x;
140
+ {% if vec4 %}
141
  let base = row * params.chunkCount;
142
  {% else %}
143
  let base = row * params.cols;
144
  {% endif %}
145
 
146
+ {% if op == "logsumexp" %}
147
+ if ({{ rowIsEmpty }}) {
148
+ if (tid == 0u) {
149
+ y[row] = {{ yv }}negative_infinity(){{ vy }};
150
+ }
151
+ return;
152
+ }
153
 
154
+ var localMax = F32_MIN;
155
+ var localNan = 0.0;
156
+ var localNanValue = 0.0;
157
+ {% if vec4 %}
158
+ for (var col = tid; col < params.chunkCount; col = col + WG) {
159
+ let value = {{ xv }}x[base + col]{{ vx }};
160
+ {% for component in ["x", "y", "z", "w"] %}
161
+ if (is_nan_f32(value.{{ component }})) {
162
+ localNan = 1.0;
163
+ localNanValue = value.{{ component }};
164
+ } else {
165
+ localMax = max(localMax, value.{{ component }});
166
+ }
167
+ {% endfor %}
168
+ }
169
+ {% else %}
170
+ for (var col = tid; col < params.cols; col = col + WG) {
171
+ let value = {{ xv }}x[base + col]{{ vx }};
172
+ if (is_nan_f32(value)) {
173
+ localNan = 1.0;
174
+ localNanValue = value;
175
+ } else {
176
+ localMax = max(localMax, value);
177
+ }
178
+ }
179
+ {% endif %}
180
+ let rowMax = reduce_max(localMax, tid);
181
+ workgroupBarrier();
182
+ let nanCount = reduce_sum(localNan, tid);
183
+ workgroupBarrier();
184
+ let nanValue = reduce_sum(localNanValue, tid);
185
+ workgroupBarrier();
186
+ let hasPositiveInf = rowMax > F32_MAX;
187
+ let hasNan = nanCount > 0.0;
188
+
189
+ var acc = 0.0;
190
+ {% if vec4 %}
191
+ for (var col = tid; col < params.chunkCount; col = col + WG) {
192
+ let value = {{ xv }}x[base + col]{{ vx }};
193
+ let exponentials = select(exp(value - vec4<f32>(rowMax)), vec4<f32>(0.0),
194
+ hasPositiveInf || hasNan);
195
+ acc = acc + (exponentials.x + exponentials.y)
196
+ + (exponentials.z + exponentials.w);
197
+ }
198
+ {% else %}
199
+ for (var col = tid; col < params.cols; col = col + WG) {
200
+ acc = acc + select(exp({{ xv }}x[base + col]{{ vx }} - rowMax), 0.0,
201
+ hasPositiveInf || hasNan);
202
+ }
203
+ {% endif %}
204
+ let sum = reduce_sum(acc, tid);
205
+ if (tid == 0u) {
206
+ let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
207
+ y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
208
+ }
209
+ {% else %}
210
+ {% if op == "mean" %}
211
+ if ({{ rowIsEmpty }}) {
212
+ {% if is_int %}
213
+ if (tid == 0u) { y[row] = {{ scalar }}(0); }
214
+ {% else %}
215
+ if (tid == 0u) { y[row] = {{ yv }}0.0{{ vy }}; }
216
+ {% endif %}
217
+ return;
218
+ }
219
+ {% elif op == "logsum" %}
220
+ if ({{ rowIsEmpty }}) {
221
+ if (tid == 0u) { y[row] = {{ yv }}negative_infinity(){{ vy }}; }
222
+ return;
223
+ }
224
+ {% endif %}
225
+
226
+ {% if vec4 %}
227
  var acc4 = vec4<{{ accType }}>(identity());
228
  for (var col = tid; col < params.chunkCount; col = col + WG) {
229
  let value = {{ xv }}x[base + col]{{ vx }};
230
+ {% if op == "max" %}
231
+ acc4 = max(acc4, value);
232
+ {% elif op == "min" %}
233
+ acc4 = min(acc4, value);
234
+ {% elif op == "prod" %}
235
  acc4 = acc4 * value;
236
+ {% elif op == "l1" %}
237
+ acc4 = acc4 + abs(value);
238
+ {% elif op == "l2" or op == "sumsquare" %}
239
+ acc4 = acc4 + value * value;
240
+ {% else %}
241
+ acc4 = acc4 + value;
242
+ {% endif %}
243
  }
244
+ {% if op == "max" %}
245
+ let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
246
+ {% elif op == "min" %}
247
+ let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
248
+ {% elif op == "prod" %}
249
  let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
250
+ {% else %}
251
+ let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
252
+ {% endif %}
253
  {% else %}
254
  var acc = identity();
255
  for (var col = tid; col < params.cols; col = col + WG) {
256
  let value = {{ xv }}x[base + col]{{ vx }};
257
+ {% if op == "l1" %}
258
+ acc = combine(acc, abs(value));
259
+ {% elif op == "l2" or op == "sumsquare" %}
260
+ acc = combine(acc, value * value);
261
+ {% else %}
262
  acc = combine(acc, value);
263
+ {% endif %}
264
  }
265
  {% endif %}
266
  partial[tid] = acc;
 
275
 
276
  if (tid == 0u) {
277
  let total = partial[0];
278
+ {% if op == "l2" and is_int %}
279
+ y[row] = {{ scalar }}(sqrt(f32(total)));
280
+ {% elif op == "l2" %}
281
+ y[row] = {{ yv }}sqrt(total){{ vy }};
282
+ {% elif op == "logsum" %}
283
+ y[row] = {{ yv }}log(total){{ vy }};
284
+ {% elif op == "mean" and is_int %}
285
+ y[row] = total / {{ scalar }}(params.cols);
286
+ {% elif op == "mean" %}
287
+ y[row] = {{ yv }}total / f32(params.cols){{ vy }};
288
+ {% else %}
289
  y[row] = {{ yv }}total{{ vy }};
290
+ {% endif %}
291
  }
292
+ {% endif %}
293
  }
build/webgpu/reduce-serial-axis.wgsl.jinja CHANGED
@@ -1,87 +1,38 @@
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 }};
@@ -89,54 +40,248 @@ fn input_offset(out_index: u32{% if hasReducedAxis.value %}, reduce_linear: u32{
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 intMode %}
132
  // Integer reduction accumulates in the output type. sum/prod/l1/sumsquare stay
133
  // in integer arithmetic; l2/logsum widen for sqrt/log and truncate afterward.
134
- var acc = {{ scalar }}({{ "1" if source.op == "prod" else "0" }});
135
  {% else %}
136
- var acc = {{ "1.0" if source.op == "prod" else "0.0" }};
137
  {% endif %}
138
  for (var r = 0u; r < {{ count }}; r = r + 1u) {
 
139
  acc = acc * {{ at }};
 
 
 
 
 
 
 
 
140
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  y[i] = {{ yv }}acc{{ vy }};
 
 
142
  }
 
1
+ {% macro reduce_multi_axis_offset(hasReduced) %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  // One thread per output element walks the Cartesian product of the reduced axes,
4
  // linearized as reduce_linear. Specialized shapes make every input offset a sum
5
  // of coordinate-times-constant terms.
6
+ fn input_offset(out_index: u32{% if hasReduced %}, reduce_linear: u32{% endif %}) -> u32 {
7
  var rem = out_index;
8
+ {% for oaxis in range(outputRank) %}
9
  {% set ostride = namespace(value=1) %}
10
+ {% for j in range(oaxis + 1, outputRank) %}
11
+ {% set ostride.value = ostride.value * outputShape[j] %}
12
  {% endfor %}
13
  {% set osafe = 1 if ostride.value == 0 else ostride.value %}
14
+ {% if not keepDims or not reduce[oaxis] %}
15
  let out_coord{{ oaxis }} = rem / {{ osafe }}u;
16
  {% endif %}
17
  rem = rem % {{ osafe }}u;
18
  {% endfor %}
19
+ {% if hasReduced %}
20
  var rrem = reduce_linear;
21
  {% endif %}
22
+ {% for a in range(rank) if reduce[a] %}
23
  {% set rstride = namespace(value=1) %}
24
+ {% for b in range(a + 1, rank) if reduce[b] %}
25
+ {% set rstride.value = rstride.value * dataShape[b] %}
26
  {% endfor %}
27
  {% set rsafe = 1 if rstride.value == 0 else rstride.value %}
28
  let red_coord{{ a }} = rrem / {{ rsafe }}u;
29
  rrem = rrem % {{ rsafe }}u;
30
  {% endfor %}
31
  {% set oc = namespace(i=0) %}
32
+ {% for a in range(rank) %}
33
+ {% if reduce[a] %}
34
  let coord{{ a }} = red_coord{{ a }};
35
+ {% elif keepDims %}
36
  let coord{{ a }} = out_coord{{ a }};
37
  {% else %}
38
  let coord{{ a }} = out_coord{{ oc.i }};
 
40
  {% endif %}
41
  {% endfor %}
42
  {% set src = namespace(value="coord0") %}
43
+ {% for a in range(1, rank) %}
44
+ {% set src.value = "(" ~ src.value ~ " * " ~ dataShape[a] ~ "u + coord" ~ a ~ ")" %}
45
+ {% endfor %}
46
+ return {{ src.value }};
47
+ }
48
+ {%- endmacro %}
49
+ // Serial one-thread-per-output reduction for the no-feature tier. f16 storage
50
+ // is widened before every accumulation and narrowed only for the final store.
51
+ {% set castF32 = castF32 is defined and castF32 %}
52
+ {% set logicalBool = logicalBool is defined and logicalBool %}
53
+ {% set intMode = intMode is defined and intMode %}
54
+ {% set yv = "f16(" if castF32 else "" %}
55
+ {% set vy = ")" if castF32 else "" %}
56
+ {% if usesF16Spec is defined and usesF16Spec %}
57
+ enable f16;
58
+ {% endif %}
59
+ {{ env.wgsl.resourceDeclarations }}
60
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
61
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
62
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
63
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
64
+ fn {{ name }}() -> {{ scalar }} {
65
+ {% if scalar == "i32" %}
66
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
67
+ {% elif scalar == "u32" %}
68
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
69
+ {% else %}
70
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
71
+ return bitcast<f32>(bits);
72
+ {% endif %}
73
+ }
74
+ {%- endmacro %}
75
+
76
+ {% if not intMode and (op == "logsum" or op == "logsumexp") %}
77
+ fn negative_infinity() -> f32 {
78
+ var bits = 0xff800000u;
79
+ return bitcast<f32>(bits);
80
+ }
81
+
82
+ {% endif %}
83
+ {% if op == "max" or op == "min" %}
84
+
85
+ {{ wgsl_minmax_identity("empty_value", op, "f32" if castF32 else scalar) }}
86
+ {% elif op == "logsumexp" %}
87
+
88
+ const F32_MIN: f32 = -3.4028234663852886e38;
89
+ {% if not intMode %}
90
+ const F32_MAX: f32 = 3.4028234663852886e38;
91
+
92
+ fn is_nan_f32(value: f32) -> bool {
93
+ let bits = bitcast<u32>(value);
94
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
95
+ }
96
+ {% endif %}
97
+ {% endif %}
98
+ {% if indexing == "rankn" %}
99
+
100
+ fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
101
+ var rem = out_index;
102
+ {% for axis in range(outputRank) %}
103
+ {% set out_stride = namespace(value=1) %}
104
+ {% for j in range(axis + 1, outputRank) %}
105
+ {% set out_stride.value = out_stride.value * outputShape[j] %}
106
+ {% endfor %}
107
+ {% set safe_out_stride = 1 if out_stride.value == 0 else out_stride.value %}
108
+ {% if not keepDims or axis != axisSpec %}
109
+ let out_coord{{ axis }} = rem / {{ safe_out_stride }}u;
110
+ {% endif %}
111
+ rem = rem % {{ safe_out_stride }}u;
112
+ {% endfor %}
113
+ {% for axis in range(rank) %}
114
+ {% if axis == axisSpec %}
115
+ let coord{{ axis }} = reduce_index;
116
+ {% elif keepDims %}
117
+ let coord{{ axis }} = out_coord{{ axis }};
118
+ {% elif axis < axisSpec %}
119
+ let coord{{ axis }} = out_coord{{ axis }};
120
+ {% else %}
121
+ let coord{{ axis }} = out_coord{{ axis - 1 }};
122
+ {% endif %}
123
+ {% endfor %}
124
+ {% set src = namespace(value="coord0") %}
125
+ {% for axis in range(1, rank) %}
126
+ {% set src.value = "(" ~ src.value ~ " * " ~ dataShape[axis] ~ "u + coord" ~ axis ~ ")" %}
127
  {% endfor %}
128
  return {{ src.value }};
129
  }
130
  {% endif %}
131
+ {% if indexing == "multiaxis" %}
132
+ {% set hasReducedAxis = namespace(value=false) %}
133
+ {% for a in range(rank) %}{% if reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
134
+ {{- reduce_multi_axis_offset(hasReducedAxis.value) }}
135
+ {% endif %}
136
+ {% if indexing == "multiaxis" %}
137
  {% set mcount = namespace(value=1) %}
138
+ {% for a in range(rank) if reduce[a] %}
139
+ {% set mcount.value = mcount.value * dataShape[a] %}
140
  {% endfor %}
141
  {% set count = mcount.value ~ "u" %}
142
  {% if hasReducedAxis.value %}
143
  {% set at = "x[input_offset(i, r)]" %}
144
+ {% set at_first = "x[input_offset(i, 0u)]" %}
145
  {% else %}
146
  {% set at = "x[input_offset(i)]" %}
147
+ {% set at_first = "x[input_offset(i)]" %}
148
  {% endif %}
149
+ {% elif indexing == "rankn" %}
150
  {% set count = "params.axisDim" %}
151
  {% set at = "x[input_offset(i, r)]" %}
152
+ {% set at_first = "x[input_offset(i, 0u)]" %}
153
  {% elif axis == 0 %}
154
  {% set count = "params.rows" %}
155
  {% set at = "x[r * params.cols + i]" %}
156
+ {% set at_first = "x[i]" %}
157
  {% else %}
158
  {% set count = "params.cols" %}
159
  {% set at = "x[i * params.cols + r]" %}
160
+ {% set at_first = "x[i * params.cols]" %}
161
  {% endif %}
162
  {% if castF32 %}
163
  {% set at = "f32(" ~ at ~ ")" %}
164
+ {% set at_first = "f32(" ~ at_first ~ ")" %}
165
  {% endif %}
166
 
167
  @compute @workgroup_size({{ reduceWorkgroupSize }})
168
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
169
  // 2D-folded flat index: gid.y carries the high bits past the
170
+ // per-axis dispatch fold width (outputs > 16.7M elements).
171
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ reduceWorkgroupSize }}u;
172
  if (i >= params.outCount) {
173
  return;
174
  }
175
+ {% if op == "logsumexp" %}
176
+ {% if intMode %}
177
+ // Integer logsumexp widens each element for exp/log, then truncates the result
178
+ // back to the integer output type.
179
+ // Integers are never NaN, so the f32 NaN-propagation path is unnecessary here.
180
+ if ({{ count }} == 0u) {
181
+ y[i] = {{ scalar }}(F32_MIN);
182
+ return;
183
+ }
184
+ var m = F32_MIN;
185
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
186
+ m = max(m, f32({{ at }}));
187
+ }
188
+ var acc = 0.0;
189
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
190
+ acc = acc + exp(f32({{ at }}) - m);
191
+ }
192
+ y[i] = {{ scalar }}(m + log(acc));
193
+ {% else %}
194
+ if ({{ count }} == 0u) {
195
+ y[i] = {{ yv }}negative_infinity(){{ vy }};
196
+ return;
197
+ }
198
+ var m = F32_MIN;
199
+ var has_nan = false;
200
+ var nan_value = 0.0;
201
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
202
+ let value = {{ at }};
203
+ if (is_nan_f32(value)) {
204
+ has_nan = true;
205
+ nan_value = value;
206
+ } else {
207
+ m = max(m, value);
208
+ }
209
+ }
210
+ if (has_nan) {
211
+ y[i] = {{ yv }}nan_value{{ vy }};
212
+ return;
213
+ }
214
+ if (m > F32_MAX) {
215
+ y[i] = {{ yv }}m{{ vy }};
216
+ return;
217
+ }
218
+ var acc = 0.0;
219
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
220
+ acc = acc + exp({{ at }} - m);
221
+ }
222
+ y[i] = {{ yv }}m + log(acc){{ vy }};
223
+ {% endif %}
224
+ {% elif op == "max" or op == "min" %}
225
+ if ({{ count }} == 0u) {
226
+ y[i] = {{ yv }}empty_value(){{ vy }};
227
+ return;
228
+ }
229
+ var acc = {{ at_first }};
230
+ for (var r = 1u; r < {{ count }}; r = r + 1u) {
231
+ acc = {{ op }}(acc, {{ at }});
232
+ }
233
+ y[i] = {{ yv }}acc{{ vy }};
234
+ {% else %}
235
+ {% if op == "mean" %}
236
+ if ({{ count }} == 0u) {
237
+ {% if intMode %}
238
+ y[i] = {{ scalar }}(0);
239
+ {% else %}
240
+ y[i] = {{ yv }}0.0{{ vy }};
241
+ {% endif %}
242
+ return;
243
+ }
244
+ {% elif op == "logsum" %}
245
+ if ({{ count }} == 0u) {
246
+ y[i] = {{ yv }}negative_infinity(){{ vy }};
247
+ return;
248
+ }
249
+ {% endif %}
250
  {% if intMode %}
251
  // Integer reduction accumulates in the output type. sum/prod/l1/sumsquare stay
252
  // in integer arithmetic; l2/logsum widen for sqrt/log and truncate afterward.
253
+ var acc = {{ scalar }}({{ "1" if op == "prod" else "0" }});
254
  {% else %}
255
+ var acc = {{ "1.0" if op == "prod" else "0.0" }};
256
  {% endif %}
257
  for (var r = 0u; r < {{ count }}; r = r + 1u) {
258
+ {% if op == "prod" %}
259
  acc = acc * {{ at }};
260
+ {% elif op == "l1" %}
261
+ acc = acc + abs({{ at }});
262
+ {% elif op == "l2" or op == "sumsquare" %}
263
+ let value = {{ at }};
264
+ acc = acc + value * value;
265
+ {% else %}
266
+ acc = acc + {{ at }};
267
+ {% endif %}
268
  }
269
+ {% if op == "l2" %}
270
+ {% if intMode %}
271
+ y[i] = {{ scalar }}(sqrt(f32(acc)));
272
+ {% else %}
273
+ y[i] = {{ yv }}sqrt(acc){{ vy }};
274
+ {% endif %}
275
+ {% elif op == "logsum" %}
276
+ y[i] = {{ yv }}log(acc){{ vy }};
277
+ {% elif op == "mean" %}
278
+ {% if intMode %}
279
+ y[i] = acc / {{ scalar }}({{ count }});
280
+ {% else %}
281
+ y[i] = {{ yv }}acc / f32({{ count }}){{ vy }};
282
+ {% endif %}
283
+ {% else %}
284
  y[i] = {{ yv }}acc{{ vy }};
285
+ {% endif %}
286
+ {% endif %}
287
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.ReduceProd",
3
  "fixtureArrays": {
4
  "rank3_axis1_middle_keepdims_input_x": [1, -2, 0.5, 4, -3, 2, -1, 0.25, 2, -0.5, 3, -2, -1, 1.5, 2, -4, 0.5, -2, -3, 1, -2, 4, 0.25, -0.5]
5
  },
@@ -7,7 +6,7 @@
7
  {
8
  "name": "all_axes_flat_rank1_boundary_8192",
9
  "provenance": {
10
- "notes": "The parallel full-reduction threshold must supersede the rank1 serial and row-reduction fallbacks."
11
  },
12
  "attrs": { "axes": [0], "keepdims": 0 },
13
  "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
@@ -104,7 +103,7 @@
104
  "provenance": {
105
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
106
  "test": "ReductionOpTest.ReduceProd",
107
- "notes": "Axis-0 companion: normal inputs produce finite subnormal products, which must not be flushed to zero."
108
  },
109
  "attrs": { "axes": [0], "keepdims": 0 },
110
  "inputs": {
@@ -596,7 +595,7 @@
596
  "x": {
597
  "dtype": "float32",
598
  "shape": [2, 2, 1024],
599
- "data": { "kind": "cycle", "values": [1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0] }
600
  }
601
  },
602
  "outputs": { "y": { "dtype": "float32", "shape": [2, 2], "tolerance": 0.00001 } }
@@ -608,7 +607,7 @@
608
  "x": {
609
  "dtype": "int32",
610
  "shape": [2, 2, 1024],
611
- "data": { "kind": "cycle", "values": [1, -1, 1, -1, 1, -1, 1, 1] }
612
  }
613
  },
614
  "outputs": { "y": { "dtype": "int32", "shape": [2, 2], "tolerance": 0.00001 } }
@@ -702,7 +701,7 @@
702
  "provenance": {
703
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
704
  "test": "ReductionOpTest.ReduceProd",
705
- "notes": "Float edge-value coverage on the working vec4 last-axis path (cols=4, %4==0 -> subgroup_last_axis_vec4). Row0 contains 0*inf -> NaN; row1 overflows the product to +inf. Metal handles inf/NaN in arithmetic (only subnormals are FTZ-flushed), so this should PASS on GPU. Fills the no-inf/NaN-input coverage gap noted in coverage."
706
  },
707
  "attrs": { "axes": [-1], "keepdims": 0 },
708
  "inputs": {
@@ -736,7 +735,11 @@
736
  "name": "tree_lastaxis_dispatch_fold_f32_70000x64",
737
  "attrs": { "axes": [-1], "keepdims": 0 },
738
  "inputs": {
739
- "x": { "dtype": "float32", "shape": [70000, 64], "data": { "kind": "cycle", "values": [1.0, -1.0, 0.5, 2.0] } }
 
 
 
 
740
  },
741
  "outputs": { "y": { "dtype": "float32", "shape": [70000], "tolerance": 0 } }
742
  },
@@ -744,11 +747,7 @@
744
  "name": "tree_lastaxis_i32_dispatch_fold_70000x64",
745
  "attrs": { "axes": [-1], "keepdims": 0 },
746
  "inputs": {
747
- "x": {
748
- "dtype": "int32",
749
- "shape": [70000, 64],
750
- "data": { "kind": "cycle", "values": [1, -1, 1, -1, 1, -1, 1, 1] }
751
- }
752
  },
753
  "outputs": { "y": { "dtype": "int32", "shape": [70000], "tolerance": 0 } }
754
  },
@@ -765,7 +764,11 @@
765
  "attrs": { "axes": [-1], "keepdims": 0 },
766
  "requires": { "features": ["subgroups"] },
767
  "inputs": {
768
- "x": { "dtype": "float32", "shape": [16384, 64], "data": { "kind": "cycle", "values": [1.0, -1.0, 0.5, 2.0] } }
 
 
 
 
769
  },
770
  "outputs": { "y": { "dtype": "float32", "shape": [16384], "tolerance": 0 } }
771
  },
@@ -778,7 +781,7 @@
778
  {
779
  "name": "axis0_narrow_f32_8192x3_splitk_guard_lock",
780
  "provenance": {
781
- "notes": "Compact lock below the historical 16-column split-K guard. Multiplicative identity input validates partial and combine identities exactly."
782
  },
783
  "attrs": { "axes": [0], "keepdims": 0 },
784
  "inputs": { "x": { "dtype": "float32", "shape": [8192, 3], "data": { "kind": "constant", "value": 1.0 } } },
@@ -923,7 +926,10 @@
923
  "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "offset": 1.0, "scale": 0.001 }
924
  }
925
  },
926
- "outputs": { "y": { "dtype": "float16", "shape": [], "tolerance": 0.05, "relTolerance": 0.01 } }
 
 
 
927
  },
928
  {
929
  "name": "f16_suffix_vec4_4x8x128",
@@ -972,11 +978,63 @@
972
  {
973
  "name": "axis0_splitk_i32_8192x5",
974
  "provenance": {
975
- "notes": "Integer ReduceProd on the axis-0 split-K route, which ReduceProd did not have until now. A three-value cycle over five columns is coprime with the row stride, so the count of negative factors differs per column and so do the signs of the results; keeping every factor at +/-1 makes the product exact."
976
  },
977
  "attrs": { "axes": [0], "keepdims": 0 },
978
  "inputs": { "x": { "dtype": "int32", "shape": [8192, 5], "data": { "kind": "cycle", "values": [1, 1, -1] } } },
979
  "outputs": { "y": { "dtype": "int32", "shape": [5], "tolerance": 0 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
980
  }
981
  ]
982
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "rank3_axis1_middle_keepdims_input_x": [1, -2, 0.5, 4, -3, 2, -1, 0.25, 2, -0.5, 3, -2, -1, 1.5, 2, -4, 0.5, -2, -3, 1, -2, 4, 0.25, -0.5]
4
  },
 
6
  {
7
  "name": "all_axes_flat_rank1_boundary_8192",
8
  "provenance": {
9
+ "notes": "Exactly 8,192 rank-1 elements exercise the inclusive lower boundary of the parallel full reduction."
10
  },
11
  "attrs": { "axes": [0], "keepdims": 0 },
12
  "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
 
103
  "provenance": {
104
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
105
  "test": "ReductionOpTest.ReduceProd",
106
+ "notes": "An axis-0 reduction of normal inputs produces finite subnormal products that must not be flushed to zero."
107
  },
108
  "attrs": { "axes": [0], "keepdims": 0 },
109
  "inputs": {
 
595
  "x": {
596
  "dtype": "float32",
597
  "shape": [2, 2, 1024],
598
+ "data": { "kind": "cycle", "values": [-1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] }
599
  }
600
  },
601
  "outputs": { "y": { "dtype": "float32", "shape": [2, 2], "tolerance": 0.00001 } }
 
607
  "x": {
608
  "dtype": "int32",
609
  "shape": [2, 2, 1024],
610
+ "data": { "kind": "cycle", "values": [-1, -1, 1, 1, 1, 1, 1, 1, 1] }
611
  }
612
  },
613
  "outputs": { "y": { "dtype": "int32", "shape": [2, 2], "tolerance": 0.00001 } }
 
701
  "provenance": {
702
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
703
  "test": "ReductionOpTest.ReduceProd",
704
+ "notes": "Four columns exercise the vectorized last-axis reduction. Row 0 contains 0 multiplied by infinity and must produce NaN; row 1 overflows to positive infinity."
705
  },
706
  "attrs": { "axes": [-1], "keepdims": 0 },
707
  "inputs": {
 
735
  "name": "tree_lastaxis_dispatch_fold_f32_70000x64",
736
  "attrs": { "axes": [-1], "keepdims": 0 },
737
  "inputs": {
738
+ "x": {
739
+ "dtype": "float32",
740
+ "shape": [70000, 64],
741
+ "data": { "kind": "cycle", "values": [1.0, -1.0, 0.5, 2.0, -1.0] }
742
+ }
743
  },
744
  "outputs": { "y": { "dtype": "float32", "shape": [70000], "tolerance": 0 } }
745
  },
 
747
  "name": "tree_lastaxis_i32_dispatch_fold_70000x64",
748
  "attrs": { "axes": [-1], "keepdims": 0 },
749
  "inputs": {
750
+ "x": { "dtype": "int32", "shape": [70000, 64], "data": { "kind": "cycle", "values": [-1, -1, 1, 1, 1] } }
 
 
 
 
751
  },
752
  "outputs": { "y": { "dtype": "int32", "shape": [70000], "tolerance": 0 } }
753
  },
 
764
  "attrs": { "axes": [-1], "keepdims": 0 },
765
  "requires": { "features": ["subgroups"] },
766
  "inputs": {
767
+ "x": {
768
+ "dtype": "float32",
769
+ "shape": [16384, 64],
770
+ "data": { "kind": "cycle", "values": [1.0, -1.0, 0.5, 2.0, -1.0] }
771
+ }
772
  },
773
  "outputs": { "y": { "dtype": "float32", "shape": [16384], "tolerance": 0 } }
774
  },
 
781
  {
782
  "name": "axis0_narrow_f32_8192x3_splitk_guard_lock",
783
  "provenance": {
784
+ "notes": "An 8,192-by-3 axis-0 reduction exercises split-K with a narrow output. Multiplicative identity input validates partial and combine identities exactly."
785
  },
786
  "attrs": { "axes": [0], "keepdims": 0 },
787
  "inputs": { "x": { "dtype": "float32", "shape": [8192, 3], "data": { "kind": "constant", "value": 1.0 } } },
 
926
  "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "offset": 1.0, "scale": 0.001 }
927
  }
928
  },
929
+ "outputs": { "y": { "dtype": "float16", "shape": [], "tolerance": 0.01, "relTolerance": 0.01 } },
930
+ "provenance": {
931
+ "notes": "A 65,543-element float16 product over factors near 1. The tolerance is scaled to the 0.25 result so a dropped or duplicated factor is observable."
932
+ }
933
  },
934
  {
935
  "name": "f16_suffix_vec4_4x8x128",
 
978
  {
979
  "name": "axis0_splitk_i32_8192x5",
980
  "provenance": {
981
+ "notes": "An integer axis-0 split-K reduction uses a three-value cycle over five columns. The coprime cycle gives each column a different count of negative factors, while values of plus or minus one keep every product exact."
982
  },
983
  "attrs": { "axes": [0], "keepdims": 0 },
984
  "inputs": { "x": { "dtype": "int32", "shape": [8192, 5], "data": { "kind": "cycle", "values": [1, 1, -1] } } },
985
  "outputs": { "y": { "dtype": "int32", "shape": [5], "tolerance": 0 } }
986
+ },
987
+ {
988
+ "name": "subgroup_rows_last_axis_f32_96x256",
989
+ "attrs": { "axes": [-1], "keepdims": 0 },
990
+ "inputs": {
991
+ "x": {
992
+ "dtype": "float32",
993
+ "shape": [96, 256],
994
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "offset": 1.0, "scale": 0.05 }
995
+ }
996
+ },
997
+ "outputs": { "y": { "dtype": "float32", "shape": [96], "tolerance": 0.00001, "relTolerance": 0.0001 } }
998
+ },
999
+ {
1000
+ "name": "subgroup_rows_last_axis_f16_80x1024",
1001
+ "attrs": { "axes": [1], "keepdims": 0 },
1002
+ "inputs": {
1003
+ "x": {
1004
+ "dtype": "float16",
1005
+ "shape": [80, 1024],
1006
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "offset": 1.0, "scale": 0.001 }
1007
+ }
1008
+ },
1009
+ "outputs": { "y": { "dtype": "float16", "shape": [80], "tolerance": 0.05, "relTolerance": 0.01 } }
1010
+ },
1011
+ {
1012
+ "name": "int32_lastaxis_subgroup_rows_64x1024",
1013
+ "provenance": {
1014
+ "notes": "Route lock for the integer subgroup-per-row last-axis product: 64 rows of 1024 int32 values fold in the output type; a seven-value cycle of +-1 shifts phase every row, so rows alternate sign."
1015
+ },
1016
+ "attrs": { "axes": [1], "keepdims": 0 },
1017
+ "inputs": {
1018
+ "x": { "dtype": "int32", "shape": [64, 1024], "data": { "kind": "cycle", "values": [1, -1, 1, 1, -1, 1, -1] } }
1019
+ },
1020
+ "outputs": { "y": { "dtype": "int32", "shape": [64], "tolerance": 0 } }
1021
+ },
1022
+ {
1023
+ "name": "multi_axis_alternating_extremes_serial_fold_only",
1024
+ "attrs": { "axes": [0, 2, 3], "keepdims": 0 },
1025
+ "inputs": {
1026
+ "x": {
1027
+ "dtype": "float32",
1028
+ "shape": [1, 4, 16, 16],
1029
+ "data": { "kind": "cycle", "values": [1e+30, 1e-30, 1e+30, 1e-30, 1.0] }
1030
+ }
1031
+ },
1032
+ "outputs": {
1033
+ "y": { "dtype": "float32", "shape": [4], "tolerance": 0.001, "relTolerance": 0.001, "allowNaN": true }
1034
+ },
1035
+ "provenance": {
1036
+ "notes": "Alternating 1e30 and 1e-30 values remain finite under the required left-to-right fold. A pairwise tree could instead form positive infinity and zero in separate partials, whose product is NaN, so this shape requires serial fold order."
1037
+ }
1038
  }
1039
  ]
1040
  }