Xenova HF Staff commited on
Commit
a66f8d4
·
verified ·
1 Parent(s): 540c327

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `ReduceLogSum` spec](https://onnx.ai/onnx/operators/onnx__ReduceLo
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `data` | `x` | `T` | — | — | Input tensor to reduce. | required |
24
 
25
  ## Outputs
26
 
27
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
- | `reduced` | `y` | `T` | derived | — | Reduced output tensor containing the log of the summed elements. | 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 (default in spec), the reduced dimension is retained with size 1; if 0, it is removed from the output shape. |
38
  | `noop_with_empty_axes` | `0` | When 1 and axes is empty, acts as a no-op applying only the non-reduction step (log of input); when 0 (default), 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
@@ -60,21 +60,25 @@ Some implementation variants require `subgroups`. These are route-specific capab
60
  - [`reduce-axis0-tilecols.wgsl.jinja`](build/webgpu/reduce-axis0-tilecols.wgsl.jinja)
61
  - [`reduce-flat-partial.wgsl.jinja`](build/webgpu/reduce-flat-partial.wgsl.jinja)
62
  - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
 
63
  - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
64
  - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
65
  - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
66
 
67
  ## Use with `@huggingface/kernels`
68
 
69
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
70
 
71
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
72
 
73
- - `y`
74
 
75
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
76
 
77
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
78
 
79
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
80
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `data` | `T` | — | — | 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 | — | Reduced output tensor containing the log of the summed elements. | 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 (default in spec), the reduced dimension is retained with size 1; if 0, it is removed from the output shape. |
39
  | `noop_with_empty_axes` | `0` | When 1 and axes is empty, acts as a no-op applying only the non-reduction step (log of input); when 0 (default), 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
 
60
  - [`reduce-axis0-tilecols.wgsl.jinja`](build/webgpu/reduce-axis0-tilecols.wgsl.jinja)
61
  - [`reduce-flat-partial.wgsl.jinja`](build/webgpu/reduce-flat-partial.wgsl.jinja)
62
  - [`reduce-noop-empty-axes.wgsl.jinja`](build/webgpu/reduce-noop-empty-axes.wgsl.jinja)
63
+ - [`reduce-row-subgroup-rows.wgsl.jinja`](build/webgpu/reduce-row-subgroup-rows.wgsl.jinja)
64
  - [`reduce-row-subgroup.wgsl.jinja`](build/webgpu/reduce-row-subgroup.wgsl.jinja)
65
  - [`reduce-row-tree.wgsl.jinja`](build/webgpu/reduce-row-tree.wgsl.jinja)
66
  - [`reduce-serial-axis.wgsl.jinja`](build/webgpu/reduce-serial-axis.wgsl.jinja)
67
 
68
  ## Use with `@huggingface/kernels`
69
 
70
+ ```sh
71
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
72
+ ```
73
 
74
+ 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.
75
 
76
+ This example supplies explicit metadata for:
77
 
78
+ - `y`
79
 
80
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
81
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
82
 
83
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
84
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.ReduceLogSum",
3
  "cases": [
4
  {
5
  "name": "1024x1024_axis1",
@@ -52,7 +51,7 @@
52
  "name": "reducelogsum-rank3-spatial-axes12-f32-128x256x256-pathology",
53
  "preset": "stress",
54
  "provenance": {
55
- "source": "authored for branch coverage",
56
  "notes": "Rank-3 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Positive inputs keep the post-reduction logarithm finite."
57
  },
58
  "attrs": { "axes": [1, 2], "keepdims": 1 },
@@ -73,7 +72,7 @@
73
  "name": "reducelogsum-spatial-axes23-f32-2x64x256x256-pathology",
74
  "preset": "stress",
75
  "provenance": {
76
- "source": "authored for branch coverage",
77
  "notes": "Rank-4 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Positive inputs keep the post-reduction logarithm finite."
78
  },
79
  "attrs": { "axes": [2, 3], "keepdims": 1 },
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "1024x1024_axis1",
 
51
  "name": "reducelogsum-rank3-spatial-axes12-f32-128x256x256-pathology",
52
  "preset": "stress",
53
  "provenance": {
54
+ "source": "repository-authored",
55
  "notes": "Rank-3 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Positive inputs keep the post-reduction logarithm finite."
56
  },
57
  "attrs": { "axes": [1, 2], "keepdims": 1 },
 
72
  "name": "reducelogsum-spatial-axes23-f32-2x64x256x256-pathology",
73
  "preset": "stress",
74
  "provenance": {
75
+ "source": "repository-authored",
76
  "notes": "Rank-4 multi-axis reduction with 128 output lanes, each serially scanning a 256x256 plane. Positive inputs keep the post-reduction logarithm finite."
77
  },
78
  "attrs": { "axes": [2, 3], "keepdims": 1 },
build/webgpu/manifest.json CHANGED
@@ -2,455 +2,251 @@
2
  "domain": "ai.onnx",
3
  "name": "ReduceLogSum",
4
  "sinceVersion": 18,
5
- "description": "Computes `log(sum(elements))` of the input tensor along the specified axes. The output rank matches the input when `keepdims` is 1; reduced dimensions are pruned when `keepdims` is 0. Reducing an empty set of values yields negative infinity.",
6
- "inputs": [{ "role": "data", "dtype": "T", "description": "Input tensor to reduce." }],
7
- "outputs": [
8
- {
9
- "role": "reduced",
10
  "dtype": "T",
11
- "rank": "ranks.data if attrs.keepdims == 1 or ((attrs.axes | length) == 0 and attrs.noop_with_empty_axes == 1) else (ranks.data - (attrs.axes | length) if (attrs.axes | length) > 0 else 0)",
12
- "description": "Reduced output tensor containing the log of the summed elements."
13
  }
14
- ],
15
- "attributes": { "keepdims": 1, "noop_with_empty_axes": 0, "axes": [] },
16
- "attributeDescriptions": {
17
- "keepdims": "If 1 (default in spec), the reduced dimension is retained with size 1; if 0, it is removed from the output shape.",
18
- "noop_with_empty_axes": "When 1 and axes is empty, acts as a no-op applying only the non-reduction step (log of input); when 0 (default), 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"] },
23
- "args": {
24
- "x": { "kind": "tensor", "semantic": "data", "role": "input" },
25
- "y": { "kind": "tensor", "semantic": "reduced", "role": "output" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  },
27
  "derive": {
28
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
29
  "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
30
  "treeWorkgroupOk": "reduceWorkgroupSize > 0 and pow2ceil(reduceWorkgroupSize) == reduceWorkgroupSize and reduceWorkgroupSize * dtypeBytes(\"float32\") <= device.limits.maxComputeWorkgroupStorageSize",
31
  "subgroupWorkgroupFloor": "min(reduceWorkgroupSize, max(1, device.adapterInfo.subgroupMaxSize))",
32
- "lastAxisRows": "rows(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
33
- "lastAxisCols": "dim(shapes.data, ranks.data - 1) if ranks.data > 0 else 1",
34
  "rowSerialPreferred": "lastAxisRows >= tunables.ROW_SERIAL_MIN_ROWS and lastAxisCols <= tunables.ROW_SERIAL_MAX_COLS",
35
- "axis0Rows": "dim(shapes.data, 0) if ranks.data >= 2 else 0",
36
- "axis0Cols": "dim(shapes.data, 1) if ranks.data >= 2 else 0",
37
  "axis0SplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axis0Rows, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
38
  "axis0SplitScratchBytes": "axis0SplitCount * axis0Cols * dtypeBytes(\"float32\")",
39
- "axis0SplitPathFits": "axis0SplitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(axis0Cols, reduceWorkgroupSize), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension and axis0SplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axis0SplitScratchBytes <= device.limits.maxBufferSize",
40
- "reduceAxis": "(attrs.axes[0] + ranks.data if attrs.axes[0] < 0 else attrs.axes[0]) if ((attrs.axes | length) == 1 and isUniqueIntList(attrs.axes, 0 - ranks.data, ranks.data, 1)) else ranks.data",
41
- "axisSplitDim": "dim(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 0",
42
- "axisSplitInner": "inner(shapes.data, reduceAxis) if ranks.data >= 2 and reduceAxis < ranks.data else 1",
43
- "axisSplitOutputs": "numel(shapes.reduced)",
44
  "axisSplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axisSplitDim, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
45
  "axisSplitScratchBytes": "axisSplitCount * axisSplitOutputs * 4",
46
- "axisSplitPathFits": "axisSplitCount <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(axisSplitOutputs, reduceWorkgroupSize), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension and axisSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and axisSplitScratchBytes <= device.limits.maxBufferSize",
47
  "axis0TilePathFits": "treeWorkgroupOk and tunables.AXIS0_TILE_COLS > 0 and tunables.AXIS0_TILE_COLS <= reduceWorkgroupSize and reduceWorkgroupSize % tunables.AXIS0_TILE_COLS == 0",
48
- "flatItems": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)",
49
  "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
50
  "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
51
- "flatPathFits": "treeWorkgroupOk and flatSplitCount <= device.limits.maxComputeWorkgroupsPerDimension and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
52
- "flatParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.reduced) == 1 and numel(shapes.data) >= tunables.FULL_REDUCE_MIN_ELEMENTS and flatPathFits",
53
- "contiguousSuffixParallelCovered": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T) and numel(shapes.reduced) > 0 and numel(shapes.data) % numel(shapes.reduced) == 0 and numel(shapes.data) / numel(shapes.reduced) >= tunables.CONTIGUOUS_SUFFIX_MIN_COLS and ((ranks.data == 3 and hasAxis(attrs.axes, 0, 3) == false and hasAxis(attrs.axes, 1, 3) and hasAxis(attrs.axes, 2, 3) and numel(shapes.reduced) == dim(shapes.data, 0)) or (ranks.data == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) == false and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.reduced) == dim(shapes.data, 0) * dim(shapes.data, 1)) or (ranks.data == 4 and hasAxis(attrs.axes, 0, 4) == false and hasAxis(attrs.axes, 1, 4) and hasAxis(attrs.axes, 2, 4) and hasAxis(attrs.axes, 3, 4) and numel(shapes.reduced) == dim(shapes.data, 0)))"
54
- },
55
- "tunables": {
56
- "WORKGROUP_SIZE": 256,
57
- "VECTOR_WIDTH": 4,
58
- "ROW_PARALLEL_MIN_COLS": 64,
59
- "SUBGROUP_MIN_COLS": 256,
60
- "SUBGROUP_SMALL_ROW_LIMIT": 32768,
61
- "AXIS0_SPLIT_MIN_ROWS": 8192,
62
- "AXIS0_SPLIT_TARGET_ROWS": 256,
63
- "AXIS0_MAX_SPLITS": 128,
64
- "AXIS0_TILE_MIN_ROWS": 64,
65
- "AXIS0_TILE_MIN_COLS": 16,
66
- "AXIS0_TILE_COLS": 16,
67
- "AXIS_SPLIT_TILE_COLS": 8,
68
- "FULL_REDUCE_MIN_ELEMENTS": 8192,
69
- "FULL_REDUCE_MAX_SPLITS": 256,
70
- "CONTIGUOUS_SUFFIX_MIN_COLS": 256,
71
- "ROW_SERIAL_MIN_ROWS": 8192,
72
- "ROW_SERIAL_MAX_COLS": 1024
73
  },
74
- "bindingSets": {
75
- "elementwise": [
76
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
77
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
78
- {
79
- "name": "params",
80
- "semantic": "kernel.params",
81
- "buffer": { "type": "uniform" },
82
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.reduced)" }] }
83
- }
84
- ],
85
- "lastAxisVec4": [
86
- {
87
- "name": "x",
88
- "arg": "x",
89
- "semantic": "data",
90
- "buffer": { "type": "read-only-storage" },
91
- "elementType": "$vectorScalar"
92
- },
93
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
94
- {
95
- "name": "params",
96
- "semantic": "kernel.params",
97
- "buffer": { "type": "uniform" },
98
- "struct": {
99
- "name": "Params",
100
- "fields": [
101
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
102
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1) / tunables.VECTOR_WIDTH" }
103
- ]
104
- }
105
- }
106
- ],
107
- "lastAxisScalar": [
108
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
109
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
110
- {
111
- "name": "params",
112
- "semantic": "kernel.params",
113
- "buffer": { "type": "uniform" },
114
- "struct": {
115
- "name": "Params",
116
- "fields": [
117
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
118
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
119
- ]
120
- }
121
- }
122
- ],
123
- "lastAxisScalarSubgroup": [
124
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
125
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
126
- {
127
- "name": "params",
128
- "semantic": "kernel.params",
129
- "buffer": { "type": "uniform" },
130
- "struct": {
131
- "name": "Params",
132
- "fields": [
133
- { "name": "rows", "type": "u32", "value": "rows(shapes.data, ranks.data - 1)" },
134
- { "name": "chunkCount", "type": "u32", "value": "dim(shapes.data, ranks.data - 1)" }
135
- ]
136
- }
137
- }
138
- ],
139
- "scalar": [
140
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
141
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
142
- {
143
- "name": "params",
144
- "semantic": "kernel.params",
145
- "buffer": { "type": "uniform" },
146
- "struct": {
147
- "name": "Params",
148
- "fields": [
149
- { "name": "rows", "type": "u32", "value": "1" },
150
- { "name": "cols", "type": "u32", "value": "1" },
151
- { "name": "outCount", "type": "u32", "value": "1" }
152
- ]
153
- }
154
- }
155
- ],
156
- "rank1Axis0": [
157
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
158
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
159
- {
160
- "name": "params",
161
- "semantic": "kernel.params",
162
- "buffer": { "type": "uniform" },
163
- "struct": {
164
- "name": "Params",
165
- "fields": [
166
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
167
- { "name": "cols", "type": "u32", "value": "1" },
168
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
169
- ]
170
- }
171
- }
172
- ],
173
- "rank2Serial": [
174
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
175
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
176
- {
177
- "name": "params",
178
- "semantic": "kernel.params",
179
- "buffer": { "type": "uniform" },
180
- "struct": {
181
- "name": "Params",
182
- "fields": [
183
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
184
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
185
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
186
- ]
187
- }
188
- }
189
- ],
190
- "rank2SerialAxis1": [
191
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
192
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
193
- {
194
- "name": "params",
195
- "semantic": "kernel.params",
196
- "buffer": { "type": "uniform" },
197
- "struct": {
198
- "name": "Params",
199
- "fields": [
200
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" },
201
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
202
- ]
203
- }
204
- }
205
- ],
206
- "axis0Parallel": [
207
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
208
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
209
- {
210
- "name": "params",
211
- "semantic": "kernel.params",
212
- "buffer": { "type": "uniform" },
213
- "struct": {
214
- "name": "Params",
215
- "fields": [
216
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
217
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
218
- ]
219
- }
220
- }
221
- ],
222
- "fullReduceSerial": [
223
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
224
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
225
- {
226
- "name": "params",
227
- "semantic": "kernel.params",
228
- "buffer": { "type": "uniform" },
229
- "struct": {
230
- "name": "Params",
231
- "fields": [
232
- { "name": "rows", "type": "u32", "value": "numel(shapes.data)" },
233
- { "name": "cols", "type": "u32", "value": "1" },
234
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
235
- ]
236
- }
237
- }
238
- ],
239
- "axisSplitReduce": [
240
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
241
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
242
- {
243
- "name": "params",
244
- "semantic": "kernel.params",
245
- "buffer": { "type": "uniform" },
246
- "struct": {
247
- "name": "Params",
248
- "fields": [
249
- { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
250
- { "name": "inner", "type": "u32", "value": "axisSplitInner" },
251
- { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
252
- ]
253
- }
254
- }
255
- ],
256
- "axisSplitCombine": [
257
- {
258
- "name": "partials",
259
- "semantic": "partials",
260
- "buffer": { "type": "read-only-storage" },
261
- "elementType": "$partialElement"
262
- },
263
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
264
- {
265
- "name": "params",
266
- "semantic": "kernel.params",
267
- "buffer": { "type": "uniform" },
268
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }] }
269
- }
270
- ],
271
- "axis0SplitReduce": [
272
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
273
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "$partialElement" },
274
- {
275
- "name": "params",
276
- "semantic": "kernel.params",
277
- "buffer": { "type": "uniform" },
278
- "struct": {
279
- "name": "Params",
280
- "fields": [
281
- { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" },
282
- { "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }
283
- ]
284
- }
285
- }
286
- ],
287
- "axis0SplitCombine": [
288
- {
289
- "name": "partials",
290
- "semantic": "partials",
291
- "buffer": { "type": "read-only-storage" },
292
- "elementType": "$partialElement"
293
- },
294
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
295
- {
296
- "name": "params",
297
- "semantic": "kernel.params",
298
- "buffer": { "type": "uniform" },
299
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "dim(shapes.data, 1)" }] }
300
- }
301
- ],
302
- "rankNAxis": [
303
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
304
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
305
- {
306
- "name": "params",
307
- "semantic": "kernel.params",
308
- "buffer": { "type": "uniform" },
309
- "struct": {
310
- "name": "Params",
311
- "fields": [
312
- { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
313
- { "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }
314
- ]
315
- }
316
- }
317
- ],
318
- "flatPartialF32": [
319
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
320
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "f32" },
321
- {
322
- "name": "params",
323
- "semantic": "kernel.params",
324
- "buffer": { "type": "uniform" },
325
- "struct": {
326
- "name": "Params",
327
- "fields": [
328
- { "name": "count4", "type": "u32", "value": "floor(numel(shapes.data) / tunables.VECTOR_WIDTH)" },
329
- { "name": "numel", "type": "u32", "value": "numel(shapes.data)" }
330
- ]
331
- }
332
- }
333
- ],
334
- "flatCombineF32": [
335
- { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
336
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
337
- {
338
- "name": "params",
339
- "semantic": "kernel.params",
340
- "buffer": { "type": "uniform" },
341
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "1" }] }
342
- }
343
- ],
344
- "suffixVec4": [
345
- {
346
- "name": "x",
347
- "arg": "x",
348
- "semantic": "data",
349
- "buffer": { "type": "read-only-storage" },
350
- "elementType": "$vectorScalar"
351
- },
352
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
353
- {
354
- "name": "params",
355
- "semantic": "kernel.params",
356
- "buffer": { "type": "uniform" },
357
- "struct": {
358
- "name": "Params",
359
- "fields": [
360
- { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
361
- {
362
- "name": "chunkCount",
363
- "type": "u32",
364
- "value": "numel(shapes.data) / numel(shapes.reduced) / tunables.VECTOR_WIDTH"
365
- }
366
- ]
367
- }
368
- }
369
- ],
370
- "suffixScalar": [
371
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
372
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
373
- {
374
- "name": "params",
375
- "semantic": "kernel.params",
376
- "buffer": { "type": "uniform" },
377
- "struct": {
378
- "name": "Params",
379
- "fields": [
380
- { "name": "rows", "type": "u32", "value": "numel(shapes.reduced)" },
381
- { "name": "cols", "type": "u32", "value": "numel(shapes.data) / numel(shapes.reduced)" }
382
- ]
383
- }
384
- }
385
- ],
386
- "multiAxis": [
387
- { "name": "x", "arg": "x", "semantic": "data", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
388
- { "name": "y", "arg": "y", "semantic": "reduced", "buffer": { "type": "storage" }, "elementType": "$T" },
389
- {
390
- "name": "params",
391
- "semantic": "kernel.params",
392
- "buffer": { "type": "uniform" },
393
- "struct": {
394
- "name": "Params",
395
- "fields": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.reduced)" }]
396
- }
397
- }
398
- ]
399
  },
400
  "variants": [
401
  {
402
  "id": "contiguous_suffix_subgroup_vec4",
403
  "priority": 30,
 
404
  "requires": { "features": ["subgroups"] },
405
- "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0"],
406
- "constants": {
407
  "scalar": "dtypes.T",
408
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
409
- "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH))))"
410
  },
411
  "passes": [
412
  {
413
  "id": "main",
414
  "name": "ReduceLogSum.ContiguousSuffixSubgroupVec4",
415
- "source": {
416
- "shader": "reduce-row-subgroup.wgsl.jinja",
417
- "inputs": {
418
- "op": "\"logsum\"",
419
- "vec4": true,
420
- "castF32": "dtypes.T == \"f16\"",
421
- "usesF16": "dtypes.T == \"f16\""
422
- }
423
  },
424
- "subgroupCollectivesWidth": "portable",
425
- "bindings": "suffixVec4",
426
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
427
  }
428
  ]
429
  },
