Xenova HF Staff commited on
Commit
05aa647
·
verified ·
1 Parent(s): 9c454f7

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -50,18 +50,27 @@ Attributes and default values (overridable per request):
50
  | `T` | `float32`, `float16`, `int8`, `uint8` |
51
  | `I` | `int64` |
52
 
 
 
 
 
 
 
 
 
53
  ## Files
54
 
55
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
56
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
57
  - [`test.json`](build/webgpu/test.json) — correctness cases
58
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
59
  - [`max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja`](build/webgpu/max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja)
60
  - [`max-pool2d-nchw-u32.wgsl.jinja`](build/webgpu/max-pool2d-nchw-u32.wgsl.jinja)
61
  - [`max-pool3d-ncdhw-k5-tiled.wgsl.jinja`](build/webgpu/max-pool3d-ncdhw-k5-tiled.wgsl.jinja)
62
  - [`pool-global-reduction.wgsl.jinja`](build/webgpu/pool-global-reduction.wgsl.jinja)
63
  - [`pool-ncl1d-x4.wgsl.jinja`](build/webgpu/pool-ncl1d-x4.wgsl.jinja)
64
  - [`pool-window-nd.wgsl.jinja`](build/webgpu/pool-window-nd.wgsl.jinja)
 
65
  - [`pool-window-unroll.wgsl.jinja`](build/webgpu/pool-window-unroll.wgsl.jinja)
66
  - [`pool2d-nchw-k2s2-vec4.wgsl.jinja`](build/webgpu/pool2d-nchw-k2s2-vec4.wgsl.jinja)
67
  - [`pool2d-nchw-separable.wgsl.jinja`](build/webgpu/pool2d-nchw-separable.wgsl.jinja)
@@ -69,7 +78,7 @@ Attributes and default values (overridable per request):
69
  ## Use with `@huggingface/kernels`
70
 
71
  ```sh
72
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
73
  ```
74
 
75
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
50
  | `T` | `float32`, `float16`, `int8`, `uint8` |
51
  | `I` | `int64` |
52
 
53
+ ## Implementation variants
54
+
55
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
56
+
57
+ - `window_parallel_ncl1d` — Workgroup-cooperative max pooling over a 1-D window: one workgroup reduces a single output element's kernel window, its invocations striding the flattened taps. Chosen when the serial one-invocation-per-output route cannot fill the device and the window is long. NaNs propagate through the fold; an all-out-of-bounds window writes 0.
58
+ - `window_parallel_nchw2d` — Workgroup-cooperative max pooling over a 2-D window: one workgroup reduces a single output element's kernel window, its invocations striding the flattened taps. Chosen when the serial one-invocation-per-output route cannot fill the device and the window is long. NaNs propagate through the fold; an all-out-of-bounds window writes 0.
59
+ - `window_parallel_ncdhw3d` — Workgroup-cooperative max pooling over a 3-D window: one workgroup reduces a single output element's kernel window, its invocations striding the flattened taps. Chosen when the serial one-invocation-per-output route cannot fill the device and the window is long. NaNs propagate through the fold; an all-out-of-bounds window writes 0.
60
+
61
  ## Files
62
 
63
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
64
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
65
  - [`test.json`](build/webgpu/test.json) — correctness cases
66
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
67
  - [`max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja`](build/webgpu/max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja)
68
  - [`max-pool2d-nchw-u32.wgsl.jinja`](build/webgpu/max-pool2d-nchw-u32.wgsl.jinja)
69
  - [`max-pool3d-ncdhw-k5-tiled.wgsl.jinja`](build/webgpu/max-pool3d-ncdhw-k5-tiled.wgsl.jinja)
70
  - [`pool-global-reduction.wgsl.jinja`](build/webgpu/pool-global-reduction.wgsl.jinja)
71
  - [`pool-ncl1d-x4.wgsl.jinja`](build/webgpu/pool-ncl1d-x4.wgsl.jinja)
72
  - [`pool-window-nd.wgsl.jinja`](build/webgpu/pool-window-nd.wgsl.jinja)
73
+ - [`pool-window-reduction.wgsl.jinja`](build/webgpu/pool-window-reduction.wgsl.jinja)
74
  - [`pool-window-unroll.wgsl.jinja`](build/webgpu/pool-window-unroll.wgsl.jinja)
75
  - [`pool2d-nchw-k2s2-vec4.wgsl.jinja`](build/webgpu/pool2d-nchw-k2s2-vec4.wgsl.jinja)
76
  - [`pool2d-nchw-separable.wgsl.jinja`](build/webgpu/pool2d-nchw-separable.wgsl.jinja)
 
78
  ## Use with `@huggingface/kernels`
79
 
80
  ```sh
81
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
82
  ```
83
 
84
  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
@@ -166,7 +166,7 @@
166
  {
167
  "name": "maxpool-f32-b1c512-20x20-k5s1p2-separable-floor",
168
  "provenance": {
169
- "notes": "A YOLO-class detector shape with a 5x5 window exercises the minimum window size admitted by the separable route."
170
  },
171
  "attrs": { "kernel_shape": [5, 5], "strides": [1, 1], "pads": [2, 2, 2, 2] },
172
  "inputs": { "x": { "dtype": "float32", "shape": [1, 512, 20, 20], "dist": "uniform" } },
@@ -187,6 +187,63 @@
187
  "inputs": { "x": { "dtype": "float32", "shape": [8, 64, 56, 56] } },
188
  "outputs": { "y": { "dtype": "float32", "shape": [8, 64, 8, 8] } },
189
  "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
  ],
192
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] }
 
166
  {
167
  "name": "maxpool-f32-b1c512-20x20-k5s1p2-separable-floor",
168
  "provenance": {
169
+ "notes": "Measures max pooling with a 5x5 window and padding 2 over 512 channels of a 20x20 YOLO-class detector feature map, keeping the spatial size unchanged."
170
  },
171
  "attrs": { "kernel_shape": [5, 5], "strides": [1, 1], "pads": [2, 2, 2, 2] },
172
  "inputs": { "x": { "dtype": "float32", "shape": [1, 512, 20, 20], "dist": "uniform" } },
 
187
  "inputs": { "x": { "dtype": "float32", "shape": [8, 64, 56, 56] } },
188
  "outputs": { "y": { "dtype": "float32", "shape": [8, 64, 8, 8] } },
189
  "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
190
+ },
191
+ {
192
+ "name": "max-pool-f32-b1c16-24x24-k12s1p2-window-parallel-starved",
193
+ "preset": "stress",
194
+ "provenance": {
195
+ "source": "synthetic benchmark",
196
+ "notes": "Measures max pooling with a 12x12 window and padding 2 over 16 channels of a 24x24 input, producing a 17x17 (289-element) output per channel."
197
+ },
198
+ "attrs": { "kernel_shape": [12, 12], "strides": [1, 1], "pads": [2, 2, 2, 2] },
199
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 16, 24, 24], "dist": "normal", "seed": 4311, "scale": 0.2 } },
200
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 17, 17], "dist": "empty" } },
201
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
202
+ },
203
+ {
204
+ "name": "max-pool-f32-b1c16-56x56-k28s28-window-parallel-extreme-starved",
205
+ "preset": "stress",
206
+ "provenance": {
207
+ "source": "synthetic benchmark",
208
+ "notes": "A 784-tap non-overlapping window leaving 64 outputs. The separable route needs strideH < kernelH, so the serial route is the only alternative here."
209
+ },
210
+ "attrs": { "kernel_shape": [28, 28], "strides": [28, 28], "pads": [0, 0, 0, 0] },
211
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 16, 56, 56], "dist": "normal", "seed": 4312, "scale": 0.2 } },
212
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 2, 2], "dist": "empty" } },
213
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
214
+ },
215
+ {
216
+ "name": "max-pool-f32-b1c32-24x24-k12s1p2-serial-side-of-gate",
217
+ "preset": "stress",
218
+ "provenance": {
219
+ "source": "synthetic benchmark",
220
+ "notes": "A 9,248-element output measures pooling over many independent windows."
221
+ },
222
+ "attrs": { "kernel_shape": [12, 12], "strides": [1, 1], "pads": [2, 2, 2, 2] },
223
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 32, 24, 24], "dist": "normal", "seed": 4313, "scale": 0.2 } },
224
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 17, 17], "dist": "empty" } },
225
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
226
+ },
227
+ {
228
+ "name": "occupancy-window_parallel_nchw2d_small_output",
229
+ "preset": "stress",
230
+ "attrs": { "kernel_shape": [12, 12], "strides": [2, 2], "pads": [2, 2, 2, 2], "ceil_mode": 0 },
231
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 24, 24], "dist": "normal", "seed": 5482, "scale": 0.2 } },
232
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 9, 9] } }
233
+ },
234
+ {
235
+ "name": "occupancy-window_parallel_outputs_to_taps_boundary_128",
236
+ "preset": "stress",
237
+ "attrs": { "kernel_shape": [128], "strides": [1] },
238
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 255], "dist": "normal", "seed": 5482, "scale": 0.2 } },
239
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 128] } }
240
+ },
241
+ {
242
+ "name": "occupancy-window_parallel_outputs_to_taps_boundary_129",
243
+ "preset": "stress",
244
+ "attrs": { "kernel_shape": [128], "strides": [1] },
245
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 256], "dist": "normal", "seed": 5482, "scale": 0.2 } },
246
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 129] } }
247
  }
248
  ],
249
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] }
build/webgpu/manifest.json CHANGED
@@ -36,7 +36,9 @@
36
  "TILED_H": { "default": 8 },
37
  "TILED_W": { "default": 8 },
38
  "TILED_LANES_D": { "default": 4 },
39
- "SEPARABLE_MIN_WINDOW": { "default": 25 }
 
 
40
  },
41
  "derive": {
42
  "spatialRank": "ranks.x - 2",
@@ -59,7 +61,6 @@
59
  "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
60
  "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
61
  "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
62
- "poolShapeOk": "ranks.x >= 3 and ranks.x <= 5 and ranks.y == ranks.x and has(attrs, \"kernel_shape\") and kernelShapeLengthOk and stridesLengthOk and dilationsLengthOk and padsLengthOk and kernelD > 0 and kernelH > 0 and kernelW > 0 and strideD > 0 and strideH > 0 and strideW > 0 and dilationD > 0 and dilationH > 0 and dilationW > 0 and padFront >= 0 and padTop >= 0 and padLeft >= 0 and padBack >= 0 and padBottom >= 0 and padRight >= 0 and (attrs.auto_pad == \"NOTSET\" or not has(attrs, \"pads\")) and dim(shapes.y, 0) == dim(shapes.x, 0) and dim(shapes.y, 1) == dim(shapes.x, 1)",
63
  "explicitOutW": "min(ceil((dim(shapes.x, ranks.x - 1) + padLeft + padRight - ((kernelW - 1) * dilationW + 1)) / strideW) + 1, ceil((dim(shapes.x, ranks.x - 1) + padLeft) / strideW)) if attrs.ceil_mode != 0 else floor((dim(shapes.x, ranks.x - 1) + padLeft + padRight - ((kernelW - 1) * dilationW + 1)) / strideW) + 1",
64
  "explicitOutH": "min(ceil((dim(shapes.x, ranks.x - 2) + padTop + padBottom - ((kernelH - 1) * dilationH + 1)) / strideH) + 1, ceil((dim(shapes.x, ranks.x - 2) + padTop) / strideH)) if attrs.ceil_mode != 0 else floor((dim(shapes.x, ranks.x - 2) + padTop + padBottom - ((kernelH - 1) * dilationH + 1)) / strideH) + 1",
65
  "explicitOutD": "(min(ceil((dim(shapes.x, 2) + padFront + padBack - ((kernelD - 1) * dilationD + 1)) / strideD) + 1, ceil((dim(shapes.x, 2) + padFront) / strideD)) if attrs.ceil_mode != 0 else floor((dim(shapes.x, 2) + padFront + padBack - ((kernelD - 1) * dilationD + 1)) / strideD) + 1) if ranks.x == 5 else 0",
@@ -67,25 +68,27 @@
67
  "inferredOutH": "ceil(dim(shapes.x, ranks.x - 2) / strideH) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutH",
68
  "inferredOutD": "ceil(dim(shapes.x, 2) / strideD) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutD",
69
  "poolOutputShape": "[dim(shapes.x, 0), dim(shapes.x, 1), inferredOutW] if ranks.x == 3 else ([dim(shapes.x, 0), dim(shapes.x, 1), inferredOutH, inferredOutW] if ranks.x == 4 else [dim(shapes.x, 0), dim(shapes.x, 1), inferredOutD, inferredOutH, inferredOutW])",
70
- "pool2dOutputShapeOk": "(dim(shapes.y, 2) == explicitOutH and dim(shapes.y, 3) == explicitOutW) or ((attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") and dim(shapes.y, 2) == ceil(dim(shapes.x, 2) / strideH) and dim(shapes.y, 3) == ceil(dim(shapes.x, 3) / strideW))",
71
  "autoPadTotalH": "max(0, (dim(shapes.y, ranks.x - 2) - 1) * strideH + ((kernelH - 1) * dilationH + 1) - dim(shapes.x, ranks.x - 2)) if ranks.x >= 4 else 0",
