Xenova HF Staff commited on
Commit
0feea6e
·
verified ·
1 Parent(s): 9e0b43e

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,79 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.AveragePool
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 19
12
+
13
+ ## Description
14
+
15
+ Applies average pooling over a sliding kernel window on input tensor `X`, computing the mean of values within each window position and writing results to `Y`. Output spatial dimensions are determined by `kernel_shape`, `strides`, `dilations`, `pads`, and `ceil_mode`; padded positions are excluded from the average by default unless `count_include_pad` is set.
16
+
17
+ See the [ONNX `AveragePool` spec](https://onnx.ai/onnx/operators/onnx__AveragePool.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `X` | `x` | `T` | — | — | Input data tensor of shape `(N x C x D1 x D2 ... Dn)`, where `N` is the batch size and `C` is the number of channels. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- |
29
+ | `Y` | `y` | `T` | same as `X` | derived; see description | Output tensor with pooled values; spatial dimensions vary based on kernel, stride, dilation, and pad settings. | required |
30
+
31
+ ## Attributes
32
+
33
+ Attributes and default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `auto_pad` | `"NOTSET"` | Deprecated auto-padding mode: `NOTSET` (use explicit pads), `SAME_UPPER`, `SAME_LOWER` (pad so output size is `ceil(input / stride)`), or `VALID` (no padding). |
38
+ | `count_include_pad` | `0` | When non-zero, pad pixels are counted in the divisor when computing the average; defaults to 0 (exclude pad). |
39
+ | `ceil_mode` | `0` | When non-zero, uses ceiling instead of floor when computing the output spatial shape; defaults to 0. |
40
+ | `kernel_shape` | — | Required kernel shape, with one positive value per spatial axis. |
41
+ | `strides` | — | Stride along each spatial axis. When omitted, every stride is 1. |
42
+ | `pads` | — | Padding at the beginning and end of each spatial axis, ordered as `[begin_0, ..., begin_n, end_0, ..., end_n]`. When omitted, every pad is 0. |
43
+ | `dilations` | — | Dilation along each spatial axis. When omitted, every dilation is 1. |
44
+
45
+ ## Type constraints
46
+
47
+ | Variable | Allowed dtypes |
48
+ | --- | --- |
49
+ | `T` | `float32`, `float16` |
50
+
51
+ ## Files
52
+
53
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
54
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
+ - [`test.json`](build/webgpu/test.json) — correctness cases
56
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
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
+
63
+ ## Use with `@huggingface/kernels`
64
+
65
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
66
+ It then allocates the result tensors automatically.
67
+
68
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
69
+
70
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
71
+
72
+ ```js
73
+ import { getKernel } from "@huggingface/kernels";
74
+
75
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.AveragePool", { version: 1 });
76
+ const { y } = await kernel({ x: { data: xData, shape: [1, 3, 32] } }, {
77
+ attrs: { kernel_shape: [2] },
78
+ });
79
+ ```
build/webgpu/average-pool2d-nchw-horizontal-reuse.wgsl.jinja ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // Four adjacent windows share source columns. For a K-wide, dilation-D window
7
+ // at stride S, their union spans (K-1)*D+1+3*S columns per row. Each output
8
+ // retains its own accumulation order.
9
+ const COUNT4: u32 = {{ source.count4 }}u;
10
+ const OUT_W4: u32 = max(1u, {{ source.outW4 }}u);
11
+ const OUT_H: u32 = max(1u, {{ source.outH }}u);
12
+ {% if source.scalarOutput is defined and source.scalarOutput %}
13
+ const OUT_W: u32 = {{ source.outW }}u;
14
+ {% endif %}
15
+ const IN_H: i32 = {{ source.inH }};
16
+ const IN_W: i32 = {{ source.inW }};
17
+ const IN_W_U: u32 = {{ source.inW }}u;
18
+ const IN_PLANE: u32 = {{ source.inPlane }}u;
19
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
20
+
21
+ fn load_value(validRow: bool, base: u32, col: i32) -> f32 {
22
+ if (validRow && col >= 0 && col < IN_W) { return f32(x[base + u32(col)]); }
23
+ return 0.0;
24
+ }
25
+
26
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
27
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
28
+ let q = gid.x + gid.y * nwg.x * WG;
29
+ if (q >= COUNT4) { return; }
30
+
31
+ let ow0 = (q % OUT_W4) * 4u;
32
+ var t = q / OUT_W4;
33
+ let oh = t % OUT_H;
34
+ let plane = t / OUT_H;
35
+ let planeBase = plane * IN_PLANE;
36
+ let startW = i32(ow0 * {{ source.strideW }}u) - {{ source.padLeft }};
37
+
38
+ var acc0 = 0.0; var acc1 = 0.0; var acc2 = 0.0; var acc3 = 0.0;
39
+ // Window validity is separable: every valid kernel row combines with every
40
+ // valid kernel column. Count each axis once and combine the counts without
41
+ // retaining a count beside every reused value.
42
+ var validRows = 0u;
43
+ {% for lane in range(4) %}
44
+ var validCols{{ lane }} = 0u;
45
+ {% for kw in range(source.kernelW) %}
46
+ validCols{{ lane }} += select(
47
+ 0u,
48
+ 1u,
49
+ startW + {{ lane * source.strideW + kw * source.dilationW }} >= 0 &&
50
+ startW + {{ lane * source.strideW + kw * source.dilationW }} < IN_W,
51
+ );
52
+ {% endfor %}
53
+ {% endfor %}
54
+ {% for kh in range(source.kernelH) %}
55
+ {
56
+ let ih = i32(oh * {{ source.strideH }}u) + {{ kh * source.dilationH - source.padTop }};
57
+ let validRow = ih >= 0 && ih < IN_H;
58
+ validRows += select(0u, 1u, validRow);
59
+ let rowBase = planeBase + u32(max(ih, 0)) * IN_W_U;
60
+ {% for j in range((source.kernelW - 1) * source.dilationW + 1 + 3 * source.strideW) %}
61
+ let v{{ kh }}_{{ j }} = load_value(validRow, rowBase, startW + {{ j }});
62
+ {% endfor %}
63
+ {% for lane in range(4) %}
64
+ {% for kw in range(source.kernelW) %}
65
+ acc{{ lane }} += v{{ kh }}_{{ lane * source.strideW + kw * source.dilationW }};
66
+ {% endfor %}
67
+ {% endfor %}
68
+ }
69
+ {% endfor %}
70
+ {% for lane in range(4) %}
71
+ let cnt{{ lane }} = validRows * validCols{{ lane }};
72
+ {% endfor %}
73
+
74
+ {% if source.scalarOutput is defined and source.scalarOutput %}
75
+ let outBase = (plane * OUT_H + oh) * OUT_W + ow0;
76
+ y[outBase] = {{ scalar }}(select(0.0, acc0 / f32(cnt0), cnt0 > 0u));
77
+ if (ow0 + 1u < OUT_W) { y[outBase + 1u] = {{ scalar }}(select(0.0, acc1 / f32(cnt1), cnt1 > 0u)); }
78
+ if (ow0 + 2u < OUT_W) { y[outBase + 2u] = {{ scalar }}(select(0.0, acc2 / f32(cnt2), cnt2 > 0u)); }
79
+ if (ow0 + 3u < OUT_W) { y[outBase + 3u] = {{ scalar }}(select(0.0, acc3 / f32(cnt3), cnt3 > 0u)); }
80
+ {% else %}
81
+ y[q] = {{ vectorScalar }}(
82
+ {{ scalar }}(select(0.0, acc0 / f32(cnt0), cnt0 > 0u)),
83
+ {{ scalar }}(select(0.0, acc1 / f32(cnt1), cnt1 > 0u)),
84
+ {{ scalar }}(select(0.0, acc2 / f32(cnt2), cnt2 > 0u)),
85
+ {{ scalar }}(select(0.0, acc3 / f32(cnt3), cnt3 > 0u))
86
+ );
87
+ {% endif %}
88
+ }
build/webgpu/average-pool2d-nchw-w3s1-reuse.wgsl.jinja ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // 2D NCHW AveragePool specialised for a 3-wide / stride-1 / pad-1 / dilation-1
7
+ // W window (countIncludePad == 0). Each invocation produces four adjacent output
8
+ // columns. With stride 1 the windows of adjacent columns overlap by 2, so the
9
+ // direct evaluation rereads each input column up to three times. Each invocation
10
+ // instead loads six contiguous columns per kernel row and slides the window in
11
+ // registers. Accumulation remains kh-major then kw-major, and excludes padding
12
+ // from the denominator.
13
+ const OUT_W: u32 = {{ source.outW }}u;
14
+ const OUT_H: u32 = {{ source.outH }}u;
15
+ const OUT_W_Q: u32 = {{ source.outWQuarter }}u; // OUT_W / 4 (columns-of-four)
16
+ const IN_H: i32 = {{ source.inH }};
17
+ const IN_W: i32 = {{ source.inW }};
18
+ const IN_W_U: u32 = {{ source.inW }}u;
19
+ const IN_PLANE: u32 = {{ source.inPlane }}u;
20
+ const PAD_TOP: i32 = {{ source.padTop }};
21
+
22
+ // Load input element at (rowBase, col), returning 0 outside [0, IN_W). The row
23
+ // is already gated, so only the horizontal bound is checked here.
24
+ fn load_col(rowValid: bool, rowBase: u32, col: i32) -> f32 {
25
+ if (rowValid && col >= 0 && col < IN_W) {
26
+ return f32(x[rowBase + u32(col)]);
27
+ }
28
+ return 0.0;
29
+ }
30
+
31
+ fn col_valid(rowValid: bool, col: i32) -> u32 {
32
+ if (rowValid && col >= 0 && col < IN_W) { return 1u; }
33
+ return 0u;
34
+ }
35
+
36
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
37
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
38
+ let tile = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
39
+ if (tile >= params.tileCount) {
40
+ return;
41
+ }
42
+
43
+ let colTile = tile % OUT_W_Q; // which group-of-four columns in this row
44
+ var t = tile / OUT_W_Q;
45
+ let oh = t % OUT_H;
46
+ let plane = t / OUT_H; // batch * channels + channel
47
+ let planeBase = plane * IN_PLANE;
48
+
49
+ let ow0 = colTile * 4u; // first output column owned by this thread
50
+ let startW = i32(ow0) - 1; // PAD_LEFT == 1; leftmost input column read
51
+ let startH = i32(oh) - PAD_TOP; // strideH == 1
52
+
53
+ var acc0 = 0.0; var acc1 = 0.0; var acc2 = 0.0; var acc3 = 0.0;
54
+ var cnt0 = 0u; var cnt1 = 0u; var cnt2 = 0u; var cnt3 = 0u;
55
+
56
+ {% for kh in range(source.kernelH) %}
57
+ {
58
+ let ih = startH + {{ kh * source.dilationH }};
59
+ let rowValid = ih >= 0 && ih < IN_H;
60
+ let rowBase = planeBase + u32(max(ih, 0)) * IN_W_U;
61
+ // Six contiguous input columns feeding the four 3-wide windows.
62
+ let v0 = load_col(rowValid, rowBase, startW + 0);
63
+ let v1 = load_col(rowValid, rowBase, startW + 1);
64
+ let v2 = load_col(rowValid, rowBase, startW + 2);
65
+ let v3 = load_col(rowValid, rowBase, startW + 3);
66
+ let v4 = load_col(rowValid, rowBase, startW + 4);
67
+ let v5 = load_col(rowValid, rowBase, startW + 5);
68
+ let c0 = col_valid(rowValid, startW + 0);
69
+ let c1 = col_valid(rowValid, startW + 1);
70
+ let c2 = col_valid(rowValid, startW + 2);
71
+ let c3 = col_valid(rowValid, startW + 3);
72
+ let c4 = col_valid(rowValid, startW + 4);
73
+ let c5 = col_valid(rowValid, startW + 5);
74
+ acc0 = acc0 + v0 + v1 + v2; cnt0 = cnt0 + c0 + c1 + c2;
75
+ acc1 = acc1 + v1 + v2 + v3; cnt1 = cnt1 + c1 + c2 + c3;
76
+ acc2 = acc2 + v2 + v3 + v4; cnt2 = cnt2 + c2 + c3 + c4;
77
+ acc3 = acc3 + v3 + v4 + v5; cnt3 = cnt3 + c3 + c4 + c5;
78
+ }
79
+ {% endfor %}
80
+
81
+ let outBase = (plane * OUT_H + oh) * OUT_W + ow0;
82
+ y[outBase + 0u] = {{ scalar }}(select(0.0, acc0 / f32(cnt0), cnt0 > 0u));
83
+ y[outBase + 1u] = {{ scalar }}(select(0.0, acc1 / f32(cnt1), cnt1 > 0u));
84
+ y[outBase + 2u] = {{ scalar }}(select(0.0, acc2 / f32(cnt2), cnt2 > 0u));
85
+ y[outBase + 3u] = {{ scalar }}(select(0.0, acc3 / f32(cnt3), cnt3 > 0u));
86
+ }
build/webgpu/bench.json ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.AveragePool",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "average-pool-f32-b1c32-64x64-k2s2",
7
+ "preset": "smoke",
8
+ "vars": { "batch": 1, "channels": 32, "inH": 64, "inW": 64 },
9
+ "attrs": { "kernel_shape": [2, 2], "strides": [2, 2], "pads": [0, 0, 0, 0] },
10
+ "inputs": { "x": { "shape": [1, 32, 64, 64], "dtype": "float32", "dist": "normal", "seed": 116, "scale": 0.2 } },
11
+ "outputs": { "y": { "shape": [1, 32, 32, 32], "dtype": "float32" } },
12
+ "bench": {
13
+ "primary": true,
14
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4" }]
15
+ }
16
+ },
17
+ {
18
+ "name": "nchw_8x64x112x112_k3s1p1_unrolled",
19
+ "preset": "smoke",
20
+ "vars": { "batch": 8, "channels": 64, "inH": 112, "inW": 112 },
21
+ "attrs": { "kernel_shape": [3, 3], "strides": [1, 1], "pads": [1, 1, 1, 1] },
22
+ "inputs": { "x": { "dtype": "float32", "shape": [8, 64, 112, 112], "dist": "normal", "seed": 205 } },
23
+ "outputs": { "y": { "dtype": "float32", "shape": [8, 64, 112, 112], "dist": "empty" } },
24
+ "bench": {
25
+ "primary": true,
26
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4 * 2" }]
27
+ }
28
+ },
29
+ {
30
+ "name": "nchw_f16_8x64x112x112_k3s1p1_unrolled",
31
+ "preset": "smoke",
32
+ "vars": { "batch": 8, "channels": 64, "inH": 112, "inW": 112 },
33
+ "attrs": { "kernel_shape": [3, 3], "strides": [1, 1], "pads": [1, 1, 1, 1] },
34
+ "inputs": { "x": { "dtype": "float16", "shape": [8, 64, 112, 112], "dist": "normal", "seed": 206 } },
35
+ "outputs": { "y": { "dtype": "float16", "shape": [8, 64, 112, 112], "dist": "empty" } },
36
+ "bench": {
37
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2 * 2" }]
38
+ }
39
+ },
40
+ {
41
+ "name": "average-pool-f32-b1c64-224x224-k7s2p3-generic",
42
+ "preset": "smoke",
43
+ "vars": { "batch": 1, "channels": 64, "inH": 224, "inW": 224 },
44
+ "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [2, 2], "pads": [3, 3, 3, 3] },
45
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 64, 224, 224], "dist": "normal", "seed": 311, "scale": 0.2 } },
46
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 112, 112], "dist": "empty" } },
47
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4" }] }
48
+ },
49
+ {
50
+ "name": "average-pool-f16-b1c64-224x224-k7s2p3-generic",
51
+ "preset": "smoke",
52
+ "vars": { "batch": 1, "channels": 64, "inH": 224, "inW": 224 },
53
+ "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [2, 2], "pads": [3, 3, 3, 3] },
54
+ "inputs": { "x": { "dtype": "float16", "shape": [1, 64, 224, 224], "dist": "normal", "seed": 312, "scale": 0.2 } },
55
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 64, 112, 112], "dist": "empty" } },
56
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2" }] }
57
+ },
58
+ {
59
+ "name": "average-pool-f32-b8c64-112x114-k3s1p1-unaligned-fallback",
60
+ "preset": "smoke",
61
+ "vars": { "batch": 8, "channels": 64, "inH": 112, "inW": 114 },
62
+ "attrs": { "count_include_pad": 0, "kernel_shape": [3, 3], "strides": [1, 1], "pads": [1, 1, 1, 1] },
63
+ "inputs": { "x": { "dtype": "float32", "shape": [8, 64, 112, 114], "dist": "normal", "seed": 313, "scale": 0.2 } },
64
+ "outputs": { "y": { "dtype": "float32", "shape": [8, 64, 112, 114], "dist": "empty" } },
65
+ "bench": {
66
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4 * 2" }]
67
+ }
68
+ },
69
+ {
70
+ "name": "average-pool-f32-ncdhw-b1c32-32x64x64-k2s2-3d",
71
+ "preset": "smoke",
72
+ "vars": { "batch": 1, "channels": 32, "inD": 32, "inH": 64, "inW": 64 },
73
+ "attrs": { "count_include_pad": 0, "kernel_shape": [2, 2, 2], "strides": [2, 2, 2], "pads": [0, 0, 0, 0, 0, 0] },
74
+ "inputs": {
75
+ "x": { "dtype": "float32", "shape": [1, 32, 32, 64, 64], "dist": "normal", "seed": 314, "scale": 0.2 }
76
+ },
77
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 16, 32, 32], "dist": "empty" } },
78
+ "bench": {
79
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inD * args.inH * args.inW * 4" }]
80
+ }
81
+ },
82
+ {
83
+ "name": "average-pool-f32-ncl-b8c256-1024-k3s2-1d",
84
+ "preset": "smoke",
85
+ "vars": { "batch": 8, "channels": 256, "inW": 1024 },
86
+ "attrs": { "count_include_pad": 0, "kernel_shape": [3], "strides": [2], "pads": [0, 0] },
87
+ "inputs": { "x": { "dtype": "float32", "shape": [8, 256, 1024], "dist": "normal", "seed": 315, "scale": 0.2 } },
88
+ "outputs": { "y": { "dtype": "float32", "shape": [8, 256, 511], "dist": "empty" } },
89
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inW * 4" }] }
90
+ },
91
+ {
92
+ "name": "global_avg_pool_bigkernel_tiny_output_launch_starved_stress",
93
+ "preset": "stress",
94
+ "vars": { "batch": 1, "channels": 512, "inH": 64, "inW": 64 },
95
+ "attrs": { "count_include_pad": 0, "kernel_shape": [64, 64], "strides": [1, 1], "pads": [0, 0, 0, 0] },
96
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 512, 64, 64], "dist": "normal", "seed": 4101, "scale": 0.2 } },
97
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 512, 1, 1], "dist": "empty" } },
98
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4" }] }
99
+ },
100
+ {
101
+ "name": "global-avg-pool-f32-b2c256-96x96-segmentation-head",
102
+ "preset": "stress",
103
+ "vars": { "batch": 2, "channels": 256, "inH": 96, "inW": 96 },
104
+ "attrs": { "count_include_pad": 0, "kernel_shape": [96, 96], "strides": [1, 1], "pads": [0, 0, 0, 0] },
105
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 256, 96, 96], "dist": "normal", "seed": 4103, "scale": 0.2 } },
106
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 256, 1, 1], "dist": "empty" } },
107
+ "bench": {
108
+ "primary": true,
109
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4" }]
110
+ }
111
+ },
112
+ {
113
+ "name": "nchw2d_generic_k7s1_overlap_reread_large_map_stress",
114
+ "preset": "stress",
115
+ "vars": { "batch": 4, "channels": 64, "inH": 128, "inW": 128 },
116
+ "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [1, 1], "pads": [3, 3, 3, 3] },
117
+ "inputs": {
118
+ "x": { "dtype": "float32", "shape": [4, 64, 128, 128], "dist": "normal", "seed": 4102, "scale": 0.2 }
119
+ },
120
+ "outputs": { "y": { "dtype": "float32", "shape": [4, 64, 128, 128], "dist": "empty" } },
121
+ "bench": {
122
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4 * 2" }]
123
+ }
124
+ },
125
+ {
126
+ "name": "average-pool-k7s1p3-f32-b2c128-96x96-segmentation-context",
127
+ "preset": "stress",
128
+ "vars": { "batch": 2, "channels": 128, "inH": 96, "inW": 96 },
129
+ "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [1, 1], "pads": [3, 3, 3, 3] },
130
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 128, 96, 96], "dist": "normal", "seed": 4104, "scale": 0.2 } },
131
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 128, 96, 96], "dist": "empty" } },
132
+ "bench": {
133
+ "primary": true,
134
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4 * 2" }]
135
+ }
136
+ },
137
+ {
138
+ "name": "average-pool-k7s1p3-f32-b1c64-112x113-odd-width-cliff",
139
+ "preset": "stress",
140
+ "vars": { "batch": 1, "channels": 64, "inH": 112, "inW": 113 },
141
+ "attrs": { "count_include_pad": 0, "kernel_shape": [7, 7], "strides": [1, 1], "pads": [3, 3, 3, 3] },
142
+ "inputs": {
143
+ "x": { "dtype": "float32", "shape": [1, 64, 112, 113], "dist": "normal", "seed": 4105, "scale": 0.2 }
144
+ },
145
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 112, 113], "dist": "empty" } },
146
+ "bench": {
147
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4 * 2" }]
148
+ }
149
+ },
150
+ {
151
+ "name": "global-averagepool-rank5-serial-reduction-pathology",
152
+ "preset": "stress",
153
+ "provenance": {
154
+ "source": "authored for variant coverage",
155
+ "notes": "Rank-5 global pooling over 131072 spatial values per plane verifies global_window_parallel coverage and its feature-tier fallbacks."
156
+ },
157
+ "vars": { "batch": 1, "channels": 32, "inD": 32, "inH": 64, "inW": 64 },
158
+ "attrs": {
159
+ "count_include_pad": 0,
160
+ "kernel_shape": [32, 64, 64],
161
+ "strides": [1, 1, 1],
162
+ "pads": [0, 0, 0, 0, 0, 0]
163
+ },
164
+ "inputs": {
165
+ "x": { "dtype": "float32", "shape": [1, 32, 32, 64, 64], "dist": "normal", "seed": 5101, "scale": 0.2 }
166
+ },
167
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 1, 1, 1], "dist": "empty" } },
168
+ "bench": {
169
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inD * args.inH * args.inW * 4" }]
170
+ }
171
+ }
172
+ ]
173
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "AveragePool",
4
+ "sinceVersion": 19,
5
+ "description": "Applies average pooling over a sliding kernel window on input tensor `X`, computing the mean of values within each window position and writing results to `Y`. Output spatial dimensions are determined by `kernel_shape`, `strides`, `dilations`, `pads`, and `ceil_mode`; padded positions are excluded from the average by default unless `count_include_pad` is set.",
6
+ "inputs": [
7
+ {
8
+ "role": "X",
9
+ "dtype": "T",
10
+ "description": "Input data tensor of shape `(N x C x D1 x D2 ... Dn)`, where `N` is the batch size and `C` is the number of channels."
11
+ }
12
+ ],
13
+ "outputs": [
14
+ {
15
+ "role": "Y",
16
+ "dtype": "T",
17
+ "description": "Output tensor with pooled values; spatial dimensions vary based on kernel, stride, dilation, and pad settings.",
18
+ "rank": "ranks.X",
19
+ "shape": "poolOutputShape"
20
+ }
21
+ ],
22
+ "attributes": { "auto_pad": "NOTSET", "count_include_pad": 0, "ceil_mode": 0 },
23
+ "attributeDescriptions": {
24
+ "auto_pad": "Deprecated auto-padding mode: `NOTSET` (use explicit pads), `SAME_UPPER`, `SAME_LOWER` (pad so output size is `ceil(input / stride)`), or `VALID` (no padding).",
25
+ "kernel_shape": "Required kernel shape, with one positive value per spatial axis.",
26
+ "strides": "Stride along each spatial axis. When omitted, every stride is 1.",
27
+ "pads": "Padding at the beginning and end of each spatial axis, ordered as `[begin_0, ..., begin_n, end_0, ..., end_n]`. When omitted, every pad is 0.",
28
+ "dilations": "Dilation along each spatial axis. When omitted, every dilation is 1.",
29
+ "count_include_pad": "When non-zero, pad pixels are counted in the divisor when computing the average; defaults to 0 (exclude pad).",
30
+ "ceil_mode": "When non-zero, uses ceiling instead of floor when computing the output spatial shape; defaults to 0."
31
+ },
32
+ "attributeConstraints": {
33
+ "auto_pad": { "values": ["NOTSET", "SAME_UPPER", "SAME_LOWER", "VALID"] },
34
+ "ceil_mode": { "values": [0, 1] },
35
+ "count_include_pad": { "values": [0, 1] },
36
+ "kernel_shape": { "required": true }
37
+ },
38
+ "typeConstraints": { "T": ["float32", "float16"] },
39
+ "args": {
40
+ "x": { "kind": "tensor", "semantic": "X", "role": "input" },
41
+ "y": { "kind": "tensor", "semantic": "Y", "role": "output" }
42
+ },
43
+ "derive": {
44
+ "spatialRank": "ranks.X - 2",
45
+ "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
46
+ "kernelShapeLengthOk": "not has(attrs, \"kernel_shape\") or (attrs.kernel_shape | length) == spatialRank",
47
+ "stridesLengthOk": "not has(attrs, \"strides\") or (attrs.strides | length) == spatialRank",
48
+ "dilationsLengthOk": "not has(attrs, \"dilations\") or (attrs.dilations | length) == spatialRank",
49
+ "padsLengthOk": "not has(attrs, \"pads\") or (attrs.pads | length) == 2 * spatialRank",
50
+ "kernelD": "attrs.kernel_shape[0] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank == 3 else 1",
51
+ "kernelH": "attrs.kernel_shape[spatialRank - 2] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 2 else 1",
52
+ "kernelW": "attrs.kernel_shape[spatialRank - 1] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 1 else 1",
53
+ "strideD": "attrs.strides[0] if stridesLengthOk and has(attrs, \"strides\") and spatialRank == 3 else 1",
54
+ "strideH": "attrs.strides[spatialRank - 2] if stridesLengthOk and has(attrs, \"strides\") and spatialRank >= 2 else 1",
55
+ "strideW": "attrs.strides[spatialRank - 1] if stridesLengthOk and has(attrs, \"strides\") and spatialRank >= 1 else 1",
56
+ "dilationD": "attrs.dilations[0] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank == 3 else 1",
57
+ "dilationH": "attrs.dilations[spatialRank - 2] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank >= 2 else 1",
58
+ "dilationW": "attrs.dilations[spatialRank - 1] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank >= 1 else 1",
59
+ "padFront": "attrs.pads[0] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
60
+ "padTop": "attrs.pads[spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
61
+ "padLeft": "attrs.pads[spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
62
+ "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
63
+ "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
64
+ "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
65
+ "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)",
66
+ "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",
67
+ "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",
68
+ "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",
69
+ "inferredOutW": "ceil(dim(shapes.X, ranks.X - 1) / strideW) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutW",
70
+ "inferredOutH": "ceil(dim(shapes.X, ranks.X - 2) / strideH) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutH",
71
+ "inferredOutD": "ceil(dim(shapes.X, 2) / strideD) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutD",
72
+ "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])",
73
+ "autoPadTotalH": "max(0, (dim(shapes.Y, 2) - 1) * strideH + ((kernelH - 1) * dilationH + 1) - dim(shapes.X, 2)) if ranks.X == 4 else 0",
74
+ "autoPadTotalW": "max(0, (dim(shapes.Y, 3) - 1) * strideW + ((kernelW - 1) * dilationW + 1) - dim(shapes.X, 3)) if ranks.X == 4 else 0",
75
+ "effectivePadTop": "(floor(autoPadTotalH / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalH - floor(autoPadTotalH / 2)) if ranks.X == 4 and (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padTop",
76
+ "effectivePadLeft": "(floor(autoPadTotalW / 2) if attrs.auto_pad == \"SAME_UPPER\" else autoPadTotalW - floor(autoPadTotalW / 2)) if ranks.X == 4 and (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padLeft",
77
+ "effectivePadBottom": "autoPadTotalH - effectivePadTop if ranks.X == 4 and (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padBottom",
78
+ "effectivePadRight": "autoPadTotalW - effectivePadLeft if ranks.X == 4 and (attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") else padRight",
79
+ "narrowF32W3UnrolledPreferred": "narrowSubgroupRange and dtypes.T == \"f32\"",
80
+ "globalWindowParallelCovered": "poolBaseOk and numel(shapes.Y) == dim(shapes.X, 0) * dim(shapes.X, 1) and numel(shapes.Y) <= device.limits.maxComputeWorkgroupsPerDimension 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"
81
+ },
82
+ "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
83
+ "bindingSets": {
84
+ "globalReduction": [
85
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$xElem" },
86
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
87
+ {
88
+ "name": "params",
89
+ "semantic": "kernel.params",
90
+ "buffer": { "type": "uniform" },
91
+ "struct": { "name": "Params", "fields": [{ "name": "spatial", "type": "u32", "value": "inner(shapes.X, 1)" }] }
92
+ }
93
+ ],
94
+ "horizontal": [
95
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
96
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$outputElement" }
97
+ ],
98
+ "w3Reuse": [
99
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
100
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
101
+ {
102
+ "name": "params",
103
+ "semantic": "kernel.params",
104
+ "buffer": { "type": "uniform" },
105
+ "struct": {
106
+ "name": "Params",
107
+ "fields": [{ "name": "tileCount", "type": "u32", "value": "numel(shapes.Y) / 4" }]
108
+ }
109
+ }
110
+ ],
111
+ "flatDirect": [
112
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
113
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
114
+ {
115
+ "name": "params",
116
+ "semantic": "kernel.params",
117
+ "buffer": { "type": "uniform" },
118
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.Y)" }] }
119
+ }
120
+ ],
121
+ "rank1Direct": [
122
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
123
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
124
+ {
125
+ "name": "params",
126
+ "semantic": "kernel.params",
127
+ "buffer": { "type": "uniform" },
128
+ "struct": {
129
+ "name": "Params",
130
+ "fields": [
131
+ { "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
132
+ { "name": "inW", "type": "u32", "value": "dim(shapes.X, 2)" },
133
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 2)" },
134
+ { "name": "kernelW", "type": "u32", "value": "kernelW" },
135
+ { "name": "strideW", "type": "u32", "value": "strideW" },
136
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
137
+ { "name": "padLeft", "type": "i32", "value": "padLeft" },
138
+ { "name": "padRight", "type": "i32", "value": "padRight" },
139
+ { "name": "countIncludePad", "type": "u32", "value": "attrs.count_include_pad" },
140
+ { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
141
+ ]
142
+ }
143
+ }
144
+ ],
145
+ "rank3Direct": [
146
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
147
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
148
+ {
149
+ "name": "params",
150
+ "semantic": "kernel.params",
151
+ "buffer": { "type": "uniform" },
152
+ "struct": {
153
+ "name": "Params",
154
+ "fields": [
155
+ { "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
156
+ { "name": "inD", "type": "u32", "value": "dim(shapes.X, 2)" },
157
+ { "name": "inH", "type": "u32", "value": "dim(shapes.X, 3)" },
158
+ { "name": "inW", "type": "u32", "value": "dim(shapes.X, 4)" },
159
+ { "name": "outD", "type": "u32", "value": "dim(shapes.Y, 2)" },
160
+ { "name": "outH", "type": "u32", "value": "dim(shapes.Y, 3)" },
161
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 4)" },
162
+ { "name": "kernelD", "type": "u32", "value": "kernelD" },
163
+ { "name": "kernelH", "type": "u32", "value": "kernelH" },
164
+ { "name": "kernelW", "type": "u32", "value": "kernelW" },
165
+ { "name": "strideD", "type": "u32", "value": "strideD" },
166
+ { "name": "strideH", "type": "u32", "value": "strideH" },
167
+ { "name": "strideW", "type": "u32", "value": "strideW" },
168
+ { "name": "dilationD", "type": "u32", "value": "dilationD" },
169
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
170
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
171
+ { "name": "padFront", "type": "i32", "value": "padFront" },
172
+ { "name": "padTop", "type": "i32", "value": "padTop" },
173
+ { "name": "padLeft", "type": "i32", "value": "padLeft" },
174
+ { "name": "padBack", "type": "i32", "value": "padBack" },
175
+ { "name": "padBottom", "type": "i32", "value": "padBottom" },
176
+ { "name": "padRight", "type": "i32", "value": "padRight" },
177
+ { "name": "countIncludePad", "type": "u32", "value": "attrs.count_include_pad" },
178
+ { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
179
+ ]
180
+ }
181
+ }
182
+ ],
183
+ "rank2Direct": [
184
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
185
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
186
+ {
187
+ "name": "params",
188
+ "semantic": "kernel.params",
189
+ "buffer": { "type": "uniform" },
190
+ "struct": {
191
+ "name": "Params",
192
+ "fields": [
193
+ { "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
194
+ { "name": "inH", "type": "u32", "value": "dim(shapes.X, 2)" },
195
+ { "name": "inW", "type": "u32", "value": "dim(shapes.X, 3)" },
196
+ { "name": "outH", "type": "u32", "value": "dim(shapes.Y, 2)" },
197
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 3)" },
198
+ { "name": "kernelH", "type": "u32", "value": "kernelH" },
199
+ { "name": "kernelW", "type": "u32", "value": "kernelW" },
200
+ { "name": "strideH", "type": "u32", "value": "strideH" },
201
+ { "name": "strideW", "type": "u32", "value": "strideW" },
202
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
203
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
204
+ { "name": "padTop", "type": "i32", "value": "effectivePadTop" },
205
+ { "name": "padLeft", "type": "i32", "value": "effectivePadLeft" },
206
+ { "name": "padBottom", "type": "i32", "value": "effectivePadBottom" },
207
+ { "name": "padRight", "type": "i32", "value": "effectivePadRight" },
208
+ { "name": "countIncludePad", "type": "u32", "value": "attrs.count_include_pad" },
209
+ { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
210
+ ]
211
+ }
212
+ }
213
+ ]
214
+ },
215
+ "tunables": { "WORKGROUP_SIZE": 256 },
216
+ "variants": [
217
+ {
218
+ "id": "global_window_parallel",
219
+ "priority": 50,
220
+ "when": ["globalWindowParallelCovered"],
221
+ "constants": {
222
+ "xElem": "(\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\") if inner(shapes.X, 1) % 4 == 0 else dtypes.T"
223
+ },
224
+ "passes": [
225
+ {
226
+ "id": "main",
227
+ "name": "AveragePool.GlobalWindowParallel",
228
+ "source": {
229
+ "shader": "pool-global-reduction.wgsl.jinja",
230
+ "inputs": {
231
+ "op": "\"average\"",
232
+ "vec4": "inner(shapes.X, 1) % 4 == 0",
233
+ "workgroupSize": "tunables.WORKGROUP_SIZE",
234
+ "useSubgroups": "device.features.has(\"subgroups\")"
235
+ }
236
+ },
237
+ "bindings": "globalReduction",
238
+ "dispatch": { "x": "numel(shapes.Y)" }
239
+ }
240
+ ]
241
+ },
242
+ {
243
+ "id": "nchw2d_k7s1_reuse_tail",
244
+ "priority": 34,
245
+ "when": ["poolBaseOk", "ranks.X == 4", "kernelH == 7", "kernelW == 7", "strideH == 1", "strideW == 1", "dilationH == 1", "dilationW == 1", "padTop == 3", "padBottom == 3", "padLeft == 3", "padRight == 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"],
246
+ "constants": { "outputElement": "dtypes.T" },
247
+ "passes": [
248
+ {
249
+ "id": "main",
250
+ "name": "AveragePool.Nchw2dK7s1ReuseTail",
251
+ "source": {
252
+ "shader": "average-pool2d-nchw-horizontal-reuse.wgsl.jinja",
253
+ "inputs": {
254
+ "kernelH": 7,
255
+ "kernelW": 7,
256
+ "strideH": 1,
257
+ "strideW": 1,
258
+ "dilationH": 1,
259
+ "dilationW": 1,
260
+ "padTop": 3,
261
+ "padLeft": 3,
262
+ "inH": "dim(shapes.X, 2)",
263
+ "inW": "dim(shapes.X, 3)",
264
+ "inPlane": "dim(shapes.X, 2) * dim(shapes.X, 3)",
265
+ "outH": "dim(shapes.Y, 2)",
266
+ "outW": "dim(shapes.Y, 3)",
267
+ "outW4": "ceilDiv(dim(shapes.Y, 3), 4)",
268
+ "count4": "dim(shapes.Y, 0) * dim(shapes.Y, 1) * dim(shapes.Y, 2) * ceilDiv(dim(shapes.Y, 3), 4)",
269
+ "scalarOutput": true
270
+ }
271
+ },
272
+ "bindings": "horizontal",
273
+ "dispatch": {
274
+ "threads": "dim(shapes.Y, 0) * dim(shapes.Y, 1) * dim(shapes.Y, 2) * ceilDiv(dim(shapes.Y, 3), 4)",
275
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
276
+ }
277
+ }
278
+ ]
279
+ },
280
+ {
281
+ "id": "nchw2d_w3s1_reuse",
282
+ "priority": 30,
283
+ "demoteWhen": ["narrowF32W3UnrolledPreferred"],
284
+ "when": ["poolBaseOk", "ranks.X == 4", "kernelW == 3", "strideW == 1", "dilationW == 1", "padLeft == 1", "padRight == 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) + padTop + padBottom - ((kernelH - 1) * dilationH + 1) + 1", "dim(shapes.Y, 3) == dim(shapes.X, 3)", "dim(shapes.Y, 2) > 0", "dim(shapes.Y, 3) > 0"],
285
+ "passes": [
286
+ {
287
+ "id": "main",
288
+ "name": "AveragePool.Nchw2dW3s1Reuse",
289
+ "source": {
290
+ "shader": "average-pool2d-nchw-w3s1-reuse.wgsl.jinja",
291
+ "inputs": {
292
+ "kernelH": "kernelH",
293
+ "dilationH": "dilationH",
294
+ "padTop": "padTop",
295
+ "inH": "dim(shapes.X, 2)",
296
+ "inW": "dim(shapes.X, 3)",
297
+ "inPlane": "dim(shapes.X, 2) * dim(shapes.X, 3)",
298
+ "outH": "dim(shapes.Y, 2)",
299
+ "outW": "dim(shapes.Y, 3)",
300
+ "outWQuarter": "floor(dim(shapes.Y, 3) / 4)"
301
+ }
302
+ },
303
+ "bindings": "w3Reuse",
304
+ "dispatch": { "threads": "numel(shapes.Y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
305
+ }
306
+ ]
307
+ },
308
+ {
309
+ "id": "nchw2d_w3s1_reuse_tail",
310
+ "priority": 29,
311
+ "when": ["poolBaseOk", "ranks.X == 4", "kernelW == 3", "strideW == 1", "dilationW == 1", "padLeft == 1", "padRight == 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) + padTop + padBottom - ((kernelH - 1) * dilationH + 1) + 1", "dim(shapes.Y, 3) == dim(shapes.X, 3)", "dim(shapes.Y, 2) > 0", "dim(shapes.Y, 3) > 0"],
312
+ "constants": { "outputElement": "dtypes.T" },
313
+ "passes": [
314
+ {
315
+ "id": "main",
316
+ "name": "AveragePool.Nchw2dW3s1ReuseTail",
317
+ "source": {
318
+ "shader": "average-pool2d-nchw-horizontal-reuse.wgsl.jinja",
319
+ "inputs": {
320
+ "kernelH": "kernelH",
321
+ "kernelW": 3,
322
+ "strideH": 1,
323
+ "strideW": 1,
324
+ "dilationH": "dilationH",
325
+ "dilationW": 1,
326
+ "padTop": "padTop",
327
+ "padLeft": 1,
328
+ "inH": "dim(shapes.X, 2)",
329
+ "inW": "dim(shapes.X, 3)",
330
+ "inPlane": "dim(shapes.X, 2) * dim(shapes.X, 3)",
331
+ "outH": "dim(shapes.Y, 2)",
332
+ "outW": "dim(shapes.Y, 3)",
333
+ "outW4": "ceilDiv(dim(shapes.Y, 3), 4)",
334
+ "count4": "dim(shapes.Y, 0) * dim(shapes.Y, 1) * dim(shapes.Y, 2) * ceilDiv(dim(shapes.Y, 3), 4)",
335
+ "scalarOutput": true
336
+ }
337
+ },
338
+ "bindings": "horizontal",
339
+ "dispatch": {
340
+ "threads": "dim(shapes.Y, 0) * dim(shapes.Y, 1) * dim(shapes.Y, 2) * ceilDiv(dim(shapes.Y, 3), 4)",
341
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
342
+ }
343
+ }
344
+ ]
345
+ },
346
+ {
347
+ "id": "nchw2d_unrolled",
348
+ "priority": 25,
349
+ "when": ["poolBaseOk", "ranks.X == 4", "dim(shapes.Y, 2) == explicitOutH", "dim(shapes.Y, 3) == explicitOutW", "kernelH >= 1", "kernelH <= 3", "kernelW >= 1", "kernelW <= 3", "strideH >= 1", "strideW >= 1", "dilationH >= 1", "dilationW >= 1", "dim(shapes.Y, 2) > 0", "dim(shapes.Y, 3) > 0"],
350
+ "passes": [
351
+ {
352
+ "id": "main",
353
+ "name": "AveragePool.Nchw2dUnrolled",
354
+ "source": {
355
+ "shader": "pool-window-unroll.wgsl.jinja",
356
+ "inputs": {
357
+ "op": "\"average\"",
358
+ "kernelH": "kernelH",
359
+ "kernelW": "kernelW",
360
+ "strideH": "strideH",
361
+ "strideW": "strideW",
362
+ "dilationH": "dilationH",
363
+ "dilationW": "dilationW",
364
+ "padTop": "padTop",
365
+ "padLeft": "padLeft",
366
+ "inH": "dim(shapes.X, 2)",
367
+ "inW": "dim(shapes.X, 3)",
368
+ "outH": "dim(shapes.Y, 2)",
369
+ "outW": "dim(shapes.Y, 3)",
370
+ "padBottom": "padBottom",
371
+ "padRight": "padRight",
372
+ "countIncludePad": "attrs.count_include_pad != 0"
373
+ }
374
+ },
375
+ "bindings": "flatDirect",
376
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
377
+ }
378
+ ]
379
+ },
380
+ {
381
+ "id": "ncl1d",
382
+ "priority": 10,
383
+ "when": ["poolBaseOk", "ranks.X == 3", "dim(shapes.Y, 2) == explicitOutW"],
384
+ "passes": [
385
+ {
386
+ "id": "main",
387
+ "name": "AveragePool.Ncl1d",
388
+ "source": { "shader": "pool-window-nd.wgsl.jinja", "inputs": { "op": "\"average\"", "spatialRank": 1 } },
389
+ "bindings": "rank1Direct",
390
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
391
+ }
392
+ ]
393
+ },
394
+ {
395
+ "id": "nchw2d",
396
+ "when": ["poolBaseOk", "ranks.X == 4", "((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)))"],
397
+ "passes": [
398
+ {
399
+ "id": "main",
400
+ "name": "nchw2d",
401
+ "source": { "shader": "pool-window-nd.wgsl.jinja", "inputs": { "op": "\"average\"", "spatialRank": 2 } },
402
+ "bindings": "rank2Direct",
403
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
404
+ }
405
+ ]
406
+ },
407
+ {
408
+ "id": "ncdhw3d",
409
+ "priority": 10,
410
+ "supersededBy": ["global_window_parallel"],
411
+ "when": ["poolBaseOk", "ranks.X == 5", "dim(shapes.Y, 2) == explicitOutD", "dim(shapes.Y, 3) == explicitOutH", "dim(shapes.Y, 4) == explicitOutW"],
412
+ "passes": [
413
+ {
414
+ "id": "main",
415
+ "name": "AveragePool.Ncdhw3d",
416
+ "source": { "shader": "pool-window-nd.wgsl.jinja", "inputs": { "op": "\"average\"", "spatialRank": 3 } },
417
+ "bindings": "rank3Direct",
418
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
419
+ }
420
+ ]
421
+ },
422
+ {
423
+ "id": "nchw2d_k7s2_reuse",
424
+ "priority": 36,
425
+ "when": ["poolBaseOk", "ranks.X == 4", "kernelH == 7", "kernelW == 7", "strideH == 2", "strideW == 2", "dilationH == 1", "dilationW == 1", "padTop == 3", "padBottom == 3", "padLeft == 3", "padRight == 3", "attrs.count_include_pad == 0", "attrs.ceil_mode == 0", "dim(shapes.Y, 2) == explicitOutH", "dim(shapes.Y, 3) == explicitOutW", "dim(shapes.Y, 3) % 4 == 0", "dim(shapes.Y, 2) > 0", "dim(shapes.Y, 3) > 0"],
426
+ "constants": { "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
427
+ "passes": [
428
+ {
429
+ "id": "main",
430
+ "name": "AveragePool.Nchw2dK7s2Reuse",
431
+ "source": {
432
+ "shader": "average-pool2d-nchw-horizontal-reuse.wgsl.jinja",
433
+ "inputs": {
434
+ "kernelH": 7,
435
+ "kernelW": 7,
436
+ "strideH": 2,
437
+ "strideW": 2,
438
+ "dilationH": 1,
439
+ "dilationW": 1,
440
+ "padTop": 3,
441
+ "padLeft": 3,
442
+ "inH": "dim(shapes.X, 2)",
443
+ "inW": "dim(shapes.X, 3)",
444
+ "inPlane": "dim(shapes.X, 2) * dim(shapes.X, 3)",
445
+ "outH": "dim(shapes.Y, 2)",
446
+ "outW": "dim(shapes.Y, 3)",
447
+ "outW4": "dim(shapes.Y, 3) / 4",
448
+ "count4": "numel(shapes.Y) / 4"
449
+ }
450
+ },
451
+ "bindings": "horizontal",
452
+ "dispatch": { "threads": "numel(shapes.Y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
453
+ }
454
+ ]
455
+ },
456
+ {
457
+ "id": "nchw2d_k7s1_reuse",
458
+ "priority": 35,
459
+ "when": ["poolBaseOk", "ranks.X == 4", "kernelH == 7", "kernelW == 7", "strideH == 1", "strideW == 1", "dilationH == 1", "dilationW == 1", "padTop == 3", "padBottom == 3", "padLeft == 3", "padRight == 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"],
460
+ "constants": { "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
461
+ "passes": [
462
+ {
463
+ "id": "main",
464
+ "name": "AveragePool.Nchw2dK7s1Reuse",
465
+ "source": {
466
+ "shader": "average-pool2d-nchw-horizontal-reuse.wgsl.jinja",
467
+ "inputs": {
468
+ "kernelH": 7,
469
+ "kernelW": 7,
470
+ "strideH": 1,
471
+ "strideW": 1,
472
+ "dilationH": 1,
473
+ "dilationW": 1,
474
+ "padTop": 3,
475
+ "padLeft": 3,
476
+ "inH": "dim(shapes.X, 2)",
477
+ "inW": "dim(shapes.X, 3)",
478
+ "inPlane": "dim(shapes.X, 2) * dim(shapes.X, 3)",
479
+ "outH": "dim(shapes.Y, 2)",
480
+ "outW": "dim(shapes.Y, 3)",
481
+ "outW4": "dim(shapes.Y, 3) / 4",
482
+ "count4": "numel(shapes.Y) / 4"
483
+ }
484
+ },
485
+ "bindings": "horizontal",
486
+ "dispatch": { "threads": "numel(shapes.Y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
487
+ }
488
+ ]
489
+ }
490
+ ]
491
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.AveragePool",
3
+ "id": "_ai_onnx_averagepool_webgpu_208ddea",
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": "MuniH46O1hxMO9h44Pke8TPe9LdxPJxSfkWzpzH7ORA=",
11
+ "average-pool2d-nchw-w3s1-reuse.wgsl.jinja": "zPPcxTvNNFow8AZiF6f6aA528J72OfqiLD4r8DTNVVs=",
12
+ "bench.json": "YSXPXL9DWp91TDTmMZrcq81UYOnXi2L5BKFjPk+8MEU=",
13
+ "manifest.json": "6q/856PdHb80O+6DlOVhpkyox/A+DIvIcgNaiZBCN6E=",
14
+ "pool-global-reduction.wgsl.jinja": "kFAmHLC4tjXVW1gTRXNZHVeLo9o4BdkmCVhTIyB9doA=",
15
+ "pool-window-nd.wgsl.jinja": "WlKw9CY8+OR7l8jGO2lyJ72P0twJi3CIvechFVjC7jA=",
16
+ "pool-window-unroll.wgsl.jinja": "65NfjRJtzKQBVh23bYGyuwr7KUKKvdWsHnsBnGHSw7k=",
17
+ "test.json": "voBL9bHxxb4Sgoc7TC6W8uyXVsC/wEElhUg3BklfbL8="
18
+ }
19
+ },
20
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
21
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.AveragePool" }
22
+ }
build/webgpu/pool-global-reduction.wgsl.jinja ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {% if source.useSubgroups %}
5
+ enable subgroups;
6
+ {% endif %}
7
+ {{ env.wgsl.resourceDeclarations }}
8
+
9
+ // Workgroup-parallel global pooling: one workgroup reduces one contiguous
10
+ // (n, c) spatial plane (NC[D]HW layout makes the plane contiguous).
11
+ // Threads stride the plane (vec4 loads when the plane size is divisible by 4),
12
+ // then combine the per-thread partials. With subgroups: a subgroup collective
13
+ // + a shared-memory fold of the per-subgroup partials. Without subgroups, a
14
+ // full workgroup tree keeps workgroup-per-plane parallelism
15
+ // instead of falling to the one-thread-per-plane serial scalar kernel. The
16
+ // epilogue finalizes average / max / Lp. Sum reassociates;
17
+ // max is exact.
18
+ const WG: u32 = {{ source.workgroupSize }}u;
19
+ {% if source.useSubgroups %}
20
+ // WebGPU subgroups are at least 4 wide, so a workgroup holds at most WG / 4 subgroups.
21
+ const MAX_SUBGROUPS: u32 = WG / 4u;
22
+ var<workgroup> wg_partials: array<f32, MAX_SUBGROUPS>;
23
+ {% else %}
24
+ var<workgroup> wg_tree: array<f32, WG>;
25
+ {% endif %}
26
+
27
+
28
+ @compute @workgroup_size(WG)
29
+ fn main(@builtin(workgroup_id) wid: vec3<u32>,
30
+ @builtin(local_invocation_id) lid: vec3<u32>
31
+ {%- if source.useSubgroups %},
32
+ @builtin(subgroup_size) sg_size: u32
33
+ {%- endif %}) {
34
+ let plane = wid.x;
35
+ let tid = lid.x;
36
+ {% if source.useSubgroups %}
37
+
38
+ // Seed cross-subgroup slots with the reduction identity so unwritten slots
39
+ // (when the workgroup has fewer than MAX_SUBGROUPS subgroups) are harmless.
40
+ if (tid < MAX_SUBGROUPS) {
41
+ wg_partials[tid] = 0.0;
42
+ }
43
+ workgroupBarrier();
44
+
45
+ {% endif %}
46
+ var partial = 0.0;
47
+ {% if source.vec4 %}
48
+ let spatialVec = params.spatial / 4u;
49
+ let base = plane * spatialVec;
50
+ var accv = vec4<f32>(0.0);
51
+ for (var i = tid; i < spatialVec; i = i + WG) {
52
+ let v = vec4<f32>(x[base + i]);
53
+ accv = accv + v;
54
+ }
55
+ partial = accv.x + accv.y + accv.z + accv.w;
56
+ {% else %}
57
+ let base = plane * params.spatial;
58
+ for (var i = tid; i < params.spatial; i = i + WG) {
59
+ let v = f32(x[base + i]);
60
+ partial = partial + v;
61
+ }
62
+ {% endif %}
63
+
64
+ {% if source.useSubgroups %}
65
+ let sg_sum = subgroupAdd(partial);
66
+ if (subgroupElect()) {
67
+ wg_partials[tid / sg_size] = sg_sum;
68
+ }
69
+ workgroupBarrier();
70
+ if (tid == 0u) {
71
+ let nsg = min((WG + sg_size - 1u) / sg_size, MAX_SUBGROUPS);
72
+ var total = 0.0;
73
+ for (var i = 0u; i < nsg; i = i + 1u) {
74
+ total = total + wg_partials[i];
75
+ }
76
+ y[plane] = {{ scalar }}(total / f32(params.spatial));
77
+ }
78
+ {% else %}
79
+ // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
80
+ wg_tree[tid] = partial;
81
+ workgroupBarrier();
82
+ for (var step = WG >> 1u; step > 0u; step = step >> 1u) {
83
+ if (tid < step) {
84
+ wg_tree[tid] = wg_tree[tid] + wg_tree[tid + step];
85
+ }
86
+ workgroupBarrier();
87
+ }
88
+ if (tid == 0u) {
89
+ let total = wg_tree[0];
90
+ y[plane] = {{ scalar }}(total / f32(params.spatial));
91
+ }
92
+ {% endif %}
93
+ }
build/webgpu/pool-window-nd.wgsl.jinja ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // Direct {{ source.spatialRank }}D {{ source.op }}-pool traversal for a channels-first tensor.
7
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
8
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
9
+ let index = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
10
+ if (index >= params.count) { return; }
11
+
12
+ let ow = index % params.outW;
13
+ var t = index / params.outW;
14
+ {% if source.spatialRank >= 2 %}
15
+ let oh = t % params.outH;
16
+ t /= params.outH;
17
+ {% endif %}
18
+ {% if source.spatialRank == 3 %}
19
+ let od = t % params.outD;
20
+ t /= params.outD;
21
+ {% endif %}
22
+ let channel = t % params.channels;
23
+ let batch = t / params.channels;
24
+
25
+ {% if source.spatialRank == 3 %}
26
+ let startD = i32(od * params.strideD) - params.padFront;
27
+ {% endif %}
28
+ {% if source.spatialRank >= 2 %}
29
+ let startH = i32(oh * params.strideH) - params.padTop;
30
+ {% endif %}
31
+ let startW = i32(ow * params.strideW) - params.padLeft;
32
+ var acc = 0.0;
33
+ var seen = 0u;
34
+
35
+ {% if source.spatialRank == 3 %}
36
+ for (var kd = 0u; kd < params.kernelD; kd += 1u) {
37
+ let id = startD + i32(kd * params.dilationD);
38
+ if (id < 0 || id >= i32(params.inD)) { continue; }
39
+ {% endif %}
40
+ {% if source.spatialRank >= 2 %}
41
+ for (var kh = 0u; kh < params.kernelH; kh += 1u) {
42
+ let ih = startH + i32(kh * params.dilationH);
43
+ if (ih < 0 || ih >= i32(params.inH)) { continue; }
44
+ {% endif %}
45
+ for (var kw = 0u; kw < params.kernelW; kw += 1u) {
46
+ let iw = startW + i32(kw * params.dilationW);
47
+ if (iw < 0 || iw >= i32(params.inW)) { continue; }
48
+ {% if source.spatialRank == 1 %}
49
+ let xIndex = (batch * params.channels + channel) * params.inW + u32(iw);
50
+ {% elif source.spatialRank == 2 %}
51
+ let xIndex = ((batch * params.channels + channel) * params.inH + u32(ih)) * params.inW + u32(iw);
52
+ {% else %}
53
+ let xIndex = (((batch * params.channels + channel) * params.inD + u32(id)) * params.inH + u32(ih)) * params.inW + u32(iw);
54
+ {% endif %}
55
+ acc += f32(x[xIndex]);
56
+ seen += 1u;
57
+ }
58
+ {% if source.spatialRank >= 2 %}
59
+ }
60
+ {% endif %}
61
+ {% if source.spatialRank == 3 %}
62
+ }
63
+ {% endif %}
64
+
65
+ var denom = seen;
66
+ if (params.countIncludePad != 0u) {
67
+ denom = 0u;
68
+ {% if source.spatialRank == 3 %}
69
+ for (var kd = 0u; kd < params.kernelD; kd += 1u) {
70
+ let id = startD + i32(kd * params.dilationD);
71
+ if (id < -params.padFront || id >= i32(params.inD) + params.padBack) { continue; }
72
+ {% endif %}
73
+ {% if source.spatialRank >= 2 %}
74
+ for (var kh = 0u; kh < params.kernelH; kh += 1u) {
75
+ let ih = startH + i32(kh * params.dilationH);
76
+ if (ih < -params.padTop || ih >= i32(params.inH) + params.padBottom) { continue; }
77
+ {% endif %}
78
+ for (var kw = 0u; kw < params.kernelW; kw += 1u) {
79
+ let iw = startW + i32(kw * params.dilationW);
80
+ if (iw < -params.padLeft || iw >= i32(params.inW) + params.padRight) { continue; }
81
+ denom += 1u;
82
+ }
83
+ {% if source.spatialRank >= 2 %}
84
+ }
85
+ {% endif %}
86
+ {% if source.spatialRank == 3 %}
87
+ }
88
+ {% endif %}
89
+ }
90
+ y[index] = {{ scalar }}(select(0.0, acc / f32(denom), denom > 0u));
91
+ }
build/webgpu/pool-window-unroll.wgsl.jinja ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Compile-time-unrolled 2D NCHW pooling for small windows. Accumulation remains
2
+ // kh-major/kw-minor, matching the runtime-loop fallbacks.
3
+ {% if usesF16 %}
4
+ enable f16;
5
+ {% endif %}
6
+ {{ env.wgsl.resourceDeclarations }}
7
+
8
+ const OUT_W: u32 = {{ source.outW }}u;
9
+ const OUT_H: u32 = {{ source.outH }}u;
10
+ const IN_H: i32 = {{ source.inH }};
11
+ const IN_W: i32 = {{ source.inW }};
12
+ const IN_W_U: u32 = {{ source.inW }}u;
13
+ const IN_PLANE: u32 = {{ source.inH * source.inW }}u;
14
+ const STRIDE_H: u32 = {{ source.strideH }}u;
15
+ const STRIDE_W: u32 = {{ source.strideW }}u;
16
+ const PAD_TOP: i32 = {{ source.padTop }};
17
+ const PAD_LEFT: i32 = {{ source.padLeft }};
18
+ {% if source.op == "average" and source.countIncludePad %}
19
+ const PAD_BOTTOM: i32 = {{ source.padBottom }};
20
+ const PAD_RIGHT: i32 = {{ source.padRight }};
21
+ {% endif %}
22
+
23
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
24
+ fn main(
25
+ @builtin(global_invocation_id) gid: vec3<u32>,
26
+ @builtin(num_workgroups) nwg: vec3<u32>
27
+ ) {
28
+ // Fold very large flat outputs into the dispatch's second dimension.
29
+ let index = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
30
+ if (index >= params.count) {
31
+ return;
32
+ }
33
+
34
+ let ow = index % OUT_W;
35
+ var plane = index / OUT_W;
36
+ let oh = plane % OUT_H;
37
+ plane /= OUT_H;
38
+ let planeBase = plane * IN_PLANE;
39
+ let startH = i32(oh * STRIDE_H) - PAD_TOP;
40
+ let startW = i32(ow * STRIDE_W) - PAD_LEFT;
41
+ {% for kh in range(source.kernelH) %}
42
+ let ih_{{ kh }} = startH + {{ kh * source.dilationH }};
43
+ {% endfor %}
44
+ {% for kw in range(source.kernelW) %}
45
+ let iw_{{ kw }} = startW + {{ kw * source.dilationW }};
46
+ {% endfor %}
47
+
48
+ var result = 0.0;
49
+ var seen = 0u;
50
+ {% for kh in range(source.kernelH) %}
51
+ if (ih_{{ kh }} >= 0 && ih_{{ kh }} < IN_H) {
52
+ let rowBase{{ kh }} = planeBase + u32(ih_{{ kh }}) * IN_W_U;
53
+ {% for kw in range(source.kernelW) %}
54
+ if (iw_{{ kw }} >= 0 && iw_{{ kw }} < IN_W) {
55
+ let value_{{ kh }}_{{ kw }} = f32(x[rowBase{{ kh }} + u32(iw_{{ kw }})]);
56
+ result += value_{{ kh }}_{{ kw }};
57
+ seen += 1u;
58
+ }
59
+ {% endfor %}
60
+ }
61
+ {% endfor %}
62
+
63
+ {% if source.op == "average" and source.countIncludePad %}
64
+ var denominator = 0u;
65
+ {% for kh in range(source.kernelH) %}
66
+ if (ih_{{ kh }} >= -PAD_TOP && ih_{{ kh }} < IN_H + PAD_BOTTOM) {
67
+ {% for kw in range(source.kernelW) %}
68
+ if (iw_{{ kw }} >= -PAD_LEFT && iw_{{ kw }} < IN_W + PAD_RIGHT) {
69
+ denominator += 1u;
70
+ }
71
+ {% endfor %}
72
+ }
73
+ {% endfor %}
74
+ {% else %}
75
+ let denominator = seen;
76
+ {% endif %}
77
+
78
+ let output = select(0.0, result / f32(denominator), denominator > 0u);
79
+ y[index] = {{ scalar }}(output);
80
+ }
build/webgpu/test.json ADDED
The diff for this file is too large to render. See raw diff