430
  {
431
  "id": "contiguous_suffix_tree_vec4",
432
  "priority": 22,
433
- "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.data) / numel(shapes.reduced)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
434
- "constants": {
435
  "scalar": "dtypes.T",
436
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
437
- "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.data) / numel(shapes.reduced), tunables.VECTOR_WIDTH)))"
438
  },
439
  "passes": [
440
  {
441
  "id": "main",
442
  "name": "ReduceLogSum.ContiguousSuffixTreeVec4",
443
- "source": {
444
- "shader": "reduce-row-tree.wgsl.jinja",
445
- "inputs": {
446
- "op": "\"logsum\"",
447
- "vec4": true,
448
- "castF32": "dtypes.T == \"f16\"",
449
- "usesF16": "dtypes.T == \"f16\""
450
- }
451
  },
452
- "bindings": "suffixVec4",
453
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
454
  }
455
  ]
456
  },
@@ -458,101 +254,138 @@
458
  "id": "contiguous_suffix_tree",
459
  "priority": 21,
460
  "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
461
- "constants": {
462
- "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.data) / numel(shapes.reduced)))",
463
  "scalar": "dtypes.T"
464
  },
465
  "passes": [
466
  {
467
  "id": "main",
468
  "name": "ReduceLogSum.ContiguousSuffixTree",
469
- "source": {
470
- "shader": "reduce-row-tree.wgsl.jinja",
471
- "inputs": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
472
- },
473
- "bindings": "suffixScalar",
474
- "dispatch": { "workgroups": "numel(shapes.reduced)" }
475
  }
476
  ]
477
  },
478
  {
479
  "id": "multi_axis_rank3",
480
  "priority": 8,
481
- "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)"],
482
  "passes": [
483
  {
484
  "id": "main",
485
  "name": "ReduceLogSum.MultiAxisRank3",
486
- "source": {
487
- "shader": "reduce-serial-axis.wgsl.jinja",
488
- "inputs": {
489
- "op": "\"logsum\"",
490
- "indexing": "\"multiaxis\"",
491
- "castF32": "dtypes.T == \"f16\"",
492
- "usesF16": "dtypes.T == \"f16\"",
493
- "rank": 3,
494
- "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
495
- "dataShape": "shapes.data",
496
- "outputShape": "shapes.reduced",
497
- "outputRank": "ranks.reduced",
498
- "keepDims": "attrs.keepdims != 0",
499
- "logicalBool": "tensorDtypes.data == \"bool\""
500
- }
501
  },
502
- "bindings": "multiAxis",
503
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
504
  }
505
  ]
506
  },
507
  {
508
  "id": "multi_axis_rank4",
509
  "priority": 8,
510
- "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))"],
511
  "passes": [
512
  {
513
  "id": "main",
514
  "name": "ReduceLogSum.MultiAxisRank4",
515
- "source": {
516
- "shader": "reduce-serial-axis.wgsl.jinja",
517
- "inputs": {
518
- "op": "\"logsum\"",
519
- "indexing": "\"multiaxis\"",
520
- "castF32": "dtypes.T == \"f16\"",
521
- "usesF16": "dtypes.T == \"f16\"",
522
- "rank": 4,
523
- "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
524
- "dataShape": "shapes.data",
525
- "outputShape": "shapes.reduced",
526
- "outputRank": "ranks.reduced",
527
- "keepDims": "attrs.keepdims != 0",
528
- "logicalBool": "tensorDtypes.data == \"bool\""
529
- }
530
  },
531
- "bindings": "multiAxis",
532
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
533
  }
534
  ]
535
  },
536
  {
537
  "id": "noop_empty_axes",
538
  "priority": 40,
539
- "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.data, shapes.reduced)"],
 
540
  "passes": [
541
  {
542
  "id": "main",
543
  "name": "ReduceLogSum.NoopEmptyAxes",
544
- "source": { "shader": "reduce-noop-empty-axes.wgsl.jinja", "inputs": { "op": "\"log\"" } },
545
- "bindings": "elementwise",
546
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
  }
548
  ]
549
  },
550
  {
551
  "id": "tree_last_axis_vec4",
552
  "priority": 23,
 
553
  "demoteWhen": ["rowSerialPreferred"],
554
- "when": ["not flatParallelCovered", "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"],
555
- "constants": {
556
  "scalar": "dtypes.T",
557
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
558
  "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
@@ -561,133 +394,135 @@
561
  {
562
  "id": "main",
563
  "name": "ReduceLogSum.TreeRowVec4",
564
- "source": {
565
- "shader": "reduce-row-tree.wgsl.jinja",
566
- "inputs": {
567
- "op": "\"logsum\"",
568
- "vec4": true,
569
- "castF32": "dtypes.T == \"f16\"",
570
- "usesF16": "dtypes.T == \"f16\""
571
- }
572
  },
573
- "bindings": "lastAxisVec4",
574
- "dispatch": { "workgroups": "lastAxisRows" }
575
  }
576
  ]
577
  },
578
  {
579
  "id": "rank0_scalar",
580
  "priority": 40,
581
- "constants": { "axis": 0 },
582
- "when": ["f16Ok(dtypes.T)", "ranks.data == 0", "ranks.reduced == 0"],
583
  "passes": [
584
  {
585
  "id": "main",
586
  "name": "ReduceLogSum.Rank0Scalar",
587
- "source": {
588
- "shader": "reduce-serial-axis.wgsl.jinja",
589
- "inputs": {
590
- "op": "\"logsum\"",
591
- "indexing": "\"axis2d\"",
592
- "castF32": "dtypes.T == \"f16\"",
593
- "usesF16": "dtypes.T == \"f16\"",
594
- "logicalBool": "tensorDtypes.data == \"bool\""
595
- }
596
  },
597
- "bindings": "scalar",
598
  "dispatch": { "x": 1 }
599
  }
600
  ]
601
  },
602
  {
603
  "id": "rank1_axis0",
604
- "constants": { "axis": 0 },
605
- "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))"],
606
  "passes": [
607
  {
608
  "id": "main",
609
  "name": "ReduceLogSum.Rank1Axis0",
610
- "source": {
611
- "shader": "reduce-serial-axis.wgsl.jinja",
612
- "inputs": {
613
- "op": "\"logsum\"",
614
- "indexing": "\"axis2d\"",
615
- "castF32": "dtypes.T == \"f16\"",
616
- "usesF16": "dtypes.T == \"f16\"",
617
- "logicalBool": "tensorDtypes.data == \"bool\""
618
- }
619
  },
620
- "bindings": "rank1Axis0",
621
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
622
  }
623
  ]
624
  },
625
  {
626
  "id": "axis1_parallel",
627
  "priority": 20,
 
628
  "demoteWhen": ["rowSerialPreferred"],
629
- "when": ["not flatParallelCovered", "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"],
630
- "constants": { "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.data, ranks.data - 1)))" },
631
  "passes": [
632
  {
633
  "id": "main",
634
  "name": "ReduceLogSum.Axis1Parallel",
635
- "source": {
636
- "shader": "reduce-row-tree.wgsl.jinja",
637
- "inputs": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
638
- },
639
- "bindings": "lastAxisScalar",
640
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
 
 
641
  }
642
  ]
643
  },
644
  {
645
  "id": "axis_split",
646
  "priority": 24,
647
- "when": ["not flatParallelCovered", "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"],
648
- "derive": { "splitCount": "axisSplitCount" },
649
- "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
 
 
 
 
650
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
651
  "passes": [
652
  {
653
  "id": "split_reduce",
654
  "name": "ReduceLogSum.AxisSplitReduce",
655
- "source": {
656
- "shader": "reduce-axis-split-reduce.wgsl.jinja",
657
- "inputs": {
658
- "op": "\"logsum\"",
659
- "workgroupSize": "reduceWorkgroupSize",
660
- "split": "splitCount",
661
- "castF32": "dtypes.T == \"f16\"",
662
- "usesF16": "dtypes.T == \"f16\""
663
- }
664
  },
665
- "bindings": "axisSplitReduce",
666
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
 
 
 
 
667
  },
668
  {
669
  "id": "combine",
670
  "name": "ReduceLogSum.AxisSplitCombine",
671
- "source": {
672
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
673
- "inputs": {
674
- "op": "\"logsum\"",
675
- "workgroupSize": "reduceWorkgroupSize",
676
- "split": "splitCount",
677
- "outputF16": "dtypes.T == \"f16\""
678
- }
679
- },
680
- "bindings": "axisSplitCombine",
681
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
682
  }
683
  ]
684
  },
685
  {
686
  "id": "axis_split_tiled_narrow",
687
  "priority": 25,
688
- "when": ["not flatParallelCovered", "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"],
689
- "derive": { "splitCount": "axisSplitCount" },
690
- "constants": {
691
  "partialElement": "\"f32\"",
692
  "scalar": "dtypes.T",
693
  "workgroupSize": "reduceWorkgroupSize",
@@ -699,121 +534,119 @@
699
  {
700
  "id": "split_reduce",
701
  "name": "ReduceLogSum.AxisSplitTiledReduce",
702
- "source": {
703
- "shader": "reduce-axis0-tilecols.wgsl.jinja",
704
- "inputs": {
705
- "op": "\"logsum\"",
706
- "workgroupSize": "reduceWorkgroupSize",
707
- "split": "splitCount",
708
- "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
709
- "castF32": "dtypes.T == \"f16\"",
710
- "usesF16": "dtypes.T == \"f16\""
711
- }
712
  },
713
- "bindings": "axisSplitReduce",
714
- "dispatch": { "workgroups": "ceilDiv((axisSplitOutputs), (constants.tileCols))", "y": "splitCount" }
 
 
 
 
715
  },
716
  {
717
  "id": "combine",
718
  "name": "ReduceLogSum.AxisSplitCombine",
719
- "source": {
720
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
721
- "inputs": {
722
- "op": "\"logsum\"",
723
- "workgroupSize": "reduceWorkgroupSize",
724
- "split": "splitCount",
725
- "outputF16": "dtypes.T == \"f16\""
726
- }
727
- },
728
- "bindings": "axisSplitCombine",
729
- "dispatch": { "threads": "axisSplitOutputs", "workgroupSize": "reduceWorkgroupSize" }
730
  }
731
  ]
732
  },
733
  {
734
  "id": "axis0_splitk",
735
  "priority": 22,
736
- "when": ["not flatParallelCovered", "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"],
737
- "derive": { "splitCount": "axis0SplitCount" },
738
- "constants": { "partialElement": "\"f32\"", "workgroupSize": "reduceWorkgroupSize", "split": "splitCount" },
739
- "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * dim(shapes.data, 1)]" }],
 
 
 
 
740
  "passes": [
741
  {
742
  "id": "split_reduce",
743
  "name": "ReduceLogSum.Axis0SplitKReduce",
744
- "source": {
745
- "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
746
- "inputs": {
747
- "op": "\"logsum\"",
748
- "workgroupSize": "reduceWorkgroupSize",
749
- "split": "splitCount",
750
- "castF32": "dtypes.T == \"f16\"",
751
- "usesF16": "dtypes.T == \"f16\""
752
- }
753
  },
754
- "bindings": "axis0SplitReduce",
755
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize", "y": "splitCount" }
 
 
 
 
756
  },
757
  {
758
  "id": "combine",
759
  "name": "ReduceLogSum.Axis0SplitKCombine",
760
- "source": {
761
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
762
- "inputs": {
763
- "op": "\"logsum\"",
764
- "workgroupSize": "reduceWorkgroupSize",
765
- "split": "splitCount",
766
- "outputF16": "dtypes.T == \"f16\""
767
- }
768
- },
769
- "bindings": "axis0SplitCombine",
770
- "dispatch": { "threads": "dim(shapes.data, 1)", "workgroupSize": "reduceWorkgroupSize" }
771
  }
772
  ]
773
  },
774
  {
775
  "id": "axis0_tilecols",
776
  "priority": 20,
777
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_TILE_MIN_ROWS", "axis0Cols >= tunables.AXIS0_TILE_MIN_COLS", "((attrs.keepdims == 0 and ranks.reduced == 1 and dim(shapes.reduced, 0) == dim(shapes.data, 1)) or (attrs.keepdims == 1 and ranks.reduced == 2 and dim(shapes.reduced, 0) == 1 and dim(shapes.reduced, 1) == dim(shapes.data, 1)))", "axis0TilePathFits"],
778
- "constants": { "workgroupSize": "reduceWorkgroupSize", "tileCols": "tunables.AXIS0_TILE_COLS" },
779
  "passes": [
780
  {
781
  "id": "main",
782
  "name": "ReduceLogSum.Axis0TileCols",
783
- "source": {
784
- "shader": "reduce-axis0-tilecols.wgsl.jinja",
785
- "inputs": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
786
- },
787
- "bindings": "axis0Parallel",
788
- "dispatch": { "workgroups": "ceilDiv((dim(shapes.data, 1)), (constants.tileCols))" }
 
 
789
  }
790
  ]
791
  },
792
  {
793
  "id": "all_axes_flat",
794
  "priority": 31,
795
- "constants": { "scalar": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
796
  "when": ["flatParallelCovered"],
 
797
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
798
  "passes": [
799
  {
800
  "id": "flat_partial",
801
  "name": "ReduceLogSum.AllAxesFlatPartial",
802
- "source": {
803
- "shader": "reduce-flat-partial.wgsl.jinja",
804
- "inputs": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16": "dtypes.T == \"f16\"" }
805
- },
806
- "bindings": "flatPartialF32",
807
  "dispatch": { "x": "flatSplitCount" }
808
  },
809
  {
810
  "id": "combine",
811
  "name": "ReduceLogSum.AllAxesFlatCombine",
812
- "source": {
813
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
814
- "inputs": { "op": "\"logsum\"", "outputF16": "dtypes.T == \"f16\"" }
815
- },
816
- "bindings": "flatCombineF32",
817
  "dispatch": { "x": 1 }
818
  }
819
  ]
@@ -821,39 +654,42 @@
821
  {
822
  "id": "rankn_single_axis_generic",
823
  "priority": 12,
 
824
  "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4"],
825
- "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))"],
826
  "passes": [
827
  {
828
  "id": "main",
829
  "name": "ReduceLogSum.RankNSingleAxisGeneric",
830
- "source": {
831
- "shader": "reduce-serial-axis.wgsl.jinja",
832
- "inputs": {
833
- "op": "\"logsum\"",
834
- "indexing": "\"rankn\"",
835
- "castF32": "dtypes.T == \"f16\"",
836
- "usesF16": "dtypes.T == \"f16\"",
837
- "rank": "ranks.data",
838
- "axis": "reduceAxis",
839
- "dataShape": "shapes.data",
840
- "outputShape": "shapes.reduced",
841
- "outputRank": "ranks.reduced",
842
- "keepDims": "attrs.keepdims != 0",
843
- "logicalBool": "tensorDtypes.data == \"bool\""
844
- }
845
  },
846
- "bindings": "rankNAxis",
847
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
848
  }
849
  ]
850
  },
851
  {
852
  "id": "subgroup_last_axis_vec4",
853
  "priority": 25,
 
854
  "requires": { "features": ["subgroups"] },
855
- "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "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"],
856
- "constants": {
857
  "scalar": "dtypes.T",
858
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
859
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
@@ -862,27 +698,29 @@
862
  {
863
  "id": "main",
864
  "name": "ReduceLogSum.SubgroupRowVec4",
865
- "source": {
866
- "shader": "reduce-row-subgroup.wgsl.jinja",
867
- "inputs": {
868
- "op": "\"logsum\"",
869
- "vec4": true,
870
- "castF32": "dtypes.T == \"f16\"",
871
- "usesF16": "dtypes.T == \"f16\""
872
- }
873
  },
874
- "subgroupCollectivesWidth": "portable",
875
- "bindings": "lastAxisVec4",
876
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
 
 
 
 
877
  }
878
  ]
879
  },
880
  {
881
  "id": "subgroup_last_axis",
882
  "priority": 24,
 
883
  "requires": { "features": ["subgroups"] },
884
- "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "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"],
885
- "constants": {
886
  "scalar": "dtypes.T",
887
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
888
  },
@@ -890,113 +728,145 @@
890
  {
891
  "id": "main",
892
  "name": "ReduceLogSum.SubgroupRow",
893
- "source": {
894
- "shader": "reduce-row-subgroup.wgsl.jinja",
895
- "inputs": {
896
- "op": "\"logsum\"",
897
- "vec4": false,
898
- "castF32": "dtypes.T == \"f16\"",
899
- "usesF16": "dtypes.T == \"f16\""
900
- }
901
  },
902
- "subgroupCollectivesWidth": "portable",
903
- "bindings": "lastAxisScalarSubgroup",
904
- "dispatch": { "workgroups": "rows(shapes.data, ranks.data - 1)" }
 
 
 
 
905
  }
906
  ]
907
  },
908
  {
909
  "id": "axis0",
910
  "priority": 0,
 
911
  "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
912
- "constants": { "axis": 0 },
913
- "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)))"],
914
  "passes": [
915
  {
916
  "id": "main",
917
  "name": "axis0",
918
- "source": {
919
- "shader": "reduce-serial-axis.wgsl.jinja",
920
- "inputs": {
921
- "op": "\"logsum\"",
922
- "indexing": "\"axis2d\"",
923
- "castF32": "dtypes.T == \"f16\"",
924
- "usesF16": "dtypes.T == \"f16\""
925
- }
926
  },
927
- "bindings": "rank2Serial",
928
- "constants": { "axis": 0 },
929
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
930
  }
931
  ]
932
  },
933
  {
934
  "id": "axis1",
935
  "priority": 0,
936
- "constants": { "axis": 1 },
937
- "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))"],
938
  "passes": [
939
  {
940
  "id": "main",
941
  "name": "axis1",
942
- "source": {
943
- "shader": "reduce-serial-axis.wgsl.jinja",
944
- "inputs": {
945
- "op": "\"logsum\"",
946
- "indexing": "\"axis2d\"",
947
- "castF32": "dtypes.T == \"f16\"",
948
- "usesF16": "dtypes.T == \"f16\""
949
- }
950
  },
951
- "bindings": "rank2SerialAxis1",
952
- "constants": { "axis": 1 },
953
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
954
  }
955
  ]
956
  },
957
  {
958
  "id": "all_axes_keepdims",
959
  "priority": 30,
960
- "constants": { "axis": 0 },
961
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 1", "ranks.reduced == ranks.data", "numel(shapes.reduced) == 1"],
962
  "passes": [
963
  {
964
  "id": "main",
965
  "name": "ReduceLogSum.Rank3AllAxesKeepdims",
966
- "source": {
967
- "shader": "reduce-serial-axis.wgsl.jinja",
968
- "inputs": {
969
- "op": "\"logsum\"",
970
- "indexing": "\"axis2d\"",
971
- "castF32": "dtypes.T == \"f16\"",
972
- "usesF16": "dtypes.T == \"f16\""
973
- }
974
  },
975
- "bindings": "fullReduceSerial",
976
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
977
  }
978
  ]
979
  },
980
  {
981
  "id": "all_axes_no_keepdims",
982
  "priority": 30,
983
- "constants": { "axis": 0 },
984
- "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.data >= 3", "attrs.keepdims == 0", "attrs.noop_with_empty_axes == 0", "ranks.reduced == 0"],
985
  "passes": [
986
  {
987
  "id": "main",
988
  "name": "ReduceLogSum.Rank3AllAxesNoKeepdims",
989
- "source": {
990
- "shader": "reduce-serial-axis.wgsl.jinja",
991
- "inputs": {
992
- "op": "\"logsum\"",
993
- "indexing": "\"axis2d\"",
994
- "castF32": "dtypes.T == \"f16\"",
995
- "usesF16": "dtypes.T == \"f16\""
996
- }
997
  },
998
- "bindings": "fullReduceSerial",
999
- "dispatch": { "threads": "numel(shapes.reduced)", "workgroupSize": "reduceWorkgroupSize" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1000
  }
1001
  ]
1002
  }
 
2
  "domain": "ai.onnx",
3
  "name": "ReduceLogSum",
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"] },
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
+ "ROW_SERIAL_MIN_ROWS": { "default": 8192 },
33
+ "ROW_SERIAL_MAX_COLS": { "default": 1024 }
34
  },
