Xenova HF Staff commited on
Commit
ea7ecc1
·
verified ·
1 Parent(s): 3ad0db8

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `ReduceLogSumExp` spec](https://onnx.ai/onnx/operators/onnx__Reduc
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `data` | `x` | `T` | — | — | The input tensor to reduce. | required |
24
 
25
  ## Outputs
26
 
27
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
- | `reduced` | `y` | `T` | derived | — | The reduced output tensor. | 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 the reduced dimension with size 1 in the output; if 0, the reduced dimension is removed. |
38
  | `noop_with_empty_axes` | `0` | When 1 and `axes` is empty, acts as an identity (no reduction); when 0 and `axes` is empty, reduces over all axes. |
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
@@ -62,21 +62,25 @@ Some implementation variants require `subgroups`. These are route-specific capab
62
  - [`reduce-flat-partial-logsumexp.wgsl.jinja`](build/webgpu/reduce-flat-partial-logsumexp.wgsl.jinja)
63
  - [`reduce-i32-axes02.wgsl.jinja`](build/webgpu/reduce-i32-axes02.wgsl.jinja)
64
  - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
 
65
  - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
66
  - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
67
  - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
68
 
69
  ## Use with `@huggingface/kernels`
70
 
71
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
72
 
73
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
74
 
75
- - `y`
76
 
77
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
78
 
79
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
80
 
81
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
82
 
 
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 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 the reduced dimension with size 1 in the output; if 0, the reduced dimension is removed. |
39
  | `noop_with_empty_axes` | `0` | When 1 and `axes` is empty, acts as an identity (no reduction); when 0 and `axes` is empty, reduces over all axes. |
 
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
 
62
  - [`reduce-flat-partial-logsumexp.wgsl.jinja`](build/webgpu/reduce-flat-partial-logsumexp.wgsl.jinja)
63
  - [`reduce-i32-axes02.wgsl.jinja`](build/webgpu/reduce-i32-axes02.wgsl.jinja)
64
  - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
65
+ - [`reduce-row-subgroup-rows.wgsl.jinja`](build/webgpu/reduce-row-subgroup-rows.wgsl.jinja)
66
  - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
67
  - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
68
  - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
69
 
70
  ## Use with `@huggingface/kernels`
71
 
72
+ ```sh
73
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
74
+ ```
75
 
76
+ 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.
77
 
78
+ This example supplies explicit metadata for:
79
 
80
+ - `y`
81
 
82
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
83
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
84
 
85
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
86
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.ReduceLogSumExp",
3
  "cases": [
4
  {
5
  "name": "1024x1024_axis1",
@@ -105,7 +104,7 @@
105
  "name": "reducelogsumexp-spatial-axes12-f32-16x256x256-low-lane",
106
  "preset": "stress",
107
  "provenance": {
108
- "source": "authored for variant coverage",
109
  "notes": "Low-lane rank-3 contiguous-suffix reduction that verifies the vec4 subgroup reducer and no-subgroup workgroup-tree fallback."
110
  },
111
  "vars": { "batch": 16, "height": 256, "width": 256 },
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "1024x1024_axis1",
 
104
  "name": "reducelogsumexp-spatial-axes12-f32-16x256x256-low-lane",
105
  "preset": "stress",
106
  "provenance": {
107
+ "source": "repository-authored",
108
  "notes": "Low-lane rank-3 contiguous-suffix reduction that verifies the vec4 subgroup reducer and no-subgroup workgroup-tree fallback."
109
  },
110
  "vars": { "batch": 16, "height": 256, "width": 256 },
build/webgpu/manifest.json CHANGED
@@ -2,474 +2,241 @@
2
  "domain": "ai.onnx",
3
  "name": "ReduceLogSumExp",
4
  "sinceVersion": 18,
5
- "description": "Computes `log(sum(exp(x)))` over the 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 negative infinity.",
6
- "inputs": [{ "role": "data", "dtype": "T", "description": "The input tensor to reduce." }],
7
- "outputs": [
8
- {
9
- "role": "reduced",
10
  "dtype": "T",
11
- "rank": "ranks.data if attrs.keepdims == 1 or ((attrs.axes | length) == 0 and attrs.noop_with_empty_axes == 1) else (ranks.data - (attrs.axes | length) if (attrs.axes | length) > 0 else 0)",
12
- "description": "The reduced output tensor."
13
  }
14
- ],
15
- "attributes": { "keepdims": 1, "noop_with_empty_axes": 0, "axes": [] },
16
- "attributeDescriptions": {
17
- "keepdims": "If 1, retains the reduced dimension with size 1 in the output; if 0, the reduced dimension is removed.",
18
- "noop_with_empty_axes": "When 1 and `axes` is empty, acts as an identity (no reduction); when 0 and `axes` is empty, reduces over all axes.",
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": "3 * 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": "3 * 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": "numel(shapes.data) / tunables.VECTOR_WIDTH if numel(shapes.data) % tunables.VECTOR_WIDTH == 0 else numel(shapes.data)",
49
  "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
50
  "flatScratchBytes": "3 * 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
- "CONTIGUOUS_SUFFIX_MIN_COLS": 256,
71
- "AXES02_WORKGROUP_SIZE": 256,
72
- "ROW_SERIAL_MIN_ROWS": 8192,
73
- "ROW_SERIAL_MAX_COLS": 1024
74
- },
75
- "bindingSets": {
76
- "axes02": [
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": {
84
- "name": "Params",
85
- "fields": [
86
- { "name": "d0", "type": "u32", "value": "dim(shapes.data, 0)" },
87
- { "name": "d1", "type": "u32", "value": "dim(shapes.data, 1)" },
88
- { "name": "d2", "type": "u32", "value": "dim(shapes.data, 2)" },
89
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
90
- ]
91
- }
92
- }
93
- ],
94
- "suffixVec4": [
95
- {
96
- "name": "x",
97
- "arg": "x",
98
- "semantic": "data",
99
- "buffer": { "type": "read-only-storage" },
100
- "elementType": "$vectorScalar"
101
- },
102
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
103
- {
104
- "name": "params",
105
- "semantic": "kernel.params",
106
- "buffer": { "type": "uniform" },
107
- "struct": {
108
- "name": "Params",
109
- "fields": [
110
- { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
111
- {
112
- "name": "chunkCount",
113
- "type": "u32",
114
- "value": "numel(shapes.data) / numel(shapes.reduced) / tunables.VECTOR_WIDTH"
115
- }
116
- ]
117
- }
118
- }
119
- ],
120
- "suffixScalar": [
121
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
122
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
123
- {
124
- "name": "params",
125
- "semantic": "kernel.params",
126
- "buffer": { "type": "uniform" },
127
- "struct": {
128
- "name": "Params",
129
- "fields": [
130
- { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
131
- { "name": "cols", "type": "u32", "value": "numel(shapes.data) / numel(shapes.reduced)" }
132
- ]
133
- }
134
- }
135
- ],
136
- "elementwise": [
137
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
138
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
139
- {
140
- "name": "params",
141
- "semantic": "kernel.params",
142
- "buffer": { "type": "uniform" },
143
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.reduced)" }] }
144
- }
145
- ],
146
- "lastAxisVec4": [
147
- {
148
- "name": "x",
149
- "arg": "x",
150
- "semantic": "data",
151
- "buffer": { "type": "read-only-storage" },
152
- "elementType": "$vectorScalar"
153
- },
154
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
155
- {
156
- "name": "params",
157
- "semantic": "kernel.params",
158
- "buffer": { "type": "uniform" },
159
- "struct": {
160
- "name": "Params",
161
- "fields": [
162
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
163
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1) / tunables.VECTOR_WIDTH" }
164
- ]
165
- }
166
- }
167
- ],
168
- "lastAxisScalar": [
169
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
170
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
171
- {
172
- "name": "params",
173
- "semantic": "kernel.params",
174
- "buffer": { "type": "uniform" },
175
- "struct": {
176
- "name": "Params",
177
- "fields": [
178
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
179
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
180
- ]
181
- }
182
- }
183
- ],
184
- "lastAxisScalarSubgroup": [
185
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
186
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
187
- {
188
- "name": "params",
189
- "semantic": "kernel.params",
190
- "buffer": { "type": "uniform" },
191
- "struct": {
192
- "name": "Params",
193
- "fields": [
194
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
195
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
196
- ]
197
- }
198
- }
199
- ],
200
- "scalar": [
201
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
202
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
203
- {
204
- "name": "params",
205
- "semantic": "kernel.params",
206
- "buffer": { "type": "uniform" },
207
- "struct": {
208
- "name": "Params",
209
- "fields": [
210
- { "name": "rows", "type": "u32", "value": "1" },
211
- { "name": "cols", "type": "u32", "value": "1" },
212
- { "name": "outCount", "type": "u32", "value": "1" }
213
- ]
214
- }
215
- }
216
- ],
217
- "rank1Axis0": [
218
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
219
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
220
- {
221
- "name": "params",
222
- "semantic": "kernel.params",
223
- "buffer": { "type": "uniform" },
224
- "struct": {
225
- "name": "Params",
226
- "fields": [
227
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
228
- { "name": "cols", "type": "u32", "value": "1" },
229
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
230
- ]
231
- }
232
- }
233
- ],
234
- "rank2Serial": [
235
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
236
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
237
- {
238
- "name": "params",
239
- "semantic": "kernel.params",
240
- "buffer": { "type": "uniform" },
241
- "struct": {
242
- "name": "Params",
243
- "fields": [
244
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
245
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
246
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
247
- ]
248
- }
249
- }
250
- ],
251
- "rank2SerialAxis1": [
252
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
253
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
254
- {
255
- "name": "params",
256
- "semantic": "kernel.params",
257
- "buffer": { "type": "uniform" },
258
- "struct": {
259
- "name": "Params",
260
- "fields": [
261
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
262
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
263
- ]
264
- }
265
- }
266
- ],
267
- "axis0Parallel": [
268
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
269
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
270
- {
271
- "name": "params",
272
- "semantic": "kernel.params",
273
- "buffer": { "type": "uniform" },
274
- "struct": {
275
- "name": "Params",
276
- "fields": [
277
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
278
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
279
- ]
280
- }
281
- }
282
- ],
283
- "fullReduceSerial": [
284
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
285
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
286
- {
287
- "name": "params",
288
- "semantic": "kernel.params",
289
- "buffer": { "type": "uniform" },
290
- "struct": {
291
- "name": "Params",
292
- "fields": [
293
- { "name": "rows", "type": "u32", "value": "numel(shapes.data)" },
294
- { "name": "cols", "type": "u32", "value": "1" },
295
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
296
- ]
297
- }
298
- }
299
- ],
300
- "axisSplitReduce": [
301
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
302
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
303
- {
304
- "name": "params",
305
- "semantic": "kernel.params",
306
- "buffer": { "type": "uniform" },
307
- "struct": {
308
- "name": "Params",
309
- "fields": [
310
- { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
311
- { "name": "inner", "type": "u32", "value": "axisSplitInner" },
312
- { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
313
- ]
314
- }
315
- }
316
- ],
317
- "axisSplitCombine": [
318
- {
319
- "name": "partials",
320
- "semantic": "partials",
321
- "buffer": { "type": "read-only-storage" },
322
- "elementType": "$partialElement"
323
- },
324
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
325
- {
326
- "name": "params",
327
- "semantic": "kernel.params",
328
- "buffer": { "type": "uniform" },
329
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }] }
330
- }
331
- ],
332
- "axis0SplitReduce": [
333
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
334
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
335
- {
336
- "name": "params",
337
- "semantic": "kernel.params",
338
- "buffer": { "type": "uniform" },
339
- "struct": {
340
- "name": "Params",
341
- "fields": [
342
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
343
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
344
- ]
345
- }
346
- }
347
- ],
348
- "axis0SplitCombine": [
349
- {
350
- "name": "partials",
351
- "semantic": "partials",
352
- "buffer": { "type": "read-only-storage" },
353
- "elementType": "$partialElement"
354
- },
355
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
356
- {
357
- "name": "params",
358
- "semantic": "kernel.params",
359
- "buffer": { "type": "uniform" },
360
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }] }
361
- }
362
- ],
363
- "rankNAxis": [
364
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
365
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
366
- {
367
- "name": "params",
368
- "semantic": "kernel.params",
369
- "buffer": { "type": "uniform" },
370
- "struct": {
371
- "name": "Params",
372
- "fields": [
373
- { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
374
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
375
- ]
376
- }
377
- }
378
- ],
379
- "flatPartialF32": [
380
- {
381
- "name": "x",
382
- "arg": "x",
383
- "semantic": "data",
384
- "buffer": { "type": "read-only-storage" },
385
- "elementType": "$flatScalar"
386
- },
387
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "f32" },
388
- {
389
- "name": "params",
390
- "semantic": "kernel.params",
391
- "buffer": { "type": "uniform" },
392
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "flatItems" }] }
393
- }
394
- ],
395
- "flatCombineF32": [
396
- { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
397
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
398
- {
399
- "name": "params",
400
- "semantic": "kernel.params",
401
- "buffer": { "type": "uniform" },
402
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "1" }] }
403
- }
404
- ],
405
- "multiAxis": [
406
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
407
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
408
- {
409
- "name": "params",
410
- "semantic": "kernel.params",
411
- "buffer": { "type": "uniform" },
412
- "struct": {
413
- "name": "Params",
414
- "fields": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }]
415
- }
416
- }
417
- ]
418
  },
