sync 6fdf6301e2bb
Browse files- README.md +89 -0
- build/webgpu/bench.json +425 -0
- build/webgpu/group-norm-combine.wgsl.jinja +50 -0
- build/webgpu/group-norm-fused.wgsl.jinja +136 -0
- build/webgpu/group-norm-slab.wgsl.jinja +233 -0
- build/webgpu/manifest.json +226 -0
- build/webgpu/metadata.json +26 -0
- build/webgpu/test.json +0 -0
README.md
CHANGED
|
@@ -1,3 +1,92 @@
|
|
| 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 |
+
# com.microsoft.GroupNorm
|
| 10 |
+
|
| 11 |
+
`com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Group normalization over a 4-D image tensor with an optional fused SiLU: `Y = gamma * (X - mean) / sqrt(variance + epsilon) + beta`, with mean and variance taken per batch item over each group of `C / groups` channels and every spatial position. Both layouts are implemented: `channels_last = 1` reads `(N, H, W, C)` and `channels_last = 0` reads `(N, C, H, W)`. `X`/`Y` and `gamma`/`beta` carry independent element types, so float16 activations with float32 weights is a first-class case. Statistics and the affine are float32 whatever the tensor type; only the store narrows.
|
| 16 |
+
|
| 17 |
+
See the [ONNX Runtime `GroupNorm` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.GroupNorm) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `X` | `T` | `4` | — | Input image tensor, `(N, H, W, C)` when `channels_last` is 1 and `(N, C, H, W)` otherwise. | required |
|
| 24 |
+
| `gamma` | `M` | `1` | — | Per-channel affine scale of shape `(C)`. | required |
|
| 25 |
+
| `beta` | `M` | `1` | — | Per-channel affine offset of shape `(C)`. | required |
|
| 26 |
+
|
| 27 |
+
## Outputs
|
| 28 |
+
|
| 29 |
+
| Name | Logical dtype | Rank | Shape | Description | Presence |
|
| 30 |
+
| --- | --- | --- | --- | --- | --- |
|
| 31 |
+
| `Y` | `T` | `4` | same as `X` | Normalized tensor with the same shape and element type as `X`. | required |
|
| 32 |
+
|
| 33 |
+
## Attributes
|
| 34 |
+
|
| 35 |
+
Attributes and default values (overridable per request):
|
| 36 |
+
|
| 37 |
+
| Attribute | Default | Description |
|
| 38 |
+
| --- | --- | --- |
|
| 39 |
+
| `activation` | — | Activation applied after the affine transform: `0` for none, `1` for SiLU. The request must supply it; the operator declares no default. |
|
| 40 |
+
| `channels_last` | `1` | `1` when `X` and `Y` are `(N, H, W, C)`, `0` when they are `(N, C, H, W)`. Defaults to `1`. Any non-zero value selects the channels-last layout. |
|
| 41 |
+
| `epsilon` | `0.00001` | Value added to the group variance before the reciprocal square root. Defaults to `1e-5`. |
|
| 42 |
+
| `groups` | — | Number of channel groups the statistics are taken over. It must divide `C`. The request must supply it; the operator declares no default. |
|
| 43 |
+
|
| 44 |
+
## Type constraints
|
| 45 |
+
|
| 46 |
+
| Variable | Allowed dtypes |
|
| 47 |
+
| --- | --- |
|
| 48 |
+
| `T` | `float32`, `float16` |
|
| 49 |
+
| `M` | `float32`, `float16` |
|
| 50 |
+
|
| 51 |
+
## Implementation variants
|
| 52 |
+
|
| 53 |
+
One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
|
| 54 |
+
|
| 55 |
+
- `split` — Three dispatches: partial group statistics, combine mean and reciprocal standard deviation, then normalize and apply affine weights. Channels-last statistics use four, two, or one aligned values per lane; group blocks and spatial rows follow device limits.
|
| 56 |
+
- `fused` — One workgroup per batch item and group, walking the group twice inside a single dispatch: once for the statistics and once to write the affine result. It avoids the split route's two extra launches on small tensors and is the route that needs no scratch buffer.
|
| 57 |
+
|
| 58 |
+
## Files
|
| 59 |
+
|
| 60 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 61 |
+
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 62 |
+
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 63 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark cases
|
| 64 |
+
- [`group-norm-combine.wgsl.jinja`](build/webgpu/group-norm-combine.wgsl.jinja)
|
| 65 |
+
- [`group-norm-fused.wgsl.jinja`](build/webgpu/group-norm-fused.wgsl.jinja)
|
| 66 |
+
- [`group-norm-slab.wgsl.jinja`](build/webgpu/group-norm-slab.wgsl.jinja)
|
| 67 |
+
|
| 68 |
+
## Use with `@huggingface/kernels`
|
| 69 |
+
|
| 70 |
+
```sh
|
| 71 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.3
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 75 |
+
|
| 76 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 77 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 78 |
+
|
| 79 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 80 |
+
|
| 81 |
+
```js
|
| 82 |
+
import { getKernel } from "@huggingface/kernels";
|
| 83 |
+
|
| 84 |
+
const kernel = await getKernel("webgpu-kernels/com.microsoft.GroupNorm", { version: 1 });
|
| 85 |
+
const { Y } = await kernel({
|
| 86 |
+
X: { data: XData, shape: [1, 2, 2, 16] },
|
| 87 |
+
gamma: { data: gammaData, shape: [16] },
|
| 88 |
+
beta: { data: betaData, shape: [16] },
|
| 89 |
+
}, {
|
| 90 |
+
attrs: { groups: 4, activation: 0 },
|
| 91 |
+
});
|
| 92 |
+
```
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tunableSpace": { "WORKGROUP_SIZE": [128, 256] },
|
| 3 |
+
"cases": [
|
| 4 |
+
{
|
| 5 |
+
"name": "flux-vae-mid-1x64x64x512-g32-f16",
|
| 6 |
+
"preset": "smoke",
|
| 7 |
+
"provenance": { "note": "Flux.2 VAE decoder mid/up-block group norm, NHWC, SiLU" },
|
| 8 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 2097152, "channels": 512 },
|
| 9 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1, "epsilon": 0.000001 },
|
| 10 |
+
"inputs": {
|
| 11 |
+
"X": { "dtype": "float16", "shape": [1, 64, 64, 512], "dist": "normal", "seed": 100, "scale": 0.5 },
|
| 12 |
+
"gamma": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 101, "scale": 0.25, "offset": 1 },
|
| 13 |
+
"beta": { "dtype": "float32", "shape": [512], "dist": "normal", "seed": 102, "scale": 0.1 }
|
| 14 |
+
},
|
| 15 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [1, 64, 64, 512], "dist": "empty" } },
|
| 16 |
+
"bench": {
|
| 17 |
+
"metrics": [
|
| 18 |
+
{
|
| 19 |
+
"type": "bandwidth",
|
| 20 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 21 |
+
}
|
| 22 |
+
],
|
| 23 |
+
"primary": true
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"name": "flux-vae-mid-1x64x64x512-g32-f32",
|
| 28 |
+
"preset": "smoke",
|
| 29 |
+
"provenance": { "note": "Flux.2 VAE decoder mid/up-block group norm at float32" },
|
| 30 |
+
"vars": { "dtype": "float32", "weightDtype": "float32", "count": 2097152, "channels": 512 },
|
| 31 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1, "epsilon": 0.000001 },
|
| 32 |
+
"inputs": {
|
| 33 |
+
"X": { "dtype": "float32", "shape": [1, 64, 64, 512], "dist": "normal", "seed": 110, "scale": 0.5 },
|
| 34 |
+
"gamma": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 111, "scale": 0.25, "offset": 1 },
|
| 35 |
+
"beta": { "dtype": "float32", "shape": [512], "dist": "normal", "seed": 112, "scale": 0.1 }
|
| 36 |
+
},
|
| 37 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [1, 64, 64, 512], "dist": "empty" } },
|
| 38 |
+
"bench": {
|
| 39 |
+
"metrics": [
|
| 40 |
+
{
|
| 41 |
+
"type": "bandwidth",
|
| 42 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 43 |
+
}
|
| 44 |
+
]
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"name": "sd15-unet-2x64x64x320-g32-f16",
|
| 49 |
+
"preset": "smoke",
|
| 50 |
+
"provenance": {
|
| 51 |
+
"note": "Stable-Diffusion-1.5 UNet resnet at the classifier-free-guidance batch; 10 channels per group"
|
| 52 |
+
},
|
| 53 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 2621440, "channels": 320 },
|
| 54 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 55 |
+
"inputs": {
|
| 56 |
+
"X": { "dtype": "float16", "shape": [2, 64, 64, 320], "dist": "normal", "seed": 120, "scale": 0.5 },
|
| 57 |
+
"gamma": { "dtype": "float32", "shape": [320], "dist": "uniform", "seed": 121, "scale": 0.25, "offset": 1 },
|
| 58 |
+
"beta": { "dtype": "float32", "shape": [320], "dist": "normal", "seed": 122, "scale": 0.1 }
|
| 59 |
+
},
|
| 60 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 64, 64, 320], "dist": "empty" } },
|
| 61 |
+
"bench": {
|
| 62 |
+
"metrics": [
|
| 63 |
+
{
|
| 64 |
+
"type": "bandwidth",
|
| 65 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 66 |
+
}
|
| 67 |
+
]
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"name": "sd15-unet-2x32x32x640-g32-f16",
|
| 72 |
+
"preset": "smoke",
|
| 73 |
+
"provenance": { "note": "Stable-Diffusion-1.5 UNet mid-resolution resnet; 20 channels per group" },
|
| 74 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 1310720, "channels": 640 },
|
| 75 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 76 |
+
"inputs": {
|
| 77 |
+
"X": { "dtype": "float16", "shape": [2, 32, 32, 640], "dist": "normal", "seed": 130, "scale": 0.5 },
|
| 78 |
+
"gamma": { "dtype": "float32", "shape": [640], "dist": "uniform", "seed": 131, "scale": 0.25, "offset": 1 },
|
| 79 |
+
"beta": { "dtype": "float32", "shape": [640], "dist": "normal", "seed": 132, "scale": 0.1 }
|
| 80 |
+
},
|
| 81 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 32, 32, 640], "dist": "empty" } },
|
| 82 |
+
"bench": {
|
| 83 |
+
"metrics": [
|
| 84 |
+
{
|
| 85 |
+
"type": "bandwidth",
|
| 86 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 87 |
+
}
|
| 88 |
+
]
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"name": "sd15-unet-2x8x8x1280-g32-f16",
|
| 93 |
+
"preset": "smoke",
|
| 94 |
+
"provenance": {
|
| 95 |
+
"note": "Stable-Diffusion-1.5 UNet lowest-resolution resnet; the few-element, low-parallelism corner"
|
| 96 |
+
},
|
| 97 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 163840, "channels": 1280 },
|
| 98 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 99 |
+
"inputs": {
|
| 100 |
+
"X": { "dtype": "float16", "shape": [2, 8, 8, 1280], "dist": "normal", "seed": 140, "scale": 0.5 },
|
| 101 |
+
"gamma": { "dtype": "float32", "shape": [1280], "dist": "uniform", "seed": 141, "scale": 0.25, "offset": 1 },
|
| 102 |
+
"beta": { "dtype": "float32", "shape": [1280], "dist": "normal", "seed": 142, "scale": 0.1 }
|
| 103 |
+
},
|
| 104 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 8, 8, 1280], "dist": "empty" } },
|
| 105 |
+
"bench": {
|
| 106 |
+
"metrics": [
|
| 107 |
+
{
|
| 108 |
+
"type": "bandwidth",
|
| 109 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 110 |
+
}
|
| 111 |
+
]
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"name": "odd-cpg-1x64x64x96-g32-f32",
|
| 116 |
+
"preset": "smoke",
|
| 117 |
+
"provenance": {
|
| 118 |
+
"note": "Three channels per group: the odd channels-per-group case the CUDA kernel refuses outright"
|
| 119 |
+
},
|
| 120 |
+
"vars": { "dtype": "float32", "weightDtype": "float32", "count": 393216, "channels": 96 },
|
| 121 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 122 |
+
"inputs": {
|
| 123 |
+
"X": { "dtype": "float32", "shape": [1, 64, 64, 96], "dist": "normal", "seed": 150, "scale": 0.5 },
|
| 124 |
+
"gamma": { "dtype": "float32", "shape": [96], "dist": "uniform", "seed": 151, "scale": 0.25, "offset": 1 },
|
| 125 |
+
"beta": { "dtype": "float32", "shape": [96], "dist": "normal", "seed": 152, "scale": 0.1 }
|
| 126 |
+
},
|
| 127 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [1, 64, 64, 96], "dist": "empty" } },
|
| 128 |
+
"bench": {
|
| 129 |
+
"metrics": [
|
| 130 |
+
{
|
| 131 |
+
"type": "bandwidth",
|
| 132 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 133 |
+
}
|
| 134 |
+
]
|
| 135 |
+
}
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"name": "nchw-4x64x128x128-g32-f32",
|
| 139 |
+
"preset": "smoke",
|
| 140 |
+
"provenance": {
|
| 141 |
+
"note": "Channels-first at the shape ai.onnx.GroupNormalization benches, for a direct comparison"
|
| 142 |
+
},
|
| 143 |
+
"vars": { "dtype": "float32", "weightDtype": "float32", "count": 4194304, "channels": 64 },
|
| 144 |
+
"attrs": { "groups": 32, "activation": 0, "channels_last": 0 },
|
| 145 |
+
"inputs": {
|
| 146 |
+
"X": { "dtype": "float32", "shape": [4, 64, 128, 128], "dist": "normal", "seed": 160, "scale": 0.5 },
|
| 147 |
+
"gamma": { "dtype": "float32", "shape": [64], "dist": "uniform", "seed": 161, "scale": 0.25, "offset": 1 },
|
| 148 |
+
"beta": { "dtype": "float32", "shape": [64], "dist": "normal", "seed": 162, "scale": 0.1 }
|
| 149 |
+
},
|
| 150 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [4, 64, 128, 128], "dist": "empty" } },
|
| 151 |
+
"bench": {
|
| 152 |
+
"metrics": [
|
| 153 |
+
{
|
| 154 |
+
"type": "bandwidth",
|
| 155 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 156 |
+
}
|
| 157 |
+
]
|
| 158 |
+
}
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"name": "nchw-2x320x64x64-g32-f16",
|
| 162 |
+
"preset": "smoke",
|
| 163 |
+
"provenance": { "note": "Channels-first Stable-Diffusion-1.5 resnet width with the fused SiLU" },
|
| 164 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 2621440, "channels": 320 },
|
| 165 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 0 },
|
| 166 |
+
"inputs": {
|
| 167 |
+
"X": { "dtype": "float16", "shape": [2, 320, 64, 64], "dist": "normal", "seed": 170, "scale": 0.5 },
|
| 168 |
+
"gamma": { "dtype": "float32", "shape": [320], "dist": "uniform", "seed": 171, "scale": 0.25, "offset": 1 },
|
| 169 |
+
"beta": { "dtype": "float32", "shape": [320], "dist": "normal", "seed": 172, "scale": 0.1 }
|
| 170 |
+
},
|
| 171 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 320, 64, 64], "dist": "empty" } },
|
| 172 |
+
"bench": {
|
| 173 |
+
"metrics": [
|
| 174 |
+
{
|
| 175 |
+
"type": "bandwidth",
|
| 176 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 177 |
+
}
|
| 178 |
+
]
|
| 179 |
+
}
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"name": "micro-2x2x2x128-g32-f16",
|
| 183 |
+
"preset": "smoke",
|
| 184 |
+
"provenance": {
|
| 185 |
+
"note": "ORT: onnxruntime/test/contrib_ops/group_norm_op_test.cc GroupNormTest.GroupNorm_128 geometry"
|
| 186 |
+
},
|
| 187 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 1024, "channels": 128 },
|
| 188 |
+
"attrs": { "groups": 32, "activation": 0, "channels_last": 1 },
|
| 189 |
+
"inputs": {
|
| 190 |
+
"X": { "dtype": "float16", "shape": [2, 2, 2, 128], "dist": "normal", "seed": 180, "scale": 0.5 },
|
| 191 |
+
"gamma": { "dtype": "float32", "shape": [128], "dist": "uniform", "seed": 181, "scale": 0.25, "offset": 1 },
|
| 192 |
+
"beta": { "dtype": "float32", "shape": [128], "dist": "normal", "seed": 182, "scale": 0.1 }
|
| 193 |
+
},
|
| 194 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 2, 2, 128], "dist": "empty" } },
|
| 195 |
+
"bench": {
|
| 196 |
+
"metrics": [
|
| 197 |
+
{
|
| 198 |
+
"type": "bandwidth",
|
| 199 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 200 |
+
}
|
| 201 |
+
]
|
| 202 |
+
}
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"name": "flux-vae-up-1x128x128x512-g32-f16",
|
| 206 |
+
"preset": "scale",
|
| 207 |
+
"provenance": { "note": "Flux.2 VAE decoder after the first 2x upsample" },
|
| 208 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 8388608, "channels": 512 },
|
| 209 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1, "epsilon": 0.000001 },
|
| 210 |
+
"inputs": {
|
| 211 |
+
"X": { "dtype": "float16", "shape": [1, 128, 128, 512], "dist": "normal", "seed": 200, "scale": 0.5 },
|
| 212 |
+
"gamma": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 201, "scale": 0.25, "offset": 1 },
|
| 213 |
+
"beta": { "dtype": "float32", "shape": [512], "dist": "normal", "seed": 202, "scale": 0.1 }
|
| 214 |
+
},
|
| 215 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [1, 128, 128, 512], "dist": "empty" } },
|
| 216 |
+
"bench": {
|
| 217 |
+
"metrics": [
|
| 218 |
+
{
|
| 219 |
+
"type": "bandwidth",
|
| 220 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 221 |
+
}
|
| 222 |
+
]
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"name": "flux-vae-up-1x256x256x256-g32-f16",
|
| 227 |
+
"preset": "scale",
|
| 228 |
+
"provenance": { "note": "Flux.2 VAE decoder after the second 2x upsample" },
|
| 229 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 16777216, "channels": 256 },
|
| 230 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1, "epsilon": 0.000001 },
|
| 231 |
+
"inputs": {
|
| 232 |
+
"X": { "dtype": "float16", "shape": [1, 256, 256, 256], "dist": "normal", "seed": 210, "scale": 0.5 },
|
| 233 |
+
"gamma": { "dtype": "float32", "shape": [256], "dist": "uniform", "seed": 211, "scale": 0.25, "offset": 1 },
|
| 234 |
+
"beta": { "dtype": "float32", "shape": [256], "dist": "normal", "seed": 212, "scale": 0.1 }
|
| 235 |
+
},
|
| 236 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [1, 256, 256, 256], "dist": "empty" } },
|
| 237 |
+
"bench": {
|
| 238 |
+
"metrics": [
|
| 239 |
+
{
|
| 240 |
+
"type": "bandwidth",
|
| 241 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 242 |
+
}
|
| 243 |
+
]
|
| 244 |
+
}
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"name": "flux-vae-convout-1x512x512x128-g32-f16",
|
| 248 |
+
"preset": "model",
|
| 249 |
+
"provenance": { "note": "Flux.2 VAE conv_norm_out: the largest single group-norm dispatch in the decoder" },
|
| 250 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 33554432, "channels": 128 },
|
| 251 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1, "epsilon": 0.000001 },
|
| 252 |
+
"inputs": {
|
| 253 |
+
"X": { "dtype": "float16", "shape": [1, 512, 512, 128], "dist": "normal", "seed": 220, "scale": 0.5 },
|
| 254 |
+
"gamma": { "dtype": "float32", "shape": [128], "dist": "uniform", "seed": 221, "scale": 0.25, "offset": 1 },
|
| 255 |
+
"beta": { "dtype": "float32", "shape": [128], "dist": "normal", "seed": 222, "scale": 0.1 }
|
| 256 |
+
},
|
| 257 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [1, 512, 512, 128], "dist": "empty" } },
|
| 258 |
+
"bench": {
|
| 259 |
+
"metrics": [
|
| 260 |
+
{
|
| 261 |
+
"type": "bandwidth",
|
| 262 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 263 |
+
}
|
| 264 |
+
]
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"name": "nchw-1x512x64x64-g32-f16",
|
| 269 |
+
"preset": "smoke",
|
| 270 |
+
"provenance": {
|
| 271 |
+
"note": "Channels-first at the Flux.2 VAE mid-block width: 32 groups, the fewest reduction outputs this op sees"
|
| 272 |
+
},
|
| 273 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 2097152, "channels": 512 },
|
| 274 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 0 },
|
| 275 |
+
"inputs": {
|
| 276 |
+
"X": { "dtype": "float16", "shape": [1, 512, 64, 64], "dist": "normal", "seed": 300, "scale": 0.5 },
|
| 277 |
+
"gamma": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 301, "scale": 0.25, "offset": 1 },
|
| 278 |
+
"beta": { "dtype": "float32", "shape": [512], "dist": "normal", "seed": 302, "scale": 0.1 }
|
| 279 |
+
},
|
| 280 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [1, 512, 64, 64], "dist": "empty" } },
|
| 281 |
+
"bench": {
|
| 282 |
+
"metrics": [
|
| 283 |
+
{
|
| 284 |
+
"type": "bandwidth",
|
| 285 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 286 |
+
}
|
| 287 |
+
]
|
| 288 |
+
}
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
"name": "nchw-8x64x64x64-g32-f32",
|
| 292 |
+
"preset": "smoke",
|
| 293 |
+
"provenance": { "note": "Channels-first with the batch axis carrying the parallelism: 256 reduction outputs" },
|
| 294 |
+
"vars": { "dtype": "float32", "weightDtype": "float32", "count": 2097152, "channels": 64 },
|
| 295 |
+
"attrs": { "groups": 32, "activation": 0, "channels_last": 0 },
|
| 296 |
+
"inputs": {
|
| 297 |
+
"X": { "dtype": "float32", "shape": [8, 64, 64, 64], "dist": "normal", "seed": 310, "scale": 0.5 },
|
| 298 |
+
"gamma": { "dtype": "float32", "shape": [64], "dist": "uniform", "seed": 311, "scale": 0.25, "offset": 1 },
|
| 299 |
+
"beta": { "dtype": "float32", "shape": [64], "dist": "normal", "seed": 312, "scale": 0.1 }
|
| 300 |
+
},
|
| 301 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [8, 64, 64, 64], "dist": "empty" } },
|
| 302 |
+
"bench": {
|
| 303 |
+
"metrics": [
|
| 304 |
+
{
|
| 305 |
+
"type": "bandwidth",
|
| 306 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 307 |
+
}
|
| 308 |
+
]
|
| 309 |
+
}
|
| 310 |
+
},
|
| 311 |
+
{
|
| 312 |
+
"name": "nchw-2x320x128x128-g32-f16",
|
| 313 |
+
"preset": "scale",
|
| 314 |
+
"provenance": { "note": "Channels-first Stable-Diffusion resnet at a working set past cache" },
|
| 315 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 10485760, "channels": 320 },
|
| 316 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 0 },
|
| 317 |
+
"inputs": {
|
| 318 |
+
"X": { "dtype": "float16", "shape": [2, 320, 128, 128], "dist": "normal", "seed": 320, "scale": 0.5 },
|
| 319 |
+
"gamma": { "dtype": "float32", "shape": [320], "dist": "uniform", "seed": 321, "scale": 0.25, "offset": 1 },
|
| 320 |
+
"beta": { "dtype": "float32", "shape": [320], "dist": "normal", "seed": 322, "scale": 0.1 }
|
| 321 |
+
},
|
| 322 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 320, 128, 128], "dist": "empty" } },
|
| 323 |
+
"bench": {
|
| 324 |
+
"metrics": [
|
| 325 |
+
{
|
| 326 |
+
"type": "bandwidth",
|
| 327 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 328 |
+
}
|
| 329 |
+
]
|
| 330 |
+
}
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"name": "nhwc-even-groups-2x64x64x320-g32-f32",
|
| 334 |
+
"preset": "smoke",
|
| 335 |
+
"provenance": {
|
| 336 |
+
"note": "Even channel-group widths spanning one, multiple, and capacity-limited channel rows per workgroup, including float32 activations. Same distributions as the authored large NHWC case."
|
| 337 |
+
},
|
| 338 |
+
"vars": { "dtype": "float32", "weightDtype": "float32", "count": 2621440, "channels": 320 },
|
| 339 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 340 |
+
"inputs": {
|
| 341 |
+
"X": { "dtype": "float32", "shape": [2, 64, 64, 320], "dist": "normal", "seed": 120, "scale": 0.5 },
|
| 342 |
+
"gamma": { "dtype": "float32", "shape": [320], "dist": "uniform", "seed": 121, "scale": 0.25, "offset": 1 },
|
| 343 |
+
"beta": { "dtype": "float32", "shape": [320], "dist": "normal", "seed": 122, "scale": 0.1 }
|
| 344 |
+
},
|
| 345 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [2, 64, 64, 320], "dist": "empty" } },
|
| 346 |
+
"bench": {
|
| 347 |
+
"metrics": [
|
| 348 |
+
{
|
| 349 |
+
"type": "bandwidth",
|
| 350 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 351 |
+
}
|
| 352 |
+
]
|
| 353 |
+
}
|
| 354 |
+
},
|
| 355 |
+
{
|
| 356 |
+
"name": "nhwc-even-groups-2x64x64x64-g32-f16",
|
| 357 |
+
"preset": "smoke",
|
| 358 |
+
"provenance": {
|
| 359 |
+
"note": "Even channel-group widths spanning one, multiple, and capacity-limited channel rows per workgroup, including float32 activations. Same distributions as the authored large NHWC case."
|
| 360 |
+
},
|
| 361 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 524288, "channels": 64 },
|
| 362 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 363 |
+
"inputs": {
|
| 364 |
+
"X": { "dtype": "float16", "shape": [2, 64, 64, 64], "dist": "normal", "seed": 120, "scale": 0.5 },
|
| 365 |
+
"gamma": { "dtype": "float32", "shape": [64], "dist": "uniform", "seed": 121, "scale": 0.25, "offset": 1 },
|
| 366 |
+
"beta": { "dtype": "float32", "shape": [64], "dist": "normal", "seed": 122, "scale": 0.1 }
|
| 367 |
+
},
|
| 368 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 64, 64, 64], "dist": "empty" } },
|
| 369 |
+
"bench": {
|
| 370 |
+
"metrics": [
|
| 371 |
+
{
|
| 372 |
+
"type": "bandwidth",
|
| 373 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 374 |
+
}
|
| 375 |
+
]
|
| 376 |
+
}
|
| 377 |
+
},
|
| 378 |
+
{
|
| 379 |
+
"name": "nhwc-even-groups-2x64x64x192-g32-f16",
|
| 380 |
+
"preset": "smoke",
|
| 381 |
+
"provenance": {
|
| 382 |
+
"note": "Even channel-group widths spanning one, multiple, and capacity-limited channel rows per workgroup, including float32 activations. Same distributions as the authored large NHWC case."
|
| 383 |
+
},
|
| 384 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 1572864, "channels": 192 },
|
| 385 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 386 |
+
"inputs": {
|
| 387 |
+
"X": { "dtype": "float16", "shape": [2, 64, 64, 192], "dist": "normal", "seed": 120, "scale": 0.5 },
|
| 388 |
+
"gamma": { "dtype": "float32", "shape": [192], "dist": "uniform", "seed": 121, "scale": 0.25, "offset": 1 },
|
| 389 |
+
"beta": { "dtype": "float32", "shape": [192], "dist": "normal", "seed": 122, "scale": 0.1 }
|
| 390 |
+
},
|
| 391 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 64, 64, 192], "dist": "empty" } },
|
| 392 |
+
"bench": {
|
| 393 |
+
"metrics": [
|
| 394 |
+
{
|
| 395 |
+
"type": "bandwidth",
|
| 396 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 397 |
+
}
|
| 398 |
+
]
|
| 399 |
+
}
|
| 400 |
+
},
|
| 401 |
+
{
|
| 402 |
+
"name": "nhwc-even-groups-2x64x64x448-g32-f16",
|
| 403 |
+
"preset": "smoke",
|
| 404 |
+
"provenance": {
|
| 405 |
+
"note": "Even channel-group widths spanning one, multiple, and capacity-limited channel rows per workgroup, including float32 activations. Same distributions as the authored large NHWC case."
|
| 406 |
+
},
|
| 407 |
+
"vars": { "dtype": "float16", "weightDtype": "float32", "count": 3670016, "channels": 448 },
|
| 408 |
+
"attrs": { "groups": 32, "activation": 1, "channels_last": 1 },
|
| 409 |
+
"inputs": {
|
| 410 |
+
"X": { "dtype": "float16", "shape": [2, 64, 64, 448], "dist": "normal", "seed": 120, "scale": 0.5 },
|
| 411 |
+
"gamma": { "dtype": "float32", "shape": [448], "dist": "uniform", "seed": 121, "scale": 0.25, "offset": 1 },
|
| 412 |
+
"beta": { "dtype": "float32", "shape": [448], "dist": "normal", "seed": 122, "scale": 0.1 }
|
| 413 |
+
},
|
| 414 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 64, 64, 448], "dist": "empty" } },
|
| 415 |
+
"bench": {
|
| 416 |
+
"metrics": [
|
| 417 |
+
{
|
| 418 |
+
"type": "bandwidth",
|
| 419 |
+
"value": "args.count * 2 * dtypeBytes(args.dtype) + args.channels * 2 * dtypeBytes(args.weightDtype)"
|
| 420 |
+
}
|
| 421 |
+
]
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
]
|
| 425 |
+
}
|
build/webgpu/group-norm-combine.wgsl.jinja
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% set comp0 = ".x" if vecSpec > 1 else "" %}
|
| 2 |
+
{% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
|
| 3 |
+
{% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
|
| 4 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 5 |
+
// per-axis workgroup fold width.
|
| 6 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
|
| 7 |
+
if ({{ name }} >= {{ bound }}) {
|
| 8 |
+
return;
|
| 9 |
+
}{% endmacro %}
|
| 10 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 11 |
+
|
| 12 |
+
// The partials hold sum(value - shift) and sum((value - shift)^2) over disjoint blocks
|
| 13 |
+
// of the group, all taken against the same `shift` — the group's first element — so
|
| 14 |
+
// they add directly. Recovering the mean adds the shift back; the variance is formed
|
| 15 |
+
// from the shifted second moment and clamped at zero, which is where a group whose
|
| 16 |
+
// values are all equal would otherwise land a few ulp below it.
|
| 17 |
+
const GROUPS: u32 = {{ groupsSpec }}u;
|
| 18 |
+
const CPG: u32 = {{ cpgSpec }}u;
|
| 19 |
+
const ROWS: u32 = {{ normRowsSpec }}u;
|
| 20 |
+
const PARTIALS_PER_ROW: u32 = {{ partialsPerRowSpec }}u;
|
| 21 |
+
const GROUP_SIZE: f32 = {{ groupSizeSpec }}.0;
|
| 22 |
+
const EPSILON: f32 = {{ epsilonSpec }};
|
| 23 |
+
const WG: u32 = {{ combineWgSpec }}u;
|
| 24 |
+
{% if phaseLayout %}
|
| 25 |
+
const HW: u32 = {{ dimHWSpec }}u;
|
| 26 |
+
const CHUNKS: u32 = {{ chunksSpec }}u;
|
| 27 |
+
const VEC: u32 = {{ vecSpec }}u;
|
| 28 |
+
{% else %}
|
| 29 |
+
const CHANNELS: u32 = {{ dimCSpec }}u;
|
| 30 |
+
const PLANE_V: u32 = {{ planeVecsSpec }}u;
|
| 31 |
+
{% endif %}
|
| 32 |
+
|
| 33 |
+
@compute @workgroup_size(WG)
|
| 34 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 35 |
+
{{ flat_index_2d("WG", "row", "ROWS") }}
|
| 36 |
+
let n = row / GROUPS;
|
| 37 |
+
let g = row % GROUPS;
|
| 38 |
+
{% if phaseLayout %}
|
| 39 |
+
let shift = f32(src[n * HW * CHUNKS + g * (CPG / VEC)]{{ comp0 }});
|
| 40 |
+
{% else %}
|
| 41 |
+
let shift = f32(src[(n * CHANNELS + g * CPG) * PLANE_V]{{ comp0 }});
|
| 42 |
+
{% endif %}
|
| 43 |
+
var total = vec2<f32>(0.0);
|
| 44 |
+
for (var k = 0u; k < PARTIALS_PER_ROW; k = k + 1u) {
|
| 45 |
+
total = total + partials[row * PARTIALS_PER_ROW + k];
|
| 46 |
+
}
|
| 47 |
+
let mean_d = total.x / GROUP_SIZE;
|
| 48 |
+
let variance = max(total.y / GROUP_SIZE - mean_d * mean_d, 0.0);
|
| 49 |
+
stats[row] = vec2<f32>(shift + mean_d, inverseSqrt(variance + EPSILON));
|
| 50 |
+
}
|
build/webgpu/group-norm-fused.wgsl.jinja
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% set comp0 = ".x" if useVec4 else "" %}
|
| 2 |
+
{% macro pre_norm(idx, sidx) %}
|
| 3 |
+
{% set x = vecF32 ~ "(X[" ~ idx ~ "])" %}
|
| 4 |
+
{% set skipTerm = "skip_v" if broadcastSkip else vecF32 ~ "(skip[" ~ sidx ~ "])" %}
|
| 5 |
+
{{ vecF32 ~ "(" ~ storeElement ~ "(" ~ x ~ " + " ~ skipTerm ~ " + bias_v))" if hasSkip else x }}{% endmacro %}
|
| 6 |
+
{% macro shift_at(idx, sidx, ch) %}
|
| 7 |
+
{% set x = "f32(X[" ~ idx ~ "]" ~ comp0 ~ ")" %}
|
| 8 |
+
{% set skipTerm = "f32(skip[" ~ sidx ~ "]" ~ ("" if broadcastSkip else comp0) ~ ")" %}
|
| 9 |
+
{% set biasTerm = " + f32(bias[" ~ ch ~ "])" if hasBias else "" %}
|
| 10 |
+
{{ "f32(" ~ tScalar ~ "(" ~ x ~ " + " ~ skipTerm ~ biasTerm ~ "))" if hasSkip else x }}{% endmacro %}
|
| 11 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 12 |
+
|
| 13 |
+
// Group normalization in one dispatch. The workgroup accumulates the group's shifted
|
| 14 |
+
// moments, folds them through workgroup memory, then walks the same elements again to
|
| 15 |
+
// write `gamma * (value - mean) * inv_std + beta`. Every intermediate is f32 whatever
|
| 16 |
+
// the tensor type; only the store narrows. The reduction is a plain halving tree over
|
| 17 |
+
// a power-of-two-padded scratch array, so it is deterministic and needs no subgroup
|
| 18 |
+
// support.
|
| 19 |
+
const CPG: u32 = {{ cpgSpec }}u;
|
| 20 |
+
const GROUPS: u32 = {{ groupsSpec }}u;
|
| 21 |
+
const ROWS: u32 = {{ normRowsSpec }}u;
|
| 22 |
+
const GROUP_SIZE: f32 = {{ groupSizeSpec }}.0;
|
| 23 |
+
const EPSILON: f32 = {{ epsilonSpec }};
|
| 24 |
+
const WG: u32 = {{ wgSpec }}u;
|
| 25 |
+
const LANES: u32 = {{ ldsSpec }}u;
|
| 26 |
+
{% if phaseLayout %}
|
| 27 |
+
const HW: u32 = {{ dimHWSpec }}u;
|
| 28 |
+
const VEC: u32 = {{ vecSpec }}u;
|
| 29 |
+
const CHUNKS: u32 = {{ chunksSpec }}u;
|
| 30 |
+
const SEGMENT: u32 = {{ fusedChunksSpec }}u;
|
| 31 |
+
const SUB_ROWS: u32 = {{ fusedRowsSpec }}u;
|
| 32 |
+
{% else %}
|
| 33 |
+
const CHANNELS: u32 = {{ dimCSpec }}u;
|
| 34 |
+
const PLANE_V: u32 = {{ planeVecsSpec }}u;
|
| 35 |
+
const SPAN_V: u32 = {{ spanVecsSpec }}u;
|
| 36 |
+
{% endif %}
|
| 37 |
+
|
| 38 |
+
// LANES is WG rounded up to a power of two; the lanes past WG are zeroed so the
|
| 39 |
+
// halving tree below stays uniform.
|
| 40 |
+
var<workgroup> lane_sums: array<vec2<f32>, LANES>;
|
| 41 |
+
{% if activationSpec == 1 %}
|
| 42 |
+
|
| 43 |
+
fn silu_e(v: {{ vecF32 }}) -> {{ vecF32 }} {
|
| 44 |
+
return v / ({{ vecF32 }}(1.0) + exp(-v));
|
| 45 |
+
}
|
| 46 |
+
{% endif %}
|
| 47 |
+
|
| 48 |
+
@compute @workgroup_size(WG)
|
| 49 |
+
fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
| 50 |
+
// 2D-folded row index: wg.y carries the high bits past the dispatch's per-axis
|
| 51 |
+
// workgroup fold width.
|
| 52 |
+
let row = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 53 |
+
if (row >= ROWS) {
|
| 54 |
+
return;
|
| 55 |
+
}
|
| 56 |
+
let n = row / GROUPS;
|
| 57 |
+
let g = row - n * GROUPS;
|
| 58 |
+
{% if phaseLayout %}
|
| 59 |
+
let chunk = lid.x % SEGMENT;
|
| 60 |
+
let sub_row = lid.x / SEGMENT;
|
| 61 |
+
let c0 = g * CPG + chunk * VEC;
|
| 62 |
+
let base_v = n * HW * CHUNKS + g * SEGMENT;
|
| 63 |
+
let shift_index = base_v;
|
| 64 |
+
{% else %}
|
| 65 |
+
let base_v = (n * CHANNELS + g * CPG) * PLANE_V;
|
| 66 |
+
let shift_index = base_v;
|
| 67 |
+
{% endif %}
|
| 68 |
+
let shift = {{ shift_at("shift_index", "shift_skip", "g * CPG") }};
|
| 69 |
+
var acc = vec2<f32>(0.0);
|
| 70 |
+
{% if phaseLayout %}
|
| 71 |
+
for (var hw = sub_row; hw < HW; hw = hw + SUB_ROWS) {
|
| 72 |
+
let idx = base_v + hw * CHUNKS + chunk;
|
| 73 |
+
let v = {{ pre_norm("idx", "skip_base + hw * CHUNKS + chunk") }};
|
| 74 |
+
{% else %}
|
| 75 |
+
for (var i = lid.x; i < SPAN_V; i = i + WG) {
|
| 76 |
+
let idx = base_v + i;
|
| 77 |
+
let v = {{ pre_norm("idx", "skip_base + i") }};
|
| 78 |
+
{% endif %}
|
| 79 |
+
let d = v - {{ vecF32 }}(shift);
|
| 80 |
+
{% if useVec4 %}
|
| 81 |
+
acc = acc + vec2<f32>(d.x + d.y + d.z + d.w, dot(d, d));
|
| 82 |
+
{% else %}
|
| 83 |
+
acc = acc + vec2<f32>(d, d * d);
|
| 84 |
+
{% endif %}
|
| 85 |
+
}
|
| 86 |
+
lane_sums[lid.x] = acc;
|
| 87 |
+
if (lid.x + WG < LANES) {
|
| 88 |
+
lane_sums[lid.x + WG] = vec2<f32>(0.0);
|
| 89 |
+
}
|
| 90 |
+
workgroupBarrier();
|
| 91 |
+
var stride = LANES / 2u;
|
| 92 |
+
loop {
|
| 93 |
+
if (stride == 0u) {
|
| 94 |
+
break;
|
| 95 |
+
}
|
| 96 |
+
if (lid.x < stride) {
|
| 97 |
+
lane_sums[lid.x] = lane_sums[lid.x] + lane_sums[lid.x + stride];
|
| 98 |
+
}
|
| 99 |
+
workgroupBarrier();
|
| 100 |
+
stride = stride / 2u;
|
| 101 |
+
}
|
| 102 |
+
let total = lane_sums[0];
|
| 103 |
+
let mean_d = total.x / GROUP_SIZE;
|
| 104 |
+
let variance = max(total.y / GROUP_SIZE - mean_d * mean_d, 0.0);
|
| 105 |
+
let mean = {{ vecF32 }}(shift + mean_d);
|
| 106 |
+
let inv_std = {{ vecF32 }}(inverseSqrt(variance + EPSILON));
|
| 107 |
+
{% if phaseLayout %}
|
| 108 |
+
{% if useVec4 %}
|
| 109 |
+
let gamma_v = vec4<f32>(f32(gamma[c0]), f32(gamma[c0 + 1u]), f32(gamma[c0 + 2u]), f32(gamma[c0 + 3u]));
|
| 110 |
+
let beta_v = vec4<f32>(f32(beta[c0]), f32(beta[c0 + 1u]), f32(beta[c0 + 2u]), f32(beta[c0 + 3u]));
|
| 111 |
+
{% else %}
|
| 112 |
+
let gamma_v = f32(gamma[c0]);
|
| 113 |
+
let beta_v = f32(beta[c0]);
|
| 114 |
+
{% endif %}
|
| 115 |
+
for (var hw = sub_row; hw < HW; hw = hw + SUB_ROWS) {
|
| 116 |
+
let idx = base_v + hw * CHUNKS + chunk;
|
| 117 |
+
{% else %}
|
| 118 |
+
for (var i = lid.x; i < SPAN_V; i = i + WG) {
|
| 119 |
+
let idx = base_v + i;
|
| 120 |
+
let channel = g * CPG + i / PLANE_V;
|
| 121 |
+
{% if useVec4 %}
|
| 122 |
+
let gamma_v = vec4<f32>(f32(gamma[channel]));
|
| 123 |
+
let beta_v = vec4<f32>(f32(beta[channel]));
|
| 124 |
+
{% else %}
|
| 125 |
+
let gamma_v = f32(gamma[channel]);
|
| 126 |
+
let beta_v = f32(beta[channel]);
|
| 127 |
+
{% endif %}
|
| 128 |
+
{% endif %}
|
| 129 |
+
let normalized = ({{ vecF32 }}({{ fusedSrc }}[idx]) - mean) * inv_std;
|
| 130 |
+
{% if activationSpec == 1 %}
|
| 131 |
+
Y[idx] = {{ storeElement }}(silu_e(gamma_v * normalized + beta_v));
|
| 132 |
+
{% else %}
|
| 133 |
+
Y[idx] = {{ storeElement }}(gamma_v * normalized + beta_v);
|
| 134 |
+
{% endif %}
|
| 135 |
+
}
|
| 136 |
+
}
|
build/webgpu/group-norm-slab.wgsl.jinja
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% set blockedStats = phase == "stats" and phaseLayout and (statsBlockedSpec | default(false)) %}
|
| 2 |
+
{% set useVector = vecSpec > 1 %}
|
| 3 |
+
{% set comp0 = ".x" if useVector else "" %}
|
| 4 |
+
{% macro channel_values(buffer, channel) %}
|
| 5 |
+
{{ vecF32 }}({% for i in range(vecSpec) %}f32({{ buffer }}[{{ channel }}{% if i > 0 %} + {{ i }}u{% endif %}]){% if not loop.last %}, {% endif %}{% endfor %}){% endmacro %}
|
| 6 |
+
{% macro component_sum(value) %}
|
| 7 |
+
{% for i in range(vecSpec) %}{{ value }}.{{ "xyzw"[i] }}{% if not loop.last %} + {% endif %}{% endfor %}{% endmacro %}
|
| 8 |
+
{% macro pre_norm(idx, sidx) %}
|
| 9 |
+
{% set x = vecF32 ~ "(X[" ~ idx ~ "])" %}
|
| 10 |
+
{% set skipTerm = "skip_v" if broadcastSkip else vecF32 ~ "(skip[" ~ sidx ~ "])" %}
|
| 11 |
+
{{ vecF32 ~ "(" ~ storeElement ~ "(" ~ x ~ " + " ~ skipTerm ~ " + bias_v))" if hasSkip else x }}{% endmacro %}
|
| 12 |
+
{% macro shift_at(idx, sidx, ch) %}
|
| 13 |
+
{% set x = "f32(X[" ~ idx ~ "]" ~ comp0 ~ ")" %}
|
| 14 |
+
{% set skipTerm = "f32(skip[" ~ sidx ~ "]" ~ ("" if broadcastSkip else comp0) ~ ")" %}
|
| 15 |
+
{% set biasTerm = " + f32(bias[" ~ ch ~ "])" if hasBias else "" %}
|
| 16 |
+
{{ "f32(" ~ tScalar ~ "(" ~ x ~ " + " ~ skipTerm ~ biasTerm ~ "))" if hasSkip else x }}{% endmacro %}
|
| 17 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 18 |
+
|
| 19 |
+
// One phase of group normalization over a 4-D image tensor.
|
| 20 |
+
//
|
| 21 |
+
// A group is every spatial position of `CPG` adjacent channels of one batch item. The
|
| 22 |
+
// statistics phase accumulates the sum and the sum of squares of `value - shift`,
|
| 23 |
+
// where `shift` is the group's first element: subtracting one member of the group
|
| 24 |
+
// removes the common offset before the second moment is formed, so the variance the
|
| 25 |
+
// combine pass derives does not cancel against a large mean. The apply phase reads the
|
| 26 |
+
// combined mean and reciprocal standard deviation back and writes
|
| 27 |
+
// `gamma * (value - mean) * inv_std + beta`, optionally through SiLU.
|
| 28 |
+
//
|
| 29 |
+
// Every accumulation and the whole affine are f32 whatever the tensor type; only the
|
| 30 |
+
// store narrows.
|
| 31 |
+
const CPG: u32 = {{ cpgSpec }}u;
|
| 32 |
+
const GROUPS: u32 = {{ groupsSpec }}u;
|
| 33 |
+
const WG: u32 = {{ wgSpec }}u;
|
| 34 |
+
const UNITS: u32 = {{ unitsSpec }}u;
|
| 35 |
+
{% if phaseLayout %}
|
| 36 |
+
const HW: u32 = {{ dimHWSpec }}u;
|
| 37 |
+
const VEC: u32 = {{ vecSpec }}u;
|
| 38 |
+
const CHUNKS: u32 = {{ chunksSpec }}u;
|
| 39 |
+
const ROWS_PER_STEP: u32 = {{ rowsPerStepSpec }}u;
|
| 40 |
+
const ROWS_PER_SLAB: u32 = {{ rowsPerSlabSpec }}u;
|
| 41 |
+
const SLABS: u32 = {{ splitSpec }}u;
|
| 42 |
+
{% if blockedStats %}
|
| 43 |
+
const GROUP_BLOCKS: u32 = {{ statsGroupBlocksSpec }}u;
|
| 44 |
+
const GROUPS_PER_BLOCK: u32 = {{ statsGroupsPerBlockSpec }}u;
|
| 45 |
+
const TILE_CHUNKS: u32 = {{ statsTileChunksSpec }}u;
|
| 46 |
+
{% endif %}
|
| 47 |
+
{% else %}
|
| 48 |
+
const CHANNELS: u32 = {{ dimCSpec }}u;
|
| 49 |
+
const PLANE_V: u32 = {{ planeVecsSpec }}u;
|
| 50 |
+
const CHUNK_V: u32 = {{ chunkVecsSpec }}u;
|
| 51 |
+
const SPLITS: u32 = {{ splitSpec }}u;
|
| 52 |
+
{% endif %}
|
| 53 |
+
{% if phase == "stats" %}
|
| 54 |
+
{% if not phaseLayout %}
|
| 55 |
+
const PARTIALS_PER_ROW: u32 = {{ partialsPerRowSpec }}u;
|
| 56 |
+
{% endif %}
|
| 57 |
+
|
| 58 |
+
// Per-lane moments, combined below: the channels-last arm folds one contiguous
|
| 59 |
+
// segment per group out of this array, the channels-first arm folds all of it.
|
| 60 |
+
var<workgroup> lane_sums: array<vec2<f32>, WG>;
|
| 61 |
+
{% endif %}
|
| 62 |
+
{% if phase == "apply" and activationSpec == 1 %}
|
| 63 |
+
|
| 64 |
+
fn silu_e(v: {{ vecF32 }}) -> {{ vecF32 }} {
|
| 65 |
+
return v / ({{ vecF32 }}(1.0) + exp(-v));
|
| 66 |
+
}
|
| 67 |
+
{% endif %}
|
| 68 |
+
|
| 69 |
+
@compute @workgroup_size(WG)
|
| 70 |
+
fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
| 71 |
+
// 2D-folded work-unit index: wg.y carries the high bits past the dispatch's
|
| 72 |
+
// per-axis workgroup fold width.
|
| 73 |
+
let unit = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 74 |
+
if (unit >= UNITS) {
|
| 75 |
+
return;
|
| 76 |
+
}
|
| 77 |
+
{% if phaseLayout %}
|
| 78 |
+
// Channels last. The workgroup owns one batch item and one slab of spatial rows and
|
| 79 |
+
// covers every channel of those rows: lane `lid.x` keeps channel chunk
|
| 80 |
+
// `lid.x % CHUNKS` and steps through the slab `ROWS_PER_STEP` rows at a time, so the
|
| 81 |
+
// workgroup reads whole contiguous channel rows and each lane's group is fixed.
|
| 82 |
+
{% if blockedStats %}
|
| 83 |
+
let spatial_unit = unit / GROUP_BLOCKS;
|
| 84 |
+
let group_begin = (unit % GROUP_BLOCKS) * GROUPS_PER_BLOCK;
|
| 85 |
+
let n = spatial_unit / SLABS;
|
| 86 |
+
let slab = spatial_unit - n * SLABS;
|
| 87 |
+
let chunk = group_begin * (CPG / VEC) + lid.x % TILE_CHUNKS;
|
| 88 |
+
let sub_row = lid.x / TILE_CHUNKS;
|
| 89 |
+
{% else %}
|
| 90 |
+
let n = unit / SLABS;
|
| 91 |
+
let slab = unit - n * SLABS;
|
| 92 |
+
let chunk = lid.x % CHUNKS;
|
| 93 |
+
let sub_row = lid.x / CHUNKS;
|
| 94 |
+
{% endif %}
|
| 95 |
+
let c0 = chunk * VEC;
|
| 96 |
+
{% if phase == "stats" or groupUniform %}
|
| 97 |
+
let g = c0 / CPG;
|
| 98 |
+
{% endif %}
|
| 99 |
+
let image_base = n * HW * CHUNKS;
|
| 100 |
+
let hw_begin = slab * ROWS_PER_SLAB;
|
| 101 |
+
let hw_end = min(hw_begin + ROWS_PER_SLAB, HW);
|
| 102 |
+
{% else %}
|
| 103 |
+
// Channels first. The workgroup owns one contiguous chunk of one (batch item,
|
| 104 |
+
// channel) plane, so every lane shares the channel and the group and the loads are
|
| 105 |
+
// contiguous across the workgroup. Adjacent units are adjacent chunks of one plane.
|
| 106 |
+
let plane = unit / SPLITS;
|
| 107 |
+
let part = unit - plane * SPLITS;
|
| 108 |
+
let n = plane / CHANNELS;
|
| 109 |
+
let c = plane % CHANNELS;
|
| 110 |
+
let g = c / CPG;
|
| 111 |
+
let base_v = plane * PLANE_V;
|
| 112 |
+
let chunk_begin = part * CHUNK_V;
|
| 113 |
+
let chunk_end = min(chunk_begin + CHUNK_V, PLANE_V);
|
| 114 |
+
{% endif %}
|
| 115 |
+
{% if phase == "stats" %}
|
| 116 |
+
{% if blockedStats %}
|
| 117 |
+
var acc = vec2<f32>(0.0);
|
| 118 |
+
// Keep tail lanes participating in the barrier without reading outside the group.
|
| 119 |
+
if (g < GROUPS) {
|
| 120 |
+
{% endif %}
|
| 121 |
+
{% if phaseLayout %}
|
| 122 |
+
let shift_index = image_base + g * (CPG / VEC);
|
| 123 |
+
{% else %}
|
| 124 |
+
let shift_index = (n * CHANNELS + g * CPG) * PLANE_V;
|
| 125 |
+
{% endif %}
|
| 126 |
+
let shift = {{ shift_at("shift_index", "shift_skip", "g * CPG") }};
|
| 127 |
+
{% if not blockedStats %}
|
| 128 |
+
var acc = vec2<f32>(0.0);
|
| 129 |
+
{% endif %}
|
| 130 |
+
{% if phaseLayout %}
|
| 131 |
+
for (var hw = hw_begin + sub_row; hw < hw_end; hw = hw + ROWS_PER_STEP) {
|
| 132 |
+
let idx = image_base + hw * CHUNKS + chunk;
|
| 133 |
+
let v = {{ pre_norm("idx", "skip_base + hw * CHUNKS + chunk") }};
|
| 134 |
+
{% else %}
|
| 135 |
+
for (var i = chunk_begin + lid.x; i < chunk_end; i = i + WG) {
|
| 136 |
+
let idx = base_v + i;
|
| 137 |
+
let v = {{ pre_norm("idx", "skip_base + i") }};
|
| 138 |
+
{% endif %}
|
| 139 |
+
let d = v - {{ vecF32 }}(shift);
|
| 140 |
+
{% if useVector %}
|
| 141 |
+
acc = acc + vec2<f32>({{ component_sum("d") }}, dot(d, d));
|
| 142 |
+
{% else %}
|
| 143 |
+
acc = acc + vec2<f32>(d, d * d);
|
| 144 |
+
{% endif %}
|
| 145 |
+
}
|
| 146 |
+
{% if blockedStats %}
|
| 147 |
+
}
|
| 148 |
+
{% endif %}
|
| 149 |
+
lane_sums[lid.x] = acc;
|
| 150 |
+
workgroupBarrier();
|
| 151 |
+
{% if phaseLayout %}
|
| 152 |
+
// One aligned segment of `CPG / VEC` lanes per group per sub-row. Each group's
|
| 153 |
+
// partial is summed in a fixed order by a single lane, so the value does not depend
|
| 154 |
+
// on how the workgroup was scheduled.
|
| 155 |
+
let segment = CPG / VEC;
|
| 156 |
+
for (var sel = lid.x; sel < {% if blockedStats %}min(GROUPS_PER_BLOCK, GROUPS - group_begin){% else %}GROUPS{% endif %}; sel = sel + WG) {
|
| 157 |
+
var total = vec2<f32>(0.0);
|
| 158 |
+
for (var r = 0u; r < ROWS_PER_STEP; r = r + 1u) {
|
| 159 |
+
let seg_base = r * {% if blockedStats %}TILE_CHUNKS{% else %}CHUNKS{% endif %} + sel * segment;
|
| 160 |
+
for (var t = 0u; t < segment; t = t + 1u) {
|
| 161 |
+
total = total + lane_sums[seg_base + t];
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
partials[(n * GROUPS + {% if blockedStats %}group_begin + {% endif %}sel) * SLABS + slab] = total;
|
| 165 |
+
}
|
| 166 |
+
{% else %}
|
| 167 |
+
// WG is a power of two on this arm, so the halving stride is uniform across the
|
| 168 |
+
// workgroup and every lane reaches every barrier.
|
| 169 |
+
var stride = WG / 2u;
|
| 170 |
+
loop {
|
| 171 |
+
if (stride == 0u) {
|
| 172 |
+
break;
|
| 173 |
+
}
|
| 174 |
+
if (lid.x < stride) {
|
| 175 |
+
lane_sums[lid.x] = lane_sums[lid.x] + lane_sums[lid.x + stride];
|
| 176 |
+
}
|
| 177 |
+
workgroupBarrier();
|
| 178 |
+
stride = stride / 2u;
|
| 179 |
+
}
|
| 180 |
+
if (lid.x == 0u) {
|
| 181 |
+
partials[(n * GROUPS + g) * PARTIALS_PER_ROW + (c - g * CPG) * SPLITS + part] = lane_sums[0];
|
| 182 |
+
}
|
| 183 |
+
{% endif %}
|
| 184 |
+
{% else %}
|
| 185 |
+
{% if groupUniform %}
|
| 186 |
+
let st = stats[n * GROUPS + g];
|
| 187 |
+
let mean = {{ vecF32 }}(st.x);
|
| 188 |
+
let inv_std = {{ vecF32 }}(st.y);
|
| 189 |
+
{% else %}
|
| 190 |
+
// The four channels this lane holds do not all belong to one group, which is what
|
| 191 |
+
// `channels_per_group % 4 != 0` means. The statistics are still read once per lane,
|
| 192 |
+
// as a component-wise gather over at most two adjacent groups.
|
| 193 |
+
let row_base = n * GROUPS;
|
| 194 |
+
let s0 = stats[row_base + c0 / CPG];
|
| 195 |
+
let s1 = stats[row_base + (c0 + 1u) / CPG];
|
| 196 |
+
let s2 = stats[row_base + (c0 + 2u) / CPG];
|
| 197 |
+
let s3 = stats[row_base + (c0 + 3u) / CPG];
|
| 198 |
+
let mean = vec4<f32>(s0.x, s1.x, s2.x, s3.x);
|
| 199 |
+
let inv_std = vec4<f32>(s0.y, s1.y, s2.y, s3.y);
|
| 200 |
+
{% endif %}
|
| 201 |
+
{% if useVector %}
|
| 202 |
+
{% if phaseLayout %}
|
| 203 |
+
let gamma_v = {{ channel_values("gamma", "c0") }};
|
| 204 |
+
let beta_v = {{ channel_values("beta", "c0") }};
|
| 205 |
+
{% else %}
|
| 206 |
+
let gamma_v = {{ vecF32 }}(f32(gamma[c]));
|
| 207 |
+
let beta_v = {{ vecF32 }}(f32(beta[c]));
|
| 208 |
+
{% endif %}
|
| 209 |
+
{% else %}
|
| 210 |
+
{% if phaseLayout %}
|
| 211 |
+
let gamma_v = f32(gamma[c0]);
|
| 212 |
+
let beta_v = f32(beta[c0]);
|
| 213 |
+
{% else %}
|
| 214 |
+
let gamma_v = f32(gamma[c]);
|
| 215 |
+
let beta_v = f32(beta[c]);
|
| 216 |
+
{% endif %}
|
| 217 |
+
{% endif %}
|
| 218 |
+
{% if phaseLayout %}
|
| 219 |
+
for (var hw = hw_begin + sub_row; hw < hw_end; hw = hw + ROWS_PER_STEP) {
|
| 220 |
+
let idx = image_base + hw * CHUNKS + chunk;
|
| 221 |
+
{% else %}
|
| 222 |
+
for (var i = chunk_begin + lid.x; i < chunk_end; i = i + WG) {
|
| 223 |
+
let idx = base_v + i;
|
| 224 |
+
{% endif %}
|
| 225 |
+
let normalized = ({{ vecF32 }}({{ applySrc }}[idx]) - mean) * inv_std;
|
| 226 |
+
{% if activationSpec == 1 %}
|
| 227 |
+
Y[idx] = {{ storeElement }}(silu_e(gamma_v * normalized + beta_v));
|
| 228 |
+
{% else %}
|
| 229 |
+
Y[idx] = {{ storeElement }}(gamma_v * normalized + beta_v);
|
| 230 |
+
{% endif %}
|
| 231 |
+
}
|
| 232 |
+
{% endif %}
|
| 233 |
+
}
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "com.microsoft",
|
| 3 |
+
"name": "GroupNorm",
|
| 4 |
+
"sinceVersion": 1,
|
| 5 |
+
"inputs": {
|
| 6 |
+
"X": { "dtype": "T", "rank": 4 },
|
| 7 |
+
"gamma": { "dtype": "M", "rank": 1 },
|
| 8 |
+
"beta": { "dtype": "M", "rank": 1 }
|
| 9 |
+
},
|
| 10 |
+
"outputs": { "Y": { "dtype": "T", "rank": 4, "shape": "shapes.X" } },
|
| 11 |
+
"attributes": { "activation": {}, "groups": {}, "channels_last": { "default": 1 }, "epsilon": { "default": 0.00001 } },
|
| 12 |
+
"attributeConstraints": { "activation": { "required": true, "values": [0, 1] }, "groups": { "required": true } },
|
| 13 |
+
"typeConstraints": { "T": ["float32", "float16"], "M": ["float32", "float16"] },
|
| 14 |
+
"tunables": {
|
| 15 |
+
"WORKGROUP_SIZE": { "default": 256 },
|
| 16 |
+
"MAX_SPLITS": { "default": 256 },
|
| 17 |
+
"VALUES_PER_SPLIT": { "default": 4096 },
|
| 18 |
+
"SPLIT_MIN_ELEMENTS": { "default": 262144 },
|
| 19 |
+
"APPLY_SLAB_ELEMENTS": { "default": 8192 },
|
| 20 |
+
"FUSED_MIN_ROWS": { "default": 64 },
|
| 21 |
+
"FUSED_MAX_GROUP_BYTES": { "default": 131072 }
|
| 22 |
+
},
|
| 23 |
+
"derive": {
|
| 24 |
+
"attrsPresent": "has(attrs, \"groups\") and has(attrs, \"activation\")",
|
| 25 |
+
"numGroups": "attrs.groups if attrsPresent else 0",
|
| 26 |
+
"activationKind": "attrs.activation if attrsPresent else 0",
|
| 27 |
+
"channelsLast": "attrs.channels_last != 0",
|
| 28 |
+
"rankOk": "ranks.X == 4 and ranks.Y == 4 and ranks.gamma == 1 and ranks.beta == 1",
|
| 29 |
+
"dimN": "dim(shapes.X, 0) if rankOk else 0",
|
| 30 |
+
"dimC": "(dim(shapes.X, 3) if channelsLast else dim(shapes.X, 1)) if rankOk else 0",
|
| 31 |
+
"dimHW": "(dim(shapes.X, 1) * dim(shapes.X, 2) if channelsLast else dim(shapes.X, 2) * dim(shapes.X, 3)) if rankOk else 0",
|
| 32 |
+
"groupsOk": "numGroups >= 1 and dimC >= 1 and dimC % numGroups == 0",
|
| 33 |
+
"channelsPerGroup": "dimC / numGroups if groupsOk else 0",
|
| 34 |
+
"planeCount": "dimN * dimC",
|
| 35 |
+
"groupSize": "channelsPerGroup * dimHW",
|
| 36 |
+
"normRows": "dimN * numGroups",
|
| 37 |
+
"elementCount": "numel(shapes.X) if rankOk else 0",
|
| 38 |
+
"shapeOk": "rankOk and groupsOk and dimN > 0 and dimHW > 0 and groupSize > 0 and sameShape(shapes.Y, shapes.X) and dim(shapes.gamma, 0) == dimC and dim(shapes.beta, 0) == dimC",
|
| 39 |
+
"deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
|
| 40 |
+
"storageBufferLimit": "min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
|
| 41 |
+
"foldedDispatchCapacity": "min(device.limits.maxComputeWorkgroupsPerDimension, 65535) * min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
|
| 42 |
+
"statsVec4Ok": "(channelsPerGroup % 4 == 0 and dimC % 4 == 0) if channelsLast else (dimHW % 4 == 0)",
|
| 43 |
+
"applyVec4Ok": "(dimC % 4 == 0) if channelsLast else (dimHW % 4 == 0)",
|
| 44 |
+
"fusedVecWidth": "4 if statsVec4Ok else 1",
|
| 45 |
+
"statsVecWidth": "4 if statsVec4Ok else (2 if channelsLast and channelsPerGroup % 2 == 0 else 1)",
|
| 46 |
+
"applyVecWidth": "4 if applyVec4Ok else 1",
|
| 47 |
+
"workgroupCap": "max(1, floor(pow2ceil(min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap) + 1) / 2))",
|
| 48 |
+
"statsChunks": "max(1, floor(dimC / statsVecWidth))",
|
| 49 |
+
"fusedRowChunks": "max(1, floor(dimC / fusedVecWidth))",
|
| 50 |
+
"statsSegment": "max(1, floor(channelsPerGroup / statsVecWidth))",
|
| 51 |
+
"statsGroupsPerBlock": "max(1, min(numGroups, floor(workgroupCap / statsSegment))) if channelsLast and statsChunks > deviceWorkgroupCap and statsSegment <= workgroupCap else max(1, numGroups)",
|
| 52 |
+
"statsGroupBlocks": "ceilDiv(max(1, numGroups), statsGroupsPerBlock)",
|
| 53 |
+
"statsTileChunks": "statsGroupsPerBlock * statsSegment if statsGroupBlocks > 1 else statsChunks",
|
| 54 |
+
"applyChunks": "max(1, floor(dimC / applyVecWidth))",
|
| 55 |
+
"statsRowsPerStep": "max(1, floor(workgroupCap / statsTileChunks))",
|
| 56 |
+
"applyRowsPerStep": "max(1, floor(workgroupCap / applyChunks))",
|
| 57 |
+
"planeVecs": "max(1, floor(dimHW / statsVecWidth))",
|
| 58 |
+
"slabCount": "ceilDiv(dimHW, ceilDiv(dimHW, max(1, min(tunables.MAX_SPLITS, dimHW, ceilDiv(dimHW * dimC, tunables.VALUES_PER_SPLIT)))))",
|
| 59 |
+
"planeSplits": "max(1, min(tunables.MAX_SPLITS, planeVecs, ceilDiv(dimHW, tunables.VALUES_PER_SPLIT)))",
|
| 60 |
+
"splitCount": "slabCount if channelsLast else planeSplits",
|
| 61 |
+
"partialsPerRow": "slabCount if channelsLast else max(1, channelsPerGroup) * planeSplits",
|
| 62 |
+
"partialElements": "max(1, normRows * partialsPerRow)",
|
| 63 |
+
"statsElements": "max(1, normRows)",
|
| 64 |
+
"statsWorkgroup": "(statsTileChunks * statsRowsPerStep) if channelsLast else min(workgroupCap, max(32, pow2ceil(ceilDiv(planeVecs, planeSplits))))",
|
| 65 |
+
"applyWorkgroup": "(applyChunks * applyRowsPerStep) if channelsLast else statsWorkgroup",
|
| 66 |
+
"applySlabCount": "ceilDiv(dimHW, ceilDiv(dimHW, max(1, min(dimHW, min(device.limits.maxComputeWorkgroupsPerDimension, 65535), ceilDiv(elementCount, tunables.APPLY_SLAB_ELEMENTS)))))",
|
| 67 |
+
"applySplit": "applySlabCount if channelsLast else planeSplits",
|
| 68 |
+
"applyUnits": "(dimN * applySlabCount) if channelsLast else (planeCount * planeSplits)",
|
| 69 |
+
"workUnits": "(dimN * slabCount * statsGroupBlocks) if channelsLast else (planeCount * planeSplits)",
|
| 70 |
+
"splitFits": "statsWorkgroup <= deviceWorkgroupCap and applyWorkgroup <= deviceWorkgroupCap and statsWorkgroup * 8 <= device.limits.maxComputeWorkgroupStorageSize and partialElements * 8 <= storageBufferLimit and workUnits > 0 and workUnits <= foldedDispatchCapacity and applyUnits <= foldedDispatchCapacity",
|
| 71 |
+
"fusedChunks": "max(1, floor(channelsPerGroup / fusedVecWidth)) if channelsLast else 1",
|
| 72 |
+
"fusedWorkgroup": "(fusedChunks * max(1, floor(workgroupCap / fusedChunks))) if channelsLast else min(workgroupCap, max(32, pow2ceil(max(1, floor(groupSize / fusedVecWidth)))))",
|
| 73 |
+
"fusedLanes": "pow2ceil(fusedWorkgroup)",
|
| 74 |
+
"fusedFits": "fusedWorkgroup <= deviceWorkgroupCap and fusedLanes * 8 <= device.limits.maxComputeWorkgroupStorageSize and normRows <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) * min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
|
| 75 |
+
"combineWorkgroup": "min(workgroupCap, 64)",
|
| 76 |
+
"statsElement": "(\"vec\" ~ statsVecWidth ~ \"<\" ~ dtypes.T ~ \">\") if statsVecWidth > 1 else dtypes.T",
|
| 77 |
+
"fusedElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if statsVec4Ok else dtypes.T",
|
| 78 |
+
"applyElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if applyVec4Ok else dtypes.T",
|
| 79 |
+
"mScalar": "dtypes.M",
|
| 80 |
+
"tScalar": "dtypes.T",
|
| 81 |
+
"contractOk": "shapeOk and f16Ok(dtypes.T) and f16Ok(dtypes.M)"
|
| 82 |
+
},
|
| 83 |
+
"when": ["contractOk"],
|
| 84 |
+
"bindings": {
|
| 85 |
+
"xIn": { "arg": "X", "name": "X", "elementType": "$statsElement" },
|
| 86 |
+
"srcX": { "arg": "X", "name": "src", "elementType": "$statsElement" },
|
| 87 |
+
"gamma": { "elementType": "$mScalar" },
|
| 88 |
+
"beta": { "elementType": "$mScalar" },
|
| 89 |
+
"yOut": { "arg": "Y", "name": "Y", "elementType": "$applyElement" },
|
| 90 |
+
"partialsW": { "name": "partials", "buffer": "storage", "elementType": "vec2<f32>" },
|
| 91 |
+
"partialsR": { "name": "partials", "buffer": "read-only-storage", "elementType": "vec2<f32>" },
|
| 92 |
+
"statsW": { "name": "stats", "buffer": "storage", "elementType": "vec2<f32>" },
|
| 93 |
+
"statsR": { "name": "stats", "buffer": "read-only-storage", "elementType": "vec2<f32>" },
|
| 94 |
+
"srcApply": { "arg": "X", "name": "src", "elementType": "$applyElement" },
|
| 95 |
+
"yFused": { "arg": "Y", "name": "Y", "elementType": "$statsElement" }
|
| 96 |
+
},
|
| 97 |
+
"variants": [
|
| 98 |
+
{
|
| 99 |
+
"id": "split",
|
| 100 |
+
"priority": 20,
|
| 101 |
+
"when": ["splitFits"],
|
| 102 |
+
"demoteWhen": ["elementCount < tunables.SPLIT_MIN_ELEMENTS * statsGroupBlocks", "(not channelsLast) and normRows >= tunables.FUSED_MIN_ROWS and groupSize * dtypeBytes(tensorDtypes.X) <= tunables.FUSED_MAX_GROUP_BYTES"],
|
| 103 |
+
"derive": {
|
| 104 |
+
"phaseLayout": "channelsLast",
|
| 105 |
+
"hasSkip": "false",
|
| 106 |
+
"hasBias": "false",
|
| 107 |
+
"broadcastSkip": "false",
|
| 108 |
+
"vecSpec": "statsVecWidth",
|
| 109 |
+
"dimCSpec": "dimC",
|
| 110 |
+
"dimHWSpec": "dimHW",
|
| 111 |
+
"cpgSpec": "channelsPerGroup",
|
| 112 |
+
"groupsSpec": "numGroups",
|
| 113 |
+
"groupSizeSpec": "groupSize",
|
| 114 |
+
"normRowsSpec": "normRows",
|
| 115 |
+
"epsilonSpec": "attrs.epsilon",
|
| 116 |
+
"activationSpec": "activationKind",
|
| 117 |
+
"chunksSpec": "statsChunks",
|
| 118 |
+
"rowsPerStepSpec": "statsRowsPerStep",
|
| 119 |
+
"splitSpec": "splitCount",
|
| 120 |
+
"rowsPerSlabSpec": "ceilDiv(dimHW, slabCount)",
|
| 121 |
+
"planeVecsSpec": "planeVecs",
|
| 122 |
+
"chunkVecsSpec": "ceilDiv(planeVecs, planeSplits)",
|
| 123 |
+
"partialsPerRowSpec": "partialsPerRow",
|
| 124 |
+
"wgSpec": "statsWorkgroup",
|
| 125 |
+
"combineWgSpec": "combineWorkgroup",
|
| 126 |
+
"vecF32": "(\"vec\" ~ statsVecWidth ~ \"<f32>\") if statsVecWidth > 1 else \"f32\"",
|
| 127 |
+
"storeElement": "statsElement",
|
| 128 |
+
"applySrc": "\"src\"",
|
| 129 |
+
"groupUniform": "true"
|
| 130 |
+
},
|
| 131 |
+
"intermediates": [
|
| 132 |
+
{ "id": "partials", "dtype": "float32", "shape": "[partialElements, 2]" },
|
| 133 |
+
{ "id": "stats", "dtype": "float32", "shape": "[statsElements, 2]" }
|
| 134 |
+
],
|
| 135 |
+
"passes": [
|
| 136 |
+
{
|
| 137 |
+
"id": "stats",
|
| 138 |
+
"name": "GroupNorm.Partials",
|
| 139 |
+
"shader": "group-norm-slab.wgsl.jinja",
|
| 140 |
+
"derive": {
|
| 141 |
+
"phase": "\"stats\"",
|
| 142 |
+
"unitsSpec": "workUnits",
|
| 143 |
+
"statsBlockedSpec": "channelsLast and statsGroupBlocks > 1",
|
| 144 |
+
"statsGroupsPerBlockSpec": "statsGroupsPerBlock",
|
| 145 |
+
"statsGroupBlocksSpec": "statsGroupBlocks",
|
| 146 |
+
"statsTileChunksSpec": "statsTileChunks"
|
| 147 |
+
},
|
| 148 |
+
"bindings": ["xIn", "partialsW"],
|
| 149 |
+
"dispatch": { "x": "min(workUnits, 65535)", "y": "ceilDiv(workUnits, 65535)", "z": 1 }
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
"id": "combine",
|
| 153 |
+
"name": "GroupNorm.Combine",
|
| 154 |
+
"shader": "group-norm-combine.wgsl.jinja",
|
| 155 |
+
"bindings": ["srcX", "partialsR", "statsW"],
|
| 156 |
+
"dispatch": {
|
| 157 |
+
"x": "min(ceilDiv((normRows), (combineWorkgroup)), 65535)",
|
| 158 |
+
"y": "ceilDiv(ceilDiv((normRows), (combineWorkgroup)), 65535)",
|
| 159 |
+
"z": 1
|
| 160 |
+
}
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"id": "apply",
|
| 164 |
+
"name": "GroupNorm.Apply",
|
| 165 |
+
"shader": "group-norm-slab.wgsl.jinja",
|
| 166 |
+
"derive": {
|
| 167 |
+
"phase": "\"apply\"",
|
| 168 |
+
"vecSpec": "applyVecWidth",
|
| 169 |
+
"chunksSpec": "applyChunks",
|
| 170 |
+
"rowsPerStepSpec": "applyRowsPerStep",
|
| 171 |
+
"wgSpec": "applyWorkgroup",
|
| 172 |
+
"splitSpec": "applySplit",
|
| 173 |
+
"rowsPerSlabSpec": "ceilDiv(dimHW, applySlabCount)",
|
| 174 |
+
"groupUniform": "(channelsPerGroup % applyVecWidth == 0) if channelsLast else true",
|
| 175 |
+
"storeElement": "applyElement",
|
| 176 |
+
"vecF32": "\"vec4<f32>\" if applyVec4Ok else \"f32\"",
|
| 177 |
+
"unitsSpec": "applyUnits"
|
| 178 |
+
},
|
| 179 |
+
"bindings": ["srcApply", "gamma", "beta", "statsR", "yOut"],
|
| 180 |
+
"dispatch": { "x": "min(applyUnits, 65535)", "y": "ceilDiv(applyUnits, 65535)", "z": 1 }
|
| 181 |
+
}
|
| 182 |
+
]
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"id": "fused",
|
| 186 |
+
"priority": 10,
|
| 187 |
+
"when": ["fusedFits"],
|
| 188 |
+
"derive": {
|
| 189 |
+
"statsElement": "fusedElement",
|
| 190 |
+
"phaseLayout": "channelsLast",
|
| 191 |
+
"hasSkip": "false",
|
| 192 |
+
"hasBias": "false",
|
| 193 |
+
"broadcastSkip": "false",
|
| 194 |
+
"useVec4": "statsVec4Ok",
|
| 195 |
+
"vecSpec": "fusedVecWidth",
|
| 196 |
+
"dimCSpec": "dimC",
|
| 197 |
+
"dimHWSpec": "dimHW",
|
| 198 |
+
"cpgSpec": "channelsPerGroup",
|
| 199 |
+
"groupsSpec": "numGroups",
|
| 200 |
+
"groupSizeSpec": "groupSize",
|
| 201 |
+
"normRowsSpec": "normRows",
|
| 202 |
+
"epsilonSpec": "attrs.epsilon",
|
| 203 |
+
"activationSpec": "activationKind",
|
| 204 |
+
"chunksSpec": "fusedRowChunks",
|
| 205 |
+
"fusedChunksSpec": "fusedChunks",
|
| 206 |
+
"fusedRowsSpec": "max(1, floor(fusedWorkgroup / fusedChunks))",
|
| 207 |
+
"spanVecsSpec": "max(1, floor(groupSize / fusedVecWidth))",
|
| 208 |
+
"wgSpec": "fusedWorkgroup",
|
| 209 |
+
"ldsSpec": "fusedLanes",
|
| 210 |
+
"vecF32": "\"vec4<f32>\" if statsVec4Ok else \"f32\"",
|
| 211 |
+
"storeElement": "fusedElement",
|
| 212 |
+
"fusedSrc": "\"X\"",
|
| 213 |
+
"planeVecsSpec": "planeVecs"
|
| 214 |
+
},
|
| 215 |
+
"passes": [
|
| 216 |
+
{
|
| 217 |
+
"id": "main",
|
| 218 |
+
"name": "GroupNorm.Fused",
|
| 219 |
+
"shader": "group-norm-fused.wgsl.jinja",
|
| 220 |
+
"bindings": ["xIn", "gamma", "beta", "yFused"],
|
| 221 |
+
"dispatch": { "x": "min(normRows, 65535)", "y": "ceilDiv(normRows, 65535)", "z": 1 }
|
| 222 |
+
}
|
| 223 |
+
]
|
| 224 |
+
}
|
| 225 |
+
]
|
| 226 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "com.microsoft.GroupNorm",
|
| 3 |
+
"id": "_com_microsoft_groupnorm_webgpu_3aa81a3",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"bench.json": "jld33+mA1k/b0oKawSw9bnQxUcFLroArkzBwCnOdyCg=",
|
| 11 |
+
"group-norm-combine.wgsl.jinja": "weqcoPzdY5lQ8K/lZVEF03/sdbu1c/2jwdrW/KT9xr4=",
|
| 12 |
+
"group-norm-fused.wgsl.jinja": "uL3EIKVa0AnJbW7kB5MRPipXhZDr9KZ62soBHeu40K0=",
|
| 13 |
+
"group-norm-slab.wgsl.jinja": "T6ccs9TsauqsOjpC2hBzy7mRE/66CmLVI1F9JcBhk+w=",
|
| 14 |
+
"manifest.json": "U5sLYCEeiEBL842xP9mLAEgUQaJ3aioLBFcvJYpPMok=",
|
| 15 |
+
"test.json": "pEkAgZoOGQcSf9k/rHscjf21d18AlN/baC/QpVhOX08="
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
|
| 19 |
+
"webgpu": {
|
| 20 |
+
"manifestSpec": "2.1",
|
| 21 |
+
"variants": {
|
| 22 |
+
"split": ["group-norm-combine.wgsl.jinja", "group-norm-slab.wgsl.jinja"],
|
| 23 |
+
"fused": ["group-norm-fused.wgsl.jinja"]
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
}
|
build/webgpu/test.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|