35
  "derive": {
36
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
37
  "reduceWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
38
  "treeWorkgroupOk": "reduceWorkgroupSize > 0 and pow2ceil(reduceWorkgroupSize) == reduceWorkgroupSize and reduceWorkgroupSize * dtypeBytes(\"float32\") <= device.limits.maxComputeWorkgroupStorageSize",
39
  "subgroupWorkgroupFloor": "min(reduceWorkgroupSize, max(1, device.adapterInfo.subgroupMaxSize))",
40
+ "lastAxisRows": "rows(shapes.x, ranks.x - 1) if ranks.x > 0 else 1",
41
+ "lastAxisCols": "dim(shapes.x, ranks.x - 1) if ranks.x > 0 else 1",
42
  "rowSerialPreferred": "lastAxisRows >= tunables.ROW_SERIAL_MIN_ROWS and lastAxisCols <= tunables.ROW_SERIAL_MAX_COLS",
43
+ "axis0Rows": "dim(shapes.x, 0) if ranks.x >= 2 else 0",
44
+ "axis0Cols": "dim(shapes.x, 1) if ranks.x >= 2 else 0",
45
  "axis0SplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axis0Rows, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
46
  "axis0SplitScratchBytes": "axis0SplitCount * axis0Cols * dtypeBytes(\"float32\")",
47
+ "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",
48
+ "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",
49
+ "axisSplitDim": "dim(shapes.x, reduceAxis) if ranks.x >= 2 and reduceAxis < ranks.x else 0",
50
+ "axisSplitInner": "inner(shapes.x, reduceAxis) if ranks.x >= 2 and reduceAxis < ranks.x else 1",
51
+ "axisSplitOutputs": "numel(shapes.y)",
52
  "axisSplitCount": "min(tunables.AXIS0_MAX_SPLITS, pow2ceil(ceilDiv(axisSplitDim, tunables.AXIS0_SPLIT_TARGET_ROWS)))",
53
  "axisSplitScratchBytes": "axisSplitCount * axisSplitOutputs * 4",
54
+ "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",
55
  "axis0TilePathFits": "treeWorkgroupOk and tunables.AXIS0_TILE_COLS > 0 and tunables.AXIS0_TILE_COLS <= reduceWorkgroupSize and reduceWorkgroupSize % tunables.AXIS0_TILE_COLS == 0",
56
+ "flatItems": "floor(numel(shapes.x) / tunables.VECTOR_WIDTH)",
57
  "flatSplitCount": "max(1, min(tunables.FULL_REDUCE_MAX_SPLITS, ceilDiv(flatItems, reduceWorkgroupSize)))",
58
  "flatScratchBytes": "flatSplitCount * dtypeBytes(\"float32\")",
59
+ "flatPathFits": "treeWorkgroupOk and flatSplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize",
60
+ "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",
61
+ "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)))"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  },
63
+ "bindings": {
64
+ "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
65
+ "y": { "buffer": "storage", "elementType": "$T" },
66
+ "params": {
67
+ "buffer": "uniform",
68
+ "struct": [
69
+ { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
70
+ { "name": "chunkCount", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y) / tunables.VECTOR_WIDTH" }
71
+ ]
72
+ },
73
+ "x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "$T" },
74
+ "params_2": {
75
+ "name": "params",
76
+ "buffer": "uniform",
77
+ "struct": [
78
+ { "name": "rows", "type": "u32", "value": "numel(shapes.y)" },
79
+ { "name": "cols", "type": "u32", "value": "numel(shapes.x) / numel(shapes.y)" }
80
+ ]
81
+ },
82
+ "params_3": {
83
+ "name": "params",
84
+ "buffer": "uniform",
85
+ "struct": [{ "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }]
86
+ },
87
+ "params_4": {
88
+ "name": "params",
89
+ "buffer": "uniform",
90
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
91
+ },
92
+ "params_5": {
93
+ "name": "params",
94
+ "buffer": "uniform",
95
+ "struct": [
96
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
97
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1) / tunables.VECTOR_WIDTH" }
98
+ ]
99
+ },
100
+ "params_6": {
101
+ "name": "params",
102
+ "buffer": "uniform",
103
+ "struct": [
104
+ { "name": "rows", "type": "u32", "value": "1" },
105
+ { "name": "cols", "type": "u32", "value": "1" },
106
+ { "name": "outCount", "type": "u32", "value": "1" }
107
+ ]
108
+ },
109
+ "params_7": {
110
+ "name": "params",
111
+ "buffer": "uniform",
112
+ "struct": [
113
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
114
+ { "name": "cols", "type": "u32", "value": "1" },
115
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
116
+ ]
117
+ },
118
+ "params_8": {
119
+ "name": "params",
120
+ "buffer": "uniform",
121
+ "struct": [
122
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
123
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
124
+ ]
125
+ },
126
+ "partials": { "buffer": "storage", "elementType": "$partialElement" },
127
+ "params_9": {
128
+ "name": "params",
129
+ "buffer": "uniform",
130
+ "struct": [
131
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
132
+ { "name": "inner", "type": "u32", "value": "axisSplitInner" },
133
+ { "name": "outputs", "type": "u32", "value": "axisSplitOutputs" }
134
+ ]
135
+ },
136
+ "partials_2": { "name": "partials", "buffer": "read-only-storage", "elementType": "$partialElement" },
137
+ "params_10": {
138
+ "name": "params",
139
+ "buffer": "uniform",
140
+ "struct": [{ "name": "cols", "type": "u32", "value": "axisSplitOutputs" }]
141
+ },
142
+ "params_11": {
143
+ "name": "params",
144
+ "buffer": "uniform",
145
+ "struct": [
146
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
147
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }
148
+ ]
149
+ },
150
+ "params_12": {
151
+ "name": "params",
152
+ "buffer": "uniform",
153
+ "struct": [{ "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" }]
154
+ },
155
+ "partials_3": { "name": "partials", "buffer": "storage", "elementType": "f32" },
156
+ "params_13": {
157
+ "name": "params",
158
+ "buffer": "uniform",
159
+ "struct": [
160
+ { "name": "count4", "type": "u32", "value": "floor(numel(shapes.x) / tunables.VECTOR_WIDTH)" },
161
+ { "name": "numel", "type": "u32", "value": "numel(shapes.x)" }
162
+ ]
163
+ },
164
+ "partials_4": { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
165
+ "params_14": { "name": "params", "buffer": "uniform", "struct": [{ "name": "cols", "type": "u32", "value": "1" }] },
166
+ "params_15": {
167
+ "name": "params",
168
+ "buffer": "uniform",
169
+ "struct": [
170
+ { "name": "axisDim", "type": "u32", "value": "axisSplitDim" },
171
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
172
+ ]
173
+ },
174
+ "params_16": {
175
+ "name": "params",
176
+ "buffer": "uniform",
177
+ "struct": [
178
+ { "name": "rows", "type": "u32", "value": "rows(shapes.x, ranks.x - 1)" },
179
+ { "name": "chunkCount", "type": "u32", "value": "dim(shapes.x, ranks.x - 1)" }
180
+ ]
181
+ },
182
+ "params_17": {
183
+ "name": "params",
184
+ "buffer": "uniform",
185
+ "struct": [
186
+ { "name": "rows", "type": "u32", "value": "dim(shapes.x, 0)" },
187
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
188
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
189
+ ]
190
+ },
191
+ "params_18": {
192
+ "name": "params",
193
+ "buffer": "uniform",
194
+ "struct": [
195
+ { "name": "cols", "type": "u32", "value": "dim(shapes.x, 1)" },
196
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
197
+ ]
198
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  },
200
  "variants": [
201
  {
202
  "id": "contiguous_suffix_subgroup_vec4",
203
  "priority": 30,
204
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.x) / numel(shapes.y)) % tunables.VECTOR_WIDTH == 0"],
205
  "requires": { "features": ["subgroups"] },
206
+ "derive": {
 
207
  "scalar": "dtypes.T",
208
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
209
+ "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(numel(shapes.x) / numel(shapes.y), tunables.VECTOR_WIDTH))))"
210
  },
211
  "passes": [
212
  {
213
  "id": "main",
214
  "name": "ReduceLogSum.ContiguousSuffixSubgroupVec4",
215
+ "shader": "reduce-row-subgroup.wgsl.jinja",
216
+ "derive": {
217
+ "op": "\"logsum\"",
218
+ "vec4": true,
219
+ "castF32": "dtypes.T == \"f16\"",
220
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
221
  },
222
+ "bindings": ["x", "y", "params"],
223
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 },
224
+ "subgroupCollectivesWidth": "portable"
225
  }
226
  ]
227
  },
228
  {
229
  "id": "contiguous_suffix_tree_vec4",
230
  "priority": 22,
231
+ "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "(numel(shapes.x) / numel(shapes.y)) % tunables.VECTOR_WIDTH == 0", "treeWorkgroupOk"],
232
+ "derive": {
233
  "scalar": "dtypes.T",
234
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
235
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(numel(shapes.x) / numel(shapes.y), tunables.VECTOR_WIDTH)))"
236
  },
237
  "passes": [
238
  {
239
  "id": "main",
240
  "name": "ReduceLogSum.ContiguousSuffixTreeVec4",
241
+ "shader": "reduce-row-tree.wgsl.jinja",
242
+ "derive": {
243
+ "op": "\"logsum\"",
244
+ "vec4": true,
245
+ "castF32": "dtypes.T == \"f16\"",
246
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
247
  },
248
+ "bindings": ["x", "y", "params"],
249
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
250
  }
251
  ]
252
  },
 
254
  "id": "contiguous_suffix_tree",
255
  "priority": 21,
256
  "when": ["not flatParallelCovered", "contiguousSuffixParallelCovered", "treeWorkgroupOk"],
257
+ "derive": {
258
+ "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(numel(shapes.x) / numel(shapes.y)))",
259
  "scalar": "dtypes.T"
260
  },
261
  "passes": [
262
  {
263
  "id": "main",
264
  "name": "ReduceLogSum.ContiguousSuffixTree",
265
+ "shader": "reduce-row-tree.wgsl.jinja",
266
+ "derive": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
267
+ "bindings": ["x_2", "y", "params_2"],
268
+ "dispatch": { "x": "min(numel(shapes.y), 65535)", "y": "ceilDiv(numel(shapes.y), 65535)", "z": 1 }
 
 
269
  }
270
  ]
271
  },
272
  {
273
  "id": "multi_axis_rank3",
274
  "priority": 8,
275
+ "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)"],
276
  "passes": [
277
  {
278
  "id": "main",
279
  "name": "ReduceLogSum.MultiAxisRank3",
280
+ "shader": "reduce-serial-axis.wgsl.jinja",
281
+ "derive": {
282
+ "reduceWorkgroupSize": "reduceWorkgroupSize",
283
+ "op": "\"logsum\"",
284
+ "indexing": "\"multiaxis\"",
285
+ "castF32": "dtypes.T == \"f16\"",
286
+ "usesF16Spec": "dtypes.T == \"f16\"",
287
+ "rank": 3,
288
+ "reduce": ["hasAxis(attrs.axes, 0, 3)", "hasAxis(attrs.axes, 1, 3)", "hasAxis(attrs.axes, 2, 3)"],
289
+ "dataShape": "shapes.x",
290
+ "outputShape": "shapes.y",
291
+ "outputRank": "ranks.y",
292
+ "keepDims": "attrs.keepdims != 0",
293
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
294
  },
295
+ "bindings": ["x_2", "y", "params_3"],
296
+ "dispatch": {
297
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
298
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
299
+ "z": 1
300
+ }
301
  }
302
  ]
303
  },
304
  {
305
  "id": "multi_axis_rank4",
306
  "priority": 8,
307
+ "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))"],
308
  "passes": [
309
  {
310
  "id": "main",
311
  "name": "ReduceLogSum.MultiAxisRank4",
312
+ "shader": "reduce-serial-axis.wgsl.jinja",
313
+ "derive": {
314
+ "reduceWorkgroupSize": "reduceWorkgroupSize",
315
+ "op": "\"logsum\"",
316
+ "indexing": "\"multiaxis\"",
317
+ "castF32": "dtypes.T == \"f16\"",
318
+ "usesF16Spec": "dtypes.T == \"f16\"",
319
+ "rank": 4,
320
+ "reduce": ["hasAxis(attrs.axes, 0, 4)", "hasAxis(attrs.axes, 1, 4)", "hasAxis(attrs.axes, 2, 4)", "hasAxis(attrs.axes, 3, 4)"],
321
+ "dataShape": "shapes.x",
322
+ "outputShape": "shapes.y",
323
+ "outputRank": "ranks.y",
324
+ "keepDims": "attrs.keepdims != 0",
325
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
326
  },
327
+ "bindings": ["x_2", "y", "params_3"],
328
+ "dispatch": {
329
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
330
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
331
+ "z": 1
332
+ }
333
  }
334
  ]
335
  },
336
  {
337
  "id": "noop_empty_axes",
338
  "priority": 40,
339
+ "when": ["dtypes.T == \"f32\"", "attrs.noop_with_empty_axes == 1", "(attrs.axes | length) == 0", "sameShape(shapes.x, shapes.y)"],
340
+ "derive": { "reduceWorkgroupSize": "reduceWorkgroupSize" },
341
  "passes": [
342
  {
343
  "id": "main",
344
  "name": "ReduceLogSum.NoopEmptyAxes",
345
+ "shader": "reduce-noop-empty-axes.wgsl.jinja",
346
+ "derive": { "op": "\"log\"" },
347
+ "bindings": ["x_2", "y", "params_4"],
348
+ "dispatch": {
349
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
350
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
351
+ "z": 1
352
+ }
353
+ }
354
+ ]
355
+ },
356
+ {
357
+ "id": "subgroup_rows_last_axis_vec4",
358
+ "priority": 26,
359
+ "when": ["not flatParallelCovered", "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)"],
360
+ "requires": { "features": ["subgroups"] },
361
+ "derive": {
362
+ "scalar": "dtypes.T",
363
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
364
+ "workgroupSize": "reduceWorkgroupSize",
365
+ "vecsPerLane": "ceilDiv(lastAxisCols / tunables.VECTOR_WIDTH, device.adapterInfo.subgroupMinSize)"
366
+ },
367
+ "passes": [
368
+ {
369
+ "id": "main",
370
+ "name": "ReduceLogSum.SubgroupRowsVec4",
371
+ "shader": "reduce-row-subgroup-rows.wgsl.jinja",
372
+ "derive": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
373
+ "bindings": ["x", "y", "params_5"],
374
+ "dispatch": {
375
+ "x": "min(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
376
+ "y": "ceilDiv(ceilDiv(lastAxisRows, reduceWorkgroupSize / device.adapterInfo.subgroupMaxSize), 65535)",
377
+ "z": 1
378
+ },
379
+ "subgroupCollectivesWidth": "portable"
380
  }
381
  ]
382
  },
383
  {
384
  "id": "tree_last_axis_vec4",
385
  "priority": 23,
386
+ "when": ["not flatParallelCovered", "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"],
387
  "demoteWhen": ["rowSerialPreferred"],
388
+ "derive": {
 
389
  "scalar": "dtypes.T",
390
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
391
  "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH)))"
 
394
  {
395
  "id": "main",
396
  "name": "ReduceLogSum.TreeRowVec4",
397
+ "shader": "reduce-row-tree.wgsl.jinja",
398
+ "derive": {
399
+ "op": "\"logsum\"",
400
+ "vec4": true,
401
+ "castF32": "dtypes.T == \"f16\"",
402
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
403
  },
404
+ "bindings": ["x", "y", "params_5"],
405
+ "dispatch": { "x": "min(lastAxisRows, 65535)", "y": "ceilDiv(lastAxisRows, 65535)", "z": 1 }
406
  }
407
  ]
408
  },
409
  {
410
  "id": "rank0_scalar",
411
  "priority": 40,
412
+ "when": ["f16Ok(dtypes.T)", "ranks.x == 0", "ranks.y == 0"],
413
+ "derive": { "axis": 0, "reduceWorkgroupSize": "reduceWorkgroupSize" },
414
  "passes": [
415
  {
416
  "id": "main",
417
  "name": "ReduceLogSum.Rank0Scalar",
418
+ "shader": "reduce-serial-axis.wgsl.jinja",
419
+ "derive": {
420
+ "op": "\"logsum\"",
421
+ "indexing": "\"axis2d\"",
422
+ "castF32": "dtypes.T == \"f16\"",
423
+ "usesF16Spec": "dtypes.T == \"f16\"",
424
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
 
425
  },
426
+ "bindings": ["x_2", "y", "params_6"],
427
  "dispatch": { "x": 1 }
428
  }
429
  ]
430
  },
431
  {
432
  "id": "rank1_axis0",
433
+ "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))"],
434
+ "derive": { "axis": 0, "reduceWorkgroupSize": "reduceWorkgroupSize" },
435
  "passes": [
436
  {
437
  "id": "main",
438
  "name": "ReduceLogSum.Rank1Axis0",
439
+ "shader": "reduce-serial-axis.wgsl.jinja",
440
+ "derive": {
441
+ "op": "\"logsum\"",
442
+ "indexing": "\"axis2d\"",
443
+ "castF32": "dtypes.T == \"f16\"",
444
+ "usesF16Spec": "dtypes.T == \"f16\"",
445
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
 
446
  },
447
+ "bindings": ["x_2", "y", "params_7"],
448
+ "dispatch": {
449
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
450
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
451
+ "z": 1
452
+ }
453
  }
454
  ]
455
  },
456
  {
457
  "id": "axis1_parallel",
458
  "priority": 20,
459
+ "when": ["not flatParallelCovered", "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"],
460
  "demoteWhen": ["rowSerialPreferred"],
461
+ "derive": { "workgroupSize": "min(reduceWorkgroupSize, pow2ceil(dim(shapes.x, ranks.x - 1)))" },
 
462
  "passes": [
463
  {
464
  "id": "main",
465
  "name": "ReduceLogSum.Axis1Parallel",
466
+ "shader": "reduce-row-tree.wgsl.jinja",
467
+ "derive": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
468
+ "bindings": ["x_2", "y", "params_8"],
469
+ "dispatch": {
470
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
471
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
472
+ "z": 1
473
+ }
474
  }
475
  ]
476
  },
477
  {
478
  "id": "axis_split",
479
  "priority": 24,
480
+ "when": ["not flatParallelCovered", "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"],
481
+ "derive": {
482
+ "splitCount": "axisSplitCount",
483
+ "partialElement": "\"f32\"",
484
+ "workgroupSize": "reduceWorkgroupSize",
485
+ "split": "splitCount"
486
+ },
487
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * axisSplitOutputs]" }],
488
  "passes": [
489
  {
490
  "id": "split_reduce",
491
  "name": "ReduceLogSum.AxisSplitReduce",
492
+ "shader": "reduce-axis-split-reduce.wgsl.jinja",
493
+ "derive": {
494
+ "op": "\"logsum\"",
495
+ "splitSpec": "splitCount",
496
+ "castF32": "dtypes.T == \"f16\"",
497
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
498
  },
499
+ "bindings": ["x_2", "partials", "params_9"],
500
+ "dispatch": {
501
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
502
+ "y": "splitCount",
503
+ "z": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)"
504
+ }
505
  },
506
  {
507
  "id": "combine",
508
  "name": "ReduceLogSum.AxisSplitCombine",
509
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
510
+ "derive": { "op": "\"logsum\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
511
+ "bindings": ["partials_2", "y", "params_10"],
512
+ "dispatch": {
513
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
514
+ "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
515
+ "z": 1
516
+ }
 
 
 
517
  }
518
  ]
519
  },
520
  {
521
  "id": "axis_split_tiled_narrow",
522
  "priority": 25,
523
+ "when": ["not flatParallelCovered", "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"],
524
+ "derive": {
525
+ "splitCount": "axisSplitCount",
526
  "partialElement": "\"f32\"",
527
  "scalar": "dtypes.T",
528
  "workgroupSize": "reduceWorkgroupSize",
 
534
  {
535
  "id": "split_reduce",
536
  "name": "ReduceLogSum.AxisSplitTiledReduce",
537
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
538
+ "derive": {
539
+ "op": "\"logsum\"",
540
+ "splitSpec": "splitCount",
541
+ "tileCols": "tunables.AXIS_SPLIT_TILE_COLS",
542
+ "castF32": "dtypes.T == \"f16\"",
543
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
544
  },
545
+ "bindings": ["x_2", "partials", "params_9"],
546
+ "dispatch": {
547
+ "x": "min(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)",
548
+ "y": "splitCount",
549
+ "z": "ceilDiv(ceilDiv((axisSplitOutputs), (tileCols)), DISPATCH_FOLD_WIDTH)"
550
+ }
551
  },
552
  {
553
  "id": "combine",
554
  "name": "ReduceLogSum.AxisSplitCombine",
555
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
556
+ "derive": { "op": "\"logsum\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
557
+ "bindings": ["partials_2", "y", "params_10"],
558
+ "dispatch": {
559
+ "x": "min(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
560
+ "y": "ceilDiv(ceilDiv((axisSplitOutputs), (reduceWorkgroupSize)), 65535)",
561
+ "z": 1
562
+ }
 
 
 
563
  }
564
  ]
565
  },
566
  {
567
  "id": "axis0_splitk",
568
  "priority": 22,
569
+ "when": ["not flatParallelCovered", "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"],
570
+ "derive": {
571
+ "splitCount": "axis0SplitCount",
572
+ "partialElement": "\"f32\"",
573
+ "workgroupSize": "reduceWorkgroupSize",
574
+ "split": "splitCount"
575
+ },
576
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[splitCount * dim(shapes.x, 1)]" }],
577
  "passes": [
578
  {
579
  "id": "split_reduce",
580
  "name": "ReduceLogSum.Axis0SplitKReduce",
581
+ "shader": "reduce-axis0-splitk-reduce.wgsl.jinja",
582
+ "derive": {
583
+ "op": "\"logsum\"",
584
+ "splitSpec": "splitCount",
585
+ "castF32": "dtypes.T == \"f16\"",
586
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
 
587
  },
588
+ "bindings": ["x_2", "partials", "params_11"],
589
+ "dispatch": {
590
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)",
591
+ "y": "splitCount",
592
+ "z": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), DISPATCH_FOLD_WIDTH)"
593
+ }
594
  },
