Xenova HF Staff commited on
Commit
8a6a5af
·
verified ·
1 Parent(s): 44b3cb1

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -39,14 +39,14 @@ See the [ONNX `GlobalAveragePool` spec](https://onnx.ai/onnx/operators/onnx__Glo
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
43
  - [`pool-global-reduction.wgsl.jinja`](build/webgpu/pool-global-reduction.wgsl.jinja)
44
  - [`pool-global-serial.wgsl.jinja`](build/webgpu/pool-global-serial.wgsl.jinja)
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.
 
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 cases
43
  - [`pool-global-reduction.wgsl.jinja`](build/webgpu/pool-global-reduction.wgsl.jinja)
44
  - [`pool-global-serial.wgsl.jinja`](build/webgpu/pool-global-serial.wgsl.jinja)
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
  ```sh
49
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
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.
build/webgpu/bench.json CHANGED
@@ -85,9 +85,7 @@
85
  },
86
  {
87
  "name": "nchw_1x1024x14x14_portable_gate_window",
88
- "provenance": {
89
- "notes": "Spatial 196 sits in the window the portable plane-parallel gate excludes (65 with subgroups, 1024 without), so a subgroupless device falls back to the serial kernel here."
90
- },
91
  "preset": "smoke",
92
  "vars": { "batch": 1, "channels": 1024, "spatial": 196 },
93
  "inputs": { "x": { "dtype": "float32", "shape": [1, 1024, 14, 14], "dist": "normal", "seed": 216 } },
@@ -107,7 +105,7 @@
107
  "name": "nchw_32x128x16x16_manyplanes_spatial256",
108
  "preset": "smoke",
109
  "provenance": {
110
- "notes": "4096 planes of 256 elements: the serial kernel already has an invocation per plane, so this is the regime where the plane-parallel workgroup has the least to add. GlobalLpPool's same shape prefers the serial route."
111
  },
112
  "vars": { "batch": 32, "channels": 128, "spatial": 256 },
113
  "inputs": { "x": { "dtype": "float32", "shape": [32, 128, 16, 16], "dist": "normal", "seed": 218 } },
@@ -150,9 +148,7 @@
150
  {
151
  "name": "nchw_1x65536x9x9_planes65536_spatial81",
152
  "preset": "smoke",
153
- "provenance": {
154
- "notes": "65536 planes: four outputs per invocation still leaves 16384 invocations, which is the regime the vec4-output serial kernel is written for."
155
- },
156
  "vars": { "batch": 1, "channels": 65536, "spatial": 81 },
157
  "inputs": { "x": { "dtype": "float32", "shape": [1, 65536, 9, 9], "dist": "normal", "seed": 222 } },
158
  "outputs": { "y": { "dtype": "float32", "shape": [1, 65536, 1, 1], "dist": "empty" } },
 
85
  },
86
  {
87
  "name": "nchw_1x1024x14x14_portable_gate_window",
88
+ "provenance": { "notes": "Measures global average pooling over 1024 channels of a 14x14 (196-element) plane." },
 
 
89
  "preset": "smoke",
90
  "vars": { "batch": 1, "channels": 1024, "spatial": 196 },
91
  "inputs": { "x": { "dtype": "float32", "shape": [1, 1024, 14, 14], "dist": "normal", "seed": 216 } },
 
105
  "name": "nchw_32x128x16x16_manyplanes_spatial256",
106
  "preset": "smoke",
107
  "provenance": {
108
+ "notes": "Measures global average pooling over 4096 planes (batch 32 x 128 channels) of a 256-element 16x16 spatial size."
109
  },
110
  "vars": { "batch": 32, "channels": 128, "spatial": 256 },
111
  "inputs": { "x": { "dtype": "float32", "shape": [32, 128, 16, 16], "dist": "normal", "seed": 218 } },
 
148
  {
149
  "name": "nchw_1x65536x9x9_planes65536_spatial81",
150
  "preset": "smoke",
151
+ "provenance": { "notes": "Measures global average pooling over 65,536 channels of a 9x9 (81-element) plane." },
 
 
152
  "vars": { "batch": 1, "channels": 65536, "spatial": 81 },
153
  "inputs": { "x": { "dtype": "float32", "shape": [1, 65536, 9, 9], "dist": "normal", "seed": 222 } },
154
  "outputs": { "y": { "dtype": "float32", "shape": [1, 65536, 1, 1], "dist": "empty" } },
build/webgpu/manifest.json CHANGED
@@ -22,15 +22,14 @@
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": "GlobalAveragePool.WgPlaneParallel",
30
  "shader": "pool-global-reduction.wgsl.jinja",
31
- "subgroupCollectivesWidth": "portable",
32
  "derive": {
33
- "op": "\"average\"",
34
  "vec4": "inner(shapes.x, 1) % 4 == 0",
35
  "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
36
  "useSubgroups": "device.features.has(\"subgroups\")"
@@ -51,7 +50,7 @@
51
  "id": "main",
52
  "name": "GlobalAveragePool.Nchw",
53
  "shader": "pool-global-serial.wgsl.jinja",
54
- "derive": { "op": "\"average\"", "outputsPerInvocation": 1 },
55
  "bindings": [
56
  "x",
57
  "y",
@@ -71,5 +70,6 @@
71
  }
72
  ]
73
  }
74
- ]
 
75
  }
 
22
  "id": "wg_plane_parallel",
23
  "priority": 30,
24
  "when": ["planeParallelEligible"],
25
+ "demoteWhen": ["not device.features.has(\"subgroups\") and dtypes.T == \"f32\" and numel(shapes.y) >= tunables.PLANE_PARALLEL_STARVED_PLANES and inner(shapes.x, 1) <= 2 * tunables.PLANE_PARALLEL_AMORTIZED_SPATIAL"],
26
  "derive": { "planeInput": "\"vec4<\" ~ dtypes.T ~ \">\" if inner(shapes.x, 1) % 4 == 0 else dtypes.T" },
27
  "passes": [
28
  {
29
  "id": "main",
30
  "name": "GlobalAveragePool.WgPlaneParallel",
31
  "shader": "pool-global-reduction.wgsl.jinja",
 
32
  "derive": {
 
33
  "vec4": "inner(shapes.x, 1) % 4 == 0",
34
  "workgroupSizeSpec": "tunables.WORKGROUP_SIZE",
35
  "useSubgroups": "device.features.has(\"subgroups\")"
 
50
  "id": "main",
51
  "name": "GlobalAveragePool.Nchw",
52
  "shader": "pool-global-serial.wgsl.jinja",
53
+ "derive": { "outputsPerInvocation": 1 },
54
  "bindings": [
55
  "x",
56
  "y",
 
70
  }
71
  ]
72
  }
73
+ ],
74
+ "bindings": {}
75
  }
build/webgpu/metadata.json CHANGED
@@ -1,22 +1,22 @@
1
  {
2
  "name": "ai.onnx.GlobalAveragePool",
3
- "id": "_ai_onnx_globalaveragepool_webgpu_56a66d8",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "3Q7Hhu7A0Ya1DWVVrIWVxyW2qxLEFF9YtJEwjfLEOQE=",
11
- "manifest.json": "13pXbztMIjCthx+34Tv/epFss6CetZgcwudxKej2M44=",
12
- "pool-global-reduction.wgsl.jinja": "SKnTSUq9nU838U0t7VyvC7hgz4h5L+Ndv1rQCM9Vks8=",
13
- "pool-global-serial.wgsl.jinja": "idkz50sNZFg0fXNECnKQxbRZK1xFDWzyuJyxIMaepEw=",
14
- "test.json": "egIwDffBXas1eU87qZq5zYwzi9nVWXppV5JdlZc9PDE="
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
  }
 
1
  {
2
  "name": "ai.onnx.GlobalAveragePool",
3
+ "id": "_ai_onnx_globalaveragepool_webgpu_545a553",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "popdR9Qi/BImt4Ux/qIVOhoPeNwySqGaBoF4YB6spn0=",
11
+ "manifest.json": "FHENwZk+f3tgm5aUoHx0/8TsVYl3ec5Hp436+Pue8a0=",
12
+ "pool-global-reduction.wgsl.jinja": "uCrSccgj/Fibd866kxpiYkprR8g6q1vofgQZZ/oxGkE=",
13
+ "pool-global-serial.wgsl.jinja": "pYR1xvboodN6Lty4Y9ytA9B1gNh2yyUZaCM0k8tRB/0=",
14
+ "test.json": "adZiAK+JRTpxqoklQoJU7ZP86KCq9GdZNUsyLpN1ruY="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
18
  "webgpu": {
19
+ "manifestSpec": "2.1",
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
@@ -22,13 +22,11 @@ var<workgroup> wg_partials: array<f32, WG>;
22
  var<workgroup> wg_tree: array<f32, WG>;
23
  {% endif %}
24
 
25
-
26
  @compute @workgroup_size(WG)
27
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
28
- @builtin(local_invocation_id) lid: vec3<u32>
29
- {%- if useSubgroups %},
30
- @builtin(subgroup_size) sg_size: u32
31
- {%- endif %}) {
32
  let plane = wid.x;
33
  let tid = lid.x;
34
  var partial = 0.0;
@@ -50,32 +48,35 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
50
  {% endif %}
51
 
52
  {% if useSubgroups %}
53
- var total = subgroupAdd(partial);
54
- // Cross-subgroup fold that assumes nothing about which invocations share a
55
- // subgroup, how many subgroups there are, or which of a subgroup's lanes are
56
- // active: every invocation owns the slot at its own index, the elected lane
57
- // publishes its subgroup partial there and every other lane publishes the
58
- // reduction identity. Each subgroup then folds all WG slots — lane `rank`,
59
- // its dense position among the active lanes, walks slots rank, rank + count,
60
- // ... — and one more collective merges the lane partials, so every slot is
61
- // merged exactly once at any legal width and partition. A one-subgroup
62
- // workgroup skips shared memory entirely; that test reads the `subgroup_size`
63
- // builtin, which is uniform, because a collective's result is not uniform to
64
- // WGSL's analysis and may not guard a barrier.
65
- if (sg_size != WG) {
66
- let rank = subgroupExclusiveAdd(1u);
67
- let count = subgroupAdd(1u);
68
- wg_partials[tid] = select(0.0, total, rank == 0u);
69
- workgroupBarrier();
70
- var acc = 0.0;
71
- for (var i = rank; i < WG; i = i + count) {
72
- acc = acc + wg_partials[i];
73
- }
74
- total = subgroupAdd(acc);
 
 
75
  }
 
 
 
76
  if (tid == 0u) {
77
- y[plane] = {{ scalar }}(total / f32(params.spatial));
78
- }
79
  {% else %}
80
  // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
81
  wg_tree[tid] = partial;
@@ -88,7 +89,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
88
  }
89
  if (tid == 0u) {
90
  let total = wg_tree[0];
 
91
  y[plane] = {{ scalar }}(total / f32(params.spatial));
92
  }
93
- {% endif %}
94
  }
 
22
  var<workgroup> wg_tree: array<f32, WG>;
23
  {% endif %}
24
 
 
25
  @compute @workgroup_size(WG)
26
  fn main(@builtin(workgroup_id) wid: vec3<u32>,
27
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
28
+ @builtin(subgroup_invocation_id) sg_lane: u32,
29
+ @builtin(subgroup_size) sg_size: u32{% endif %}) {
 
30
  let plane = wid.x;
31
  let tid = lid.x;
32
  var partial = 0.0;
 
48
  {% endif %}
49
 
50
  {% if useSubgroups %}
51
+ {% filter indent(2, true) %}
52
+ var total = subgroupAdd(partial);
53
+ // Cross-subgroup fold that assumes nothing about which invocations share a
54
+ // subgroup or how many subgroups there are (it does require the uniform
55
+ // control flow this entry point already has): every invocation owns the slot
56
+ // at its own index, the elected lane
57
+ // publishes its subgroup partial there and every other lane publishes the
58
+ // reduction identity. Each subgroup then folds all WG slots — lane `sg_lane`
59
+ // walks slots sg_lane, sg_lane + sg_size, ... — and one more collective merges
60
+ // the lane partials, so every slot is merged exactly once at any legal width.
61
+ // The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
62
+ // `subgroupAdd(1u)`: those agree with them under this contract, but a driver
63
+ // in the wild answers a claim derived from them with zero. A one-subgroup
64
+ // workgroup skips shared memory entirely; that test reads the `subgroup_size`
65
+ // builtin, which is uniform, because a collective's result is not uniform to
66
+ // WGSL's analysis and may not guard a barrier.
67
+ if (sg_size != WG) {
68
+ let rank = sg_lane;
69
+ let lanes = sg_size;
70
+ wg_partials[tid] = select(0.0, total, rank == 0u);
71
+ workgroupBarrier();
72
+ var acc = 0.0;
73
+ for (var i = rank; i < WG; i = i + lanes) {
74
+ acc = acc + wg_partials[i];
75
  }
76
+ total = subgroupAdd(acc);
77
+ }
78
+ {% endfilter %}
79
  if (tid == 0u) {
 
 
80
  {% else %}
81
  // No-subgroups fallback: full workgroup-tree reduction of the per-thread partials.
82
  wg_tree[tid] = partial;
 
89
  }
90
  if (tid == 0u) {
91
  let total = wg_tree[0];
92
+ {% endif %}
93
  y[plane] = {{ scalar }}(total / f32(params.spatial));
94
  }
 
95
  }
build/webgpu/pool-global-serial.wgsl.jinja CHANGED
@@ -1,4 +1,5 @@
1
  {{ env.wgsl.resourceDeclarations }}
 
2
  fn pool_plane(out_index: u32) -> f32 {
3
  let base = out_index * params.spatial;
4
  var result = 0.0;
 
1
  {{ env.wgsl.resourceDeclarations }}
2
+
3
  fn pool_plane(out_index: u32) -> f32 {
4
  let base = out_index * params.spatial;
5
  var result = 0.0;
build/webgpu/test.json CHANGED
@@ -36,7 +36,7 @@
36
  "provenance": {
37
  "source": "onnxruntime/test/providers/cpu/nn/pool_op_test.cc",
38
  "test": "PoolTest.GlobalAveragePool_Large_128",
39
- "notes": "Large compact-cycle version selects the workgroup-parallel spatial-plane reduction variant while keeping the exact average subnormal."
40
  },
41
  "inputs": {
42
  "x": {
@@ -346,9 +346,7 @@
346
  },
347
  {
348
  "name": "f32_nchw2d_scalar_smallspatial_1x16x7x7",
349
- "provenance": {
350
- "notes": "A compact float32 7x7 input keeps the spatial size below the plane-parallel threshold and exercises scalar GlobalAveragePool."
351
- },
352
  "inputs": {
353
  "x": {
354
  "dtype": "float32",
@@ -359,9 +357,9 @@
359
  "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 1, 1], "tolerance": 0.000001 } }
360
  },
361
  {
362
- "name": "f32_nchw2d_spatial64_selector_boundary_1x16x8x8",
363
  "provenance": {
364
- "notes": "Pins the 64-element crossover: fixed subgroup-32 adapters favor the serial scalar plane kernel here, while variable-width subgroup adapters retain the workgroup-parallel route."
365
  },
366
  "inputs": {
367
  "x": {
@@ -403,7 +401,7 @@
403
  {
404
  "name": "f32_nchw2d_lowoccupancy_8planes_1x8x128x128",
405
  "provenance": {
406
- "notes": "Eight independent float32 planes with a large spatial extent exercise low-occupancy GlobalAveragePool."
407
  },
408
  "inputs": {
409
  "x": {
 
36
  "provenance": {
37
  "source": "onnxruntime/test/providers/cpu/nn/pool_op_test.cc",
38
  "test": "PoolTest.GlobalAveragePool_Large_128",
39
+ "notes": "A rank-three input with 1,024 equal subnormal values on its single spatial axis must average exactly to that subnormal."
40
  },
41
  "inputs": {
42
  "x": {
 
346
  },
347
  {
348
  "name": "f32_nchw2d_scalar_smallspatial_1x16x7x7",
349
+ "provenance": { "notes": "A compact float32 7x7 input checks spatial averaging over a short plane." },
 
 
350
  "inputs": {
351
  "x": {
352
  "dtype": "float32",
 
357
  "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 1, 1], "tolerance": 0.000001 } }
358
  },
359
  {
360
+ "name": "f32_nchw2d_spatial64_boundary_1x16x8x8",
361
  "provenance": {
362
+ "notes": "A float32 global average pool reduces 16 channels of an 8x8 (64-element) plane to a single value each."
363
  },
364
  "inputs": {
365
  "x": {
 
401
  {
402
  "name": "f32_nchw2d_lowoccupancy_8planes_1x8x128x128",
403
  "provenance": {
404
+ "notes": "A float32 global average pool reduces 8 channels of a 128x128 (16,384-element) plane to a single value each."
405
  },
406
  "inputs": {
407
  "x": {