sync 2e7068faf55e
Browse files- README.md +96 -0
- build/webgpu/bench.json +231 -0
- build/webgpu/manifest.json +752 -0
- build/webgpu/metadata.json +20 -0
- build/webgpu/sparse-attention.wgsl.jinja +425 -0
- build/webgpu/sparse-kv-append.wgsl.jinja +125 -0
- build/webgpu/sparse-q-rotary.wgsl.jinja +102 -0
- build/webgpu/test.json +745 -0
README.md
CHANGED
|
@@ -1,3 +1,99 @@
|
|
| 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.SparseAttention
|
| 10 |
+
|
| 11 |
+
`com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Block-sparse causal attention used by Phi-3-small. `block_row_indices` and `block_col_indices` encode one or more CSR block masks, and layouts cycle over query heads. Grouped-query heads, separate or packed `[Q|K|V]`, explicit scaling, partial or full rotary embedding in NeoX or interleaved layout, and float16 are supported. The past/present key and value tensors share allocations and are updated in place. Head sizes must be non-zero multiples of 8; bfloat16 is not implemented.
|
| 16 |
+
|
| 17 |
+
See the [ONNX Runtime `SparseAttention` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.SparseAttention) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `query` | `queryT` | `T` | same as logical dtype | `3` | — | Query `(batch_size, sequence_length, num_heads * head_size)`, or packed `[Q\|K\|V]` `(batch_size, sequence_length, (num_heads + 2 * kv_num_heads) * head_size)` when `key` and `value` are omitted. | required |
|
| 24 |
+
| `key` | `keyT` | `T` | same as logical dtype | `3` | — | Key `(batch_size, sequence_length, kv_num_heads * head_size)`. Omitted for packed QKV. | optional |
|
| 25 |
+
| `value` | `valueT` | `T` | same as logical dtype | `3` | — | Value `(batch_size, sequence_length, kv_num_heads * head_size)`. Omitted for packed QKV. | optional |
|
| 26 |
+
| `past_key` | `pastKeyT` | `T` | same as logical dtype | `4` | — | Key cache `(batch_size, kv_num_heads, max_cache_sequence_length, head_size)`, updated in place. | required |
|
| 27 |
+
| `past_value` | `pastValueT` | `T` | same as logical dtype | `4` | — | Value cache with the same shape as `past_key`, updated in place. | required |
|
| 28 |
+
| `block_row_indices` | `blockRowIndicesT` | `M` | `int32` | `2` | — | CSR row pointers `(num_layout, max_blocks + 1)`. Each layout starts at zero, is monotonically non-decreasing, and ends no later than that layout's `block_col_indices` width. | required |
|
| 29 |
+
| `block_col_indices` | `blockColIndicesT` | `M` | `int32` | `2` | — | CSR column indices `(num_layout, max_nnz_blocks)`, right-padded past each layout's non-zero count. Every active entry is in `[0, max_blocks)`. | required |
|
| 30 |
+
| `total_sequence_length` | `totalSequenceLengthT` | `M` | `int32` | — | — | Scalar or one-element vector holding the maximum total key length. Equal to `sequence_length` exactly in the prompt case, which is how the past length is decided. The value fits the cache, the sparse layout's `max_blocks * sparse_block_size` capacity, and the rotary-cache row count when rotary is enabled. | required |
|
| 31 |
+
| `key_total_sequence_lengths` | `keyTotalSequenceLengthsT` | `M` | `int32` | `1` | — | Per-batch total key length excluding padding, shape `(batch_size)`. Each value is at most `total_sequence_length` and is at least 1 for a prompt or at least `sequence_length` otherwise. | required |
|
| 32 |
+
| `cos_cache` | `cosCacheT` | `T` | same as logical dtype | `2` | — | Rotary cosine cache `(max_rotary_sequence_length, rotary_dimension / 2)`, where the width is a multiple of 8 no larger than `head_size / 2`. Required with `sin_cache` when `do_rotary` is 1. | optional |
|
| 33 |
+
| `sin_cache` | `sinCacheT` | `T` | same as logical dtype | `2` | — | Rotary sine cache with the same shape as `cos_cache`; required with it when `do_rotary` is 1. | optional |
|
| 34 |
+
|
| 35 |
+
## Outputs
|
| 36 |
+
|
| 37 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 38 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 39 |
+
| `output` | `outputT` | `T` | `3` | derived; see description | Attention output `(batch_size, sequence_length, num_heads * head_size)`. | required |
|
| 40 |
+
| `past_key` | `pastKeyT` | `T` | `4` | same as `past_key` | The key cache tensor itself after the in-place append; ONNX names this output `present_key`. | required |
|
| 41 |
+
| `past_value` | `pastValueT` | `T` | `4` | same as `past_value` | The value cache tensor itself after the in-place append; ONNX names this output `present_value`. | required |
|
| 42 |
+
|
| 43 |
+
## Attributes
|
| 44 |
+
|
| 45 |
+
Attributes and default values (overridable per request):
|
| 46 |
+
|
| 47 |
+
| Attribute | Default | Description |
|
| 48 |
+
| --- | --- | --- |
|
| 49 |
+
| `do_rotary` | `0` | Set to 1 to apply rotary embedding to Q and to K before it enters the cache; every other value disables rotary embedding. |
|
| 50 |
+
| `rotary_interleaved` | `0` | Set to 1 to rotate adjacent pairs instead of using the NeoX half-split; every other value selects the NeoX layout. |
|
| 51 |
+
| `num_heads` | — | Number of query heads. |
|
| 52 |
+
| `kv_num_heads` | — | Number of key/value heads; must divide `num_heads`. |
|
| 53 |
+
| `sparse_block_size` | — | Tokens per sparse block; one of 16, 32, 64, 128. |
|
| 54 |
+
| `scale` | — | Scale applied to query-key products; omitted or zero uses `1 / sqrt(head_size)`. |
|
| 55 |
+
|
| 56 |
+
## Type constraints
|
| 57 |
+
|
| 58 |
+
| Variable | Allowed dtypes |
|
| 59 |
+
| --- | --- |
|
| 60 |
+
| `T` | `float32`, `float16` |
|
| 61 |
+
| `M` | `int32` |
|
| 62 |
+
|
| 63 |
+
## Files
|
| 64 |
+
|
| 65 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 66 |
+
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 67 |
+
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 68 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
| 69 |
+
- [`sparse-attention.wgsl.jinja`](build/webgpu/sparse-attention.wgsl.jinja)
|
| 70 |
+
- [`sparse-kv-append.wgsl.jinja`](build/webgpu/sparse-kv-append.wgsl.jinja)
|
| 71 |
+
- [`sparse-q-rotary.wgsl.jinja`](build/webgpu/sparse-q-rotary.wgsl.jinja)
|
| 72 |
+
|
| 73 |
+
## Use with `@huggingface/kernels`
|
| 74 |
+
|
| 75 |
+
The loader derives every required output's shape and logical dtype from the manifest contract and this call.
|
| 76 |
+
It then allocates the result tensors automatically.
|
| 77 |
+
|
| 78 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 79 |
+
|
| 80 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 81 |
+
|
| 82 |
+
```js
|
| 83 |
+
import { getKernel } from "@huggingface/kernels";
|
| 84 |
+
|
| 85 |
+
const kernel = await getKernel("webgpu-kernels/com.microsoft.SparseAttention", { version: 1 });
|
| 86 |
+
const { pastKeyT, pastValueT, outputT } = await kernel({
|
| 87 |
+
queryT: { data: queryTData, shape: [1, 32, 8] },
|
| 88 |
+
keyT: { data: keyTData, shape: [1, 32, 8] },
|
| 89 |
+
valueT: { data: valueTData, shape: [1, 32, 8] },
|
| 90 |
+
pastKeyT: { data: pastKeyTData, shape: [1, 1, 32, 8] },
|
| 91 |
+
pastValueT: { data: pastValueTData, shape: [1, 1, 32, 8] },
|
| 92 |
+
blockRowIndicesT: { data: blockRowIndicesTData, shape: [1, 3] },
|
| 93 |
+
blockColIndicesT: { data: blockColIndicesTData, shape: [1, 3] },
|
| 94 |
+
totalSequenceLengthT: { data: totalSequenceLengthTData, shape: [1] },
|
| 95 |
+
keyTotalSequenceLengthsT: { data: keyTotalSequenceLengthsTData, shape: [1] },
|
| 96 |
+
}, {
|
| 97 |
+
attrs: { num_heads: 1, kv_num_heads: 1, sparse_block_size: 16 },
|
| 98 |
+
});
|
| 99 |
+
```
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.SparseAttention",
|
| 3 |
+
"fixtureArrays": {
|
| 4 |
+
"block_column_indices_t_pattern": [0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0, 3, 4, 5, 6, 7, 8, 9, 10, 0, 4, 5, 6, 7, 8, 9, 10, 11, 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 4, 6, 7, 8, 9, 10, 11, 12, 13, 0, 4, 7, 8, 9, 10, 11, 12, 13, 14, 0, 4, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 2, 3, 4, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, 8, 9, 10, 3, 4, 5, 6, 7, 8, 9, 10, 11, 3, 5, 6, 7, 8, 9, 10, 11, 12, 3, 6, 7, 8, 9, 10, 11, 12, 13, 3, 7, 8, 9, 10, 11, 12, 13, 14, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, 0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 4, 5, 6, 7, 8, 9, 10, 11, 2, 5, 6, 7, 8, 9, 10, 11, 12, 2, 6, 7, 8, 9, 10, 11, 12, 13, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 2, 6, 8, 9, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, 0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3, 4, 5, 6, 7, 8, 9, 10, 1, 4, 5, 6, 7, 8, 9, 10, 11, 1, 5, 6, 7, 8, 9, 10, 11, 12, 1, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 5, 7, 8, 9, 10, 11, 12, 13, 14, 1, 5, 8, 9, 10, 11, 12, 13, 14, 15, -1, -1],
|
| 5 |
+
"sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT": [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 54, 63, 72, 82, 92, 102, 112, 0, 1, 3, 6, 10, 15, 21, 28, 36, 44, 52, 60, 69, 78, 87, 96, 106, 0, 1, 3, 6, 10, 15, 21, 28, 36, 44, 52, 61, 70, 79, 88, 98, 108, 0, 1, 3, 6, 10, 15, 21, 28, 36, 44, 53, 62, 71, 80, 90, 100, 110]
|
| 6 |
+
},
|
| 7 |
+
"tunableSpace": { "WORKGROUP_SIZE": [32, 64, 128, 256], "APPEND_WORKGROUP_SIZE": [64, 128, 256] },
|
| 8 |
+
"cases": [
|
| 9 |
+
{
|
| 10 |
+
"name": "sparse-prompt-b1-s1024-h32kv8-d128-blk64",
|
| 11 |
+
"preset": "smoke",
|
| 12 |
+
"vars": {
|
| 13 |
+
"dtype": "float32",
|
| 14 |
+
"batch": 1,
|
| 15 |
+
"seq": 1024,
|
| 16 |
+
"heads": 32,
|
| 17 |
+
"kvHeads": 8,
|
| 18 |
+
"headDim": 128,
|
| 19 |
+
"qkPairs": 13254656,
|
| 20 |
+
"attendedKeys": 1024,
|
| 21 |
+
"dtypeBytes": 4
|
| 22 |
+
},
|
| 23 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 8, "sparse_block_size": 64 },
|
| 24 |
+
"inputs": {
|
| 25 |
+
"queryT": { "shape": [1, 1024, 4096], "dtype": "float32", "dist": "normal", "seed": 9300, "scale": 1 },
|
| 26 |
+
"keyT": { "shape": [1, 1024, 1024], "dtype": "float32", "dist": "normal", "seed": 9301, "scale": 1 },
|
| 27 |
+
"valueT": { "shape": [1, 1024, 1024], "dtype": "float32", "dist": "normal", "seed": 9302, "scale": 1 },
|
| 28 |
+
"pastKeyT": { "shape": [1, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9303, "scale": 1 },
|
| 29 |
+
"pastValueT": { "shape": [1, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9304, "scale": 1 },
|
| 30 |
+
"blockRowIndicesT": {
|
| 31 |
+
"shape": [4, 17],
|
| 32 |
+
"dtype": "int32",
|
| 33 |
+
"data": {
|
| 34 |
+
"kind": "values",
|
| 35 |
+
"values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"blockColIndicesT": {
|
| 39 |
+
"shape": [4, 112],
|
| 40 |
+
"dtype": "int32",
|
| 41 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
|
| 42 |
+
},
|
| 43 |
+
"totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
|
| 44 |
+
"keyTotalSequenceLengthsT": { "shape": [1], "dtype": "int32", "dist": "constant", "value": 1024 }
|
| 45 |
+
},
|
| 46 |
+
"outputs": { "outputT": { "shape": [1, 1024, 4096], "dtype": "float32" } },
|
| 47 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "4 * args.qkPairs * args.headDim" }] }
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"name": "sparse-decode-b8-past1023-h32kv8-d128-blk64",
|
| 51 |
+
"preset": "smoke",
|
| 52 |
+
"vars": {
|
| 53 |
+
"dtype": "float32",
|
| 54 |
+
"batch": 8,
|
| 55 |
+
"seq": 1,
|
| 56 |
+
"heads": 32,
|
| 57 |
+
"kvHeads": 8,
|
| 58 |
+
"headDim": 128,
|
| 59 |
+
"qkPairs": 163840,
|
| 60 |
+
"attendedKeys": 640,
|
| 61 |
+
"dtypeBytes": 4
|
| 62 |
+
},
|
| 63 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 8, "sparse_block_size": 64 },
|
| 64 |
+
"inputs": {
|
| 65 |
+
"queryT": { "shape": [8, 1, 4096], "dtype": "float32", "dist": "normal", "seed": 9310, "scale": 1 },
|
| 66 |
+
"keyT": { "shape": [8, 1, 1024], "dtype": "float32", "dist": "normal", "seed": 9311, "scale": 1 },
|
| 67 |
+
"valueT": { "shape": [8, 1, 1024], "dtype": "float32", "dist": "normal", "seed": 9312, "scale": 1 },
|
| 68 |
+
"pastKeyT": { "shape": [8, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9313, "scale": 1 },
|
| 69 |
+
"pastValueT": { "shape": [8, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9314, "scale": 1 },
|
| 70 |
+
"blockRowIndicesT": {
|
| 71 |
+
"shape": [4, 17],
|
| 72 |
+
"dtype": "int32",
|
| 73 |
+
"data": {
|
| 74 |
+
"kind": "values",
|
| 75 |
+
"values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
|
| 76 |
+
}
|
| 77 |
+
},
|
| 78 |
+
"blockColIndicesT": {
|
| 79 |
+
"shape": [4, 112],
|
| 80 |
+
"dtype": "int32",
|
| 81 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
|
| 82 |
+
},
|
| 83 |
+
"totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
|
| 84 |
+
"keyTotalSequenceLengthsT": { "shape": [8], "dtype": "int32", "dist": "constant", "value": 1024 }
|
| 85 |
+
},
|
| 86 |
+
"outputs": { "outputT": { "shape": [8, 1, 4096], "dtype": "float32" } },
|
| 87 |
+
"bench": {
|
| 88 |
+
"metrics": [
|
| 89 |
+
{
|
| 90 |
+
"type": "bandwidth",
|
| 91 |
+
"value": "2 * args.batch * args.kvHeads * args.attendedKeys * args.headDim * args.dtypeBytes"
|
| 92 |
+
}
|
| 93 |
+
]
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"name": "sparse-decode-f16-b8-past1023",
|
| 98 |
+
"preset": "model",
|
| 99 |
+
"vars": {
|
| 100 |
+
"dtype": "float16",
|
| 101 |
+
"batch": 8,
|
| 102 |
+
"seq": 1,
|
| 103 |
+
"heads": 32,
|
| 104 |
+
"kvHeads": 8,
|
| 105 |
+
"headDim": 128,
|
| 106 |
+
"qkPairs": 163840,
|
| 107 |
+
"attendedKeys": 640,
|
| 108 |
+
"dtypeBytes": 2
|
| 109 |
+
},
|
| 110 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 8, "sparse_block_size": 64 },
|
| 111 |
+
"inputs": {
|
| 112 |
+
"queryT": { "shape": [8, 1, 4096], "dtype": "float16", "dist": "normal", "seed": 9320, "scale": 1 },
|
| 113 |
+
"keyT": { "shape": [8, 1, 1024], "dtype": "float16", "dist": "normal", "seed": 9321, "scale": 1 },
|
| 114 |
+
"valueT": { "shape": [8, 1, 1024], "dtype": "float16", "dist": "normal", "seed": 9322, "scale": 1 },
|
| 115 |
+
"pastKeyT": { "shape": [8, 8, 1024, 128], "dtype": "float16", "dist": "normal", "seed": 9323, "scale": 1 },
|
| 116 |
+
"pastValueT": { "shape": [8, 8, 1024, 128], "dtype": "float16", "dist": "normal", "seed": 9324, "scale": 1 },
|
| 117 |
+
"blockRowIndicesT": {
|
| 118 |
+
"shape": [4, 17],
|
| 119 |
+
"dtype": "int32",
|
| 120 |
+
"data": {
|
| 121 |
+
"kind": "values",
|
| 122 |
+
"values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"blockColIndicesT": {
|
| 126 |
+
"shape": [4, 112],
|
| 127 |
+
"dtype": "int32",
|
| 128 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
|
| 129 |
+
},
|
| 130 |
+
"totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
|
| 131 |
+
"keyTotalSequenceLengthsT": { "shape": [8], "dtype": "int32", "dist": "constant", "value": 1024 }
|
| 132 |
+
},
|
| 133 |
+
"outputs": { "outputT": { "shape": [8, 1, 4096], "dtype": "float16" } },
|
| 134 |
+
"bench": {
|
| 135 |
+
"metrics": [
|
| 136 |
+
{
|
| 137 |
+
"type": "bandwidth",
|
| 138 |
+
"value": "2 * args.batch * args.kvHeads * args.attendedKeys * args.headDim * args.dtypeBytes"
|
| 139 |
+
}
|
| 140 |
+
]
|
| 141 |
+
}
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"name": "sparse-decode-packed-rotary-b8-past1023",
|
| 145 |
+
"preset": "model",
|
| 146 |
+
"vars": {
|
| 147 |
+
"dtype": "float32",
|
| 148 |
+
"batch": 8,
|
| 149 |
+
"seq": 1,
|
| 150 |
+
"heads": 32,
|
| 151 |
+
"kvHeads": 8,
|
| 152 |
+
"headDim": 128,
|
| 153 |
+
"qkPairs": 163840,
|
| 154 |
+
"attendedKeys": 640,
|
| 155 |
+
"dtypeBytes": 4
|
| 156 |
+
},
|
| 157 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 8, "sparse_block_size": 64, "do_rotary": 1 },
|
| 158 |
+
"inputs": {
|
| 159 |
+
"queryT": { "shape": [8, 1, 6144], "dtype": "float32", "dist": "normal", "seed": 9330, "scale": 1 },
|
| 160 |
+
"pastKeyT": { "shape": [8, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9333, "scale": 1 },
|
| 161 |
+
"pastValueT": { "shape": [8, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9334, "scale": 1 },
|
| 162 |
+
"blockRowIndicesT": {
|
| 163 |
+
"shape": [4, 17],
|
| 164 |
+
"dtype": "int32",
|
| 165 |
+
"data": {
|
| 166 |
+
"kind": "values",
|
| 167 |
+
"values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
|
| 168 |
+
}
|
| 169 |
+
},
|
| 170 |
+
"blockColIndicesT": {
|
| 171 |
+
"shape": [4, 112],
|
| 172 |
+
"dtype": "int32",
|
| 173 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
|
| 174 |
+
},
|
| 175 |
+
"totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
|
| 176 |
+
"keyTotalSequenceLengthsT": { "shape": [8], "dtype": "int32", "dist": "constant", "value": 1024 },
|
| 177 |
+
"cosCacheT": { "shape": [1024, 64], "dtype": "float32", "dist": "normal", "seed": 9335, "scale": 1 },
|
| 178 |
+
"sinCacheT": { "shape": [1024, 64], "dtype": "float32", "dist": "normal", "seed": 9336, "scale": 1 }
|
| 179 |
+
},
|
| 180 |
+
"outputs": { "outputT": { "shape": [8, 1, 4096], "dtype": "float32" } },
|
| 181 |
+
"bench": {
|
| 182 |
+
"metrics": [
|
| 183 |
+
{
|
| 184 |
+
"type": "bandwidth",
|
| 185 |
+
"value": "2 * args.batch * args.kvHeads * args.attendedKeys * args.headDim * args.dtypeBytes"
|
| 186 |
+
}
|
| 187 |
+
]
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
{
|
| 191 |
+
"name": "sparse-chunked-b2-s128-past896",
|
| 192 |
+
"preset": "model",
|
| 193 |
+
"vars": {
|
| 194 |
+
"dtype": "float32",
|
| 195 |
+
"batch": 2,
|
| 196 |
+
"seq": 128,
|
| 197 |
+
"heads": 32,
|
| 198 |
+
"kvHeads": 8,
|
| 199 |
+
"headDim": 128,
|
| 200 |
+
"qkPairs": 4919296,
|
| 201 |
+
"attendedKeys": 704,
|
| 202 |
+
"dtypeBytes": 4
|
| 203 |
+
},
|
| 204 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 8, "sparse_block_size": 64 },
|
| 205 |
+
"inputs": {
|
| 206 |
+
"queryT": { "shape": [2, 128, 4096], "dtype": "float32", "dist": "normal", "seed": 9340, "scale": 1 },
|
| 207 |
+
"keyT": { "shape": [2, 128, 1024], "dtype": "float32", "dist": "normal", "seed": 9341, "scale": 1 },
|
| 208 |
+
"valueT": { "shape": [2, 128, 1024], "dtype": "float32", "dist": "normal", "seed": 9342, "scale": 1 },
|
| 209 |
+
"pastKeyT": { "shape": [2, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9343, "scale": 1 },
|
| 210 |
+
"pastValueT": { "shape": [2, 8, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9344, "scale": 1 },
|
| 211 |
+
"blockRowIndicesT": {
|
| 212 |
+
"shape": [4, 17],
|
| 213 |
+
"dtype": "int32",
|
| 214 |
+
"data": {
|
| 215 |
+
"kind": "values",
|
| 216 |
+
"values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
|
| 217 |
+
}
|
| 218 |
+
},
|
| 219 |
+
"blockColIndicesT": {
|
| 220 |
+
"shape": [4, 112],
|
| 221 |
+
"dtype": "int32",
|
| 222 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
|
| 223 |
+
},
|
| 224 |
+
"totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
|
| 225 |
+
"keyTotalSequenceLengthsT": { "shape": [2], "dtype": "int32", "dist": "constant", "value": 1024 }
|
| 226 |
+
},
|
| 227 |
+
"outputs": { "outputT": { "shape": [2, 128, 4096], "dtype": "float32" } },
|
| 228 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "4 * args.qkPairs * args.headDim" }] }
|
| 229 |
+
}
|
| 230 |
+
]
|
| 231 |
+
}
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,752 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "com.microsoft",
|
| 3 |
+
"name": "SparseAttention",
|
| 4 |
+
"sinceVersion": 1,
|
| 5 |
+
"description": "Block-sparse causal attention used by Phi-3-small. `block_row_indices` and `block_col_indices` encode one or more CSR block masks, and layouts cycle over query heads. Grouped-query heads, separate or packed `[Q|K|V]`, explicit scaling, partial or full rotary embedding in NeoX or interleaved layout, and float16 are supported. The past/present key and value tensors share allocations and are updated in place. Head sizes must be non-zero multiples of 8; bfloat16 is not implemented.",
|
| 6 |
+
"inputs": [
|
| 7 |
+
{
|
| 8 |
+
"role": "query",
|
| 9 |
+
"dtype": "T",
|
| 10 |
+
"rank": 3,
|
| 11 |
+
"description": "Query `(batch_size, sequence_length, num_heads * head_size)`, or packed `[Q|K|V]` `(batch_size, sequence_length, (num_heads + 2 * kv_num_heads) * head_size)` when `key` and `value` are omitted."
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"role": "key",
|
| 15 |
+
"dtype": "T",
|
| 16 |
+
"rank": 3,
|
| 17 |
+
"optional": true,
|
| 18 |
+
"description": "Key `(batch_size, sequence_length, kv_num_heads * head_size)`. Omitted for packed QKV."
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"role": "value",
|
| 22 |
+
"dtype": "T",
|
| 23 |
+
"rank": 3,
|
| 24 |
+
"optional": true,
|
| 25 |
+
"description": "Value `(batch_size, sequence_length, kv_num_heads * head_size)`. Omitted for packed QKV."
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"role": "past_key",
|
| 29 |
+
"dtype": "T",
|
| 30 |
+
"rank": 4,
|
| 31 |
+
"description": "Key cache `(batch_size, kv_num_heads, max_cache_sequence_length, head_size)`, updated in place."
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"role": "past_value",
|
| 35 |
+
"dtype": "T",
|
| 36 |
+
"rank": 4,
|
| 37 |
+
"description": "Value cache with the same shape as `past_key`, updated in place."
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"role": "block_row_indices",
|
| 41 |
+
"dtype": "M",
|
| 42 |
+
"rank": 2,
|
| 43 |
+
"description": "CSR row pointers `(num_layout, max_blocks + 1)`. Each layout starts at zero, is monotonically non-decreasing, and ends no later than that layout's `block_col_indices` width."
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"role": "block_col_indices",
|
| 47 |
+
"dtype": "M",
|
| 48 |
+
"rank": 2,
|
| 49 |
+
"description": "CSR column indices `(num_layout, max_nnz_blocks)`, right-padded past each layout's non-zero count. Every active entry is in `[0, max_blocks)`."
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"role": "total_sequence_length",
|
| 53 |
+
"dtype": "M",
|
| 54 |
+
"description": "Scalar or one-element vector holding the maximum total key length. Equal to `sequence_length` exactly in the prompt case, which is how the past length is decided. The value fits the cache, the sparse layout's `max_blocks * sparse_block_size` capacity, and the rotary-cache row count when rotary is enabled."
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"role": "key_total_sequence_lengths",
|
| 58 |
+
"dtype": "M",
|
| 59 |
+
"rank": 1,
|
| 60 |
+
"description": "Per-batch total key length excluding padding, shape `(batch_size)`. Each value is at most `total_sequence_length` and is at least 1 for a prompt or at least `sequence_length` otherwise."
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"role": "cos_cache",
|
| 64 |
+
"dtype": "T",
|
| 65 |
+
"rank": 2,
|
| 66 |
+
"optional": true,
|
| 67 |
+
"description": "Rotary cosine cache `(max_rotary_sequence_length, rotary_dimension / 2)`, where the width is a multiple of 8 no larger than `head_size / 2`. Required with `sin_cache` when `do_rotary` is 1."
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"role": "sin_cache",
|
| 71 |
+
"dtype": "T",
|
| 72 |
+
"rank": 2,
|
| 73 |
+
"optional": true,
|
| 74 |
+
"description": "Rotary sine cache with the same shape as `cos_cache`; required with it when `do_rotary` is 1."
|
| 75 |
+
}
|
| 76 |
+
],
|
| 77 |
+
"outputs": [
|
| 78 |
+
{
|
| 79 |
+
"role": "output",
|
| 80 |
+
"dtype": "T",
|
| 81 |
+
"rank": 3,
|
| 82 |
+
"shape": "[batchSize, seqLen, numHeads * headSize]",
|
| 83 |
+
"description": "Attention output `(batch_size, sequence_length, num_heads * head_size)`."
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"role": "past_key",
|
| 87 |
+
"dtype": "T",
|
| 88 |
+
"rank": 4,
|
| 89 |
+
"shape": "shapes.pastKeyT",
|
| 90 |
+
"description": "The key cache tensor itself after the in-place append; ONNX names this output `present_key`."
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"role": "past_value",
|
| 94 |
+
"dtype": "T",
|
| 95 |
+
"rank": 4,
|
| 96 |
+
"shape": "shapes.pastValueT",
|
| 97 |
+
"description": "The value cache tensor itself after the in-place append; ONNX names this output `present_value`."
|
| 98 |
+
}
|
| 99 |
+
],
|
| 100 |
+
"attributes": { "do_rotary": 0, "rotary_interleaved": 0 },
|
| 101 |
+
"attributeDescriptions": {
|
| 102 |
+
"num_heads": "Number of query heads.",
|
| 103 |
+
"kv_num_heads": "Number of key/value heads; must divide `num_heads`.",
|
| 104 |
+
"sparse_block_size": "Tokens per sparse block; one of 16, 32, 64, 128.",
|
| 105 |
+
"do_rotary": "Set to 1 to apply rotary embedding to Q and to K before it enters the cache; every other value disables rotary embedding.",
|
| 106 |
+
"rotary_interleaved": "Set to 1 to rotate adjacent pairs instead of using the NeoX half-split; every other value selects the NeoX layout.",
|
| 107 |
+
"scale": "Scale applied to query-key products; omitted or zero uses `1 / sqrt(head_size)`."
|
| 108 |
+
},
|
| 109 |
+
"attributeConstraints": {
|
| 110 |
+
"num_heads": { "required": true },
|
| 111 |
+
"kv_num_heads": { "required": true },
|
| 112 |
+
"sparse_block_size": { "required": true }
|
| 113 |
+
},
|
| 114 |
+
"typeConstraints": { "T": ["float32", "float16"], "M": ["int32"] },
|
| 115 |
+
"args": {
|
| 116 |
+
"queryT": { "kind": "tensor", "semantic": "query", "role": "input" },
|
| 117 |
+
"keyT": { "kind": "tensor", "semantic": "key", "role": "input", "required": false },
|
| 118 |
+
"valueT": { "kind": "tensor", "semantic": "value", "role": "input", "required": false },
|
| 119 |
+
"pastKeyT": { "kind": "tensor", "semantic": "past_key", "role": "inout" },
|
| 120 |
+
"pastValueT": { "kind": "tensor", "semantic": "past_value", "role": "inout" },
|
| 121 |
+
"blockRowIndicesT": { "kind": "tensor", "semantic": "block_row_indices", "role": "input", "dtype": "int32" },
|
| 122 |
+
"blockColIndicesT": { "kind": "tensor", "semantic": "block_col_indices", "role": "input", "dtype": "int32" },
|
| 123 |
+
"totalSequenceLengthT": { "kind": "tensor", "semantic": "total_sequence_length", "role": "input", "dtype": "int32" },
|
| 124 |
+
"keyTotalSequenceLengthsT": {
|
| 125 |
+
"kind": "tensor",
|
| 126 |
+
"semantic": "key_total_sequence_lengths",
|
| 127 |
+
"role": "input",
|
| 128 |
+
"dtype": "int32"
|
| 129 |
+
},
|
| 130 |
+
"cosCacheT": { "kind": "tensor", "semantic": "cos_cache", "role": "input", "required": false },
|
| 131 |
+
"sinCacheT": { "kind": "tensor", "semantic": "sin_cache", "role": "input", "required": false },
|
| 132 |
+
"outputT": { "kind": "tensor", "semantic": "output", "role": "output" }
|
| 133 |
+
},
|
| 134 |
+
"tunables": { "WORKGROUP_SIZE": 128, "APPEND_WORKGROUP_SIZE": 256, "NARROW_MIN_WORKGROUPS": 1024, "QUERY_TILE": 4 },
|
| 135 |
+
"derive": {
|
| 136 |
+
"batchSize": "dim(shapes.queryT, 0)",
|
| 137 |
+
"seqLen": "dim(shapes.queryT, 1)",
|
| 138 |
+
"numHeads": "attrs.num_heads",
|
| 139 |
+
"kvNumHeads": "attrs.kv_num_heads",
|
| 140 |
+
"sparseBlockSize": "attrs.sparse_block_size",
|
| 141 |
+
"headSize": "dim(shapes.pastKeyT, 3)",
|
| 142 |
+
"headVec": "headSize / 4",
|
| 143 |
+
"sparseWidthBound": "max(256, tunables.WORKGROUP_SIZE)",
|
| 144 |
+
"sparseQueryTileCap": "max(1, floor((device.limits.maxComputeWorkgroupStorageSize / 4 - sparseWidthBound) / (2 * headSize + 3 * sparseWidthBound)))",
|
| 145 |
+
"sparseQueryTileWant": "min(tunables.QUERY_TILE, min(sparseBlockSize, sparseQueryTileCap))",
|
| 146 |
+
"sparseQueryTile": "1 if seqLen <= 1 else (16 if sparseQueryTileWant >= 16 and seqLen >= 16 else (8 if sparseQueryTileWant >= 8 and seqLen >= 8 else (4 if sparseQueryTileWant >= 4 and seqLen >= 4 else (2 if sparseQueryTileWant >= 2 and seqLen >= 2 else 1))))",
|
| 147 |
+
"sparseQueryTiles": "ceilDiv(seqLen, sparseQueryTile)",
|
| 148 |
+
"sparseAttnWorkgroups": "sparseQueryTiles * batchSize * numHeads",
|
| 149 |
+
"sparseAttnWorkgroup": "min(256, max(32, pow2ceil(headVec))) if sparseAttnWorkgroups >= tunables.NARROW_MIN_WORKGROUPS else tunables.WORKGROUP_SIZE",
|
| 150 |
+
"maxCacheSeq": "dim(shapes.pastKeyT, 2)",
|
| 151 |
+
"numLayout": "dim(shapes.blockRowIndicesT, 0)",
|
| 152 |
+
"maxBlocks": "dim(shapes.blockRowIndicesT, 1) - 1",
|
| 153 |
+
"maxNnz": "dim(shapes.blockColIndicesT, 1)",
|
| 154 |
+
"packedQkv": "not present.keyT",
|
| 155 |
+
"qHidden": "numHeads * headSize",
|
| 156 |
+
"kvHidden": "kvNumHeads * headSize",
|
| 157 |
+
"packedStride": "(numHeads + 2 * kvNumHeads) * headSize",
|
| 158 |
+
"doRotary": "attrs.do_rotary == 1",
|
| 159 |
+
"rotaryHalf": "dim(shapes.cosCacheT, 1) if doRotary and present.cosCacheT and ranks.cosCacheT == 2 else 0",
|
| 160 |
+
"rotaryDim": "2 * rotaryHalf",
|
| 161 |
+
"useRotary": "doRotary and rotaryDim > 0",
|
| 162 |
+
"rotaryInterleaved": "attrs.rotary_interleaved == 1",
|
| 163 |
+
"qRotaryElements": "batchSize * numHeads * seqLen * headSize",
|
| 164 |
+
"cacheShapeOk": "ranks.pastKeyT == 4 and ranks.pastValueT == 4 and dim(shapes.pastKeyT, 0) == batchSize and dim(shapes.pastKeyT, 1) == kvNumHeads and sameShape(shapes.pastValueT, shapes.pastKeyT)",
|
| 165 |
+
"queryShapeOk": "dim(shapes.queryT, 2) == (packedStride if packedQkv else qHidden)",
|
| 166 |
+
"kvShapeOk": "packedQkv or (present.valueT and ranks.keyT == 3 and ranks.valueT == 3 and dim(shapes.keyT, 0) == batchSize and dim(shapes.keyT, 1) == seqLen and dim(shapes.keyT, 2) == kvHidden and sameShape(shapes.valueT, shapes.keyT) and tensorDtypes.keyT == tensorDtypes.queryT and tensorDtypes.valueT == tensorDtypes.queryT)",
|
| 167 |
+
"kvPairOk": "present.keyT == present.valueT",
|
| 168 |
+
"rotaryPairOk": "not doRotary or (present.cosCacheT and present.sinCacheT and ranks.cosCacheT == 2 and ranks.sinCacheT == 2 and rotaryHalf % 8 == 0 and rotaryDim <= headSize and sameShape(shapes.sinCacheT, shapes.cosCacheT) and tensorDtypes.cosCacheT == tensorDtypes.queryT and tensorDtypes.sinCacheT == tensorDtypes.queryT)",
|
| 169 |
+
"blockIndexShapeOk": "ranks.blockRowIndicesT == 2 and ranks.blockColIndicesT == 2 and dim(shapes.blockColIndicesT, 0) == numLayout and maxBlocks >= 1 and maxNnz >= 0 and maxNnz <= maxBlocks * maxBlocks and tensorDtypes.blockRowIndicesT == \"int32\" and tensorDtypes.blockColIndicesT == \"int32\"",
|
| 170 |
+
"scheduleShapeOk": "(ranks.totalSequenceLengthT == 0 or ranks.totalSequenceLengthT == 1) and numel(shapes.totalSequenceLengthT) == 1 and ranks.keyTotalSequenceLengthsT == 1 and dim(shapes.keyTotalSequenceLengthsT, 0) == batchSize and tensorDtypes.totalSequenceLengthT == \"int32\" and tensorDtypes.keyTotalSequenceLengthsT == \"int32\"",
|
| 171 |
+
"geometryOk": "tunables.WORKGROUP_SIZE >= 1 and floor(tunables.WORKGROUP_SIZE) == tunables.WORKGROUP_SIZE and pow2ceil(tunables.WORKGROUP_SIZE) == tunables.WORKGROUP_SIZE and tunables.WORKGROUP_SIZE <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.WORKGROUP_SIZE <= device.limits.maxComputeWorkgroupSizeX and tunables.APPEND_WORKGROUP_SIZE >= 1 and floor(tunables.APPEND_WORKGROUP_SIZE) == tunables.APPEND_WORKGROUP_SIZE and tunables.APPEND_WORKGROUP_SIZE <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.APPEND_WORKGROUP_SIZE <= device.limits.maxComputeWorkgroupSizeX and sparseQueryTiles <= device.limits.maxComputeWorkgroupsPerDimension and batchSize * numHeads <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(ceilDiv(qRotaryElements, tunables.APPEND_WORKGROUP_SIZE), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension and (2 * sparseQueryTile * headSize + (3 * sparseQueryTile + 1) * sparseAttnWorkgroup) * 4 <= device.limits.maxComputeWorkgroupStorageSize and sparseAttnWorkgroup <= device.limits.maxComputeInvocationsPerWorkgroup and sparseAttnWorkgroup <= device.limits.maxComputeWorkgroupSizeX",
|
| 172 |
+
"contract": "ranks.queryT == 3 and ranks.outputT == 3 and (tensorDtypes.queryT == \"float32\" or tensorDtypes.queryT == \"float16\") and f16Ok(dtypes.T) and tensorDtypes.pastKeyT == tensorDtypes.queryT and tensorDtypes.pastValueT == tensorDtypes.queryT and tensorDtypes.outputT == tensorDtypes.queryT and numHeads >= 1 and kvNumHeads >= 1 and numHeads % kvNumHeads == 0 and headSize >= 8 and headSize % 8 == 0 and (not doRotary or headSize % 16 == 0) and numLayout >= 1 and numHeads % numLayout == 0 and (sparseBlockSize == 16 or sparseBlockSize == 32 or sparseBlockSize == 64 or sparseBlockSize == 128) and cacheShapeOk and queryShapeOk and kvShapeOk and kvPairOk and rotaryPairOk and blockIndexShapeOk and scheduleShapeOk and dim(shapes.outputT, 0) == batchSize and dim(shapes.outputT, 1) == seqLen and dim(shapes.outputT, 2) == qHidden",
|
| 173 |
+
"packedContract": "contract and packedQkv and not useRotary",
|
| 174 |
+
"packedRotaryContract": "contract and packedQkv and useRotary",
|
| 175 |
+
"separateContract": "contract and not packedQkv and not useRotary",
|
| 176 |
+
"separateRotaryContract": "contract and not packedQkv and useRotary"
|
| 177 |
+
},
|
| 178 |
+
"constants": {
|
| 179 |
+
"scalar": "dtypes.T",
|
| 180 |
+
"usesF16": "dtypes.T == \"f16\"",
|
| 181 |
+
"numHeads": "numHeads",
|
| 182 |
+
"kvNumHeads": "kvNumHeads",
|
| 183 |
+
"headSize": "headSize",
|
| 184 |
+
"headVec": "headVec",
|
| 185 |
+
"cacheVec": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\"",
|
| 186 |
+
"attnWorkgroup": "sparseAttnWorkgroup",
|
| 187 |
+
"maxCacheSeq": "maxCacheSeq",
|
| 188 |
+
"sparseBlockSize": "sparseBlockSize",
|
| 189 |
+
"numLayout": "numLayout",
|
| 190 |
+
"maxBlocks": "maxBlocks",
|
| 191 |
+
"maxNnz": "maxNnz",
|
| 192 |
+
"packedStride": "packedStride",
|
| 193 |
+
"packedQkv": "packedQkv",
|
| 194 |
+
"usesRotary": "useRotary",
|
| 195 |
+
"rotaryHalf": "rotaryHalf",
|
| 196 |
+
"rotaryDim": "rotaryDim",
|
| 197 |
+
"rotaryInterleaved": "rotaryInterleaved",
|
| 198 |
+
"appendWorkgroupSize": "tunables.APPEND_WORKGROUP_SIZE"
|
| 199 |
+
},
|
| 200 |
+
"bindingSets": {
|
| 201 |
+
"appendSeparate": [
|
| 202 |
+
{
|
| 203 |
+
"name": "new_key",
|
| 204 |
+
"arg": "keyT",
|
| 205 |
+
"semantic": "key",
|
| 206 |
+
"buffer": { "type": "read-only-storage" },
|
| 207 |
+
"elementType": "$scalar"
|
| 208 |
+
},
|
| 209 |
+
{
|
| 210 |
+
"name": "new_value",
|
| 211 |
+
"arg": "valueT",
|
| 212 |
+
"semantic": "value",
|
| 213 |
+
"buffer": { "type": "read-only-storage" },
|
| 214 |
+
"elementType": "$scalar"
|
| 215 |
+
},
|
| 216 |
+
{
|
| 217 |
+
"name": "present_key",
|
| 218 |
+
"arg": "pastKeyT",
|
| 219 |
+
"semantic": "past_key",
|
| 220 |
+
"buffer": { "type": "storage" },
|
| 221 |
+
"elementType": "$scalar"
|
| 222 |
+
},
|
| 223 |
+
{
|
| 224 |
+
"name": "present_value",
|
| 225 |
+
"arg": "pastValueT",
|
| 226 |
+
"semantic": "past_value",
|
| 227 |
+
"buffer": { "type": "storage" },
|
| 228 |
+
"elementType": "$scalar"
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"name": "key_total_sequence_lengths",
|
| 232 |
+
"arg": "keyTotalSequenceLengthsT",
|
| 233 |
+
"semantic": "key_total_sequence_lengths",
|
| 234 |
+
"buffer": { "type": "read-only-storage" },
|
| 235 |
+
"elementType": "i32"
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"name": "total_sequence_length",
|
| 239 |
+
"arg": "totalSequenceLengthT",
|
| 240 |
+
"semantic": "total_sequence_length",
|
| 241 |
+
"buffer": { "type": "read-only-storage" },
|
| 242 |
+
"elementType": "i32"
|
| 243 |
+
},
|
| 244 |
+
{
|
| 245 |
+
"name": "params",
|
| 246 |
+
"semantic": "kernel.params",
|
| 247 |
+
"buffer": { "type": "uniform" },
|
| 248 |
+
"struct": {
|
| 249 |
+
"name": "Params",
|
| 250 |
+
"fields": [
|
| 251 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 252 |
+
{ "name": "seqLen", "type": "u32", "value": "seqLen" }
|
| 253 |
+
]
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
],
|
| 257 |
+
"appendSeparateRotary": [
|
| 258 |
+
{
|
| 259 |
+
"name": "new_key",
|
| 260 |
+
"arg": "keyT",
|
| 261 |
+
"semantic": "key",
|
| 262 |
+
"buffer": { "type": "read-only-storage" },
|
| 263 |
+
"elementType": "$scalar"
|
| 264 |
+
},
|
| 265 |
+
{
|
| 266 |
+
"name": "new_value",
|
| 267 |
+
"arg": "valueT",
|
| 268 |
+
"semantic": "value",
|
| 269 |
+
"buffer": { "type": "read-only-storage" },
|
| 270 |
+
"elementType": "$scalar"
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"name": "present_key",
|
| 274 |
+
"arg": "pastKeyT",
|
| 275 |
+
"semantic": "past_key",
|
| 276 |
+
"buffer": { "type": "storage" },
|
| 277 |
+
"elementType": "$scalar"
|
| 278 |
+
},
|
| 279 |
+
{
|
| 280 |
+
"name": "present_value",
|
| 281 |
+
"arg": "pastValueT",
|
| 282 |
+
"semantic": "past_value",
|
| 283 |
+
"buffer": { "type": "storage" },
|
| 284 |
+
"elementType": "$scalar"
|
| 285 |
+
},
|
| 286 |
+
{
|
| 287 |
+
"name": "key_total_sequence_lengths",
|
| 288 |
+
"arg": "keyTotalSequenceLengthsT",
|
| 289 |
+
"semantic": "key_total_sequence_lengths",
|
| 290 |
+
"buffer": { "type": "read-only-storage" },
|
| 291 |
+
"elementType": "i32"
|
| 292 |
+
},
|
| 293 |
+
{
|
| 294 |
+
"name": "total_sequence_length",
|
| 295 |
+
"arg": "totalSequenceLengthT",
|
| 296 |
+
"semantic": "total_sequence_length",
|
| 297 |
+
"buffer": { "type": "read-only-storage" },
|
| 298 |
+
"elementType": "i32"
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"name": "cos_cache",
|
| 302 |
+
"arg": "cosCacheT",
|
| 303 |
+
"semantic": "cos_cache",
|
| 304 |
+
"buffer": { "type": "read-only-storage" },
|
| 305 |
+
"elementType": "$scalar"
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"name": "sin_cache",
|
| 309 |
+
"arg": "sinCacheT",
|
| 310 |
+
"semantic": "sin_cache",
|
| 311 |
+
"buffer": { "type": "read-only-storage" },
|
| 312 |
+
"elementType": "$scalar"
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"name": "params",
|
| 316 |
+
"semantic": "kernel.params",
|
| 317 |
+
"buffer": { "type": "uniform" },
|
| 318 |
+
"struct": {
|
| 319 |
+
"name": "Params",
|
| 320 |
+
"fields": [
|
| 321 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 322 |
+
{ "name": "seqLen", "type": "u32", "value": "seqLen" }
|
| 323 |
+
]
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
],
|
| 327 |
+
"appendPacked": [
|
| 328 |
+
{
|
| 329 |
+
"name": "packed_qkv",
|
| 330 |
+
"arg": "queryT",
|
| 331 |
+
"semantic": "query",
|
| 332 |
+
"buffer": { "type": "read-only-storage" },
|
| 333 |
+
"elementType": "$scalar"
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"name": "present_key",
|
| 337 |
+
"arg": "pastKeyT",
|
| 338 |
+
"semantic": "past_key",
|
| 339 |
+
"buffer": { "type": "storage" },
|
| 340 |
+
"elementType": "$scalar"
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"name": "present_value",
|
| 344 |
+
"arg": "pastValueT",
|
| 345 |
+
"semantic": "past_value",
|
| 346 |
+
"buffer": { "type": "storage" },
|
| 347 |
+
"elementType": "$scalar"
|
| 348 |
+
},
|
| 349 |
+
{
|
| 350 |
+
"name": "key_total_sequence_lengths",
|
| 351 |
+
"arg": "keyTotalSequenceLengthsT",
|
| 352 |
+
"semantic": "key_total_sequence_lengths",
|
| 353 |
+
"buffer": { "type": "read-only-storage" },
|
| 354 |
+
"elementType": "i32"
|
| 355 |
+
},
|
| 356 |
+
{
|
| 357 |
+
"name": "total_sequence_length",
|
| 358 |
+
"arg": "totalSequenceLengthT",
|
| 359 |
+
"semantic": "total_sequence_length",
|
| 360 |
+
"buffer": { "type": "read-only-storage" },
|
| 361 |
+
"elementType": "i32"
|
| 362 |
+
},
|
| 363 |
+
{
|
| 364 |
+
"name": "params",
|
| 365 |
+
"semantic": "kernel.params",
|
| 366 |
+
"buffer": { "type": "uniform" },
|
| 367 |
+
"struct": {
|
| 368 |
+
"name": "Params",
|
| 369 |
+
"fields": [
|
| 370 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 371 |
+
{ "name": "seqLen", "type": "u32", "value": "seqLen" }
|
| 372 |
+
]
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
],
|
| 376 |
+
"appendPackedRotary": [
|
| 377 |
+
{
|
| 378 |
+
"name": "packed_qkv",
|
| 379 |
+
"arg": "queryT",
|
| 380 |
+
"semantic": "query",
|
| 381 |
+
"buffer": { "type": "read-only-storage" },
|
| 382 |
+
"elementType": "$scalar"
|
| 383 |
+
},
|
| 384 |
+
{
|
| 385 |
+
"name": "present_key",
|
| 386 |
+
"arg": "pastKeyT",
|
| 387 |
+
"semantic": "past_key",
|
| 388 |
+
"buffer": { "type": "storage" },
|
| 389 |
+
"elementType": "$scalar"
|
| 390 |
+
},
|
| 391 |
+
{
|
| 392 |
+
"name": "present_value",
|
| 393 |
+
"arg": "pastValueT",
|
| 394 |
+
"semantic": "past_value",
|
| 395 |
+
"buffer": { "type": "storage" },
|
| 396 |
+
"elementType": "$scalar"
|
| 397 |
+
},
|
| 398 |
+
{
|
| 399 |
+
"name": "key_total_sequence_lengths",
|
| 400 |
+
"arg": "keyTotalSequenceLengthsT",
|
| 401 |
+
"semantic": "key_total_sequence_lengths",
|
| 402 |
+
"buffer": { "type": "read-only-storage" },
|
| 403 |
+
"elementType": "i32"
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"name": "total_sequence_length",
|
| 407 |
+
"arg": "totalSequenceLengthT",
|
| 408 |
+
"semantic": "total_sequence_length",
|
| 409 |
+
"buffer": { "type": "read-only-storage" },
|
| 410 |
+
"elementType": "i32"
|
| 411 |
+
},
|
| 412 |
+
{
|
| 413 |
+
"name": "cos_cache",
|
| 414 |
+
"arg": "cosCacheT",
|
| 415 |
+
"semantic": "cos_cache",
|
| 416 |
+
"buffer": { "type": "read-only-storage" },
|
| 417 |
+
"elementType": "$scalar"
|
| 418 |
+
},
|
| 419 |
+
{
|
| 420 |
+
"name": "sin_cache",
|
| 421 |
+
"arg": "sinCacheT",
|
| 422 |
+
"semantic": "sin_cache",
|
| 423 |
+
"buffer": { "type": "read-only-storage" },
|
| 424 |
+
"elementType": "$scalar"
|
| 425 |
+
},
|
| 426 |
+
{
|
| 427 |
+
"name": "params",
|
| 428 |
+
"semantic": "kernel.params",
|
| 429 |
+
"buffer": { "type": "uniform" },
|
| 430 |
+
"struct": {
|
| 431 |
+
"name": "Params",
|
| 432 |
+
"fields": [
|
| 433 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 434 |
+
{ "name": "seqLen", "type": "u32", "value": "seqLen" }
|
| 435 |
+
]
|
| 436 |
+
}
|
| 437 |
+
}
|
| 438 |
+
],
|
| 439 |
+
"qRotary": [
|
| 440 |
+
{
|
| 441 |
+
"name": "query",
|
| 442 |
+
"arg": "queryT",
|
| 443 |
+
"semantic": "query",
|
| 444 |
+
"buffer": { "type": "read-only-storage" },
|
| 445 |
+
"elementType": "$scalar"
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"name": "cos_cache",
|
| 449 |
+
"arg": "cosCacheT",
|
| 450 |
+
"semantic": "cos_cache",
|
| 451 |
+
"buffer": { "type": "read-only-storage" },
|
| 452 |
+
"elementType": "$scalar"
|
| 453 |
+
},
|
| 454 |
+
{
|
| 455 |
+
"name": "sin_cache",
|
| 456 |
+
"arg": "sinCacheT",
|
| 457 |
+
"semantic": "sin_cache",
|
| 458 |
+
"buffer": { "type": "read-only-storage" },
|
| 459 |
+
"elementType": "$scalar"
|
| 460 |
+
},
|
| 461 |
+
{ "name": "q_rotary", "semantic": "QRotary", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 462 |
+
{
|
| 463 |
+
"name": "key_total_sequence_lengths",
|
| 464 |
+
"arg": "keyTotalSequenceLengthsT",
|
| 465 |
+
"semantic": "key_total_sequence_lengths",
|
| 466 |
+
"buffer": { "type": "read-only-storage" },
|
| 467 |
+
"elementType": "i32"
|
| 468 |
+
},
|
| 469 |
+
{
|
| 470 |
+
"name": "total_sequence_length",
|
| 471 |
+
"arg": "totalSequenceLengthT",
|
| 472 |
+
"semantic": "total_sequence_length",
|
| 473 |
+
"buffer": { "type": "read-only-storage" },
|
| 474 |
+
"elementType": "i32"
|
| 475 |
+
},
|
| 476 |
+
{
|
| 477 |
+
"name": "params",
|
| 478 |
+
"semantic": "kernel.params",
|
| 479 |
+
"buffer": { "type": "uniform" },
|
| 480 |
+
"struct": {
|
| 481 |
+
"name": "Params",
|
| 482 |
+
"fields": [
|
| 483 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 484 |
+
{ "name": "seqLen", "type": "u32", "value": "seqLen" }
|
| 485 |
+
]
|
| 486 |
+
}
|
| 487 |
+
}
|
| 488 |
+
],
|
| 489 |
+
"attentionDirect": [
|
| 490 |
+
{
|
| 491 |
+
"name": "query",
|
| 492 |
+
"arg": "queryT",
|
| 493 |
+
"semantic": "query",
|
| 494 |
+
"buffer": { "type": "read-only-storage" },
|
| 495 |
+
"elementType": "$scalar"
|
| 496 |
+
},
|
| 497 |
+
{
|
| 498 |
+
"name": "present_key",
|
| 499 |
+
"arg": "pastKeyT",
|
| 500 |
+
"semantic": "past_key",
|
| 501 |
+
"buffer": { "type": "read-only-storage" },
|
| 502 |
+
"elementType": "$cacheVec"
|
| 503 |
+
},
|
| 504 |
+
{
|
| 505 |
+
"name": "present_value",
|
| 506 |
+
"arg": "pastValueT",
|
| 507 |
+
"semantic": "past_value",
|
| 508 |
+
"buffer": { "type": "read-only-storage" },
|
| 509 |
+
"elementType": "$cacheVec"
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"name": "block_row_indices",
|
| 513 |
+
"arg": "blockRowIndicesT",
|
| 514 |
+
"semantic": "block_row_indices",
|
| 515 |
+
"buffer": { "type": "read-only-storage" },
|
| 516 |
+
"elementType": "i32"
|
| 517 |
+
},
|
| 518 |
+
{
|
| 519 |
+
"name": "block_col_indices",
|
| 520 |
+
"arg": "blockColIndicesT",
|
| 521 |
+
"semantic": "block_col_indices",
|
| 522 |
+
"buffer": { "type": "read-only-storage" },
|
| 523 |
+
"elementType": "i32"
|
| 524 |
+
},
|
| 525 |
+
{
|
| 526 |
+
"name": "key_total_sequence_lengths",
|
| 527 |
+
"arg": "keyTotalSequenceLengthsT",
|
| 528 |
+
"semantic": "key_total_sequence_lengths",
|
| 529 |
+
"buffer": { "type": "read-only-storage" },
|
| 530 |
+
"elementType": "i32"
|
| 531 |
+
},
|
| 532 |
+
{
|
| 533 |
+
"name": "total_sequence_length",
|
| 534 |
+
"arg": "totalSequenceLengthT",
|
| 535 |
+
"semantic": "total_sequence_length",
|
| 536 |
+
"buffer": { "type": "read-only-storage" },
|
| 537 |
+
"elementType": "i32"
|
| 538 |
+
},
|
| 539 |
+
{
|
| 540 |
+
"name": "output",
|
| 541 |
+
"arg": "outputT",
|
| 542 |
+
"semantic": "output",
|
| 543 |
+
"buffer": { "type": "storage" },
|
| 544 |
+
"elementType": "$scalar"
|
| 545 |
+
},
|
| 546 |
+
{
|
| 547 |
+
"name": "params",
|
| 548 |
+
"semantic": "kernel.params",
|
| 549 |
+
"buffer": { "type": "uniform" },
|
| 550 |
+
"struct": {
|
| 551 |
+
"name": "Params",
|
| 552 |
+
"fields": [
|
| 553 |
+
{ "name": "seqLen", "type": "u32", "value": "seqLen" },
|
| 554 |
+
{ "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
|
| 555 |
+
]
|
| 556 |
+
}
|
| 557 |
+
}
|
| 558 |
+
],
|
| 559 |
+
"attentionRotary": [
|
| 560 |
+
{ "name": "q_rotary", "semantic": "QRotary", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
|
| 561 |
+
{
|
| 562 |
+
"name": "present_key",
|
| 563 |
+
"arg": "pastKeyT",
|
| 564 |
+
"semantic": "past_key",
|
| 565 |
+
"buffer": { "type": "read-only-storage" },
|
| 566 |
+
"elementType": "$cacheVec"
|
| 567 |
+
},
|
| 568 |
+
{
|
| 569 |
+
"name": "present_value",
|
| 570 |
+
"arg": "pastValueT",
|
| 571 |
+
"semantic": "past_value",
|
| 572 |
+
"buffer": { "type": "read-only-storage" },
|
| 573 |
+
"elementType": "$cacheVec"
|
| 574 |
+
},
|
| 575 |
+
{
|
| 576 |
+
"name": "block_row_indices",
|
| 577 |
+
"arg": "blockRowIndicesT",
|
| 578 |
+
"semantic": "block_row_indices",
|
| 579 |
+
"buffer": { "type": "read-only-storage" },
|
| 580 |
+
"elementType": "i32"
|
| 581 |
+
},
|
| 582 |
+
{
|
| 583 |
+
"name": "block_col_indices",
|
| 584 |
+
"arg": "blockColIndicesT",
|
| 585 |
+
"semantic": "block_col_indices",
|
| 586 |
+
"buffer": { "type": "read-only-storage" },
|
| 587 |
+
"elementType": "i32"
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
"name": "key_total_sequence_lengths",
|
| 591 |
+
"arg": "keyTotalSequenceLengthsT",
|
| 592 |
+
"semantic": "key_total_sequence_lengths",
|
| 593 |
+
"buffer": { "type": "read-only-storage" },
|
| 594 |
+
"elementType": "i32"
|
| 595 |
+
},
|
| 596 |
+
{
|
| 597 |
+
"name": "total_sequence_length",
|
| 598 |
+
"arg": "totalSequenceLengthT",
|
| 599 |
+
"semantic": "total_sequence_length",
|
| 600 |
+
"buffer": { "type": "read-only-storage" },
|
| 601 |
+
"elementType": "i32"
|
| 602 |
+
},
|
| 603 |
+
{
|
| 604 |
+
"name": "output",
|
| 605 |
+
"arg": "outputT",
|
| 606 |
+
"semantic": "output",
|
| 607 |
+
"buffer": { "type": "storage" },
|
| 608 |
+
"elementType": "$scalar"
|
| 609 |
+
},
|
| 610 |
+
{
|
| 611 |
+
"name": "params",
|
| 612 |
+
"semantic": "kernel.params",
|
| 613 |
+
"buffer": { "type": "uniform" },
|
| 614 |
+
"struct": {
|
| 615 |
+
"name": "Params",
|
| 616 |
+
"fields": [
|
| 617 |
+
{ "name": "seqLen", "type": "u32", "value": "seqLen" },
|
| 618 |
+
{ "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
|
| 619 |
+
]
|
| 620 |
+
}
|
| 621 |
+
}
|
| 622 |
+
]
|
| 623 |
+
},
|
| 624 |
+
"variants": [
|
| 625 |
+
{
|
| 626 |
+
"id": "separate",
|
| 627 |
+
"priority": 0,
|
| 628 |
+
"when": ["separateContract", "geometryOk"],
|
| 629 |
+
"passes": [
|
| 630 |
+
{
|
| 631 |
+
"id": "append",
|
| 632 |
+
"name": "SparseAttention.Append",
|
| 633 |
+
"shader": "sparse-kv-append.wgsl.jinja",
|
| 634 |
+
"bindings": "appendSeparate",
|
| 635 |
+
"constants": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
|
| 636 |
+
"dispatch": {
|
| 637 |
+
"threads": "batchSize * kvNumHeads * seqLen * headSize",
|
| 638 |
+
"workgroupSize": "constants.appendWorkgroupSize"
|
| 639 |
+
}
|
| 640 |
+
},
|
| 641 |
+
{
|
| 642 |
+
"id": "attention",
|
| 643 |
+
"name": "SparseAttention.Attention",
|
| 644 |
+
"shader": "sparse-attention.wgsl.jinja",
|
| 645 |
+
"bindings": "attentionDirect",
|
| 646 |
+
"dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
|
| 647 |
+
"constants": { "qTile": "sparseQueryTile" }
|
| 648 |
+
}
|
| 649 |
+
]
|
| 650 |
+
},
|
| 651 |
+
{
|
| 652 |
+
"id": "separate_rotary",
|
| 653 |
+
"priority": 10,
|
| 654 |
+
"when": ["separateRotaryContract", "geometryOk"],
|
| 655 |
+
"intermediates": [{ "id": "QRotary", "dtype": "float32", "shape": "[qRotaryElements]" }],
|
| 656 |
+
"passes": [
|
| 657 |
+
{
|
| 658 |
+
"id": "append",
|
| 659 |
+
"name": "SparseAttention.Append",
|
| 660 |
+
"shader": "sparse-kv-append.wgsl.jinja",
|
| 661 |
+
"bindings": "appendSeparateRotary",
|
| 662 |
+
"constants": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
|
| 663 |
+
"dispatch": {
|
| 664 |
+
"threads": "batchSize * kvNumHeads * seqLen * headSize",
|
| 665 |
+
"workgroupSize": "constants.appendWorkgroupSize"
|
| 666 |
+
}
|
| 667 |
+
},
|
| 668 |
+
{
|
| 669 |
+
"id": "qrotary",
|
| 670 |
+
"name": "SparseAttention.QueryRotary",
|
| 671 |
+
"shader": "sparse-q-rotary.wgsl.jinja",
|
| 672 |
+
"bindings": "qRotary",
|
| 673 |
+
"dispatch": {
|
| 674 |
+
"threads": "batchSize * numHeads * seqLen * headSize",
|
| 675 |
+
"workgroupSize": "constants.appendWorkgroupSize"
|
| 676 |
+
}
|
| 677 |
+
},
|
| 678 |
+
{
|
| 679 |
+
"id": "attention",
|
| 680 |
+
"name": "SparseAttention.Attention",
|
| 681 |
+
"shader": "sparse-attention.wgsl.jinja",
|
| 682 |
+
"bindings": "attentionRotary",
|
| 683 |
+
"dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
|
| 684 |
+
"constants": { "qTile": "sparseQueryTile" }
|
| 685 |
+
}
|
| 686 |
+
]
|
| 687 |
+
},
|
| 688 |
+
{
|
| 689 |
+
"id": "packed",
|
| 690 |
+
"priority": 0,
|
| 691 |
+
"when": ["packedContract", "geometryOk"],
|
| 692 |
+
"passes": [
|
| 693 |
+
{
|
| 694 |
+
"id": "append",
|
| 695 |
+
"name": "SparseAttention.Append",
|
| 696 |
+
"shader": "sparse-kv-append.wgsl.jinja",
|
| 697 |
+
"bindings": "appendPacked",
|
| 698 |
+
"constants": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
|
| 699 |
+
"dispatch": {
|
| 700 |
+
"threads": "batchSize * kvNumHeads * seqLen * headSize",
|
| 701 |
+
"workgroupSize": "constants.appendWorkgroupSize"
|
| 702 |
+
}
|
| 703 |
+
},
|
| 704 |
+
{
|
| 705 |
+
"id": "attention",
|
| 706 |
+
"name": "SparseAttention.Attention",
|
| 707 |
+
"shader": "sparse-attention.wgsl.jinja",
|
| 708 |
+
"bindings": "attentionDirect",
|
| 709 |
+
"dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
|
| 710 |
+
"constants": { "qTile": "sparseQueryTile" }
|
| 711 |
+
}
|
| 712 |
+
]
|
| 713 |
+
},
|
| 714 |
+
{
|
| 715 |
+
"id": "packed_rotary",
|
| 716 |
+
"priority": 10,
|
| 717 |
+
"when": ["packedRotaryContract", "geometryOk"],
|
| 718 |
+
"intermediates": [{ "id": "QRotary", "dtype": "float32", "shape": "[qRotaryElements]" }],
|
| 719 |
+
"passes": [
|
| 720 |
+
{
|
| 721 |
+
"id": "append",
|
| 722 |
+
"name": "SparseAttention.Append",
|
| 723 |
+
"shader": "sparse-kv-append.wgsl.jinja",
|
| 724 |
+
"bindings": "appendPackedRotary",
|
| 725 |
+
"constants": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
|
| 726 |
+
"dispatch": {
|
| 727 |
+
"threads": "batchSize * kvNumHeads * seqLen * headSize",
|
| 728 |
+
"workgroupSize": "constants.appendWorkgroupSize"
|
| 729 |
+
}
|
| 730 |
+
},
|
| 731 |
+
{
|
| 732 |
+
"id": "qrotary",
|
| 733 |
+
"name": "SparseAttention.QueryRotary",
|
| 734 |
+
"shader": "sparse-q-rotary.wgsl.jinja",
|
| 735 |
+
"bindings": "qRotary",
|
| 736 |
+
"dispatch": {
|
| 737 |
+
"threads": "batchSize * numHeads * seqLen * headSize",
|
| 738 |
+
"workgroupSize": "constants.appendWorkgroupSize"
|
| 739 |
+
}
|
| 740 |
+
},
|
| 741 |
+
{
|
| 742 |
+
"id": "attention",
|
| 743 |
+
"name": "SparseAttention.Attention",
|
| 744 |
+
"shader": "sparse-attention.wgsl.jinja",
|
| 745 |
+
"bindings": "attentionRotary",
|
| 746 |
+
"dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
|
| 747 |
+
"constants": { "qTile": "sparseQueryTile" }
|
| 748 |
+
}
|
| 749 |
+
]
|
| 750 |
+
}
|
| 751 |
+
]
|
| 752 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "com.microsoft.SparseAttention",
|
| 3 |
+
"id": "_com_microsoft_sparseattention_webgpu_07e93c9",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"bench.json": "E0HHOiKzV0TA0/6aDZXLjmxd2z/Q5iYFBFGNYeQoQM0=",
|
| 11 |
+
"manifest.json": "L9ljQObhgAxTXE7AuOhOeG1E7zQwFIDFC+FnIgrU864=",
|
| 12 |
+
"sparse-attention.wgsl.jinja": "zYSxDV01i/wcjq9YJjdWDzRHkAbZA6Ctx7Fi/3xdrF4=",
|
| 13 |
+
"sparse-kv-append.wgsl.jinja": "RavNXJuGESPqe/Jzt5Ek6+844s5q2w/ocyUgGP3cWec=",
|
| 14 |
+
"sparse-q-rotary.wgsl.jinja": "hI7d9cx6RqRO079h4xPBMTMj+qKqIUOKW3pF3vdeWdg=",
|
| 15 |
+
"test.json": "aOQD21sQAgUaJwht0fYeGHtHCmUc7VPdhXjgKLH9YEU="
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
|
| 19 |
+
"webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.SparseAttention" }
|
| 20 |
+
}
|
build/webgpu/sparse-attention.wgsl.jinja
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% macro sparse_schedule() %}
|
| 2 |
+
// How much history precedes this call's tokens. Prompt mode is determined from
|
| 3 |
+
// scalar total_sequence_length rather than per-batch values and forces the past
|
| 4 |
+
// length to zero; a padded prompt row must not be read as if it had history.
|
| 5 |
+
fn past_sequence_length(batch: u32) -> u32 {
|
| 6 |
+
if (u32(total_sequence_length[0]) == params.seqLen) {
|
| 7 |
+
return 0u;
|
| 8 |
+
}
|
| 9 |
+
let total = u32(key_total_sequence_lengths[batch]);
|
| 10 |
+
return select(0u, total - params.seqLen, total >= params.seqLen);
|
| 11 |
+
}
|
| 12 |
+
{%- endmacro %}
|
| 13 |
+
|
| 14 |
+
{% if usesF16 %}
|
| 15 |
+
enable f16;
|
| 16 |
+
{% endif %}
|
| 17 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 18 |
+
|
| 19 |
+
// com.microsoft.SparseAttention, attention pass.
|
| 20 |
+
// One workgroup per (batch, query-token tile, query head). The workgroup sweeps only the key
|
| 21 |
+
// blocks this query's layout row selects, in tiles of WG keys with one key per thread,
|
| 22 |
+
// merging each tile into a running online softmax so the score row is never
|
| 23 |
+
// materialized. Repeated CSR columns are suppressed because the CSR data represents a
|
| 24 |
+
// boolean block mask.
|
| 25 |
+
//
|
| 26 |
+
// The block mask is CSR: row q_abs / SPARSE_BLOCK of layout (head % NUM_LAYOUT) spans
|
| 27 |
+
// block_col_indices[start .. end), and each of those columns names SPARSE_BLOCK
|
| 28 |
+
// contiguous keys. Sweeping (end - start) * SPARSE_BLOCK virtual slots and resolving
|
| 29 |
+
// each through the column list is what makes the kernel cost the SELECTED blocks rather
|
| 30 |
+
// than the whole history -- the reason the operator exists.
|
| 31 |
+
//
|
| 32 |
+
// Causality bounds every visited row at this query's absolute position. A layout
|
| 33 |
+
// with the triangular dense count and a sparse row with row + 1 entries take the
|
| 34 |
+
// dense shortcut without inspecting their column values.
|
| 35 |
+
const Q_HEADS: u32 = {{ numHeads }}u;
|
| 36 |
+
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 37 |
+
const HEAD_DIM: u32 = {{ headSize }}u;
|
| 38 |
+
// The cache is read four elements at a time. The sweep was one narrow dependent load
|
| 39 |
+
// per head dimension per key, and the score phase alone issues HEAD_DIM of them for
|
| 40 |
+
// every key a thread owns; a vec4 view quarters that count. The operator's contract
|
| 41 |
+
// requires head_size to be a multiple of eight, so there is no shape where the
|
| 42 |
+
// quartering does not divide and no scalar form to fall back to.
|
| 43 |
+
const HEAD_VEC: u32 = {{ headVec }}u;
|
| 44 |
+
const MAX_CACHE_SEQ: u32 = {{ maxCacheSeq }}u;
|
| 45 |
+
const SPARSE_BLOCK: u32 = {{ sparseBlockSize }}u;
|
| 46 |
+
const NUM_LAYOUT: u32 = {{ numLayout }}u;
|
| 47 |
+
const ROW_STRIDE: u32 = {{ maxBlocks + 1 }}u;
|
| 48 |
+
const COL_STRIDE: u32 = {{ maxNnz }}u;
|
| 49 |
+
const Q_HIDDEN: u32 = {{ numHeads * headSize }}u;
|
| 50 |
+
{% if not usesRotary %}
|
| 51 |
+
const Q_STRIDE: u32 = {{ packedStride if packedQkv else numHeads * headSize }}u;
|
| 52 |
+
{% endif %}
|
| 53 |
+
const WG: u32 = {{ attnWorkgroup }}u;
|
| 54 |
+
const Q_TILE: u32 = {{ qTile }}u;
|
| 55 |
+
|
| 56 |
+
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 57 |
+
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 58 |
+
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
| 59 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 60 |
+
// here changes +inf-row behavior.
|
| 61 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 62 |
+
|
| 63 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 64 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 68 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 69 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 70 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 71 |
+
fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
| 72 |
+
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 73 |
+
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 74 |
+
}
|
| 75 |
+
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 76 |
+
return exp(shifted_value(value, maxValue));
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
var<workgroup> q_shared: array<vec4<f32>, Q_TILE * HEAD_VEC>;
|
| 80 |
+
var<workgroup> running_out: array<vec4<f32>, Q_TILE * HEAD_VEC>;
|
| 81 |
+
var<workgroup> probs: array<f32, Q_TILE * WG>;
|
| 82 |
+
// One resolved cache row base per key of the current tile, so the value accumulation
|
| 83 |
+
// re-reads a base instead of re-walking the column list per head dimension.
|
| 84 |
+
var<workgroup> key_rows: array<u32, WG>;
|
| 85 |
+
|
| 86 |
+
{% set mdStreams = qTile %}
|
| 87 |
+
// Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
|
| 88 |
+
// mNew = max(m1, m2)
|
| 89 |
+
// dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
|
| 90 |
+
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 91 |
+
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 92 |
+
// calls before their shared partial storage is reused.
|
| 93 |
+
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 94 |
+
{% if combineSubgroups %}
|
| 95 |
+
// Per-subgroup partials are published into a deterministic slot: the subgroup's
|
| 96 |
+
// ordinal index within the workgroup (lidx / sgSize). The online (m, d) merge
|
| 97 |
+
// is not float-associative, so thread 0 must fold partials in a fixed order.
|
| 98 |
+
// Subgroups partition a workgroup into contiguous ordinal ranges on supported
|
| 99 |
+
// backends, so the ordinal slot is unique per subgroup and every slot in
|
| 100 |
+
// [0, subgroupCount) is written (each subgroup elects one leader).
|
| 101 |
+
// Sized for the worst case of one partial per invocation.
|
| 102 |
+
var<workgroup> partialM: array<f32, WG>;
|
| 103 |
+
var<workgroup> partialD: array<f32, WG>;
|
| 104 |
+
var<workgroup> combinedMD: vec2<f32>;
|
| 105 |
+
|
| 106 |
+
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 107 |
+
// it (no barriers, no shared state); otherwise subgroup leaders publish
|
| 108 |
+
// partials through shared memory and thread 0 folds them in ordinal order.
|
| 109 |
+
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 110 |
+
let sgM = subgroupMax(m);
|
| 111 |
+
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
| 112 |
+
// element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
|
| 113 |
+
// landed in d via exp(NaN); both survive the merge and are detected by the
|
| 114 |
+
// code after the reduction.
|
| 115 |
+
let sgD = subgroupAdd(d * exp_shift(m, sgM));
|
| 116 |
+
if (sgSize == WG) {
|
| 117 |
+
return vec2<f32>(sgM, sgD);
|
| 118 |
+
}
|
| 119 |
+
let subgroupCount = (WG + sgSize - 1u) / sgSize;
|
| 120 |
+
// Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
|
| 121 |
+
// fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
|
| 122 |
+
// subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
|
| 123 |
+
// flash-attention loop that re-uses this shared memory each iteration — would
|
| 124 |
+
// otherwise read stale shared memory. Identity makes such a slot a no-op.
|
| 125 |
+
// (max identity = -FLT_MAX, denom identity = 0.)
|
| 126 |
+
if (lidx < subgroupCount) {
|
| 127 |
+
partialM[lidx] = -FLT_MAX;
|
| 128 |
+
partialD[lidx] = 0.0;
|
| 129 |
+
}
|
| 130 |
+
workgroupBarrier();
|
| 131 |
+
if (subgroupElect()) {
|
| 132 |
+
let slot = lidx / sgSize;
|
| 133 |
+
partialM[slot] = sgM;
|
| 134 |
+
partialD[slot] = sgD;
|
| 135 |
+
}
|
| 136 |
+
workgroupBarrier();
|
| 137 |
+
if (lidx == 0u) {
|
| 138 |
+
var accM = -FLT_MAX;
|
| 139 |
+
var accD = 0.0;
|
| 140 |
+
for (var i = 0u; i < subgroupCount; i = i + 1u) {
|
| 141 |
+
let mNew = max(accM, partialM[i]);
|
| 142 |
+
accD = accD * exp_shift(accM, mNew) + partialD[i] * exp_shift(partialM[i], mNew);
|
| 143 |
+
accM = mNew;
|
| 144 |
+
}
|
| 145 |
+
combinedMD = vec2<f32>(accM, accD);
|
| 146 |
+
}
|
| 147 |
+
workgroupBarrier();
|
| 148 |
+
return combinedMD;
|
| 149 |
+
}
|
| 150 |
+
{% else %}
|
| 151 |
+
{% set mdStreamed = mdStreams is defined %}
|
| 152 |
+
{% set mdStreams = mdStreams if mdStreams is defined else 1 %}
|
| 153 |
+
{% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
|
| 154 |
+
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 155 |
+
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 156 |
+
{% if mdStreamed %}
|
| 157 |
+
|
| 158 |
+
// In-place fold of {{ mdStreams }} streams. The caller stores its per-thread
|
| 159 |
+
// partials into partialM/partialD first and reads the merged pair of stream s
|
| 160 |
+
// from slot s * WG afterwards.
|
| 161 |
+
fn combine_partials_streams(lidx: u32) {
|
| 162 |
+
workgroupBarrier();
|
| 163 |
+
var stride = WG / 2u;
|
| 164 |
+
loop {
|
| 165 |
+
if (stride == 0u) {
|
| 166 |
+
break;
|
| 167 |
+
}
|
| 168 |
+
if (lidx < stride) {
|
| 169 |
+
{% for s in range(mdStreams) %}
|
| 170 |
+
{
|
| 171 |
+
let slot = {{ s }}u * WG + lidx;
|
| 172 |
+
let m1 = partialM[slot];
|
| 173 |
+
let d1 = partialD[slot];
|
| 174 |
+
let m2 = partialM[slot + stride];
|
| 175 |
+
let d2 = partialD[slot + stride];
|
| 176 |
+
let mNew = max(m1, m2);
|
| 177 |
+
partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 178 |
+
partialM[slot] = mNew;
|
| 179 |
+
}
|
| 180 |
+
{% endfor %}
|
| 181 |
+
}
|
| 182 |
+
workgroupBarrier();
|
| 183 |
+
stride = stride / 2u;
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
{% else %}
|
| 187 |
+
|
| 188 |
+
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 189 |
+
partialM[lidx] = m;
|
| 190 |
+
partialD[lidx] = d;
|
| 191 |
+
workgroupBarrier();
|
| 192 |
+
var stride = WG / 2u;
|
| 193 |
+
loop {
|
| 194 |
+
if (stride == 0u) {
|
| 195 |
+
break;
|
| 196 |
+
}
|
| 197 |
+
if (lidx < stride) {
|
| 198 |
+
let m1 = partialM[lidx];
|
| 199 |
+
let d1 = partialD[lidx];
|
| 200 |
+
let m2 = partialM[lidx + stride];
|
| 201 |
+
let d2 = partialD[lidx + stride];
|
| 202 |
+
let mNew = max(m1, m2);
|
| 203 |
+
partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 204 |
+
partialM[lidx] = mNew;
|
| 205 |
+
}
|
| 206 |
+
workgroupBarrier();
|
| 207 |
+
stride = stride / 2u;
|
| 208 |
+
}
|
| 209 |
+
let merged = vec2<f32>(partialM[0], partialD[0]);
|
| 210 |
+
// Trailing barrier so back-to-back calls cannot race a next call's partial
|
| 211 |
+
// stores against this call's reads of slot 0.
|
| 212 |
+
workgroupBarrier();
|
| 213 |
+
return merged;
|
| 214 |
+
}
|
| 215 |
+
{% endif %}
|
| 216 |
+
{% endif %}
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 220 |
+
fn scale_value() -> f32 {
|
| 221 |
+
if (params.scale != 0.0) { return params.scale; }
|
| 222 |
+
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
{{ sparse_schedule() }}
|
| 227 |
+
|
| 228 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 229 |
+
fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
| 230 |
+
let tile0 = wg.x * Q_TILE;
|
| 231 |
+
let head = wg.y % Q_HEADS;
|
| 232 |
+
let batch = wg.y / Q_HEADS;
|
| 233 |
+
let tid = lid.x;
|
| 234 |
+
|
| 235 |
+
let past = past_sequence_length(batch);
|
| 236 |
+
let kv_head = head / (Q_HEADS / KV_HEADS);
|
| 237 |
+
let scale = scale_value();
|
| 238 |
+
|
| 239 |
+
// Layouts cycle over the heads: four layouts and eight heads give S0 S1 S2 S3 S0 ...
|
| 240 |
+
let layout_id = head % NUM_LAYOUT;
|
| 241 |
+
let row_base = layout_id * ROW_STRIDE;
|
| 242 |
+
// A layout at or above the triangular dense-block count is treated as dense.
|
| 243 |
+
let dense_nnz = ROW_STRIDE * (ROW_STRIDE - 1u) / 2u;
|
| 244 |
+
let layout_is_dense = u32(block_row_indices[row_base + ROW_STRIDE - 1u]) >= dense_nnz;
|
| 245 |
+
|
| 246 |
+
{% for j in range(qTile) %}
|
| 247 |
+
// Keys 0 .. q_abs are visible; the +1 makes the bound exclusive. A tile that runs past
|
| 248 |
+
// the last token carries inactive queries; they take no mask row and store nothing.
|
| 249 |
+
let live_{{ j }} = tile0 + {{ j }}u < params.seqLen;
|
| 250 |
+
let q_abs_{{ j }} = past + tile0 + {{ j }}u;
|
| 251 |
+
let key_bound_{{ j }} = q_abs_{{ j }} + 1u;
|
| 252 |
+
let mask_row_{{ j }} = q_abs_{{ j }} / SPARSE_BLOCK;
|
| 253 |
+
{% endfor %}
|
| 254 |
+
|
| 255 |
+
{% for j in range(qTile) %}
|
| 256 |
+
{
|
| 257 |
+
{% if usesRotary %}
|
| 258 |
+
let q_base = ((batch * Q_HEADS + head) * params.seqLen + min(tile0 + {{ j }}u, params.seqLen - 1u)) * HEAD_DIM;
|
| 259 |
+
{% else %}
|
| 260 |
+
let q_base = (batch * params.seqLen + min(tile0 + {{ j }}u, params.seqLen - 1u)) * Q_STRIDE + head * HEAD_DIM;
|
| 261 |
+
{% endif %}
|
| 262 |
+
for (var dv: u32 = tid; dv < HEAD_VEC; dv = dv + WG) {
|
| 263 |
+
let qb = q_base + dv * 4u;
|
| 264 |
+
{% if usesRotary %}
|
| 265 |
+
q_shared[{{ j }}u * HEAD_VEC + dv] = vec4<f32>(q_rotary[qb], q_rotary[qb + 1u], q_rotary[qb + 2u], q_rotary[qb + 3u]);
|
| 266 |
+
{% else %}
|
| 267 |
+
q_shared[{{ j }}u * HEAD_VEC + dv] = vec4<f32>(f32(query[qb]), f32(query[qb + 1u]), f32(query[qb + 2u]), f32(query[qb + 3u]));
|
| 268 |
+
{% endif %}
|
| 269 |
+
running_out[{{ j }}u * HEAD_VEC + dv] = vec4<f32>(0.0);
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
{% endfor %}
|
| 273 |
+
workgroupBarrier();
|
| 274 |
+
|
| 275 |
+
{% for j in range(qTile) %}
|
| 276 |
+
var runningMax_{{ j }} = -FLT_MAX;
|
| 277 |
+
var runningDenom_{{ j }} = 0.0;
|
| 278 |
+
{% endfor %}
|
| 279 |
+
|
| 280 |
+
// Consecutive tokens span at most two mask rows, and every query of a row selects the
|
| 281 |
+
// same blocks, so one sweep per row covers the tile. A query contributes only to the
|
| 282 |
+
// sweep of its own row, which is why its online state is never merged across rows.
|
| 283 |
+
{% if qTile > 1 %}
|
| 284 |
+
let row_first = q_abs_0 / SPARSE_BLOCK;
|
| 285 |
+
let row_last = mask_row_{{ qTile - 1 }};
|
| 286 |
+
for (var mask_row = row_first; mask_row <= row_last; mask_row = mask_row + 1u) {
|
| 287 |
+
{% else %}
|
| 288 |
+
{
|
| 289 |
+
let mask_row = mask_row_0;
|
| 290 |
+
{% endif %}
|
| 291 |
+
let start = u32(block_row_indices[row_base + mask_row]);
|
| 292 |
+
let end = u32(block_row_indices[row_base + mask_row + 1u]);
|
| 293 |
+
// A row listing every causal block is also dense, so its column values are ignored.
|
| 294 |
+
let dense_row = layout_is_dense || (end - start == mask_row + 1u);
|
| 295 |
+
// The dense sweep runs to the furthest causal bound of the queries on this row; each
|
| 296 |
+
// query still drops the keys past its own.
|
| 297 |
+
var bound_max = 0u;
|
| 298 |
+
{% for j in range(qTile) %}
|
| 299 |
+
if (live_{{ j }} && mask_row_{{ j }} == mask_row) { bound_max = max(bound_max, key_bound_{{ j }}); }
|
| 300 |
+
{% endfor %}
|
| 301 |
+
let slot_count = select((end - start) * SPARSE_BLOCK, bound_max, dense_row);
|
| 302 |
+
|
| 303 |
+
var tileBase: u32 = 0u;
|
| 304 |
+
loop {
|
| 305 |
+
if (tileBase >= slot_count) {
|
| 306 |
+
break;
|
| 307 |
+
}
|
| 308 |
+
let slot = tileBase + tid;
|
| 309 |
+
|
| 310 |
+
// Resolving the slot is query-independent: the block list belongs to the row, and
|
| 311 |
+
// only the causal bound below differs between the queries sharing it.
|
| 312 |
+
var key = 0u;
|
| 313 |
+
var keyResolved = false;
|
| 314 |
+
if (slot < slot_count) {
|
| 315 |
+
if (!dense_row) {
|
| 316 |
+
// CSR is a boolean mask. Repeating a column therefore cannot repeat its
|
| 317 |
+
// probability mass, even though malformed-but-valid CSR may contain duplicates.
|
| 318 |
+
let entry = slot / SPARSE_BLOCK;
|
| 319 |
+
let block = u32(block_col_indices[layout_id * COL_STRIDE + start + entry]);
|
| 320 |
+
var duplicate = false;
|
| 321 |
+
for (var previous = 0u; previous < entry; previous = previous + 1u) {
|
| 322 |
+
duplicate = duplicate ||
|
| 323 |
+
u32(block_col_indices[layout_id * COL_STRIDE + start + previous]) == block;
|
| 324 |
+
}
|
| 325 |
+
key = block * SPARSE_BLOCK + slot % SPARSE_BLOCK;
|
| 326 |
+
keyResolved = !duplicate;
|
| 327 |
+
} else {
|
| 328 |
+
key = slot;
|
| 329 |
+
keyResolved = true;
|
| 330 |
+
}
|
| 331 |
+
}
|
| 332 |
+
// Always a row this cache holds, so an unresolved slot leaves a readable base rather
|
| 333 |
+
// than a stale or uninitialized one and the accumulation below needs no guard.
|
| 334 |
+
let base = ((batch * KV_HEADS + kv_head) * MAX_CACHE_SEQ + select(0u, key, keyResolved)) * HEAD_DIM;
|
| 335 |
+
key_rows[tid] = base;
|
| 336 |
+
|
| 337 |
+
{% for j in range(qTile) %}
|
| 338 |
+
let allowed_{{ j }} = keyResolved && live_{{ j }} && mask_row_{{ j }} == mask_row && key < key_bound_{{ j }};
|
| 339 |
+
var acc_{{ j }} = 0.0;
|
| 340 |
+
{% endfor %}
|
| 341 |
+
if (keyResolved) {
|
| 342 |
+
// One key vector, every query's score: this is the reuse the tile exists for.
|
| 343 |
+
let row_v = base / 4u;
|
| 344 |
+
for (var dv: u32 = 0u; dv < HEAD_VEC; dv = dv + 1u) {
|
| 345 |
+
let kv = vec4<f32>(present_key[row_v + dv]);
|
| 346 |
+
{% for j in range(qTile) %}
|
| 347 |
+
acc_{{ j }} = acc_{{ j }} + dot(q_shared[{{ j }}u * HEAD_VEC + dv], kv);
|
| 348 |
+
{% endfor %}
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
// One barrier tree folds all Q_TILE online-softmax partials.
|
| 353 |
+
{% for j in range(qTile) %}
|
| 354 |
+
let score_{{ j }} = acc_{{ j }} * scale;
|
| 355 |
+
partialM[{{ j }}u * WG + tid] = select(-FLT_MAX, score_{{ j }}, allowed_{{ j }});
|
| 356 |
+
partialD[{{ j }}u * WG + tid] = select(0.0, 1.0, allowed_{{ j }});
|
| 357 |
+
{% endfor %}
|
| 358 |
+
combine_partials_streams(tid);
|
| 359 |
+
{% for j in range(qTile) %}
|
| 360 |
+
let tile_{{ j }} = vec2<f32>(partialM[{{ j }}u * WG], partialD[{{ j }}u * WG]);
|
| 361 |
+
{% endfor %}
|
| 362 |
+
|
| 363 |
+
{% for j in range(qTile) %}
|
| 364 |
+
let newMax_{{ j }} = max(runningMax_{{ j }}, tile_{{ j }}.x);
|
| 365 |
+
let correction_{{ j }} = exp_shift(runningMax_{{ j }}, newMax_{{ j }});
|
| 366 |
+
runningDenom_{{ j }} = runningDenom_{{ j }} * correction_{{ j }}
|
| 367 |
+
+ tile_{{ j }}.y * exp_shift(tile_{{ j }}.x, newMax_{{ j }});
|
| 368 |
+
runningMax_{{ j }} = newMax_{{ j }};
|
| 369 |
+
probs[{{ j }}u * WG + tid] = select(0.0, exp_shift(score_{{ j }}, newMax_{{ j }}), allowed_{{ j }});
|
| 370 |
+
{% endfor %}
|
| 371 |
+
workgroupBarrier();
|
| 372 |
+
|
| 373 |
+
// running_out[j][d] is owned by the same thread across every tile (tid = d mod WG),
|
| 374 |
+
// so this rescale-and-accumulate needs no further synchronization. One value vector
|
| 375 |
+
// serves every query, which is the other half of the tile's reuse; a key outside a
|
| 376 |
+
// query's causal bound carries prob 0 and is multiplied away.
|
| 377 |
+
let tileCount = min(WG, slot_count - tileBase);
|
| 378 |
+
for (var dv: u32 = tid; dv < HEAD_VEC; dv = dv + WG) {
|
| 379 |
+
{% for j in range(qTile) %}
|
| 380 |
+
var vSum_{{ j }} = vec4<f32>(0.0);
|
| 381 |
+
{% endfor %}
|
| 382 |
+
for (var i: u32 = 0u; i < tileCount; i = i + 1u) {
|
| 383 |
+
let vv = vec4<f32>(present_value[key_rows[i] / 4u + dv]);
|
| 384 |
+
{% for j in range(qTile) %}
|
| 385 |
+
vSum_{{ j }} = vSum_{{ j }} + probs[{{ j }}u * WG + i] * vv;
|
| 386 |
+
{% endfor %}
|
| 387 |
+
}
|
| 388 |
+
{% for j in range(qTile) %}
|
| 389 |
+
running_out[{{ j }}u * HEAD_VEC + dv] = running_out[{{ j }}u * HEAD_VEC + dv] * correction_{{ j }} + vSum_{{ j }};
|
| 390 |
+
{% endfor %}
|
| 391 |
+
}
|
| 392 |
+
// Orders this tile's probs and key_rows reads before the next tile writes them.
|
| 393 |
+
workgroupBarrier();
|
| 394 |
+
|
| 395 |
+
tileBase = tileBase + WG;
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
// An all-masked row becomes a uniform average over causal history rather than zero.
|
| 400 |
+
{% for j in range(qTile) %}
|
| 401 |
+
if (live_{{ j }}) {
|
| 402 |
+
let hasMass_{{ j }} = runningDenom_{{ j }} > 0.0;
|
| 403 |
+
let invDenom_{{ j }} = select(0.0, 1.0 / runningDenom_{{ j }}, hasMass_{{ j }});
|
| 404 |
+
let out_base_{{ j }} = (batch * params.seqLen + tile0 + {{ j }}u) * Q_HIDDEN + head * HEAD_DIM;
|
| 405 |
+
for (var dv: u32 = tid; dv < HEAD_VEC; dv = dv + WG) {
|
| 406 |
+
var out_v: vec4<f32>;
|
| 407 |
+
if (hasMass_{{ j }}) {
|
| 408 |
+
out_v = running_out[{{ j }}u * HEAD_VEC + dv] * invDenom_{{ j }};
|
| 409 |
+
} else {
|
| 410 |
+
var total = vec4<f32>(0.0);
|
| 411 |
+
for (var key = 0u; key < key_bound_{{ j }}; key = key + 1u) {
|
| 412 |
+
let row_v = (((batch * KV_HEADS + kv_head) * MAX_CACHE_SEQ + key) * HEAD_DIM) / 4u;
|
| 413 |
+
total = total + vec4<f32>(present_value[row_v + dv]);
|
| 414 |
+
}
|
| 415 |
+
out_v = total / f32(key_bound_{{ j }});
|
| 416 |
+
}
|
| 417 |
+
let ob = out_base_{{ j }} + dv * 4u;
|
| 418 |
+
output[ob] = {{ scalar }}(out_v.x);
|
| 419 |
+
output[ob + 1u] = {{ scalar }}(out_v.y);
|
| 420 |
+
output[ob + 2u] = {{ scalar }}(out_v.z);
|
| 421 |
+
output[ob + 3u] = {{ scalar }}(out_v.w);
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
{% endfor %}
|
| 425 |
+
}
|
build/webgpu/sparse-kv-append.wgsl.jinja
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% macro sparse_schedule() %}
|
| 2 |
+
// How much history precedes this call's tokens. Prompt mode is determined from
|
| 3 |
+
// scalar total_sequence_length rather than per-batch values and forces the past
|
| 4 |
+
// length to zero; a padded prompt row must not be read as if it had history.
|
| 5 |
+
fn past_sequence_length(batch: u32) -> u32 {
|
| 6 |
+
if (u32(total_sequence_length[0]) == params.seqLen) {
|
| 7 |
+
return 0u;
|
| 8 |
+
}
|
| 9 |
+
let total = u32(key_total_sequence_lengths[batch]);
|
| 10 |
+
return select(0u, total - params.seqLen, total >= params.seqLen);
|
| 11 |
+
}
|
| 12 |
+
{%- endmacro %}
|
| 13 |
+
{% macro sparse_rotary(interleaved) %}
|
| 14 |
+
// Which cos/sin entry a component uses, and which member of its rotation pair it is.
|
| 15 |
+
// The two layouts differ only here: the NeoX split pairs d with d + ROTARY_HALF, and the
|
| 16 |
+
// interleaved layout pairs the even lane with the odd one beside it.
|
| 17 |
+
fn rotary_slot(d: u32) -> u32 {
|
| 18 |
+
{% if interleaved %}
|
| 19 |
+
return d / 2u;
|
| 20 |
+
{% else %}
|
| 21 |
+
return select(d - ROTARY_HALF, d, d < ROTARY_HALF);
|
| 22 |
+
{% endif %}
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
fn rotary_partner(d: u32) -> u32 {
|
| 26 |
+
{% if interleaved %}
|
| 27 |
+
return select(d - 1u, d + 1u, (d % 2u) == 0u);
|
| 28 |
+
{% else %}
|
| 29 |
+
return select(d - ROTARY_HALF, d + ROTARY_HALF, d < ROTARY_HALF);
|
| 30 |
+
{% endif %}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
fn rotary_is_first(d: u32) -> bool {
|
| 34 |
+
{% if interleaved %}
|
| 35 |
+
return (d % 2u) == 0u;
|
| 36 |
+
{% else %}
|
| 37 |
+
return d < ROTARY_HALF;
|
| 38 |
+
{% endif %}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// One component of the rotation, written from the component's own point of view so a
|
| 42 |
+
// single expression covers both members of the pair: the leading one subtracts its
|
| 43 |
+
// partner's sine term, the trailing one adds it.
|
| 44 |
+
fn rotary_value(own: f32, partner: f32, cs: f32, sn: f32, first: bool) -> f32 {
|
| 45 |
+
return select(own * cs + partner * sn, own * cs - partner * sn, first);
|
| 46 |
+
}
|
| 47 |
+
{%- endmacro %}
|
| 48 |
+
|
| 49 |
+
{% if usesF16 %}
|
| 50 |
+
enable f16;
|
| 51 |
+
{% endif %}
|
| 52 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 53 |
+
|
| 54 |
+
// com.microsoft.SparseAttention, KV append pass.
|
| 55 |
+
// past_key and present_key are the same allocation, so this writes only the new rows:
|
| 56 |
+
// one invocation per (batch, kv head, new token, component) of the BNSH cache, landing
|
| 57 |
+
// at absolute position past + t. Nothing rewrites the retained history.
|
| 58 |
+
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 59 |
+
const HEAD_DIM: u32 = {{ headSize }}u;
|
| 60 |
+
const MAX_CACHE_SEQ: u32 = {{ maxCacheSeq }}u;
|
| 61 |
+
{% if packedQkv %}
|
| 62 |
+
// Packed layout: query carries [Q | K | V] on one row, so K starts after the query
|
| 63 |
+
// heads and V after the key heads.
|
| 64 |
+
const Q_STRIDE: u32 = {{ packedStride }}u;
|
| 65 |
+
const PACKED_K_OFFSET: u32 = {{ numHeads * headSize }}u;
|
| 66 |
+
const PACKED_V_OFFSET: u32 = {{ (numHeads + kvNumHeads) * headSize }}u;
|
| 67 |
+
{% else %}
|
| 68 |
+
const KV_HIDDEN: u32 = {{ kvNumHeads * headSize }}u;
|
| 69 |
+
{% endif %}
|
| 70 |
+
const WG: u32 = {{ appendWorkgroupSize }}u;
|
| 71 |
+
{% if usesRotary %}
|
| 72 |
+
const ROTARY_HALF: u32 = {{ rotaryHalf }}u;
|
| 73 |
+
const ROTARY_DIM: u32 = {{ rotaryDim }}u;
|
| 74 |
+
{% endif %}
|
| 75 |
+
|
| 76 |
+
{{ sparse_schedule() }}
|
| 77 |
+
{% if usesRotary %}
|
| 78 |
+
|
| 79 |
+
{{ sparse_rotary(rotaryInterleaved) }}
|
| 80 |
+
{% endif %}
|
| 81 |
+
|
| 82 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 83 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>,
|
| 84 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 85 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 86 |
+
// maxComputeWorkgroupsPerDimension limit. Reduces to gid.x when nwg.y == 1.
|
| 87 |
+
let index = gid.x + gid.y * nwg.x * WG;
|
| 88 |
+
let count = params.batchSize * KV_HEADS * params.seqLen * HEAD_DIM;
|
| 89 |
+
if (index >= count) {
|
| 90 |
+
return;
|
| 91 |
+
}
|
| 92 |
+
let d = index % HEAD_DIM;
|
| 93 |
+
let t = (index / HEAD_DIM) % params.seqLen;
|
| 94 |
+
let kv_head = (index / (HEAD_DIM * params.seqLen)) % KV_HEADS;
|
| 95 |
+
let batch = index / (HEAD_DIM * params.seqLen * KV_HEADS);
|
| 96 |
+
|
| 97 |
+
let position = past_sequence_length(batch) + t;
|
| 98 |
+
let dst = ((batch * KV_HEADS + kv_head) * MAX_CACHE_SEQ + position) * HEAD_DIM + d;
|
| 99 |
+
{% if packedQkv %}
|
| 100 |
+
let row = (batch * params.seqLen + t) * Q_STRIDE;
|
| 101 |
+
let k_base = row + PACKED_K_OFFSET + kv_head * HEAD_DIM;
|
| 102 |
+
let v_base = row + PACKED_V_OFFSET + kv_head * HEAD_DIM;
|
| 103 |
+
{% else %}
|
| 104 |
+
let k_base = ((batch * params.seqLen + t) * KV_HIDDEN) + kv_head * HEAD_DIM;
|
| 105 |
+
let v_base = k_base;
|
| 106 |
+
{% endif %}
|
| 107 |
+
|
| 108 |
+
{% if usesRotary %}
|
| 109 |
+
// The key is rotated at its own absolute position before it enters the cache, so the
|
| 110 |
+
// cache holds post-rotary keys and the attention pass never rotates again.
|
| 111 |
+
if (d < ROTARY_DIM) {
|
| 112 |
+
let slot = rotary_slot(d);
|
| 113 |
+
let cs = f32(cos_cache[position * ROTARY_HALF + slot]);
|
| 114 |
+
let sn = f32(sin_cache[position * ROTARY_HALF + slot]);
|
| 115 |
+
let own = f32({{ kvSource }}[k_base + d]);
|
| 116 |
+
let partner = f32({{ kvSource }}[k_base + rotary_partner(d)]);
|
| 117 |
+
present_key[dst] = {{ scalar }}(rotary_value(own, partner, cs, sn, rotary_is_first(d)));
|
| 118 |
+
} else {
|
| 119 |
+
present_key[dst] = {{ scalar }}({{ kvSource }}[k_base + d]);
|
| 120 |
+
}
|
| 121 |
+
{% else %}
|
| 122 |
+
present_key[dst] = {{ scalar }}({{ kvSource }}[k_base + d]);
|
| 123 |
+
{% endif %}
|
| 124 |
+
present_value[dst] = {{ scalar }}({{ vSource }}[v_base + d]);
|
| 125 |
+
}
|
build/webgpu/sparse-q-rotary.wgsl.jinja
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% macro sparse_schedule() %}
|
| 2 |
+
// How much history precedes this call's tokens. Prompt mode is determined from
|
| 3 |
+
// scalar total_sequence_length rather than per-batch values and forces the past
|
| 4 |
+
// length to zero; a padded prompt row must not be read as if it had history.
|
| 5 |
+
fn past_sequence_length(batch: u32) -> u32 {
|
| 6 |
+
if (u32(total_sequence_length[0]) == params.seqLen) {
|
| 7 |
+
return 0u;
|
| 8 |
+
}
|
| 9 |
+
let total = u32(key_total_sequence_lengths[batch]);
|
| 10 |
+
return select(0u, total - params.seqLen, total >= params.seqLen);
|
| 11 |
+
}
|
| 12 |
+
{%- endmacro %}
|
| 13 |
+
{% macro sparse_rotary(interleaved) %}
|
| 14 |
+
// Which cos/sin entry a component uses, and which member of its rotation pair it is.
|
| 15 |
+
// The two layouts differ only here: the NeoX split pairs d with d + ROTARY_HALF, and the
|
| 16 |
+
// interleaved layout pairs the even lane with the odd one beside it.
|
| 17 |
+
fn rotary_slot(d: u32) -> u32 {
|
| 18 |
+
{% if interleaved %}
|
| 19 |
+
return d / 2u;
|
| 20 |
+
{% else %}
|
| 21 |
+
return select(d - ROTARY_HALF, d, d < ROTARY_HALF);
|
| 22 |
+
{% endif %}
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
fn rotary_partner(d: u32) -> u32 {
|
| 26 |
+
{% if interleaved %}
|
| 27 |
+
return select(d - 1u, d + 1u, (d % 2u) == 0u);
|
| 28 |
+
{% else %}
|
| 29 |
+
return select(d - ROTARY_HALF, d + ROTARY_HALF, d < ROTARY_HALF);
|
| 30 |
+
{% endif %}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
fn rotary_is_first(d: u32) -> bool {
|
| 34 |
+
{% if interleaved %}
|
| 35 |
+
return (d % 2u) == 0u;
|
| 36 |
+
{% else %}
|
| 37 |
+
return d < ROTARY_HALF;
|
| 38 |
+
{% endif %}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// One component of the rotation, written from the component's own point of view so a
|
| 42 |
+
// single expression covers both members of the pair: the leading one subtracts its
|
| 43 |
+
// partner's sine term, the trailing one adds it.
|
| 44 |
+
fn rotary_value(own: f32, partner: f32, cs: f32, sn: f32, first: bool) -> f32 {
|
| 45 |
+
return select(own * cs + partner * sn, own * cs - partner * sn, first);
|
| 46 |
+
}
|
| 47 |
+
{%- endmacro %}
|
| 48 |
+
|
| 49 |
+
{% if usesF16 %}
|
| 50 |
+
enable f16;
|
| 51 |
+
{% endif %}
|
| 52 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 53 |
+
|
| 54 |
+
// com.microsoft.SparseAttention, query rotary pass.
|
| 55 |
+
// Rotates Q into a (batch, heads, seq, dim) scratch buffer so the attention pass binds
|
| 56 |
+
// one query tensor instead of the query plus both caches. That keeps the attention pass
|
| 57 |
+
// at the eight storage buffers WebGPU guarantees; folding the rotation in would need
|
| 58 |
+
// ten.
|
| 59 |
+
const Q_HEADS: u32 = {{ numHeads }}u;
|
| 60 |
+
const HEAD_DIM: u32 = {{ headSize }}u;
|
| 61 |
+
const ROTARY_HALF: u32 = {{ rotaryHalf }}u;
|
| 62 |
+
const ROTARY_DIM: u32 = {{ rotaryDim }}u;
|
| 63 |
+
const Q_STRIDE: u32 = {{ packedStride if packedQkv else numHeads * headSize }}u;
|
| 64 |
+
const WG: u32 = {{ appendWorkgroupSize }}u;
|
| 65 |
+
|
| 66 |
+
{{ sparse_schedule() }}
|
| 67 |
+
|
| 68 |
+
{{ sparse_rotary(rotaryInterleaved) }}
|
| 69 |
+
|
| 70 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 71 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>,
|
| 72 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 73 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 74 |
+
// maxComputeWorkgroupsPerDimension limit. Reduces to gid.x when nwg.y == 1.
|
| 75 |
+
let index = gid.x + gid.y * nwg.x * WG;
|
| 76 |
+
let count = params.batchSize * Q_HEADS * params.seqLen * HEAD_DIM;
|
| 77 |
+
if (index >= count) {
|
| 78 |
+
return;
|
| 79 |
+
}
|
| 80 |
+
let d = index % HEAD_DIM;
|
| 81 |
+
let t = (index / HEAD_DIM) % params.seqLen;
|
| 82 |
+
let head = (index / (HEAD_DIM * params.seqLen)) % Q_HEADS;
|
| 83 |
+
let batch = index / (HEAD_DIM * params.seqLen * Q_HEADS);
|
| 84 |
+
|
| 85 |
+
// A query token's rotary position is the same absolute position the attention pass
|
| 86 |
+
// uses for causality: the history in front of it plus its own offset.
|
| 87 |
+
let position = past_sequence_length(batch) + t;
|
| 88 |
+
// The packed query's Q block starts at offset zero, so this base serves both layouts.
|
| 89 |
+
let q_base = (batch * params.seqLen + t) * Q_STRIDE + head * HEAD_DIM;
|
| 90 |
+
if (d < ROTARY_DIM) {
|
| 91 |
+
let slot = rotary_slot(d);
|
| 92 |
+
let cs = f32(cos_cache[position * ROTARY_HALF + slot]);
|
| 93 |
+
let sn = f32(sin_cache[position * ROTARY_HALF + slot]);
|
| 94 |
+
let own = f32(query[q_base + d]);
|
| 95 |
+
let partner = f32(query[q_base + rotary_partner(d)]);
|
| 96 |
+
// Rotary Q is materialized in the operator dtype. Round to f16 here when T
|
| 97 |
+
// is float16, then widen only for the f32 accumulation scratch buffer.
|
| 98 |
+
q_rotary[index] = f32({{ scalar }}(rotary_value(own, partner, cs, sn, rotary_is_first(d))));
|
| 99 |
+
} else {
|
| 100 |
+
q_rotary[index] = f32(query[q_base + d]);
|
| 101 |
+
}
|
| 102 |
+
}
|
build/webgpu/test.json
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.SparseAttention",
|
| 3 |
+
"fixtureArrays": {
|
| 4 |
+
"prompt_separate_two_layouts_input_blockColIndicesT": [0, 0, 1, 1, 2, 1, 2, 3, -1, 0, 0, 1, 0, 1, 2, 0, 2, 3]
|
| 5 |
+
},
|
| 6 |
+
"cases": [
|
| 7 |
+
{
|
| 8 |
+
"name": "prompt_separate_two_layouts",
|
| 9 |
+
"provenance": {
|
| 10 |
+
"notes": "The two-layout CSR example from the ONNX Runtime operator documentation, using separate Q/K/V tensors."
|
| 11 |
+
},
|
| 12 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16 },
|
| 13 |
+
"inputs": {
|
| 14 |
+
"queryT": {
|
| 15 |
+
"dtype": "float32",
|
| 16 |
+
"shape": [2, 32, 32],
|
| 17 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 18 |
+
},
|
| 19 |
+
"keyT": {
|
| 20 |
+
"dtype": "float32",
|
| 21 |
+
"shape": [2, 32, 16],
|
| 22 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 23 |
+
},
|
| 24 |
+
"valueT": {
|
| 25 |
+
"dtype": "float32",
|
| 26 |
+
"shape": [2, 32, 16],
|
| 27 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 28 |
+
},
|
| 29 |
+
"pastKeyT": { "dtype": "float32", "shape": [2, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 30 |
+
"pastValueT": { "dtype": "float32", "shape": [2, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 31 |
+
"blockRowIndicesT": {
|
| 32 |
+
"dtype": "int32",
|
| 33 |
+
"shape": [2, 5],
|
| 34 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 35 |
+
},
|
| 36 |
+
"blockColIndicesT": {
|
| 37 |
+
"dtype": "int32",
|
| 38 |
+
"shape": [2, 9],
|
| 39 |
+
"data": {
|
| 40 |
+
"kind": "values",
|
| 41 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 45 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [32, 32] } }
|
| 46 |
+
},
|
| 47 |
+
"outputs": {
|
| 48 |
+
"outputT": { "dtype": "float32", "shape": [2, 32, 32], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 49 |
+
"pastKeyT": { "dtype": "float32", "shape": [2, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 50 |
+
"pastValueT": { "dtype": "float32", "shape": [2, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"name": "prompt_packed_two_layouts",
|
| 55 |
+
"provenance": { "notes": "Packed [Q|K|V] form of the documented two-layout prompt case." },
|
| 56 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16 },
|
| 57 |
+
"inputs": {
|
| 58 |
+
"queryT": {
|
| 59 |
+
"dtype": "float32",
|
| 60 |
+
"shape": [1, 32, 64],
|
| 61 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.27, "scale": 0.5 }
|
| 62 |
+
},
|
| 63 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 64 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 65 |
+
"blockRowIndicesT": {
|
| 66 |
+
"dtype": "int32",
|
| 67 |
+
"shape": [2, 5],
|
| 68 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 69 |
+
},
|
| 70 |
+
"blockColIndicesT": {
|
| 71 |
+
"dtype": "int32",
|
| 72 |
+
"shape": [2, 9],
|
| 73 |
+
"data": {
|
| 74 |
+
"kind": "values",
|
| 75 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 76 |
+
}
|
| 77 |
+
},
|
| 78 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 79 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } }
|
| 80 |
+
},
|
| 81 |
+
"outputs": {
|
| 82 |
+
"outputT": { "dtype": "float32", "shape": [1, 32, 32], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 83 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 84 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 85 |
+
}
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"name": "prompt_separate_rotary_neox",
|
| 89 |
+
"provenance": {
|
| 90 |
+
"notes": "Full-width NeoX rotary with the minimum accepted head width; rotary_interleaved values other than 1 retain the NeoX layout."
|
| 91 |
+
},
|
| 92 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16, "do_rotary": 1, "rotary_interleaved": 2 },
|
| 93 |
+
"inputs": {
|
| 94 |
+
"queryT": {
|
| 95 |
+
"dtype": "float32",
|
| 96 |
+
"shape": [1, 32, 64],
|
| 97 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 98 |
+
},
|
| 99 |
+
"keyT": {
|
| 100 |
+
"dtype": "float32",
|
| 101 |
+
"shape": [1, 32, 32],
|
| 102 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 103 |
+
},
|
| 104 |
+
"valueT": {
|
| 105 |
+
"dtype": "float32",
|
| 106 |
+
"shape": [1, 32, 32],
|
| 107 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 108 |
+
},
|
| 109 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 16], "data": { "kind": "constant", "value": 0.0 } },
|
| 110 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 16], "data": { "kind": "constant", "value": 0.0 } },
|
| 111 |
+
"blockRowIndicesT": {
|
| 112 |
+
"dtype": "int32",
|
| 113 |
+
"shape": [2, 5],
|
| 114 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 115 |
+
},
|
| 116 |
+
"blockColIndicesT": {
|
| 117 |
+
"dtype": "int32",
|
| 118 |
+
"shape": [2, 9],
|
| 119 |
+
"data": {
|
| 120 |
+
"kind": "values",
|
| 121 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 125 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 126 |
+
"cosCacheT": { "dtype": "float32", "shape": [64, 8], "data": { "kind": "constant", "value": 0.8 } },
|
| 127 |
+
"sinCacheT": { "dtype": "float32", "shape": [64, 8], "data": { "kind": "constant", "value": 0.6 } }
|
| 128 |
+
},
|
| 129 |
+
"outputs": {
|
| 130 |
+
"outputT": { "dtype": "float32", "shape": [1, 32, 64], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 131 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 16], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 132 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 16], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 133 |
+
}
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"name": "prompt_packed_partial_rotary_interleaved",
|
| 137 |
+
"provenance": {
|
| 138 |
+
"notes": "Packed QKV with interleaved rotary over the first 16 of 32 head channels; ONNX Runtime copies the remainder through."
|
| 139 |
+
},
|
| 140 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16, "do_rotary": 1, "rotary_interleaved": 1 },
|
| 141 |
+
"inputs": {
|
| 142 |
+
"queryT": {
|
| 143 |
+
"dtype": "float32",
|
| 144 |
+
"shape": [1, 32, 256],
|
| 145 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.19, "scale": 0.5 }
|
| 146 |
+
},
|
| 147 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 32], "data": { "kind": "constant", "value": 0.0 } },
|
| 148 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 32], "data": { "kind": "constant", "value": 0.0 } },
|
| 149 |
+
"blockRowIndicesT": {
|
| 150 |
+
"dtype": "int32",
|
| 151 |
+
"shape": [2, 5],
|
| 152 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 153 |
+
},
|
| 154 |
+
"blockColIndicesT": {
|
| 155 |
+
"dtype": "int32",
|
| 156 |
+
"shape": [2, 9],
|
| 157 |
+
"data": {
|
| 158 |
+
"kind": "values",
|
| 159 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 160 |
+
}
|
| 161 |
+
},
|
| 162 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 163 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 164 |
+
"cosCacheT": { "dtype": "float32", "shape": [64, 8], "data": { "kind": "constant", "value": 0.8 } },
|
| 165 |
+
"sinCacheT": { "dtype": "float32", "shape": [64, 8], "data": { "kind": "constant", "value": 0.6 } }
|
| 166 |
+
},
|
| 167 |
+
"outputs": {
|
| 168 |
+
"outputT": { "dtype": "float32", "shape": [1, 32, 128], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 169 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 32], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 170 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 32], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 171 |
+
}
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"name": "decode_separate_two_layouts",
|
| 175 |
+
"provenance": {
|
| 176 |
+
"notes": "Two decode rows with different history lengths, so the scalar maximum and per-row key lengths have distinct jobs."
|
| 177 |
+
},
|
| 178 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16 },
|
| 179 |
+
"inputs": {
|
| 180 |
+
"queryT": {
|
| 181 |
+
"dtype": "float32",
|
| 182 |
+
"shape": [2, 1, 32],
|
| 183 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 184 |
+
},
|
| 185 |
+
"keyT": {
|
| 186 |
+
"dtype": "float32",
|
| 187 |
+
"shape": [2, 1, 16],
|
| 188 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 189 |
+
},
|
| 190 |
+
"valueT": {
|
| 191 |
+
"dtype": "float32",
|
| 192 |
+
"shape": [2, 1, 16],
|
| 193 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 194 |
+
},
|
| 195 |
+
"pastKeyT": {
|
| 196 |
+
"dtype": "float32",
|
| 197 |
+
"shape": [2, 2, 64, 8],
|
| 198 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
|
| 199 |
+
},
|
| 200 |
+
"pastValueT": {
|
| 201 |
+
"dtype": "float32",
|
| 202 |
+
"shape": [2, 2, 64, 8],
|
| 203 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
|
| 204 |
+
},
|
| 205 |
+
"blockRowIndicesT": {
|
| 206 |
+
"dtype": "int32",
|
| 207 |
+
"shape": [2, 5],
|
| 208 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 209 |
+
},
|
| 210 |
+
"blockColIndicesT": {
|
| 211 |
+
"dtype": "int32",
|
| 212 |
+
"shape": [2, 9],
|
| 213 |
+
"data": {
|
| 214 |
+
"kind": "values",
|
| 215 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 216 |
+
}
|
| 217 |
+
},
|
| 218 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [41] } },
|
| 219 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [41, 34] } }
|
| 220 |
+
},
|
| 221 |
+
"outputs": {
|
| 222 |
+
"outputT": { "dtype": "float32", "shape": [2, 1, 32], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 223 |
+
"pastKeyT": { "dtype": "float32", "shape": [2, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 224 |
+
"pastValueT": { "dtype": "float32", "shape": [2, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 225 |
+
}
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"name": "decode_packed_rotary_neox",
|
| 229 |
+
"provenance": { "notes": "Packed decode with full-width NeoX rotary at each row's absolute position." },
|
| 230 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16, "do_rotary": 1 },
|
| 231 |
+
"inputs": {
|
| 232 |
+
"queryT": {
|
| 233 |
+
"dtype": "float32",
|
| 234 |
+
"shape": [2, 1, 128],
|
| 235 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 236 |
+
},
|
| 237 |
+
"pastKeyT": {
|
| 238 |
+
"dtype": "float32",
|
| 239 |
+
"shape": [2, 2, 64, 16],
|
| 240 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
|
| 241 |
+
},
|
| 242 |
+
"pastValueT": {
|
| 243 |
+
"dtype": "float32",
|
| 244 |
+
"shape": [2, 2, 64, 16],
|
| 245 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
|
| 246 |
+
},
|
| 247 |
+
"blockRowIndicesT": {
|
| 248 |
+
"dtype": "int32",
|
| 249 |
+
"shape": [2, 5],
|
| 250 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 251 |
+
},
|
| 252 |
+
"blockColIndicesT": {
|
| 253 |
+
"dtype": "int32",
|
| 254 |
+
"shape": [2, 9],
|
| 255 |
+
"data": {
|
| 256 |
+
"kind": "values",
|
| 257 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 258 |
+
}
|
| 259 |
+
},
|
| 260 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [41] } },
|
| 261 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [41, 34] } },
|
| 262 |
+
"cosCacheT": { "dtype": "float32", "shape": [64, 8], "data": { "kind": "constant", "value": 0.8 } },
|
| 263 |
+
"sinCacheT": { "dtype": "float32", "shape": [64, 8], "data": { "kind": "constant", "value": 0.6 } }
|
| 264 |
+
},
|
| 265 |
+
"outputs": {
|
| 266 |
+
"outputT": { "dtype": "float32", "shape": [2, 1, 64], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 267 |
+
"pastKeyT": { "dtype": "float32", "shape": [2, 2, 64, 16], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 268 |
+
"pastValueT": { "dtype": "float32", "shape": [2, 2, 64, 16], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 269 |
+
}
|
| 270 |
+
},
|
| 271 |
+
{
|
| 272 |
+
"name": "chunked_separate_two_layouts",
|
| 273 |
+
"provenance": {
|
| 274 |
+
"notes": "Eight new tokens over 24 cached tokens, covering the non-prompt sequence_length > 1 path."
|
| 275 |
+
},
|
| 276 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16 },
|
| 277 |
+
"inputs": {
|
| 278 |
+
"queryT": {
|
| 279 |
+
"dtype": "float32",
|
| 280 |
+
"shape": [2, 8, 32],
|
| 281 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 282 |
+
},
|
| 283 |
+
"keyT": {
|
| 284 |
+
"dtype": "float32",
|
| 285 |
+
"shape": [2, 8, 16],
|
| 286 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 287 |
+
},
|
| 288 |
+
"valueT": {
|
| 289 |
+
"dtype": "float32",
|
| 290 |
+
"shape": [2, 8, 16],
|
| 291 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 292 |
+
},
|
| 293 |
+
"pastKeyT": {
|
| 294 |
+
"dtype": "float32",
|
| 295 |
+
"shape": [2, 2, 64, 8],
|
| 296 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
|
| 297 |
+
},
|
| 298 |
+
"pastValueT": {
|
| 299 |
+
"dtype": "float32",
|
| 300 |
+
"shape": [2, 2, 64, 8],
|
| 301 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
|
| 302 |
+
},
|
| 303 |
+
"blockRowIndicesT": {
|
| 304 |
+
"dtype": "int32",
|
| 305 |
+
"shape": [2, 5],
|
| 306 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 307 |
+
},
|
| 308 |
+
"blockColIndicesT": {
|
| 309 |
+
"dtype": "int32",
|
| 310 |
+
"shape": [2, 9],
|
| 311 |
+
"data": {
|
| 312 |
+
"kind": "values",
|
| 313 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 314 |
+
}
|
| 315 |
+
},
|
| 316 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 317 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [32, 32] } }
|
| 318 |
+
},
|
| 319 |
+
"outputs": {
|
| 320 |
+
"outputT": { "dtype": "float32", "shape": [2, 8, 32], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 321 |
+
"pastKeyT": { "dtype": "float32", "shape": [2, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 322 |
+
"pastValueT": { "dtype": "float32", "shape": [2, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 323 |
+
}
|
| 324 |
+
},
|
| 325 |
+
{
|
| 326 |
+
"name": "prompt_all_rows_gqa_four_to_one",
|
| 327 |
+
"provenance": {
|
| 328 |
+
"notes": "A 64-token prompt reaches every documented block row while four query heads share each KV head."
|
| 329 |
+
},
|
| 330 |
+
"attrs": { "num_heads": 8, "kv_num_heads": 2, "sparse_block_size": 16 },
|
| 331 |
+
"inputs": {
|
| 332 |
+
"queryT": {
|
| 333 |
+
"dtype": "float32",
|
| 334 |
+
"shape": [1, 64, 64],
|
| 335 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 336 |
+
},
|
| 337 |
+
"keyT": {
|
| 338 |
+
"dtype": "float32",
|
| 339 |
+
"shape": [1, 64, 16],
|
| 340 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 341 |
+
},
|
| 342 |
+
"valueT": {
|
| 343 |
+
"dtype": "float32",
|
| 344 |
+
"shape": [1, 64, 16],
|
| 345 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 346 |
+
},
|
| 347 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 348 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 349 |
+
"blockRowIndicesT": {
|
| 350 |
+
"dtype": "int32",
|
| 351 |
+
"shape": [2, 5],
|
| 352 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 353 |
+
},
|
| 354 |
+
"blockColIndicesT": {
|
| 355 |
+
"dtype": "int32",
|
| 356 |
+
"shape": [2, 9],
|
| 357 |
+
"data": {
|
| 358 |
+
"kind": "values",
|
| 359 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 360 |
+
}
|
| 361 |
+
},
|
| 362 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } },
|
| 363 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
| 364 |
+
},
|
| 365 |
+
"outputs": {
|
| 366 |
+
"outputT": { "dtype": "float32", "shape": [1, 64, 64], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 367 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 368 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 369 |
+
}
|
| 370 |
+
},
|
| 371 |
+
{
|
| 372 |
+
"name": "prompt_block32_head_dim16",
|
| 373 |
+
"provenance": {
|
| 374 |
+
"notes": "Block size 32 and head width 16 exercise independent key-tile and output-channel tails."
|
| 375 |
+
},
|
| 376 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 32 },
|
| 377 |
+
"inputs": {
|
| 378 |
+
"queryT": {
|
| 379 |
+
"dtype": "float32",
|
| 380 |
+
"shape": [1, 64, 64],
|
| 381 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 382 |
+
},
|
| 383 |
+
"keyT": {
|
| 384 |
+
"dtype": "float32",
|
| 385 |
+
"shape": [1, 64, 32],
|
| 386 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 387 |
+
},
|
| 388 |
+
"valueT": {
|
| 389 |
+
"dtype": "float32",
|
| 390 |
+
"shape": [1, 64, 32],
|
| 391 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 392 |
+
},
|
| 393 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 128, 16], "data": { "kind": "constant", "value": 0.0 } },
|
| 394 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 128, 16], "data": { "kind": "constant", "value": 0.0 } },
|
| 395 |
+
"blockRowIndicesT": {
|
| 396 |
+
"dtype": "int32",
|
| 397 |
+
"shape": [2, 5],
|
| 398 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 399 |
+
},
|
| 400 |
+
"blockColIndicesT": {
|
| 401 |
+
"dtype": "int32",
|
| 402 |
+
"shape": [2, 9],
|
| 403 |
+
"data": {
|
| 404 |
+
"kind": "values",
|
| 405 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 406 |
+
}
|
| 407 |
+
},
|
| 408 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } },
|
| 409 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
| 410 |
+
},
|
| 411 |
+
"outputs": {
|
| 412 |
+
"outputT": { "dtype": "float32", "shape": [1, 64, 64], "tolerance": 0.00003, "relTolerance": 0.0003 },
|
| 413 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 128, 16], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 414 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 128, 16], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 415 |
+
}
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"name": "prompt_explicit_scale",
|
| 419 |
+
"provenance": { "notes": "Explicit non-default score scale." },
|
| 420 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16, "scale": 0.25 },
|
| 421 |
+
"inputs": {
|
| 422 |
+
"queryT": {
|
| 423 |
+
"dtype": "float32",
|
| 424 |
+
"shape": [1, 32, 32],
|
| 425 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 426 |
+
},
|
| 427 |
+
"keyT": {
|
| 428 |
+
"dtype": "float32",
|
| 429 |
+
"shape": [1, 32, 16],
|
| 430 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 431 |
+
},
|
| 432 |
+
"valueT": {
|
| 433 |
+
"dtype": "float32",
|
| 434 |
+
"shape": [1, 32, 16],
|
| 435 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 436 |
+
},
|
| 437 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 438 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 439 |
+
"blockRowIndicesT": {
|
| 440 |
+
"dtype": "int32",
|
| 441 |
+
"shape": [2, 5],
|
| 442 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 443 |
+
},
|
| 444 |
+
"blockColIndicesT": {
|
| 445 |
+
"dtype": "int32",
|
| 446 |
+
"shape": [2, 9],
|
| 447 |
+
"data": {
|
| 448 |
+
"kind": "values",
|
| 449 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 450 |
+
}
|
| 451 |
+
},
|
| 452 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 453 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } }
|
| 454 |
+
},
|
| 455 |
+
"outputs": {
|
| 456 |
+
"outputT": { "dtype": "float32", "shape": [1, 32, 32], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 457 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 458 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 2, 64, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 459 |
+
}
|
| 460 |
+
},
|
| 461 |
+
{
|
| 462 |
+
"name": "f16_prompt_separate",
|
| 463 |
+
"provenance": { "notes": "Meaningful float16 prompt; scores and online-softmax accumulators remain float32." },
|
| 464 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16 },
|
| 465 |
+
"inputs": {
|
| 466 |
+
"queryT": {
|
| 467 |
+
"dtype": "float16",
|
| 468 |
+
"shape": [1, 32, 32],
|
| 469 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 470 |
+
},
|
| 471 |
+
"keyT": {
|
| 472 |
+
"dtype": "float16",
|
| 473 |
+
"shape": [1, 32, 16],
|
| 474 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 475 |
+
},
|
| 476 |
+
"valueT": {
|
| 477 |
+
"dtype": "float16",
|
| 478 |
+
"shape": [1, 32, 16],
|
| 479 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 480 |
+
},
|
| 481 |
+
"pastKeyT": { "dtype": "float16", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 482 |
+
"pastValueT": { "dtype": "float16", "shape": [1, 2, 64, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 483 |
+
"blockRowIndicesT": {
|
| 484 |
+
"dtype": "int32",
|
| 485 |
+
"shape": [2, 5],
|
| 486 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 487 |
+
},
|
| 488 |
+
"blockColIndicesT": {
|
| 489 |
+
"dtype": "int32",
|
| 490 |
+
"shape": [2, 9],
|
| 491 |
+
"data": {
|
| 492 |
+
"kind": "values",
|
| 493 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 494 |
+
}
|
| 495 |
+
},
|
| 496 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 497 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } }
|
| 498 |
+
},
|
| 499 |
+
"outputs": {
|
| 500 |
+
"outputT": { "dtype": "float16", "shape": [1, 32, 32], "tolerance": 0.003, "relTolerance": 0.01 },
|
| 501 |
+
"pastKeyT": { "dtype": "float16", "shape": [1, 2, 64, 8], "tolerance": 0.001, "relTolerance": 0.001 },
|
| 502 |
+
"pastValueT": { "dtype": "float16", "shape": [1, 2, 64, 8], "tolerance": 0.001, "relTolerance": 0.001 }
|
| 503 |
+
}
|
| 504 |
+
},
|
| 505 |
+
{
|
| 506 |
+
"name": "f16_decode_packed_partial_rotary",
|
| 507 |
+
"provenance": { "notes": "Float16 packed decode with partial NeoX rotary and unequal per-batch history lengths." },
|
| 508 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 16, "do_rotary": 1 },
|
| 509 |
+
"inputs": {
|
| 510 |
+
"queryT": {
|
| 511 |
+
"dtype": "float16",
|
| 512 |
+
"shape": [2, 1, 256],
|
| 513 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 514 |
+
},
|
| 515 |
+
"pastKeyT": {
|
| 516 |
+
"dtype": "float16",
|
| 517 |
+
"shape": [2, 2, 64, 32],
|
| 518 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
|
| 519 |
+
},
|
| 520 |
+
"pastValueT": {
|
| 521 |
+
"dtype": "float16",
|
| 522 |
+
"shape": [2, 2, 64, 32],
|
| 523 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
|
| 524 |
+
},
|
| 525 |
+
"blockRowIndicesT": {
|
| 526 |
+
"dtype": "int32",
|
| 527 |
+
"shape": [2, 5],
|
| 528 |
+
"data": { "kind": "values", "values": [0, 1, 3, 5, 8, 0, 1, 3, 6, 9] }
|
| 529 |
+
},
|
| 530 |
+
"blockColIndicesT": {
|
| 531 |
+
"dtype": "int32",
|
| 532 |
+
"shape": [2, 9],
|
| 533 |
+
"data": {
|
| 534 |
+
"kind": "values",
|
| 535 |
+
"values": { "$ref": "#/fixtureArrays/prompt_separate_two_layouts_input_blockColIndicesT" }
|
| 536 |
+
}
|
| 537 |
+
},
|
| 538 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [41] } },
|
| 539 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [41, 34] } },
|
| 540 |
+
"cosCacheT": { "dtype": "float16", "shape": [64, 8], "data": { "kind": "constant", "value": 0.8 } },
|
| 541 |
+
"sinCacheT": { "dtype": "float16", "shape": [64, 8], "data": { "kind": "constant", "value": 0.6 } }
|
| 542 |
+
},
|
| 543 |
+
"outputs": {
|
| 544 |
+
"outputT": { "dtype": "float16", "shape": [2, 1, 128], "tolerance": 0.003, "relTolerance": 0.01 },
|
| 545 |
+
"pastKeyT": { "dtype": "float16", "shape": [2, 2, 64, 32], "tolerance": 0.002, "relTolerance": 0.005 },
|
| 546 |
+
"pastValueT": { "dtype": "float16", "shape": [2, 2, 64, 32], "tolerance": 0.001, "relTolerance": 0.001 }
|
| 547 |
+
}
|
| 548 |
+
},
|
| 549 |
+
{
|
| 550 |
+
"name": "dense_layout_ignores_malformed_columns",
|
| 551 |
+
"provenance": {
|
| 552 |
+
"notes": "ORT classifies a layout from its final CSR count. Once dense, repeated non-prefix column contents are ignored."
|
| 553 |
+
},
|
| 554 |
+
"attrs": { "num_heads": 1, "kv_num_heads": 1, "sparse_block_size": 16 },
|
| 555 |
+
"inputs": {
|
| 556 |
+
"queryT": {
|
| 557 |
+
"dtype": "float32",
|
| 558 |
+
"shape": [1, 32, 8],
|
| 559 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 560 |
+
},
|
| 561 |
+
"keyT": {
|
| 562 |
+
"dtype": "float32",
|
| 563 |
+
"shape": [1, 32, 8],
|
| 564 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 565 |
+
},
|
| 566 |
+
"valueT": {
|
| 567 |
+
"dtype": "float32",
|
| 568 |
+
"shape": [1, 32, 8],
|
| 569 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 570 |
+
},
|
| 571 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 32, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 572 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 32, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 573 |
+
"blockRowIndicesT": { "dtype": "int32", "shape": [1, 3], "data": { "kind": "values", "values": [0, 1, 3] } },
|
| 574 |
+
"blockColIndicesT": { "dtype": "int32", "shape": [1, 3], "data": { "kind": "values", "values": [1, 1, 1] } },
|
| 575 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
| 576 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } }
|
| 577 |
+
},
|
| 578 |
+
"outputs": {
|
| 579 |
+
"outputT": { "dtype": "float32", "shape": [1, 32, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 580 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 32, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 581 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 32, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 582 |
+
}
|
| 583 |
+
},
|
| 584 |
+
{
|
| 585 |
+
"name": "sparse_duplicate_columns_are_idempotent",
|
| 586 |
+
"provenance": {
|
| 587 |
+
"notes": "A sparse row repeats block 0. CSR encodes a boolean mask, so the duplicate must not duplicate probability mass."
|
| 588 |
+
},
|
| 589 |
+
"attrs": { "num_heads": 1, "kv_num_heads": 1, "sparse_block_size": 16 },
|
| 590 |
+
"inputs": {
|
| 591 |
+
"queryT": {
|
| 592 |
+
"dtype": "float32",
|
| 593 |
+
"shape": [1, 48, 8],
|
| 594 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 595 |
+
},
|
| 596 |
+
"keyT": {
|
| 597 |
+
"dtype": "float32",
|
| 598 |
+
"shape": [1, 48, 8],
|
| 599 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 600 |
+
},
|
| 601 |
+
"valueT": {
|
| 602 |
+
"dtype": "float32",
|
| 603 |
+
"shape": [1, 48, 8],
|
| 604 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 605 |
+
},
|
| 606 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 48, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 607 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 48, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 608 |
+
"blockRowIndicesT": { "dtype": "int32", "shape": [1, 4], "data": { "kind": "values", "values": [0, 1, 3, 5] } },
|
| 609 |
+
"blockColIndicesT": {
|
| 610 |
+
"dtype": "int32",
|
| 611 |
+
"shape": [1, 5],
|
| 612 |
+
"data": { "kind": "values", "values": [0, 0, 1, 0, 0] }
|
| 613 |
+
},
|
| 614 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [48] } },
|
| 615 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [48] } }
|
| 616 |
+
},
|
| 617 |
+
"outputs": {
|
| 618 |
+
"outputT": { "dtype": "float32", "shape": [1, 48, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 619 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 48, 8], "tolerance": 0.00002, "relTolerance": 0.0002 },
|
| 620 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 48, 8], "tolerance": 0.00002, "relTolerance": 0.0002 }
|
| 621 |
+
}
|
| 622 |
+
},
|
| 623 |
+
{
|
| 624 |
+
"name": "empty_csr_row_uniform_scalar_total",
|
| 625 |
+
"provenance": {
|
| 626 |
+
"notes": "Pinned all-masked-row behavior from ORT's lowest-sentinel softmax: one causal key yields its value, not zero. Also covers scalar total_sequence_length and a zero-width column tensor."
|
| 627 |
+
},
|
| 628 |
+
"attrs": { "num_heads": 1, "kv_num_heads": 1, "sparse_block_size": 16 },
|
| 629 |
+
"inputs": {
|
| 630 |
+
"queryT": {
|
| 631 |
+
"dtype": "float32",
|
| 632 |
+
"shape": [1, 1, 8],
|
| 633 |
+
"data": { "kind": "values", "values": [0.5, -0.5, 1.0, -1.0, 2.0, -2.0, 3.0, -3.0] }
|
| 634 |
+
},
|
| 635 |
+
"keyT": {
|
| 636 |
+
"dtype": "float32",
|
| 637 |
+
"shape": [1, 1, 8],
|
| 638 |
+
"data": { "kind": "values", "values": [8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0] }
|
| 639 |
+
},
|
| 640 |
+
"valueT": {
|
| 641 |
+
"dtype": "float32",
|
| 642 |
+
"shape": [1, 1, 8],
|
| 643 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
|
| 644 |
+
},
|
| 645 |
+
"pastKeyT": {
|
| 646 |
+
"dtype": "float32",
|
| 647 |
+
"shape": [1, 1, 16, 8],
|
| 648 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.25 }
|
| 649 |
+
},
|
| 650 |
+
"pastValueT": {
|
| 651 |
+
"dtype": "float32",
|
| 652 |
+
"shape": [1, 1, 16, 8],
|
| 653 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.25 }
|
| 654 |
+
},
|
| 655 |
+
"blockRowIndicesT": { "dtype": "int32", "shape": [1, 2], "data": { "kind": "values", "values": [0, 0] } },
|
| 656 |
+
"blockColIndicesT": { "dtype": "int32", "shape": [1, 0], "data": { "kind": "values", "values": [] } },
|
| 657 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
|
| 658 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1] } }
|
| 659 |
+
},
|
| 660 |
+
"outputs": {
|
| 661 |
+
"outputT": {
|
| 662 |
+
"dtype": "float32",
|
| 663 |
+
"shape": [1, 1, 8],
|
| 664 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] },
|
| 665 |
+
"tolerance": 0
|
| 666 |
+
},
|
| 667 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 16, 8], "tolerance": 0 },
|
| 668 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 16, 8], "tolerance": 0 }
|
| 669 |
+
}
|
| 670 |
+
},
|
| 671 |
+
{
|
| 672 |
+
"name": "optional_rotary_input_ignored_when_attribute_is_not_one",
|
| 673 |
+
"provenance": {
|
| 674 |
+
"notes": "The CPU kernel enables do_rotary only for the exact integer value 1. Otherwise optional rotary inputs are ignored and need not be paired."
|
| 675 |
+
},
|
| 676 |
+
"attrs": { "num_heads": 1, "kv_num_heads": 1, "sparse_block_size": 16, "do_rotary": 2, "rotary_interleaved": 2 },
|
| 677 |
+
"inputs": {
|
| 678 |
+
"queryT": {
|
| 679 |
+
"dtype": "float32",
|
| 680 |
+
"shape": [1, 1, 8],
|
| 681 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 682 |
+
},
|
| 683 |
+
"keyT": {
|
| 684 |
+
"dtype": "float32",
|
| 685 |
+
"shape": [1, 1, 8],
|
| 686 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 687 |
+
},
|
| 688 |
+
"valueT": {
|
| 689 |
+
"dtype": "float32",
|
| 690 |
+
"shape": [1, 1, 8],
|
| 691 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 692 |
+
},
|
| 693 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 16, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 694 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 16, 8], "data": { "kind": "constant", "value": 0.0 } },
|
| 695 |
+
"blockRowIndicesT": { "dtype": "int32", "shape": [1, 2], "data": { "kind": "values", "values": [0, 1] } },
|
| 696 |
+
"blockColIndicesT": { "dtype": "int32", "shape": [1, 1], "data": { "kind": "values", "values": [0] } },
|
| 697 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1] } },
|
| 698 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1] } },
|
| 699 |
+
"cosCacheT": { "dtype": "float32", "shape": [1, 1], "data": { "kind": "constant", "value": 0.0 } }
|
| 700 |
+
},
|
| 701 |
+
"outputs": {
|
| 702 |
+
"outputT": { "dtype": "float32", "shape": [1, 1, 8], "tolerance": 0.000001, "relTolerance": 0.000001 },
|
| 703 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 16, 8], "tolerance": 0.000001, "relTolerance": 0.000001 },
|
| 704 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 16, 8], "tolerance": 0.000001, "relTolerance": 0.000001 }
|
| 705 |
+
}
|
| 706 |
+
},
|
| 707 |
+
{
|
| 708 |
+
"name": "zero_width_rotary_is_a_noop",
|
| 709 |
+
"provenance": {
|
| 710 |
+
"notes": "ONNX Runtime accepts zero-width paired caches with do_rotary=1; the resulting zero-dimensional rotary transform copies Q and K through."
|
| 711 |
+
},
|
| 712 |
+
"attrs": { "num_heads": 1, "kv_num_heads": 1, "sparse_block_size": 16, "do_rotary": 1, "rotary_interleaved": 1 },
|
| 713 |
+
"inputs": {
|
| 714 |
+
"queryT": {
|
| 715 |
+
"dtype": "float32",
|
| 716 |
+
"shape": [1, 1, 16],
|
| 717 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
|
| 718 |
+
},
|
| 719 |
+
"keyT": {
|
| 720 |
+
"dtype": "float32",
|
| 721 |
+
"shape": [1, 1, 16],
|
| 722 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
|
| 723 |
+
},
|
| 724 |
+
"valueT": {
|
| 725 |
+
"dtype": "float32",
|
| 726 |
+
"shape": [1, 1, 16],
|
| 727 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
|
| 728 |
+
},
|
| 729 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 16, 16], "data": { "kind": "constant", "value": 0.0 } },
|
| 730 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 16, 16], "data": { "kind": "constant", "value": 0.0 } },
|
| 731 |
+
"blockRowIndicesT": { "dtype": "int32", "shape": [1, 2], "data": { "kind": "values", "values": [0, 1] } },
|
| 732 |
+
"blockColIndicesT": { "dtype": "int32", "shape": [1, 1], "data": { "kind": "values", "values": [0] } },
|
| 733 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1] } },
|
| 734 |
+
"keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1] } },
|
| 735 |
+
"cosCacheT": { "dtype": "float32", "shape": [1, 0], "data": { "kind": "values", "values": [] } },
|
| 736 |
+
"sinCacheT": { "dtype": "float32", "shape": [1, 0], "data": { "kind": "values", "values": [] } }
|
| 737 |
+
},
|
| 738 |
+
"outputs": {
|
| 739 |
+
"outputT": { "dtype": "float32", "shape": [1, 1, 16], "tolerance": 0.000001, "relTolerance": 0.000001 },
|
| 740 |
+
"pastKeyT": { "dtype": "float32", "shape": [1, 1, 16, 16], "tolerance": 0.000001, "relTolerance": 0.000001 },
|
| 741 |
+
"pastValueT": { "dtype": "float32", "shape": [1, 1, 16, 16], "tolerance": 0.000001, "relTolerance": 0.000001 }
|
| 742 |
+
}
|
| 743 |
+
}
|
| 744 |
+
]
|
| 745 |
+
}
|