595
  {
596
  "id": "combine",
597
  "name": "ReduceLogSum.Axis0SplitKCombine",
598
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
599
+ "derive": { "op": "\"logsum\"", "splitSpec": "splitCount", "outputF16": "dtypes.T == \"f16\"" },
600
+ "bindings": ["partials_2", "y", "params_12"],
601
+ "dispatch": {
602
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
603
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (reduceWorkgroupSize)), 65535)",
604
+ "z": 1
605
+ }
 
 
 
606
  }
607
  ]
608
  },
609
  {
610
  "id": "axis0_tilecols",
611
  "priority": 20,
612
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x == 2", "reduceAxis == 0", "axis0Rows >= tunables.AXIS0_TILE_MIN_ROWS", "axis0Cols >= tunables.AXIS0_TILE_MIN_COLS", "((attrs.keepdims == 0 and ranks.y == 1 and dim(shapes.y, 0) == dim(shapes.x, 1)) or (attrs.keepdims == 1 and ranks.y == 2 and dim(shapes.y, 0) == 1 and dim(shapes.y, 1) == dim(shapes.x, 1)))", "axis0TilePathFits"],
613
+ "derive": { "workgroupSize": "reduceWorkgroupSize", "tileCols": "tunables.AXIS0_TILE_COLS" },
614
  "passes": [
615
  {
616
  "id": "main",
617
  "name": "ReduceLogSum.Axis0TileCols",
618
+ "shader": "reduce-axis0-tilecols.wgsl.jinja",
619
+ "derive": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
620
+ "bindings": ["x_2", "y", "params_11"],
621
+ "dispatch": {
622
+ "x": "min(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
623
+ "y": "ceilDiv(ceilDiv((dim(shapes.x, 1)), (tileCols)), 65535)",
624
+ "z": 1
625
+ }
626
  }
627
  ]
628
  },
629
  {
630
  "id": "all_axes_flat",
631
  "priority": 31,
 
632
  "when": ["flatParallelCovered"],
633
+ "derive": { "scalar": "dtypes.T", "workgroupSize": "reduceWorkgroupSize", "split": "flatSplitCount" },
634
  "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[flatSplitCount]" }],
635
  "passes": [
636
  {
637
  "id": "flat_partial",
638
  "name": "ReduceLogSum.AllAxesFlatPartial",
639
+ "shader": "reduce-flat-partial.wgsl.jinja",
640
+ "derive": { "op": "\"logsum\"", "castF32": "dtypes.T == \"f16\"", "usesF16Spec": "dtypes.T == \"f16\"" },
641
+ "bindings": ["x_2", "partials_3", "params_13"],
 
 
642
  "dispatch": { "x": "flatSplitCount" }
643
  },
644
  {
645
  "id": "combine",
646
  "name": "ReduceLogSum.AllAxesFlatCombine",
647
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
648
+ "derive": { "op": "\"logsum\"", "outputF16": "dtypes.T == \"f16\"" },
649
+ "bindings": ["partials_4", "y", "params_14"],
 
 
650
  "dispatch": { "x": 1 }
651
  }
652
  ]
 
654
  {
655
  "id": "rankn_single_axis_generic",
656
  "priority": 12,
657
+ "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))"],
658
  "supersededBy": ["axis_split_tiled_narrow", "axis_split", "subgroup_last_axis_vec4", "subgroup_last_axis", "tree_last_axis_vec4"],
 
659
  "passes": [
660
  {
661
  "id": "main",
662
  "name": "ReduceLogSum.RankNSingleAxisGeneric",
663
+ "shader": "reduce-serial-axis.wgsl.jinja",
664
+ "derive": {
665
+ "reduceWorkgroupSize": "reduceWorkgroupSize",
666
+ "op": "\"logsum\"",
667
+ "indexing": "\"rankn\"",
668
+ "castF32": "dtypes.T == \"f16\"",
669
+ "usesF16Spec": "dtypes.T == \"f16\"",
670
+ "rank": "ranks.x",
671
+ "axisSpec": "reduceAxis",
672
+ "dataShape": "shapes.x",
673
+ "outputShape": "shapes.y",
674
+ "outputRank": "ranks.y",
675
+ "keepDims": "attrs.keepdims != 0",
676
+ "logicalBool": "tensorDtypes.x == \"bool\""
 
677
  },
678
+ "bindings": ["x_2", "y", "params_15"],
679
+ "dispatch": {
680
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
681
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
682
+ "z": 1
683
+ }
684
  }
685
  ]
686
  },
687
  {
688
  "id": "subgroup_last_axis_vec4",
689
  "priority": 25,
690
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "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"],
691
  "requires": { "features": ["subgroups"] },
692
+ "derive": {
 
693
  "scalar": "dtypes.T",
694
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
695
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(ceilDiv(lastAxisCols, tunables.VECTOR_WIDTH))))"
 
698
  {
699
  "id": "main",
700
  "name": "ReduceLogSum.SubgroupRowVec4",
701
+ "shader": "reduce-row-subgroup.wgsl.jinja",
702
+ "derive": {
703
+ "op": "\"logsum\"",
704
+ "vec4": true,
705
+ "castF32": "dtypes.T == \"f16\"",
706
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
707
  },
708
+ "bindings": ["x", "y", "params_5"],
709
+ "dispatch": {
710
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
711
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
712
+ "z": 1
713
+ },
714
+ "subgroupCollectivesWidth": "portable"
715
  }
716
  ]
717
  },
718
  {
719
  "id": "subgroup_last_axis",
720
  "priority": 24,
721
+ "when": ["device.wgslLanguageFeatures.has(\"subgroup_id\")", "not flatParallelCovered", "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"],
722
  "requires": { "features": ["subgroups"] },
723
+ "derive": {
 
724
  "scalar": "dtypes.T",
725
  "workgroupSize": "min(reduceWorkgroupSize, max(subgroupWorkgroupFloor, pow2ceil(lastAxisCols)))"
726
  },
 
728
  {
729
  "id": "main",
730
  "name": "ReduceLogSum.SubgroupRow",
731
+ "shader": "reduce-row-subgroup.wgsl.jinja",
732
+ "derive": {
733
+ "op": "\"logsum\"",
734
+ "vec4": false,
735
+ "castF32": "dtypes.T == \"f16\"",
736
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
737
  },
738
+ "bindings": ["x_2", "y", "params_16"],
739
+ "dispatch": {
740
+ "x": "min(rows(shapes.x, ranks.x - 1), 65535)",
741
+ "y": "ceilDiv(rows(shapes.x, ranks.x - 1), 65535)",
742
+ "z": 1
743
+ },
744
+ "subgroupCollectivesWidth": "portable"
745
  }
746
  ]
747
  },
748
  {
749
  "id": "axis0",
750
  "priority": 0,
751
+ "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)))"],
752
  "supersededBy": ["axis_split_tiled_narrow", "axis0_splitk", "axis0_tilecols"],
753
+ "derive": { "axis": 0 },
 
754
  "passes": [
755
  {
756
  "id": "main",
757
  "name": "axis0",
758
+ "shader": "reduce-serial-axis.wgsl.jinja",
759
+ "derive": {
760
+ "axis": 0,
761
+ "op": "\"logsum\"",
762
+ "indexing": "\"axis2d\"",
763
+ "castF32": "dtypes.T == \"f16\"",
764
+ "usesF16Spec": "dtypes.T == \"f16\""
 
765
  },
766
+ "bindings": ["x_2", "y", "params_17"],
767
+ "dispatch": {
768
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
769
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
770
+ "z": 1
771
+ }
772
  }
773
  ]
774
  },
775
  {
776
  "id": "axis1",
777
  "priority": 0,
778
+ "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))"],
779
+ "derive": { "axis": 1 },
780
  "passes": [
781
  {
782
  "id": "main",
783
  "name": "axis1",
784
+ "shader": "reduce-serial-axis.wgsl.jinja",
785
+ "derive": {
786
+ "axis": 1,
787
+ "op": "\"logsum\"",
788
+ "indexing": "\"axis2d\"",
789
+ "castF32": "dtypes.T == \"f16\"",
790
+ "usesF16Spec": "dtypes.T == \"f16\""
 
791
  },
792
+ "bindings": ["x_2", "y", "params_18"],
793
+ "dispatch": {
794
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
795
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
796
+ "z": 1
797
+ }
798
  }
799
  ]
800
  },
801
  {
802
  "id": "all_axes_keepdims",
803
  "priority": 30,
804
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 1", "ranks.y == ranks.x", "numel(shapes.y) == 1"],
805
+ "derive": { "axis": 0 },
806
  "passes": [
807
  {
808
  "id": "main",
809
  "name": "ReduceLogSum.Rank3AllAxesKeepdims",
810
+ "shader": "reduce-serial-axis.wgsl.jinja",
811
+ "derive": {
812
+ "op": "\"logsum\"",
813
+ "indexing": "\"axis2d\"",
814
+ "castF32": "dtypes.T == \"f16\"",
815
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
816
  },
817
+ "bindings": [
818
+ "x_2",
819
+ "y",
820
+ {
821
+ "name": "params",
822
+ "struct": [
823
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
824
+ { "name": "cols", "type": "u32", "value": "1" },
825
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
826
+ ]
827
+ }
828
+ ],
829
+ "dispatch": {
830
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
831
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
832
+ "z": 1
833
+ }
834
  }
835
  ]
836
  },
837
  {
838
  "id": "all_axes_no_keepdims",
839
  "priority": 30,
840
+ "when": ["not flatParallelCovered", "f16Ok(dtypes.T)", "ranks.x >= 3", "attrs.keepdims == 0", "attrs.noop_with_empty_axes == 0", "ranks.y == 0"],
841
+ "derive": { "axis": 0 },
842
  "passes": [
843
  {
844
  "id": "main",
845
  "name": "ReduceLogSum.Rank3AllAxesNoKeepdims",
846
+ "shader": "reduce-serial-axis.wgsl.jinja",
847
+ "derive": {
848
+ "op": "\"logsum\"",
849
+ "indexing": "\"axis2d\"",
850
+ "castF32": "dtypes.T == \"f16\"",
851
+ "usesF16Spec": "dtypes.T == \"f16\""
 
 
852
  },
853
+ "bindings": [
854
+ "x_2",
855
+ "y",
856
+ {
857
+ "name": "params",
858
+ "struct": [
859
+ { "name": "rows", "type": "u32", "value": "numel(shapes.x)" },
860
+ { "name": "cols", "type": "u32", "value": "1" },
861
+ { "name": "outCount", "type": "u32", "value": "numel(shapes.y)" }
862
+ ]
863
+ }
864
+ ],
865
+ "dispatch": {
866
+ "x": "min(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
867
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (reduceWorkgroupSize)), 65535)",
868
+ "z": 1
869
+ }
870
  }
871
  ]
872
  }
build/webgpu/metadata.json CHANGED
@@ -1,26 +1,54 @@
1
  {
2
  "name": "ai.onnx.ReduceLogSum",
3
- "id": "_ai_onnx_reducelogsum_webgpu_4c77048",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "tExpDbPGHls8UweHAWP2+FY+piqkNai/ffADNFmbuq4=",
11
- "manifest.json": "/pyMW/xHAyNmrprpHqurMtEhzXppdfFwqyI9wd4JVoM=",
12
- "reduce-axis-split-reduce.wgsl.jinja": "Q50AhcbpYvCADMPrbGtPXKbmfT8f6LmQY+6+/CEX224=",
13
- "reduce-axis0-splitk-combine.wgsl.jinja": "ul0TxUqohcvm6CDpKIFB0WoPiPqqpuh1AvsW1+6+3Rk=",
14
- "reduce-axis0-splitk-reduce.wgsl.jinja": "jc7OBcuREabFLa+kwdZs2oEI3W/BWfHh8nyeUQnIVyY=",
15
- "reduce-axis0-tilecols.wgsl.jinja": "6PKq2qx5+gRoZjG0LEOrwi1PbWT9b7QGzMjEekpvqbQ=",
16
- "reduce-flat-partial.wgsl.jinja": "Jz5ZMpVLRJ702gjunfgrUEwqOase9mEBr5SRHlwA0zU=",
17
- "reduce-noop-empty-axes.wgsl.jinja": "77n6kZMxBe/1e8ye2C8S3HMvXWicW/c3pzHEnQS42Gk=",
18
- "reduce-row-subgroup.wgsl.jinja": "oJiY7cuPhJ++GEdGcV/RauZy5PUHrVD4YHx0sZyh6Tk=",
19
- "reduce-row-tree.wgsl.jinja": "Ds+XUWliKXTMjtZRfglx5ilTvdUJS8Hbdm4O1yKzF20=",
20
- "reduce-serial-axis.wgsl.jinja": "JzfMwQJ1gzBxszuL1+IC7d/kWKP9dArAHl63PkAfdmA=",
21
- "test.json": "R7TN083sH8d7kSwSt1fUUftGCxWFgrsezAmGa+hTAKw="
 
22
  }
23
  },
24
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
25
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.ReduceLogSum" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
 
1
  {
2
  "name": "ai.onnx.ReduceLogSum",
3
+ "id": "_ai_onnx_reducelogsum_webgpu_ba31210",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "qZglUd8228INjSOu3OCeuW6FlX7Shf0Ap2Lt7cGFd+s=",
11
+ "manifest.json": "D+Q5ZJye8i64JgaF9Wnm0+kGdQoDhasfgmGhWTixf9M=",
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": "iLqQIC+Sa+EUzZG4Z3BqZOT2XA1TQEuRcFR2JbkEa30=",
15
+ "reduce-axis0-tilecols.wgsl.jinja": "PjYkEUQJBeG70td3W2xxmexH9x6XNIfeSzXBY47HbaU=",
16
+ "reduce-flat-partial.wgsl.jinja": "+qToL+wFi9QOxvY887aBAEwZK6Xu/eLQkucKvJYNwSk=",
17
+ "reduce-noop-empty-axes.wgsl.jinja": "QacjLo74udD8ilCTheqHUKAHzx/wtFiB59MjOx0K97A=",
18
+ "reduce-row-subgroup-rows.wgsl.jinja": "76u7rAvFoZZKrFDs2A2jk0vkB0uNrPL6twdOBE9b+v8=",
19
+ "reduce-row-subgroup.wgsl.jinja": "2mu9LEsk8HfaLvucBCfcB1/ENpXkD6ELiCtRt+5UqiU=",
20
+ "reduce-row-tree.wgsl.jinja": "Bwa5xcI0bTmKXb4r9Cc1bfVbM5rNqqpQVrWWVqcb8xA=",
21
+ "reduce-serial-axis.wgsl.jinja": "fvUV9htqzKzt4Pg05pYtRmup/5QIHYaUGQHJZnthXKo=",
22
+ "test.json": "rZ8qxOrYJTsIKPSVeVsqAbInFA12l0rOb0JEpHhG6Ag="
23
  }
24
  },
25
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
26
+ "webgpu": {
27
+ "manifestSpec": "2.0",
28
+ "variants": {
29
+ "contiguous_suffix_subgroup_vec4": ["reduce-row-subgroup.wgsl.jinja"],
30
+ "contiguous_suffix_tree_vec4": ["reduce-row-tree.wgsl.jinja"],
31
+ "contiguous_suffix_tree": ["reduce-row-tree.wgsl.jinja"],
32
+ "multi_axis_rank3": ["reduce-serial-axis.wgsl.jinja"],
33
+ "multi_axis_rank4": ["reduce-serial-axis.wgsl.jinja"],
34
+ "noop_empty_axes": ["reduce-noop-empty-axes.wgsl.jinja"],
35
+ "subgroup_rows_last_axis_vec4": ["reduce-row-subgroup-rows.wgsl.jinja"],
36
+ "tree_last_axis_vec4": ["reduce-row-tree.wgsl.jinja"],
37
+ "rank0_scalar": ["reduce-serial-axis.wgsl.jinja"],
38
+ "rank1_axis0": ["reduce-serial-axis.wgsl.jinja"],
39
+ "axis1_parallel": ["reduce-row-tree.wgsl.jinja"],
40
+ "axis_split": ["reduce-axis-split-reduce.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
41
+ "axis_split_tiled_narrow": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-axis0-tilecols.wgsl.jinja"],
42
+ "axis0_splitk": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-axis0-splitk-reduce.wgsl.jinja"],
43
+ "axis0_tilecols": ["reduce-axis0-tilecols.wgsl.jinja"],
44
+ "all_axes_flat": ["reduce-axis0-splitk-combine.wgsl.jinja", "reduce-flat-partial.wgsl.jinja"],
45
+ "rankn_single_axis_generic": ["reduce-serial-axis.wgsl.jinja"],
46
+ "subgroup_last_axis_vec4": ["reduce-row-subgroup.wgsl.jinja"],
47
+ "subgroup_last_axis": ["reduce-row-subgroup.wgsl.jinja"],
48
+ "axis0": ["reduce-serial-axis.wgsl.jinja"],
49
+ "axis1": ["reduce-serial-axis.wgsl.jinja"],
50
+ "all_axes_keepdims": ["reduce-serial-axis.wgsl.jinja"],
51
+ "all_axes_no_keepdims": ["reduce-serial-axis.wgsl.jinja"]
52
+ }
53
+ }
54
  }
build/webgpu/reduce-axis-split-reduce.wgsl.jinja CHANGED
@@ -4,30 +4,53 @@
4
  // inner-axis elements. The combine pass folds the segments and finalizes the
5
  // selected reduction.
6
  //
7
- // logsumexp writes three partial planes per output and segment: the segment
8
- // maximum, the sum of exp(x - maximum), and a packed NaN marker.
9
- {% set castF32 = source.castF32 is defined and source.castF32 %}
 
 
10
  {% set xa = "f32(" if castF32 else "" %}
11
  {% set ax = ")" if castF32 else "" %}
12
- {% if source.usesF16 is defined and source.usesF16 %}
13
  enable f16;
14
  {% endif %}
15
  {{ env.wgsl.resourceDeclarations }}
16
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
17
- * evaluation, so f32 identities are constructed at runtime from their
18
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
 
21
  const WG: u32 = {{ workgroupSize }}u;
22
  const SPLIT: u32 = {{ split }}u;
 
 
 
 
 
 
 
 
 
 
23
 
24
  @compute @workgroup_size(WG, 1, 1)
25
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
26
- @builtin(workgroup_id) wg: vec3<u32>,
27
- @builtin(num_workgroups) nwg: vec3<u32>) {
28
  // 2D-folded output index: wg.z carries the high bits past the
29
  // per-dimension dispatch limit on the x dimension.
30
- let output_index = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
31
  let seg = wg.y;
32
  if (output_index >= params.outputs) { return; }
33
 
@@ -41,9 +64,52 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
41
  var a1 = a0 + chunk;
42
  if (a1 > params.axisDim) { a1 = params.axisDim; }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  var acc = 0.0;
 
45
  for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
 
 
 
 
 
 
 
 
 
 
46
  acc = acc + {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
 
47
  }
48
  partials[seg * params.outputs + output_index] = acc;
 
49
  }
 
4
  // inner-axis elements. The combine pass folds the segments and finalizes the
5
  // selected reduction.
6
  //
7
+ {% if op == "logsumexp" %}
8
+ // Each output segment writes three partial planes: its maximum, the sum of
9
+ // exp(x - maximum), and a packed NaN marker.
10
+ {% endif %}
11
+ {% set castF32 = castF32 is defined and castF32 %}
12
  {% set xa = "f32(" if castF32 else "" %}
13
  {% set ax = ")" if castF32 else "" %}