419
  "variants": [
420
  {
421
  "id": "contiguous_suffix_subgroup_vec4",
422
  "priority": 30,
 
423
  "requires": { "features": ["subgroups"] },
424
- "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0"],
425
- "constants": {
426
  "scalar": "dtypes.T",
427
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
428
- "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH))))"
429
  },
430
  "passes": [
431
  {
432
  "id": "main",
433
  "name": "ReduceLogSumExp.ContiguousSuffixSubgroupVec4",
434
- "source": {
435
- "shader": "reduce-row-subgroup.wgsl.jinja",
436
- "inputs": {
437
- "op": "\"logsumexp\"",
438
- "vec4": true,
439
- "castF32": "dtypes.T == \"f16\"",
440
- "usesF16": "dtypes.T == \"f16\""
441
- }
442
  },
443
- "subgroupCollectivesWidth": "portable",
444
- "bindings": "suffixVec4",
445
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
446
  }
447
  ]
448
  },
449
  {
450
  "id": "contiguous_suffix_tree_vec4",
451
  "priority": 22,
452
- "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
453
- "constants": {
454
  "scalar": "dtypes.T",
455
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
456
- "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH)))"
457
  },
458
  "passes": [
459
  {
460
  "id": "main",
461
  "name": "ReduceLogSumExp.ContiguousSuffixTreeVec4",
462
- "source": {
463
- "shader": "reduce-row-tree.wgsl.jinja",
464
- "inputs": {
465
- "op": "\"logsumexp\"",
466
- "vec4": true,
467
- "castF32": "dtypes.T == \"f16\"",
468
- "usesF16": "dtypes.T == \"f16\""
469
- }
470
  },
471
- "bindings": "suffixVec4",
472
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
473
  }
474
  ]
475
  },
@@ -477,123 +244,172 @@
477
  "id": "contiguous_suffix_tree",
478
  "priority": 21,
479
  "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
480
- "constants": {
481
- "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.data) / numel(shapes.reduced)))",
482
  "scalar": "dtypes.T"
483
  },
484
  "passes": [
485
  {
486
  "id": "main",
487
  "name": "ReduceLogSumExp.ContiguousSuffixTree",
488
- "source": {
489
- "shader": "reduce-row-tree.wgsl.jinja",
490
- "inputs": { "op": "\"logsumexp\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
491
- },
492
- "bindings": "suffixScalar",
493
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
494
  }
495
  ]
496
  },
497
  {
498
  "id": "multi_axis_rank3",
499
  "priority": 8,
500
- "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)"],
 
501
  "passes": [
502
  {
503
  "id": "main",
504
  "name": "ReduceLogSumExp.MultiAxisRank3",
505
- "source": {
506
- "shader": "reduce-serial-axis.wgsl.jinja",
507
- "inputs": {
508
- "op": "\"logsumexp\"",
509
- "indexing": "\"multiaxis\"",
510
- "rank": 3,
511
- "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
512
- "dataShape": "shapes.data",
513
- "outputShape": "shapes.reduced",
514
- "outputRank": "ranks.reduced",
515
- "keepDims": "attrs.keepdims != 0",
516
- "intMode": "dtypes.T == \"i32\"",
517
- "castF32": "dtypes.T == \"f16\"",
518
- "usesF16": "dtypes.T == \"f16\""
519
- }
520
  },
521
- "bindings": "multiAxis",
522
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
523
  }
524
- ],
525
- "constants": { "scalar": "dtypes.T" }
526
  },
527
  {
528
  "id": "multi_axis_rank4",
529
  "priority": 8,
530
- "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))"],
 
531
  "passes": [
532
  {
533
  "id": "main",
534
  "name": "ReduceLogSumExp.MultiAxisRank4",
535
- "source": {
536
- "shader": "reduce-serial-axis.wgsl.jinja",
537
- "inputs": {
538
- "op": "\"logsumexp\"",
539
- "indexing": "\"multiaxis\"",
540
- "rank": 4,
541
- "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
542
- "dataShape": "shapes.data",
543
- "outputShape": "shapes.reduced",
544
- "outputRank": "ranks.reduced",
545
- "keepDims": "attrs.keepdims != 0",
546
- "intMode": "dtypes.T == \"i32\"",
547
- "castF32": "dtypes.T == \"f16\"",
548
- "usesF16": "dtypes.T == \"f16\""
549
- }
550
  },
551
- "bindings": "multiAxis",
552
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
553
  }
554
- ],
555
- "constants": { "scalar": "dtypes.T" }
556
  },
557
  {
558
  "id": "int32_rank3_axes02_keepdims",
559
  "priority": 30,
560
- "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"],
 
 
 
561
  "passes": [
562
  {
563
  "id": "main",
564
  "name": "ReduceLogSumExp.Int32Rank3Axes02Keepdims",
565
- "bindings": "axes02",
566
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "axes02WorkgroupSize" },
567
- "source": {
568
- "shader": "reduce-i32-axes02.wgsl.jinja",
569
- "inputs": { "op": "\"logsumexp\"", "workgroupSize": "axes02WorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  }
571
  }
572
- ],
573
- "derive": {
574
- "axes02WorkgroupSize": "min(tunables.AXES02_WORKGROUP_SIZE, device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)"
575
- }
576
  },
577
  {
578
  "id": "noop_empty_axes",
579
  "priority": 40,
580
- "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.data, shapes.reduced)"],
 
581
  "passes": [
582
  {
583
  "id": "main",
584
  "name": "ReduceLogSumExp.NoopEmptyAxes",
585
- "source": { "shader": "reduce-noop-empty-axes.wgsl.jinja", "inputs": { "op": "\"identity\"" } },
586
- "bindings": "elementwise",
587
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  }
589
  ]
590
  },
591
  {
592
  "id": "tree_last_axis_vec4",
593
  "priority": 23,
 
594
  "demoteWhen": ["rowSerialPreferred"],
595
- "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "attrs.noop_with_empty_axes == 0", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "lastAxisCols % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
596
- "constants": {
597
  "scalar": "dtypes.T",
598
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
599
  "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
@@ -602,135 +418,137 @@
602
  {
603
  "id": "main",
604
  "name": "ReduceLogSumExp.TreeRowVec4",
605
- "source": {
606
- "shader": "reduce-row-tree.wgsl.jinja",
607
- "inputs": {
608
- "op": "\"logsumexp\"",
609
- "vec4": true,
610
- "castF32": "dtypes.T == \"f16\"",
611
- "usesF16": "dtypes.T == \"f16\""
612
- }
613
  },
614
- "bindings": "lastAxisVec4",
615
- "dispatch": { "workgroups": "lastAxisRows" }
616
  }
617
  ]
618
  },
619
  {
620
  "id": "rank0_scalar",
621
  "priority": 40,
622
- "constants": { "axis": 0, "scalar": "dtypes.T" },
623
- "when": ["f16Ok(dtypes.T)", "ranks.data == 0", "ranks.reduced == 0"],
624
  "passes": [
625
  {
626
  "id": "main",
627
  "name": "ReduceLogSumExp.Rank0Scalar",
628
- "source": {
629
- "shader": "reduce-serial-axis.wgsl.jinja",
630
- "inputs": {
631
- "op": "\"logsumexp\"",
632
- "indexing": "\"axis2d\"",
633
- "intMode": "dtypes.T == \"i32\"",
634
- "castF32": "dtypes.T == \"f16\"",
635
- "usesF16": "dtypes.T == \"f16\"",
636
- "logicalBool": "tensorDtypes.data == \"bool\""
637
- }
638
  },
639
- "bindings": "scalar",
640
  "dispatch": { "x": 1 }
641
  }
642
  ]
643
  },
644
  {
645
  "id": "rank1_axis0",
646
- "constants": { "axis": 0, "scalar": "dtypes.T" },
647
- "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))"],
648
  "passes": [
649
  {
650
  "id": "main",
651
  "name": "ReduceLogSumExp.Rank1Axis0",
652
- "source": {
653
- "shader": "reduce-serial-axis.wgsl.jinja",
654
- "inputs": {
655
- "op": "\"logsumexp\"",
656
- "indexing": "\"axis2d\"",
657
- "intMode": "dtypes.T == \"i32\"",
658
- "castF32": "dtypes.T == \"f16\"",
659
- "usesF16": "dtypes.T == \"f16\"",
660
- "logicalBool": "tensorDtypes.data == \"bool\""
661
- }
662
  },
663
- "bindings": "rank1Axis0",
664
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
665
  }
666
  ]
667
  },
668
  {
669
  "id": "axis1_parallel",
670
  "priority": 20,
 
671
  "demoteWhen": ["rowSerialPreferred"],
672
- "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 2", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "treeWorkgroupOk"],
673
- "constants": { "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.data, ranks.data - 1)))" },
674
  "passes": [
675
  {
676
  "id": "main",
677
  "name": "ReduceLogSumExp.Axis1Parallel",
678
- "source": {
679
- "shader": "reduce-row-tree.wgsl.jinja",
680
- "inputs": { "op": "\"logsumexp\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
681
- },
682
- "bindings": "lastAxisScalar",
683
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
 
 
684
  }
685
  ]
686
  },
687
  {
688
  "id": "axis_split",
689
  "priority": 24,
690
- "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"],
691
- "derive": { "splitCount": "axisSplitCount" },
692
- "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
 
 
 
 
693
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[3 * splitCount * axisSplitOutputs]" }],
694
  "passes": [
695
  {
696
  "id": "split_reduce",
697
  "name": "ReduceLogSumExp.AxisSplitReduce",
698
- "source": {
699
- "shader": "reduce-axis-split-reduce.wgsl.jinja",
700
- "inputs": {
701
- "op": "\"logsumexp\"",
702
- "workgroupSize": "reduceWorkgroupSize",
703
- "split": "splitCount",
704
- "castF32": "dtypes.T == \"f16\"",
705
- "usesF16": "dtypes.T == \"f16\""
706
- }
707
  },
708
- "bindings": "axisSplitReduce",
709
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
 
 
 
 
710
  },
711
  {
712
  "id": "combine",
713
  "name": "ReduceLogSumExp.AxisSplitCombine",
714
- "source": {
715
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
716
- "inputs": {
717
- "op": "\"logsumexp\"",
718
- "workgroupSize": "reduceWorkgroupSize",
719
- "split": "splitCount",
720
- "outputF16": "dtypes.T == \"f16\""
721
- }
722
- },
723
- "bindings": "axisSplitCombine",
724
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
725
  }
726
  ]
727
  },
728
  {
729
  "id": "axis_split_tiled_narrow",
730
  "priority": 25,
731
- "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"],
732
- "derive": { "splitCount": "axisSplitCount" },
733
- "constants": {
734
  "partialElement": "\"f32\"",
735
  "scalar": "dtypes.T",
736
  "workgroupSize": "reduceWorkgroupSize",
@@ -742,130 +560,136 @@
742
  {
743
  "id": "split_reduce",
744
  "name": "ReduceLogSumExp.AxisSplitTiledReduce",
745
- "source": {
746
- "shader": "reduce-axis0-tilecols.wgsl.jinja",
747
- "inputs": {
748
- "op": "\"logsumexp\"",
749
- "workgroupSize": "reduceWorkgroupSize",
750
- "split": "splitCount",
751
- "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
752
- "castF32": "dtypes.T == \"f16\"",
753
- "usesF16": "dtypes.T == \"f16\""
754
- }
755
  },
756
- "bindings": "axisSplitReduce",
757
- "dispatch": { "workgroups": "ceilDiv((axisSplitOutputs), (constants.tileCols))", "y": "splitCount" }
 
 
 
 
758
  },
759
  {
760
  "id": "combine",
761
  "name": "ReduceLogSumExp.AxisSplitCombine",
762
- "source": {
763
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
764
- "inputs": {
765
- "op": "\"logsumexp\"",
766
- "workgroupSize": "reduceWorkgroupSize",
767
- "split": "splitCount",
768
- "outputF16": "dtypes.T == \"f16\""
769
- }
770
- },
771
- "bindings": "axisSplitCombine",
772
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
773
  }
774
  ]
775
  },
776
  {
777
  "id": "axis0_splitk",
778
  "priority": 22,
779
- "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"],
780
- "derive": { "splitCount": "axis0SplitCount" },
781
- "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
782
- "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[3 * splitCount * dim(shapes.data, 1)]" }],
 
 
 
 
783
  "passes": [
784
  {
785
  "id": "split_reduce",
786
  "name": "ReduceLogSumExp.Axis0SplitKReduce",
787
- "source": {
788
- "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
789
- "inputs": {
790
- "op": "\"logsumexp\"",
791
- "workgroupSize": "reduceWorkgroupSize",
792
- "split": "splitCount",
793
- "castF32": "dtypes.T == \"f16\"",
794
- "usesF16": "dtypes.T == \"f16\""
795
- }
796
  },
797
- "bindings": "axis0SplitReduce",
798
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
 
 
 
 
799
  },
800
  {
801
  "id": "combine",
802
  "name": "ReduceLogSumExp.Axis0SplitKCombine",
803
- "source": {
804
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
805
- "inputs": {
806
- "op": "\"logsumexp\"",
807
- "workgroupSize": "reduceWorkgroupSize",
808
- "split": "splitCount",
809
- "outputF16": "dtypes.T == \"f16\""
810
- }
811
- },
812
- "bindings": "axis0SplitCombine",
813
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize" }
814
  }
815
  ]
