sync 91d990483a17
Browse files- README.md +15 -11
- build/webgpu/bench.json +0 -1
- build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja +3 -6
- build/webgpu/bias-softmax-longrow-stats.wgsl.jinja +32 -38
- build/webgpu/bias-softmax.wgsl.jinja +38 -46
- build/webgpu/manifest.json +88 -181
- build/webgpu/metadata.json +16 -9
- build/webgpu/test.json +38 -14
README.md
CHANGED
|
@@ -18,16 +18,16 @@ See the [ONNX Runtime `BiasSoftmax` contrib-operator spec](https://github.com/mi
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
-
| Name |
|
| 22 |
-
| --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `data` | `
|
| 24 |
-
| `bias` | `
|
| 25 |
|
| 26 |
## Outputs
|
| 27 |
|
| 28 |
-
| Name |
|
| 29 |
-
| --- | --- | --- | --- | --- | --- |
|
| 30 |
-
| `output` | `
|
| 31 |
|
| 32 |
## Attributes
|
| 33 |
|
|
@@ -36,7 +36,7 @@ Attributes and default values (overridable per request):
|
|
| 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,
|
| 40 |
|
| 41 |
## Type constraints
|
| 42 |
|
|
@@ -46,7 +46,7 @@ Attributes and default values (overridable per request):
|
|
| 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
|
|
@@ -56,10 +56,14 @@ Attributes and default values (overridable per request):
|
|
| 56 |
|
| 57 |
## Use with `@huggingface/kernels`
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
|
|
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
+
| Name | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `data` | `T` | — | — | The input data tensor. | required |
|
| 24 |
+
| `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 | Logical dtype | Rank | Shape | Description | Presence |
|
| 29 |
+
| --- | --- | --- | --- | --- | --- |
|
| 30 |
+
| `output` | `T` | same as `data` | same as `data` | The output tensor; same shape as data. | required |
|
| 31 |
|
| 32 |
## Attributes
|
| 33 |
|
|
|
|
| 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` | — | Let `data_row` and `bias_row` index flattened rows of length `product(data.shape[axis:])`. When 1, consecutive data-row groups reuse each bias row: `bias_row = floor(data_row / (data_row_count / bias_row_count))`. When 0, bias rows repeat cyclically: `bias_row = data_row % bias_row_count`. |
|
| 40 |
|
| 41 |
## Type constraints
|
| 42 |
|
|
|
|
| 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 + tuning cases
|
|
|
|
| 56 |
|
| 57 |
## Use with `@huggingface/kernels`
|
| 58 |
|
| 59 |
+
```sh
|
| 60 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.2
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 64 |
|
| 65 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 66 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 67 |
|
| 68 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 69 |
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "com.microsoft.BiasSoftmax",
|
| 3 |
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256], "BLOCK_COLS": [1024, 2048, 4096, 8192] },
|
| 4 |
"cases": [
|
| 5 |
{
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256], "BLOCK_COLS": [1024, 2048, 4096, 8192] },
|
| 3 |
"cases": [
|
| 4 |
{
|
build/webgpu/bias-softmax-longrow-normalize.wgsl.jinja
CHANGED
|
@@ -1,8 +1,5 @@
|
|
| 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;
|
|
@@ -10,10 +7,10 @@ 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
|
| 14 |
-
return (row / {{
|
| 15 |
{% else %}
|
| 16 |
-
return (row % {{
|
| 17 |
{% endif %}
|
| 18 |
}
|
| 19 |
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
|
|
|
| 1 |
// Parallel normalization for split long rows. One workgroup handles each
|
| 2 |
// BLOCK_COLS slice and reuses the finished per-row statistics.
|
|
|
|
|
|
|
|
|
|
| 3 |
{{ env.wgsl.resourceDeclarations }}
|
| 4 |
|
| 5 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
|
|
|
| 7 |
fn bias_index(index: u32) -> u32 {
|
| 8 |
let row = index / params.blockSize;
|
| 9 |
let column = index % params.blockSize;
|
| 10 |
+
{% if isInnerBroadcast %}
|
| 11 |
+
return (row / {{ innerRepeat }}u) * params.blockSize + column;
|
| 12 |
{% else %}
|
| 13 |
+
return (row % {{ biasBlockCountSpec }}u) * params.blockSize + column;
|
| 14 |
{% endif %}
|
| 15 |
}
|
| 16 |
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
build/webgpu/bias-softmax-longrow-stats.wgsl.jinja
CHANGED
|
@@ -1,21 +1,21 @@
|
|
| 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
|
| 5 |
enable f16;
|
| 6 |
{% endif %}
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
|
| 9 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 10 |
-
{% if
|
| 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
|
| 16 |
-
return (row / {{
|
| 17 |
{% else %}
|
| 18 |
-
return (row % {{
|
| 19 |
{% endif %}
|
| 20 |
}
|
| 21 |
{% endif %}
|
|
@@ -49,20 +49,22 @@ fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
|
| 49 |
// calls before their shared partial storage is reused.
|
| 50 |
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 51 |
{% if combineSubgroups %}
|
| 52 |
-
//
|
| 53 |
-
//
|
| 54 |
-
//
|
| 55 |
-
//
|
| 56 |
-
//
|
| 57 |
-
//
|
| 58 |
-
//
|
|
|
|
| 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)
|
| 65 |
-
//
|
| 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
|
|
@@ -73,31 +75,24 @@ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
|
| 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 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
}
|
| 93 |
workgroupBarrier();
|
| 94 |
if (lidx == 0u) {
|
| 95 |
var accM = -FLT_MAX;
|
| 96 |
var accD = 0.0;
|
| 97 |
-
for (var
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
}
|
| 102 |
combinedMD = vec2<f32>(accM, accD);
|
| 103 |
}
|
|
@@ -112,9 +107,8 @@ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
|
| 112 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 113 |
{% if mdStreamed %}
|
| 114 |
|
| 115 |
-
// In-place fold of {{ mdStreams }} streams.
|
| 116 |
-
//
|
| 117 |
-
// from slot s * WG afterwards.
|
| 118 |
fn combine_partials_streams(lidx: u32) {
|
| 119 |
workgroupBarrier();
|
| 120 |
var stride = WG / 2u;
|
|
@@ -176,7 +170,7 @@ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
|
| 176 |
@compute @workgroup_size(WG)
|
| 177 |
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 178 |
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 179 |
-
{% if
|
| 180 |
let block = wid.x;
|
| 181 |
let row = wid.y;
|
| 182 |
let rowBase = row * params.blockSize;
|
|
@@ -209,7 +203,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 209 |
|
| 210 |
let merged = combine_partials(m, d, lid.x);
|
| 211 |
if (lid.x == 0u) {
|
| 212 |
-
{% if
|
| 213 |
let out = row * params.blocks + block;
|
| 214 |
blockMax[out] = merged.x;
|
| 215 |
blockSum[out] = merged.y;
|
|
|
|
| 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 stage == "block" and usesF16 %}
|
| 5 |
enable f16;
|
| 6 |
{% endif %}
|
| 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 |
fn bias_index(index: u32) -> u32 {
|
| 13 |
let row = index / params.blockSize;
|
| 14 |
let column = index % params.blockSize;
|
| 15 |
+
{% if isInnerBroadcast | default(false) %}
|
| 16 |
+
return (row / {{ innerRepeat | default(1) }}u) * params.blockSize + column;
|
| 17 |
{% else %}
|
| 18 |
+
return (row % {{ biasBlockCountSpec | default(1) }}u) * params.blockSize + column;
|
| 19 |
{% endif %}
|
| 20 |
}
|
| 21 |
{% endif %}
|
|
|
|
| 49 |
// calls before their shared partial storage is reused.
|
| 50 |
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 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
|
|
|
|
| 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 |
}
|
|
|
|
| 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;
|
|
|
|
| 170 |
@compute @workgroup_size(WG)
|
| 171 |
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 172 |
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 173 |
+
{% if stage == "block" %}
|
| 174 |
let block = wid.x;
|
| 175 |
let row = wid.y;
|
| 176 |
let rowBase = row * params.blockSize;
|
|
|
|
| 203 |
|
| 204 |
let merged = combine_partials(m, d, lid.x);
|
| 205 |
if (lid.x == 0u) {
|
| 206 |
+
{% if stage == "block" %}
|
| 207 |
let out = row * params.blocks + block;
|
| 208 |
blockMax[out] = merged.x;
|
| 209 |
blockSum[out] = merged.y;
|
build/webgpu/bias-softmax.wgsl.jinja
CHANGED
|
@@ -2,9 +2,6 @@
|
|
| 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 %}
|
|
@@ -42,20 +39,22 @@ fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
|
| 42 |
// calls before their shared partial storage is reused.
|
| 43 |
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 44 |
{% if combineSubgroups %}
|
| 45 |
-
//
|
| 46 |
-
//
|
| 47 |
-
//
|
| 48 |
-
//
|
| 49 |
-
//
|
| 50 |
-
//
|
| 51 |
-
//
|
|
|
|
| 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)
|
| 58 |
-
//
|
| 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
|
|
@@ -66,31 +65,24 @@ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
|
| 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 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
}
|
| 86 |
workgroupBarrier();
|
| 87 |
if (lidx == 0u) {
|
| 88 |
var accM = -FLT_MAX;
|
| 89 |
var accD = 0.0;
|
| 90 |
-
for (var
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
}
|
| 95 |
combinedMD = vec2<f32>(accM, accD);
|
| 96 |
}
|
|
@@ -105,9 +97,8 @@ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
|
| 105 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 106 |
{% if mdStreamed %}
|
| 107 |
|
| 108 |
-
// In-place fold of {{ mdStreams }} streams.
|
| 109 |
-
//
|
| 110 |
-
// from slot s * WG afterwards.
|
| 111 |
fn combine_partials_streams(lidx: u32) {
|
| 112 |
workgroupBarrier();
|
| 113 |
var stride = WG / 2u;
|
|
@@ -170,16 +161,18 @@ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
|
| 170 |
fn bias_index(index: u32) -> u32 {
|
| 171 |
let row = index / params.blockSize;
|
| 172 |
let column = index % params.blockSize;
|
| 173 |
-
{% if
|
| 174 |
-
return (row / {{
|
| 175 |
{% else %}
|
| 176 |
-
return (row % {{
|
| 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 %}
|
|
@@ -187,15 +180,14 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 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
|
| 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 |
-
//
|
| 197 |
-
//
|
| 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);
|
|
@@ -224,7 +216,7 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 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, {{
|
| 228 |
var m = -FLT_MAX;
|
| 229 |
var denom = 0.0;
|
| 230 |
for (var dd = 0u; dd < params.blockSize; dd += 1u) {
|
|
@@ -243,10 +235,10 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
|
| 243 |
{% endif %}
|
| 244 |
}
|
| 245 |
{% else %}
|
| 246 |
-
// 2D-folded row index: wid.y carries the high bits
|
| 247 |
-
//
|
| 248 |
-
//
|
| 249 |
-
let row = wid.x + wid.y *
|
| 250 |
if (row >= params.batchCount) {
|
| 251 |
return;
|
| 252 |
}
|
|
|
|
| 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 combineSubgroups %}
|
| 6 |
enable subgroups;
|
| 7 |
{% endif %}
|
|
|
|
| 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
|
| 44 |
+
// publishes the subgroup pair in the slot at its OWN invocation index and sets
|
| 45 |
+
// that index's bit in a workgroup bitmask; thread 0 then folds exactly the
|
| 46 |
+
// published slots, in ascending index order (the online (m, d) merge is not
|
| 47 |
+
// float-associative, so the order is fixed), and clears the mask for the next
|
| 48 |
+
// call as it reads it. Workgroup memory starts zeroed, so the mask needs no
|
| 49 |
+
// setup. Same three collectives as a single-subgroup reduce, two barriers.
|
| 50 |
var<workgroup> partialM: array<f32, WG>;
|
| 51 |
var<workgroup> partialD: array<f32, WG>;
|
| 52 |
+
var<workgroup> leaderMask: array<atomic<u32>, (WG + 31u) / 32u>;
|
| 53 |
var<workgroup> combinedMD: vec2<f32>;
|
| 54 |
|
| 55 |
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 56 |
+
// it (no barriers, no shared state). `subgroup_size` is the size of the current
|
| 57 |
+
// subgroup and uniform, so the test is exact and may guard the barriers below.
|
| 58 |
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 59 |
let sgM = subgroupMax(m);
|
| 60 |
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
|
|
|
| 65 |
if (sgSize == WG) {
|
| 66 |
return vec2<f32>(sgM, sgD);
|
| 67 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
if (subgroupElect()) {
|
| 69 |
+
partialM[lidx] = sgM;
|
| 70 |
+
partialD[lidx] = sgD;
|
| 71 |
+
atomicOr(&leaderMask[lidx / 32u], 1u << (lidx % 32u));
|
| 72 |
}
|
| 73 |
workgroupBarrier();
|
| 74 |
if (lidx == 0u) {
|
| 75 |
var accM = -FLT_MAX;
|
| 76 |
var accD = 0.0;
|
| 77 |
+
for (var w = 0u; w < (WG + 31u) / 32u; w = w + 1u) {
|
| 78 |
+
var bits = atomicExchange(&leaderMask[w], 0u);
|
| 79 |
+
while (bits != 0u) {
|
| 80 |
+
let slot = w * 32u + firstTrailingBit(bits);
|
| 81 |
+
bits = bits & (bits - 1u);
|
| 82 |
+
let mNew = max(accM, partialM[slot]);
|
| 83 |
+
accD = accD * exp_shift(accM, mNew) + partialD[slot] * exp_shift(partialM[slot], mNew);
|
| 84 |
+
accM = mNew;
|
| 85 |
+
}
|
| 86 |
}
|
| 87 |
combinedMD = vec2<f32>(accM, accD);
|
| 88 |
}
|
|
|
|
| 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;
|
|
|
|
| 161 |
fn bias_index(index: u32) -> u32 {
|
| 162 |
let row = index / params.blockSize;
|
| 163 |
let column = index % params.blockSize;
|
| 164 |
+
{% if isInnerBroadcast %}
|
| 165 |
+
return (row / {{ innerRepeat }}u) * params.blockSize + column;
|
| 166 |
{% else %}
|
| 167 |
+
return (row % {{ biasBlockCountSpec }}u) * params.blockSize + column;
|
| 168 |
{% endif %}
|
| 169 |
}
|
| 170 |
|
| 171 |
@compute @workgroup_size(WG)
|
| 172 |
fn main(@builtin(workgroup_id) wid: vec3<u32>,
|
| 173 |
+
{%- if packedRows %}
|
| 174 |
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 175 |
+
{%- endif %}
|
| 176 |
@builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups and not packedRows %},
|
| 177 |
@builtin(subgroup_size) sgSize: u32{% endif %}) {
|
| 178 |
{% if packedRows %}
|
|
|
|
| 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[bias_index(i0)]);
|
| 187 |
let v1 = f32(data[i1]) + f32(bias[bias_index(i1)]);
|
| 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.
|
|
|
|
| 191 |
if (is_finite_f32(v0) && is_finite_f32(v1)) {
|
| 192 |
if (v0 >= v1) {
|
| 193 |
let e = exp(v1 - v0);
|
|
|
|
| 216 |
{% else %}
|
| 217 |
// Short rows fit in private registers, so the normalization sweep does not
|
| 218 |
// reread data or bias from storage.
|
| 219 |
+
var values: array<f32, {{ blockSize }}>;
|
| 220 |
var m = -FLT_MAX;
|
| 221 |
var denom = 0.0;
|
| 222 |
for (var dd = 0u; dd < params.blockSize; dd += 1u) {
|
|
|
|
| 235 |
{% endif %}
|
| 236 |
}
|
| 237 |
{% else %}
|
| 238 |
+
// 2D-folded row index: wid.y carries the high bits once the row count passes the
|
| 239 |
+
// per-axis dispatch fold width, and is zero when it does not; the
|
| 240 |
+
// row >= params.batchCount guard drops the over-dispatched tail.
|
| 241 |
+
let row = wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 242 |
if (row >= params.batchCount) {
|
| 243 |
return;
|
| 244 |
}
|
build/webgpu/manifest.json
CHANGED
|
@@ -2,36 +2,12 @@
|
|
| 2 |
"domain": "com.microsoft",
|
| 3 |
"name": "BiasSoftmax",
|
| 4 |
"sinceVersion": 1,
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
|
| 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 |
-
"
|
| 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)",
|
|
@@ -39,50 +15,18 @@
|
|
| 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 |
-
"
|
| 43 |
-
"
|
| 44 |
-
"
|
| 45 |
-
|
| 46 |
-
|
| 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": ["
|
| 85 |
-
"
|
| 86 |
"intermediates": [
|
| 87 |
{
|
| 88 |
"id": "blockMax",
|
|
@@ -101,43 +45,24 @@
|
|
| 101 |
{
|
| 102 |
"id": "block_stats",
|
| 103 |
"name": "BiasSoftmax.LongRowBlockStats",
|
| 104 |
-
"
|
| 105 |
-
|
| 106 |
-
"
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 111 |
-
}
|
| 112 |
},
|
| 113 |
"bindings": [
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 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 |
-
"
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 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" }
|
|
@@ -145,30 +70,16 @@
|
|
| 145 |
{
|
| 146 |
"id": "row_stats",
|
| 147 |
"name": "BiasSoftmax.LongRowStats",
|
| 148 |
-
"
|
|
|
|
| 149 |
"bindings": [
|
| 150 |
-
{
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 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 |
-
"
|
| 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" }
|
|
@@ -176,44 +87,19 @@
|
|
| 176 |
{
|
| 177 |
"id": "normalize",
|
| 178 |
"name": "BiasSoftmax.LongRowNormalize",
|
| 179 |
-
"
|
| 180 |
-
|
| 181 |
-
"
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 185 |
-
}
|
| 186 |
},
|
| 187 |
"bindings": [
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 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 |
}
|
|
@@ -222,8 +108,8 @@
|
|
| 222 |
{
|
| 223 |
"id": "packed_rows",
|
| 224 |
"priority": 30,
|
| 225 |
-
"when": ["
|
| 226 |
-
"
|
| 227 |
"scalar": "dtypes.T",
|
| 228 |
"usesF16": "dtypes.T == \"f16\"",
|
| 229 |
"combineSubgroups": false,
|
|
@@ -233,25 +119,37 @@
|
|
| 233 |
{
|
| 234 |
"id": "main",
|
| 235 |
"name": "BiasSoftmax.PackedRows",
|
| 236 |
-
"
|
| 237 |
-
|
| 238 |
-
"
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
"biasBlockCount": "max(1, biasBlockCount)",
|
| 242 |
-
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 243 |
-
}
|
| 244 |
},
|
| 245 |
-
"bindings":
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
}
|
| 248 |
]
|
| 249 |
},
|
| 250 |
{
|
| 251 |
"id": "adaptive_row",
|
| 252 |
"priority": 10,
|
| 253 |
-
"when": ["numel(shapes.data)
|
| 254 |
-
"
|
| 255 |
"packedRows": false,
|
| 256 |
"scalar": "dtypes.T",
|
| 257 |
"usesF16": "dtypes.T == \"f16\"",
|
|
@@ -261,17 +159,26 @@
|
|
| 261 |
{
|
| 262 |
"id": "main",
|
| 263 |
"name": "BiasSoftmax.AdaptiveRow",
|
| 264 |
-
"
|
| 265 |
-
|
| 266 |
-
"
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
"biasBlockCount": "max(1, biasBlockCount)",
|
| 270 |
-
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
| 271 |
-
}
|
| 272 |
},
|
| 273 |
-
"bindings":
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
}
|
| 276 |
]
|
| 277 |
}
|
|
|
|
| 2 |
"domain": "com.microsoft",
|
| 3 |
"name": "BiasSoftmax",
|
| 4 |
"sinceVersion": 1,
|
| 5 |
+
"inputs": { "data": { "dtype": "T" }, "bias": { "dtype": "T" } },
|
| 6 |
+
"outputs": { "output": { "dtype": "T", "rank": "ranks.data", "shape": "shapes.data" } },
|
| 7 |
+
"attributes": { "axis": { "default": 1 }, "is_inner_broadcast": {} },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"attributeConstraints": { "is_inner_broadcast": { "required": true } },
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
"typeConstraints": { "T": ["float32", "float16"] },
|
| 10 |
+
"tunables": { "WORKGROUP_SIZE": { "default": 256 }, "BLOCK_COLS": { "default": 2048 } },
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"derive": {
|
| 12 |
"axisNorm": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.data",
|
| 13 |
"batchCount": "outer(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": { "buffer": "read-only-storage", "elementType": "$scalar" },
|
| 21 |
+
"bias": { "buffer": "read-only-storage", "elementType": "$scalar" },
|
| 22 |
+
"output": { "buffer": "storage", "elementType": "$scalar" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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", "usesF16": "dtypes.T == \"f16\"", "combineSubgroups": false },
|
| 30 |
"intermediates": [
|
| 31 |
{
|
| 32 |
"id": "blockMax",
|
|
|
|
| 45 |
{
|
| 46 |
"id": "block_stats",
|
| 47 |
"name": "BiasSoftmax.LongRowBlockStats",
|
| 48 |
+
"shader": "bias-softmax-longrow-stats.wgsl.jinja",
|
| 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))"
|
|
|
|
|
|
|
| 54 |
},
|
| 55 |
"bindings": [
|
| 56 |
+
"data",
|
| 57 |
+
"bias",
|
| 58 |
+
{ "name": "blockMax", "buffer": "storage", "elementType": "f32" },
|
| 59 |
+
{ "name": "blockSum", "buffer": "storage", "elementType": "f32" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
{
|
| 61 |
"name": "params",
|
| 62 |
+
"struct": [
|
| 63 |
+
{ "name": "blockSize", "type": "u32", "value": "blockSize" },
|
| 64 |
+
{ "name": "blocks", "type": "u32", "value": "ceilDiv(blockSize, tunables.BLOCK_COLS)" }
|
| 65 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
],
|
| 68 |
"dispatch": { "x": "ceilDiv(blockSize, tunables.BLOCK_COLS)", "y": "batchCount" }
|
|
|
|
| 70 |
{
|
| 71 |
"id": "row_stats",
|
| 72 |
"name": "BiasSoftmax.LongRowStats",
|
| 73 |
+
"shader": "bias-softmax-longrow-stats.wgsl.jinja",
|
| 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", "buffer": "storage", "elementType": "f32" },
|
| 79 |
+
{ "name": "rowSum", "buffer": "storage", "elementType": "f32" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
{
|
| 81 |
"name": "params",
|
| 82 |
+
"struct": [{ "name": "blocks", "type": "u32", "value": "ceilDiv(blockSize, tunables.BLOCK_COLS)" }]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
}
|
| 84 |
],
|
| 85 |
"dispatch": { "x": "batchCount" }
|
|
|
|
| 87 |
{
|
| 88 |
"id": "normalize",
|
| 89 |
"name": "BiasSoftmax.LongRowNormalize",
|
| 90 |
+
"shader": "bias-softmax-longrow-normalize.wgsl.jinja",
|
| 91 |
+
"derive": {
|
| 92 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 93 |
+
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 94 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
|
|
|
|
|
|
| 95 |
},
|
| 96 |
"bindings": [
|
| 97 |
+
"data",
|
| 98 |
+
"bias",
|
| 99 |
+
{ "name": "rowMax", "buffer": "read-only-storage", "elementType": "f32" },
|
| 100 |
+
{ "name": "rowSum", "buffer": "read-only-storage", "elementType": "f32" },
|
| 101 |
+
"output",
|
| 102 |
+
{ "name": "params", "struct": [{ "name": "blockSize", "type": "u32", "value": "blockSize" }] }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
],
|
| 104 |
"dispatch": { "x": "ceilDiv(blockSize, tunables.BLOCK_COLS)", "y": "batchCount" }
|
| 105 |
}
|
|
|
|
| 108 |
{
|
| 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,
|
|
|
|
| 119 |
{
|
| 120 |
"id": "main",
|
| 121 |
"name": "BiasSoftmax.PackedRows",
|
| 122 |
+
"shader": "bias-softmax.wgsl.jinja",
|
| 123 |
+
"derive": {
|
| 124 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 125 |
+
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 126 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
|
|
|
|
|
|
|
|
|
| 127 |
},
|
| 128 |
+
"bindings": [
|
| 129 |
+
"data",
|
| 130 |
+
"bias",
|
| 131 |
+
"output",
|
| 132 |
+
{
|
| 133 |
+
"name": "params",
|
| 134 |
+
"struct": [
|
| 135 |
+
{ "name": "blockSize", "type": "u32", "value": "blockSize" },
|
| 136 |
+
{ "name": "batchCount", "type": "u32", "value": "batchCount" }
|
| 137 |
+
]
|
| 138 |
+
}
|
| 139 |
+
],
|
| 140 |
+
"dispatch": {
|
| 141 |
+
"x": "min(ceilDiv((batchCount), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
|
| 142 |
+
"y": 1,
|
| 143 |
+
"z": 1
|
| 144 |
+
}
|
| 145 |
}
|
| 146 |
]
|
| 147 |
},
|
| 148 |
{
|
| 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\"",
|
|
|
|
| 159 |
{
|
| 160 |
"id": "main",
|
| 161 |
"name": "BiasSoftmax.AdaptiveRow",
|
| 162 |
+
"shader": "bias-softmax.wgsl.jinja",
|
| 163 |
+
"derive": {
|
| 164 |
+
"isInnerBroadcast": "attrs.is_inner_broadcast != 0",
|
| 165 |
+
"biasBlockCountSpec": "max(1, biasBlockCount)",
|
| 166 |
+
"innerRepeat": "max(1, batchCount / max(1, biasBlockCount))"
|
|
|
|
|
|
|
|
|
|
| 167 |
},
|
| 168 |
+
"bindings": [
|
| 169 |
+
"data",
|
| 170 |
+
"bias",
|
| 171 |
+
"output",
|
| 172 |
+
{
|
| 173 |
+
"name": "params",
|
| 174 |
+
"struct": [
|
| 175 |
+
{ "name": "blockSize", "type": "u32", "value": "blockSize" },
|
| 176 |
+
{ "name": "batchCount", "type": "u32", "value": "batchCount" }
|
| 177 |
+
]
|
| 178 |
+
}
|
| 179 |
+
],
|
| 180 |
+
"dispatch": { "x": "min(batchCount, 65535)", "y": "ceilDiv(batchCount, 65535)", "z": 1 },
|
| 181 |
+
"subgroupCollectivesWidth": "portable"
|
| 182 |
}
|
| 183 |
]
|
| 184 |
}
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,20 +1,27 @@
|
|
| 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-longrow-stats.wgsl.jinja": "
|
| 13 |
-
"bias-softmax.wgsl.jinja": "
|
| 14 |
-
"manifest.json": "
|
| 15 |
-
"test.json": "
|
| 16 |
}
|
| 17 |
},
|
| 18 |
-
"provenance": { "kernel": { "sha": "
|
| 19 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.BiasSoftmax",
|
| 3 |
+
"id": "_com_microsoft_biassoftmax_webgpu_cbcd64a",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "rgHjgRMHX0A/pGpGh4i7pvOQ59E3cUmsKVhIyuZBnvY=",
|
| 11 |
+
"bias-softmax-longrow-normalize.wgsl.jinja": "lX2Z/FpHVrFdQTOlt+JKYwA7eZAokjLCgluP7kssTkc=",
|
| 12 |
+
"bias-softmax-longrow-stats.wgsl.jinja": "SXOFecNckSGTdUceUizrqmBaFAeByEkUA0bei53gRw0=",
|
| 13 |
+
"bias-softmax.wgsl.jinja": "CR9eMAgk196zS8W9yMWQTLlqDcvrrzVdvHMe9NjPnO0=",
|
| 14 |
+
"manifest.json": "Byu1oYp1SQbvVzBg59JbyQIwhIJTedDlg9UrOMbePEU=",
|
| 15 |
+
"test.json": "B+sdyYMUEL3T1qIYkK3plwl3nTChHF7KfEa6LDITSdE="
|
| 16 |
}
|
| 17 |
},
|
| 18 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 19 |
+
"webgpu": {
|
| 20 |
+
"manifestSpec": "2.0",
|
| 21 |
+
"variants": {
|
| 22 |
+
"longrow_split": ["bias-softmax-longrow-normalize.wgsl.jinja", "bias-softmax-longrow-stats.wgsl.jinja"],
|
| 23 |
+
"packed_rows": ["bias-softmax.wgsl.jinja"],
|
| 24 |
+
"adaptive_row": ["bias-softmax.wgsl.jinja"]
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
}
|
build/webgpu/test.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
| 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
|
| 9 |
},
|
| 10 |
"attrs": { "axis": -1, "is_inner_broadcast": 1 },
|
| 11 |
"inputs": {
|
|
@@ -29,7 +28,7 @@
|
|
| 29 |
"name": "outer_broadcast_cycles_bias_rows",
|
| 30 |
"provenance": {
|
| 31 |
"source": "onnxruntime/contrib_ops/cuda/math/bias_softmax_impl.cu",
|
| 32 |
-
"notes": "
|
| 33 |
},
|
| 34 |
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 35 |
"inputs": {
|
|
@@ -54,7 +53,7 @@
|
|
| 54 |
"provenance": {
|
| 55 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 56 |
"test": "BiasSoftmaxTest.InnerBroadcastFullBiasBatch",
|
| 57 |
-
"notes": "Small deterministic
|
| 58 |
},
|
| 59 |
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 60 |
"inputs": {
|
|
@@ -105,7 +104,7 @@
|
|
| 105 |
"provenance": {
|
| 106 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 107 |
"test": "BiasSoftmaxTest.InnerBroadcastNegativeAxis",
|
| 108 |
-
"notes": "
|
| 109 |
},
|
| 110 |
"attrs": { "axis": -2, "is_inner_broadcast": 1 },
|
| 111 |
"inputs": {
|
|
@@ -134,7 +133,7 @@
|
|
| 134 |
"provenance": {
|
| 135 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 136 |
"test": "BiasSoftmaxTest.OuterBroadcastNegativeAxis",
|
| 137 |
-
"notes": "
|
| 138 |
},
|
| 139 |
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 140 |
"inputs": {
|
|
@@ -199,12 +198,12 @@
|
|
| 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
|
| 203 |
},
|
| 204 |
"provenance": {
|
| 205 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 206 |
"test": "BiasSoftmaxTest.InnerBroadcastFullBiasBatch",
|
| 207 |
-
"notes": "
|
| 208 |
},
|
| 209 |
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 210 |
"inputs": {
|
|
@@ -278,7 +277,7 @@
|
|
| 278 |
{
|
| 279 |
"name": "dispatch_cliff_rows_65537",
|
| 280 |
"provenance": {
|
| 281 |
-
"notes": "65,537 short rows cross the per-dimension workgroup limit. The
|
| 282 |
},
|
| 283 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 284 |
"inputs": {
|
|
@@ -294,7 +293,7 @@
|
|
| 294 |
{
|
| 295 |
"name": "fully_masked_row_neg_inf_bias",
|
| 296 |
"provenance": {
|
| 297 |
-
"notes": "
|
| 298 |
},
|
| 299 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 300 |
"inputs": {
|
|
@@ -314,10 +313,35 @@
|
|
| 314 |
}
|
| 315 |
}
|
| 316 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
{
|
| 318 |
"name": "axis0_full_reduce_single_block",
|
| 319 |
"provenance": {
|
| 320 |
-
"notes": "axis=0
|
| 321 |
},
|
| 322 |
"attrs": { "axis": 0, "is_inner_broadcast": 0 },
|
| 323 |
"inputs": {
|
|
@@ -336,7 +360,7 @@
|
|
| 336 |
{
|
| 337 |
"name": "bias_fold_skip_middle_dim_axis1",
|
| 338 |
"provenance": {
|
| 339 |
-
"notes": "
|
| 340 |
},
|
| 341 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 342 |
"inputs": {
|
|
@@ -350,7 +374,7 @@
|
|
| 350 |
{
|
| 351 |
"name": "many_two_element_rows_axis1",
|
| 352 |
"provenance": {
|
| 353 |
-
"notes": "
|
| 354 |
},
|
| 355 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 356 |
"inputs": {
|
|
@@ -402,7 +426,7 @@
|
|
| 402 |
{
|
| 403 |
"name": "attn_rows_axis2_4x64x256",
|
| 404 |
"provenance": {
|
| 405 |
-
"notes": "
|
| 406 |
},
|
| 407 |
"attrs": { "axis": 2, "is_inner_broadcast": 0 },
|
| 408 |
"inputs": {
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"cases": [
|
| 3 |
{
|
| 4 |
"name": "inner_broadcast_consecutive_row_groups",
|
| 5 |
"provenance": {
|
| 6 |
"source": "onnxruntime/contrib_ops/cuda/math/bias_softmax_impl.cu",
|
| 7 |
+
"notes": "Distinguishes flattened inner-broadcast row grouping from outer mode and right-aligned broadcasting."
|
| 8 |
},
|
| 9 |
"attrs": { "axis": -1, "is_inner_broadcast": 1 },
|
| 10 |
"inputs": {
|
|
|
|
| 28 |
"name": "outer_broadcast_cycles_bias_rows",
|
| 29 |
"provenance": {
|
| 30 |
"source": "onnxruntime/contrib_ops/cuda/math/bias_softmax_impl.cu",
|
| 31 |
+
"notes": "Non-broadcastable data and bias shapes exercise cyclic outer-broadcast row selection."
|
| 32 |
},
|
| 33 |
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 34 |
"inputs": {
|
|
|
|
| 53 |
"provenance": {
|
| 54 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 55 |
"test": "BiasSoftmaxTest.InnerBroadcastFullBiasBatch",
|
| 56 |
+
"notes": "Small deterministic suffix-softmax case."
|
| 57 |
},
|
| 58 |
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 59 |
"inputs": {
|
|
|
|
| 104 |
"provenance": {
|
| 105 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 106 |
"test": "BiasSoftmaxTest.InnerBroadcastNegativeAxis",
|
| 107 |
+
"notes": "Negative-axis inner-broadcast case."
|
| 108 |
},
|
| 109 |
"attrs": { "axis": -2, "is_inner_broadcast": 1 },
|
| 110 |
"inputs": {
|
|
|
|
| 133 |
"provenance": {
|
| 134 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 135 |
"test": "BiasSoftmaxTest.OuterBroadcastNegativeAxis",
|
| 136 |
+
"notes": "Negative-axis outer-broadcast case."
|
| 137 |
},
|
| 138 |
"attrs": { "axis": -1, "is_inner_broadcast": 0 },
|
| 139 |
"inputs": {
|
|
|
|
| 198 |
"name": "f32_large_gap_subnormal_tail_gpu_gap",
|
| 199 |
"skipGpu": {
|
| 200 |
"category": "permanent",
|
| 201 |
+
"reason": "Portable WGSL floating-point semantics do not guarantee preservation of subnormal values. The 87.5-point logit gap requires a positive subnormal probability tail, which may be flushed to zero."
|
| 202 |
},
|
| 203 |
"provenance": {
|
| 204 |
"source": "onnxruntime/test/contrib_ops/bias_softmax_op_test.cc",
|
| 205 |
"test": "BiasSoftmaxTest.InnerBroadcastFullBiasBatch",
|
| 206 |
+
"notes": "An 87.5-point logit gap leaves a positive subnormal probability tail after suffix softmax over data+bias."
|
| 207 |
},
|
| 208 |
"attrs": { "axis": 1, "is_inner_broadcast": 1 },
|
| 209 |
"inputs": {
|
|
|
|
| 277 |
{
|
| 278 |
"name": "dispatch_cliff_rows_65537",
|
| 279 |
"provenance": {
|
| 280 |
+
"notes": "65,537 short rows cross the per-dimension workgroup limit. The packed route grid-strides over rows, while the generic route reconstructs row indices from a two-dimensional dispatch."
|
| 281 |
},
|
| 282 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 283 |
"inputs": {
|
|
|
|
| 293 |
{
|
| 294 |
"name": "fully_masked_row_neg_inf_bias",
|
| 295 |
"provenance": {
|
| 296 |
+
"notes": "The additive bias is -Infinity across the entire suffix block. The expected result uses the package's all-zero convention for a fully masked row, preventing NaNs from propagating into attention."
|
| 297 |
},
|
| 298 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 299 |
"inputs": {
|
|
|
|
| 313 |
}
|
| 314 |
}
|
| 315 |
},
|
| 316 |
+
{
|
| 317 |
+
"name": "attn_rows_axis3_row1_all_neg_inf_bias_2x8x4x64",
|
| 318 |
+
"provenance": {
|
| 319 |
+
"notes": "Data has shape [2, 8, 4, 64], and bias [1, 1, 4, 64] uses outer broadcasting. Bias query row 1 is -Infinity at every key, so 16 of the 64 softmax rows are fully masked and must be all zero; the other rows remain finite. This operator's zero-denominator rule differs from applying ONNX Softmax directly to data plus bias, which would produce NaN for the fully masked rows."
|
| 320 |
+
},
|
| 321 |
+
"attrs": { "axis": 3, "is_inner_broadcast": 0 },
|
| 322 |
+
"inputs": {
|
| 323 |
+
"data": {
|
| 324 |
+
"dtype": "float32",
|
| 325 |
+
"shape": [2, 8, 4, 64],
|
| 326 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.017, "cosStep": 0.031 }
|
| 327 |
+
},
|
| 328 |
+
"bias": {
|
| 329 |
+
"dtype": "float32",
|
| 330 |
+
"shape": [1, 1, 4, 64],
|
| 331 |
+
"data": {
|
| 332 |
+
"kind": "values",
|
| 333 |
+
"values": [0.1, 0.1906, 0.2752, 0.3492, 0.4087, 0.4504, 0.4717, 0.4715, 0.4493, 0.4061, 0.3436, 0.265, 0.1738, 0.0744, -0.0283, -0.1293, -0.2239, -0.3075, -0.376, -0.4264, -0.4563, -0.4646, -0.4512, -0.417, -0.3642, -0.2957, -0.2155, -0.1278, -0.0373, 0.051, 0.1326, 0.2033, 0.2593, 0.2978, 0.3171, 0.3164, 0.2959, 0.2571, 0.2022, 0.1347, 0.0584, -0.0222, -0.1024, -0.1777, -0.2435, -0.296, -0.3319, -0.349, -0.3458, -0.3223, -0.2793, -0.2187, -0.1434, -0.0571, 0.0358, 0.1307, 0.2226, 0.3069, 0.379, 0.4352, 0.4725, 0.4887, 0.4829, 0.4552, "-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.4895, 0.458, 0.4063, 0.3372, 0.2542, 0.1614, 0.0636, -0.0343, -0.1275, -0.2114, -0.282, -0.3358, -0.3705, -0.3848, -0.3781, -0.3515, -0.3066, -0.2464, -0.1744, -0.0948, -0.0122, 0.0686, 0.1431, 0.207, 0.2566, 0.2892, 0.3028, 0.2966, 0.2708, 0.2266, 0.1664, 0.0935, 0.0118, -0.0744, -0.1602, -0.2409, -0.3121, -0.3695, -0.41, -0.4309, -0.4307, -0.4092, -0.3669, -0.3056, -0.2282, -0.1382, -0.0401, 0.0616, 0.1619, 0.2557, 0.3387, 0.4067, 0.4564, 0.4856, 0.4928, 0.4779, 0.4417, 0.3864, 0.3147, 0.2305, 0.1381, 0.0423, -0.052, -0.1401, 0.271, 0.1834, 0.0902, -0.0037, -0.0937, -0.175, -0.2436, -0.2963, -0.3304, -0.3445, -0.3382, -0.3123, -0.2685, -0.2096, -0.139, -0.061, 0.0199, 0.0989, 0.1716, 0.2336, 0.2813, 0.3118, 0.3232, 0.3144, 0.2858, 0.2386, 0.1751, 0.0984, 0.0126, -0.078, -0.1685, -0.2541, -0.3303, -0.3928, -0.4382, -0.4639, -0.4683, -0.4508, -0.4119, -0.3534, -0.278, -0.1892, -0.0912, 0.0112, 0.1131, 0.2095, 0.2959, 0.3681, 0.4226, 0.4571, 0.4701, 0.4612, 0.4311, 0.3818, 0.316, 0.2374, 0.1503, 0.0592, -0.0309, -0.1153, -0.1897, -0.2502, -0.2939, -0.3186]
|
| 334 |
+
}
|
| 335 |
+
}
|
| 336 |
+
},
|
| 337 |
+
"outputs": {
|
| 338 |
+
"output": { "dtype": "float32", "shape": [2, 8, 4, 64], "tolerance": 0.000001, "relTolerance": 0.000001 }
|
| 339 |
+
}
|
| 340 |
+
},
|
| 341 |
{
|
| 342 |
"name": "axis0_full_reduce_single_block",
|
| 343 |
"provenance": {
|
| 344 |
+
"notes": "With axis=0, one softmax block covers the whole tensor. The bias supplies one complete flattened row with shape [4,6]."
|
| 345 |
},
|
| 346 |
"attrs": { "axis": 0, "is_inner_broadcast": 0 },
|
| 347 |
"inputs": {
|
|
|
|
| 360 |
{
|
| 361 |
"name": "bias_fold_skip_middle_dim_axis1",
|
| 362 |
"provenance": {
|
| 363 |
+
"notes": "Bias [1,3,4] broadcasts across the batch axis of data [2,3,4]. axis=1 reduces each 12-element suffix while the size-one bias axis is skipped during broadcast indexing."
|
| 364 |
},
|
| 365 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 366 |
"inputs": {
|
|
|
|
| 374 |
{
|
| 375 |
"name": "many_two_element_rows_axis1",
|
| 376 |
"provenance": {
|
| 377 |
+
"notes": "Many two-element softmax rows exercise the scalar-row fallback and folded dispatch accounting used by `biassoftmax-f32-launchbound-262144x2-axis1`."
|
| 378 |
},
|
| 379 |
"attrs": { "axis": 1, "is_inner_broadcast": 0 },
|
| 380 |
"inputs": {
|
|
|
|
| 426 |
{
|
| 427 |
"name": "attn_rows_axis2_4x64x256",
|
| 428 |
"provenance": {
|
| 429 |
+
"notes": "An attention-score layout with broadcast bias exercises many axis-2 softmax rows at a compact scale representative of `biassoftmax-f32-attn-32x512x512`."
|
| 430 |
},
|
| 431 |
"attrs": { "axis": 2, "is_inner_broadcast": 0 },
|
| 432 |
"inputs": {
|