Xenova HF Staff commited on
Commit
f03331e
·
verified ·
1 Parent(s): 35f8daf

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,27 +18,27 @@ See the [ONNX Runtime `SparseAttention` contrib-operator spec](https://github.co
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
 
@@ -47,11 +47,11 @@ Attributes and default values (overridable per request):
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
 
@@ -66,7 +66,7 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
66
 
67
  ## Files
68
 
69
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
70
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
71
  - [`test.json`](build/webgpu/test.json) — correctness cases
72
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -77,10 +77,14 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
77
 
78
  ## Use with `@huggingface/kernels`
79
 
80
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
81
- It then allocates the result tensors automatically.
 
 
 
82
 
83
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
84
 
85
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
86
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- | --- |
23
+ | `queryT` | `query` | `T` | same as logical dtype | `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
+ | `keyT` | `key` | `T` | same as logical dtype | `3` | — | Key `(batch_size, sequence_length, kv_num_heads * head_size)`. Omitted for packed QKV. | optional |
25
+ | `valueT` | `value` | `T` | same as logical dtype | `3` | — | Value `(batch_size, sequence_length, kv_num_heads * head_size)`. Omitted for packed QKV. | optional |
26
+ | `pastKeyT` | `past_key` | `T` | same as logical dtype | `4` | — | Key cache `(batch_size, kv_num_heads, max_cache_sequence_length, head_size)`, updated in place. | required |
27
+ | `pastValueT` | `past_value` | `T` | same as logical dtype | `4` | — | Value cache with the same shape as `past_key`, updated in place. | required |
28
+ | `blockRowIndicesT` | `block_row_indices` | `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
+ | `blockColIndicesT` | `block_col_indices` | `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
+ | `totalSequenceLengthT` | `total_sequence_length` | `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
+ | `keyTotalSequenceLengthsT` | `key_total_sequence_lengths` | `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
+ | `cosCacheT` | `cos_cache` | `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
+ | `sinCacheT` | `sin_cache` | `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 | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
38
  | --- | --- | --- | --- | --- | --- | --- |
39
+ | `outputT` | `output` | `T` | `3` | derived | Attention output `(batch_size, sequence_length, num_heads * head_size)`. | required |
40
+ | `pastKeyT` | `past_key` | `T` | `4` | same as `pastKeyT` | The key cache tensor itself after the in-place append; ONNX names this output `present_key`. | required |
41
+ | `pastValueT` | `past_value` | `T` | `4` | same as `pastValueT` | The value cache tensor itself after the in-place append; ONNX names this output `present_value`. | required |
42
 
43
  ## Attributes
44
 
 
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
  | `kv_num_heads` | — | Number of key/value heads; must divide `num_heads`. |
51
+ | `num_heads` | — | Number of query heads. |
52
+ | `rotary_interleaved` | `0` | Set to 1 to rotate adjacent pairs instead of using the NeoX half-split; every other value selects the NeoX layout. |
53
  | `scale` | — | Scale applied to query-key products; omitted or zero uses `1 / sqrt(head_size)`. |
54
+ | `sparse_block_size` | — | Tokens per sparse block; one of 16, 32, 64, 128. |
55
 
56
  ## Type constraints
57
 
 
66
 
67
  ## Files
68
 
69
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
70
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
71
  - [`test.json`](build/webgpu/test.json) — correctness cases
72
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
77
 
78
  ## Use with `@huggingface/kernels`
79
 
80
+ ```sh
81
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
82
+ ```
83
+
84
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
85
 
86
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
87
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
88
 
89
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
90
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
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]
@@ -226,6 +225,703 @@
226
  },
227
  "outputs": { "outputT": { "shape": [2, 128, 4096], "dtype": "float32" } },
228
  "bench": { "metrics": [{ "type": "gflops", "value": "4 * args.qkPairs * args.headDim" }] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  }
230
  ]
231
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "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],
4
  "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]
 
225
  },
226
  "outputs": { "outputT": { "shape": [2, 128, 4096], "dtype": "float32" } },
227
  "bench": { "metrics": [{ "type": "gflops", "value": "4 * args.qkPairs * args.headDim" }] }
228
+ },
229
+ {
230
+ "name": "boundary-sgmat_capacity_d96_s64_packed0_rotary0",
231
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
232
+ "inputs": {
233
+ "queryT": {
234
+ "dtype": "float32",
235
+ "shape": [2, 64, 192],
236
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
237
+ },
238
+ "keyT": {
239
+ "dtype": "float32",
240
+ "shape": [2, 64, 192],
241
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
242
+ },
243
+ "valueT": {
244
+ "dtype": "float32",
245
+ "shape": [2, 64, 192],
246
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
247
+ },
248
+ "pastKeyT": {
249
+ "dtype": "float32",
250
+ "shape": [2, 2, 256, 96],
251
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
252
+ },
253
+ "pastValueT": {
254
+ "dtype": "float32",
255
+ "shape": [2, 2, 256, 96],
256
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
257
+ },
258
+ "blockRowIndicesT": {
259
+ "dtype": "int32",
260
+ "shape": [2, 5],
261
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
262
+ },
263
+ "blockColIndicesT": {
264
+ "dtype": "int32",
265
+ "shape": [2, 7],
266
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
267
+ },
268
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
269
+ "keyTotalSequenceLengthsT": {
270
+ "dtype": "int32",
271
+ "shape": [2],
272
+ "data": { "kind": "values", "values": [164, 132] }
273
+ }
274
+ },
275
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 64, 192] } },
276
+ "preset": "model"
277
+ },
278
+ {
279
+ "name": "boundary-sgmat_capacity_d96_s64_packed0_rotary1",
280
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 0 },
281
+ "inputs": {
282
+ "queryT": {
283
+ "dtype": "float32",
284
+ "shape": [2, 64, 192],
285
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
286
+ },
287
+ "keyT": {
288
+ "dtype": "float32",
289
+ "shape": [2, 64, 192],
290
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
291
+ },
292
+ "valueT": {
293
+ "dtype": "float32",
294
+ "shape": [2, 64, 192],
295
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
296
+ },
297
+ "pastKeyT": {
298
+ "dtype": "float32",
299
+ "shape": [2, 2, 256, 96],
300
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
301
+ },
302
+ "pastValueT": {
303
+ "dtype": "float32",
304
+ "shape": [2, 2, 256, 96],
305
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
306
+ },
307
+ "blockRowIndicesT": {
308
+ "dtype": "int32",
309
+ "shape": [2, 5],
310
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
311
+ },
312
+ "blockColIndicesT": {
313
+ "dtype": "int32",
314
+ "shape": [2, 7],
315
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
316
+ },
317
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
318
+ "keyTotalSequenceLengthsT": {
319
+ "dtype": "int32",
320
+ "shape": [2],
321
+ "data": { "kind": "values", "values": [164, 132] }
322
+ },
323
+ "cosCacheT": {
324
+ "dtype": "float32",
325
+ "shape": [256, 48],
326
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
327
+ },
328
+ "sinCacheT": {
329
+ "dtype": "float32",
330
+ "shape": [256, 48],
331
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
332
+ }
333
+ },
334
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 64, 192] } },
335
+ "preset": "model"
336
+ },
337
+ {
338
+ "name": "boundary-sgmat_capacity_d96_s64_packed1_rotary0",
339
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
340
+ "inputs": {
341
+ "queryT": {
342
+ "dtype": "float32",
343
+ "shape": [2, 64, 576],
344
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
345
+ },
346
+ "pastKeyT": {
347
+ "dtype": "float32",
348
+ "shape": [2, 2, 256, 96],
349
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
350
+ },
351
+ "pastValueT": {
352
+ "dtype": "float32",
353
+ "shape": [2, 2, 256, 96],
354
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
355
+ },
356
+ "blockRowIndicesT": {
357
+ "dtype": "int32",
358
+ "shape": [2, 5],
359
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
360
+ },
361
+ "blockColIndicesT": {
362
+ "dtype": "int32",
363
+ "shape": [2, 7],
364
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
365
+ },
366
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
367
+ "keyTotalSequenceLengthsT": {
368
+ "dtype": "int32",
369
+ "shape": [2],
370
+ "data": { "kind": "values", "values": [164, 132] }
371
+ }
372
+ },
373
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 64, 192] } },
374
+ "preset": "model"
375
+ },
376
+ {
377
+ "name": "boundary-sgmat_capacity_d96_s64_packed1_rotary1",
378
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 1 },
379
+ "inputs": {
380
+ "queryT": {
381
+ "dtype": "float32",
382
+ "shape": [2, 64, 576],
383
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
384
+ },
385
+ "pastKeyT": {
386
+ "dtype": "float32",
387
+ "shape": [2, 2, 256, 96],
388
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
389
+ },
390
+ "pastValueT": {
391
+ "dtype": "float32",
392
+ "shape": [2, 2, 256, 96],
393
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
394
+ },
395
+ "blockRowIndicesT": {
396
+ "dtype": "int32",
397
+ "shape": [2, 5],
398
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
399
+ },
400
+ "blockColIndicesT": {
401
+ "dtype": "int32",
402
+ "shape": [2, 7],
403
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
404
+ },
405
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
406
+ "keyTotalSequenceLengthsT": {
407
+ "dtype": "int32",
408
+ "shape": [2],
409
+ "data": { "kind": "values", "values": [164, 132] }
410
+ },
411
+ "cosCacheT": {
412
+ "dtype": "float32",
413
+ "shape": [256, 48],
414
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
415
+ },
416
+ "sinCacheT": {
417
+ "dtype": "float32",
418
+ "shape": [256, 48],
419
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
420
+ }
421
+ },
422
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 64, 192] } },
423
+ "preset": "model"
424
+ },
425
+ {
426
+ "name": "boundary-sgmat_capacity_d96_s65_packed0_rotary0",
427
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
428
+ "inputs": {
429
+ "queryT": {
430
+ "dtype": "float32",
431
+ "shape": [2, 65, 192],
432
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
433
+ },
434
+ "keyT": {
435
+ "dtype": "float32",
436
+ "shape": [2, 65, 192],
437
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
438
+ },
439
+ "valueT": {
440
+ "dtype": "float32",
441
+ "shape": [2, 65, 192],
442
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
443
+ },
444
+ "pastKeyT": {
445
+ "dtype": "float32",
446
+ "shape": [2, 2, 256, 96],
447
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
448
+ },
449
+ "pastValueT": {
450
+ "dtype": "float32",
451
+ "shape": [2, 2, 256, 96],
452
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
453
+ },
454
+ "blockRowIndicesT": {
455
+ "dtype": "int32",
456
+ "shape": [2, 5],
457
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
458
+ },
459
+ "blockColIndicesT": {
460
+ "dtype": "int32",
461
+ "shape": [2, 7],
462
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
463
+ },
464
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
465
+ "keyTotalSequenceLengthsT": {
466
+ "dtype": "int32",
467
+ "shape": [2],
468
+ "data": { "kind": "values", "values": [165, 133] }
469
+ }
470
+ },
471
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 65, 192] } },
472
+ "preset": "model"
473
+ },
474
+ {
475
+ "name": "boundary-sgmat_capacity_d96_s65_packed0_rotary1",
476
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 0 },
477
+ "inputs": {
478
+ "queryT": {
479
+ "dtype": "float32",
480
+ "shape": [2, 65, 192],
481
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
482
+ },
483
+ "keyT": {
484
+ "dtype": "float32",
485
+ "shape": [2, 65, 192],
486
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
487
+ },
488
+ "valueT": {
489
+ "dtype": "float32",
490
+ "shape": [2, 65, 192],
491
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
492
+ },
493
+ "pastKeyT": {
494
+ "dtype": "float32",
495
+ "shape": [2, 2, 256, 96],
496
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
497
+ },
498
+ "pastValueT": {
499
+ "dtype": "float32",
500
+ "shape": [2, 2, 256, 96],
501
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
502
+ },
503
+ "blockRowIndicesT": {
504
+ "dtype": "int32",
505
+ "shape": [2, 5],
506
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
507
+ },
508
+ "blockColIndicesT": {
509
+ "dtype": "int32",
510
+ "shape": [2, 7],
511
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
512
+ },
513
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
514
+ "keyTotalSequenceLengthsT": {
515
+ "dtype": "int32",
516
+ "shape": [2],
517
+ "data": { "kind": "values", "values": [165, 133] }
518
+ },
519
+ "cosCacheT": {
520
+ "dtype": "float32",
521
+ "shape": [256, 48],
522
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
523
+ },
524
+ "sinCacheT": {
525
+ "dtype": "float32",
526
+ "shape": [256, 48],
527
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
528
+ }
529
+ },
530
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 65, 192] } },
531
+ "preset": "model"
532
+ },
533
+ {
534
+ "name": "boundary-sgmat_capacity_d96_s65_packed1_rotary0",
535
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
536
+ "inputs": {
537
+ "queryT": {
538
+ "dtype": "float32",
539
+ "shape": [2, 65, 576],
540
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
541
+ },
542
+ "pastKeyT": {
543
+ "dtype": "float32",
544
+ "shape": [2, 2, 256, 96],
545
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
546
+ },
547
+ "pastValueT": {
548
+ "dtype": "float32",
549
+ "shape": [2, 2, 256, 96],
550
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
551
+ },
552
+ "blockRowIndicesT": {
553
+ "dtype": "int32",
554
+ "shape": [2, 5],
555
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
556
+ },
557
+ "blockColIndicesT": {
558
+ "dtype": "int32",
559
+ "shape": [2, 7],
560
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
561
+ },
562
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
563
+ "keyTotalSequenceLengthsT": {
564
+ "dtype": "int32",
565
+ "shape": [2],
566
+ "data": { "kind": "values", "values": [165, 133] }
567
+ }
568
+ },
569
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 65, 192] } },
570
+ "preset": "model"
571
+ },
572
+ {
573
+ "name": "boundary-sgmat_capacity_d96_s65_packed1_rotary1",
574
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 1 },
575
+ "inputs": {
576
+ "queryT": {
577
+ "dtype": "float32",
578
+ "shape": [2, 65, 576],
579
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
580
+ },
581
+ "pastKeyT": {
582
+ "dtype": "float32",
583
+ "shape": [2, 2, 256, 96],
584
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
585
+ },
586
+ "pastValueT": {
587
+ "dtype": "float32",
588
+ "shape": [2, 2, 256, 96],
589
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
590
+ },
591
+ "blockRowIndicesT": {
592
+ "dtype": "int32",
593
+ "shape": [2, 5],
594
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
595
+ },
596
+ "blockColIndicesT": {
597
+ "dtype": "int32",
598
+ "shape": [2, 7],
599
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
600
+ },
601
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
602
+ "keyTotalSequenceLengthsT": {
603
+ "dtype": "int32",
604
+ "shape": [2],
605
+ "data": { "kind": "values", "values": [165, 133] }
606
+ },
607
+ "cosCacheT": {
608
+ "dtype": "float32",
609
+ "shape": [256, 48],
610
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
611
+ },
612
+ "sinCacheT": {
613
+ "dtype": "float32",
614
+ "shape": [256, 48],
615
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
616
+ }
617
+ },
618
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 65, 192] } },
619
+ "preset": "model"
620
+ },
621
+ {
622
+ "name": "boundary-sgmat_prompt_separate_blk64_duplicate_csr",
623
+ "attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 64 },
624
+ "inputs": {
625
+ "queryT": {
626
+ "dtype": "float32",
627
+ "shape": [1, 192, 256],
628
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
629
+ },
630
+ "keyT": {
631
+ "dtype": "float32",
632
+ "shape": [1, 192, 128],
633
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
634
+ },
635
+ "valueT": {
636
+ "dtype": "float32",
637
+ "shape": [1, 192, 128],
638
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
639
+ },
640
+ "pastKeyT": { "dtype": "float32", "shape": [1, 2, 192, 64], "data": { "kind": "constant", "value": 0.0 } },
641
+ "pastValueT": { "dtype": "float32", "shape": [1, 2, 192, 64], "data": { "kind": "constant", "value": 0.0 } },
642
+ "blockRowIndicesT": {
643
+ "dtype": "int32",
644
+ "shape": [2, 4],
645
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 0, 1, 2, 3] }
646
+ },
647
+ "blockColIndicesT": {
648
+ "dtype": "int32",
649
+ "shape": [2, 5],
650
+ "data": { "kind": "values", "values": [0, 0, 1, 1, 1, 0, 1, 2, 0, 0] }
651
+ },
652
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [192] } },
653
+ "keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [192] } }
654
+ },
655
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 192, 256] } },
656
+ "preset": "model"
657
+ },
658
+ {
659
+ "name": "boundary-sgmat_prompt_tail_s100_explicit_scale",
660
+ "attrs": { "num_heads": 2, "kv_num_heads": 1, "sparse_block_size": 64, "scale": 0.25 },
661
+ "inputs": {
662
+ "queryT": {
663
+ "dtype": "float32",
664
+ "shape": [1, 100, 64],
665
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
666
+ },
667
+ "keyT": {
668
+ "dtype": "float32",
669
+ "shape": [1, 100, 32],
670
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
671
+ },
672
+ "valueT": {
673
+ "dtype": "float32",
674
+ "shape": [1, 100, 32],
675
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
676
+ },
677
+ "pastKeyT": { "dtype": "float32", "shape": [1, 1, 128, 32], "data": { "kind": "constant", "value": 0.0 } },
678
+ "pastValueT": { "dtype": "float32", "shape": [1, 1, 128, 32], "data": { "kind": "constant", "value": 0.0 } },
679
+ "blockRowIndicesT": { "dtype": "int32", "shape": [1, 3], "data": { "kind": "values", "values": [0, 1, 3] } },
680
+ "blockColIndicesT": { "dtype": "int32", "shape": [1, 3], "data": { "kind": "values", "values": [0, 0, 1] } },
681
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [100] } },
682
+ "keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [100] } }
683
+ },
684
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 100, 64] } },
685
+ "preset": "model"
686
+ },
687
+ {
688
+ "name": "boundary-sgmat_prompt_blk128_subtiles",
689
+ "attrs": { "num_heads": 2, "kv_num_heads": 1, "sparse_block_size": 128 },
690
+ "inputs": {
691
+ "queryT": {
692
+ "dtype": "float32",
693
+ "shape": [1, 256, 256],
694
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
695
+ },
696
+ "keyT": {
697
+ "dtype": "float32",
698
+ "shape": [1, 256, 128],
699
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
700
+ },
701
+ "valueT": {
702
+ "dtype": "float32",
703
+ "shape": [1, 256, 128],
704
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
705
+ },
706
+ "pastKeyT": { "dtype": "float32", "shape": [1, 1, 256, 128], "data": { "kind": "constant", "value": 0.0 } },
707
+ "pastValueT": { "dtype": "float32", "shape": [1, 1, 256, 128], "data": { "kind": "constant", "value": 0.0 } },
708
+ "blockRowIndicesT": { "dtype": "int32", "shape": [1, 3], "data": { "kind": "values", "values": [0, 1, 2] } },
709
+ "blockColIndicesT": { "dtype": "int32", "shape": [1, 2], "data": { "kind": "values", "values": [0, 1] } },
710
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
711
+ "keyTotalSequenceLengthsT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } }
712
+ },
713
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 256, 256] } },
714
+ "preset": "model"
715
+ },
716
+ {
717
+ "name": "boundary-sgmat_parallelism_d96_s128",
718
+ "attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 64 },
719
+ "inputs": {
720
+ "queryT": {
721
+ "dtype": "float32",
722
+ "shape": [2, 128, 384],
723
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
724
+ },
725
+ "keyT": {
726
+ "dtype": "float32",
727
+ "shape": [2, 128, 192],
728
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
729
+ },
730
+ "valueT": {
731
+ "dtype": "float32",
732
+ "shape": [2, 128, 192],
733
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
734
+ },
735
+ "pastKeyT": {
736
+ "dtype": "float32",
737
+ "shape": [2, 2, 256, 96],
738
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
739
+ },
740
+ "pastValueT": {
741
+ "dtype": "float32",
742
+ "shape": [2, 2, 256, 96],
743
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
744
+ },
745
+ "blockRowIndicesT": {
746
+ "dtype": "int32",
747
+ "shape": [2, 5],
748
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
749
+ },
750
+ "blockColIndicesT": {
751
+ "dtype": "int32",
752
+ "shape": [2, 7],
753
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
754
+ },
755
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
756
+ "keyTotalSequenceLengthsT": {
757
+ "dtype": "int32",
758
+ "shape": [2],
759
+ "data": { "kind": "values", "values": [256, 256] }
760
+ }
761
+ },
762
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 128, 384] } },
763
+ "preset": "model"
764
+ },
765
+ {
766
+ "name": "boundary-sgmat_parallelism_d96_s256",
767
+ "attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 64 },
768
+ "inputs": {
769
+ "queryT": {
770
+ "dtype": "float32",
771
+ "shape": [2, 256, 384],
772
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
773
+ },
774
+ "keyT": {
775
+ "dtype": "float32",
776
+ "shape": [2, 256, 192],
777
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
778
+ },
779
+ "valueT": {
780
+ "dtype": "float32",
781
+ "shape": [2, 256, 192],
782
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
783
+ },
784
+ "pastKeyT": {
785
+ "dtype": "float32",
786
+ "shape": [2, 2, 256, 96],
787
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
788
+ },
789
+ "pastValueT": {
790
+ "dtype": "float32",
791
+ "shape": [2, 2, 256, 96],
792
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
793
+ },
794
+ "blockRowIndicesT": {
795
+ "dtype": "int32",
796
+ "shape": [2, 5],
797
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
798
+ },
799
+ "blockColIndicesT": {
800
+ "dtype": "int32",
801
+ "shape": [2, 7],
802
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
803
+ },
804
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
805
+ "keyTotalSequenceLengthsT": {
806
+ "dtype": "int32",
807
+ "shape": [2],
808
+ "data": { "kind": "values", "values": [256, 256] }
809
+ }
810
+ },
811
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 256, 384] } },
812
+ "preset": "model"
813
+ },
814
+ {
815
+ "name": "sparse-low-parallelism-s64-h8-d32-past960",
816
+ "preset": "model",
817
+ "attrs": { "num_heads": 8, "kv_num_heads": 4, "sparse_block_size": 64 },
818
+ "inputs": {
819
+ "queryT": { "shape": [1, 64, 256], "dtype": "float32", "dist": "normal", "seed": 9300, "scale": 1 },
820
+ "keyT": { "shape": [1, 64, 128], "dtype": "float32", "dist": "normal", "seed": 9301, "scale": 1 },
821
+ "valueT": { "shape": [1, 64, 128], "dtype": "float32", "dist": "normal", "seed": 9302, "scale": 1 },
822
+ "pastKeyT": { "shape": [1, 4, 1024, 32], "dtype": "float32", "dist": "normal", "seed": 9303, "scale": 1 },
823
+ "pastValueT": { "shape": [1, 4, 1024, 32], "dtype": "float32", "dist": "normal", "seed": 9304, "scale": 1 },
824
+ "blockRowIndicesT": {
825
+ "shape": [4, 17],
826
+ "dtype": "int32",
827
+ "data": {
828
+ "kind": "values",
829
+ "values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
830
+ }
831
+ },
832
+ "blockColIndicesT": {
833
+ "shape": [4, 112],
834
+ "dtype": "int32",
835
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
836
+ },
837
+ "totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
838
+ "keyTotalSequenceLengthsT": { "shape": [1], "dtype": "int32", "dist": "constant", "value": 1024 }
839
+ },
840
+ "outputs": { "outputT": { "shape": [1, 64, 256], "dtype": "float32" } }
841
+ },
842
+ {
843
+ "name": "sparse-low-parallelism-s64-h8-d96-past960",
844
+ "preset": "model",
845
+ "attrs": { "num_heads": 8, "kv_num_heads": 4, "sparse_block_size": 64 },
846
+ "inputs": {
847
+ "queryT": { "shape": [1, 64, 768], "dtype": "float32", "dist": "normal", "seed": 9300, "scale": 1 },
848
+ "keyT": { "shape": [1, 64, 384], "dtype": "float32", "dist": "normal", "seed": 9301, "scale": 1 },
849
+ "valueT": { "shape": [1, 64, 384], "dtype": "float32", "dist": "normal", "seed": 9302, "scale": 1 },
850
+ "pastKeyT": { "shape": [1, 4, 1024, 96], "dtype": "float32", "dist": "normal", "seed": 9303, "scale": 1 },
851
+ "pastValueT": { "shape": [1, 4, 1024, 96], "dtype": "float32", "dist": "normal", "seed": 9304, "scale": 1 },
852
+ "blockRowIndicesT": {
853
+ "shape": [4, 17],
854
+ "dtype": "int32",
855
+ "data": {
856
+ "kind": "values",
857
+ "values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
858
+ }
859
+ },
860
+ "blockColIndicesT": {
861
+ "shape": [4, 112],
862
+ "dtype": "int32",
863
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
864
+ },
865
+ "totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
866
+ "keyTotalSequenceLengthsT": { "shape": [1], "dtype": "int32", "dist": "constant", "value": 1024 }
867
+ },
868
+ "outputs": { "outputT": { "shape": [1, 64, 768], "dtype": "float32" } }
869
+ },
870
+ {
871
+ "name": "sparse-low-parallelism-s64-h8-d128-past960",
872
+ "preset": "model",
873
+ "attrs": { "num_heads": 8, "kv_num_heads": 4, "sparse_block_size": 64 },
874
+ "inputs": {
875
+ "queryT": { "shape": [1, 64, 1024], "dtype": "float32", "dist": "normal", "seed": 9300, "scale": 1 },
876
+ "keyT": { "shape": [1, 64, 512], "dtype": "float32", "dist": "normal", "seed": 9301, "scale": 1 },
877
+ "valueT": { "shape": [1, 64, 512], "dtype": "float32", "dist": "normal", "seed": 9302, "scale": 1 },
878
+ "pastKeyT": { "shape": [1, 4, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9303, "scale": 1 },
879
+ "pastValueT": { "shape": [1, 4, 1024, 128], "dtype": "float32", "dist": "normal", "seed": 9304, "scale": 1 },
880
+ "blockRowIndicesT": {
881
+ "shape": [4, 17],
882
+ "dtype": "int32",
883
+ "data": {
884
+ "kind": "values",
885
+ "values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
886
+ }
887
+ },
888
+ "blockColIndicesT": {
889
+ "shape": [4, 112],
890
+ "dtype": "int32",
891
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
892
+ },
893
+ "totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1024] } },
894
+ "keyTotalSequenceLengthsT": { "shape": [1], "dtype": "int32", "dist": "constant", "value": 1024 }
895
+ },
896
+ "outputs": { "outputT": { "shape": [1, 64, 1024], "dtype": "float32" } }
897
+ },
898
+ {
899
+ "name": "sparse-crossover-s128-h8-d96-past128",
900
+ "preset": "model",
901
+ "attrs": { "num_heads": 8, "kv_num_heads": 4, "sparse_block_size": 64 },
902
+ "inputs": {
903
+ "queryT": { "shape": [1, 128, 768], "dtype": "float32", "dist": "normal", "seed": 9300, "scale": 1 },
904
+ "keyT": { "shape": [1, 128, 384], "dtype": "float32", "dist": "normal", "seed": 9301, "scale": 1 },
905
+ "valueT": { "shape": [1, 128, 384], "dtype": "float32", "dist": "normal", "seed": 9302, "scale": 1 },
906
+ "pastKeyT": { "shape": [1, 4, 1024, 96], "dtype": "float32", "dist": "normal", "seed": 9303, "scale": 1 },
907
+ "pastValueT": { "shape": [1, 4, 1024, 96], "dtype": "float32", "dist": "normal", "seed": 9304, "scale": 1 },
908
+ "blockRowIndicesT": {
909
+ "shape": [4, 17],
910
+ "dtype": "int32",
911
+ "data": {
912
+ "kind": "values",
913
+ "values": { "$ref": "#/fixtureArrays/sparse-prompt-b1-s1024-h32kv8-d128-blk64_input_blockRowIndicesT" }
914
+ }
915
+ },
916
+ "blockColIndicesT": {
917
+ "shape": [4, 112],
918
+ "dtype": "int32",
919
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/block_column_indices_t_pattern" } }
920
+ },
921
+ "totalSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [256] } },
922
+ "keyTotalSequenceLengthsT": { "shape": [1], "dtype": "int32", "dist": "constant", "value": 256 }
923
+ },
924
+ "outputs": { "outputT": { "shape": [1, 128, 768], "dtype": "float32" } }
925
  }
926
  ]
927
  }
build/webgpu/manifest.json CHANGED
@@ -2,109 +2,31 @@
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 },
@@ -112,31 +34,13 @@
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": {
135
- "WORKGROUP_SIZE": 128,
136
- "APPEND_WORKGROUP_SIZE": 256,
137
- "NARROW_MIN_WORKGROUPS": 1024,
138
- "QUERY_TILE": 4,
139
- "V_STAGE_MAX_WORKGROUPS": 512
 
140
  },
141
  "derive": {
142
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
@@ -178,7 +82,7 @@
178
  "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)",
179
  "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\"",
180
  "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\"",
181
- "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",
182
  "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",
183
  "packedContract": "contract and packedQkv and not useRotary",
184
  "packedRotaryContract": "contract and packedQkv and useRotary",
@@ -186,622 +90,112 @@
186
  "separateRotaryContract": "contract and not packedQkv and useRotary",
187
  "sparseVStageWorthIt": "sparseQueryTiles * batchSize * numHeads <= tunables.V_STAGE_MAX_WORKGROUPS",
188
  "sgmatQueryTiles": "ceilDiv(seqLen, 64)",
189
- "sparseSgmatLdsBytes": "(64 * 32 + 64 * 64 + 64 * 2 + 128 * 2) * 4",
190
- "sparseSgmatGeometryOk": "256 <= device.limits.maxComputeInvocationsPerWorkgroup and 256 <= device.limits.maxComputeWorkgroupSizeX and sgmatQueryTiles <= device.limits.maxComputeWorkgroupsPerDimension and batchSize * numHeads <= device.limits.maxComputeWorkgroupsPerDimension and sparseSgmatLdsBytes <= device.limits.maxComputeWorkgroupStorageSize",
191
- "sparseSgmatOk": "tensorDtypes.queryT == \"float32\" and seqLen >= 64 and sparseBlockSize % 64 == 0 and headSize % 32 == 0 and headSize <= 128 and maxCacheSeq % 64 == 0 and device.features.has(\"subgroups\") and wave32Effective and device.features.has(\"chromium-experimental-subgroup-matrix\") and sparseSgmatGeometryOk"
192
- },
193
- "constants": {
 
194
  "scalar": "dtypes.T",
195
- "usesF16": "dtypes.T == \"f16\"",
196
- "numHeads": "numHeads",
197
- "kvNumHeads": "kvNumHeads",
198
- "headSize": "headSize",
199
- "headVec": "headVec",
200
  "cacheVec": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\"",
201
  "attnWorkgroup": "sparseAttnWorkgroup",
202
- "maxCacheSeq": "maxCacheSeq",
203
- "sparseBlockSize": "sparseBlockSize",
204
- "numLayout": "numLayout",
205
- "maxBlocks": "maxBlocks",
206
- "maxNnz": "maxNnz",
207
- "packedStride": "packedStride",
208
- "packedQkv": "packedQkv",
209
  "usesRotary": "useRotary",
210
- "rotaryHalf": "rotaryHalf",
211
- "rotaryDim": "rotaryDim",
212
- "rotaryInterleaved": "rotaryInterleaved",
213
  "appendWorkgroupSize": "tunables.APPEND_WORKGROUP_SIZE"
214
  },
215
- "bindingSets": {
216
- "appendSeparate": [
217
- {
218
- "name": "new_key",
219
- "arg": "keyT",
220
- "semantic": "key",
221
- "buffer": { "type": "read-only-storage" },
222
- "elementType": "$scalar"
223
- },
224
- {
225
- "name": "new_value",
226
- "arg": "valueT",
227
- "semantic": "value",
228
- "buffer": { "type": "read-only-storage" },
229
- "elementType": "$scalar"
230
- },
231
- {
232
- "name": "present_key",
233
- "arg": "pastKeyT",
234
- "semantic": "past_key",
235
- "buffer": { "type": "storage" },
236
- "elementType": "$scalar"
237
- },
238
- {
239
- "name": "present_value",
240
- "arg": "pastValueT",
241
- "semantic": "past_value",
242
- "buffer": { "type": "storage" },
243
- "elementType": "$scalar"
244
- },
245
- {
246
- "name": "key_total_sequence_lengths",
247
- "arg": "keyTotalSequenceLengthsT",
248
- "semantic": "key_total_sequence_lengths",
249
- "buffer": { "type": "read-only-storage" },
250
- "elementType": "i32"
251
- },
252
- {
253
- "name": "total_sequence_length",
254
- "arg": "totalSequenceLengthT",
255
- "semantic": "total_sequence_length",
256
- "buffer": { "type": "read-only-storage" },
257
- "elementType": "i32"
258
- },
259
- {
260
- "name": "params",
261
- "semantic": "kernel.params",
262
- "buffer": { "type": "uniform" },
263
- "struct": {
264
- "name": "Params",
265
- "fields": [
266
- { "name": "batchSize", "type": "u32", "value": "batchSize" },
267
- { "name": "seqLen", "type": "u32", "value": "seqLen" }
268
- ]
269
- }
270
- }
271
- ],
272
- "appendSeparateRotary": [
273
- {
274
- "name": "new_key",
275
- "arg": "keyT",
276
- "semantic": "key",
277
- "buffer": { "type": "read-only-storage" },
278
- "elementType": "$scalar"
279
- },
280
- {
281
- "name": "new_value",
282
- "arg": "valueT",
283
- "semantic": "value",
284
- "buffer": { "type": "read-only-storage" },
285
- "elementType": "$scalar"
286
- },
287
- {
288
- "name": "present_key",
289
- "arg": "pastKeyT",
290
- "semantic": "past_key",
291
- "buffer": { "type": "storage" },
292
- "elementType": "$scalar"
293
- },
294
- {
295
- "name": "present_value",
296
- "arg": "pastValueT",
297
- "semantic": "past_value",
298
- "buffer": { "type": "storage" },
299
- "elementType": "$scalar"
300
- },
301
- {
302
- "name": "key_total_sequence_lengths",
303
- "arg": "keyTotalSequenceLengthsT",
304
- "semantic": "key_total_sequence_lengths",
305
- "buffer": { "type": "read-only-storage" },
306
- "elementType": "i32"
307
- },
308
- {
309
- "name": "total_sequence_length",
310
- "arg": "totalSequenceLengthT",
311
- "semantic": "total_sequence_length",
312
- "buffer": { "type": "read-only-storage" },
313
- "elementType": "i32"
314
- },
315
- {
316
- "name": "cos_cache",
317
- "arg": "cosCacheT",
318
- "semantic": "cos_cache",
319
- "buffer": { "type": "read-only-storage" },
320
- "elementType": "$scalar"
321
- },
322
- {
323
- "name": "sin_cache",
324
- "arg": "sinCacheT",
325
- "semantic": "sin_cache",
326
- "buffer": { "type": "read-only-storage" },
327
- "elementType": "$scalar"
328
- },
329
- {
330
- "name": "params",
331
- "semantic": "kernel.params",
332
- "buffer": { "type": "uniform" },
333
- "struct": {
334
- "name": "Params",
335
- "fields": [
336
- { "name": "batchSize", "type": "u32", "value": "batchSize" },
337
- { "name": "seqLen", "type": "u32", "value": "seqLen" }
338
- ]
339
- }
340
- }
341
- ],
342
- "appendPacked": [
343
- {
344
- "name": "packed_qkv",
345
- "arg": "queryT",
346
- "semantic": "query",
347
- "buffer": { "type": "read-only-storage" },
348
- "elementType": "$scalar"
349
- },
350
- {
351
- "name": "present_key",
352
- "arg": "pastKeyT",
353
- "semantic": "past_key",
354
- "buffer": { "type": "storage" },
355
- "elementType": "$scalar"
356
- },
357
- {
358
- "name": "present_value",
359
- "arg": "pastValueT",
360
- "semantic": "past_value",
361
- "buffer": { "type": "storage" },
362
- "elementType": "$scalar"
363
- },
364
- {
365
- "name": "key_total_sequence_lengths",
366
- "arg": "keyTotalSequenceLengthsT",
367
- "semantic": "key_total_sequence_lengths",
368
- "buffer": { "type": "read-only-storage" },
369
- "elementType": "i32"
370
- },
371
- {
372
- "name": "total_sequence_length",
373
- "arg": "totalSequenceLengthT",
374
- "semantic": "total_sequence_length",
375
- "buffer": { "type": "read-only-storage" },
376
- "elementType": "i32"
377
- },
378
- {
379
- "name": "params",
380
- "semantic": "kernel.params",
381
- "buffer": { "type": "uniform" },
382
- "struct": {
383
- "name": "Params",
384
- "fields": [
385
- { "name": "batchSize", "type": "u32", "value": "batchSize" },
386
- { "name": "seqLen", "type": "u32", "value": "seqLen" }
387
- ]
388
- }
389
- }
390
- ],
391
- "appendPackedRotary": [
392
- {
393
- "name": "packed_qkv",
394
- "arg": "queryT",
395
- "semantic": "query",
396
- "buffer": { "type": "read-only-storage" },
397
- "elementType": "$scalar"
398
- },
399
- {
400
- "name": "present_key",
401
- "arg": "pastKeyT",
402
- "semantic": "past_key",
403
- "buffer": { "type": "storage" },
404
- "elementType": "$scalar"
405
- },
406
- {
407
- "name": "present_value",
408
- "arg": "pastValueT",
409
- "semantic": "past_value",
410
- "buffer": { "type": "storage" },
411
- "elementType": "$scalar"
412
- },
413
- {
414
- "name": "key_total_sequence_lengths",
415
- "arg": "keyTotalSequenceLengthsT",
416
- "semantic": "key_total_sequence_lengths",
417
- "buffer": { "type": "read-only-storage" },
418
- "elementType": "i32"
419
- },
420
- {
421
- "name": "total_sequence_length",
422
- "arg": "totalSequenceLengthT",
423
- "semantic": "total_sequence_length",
424
- "buffer": { "type": "read-only-storage" },
425
- "elementType": "i32"
426
- },
427
- {
428
- "name": "cos_cache",
429
- "arg": "cosCacheT",
430
- "semantic": "cos_cache",
431
- "buffer": { "type": "read-only-storage" },
432
- "elementType": "$scalar"
433
- },
434
- {
435
- "name": "sin_cache",
436
- "arg": "sinCacheT",
437
- "semantic": "sin_cache",
438
- "buffer": { "type": "read-only-storage" },
439
- "elementType": "$scalar"
440
- },
441
- {
442
- "name": "params",
443
- "semantic": "kernel.params",
444
- "buffer": { "type": "uniform" },
445
- "struct": {
446
- "name": "Params",
447
- "fields": [
448
- { "name": "batchSize", "type": "u32", "value": "batchSize" },
449
- { "name": "seqLen", "type": "u32", "value": "seqLen" }
450
- ]
451
- }
452
- }
453
- ],
454
- "qRotary": [
455
- {
456
- "name": "query",
457
- "arg": "queryT",
458
- "semantic": "query",
459
- "buffer": { "type": "read-only-storage" },
460
- "elementType": "$scalar"
461
- },
462
- {
463
- "name": "cos_cache",
464
- "arg": "cosCacheT",
465
- "semantic": "cos_cache",
466
- "buffer": { "type": "read-only-storage" },
467
- "elementType": "$scalar"
468
- },
469
- {
470
- "name": "sin_cache",
471
- "arg": "sinCacheT",
472
- "semantic": "sin_cache",
473
- "buffer": { "type": "read-only-storage" },
474
- "elementType": "$scalar"
475
- },
476
- { "name": "q_rotary", "semantic": "QRotary", "buffer": { "type": "storage" }, "elementType": "f32" },
477
- {
478
- "name": "key_total_sequence_lengths",
479
- "arg": "keyTotalSequenceLengthsT",
480
- "semantic": "key_total_sequence_lengths",
481
- "buffer": { "type": "read-only-storage" },
482
- "elementType": "i32"
483
- },
484
- {
485
- "name": "total_sequence_length",
486
- "arg": "totalSequenceLengthT",
487
- "semantic": "total_sequence_length",
488
- "buffer": { "type": "read-only-storage" },
489
- "elementType": "i32"
490
- },
491
- {
492
- "name": "params",
493
- "semantic": "kernel.params",
494
- "buffer": { "type": "uniform" },
495
- "struct": {
496
- "name": "Params",
497
- "fields": [
498
- { "name": "batchSize", "type": "u32", "value": "batchSize" },
499
- { "name": "seqLen", "type": "u32", "value": "seqLen" }
500
- ]
501
- }
502
- }
503
- ],
504
- "attentionDirect": [
505
- {
506
- "name": "query",
507
- "arg": "queryT",
508
- "semantic": "query",
509
- "buffer": { "type": "read-only-storage" },
510
- "elementType": "$scalar"
511
- },
512
- {
513
- "name": "present_key",
514
- "arg": "pastKeyT",
515
- "semantic": "past_key",
516
- "buffer": { "type": "read-only-storage" },
517
- "elementType": "$cacheVec"
518
- },
519
- {
520
- "name": "present_value",
521
- "arg": "pastValueT",
522
- "semantic": "past_value",
523
- "buffer": { "type": "read-only-storage" },
524
- "elementType": "$cacheVec"
525
- },
526
- {
527
- "name": "block_row_indices",
528
- "arg": "blockRowIndicesT",
529
- "semantic": "block_row_indices",
530
- "buffer": { "type": "read-only-storage" },
531
- "elementType": "i32"
532
- },
533
- {
534
- "name": "block_col_indices",
535
- "arg": "blockColIndicesT",
536
- "semantic": "block_col_indices",
537
- "buffer": { "type": "read-only-storage" },
538
- "elementType": "i32"
539
- },
540
- {
541
- "name": "key_total_sequence_lengths",
542
- "arg": "keyTotalSequenceLengthsT",
543
- "semantic": "key_total_sequence_lengths",
544
- "buffer": { "type": "read-only-storage" },
545
- "elementType": "i32"
546
- },
547
- {
548
- "name": "total_sequence_length",
549
- "arg": "totalSequenceLengthT",
550
- "semantic": "total_sequence_length",
551
- "buffer": { "type": "read-only-storage" },
552
- "elementType": "i32"
553
- },
554
- {
555
- "name": "output",
556
- "arg": "outputT",
557
- "semantic": "output",
558
- "buffer": { "type": "storage" },
559
- "elementType": "$scalar"
560
- },
561
- {
562
- "name": "params",
563
- "semantic": "kernel.params",
564
- "buffer": { "type": "uniform" },
565
- "struct": {
566
- "name": "Params",
567
- "fields": [
568
- { "name": "seqLen", "type": "u32", "value": "seqLen" },
569
- { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
570
- ]
571
- }
572
- }
573
- ],
574
- "attentionSgmat": [
575
- {
576
- "name": "query",
577
- "arg": "queryT",
578
- "semantic": "query",
579
- "buffer": { "type": "read-only-storage" },
580
- "elementType": "$scalar"
581
- },
582
- {
583
- "name": "present_key",
584
- "arg": "pastKeyT",
585
- "semantic": "past_key",
586
- "buffer": { "type": "read-only-storage" },
587
- "elementType": "$scalar"
588
- },
589
- {
590
- "name": "present_value",
591
- "arg": "pastValueT",
592
- "semantic": "past_value",
593
- "buffer": { "type": "read-only-storage" },
594
- "elementType": "$scalar"
595
- },
596
- {
597
- "name": "block_row_indices",
598
- "arg": "blockRowIndicesT",
599
- "semantic": "block_row_indices",
600
- "buffer": { "type": "read-only-storage" },
601
- "elementType": "i32"
602
- },
603
- {
604
- "name": "block_col_indices",
605
- "arg": "blockColIndicesT",
606
- "semantic": "block_col_indices",
607
- "buffer": { "type": "read-only-storage" },
608
- "elementType": "i32"
609
- },
610
- {
611
- "name": "key_total_sequence_lengths",
612
- "arg": "keyTotalSequenceLengthsT",
613
- "semantic": "key_total_sequence_lengths",
614
- "buffer": { "type": "read-only-storage" },
615
- "elementType": "i32"
616
- },
617
- {
618
- "name": "total_sequence_length",
619
- "arg": "totalSequenceLengthT",
620
- "semantic": "total_sequence_length",
621
- "buffer": { "type": "read-only-storage" },
622
- "elementType": "i32"
623
- },
624
- {
625
- "name": "output",
626
- "arg": "outputT",
627
- "semantic": "output",
628
- "buffer": { "type": "storage" },
629
- "elementType": "$scalar"
630
- },
631
- {
632
- "name": "params",
633
- "semantic": "kernel.params",
634
- "buffer": { "type": "uniform" },
635
- "struct": {
636
- "name": "Params",
637
- "fields": [
638
- { "name": "seqLen", "type": "u32", "value": "seqLen" },
639
- { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
640
- ]
641
- }
642
- }
643
- ],
644
- "attentionRotary": [
645
- { "name": "q_rotary", "semantic": "QRotary", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
646
- {
647
- "name": "present_key",
648
- "arg": "pastKeyT",
649
- "semantic": "past_key",
650
- "buffer": { "type": "read-only-storage" },
651
- "elementType": "$cacheVec"
652
- },
653
- {
654
- "name": "present_value",
655
- "arg": "pastValueT",
656
- "semantic": "past_value",
657
- "buffer": { "type": "read-only-storage" },
658
- "elementType": "$cacheVec"
659
- },
660
- {
661
- "name": "block_row_indices",
662
- "arg": "blockRowIndicesT",
663
- "semantic": "block_row_indices",
664
- "buffer": { "type": "read-only-storage" },
665
- "elementType": "i32"
666
- },
667
- {
668
- "name": "block_col_indices",
669
- "arg": "blockColIndicesT",
670
- "semantic": "block_col_indices",
671
- "buffer": { "type": "read-only-storage" },
672
- "elementType": "i32"
673
- },
674
- {
675
- "name": "key_total_sequence_lengths",
676
- "arg": "keyTotalSequenceLengthsT",
677
- "semantic": "key_total_sequence_lengths",
678
- "buffer": { "type": "read-only-storage" },
679
- "elementType": "i32"
680
- },
681
- {
682
- "name": "total_sequence_length",
683
- "arg": "totalSequenceLengthT",
684
- "semantic": "total_sequence_length",
685
- "buffer": { "type": "read-only-storage" },
686
- "elementType": "i32"
687
- },
688
- {
689
- "name": "output",
690
- "arg": "outputT",
691
- "semantic": "output",
692
- "buffer": { "type": "storage" },
693
- "elementType": "$scalar"
694
- },
695
- {
696
- "name": "params",
697
- "semantic": "kernel.params",
698
- "buffer": { "type": "uniform" },
699
- "struct": {
700
- "name": "Params",
701
- "fields": [
702
- { "name": "seqLen", "type": "u32", "value": "seqLen" },
703
- { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
704
- ]
705
- }
706
- }
707
- ],
708
- "attentionSgmatRotary": [
709
- { "name": "q_rotary", "semantic": "QRotary", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
710
- {
711
- "name": "present_key",
712
- "arg": "pastKeyT",
713
- "semantic": "past_key",
714
- "buffer": { "type": "read-only-storage" },
715
- "elementType": "$scalar"
716
- },
717
- {
718
- "name": "present_value",
719
- "arg": "pastValueT",
720
- "semantic": "past_value",
721
- "buffer": { "type": "read-only-storage" },
722
- "elementType": "$scalar"
723
- },
724
- {
725
- "name": "block_row_indices",
726
- "arg": "blockRowIndicesT",
727
- "semantic": "block_row_indices",
728
- "buffer": { "type": "read-only-storage" },
729
- "elementType": "i32"
730
- },
731
- {
732
- "name": "block_col_indices",
733
- "arg": "blockColIndicesT",
734
- "semantic": "block_col_indices",
735
- "buffer": { "type": "read-only-storage" },
736
- "elementType": "i32"
737
- },
738
- {
739
- "name": "key_total_sequence_lengths",
740
- "arg": "keyTotalSequenceLengthsT",
741
- "semantic": "key_total_sequence_lengths",
742
- "buffer": { "type": "read-only-storage" },
743
- "elementType": "i32"
744
- },
745
- {
746
- "name": "total_sequence_length",
747
- "arg": "totalSequenceLengthT",
748
- "semantic": "total_sequence_length",
749
- "buffer": { "type": "read-only-storage" },
750
- "elementType": "i32"
751
- },
752
- {
753
- "name": "output",
754
- "arg": "outputT",
755
- "semantic": "output",
756
- "buffer": { "type": "storage" },
757
- "elementType": "$scalar"
758
- },
759
- {
760
- "name": "params",
761
- "semantic": "kernel.params",
762
- "buffer": { "type": "uniform" },
763
- "struct": {
764
- "name": "Params",
765
- "fields": [
766
- { "name": "seqLen", "type": "u32", "value": "seqLen" },
767
- { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
768
- ]
769
- }
770
- }
771
- ]
772
  },
773
  "variants": [
774
  {
775
  "id": "separate",
776
  "priority": 0,
777
- "when": ["separateContract", "geometryOk"],
 
778
  "passes": [
779
  {
780
  "id": "append",
781
  "name": "SparseAttention.Append",
782
  "shader": "sparse-kv-append.wgsl.jinja",
783
- "bindings": "appendSeparate",
784
- "constants": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
785
  "dispatch": {
786
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
787
- "workgroupSize": "constants.appendWorkgroupSize"
 
788
  }
789
  },
790
  {
791
  "id": "attention",
792
  "name": "SparseAttention.Attention",
793
  "shader": "sparse-attention.wgsl.jinja",
794
- "bindings": "attentionDirect",
795
- "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
796
- "constants": { "qTile": "sparseQueryTile" }
797
  }
798
- ],
799
- "constants": { "vStageWorthIt": "sparseVStageWorthIt" }
800
  },
801
  {
802
  "id": "separate_sgmat",
803
  "priority": 20,
804
- "when": ["separateContract", "geometryOk", "sparseSgmatOk"],
805
  "requires": {
806
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
807
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
@@ -811,64 +205,69 @@
811
  "id": "append",
812
  "name": "SparseAttention.Append",
813
  "shader": "sparse-kv-append.wgsl.jinja",
814
- "bindings": "appendSeparate",
815
- "constants": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
816
  "dispatch": {
817
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
818
- "workgroupSize": "constants.appendWorkgroupSize"
 
819
  }
820
  },
821
  {
822
  "id": "attention",
823
  "name": "SparseAttention.AttentionSgmat",
824
  "shader": "sparse-attention-sgmat.wgsl.jinja",
825
- "bindings": "attentionSgmat",
826
- "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" }
 
827
  }
828
- ]
 
829
  },
830
  {
831
  "id": "separate_rotary",
832
  "priority": 10,
833
- "when": ["separateRotaryContract", "geometryOk"],
 
834
  "intermediates": [{ "id": "QRotary", "dtype": "float32", "shape": "[qRotaryElements]" }],
835
  "passes": [
836
  {
837
  "id": "append",
838
  "name": "SparseAttention.Append",
839
  "shader": "sparse-kv-append.wgsl.jinja",
840
- "bindings": "appendSeparateRotary",
841
- "constants": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
842
  "dispatch": {
843
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
844
- "workgroupSize": "constants.appendWorkgroupSize"
 
845
  }
846
  },
847
  {
848
  "id": "qrotary",
849
  "name": "SparseAttention.QueryRotary",
850
  "shader": "sparse-q-rotary.wgsl.jinja",
851
- "bindings": "qRotary",
852
  "dispatch": {
853
- "threads": "batchSize * numHeads * seqLen * headSize",
854
- "workgroupSize": "constants.appendWorkgroupSize"
 
855
  }
856
  },
857
  {
858
  "id": "attention",
859
  "name": "SparseAttention.Attention",
860
  "shader": "sparse-attention.wgsl.jinja",
861
- "bindings": "attentionRotary",
862
- "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
863
- "constants": { "qTile": "sparseQueryTile" }
864
  }
865
- ],
866
- "constants": { "vStageWorthIt": "sparseVStageWorthIt" }
867
  },
868
  {
869
  "id": "separate_rotary_sgmat",
870
  "priority": 30,
871
- "when": ["separateRotaryContract", "geometryOk", "sparseSgmatOk"],
872
  "requires": {
873
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
874
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
@@ -879,63 +278,68 @@
879
  "id": "append",
880
  "name": "SparseAttention.Append",
881
  "shader": "sparse-kv-append.wgsl.jinja",
882
- "bindings": "appendSeparateRotary",
883
- "constants": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
884
  "dispatch": {
885
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
886
- "workgroupSize": "constants.appendWorkgroupSize"
 
887
  }
888
  },
889
  {
890
  "id": "qrotary",
891
  "name": "SparseAttention.QueryRotary",
892
  "shader": "sparse-q-rotary.wgsl.jinja",
893
- "bindings": "qRotary",
894
  "dispatch": {
895
- "threads": "batchSize * numHeads * seqLen * headSize",
896
- "workgroupSize": "constants.appendWorkgroupSize"
 
897
  }
898
  },
899
  {
900
  "id": "attention",
901
  "name": "SparseAttention.AttentionSgmat",
902
  "shader": "sparse-attention-sgmat.wgsl.jinja",
903
- "bindings": "attentionSgmatRotary",
904
- "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" }
 
905
  }
906
- ]
 
907
  },
908
  {
909
  "id": "packed",
910
  "priority": 0,
911
- "when": ["packedContract", "geometryOk"],
 
912
  "passes": [
913
  {
914
  "id": "append",
915
  "name": "SparseAttention.Append",
916
  "shader": "sparse-kv-append.wgsl.jinja",
917
- "bindings": "appendPacked",
918
- "constants": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
919
  "dispatch": {
920
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
921
- "workgroupSize": "constants.appendWorkgroupSize"
 
922
  }
923
  },
924
  {
925
  "id": "attention",
926
  "name": "SparseAttention.Attention",
927
  "shader": "sparse-attention.wgsl.jinja",
928
- "bindings": "attentionDirect",
929
- "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
930
- "constants": { "qTile": "sparseQueryTile" }
931
  }
932
- ],
933
- "constants": { "vStageWorthIt": "sparseVStageWorthIt" }
934
  },
935
  {
936
  "id": "packed_sgmat",
937
  "priority": 20,
938
- "when": ["packedContract", "geometryOk", "sparseSgmatOk"],
939
  "requires": {
940
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
941
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
@@ -945,64 +349,69 @@
945
  "id": "append",
946
  "name": "SparseAttention.Append",
947
  "shader": "sparse-kv-append.wgsl.jinja",
948
- "bindings": "appendPacked",
949
- "constants": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
950
  "dispatch": {
951
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
952
- "workgroupSize": "constants.appendWorkgroupSize"
 
953
  }
954
  },
955
  {
956
  "id": "attention",
957
  "name": "SparseAttention.AttentionSgmat",
958
  "shader": "sparse-attention-sgmat.wgsl.jinja",
959
- "bindings": "attentionSgmat",
960
- "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" }
 
961
  }
962
- ]
 
963
  },
964
  {
965
  "id": "packed_rotary",
966
  "priority": 10,
967
- "when": ["packedRotaryContract", "geometryOk"],
 
968
  "intermediates": [{ "id": "QRotary", "dtype": "float32", "shape": "[qRotaryElements]" }],
969
  "passes": [
970
  {
971
  "id": "append",
972
  "name": "SparseAttention.Append",
973
  "shader": "sparse-kv-append.wgsl.jinja",
974
- "bindings": "appendPackedRotary",
975
- "constants": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
976
  "dispatch": {
977
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
978
- "workgroupSize": "constants.appendWorkgroupSize"
 
979
  }
980
  },
981
  {
982
  "id": "qrotary",
983
  "name": "SparseAttention.QueryRotary",
984
  "shader": "sparse-q-rotary.wgsl.jinja",
985
- "bindings": "qRotary",
986
  "dispatch": {
987
- "threads": "batchSize * numHeads * seqLen * headSize",
988
- "workgroupSize": "constants.appendWorkgroupSize"
 
989
  }
990
  },
991
  {
992
  "id": "attention",
993
  "name": "SparseAttention.Attention",
994
  "shader": "sparse-attention.wgsl.jinja",
995
- "bindings": "attentionRotary",
996
- "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" },
997
- "constants": { "qTile": "sparseQueryTile" }
998
  }
999
- ],
1000
- "constants": { "vStageWorthIt": "sparseVStageWorthIt" }
1001
  },
1002
  {
1003
  "id": "packed_rotary_sgmat",
1004
  "priority": 30,
1005
- "when": ["packedRotaryContract", "geometryOk", "sparseSgmatOk"],
1006
  "requires": {
1007
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
1008
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
@@ -1013,31 +422,35 @@
1013
  "id": "append",
1014
  "name": "SparseAttention.Append",
1015
  "shader": "sparse-kv-append.wgsl.jinja",
1016
- "bindings": "appendPackedRotary",
1017
- "constants": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
1018
  "dispatch": {
1019
- "threads": "batchSize * kvNumHeads * seqLen * headSize",
1020
- "workgroupSize": "constants.appendWorkgroupSize"
 
1021
  }
1022
  },
1023
  {
1024
  "id": "qrotary",
1025
  "name": "SparseAttention.QueryRotary",
1026
  "shader": "sparse-q-rotary.wgsl.jinja",
1027
- "bindings": "qRotary",
1028
  "dispatch": {
1029
- "threads": "batchSize * numHeads * seqLen * headSize",
1030
- "workgroupSize": "constants.appendWorkgroupSize"
 
1031
  }
1032
  },
1033
  {
1034
  "id": "attention",
1035
  "name": "SparseAttention.AttentionSgmat",
1036
  "shader": "sparse-attention-sgmat.wgsl.jinja",
1037
- "bindings": "attentionSgmatRotary",
1038
- "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" }
 
1039
  }
1040
- ]
 
1041
  }
1042
  ]
1043
  }
 
2
  "domain": "com.microsoft",
3
  "name": "SparseAttention",
4
  "sinceVersion": 1,
5
+ "inputs": {
6
+ "queryT": { "onnx": "query", "dtype": "T", "rank": 3 },
7
+ "keyT": { "onnx": "key", "dtype": "T", "rank": 3, "optional": true },
8
+ "valueT": { "onnx": "value", "dtype": "T", "rank": 3, "optional": true },
9
+ "pastKeyT": { "onnx": "past_key", "dtype": "T", "rank": 4 },
10
+ "pastValueT": { "onnx": "past_value", "dtype": "T", "rank": 4 },
11
+ "blockRowIndicesT": { "onnx": "block_row_indices", "dtype": "M", "rank": 2, "storage": "int32" },
12
+ "blockColIndicesT": { "onnx": "block_col_indices", "dtype": "M", "rank": 2, "storage": "int32" },
13
+ "totalSequenceLengthT": { "onnx": "total_sequence_length", "dtype": "M", "storage": "int32" },
14
+ "keyTotalSequenceLengthsT": { "onnx": "key_total_sequence_lengths", "dtype": "M", "rank": 1, "storage": "int32" },
15
+ "cosCacheT": { "onnx": "cos_cache", "dtype": "T", "rank": 2, "optional": true },
16
+ "sinCacheT": { "onnx": "sin_cache", "dtype": "T", "rank": 2, "optional": true }
17
+ },
18
+ "outputs": {
19
+ "outputT": { "onnx": "output", "dtype": "T", "rank": 3, "shape": "[batchSize, seqLen, numHeads * headSize]" },
20
+ "pastKeyT": { "onnx": "past_key", "dtype": "T", "rank": 4, "shape": "shapes.pastKeyT" },
21
+ "pastValueT": { "onnx": "past_value", "dtype": "T", "rank": 4, "shape": "shapes.pastValueT" }
22
+ },
23
+ "attributes": {
24
+ "do_rotary": { "default": 0 },
25
+ "rotary_interleaved": { "default": 0 },
26
+ "num_heads": {},
27
+ "kv_num_heads": {},
28
+ "sparse_block_size": {},
29
+ "scale": {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  },
31
  "attributeConstraints": {
32
  "num_heads": { "required": true },
 
34
  "sparse_block_size": { "required": true }
35
  },
36
  "typeConstraints": { "T": ["float32", "float16"], "M": ["int32"] },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  "tunables": {
38
+ "WORKGROUP_SIZE": { "default": 128 },
39
+ "APPEND_WORKGROUP_SIZE": { "default": 256 },
40
+ "NARROW_MIN_WORKGROUPS": { "default": 1024 },
41
+ "QUERY_TILE": { "default": 4 },
42
+ "V_STAGE_MAX_WORKGROUPS": { "default": 512 },
43
+ "MATRIX_MIN_WORKGROUPS": { "default": 16 }
44
  },
45
  "derive": {
46
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
 
82
  "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)",
83
  "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\"",
84
  "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\"",
85
+ "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 <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and batchSize * numHeads <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(ceilDiv(qRotaryElements, tunables.APPEND_WORKGROUP_SIZE), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and (2 * sparseQueryTile * headSize + (3 * sparseQueryTile + 1) * sparseAttnWorkgroup) * 4 <= device.limits.maxComputeWorkgroupStorageSize and sparseAttnWorkgroup <= device.limits.maxComputeInvocationsPerWorkgroup and sparseAttnWorkgroup <= device.limits.maxComputeWorkgroupSizeX",
86
  "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",
87
  "packedContract": "contract and packedQkv and not useRotary",
88
  "packedRotaryContract": "contract and packedQkv and useRotary",
 
90
  "separateRotaryContract": "contract and not packedQkv and useRotary",
91
  "sparseVStageWorthIt": "sparseQueryTiles * batchSize * numHeads <= tunables.V_STAGE_MAX_WORKGROUPS",
92
  "sgmatQueryTiles": "ceilDiv(seqLen, 64)",
93
+ "sgmatDirectQuery": "seqLen % 64 == 0",
94
+ "sparseSgmatTileN": "64 if (64 * 32 + 64 * 64 + 64 * 2 + 128 * 2) * 4 <= device.limits.maxComputeWorkgroupStorageSize else 32",
95
+ "sparseSgmatTileK": "sparseSgmatTileN / 2",
96
+ "sparseSgmatLdsBytes": "(64 * sparseSgmatTileK + 64 * sparseSgmatTileN + 64 * 2 + 128 * 2) * 4",
97
+ "sparseSgmatGeometryOk": "256 <= device.limits.maxComputeInvocationsPerWorkgroup and 256 <= device.limits.maxComputeWorkgroupSizeX and sgmatQueryTiles <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and batchSize * numHeads <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and sparseSgmatLdsBytes <= device.limits.maxComputeWorkgroupStorageSize",
98
+ "sparseSgmatOk": "tensorDtypes.queryT == \"float32\" and seqLen >= 64 and sparseBlockSize % 64 == 0 and headSize % 32 == 0 and headSize <= 128 and maxCacheSeq % 64 == 0 and device.features.has(\"subgroups\") and wave32Effective and device.features.has(\"chromium-experimental-subgroup-matrix\") and sparseSgmatGeometryOk",
99
  "scalar": "dtypes.T",
 
 
 
 
 
100
  "cacheVec": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\"",
101
  "attnWorkgroup": "sparseAttnWorkgroup",
 
 
 
 
 
 
 
102
  "usesRotary": "useRotary",
 
 
 
103
  "appendWorkgroupSize": "tunables.APPEND_WORKGROUP_SIZE"
104
  },
105
+ "when": ["geometryOk"],
106
+ "bindings": {
107
+ "new_key": { "arg": "keyT", "buffer": "read-only-storage", "elementType": "$scalar" },
108
+ "new_value": { "arg": "valueT", "buffer": "read-only-storage", "elementType": "$scalar" },
109
+ "present_key": { "arg": "pastKeyT", "buffer": "storage", "elementType": "$scalar" },
110
+ "present_value": { "arg": "pastValueT", "buffer": "storage", "elementType": "$scalar" },
111
+ "key_total_sequence_lengths": {
112
+ "arg": "keyTotalSequenceLengthsT",
113
+ "buffer": "read-only-storage",
114
+ "elementType": "i32"
115
+ },
116
+ "total_sequence_length": { "arg": "totalSequenceLengthT", "buffer": "read-only-storage", "elementType": "i32" },
117
+ "params": {
118
+ "buffer": "uniform",
119
+ "struct": [
120
+ { "name": "batchSize", "type": "u32", "value": "batchSize" },
121
+ { "name": "seqLen", "type": "u32", "value": "seqLen" }
122
+ ]
123
+ },
124
+ "cos_cache": { "arg": "cosCacheT", "buffer": "read-only-storage", "elementType": "$scalar" },
125
+ "sin_cache": { "arg": "sinCacheT", "buffer": "read-only-storage", "elementType": "$scalar" },
126
+ "packed_qkv": { "arg": "queryT", "buffer": "read-only-storage", "elementType": "$scalar" },
127
+ "query": { "arg": "queryT", "buffer": "read-only-storage", "elementType": "$scalar" },
128
+ "present_key_2": {
129
+ "arg": "pastKeyT",
130
+ "name": "present_key",
131
+ "buffer": "read-only-storage",
132
+ "elementType": "$cacheVec"
133
+ },
134
+ "present_value_2": {
135
+ "arg": "pastValueT",
136
+ "name": "present_value",
137
+ "buffer": "read-only-storage",
138
+ "elementType": "$cacheVec"
139
+ },
140
+ "block_row_indices": { "arg": "blockRowIndicesT", "buffer": "read-only-storage", "elementType": "i32" },
141
+ "block_col_indices": { "arg": "blockColIndicesT", "buffer": "read-only-storage", "elementType": "i32" },
142
+ "output": { "arg": "outputT", "buffer": "storage", "elementType": "$scalar" },
143
+ "params_2": {
144
+ "name": "params",
145
+ "buffer": "uniform",
146
+ "struct": [
147
+ { "name": "seqLen", "type": "u32", "value": "seqLen" },
148
+ { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
149
+ ]
150
+ },
151
+ "q_rotary": { "scratch": "QRotary", "buffer": "read-only-storage", "elementType": "f32" },
152
+ "present_key_3": {
153
+ "arg": "pastKeyT",
154
+ "name": "present_key",
155
+ "buffer": "read-only-storage",
156
+ "elementType": "$scalar"
157
+ },
158
+ "present_value_3": {
159
+ "arg": "pastValueT",
160
+ "name": "present_value",
161
+ "buffer": "read-only-storage",
162
+ "elementType": "$scalar"
163
+ },
164
+ "q_rotary_2": { "scratch": "QRotary", "name": "q_rotary", "buffer": "storage", "elementType": "f32" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  },
166
  "variants": [
167
  {
168
  "id": "separate",
169
  "priority": 0,
170
+ "when": ["separateContract"],
171
+ "derive": { "vStageWorthIt": "sparseVStageWorthIt" },
172
  "passes": [
173
  {
174
  "id": "append",
175
  "name": "SparseAttention.Append",
176
  "shader": "sparse-kv-append.wgsl.jinja",
177
+ "derive": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
178
+ "bindings": ["new_key", "new_value", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "params"],
179
  "dispatch": {
180
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
181
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
182
+ "z": 1
183
  }
184
  },
185
  {
186
  "id": "attention",
187
  "name": "SparseAttention.Attention",
188
  "shader": "sparse-attention.wgsl.jinja",
189
+ "derive": { "qTile": "sparseQueryTile" },
190
+ "bindings": ["query", "present_key_2", "present_value_2", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
191
+ "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" }
192
  }
193
+ ]
 
194
  },
195
  {
196
  "id": "separate_sgmat",
197
  "priority": 20,
198
+ "when": ["separateContract", "sparseSgmatOk"],
199
  "requires": {
200
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
201
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
 
205
  "id": "append",
206
  "name": "SparseAttention.Append",
207
  "shader": "sparse-kv-append.wgsl.jinja",
208
+ "derive": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
209
+ "bindings": ["new_key", "new_value", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "params"],
210
  "dispatch": {
211
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
212
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
213
+ "z": 1
214
  }
215
  },
216
  {
217
  "id": "attention",
218
  "name": "SparseAttention.AttentionSgmat",
219
  "shader": "sparse-attention-sgmat.wgsl.jinja",
220
+ "bindings": ["query", "present_key_3", "present_value_3", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
221
+ "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" },
222
+ "subgroupCollectivesWidth": 32
223
  }
224
+ ],
225
+ "demoteWhen": ["sgmatQueryTiles * batchSize * numHeads * sparseSgmatTileN < tunables.MATRIX_MIN_WORKGROUPS * 64"]
226
  },
227
  {
228
  "id": "separate_rotary",
229
  "priority": 10,
230
+ "when": ["separateRotaryContract"],
231
+ "derive": { "vStageWorthIt": "sparseVStageWorthIt" },
232
  "intermediates": [{ "id": "QRotary", "dtype": "float32", "shape": "[qRotaryElements]" }],
233
  "passes": [
234
  {
235
  "id": "append",
236
  "name": "SparseAttention.Append",
237
  "shader": "sparse-kv-append.wgsl.jinja",
238
+ "derive": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
239
+ "bindings": ["new_key", "new_value", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "cos_cache", "sin_cache", "params"],
240
  "dispatch": {
241
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
242
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
243
+ "z": 1
244
  }
245
  },
246
  {
247
  "id": "qrotary",
248
  "name": "SparseAttention.QueryRotary",
249
  "shader": "sparse-q-rotary.wgsl.jinja",
250
+ "bindings": ["query", "cos_cache", "sin_cache", "q_rotary_2", "key_total_sequence_lengths", "total_sequence_length", "params"],
251
  "dispatch": {
252
+ "x": "min(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
253
+ "y": "ceilDiv(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
254
+ "z": 1
255
  }
256
  },
257
  {
258
  "id": "attention",
259
  "name": "SparseAttention.Attention",
260
  "shader": "sparse-attention.wgsl.jinja",
261
+ "derive": { "qTile": "sparseQueryTile" },
262
+ "bindings": ["q_rotary", "present_key_2", "present_value_2", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
263
+ "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" }
264
  }
265
+ ]
 
266
  },
267
  {
268
  "id": "separate_rotary_sgmat",
269
  "priority": 30,
270
+ "when": ["separateRotaryContract", "sparseSgmatOk"],
271
  "requires": {
272
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
273
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
 
278
  "id": "append",
279
  "name": "SparseAttention.Append",
280
  "shader": "sparse-kv-append.wgsl.jinja",
281
+ "derive": { "kvSource": "\"new_key\"", "vSource": "\"new_value\"" },
282
+ "bindings": ["new_key", "new_value", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "cos_cache", "sin_cache", "params"],
283
  "dispatch": {
284
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
285
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
286
+ "z": 1
287
  }
288
  },
289
  {
290
  "id": "qrotary",
291
  "name": "SparseAttention.QueryRotary",
292
  "shader": "sparse-q-rotary.wgsl.jinja",
293
+ "bindings": ["query", "cos_cache", "sin_cache", "q_rotary_2", "key_total_sequence_lengths", "total_sequence_length", "params"],
294
  "dispatch": {
295
+ "x": "min(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
296
+ "y": "ceilDiv(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
297
+ "z": 1
298
  }
299
  },
300
  {
301
  "id": "attention",
302
  "name": "SparseAttention.AttentionSgmat",
303
  "shader": "sparse-attention-sgmat.wgsl.jinja",
304
+ "bindings": ["q_rotary", "present_key_3", "present_value_3", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
305
+ "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" },
306
+ "subgroupCollectivesWidth": 32
307
  }
308
+ ],
309
+ "demoteWhen": ["sgmatQueryTiles * batchSize * numHeads * sparseSgmatTileN < tunables.MATRIX_MIN_WORKGROUPS * 64"]
310
  },
311
  {
312
  "id": "packed",
313
  "priority": 0,
314
+ "when": ["packedContract"],
315
+ "derive": { "vStageWorthIt": "sparseVStageWorthIt" },
316
  "passes": [
317
  {
318
  "id": "append",
319
  "name": "SparseAttention.Append",
320
  "shader": "sparse-kv-append.wgsl.jinja",
321
+ "derive": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
322
+ "bindings": ["packed_qkv", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "params"],
323
  "dispatch": {
324
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
325
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
326
+ "z": 1
327
  }
328
  },
329
  {
330
  "id": "attention",
331
  "name": "SparseAttention.Attention",
332
  "shader": "sparse-attention.wgsl.jinja",
333
+ "derive": { "qTile": "sparseQueryTile" },
334
+ "bindings": ["query", "present_key_2", "present_value_2", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
335
+ "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" }
336
  }
337
+ ]
 
338
  },
339
  {
340
  "id": "packed_sgmat",
341
  "priority": 20,
342
+ "when": ["packedContract", "sparseSgmatOk"],
343
  "requires": {
344
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
345
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
 
349
  "id": "append",
350
  "name": "SparseAttention.Append",
351
  "shader": "sparse-kv-append.wgsl.jinja",
352
+ "derive": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
353
+ "bindings": ["packed_qkv", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "params"],
354
  "dispatch": {
355
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
356
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
357
+ "z": 1
358
  }
359
  },
360
  {
361
  "id": "attention",
362
  "name": "SparseAttention.AttentionSgmat",
363
  "shader": "sparse-attention-sgmat.wgsl.jinja",
364
+ "bindings": ["query", "present_key_3", "present_value_3", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
365
+ "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" },
366
+ "subgroupCollectivesWidth": 32
367
  }
368
+ ],
369
+ "demoteWhen": ["sgmatQueryTiles * batchSize * numHeads * sparseSgmatTileN < tunables.MATRIX_MIN_WORKGROUPS * 64"]
370
  },
371
  {
372
  "id": "packed_rotary",
373
  "priority": 10,
374
+ "when": ["packedRotaryContract"],
375
+ "derive": { "vStageWorthIt": "sparseVStageWorthIt" },
376
  "intermediates": [{ "id": "QRotary", "dtype": "float32", "shape": "[qRotaryElements]" }],
377
  "passes": [
378
  {
379
  "id": "append",
380
  "name": "SparseAttention.Append",
381
  "shader": "sparse-kv-append.wgsl.jinja",
382
+ "derive": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
383
+ "bindings": ["packed_qkv", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "cos_cache", "sin_cache", "params"],
384
  "dispatch": {
385
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
386
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
387
+ "z": 1
388
  }
389
  },
390
  {
391
  "id": "qrotary",
392
  "name": "SparseAttention.QueryRotary",
393
  "shader": "sparse-q-rotary.wgsl.jinja",
394
+ "bindings": ["query", "cos_cache", "sin_cache", "q_rotary_2", "key_total_sequence_lengths", "total_sequence_length", "params"],
395
  "dispatch": {
396
+ "x": "min(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
397
+ "y": "ceilDiv(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
398
+ "z": 1
399
  }
400
  },
401
  {
402
  "id": "attention",
403
  "name": "SparseAttention.Attention",
404
  "shader": "sparse-attention.wgsl.jinja",
405
+ "derive": { "qTile": "sparseQueryTile" },
406
+ "bindings": ["q_rotary", "present_key_2", "present_value_2", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
407
+ "dispatch": { "x": "sparseQueryTiles", "y": "batchSize * numHeads" }
408
  }
409
+ ]
 
410
  },
411
  {
412
  "id": "packed_rotary_sgmat",
413
  "priority": 30,
414
+ "when": ["packedRotaryContract", "sparseSgmatOk"],
415
  "requires": {
416
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
417
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
 
422
  "id": "append",
423
  "name": "SparseAttention.Append",
424
  "shader": "sparse-kv-append.wgsl.jinja",
425
+ "derive": { "kvSource": "\"packed_qkv\"", "vSource": "\"packed_qkv\"" },
426
+ "bindings": ["packed_qkv", "present_key", "present_value", "key_total_sequence_lengths", "total_sequence_length", "cos_cache", "sin_cache", "params"],
427
  "dispatch": {
428
+ "x": "min(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
429
+ "y": "ceilDiv(ceilDiv((batchSize * kvNumHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
430
+ "z": 1
431
  }
432
  },
433
  {
434
  "id": "qrotary",
435
  "name": "SparseAttention.QueryRotary",
436
  "shader": "sparse-q-rotary.wgsl.jinja",
437
+ "bindings": ["query", "cos_cache", "sin_cache", "q_rotary_2", "key_total_sequence_lengths", "total_sequence_length", "params"],
438
  "dispatch": {
439
+ "x": "min(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
440
+ "y": "ceilDiv(ceilDiv((batchSize * numHeads * seqLen * headSize), (appendWorkgroupSize)), 65535)",
441
+ "z": 1
442
  }
443
  },
444
  {
445
  "id": "attention",
446
  "name": "SparseAttention.AttentionSgmat",
447
  "shader": "sparse-attention-sgmat.wgsl.jinja",
448
+ "bindings": ["q_rotary", "present_key_3", "present_value_3", "block_row_indices", "block_col_indices", "key_total_sequence_lengths", "total_sequence_length", "output", "params_2"],
449
+ "dispatch": { "x": "sgmatQueryTiles", "y": "batchSize * numHeads" },
450
+ "subgroupCollectivesWidth": 32
451
  }
452
+ ],
453
+ "demoteWhen": ["sgmatQueryTiles * batchSize * numHeads * sparseSgmatTileN < tunables.MATRIX_MIN_WORKGROUPS * 64"]
454
  }
455
  ]
456
  }
build/webgpu/metadata.json CHANGED
@@ -1,21 +1,33 @@
1
  {
2
  "name": "com.microsoft.SparseAttention",
3
- "id": "_com_microsoft_sparseattention_webgpu_4a03a84",
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": "gKMlEB+YkQi6M1AeKLoodBrq6h8O9hZGRuRd1MoYsak=",
12
- "sparse-attention-sgmat.wgsl.jinja": "OKpr/4DCSDNAm1vzXOoPqlMGauOYb3Lw3cymXu+dXWI=",
13
- "sparse-attention.wgsl.jinja": "Piza+iXd87OglOJjKORQmvbF1St7TzSgN37dCuv36K4=",
14
- "sparse-kv-append.wgsl.jinja": "RavNXJuGESPqe/Jzt5Ek6+844s5q2w/ocyUgGP3cWec=",
15
- "sparse-q-rotary.wgsl.jinja": "hI7d9cx6RqRO079h4xPBMTMj+qKqIUOKW3pF3vdeWdg=",
16
- "test.json": "BTrN5cYcQUbwYPpPG9o1rqyvAiCPN2ePxNF+VuaQUmk="
17
  }
18
  },
19
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
20
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.SparseAttention" }
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
 
1
  {
2
  "name": "com.microsoft.SparseAttention",
3
+ "id": "_com_microsoft_sparseattention_webgpu_9e87250",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "nMASpXh23ARMsnca9ti0EsTpERZGnOaFX7OI9QEKmcc=",
11
+ "manifest.json": "pGu41bfBMFQF8i3CC/V4Elx5QtkSWNUtxk4Z6+gSFsM=",
12
+ "sparse-attention-sgmat.wgsl.jinja": "At5cbiFuunZkmu32JwzR5KDqNs6/D2VfJb89Op7HljM=",
13
+ "sparse-attention.wgsl.jinja": "4VYhFW1x7bbvfGHKcFwP/sptJwhPShWMSe383RasMEY=",
14
+ "sparse-kv-append.wgsl.jinja": "BHKpoS1Ekd524fT8Ft3A7OBCaCXcCe8VpJzXy5q9lA4=",
15
+ "sparse-q-rotary.wgsl.jinja": "1eKB4VVovC4N6CeIJW+S27RUk8VMkDUSYpHp/BXTfRY=",
16
+ "test.json": "lURO8tA6ovXlCiXrg3GjYlxvgcn4OdJ/A1EJ93voYq4="
17
  }
18
  },
19
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
20
+ "webgpu": {
21
+ "manifestSpec": "2.0",
22
+ "variants": {
23
+ "separate": ["sparse-attention.wgsl.jinja", "sparse-kv-append.wgsl.jinja"],
24
+ "separate_sgmat": ["sparse-attention-sgmat.wgsl.jinja", "sparse-kv-append.wgsl.jinja"],
25
+ "separate_rotary": ["sparse-attention.wgsl.jinja", "sparse-kv-append.wgsl.jinja", "sparse-q-rotary.wgsl.jinja"],
26
+ "separate_rotary_sgmat": ["sparse-attention-sgmat.wgsl.jinja", "sparse-kv-append.wgsl.jinja", "sparse-q-rotary.wgsl.jinja"],
27
+ "packed": ["sparse-attention.wgsl.jinja", "sparse-kv-append.wgsl.jinja"],
28
+ "packed_sgmat": ["sparse-attention-sgmat.wgsl.jinja", "sparse-kv-append.wgsl.jinja"],
29
+ "packed_rotary": ["sparse-attention.wgsl.jinja", "sparse-kv-append.wgsl.jinja", "sparse-q-rotary.wgsl.jinja"],
30
+ "packed_rotary_sgmat": ["sparse-attention-sgmat.wgsl.jinja", "sparse-kv-append.wgsl.jinja", "sparse-q-rotary.wgsl.jinja"]
31
+ }
32
+ }
33
  }
build/webgpu/sparse-attention-sgmat.wgsl.jinja CHANGED
@@ -1,3 +1,4 @@
 
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
@@ -18,32 +19,19 @@ enable subgroup_size_control;
18
  enable chromium_experimental_subgroup_matrix;
19
  diagnostic(off, chromium.subgroup_matrix_uniformity);
20
 
 
21
  {{ env.wgsl.resourceDeclarations }}
22
 
23
- // com.microsoft.SparseAttention, subgroup-matrix attention pass (prefill band).
24
- // One workgroup per (batch, 64-query tile, query head). The block-sparse layout
25
- // already lists dense SPARSE_BLOCK-wide key tiles, so each selected block is
26
- // walked as 64x64 score tiles on 8x8 subgroup matrices with no gather step: the
27
- // K and V rows of a block are contiguous rows of the head-major cache and load
28
- // directly as matrix fragments. Only Q goes through shared memory, for the
29
- // query-tail zero padding.
30
- //
31
- // The pass runs the selected blocks twice. Sweep one folds each score tile
32
- // into running per-row (max, denominator) softmax statistics; sweep two
33
- // recomputes the same tiles (bit-identical: same operands, same order),
34
- // exponentiates against the now-final row max, and accumulates P.V into
35
- // subgroup-matrix result fragments. Recomputing QK costs half the ideal FLOPs
36
- // again, but it is what lets both GEMMs stay on the matrix unit: a running
37
- // online rescale of the output fragments would need an elementwise multiply on
38
- // opaque fragments, which only exists as a shared-memory round trip per key
39
- // tile, and the output accumulator alone (64 x head_size f32) is the whole
40
- // workgroup storage budget.
41
  //
42
- // Causality, CSR duplicate suppression, the dense-row shortcut, and the
43
- // all-masked-row uniform average reproduce the online kernel's semantics; a
44
- // key past a query's bound contributes probability zero and multiplies away
45
- // against whatever the cache holds there, exactly as the online kernel's
46
- // value walk does.
47
  const Q_HEADS: u32 = {{ numHeads }}u;
48
  const KV_HEADS: u32 = {{ kvNumHeads }}u;
49
  const HEAD_DIM: u32 = {{ headSize }}u;
@@ -56,15 +44,13 @@ const Q_HIDDEN: u32 = {{ numHeads * headSize }}u;
56
  {% if not usesRotary %}
57
  const Q_STRIDE: u32 = {{ packedStride if packedQkv else numHeads * headSize }}u;
58
  {% endif %}
59
- // 64 query rows x 64 key columns per score tile, staged over the head
60
- // dimension in 32-wide chunks; 8 subgroups of 32 lanes as a 4x2 grid, each
61
- // owning a 16x32 score sub-tile. sparse_block_size here is a multiple of 64
62
- // and head_size a multiple of 32, so every key tile is a whole aligned run of
63
- // cache rows and the head loop has no tail.
64
  const TILE_M: u32 = 64u;
65
- const TILE_N: u32 = 64u;
66
- const TILE_K: u32 = 32u;
67
- const SUB_TILES: u32 = {{ (sparseBlockSize / 64) | int }}u;
68
  {% set pvColBlocks = (headSize / 16) | int %}
69
  // Each of the two subgroup column groups owns half the output head dimension.
70
  const PV_COL_SPAN: u32 = {{ (headSize / 2) | int }}u;
@@ -93,11 +79,11 @@ fn exp_shift(value: f32, maxValue: f32) -> f32 {
93
  }
94
 
95
  // Q staging for the score GEMM; the score epilogues alias it as the
96
- // fragment-store scratch (8 subgroups x 4 banks x 64 elements).
97
- var<workgroup> tile_q: array<f32, 2048>;
98
  // Tile probabilities for the P.V GEMM; the output epilogue aliases it as the
99
  // result-fragment scratch once the last key tile's readers are done.
100
- var<workgroup> prob_tile: array<f32, 4096>;
101
  var<workgroup> row_m: array<f32, 64>;
102
  var<workgroup> row_d: array<f32, 64>;
103
  // Per-key-tile row partials, one slot per (row, subgroup column group).
@@ -113,15 +99,28 @@ fn scale_value() -> f32 {
113
 
114
  {{ sparse_schedule() }}
115
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  {% macro score_tile() %}
117
- // S = Q.K^T for this key tile, accumulated over the head dimension in
118
- // TILE_K chunks. Q is staged with query tails zero-padded; K loads
119
- // straight from the cache as column-major right fragments.
120
  for (var k_base = 0u; k_base < HEAD_DIM; k_base += TILE_K) {
 
121
  {
122
  let a_row = li / 4u;
123
- let a_col = (li % 4u) * 8u;
124
- for (var i = 0u; i < 8u; i++) {
125
  let k = k_base + a_col + i;
126
  {% if usesRotary %}
127
  let q_index = ((batch * Q_HEADS + head) * params.seqLen
@@ -137,26 +136,29 @@ fn scale_value() -> f32 {
137
  }
138
  }
139
  workgroupBarrier();
 
140
  for (var step = 0u; step < TILE_K; step += 8u) {
141
  {% for rb in range(2) %}
142
  let mat_a{{ rb }} = subgroupMatrixLoad<subgroup_matrix_left<f32, 8, 8>, row_major>(
143
- &tile_q, (base_a + {{ rb * 8 }}u) * TILE_K + step, TILE_K
144
  );
145
  {% endfor %}
146
- {% for cb in range(4) %}
147
  let mat_b{{ cb }} = subgroupMatrixLoad<subgroup_matrix_right<f32, 8, 8>, col_major>(
148
  &present_key, key_row_elem + (base_b + {{ cb * 8 }}u) * HEAD_DIM + k_base + step, HEAD_DIM
149
  );
150
  {% endfor %}
151
  {% for rb in range(2) %}
152
- {% for cb in range(4) %}
153
  mat_s{{ rb }}{{ cb }} = subgroupMatrixMultiplyAccumulate(
154
  mat_a{{ rb }}, mat_b{{ cb }}, mat_s{{ rb }}{{ cb }}
155
  );
156
  {% endfor %}
157
  {% endfor %}
158
  }
 
159
  workgroupBarrier();
 
160
  }
161
  {% endmacro %}
162
 
@@ -167,8 +169,8 @@ fn scale_value() -> f32 {
167
  for (var mask_row = row_first; mask_row <= row_last; mask_row++) {
168
  let start = u32(block_row_indices[row_base + mask_row]);
169
  let end = u32(block_row_indices[row_base + mask_row + 1u]);
170
- // A row listing every causal block is also dense, so its column values are
171
- // ignored, matching the online kernel and the pinned dense-layout fixture.
172
  let dense_row = layout_is_dense || (end - start == mask_row + 1u);
173
  // Furthest causal bound (exclusive) of the tile's queries on this row.
174
  let bound_max = min(q_abs0 + rows_live, (mask_row + 1u) * SPARSE_BLOCK);
@@ -197,7 +199,7 @@ fn scale_value() -> f32 {
197
  if (tile_ok && key_base < bound_max) {
198
  let key_row_elem = ((batch * KV_HEADS + kv_head) * MAX_CACHE_SEQ + key_base) * HEAD_DIM;
199
  {% for rb in range(2) %}
200
- {% for cb in range(4) %}
201
  var mat_s{{ rb }}{{ cb }}: subgroup_matrix_result<f32, 8, 8>;
202
  {% endfor %}
203
  {% endfor %}
@@ -215,13 +217,13 @@ fn scale_value() -> f32 {
215
  var tile_stat_m{{ rb }} = -FLT_MAX;
216
  var tile_stat_d{{ rb }} = 0.0;
217
  {% endif %}
218
- {% for cb in range(4) %}
219
  subgroupMatrixStore<row_major>(
220
- &tile_q, (subgroup * 4u + {{ cb }}u) * 64u, mat_s{{ rb }}{{ cb }}, 8u
221
  );
222
  {% endfor %}
223
  workgroupBarrier();
224
- {% for cb in range(4) %}
225
  for (var pair = 0u; pair < 2u; pair++) {
226
  let r = base_a + {{ rb * 8 }}u + row_in_block;
227
  let key = key_base + base_b + {{ cb * 8 }}u + col_in_block + pair;
@@ -230,7 +232,7 @@ fn scale_value() -> f32 {
230
  {% if phase == "stats" %}
231
  if (allowed) {
232
  let scored = tile_q[
233
- (subgroup * 4u + {{ cb }}u) * 64u + row_in_block * 8u + col_in_block + pair
234
  ] * scale;
235
  let prev_m = tile_stat_m{{ rb }};
236
  tile_stat_m{{ rb }} = max(tile_stat_m{{ rb }}, scored);
@@ -241,7 +243,7 @@ fn scale_value() -> f32 {
241
  var prob = 0.0;
242
  if (allowed) {
243
  prob = exp_shift(tile_q[
244
- (subgroup * 4u + {{ cb }}u) * 64u + row_in_block * 8u + col_in_block + pair
245
  ] * scale, row_m[r]);
246
  }
247
  prob_tile[r * TILE_N + base_b + {{ cb * 8 }}u + col_in_block + pair] = prob;
@@ -348,7 +350,7 @@ fn main(
348
  let subtile_idy = subgroup % 4u;
349
  let subtile_idx = subgroup / 4u;
350
  let base_a = subtile_idy * 16u;
351
- let base_b = subtile_idx * 32u;
352
  let row_in_block = lane / 4u;
353
  let col_in_block = (lane % 4u) * 2u;
354
 
@@ -374,30 +376,34 @@ fn main(
374
  {{ sweep("stats") }}
375
  {{ sweep("apply") }}
376
 
377
- // Normalize by the final denominators and store; the result fragments
378
- // round-trip through prob_tile, which no reader needs any more.
 
 
379
  {% for rb in range(2) %}
380
- {% if rb > 0 %}
 
381
  workgroupBarrier();
382
  {% endif %}
383
- {% for cb in range(pvColBlocks) %}
384
  subgroupMatrixStore<row_major>(
385
- &prob_tile, (subgroup * {{ pvColBlocks }}u + {{ cb }}u) * 64u, mat_o{{ rb }}{{ cb }}, 8u
386
  );
387
  {% endfor %}
388
  workgroupBarrier();
389
- {% for cb in range(pvColBlocks) %}
390
  for (var pair = 0u; pair < 2u; pair++) {
391
  let r = base_a + {{ rb * 8 }}u + row_in_block;
392
  if (r < rows_live && row_d[r] > 0.0) {
393
  let dcol = subtile_idx * PV_COL_SPAN + {{ cb * 8 }}u + col_in_block + pair;
394
  let out_index = (batch * params.seqLen + tile0 + r) * Q_HIDDEN + head * HEAD_DIM + dcol;
395
  output[out_index] = {{ scalar }}(prob_tile[
396
- (subgroup * {{ pvColBlocks }}u + {{ cb }}u) * 64u + row_in_block * 8u + col_in_block + pair
397
  ] / row_d[r]);
398
  }
399
  }
400
  {% endfor %}
 
401
  {% endfor %}
402
 
403
  // An all-masked row becomes a uniform average over causal history rather
 
1
+ {% set scoreColBlocks = (sparseSgmatTileN / 16) | int %}
2
  {% macro sparse_schedule() %}
3
  // How much history precedes this call's tokens. Prompt mode is determined from
4
  // scalar total_sequence_length rather than per-batch values and forces the past
 
19
  enable chromium_experimental_subgroup_matrix;
20
  diagnostic(off, chromium.subgroup_matrix_uniformity);
21
 
22
+
23
  {{ env.wgsl.resourceDeclarations }}
24
 
25
+ // Subgroup-matrix attention over 64-query tiles. Each workgroup processes one
26
+ // `(batch, query tile, query head)` tuple. Selected sparse blocks are traversed
27
+ // as 64-query score tiles; their contiguous K/V cache rows load directly as matrix
28
+ // fragments. Sequences of complete query tiles also load Q directly; sequences
29
+ // with a partial final tile stage Q with zero padding. The manifest bounds tile widths by workgroup storage.
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  //
31
+ // The first sweep folds score tiles into per-row `(max, denominator)` softmax
32
+ // statistics. The second recomputes the same scores, applies the completed
33
+ // normalization, and accumulates P.V into result fragments. Causal bounds,
34
+ // duplicate CSR columns, dense rows, and all-masked rows are handled explicitly.
 
35
  const Q_HEADS: u32 = {{ numHeads }}u;
36
  const KV_HEADS: u32 = {{ kvNumHeads }}u;
37
  const HEAD_DIM: u32 = {{ headSize }}u;
 
44
  {% if not usesRotary %}
45
  const Q_STRIDE: u32 = {{ packedStride if packedQkv else numHeads * headSize }}u;
46
  {% endif %}
47
+ // Eight 32-lane subgroups form a 4x2 grid. The device storage budget chooses
48
+ // 64 or 32 key columns and a matching head-dimension staging width. Both divide
49
+ // the admitted sparse-block and head dimensions without a key or head tail.
 
 
50
  const TILE_M: u32 = 64u;
51
+ const TILE_N: u32 = {{ sparseSgmatTileN }}u;
52
+ const TILE_K: u32 = {{ sparseSgmatTileK }}u;
53
+ const SUB_TILES: u32 = {{ (sparseBlockSize / sparseSgmatTileN) | int }}u;
54
  {% set pvColBlocks = (headSize / 16) | int %}
55
  // Each of the two subgroup column groups owns half the output head dimension.
56
  const PV_COL_SPAN: u32 = {{ (headSize / 2) | int }}u;
 
79
  }
80
 
81
  // Q staging for the score GEMM; the score epilogues alias it as the
82
+ // fragment-store scratch (8 subgroups x scoreColBlocks banks x 64 elements).
83
+ var<workgroup> tile_q: array<f32, {{ 64 * sparseSgmatTileK }}>;
84
  // Tile probabilities for the P.V GEMM; the output epilogue aliases it as the
85
  // result-fragment scratch once the last key tile's readers are done.
86
+ var<workgroup> prob_tile: array<f32, {{ 64 * sparseSgmatTileN }}>;
87
  var<workgroup> row_m: array<f32, 64>;
88
  var<workgroup> row_d: array<f32, 64>;
89
  // Per-key-tile row partials, one slot per (row, subgroup column group).
 
99
 
100
  {{ sparse_schedule() }}
101
 
102
+ {% set queryMatrixSource = "tile_q" if not sgmatDirectQuery else ("q_rotary" if usesRotary else "query") %}
103
+ {% set queryMatrixStride = "TILE_K" if not sgmatDirectQuery else ("HEAD_DIM" if usesRotary else "Q_STRIDE") %}
104
+ {% macro query_matrix_offset(rb) -%}
105
+ {% if not sgmatDirectQuery -%}
106
+ (base_a + {{ rb * 8 }}u) * TILE_K + step
107
+ {%- elif usesRotary -%}
108
+ ((batch * Q_HEADS + head) * params.seqLen + tile0 + base_a + {{ rb * 8 }}u) * HEAD_DIM + k_base + step
109
+ {%- else -%}
110
+ (batch * params.seqLen + tile0 + base_a + {{ rb * 8 }}u) * Q_STRIDE + head * HEAD_DIM + k_base + step
111
+ {%- endif %}
112
+ {%- endmacro %}
113
+
114
  {% macro score_tile() %}
115
+ // S = Q.K^T, retaining the same sequence of 8-wide matrix operations.
116
+ // Fully populated query tiles need neither staging nor K-loop barriers.
117
+ // A sequence with a partial query tile retains zero-padded staging.
118
  for (var k_base = 0u; k_base < HEAD_DIM; k_base += TILE_K) {
119
+ {% if not sgmatDirectQuery %}
120
  {
121
  let a_row = li / 4u;
122
+ let a_col = (li % 4u) * {{ (sparseSgmatTileK / 4) | int }}u;
123
+ for (var i = 0u; i < {{ (sparseSgmatTileK / 4) | int }}u; i++) {
124
  let k = k_base + a_col + i;
125
  {% if usesRotary %}
126
  let q_index = ((batch * Q_HEADS + head) * params.seqLen
 
136
  }
137
  }
138
  workgroupBarrier();
139
+ {% endif %}
140
  for (var step = 0u; step < TILE_K; step += 8u) {
141
  {% for rb in range(2) %}
142
  let mat_a{{ rb }} = subgroupMatrixLoad<subgroup_matrix_left<f32, 8, 8>, row_major>(
143
+ &{{ queryMatrixSource }}, {{ query_matrix_offset(rb) }}, {{ queryMatrixStride }}
144
  );
145
  {% endfor %}
146
+ {% for cb in range(scoreColBlocks) %}
147
  let mat_b{{ cb }} = subgroupMatrixLoad<subgroup_matrix_right<f32, 8, 8>, col_major>(
148
  &present_key, key_row_elem + (base_b + {{ cb * 8 }}u) * HEAD_DIM + k_base + step, HEAD_DIM
149
  );
150
  {% endfor %}
151
  {% for rb in range(2) %}
152
+ {% for cb in range(scoreColBlocks) %}
153
  mat_s{{ rb }}{{ cb }} = subgroupMatrixMultiplyAccumulate(
154
  mat_a{{ rb }}, mat_b{{ cb }}, mat_s{{ rb }}{{ cb }}
155
  );
156
  {% endfor %}
157
  {% endfor %}
158
  }
159
+ {% if not sgmatDirectQuery %}
160
  workgroupBarrier();
161
+ {% endif %}
162
  }
163
  {% endmacro %}
164
 
 
169
  for (var mask_row = row_first; mask_row <= row_last; mask_row++) {
170
  let start = u32(block_row_indices[row_base + mask_row]);
171
  let end = u32(block_row_indices[row_base + mask_row + 1u]);
172
+ // A row listing every causal block is dense; its column-index values are not
173
+ // read.
174
  let dense_row = layout_is_dense || (end - start == mask_row + 1u);
175
  // Furthest causal bound (exclusive) of the tile's queries on this row.
176
  let bound_max = min(q_abs0 + rows_live, (mask_row + 1u) * SPARSE_BLOCK);
 
199
  if (tile_ok && key_base < bound_max) {
200
  let key_row_elem = ((batch * KV_HEADS + kv_head) * MAX_CACHE_SEQ + key_base) * HEAD_DIM;
201
  {% for rb in range(2) %}
202
+ {% for cb in range(scoreColBlocks) %}
203
  var mat_s{{ rb }}{{ cb }}: subgroup_matrix_result<f32, 8, 8>;
204
  {% endfor %}
205
  {% endfor %}
 
217
  var tile_stat_m{{ rb }} = -FLT_MAX;
218
  var tile_stat_d{{ rb }} = 0.0;
219
  {% endif %}
220
+ {% for cb in range(scoreColBlocks) %}
221
  subgroupMatrixStore<row_major>(
222
+ &tile_q, (subgroup * {{ scoreColBlocks }}u + {{ cb }}u) * 64u, mat_s{{ rb }}{{ cb }}, 8u
223
  );
224
  {% endfor %}
225
  workgroupBarrier();
226
+ {% for cb in range(scoreColBlocks) %}
227
  for (var pair = 0u; pair < 2u; pair++) {
228
  let r = base_a + {{ rb * 8 }}u + row_in_block;
229
  let key = key_base + base_b + {{ cb * 8 }}u + col_in_block + pair;
 
232
  {% if phase == "stats" %}
233
  if (allowed) {
234
  let scored = tile_q[
235
+ (subgroup * {{ scoreColBlocks }}u + {{ cb }}u) * 64u + row_in_block * 8u + col_in_block + pair
236
  ] * scale;
237
  let prev_m = tile_stat_m{{ rb }};
238
  tile_stat_m{{ rb }} = max(tile_stat_m{{ rb }}, scored);
 
243
  var prob = 0.0;
244
  if (allowed) {
245
  prob = exp_shift(tile_q[
246
+ (subgroup * {{ scoreColBlocks }}u + {{ cb }}u) * 64u + row_in_block * 8u + col_in_block + pair
247
  ] * scale, row_m[r]);
248
  }
249
  prob_tile[r * TILE_N + base_b + {{ cb * 8 }}u + col_in_block + pair] = prob;
 
350
  let subtile_idy = subgroup % 4u;
351
  let subtile_idx = subgroup / 4u;
352
  let base_a = subtile_idy * 16u;
353
+ let base_b = subtile_idx * {{ (sparseSgmatTileN / 2) | int }}u;
354
  let row_in_block = lane / 4u;
355
  let col_in_block = (lane % 4u) * 2u;
356
 
 
376
  {{ sweep("stats") }}
377
  {{ sweep("apply") }}
378
 
379
+ // Normalize by the final denominators and store. Publish only as many
380
+ // fragment columns per batch as fit prob_tile, keeping the smaller key tile's
381
+ // storage bound even when the output head is wider.
382
+ {% set storeColBlocks = pvColBlocks if pvColBlocks <= sparseSgmatTileN / 8 else (sparseSgmatTileN / 8) | int %}
383
  {% for rb in range(2) %}
384
+ {% for colBase in range(0, pvColBlocks, storeColBlocks) %}
385
+ {% if rb > 0 or colBase > 0 %}
386
  workgroupBarrier();
387
  {% endif %}
388
+ {% for cb in range(colBase, pvColBlocks if pvColBlocks < colBase + storeColBlocks else colBase + storeColBlocks) %}
389
  subgroupMatrixStore<row_major>(
390
+ &prob_tile, (subgroup * {{ storeColBlocks }}u + {{ cb - colBase }}u) * 64u, mat_o{{ rb }}{{ cb }}, 8u
391
  );
392
  {% endfor %}
393
  workgroupBarrier();
394
+ {% for cb in range(colBase, pvColBlocks if pvColBlocks < colBase + storeColBlocks else colBase + storeColBlocks) %}
395
  for (var pair = 0u; pair < 2u; pair++) {
396
  let r = base_a + {{ rb * 8 }}u + row_in_block;
397
  if (r < rows_live && row_d[r] > 0.0) {
398
  let dcol = subtile_idx * PV_COL_SPAN + {{ cb * 8 }}u + col_in_block + pair;
399
  let out_index = (batch * params.seqLen + tile0 + r) * Q_HIDDEN + head * HEAD_DIM + dcol;
400
  output[out_index] = {{ scalar }}(prob_tile[
401
+ (subgroup * {{ storeColBlocks }}u + {{ cb - colBase }}u) * 64u + row_in_block * 8u + col_in_block + pair
402
  ] / row_d[r]);
403
  }
404
  }
405
  {% endfor %}
406
+ {% endfor %}
407
  {% endfor %}
408
 
409
  // An all-masked row becomes a uniform average over causal history rather
build/webgpu/sparse-attention.wgsl.jinja CHANGED
@@ -11,9 +11,6 @@ fn past_sequence_length(batch: u32) -> u32 {
11
  }
12
  {%- endmacro %}
13
 
14
- {% if usesF16 %}
15
- enable f16;
16
- {% endif %}
17
  {{ env.wgsl.resourceDeclarations }}
18
 
19
  // com.microsoft.SparseAttention, attention pass.
@@ -35,11 +32,8 @@ enable f16;
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;
@@ -80,12 +74,8 @@ 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
  {% if vStageWorthIt %}
83
- // Staged value rows for the running-output walk (small-dispatch shapes only):
84
- // the walk itself occupies just HEAD_VEC lanes, so the whole workgroup
85
- // prefetches each chunk of value rows at full width and the walkers read
86
- // shared memory instead of chaining dependent global loads. Large dispatches
87
- // already hide that latency across resident workgroups, and the chunk
88
- // barriers only cost them, so they keep the direct walk.
89
  const V_STAGE_KEYS: u32 = 16u;
90
  var<workgroup> v_stage: array<vec4<f32>, V_STAGE_KEYS * HEAD_VEC>;
91
  {% endif %}
@@ -102,20 +92,22 @@ var<workgroup> key_rows: array<u32, WG>;
102
  // calls before their shared partial storage is reused.
103
  {% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
104
  {% if combineSubgroups %}
105
- // Per-subgroup partials are published into a deterministic slot: the subgroup's
106
- // ordinal index within the workgroup (lidx / sgSize). The online (m, d) merge
107
- // is not float-associative, so thread 0 must fold partials in a fixed order.
108
- // Subgroups partition a workgroup into contiguous ordinal ranges on supported
109
- // backends, so the ordinal slot is unique per subgroup and every slot in
110
- // [0, subgroupCount) is written (each subgroup elects one leader).
111
- // Sized for the worst case of one partial per invocation.
 
112
  var<workgroup> partialM: array<f32, WG>;
113
  var<workgroup> partialD: array<f32, WG>;
 
114
  var<workgroup> combinedMD: vec2<f32>;
115
 
116
  // When the whole workgroup is one subgroup the subgroup reduce already covers
117
- // it (no barriers, no shared state); otherwise subgroup leaders publish
118
- // partials through shared memory and thread 0 folds them in ordinal order.
119
  fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
120
  let sgM = subgroupMax(m);
121
  // A lane with no elements contributes d == 0 (exact identity). A +inf
@@ -126,31 +118,24 @@ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
126
  if (sgSize == WG) {
127
  return vec2<f32>(sgM, sgD);
128
  }
129
- let subgroupCount = (WG + sgSize - 1u) / sgSize;
130
- // Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
131
- // fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
132
- // subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
133
- // flash-attention loop that re-uses this shared memory each iteration — would
134
- // otherwise read stale shared memory. Identity makes such a slot a no-op.
135
- // (max identity = -FLT_MAX, denom identity = 0.)
136
- if (lidx < subgroupCount) {
137
- partialM[lidx] = -FLT_MAX;
138
- partialD[lidx] = 0.0;
139
- }
140
- workgroupBarrier();
141
  if (subgroupElect()) {
142
- let slot = lidx / sgSize;
143
- partialM[slot] = sgM;
144
- partialD[slot] = sgD;
145
  }
146
  workgroupBarrier();
147
  if (lidx == 0u) {
148
  var accM = -FLT_MAX;
149
  var accD = 0.0;
150
- for (var i = 0u; i < subgroupCount; i = i + 1u) {
151
- let mNew = max(accM, partialM[i]);
152
- accD = accD * exp_shift(accM, mNew) + partialD[i] * exp_shift(partialM[i], mNew);
153
- accM = mNew;
 
 
 
 
 
154
  }
155
  combinedMD = vec2<f32>(accM, accD);
156
  }
@@ -165,9 +150,8 @@ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
165
  var<workgroup> partialD: array<f32, {{ mdExtent }}>;
166
  {% if mdStreamed %}
167
 
168
- // In-place fold of {{ mdStreams }} streams. The caller stores its per-thread
169
- // partials into partialM/partialD first and reads the merged pair of stream s
170
- // from slot s * WG afterwards.
171
  fn combine_partials_streams(lidx: u32) {
172
  workgroupBarrier();
173
  var stride = WG / 2u;
 
11
  }
12
  {%- endmacro %}
13
 
 
 
 
14
  {{ env.wgsl.resourceDeclarations }}
15
 
16
  // com.microsoft.SparseAttention, attention pass.
 
32
  const Q_HEADS: u32 = {{ numHeads }}u;
33
  const KV_HEADS: u32 = {{ kvNumHeads }}u;
34
  const HEAD_DIM: u32 = {{ headSize }}u;
35
+ // The cache is read four elements at a time. `head_size` is a multiple of eight,
36
+ // so every row divides into whole vec4 values and requires no scalar tail.
 
 
 
37
  const HEAD_VEC: u32 = {{ headVec }}u;
38
  const MAX_CACHE_SEQ: u32 = {{ maxCacheSeq }}u;
39
  const SPARSE_BLOCK: u32 = {{ sparseBlockSize }}u;
 
74
  var<workgroup> running_out: array<vec4<f32>, Q_TILE * HEAD_VEC>;
75
  var<workgroup> probs: array<f32, Q_TILE * WG>;
76
  {% if vStageWorthIt %}
77
+ // The workgroup stages each chunk of value rows, then HEAD_VEC lanes consume
78
+ // those rows from shared memory during the running-output update.
 
 
 
 
79
  const V_STAGE_KEYS: u32 = 16u;
80
  var<workgroup> v_stage: array<vec4<f32>, V_STAGE_KEYS * HEAD_VEC>;
81
  {% endif %}
 
92
  // calls before their shared partial storage is reused.
93
  {% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
94
  {% if combineSubgroups %}
95
+ // Cross-subgroup merge that assumes nothing about which invocations share a
96
+ // subgroup or how many subgroups there are: each subgroup's elected lane
97
+ // publishes the subgroup pair in the slot at its OWN invocation index and sets
98
+ // that index's bit in a workgroup bitmask; thread 0 then folds exactly the
99
+ // published slots, in ascending index order (the online (m, d) merge is not
100
+ // float-associative, so the order is fixed), and clears the mask for the next
101
+ // call as it reads it. Workgroup memory starts zeroed, so the mask needs no
102
+ // setup. Same three collectives as a single-subgroup reduce, two barriers.
103
  var<workgroup> partialM: array<f32, WG>;
104
  var<workgroup> partialD: array<f32, WG>;
105
+ var<workgroup> leaderMask: array<atomic<u32>, (WG + 31u) / 32u>;
106
  var<workgroup> combinedMD: vec2<f32>;
107
 
108
  // When the whole workgroup is one subgroup the subgroup reduce already covers
109
+ // it (no barriers, no shared state). `subgroup_size` is the size of the current
110
+ // subgroup and uniform, so the test is exact and may guard the barriers below.
111
  fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
112
  let sgM = subgroupMax(m);
113
  // A lane with no elements contributes d == 0 (exact identity). A +inf
 
118
  if (sgSize == WG) {
119
  return vec2<f32>(sgM, sgD);
120
  }
 
 
 
 
 
 
 
 
 
 
 
 
121
  if (subgroupElect()) {
122
+ partialM[lidx] = sgM;
123
+ partialD[lidx] = sgD;
124
+ atomicOr(&leaderMask[lidx / 32u], 1u << (lidx % 32u));
125
  }
126
  workgroupBarrier();
127
  if (lidx == 0u) {
128
  var accM = -FLT_MAX;
129
  var accD = 0.0;
130
+ for (var w = 0u; w < (WG + 31u) / 32u; w = w + 1u) {
131
+ var bits = atomicExchange(&leaderMask[w], 0u);
132
+ while (bits != 0u) {
133
+ let slot = w * 32u + firstTrailingBit(bits);
134
+ bits = bits & (bits - 1u);
135
+ let mNew = max(accM, partialM[slot]);
136
+ accD = accD * exp_shift(accM, mNew) + partialD[slot] * exp_shift(partialM[slot], mNew);
137
+ accM = mNew;
138
+ }
139
  }
140
  combinedMD = vec2<f32>(accM, accD);
141
  }
 
150
  var<workgroup> partialD: array<f32, {{ mdExtent }}>;
151
  {% if mdStreamed %}
152
 
153
+ // In-place fold of {{ mdStreams }} streams. Input partials occupy
154
+ // partialM/partialD; stream s returns its merged pair in slot s * WG.
 
155
  fn combine_partials_streams(lidx: u32) {
156
  workgroupBarrier();
157
  var stride = WG / 2u;
build/webgpu/sparse-kv-append.wgsl.jinja CHANGED
@@ -46,9 +46,6 @@ fn rotary_value(own: f32, partner: f32, cs: f32, sn: f32, first: bool) -> f32 {
46
  }
47
  {%- endmacro %}
48
 
49
- {% if usesF16 %}
50
- enable f16;
51
- {% endif %}
52
  {{ env.wgsl.resourceDeclarations }}
53
 
54
  // com.microsoft.SparseAttention, KV append pass.
@@ -80,11 +77,10 @@ const ROTARY_DIM: u32 = {{ rotaryDim }}u;
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;
 
46
  }
47
  {%- endmacro %}
48
 
 
 
 
49
  {{ env.wgsl.resourceDeclarations }}
50
 
51
  // com.microsoft.SparseAttention, KV append pass.
 
77
  {% endif %}
78
 
79
  @compute @workgroup_size(WG, 1, 1)
80
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
 
81
  // 2D-folded flat index: gid.y carries the high bits past the
82
+ // per-axis dispatch fold width. Reduces to gid.x when the dispatch does not fold.
83
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
84
  let count = params.batchSize * KV_HEADS * params.seqLen * HEAD_DIM;
85
  if (index >= count) {
86
  return;
build/webgpu/sparse-q-rotary.wgsl.jinja CHANGED
@@ -46,9 +46,6 @@ fn rotary_value(own: f32, partner: f32, cs: f32, sn: f32, first: bool) -> f32 {
46
  }
47
  {%- endmacro %}
48
 
49
- {% if usesF16 %}
50
- enable f16;
51
- {% endif %}
52
  {{ env.wgsl.resourceDeclarations }}
53
 
54
  // com.microsoft.SparseAttention, query rotary pass.
@@ -68,11 +65,10 @@ const WG: u32 = {{ appendWorkgroupSize }}u;
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;
 
46
  }
47
  {%- endmacro %}
48
 
 
 
 
49
  {{ env.wgsl.resourceDeclarations }}
50
 
51
  // com.microsoft.SparseAttention, query rotary pass.
 
65
  {{ sparse_rotary(rotaryInterleaved) }}
66
 
67
  @compute @workgroup_size(WG, 1, 1)
68
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
 
69
  // 2D-folded flat index: gid.y carries the high bits past the
70
+ // per-axis dispatch fold width. Reduces to gid.x when the dispatch does not fold.
71
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
72
  let count = params.batchSize * Q_HEADS * params.seqLen * HEAD_DIM;
73
  if (index >= count) {
74
  return;
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
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
  },
@@ -1056,6 +1055,556 @@
1056
  "pastKeyT": { "dtype": "float32", "shape": [1, 1, 128, 64], "tolerance": 0.00002, "relTolerance": 0.0002 },
1057
  "pastValueT": { "dtype": "float32", "shape": [1, 1, 128, 64], "tolerance": 0.00002, "relTolerance": 0.0002 }
1058
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1059
  }
1060
  ]
1061
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "prompt_separate_two_layouts_input_blockColIndicesT": [0, 0, 1, 1, 2, 1, 2, 3, -1, 0, 0, 1, 0, 1, 2, 0, 2, 3]
4
  },
 
1055
  "pastKeyT": { "dtype": "float32", "shape": [1, 1, 128, 64], "tolerance": 0.00002, "relTolerance": 0.0002 },
1056
  "pastValueT": { "dtype": "float32", "shape": [1, 1, 128, 64], "tolerance": 0.00002, "relTolerance": 0.0002 }
1057
  }
1058
+ },
1059
+ {
1060
+ "name": "sgmat_capacity_d96_s64_packed0_rotary0",
1061
+ "provenance": {
1062
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1063
+ },
1064
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
1065
+ "inputs": {
1066
+ "queryT": {
1067
+ "dtype": "float32",
1068
+ "shape": [2, 64, 192],
1069
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1070
+ },
1071
+ "keyT": {
1072
+ "dtype": "float32",
1073
+ "shape": [2, 64, 192],
1074
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
1075
+ },
1076
+ "valueT": {
1077
+ "dtype": "float32",
1078
+ "shape": [2, 64, 192],
1079
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
1080
+ },
1081
+ "pastKeyT": {
1082
+ "dtype": "float32",
1083
+ "shape": [2, 2, 256, 96],
1084
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1085
+ },
1086
+ "pastValueT": {
1087
+ "dtype": "float32",
1088
+ "shape": [2, 2, 256, 96],
1089
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1090
+ },
1091
+ "blockRowIndicesT": {
1092
+ "dtype": "int32",
1093
+ "shape": [2, 5],
1094
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1095
+ },
1096
+ "blockColIndicesT": {
1097
+ "dtype": "int32",
1098
+ "shape": [2, 7],
1099
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1100
+ },
1101
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1102
+ "keyTotalSequenceLengthsT": {
1103
+ "dtype": "int32",
1104
+ "shape": [2],
1105
+ "data": { "kind": "values", "values": [164, 132] }
1106
+ }
1107
+ },
1108
+ "outputs": {
1109
+ "outputT": { "dtype": "float32", "shape": [2, 64, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1110
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1111
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1112
+ }
1113
+ },
1114
+ {
1115
+ "name": "sgmat_capacity_d96_s64_packed0_rotary1",
1116
+ "provenance": {
1117
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1118
+ },
1119
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 0 },
1120
+ "inputs": {
1121
+ "queryT": {
1122
+ "dtype": "float32",
1123
+ "shape": [2, 64, 192],
1124
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1125
+ },
1126
+ "keyT": {
1127
+ "dtype": "float32",
1128
+ "shape": [2, 64, 192],
1129
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
1130
+ },
1131
+ "valueT": {
1132
+ "dtype": "float32",
1133
+ "shape": [2, 64, 192],
1134
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
1135
+ },
1136
+ "pastKeyT": {
1137
+ "dtype": "float32",
1138
+ "shape": [2, 2, 256, 96],
1139
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1140
+ },
1141
+ "pastValueT": {
1142
+ "dtype": "float32",
1143
+ "shape": [2, 2, 256, 96],
1144
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1145
+ },
1146
+ "blockRowIndicesT": {
1147
+ "dtype": "int32",
1148
+ "shape": [2, 5],
1149
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1150
+ },
1151
+ "blockColIndicesT": {
1152
+ "dtype": "int32",
1153
+ "shape": [2, 7],
1154
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1155
+ },
1156
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1157
+ "keyTotalSequenceLengthsT": {
1158
+ "dtype": "int32",
1159
+ "shape": [2],
1160
+ "data": { "kind": "values", "values": [164, 132] }
1161
+ },
1162
+ "cosCacheT": {
1163
+ "dtype": "float32",
1164
+ "shape": [256, 48],
1165
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
1166
+ },
1167
+ "sinCacheT": {
1168
+ "dtype": "float32",
1169
+ "shape": [256, 48],
1170
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
1171
+ }
1172
+ },
1173
+ "outputs": {
1174
+ "outputT": { "dtype": "float32", "shape": [2, 64, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1175
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1176
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1177
+ }
1178
+ },
1179
+ {
1180
+ "name": "sgmat_capacity_d96_s64_packed1_rotary0",
1181
+ "provenance": {
1182
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1183
+ },
1184
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
1185
+ "inputs": {
1186
+ "queryT": {
1187
+ "dtype": "float32",
1188
+ "shape": [2, 64, 576],
1189
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1190
+ },
1191
+ "pastKeyT": {
1192
+ "dtype": "float32",
1193
+ "shape": [2, 2, 256, 96],
1194
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1195
+ },
1196
+ "pastValueT": {
1197
+ "dtype": "float32",
1198
+ "shape": [2, 2, 256, 96],
1199
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1200
+ },
1201
+ "blockRowIndicesT": {
1202
+ "dtype": "int32",
1203
+ "shape": [2, 5],
1204
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1205
+ },
1206
+ "blockColIndicesT": {
1207
+ "dtype": "int32",
1208
+ "shape": [2, 7],
1209
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1210
+ },
1211
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1212
+ "keyTotalSequenceLengthsT": {
1213
+ "dtype": "int32",
1214
+ "shape": [2],
1215
+ "data": { "kind": "values", "values": [164, 132] }
1216
+ }
1217
+ },
1218
+ "outputs": {
1219
+ "outputT": { "dtype": "float32", "shape": [2, 64, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1220
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1221
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1222
+ }
1223
+ },
1224
+ {
1225
+ "name": "sgmat_capacity_d96_s64_packed1_rotary1",
1226
+ "provenance": {
1227
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1228
+ },
1229
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 1 },
1230
+ "inputs": {
1231
+ "queryT": {
1232
+ "dtype": "float32",
1233
+ "shape": [2, 64, 576],
1234
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1235
+ },
1236
+ "pastKeyT": {
1237
+ "dtype": "float32",
1238
+ "shape": [2, 2, 256, 96],
1239
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1240
+ },
1241
+ "pastValueT": {
1242
+ "dtype": "float32",
1243
+ "shape": [2, 2, 256, 96],
1244
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1245
+ },
1246
+ "blockRowIndicesT": {
1247
+ "dtype": "int32",
1248
+ "shape": [2, 5],
1249
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1250
+ },
1251
+ "blockColIndicesT": {
1252
+ "dtype": "int32",
1253
+ "shape": [2, 7],
1254
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1255
+ },
1256
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1257
+ "keyTotalSequenceLengthsT": {
1258
+ "dtype": "int32",
1259
+ "shape": [2],
1260
+ "data": { "kind": "values", "values": [164, 132] }
1261
+ },
1262
+ "cosCacheT": {
1263
+ "dtype": "float32",
1264
+ "shape": [256, 48],
1265
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
1266
+ },
1267
+ "sinCacheT": {
1268
+ "dtype": "float32",
1269
+ "shape": [256, 48],
1270
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
1271
+ }
1272
+ },
1273
+ "outputs": {
1274
+ "outputT": { "dtype": "float32", "shape": [2, 64, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1275
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1276
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1277
+ }
1278
+ },
1279
+ {
1280
+ "name": "sgmat_capacity_d96_s65_packed0_rotary0",
1281
+ "provenance": {
1282
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1283
+ },
1284
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
1285
+ "inputs": {
1286
+ "queryT": {
1287
+ "dtype": "float32",
1288
+ "shape": [2, 65, 192],
1289
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1290
+ },
1291
+ "keyT": {
1292
+ "dtype": "float32",
1293
+ "shape": [2, 65, 192],
1294
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
1295
+ },
1296
+ "valueT": {
1297
+ "dtype": "float32",
1298
+ "shape": [2, 65, 192],
1299
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
1300
+ },
1301
+ "pastKeyT": {
1302
+ "dtype": "float32",
1303
+ "shape": [2, 2, 256, 96],
1304
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1305
+ },
1306
+ "pastValueT": {
1307
+ "dtype": "float32",
1308
+ "shape": [2, 2, 256, 96],
1309
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1310
+ },
1311
+ "blockRowIndicesT": {
1312
+ "dtype": "int32",
1313
+ "shape": [2, 5],
1314
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1315
+ },
1316
+ "blockColIndicesT": {
1317
+ "dtype": "int32",
1318
+ "shape": [2, 7],
1319
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1320
+ },
1321
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1322
+ "keyTotalSequenceLengthsT": {
1323
+ "dtype": "int32",
1324
+ "shape": [2],
1325
+ "data": { "kind": "values", "values": [165, 133] }
1326
+ }
1327
+ },
1328
+ "outputs": {
1329
+ "outputT": { "dtype": "float32", "shape": [2, 65, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1330
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1331
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1332
+ }
1333
+ },
1334
+ {
1335
+ "name": "sgmat_capacity_d96_s65_packed0_rotary1",
1336
+ "provenance": {
1337
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1338
+ },
1339
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 0 },
1340
+ "inputs": {
1341
+ "queryT": {
1342
+ "dtype": "float32",
1343
+ "shape": [2, 65, 192],
1344
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1345
+ },
1346
+ "keyT": {
1347
+ "dtype": "float32",
1348
+ "shape": [2, 65, 192],
1349
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
1350
+ },
1351
+ "valueT": {
1352
+ "dtype": "float32",
1353
+ "shape": [2, 65, 192],
1354
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
1355
+ },
1356
+ "pastKeyT": {
1357
+ "dtype": "float32",
1358
+ "shape": [2, 2, 256, 96],
1359
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1360
+ },
1361
+ "pastValueT": {
1362
+ "dtype": "float32",
1363
+ "shape": [2, 2, 256, 96],
1364
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1365
+ },
1366
+ "blockRowIndicesT": {
1367
+ "dtype": "int32",
1368
+ "shape": [2, 5],
1369
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1370
+ },
1371
+ "blockColIndicesT": {
1372
+ "dtype": "int32",
1373
+ "shape": [2, 7],
1374
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1375
+ },
1376
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1377
+ "keyTotalSequenceLengthsT": {
1378
+ "dtype": "int32",
1379
+ "shape": [2],
1380
+ "data": { "kind": "values", "values": [165, 133] }
1381
+ },
1382
+ "cosCacheT": {
1383
+ "dtype": "float32",
1384
+ "shape": [256, 48],
1385
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
1386
+ },
1387
+ "sinCacheT": {
1388
+ "dtype": "float32",
1389
+ "shape": [256, 48],
1390
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
1391
+ }
1392
+ },
1393
+ "outputs": {
1394
+ "outputT": { "dtype": "float32", "shape": [2, 65, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1395
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1396
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1397
+ }
1398
+ },
1399
+ {
1400
+ "name": "sgmat_capacity_d96_s65_packed1_rotary0",
1401
+ "provenance": {
1402
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1403
+ },
1404
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64 },
1405
+ "inputs": {
1406
+ "queryT": {
1407
+ "dtype": "float32",
1408
+ "shape": [2, 65, 576],
1409
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1410
+ },
1411
+ "pastKeyT": {
1412
+ "dtype": "float32",
1413
+ "shape": [2, 2, 256, 96],
1414
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1415
+ },
1416
+ "pastValueT": {
1417
+ "dtype": "float32",
1418
+ "shape": [2, 2, 256, 96],
1419
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1420
+ },
1421
+ "blockRowIndicesT": {
1422
+ "dtype": "int32",
1423
+ "shape": [2, 5],
1424
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1425
+ },
1426
+ "blockColIndicesT": {
1427
+ "dtype": "int32",
1428
+ "shape": [2, 7],
1429
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1430
+ },
1431
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1432
+ "keyTotalSequenceLengthsT": {
1433
+ "dtype": "int32",
1434
+ "shape": [2],
1435
+ "data": { "kind": "values", "values": [165, 133] }
1436
+ }
1437
+ },
1438
+ "outputs": {
1439
+ "outputT": { "dtype": "float32", "shape": [2, 65, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1440
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1441
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1442
+ }
1443
+ },
1444
+ {
1445
+ "name": "sgmat_capacity_d96_s65_packed1_rotary1",
1446
+ "provenance": {
1447
+ "notes": "Three 32-wide head blocks exercise the final partial publication bank on a constrained matrix tile; per-batch history crosses sparse rows, with complete and padded query tiles."
1448
+ },
1449
+ "attrs": { "num_heads": 2, "kv_num_heads": 2, "sparse_block_size": 64, "do_rotary": 1, "rotary_interleaved": 1 },
1450
+ "inputs": {
1451
+ "queryT": {
1452
+ "dtype": "float32",
1453
+ "shape": [2, 65, 576],
1454
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1455
+ },
1456
+ "pastKeyT": {
1457
+ "dtype": "float32",
1458
+ "shape": [2, 2, 256, 96],
1459
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1460
+ },
1461
+ "pastValueT": {
1462
+ "dtype": "float32",
1463
+ "shape": [2, 2, 256, 96],
1464
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1465
+ },
1466
+ "blockRowIndicesT": {
1467
+ "dtype": "int32",
1468
+ "shape": [2, 5],
1469
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1470
+ },
1471
+ "blockColIndicesT": {
1472
+ "dtype": "int32",
1473
+ "shape": [2, 7],
1474
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1475
+ },
1476
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1477
+ "keyTotalSequenceLengthsT": {
1478
+ "dtype": "int32",
1479
+ "shape": [2],
1480
+ "data": { "kind": "values", "values": [165, 133] }
1481
+ },
1482
+ "cosCacheT": {
1483
+ "dtype": "float32",
1484
+ "shape": [256, 48],
1485
+ "data": { "kind": "fillFloat32", "sinStep": 0.01, "cosStep": 0.013, "scale": 0.5 }
1486
+ },
1487
+ "sinCacheT": {
1488
+ "dtype": "float32",
1489
+ "shape": [256, 48],
1490
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.013, "scale": 0.5 }
1491
+ }
1492
+ },
1493
+ "outputs": {
1494
+ "outputT": { "dtype": "float32", "shape": [2, 65, 192], "tolerance": 0.0001, "relTolerance": 0.0005 },
1495
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1496
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1497
+ }
1498
+ },
1499
+ {
1500
+ "name": "sgmat_parallelism_d96_s128",
1501
+ "provenance": {
1502
+ "notes": "The matrix scheduling boundary: 16 or 32 workgroups with identical batch/head geometry. A 96-wide head exercises partial result-bank publication on the smaller capacity tier."
1503
+ },
1504
+ "attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 64 },
1505
+ "inputs": {
1506
+ "queryT": {
1507
+ "dtype": "float32",
1508
+ "shape": [2, 128, 384],
1509
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1510
+ },
1511
+ "keyT": {
1512
+ "dtype": "float32",
1513
+ "shape": [2, 128, 192],
1514
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
1515
+ },
1516
+ "valueT": {
1517
+ "dtype": "float32",
1518
+ "shape": [2, 128, 192],
1519
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
1520
+ },
1521
+ "pastKeyT": {
1522
+ "dtype": "float32",
1523
+ "shape": [2, 2, 256, 96],
1524
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1525
+ },
1526
+ "pastValueT": {
1527
+ "dtype": "float32",
1528
+ "shape": [2, 2, 256, 96],
1529
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1530
+ },
1531
+ "blockRowIndicesT": {
1532
+ "dtype": "int32",
1533
+ "shape": [2, 5],
1534
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1535
+ },
1536
+ "blockColIndicesT": {
1537
+ "dtype": "int32",
1538
+ "shape": [2, 7],
1539
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1540
+ },
1541
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1542
+ "keyTotalSequenceLengthsT": {
1543
+ "dtype": "int32",
1544
+ "shape": [2],
1545
+ "data": { "kind": "values", "values": [256, 256] }
1546
+ }
1547
+ },
1548
+ "outputs": {
1549
+ "outputT": { "dtype": "float32", "shape": [2, 128, 384], "tolerance": 0.0001, "relTolerance": 0.0005 },
1550
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1551
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1552
+ }
1553
+ },
1554
+ {
1555
+ "name": "sgmat_parallelism_d96_s256",
1556
+ "provenance": {
1557
+ "notes": "The matrix scheduling boundary: 16 or 32 workgroups with identical batch/head geometry. A 96-wide head exercises partial result-bank publication on the smaller capacity tier."
1558
+ },
1559
+ "attrs": { "num_heads": 4, "kv_num_heads": 2, "sparse_block_size": 64 },
1560
+ "inputs": {
1561
+ "queryT": {
1562
+ "dtype": "float32",
1563
+ "shape": [2, 256, 384],
1564
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1565
+ },
1566
+ "keyT": {
1567
+ "dtype": "float32",
1568
+ "shape": [2, 256, 192],
1569
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.5 }
1570
+ },
1571
+ "valueT": {
1572
+ "dtype": "float32",
1573
+ "shape": [2, 256, 192],
1574
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
1575
+ },
1576
+ "pastKeyT": {
1577
+ "dtype": "float32",
1578
+ "shape": [2, 2, 256, 96],
1579
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 0.25 }
1580
+ },
1581
+ "pastValueT": {
1582
+ "dtype": "float32",
1583
+ "shape": [2, 2, 256, 96],
1584
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.013, "scale": 0.25 }
1585
+ },
1586
+ "blockRowIndicesT": {
1587
+ "dtype": "int32",
1588
+ "shape": [2, 5],
1589
+ "data": { "kind": "values", "values": [0, 1, 3, 5, 7, 0, 1, 2, 4, 5] }
1590
+ },
1591
+ "blockColIndicesT": {
1592
+ "dtype": "int32",
1593
+ "shape": [2, 7],
1594
+ "data": { "kind": "values", "values": [0, 0, 1, 0, 2, 1, 3, 0, 1, 0, 2, 3, 0, 0] }
1595
+ },
1596
+ "totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } },
1597
+ "keyTotalSequenceLengthsT": {
1598
+ "dtype": "int32",
1599
+ "shape": [2],
1600
+ "data": { "kind": "values", "values": [256, 256] }
1601
+ }
1602
+ },
1603
+ "outputs": {
1604
+ "outputT": { "dtype": "float32", "shape": [2, 256, 384], "tolerance": 0.0001, "relTolerance": 0.0005 },
1605
+ "pastKeyT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 },
1606
+ "pastValueT": { "dtype": "float32", "shape": [2, 2, 256, 96], "tolerance": 0.00002, "relTolerance": 0.0002 }
1607
+ }
1608
  }
1609
  ]
1610
  }