816
  },
817
  {
818
  "id": "axis0_tilecols",
819
  "priority": 20,
820
- "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "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"],
821
- "constants": { "workgroupSize": "reduceWorkgroupSize", "tileCols": "tunables.AXIS0_TILE_COLS" },
822
  "passes": [
823
  {
824
  "id": "main",
825
  "name": "ReduceLogSumExp.Axis0TileCols",
826
- "source": {
827
- "shader": "reduce-axis0-tilecols.wgsl.jinja",
828
- "inputs": { "op": "\"logsumexp\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
829
- },
830
- "bindings": "axis0Parallel",
831
- "dispatch": { "workgroups": "ceilDiv((dim(shapes.data, 1)), (constants.tileCols))" }
 
 
832
  }
833
  ]
834
  },
835
  {
836
  "id": "all_axes_flat",
837
  "priority": 31,
838
- "constants": {
 
839
  "scalar": "dtypes.T",
840
  "workgroupSize": "reduceWorkgroupSize",
841
- "flatScalar": "\"vec4<\" ~ dtypes.T ~ \">\" if numel(shapes.data) % tunables.VECTOR_WIDTH == 0 else dtypes.T",
842
  "split": "flatSplitCount"
843
  },
844
- "when": ["flatParallelCovered"],
845
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[3 * flatSplitCount]" }],
846
  "passes": [
847
  {
848
  "id": "flat_partial",
849
  "name": "ReduceLogSumExp.AllAxesFlatPartial",
850
- "source": {
851
- "shader": "reduce-flat-partial-logsumexp.wgsl.jinja",
852
- "inputs": {
853
- "vec4": "numel(shapes.data) % tunables.VECTOR_WIDTH == 0",
854
- "castF32": "dtypes.T == \"f16\"",
855
- "usesF16": "dtypes.T == \"f16\""
856
- }
857
  },
858
- "bindings": "flatPartialF32",
 
 
 
 
859
  "dispatch": { "x": "flatSplitCount" }
860
  },
861
  {
862
  "id": "combine",
863
  "name": "ReduceLogSumExp.AllAxesFlatCombine",
864
- "source": {
865
- "shader": "reduce-flat-combine-logsumexp.wgsl.jinja",
866
- "inputs": { "outputF16": "dtypes.T == \"f16\"" }
867
- },
868
- "bindings": "flatCombineF32",
 
 
869
  "dispatch": { "x": 1 }
870
  }
871
  ]
@@ -873,40 +697,42 @@
873
  {
874
  "id": "rankn_single_axis_generic",
875
  "priority": 12,
 
876
  "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4"],
877
- "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))"],
878
  "passes": [
879
  {
880
  "id": "main",
881
  "name": "ReduceLogSumExp.RankNSingleAxisGeneric",
882
- "source": {
883
- "shader": "reduce-serial-axis.wgsl.jinja",
884
- "inputs": {
885
- "op": "\"logsumexp\"",
886
- "indexing": "\"rankn\"",
887
- "rank": "ranks.data",
888
- "axis": "reduceAxis",
889
- "dataShape": "shapes.data",
890
- "outputShape": "shapes.reduced",
891
- "outputRank": "ranks.reduced",
892
- "keepDims": "attrs.keepdims != 0",
893
- "intMode": "dtypes.T == \"i32\"",
894
- "castF32": "dtypes.T == \"f16\"",
895
- "usesF16": "dtypes.T == \"f16\""
896
- }
897
  },
898
- "bindings": "rankNAxis",
899
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
900
  }
901
- ],
902
- "constants": { "scalar": "dtypes.T" }
903
  },
904
  {
905
  "id": "subgroup_last_axis_vec4",
906
  "priority": 25,
 
907
  "requires": { "features": ["subgroups"] },
908
- "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "dim(shapes.data, ranks.data - 1) >= 4", "dim(shapes.data, ranks.data - 1) % tunables.VECTOR_WIDTH == 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "not rowSerialPreferred"],
909
- "constants": {
910
  "scalar": "dtypes.T",
911
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
912
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
@@ -915,27 +741,29 @@
915
  {
916
  "id": "main",
917
  "name": "ReduceLogSumExp.SubgroupRowVec4",
918
- "source": {
919
- "shader": "reduce-row-subgroup.wgsl.jinja",
920
- "inputs": {
921
- "op": "\"logsumexp\"",
922
- "vec4": true,
923
- "castF32": "dtypes.T == \"f16\"",
924
- "usesF16": "dtypes.T == \"f16\""
925
- }
 
 
 
 
926
  },
927
- "subgroupCollectivesWidth": "portable",
928
- "bindings": "lastAxisVec4",
929
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
930
  }
931
  ]
932
  },
933
  {
934
  "id": "subgroup_last_axis",
935
  "priority": 24,
 
936
  "requires": { "features": ["subgroups"] },
937
- "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.data >= 1", "reduceAxis == ranks.data - 1", "numel(shapes.reduced) == rows(shapes.data, ranks.data - 1)", "dim(shapes.data, ranks.data - 1) > 0", "dim(shapes.data, ranks.data - 1) % tunables.VECTOR_WIDTH != 0", "(lastAxisCols >= tunables.SUBGROUP_MIN_COLS or lastAxisRows < tunables.SUBGROUP_SMALL_ROW_LIMIT)", "not rowSerialPreferred"],
938
- "constants": {
939
  "scalar": "dtypes.T",
940
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
941
  },
@@ -943,117 +771,149 @@
943
  {
944
  "id": "main",
945
  "name": "ReduceLogSumExp.SubgroupRow",
946
- "source": {
947
- "shader": "reduce-row-subgroup.wgsl.jinja",
948
- "inputs": {
949
- "op": "\"logsumexp\"",
950
- "vec4": false,
951
- "castF32": "dtypes.T == \"f16\"",
952
- "usesF16": "dtypes.T == \"f16\""
953
- }
954
  },
955
- "subgroupCollectivesWidth": "portable",
956
- "bindings": "lastAxisScalarSubgroup",
957
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
 
 
 
 
958
  }
959
  ]
960
  },
961
  {
962
  "id": "axis0",
963
  "priority": 0,
 
964
  "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
965
- "constants": { "axis": 0, "scalar": "dtypes.T" },
966
- "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)))"],
967
  "passes": [
968
  {
969
  "id": "main",
970
  "name": "axis0",
971
- "source": {
972
- "shader": "reduce-serial-axis.wgsl.jinja",
973
- "inputs": {
974
- "op": "\"logsumexp\"",
975
- "indexing": "\"axis2d\"",
976
- "intMode": "dtypes.T == \"i32\"",
977
- "castF32": "dtypes.T == \"f16\"",
978
- "usesF16": "dtypes.T == \"f16\""
979
- }
980
  },
981
- "bindings": "rank2Serial",
982
- "constants": { "axis": 0 },
983
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
984
  }
985
  ]
986
  },
987
  {
988
  "id": "axis1",
989
  "priority": 0,
990
- "constants": { "axis": 1, "scalar": "dtypes.T" },
991
- "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))"],
992
  "passes": [
993
  {
994
  "id": "main",
995
  "name": "axis1",
996
- "source": {
997
- "shader": "reduce-serial-axis.wgsl.jinja",
998
- "inputs": {
999
- "op": "\"logsumexp\"",
1000
- "indexing": "\"axis2d\"",
1001
- "intMode": "dtypes.T == \"i32\"",
1002
- "castF32": "dtypes.T == \"f16\"",
1003
- "usesF16": "dtypes.T == \"f16\""
1004
- }
1005
  },
1006
- "bindings": "rank2SerialAxis1",
1007
- "constants": { "axis": 1 },
1008
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
1009
  }
1010
  ]
1011
  },
1012
  {
1013
  "id": "all_axes_keepdims",
1014
  "priority": 30,
1015
- "constants": { "axis": 0, "scalar": "dtypes.T" },
1016
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 1", "ranks.reduced == ranks.data", "numel(shapes.reduced) == 1"],
1017
  "passes": [
1018
  {
1019
  "id": "main",
1020
  "name": "ReduceLogSumExp.Rank3AllAxesKeepdims",
1021
- "source": {
1022
- "shader": "reduce-serial-axis.wgsl.jinja",
1023
- "inputs": {
1024
- "op": "\"logsumexp\"",
1025
- "indexing": "\"axis2d\"",
1026
- "intMode": "dtypes.T == \"i32\"",
1027
- "castF32": "dtypes.T == \"f16\"",
1028
- "usesF16": "dtypes.T == \"f16\""
1029
- }
1030
  },
1031
- "bindings": "fullReduceSerial",
1032
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1033
  }
1034
  ]
1035
  },
1036
  {
1037
  "id": "all_axes_no_keepdims",
1038
  "priority": 30,
1039
- "constants": { "axis": 0, "scalar": "dtypes.T" },
1040
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 0", "attrs.noop_with_empty_axes == 0", "ranks.reduced == 0"],
1041
  "passes": [
1042
  {
1043
  "id": "main",
1044
  "name": "ReduceLogSumExp.Rank3AllAxesNoKeepdims",
1045
- "source": {
1046
- "shader": "reduce-serial-axis.wgsl.jinja",
1047
- "inputs": {
1048
- "op": "\"logsumexp\"",
1049
- "indexing": "\"axis2d\"",
1050
- "intMode": "dtypes.T == \"i32\"",
1051
- "castF32": "dtypes.T == \"f16\"",
1052
- "usesF16": "dtypes.T == \"f16\""
1053
- }
1054
  },
1055
- "bindings": "fullReduceSerial",
1056
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
  }
1058
  ]
1059
  }
 
2
  "domain": "ai.onnx",
3
  "name": "ReduceLogSumExp",
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
+ "CONTIGUOUS_SUFFIX_MIN_COLS": { "default": 256 },
32
+ "AXES02_WORKGROUP_SIZE": { "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": "3 * 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": "3 * 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": "numel(shapes.x) / tunables.VECTOR_WIDTH if numel(shapes.x) % tunables.VECTOR_WIDTH == 0 else numel(shapes.x)",
58
  "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
59
  "flatScratchBytes": "3 * 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": "outCount", "type": "u32", "value": "numel(shapes.y)" }]
87
+ },
88
+ "params_5": {
89
+ "name": "params",
90
+ "buffer": "uniform",
91
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
92
+ },
93
+ "params_6": {
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_7": {
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_8": {
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
+ "params_9": {
120
+ "name": "params",
121
+ "buffer": "uniform",
122
+ "struct": [
123
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
124
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
125
+ ]
126
+ },
127
+ "partials": { "buffer": "storage", "elementType": "$partialElement" },
128
+ "params_10": {
129
+ "name": "params",
130
+ "buffer": "uniform",
131
+ "struct": [
132
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
133
+ { "name": "inner", "type": "u32", "value": "axisSplitInner" },
134
+ { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
135
+ ]
136
+ },
137
+ "partials_2": { "name": "partials", "buffer": "read-only-storage", "elementType": "$partialElement" },
138
+ "params_11": {
139
+ "name": "params",
140
+ "buffer": "uniform",
141
+ "struct": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }]
142
+ },
143
+ "params_12": {
144
+ "name": "params",
145
+ "buffer": "uniform",
146
+ "struct": [
147
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
148
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }
149
+ ]
150
+ },
151
+ "params_13": {
152
+ "name": "params",
153
+ "buffer": "uniform",
154
+ "struct": [{ "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }]
155
+ },
156
+ "params_16": {
157
+ "name": "params",
158
+ "buffer": "uniform",
159
+ "struct": [
160
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
161
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
162
+ ]
163
+ },
164
+ "params_17": {
165
+ "name": "params",
166
+ "buffer": "uniform",
167
+ "struct": [
168
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
169
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
170
+ ]
171
+ },
172
+ "params_18": {
173
+ "name": "params",
174
+ "buffer": "uniform",
175
+ "struct": [
176
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
177
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
178
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
179
+ ]
180
+ },
181
+ "params_19": {
182
+ "name": "params",
183
+ "buffer": "uniform",
184
+ "struct": [
185
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
186
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
187
+ ]
188
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  },
190
  "variants": [
191
  {
192
  "id": "contiguous_suffix_subgroup_vec4",
193
  "priority": 30,
194
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.x) / numel(shapes.y)) % tunables.VECTOR_WIDTH == 0"],
195
  "requires": { "features": ["subgroups"] },
196
+ "derive": {
 
197
  "scalar": "dtypes.T",
198
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
199
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.x) / numel(shapes.y), tunables.VECTOR_WIDTH))))"
200
  },
201
  "passes": [
202
  {
203
  "id": "main",
204
  "name": "ReduceLogSumExp.ContiguousSuffixSubgroupVec4",
205
+ "shader": "reduce-row-subgroup.wgsl.jinja",
206
+ "derive": {
207
+ "op": "\"logsumexp\"",
208
+ "vec4": true,
209
+ "castF32": "dtypes.T == \"f16\"",
210
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
211
  },
212
+ "bindings": ["x", "y", "params"],
213
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 },
214
+ "subgroupCollectivesWidth": "portable"
215
  }
216
  ]
217
  },
218
  {
219
  "id": "contiguous_suffix_tree_vec4",
220
  "priority": 22,
221
+ "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.x) / numel(shapes.y)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
222
+ "derive": {
223
  "scalar": "dtypes.T",
224
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
225
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.x) / numel(shapes.y), tunables.VECTOR_WIDTH)))"
226
  },
