sync 91d990483a17
Browse files- README.md +12 -9
- build/webgpu/bench.json +1 -2
- build/webgpu/manifest.json +44 -80
- build/webgpu/metadata.json +11 -8
- build/webgpu/pool-global-reduction.wgsl.jinja +41 -37
- build/webgpu/pool-global-serial.wgsl.jinja +3 -6
- build/webgpu/test.json +1 -2
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 |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `
|
| 24 |
|
| 25 |
## Outputs
|
| 26 |
|
| 27 |
-
| Name |
|
| 28 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 29 |
-
| `
|
| 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 |
-
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
|
| 52 |
-
|
| 53 |
|
| 54 |
-
|
| 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": "
|
| 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 |
-
"
|
| 6 |
-
"
|
| 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.
|
| 34 |
-
"planeParallelEligible": "inner(shapes.
|
| 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": ["
|
| 76 |
-
"
|
| 77 |
"passes": [
|
| 78 |
{
|
| 79 |
"id": "main",
|
| 80 |
"name": "GlobalMaxPool.WgPlaneParallel",
|
| 81 |
-
"
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
}
|
| 89 |
},
|
| 90 |
-
"bindings":
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
| 93 |
]
|
| 94 |
},
|
| 95 |
{
|
| 96 |
"id": "nchw",
|
| 97 |
-
"when": ["
|
| 98 |
"passes": [
|
| 99 |
{
|
| 100 |
"id": "main",
|
| 101 |
"name": "GlobalMaxPool.Nchw",
|
| 102 |
-
"
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"bench.json": "
|
| 11 |
-
"manifest.json": "
|
| 12 |
-
"pool-global-reduction.wgsl.jinja": "
|
| 13 |
-
"pool-global-serial.wgsl.jinja": "
|
| 14 |
-
"test.json": "
|
| 15 |
}
|
| 16 |
},
|
| 17 |
-
"provenance": { "kernel": { "sha": "
|
| 18 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
//
|
| 14 |
-
//
|
| 15 |
-
//
|
| 16 |
-
//
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
//
|
| 21 |
-
|
| 22 |
-
var<workgroup> wg_partials: array<f32,
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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>
|
| 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 *
|
| 38 |
-
{% for lane in range(
|
| 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": "
|
| 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": {
|