sync 6fdf6301e2bb
Browse files- README.md +8 -2
- build/webgpu/bench.json +1 -1
- build/webgpu/manifest.json +129 -143
- build/webgpu/metadata.json +14 -13
- build/webgpu/reduce-arg-axis-split-combine.wgsl.jinja +14 -20
- build/webgpu/reduce-arg-axis-split-reduce.wgsl.jinja +28 -14
- build/webgpu/reduce-arg-axis-split-tiled.wgsl.jinja +5 -16
- build/webgpu/reduce-arg-axis-tiled.wgsl.jinja +4 -11
- build/webgpu/reduce-arg-axis.wgsl.jinja +13 -22
- build/webgpu/reduce-arg-row-split.wgsl.jinja +49 -79
- build/webgpu/reduce-arg-row-subgroup.wgsl.jinja +75 -95
- build/webgpu/test.json +122 -10
README.md
CHANGED
|
@@ -45,6 +45,12 @@ Default values (overridable per request):
|
|
| 45 |
| `T` | `float32`, `float16`, `int32`, `uint32`, `int16`, `int8`, `uint8` |
|
| 46 |
| `I` | `int64` |
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
## Device requirements
|
| 49 |
|
| 50 |
Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
|
@@ -54,7 +60,7 @@ Some implementation variants require `subgroups`. These are route-specific capab
|
|
| 54 |
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 55 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 56 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 57 |
-
- [`bench.json`](build/webgpu/bench.json) — benchmark
|
| 58 |
- [`reduce-arg-axis-split-combine.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-combine.wgsl.jinja)
|
| 59 |
- [`reduce-arg-axis-split-reduce.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-reduce.wgsl.jinja)
|
| 60 |
- [`reduce-arg-axis-split-tiled.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-tiled.wgsl.jinja)
|
|
@@ -66,7 +72,7 @@ Some implementation variants require `subgroups`. These are route-specific capab
|
|
| 66 |
## Use with `@huggingface/kernels`
|
| 67 |
|
| 68 |
```sh
|
| 69 |
-
npm install --save-exact @huggingface/kernels@0.0.1-preview.
|
| 70 |
```
|
| 71 |
|
| 72 |
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
|
|
|
| 45 |
| `T` | `float32`, `float16`, `int32`, `uint32`, `int16`, `int8`, `uint8` |
|
| 46 |
| `I` | `int64` |
|
| 47 |
|
| 48 |
+
## Implementation variants
|
| 49 |
+
|
| 50 |
+
One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
|
| 51 |
+
|
| 52 |
+
- `axis_split_packed` — Scans four adjacent float32 inner-axis columns per invocation while preserving each column’s axis order and the scalar partial/combine layout. Small output grids retain the scalar route.
|
| 53 |
+
|
| 54 |
## Device requirements
|
| 55 |
|
| 56 |
Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
|
|
|
| 60 |
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 61 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 62 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 63 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark cases
|
| 64 |
- [`reduce-arg-axis-split-combine.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-combine.wgsl.jinja)
|
| 65 |
- [`reduce-arg-axis-split-reduce.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-reduce.wgsl.jinja)
|
| 66 |
- [`reduce-arg-axis-split-tiled.wgsl.jinja`](build/webgpu/reduce-arg-axis-split-tiled.wgsl.jinja)
|
|
|
|
| 72 |
## Use with `@huggingface/kernels`
|
| 73 |
|
| 74 |
```sh
|
| 75 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.3
|
| 76 |
```
|
| 77 |
|
| 78 |
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
build/webgpu/bench.json
CHANGED
|
@@ -137,7 +137,7 @@
|
|
| 137 |
"name": "argmax-lastaxis-f32-64x50260-vocab-control",
|
| 138 |
"preset": "stress",
|
| 139 |
"provenance": {
|
| 140 |
-
"notes": "
|
| 141 |
},
|
| 142 |
"vars": { "dtype": "float32", "rows": 64, "cols": 50260 },
|
| 143 |
"attrs": { "axis": 1 },
|
|
|
|
| 137 |
"name": "argmax-lastaxis-f32-64x50260-vocab-control",
|
| 138 |
"preset": "stress",
|
| 139 |
"provenance": {
|
| 140 |
+
"notes": "The next multiple-of-four vocabulary width above the paired 50,257-column case, with nearly the same input bytes."
|
| 141 |
},
|
| 142 |
"vars": { "dtype": "float32", "rows": 64, "cols": 50260 },
|
| 143 |
"attrs": { "axis": 1 },
|
build/webgpu/manifest.json
CHANGED
|
@@ -60,71 +60,9 @@
|
|
| 60 |
"rowSplitScratchBytes": "rowSplitCount * outputCount * 8",
|
| 61 |
"rowSplitScratchFits": "rowSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and rowSplitScratchBytes <= device.limits.maxBufferSize",
|
| 62 |
"rowSplitDispatchFits": "outputCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and rowSplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
|
| 63 |
-
"tileDispatchFits": "ceilDiv(outputCount, tunables.TILE_COLUMNS) <= foldedDispatchCapacity"
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
"x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
|
| 67 |
-
"partials_val": { "buffer": "storage", "elementType": "u32" },
|
| 68 |
-
"partials_idx": { "buffer": "storage", "elementType": "u32" },
|
| 69 |
-
"params": {
|
| 70 |
-
"buffer": "uniform",
|
| 71 |
-
"struct": [
|
| 72 |
-
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 73 |
-
{ "name": "chunkCount", "type": "u32", "value": "axisDim / 4" }
|
| 74 |
-
]
|
| 75 |
-
},
|
| 76 |
-
"partials_val_2": { "name": "partials_val", "buffer": "read-only-storage", "elementType": "u32" },
|
| 77 |
-
"partials_idx_2": { "name": "partials_idx", "buffer": "read-only-storage", "elementType": "u32" },
|
| 78 |
-
"y": { "buffer": "storage", "elementType": "u32" },
|
| 79 |
-
"params_2": {
|
| 80 |
-
"name": "params",
|
| 81 |
-
"buffer": "uniform",
|
| 82 |
-
"struct": [{ "name": "outputCount", "type": "u32", "value": "outputCount" }]
|
| 83 |
-
},
|
| 84 |
-
"x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "$scalar" },
|
| 85 |
-
"params_3": {
|
| 86 |
-
"name": "params",
|
| 87 |
-
"buffer": "uniform",
|
| 88 |
-
"struct": [
|
| 89 |
-
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 90 |
-
{ "name": "chunkCount", "type": "u32", "value": "axisDim" }
|
| 91 |
-
]
|
| 92 |
-
},
|
| 93 |
-
"params_4": {
|
| 94 |
-
"name": "params",
|
| 95 |
-
"buffer": "uniform",
|
| 96 |
-
"struct": [
|
| 97 |
-
{ "name": "axisDim", "type": "u32", "value": "axisDim" },
|
| 98 |
-
{ "name": "outputCount", "type": "u32", "value": "outputCount" },
|
| 99 |
-
{ "name": "innerSize", "type": "u32", "value": "axisInner" }
|
| 100 |
-
]
|
| 101 |
-
},
|
| 102 |
-
"params_5": {
|
| 103 |
-
"name": "params",
|
| 104 |
-
"buffer": "uniform",
|
| 105 |
-
"struct": [
|
| 106 |
-
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 107 |
-
{ "name": "chunkCount", "type": "u32", "value": "axisDim / 4" },
|
| 108 |
-
{
|
| 109 |
-
"name": "rowStride",
|
| 110 |
-
"type": "u32",
|
| 111 |
-
"value": "max(1, min(outputCount, min(device.limits.maxComputeWorkgroupsPerDimension, 65535)))"
|
| 112 |
-
}
|
| 113 |
-
]
|
| 114 |
-
},
|
| 115 |
-
"params_6": {
|
| 116 |
-
"name": "params",
|
| 117 |
-
"buffer": "uniform",
|
| 118 |
-
"struct": [
|
| 119 |
-
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 120 |
-
{ "name": "chunkCount", "type": "u32", "value": "axisDim" },
|
| 121 |
-
{
|
| 122 |
-
"name": "rowStride",
|
| 123 |
-
"type": "u32",
|
| 124 |
-
"value": "max(1, min(outputCount, min(device.limits.maxComputeWorkgroupsPerDimension, 65535)))"
|
| 125 |
-
}
|
| 126 |
-
]
|
| 127 |
-
}
|
| 128 |
},
|
| 129 |
"variants": [
|
| 130 |
{
|
|
@@ -134,8 +72,6 @@
|
|
| 134 |
"derive": {
|
| 135 |
"scalar": "dtypes.T",
|
| 136 |
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
| 137 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 138 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 139 |
"workgroupSize": "workgroupSize",
|
| 140 |
"split": "rowSplitCount"
|
| 141 |
},
|
|
@@ -148,8 +84,7 @@
|
|
| 148 |
"id": "split_reduce",
|
| 149 |
"name": "ArgMax.LastAxisSplitVec4Reduce",
|
| 150 |
"shader": "reduce-arg-row-split.wgsl.jinja",
|
| 151 |
-
"
|
| 152 |
-
"derive": { "modeSpec": "\"max\"", "vec4": true, "useSubgroups": "device.features.has(\"subgroups\")" },
|
| 153 |
"bindings": ["x", "partials_val", "partials_idx", "params"],
|
| 154 |
"dispatch": { "x": "outputCount", "y": "rowSplitCount" }
|
| 155 |
},
|
|
@@ -157,8 +92,7 @@
|
|
| 157 |
"id": "combine",
|
| 158 |
"name": "ArgMax.LastAxisSplitCombine",
|
| 159 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 160 |
-
"
|
| 161 |
-
"bindings": ["partials_val_2", "partials_idx_2", "y", "params_2"],
|
| 162 |
"dispatch": {
|
| 163 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 164 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
@@ -171,13 +105,7 @@
|
|
| 171 |
"id": "last_axis_split",
|
| 172 |
"priority": 41,
|
| 173 |
"when": ["baseContract", "rowParallelType", "lastAxis", "axisDim >= tunables.ROW_SPLIT_MIN_AXIS", "axisDim % 4 != 0", "outputCount <= tunables.ROW_SPLIT_MAX_OUTPUTS", "rowSplitScratchFits", "rowSplitDispatchFits", "rowStorageFits"],
|
| 174 |
-
"derive": {
|
| 175 |
-
"scalar": "dtypes.T",
|
| 176 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 177 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 178 |
-
"workgroupSize": "workgroupSize",
|
| 179 |
-
"split": "rowSplitCount"
|
| 180 |
-
},
|
| 181 |
"intermediates": [
|
| 182 |
{ "id": "partials_val", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" },
|
| 183 |
{ "id": "partials_idx", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" }
|
|
@@ -187,17 +115,15 @@
|
|
| 187 |
"id": "split_reduce",
|
| 188 |
"name": "ArgMax.LastAxisSplitReduce",
|
| 189 |
"shader": "reduce-arg-row-split.wgsl.jinja",
|
| 190 |
-
"
|
| 191 |
-
"
|
| 192 |
-
"bindings": ["x_2", "partials_val", "partials_idx", "params_3"],
|
| 193 |
"dispatch": { "x": "outputCount", "y": "rowSplitCount" }
|
| 194 |
},
|
| 195 |
{
|
| 196 |
"id": "combine",
|
| 197 |
"name": "ArgMax.LastAxisSplitCombine",
|
| 198 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 199 |
-
"
|
| 200 |
-
"bindings": ["partials_val_2", "partials_idx_2", "y", "params_2"],
|
| 201 |
"dispatch": {
|
| 202 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 203 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
@@ -212,7 +138,6 @@
|
|
| 212 |
"when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.SPLIT_MIN_AXIS", "outputCount >= tunables.SPLIT_TILE_COLUMNS", "outputCount <= 2 * tunables.SPLIT_TILE_COLUMNS", "workgroupSize % tunables.SPLIT_TILE_COLUMNS == 0", "splitScratchFits", "splitTileDispatchFits"],
|
| 213 |
"derive": {
|
| 214 |
"scalar": "dtypes.T",
|
| 215 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 216 |
"workgroupSize": "workgroupSize",
|
| 217 |
"split": "splitCount",
|
| 218 |
"tileCols": "tunables.SPLIT_TILE_COLUMNS"
|
|
@@ -226,16 +151,14 @@
|
|
| 226 |
"id": "split_reduce",
|
| 227 |
"name": "ArgMax.AxisSplitTiledReduce",
|
| 228 |
"shader": "reduce-arg-axis-split-tiled.wgsl.jinja",
|
| 229 |
-
"
|
| 230 |
-
"bindings": ["x_2", "partials_val", "partials_idx", "params_4"],
|
| 231 |
"dispatch": { "x": "ceilDiv(outputCount, tunables.SPLIT_TILE_COLUMNS)", "y": "splitCount" }
|
| 232 |
},
|
| 233 |
{
|
| 234 |
"id": "combine",
|
| 235 |
"name": "ArgMax.AxisSplitCombine",
|
| 236 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 237 |
-
"
|
| 238 |
-
"bindings": ["partials_val_2", "partials_idx_2", "y", "params_2"],
|
| 239 |
"dispatch": {
|
| 240 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 241 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
@@ -252,8 +175,6 @@
|
|
| 252 |
"derive": {
|
| 253 |
"scalar": "dtypes.T",
|
| 254 |
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
| 255 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 256 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 257 |
"workgroupSize": "device.adapterInfo.subgroupMinSize"
|
| 258 |
},
|
| 259 |
"passes": [
|
|
@@ -261,9 +182,8 @@
|
|
| 261 |
"id": "main",
|
| 262 |
"name": "ArgMax.SubgroupMinLastAxisVec4",
|
| 263 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 264 |
-
"
|
| 265 |
-
"
|
| 266 |
-
"bindings": ["x", "y", "params_5"],
|
| 267 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 268 |
}
|
| 269 |
]
|
|
@@ -273,20 +193,14 @@
|
|
| 273 |
"priority": 41,
|
| 274 |
"when": ["baseContract", "rowParallelType", "lastAxis", "axisDim % 4 != 0", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize >= 4", "device.adapterInfo.subgroupMinSize <= device.adapterInfo.subgroupMaxSize", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeInvocationsPerWorkgroup", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeWorkgroupSizeX", "axisDim >= device.adapterInfo.subgroupMinSize * 4", "axisDim <= device.adapterInfo.subgroupMinSize * 64", "outputCount >= device.adapterInfo.subgroupMinSize * 4", "rowDispatchFits"],
|
| 275 |
"requires": { "features": ["subgroups"] },
|
| 276 |
-
"derive": {
|
| 277 |
-
"scalar": "dtypes.T",
|
| 278 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 279 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 280 |
-
"workgroupSize": "device.adapterInfo.subgroupMinSize"
|
| 281 |
-
},
|
| 282 |
"passes": [
|
| 283 |
{
|
| 284 |
"id": "main",
|
| 285 |
"name": "ArgMax.SubgroupMinLastAxis",
|
| 286 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 287 |
-
"
|
| 288 |
-
"
|
| 289 |
-
"bindings": ["x_2", "y", "params_6"],
|
| 290 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 291 |
}
|
| 292 |
]
|
|
@@ -298,8 +212,6 @@
|
|
| 298 |
"derive": {
|
| 299 |
"scalar": "dtypes.T",
|
| 300 |
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
| 301 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 302 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 303 |
"workgroupSize": "min(workgroupSize, max(32, pow2ceil(ceilDiv(axisDim, 4))))"
|
| 304 |
},
|
| 305 |
"passes": [
|
|
@@ -307,9 +219,8 @@
|
|
| 307 |
"id": "main",
|
| 308 |
"name": "ArgMax.LastAxisVec4",
|
| 309 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 310 |
-
"
|
| 311 |
-
"
|
| 312 |
-
"bindings": ["x", "y", "params_5"],
|
| 313 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 314 |
}
|
| 315 |
]
|
|
@@ -318,46 +229,79 @@
|
|
| 318 |
"id": "last_axis",
|
| 319 |
"priority": 39,
|
| 320 |
"when": ["baseContract", "rowParallelType", "lastAxis", "axisDim % 4 != 0", "parallelRowWorthwhile", "rowDispatchFits", "rowStorageFits"],
|
| 321 |
-
"derive": {
|
| 322 |
-
"scalar": "dtypes.T",
|
| 323 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 324 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 325 |
-
"workgroupSize": "min(workgroupSize, max(32, pow2ceil(axisDim)))"
|
| 326 |
-
},
|
| 327 |
"passes": [
|
| 328 |
{
|
| 329 |
"id": "main",
|
| 330 |
"name": "ArgMax.LastAxis",
|
| 331 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 332 |
-
"
|
| 333 |
-
|
| 334 |
-
|
|
|
|
|
|
|
|
|
|
| 335 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 336 |
}
|
| 337 |
]
|
| 338 |
},
|
| 339 |
{
|
| 340 |
-
"id": "
|
| 341 |
-
"priority":
|
| 342 |
-
"when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.SPLIT_MIN_AXIS", "outputCount >= 1", "outputCount <= tunables.SPLIT_MAX_OUTPUTS", "splitScratchFits", "splitDispatchFits", "threadDispatchFits"],
|
| 343 |
-
"demoteWhen": ["narrowLargeAxisTilePreferred"],
|
| 344 |
"derive": {
|
| 345 |
"scalar": "dtypes.T",
|
| 346 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 347 |
"workgroupSize": "workgroupSize",
|
| 348 |
-
"split": "splitCount"
|
|
|
|
| 349 |
},
|
| 350 |
"intermediates": [
|
| 351 |
{ "id": "partials_val", "dtype": "uint32", "shape": "[splitCount * outputCount]" },
|
| 352 |
{ "id": "partials_idx", "dtype": "uint32", "shape": "[splitCount * outputCount]" }
|
| 353 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
"passes": [
|
| 355 |
{
|
| 356 |
"id": "split_reduce",
|
| 357 |
"name": "ArgMax.AxisSplitReduce",
|
| 358 |
"shader": "reduce-arg-axis-split-reduce.wgsl.jinja",
|
| 359 |
-
"
|
| 360 |
-
"bindings": ["x_2", "partials_val", "partials_idx", "params_4"],
|
| 361 |
"dispatch": {
|
| 362 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), DISPATCH_FOLD_WIDTH)",
|
| 363 |
"y": "splitCount",
|
|
@@ -368,8 +312,7 @@
|
|
| 368 |
"id": "combine",
|
| 369 |
"name": "ArgMax.AxisSplitCombine",
|
| 370 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 371 |
-
"
|
| 372 |
-
"bindings": ["partials_val_2", "partials_idx_2", "y", "params_2"],
|
| 373 |
"dispatch": {
|
| 374 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 375 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
@@ -382,19 +325,13 @@
|
|
| 382 |
"id": "axis_tiled",
|
| 383 |
"priority": 20,
|
| 384 |
"when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.TILED_MIN_AXIS", "outputCount >= tunables.TILED_MIN_OUTPUTS", "workgroupSize % tunables.TILE_COLUMNS == 0", "rowStorageFits", "tileDispatchFits"],
|
| 385 |
-
"derive": {
|
| 386 |
-
"scalar": "dtypes.T",
|
| 387 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 388 |
-
"workgroupSize": "workgroupSize",
|
| 389 |
-
"tileCols": "tunables.TILE_COLUMNS"
|
| 390 |
-
},
|
| 391 |
"passes": [
|
| 392 |
{
|
| 393 |
"id": "main",
|
| 394 |
"name": "ArgMax.AxisTiled",
|
| 395 |
"shader": "reduce-arg-axis-tiled.wgsl.jinja",
|
| 396 |
-
"
|
| 397 |
-
"bindings": ["x_2", "y", "params_4"],
|
| 398 |
"dispatch": {
|
| 399 |
"x": "min(ceilDiv((outputCount), (tunables.TILE_COLUMNS)), 65535)",
|
| 400 |
"y": "ceilDiv(ceilDiv((outputCount), (tunables.TILE_COLUMNS)), 65535)",
|
|
@@ -408,19 +345,13 @@
|
|
| 408 |
"priority": 0,
|
| 409 |
"when": ["baseContract", "threadDispatchFits"],
|
| 410 |
"supersededBy": ["axis_tiled"],
|
| 411 |
-
"derive": {
|
| 412 |
-
"scalar": "dtypes.T",
|
| 413 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 414 |
-
"selectLastIndex": "attrs.select_last_index != 0",
|
| 415 |
-
"workgroupSize": "workgroupSize"
|
| 416 |
-
},
|
| 417 |
"passes": [
|
| 418 |
{
|
| 419 |
"id": "main",
|
| 420 |
"name": "ArgMax.AxisSerial",
|
| 421 |
"shader": "reduce-arg-axis.wgsl.jinja",
|
| 422 |
-
"
|
| 423 |
-
"bindings": ["x_2", "y", "params_4"],
|
| 424 |
"dispatch": {
|
| 425 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 426 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
@@ -429,5 +360,60 @@
|
|
| 429 |
}
|
| 430 |
]
|
| 431 |
}
|
| 432 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
}
|
|
|
|
| 60 |
"rowSplitScratchBytes": "rowSplitCount * outputCount * 8",
|
| 61 |
"rowSplitScratchFits": "rowSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and rowSplitScratchBytes <= device.limits.maxBufferSize",
|
| 62 |
"rowSplitDispatchFits": "outputCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and rowSplitCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
|
| 63 |
+
"tileDispatchFits": "ceilDiv(outputCount, tunables.TILE_COLUMNS) <= foldedDispatchCapacity",
|
| 64 |
+
"selectLastIndex": "attrs.select_last_index != 0",
|
| 65 |
+
"modeSpec": "\"max\""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
},
|
| 67 |
"variants": [
|
| 68 |
{
|
|
|
|
| 72 |
"derive": {
|
| 73 |
"scalar": "dtypes.T",
|
| 74 |
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
|
|
|
|
|
|
| 75 |
"workgroupSize": "workgroupSize",
|
| 76 |
"split": "rowSplitCount"
|
| 77 |
},
|
|
|
|
| 84 |
"id": "split_reduce",
|
| 85 |
"name": "ArgMax.LastAxisSplitVec4Reduce",
|
| 86 |
"shader": "reduce-arg-row-split.wgsl.jinja",
|
| 87 |
+
"derive": { "vec4": true, "useSubgroups": "device.features.has(\"subgroups\")" },
|
|
|
|
| 88 |
"bindings": ["x", "partials_val", "partials_idx", "params"],
|
| 89 |
"dispatch": { "x": "outputCount", "y": "rowSplitCount" }
|
| 90 |
},
|
|
|
|
| 92 |
"id": "combine",
|
| 93 |
"name": "ArgMax.LastAxisSplitCombine",
|
| 94 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 95 |
+
"bindings": ["partials_val_u32", "partials_idx_u32", "y", "params_combine"],
|
|
|
|
| 96 |
"dispatch": {
|
| 97 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 98 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
|
|
| 105 |
"id": "last_axis_split",
|
| 106 |
"priority": 41,
|
| 107 |
"when": ["baseContract", "rowParallelType", "lastAxis", "axisDim >= tunables.ROW_SPLIT_MIN_AXIS", "axisDim % 4 != 0", "outputCount <= tunables.ROW_SPLIT_MAX_OUTPUTS", "rowSplitScratchFits", "rowSplitDispatchFits", "rowStorageFits"],
|
| 108 |
+
"derive": { "scalar": "dtypes.T", "workgroupSize": "workgroupSize", "split": "rowSplitCount" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
"intermediates": [
|
| 110 |
{ "id": "partials_val", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" },
|
| 111 |
{ "id": "partials_idx", "dtype": "uint32", "shape": "[rowSplitCount * outputCount]" }
|
|
|
|
| 115 |
"id": "split_reduce",
|
| 116 |
"name": "ArgMax.LastAxisSplitReduce",
|
| 117 |
"shader": "reduce-arg-row-split.wgsl.jinja",
|
| 118 |
+
"derive": { "vec4": false, "useSubgroups": "device.features.has(\"subgroups\")" },
|
| 119 |
+
"bindings": ["x_main", "partials_val", "partials_idx", "params__uniform"],
|
|
|
|
| 120 |
"dispatch": { "x": "outputCount", "y": "rowSplitCount" }
|
| 121 |
},
|
| 122 |
{
|
| 123 |
"id": "combine",
|
| 124 |
"name": "ArgMax.LastAxisSplitCombine",
|
| 125 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 126 |
+
"bindings": ["partials_val_u32", "partials_idx_u32", "y", "params_combine"],
|
|
|
|
| 127 |
"dispatch": {
|
| 128 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 129 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
|
|
| 138 |
"when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.SPLIT_MIN_AXIS", "outputCount >= tunables.SPLIT_TILE_COLUMNS", "outputCount <= 2 * tunables.SPLIT_TILE_COLUMNS", "workgroupSize % tunables.SPLIT_TILE_COLUMNS == 0", "splitScratchFits", "splitTileDispatchFits"],
|
| 139 |
"derive": {
|
| 140 |
"scalar": "dtypes.T",
|
|
|
|
| 141 |
"workgroupSize": "workgroupSize",
|
| 142 |
"split": "splitCount",
|
| 143 |
"tileCols": "tunables.SPLIT_TILE_COLUMNS"
|
|
|
|
| 151 |
"id": "split_reduce",
|
| 152 |
"name": "ArgMax.AxisSplitTiledReduce",
|
| 153 |
"shader": "reduce-arg-axis-split-tiled.wgsl.jinja",
|
| 154 |
+
"bindings": ["x_main", "partials_val", "partials_idx", "params_main"],
|
|
|
|
| 155 |
"dispatch": { "x": "ceilDiv(outputCount, tunables.SPLIT_TILE_COLUMNS)", "y": "splitCount" }
|
| 156 |
},
|
| 157 |
{
|
| 158 |
"id": "combine",
|
| 159 |
"name": "ArgMax.AxisSplitCombine",
|
| 160 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 161 |
+
"bindings": ["partials_val_u32", "partials_idx_u32", "y", "params_combine"],
|
|
|
|
| 162 |
"dispatch": {
|
| 163 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 164 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
|
|
| 175 |
"derive": {
|
| 176 |
"scalar": "dtypes.T",
|
| 177 |
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
|
|
|
|
|
|
| 178 |
"workgroupSize": "device.adapterInfo.subgroupMinSize"
|
| 179 |
},
|
| 180 |
"passes": [
|
|
|
|
| 182 |
"id": "main",
|
| 183 |
"name": "ArgMax.SubgroupMinLastAxisVec4",
|
| 184 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 185 |
+
"derive": { "vec4": true, "singleSubgroup": true },
|
| 186 |
+
"bindings": ["x", "y", "params_last_axis_vec4"],
|
|
|
|
| 187 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 188 |
}
|
| 189 |
]
|
|
|
|
| 193 |
"priority": 41,
|
| 194 |
"when": ["baseContract", "rowParallelType", "lastAxis", "axisDim % 4 != 0", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize >= 4", "device.adapterInfo.subgroupMinSize <= device.adapterInfo.subgroupMaxSize", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeInvocationsPerWorkgroup", "device.adapterInfo.subgroupMinSize <= device.limits.maxComputeWorkgroupSizeX", "axisDim >= device.adapterInfo.subgroupMinSize * 4", "axisDim <= device.adapterInfo.subgroupMinSize * 64", "outputCount >= device.adapterInfo.subgroupMinSize * 4", "rowDispatchFits"],
|
| 195 |
"requires": { "features": ["subgroups"] },
|
| 196 |
+
"derive": { "scalar": "dtypes.T", "workgroupSize": "device.adapterInfo.subgroupMinSize" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
"passes": [
|
| 198 |
{
|
| 199 |
"id": "main",
|
| 200 |
"name": "ArgMax.SubgroupMinLastAxis",
|
| 201 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 202 |
+
"derive": { "vec4": false, "singleSubgroup": true },
|
| 203 |
+
"bindings": ["x_main", "y", "params_last_axis"],
|
|
|
|
| 204 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 205 |
}
|
| 206 |
]
|
|
|
|
| 212 |
"derive": {
|
| 213 |
"scalar": "dtypes.T",
|
| 214 |
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
|
|
|
|
|
|
| 215 |
"workgroupSize": "min(workgroupSize, max(32, pow2ceil(ceilDiv(axisDim, 4))))"
|
| 216 |
},
|
| 217 |
"passes": [
|
|
|
|
| 219 |
"id": "main",
|
| 220 |
"name": "ArgMax.LastAxisVec4",
|
| 221 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 222 |
+
"derive": { "vec4": true, "useSubgroups": "device.features.has(\"subgroups\")" },
|
| 223 |
+
"bindings": ["x", "y", "params_last_axis_vec4"],
|
|
|
|
| 224 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 225 |
}
|
| 226 |
]
|
|
|
|
| 229 |
"id": "last_axis",
|
| 230 |
"priority": 39,
|
| 231 |
"when": ["baseContract", "rowParallelType", "lastAxis", "axisDim % 4 != 0", "parallelRowWorthwhile", "rowDispatchFits", "rowStorageFits"],
|
| 232 |
+
"derive": { "scalar": "dtypes.T", "workgroupSize": "min(workgroupSize, max(32, pow2ceil(axisDim)))" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
"passes": [
|
| 234 |
{
|
| 235 |
"id": "main",
|
| 236 |
"name": "ArgMax.LastAxis",
|
| 237 |
"shader": "reduce-arg-row-subgroup.wgsl.jinja",
|
| 238 |
+
"derive": {
|
| 239 |
+
"vec4": false,
|
| 240 |
+
"useSubgroups": "device.features.has(\"subgroups\")",
|
| 241 |
+
"adjacentScalarLoads": "false"
|
| 242 |
+
},
|
| 243 |
+
"bindings": ["x_main", "y", "params_last_axis"],
|
| 244 |
"dispatch": { "x": "min(outputCount, 65535)", "y": "ceilDiv(outputCount, 65535)", "z": 1 }
|
| 245 |
}
|
| 246 |
]
|
| 247 |
},
|
| 248 |
{
|
| 249 |
+
"id": "axis_split_packed",
|
| 250 |
+
"priority": 32,
|
| 251 |
+
"when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.SPLIT_MIN_AXIS", "outputCount >= 1", "outputCount <= tunables.SPLIT_MAX_OUTPUTS", "splitScratchFits", "splitDispatchFits", "threadDispatchFits", "dtypes.T == \"f32\"", "axisInner % 4 == 0"],
|
| 252 |
+
"demoteWhen": ["narrowLargeAxisTilePreferred", "outputCount < workgroupSize * 2"],
|
| 253 |
"derive": {
|
| 254 |
"scalar": "dtypes.T",
|
|
|
|
| 255 |
"workgroupSize": "workgroupSize",
|
| 256 |
+
"split": "splitCount",
|
| 257 |
+
"vectorScalar": "\"vec4<f32>\""
|
| 258 |
},
|
| 259 |
"intermediates": [
|
| 260 |
{ "id": "partials_val", "dtype": "uint32", "shape": "[splitCount * outputCount]" },
|
| 261 |
{ "id": "partials_idx", "dtype": "uint32", "shape": "[splitCount * outputCount]" }
|
| 262 |
],
|
| 263 |
+
"passes": [
|
| 264 |
+
{
|
| 265 |
+
"id": "split_reduce",
|
| 266 |
+
"name": "ArgMax.AxisSplitPackedReduce",
|
| 267 |
+
"shader": "reduce-arg-axis-split-reduce.wgsl.jinja",
|
| 268 |
+
"derive": { "columnVectorWidth": 4 },
|
| 269 |
+
"bindings": ["x", "partials_val", "partials_idx", "params_main"],
|
| 270 |
+
"dispatch": {
|
| 271 |
+
"x": "min(ceilDiv(outputCount, workgroupSize * 4), DISPATCH_FOLD_WIDTH)",
|
| 272 |
+
"y": "splitCount",
|
| 273 |
+
"z": "ceilDiv(ceilDiv(outputCount, workgroupSize * 4), DISPATCH_FOLD_WIDTH)"
|
| 274 |
+
}
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"id": "combine",
|
| 278 |
+
"name": "ArgMax.AxisSplitCombine",
|
| 279 |
+
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 280 |
+
"bindings": ["partials_val_u32", "partials_idx_u32", "y", "params_combine"],
|
| 281 |
+
"dispatch": {
|
| 282 |
+
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 283 |
+
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 284 |
+
"z": 1
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
]
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"id": "axis_split",
|
| 291 |
+
"priority": 30,
|
| 292 |
+
"when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.SPLIT_MIN_AXIS", "outputCount >= 1", "outputCount <= tunables.SPLIT_MAX_OUTPUTS", "splitScratchFits", "splitDispatchFits", "threadDispatchFits"],
|
| 293 |
+
"demoteWhen": ["narrowLargeAxisTilePreferred"],
|
| 294 |
+
"derive": { "scalar": "dtypes.T", "workgroupSize": "workgroupSize", "split": "splitCount" },
|
| 295 |
+
"intermediates": [
|
| 296 |
+
{ "id": "partials_val", "dtype": "uint32", "shape": "[splitCount * outputCount]" },
|
| 297 |
+
{ "id": "partials_idx", "dtype": "uint32", "shape": "[splitCount * outputCount]" }
|
| 298 |
+
],
|
| 299 |
"passes": [
|
| 300 |
{
|
| 301 |
"id": "split_reduce",
|
| 302 |
"name": "ArgMax.AxisSplitReduce",
|
| 303 |
"shader": "reduce-arg-axis-split-reduce.wgsl.jinja",
|
| 304 |
+
"bindings": ["x_main", "partials_val", "partials_idx", "params_main"],
|
|
|
|
| 305 |
"dispatch": {
|
| 306 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), DISPATCH_FOLD_WIDTH)",
|
| 307 |
"y": "splitCount",
|
|
|
|
| 312 |
"id": "combine",
|
| 313 |
"name": "ArgMax.AxisSplitCombine",
|
| 314 |
"shader": "reduce-arg-axis-split-combine.wgsl.jinja",
|
| 315 |
+
"bindings": ["partials_val_u32", "partials_idx_u32", "y", "params_combine"],
|
|
|
|
| 316 |
"dispatch": {
|
| 317 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 318 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
|
|
| 325 |
"id": "axis_tiled",
|
| 326 |
"priority": 20,
|
| 327 |
"when": ["baseContract", "nativeParallelType", "not lastAxis", "axisDim >= tunables.TILED_MIN_AXIS", "outputCount >= tunables.TILED_MIN_OUTPUTS", "workgroupSize % tunables.TILE_COLUMNS == 0", "rowStorageFits", "tileDispatchFits"],
|
| 328 |
+
"derive": { "scalar": "dtypes.T", "workgroupSize": "workgroupSize", "tileCols": "tunables.TILE_COLUMNS" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
"passes": [
|
| 330 |
{
|
| 331 |
"id": "main",
|
| 332 |
"name": "ArgMax.AxisTiled",
|
| 333 |
"shader": "reduce-arg-axis-tiled.wgsl.jinja",
|
| 334 |
+
"bindings": ["x_main", "y", "params_main"],
|
|
|
|
| 335 |
"dispatch": {
|
| 336 |
"x": "min(ceilDiv((outputCount), (tunables.TILE_COLUMNS)), 65535)",
|
| 337 |
"y": "ceilDiv(ceilDiv((outputCount), (tunables.TILE_COLUMNS)), 65535)",
|
|
|
|
| 345 |
"priority": 0,
|
| 346 |
"when": ["baseContract", "threadDispatchFits"],
|
| 347 |
"supersededBy": ["axis_tiled"],
|
| 348 |
+
"derive": { "scalar": "dtypes.T", "workgroupSize": "workgroupSize" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
"passes": [
|
| 350 |
{
|
| 351 |
"id": "main",
|
| 352 |
"name": "ArgMax.AxisSerial",
|
| 353 |
"shader": "reduce-arg-axis.wgsl.jinja",
|
| 354 |
+
"bindings": ["x_main", "y", "params_main"],
|
|
|
|
| 355 |
"dispatch": {
|
| 356 |
"x": "min(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
| 357 |
"y": "ceilDiv(ceilDiv((outputCount), (workgroupSize)), 65535)",
|
|
|
|
| 360 |
}
|
| 361 |
]
|
| 362 |
}
|
| 363 |
+
],
|
| 364 |
+
"bindings": {
|
| 365 |
+
"x": { "elementType": "$vectorScalar" },
|
| 366 |
+
"partials_val": { "buffer": "storage", "elementType": "u32" },
|
| 367 |
+
"partials_idx": { "buffer": "storage", "elementType": "u32" },
|
| 368 |
+
"params": {
|
| 369 |
+
"struct": [
|
| 370 |
+
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 371 |
+
{ "name": "chunkCount", "type": "u32", "value": "axisDim / 4" }
|
| 372 |
+
]
|
| 373 |
+
},
|
| 374 |
+
"partials_val_u32": { "name": "partials_val", "buffer": "read-only-storage", "elementType": "u32" },
|
| 375 |
+
"partials_idx_u32": { "name": "partials_idx", "buffer": "read-only-storage", "elementType": "u32" },
|
| 376 |
+
"y": { "elementType": "u32" },
|
| 377 |
+
"params_combine": { "name": "params", "struct": [{ "name": "outputCount", "type": "u32", "value": "outputCount" }] },
|
| 378 |
+
"x_main": { "name": "x", "elementType": "$scalar" },
|
| 379 |
+
"params__uniform": {
|
| 380 |
+
"name": "params",
|
| 381 |
+
"struct": [
|
| 382 |
+
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 383 |
+
{ "name": "chunkCount", "type": "u32", "value": "axisDim" }
|
| 384 |
+
]
|
| 385 |
+
},
|
| 386 |
+
"params_main": {
|
| 387 |
+
"name": "params",
|
| 388 |
+
"struct": [
|
| 389 |
+
{ "name": "axisDim", "type": "u32", "value": "axisDim" },
|
| 390 |
+
{ "name": "outputCount", "type": "u32", "value": "outputCount" },
|
| 391 |
+
{ "name": "innerSize", "type": "u32", "value": "axisInner" }
|
| 392 |
+
]
|
| 393 |
+
},
|
| 394 |
+
"params_last_axis_vec4": {
|
| 395 |
+
"name": "params",
|
| 396 |
+
"struct": [
|
| 397 |
+
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 398 |
+
{ "name": "chunkCount", "type": "u32", "value": "axisDim / 4" },
|
| 399 |
+
{
|
| 400 |
+
"name": "rowStride",
|
| 401 |
+
"type": "u32",
|
| 402 |
+
"value": "max(1, min(outputCount, min(device.limits.maxComputeWorkgroupsPerDimension, 65535)))"
|
| 403 |
+
}
|
| 404 |
+
]
|
| 405 |
+
},
|
| 406 |
+
"params_last_axis": {
|
| 407 |
+
"name": "params",
|
| 408 |
+
"struct": [
|
| 409 |
+
{ "name": "rows", "type": "u32", "value": "outputCount" },
|
| 410 |
+
{ "name": "chunkCount", "type": "u32", "value": "axisDim" },
|
| 411 |
+
{
|
| 412 |
+
"name": "rowStride",
|
| 413 |
+
"type": "u32",
|
| 414 |
+
"value": "max(1, min(outputCount, min(device.limits.maxComputeWorkgroupsPerDimension, 65535)))"
|
| 415 |
+
}
|
| 416 |
+
]
|
| 417 |
+
}
|
| 418 |
+
}
|
| 419 |
}
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.ArgMax",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"bench.json": "
|
| 11 |
-
"manifest.json": "
|
| 12 |
-
"reduce-arg-axis-split-combine.wgsl.jinja": "
|
| 13 |
-
"reduce-arg-axis-split-reduce.wgsl.jinja": "
|
| 14 |
-
"reduce-arg-axis-split-tiled.wgsl.jinja": "
|
| 15 |
-
"reduce-arg-axis-tiled.wgsl.jinja": "
|
| 16 |
-
"reduce-arg-axis.wgsl.jinja": "
|
| 17 |
-
"reduce-arg-row-split.wgsl.jinja": "
|
| 18 |
-
"reduce-arg-row-subgroup.wgsl.jinja": "
|
| 19 |
-
"test.json": "
|
| 20 |
}
|
| 21 |
},
|
| 22 |
-
"provenance": { "kernel": { "sha": "
|
| 23 |
"webgpu": {
|
| 24 |
-
"manifestSpec": "2.
|
| 25 |
"variants": {
|
| 26 |
"last_axis_split_vec4": ["reduce-arg-axis-split-combine.wgsl.jinja", "reduce-arg-row-split.wgsl.jinja"],
|
| 27 |
"last_axis_split": ["reduce-arg-axis-split-combine.wgsl.jinja", "reduce-arg-row-split.wgsl.jinja"],
|
|
@@ -30,6 +30,7 @@
|
|
| 30 |
"subgroup_min_last_axis": ["reduce-arg-row-subgroup.wgsl.jinja"],
|
| 31 |
"last_axis_vec4": ["reduce-arg-row-subgroup.wgsl.jinja"],
|
| 32 |
"last_axis": ["reduce-arg-row-subgroup.wgsl.jinja"],
|
|
|
|
| 33 |
"axis_split": ["reduce-arg-axis-split-combine.wgsl.jinja", "reduce-arg-axis-split-reduce.wgsl.jinja"],
|
| 34 |
"axis_tiled": ["reduce-arg-axis-tiled.wgsl.jinja"],
|
| 35 |
"axis_serial": ["reduce-arg-axis.wgsl.jinja"]
|
|
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.ArgMax",
|
| 3 |
+
"id": "_ai_onnx_argmax_webgpu_1e3a265",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "iuzwJJYAdNsUw+DglCvHX2WAtQEjRzfBOZt6MvlMa1U=",
|
| 11 |
+
"manifest.json": "TiCizYs12kW8ZsFx/7wB+8OysF5fLvrk+gdZ0EoFqNk=",
|
| 12 |
+
"reduce-arg-axis-split-combine.wgsl.jinja": "Zgdr9BmWms05/uDEY+YMSJE5jj6soIVfmxLsIfzt6M4=",
|
| 13 |
+
"reduce-arg-axis-split-reduce.wgsl.jinja": "iYeNFJ7YX9FMCNdlFlhUIW21KpKmU4ouODT2u9MjjjE=",
|
| 14 |
+
"reduce-arg-axis-split-tiled.wgsl.jinja": "MvEKY6B3FsRB7SfFPVWK7rWWCUsdhTSYOUmbQxtzP2w=",
|
| 15 |
+
"reduce-arg-axis-tiled.wgsl.jinja": "sWYpTFWzc2ATUo2VGrKw+zLAyWFVD6O9og+WpY9f28g=",
|
| 16 |
+
"reduce-arg-axis.wgsl.jinja": "9wjP1QeFI3ydGt9T537YVC3O8tPYSK4qSvw+urw1ruY=",
|
| 17 |
+
"reduce-arg-row-split.wgsl.jinja": "yfXWnH5jaZHa5PW28zyTSF0O7A1yGKOIJUCZdZliFS0=",
|
| 18 |
+
"reduce-arg-row-subgroup.wgsl.jinja": "A1sX+NMUilPMxLDzHhdKFlwdHIDgFTe9a6igb7pOX7k=",
|
| 19 |
+
"test.json": "6RGFz3E0tdm0K3gRicwYZ3hU54MqthGV/tnBeAxFxHs="
|
| 20 |
}
|
| 21 |
},
|
| 22 |
+
"provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
|
| 23 |
"webgpu": {
|
| 24 |
+
"manifestSpec": "2.1",
|
| 25 |
"variants": {
|
| 26 |
"last_axis_split_vec4": ["reduce-arg-axis-split-combine.wgsl.jinja", "reduce-arg-row-split.wgsl.jinja"],
|
| 27 |
"last_axis_split": ["reduce-arg-axis-split-combine.wgsl.jinja", "reduce-arg-row-split.wgsl.jinja"],
|
|
|
|
| 30 |
"subgroup_min_last_axis": ["reduce-arg-row-subgroup.wgsl.jinja"],
|
| 31 |
"last_axis_vec4": ["reduce-arg-row-subgroup.wgsl.jinja"],
|
| 32 |
"last_axis": ["reduce-arg-row-subgroup.wgsl.jinja"],
|
| 33 |
+
"axis_split_packed": ["reduce-arg-axis-split-combine.wgsl.jinja", "reduce-arg-axis-split-reduce.wgsl.jinja"],
|
| 34 |
"axis_split": ["reduce-arg-axis-split-combine.wgsl.jinja", "reduce-arg-axis-split-reduce.wgsl.jinja"],
|
| 35 |
"axis_tiled": ["reduce-arg-axis-tiled.wgsl.jinja"],
|
| 36 |
"axis_serial": ["reduce-arg-axis.wgsl.jinja"]
|
build/webgpu/reduce-arg-axis-split-combine.wgsl.jinja
CHANGED
|
@@ -7,9 +7,17 @@
|
|
| 7 |
// The fold compares (value, index), never segment order. Equal values keep the
|
| 8 |
// smaller index for first-index mode and the larger index for last-index mode.
|
| 9 |
// Empty segments carry SENTINEL_IDX and are skipped.
|
| 10 |
-
{% set cmp = ">" if modeSpec == "max" else "<" %}
|
| 11 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 12 |
{% set work = scalar if is_int else "f32" %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
|
| 15 |
const WG: u32 = {{ workgroupSize }}u;
|
|
@@ -18,28 +26,14 @@ const SENTINEL_IDX: u32 = 4294967295u;
|
|
| 18 |
|
| 19 |
@compute @workgroup_size(WG, 1, 1)
|
| 20 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
return;
|
| 24 |
-
}
|
| 25 |
-
{% if scalar == "u32" %}
|
| 26 |
-
var out_val = partials_val[output_index];
|
| 27 |
-
{% else %}
|
| 28 |
-
var out_val = bitcast<{{ work }}>(partials_val[output_index]);
|
| 29 |
-
{% endif %}
|
| 30 |
var out_idx = partials_idx[output_index];
|
| 31 |
for (var split_index = 1u; split_index < SPLIT; split_index++) {
|
| 32 |
-
{%
|
| 33 |
-
let candidate_val =
|
| 34 |
-
{% else %}
|
| 35 |
-
let candidate_val = bitcast<{{ work }}>(partials_val[split_index * params.outputCount + output_index]);
|
| 36 |
-
{% endif %}
|
| 37 |
let candidate_idx = partials_idx[split_index * params.outputCount + output_index];
|
| 38 |
-
{
|
| 39 |
-
if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx > out_idx))) {
|
| 40 |
-
{% else %}
|
| 41 |
-
if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx < out_idx))) {
|
| 42 |
-
{% endif %}
|
| 43 |
out_val = candidate_val;
|
| 44 |
out_idx = candidate_idx;
|
| 45 |
}
|
|
|
|
| 7 |
// The fold compares (value, index), never segment order. Equal values keep the
|
| 8 |
// smaller index for first-index mode and the larger index for last-index mode.
|
| 9 |
// Empty segments carry SENTINEL_IDX and are skipped.
|
|
|
|
| 10 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 11 |
{% set work = scalar if is_int else "f32" %}
|
| 12 |
+
{% macro arg_merge_wins(v, i, best, bestIdx) %}{{ i }} != SENTINEL_IDX && ({{ bestIdx }} == SENTINEL_IDX || {{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ i }} {{ ">" if selectLastIndex else "<" }} {{ bestIdx }})){% endmacro %}
|
| 13 |
+
{% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
|
| 14 |
+
{% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
|
| 15 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 16 |
+
// per-axis workgroup fold width.
|
| 17 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
|
| 18 |
+
if ({{ name }} >= {{ bound }}) {
|
| 19 |
+
return;
|
| 20 |
+
}{% endmacro %}
|
| 21 |
{{ env.wgsl.resourceDeclarations }}
|
| 22 |
|
| 23 |
const WG: u32 = {{ workgroupSize }}u;
|
|
|
|
| 26 |
|
| 27 |
@compute @workgroup_size(WG, 1, 1)
|
| 28 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 29 |
+
{{ flat_index_2d("WG", "output_index", "params.outputCount") }}
|
| 30 |
+
var out_val = {{ "partials_val[output_index]" if scalar == "u32" else "bitcast<" ~ work ~ ">(partials_val[output_index])" }};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
var out_idx = partials_idx[output_index];
|
| 32 |
for (var split_index = 1u; split_index < SPLIT; split_index++) {
|
| 33 |
+
{% set candidate = "partials_val[split_index * params.outputCount + output_index]" %}
|
| 34 |
+
let candidate_val = {{ candidate if scalar == "u32" else "bitcast<" ~ work ~ ">(" ~ candidate ~ ")" }};
|
|
|
|
|
|
|
|
|
|
| 35 |
let candidate_idx = partials_idx[split_index * params.outputCount + output_index];
|
| 36 |
+
if ({{ arg_merge_wins("candidate_val", "candidate_idx", "out_val", "out_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
out_val = candidate_val;
|
| 38 |
out_idx = candidate_idx;
|
| 39 |
}
|
build/webgpu/reduce-arg-axis-split-reduce.wgsl.jinja
CHANGED
|
@@ -6,9 +6,10 @@
|
|
| 6 |
// layout without losing integer precision. The combine pass restores the native
|
| 7 |
// comparison type. Equal values keep the lowest index by default or the highest
|
| 8 |
// index when select_last_index is enabled.
|
| 9 |
-
{% set cmp = ">" if modeSpec == "max" else "<" %}
|
| 10 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 11 |
{% set work = scalar if is_int else "f32" %}
|
|
|
|
|
|
|
| 12 |
{{ env.wgsl.resourceDeclarations }}
|
| 13 |
|
| 14 |
const WG: u32 = {{ workgroupSize }}u;
|
|
@@ -34,35 +35,48 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 34 |
@builtin(workgroup_id) wg: vec3<u32>) {
|
| 35 |
// 2D-folded column index: wg.z carries the high bits past the per-axis dispatch
|
| 36 |
// fold width on the x dimension.
|
| 37 |
-
let output_index = (wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u) * WG + (gid.x % WG);
|
| 38 |
let split_index = wg.y;
|
| 39 |
if (output_index >= params.outputCount) { return; }
|
| 40 |
let outer_index = output_index / params.innerSize;
|
| 41 |
let inner_index = output_index % params.innerSize;
|
| 42 |
-
let input_base = outer_index * params.axisDim * params.innerSize + inner_index;
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
// Even split of [0, rows) across SPLIT segments (last segment may be shorter).
|
| 45 |
let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
|
| 46 |
let axis_begin = split_index * chunk;
|
| 47 |
var axis_end = min(axis_begin + chunk, params.axisDim);
|
| 48 |
|
| 49 |
-
var best_val = lane_identity();
|
| 50 |
-
var best_idx = SENTINEL_IDX;
|
| 51 |
for (var axis_index = axis_begin; axis_index < axis_end; axis_index++) {
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
{% else %}
|
| 56 |
-
|
| 57 |
-
{
|
| 58 |
best_val = v;
|
| 59 |
best_idx = axis_index;
|
| 60 |
}
|
|
|
|
| 61 |
}
|
| 62 |
-
{% if
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
| 64 |
{% else %}
|
| 65 |
-
partials_val[split_index * params.outputCount + output_index] = bitcast<u32>(best_val);
|
| 66 |
-
{% endif %}
|
| 67 |
partials_idx[split_index * params.outputCount + output_index] = best_idx;
|
|
|
|
| 68 |
}
|
|
|
|
| 6 |
// layout without losing integer precision. The combine pass restores the native
|
| 7 |
// comparison type. Equal values keep the lowest index by default or the highest
|
| 8 |
// index when select_last_index is enabled.
|
|
|
|
| 9 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 10 |
{% set work = scalar if is_int else "f32" %}
|
| 11 |
+
{% set columnVectorWidth = columnVectorWidth | default(1) %}
|
| 12 |
+
{% macro arg_wins(v, i, best, bestIdx) %}{{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ "(" ~ bestIdx ~ " == SENTINEL_IDX || " ~ i ~ " > " ~ bestIdx ~ ")" if selectLastIndex else i ~ " < " ~ bestIdx }}){% endmacro %}
|
| 13 |
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
|
| 15 |
const WG: u32 = {{ workgroupSize }}u;
|
|
|
|
| 35 |
@builtin(workgroup_id) wg: vec3<u32>) {
|
| 36 |
// 2D-folded column index: wg.z carries the high bits past the per-axis dispatch
|
| 37 |
// fold width on the x dimension.
|
| 38 |
+
let output_index = {% if columnVectorWidth > 1 %}({% endif %}(wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u) * WG + (gid.x % WG){% if columnVectorWidth > 1 %}) * {{ columnVectorWidth }}u{% endif %};
|
| 39 |
let split_index = wg.y;
|
| 40 |
if (output_index >= params.outputCount) { return; }
|
| 41 |
let outer_index = output_index / params.innerSize;
|
| 42 |
let inner_index = output_index % params.innerSize;
|
| 43 |
+
let input_base = {% if columnVectorWidth > 1 %}({% endif %}outer_index * params.axisDim * params.innerSize + inner_index{% if columnVectorWidth > 1 %}) / {{ columnVectorWidth }}u{% endif %};
|
| 44 |
+
{% if columnVectorWidth > 1 %}
|
| 45 |
+
let inner_vectors = params.innerSize / {{ columnVectorWidth }}u;
|
| 46 |
+
{% endif %}
|
| 47 |
|
| 48 |
// Even split of [0, rows) across SPLIT segments (last segment may be shorter).
|
| 49 |
let chunk = (params.axisDim + SPLIT - 1u) / SPLIT;
|
| 50 |
let axis_begin = split_index * chunk;
|
| 51 |
var axis_end = min(axis_begin + chunk, params.axisDim);
|
| 52 |
|
| 53 |
+
var best_val = {% if columnVectorWidth > 1 %}vec{{ columnVectorWidth }}<{{ work }}>({% endif %}lane_identity(){% if columnVectorWidth > 1 %}){% endif %};
|
| 54 |
+
var best_idx = {% if columnVectorWidth > 1 %}vec{{ columnVectorWidth }}<u32>({% endif %}SENTINEL_IDX{% if columnVectorWidth > 1 %}){% endif %};
|
| 55 |
for (var axis_index = axis_begin; axis_index < axis_end; axis_index++) {
|
| 56 |
+
{% if columnVectorWidth > 1 %}
|
| 57 |
+
let v = x[input_base + axis_index * inner_vectors];
|
| 58 |
+
let take = vec{{ columnVectorWidth }}<bool>(
|
| 59 |
+
{% for c in ['x', 'y', 'z', 'w'][:columnVectorWidth] %}
|
| 60 |
+
{{ arg_wins("v." ~ c, "axis_index", "best_val." ~ c, "best_idx." ~ c) }}{{ ',' if not loop.last else '' }}
|
| 61 |
+
{% endfor %}
|
| 62 |
+
);
|
| 63 |
+
best_val = select(best_val, v, take);
|
| 64 |
+
best_idx = select(best_idx, vec{{ columnVectorWidth }}<u32>(axis_index), take);
|
| 65 |
{% else %}
|
| 66 |
+
let v = x[input_base + axis_index * params.innerSize];
|
| 67 |
+
if ({{ arg_wins("v", "axis_index", "best_val", "best_idx") }}) {
|
| 68 |
best_val = v;
|
| 69 |
best_idx = axis_index;
|
| 70 |
}
|
| 71 |
+
{% endif %}
|
| 72 |
}
|
| 73 |
+
{% if columnVectorWidth > 1 %}
|
| 74 |
+
{% for component in ['x', 'y', 'z', 'w'][:columnVectorWidth] %}
|
| 75 |
+
partials_val[split_index * params.outputCount + output_index + {{ loop.index0 }}u] = bitcast<u32>(best_val.{{ component }});
|
| 76 |
+
partials_idx[split_index * params.outputCount + output_index + {{ loop.index0 }}u] = best_idx.{{ component }};
|
| 77 |
+
{% endfor %}
|
| 78 |
{% else %}
|
| 79 |
+
partials_val[split_index * params.outputCount + output_index] = {{ "best_val" if scalar == "u32" else "bitcast<u32>(best_val)" }};
|
|
|
|
| 80 |
partials_idx[split_index * params.outputCount + output_index] = best_idx;
|
| 81 |
+
{% endif %}
|
| 82 |
}
|
build/webgpu/reduce-arg-axis-split-tiled.wgsl.jinja
CHANGED
|
@@ -7,9 +7,10 @@
|
|
| 7 |
//
|
| 8 |
// The final fold is intentionally subgroup-free: it is small, deterministic,
|
| 9 |
// and preserves identical first/last-index tie semantics on every feature tier.
|
| 10 |
-
{% set cmp = ">" if modeSpec == "max" else "<" %}
|
| 11 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 12 |
{% set work = scalar if is_int else "f32" %}
|
|
|
|
|
|
|
| 13 |
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
|
| 15 |
const WG: u32 = {{ workgroupSize }}u;
|
|
@@ -56,11 +57,7 @@ fn main(
|
|
| 56 |
if (in_bounds) {
|
| 57 |
for (var axis_index = axis_begin + row_lane; axis_index < axis_end; axis_index += ROW_LANES) {
|
| 58 |
let value = x[input_base + axis_index * params.innerSize];
|
| 59 |
-
{
|
| 60 |
-
if (value {{ cmp }} best_val || (value == best_val && (best_idx == SENTINEL_IDX || axis_index > best_idx))) {
|
| 61 |
-
{% else %}
|
| 62 |
-
if (value {{ cmp }} best_val || (value == best_val && axis_index < best_idx)) {
|
| 63 |
-
{% endif %}
|
| 64 |
best_val = value;
|
| 65 |
best_idx = axis_index;
|
| 66 |
}
|
|
@@ -76,20 +73,12 @@ fn main(
|
|
| 76 |
for (var lane = 1u; lane < ROW_LANES; lane++) {
|
| 77 |
let candidate_val = partial_val[lane * TILE_COLS + col_lane];
|
| 78 |
let candidate_idx = partial_idx[lane * TILE_COLS + col_lane];
|
| 79 |
-
{
|
| 80 |
-
if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx > out_idx))) {
|
| 81 |
-
{% else %}
|
| 82 |
-
if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx < out_idx))) {
|
| 83 |
-
{% endif %}
|
| 84 |
out_val = candidate_val;
|
| 85 |
out_idx = candidate_idx;
|
| 86 |
}
|
| 87 |
}
|
| 88 |
-
{
|
| 89 |
-
partials_val[split_index * params.outputCount + output_index] = out_val;
|
| 90 |
-
{% else %}
|
| 91 |
-
partials_val[split_index * params.outputCount + output_index] = bitcast<u32>(out_val);
|
| 92 |
-
{% endif %}
|
| 93 |
partials_idx[split_index * params.outputCount + output_index] = out_idx;
|
| 94 |
}
|
| 95 |
}
|
|
|
|
| 7 |
//
|
| 8 |
// The final fold is intentionally subgroup-free: it is small, deterministic,
|
| 9 |
// and preserves identical first/last-index tie semantics on every feature tier.
|
|
|
|
| 10 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 11 |
{% set work = scalar if is_int else "f32" %}
|
| 12 |
+
{% macro arg_wins(v, i, best, bestIdx) %}{{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ "(" ~ bestIdx ~ " == SENTINEL_IDX || " ~ i ~ " > " ~ bestIdx ~ ")" if selectLastIndex else i ~ " < " ~ bestIdx }}){% endmacro %}
|
| 13 |
+
{% macro arg_merge_wins(v, i, best, bestIdx) %}{{ i }} != SENTINEL_IDX && ({{ bestIdx }} == SENTINEL_IDX || {{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ i }} {{ ">" if selectLastIndex else "<" }} {{ bestIdx }})){% endmacro %}
|
| 14 |
{{ env.wgsl.resourceDeclarations }}
|
| 15 |
|
| 16 |
const WG: u32 = {{ workgroupSize }}u;
|
|
|
|
| 57 |
if (in_bounds) {
|
| 58 |
for (var axis_index = axis_begin + row_lane; axis_index < axis_end; axis_index += ROW_LANES) {
|
| 59 |
let value = x[input_base + axis_index * params.innerSize];
|
| 60 |
+
if ({{ arg_wins("value", "axis_index", "best_val", "best_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
best_val = value;
|
| 62 |
best_idx = axis_index;
|
| 63 |
}
|
|
|
|
| 73 |
for (var lane = 1u; lane < ROW_LANES; lane++) {
|
| 74 |
let candidate_val = partial_val[lane * TILE_COLS + col_lane];
|
| 75 |
let candidate_idx = partial_idx[lane * TILE_COLS + col_lane];
|
| 76 |
+
if ({{ arg_merge_wins("candidate_val", "candidate_idx", "out_val", "out_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
out_val = candidate_val;
|
| 78 |
out_idx = candidate_idx;
|
| 79 |
}
|
| 80 |
}
|
| 81 |
+
partials_val[split_index * params.outputCount + output_index] = {{ "out_val" if scalar == "u32" else "bitcast<u32>(out_val)" }};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
partials_idx[split_index * params.outputCount + output_index] = out_idx;
|
| 83 |
}
|
| 84 |
}
|
build/webgpu/reduce-arg-axis-tiled.wgsl.jinja
CHANGED
|
@@ -4,9 +4,10 @@
|
|
| 4 |
// bitcast round trip. Flattening the output as (outer, inner) lets one coalesced
|
| 5 |
// column tile handle every non-contiguous axis. f16 uses the serial or
|
| 6 |
// contiguous routes because this kernel has no widened-f16 scratch path.
|
| 7 |
-
{% set cmp = ">" if modeSpec == "max" else "<" %}
|
| 8 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 9 |
{% set work = scalar if is_int else "f32" %}
|
|
|
|
|
|
|
| 10 |
{{ env.wgsl.resourceDeclarations }}
|
| 11 |
|
| 12 |
const WG: u32 = {{ workgroupSize }}u;
|
|
@@ -50,11 +51,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 50 |
if (in_bounds) {
|
| 51 |
for (var axis_index = row_lane; axis_index < params.axisDim; axis_index = axis_index + ROW_LANES) {
|
| 52 |
let v = x[input_base + axis_index * params.innerSize];
|
| 53 |
-
{
|
| 54 |
-
if (v {{ cmp }} best_val || (v == best_val && (best_idx == SENTINEL_IDX || axis_index > best_idx))) {
|
| 55 |
-
{% else %}
|
| 56 |
-
if (v {{ cmp }} best_val || (v == best_val && axis_index < best_idx)) {
|
| 57 |
-
{% endif %}
|
| 58 |
best_val = v;
|
| 59 |
best_idx = axis_index;
|
| 60 |
}
|
|
@@ -70,11 +67,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 70 |
for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
|
| 71 |
let candidate_val = partial_val[lane * TILE_COLS + col_lane];
|
| 72 |
let candidate_idx = partial_idx[lane * TILE_COLS + col_lane];
|
| 73 |
-
{
|
| 74 |
-
if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx > out_idx))) {
|
| 75 |
-
{% else %}
|
| 76 |
-
if (candidate_idx != SENTINEL_IDX && (out_idx == SENTINEL_IDX || candidate_val {{ cmp }} out_val || (candidate_val == out_val && candidate_idx < out_idx))) {
|
| 77 |
-
{% endif %}
|
| 78 |
out_val = candidate_val;
|
| 79 |
out_idx = candidate_idx;
|
| 80 |
}
|
|
|
|
| 4 |
// bitcast round trip. Flattening the output as (outer, inner) lets one coalesced
|
| 5 |
// column tile handle every non-contiguous axis. f16 uses the serial or
|
| 6 |
// contiguous routes because this kernel has no widened-f16 scratch path.
|
|
|
|
| 7 |
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 8 |
{% set work = scalar if is_int else "f32" %}
|
| 9 |
+
{% macro arg_wins(v, i, best, bestIdx) %}{{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ "(" ~ bestIdx ~ " == SENTINEL_IDX || " ~ i ~ " > " ~ bestIdx ~ ")" if selectLastIndex else i ~ " < " ~ bestIdx }}){% endmacro %}
|
| 10 |
+
{% macro arg_merge_wins(v, i, best, bestIdx) %}{{ i }} != SENTINEL_IDX && ({{ bestIdx }} == SENTINEL_IDX || {{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ i }} {{ ">" if selectLastIndex else "<" }} {{ bestIdx }})){% endmacro %}
|
| 11 |
{{ env.wgsl.resourceDeclarations }}
|
| 12 |
|
| 13 |
const WG: u32 = {{ workgroupSize }}u;
|
|
|
|
| 51 |
if (in_bounds) {
|
| 52 |
for (var axis_index = row_lane; axis_index < params.axisDim; axis_index = axis_index + ROW_LANES) {
|
| 53 |
let v = x[input_base + axis_index * params.innerSize];
|
| 54 |
+
if ({{ arg_wins("v", "axis_index", "best_val", "best_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
best_val = v;
|
| 56 |
best_idx = axis_index;
|
| 57 |
}
|
|
|
|
| 67 |
for (var lane = 1u; lane < ROW_LANES; lane = lane + 1u) {
|
| 68 |
let candidate_val = partial_val[lane * TILE_COLS + col_lane];
|
| 69 |
let candidate_idx = partial_idx[lane * TILE_COLS + col_lane];
|
| 70 |
+
if ({{ arg_merge_wins("candidate_val", "candidate_idx", "out_val", "out_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
out_val = candidate_val;
|
| 72 |
out_idx = candidate_idx;
|
| 73 |
}
|
build/webgpu/reduce-arg-axis.wgsl.jinja
CHANGED
|
@@ -2,43 +2,34 @@
|
|
| 2 |
// tensor around the reduced axis into (outer, axis, inner) gives one
|
| 3 |
// rank-independent coordinate calculation. One thread owns one output position
|
| 4 |
// and walks the reduced axis at innerSize stride.
|
| 5 |
-
{% set cmp = ">"
|
| 6 |
-
{%
|
| 7 |
-
|
| 8 |
-
{%
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
{{ env.wgsl.resourceDeclarations }}
|
| 10 |
|
| 11 |
@compute @workgroup_size({{ workgroupSize }})
|
| 12 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 13 |
-
|
| 14 |
-
let output_index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ workgroupSize }}u;
|
| 15 |
-
if (output_index >= params.outputCount) {
|
| 16 |
-
return;
|
| 17 |
-
}
|
| 18 |
|
| 19 |
let outer_index = output_index / params.innerSize;
|
| 20 |
let inner_index = output_index % params.innerSize;
|
| 21 |
let input_base = outer_index * params.axisDim * params.innerSize + inner_index;
|
| 22 |
|
| 23 |
var best_index = 0u;
|
| 24 |
-
|
| 25 |
-
var best = x[input_base];
|
| 26 |
-
for (var axis_index = 1u; axis_index < params.axisDim; axis_index++) {
|
| 27 |
-
let candidate = x[input_base + axis_index * params.innerSize];
|
| 28 |
-
if (candidate {{ cmp }} best{% if selectLastIndex %} || candidate == best{% endif %}) {
|
| 29 |
-
best = candidate;
|
| 30 |
-
best_index = axis_index;
|
| 31 |
-
}
|
| 32 |
-
}
|
| 33 |
-
{%- else %}
|
| 34 |
-
var best = f32(x[input_base]);
|
| 35 |
for (var axis_index = 1u; axis_index < params.axisDim; axis_index++) {
|
| 36 |
-
let candidate = f32(x[input_base + axis_index * params.innerSize]);
|
| 37 |
if (candidate {{ cmp }} best{% if selectLastIndex %} || candidate == best{% endif %}) {
|
| 38 |
best = candidate;
|
| 39 |
best_index = axis_index;
|
| 40 |
}
|
| 41 |
}
|
| 42 |
-
{%- endif %}
|
| 43 |
y[output_index] = best_index;
|
| 44 |
}
|
|
|
|
| 2 |
// tensor around the reduced axis into (outer, axis, inner) gives one
|
| 3 |
// rank-independent coordinate calculation. One thread owns one output position
|
| 4 |
// and walks the reduced axis at innerSize stride.
|
| 5 |
+
{% set cmp = ">" %}
|
| 6 |
+
{% set widen = scalar != "i32" and scalar != "u32" %}
|
| 7 |
+
{% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
|
| 8 |
+
{% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
|
| 9 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 10 |
+
// per-axis workgroup fold width.
|
| 11 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
|
| 12 |
+
if ({{ name }} >= {{ bound }}) {
|
| 13 |
+
return;
|
| 14 |
+
}{% endmacro %}
|
| 15 |
{{ env.wgsl.resourceDeclarations }}
|
| 16 |
|
| 17 |
@compute @workgroup_size({{ workgroupSize }})
|
| 18 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 19 |
+
{{ flat_index_2d(workgroupSize, "output_index", "params.outputCount") }}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
let outer_index = output_index / params.innerSize;
|
| 22 |
let inner_index = output_index % params.innerSize;
|
| 23 |
let input_base = outer_index * params.axisDim * params.innerSize + inner_index;
|
| 24 |
|
| 25 |
var best_index = 0u;
|
| 26 |
+
var best = {{ "f32(x[input_base])" if widen else "x[input_base]" }};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
for (var axis_index = 1u; axis_index < params.axisDim; axis_index++) {
|
| 28 |
+
let candidate = {{ "f32(x[input_base + axis_index * params.innerSize])" if widen else "x[input_base + axis_index * params.innerSize]" }};
|
| 29 |
if (candidate {{ cmp }} best{% if selectLastIndex %} || candidate == best{% endif %}) {
|
| 30 |
best = candidate;
|
| 31 |
best_index = axis_index;
|
| 32 |
}
|
| 33 |
}
|
|
|
|
| 34 |
y[output_index] = best_index;
|
| 35 |
}
|
build/webgpu/reduce-arg-row-split.wgsl.jinja
CHANGED
|
@@ -1,33 +1,26 @@
|
|
| 1 |
-
{% set
|
| 2 |
-
{% if
|
| 3 |
-
|
| 4 |
-
{%
|
|
|
|
| 5 |
{% if useSubgroups %}
|
| 6 |
enable subgroups;
|
| 7 |
-
{%
|
| 8 |
{{ env.wgsl.resourceDeclarations }}
|
| 9 |
|
| 10 |
const WG: u32 = {{ workgroupSize }}u;
|
| 11 |
const SPLIT: u32 = {{ split }}u;
|
| 12 |
const SENTINEL_IDX: u32 = 4294967295u;
|
| 13 |
-
{%- if scalar == "i32" %}
|
| 14 |
-
{% set work = "i32" %}
|
| 15 |
-
{% elif scalar == "u32" %}
|
| 16 |
-
{% set work = "u32" %}
|
| 17 |
-
{% else %}
|
| 18 |
-
{% set work = "f32" %}
|
| 19 |
-
{% endif %}
|
| 20 |
-
{% set cmp = ">" if modeSpec == "max" else "<" %}
|
| 21 |
|
| 22 |
fn lane_identity() -> {{ work }} {
|
| 23 |
-
{%
|
| 24 |
return bitcast<i32>({{ "0x80000000u" if modeSpec == "max" else "0x7fffffffu" }});
|
| 25 |
-
{%
|
| 26 |
return {{ "0u" if modeSpec == "max" else "0xffffffffu" }};
|
| 27 |
-
{%
|
| 28 |
var bits = {{ "0xff800000u" if modeSpec == "max" else "0x7f800000u" }};
|
| 29 |
return bitcast<f32>(bits);
|
| 30 |
-
{%
|
| 31 |
}
|
| 32 |
|
| 33 |
var<workgroup> wg_val: array<{{ work }}, WG>;
|
|
@@ -36,6 +29,7 @@ var<workgroup> wg_idx: array<u32, WG>;
|
|
| 36 |
@compute @workgroup_size(WG, 1, 1)
|
| 37 |
fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
| 38 |
@builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
|
|
|
|
| 39 |
@builtin(subgroup_size) subgroup_size: u32{% endif %}) {
|
| 40 |
let row = wg.x;
|
| 41 |
let split_index = wg.y;
|
|
@@ -48,68 +42,59 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
|
| 48 |
var best_val = lane_identity();
|
| 49 |
var best_idx = SENTINEL_IDX;
|
| 50 |
for (var chunk = segment_begin + tid; chunk < segment_end; chunk += WG) {
|
| 51 |
-
{%
|
| 52 |
let value4 = x[base + chunk];
|
| 53 |
-
{%
|
| 54 |
{
|
| 55 |
-
{
|
| 56 |
-
let value = f32(value4.{{ component }});
|
| 57 |
-
{%- else %}
|
| 58 |
-
let value = value4.{{ component }};
|
| 59 |
-
{%- endif %}
|
| 60 |
let index = chunk * 4u + {{ loop.index0 }}u;
|
| 61 |
-
{
|
| 62 |
-
if (value {{ cmp }} best_val || (value == best_val && (best_idx == SENTINEL_IDX || index > best_idx))) {
|
| 63 |
-
{%- else %}
|
| 64 |
-
if (value {{ cmp }} best_val || (value == best_val && index < best_idx)) {
|
| 65 |
-
{%- endif %}
|
| 66 |
best_val = value;
|
| 67 |
best_idx = index;
|
| 68 |
}
|
| 69 |
}
|
| 70 |
-
{%
|
| 71 |
{% else %}
|
| 72 |
-
{
|
| 73 |
-
|
| 74 |
-
{%- else %}
|
| 75 |
-
let value = x[base + chunk];
|
| 76 |
-
{%- endif %}
|
| 77 |
-
{% if selectLastIndex %}
|
| 78 |
-
if (value {{ cmp }} best_val || (value == best_val && (best_idx == SENTINEL_IDX || chunk > best_idx))) {
|
| 79 |
-
{%- else %}
|
| 80 |
-
if (value {{ cmp }} best_val || (value == best_val && chunk < best_idx)) {
|
| 81 |
-
{%- endif %}
|
| 82 |
best_val = value;
|
| 83 |
best_idx = chunk;
|
| 84 |
}
|
| 85 |
-
{%
|
| 86 |
}
|
| 87 |
-
{%
|
| 88 |
-
let subgroup_val =
|
|
|
|
| 89 |
let subgroup_candidate = select(0u, best_idx, best_val == subgroup_val && best_idx != SENTINEL_IDX);
|
| 90 |
let subgroup_idx = subgroupMax(subgroup_candidate);
|
| 91 |
-
{%
|
| 92 |
let subgroup_candidate = select(SENTINEL_IDX, best_idx, best_val == subgroup_val);
|
| 93 |
let subgroup_idx = subgroupMin(subgroup_candidate);
|
| 94 |
-
{%
|
| 95 |
// The cross-subgroup fold assumes nothing about which invocations share a
|
| 96 |
-
// subgroup
|
| 97 |
-
//
|
|
|
|
| 98 |
// publishes its subgroup pair there and every other lane publishes the
|
| 99 |
// identity pair (the sentinel index, which the merge skips). Each subgroup
|
| 100 |
-
// then folds all WG slots — lane `
|
| 101 |
-
//
|
| 102 |
-
//
|
| 103 |
-
//
|
| 104 |
-
//
|
| 105 |
var output_val = subgroup_val;
|
| 106 |
var output_idx = subgroup_idx;
|
| 107 |
// A one-subgroup workgroup is already fully reduced by the collectives above.
|
| 108 |
// The test reads the `subgroup_size` builtin, which is uniform; a collective's
|
| 109 |
// result is not uniform to WGSL's analysis and may not guard a barrier.
|
| 110 |
if (subgroup_size != WG) {
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
let leader = rank == 0u;
|
| 114 |
wg_val[tid] = select(lane_identity(), subgroup_val, leader);
|
| 115 |
wg_idx[tid] = select(SENTINEL_IDX, subgroup_idx, leader);
|
|
@@ -119,33 +104,26 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
|
| 119 |
for (var slot = rank; slot < WG; slot = slot + count) {
|
| 120 |
let candidate_val = wg_val[slot];
|
| 121 |
let candidate_idx = wg_idx[slot];
|
| 122 |
-
{
|
| 123 |
-
if (candidate_idx != SENTINEL_IDX && (fold_idx == SENTINEL_IDX || candidate_val {{ cmp }} fold_val || (candidate_val == fold_val && candidate_idx > fold_idx))) {
|
| 124 |
-
{%- else %}
|
| 125 |
-
if (candidate_idx != SENTINEL_IDX && (fold_idx == SENTINEL_IDX || candidate_val {{ cmp }} fold_val || (candidate_val == fold_val && candidate_idx < fold_idx))) {
|
| 126 |
-
{%- endif %}
|
| 127 |
fold_val = candidate_val;
|
| 128 |
fold_idx = candidate_idx;
|
| 129 |
}
|
| 130 |
}
|
| 131 |
-
output_val =
|
|
|
|
| 132 |
let fold_candidate = select(0u, fold_idx, fold_val == output_val && fold_idx != SENTINEL_IDX);
|
| 133 |
output_idx = subgroupMax(fold_candidate);
|
| 134 |
-
{%
|
| 135 |
let fold_candidate = select(SENTINEL_IDX, fold_idx, fold_val == output_val);
|
| 136 |
output_idx = subgroupMin(fold_candidate);
|
| 137 |
-
{%
|
| 138 |
}
|
| 139 |
if (tid == 0u) {
|
| 140 |
let scratch_index = split_index * params.rows + row;
|
| 141 |
-
{
|
| 142 |
-
partials_val[scratch_index] = output_val;
|
| 143 |
-
{%- else %}
|
| 144 |
-
partials_val[scratch_index] = bitcast<u32>(output_val);
|
| 145 |
-
{%- endif %}
|
| 146 |
partials_idx[scratch_index] = output_idx;
|
| 147 |
}
|
| 148 |
-
{%
|
| 149 |
wg_val[tid] = best_val;
|
| 150 |
wg_idx[tid] = best_idx;
|
| 151 |
workgroupBarrier();
|
|
@@ -154,11 +132,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
|
| 154 |
let candidate_val = wg_val[tid + stride];
|
| 155 |
let candidate_idx = wg_idx[tid + stride];
|
| 156 |
let current_idx = wg_idx[tid];
|
| 157 |
-
{
|
| 158 |
-
if (candidate_idx != SENTINEL_IDX && (current_idx == SENTINEL_IDX || candidate_val {{ cmp }} wg_val[tid] || (candidate_val == wg_val[tid] && candidate_idx > current_idx))) {
|
| 159 |
-
{%- else %}
|
| 160 |
-
if (candidate_idx != SENTINEL_IDX && (current_idx == SENTINEL_IDX || candidate_val {{ cmp }} wg_val[tid] || (candidate_val == wg_val[tid] && candidate_idx < current_idx))) {
|
| 161 |
-
{%- endif %}
|
| 162 |
wg_val[tid] = candidate_val;
|
| 163 |
wg_idx[tid] = candidate_idx;
|
| 164 |
}
|
|
@@ -167,12 +141,8 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
|
| 167 |
}
|
| 168 |
if (tid == 0u) {
|
| 169 |
let scratch_index = split_index * params.rows + row;
|
| 170 |
-
{
|
| 171 |
-
partials_val[scratch_index] = wg_val[0];
|
| 172 |
-
{%- else %}
|
| 173 |
-
partials_val[scratch_index] = bitcast<u32>(wg_val[0]);
|
| 174 |
-
{%- endif %}
|
| 175 |
partials_idx[scratch_index] = wg_idx[0];
|
| 176 |
}
|
| 177 |
-
{%
|
| 178 |
}
|
|
|
|
| 1 |
+
{% set is_int = scalar == "i32" or scalar == "u32" %}
|
| 2 |
+
{% set work = scalar if is_int else "f32" %}
|
| 3 |
+
{% set sgReduce = "subgroupMax" %}
|
| 4 |
+
{% macro arg_wins(v, i, best, bestIdx) %}{{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ "(" ~ bestIdx ~ " == SENTINEL_IDX || " ~ i ~ " > " ~ bestIdx ~ ")" if selectLastIndex else i ~ " < " ~ bestIdx }}){% endmacro %}
|
| 5 |
+
{% macro arg_merge_wins(v, i, best, bestIdx) %}{{ i }} != SENTINEL_IDX && ({{ bestIdx }} == SENTINEL_IDX || {{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ i }} {{ ">" if selectLastIndex else "<" }} {{ bestIdx }})){% endmacro %}
|
| 6 |
{% if useSubgroups %}
|
| 7 |
enable subgroups;
|
| 8 |
+
{% endif %}
|
| 9 |
{{ env.wgsl.resourceDeclarations }}
|
| 10 |
|
| 11 |
const WG: u32 = {{ workgroupSize }}u;
|
| 12 |
const SPLIT: u32 = {{ split }}u;
|
| 13 |
const SENTINEL_IDX: u32 = 4294967295u;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
fn lane_identity() -> {{ work }} {
|
| 16 |
+
{% if scalar == "i32" %}
|
| 17 |
return bitcast<i32>({{ "0x80000000u" if modeSpec == "max" else "0x7fffffffu" }});
|
| 18 |
+
{% elif scalar == "u32" %}
|
| 19 |
return {{ "0u" if modeSpec == "max" else "0xffffffffu" }};
|
| 20 |
+
{% else %}
|
| 21 |
var bits = {{ "0xff800000u" if modeSpec == "max" else "0x7f800000u" }};
|
| 22 |
return bitcast<f32>(bits);
|
| 23 |
+
{% endif %}
|
| 24 |
}
|
| 25 |
|
| 26 |
var<workgroup> wg_val: array<{{ work }}, WG>;
|
|
|
|
| 29 |
@compute @workgroup_size(WG, 1, 1)
|
| 30 |
fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
| 31 |
@builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
|
| 32 |
+
@builtin(subgroup_invocation_id) subgroup_lane: u32,
|
| 33 |
@builtin(subgroup_size) subgroup_size: u32{% endif %}) {
|
| 34 |
let row = wg.x;
|
| 35 |
let split_index = wg.y;
|
|
|
|
| 42 |
var best_val = lane_identity();
|
| 43 |
var best_idx = SENTINEL_IDX;
|
| 44 |
for (var chunk = segment_begin + tid; chunk < segment_end; chunk += WG) {
|
| 45 |
+
{% if vec4 %}
|
| 46 |
let value4 = x[base + chunk];
|
| 47 |
+
{% for component in ["x", "y", "z", "w"] %}
|
| 48 |
{
|
| 49 |
+
let value = {{ "f32(value4." ~ component ~ ")" if scalar == "f16" else "value4." ~ component }};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
let index = chunk * 4u + {{ loop.index0 }}u;
|
| 51 |
+
if ({{ arg_wins("value", "index", "best_val", "best_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
best_val = value;
|
| 53 |
best_idx = index;
|
| 54 |
}
|
| 55 |
}
|
| 56 |
+
{% endfor %}
|
| 57 |
{% else %}
|
| 58 |
+
let value = {{ "f32(x[base + chunk])" if scalar == "f16" else "x[base + chunk]" }};
|
| 59 |
+
if ({{ arg_wins("value", "chunk", "best_val", "best_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
best_val = value;
|
| 61 |
best_idx = chunk;
|
| 62 |
}
|
| 63 |
+
{% endif %}
|
| 64 |
}
|
| 65 |
+
{% if useSubgroups %}
|
| 66 |
+
let subgroup_val = {{ sgReduce }}(best_val);
|
| 67 |
+
{% if selectLastIndex %}
|
| 68 |
let subgroup_candidate = select(0u, best_idx, best_val == subgroup_val && best_idx != SENTINEL_IDX);
|
| 69 |
let subgroup_idx = subgroupMax(subgroup_candidate);
|
| 70 |
+
{% else %}
|
| 71 |
let subgroup_candidate = select(SENTINEL_IDX, best_idx, best_val == subgroup_val);
|
| 72 |
let subgroup_idx = subgroupMin(subgroup_candidate);
|
| 73 |
+
{% endif %}
|
| 74 |
// The cross-subgroup fold assumes nothing about which invocations share a
|
| 75 |
+
// subgroup or how many subgroups there are (it does require the uniform
|
| 76 |
+
// control flow this entry point already has): every invocation owns the slot
|
| 77 |
+
// at its own index, the elected lane
|
| 78 |
// publishes its subgroup pair there and every other lane publishes the
|
| 79 |
// identity pair (the sentinel index, which the merge skips). Each subgroup
|
| 80 |
+
// then folds all WG slots — lane `subgroup_lane` walks slots subgroup_lane,
|
| 81 |
+
// subgroup_lane + subgroup_size, ... — and one more collective merges the lane
|
| 82 |
+
// partials, so every slot is merged exactly once at any legal width. The merge
|
| 83 |
+
// (best value, then the tie-break index) is commutative and associative, so
|
| 84 |
+
// the fold order does not change the result.
|
| 85 |
var output_val = subgroup_val;
|
| 86 |
var output_idx = subgroup_idx;
|
| 87 |
// A one-subgroup workgroup is already fully reduced by the collectives above.
|
| 88 |
// The test reads the `subgroup_size` builtin, which is uniform; a collective's
|
| 89 |
// result is not uniform to WGSL's analysis and may not guard a barrier.
|
| 90 |
if (subgroup_size != WG) {
|
| 91 |
+
// The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
|
| 92 |
+
// `subgroupAdd(1u)`: those agree with them under this contract, but a driver
|
| 93 |
+
// in the wild answers a claim derived from them with zero and leaves most of
|
| 94 |
+
// the workgroup's slots unclaimed. Every lane of this entry point is active
|
| 95 |
+
// here, so the full subgroup width is the right stride.
|
| 96 |
+
let rank = subgroup_lane;
|
| 97 |
+
let count = subgroup_size;
|
| 98 |
let leader = rank == 0u;
|
| 99 |
wg_val[tid] = select(lane_identity(), subgroup_val, leader);
|
| 100 |
wg_idx[tid] = select(SENTINEL_IDX, subgroup_idx, leader);
|
|
|
|
| 104 |
for (var slot = rank; slot < WG; slot = slot + count) {
|
| 105 |
let candidate_val = wg_val[slot];
|
| 106 |
let candidate_idx = wg_idx[slot];
|
| 107 |
+
if ({{ arg_merge_wins("candidate_val", "candidate_idx", "fold_val", "fold_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
fold_val = candidate_val;
|
| 109 |
fold_idx = candidate_idx;
|
| 110 |
}
|
| 111 |
}
|
| 112 |
+
output_val = {{ sgReduce }}(fold_val);
|
| 113 |
+
{% if selectLastIndex %}
|
| 114 |
let fold_candidate = select(0u, fold_idx, fold_val == output_val && fold_idx != SENTINEL_IDX);
|
| 115 |
output_idx = subgroupMax(fold_candidate);
|
| 116 |
+
{% else %}
|
| 117 |
let fold_candidate = select(SENTINEL_IDX, fold_idx, fold_val == output_val);
|
| 118 |
output_idx = subgroupMin(fold_candidate);
|
| 119 |
+
{% endif %}
|
| 120 |
}
|
| 121 |
if (tid == 0u) {
|
| 122 |
let scratch_index = split_index * params.rows + row;
|
| 123 |
+
partials_val[scratch_index] = {{ "output_val" if scalar == "u32" else "bitcast<u32>(output_val)" }};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
partials_idx[scratch_index] = output_idx;
|
| 125 |
}
|
| 126 |
+
{% else %}
|
| 127 |
wg_val[tid] = best_val;
|
| 128 |
wg_idx[tid] = best_idx;
|
| 129 |
workgroupBarrier();
|
|
|
|
| 132 |
let candidate_val = wg_val[tid + stride];
|
| 133 |
let candidate_idx = wg_idx[tid + stride];
|
| 134 |
let current_idx = wg_idx[tid];
|
| 135 |
+
if ({{ arg_merge_wins("candidate_val", "candidate_idx", "wg_val[tid]", "current_idx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
wg_val[tid] = candidate_val;
|
| 137 |
wg_idx[tid] = candidate_idx;
|
| 138 |
}
|
|
|
|
| 141 |
}
|
| 142 |
if (tid == 0u) {
|
| 143 |
let scratch_index = split_index * params.rows + row;
|
| 144 |
+
partials_val[scratch_index] = {{ "wg_val[0]" if scalar == "u32" else "bitcast<u32>(wg_val[0])" }};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
partials_idx[scratch_index] = wg_idx[0];
|
| 146 |
}
|
| 147 |
+
{% endif %}
|
| 148 |
}
|
build/webgpu/reduce-arg-row-subgroup.wgsl.jinja
CHANGED
|
@@ -11,148 +11,135 @@
|
|
| 11 |
// integer bounds; ties against the sentinel use the same index rule, so a row
|
| 12 |
// filled with the sentinel still produces the correct index. NaNs are never
|
| 13 |
// accepted, and an all-NaN row returns index zero.
|
| 14 |
-
{%
|
| 15 |
-
{%
|
| 16 |
-
|
| 17 |
-
{%
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
{% if useSubgroups %}
|
| 19 |
enable subgroups;
|
| 20 |
-
{%
|
| 21 |
{{ env.wgsl.resourceDeclarations }}
|
| 22 |
|
| 23 |
const WG: u32 = {{ workgroupSize }}u;
|
| 24 |
const SENTINEL_IDX: u32 = 4294967295u;
|
| 25 |
-
{% if
|
| 26 |
|
| 27 |
-
fn
|
| 28 |
var bits = 0xff800000u;
|
| 29 |
return bitcast<f32>(bits);
|
| 30 |
}
|
|
|
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
{% endif %}
|
| 33 |
-
{%
|
| 34 |
-
|
| 35 |
-
{% elif scalar == "u32" %}
|
| 36 |
-
{% set cmpType = "u32" %}
|
| 37 |
-
{% else %}
|
| 38 |
-
{% set cmpType = "f32" %}
|
| 39 |
-
{% endif %}
|
| 40 |
-
{% set cmp = ">" %}
|
| 41 |
-
{%- if cmpType == "i32" %}
|
| 42 |
-
{% set identityVal = "-2147483647i - 1i" %}
|
| 43 |
-
{%- elif cmpType == "u32" %}
|
| 44 |
-
{% set identityVal = "0u" %}
|
| 45 |
-
{%- else %}
|
| 46 |
-
{% set identityVal = "neg_inf_f32()" %}
|
| 47 |
-
{%- endif %}
|
| 48 |
-
{%- if not (useSubgroups and singleSubgroup is defined and singleSubgroup) %}
|
| 49 |
var<workgroup> wgVal: array<{{ cmpType }}, WG>;
|
| 50 |
var<workgroup> wgIdx: array<u32, WG>;
|
| 51 |
-
{%
|
| 52 |
|
| 53 |
@compute @workgroup_size(WG, 1, 1)
|
| 54 |
fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
| 55 |
-
@builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups and not
|
|
|
|
| 56 |
@builtin(subgroup_size) sgSize: u32{% endif %}) {
|
| 57 |
let row = wg.x + wg.y * params.rowStride;
|
| 58 |
if (row >= params.rows) {
|
| 59 |
return;
|
| 60 |
}
|
| 61 |
let tid = lid.x;
|
| 62 |
-
let base = row * params.chunkCount;
|
| 63 |
-
var bestVal:
|
| 64 |
-
{%- elif cmpType == "u32" %}
|
| 65 |
-
var bestVal: u32 = 0u;
|
| 66 |
-
{%- else %}
|
| 67 |
-
var bestVal: f32 = neg_inf_f32();
|
| 68 |
-
{%- endif %}
|
| 69 |
var bestIdx: u32 = SENTINEL_IDX;
|
| 70 |
-
{%
|
| 71 |
for (var c = tid; c < params.chunkCount; c = c + WG) {
|
| 72 |
let v4 = x[base + c];
|
| 73 |
-
{%
|
| 74 |
{
|
| 75 |
-
{
|
| 76 |
-
let v = f32(v4.{{ comp }});
|
| 77 |
-
{%- else %}
|
| 78 |
-
let v = v4.{{ comp }};
|
| 79 |
-
{%- endif %}
|
| 80 |
let idx = c * 4u + {{ loop.index0 }}u;
|
| 81 |
-
{
|
| 82 |
-
if (v {{ cmp }} bestVal || (v == bestVal && (bestIdx == SENTINEL_IDX || idx > bestIdx))) {
|
| 83 |
-
{%- else %}
|
| 84 |
-
if (v {{ cmp }} bestVal || (v == bestVal && idx < bestIdx)) {
|
| 85 |
-
{%- endif %}
|
| 86 |
bestVal = v;
|
| 87 |
bestIdx = idx;
|
| 88 |
}
|
| 89 |
}
|
| 90 |
-
{%
|
| 91 |
}
|
| 92 |
-
{%
|
| 93 |
-
//
|
| 94 |
-
//
|
| 95 |
-
//
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
{%- for plane in [0, 1, 2, 3] %}
|
| 99 |
{
|
| 100 |
-
let c = c0 + {{ plane }}u * WG;
|
| 101 |
if (c < params.chunkCount) {
|
| 102 |
-
{
|
| 103 |
-
|
| 104 |
-
{%- else %}
|
| 105 |
-
let v = x[base + c];
|
| 106 |
-
{%- endif %}
|
| 107 |
-
{% if selectLastIndex %}
|
| 108 |
-
if (v {{ cmp }} bestVal || (v == bestVal && (bestIdx == SENTINEL_IDX || c > bestIdx))) {
|
| 109 |
-
{%- else %}
|
| 110 |
-
if (v {{ cmp }} bestVal || (v == bestVal && c < bestIdx)) {
|
| 111 |
-
{%- endif %}
|
| 112 |
bestVal = v;
|
| 113 |
bestIdx = c;
|
| 114 |
}
|
| 115 |
}
|
| 116 |
}
|
| 117 |
-
{%
|
| 118 |
}
|
| 119 |
-
{%
|
| 120 |
-
{%
|
| 121 |
-
let m =
|
|
|
|
| 122 |
// Lanes that match the subgroup best contribute their (real) index; the
|
| 123 |
// rest contribute 0 so subgroupMax picks the highest matching index.
|
| 124 |
let cand = select(0u, bestIdx, bestVal == m && bestIdx != SENTINEL_IDX);
|
| 125 |
let sgIdx = subgroupMax(cand);
|
| 126 |
-
{%
|
| 127 |
// Lanes that match the subgroup best contribute their index; the rest
|
| 128 |
// contribute the sentinel so subgroupMin picks the lowest matching index.
|
| 129 |
let cand = select(SENTINEL_IDX, bestIdx, bestVal == m);
|
| 130 |
let sgIdx = subgroupMin(cand);
|
| 131 |
-
{%
|
| 132 |
-
{% if singleSubgroup
|
| 133 |
// The workgroup is one (possibly partial) subgroup: elect the writer rather
|
| 134 |
// than assuming lane 0 is active.
|
| 135 |
if (subgroupElect()) {
|
| 136 |
y[row] = select(sgIdx, 0u, sgIdx == SENTINEL_IDX);
|
| 137 |
}
|
| 138 |
-
{%
|
| 139 |
// Cross-subgroup fold that assumes nothing about which invocations share a
|
| 140 |
-
// subgroup
|
| 141 |
-
//
|
|
|
|
| 142 |
// publishes its subgroup pair there and every other lane publishes the
|
| 143 |
// identity pair (the sentinel index, which the merge skips). Each subgroup
|
| 144 |
-
// then folds all WG slots — lane `
|
| 145 |
-
//
|
| 146 |
-
//
|
| 147 |
-
//
|
| 148 |
-
//
|
| 149 |
var winnerIdx = sgIdx;
|
| 150 |
// A one-subgroup workgroup is already fully reduced by the collectives above.
|
| 151 |
// The test reads the `subgroup_size` builtin, which is uniform; a collective's
|
| 152 |
// result is not uniform to WGSL's analysis and may not guard a barrier.
|
| 153 |
if (sgSize != WG) {
|
| 154 |
-
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
let leader = rank == 0u;
|
| 157 |
wgVal[tid] = select({{ identityVal }}, m, leader);
|
| 158 |
wgIdx[tid] = select(SENTINEL_IDX, sgIdx, leader);
|
|
@@ -162,25 +149,22 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
|
| 162 |
for (var i = rank; i < WG; i = i + count) {
|
| 163 |
let v = wgVal[i];
|
| 164 |
let vi = wgIdx[i];
|
| 165 |
-
{
|
| 166 |
-
if (vi != SENTINEL_IDX && (v {{ cmp }} outVal || (v == outVal && (outIdx == SENTINEL_IDX || vi > outIdx)))) {
|
| 167 |
-
{%- else %}
|
| 168 |
-
if (vi != SENTINEL_IDX && (v {{ cmp }} outVal || (v == outVal && vi < outIdx))) {
|
| 169 |
-
{%- endif %}
|
| 170 |
outVal = v;
|
| 171 |
outIdx = vi;
|
| 172 |
}
|
| 173 |
}
|
| 174 |
-
let foldVal =
|
|
|
|
| 175 |
winnerIdx = subgroupMax(select(0u, outIdx, outVal == foldVal && outIdx != SENTINEL_IDX));
|
| 176 |
-
{%
|
| 177 |
winnerIdx = subgroupMin(select(SENTINEL_IDX, outIdx, outVal == foldVal));
|
| 178 |
-
{%
|
| 179 |
}
|
| 180 |
if (tid == 0u) {
|
| 181 |
y[row] = select(winnerIdx, 0u, winnerIdx == SENTINEL_IDX);
|
| 182 |
}
|
| 183 |
-
{%
|
| 184 |
{% else %}
|
| 185 |
// No-subgroup tier: workgroup barrier tree-reduction over the per-thread
|
| 186 |
// (bestVal, bestIdx) pairs. Same ONNX tie rule (lowest index by default,
|
|
@@ -195,11 +179,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
|
| 195 |
let v = wgVal[tid + stride];
|
| 196 |
let vi = wgIdx[tid + stride];
|
| 197 |
let curIdx = wgIdx[tid];
|
| 198 |
-
{
|
| 199 |
-
if (vi != SENTINEL_IDX && (curIdx == SENTINEL_IDX || v {{ cmp }} wgVal[tid] || (v == wgVal[tid] && vi > curIdx))) {
|
| 200 |
-
{%- else %}
|
| 201 |
-
if (vi != SENTINEL_IDX && (curIdx == SENTINEL_IDX || v {{ cmp }} wgVal[tid] || (v == wgVal[tid] && vi < curIdx))) {
|
| 202 |
-
{%- endif %}
|
| 203 |
wgVal[tid] = v;
|
| 204 |
wgIdx[tid] = vi;
|
| 205 |
}
|
|
@@ -209,5 +189,5 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
|
| 209 |
if (tid == 0u) {
|
| 210 |
y[row] = select(wgIdx[0], 0u, wgIdx[0] == SENTINEL_IDX);
|
| 211 |
}
|
| 212 |
-
{%
|
| 213 |
}
|
|
|
|
| 11 |
// integer bounds; ties against the sentinel use the same index rule, so a row
|
| 12 |
// filled with the sentinel still produces the correct index. NaNs are never
|
| 13 |
// accepted, and an all-NaN row returns index zero.
|
| 14 |
+
{% set useSubgroups = useSubgroups | default(true) %}
|
| 15 |
+
{% set singleSubgroup = useSubgroups and singleSubgroup | default(false) %}
|
| 16 |
+
{% set adjacent = adjacentScalarLoads | default(false) %}
|
| 17 |
+
{% set cmpType = scalar if scalar == "i32" or scalar == "u32" else "f32" %}
|
| 18 |
+
{% set sgReduce = "subgroupMax" %}
|
| 19 |
+
{% if cmpType == "i32" %}
|
| 20 |
+
{% set identityVal = "-2147483647i - 1i" %}
|
| 21 |
+
{% elif cmpType == "u32" %}
|
| 22 |
+
{% set identityVal = "0u" %}
|
| 23 |
+
{% else %}
|
| 24 |
+
{% set identityVal = "negative_infinity()" %}
|
| 25 |
+
{% endif %}
|
| 26 |
+
{% macro arg_wins(v, i, best, bestIdx) %}{{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ "(" ~ bestIdx ~ " == SENTINEL_IDX || " ~ i ~ " > " ~ bestIdx ~ ")" if selectLastIndex else i ~ " < " ~ bestIdx }}){% endmacro %}
|
| 27 |
+
{% macro arg_merge_wins(v, i, best, bestIdx) %}{{ i }} != SENTINEL_IDX && ({{ bestIdx }} == SENTINEL_IDX || {{ v }} {{ ">" if modeSpec == "max" else "<" }} {{ best }} || ({{ v }} == {{ best }} && {{ i }} {{ ">" if selectLastIndex else "<" }} {{ bestIdx }})){% endmacro %}
|
| 28 |
{% if useSubgroups %}
|
| 29 |
enable subgroups;
|
| 30 |
+
{% endif %}
|
| 31 |
{{ env.wgsl.resourceDeclarations }}
|
| 32 |
|
| 33 |
const WG: u32 = {{ workgroupSize }}u;
|
| 34 |
const SENTINEL_IDX: u32 = 4294967295u;
|
| 35 |
+
{% if cmpType == "f32" and modeSpec == "max" %}
|
| 36 |
|
| 37 |
+
fn negative_infinity() -> f32 {
|
| 38 |
var bits = 0xff800000u;
|
| 39 |
return bitcast<f32>(bits);
|
| 40 |
}
|
| 41 |
+
{% elif cmpType == "f32" %}
|
| 42 |
|
| 43 |
+
fn pos_inf_f32() -> f32 {
|
| 44 |
+
var bits = 0x7f800000u;
|
| 45 |
+
return bitcast<f32>(bits);
|
| 46 |
+
}
|
| 47 |
{% endif %}
|
| 48 |
+
{% if not singleSubgroup %}
|
| 49 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
var<workgroup> wgVal: array<{{ cmpType }}, WG>;
|
| 51 |
var<workgroup> wgIdx: array<u32, WG>;
|
| 52 |
+
{% endif %}
|
| 53 |
|
| 54 |
@compute @workgroup_size(WG, 1, 1)
|
| 55 |
fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
| 56 |
+
@builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups and not singleSubgroup %},
|
| 57 |
+
@builtin(subgroup_invocation_id) sgLane: u32,
|
| 58 |
@builtin(subgroup_size) sgSize: u32{% endif %}) {
|
| 59 |
let row = wg.x + wg.y * params.rowStride;
|
| 60 |
if (row >= params.rows) {
|
| 61 |
return;
|
| 62 |
}
|
| 63 |
let tid = lid.x;
|
| 64 |
+
let base = row * params.chunkCount;
|
| 65 |
+
var bestVal: {{ cmpType }} = {{ identityVal }};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
var bestIdx: u32 = SENTINEL_IDX;
|
| 67 |
+
{% if vec4 %}
|
| 68 |
for (var c = tid; c < params.chunkCount; c = c + WG) {
|
| 69 |
let v4 = x[base + c];
|
| 70 |
+
{% for comp in ["x", "y", "z", "w"] %}
|
| 71 |
{
|
| 72 |
+
let v = {{ "f32(v4." ~ comp ~ ")" if scalar == "f16" else "v4." ~ comp }};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
let idx = c * 4u + {{ loop.index0 }}u;
|
| 74 |
+
if ({{ arg_wins("v", "idx", "bestVal", "bestIdx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
bestVal = v;
|
| 76 |
bestIdx = idx;
|
| 77 |
}
|
| 78 |
}
|
| 79 |
+
{% endfor %}
|
| 80 |
}
|
| 81 |
+
{% else %}
|
| 82 |
+
// Scan four bounded scalar values per lane. For long rows the manifest may
|
| 83 |
+
// assign adjacent values to a lane; otherwise the four planes stay WG apart.
|
| 84 |
+
// Both layouts cover each input exactly once and use the same tie comparison.
|
| 85 |
+
for (var c0 = {{ "4u * tid" if adjacent else "tid" }}; c0 < params.chunkCount; c0 = c0 + 4u * WG) {
|
| 86 |
+
{% for plane in [0, 1, 2, 3] %}
|
|
|
|
| 87 |
{
|
| 88 |
+
let c = c0 + {{ plane }}u{{ "" if adjacent else " * WG" }};
|
| 89 |
if (c < params.chunkCount) {
|
| 90 |
+
let v = {{ "f32(x[base + c])" if scalar == "f16" else "x[base + c]" }};
|
| 91 |
+
if ({{ arg_wins("v", "c", "bestVal", "bestIdx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
bestVal = v;
|
| 93 |
bestIdx = c;
|
| 94 |
}
|
| 95 |
}
|
| 96 |
}
|
| 97 |
+
{% endfor %}
|
| 98 |
}
|
| 99 |
+
{% endif %}
|
| 100 |
+
{% if useSubgroups %}
|
| 101 |
+
let m = {{ sgReduce }}(bestVal);
|
| 102 |
+
{% if selectLastIndex %}
|
| 103 |
// Lanes that match the subgroup best contribute their (real) index; the
|
| 104 |
// rest contribute 0 so subgroupMax picks the highest matching index.
|
| 105 |
let cand = select(0u, bestIdx, bestVal == m && bestIdx != SENTINEL_IDX);
|
| 106 |
let sgIdx = subgroupMax(cand);
|
| 107 |
+
{% else %}
|
| 108 |
// Lanes that match the subgroup best contribute their index; the rest
|
| 109 |
// contribute the sentinel so subgroupMin picks the lowest matching index.
|
| 110 |
let cand = select(SENTINEL_IDX, bestIdx, bestVal == m);
|
| 111 |
let sgIdx = subgroupMin(cand);
|
| 112 |
+
{% endif %}
|
| 113 |
+
{% if singleSubgroup %}
|
| 114 |
// The workgroup is one (possibly partial) subgroup: elect the writer rather
|
| 115 |
// than assuming lane 0 is active.
|
| 116 |
if (subgroupElect()) {
|
| 117 |
y[row] = select(sgIdx, 0u, sgIdx == SENTINEL_IDX);
|
| 118 |
}
|
| 119 |
+
{% else %}
|
| 120 |
// Cross-subgroup fold that assumes nothing about which invocations share a
|
| 121 |
+
// subgroup or how many subgroups there are (it does require the uniform
|
| 122 |
+
// control flow this entry point already has): every invocation owns the slot
|
| 123 |
+
// at its own index, the elected lane
|
| 124 |
// publishes its subgroup pair there and every other lane publishes the
|
| 125 |
// identity pair (the sentinel index, which the merge skips). Each subgroup
|
| 126 |
+
// then folds all WG slots — lane `sgLane` walks slots sgLane, sgLane + sgSize,
|
| 127 |
+
// ... — and one more collective merges the lane partials, so every slot is
|
| 128 |
+
// merged exactly once at any legal width. The merge is commutative and
|
| 129 |
+
// associative (best value, then the tie-break index), so the fold order does
|
| 130 |
+
// not change the result.
|
| 131 |
var winnerIdx = sgIdx;
|
| 132 |
// A one-subgroup workgroup is already fully reduced by the collectives above.
|
| 133 |
// The test reads the `subgroup_size` builtin, which is uniform; a collective's
|
| 134 |
// result is not uniform to WGSL's analysis and may not guard a barrier.
|
| 135 |
if (sgSize != WG) {
|
| 136 |
+
// The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
|
| 137 |
+
// `subgroupAdd(1u)`: those agree with them under this contract, but a driver
|
| 138 |
+
// in the wild answers a claim derived from them with zero and leaves most of
|
| 139 |
+
// the workgroup's slots unclaimed. Every lane of this entry point is active
|
| 140 |
+
// here, so the full subgroup width is the right stride.
|
| 141 |
+
let rank = sgLane;
|
| 142 |
+
let count = sgSize;
|
| 143 |
let leader = rank == 0u;
|
| 144 |
wgVal[tid] = select({{ identityVal }}, m, leader);
|
| 145 |
wgIdx[tid] = select(SENTINEL_IDX, sgIdx, leader);
|
|
|
|
| 149 |
for (var i = rank; i < WG; i = i + count) {
|
| 150 |
let v = wgVal[i];
|
| 151 |
let vi = wgIdx[i];
|
| 152 |
+
if (vi != SENTINEL_IDX && ({{ arg_wins("v", "vi", "outVal", "outIdx") }})) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
outVal = v;
|
| 154 |
outIdx = vi;
|
| 155 |
}
|
| 156 |
}
|
| 157 |
+
let foldVal = {{ sgReduce }}(outVal);
|
| 158 |
+
{% if selectLastIndex %}
|
| 159 |
winnerIdx = subgroupMax(select(0u, outIdx, outVal == foldVal && outIdx != SENTINEL_IDX));
|
| 160 |
+
{% else %}
|
| 161 |
winnerIdx = subgroupMin(select(SENTINEL_IDX, outIdx, outVal == foldVal));
|
| 162 |
+
{% endif %}
|
| 163 |
}
|
| 164 |
if (tid == 0u) {
|
| 165 |
y[row] = select(winnerIdx, 0u, winnerIdx == SENTINEL_IDX);
|
| 166 |
}
|
| 167 |
+
{% endif %}
|
| 168 |
{% else %}
|
| 169 |
// No-subgroup tier: workgroup barrier tree-reduction over the per-thread
|
| 170 |
// (bestVal, bestIdx) pairs. Same ONNX tie rule (lowest index by default,
|
|
|
|
| 179 |
let v = wgVal[tid + stride];
|
| 180 |
let vi = wgIdx[tid + stride];
|
| 181 |
let curIdx = wgIdx[tid];
|
| 182 |
+
if ({{ arg_merge_wins("v", "vi", "wgVal[tid]", "curIdx") }}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
wgVal[tid] = v;
|
| 184 |
wgIdx[tid] = vi;
|
| 185 |
}
|
|
|
|
| 189 |
if (tid == 0u) {
|
| 190 |
y[row] = select(wgIdx[0], 0u, wgIdx[0] == SENTINEL_IDX);
|
| 191 |
}
|
| 192 |
+
{% endif %}
|
| 193 |
}
|
build/webgpu/test.json
CHANGED
|
@@ -864,9 +864,7 @@
|
|
| 864 |
},
|
| 865 |
{
|
| 866 |
"name": "subgroup_min_vec4_rows128x256_ties_first",
|
| 867 |
-
"provenance": {
|
| 868 |
-
"notes": "Route-locks the one-minimum-subgroup vec4 path on variable-width adapters and verifies first-index tie ordering without a workgroup-memory combine."
|
| 869 |
-
},
|
| 870 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 871 |
"inputs": { "x": { "dtype": "float32", "shape": [128, 256], "data": { "kind": "constant", "value": 7.0 } } },
|
| 872 |
"outputs": {
|
|
@@ -875,9 +873,7 @@
|
|
| 875 |
},
|
| 876 |
{
|
| 877 |
"name": "subgroup_min_scalar_rows128x257_ties_last",
|
| 878 |
-
"provenance": {
|
| 879 |
-
"notes": "Route-locks the unaligned one-minimum-subgroup path and verifies select_last_index ordering across all lane-local chunks."
|
| 880 |
-
},
|
| 881 |
"attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
|
| 882 |
"inputs": { "x": { "dtype": "float32", "shape": [128, 257], "data": { "kind": "constant", "value": 7.0 } } },
|
| 883 |
"outputs": {
|
|
@@ -1122,7 +1118,7 @@
|
|
| 1122 |
{
|
| 1123 |
"name": "last_axis_split_vec4_logits_ties_first",
|
| 1124 |
"provenance": {
|
| 1125 |
-
"notes": "A
|
| 1126 |
},
|
| 1127 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 1128 |
"inputs": {
|
|
@@ -1146,7 +1142,7 @@
|
|
| 1146 |
{
|
| 1147 |
"name": "last_axis_split_vec4_i32_negative_rows",
|
| 1148 |
"provenance": {
|
| 1149 |
-
"notes": "
|
| 1150 |
},
|
| 1151 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 1152 |
"inputs": {
|
|
@@ -1163,7 +1159,7 @@
|
|
| 1163 |
{
|
| 1164 |
"name": "last_axis_split_scalar_u32_above_int32_max",
|
| 1165 |
"provenance": {
|
| 1166 |
-
"notes": "
|
| 1167 |
},
|
| 1168 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 1169 |
"inputs": {
|
|
@@ -1264,7 +1260,7 @@
|
|
| 1264 |
{
|
| 1265 |
"name": "subgroup_min_scalar_keepdims_128x197_vit_tokens",
|
| 1266 |
"provenance": {
|
| 1267 |
-
"notes": "
|
| 1268 |
},
|
| 1269 |
"attrs": { "axis": -1 },
|
| 1270 |
"inputs": {
|
|
@@ -1275,6 +1271,122 @@
|
|
| 1275 |
}
|
| 1276 |
},
|
| 1277 |
"outputs": { "y": { "dtype": "uint32", "shape": [128, 1], "tolerance": 0 } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1278 |
}
|
| 1279 |
]
|
| 1280 |
}
|
|
|
|
| 864 |
},
|
| 865 |
{
|
| 866 |
"name": "subgroup_min_vec4_rows128x256_ties_first",
|
| 867 |
+
"provenance": { "notes": "Aligned 256-element rows with equal maxima verify first-index tie ordering." },
|
|
|
|
|
|
|
| 868 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 869 |
"inputs": { "x": { "dtype": "float32", "shape": [128, 256], "data": { "kind": "constant", "value": 7.0 } } },
|
| 870 |
"outputs": {
|
|
|
|
| 873 |
},
|
| 874 |
{
|
| 875 |
"name": "subgroup_min_scalar_rows128x257_ties_last",
|
| 876 |
+
"provenance": { "notes": "Unaligned 257-element rows with equal maxima verify last-index tie ordering." },
|
|
|
|
|
|
|
| 877 |
"attrs": { "axis": 1, "keepdims": 0, "select_last_index": 1 },
|
| 878 |
"inputs": { "x": { "dtype": "float32", "shape": [128, 257], "data": { "kind": "constant", "value": 7.0 } } },
|
| 879 |
"outputs": {
|
|
|
|
| 1118 |
{
|
| 1119 |
"name": "last_axis_split_vec4_logits_ties_first",
|
| 1120 |
"provenance": {
|
| 1121 |
+
"notes": "A 32,768-element row has equal maxima across distant segments; the result must be the first global index."
|
| 1122 |
},
|
| 1123 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 1124 |
"inputs": {
|
|
|
|
| 1142 |
{
|
| 1143 |
"name": "last_axis_split_vec4_i32_negative_rows",
|
| 1144 |
"provenance": {
|
| 1145 |
+
"notes": "A [2, 32768] int32 tensor's last axis cycles seven negative values (-9, -5, -3, -1, -2, -4, -6), so a zero-initialized accumulator would misreport the maximum. Since 32768 is not a multiple of 7, row 1 begins one step into the cycle, putting its first-occurrence maximum at column 2 versus row 0's column 3."
|
| 1146 |
},
|
| 1147 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 1148 |
"inputs": {
|
|
|
|
| 1159 |
{
|
| 1160 |
"name": "last_axis_split_scalar_u32_above_int32_max",
|
| 1161 |
"provenance": {
|
| 1162 |
+
"notes": "A [2, 32769] uint32 tensor's last axis cycles seven values; six of seven exceed 2^31 (up to 4,294,967,290, near UINT32_MAX), so a signed comparison would misorder them. Since 32769 is not a multiple of 7, row 1 begins two steps into the cycle, moving its maximum's column from 3 (row 0) to 1 (row 1)."
|
| 1163 |
},
|
| 1164 |
"attrs": { "axis": 1, "keepdims": 0 },
|
| 1165 |
"inputs": {
|
|
|
|
| 1260 |
{
|
| 1261 |
"name": "subgroup_min_scalar_keepdims_128x197_vit_tokens",
|
| 1262 |
"provenance": {
|
| 1263 |
+
"notes": "A 197-element last axis across 128 rows checks an unaligned reduction and default keepdims."
|
| 1264 |
},
|
| 1265 |
"attrs": { "axis": -1 },
|
| 1266 |
"inputs": {
|
|
|
|
| 1271 |
}
|
| 1272 |
},
|
| 1273 |
"outputs": { "y": { "dtype": "uint32", "shape": [128, 1], "tolerance": 0 } }
|
| 1274 |
+
},
|
| 1275 |
+
{
|
| 1276 |
+
"name": "ort_wide_last_axis_2x202048_keepdims_negative_axis",
|
| 1277 |
+
"provenance": {
|
| 1278 |
+
"source": "onnxruntime/test/providers/cpu/reduction/reduction_ops_test.cc",
|
| 1279 |
+
"test": "ReductionOpTest.ArgMax_float_wide_last_axis_keepdims",
|
| 1280 |
+
"notes": "Vocabulary-scale last axis with keepdims and a negative axis; upstream added it so the CUDA multi-block scratch path is covered end to end. 2 x 202048 float32 = 1.6 MB."
|
| 1281 |
+
},
|
| 1282 |
+
"attrs": { "axis": -1, "keepdims": 1 },
|
| 1283 |
+
"inputs": {
|
| 1284 |
+
"x": {
|
| 1285 |
+
"dtype": "float32",
|
| 1286 |
+
"shape": [2, 202048],
|
| 1287 |
+
"data": {
|
| 1288 |
+
"kind": "cycle",
|
| 1289 |
+
"values": [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5, 16.0, 16.5, 17.0, 17.5, 18.0, 18.5, 19.0, 19.5, 20.0, 20.5, 21.0, 21.5, 22.0, 22.5, 23.0, 23.5, 24.0, 24.5, 25.0, 25.5, 26.0, 26.5, 27.0, 27.5, 28.0, 28.5, 29.0, 29.5, 30.0, 30.5, 31.0, 31.5, 32.0, 32.5, 33.0, 33.5, 34.0, 34.5, 35.0, 35.5, 36.0, 36.5, 37.0, 37.5, 38.0, 38.5, 39.0, 39.5, 40.0, 40.5, 41.0, 41.5, 42.0, 42.5, 43.0, 43.5, 44.0, 44.5, 45.0, 45.5, 46.0, 46.5, 47.0, 47.5, 48.0]
|
| 1290 |
+
}
|
| 1291 |
+
}
|
| 1292 |
+
},
|
| 1293 |
+
"outputs": { "y": { "dtype": "uint32", "shape": [2, 1], "tolerance": 0 } }
|
| 1294 |
+
},
|
| 1295 |
+
{
|
| 1296 |
+
"name": "packed_axis_columns_8192x32_ties_first",
|
| 1297 |
+
"provenance": {
|
| 1298 |
+
"notes": "A [8192, 32] float32 tensor's axis=-2 cycles a 13-value pattern whose maximum, 8.0, repeats twice per period; every column sees that tie across its 8192 rows, and select_last_index=0 resolves each to the earliest matching row."
|
| 1299 |
+
},
|
| 1300 |
+
"tunables": { "WORKGROUP_SIZE": 16 },
|
| 1301 |
+
"attrs": { "axis": -2, "keepdims": 0, "select_last_index": 0 },
|
| 1302 |
+
"inputs": {
|
| 1303 |
+
"x": {
|
| 1304 |
+
"dtype": "float32",
|
| 1305 |
+
"shape": [8192, 32],
|
| 1306 |
+
"data": { "kind": "cycle", "values": [-3.0, 7.0, 7.0, -9.0, 2.0, 8.0, -9.0, 8.0, -1.0, 0.0, 2.0, 7.0, -9.0] }
|
| 1307 |
+
}
|
| 1308 |
+
},
|
| 1309 |
+
"outputs": { "y": { "dtype": "uint32", "shape": [32], "tolerance": 0 } }
|
| 1310 |
+
},
|
| 1311 |
+
{
|
| 1312 |
+
"name": "packed_axis_columns_8192x32_ties_last",
|
| 1313 |
+
"provenance": {
|
| 1314 |
+
"notes": "A [8192, 32] float32 tensor's axis=-2 cycles a 13-value pattern whose maximum, 8.0, repeats twice per period; every column sees that tie across its 8192 rows, and select_last_index=1 resolves each to the latest matching row."
|
| 1315 |
+
},
|
| 1316 |
+
"tunables": { "WORKGROUP_SIZE": 16 },
|
| 1317 |
+
"attrs": { "axis": -2, "keepdims": 0, "select_last_index": 1 },
|
| 1318 |
+
"inputs": {
|
| 1319 |
+
"x": {
|
| 1320 |
+
"dtype": "float32",
|
| 1321 |
+
"shape": [8192, 32],
|
| 1322 |
+
"data": { "kind": "cycle", "values": [-3.0, 7.0, 7.0, -9.0, 2.0, 8.0, -9.0, 8.0, -1.0, 0.0, 2.0, 7.0, -9.0] }
|
| 1323 |
+
}
|
| 1324 |
+
},
|
| 1325 |
+
"outputs": { "y": { "dtype": "uint32", "shape": [32], "tolerance": 0 } }
|
| 1326 |
+
},
|
| 1327 |
+
{
|
| 1328 |
+
"name": "packed_axis_columns_8193x28_ties_first",
|
| 1329 |
+
"provenance": {
|
| 1330 |
+
"notes": "A [8193, 28] float32 tensor's axis=-2 (one row more than the 8192-row case) cycles the same 13-value pattern, whose maximum 8.0 repeats twice per period; keepdims=1 keeps that axis as size 1 in a [1, 28] output, and select_last_index=0 resolves each column's tie to its earliest row."
|
| 1331 |
+
},
|
| 1332 |
+
"tunables": { "WORKGROUP_SIZE": 8 },
|
| 1333 |
+
"attrs": { "axis": -2, "keepdims": 1, "select_last_index": 0 },
|
| 1334 |
+
"inputs": {
|
| 1335 |
+
"x": {
|
| 1336 |
+
"dtype": "float32",
|
| 1337 |
+
"shape": [8193, 28],
|
| 1338 |
+
"data": { "kind": "cycle", "values": [-3.0, 7.0, 7.0, -9.0, 2.0, 8.0, -9.0, 8.0, -1.0, 0.0, 2.0, 7.0, -9.0] }
|
| 1339 |
+
}
|
| 1340 |
+
},
|
| 1341 |
+
"outputs": { "y": { "dtype": "uint32", "shape": [1, 28], "tolerance": 0 } }
|
| 1342 |
+
},
|
| 1343 |
+
{
|
| 1344 |
+
"name": "packed_axis_columns_8193x28_ties_last",
|
| 1345 |
+
"provenance": {
|
| 1346 |
+
"notes": "A [8193, 28] float32 tensor's axis=-2 (one row more than the 8192-row case) cycles the same 13-value pattern, whose maximum 8.0 repeats twice per period; keepdims=1 keeps that axis as size 1 in a [1, 28] output, and select_last_index=1 resolves each column's tie to its latest row."
|
| 1347 |
+
},
|
| 1348 |
+
"tunables": { "WORKGROUP_SIZE": 8 },
|
| 1349 |
+
"attrs": { "axis": -2, "keepdims": 1, "select_last_index": 1 },
|
| 1350 |
+
"inputs": {
|
| 1351 |
+
"x": {
|
| 1352 |
+
"dtype": "float32",
|
| 1353 |
+
"shape": [8193, 28],
|
| 1354 |
+
"data": { "kind": "cycle", "values": [-3.0, 7.0, 7.0, -9.0, 2.0, 8.0, -9.0, 8.0, -1.0, 0.0, 2.0, 7.0, -9.0] }
|
| 1355 |
+
}
|
| 1356 |
+
},
|
| 1357 |
+
"outputs": { "y": { "dtype": "uint32", "shape": [1, 28], "tolerance": 0 } }
|
| 1358 |
+
},
|
| 1359 |
+
{
|
| 1360 |
+
"name": "packed_axis_columns_2x8193x12_ties_first",
|
| 1361 |
+
"provenance": {
|
| 1362 |
+
"notes": "A [2, 8193, 12] float32 tensor's axis=-2 (the 8193-length middle axis) cycles the same 13-value pattern for each of the 2 batches' 12 columns; the maximum 8.0 repeats twice per period, and select_last_index=0 resolves each tie to its earliest row, giving a [2, 12] output."
|
| 1363 |
+
},
|
| 1364 |
+
"tunables": { "WORKGROUP_SIZE": 8 },
|
| 1365 |
+
"attrs": { "axis": -2, "keepdims": 0, "select_last_index": 0 },
|
| 1366 |
+
"inputs": {
|
| 1367 |
+
"x": {
|
| 1368 |
+
"dtype": "float32",
|
| 1369 |
+
"shape": [2, 8193, 12],
|
| 1370 |
+
"data": { "kind": "cycle", "values": [-3.0, 7.0, 7.0, -9.0, 2.0, 8.0, -9.0, 8.0, -1.0, 0.0, 2.0, 7.0, -9.0] }
|
| 1371 |
+
}
|
| 1372 |
+
},
|
| 1373 |
+
"outputs": { "y": { "dtype": "uint32", "shape": [2, 12], "tolerance": 0 } }
|
| 1374 |
+
},
|
| 1375 |
+
{
|
| 1376 |
+
"name": "packed_axis_columns_2x8193x12_ties_last",
|
| 1377 |
+
"provenance": {
|
| 1378 |
+
"notes": "A [2, 8193, 12] float32 tensor's axis=-2 (the 8193-length middle axis) cycles the same 13-value pattern for each of the 2 batches' 12 columns; the maximum 8.0 repeats twice per period, and select_last_index=1 resolves each tie to its latest row, giving a [2, 12] output."
|
| 1379 |
+
},
|
| 1380 |
+
"tunables": { "WORKGROUP_SIZE": 8 },
|
| 1381 |
+
"attrs": { "axis": -2, "keepdims": 0, "select_last_index": 1 },
|
| 1382 |
+
"inputs": {
|
| 1383 |
+
"x": {
|
| 1384 |
+
"dtype": "float32",
|
| 1385 |
+
"shape": [2, 8193, 12],
|
| 1386 |
+
"data": { "kind": "cycle", "values": [-3.0, 7.0, 7.0, -9.0, 2.0, 8.0, -9.0, 8.0, -1.0, 0.0, 2.0, 7.0, -9.0] }
|
| 1387 |
+
}
|
| 1388 |
+
},
|
| 1389 |
+
"outputs": { "y": { "dtype": "uint32", "shape": [2, 12], "tolerance": 0 } }
|
| 1390 |
}
|
| 1391 |
]
|
| 1392 |
}
|