227
  "passes": [
228
  {
229
  "id": "main",
230
  "name": "ReduceLogSumExp.ContiguousSuffixTreeVec4",
231
+ "shader": "reduce-row-tree.wgsl.jinja",
232
+ "derive": {
233
+ "op": "\"logsumexp\"",
234
+ "vec4": true,
235
+ "castF32": "dtypes.T == \"f16\"",
236
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
237
  },
238
+ "bindings": ["x", "y", "params"],
239
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
240
  }
241
  ]
242
  },
 
244
  "id": "contiguous_suffix_tree",
245
  "priority": 21,
246
  "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
247
+ "derive": {
248
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.x) / numel(shapes.y)))",
249
  "scalar": "dtypes.T"
250
  },
251
  "passes": [
252
  {
253
  "id": "main",
254
  "name": "ReduceLogSumExp.ContiguousSuffixTree",
255
+ "shader": "reduce-row-tree.wgsl.jinja",
256
+ "derive": { "op": "\"logsumexp\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
257
+ "bindings": ["x_2", "y", "params_2"],
258
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
 
 
259
  }
260
  ]
261
  },
262
  {
263
  "id": "multi_axis_rank3",
264
  "priority": 8,
265
+ "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)"],
266
+ "derive": { "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
267
  "passes": [
268
  {
269
  "id": "main",
270
  "name": "ReduceLogSumExp.MultiAxisRank3",
271
+ "shader": "reduce-serial-axis.wgsl.jinja",
272
+ "derive": {
273
+ "op": "\"logsumexp\"",
274
+ "indexing": "\"multiaxis\"",
275
+ "rank": 3,
276
+ "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
277
+ "dataShape": "shapes.x",
278
+ "outputShape": "shapes.y",
279
+ "outputRank": "ranks.y",
280
+ "keepDims": "attrs.keepdims != 0",
281
+ "intMode": "dtypes.T == \"i32\"",
282
+ "castF32": "dtypes.T == \"f16\"",
283
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
284
  },
285
+ "bindings": ["x_2", "y", "params_3"],
286
+ "dispatch": {
287
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
288
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
289
+ "z": 1
290
+ }
291
  }
292
+ ]
 
293
  },
294
  {
295
  "id": "multi_axis_rank4",
296
  "priority": 8,
297
+ "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))"],
298
+ "derive": { "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
299
  "passes": [
300
  {
301
  "id": "main",
302
  "name": "ReduceLogSumExp.MultiAxisRank4",
303
+ "shader": "reduce-serial-axis.wgsl.jinja",
304
+ "derive": {
305
+ "op": "\"logsumexp\"",
306
+ "indexing": "\"multiaxis\"",
307
+ "rank": 4,
308
+ "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
309
+ "dataShape": "shapes.x",
310
+ "outputShape": "shapes.y",
311
+ "outputRank": "ranks.y",
312
+ "keepDims": "attrs.keepdims != 0",
313
+ "intMode": "dtypes.T == \"i32\"",
314
+ "castF32": "dtypes.T == \"f16\"",
315
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
316
  },
317
+ "bindings": ["x_2", "y", "params_3"],
318
+ "dispatch": {
319
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
320
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
321
+ "z": 1
322
+ }
323
  }
324
+ ]
 
325
  },
326
  {
327
  "id": "int32_rank3_axes02_keepdims",
328
  "priority": 30,
329
+ "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"],
330
+ "derive": {
331
+ "axes02WorkgroupSize": "min(tunables.AXES02_WORKGROUP_SIZE, device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)"
332
+ },
333
  "passes": [
334
  {
335
  "id": "main",
336
  "name": "ReduceLogSumExp.Int32Rank3Axes02Keepdims",
337
+ "shader": "reduce-i32-axes02.wgsl.jinja",
338
+ "derive": { "op": "\"logsumexp\"", "workgroupSizeSpec": "axes02WorkgroupSize" },
339
+ "bindings": [
340
+ "x_2",
341
+ "y",
342
+ {
343
+ "name": "params",
344
+ "struct": [
345
+ { "name": "d0", "type": "u32", "value": "dim(shapes.x, 0)" },
346
+ { "name": "d1", "type": "u32", "value": "dim(shapes.x, 1)" },
347
+ { "name": "d2", "type": "u32", "value": "dim(shapes.x, 2)" },
348
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
349
+ ]
350
+ }
351
+ ],
352
+ "dispatch": {
353
+ "x": "min(ceilDiv((numel(shapes.y)), (axes02WorkgroupSize)), 65535)",
354
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (axes02WorkgroupSize)), 65535)",
355
+ "z": 1
356
  }
357
  }
358
+ ]
 
 
 
359
  },
360
  {
361
  "id": "noop_empty_axes",
362
  "priority": 40,
363
+ "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.x, shapes.y)"],
364
+ "derive": { "reduceWorkgroupSize": "reduceWorkgroupSize" },
365
  "passes": [
366
  {
367
  "id": "main",
368
  "name": "ReduceLogSumExp.NoopEmptyAxes",
369
+ "shader": "reduce-noop-empty-axes.wgsl.jinja",
370
+ "derive": { "op": "\"identity\"" },
371
+ "bindings": ["x_2", "y", "params_5"],
372
+ "dispatch": {
373
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
374
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
375
+ "z": 1
376
+ }
377
+ }
378
+ ]
379
+ },
380
+ {
381
+ "id": "subgroup_rows_last_axis_vec4",
382
+ "priority": 26,
383
+ "when": ["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)", "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)"],
384
+ "requires": { "features": ["subgroups"] },
385
+ "derive": {
386
+ "scalar": "dtypes.T",
387
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
388
+ "workgroupSize": "reduceWorkgroupSize",
389
+ "vecsPerLane": "ceilDiv(lastAxisCols / tunables.VECTOR_WIDTH, device.adapterInfo.subgroupMinSize)"
390
+ },
391
+ "passes": [
392
+ {
393
+ "id": "main",
394
+ "name": "ReduceLogSumExp.SubgroupRowsVec4",
395
+ "shader": "reduce-row-subgroup-rows.wgsl.jinja",
396
+ "derive": { "op": "\"logsumexp\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
397
+ "bindings": ["x", "y", "params_6"],
398
+ "dispatch": {
399
+ "x": "min(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
400
+ "y": "ceilDiv(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
401
+ "z": 1
402
+ },
403
+ "subgroupCollectivesWidth": "portable"
404
  }
405
  ]
406
  },
407
  {
408
  "id": "tree_last_axis_vec4",
409
  "priority": 23,
410
+ "when": ["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)", "attrs.noop_with_empty_axes == 0", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "lastAxisCols % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
411
  "demoteWhen": ["rowSerialPreferred"],
412
+ "derive": {
 
413
  "scalar": "dtypes.T",
414
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
415
  "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
 
418
  {
419
  "id": "main",
420
  "name": "ReduceLogSumExp.TreeRowVec4",
421
+ "shader": "reduce-row-tree.wgsl.jinja",
422
+ "derive": {
423
+ "op": "\"logsumexp\"",
424
+ "vec4": true,
425
+ "castF32": "dtypes.T == \"f16\"",
426
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
427
  },
428
+ "bindings": ["x", "y", "params_6"],
429
+ "dispatch": { "x": "min(lastAxisRows, 65535)", "y": "ceilDiv(lastAxisRows, 65535)", "z": 1 }
430
  }
431
  ]
432
  },
433
  {
434
  "id": "rank0_scalar",
435
  "priority": 40,
436
+ "when": ["f16Ok(dtypes.T)", "ranks.x == 0", "ranks.y == 0"],
437
+ "derive": { "axis": 0, "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
438
  "passes": [
439
  {
440
  "id": "main",
441
  "name": "ReduceLogSumExp.Rank0Scalar",
442
+ "shader": "reduce-serial-axis.wgsl.jinja",
443
+ "derive": {
444
+ "op": "\"logsumexp\"",
445
+ "indexing": "\"axis2d\"",
446
+ "intMode": "dtypes.T == \"i32\"",
447
+ "castF32": "dtypes.T == \"f16\"",
448
+ "usesF16Spec": "dtypes.T == \"f16\"",
449
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
 
450
  },
451
+ "bindings": ["x_2", "y", "params_7"],
452
  "dispatch": { "x": 1 }
453
  }
454
  ]
455
  },
456
  {
457
  "id": "rank1_axis0",
458
+ "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))"],
459
+ "derive": { "axis": 0, "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
460
  "passes": [
461
  {
462
  "id": "main",
463
  "name": "ReduceLogSumExp.Rank1Axis0",
464
+ "shader": "reduce-serial-axis.wgsl.jinja",
465
+ "derive": {
466
+ "op": "\"logsumexp\"",
467
+ "indexing": "\"axis2d\"",
468
+ "intMode": "dtypes.T == \"i32\"",
469
+ "castF32": "dtypes.T == \"f16\"",
470
+ "usesF16Spec": "dtypes.T == \"f16\"",
471
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
 
472
  },
473
+ "bindings": ["x_2", "y", "params_8"],
474
+ "dispatch": {
475
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
476
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
477
+ "z": 1
478
+ }
479
  }
480
  ]
481
  },
482
  {
483
  "id": "axis1_parallel",
484
  "priority": 20,
485
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "f16Ok(dtypes.T)", "ranks.x >= 2", "reduceAxis == ranks.x - 1", "numel(shapes.y) == rows(shapes.x, ranks.x - 1)", "lastAxisCols >= tunables.ROW_PARALLEL_MIN_COLS", "treeWorkgroupOk"],
486
  "demoteWhen": ["rowSerialPreferred"],
487
+ "derive": { "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.x, ranks.x - 1)))" },
 
488
  "passes": [
489
  {
490
  "id": "main",
491
  "name": "ReduceLogSumExp.Axis1Parallel",
492
+ "shader": "reduce-row-tree.wgsl.jinja",
493
+ "derive": { "op": "\"logsumexp\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
494
+ "bindings": ["x_2", "y", "params_9"],
495
+ "dispatch": {
496
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
497
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
498
+ "z": 1
499
+ }
500
  }
501
  ]
502
  },
503
  {
504
  "id": "axis_split",
505
  "priority": 24,
506
+ "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"],
507
+ "derive": {
508
+ "splitCount": "axisSplitCount",
509
+ "partialElement": "\"f32\"",
510
+ "workgroupSize": "reduceWorkgroupSize",
511
+ "split": "splitCount"
512
+ },
513
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[3 * splitCount * axisSplitOutputs]" }],
514
  "passes": [
515
  {
516
  "id": "split_reduce",
517
  "name": "ReduceLogSumExp.AxisSplitReduce",
518
+ "shader": "reduce-axis-split-reduce.wgsl.jinja",
519
+ "derive": {
520
+ "op": "\"logsumexp\"",
521
+ "splitSpec": "splitCount",
522
+ "castF32": "dtypes.T == \"f16\"",
523
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
524
  },
525
+ "bindings": ["x_2", "partials", "params_10"],
526
+ "dispatch": {
527
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
528
+ "y": "splitCount",
529
+ "z": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)"
530
+ }
531
  },
532
  {
533
  "id": "combine",
534
  "name": "ReduceLogSumExp.AxisSplitCombine",
535
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
536
+ "derive": { "op": "\"logsumexp\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
537
+ "bindings": ["partials_2", "y", "params_11"],
538
+ "dispatch": {
539
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
540
+ "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
541
+ "z": 1
542
+ }
 
 
 
543
  }
544
  ]
545
  },
546
  {
547
  "id": "axis_split_tiled_narrow",
548
  "priority": 25,
549
+ "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"],
550
+ "derive": {
551
+ "splitCount": "axisSplitCount",
552
  "partialElement": "\"f32\"",
553
  "scalar": "dtypes.T",
554
  "workgroupSize": "reduceWorkgroupSize",
 
560
  {
561
  "id": "split_reduce",
562
  "name": "ReduceLogSumExp.AxisSplitTiledReduce",
563
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
564
+ "derive": {
565
+ "op": "\"logsumexp\"",
566
+ "splitSpec": "splitCount",
567
+ "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
568
+ "castF32": "dtypes.T == \"f16\"",
569
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
570
  },
571
+ "bindings": ["x_2", "partials", "params_10"],
572
+ "dispatch": {
573
+ "x": "min(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)",
574
+ "y": "splitCount",
575
+ "z": "ceilDiv(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)"
576
+ }
577
  },
578
  {
579
  "id": "combine",
580
  "name": "ReduceLogSumExp.AxisSplitCombine",
581
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
582
+ "derive": { "op": "\"logsumexp\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
583
+ "bindings": ["partials_2", "y", "params_11"],
584
+ "dispatch": {
585
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
586
+ "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
587
+ "z": 1
588
+ }
 
 
 
589
  }
590
  ]
591
  },
592
  {
593
  "id": "axis0_splitk",
594
  "priority": 22,
595
+ "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"],
596
+ "derive": {
597
+ "splitCount": "axis0SplitCount",
598
+ "partialElement": "\"f32\"",
599
+ "workgroupSize": "reduceWorkgroupSize",
600
+ "split": "splitCount"
601
+ },
602
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[3 * splitCount * dim(shapes.x, 1)]" }],
603
  "passes": [
604
  {
605
  "id": "split_reduce",
606
  "name": "ReduceLogSumExp.Axis0SplitKReduce",
607
+ "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
608
+ "derive": {
609
+ "op": "\"logsumexp\"",
610
+ "splitSpec": "splitCount",
611
+ "castF32": "dtypes.T == \"f16\"",
612
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
613
  },
614
+ "bindings": ["x_2", "partials", "params_12"],
615
+ "dispatch": {
616
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
617
+ "y": "splitCount",
618
+ "z": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)"
619
+ }
620
  },
621
  {
622
  "id": "combine",
623
  "name": "ReduceLogSumExp.Axis0SplitKCombine",
624
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
625
+ "derive": { "op": "\"logsumexp\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
626
+ "bindings": ["partials_2", "y", "params_13"],
627
+ "dispatch": {
628
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
629
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
630
+ "z": 1
631
+ }
 
 
 
632
  }
633
  ]
634
  },
635
  {
636
  "id": "axis0_tilecols",
637
  "priority": 20,
638
+ "when": ["not flatParallelCovered", "(dtypes.T == \"f32\" or dtypes.T == \"f16\")", "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"],
639
+ "derive": { "workgroupSize": "reduceWorkgroupSize", "tileCols": "tunables.AXIS0_TILE_COLS" },
640
  "passes": [
641
  {
642
  "id": "main",
643
  "name": "ReduceLogSumExp.Axis0TileCols",
644
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
645
+ "derive": { "op": "\"logsumexp\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
646
+ "bindings": ["x_2", "y", "params_12"],
647
+ "dispatch": {
648
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
649
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
650
+ "z": 1
651
+ }
652
  }
653
  ]
654
  },
655
  {
656
  "id": "all_axes_flat",
657
  "priority": 31,
658
+ "when": ["flatParallelCovered"],
659
+ "derive": {
660
  "scalar": "dtypes.T",
661
  "workgroupSize": "reduceWorkgroupSize",
662
+ "flatScalar": "\"vec4<\" ~ dtypes.T ~ \">\" if numel(shapes.x) % tunables.VECTOR_WIDTH == 0 else dtypes.T",
663
  "split": "flatSplitCount"
664
  },
 
665
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[3 * flatSplitCount]" }],
666
  "passes": [
667
  {
668
  "id": "flat_partial",
669
  "name": "ReduceLogSumExp.AllAxesFlatPartial",
670
+ "shader": "reduce-flat-partial-logsumexp.wgsl.jinja",
671
+ "derive": {
672
+ "vec4": "numel(shapes.x) % tunables.VECTOR_WIDTH == 0",
673
+ "castF32": "dtypes.T == \"f16\"",
674
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
675
  },
676
+ "bindings": [
677
+ { "arg": "x", "elementType": "$flatScalar" },
678
+ { "name": "partials", "buffer": "storage", "elementType": "f32" },
679
+ { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "flatItems" }] }
680
+ ],
681
  "dispatch": { "x": "flatSplitCount" }
682
  },
683
  {
684
  "id": "combine",
685
  "name": "ReduceLogSumExp.AllAxesFlatCombine",
686
+ "shader": "reduce-flat-combine-logsumexp.wgsl.jinja",
687
+ "derive": { "outputF16": "dtypes.T == \"f16\"" },
688
+ "bindings": [
689
+ { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
690
+ "y",
691
+ { "name": "params", "struct": [{ "name": "cols", "type": "u32", "value": "1" }] }
692
+ ],
693
  "dispatch": { "x": 1 }
694
  }
695
  ]
 
697
  {
698
  "id": "rankn_single_axis_generic",
699
  "priority": 12,
700
+ "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))"],
701
  "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4"],
702
+ "derive": { "scalar": "dtypes.T", "reduceWorkgroupSize": "reduceWorkgroupSize" },
703
  "passes": [
704
  {
705
  "id": "main",
706
  "name": "ReduceLogSumExp.RankNSingleAxisGeneric",
707
+ "shader": "reduce-serial-axis.wgsl.jinja",
708
+ "derive": {
709
+ "op": "\"logsumexp\"",
710
+ "indexing": "\"rankn\"",
711
+ "rank": "ranks.x",
712
+ "axisSpec": "reduceAxis",
713
+ "dataShape": "shapes.x",
714
+ "outputShape": "shapes.y",
715
+ "outputRank": "ranks.y",
716
+ "keepDims": "attrs.keepdims != 0",
717
+ "intMode": "dtypes.T == \"i32\"",
718
+ "castF32": "dtypes.T == \"f16\"",
719
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
720
  },
721
+ "bindings": ["x_2", "y", "params_16"],
722
+ "dispatch": {
723
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
724
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
725
+ "z": 1
726
+ }
727
  }
728
+ ]
 
729
  },