14
+ {% if usesF16Spec is defined and usesF16Spec %}
15
  enable f16;
16
  {% endif %}
17
  {{ env.wgsl.resourceDeclarations }}
18
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
19
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
20
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
21
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
22
+ fn {{ name }}() -> {{ scalar }} {
23
+ {% if scalar == "i32" %}
24
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
25
+ {% elif scalar == "u32" %}
26
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
27
+ {% else %}
28
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
29
+ return bitcast<f32>(bits);
30
+ {% endif %}
31
+ }
32
+ {%- endmacro %}
33
 
34
 
35
  const WG: u32 = {{ workgroupSize }}u;
36
  const SPLIT: u32 = {{ split }}u;
37
+ {% if op == "logsumexp" %}
38
+ const F32_MIN: f32 = -3.4028234663852886e38;
39
+
40
+ fn is_nan_f32(value: f32) -> bool {
41
+ let bits = bitcast<u32>(value);
42
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
43
+ }
44
+ {% elif op == "max" or op == "min" %}
45
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
46
+ {% endif %}
47
 
48
  @compute @workgroup_size(WG, 1, 1)
49
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
+ @builtin(workgroup_id) wg: vec3<u32>) {
 
51
  // 2D-folded output index: wg.z carries the high bits past the
52
  // per-dimension dispatch limit on the x dimension.
53
+ let output_index = (wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u) * WG + (gid.x % WG);
54
  let seg = wg.y;
55
  if (output_index >= params.outputs) { return; }
56
 
 
64
  var a1 = a0 + chunk;
65
  if (a1 > params.axisDim) { a1 = params.axisDim; }
66
 
67
+ {% if op == "logsumexp" %}
68
+ var local_max = F32_MIN;
69
+ var local_nan_count = 0.0;
70
+ var local_nan_value = 0.0;
71
+ for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
72
+ let value = {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
73
+ if (is_nan_f32(value)) {
74
+ local_nan_count = local_nan_count + 1.0;
75
+ local_nan_value = value;
76
+ } else {
77
+ local_max = max(local_max, value);
78
+ }
79
+ }
80
+ var acc = 0.0;
81
+ if (local_nan_count == 0.0) {
82
+ for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
83
+ acc = acc + exp({{ xa }}x[input_base + axis_index * params.inner]{{ ax }} - local_max);
84
+ }
85
+ }
86
+ partials[seg * params.outputs + output_index] = local_max;
87
+ partials[(SPLIT + seg) * params.outputs + output_index] = acc;
88
+ partials[(2u * SPLIT + seg) * params.outputs + output_index] = select(0.0, local_nan_value, local_nan_count > 0.0);
89
+ {% else %}
90
+ {% if op == "max" %}
91
+ var acc = reduction_identity();
92
+ {% elif op == "min" %}
93
+ var acc = reduction_identity();
94
+ {% elif op == "prod" %}
95
+ var acc = 1.0;
96
+ {% else %}
97
  var acc = 0.0;
98
+ {% endif %}
99
  for (var axis_index = a0; axis_index < a1; axis_index = axis_index + 1u) {
100
+ {% if op == "max" or op == "min" %}
101
+ acc = {{ op }}(acc, {{ xa }}x[input_base + axis_index * params.inner]{{ ax }});
102
+ {% elif op == "prod" %}
103
+ acc = acc * {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
104
+ {% elif op == "l1" %}
105
+ acc = acc + abs({{ xa }}x[input_base + axis_index * params.inner]{{ ax }});
106
+ {% elif op == "l2" or op == "sumsquare" %}
107
+ let value = {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
108
+ acc = acc + value * value;
109
+ {% else %}
110
  acc = acc + {{ xa }}x[input_base + axis_index * params.inner]{{ ax }};
111
+ {% endif %}
112
  }
113
  partials[seg * params.outputs + output_index] = acc;
114
+ {% endif %}
115
  }
build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja CHANGED
@@ -2,31 +2,121 @@
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
5
- {% set yv = "f16(" if source.outputF16 else "" %}
6
- {% set vy = ")" if source.outputF16 else "" %}
7
- {% if source.outputF16 %}
 
 
 
8
  enable f16;
9
  {% endif %}
10
  {{ env.wgsl.resourceDeclarations }}
11
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
12
- * evaluation, so f32 identities are constructed at runtime from their
13
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
 
16
  const WG: u32 = {{ workgroupSize }}u;
17
  const SPLIT: u32 = {{ split }}u;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  @compute @workgroup_size(WG, 1, 1)
20
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
21
  @builtin(num_workgroups) nwg: vec3<u32>) {
22
  let stride = nwg.x * WG;
23
- let start = (gid.y * nwg.x * WG) + gid.x;
24
  for (var col = start; col < params.cols; col = col + stride) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  var total = 0.0;
 
 
26
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
27
  let p = partials[seg * params.cols + col];
 
 
 
 
 
28
  total = total + p;
 
29
  }
 
 
 
 
 
 
30
  y[col] = {{ yv }}log(total){{ vy }};
 
 
 
 
 
 
 
 
 
 
31
  }
32
  }
 
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
5
+ {% set addBias = addBias is defined and addBias %}
6
+ {% set biasCols = biasCols | default(0) %}
7
+ {% set intMode = intMode is defined and intMode %}
8
+ {% set yv = "f16(" if outputF16 else "" %}
9
+ {% set vy = ")" if outputF16 else "" %}
10
+ {% if outputF16 %}
11
  enable f16;
12
  {% endif %}
13
  {{ env.wgsl.resourceDeclarations }}
14
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
15
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
16
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
17
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
18
+ fn {{ name }}() -> {{ scalar }} {
19
+ {% if scalar == "i32" %}
20
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
21
+ {% elif scalar == "u32" %}
22
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
23
+ {% else %}
24
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
25
+ return bitcast<f32>(bits);
26
+ {% endif %}
27
+ }
28
+ {%- endmacro %}
29
 
30
 
31
  const WG: u32 = {{ workgroupSize }}u;
32
  const SPLIT: u32 = {{ split }}u;
33
+ {% if addBias %}
34
+ const BIAS_COLS: u32 = {{ biasCols }}u;
35
+ {% endif %}
36
+ {% if op == "logsumexp" %}
37
+ const F32_MIN: f32 = -3.4028234663852886e38;
38
+ const F32_MAX: f32 = 3.4028234663852886e38;
39
+
40
+ fn is_nan_f32(value: f32) -> bool {
41
+ let bits = bitcast<u32>(value);
42
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
43
+ }
44
+ {% elif op == "max" or op == "min" %}
45
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
46
+ {% endif %}
47
 
48
  @compute @workgroup_size(WG, 1, 1)
49
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
  @builtin(num_workgroups) nwg: vec3<u32>) {
51
  let stride = nwg.x * WG;
52
+ let start = (gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG) + gid.x;
53
  for (var col = start; col < params.cols; col = col + stride) {
54
+ {% if op == "logsumexp" %}
55
+ // Merge SPLIT (segMax, segSumExp) pairs stably; carry NaN / +Inf markers.
56
+ var nan_value = 0.0;
57
+ var has_nan = false;
58
+ var global_max = F32_MIN;
59
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
60
+ let nv = partials[(2u * SPLIT + seg) * params.cols + col];
61
+ if (nv != 0.0 || is_nan_f32(nv)) {
62
+ has_nan = true;
63
+ nan_value = nv;
64
+ }
65
+ global_max = max(global_max, partials[seg * params.cols + col]);
66
+ }
67
+ var sum = 0.0;
68
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
69
+ let seg_max = partials[seg * params.cols + col];
70
+ let seg_sum = partials[(SPLIT + seg) * params.cols + col];
71
+ sum = sum + seg_sum * exp(seg_max - global_max);
72
+ }
73
+ let has_positive_inf = global_max > F32_MAX;
74
+ let finite_or_inf = select(global_max + log(sum), global_max, has_positive_inf);
75
+ y[col] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
76
+ {% else %}
77
+ {% if intMode %}
78
+ {% if op == "prod" %}
79
+ var total = 1i;
80
+ {% else %}
81
+ var total = 0i;
82
+ {% endif %}
83
+ {% else %}
84
+ {% if op == "max" %}
85
+ var total = reduction_identity();
86
+ {% elif op == "min" %}
87
+ var total = reduction_identity();
88
+ {% elif op == "prod" %}
89
+ var total = 1.0;
90
+ {% else %}
91
  var total = 0.0;
92
+ {% endif %}
93
+ {% endif %}
94
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
95
  let p = partials[seg * params.cols + col];
96
+ {% if op == "max" or op == "min" %}
97
+ total = {{ op }}(total, p);
98
+ {% elif op == "prod" %}
99
+ total = total * p;
100
+ {% else %}
101
  total = total + p;
102
+ {% endif %}
103
  }
104
+ {% if addBias %}
105
+ total = total + f32(bias[col % BIAS_COLS]);
106
+ {% endif %}
107
+ {% if op == "l2" %}
108
+ y[col] = {{ yv }}sqrt(total){{ vy }};
109
+ {% elif op == "logsum" %}
110
  y[col] = {{ yv }}log(total){{ vy }};
111
+ {% elif op == "mean" %}
112
+ y[col] = {{ yv }}total / f32(params.rows){{ vy }};
113
+ {% else %}
114
+ {% if outputF16 %}
115
+ y[col] = f16(total);
116
+ {% else %}
117
+ y[col] = total;
118
+ {% endif %}
119
+ {% endif %}
120
+ {% endif %}
121
  }
122
  }
build/webgpu/reduce-axis0-splitk-reduce.wgsl.jinja CHANGED
@@ -2,31 +2,49 @@
2
  // segments increases residency for tall matrices. Each (column, segment)
3
  // invocation reduces one row slice and writes partials[segment * columns +
4
  // column]. Adjacent column threads keep row reads coalesced.
5
- //
6
- // logsumexp writes the segment maximum and sum of exp(x - maximum) as separate
7
- // partial planes; the combine pass merges them stably and handles NaN and +Inf.
8
- {% set castF32 = source.castF32 is defined and source.castF32 %}
9
  {% set xa = "f32(" if castF32 else "" %}
10
  {% set ax = ")" if castF32 else "" %}
11
- {% if source.usesF16 is defined and source.usesF16 %}
12
  enable f16;
13
  {% endif %}
14
  {{ env.wgsl.resourceDeclarations }}
15
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
16
- * evaluation, so f32 identities are constructed at runtime from their
17
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
 
20
  const WG: u32 = {{ workgroupSize }}u;
21
  const SPLIT: u32 = {{ split }}u;
 
 
 
 
 
 
 
 
 
 
22
 
23
  @compute @workgroup_size(WG, 1, 1)
24
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
25
- @builtin(workgroup_id) wg: vec3<u32>,
26
- @builtin(num_workgroups) nwg: vec3<u32>) {
27
- // 2D-folded column index: wg.z carries the high bits past the maxComputeWorkgroupsPerDimension
28
- // workgroup-per-dimension dispatch limit on the x dimension.
29
- let col = (wg.x + wg.z * nwg.x) * WG + (gid.x % WG);
30
  let seg = wg.y;
31
  if (col >= params.cols) { return; }
32
 
@@ -36,9 +54,54 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
36
  var r1 = r0 + chunk;
37
  if (r1 > params.rows) { r1 = params.rows; }
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  var acc = 0.0;
 
40
  for (var row = r0; row < r1; row = row + 1u) {
 
 
 
 
 
 
 
 
 
 
41
  acc = acc + {{ xa }}x[row * params.cols + col]{{ ax }};
 
42
  }
43
  partials[seg * params.cols + col] = acc;
 
44
  }
 
2
  // segments increases residency for tall matrices. Each (column, segment)
3
  // invocation reduces one row slice and writes partials[segment * columns +
4
  // column]. Adjacent column threads keep row reads coalesced.
5
+ {% set castF32 = castF32 is defined and castF32 %}
 
 
 
6
  {% set xa = "f32(" if castF32 else "" %}
7
  {% set ax = ")" if castF32 else "" %}
8
+ {% if usesF16Spec is defined and usesF16Spec %}
9
  enable f16;
10
  {% endif %}
11
  {{ env.wgsl.resourceDeclarations }}
12
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
13
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
14
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
15
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
16
+ fn {{ name }}() -> {{ scalar }} {
17
+ {% if scalar == "i32" %}
18
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
19
+ {% elif scalar == "u32" %}
20
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
21
+ {% else %}
22
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
23
+ return bitcast<f32>(bits);
24
+ {% endif %}
25
+ }
26
+ {%- endmacro %}
27
 
28
 
29
  const WG: u32 = {{ workgroupSize }}u;
30
  const SPLIT: u32 = {{ split }}u;
31
+ {% if op == "logsumexp" %}
32
+ const F32_MIN: f32 = -3.4028234663852886e38;
33
+
34
+ fn is_nan_f32(value: f32) -> bool {
35
+ let bits = bitcast<u32>(value);
36
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
37
+ }
38
+ {% elif op == "max" or op == "min" %}
39
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
40
+ {% endif %}
41
 
42
  @compute @workgroup_size(WG, 1, 1)
43
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
44
+ @builtin(workgroup_id) wg: vec3<u32>) {
45
+ // 2D-folded column index: wg.z carries the high bits past the per-axis dispatch
46
+ // fold width on the x dimension.
47
+ let col = (wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u) * WG + (gid.x % WG);
 
48
  let seg = wg.y;
49
  if (col >= params.cols) { return; }
50
 
 
54
  var r1 = r0 + chunk;
55
  if (r1 > params.rows) { r1 = params.rows; }
56
 
57
+ {% if op == "logsumexp" %}
58
+ var local_max = F32_MIN;
59
+ var local_nan_count = 0.0;
60
+ var local_nan_value = 0.0;
61
+ for (var row = r0; row < r1; row = row + 1u) {
62
+ let value = {{ xa }}x[row * params.cols + col]{{ ax }};
63
+ if (is_nan_f32(value)) {
64
+ local_nan_count = local_nan_count + 1.0;
65
+ local_nan_value = value;
66
+ } else {
67
+ local_max = max(local_max, value);
68
+ }
69
+ }
70
+ var acc = 0.0;
71
+ if (local_nan_count == 0.0) {
72
+ for (var row = r0; row < r1; row = row + 1u) {
73
+ acc = acc + exp({{ xa }}x[row * params.cols + col]{{ ax }} - local_max);
74
+ }
75
+ }
76
+ // Three partial planes: seg max, seg sumexp(x - max), and a packed nan
77
+ // marker (count in the low slot, the nan bit-pattern smuggled as f32).
78
+ partials[seg * params.cols + col] = local_max;
79
+ partials[(SPLIT + seg) * params.cols + col] = acc;
80
+ partials[(2u * SPLIT + seg) * params.cols + col] = select(0.0, local_nan_value, local_nan_count > 0.0);
81
+ {% else %}
82
+ {% if op == "max" %}
83
+ var acc = reduction_identity();
84
+ {% elif op == "min" %}
85
+ var acc = reduction_identity();
86
+ {% elif op == "prod" %}
87
+ var acc = 1.0;
88
+ {% else %}
89
  var acc = 0.0;
90
+ {% endif %}
91
  for (var row = r0; row < r1; row = row + 1u) {
92
+ {% if op == "max" or op == "min" %}
93
+ acc = {{ op }}(acc, {{ xa }}x[row * params.cols + col]{{ ax }});
94
+ {% elif op == "prod" %}
95
+ acc = acc * {{ xa }}x[row * params.cols + col]{{ ax }};
96
+ {% elif op == "l1" %}
97
+ acc = acc + abs({{ xa }}x[row * params.cols + col]{{ ax }});
98
+ {% elif op == "l2" or op == "sumsquare" %}
99
+ let value = {{ xa }}x[row * params.cols + col]{{ ax }};
100
+ acc = acc + value * value;
101
+ {% else %}
102
  acc = acc + {{ xa }}x[row * params.cols + col]{{ ax }};
103
+ {% endif %}
104
  }
105
  partials[seg * params.cols + col] = acc;
106
+ {% endif %}
107
  }
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,13 +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
 
47
- var<workgroup> partial: array<{{ scalar if (source.op == "max" or source.op == "min" or intMode) else "f32" }}, WG>;
 
 
 
 
 
 
 
 
48
  @compute @workgroup_size(WG, 1, 1)
49
- fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>{% if not splitMode %},
50
- @builtin(num_workgroups) nwg: vec3<u32>{% endif %}) {
51
  let tid = lid.x;
52
  let col_lane = tid % TILE_COLS;
53
  let row_lane = tid / TILE_COLS;
@@ -68,13 +93,14 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
68
  // 2D-folded tile index: wg.y carries the high bits past the dispatch limit.
69
  // The batched form reuses this same coalesced axis-0 reduction for a middle
70
  // axis by assigning consecutive tiles to each outer slice.
71
- let tile = wg.x + wg.y * nwg.x;
72
  let col = tile * TILE_COLS + col_lane;
73
  let inputBase = 0u;
74
  let outputIndex = col;
75
  let in_bounds = col < params.cols;
76
  {% endif %}
77
- {% if source.op == "logsum" and not splitMode %}
 
78
 
79
  if (params.rows == 0u) {
80
  if (row_lane == 0u && in_bounds) {
@@ -84,10 +110,117 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
84
  }
85
  {% endif %}
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  var acc = 0.0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  if (in_bounds) {
89
  for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
 
 
 
 
 
 
 
 
 
 
90
  acc = acc + {{ elem }};
 
91
  }
92
  }
93
  partial[tid] = acc;
@@ -96,12 +229,32 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
96
  if (row_lane == 0u && in_bounds) {
97
  var total = partial[col_lane];
98
  for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
 
 
 
 
 
99
  total = total + partial[lane * TILE_COLS + col_lane];
 
100
  }
101
  {% if splitMode %}
102
  partials[seg * params.outputs + outputIndex] = total;
103
  {% else %}
 
 
 
 
 
104
  y[outputIndex] = {{ yv }}log(total){{ vy }};
 
 
 
 
 
 
 
 
105
  {% endif %}
106
  }
 
107
  }
 
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) {
106
  if (row_lane == 0u && in_bounds) {
 
110
  }
111
  {% endif %}
112
 
113
+ var local_max = F32_MIN;
114
+ var local_nan_count = 0.0;
115
+ var local_nan_value = 0.0;
116
+ if (in_bounds) {
117
+ for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
118
+ let value = {{ elem }};
119
+ if (is_nan_f32(value)) {
120
+ local_nan_count = local_nan_count + 1.0;
121
+ local_nan_value = value;
122
+ } else {
123
+ local_max = max(local_max, value);
124
+ }
125
+ }
126
+ }
127
+
128
+ partial[tid] = local_max;
129
+ workgroupBarrier();
130
+ if (row_lane == 0u && in_bounds) {
131
+ var max_lanes = partial[col_lane];
132
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
133
+ max_lanes = max(max_lanes, partial[lane * TILE_COLS + col_lane]);
134
+ }
135
+ partial[col_lane] = max_lanes;
136
+ }
137
+ workgroupBarrier();
138
+ let max_value = partial[col_lane];
139
+ workgroupBarrier();
140
+
141
+ partial[tid] = local_nan_count;
142
+ workgroupBarrier();
143
+ if (row_lane == 0u && in_bounds) {
144
+ var nan_lanes = partial[col_lane];
145
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
146
+ nan_lanes = nan_lanes + partial[lane * TILE_COLS + col_lane];
147
+ }
148
+ partial[col_lane] = nan_lanes;
149
+ }
150
+ workgroupBarrier();
151
+ let nan_count = partial[col_lane];
152
+ workgroupBarrier();
153
+
154
+ partial[tid] = local_nan_value;
155
+ workgroupBarrier();
156
+ if (row_lane == 0u && in_bounds) {
157
+ var nan_value_lanes = partial[col_lane];
158
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
159
+ nan_value_lanes = nan_value_lanes + partial[lane * TILE_COLS + col_lane];
160
+ }
161
+ partial[col_lane] = nan_value_lanes;
162
+ }
163
+ workgroupBarrier();
164
+ let nan_value = partial[col_lane];
165
+ let has_nan = nan_count > 0.0;
166
+ let has_positive_inf = max_value > F32_MAX;
167
+ workgroupBarrier();
168
+
169
  var acc = 0.0;
