sync 2e7068faf55e
Browse files- README.md +73 -0
- build/webgpu/bench.json +73 -0
- build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja +59 -0
- build/webgpu/bias-softmax-longrow-stats.wgsl.jinja +221 -0
- build/webgpu/bias-softmax.wgsl.jinja +281 -0
- build/webgpu/manifest.json +279 -0
- build/webgpu/metadata.json +20 -0
- build/webgpu/test.json +442 -0
README.md
CHANGED
|
@@ -1,3 +1,76 @@
|
|
| 1 |
---
|
|
|
|
| 2 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
library_name: kernels
|
| 3 |
license: apache-2.0
|
| 4 |
+
tags:
|
| 5 |
+
- kernel
|
| 6 |
+
- webgpu
|
| 7 |
+
- wgsl
|
| 8 |
---
|
| 9 |
+
# com.microsoft.BiasSoftmax
|
| 10 |
+
|
| 11 |
+
`com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Computes `softmax(data + bias)` over the flattened suffix beginning at `axis`. The required `is_inner_broadcast` attribute selects how bias rows are reused: consecutive groups for inner broadcast or cyclic groups for outer broadcast. This specializes the `softmax(scores + additive_mask)` pattern used by transformer attention. Float16 and float32 are supported; the schema's double type is not.
|
| 16 |
+
|
| 17 |
+
See the [ONNX Runtime `BiasSoftmax` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.BiasSoftmax) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `data` | `data` | `T` | — | — | The input data tensor. | required |
|
| 24 |
+
| `bias` | `bias` | `T` | — | — | The bias (or additive mask) tensor. Its element count must be an integral number of flattened softmax rows and that row count must divide the data row count. | required |
|
| 25 |
+
|
| 26 |
+
## Outputs
|
| 27 |
+
|
| 28 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 29 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 30 |
+
| `output` | `output` | `T` | same as `data` | same as `data` | The output tensor; same shape as data. | required |
|
| 31 |
+
|
| 32 |
+
## Attributes
|
| 33 |
+
|
| 34 |
+
Attributes and default values (overridable per request):
|
| 35 |
+
|
| 36 |
+
| Attribute | Default | Description |
|
| 37 |
+
| --- | --- | --- |
|
| 38 |
+
| `axis` | `1` | The axis from which softmax is applied; dimensions from `axis` onward are included in the softmax reduction. |
|
| 39 |
+
| `is_inner_broadcast` | — | When 1, bias is broadcast across dimensions from `broadcast_axis` to `axis-1`; when 0, bias is broadcast across dimensions 0 to `broadcast_axis-1`. |
|
| 40 |
+
|
| 41 |
+
## Type constraints
|
| 42 |
+
|
| 43 |
+
| Variable | Allowed dtypes |
|
| 44 |
+
| --- | --- |
|
| 45 |
+
| `T` | `float32`, `float16` |
|
| 46 |
+
|
| 47 |
+
## Files
|
| 48 |
+
|
| 49 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, 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 + tuning cases
|
| 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 |
+
The loader derives every required output's shape and logical dtype from the manifest contract and this call.
|
| 60 |
+
It then allocates the result tensors automatically.
|
| 61 |
+
|
| 62 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 63 |
+
|
| 64 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 65 |
+
|
| 66 |
+
```js
|
| 67 |
+
import { getKernel } from "@huggingface/kernels";
|
| 68 |
+
|
| 69 |
+
const kernel = await getKernel("webgpu-kernels/com.microsoft.BiasSoftmax", { version: 1 });
|
| 70 |
+
const { output } = await kernel({
|
| 71 |
+
data: { data: dataData, shape: [1, 2, 2] },
|
| 72 |
+
bias: { data: biasData, shape: [1, 2, 2] },
|
| 73 |
+
}, {
|
| 74 |
+
attrs: { is_inner_broadcast: 1 },
|
| 75 |
+
});
|
| 76 |
+
```
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.BiasSoftmax",
|
| 3 |
+
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256], "BLOCK_COLS": [1024, 2048, 4096, 8192] },
|
| 4 |
+
"cases": [
|
| 5 |
+
{
|
| 6 |
+
"name": "biassoftmax-f32-attn-32x512x512",
|
| 7 |
+
"preset": "smoke",
|
| 8 |
+
"vars": { "dtype": "float32" },
|
| 9 |
+
"attrs": { "axis": 2, "is_inner_broadcast": 0 },
|
| 10 |
+
"inputs": {
|
| 11 |
+
"data": { "shape": [32, 512, 512], "dtype": "float32", "dist": "normal", "seed": 510, "scale": 2 },
|
| 12 |
+
"bias": { "shape": [1, 512, 512], "dtype": "float32", "dist": "normal", "seed": 511, "scale": 1 }
|
| 13 |
+
},
|
| 14 |
+
"outputs": { "output": { "shape": [32, 512, 512], "dtype": "float32" } },
|
| 15 |
+
"bench": {
|
| 16 |
+
"primary": true,
|
| 17 |
+
"metrics": [{ "type": "bandwidth", "value": "(32 * 512 * 512 * 2 + 512 * 512) * dtypeBytes(args.dtype)" }]
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"name": "biassoftmax-f32-suffix-64x16x256",
|
| 22 |
+
"preset": "stress",
|
| 23 |
+
"vars": { "dtype": "float32" },
|
| 24 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 25 |
+
"inputs": {
|
| 26 |
+
"data": { "shape": [64, 16, 256], "dtype": "float32", "dist": "normal", "seed": 512, "scale": 2 },
|
| 27 |
+
"bias": { "shape": [1, 16, 256], "dtype": "float32", "dist": "normal", "seed": 513, "scale": 1 }
|
| 28 |
+
},
|
| 29 |
+
"outputs": { "output": { "shape": [64, 16, 256], "dtype": "float32" } },
|
| 30 |
+
"bench": {
|
| 31 |
+
"metrics": [{ "type": "bandwidth", "value": "(64 * 16 * 256 * 2 + 16 * 256) * dtypeBytes(args.dtype)" }]
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"name": "biassoftmax-f16-attn-32x512x512",
|
| 36 |
+
"preset": "model",
|
| 37 |
+
"vars": { "dtype": "float16" },
|
| 38 |
+
"attrs": { "axis": 2, "is_inner_broadcast": 0 },
|
| 39 |
+
"inputs": {
|
| 40 |
+
"data": { "shape": [32, 512, 512], "dtype": "float16", "dist": "normal", "seed": 514, "scale": 2 },
|
| 41 |
+
"bias": { "shape": [1, 512, 512], "dtype": "float16", "dist": "normal", "seed": 515, "scale": 1 }
|
| 42 |
+
},
|
| 43 |
+
"outputs": { "output": { "shape": [32, 512, 512], "dtype": "float16" } },
|
| 44 |
+
"bench": {
|
| 45 |
+
"metrics": [{ "type": "bandwidth", "value": "(32 * 512 * 512 * 2 + 512 * 512) * dtypeBytes(args.dtype)" }]
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"name": "biassoftmax-f32-lowocc-8x524288-axis1",
|
| 50 |
+
"preset": "stress",
|
| 51 |
+
"vars": { "dtype": "float32" },
|
| 52 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 53 |
+
"inputs": {
|
| 54 |
+
"data": { "shape": [8, 524288], "dtype": "float32", "dist": "normal", "seed": 530, "scale": 2 },
|
| 55 |
+
"bias": { "shape": [1, 524288], "dtype": "float32", "dist": "normal", "seed": 531, "scale": 1 }
|
| 56 |
+
},
|
| 57 |
+
"outputs": { "output": { "shape": [8, 524288], "dtype": "float32" } },
|
| 58 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "(8 * 524288 * 2 + 524288) * dtypeBytes(args.dtype)" }] }
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"name": "biassoftmax-f32-launchbound-262144x2-axis1",
|
| 62 |
+
"preset": "stress",
|
| 63 |
+
"vars": { "dtype": "float32" },
|
| 64 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 65 |
+
"inputs": {
|
| 66 |
+
"data": { "shape": [262144, 2], "dtype": "float32", "dist": "normal", "seed": 532, "scale": 2 },
|
| 67 |
+
"bias": { "shape": [1, 2], "dtype": "float32", "dist": "normal", "seed": 533, "scale": 1 }
|
| 68 |
+
},
|
| 69 |
+
"outputs": { "output": { "shape": [262144, 2], "dtype": "float32" } },
|
| 70 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "(262144 * 2 * 2 + 2) * dtypeBytes(args.dtype)" }] }
|
| 71 |
+
}
|
| 72 |
+
]
|
| 73 |
+
}
|
build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Parallel normalization for split long rows. One workgroup handles each
|
| 2 |
+
// BLOCK_COLS slice and reuses the finished per-row statistics.
|
| 3 |
+
{% if usesF16 %}
|
| 4 |
+
enable f16;
|
| 5 |
+
{% endif %}
|
| 6 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 7 |
+
|
| 8 |
+
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 9 |
+
const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
|
| 10 |
+
fn bias_index(index: u32) -> u32 {
|
| 11 |
+
let row = index / params.blockSize;
|
| 12 |
+
let column = index % params.blockSize;
|
| 13 |
+
{% if source.isInnerBroadcast %}
|
| 14 |
+
return (row / {{ source.innerRepeat }}u) * params.blockSize + column;
|
| 15 |
+
{% else %}
|
| 16 |
+
return (row % {{ source.biasBlockCount }}u) * params.blockSize + column;
|
| 17 |
+
{% endif %}
|
| 18 |
+
}
|
| 19 |
+
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 20 |
+
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 21 |
+
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
| 22 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 23 |
+
// here changes +inf-row behavior.
|
| 24 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 25 |
+
|
| 26 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 27 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 31 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 32 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 33 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 34 |
+
fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
| 35 |
+
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 36 |
+
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 37 |
+
}
|
| 38 |
+
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 39 |
+
return exp(shifted_value(value, maxValue));
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
@compute @workgroup_size(WG)
|
| 43 |
+
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 44 |
+
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 45 |
+
let block = wid.x;
|
| 46 |
+
let row = wid.y;
|
| 47 |
+
let rowBase = row * params.blockSize;
|
| 48 |
+
let colStart = block * BLOCK_COLS;
|
| 49 |
+
let colEnd = min(params.blockSize, colStart + BLOCK_COLS);
|
| 50 |
+
let m = rowMax[row];
|
| 51 |
+
let d = rowSum[row];
|
| 52 |
+
let inv = select(0.0, 1.0 / d, d > 0.0);
|
| 53 |
+
|
| 54 |
+
for (var col = colStart + lid.x; col < colEnd; col += WG) {
|
| 55 |
+
let i = rowBase + col;
|
| 56 |
+
let v = f32(data[i]) + f32(bias[bias_index(i)]);
|
| 57 |
+
output[i] = {{ scalar }}(exp_shift(v, m) * inv);
|
| 58 |
+
}
|
| 59 |
+
}
|
build/webgpu/bias-softmax-longrow-stats.wgsl.jinja
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Split statistics for very long contiguous BiasSoftmax rows. The block stage
|
| 2 |
+
// computes independent online (maximum, sum-exp) pairs over BLOCK_COLS. The row
|
| 3 |
+
// stage deterministically folds them into one result per row.
|
| 4 |
+
{% if source.stage == "block" and usesF16 %}
|
| 5 |
+
enable f16;
|
| 6 |
+
{% endif %}
|
| 7 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
+
|
| 9 |
+
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 10 |
+
{% if source.stage == "block" %}
|
| 11 |
+
const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
|
| 12 |
+
fn bias_index(index: u32) -> u32 {
|
| 13 |
+
let row = index / params.blockSize;
|
| 14 |
+
let column = index % params.blockSize;
|
| 15 |
+
{% if source.isInnerBroadcast | default(false) %}
|
| 16 |
+
return (row / {{ source.innerRepeat | default(1) }}u) * params.blockSize + column;
|
| 17 |
+
{% else %}
|
| 18 |
+
return (row % {{ source.biasBlockCount | default(1) }}u) * params.blockSize + column;
|
| 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
|
| 25 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 26 |
+
// here changes +inf-row behavior.
|
| 27 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 28 |
+
|
| 29 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 30 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 34 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 35 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 36 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 37 |
+
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 |
+
}
|
| 44 |
+
// Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
|
| 45 |
+
// mNew = max(m1, m2)
|
| 46 |
+
// dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
|
| 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 combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 51 |
+
{% if combineSubgroups %}
|
| 52 |
+
// Per-subgroup partials are published into a deterministic slot: the subgroup's
|
| 53 |
+
// ordinal index within the workgroup (lidx / sgSize). The online (m, d) merge
|
| 54 |
+
// is not float-associative, so thread 0 must fold partials in a fixed order.
|
| 55 |
+
// Subgroups partition a workgroup into contiguous ordinal ranges on supported
|
| 56 |
+
// backends, so the ordinal slot is unique per subgroup and every slot in
|
| 57 |
+
// [0, subgroupCount) is written (each subgroup elects one leader).
|
| 58 |
+
// Sized for the worst case of one partial per invocation.
|
| 59 |
+
var<workgroup> partialM: array<f32, WG>;
|
| 60 |
+
var<workgroup> partialD: array<f32, WG>;
|
| 61 |
+
var<workgroup> combinedMD: vec2<f32>;
|
| 62 |
+
|
| 63 |
+
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 64 |
+
// it (no barriers, no shared state); otherwise subgroup leaders publish
|
| 65 |
+
// partials through shared memory and thread 0 folds them in ordinal order.
|
| 66 |
+
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 67 |
+
let sgM = subgroupMax(m);
|
| 68 |
+
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
| 69 |
+
// element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
|
| 70 |
+
// landed in d via exp(NaN); both survive the merge and are detected by the
|
| 71 |
+
// code after the reduction.
|
| 72 |
+
let sgD = subgroupAdd(d * exp_shift(m, sgM));
|
| 73 |
+
if (sgSize == WG) {
|
| 74 |
+
return vec2<f32>(sgM, sgD);
|
| 75 |
+
}
|
| 76 |
+
let subgroupCount = (WG + sgSize - 1u) / sgSize;
|
| 77 |
+
// Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
|
| 78 |
+
// fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
|
| 79 |
+
// subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
|
| 80 |
+
// flash-attention loop that re-uses this shared memory each iteration — would
|
| 81 |
+
// otherwise read stale shared memory. Identity makes such a slot a no-op.
|
| 82 |
+
// (max identity = -FLT_MAX, denom identity = 0.)
|
| 83 |
+
if (lidx < subgroupCount) {
|
| 84 |
+
partialM[lidx] = -FLT_MAX;
|
| 85 |
+
partialD[lidx] = 0.0;
|
| 86 |
+
}
|
| 87 |
+
workgroupBarrier();
|
| 88 |
+
if (subgroupElect()) {
|
| 89 |
+
let slot = lidx / sgSize;
|
| 90 |
+
partialM[slot] = sgM;
|
| 91 |
+
partialD[slot] = sgD;
|
| 92 |
+
}
|
| 93 |
+
workgroupBarrier();
|
| 94 |
+
if (lidx == 0u) {
|
| 95 |
+
var accM = -FLT_MAX;
|
| 96 |
+
var accD = 0.0;
|
| 97 |
+
for (var i = 0u; i < subgroupCount; i = i + 1u) {
|
| 98 |
+
let mNew = max(accM, partialM[i]);
|
| 99 |
+
accD = accD * exp_shift(accM, mNew) + partialD[i] * exp_shift(partialM[i], mNew);
|
| 100 |
+
accM = mNew;
|
| 101 |
+
}
|
| 102 |
+
combinedMD = vec2<f32>(accM, accD);
|
| 103 |
+
}
|
| 104 |
+
workgroupBarrier();
|
| 105 |
+
return combinedMD;
|
| 106 |
+
}
|
| 107 |
+
{% else %}
|
| 108 |
+
{% set mdStreamed = mdStreams is defined %}
|
| 109 |
+
{% set mdStreams = mdStreams if mdStreams is defined else 1 %}
|
| 110 |
+
{% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
|
| 111 |
+
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 112 |
+
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 113 |
+
{% if mdStreamed %}
|
| 114 |
+
|
| 115 |
+
// In-place fold of {{ mdStreams }} streams. The caller stores its per-thread
|
| 116 |
+
// partials into partialM/partialD first and reads the merged pair of stream s
|
| 117 |
+
// from slot s * WG afterwards.
|
| 118 |
+
fn combine_partials_streams(lidx: u32) {
|
| 119 |
+
workgroupBarrier();
|
| 120 |
+
var stride = WG / 2u;
|
| 121 |
+
loop {
|
| 122 |
+
if (stride == 0u) {
|
| 123 |
+
break;
|
| 124 |
+
}
|
| 125 |
+
if (lidx < stride) {
|
| 126 |
+
{% for s in range(mdStreams) %}
|
| 127 |
+
{
|
| 128 |
+
let slot = {{ s }}u * WG + lidx;
|
| 129 |
+
let m1 = partialM[slot];
|
| 130 |
+
let d1 = partialD[slot];
|
| 131 |
+
let m2 = partialM[slot + stride];
|
| 132 |
+
let d2 = partialD[slot + stride];
|
| 133 |
+
let mNew = max(m1, m2);
|
| 134 |
+
partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 135 |
+
partialM[slot] = mNew;
|
| 136 |
+
}
|
| 137 |
+
{% endfor %}
|
| 138 |
+
}
|
| 139 |
+
workgroupBarrier();
|
| 140 |
+
stride = stride / 2u;
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
{% else %}
|
| 144 |
+
|
| 145 |
+
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 146 |
+
partialM[lidx] = m;
|
| 147 |
+
partialD[lidx] = d;
|
| 148 |
+
workgroupBarrier();
|
| 149 |
+
var stride = WG / 2u;
|
| 150 |
+
loop {
|
| 151 |
+
if (stride == 0u) {
|
| 152 |
+
break;
|
| 153 |
+
}
|
| 154 |
+
if (lidx < stride) {
|
| 155 |
+
let m1 = partialM[lidx];
|
| 156 |
+
let d1 = partialD[lidx];
|
| 157 |
+
let m2 = partialM[lidx + stride];
|
| 158 |
+
let d2 = partialD[lidx + stride];
|
| 159 |
+
let mNew = max(m1, m2);
|
| 160 |
+
partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 161 |
+
partialM[lidx] = mNew;
|
| 162 |
+
}
|
| 163 |
+
workgroupBarrier();
|
| 164 |
+
stride = stride / 2u;
|
| 165 |
+
}
|
| 166 |
+
let merged = vec2<f32>(partialM[0], partialD[0]);
|
| 167 |
+
// Trailing barrier so back-to-back calls cannot race a next call's partial
|
| 168 |
+
// stores against this call's reads of slot 0.
|
| 169 |
+
workgroupBarrier();
|
| 170 |
+
return merged;
|
| 171 |
+
}
|
| 172 |
+
{% endif %}
|
| 173 |
+
{% endif %}
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@compute @workgroup_size(WG)
|
| 177 |
+
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 178 |
+
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 179 |
+
{% if source.stage == "block" %}
|
| 180 |
+
let block = wid.x;
|
| 181 |
+
let row = wid.y;
|
| 182 |
+
let rowBase = row * params.blockSize;
|
| 183 |
+
let colStart = block * BLOCK_COLS;
|
| 184 |
+
let colEnd = min(params.blockSize, colStart + BLOCK_COLS);
|
| 185 |
+
|
| 186 |
+
var m = -FLT_MAX;
|
| 187 |
+
var d = 0.0;
|
| 188 |
+
for (var col = colStart + lid.x; col < colEnd; col += WG) {
|
| 189 |
+
let i = rowBase + col;
|
| 190 |
+
let v = f32(data[i]) + f32(bias[bias_index(i)]);
|
| 191 |
+
let mNew = max(m, v);
|
| 192 |
+
d = d * exp_shift(m, mNew) + exp_shift(v, mNew);
|
| 193 |
+
m = mNew;
|
| 194 |
+
}
|
| 195 |
+
{% else %}
|
| 196 |
+
let row = wid.x;
|
| 197 |
+
let base = row * params.blocks;
|
| 198 |
+
|
| 199 |
+
var m = -FLT_MAX;
|
| 200 |
+
var d = 0.0;
|
| 201 |
+
for (var block = lid.x; block < params.blocks; block += WG) {
|
| 202 |
+
let bm = blockMax[base + block];
|
| 203 |
+
let bd = blockSum[base + block];
|
| 204 |
+
let mNew = max(m, bm);
|
| 205 |
+
d = d * exp_shift(m, mNew) + bd * exp_shift(bm, mNew);
|
| 206 |
+
m = mNew;
|
| 207 |
+
}
|
| 208 |
+
{% endif %}
|
| 209 |
+
|
| 210 |
+
let merged = combine_partials(m, d, lid.x);
|
| 211 |
+
if (lid.x == 0u) {
|
| 212 |
+
{% if source.stage == "block" %}
|
| 213 |
+
let out = row * params.blocks + block;
|
| 214 |
+
blockMax[out] = merged.x;
|
| 215 |
+
blockSum[out] = merged.y;
|
| 216 |
+
{% else %}
|
| 217 |
+
rowMax[row] = merged.x;
|
| 218 |
+
rowSum[row] = merged.y;
|
| 219 |
+
{% endif %}
|
| 220 |
+
}
|
| 221 |
+
}
|
build/webgpu/bias-softmax.wgsl.jinja
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// BiasSoftmax computes softmax(data + bias) over suffix axes [axis, rank), not
|
| 2 |
+
// only the last axis. A workgroup normally owns one softmax block; short rows
|
| 3 |
+
// can instead be packed with one invocation per row. Inner broadcast reuses
|
| 4 |
+
// one bias row across consecutive data rows; outer broadcast cycles bias rows.
|
| 5 |
+
{% if usesF16 %}
|
| 6 |
+
enable f16;
|
| 7 |
+
{% endif %}
|
| 8 |
+
{% if combineSubgroups %}
|
| 9 |
+
enable subgroups;
|
| 10 |
+
{% endif %}
|
| 11 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 12 |
+
|
| 13 |
+
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 14 |
+
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 15 |
+
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 16 |
+
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
| 17 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 18 |
+
// here changes +inf-row behavior.
|
| 19 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 20 |
+
|
| 21 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 22 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 26 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 27 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 28 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 29 |
+
fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
| 30 |
+
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 31 |
+
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 32 |
+
}
|
| 33 |
+
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 34 |
+
return exp(shifted_value(value, maxValue));
|
| 35 |
+
}
|
| 36 |
+
{% if not packedRows %}
|
| 37 |
+
// Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
|
| 38 |
+
// mNew = max(m1, m2)
|
| 39 |
+
// dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
|
| 40 |
+
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 41 |
+
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 42 |
+
// calls before their shared partial storage is reused.
|
| 43 |
+
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 44 |
+
{% if combineSubgroups %}
|
| 45 |
+
// Per-subgroup partials are published into a deterministic slot: the subgroup's
|
| 46 |
+
// ordinal index within the workgroup (lidx / sgSize). The online (m, d) merge
|
| 47 |
+
// is not float-associative, so thread 0 must fold partials in a fixed order.
|
| 48 |
+
// Subgroups partition a workgroup into contiguous ordinal ranges on supported
|
| 49 |
+
// backends, so the ordinal slot is unique per subgroup and every slot in
|
| 50 |
+
// [0, subgroupCount) is written (each subgroup elects one leader).
|
| 51 |
+
// Sized for the worst case of one partial per invocation.
|
| 52 |
+
var<workgroup> partialM: array<f32, WG>;
|
| 53 |
+
var<workgroup> partialD: array<f32, WG>;
|
| 54 |
+
var<workgroup> combinedMD: vec2<f32>;
|
| 55 |
+
|
| 56 |
+
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 57 |
+
// it (no barriers, no shared state); otherwise subgroup leaders publish
|
| 58 |
+
// partials through shared memory and thread 0 folds them in ordinal order.
|
| 59 |
+
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 60 |
+
let sgM = subgroupMax(m);
|
| 61 |
+
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
| 62 |
+
// element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
|
| 63 |
+
// landed in d via exp(NaN); both survive the merge and are detected by the
|
| 64 |
+
// code after the reduction.
|
| 65 |
+
let sgD = subgroupAdd(d * exp_shift(m, sgM));
|
| 66 |
+
if (sgSize == WG) {
|
| 67 |
+
return vec2<f32>(sgM, sgD);
|
| 68 |
+
}
|
| 69 |
+
let subgroupCount = (WG + sgSize - 1u) / sgSize;
|
| 70 |
+
// Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
|
| 71 |
+
// fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
|
| 72 |
+
// subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
|
| 73 |
+
// flash-attention loop that re-uses this shared memory each iteration — would
|
| 74 |
+
// otherwise read stale shared memory. Identity makes such a slot a no-op.
|
| 75 |
+
// (max identity = -FLT_MAX, denom identity = 0.)
|
| 76 |
+
if (lidx < subgroupCount) {
|
| 77 |
+
partialM[lidx] = -FLT_MAX;
|
| 78 |
+
partialD[lidx] = 0.0;
|
| 79 |
+
}
|
| 80 |
+
workgroupBarrier();
|
| 81 |
+
if (subgroupElect()) {
|
| 82 |
+
let slot = lidx / sgSize;
|
| 83 |
+
partialM[slot] = sgM;
|
| 84 |
+
partialD[slot] = sgD;
|
| 85 |
+
}
|
| 86 |
+
workgroupBarrier();
|
| 87 |
+
if (lidx == 0u) {
|
| 88 |
+
var accM = -FLT_MAX;
|
| 89 |
+
var accD = 0.0;
|
| 90 |
+
for (var i = 0u; i < subgroupCount; i = i + 1u) {
|
| 91 |
+
let mNew = max(accM, partialM[i]);
|
| 92 |
+
accD = accD * exp_shift(accM, mNew) + partialD[i] * exp_shift(partialM[i], mNew);
|
| 93 |
+
accM = mNew;
|
| 94 |
+
}
|
| 95 |
+
combinedMD = vec2<f32>(accM, accD);
|
| 96 |
+
}
|
| 97 |
+
workgroupBarrier();
|
| 98 |
+
return combinedMD;
|
| 99 |
+
}
|
| 100 |
+
{% else %}
|
| 101 |
+
{% set mdStreamed = mdStreams is defined %}
|
| 102 |
+
{% set mdStreams = mdStreams if mdStreams is defined else 1 %}
|
| 103 |
+
{% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
|
| 104 |
+
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 105 |
+
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 106 |
+
{% if mdStreamed %}
|
| 107 |
+
|
| 108 |
+
// In-place fold of {{ mdStreams }} streams. The caller stores its per-thread
|
| 109 |
+
// partials into partialM/partialD first and reads the merged pair of stream s
|
| 110 |
+
// from slot s * WG afterwards.
|
| 111 |
+
fn combine_partials_streams(lidx: u32) {
|
| 112 |
+
workgroupBarrier();
|
| 113 |
+
var stride = WG / 2u;
|
| 114 |
+
loop {
|
| 115 |
+
if (stride == 0u) {
|
| 116 |
+
break;
|
| 117 |
+
}
|
| 118 |
+
if (lidx < stride) {
|
| 119 |
+
{% for s in range(mdStreams) %}
|
| 120 |
+
{
|
| 121 |
+
let slot = {{ s }}u * WG + lidx;
|
| 122 |
+
let m1 = partialM[slot];
|
| 123 |
+
let d1 = partialD[slot];
|
| 124 |
+
let m2 = partialM[slot + stride];
|
| 125 |
+
let d2 = partialD[slot + stride];
|
| 126 |
+
let mNew = max(m1, m2);
|
| 127 |
+
partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 128 |
+
partialM[slot] = mNew;
|
| 129 |
+
}
|
| 130 |
+
{% endfor %}
|
| 131 |
+
}
|
| 132 |
+
workgroupBarrier();
|
| 133 |
+
stride = stride / 2u;
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
{% else %}
|
| 137 |
+
|
| 138 |
+
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 139 |
+
partialM[lidx] = m;
|
| 140 |
+
partialD[lidx] = d;
|
| 141 |
+
workgroupBarrier();
|
| 142 |
+
var stride = WG / 2u;
|
| 143 |
+
loop {
|
| 144 |
+
if (stride == 0u) {
|
| 145 |
+
break;
|
| 146 |
+
}
|
| 147 |
+
if (lidx < stride) {
|
| 148 |
+
let m1 = partialM[lidx];
|
| 149 |
+
let d1 = partialD[lidx];
|
| 150 |
+
let m2 = partialM[lidx + stride];
|
| 151 |
+
let d2 = partialD[lidx + stride];
|
| 152 |
+
let mNew = max(m1, m2);
|
| 153 |
+
partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 154 |
+
partialM[lidx] = mNew;
|
| 155 |
+
}
|
| 156 |
+
workgroupBarrier();
|
| 157 |
+
stride = stride / 2u;
|
| 158 |
+
}
|
| 159 |
+
let merged = vec2<f32>(partialM[0], partialD[0]);
|
| 160 |
+
// Trailing barrier so back-to-back calls cannot race a next call's partial
|
| 161 |
+
// stores against this call's reads of slot 0.
|
| 162 |
+
workgroupBarrier();
|
| 163 |
+
return merged;
|
| 164 |
+
}
|
| 165 |
+
{% endif %}
|
| 166 |
+
{% endif %}
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
{% endif %}
|
| 170 |
+
fn bias_index(index: u32) -> u32 {
|
| 171 |
+
let row = index / params.blockSize;
|
| 172 |
+
let column = index % params.blockSize;
|
| 173 |
+
{% if source.isInnerBroadcast %}
|
| 174 |
+
return (row / {{ source.innerRepeat }}u) * params.blockSize + column;
|
| 175 |
+
{% else %}
|
| 176 |
+
return (row % {{ source.biasBlockCount }}u) * params.blockSize + column;
|
| 177 |
+
{% endif %}
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
@compute @workgroup_size(WG)
|
| 181 |
+
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 182 |
+
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 183 |
+
@builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups and not packedRows %},
|
| 184 |
+
@builtin(subgroup_size) sgSize: u32{% endif %}) {
|
| 185 |
+
{% if packedRows %}
|
| 186 |
+
// One invocation owns a complete short row. Grid-striding keeps the dispatch
|
| 187 |
+
// one-dimensional and bounded even for very large batch counts.
|
| 188 |
+
for (var row = wid.x * WG + lid.x; row < params.batchCount; row += nwg.x * WG) {
|
| 189 |
+
let base = row * params.blockSize;
|
| 190 |
+
{% if source.blockSize == 2 %}
|
| 191 |
+
let i0 = base;
|
| 192 |
+
let i1 = base + 1u;
|
| 193 |
+
let v0 = f32(data[i0]) + f32(bias[bias_index(i0)]);
|
| 194 |
+
let v1 = f32(data[i1]) + f32(bias[bias_index(i1)]);
|
| 195 |
+
|
| 196 |
+
// The overwhelmingly common two-element case is a logistic pair. Choose
|
| 197 |
+
// the non-positive exponent branch for stability and use one exp total.
|
| 198 |
+
// Non-finite values retain the generic kernel's established semantics.
|
| 199 |
+
if (is_finite_f32(v0) && is_finite_f32(v1)) {
|
| 200 |
+
if (v0 >= v1) {
|
| 201 |
+
let e = exp(v1 - v0);
|
| 202 |
+
let inv = 1.0 / (1.0 + e);
|
| 203 |
+
output[i0] = {{ scalar }}(inv);
|
| 204 |
+
output[i1] = {{ scalar }}(e * inv);
|
| 205 |
+
} else {
|
| 206 |
+
let e = exp(v0 - v1);
|
| 207 |
+
let inv = 1.0 / (1.0 + e);
|
| 208 |
+
output[i0] = {{ scalar }}(e * inv);
|
| 209 |
+
output[i1] = {{ scalar }}(inv);
|
| 210 |
+
}
|
| 211 |
+
} else {
|
| 212 |
+
var m = -FLT_MAX;
|
| 213 |
+
var denom = 0.0;
|
| 214 |
+
let newM0 = max(m, v0);
|
| 215 |
+
denom = denom * exp_shift(m, newM0) + exp_shift(v0, newM0);
|
| 216 |
+
m = newM0;
|
| 217 |
+
let newM1 = max(m, v1);
|
| 218 |
+
denom = denom * exp_shift(m, newM1) + exp_shift(v1, newM1);
|
| 219 |
+
m = newM1;
|
| 220 |
+
let inv = select(0.0, 1.0 / denom, denom > 0.0);
|
| 221 |
+
output[i0] = {{ scalar }}(exp_shift(v0, m) * inv);
|
| 222 |
+
output[i1] = {{ scalar }}(exp_shift(v1, m) * inv);
|
| 223 |
+
}
|
| 224 |
+
{% else %}
|
| 225 |
+
// Short rows fit in private registers, so the normalization sweep does not
|
| 226 |
+
// reread data or bias from storage.
|
| 227 |
+
var values: array<f32, {{ source.blockSize }}>;
|
| 228 |
+
var m = -FLT_MAX;
|
| 229 |
+
var denom = 0.0;
|
| 230 |
+
for (var dd = 0u; dd < params.blockSize; dd += 1u) {
|
| 231 |
+
let i = base + dd;
|
| 232 |
+
let v = f32(data[i]) + f32(bias[bias_index(i)]);
|
| 233 |
+
values[dd] = v;
|
| 234 |
+
let new_m = max(m, v);
|
| 235 |
+
denom = denom * exp_shift(m, new_m) + exp_shift(v, new_m);
|
| 236 |
+
m = new_m;
|
| 237 |
+
}
|
| 238 |
+
let inv = select(0.0, 1.0 / denom, denom > 0.0);
|
| 239 |
+
for (var dd = 0u; dd < params.blockSize; dd += 1u) {
|
| 240 |
+
let i = base + dd;
|
| 241 |
+
output[i] = {{ scalar }}(exp_shift(values[dd], m) * inv);
|
| 242 |
+
}
|
| 243 |
+
{% endif %}
|
| 244 |
+
}
|
| 245 |
+
{% else %}
|
| 246 |
+
// 2D-folded row index: wid.y carries the high bits past the maxComputeWorkgroupsPerDimension
|
| 247 |
+
// workgroup-per-dimension dispatch limit. Reduces to wid.x when nwg.y == 1;
|
| 248 |
+
// the row >= params.batchCount guard drops the over-dispatched tail.
|
| 249 |
+
let row = wid.x + wid.y * nwg.x;
|
| 250 |
+
if (row >= params.batchCount) {
|
| 251 |
+
return;
|
| 252 |
+
}
|
| 253 |
+
let base = row * params.blockSize;
|
| 254 |
+
let tid = lid.x;
|
| 255 |
+
|
| 256 |
+
// Single online (m, d) stats pass over (data + bias), then one cooperative
|
| 257 |
+
// combine. The row is read once for stats and once for normalization.
|
| 258 |
+
var m = -FLT_MAX;
|
| 259 |
+
var denom = 0.0;
|
| 260 |
+
for (var dd = tid; dd < params.blockSize; dd = dd + WG) {
|
| 261 |
+
let i = base + dd;
|
| 262 |
+
let v = f32(data[i]) + f32(bias[bias_index(i)]);
|
| 263 |
+
let new_m = max(m, v);
|
| 264 |
+
denom = denom * exp_shift(m, new_m) + exp_shift(v, new_m);
|
| 265 |
+
m = new_m;
|
| 266 |
+
}
|
| 267 |
+
{% if combineSubgroups %}
|
| 268 |
+
let md = combine_partials(m, denom, tid, sgSize);
|
| 269 |
+
{% else %}
|
| 270 |
+
let md = combine_partials(m, denom, tid);
|
| 271 |
+
{% endif %}
|
| 272 |
+
let rowMax = md.x;
|
| 273 |
+
let inv = select(0.0, 1.0 / md.y, md.y > 0.0);
|
| 274 |
+
|
| 275 |
+
for (var dd = tid; dd < params.blockSize; dd = dd + WG) {
|
| 276 |
+
let i = base + dd;
|
| 277 |
+
let v = f32(data[i]) + f32(bias[bias_index(i)]);
|
| 278 |
+
output[i] = {{ scalar }}(exp_shift(v, rowMax) * inv);
|
| 279 |
+
}
|
| 280 |
+
{% endif %}
|
| 281 |
+
}
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "com.microsoft",
|
| 3 |
+
"name": "BiasSoftmax",
|
| 4 |
+
"sinceVersion": 1,
|
| 5 |
+
"description": "Computes `softmax(data + bias)` over the flattened suffix beginning at `axis`. The required `is_inner_broadcast` attribute selects how bias rows are reused: consecutive groups for inner broadcast or cyclic groups for outer broadcast. This specializes the `softmax(scores + additive_mask)` pattern used by transformer attention. Float16 and float32 are supported; the schema's double type is not.",
|
| 6 |
+
"inputs": [
|
| 7 |
+
{ "role": "data", "dtype": "T", "description": "The input data tensor." },
|
| 8 |
+
{
|
| 9 |
+
"role": "bias",
|
| 10 |
+
"dtype": "T",
|
| 11 |
+
"description": "The bias (or additive mask) tensor. Its element count must be an integral number of flattened softmax rows and that row count must divide the data row count."
|
| 12 |
+
}
|
| 13 |
+
],
|
| 14 |
+
"outputs": [
|
| 15 |
+
{
|
| 16 |
+
"role": "output",
|
| 17 |
+
"dtype": "T",
|
| 18 |
+
"rank": "ranks.data",
|
| 19 |
+
"shape": "shapes.data",
|
| 20 |
+
"description": "The output tensor; same shape as data."
|
| 21 |
+
}
|
| 22 |
+
],
|
| 23 |
+
"attributes": { "axis": 1 },
|
| 24 |
+
"attributeConstraints": { "is_inner_broadcast": { "required": true } },
|
| 25 |
+
"attributeDescriptions": {
|
| 26 |
+
"axis": "The axis from which softmax is applied; dimensions from `axis` onward are included in the softmax reduction.",
|
| 27 |
+
"is_inner_broadcast": "When 1, bias is broadcast across dimensions from `broadcast_axis` to `axis-1`; when 0, bias is broadcast across dimensions 0 to `broadcast_axis-1`."
|
| 28 |
+
},
|
| 29 |
+
"typeConstraints": { "T": ["float32", "float16"] },
|
| 30 |
+
"args": {
|
| 31 |
+
"data": { "kind": "tensor", "semantic": "data", "role": "input" },
|
| 32 |
+
"bias": { "kind": "tensor", "semantic": "bias", "role": "input" },
|
| 33 |
+
"output": { "kind": "tensor", "semantic": "output", "role": "output" }
|
| 34 |
+
},
|
| 35 |
+
"derive": {
|
| 36 |
+
"axisNorm": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.data",
|
| 37 |
+
"batchCount": "outer(shapes.data, axisNorm)",
|
| 38 |
+
"blockSize": "dim(shapes.data, axisNorm) * inner(shapes.data, axisNorm)",
|
| 39 |
+
"biasBlockCount": "numel(shapes.bias) / max(1, blockSize)",
|
| 40 |
+
"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)"
|
| 41 |
+
},
|
| 42 |
+
"tunables": { "WORKGROUP_SIZE": 256, "BLOCK_COLS": 2048 },
|
| 43 |
+
"bindingSets": {
|
| 44 |
+
"biasSoftmax": [
|
| 45 |
+
{
|
| 46 |
+
"name": "data",
|
| 47 |
+
"arg": "data",
|
| 48 |
+
"semantic": "data",
|
| 49 |
+
"buffer": { "type": "read-only-storage" },
|
| 50 |
+
"elementType": "$scalar"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"name": "bias",
|
| 54 |
+
"arg": "bias",
|
| 55 |
+
"semantic": "bias",
|
| 56 |
+
"buffer": { "type": "read-only-storage" },
|
| 57 |
+
"elementType": "$scalar"
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"name": "output",
|
| 61 |
+
"arg": "output",
|
| 62 |
+
"semantic": "output",
|
| 63 |
+
"buffer": { "type": "storage" },
|
| 64 |
+
"elementType": "$scalar"
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"name": "params",
|
| 68 |
+
"semantic": "kernel.params",
|
| 69 |
+
"buffer": { "type": "uniform" },
|
| 70 |
+
"struct": {
|
| 71 |
+
"name": "Params",
|
| 72 |
+
"fields": [
|
| 73 |
+
{ "name": "blockSize", "type": "u32", "value": "blockSize" },
|
| 74 |
+
{ "name": "batchCount", "type": "u32", "value": "batchCount" }
|
| 75 |
+
]
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
]
|
| 79 |
+
},
|
| 80 |
+
"variants": [
|
| 81 |
+
{
|
| 82 |
+
"id": "longrow_split",
|
| 83 |
+
"priority": 40,
|
| 84 |
+
"when": ["numel(shapes.data) == numel(shapes.output)", "ranks.data >= 1", "attrs.axis + ranks.data >= 0", "attrs.axis < ranks.data", "biasContract", "blockSize >= 65536", "batchCount > 0", "batchCount <= device.limits.maxComputeWorkgroupsPerDimension", "ceilDiv(blockSize, tunables.BLOCK_COLS) <= device.limits.maxComputeWorkgroupsPerDimension", "f16Ok(dtypes.T)"],
|
| 85 |
+
"constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"", "combineSubgroups": false },
|
| 86 |
+
"intermediates": [
|
| 87 |
+
{
|
| 88 |
+
"id": "blockMax",
|
| 89 |
+
"dtype": "float32",
|
| 90 |
+
"shape": "[outer(shapes.data, axisNorm) * ceilDiv(dim(shapes.data, axisNorm) * inner(shapes.data, axisNorm), tunables.BLOCK_COLS)]"
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"id": "blockSum",
|
| 94 |
+
"dtype": "float32",
|
| 95 |
+
"shape": "[outer(shapes.data, axisNorm) * ceilDiv(dim(shapes.data, axisNorm) * inner(shapes.data, axisNorm), tunables.BLOCK_COLS)]"
|
| 96 |
+
},
|
| 97 |
+
{ "id": "rowMax", "dtype": "float32", "shape": "[outer(shapes.data, axisNorm)]" },
|
| 98 |
+
{ "id": "rowSum", "dtype": "float32", "shape": "[outer(shapes.data, axisNorm)]" }
|
| 99 |
+
],
|
| 100 |
+
"passes": [
|
| 101 |
+
{
|
| 102 |
+
"id": "block_stats",
|
| 103 |
+
"name": "BiasSoftmax.LongRowBlockStats",
|
| 104 |
+
"source": {
|
| 105 |
+
"shader": "bias-softmax-longrow-stats.wgsl.jinja",
|
| 106 |
+
"inputs": {
|
| 107 |
+
"stage": "\"block\"",
|
| 108 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 109 |
+
"biasBlockCount": "max(1, biasBlockCount)",
|
| 110 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"bindings": [
|
| 114 |
+
{
|
| 115 |
+
"name": "data",
|
| 116 |
+
"arg": "data",
|
| 117 |
+
"semantic": "data",
|
| 118 |
+
"buffer": { "type": "read-only-storage" },
|
| 119 |
+
"elementType": "$scalar"
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"name": "bias",
|
| 123 |
+
"arg": "bias",
|
| 124 |
+
"semantic": "bias",
|
| 125 |
+
"buffer": { "type": "read-only-storage" },
|
| 126 |
+
"elementType": "$scalar"
|
| 127 |
+
},
|
| 128 |
+
{ "name": "blockMax", "semantic": "blockMax", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 129 |
+
{ "name": "blockSum", "semantic": "blockSum", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 130 |
+
{
|
| 131 |
+
"name": "params",
|
| 132 |
+
"semantic": "kernel.params",
|
| 133 |
+
"buffer": { "type": "uniform" },
|
| 134 |
+
"struct": {
|
| 135 |
+
"name": "Params",
|
| 136 |
+
"fields": [
|
| 137 |
+
{ "name": "blockSize", "type": "u32", "value": "blockSize" },
|
| 138 |
+
{ "name": "blocks", "type": "u32", "value": "ceilDiv(blockSize, tunables.BLOCK_COLS)" }
|
| 139 |
+
]
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
],
|
| 143 |
+
"dispatch": { "x": "ceilDiv(blockSize, tunables.BLOCK_COLS)", "y": "batchCount" }
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"id": "row_stats",
|
| 147 |
+
"name": "BiasSoftmax.LongRowStats",
|
| 148 |
+
"source": { "shader": "bias-softmax-longrow-stats.wgsl.jinja", "inputs": { "stage": "\"row\"" } },
|
| 149 |
+
"bindings": [
|
| 150 |
+
{
|
| 151 |
+
"name": "blockMax",
|
| 152 |
+
"semantic": "blockMax",
|
| 153 |
+
"buffer": { "type": "read-only-storage" },
|
| 154 |
+
"elementType": "f32"
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"name": "blockSum",
|
| 158 |
+
"semantic": "blockSum",
|
| 159 |
+
"buffer": { "type": "read-only-storage" },
|
| 160 |
+
"elementType": "f32"
|
| 161 |
+
},
|
| 162 |
+
{ "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 163 |
+
{ "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 164 |
+
{
|
| 165 |
+
"name": "params",
|
| 166 |
+
"semantic": "kernel.params",
|
| 167 |
+
"buffer": { "type": "uniform" },
|
| 168 |
+
"struct": {
|
| 169 |
+
"name": "Params",
|
| 170 |
+
"fields": [{ "name": "blocks", "type": "u32", "value": "ceilDiv(blockSize, tunables.BLOCK_COLS)" }]
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
],
|
| 174 |
+
"dispatch": { "x": "batchCount" }
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"id": "normalize",
|
| 178 |
+
"name": "BiasSoftmax.LongRowNormalize",
|
| 179 |
+
"source": {
|
| 180 |
+
"shader": "bias-softmax-longrow-normalize.wgsl.jinja",
|
| 181 |
+
"inputs": {
|
| 182 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 183 |
+
"biasBlockCount": "max(1, biasBlockCount)",
|
| 184 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 185 |
+
}
|
| 186 |
+
},
|
| 187 |
+
"bindings": [
|
| 188 |
+
{
|
| 189 |
+
"name": "data",
|
| 190 |
+
"arg": "data",
|
| 191 |
+
"semantic": "data",
|
| 192 |
+
"buffer": { "type": "read-only-storage" },
|
| 193 |
+
"elementType": "$scalar"
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"name": "bias",
|
| 197 |
+
"arg": "bias",
|
| 198 |
+
"semantic": "bias",
|
| 199 |
+
"buffer": { "type": "read-only-storage" },
|
| 200 |
+
"elementType": "$scalar"
|
| 201 |
+
},
|
| 202 |
+
{ "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
|
| 203 |
+
{ "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
|
| 204 |
+
{
|
| 205 |
+
"name": "output",
|
| 206 |
+
"arg": "output",
|
| 207 |
+
"semantic": "output",
|
| 208 |
+
"buffer": { "type": "storage" },
|
| 209 |
+
"elementType": "$scalar"
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"name": "params",
|
| 213 |
+
"semantic": "kernel.params",
|
| 214 |
+
"buffer": { "type": "uniform" },
|
| 215 |
+
"struct": { "name": "Params", "fields": [{ "name": "blockSize", "type": "u32", "value": "blockSize" }] }
|
| 216 |
+
}
|
| 217 |
+
],
|
| 218 |
+
"dispatch": { "x": "ceilDiv(blockSize, tunables.BLOCK_COLS)", "y": "batchCount" }
|
| 219 |
+
}
|
| 220 |
+
]
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"id": "packed_rows",
|
| 224 |
+
"priority": 30,
|
| 225 |
+
"when": ["numel(shapes.data) == numel(shapes.output)", "ranks.data >= 1", "attrs.axis + ranks.data >= 0", "attrs.axis < ranks.data", "biasContract", "blockSize > 0", "blockSize <= 8", "batchCount >= 64", "f16Ok(dtypes.T)"],
|
| 226 |
+
"constants": {
|
| 227 |
+
"scalar": "dtypes.T",
|
| 228 |
+
"usesF16": "dtypes.T == \"f16\"",
|
| 229 |
+
"combineSubgroups": false,
|
| 230 |
+
"packedRows": true
|
| 231 |
+
},
|
| 232 |
+
"passes": [
|
| 233 |
+
{
|
| 234 |
+
"id": "main",
|
| 235 |
+
"name": "BiasSoftmax.PackedRows",
|
| 236 |
+
"source": {
|
| 237 |
+
"shader": "bias-softmax.wgsl.jinja",
|
| 238 |
+
"inputs": {
|
| 239 |
+
"blockSize": "blockSize",
|
| 240 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 241 |
+
"biasBlockCount": "max(1, biasBlockCount)",
|
| 242 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 243 |
+
}
|
| 244 |
+
},
|
| 245 |
+
"bindings": "biasSoftmax",
|
| 246 |
+
"dispatch": { "gridStride": "batchCount", "workgroupSize": "tunables.WORKGROUP_SIZE" }
|
| 247 |
+
}
|
| 248 |
+
]
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"id": "adaptive_row",
|
| 252 |
+
"priority": 10,
|
| 253 |
+
"when": ["numel(shapes.data) == numel(shapes.output)", "ranks.data >= 1", "attrs.axis + ranks.data >= 0", "attrs.axis < ranks.data", "biasContract", "numel(shapes.data) >= 0", "f16Ok(dtypes.T)"],
|
| 254 |
+
"constants": {
|
| 255 |
+
"packedRows": false,
|
| 256 |
+
"scalar": "dtypes.T",
|
| 257 |
+
"usesF16": "dtypes.T == \"f16\"",
|
| 258 |
+
"combineSubgroups": "device.features.has(\"subgroups\")"
|
| 259 |
+
},
|
| 260 |
+
"passes": [
|
| 261 |
+
{
|
| 262 |
+
"id": "main",
|
| 263 |
+
"name": "BiasSoftmax.AdaptiveRow",
|
| 264 |
+
"source": {
|
| 265 |
+
"shader": "bias-softmax.wgsl.jinja",
|
| 266 |
+
"inputs": {
|
| 267 |
+
"blockSize": "blockSize",
|
| 268 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 269 |
+
"biasBlockCount": "max(1, biasBlockCount)",
|
| 270 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 271 |
+
}
|
| 272 |
+
},
|
| 273 |
+
"bindings": "biasSoftmax",
|
| 274 |
+
"dispatch": { "workgroups": "batchCount" }
|
| 275 |
+
}
|
| 276 |
+
]
|
| 277 |
+
}
|
| 278 |
+
]
|
| 279 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "com.microsoft.BiasSoftmax",
|
| 3 |
+
"id": "_com_microsoft_biassoftmax_webgpu_d835fbf",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"bench.json": "byPgkRP7DSHGPJmC+gIf4Ataa/cGEumT0wZKQTNmAbc=",
|
| 11 |
+
"bias-softmax-longrow-normalize.wgsl.jinja": "as8qI0C8JI2Hv60Gubt6JYGw7b2viGi8HjugoaR6sIM=",
|
| 12 |
+
"bias-softmax-longrow-stats.wgsl.jinja": "etXAjT/0FmGx03+aKetnioDZrZuZPxryTqxTDp2d5nY=",
|
| 13 |
+
"bias-softmax.wgsl.jinja": "JCf3tYMWpCyp8PRZMRx8q52g8Kg5CdcX0qzWX6F1w5M=",
|
| 14 |
+
"manifest.json": "Hmp1oerHvNRx2b3UbkPVfUJUK+xDiuZOFGY5e8ej5h0=",
|
| 15 |
+
"test.json": "tPesDd/IQUsZCm/THfvKdxo+IMX0k7DLG/Ggay8AFnk="
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
|
| 19 |
+
"webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.BiasSoftmax" }
|
| 20 |
+
}
|
build/webgpu/test.json
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.BiasSoftmax",
|
| 3 |
+
"cases": [
|
| 4 |
+
{
|
| 5 |
+
"name": "inner_broadcast_consecutive_row_groups",
|
| 6 |
+
"provenance": {
|
| 7 |
+
"source": "onnxruntime/contrib_ops/cuda/math/bias_softmax_impl.cu",
|
| 8 |
+
"notes": "Distinguishes ORT's flattened inner-broadcast row grouping from both outer mode and NumPy right-aligned broadcasting."
|
| 9 |
+
},
|
| 10 |
+
"attrs": { "axis": -1, "is_inner_broadcast": 1 },
|
| 11 |
+
"inputs": {
|
| 12 |
+
"data": { "dtype": "float32", "shape": [2, 3, 2], "data": { "kind": "constant", "value": 0.0 } },
|
| 13 |
+
"bias": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [0.0, 2.0, 2.0, 0.0] } }
|
| 14 |
+
},
|
| 15 |
+
"outputs": {
|
| 16 |
+
"output": {
|
| 17 |
+
"dtype": "float32",
|
| 18 |
+
"shape": [2, 3, 2],
|
| 19 |
+
"tolerance": 0.000001,
|
| 20 |
+
"relTolerance": 0.000001,
|
| 21 |
+
"data": {
|
| 22 |
+
"kind": "values",
|
| 23 |
+
"values": [0.119202922022, 0.880797077978, 0.119202922022, 0.880797077978, 0.119202922022, 0.880797077978, 0.880797077978, 0.119202922022, 0.880797077978, 0.119202922022, 0.880797077978, 0.119202922022]
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"name": "outer_broadcast_cycles_bias_rows",
|
| 30 |
+
"provenance": {
|
| 31 |
+
"source": "onnxruntime/contrib_ops/cuda/math/bias_softmax_impl.cu",
|
| 32 |
+
"notes": "Uses the same non-NumPy-broadcastable shapes as the inner case and pins ORT's cyclic outer-broadcast row selection."
|
| 33 |
+
},
|
| 34 |
+
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 35 |
+
"inputs": {
|
| 36 |
+
"data": { "dtype": "float32", "shape": [2, 3, 2], "data": { "kind": "constant", "value": 0.0 } },
|
| 37 |
+
"bias": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [0.0, 2.0, 2.0, 0.0] } }
|
| 38 |
+
},
|
| 39 |
+
"outputs": {
|
| 40 |
+
"output": {
|
| 41 |
+
"dtype": "float32",
|
| 42 |
+
"shape": [2, 3, 2],
|
| 43 |
+
"tolerance": 0.000001,
|
| 44 |
+
"relTolerance": 0.000001,
|
| 45 |
+
"data": {
|
| 46 |
+
"kind": "values",
|
| 47 |
+
"values": [0.119202922022, 0.880797077978, 0.880797077978, 0.119202922022, 0.119202922022, 0.880797077978, 0.880797077978, 0.119202922022, 0.119202922022, 0.880797077978, 0.880797077978, 0.119202922022]
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"name": "ort_inner_broadcast_full_suffix_softmax",
|
| 54 |
+
"provenance": {
|
| 55 |
+
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 56 |
+
"test": "BiasSoftmaxTest.InnerBroadcastFullBiasBatch",
|
| 57 |
+
"notes": "Small deterministic projection of ORT's suffix softmax semantics."
|
| 58 |
+
},
|
| 59 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 60 |
+
"inputs": {
|
| 61 |
+
"data": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] } },
|
| 62 |
+
"bias": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0] } }
|
| 63 |
+
},
|
| 64 |
+
"outputs": {
|
| 65 |
+
"output": {
|
| 66 |
+
"dtype": "float32",
|
| 67 |
+
"shape": [1, 2, 2],
|
| 68 |
+
"tolerance": 0.000001,
|
| 69 |
+
"relTolerance": 0.000001,
|
| 70 |
+
"data": { "kind": "values", "values": [0.03205860328, 0.087144318742, 0.23688281809, 0.643914259888] }
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"name": "ort_inner_broadcast_empty_bias_batch",
|
| 76 |
+
"provenance": {
|
| 77 |
+
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 78 |
+
"test": "BiasSoftmaxTest.InnerBroadcastEmptyBiasBatch",
|
| 79 |
+
"notes": "Compact deterministic projection where one bias batch broadcasts across two input batches."
|
| 80 |
+
},
|
| 81 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 82 |
+
"inputs": {
|
| 83 |
+
"data": {
|
| 84 |
+
"dtype": "float32",
|
| 85 |
+
"shape": [2, 2, 2],
|
| 86 |
+
"data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
|
| 87 |
+
},
|
| 88 |
+
"bias": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0] } }
|
| 89 |
+
},
|
| 90 |
+
"outputs": {
|
| 91 |
+
"output": {
|
| 92 |
+
"dtype": "float32",
|
| 93 |
+
"shape": [2, 2, 2],
|
| 94 |
+
"tolerance": 0.000001,
|
| 95 |
+
"relTolerance": 0.000001,
|
| 96 |
+
"data": {
|
| 97 |
+
"kind": "values",
|
| 98 |
+
"values": [0.03205860328, 0.087144318742, 0.23688281809, 0.643914259888, 0.03205860328, 0.087144318742, 0.23688281809, 0.643914259888]
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"name": "ort_inner_broadcast_negative_axis",
|
| 105 |
+
"provenance": {
|
| 106 |
+
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 107 |
+
"test": "BiasSoftmaxTest.InnerBroadcastNegativeAxis",
|
| 108 |
+
"notes": "Small deterministic projection of ORT's negative-axis inner broadcast pattern."
|
| 109 |
+
},
|
| 110 |
+
"attrs": { "axis": -2, "is_inner_broadcast": 1 },
|
| 111 |
+
"inputs": {
|
| 112 |
+
"data": {
|
| 113 |
+
"dtype": "float32",
|
| 114 |
+
"shape": [2, 2, 2],
|
| 115 |
+
"data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
|
| 116 |
+
},
|
| 117 |
+
"bias": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0] } }
|
| 118 |
+
},
|
| 119 |
+
"outputs": {
|
| 120 |
+
"output": {
|
| 121 |
+
"dtype": "float32",
|
| 122 |
+
"shape": [2, 2, 2],
|
| 123 |
+
"tolerance": 0.000001,
|
| 124 |
+
"relTolerance": 0.000001,
|
| 125 |
+
"data": {
|
| 126 |
+
"kind": "values",
|
| 127 |
+
"values": [0.03205860328, 0.087144318742, 0.23688281809, 0.643914259888, 0.03205860328, 0.087144318742, 0.23688281809, 0.643914259888]
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"name": "ort_outer_broadcast_negative_axis",
|
| 134 |
+
"provenance": {
|
| 135 |
+
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 136 |
+
"test": "BiasSoftmaxTest.OuterBroadcastNegativeAxis",
|
| 137 |
+
"notes": "Small deterministic projection of ORT's negative-axis outer broadcast pattern."
|
| 138 |
+
},
|
| 139 |
+
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 140 |
+
"inputs": {
|
| 141 |
+
"data": {
|
| 142 |
+
"dtype": "float32",
|
| 143 |
+
"shape": [2, 2, 3],
|
| 144 |
+
"data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
|
| 145 |
+
},
|
| 146 |
+
"bias": {
|
| 147 |
+
"dtype": "float32",
|
| 148 |
+
"shape": [2, 3],
|
| 149 |
+
"data": { "kind": "values", "values": [0.0, 1.0, 2.0, 2.0, 1.0, 0.0] }
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
"outputs": {
|
| 153 |
+
"output": {
|
| 154 |
+
"dtype": "float32",
|
| 155 |
+
"shape": [2, 2, 3],
|
| 156 |
+
"tolerance": 0.000001,
|
| 157 |
+
"relTolerance": 0.000001,
|
| 158 |
+
"data": {
|
| 159 |
+
"kind": "values",
|
| 160 |
+
"values": [0.09003057317, 0.244728471055, 0.665240955775, 0.665240955775, 0.244728471055, 0.09003057317, 0.09003057317, 0.244728471055, 0.665240955775, 0.665240955775, 0.244728471055, 0.09003057317]
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
},
|
| 165 |
+
{
|
| 166 |
+
"name": "ort_outer_broadcast_full_bias_batch",
|
| 167 |
+
"provenance": {
|
| 168 |
+
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 169 |
+
"test": "BiasSoftmaxTest.OuterBroadcastFullBiasBatch",
|
| 170 |
+
"notes": "Compact deterministic projection where every input batch has its own full bias row."
|
| 171 |
+
},
|
| 172 |
+
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 173 |
+
"inputs": {
|
| 174 |
+
"data": {
|
| 175 |
+
"dtype": "float32",
|
| 176 |
+
"shape": [2, 2, 3],
|
| 177 |
+
"data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
|
| 178 |
+
},
|
| 179 |
+
"bias": {
|
| 180 |
+
"dtype": "float32",
|
| 181 |
+
"shape": [2, 2, 3],
|
| 182 |
+
"data": { "kind": "values", "values": [0.0, 1.0, 2.0, 2.0, 1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0] }
|
| 183 |
+
}
|
| 184 |
+
},
|
| 185 |
+
"outputs": {
|
| 186 |
+
"output": {
|
| 187 |
+
"dtype": "float32",
|
| 188 |
+
"shape": [2, 2, 3],
|
| 189 |
+
"tolerance": 0.000001,
|
| 190 |
+
"relTolerance": 0.000001,
|
| 191 |
+
"data": {
|
| 192 |
+
"kind": "values",
|
| 193 |
+
"values": [0.09003057317, 0.244728471055, 0.665240955775, 0.665240955775, 0.244728471055, 0.09003057317, 0.09003057317, 0.244728471055, 0.665240955775, 0.665240955775, 0.244728471055, 0.09003057317]
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"name": "f32_large_gap_subnormal_tail_gpu_gap",
|
| 200 |
+
"skipGpu": {
|
| 201 |
+
"category": "permanent",
|
| 202 |
+
"reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; the 87.5-point logit gap makes the tail probability subnormal, which collapses to zero instead of ORT's positive subnormal value."
|
| 203 |
+
},
|
| 204 |
+
"provenance": {
|
| 205 |
+
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 206 |
+
"test": "BiasSoftmaxTest.InnerBroadcastFullBiasBatch",
|
| 207 |
+
"notes": "BiasSoftmax is a suffix softmax over data+bias; an 87.5-point gap leaves a valid positive subnormal probability tail in ORT CPU."
|
| 208 |
+
},
|
| 209 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 210 |
+
"inputs": {
|
| 211 |
+
"data": { "dtype": "float32", "shape": [1, 2], "data": { "kind": "values", "values": [0.0, -87.5] } },
|
| 212 |
+
"bias": { "dtype": "float32", "shape": [1, 2], "data": { "kind": "values", "values": [0.0, 0.0] } }
|
| 213 |
+
},
|
| 214 |
+
"outputs": {
|
| 215 |
+
"output": {
|
| 216 |
+
"dtype": "float32",
|
| 217 |
+
"shape": [1, 2],
|
| 218 |
+
"tolerance": 2e-45,
|
| 219 |
+
"relTolerance": 0,
|
| 220 |
+
"data": { "kind": "values", "values": [1.0, 9.982351397596697e-39] }
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"name": "ort_outer_broadcast_empty_bias_batch",
|
| 226 |
+
"provenance": {
|
| 227 |
+
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 228 |
+
"test": "BiasSoftmaxTest.OuterBroadcastEmptyBiasBatch",
|
| 229 |
+
"notes": "Compact deterministic projection where one outer-broadcast bias row is reused for every input batch."
|
| 230 |
+
},
|
| 231 |
+
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 232 |
+
"inputs": {
|
| 233 |
+
"data": {
|
| 234 |
+
"dtype": "float32",
|
| 235 |
+
"shape": [2, 2, 3],
|
| 236 |
+
"data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
|
| 237 |
+
},
|
| 238 |
+
"bias": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [0.0, 1.0, 2.0] } }
|
| 239 |
+
},
|
| 240 |
+
"outputs": {
|
| 241 |
+
"output": {
|
| 242 |
+
"dtype": "float32",
|
| 243 |
+
"shape": [2, 2, 3],
|
| 244 |
+
"tolerance": 0.000001,
|
| 245 |
+
"relTolerance": 0.000001,
|
| 246 |
+
"data": {
|
| 247 |
+
"kind": "values",
|
| 248 |
+
"values": [0.09003057317, 0.244728471055, 0.665240955775, 0.09003057317, 0.244728471055, 0.665240955775, 0.09003057317, 0.244728471055, 0.665240955775, 0.09003057317, 0.244728471055, 0.665240955775]
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"name": "empty_zero_dim",
|
| 255 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 256 |
+
"inputs": {
|
| 257 |
+
"data": { "dtype": "float32", "shape": [0, 2, 2], "data": { "kind": "values", "values": [] } },
|
| 258 |
+
"bias": { "dtype": "float32", "shape": [0, 2, 2], "data": { "kind": "values", "values": [] } }
|
| 259 |
+
},
|
| 260 |
+
"outputs": { "output": { "dtype": "float32", "shape": [0, 2, 2], "tolerance": 0 } }
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"name": "f16_default_axis_two_element_row",
|
| 264 |
+
"attrs": { "is_inner_broadcast": 1 },
|
| 265 |
+
"inputs": {
|
| 266 |
+
"data": { "dtype": "float16", "shape": [1, 2], "data": { "kind": "values", "values": [0.0, 0.0] } },
|
| 267 |
+
"bias": { "dtype": "float16", "shape": [1, 2], "data": { "kind": "values", "values": [0.0, 0.0] } }
|
| 268 |
+
},
|
| 269 |
+
"outputs": {
|
| 270 |
+
"output": {
|
| 271 |
+
"dtype": "float16",
|
| 272 |
+
"shape": [1, 2],
|
| 273 |
+
"tolerance": 0,
|
| 274 |
+
"data": { "kind": "values", "values": [0.5, 0.5] }
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"name": "dispatch_cliff_rows_65537",
|
| 280 |
+
"provenance": {
|
| 281 |
+
"notes": "65,537 short rows cross the per-dimension workgroup limit. The selected packed route keeps the dispatch small and grid-strides; an all-variant run also retains coverage of the generic kernel's 2D-folded row reconstruction."
|
| 282 |
+
},
|
| 283 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 284 |
+
"inputs": {
|
| 285 |
+
"data": {
|
| 286 |
+
"dtype": "float32",
|
| 287 |
+
"shape": [65537, 4],
|
| 288 |
+
"data": { "kind": "cycle", "values": [0.1, 0.2, 0.3, 0.4, 0.5] }
|
| 289 |
+
},
|
| 290 |
+
"bias": { "dtype": "float32", "shape": [65537, 4], "data": { "kind": "constant", "value": 0.0 } }
|
| 291 |
+
},
|
| 292 |
+
"outputs": { "output": { "dtype": "float32", "shape": [65537, 4], "tolerance": 0.0001 } }
|
| 293 |
+
},
|
| 294 |
+
{
|
| 295 |
+
"name": "fully_masked_row_neg_inf_bias",
|
| 296 |
+
"provenance": {
|
| 297 |
+
"notes": "Fully additive-masked softmax row (bias = -Infinity over the whole suffix block). GPU (FLT_MAX-init online softmax), the TS reference, ai.onnx.Softmax, and ORT's WebGPU EP all emit 0 for a fully-masked row (a NaN would poison attention). Pinned to 0."
|
| 298 |
+
},
|
| 299 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 300 |
+
"inputs": {
|
| 301 |
+
"data": { "dtype": "float32", "shape": [1, 4], "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] } },
|
| 302 |
+
"bias": {
|
| 303 |
+
"dtype": "float32",
|
| 304 |
+
"shape": [1, 4],
|
| 305 |
+
"data": { "kind": "values", "values": ["-Infinity", "-Infinity", "-Infinity", "-Infinity"] }
|
| 306 |
+
}
|
| 307 |
+
},
|
| 308 |
+
"outputs": {
|
| 309 |
+
"output": {
|
| 310 |
+
"dtype": "float32",
|
| 311 |
+
"shape": [1, 4],
|
| 312 |
+
"tolerance": 0.000001,
|
| 313 |
+
"data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] }
|
| 314 |
+
}
|
| 315 |
+
}
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"name": "axis0_full_reduce_single_block",
|
| 319 |
+
"provenance": {
|
| 320 |
+
"notes": "axis=0 => outer(data,0)=1 => one softmax block over the whole tensor (batchCount=1, blockSize=numel). ORT requires one complete flattened bias row, so bias has the full [4,6] shape."
|
| 321 |
+
},
|
| 322 |
+
"attrs": { "axis": 0, "is_inner_broadcast": 0 },
|
| 323 |
+
"inputs": {
|
| 324 |
+
"data": { "dtype": "float32", "shape": [4, 6], "data": { "kind": "linspace", "start": -3.0, "end": 3.0 } },
|
| 325 |
+
"bias": {
|
| 326 |
+
"dtype": "float32",
|
| 327 |
+
"shape": [4, 6],
|
| 328 |
+
"data": {
|
| 329 |
+
"kind": "values",
|
| 330 |
+
"values": [0.5, -0.5, 1.0, -1.0, 0.25, -0.25, 0.5, -0.5, 1.0, -1.0, 0.25, -0.25, 0.5, -0.5, 1.0, -1.0, 0.25, -0.25, 0.5, -0.5, 1.0, -1.0, 0.25, -0.25]
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
},
|
| 334 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 6], "tolerance": 0.000001, "relTolerance": 0.000001 } }
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
"name": "bias_fold_skip_middle_dim_axis1",
|
| 338 |
+
"provenance": {
|
| 339 |
+
"notes": "bias [1,3,4] broadcast over batch axis of data [2,3,4], axis=1 (suffix softmax over 3*4=12). Exercises the compiled bias_index() fold when a size-1 bias axis is skipped and the reduction spans multiple axes. Truth from the TS reference."
|
| 340 |
+
},
|
| 341 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 342 |
+
"inputs": {
|
| 343 |
+
"data": { "dtype": "float32", "shape": [2, 3, 4], "data": { "kind": "linspace", "start": -2.0, "end": 2.0 } },
|
| 344 |
+
"bias": { "dtype": "float32", "shape": [1, 3, 4], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } }
|
| 345 |
+
},
|
| 346 |
+
"outputs": {
|
| 347 |
+
"output": { "dtype": "float32", "shape": [2, 3, 4], "tolerance": 0.000001, "relTolerance": 0.000001 }
|
| 348 |
+
}
|
| 349 |
+
},
|
| 350 |
+
{
|
| 351 |
+
"name": "many_two_element_rows_axis1",
|
| 352 |
+
"provenance": {
|
| 353 |
+
"notes": "Compact companion for bench case biassoftmax-f32-launchbound-262144x2-axis1: many tiny softmax rows stress the scalar-row fallback and folded dispatch accounting."
|
| 354 |
+
},
|
| 355 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 356 |
+
"inputs": {
|
| 357 |
+
"data": {
|
| 358 |
+
"dtype": "float32",
|
| 359 |
+
"shape": [4096, 2],
|
| 360 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.17, "cosStep": 0.31 }
|
| 361 |
+
},
|
| 362 |
+
"bias": { "dtype": "float32", "shape": [1, 2], "data": { "kind": "values", "values": [0.125, -0.25] } }
|
| 363 |
+
},
|
| 364 |
+
"outputs": {
|
| 365 |
+
"output": { "dtype": "float32", "shape": [4096, 2], "tolerance": 0.000001, "relTolerance": 0.000001 }
|
| 366 |
+
}
|
| 367 |
+
},
|
| 368 |
+
{
|
| 369 |
+
"name": "longrow_split_inner_broadcast_axis1_4x65536_groups2",
|
| 370 |
+
"provenance": {
|
| 371 |
+
"notes": "A long split row with consecutive inner-broadcast groups checks that each group reads the correct bias row across the full reduction."
|
| 372 |
+
},
|
| 373 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 374 |
+
"inputs": {
|
| 375 |
+
"data": {
|
| 376 |
+
"dtype": "float32",
|
| 377 |
+
"shape": [4, 65536],
|
| 378 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.0017, "cosStep": 0.0031 }
|
| 379 |
+
},
|
| 380 |
+
"bias": {
|
| 381 |
+
"dtype": "float32",
|
| 382 |
+
"shape": [2, 65536],
|
| 383 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.0011, "cosStep": 0.0023 }
|
| 384 |
+
}
|
| 385 |
+
},
|
| 386 |
+
"outputs": {
|
| 387 |
+
"output": { "dtype": "float32", "shape": [4, 65536], "tolerance": 0.000001, "relTolerance": 0.00001 }
|
| 388 |
+
}
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"name": "longrow_split_f16_axis1_4x65536_groups2",
|
| 392 |
+
"provenance": {
|
| 393 |
+
"notes": "Exercises float16 storage on the split long-row path over 65536 elements. A -30-to-0 ramp concentrates probability mass so normal float16 outputs and underflowing tail zeros coexist, while the row statistics remain in float32 scratch."
|
| 394 |
+
},
|
| 395 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 396 |
+
"inputs": {
|
| 397 |
+
"data": { "dtype": "float16", "shape": [4, 65536], "data": { "kind": "linspace", "start": -30.0, "end": 0.0 } },
|
| 398 |
+
"bias": { "dtype": "float16", "shape": [2, 65536], "data": { "kind": "constant", "value": 0.0 } }
|
| 399 |
+
},
|
| 400 |
+
"outputs": { "output": { "dtype": "float16", "shape": [4, 65536], "tolerance": 1e-7, "relTolerance": 0.01 } }
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"name": "attn_rows_axis2_4x64x256",
|
| 404 |
+
"provenance": {
|
| 405 |
+
"notes": "Compact companion for bench case biassoftmax-f32-attn-32x512x512, preserving the attention-score layout with a broadcast bias over many axis-2 softmax rows."
|
| 406 |
+
},
|
| 407 |
+
"attrs": { "axis": 2, "is_inner_broadcast": 0 },
|
| 408 |
+
"inputs": {
|
| 409 |
+
"data": {
|
| 410 |
+
"dtype": "float32",
|
| 411 |
+
"shape": [4, 64, 256],
|
| 412 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 413 |
+
},
|
| 414 |
+
"bias": {
|
| 415 |
+
"dtype": "float32",
|
| 416 |
+
"shape": [1, 64, 256],
|
| 417 |
+
"data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.011, "cosStep": 0.023 }
|
| 418 |
+
}
|
| 419 |
+
},
|
| 420 |
+
"outputs": {
|
| 421 |
+
"output": { "dtype": "float32", "shape": [4, 64, 256], "tolerance": 0.000001, "relTolerance": 0.000001 }
|
| 422 |
+
}
|
| 423 |
+
},
|
| 424 |
+
{
|
| 425 |
+
"name": "rank8_inner_broadcast_full_suffix_softmax",
|
| 426 |
+
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 427 |
+
"inputs": {
|
| 428 |
+
"data": {
|
| 429 |
+
"dtype": "float32",
|
| 430 |
+
"shape": [1, 2, 1, 2, 1, 2, 2, 2],
|
| 431 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27 }
|
| 432 |
+
},
|
| 433 |
+
"bias": {
|
| 434 |
+
"dtype": "float32",
|
| 435 |
+
"shape": [1, 2, 1, 2, 1, 2, 2, 2],
|
| 436 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.11 }
|
| 437 |
+
}
|
| 438 |
+
},
|
| 439 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 1, 2, 1, 2, 2, 2], "tolerance": 0.000002 } }
|
| 440 |
+
}
|
| 441 |
+
]
|
| 442 |
+
}
|