sync 6fdf6301e2bb
Browse files- README.md +118 -0
- build/webgpu/attn-packed-varlen-cluster.wgsl.jinja +432 -0
- build/webgpu/attn-packed-varlen-scalar.wgsl.jinja +187 -0
- build/webgpu/bench.json +0 -0
- build/webgpu/manifest.json +667 -0
- build/webgpu/metadata.json +47 -0
- build/webgpu/test.json +0 -0
README.md
CHANGED
|
@@ -1,3 +1,121 @@
|
|
| 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.PackedMultiHeadAttention
|
| 10 |
+
|
| 11 |
+
`com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Multi-head self-attention over a padding-removed token stream. The `token_offset` and `cumulative_sequence_length` schedule maps every packed token to its sequence, and a token attends only the keys of that sequence. Query is either packed `[T, N, 3, H]` with key and value absent, or a separate `[T, hidden]` triple. The optional attention bias is indexed in PADDED coordinates. Float32 and float16 only; bfloat16 is not implemented.
|
| 16 |
+
|
| 17 |
+
See the [ONNX Runtime `PackedMultiHeadAttention` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.PackedMultiHeadAttention) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Upstream name | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `queryT` | `query` | `T` | same as logical dtype | — | — | Packed queries: `[token_count, num_heads, 3, head_size]` with `key` and `value` absent, or `[token_count, hidden_size]` alongside both. | required |
|
| 24 |
+
| `keyT` | `key` | `T` | same as logical dtype | `2` | — | Keys with shape `[token_count, hidden_size]`, matching `query` exactly. Present only in the separate form. | optional |
|
| 25 |
+
| `valueT` | `value` | `T` | same as logical dtype | `2` | — | Values with shape `[token_count, v_hidden_size]`. Present only in the separate form. | optional |
|
| 26 |
+
| `biasT` | `bias` | `T` | same as logical dtype | `1` | — | Optional packed input-projection bias with shape `[hidden_size + hidden_size + v_hidden_size]`, added to Q, K and V. | optional |
|
| 27 |
+
| `tokenOffsetT` | `token_offset` | `M` | `int32` | `2` | — | Shape `[batch_size, sequence_length]`. The first `token_count` entries hold each packed token's flat index in the padded grid; the rest hold the padding positions. This tensor carries the padded `sequence_length`. | required |
|
| 28 |
+
| `cumulativeSequenceLengthT` | `cumulative_sequence_length` | `M` | `int32` | `1` | — | Exclusive prefix sums with shape `[batch_size + 1]`, starting at 0 and ending at `token_count`. Sequence `i` owns packed tokens `[cum[i], cum[i + 1])`. Outputs are unspecified for a malformed schedule. | required |
|
| 29 |
+
| `attentionBiasT` | `attention_bias` | `T` | same as logical dtype | `4` | — | Optional additive score bias with shape `[batch_size or 1, num_heads or 1, sequence_length, sequence_length]`. Its trailing axes are PADDED positions inside a sequence, not packed indices. | optional |
|
| 30 |
+
|
| 31 |
+
## Outputs
|
| 32 |
+
|
| 33 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 34 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 35 |
+
| `outputT` | `output` | `T` | `2` | derived | Attention output with shape `[token_count, v_hidden_size]`, in packed token order. | required |
|
| 36 |
+
|
| 37 |
+
## Attributes
|
| 38 |
+
|
| 39 |
+
Attributes and default values (overridable per request):
|
| 40 |
+
|
| 41 |
+
| Attribute | Default | Description |
|
| 42 |
+
| --- | --- | --- |
|
| 43 |
+
| `num_heads` | — | Number of attention heads. Required; the hidden size must be divisible by it. |
|
| 44 |
+
| `scale` | — | Optional score scale. Omission, and the value `0`, both select `1 / sqrt(head_size)`. |
|
| 45 |
+
|
| 46 |
+
## Type constraints
|
| 47 |
+
|
| 48 |
+
| Variable | Allowed dtypes |
|
| 49 |
+
| --- | --- |
|
| 50 |
+
| `T` | `float32`, `float16` |
|
| 51 |
+
| `M` | `int32` |
|
| 52 |
+
|
| 53 |
+
## Implementation variants
|
| 54 |
+
|
| 55 |
+
One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
|
| 56 |
+
|
| 57 |
+
- `packed_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 58 |
+
- `packed_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 59 |
+
- `packed_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 60 |
+
- `packed_attn_bias_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 61 |
+
- `packed_attn_bias_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 62 |
+
- `packed_attn_bias_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 63 |
+
- `packed_bias_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 64 |
+
- `packed_bias_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 65 |
+
- `packed_bias_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 66 |
+
- `packed_bias_attn_bias_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 67 |
+
- `packed_bias_attn_bias_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 68 |
+
- `packed_bias_attn_bias_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 69 |
+
- `separate_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 70 |
+
- `separate_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 71 |
+
- `separate_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 72 |
+
- `separate_attn_bias_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 73 |
+
- `separate_attn_bias_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 74 |
+
- `separate_attn_bias_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 75 |
+
- `separate_bias_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 76 |
+
- `separate_bias_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 77 |
+
- `separate_bias_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 78 |
+
- `separate_bias_attn_bias_flash` — Tiled attention with independent Q/K and V slices and subgroup dot reductions. Equal wide heads target four `vec4` fragments per lane, bounded by subgroup/workgroup limits and native staging capacity. Unequal tiles cap work by token count and yield to serial or wider portable clusters when reuse is limited.
|
| 79 |
+
- `separate_bias_attn_bias_flash_nosg` — Portable tiled attention with independent query/key and value slices. Lanes cover the larger width, with guarded zero Q/K fragments and shared reductions. Device limits bound lane count and staging. Unequal-width tiles yield to serial attention below the query-reuse target.
|
| 80 |
+
- `separate_bias_attn_bias_serial` — Portable serial-key attention: one workgroup owns one (packed token, head), walks that token's own sequence and reduces each dot through workgroup memory. Head dimensions need no alignment and the value head dimension may differ.
|
| 81 |
+
|
| 82 |
+
## Device requirements
|
| 83 |
+
|
| 84 |
+
Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
| 85 |
+
|
| 86 |
+
## Files
|
| 87 |
+
|
| 88 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 89 |
+
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 90 |
+
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 91 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark cases
|
| 92 |
+
- [`attn-packed-varlen-cluster.wgsl.jinja`](build/webgpu/attn-packed-varlen-cluster.wgsl.jinja)
|
| 93 |
+
- [`attn-packed-varlen-scalar.wgsl.jinja`](build/webgpu/attn-packed-varlen-scalar.wgsl.jinja)
|
| 94 |
+
|
| 95 |
+
## Use with `@huggingface/kernels`
|
| 96 |
+
|
| 97 |
+
```sh
|
| 98 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.3
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 102 |
+
|
| 103 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 104 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 105 |
+
|
| 106 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 107 |
+
|
| 108 |
+
```js
|
| 109 |
+
import { getKernel } from "@huggingface/kernels";
|
| 110 |
+
|
| 111 |
+
const kernel = await getKernel("webgpu-kernels/com.microsoft.PackedMultiHeadAttention", { version: 1 });
|
| 112 |
+
const { outputT } = await kernel({
|
| 113 |
+
queryT: { data: queryTData, shape: [1, 16] },
|
| 114 |
+
keyT: { data: keyTData, shape: [1, 16] },
|
| 115 |
+
valueT: { data: valueTData, shape: [1, 20] },
|
| 116 |
+
tokenOffsetT: { data: tokenOffsetTData, shape: [1, 1] },
|
| 117 |
+
cumulativeSequenceLengthT: { data: cumulativeSequenceLengthTData, shape: [2] },
|
| 118 |
+
}, {
|
| 119 |
+
attrs: { num_heads: 1 },
|
| 120 |
+
});
|
| 121 |
+
```
|
build/webgpu/attn-packed-varlen-cluster.wgsl.jinja
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% set parallelSoftmax = distributedSoftmax is defined and distributedSoftmax and not useSubgroups %}
|
| 2 |
+
{% if useSubgroups %}
|
| 3 |
+
enable subgroups;
|
| 4 |
+
{% endif %}
|
| 5 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 6 |
+
{% set QBUF = "query" %}
|
| 7 |
+
{% set KBUF = "query" if packedQkv else "key" %}
|
| 8 |
+
{% set VBUF = "query" if packedQkv else "value" %}
|
| 9 |
+
{% set BIAS_K_ROW = biasKOffset if biasKOffset is defined else 0 %}
|
| 10 |
+
{% set BIAS_V_ROW = biasVOffset if biasVOffset is defined else 0 %}
|
| 11 |
+
{% set ST = "f16" if packedStageBytes == 2 else "f32" %}
|
| 12 |
+
{% set SLICE_COUNT = (((headDimV4 + LPQ - 1) / LPQ) | int) %}
|
| 13 |
+
{% set queryTail = headDimV4 % LPQ != 0 %}
|
| 14 |
+
{% set VALUE_DIM_V4 = ((vHeadDim / 4) | int) %}
|
| 15 |
+
{% set VALUE_SLICES = (((VALUE_DIM_V4 + LPQ - 1) / LPQ) | int) %}
|
| 16 |
+
{% set unequalValue = vHeadDim != headDim %}
|
| 17 |
+
{% set valueTail = VALUE_DIM_V4 % LPQ != 0 %}
|
| 18 |
+
{% set valueWidth = "VALUE_HEAD_DIM_V4" if unequalValue else "HEAD_DIM_V4" %}
|
| 19 |
+
{% set valueLane = "valueLaneSlice" if unequalValue else "laneSlice" %}
|
| 20 |
+
|
| 21 |
+
// Tiled flash attention over a padding-removed packed token stream. One
|
| 22 |
+
// workgroup covers TILE_Q consecutive packed tokens for one head, with an
|
| 23 |
+
// LPQ-lane cluster per token; K and V are staged in workgroup memory once per
|
| 24 |
+
// TILE_K keys and reused by every token in the tile. Each lane owns contiguous
|
| 25 |
+
// query and output vec4 slices, sized independently for the Q/K and V widths.
|
| 26 |
+
// Dot products, online softmax and weighted value sums accumulate in f32; output
|
| 27 |
+
// conversion happens at the store. The staging type above governs biased K/V.
|
| 28 |
+
|
| 29 |
+
// A packed tile can straddle a sequence boundary, so the key loop runs over the
|
| 30 |
+
// workgroup's union range [wgStart, wgEnd) — the first token's sequence start to
|
| 31 |
+
// the last token's sequence end — so the trip count is uniform, and each token
|
| 32 |
+
// masks the keys outside its own sequence.
|
| 33 |
+
|
| 34 |
+
{% if packedQkv %}
|
| 35 |
+
// Source: one packed `[T, N, 3, H]` buffer; Q, K and V differ only by offset.
|
| 36 |
+
{% else %}
|
| 37 |
+
// Source: separate token-major `[T, hidden]` query, key and value buffers.
|
| 38 |
+
{% endif %}
|
| 39 |
+
{% if hasBias %}
|
| 40 |
+
|
| 41 |
+
// Packed `[Q; K; V]` bias rows: the Q bias enters the query registers, and the
|
| 42 |
+
// K and V biases fold into the staged tiles, so a bias element is added once per
|
| 43 |
+
// tile rather than once per (token, key).
|
| 44 |
+
{% endif %}
|
| 45 |
+
{% if hasAttnBias %}
|
| 46 |
+
|
| 47 |
+
// The additive attention bias lives in PADDED coordinates: its trailing two axes
|
| 48 |
+
// are indexed by the query's and the key's position inside their shared
|
| 49 |
+
// sequence, not by their packed indices. A zero broadcast stride collapses the
|
| 50 |
+
// batch or head axis.
|
| 51 |
+
{% endif %}
|
| 52 |
+
|
| 53 |
+
{% if useSubgroups %}
|
| 54 |
+
// The per-lane dot fragments combine through a log2(LPQ) subgroupShuffleXor
|
| 55 |
+
// butterfly inside each cluster.
|
| 56 |
+
{% else %}
|
| 57 |
+
// The per-lane dot fragments combine through workgroup memory; this form
|
| 58 |
+
// requires no subgroup feature.
|
| 59 |
+
{% endif %}
|
| 60 |
+
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 61 |
+
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 62 |
+
{% if unequalValue %}
|
| 63 |
+
const VALUE_HEAD_DIM_V4: u32 = {{ VALUE_DIM_V4 }}u;
|
| 64 |
+
{% if hasBias %}
|
| 65 |
+
const VALUE_HEAD_DIM: u32 = {{ vHeadDim }}u;
|
| 66 |
+
{% endif %}
|
| 67 |
+
{% endif %}
|
| 68 |
+
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 69 |
+
const Q_ROW_V4: u32 = {{ qRowStrideV4 }}u;
|
| 70 |
+
const Q_HEAD_V4: u32 = {{ qHeadStrideV4 }}u;
|
| 71 |
+
const K_ROW_V4: u32 = {{ kRowStrideV4 }}u;
|
| 72 |
+
const K_HEAD_V4: u32 = {{ kHeadStrideV4 }}u;
|
| 73 |
+
const K_OFFSET_V4: u32 = {{ kOffsetV4 }}u;
|
| 74 |
+
const V_ROW_V4: u32 = {{ vRowStrideV4 }}u;
|
| 75 |
+
const V_HEAD_V4: u32 = {{ vHeadStrideV4 }}u;
|
| 76 |
+
const V_OFFSET_V4: u32 = {{ vOffsetV4 }}u;
|
| 77 |
+
const O_ROW_V4: u32 = {{ oRowStrideV4 }}u;
|
| 78 |
+
const O_HEAD_V4: u32 = {{ oHeadStrideV4 }}u;
|
| 79 |
+
{% if hasBias %}
|
| 80 |
+
const BIAS_K: u32 = {{ BIAS_K_ROW }}u;
|
| 81 |
+
const BIAS_V: u32 = {{ BIAS_V_ROW }}u;
|
| 82 |
+
{% endif %}
|
| 83 |
+
const TILE_Q: u32 = {{ TILE_Q }}u;
|
| 84 |
+
const TILE_K: u32 = {{ TILE_K }}u;
|
| 85 |
+
// Lanes per token cluster, sized by the manifest for query and value ownership.
|
| 86 |
+
const LPQ: u32 = {{ LPQ }}u;
|
| 87 |
+
const SLICE: u32 = {% if queryTail %}{{ SLICE_COUNT }}u{% else %}HEAD_DIM_V4 / LPQ{% endif %}; // query vec4s per lane slice
|
| 88 |
+
const WG: u32 = TILE_Q * LPQ;
|
| 89 |
+
const NEG_INF: f32 = -3.4028234663852886e38;
|
| 90 |
+
|
| 91 |
+
var<workgroup> k_tile: array<vec4<{{ ST }}>, TILE_K * HEAD_DIM_V4>;
|
| 92 |
+
var<workgroup> v_tile: array<vec4<{{ ST }}>, TILE_K * {{ valueWidth }}>;
|
| 93 |
+
{% if not useSubgroups %}
|
| 94 |
+
// Reuse scratch for dot fragments, maxima, sums and probabilities when the
|
| 95 |
+
// manifest admits the full tile; otherwise retain one key's reduction slots.
|
| 96 |
+
var<workgroup> red: array<f32, {% if parallelSoftmax %}{{ TILE_Q * (LPQ * TILE_K if LPQ * TILE_K > 2 * LPQ + TILE_K else 2 * LPQ + TILE_K) }}{% else %}WG{% endif %}>;
|
| 97 |
+
{% endif %}
|
| 98 |
+
|
| 99 |
+
{% set ATTN_SCALE_DIM = "HEAD_DIM" %}fn scale_value() -> f32 {
|
| 100 |
+
if (params.scale != 0.0) { return params.scale; }
|
| 101 |
+
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
| 102 |
+
}
|
| 103 |
+
// Packed-token metadata. `token_offset` records, for every packed token, its
|
| 104 |
+
// flat index in the padded `[batch, sequence]` grid, so the sequence index and
|
| 105 |
+
// the position inside that sequence are one load and two integer operations.
|
| 106 |
+
// Packing keeps a sequence's tokens contiguous and in ascending position order,
|
| 107 |
+
// so the packed index of a sequence's first token is `token - position` and the
|
| 108 |
+
// sequence's packed key range is [keyStart, keyEnd).
|
| 109 |
+
struct PackedToken {
|
| 110 |
+
sequence: u32,
|
| 111 |
+
position: u32,
|
| 112 |
+
keyStart: u32,
|
| 113 |
+
keyEnd: u32,
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
fn packed_token_of(token: u32, seqPad: u32) -> PackedToken {
|
| 117 |
+
let flat = u32(token_offset[token]);
|
| 118 |
+
var info: PackedToken;
|
| 119 |
+
info.sequence = flat / seqPad;
|
| 120 |
+
info.position = flat - info.sequence * seqPad;
|
| 121 |
+
info.keyStart = token - info.position;
|
| 122 |
+
info.keyEnd = u32(cumulative_sequence_length[info.sequence + 1u]);
|
| 123 |
+
return info;
|
| 124 |
+
}
|
| 125 |
+
{% if hasBias %}
|
| 126 |
+
|
| 127 |
+
{% set biasScalarWiden = usesF16 %}
|
| 128 |
+
{% set BW = "f32(" if biasScalarWiden is defined and biasScalarWiden else "" %}
|
| 129 |
+
{% set BC = ")" if biasScalarWiden is defined and biasScalarWiden else "" %}
|
| 130 |
+
fn load_bias4(base: u32, d4: u32) -> vec4<f32> {
|
| 131 |
+
let offset = base + d4 * 4u;
|
| 132 |
+
return vec4<f32>({{ BW }}bias[offset]{{ BC }}, {{ BW }}bias[offset + 1u]{{ BC }}, {{ BW }}bias[offset + 2u]{{ BC }}, {{ BW }}bias[offset + 3u]{{ BC }});
|
| 133 |
+
}
|
| 134 |
+
{% endif %}
|
| 135 |
+
|
| 136 |
+
{% macro packed_dot_fragment() %}
|
| 137 |
+
var part: f32 = 0.0;
|
| 138 |
+
let kb = kk * HEAD_DIM_V4 + laneSlice;
|
| 139 |
+
{% for c in range(SLICE_COUNT) %}
|
| 140 |
+
{% if queryTail %}
|
| 141 |
+
if (laneSlice + {{ c }}u < HEAD_DIM_V4) {
|
| 142 |
+
{% endif %}
|
| 143 |
+
part = part + dot(qr{{ c }}, vec4<f32>(k_tile[kb + {{ c }}u]));
|
| 144 |
+
{% if queryTail %}
|
| 145 |
+
}
|
| 146 |
+
{% endif %}
|
| 147 |
+
{% endfor %}
|
| 148 |
+
{% endmacro %}
|
| 149 |
+
{% macro packed_value_accumulate() %}
|
| 150 |
+
let vRow = kk * {{ valueWidth }} + {{ valueLane }};
|
| 151 |
+
{% for c in range(VALUE_SLICES) %}
|
| 152 |
+
{% if valueTail %}
|
| 153 |
+
if ({{ valueLane }} + {{ c }}u < {{ valueWidth }}) {
|
| 154 |
+
{% endif %}
|
| 155 |
+
o{{ c }} = o{{ c }} + p * vec4<f32>(v_tile[vRow + {{ c }}u]);
|
| 156 |
+
{% if valueTail %}
|
| 157 |
+
}
|
| 158 |
+
{% endif %}
|
| 159 |
+
{% endfor %}
|
| 160 |
+
{% endmacro %}
|
| 161 |
+
{% macro packed_score_write(score_index = "kk") %}
|
| 162 |
+
if (kj >= minKj && kj < maxKj) {
|
| 163 |
+
{% if hasAttnBias %}
|
| 164 |
+
let maskIndex = info.sequence * params.maskBatchStride + h * params.maskHeadStride
|
| 165 |
+
+ info.position * params.maskSeqStride + (kj - minKj);
|
| 166 |
+
s[{{ score_index }}] = part * SCALE + f32(attn_bias[maskIndex]);
|
| 167 |
+
{% else %}
|
| 168 |
+
s[{{ score_index }}] = part * SCALE;
|
| 169 |
+
{% endif %}
|
| 170 |
+
}
|
| 171 |
+
{% endmacro %}
|
| 172 |
+
|
| 173 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 174 |
+
fn main(
|
| 175 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 176 |
+
@builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
|
| 177 |
+
@builtin(subgroup_size) sgSize: u32{% endif %}
|
| 178 |
+
) {
|
| 179 |
+
let h = wg.y;
|
| 180 |
+
// The query-tile axis is folded over x and z with a baked stride; z is 1
|
| 181 |
+
// whenever the tile count fits one dimension, so the fold term vanishes there.
|
| 182 |
+
let tile = wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 183 |
+
let firstToken = tile * TILE_Q;
|
| 184 |
+
if (firstToken >= params.tokenCount || h >= Q_HEADS) {
|
| 185 |
+
return;
|
| 186 |
+
}
|
| 187 |
+
{% if useSubgroups %}
|
| 188 |
+
// The subgroupShuffleXor butterfly requires every LPQ-lane cluster to fit in,
|
| 189 |
+
// and align with, one subgroup. Keep a uniform guard for defensive portability.
|
| 190 |
+
if (sgSize < LPQ || sgSize % LPQ != 0u) { return; }
|
| 191 |
+
{% endif %}
|
| 192 |
+
|
| 193 |
+
let tid = lid.x;
|
| 194 |
+
let qSub = tid / LPQ;
|
| 195 |
+
let lane = tid % LPQ;
|
| 196 |
+
let qIdx = firstToken + qSub;
|
| 197 |
+
let qValid = qIdx < params.tokenCount;
|
| 198 |
+
let qClamped = min(qIdx, params.tokenCount - 1u);
|
| 199 |
+
let info = packed_token_of(qClamped, params.seqPad);
|
| 200 |
+
let minKj = info.keyStart;
|
| 201 |
+
let maxKj = info.keyEnd;
|
| 202 |
+
|
| 203 |
+
// Union key range for the whole tile: packed tokens are ordered, so the first
|
| 204 |
+
// token's sequence start and the last token's sequence end bound every token
|
| 205 |
+
// in the tile.
|
| 206 |
+
let lastToken = min(firstToken + TILE_Q - 1u, params.tokenCount - 1u);
|
| 207 |
+
let wgStart = packed_token_of(firstToken, params.seqPad).keyStart;
|
| 208 |
+
let wgEnd = packed_token_of(lastToken, params.seqPad).keyEnd;
|
| 209 |
+
|
| 210 |
+
let SCALE = scale_value();
|
| 211 |
+
let laneSlice = lane * SLICE;
|
| 212 |
+
{% if unequalValue %}
|
| 213 |
+
let valueLaneSlice = lane * {{ VALUE_SLICES }}u;
|
| 214 |
+
{% endif %}
|
| 215 |
+
let qBase4 = qClamped * Q_ROW_V4 + h * Q_HEAD_V4 + laneSlice;
|
| 216 |
+
{% for c in range(SLICE_COUNT) %}
|
| 217 |
+
{% if queryTail %}
|
| 218 |
+
var qr{{ c }} = vec4<f32>(0.0);
|
| 219 |
+
if (laneSlice + {{ c }}u < HEAD_DIM_V4) {
|
| 220 |
+
qr{{ c }} = vec4<f32>({{ QBUF }}[qBase4 + {{ c }}u]);
|
| 221 |
+
{% if hasBias %}
|
| 222 |
+
qr{{ c }} = qr{{ c }} + load_bias4(h * HEAD_DIM, laneSlice + {{ c }}u);
|
| 223 |
+
{% endif %}
|
| 224 |
+
}
|
| 225 |
+
{% else %}
|
| 226 |
+
var qr{{ c }} = vec4<f32>({{ QBUF }}[qBase4 + {{ c }}u]);
|
| 227 |
+
{% if hasBias %}
|
| 228 |
+
qr{{ c }} = qr{{ c }} + load_bias4(h * HEAD_DIM, laneSlice + {{ c }}u);
|
| 229 |
+
{% endif %}
|
| 230 |
+
{% endif %}
|
| 231 |
+
{% if not unequalValue %}
|
| 232 |
+
var o{{ c }} = vec4<f32>(0.0);
|
| 233 |
+
{% endif %}
|
| 234 |
+
{% endfor %}
|
| 235 |
+
{% if unequalValue %}
|
| 236 |
+
{% for c in range(VALUE_SLICES) %}
|
| 237 |
+
var o{{ c }} = vec4<f32>(0.0);
|
| 238 |
+
{% endfor %}
|
| 239 |
+
{% endif %}
|
| 240 |
+
|
| 241 |
+
var m: f32 = NEG_INF;
|
| 242 |
+
var l: f32 = 0.0;
|
| 243 |
+
|
| 244 |
+
var kStart: u32 = wgStart;
|
| 245 |
+
loop {
|
| 246 |
+
if (kStart >= wgEnd) { break; }
|
| 247 |
+
|
| 248 |
+
// Cooperative K/V tile load with out-of-range keys zero-filled. The K and V
|
| 249 |
+
// biases are token-independent, so they fold in here, once per staged word.
|
| 250 |
+
workgroupBarrier();
|
| 251 |
+
{% if unequalValue %}
|
| 252 |
+
{% for tile, buffer, rowStride, headStride, offset, width, biasRow, biasWidth in [
|
| 253 |
+
["k_tile", KBUF, "K_ROW_V4", "K_HEAD_V4", "K_OFFSET_V4", "HEAD_DIM_V4", "BIAS_K", "HEAD_DIM"],
|
| 254 |
+
["v_tile", VBUF, "V_ROW_V4", "V_HEAD_V4", "V_OFFSET_V4", "VALUE_HEAD_DIM_V4", "BIAS_V", "VALUE_HEAD_DIM"]
|
| 255 |
+
] %}
|
| 256 |
+
for (var i: u32 = tid; i < TILE_K * {{ width }}; i = i + WG) {
|
| 257 |
+
let slot = i / {{ width }};
|
| 258 |
+
let d4 = i % {{ width }};
|
| 259 |
+
let kj = kStart + slot;
|
| 260 |
+
if (kj < wgEnd) {
|
| 261 |
+
var word = vec4<f32>({{ buffer }}[kj * {{ rowStride }} + h * {{ headStride }} + {{ offset }} + d4]);
|
| 262 |
+
{% if hasBias %}
|
| 263 |
+
word = word + load_bias4({{ biasRow }} + h * {{ biasWidth }}, d4);
|
| 264 |
+
{% endif %}
|
| 265 |
+
{{ tile }}[i] = vec4<{{ ST }}>(word);
|
| 266 |
+
} else {
|
| 267 |
+
{{ tile }}[i] = vec4<{{ ST }}>(0.0);
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
{% endfor %}
|
| 271 |
+
{% else %}
|
| 272 |
+
for (var i: u32 = tid; i < TILE_K * HEAD_DIM_V4; i = i + WG) {
|
| 273 |
+
let slot = i / HEAD_DIM_V4;
|
| 274 |
+
let d4 = i % HEAD_DIM_V4;
|
| 275 |
+
let kj = kStart + slot;
|
| 276 |
+
if (kj < wgEnd) {
|
| 277 |
+
var kWord = vec4<f32>({{ KBUF }}[kj * K_ROW_V4 + h * K_HEAD_V4 + K_OFFSET_V4 + d4]);
|
| 278 |
+
var vWord = vec4<f32>({{ VBUF }}[kj * V_ROW_V4 + h * V_HEAD_V4 + V_OFFSET_V4 + d4]);
|
| 279 |
+
{% if hasBias %}
|
| 280 |
+
kWord = kWord + load_bias4(BIAS_K + h * HEAD_DIM, d4);
|
| 281 |
+
vWord = vWord + load_bias4(BIAS_V + h * HEAD_DIM, d4);
|
| 282 |
+
{% endif %}
|
| 283 |
+
k_tile[i] = vec4<{{ ST }}>(kWord);
|
| 284 |
+
v_tile[i] = vec4<{{ ST }}>(vWord);
|
| 285 |
+
} else {
|
| 286 |
+
k_tile[i] = vec4<{{ ST }}>(0.0);
|
| 287 |
+
v_tile[i] = vec4<{{ ST }}>(0.0);
|
| 288 |
+
}
|
| 289 |
+
}
|
| 290 |
+
{% endif %}
|
| 291 |
+
workgroupBarrier();
|
| 292 |
+
|
| 293 |
+
{% if parallelSoftmax %}
|
| 294 |
+
// Each cluster lane owns disjoint key scores and exponentials. The dot
|
| 295 |
+
// fragments, maxima, sums and probabilities reuse one shared allocation.
|
| 296 |
+
var s: array<f32, {{ ((TILE_K + LPQ - 1) / LPQ) | int }}>;
|
| 297 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 298 |
+
{{ packed_dot_fragment() }}
|
| 299 |
+
red[kk * WG + tid] = part;
|
| 300 |
+
}
|
| 301 |
+
workgroupBarrier();
|
| 302 |
+
var localMax = NEG_INF;
|
| 303 |
+
for (var kk: u32 = lane; kk < TILE_K; kk = kk + LPQ) {
|
| 304 |
+
let kj = kStart + kk;
|
| 305 |
+
let key_slot = kk / LPQ;
|
| 306 |
+
s[key_slot] = NEG_INF;
|
| 307 |
+
var part: f32 = 0.0;
|
| 308 |
+
let cbase = kk * WG + tid - lane;
|
| 309 |
+
for (var j: u32 = 0u; j < LPQ; j = j + 1u) {
|
| 310 |
+
part = part + red[cbase + j];
|
| 311 |
+
}
|
| 312 |
+
{{ packed_score_write("key_slot") }}
|
| 313 |
+
localMax = max(localMax, s[key_slot]);
|
| 314 |
+
}
|
| 315 |
+
// All fragment readers finish before their storage is reused for maxima.
|
| 316 |
+
workgroupBarrier();
|
| 317 |
+
red[tid] = localMax;
|
| 318 |
+
workgroupBarrier();
|
| 319 |
+
let cbase = tid - lane;
|
| 320 |
+
// Keyless lanes contribute only NEG_INF or zero to max/sum. Dot fragments
|
| 321 |
+
// above still consume every lane, including when TILE_K is smaller than LPQ.
|
| 322 |
+
var tileMax = NEG_INF;
|
| 323 |
+
for (var j: u32 = 0u; j < {{ "LPQ" if TILE_K >= LPQ else "TILE_K" }}; j = j + 1u) {
|
| 324 |
+
tileMax = max(tileMax, red[cbase + j]);
|
| 325 |
+
}
|
| 326 |
+
let newMax = max(m, tileMax);
|
| 327 |
+
let corr = select(exp(m - newMax), 0.0, m == NEG_INF);
|
| 328 |
+
var localSum: f32 = 0.0;
|
| 329 |
+
for (var kk: u32 = lane; kk < TILE_K; kk = kk + LPQ) {
|
| 330 |
+
let score = s[kk / LPQ];
|
| 331 |
+
let pk = select(0.0, exp(score - newMax), score != NEG_INF);
|
| 332 |
+
red[2u * WG + qSub * TILE_K + kk] = pk;
|
| 333 |
+
localSum = localSum + pk;
|
| 334 |
+
}
|
| 335 |
+
// Sum slots are disjoint from the maxima still being consumed above.
|
| 336 |
+
red[WG + tid] = localSum;
|
| 337 |
+
workgroupBarrier();
|
| 338 |
+
var pSum: f32 = 0.0;
|
| 339 |
+
for (var j: u32 = 0u; j < {{ "LPQ" if TILE_K >= LPQ else "TILE_K" }}; j = j + 1u) {
|
| 340 |
+
pSum = pSum + red[WG + cbase + j];
|
| 341 |
+
}
|
| 342 |
+
l = l * corr + pSum;
|
| 343 |
+
m = newMax;
|
| 344 |
+
{% else %}
|
| 345 |
+
var s: array<f32, TILE_K>;
|
| 346 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 347 |
+
let kj = kStart + kk;
|
| 348 |
+
s[kk] = NEG_INF;
|
| 349 |
+
{{ packed_dot_fragment() }}
|
| 350 |
+
{% if useSubgroups %}
|
| 351 |
+
// log2(LPQ) butterfly all-reduce inside the cluster: every lane ends with
|
| 352 |
+
// the full dot.
|
| 353 |
+
part = part + subgroupShuffleXor(part, 1u);
|
| 354 |
+
{% for shift in [2, 4, 8, 16, 32] %}
|
| 355 |
+
{% if LPQ > shift %}
|
| 356 |
+
part = part + subgroupShuffleXor(part, {{ shift }}u);
|
| 357 |
+
{% endif %}
|
| 358 |
+
{% endfor %}
|
| 359 |
+
{% else %}
|
| 360 |
+
red[tid] = part;
|
| 361 |
+
workgroupBarrier();
|
| 362 |
+
let cbase = tid - lane;
|
| 363 |
+
part = 0.0;
|
| 364 |
+
for (var j: u32 = 0u; j < LPQ; j = j + 1u) {
|
| 365 |
+
part = part + red[cbase + j];
|
| 366 |
+
}
|
| 367 |
+
workgroupBarrier();
|
| 368 |
+
{% endif %}
|
| 369 |
+
{{ packed_score_write() }}
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
// Per-lane online softmax over the tile. s[kk] is reused to hold the
|
| 373 |
+
// exponentiated probabilities for the value accumulation below.
|
| 374 |
+
var tileMax: f32 = s[0];
|
| 375 |
+
for (var kk: u32 = 1u; kk < TILE_K; kk = kk + 1u) {
|
| 376 |
+
tileMax = max(tileMax, s[kk]);
|
| 377 |
+
}
|
| 378 |
+
let newMax = max(m, tileMax);
|
| 379 |
+
let corr = select(exp(m - newMax), 0.0, m == NEG_INF);
|
| 380 |
+
var pSum: f32 = 0.0;
|
| 381 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 382 |
+
let pk = select(0.0, exp(s[kk] - newMax), s[kk] != NEG_INF);
|
| 383 |
+
s[kk] = pk;
|
| 384 |
+
pSum = pSum + pk;
|
| 385 |
+
}
|
| 386 |
+
l = l * corr + pSum;
|
| 387 |
+
m = newMax;
|
| 388 |
+
|
| 389 |
+
{% endif %}
|
| 390 |
+
|
| 391 |
+
// A boundary tile addresses V rows outside this token's sequence. Because
|
| 392 |
+
// 0 * NaN is NaN, the guarded loop skips the V read entirely for a key with
|
| 393 |
+
// no probability mass; an interior tile runs the unguarded FMA chain. One
|
| 394 |
+
// range test covers the whole lane slice, and one probability load feeds
|
| 395 |
+
// every vec4 of it.
|
| 396 |
+
let tileInterior = kStart >= minKj && kStart + TILE_K <= maxKj;
|
| 397 |
+
{% for c in range(VALUE_SLICES) %}
|
| 398 |
+
o{{ c }} = o{{ c }} * corr;
|
| 399 |
+
{% endfor %}
|
| 400 |
+
if (tileInterior) {
|
| 401 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 402 |
+
let p = {% if parallelSoftmax %}red[2u * WG + qSub * TILE_K + kk]{% else %}s[kk]{% endif %};
|
| 403 |
+
{{ packed_value_accumulate() }}
|
| 404 |
+
}
|
| 405 |
+
} else {
|
| 406 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 407 |
+
let p = {% if parallelSoftmax %}red[2u * WG + qSub * TILE_K + kk]{% else %}s[kk]{% endif %};
|
| 408 |
+
if (p != 0.0) {
|
| 409 |
+
{{ packed_value_accumulate() }}
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
kStart = kStart + TILE_K;
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
if (qValid) {
|
| 418 |
+
// l == 0 means this token had no probability-bearing key. Emit 0 rather
|
| 419 |
+
// than 0/0.
|
| 420 |
+
let inv = select(0.0, 1.0 / l, l > 0.0);
|
| 421 |
+
let outBase4 = qIdx * O_ROW_V4 + h * O_HEAD_V4 + {{ valueLane }};
|
| 422 |
+
{% for c in range(VALUE_SLICES) %}
|
| 423 |
+
{% if valueTail %}
|
| 424 |
+
if ({{ valueLane }} + {{ c }}u < {{ valueWidth }}) {
|
| 425 |
+
{% endif %}
|
| 426 |
+
output[outBase4 + {{ c }}u] = vec4<{{ scalar }}>(o{{ c }} * inv);
|
| 427 |
+
{% if valueTail %}
|
| 428 |
+
}
|
| 429 |
+
{% endif %}
|
| 430 |
+
{% endfor %}
|
| 431 |
+
}
|
| 432 |
+
}
|
build/webgpu/attn-packed-varlen-scalar.wgsl.jinja
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
+
{% set QBUF = "query" %}
|
| 3 |
+
{% set KBUF = "query" if packedQkv else "key" %}
|
| 4 |
+
{% set VBUF = "query" if packedQkv else "value" %}
|
| 5 |
+
{% set BIAS_K_ROW = biasKOffset if biasKOffset is defined else 0 %}
|
| 6 |
+
{% set BIAS_V_ROW = biasVOffset if biasVOffset is defined else 0 %}
|
| 7 |
+
|
| 8 |
+
// Online-softmax attention over a padding-removed packed token stream, with no
|
| 9 |
+
// feature requirements. One workgroup owns one (packed token, query head): the
|
| 10 |
+
// workgroup walks its own sequence's keys serially, cooperates on each q·k dot
|
| 11 |
+
// through a shared-memory tree reduction, and applies the online rescale to a
|
| 12 |
+
// running value accumulator per key. Head dimensions need no alignment and the
|
| 13 |
+
// V head dimension may differ from the Q/K head dimension.
|
| 14 |
+
{% if packedQkv %}
|
| 15 |
+
// Source: one packed `[T, N, 3, H]` buffer; Q, K and V differ only by offset.
|
| 16 |
+
{% else %}
|
| 17 |
+
// Source: separate token-major `[T, hidden]` query, key and value buffers.
|
| 18 |
+
{% endif %}
|
| 19 |
+
{% if hasBias %}
|
| 20 |
+
// Packed `[Q; K; V]` bias rows are added to Q, K and V during the key walk,
|
| 21 |
+
// which is where the reference adds them.
|
| 22 |
+
{% endif %}
|
| 23 |
+
{% if hasAttnBias %}
|
| 24 |
+
// The additive attention bias lives in PADDED coordinates: its trailing two
|
| 25 |
+
// axes are indexed by the query's and the key's position inside their shared
|
| 26 |
+
// sequence, not by their packed indices. A zero broadcast stride collapses the
|
| 27 |
+
// batch or head axis.
|
| 28 |
+
{% endif %}
|
| 29 |
+
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 30 |
+
const V_HEAD_DIM: u32 = {{ vHeadDim }}u;
|
| 31 |
+
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 32 |
+
const Q_ROW: u32 = {{ qRowStride }}u;
|
| 33 |
+
const Q_HEAD: u32 = {{ qHeadStride }}u;
|
| 34 |
+
const K_ROW: u32 = {{ kRowStride }}u;
|
| 35 |
+
const K_HEAD: u32 = {{ kHeadStride }}u;
|
| 36 |
+
const K_OFFSET: u32 = {{ kOffset }}u;
|
| 37 |
+
const V_ROW: u32 = {{ vRowStride }}u;
|
| 38 |
+
const V_HEAD: u32 = {{ vHeadStride }}u;
|
| 39 |
+
const V_OFFSET: u32 = {{ vOffset }}u;
|
| 40 |
+
const O_ROW: u32 = {{ oRowStride }}u;
|
| 41 |
+
const O_HEAD: u32 = {{ oHeadStride }}u;
|
| 42 |
+
{% if hasBias %}
|
| 43 |
+
const BIAS_K: u32 = {{ BIAS_K_ROW }}u;
|
| 44 |
+
const BIAS_V: u32 = {{ BIAS_V_ROW }}u;
|
| 45 |
+
{% endif %}
|
| 46 |
+
const WG: u32 = {{ workgroupSize }}u;
|
| 47 |
+
|
| 48 |
+
var<workgroup> partial: array<f32, WG>;
|
| 49 |
+
var<workgroup> running_max: f32;
|
| 50 |
+
var<workgroup> running_denom: f32;
|
| 51 |
+
var<workgroup> running_out: array<f32, V_HEAD_DIM>;
|
| 52 |
+
var<workgroup> previous_scale: f32;
|
| 53 |
+
{% macro wgsl_tree_reduce_f32(name, mode, buffer="partial", wg="WG", trailingBarrier=true, valueType="f32") %}
|
| 54 |
+
fn {{ name }}(value: {{ valueType }}, tid: u32) -> {{ valueType }} {
|
| 55 |
+
{{ buffer }}[tid] = value;
|
| 56 |
+
workgroupBarrier();
|
| 57 |
+
// Ceil-halving keeps every lane when the workgroup size is not a power of
|
| 58 |
+
// two. For even n this matches the power-of-two tree order; for odd n, lanes
|
| 59 |
+
// [0, n-half) fold the upper tail while the middle lane carries forward.
|
| 60 |
+
var n: u32 = {{ wg }};
|
| 61 |
+
loop {
|
| 62 |
+
let half = (n + 1u) / 2u;
|
| 63 |
+
if (tid < n - half) {
|
| 64 |
+
{{ buffer }}[tid] = {{ buffer }}[tid] + {{ buffer }}[tid + half];
|
| 65 |
+
}
|
| 66 |
+
workgroupBarrier();
|
| 67 |
+
n = half;
|
| 68 |
+
if (n == 1u) {
|
| 69 |
+
break;
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
// The default trailing barrier makes this helper safe for back-to-back calls: every lane reads
|
| 73 |
+
// slot 0 here, so the next call's first store must not run until all lanes have read it.
|
| 74 |
+
// `trailingBarrier=false` is safe only when the buffer is never written again before kernel exit.
|
| 75 |
+
let reduced = {{ buffer }}[0];
|
| 76 |
+
workgroupBarrier();
|
| 77 |
+
return reduced;
|
| 78 |
+
}
|
| 79 |
+
{% endmacro %}
|
| 80 |
+
{{ wgsl_tree_reduce_f32("reduce_sum", "add", "partial", "WG") }}
|
| 81 |
+
{% set ATTN_SCALE_DIM = "HEAD_DIM" %}fn scale_value() -> f32 {
|
| 82 |
+
if (params.scale != 0.0) { return params.scale; }
|
| 83 |
+
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
| 84 |
+
}
|
| 85 |
+
// Packed-token metadata. `token_offset` records, for every packed token, its
|
| 86 |
+
// flat index in the padded `[batch, sequence]` grid, so the sequence index and
|
| 87 |
+
// the position inside that sequence are one load and two integer operations.
|
| 88 |
+
// Packing keeps a sequence's tokens contiguous and in ascending position order,
|
| 89 |
+
// so the packed index of a sequence's first token is `token - position` and the
|
| 90 |
+
// sequence's packed key range is [keyStart, keyEnd).
|
| 91 |
+
struct PackedToken {
|
| 92 |
+
sequence: u32,
|
| 93 |
+
position: u32,
|
| 94 |
+
keyStart: u32,
|
| 95 |
+
keyEnd: u32,
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
fn packed_token_of(token: u32, seqPad: u32) -> PackedToken {
|
| 99 |
+
let flat = u32(token_offset[token]);
|
| 100 |
+
var info: PackedToken;
|
| 101 |
+
info.sequence = flat / seqPad;
|
| 102 |
+
info.position = flat - info.sequence * seqPad;
|
| 103 |
+
info.keyStart = token - info.position;
|
| 104 |
+
info.keyEnd = u32(cumulative_sequence_length[info.sequence + 1u]);
|
| 105 |
+
return info;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 109 |
+
fn main(
|
| 110 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 111 |
+
@builtin(local_invocation_id) lid: vec3<u32>
|
| 112 |
+
) {
|
| 113 |
+
// The token axis is folded over x and z with a baked stride; z is 1 whenever
|
| 114 |
+
// the token count fits one dimension, so the fold term vanishes there.
|
| 115 |
+
let token = wg.x + wg.z * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 116 |
+
let h = wg.y;
|
| 117 |
+
if (token >= params.tokenCount || h >= Q_HEADS) {
|
| 118 |
+
return;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
let tid = lid.x;
|
| 122 |
+
let info = packed_token_of(token, params.seqPad);
|
| 123 |
+
let qBase = token * Q_ROW + h * Q_HEAD;
|
| 124 |
+
let oBase = token * O_ROW + h * O_HEAD;
|
| 125 |
+
|
| 126 |
+
if (tid == 0u) {
|
| 127 |
+
running_max = -3.4028234663852886e38;
|
| 128 |
+
running_denom = 0.0;
|
| 129 |
+
}
|
| 130 |
+
for (var d: u32 = tid; d < V_HEAD_DIM; d = d + WG) {
|
| 131 |
+
running_out[d] = 0.0;
|
| 132 |
+
}
|
| 133 |
+
workgroupBarrier();
|
| 134 |
+
|
| 135 |
+
for (var keyToken: u32 = info.keyStart; keyToken < info.keyEnd; keyToken = keyToken + 1u) {
|
| 136 |
+
let kBase = keyToken * K_ROW + h * K_HEAD + K_OFFSET;
|
| 137 |
+
var partial_dot = 0.0;
|
| 138 |
+
for (var d: u32 = tid; d < HEAD_DIM; d = d + WG) {
|
| 139 |
+
var q_value = f32({{ QBUF }}[qBase + d]);
|
| 140 |
+
var k_value = f32({{ KBUF }}[kBase + d]);
|
| 141 |
+
{% if hasBias %}
|
| 142 |
+
let channel = h * HEAD_DIM + d;
|
| 143 |
+
q_value = q_value + f32(bias[channel]);
|
| 144 |
+
k_value = k_value + f32(bias[BIAS_K + channel]);
|
| 145 |
+
{% endif %}
|
| 146 |
+
partial_dot = partial_dot + q_value * k_value;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// reduce_sum returns the same partial[0] to every lane, so `score` is
|
| 150 |
+
// already workgroup-uniform; tid==0 advances the running max/denominator
|
| 151 |
+
// (broadcast through shared memory by the barrier below).
|
| 152 |
+
{% if hasAttnBias %}
|
| 153 |
+
let maskIndex = info.sequence * params.maskBatchStride + h * params.maskHeadStride
|
| 154 |
+
+ info.position * params.maskSeqStride + (keyToken - info.keyStart);
|
| 155 |
+
let score = reduce_sum(partial_dot, tid) * scale_value() + f32(attn_bias[maskIndex]);
|
| 156 |
+
{% else %}
|
| 157 |
+
let score = reduce_sum(partial_dot, tid) * scale_value();
|
| 158 |
+
{% endif %}
|
| 159 |
+
if (tid == 0u) {
|
| 160 |
+
let next_max = max(running_max, score);
|
| 161 |
+
let old_factor = exp(running_max - next_max);
|
| 162 |
+
let new_factor = exp(score - next_max);
|
| 163 |
+
previous_scale = old_factor;
|
| 164 |
+
running_max = next_max;
|
| 165 |
+
running_denom = running_denom * old_factor + new_factor;
|
| 166 |
+
}
|
| 167 |
+
workgroupBarrier();
|
| 168 |
+
|
| 169 |
+
let probability_numerator = exp(score - running_max);
|
| 170 |
+
let vBase = keyToken * V_ROW + h * V_HEAD + V_OFFSET;
|
| 171 |
+
for (var d: u32 = tid; d < V_HEAD_DIM; d = d + WG) {
|
| 172 |
+
var v_value = f32({{ VBUF }}[vBase + d]);
|
| 173 |
+
{% if hasBias %}
|
| 174 |
+
v_value = v_value + f32(bias[BIAS_V + h * V_HEAD_DIM + d]);
|
| 175 |
+
{% endif %}
|
| 176 |
+
running_out[d] = running_out[d] * previous_scale + probability_numerator * v_value;
|
| 177 |
+
}
|
| 178 |
+
workgroupBarrier();
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
// A token whose sequence carries no key leaves running_denom at zero; emit a
|
| 182 |
+
// zero row rather than 0 * (1/0) = NaN.
|
| 183 |
+
let inv_denom = select(0.0, 1.0 / running_denom, running_denom > 0.0);
|
| 184 |
+
for (var d: u32 = tid; d < V_HEAD_DIM; d = d + WG) {
|
| 185 |
+
output[oBase + d] = {{ outputScalar }}(running_out[d] * inv_denom);
|
| 186 |
+
}
|
| 187 |
+
}
|
build/webgpu/bench.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,667 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "com.microsoft",
|
| 3 |
+
"name": "PackedMultiHeadAttention",
|
| 4 |
+
"sinceVersion": 1,
|
| 5 |
+
"inputs": {
|
| 6 |
+
"queryT": { "onnx": "query", "dtype": "T" },
|
| 7 |
+
"keyT": { "onnx": "key", "dtype": "T", "rank": 2, "optional": true },
|
| 8 |
+
"valueT": { "onnx": "value", "dtype": "T", "rank": 2, "optional": true },
|
| 9 |
+
"biasT": { "onnx": "bias", "dtype": "T", "rank": 1, "optional": true },
|
| 10 |
+
"tokenOffsetT": { "onnx": "token_offset", "dtype": "M", "rank": 2, "storage": "int32" },
|
| 11 |
+
"cumulativeSequenceLengthT": { "onnx": "cumulative_sequence_length", "dtype": "M", "rank": 1, "storage": "int32" },
|
| 12 |
+
"attentionBiasT": { "onnx": "attention_bias", "dtype": "T", "rank": 4, "optional": true }
|
| 13 |
+
},
|
| 14 |
+
"outputs": { "outputT": { "onnx": "output", "dtype": "T", "rank": 2, "shape": "[tokenCount, vHiddenSize]" } },
|
| 15 |
+
"attributes": { "num_heads": {}, "scale": {} },
|
| 16 |
+
"attributeConstraints": { "num_heads": { "required": true } },
|
| 17 |
+
"typeConstraints": { "T": ["float32", "float16"], "M": ["int32"] },
|
| 18 |
+
"tunables": { "CLUSTER_TILE_K": { "default": 16 }, "SCALAR_WORKGROUP_SIZE": { "default": 64 } },
|
| 19 |
+
"derive": {
|
| 20 |
+
"numHeads": "attrs.num_heads",
|
| 21 |
+
"qNumHeads": "numHeads",
|
| 22 |
+
"packedQkv": "ranks.queryT == 4",
|
| 23 |
+
"tokenCount": "dim(shapes.queryT, 0)",
|
| 24 |
+
"batchSize": "dim(shapes.tokenOffsetT, 0) if ranks.tokenOffsetT == 2 else 0",
|
| 25 |
+
"seqPad": "dim(shapes.tokenOffsetT, 1) if ranks.tokenOffsetT == 2 else 0",
|
| 26 |
+
"packedHeadDim": "dim(shapes.queryT, 3) if packedQkv else 0",
|
| 27 |
+
"hiddenSize": "numHeads * packedHeadDim if packedQkv else (dim(shapes.queryT, 1) if ranks.queryT == 2 else 0)",
|
| 28 |
+
"vHiddenSize": "hiddenSize if packedQkv else (dim(shapes.valueT, 1) if present.valueT and ranks.valueT == 2 else 0)",
|
| 29 |
+
"headDim": "hiddenSize / numHeads if numHeads > 0 and hiddenSize % numHeads == 0 else 0",
|
| 30 |
+
"vHeadDim": "vHiddenSize / numHeads if numHeads > 0 and vHiddenSize % numHeads == 0 else 0",
|
| 31 |
+
"attentionScale": "attrs.scale if has(attrs, \"scale\") else 0",
|
| 32 |
+
"qRowStride": "numHeads * 3 * headDim if packedQkv else hiddenSize",
|
| 33 |
+
"qHeadStride": "3 * headDim if packedQkv else headDim",
|
| 34 |
+
"kRowStride": "qRowStride if packedQkv else hiddenSize",
|
| 35 |
+
"kHeadStride": "qHeadStride if packedQkv else headDim",
|
| 36 |
+
"kOffset": "headDim if packedQkv else 0",
|
| 37 |
+
"vRowStride": "qRowStride if packedQkv else vHiddenSize",
|
| 38 |
+
"vHeadStride": "qHeadStride if packedQkv else vHeadDim",
|
| 39 |
+
"vOffset": "2 * headDim if packedQkv else 0",
|
| 40 |
+
"oRowStride": "vHiddenSize",
|
| 41 |
+
"oHeadStride": "vHeadDim",
|
| 42 |
+
"biasKOffset": "hiddenSize",
|
| 43 |
+
"biasVOffset": "2 * hiddenSize",
|
| 44 |
+
"attnBiasHeads": "dim(shapes.attentionBiasT, 1) if present.attentionBiasT and ranks.attentionBiasT == 4 else 0",
|
| 45 |
+
"maskSeqStride": "seqPad",
|
| 46 |
+
"maskHeadStride": "0 if attnBiasHeads == 1 else seqPad * seqPad",
|
| 47 |
+
"maskBatchStride": "0 if (not present.attentionBiasT) or ranks.attentionBiasT != 4 or dim(shapes.attentionBiasT, 0) == 1 else attnBiasHeads * seqPad * seqPad",
|
| 48 |
+
"dtypeOk": "tensorDtypes.outputT == tensorDtypes.queryT and (tensorDtypes.queryT == \"float32\" or tensorDtypes.queryT == \"float16\") and f16Ok(dtypes.T)",
|
| 49 |
+
"packedShapeOk": "packedQkv and not present.keyT and not present.valueT and dim(shapes.queryT, 1) == numHeads and dim(shapes.queryT, 2) == 3 and packedHeadDim > 0",
|
| 50 |
+
"separateShapeOk": "ranks.queryT == 2 and present.keyT and present.valueT and ranks.keyT == 2 and ranks.valueT == 2 and sameShape(shapes.keyT, shapes.queryT) and dim(shapes.valueT, 0) == tokenCount and tensorDtypes.keyT == tensorDtypes.queryT and tensorDtypes.valueT == tensorDtypes.queryT",
|
| 51 |
+
"scheduleOk": "ranks.tokenOffsetT == 2 and ranks.cumulativeSequenceLengthT == 1 and dim(shapes.cumulativeSequenceLengthT, 0) == batchSize + 1 and batchSize > 0 and seqPad > 0 and numel(shapes.tokenOffsetT) >= tokenCount",
|
| 52 |
+
"outputShapeOk": "ranks.outputT == 2 and dim(shapes.outputT, 0) == tokenCount and dim(shapes.outputT, 1) == vHiddenSize",
|
| 53 |
+
"biasOk": "present.biasT and ranks.biasT == 1 and dim(shapes.biasT, 0) == 2 * hiddenSize + vHiddenSize and tensorDtypes.biasT == tensorDtypes.queryT",
|
| 54 |
+
"attnBiasOk": "present.attentionBiasT and ranks.attentionBiasT == 4 and (dim(shapes.attentionBiasT, 0) == 1 or dim(shapes.attentionBiasT, 0) == batchSize) and (attnBiasHeads == 1 or attnBiasHeads == numHeads) and dim(shapes.attentionBiasT, 2) == seqPad and dim(shapes.attentionBiasT, 3) == seqPad and tensorDtypes.attentionBiasT == tensorDtypes.queryT",
|
| 55 |
+
"hasBias": "present.biasT",
|
| 56 |
+
"hasAttnBias": "present.attentionBiasT",
|
| 57 |
+
"baseContract": "dtypeOk and scheduleOk and outputShapeOk and numHeads > 0 and headDim > 0 and vHeadDim > 0 and tokenCount > 0 and (packedShapeOk or separateShapeOk)",
|
| 58 |
+
"usesF16": "tensorDtypes.queryT == \"float16\"",
|
| 59 |
+
"scalar": "\"f16\" if usesF16 else \"f32\"",
|
| 60 |
+
"outputScalar": "scalar",
|
| 61 |
+
"inputVec4": "\"vec4<f16>\" if usesF16 else \"vec4<f32>\"",
|
| 62 |
+
"qkvBytes": "2 if usesF16 else 4",
|
| 63 |
+
"packedStageBytes": "4 if usesF16 and present.biasT and vHeadDim != headDim else qkvBytes",
|
| 64 |
+
"clusterWidthAligned": "headDim % 16 == 0 and vHeadDim % 4 == 0",
|
| 65 |
+
"headDimV4": "headDim / 4",
|
| 66 |
+
"qRowStrideV4": "qRowStride / 4",
|
| 67 |
+
"qHeadStrideV4": "qHeadStride / 4",
|
| 68 |
+
"kRowStrideV4": "kRowStride / 4",
|
| 69 |
+
"kHeadStrideV4": "kHeadStride / 4",
|
| 70 |
+
"kOffsetV4": "kOffset / 4",
|
| 71 |
+
"vRowStrideV4": "vRowStride / 4",
|
| 72 |
+
"vHeadStrideV4": "vHeadStride / 4",
|
| 73 |
+
"vOffsetV4": "vOffset / 4",
|
| 74 |
+
"oRowStrideV4": "oRowStride / 4",
|
| 75 |
+
"oHeadStrideV4": "oHeadStride / 4",
|
| 76 |
+
"scalarWg": "tunables.SCALAR_WORKGROUP_SIZE",
|
| 77 |
+
"scalarBytes": "4 * scalarWg + 4 * vHeadDim + 12",
|
| 78 |
+
"scalarOk": "scalarWg <= device.limits.maxComputeInvocationsPerWorkgroup and scalarWg <= device.limits.maxComputeWorkgroupSizeX and scalarBytes <= device.limits.maxComputeWorkgroupStorageSize and numHeads <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
|
| 79 |
+
"clusterTileQ": "min(16, pow2ceil(tokenCount)) if vHeadDim != headDim else 16",
|
| 80 |
+
"clusterBaseLpq": "8 if headDim % 32 == 0 else 4",
|
| 81 |
+
"clusterTileK": "min(tunables.CLUSTER_TILE_K, pow2ceil(tokenCount)) if vHeadDim != headDim else tunables.CLUSTER_TILE_K",
|
| 82 |
+
"clusterTileBytes": "clusterTileK * (headDim + vHeadDim) * packedStageBytes",
|
| 83 |
+
"clusterLaneTarget": "min(32, max(clusterBaseLpq, pow2ceil(headDim / 16)), device.adapterInfo.subgroupMinSize, pow2ceil(floor(min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX) / clusterTileQ) + 1) / 2) if vHeadDim == headDim and clusterWidthAligned and clusterTileBytes <= device.limits.maxComputeWorkgroupStorageSize and device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and device.adapterInfo.subgroupMinSize >= clusterBaseLpq else clusterBaseLpq",
|
| 84 |
+
"clusterLpq": "clusterLaneTarget if ceilDiv(headDim, 4 * clusterBaseLpq) >= 2 * ceilDiv(headDim, 4 * clusterLaneTarget) else clusterBaseLpq",
|
| 85 |
+
"clusterWg": "clusterTileQ * clusterLpq",
|
| 86 |
+
"clusterNosgBytes": "clusterTileBytes + 4 * clusterWg",
|
| 87 |
+
"clusterTiles": "ceilDiv(tokenCount, clusterTileQ)",
|
| 88 |
+
"clusterShapeOk": "clusterWidthAligned and clusterWg <= device.limits.maxComputeInvocationsPerWorkgroup and clusterWg <= device.limits.maxComputeWorkgroupSizeX and numHeads <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
|
| 89 |
+
"clusterOk": "clusterShapeOk and clusterTileBytes <= device.limits.maxComputeWorkgroupStorageSize",
|
| 90 |
+
"clusterNosgOk": "clusterShapeOk and clusterNosgBytes <= device.limits.maxComputeWorkgroupStorageSize",
|
| 91 |
+
"clusterSubgroupsOk": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize >= clusterLpq and device.adapterInfo.subgroupMinSize % clusterLpq == 0 and device.adapterInfo.subgroupMaxSize >= device.adapterInfo.subgroupMinSize and device.adapterInfo.subgroupMaxSize % clusterLpq == 0",
|
| 92 |
+
"portableLaneTarget": "min(64, pow2ceil(max(headDimV4, vHeadDim / 4) / 4), device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
|
| 93 |
+
"portableLpq": "max(clusterLpq, pow2ceil(portableLaneTarget + 1) / 2) if vHeadDim != headDim else (64 if portableLaneTarget >= 64 and headDimV4 % 64 == 0 else (32 if portableLaneTarget >= 32 and headDimV4 % 32 == 0 else (16 if portableLaneTarget >= 16 and headDimV4 % 16 == 0 else clusterLpq)))",
|
| 94 |
+
"portableTileQ": "min(clusterTileQ, pow2ceil(tokenCount), max(1, floor(min(clusterWg, device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX) / portableLpq)))",
|
| 95 |
+
"portableWg": "portableTileQ * portableLpq",
|
| 96 |
+
"portableKeyCapacity": "max(1, floor(device.limits.maxComputeWorkgroupStorageSize / ((headDim + vHeadDim) * packedStageBytes + 4 * portableWg)))",
|
| 97 |
+
"portableTileK": "min(clusterTileK, pow2ceil(tokenCount), pow2ceil(portableKeyCapacity + 1) / 2)",
|
| 98 |
+
"clusterDistributedBytes": "portableTileK * (headDim + vHeadDim) * packedStageBytes + 4 * max(portableWg * portableTileK, 2 * portableWg + portableTileQ * portableTileK)",
|
| 99 |
+
"clusterDistributedOk": "clusterDistributedBytes <= device.limits.maxComputeWorkgroupStorageSize",
|
| 100 |
+
"clusterUseDistributed": "clusterDistributedOk and (clusterLpq > 4 or vHeadDim != headDim)",
|
| 101 |
+
"portableTiles": "ceilDiv(tokenCount, portableTileQ if clusterUseDistributed else clusterTileQ)",
|
| 102 |
+
"clusterPortableOk": "clusterShapeOk and (clusterUseDistributed or clusterNosgOk) and portableWg <= device.limits.maxComputeInvocationsPerWorkgroup and portableWg <= device.limits.maxComputeWorkgroupSizeX and ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"
|
| 103 |
+
},
|
| 104 |
+
"bindings": {
|
| 105 |
+
"query": { "arg": "queryT", "name": "query" },
|
| 106 |
+
"key": { "arg": "keyT", "name": "key" },
|
| 107 |
+
"value": { "arg": "valueT", "name": "value" },
|
| 108 |
+
"bias": { "arg": "biasT", "name": "bias" },
|
| 109 |
+
"tokenOffset": { "arg": "tokenOffsetT", "name": "token_offset" },
|
| 110 |
+
"cumSeqLen": { "arg": "cumulativeSequenceLengthT", "name": "cumulative_sequence_length" },
|
| 111 |
+
"attnBias": { "arg": "attentionBiasT", "name": "attn_bias" },
|
| 112 |
+
"output": { "arg": "outputT", "name": "output" },
|
| 113 |
+
"query4": { "arg": "queryT", "name": "query", "elementType": "$inputVec4" },
|
| 114 |
+
"key4": { "arg": "keyT", "name": "key", "elementType": "$inputVec4" },
|
| 115 |
+
"value4": { "arg": "valueT", "name": "value", "elementType": "$inputVec4" },
|
| 116 |
+
"output4": { "arg": "outputT", "name": "output", "elementType": "$inputVec4" },
|
| 117 |
+
"params": {
|
| 118 |
+
"name": "params",
|
| 119 |
+
"struct": [
|
| 120 |
+
{ "name": "tokenCount", "type": "u32", "value": "tokenCount" },
|
| 121 |
+
{ "name": "seqPad", "type": "u32", "value": "seqPad" },
|
| 122 |
+
{ "name": "scale", "type": "f32", "value": "attentionScale" }
|
| 123 |
+
]
|
| 124 |
+
},
|
| 125 |
+
"paramsMask": {
|
| 126 |
+
"name": "params",
|
| 127 |
+
"struct": [
|
| 128 |
+
{ "name": "tokenCount", "type": "u32", "value": "tokenCount" },
|
| 129 |
+
{ "name": "seqPad", "type": "u32", "value": "seqPad" },
|
| 130 |
+
{ "name": "scale", "type": "f32", "value": "attentionScale" },
|
| 131 |
+
{ "name": "maskBatchStride", "type": "u32", "value": "maskBatchStride" },
|
| 132 |
+
{ "name": "maskHeadStride", "type": "u32", "value": "maskHeadStride" },
|
| 133 |
+
{ "name": "maskSeqStride", "type": "u32", "value": "maskSeqStride" }
|
| 134 |
+
]
|
| 135 |
+
}
|
| 136 |
+
},
|
| 137 |
+
"variants": [
|
| 138 |
+
{
|
| 139 |
+
"id": "packed_flash",
|
| 140 |
+
"priority": 40,
|
| 141 |
+
"when": ["baseContract", "packedShapeOk", "not present.biasT", "not present.attentionBiasT", "clusterOk", "clusterSubgroupsOk"],
|
| 142 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 143 |
+
"passes": [
|
| 144 |
+
{
|
| 145 |
+
"id": "main",
|
| 146 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 147 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 148 |
+
"bindings": ["query4", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 149 |
+
"dispatch": {
|
| 150 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 151 |
+
"y": "numHeads",
|
| 152 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
],
|
| 156 |
+
"requires": { "features": ["subgroups"] },
|
| 157 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"id": "packed_flash_nosg",
|
| 161 |
+
"priority": 20,
|
| 162 |
+
"when": ["baseContract", "packedShapeOk", "not present.biasT", "not present.attentionBiasT", "clusterPortableOk"],
|
| 163 |
+
"derive": {
|
| 164 |
+
"useSubgroups": false,
|
| 165 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 166 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 167 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 168 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 169 |
+
},
|
| 170 |
+
"passes": [
|
| 171 |
+
{
|
| 172 |
+
"id": "main",
|
| 173 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 174 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 175 |
+
"bindings": ["query4", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 176 |
+
"dispatch": {
|
| 177 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 178 |
+
"y": "numHeads",
|
| 179 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
],
|
| 183 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 184 |
+
},
|
| 185 |
+
{
|
| 186 |
+
"id": "packed_serial",
|
| 187 |
+
"priority": 0,
|
| 188 |
+
"when": ["baseContract", "packedShapeOk", "not present.biasT", "not present.attentionBiasT", "scalarOk"],
|
| 189 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 190 |
+
"passes": [
|
| 191 |
+
{
|
| 192 |
+
"id": "main",
|
| 193 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 194 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 195 |
+
"bindings": ["query", "tokenOffset", "cumSeqLen", "output", "params"],
|
| 196 |
+
"dispatch": {
|
| 197 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 198 |
+
"y": "numHeads",
|
| 199 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
]
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"id": "packed_attn_bias_flash",
|
| 206 |
+
"priority": 40,
|
| 207 |
+
"when": ["baseContract", "packedShapeOk", "not present.biasT", "attnBiasOk", "clusterOk", "clusterSubgroupsOk"],
|
| 208 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 209 |
+
"passes": [
|
| 210 |
+
{
|
| 211 |
+
"id": "main",
|
| 212 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 213 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 214 |
+
"bindings": ["query4", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 215 |
+
"dispatch": {
|
| 216 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 217 |
+
"y": "numHeads",
|
| 218 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
],
|
| 222 |
+
"requires": { "features": ["subgroups"] },
|
| 223 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"id": "packed_attn_bias_flash_nosg",
|
| 227 |
+
"priority": 20,
|
| 228 |
+
"when": ["baseContract", "packedShapeOk", "not present.biasT", "attnBiasOk", "clusterPortableOk"],
|
| 229 |
+
"derive": {
|
| 230 |
+
"useSubgroups": false,
|
| 231 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 232 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 233 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 234 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 235 |
+
},
|
| 236 |
+
"passes": [
|
| 237 |
+
{
|
| 238 |
+
"id": "main",
|
| 239 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 240 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 241 |
+
"bindings": ["query4", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 242 |
+
"dispatch": {
|
| 243 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 244 |
+
"y": "numHeads",
|
| 245 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
],
|
| 249 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
"id": "packed_attn_bias_serial",
|
| 253 |
+
"priority": 0,
|
| 254 |
+
"when": ["baseContract", "packedShapeOk", "not present.biasT", "attnBiasOk", "scalarOk"],
|
| 255 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 256 |
+
"passes": [
|
| 257 |
+
{
|
| 258 |
+
"id": "main",
|
| 259 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 260 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 261 |
+
"bindings": ["query", "tokenOffset", "cumSeqLen", "attnBias", "output", "paramsMask"],
|
| 262 |
+
"dispatch": {
|
| 263 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 264 |
+
"y": "numHeads",
|
| 265 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
]
|
| 269 |
+
},
|
| 270 |
+
{
|
| 271 |
+
"id": "packed_bias_flash",
|
| 272 |
+
"priority": 40,
|
| 273 |
+
"when": ["baseContract", "packedShapeOk", "biasOk", "not present.attentionBiasT", "clusterOk", "clusterSubgroupsOk"],
|
| 274 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 275 |
+
"passes": [
|
| 276 |
+
{
|
| 277 |
+
"id": "main",
|
| 278 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 279 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 280 |
+
"bindings": ["query4", "bias", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 281 |
+
"dispatch": {
|
| 282 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 283 |
+
"y": "numHeads",
|
| 284 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
],
|
| 288 |
+
"requires": { "features": ["subgroups"] },
|
| 289 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"id": "packed_bias_flash_nosg",
|
| 293 |
+
"priority": 20,
|
| 294 |
+
"when": ["baseContract", "packedShapeOk", "biasOk", "not present.attentionBiasT", "clusterPortableOk"],
|
| 295 |
+
"derive": {
|
| 296 |
+
"useSubgroups": false,
|
| 297 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 298 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 299 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 300 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 301 |
+
},
|
| 302 |
+
"passes": [
|
| 303 |
+
{
|
| 304 |
+
"id": "main",
|
| 305 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 306 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 307 |
+
"bindings": ["query4", "bias", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 308 |
+
"dispatch": {
|
| 309 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 310 |
+
"y": "numHeads",
|
| 311 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 312 |
+
}
|
| 313 |
+
}
|
| 314 |
+
],
|
| 315 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"id": "packed_bias_serial",
|
| 319 |
+
"priority": 0,
|
| 320 |
+
"when": ["baseContract", "packedShapeOk", "biasOk", "not present.attentionBiasT", "scalarOk"],
|
| 321 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 322 |
+
"passes": [
|
| 323 |
+
{
|
| 324 |
+
"id": "main",
|
| 325 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 326 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 327 |
+
"bindings": ["query", "bias", "tokenOffset", "cumSeqLen", "output", "params"],
|
| 328 |
+
"dispatch": {
|
| 329 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 330 |
+
"y": "numHeads",
|
| 331 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 332 |
+
}
|
| 333 |
+
}
|
| 334 |
+
]
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
"id": "packed_bias_attn_bias_flash",
|
| 338 |
+
"priority": 40,
|
| 339 |
+
"when": ["baseContract", "packedShapeOk", "biasOk", "attnBiasOk", "clusterOk", "clusterSubgroupsOk"],
|
| 340 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 341 |
+
"passes": [
|
| 342 |
+
{
|
| 343 |
+
"id": "main",
|
| 344 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 345 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 346 |
+
"bindings": ["query4", "bias", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 347 |
+
"dispatch": {
|
| 348 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 349 |
+
"y": "numHeads",
|
| 350 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 351 |
+
}
|
| 352 |
+
}
|
| 353 |
+
],
|
| 354 |
+
"requires": { "features": ["subgroups"] },
|
| 355 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"id": "packed_bias_attn_bias_flash_nosg",
|
| 359 |
+
"priority": 20,
|
| 360 |
+
"when": ["baseContract", "packedShapeOk", "biasOk", "attnBiasOk", "clusterPortableOk"],
|
| 361 |
+
"derive": {
|
| 362 |
+
"useSubgroups": false,
|
| 363 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 364 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 365 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 366 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 367 |
+
},
|
| 368 |
+
"passes": [
|
| 369 |
+
{
|
| 370 |
+
"id": "main",
|
| 371 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 372 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 373 |
+
"bindings": ["query4", "bias", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 374 |
+
"dispatch": {
|
| 375 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 376 |
+
"y": "numHeads",
|
| 377 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 378 |
+
}
|
| 379 |
+
}
|
| 380 |
+
],
|
| 381 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 382 |
+
},
|
| 383 |
+
{
|
| 384 |
+
"id": "packed_bias_attn_bias_serial",
|
| 385 |
+
"priority": 0,
|
| 386 |
+
"when": ["baseContract", "packedShapeOk", "biasOk", "attnBiasOk", "scalarOk"],
|
| 387 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 388 |
+
"passes": [
|
| 389 |
+
{
|
| 390 |
+
"id": "main",
|
| 391 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 392 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 393 |
+
"bindings": ["query", "bias", "tokenOffset", "cumSeqLen", "attnBias", "output", "paramsMask"],
|
| 394 |
+
"dispatch": {
|
| 395 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 396 |
+
"y": "numHeads",
|
| 397 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 398 |
+
}
|
| 399 |
+
}
|
| 400 |
+
]
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"id": "separate_flash",
|
| 404 |
+
"priority": 40,
|
| 405 |
+
"when": ["baseContract", "separateShapeOk", "not present.biasT", "not present.attentionBiasT", "clusterOk", "clusterSubgroupsOk"],
|
| 406 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 407 |
+
"passes": [
|
| 408 |
+
{
|
| 409 |
+
"id": "main",
|
| 410 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 411 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 412 |
+
"bindings": ["query4", "key4", "value4", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 413 |
+
"dispatch": {
|
| 414 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 415 |
+
"y": "numHeads",
|
| 416 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 417 |
+
}
|
| 418 |
+
}
|
| 419 |
+
],
|
| 420 |
+
"requires": { "features": ["subgroups"] },
|
| 421 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 422 |
+
},
|
| 423 |
+
{
|
| 424 |
+
"id": "separate_flash_nosg",
|
| 425 |
+
"priority": 20,
|
| 426 |
+
"when": ["baseContract", "separateShapeOk", "not present.biasT", "not present.attentionBiasT", "clusterPortableOk"],
|
| 427 |
+
"derive": {
|
| 428 |
+
"useSubgroups": false,
|
| 429 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 430 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 431 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 432 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 433 |
+
},
|
| 434 |
+
"passes": [
|
| 435 |
+
{
|
| 436 |
+
"id": "main",
|
| 437 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 438 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 439 |
+
"bindings": ["query4", "key4", "value4", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 440 |
+
"dispatch": {
|
| 441 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 442 |
+
"y": "numHeads",
|
| 443 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 444 |
+
}
|
| 445 |
+
}
|
| 446 |
+
],
|
| 447 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 448 |
+
},
|
| 449 |
+
{
|
| 450 |
+
"id": "separate_serial",
|
| 451 |
+
"priority": 0,
|
| 452 |
+
"when": ["baseContract", "separateShapeOk", "not present.biasT", "not present.attentionBiasT", "scalarOk"],
|
| 453 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 454 |
+
"passes": [
|
| 455 |
+
{
|
| 456 |
+
"id": "main",
|
| 457 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 458 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 459 |
+
"bindings": ["query", "key", "value", "tokenOffset", "cumSeqLen", "output", "params"],
|
| 460 |
+
"dispatch": {
|
| 461 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 462 |
+
"y": "numHeads",
|
| 463 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 464 |
+
}
|
| 465 |
+
}
|
| 466 |
+
]
|
| 467 |
+
},
|
| 468 |
+
{
|
| 469 |
+
"id": "separate_attn_bias_flash",
|
| 470 |
+
"priority": 40,
|
| 471 |
+
"when": ["baseContract", "separateShapeOk", "not present.biasT", "attnBiasOk", "clusterOk", "clusterSubgroupsOk"],
|
| 472 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 473 |
+
"passes": [
|
| 474 |
+
{
|
| 475 |
+
"id": "main",
|
| 476 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 477 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 478 |
+
"bindings": ["query4", "key4", "value4", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 479 |
+
"dispatch": {
|
| 480 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 481 |
+
"y": "numHeads",
|
| 482 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 483 |
+
}
|
| 484 |
+
}
|
| 485 |
+
],
|
| 486 |
+
"requires": { "features": ["subgroups"] },
|
| 487 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 488 |
+
},
|
| 489 |
+
{
|
| 490 |
+
"id": "separate_attn_bias_flash_nosg",
|
| 491 |
+
"priority": 20,
|
| 492 |
+
"when": ["baseContract", "separateShapeOk", "not present.biasT", "attnBiasOk", "clusterPortableOk"],
|
| 493 |
+
"derive": {
|
| 494 |
+
"useSubgroups": false,
|
| 495 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 496 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 497 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 498 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 499 |
+
},
|
| 500 |
+
"passes": [
|
| 501 |
+
{
|
| 502 |
+
"id": "main",
|
| 503 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 504 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 505 |
+
"bindings": ["query4", "key4", "value4", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 506 |
+
"dispatch": {
|
| 507 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 508 |
+
"y": "numHeads",
|
| 509 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 510 |
+
}
|
| 511 |
+
}
|
| 512 |
+
],
|
| 513 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 514 |
+
},
|
| 515 |
+
{
|
| 516 |
+
"id": "separate_attn_bias_serial",
|
| 517 |
+
"priority": 0,
|
| 518 |
+
"when": ["baseContract", "separateShapeOk", "not present.biasT", "attnBiasOk", "scalarOk"],
|
| 519 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 520 |
+
"passes": [
|
| 521 |
+
{
|
| 522 |
+
"id": "main",
|
| 523 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 524 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 525 |
+
"bindings": ["query", "key", "value", "tokenOffset", "cumSeqLen", "attnBias", "output", "paramsMask"],
|
| 526 |
+
"dispatch": {
|
| 527 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 528 |
+
"y": "numHeads",
|
| 529 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 530 |
+
}
|
| 531 |
+
}
|
| 532 |
+
]
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"id": "separate_bias_flash",
|
| 536 |
+
"priority": 40,
|
| 537 |
+
"when": ["baseContract", "separateShapeOk", "biasOk", "not present.attentionBiasT", "clusterOk", "clusterSubgroupsOk"],
|
| 538 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 539 |
+
"passes": [
|
| 540 |
+
{
|
| 541 |
+
"id": "main",
|
| 542 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 543 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 544 |
+
"bindings": ["query4", "key4", "value4", "bias", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 545 |
+
"dispatch": {
|
| 546 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 547 |
+
"y": "numHeads",
|
| 548 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
],
|
| 552 |
+
"requires": { "features": ["subgroups"] },
|
| 553 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 554 |
+
},
|
| 555 |
+
{
|
| 556 |
+
"id": "separate_bias_flash_nosg",
|
| 557 |
+
"priority": 20,
|
| 558 |
+
"when": ["baseContract", "separateShapeOk", "biasOk", "not present.attentionBiasT", "clusterPortableOk"],
|
| 559 |
+
"derive": {
|
| 560 |
+
"useSubgroups": false,
|
| 561 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 562 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 563 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 564 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 565 |
+
},
|
| 566 |
+
"passes": [
|
| 567 |
+
{
|
| 568 |
+
"id": "main",
|
| 569 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 570 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 571 |
+
"bindings": ["query4", "key4", "value4", "bias", "tokenOffset", "cumSeqLen", "output4", "params"],
|
| 572 |
+
"dispatch": {
|
| 573 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 574 |
+
"y": "numHeads",
|
| 575 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 576 |
+
}
|
| 577 |
+
}
|
| 578 |
+
],
|
| 579 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 580 |
+
},
|
| 581 |
+
{
|
| 582 |
+
"id": "separate_bias_serial",
|
| 583 |
+
"priority": 0,
|
| 584 |
+
"when": ["baseContract", "separateShapeOk", "biasOk", "not present.attentionBiasT", "scalarOk"],
|
| 585 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 586 |
+
"passes": [
|
| 587 |
+
{
|
| 588 |
+
"id": "main",
|
| 589 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 590 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 591 |
+
"bindings": ["query", "key", "value", "bias", "tokenOffset", "cumSeqLen", "output", "params"],
|
| 592 |
+
"dispatch": {
|
| 593 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 594 |
+
"y": "numHeads",
|
| 595 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 596 |
+
}
|
| 597 |
+
}
|
| 598 |
+
]
|
| 599 |
+
},
|
| 600 |
+
{
|
| 601 |
+
"id": "separate_bias_attn_bias_flash",
|
| 602 |
+
"priority": 40,
|
| 603 |
+
"when": ["baseContract", "separateShapeOk", "biasOk", "attnBiasOk", "clusterOk", "clusterSubgroupsOk"],
|
| 604 |
+
"derive": { "useSubgroups": true, "LPQ": "clusterLpq", "TILE_Q": "clusterTileQ", "TILE_K": "clusterTileK" },
|
| 605 |
+
"passes": [
|
| 606 |
+
{
|
| 607 |
+
"id": "main",
|
| 608 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 609 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 610 |
+
"bindings": ["query4", "key4", "value4", "bias", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 611 |
+
"dispatch": {
|
| 612 |
+
"x": "min(clusterTiles, DISPATCH_FOLD_WIDTH)",
|
| 613 |
+
"y": "numHeads",
|
| 614 |
+
"z": "ceilDiv(clusterTiles, DISPATCH_FOLD_WIDTH)"
|
| 615 |
+
}
|
| 616 |
+
}
|
| 617 |
+
],
|
| 618 |
+
"requires": { "features": ["subgroups"] },
|
| 619 |
+
"demoteWhen": ["vHeadDim != headDim and (tokenCount < clusterLpq or (clusterUseDistributed and portableLpq > clusterLpq))"]
|
| 620 |
+
},
|
| 621 |
+
{
|
| 622 |
+
"id": "separate_bias_attn_bias_flash_nosg",
|
| 623 |
+
"priority": 20,
|
| 624 |
+
"when": ["baseContract", "separateShapeOk", "biasOk", "attnBiasOk", "clusterPortableOk"],
|
| 625 |
+
"derive": {
|
| 626 |
+
"useSubgroups": false,
|
| 627 |
+
"LPQ": "portableLpq if clusterUseDistributed else clusterLpq",
|
| 628 |
+
"TILE_Q": "portableTileQ if clusterUseDistributed else clusterTileQ",
|
| 629 |
+
"TILE_K": "portableTileK if clusterUseDistributed else clusterTileK",
|
| 630 |
+
"distributedSoftmax": "clusterUseDistributed"
|
| 631 |
+
},
|
| 632 |
+
"passes": [
|
| 633 |
+
{
|
| 634 |
+
"id": "main",
|
| 635 |
+
"name": "PackedMultiHeadAttention.Cluster",
|
| 636 |
+
"shader": "attn-packed-varlen-cluster.wgsl.jinja",
|
| 637 |
+
"bindings": ["query4", "key4", "value4", "bias", "tokenOffset", "cumSeqLen", "attnBias", "output4", "paramsMask"],
|
| 638 |
+
"dispatch": {
|
| 639 |
+
"x": "min(portableTiles, DISPATCH_FOLD_WIDTH)",
|
| 640 |
+
"y": "numHeads",
|
| 641 |
+
"z": "ceilDiv(portableTiles, DISPATCH_FOLD_WIDTH)"
|
| 642 |
+
}
|
| 643 |
+
}
|
| 644 |
+
],
|
| 645 |
+
"demoteWhen": ["vHeadDim != headDim and tokenCount < clusterLpq"]
|
| 646 |
+
},
|
| 647 |
+
{
|
| 648 |
+
"id": "separate_bias_attn_bias_serial",
|
| 649 |
+
"priority": 0,
|
| 650 |
+
"when": ["baseContract", "separateShapeOk", "biasOk", "attnBiasOk", "scalarOk"],
|
| 651 |
+
"derive": { "workgroupSize": "scalarWg" },
|
| 652 |
+
"passes": [
|
| 653 |
+
{
|
| 654 |
+
"id": "main",
|
| 655 |
+
"name": "PackedMultiHeadAttention.Serial",
|
| 656 |
+
"shader": "attn-packed-varlen-scalar.wgsl.jinja",
|
| 657 |
+
"bindings": ["query", "key", "value", "bias", "tokenOffset", "cumSeqLen", "attnBias", "output", "paramsMask"],
|
| 658 |
+
"dispatch": {
|
| 659 |
+
"x": "min(tokenCount, DISPATCH_FOLD_WIDTH)",
|
| 660 |
+
"y": "numHeads",
|
| 661 |
+
"z": "ceilDiv(tokenCount, DISPATCH_FOLD_WIDTH)"
|
| 662 |
+
}
|
| 663 |
+
}
|
| 664 |
+
]
|
| 665 |
+
}
|
| 666 |
+
]
|
| 667 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "com.microsoft.PackedMultiHeadAttention",
|
| 3 |
+
"id": "_com_microsoft_packedmultiheadattention_webgpu_96b8ba0",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"attn-packed-varlen-cluster.wgsl.jinja": "p9O8Y9FODuK+3sS5XCmt34ubccsQ1rHO8KyYdKFbox8=",
|
| 11 |
+
"attn-packed-varlen-scalar.wgsl.jinja": "6gm8Q90Uq/Fwm53KBabaxD39SZDE/XPt93lhHeOKEKo=",
|
| 12 |
+
"bench.json": "QU1tbtEXCd2xke0vrPrixj1rlwWltGZXLr8StqGlVuM=",
|
| 13 |
+
"manifest.json": "CImYmm6QB0MSplEddT1DpForRg78IeIsHWzBijr+yks=",
|
| 14 |
+
"test.json": "Y3VjrtppOZI+cmCpPGhsEdWnF8qd981T9VIMTNux648="
|
| 15 |
+
}
|
| 16 |
+
},
|
| 17 |
+
"provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
|
| 18 |
+
"webgpu": {
|
| 19 |
+
"manifestSpec": "2.1",
|
| 20 |
+
"variants": {
|
| 21 |
+
"packed_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 22 |
+
"packed_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 23 |
+
"packed_serial": ["attn-packed-varlen-scalar.wgsl.jinja"],
|
| 24 |
+
"packed_attn_bias_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 25 |
+
"packed_attn_bias_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 26 |
+
"packed_attn_bias_serial": ["attn-packed-varlen-scalar.wgsl.jinja"],
|
| 27 |
+
"packed_bias_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 28 |
+
"packed_bias_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 29 |
+
"packed_bias_serial": ["attn-packed-varlen-scalar.wgsl.jinja"],
|
| 30 |
+
"packed_bias_attn_bias_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 31 |
+
"packed_bias_attn_bias_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 32 |
+
"packed_bias_attn_bias_serial": ["attn-packed-varlen-scalar.wgsl.jinja"],
|
| 33 |
+
"separate_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 34 |
+
"separate_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 35 |
+
"separate_serial": ["attn-packed-varlen-scalar.wgsl.jinja"],
|
| 36 |
+
"separate_attn_bias_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 37 |
+
"separate_attn_bias_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 38 |
+
"separate_attn_bias_serial": ["attn-packed-varlen-scalar.wgsl.jinja"],
|
| 39 |
+
"separate_bias_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 40 |
+
"separate_bias_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 41 |
+
"separate_bias_serial": ["attn-packed-varlen-scalar.wgsl.jinja"],
|
| 42 |
+
"separate_bias_attn_bias_flash": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 43 |
+
"separate_bias_attn_bias_flash_nosg": ["attn-packed-varlen-cluster.wgsl.jinja"],
|
| 44 |
+
"separate_bias_attn_bias_serial": ["attn-packed-varlen-scalar.wgsl.jinja"]
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
}
|
build/webgpu/test.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|