170
+ if (in_bounds && !has_nan && !has_positive_inf) {
171
+ for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
172
+ acc = acc + exp({{ elem }} - max_value);
173
+ }
174
+ }
175
+ partial[tid] = acc;
176
+ workgroupBarrier();
177
+
178
+ if (row_lane == 0u && in_bounds) {
179
+ var sum = partial[col_lane];
180
+ for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
181
+ sum = sum + partial[lane * TILE_COLS + col_lane];
182
+ }
183
+ {% if splitMode %}
184
+ partials[seg * params.outputs + outputIndex] = max_value;
185
+ partials[(SPLIT + seg) * params.outputs + outputIndex] = sum;
186
+ partials[(2u * SPLIT + seg) * params.outputs + outputIndex] = select(0.0, nan_value, has_nan);
187
+ {% else %}
188
+ let finite_or_inf = select(max_value + log(sum), max_value, has_positive_inf);
189
+ y[outputIndex] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
190
+ {% endif %}
191
+ }
192
+ {% else %}
193
+ {% if op == "logsum" and not splitMode %}
194
+
195
+ if (params.rows == 0u) {
196
+ if (row_lane == 0u && in_bounds) {
197
+ y[outputIndex] = {{ yv }}negative_infinity(){{ vy }};
198
+ }
199
+ return;
200
+ }
201
+ {% endif %}
202
+
203
+ {% if op == "max" or op == "min" %}
204
+ var acc = lane_identity();
205
+ {% elif op == "prod" %}
206
+ var acc = {% if intMode %}{{ scalar }}(1){% else %}1.0{% endif %};
207
+ {% else %}
208
+ var acc = {% if intMode %}{{ scalar }}(0){% else %}0.0{% endif %};
209
+ {% endif %}
210
  if (in_bounds) {
211
  for (var row = {{ rowBegin }}; row < {{ rowEnd }}; row = row + ROW_LANES) {
212
+ {% if op == "max" or op == "min" %}
213
+ acc = {{ op }}(acc, {{ elem }});
214
+ {% elif op == "prod" %}
215
+ acc = acc * {{ elem }};
216
+ {% elif op == "l1" %}
217
+ acc = acc + abs({{ elem }});
218
+ {% elif op == "l2" or op == "sumsquare" %}
219
+ let value = {{ elem }};
220
+ acc = acc + value * value;
221
+ {% else %}
222
  acc = acc + {{ elem }};
223
+ {% endif %}
224
  }
225
  }
226
  partial[tid] = acc;
 
229
  if (row_lane == 0u && in_bounds) {
230
  var total = partial[col_lane];
231
  for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
232
+ {% if op == "max" or op == "min" %}
233
+ total = {{ op }}(total, partial[lane * TILE_COLS + col_lane]);
234
+ {% elif op == "prod" %}
235
+ total = total * partial[lane * TILE_COLS + col_lane];
236
+ {% else %}
237
  total = total + partial[lane * TILE_COLS + col_lane];
238
+ {% endif %}
239
  }
240
  {% if splitMode %}
241
  partials[seg * params.outputs + outputIndex] = total;
242
  {% else %}
243
+ {% if op == "l2" and intMode %}
244
+ y[outputIndex] = {{ scalar }}(sqrt(f32(total)));
245
+ {% elif op == "l2" %}
246
+ y[outputIndex] = {{ yv }}sqrt(total){{ vy }};
247
+ {% elif op == "logsum" %}
248
  y[outputIndex] = {{ yv }}log(total){{ vy }};
249
+ {% elif op == "mean" and intMode %}
250
+ // Integer division truncates toward zero.
251
+ y[outputIndex] = total / {{ scalar }}(params.rows);
252
+ {% elif op == "mean" %}
253
+ y[outputIndex] = {{ yv }}total / f32(params.rows){{ vy }};
254
+ {% else %}
255
+ y[outputIndex] = {{ yv }}total{{ vy }};
256
+ {% endif %}
257
  {% endif %}
258
  }
259
+ {% endif %}
260
  }
build/webgpu/reduce-flat-partial.wgsl.jinja CHANGED
@@ -6,20 +6,37 @@
6
  // Scalar f32 bindings keep arbitrary element counts legal. The grid-stride loop
7
  // manually assembles full vec4 groups from contiguous scalars, and one global
8
  // thread folds the final zero-to-three scalar elements exactly once.
9
- {% set castF32 = source.castF32 is defined and source.castF32 %}
 
10
  {% set xa = "f32(" if castF32 else "" %}
11
  {% set ax = ")" if castF32 else "" %}
12
- {% if source.usesF16 is defined and source.usesF16 %}
13
  enable f16;
14
  {% endif %}
15
  {{ env.wgsl.resourceDeclarations }}
16
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
17
- * evaluation, so f32 identities are constructed at runtime from their
18
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
 
21
  const WG: u32 = {{ workgroupSize }}u;
22
- var<workgroup> red: array<{{ "i32" if source.intMode else "f32" }}, WG>;
 
 
 
 
23
  @compute @workgroup_size(WG)
24
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
25
  @builtin(local_invocation_id) lid: vec3<u32>,