730
  {
731
  "id": "subgroup_last_axis_vec4",
732
  "priority": 25,
733
+ "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)", "not rowSerialPreferred"],
734
  "requires": { "features": ["subgroups"] },
735
+ "derive": {
 
736
  "scalar": "dtypes.T",
737
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
738
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
 
741
  {
742
  "id": "main",
743
  "name": "ReduceLogSumExp.SubgroupRowVec4",
744
+ "shader": "reduce-row-subgroup.wgsl.jinja",
745
+ "derive": {
746
+ "op": "\"logsumexp\"",
747
+ "vec4": true,
748
+ "castF32": "dtypes.T == \"f16\"",
749
+ "usesF16Spec": "dtypes.T == \"f16\""
750
+ },
751
+ "bindings": ["x", "y", "params_6"],
752
+ "dispatch": {
753
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
754
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
755
+ "z": 1
756
  },
757
+ "subgroupCollectivesWidth": "portable"
 
 
758
  }
759
  ]
760
  },
761
  {
762
  "id": "subgroup_last_axis",
763
  "priority": 24,
764
+ "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)", "not rowSerialPreferred"],
765
  "requires": { "features": ["subgroups"] },
766
+ "derive": {
 
767
  "scalar": "dtypes.T",
768
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
769
  },
 
771
  {
772
  "id": "main",
773
  "name": "ReduceLogSumExp.SubgroupRow",
774
+ "shader": "reduce-row-subgroup.wgsl.jinja",
775
+ "derive": {
776
+ "op": "\"logsumexp\"",
777
+ "vec4": false,
778
+ "castF32": "dtypes.T == \"f16\"",
779
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
780
  },
781
+ "bindings": ["x_2", "y", "params_17"],
782
+ "dispatch": {
783
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
784
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
785
+ "z": 1
786
+ },
787
+ "subgroupCollectivesWidth": "portable"
788
  }
789
  ]
790
  },
791
  {
792
  "id": "axis0",
793
  "priority": 0,
794
+ "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)))"],
795
  "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
796
+ "derive": { "axis": 0, "scalar": "dtypes.T" },
 
797
  "passes": [
798
  {
799
  "id": "main",
800
  "name": "axis0",
801
+ "shader": "reduce-serial-axis.wgsl.jinja",
802
+ "derive": {
803
+ "axis": 0,
804
+ "op": "\"logsumexp\"",
805
+ "indexing": "\"axis2d\"",
806
+ "intMode": "dtypes.T == \"i32\"",
807
+ "castF32": "dtypes.T == \"f16\"",
808
+ "usesF16Spec": "dtypes.T == \"f16\""
 
809
  },
810
+ "bindings": ["x_2", "y", "params_18"],
811
+ "dispatch": {
812
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
813
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
814
+ "z": 1
815
+ }
816
  }
817
  ]
818
  },
819
  {
820
  "id": "axis1",
821
  "priority": 0,
822
+ "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))"],
823
+ "derive": { "axis": 1, "scalar": "dtypes.T" },
824
  "passes": [
825
  {
826
  "id": "main",
827
  "name": "axis1",
828
+ "shader": "reduce-serial-axis.wgsl.jinja",
829
+ "derive": {
830
+ "axis": 1,
831
+ "op": "\"logsumexp\"",
832
+ "indexing": "\"axis2d\"",
833
+ "intMode": "dtypes.T == \"i32\"",
834
+ "castF32": "dtypes.T == \"f16\"",
835
+ "usesF16Spec": "dtypes.T == \"f16\""
 
836
  },
837
+ "bindings": ["x_2", "y", "params_19"],
838
+ "dispatch": {
839
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
840
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
841
+ "z": 1
842
+ }
843
  }
844
  ]
845
  },
846
  {
847
  "id": "all_axes_keepdims",
848
  "priority": 30,
849
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 1", "ranks.y == ranks.x", "numel(shapes.y) == 1"],
850
+ "derive": { "axis": 0, "scalar": "dtypes.T" },
851
  "passes": [
852
  {
853
  "id": "main",
854
  "name": "ReduceLogSumExp.Rank3AllAxesKeepdims",
855
+ "shader": "reduce-serial-axis.wgsl.jinja",
856
+ "derive": {
857
+ "op": "\"logsumexp\"",
858
+ "indexing": "\"axis2d\"",
859
+ "intMode": "dtypes.T == \"i32\"",
860
+ "castF32": "dtypes.T == \"f16\"",
861
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
862
  },
863
+ "bindings": [
864
+ "x_2",
865
+ "y",
866
+ {
867
+ "name": "params",
868
+ "struct": [
869
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
870
+ { "name": "cols", "type": "u32", "value": "1" },
871
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
872
+ ]
873
+ }
874
+ ],
875
+ "dispatch": {
876
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
877
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
878
+ "z": 1
879
+ }
880
  }
881
  ]
882
  },
883
  {
884
  "id": "all_axes_no_keepdims",
885
  "priority": 30,
886
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 0", "attrs.noop_with_empty_axes == 0", "ranks.y == 0"],
887
+ "derive": { "axis": 0, "scalar": "dtypes.T" },
888
  "passes": [
889
  {
890
  "id": "main",
891
  "name": "ReduceLogSumExp.Rank3AllAxesNoKeepdims",
892
+ "shader": "reduce-serial-axis.wgsl.jinja",
893
+ "derive": {
894
+ "op": "\"logsumexp\"",
895
+ "indexing": "\"axis2d\"",
896
+ "intMode": "dtypes.T == \"i32\"",
897
+ "castF32": "dtypes.T == \"f16\"",
898
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
899
  },
900
+ "bindings": [
901
+ "x_2",
902
+ "y",
903
+ {
904
+ "name": "params",
905
+ "struct": [
906
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
907
+ { "name": "cols", "type": "u32", "value": "1" },
908
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
909
+ ]
910
+ }
911
+ ],
912
+ "dispatch": {
913
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
914
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
915
+ "z": 1
916
+ }
917
  }
918
  ]
919
  }
build/webgpu/metadata.json CHANGED
@@ -1,28 +1,57 @@
1
  {
2
  "name": "ai.onnx.ReduceLogSumExp",
3
- "id": "_ai_onnx_reducelogsumexp_webgpu_85769e2",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "osiIimng7rf8c4+lWBGf/4BUtBJ1pMrNA2I08w6ygf0=",
11
- "manifest.json": "rbJKRnqa38U/J/QPQmwV9MWpWxBCCQQoVVrRzy1fRI0=",
12
- "reduce-axis-split-reduce.wgsl.jinja": "CKODGEG8mXIkTNFzjDJZwfExy1KzYxG50C3LaUiYd3U=",
13
- "reduce-axis0-splitk-combine.wgsl.jinja": "P3U2gCak59UZSyakvyuWCDhW27IPcilPg+yE1BPY5nM=",
14
- "reduce-axis0-splitk-reduce.wgsl.jinja": "cowJ5TK8xFHBwnCPYUYEBScEIKXFiCN4mlu0B2lWaw4=",
15
- "reduce-axis0-tilecols.wgsl.jinja": "CNwWK3NVE27T0IJo4+HoDfLFDGMuJ0PVDJRk5Jmwq7o=",
16
- "reduce-flat-combine-logsumexp.wgsl.jinja": "qrI8ma9jUPm+p+oWJejet/DvaNNTgjw4AoUBKIgoaPQ=",
17
- "reduce-flat-partial-logsumexp.wgsl.jinja": "dl9CTTHHIAjnI0QvdFpNkv4IFWD62Cf0Q3jpgsAfpPo=",
18
- "reduce-i32-axes02.wgsl.jinja": "qSNKLHjGG9CFO6DRIlBsQ7XETdKrQbbnn3O7NJPxvY0=",
19
- "reduce-noop-empty-axes.wgsl.jinja": "IO2MEzyBGJr3Z8AVP7ELJXqfhwcG/vtBfa0wCdkbuwo=",
20
- "reduce-row-subgroup.wgsl.jinja": "GqRcdIa6KA7BPnGGO9ApvQPizeinFAG1XDOSLsutZ6Y=",
21
- "reduce-row-tree.wgsl.jinja": "yX9bu0RBuTGybmUUcK4bZ9Gf0CyqDsMThC9jIXoXxNU=",
22
- "reduce-serial-axis.wgsl.jinja": "BGP2SlwMidLS+xcUX/FBC9lAwCi5gLM6DWOZYiqxYQo=",
23
- "test.json": "nvmxvijOcgGOSHCGvqrJ6/iAdaTeHTSgnc4Ghf7IGCE="
 
24
  }
25
  },
26
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
27
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.ReduceLogSumExp" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
 
