Xenova HF Staff commited on
Commit
45825ca
·
verified ·
1 Parent(s): 6037d52

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -48,23 +48,32 @@ Attributes and default values (overridable per request):
48
  | --- | --- |
49
  | `T` | `float32`, `float16` |
50
 
 
 
 
 
 
 
 
 
51
  ## Files
52
 
53
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
54
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
  - [`test.json`](build/webgpu/test.json) — correctness cases
56
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
57
  - [`average-pool2d-nchw-horizontal-reuse.wgsl.jinja`](build/webgpu/average-pool2d-nchw-horizontal-reuse.wgsl.jinja)
58
  - [`average-pool2d-nchw-w3s1-reuse.wgsl.jinja`](build/webgpu/average-pool2d-nchw-w3s1-reuse.wgsl.jinja)
59
  - [`pool-global-reduction.wgsl.jinja`](build/webgpu/pool-global-reduction.wgsl.jinja)
60
  - [`pool-window-nd.wgsl.jinja`](build/webgpu/pool-window-nd.wgsl.jinja)
 
61
  - [`pool-window-unroll.wgsl.jinja`](build/webgpu/pool-window-unroll.wgsl.jinja)
62
  - [`pool2d-nchw-separable.wgsl.jinja`](build/webgpu/pool2d-nchw-separable.wgsl.jinja)
63
 
64
  ## Use with `@huggingface/kernels`
65
 
66
  ```sh
67
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
68
  ```
69
 
70
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
48
  | --- | --- |
49
  | `T` | `float32`, `float16` |
50
 
51
+ ## Implementation variants
52
+
53
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
54
+
55
+ - `window_parallel_ncl1d` — Workgroup-cooperative average 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. Padded taps leave the divisor unless `count_include_pad` is set.
56
+ - `window_parallel_nchw2d` — Workgroup-cooperative average 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. Padded taps leave the divisor unless `count_include_pad` is set.
57
+ - `window_parallel_ncdhw3d` — Workgroup-cooperative average 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. Padded taps leave the divisor unless `count_include_pad` is set.
58
+
59
  ## Files
60
 
61
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
62
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
63
  - [`test.json`](build/webgpu/test.json) — correctness cases
64
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
65
  - [`average-pool2d-nchw-horizontal-reuse.wgsl.jinja`](build/webgpu/average-pool2d-nchw-horizontal-reuse.wgsl.jinja)
66
  - [`average-pool2d-nchw-w3s1-reuse.wgsl.jinja`](build/webgpu/average-pool2d-nchw-w3s1-reuse.wgsl.jinja)
67
  - [`pool-global-reduction.wgsl.jinja`](build/webgpu/pool-global-reduction.wgsl.jinja)
68
  - [`pool-window-nd.wgsl.jinja`](build/webgpu/pool-window-nd.wgsl.jinja)
69
+ - [`pool-window-reduction.wgsl.jinja`](build/webgpu/pool-window-reduction.wgsl.jinja)
70
  - [`pool-window-unroll.wgsl.jinja`](build/webgpu/pool-window-unroll.wgsl.jinja)
71
  - [`pool2d-nchw-separable.wgsl.jinja`](build/webgpu/pool2d-nchw-separable.wgsl.jinja)
72
 
73
  ## Use with `@huggingface/kernels`
74
 
75
  ```sh
76
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
77
  ```
78
 
79
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
build/webgpu/average-pool2d-nchw-horizontal-reuse.wgsl.jinja CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Four adjacent windows share source columns. For a K-wide, dilation-D window
@@ -22,8 +28,7 @@ fn load_value(validRow: bool, base: u32, col: i32) -> f32 {
22
 
23
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
24
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
25
- let q = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
26
- if (q >= COUNT4) { return; }
27
 
28
  let ow0 = (q % OUT_W4) * 4u;
29
  var t = q / OUT_W4;
 
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 windows share source columns. For a K-wide, dilation-D window
 
28
 
29
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
30
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
31
+ {{ flat_index_2d("WG", "q", "COUNT4", guardInline=true) }}
 
32
 
33
  let ow0 = (q % OUT_W4) * 4u;
34
  var t = q / OUT_W4;
build/webgpu/average-pool2d-nchw-w3s1-reuse.wgsl.jinja CHANGED
@@ -1,3 +1,11 @@
 
 
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // 2D NCHW AveragePool specialised for a 3-wide / stride-1 / pad-1 / dilation-1
@@ -32,10 +40,7 @@ fn col_valid(rowValid: bool, col: i32) -> u32 {
32
 
33
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
34
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
35
- let tile = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
36
- if (tile >= params.tileCount) {
37
- return;
38
- }
39
 
40
  let colTile = tile % OUT_W_Q; // which group-of-four columns in this row
41
  var t = tile / OUT_W_Q;
 
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 AveragePool specialised for a 3-wide / stride-1 / pad-1 / dilation-1
 
40
 
41
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
42
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
43
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "tile", "params.tileCount") }}
 
 
 
44
 
45
  let colTile = tile % OUT_W_Q; // which group-of-four columns in this row
46
  var t = tile / OUT_W_Q;
build/webgpu/bench.json CHANGED
@@ -36,6 +36,17 @@
36
  "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2 * 2" }]
37
  }
38
  },
 
 
 
 
 
 
 
 
 
 
 
39
  {
40
  "name": "average-pool-f32-b1c64-224x224-k7s2p3-generic",
41
  "preset": "smoke",
@@ -198,6 +209,81 @@
198
  "inputs": { "x": { "dtype": "float32", "shape": [8, 64, 56, 56] } },
199
  "outputs": { "y": { "dtype": "float32", "shape": [8, 64, 8, 8] } },
200
  "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
  ]
203
  }
 
36
  "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2 * 2" }]
37
  }
38
  },
39
+ {
40
+ "name": "nchw_f16_1x128x56x56_k3s1p1",
41
+ "preset": "model",
42
+ "vars": { "batch": 1, "channels": 128, "inH": 56, "inW": 56 },
43
+ "attrs": { "kernel_shape": [3, 3], "strides": [1, 1], "pads": [1, 1, 1, 1] },
44
+ "inputs": { "x": { "dtype": "float16", "shape": [1, 128, 56, 56], "dist": "normal", "seed": 207 } },
45
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 128, 56, 56], "dist": "empty" } },
46
+ "bench": {
47
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2 * 2" }]
48
+ }
49
+ },
50
  {
51
  "name": "average-pool-f32-b1c64-224x224-k7s2p3-generic",
52
  "preset": "smoke",
 
209
  "inputs": { "x": { "dtype": "float32", "shape": [8, 64, 56, 56] } },
210
  "outputs": { "y": { "dtype": "float32", "shape": [8, 64, 8, 8] } },
211
  "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
212
+ },
213
+ {
214
+ "name": "average-pool-f32-b1c32-24x24-k12s1p2-window-parallel-starved",
215
+ "preset": "stress",
216
+ "provenance": {
217
+ "source": "synthetic benchmark",
218
+ "notes": "A 144-tap padded window produces 9,248 outputs across 32 channels. This brackets the region where output parallelism exceeds the number of taps."
219
+ },
220
+ "attrs": { "count_include_pad": 0, "kernel_shape": [12, 12], "strides": [1, 1], "pads": [2, 2, 2, 2] },
221
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 32, 24, 24], "dist": "normal", "seed": 4301, "scale": 0.2 } },
222
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 17, 17], "dist": "empty" } },
223
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
224
+ },
225
+ {
226
+ "name": "average-pool-f32-b1c64-24x24-k12s1p2-serial-side-of-gate",
227
+ "preset": "stress",
228
+ "provenance": {
229
+ "source": "synthetic benchmark",
230
+ "notes": "Measures average pooling with a 12x12 window and padding 2 over 64 channels of a 24x24 input, producing a 17x17 (18,496-element) output."
231
+ },
232
+ "attrs": { "count_include_pad": 0, "kernel_shape": [12, 12], "strides": [1, 1], "pads": [2, 2, 2, 2] },
233
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 64, 24, 24], "dist": "normal", "seed": 4302, "scale": 0.2 } },
234
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 17, 17], "dist": "empty" } },
235
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
236
+ },
237
+ {
238
+ "name": "average-pool-f32-b1c16-56x56-k28s28-window-parallel-extreme-starved",
239
+ "preset": "stress",
240
+ "provenance": {
241
+ "source": "synthetic benchmark",
242
+ "notes": "A 784-tap non-overlapping window leaving 64 outputs: the serial route dispatches a single workgroup and loops 784 taps on each of 64 invocations."
243
+ },
244
+ "attrs": { "count_include_pad": 0, "kernel_shape": [28, 28], "strides": [28, 28], "pads": [0, 0, 0, 0] },
245
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 16, 56, 56], "dist": "normal", "seed": 4303, "scale": 0.2 } },
246
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 2, 2], "dist": "empty" } },
247
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
248
+ },
249
+ {
250
+ "name": "average-pool-f32-b1c64-14x14-k14s1p6-padded-near-global",
251
+ "preset": "stress",
252
+ "provenance": {
253
+ "source": "synthetic benchmark",
254
+ "notes": "A padded near-global pool from a classifier head: a 196-tap window over a 14x14 map with pad 6 leaves 10816 outputs, 43 serial dispatch groups."
255
+ },
256
+ "attrs": { "count_include_pad": 0, "kernel_shape": [14, 14], "strides": [1, 1], "pads": [6, 6, 6, 6] },
257
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 64, 14, 14], "dist": "normal", "seed": 4304, "scale": 0.2 } },
258
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 13, 13], "dist": "empty" } },
259
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.y)) * 4" }] }
260
+ },
261
+ {
262
+ "name": "occupancy-window_parallel_nchw2d_count_include_pad_k12p2_small_output",
263
+ "preset": "stress",
264
+ "attrs": {
265
+ "kernel_shape": [12, 12],
266
+ "strides": [2, 2],
267
+ "pads": [2, 2, 2, 2],
268
+ "count_include_pad": 1,
269
+ "ceil_mode": 0
270
+ },
271
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 24, 24], "dist": "normal", "seed": 5482, "scale": 0.2 } },
272
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 9, 9] } }
273
+ },
274
+ {
275
+ "name": "occupancy-window_parallel_outputs_to_taps_boundary_128",
276
+ "preset": "stress",
277
+ "attrs": { "kernel_shape": [128], "strides": [1] },
278
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 255], "dist": "normal", "seed": 5482, "scale": 0.2 } },
279
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 128] } }
280
+ },
281
+ {
282
+ "name": "occupancy-window_parallel_outputs_to_taps_boundary_129",
283
+ "preset": "stress",
284
+ "attrs": { "kernel_shape": [128], "strides": [1] },
285
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 256], "dist": "normal", "seed": 5482, "scale": 0.2 } },
286
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 129] } }
287
  }
