sync 6fdf6301e2bb
Browse files- README.md +17 -3
- build/webgpu/bench.json +473 -1
- build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja +7 -6
- build/webgpu/bias-softmax.wgsl.jinja +15 -48
- build/webgpu/manifest.json +139 -26
- build/webgpu/metadata.json +15 -10
- build/webgpu/{bias-softmax-longrow-stats.wgsl.jinja → softmax-longrow-stats.wgsl.jinja} +31 -111
- build/webgpu/softmax-online.wgsl.jinja +149 -0
- build/webgpu/softmax-subgroup-rows.wgsl.jinja +109 -0
- build/webgpu/test.json +440 -0
README.md
CHANGED
|
@@ -44,20 +44,34 @@ Attributes and default values (overridable per request):
|
|
| 44 |
| --- | --- |
|
| 45 |
| `T` | `float32`, `float16` |
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
## Files
|
| 48 |
|
| 49 |
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 50 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 51 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 52 |
-
- [`bench.json`](build/webgpu/bench.json) — benchmark
|
| 53 |
- [`bias-softmax-longrow-normalize.wgsl.jinja`](build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja)
|
| 54 |
-
- [`bias-softmax-longrow-stats.wgsl.jinja`](build/webgpu/bias-softmax-longrow-stats.wgsl.jinja)
|
| 55 |
- [`bias-softmax.wgsl.jinja`](build/webgpu/bias-softmax.wgsl.jinja)
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
## Use with `@huggingface/kernels`
|
| 58 |
|
| 59 |
```sh
|
| 60 |
-
npm install --save-exact @huggingface/kernels@0.0.1-preview.
|
| 61 |
```
|
| 62 |
|
| 63 |
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
|
|
|
| 44 |
| --- | --- |
|
| 45 |
| `T` | `float32`, `float16` |
|
| 46 |
|
| 47 |
+
## Implementation variants
|
| 48 |
+
|
| 49 |
+
One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
|
| 50 |
+
|
| 51 |
+
- `online_subgroup_rows_vec4` — Subgroup-per-row fused bias and softmax with register-resident vec4 values, f32 addition and reduction, and the existing zero-output convention for fully masked rows.
|
| 52 |
+
- `online_workgroup_vec4` — Vectorized workgroup online bias-softmax with f32 addition and reduction; portable fallback below the register-resident subgroup-row path.
|
| 53 |
+
- `online_workgroup_scalar_vec4` — Logical vec4 online reduction with scalar storage for unaligned row strides. Invalid tail components contribute zero denominator and are never stored.
|
| 54 |
+
|
| 55 |
+
## Device requirements
|
| 56 |
+
|
| 57 |
+
Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
| 58 |
+
|
| 59 |
## Files
|
| 60 |
|
| 61 |
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 62 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 63 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 64 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark cases
|
| 65 |
- [`bias-softmax-longrow-normalize.wgsl.jinja`](build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja)
|
|
|
|
| 66 |
- [`bias-softmax.wgsl.jinja`](build/webgpu/bias-softmax.wgsl.jinja)
|
| 67 |
+
- [`softmax-longrow-stats.wgsl.jinja`](build/webgpu/softmax-longrow-stats.wgsl.jinja)
|
| 68 |
+
- [`softmax-online.wgsl.jinja`](build/webgpu/softmax-online.wgsl.jinja)
|
| 69 |
+
- [`softmax-subgroup-rows.wgsl.jinja`](build/webgpu/softmax-subgroup-rows.wgsl.jinja)
|
| 70 |
|
| 71 |
## Use with `@huggingface/kernels`
|
| 72 |
|
| 73 |
```sh
|
| 74 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.3
|
| 75 |
```
|
| 76 |
|
| 77 |
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
| 1 |
{
|
| 2 |
-
"tunableSpace": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
"cases": [
|
| 4 |
{
|
| 5 |
"name": "biassoftmax-f32-attn-32x512x512",
|
|
@@ -67,6 +71,474 @@
|
|
| 67 |
},
|
| 68 |
"outputs": { "output": { "shape": [262144, 2], "dtype": "float32" } },
|
| 69 |
"bench": { "metrics": [{ "type": "bandwidth", "value": "(262144 * 2 * 2 + 2) * dtypeBytes(args.dtype)" }] }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
}
|
| 71 |
]
|
| 72 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"tunableSpace": {
|
| 3 |
+
"WORKGROUP_SIZE": [64, 128, 256],
|
| 4 |
+
"BLOCK_COLS": [1024, 2048, 4096, 8192],
|
| 5 |
+
"ONLINE_WORKGROUP_SIZE": [32, 64, 128, 256]
|
| 6 |
+
},
|
| 7 |
"cases": [
|
| 8 |
{
|
| 9 |
"name": "biassoftmax-f32-attn-32x512x512",
|
|
|
|
| 71 |
},
|
| 72 |
"outputs": { "output": { "shape": [262144, 2], "dtype": "float32" } },
|
| 73 |
"bench": { "metrics": [{ "type": "bandwidth", "value": "(262144 * 2 * 2 + 2) * dtypeBytes(args.dtype)" }] }
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"name": "reuse_float32_r4096_c513_bias16_wg256_inner0",
|
| 77 |
+
"preset": "stress",
|
| 78 |
+
"vars": { "dtype": "float32" },
|
| 79 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 80 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 81 |
+
"inputs": {
|
| 82 |
+
"data": { "shape": [4096, 513], "dtype": "float32", "dist": "normal", "seed": 601, "scale": 2 },
|
| 83 |
+
"bias": { "shape": [16, 513], "dtype": "float32", "dist": "normal", "seed": 602, "scale": 1 }
|
| 84 |
+
},
|
| 85 |
+
"outputs": { "output": { "shape": [4096, 513], "dtype": "float32" } },
|
| 86 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "(4096 * 513 * 2 + 16 * 513) * dtypeBytes(args.dtype)" }] }
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"name": "reuse_float32_r1024_c4095_bias16_wg256_inner0",
|
| 90 |
+
"preset": "stress",
|
| 91 |
+
"vars": { "dtype": "float32" },
|
| 92 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 93 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 94 |
+
"inputs": {
|
| 95 |
+
"data": { "shape": [1024, 4095], "dtype": "float32", "dist": "normal", "seed": 601, "scale": 2 },
|
| 96 |
+
"bias": { "shape": [16, 4095], "dtype": "float32", "dist": "normal", "seed": 602, "scale": 1 }
|
| 97 |
+
},
|
| 98 |
+
"outputs": { "output": { "shape": [1024, 4095], "dtype": "float32" } },
|
| 99 |
+
"bench": {
|
| 100 |
+
"metrics": [{ "type": "bandwidth", "value": "(1024 * 4095 * 2 + 16 * 4095) * dtypeBytes(args.dtype)" }]
|
| 101 |
+
}
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"name": "reuse_float16_r4096_c513_bias16_wg256_inner0",
|
| 105 |
+
"preset": "stress",
|
| 106 |
+
"vars": { "dtype": "float16" },
|
| 107 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 108 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 109 |
+
"inputs": {
|
| 110 |
+
"data": { "shape": [4096, 513], "dtype": "float16", "dist": "normal", "seed": 601, "scale": 2 },
|
| 111 |
+
"bias": { "shape": [16, 513], "dtype": "float16", "dist": "normal", "seed": 602, "scale": 1 }
|
| 112 |
+
},
|
| 113 |
+
"outputs": { "output": { "shape": [4096, 513], "dtype": "float16" } },
|
| 114 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "(4096 * 513 * 2 + 16 * 513) * dtypeBytes(args.dtype)" }] }
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"name": "reuse_float16_r1024_c4095_bias16_wg256_inner0",
|
| 118 |
+
"preset": "stress",
|
| 119 |
+
"vars": { "dtype": "float16" },
|
| 120 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 121 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 122 |
+
"inputs": {
|
| 123 |
+
"data": { "shape": [1024, 4095], "dtype": "float16", "dist": "normal", "seed": 601, "scale": 2 },
|
| 124 |
+
"bias": { "shape": [16, 4095], "dtype": "float16", "dist": "normal", "seed": 602, "scale": 1 }
|
| 125 |
+
},
|
| 126 |
+
"outputs": { "output": { "shape": [1024, 4095], "dtype": "float16" } },
|
| 127 |
+
"bench": {
|
| 128 |
+
"metrics": [{ "type": "bandwidth", "value": "(1024 * 4095 * 2 + 16 * 4095) * dtypeBytes(args.dtype)" }]
|
| 129 |
+
}
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"name": "long_float32_r4_c65537_bias2_wg64_inner1",
|
| 133 |
+
"preset": "stress",
|
| 134 |
+
"vars": { "dtype": "float32" },
|
| 135 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 136 |
+
"tunables": { "WORKGROUP_SIZE": 64 },
|
| 137 |
+
"inputs": {
|
| 138 |
+
"data": { "shape": [4, 65537], "dtype": "float32", "dist": "normal", "seed": 611, "scale": 2 },
|
| 139 |
+
"bias": { "shape": [2, 65537], "dtype": "float32", "dist": "normal", "seed": 612, "scale": 1 }
|
| 140 |
+
},
|
| 141 |
+
"outputs": { "output": { "shape": [4, 65537], "dtype": "float32" } },
|
| 142 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "(4 * 65537 * 2 + 2 * 65537) * dtypeBytes(args.dtype)" }] }
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"name": "long_float32_r8_c131073_bias4_wg256_inner0",
|
| 146 |
+
"preset": "stress",
|
| 147 |
+
"vars": { "dtype": "float32" },
|
| 148 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 149 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 150 |
+
"inputs": {
|
| 151 |
+
"data": { "shape": [8, 131073], "dtype": "float32", "dist": "normal", "seed": 611, "scale": 2 },
|
| 152 |
+
"bias": { "shape": [4, 131073], "dtype": "float32", "dist": "normal", "seed": 612, "scale": 1 }
|
| 153 |
+
},
|
| 154 |
+
"outputs": { "output": { "shape": [8, 131073], "dtype": "float32" } },
|
| 155 |
+
"bench": {
|
| 156 |
+
"metrics": [{ "type": "bandwidth", "value": "(8 * 131073 * 2 + 4 * 131073) * dtypeBytes(args.dtype)" }]
|
| 157 |
+
}
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"name": "long_float16_r4_c65537_bias2_wg64_inner1",
|
| 161 |
+
"preset": "stress",
|
| 162 |
+
"vars": { "dtype": "float16" },
|
| 163 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 164 |
+
"tunables": { "WORKGROUP_SIZE": 64 },
|
| 165 |
+
"inputs": {
|
| 166 |
+
"data": { "shape": [4, 65537], "dtype": "float16", "dist": "normal", "seed": 611, "scale": 2 },
|
| 167 |
+
"bias": { "shape": [2, 65537], "dtype": "float16", "dist": "normal", "seed": 612, "scale": 1 }
|
| 168 |
+
},
|
| 169 |
+
"outputs": { "output": { "shape": [4, 65537], "dtype": "float16" } },
|
| 170 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "(4 * 65537 * 2 + 2 * 65537) * dtypeBytes(args.dtype)" }] }
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"name": "long_float16_r8_c131073_bias4_wg256_inner0",
|
| 174 |
+
"preset": "stress",
|
| 175 |
+
"vars": { "dtype": "float16" },
|
| 176 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 177 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 178 |
+
"inputs": {
|
| 179 |
+
"data": { "shape": [8, 131073], "dtype": "float16", "dist": "normal", "seed": 611, "scale": 2 },
|
| 180 |
+
"bias": { "shape": [4, 131073], "dtype": "float16", "dist": "normal", "seed": 612, "scale": 1 }
|
| 181 |
+
},
|
| 182 |
+
"outputs": { "output": { "shape": [8, 131073], "dtype": "float16" } },
|
| 183 |
+
"bench": {
|
| 184 |
+
"metrics": [{ "type": "bandwidth", "value": "(8 * 131073 * 2 + 4 * 131073) * dtypeBytes(args.dtype)" }]
|
| 185 |
+
}
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"name": "subgroup-neighbor-float32-r64-c64-bias2-inner0",
|
| 189 |
+
"preset": "stress",
|
| 190 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 191 |
+
"inputs": {
|
| 192 |
+
"data": { "shape": [64, 64], "dtype": "float32", "dist": "normal", "seed": 761, "scale": 2 },
|
| 193 |
+
"bias": { "shape": [2, 64], "dtype": "float32", "dist": "normal", "seed": 762, "scale": 1 }
|
| 194 |
+
},
|
| 195 |
+
"outputs": { "output": { "shape": [64, 64], "dtype": "float32" } },
|
| 196 |
+
"vars": { "dtype": "float32", "rows": 64, "cols": 64, "biasRows": 2 },
|
| 197 |
+
"bench": {
|
| 198 |
+
"metrics": [
|
| 199 |
+
{
|
| 200 |
+
"type": "bandwidth",
|
| 201 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 202 |
+
}
|
| 203 |
+
]
|
| 204 |
+
}
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"name": "subgroup-neighbor-float32-r65-c256-bias5-inner1",
|
| 208 |
+
"preset": "stress",
|
| 209 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 210 |
+
"inputs": {
|
| 211 |
+
"data": { "shape": [65, 256], "dtype": "float32", "dist": "normal", "seed": 761, "scale": 2 },
|
| 212 |
+
"bias": { "shape": [5, 256], "dtype": "float32", "dist": "normal", "seed": 762, "scale": 1 }
|
| 213 |
+
},
|
| 214 |
+
"outputs": { "output": { "shape": [65, 256], "dtype": "float32" } },
|
| 215 |
+
"vars": { "dtype": "float32", "rows": 65, "cols": 256, "biasRows": 5 },
|
| 216 |
+
"bench": {
|
| 217 |
+
"metrics": [
|
| 218 |
+
{
|
| 219 |
+
"type": "bandwidth",
|
| 220 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 221 |
+
}
|
| 222 |
+
]
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"name": "subgroup-neighbor-float32-r2048-c128-bias16-inner0",
|
| 227 |
+
"preset": "stress",
|
| 228 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 229 |
+
"inputs": {
|
| 230 |
+
"data": { "shape": [2048, 128], "dtype": "float32", "dist": "normal", "seed": 761, "scale": 2 },
|
| 231 |
+
"bias": { "shape": [16, 128], "dtype": "float32", "dist": "normal", "seed": 762, "scale": 1 }
|
| 232 |
+
},
|
| 233 |
+
"outputs": { "output": { "shape": [2048, 128], "dtype": "float32" } },
|
| 234 |
+
"vars": { "dtype": "float32", "rows": 2048, "cols": 128, "biasRows": 16 },
|
| 235 |
+
"bench": {
|
| 236 |
+
"metrics": [
|
| 237 |
+
{
|
| 238 |
+
"type": "bandwidth",
|
| 239 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 240 |
+
}
|
| 241 |
+
]
|
| 242 |
+
}
|
| 243 |
+
},
|
| 244 |
+
{
|
| 245 |
+
"name": "subgroup-neighbor-float32-r512-c1024-bias8-inner1",
|
| 246 |
+
"preset": "stress",
|
| 247 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 248 |
+
"inputs": {
|
| 249 |
+
"data": { "shape": [512, 1024], "dtype": "float32", "dist": "normal", "seed": 761, "scale": 2 },
|
| 250 |
+
"bias": { "shape": [8, 1024], "dtype": "float32", "dist": "normal", "seed": 762, "scale": 1 }
|
| 251 |
+
},
|
| 252 |
+
"outputs": { "output": { "shape": [512, 1024], "dtype": "float32" } },
|
| 253 |
+
"vars": { "dtype": "float32", "rows": 512, "cols": 1024, "biasRows": 8 },
|
| 254 |
+
"bench": {
|
| 255 |
+
"metrics": [
|
| 256 |
+
{
|
| 257 |
+
"type": "bandwidth",
|
| 258 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 259 |
+
}
|
| 260 |
+
]
|
| 261 |
+
}
|
| 262 |
+
},
|
| 263 |
+
{
|
| 264 |
+
"name": "subgroup-neighbor-float32-r128-c1028-bias4-inner0",
|
| 265 |
+
"preset": "stress",
|
| 266 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 267 |
+
"inputs": {
|
| 268 |
+
"data": { "shape": [128, 1028], "dtype": "float32", "dist": "normal", "seed": 761, "scale": 2 },
|
| 269 |
+
"bias": { "shape": [4, 1028], "dtype": "float32", "dist": "normal", "seed": 762, "scale": 1 }
|
| 270 |
+
},
|
| 271 |
+
"outputs": { "output": { "shape": [128, 1028], "dtype": "float32" } },
|
| 272 |
+
"vars": { "dtype": "float32", "rows": 128, "cols": 1028, "biasRows": 4 },
|
| 273 |
+
"bench": {
|
| 274 |
+
"metrics": [
|
| 275 |
+
{
|
| 276 |
+
"type": "bandwidth",
|
| 277 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 278 |
+
}
|
| 279 |
+
]
|
| 280 |
+
}
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"name": "subgroup-neighbor-float32-r63-c512-bias3-inner1",
|
| 284 |
+
"preset": "stress",
|
| 285 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 286 |
+
"inputs": {
|
| 287 |
+
"data": { "shape": [63, 512], "dtype": "float32", "dist": "normal", "seed": 761, "scale": 2 },
|
| 288 |
+
"bias": { "shape": [3, 512], "dtype": "float32", "dist": "normal", "seed": 762, "scale": 1 }
|
| 289 |
+
},
|
| 290 |
+
"outputs": { "output": { "shape": [63, 512], "dtype": "float32" } },
|
| 291 |
+
"vars": { "dtype": "float32", "rows": 63, "cols": 512, "biasRows": 3 },
|
| 292 |
+
"bench": {
|
| 293 |
+
"metrics": [
|
| 294 |
+
{
|
| 295 |
+
"type": "bandwidth",
|
| 296 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 297 |
+
}
|
| 298 |
+
]
|
| 299 |
+
}
|
| 300 |
+
},
|
| 301 |
+
{
|
| 302 |
+
"name": "subgroup-neighbor-float16-r64-c64-bias2-inner0",
|
| 303 |
+
"preset": "stress",
|
| 304 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 305 |
+
"inputs": {
|
| 306 |
+
"data": { "shape": [64, 64], "dtype": "float16", "dist": "normal", "seed": 761, "scale": 2 },
|
| 307 |
+
"bias": { "shape": [2, 64], "dtype": "float16", "dist": "normal", "seed": 762, "scale": 1 }
|
| 308 |
+
},
|
| 309 |
+
"outputs": { "output": { "shape": [64, 64], "dtype": "float16" } },
|
| 310 |
+
"vars": { "dtype": "float16", "rows": 64, "cols": 64, "biasRows": 2 },
|
| 311 |
+
"bench": {
|
| 312 |
+
"metrics": [
|
| 313 |
+
{
|
| 314 |
+
"type": "bandwidth",
|
| 315 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 316 |
+
}
|
| 317 |
+
]
|
| 318 |
+
}
|
| 319 |
+
},
|
| 320 |
+
{
|
| 321 |
+
"name": "subgroup-neighbor-float16-r65-c256-bias5-inner1",
|
| 322 |
+
"preset": "stress",
|
| 323 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 324 |
+
"inputs": {
|
| 325 |
+
"data": { "shape": [65, 256], "dtype": "float16", "dist": "normal", "seed": 761, "scale": 2 },
|
| 326 |
+
"bias": { "shape": [5, 256], "dtype": "float16", "dist": "normal", "seed": 762, "scale": 1 }
|
| 327 |
+
},
|
| 328 |
+
"outputs": { "output": { "shape": [65, 256], "dtype": "float16" } },
|
| 329 |
+
"vars": { "dtype": "float16", "rows": 65, "cols": 256, "biasRows": 5 },
|
| 330 |
+
"bench": {
|
| 331 |
+
"metrics": [
|
| 332 |
+
{
|
| 333 |
+
"type": "bandwidth",
|
| 334 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 335 |
+
}
|
| 336 |
+
]
|
| 337 |
+
}
|
| 338 |
+
},
|
| 339 |
+
{
|
| 340 |
+
"name": "subgroup-neighbor-float16-r2048-c128-bias16-inner0",
|
| 341 |
+
"preset": "stress",
|
| 342 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 343 |
+
"inputs": {
|
| 344 |
+
"data": { "shape": [2048, 128], "dtype": "float16", "dist": "normal", "seed": 761, "scale": 2 },
|
| 345 |
+
"bias": { "shape": [16, 128], "dtype": "float16", "dist": "normal", "seed": 762, "scale": 1 }
|
| 346 |
+
},
|
| 347 |
+
"outputs": { "output": { "shape": [2048, 128], "dtype": "float16" } },
|
| 348 |
+
"vars": { "dtype": "float16", "rows": 2048, "cols": 128, "biasRows": 16 },
|
| 349 |
+
"bench": {
|
| 350 |
+
"metrics": [
|
| 351 |
+
{
|
| 352 |
+
"type": "bandwidth",
|
| 353 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 354 |
+
}
|
| 355 |
+
]
|
| 356 |
+
}
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"name": "subgroup-neighbor-float16-r512-c1024-bias8-inner1",
|
| 360 |
+
"preset": "stress",
|
| 361 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 362 |
+
"inputs": {
|
| 363 |
+
"data": { "shape": [512, 1024], "dtype": "float16", "dist": "normal", "seed": 761, "scale": 2 },
|
| 364 |
+
"bias": { "shape": [8, 1024], "dtype": "float16", "dist": "normal", "seed": 762, "scale": 1 }
|
| 365 |
+
},
|
| 366 |
+
"outputs": { "output": { "shape": [512, 1024], "dtype": "float16" } },
|
| 367 |
+
"vars": { "dtype": "float16", "rows": 512, "cols": 1024, "biasRows": 8 },
|
| 368 |
+
"bench": {
|
| 369 |
+
"metrics": [
|
| 370 |
+
{
|
| 371 |
+
"type": "bandwidth",
|
| 372 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 373 |
+
}
|
| 374 |
+
]
|
| 375 |
+
}
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"name": "subgroup-neighbor-float16-r128-c1028-bias4-inner0",
|
| 379 |
+
"preset": "stress",
|
| 380 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 381 |
+
"inputs": {
|
| 382 |
+
"data": { "shape": [128, 1028], "dtype": "float16", "dist": "normal", "seed": 761, "scale": 2 },
|
| 383 |
+
"bias": { "shape": [4, 1028], "dtype": "float16", "dist": "normal", "seed": 762, "scale": 1 }
|
| 384 |
+
},
|
| 385 |
+
"outputs": { "output": { "shape": [128, 1028], "dtype": "float16" } },
|
| 386 |
+
"vars": { "dtype": "float16", "rows": 128, "cols": 1028, "biasRows": 4 },
|
| 387 |
+
"bench": {
|
| 388 |
+
"metrics": [
|
| 389 |
+
{
|
| 390 |
+
"type": "bandwidth",
|
| 391 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 392 |
+
}
|
| 393 |
+
]
|
| 394 |
+
}
|
| 395 |
+
},
|
| 396 |
+
{
|
| 397 |
+
"name": "subgroup-neighbor-float16-r63-c512-bias3-inner1",
|
| 398 |
+
"preset": "stress",
|
| 399 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 400 |
+
"inputs": {
|
| 401 |
+
"data": { "shape": [63, 512], "dtype": "float16", "dist": "normal", "seed": 761, "scale": 2 },
|
| 402 |
+
"bias": { "shape": [3, 512], "dtype": "float16", "dist": "normal", "seed": 762, "scale": 1 }
|
| 403 |
+
},
|
| 404 |
+
"outputs": { "output": { "shape": [63, 512], "dtype": "float16" } },
|
| 405 |
+
"vars": { "dtype": "float16", "rows": 63, "cols": 512, "biasRows": 3 },
|
| 406 |
+
"bench": {
|
| 407 |
+
"metrics": [
|
| 408 |
+
{
|
| 409 |
+
"type": "bandwidth",
|
| 410 |
+
"value": "(2 * args.rows * args.cols + args.biasRows * args.cols) * dtypeBytes(args.dtype)"
|
| 411 |
+
}
|
| 412 |
+
]
|
| 413 |
+
}
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"name": "workgroup-neighbor-float32-r65-c33-bias5-inner1",
|
| 417 |
+
"preset": "stress",
|
| 418 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 419 |
+
"inputs": {
|
| 420 |
+
"data": { "dtype": "float32", "shape": [65, 33], "dist": "normal", "seed": 701, "scale": 2 },
|
| 421 |
+
"bias": { "dtype": "float32", "shape": [5, 33], "dist": "normal", "seed": 702, "scale": 1 }
|
| 422 |
+
},
|
| 423 |
+
"outputs": { "output": { "dtype": "float32", "shape": [65, 33] } },
|
| 424 |
+
"bench": {
|
| 425 |
+
"metrics": [
|
| 426 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 427 |
+
]
|
| 428 |
+
},
|
| 429 |
+
"vars": { "dtype": "float32", "rows": 65, "cols": 33, "biasRows": 5 }
|
| 430 |
+
},
|
| 431 |
+
{
|
| 432 |
+
"name": "workgroup-neighbor-float32-r512-c257-bias8-inner0",
|
| 433 |
+
"preset": "stress",
|
| 434 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 435 |
+
"inputs": {
|
| 436 |
+
"data": { "dtype": "float32", "shape": [512, 257], "dist": "normal", "seed": 701, "scale": 2 },
|
| 437 |
+
"bias": { "dtype": "float32", "shape": [8, 257], "dist": "normal", "seed": 702, "scale": 1 }
|
| 438 |
+
},
|
| 439 |
+
"outputs": { "output": { "dtype": "float32", "shape": [512, 257] } },
|
| 440 |
+
"bench": {
|
| 441 |
+
"metrics": [
|
| 442 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 443 |
+
]
|
| 444 |
+
},
|
| 445 |
+
"vars": { "dtype": "float32", "rows": 512, "cols": 257, "biasRows": 8 }
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"name": "workgroup-neighbor-float32-r128-c1025-bias4-inner1",
|
| 449 |
+
"preset": "stress",
|
| 450 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 451 |
+
"inputs": {
|
| 452 |
+
"data": { "dtype": "float32", "shape": [128, 1025], "dist": "normal", "seed": 701, "scale": 2 },
|
| 453 |
+
"bias": { "dtype": "float32", "shape": [4, 1025], "dist": "normal", "seed": 702, "scale": 1 }
|
| 454 |
+
},
|
| 455 |
+
"outputs": { "output": { "dtype": "float32", "shape": [128, 1025] } },
|
| 456 |
+
"bench": {
|
| 457 |
+
"metrics": [
|
| 458 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 459 |
+
]
|
| 460 |
+
},
|
| 461 |
+
"vars": { "dtype": "float32", "rows": 128, "cols": 1025, "biasRows": 4 }
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"name": "workgroup-neighbor-float32-r64-c4097-bias2-inner0",
|
| 465 |
+
"preset": "stress",
|
| 466 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 467 |
+
"inputs": {
|
| 468 |
+
"data": { "dtype": "float32", "shape": [64, 4097], "dist": "normal", "seed": 701, "scale": 2 },
|
| 469 |
+
"bias": { "dtype": "float32", "shape": [2, 4097], "dist": "normal", "seed": 702, "scale": 1 }
|
| 470 |
+
},
|
| 471 |
+
"outputs": { "output": { "dtype": "float32", "shape": [64, 4097] } },
|
| 472 |
+
"bench": {
|
| 473 |
+
"metrics": [
|
| 474 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 475 |
+
]
|
| 476 |
+
},
|
| 477 |
+
"vars": { "dtype": "float32", "rows": 64, "cols": 4097, "biasRows": 2 }
|
| 478 |
+
},
|
| 479 |
+
{
|
| 480 |
+
"name": "workgroup-neighbor-float16-r65-c33-bias5-inner1",
|
| 481 |
+
"preset": "stress",
|
| 482 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 483 |
+
"inputs": {
|
| 484 |
+
"data": { "dtype": "float16", "shape": [65, 33], "dist": "normal", "seed": 701, "scale": 2 },
|
| 485 |
+
"bias": { "dtype": "float16", "shape": [5, 33], "dist": "normal", "seed": 702, "scale": 1 }
|
| 486 |
+
},
|
| 487 |
+
"outputs": { "output": { "dtype": "float16", "shape": [65, 33] } },
|
| 488 |
+
"bench": {
|
| 489 |
+
"metrics": [
|
| 490 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 491 |
+
]
|
| 492 |
+
},
|
| 493 |
+
"vars": { "dtype": "float16", "rows": 65, "cols": 33, "biasRows": 5 }
|
| 494 |
+
},
|
| 495 |
+
{
|
| 496 |
+
"name": "workgroup-neighbor-float16-r512-c257-bias8-inner0",
|
| 497 |
+
"preset": "stress",
|
| 498 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 499 |
+
"inputs": {
|
| 500 |
+
"data": { "dtype": "float16", "shape": [512, 257], "dist": "normal", "seed": 701, "scale": 2 },
|
| 501 |
+
"bias": { "dtype": "float16", "shape": [8, 257], "dist": "normal", "seed": 702, "scale": 1 }
|
| 502 |
+
},
|
| 503 |
+
"outputs": { "output": { "dtype": "float16", "shape": [512, 257] } },
|
| 504 |
+
"bench": {
|
| 505 |
+
"metrics": [
|
| 506 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 507 |
+
]
|
| 508 |
+
},
|
| 509 |
+
"vars": { "dtype": "float16", "rows": 512, "cols": 257, "biasRows": 8 }
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"name": "workgroup-neighbor-float16-r128-c1025-bias4-inner1",
|
| 513 |
+
"preset": "stress",
|
| 514 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 515 |
+
"inputs": {
|
| 516 |
+
"data": { "dtype": "float16", "shape": [128, 1025], "dist": "normal", "seed": 701, "scale": 2 },
|
| 517 |
+
"bias": { "dtype": "float16", "shape": [4, 1025], "dist": "normal", "seed": 702, "scale": 1 }
|
| 518 |
+
},
|
| 519 |
+
"outputs": { "output": { "dtype": "float16", "shape": [128, 1025] } },
|
| 520 |
+
"bench": {
|
| 521 |
+
"metrics": [
|
| 522 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 523 |
+
]
|
| 524 |
+
},
|
| 525 |
+
"vars": { "dtype": "float16", "rows": 128, "cols": 1025, "biasRows": 4 }
|
| 526 |
+
},
|
| 527 |
+
{
|
| 528 |
+
"name": "workgroup-neighbor-float16-r64-c4097-bias2-inner0",
|
| 529 |
+
"preset": "stress",
|
| 530 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 531 |
+
"inputs": {
|
| 532 |
+
"data": { "dtype": "float16", "shape": [64, 4097], "dist": "normal", "seed": 701, "scale": 2 },
|
| 533 |
+
"bias": { "dtype": "float16", "shape": [2, 4097], "dist": "normal", "seed": 702, "scale": 1 }
|
| 534 |
+
},
|
| 535 |
+
"outputs": { "output": { "dtype": "float16", "shape": [64, 4097] } },
|
| 536 |
+
"bench": {
|
| 537 |
+
"metrics": [
|
| 538 |
+
{ "type": "bandwidth", "value": "(args.rows * 2 + args.biasRows) * args.cols * dtypeBytes(args.dtype)" }
|
| 539 |
+
]
|
| 540 |
+
},
|
| 541 |
+
"vars": { "dtype": "float16", "rows": 64, "cols": 4097, "biasRows": 2 }
|
| 542 |
}
|
| 543 |
]
|
| 544 |
}
|
build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja
CHANGED
|
@@ -4,13 +4,12 @@
|
|
| 4 |
|
| 5 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 6 |
const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
let column = index % params.blockSize;
|
| 10 |
{% if isInnerBroadcast %}
|
| 11 |
-
return (row / {{ innerRepeat }}u) * params.blockSize
|
| 12 |
{% else %}
|
| 13 |
-
return (row % {{ biasBlockCountSpec }}u) * params.blockSize
|
| 14 |
{% endif %}
|
| 15 |
}
|
| 16 |
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
|
@@ -32,6 +31,7 @@ fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
|
| 32 |
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 33 |
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 34 |
}
|
|
|
|
| 35 |
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 36 |
return exp(shifted_value(value, maxValue));
|
| 37 |
}
|
|
@@ -42,6 +42,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 42 |
let block = wid.x;
|
| 43 |
let row = wid.y;
|
| 44 |
let rowBase = row * params.blockSize;
|
|
|
|
| 45 |
let colStart = block * BLOCK_COLS;
|
| 46 |
let colEnd = min(params.blockSize, colStart + BLOCK_COLS);
|
| 47 |
let m = rowMax[row];
|
|
@@ -50,7 +51,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 50 |
|
| 51 |
for (var col = colStart + lid.x; col < colEnd; col += WG) {
|
| 52 |
let i = rowBase + col;
|
| 53 |
-
let v = f32(data[i]) + f32(bias[
|
| 54 |
output[i] = {{ scalar }}(exp_shift(v, m) * inv);
|
| 55 |
}
|
| 56 |
}
|
|
|
|
| 4 |
|
| 5 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 6 |
const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
|
| 7 |
+
// Flattened bias-row address shared by packed, adaptive, and split rows.
|
| 8 |
+
fn bias_base(row: u32) -> u32 {
|
|
|
|
| 9 |
{% if isInnerBroadcast %}
|
| 10 |
+
return (row / {{ innerRepeat }}u) * params.blockSize;
|
| 11 |
{% else %}
|
| 12 |
+
return (row % {{ biasBlockCountSpec }}u) * params.blockSize;
|
| 13 |
{% endif %}
|
| 14 |
}
|
| 15 |
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
|
|
|
| 31 |
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 32 |
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 33 |
}
|
| 34 |
+
|
| 35 |
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 36 |
return exp(shifted_value(value, maxValue));
|
| 37 |
}
|
|
|
|
| 42 |
let block = wid.x;
|
| 43 |
let row = wid.y;
|
| 44 |
let rowBase = row * params.blockSize;
|
| 45 |
+
let biasBase = bias_base(row);
|
| 46 |
let colStart = block * BLOCK_COLS;
|
| 47 |
let colEnd = min(params.blockSize, colStart + BLOCK_COLS);
|
| 48 |
let m = rowMax[row];
|
|
|
|
| 51 |
|
| 52 |
for (var col = colStart + lid.x; col < colEnd; col += WG) {
|
| 53 |
let i = rowBase + col;
|
| 54 |
+
let v = f32(data[i]) + f32(bias[biasBase + col]);
|
| 55 |
output[i] = {{ scalar }}(exp_shift(v, m) * inv);
|
| 56 |
}
|
| 57 |
}
|
build/webgpu/bias-softmax.wgsl.jinja
CHANGED
|
@@ -27,6 +27,7 @@ fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
|
| 27 |
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 28 |
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 29 |
}
|
|
|
|
| 30 |
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 31 |
return exp(shifted_value(value, maxValue));
|
| 32 |
}
|
|
@@ -37,7 +38,6 @@ fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
|
| 37 |
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 38 |
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 39 |
// calls before their shared partial storage is reused.
|
| 40 |
-
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 41 |
{% if combineSubgroups %}
|
| 42 |
// Cross-subgroup merge that assumes nothing about which invocations share a
|
| 43 |
// subgroup or how many subgroups there are: each subgroup's elected lane
|
|
@@ -90,41 +90,9 @@ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
|
| 90 |
return combinedMD;
|
| 91 |
}
|
| 92 |
{% else %}
|
| 93 |
-
{% set
|
| 94 |
-
{% set mdStreams = mdStreams if mdStreams is defined else 1 %}
|
| 95 |
-
{% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
|
| 96 |
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 97 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 98 |
-
{% if mdStreamed %}
|
| 99 |
-
|
| 100 |
-
// In-place fold of {{ mdStreams }} streams. Input partials occupy
|
| 101 |
-
// partialM/partialD; stream s returns its merged pair in slot s * WG.
|
| 102 |
-
fn combine_partials_streams(lidx: u32) {
|
| 103 |
-
workgroupBarrier();
|
| 104 |
-
var stride = WG / 2u;
|
| 105 |
-
loop {
|
| 106 |
-
if (stride == 0u) {
|
| 107 |
-
break;
|
| 108 |
-
}
|
| 109 |
-
if (lidx < stride) {
|
| 110 |
-
{% for s in range(mdStreams) %}
|
| 111 |
-
{
|
| 112 |
-
let slot = {{ s }}u * WG + lidx;
|
| 113 |
-
let m1 = partialM[slot];
|
| 114 |
-
let d1 = partialD[slot];
|
| 115 |
-
let m2 = partialM[slot + stride];
|
| 116 |
-
let d2 = partialD[slot + stride];
|
| 117 |
-
let mNew = max(m1, m2);
|
| 118 |
-
partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 119 |
-
partialM[slot] = mNew;
|
| 120 |
-
}
|
| 121 |
-
{% endfor %}
|
| 122 |
-
}
|
| 123 |
-
workgroupBarrier();
|
| 124 |
-
stride = stride / 2u;
|
| 125 |
-
}
|
| 126 |
-
}
|
| 127 |
-
{% else %}
|
| 128 |
|
| 129 |
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 130 |
partialM[lidx] = m;
|
|
@@ -154,25 +122,22 @@ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
|
| 154 |
return merged;
|
| 155 |
}
|
| 156 |
{% endif %}
|
| 157 |
-
{% endif %}
|
| 158 |
-
|
| 159 |
|
| 160 |
{% endif %}
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
let column = index % params.blockSize;
|
| 164 |
{% if isInnerBroadcast %}
|
| 165 |
-
return (row / {{ innerRepeat }}u) * params.blockSize
|
| 166 |
{% else %}
|
| 167 |
-
return (row % {{ biasBlockCountSpec }}u) * params.blockSize
|
| 168 |
{% endif %}
|
| 169 |
}
|
| 170 |
|
| 171 |
@compute @workgroup_size(WG)
|
| 172 |
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 173 |
-
{%
|
| 174 |
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 175 |
-
{%
|
| 176 |
@builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups and not packedRows %},
|
| 177 |
@builtin(subgroup_size) sgSize: u32{% endif %}) {
|
| 178 |
{% if packedRows %}
|
|
@@ -180,11 +145,12 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 180 |
// one-dimensional and bounded even for very large batch counts.
|
| 181 |
for (var row = wid.x * WG + lid.x; row < params.batchCount; row += nwg.x * WG) {
|
| 182 |
let base = row * params.blockSize;
|
|
|
|
| 183 |
{% if blockSize == 2 %}
|
| 184 |
let i0 = base;
|
| 185 |
let i1 = base + 1u;
|
| 186 |
-
let v0 = f32(data[i0]) + f32(bias[
|
| 187 |
-
let v1 = f32(data[i1]) + f32(bias[
|
| 188 |
|
| 189 |
// Compute a two-element row as a logistic pair, choosing the non-positive
|
| 190 |
// exponent branch for stability. Non-finite rows use the reduction below.
|
|
@@ -221,7 +187,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 221 |
var denom = 0.0;
|
| 222 |
for (var dd = 0u; dd < params.blockSize; dd += 1u) {
|
| 223 |
let i = base + dd;
|
| 224 |
-
let v = f32(data[i]) + f32(bias[
|
| 225 |
values[dd] = v;
|
| 226 |
let new_m = max(m, v);
|
| 227 |
denom = denom * exp_shift(m, new_m) + exp_shift(v, new_m);
|
|
@@ -243,6 +209,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 243 |
return;
|
| 244 |
}
|
| 245 |
let base = row * params.blockSize;
|
|
|
|
| 246 |
let tid = lid.x;
|
| 247 |
|
| 248 |
// Single online (m, d) stats pass over (data + bias), then one cooperative
|
|
@@ -251,7 +218,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 251 |
var denom = 0.0;
|
| 252 |
for (var dd = tid; dd < params.blockSize; dd = dd + WG) {
|
| 253 |
let i = base + dd;
|
| 254 |
-
let v = f32(data[i]) + f32(bias[
|
| 255 |
let new_m = max(m, v);
|
| 256 |
denom = denom * exp_shift(m, new_m) + exp_shift(v, new_m);
|
| 257 |
m = new_m;
|
|
@@ -266,7 +233,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 266 |
|
| 267 |
for (var dd = tid; dd < params.blockSize; dd = dd + WG) {
|
| 268 |
let i = base + dd;
|
| 269 |
-
let v = f32(data[i]) + f32(bias[
|
| 270 |
output[i] = {{ scalar }}(exp_shift(v, rowMax) * inv);
|
| 271 |
}
|
| 272 |
{% endif %}
|
|
|
|
| 27 |
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 28 |
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 29 |
}
|
| 30 |
+
|
| 31 |
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 32 |
return exp(shifted_value(value, maxValue));
|
| 33 |
}
|
|
|
|
| 38 |
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 39 |
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 40 |
// calls before their shared partial storage is reused.
|
|
|
|
| 41 |
{% if combineSubgroups %}
|
| 42 |
// Cross-subgroup merge that assumes nothing about which invocations share a
|
| 43 |
// subgroup or how many subgroups there are: each subgroup's elected lane
|
|
|
|
| 90 |
return combinedMD;
|
| 91 |
}
|
| 92 |
{% else %}
|
| 93 |
+
{% set mdExtent = "WG" %}
|
|
|
|
|
|
|
| 94 |
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 95 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 98 |
partialM[lidx] = m;
|
|
|
|
| 122 |
return merged;
|
| 123 |
}
|
| 124 |
{% endif %}
|
|
|
|
|
|
|
| 125 |
|
| 126 |
{% endif %}
|
| 127 |
+
// Flattened bias-row address shared by packed, adaptive, and split rows.
|
| 128 |
+
fn bias_base(row: u32) -> u32 {
|
|
|
|
| 129 |
{% if isInnerBroadcast %}
|
| 130 |
+
return (row / {{ innerRepeat }}u) * params.blockSize;
|
| 131 |
{% else %}
|
| 132 |
+
return (row % {{ biasBlockCountSpec }}u) * params.blockSize;
|
| 133 |
{% endif %}
|
| 134 |
}
|
| 135 |
|
| 136 |
@compute @workgroup_size(WG)
|
| 137 |
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 138 |
+
{% if packedRows %}
|
| 139 |
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 140 |
+
{% endif %}
|
| 141 |
@builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups and not packedRows %},
|
| 142 |
@builtin(subgroup_size) sgSize: u32{% endif %}) {
|
| 143 |
{% if packedRows %}
|
|
|
|
| 145 |
// one-dimensional and bounded even for very large batch counts.
|
| 146 |
for (var row = wid.x * WG + lid.x; row < params.batchCount; row += nwg.x * WG) {
|
| 147 |
let base = row * params.blockSize;
|
| 148 |
+
let biasBase = bias_base(row);
|
| 149 |
{% if blockSize == 2 %}
|
| 150 |
let i0 = base;
|
| 151 |
let i1 = base + 1u;
|
| 152 |
+
let v0 = f32(data[i0]) + f32(bias[biasBase]);
|
| 153 |
+
let v1 = f32(data[i1]) + f32(bias[biasBase + 1u]);
|
| 154 |
|
| 155 |
// Compute a two-element row as a logistic pair, choosing the non-positive
|
| 156 |
// exponent branch for stability. Non-finite rows use the reduction below.
|
|
|
|
| 187 |
var denom = 0.0;
|
| 188 |
for (var dd = 0u; dd < params.blockSize; dd += 1u) {
|
| 189 |
let i = base + dd;
|
| 190 |
+
let v = f32(data[i]) + f32(bias[biasBase + dd]);
|
| 191 |
values[dd] = v;
|
| 192 |
let new_m = max(m, v);
|
| 193 |
denom = denom * exp_shift(m, new_m) + exp_shift(v, new_m);
|
|
|
|
| 209 |
return;
|
| 210 |
}
|
| 211 |
let base = row * params.blockSize;
|
| 212 |
+
let biasBase = bias_base(row);
|
| 213 |
let tid = lid.x;
|
| 214 |
|
| 215 |
// Single online (m, d) stats pass over (data + bias), then one cooperative
|
|
|
|
| 218 |
var denom = 0.0;
|
| 219 |
for (var dd = tid; dd < params.blockSize; dd = dd + WG) {
|
| 220 |
let i = base + dd;
|
| 221 |
+
let v = f32(data[i]) + f32(bias[biasBase + dd]);
|
| 222 |
let new_m = max(m, v);
|
| 223 |
denom = denom * exp_shift(m, new_m) + exp_shift(v, new_m);
|
| 224 |
m = new_m;
|
|
|
|
| 233 |
|
| 234 |
for (var dd = tid; dd < params.blockSize; dd = dd + WG) {
|
| 235 |
let i = base + dd;
|
| 236 |
+
let v = f32(data[i]) + f32(bias[biasBase + dd]);
|
| 237 |
output[i] = {{ scalar }}(exp_shift(v, rowMax) * inv);
|
| 238 |
}
|
| 239 |
{% endif %}
|
build/webgpu/manifest.json
CHANGED
|
@@ -7,26 +7,149 @@
|
|
| 7 |
"attributes": { "axis": { "default": 1 }, "is_inner_broadcast": {} },
|
| 8 |
"attributeConstraints": { "is_inner_broadcast": { "required": true } },
|
| 9 |
"typeConstraints": { "T": ["float32", "float16"] },
|
| 10 |
-
"tunables": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"derive": {
|
| 12 |
"axisNorm": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.data",
|
| 13 |
"batchCount": "outer(shapes.data, axisNorm)",
|
| 14 |
"blockSize": "dim(shapes.data, axisNorm) * inner(shapes.data, axisNorm)",
|
| 15 |
"biasBlockCount": "numel(shapes.bias) / max(1, blockSize)",
|
| 16 |
-
"biasContract": "(numel(shapes.data) == 0 and numel(shapes.bias) == 0) or (blockSize > 0 and biasBlockCount > 0 and numel(shapes.bias) % blockSize == 0 and biasBlockCount <= batchCount and batchCount % biasBlockCount == 0)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
},
|
| 18 |
"when": ["numel(shapes.data) == numel(shapes.output)", "ranks.data >= 1", "attrs.axis + ranks.data >= 0", "attrs.axis < ranks.data", "biasContract", "f16Ok(dtypes.T)"],
|
| 19 |
"bindings": {
|
| 20 |
-
"data": { "
|
| 21 |
-
"bias": { "
|
| 22 |
-
"output": { "
|
| 23 |
},
|
| 24 |
"variants": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
{
|
| 26 |
"id": "longrow_split",
|
| 27 |
"priority": 40,
|
| 28 |
"when": ["blockSize >= 65536", "batchCount > 0", "batchCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceilDiv(blockSize, tunables.BLOCK_COLS) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
|
| 29 |
-
"derive": { "scalar": "dtypes.T", "
|
| 30 |
"intermediates": [
|
| 31 |
{
|
| 32 |
"id": "blockMax",
|
|
@@ -45,9 +168,10 @@
|
|
| 45 |
{
|
| 46 |
"id": "block_stats",
|
| 47 |
"name": "BiasSoftmax.LongRowBlockStats",
|
| 48 |
-
"shader": "
|
| 49 |
"derive": {
|
| 50 |
"stage": "\"block\"",
|
|
|
|
| 51 |
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 52 |
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 53 |
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
|
@@ -55,8 +179,8 @@
|
|
| 55 |
"bindings": [
|
| 56 |
"data",
|
| 57 |
"bias",
|
| 58 |
-
{ "name": "blockMax", "
|
| 59 |
-
{ "name": "blockSum", "
|
| 60 |
{
|
| 61 |
"name": "params",
|
| 62 |
"struct": [
|
|
@@ -70,13 +194,13 @@
|
|
| 70 |
{
|
| 71 |
"id": "row_stats",
|
| 72 |
"name": "BiasSoftmax.LongRowStats",
|
| 73 |
-
"shader": "
|
| 74 |
"derive": { "stage": "\"row\"" },
|
| 75 |
"bindings": [
|
| 76 |
{ "name": "blockMax", "buffer": "read-only-storage", "elementType": "f32" },
|
| 77 |
{ "name": "blockSum", "buffer": "read-only-storage", "elementType": "f32" },
|
| 78 |
-
{ "name": "rowMax", "
|
| 79 |
-
{ "name": "rowSum", "
|
| 80 |
{
|
| 81 |
"name": "params",
|
| 82 |
"struct": [{ "name": "blocks", "type": "u32", "value": "ceilDiv(blockSize, tunables.BLOCK_COLS)" }]
|
|
@@ -109,12 +233,7 @@
|
|
| 109 |
"id": "packed_rows",
|
| 110 |
"priority": 30,
|
| 111 |
"when": ["blockSize > 0", "blockSize <= 8", "batchCount >= 64"],
|
| 112 |
-
"derive": {
|
| 113 |
-
"scalar": "dtypes.T",
|
| 114 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 115 |
-
"combineSubgroups": false,
|
| 116 |
-
"packedRows": true
|
| 117 |
-
},
|
| 118 |
"passes": [
|
| 119 |
{
|
| 120 |
"id": "main",
|
|
@@ -149,12 +268,7 @@
|
|
| 149 |
"id": "adaptive_row",
|
| 150 |
"priority": 10,
|
| 151 |
"when": ["numel(shapes.data) >= 0"],
|
| 152 |
-
"derive": {
|
| 153 |
-
"packedRows": false,
|
| 154 |
-
"scalar": "dtypes.T",
|
| 155 |
-
"usesF16": "dtypes.T == \"f16\"",
|
| 156 |
-
"combineSubgroups": "device.features.has(\"subgroups\")"
|
| 157 |
-
},
|
| 158 |
"passes": [
|
| 159 |
{
|
| 160 |
"id": "main",
|
|
@@ -177,8 +291,7 @@
|
|
| 177 |
]
|
| 178 |
}
|
| 179 |
],
|
| 180 |
-
"dispatch": { "x": "min(batchCount, 65535)", "y": "ceilDiv(batchCount, 65535)", "z": 1 }
|
| 181 |
-
"subgroupCollectivesWidth": "portable"
|
| 182 |
}
|
| 183 |
]
|
| 184 |
}
|
|
|
|
| 7 |
"attributes": { "axis": { "default": 1 }, "is_inner_broadcast": {} },
|
| 8 |
"attributeConstraints": { "is_inner_broadcast": { "required": true } },
|
| 9 |
"typeConstraints": { "T": ["float32", "float16"] },
|
| 10 |
+
"tunables": {
|
| 11 |
+
"WORKGROUP_SIZE": { "default": 256 },
|
| 12 |
+
"BLOCK_COLS": { "default": 2048 },
|
| 13 |
+
"ONLINE_WORKGROUP_SIZE": { "default": 64 }
|
| 14 |
+
},
|
| 15 |
"derive": {
|
| 16 |
"axisNorm": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.data",
|
| 17 |
"batchCount": "outer(shapes.data, axisNorm)",
|
| 18 |
"blockSize": "dim(shapes.data, axisNorm) * inner(shapes.data, axisNorm)",
|
| 19 |
"biasBlockCount": "numel(shapes.bias) / max(1, blockSize)",
|
| 20 |
+
"biasContract": "(numel(shapes.data) == 0 and numel(shapes.bias) == 0) or (blockSize > 0 and biasBlockCount > 0 and numel(shapes.bias) % blockSize == 0 and biasBlockCount <= batchCount and batchCount % biasBlockCount == 0)",
|
| 21 |
+
"deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
|
| 22 |
+
"subgroupRowsWorkgroup": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
|
| 23 |
+
"subgroupRowsLaneVecs": "ceilDiv(blockSize / 4, device.adapterInfo.subgroupMinSize) if (device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and device.adapterInfo.subgroupMinSize > 0) else 0",
|
| 24 |
+
"biasBaseWorkgroup": "min(tunables.ONLINE_WORKGROUP_SIZE, deviceWorkgroupCap)",
|
| 25 |
+
"biasMaxWorkgroup": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
|
| 26 |
+
"biasWorkgroupSize": "min(biasMaxWorkgroup, pow2ceil(ceilDiv(blockSize, 4)), max(biasBaseWorkgroup, pow2ceil(ceilDiv(biasBaseWorkgroup * biasMaxWorkgroup, max(1, batchCount)))))"
|
| 27 |
},
|
| 28 |
"when": ["numel(shapes.data) == numel(shapes.output)", "ranks.data >= 1", "attrs.axis + ranks.data >= 0", "attrs.axis < ranks.data", "biasContract", "f16Ok(dtypes.T)"],
|
| 29 |
"bindings": {
|
| 30 |
+
"data": { "elementType": "$scalar" },
|
| 31 |
+
"bias": { "elementType": "$scalar" },
|
| 32 |
+
"output": { "elementType": "$scalar" }
|
| 33 |
},
|
| 34 |
"variants": [
|
| 35 |
+
{
|
| 36 |
+
"id": "online_subgroup_rows_vec4",
|
| 37 |
+
"priority": 20,
|
| 38 |
+
"when": ["blockSize % 4 == 0", "has(device.adapterInfo, \"subgroupMinSize\")", "device.adapterInfo.subgroupMinSize >= 16", "blockSize >= 64", "subgroupRowsLaneVecs >= 1", "subgroupRowsLaneVecs <= 8", "batchCount >= 64", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMaxSize <= subgroupRowsWorkgroup", "pow2ceil(subgroupRowsWorkgroup) == subgroupRowsWorkgroup", "ceilDiv(ceilDiv(batchCount, subgroupRowsWorkgroup / device.adapterInfo.subgroupMaxSize), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
|
| 39 |
+
"requires": { "features": ["subgroups"] },
|
| 40 |
+
"derive": {
|
| 41 |
+
"scalar": "dtypes.T",
|
| 42 |
+
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
| 43 |
+
"workgroupSize": "subgroupRowsWorkgroup",
|
| 44 |
+
"vecsPerLane": "subgroupRowsLaneVecs",
|
| 45 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 46 |
+
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 47 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 48 |
+
},
|
| 49 |
+
"passes": [
|
| 50 |
+
{
|
| 51 |
+
"id": "main",
|
| 52 |
+
"name": "BiasSoftmax.SubgroupRowsVec4",
|
| 53 |
+
"shader": "softmax-subgroup-rows.wgsl.jinja",
|
| 54 |
+
"derive": { "op": "\"biassoftmax\"" },
|
| 55 |
+
"bindings": [
|
| 56 |
+
{ "arg": "data", "name": "x", "elementType": "$vectorScalar" },
|
| 57 |
+
{ "arg": "bias", "name": "bias", "elementType": "$vectorScalar" },
|
| 58 |
+
{ "arg": "output", "name": "y", "elementType": "$vectorScalar" },
|
| 59 |
+
{
|
| 60 |
+
"name": "params",
|
| 61 |
+
"struct": [
|
| 62 |
+
{ "name": "rows", "type": "u32", "value": "batchCount" },
|
| 63 |
+
{ "name": "vecCols", "type": "u32", "value": "blockSize / 4" }
|
| 64 |
+
]
|
| 65 |
+
}
|
| 66 |
+
],
|
| 67 |
+
"dispatch": {
|
| 68 |
+
"x": "min(ceilDiv(batchCount, subgroupRowsWorkgroup / device.adapterInfo.subgroupMaxSize), 65535)",
|
| 69 |
+
"y": "ceilDiv(ceilDiv(batchCount, subgroupRowsWorkgroup / device.adapterInfo.subgroupMaxSize), 65535)",
|
| 70 |
+
"z": 1
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
]
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"id": "online_workgroup_vec4",
|
| 77 |
+
"priority": 15,
|
| 78 |
+
"when": ["blockSize % 4 == 0", "blockSize > 32", "blockSize < 65536", "pow2ceil(biasWorkgroupSize) == biasWorkgroupSize", "ceilDiv(batchCount, min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
|
| 79 |
+
"derive": {
|
| 80 |
+
"scalar": "dtypes.T",
|
| 81 |
+
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
| 82 |
+
"workgroupSize": "biasWorkgroupSize",
|
| 83 |
+
"combineSubgroups": false,
|
| 84 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 85 |
+
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 86 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))",
|
| 87 |
+
"scalarVec4": false
|
| 88 |
+
},
|
| 89 |
+
"passes": [
|
| 90 |
+
{
|
| 91 |
+
"id": "main",
|
| 92 |
+
"name": "BiasSoftmax.WorkgroupVec4",
|
| 93 |
+
"shader": "softmax-online.wgsl.jinja",
|
| 94 |
+
"derive": { "op": "\"biassoftmax\"", "useVec4": true },
|
| 95 |
+
"bindings": [
|
| 96 |
+
{ "arg": "data", "name": "x", "elementType": "$vectorScalar" },
|
| 97 |
+
{ "arg": "bias", "name": "bias", "elementType": "$vectorScalar" },
|
| 98 |
+
{ "arg": "output", "name": "y", "elementType": "$vectorScalar" },
|
| 99 |
+
{
|
| 100 |
+
"name": "params",
|
| 101 |
+
"struct": [
|
| 102 |
+
{ "name": "rows", "type": "u32", "value": "batchCount" },
|
| 103 |
+
{ "name": "vecCols", "type": "u32", "value": "blockSize / 4" }
|
| 104 |
+
]
|
| 105 |
+
}
|
| 106 |
+
],
|
| 107 |
+
"dispatch": { "x": "min(batchCount, 65535)", "y": "ceilDiv(batchCount, 65535)", "z": 1 }
|
| 108 |
+
}
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"id": "online_workgroup_scalar_vec4",
|
| 113 |
+
"priority": 15,
|
| 114 |
+
"when": ["blockSize % 4 != 0", "blockSize > 32", "blockSize < 65536", "pow2ceil(biasWorkgroupSize) == biasWorkgroupSize", "ceilDiv(batchCount, min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
|
| 115 |
+
"derive": {
|
| 116 |
+
"scalar": "dtypes.T",
|
| 117 |
+
"vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
| 118 |
+
"workgroupSize": "biasWorkgroupSize",
|
| 119 |
+
"combineSubgroups": false,
|
| 120 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 121 |
+
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 122 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))",
|
| 123 |
+
"scalarVec4": true
|
| 124 |
+
},
|
| 125 |
+
"passes": [
|
| 126 |
+
{
|
| 127 |
+
"id": "main",
|
| 128 |
+
"name": "BiasSoftmax.WorkgroupScalarVec4",
|
| 129 |
+
"shader": "softmax-online.wgsl.jinja",
|
| 130 |
+
"derive": { "op": "\"biassoftmax\"", "useVec4": true },
|
| 131 |
+
"bindings": [
|
| 132 |
+
{ "arg": "data", "name": "x", "elementType": "$scalar" },
|
| 133 |
+
{ "arg": "bias", "name": "bias", "elementType": "$scalar" },
|
| 134 |
+
{ "arg": "output", "name": "y", "elementType": "$scalar" },
|
| 135 |
+
{
|
| 136 |
+
"name": "params",
|
| 137 |
+
"struct": [
|
| 138 |
+
{ "name": "rows", "type": "u32", "value": "batchCount" },
|
| 139 |
+
{ "name": "vecCols", "type": "u32", "value": "ceilDiv(blockSize, 4)" },
|
| 140 |
+
{ "name": "cols", "type": "u32", "value": "blockSize" }
|
| 141 |
+
]
|
| 142 |
+
}
|
| 143 |
+
],
|
| 144 |
+
"dispatch": { "x": "min(batchCount, 65535)", "y": "ceilDiv(batchCount, 65535)", "z": 1 }
|
| 145 |
+
}
|
| 146 |
+
]
|
| 147 |
+
},
|
| 148 |
{
|
| 149 |
"id": "longrow_split",
|
| 150 |
"priority": 40,
|
| 151 |
"when": ["blockSize >= 65536", "batchCount > 0", "batchCount <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceilDiv(blockSize, tunables.BLOCK_COLS) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
|
| 152 |
+
"derive": { "scalar": "dtypes.T", "combineSubgroups": false },
|
| 153 |
"intermediates": [
|
| 154 |
{
|
| 155 |
"id": "blockMax",
|
|
|
|
| 168 |
{
|
| 169 |
"id": "block_stats",
|
| 170 |
"name": "BiasSoftmax.LongRowBlockStats",
|
| 171 |
+
"shader": "softmax-longrow-stats.wgsl.jinja",
|
| 172 |
"derive": {
|
| 173 |
"stage": "\"block\"",
|
| 174 |
+
"biasRow": true,
|
| 175 |
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 176 |
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 177 |
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
|
|
|
| 179 |
"bindings": [
|
| 180 |
"data",
|
| 181 |
"bias",
|
| 182 |
+
{ "name": "blockMax", "elementType": "f32" },
|
| 183 |
+
{ "name": "blockSum", "elementType": "f32" },
|
| 184 |
{
|
| 185 |
"name": "params",
|
| 186 |
"struct": [
|
|
|
|
| 194 |
{
|
| 195 |
"id": "row_stats",
|
| 196 |
"name": "BiasSoftmax.LongRowStats",
|
| 197 |
+
"shader": "softmax-longrow-stats.wgsl.jinja",
|
| 198 |
"derive": { "stage": "\"row\"" },
|
| 199 |
"bindings": [
|
| 200 |
{ "name": "blockMax", "buffer": "read-only-storage", "elementType": "f32" },
|
| 201 |
{ "name": "blockSum", "buffer": "read-only-storage", "elementType": "f32" },
|
| 202 |
+
{ "name": "rowMax", "elementType": "f32" },
|
| 203 |
+
{ "name": "rowSum", "elementType": "f32" },
|
| 204 |
{
|
| 205 |
"name": "params",
|
| 206 |
"struct": [{ "name": "blocks", "type": "u32", "value": "ceilDiv(blockSize, tunables.BLOCK_COLS)" }]
|
|
|
|
| 233 |
"id": "packed_rows",
|
| 234 |
"priority": 30,
|
| 235 |
"when": ["blockSize > 0", "blockSize <= 8", "batchCount >= 64"],
|
| 236 |
+
"derive": { "scalar": "dtypes.T", "combineSubgroups": false, "packedRows": true },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
"passes": [
|
| 238 |
{
|
| 239 |
"id": "main",
|
|
|
|
| 268 |
"id": "adaptive_row",
|
| 269 |
"priority": 10,
|
| 270 |
"when": ["numel(shapes.data) >= 0"],
|
| 271 |
+
"derive": { "packedRows": false, "scalar": "dtypes.T", "combineSubgroups": "device.features.has(\"subgroups\")" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
"passes": [
|
| 273 |
{
|
| 274 |
"id": "main",
|
|
|
|
| 291 |
]
|
| 292 |
}
|
| 293 |
],
|
| 294 |
+
"dispatch": { "x": "min(batchCount, 65535)", "y": "ceilDiv(batchCount, 65535)", "z": 1 }
|
|
|
|
| 295 |
}
|
| 296 |
]
|
| 297 |
}
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,25 +1,30 @@
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.BiasSoftmax",
|
| 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 |
-
"bias-softmax-longrow-normalize.wgsl.jinja": "
|
| 12 |
-
"bias-softmax
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
"
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
},
|
| 18 |
-
"provenance": { "kernel": { "sha": "
|
| 19 |
"webgpu": {
|
| 20 |
-
"manifestSpec": "2.
|
| 21 |
"variants": {
|
| 22 |
-
"
|
|
|
|
|
|
|
|
|
|
| 23 |
"packed_rows": ["bias-softmax.wgsl.jinja"],
|
| 24 |
"adaptive_row": ["bias-softmax.wgsl.jinja"]
|
| 25 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.BiasSoftmax",
|
| 3 |
+
"id": "_com_microsoft_biassoftmax_webgpu_645de3f",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "ULVbXINWou5wlT+4e2N9gtCE7nF8AjENyJAZwBBGwP4=",
|
| 11 |
+
"bias-softmax-longrow-normalize.wgsl.jinja": "C4RjAERxuPnTg7GKB286RYMPx5HWXFd9LqKrUcAdtYs=",
|
| 12 |
+
"bias-softmax.wgsl.jinja": "ptSv4JQ/WGXTqUKNqFowI6g64po1WtcXEP0sjM2LHEk=",
|
| 13 |
+
"manifest.json": "P0QfnLqsi08AxrTCYJkPTTQ0nFFh24vGS/7rwAKoOMw=",
|
| 14 |
+
"softmax-longrow-stats.wgsl.jinja": "oj7lwGMXQNTAI9NWK3/PgXwLT2R3vtTEmh6HUw4BtLE=",
|
| 15 |
+
"softmax-online.wgsl.jinja": "N/W8ChLz0EbG2y9O85tk1rmZwc6CpvlQb5lwxgfvWks=",
|
| 16 |
+
"softmax-subgroup-rows.wgsl.jinja": "S9jb9PzuCJjMBpOcVe2hWmSupF9Nia9AA5OyaIlTwSk=",
|
| 17 |
+
"test.json": "Do/oRO/PgM8zp16pyq7EVawVJu1VThzud/P5lPfX+I8="
|
| 18 |
}
|
| 19 |
},
|
| 20 |
+
"provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
|
| 21 |
"webgpu": {
|
| 22 |
+
"manifestSpec": "2.1",
|
| 23 |
"variants": {
|
| 24 |
+
"online_subgroup_rows_vec4": ["softmax-subgroup-rows.wgsl.jinja"],
|
| 25 |
+
"online_workgroup_vec4": ["softmax-online.wgsl.jinja"],
|
| 26 |
+
"online_workgroup_scalar_vec4": ["softmax-online.wgsl.jinja"],
|
| 27 |
+
"longrow_split": ["bias-softmax-longrow-normalize.wgsl.jinja", "softmax-longrow-stats.wgsl.jinja"],
|
| 28 |
"packed_rows": ["bias-softmax.wgsl.jinja"],
|
| 29 |
"adaptive_row": ["bias-softmax.wgsl.jinja"]
|
| 30 |
}
|
build/webgpu/{bias-softmax-longrow-stats.wgsl.jinja → softmax-longrow-stats.wgsl.jinja}
RENAMED
|
@@ -1,24 +1,29 @@
|
|
| 1 |
-
//
|
| 2 |
-
//
|
| 3 |
-
//
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
{%
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
|
| 9 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 10 |
{% if stage == "block" %}
|
| 11 |
const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
{% if isInnerBroadcast
|
| 16 |
-
return (row / {{ innerRepeat
|
| 17 |
{% else %}
|
| 18 |
-
return (row % {{ biasBlockCountSpec
|
| 19 |
{% endif %}
|
| 20 |
}
|
| 21 |
{% endif %}
|
|
|
|
| 22 |
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 23 |
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 24 |
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
|
@@ -38,6 +43,7 @@ fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
|
| 38 |
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 39 |
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 40 |
}
|
|
|
|
| 41 |
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 42 |
return exp(shifted_value(value, maxValue));
|
| 43 |
}
|
|
@@ -47,94 +53,9 @@ fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
|
| 47 |
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 48 |
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 49 |
// calls before their shared partial storage is reused.
|
| 50 |
-
{% set
|
| 51 |
-
{% if combineSubgroups %}
|
| 52 |
-
// Cross-subgroup merge that assumes nothing about which invocations share a
|
| 53 |
-
// subgroup or how many subgroups there are: each subgroup's elected lane
|
| 54 |
-
// publishes the subgroup pair in the slot at its OWN invocation index and sets
|
| 55 |
-
// that index's bit in a workgroup bitmask; thread 0 then folds exactly the
|
| 56 |
-
// published slots, in ascending index order (the online (m, d) merge is not
|
| 57 |
-
// float-associative, so the order is fixed), and clears the mask for the next
|
| 58 |
-
// call as it reads it. Workgroup memory starts zeroed, so the mask needs no
|
| 59 |
-
// setup. Same three collectives as a single-subgroup reduce, two barriers.
|
| 60 |
-
var<workgroup> partialM: array<f32, WG>;
|
| 61 |
-
var<workgroup> partialD: array<f32, WG>;
|
| 62 |
-
var<workgroup> leaderMask: array<atomic<u32>, (WG + 31u) / 32u>;
|
| 63 |
-
var<workgroup> combinedMD: vec2<f32>;
|
| 64 |
-
|
| 65 |
-
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 66 |
-
// it (no barriers, no shared state). `subgroup_size` is the size of the current
|
| 67 |
-
// subgroup and uniform, so the test is exact and may guard the barriers below.
|
| 68 |
-
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 69 |
-
let sgM = subgroupMax(m);
|
| 70 |
-
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
| 71 |
-
// element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
|
| 72 |
-
// landed in d via exp(NaN); both survive the merge and are detected by the
|
| 73 |
-
// code after the reduction.
|
| 74 |
-
let sgD = subgroupAdd(d * exp_shift(m, sgM));
|
| 75 |
-
if (sgSize == WG) {
|
| 76 |
-
return vec2<f32>(sgM, sgD);
|
| 77 |
-
}
|
| 78 |
-
if (subgroupElect()) {
|
| 79 |
-
partialM[lidx] = sgM;
|
| 80 |
-
partialD[lidx] = sgD;
|
| 81 |
-
atomicOr(&leaderMask[lidx / 32u], 1u << (lidx % 32u));
|
| 82 |
-
}
|
| 83 |
-
workgroupBarrier();
|
| 84 |
-
if (lidx == 0u) {
|
| 85 |
-
var accM = -FLT_MAX;
|
| 86 |
-
var accD = 0.0;
|
| 87 |
-
for (var w = 0u; w < (WG + 31u) / 32u; w = w + 1u) {
|
| 88 |
-
var bits = atomicExchange(&leaderMask[w], 0u);
|
| 89 |
-
while (bits != 0u) {
|
| 90 |
-
let slot = w * 32u + firstTrailingBit(bits);
|
| 91 |
-
bits = bits & (bits - 1u);
|
| 92 |
-
let mNew = max(accM, partialM[slot]);
|
| 93 |
-
accD = accD * exp_shift(accM, mNew) + partialD[slot] * exp_shift(partialM[slot], mNew);
|
| 94 |
-
accM = mNew;
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
combinedMD = vec2<f32>(accM, accD);
|
| 98 |
-
}
|
| 99 |
-
workgroupBarrier();
|
| 100 |
-
return combinedMD;
|
| 101 |
-
}
|
| 102 |
-
{% else %}
|
| 103 |
-
{% set mdStreamed = mdStreams is defined %}
|
| 104 |
-
{% set mdStreams = mdStreams if mdStreams is defined else 1 %}
|
| 105 |
-
{% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
|
| 106 |
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 107 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 108 |
-
{% if mdStreamed %}
|
| 109 |
-
|
| 110 |
-
// In-place fold of {{ mdStreams }} streams. Input partials occupy
|
| 111 |
-
// partialM/partialD; stream s returns its merged pair in slot s * WG.
|
| 112 |
-
fn combine_partials_streams(lidx: u32) {
|
| 113 |
-
workgroupBarrier();
|
| 114 |
-
var stride = WG / 2u;
|
| 115 |
-
loop {
|
| 116 |
-
if (stride == 0u) {
|
| 117 |
-
break;
|
| 118 |
-
}
|
| 119 |
-
if (lidx < stride) {
|
| 120 |
-
{% for s in range(mdStreams) %}
|
| 121 |
-
{
|
| 122 |
-
let slot = {{ s }}u * WG + lidx;
|
| 123 |
-
let m1 = partialM[slot];
|
| 124 |
-
let d1 = partialD[slot];
|
| 125 |
-
let m2 = partialM[slot + stride];
|
| 126 |
-
let d2 = partialD[slot + stride];
|
| 127 |
-
let mNew = max(m1, m2);
|
| 128 |
-
partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 129 |
-
partialM[slot] = mNew;
|
| 130 |
-
}
|
| 131 |
-
{% endfor %}
|
| 132 |
-
}
|
| 133 |
-
workgroupBarrier();
|
| 134 |
-
stride = stride / 2u;
|
| 135 |
-
}
|
| 136 |
-
}
|
| 137 |
-
{% else %}
|
| 138 |
|
| 139 |
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 140 |
partialM[lidx] = m;
|
|
@@ -163,36 +84,35 @@ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
|
| 163 |
workgroupBarrier();
|
| 164 |
return merged;
|
| 165 |
}
|
| 166 |
-
{% endif %}
|
| 167 |
-
{% endif %}
|
| 168 |
-
|
| 169 |
|
| 170 |
@compute @workgroup_size(WG)
|
| 171 |
-
fn main(@builtin(workgroup_id)
|
| 172 |
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 173 |
{% if stage == "block" %}
|
| 174 |
-
let block =
|
| 175 |
-
let row =
|
| 176 |
-
let rowBase = row *
|
|
|
|
|
|
|
|
|
|
| 177 |
let colStart = block * BLOCK_COLS;
|
| 178 |
-
let colEnd = min(
|
| 179 |
|
| 180 |
var m = -FLT_MAX;
|
| 181 |
var d = 0.0;
|
| 182 |
-
for (var col = colStart + lid.x; col < colEnd; col
|
| 183 |
-
let
|
| 184 |
-
let v = f32(data[i]) + f32(bias[bias_index(i)]);
|
| 185 |
let mNew = max(m, v);
|
| 186 |
d = d * exp_shift(m, mNew) + exp_shift(v, mNew);
|
| 187 |
m = mNew;
|
| 188 |
}
|
| 189 |
{% else %}
|
| 190 |
-
let row =
|
| 191 |
let base = row * params.blocks;
|
| 192 |
|
| 193 |
var m = -FLT_MAX;
|
| 194 |
var d = 0.0;
|
| 195 |
-
for (var block = lid.x; block < params.blocks; block
|
| 196 |
let bm = blockMax[base + block];
|
| 197 |
let bd = blockSum[base + block];
|
| 198 |
let mNew = max(m, bm);
|
|
|
|
| 1 |
+
// Online-softmax (maximum, sum-exp) statistics for very long contiguous
|
| 2 |
+
// last-axis rows. The statistics are the same for softmax and log-softmax.
|
| 3 |
+
// The block stage assigns one workgroup to each (block, row), merges per-thread
|
| 4 |
+
// online pairs, and writes one partial pair. The row stage assigns one
|
| 5 |
+
// workgroup per row and folds those block partials into rowMax and rowSum.
|
| 6 |
+
{% set biasRow = biasRow | default(false) %}
|
| 7 |
+
{% set isInnerBroadcast = isInnerBroadcast if isInnerBroadcast is defined else false %}
|
| 8 |
+
{% set innerRepeat = innerRepeat if innerRepeat is defined else 1 %}
|
| 9 |
+
{% set biasBlockCountSpec = biasBlockCountSpec if biasBlockCountSpec is defined else 1 %}
|
| 10 |
+
{% set cols = "params.blockSize" if biasRow else "params.cols" %}
|
| 11 |
{{ env.wgsl.resourceDeclarations }}
|
| 12 |
|
| 13 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 14 |
{% if stage == "block" %}
|
| 15 |
const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
|
| 16 |
+
{% if biasRow %}
|
| 17 |
+
// Flattened bias-row address shared by packed, adaptive, and split rows.
|
| 18 |
+
fn bias_base(row: u32) -> u32 {
|
| 19 |
+
{% if isInnerBroadcast %}
|
| 20 |
+
return (row / {{ innerRepeat }}u) * params.blockSize;
|
| 21 |
{% else %}
|
| 22 |
+
return (row % {{ biasBlockCountSpec }}u) * params.blockSize;
|
| 23 |
{% endif %}
|
| 24 |
}
|
| 25 |
{% endif %}
|
| 26 |
+
{% endif %}
|
| 27 |
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 28 |
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 29 |
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
|
|
|
| 43 |
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 44 |
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 45 |
}
|
| 46 |
+
|
| 47 |
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 48 |
return exp(shifted_value(value, maxValue));
|
| 49 |
}
|
|
|
|
| 53 |
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 54 |
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 55 |
// calls before their shared partial storage is reused.
|
| 56 |
+
{% set mdExtent = "WG" %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 58 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 61 |
partialM[lidx] = m;
|
|
|
|
| 84 |
workgroupBarrier();
|
| 85 |
return merged;
|
| 86 |
}
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
@compute @workgroup_size(WG)
|
| 89 |
+
fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
| 90 |
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 91 |
{% if stage == "block" %}
|
| 92 |
+
let block = wg.x;
|
| 93 |
+
let row = wg.y;
|
| 94 |
+
let rowBase = row * {{ cols }};
|
| 95 |
+
{% if biasRow %}
|
| 96 |
+
let biasBase = bias_base(row);
|
| 97 |
+
{% endif %}
|
| 98 |
let colStart = block * BLOCK_COLS;
|
| 99 |
+
let colEnd = min({{ cols }}, colStart + BLOCK_COLS);
|
| 100 |
|
| 101 |
var m = -FLT_MAX;
|
| 102 |
var d = 0.0;
|
| 103 |
+
for (var col = colStart + lid.x; col < colEnd; col = col + WG) {
|
| 104 |
+
let v = {{ "f32(data[rowBase + col]) + f32(bias[biasBase + col])" if biasRow else "f32(x[rowBase + col])" }};
|
|
|
|
| 105 |
let mNew = max(m, v);
|
| 106 |
d = d * exp_shift(m, mNew) + exp_shift(v, mNew);
|
| 107 |
m = mNew;
|
| 108 |
}
|
| 109 |
{% else %}
|
| 110 |
+
let row = wg.x;
|
| 111 |
let base = row * params.blocks;
|
| 112 |
|
| 113 |
var m = -FLT_MAX;
|
| 114 |
var d = 0.0;
|
| 115 |
+
for (var block = lid.x; block < params.blocks; block = block + WG) {
|
| 116 |
let bm = blockMax[base + block];
|
| 117 |
let bd = blockSum[base + block];
|
| 118 |
let mNew = max(m, bm);
|
build/webgpu/softmax-online.wgsl.jinja
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
+
|
| 3 |
+
// Workgroup-level online normalization over one contiguous last-axis row per
|
| 4 |
+
// workgroup. Each thread keeps an online (maximum, denominator) pair over its
|
| 5 |
+
// strided chunk. The workgroup merges those pairs, then a fused second sweep
|
| 6 |
+
// writes the normalized result. Vectorized rows use native vec4 storage when
|
| 7 |
+
// aligned, or guarded scalar storage at unaligned row strides. Both paths retain
|
| 8 |
+
// f32 bias and reduction math.
|
| 9 |
+
//
|
| 10 |
+
// `workgroupSize` fixes the merge-tree width for this specialization.
|
| 11 |
+
// `scalarVec4` selects scalar storage for fused bias rows only.
|
| 12 |
+
{% macro read_vec4(c) %}
|
| 13 |
+
{% if scalarVec4 %}load_word(rowBase, biasBase, {{ c }}){% else %}vec4<f32>(x[rowBase + {{ c }}]) + vec4<f32>(bias[biasBase + {{ c }}]){% endif %}
|
| 14 |
+
{% endmacro %}
|
| 15 |
+
{% macro store_vec4(value, c) %}
|
| 16 |
+
{% if scalarVec4 %}
|
| 17 |
+
{% for i in range(4) %}
|
| 18 |
+
if ({{ c }} * 4u + {{ i }}u < params.cols) {
|
| 19 |
+
y[rowBase + {{ c }} * 4u + {{ i }}u] = {{ scalar }}({{ value }}[{{ i }}]);
|
| 20 |
+
}
|
| 21 |
+
{% endfor %}
|
| 22 |
+
{% else %}
|
| 23 |
+
y[rowBase + {{ c }}] = vec4<{{ scalar }}>({{ value }});
|
| 24 |
+
{% endif %}
|
| 25 |
+
{% endmacro %}
|
| 26 |
+
const WG: u32 = {{ workgroupSize }}u;
|
| 27 |
+
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 28 |
+
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 29 |
+
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
| 30 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 31 |
+
// here changes +inf-row behavior.
|
| 32 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 33 |
+
|
| 34 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 35 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 39 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 40 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 41 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 42 |
+
fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
| 43 |
+
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 44 |
+
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 48 |
+
return exp(shifted_value(value, maxValue));
|
| 49 |
+
}
|
| 50 |
+
// Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
|
| 51 |
+
// mNew = max(m1, m2)
|
| 52 |
+
// dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
|
| 53 |
+
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 54 |
+
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 55 |
+
// calls before their shared partial storage is reused.
|
| 56 |
+
{% set mdExtent = "WG" %}
|
| 57 |
+
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 58 |
+
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 59 |
+
|
| 60 |
+
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 61 |
+
partialM[lidx] = m;
|
| 62 |
+
partialD[lidx] = d;
|
| 63 |
+
workgroupBarrier();
|
| 64 |
+
var stride = WG / 2u;
|
| 65 |
+
loop {
|
| 66 |
+
if (stride == 0u) {
|
| 67 |
+
break;
|
| 68 |
+
}
|
| 69 |
+
if (lidx < stride) {
|
| 70 |
+
let m1 = partialM[lidx];
|
| 71 |
+
let d1 = partialD[lidx];
|
| 72 |
+
let m2 = partialM[lidx + stride];
|
| 73 |
+
let d2 = partialD[lidx + stride];
|
| 74 |
+
let mNew = max(m1, m2);
|
| 75 |
+
partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 76 |
+
partialM[lidx] = mNew;
|
| 77 |
+
}
|
| 78 |
+
workgroupBarrier();
|
| 79 |
+
stride = stride / 2u;
|
| 80 |
+
}
|
| 81 |
+
let merged = vec2<f32>(partialM[0], partialD[0]);
|
| 82 |
+
// Trailing barrier so back-to-back calls cannot race a next call's partial
|
| 83 |
+
// stores against this call's reads of slot 0.
|
| 84 |
+
workgroupBarrier();
|
| 85 |
+
return merged;
|
| 86 |
+
}
|
| 87 |
+
{% if useVec4 and scalarVec4 %}
|
| 88 |
+
fn load_word(rowBase: u32, biasBase: u32, c: u32) -> vec4<f32> {
|
| 89 |
+
let col = c * 4u;
|
| 90 |
+
if (col + 3u < params.cols) {
|
| 91 |
+
return vec4<f32>({% for i in range(4) %}f32(x[rowBase + col + {{ i }}u]){% if not loop.last %}, {% endif %}{% endfor %}) + vec4<f32>({% for i in range(4) %}f32(bias[biasBase + col + {{ i }}u]){% if not loop.last %}, {% endif %}{% endfor %});
|
| 92 |
+
}
|
| 93 |
+
var v = vec4<f32>(-FLT_MAX);
|
| 94 |
+
{% for i in range(4) %}
|
| 95 |
+
if (col + {{ i }}u < params.cols) {
|
| 96 |
+
v[{{ i }}] = f32(x[rowBase + col + {{ i }}u]) + f32(bias[biasBase + col + {{ i }}u]);
|
| 97 |
+
}
|
| 98 |
+
{% endfor %}
|
| 99 |
+
return v;
|
| 100 |
+
}
|
| 101 |
+
{% endif %}
|
| 102 |
+
|
| 103 |
+
@compute @workgroup_size({{ workgroupSize }})
|
| 104 |
+
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 105 |
+
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 106 |
+
// 2D-folded row index: wid.y carries the high bits past the
|
| 107 |
+
// per-axis dispatch fold width (rows > the per-axis dispatch fold width).
|
| 108 |
+
let row = wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 109 |
+
if (row >= params.rows) {
|
| 110 |
+
return;
|
| 111 |
+
}
|
| 112 |
+
let rowBase = row * params.{% if scalarVec4 %}cols{% else %}vecCols{% endif %};
|
| 113 |
+
{% if isInnerBroadcast %}
|
| 114 |
+
let biasBase = (row / {{ innerRepeat }}u) * params.{% if scalarVec4 %}cols{% else %}vecCols{% endif %};
|
| 115 |
+
{% else %}
|
| 116 |
+
let biasBase = (row % {{ biasBlockCountSpec }}u) * params.{% if scalarVec4 %}cols{% else %}vecCols{% endif %};
|
| 117 |
+
{% endif %}
|
| 118 |
+
|
| 119 |
+
// Thread-local online (m, d) over a strided chunk; f32 accumulation.
|
| 120 |
+
var m = -FLT_MAX;
|
| 121 |
+
var d = 0.0;
|
| 122 |
+
for (var c = lid.x; c < params.vecCols; c = c + WG) {
|
| 123 |
+
let v = {{ read_vec4("c") }};
|
| 124 |
+
let mNew = max(m, max(max(v.x, v.y), max(v.z, v.w)));
|
| 125 |
+
{% if scalarVec4 %}
|
| 126 |
+
// Padding is an identity even when valid values equal -FLT_MAX.
|
| 127 |
+
// Its exponential must contribute zero, not exp(0).
|
| 128 |
+
let valid = vec4<u32>(c * 4u) + vec4<u32>(0u, 1u, 2u, 3u) < vec4<u32>(params.cols);
|
| 129 |
+
let terms = vec4<f32>(exp_shift(v.x, mNew), exp_shift(v.y, mNew), exp_shift(v.z, mNew), exp_shift(v.w, mNew));
|
| 130 |
+
let masked = select(vec4<f32>(0.0), terms, valid);
|
| 131 |
+
d = d * exp_shift(m, mNew) + masked.x + masked.y + masked.z + masked.w;
|
| 132 |
+
{% else %}
|
| 133 |
+
d = d * exp_shift(m, mNew)
|
| 134 |
+
+ exp_shift(v.x, mNew) + exp_shift(v.y, mNew) + exp_shift(v.z, mNew) + exp_shift(v.w, mNew);
|
| 135 |
+
{% endif %}
|
| 136 |
+
m = mNew;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
let rowMD = combine_partials(m, d, lid.x);
|
| 140 |
+
let finalM = rowMD.x;
|
| 141 |
+
let finalD = rowMD.y;
|
| 142 |
+
|
| 143 |
+
let inv = select(0.0, 1.0 / finalD, finalD > 0.0);
|
| 144 |
+
for (var c = lid.x; c < params.vecCols; c += WG) {
|
| 145 |
+
let v = {{ read_vec4("c") }};
|
| 146 |
+
let o = vec4<f32>(exp_shift(v.x, finalM), exp_shift(v.y, finalM), exp_shift(v.z, finalM), exp_shift(v.w, finalM)) * inv;
|
| 147 |
+
{{ store_vec4("o", "c") }}
|
| 148 |
+
}
|
| 149 |
+
}
|
build/webgpu/softmax-subgroup-rows.wgsl.jinja
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
enable subgroups;
|
| 2 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 3 |
+
|
| 4 |
+
// Subgroup-per-row online normalization over a contiguous last axis of
|
| 5 |
+
// moderate width. One SUBGROUP owns each row and a workgroup carries as many
|
| 6 |
+
// rows as it has subgroups, so the (max, denom) merge is one pair of subgroup
|
| 7 |
+
// collectives: no workgroup memory, no barriers. Each lane keeps its vec4 words
|
| 8 |
+
// in registers between the statistics sweep and the normalizing store, so the
|
| 9 |
+
// row is read from memory once. Each lane forms an online (maximum,
|
| 10 |
+
// denominator) pair, and subgroup collectives merge those pairs.
|
| 11 |
+
const WG: u32 = {{ workgroupSize }}u;
|
| 12 |
+
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 13 |
+
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 14 |
+
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
| 15 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 16 |
+
// here changes +inf-row behavior.
|
| 17 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 18 |
+
|
| 19 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 20 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 24 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 25 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 26 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 27 |
+
fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
| 28 |
+
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 29 |
+
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 33 |
+
return exp(shifted_value(value, maxValue));
|
| 34 |
+
}
|
| 35 |
+
var<workgroup> sgLaneClaims: atomic<u32>;
|
| 36 |
+
|
| 37 |
+
struct SubgroupLogicalLanes {
|
| 38 |
+
ltid: u32,
|
| 39 |
+
ord: u32,
|
| 40 |
+
rank: u32,
|
| 41 |
+
count: u32,
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
fn subgroup_logical_lanes(rank: u32, count: u32) -> SubgroupLogicalLanes {
|
| 45 |
+
// Every lane performs the atomic so no collective follows a lane guard: a
|
| 46 |
+
// subgroup op after a closed `if (rank == 0u)` block is a reconvergence
|
| 47 |
+
// hazard, since every subgroup operation must be reached in uniform control
|
| 48 |
+
// flow. Only the lane-0 lane adds its subgroup's claim, every other lane
|
| 49 |
+
// adds 0 and discards its snapshot. Lane 0 is the lowest lane, which is the
|
| 50 |
+
// lane `subgroupBroadcastFirst` reads.
|
| 51 |
+
let ticket = atomicAdd(&sgLaneClaims, select(0u, count | (1u << 16u), rank == 0u));
|
| 52 |
+
let claim = subgroupBroadcastFirst(ticket);
|
| 53 |
+
return SubgroupLogicalLanes((claim & 0xffffu) + rank, claim >> 16u, rank, count);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
@compute @workgroup_size({{ workgroupSize }})
|
| 57 |
+
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 58 |
+
@builtin(subgroup_invocation_id) sgLaneId: u32,
|
| 59 |
+
@builtin(subgroup_size) sgSize: u32) {
|
| 60 |
+
// Rows tile the folded workgroup grid; the lanes of one subgroup share a row.
|
| 61 |
+
// Which subgroup owns which row, and which lane holds which columns, come from
|
| 62 |
+
// the logical lane coordinates, so no assumption is made about which
|
| 63 |
+
// invocations share a subgroup.
|
| 64 |
+
let L = subgroup_logical_lanes(sgLaneId, sgSize);
|
| 65 |
+
let sgLane = L.rank;
|
| 66 |
+
let rowsPerWorkgroup = WG / sgSize;
|
| 67 |
+
let row = (wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u) * rowsPerWorkgroup + L.ord;
|
| 68 |
+
// A subgroup past the last row runs the collectives on an empty row and
|
| 69 |
+
// stores nothing, so every collective stays in uniform control flow.
|
| 70 |
+
let rowValid = row < params.rows;
|
| 71 |
+
let vecLimit = select(0u, params.vecCols, rowValid);
|
| 72 |
+
let rowBase = row * params.vecCols;
|
| 73 |
+
{% if isInnerBroadcast %}
|
| 74 |
+
let biasBase = (row / {{ innerRepeat }}u) * params.vecCols;
|
| 75 |
+
{% else %}
|
| 76 |
+
let biasBase = (row % {{ biasBlockCountSpec }}u) * params.vecCols;
|
| 77 |
+
{% endif %}
|
| 78 |
+
|
| 79 |
+
// Lane-local online (m, d) over the lane's strided words; f32 accumulation.
|
| 80 |
+
var m = -FLT_MAX;
|
| 81 |
+
var d = 0.0;
|
| 82 |
+
{% for i in range(vecsPerLane) %}
|
| 83 |
+
var v{{ i }} = vec4<f32>(0.0);
|
| 84 |
+
let c{{ i }} = sgLane + {{ i }}u * sgSize;
|
| 85 |
+
if (c{{ i }} < vecLimit) {
|
| 86 |
+
v{{ i }} = vec4<f32>(x[rowBase + c{{ i }}]) + vec4<f32>(bias[biasBase + c{{ i }}]);
|
| 87 |
+
let mNew{{ i }} = max(m, max(max(v{{ i }}.x, v{{ i }}.y), max(v{{ i }}.z, v{{ i }}.w)));
|
| 88 |
+
d = d * exp_shift(m, mNew{{ i }})
|
| 89 |
+
+ exp_shift(v{{ i }}.x, mNew{{ i }}) + exp_shift(v{{ i }}.y, mNew{{ i }})
|
| 90 |
+
+ exp_shift(v{{ i }}.z, mNew{{ i }}) + exp_shift(v{{ i }}.w, mNew{{ i }});
|
| 91 |
+
m = mNew{{ i }};
|
| 92 |
+
}
|
| 93 |
+
{% endfor %}
|
| 94 |
+
|
| 95 |
+
// Subgroup merge: a lane with no words contributes d == 0 (exact identity).
|
| 96 |
+
let finalM = subgroupMax(m);
|
| 97 |
+
let finalD = subgroupAdd(d * exp_shift(m, finalM));
|
| 98 |
+
|
| 99 |
+
// BiasSoftmax treats an entirely additive-masked row as zero.
|
| 100 |
+
let inv = select(0.0, 1.0 / finalD, finalD > 0.0);
|
| 101 |
+
{% for i in range(vecsPerLane) %}
|
| 102 |
+
if (c{{ i }} < vecLimit) {
|
| 103 |
+
let o{{ i }} = vec4<f32>(
|
| 104 |
+
exp_shift(v{{ i }}.x, finalM), exp_shift(v{{ i }}.y, finalM),
|
| 105 |
+
exp_shift(v{{ i }}.z, finalM), exp_shift(v{{ i }}.w, finalM)) * inv;
|
| 106 |
+
y[rowBase + c{{ i }}] = vec4<{{ scalar }}>(o{{ i }});
|
| 107 |
+
}
|
| 108 |
+
{% endfor %}
|
| 109 |
+
}
|
build/webgpu/test.json
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
"cases": [
|
| 3 |
{
|
| 4 |
"name": "inner_broadcast_consecutive_row_groups",
|
|
@@ -461,6 +465,442 @@
|
|
| 461 |
}
|
| 462 |
},
|
| 463 |
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 1, 2, 1, 2, 2, 2], "tolerance": 0.000002 } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
}
|
| 465 |
]
|
| 466 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"fixtureArrays": {
|
| 3 |
+
"subgroup_rows_float32_nonfinite_bias_input_bias": [0.25, 0.25, 0.25, "Infinity", 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, "NaN", 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25],
|
| 4 |
+
"ragged_float32_inner_broadcast_nonfinite_tail_input_bias": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "NaN", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity"]
|
| 5 |
+
},
|
| 6 |
"cases": [
|
| 7 |
{
|
| 8 |
"name": "inner_broadcast_consecutive_row_groups",
|
|
|
|
| 465 |
}
|
| 466 |
},
|
| 467 |
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 1, 2, 1, 2, 2, 2], "tolerance": 0.000002 } }
|
| 468 |
+
},
|
| 469 |
+
{
|
| 470 |
+
"name": "bias_row_tail_float32_6x513_inner1_wg256",
|
| 471 |
+
"provenance": {
|
| 472 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 473 |
+
},
|
| 474 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 475 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 476 |
+
"inputs": {
|
| 477 |
+
"data": {
|
| 478 |
+
"dtype": "float32",
|
| 479 |
+
"shape": [6, 513],
|
| 480 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 481 |
+
},
|
| 482 |
+
"bias": {
|
| 483 |
+
"dtype": "float32",
|
| 484 |
+
"shape": [2, 513],
|
| 485 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 486 |
+
}
|
| 487 |
+
},
|
| 488 |
+
"outputs": { "output": { "dtype": "float32", "shape": [6, 513], "tolerance": 0.000001 } }
|
| 489 |
+
},
|
| 490 |
+
{
|
| 491 |
+
"name": "bias_row_tail_float32_6x4095_inner0_wg256",
|
| 492 |
+
"provenance": {
|
| 493 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 494 |
+
},
|
| 495 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 496 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 497 |
+
"inputs": {
|
| 498 |
+
"data": {
|
| 499 |
+
"dtype": "float32",
|
| 500 |
+
"shape": [6, 4095],
|
| 501 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 502 |
+
},
|
| 503 |
+
"bias": {
|
| 504 |
+
"dtype": "float32",
|
| 505 |
+
"shape": [3, 4095],
|
| 506 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 507 |
+
}
|
| 508 |
+
},
|
| 509 |
+
"outputs": { "output": { "dtype": "float32", "shape": [6, 4095], "tolerance": 1e-7 } }
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"name": "bias_row_tail_float32_4x65537_inner1_wg64",
|
| 513 |
+
"provenance": {
|
| 514 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 515 |
+
},
|
| 516 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 517 |
+
"tunables": { "WORKGROUP_SIZE": 64 },
|
| 518 |
+
"inputs": {
|
| 519 |
+
"data": {
|
| 520 |
+
"dtype": "float32",
|
| 521 |
+
"shape": [4, 65537],
|
| 522 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 523 |
+
},
|
| 524 |
+
"bias": {
|
| 525 |
+
"dtype": "float32",
|
| 526 |
+
"shape": [2, 65537],
|
| 527 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 528 |
+
}
|
| 529 |
+
},
|
| 530 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 65537], "tolerance": 1e-8 } }
|
| 531 |
+
},
|
| 532 |
+
{
|
| 533 |
+
"name": "bias_row_tail_float32_4x131073_inner0_wg256",
|
| 534 |
+
"provenance": {
|
| 535 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 536 |
+
},
|
| 537 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 538 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 539 |
+
"inputs": {
|
| 540 |
+
"data": {
|
| 541 |
+
"dtype": "float32",
|
| 542 |
+
"shape": [4, 131073],
|
| 543 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 544 |
+
},
|
| 545 |
+
"bias": {
|
| 546 |
+
"dtype": "float32",
|
| 547 |
+
"shape": [2, 131073],
|
| 548 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 549 |
+
}
|
| 550 |
+
},
|
| 551 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 131073], "tolerance": 1e-8 } }
|
| 552 |
+
},
|
| 553 |
+
{
|
| 554 |
+
"name": "bias_row_tail_float16_6x513_inner1_wg256",
|
| 555 |
+
"provenance": {
|
| 556 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 557 |
+
},
|
| 558 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 559 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 560 |
+
"inputs": {
|
| 561 |
+
"data": {
|
| 562 |
+
"dtype": "float16",
|
| 563 |
+
"shape": [6, 513],
|
| 564 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 565 |
+
},
|
| 566 |
+
"bias": {
|
| 567 |
+
"dtype": "float16",
|
| 568 |
+
"shape": [2, 513],
|
| 569 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 570 |
+
}
|
| 571 |
+
},
|
| 572 |
+
"outputs": { "output": { "dtype": "float16", "shape": [6, 513], "tolerance": 0.00001 } }
|
| 573 |
+
},
|
| 574 |
+
{
|
| 575 |
+
"name": "bias_row_tail_float16_6x4095_inner0_wg256",
|
| 576 |
+
"provenance": {
|
| 577 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 578 |
+
},
|
| 579 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 580 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 581 |
+
"inputs": {
|
| 582 |
+
"data": {
|
| 583 |
+
"dtype": "float16",
|
| 584 |
+
"shape": [6, 4095],
|
| 585 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 586 |
+
},
|
| 587 |
+
"bias": {
|
| 588 |
+
"dtype": "float16",
|
| 589 |
+
"shape": [3, 4095],
|
| 590 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 591 |
+
}
|
| 592 |
+
},
|
| 593 |
+
"outputs": { "output": { "dtype": "float16", "shape": [6, 4095], "tolerance": 0.000001 } }
|
| 594 |
+
},
|
| 595 |
+
{
|
| 596 |
+
"name": "bias_row_tail_float16_4x65537_inner1_wg64",
|
| 597 |
+
"provenance": {
|
| 598 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 599 |
+
},
|
| 600 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 601 |
+
"tunables": { "WORKGROUP_SIZE": 64 },
|
| 602 |
+
"inputs": {
|
| 603 |
+
"data": {
|
| 604 |
+
"dtype": "float16",
|
| 605 |
+
"shape": [4, 65537],
|
| 606 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 607 |
+
},
|
| 608 |
+
"bias": {
|
| 609 |
+
"dtype": "float16",
|
| 610 |
+
"shape": [2, 65537],
|
| 611 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 612 |
+
}
|
| 613 |
+
},
|
| 614 |
+
"outputs": { "output": { "dtype": "float16", "shape": [4, 65537], "tolerance": 1e-7 } }
|
| 615 |
+
},
|
| 616 |
+
{
|
| 617 |
+
"name": "bias_row_tail_float16_4x131073_inner0_wg256",
|
| 618 |
+
"provenance": {
|
| 619 |
+
"notes": "Odd suffix lengths cross invocation and block boundaries. Distinct bias rows distinguish cyclic reuse from consecutive row groups."
|
| 620 |
+
},
|
| 621 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 622 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 623 |
+
"inputs": {
|
| 624 |
+
"data": {
|
| 625 |
+
"dtype": "float16",
|
| 626 |
+
"shape": [4, 131073],
|
| 627 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 628 |
+
},
|
| 629 |
+
"bias": {
|
| 630 |
+
"dtype": "float16",
|
| 631 |
+
"shape": [2, 131073],
|
| 632 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.037, "cosStep": 0.053 }
|
| 633 |
+
}
|
| 634 |
+
},
|
| 635 |
+
"outputs": { "output": { "dtype": "float16", "shape": [4, 131073], "tolerance": 1e-7 } }
|
| 636 |
+
},
|
| 637 |
+
{
|
| 638 |
+
"name": "subgroup_rows_float32_65x256_bias5_inner1",
|
| 639 |
+
"provenance": {
|
| 640 |
+
"notes": "Subgroup-row route boundary: row tail or register budget boundary with flattened bias-row reuse. Values are added and accumulated in float32 before the output cast."
|
| 641 |
+
},
|
| 642 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 643 |
+
"inputs": {
|
| 644 |
+
"data": {
|
| 645 |
+
"dtype": "float32",
|
| 646 |
+
"shape": [65, 256],
|
| 647 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 2.0 }
|
| 648 |
+
},
|
| 649 |
+
"bias": {
|
| 650 |
+
"dtype": "float32",
|
| 651 |
+
"shape": [5, 256],
|
| 652 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 1.0 }
|
| 653 |
+
}
|
| 654 |
+
},
|
| 655 |
+
"outputs": { "output": { "dtype": "float32", "shape": [65, 256], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 656 |
+
},
|
| 657 |
+
{
|
| 658 |
+
"name": "subgroup_rows_float32_64x1024_bias4_inner0",
|
| 659 |
+
"provenance": {
|
| 660 |
+
"notes": "Subgroup-row route boundary: row tail or register budget boundary with flattened bias-row reuse. Values are added and accumulated in float32 before the output cast."
|
| 661 |
+
},
|
| 662 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 663 |
+
"inputs": {
|
| 664 |
+
"data": {
|
| 665 |
+
"dtype": "float32",
|
| 666 |
+
"shape": [64, 1024],
|
| 667 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 2.0 }
|
| 668 |
+
},
|
| 669 |
+
"bias": {
|
| 670 |
+
"dtype": "float32",
|
| 671 |
+
"shape": [4, 1024],
|
| 672 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 1.0 }
|
| 673 |
+
}
|
| 674 |
+
},
|
| 675 |
+
"outputs": { "output": { "dtype": "float32", "shape": [64, 1024], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 676 |
+
},
|
| 677 |
+
{
|
| 678 |
+
"name": "subgroup_rows_float32_nonfinite_bias",
|
| 679 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 680 |
+
"inputs": {
|
| 681 |
+
"data": { "dtype": "float32", "shape": [64, 64], "data": { "kind": "constant", "value": 0.5 } },
|
| 682 |
+
"bias": {
|
| 683 |
+
"dtype": "float32",
|
| 684 |
+
"shape": [4, 64],
|
| 685 |
+
"data": {
|
| 686 |
+
"kind": "values",
|
| 687 |
+
"values": { "$ref": "#/fixtureArrays/subgroup_rows_float32_nonfinite_bias_input_bias" }
|
| 688 |
+
}
|
| 689 |
+
}
|
| 690 |
+
},
|
| 691 |
+
"outputs": {
|
| 692 |
+
"output": {
|
| 693 |
+
"dtype": "float32",
|
| 694 |
+
"shape": [64, 64],
|
| 695 |
+
"allowNaN": true,
|
| 696 |
+
"tolerance": 0.00001,
|
| 697 |
+
"relTolerance": 0.0001
|
| 698 |
+
}
|
| 699 |
+
},
|
| 700 |
+
"provenance": {
|
| 701 |
+
"notes": "Subgroup-row bias fusion must retain the operator semantics for positive infinity, a NaN bias entry, and fully negative-infinity masked rows; this fixture checks their positions as well as finite neighboring rows."
|
| 702 |
+
}
|
| 703 |
+
},
|
| 704 |
+
{
|
| 705 |
+
"name": "subgroup_rows_float16_65x256_bias5_inner1",
|
| 706 |
+
"provenance": {
|
| 707 |
+
"notes": "Subgroup-row route boundary: row tail or register budget boundary with flattened bias-row reuse. Values are added and accumulated in float32 before the output cast."
|
| 708 |
+
},
|
| 709 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 710 |
+
"inputs": {
|
| 711 |
+
"data": {
|
| 712 |
+
"dtype": "float16",
|
| 713 |
+
"shape": [65, 256],
|
| 714 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 2.0 }
|
| 715 |
+
},
|
| 716 |
+
"bias": {
|
| 717 |
+
"dtype": "float16",
|
| 718 |
+
"shape": [5, 256],
|
| 719 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 1.0 }
|
| 720 |
+
}
|
| 721 |
+
},
|
| 722 |
+
"outputs": { "output": { "dtype": "float16", "shape": [65, 256], "tolerance": 0.001, "relTolerance": 0.002 } }
|
| 723 |
+
},
|
| 724 |
+
{
|
| 725 |
+
"name": "subgroup_rows_float16_64x1024_bias4_inner0",
|
| 726 |
+
"provenance": {
|
| 727 |
+
"notes": "Subgroup-row route boundary: row tail or register budget boundary with flattened bias-row reuse. Values are added and accumulated in float32 before the output cast."
|
| 728 |
+
},
|
| 729 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 730 |
+
"inputs": {
|
| 731 |
+
"data": {
|
| 732 |
+
"dtype": "float16",
|
| 733 |
+
"shape": [64, 1024],
|
| 734 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 2.0 }
|
| 735 |
+
},
|
| 736 |
+
"bias": {
|
| 737 |
+
"dtype": "float16",
|
| 738 |
+
"shape": [4, 1024],
|
| 739 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 1.0 }
|
| 740 |
+
}
|
| 741 |
+
},
|
| 742 |
+
"outputs": { "output": { "dtype": "float16", "shape": [64, 1024], "tolerance": 0.001, "relTolerance": 0.002 } }
|
| 743 |
+
},
|
| 744 |
+
{
|
| 745 |
+
"name": "subgroup_rows_float16_nonfinite_bias",
|
| 746 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 747 |
+
"inputs": {
|
| 748 |
+
"data": { "dtype": "float16", "shape": [64, 64], "data": { "kind": "constant", "value": 0.5 } },
|
| 749 |
+
"bias": {
|
| 750 |
+
"dtype": "float16",
|
| 751 |
+
"shape": [4, 64],
|
| 752 |
+
"data": {
|
| 753 |
+
"kind": "values",
|
| 754 |
+
"values": { "$ref": "#/fixtureArrays/subgroup_rows_float32_nonfinite_bias_input_bias" }
|
| 755 |
+
}
|
| 756 |
+
}
|
| 757 |
+
},
|
| 758 |
+
"outputs": {
|
| 759 |
+
"output": { "dtype": "float16", "shape": [64, 64], "allowNaN": true, "tolerance": 0.001, "relTolerance": 0.002 }
|
| 760 |
+
},
|
| 761 |
+
"provenance": {
|
| 762 |
+
"notes": "Subgroup-row bias fusion must retain the operator semantics for positive infinity, a NaN bias entry, and fully negative-infinity masked rows; this fixture checks their positions as well as finite neighboring rows."
|
| 763 |
+
}
|
| 764 |
+
},
|
| 765 |
+
{
|
| 766 |
+
"name": "ragged_float32_negative_limit_cols33",
|
| 767 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 768 |
+
"inputs": {
|
| 769 |
+
"data": {
|
| 770 |
+
"dtype": "float32",
|
| 771 |
+
"shape": [3, 33],
|
| 772 |
+
"data": { "kind": "constant", "value": -3.4028234663852886e+38 }
|
| 773 |
+
},
|
| 774 |
+
"bias": { "dtype": "float32", "shape": [1, 33], "data": { "kind": "constant", "value": 0.0 } }
|
| 775 |
+
},
|
| 776 |
+
"outputs": { "output": { "dtype": "float32", "shape": [3, 33], "tolerance": 1e-7, "relTolerance": 0.00001 } },
|
| 777 |
+
"provenance": {
|
| 778 |
+
"notes": "Invalid logical-vec4 tail components must contribute exactly zero denominator, including when valid data equals the negative finite-limit sentinel."
|
| 779 |
+
}
|
| 780 |
+
},
|
| 781 |
+
{
|
| 782 |
+
"name": "ragged_float32_negative_limit_cols65",
|
| 783 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 784 |
+
"inputs": {
|
| 785 |
+
"data": {
|
| 786 |
+
"dtype": "float32",
|
| 787 |
+
"shape": [3, 65],
|
| 788 |
+
"data": { "kind": "constant", "value": -3.4028234663852886e+38 }
|
| 789 |
+
},
|
| 790 |
+
"bias": { "dtype": "float32", "shape": [1, 65], "data": { "kind": "constant", "value": 0.0 } }
|
| 791 |
+
},
|
| 792 |
+
"outputs": { "output": { "dtype": "float32", "shape": [3, 65], "tolerance": 1e-7, "relTolerance": 0.00001 } },
|
| 793 |
+
"provenance": {
|
| 794 |
+
"notes": "Invalid logical-vec4 tail components must contribute exactly zero denominator, including when valid data equals the negative finite-limit sentinel."
|
| 795 |
+
}
|
| 796 |
+
},
|
| 797 |
+
{
|
| 798 |
+
"name": "ragged_float32_negative_limit_cols67",
|
| 799 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 800 |
+
"inputs": {
|
| 801 |
+
"data": {
|
| 802 |
+
"dtype": "float32",
|
| 803 |
+
"shape": [3, 67],
|
| 804 |
+
"data": { "kind": "constant", "value": -3.4028234663852886e+38 }
|
| 805 |
+
},
|
| 806 |
+
"bias": { "dtype": "float32", "shape": [1, 67], "data": { "kind": "constant", "value": 0.0 } }
|
| 807 |
+
},
|
| 808 |
+
"outputs": { "output": { "dtype": "float32", "shape": [3, 67], "tolerance": 1e-7, "relTolerance": 0.00001 } },
|
| 809 |
+
"provenance": {
|
| 810 |
+
"notes": "Invalid logical-vec4 tail components must contribute exactly zero denominator, including when valid data equals the negative finite-limit sentinel."
|
| 811 |
+
}
|
| 812 |
+
},
|
| 813 |
+
{
|
| 814 |
+
"name": "ragged_float32_inner_broadcast_nonfinite_tail",
|
| 815 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 816 |
+
"inputs": {
|
| 817 |
+
"data": {
|
| 818 |
+
"dtype": "float32",
|
| 819 |
+
"shape": [6, 67],
|
| 820 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 2.0 }
|
| 821 |
+
},
|
| 822 |
+
"bias": {
|
| 823 |
+
"dtype": "float32",
|
| 824 |
+
"shape": [2, 67],
|
| 825 |
+
"data": {
|
| 826 |
+
"kind": "values",
|
| 827 |
+
"values": { "$ref": "#/fixtureArrays/ragged_float32_inner_broadcast_nonfinite_tail_input_bias" }
|
| 828 |
+
}
|
| 829 |
+
}
|
| 830 |
+
},
|
| 831 |
+
"outputs": {
|
| 832 |
+
"output": { "dtype": "float32", "shape": [6, 67], "tolerance": 1e-7, "relTolerance": 0.00001, "allowNaN": true }
|
| 833 |
+
},
|
| 834 |
+
"provenance": {
|
| 835 |
+
"notes": "Consecutive groups reuse each ragged bias row: first group has a NaN in the last valid component, second group is fully additive-masked. Check exact NaN positions and zero rows."
|
| 836 |
+
}
|
| 837 |
+
},
|
| 838 |
+
{
|
| 839 |
+
"name": "ragged_float16_negative_limit_cols33",
|
| 840 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 841 |
+
"inputs": {
|
| 842 |
+
"data": { "dtype": "float16", "shape": [3, 33], "data": { "kind": "constant", "value": -65504.0 } },
|
| 843 |
+
"bias": { "dtype": "float16", "shape": [1, 33], "data": { "kind": "constant", "value": 0.0 } }
|
| 844 |
+
},
|
| 845 |
+
"outputs": { "output": { "dtype": "float16", "shape": [3, 33], "tolerance": 0.00001, "relTolerance": 0.001 } },
|
| 846 |
+
"provenance": {
|
| 847 |
+
"notes": "Invalid logical-vec4 tail components must contribute exactly zero denominator, including when valid data equals the negative finite-limit sentinel."
|
| 848 |
+
}
|
| 849 |
+
},
|
| 850 |
+
{
|
| 851 |
+
"name": "ragged_float16_negative_limit_cols65",
|
| 852 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 853 |
+
"inputs": {
|
| 854 |
+
"data": { "dtype": "float16", "shape": [3, 65], "data": { "kind": "constant", "value": -65504.0 } },
|
| 855 |
+
"bias": { "dtype": "float16", "shape": [1, 65], "data": { "kind": "constant", "value": 0.0 } }
|
| 856 |
+
},
|
| 857 |
+
"outputs": { "output": { "dtype": "float16", "shape": [3, 65], "tolerance": 0.00001, "relTolerance": 0.001 } },
|
| 858 |
+
"provenance": {
|
| 859 |
+
"notes": "Invalid logical-vec4 tail components must contribute exactly zero denominator, including when valid data equals the negative finite-limit sentinel."
|
| 860 |
+
}
|
| 861 |
+
},
|
| 862 |
+
{
|
| 863 |
+
"name": "ragged_float16_negative_limit_cols67",
|
| 864 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 865 |
+
"inputs": {
|
| 866 |
+
"data": { "dtype": "float16", "shape": [3, 67], "data": { "kind": "constant", "value": -65504.0 } },
|
| 867 |
+
"bias": { "dtype": "float16", "shape": [1, 67], "data": { "kind": "constant", "value": 0.0 } }
|
| 868 |
+
},
|
| 869 |
+
"outputs": { "output": { "dtype": "float16", "shape": [3, 67], "tolerance": 0.00001, "relTolerance": 0.001 } },
|
| 870 |
+
"provenance": {
|
| 871 |
+
"notes": "Invalid logical-vec4 tail components must contribute exactly zero denominator, including when valid data equals the negative finite-limit sentinel."
|
| 872 |
+
}
|
| 873 |
+
},
|
| 874 |
+
{
|
| 875 |
+
"name": "ragged_float16_inner_broadcast_nonfinite_tail",
|
| 876 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 877 |
+
"inputs": {
|
| 878 |
+
"data": {
|
| 879 |
+
"dtype": "float16",
|
| 880 |
+
"shape": [6, 67],
|
| 881 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 2.0 }
|
| 882 |
+
},
|
| 883 |
+
"bias": {
|
| 884 |
+
"dtype": "float16",
|
| 885 |
+
"shape": [2, 67],
|
| 886 |
+
"data": {
|
| 887 |
+
"kind": "values",
|
| 888 |
+
"values": { "$ref": "#/fixtureArrays/ragged_float32_inner_broadcast_nonfinite_tail_input_bias" }
|
| 889 |
+
}
|
| 890 |
+
}
|
| 891 |
+
},
|
| 892 |
+
"outputs": {
|
| 893 |
+
"output": {
|
| 894 |
+
"dtype": "float16",
|
| 895 |
+
"shape": [6, 67],
|
| 896 |
+
"tolerance": 0.00001,
|
| 897 |
+
"relTolerance": 0.001,
|
| 898 |
+
"allowNaN": true
|
| 899 |
+
}
|
| 900 |
+
},
|
| 901 |
+
"provenance": {
|
| 902 |
+
"notes": "Consecutive groups reuse each ragged bias row: first group has a NaN in the last valid component, second group is fully additive-masked. Check exact NaN positions and zero rows."
|
| 903 |
+
}
|
| 904 |
}
|
| 905 |
]
|
| 906 |
}
|