@@ -27,12 +44,38 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
27
  @builtin(num_workgroups) nwg: vec3<u32>) {
28
  let tid = lid.x;
29
  let gstride = nwg.x * WG;
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  var acc = 0.0;
 
31
  // Grid-stride over the flat vec4 groups (params.count4 = numel / 4, floored).
32
  for (var i = gid.x; i < params.count4; i = i + gstride) {
33
  let b = 4u * i;
34
- let v = vec4<{{ "i32" if source.intMode else "f32" }}>({{ xa }}x[b]{{ ax }}, {{ xa }}x[b + 1u]{{ ax }}, {{ xa }}x[b + 2u]{{ ax }}, {{ xa }}x[b + 3u]{{ ax }});
 
 
 
 
 
 
 
 
 
 
 
35
  acc = acc + v.x + v.y + v.z + v.w;
 
36
  }
37
  // Scalar tail (the 0..3 elements past the last full vec4). One global thread
38
  // folds it so it is counted exactly once; the count is tiny so serializing it
@@ -40,7 +83,19 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
40
  if (gid.x == 0u) {
41
  for (var i = 4u * params.count4; i < params.numel; i = i + 1u) {
42
  let s = {{ xa }}x[i]{{ ax }};
 
 
 
 
 
 
 
 
 
 
 
43
  acc = acc + s;
 
44
  }
45
  }
46
  red[tid] = acc;
@@ -49,7 +104,15 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
49
  loop {
50
  if (stride == 0u) { break; }
51
  if (tid < stride) {
 
 
 
 
 
 
 
52
  red[tid] = red[tid] + red[tid + stride];
 
53
  }
54
  stride = stride / 2u;
55
  workgroupBarrier();
 
6
  // Scalar f32 bindings keep arbitrary element counts legal. The grid-stride loop
7
  // manually assembles full vec4 groups from contiguous scalars, and one global
8
  // thread folds the final zero-to-three scalar elements exactly once.
9
+ {% set intMode = intMode is defined and intMode %}
10
+ {% set castF32 = castF32 is defined and castF32 %}
11
  {% set xa = "f32(" if castF32 else "" %}
12
  {% set ax = ")" if castF32 else "" %}
13
+ {% if usesF16Spec is defined and usesF16Spec %}
14
  enable f16;
15
  {% endif %}
16
  {{ env.wgsl.resourceDeclarations }}
17
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
18
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
19
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
20
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
21
+ fn {{ name }}() -> {{ scalar }} {
22
+ {% if scalar == "i32" %}
23
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
24
+ {% elif scalar == "u32" %}
25
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
26
+ {% else %}
27
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
28
+ return bitcast<f32>(bits);
29
+ {% endif %}
30
+ }
31
+ {%- endmacro %}
32
 
33
 
34
  const WG: u32 = {{ workgroupSize }}u;
35
+ var<workgroup> red: array<{{ "i32" if intMode else "f32" }}, WG>;
36
+ {% if op == "max" or op == "min" %}
37
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
38
+
39
+ {% endif %}
40
  @compute @workgroup_size(WG)
41
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
42
  @builtin(local_invocation_id) lid: vec3<u32>,
 
44
  @builtin(num_workgroups) nwg: vec3<u32>) {
45
  let tid = lid.x;
46
  let gstride = nwg.x * WG;
47
+ {% if intMode %}
48
+ {% if op == "prod" %}
49
+ var acc = 1i;
50
+ {% else %}
51
+ var acc = 0i;
52
+ {% endif %}
53
+ {% elif op == "max" %}
54
+ var acc = reduction_identity();
55
+ {% elif op == "min" %}
56
+ var acc = reduction_identity();
57
+ {% elif op == "prod" %}
58
+ var acc = 1.0;
59
+ {% else %}
60
  var acc = 0.0;
61
+ {% endif %}
62
  // Grid-stride over the flat vec4 groups (params.count4 = numel / 4, floored).
63
  for (var i = gid.x; i < params.count4; i = i + gstride) {
64
  let b = 4u * i;
65
+ let v = vec4<{{ "i32" if intMode else "f32" }}>({{ xa }}x[b]{{ ax }}, {{ xa }}x[b + 1u]{{ ax }}, {{ xa }}x[b + 2u]{{ ax }}, {{ xa }}x[b + 3u]{{ ax }});
66
+ {% if op == "max" %}
67
+ acc = max(acc, max(max(v.x, v.y), max(v.z, v.w)));
68
+ {% elif op == "min" %}
69
+ acc = min(acc, min(min(v.x, v.y), min(v.z, v.w)));
70
+ {% elif op == "prod" %}
71
+ acc = acc * v.x * v.y * v.z * v.w;
72
+ {% elif op == "l1" %}
73
+ acc = acc + abs(v.x) + abs(v.y) + abs(v.z) + abs(v.w);
74
+ {% elif op == "l2" or op == "sumsquare" %}
75
+ acc = acc + dot(v, v);
76
+ {% else %}
77
  acc = acc + v.x + v.y + v.z + v.w;
78
+ {% endif %}
79
  }
80
  // Scalar tail (the 0..3 elements past the last full vec4). One global thread
81
  // folds it so it is counted exactly once; the count is tiny so serializing it
 
83
  if (gid.x == 0u) {
84
  for (var i = 4u * params.count4; i < params.numel; i = i + 1u) {
85
  let s = {{ xa }}x[i]{{ ax }};
86
+ {% if op == "max" %}
87
+ acc = max(acc, s);
88
+ {% elif op == "min" %}
89
+ acc = min(acc, s);
90
+ {% elif op == "prod" %}
91
+ acc = acc * s;
92
+ {% elif op == "l1" %}
93
+ acc = acc + abs(s);
94
+ {% elif op == "l2" or op == "sumsquare" %}
95
+ acc = acc + s * s;
96
+ {% else %}
97
  acc = acc + s;
98
+ {% endif %}
99
  }
100
  }
101
  red[tid] = acc;
 
104
  loop {
105
  if (stride == 0u) { break; }
106
  if (tid < stride) {
107
+ {% if op == "max" %}
108
+ red[tid] = max(red[tid], red[tid + stride]);
109
+ {% elif op == "min" %}
110
+ red[tid] = min(red[tid], red[tid + stride]);
111
+ {% elif op == "prod" %}
112
+ red[tid] = red[tid] * red[tid + stride];
113
+ {% else %}
114
  red[tid] = red[tid] + red[tid + stride];
115
+ {% endif %}
116
  }
117
  stride = stride / 2u;
118
  workgroupBarrier();
build/webgpu/reduce-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,63 @@
1
- // Subgroup row reduction for a contiguous last axis. One workgroup owns each
2
  // output row. Threads reduce strided chunks, subgroup leaders deposit their
3
  // partials in workgroup memory, and thread 0 folds those slots and finalizes.
4
- //
5
- // Accumulators are f32 except for integer max/min. Mean, l2, and logsum apply
6
- // division, square root, or logarithm only at finalization. logsumexp uses
7
- // max-subtraction, bit-exact NaN detection, NaN propagation, and a +Inf
8
- // short-circuit. Runtime bitcasts provide max/min infinity identities because
9
- // WGSL constant evaluation rejects infinite constants. f16 storage is widened
10
- // before accumulation and narrowed only for the final store.
11
- {% set castF32 = source.castF32 is defined and source.castF32 %}
 
 
 
 
 
 
 
12
  {% set scalar = "f32" if castF32 else scalar %}
13
- {% set xv = ("vec4<f32>(" if source.vec4 else "f32(") if castF32 else "" %}
14
  {% set vx = ")" if castF32 else "" %}
15
  {% set yv = "f16(" if castF32 else "" %}
16
  {% set vy = ")" if castF32 else "" %}
17
  enable subgroups;
18
- {% if source.usesF16 is defined and source.usesF16 %}
19
  enable f16;
20
  {% endif %}
21
  {{ env.wgsl.resourceDeclarations }}
22
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
23
- * evaluation, so f32 identities are constructed at runtime from their
24
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
 
27
  const WG: u32 = {{ workgroupSize }}u;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  var<workgroup> wgPartial: array<{{ scalar }}, WG>;
29
 
30
  {% macro emit_reduce(name, collective, combine) %}
@@ -44,26 +77,141 @@ fn {{ name }}(value: {{ scalar }}, sgLid: u32, sgId: u32, numSg: u32) -> {{ scal
44
  workgroupBarrier();
45
  return total;
46
  }
47
- {%- endmacro %}{{ emit_reduce("reduce_row", "subgroupAdd", "total = total + wgPartial[i];") }}
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  @compute @workgroup_size(WG, 1, 1)
49
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
50
- @builtin(num_workgroups) nwg: vec3<u32>,
51
  @builtin(local_invocation_id) lid: vec3<u32>,
52
  @builtin(subgroup_invocation_id) sgLid: u32,
53
  @builtin(subgroup_id) sgId: u32,
54
  @builtin(num_subgroups) numSg: u32) {
55
- let row = wg.x + wg.y * nwg.x;
56
  if (row >= params.rows) {
57
  return;
58
  }
59
  let tid = lid.x;
60
- let base = row * params.chunkCount; let INIT: f32 = 0.0;{% if source.vec4 %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  var acc4 = vec4<{{ scalar }}>(INIT);
62
  for (var c = tid; c < params.chunkCount; c = c + WG) {
63
- let v = {{ xv }}x[base + c]{{ vx }}; acc4 = acc4 + v; } let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);{% else %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  var acc = INIT;
65
  for (var c = tid; c < params.chunkCount; c = c + WG) {
66
- let v = {{ xv }}x[base + c]{{ vx }}; acc = acc + v; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  {%- endif %}
68
  let total = reduce_row(acc, sgLid, sgId, numSg);
69
- if (tid == 0u) { y[row] = {{ yv }}log(total){{ vy }}; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Subgroup reduction for a contiguous last axis. One workgroup owns each
2
  // output row. Threads reduce strided chunks, subgroup leaders deposit their
3
  // partials in workgroup memory, and thread 0 folds those slots and finalizes.
4
+ {% if op == "mean" %}
5
+ // The finalizer divides the f32 sum by the row width.
6
+ {% elif op == "l2" %}
7
+ // The finalizer takes the square root of the sum of squares.
8
+ {% elif op == "logsum" %}
9
+ // The finalizer takes the logarithm of the sum.
10
+ {% elif op == "logsumexp" %}
11
+ // Max subtraction, explicit NaN propagation, and a positive-infinity branch
12
+ // define the stable log-sum-exp result.
13
+ {% elif op == "max" or op == "min" %}
14
+ // Integer values retain their native type; f32 identities are constructed from
15
+ // IEEE-754 bit patterns because WGSL rejects infinite constants.
16
+ {% endif %}
17
+ // f16 storage widens before accumulation and narrows only at the final store.
18
+ {% set castF32 = castF32 is defined and castF32 %}
19
  {% set scalar = "f32" if castF32 else scalar %}
20
+ {% set xv = ("vec4<f32>(" if vec4 else "f32(") if castF32 else "" %}
21
  {% set vx = ")" if castF32 else "" %}
22
  {% set yv = "f16(" if castF32 else "" %}
23
  {% set vy = ")" if castF32 else "" %}
24
  enable subgroups;
25
+ {% if usesF16Spec is defined and usesF16Spec %}
26
  enable f16;
27
  {% endif %}
28
  {{ env.wgsl.resourceDeclarations }}
29
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
30
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
31
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
32
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
33
+ fn {{ name }}() -> {{ scalar }} {
34
+ {% if scalar == "i32" %}
35
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
36
+ {% elif scalar == "u32" %}
37
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
38
+ {% else %}
39
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
40
+ return bitcast<f32>(bits);
41
+ {% endif %}
42
+ }
43
+ {%- endmacro %}
44
 
45
 
46
  const WG: u32 = {{ workgroupSize }}u;
47
+ {%- if op == "max" or op == "min" %}
48
+ {{ wgsl_minmax_identity("reduction_identity", op, scalar) }}
49
+ {%- endif %}
50
+ {%- if op == "logsumexp" %}
51
+
52
+ const F32_MIN: f32 = -3.4028234663852886e38;
53
+ const F32_MAX: f32 = 3.4028234663852886e38;
54
+
55
+ fn is_nan_f32(value: f32) -> bool {
56
+ let bits = bitcast<u32>(value);
57
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
58
+ }
59
+ {%- endif %}
60
+
61
  var<workgroup> wgPartial: array<{{ scalar }}, WG>;
62
 
63
  {% macro emit_reduce(name, collective, combine) %}
 
77
  workgroupBarrier();
78
  return total;
79
  }
80
+ {%- endmacro %}
81
+ {%- if op == "max" %}
82
+ {{ emit_reduce("reduce_row", "subgroupMax", "total = max(total, wgPartial[i]);") }}
83
+ {%- elif op == "min" %}
84
+ {{ emit_reduce("reduce_row", "subgroupMin", "total = min(total, wgPartial[i]);") }}
85
+ {%- elif op == "prod" %}
86
+ {{ emit_reduce("reduce_row", "subgroupMul", "total = total * wgPartial[i];") }}
87
+ {%- elif op == "logsumexp" %}
88
+ {{ emit_reduce("reduce_row_add", "subgroupAdd", "total = total + wgPartial[i];") }}
89
+ {{ emit_reduce("reduce_row_max", "subgroupMax", "total = max(total, wgPartial[i]);") }}
90
+ {%- else %}
91
+ {{ emit_reduce("reduce_row", "subgroupAdd", "total = total + wgPartial[i];") }}
92
+ {%- endif %}
93
+
94
  @compute @workgroup_size(WG, 1, 1)
95
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
 
96
  @builtin(local_invocation_id) lid: vec3<u32>,
97
  @builtin(subgroup_invocation_id) sgLid: u32,
98
  @builtin(subgroup_id) sgId: u32,
99
  @builtin(num_subgroups) numSg: u32) {
100
+ let row = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
101
  if (row >= params.rows) {
102
  return;
103
  }
104
  let tid = lid.x;
105
+ let base = row * params.chunkCount;
106
+ {%- if op == "logsumexp" %}
107
+ var localMax = F32_MIN;
108
+ var localNan = 0.0;
109
+ var localNanValue = 0.0;
110
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
111
+ let v = {{ xv }}x[base + c]{{ vx }};
112
+ {%- if vec4 %}
113
+ {% for comp in ["x", "y", "z", "w"] %}
114
+ if (is_nan_f32(v.{{ comp }})) {
115
+ localNan = 1.0;
116
+ localNanValue = v.{{ comp }};
117
+ } else {
118
+ localMax = max(localMax, v.{{ comp }});
119
+ }
120
+ {%- endfor %}
121
+ {% else %}
122
+ if (is_nan_f32(v)) {
123
+ localNan = 1.0;
124
+ localNanValue = v;
125
+ } else {
126
+ localMax = max(localMax, v);
127
+ }
128
+ {%- endif %}
129
+ }
130
+ let rowMax = reduce_row_max(localMax, sgLid, sgId, numSg);
131
+ let nanCount = reduce_row_add(localNan, sgLid, sgId, numSg);
132
+ let nanValue = reduce_row_add(localNanValue, sgLid, sgId, numSg);
133
+ let hasPositiveInf = rowMax > F32_MAX;
134
+ let hasNan = nanCount > 0.0;
135
+ var acc = 0.0;
136
+ for (var c = tid; c < params.chunkCount; c = c + WG) {
137
+ let v = {{ xv }}x[base + c]{{ vx }};
138
+ {%- if vec4 %}
139
+ let e = select(exp(v - vec4<f32>(rowMax)), vec4<f32>(0.0), hasPositiveInf || hasNan);
140
+ acc = acc + ((e.x + e.y) + (e.z + e.w));
141
+ {%- else %}
142
+ acc = acc + select(exp(v - rowMax), 0.0, hasPositiveInf || hasNan);
143
+ {%- endif %}
144
+ }
145
+ let sum = reduce_row_add(acc, sgLid, sgId, numSg);
146
+ if (tid == 0u) {
147
+ let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
148
+ y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
149
+ }
150
+ {%- else %}
151
+ {% if op == "max" or op == "min" %}
152
+ let INIT: {{ scalar }} = reduction_identity();
153
+ {%- elif op == "prod" %}
154
+ let INIT: f32 = 1.0;
155
+ {%- else %}
156
+ let INIT: f32 = 0.0;
157
+ {%- endif %}
158
+ {% if vec4 %}
159
  var acc4 = vec4<{{ scalar }}>(INIT);
160
  for (var c = tid; c < params.chunkCount; c = c + WG) {
161
+ let v = {{ xv }}x[base + c]{{ vx }};
162
+ {%- if op == "max" %}
163
+ acc4 = max(acc4, v);
164
+ {%- elif op == "min" %}
165
+ acc4 = min(acc4, v);
166
+ {%- elif op == "prod" %}
167
+ acc4 = acc4 * v;
168
+ {%- elif op == "l1" %}
169
+ acc4 = acc4 + abs(v);
170
+ {%- elif op == "l2" or op == "sumsquare" %}
171
+ acc4 = acc4 + v * v;
172
+ {%- else %}
173
+ acc4 = acc4 + v;
174
+ {%- endif %}
175
+ }
176
+ {%- if op == "max" %}
177
+ let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
178
+ {%- elif op == "min" %}
179
+ let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
180
+ {%- elif op == "prod" %}
181
+ let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
182
+ {%- else %}
183
+ let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
184
+ {%- endif %}
185
+ {% else %}
186
  var acc = INIT;
187
  for (var c = tid; c < params.chunkCount; c = c + WG) {
188
+ let v = {{ xv }}x[base + c]{{ vx }};
189
+ {%- if op == "max" %}
190
+ acc = max(acc, v);
191
+ {%- elif op == "min" %}
192
+ acc = min(acc, v);
193
+ {%- elif op == "prod" %}
194
+ acc = acc * v;
195
+ {%- elif op == "l1" %}
196
+ acc = acc + abs(v);
197
+ {%- elif op == "l2" or op == "sumsquare" %}
198
+ acc = acc + v * v;
199
+ {%- else %}
200
+ acc = acc + v;
201
+ {%- endif %}
202
+ }
203
  {%- endif %}
204
  let total = reduce_row(acc, sgLid, sgId, numSg);
205
+ if (tid == 0u) {
206
+ {%- if op == "mean" %}
207
+ y[row] = {{ yv }}total / f32(params.cols){{ vy }};
208
+ {%- elif op == "l2" %}
209
+ y[row] = {{ yv }}sqrt(total){{ vy }};
210
+ {%- elif op == "logsum" %}
211
+ y[row] = {{ yv }}log(total){{ vy }};
212
+ {%- else %}
213
+ y[row] = {{ yv }}total{{ vy }};
214
+ {%- endif %}
215
+ }
216
+ {%- endif %}
217
+ }
build/webgpu/reduce-row-tree.wgsl.jinja CHANGED
@@ -1,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,50 +52,215 @@ fn negative_infinity() -> f32 {
34
 
35
  const WG: u32 = {{ workgroupSize }}u;
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  {% set is_int = scalar == "i32" or scalar == "u32" %}
38
- {% set intAdditive = is_int and (source.op == "sum" or source.op == "l1" or source.op == "sumsquare"
39
- or source.op == "l2" or source.op == "mean") %}
40
- {% set accType = scalar if source.op == "max" or source.op == "min" or source.op == "prod" or intAdditive else "f32" %}
 
 
 
 
 
 
 
41
  fn identity() -> {{ accType }} { return {{ accType }}(0); }
 
42
 
43
  fn combine(a: {{ accType }}, b: {{ accType }}) -> {{ accType }} {
 
 
 
 
 
 
 
44
  return a + b;
 
45
  }
46
 
47
  var<workgroup> partial: array<{{ accType }}, WG>;
 
48
 
49
  @compute @workgroup_size(WG, 1, 1)
50
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
51
- @builtin(num_workgroups) nwg: vec3<u32>,
52
  @builtin(local_invocation_id) lid: vec3<u32>) {
53
- let row = wg.x + wg.y * nwg.x;
54
  if (row >= params.rows) {
55
  return;
56
  }
57
  let tid = lid.x;
58
- {% if source.vec4 %}
59
  let base = row * params.chunkCount;
60
  {% else %}
61
  let base = row * params.cols;
62
  {% endif %}
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  if ({{ rowIsEmpty }}) {
65
  if (tid == 0u) { y[row] = {{ yv }}negative_infinity(){{ vy }}; }
66
  return;
67
  }
 
68
 
69
- {% if source.vec4 %}
70
  var acc4 = vec4<{{ accType }}>(identity());
71
  for (var col = tid; col < params.chunkCount; col = col + WG) {
72
  let value = {{ xv }}x[base + col]{{ vx }};
 
 
 
 
 
 
 
 
 
 
 
73
  acc4 = acc4 + value;
 
74
  }
 
 
 
 
 
 
 
75
  let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
 
76
  {% else %}
77
  var acc = identity();
78
  for (var col = tid; col < params.cols; col = col + WG) {
79
  let value = {{ xv }}x[base + col]{{ vx }};
 
 
 
 
 
80
  acc = combine(acc, value);
 
81
  }
82
  {% endif %}
83
  partial[tid] = acc;
@@ -92,6 +275,19 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
92
 
93
  if (tid == 0u) {
94
  let total = partial[0];
 
 
 
 
 
95
  y[row] = {{ yv }}log(total){{ vy }};
 
 
 
 
 
 
 
96
  }
 
97
  }
 
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
+
59
+ var<workgroup> partial: array<f32, WG>;
60
+ {% macro wgsl_tree_reduce_f32(name, mode, buffer="partial", wg="WG", trailingBarrier=true) %}
61
+ fn {{ name }}(value: f32, tid: u32) -> f32 {
62
+ {{ buffer }}[tid] = value;
63
+ workgroupBarrier();
64
+ // Ceil-halving keeps every lane when the workgroup size is not a power of
65
+ // two. For even n this matches the power-of-two tree order; for odd n, lanes
66
+ // [0, n-half) fold the upper tail while the middle lane carries forward.
67
+ var n: u32 = {{ wg }};
68
+ loop {
69
+ let half = (n + 1u) / 2u;
70
+ if (tid < n - half) {
71
+ {% if mode == "max" %}
72
+ {{ buffer }}[tid] = max({{ buffer }}[tid], {{ buffer }}[tid + half]);
73
+ {% else %}
74
+ {{ buffer }}[tid] = {{ buffer }}[tid] + {{ buffer }}[tid + half];
75
+ {% endif %}
76
+ }
77
+ workgroupBarrier();
78
+ n = half;
79
+ if (n == 1u) {
80
+ break;
81
+ }
82
+ }
83
+ // The default trailing barrier makes this helper safe for back-to-back calls: every lane reads
84
+ // slot 0 here, so the next call's first store must not run until all lanes have read it.
85
+ // `trailingBarrier=false` is safe only when the buffer is never written again before kernel exit.
86
+ let reduced = {{ buffer }}[0];
87
+ {% if trailingBarrier %}
88
+ workgroupBarrier();
89
+ {% endif %}
90
+ return reduced;
91
+ }
92
+ {% endmacro %}
93
+
94
+ {{ wgsl_tree_reduce_f32("reduce_sum", "add", "partial", "WG") }}
95
+ {{ wgsl_tree_reduce_f32("reduce_max", "max", "partial", "WG") }}
96
+
97
+ fn is_nan_f32(value: f32) -> bool {
98
+ let bits = bitcast<u32>(value);
99
+ return (bits & 0x7f800000u) == 0x7f800000u
100
+ && (bits & 0x007fffffu) != 0u;
101
+ }
102
+ {% else %}
103
  {% set is_int = scalar == "i32" or scalar == "u32" %}
104
+ {% set intAdditive = is_int and (op == "sum" or op == "l1" or op == "sumsquare"
105
+ or op == "l2" or op == "mean") %}
106
+ {% set accType = scalar if op == "max" or op == "min" or op == "prod" or intAdditive else "f32" %}
107
+ {% if op == "max" or op == "min" %}
108
+ {{ wgsl_minmax_identity("identity", op, accType) }}
109
+ {% elif op == "prod" and accType == "i32" %}
110
+ fn identity() -> i32 { return 1i; }
111
+ {% elif op == "prod" %}
112
+ fn identity() -> {{ accType }} { return {{ accType }}(1); }
113
+ {% else %}
114
  fn identity() -> {{ accType }} { return {{ accType }}(0); }
115
+ {% endif %}
116
 
117
  fn combine(a: {{ accType }}, b: {{ accType }}) -> {{ accType }} {
118
+ {% if op == "max" %}
119
+ return max(a, b);
120
+ {% elif op == "min" %}
121
+ return min(a, b);
122
+ {% elif op == "prod" %}
123
+ return a * b;
124
+ {% else %}
125
  return a + b;
126
+ {% endif %}
127
  }
128
 
129
  var<workgroup> partial: array<{{ accType }}, WG>;
130
+ {% endif %}
131
 
132
  @compute @workgroup_size(WG, 1, 1)
133
  fn main(@builtin(workgroup_id) wg: vec3<u32>,
 
134
  @builtin(local_invocation_id) lid: vec3<u32>) {
135
+ let row = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
136
  if (row >= params.rows) {
137
  return;
138
  }
139
  let tid = lid.x;
140
+ {% if vec4 %}
141
  let base = row * params.chunkCount;
142
  {% else %}
143
  let base = row * params.cols;
144
  {% endif %}
145
 
146
+ {% if op == "logsumexp" %}
147
+ if ({{ rowIsEmpty }}) {
148
+ if (tid == 0u) {
149
+ y[row] = {{ yv }}negative_infinity(){{ vy }};
150
+ }
151
+ return;
152
+ }
153
+
154
+ var localMax = F32_MIN;
155
+ var localNan = 0.0;
156
+ var localNanValue = 0.0;
157
+ {% if vec4 %}
158
+ for (var col = tid; col < params.chunkCount; col = col + WG) {
159
+ let value = {{ xv }}x[base + col]{{ vx }};
160
+ {% for component in ["x", "y", "z", "w"] %}
161
+ if (is_nan_f32(value.{{ component }})) {
162
+ localNan = 1.0;
163
+ localNanValue = value.{{ component }};
164
+ } else {
165
+ localMax = max(localMax, value.{{ component }});
166
+ }
167
+ {% endfor %}
168
+ }
169
+ {% else %}
170
+ for (var col = tid; col < params.cols; col = col + WG) {
171
+ let value = {{ xv }}x[base + col]{{ vx }};
172
+ if (is_nan_f32(value)) {
173
+ localNan = 1.0;
174
+ localNanValue = value;
175
+ } else {
176
+ localMax = max(localMax, value);
177
+ }
178
+ }
179
+ {% endif %}
180
+ let rowMax = reduce_max(localMax, tid);
181
+ workgroupBarrier();
182
+ let nanCount = reduce_sum(localNan, tid);
183
+ workgroupBarrier();
184
+ let nanValue = reduce_sum(localNanValue, tid);
185
+ workgroupBarrier();
186
+ let hasPositiveInf = rowMax > F32_MAX;
187
+ let hasNan = nanCount > 0.0;
188
+
189
+ var acc = 0.0;
190
+ {% if vec4 %}
191
+ for (var col = tid; col < params.chunkCount; col = col + WG) {
192
+ let value = {{ xv }}x[base + col]{{ vx }};
193
+ let exponentials = select(exp(value - vec4<f32>(rowMax)), vec4<f32>(0.0),
194
+ hasPositiveInf || hasNan);
195
+ acc = acc + (exponentials.x + exponentials.y)
196
+ + (exponentials.z + exponentials.w);
197
+ }
198
+ {% else %}
199
+ for (var col = tid; col < params.cols; col = col + WG) {
200
+ acc = acc + select(exp({{ xv }}x[base + col]{{ vx }} - rowMax), 0.0,
201
+ hasPositiveInf || hasNan);
202
+ }
203
+ {% endif %}
204
+ let sum = reduce_sum(acc, tid);
205
+ if (tid == 0u) {
206
+ let finiteOrInf = select(rowMax + log(sum), rowMax, hasPositiveInf);
207
+ y[row] = {{ yv }}select(finiteOrInf, nanValue, hasNan){{ vy }};
208
+ }
209
+ {% else %}
210
+ {% if op == "mean" %}
211
+ if ({{ rowIsEmpty }}) {
212
+ {% if is_int %}
213
+ if (tid == 0u) { y[row] = {{ scalar }}(0); }
214
+ {% else %}
215
+ if (tid == 0u) { y[row] = {{ yv }}0.0{{ vy }}; }
216
+ {% endif %}
217
+ return;
218
+ }
219
+ {% elif op == "logsum" %}
220
  if ({{ rowIsEmpty }}) {
221
  if (tid == 0u) { y[row] = {{ yv }}negative_infinity(){{ vy }}; }
222
  return;
223
  }
224
+ {% endif %}
225
 
226
+ {% if vec4 %}
227
  var acc4 = vec4<{{ accType }}>(identity());
228
  for (var col = tid; col < params.chunkCount; col = col + WG) {
229
  let value = {{ xv }}x[base + col]{{ vx }};
230
+ {% if op == "max" %}
231
+ acc4 = max(acc4, value);
232
+ {% elif op == "min" %}
233
+ acc4 = min(acc4, value);
234
+ {% elif op == "prod" %}
235
+ acc4 = acc4 * value;
236
+ {% elif op == "l1" %}
237
+ acc4 = acc4 + abs(value);
238
+ {% elif op == "l2" or op == "sumsquare" %}
239
+ acc4 = acc4 + value * value;
240
+ {% else %}
241
  acc4 = acc4 + value;
242
+ {% endif %}
243
  }
244
+ {% if op == "max" %}
245
+ let acc = max(max(acc4.x, acc4.y), max(acc4.z, acc4.w));
246
+ {% elif op == "min" %}
247
+ let acc = min(min(acc4.x, acc4.y), min(acc4.z, acc4.w));
248
+ {% elif op == "prod" %}
249
+ let acc = (acc4.x * acc4.y) * (acc4.z * acc4.w);
250
+ {% else %}
251
  let acc = (acc4.x + acc4.y) + (acc4.z + acc4.w);
252
+ {% endif %}
253
  {% else %}
254
  var acc = identity();
255
  for (var col = tid; col < params.cols; col = col + WG) {
256
  let value = {{ xv }}x[base + col]{{ vx }};
257
+ {% if op == "l1" %}
258
+ acc = combine(acc, abs(value));
259
+ {% elif op == "l2" or op == "sumsquare" %}
260
+ acc = combine(acc, value * value);
261
+ {% else %}
262
  acc = combine(acc, value);
263
+ {% endif %}
264
  }
265
  {% endif %}
266
  partial[tid] = acc;
 
275
 
276
  if (tid == 0u) {
277
  let total = partial[0];
278
+ {% if op == "l2" and is_int %}
279
+ y[row] = {{ scalar }}(sqrt(f32(total)));
280
+ {% elif op == "l2" %}
281
+ y[row] = {{ yv }}sqrt(total){{ vy }};
282
+ {% elif op == "logsum" %}
283
  y[row] = {{ yv }}log(total){{ vy }};
284
+ {% elif op == "mean" and is_int %}
285
+ y[row] = total / {{ scalar }}(params.cols);
286
+ {% elif op == "mean" %}
287
+ y[row] = {{ yv }}total / f32(params.cols){{ vy }};
288
+ {% else %}
289
+ y[row] = {{ yv }}total{{ vy }};
290
+ {% endif %}
291
  }
292
+ {% endif %}
293
  }
build/webgpu/reduce-serial-axis.wgsl.jinja CHANGED
@@ -1,144 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 yv = "f16(" if castF32 else "" %}
5
  {% set vy = ")" if castF32 else "" %}
6
- {% if source.usesF16 is defined and source.usesF16 %}
7
  enable f16;
8
  {% endif %}
9
  {{ env.wgsl.resourceDeclarations }}
10
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
11
- * evaluation, so f32 identities are constructed at runtime from their
12
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
14
  fn negative_infinity() -> f32 {
15
  var bits = 0xff800000u;
16
  return bitcast<f32>(bits);
17
  }
18
 
19
- {% if source.indexing == "rankn" %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
22
  var rem = out_index;
23
- {% for axis in range(source.outputRank) %}
24
  {% set out_stride = namespace(value=1) %}
25
- {% for j in range(axis + 1, source.outputRank) %}
26
- {% set out_stride.value = out_stride.value * source.outputShape[j] %}
27
  {% endfor %}
28
  {% set safe_out_stride = 1 if out_stride.value == 0 else out_stride.value %}
29
- {% if not source.keepDims or axis != source.axis %}
30
  let out_coord{{ axis }} = rem / {{ safe_out_stride }}u;
31
  {% endif %}
32
  rem = rem % {{ safe_out_stride }}u;
33
  {% endfor %}
34
- {% for axis in range(source.rank) %}
35
- {% if axis == source.axis %}
36
  let coord{{ axis }} = reduce_index;
37
- {% elif source.keepDims %}
38
  let coord{{ axis }} = out_coord{{ axis }};
39
- {% elif axis < source.axis %}
40
  let coord{{ axis }} = out_coord{{ axis }};
41
  {% else %}
42
  let coord{{ axis }} = out_coord{{ axis - 1 }};
43
  {% endif %}
44
  {% endfor %}
45
  {% set src = namespace(value="coord0") %}
46
- {% for axis in range(1, source.rank) %}
47
- {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[axis] ~ "u + coord" ~ axis ~ ")" %}
48
  {% endfor %}
49
  return {{ src.value }};
50
  }
51
  {% endif %}
52
- {% if source.indexing == "multiaxis" %}
53
  {% set hasReducedAxis = namespace(value=false) %}
54
- {% for a in range(source.rank) %}{% if source.reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
55
-
56
- // One thread per output element walks the Cartesian product of the reduced axes,
57
- // linearized as reduce_linear. Specialized shapes make every input offset a sum
58
- // of coordinate-times-constant terms.
59
- fn input_offset(out_index: u32{% if hasReducedAxis.value %}, reduce_linear: u32{% endif %}) -> u32 {
60
- var rem = out_index;
61
- {% for oaxis in range(source.outputRank) %}
62
- {% set ostride = namespace(value=1) %}
63
- {% for j in range(oaxis + 1, source.outputRank) %}
64
- {% set ostride.value = ostride.value * source.outputShape[j] %}
65
- {% endfor %}
66
- {% set osafe = 1 if ostride.value == 0 else ostride.value %}
67
- {% if not source.keepDims or not source.reduce[oaxis] %}
68
- let out_coord{{ oaxis }} = rem / {{ osafe }}u;
69
  {% endif %}
70
- rem = rem % {{ osafe }}u;
71
- {% endfor %}
72
- {% if hasReducedAxis.value %}
73
- var rrem = reduce_linear;
74
- {% endif %}
75
- {% for a in range(source.rank) if source.reduce[a] %}
76
- {% set rstride = namespace(value=1) %}
77
- {% for b in range(a + 1, source.rank) if source.reduce[b] %}
78
- {% set rstride.value = rstride.value * source.dataShape[b] %}
79
- {% endfor %}
80
- {% set rsafe = 1 if rstride.value == 0 else rstride.value %}
81
- let red_coord{{ a }} = rrem / {{ rsafe }}u;
82
- rrem = rrem % {{ rsafe }}u;
83
- {% endfor %}
84
- {% set oc = namespace(i=0) %}
85
- {% for a in range(source.rank) %}
86
- {% if source.reduce[a] %}
87
- let coord{{ a }} = red_coord{{ a }};
88
- {% elif source.keepDims %}
89
- let coord{{ a }} = out_coord{{ a }};
90
- {% else %}
91
- let coord{{ a }} = out_coord{{ oc.i }};
92
- {% set oc.i = oc.i + 1 %}
93
- {% endif %}
94
- {% endfor %}
95
- {% set src = namespace(value="coord0") %}
96
- {% for a in range(1, source.rank) %}
97
- {% set src.value = "(" ~ src.value ~ " * " ~ source.dataShape[a] ~ "u + coord" ~ a ~ ")" %}
98
- {% endfor %}
99
- return {{ src.value }};
100
- }
101
- {% endif %}
102
- {% if source.indexing == "multiaxis" %}
103
  {% set mcount = namespace(value=1) %}
104
- {% for a in range(source.rank) if source.reduce[a] %}
105
- {% set mcount.value = mcount.value * source.dataShape[a] %}
106
  {% endfor %}
107
  {% set count = mcount.value ~ "u" %}
108
  {% if hasReducedAxis.value %}
109
  {% set at = "x[input_offset(i, r)]" %}
 
110
  {% else %}
111
  {% set at = "x[input_offset(i)]" %}
 
112
  {% endif %}
113
- {% elif source.indexing == "rankn" %}
114
  {% set count = "params.axisDim" %}
115
  {% set at = "x[input_offset(i, r)]" %}
 
116
  {% elif axis == 0 %}
117
  {% set count = "params.rows" %}
118
  {% set at = "x[r * params.cols + i]" %}
 
119
  {% else %}
120
  {% set count = "params.cols" %}
121
  {% set at = "x[i * params.cols + r]" %}
 
122
  {% endif %}
123
  {% if castF32 %}
124
  {% set at = "f32(" ~ at ~ ")" %}
 
125
  {% endif %}
126
 
127
  @compute @workgroup_size({{ reduceWorkgroupSize }})
128
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
129
  // 2D-folded flat index: gid.y carries the high bits past the
130
- // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
131
- let i = gid.x + gid.y * nwg.x * {{ reduceWorkgroupSize }}u;
132
  if (i >= params.outCount) {
133
  return;
134
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  if ({{ count }} == 0u) {
136
  y[i] = {{ yv }}negative_infinity(){{ vy }};
137
  return;
138
  }
139
- var acc = {{ "1.0" if source.op == "prod" else "0.0" }};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  for (var r = 0u; r < {{ count }}; r = r + 1u) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  acc = acc + {{ at }};
 
142
  }
 
 
 
 
 
 
 
143
  y[i] = {{ yv }}log(acc){{ vy }};
 
 
 
 
 
 
 
 
 
 
144
  }
 
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 %}
90
+ const F32_MAX: f32 = 3.4028234663852886e38;
91
+
92
+ fn is_nan_f32(value: f32) -> bool {
93
+ let bits = bitcast<u32>(value);
94
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
95
+ }
96
+ {% endif %}
97
+ {% endif %}
98
+ {% if indexing == "rankn" %}
99
 
100
  fn input_offset(out_index: u32, reduce_index: u32) -> u32 {
101
  var rem = out_index;
102
+ {% for axis in range(outputRank) %}
103
  {% set out_stride = namespace(value=1) %}
104
+ {% for j in range(axis + 1, outputRank) %}
105
+ {% set out_stride.value = out_stride.value * outputShape[j] %}
106
  {% endfor %}
107
  {% set safe_out_stride = 1 if out_stride.value == 0 else out_stride.value %}
108
+ {% if not keepDims or axis != axisSpec %}
109
  let out_coord{{ axis }} = rem / {{ safe_out_stride }}u;
110
  {% endif %}
111
  rem = rem % {{ safe_out_stride }}u;
112
  {% endfor %}
113
+ {% for axis in range(rank) %}
114
+ {% if axis == axisSpec %}
115
  let coord{{ axis }} = reduce_index;
116
+ {% elif keepDims %}
117
  let coord{{ axis }} = out_coord{{ axis }};
118
+ {% elif axis < axisSpec %}
119
  let coord{{ axis }} = out_coord{{ axis }};
120
  {% else %}
121
  let coord{{ axis }} = out_coord{{ axis - 1 }};
122
  {% endif %}
123
  {% endfor %}
124
  {% set src = namespace(value="coord0") %}
125
+ {% for axis in range(1, rank) %}
126
+ {% set src.value = "(" ~ src.value ~ " * " ~ dataShape[axis] ~ "u + coord" ~ axis ~ ")" %}
127
  {% endfor %}
128
  return {{ src.value }};
129
  }
130
  {% endif %}
131
+ {% if indexing == "multiaxis" %}
132
  {% set hasReducedAxis = namespace(value=false) %}
133
+ {% for a in range(rank) %}{% if reduce[a] %}{% set hasReducedAxis.value = true %}{% endif %}{% endfor %}
134
+ {{- reduce_multi_axis_offset(hasReducedAxis.value) }}
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  {% endif %}
136
+ {% if indexing == "multiaxis" %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  {% set mcount = namespace(value=1) %}
138
+ {% for a in range(rank) if reduce[a] %}
139
+ {% set mcount.value = mcount.value * dataShape[a] %}
140
  {% endfor %}
141
  {% set count = mcount.value ~ "u" %}
142
  {% if hasReducedAxis.value %}
143
  {% set at = "x[input_offset(i, r)]" %}
144
+ {% set at_first = "x[input_offset(i, 0u)]" %}
145
  {% else %}
146
  {% set at = "x[input_offset(i)]" %}
147
+ {% set at_first = "x[input_offset(i)]" %}
148
  {% endif %}
149
+ {% elif indexing == "rankn" %}
150
  {% set count = "params.axisDim" %}
151
  {% set at = "x[input_offset(i, r)]" %}
152
+ {% set at_first = "x[input_offset(i, 0u)]" %}
153
  {% elif axis == 0 %}
154
  {% set count = "params.rows" %}
155
  {% set at = "x[r * params.cols + i]" %}
156
+ {% set at_first = "x[i]" %}
157
  {% else %}
158
  {% set count = "params.cols" %}
159
  {% set at = "x[i * params.cols + r]" %}
160
+ {% set at_first = "x[i * params.cols]" %}
161
  {% endif %}
162
  {% if castF32 %}
163
  {% set at = "f32(" ~ at ~ ")" %}
164
+ {% set at_first = "f32(" ~ at_first ~ ")" %}
165
  {% endif %}
166
 
167
  @compute @workgroup_size({{ reduceWorkgroupSize }})
168
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
169
  // 2D-folded flat index: gid.y carries the high bits past the
170
+ // per-axis dispatch fold width (outputs > 16.7M elements).
171
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ reduceWorkgroupSize }}u;
172
  if (i >= params.outCount) {
173
  return;
174
  }
175
+ {% if op == "logsumexp" %}
176
+ {% if intMode %}
177
+ // Integer logsumexp widens each element for exp/log, then truncates the result
178
+ // back to the integer output type.
179
+ // Integers are never NaN, so the f32 NaN-propagation path is unnecessary here.
180
+ if ({{ count }} == 0u) {
181
+ y[i] = {{ scalar }}(F32_MIN);
182
+ return;
183
+ }
184
+ var m = F32_MIN;
185
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
186
+ m = max(m, f32({{ at }}));
187
+ }
188
+ var acc = 0.0;
189
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
190
+ acc = acc + exp(f32({{ at }}) - m);
191
+ }
192
+ y[i] = {{ scalar }}(m + log(acc));
193
+ {% else %}
194
  if ({{ count }} == 0u) {
195
  y[i] = {{ yv }}negative_infinity(){{ vy }};
196
  return;
197
  }
