Xenova HF Staff commited on
Commit
e7be652
·
verified ·
1 Parent(s): ee8ac59

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `GlobalMaxPool` spec](https://onnx.ai/onnx/operators/onnx__GlobalM
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)`, 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` | — | Output tensor of shape `(N x C x 1 x ... x 1)`; the maximum value over each spatial region per channel. | required |
30
 
31
  ## Type constraints
32
 
@@ -36,7 +36,7 @@ See the [ONNX `GlobalMaxPool` spec](https://onnx.ai/onnx/operators/onnx__GlobalM
36
 
37
  ## Files
38
 
39
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
40
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
41
  - [`test.json`](build/webgpu/test.json) — correctness cases
42
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -45,15 +45,18 @@ See the [ONNX `GlobalMaxPool` spec](https://onnx.ai/onnx/operators/onnx__GlobalM
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
49
 
50
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
51
 
52
- - `y`
53
 
54
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
55
 
56
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
57
 
58
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
59
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `X` | `T` | — | — | Input tensor of shape `(N x C x D1 x ... x Dn)`, where `N` is the batch size and `C` is the number of channels. | required |
24
 
25
  ## Outputs
26
 
27
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
+ | `y` | `Y` | `T` | same as `x` | — | Output tensor of shape `(N x C x 1 x ... x 1)`; the maximum value over each spatial region per channel. | required |
30
 
31
  ## Type constraints
32
 
 
36
 
37
  ## Files
38
 
39
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
40
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
41
  - [`test.json`](build/webgpu/test.json) — correctness cases
42
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
+ ```sh
49
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
50
+ ```
51
 
52
+ Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
53
 
54
+ This example supplies explicit metadata for:
55
 
56
+ - `y`
57
 
58
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
59
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
60
 
61
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
62
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.GlobalMaxPool",
3
  "cases": [
4
  {
5
  "name": "nchw_1x2048x7x7",
@@ -47,7 +46,7 @@
47
  "name": "nchw_1x8x512x512_lowoccupancy_8planes",
48
  "preset": "stress",
49
  "provenance": {
50
- "source": "authored for variant coverage",
51
  "notes": "Low-occupancy eight-plane global reduction that verifies the cooperative plane route; legacy serial paths remain reserved for shapes outside its feature-tier domain."
52
  },
53
  "vars": { "batch": 1, "channels": 8, "spatial": 262144 },
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "nchw_1x2048x7x7",
 
46
  "name": "nchw_1x8x512x512_lowoccupancy_8planes",
47
  "preset": "stress",
48
  "provenance": {
49
+ "source": "synthetic benchmark",
50
  "notes": "Low-occupancy eight-plane global reduction that verifies the cooperative plane route; legacy serial paths remain reserved for shapes outside its feature-tier domain."
51
  },
52
  "vars": { "batch": 1, "channels": 8, "spatial": 262144 },
build/webgpu/manifest.json CHANGED
@@ -2,109 +2,73 @@
2
  "domain": "ai.onnx",
3
  "name": "GlobalMaxPool",
4
  "sinceVersion": 1,
5
- "description": "Applies max pooling across all spatial dimensions of `X`, producing one value per channel. Equivalent to MaxPool with kernel size equal to the full spatial extent of the input; output shape is `(N x C x 1 x ... x 1)`.",
6
- "inputs": [
7
- {
8
- "role": "X",
9
- "dtype": "T",
10
- "description": "Input tensor of shape `(N x C x D1 x ... x 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
- "rank": "ranks.X",
18
- "description": "Output tensor of shape `(N x C x 1 x ... x 1)`; the maximum value over each spatial region per channel."
19
- }
20
- ],
21
  "typeConstraints": { "T": ["float32", "float16"] },
22
- "args": {
23
- "x": { "kind": "tensor", "semantic": "X", "role": "input" },
24
- "y": { "kind": "tensor", "semantic": "Y", "role": "output" }
25
- },
26
  "tunables": {
27
- "WORKGROUP_SIZE": 256,
28
- "PLANE_PARALLEL_MIN_SPATIAL": 64,
29
- "PLANE_PARALLEL_AMORTIZED_SPATIAL": 768,
30
- "PLANE_PARALLEL_STARVED_PLANES": 1024
31
  },
32
  "derive": {
33
- "baseContract": "f16Ok(dtypes.T) and ranks.X >= 3 and ranks.Y == ranks.X and dim(shapes.Y, 0) == dim(shapes.X, 0) and dim(shapes.Y, 1) == dim(shapes.X, 1) and inner(shapes.Y, 1) == 1",
34
- "planeParallelEligible": "inner(shapes.X, 1) >= tunables.PLANE_PARALLEL_MIN_SPATIAL and (inner(shapes.X, 1) >= tunables.PLANE_PARALLEL_AMORTIZED_SPATIAL or numel(shapes.Y) <= tunables.PLANE_PARALLEL_STARVED_PLANES) and numel(shapes.Y) <= device.limits.maxComputeWorkgroupsPerDimension"
35
- },
36
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
37
- "bindingSets": {
38
- "parallelPlane": [
39
- {
40
- "name": "x",
41
- "arg": "x",
42
- "semantic": "X",
43
- "buffer": { "type": "read-only-storage" },
44
- "elementType": "$planeInput"
45
- },
46
- { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
47
- {
48
- "name": "params",
49
- "semantic": "kernel.params",
50
- "buffer": { "type": "uniform" },
51
- "struct": { "name": "Params", "fields": [{ "name": "spatial", "type": "u32", "value": "inner(shapes.X, 1)" }] }
52
- }
53
- ],
54
- "serialPlane": [
55
- { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
56
- { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
57
- {
58
- "name": "params",
59
- "semantic": "kernel.params",
60
- "buffer": { "type": "uniform" },
61
- "struct": {
62
- "name": "Params",
63
- "fields": [
64
- { "name": "spatial", "type": "u32", "value": "inner(shapes.X, 1)" },
65
- { "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
66
- ]
67
- }
68
- }
69
- ]
70
  },
 
71
  "variants": [
72
  {
73
  "id": "wg_plane_parallel",
74
  "priority": 30,
75
- "when": ["baseContract", "planeParallelEligible"],
76
- "constants": { "planeInput": "\"vec4<\" ~ dtypes.T ~ \">\" if inner(shapes.X, 1) % 4 == 0 else dtypes.T" },
77
  "passes": [
78
  {
79
  "id": "main",
80
  "name": "GlobalMaxPool.WgPlaneParallel",
81
- "source": {
82
- "shader": "pool-global-reduction.wgsl.jinja",
83
- "inputs": {
84
- "op": "\"max\"",
85
- "vec4": "inner(shapes.X, 1) % 4 == 0",
86
- "workgroupSize": "tunables.WORKGROUP_SIZE",
87
- "useSubgroups": "device.features.has(\"subgroups\")"
88
- }
89
  },
90
- "bindings": "parallelPlane",
91
- "dispatch": { "x": "numel(shapes.Y)" }
 
 
 
 
92
  }
93
  ]
94
  },
95
  {
96
  "id": "nchw",
97
- "when": ["baseContract", "not planeParallelEligible"],
98
  "passes": [
99
  {
100
  "id": "main",
101
  "name": "GlobalMaxPool.Nchw",
102
- "source": {
103
- "shader": "pool-global-serial.wgsl.jinja",
104
- "inputs": { "op": "\"max\"", "outputsPerInvocation": 1 }
105
- },
106
- "bindings": "serialPlane",
107
- "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
109
  ]
110
  }
 
2
  "domain": "ai.onnx",
3
  "name": "GlobalMaxPool",
4
  "sinceVersion": 1,
5
+ "inputs": { "x": { "onnx": "X", "dtype": "T" } },
6
+ "outputs": { "y": { "onnx": "Y", "dtype": "T", "rank": "ranks.x" } },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  "typeConstraints": { "T": ["float32", "float16"] },
 
 
 
 
8
  "tunables": {
9
+ "WORKGROUP_SIZE": { "default": 256 },
10
+ "PLANE_PARALLEL_MIN_SPATIAL": { "default": 64 },
11
+ "PLANE_PARALLEL_AMORTIZED_SPATIAL": { "default": 768 },
12
+ "PLANE_PARALLEL_STARVED_PLANES": { "default": 1024 }
13
  },
14
  "derive": {
15
+ "baseContract": "f16Ok(dtypes.T) and ranks.x >= 3 and ranks.y == ranks.x and dim(shapes.y, 0) == dim(shapes.x, 0) and dim(shapes.y, 1) == dim(shapes.x, 1) and inner(shapes.y, 1) == 1",
16
+ "planeParallelEligible": "inner(shapes.x, 1) >= tunables.PLANE_PARALLEL_MIN_SPATIAL and (inner(shapes.x, 1) >= tunables.PLANE_PARALLEL_AMORTIZED_SPATIAL or numel(shapes.y) <= tunables.PLANE_PARALLEL_STARVED_PLANES) and numel(shapes.y) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
17
+ "scalar": "dtypes.T"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  },
19
+ "when": ["baseContract"],
20
  "variants": [
21
  {
22
  "id": "wg_plane_parallel",
23
  "priority": 30,
24
+ "when": ["planeParallelEligible"],
25
+ "derive": { "planeInput": "\"vec4<\" ~ dtypes.T ~ \">\" if inner(shapes.x, 1) % 4 == 0 else dtypes.T" },
26
  "passes": [
27
  {
28
  "id": "main",
29
  "name": "GlobalMaxPool.WgPlaneParallel",
30
+ "shader": "pool-global-reduction.wgsl.jinja",
31
+ "subgroupCollectivesWidth": "portable",
32
+ "derive": {
33
+ "op": "\"max\"",
34
+ "vec4": "inner(shapes.x, 1) % 4 == 0",
35
+ "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
36
+ "useSubgroups": "device.features.has(\"subgroups\")"
 
37
  },
38
+ "bindings": [
39
+ { "arg": "x", "elementType": "$planeInput" },
40
+ "y",
41
+ { "name": "params", "struct": [{ "name": "spatial", "type": "u32", "value": "inner(shapes.x, 1)" }] }
42
+ ],
43
+ "dispatch": { "x": "numel(shapes.y)" }
44
  }
45
  ]
46
  },
47
  {
48
  "id": "nchw",
49
+ "when": ["not planeParallelEligible"],
50
  "passes": [
51
  {
52
  "id": "main",
53
  "name": "GlobalMaxPool.Nchw",
54
+ "shader": "pool-global-serial.wgsl.jinja",
55
+ "derive": { "op": "\"max\"", "outputsPerInvocation": 1 },
56
+ "bindings": [
57
+ "x",
58
+ "y",
59
+ {
60
+ "name": "params",
61
+ "struct": [
62
+ { "name": "spatial", "type": "u32", "value": "inner(shapes.x, 1)" },
63
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
64
+ ]
65
+ }
66
+ ],
67
+ "dispatch": {
68
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
69
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
70
+ "z": 1
71
+ }
72
  }
73
  ]
74
  }
build/webgpu/metadata.json CHANGED
@@ -1,19 +1,22 @@
1
  {
2
  "name": "ai.onnx.GlobalMaxPool",
3
- "id": "_ai_onnx_globalmaxpool_webgpu_e1e2a9b",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "Eet2dCeLHwGCclxUWpvzSTP1eo23iWxOYR0Hv0IevB8=",
11
- "manifest.json": "Er9sIUKGJm7kZSVuRlRzkWCQjRnFZTlvPrketaONZXk=",
12
- "pool-global-reduction.wgsl.jinja": "pY5tVjS1U8d/Hbabf04MwlObnCibUKA2Bjr0b4W3prk=",
13
- "pool-global-serial.wgsl.jinja": "t2QTHN0c2huEUxRxnzd9dQZOcXvm89WwJiSzz9pOtzo=",
14
- "test.json": "g0uQA0LM6LP5qlY6wRsAtFjX/IbNwTJMtiocyw157cI="
15
  }
16
  },
17
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
18
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.GlobalMaxPool" }
 
 
 
19
  }
 
1
  {
2
  "name": "ai.onnx.GlobalMaxPool",
3
+ "id": "_ai_onnx_globalmaxpool_webgpu_3b227dd",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "ylL2awmbpw5AHQZ8F59dhSjg/cNeDcbRmweZNMUcmJI=",
11
+ "manifest.json": "wErFlwEMiglUVXVU3mkqkU91gMQEcJV+HlmHnZ58vj8=",
12
+ "pool-global-reduction.wgsl.jinja": "d0Kc8mGbdDfI0PElsqLqjpnRqfyQeaS5LqSO/f8tfTM=",
13
+ "pool-global-serial.wgsl.jinja": "1tV3ld6YisYBbl2khXWO6Nyh3dtgh3sEFmRvB1ODdek=",
14
+ "test.json": "mtjdotV4Sw0xAfrVdrLQvtL5emPC7ir7K4pqJOJf3iM="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
18
+ "webgpu": {
19
+ "manifestSpec": "2.0",
20
+ "variants": { "wg_plane_parallel": ["pool-global-reduction.wgsl.jinja"], "nchw": ["pool-global-serial.wgsl.jinja"] }
21
+ }
22
  }
build/webgpu/pool-global-reduction.wgsl.jinja CHANGED
@@ -1,7 +1,4 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
- {% if source.useSubgroups %}
5
  enable subgroups;
6
  {% endif %}
7
  {{ env.wgsl.resourceDeclarations }}
@@ -9,17 +6,17 @@ enable subgroups;
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 %}
@@ -28,7 +25,7 @@ fn negative_infinity() -> f32 {
28
  var bits = 0xff800000u;
29
  return bitcast<f32>(bits);
30
  }
31
- {% if source.useSubgroups %}
32
 
33
  fn quiet_nan() -> f32 {
34
  var bits = 0x7fc00000u;
@@ -54,23 +51,13 @@ fn max_propagate_nan(a: f32, b: f32) -> f32 {
54
  @compute @workgroup_size(WG)
55
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
56
  @builtin(local_invocation_id) lid: vec3<u32>
57
- {%- if source.useSubgroups %},
58
  @builtin(subgroup_size) sg_size: u32
59
  {%- endif %}) {
60
  let plane = wid.x;
61
  let tid = lid.x;
62
- {% if source.useSubgroups %}
63
-
64
- // Seed cross-subgroup slots with the reduction identity so unwritten slots
65
- // (when the workgroup has fewer than MAX_SUBGROUPS subgroups) are harmless.
66
- if (tid < MAX_SUBGROUPS) {
67
- wg_partials[tid] = negative_infinity();
68
- }
69
- workgroupBarrier();
70
-
71
- {% endif %}
72
  var partial = negative_infinity();
73
- {% if source.vec4 %}
74
  let spatialVec = params.spatial / 4u;
75
  let base = plane * spatialVec;
76
  for (var i = tid; i < spatialVec; i = i + WG) {
@@ -88,21 +75,38 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
88
  }
89
  {% endif %}
90
 
91
- {% if source.useSubgroups %}
92
  // subgroupMax does not propagate NaN; carry an explicit NaN flag alongside.
93
  let lane_nan = is_nan_f32(partial);
94
  let sg_nan = subgroupAny(lane_nan);
95
  let sg_max = subgroupMax(select(partial, negative_infinity(), lane_nan));
96
- if (subgroupElect()) {
97
- wg_partials[tid / sg_size] = select(sg_max, quiet_nan(), sg_nan);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  }
99
- workgroupBarrier();
100
  if (tid == 0u) {
101
- let nsg = min((WG + sg_size - 1u) / sg_size, MAX_SUBGROUPS);
102
- var total = negative_infinity();
103
- for (var i = 0u; i < nsg; i = i + 1u) {
104
- total = max_propagate_nan(total, wg_partials[i]);
105
- }
106
  y[plane] = {{ scalar }}(total);
107
  }
108
  {% else %}
 
1
+ {% if useSubgroups %}
 
 
 
2
  enable subgroups;
3
  {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
 
6
  // Workgroup-parallel global pooling: one workgroup reduces one contiguous
7
  // (n, c) spatial plane (NC[D]HW layout makes the plane contiguous).
8
  // Threads stride the plane (vec4 loads when the plane size is divisible by 4),
9
+ // then combine the per-thread partials. With subgroups: a subgroup collective,
10
+ // then one shared-memory slot per invocation (the subgroup partial from its
11
+ // elected lane, the identity from every other lane) that every subgroup folds.
12
+ // Without subgroups, a full workgroup tree combines the per-thread partials.
13
+ // The fold propagates NaNs while taking the maximum.
14
+ const WG: u32 = {{ workgroupSizeSpec }}u;
15
+ {% if useSubgroups %}
16
+ // One slot per invocation: the cross-subgroup fold below indexes by
17
+ // local_invocation_id, never by a subgroup ordinal, so no subgroup width or
18
+ // partition can overflow or alias it.
19
+ var<workgroup> wg_partials: array<f32, WG>;
20
  {% else %}
21
  var<workgroup> wg_tree: array<f32, WG>;
22
  {% endif %}
 
25
  var bits = 0xff800000u;
26
  return bitcast<f32>(bits);
27
  }
28
+ {% if useSubgroups %}
29
 
30
  fn quiet_nan() -> f32 {
31
  var bits = 0x7fc00000u;
 
51
  @compute @workgroup_size(WG)
52
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
53
  @builtin(local_invocation_id) lid: vec3<u32>
54
+ {%- if useSubgroups %},
55
  @builtin(subgroup_size) sg_size: u32
56
  {%- endif %}) {
57
  let plane = wid.x;
58
  let tid = lid.x;
 
 
 
 
 
 
 
 
 
 
59
  var partial = negative_infinity();
60
+ {% if vec4 %}
61
  let spatialVec = params.spatial / 4u;
62
  let base = plane * spatialVec;
63
  for (var i = tid; i < spatialVec; i = i + WG) {
 
75
  }
76
  {% endif %}
77
 
78
+ {% if useSubgroups %}
79
  // subgroupMax does not propagate NaN; carry an explicit NaN flag alongside.
80
  let lane_nan = is_nan_f32(partial);
81
  let sg_nan = subgroupAny(lane_nan);
82
  let sg_max = subgroupMax(select(partial, negative_infinity(), lane_nan));
83
+ var total = select(sg_max, quiet_nan(), sg_nan);
84
+ // Cross-subgroup fold that assumes nothing about which invocations share a
85
+ // subgroup, how many subgroups there are, or which of a subgroup's lanes are
86
+ // active: every invocation owns the slot at its own index, the elected lane
87
+ // publishes its subgroup partial there and every other lane publishes the
88
+ // reduction identity. Each subgroup then folds all WG slots — lane `rank`,
89
+ // its dense position among the active lanes, walks slots rank, rank + count,
90
+ // ... — and one more collective merges the lane partials, so every slot is
91
+ // merged exactly once at any legal width and partition. A one-subgroup
92
+ // workgroup skips shared memory entirely; that test reads the `subgroup_size`
93
+ // builtin, which is uniform, because a collective's result is not uniform to
94
+ // WGSL's analysis and may not guard a barrier.
95
+ if (sg_size != WG) {
96
+ let rank = subgroupExclusiveAdd(1u);
97
+ let count = subgroupAdd(1u);
98
+ wg_partials[tid] = select(negative_infinity(), total, rank == 0u);
99
+ workgroupBarrier();
100
+ var acc = negative_infinity();
101
+ for (var i = rank; i < WG; i = i + count) {
102
+ acc = max_propagate_nan(acc, wg_partials[i]);
103
+ }
104
+ let acc_nan = is_nan_f32(acc);
105
+ let fold_nan = subgroupAny(acc_nan);
106
+ let fold_max = subgroupMax(select(acc, negative_infinity(), acc_nan));
107
+ total = select(fold_max, quiet_nan(), fold_nan);
108
  }
 
109
  if (tid == 0u) {
 
 
 
 
 
110
  y[plane] = {{ scalar }}(total);
111
  }
112
  {% else %}
build/webgpu/pool-global-serial.wgsl.jinja CHANGED
@@ -1,6 +1,3 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  fn negative_infinity() -> f32 {
@@ -31,11 +28,11 @@ fn pool_plane(out_index: u32) -> f32 {
31
  }
32
 
33
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
34
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
35
  // Each invocation produces one or four channel planes. gid.y carries folded workgroups beyond
36
  // the device's per-dimension dispatch limit.
37
- let first = (gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u) * {{ source.outputsPerInvocation }}u;
38
- {% for lane in range(source.outputsPerInvocation) %}
39
  if (first + {{ lane }}u < params.count) {
40
  y[first + {{ lane }}u] = {{ scalar }}(pool_plane(first + {{ lane }}u));
41
  }
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  fn negative_infinity() -> f32 {
 
28
  }
29
 
30
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
31
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
32
  // Each invocation produces one or four channel planes. gid.y carries folded workgroups beyond
33
  // the device's per-dimension dispatch limit.
34
+ let first = (gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u) * {{ outputsPerInvocation }}u;
35
+ {% for lane in range(outputsPerInvocation) %}
36
  if (first + {{ lane }}u < params.count) {
37
  y[first + {{ lane }}u] = {{ scalar }}(pool_plane(first + {{ lane }}u));
38
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.GlobalMaxPool",
3
  "cases": [
4
  {
5
  "name": "dispatch_cliff_ncl1d",
@@ -64,7 +63,7 @@
64
  {
65
  "name": "f32_nchw2d_smallspatial_1x16x7x7",
66
  "provenance": {
67
- "notes": "Compact f32 sibling for the 7x7 small-spatial vec4-output benchmark; keeps channels small while exercising the same low-spatial output path."
68
  },
69
  "inputs": {
70
  "x": {
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "dispatch_cliff_ncl1d",
 
63
  {
64
  "name": "f32_nchw2d_smallspatial_1x16x7x7",
65
  "provenance": {
66
+ "notes": "A compact float32 7x7 input with few channels exercises the small-spatial vec4-output path."
67
  },
68
  "inputs": {
69
  "x": {