72
  "autoPadTotalW": "max(0, (dim(shapes.y, ranks.x - 1) - 1) * strideW + ((kernelW - 1) * dilationW + 1) - dim(shapes.x, ranks.x - 1))",
 
73
  "effectivePadH": "(floor(autoPadTotalH / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalH - floor(autoPadTotalH / 2)) if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padTop",
74
  "effectivePadW": "(floor(autoPadTotalW / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalW - floor(autoPadTotalW / 2)) if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padLeft",
 
 
 
 
 
 
75
  "floatDtypeOk": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T)",
76
  "globalWindowParallelCovered": "not present.indices and floatDtypeOk and poolShapeOk and numel(shapes.y) == dim(shapes.x, 0) * dim(shapes.x, 1) and numel(shapes.y) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and attrs.auto_pad == \"NOTSET\" and strideD == 1 and strideH == 1 and strideW == 1 and dilationD == 1 and dilationH == 1 and dilationW == 1 and padFront == 0 and padTop == 0 and padLeft == 0 and padBack == 0 and padBottom == 0 and padRight == 0 and ((ranks.x == 3 and kernelW == dim(shapes.x, 2)) or (ranks.x == 4 and kernelH == dim(shapes.x, 2) and kernelW == dim(shapes.x, 3)) or (ranks.x == 5 and kernelD == dim(shapes.x, 2) and kernelH == dim(shapes.x, 3) and kernelW == dim(shapes.x, 4))) and inner(shapes.x, 1) >= 256",
77
  "tiledStorageBytes": "(tunables.TILED_D + kernelD - 1) * (tunables.TILED_H + kernelH - 1) * (tunables.TILED_W + kernelW - 1) * 4",
78
  "tiledDeviceOk": "tiledStorageBytes <= device.limits.maxComputeWorkgroupStorageSize and tunables.TILED_W * tunables.TILED_H * tunables.TILED_LANES_D <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.TILED_W <= device.limits.maxComputeWorkgroupSizeX and tunables.TILED_H <= device.limits.maxComputeWorkgroupSizeY and tunables.TILED_LANES_D <= device.limits.maxComputeWorkgroupSizeZ",
79
- "tiledDispatchOk": "ceilDiv(dim(shapes.y, 4), tunables.TILED_W) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(dim(shapes.y, 3), tunables.TILED_H) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dim(shapes.x, 0) * dim(shapes.x, 1) * ceilDiv(dim(shapes.y, 2), tunables.TILED_D) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
80
- "separableTmpElements": "dim(shapes.x, 0) * dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.y, 3)",
81
- "separableFits": "separableTmpElements * 4 <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize) and ceilDiv(separableTmpElements, tunables.WORKGROUP_SIZE) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) * min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
82
- "autoPadTotalD": "max(0, (dim(shapes.y, 2) - 1) * strideD + ((kernelD - 1) * dilationD + 1) - dim(shapes.x, 2)) if ranks.x == 5 else 0",
83
- "effectivePadD": "(floor(autoPadTotalD / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalD - floor(autoPadTotalD / 2)) if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padFront"
84
  },
