Xenova HF Staff commited on
Commit
fcc4309
·
verified ·
1 Parent(s): f4cb595

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,80 @@
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.LpPool
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 18
12
+
13
+ ## Description
14
+
15
+ Applies Lp pooling over a spatial input tensor by computing the Lp norm within each kernel window and writing the result to the output. Output spatial dimensions are determined by the kernel size, strides, padding, and `ceil_mode`; `p` controls which norm is used (e.g. `p=1` for sum-of-absolutes, `p=2` for Euclidean).
16
+
17
+ See the [ONNX `LpPool` spec](https://onnx.ai/onnx/operators/onnx__LpPool.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 tensor of shape `(N x C x D1 x ... x Dn)`; for images the spatial axes are `H` and `W`. | 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 after Lp pooling; spatial dimensions vary with kernel, stride, and pad settings. | required |
30
+
31
+ ## Attributes
32
+
33
+ Attributes and default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `p` | `2` | The exponent of the Lp norm used for pooling; default `2` gives Euclidean (L2) pooling. |
38
+ | `auto_pad` | `"NOTSET"` | Deprecated auto-padding mode (`NOTSET`, `SAME_UPPER`, `SAME_LOWER`, or `VALID`). It cannot be used together with `pads`. |
39
+ | `ceil_mode` | `0` | When non-zero, uses `ceil` instead of `floor` to compute output spatial dimensions. |
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
+ - [`pool-global-reduction.wgsl.jinja`](build/webgpu/pool-global-reduction.wgsl.jinja)
58
+ - [`pool-ncl1d-x4.wgsl.jinja`](build/webgpu/pool-ncl1d-x4.wgsl.jinja)
59
+ - [`pool-window-nd.wgsl.jinja`](build/webgpu/pool-window-nd.wgsl.jinja)
60
+ - [`pool-window-unroll.wgsl.jinja`](build/webgpu/pool-window-unroll.wgsl.jinja)
61
+ - [`pool2d-nchw-k2s2-vec4.wgsl.jinja`](build/webgpu/pool2d-nchw-k2s2-vec4.wgsl.jinja)
62
+ - [`pool2d-nchw-separable.wgsl.jinja`](build/webgpu/pool2d-nchw-separable.wgsl.jinja)
63
+
64
+ ## Use with `@huggingface/kernels`
65
+
66
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
67
+ It then allocates the result tensors automatically.
68
+
69
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
70
+
71
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
72
+
73
+ ```js
74
+ import { getKernel } from "@huggingface/kernels";
75
+
76
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.LpPool", { version: 1 });
77
+ const { y } = await kernel({ x: { data: xData, shape: [1, 1, 4] } }, {
78
+ attrs: { kernel_shape: [3] },
79
+ });
80
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.LpPool",
3
+ "cases": [
4
+ {
5
+ "name": "nchw_8_32_64_64_k3",
6
+ "preset": "smoke",
7
+ "attrs": { "p": 2, "kernel_shape": [3, 3], "strides": [2, 2] },
8
+ "inputs": { "x": { "dtype": "float32", "shape": [8, 32, 64, 64] } },
9
+ "outputs": { "y": { "dtype": "float32", "shape": [8, 32, 31, 31] } }
10
+ },
11
+ {
12
+ "name": "nchw_8x64x112x112_k2s2_p2_unrolled",
13
+ "preset": "smoke",
14
+ "vars": { "batch": 8, "channels": 64, "inH": 112, "inW": 112 },
15
+ "attrs": { "p": 2, "kernel_shape": [2, 2], "strides": [2, 2] },
16
+ "inputs": { "x": { "dtype": "float32", "shape": [8, 64, 112, 112], "dist": "normal", "seed": 206 } },
17
+ "outputs": { "y": { "dtype": "float32", "shape": [8, 64, 56, 56], "dist": "empty" } },
18
+ "bench": {
19
+ "primary": true,
20
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4" }]
21
+ }
22
+ },
23
+ {
24
+ "name": "nchw_f16_8x64x112x112_k2s2_p2_unrolled",
25
+ "preset": "smoke",
26
+ "vars": { "batch": 8, "channels": 64, "inH": 112, "inW": 112 },
27
+ "attrs": { "p": 2, "kernel_shape": [2, 2], "strides": [2, 2] },
28
+ "inputs": { "x": { "dtype": "float16", "shape": [8, 64, 112, 112], "dist": "normal", "seed": 207 } },
29
+ "outputs": { "y": { "dtype": "float16", "shape": [8, 64, 56, 56], "dist": "empty" } },
30
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2" }] }
31
+ },
32
+ {
33
+ "name": "nchw_f16_8x64x112x110_k2s2_p2_unaligned_width",
34
+ "preset": "smoke",
35
+ "vars": { "batch": 8, "channels": 64, "inH": 112, "inW": 110 },
36
+ "attrs": { "p": 2, "kernel_shape": [2, 2], "strides": [2, 2] },
37
+ "inputs": { "x": { "dtype": "float16", "shape": [8, 64, 112, 110], "dist": "normal", "seed": 311 } },
38
+ "outputs": { "y": { "dtype": "float16", "shape": [8, 64, 56, 55], "dist": "empty" } },
39
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2" }] }
40
+ },
41
+ {
42
+ "name": "nchw_f16_8x64x113x112_k2s2_p2_unaligned_height",
43
+ "preset": "smoke",
44
+ "vars": { "batch": 8, "channels": 64, "inH": 113, "inW": 112 },
45
+ "attrs": { "p": 2, "kernel_shape": [2, 2], "strides": [2, 2] },
46
+ "inputs": { "x": { "dtype": "float16", "shape": [8, 64, 113, 112], "dist": "normal", "seed": 312 } },
47
+ "outputs": { "y": { "dtype": "float16", "shape": [8, 64, 56, 56], "dist": "empty" } },
48
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 2" }] }
49
+ },
50
+ {
51
+ "name": "ncl1d_f32_8x256x4000_k4s2_p2_seqpool",
52
+ "preset": "smoke",
53
+ "vars": { "batch": 8, "channels": 256, "inW": 4000 },
54
+ "attrs": { "p": 2, "kernel_shape": [4], "strides": [2] },
55
+ "inputs": { "x": { "dtype": "float32", "shape": [8, 256, 4000], "dist": "normal", "seed": 313 } },
56
+ "outputs": { "y": { "dtype": "float32", "shape": [8, 256, 1999], "dist": "empty" } },
57
+ "bench": {
58
+ "metrics": [{ "type": "bandwidth", "value": "(args.batch * args.channels * args.inW + numel(shapes.y)) * 4" }]
59
+ }
60
+ },
61
+ {
62
+ "name": "ncdhw3d_f32_2x16x16x16x16_k2s2_p2_volumetric",
63
+ "preset": "smoke",
64
+ "vars": { "batch": 2, "channels": 16, "inD": 16, "inH": 16, "inW": 16 },
65
+ "attrs": { "p": 2, "kernel_shape": [2, 2, 2], "strides": [2, 2, 2] },
66
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 16, 16, 16, 16], "dist": "normal", "seed": 314 } },
67
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 16, 8, 8, 8], "dist": "empty" } },
68
+ "bench": {
69
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inD * args.inH * args.inW * 4" }]
70
+ }
71
+ },
72
+ {
73
+ "name": "nchw2d_fallback_bigkernel_lowocc_p2_128x128window",
74
+ "preset": "stress",
75
+ "vars": { "batch": 1, "channels": 64, "inH": 128, "inW": 128 },
76
+ "attrs": { "p": 2, "kernel_shape": [128, 128] },
77
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 64, 128, 128], "dist": "normal", "seed": 741, "scale": 2 } },
78
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 1, 1], "dist": "empty" } },
79
+ "bench": {
80
+ "primary": true,
81
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4" }]
82
+ }
83
+ },
84
+ {
85
+ "name": "nchw2d_fallback_bigkernel_p3_pow_transcendental_96x96window",
86
+ "preset": "stress",
87
+ "vars": { "batch": 1, "channels": 32, "inH": 96, "inW": 96 },
88
+ "attrs": { "p": 3, "kernel_shape": [96, 96] },
89
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 32, 96, 96], "dist": "normal", "seed": 742, "scale": 2 } },
90
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 1, 1], "dist": "empty" } },
91
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inH * args.inW * 4" }] }
92
+ },
93
+ {
94
+ "name": "ncl1d_bigkernel_lowocc_p2_seqglobalpool",
95
+ "preset": "stress",
96
+ "vars": { "batch": 1, "channels": 64, "inW": 16384 },
97
+ "attrs": { "p": 2, "kernel_shape": [16384] },
98
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 64, 16384], "dist": "normal", "seed": 743, "scale": 2 } },
99
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 1], "dist": "empty" } },
100
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inW * 4" }] }
101
+ },
102
+ {
103
+ "name": "global-lppool-p2-rank5-serial-reduction-pathology",
104
+ "preset": "stress",
105
+ "provenance": {
106
+ "source": "authored for variant coverage",
107
+ "notes": "Realistic rank-5 global L2 pool that verifies global_window_parallel coverage and its feature-tier fallbacks."
108
+ },
109
+ "vars": { "batch": 1, "channels": 32, "inD": 32, "inH": 64, "inW": 64 },
110
+ "attrs": { "p": 2, "kernel_shape": [32, 64, 64] },
111
+ "inputs": {
112
+ "x": { "dtype": "float32", "shape": [1, 32, 32, 64, 64], "dist": "normal", "seed": 5103, "scale": 0.2 }
113
+ },
114
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 32, 1, 1, 1], "dist": "empty" } },
115
+ "bench": {
116
+ "metrics": [{ "type": "bandwidth", "value": "args.batch * args.channels * args.inD * args.inH * args.inW * 4" }]
117
+ }
118
+ },
119
+ {
120
+ "name": "nchw_f32_1x64x224x224_k7s2p3_generic_bigwindow",
121
+ "preset": "stress",
122
+ "attrs": { "p": 2, "kernel_shape": [7, 7], "strides": [2, 2], "pads": [3, 3, 3, 3] },
123
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 64, 224, 224], "dist": "normal", "seed": 401, "scale": 2 } },
124
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 112, 112], "dist": "empty" } },
125
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "numel(shapes.x) * 4" }] }
126
+ },
127
+ {
128
+ "name": "nchw_f32_1x64x224x224_k6s2pad2_separable_breakeven",
129
+ "preset": "stress",
130
+ "attrs": { "p": 2, "kernel_shape": [6, 6], "strides": [2, 2], "pads": [2, 2, 2, 2] },
131
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 64, 224, 224], "dist": "normal", "seed": 409, "scale": 2 } },
132
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 112, 112], "dist": "empty" } },
133
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "numel(shapes.x) * 4" }] }
134
+ }
135
+ ],
136
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] }
137
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,461 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "LpPool",
4
+ "sinceVersion": 18,
5
+ "description": "Applies Lp pooling over a spatial input tensor by computing the Lp norm within each kernel window and writing the result to the output. Output spatial dimensions are determined by the kernel size, strides, padding, and `ceil_mode`; `p` controls which norm is used (e.g. `p=1` for sum-of-absolutes, `p=2` for Euclidean).",
6
+ "inputs": [
7
+ {
8
+ "role": "X",
9
+ "dtype": "T",
10
+ "description": "Input tensor of shape `(N x C x D1 x ... x Dn)`; for images the spatial axes are `H` and `W`."
11
+ }
12
+ ],
13
+ "outputs": [
14
+ {
15
+ "role": "Y",
16
+ "dtype": "T",
17
+ "description": "Output tensor after Lp pooling; spatial dimensions vary with kernel, stride, and pad settings.",
18
+ "rank": "ranks.X",
19
+ "shape": "poolOutputShape"
20
+ }
21
+ ],
22
+ "attributes": { "p": 2, "auto_pad": "NOTSET", "ceil_mode": 0 },
23
+ "attributeDescriptions": {
24
+ "p": "The exponent of the Lp norm used for pooling; default `2` gives Euclidean (L2) pooling.",
25
+ "auto_pad": "Deprecated auto-padding mode (`NOTSET`, `SAME_UPPER`, `SAME_LOWER`, or `VALID`). It cannot be used together with `pads`.",
26
+ "kernel_shape": "Required kernel shape, with one positive value per spatial axis.",
27
+ "strides": "Stride along each spatial axis. When omitted, every stride is 1.",
28
+ "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.",
29
+ "dilations": "Dilation along each spatial axis. When omitted, every dilation is 1.",
30
+ "ceil_mode": "When non-zero, uses `ceil` instead of `floor` to compute output spatial dimensions."
31
+ },
32
+ "attributeConstraints": {
33
+ "auto_pad": { "values": ["NOTSET", "SAME_UPPER", "SAME_LOWER", "VALID"] },
34
+ "ceil_mode": { "values": [0, 1] },
35
+ "kernel_shape": { "required": true }
36
+ },
37
+ "typeConstraints": { "T": ["float32", "float16"] },
38
+ "args": {
39
+ "x": { "kind": "tensor", "semantic": "X", "role": "x" },
40
+ "y": { "kind": "tensor", "semantic": "Y", "role": "y" }
41
+ },
42
+ "derive": {
43
+ "spatialRank": "ranks.X - 2",
44
+ "kernelShapeLengthOk": "not has(attrs, \"kernel_shape\") or (attrs.kernel_shape | length) == spatialRank",
45
+ "stridesLengthOk": "not has(attrs, \"strides\") or (attrs.strides | length) == spatialRank",
46
+ "dilationsLengthOk": "not has(attrs, \"dilations\") or (attrs.dilations | length) == spatialRank",
47
+ "padsLengthOk": "not has(attrs, \"pads\") or (attrs.pads | length) == 2 * spatialRank",
48
+ "kernelD": "attrs.kernel_shape[0] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank == 3 else 1",
49
+ "kernelH": "attrs.kernel_shape[spatialRank - 2] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 2 else 1",
50
+ "kernelW": "attrs.kernel_shape[spatialRank - 1] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 1 else 1",
51
+ "strideD": "attrs.strides[0] if stridesLengthOk and has(attrs, \"strides\") and spatialRank == 3 else 1",
52
+ "strideH": "attrs.strides[spatialRank - 2] if stridesLengthOk and has(attrs, \"strides\") and spatialRank >= 2 else 1",
53
+ "strideW": "attrs.strides[spatialRank - 1] if stridesLengthOk and has(attrs, \"strides\") and spatialRank >= 1 else 1",
54
+ "dilationD": "attrs.dilations[0] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank == 3 else 1",
55
+ "dilationH": "attrs.dilations[spatialRank - 2] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank >= 2 else 1",
56
+ "dilationW": "attrs.dilations[spatialRank - 1] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank >= 1 else 1",
57
+ "padFront": "attrs.pads[0] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
58
+ "padTop": "attrs.pads[spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
59
+ "padLeft": "attrs.pads[spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
60
+ "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
61
+ "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
62
+ "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
63
+ "poolBaseOk": "f16Ok(dtypes.T) and attrs.p > 0 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)",
64
+ "explicitOutW": "min(ceil((dim(shapes.X, ranks.X - 1) + padLeft + padRight - ((kernelW - 1) * dilationW + 1)) / strideW) + 1, ceil((dim(shapes.X, ranks.X - 1) + padLeft) / strideW)) if attrs.ceil_mode != 0 else floor((dim(shapes.X, ranks.X - 1) + padLeft + padRight - ((kernelW - 1) * dilationW + 1)) / strideW) + 1",
65
+ "explicitOutH": "min(ceil((dim(shapes.X, ranks.X - 2) + padTop + padBottom - ((kernelH - 1) * dilationH + 1)) / strideH) + 1, ceil((dim(shapes.X, ranks.X - 2) + padTop) / strideH)) if attrs.ceil_mode != 0 else floor((dim(shapes.X, ranks.X - 2) + padTop + padBottom - ((kernelH - 1) * dilationH + 1)) / strideH) + 1",
66
+ "explicitOutD": "(min(ceil((dim(shapes.X, 2) + padFront + padBack - ((kernelD - 1) * dilationD + 1)) / strideD) + 1, ceil((dim(shapes.X, 2) + padFront) / strideD)) if attrs.ceil_mode != 0 else floor((dim(shapes.X, 2) + padFront + padBack - ((kernelD - 1) * dilationD + 1)) / strideD) + 1) if ranks.X == 5 else 0",
67
+ "inferredOutW": "ceil(dim(shapes.X, ranks.X - 1) / strideW) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutW",
68
+ "inferredOutH": "ceil(dim(shapes.X, ranks.X - 2) / strideH) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutH",
69
+ "inferredOutD": "ceil(dim(shapes.X, 2) / strideD) if attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\" else explicitOutD",
70
+ "poolOutputShape": "[dim(shapes.X, 0), dim(shapes.X, 1), inferredOutW] if ranks.X == 3 else ([dim(shapes.X, 0), dim(shapes.X, 1), inferredOutH, inferredOutW] if ranks.X == 4 else [dim(shapes.X, 0), dim(shapes.X, 1), inferredOutD, inferredOutH, inferredOutW])",
71
+ "autoPadTotalH": "max(0, (dim(shapes.Y, 2) - 1) * strideH + ((kernelH - 1) * dilationH + 1) - dim(shapes.X, 2)) if ranks.X == 4 else 0",
72
+ "autoPadTotalW": "max(0, (dim(shapes.Y, 3) - 1) * strideW + ((kernelW - 1) * dilationW + 1) - dim(shapes.X, 3)) if ranks.X == 4 else 0",
73
+ "effectivePadH": "(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",
74
+ "effectivePadW": "(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",
75
+ "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)))",
76
+ "separableTmpElements": "dim(shapes.X, 0) * dim(shapes.X, 1) * dim(shapes.X, 2) * dim(shapes.Y, 3)",
77
+ "separableFits": "separableTmpElements * 4 <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize) and ceilDiv(separableTmpElements, tunables.WORKGROUP_SIZE) <= device.limits.maxComputeWorkgroupsPerDimension * device.limits.maxComputeWorkgroupsPerDimension"
78
+ },
79
+ "constants": { "usesF16": "dtypes.T == \"f16\"", "scalar": "dtypes.T" },
80
+ "bindingSets": {
81
+ "globalReduction": [
82
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$xElem" },
83
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
84
+ {
85
+ "name": "params",
86
+ "semantic": "kernel.params",
87
+ "buffer": { "type": "uniform" },
88
+ "struct": { "name": "Params", "fields": [{ "name": "spatial", "type": "u32", "value": "inner(shapes.X, 1)" }] }
89
+ }
90
+ ],
91
+ "rank2Vec4": [
92
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
93
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
94
+ {
95
+ "name": "params",
96
+ "semantic": "kernel.params",
97
+ "buffer": { "type": "uniform" },
98
+ "struct": {
99
+ "name": "Params",
100
+ "fields": [{ "name": "tileCount", "type": "u32", "value": "numel(shapes.Y) / 2" }]
101
+ }
102
+ }
103
+ ],
104
+ "flatDirect": [
105
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
106
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
107
+ {
108
+ "name": "params",
109
+ "semantic": "kernel.params",
110
+ "buffer": { "type": "uniform" },
111
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.Y)" }] }
112
+ }
113
+ ],
114
+ "rank1X4": [
115
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
116
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
117
+ {
118
+ "name": "params",
119
+ "semantic": "kernel.params",
120
+ "buffer": { "type": "uniform" },
121
+ "struct": {
122
+ "name": "Params",
123
+ "fields": [
124
+ {
125
+ "name": "blockCount",
126
+ "type": "u32",
127
+ "value": "dim(shapes.X, 0) * dim(shapes.X, 1) * ceilDiv(dim(shapes.Y, 2), 4)"
128
+ }
129
+ ]
130
+ }
131
+ }
132
+ ],
133
+ "rank1Direct": [
134
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
135
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
136
+ {
137
+ "name": "params",
138
+ "semantic": "kernel.params",
139
+ "buffer": { "type": "uniform" },
140
+ "struct": {
141
+ "name": "Params",
142
+ "fields": [
143
+ { "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
144
+ { "name": "inW", "type": "u32", "value": "dim(shapes.X, 2)" },
145
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 2)" },
146
+ { "name": "kernelW", "type": "u32", "value": "kernelW" },
147
+ { "name": "strideW", "type": "u32", "value": "strideW" },
148
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
149
+ { "name": "padW", "type": "i32", "value": "padLeft" },
150
+ { "name": "p", "type": "f32", "value": "attrs.p" },
151
+ { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
152
+ ]
153
+ }
154
+ }
155
+ ],
156
+ "rank3Direct": [
157
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
158
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
159
+ {
160
+ "name": "params",
161
+ "semantic": "kernel.params",
162
+ "buffer": { "type": "uniform" },
163
+ "struct": {
164
+ "name": "Params",
165
+ "fields": [
166
+ { "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
167
+ { "name": "inD", "type": "u32", "value": "dim(shapes.X, 2)" },
168
+ { "name": "inH", "type": "u32", "value": "dim(shapes.X, 3)" },
169
+ { "name": "inW", "type": "u32", "value": "dim(shapes.X, 4)" },
170
+ { "name": "outD", "type": "u32", "value": "dim(shapes.Y, 2)" },
171
+ { "name": "outH", "type": "u32", "value": "dim(shapes.Y, 3)" },
172
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 4)" },
173
+ { "name": "kernelD", "type": "u32", "value": "kernelD" },
174
+ { "name": "kernelH", "type": "u32", "value": "kernelH" },
175
+ { "name": "kernelW", "type": "u32", "value": "kernelW" },
176
+ { "name": "strideD", "type": "u32", "value": "strideD" },
177
+ { "name": "strideH", "type": "u32", "value": "strideH" },
178
+ { "name": "strideW", "type": "u32", "value": "strideW" },
179
+ { "name": "dilationD", "type": "u32", "value": "dilationD" },
180
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
181
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
182
+ { "name": "padD", "type": "i32", "value": "padFront" },
183
+ { "name": "padH", "type": "i32", "value": "padTop" },
184
+ { "name": "padW", "type": "i32", "value": "padLeft" },
185
+ { "name": "p", "type": "f32", "value": "attrs.p" },
186
+ { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
187
+ ]
188
+ }
189
+ }
190
+ ],
191
+ "rank2Direct": [
192
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
193
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
194
+ {
195
+ "name": "params",
196
+ "semantic": "kernel.params",
197
+ "buffer": { "type": "uniform" },
198
+ "struct": {
199
+ "name": "Params",
200
+ "fields": [
201
+ { "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
202
+ { "name": "inH", "type": "u32", "value": "dim(shapes.X, 2)" },
203
+ { "name": "inW", "type": "u32", "value": "dim(shapes.X, 3)" },
204
+ { "name": "outH", "type": "u32", "value": "dim(shapes.Y, 2)" },
205
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 3)" },
206
+ { "name": "kernelH", "type": "u32", "value": "kernelH" },
207
+ { "name": "kernelW", "type": "u32", "value": "kernelW" },
208
+ { "name": "strideH", "type": "u32", "value": "strideH" },
209
+ { "name": "strideW", "type": "u32", "value": "strideW" },
210
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
211
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
212
+ { "name": "padH", "type": "i32", "value": "effectivePadH" },
213
+ { "name": "padW", "type": "i32", "value": "effectivePadW" },
214
+ { "name": "p", "type": "f32", "value": "attrs.p" },
215
+ { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
216
+ ]
217
+ }
218
+ }
219
+ ],
220
+ "rank2SeparableW": [
221
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
222
+ { "name": "tmp", "semantic": "tmp", "buffer": { "type": "storage" }, "elementType": "f32" },
223
+ {
224
+ "name": "params",
225
+ "semantic": "kernel.params",
226
+ "buffer": { "type": "uniform" },
227
+ "struct": {
228
+ "name": "Params",
229
+ "fields": [
230
+ { "name": "inH", "type": "u32", "value": "dim(shapes.X, 2)" },
231
+ { "name": "inW", "type": "u32", "value": "dim(shapes.X, 3)" },
232
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 3)" },
233
+ { "name": "kernelW", "type": "u32", "value": "kernelW" },
234
+ { "name": "strideW", "type": "u32", "value": "strideW" },
235
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
236
+ { "name": "padW", "type": "i32", "value": "effectivePadW" },
237
+ { "name": "p", "type": "f32", "value": "attrs.p" },
238
+ {
239
+ "name": "tmpCount",
240
+ "type": "u32",
241
+ "value": "dim(shapes.X, 0) * dim(shapes.X, 1) * dim(shapes.X, 2) * dim(shapes.Y, 3)"
242
+ }
243
+ ]
244
+ }
245
+ }
246
+ ],
247
+ "rank2SeparableH": [
248
+ { "name": "tmp", "semantic": "tmp", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
249
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
250
+ {
251
+ "name": "params",
252
+ "semantic": "kernel.params",
253
+ "buffer": { "type": "uniform" },
254
+ "struct": {
255
+ "name": "Params",
256
+ "fields": [
257
+ { "name": "inH", "type": "u32", "value": "dim(shapes.X, 2)" },
258
+ { "name": "outH", "type": "u32", "value": "dim(shapes.Y, 2)" },
259
+ { "name": "outW", "type": "u32", "value": "dim(shapes.Y, 3)" },
260
+ { "name": "kernelH", "type": "u32", "value": "kernelH" },
261
+ { "name": "strideH", "type": "u32", "value": "strideH" },
262
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
263
+ { "name": "padH", "type": "i32", "value": "effectivePadH" },
264
+ { "name": "p", "type": "f32", "value": "attrs.p" },
265
+ { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
266
+ ]
267
+ }
268
+ }
269
+ ]
270
+ },
271
+ "tunables": { "WORKGROUP_SIZE": 256, "SEPARABLE_MIN_WINDOW": 42 },
272
+ "variants": [
273
+ {
274
+ "id": "global_window_parallel",
275
+ "priority": 50,
276
+ "when": ["globalWindowParallelCovered"],
277
+ "constants": {
278
+ "xElem": "(\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\") if inner(shapes.X, 1) % 4 == 0 else dtypes.T"
279
+ },
280
+ "passes": [
281
+ {
282
+ "id": "main",
283
+ "name": "LpPool.GlobalWindowParallel",
284
+ "source": {
285
+ "shader": "pool-global-reduction.wgsl.jinja",
286
+ "inputs": {
287
+ "op": "\"lp\"",
288
+ "vec4": "inner(shapes.X, 1) % 4 == 0",
289
+ "p": "attrs.p",
290
+ "workgroupSize": "tunables.WORKGROUP_SIZE",
291
+ "useSubgroups": "device.features.has(\"subgroups\")"
292
+ }
293
+ },
294
+ "bindings": "globalReduction",
295
+ "dispatch": { "x": "numel(shapes.Y)" }
296
+ }
297
+ ]
298
+ },
299
+ {
300
+ "id": "nchw2d_k2s2_vec4",
301
+ "priority": 30,
302
+ "when": ["(attrs.auto_pad != \"SAME_UPPER\" and attrs.auto_pad != \"SAME_LOWER\")", "poolBaseOk", "ranks.X == 4", "kernelH == 2", "kernelW == 2", "strideH == 2", "strideW == 2", "dilationH == 1", "dilationW == 1", "padTop == 0", "padLeft == 0", "padBottom == 0", "padRight == 0", "attrs.ceil_mode == 0", "dim(shapes.X, 3) % 4 == 0", "dim(shapes.Y, 2) == floor(dim(shapes.X, 2) / 2)", "dim(shapes.Y, 3) == floor(dim(shapes.X, 3) / 2)", "dim(shapes.Y, 2) > 0", "dim(shapes.Y, 3) > 0"],
303
+ "constants": { "xVec4": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\"" },
304
+ "passes": [
305
+ {
306
+ "id": "main",
307
+ "name": "LpPool.Nchw2dK2s2Vec4",
308
+ "source": {
309
+ "shader": "pool2d-nchw-k2s2-vec4.wgsl.jinja",
310
+ "inputs": {
311
+ "op": "\"lp\"",
312
+ "outH": "dim(shapes.Y, 2)",
313
+ "outW": "dim(shapes.Y, 3)",
314
+ "outWHalf": "floor(dim(shapes.Y, 3) / 2)",
315
+ "inWVec4": "floor(dim(shapes.X, 3) / 4)",
316
+ "inPlaneVec4": "floor(dim(shapes.X, 2) * dim(shapes.X, 3) / 4)",
317
+ "p": "attrs.p"
318
+ }
319
+ },
320
+ "bindings": "rank2Vec4",
321
+ "dispatch": { "threads": "numel(shapes.Y) / 2", "workgroupSize": "tunables.WORKGROUP_SIZE" }
322
+ }
323
+ ]
324
+ },
325
+ {
326
+ "id": "nchw2d_unrolled",
327
+ "priority": 25,
328
+ "when": ["(attrs.auto_pad != \"SAME_UPPER\" and attrs.auto_pad != \"SAME_LOWER\")", "poolBaseOk", "ranks.X == 4", "kernelH > 0", "kernelW > 0", "strideH > 0", "strideW > 0", "kernelH <= 3", "kernelW <= 3", "dilationH >= 1", "dilationW >= 1", "dim(shapes.Y, 2) > 0", "dim(shapes.Y, 3) > 0"],
329
+ "passes": [
330
+ {
331
+ "id": "main",
332
+ "name": "LpPool.Nchw2dUnrolled",
333
+ "source": {
334
+ "shader": "pool-window-unroll.wgsl.jinja",
335
+ "inputs": {
336
+ "op": "\"lp\"",
337
+ "kernelH": "kernelH",
338
+ "kernelW": "kernelW",
339
+ "strideH": "strideH",
340
+ "strideW": "strideW",
341
+ "dilationH": "dilationH",
342
+ "dilationW": "dilationW",
343
+ "padTop": "padTop",
344
+ "padLeft": "padLeft",
345
+ "inH": "dim(shapes.X, 2)",
346
+ "inW": "dim(shapes.X, 3)",
347
+ "outH": "dim(shapes.Y, 2)",
348
+ "outW": "dim(shapes.Y, 3)",
349
+ "p": "attrs.p"
350
+ }
351
+ },
352
+ "bindings": "flatDirect",
353
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
354
+ }
355
+ ]
356
+ },
357
+ {
358
+ "id": "ncl1d_k4s2_p2_x4",
359
+ "priority": 30,
360
+ "when": ["poolBaseOk", "ranks.X == 3", "attrs.auto_pad == \"NOTSET\"", "attrs.p == 2", "kernelW == 4", "strideW == 2", "dilationW == 1", "padLeft == 0", "padRight == 0", "attrs.ceil_mode == 0", "dim(shapes.X, 2) % 4 == 0", "dim(shapes.Y, 2) == floor((dim(shapes.X, 2) - 4) / 2) + 1"],
361
+ "constants": { "xVec4": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\"" },
362
+ "passes": [
363
+ {
364
+ "id": "main",
365
+ "name": "LpPool.Ncl1dK4s2P2X4",
366
+ "source": {
367
+ "shader": "pool-ncl1d-x4.wgsl.jinja",
368
+ "inputs": { "op": "\"lp\"", "inW": "dim(shapes.X, 2)", "outW": "dim(shapes.Y, 2)" }
369
+ },
370
+ "bindings": "rank1X4",
371
+ "dispatch": {
372
+ "threads": "dim(shapes.X, 0) * dim(shapes.X, 1) * ceilDiv(dim(shapes.Y, 2), 4)",
373
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
374
+ }
375
+ }
376
+ ]
377
+ },
378
+ {
379
+ "id": "ncl1d",
380
+ "priority": 10,
381
+ "when": ["poolBaseOk", "ranks.X == 3", "kernelW > 0", "strideW > 0"],
382
+ "constants": { "pIs1": "attrs.p == 1", "pIs2": "attrs.p == 2" },
383
+ "passes": [
384
+ {
385
+ "id": "main",
386
+ "name": "LpPool.NCL1d",
387
+ "source": { "shader": "pool-window-nd.wgsl.jinja", "inputs": { "op": "\"lp\"", "spatialRank": 1 } },
388
+ "bindings": "rank1Direct",
389
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
390
+ }
391
+ ]
392
+ },
393
+ {
394
+ "id": "nchw2d_separable",
395
+ "priority": 22,
396
+ "when": ["(attrs.auto_pad != \"SAME_UPPER\" and attrs.auto_pad != \"SAME_LOWER\")", "poolBaseOk", "ranks.X == 4", "kernelH > 0", "kernelW > 0", "strideH > 0", "strideW > 0", "kernelH > 1", "kernelW > 1", "kernelH * kernelW >= tunables.SEPARABLE_MIN_WINDOW", "separableFits"],
397
+ "constants": { "pIs1": "attrs.p == 1", "pIs2": "attrs.p == 2" },
398
+ "intermediates": [{ "id": "tmp", "dtype": "float32", "shape": "[separableTmpElements]" }],
399
+ "passes": [
400
+ {
401
+ "id": "reduce_w",
402
+ "name": "LpPool.SeparableW",
403
+ "source": { "shader": "pool2d-nchw-separable.wgsl.jinja", "inputs": { "op": "\"lp\"", "axis": "\"w\"" } },
404
+ "bindings": "rank2SeparableW",
405
+ "dispatch": { "threads": "separableTmpElements", "workgroupSize": "tunables.WORKGROUP_SIZE" }
406
+ },
407
+ {
408
+ "id": "reduce_h",
409
+ "name": "LpPool.SeparableH",
410
+ "source": { "shader": "pool2d-nchw-separable.wgsl.jinja", "inputs": { "op": "\"lp\"", "axis": "\"h\"" } },
411
+ "bindings": "rank2SeparableH",
412
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
413
+ }
414
+ ]
415
+ },
416
+ {
417
+ "id": "nchw2d",
418
+ "when": ["(attrs.auto_pad != \"SAME_UPPER\" and attrs.auto_pad != \"SAME_LOWER\")", "poolBaseOk", "ranks.X == 4", "kernelH > 0", "kernelW > 0", "strideH > 0", "strideW > 0"],
419
+ "constants": { "pIs1": "attrs.p == 1", "pIs2": "attrs.p == 2" },
420
+ "passes": [
421
+ {
422
+ "id": "main",
423
+ "name": "LpPool.NCHW2d",
424
+ "source": { "shader": "pool-window-nd.wgsl.jinja", "inputs": { "op": "\"lp\"", "spatialRank": 2 } },
425
+ "bindings": "rank2Direct",
426
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
427
+ }
428
+ ]
429
+ },
430
+ {
431
+ "id": "nchw2d_autopad",
432
+ "priority": 15,
433
+ "when": ["(attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\")", "poolBaseOk", "ranks.X == 4", "kernelH > 0", "kernelW > 0", "strideH > 0", "strideW > 0", "dim(shapes.Y, 2) == ceil(dim(shapes.X, 2) / strideH)", "dim(shapes.Y, 3) == ceil(dim(shapes.X, 3) / strideW)"],
434
+ "constants": { "pIs1": "attrs.p == 1", "pIs2": "attrs.p == 2" },
435
+ "passes": [
436
+ {
437
+ "id": "main",
438
+ "name": "LpPool.NCHW2dAutoPad",
439
+ "source": { "shader": "pool-window-nd.wgsl.jinja", "inputs": { "op": "\"lp\"", "spatialRank": 2 } },
440
+ "bindings": "rank2Direct",
441
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
442
+ }
443
+ ]
444
+ },
445
+ {
446
+ "id": "ncdhw3d",
447
+ "priority": 10,
448
+ "supersededBy": ["global_window_parallel"],
449
+ "when": ["poolBaseOk", "ranks.X == 5", "kernelD > 0", "kernelH > 0", "kernelW > 0", "strideD > 0", "strideH > 0", "strideW > 0"],
450
+ "passes": [
451
+ {
452
+ "id": "main",
453
+ "name": "LpPool.NCDHW3d",
454
+ "source": { "shader": "pool-window-nd.wgsl.jinja", "inputs": { "op": "\"lp\"", "spatialRank": 3 } },
455
+ "bindings": "rank3Direct",
456
+ "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
457
+ }
458
+ ]
459
+ }
460
+ ]
461
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.LpPool",
3
+ "id": "_ai_onnx_lppool_webgpu_cd77da5",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "w6aN2KgWABkA/ms17d5eRGnNY4W16SbuGU0Bv4Ua+PM=",
11
+ "manifest.json": "u27G/WowR9lMUG+lwNF4ur0POWFvxA1Pj1D/why0G3w=",
12
+ "pool-global-reduction.wgsl.jinja": "6v3XBuPgokHWHa3DRfhk8PyQ2km8soKnuJ9NNZUgSTo=",
13
+ "pool-ncl1d-x4.wgsl.jinja": "uZW9X2c3ilCWzT0YCg9/fs/L3wNTS5j4aRO31UQpFbA=",
14
+ "pool-window-nd.wgsl.jinja": "h6hIDb6qxMwApe1Wcw4x9cEnjWY1WpYWk3vR8ecYF/w=",
15
+ "pool-window-unroll.wgsl.jinja": "HVLK0vaS22ommayIv3VISzdyU3X1lgtFYG9N5WCOUUM=",
16
+ "pool2d-nchw-k2s2-vec4.wgsl.jinja": "Oq+ar9au4O/oG5ZU8MtSJYLKGXB/GupLNnYHEgmuaUI=",
17
+ "pool2d-nchw-separable.wgsl.jinja": "0Av3hMIG4Rnrut2XitTZrqU8l7ih1QV0/w01vyAsbe4=",
18
+ "test.json": "5NKe4iEQZXXCRAfrhel4tIqfaUs+/u9uZCKdPbPU4oI="
19
+ }
20
+ },
21
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
22
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.LpPool" }
23
+ }
build/webgpu/pool-global-reduction.wgsl.jinja ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ {% if source.op == "lp" and source.p != 1 and source.p != 2 %}
28
+ const P: f32 = f32({{ source.p }});
29
+ {% endif %}
30
+
31
+ @compute @workgroup_size(WG)
32
+ fn main(@builtin(workgroup_id) wid: vec3<u32>,
33
+ @builtin(local_invocation_id) lid: vec3<u32>
34
+ {%- if source.useSubgroups %},
35
+ @builtin(subgroup_size) sg_size: u32
36
+ {%- endif %}) {
37
+ let plane = wid.x;
38
+ let tid = lid.x;
39
+ {% if source.useSubgroups %}
40
+
41
+ // Seed cross-subgroup slots with the reduction identity so unwritten slots
42
+ // (when the workgroup has fewer than MAX_SUBGROUPS subgroups) are harmless.
43
+ if (tid < MAX_SUBGROUPS) {
44
+ wg_partials[tid] = 0.0;
45
+ }
46
+ workgroupBarrier();
47
+
48
+ {% endif %}
49
+ var partial = 0.0;
50
+ {% if source.vec4 %}
51
+ let spatialVec = params.spatial / 4u;
52
+ let base = plane * spatialVec;
53
+ var accv = vec4<f32>(0.0);
54
+ for (var i = tid; i < spatialVec; i = i + WG) {
55
+ let v = vec4<f32>(x[base + i]);
56
+ {% if source.p == 2 %}
57
+ accv = accv + v * v;
58
+ {% elif source.p == 1 %}
59
+ accv = accv + abs(v);
60
+ {% else %}
61
+ accv = accv + pow(abs(v), vec4<f32>(P));
62
+ {% endif %}
63
+ }
64
+ partial = accv.x + accv.y + accv.z + accv.w;
65
+ {% else %}
66
+ let base = plane * params.spatial;
67
+ for (var i = tid; i < params.spatial; i = i + WG) {
68
+ let v = f32(x[base + i]);
69
+ {% if source.p == 2 %}
70
+ partial = partial + v * v;
71
+ {% elif source.p == 1 %}
72
+ partial = partial + abs(v);
73
+ {% else %}
74
+ partial = partial + pow(abs(v), P);
75
+ {% endif %}
76
+ }
77
+ {% endif %}
78
+
79
+ {% if source.useSubgroups %}
80
+ let sg_sum = subgroupAdd(partial);
81
+ if (subgroupElect()) {
82
+ wg_partials[tid / sg_size] = sg_sum;
83
+ }
84
+ workgroupBarrier();
85
+ if (tid == 0u) {
86
+ let nsg = min((WG + sg_size - 1u) / sg_size, MAX_SUBGROUPS);
87
+ var total = 0.0;
88
+ for (var i = 0u; i < nsg; i = i + 1u) {
89
+ total = total + wg_partials[i];
90
+ }
91
+ {% if source.p == 2 %}
92
+ y[plane] = {{ scalar }}(sqrt(total));
93
+ {% elif source.p == 1 %}
94
+ y[plane] = {{ scalar }}(total);
95
+ {% else %}
96
+ y[plane] = {{ scalar }}(pow(total, 1.0 / P));
97
+ {% endif %}
98
+ }
99
+ {% else %}
100
+ // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
101
+ wg_tree[tid] = partial;
102
+ workgroupBarrier();
103
+ for (var step = WG >> 1u; step > 0u; step = step >> 1u) {
104
+ if (tid < step) {
105
+ wg_tree[tid] = wg_tree[tid] + wg_tree[tid + step];
106
+ }
107
+ workgroupBarrier();
108
+ }
109
+ if (tid == 0u) {
110
+ let total = wg_tree[0];
111
+ {% if source.p == 2 %}
112
+ y[plane] = {{ scalar }}(sqrt(total));
113
+ {% elif source.p == 1 %}
114
+ y[plane] = {{ scalar }}(total);
115
+ {% else %}
116
+ y[plane] = {{ scalar }}(pow(total, 1.0 / P));
117
+ {% endif %}
118
+ }
119
+ {% endif %}
120
+ }
build/webgpu/pool-ncl1d-x4.wgsl.jinja ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // Four adjacent NCL outputs per invocation for non-overlapping max k2/s2 and
7
+ // overlapping L2 k4/s2 windows. Both layouts consume aligned vec4 input blocks.
8
+ // The L2 route reuses shared middle samples instead of loading every window
9
+ // independently.
10
+ const IN_W: u32 = {{ source.inW }}u;
11
+ const OUT_W: u32 = {{ source.outW }}u;
12
+ const BLOCKS_PER_PLANE: u32 = (OUT_W + 3u) / 4u;
13
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
14
+ fn main(
15
+ @builtin(global_invocation_id) gid: vec3<u32>,
16
+ @builtin(num_workgroups) nwg: vec3<u32>,
17
+ ) {
18
+ let block = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
19
+ if (block >= params.blockCount) { return; }
20
+
21
+ let plane = block / BLOCKS_PER_PLANE;
22
+ let outputBlock = block - plane * BLOCKS_PER_PLANE;
23
+ let ow = outputBlock * 4u;
24
+ let inputVec = plane * (IN_W / 4u) + ow / 2u;
25
+ let outputBase = plane * OUT_W + ow;
26
+ let a = vec4<f32>(x[inputVec]);
27
+ let b = vec4<f32>(x[inputVec + 1u]);
28
+
29
+ // k4/s2 L2: neighboring windows share their middle pair. The final block
30
+ // may have only three outputs, in which case no third vec4 read is needed.
31
+ let aa = a * a;
32
+ let bb = b * b;
33
+ y[outputBase] = {{ scalar }}(sqrt(aa.x + aa.y + aa.z + aa.w));
34
+ if (ow + 1u < OUT_W) {
35
+ y[outputBase + 1u] = {{ scalar }}(sqrt(aa.z + aa.w + bb.x + bb.y));
36
+ }
37
+ if (ow + 2u < OUT_W) {
38
+ y[outputBase + 2u] = {{ scalar }}(sqrt(bb.x + bb.y + bb.z + bb.w));
39
+ }
40
+ if (ow + 3u < OUT_W) {
41
+ let c = vec4<f32>(x[inputVec + 2u]);
42
+ let cc = c * c;
43
+ y[outputBase + 3u] = {{ scalar }}(sqrt(bb.z + bb.w + cc.x + cc.y));
44
+ }
45
+ }
build/webgpu/pool-window-nd.wgsl.jinja ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ var acc = 0.0;
26
+
27
+ {% if source.spatialRank == 3 %}
28
+ for (var kd = 0u; kd < params.kernelD; kd += 1u) {
29
+ let id = i32(od * params.strideD + kd * params.dilationD) - params.padD;
30
+ if (id < 0 || id >= i32(params.inD)) { continue; }
31
+ {% endif %}
32
+ {% if source.spatialRank >= 2 %}
33
+ for (var kh = 0u; kh < params.kernelH; kh += 1u) {
34
+ let ih = i32(oh * params.strideH + kh * params.dilationH) - params.padH;
35
+ if (ih < 0 || ih >= i32(params.inH)) { continue; }
36
+ {% endif %}
37
+ for (var kw = 0u; kw < params.kernelW; kw += 1u) {
38
+ let iw = i32(ow * params.strideW + kw * params.dilationW) - params.padW;
39
+ if (iw < 0 || iw >= i32(params.inW)) { continue; }
40
+ {% if source.spatialRank == 1 %}
41
+ let xIndex = (batch * params.channels + channel) * params.inW + u32(iw);
42
+ {% elif source.spatialRank == 2 %}
43
+ let xIndex = ((batch * params.channels + channel) * params.inH + u32(ih)) * params.inW + u32(iw);
44
+ {% else %}
45
+ let xIndex = (((batch * params.channels + channel) * params.inD + u32(id)) * params.inH + u32(ih)) * params.inW + u32(iw);
46
+ {% endif %}
47
+ {% if pIs2 %}
48
+ let value = f32(x[xIndex]);
49
+ acc += value * value;
50
+ {% elif pIs1 %}
51
+ acc += abs(f32(x[xIndex]));
52
+ {% else %}
53
+ acc += pow(abs(f32(x[xIndex])), params.p);
54
+ {% endif %}
55
+ }
56
+ {% if source.spatialRank >= 2 %}
57
+ }
58
+ {% endif %}
59
+ {% if source.spatialRank == 3 %}
60
+ }
61
+ {% endif %}
62
+
63
+ {% if pIs2 %}
64
+ y[index] = {{ scalar }}(sqrt(acc));
65
+ {% elif pIs1 %}
66
+ y[index] = {{ scalar }}(acc);
67
+ {% else %}
68
+ y[index] = {{ scalar }}(pow(acc, 1.0 / params.p));
69
+ {% endif %}
70
+ }
build/webgpu/pool-window-unroll.wgsl.jinja ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 == "lp" and source.p != 1 and source.p != 2 %}
19
+ const P: f32 = f32({{ source.p }});
20
+ {% endif %}
21
+
22
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
23
+ fn main(
24
+ @builtin(global_invocation_id) gid: vec3<u32>,
25
+ @builtin(num_workgroups) nwg: vec3<u32>
26
+ ) {
27
+ // Fold very large flat outputs into the dispatch's second dimension.
28
+ let index = gid.x + gid.y * nwg.x * {{ 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;
35
+ let oh = plane % OUT_H;
36
+ plane /= OUT_H;
37
+ let planeBase = plane * IN_PLANE;
38
+ let startH = i32(oh * STRIDE_H) - PAD_TOP;
39
+ let startW = i32(ow * STRIDE_W) - PAD_LEFT;
40
+ {% for kh in range(source.kernelH) %}
41
+ let ih_{{ kh }} = startH + {{ kh * source.dilationH }};
42
+ {% endfor %}
43
+ {% for kw in range(source.kernelW) %}
44
+ let iw_{{ kw }} = startW + {{ kw * source.dilationW }};
45
+ {% endfor %}
46
+
47
+ var result = 0.0;
48
+ {% for kh in range(source.kernelH) %}
49
+ if (ih_{{ kh }} >= 0 && ih_{{ kh }} < IN_H) {
50
+ let rowBase{{ kh }} = planeBase + u32(ih_{{ kh }}) * IN_W_U;
51
+ {% for kw in range(source.kernelW) %}
52
+ if (iw_{{ kw }} >= 0 && iw_{{ kw }} < IN_W) {
53
+ let value_{{ kh }}_{{ kw }} = f32(x[rowBase{{ kh }} + u32(iw_{{ kw }})]);
54
+ {% if source.op == "lp" and source.p == 2 %}
55
+ result += value_{{ kh }}_{{ kw }} * value_{{ kh }}_{{ kw }};
56
+ {% elif source.op == "lp" and source.p == 1 %}
57
+ result += abs(value_{{ kh }}_{{ kw }});
58
+ {% else %}
59
+ result += pow(abs(value_{{ kh }}_{{ kw }}), P);
60
+ {% endif %}
61
+ }
62
+ {% endfor %}
63
+ }
64
+ {% endfor %}
65
+
66
+
67
+ {% if source.op == "lp" and source.p == 2 %}
68
+ let output = sqrt(result);
69
+ {% elif source.op == "lp" and source.p == 1 %}
70
+ let output = result;
71
+ {% else %}
72
+ let output = pow(result, 1.0 / P);
73
+ {% endif %}
74
+ y[index] = {{ scalar }}(output);
75
+ }
build/webgpu/pool2d-nchw-k2s2-vec4.wgsl.jinja ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // 2D NCHW LpPool specialised for the dense 2x2 / stride-2 / no-pad /
7
+ // dilation-1 window. Each thread produces two adjacent output columns from one
8
+ // aligned vec4 load per input row. A vec4 over
9
+ // input columns [4*tileCol .. 4*tileCol+3] holds the two side-by-side 2-wide
10
+ // windows. The p-norm is accumulated in f32 (sum of
11
+ // |x|^p over the 2x2 window, same four elements / same count as the scalar
12
+ // fallback) and reduced by the p-th root; p=2 specialises to sum-of-squares +
13
+ // sqrt, p=1 to sum-of-abs.
14
+ const OUT_W: u32 = {{ source.outW }}u; // == IN_W / 2 (no pad, stride 2)
15
+ const OUT_H: u32 = {{ source.outH }}u;
16
+ const OUT_W_HALF: u32 = {{ source.outWHalf }}u;
17
+ const IN_W_VEC4: u32 = {{ source.inWVec4 }}u; // input row length in vec4 units
18
+ const IN_PLANE_VEC4: u32 = {{ source.inPlaneVec4 }}u;
19
+ {% if source.p != 1 and source.p != 2 %}
20
+ const P: f32 = f32({{ source.p }});
21
+
22
+ {% endif %}
23
+ {% if source.p == 2 %}
24
+ fn term(v: {{ scalar }}) -> f32 { let f = f32(v); return f * f; }
25
+ {% elif source.p == 1 %}
26
+ fn term(v: {{ scalar }}) -> f32 { return abs(f32(v)); }
27
+ {% else %}
28
+ fn term(v: {{ scalar }}) -> f32 { return pow(abs(f32(v)), P); }
29
+ {% endif %}
30
+
31
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
32
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
33
+ // 2D-folded flat tile index (one tile = two output columns).
34
+ let tile = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
35
+ if (tile >= params.tileCount) {
36
+ return;
37
+ }
38
+
39
+ let tileCol = tile % OUT_W_HALF; // pair-of-columns within the row
40
+ var t = tile / OUT_W_HALF;
41
+ let oh = t % OUT_H;
42
+ let plane = t / OUT_H; // batch * channels + channel
43
+
44
+ let row0Vec4 = plane * IN_PLANE_VEC4 + (oh * 2u) * IN_W_VEC4 + tileCol;
45
+ let row1Vec4 = row0Vec4 + IN_W_VEC4;
46
+
47
+ let r0 = x[row0Vec4];
48
+ let r1 = x[row1Vec4];
49
+
50
+ // Column pair 0 spans vec4 lanes 0,1; column pair 1 spans lanes 2,3.
51
+ let acc0 = term(r0.x) + term(r0.y) + term(r1.x) + term(r1.y);
52
+ let acc1 = term(r0.z) + term(r0.w) + term(r1.z) + term(r1.w);
53
+
54
+ let outBase = (plane * OUT_H + oh) * OUT_W + tileCol * 2u;
55
+ {% if source.p == 2 %}
56
+ y[outBase] = {{ scalar }}(sqrt(acc0));
57
+ y[outBase + 1u] = {{ scalar }}(sqrt(acc1));
58
+ {% elif source.p == 1 %}
59
+ y[outBase] = {{ scalar }}(acc0);
60
+ y[outBase + 1u] = {{ scalar }}(acc1);
61
+ {% else %}
62
+ y[outBase] = {{ scalar }}(pow(acc0, 1.0 / P));
63
+ y[outBase + 1u] = {{ scalar }}(pow(acc1, 1.0 / P));
64
+ {% endif %}
65
+ }
build/webgpu/pool2d-nchw-separable.wgsl.jinja ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // Separable NCHW pooling: one spatial axis per pass.
7
+ //
8
+ // Both reductions here are associative, so a kernelH x kernelW window
9
+ // factorizes into a reduction along W followed by one along H. The direct
10
+ // kernel reads kernelH*kernelW inputs per output; separating them reads kernelW
11
+ // into an intermediate cell that neighbouring outputs then share, plus kernelH
12
+ // per output.
13
+ //
14
+ // The intermediate is f32 because the direct kernel also accumulates in f32
15
+ // and casts once at the end, including for i32 input.
16
+ //
17
+ // Max pooling is bit-identical to the direct kernel because max is idempotent
18
+ // as well as associative. Lp pooling is within tolerance but not bit-identical:
19
+ // summing each row first regroups f32 additions relative to the direct kernel.
20
+ //
21
+ // `axis` selects the pass: "w" reduces x into tmp, and "h" reduces tmp into y.
22
+ // AveragePool is not wired here because its denominator would require valid-tap
23
+ // counts to travel alongside the sum.
24
+
25
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
26
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
27
+ let index = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ {% if source.axis == "w" %}
29
+ if (index >= params.tmpCount) { return; }
30
+ // One cell per (batch, channel, input row, output column).
31
+ let ow = index % params.outW;
32
+ let t = index / params.outW;
33
+ let ih = t % params.inH;
34
+ let nc = t / params.inH;
35
+
36
+ var acc = 0.0;
37
+ for (var kw = 0u; kw < params.kernelW; kw += 1u) {
38
+ let iw = i32(ow * params.strideW + kw * params.dilationW) - params.padW;
39
+ if (iw < 0 || iw >= i32(params.inW)) { continue; }
40
+ let value = f32(x[(nc * params.inH + ih) * params.inW + u32(iw)]);
41
+ {% if pIs2 %}
42
+ acc += value * value;
43
+ {% elif pIs1 %}
44
+ acc += abs(value);
45
+ {% else %}
46
+ acc += pow(abs(value), params.p);
47
+ {% endif %}
48
+ }
49
+ tmp[index] = acc;
50
+ {% else %}
51
+ if (index >= params.count) { return; }
52
+ let ow = index % params.outW;
53
+ let t = index / params.outW;
54
+ let oh = t % params.outH;
55
+ let nc = t / params.outH;
56
+
57
+ var acc = 0.0;
58
+ for (var kh = 0u; kh < params.kernelH; kh += 1u) {
59
+ let ih = i32(oh * params.strideH + kh * params.dilationH) - params.padH;
60
+ if (ih < 0 || ih >= i32(params.inH)) { continue; }
61
+ let partial = tmp[(nc * params.inH + u32(ih)) * params.outW + ow];
62
+ acc += partial;
63
+ }
64
+
65
+ {% if pIs2 %}
66
+ y[index] = {{ scalar }}(sqrt(acc));
67
+ {% elif pIs1 %}
68
+ y[index] = {{ scalar }}(acc);
69
+ {% else %}
70
+ y[index] = {{ scalar }}(pow(acc, 1.0 / params.p));
71
+ {% endif %}
72
+ {% endif %}
73
+ }
build/webgpu/test.json ADDED
The diff for this file is too large to render. See raw diff