1
  {
2
  "name": "ai.onnx.ReduceLogSumExp",
3
+ "id": "_ai_onnx_reducelogsumexp_webgpu_fd6e0c7",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "msizeqfB88GjW8v7QU9KoKyjW3qc8lJJXaCPf6OejeI=",
11
+ "manifest.json": "ofeVPiTS0KOqgOnbSIdNK8ULQiV3GSHu3Sxay4FY9U0=",
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-combine-logsumexp.wgsl.jinja": "vwwisgGJGC6hBX/WYMf/OEXKa71pMJKX6v2ggWuXWpM=",
17
+ "reduce-flat-partial-logsumexp.wgsl.jinja": "5ZVJnWsR0xWm4Oet3oqP0V7ePeqLea5wBs/rBsy9L20=",
18
+ "reduce-i32-axes02.wgsl.jinja": "FsDSFjExTqueGACUuk9bPO7gN5yp8sJHtEXapuxter0=",
19
+ "reduce-noop-empty-axes.wgsl.jinja": "NNvXRO0Tt3Mrvk2Wdssndbea61oQN4tj+O08NVA43Ns=",
20
+ "reduce-row-subgroup-rows.wgsl.jinja": "76u7rAvFoZZKrFDs2A2jk0vkB0uNrPL6twdOBE9b+v8=",
21
+ "reduce-row-subgroup.wgsl.jinja": "2mu9LEsk8HfaLvucBCfcB1/ENpXkD6ELiCtRt+5UqiU=",
22
+ "reduce-row-tree.wgsl.jinja": "Bwa5xcI0bTmKXb4r9Cc1bfVbM5rNqqpQVrWWVqcb8xA=",
23
+ "reduce-serial-axis.wgsl.jinja": "fvUV9htqzKzt4Pg05pYtRmup/5QIHYaUGQHJZnthXKo=",
24
+ "test.json": "QyxQrLhFNbxc+mf7VOuv8RipIY2AlNZEhJZJvTQjuNw="
25
  }
26
  },
27
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
28
+ "webgpu": {
29
+ "manifestSpec": "2.0",
30
+ "variants": {
31
+ "contiguous_suffix_subgroup_vec4": ["reduce-row-subgroup.wgsl.jinja"],
32
+ "contiguous_suffix_tree_vec4": ["reduce-row-tree.wgsl.jinja"],
33
+ "contiguous_suffix_tree": ["reduce-row-tree.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
+ "noop_empty_axes": ["reduce-noop-empty-axes.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
+ "axis1_parallel": ["reduce-row-tree.wgsl.jinja"],
43
+ "axis_split": ["reduce-axis-split-reduce.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
44
+ "axis_split_tiled_narrow": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-axis0-tilecols.wgsl.jinja"],
45
+ "axis0_splitk": ["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-flat-combine-logsumexp.wgsl.jinja", "reduce-flat-partial-logsumexp.wgsl.jinja"],
48
+ "rankn_single_axis_generic": ["reduce-serial-axis.wgsl.jinja"],
49
+ "subgroup_last_axis_vec4": ["reduce-row-subgroup.wgsl.jinja"],
50
+ "subgroup_last_axis": ["reduce-row-subgroup.wgsl.jinja"],
51
+ "axis0": ["reduce-serial-axis.wgsl.jinja"],
52
+ "axis1": ["reduce-serial-axis.wgsl.jinja"],
53
+ "all_axes_keepdims": ["reduce-serial-axis.wgsl.jinja"],
54
+ "all_axes_no_keepdims": ["reduce-serial-axis.wgsl.jinja"]
55
+ }
56
+ }
57
  }
build/webgpu/reduce-axis-split-reduce.wgsl.jinja CHANGED
@@ -4,36 +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
  const F32_MIN: f32 = -3.4028234663852886e38;
24
 
25
  fn is_nan_f32(value: f32) -> bool {
26
  let bits = bitcast<u32>(value);
27
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
28
  }
 
 
 
29
 
30
  @compute @workgroup_size(WG, 1, 1)
31
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
32
- @builtin(workgroup_id) wg: vec3<u32>,
33
- @builtin(num_workgroups) nwg: vec3<u32>) {
34
  // 2D-folded output index: wg.z carries the high bits past the
35
  // per-dimension dispatch limit on the x dimension.
36
- let output_index = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
37
  let seg = wg.y;
38
  if (output_index >= params.outputs) { return; }
39
 
@@ -47,6 +64,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
47
  var a1 = a0 + chunk;
48
  if (a1 > params.axisDim) { a1 = params.axisDim; }
49
 
 
50
  var local_max = F32_MIN;
51
  var local_nan_count = 0.0;
52
  var local_nan_value = 0.0;
@@ -68,4 +86,30 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
68
  partials[seg * params.outputs + output_index] = local_max;
69
  partials[(SPLIT + seg) * params.outputs + output_index] = acc;
70
  partials[(2u * SPLIT + seg) * params.outputs + output_index] = select(0.0, local_nan_value, local_nan_count > 0.0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
 
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;
 
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,19 +2,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 yv = "f16(" if source.outputF16 else "" %}
6
- {% set vy = ")" if source.outputF16 else "" %}
7
- {% if source.outputF16 %}
 
 
 
8
  enable f16;
9
  {% endif %}
10
  {{ env.wgsl.resourceDeclarations }}
11
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
12
- * evaluation, so f32 identities are constructed at runtime from their
13
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
 
16
  const WG: u32 = {{ workgroupSize }}u;
17
  const SPLIT: u32 = {{ split }}u;
 
 
 
 
18
  const F32_MIN: f32 = -3.4028234663852886e38;
19
  const F32_MAX: f32 = 3.4028234663852886e38;
20
 
@@ -22,13 +41,17 @@ fn is_nan_f32(value: f32) -> bool {
22
  let bits = bitcast<u32>(value);
23
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
24
  }
 
 
 
25
 
26
  @compute @workgroup_size(WG, 1, 1)
27
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
28
  @builtin(num_workgroups) nwg: vec3<u32>) {
29
  let stride = nwg.x * WG;
30
- let start = (gid.y * nwg.x * WG) + gid.x;
31
  for (var col = start; col < params.cols; col = col + stride) {
 
32
  // Merge SPLIT (segMax, segSumExp) pairs stably; carry NaN / +Inf markers.
33
  var nan_value = 0.0;
34
  var has_nan = false;
@@ -50,5 +73,50 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
  let has_positive_inf = global_max > F32_MAX;
51
  let finite_or_inf = select(global_max + log(sum), global_max, has_positive_inf);
52
  y[col] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
  }
 
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
 
 
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;
 
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,37 +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
  const F32_MIN: f32 = -3.4028234663852886e38;
23
 
24
  fn is_nan_f32(value: f32) -> bool {
25
  let bits = bitcast<u32>(value);
26
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
27
  }
 
 
 
28
 
29
  @compute @workgroup_size(WG, 1, 1)
30
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
31
- @builtin(workgroup_id) wg: vec3<u32>,
32
- @builtin(num_workgroups) nwg: vec3<u32>) {
33
- // 2D-folded column index: wg.z carries the high bits past the maxComputeWorkgroupsPerDimension
34
- // workgroup-per-dimension dispatch limit on the x dimension.
35
- let col = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
36
  let seg = wg.y;
37
  if (col >= params.cols) { return; }
38
 
@@ -42,6 +54,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
42
  var r1 = r0 + chunk;
43
  if (r1 > params.rows) { r1 = params.rows; }
44
 
 
45
  var local_max = F32_MIN;
46
  var local_nan_count = 0.0;
47
  var local_nan_value = 0.0;
@@ -65,4 +78,38 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
65
  partials[seg * params.cols + col] = local_max;
66
  partials[(SPLIT + seg) * params.cols + col] = acc;
67
  partials[(2u * SPLIT + seg) * params.cols + col] = select(0.0, local_nan_value, local_nan_count > 0.0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
 
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;
 
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,23 +13,35 @@
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
- {% if not splitMode and not intMode and (source.op == "logsum" or source.op == "logsumexp") %}
33
  fn negative_infinity() -> f32 {
34
  var bits = 0xff800000u;
35
  return bitcast<f32>(bits);
@@ -41,21 +53,26 @@ const WG: u32 = {{ workgroupSize }}u;
41
  const TILE_COLS: u32 = {{ tileCols }}u;
42
  const ROW_LANES: u32 = WG / TILE_COLS;
43
  {% if splitMode %}
44
- const SPLIT: u32 = {{ source.split }}u;
45
  {% endif %}
 
 
 
46
  const F32_MIN: f32 = -3.4028234663852886e38;
47
  const F32_MAX: f32 = 3.4028234663852886e38;
 
48
 
49
- var<workgroup> partial: array<{{ scalar if (source.op == "max" or source.op == "min" or intMode) else "f32" }}, WG>;
 
50
 
51
  fn is_nan_f32(value: f32) -> bool {
52
  let bits = bitcast<u32>(value);
53
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
54
  }
55
 
 
56
  @compute @workgroup_size(WG, 1, 1)
57
- fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>{% if not splitMode %},
58
- @builtin(num_workgroups) nwg: vec3<u32>{% endif %}) {
59
  let tid = lid.x;
60
  let col_lane = tid % TILE_COLS;
61
  let row_lane = tid / TILE_COLS;
@@ -76,12 +93,13 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
76
  // 2D-folded tile index: wg.y carries the high bits past the dispatch limit.
77
  // The batched form reuses this same coalesced axis-0 reduction for a middle
78
  // axis by assigning consecutive tiles to each outer slice.
79
- let tile = wg.x + wg.y * nwg.x;
80
  let col = tile * TILE_COLS + col_lane;
81
  let inputBase = 0u;
82
  let outputIndex = col;
83
  let in_bounds = col < params.cols;
84
  {% endif %}
 
85
  {% if not splitMode %}
86
 
87
  if (params.rows == 0u) {
@@ -171,4 +189,72 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
171
  y[outputIndex] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
172
  {% endif %}
173
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  }
 
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);
 
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) {
 
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;
227
+ workgroupBarrier();
228
+
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-combine-logsumexp.wgsl.jinja CHANGED
@@ -4,9 +4,9 @@
4
  // to each output column, this scalar-only path has a full workgroup fold the
5
  // partial planes. That avoids leaving one lane to execute 2*SPLIT
6
  // transcendental merges when the output has exactly one element.
7
- {% set yv = "f16(" if source.outputF16 else "" %}
8
- {% set vy = ")" if source.outputF16 else "" %}
9
- {% if source.outputF16 %}
10
  enable f16;
11
  {% endif %}
12
  {{ env.wgsl.resourceDeclarations }}
 
4
  // to each output column, this scalar-only path has a full workgroup fold the
5
  // partial planes. That avoids leaving one lane to execute 2*SPLIT
6
  // transcendental merges when the output has exactly one element.
7
+ {% set yv = "f16(" if outputF16 else "" %}
8
+ {% set vy = ")" if outputF16 else "" %}
9
+ {% if outputF16 %}
10
  enable f16;
11
  {% endif %}
12
  {{ env.wgsl.resourceDeclarations }}
build/webgpu/reduce-flat-partial-logsumexp.wgsl.jinja CHANGED
@@ -4,11 +4,11 @@
4
  // Workgroups write three partial planes: segment maximum, shifted exponential
5
  // sum, and a NaN marker. The combine pass merges them stably and emits
6
  // globalMaximum + log(sum), preserving NaN and positive infinity.
7
- {% set vec4 = source.vec4 | default(true) %}
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 }}
 
4
  // Workgroups write three partial planes: segment maximum, shifted exponential
5
  // sum, and a NaN marker. The combine pass merges them stably and emits
6
  // globalMaximum + log(sum), preserving NaN and positive infinity.
7
+ {% set vec4 = vec4 | default(true) %}
8
+ {% set castF32 = castF32 is defined and castF32 %}
9
  {% set xa = "f32(" if castF32 else "" %}
10
  {% set ax = ")" if castF32 else "" %}
11
+ {% if usesF16Spec is defined and usesF16Spec %}
12
  enable f16;
13
  {% endif %}
14
  {{ env.wgsl.resourceDeclarations }}
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.d0;
11
  let d1 = params.d1;
12
  let d2 = params.d2;
 
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.d0;
10
  let d1 = params.d1;
11
  let d2 = params.d2;
build/webgpu/reduce-noop-empty-axes.wgsl.jinja CHANGED
@@ -1,10 +1,10 @@
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
  }
 
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
  }
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,54 @@
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
  const F32_MIN: f32 = -3.4028234663852886e38;
29
  const F32_MAX: f32 = 3.4028234663852886e38;
30
 
@@ -32,6 +56,8 @@ fn is_nan_f32(value: f32) -> bool {
32
  let bits = bitcast<u32>(value);
33
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
34
  }
 
 
35
  var<workgroup> wgPartial: array<{{ scalar }}, WG>;
36
 
37
  {% macro emit_reduce(name, collective, combine) %}