85
  "bindings": {
86
- "x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "$xVec4" },
87
  "params": {
88
- "buffer": "uniform",
89
  "struct": [
90
  { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
91
  { "name": "inW", "type": "u32", "value": "dim(shapes.x, 2)" },
@@ -97,9 +100,8 @@
97
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
98
  ]
99
  },
100
- "params_7": {
101
  "name": "params",
102
- "buffer": "uniform",
103
  "struct": [
104
  { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
105
  { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
@@ -116,6 +118,54 @@
116
  { "name": "padW", "type": "i32", "value": "effectivePadW" },
117
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
118
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  }
120
  },
121
  "variants": [
@@ -132,9 +182,7 @@
132
  "id": "main",
133
  "name": "MaxPool.GlobalWindowParallel",
134
  "shader": "pool-global-reduction.wgsl.jinja",
135
- "subgroupCollectivesWidth": "portable",
136
  "derive": {
137
- "op": "\"max\"",
138
  "vec4": "inner(shapes.x, 1) % 4 == 0",
139
  "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
140
  "useSubgroups": "device.features.has(\"subgroups\")"
@@ -148,11 +196,83 @@
148
  }
149
  ]
150
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  {
152
  "id": "nchw2d_k3s2p1_vec4",
153
  "priority": 35,
154
  "when": ["not present.indices", "f16Ok(dtypes.T)", "dtypes.T == \"f16\"", "poolShapeOk", "ranks.x == 4", "ranks.y == 4", "attrs.auto_pad == \"NOTSET\"", "kernelH == 3", "kernelW == 3", "strideH == 2", "strideW == 2", "dilationH == 1", "dilationW == 1", "padTop == 1", "padLeft == 1", "padBottom == 1", "padRight == 1", "attrs.ceil_mode == 0", "dim(shapes.x, 2) % 2 == 0", "dim(shapes.x, 3) % 8 == 0", "dim(shapes.y, 0) == dim(shapes.x, 0)", "dim(shapes.y, 1) == dim(shapes.x, 1)", "dim(shapes.y, 2) == dim(shapes.x, 2) / 2", "dim(shapes.y, 3) == dim(shapes.x, 3) / 2", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) >= 4"],
155
- "derive": { "scalar": "dtypes.T", "xVec4": "\"vec4<f16>\"", "yVec4": "\"vec4<f16>\"" },
156
  "passes": [
157
  {
158
  "id": "main",
@@ -166,7 +286,7 @@
166
  "outBlocks": "dim(shapes.y, 3) / 4"
167
  },
168
  "bindings": [
169
- "x_2",
170
  { "arg": "y", "elementType": "$yVec4" },
171
  { "name": "params", "struct": [{ "name": "tileCount", "type": "u32", "value": "numel(shapes.y) / 4" }] }
172
  ],
@@ -189,7 +309,6 @@
189
  "name": "MaxPool.Nchw2dK2s2Vec4",
190
  "shader": "pool2d-nchw-k2s2-vec4.wgsl.jinja",
191
  "derive": {
192
- "op": "\"max\"",
193
  "outH": "dim(shapes.y, 2)",
194
  "outW": "dim(shapes.y, 3)",
195
  "outWHalf": "floor(dim(shapes.y, 3) / 2)",
@@ -197,7 +316,7 @@
197
  "inPlaneVec4": "floor(dim(shapes.x, 2) * dim(shapes.x, 3) / 4)"
198
  },
199
  "bindings": [
200
- "x_2",
201
  "y",
202
  { "name": "params", "struct": [{ "name": "tileCount", "type": "u32", "value": "numel(shapes.y) / 2" }] }
203
  ],
@@ -220,7 +339,6 @@
220
  "name": "MaxPool.Nchw2dUnrolled",
221
  "shader": "pool-window-unroll.wgsl.jinja",
222
  "derive": {
223
- "op": "\"max\"",
224
  "kernelHSpec": "kernelH",
225
  "kernelWSpec": "kernelW",
226
  "strideHSpec": "strideH",
@@ -257,9 +375,9 @@
257
  "id": "main",
258
  "name": "MaxPool.Ncl1dK2s2X4",
259
  "shader": "pool-ncl1d-x4.wgsl.jinja",
260
- "derive": { "op": "\"max\"", "inW": "dim(shapes.x, 2)", "outW": "dim(shapes.y, 2)" },
261
  "bindings": [
262
- "x_2",
263
  "y",
264
  {
265
  "name": "params",
@@ -290,7 +408,7 @@
290
  "id": "main",
291
  "name": "MaxPool.Ncl1d",
292
  "shader": "pool-window-nd.wgsl.jinja",
293
- "derive": { "op": "\"max\"", "spatialRankSpec": 1 },
294
  "bindings": ["x", "y", "params"],
295
  "dispatch": {
296
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -303,13 +421,13 @@
303
  {
304
  "id": "nchw2d_uint8",
305
  "priority": 20,
306
- "when": ["not present.indices", "tensorDtypes.x == \"uint8\"", "tensorDtypes.y == \"uint8\"", "poolShapeOk", "ranks.x == 4", "dim(shapes.y, 2) == explicitOutH", "dim(shapes.y, 3) == explicitOutW"],
307
  "passes": [
308
  {
309
  "id": "main",
310
- "name": "nchw2d_uint8",
311
  "shader": "max-pool2d-nchw-u32.wgsl.jinja",
312
- "bindings": ["x", "y", "params_7"],
313
  "dispatch": {
314
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
315
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -322,17 +440,17 @@
322
  "id": "nchw2d_separable",
323
  "priority": 22,
324
  "when": ["not present.indices", "f16Ok(dtypes.T) and dtypes.T != \"u32\"", "poolShapeOk", "ranks.x == 4", "ranks.y == 4", "pool2dOutputShapeOk", "kernelH > 1", "kernelW > 1", "kernelH * kernelW >= tunables.SEPARABLE_MIN_WINDOW", "strideH < kernelH", "separableFits"],
325
- "derive": { "scalar": "dtypes.T", "pIs1": false, "pIs2": false },
326
  "intermediates": [{ "id": "tmp", "dtype": "float32", "shape": "[separableTmpElements]" }],
327
  "passes": [
328
  {
329
  "id": "reduce_w",
330
  "name": "MaxPool.SeparableW",
331
  "shader": "pool2d-nchw-separable.wgsl.jinja",
332
- "derive": { "axisSpec": "\"w\"", "op": "\"max\"", "windowArea": "kernelH * kernelW" },
333
  "bindings": [
334
  "x",
335
- { "name": "tmp", "buffer": "storage", "elementType": "f32" },
336
  {
337
  "name": "params",
338
  "struct": [
@@ -361,7 +479,7 @@
361
  "id": "reduce_h",
362
  "name": "MaxPool.SeparableH",
363
  "shader": "pool2d-nchw-separable.wgsl.jinja",
364
- "derive": { "axisSpec": "\"h\"", "op": "\"max\"", "windowArea": "kernelH * kernelW" },
365
  "bindings": [
366
  { "name": "tmp", "buffer": "read-only-storage", "elementType": "f32" },
367
  "y",
@@ -399,10 +517,10 @@
399
  "passes": [
400
  {
401
  "id": "main",
402
- "name": "nchw2d",
403
  "shader": "pool-window-nd.wgsl.jinja",
404
- "derive": { "op": "\"max\"", "spatialRankSpec": 2 },
405
- "bindings": ["x", "y", "params_7"],
406
  "dispatch": {
407
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
408
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -454,7 +572,7 @@
454
  "id": "main",
455
  "name": "MaxPool.Ncdhw3d",
456
  "shader": "pool-window-nd.wgsl.jinja",
457
- "derive": { "op": "\"max\"", "spatialRankSpec": 3 },
458
  "bindings": [
459
  "x",
460
  "y",
@@ -502,7 +620,7 @@
502
  "id": "main",
503
  "name": "MaxPool.Ncl1dIndices",
504
  "shader": "pool-window-nd.wgsl.jinja",
505
- "derive": { "op": "\"max\"", "spatialRankSpec": 1 },
506
  "bindings": ["x", "y", "indices", "params"],
507
  "dispatch": {
508
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -515,15 +633,15 @@
515
  {
516
  "id": "nchw2d_uint8_indices",
517
  "priority": 16,
518
- "when": ["present.indices", "tensorDtypes.x == \"uint8\"", "tensorDtypes.y == \"uint8\"", "poolShapeOk", "ranks.x == 4", "dim(shapes.y, 2) == explicitOutH", "dim(shapes.y, 3) == explicitOutW"],
519
  "derive": { "scalar": "\"u32\"", "hasIndices": true, "storageOrder": "attrs.storage_order" },
520
  "passes": [
521
  {
522
  "id": "main",
523
  "name": "MaxPool.Nchw2dUint8Indices",
524
  "shader": "pool-window-nd.wgsl.jinja",
525
- "derive": { "op": "\"max\"", "spatialRankSpec": 2 },
526
- "bindings": ["x", "y", "indices", "params_7"],
527
  "dispatch": {
528
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
529
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -542,8 +660,8 @@
542
  "id": "main",
543
  "name": "MaxPool.Nchw2dIndices",
544
  "shader": "pool-window-nd.wgsl.jinja",
545
- "derive": { "op": "\"max\"", "spatialRankSpec": 2 },
546
- "bindings": ["x", "y", "indices", "params_7"],
547
  "dispatch": {
548
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
549
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
36
  "TILED_H": { "default": 8 },
37
  "TILED_W": { "default": 8 },
38
  "TILED_LANES_D": { "default": 4 },
39
+ "SEPARABLE_MIN_WINDOW": { "default": 25 },
40
+ "WINDOW_PARALLEL_MIN_TAPS": { "default": 128 },
41
+ "WINDOW_PARALLEL_MAX_SERIAL_GROUPS": { "default": 64 }
42
  },
43
  "derive": {
44
  "spatialRank": "ranks.x - 2",
 
61
  "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
62
  "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
63
  "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
 
64
  "explicitOutW": "min(ceil((dim(shapes.x, ranks.x - 1) + padLeft + padRight - ((kernelW - 1) * dilationW + 1)) / strideW) + 1, ceil((dim(shapes.x, ranks.x - 1) + padLeft) / strideW)) if attrs.ceil_mode != 0 else floor((dim(shapes.x, ranks.x - 1) + padLeft + padRight - ((kernelW - 1) * dilationW + 1)) / strideW) + 1",
65
  "explicitOutH": "min(ceil((dim(shapes.x, ranks.x - 2) + padTop + padBottom - ((kernelH - 1) * dilationH + 1)) / strideH) + 1, ceil((dim(shapes.x, ranks.x - 2) + padTop) / strideH)) if attrs.ceil_mode != 0 else floor((dim(shapes.x, ranks.x - 2) + padTop + padBottom - ((kernelH - 1) * dilationH + 1)) / strideH) + 1",
66
  "explicitOutD": "(min(ceil((dim(shapes.x, 2) + padFront + padBack - ((kernelD - 1) * dilationD + 1)) / strideD) + 1, ceil((dim(shapes.x, 2) + padFront) / strideD)) if attrs.ceil_mode != 0 else floor((dim(shapes.x, 2) + padFront + padBack - ((kernelD - 1) * dilationD + 1)) / strideD) + 1) if ranks.x == 5 else 0",
 
68
  "inferredOutH": "ceil(dim(shapes.x, ranks.x - 2) / strideH) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutH",
69
  "inferredOutD": "ceil(dim(shapes.x, 2) / strideD) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutD",
70
  "poolOutputShape": "[dim(shapes.x, 0), dim(shapes.x, 1), inferredOutW] if ranks.x == 3 else ([dim(shapes.x, 0), dim(shapes.x, 1), inferredOutH, inferredOutW] if ranks.x == 4 else [dim(shapes.x, 0), dim(shapes.x, 1), inferredOutD, inferredOutH, inferredOutW])",
71
+ "autoPadTotalD": "max(0, (dim(shapes.y, 2) - 1) * strideD + ((kernelD - 1) * dilationD + 1) - dim(shapes.x, 2)) if ranks.x == 5 else 0",
72
  "autoPadTotalH": "max(0, (dim(shapes.y, ranks.x - 2) - 1) * strideH + ((kernelH - 1) * dilationH + 1) - dim(shapes.x, ranks.x - 2)) if ranks.x >= 4 else 0",
73
  "autoPadTotalW": "max(0, (dim(shapes.y, ranks.x - 1) - 1) * strideW + ((kernelW - 1) * dilationW + 1) - dim(shapes.x, ranks.x - 1))",
74
+ "effectivePadD": "(floor(autoPadTotalD / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalD - floor(autoPadTotalD / 2)) if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padFront",
75
  "effectivePadH": "(floor(autoPadTotalH / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalH - floor(autoPadTotalH / 2)) if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padTop",
76
  "effectivePadW": "(floor(autoPadTotalW / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalW - floor(autoPadTotalW / 2)) if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padLeft",
77
+ "windowTaps": "kernelD * kernelH * kernelW",
78
+ "windowParallelEligible": "numel(shapes.y) > 0 and windowTaps >= tunables.WINDOW_PARALLEL_MIN_TAPS and ceilDiv(numel(shapes.y), tunables.WORKGROUP_SIZE) < tunables.WINDOW_PARALLEL_MAX_SERIAL_GROUPS and numel(shapes.y) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and numel(shapes.y) <= windowTaps",
79
+ "separableTmpElements": "dim(shapes.x, 0) * dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.y, 3)",
80
+ "separableFits": "separableTmpElements * 4 <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize) and ceilDiv(separableTmpElements, tunables.WORKGROUP_SIZE) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) * min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
81
+ "poolShapeOk": "ranks.x >= 3 and ranks.x <= 5 and ranks.y == ranks.x and has(attrs, \"kernel_shape\") and kernelShapeLengthOk and stridesLengthOk and dilationsLengthOk and padsLengthOk and kernelD > 0 and kernelH > 0 and kernelW > 0 and strideD > 0 and strideH > 0 and strideW > 0 and dilationD > 0 and dilationH > 0 and dilationW > 0 and padFront >= 0 and padTop >= 0 and padLeft >= 0 and padBack >= 0 and padBottom >= 0 and padRight >= 0 and (attrs.auto_pad == \"NOTSET\" or not has(attrs, \"pads\")) and dim(shapes.y, 0) == dim(shapes.x, 0) and dim(shapes.y, 1) == dim(shapes.x, 1)",
82
+ "pool2dOutputShapeOk": "(dim(shapes.y, 2) == explicitOutH and dim(shapes.y, 3) == explicitOutW) or ((attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") and dim(shapes.y, 2) == ceil(dim(shapes.x, 2) / strideH) and dim(shapes.y, 3) == ceil(dim(shapes.x, 3) / strideW))",
83
  "floatDtypeOk": "(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T)",
84
  "globalWindowParallelCovered": "not present.indices and floatDtypeOk and poolShapeOk and numel(shapes.y) == dim(shapes.x, 0) * dim(shapes.x, 1) and numel(shapes.y) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and attrs.auto_pad == \"NOTSET\" and strideD == 1 and strideH == 1 and strideW == 1 and dilationD == 1 and dilationH == 1 and dilationW == 1 and padFront == 0 and padTop == 0 and padLeft == 0 and padBack == 0 and padBottom == 0 and padRight == 0 and ((ranks.x == 3 and kernelW == dim(shapes.x, 2)) or (ranks.x == 4 and kernelH == dim(shapes.x, 2) and kernelW == dim(shapes.x, 3)) or (ranks.x == 5 and kernelD == dim(shapes.x, 2) and kernelH == dim(shapes.x, 3) and kernelW == dim(shapes.x, 4))) and inner(shapes.x, 1) >= 256",
85
  "tiledStorageBytes": "(tunables.TILED_D + kernelD - 1) * (tunables.TILED_H + kernelH - 1) * (tunables.TILED_W + kernelW - 1) * 4",
86
  "tiledDeviceOk": "tiledStorageBytes <= device.limits.maxComputeWorkgroupStorageSize and tunables.TILED_W * tunables.TILED_H * tunables.TILED_LANES_D <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.TILED_W <= device.limits.maxComputeWorkgroupSizeX and tunables.TILED_H <= device.limits.maxComputeWorkgroupSizeY and tunables.TILED_LANES_D <= device.limits.maxComputeWorkgroupSizeZ",
87
+ "tiledDispatchOk": "ceilDiv(dim(shapes.y, 4), tunables.TILED_W) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(dim(shapes.y, 3), tunables.TILED_H) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dim(shapes.x, 0) * dim(shapes.x, 1) * ceilDiv(dim(shapes.y, 2), tunables.TILED_D) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"
 
 
 
 
88
  },
89
  "bindings": {
90
+ "x_main": { "name": "x", "elementType": "$xVec4" },
91
  "params": {
 
92
  "struct": [
93
  { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
94
  { "name": "inW", "type": "u32", "value": "dim(shapes.x, 2)" },
 
100
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
101
  ]
102
  },
103
+ "params_main": {
104
  "name": "params",
 
105
  "struct": [
106
  { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
107
  { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
 
118
  { "name": "padW", "type": "i32", "value": "effectivePadW" },
119
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
120
  ]
121
+ },
122
+ "params_window_ncl1d": {
123
+ "name": "params",
124
+ "struct": [
125
+ { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
126
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 2)" },
127
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 2)" },
128
+ { "name": "strideW", "type": "u32", "value": "strideW" },
129
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
130
+ { "name": "padW", "type": "i32", "value": "effectivePadW" }
131
+ ]
132
+ },
133
+ "params_window_nchw2d": {
134
+ "name": "params",
135
+ "struct": [
136
+ { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
137
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
138
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 3)" },
139
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 2)" },
140
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 3)" },
141
+ { "name": "strideH", "type": "u32", "value": "strideH" },
142
+ { "name": "strideW", "type": "u32", "value": "strideW" },
143
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
144
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
145
+ { "name": "padH", "type": "i32", "value": "effectivePadH" },
146
+ { "name": "padW", "type": "i32", "value": "effectivePadW" }
147
+ ]
148
+ },
149
+ "params_window_ncdhw3d": {
150
+ "name": "params",
151
+ "struct": [
152
+ { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
153
+ { "name": "inD", "type": "u32", "value": "dim(shapes.x, 2)" },
154
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 3)" },
155
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 4)" },
156
+ { "name": "outD", "type": "u32", "value": "dim(shapes.y, 2)" },
157
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 3)" },
158
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 4)" },
159
+ { "name": "strideD", "type": "u32", "value": "strideD" },
160
+ { "name": "strideH", "type": "u32", "value": "strideH" },
161
+ { "name": "strideW", "type": "u32", "value": "strideW" },
162
+ { "name": "dilationD", "type": "u32", "value": "dilationD" },
163
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
164
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
165
+ { "name": "padD", "type": "i32", "value": "effectivePadD" },
166
+ { "name": "padH", "type": "i32", "value": "effectivePadH" },
167
+ { "name": "padW", "type": "i32", "value": "effectivePadW" }
168
+ ]
169
  }
170
  },
171
  "variants": [
 
182
  "id": "main",
183
  "name": "MaxPool.GlobalWindowParallel",
184
  "shader": "pool-global-reduction.wgsl.jinja",
 
185
  "derive": {
 
186
  "vec4": "inner(shapes.x, 1) % 4 == 0",
187
  "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
188
  "useSubgroups": "device.features.has(\"subgroups\")"
 
196
  }
197
  ]
198
  },
199
+ {
200
+ "id": "window_parallel_ncl1d",
201
+ "priority": 28,
202
+ "when": ["not present.indices", "floatDtypeOk", "poolShapeOk", "ranks.x == 3", "dim(shapes.y, 2) == inferredOutW", "windowParallelEligible"],
203
+ "supersededBy": ["global_window_parallel"],
204
+ "derive": { "scalar": "dtypes.T" },
205
+ "passes": [
206
+ {
207
+ "id": "main",
208
+ "name": "MaxPool.WindowParallelNcl1d",
209
+ "shader": "pool-window-reduction.wgsl.jinja",
210
+ "derive": {
211
+ "spatialRankSpec": 1,
212
+ "kernelDSpec": "kernelD",
213
+ "kernelHSpec": "kernelH",
214
+ "kernelWSpec": "kernelW",
215
+ "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
216
+ "useSubgroups": "device.features.has(\"subgroups\")"
217
+ },
218
+ "bindings": ["x", "y", "params_window_ncl1d"],
219
+ "dispatch": { "x": "numel(shapes.y)" }
220
+ }
221
+ ]
222
+ },
223
+ {
224
+ "id": "window_parallel_nchw2d",
225
+ "priority": 28,
226
+ "when": ["not present.indices", "floatDtypeOk", "poolShapeOk", "ranks.x == 4", "dim(shapes.y, 2) == inferredOutH", "dim(shapes.y, 3) == inferredOutW", "windowParallelEligible"],
227
+ "supersededBy": ["global_window_parallel"],
228
+ "derive": { "scalar": "dtypes.T" },
229
+ "passes": [
230
+ {
231
+ "id": "main",
232
+ "name": "MaxPool.WindowParallelNchw2d",
233
+ "shader": "pool-window-reduction.wgsl.jinja",
234
+ "derive": {
235
+ "spatialRankSpec": 2,
236
+ "kernelDSpec": "kernelD",
237
+ "kernelHSpec": "kernelH",
238
+ "kernelWSpec": "kernelW",
239
+ "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
240
+ "useSubgroups": "device.features.has(\"subgroups\")"
241
+ },
242
+ "bindings": ["x", "y", "params_window_nchw2d"],
243
+ "dispatch": { "x": "numel(shapes.y)" }
244
+ }
245
+ ]
246
+ },
247
+ {
248
+ "id": "window_parallel_ncdhw3d",
249
+ "priority": 28,
250
+ "when": ["not present.indices", "floatDtypeOk", "poolShapeOk", "ranks.x == 5", "dim(shapes.y, 2) == inferredOutD", "dim(shapes.y, 3) == inferredOutH", "dim(shapes.y, 4) == inferredOutW", "windowParallelEligible"],
251
+ "supersededBy": ["global_window_parallel"],
252
+ "derive": { "scalar": "dtypes.T" },
253
+ "passes": [
254
+ {
255
+ "id": "main",
256
+ "name": "MaxPool.WindowParallelNcdhw3d",
257
+ "shader": "pool-window-reduction.wgsl.jinja",
258
+ "derive": {
259
+ "spatialRankSpec": 3,
260
+ "kernelDSpec": "kernelD",
261
+ "kernelHSpec": "kernelH",
262
+ "kernelWSpec": "kernelW",
263
+ "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
264
+ "useSubgroups": "device.features.has(\"subgroups\")"
265
+ },
266
+ "bindings": ["x", "y", "params_window_ncdhw3d"],
267
+ "dispatch": { "x": "numel(shapes.y)" }
268
+ }
269
+ ]
270
+ },
271
  {
272
  "id": "nchw2d_k3s2p1_vec4",
273
  "priority": 35,
274
  "when": ["not present.indices", "f16Ok(dtypes.T)", "dtypes.T == \"f16\"", "poolShapeOk", "ranks.x == 4", "ranks.y == 4", "attrs.auto_pad == \"NOTSET\"", "kernelH == 3", "kernelW == 3", "strideH == 2", "strideW == 2", "dilationH == 1", "dilationW == 1", "padTop == 1", "padLeft == 1", "padBottom == 1", "padRight == 1", "attrs.ceil_mode == 0", "dim(shapes.x, 2) % 2 == 0", "dim(shapes.x, 3) % 8 == 0", "dim(shapes.y, 0) == dim(shapes.x, 0)", "dim(shapes.y, 1) == dim(shapes.x, 1)", "dim(shapes.y, 2) == dim(shapes.x, 2) / 2", "dim(shapes.y, 3) == dim(shapes.x, 3) / 2", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) >= 4"],
275
+ "derive": { "xVec4": "\"vec4<f16>\"", "yVec4": "\"vec4<f16>\"" },
276
  "passes": [
277
  {
278
  "id": "main",
 
286
  "outBlocks": "dim(shapes.y, 3) / 4"
287
  },
288
  "bindings": [
289
+ "x_main",
290
  { "arg": "y", "elementType": "$yVec4" },
291
  { "name": "params", "struct": [{ "name": "tileCount", "type": "u32", "value": "numel(shapes.y) / 4" }] }
292
  ],
 
309
  "name": "MaxPool.Nchw2dK2s2Vec4",
310
  "shader": "pool2d-nchw-k2s2-vec4.wgsl.jinja",
311
  "derive": {
 
312
  "outH": "dim(shapes.y, 2)",
313
  "outW": "dim(shapes.y, 3)",
314
  "outWHalf": "floor(dim(shapes.y, 3) / 2)",
 
316
  "inPlaneVec4": "floor(dim(shapes.x, 2) * dim(shapes.x, 3) / 4)"
317
  },
318
  "bindings": [
319
+ "x_main",
320
  "y",
321
  { "name": "params", "struct": [{ "name": "tileCount", "type": "u32", "value": "numel(shapes.y) / 2" }] }
322
  ],
 
339
  "name": "MaxPool.Nchw2dUnrolled",
340
  "shader": "pool-window-unroll.wgsl.jinja",
341
  "derive": {
 
342
  "kernelHSpec": "kernelH",
343
  "kernelWSpec": "kernelW",
344
  "strideHSpec": "strideH",
 
375
  "id": "main",
376
  "name": "MaxPool.Ncl1dK2s2X4",
377
  "shader": "pool-ncl1d-x4.wgsl.jinja",
378
+ "derive": { "inW": "dim(shapes.x, 2)", "outW": "dim(shapes.y, 2)" },
379
  "bindings": [
380
+ "x_main",
381
  "y",
382
  {
383
  "name": "params",
 
408
  "id": "main",
409
  "name": "MaxPool.Ncl1d",
410
  "shader": "pool-window-nd.wgsl.jinja",
411
+ "derive": { "spatialRankSpec": 1 },
412
  "bindings": ["x", "y", "params"],
413
  "dispatch": {
414
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
421
  {
422
  "id": "nchw2d_uint8",
423
  "priority": 20,
424
+ "when": ["not present.indices", "tensorDtypes.x == \"uint8\"", "tensorDtypes.y == \"uint8\"", "poolShapeOk", "ranks.x == 4", "pool2dOutputShapeOk"],
425
  "passes": [
426
  {
427
  "id": "main",
428
+ "name": "MaxPool.Nchw2dUint8",
429
  "shader": "max-pool2d-nchw-u32.wgsl.jinja",
430
+ "bindings": ["x", "y", "params_main"],
431
  "dispatch": {
432
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
433
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
440
  "id": "nchw2d_separable",
441
  "priority": 22,
442
  "when": ["not present.indices", "f16Ok(dtypes.T) and dtypes.T != \"u32\"", "poolShapeOk", "ranks.x == 4", "ranks.y == 4", "pool2dOutputShapeOk", "kernelH > 1", "kernelW > 1", "kernelH * kernelW >= tunables.SEPARABLE_MIN_WINDOW", "strideH < kernelH", "separableFits"],
443
+ "derive": { "scalar": "dtypes.T" },
444
  "intermediates": [{ "id": "tmp", "dtype": "float32", "shape": "[separableTmpElements]" }],
445
  "passes": [
446
  {
447
  "id": "reduce_w",
448
  "name": "MaxPool.SeparableW",
449
  "shader": "pool2d-nchw-separable.wgsl.jinja",
450
+ "derive": { "axisSpec": "\"w\"", "windowArea": "kernelH * kernelW" },
451
  "bindings": [
452
  "x",
453
+ { "name": "tmp", "elementType": "f32" },
454
  {
455
  "name": "params",
456
  "struct": [
 
479
  "id": "reduce_h",
480
  "name": "MaxPool.SeparableH",
481
  "shader": "pool2d-nchw-separable.wgsl.jinja",
482
+ "derive": { "axisSpec": "\"h\"", "windowArea": "kernelH * kernelW" },
483
  "bindings": [
484
  { "name": "tmp", "buffer": "read-only-storage", "elementType": "f32" },
485
  "y",
 
517
  "passes": [
518
  {
519
  "id": "main",
520
+ "name": "MaxPool.Nchw2d",
521
  "shader": "pool-window-nd.wgsl.jinja",
522
+ "derive": { "spatialRankSpec": 2 },
523
+ "bindings": ["x", "y", "params_main"],
524
  "dispatch": {
525
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
526
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
572
  "id": "main",
573
  "name": "MaxPool.Ncdhw3d",
574
  "shader": "pool-window-nd.wgsl.jinja",
575
+ "derive": { "spatialRankSpec": 3 },
576
  "bindings": [
577
  "x",
578
  "y",
 
620
  "id": "main",
621
  "name": "MaxPool.Ncl1dIndices",
622
  "shader": "pool-window-nd.wgsl.jinja",
623
+ "derive": { "spatialRankSpec": 1 },
624
  "bindings": ["x", "y", "indices", "params"],
625
  "dispatch": {
626
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
633
  {
634
  "id": "nchw2d_uint8_indices",
635
  "priority": 16,
636
+ "when": ["present.indices", "tensorDtypes.x == \"uint8\"", "tensorDtypes.y == \"uint8\"", "poolShapeOk", "ranks.x == 4", "pool2dOutputShapeOk"],
637
  "derive": { "scalar": "\"u32\"", "hasIndices": true, "storageOrder": "attrs.storage_order" },
638
  "passes": [
639
  {
640
  "id": "main",
641
  "name": "MaxPool.Nchw2dUint8Indices",
642
  "shader": "pool-window-nd.wgsl.jinja",
643
+ "derive": { "spatialRankSpec": 2 },
644
+ "bindings": ["x", "y", "indices", "params_main"],
645
  "dispatch": {
646
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
647
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
660
  "id": "main",
661
  "name": "MaxPool.Nchw2dIndices",
662
  "shader": "pool-window-nd.wgsl.jinja",
663
+ "derive": { "spatialRankSpec": 2 },
664
+ "bindings": ["x", "y", "indices", "params_main"],
665
  "dispatch": {
666
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
667
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
build/webgpu/max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja CHANGED
@@ -6,7 +6,14 @@
6
  // The first loaded vector normally contributes only its final lane. At the left
7
  // edge that lane is padding, so the load is skipped. A NaN in any selected input
8
  // propagates to the corresponding output lane.
9
- enable f16;
 
 
 
 
 
 
 
10
  {{ env.wgsl.resourceDeclarations }}
11
 
12
  const IN_H: i32 = {{ inH }};
@@ -19,13 +26,11 @@ fn negative_infinity() -> f32 {
19
  var bits = 0xff800000u;
20
  return bitcast<f32>(bits);
21
  }
22
-
23
  fn is_nan_f32(value: f32) -> bool {
24
  let bits = bitcast<u32>(value);
25
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
26
  }
27
 
28
-
29
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
30
  if (is_nan_f32(a)) {
31
  return a;
@@ -74,10 +79,7 @@ fn combine_rows(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
74
  fn main(
75
  @builtin(global_invocation_id) gid: vec3<u32>
76
  ) {
77
- let tile = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
78
- if (tile >= params.tileCount) {
79
- return;
80
- }
81
 
82
  let block = tile % OUT_BLOCKS;
83
  var outer = tile / OUT_BLOCKS;
 
6
  // The first loaded vector normally contributes only its final lane. At the left
7
  // edge that lane is padding, so the load is skipped. A NaN in any selected input
8
  // propagates to the corresponding output lane.
9
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
10
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
11
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
12
+ // per-axis workgroup fold width.
13
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
14
+ if ({{ name }} >= {{ bound }}) {
15
+ return;
16
+ }{% endmacro %}
17
  {{ env.wgsl.resourceDeclarations }}
18
 
19
  const IN_H: i32 = {{ inH }};
 
26
  var bits = 0xff800000u;
27
  return bitcast<f32>(bits);
28
  }
 
29
  fn is_nan_f32(value: f32) -> bool {
30
  let bits = bitcast<u32>(value);
31
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
32
  }
33
 
 
34
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
35
  if (is_nan_f32(a)) {
36
  return a;
 
79
  fn main(
80
  @builtin(global_invocation_id) gid: vec3<u32>
81
  ) {
82
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "tile", "params.tileCount") }}
 
 
 
83
 
84
  let block = tile % OUT_BLOCKS;
85
  var outer = tile / OUT_BLOCKS;
build/webgpu/max-pool2d-nchw-u32.wgsl.jinja CHANGED
@@ -1,13 +1,16 @@
 
 
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
4
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
5
- // 2D-folded flat index: gid.y carries the high bits past the
6
- // per-axis dispatch fold width (outputs > 16.7M elements).
7
- let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
8
- if (index >= params.count) {
9
- return;
10
- }
11
 
12
  let ow = index % params.outW;
13
  var t = index / params.outW;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
+ // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
6
+ if ({{ name }} >= {{ bound }}) {
7
+ return;
8
+ }{% endmacro %}
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
12
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
13
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
 
 
 
 
 
14
 
15
  let ow = index % params.outW;
16
  var t = index / params.outW;
build/webgpu/max-pool3d-ncdhw-k5-tiled.wgsl.jinja CHANGED
@@ -5,13 +5,11 @@ fn negative_infinity() -> f32 {
5
  return bitcast<f32>(bits);
6
  }
7
 
8
-
9
  fn is_nan_f32(value: f32) -> bool {
10
  let bits = bitcast<u32>(value);
11
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
12
  }
13
 
14
-
15
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
16
  if (is_nan_f32(a)) {
17
  return a;
 
5
  return bitcast<f32>(bits);
6
  }
7
 
 
8
  fn is_nan_f32(value: f32) -> bool {
9
  let bits = bitcast<u32>(value);
10
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
11
  }
12
 
 
13
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
14
  if (is_nan_f32(a)) {
15
  return a;
build/webgpu/metadata.json CHANGED
@@ -1,31 +1,35 @@
1
  {
2
  "name": "ai.onnx.MaxPool",
3
- "id": "_ai_onnx_maxpool_webgpu_0edff93",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "AOgKxSadb2wkwmpf8SkHhMdwM1IpGC/TbJxTvDQZdNQ=",
11
- "manifest.json": "5TTbqWe4MvoPE69DBi3YswL01bPFczoJuMndbfJkd2Q=",
12
- "max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja": "NhbuTmSlBus0wOCYKCGshztjvOa5Ql6fscn6l5+9plc=",
13
- "max-pool2d-nchw-u32.wgsl.jinja": "iLeF9bnjdoEU7ZeANJl9SED9RLEjB3Uo1/0MR5ktRCI=",
14
- "max-pool3d-ncdhw-k5-tiled.wgsl.jinja": "Zwv1VailOkU7xlslNrbnUz9zyTXUSPGefhG71ybRVIk=",
15
- "pool-global-reduction.wgsl.jinja": "d0Kc8mGbdDfI0PElsqLqjpnRqfyQeaS5LqSO/f8tfTM=",
16
- "pool-ncl1d-x4.wgsl.jinja": "wADuoaBQjOxDwie14Tmpl/I5T6TzYIKKjepOAPZNwEE=",
17
- "pool-window-nd.wgsl.jinja": "FtY2LWLhpfgAj3ye2GP+8uez3BF7qpIncbH7yX+bauo=",
18
- "pool-window-unroll.wgsl.jinja": "/7wF7g88WmvgGS45Lg8NikpwzGdUoIX7VkTGRWjQy9w=",
19
- "pool2d-nchw-k2s2-vec4.wgsl.jinja": "9LgEqP1IYbTszmUOHFYrIPpobfhunkVDsVITvSPdn58=",
20
- "pool2d-nchw-separable.wgsl.jinja": "1v8HRM4mORp/jBxY1NUObJ3vjeV5fgR6PD3mBSmyIUM=",
21
- "test.json": "1TZGbirb+TNnfoZRenB4AaQ1b5pqHnIrKUf9SGrs/Ac="
 
22
  }
23
  },
24
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
25
  "webgpu": {
26
- "manifestSpec": "2.0",
27
  "variants": {
28
  "global_window_parallel": ["pool-global-reduction.wgsl.jinja"],
 
 
 
29
  "nchw2d_k3s2p1_vec4": ["max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja"],
30
  "nchw2d_k2s2_vec4": ["pool2d-nchw-k2s2-vec4.wgsl.jinja"],
31
  "nchw2d_unrolled": ["pool-window-unroll.wgsl.jinja"],
 
1
  {
2
  "name": "ai.onnx.MaxPool",
3
+ "id": "_ai_onnx_maxpool_webgpu_9061839",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "CzLX4BrHv1R1JjxoSPkcWW10fd41NBG/vCioxDvTPAQ=",
11
+ "manifest.json": "2OsDYv2oeu4uHXX/5CQaYRkUph0hiuGYIYO5knHpIy4=",
12
+ "max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja": "507C/27t9YHR/gBvMJ9utJoVSQd8VUbm5oFs2jmESbo=",
13
+ "max-pool2d-nchw-u32.wgsl.jinja": "gS06j2pL8ITtuIdsLfQ3dZ+xLtrT6PJ1uH8sZEOGLMk=",
14
+ "max-pool3d-ncdhw-k5-tiled.wgsl.jinja": "JK3OfWsbQgJwIPKzQ1pZF4apPOHxA5RjnqzvoeqStVk=",
15
+ "pool-global-reduction.wgsl.jinja": "zsm141V8sZPAMwW+7yukfWPSpZsTLpdPoJkJHMf6Lr8=",
16
+ "pool-ncl1d-x4.wgsl.jinja": "r5hgQdHvV4WNJl1fUIbXORTuyWuf2EMDwDbbjHVIh9M=",
17
+ "pool-window-nd.wgsl.jinja": "mEftaXkif8tNyclNzIvkTfDMKBnkhS0TWRDTqT/5fLQ=",
18
+ "pool-window-reduction.wgsl.jinja": "b5BNYyK2mJ3qtdps6TxFRdGsf750RUcLEWhsyGh0YcY=",
19
+ "pool-window-unroll.wgsl.jinja": "1xyl/O2Zf1q8AWYD7obTxfJ0bpJxidnsaL4FociOdpY=",
20
+ "pool2d-nchw-k2s2-vec4.wgsl.jinja": "1LKfPAT8o8rGEUjdyMiBTZWGcYpLtVJQXmZxuWOtkTY=",
21
+ "pool2d-nchw-separable.wgsl.jinja": "2ulFFdVpiOnpnkJiCHA2TRUwDzfORoVh9EwIFEM42FI=",
22
+ "test.json": "8jRLh4Ogf3wHcAFtHvcBX8EmDrEYUqUCT2YlGXRfOTM="
23
  }
24
  },
25
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
26
  "webgpu": {
27
+ "manifestSpec": "2.1",
28
  "variants": {
29
  "global_window_parallel": ["pool-global-reduction.wgsl.jinja"],
30
+ "window_parallel_ncl1d": ["pool-window-reduction.wgsl.jinja"],
31
+ "window_parallel_nchw2d": ["pool-window-reduction.wgsl.jinja"],
32
+ "window_parallel_ncdhw3d": ["pool-window-reduction.wgsl.jinja"],
33
  "nchw2d_k3s2p1_vec4": ["max-pool2d-nchw-k3s2p1-vec4.wgsl.jinja"],
34
  "nchw2d_k2s2_vec4": ["pool2d-nchw-k2s2-vec4.wgsl.jinja"],
35
  "nchw2d_unrolled": ["pool-window-unroll.wgsl.jinja"],
build/webgpu/pool-global-reduction.wgsl.jinja CHANGED
@@ -31,8 +31,8 @@ fn quiet_nan() -> f32 {
31
  var bits = 0x7fc00000u;
32
  return bitcast<f32>(bits);
33
  }
34
-
35
  {% endif %}
 
36
  fn is_nan_f32(value: f32) -> bool {
37
  let bits = bitcast<u32>(value);
38
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
@@ -50,10 +50,9 @@ fn max_propagate_nan(a: f32, b: f32) -> f32 {
50
 
51
  @compute @workgroup_size(WG)
52
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
53
- @builtin(local_invocation_id) lid: vec3<u32>
54
- {%- if useSubgroups %},
55
- @builtin(subgroup_size) sg_size: u32
56
- {%- endif %}) {
57
  let plane = wid.x;
58
  let tid = lid.x;
59
  var partial = negative_infinity();
@@ -76,39 +75,42 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
76
  {% endif %}
77
 
78
  {% if useSubgroups %}
79
- // subgroupMax does not propagate NaN; carry an explicit NaN flag alongside.
80
- let lane_nan = is_nan_f32(partial);
81
- let sg_nan = subgroupAny(lane_nan);
82
- let sg_max = subgroupMax(select(partial, negative_infinity(), lane_nan));
83
- var total = select(sg_max, quiet_nan(), sg_nan);
84
- // Cross-subgroup fold that assumes nothing about which invocations share a
85
- // subgroup, how many subgroups there are, or which of a subgroup's lanes are
86
- // active: every invocation owns the slot at its own index, the elected lane
87
- // publishes its subgroup partial there and every other lane publishes the
88
- // reduction identity. Each subgroup then folds all WG slots — lane `rank`,
89
- // its dense position among the active lanes, walks slots rank, rank + count,
90
- // ... — and one more collective merges the lane partials, so every slot is
91
- // merged exactly once at any legal width and partition. A one-subgroup
92
- // workgroup skips shared memory entirely; that test reads the `subgroup_size`
93
- // builtin, which is uniform, because a collective's result is not uniform to
94
- // WGSL's analysis and may not guard a barrier.
95
- if (sg_size != WG) {
96
- let rank = subgroupExclusiveAdd(1u);
97
- let count = subgroupAdd(1u);
98
- wg_partials[tid] = select(negative_infinity(), total, rank == 0u);
99
- workgroupBarrier();
100
- var acc = negative_infinity();
101
- for (var i = rank; i < WG; i = i + count) {
102
- acc = max_propagate_nan(acc, wg_partials[i]);
103
- }
104
- let acc_nan = is_nan_f32(acc);
105
- let fold_nan = subgroupAny(acc_nan);
106
- let fold_max = subgroupMax(select(acc, negative_infinity(), acc_nan));
107
- total = select(fold_max, quiet_nan(), fold_nan);
108
  }
 
 
 
 
 
 
109
  if (tid == 0u) {
110
- y[plane] = {{ scalar }}(total);
111
- }
112
  {% else %}
113
  // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
114
  wg_tree[tid] = partial;
@@ -121,7 +123,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
121
  }
122
  if (tid == 0u) {
123
  let total = wg_tree[0];
 
124
  y[plane] = {{ scalar }}(total);
125
  }
126
- {% endif %}
127
  }
 
31
  var bits = 0x7fc00000u;
32
  return bitcast<f32>(bits);
33
  }
 
34
  {% endif %}
35
+
36
  fn is_nan_f32(value: f32) -> bool {
37
  let bits = bitcast<u32>(value);
38
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
 
50
 
51
  @compute @workgroup_size(WG)
52
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
53
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
54
+ @builtin(subgroup_invocation_id) sg_lane: u32,
55
+ @builtin(subgroup_size) sg_size: u32{% endif %}) {
 
56
  let plane = wid.x;
57
  let tid = lid.x;
58
  var partial = negative_infinity();
 
75
  {% endif %}
76
 
77
  {% if useSubgroups %}
78
+ {% filter indent(2, true) %}
79
+ // subgroupMax does not propagate NaN; carry an explicit NaN flag alongside.
80
+ let lane_nan = is_nan_f32(partial);
81
+ let sg_nan = subgroupAny(lane_nan);
82
+ let sg_max = subgroupMax(select(partial, negative_infinity(), lane_nan));
83
+ var total = select(sg_max, quiet_nan(), sg_nan);
84
+ // Cross-subgroup fold that assumes nothing about which invocations share a
85
+ // subgroup or how many subgroups there are (it does require the uniform
86
+ // control flow this entry point already has): every invocation owns the slot
87
+ // at its own index, the elected lane
88
+ // publishes its subgroup partial there and every other lane publishes the
89
+ // reduction identity. Each subgroup then folds all WG slots — lane `sg_lane`
90
+ // walks slots sg_lane, sg_lane + sg_size, ... — and one more collective merges
91
+ // the lane partials, so every slot is merged exactly once at any legal width.
92
+ // The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
93
+ // `subgroupAdd(1u)`: those agree with them under this contract, but a driver
94
+ // in the wild answers a claim derived from them with zero. A one-subgroup
95
+ // workgroup skips shared memory entirely; that test reads the `subgroup_size`
96
+ // builtin, which is uniform, because a collective's result is not uniform to
97
+ // WGSL's analysis and may not guard a barrier.
98
+ if (sg_size != WG) {
99
+ let rank = sg_lane;
100
+ let lanes = sg_size;
101
+ wg_partials[tid] = select(negative_infinity(), total, rank == 0u);
102
+ workgroupBarrier();
103
+ var acc = negative_infinity();
104
+ for (var i = rank; i < WG; i = i + lanes) {
105
+ acc = max_propagate_nan(acc, wg_partials[i]);
 
106
  }
107
+ let acc_nan = is_nan_f32(acc);
108
+ let fold_nan = subgroupAny(acc_nan);
109
+ let fold_max = subgroupMax(select(acc, negative_infinity(), acc_nan));
110
+ total = select(fold_max, quiet_nan(), fold_nan);
111
+ }
112
+ {% endfilter %}
113
  if (tid == 0u) {
 
 
114
  {% else %}
115
  // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
116
  wg_tree[tid] = partial;
 
123
  }
124
  if (tid == 0u) {
125
  let total = wg_tree[0];
126
+ {% endif %}
127
  y[plane] = {{ scalar }}(total);
128
  }
 
129
  }
build/webgpu/pool-ncl1d-x4.wgsl.jinja CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Four adjacent NCL max-pool outputs per invocation for non-overlapping k2/s2
@@ -11,23 +17,21 @@ fn is_nan_f32(value: f32) -> bool {
11
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
12
  }
13
 
14
-
15
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
16
- if (is_nan_f32(a)) { return a; }
17
- if (is_nan_f32(b)) { return b; }
 
 
 
 
18
  return max(a, b);
19
  }
20
 
21
- fn pair_max(a: f32, b: f32) -> f32 {
22
- return max_propagate_nan(a, b);
23
- }
24
-
25
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
26
  fn main(
27
  @builtin(global_invocation_id) gid: vec3<u32>,
28
  ) {
29
- let block = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
30
- if (block >= params.blockCount) { return; }
31
 
32
  let plane = block / BLOCKS_PER_PLANE;
33
  let outputBlock = block - plane * BLOCKS_PER_PLANE;
@@ -38,8 +42,8 @@ fn main(
38
  let b = vec4<f32>(x[inputVec + 1u]);
39
 
40
  // k2/s2: two aligned vec4 reads cover four independent windows exactly.
41
- y[outputBase] = {{ scalar }}(pair_max(a.x, a.y));
42
- y[outputBase + 1u] = {{ scalar }}(pair_max(a.z, a.w));
43
- y[outputBase + 2u] = {{ scalar }}(pair_max(b.x, b.y));
44
- y[outputBase + 3u] = {{ scalar }}(pair_max(b.z, b.w));
45
  }
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
+ // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
6
+ if ({{ name }} >= {{ bound }}) { return; }{% endmacro %}
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
  // Four adjacent NCL max-pool outputs per invocation for non-overlapping k2/s2
 
17
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
18
  }
19
 
 
20
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
21
+ if (is_nan_f32(a)) {
22
+ return a;
23
+ }
24
+ if (is_nan_f32(b)) {
25
+ return b;
26
+ }
27
  return max(a, b);
28
  }
29
 
 
 
 
 
30
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
31
  fn main(
32
  @builtin(global_invocation_id) gid: vec3<u32>,
33
  ) {
34
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "block", "params.blockCount", guardInline=true) }}
 
35
 
36
  let plane = block / BLOCKS_PER_PLANE;
37
  let outputBlock = block - plane * BLOCKS_PER_PLANE;
 
42
  let b = vec4<f32>(x[inputVec + 1u]);
43
 
44
  // k2/s2: two aligned vec4 reads cover four independent windows exactly.
45
+ y[outputBase] = {{ scalar }}(max_propagate_nan(a.x, a.y));
46
+ y[outputBase + 1u] = {{ scalar }}(max_propagate_nan(a.z, a.w));
47
+ y[outputBase + 2u] = {{ scalar }}(max_propagate_nan(b.x, b.y));
48
+ y[outputBase + 3u] = {{ scalar }}(max_propagate_nan(b.z, b.w));
49
  }
build/webgpu/pool-window-nd.wgsl.jinja CHANGED
@@ -1,5 +1,11 @@
1
  {% set hasIndices = hasIndices is defined and hasIndices %}
2
  {% set storageOrder = storageOrder | default(0) %}
 
 
 
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
  // Direct spatial-pooling traversal for a channels-first tensor.
@@ -7,23 +13,24 @@ fn negative_infinity() -> f32 {
7
  var bits = 0xff800000u;
8
  return bitcast<f32>(bits);
9
  }
10
-
11
  fn is_nan_f32(value: f32) -> bool {
12
  let bits = bitcast<u32>(value);
13
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
14
  }
15
 
16
-
17
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
18
- if (is_nan_f32(a)) { return a; }
19
- if (is_nan_f32(b)) { return b; }
 
 
 
 
20
  return max(a, b);
21
  }
22
 
23
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
24
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
25
- let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
26
- if (index >= params.count) { return; }
27
 
28
  let ow = index % params.outW;
29
  var t = index / params.outW;
 
1
  {% set hasIndices = hasIndices is defined and hasIndices %}
2
  {% set storageOrder = storageOrder | default(0) %}
3
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
4
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
5
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
6
+ // per-axis workgroup fold width.
7
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
8
+ if ({{ name }} >= {{ bound }}) { return; }{% endmacro %}
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
  // Direct spatial-pooling traversal for a channels-first tensor.
 
13
  var bits = 0xff800000u;
14
  return bitcast<f32>(bits);
15
  }
 
16
  fn is_nan_f32(value: f32) -> bool {
17
  let bits = bitcast<u32>(value);
18
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
19
  }
20
 
 
21
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
22
+ if (is_nan_f32(a)) {
23
+ return a;
24
+ }
25
+ if (is_nan_f32(b)) {
26
+ return b;
27
+ }
28
  return max(a, b);
29
  }
30
 
31
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
32
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
33
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index", guardInline=true) }}
 
34
 
35
  let ow = index % params.outW;
36
  var t = index / params.outW;
build/webgpu/pool-window-reduction.wgsl.jinja ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if useSubgroups %}
2
+ enable subgroups;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // Workgroup-cooperative windowed pooling: one workgroup reduces the kernel
7
+ // window of ONE output element. The serial route gives each output element a
8
+ // single invocation that loops the whole window, so its only parallelism is the
9
+ // output element count; when that count launches too few workgroups and the window is
10
+ // long, the window itself is the parallelism worth spending. Threads here stride
11
+ // the flattened tap index [kD x] kH x kW, each tap resolving its own padded,
12
+ // strided and dilated input coordinate, then combine the per-thread partials.
13
+ // With subgroups: a subgroup collective, then one shared-memory slot per
14
+ // invocation (the subgroup partial from its elected lane, the identity from
15
+ // every other lane) that every subgroup folds. Without subgroups, a full
16
+ // workgroup tree combines the per-thread partials.
17
+ // The fold propagates NaNs while taking the maximum. The in-bounds tap count is
18
+ // reduced alongside it so an output whose window is entirely out of bounds
19
+ // writes 0 rather than -inf, matching the serial route.
20
+ const WG: u32 = {{ workgroupSizeSpec }}u;
21
+ const KW: u32 = {{ kernelWSpec }}u;
22
+ {% if spatialRankSpec >= 2 %}
23
+ const KH: u32 = {{ kernelHSpec }}u;
24
+ {% endif %}
25
+ // Flattened tap count of the whole window; the loop bound and, for
26
+ // count_include_pad == 1, the divisor.
27
+ const TAPS: u32 = {{ kernelDSpec * kernelHSpec * kernelWSpec }}u;
28
+ // One slot per invocation: the cross-subgroup fold below indexes by
29
+ // local_invocation_id, never by a subgroup ordinal, so no subgroup width or
30
+ // partition can overflow or alias it.
31
+ var<workgroup> wg_partials: array<f32, WG>;
32
+ var<workgroup> wg_counts: array<f32, WG>;
33
+
34
+ fn negative_infinity() -> f32 {
35
+ var bits = 0xff800000u;
36
+ return bitcast<f32>(bits);
37
+ }
38
+ {% if useSubgroups %}
39
+
40
+ fn quiet_nan() -> f32 {
41
+ var bits = 0x7fc00000u;
42
+ return bitcast<f32>(bits);
43
+ }
44
+ {% endif %}
45
+
46
+ fn is_nan_f32(value: f32) -> bool {
47
+ let bits = bitcast<u32>(value);
48
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
49
+ }
50
+
51
+ fn max_propagate_nan(a: f32, b: f32) -> f32 {
52
+ if (is_nan_f32(a)) {
53
+ return a;
54
+ }
55
+ if (is_nan_f32(b)) {
56
+ return b;
57
+ }
58
+ return max(a, b);
59
+ }
60
+
61
+ @compute @workgroup_size(WG)
62
+ fn main(@builtin(workgroup_id) wid: vec3<u32>,
63
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
64
+ @builtin(subgroup_invocation_id) sg_lane: u32,
65
+ @builtin(subgroup_size) sg_size: u32{% endif %}) {
66
+ // One workgroup per output element: the dispatch is exactly numel(y), so no
67
+ // bounds guard is needed here.
68
+ let index = wid.x;
69
+ let tid = lid.x;
70
+
71
+ let ow = index % params.outW;
72
+ var t = index / params.outW;
73
+ {% if spatialRankSpec >= 2 %}
74
+ let oh = t % params.outH;
75
+ t /= params.outH;
76
+ {% endif %}
77
+ {% if spatialRankSpec == 3 %}
78
+ let od = t % params.outD;
79
+ t /= params.outD;
80
+ {% endif %}
81
+ let channel = t % params.channels;
82
+ let batch = t / params.channels;
83
+
84
+ let startW = i32(ow * params.strideW) - params.padW;
85
+ {% if spatialRankSpec >= 2 %}
86
+ let startH = i32(oh * params.strideH) - params.padH;
87
+ {% endif %}
88
+ {% if spatialRankSpec == 3 %}
89
+ let startD = i32(od * params.strideD) - params.padD;
90
+ {% endif %}
91
+ {% if spatialRankSpec == 1 %}
92
+ let planeBase = (batch * params.channels + channel) * params.inW;
93
+ {% elif spatialRankSpec == 2 %}
94
+ let planeBase = (batch * params.channels + channel) * params.inH * params.inW;
95
+ {% else %}
96
+ let planeBase = (batch * params.channels + channel) * params.inD * params.inH * params.inW;
97
+ {% endif %}
98
+
99
+ var partial = negative_infinity();
100
+ var counted = 0.0;
101
+ for (var tap = tid; tap < TAPS; tap = tap + WG) {
102
+ let kw = tap % KW;
103
+ {% if spatialRankSpec >= 2 %}
104
+ let rest = tap / KW;
105
+ let kh = rest % KH;
106
+ {% endif %}
107
+ {% if spatialRankSpec == 3 %}
108
+ let kd = rest / KH;
109
+ {% endif %}
110
+ let iw = startW + i32(kw * params.dilationW);
111
+ {% if spatialRankSpec >= 2 %}
112
+ let ih = startH + i32(kh * params.dilationH);
113
+ {% endif %}
114
+ {% if spatialRankSpec == 3 %}
115
+ let id = startD + i32(kd * params.dilationD);
116
+ {% endif %}
117
+ {% if spatialRankSpec == 1 %}
118
+ let inBounds = iw >= 0 && iw < i32(params.inW);
119
+ {% elif spatialRankSpec == 2 %}
120
+ let inBounds = iw >= 0 && iw < i32(params.inW) && ih >= 0 && ih < i32(params.inH);
121
+ {% else %}
122
+ let inBounds = iw >= 0 && iw < i32(params.inW) && ih >= 0 && ih < i32(params.inH) && id >= 0 && id < i32(params.inD);
123
+ {% endif %}
124
+ if (inBounds) {
125
+ {% if spatialRankSpec == 1 %}
126
+ let value = f32(x[planeBase + u32(iw)]);
127
+ {% elif spatialRankSpec == 2 %}
128
+ let value = f32(x[planeBase + u32(ih) * params.inW + u32(iw)]);
129
+ {% else %}
130
+ let value = f32(x[planeBase + (u32(id) * params.inH + u32(ih)) * params.inW + u32(iw)]);
131
+ {% endif %}
132
+ partial = max_propagate_nan(partial, value);
133
+ counted = counted + 1.0;
134
+ }
135
+ }
136
+
137
+ {% if useSubgroups %}
138
+ {% filter indent(2, true) %}
139
+ // subgroupMax does not propagate NaN; carry an explicit NaN flag alongside.
140
+ let lane_nan = is_nan_f32(partial);
141
+ let sg_nan = subgroupAny(lane_nan);
142
+ let sg_max = subgroupMax(select(partial, negative_infinity(), lane_nan));
143
+ var total = select(sg_max, quiet_nan(), sg_nan);
144
+ var countTotal = subgroupAdd(counted);
145
+ // Cross-subgroup fold that assumes nothing about which invocations share a
146
+ // subgroup or how many subgroups there are (it does require the uniform
147
+ // control flow this entry point already has): every invocation owns the slot
148
+ // at its own index, the elected lane
149
+ // publishes its subgroup partial there and every other lane publishes the
150
+ // reduction identity. Each subgroup then folds all WG slots — lane `sg_lane`
151
+ // walks slots sg_lane, sg_lane + sg_size, ... — and one more collective merges
152
+ // the lane partials, so every slot is merged exactly once at any legal width.
153
+ // The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
154
+ // `subgroupAdd(1u)`: those agree with them under this contract, but a driver
155
+ // in the wild answers a claim derived from them with zero. A one-subgroup
156
+ // workgroup skips shared memory entirely; that test reads the `subgroup_size`
157
+ // builtin, which is uniform, because a collective's result is not uniform to
158
+ // WGSL's analysis and may not guard a barrier.
159
+ if (sg_size != WG) {
160
+ let rank = sg_lane;
161
+ let lanes = sg_size;
162
+ wg_partials[tid] = select(negative_infinity(), total, rank == 0u);
163
+ wg_counts[tid] = select(0.0, countTotal, rank == 0u);
164
+ workgroupBarrier();
165
+ var acc = negative_infinity();
166
+ var countAcc = 0.0;
167
+ for (var i = rank; i < WG; i = i + lanes) {
168
+ acc = max_propagate_nan(acc, wg_partials[i]);
169
+ countAcc = countAcc + wg_counts[i];
170
+ }
171
+ let acc_nan = is_nan_f32(acc);
172
+ let fold_nan = subgroupAny(acc_nan);
173
+ let fold_max = subgroupMax(select(acc, negative_infinity(), acc_nan));
174
+ total = select(fold_max, quiet_nan(), fold_nan);
175
+ countTotal = subgroupAdd(countAcc);
176
+ }
177
+ {% endfilter %}
178
+ {% else %}
179
+ // No-subgroups fallback: full workgroup-tree reduction of the per-thread
180
+ // partials, carrying the unpaired entry forward for odd active widths.
181
+ wg_partials[tid] = partial;
182
+ wg_counts[tid] = counted;
183
+ workgroupBarrier();
184
+ for (var liveCount = WG; liveCount > 1u; liveCount = (liveCount + 1u) / 2u) {
185
+ let step = (liveCount + 1u) / 2u;
186
+ if (tid + step < liveCount) {
187
+ wg_partials[tid] = max_propagate_nan(wg_partials[tid], wg_partials[tid + step]);
188
+ wg_counts[tid] = wg_counts[tid] + wg_counts[tid + step];
189
+ }
190
+ workgroupBarrier();
191
+ }
192
+ let total = wg_partials[0];
193
+ let countTotal = wg_counts[0];
194
+ {% endif %}
195
+ if (tid == 0u) {
196
+ y[index] = {{ scalar }}(select(0.0, total, countTotal > 0.0));
197
+ }
198
+ }
build/webgpu/pool-window-unroll.wgsl.jinja CHANGED
@@ -1,5 +1,13 @@
1
  // Compile-time-unrolled 2D NCHW pooling for small windows. Accumulation remains
2
  // kh-major/kw-minor, matching the loop-based implementations.
 
 
 
 
 
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
  const OUT_W: u32 = {{ outW }}u;
@@ -17,13 +25,11 @@ fn negative_infinity() -> f32 {
17
  var bits = 0xff800000u;
18
  return bitcast<f32>(bits);
19
  }
20
-
21
  fn is_nan_f32(value: f32) -> bool {
22
  let bits = bitcast<u32>(value);
23
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
24
  }
25
 
26
-
27
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
28
  if (is_nan_f32(a)) {
29
  return a;
@@ -38,11 +44,7 @@ fn max_propagate_nan(a: f32, b: f32) -> f32 {
38
  fn main(
39
  @builtin(global_invocation_id) gid: vec3<u32>
40
  ) {
41
- // Fold very large flat outputs into the dispatch's second dimension.
42
- let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
43
- if (index >= params.count) {
44
- return;
45
- }
46
 
47
  let ow = index % OUT_W;
48
  var plane = index / OUT_W;
@@ -73,7 +75,6 @@ fn main(
73
  }
74
  {% endfor %}
75
 
76
-
77
  let output = select(0.0, result, seen != 0u);
78
  y[index] = {{ scalar }}(output);
79
  }
 
1
  // Compile-time-unrolled 2D NCHW pooling for small windows. Accumulation remains
2
  // kh-major/kw-minor, matching the loop-based implementations.
3
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
4
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
5
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
6
+ // per-axis workgroup fold width.
7
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
8
+ if ({{ name }} >= {{ bound }}) {
9
+ return;
10
+ }{% endmacro %}
11
  {{ env.wgsl.resourceDeclarations }}
12
 
13
  const OUT_W: u32 = {{ outW }}u;
 
25
  var bits = 0xff800000u;
26
  return bitcast<f32>(bits);
27
  }
 
28
  fn is_nan_f32(value: f32) -> bool {
29
  let bits = bitcast<u32>(value);
30
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
31
  }
32
 
 
33
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
34
  if (is_nan_f32(a)) {
35
  return a;
 
44
  fn main(
45
  @builtin(global_invocation_id) gid: vec3<u32>
46
  ) {
47
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
 
 
 
 
48
 
49
  let ow = index % OUT_W;
50
  var plane = index / OUT_W;
 
75
  }
76
  {% endfor %}
77
 
 
78
  let output = select(0.0, result, seen != 0u);
79
  y[index] = {{ scalar }}(output);
80
  }
build/webgpu/pool2d-nchw-k2s2-vec4.wgsl.jinja CHANGED
@@ -1,3 +1,11 @@
 
 
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // 2D NCHW MaxPool specialised for the dense 2x2 / stride-2 / no-pad /
@@ -24,11 +32,8 @@ fn max_nan(a: {{ scalar }}, b: {{ scalar }}) -> {{ scalar }} {
24
 
25
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
26
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
27
- // 2D-folded flat tile index (one tile = two output columns).
28
- let tile = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
29
- if (tile >= params.tileCount) {
30
- return;
31
- }
32
 
33
  let tileCol = tile % OUT_W_HALF; // pair-of-columns within the row
34
  var t = tile / OUT_W_HALF;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
+ // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
6
+ if ({{ name }} >= {{ bound }}) {
7
+ return;
8
+ }{% endmacro %}
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
  // 2D NCHW MaxPool specialised for the dense 2x2 / stride-2 / no-pad /
 
32
 
33
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
34
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
35
+ // One flat index per tile (one tile = two output columns).
36
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "tile", "params.tileCount") }}
 
 
 
37
 
38
  let tileCol = tile % OUT_W_HALF; // pair-of-columns within the row
39
  var t = tile / OUT_W_HALF;
build/webgpu/pool2d-nchw-separable.wgsl.jinja CHANGED
@@ -12,16 +12,18 @@ fn negative_infinity() -> f32 {
12
  var bits = 0xff800000u;
13
  return bitcast<f32>(bits);
14
  }
15
-
16
  fn is_nan_f32(value: f32) -> bool {
17
  let bits = bitcast<u32>(value);
18
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
19
  }
20
 
21
-
22
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
23
- if (is_nan_f32(a)) { return a; }
24
- if (is_nan_f32(b)) { return b; }
 
 
 
 
25
  return max(a, b);
26
  }
27
 
 
12
  var bits = 0xff800000u;
13
  return bitcast<f32>(bits);
14
  }
 
15
  fn is_nan_f32(value: f32) -> bool {
16
  let bits = bitcast<u32>(value);
17
  return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
18
  }
19
 
 
20
  fn max_propagate_nan(a: f32, b: f32) -> f32 {
21
+ if (is_nan_f32(a)) {
22
+ return a;
23
+ }
24
+ if (is_nan_f32(b)) {
25
+ return b;
26
+ }
27
  return max(a, b);
28
  }
29
 
build/webgpu/test.json CHANGED
@@ -1178,7 +1178,7 @@
1178
  {
1179
  "name": "f16_nchw_realistic_k3s2_pad1",
1180
  "provenance": {
1181
- "notes": "A realistic float16 ResNet-stem shape [1,64,56,56] with k3/s2/pad1 bypasses the k2 vec4 specialization and exercises padded windows in the generic NCHW loop."
1182
  },
1183
  "attrs": { "kernel_shape": [3, 3], "strides": [2, 2], "pads": [1, 1, 1, 1] },
1184
  "inputs": {
@@ -1237,9 +1237,7 @@
1237
  },
1238
  {
1239
  "name": "ncdhw3d_f32_k5s1pad2_tiled",
1240
- "provenance": {
1241
- "notes": "Compact correctness lock for the separable workgroup-tiled 5x5x5 stride-1/pad-2 path used by the realistic 48-cubed stress case."
1242
- },
1243
  "attrs": { "kernel_shape": [5, 5, 5], "pads": [2, 2, 2, 2, 2, 2] },
1244
  "inputs": {
1245
  "x": {
@@ -1253,7 +1251,7 @@
1253
  {
1254
  "name": "ncdhw3d_f32_k5s1pad2_tiled_partial_depth",
1255
  "provenance": {
1256
- "notes": "Correctness lock for a final tiled depth brick containing one valid output plane instead of requiring outD to be divisible by 24."
1257
  },
1258
  "attrs": { "kernel_shape": [5, 5, 5], "pads": [2, 2, 2, 2, 2, 2] },
1259
  "inputs": {
@@ -1429,7 +1427,7 @@
1429
  {
1430
  "name": "separable_k6_nopad_f16",
1431
  "provenance": {
1432
- "notes": "A float16 6x6 window is the smallest square reaching SEPARABLE_MIN_WINDOW=36 and exercises the separable two-dimensional pooling path."
1433
  },
1434
  "attrs": { "kernel_shape": [6, 6] },
1435
  "inputs": {
@@ -1444,7 +1442,7 @@
1444
  {
1445
  "name": "autopad_same_upper_rank3_route",
1446
  "provenance": {
1447
- "notes": "A 1-D SAME_UPPER pool is a legal ONNX graph whose output extent the port itself infers, and the generic rank-3 selector compared that extent against the explicit-pad arithmetic, which SAME cannot satisfy. The pinned values are exact: padding contributes negative infinity, so the maxima over the three windows are 2, 4 and 5."
1448
  },
1449
  "attrs": { "auto_pad": "SAME_UPPER", "kernel_shape": [3], "strides": [2] },
1450
  "inputs": {
@@ -1466,7 +1464,7 @@
1466
  {
1467
  "name": "autopad_same_upper_rank5_route",
1468
  "provenance": {
1469
- "notes": "A rank-5 SAME_UPPER pool derives output extents from automatic padding on all three spatial axes; volumetric path eligibility must use those derived extents."
1470
  },
1471
  "attrs": { "auto_pad": "SAME_UPPER", "kernel_shape": [3, 3, 3], "strides": [2, 2, 2] },
1472
  "inputs": {
@@ -1477,6 +1475,290 @@
1477
  }
1478
  },
1479
  "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 2, 2], "tolerance": 0 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1480
  }
1481
  ]
1482
  }
 
1178
  {
1179
  "name": "f16_nchw_realistic_k3s2_pad1",
1180
  "provenance": {
1181
+ "notes": "A float16 [1,64,56,56] pooling case with a 3x3 window, stride two, and padding one checks padded edge windows."
1182
  },
1183
  "attrs": { "kernel_shape": [3, 3], "strides": [2, 2], "pads": [1, 1, 1, 1] },
1184
  "inputs": {
 
1237
  },
1238
  {
1239
  "name": "ncdhw3d_f32_k5s1pad2_tiled",
1240
+ "provenance": { "notes": "Compact 5x5x5 stride-one, pad-two pooling checks all spatial boundaries." },
 
 
1241
  "attrs": { "kernel_shape": [5, 5, 5], "pads": [2, 2, 2, 2, 2, 2] },
1242
  "inputs": {
1243
  "x": {
 
1251
  {
1252
  "name": "ncdhw3d_f32_k5s1pad2_tiled_partial_depth",
1253
  "provenance": {
1254
+ "notes": "The final depth region contains one valid output plane, checking a non-multiple-of-24 output depth."
1255
  },
1256
  "attrs": { "kernel_shape": [5, 5, 5], "pads": [2, 2, 2, 2, 2, 2] },
1257
  "inputs": {
 
1427
  {
1428
  "name": "separable_k6_nopad_f16",
1429
  "provenance": {
1430
+ "notes": "A float16 max pool with a 6x6 (36-element) window and no padding reduces a 16x16 input to an 11x11 output over 2 channels."
1431
  },
1432
  "attrs": { "kernel_shape": [6, 6] },
1433
  "inputs": {
 
1442
  {
1443
  "name": "autopad_same_upper_rank3_route",
1444
  "provenance": {
1445
+ "notes": "A 1-D SAME_UPPER pool infers its output extent from implicit padding. Padding contributes negative infinity, so the three window maxima are exactly 2, 4 and 5."
1446
  },
1447
  "attrs": { "auto_pad": "SAME_UPPER", "kernel_shape": [3], "strides": [2] },
1448
  "inputs": {
 
1464
  {
1465
  "name": "autopad_same_upper_rank5_route",
1466
  "provenance": {
1467
+ "notes": "A rank-5 (3-D) max pool with a 3x3x3 window, stride 2 and SAME_UPPER automatic padding reduces a 3x3x3 input to a 2x2x2 output."
1468
  },
1469
  "attrs": { "auto_pad": "SAME_UPPER", "kernel_shape": [3, 3, 3], "strides": [2, 2, 2] },
1470
  "inputs": {
 
1475
  }
1476
  },
1477
  "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 2, 2], "tolerance": 0 } }
1478
+ },
1479
+ {
1480
+ "name": "uint8_same_upper",
1481
+ "provenance": {
1482
+ "notes": "Asymmetric automatic padding with uint8 storage; increasing inputs pin each maximum and its flattened index."
1483
+ },
1484
+ "attrs": { "auto_pad": "SAME_UPPER", "kernel_shape": [3, 3], "strides": [2, 2] },
1485
+ "inputs": {
1486
+ "x": {
1487
+ "dtype": "uint8",
1488
+ "shape": [1, 1, 4, 4],
1489
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ceil_mode_partial_windows_input_x" } }
1490
+ }
1491
+ },
1492
+ "outputs": {
1493
+ "y": {
1494
+ "dtype": "uint8",
1495
+ "shape": [1, 1, 2, 2],
1496
+ "data": { "kind": "values", "values": [11, 12, 15, 16] },
1497
+ "tolerance": 0
1498
+ }
1499
+ }
1500
+ },
1501
+ {
1502
+ "name": "uint8_same_upper_indices",
1503
+ "provenance": {
1504
+ "notes": "Asymmetric automatic padding with uint8 storage; increasing inputs pin each maximum and its flattened index."
1505
+ },
1506
+ "attrs": { "auto_pad": "SAME_UPPER", "kernel_shape": [3, 3], "strides": [2, 2] },
1507
+ "inputs": {
1508
+ "x": {
1509
+ "dtype": "uint8",
1510
+ "shape": [1, 1, 4, 4],
1511
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ceil_mode_partial_windows_input_x" } }
1512
+ }
1513
+ },
1514
+ "outputs": {
1515
+ "y": {
1516
+ "dtype": "uint8",
1517
+ "shape": [1, 1, 2, 2],
1518
+ "data": { "kind": "values", "values": [11, 12, 15, 16] },
1519
+ "tolerance": 0
1520
+ },
1521
+ "indices": {
1522
+ "dtype": "uint32",
1523
+ "shape": [1, 1, 2, 2],
1524
+ "data": { "kind": "values", "values": [10, 11, 14, 15] },
1525
+ "tolerance": 0
1526
+ }
1527
+ }
1528
+ },
1529
+ {
1530
+ "name": "uint8_same_lower",
1531
+ "provenance": {
1532
+ "notes": "Asymmetric automatic padding with uint8 storage; increasing inputs pin each maximum and its flattened index."
1533
+ },
1534
+ "attrs": { "auto_pad": "SAME_LOWER", "kernel_shape": [3, 3], "strides": [2, 2] },
1535
+ "inputs": {
1536
+ "x": {
1537
+ "dtype": "uint8",
1538
+ "shape": [1, 1, 4, 4],
1539
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ceil_mode_partial_windows_input_x" } }
1540
+ }
1541
+ },
1542
+ "outputs": {
1543
+ "y": {
1544
+ "dtype": "uint8",
1545
+ "shape": [1, 1, 2, 2],
1546
+ "data": { "kind": "values", "values": [6, 8, 14, 16] },
1547
+ "tolerance": 0
1548
+ }
1549
+ }
1550
+ },
1551
+ {
1552
+ "name": "uint8_same_lower_indices",
1553
+ "provenance": {
1554
+ "notes": "Asymmetric automatic padding with uint8 storage; increasing inputs pin each maximum and its flattened index."
1555
+ },
1556
+ "attrs": { "auto_pad": "SAME_LOWER", "kernel_shape": [3, 3], "strides": [2, 2] },
1557
+ "inputs": {
1558
+ "x": {
1559
+ "dtype": "uint8",
1560
+ "shape": [1, 1, 4, 4],
1561
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ceil_mode_partial_windows_input_x" } }
1562
+ }
1563
+ },
1564
+ "outputs": {
1565
+ "y": {
1566
+ "dtype": "uint8",
1567
+ "shape": [1, 1, 2, 2],
1568
+ "data": { "kind": "values", "values": [6, 8, 14, 16] },
1569
+ "tolerance": 0
1570
+ },
1571
+ "indices": {
1572
+ "dtype": "uint32",
1573
+ "shape": [1, 1, 2, 2],
1574
+ "data": { "kind": "values", "values": [5, 7, 13, 15] },
1575
+ "tolerance": 0
1576
+ }
1577
+ }
1578
+ },
1579
+ {
1580
+ "name": "ort_maxpool_large_kernel_small_output_pad2",
1581
+ "provenance": {
1582
+ "source": "onnxruntime/test/providers/cpu/nn/pool_op_test.cc",
1583
+ "test": "PoolTest.MaxPool_LargeKernelMultiElementOutput_WebGpu",
1584
+ "notes": "A 12x12 max-pool window with padding 2 over a 24x24 input produces a 17x17 output, a large window behind a small output. Transcribed from onnxruntime's PoolTest.MaxPool_LargeKernelMultiElementOutput_WebGpu."
1585
+ },
1586
+ "attrs": { "kernel_shape": [12, 12], "pads": [2, 2, 2, 2], "strides": [1, 1], "ceil_mode": 0 },
1587
+ "inputs": {
1588
+ "x": { "dtype": "float32", "shape": [1, 2, 24, 24], "data": { "kind": "linspace", "start": 0.0, "end": 11.51 } }
1589
+ },
1590
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 17, 17], "tolerance": 0.00001 } }
1591
+ },
1592
+ {
1593
+ "name": "window_parallel_nchw2d_nan_window_f32",
1594
+ "provenance": {
1595
+ "notes": "NaNs inside a 144-tap window must survive both the per-invocation fold and the cross-invocation reduction, which carries an explicit NaN flag because subgroupMax does not propagate one."
1596
+ },
1597
+ "attrs": { "kernel_shape": [12, 12], "strides": [1, 1], "pads": [2, 2, 2, 2], "ceil_mode": 0 },
1598
+ "inputs": {
1599
+ "x": {
1600
+ "dtype": "float32",
1601
+ "shape": [1, 2, 24, 24],
1602
+ "data": {
1603
+ "kind": "fillFloat32",
1604
+ "sinStep": 0.031,
1605
+ "cosStep": 0.053,
1606
+ "scale": 0.7,
1607
+ "nanStart": 300.0,
1608
+ "nanCount": 4.0
1609
+ }
1610
+ }
1611
+ },
1612
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 17, 17], "tolerance": 0, "allowNaN": true } }
1613
+ },
1614
+ {
1615
+ "name": "window_parallel_ncl1d_k256",
1616
+ "provenance": {
1617
+ "notes": "A 1-D 256-tap window over 64 outputs: the serial route would give each output one invocation looping 256 taps."
1618
+ },
1619
+ "attrs": { "kernel_shape": [256], "strides": [256], "pads": [0, 0] },
1620
+ "inputs": {
1621
+ "x": {
1622
+ "dtype": "float32",
1623
+ "shape": [1, 16, 1024],
1624
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.023, "scale": 0.5 }
1625
+ }
1626
+ },
1627
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 4], "tolerance": 0.00001 } }
1628
+ },
1629
+ {
1630
+ "name": "window_parallel_ncdhw3d_k8_pads_512_taps",
1631
+ "provenance": {
1632
+ "notes": "A 3-D 8x8x8 window with one pad on every face: out-of-bounds taps must leave the maximum untouched and still be excluded from the in-bounds tap count."
1633
+ },
1634
+ "attrs": { "kernel_shape": [8, 8, 8], "strides": [8, 8, 8], "pads": [1, 1, 1, 1, 1, 1] },
1635
+ "inputs": {
1636
+ "x": {
1637
+ "dtype": "float32",
1638
+ "shape": [1, 4, 16, 16, 16],
1639
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 0.5 }
1640
+ }
1641
+ },
1642
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 2, 2, 2], "tolerance": 0.00001 } }
1643
+ },
1644
+ {
1645
+ "name": "window_parallel_nchw2d_f16_k12p2",
1646
+ "provenance": { "notes": "float16 max pooling through the cooperative window reduction." },
1647
+ "attrs": { "kernel_shape": [12, 12], "strides": [1, 1], "pads": [2, 2, 2, 2], "ceil_mode": 0 },
1648
+ "inputs": {
1649
+ "x": {
1650
+ "dtype": "float16",
1651
+ "shape": [1, 3, 24, 24],
1652
+ "data": { "kind": "fillFloat32", "sinStep": 0.041, "cosStep": 0.057, "scale": 1.1 }
1653
+ }
1654
+ },
1655
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 3, 17, 17], "tolerance": 0.003 } }
1656
+ },
1657
+ {
1658
+ "name": "window_parallel_nchw2d_dilation_stride_ceil_overhang",
1659
+ "provenance": {
1660
+ "notes": "ceil_mode=1 with dilation 2 and stride 5: the last window overhangs the input, and an output whose window holds no in-bounds tap must still write 0 rather than negative infinity."
1661
+ },
1662
+ "attrs": {
1663
+ "kernel_shape": [12, 12],
1664
+ "strides": [5, 5],
1665
+ "pads": [3, 2, 4, 3],
1666
+ "dilations": [2, 2],
1667
+ "ceil_mode": 1
1668
+ },
1669
+ "inputs": {
1670
+ "x": {
1671
+ "dtype": "float32",
1672
+ "shape": [1, 4, 30, 30],
1673
+ "data": { "kind": "fillFloat32", "sinStep": 0.029, "cosStep": 0.047, "scale": 0.8 }
1674
+ }
1675
+ },
1676
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 4, 4], "tolerance": 0.00001 } }
1677
+ },
1678
+ {
1679
+ "name": "window_parallel_nchw2d_nan_window_f32_small_output",
1680
+ "provenance": {
1681
+ "notes": "NaNs inside a 144-tap window must survive both the per-invocation fold and the cross-invocation reduction, which carries an explicit NaN flag because subgroupMax does not propagate one."
1682
+ },
1683
+ "attrs": { "kernel_shape": [12, 12], "strides": [2, 2], "pads": [2, 2, 2, 2], "ceil_mode": 0 },
1684
+ "inputs": {
1685
+ "x": {
1686
+ "dtype": "float32",
1687
+ "shape": [1, 1, 24, 24],
1688
+ "data": {
1689
+ "kind": "fillFloat32",
1690
+ "sinStep": 0.031,
1691
+ "cosStep": 0.053,
1692
+ "scale": 0.7,
1693
+ "nanStart": 300.0,
1694
+ "nanCount": 4.0
1695
+ }
1696
+ }
1697
+ },
1698
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 9, 9], "tolerance": 0, "allowNaN": true } }
1699
+ },
1700
+ {
1701
+ "name": "window_parallel_nchw2d_f16_k12p2_small_output",
1702
+ "provenance": { "notes": "float16 max pooling through the cooperative window reduction." },
1703
+ "attrs": { "kernel_shape": [12, 12], "strides": [2, 2], "pads": [2, 2, 2, 2], "ceil_mode": 0 },
1704
+ "inputs": {
1705
+ "x": {
1706
+ "dtype": "float16",
1707
+ "shape": [1, 1, 24, 24],
1708
+ "data": { "kind": "fillFloat32", "sinStep": 0.041, "cosStep": 0.057, "scale": 1.1 }
1709
+ }
1710
+ },
1711
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1, 9, 9], "tolerance": 0.003 } }
1712
+ },
1713
+ {
1714
+ "name": "window_parallel_outputs_to_taps_boundary_128",
1715
+ "attrs": { "kernel_shape": [128], "strides": [1] },
1716
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 255], "data": { "kind": "fillFloat32", "scale": 0.3 } } },
1717
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 128] } }
1718
+ },
1719
+ {
1720
+ "name": "window_parallel_outputs_to_taps_boundary_129",
1721
+ "attrs": { "kernel_shape": [128], "strides": [1] },
1722
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 256], "data": { "kind": "fillFloat32", "scale": 0.3 } } },
1723
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 129] } }
1724
+ },
1725
+ {
1726
+ "name": "window_parallel_genuinely_empty_dilated_window",
1727
+ "attrs": { "kernel_shape": [128], "dilations": [2], "pads": [127, 127] },
1728
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "constant", "value": 7.0 } } },
1729
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
1730
+ },
1731
+ {
1732
+ "name": "window_parallel_arbitrary_workgroup_96",
1733
+ "tunables": { "WORKGROUP_SIZE": 96 },
1734
+ "attrs": { "kernel_shape": [128], "strides": [128] },
1735
+ "inputs": {
1736
+ "x": {
1737
+ "dtype": "float32",
1738
+ "shape": [1, 1, 128],
1739
+ "data": {
1740
+ "kind": "values",
1741
+ "values": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 17.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
1742
+ }
1743
+ }
1744
+ },
1745
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
1746
+ },
1747
+ {
1748
+ "name": "window_parallel_arbitrary_workgroup_127",
1749
+ "tunables": { "WORKGROUP_SIZE": 127 },
1750
+ "attrs": { "kernel_shape": [128], "strides": [128] },
1751
+ "inputs": {
1752
+ "x": {
1753
+ "dtype": "float32",
1754
+ "shape": [1, 1, 128],
1755
+ "data": {
1756
+ "kind": "values",
1757
+ "values": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 17.0, 1.0]
1758
+ }
1759
+ }
1760
+ },
1761
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
1762
  }
1763
  ]
1764
  }