198
+ var m = F32_MIN;
199
+ var has_nan = false;
200
+ var nan_value = 0.0;
201
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
202
+ let value = {{ at }};
203
+ if (is_nan_f32(value)) {
204
+ has_nan = true;
205
+ nan_value = value;
206
+ } else {
207
+ m = max(m, value);
208
+ }
209
+ }
210
+ if (has_nan) {
211
+ y[i] = {{ yv }}nan_value{{ vy }};
212
+ return;
213
+ }
214
+ if (m > F32_MAX) {
215
+ y[i] = {{ yv }}m{{ vy }};
216
+ return;
217
+ }
218
+ var acc = 0.0;
219
  for (var r = 0u; r < {{ count }}; r = r + 1u) {
220
+ acc = acc + exp({{ at }} - m);
221
+ }
222
+ y[i] = {{ yv }}m + log(acc){{ vy }};
223
+ {% endif %}
224
+ {% elif op == "max" or op == "min" %}
225
+ if ({{ count }} == 0u) {
226
+ y[i] = {{ yv }}empty_value(){{ vy }};
227
+ return;
228
+ }
229
+ var acc = {{ at_first }};
230
+ for (var r = 1u; r < {{ count }}; r = r + 1u) {
231
+ acc = {{ op }}(acc, {{ at }});
232
+ }
233
+ y[i] = {{ yv }}acc{{ vy }};
234
+ {% else %}
235
+ {% if op == "mean" %}
236
+ if ({{ count }} == 0u) {
237
+ {% if intMode %}
238
+ y[i] = {{ scalar }}(0);
239
+ {% else %}
240
+ y[i] = {{ yv }}0.0{{ vy }};
241
+ {% endif %}
242
+ return;
243
+ }
244
+ {% elif op == "logsum" %}
245
+ if ({{ count }} == 0u) {
246
+ y[i] = {{ yv }}negative_infinity(){{ vy }};
247
+ return;
248
+ }
249
+ {% endif %}
250
+ {% if intMode %}
251
+ // Integer reduction accumulates in the output type. sum/prod/l1/sumsquare stay
252
+ // in integer arithmetic; l2/logsum widen for sqrt/log and truncate afterward.
253
+ var acc = {{ scalar }}({{ "1" if op == "prod" else "0" }});
254
+ {% else %}
255
+ var acc = {{ "1.0" if op == "prod" else "0.0" }};
256
+ {% endif %}
257
+ for (var r = 0u; r < {{ count }}; r = r + 1u) {
258
+ {% if op == "prod" %}
259
+ acc = acc * {{ at }};
260
+ {% elif op == "l1" %}
261
+ acc = acc + abs({{ at }});
262
+ {% elif op == "l2" or op == "sumsquare" %}
263
+ let value = {{ at }};
264
+ acc = acc + value * value;
265
+ {% else %}
266
  acc = acc + {{ at }};
267
+ {% endif %}
268
  }
269
+ {% if op == "l2" %}
270
+ {% if intMode %}
271
+ y[i] = {{ scalar }}(sqrt(f32(acc)));
272
+ {% else %}
273
+ y[i] = {{ yv }}sqrt(acc){{ vy }};
274
+ {% endif %}
275
+ {% elif op == "logsum" %}
276
  y[i] = {{ yv }}log(acc){{ vy }};
277
+ {% elif op == "mean" %}
278
+ {% if intMode %}
279
+ y[i] = acc / {{ scalar }}({{ count }});
280
+ {% else %}
281
+ y[i] = {{ yv }}acc / f32({{ count }}){{ vy }};
282
+ {% endif %}
283
+ {% else %}
284
+ y[i] = {{ yv }}acc{{ vy }};
285
+ {% endif %}
286
+ {% endif %}
287
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.ReduceLogSum",
3
  "fixtureArrays": {
4
  "onnx_backend_reduce_log_sum_input_x": [0.54881352186203, 0.7151893377304077, 0.6027633547782898, 0.5448831915855408, 0.42365479469299316, 0.6458941102027893, 0.4375872015953064, 0.891772985458374, 0.9636627435684204, 0.3834415078163147, 0.7917250394821167, 0.5288949012756348, 0.5680445432662964, 0.9255966544151306, 0.07103605568408966, 0.08712930232286453, 0.020218396559357643, 0.832619845867157, 0.7781567573547363, 0.8700121641159058, 0.978618323802948, 0.7991585731506348, 0.4614793658256531, 0.7805292010307312, 0.11827442795038223, 0.6399210095405579, 0.14335328340530396, 0.9446688890457153, 0.5218483209609985, 0.4146619439125061, 0.26455560326576233, 0.7742336988449097, 0.4561503231525421, 0.568433940410614, 0.018789799883961678, 0.6176354885101318, 0.6120957136154175, 0.6169340014457703, 0.9437480568885803, 0.681820273399353, 0.35950788855552673, 0.43703195452690125, 0.6976311802864075, 0.0602254718542099, 0.6667667031288147, 0.670637845993042, 0.21038256585597992, 0.12892629206180573, 0.31542834639549255, 0.36371076107025146, 0.5701967477798462, 0.4386015236377716, 0.9883738160133362, 0.10204481333494186, 0.20887675881385803, 0.16130951046943665, 0.6531082987785339, 0.25329160690307617, 0.4663107693195343, 0.24442559480667114]
5
  },
@@ -7,7 +6,7 @@
7
  {
8
  "name": "all_axes_flat_rank1_boundary_8192",
9
  "provenance": {
10
- "notes": "The parallel full-reduction threshold must supersede the rank1 serial and row-reduction fallbacks."
11
  },
12
  "attrs": { "axes": [0], "keepdims": 0 },
13
  "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
@@ -120,7 +119,7 @@
120
  "provenance": {
121
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
122
  "test": "ReductionOpTest.ReduceLogSum",
123
- "notes": "Axis-0 companion: the reduced sums are finite subnormal, so ReduceLogSum should stay finite rather than returning -Infinity."
124
  },
125
  "attrs": { "axes": [0], "keepdims": 0 },
126
  "inputs": {
@@ -456,7 +455,7 @@
456
  "provenance": {
457
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
458
  "test": "ReductionOpTest.ReduceLogSumAxes01",
459
- "notes": "Compact positive tensor covering the same multi-axis ReduceLogSum surface as the ORT case."
460
  },
461
  "attrs": { "axes": [0, 1], "keepdims": 0 },
462
  "inputs": {
@@ -477,7 +476,7 @@
477
  "shape": [2, 2, 1024],
478
  "data": {
479
  "kind": "cycle",
480
- "values": [1.0, 2.0, 0.5, 3.25, 1.5, 2.0, 0.75, 4.0, 3.5, 1.25, 0.25, 2.25, 5.0, 4.0, 2.75, 1.0]
481
  }
482
  }
483
  },
@@ -534,7 +533,7 @@
534
  },
535
  "outputs": { "y": { "dtype": "float32", "shape": [3], "allowNaN": true, "tolerance": 0 } },
536
  "provenance": {
537
- "notes": "Row 0 sums to 6.0 (finite positive, log(6)=1.7917...), row 1 sums to -15.0 (log(-15)=NaN), row 2 sums to -0.5 (log(-0.5)=NaN). Only row 0 is finite; rows 1 and 2 are NaN. Evaluated against the trusted TS reference."
538
  }
539
  },
540
  {
@@ -544,15 +543,15 @@
544
  "x": {
545
  "dtype": "float32",
546
  "shape": [2, 4],
547
- "data": { "kind": "values", "values": [1.0, -1.0, 2.0, -2.0, 0.0, 0.0, 0.0, 0.0] }
548
  }
549
  },
550
  "outputs": {
551
  "y": {
552
  "dtype": "float32",
553
  "shape": [2],
554
- "data": { "kind": "values", "values": ["-Infinity", "-Infinity"] },
555
- "tolerance": 0
556
  }
557
  }
558
  },
@@ -571,7 +570,7 @@
571
  {
572
  "name": "axis0_narrow_f32_8192x3_splitk_guard_lock",
573
  "provenance": {
574
- "notes": "Compact lock below the historical 16-column split-K guard. Constant ones verify log is applied once after combining all partial sums."
575
  },
576
  "attrs": { "axes": [0], "keepdims": 0 },
577
  "inputs": { "x": { "dtype": "float32", "shape": [8192, 3], "data": { "kind": "constant", "value": 1.0 } } },
@@ -741,6 +740,30 @@
741
  }
742
  },
743
  "outputs": { "y": { "dtype": "float16", "shape": [64], "tolerance": 0.05, "relTolerance": 0.002 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
  }
745
  ]
746
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "onnx_backend_reduce_log_sum_input_x": [0.54881352186203, 0.7151893377304077, 0.6027633547782898, 0.5448831915855408, 0.42365479469299316, 0.6458941102027893, 0.4375872015953064, 0.891772985458374, 0.9636627435684204, 0.3834415078163147, 0.7917250394821167, 0.5288949012756348, 0.5680445432662964, 0.9255966544151306, 0.07103605568408966, 0.08712930232286453, 0.020218396559357643, 0.832619845867157, 0.7781567573547363, 0.8700121641159058, 0.978618323802948, 0.7991585731506348, 0.4614793658256531, 0.7805292010307312, 0.11827442795038223, 0.6399210095405579, 0.14335328340530396, 0.9446688890457153, 0.5218483209609985, 0.4146619439125061, 0.26455560326576233, 0.7742336988449097, 0.4561503231525421, 0.568433940410614, 0.018789799883961678, 0.6176354885101318, 0.6120957136154175, 0.6169340014457703, 0.9437480568885803, 0.681820273399353, 0.35950788855552673, 0.43703195452690125, 0.6976311802864075, 0.0602254718542099, 0.6667667031288147, 0.670637845993042, 0.21038256585597992, 0.12892629206180573, 0.31542834639549255, 0.36371076107025146, 0.5701967477798462, 0.4386015236377716, 0.9883738160133362, 0.10204481333494186, 0.20887675881385803, 0.16130951046943665, 0.6531082987785339, 0.25329160690307617, 0.4663107693195343, 0.24442559480667114]
4
  },
 
6
  {
7
  "name": "all_axes_flat_rank1_boundary_8192",
8
  "provenance": {
9
+ "notes": "Exactly 8,192 rank-1 elements exercise the inclusive lower boundary of the parallel full reduction."
10
  },
11
  "attrs": { "axes": [0], "keepdims": 0 },
12
  "inputs": { "x": { "dtype": "float32", "shape": [8192], "data": { "kind": "constant", "value": 1.0 } } },
 
119
  "provenance": {
120
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
121
  "test": "ReductionOpTest.ReduceLogSum",
122
+ "notes": "The axis-0 sums are finite subnormal values, so their logarithms should remain finite rather than return negative infinity."
123
  },
124
  "attrs": { "axes": [0], "keepdims": 0 },
125
  "inputs": {
 
455
  "provenance": {
456
  "source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
457
  "test": "ReductionOpTest.ReduceLogSumAxes01",
458
+ "notes": "A compact positive tensor exercises multi-axis ReduceLogSum with a finite logarithm."
459
  },
460
  "attrs": { "axes": [0, 1], "keepdims": 0 },
461
  "inputs": {
 
476
  "shape": [2, 2, 1024],
477
  "data": {
478
  "kind": "cycle",
479
+ "values": [1.0, 2.0, 0.5, 3.25, 1.5, 2.0, 0.75, 4.0, 3.5, 1.25, 0.25, 2.25, 5.0, 4.0, 2.75, 1.0, 6.5]
480
  }
481
  }
482
  },
 
533
  },
534
  "outputs": { "y": { "dtype": "float32", "shape": [3], "allowNaN": true, "tolerance": 0 } },
535
  "provenance": {
536
+ "notes": "Row 0 sums to 6 and returns log(6); rows 1 and 2 sum to negative values and therefore return NaN."
537
  }
538
  },
539
  {
 
543
  "x": {
544
  "dtype": "float32",
545
  "shape": [2, 4],
546
+ "data": { "kind": "values", "values": [1.0, -1.0, 2.0, -2.0, 1.0, 0.0, 0.0, 0.0] }
547
  }
548
  },
549
  "outputs": {
550
  "y": {
551
  "dtype": "float32",
552
  "shape": [2],
553
+ "data": { "kind": "values", "values": ["-Infinity", 0.0] },
554
+ "tolerance": 0.000001
555
  }
556
  }
557
  },
 
570
  {
571
  "name": "axis0_narrow_f32_8192x3_splitk_guard_lock",
572
  "provenance": {
573
+ "notes": "An 8,192-by-3 axis-0 reduction exercises split-K with a narrow output. Constant ones verify that log is applied once after all partial sums are combined."
574
  },
575
  "attrs": { "axes": [0], "keepdims": 0 },
576
  "inputs": { "x": { "dtype": "float32", "shape": [8192, 3], "data": { "kind": "constant", "value": 1.0 } } },
 
740
  }
741
  },
742
  "outputs": { "y": { "dtype": "float16", "shape": [64], "tolerance": 0.05, "relTolerance": 0.002 } }
743
+ },
744
+ {
745
+ "name": "subgroup_rows_last_axis_f32_96x256",
746
+ "attrs": { "axes": [-1], "keepdims": 0 },
747
+ "inputs": {
748
+ "x": {
749
+ "dtype": "float32",
750
+ "shape": [96, 256],
751
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "offset": 2.0 }
752
+ }
753
+ },
754
+ "outputs": { "y": { "dtype": "float32", "shape": [96], "tolerance": 0.0002, "relTolerance": 0.0001 } }
755
+ },
756
+ {
757
+ "name": "subgroup_rows_last_axis_f16_80x1024",
758
+ "attrs": { "axes": [1], "keepdims": 0 },
759
+ "inputs": {
760
+ "x": {
761
+ "dtype": "float16",
762
+ "shape": [80, 1024],
763
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.07, "scale": 0.2, "offset": 2.0 }
764
+ }
765
+ },
766
+ "outputs": { "y": { "dtype": "float16", "shape": [80], "tolerance": 0.05, "relTolerance": 0.002 } }
767
  }
768
  ]
769
  }