@@ -51,26 +77,39 @@ fn {{ name }}(value: {{ scalar }}, sgLid: u32, sgId: u32, numSg: u32) -> {{ scal
51
  workgroupBarrier();
52
  return total;
53
  }
54
- {%- endmacro %}{{ emit_reduce("reduce_row_add", "subgroupAdd", "total = total + wgPartial[i];") }}
 
 
 
 
 
 
 
 
55
  {{ emit_reduce("reduce_row_max", "subgroupMax", "total = max(total, wgPartial[i]);") }}
 
 
 
 
56
  @compute @workgroup_size(WG, 1, 1)
57
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
58
- @builtin(num_workgroups) nwg: vec3<u32>,
59
  @builtin(local_invocation_id) lid: vec3<u32>,
60
  @builtin(subgroup_invocation_id) sgLid: u32,
61
  @builtin(subgroup_id) sgId: u32,
62
  @builtin(num_subgroups) numSg: u32) {
63
- let row = wg.x + wg.y * nwg.x;
64
  if (row >= params.rows) {
65
  return;
66
  }
67
  let tid = lid.x;
68
- let base = row * params.chunkCount; var localMax = F32_MIN;
 
 
69
  var localNan = 0.0;
70
  var localNanValue = 0.0;
71
  for (var c = tid; c < params.chunkCount; c = c + WG) {
72
  let v = {{ xv }}x[base + c]{{ vx }};
73
- {%- if source.vec4 %}
74
  {% for comp in ["x", "y", "z", "w"] %}
75
  if (is_nan_f32(v.{{ comp }})) {
76
  localNan = 1.0;
@@ -96,7 +135,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
96
  var acc = 0.0;
97
  for (var c = tid; c < params.chunkCount; c = c + WG) {
98
  let v = {{ xv }}x[base + c]{{ vx }};
99
- {%- if source.vec4 %}
100
  let e = select(exp(v - vec4<f32>(rowMax)), vec4<f32>(0.0), hasPositiveInf || hasNan);
101
  acc = acc + ((e.x + e.y) + (e.z + e.w));
102
  {%- else %}
@@ -107,4 +146,72 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
107
  if (tid == 0u) {
108
  let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
109
  y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
110
- }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
 
 
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;
 
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 %}
 
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,30 +1,48 @@
1
- // Portable one-workgroup-per-row reduction for the Reduce value family.
2
  // Threads stride a contiguous row, accumulate locally, and fold their values
3
  // through a shared-memory tree without relying on subgroups.
4
- //
5
- // Max, min, and product use the native f32/i32/u32 value type. Additive
6
- // transforms accumulate float inputs in f32 but retain integer accumulation
7
- // for sum, L1, and sum-of-squares. Mean, L2, log-sum, and log-sum-exp are
8
- // f32-only because their finalizers divide, take a square root, or take a log.
 
 
 
 
 
 
9
  // f16 storage widens through f32 for both accumulation and the shared tree,
10
  // then narrows only at the final store.
11
- {% set isVec4 = source.vec4 is defined and source.vec4 %}
12
  {% set rowIsEmpty = "params.chunkCount == 0u" if isVec4 else "params.cols == 0u" %}
13
- {% set castF32 = source.castF32 is defined and source.castF32 %}
14
  {% set scalar = "f32" if castF32 else scalar %}
15
- {% set xv = ("vec4<f32>(" if source.vec4 else "f32(") if castF32 else "" %}
16
  {% set vx = ")" if castF32 else "" %}
17
  {% set yv = "f16(" if castF32 else "" %}
18
  {% set vy = ")" if castF32 else "" %}
19
- {% if source.usesF16 is defined and source.usesF16 %}
20
  enable f16;
21
  {% endif %}
22
  {{ env.wgsl.resourceDeclarations }}
23
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
24
- * evaluation, so f32 identities are constructed at runtime from their
25
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- {% if scalar != "i32" and scalar != "u32" and (source.op == "logsum" or source.op == "logsumexp") %}
28
  fn negative_infinity() -> f32 {
29
  var bits = 0xff800000u;
30
  return bitcast<f32>(bits);
@@ -34,6 +52,7 @@ fn negative_infinity() -> f32 {
34
 
35
  const WG: u32 = {{ workgroupSize }}u;
36
 
 
37
  const F32_MIN: f32 = -3.4028234663852886e38;
38
  const F32_MAX: f32 = 3.4028234663852886e38;
39
 
@@ -80,22 +99,51 @@ fn is_nan_f32(value: f32) -> bool {
80
  return (bits & 0x7f800000u) == 0x7f800000u
81
  && (bits & 0x007fffffu) != 0u;
82
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  @compute @workgroup_size(WG, 1, 1)
85
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
86
- @builtin(num_workgroups) nwg: vec3<u32>,
87
  @builtin(local_invocation_id) lid: vec3<u32>) {
88
- let row = wg.x + wg.y * nwg.x;
89
  if (row >= params.rows) {
90
  return;
91
  }
92
  let tid = lid.x;
93
- {% if source.vec4 %}
94
  let base = row * params.chunkCount;
95
  {% else %}
96
  let base = row * params.cols;
97
  {% endif %}
98
 
 
99
  if ({{ rowIsEmpty }}) {
100
  if (tid == 0u) {
101
  y[row] = {{ yv }}negative_infinity(){{ vy }};
@@ -106,7 +154,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
106
  var localMax = F32_MIN;
107
  var localNan = 0.0;
108
  var localNanValue = 0.0;
109
- {% if source.vec4 %}
110
  for (var col = tid; col < params.chunkCount; col = col + WG) {
111
  let value = {{ xv }}x[base + col]{{ vx }};
112
  {% for component in ["x", "y", "z", "w"] %}
@@ -139,7 +187,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
139
  let hasNan = nanCount > 0.0;
140
 
141
  var acc = 0.0;
142
- {% if source.vec4 %}
143
  for (var col = tid; col < params.chunkCount; col = col + WG) {
144
  let value = {{ xv }}x[base + col]{{ vx }};
145
  let exponentials = select(exp(value - vec4<f32>(rowMax)), vec4<f32>(0.0),
@@ -158,4 +206,88 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
158
  let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
159
  y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
160
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  }
 
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);
 
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
 
 
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 }};
 
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"] %}
 
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),
 
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;
267
+ workgroupBarrier();
268
+
269
+ for (var step = WG >> 1u; step > 0u; step = step >> 1u) {
270
+ if (tid < step) {
271
+ partial[tid] = combine(partial[tid], partial[tid + step]);
272
+ }
273
+ workgroupBarrier();
274
+ }
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,24 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 not intMode and (source.op == "logsum" or source.op == "logsumexp") %}
16
  fn negative_infinity() -> f32 {
17
  var bits = 0xff800000u;
18
  return bitcast<f32>(bits);
19
  }
20
 
21
  {% endif %}
 
 
 
 
22
 
23
  const F32_MIN: f32 = -3.4028234663852886e38;
24
  {% if not intMode %}
@@ -29,122 +94,85 @@ fn is_nan_f32(value: f32) -> bool {
29
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
30
  }
31
  {% endif %}
32
- {% if source.indexing == "rankn" %}
 
33
 
34
  fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
35
  var rem = out_index;
36
- {% for axis in range(source.outputRank) %}
37
  {% set out_stride = namespace(value=1) %}
38
- {% for j in range(axis + 1, source.outputRank) %}
39
- {% set out_stride.value = out_stride.value * source.outputShape[j] %}
40
  {% endfor %}
41
  {% set safe_out_stride = 1 if out_stride.value == 0 else out_stride.value %}
42
- {% if not source.keepDims or axis != source.axis %}
43
  let out_coord{{ axis }} = rem / {{ safe_out_stride }}u;
44
  {% endif %}
45
  rem = rem % {{ safe_out_stride }}u;
46
  {% endfor %}
47
- {% for axis in range(source.rank) %}
48
- {% if axis == source.axis %}
49
  let coord{{ axis }} = reduce_index;
50
- {% elif source.keepDims %}
51
  let coord{{ axis }} = out_coord{{ axis }};
52
- {% elif axis < source.axis %}
53
  let coord{{ axis }} = out_coord{{ axis }};
54
  {% else %}
55
  let coord{{ axis }} = out_coord{{ axis - 1 }};
56
  {% endif %}
57
  {% endfor %}
58
  {% set src = namespace(value="coord0") %}
59
- {% for axis in range(1, source.rank) %}
60
- {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[axis] ~ "u + coord" ~ axis ~ ")" %}
61
  {% endfor %}
62
  return {{ src.value }};
63
  }
64
  {% endif %}
65
- {% if source.indexing == "multiaxis" %}
66
  {% set hasReducedAxis = namespace(value=false) %}
67
- {% for a in range(source.rank) %}{% if source.reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
68
-
69
- // One thread per output element walks the Cartesian product of the reduced axes,
70
- // linearized as reduce_linear. Specialized shapes make every input offset a sum
71
- // of coordinate-times-constant terms.
72
- fn input_offset(out_index: u32{% if hasReducedAxis.value %}, reduce_linear: u32{% endif %}) -> u32 {
73
- var rem = out_index;
74
- {% for oaxis in range(source.outputRank) %}
75
- {% set ostride = namespace(value=1) %}
76
- {% for j in range(oaxis + 1, source.outputRank) %}
77
- {% set ostride.value = ostride.value * source.outputShape[j] %}
78
- {% endfor %}
79
- {% set osafe = 1 if ostride.value == 0 else ostride.value %}
80
- {% if not source.keepDims or not source.reduce[oaxis] %}
81
- let out_coord{{ oaxis }} = rem / {{ osafe }}u;
82
- {% endif %}
83
- rem = rem % {{ osafe }}u;
84
- {% endfor %}
85
- {% if hasReducedAxis.value %}
86
- var rrem = reduce_linear;
87
  {% endif %}
88
- {% for a in range(source.rank) if source.reduce[a] %}
89
- {% set rstride = namespace(value=1) %}
90
- {% for b in range(a + 1, source.rank) if source.reduce[b] %}
91
- {% set rstride.value = rstride.value * source.dataShape[b] %}
92
- {% endfor %}
93
- {% set rsafe = 1 if rstride.value == 0 else rstride.value %}
94
- let red_coord{{ a }} = rrem / {{ rsafe }}u;
95
- rrem = rrem % {{ rsafe }}u;
96
- {% endfor %}
97
- {% set oc = namespace(i=0) %}
98
- {% for a in range(source.rank) %}
99
- {% if source.reduce[a] %}
100
- let coord{{ a }} = red_coord{{ a }};
101
- {% elif source.keepDims %}
102
- let coord{{ a }} = out_coord{{ a }};
103
- {% else %}
104
- let coord{{ a }} = out_coord{{ oc.i }};
105
- {% set oc.i = oc.i + 1 %}
106
- {% endif %}
107
- {% endfor %}
108
- {% set src = namespace(value="coord0") %}
109
- {% for a in range(1, source.rank) %}
110
- {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[a] ~ "u + coord" ~ a ~ ")" %}
111
- {% endfor %}
112
- return {{ src.value }};
113
- }
114
- {% endif %}
115
- {% if source.indexing == "multiaxis" %}
116
  {% set mcount = namespace(value=1) %}
117
- {% for a in range(source.rank) if source.reduce[a] %}
118
- {% set mcount.value = mcount.value * source.dataShape[a] %}
119
  {% endfor %}
120
  {% set count = mcount.value ~ "u" %}
121
  {% if hasReducedAxis.value %}
122
  {% set at = "x[input_offset(i, r)]" %}
 
123
  {% else %}
124
  {% set at = "x[input_offset(i)]" %}
 
125
  {% endif %}
126
- {% elif source.indexing == "rankn" %}
127
  {% set count = "params.axisDim" %}
128
  {% set at = "x[input_offset(i, r)]" %}
 
129
  {% elif axis == 0 %}
130
  {% set count = "params.rows" %}
131
  {% set at = "x[r * params.cols + i]" %}
 
132
  {% else %}
133
  {% set count = "params.cols" %}
134
  {% set at = "x[i * params.cols + r]" %}
 
135
  {% endif %}
136
  {% if castF32 %}
137
  {% set at = "f32(" ~ at ~ ")" %}
 
138
  {% endif %}
139
 
140
  @compute @workgroup_size({{ reduceWorkgroupSize }})
141
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
142
  // 2D-folded flat index: gid.y carries the high bits past the
143
- // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
144
- let i = gid.x + gid.y * nwg.x * {{ reduceWorkgroupSize }}u;
145
  if (i >= params.outCount) {
146
  return;
147
  }
 
148
  {% if intMode %}
149
  // Integer logsumexp widens each element for exp/log, then truncates the result
150
  // back to the integer output type.
@@ -193,4 +221,67 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
193
  }
194
  y[i] = {{ yv }}m + log(acc){{ vy }};
195
  {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  }
 
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 }};
39
+ {% set oc.i = oc.i + 1 %}
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 %}
 
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.
 
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.ReduceLogSumExp",
3
  "cases": [
4
  {
5
  "name": "contiguous_suffix_axes12_parallel",
@@ -16,7 +15,7 @@
16
  {
17
  "name": "all_axes_flat_rank1_boundary_8192",
18
  "provenance": {
19
- "notes": "The parallel full-reduction threshold must supersede the rank1 serial and row-reduction fallbacks."
20
  },
21
  "attrs": { "axes": [0], "keepdims": 0 },
22
  "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
@@ -73,10 +72,10 @@
73
  "x": {
74
  "dtype": "float32",
75
  "shape": [3, 2],
76
- "data": { "kind": "values", "values": ["Infinity", -1.0, 2.0, "Infinity", 3.0, 4.0] }
77
  }
78
  },
79
- "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0 } }
80
  },
81
  {
82
  "name": "axis0_nan_overrides_positive_infinity",
@@ -345,7 +344,7 @@
345
  "name": "onnx_backend_keepdims_example_f32_projection",
346
  "provenance": {
347
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_keepdims_example",
348
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
349
  },
350
  "attrs": { "axes": [1], "keepdims": 1 },
351
  "inputs": {
@@ -361,7 +360,7 @@
361
  "name": "onnx_backend_negative_axis_keepdims_example_f32_projection",
362
  "provenance": {
363
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_negative_axes_keepdims_example",
364
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
365
  },
366
  "attrs": { "axes": [-2], "keepdims": 1 },
367
  "inputs": {
@@ -377,7 +376,7 @@
377
  "name": "onnx_backend_do_not_keepdims_example_f32_projection",
378
  "provenance": {
379
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_do_not_keepdims_example",
380
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
381
  },
382
  "attrs": { "axes": [1], "keepdims": 0 },
383
  "inputs": {
@@ -393,7 +392,7 @@
393
  "name": "onnx_backend_keepdims_random_f32_projection",
394
  "provenance": {
395
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_keepdims_random",
396
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
397
  },
398
  "attrs": { "axes": [1], "keepdims": 1 },
399
  "inputs": {
@@ -412,7 +411,7 @@
412
  "name": "onnx_backend_negative_axis_keepdims_random_f32_projection",
413
  "provenance": {
414
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_negative_axes_keepdims_random",
415
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
416
  },
417
  "attrs": { "axes": [-2], "keepdims": 1 },
418
  "inputs": {
@@ -431,7 +430,7 @@
431
  "name": "onnx_backend_do_not_keepdims_random_f32_projection",
432
  "provenance": {
433
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_do_not_keepdims_random",
434
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
435
  },
436
  "attrs": { "axes": [1], "keepdims": 0 },
437
  "inputs": {
@@ -450,7 +449,7 @@
450
  "name": "onnx_backend_default_axes_keepdims_example_f32_projection",
451
  "provenance": {
452
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_default_axes_keepdims_example",
453
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
454
  },
455
  "attrs": { "keepdims": 1 },
456
  "inputs": {
@@ -490,7 +489,7 @@
490
  "name": "onnx_backend_default_axes_keepdims_random_f32_projection",
491
  "provenance": {
492
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_default_axes_keepdims_random",
493
- "notes": "Official ONNX backend tensor is float64; projected to float32 to match this framework dtype support."
494
  },
495
  "attrs": { "keepdims": 1 },
496
  "inputs": {
@@ -660,7 +659,7 @@
660
  "shape": [2, 2, 1024],
661
  "data": {
662
  "kind": "cycle",
663
- "values": [1.0, -2.0, 0.5, 3.25, -1.5, 2.0, -0.75, 4.0, -3.5, 1.25, 0.0, -2.25, 5.0, -4.0, 2.75, -1.0]
664
  }
665
  }
666
  },
@@ -774,7 +773,7 @@
774
  "x": {
775
  "dtype": "float32",
776
  "shape": [2, 260],
777
- "data": { "kind": "cycle", "values": ["NaN", "Infinity", 1.0, 2.0] }
778
  }
779
  },
780
  "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0, "allowNaN": true } }
@@ -936,6 +935,60 @@
936
  }
937
  },
938
  "outputs": { "y": { "dtype": "float16", "shape": [64], "tolerance": 0.05, "relTolerance": 0.002 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  }
940
  ]
941
  }
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "contiguous_suffix_axes12_parallel",
 
15
  {
16
  "name": "all_axes_flat_rank1_boundary_8192",
17
  "provenance": {
18
+ "notes": "Exactly 8,192 rank-1 elements exercise the inclusive lower boundary of the parallel full reduction."
19
  },
20
  "attrs": { "axes": [0], "keepdims": 0 },
21
  "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
 
72
  "x": {
73
  "dtype": "float32",
74
  "shape": [3, 2],
75
+ "data": { "kind": "values", "values": ["Infinity", "-Infinity", 2.0, "-Infinity", 3.0, 0.0] }
76
  }
77
  },
78
+ "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0.000001 } }
79
  },
80
  {
81
  "name": "axis0_nan_overrides_positive_infinity",
 
344
  "name": "onnx_backend_keepdims_example_f32_projection",
345
  "provenance": {
346
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_keepdims_example",
347
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
348
  },
349
  "attrs": { "axes": [1], "keepdims": 1 },
350
  "inputs": {
 
360
  "name": "onnx_backend_negative_axis_keepdims_example_f32_projection",
361
  "provenance": {
362
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_negative_axes_keepdims_example",
363
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
364
  },
365
  "attrs": { "axes": [-2], "keepdims": 1 },
366
  "inputs": {
 
376
  "name": "onnx_backend_do_not_keepdims_example_f32_projection",
377
  "provenance": {
378
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_do_not_keepdims_example",
379
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
380
  },
381
  "attrs": { "axes": [1], "keepdims": 0 },
382
  "inputs": {
 
392
  "name": "onnx_backend_keepdims_random_f32_projection",
393
  "provenance": {
394
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_keepdims_random",
395
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
396
  },
397
  "attrs": { "axes": [1], "keepdims": 1 },
398
  "inputs": {
 
411
  "name": "onnx_backend_negative_axis_keepdims_random_f32_projection",
412
  "provenance": {
413
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_negative_axes_keepdims_random",
414
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
415
  },
416
  "attrs": { "axes": [-2], "keepdims": 1 },
417
  "inputs": {
 
430
  "name": "onnx_backend_do_not_keepdims_random_f32_projection",
431
  "provenance": {
432
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_do_not_keepdims_random",
433
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
434
  },
435
  "attrs": { "axes": [1], "keepdims": 0 },
436
  "inputs": {
 
449
  "name": "onnx_backend_default_axes_keepdims_example_f32_projection",
450
  "provenance": {
451
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_default_axes_keepdims_example",
452
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
453
  },
454
  "attrs": { "keepdims": 1 },
455
  "inputs": {
 
489
  "name": "onnx_backend_default_axes_keepdims_random_f32_projection",
490
  "provenance": {
491
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_reduce_log_sum_exp_default_axes_keepdims_random",
492
+ "notes": "The official ONNX backend fixture uses float64; this case preserves its values in supported float32 storage."
493
  },
494
  "attrs": { "keepdims": 1 },
495
  "inputs": {
 
659
  "shape": [2, 2, 1024],
660
  "data": {
661
  "kind": "cycle",
662
+ "values": [1.0, -2.0, 0.5, 3.25, -1.5, 2.0, -0.75, 4.0, -3.5, 1.25, 0.0, -2.25, 5.0, -4.0, 2.75, -1.0, 6.5]
663
  }
664
  }
665
  },
 
773
  "x": {
774
  "dtype": "float32",
775
  "shape": [2, 260],
776
+ "data": { "kind": "cycle", "values": ["Infinity", 1.0, 2.0, 3.0], "nanStart": 0.0, "nanCount": 1.0 }
777
  }
778
  },
779
  "outputs": { "y": { "dtype": "float32", "shape": [2], "tolerance": 0, "allowNaN": true } }
 
935
  }
936
  },
937
  "outputs": { "y": { "dtype": "float16", "shape": [64], "tolerance": 0.05, "relTolerance": 0.002 } }
938
+ },
939
+ {
940
+ "name": "subgroup_rows_last_axis_f32_96x256",
941
+ "attrs": { "axes": [-1], "keepdims": 0 },
942
+ "inputs": {
943
+ "x": {
944
+ "dtype": "float32",
945
+ "shape": [96, 256],
946
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
947
+ }
948
+ },
949
+ "outputs": { "y": { "dtype": "float32", "shape": [96], "tolerance": 0.0002, "relTolerance": 0.0001 } }
950
+ },
951
+ {
952
+ "name": "subgroup_rows_last_axis_f32_rank3_2x40x1024",
953
+ "attrs": { "axes": [-1], "keepdims": 0 },
954
+ "inputs": {
955
+ "x": {
956
+ "dtype": "float32",
957
+ "shape": [2, 40, 1024],
958
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.25 }
959
+ }
960
+ },
961
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 40], "tolerance": 0.0002, "relTolerance": 0.0001 } }
962
+ },
963
+ {
964
+ "name": "subgroup_rows_last_axis_f16_80x1024",
965
+ "attrs": { "axes": [1], "keepdims": 0 },
966
+ "inputs": {
967
+ "x": {
968
+ "dtype": "float16",
969
+ "shape": [80, 1024],
970
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2 }
971
+ }
972
+ },
973
+ "outputs": { "y": { "dtype": "float16", "shape": [80], "tolerance": 0.05, "relTolerance": 0.002 } }
974
+ },
975
+ {
976
+ "name": "subgroup_rows_last_axis_nan_inf_and_finite_rows_64x260",
977
+ "requires": { "features": ["subgroups"] },
978
+ "attrs": { "axes": [1], "keepdims": 0 },
979
+ "inputs": {
980
+ "x": {
981
+ "dtype": "float32",
982
+ "shape": [64, 260],
983
+ "data": {
984
+ "kind": "cycle",
985
+ "values": [0.0, 0.7232, 1.3486, 1.7914, 1.9918, "NaN", 1.5931, 1.0481, 0.3612, -0.3746, -1.0597, -1.6013, -1.9263, -1.9905, -1.7853, -1.3385, -0.7105, 0.0136, 0.7359, 1.3586, 1.7974, 1.993, 1.9187, 1.5849, 1.0365, 0.3478, -0.388, -1.0712, -1.6095, -1.9299, -1.9891, -1.7791, -1.3283, -0.6978, 0.0273, 0.7486, 1.3686, 1.8034, 1.994, 1.9149, 1.5765, 1.0248, 0.3343, -0.4013, -1.0827, -1.6175, -1.9334, -1.9876, -1.7728, -1.3181, -0.685, 0.0409, 0.7612, 1.3785, 1.8092, 1.9951, 1.9109, 1.5681, 1.013, 0.3209, -0.4147, -1.0941, -1.6255, -1.9369, -1.9861, -1.7665, -1.3078, -0.6721, 0.0545, 0.7738, 1.3883, 1.815, 1.996, 1.9068, 1.5596, 1.0013, 0.3074, -0.428, -1.1055, -1.6334, -1.9402, -1.9844, -1.7601, -1.2975, -0.6593, 0.0681, 0.7863, 1.3981, 1.8207, 1.9968, 1.9027, 1.551, 0.9895, 0.294, -0.4413, -1.1168, -1.6412, -1.9435, -1.9827, -1.7535, "Infinity", -0.6464, 0.0818, 0.7988, 1.4078, 1.8263, 1.9975, 1.8984, 1.5424, 0.9776, 0.2805, -0.4546, -1.1281, -1.649, -1.9466, -1.9808, -1.747, -1.2766, -0.6335, 0.0954, 0.8113, 1.4175, 1.8318, 1.9981, 1.8941, 1.5337, 0.9657, 0.267, -0.4678, -1.1393, -1.6566, -1.9497, -1.9789, -1.7403, -1.2661, -0.6206, 0.109, 0.8238, 1.4271, 1.8372, 1.9987, 1.8897, 1.5249, 0.9537, 0.2535, -0.4811, -1.1505, -1.6642, -1.9527, -1.9769, -1.7335, -1.2555, -0.6076, 0.1226, 0.8362, 1.4366, 1.8425, 1.9991, 1.8852, 1.516, 0.9417, 0.2399, -0.4943, -1.1616, -1.6718, -1.9556, -1.9748, -1.7267, -1.2449, -0.5946, 0.1362, 0.8485, 1.446, 1.8478, 1.9995, 1.8806, 1.5071, 0.9297, 0.2264, -0.5075, -1.1727, -1.6792, -1.9584, -1.9726, -1.7198, -1.2342, -0.5816, 0.1498, 0.8608, 1.4554, 1.853, 1.9997, 1.8759, 1.4981, 0.9176, 0.2129, -0.5207, -1.1837, -1.6866, -1.9611, "-Infinity", -1.7128, -1.2234, -0.5685, 0.1634, 0.8731, 1.4647, 1.8581, 1.9999, 1.8711, 1.489, 0.9054, 0.1993, -0.5338, -1.1947, -1.6939, -1.9638, -1.9679, -1.7057, -1.2126, -0.5554, 0.177, 0.8854, 1.474, 1.8631, 2.0, 1.8662, 1.4799, 0.8933, 0.1857, -0.5469, -1.2056, -1.7011, -1.9663, -1.9654, -1.6985, -1.2017, -0.5423, 0.1905, 0.8976, 1.4831, 1.868, 2.0, 1.8613, 1.4707, 0.8811, 0.1722, -0.56, -1.2164, -1.7082, -1.9688, -1.9629, -1.6913, -1.1908, -0.5292, 0.2041, 0.9097, 1.4922, 1.8728, 1.9999, 1.8563, 1.4614, 0.8688, 0.1586, -0.5731, -1.2272, -1.7152, -1.9711, -1.9602, -1.684, -1.1798, -0.516, 0.2176, 0.9218, 1.5013, 1.8775, 1.9997, 1.8512, 1.4521, 0.8565, 0.145, -0.5862, -1.238, -1.7222, -1.9734, -1.9574, -1.6766, -1.1688, -0.5028, 0.2312, 0.9339, 1.5103, 1.8822, 1.9994, 1.846, 1.4427, 0.8442, 0.1314, -0.5992, -1.2486]
986
+ }
987
+ }
988
+ },
989
+ "outputs": {
990
+ "y": { "dtype": "float32", "shape": [64], "tolerance": 0.0001, "allowNaN": true, "relTolerance": 0.0001 }
991
+ }
992
  }
993
  ]
994
  }