288
  ]
289
  }
build/webgpu/manifest.json CHANGED
@@ -20,10 +20,16 @@
20
  "kernel_shape": { "required": true }
21
  },
22
  "typeConstraints": { "T": ["float32", "float16"] },
23
- "tunables": { "WORKGROUP_SIZE": { "default": 256 }, "SEPARABLE_MIN_WINDOW": { "default": 25 } },
 
 
 
 
 
24
  "derive": {
25
  "spatialRank": "ranks.x - 2",
26
  "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
 
27
  "kernelShapeLengthOk": "not has(attrs, \"kernel_shape\") or (attrs.kernel_shape | length) == spatialRank",
28
  "stridesLengthOk": "not has(attrs, \"strides\") or (attrs.strides | length) == spatialRank",
29
  "dilationsLengthOk": "not has(attrs, \"dilations\") or (attrs.dilations | length) == spatialRank",
@@ -43,7 +49,6 @@
43
  "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
44
  "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
45
  "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
46
- "poolBaseOk": "f16Ok(dtypes.T) and 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)",
47
  "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",
48
  "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",
49
  "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",
@@ -54,20 +59,78 @@
54
  "autoPadTotalD": "max(0, (dim(shapes.y, 2) - 1) * strideD + ((kernelD - 1) * dilationD + 1) - dim(shapes.x, 2)) if ranks.x == 5 else 0",
55
  "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",
56
  "autoPadTotalW": "max(0, (dim(shapes.y, ranks.x - 1) - 1) * strideW + ((kernelW - 1) * dilationW + 1) - dim(shapes.x, ranks.x - 1))",
57
- "effectivePadFront": "(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",
58
- "effectivePadBack": "autoPadTotalD - effectivePadFront if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padBack",
59
- "effectivePadTop": "(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",
60
- "effectivePadLeft": "(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",
61
- "effectivePadBottom": "autoPadTotalH - effectivePadTop if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padBottom",
62
- "effectivePadRight": "autoPadTotalW - effectivePadLeft if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padRight",
63
- "narrowF32W3UnrolledPreferred": "narrowSubgroupRange and dtypes.T == \"f32\"",
64
- "globalWindowParallelCovered": "poolBaseOk 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",
65
  "separableTmpElements": "dim(shapes.x, 0) * dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.y, 3)",
66
  "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)",
67
- "wholeWindowDenominator": "attrs.ceil_mode == 0 and (attrs.count_include_pad == 1 or (effectivePadTop == 0 and effectivePadLeft == 0 and effectivePadBottom == 0 and effectivePadRight == 0))",
68
- "scalar": "dtypes.T"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  },
70
- "bindings": { "y_2": { "name": "y", "buffer": "storage", "elementType": "$outputElement" } },
71
  "variants": [
72
  {
73
  "id": "global_window_parallel",
@@ -81,7 +144,6 @@
81
  "id": "main",
82
  "name": "AveragePool.GlobalWindowParallel",
83
  "shader": "pool-global-reduction.wgsl.jinja",
84
- "subgroupCollectivesWidth": "portable",
85
  "derive": {
86
  "op": "\"average\"",
87
  "vec4": "inner(shapes.x, 1) % 4 == 0",
@@ -97,10 +159,85 @@
97
  }
98
  ]
99
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  {
101
  "id": "nchw2d_k7s1_reuse_tail",
102
  "priority": 34,
103
- "when": ["poolBaseOk", "ranks.x == 4", "kernelH == 7", "kernelW == 7", "strideH == 1", "strideW == 1", "dilationH == 1", "dilationW == 1", "effectivePadTop == 3", "effectivePadBottom == 3", "effectivePadLeft == 3", "effectivePadRight == 3", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 != 0", "dim(shapes.y, 2) == dim(shapes.x, 2)", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
104
  "derive": { "outputElement": "dtypes.T" },
105
  "passes": [
106
  {
@@ -116,16 +253,11 @@
116
  "dilationWSpec": 1,
117
  "padTopSpec": 3,
118
  "padLeftSpec": 3,
119
- "inH": "dim(shapes.x, 2)",
120
- "inW": "dim(shapes.x, 3)",
121
- "inPlane": "dim(shapes.x, 2) * dim(shapes.x, 3)",
122
- "outH": "dim(shapes.y, 2)",
123
- "outW": "dim(shapes.y, 3)",
124
  "outW4": "ceilDiv(dim(shapes.y, 3), 4)",
125
  "count4": "dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)",
126
  "scalarOutput": true
127
  },
128
- "bindings": ["x", "y_2"],
129
  "dispatch": {
130
  "x": "min(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
131
  "y": "ceilDiv(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -137,8 +269,8 @@
137
  {
138
  "id": "nchw2d_w3s1_reuse",
139
  "priority": 30,
140
- "when": ["poolBaseOk", "ranks.x == 4", "kernelW == 3", "strideW == 1", "dilationW == 1", "effectivePadLeft == 1", "effectivePadRight == 1", "kernelH >= 1", "kernelH <= 3", "strideH == 1", "dilationH >= 1", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 == 0", "dim(shapes.y, 2) == dim(shapes.x, 2) + effectivePadTop + effectivePadBottom - ((kernelH - 1) * dilationH + 1) + 1", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
141
- "demoteWhen": ["narrowF32W3UnrolledPreferred"],
142
  "passes": [
143
  {
144
  "id": "main",
@@ -147,12 +279,7 @@
147
  "derive": {
148
  "kernelHSpec": "kernelH",
149
  "dilationHSpec": "dilationH",
150
- "padTopSpec": "effectivePadTop",
151
- "inH": "dim(shapes.x, 2)",
152
- "inW": "dim(shapes.x, 3)",
153
- "inPlane": "dim(shapes.x, 2) * dim(shapes.x, 3)",
154
- "outH": "dim(shapes.y, 2)",
155
- "outW": "dim(shapes.y, 3)",
156
  "outWQuarter": "floor(dim(shapes.y, 3) / 4)"
157
  },
158
  "bindings": [
@@ -171,7 +298,7 @@
171
  {
172
  "id": "nchw2d_w3s1_reuse_tail",
173
  "priority": 29,
174
- "when": ["poolBaseOk", "ranks.x == 4", "kernelW == 3", "strideW == 1", "dilationW == 1", "effectivePadLeft == 1", "effectivePadRight == 1", "kernelH >= 1", "kernelH <= 3", "strideH == 1", "dilationH >= 1", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 != 0", "dim(shapes.y, 2) == dim(shapes.x, 2) + effectivePadTop + effectivePadBottom - ((kernelH - 1) * dilationH + 1) + 1", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
175
  "derive": { "outputElement": "dtypes.T" },
176
  "passes": [
177
  {
@@ -185,18 +312,13 @@
185
  "strideWSpec": 1,
186
  "dilationHSpec": "dilationH",
187
  "dilationWSpec": 1,
188
- "padTopSpec": "effectivePadTop",
189
  "padLeftSpec": 1,
190
- "inH": "dim(shapes.x, 2)",
191
- "inW": "dim(shapes.x, 3)",
192
- "inPlane": "dim(shapes.x, 2) * dim(shapes.x, 3)",
193
- "outH": "dim(shapes.y, 2)",
194
- "outW": "dim(shapes.y, 3)",
195
  "outW4": "ceilDiv(dim(shapes.y, 3), 4)",
196
  "count4": "dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)",
197
  "scalarOutput": true
198
  },
199
- "bindings": ["x", "y_2"],
200
  "dispatch": {
201
  "x": "min(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
202
  "y": "ceilDiv(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -209,7 +331,7 @@
209
  "id": "nchw2d_separable",
210
  "priority": 22,
211
  "when": ["poolBaseOk", "ranks.x == 4", "ranks.y == 4", "dim(shapes.y, 2) == inferredOutH", "dim(shapes.y, 3) == inferredOutW", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0", "kernelH > 1", "kernelW > 1", "kernelH * kernelW >= tunables.SEPARABLE_MIN_WINDOW", "strideH < kernelH", "wholeWindowDenominator", "separableFits"],
212
- "derive": { "scalar": "dtypes.T", "pIs1": false, "pIs2": false },
213
  "intermediates": [{ "id": "tmp", "dtype": "float32", "shape": "[separableTmpElements]" }],
214
  "passes": [
215
  {
@@ -219,7 +341,7 @@
219
  "derive": { "axisSpec": "\"w\"", "op": "\"avg\"", "windowArea": "kernelH * kernelW" },
220
  "bindings": [
221
  "x",
222
- { "name": "tmp", "buffer": "storage", "elementType": "f32" },
223
  {
224
  "name": "params",
225
  "struct": [
@@ -229,7 +351,7 @@
229
  { "name": "kernelW", "type": "u32", "value": "kernelW" },
230
  { "name": "strideW", "type": "u32", "value": "strideW" },
231
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
232
- { "name": "padW", "type": "i32", "value": "effectivePadLeft" },
233
  { "name": "tmpCount", "type": "u32", "value": "separableTmpElements" }
234
  ]
235
  }
@@ -257,7 +379,7 @@
257
  { "name": "kernelH", "type": "u32", "value": "kernelH" },
258
  { "name": "strideH", "type": "u32", "value": "strideH" },
259
  { "name": "dilationH", "type": "u32", "value": "dilationH" },
260
- { "name": "padH", "type": "i32", "value": "effectivePadTop" },
261
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
262
  ]
263
  }
@@ -287,8 +409,8 @@
287
  "strideWSpec": "strideW",
288
  "dilationHSpec": "dilationH",
289
  "dilationWSpec": "dilationW",
290
- "padTopSpec": "effectivePadTop",
291
- "padLeftSpec": "effectivePadLeft",
292
  "inH": "dim(shapes.x, 2)",
293
  "inW": "dim(shapes.x, 3)",
294
  "outH": "dim(shapes.y, 2)",
@@ -332,7 +454,7 @@
332
  { "name": "kernelW", "type": "u32", "value": "kernelW" },
333
  { "name": "strideW", "type": "u32", "value": "strideW" },
334
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
335
- { "name": "padLeft", "type": "i32", "value": "effectivePadLeft" },
336
  { "name": "padRight", "type": "i32", "value": "effectivePadRight" },
337
  { "name": "countIncludePad", "type": "u32", "value": "attrs.count_include_pad" },
338
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
@@ -353,7 +475,7 @@
353
  "passes": [
354
  {
355
  "id": "main",
356
- "name": "nchw2d",
357
  "shader": "pool-window-nd.wgsl.jinja",
358
  "derive": { "op": "\"average\"", "spatialRankSpec": 2 },
359
  "bindings": [
@@ -373,8 +495,8 @@
373
  { "name": "strideW", "type": "u32", "value": "strideW" },
374
  { "name": "dilationH", "type": "u32", "value": "dilationH" },
375
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
376
- { "name": "padTop", "type": "i32", "value": "effectivePadTop" },
377
- { "name": "padLeft", "type": "i32", "value": "effectivePadLeft" },
378
  { "name": "padBottom", "type": "i32", "value": "effectivePadBottom" },
379
  { "name": "padRight", "type": "i32", "value": "effectivePadRight" },
380
  { "name": "countIncludePad", "type": "u32", "value": "attrs.count_include_pad" },
@@ -423,9 +545,9 @@
423
  { "name": "dilationD", "type": "u32", "value": "dilationD" },
424
  { "name": "dilationH", "type": "u32", "value": "dilationH" },
425
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
426
- { "name": "padFront", "type": "i32", "value": "effectivePadFront" },
427
- { "name": "padTop", "type": "i32", "value": "effectivePadTop" },
428
- { "name": "padLeft", "type": "i32", "value": "effectivePadLeft" },
429
  { "name": "padBack", "type": "i32", "value": "effectivePadBack" },
430
  { "name": "padBottom", "type": "i32", "value": "effectivePadBottom" },
431
  { "name": "padRight", "type": "i32", "value": "effectivePadRight" },
@@ -445,7 +567,7 @@
445
  {
446
  "id": "nchw2d_k7s2_reuse",
447
  "priority": 36,
448
- "when": ["poolBaseOk", "ranks.x == 4", "kernelH == 7", "kernelW == 7", "strideH == 2", "strideW == 2", "dilationH == 1", "dilationW == 1", "effectivePadTop == 3", "effectivePadBottom == 3", "effectivePadLeft == 3", "effectivePadRight == 3", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.y, 2) == inferredOutH", "dim(shapes.y, 3) == inferredOutW", "dim(shapes.y, 3) % 4 == 0", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
449
  "derive": { "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
450
  "passes": [
451
  {
@@ -461,15 +583,10 @@
461
  "dilationWSpec": 1,
462
  "padTopSpec": 3,
463
  "padLeftSpec": 3,
464
- "inH": "dim(shapes.x, 2)",
465
- "inW": "dim(shapes.x, 3)",
466
- "inPlane": "dim(shapes.x, 2) * dim(shapes.x, 3)",
467
- "outH": "dim(shapes.y, 2)",
468
- "outW": "dim(shapes.y, 3)",
469
  "outW4": "dim(shapes.y, 3) / 4",
470
  "count4": "numel(shapes.y) / 4"
471
  },
472
- "bindings": ["x", "y_2"],
473
  "dispatch": {
474
  "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
475
  "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -481,7 +598,7 @@
481
  {
482
  "id": "nchw2d_k7s1_reuse",
483
  "priority": 35,
484
- "when": ["poolBaseOk", "ranks.x == 4", "kernelH == 7", "kernelW == 7", "strideH == 1", "strideW == 1", "dilationH == 1", "dilationW == 1", "effectivePadTop == 3", "effectivePadBottom == 3", "effectivePadLeft == 3", "effectivePadRight == 3", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 == 0", "dim(shapes.y, 2) == dim(shapes.x, 2)", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
485
  "derive": { "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
486
  "passes": [
487
  {
@@ -497,15 +614,10 @@
497
  "dilationWSpec": 1,
498
  "padTopSpec": 3,
499
  "padLeftSpec": 3,
500
- "inH": "dim(shapes.x, 2)",
501
- "inW": "dim(shapes.x, 3)",
502
- "inPlane": "dim(shapes.x, 2) * dim(shapes.x, 3)",
503
- "outH": "dim(shapes.y, 2)",
504
- "outW": "dim(shapes.y, 3)",
505
  "outW4": "dim(shapes.y, 3) / 4",
506
  "count4": "numel(shapes.y) / 4"
507
  },
508
- "bindings": ["x", "y_2"],
509
  "dispatch": {
510
  "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
511
  "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
 
20
  "kernel_shape": { "required": true }
21
  },
22
  "typeConstraints": { "T": ["float32", "float16"] },
23
+ "tunables": {
24
+ "WORKGROUP_SIZE": { "default": 256 },
25
+ "SEPARABLE_MIN_WINDOW": { "default": 25 },
26
+ "WINDOW_PARALLEL_MIN_TAPS": { "default": 128 },
27
+ "WINDOW_PARALLEL_MAX_SERIAL_GROUPS": { "default": 64 }
28
+ },
29
  "derive": {
30
  "spatialRank": "ranks.x - 2",
31
  "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
32
+ "variableSubgroup16To32": "device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 16 and device.adapterInfo.subgroupMaxSize == 32",
33
  "kernelShapeLengthOk": "not has(attrs, \"kernel_shape\") or (attrs.kernel_shape | length) == spatialRank",
34
  "stridesLengthOk": "not has(attrs, \"strides\") or (attrs.strides | length) == spatialRank",
35
  "dilationsLengthOk": "not has(attrs, \"dilations\") or (attrs.dilations | length) == spatialRank",
 
49
  "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
50
  "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
51
  "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
 
52
  "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",
53
  "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",
54
  "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",
 
59
  "autoPadTotalD": "max(0, (dim(shapes.y, 2) - 1) * strideD + ((kernelD - 1) * dilationD + 1) - dim(shapes.x, 2)) if ranks.x == 5 else 0",
60
  "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",
61
  "autoPadTotalW": "max(0, (dim(shapes.y, ranks.x - 1) - 1) * strideW + ((kernelW - 1) * dilationW + 1) - dim(shapes.x, ranks.x - 1))",
62
+ "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",
63
+ "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",
64
+ "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",
65
+ "windowTaps": "kernelD * kernelH * kernelW",
66
+ "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",
 
 
 
67
  "separableTmpElements": "dim(shapes.x, 0) * dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.y, 3)",
68
  "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)",
69
+ "poolBaseOk": "f16Ok(dtypes.T) and 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)",
70
+ "effectivePadBack": "autoPadTotalD - effectivePadD if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padBack",
71
+ "effectivePadBottom": "autoPadTotalH - effectivePadH if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padBottom",
72
+ "effectivePadRight": "autoPadTotalW - effectivePadW if (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padRight",
73
+ "narrowF32W3UnrolledPreferred": "narrowSubgroupRange and dtypes.T == \"f32\"",
74
+ "globalWindowParallelCovered": "poolBaseOk 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",
75
+ "wholeWindowDenominator": "attrs.ceil_mode == 0 and (attrs.count_include_pad == 1 or (effectivePadH == 0 and effectivePadW == 0 and effectivePadBottom == 0 and effectivePadRight == 0))",
76
+ "scalar": "dtypes.T",
77
+ "inH": "dim(shapes.x, 2)",
78
+ "inPlane": "dim(shapes.x, 2) * dim(shapes.x, 3)",
79
+ "inW": "dim(shapes.x, 3)",
80
+ "outH": "dim(shapes.y, 2)",
81
+ "outW": "dim(shapes.y, 3)"
82
+ },
83
+ "bindings": {
84
+ "y_main": { "name": "y", "elementType": "$outputElement" },
85
+ "params_window_ncl1d": {
86
+ "name": "params",
87
+ "struct": [
88
+ { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
89
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 2)" },
90
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 2)" },
91
+ { "name": "strideW", "type": "u32", "value": "strideW" },
92
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
93
+ { "name": "padW", "type": "i32", "value": "effectivePadW" }
94
+ ]
95
+ },
96
+ "params_window_nchw2d": {
97
+ "name": "params",
98
+ "struct": [
99
+ { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
100
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
101
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 3)" },
102
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 2)" },
103
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 3)" },
104
+ { "name": "strideH", "type": "u32", "value": "strideH" },
105
+ { "name": "strideW", "type": "u32", "value": "strideW" },
106
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
107
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
108
+ { "name": "padH", "type": "i32", "value": "effectivePadH" },
109
+ { "name": "padW", "type": "i32", "value": "effectivePadW" }
110
+ ]
111
+ },
112
+ "params_window_ncdhw3d": {
113
+ "name": "params",
114
+ "struct": [
115
+ { "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
116
+ { "name": "inD", "type": "u32", "value": "dim(shapes.x, 2)" },
117
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 3)" },
118
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 4)" },
119
+ { "name": "outD", "type": "u32", "value": "dim(shapes.y, 2)" },
120
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 3)" },
121
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 4)" },
122
+ { "name": "strideD", "type": "u32", "value": "strideD" },
123
+ { "name": "strideH", "type": "u32", "value": "strideH" },
124
+ { "name": "strideW", "type": "u32", "value": "strideW" },
125
+ { "name": "dilationD", "type": "u32", "value": "dilationD" },
126
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
127
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
128
+ { "name": "padD", "type": "i32", "value": "effectivePadD" },
129
+ { "name": "padH", "type": "i32", "value": "effectivePadH" },
130
+ { "name": "padW", "type": "i32", "value": "effectivePadW" }
131
+ ]
132
+ }
133
  },
 
134
  "variants": [
135
  {
136
  "id": "global_window_parallel",
 
144
  "id": "main",
145
  "name": "AveragePool.GlobalWindowParallel",
146
  "shader": "pool-global-reduction.wgsl.jinja",
 
147
  "derive": {
148
  "op": "\"average\"",
149
  "vec4": "inner(shapes.x, 1) % 4 == 0",
 
159
  }
160
  ]
161
  },
162
+ {
163
+ "id": "window_parallel_ncl1d",
164
+ "priority": 28,
165
+ "when": ["poolBaseOk", "ranks.x == 3", "dim(shapes.y, 2) == inferredOutW", "attrs.count_include_pad == 0 or attrs.ceil_mode == 0", "windowParallelEligible"],
166
+ "supersededBy": ["global_window_parallel"],
167
+ "passes": [
168
+ {
169
+ "id": "main",
170
+ "name": "AveragePool.WindowParallelNcl1d",
171
+ "shader": "pool-window-reduction.wgsl.jinja",
172
+ "derive": {
173
+ "op": "\"average\"",
174
+ "spatialRankSpec": 1,
175
+ "kernelDSpec": "kernelD",
176
+ "kernelHSpec": "kernelH",
177
+ "kernelWSpec": "kernelW",
178
+ "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
179
+ "useSubgroups": "device.features.has(\"subgroups\")",
180
+ "countIncludePad": "attrs.count_include_pad != 0"
181
+ },
182
+ "bindings": ["x", "y", "params_window_ncl1d"],
183
+ "dispatch": { "x": "numel(shapes.y)" }
184
+ }
185
+ ]
186
+ },
187
+ {
188
+ "id": "window_parallel_nchw2d",
189
+ "priority": 28,
190
+ "when": ["poolBaseOk", "ranks.x == 4", "dim(shapes.y, 2) == inferredOutH", "dim(shapes.y, 3) == inferredOutW", "attrs.count_include_pad == 0 or attrs.ceil_mode == 0", "windowParallelEligible"],
191
+ "supersededBy": ["global_window_parallel"],
192
+ "passes": [
193
+ {
194
+ "id": "main",
195
+ "name": "AveragePool.WindowParallelNchw2d",
196
+ "shader": "pool-window-reduction.wgsl.jinja",
197
+ "derive": {
198
+ "op": "\"average\"",
199
+ "spatialRankSpec": 2,
200
+ "kernelDSpec": "kernelD",
201
+ "kernelHSpec": "kernelH",
202
+ "kernelWSpec": "kernelW",
203
+ "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
204
+ "useSubgroups": "device.features.has(\"subgroups\")",
205
+ "countIncludePad": "attrs.count_include_pad != 0"
206
+ },
207
+ "bindings": ["x", "y", "params_window_nchw2d"],
208
+ "dispatch": { "x": "numel(shapes.y)" }
209
+ }
210
+ ]
211
+ },
212
+ {
213
+ "id": "window_parallel_ncdhw3d",
214
+ "priority": 28,
215
+ "when": ["poolBaseOk", "ranks.x == 5", "dim(shapes.y, 2) == inferredOutD", "dim(shapes.y, 3) == inferredOutH", "dim(shapes.y, 4) == inferredOutW", "attrs.count_include_pad == 0 or attrs.ceil_mode == 0", "windowParallelEligible"],
216
+ "supersededBy": ["global_window_parallel"],
217
+ "passes": [
218
+ {
219
+ "id": "main",
220
+ "name": "AveragePool.WindowParallelNcdhw3d",
221
+ "shader": "pool-window-reduction.wgsl.jinja",
222
+ "derive": {
223
+ "op": "\"average\"",
224
+ "spatialRankSpec": 3,
225
+ "kernelDSpec": "kernelD",
226
+ "kernelHSpec": "kernelH",
227
+ "kernelWSpec": "kernelW",
228
+ "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
229
+ "useSubgroups": "device.features.has(\"subgroups\")",
230
+ "countIncludePad": "attrs.count_include_pad != 0"
231
+ },
232
+ "bindings": ["x", "y", "params_window_ncdhw3d"],
233
+ "dispatch": { "x": "numel(shapes.y)" }
234
+ }
235
+ ]
236
+ },
237
  {
238
  "id": "nchw2d_k7s1_reuse_tail",
239
  "priority": 34,
240
+ "when": ["poolBaseOk", "ranks.x == 4", "kernelH == 7", "kernelW == 7", "strideH == 1", "strideW == 1", "dilationH == 1", "dilationW == 1", "effectivePadH == 3", "effectivePadBottom == 3", "effectivePadW == 3", "effectivePadRight == 3", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 != 0", "dim(shapes.y, 2) == dim(shapes.x, 2)", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
241
  "derive": { "outputElement": "dtypes.T" },
242
  "passes": [
243
  {
 
253
  "dilationWSpec": 1,
254
  "padTopSpec": 3,
255
  "padLeftSpec": 3,
 
 
 
 
 
256
  "outW4": "ceilDiv(dim(shapes.y, 3), 4)",
257
  "count4": "dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)",
258
  "scalarOutput": true
259
  },
260
+ "bindings": ["x", "y_main"],
261
  "dispatch": {
262
  "x": "min(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
263
  "y": "ceilDiv(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
 
269
  {
270
  "id": "nchw2d_w3s1_reuse",
271
  "priority": 30,
272
+ "when": ["poolBaseOk", "ranks.x == 4", "kernelW == 3", "strideW == 1", "dilationW == 1", "effectivePadW == 1", "effectivePadRight == 1", "kernelH >= 1", "kernelH <= 3", "strideH == 1", "dilationH >= 1", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 == 0", "dim(shapes.y, 2) == dim(shapes.x, 2) + effectivePadH + effectivePadBottom - ((kernelH - 1) * dilationH + 1) + 1", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
273
+ "demoteWhen": ["narrowF32W3UnrolledPreferred", "variableSubgroup16To32 and dtypes.T == \"f16\""],
274
  "passes": [
275
  {
276
  "id": "main",
 
279
  "derive": {
280
  "kernelHSpec": "kernelH",
281
  "dilationHSpec": "dilationH",
282
+ "padTopSpec": "effectivePadH",
 
 
 
 
 
283
  "outWQuarter": "floor(dim(shapes.y, 3) / 4)"
284
  },
285
  "bindings": [
 
298
  {
299
  "id": "nchw2d_w3s1_reuse_tail",
300
  "priority": 29,
301
+ "when": ["poolBaseOk", "ranks.x == 4", "kernelW == 3", "strideW == 1", "dilationW == 1", "effectivePadW == 1", "effectivePadRight == 1", "kernelH >= 1", "kernelH <= 3", "strideH == 1", "dilationH >= 1", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 != 0", "dim(shapes.y, 2) == dim(shapes.x, 2) + effectivePadH + effectivePadBottom - ((kernelH - 1) * dilationH + 1) + 1", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
302
  "derive": { "outputElement": "dtypes.T" },
303
  "passes": [
304
  {
 
312
  "strideWSpec": 1,
313
  "dilationHSpec": "dilationH",
314
  "dilationWSpec": 1,
315
+ "padTopSpec": "effectivePadH",
316
  "padLeftSpec": 1,
 
 
 
 
 
317
  "outW4": "ceilDiv(dim(shapes.y, 3), 4)",
318
  "count4": "dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)",
319
  "scalarOutput": true
320
  },
321
+ "bindings": ["x", "y_main"],
322
  "dispatch": {
323
  "x": "min(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
324
  "y": "ceilDiv(ceilDiv((dim(shapes.y, 0) * dim(shapes.y, 1) * dim(shapes.y, 2) * ceilDiv(dim(shapes.y, 3), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
 
331
  "id": "nchw2d_separable",
332
  "priority": 22,
333
  "when": ["poolBaseOk", "ranks.x == 4", "ranks.y == 4", "dim(shapes.y, 2) == inferredOutH", "dim(shapes.y, 3) == inferredOutW", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0", "kernelH > 1", "kernelW > 1", "kernelH * kernelW >= tunables.SEPARABLE_MIN_WINDOW", "strideH < kernelH", "wholeWindowDenominator", "separableFits"],
334
+ "derive": { "scalar": "dtypes.T" },
335
  "intermediates": [{ "id": "tmp", "dtype": "float32", "shape": "[separableTmpElements]" }],
336
  "passes": [
337
  {
 
341
  "derive": { "axisSpec": "\"w\"", "op": "\"avg\"", "windowArea": "kernelH * kernelW" },
342
  "bindings": [
343
  "x",
344
+ { "name": "tmp", "elementType": "f32" },
345
  {
346
  "name": "params",
347
  "struct": [
 
351
  { "name": "kernelW", "type": "u32", "value": "kernelW" },
352
  { "name": "strideW", "type": "u32", "value": "strideW" },
353
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
354
+ { "name": "padW", "type": "i32", "value": "effectivePadW" },
355
  { "name": "tmpCount", "type": "u32", "value": "separableTmpElements" }
356
  ]
357
  }
 
379
  { "name": "kernelH", "type": "u32", "value": "kernelH" },
380
  { "name": "strideH", "type": "u32", "value": "strideH" },
381
  { "name": "dilationH", "type": "u32", "value": "dilationH" },
382
+ { "name": "padH", "type": "i32", "value": "effectivePadH" },
383
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
384
  ]
385
  }
 
409
  "strideWSpec": "strideW",
410
  "dilationHSpec": "dilationH",
411
  "dilationWSpec": "dilationW",
412
+ "padTopSpec": "effectivePadH",
413
+ "padLeftSpec": "effectivePadW",
414
  "inH": "dim(shapes.x, 2)",
415
  "inW": "dim(shapes.x, 3)",
416
  "outH": "dim(shapes.y, 2)",
 
454
  { "name": "kernelW", "type": "u32", "value": "kernelW" },
455
  { "name": "strideW", "type": "u32", "value": "strideW" },
456
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
457
+ { "name": "padLeft", "type": "i32", "value": "effectivePadW" },
458
  { "name": "padRight", "type": "i32", "value": "effectivePadRight" },
459
  { "name": "countIncludePad", "type": "u32", "value": "attrs.count_include_pad" },
460
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
 
475
  "passes": [
476
  {
477
  "id": "main",
478
+ "name": "AveragePool.Nchw2d",
479
  "shader": "pool-window-nd.wgsl.jinja",
480
  "derive": { "op": "\"average\"", "spatialRankSpec": 2 },
481
  "bindings": [
 
495
  { "name": "strideW", "type": "u32", "value": "strideW" },
496
  { "name": "dilationH", "type": "u32", "value": "dilationH" },
497
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
498
+ { "name": "padTop", "type": "i32", "value": "effectivePadH" },
499
+ { "name": "padLeft", "type": "i32", "value": "effectivePadW" },
500
  { "name": "padBottom", "type": "i32", "value": "effectivePadBottom" },
501
  { "name": "padRight", "type": "i32", "value": "effectivePadRight" },
502
  { "name": "countIncludePad", "type": "u32", "value": "attrs.count_include_pad" },
 
545
  { "name": "dilationD", "type": "u32", "value": "dilationD" },
546
  { "name": "dilationH", "type": "u32", "value": "dilationH" },
547
  { "name": "dilationW", "type": "u32", "value": "dilationW" },
548
+ { "name": "padFront", "type": "i32", "value": "effectivePadD" },
549
+ { "name": "padTop", "type": "i32", "value": "effectivePadH" },
550
+ { "name": "padLeft", "type": "i32", "value": "effectivePadW" },
551
  { "name": "padBack", "type": "i32", "value": "effectivePadBack" },
552
  { "name": "padBottom", "type": "i32", "value": "effectivePadBottom" },
553
  { "name": "padRight", "type": "i32", "value": "effectivePadRight" },
 
567
  {
568
  "id": "nchw2d_k7s2_reuse",
569
  "priority": 36,
570
+ "when": ["poolBaseOk", "ranks.x == 4", "kernelH == 7", "kernelW == 7", "strideH == 2", "strideW == 2", "dilationH == 1", "dilationW == 1", "effectivePadH == 3", "effectivePadBottom == 3", "effectivePadW == 3", "effectivePadRight == 3", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.y, 2) == inferredOutH", "dim(shapes.y, 3) == inferredOutW", "dim(shapes.y, 3) % 4 == 0", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
571
  "derive": { "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
572
  "passes": [
573
  {
 
583
  "dilationWSpec": 1,
584
  "padTopSpec": 3,
585
  "padLeftSpec": 3,
 
 
 
 
 
586
  "outW4": "dim(shapes.y, 3) / 4",
587
  "count4": "numel(shapes.y) / 4"
588
  },
589
+ "bindings": ["x", "y_main"],
590
  "dispatch": {
591
  "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
592
  "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
 
598
  {
599
  "id": "nchw2d_k7s1_reuse",
600
  "priority": 35,
601
+ "when": ["poolBaseOk", "ranks.x == 4", "kernelH == 7", "kernelW == 7", "strideH == 1", "strideW == 1", "dilationH == 1", "dilationW == 1", "effectivePadH == 3", "effectivePadBottom == 3", "effectivePadW == 3", "effectivePadRight == 3", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.x, 3) % 4 == 0", "dim(shapes.y, 2) == dim(shapes.x, 2)", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dim(shapes.y, 2) > 0", "dim(shapes.y, 3) > 0"],
602
  "derive": { "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
603
  "passes": [
604
  {
 
614
  "dilationWSpec": 1,
615
  "padTopSpec": 3,
616
  "padLeftSpec": 3,
 
 
 
 
 
617
  "outW4": "dim(shapes.y, 3) / 4",
618
  "count4": "numel(shapes.y) / 4"
619
  },
620
+ "bindings": ["x", "y_main"],
621
  "dispatch": {
622
  "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
623
  "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
build/webgpu/metadata.json CHANGED
@@ -1,28 +1,32 @@
1
  {
2
  "name": "ai.onnx.AveragePool",
3
- "id": "_ai_onnx_averagepool_webgpu_1a52fa9",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "average-pool2d-nchw-horizontal-reuse.wgsl.jinja": "WhpEal2kC3XudqPetAZeL6v/IGK1olc+Fb4HzzDj890=",
11
- "average-pool2d-nchw-w3s1-reuse.wgsl.jinja": "IsI2gMcZbY0eemYs5z0pTC86cgaDw605Xfn2iQDBJkw=",
12
- "bench.json": "7vXmd7ZA5xBCZjqXJYtMzO7vq/0qgzf5gNZ1S6J5Zps=",
13
- "manifest.json": "a0sXaOaq8613yIAtQBB1R+xrbvA48WTKgkPo14ruXhA=",
14
- "pool-global-reduction.wgsl.jinja": "SKnTSUq9nU838U0t7VyvC7hgz4h5L+Ndv1rQCM9Vks8=",
15
- "pool-window-nd.wgsl.jinja": "2shuAamdoFjQCOq2LK2XC6pKsa10zad6bDhqY1h2zBI=",
16
- "pool-window-unroll.wgsl.jinja": "iSbZ3lQkW4r/CZzsBitQBpHgBLRJ83MekQxyVqbInwI=",
 
17
  "pool2d-nchw-separable.wgsl.jinja": "GgafQWV6Pn5SqSn5gMns0EJEHGqk2WkdCdVurt3MgA8=",
18
- "test.json": "2Xnd9YqkiREFwBJdjyqFIbqnT2KGPNdXYpCezSLDXkA="
19
  }
20
  },
21
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
22
  "webgpu": {
23
- "manifestSpec": "2.0",
24
  "variants": {
25
  "global_window_parallel": ["pool-global-reduction.wgsl.jinja"],
 
 
 
26
  "nchw2d_k7s1_reuse_tail": ["average-pool2d-nchw-horizontal-reuse.wgsl.jinja"],
27
  "nchw2d_w3s1_reuse": ["average-pool2d-nchw-w3s1-reuse.wgsl.jinja"],
28
  "nchw2d_w3s1_reuse_tail": ["average-pool2d-nchw-horizontal-reuse.wgsl.jinja"],
 
1
  {
2
  "name": "ai.onnx.AveragePool",
3
+ "id": "_ai_onnx_averagepool_webgpu_6441176",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "average-pool2d-nchw-horizontal-reuse.wgsl.jinja": "heeYKFbnmrXdEohH7tUG5PzItrk5yPz3hEgnuNihBWg=",
11
+ "average-pool2d-nchw-w3s1-reuse.wgsl.jinja": "D4razLs8qLkk5zubCU+wUa9OVgFisNZ95cGwXipOvwI=",
12
+ "bench.json": "ZvF9gqBTship90W+TiMfWNyrdsJYyEAwZAdZ5r6/vc0=",
13
+ "manifest.json": "/fTFl6NMtKYnbxuSbihY039sqo+P8xhGXVjFtj2gA6c=",
14
+ "pool-global-reduction.wgsl.jinja": "uCrSccgj/Fibd866kxpiYkprR8g6q1vofgQZZ/oxGkE=",
15
+ "pool-window-nd.wgsl.jinja": "QCtdlEJy4LmSU9g7oUOR0YbZ9cZuxH6P1ERT3m7YACY=",
16
+ "pool-window-reduction.wgsl.jinja": "Jv5CY3dfoWUzgoxb4H35nlKspu9GKe10Z1bRjGQ4DPk=",
17
+ "pool-window-unroll.wgsl.jinja": "UWhk8iANx6euYlJV5W31BP0D4FaJEC+lV/3y95T2wOQ=",
18
  "pool2d-nchw-separable.wgsl.jinja": "GgafQWV6Pn5SqSn5gMns0EJEHGqk2WkdCdVurt3MgA8=",
19
+ "test.json": "zw4aG7ZsPYmGpRG2g8Yi++TwV5Fvas/zLR5Gesmxys8="
20
  }
21
  },
22
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
23
  "webgpu": {
24
+ "manifestSpec": "2.1",
25
  "variants": {
26
  "global_window_parallel": ["pool-global-reduction.wgsl.jinja"],
27
+ "window_parallel_ncl1d": ["pool-window-reduction.wgsl.jinja"],
28
+ "window_parallel_nchw2d": ["pool-window-reduction.wgsl.jinja"],
29
+ "window_parallel_ncdhw3d": ["pool-window-reduction.wgsl.jinja"],
30
  "nchw2d_k7s1_reuse_tail": ["average-pool2d-nchw-horizontal-reuse.wgsl.jinja"],
31
  "nchw2d_w3s1_reuse": ["average-pool2d-nchw-w3s1-reuse.wgsl.jinja"],
32
  "nchw2d_w3s1_reuse_tail": ["average-pool2d-nchw-horizontal-reuse.wgsl.jinja"],
build/webgpu/pool-global-reduction.wgsl.jinja CHANGED
@@ -22,13 +22,11 @@ var<workgroup> wg_partials: array<f32, WG>;
22
  var<workgroup> wg_tree: array<f32, WG>;
23
  {% endif %}
24
 
25
-
26
  @compute @workgroup_size(WG)
27
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
28
- @builtin(local_invocation_id) lid: vec3<u32>
29
- {%- if useSubgroups %},
30
- @builtin(subgroup_size) sg_size: u32
31
- {%- endif %}) {
32
  let plane = wid.x;
33
  let tid = lid.x;
34
  var partial = 0.0;
@@ -50,32 +48,35 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
50
  {% endif %}
51
 
52
  {% if useSubgroups %}
53
- var total = subgroupAdd(partial);
54
- // Cross-subgroup fold that assumes nothing about which invocations share a
55
- // subgroup, how many subgroups there are, or which of a subgroup's lanes are
56
- // active: every invocation owns the slot at its own index, the elected lane
57
- // publishes its subgroup partial there and every other lane publishes the
58
- // reduction identity. Each subgroup then folds all WG slots — lane `rank`,
59
- // its dense position among the active lanes, walks slots rank, rank + count,
60
- // ... — and one more collective merges the lane partials, so every slot is
61
- // merged exactly once at any legal width and partition. A one-subgroup
62
- // workgroup skips shared memory entirely; that test reads the `subgroup_size`
63
- // builtin, which is uniform, because a collective's result is not uniform to
64
- // WGSL's analysis and may not guard a barrier.
65
- if (sg_size != WG) {
66
- let rank = subgroupExclusiveAdd(1u);
67
- let count = subgroupAdd(1u);
68
- wg_partials[tid] = select(0.0, total, rank == 0u);
69
- workgroupBarrier();
70
- var acc = 0.0;
71
- for (var i = rank; i < WG; i = i + count) {
72
- acc = acc + wg_partials[i];
73
- }
74
- total = subgroupAdd(acc);
 
 
75
  }
 
 
 
76
  if (tid == 0u) {
77
- y[plane] = {{ scalar }}(total / f32(params.spatial));
78
- }
79
  {% else %}
80
  // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
81
  wg_tree[tid] = partial;
@@ -88,7 +89,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
88
  }
89
  if (tid == 0u) {
90
  let total = wg_tree[0];
 
91
  y[plane] = {{ scalar }}(total / f32(params.spatial));
92
  }
93
- {% endif %}
94
  }
 
22
  var<workgroup> wg_tree: array<f32, WG>;
23
  {% endif %}
24
 
 
25
  @compute @workgroup_size(WG)
26
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
27
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
28
+ @builtin(subgroup_invocation_id) sg_lane: u32,
29
+ @builtin(subgroup_size) sg_size: u32{% endif %}) {
 
30
  let plane = wid.x;
31
  let tid = lid.x;
32
  var partial = 0.0;
 
48
  {% endif %}
49
 
50
  {% if useSubgroups %}
51
+ {% filter indent(2, true) %}
52
+ var total = subgroupAdd(partial);
53
+ // Cross-subgroup fold that assumes nothing about which invocations share a
54
+ // subgroup or how many subgroups there are (it does require the uniform
55
+ // control flow this entry point already has): every invocation owns the slot
56
+ // at its own index, the elected lane
57
+ // publishes its subgroup partial there and every other lane publishes the
58
+ // reduction identity. Each subgroup then folds all WG slots — lane `sg_lane`
59
+ // walks slots sg_lane, sg_lane + sg_size, ... — and one more collective merges
60
+ // the lane partials, so every slot is merged exactly once at any legal width.
61
+ // The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
62
+ // `subgroupAdd(1u)`: those agree with them under this contract, but a driver
63
+ // in the wild answers a claim derived from them with zero. A one-subgroup
64
+ // workgroup skips shared memory entirely; that test reads the `subgroup_size`
65
+ // builtin, which is uniform, because a collective's result is not uniform to
66
+ // WGSL's analysis and may not guard a barrier.
67
+ if (sg_size != WG) {
68
+ let rank = sg_lane;
69
+ let lanes = sg_size;
70
+ wg_partials[tid] = select(0.0, total, rank == 0u);
71
+ workgroupBarrier();
72
+ var acc = 0.0;
73
+ for (var i = rank; i < WG; i = i + lanes) {
74
+ acc = acc + wg_partials[i];
75
  }
76
+ total = subgroupAdd(acc);
77
+ }
78
+ {% endfilter %}
79
  if (tid == 0u) {
 
 
80
  {% else %}
81
  // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
82
  wg_tree[tid] = partial;
 
89
  }
90
  if (tid == 0u) {
91
  let total = wg_tree[0];
92
+ {% endif %}
93
  y[plane] = {{ scalar }}(total / f32(params.spatial));
94
  }
 
95
  }
build/webgpu/pool-window-nd.wgsl.jinja CHANGED
@@ -1,10 +1,15 @@
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Direct spatial-pooling traversal for a channels-first tensor.
4
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
5
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
6
- let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
7
- if (index >= params.count) { return; }
8
 
9
  let ow = index % params.outW;
10
  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 }}) { return; }{% endmacro %}
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
  // Direct spatial-pooling traversal for a channels-first tensor.
10
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
11
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
12
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index", guardInline=true) }}
 
13
 
14
  let ow = index % params.outW;
15
  var t = index / params.outW;
build/webgpu/pool-window-reduction.wgsl.jinja ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% set needsCount = op == "max" or (op == "average" and not countIncludePad) %}
2
+ {% if useSubgroups %}
3
+ enable subgroups;
4
+ {% endif %}
5
+ {{ env.wgsl.resourceDeclarations }}
6
+
7
+ // Workgroup-cooperative windowed pooling: one workgroup reduces the kernel
8
+ // window of ONE output element. The serial route gives each output element a
9
+ // single invocation that loops the whole window, so its only parallelism is the
10
+ // output element count; when that count launches too few workgroups and the window is
11
+ // long, the window itself is the parallelism worth spending. Threads here stride
12
+ // the flattened tap index [kD x] kH x kW, each tap resolving its own padded,
13
+ // strided and dilated input coordinate, then combine the per-thread partials.
14
+ // With subgroups: a subgroup collective, then one shared-memory slot per
15
+ // invocation (the subgroup partial from its elected lane, the identity from
16
+ // every other lane) that every subgroup folds. Without subgroups, a full
17
+ // workgroup tree combines the per-thread partials.
18
+ {% if countIncludePad %}
19
+ // count_include_pad == 1 with ceil_mode == 0 makes every tap of every window
20
+ // land inside the padded extent, so the divisor is the baked tap count and no
21
+ // second reduction is needed. The tree changes the association of additions.
22
+ {% else %}
23
+ // count_include_pad == 0 divides by the in-bounds tap count, which is reduced
24
+ // across the workgroup alongside the sum. An output whose window is entirely
25
+ // out of bounds writes 0, matching the serial route. The tree changes the
26
+ // association of additions.
27
+ {% endif %}
28
+ const WG: u32 = {{ workgroupSizeSpec }}u;
29
+ const KW: u32 = {{ kernelWSpec }}u;
30
+ {% if spatialRankSpec >= 2 %}
31
+ const KH: u32 = {{ kernelHSpec }}u;
32
+ {% endif %}
33
+ // Flattened tap count of the whole window; the loop bound and, for
34
+ // count_include_pad == 1, the divisor.
35
+ const TAPS: u32 = {{ kernelDSpec * kernelHSpec * kernelWSpec }}u;
36
+ // One slot per invocation: the cross-subgroup fold below indexes by
37
+ // local_invocation_id, never by a subgroup ordinal, so no subgroup width or
38
+ // partition can overflow or alias it.
39
+ var<workgroup> wg_partials: array<f32, WG>;
40
+ {% if needsCount %}
41
+ var<workgroup> wg_counts: array<f32, WG>;
42
+ {% endif %}
43
+
44
+ @compute @workgroup_size(WG)
45
+ fn main(@builtin(workgroup_id) wid: vec3<u32>,
46
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
47
+ @builtin(subgroup_invocation_id) sg_lane: u32,
48
+ @builtin(subgroup_size) sg_size: u32{% endif %}) {
49
+ // One workgroup per output element: the dispatch is exactly numel(y), so no
50
+ // bounds guard is needed here.
51
+ let index = wid.x;
52
+ let tid = lid.x;
53
+
54
+ let ow = index % params.outW;
55
+ var t = index / params.outW;
56
+ {% if spatialRankSpec >= 2 %}
57
+ let oh = t % params.outH;
58
+ t /= params.outH;
59
+ {% endif %}
60
+ {% if spatialRankSpec == 3 %}
61
+ let od = t % params.outD;
62
+ t /= params.outD;
63
+ {% endif %}
64
+ let channel = t % params.channels;
65
+ let batch = t / params.channels;
66
+
67
+ let startW = i32(ow * params.strideW) - params.padW;
68
+ {% if spatialRankSpec >= 2 %}
69
+ let startH = i32(oh * params.strideH) - params.padH;
70
+ {% endif %}
71
+ {% if spatialRankSpec == 3 %}
72
+ let startD = i32(od * params.strideD) - params.padD;
73
+ {% endif %}
74
+ {% if spatialRankSpec == 1 %}
75
+ let planeBase = (batch * params.channels + channel) * params.inW;
76
+ {% elif spatialRankSpec == 2 %}
77
+ let planeBase = (batch * params.channels + channel) * params.inH * params.inW;
78
+ {% else %}
79
+ let planeBase = (batch * params.channels + channel) * params.inD * params.inH * params.inW;
80
+ {% endif %}
81
+
82
+ var partial = 0.0;
83
+ {% if needsCount %}
84
+ var counted = 0.0;
85
+ {% endif %}
86
+ for (var tap = tid; tap < TAPS; tap = tap + WG) {
87
+ let kw = tap % KW;
88
+ {% if spatialRankSpec >= 2 %}
89
+ let rest = tap / KW;
90
+ let kh = rest % KH;
91
+ {% endif %}
92
+ {% if spatialRankSpec == 3 %}
93
+ let kd = rest / KH;
94
+ {% endif %}
95
+ let iw = startW + i32(kw * params.dilationW);
96
+ {% if spatialRankSpec >= 2 %}
97
+ let ih = startH + i32(kh * params.dilationH);
98
+ {% endif %}
99
+ {% if spatialRankSpec == 3 %}
100
+ let id = startD + i32(kd * params.dilationD);
101
+ {% endif %}
102
+ {% if spatialRankSpec == 1 %}
103
+ let inBounds = iw >= 0 && iw < i32(params.inW);
104
+ {% elif spatialRankSpec == 2 %}
105
+ let inBounds = iw >= 0 && iw < i32(params.inW) && ih >= 0 && ih < i32(params.inH);
106
+ {% else %}
107
+ let inBounds = iw >= 0 && iw < i32(params.inW) && ih >= 0 && ih < i32(params.inH) && id >= 0 && id < i32(params.inD);
108
+ {% endif %}
109
+ if (inBounds) {
110
+ {% if spatialRankSpec == 1 %}
111
+ let value = f32(x[planeBase + u32(iw)]);
112
+ {% elif spatialRankSpec == 2 %}
113
+ let value = f32(x[planeBase + u32(ih) * params.inW + u32(iw)]);
114
+ {% else %}
115
+ let value = f32(x[planeBase + (u32(id) * params.inH + u32(ih)) * params.inW + u32(iw)]);
116
+ {% endif %}
117
+ partial = partial + value;
118
+ {% if needsCount %}
119
+ counted = counted + 1.0;
120
+ {% endif %}
121
+ }
122
+ }
123
+
124
+ {% if useSubgroups %}
125
+ {% filter indent(2, true) %}
126
+ var total = subgroupAdd(partial);
127
+ {% if needsCount %}
128
+ var countTotal = subgroupAdd(counted);
129
+ {% endif %}
130
+ // Cross-subgroup fold that assumes nothing about which invocations share a
131
+ // subgroup or how many subgroups there are (it does require the uniform
132
+ // control flow this entry point already has): every invocation owns the slot
133
+ // at its own index, the elected lane
134
+ // publishes its subgroup partial there and every other lane publishes the
135
+ // reduction identity. Each subgroup then folds all WG slots — lane `sg_lane`
136
+ // walks slots sg_lane, sg_lane + sg_size, ... — and one more collective merges
137
+ // the lane partials, so every slot is merged exactly once at any legal width.
138
+ // The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
139
+ // `subgroupAdd(1u)`: those agree with them under this contract, but a driver
140
+ // in the wild answers a claim derived from them with zero. A one-subgroup
141
+ // workgroup skips shared memory entirely; that test reads the `subgroup_size`
142
+ // builtin, which is uniform, because a collective's result is not uniform to
143
+ // WGSL's analysis and may not guard a barrier.
144
+ if (sg_size != WG) {
145
+ let rank = sg_lane;
146
+ let lanes = sg_size;
147
+ wg_partials[tid] = select(0.0, total, rank == 0u);
148
+ {% if needsCount %}
149
+ wg_counts[tid] = select(0.0, countTotal, rank == 0u);
150
+ {% endif %}
151
+ workgroupBarrier();
152
+ var acc = 0.0;
153
+ {% if needsCount %}
154
+ var countAcc = 0.0;
155
+ {% endif %}
156
+ for (var i = rank; i < WG; i = i + lanes) {
157
+ acc = acc + wg_partials[i];
158
+ {% if needsCount %}
159
+ countAcc = countAcc + wg_counts[i];
160
+ {% endif %}
161
+ }
162
+ total = subgroupAdd(acc);
163
+ {% if needsCount %}
164
+ countTotal = subgroupAdd(countAcc);
165
+ {% endif %}
166
+ }
167
+ {% endfilter %}
168
+ {% else %}
169
+ // No-subgroups fallback: full workgroup-tree reduction of the per-thread
170
+ // partials, carrying the unpaired entry forward for odd active widths.
171
+ wg_partials[tid] = partial;
172
+ {% if needsCount %}
173
+ wg_counts[tid] = counted;
174
+ {% endif %}
175
+ workgroupBarrier();
176
+ for (var liveCount = WG; liveCount > 1u; liveCount = (liveCount + 1u) / 2u) {
177
+ let step = (liveCount + 1u) / 2u;
178
+ if (tid + step < liveCount) {
179
+ wg_partials[tid] = wg_partials[tid] + wg_partials[tid + step];
180
+ {% if needsCount %}
181
+ wg_counts[tid] = wg_counts[tid] + wg_counts[tid + step];
182
+ {% endif %}
183
+ }
184
+ workgroupBarrier();
185
+ }
186
+ let total = wg_partials[0];
187
+ {% if needsCount %}
188
+ let countTotal = wg_counts[0];
189
+ {% endif %}
190
+ {% endif %}
191
+ if (tid == 0u) {
192
+ {% if countIncludePad %}
193
+ y[index] = {{ scalar }}(total / f32(TAPS));
194
+ {% else %}
195
+ y[index] = {{ scalar }}(select(0.0, total / countTotal, countTotal > 0.0));
196
+ {% endif %}
197
+ }
198
+ }
build/webgpu/pool-window-unroll.wgsl.jinja CHANGED
@@ -1,8 +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
- {% set countIncludePad = countIncludePad is defined and countIncludePad %}
4
- {% set padBottomSpec = padBottomSpec | default(0) %}
5
- {% set padRightSpec = padRightSpec | default(0) %}
 
 
 
 
 
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
  const OUT_W: u32 = {{ outW }}u;
@@ -24,11 +29,7 @@ const PAD_RIGHT: i32 = {{ padRightSpec }};
24
  fn main(
25
  @builtin(global_invocation_id) gid: vec3<u32>
26
  ) {
27
- // Fold very large flat outputs into the dispatch's second dimension.
28
- let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
29
- if (index >= params.count) {
30
- return;
31
- }
32
 
33
  let ow = index % OUT_W;
34
  var plane = index / OUT_W;
@@ -58,8 +59,8 @@ fn main(
58
  {% endfor %}
59
  }
60
  {% endfor %}
61
-
62
  {% if op == "average" and countIncludePad %}
 
63
  var denominator = 0u;
64
  {% for kh in range(kernelHSpec) %}
65
  if (ih_{{ kh }} >= -PAD_TOP && ih_{{ kh }} < IN_H + PAD_BOTTOM) {
@@ -71,6 +72,7 @@ fn main(
71
  }
72
  {% endfor %}
73
  {% else %}
 
74
  let denominator = seen;
75
  {% endif %}
76
 
 
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;
 
29
  fn main(
30
  @builtin(global_invocation_id) gid: vec3<u32>
31
  ) {
32
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
 
 
 
 
33
 
34
  let ow = index % OUT_W;
35
  var plane = index / OUT_W;
 
59
  {% endfor %}
60
  }
61
  {% endfor %}
 
62
  {% if op == "average" and countIncludePad %}
63
+
64
  var denominator = 0u;
65
  {% for kh in range(kernelHSpec) %}
66
  if (ih_{{ kh }} >= -PAD_TOP && ih_{{ kh }} < IN_H + PAD_BOTTOM) {
 
72
  }
73
  {% endfor %}
74
  {% else %}
75
+
76
  let denominator = seen;
77
  {% endif %}
78
 
build/webgpu/test.json CHANGED
@@ -93,7 +93,7 @@
93
  "provenance": {
94
  "source": "onnxruntime/test/providers/cpu/nn/pool_op_test.cc",
95
  "test": "PoolTest.AveragePool",
96
- "notes": "A 4x1 kernel bypasses the small-window unrolled path and covers the generic 2D AveragePool kernel with the same valid subnormal-average edge."
97
  },
98
  "attrs": { "count_include_pad": 0, "kernel_shape": [4, 1], "strides": [1, 1], "pads": [0, 0, 0, 0] },
99
  "inputs": {
@@ -985,7 +985,7 @@
985
  {
986
  "name": "f16_generic_global7x7_b1c256_nchw",
987
  "provenance": {
988
- "notes": "Exercises the generic f16 NCHW 2D route with a full 7x7 reduction from [1,256,7,7] to [1,256,1,1]; kernelW=7 excludes the small-window unrolled route."
989
  },
990
  "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [1, 1], "pads": [0, 0, 0, 0] },
991
  "inputs": {
@@ -1000,7 +1000,7 @@
1000
  {
1001
  "name": "f16_generic_count_include_pad_k5s1p2_b1c32_16x16",
1002
  "provenance": {
1003
- "notes": "A realistic [1,32,16,16] float16 k5/s1/p2 pool selects the generic NCHW path. With count_include_pad=1, edge windows accumulate in-bounds values but divide by the full padded window size."
1004
  },
1005
  "attrs": { "count_include_pad": 1, "kernel_shape": [5, 5], "strides": [1, 1], "pads": [2, 2, 2, 2] },
1006
  "inputs": {
@@ -1129,7 +1129,7 @@
1129
  {
1130
  "name": "k7s2p3_horizontal_reuse_f32",
1131
  "provenance": {
1132
- "notes": "Compact route fixture for the ImageNet-style 7x7/stride-2/pad-3 pool. It locks the four-output horizontal reuse path and exclude-pad edge divisors."
1133
  },
1134
  "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [2, 2], "pads": [3, 3, 3, 3] },
1135
  "inputs": {
@@ -1170,6 +1170,288 @@
1170
  }
1171
  },
1172
  "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 8, 10], "tolerance": 0.00001 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1173
  }
1174
  ]
1175
  }
 
93
  "provenance": {
94
  "source": "onnxruntime/test/providers/cpu/nn/pool_op_test.cc",
95
  "test": "PoolTest.AveragePool",
96
+ "notes": "A 4x1 pooling window checks a valid subnormal average over a non-square reduction."
97
  },
98
  "attrs": { "count_include_pad": 0, "kernel_shape": [4, 1], "strides": [1, 1], "pads": [0, 0, 0, 0] },
99
  "inputs": {
 
985
  {
986
  "name": "f16_generic_global7x7_b1c256_nchw",
987
  "provenance": {
988
+ "notes": "A full 7x7 float16 reduction from [1,256,7,7] to [1,256,1,1] checks global pooling across every channel."
989
  },
990
  "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [1, 1], "pads": [0, 0, 0, 0] },
991
  "inputs": {
 
1000
  {
1001
  "name": "f16_generic_count_include_pad_k5s1p2_b1c32_16x16",
1002
  "provenance": {
1003
+ "notes": "A [1,32,16,16] float16 pool with a 5x5 window, stride one, padding two, and count_include_pad=1; edge windows divide by the full padded window size."
1004
  },
1005
  "attrs": { "count_include_pad": 1, "kernel_shape": [5, 5], "strides": [1, 1], "pads": [2, 2, 2, 2] },
1006
  "inputs": {
 
1129
  {
1130
  "name": "k7s2p3_horizontal_reuse_f32",
1131
  "provenance": {
1132
+ "notes": "A 7x7, stride-2, pad-3 pool checks horizontal output positions and exclude-pad edge divisors."
1133
  },
1134
  "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [2, 2], "pads": [3, 3, 3, 3] },
1135
  "inputs": {
 
1170
  }
1171
  },
1172
  "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 8, 10], "tolerance": 0.00001 } }
1173
+ },
1174
+ {
1175
+ "name": "ort_avgpool_large_kernel_small_output_pad2",
1176
+ "provenance": {
1177
+ "source": "onnxruntime/test/providers/cpu/nn/pool_op_test.cc",
1178
+ "test": "PoolTest.AveragePool_LargeKernelMultiElementOutput_WebGpu",
1179
+ "notes": "A 12x12 average-pool window with padding 2 over a 24x24 input produces a 17x17 output, a large window behind a small output; padding is excluded from the divisor. Transcribed from onnxruntime's PoolTest.AveragePool_LargeKernelMultiElementOutput_WebGpu."
1180
+ },
1181
+ "attrs": {
1182
+ "kernel_shape": [12, 12],
1183
+ "pads": [2, 2, 2, 2],
1184
+ "strides": [1, 1],
1185
+ "count_include_pad": 0,
1186
+ "ceil_mode": 0
1187
+ },
1188
+ "inputs": {
1189
+ "x": { "dtype": "float32", "shape": [1, 2, 24, 24], "data": { "kind": "linspace", "start": 0.0, "end": 11.51 } }
1190
+ },
1191
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 17, 17], "tolerance": 0.00001 } }
1192
+ },
1193
+ {
1194
+ "name": "window_parallel_nchw2d_count_include_pad_k12p2",
1195
+ "provenance": {
1196
+ "notes": "A 12x12 window with padding two over a 24x24 input and count_include_pad=1 produces 867 outputs; edge windows divide by the full padded tap count."
1197
+ },
1198
+ "attrs": {
1199
+ "kernel_shape": [12, 12],
1200
+ "strides": [1, 1],
1201
+ "pads": [2, 2, 2, 2],
1202
+ "count_include_pad": 1,
1203
+ "ceil_mode": 0
1204
+ },
1205
+ "inputs": {
1206
+ "x": {
1207
+ "dtype": "float32",
1208
+ "shape": [1, 3, 24, 24],
1209
+ "data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.053, "scale": 0.7 }
1210
+ }
1211
+ },
1212
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 17, 17], "tolerance": 0.00001 } }
1213
+ },
1214
+ {
1215
+ "name": "window_parallel_nchw2d_ceil_mode_dilation_stride_overhang",
1216
+ "provenance": {
1217
+ "notes": "ceil_mode=1 with dilation 2, stride 5 and asymmetric pads: the last window in each axis starts inside the input and runs past both the input and the padded extent, so the cooperative reduction must reduce the in-bounds tap count as well as the sum."
1218
+ },
1219
+ "attrs": {
1220
+ "kernel_shape": [12, 12],
1221
+ "strides": [5, 5],
1222
+ "pads": [3, 2, 4, 3],
1223
+ "dilations": [2, 2],
1224
+ "count_include_pad": 0,
1225
+ "ceil_mode": 1
1226
+ },
1227
+ "inputs": {
1228
+ "x": {
1229
+ "dtype": "float32",
1230
+ "shape": [1, 4, 30, 30],
1231
+ "data": { "kind": "fillFloat32", "sinStep": 0.029, "cosStep": 0.047, "scale": 0.8 }
1232
+ }
1233
+ },
1234
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 4, 4], "tolerance": 0.00001 } }
1235
+ },
1236
+ {
1237
+ "name": "window_parallel_ncl1d_k128_at_min_taps",
1238
+ "provenance": {
1239
+ "notes": "A 512-element row is reduced by a 128-element average-pool window to 4 outputs per channel; a 127-element sibling window in this file reduces the same row by one fewer tap."
1240
+ },
1241
+ "attrs": { "kernel_shape": [128], "strides": [128], "pads": [0, 0] },
1242
+ "inputs": {
1243
+ "x": {
1244
+ "dtype": "float32",
1245
+ "shape": [1, 8, 512],
1246
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.023, "scale": 0.5 }
1247
+ }
1248
+ },
1249
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 8, 4], "tolerance": 0.00001 } }
1250
+ },
1251
+ {
1252
+ "name": "window_parallel_ncl1d_k127_below_min_taps",
1253
+ "provenance": {
1254
+ "notes": "A 512-element row is reduced by a 127-element average-pool window to 4 outputs per channel; its 128-element sibling in this file uses one more tap over the same row."
1255
+ },
1256
+ "attrs": { "kernel_shape": [127], "strides": [127], "pads": [0, 0] },
1257
+ "inputs": {
1258
+ "x": {
1259
+ "dtype": "float32",
1260
+ "shape": [1, 8, 512],
1261
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.023, "scale": 0.5 }
1262
+ }
1263
+ },
1264
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 8, 4], "tolerance": 0.00001 } }
1265
+ },
1266
+ {
1267
+ "name": "window_parallel_ncdhw3d_k8_pads_512_taps",
1268
+ "provenance": {
1269
+ "notes": "A 3-D 8x8x8 window with one pad on every face: 512 taps behind 32 outputs, the shape the serial route parallelises worst."
1270
+ },
1271
+ "attrs": { "kernel_shape": [8, 8, 8], "strides": [8, 8, 8], "pads": [1, 1, 1, 1, 1, 1], "count_include_pad": 0 },
1272
+ "inputs": {
1273
+ "x": {
1274
+ "dtype": "float32",
1275
+ "shape": [1, 4, 16, 16, 16],
1276
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 0.5 }
1277
+ }
1278
+ },
1279
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 2, 2, 2], "tolerance": 0.00001 } }
1280
+ },
1281
+ {
1282
+ "name": "window_parallel_nchw2d_serial_groups_at_gate",
1283
+ "provenance": { "notes": "A 16,128-element output checks pooling across many independent windows." },
1284
+ "attrs": {
1285
+ "kernel_shape": [12, 12],
1286
+ "strides": [1, 1],
1287
+ "pads": [2, 2, 2, 2],
1288
+ "count_include_pad": 0,
1289
+ "ceil_mode": 0
1290
+ },
1291
+ "inputs": {
1292
+ "x": {
1293
+ "dtype": "float32",
1294
+ "shape": [1, 63, 23, 23],
1295
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.037, "scale": 0.4 }
1296
+ }
1297
+ },
1298
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 63, 16, 16], "tolerance": 0.00001 } }
1299
+ },
1300
+ {
1301
+ "name": "window_parallel_nchw2d_serial_groups_over_gate",
1302
+ "provenance": {
1303
+ "notes": "One channel more than its sibling puts the output at exactly 64 serial dispatch groups, so the serial route is assumed to fill the device on its own and the cooperative reduction is rejected."
1304
+ },
1305
+ "attrs": {
1306
+ "kernel_shape": [12, 12],
1307
+ "strides": [1, 1],
1308
+ "pads": [2, 2, 2, 2],
1309
+ "count_include_pad": 0,
1310
+ "ceil_mode": 0
1311
+ },
1312
+ "inputs": {
1313
+ "x": {
1314
+ "dtype": "float32",
1315
+ "shape": [1, 64, 23, 23],
1316
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.037, "scale": 0.4 }
1317
+ }
1318
+ },
1319
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 16, 16], "tolerance": 0.00001 } }
1320
+ },
1321
+ {
1322
+ "name": "window_parallel_gate_ceil_mode_with_count_include_pad",
1323
+ "provenance": {
1324
+ "notes": "A 12x12 average-pool window with dilation 2, stride 5 and padding [3,2,4,3] combines ceil_mode and count_include_pad, both enabled, over a 30x30 input to produce a 4x4 output."
1325
+ },
1326
+ "attrs": {
1327
+ "kernel_shape": [12, 12],
1328
+ "strides": [5, 5],
1329
+ "pads": [3, 2, 4, 3],
1330
+ "dilations": [2, 2],
1331
+ "count_include_pad": 1,
1332
+ "ceil_mode": 1
1333
+ },
1334
+ "inputs": {
1335
+ "x": {
1336
+ "dtype": "float32",
1337
+ "shape": [1, 4, 30, 30],
1338
+ "data": { "kind": "fillFloat32", "sinStep": 0.029, "cosStep": 0.047, "scale": 0.8 }
1339
+ }
1340
+ },
1341
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 4, 4], "tolerance": 0.00001 } }
1342
+ },
1343
+ {
1344
+ "name": "window_parallel_nchw2d_f16_k12p2",
1345
+ "provenance": {
1346
+ "notes": "float16 input and output through the cooperative window reduction, which loads and accumulates in f32 and narrows once at the store."
1347
+ },
1348
+ "attrs": {
1349
+ "kernel_shape": [12, 12],
1350
+ "strides": [1, 1],
1351
+ "pads": [2, 2, 2, 2],
1352
+ "count_include_pad": 0,
1353
+ "ceil_mode": 0
1354
+ },
1355
+ "inputs": {
1356
+ "x": {
1357
+ "dtype": "float16",
1358
+ "shape": [1, 3, 24, 24],
1359
+ "data": { "kind": "fillFloat32", "sinStep": 0.041, "cosStep": 0.057, "scale": 1.1 }
1360
+ }
1361
+ },
1362
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 3, 17, 17], "tolerance": 0.003 } }
1363
+ },
1364
+ {
1365
+ "name": "window_parallel_nchw2d_count_include_pad_k12p2_small_output",
1366
+ "provenance": {
1367
+ "notes": "A 12x12 window with stride two and padding two over a 24x24 input and count_include_pad=1 produces 81 outputs; edge windows divide by the full padded tap count."
1368
+ },
1369
+ "attrs": {
1370
+ "kernel_shape": [12, 12],
1371
+ "strides": [2, 2],
1372
+ "pads": [2, 2, 2, 2],
1373
+ "count_include_pad": 1,
1374
+ "ceil_mode": 0
1375
+ },
1376
+ "inputs": {
1377
+ "x": {
1378
+ "dtype": "float32",
1379
+ "shape": [1, 1, 24, 24],
1380
+ "data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.053, "scale": 0.7 }
1381
+ }
1382
+ },
1383
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 9, 9], "tolerance": 0.00001 } }
1384
+ },
1385
+ {
1386
+ "name": "window_parallel_nchw2d_f16_k12p2_small_output",
1387
+ "provenance": {
1388
+ "notes": "float16 input and output through the cooperative window reduction, which loads and accumulates in f32 and narrows once at the store."
1389
+ },
1390
+ "attrs": {
1391
+ "kernel_shape": [12, 12],
1392
+ "strides": [2, 2],
1393
+ "pads": [2, 2, 2, 2],
1394
+ "count_include_pad": 0,
1395
+ "ceil_mode": 0
1396
+ },
1397
+ "inputs": {
1398
+ "x": {
1399
+ "dtype": "float16",
1400
+ "shape": [1, 1, 24, 24],
1401
+ "data": { "kind": "fillFloat32", "sinStep": 0.041, "cosStep": 0.057, "scale": 1.1 }
1402
+ }
1403
+ },
1404
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1, 9, 9], "tolerance": 0.003 } }
1405
+ },
1406
+ {
1407
+ "name": "window_parallel_outputs_to_taps_boundary_128",
1408
+ "attrs": { "kernel_shape": [128], "strides": [1] },
1409
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 255], "data": { "kind": "fillFloat32", "scale": 0.3 } } },
1410
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 128] } }
1411
+ },
1412
+ {
1413
+ "name": "window_parallel_outputs_to_taps_boundary_129",
1414
+ "attrs": { "kernel_shape": [128], "strides": [1] },
1415
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 256], "data": { "kind": "fillFloat32", "scale": 0.3 } } },
1416
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 129] } }
1417
+ },
1418
+ {
1419
+ "name": "window_parallel_genuinely_empty_dilated_window",
1420
+ "attrs": { "kernel_shape": [128], "dilations": [2], "pads": [127, 127] },
1421
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "constant", "value": 7.0 } } },
1422
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
1423
+ },
1424
+ {
1425
+ "name": "window_parallel_arbitrary_workgroup_96",
1426
+ "tunables": { "WORKGROUP_SIZE": 96 },
1427
+ "attrs": { "kernel_shape": [128], "strides": [128] },
1428
+ "inputs": {
1429
+ "x": {
1430
+ "dtype": "float32",
1431
+ "shape": [1, 1, 128],
1432
+ "data": {
1433
+ "kind": "values",
1434
+ "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]
1435
+ }
1436
+ }
1437
+ },
1438
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
1439
+ },
1440
+ {
1441
+ "name": "window_parallel_arbitrary_workgroup_127",
1442
+ "tunables": { "WORKGROUP_SIZE": 127 },
1443
+ "attrs": { "kernel_shape": [128], "strides": [128] },
1444
+ "inputs": {
1445
+ "x": {
1446
+ "dtype": "float32",
1447
+ "shape": [1, 1, 128],
1448
+ "data": {
1449
+ "kind": "values",
1450
+ "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]
1451
+ }
1452
+ }
1453
+ },
1454
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1] } }
1455
  }
1456
  ]
1457
  }