sync 91d990483a17
Browse files- README.md +82 -29
- build/webgpu/attention-rank4-tiled.wgsl.jinja +26 -13
- build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja +8 -7
- build/webgpu/attn-flash-decode-splitk.wgsl.jinja +44 -49
- build/webgpu/attn-flash-online.wgsl.jinja +30 -48
- build/webgpu/attn-flash-prefill-cluster.wgsl.jinja +209 -96
- build/webgpu/attn-flash-q32-broadcast.wgsl.jinja +1 -27
- build/webgpu/attn-materialized-rowstats-combine-f32.wgsl.jinja +2 -3
- build/webgpu/attn-materialized-sgmat-f32.wgsl.jinja +30 -51
- build/webgpu/attn-online-scalar.wgsl.jinja +6 -40
- build/webgpu/bench.json +6 -13
- build/webgpu/gqa-attention.wgsl.jinja +15 -10
- build/webgpu/gqa-present.wgsl.jinja +16 -18
- build/webgpu/gqa-qprep.wgsl.jinja +10 -17
- build/webgpu/manifest.json +0 -0
- build/webgpu/metadata.json +88 -18
- build/webgpu/test.json +438 -77
README.md
CHANGED
|
@@ -18,31 +18,31 @@ See the [ONNX Runtime `GroupQueryAttention` contrib-operator spec](https://githu
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
-
| Name |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `
|
| 24 |
-
| `
|
| 25 |
-
| `
|
| 26 |
-
| `
|
| 27 |
-
| `
|
| 28 |
-
| `
|
| 29 |
-
| `
|
| 30 |
-
| `
|
| 31 |
-
| `
|
| 32 |
-
| `
|
| 33 |
-
| `
|
| 34 |
-
| `
|
| 35 |
-
| `
|
| 36 |
-
| `
|
| 37 |
-
| `
|
| 38 |
|
| 39 |
## Outputs
|
| 40 |
|
| 41 |
-
| Name |
|
| 42 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 43 |
-
| `
|
| 44 |
-
| `
|
| 45 |
-
| `
|
| 46 |
|
| 47 |
## Attributes
|
| 48 |
|
|
@@ -53,16 +53,16 @@ Attributes and default values (overridable per request):
|
|
| 53 |
| `causal` | `1` | Whether to apply a causal mask. Set to 0 for bidirectional attention; `local_window_size` must then be -1. |
|
| 54 |
| `do_rotary` | `0` | Set to 1 to apply rotary position embeddings. The default 0 disables them. |
|
| 55 |
| `k_quant_type` | `"NONE"` | Key-cache quantization mode: `NONE`, `PER_TENSOR`, or `PER_CHANNEL`. |
|
|
|
|
|
|
|
| 56 |
| `local_window_size` | `-1` | Left window size for causal local attention. The default -1 disables local attention, and the value must be -1 when `causal` is 0. |
|
|
|
|
| 57 |
| `qk_norm_epsilon` | `0.000001` | Epsilon for the per-head Q/K RMS normalization applied when both normalization weights are provided. |
|
|
|
|
| 58 |
| `sliding_window_cache` | `0` | Set to 1 when past/present caches are fixed-size window buffers that evict old tokens from the front. Requires `local_window_size > 0` and enough cache capacity. |
|
| 59 |
| `smooth_softmax` | `-1` | Set to 1 to enable the smooth-softmax denominator term. |
|
| 60 |
| `softcap` | `0` | Positive softcap applied to attention scores. The default 0 disables soft-capping. |
|
| 61 |
| `v_quant_type` | `"NONE"` | Value-cache quantization mode: `NONE`, `PER_TENSOR`, or `PER_CHANNEL`. |
|
| 62 |
-
| `kv_cache_bit_width` | — | Quantized cache bit width, either 8 or 4. Four-bit values are packed two per uint8 element. |
|
| 63 |
-
| `kv_num_heads` | — | Number of key/value attention heads. |
|
| 64 |
-
| `num_heads` | — | Number of query attention heads. |
|
| 65 |
-
| `scale` | — | Optional QK score scale; zero or omission selects `1 / sqrt(head_size)`. |
|
| 66 |
|
| 67 |
## Type constraints
|
| 68 |
|
|
@@ -73,13 +73,63 @@ Attributes and default values (overridable per request):
|
|
| 73 |
| `T_KV_SCALE` | `float32` |
|
| 74 |
| `M` | `int32` |
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
## Device requirements
|
| 77 |
|
| 78 |
Some implementation variants require `subgroup-matrix`, `shader-f16`, and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
| 79 |
|
| 80 |
## Files
|
| 81 |
|
| 82 |
-
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 83 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 84 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 85 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
@@ -98,16 +148,19 @@ Some implementation variants require `subgroup-matrix`, `shader-f16`, and `subgr
|
|
| 98 |
|
| 99 |
## Use with `@huggingface/kernels`
|
| 100 |
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
-
|
| 104 |
|
| 105 |
- `presentKeyT`
|
| 106 |
- `presentValueT`
|
| 107 |
|
| 108 |
-
Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
|
| 109 |
-
|
| 110 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
|
|
|
| 111 |
|
| 112 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 113 |
|
|
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `queryT` | `query` | `T` | `3` | — | Query tensor of shape `(batch_size, sequence_length, num_heads * head_size)`. | required |
|
| 24 |
+
| `keyT` | `key` | `T` | `3` | — | Key tensor of shape `(batch_size, kv_sequence_length, kv_num_heads * head_size)`. | required |
|
| 25 |
+
| `valueT` | `value` | `T` | `3` | — | Value tensor of shape `(batch_size, kv_sequence_length, kv_num_heads * head_size)`. | required |
|
| 26 |
+
| `pastKeyT` | `past_key` | `T_CACHE` | `4` | — | Optional cached key state in BNSH format. Its sequence axis is `max_sequence_length` when the past and present buffers are shared, otherwise `past_sequence_length`; int4 stores each signed value as a +8-biased nibble, with the even head coordinate low, packing two values per logical uint8 element and widening each byte to one u32 WebGPU buffer word. | optional |
|
| 27 |
+
| `pastValueT` | `past_value` | `T_CACHE` | `4` | — | Optional cached value state in BNSH format with the same length and packing semantics as `past_key`. | optional |
|
| 28 |
+
| `seqlensKT` | `seqlens_k` | `M` | `1` | — | Int32 tensor of shape `(batch_size)` containing each sample's total sequence length minus one. | required |
|
| 29 |
+
| `totalSequenceLengthT` | `total_sequence_length` | `M` | `1` | — | Length-one int32 tensor containing the maximum total sequence length (past plus new) in the batch. | required |
|
| 30 |
+
| `cosCacheT` | `cos_cache` | `T` | `2` | — | Optional cosine cache for rotary embeddings with shape `(max_sequence_length, head_size / 2)`. | optional |
|
| 31 |
+
| `sinCacheT` | `sin_cache` | `T` | `2` | — | Optional sine cache for rotary embeddings with shape `(max_sequence_length, head_size / 2)`. | optional |
|
| 32 |
+
| `attentionBiasT` | `attention_bias` | `T` | `4` | — | Optional additive term for QK scores with shape `(batch_size or 1, num_heads or 1, sequence_length, total_sequence_length)`; the first two dimensions broadcast. | optional |
|
| 33 |
+
| `headSinkT` | `head_sink` | `T` | `1` | — | Optional per-head smooth factor of shape `(num_heads)` added to the softmax denominator. | optional |
|
| 34 |
+
| `kScaleT` | `k_scale` | `T_KV_SCALE` | `1` | — | Optional float32 key-cache scale: one value for `PER_TENSOR`, or `kv_num_heads * head_size` values for `PER_CHANNEL`. | optional |
|
| 35 |
+
| `vScaleT` | `v_scale` | `T_KV_SCALE` | `1` | — | Optional float32 value-cache scale with the same shape convention as `k_scale`. | optional |
|
| 36 |
+
| `qNormWeightT` | `q_norm_weight` | `T` | `1` | — | Optional per-head RMS-normalization weight of shape `(head_size)` applied to queries before rotary embedding. It must be provided together with `k_norm_weight`. | optional |
|
| 37 |
+
| `kNormWeightT` | `k_norm_weight` | `T` | `1` | — | Optional per-head RMS-normalization weight of shape `(head_size)` applied to keys before rotary embedding. It must be provided together with `q_norm_weight`. | optional |
|
| 38 |
|
| 39 |
## Outputs
|
| 40 |
|
| 41 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 42 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 43 |
+
| `outputT` | `output` | `T` | `3` | same as `queryT` | Attention output of shape `(batch_size, sequence_length, hidden_size)`. | required |
|
| 44 |
+
| `presentKeyT` | `present_key` | `T_CACHE` | `4` | — | Updated key cache in BNSH format. Its sequence axis is `max_sequence_length` for a shared buffer, otherwise `past_sequence_length + kv_sequence_length`; int4 stores each signed value as a +8-biased nibble, with the even head coordinate low, packing two values per logical uint8 element and widening each byte to one u32 WebGPU buffer word. | required |
|
| 45 |
+
| `presentValueT` | `present_value` | `T_CACHE` | `4` | — | Updated value cache in BNSH format with the same length and packing semantics as `present_key`. | required |
|
| 46 |
|
| 47 |
## Attributes
|
| 48 |
|
|
|
|
| 53 |
| `causal` | `1` | Whether to apply a causal mask. Set to 0 for bidirectional attention; `local_window_size` must then be -1. |
|
| 54 |
| `do_rotary` | `0` | Set to 1 to apply rotary position embeddings. The default 0 disables them. |
|
| 55 |
| `k_quant_type` | `"NONE"` | Key-cache quantization mode: `NONE`, `PER_TENSOR`, or `PER_CHANNEL`. |
|
| 56 |
+
| `kv_cache_bit_width` | — | Quantized cache bit width, either 8 or 4. Four-bit values are packed two per uint8 element. |
|
| 57 |
+
| `kv_num_heads` | — | Number of key/value attention heads. |
|
| 58 |
| `local_window_size` | `-1` | Left window size for causal local attention. The default -1 disables local attention, and the value must be -1 when `causal` is 0. |
|
| 59 |
+
| `num_heads` | — | Number of query attention heads. |
|
| 60 |
| `qk_norm_epsilon` | `0.000001` | Epsilon for the per-head Q/K RMS normalization applied when both normalization weights are provided. |
|
| 61 |
+
| `scale` | — | Optional QK score scale; zero or omission selects `1 / sqrt(head_size)`. |
|
| 62 |
| `sliding_window_cache` | `0` | Set to 1 when past/present caches are fixed-size window buffers that evict old tokens from the front. Requires `local_window_size > 0` and enough cache capacity. |
|
| 63 |
| `smooth_softmax` | `-1` | Set to 1 to enable the smooth-softmax denominator term. |
|
| 64 |
| `softcap` | `0` | Positive softcap applied to attention scores. The default 0 disables soft-capping. |
|
| 65 |
| `v_quant_type` | `"NONE"` | Value-cache quantization mode: `NONE`, `PER_TENSOR`, or `PER_CHANNEL`. |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
## Type constraints
|
| 68 |
|
|
|
|
| 73 |
| `T_KV_SCALE` | `float32` |
|
| 74 |
| `M` | `int32` |
|
| 75 |
|
| 76 |
+
## Implementation variants
|
| 77 |
+
|
| 78 |
+
One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
|
| 79 |
+
|
| 80 |
+
- `qkv_present_materialized_sgmat_f32` — Materialized float32 subgroup-matrix prefill for bidirectional QKV without past state. `seqlens_k` is metadata-only; the score pass emits per-row softmax statistics, the apply pass normalizes and applies the scores, and the present copy preserves the supplied key and value tensors.
|
| 81 |
+
- `past_kv_materialized_sgmat_f32` — Materializes the causal score matrix with float32 subgroup-matrix tiles over the shared float cache, then applies the softmax-normalized weights with subgroup-matrix tiles. Both passes skip key tiles beyond each query tile's causal bound.
|
| 82 |
+
- `past_kv_bias_materialized_sgmat_f32` — Materializes causal scores with float32 subgroup-matrix tiles, broadcasts and adds the attention bias across batch and head dimensions, folds the biased scores into row statistics, and applies the normalized weights with subgroup-matrix tiles.
|
| 83 |
+
- `past_kv_materialized_sgmat_f16` — Materializes the causal score matrix with `f16` operand tiles feeding `f32`-accumulating subgroup matrices over the shared cache and applies the softmax-normalized weights the same way; scores and row statistics stay `f32`. The score pass skips key tiles past each query tile's causal bound and the apply pass stops its reduction there.
|
| 84 |
+
- `past_kv_rotary_materialized_sgmat_f32` — Rotates each query block at its absolute positions, then materializes causal scores against rotary-transformed cached keys and applies the normalized weights. The score and apply passes skip tiles beyond each query tile's causal bound.
|
| 85 |
+
- `past_kv_rotary_materialized_sgmat_f16` — Materializes the causal score matrix with `f16` operand tiles feeding `f32`-accumulating subgroup matrices over the shared cache and applies the softmax-normalized weights the same way; scores and row statistics stay `f32`. The score pass skips key tiles past each query tile's causal bound and the apply pass stops its reduction there.
|
| 86 |
+
- `new_kv_past_materialized_sgmat_f32` — For chunked prefill, merges the past cache with new key/value rows, materializes causal scores over the merged cache, and applies the normalized weights using the same right-aligned causal bound.
|
| 87 |
+
- `window_shift_materialized_sgmat_f32` — Materializes causal chunked-prefill attention with a windowed cache. The shift pass compacts surviving rows and appends the chunk; score and apply passes enforce both the sliding-window floor and causal bound.
|
| 88 |
+
- `share_append_materialized_sgmat_f32` — Materializes causal chunked-prefill attention while updating a shared-capacity cache in place. Score and apply passes bound every causal tile by the live length from `seqlens_k`, not the buffer capacity, so right-padded batches remain left-aligned.
|
| 89 |
+
- `new_kv_share_append_split` — Retains the existing cache and appends new key/value rows in separate passes before portable attention. It avoids rebuilding unchanged cache positions when the past and present allocations share capacity.
|
| 90 |
+
- `qkv_present_tiled_nosg` — Portable tiled prefill route that computes attention online and writes the present cache separately. It is used when the flash shape is valid but no suitable subgroup route is admissible.
|
| 91 |
+
- `quant_int8_decode_splitk` — Appends int8-quantized key/value rows, partitions cached decode across the key axis, and merges partial online-softmax results. Used when one workgroup per head exposes too little independent work.
|
| 92 |
+
- `qkv_present_flash_splitk` — Partitions direct Q/K/V attention across the key axis, merges partial online-softmax results, and writes the present cache separately. It serves short-query shapes needing more key-axis parallelism.
|
| 93 |
+
- `qkv_present_flash_cluster` — Computes clustered online-softmax prefill directly from Q/K/V and writes the present cache separately. The family provides subgroup and portable reductions for the same tiled algorithm.
|
| 94 |
+
- `quant_int8_decode_splitk_nosg` — Appends int8-quantized key/value rows, partitions cached decode across the key axis, and merges partial online-softmax results. Used when one workgroup per head exposes too little independent work.
|
| 95 |
+
- `qkv_present_flash_splitk_nosg` — Partitions direct Q/K/V attention across the key axis, merges partial online-softmax results, and writes the present cache separately. It serves short-query shapes needing more key-axis parallelism.
|
| 96 |
+
- `qkv_present_flash_cluster_nosg` — Computes clustered online-softmax prefill directly from Q/K/V and writes the present cache separately. The family provides subgroup and portable reductions for the same tiled algorithm.
|
| 97 |
+
- `past_kv_decode_splitk` — Updates the float cache in copy, merge, or window-shift mode, then partitions cached decode across the key axis and combines partial online-softmax results. The family includes subgroup and portable forms.
|
| 98 |
+
- `new_kv_past_decode_splitk` — Updates the float cache in copy, merge, or window-shift mode, then partitions cached decode across the key axis and combines partial online-softmax results. The family includes subgroup and portable forms.
|
| 99 |
+
- `window_shift_decode_splitk` — Updates the float cache in copy, merge, or window-shift mode, then partitions cached decode across the key axis and combines partial online-softmax results. The family includes subgroup and portable forms.
|
| 100 |
+
- `past_kv_decode_splitk_nosg` — Updates the float cache in copy, merge, or window-shift mode, then partitions cached decode across the key axis and combines partial online-softmax results. The family includes subgroup and portable forms.
|
| 101 |
+
- `new_kv_past_decode_splitk_nosg` — Updates the float cache in copy, merge, or window-shift mode, then partitions cached decode across the key axis and combines partial online-softmax results. The family includes subgroup and portable forms.
|
| 102 |
+
- `window_shift_decode_splitk_nosg` — Updates the float cache in copy, merge, or window-shift mode, then partitions cached decode across the key axis and combines partial online-softmax results. The family includes subgroup and portable forms.
|
| 103 |
+
- `past_kv_flash_prefill` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 104 |
+
- `new_kv_past_flash_prefill` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 105 |
+
- `past_kv_rotary_flash_prefill` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 106 |
+
- `past_kv_softcap_flash_prefill` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 107 |
+
- `past_kv_headsink_flash_prefill` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 108 |
+
- `past_kv_bias_headsink_flash_prefill` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 109 |
+
- `window_shift_flash_prefill` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 110 |
+
- `past_kv_flash_prefill_nosg` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 111 |
+
- `new_kv_past_flash_prefill_nosg` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 112 |
+
- `past_kv_rotary_flash_prefill_nosg` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 113 |
+
- `past_kv_softcap_flash_prefill_nosg` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 114 |
+
- `past_kv_headsink_flash_prefill_nosg` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 115 |
+
- `past_kv_bias_headsink_flash_prefill_nosg` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 116 |
+
- `window_shift_flash_prefill_nosg` — Updates the float cache in the selected mode and applies clustered online-softmax causal prefill with the configured cache, mask, rotary, softcap, bias, or head-sink features.
|
| 117 |
+
- `quant_int8_flash_prefill` — Builds an int8 or int4 cache and applies clustered online-softmax prefill directly from the packed values. The family supplies subgroup and portable reductions.
|
| 118 |
+
- `quant_int4_flash_prefill` — Builds an int8 or int4 cache and applies clustered online-softmax prefill directly from the packed values. The family supplies subgroup and portable reductions.
|
| 119 |
+
- `quant_int8_flash_prefill_nosg` — Builds an int8 or int4 cache and applies clustered online-softmax prefill directly from the packed values. The family supplies subgroup and portable reductions.
|
| 120 |
+
- `quant_int4_flash_prefill_nosg` — Builds an int8 or int4 cache and applies clustered online-softmax prefill directly from the packed values. The family supplies subgroup and portable reductions.
|
| 121 |
+
- `share_append_split_decode_splitk` — Retains shared-capacity cache rows, appends new rows separately, then partitions decode across the key axis. It avoids rebuilding unchanged cache positions while exposing split-key parallelism.
|
| 122 |
+
- `share_append_split_decode_splitk_nosg` — Retains shared-capacity cache rows, appends new rows separately, then partitions decode across the key axis. It avoids rebuilding unchanged cache positions while exposing split-key parallelism.
|
| 123 |
+
- `share_append_split_flash_prefill` — Retains shared-capacity cache rows, appends new rows separately, then applies clustered causal prefill. It avoids rebuilding unchanged cache positions and includes subgroup and portable forms.
|
| 124 |
+
- `share_append_split_flash_prefill_nosg` — Retains shared-capacity cache rows, appends new rows separately, then applies clustered causal prefill. It avoids rebuilding unchanged cache positions and includes subgroup and portable forms.
|
| 125 |
+
|
| 126 |
## Device requirements
|
| 127 |
|
| 128 |
Some implementation variants require `subgroup-matrix`, `shader-f16`, and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
| 129 |
|
| 130 |
## Files
|
| 131 |
|
| 132 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 133 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 134 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 135 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
|
|
| 148 |
|
| 149 |
## Use with `@huggingface/kernels`
|
| 150 |
|
| 151 |
+
```sh
|
| 152 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.2
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
|
| 156 |
|
| 157 |
+
This example supplies explicit metadata for:
|
| 158 |
|
| 159 |
- `presentKeyT`
|
| 160 |
- `presentValueT`
|
| 161 |
|
|
|
|
|
|
|
| 162 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 163 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 164 |
|
| 165 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 166 |
|
build/webgpu/attention-rank4-tiled.wgsl.jinja
CHANGED
|
@@ -1,19 +1,15 @@
|
|
| 1 |
-
{% if
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
|
| 6 |
// Thread-per-query online-softmax flash fallback for rank-4 (BNSH) attention with
|
| 7 |
// no optional feature requirements. Each invocation owns one full query row and walks the keys
|
| 8 |
// independently, avoiding per-key workgroup barriers and tree reductions.
|
| 9 |
//
|
| 10 |
-
// BLOCK_M consecutive queries of one (batch, q_head) are tiled into a workgroup
|
| 11 |
-
//
|
| 12 |
-
// L2 cache (read once per head, broadcast across the tile). The per-query online
|
| 13 |
// accumulator lives in shared memory laid out d-major (d*BLOCK_M + tid) so the
|
| 14 |
-
// BLOCK_M threads touch consecutive addresses for a fixed d
|
| 15 |
-
//
|
| 16 |
-
// the workgroup-storage budget;
|
| 17 |
// larger heads use the scalar online-attention fallback.
|
| 18 |
const BLOCK_M: u32 = {{ blockM }}u;
|
| 19 |
|
|
@@ -34,13 +30,12 @@ fn kv_head(q_head: u32) -> u32 {
|
|
| 34 |
@compute @workgroup_size(BLOCK_M, 1, 1)
|
| 35 |
fn main(
|
| 36 |
@builtin(workgroup_id) wg: vec3<u32>,
|
| 37 |
-
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 38 |
@builtin(local_invocation_id) lid: vec3<u32>
|
| 39 |
) {
|
| 40 |
let tid = lid.x;
|
| 41 |
// 2D-folded tile index over (batch, q_head, q_seq tile); wg.y carries the high
|
| 42 |
-
// bits past the
|
| 43 |
-
let tile = wg.x + wg.y *
|
| 44 |
let qTiles = (params.qSeq + BLOCK_M - 1u) / BLOCK_M;
|
| 45 |
// count = numel(Y) = batch·qHeads·qSeq·vHeadSize, so count/(vHeadSize·qSeq) =
|
| 46 |
// batch·qHeads. Drop the over-dispatched tail from the 2D fold.
|
|
@@ -56,8 +51,14 @@ fn main(
|
|
| 56 |
if (qs >= params.qSeq) { return; }
|
| 57 |
let kh = kv_head(qh);
|
| 58 |
|
|
|
|
| 59 |
// Token-major packed QKV: row = (batch*seq + token)*HIDDEN + head*head_dim.
|
| 60 |
let qBase = (batch * params.qSeq + qs) * params.qHidden + qh * params.headSize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
let scale = scale_value();
|
| 62 |
|
| 63 |
for (var d: u32 = 0u; d < params.vHeadSize; d = d + 1u) {
|
|
@@ -82,10 +83,14 @@ fn main(
|
|
| 82 |
// A rejected bool-mask key has zero softmax mass. Skipping it is safe here:
|
| 83 |
// this thread-per-query kernel has no barriers inside the key loop.
|
| 84 |
if (masked) { continue; }
|
| 85 |
-
//
|
| 86 |
var score: f32 = -3.4028234663852886e38;
|
| 87 |
if (!masked) {
|
|
|
|
| 88 |
let kRow = (batch * params.kvSeq + ks) * params.kvHidden + kh * params.headSize;
|
|
|
|
|
|
|
|
|
|
| 89 |
var dot: f32 = 0.0;
|
| 90 |
for (var d: u32 = 0u; d < params.headSize; d = d + 1u) {
|
| 91 |
dot = dot + f32(q[qBase + d]) * f32(k[kRow + d]);
|
|
@@ -105,14 +110,22 @@ fn main(
|
|
| 105 |
let weight = exp(score - next_max);
|
| 106 |
running_max = next_max;
|
| 107 |
running_denom = running_denom * prev_scale + weight;
|
|
|
|
| 108 |
let vRow = (batch * params.kvSeq + ks) * params.vHidden + kh * params.vHeadSize;
|
|
|
|
|
|
|
|
|
|
| 109 |
for (var d: u32 = 0u; d < params.vHeadSize; d = d + 1u) {
|
| 110 |
acc[d * BLOCK_M + tid] = acc[d * BLOCK_M + tid] * prev_scale + weight * f32(v[vRow + d]);
|
| 111 |
}
|
| 112 |
}
|
| 113 |
|
| 114 |
let inv_denom = select(0.0, 1.0 / running_denom, running_denom > 0.0);
|
|
|
|
| 115 |
let yBase = (batch * params.qSeq + qs) * (params.qHeads * params.vHeadSize) + qh * params.vHeadSize;
|
|
|
|
|
|
|
|
|
|
| 116 |
for (var d: u32 = 0u; d < params.vHeadSize; d = d + 1u) {
|
| 117 |
y[yBase + d] = {{ scalar }}(acc[d * BLOCK_M + tid] * inv_denom);
|
| 118 |
}
|
|
|
|
| 1 |
+
{% set layout = layout if layout is defined else "bnsh" %}
|
|
|
|
|
|
|
| 2 |
{{ env.wgsl.resourceDeclarations }}
|
| 3 |
|
| 4 |
// Thread-per-query online-softmax flash fallback for rank-4 (BNSH) attention with
|
| 5 |
// no optional feature requirements. Each invocation owns one full query row and walks the keys
|
| 6 |
// independently, avoiding per-key workgroup barriers and tree reductions.
|
| 7 |
//
|
| 8 |
+
// BLOCK_M consecutive queries of one (batch, q_head) are tiled into a workgroup,
|
| 9 |
+
// so adjacent query threads reuse the same K/V row at each key index. The per-query online
|
|
|
|
| 10 |
// accumulator lives in shared memory laid out d-major (d*BLOCK_M + tid) so the
|
| 11 |
+
// BLOCK_M threads touch consecutive addresses for a fixed d. The value-head
|
| 12 |
+
// width is capped so the accumulator fits in the workgroup-storage budget;
|
|
|
|
| 13 |
// larger heads use the scalar online-attention fallback.
|
| 14 |
const BLOCK_M: u32 = {{ blockM }}u;
|
| 15 |
|
|
|
|
| 30 |
@compute @workgroup_size(BLOCK_M, 1, 1)
|
| 31 |
fn main(
|
| 32 |
@builtin(workgroup_id) wg: vec3<u32>,
|
|
|
|
| 33 |
@builtin(local_invocation_id) lid: vec3<u32>
|
| 34 |
) {
|
| 35 |
let tid = lid.x;
|
| 36 |
// 2D-folded tile index over (batch, q_head, q_seq tile); wg.y carries the high
|
| 37 |
+
// bits past the per-axis dispatch fold width.
|
| 38 |
+
let tile = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 39 |
let qTiles = (params.qSeq + BLOCK_M - 1u) / BLOCK_M;
|
| 40 |
// count = numel(Y) = batch·qHeads·qSeq·vHeadSize, so count/(vHeadSize·qSeq) =
|
| 41 |
// batch·qHeads. Drop the over-dispatched tail from the 2D fold.
|
|
|
|
| 51 |
if (qs >= params.qSeq) { return; }
|
| 52 |
let kh = kv_head(qh);
|
| 53 |
|
| 54 |
+
{% if layout == "bsh" %}
|
| 55 |
// Token-major packed QKV: row = (batch*seq + token)*HIDDEN + head*head_dim.
|
| 56 |
let qBase = (batch * params.qSeq + qs) * params.qHidden + qh * params.headSize;
|
| 57 |
+
{% else %}
|
| 58 |
+
let qBase = ((batch * params.qHeads + qh) * params.qSeq + qs) * params.headSize;
|
| 59 |
+
let kBase = (batch * params.kvHeads + kh) * params.kvSeq;
|
| 60 |
+
let vBase = (batch * params.kvHeads + kh) * params.kvSeq;
|
| 61 |
+
{% endif %}
|
| 62 |
let scale = scale_value();
|
| 63 |
|
| 64 |
for (var d: u32 = 0u; d < params.vHeadSize; d = d + 1u) {
|
|
|
|
| 83 |
// A rejected bool-mask key has zero softmax mass. Skipping it is safe here:
|
| 84 |
// this thread-per-query kernel has no barriers inside the key loop.
|
| 85 |
if (masked) { continue; }
|
| 86 |
+
// Adjacent query threads load the same K/V row for this key.
|
| 87 |
var score: f32 = -3.4028234663852886e38;
|
| 88 |
if (!masked) {
|
| 89 |
+
{% if layout == "bsh" %}
|
| 90 |
let kRow = (batch * params.kvSeq + ks) * params.kvHidden + kh * params.headSize;
|
| 91 |
+
{% else %}
|
| 92 |
+
let kRow = (kBase + ks) * params.headSize;
|
| 93 |
+
{% endif %}
|
| 94 |
var dot: f32 = 0.0;
|
| 95 |
for (var d: u32 = 0u; d < params.headSize; d = d + 1u) {
|
| 96 |
dot = dot + f32(q[qBase + d]) * f32(k[kRow + d]);
|
|
|
|
| 110 |
let weight = exp(score - next_max);
|
| 111 |
running_max = next_max;
|
| 112 |
running_denom = running_denom * prev_scale + weight;
|
| 113 |
+
{% if layout == "bsh" %}
|
| 114 |
let vRow = (batch * params.kvSeq + ks) * params.vHidden + kh * params.vHeadSize;
|
| 115 |
+
{% else %}
|
| 116 |
+
let vRow = (vBase + ks) * params.vHeadSize;
|
| 117 |
+
{% endif %}
|
| 118 |
for (var d: u32 = 0u; d < params.vHeadSize; d = d + 1u) {
|
| 119 |
acc[d * BLOCK_M + tid] = acc[d * BLOCK_M + tid] * prev_scale + weight * f32(v[vRow + d]);
|
| 120 |
}
|
| 121 |
}
|
| 122 |
|
| 123 |
let inv_denom = select(0.0, 1.0 / running_denom, running_denom > 0.0);
|
| 124 |
+
{% if layout == "bsh" %}
|
| 125 |
let yBase = (batch * params.qSeq + qs) * (params.qHeads * params.vHeadSize) + qh * params.vHeadSize;
|
| 126 |
+
{% else %}
|
| 127 |
+
let yBase = ((batch * params.qHeads + qh) * params.qSeq + qs) * params.vHeadSize;
|
| 128 |
+
{% endif %}
|
| 129 |
for (var d: u32 = 0u; d < params.vHeadSize; d = d + 1u) {
|
| 130 |
y[yBase + d] = {{ scalar }}(acc[d * BLOCK_M + tid] * inv_denom);
|
| 131 |
}
|
build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja
CHANGED
|
@@ -3,6 +3,7 @@ enable f16;
|
|
| 3 |
{% endif %}
|
| 4 |
{% if splitQueries is not defined %}{% set splitQueries = false %}{% endif %}
|
| 5 |
{% if hasGate is not defined %}{% set hasGate = false %}{% endif %}
|
|
|
|
| 6 |
{{ env.wgsl.resourceDeclarations }}
|
| 7 |
|
| 8 |
// Split-K flash decode, pass 2 of 2. Combines the per-split un-normalized online
|
|
@@ -14,10 +15,10 @@ enable f16;
|
|
| 14 |
// One workgroup per (batch, head); one thread per output vec4 channel. The
|
| 15 |
// per-split (max, denom) scan is tiny (NUM_SPLITS is small), so every thread
|
| 16 |
// recomputes it locally and keeps the merge state in registers.
|
| 17 |
-
{% if
|
| 18 |
// Output layout: rank-4 [batch, heads, seq, headDim], matching the split pass.
|
| 19 |
-
{% elif
|
| 20 |
-
// Output layout: flat [heads, headDim], matching
|
| 21 |
{% else %}
|
| 22 |
// Output layout: token-major [batch, seq, heads * headDim], matching the split pass.
|
| 23 |
{% endif %}
|
|
@@ -27,7 +28,7 @@ const NUM_SPLITS: u32 = {{ numSplits }}u;
|
|
| 27 |
{% if splitQueries %}
|
| 28 |
const Q_SEQ: u32 = {{ qSeq }}u;
|
| 29 |
{% endif %}
|
| 30 |
-
{% if
|
| 31 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
|
| 32 |
{% endif %}
|
| 33 |
{% if hasBias %}
|
|
@@ -97,13 +98,13 @@ fn main(
|
|
| 97 |
outV = outV + partial_out[pBase + d4] * w;
|
| 98 |
}
|
| 99 |
|
| 100 |
-
{% if
|
| 101 |
{% if splitQueries %}
|
| 102 |
let qBaseV4 = (b * Q_SEQ + queryToken) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 103 |
{% else %}
|
| 104 |
let qBaseV4 = b * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 105 |
{% endif %}
|
| 106 |
-
{% elif
|
| 107 |
let qBaseV4 = h * HEAD_DIM_V4;
|
| 108 |
{% else %}
|
| 109 |
{% if splitQueries %}
|
|
@@ -123,7 +124,7 @@ fn main(
|
|
| 123 |
outValue = outValue + vec4<f32>(bias[vBiasBase], bias[vBiasBase + 1u], bias[vBiasBase + 2u], bias[vBiasBase + 3u]);
|
| 124 |
{% endif %}
|
| 125 |
{% if hasGate %}
|
| 126 |
-
//
|
| 127 |
let gateV = vec4<f32>(gate[qBaseV4 + d4]);
|
| 128 |
outValue = outValue * (vec4<f32>(1.0) / (vec4<f32>(1.0) + exp(-gateV)));
|
| 129 |
{% endif %}
|
|
|
|
| 3 |
{% endif %}
|
| 4 |
{% if splitQueries is not defined %}{% set splitQueries = false %}{% endif %}
|
| 5 |
{% if hasGate is not defined %}{% set hasGate = false %}{% endif %}
|
| 6 |
+
{% set qSeq = qSeq | default(0) %}
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
|
| 9 |
// Split-K flash decode, pass 2 of 2. Combines the per-split un-normalized online
|
|
|
|
| 15 |
// One workgroup per (batch, head); one thread per output vec4 channel. The
|
| 16 |
// per-split (max, denom) scan is tiny (NUM_SPLITS is small), so every thread
|
| 17 |
// recomputes it locally and keeps the merge state in registers.
|
| 18 |
+
{% if layout == "bhsd" %}
|
| 19 |
// Output layout: rank-4 [batch, heads, seq, headDim], matching the split pass.
|
| 20 |
+
{% elif layout == "layer_cache" %}
|
| 21 |
+
// Output layout: flat [heads, headDim], matching the cached-decode split pass.
|
| 22 |
{% else %}
|
| 23 |
// Output layout: token-major [batch, seq, heads * headDim], matching the split pass.
|
| 24 |
{% endif %}
|
|
|
|
| 28 |
{% if splitQueries %}
|
| 29 |
const Q_SEQ: u32 = {{ qSeq }}u;
|
| 30 |
{% endif %}
|
| 31 |
+
{% if layout == "bsh" %}
|
| 32 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
|
| 33 |
{% endif %}
|
| 34 |
{% if hasBias %}
|
|
|
|
| 98 |
outV = outV + partial_out[pBase + d4] * w;
|
| 99 |
}
|
| 100 |
|
| 101 |
+
{% if layout == "bsh" %}
|
| 102 |
{% if splitQueries %}
|
| 103 |
let qBaseV4 = (b * Q_SEQ + queryToken) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 104 |
{% else %}
|
| 105 |
let qBaseV4 = b * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 106 |
{% endif %}
|
| 107 |
+
{% elif layout == "layer_cache" %}
|
| 108 |
let qBaseV4 = h * HEAD_DIM_V4;
|
| 109 |
{% else %}
|
| 110 |
{% if splitQueries %}
|
|
|
|
| 124 |
outValue = outValue + vec4<f32>(bias[vBiasBase], bias[vBiasBase + 1u], bias[vBiasBase + 2u], bias[vBiasBase + 3u]);
|
| 125 |
{% endif %}
|
| 126 |
{% if hasGate %}
|
| 127 |
+
// The gated route multiplies the normalized attention output elementwise by its gate.
|
| 128 |
let gateV = vec4<f32>(gate[qBaseV4 + d4]);
|
| 129 |
outValue = outValue * (vec4<f32>(1.0) / (vec4<f32>(1.0) + exp(-gateV)));
|
| 130 |
{% endif %}
|
build/webgpu/attn-flash-decode-splitk.wgsl.jinja
CHANGED
|
@@ -4,17 +4,19 @@
|
|
| 4 |
{% if cacheSeqlens is not defined %}{% set cacheSeqlens = false %}{% endif %}
|
| 5 |
{% if hasMask is not defined %}{% set hasMask = false %}{% endif %}
|
| 6 |
{% if maskIsBool is not defined %}{% set maskIsBool = false %}{% endif %}
|
| 7 |
-
{% set
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
{% if useSubgroups %}
|
| 9 |
enable subgroups;
|
| 10 |
{% endif %}
|
| 11 |
-
{% if usesF16 %}
|
| 12 |
-
enable f16;
|
| 13 |
-
{% endif %}
|
| 14 |
{{ env.wgsl.resourceDeclarations }}
|
| 15 |
|
| 16 |
-
// Split-K flash attention, pass 1 of 2; the merge pass follows.
|
| 17 |
-
//
|
| 18 |
//
|
| 19 |
// The non-split flash decode launches only `batch * numHeads` workgroups, each
|
| 20 |
// sweeping the whole KV sequence serially in WG-key tiles. This pass splits the
|
|
@@ -24,25 +26,24 @@ enable f16;
|
|
| 24 |
// its range — the running (max, denom) and the softmax-weighted V sum before the
|
| 25 |
// final divide — and the merge pass combines the per-split states with the online
|
| 26 |
// rule.
|
| 27 |
-
{% if
|
| 28 |
// Layout: rank-4 [batch, heads, seq, headDim] for Q/K/V.
|
| 29 |
-
{% elif
|
| 30 |
// Layout: flat query [heads, headDim] plus a persistent KV cache laid out
|
| 31 |
-
// [layer, cacheLen, kvHeads, headDim].
|
| 32 |
-
// dispatch has a single implicit batch.
|
| 33 |
{% else %}
|
| 34 |
// Layout: token-major [batch, seq, heads * headDim]; Q and KV hidden strides
|
| 35 |
// are compiled constants.
|
| 36 |
{% endif %}
|
| 37 |
-
{% if (fusedQNormRope is defined and fusedQNormRope) or
|
| 38 |
{% endif %}
|
| 39 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 40 |
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 41 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 42 |
-
{% if
|
| 43 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
|
| 44 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
| 45 |
-
{% elif
|
| 46 |
const LAYER: u32 = {{ layer }}u;
|
| 47 |
const CACHE_LEN: u32 = {{ cacheLen }}u;
|
| 48 |
const ATTN_SCALE: f32 = {{ scale }};
|
|
@@ -96,20 +97,22 @@ var<workgroup> vacc_sh: array<vec4<f32>, WG>;
|
|
| 96 |
// calls before their shared partial storage is reused.
|
| 97 |
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 98 |
{% if combineSubgroups %}
|
| 99 |
-
//
|
| 100 |
-
//
|
| 101 |
-
//
|
| 102 |
-
//
|
| 103 |
-
//
|
| 104 |
-
//
|
| 105 |
-
//
|
|
|
|
| 106 |
var<workgroup> partialM: array<f32, WG>;
|
| 107 |
var<workgroup> partialD: array<f32, WG>;
|
|
|
|
| 108 |
var<workgroup> combinedMD: vec2<f32>;
|
| 109 |
|
| 110 |
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 111 |
-
// it (no barriers, no shared state)
|
| 112 |
-
//
|
| 113 |
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 114 |
let sgM = subgroupMax(m);
|
| 115 |
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
|
@@ -120,31 +123,24 @@ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
|
| 120 |
if (sgSize == WG) {
|
| 121 |
return vec2<f32>(sgM, sgD);
|
| 122 |
}
|
| 123 |
-
let subgroupCount = (WG + sgSize - 1u) / sgSize;
|
| 124 |
-
// Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
|
| 125 |
-
// fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
|
| 126 |
-
// subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
|
| 127 |
-
// flash-attention loop that re-uses this shared memory each iteration — would
|
| 128 |
-
// otherwise read stale shared memory. Identity makes such a slot a no-op.
|
| 129 |
-
// (max identity = -FLT_MAX, denom identity = 0.)
|
| 130 |
-
if (lidx < subgroupCount) {
|
| 131 |
-
partialM[lidx] = -FLT_MAX;
|
| 132 |
-
partialD[lidx] = 0.0;
|
| 133 |
-
}
|
| 134 |
-
workgroupBarrier();
|
| 135 |
if (subgroupElect()) {
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
}
|
| 140 |
workgroupBarrier();
|
| 141 |
if (lidx == 0u) {
|
| 142 |
var accM = -FLT_MAX;
|
| 143 |
var accD = 0.0;
|
| 144 |
-
for (var
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
}
|
| 149 |
combinedMD = vec2<f32>(accM, accD);
|
| 150 |
}
|
|
@@ -159,9 +155,8 @@ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
|
| 159 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 160 |
{% if mdStreamed %}
|
| 161 |
|
| 162 |
-
// In-place fold of {{ mdStreams }} streams.
|
| 163 |
-
//
|
| 164 |
-
// from slot s * WG afterwards.
|
| 165 |
fn combine_partials_streams(lidx: u32) {
|
| 166 |
workgroupBarrier();
|
| 167 |
var stride = WG / 2u;
|
|
@@ -220,7 +215,7 @@ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
|
| 220 |
{% endif %}
|
| 221 |
|
| 222 |
|
| 223 |
-
{% if
|
| 224 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 225 |
fn scale_value() -> f32 {
|
| 226 |
{% if ATTN_SCALE_OVERRIDE is defined %}
|
|
@@ -310,12 +305,12 @@ fn main(
|
|
| 310 |
{% endif %}
|
| 311 |
let h = wg.y;
|
| 312 |
let b = wg.z;
|
| 313 |
-
if (h >= Q_HEADS || split >= NUM_SPLITS{% if splitQueries %} || queryToken >= Q_SEQ{% endif %}{% if
|
| 314 |
return;
|
| 315 |
}
|
| 316 |
let tid = lid.x;
|
| 317 |
let hKv = h / (Q_HEADS / KV_HEADS);
|
| 318 |
-
{% if
|
| 319 |
let kvSeq = params.past_len + 1u;
|
| 320 |
{% else %}
|
| 321 |
let cacheSeq = params.kvSeq;
|
|
@@ -329,7 +324,7 @@ fn main(
|
|
| 329 |
{% endif %}
|
| 330 |
|
| 331 |
// Query row (decode uses token zero; short-query prefill folds the token into wg.x).
|
| 332 |
-
{% if
|
| 333 |
{% if splitQueries %}
|
| 334 |
let qBaseV4 = (b * Q_SEQ + queryToken) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 335 |
{% else %}
|
|
@@ -337,7 +332,7 @@ fn main(
|
|
| 337 |
{% endif %}
|
| 338 |
let kvBaseV4 = b * kvSeq * KV_HIDDEN_V4 + hKv * HEAD_DIM_V4;
|
| 339 |
let kvTokenStrideV4 = KV_HIDDEN_V4;
|
| 340 |
-
{% elif
|
| 341 |
let qBaseV4 = h * HEAD_DIM_V4;
|
| 342 |
let kvBaseV4 = (LAYER * CACHE_LEN * KV_HEADS + hKv) * HEAD_DIM_V4;
|
| 343 |
let kvTokenStrideV4 = KV_HEADS * HEAD_DIM_V4;
|
|
|
|
| 4 |
{% if cacheSeqlens is not defined %}{% set cacheSeqlens = false %}{% endif %}
|
| 5 |
{% if hasMask is not defined %}{% set hasMask = false %}{% endif %}
|
| 6 |
{% if maskIsBool is not defined %}{% set maskIsBool = false %}{% endif %}
|
| 7 |
+
{% set layer = layer | default(0) %}
|
| 8 |
+
{% set cacheLen = cacheLen | default(0) %}
|
| 9 |
+
{% set scale = scale | default("0.0") %}
|
| 10 |
+
{% if hasRotary is not defined %}{% set hasRotary = false %}{% endif %}
|
| 11 |
+
{% set qSeq = qSeq | default(0) %}
|
| 12 |
+
{% set splitKWorkgroupSize = workgroupSizeSpec if workgroupSizeSpec is defined else tunables.WORKGROUP_SIZE %}
|
| 13 |
{% if useSubgroups %}
|
| 14 |
enable subgroups;
|
| 15 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 16 |
{{ env.wgsl.resourceDeclarations }}
|
| 17 |
|
| 18 |
+
// Split-K flash attention, pass 1 of 2; the merge pass follows. This geometry
|
| 19 |
+
// handles decode and short-query, long-context prefill inputs.
|
| 20 |
//
|
| 21 |
// The non-split flash decode launches only `batch * numHeads` workgroups, each
|
| 22 |
// sweeping the whole KV sequence serially in WG-key tiles. This pass splits the
|
|
|
|
| 26 |
// its range — the running (max, denom) and the softmax-weighted V sum before the
|
| 27 |
// final divide — and the merge pass combines the per-split states with the online
|
| 28 |
// rule.
|
| 29 |
+
{% if layout == "bhsd" %}
|
| 30 |
// Layout: rank-4 [batch, heads, seq, headDim] for Q/K/V.
|
| 31 |
+
{% elif layout == "layer_cache" %}
|
| 32 |
// Layout: flat query [heads, headDim] plus a persistent KV cache laid out
|
| 33 |
+
// [layer, cacheLen, kvHeads, headDim]. The dispatch has a single implicit batch.
|
|
|
|
| 34 |
{% else %}
|
| 35 |
// Layout: token-major [batch, seq, heads * headDim]; Q and KV hidden strides
|
| 36 |
// are compiled constants.
|
| 37 |
{% endif %}
|
| 38 |
+
{% if (fusedQNormRope is defined and fusedQNormRope) or layout != "layer_cache" %}const HEAD_DIM: u32 = {{ headDim }}u;
|
| 39 |
{% endif %}
|
| 40 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 41 |
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 42 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 43 |
+
{% if layout == "bsh" %}
|
| 44 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
|
| 45 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
| 46 |
+
{% elif layout == "layer_cache" %}
|
| 47 |
const LAYER: u32 = {{ layer }}u;
|
| 48 |
const CACHE_LEN: u32 = {{ cacheLen }}u;
|
| 49 |
const ATTN_SCALE: f32 = {{ scale }};
|
|
|
|
| 97 |
// calls before their shared partial storage is reused.
|
| 98 |
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 99 |
{% if combineSubgroups %}
|
| 100 |
+
// Cross-subgroup merge that assumes nothing about which invocations share a
|
| 101 |
+
// subgroup or how many subgroups there are: each subgroup's elected lane
|
| 102 |
+
// publishes the subgroup pair in the slot at its OWN invocation index and sets
|
| 103 |
+
// that index's bit in a workgroup bitmask; thread 0 then folds exactly the
|
| 104 |
+
// published slots, in ascending index order (the online (m, d) merge is not
|
| 105 |
+
// float-associative, so the order is fixed), and clears the mask for the next
|
| 106 |
+
// call as it reads it. Workgroup memory starts zeroed, so the mask needs no
|
| 107 |
+
// setup. Same three collectives as a single-subgroup reduce, two barriers.
|
| 108 |
var<workgroup> partialM: array<f32, WG>;
|
| 109 |
var<workgroup> partialD: array<f32, WG>;
|
| 110 |
+
var<workgroup> leaderMask: array<atomic<u32>, (WG + 31u) / 32u>;
|
| 111 |
var<workgroup> combinedMD: vec2<f32>;
|
| 112 |
|
| 113 |
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 114 |
+
// it (no barriers, no shared state). `subgroup_size` is the size of the current
|
| 115 |
+
// subgroup and uniform, so the test is exact and may guard the barriers below.
|
| 116 |
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 117 |
let sgM = subgroupMax(m);
|
| 118 |
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
|
|
|
| 123 |
if (sgSize == WG) {
|
| 124 |
return vec2<f32>(sgM, sgD);
|
| 125 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
if (subgroupElect()) {
|
| 127 |
+
partialM[lidx] = sgM;
|
| 128 |
+
partialD[lidx] = sgD;
|
| 129 |
+
atomicOr(&leaderMask[lidx / 32u], 1u << (lidx % 32u));
|
| 130 |
}
|
| 131 |
workgroupBarrier();
|
| 132 |
if (lidx == 0u) {
|
| 133 |
var accM = -FLT_MAX;
|
| 134 |
var accD = 0.0;
|
| 135 |
+
for (var w = 0u; w < (WG + 31u) / 32u; w = w + 1u) {
|
| 136 |
+
var bits = atomicExchange(&leaderMask[w], 0u);
|
| 137 |
+
while (bits != 0u) {
|
| 138 |
+
let slot = w * 32u + firstTrailingBit(bits);
|
| 139 |
+
bits = bits & (bits - 1u);
|
| 140 |
+
let mNew = max(accM, partialM[slot]);
|
| 141 |
+
accD = accD * exp_shift(accM, mNew) + partialD[slot] * exp_shift(partialM[slot], mNew);
|
| 142 |
+
accM = mNew;
|
| 143 |
+
}
|
| 144 |
}
|
| 145 |
combinedMD = vec2<f32>(accM, accD);
|
| 146 |
}
|
|
|
|
| 155 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 156 |
{% if mdStreamed %}
|
| 157 |
|
| 158 |
+
// In-place fold of {{ mdStreams }} streams. Input partials occupy
|
| 159 |
+
// partialM/partialD; stream s returns its merged pair in slot s * WG.
|
|
|
|
| 160 |
fn combine_partials_streams(lidx: u32) {
|
| 161 |
workgroupBarrier();
|
| 162 |
var stride = WG / 2u;
|
|
|
|
| 215 |
{% endif %}
|
| 216 |
|
| 217 |
|
| 218 |
+
{% if layout == "layer_cache" %}{% set ATTN_SCALE_OVERRIDE = "ATTN_SCALE" %}{% endif %}
|
| 219 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 220 |
fn scale_value() -> f32 {
|
| 221 |
{% if ATTN_SCALE_OVERRIDE is defined %}
|
|
|
|
| 305 |
{% endif %}
|
| 306 |
let h = wg.y;
|
| 307 |
let b = wg.z;
|
| 308 |
+
if (h >= Q_HEADS || split >= NUM_SPLITS{% if splitQueries %} || queryToken >= Q_SEQ{% endif %}{% if layout == "layer_cache" %} || params.past_len >= CACHE_LEN{% endif %}) {
|
| 309 |
return;
|
| 310 |
}
|
| 311 |
let tid = lid.x;
|
| 312 |
let hKv = h / (Q_HEADS / KV_HEADS);
|
| 313 |
+
{% if layout == "layer_cache" %}
|
| 314 |
let kvSeq = params.past_len + 1u;
|
| 315 |
{% else %}
|
| 316 |
let cacheSeq = params.kvSeq;
|
|
|
|
| 324 |
{% endif %}
|
| 325 |
|
| 326 |
// Query row (decode uses token zero; short-query prefill folds the token into wg.x).
|
| 327 |
+
{% if layout == "bsh" %}
|
| 328 |
{% if splitQueries %}
|
| 329 |
let qBaseV4 = (b * Q_SEQ + queryToken) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 330 |
{% else %}
|
|
|
|
| 332 |
{% endif %}
|
| 333 |
let kvBaseV4 = b * kvSeq * KV_HIDDEN_V4 + hKv * HEAD_DIM_V4;
|
| 334 |
let kvTokenStrideV4 = KV_HIDDEN_V4;
|
| 335 |
+
{% elif layout == "layer_cache" %}
|
| 336 |
let qBaseV4 = h * HEAD_DIM_V4;
|
| 337 |
let kvBaseV4 = (LAYER * CACHE_LEN * KV_HEADS + hKv) * HEAD_DIM_V4;
|
| 338 |
let kvTokenStrideV4 = KV_HEADS * HEAD_DIM_V4;
|
build/webgpu/attn-flash-online.wgsl.jinja
CHANGED
|
@@ -1,13 +1,10 @@
|
|
| 1 |
-
{% if usesF16 %}
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{% if combineSubgroups %}
|
| 5 |
enable subgroups;
|
| 6 |
{% endif %}
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
|
| 9 |
-
// Flash-style tiled online-softmax attention
|
| 10 |
-
//
|
| 11 |
// (batch, head, query token); the workgroup sweeps the KV sequence in tiles
|
| 12 |
// of WG keys, each thread owning one key of the tile (full q·k dot in
|
| 13 |
// registers, vec4 loads), so a tile needs a single fused (max, sum-exp)
|
|
@@ -16,9 +13,6 @@ enable subgroups;
|
|
| 16 |
// per tile; the full score matrix is never materialized.
|
| 17 |
// Layout: token-major [batch, seq, heads * headDim]; Q and KV hidden strides
|
| 18 |
// are compiled constants.
|
| 19 |
-
{% if headsFromParams %}
|
| 20 |
-
// Q/KV head counts are uniforms; the GQA mapping is h / (qHeads / kvHeads).
|
| 21 |
-
{% endif %}
|
| 22 |
{% if hasBias %}
|
| 23 |
// Packed [Q; K; V] bias rows. The K bias adds the same dot(q, biasK) to every
|
| 24 |
// key score, which softmax cancels, so only the Q and V biases are applied.
|
|
@@ -32,10 +26,8 @@ const HEAD_DIM: u32 = {{ headDim }}u;
|
|
| 32 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 33 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
|
| 34 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
| 35 |
-
{% if not headsFromParams %}
|
| 36 |
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 37 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 38 |
-
{% endif %}
|
| 39 |
{% set qHeads = "params.qHeads" if headsFromParams else "Q_HEADS" %}
|
| 40 |
{% set kvHeads = "params.kvHeads" if headsFromParams else "KV_HEADS" %}
|
| 41 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
|
@@ -73,20 +65,22 @@ var<workgroup> probs: array<f32, WG>;
|
|
| 73 |
// calls before their shared partial storage is reused.
|
| 74 |
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 75 |
{% if combineSubgroups %}
|
| 76 |
-
//
|
| 77 |
-
//
|
| 78 |
-
//
|
| 79 |
-
//
|
| 80 |
-
//
|
| 81 |
-
//
|
| 82 |
-
//
|
|
|
|
| 83 |
var<workgroup> partialM: array<f32, WG>;
|
| 84 |
var<workgroup> partialD: array<f32, WG>;
|
|
|
|
| 85 |
var<workgroup> combinedMD: vec2<f32>;
|
| 86 |
|
| 87 |
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 88 |
-
// it (no barriers, no shared state)
|
| 89 |
-
//
|
| 90 |
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 91 |
let sgM = subgroupMax(m);
|
| 92 |
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
|
@@ -97,31 +91,24 @@ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
|
| 97 |
if (sgSize == WG) {
|
| 98 |
return vec2<f32>(sgM, sgD);
|
| 99 |
}
|
| 100 |
-
let subgroupCount = (WG + sgSize - 1u) / sgSize;
|
| 101 |
-
// Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
|
| 102 |
-
// fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
|
| 103 |
-
// subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
|
| 104 |
-
// flash-attention loop that re-uses this shared memory each iteration — would
|
| 105 |
-
// otherwise read stale shared memory. Identity makes such a slot a no-op.
|
| 106 |
-
// (max identity = -FLT_MAX, denom identity = 0.)
|
| 107 |
-
if (lidx < subgroupCount) {
|
| 108 |
-
partialM[lidx] = -FLT_MAX;
|
| 109 |
-
partialD[lidx] = 0.0;
|
| 110 |
-
}
|
| 111 |
-
workgroupBarrier();
|
| 112 |
if (subgroupElect()) {
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
}
|
| 117 |
workgroupBarrier();
|
| 118 |
if (lidx == 0u) {
|
| 119 |
var accM = -FLT_MAX;
|
| 120 |
var accD = 0.0;
|
| 121 |
-
for (var
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
}
|
| 126 |
combinedMD = vec2<f32>(accM, accD);
|
| 127 |
}
|
|
@@ -136,9 +123,8 @@ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
|
| 136 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 137 |
{% if mdStreamed %}
|
| 138 |
|
| 139 |
-
// In-place fold of {{ mdStreams }} streams.
|
| 140 |
-
//
|
| 141 |
-
// from slot s * WG afterwards.
|
| 142 |
fn combine_partials_streams(lidx: u32) {
|
| 143 |
workgroupBarrier();
|
| 144 |
var stride = WG / 2u;
|
|
@@ -197,8 +183,8 @@ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
|
| 197 |
{% endif %}
|
| 198 |
|
| 199 |
|
| 200 |
-
//
|
| 201 |
-
//
|
| 202 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 203 |
fn scale_value() -> f32 {
|
| 204 |
if (params.scale != 0.0) { return params.scale; }
|
|
@@ -257,11 +243,7 @@ fn main(
|
|
| 257 |
// Causal upper bound: query qi attends only keys 0..qi, so stop after the tile
|
| 258 |
// containing qi and skip the unattended tail. Non-causal keeps the full kvSeq
|
| 259 |
// sweep.
|
| 260 |
-
{% if hasCausal %}
|
| 261 |
-
var keyBoundV = select(params.kvSeq, min(params.kvSeq, qi + 1u), params.isCausal != 0u);
|
| 262 |
-
{% else %}
|
| 263 |
var keyBoundV = params.kvSeq;
|
| 264 |
-
{% endif %}
|
| 265 |
var keyFloor: u32 = 0u;
|
| 266 |
{% if hasWindow %}
|
| 267 |
// Sliding window: query qi sits at absolute position p = kvSeq - qSeq
|
|
@@ -287,7 +269,7 @@ fn main(
|
|
| 287 |
var score = -FLT_MAX;
|
| 288 |
var m = -FLT_MAX;
|
| 289 |
var dPart = 0.0;
|
| 290 |
-
var keyAllowed = kj < keyBound{% if
|
| 291 |
if (keyAllowed) {
|
| 292 |
let kRowV4 = kvBaseV4 + kj * kvTokenStrideV4;
|
| 293 |
{% if hasMask %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{% if combineSubgroups %}
|
| 2 |
enable subgroups;
|
| 3 |
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
|
| 6 |
+
// Flash-style tiled online-softmax attention for vec4-aligned head dimensions.
|
| 7 |
+
// One workgroup per
|
| 8 |
// (batch, head, query token); the workgroup sweeps the KV sequence in tiles
|
| 9 |
// of WG keys, each thread owning one key of the tile (full q·k dot in
|
| 10 |
// registers, vec4 loads), so a tile needs a single fused (max, sum-exp)
|
|
|
|
| 13 |
// per tile; the full score matrix is never materialized.
|
| 14 |
// Layout: token-major [batch, seq, heads * headDim]; Q and KV hidden strides
|
| 15 |
// are compiled constants.
|
|
|
|
|
|
|
|
|
|
| 16 |
{% if hasBias %}
|
| 17 |
// Packed [Q; K; V] bias rows. The K bias adds the same dot(q, biasK) to every
|
| 18 |
// key score, which softmax cancels, so only the Q and V biases are applied.
|
|
|
|
| 26 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 27 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
|
| 28 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
|
|
|
| 29 |
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 30 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
|
|
|
| 31 |
{% set qHeads = "params.qHeads" if headsFromParams else "Q_HEADS" %}
|
| 32 |
{% set kvHeads = "params.kvHeads" if headsFromParams else "KV_HEADS" %}
|
| 33 |
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
|
|
|
| 65 |
// calls before their shared partial storage is reused.
|
| 66 |
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 67 |
{% if combineSubgroups %}
|
| 68 |
+
// Cross-subgroup merge that assumes nothing about which invocations share a
|
| 69 |
+
// subgroup or how many subgroups there are: each subgroup's elected lane
|
| 70 |
+
// publishes the subgroup pair in the slot at its OWN invocation index and sets
|
| 71 |
+
// that index's bit in a workgroup bitmask; thread 0 then folds exactly the
|
| 72 |
+
// published slots, in ascending index order (the online (m, d) merge is not
|
| 73 |
+
// float-associative, so the order is fixed), and clears the mask for the next
|
| 74 |
+
// call as it reads it. Workgroup memory starts zeroed, so the mask needs no
|
| 75 |
+
// setup. Same three collectives as a single-subgroup reduce, two barriers.
|
| 76 |
var<workgroup> partialM: array<f32, WG>;
|
| 77 |
var<workgroup> partialD: array<f32, WG>;
|
| 78 |
+
var<workgroup> leaderMask: array<atomic<u32>, (WG + 31u) / 32u>;
|
| 79 |
var<workgroup> combinedMD: vec2<f32>;
|
| 80 |
|
| 81 |
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 82 |
+
// it (no barriers, no shared state). `subgroup_size` is the size of the current
|
| 83 |
+
// subgroup and uniform, so the test is exact and may guard the barriers below.
|
| 84 |
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 85 |
let sgM = subgroupMax(m);
|
| 86 |
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
|
|
|
| 91 |
if (sgSize == WG) {
|
| 92 |
return vec2<f32>(sgM, sgD);
|
| 93 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
if (subgroupElect()) {
|
| 95 |
+
partialM[lidx] = sgM;
|
| 96 |
+
partialD[lidx] = sgD;
|
| 97 |
+
atomicOr(&leaderMask[lidx / 32u], 1u << (lidx % 32u));
|
| 98 |
}
|
| 99 |
workgroupBarrier();
|
| 100 |
if (lidx == 0u) {
|
| 101 |
var accM = -FLT_MAX;
|
| 102 |
var accD = 0.0;
|
| 103 |
+
for (var w = 0u; w < (WG + 31u) / 32u; w = w + 1u) {
|
| 104 |
+
var bits = atomicExchange(&leaderMask[w], 0u);
|
| 105 |
+
while (bits != 0u) {
|
| 106 |
+
let slot = w * 32u + firstTrailingBit(bits);
|
| 107 |
+
bits = bits & (bits - 1u);
|
| 108 |
+
let mNew = max(accM, partialM[slot]);
|
| 109 |
+
accD = accD * exp_shift(accM, mNew) + partialD[slot] * exp_shift(partialM[slot], mNew);
|
| 110 |
+
accM = mNew;
|
| 111 |
+
}
|
| 112 |
}
|
| 113 |
combinedMD = vec2<f32>(accM, accD);
|
| 114 |
}
|
|
|
|
| 123 |
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 124 |
{% if mdStreamed %}
|
| 125 |
|
| 126 |
+
// In-place fold of {{ mdStreams }} streams. Input partials occupy
|
| 127 |
+
// partialM/partialD; stream s returns its merged pair in slot s * WG.
|
|
|
|
| 128 |
fn combine_partials_streams(lidx: u32) {
|
| 129 |
workgroupBarrier();
|
| 130 |
var stride = WG / 2u;
|
|
|
|
| 183 |
{% endif %}
|
| 184 |
|
| 185 |
|
| 186 |
+
// An explicit-zero specialization bakes the scale as 0. Otherwise,
|
| 187 |
+
// params.scale == 0 encodes an omitted scale and selects 1/sqrt(headDim).
|
| 188 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 189 |
fn scale_value() -> f32 {
|
| 190 |
if (params.scale != 0.0) { return params.scale; }
|
|
|
|
| 243 |
// Causal upper bound: query qi attends only keys 0..qi, so stop after the tile
|
| 244 |
// containing qi and skip the unattended tail. Non-causal keeps the full kvSeq
|
| 245 |
// sweep.
|
|
|
|
|
|
|
|
|
|
| 246 |
var keyBoundV = params.kvSeq;
|
|
|
|
| 247 |
var keyFloor: u32 = 0u;
|
| 248 |
{% if hasWindow %}
|
| 249 |
// Sliding window: query qi sits at absolute position p = kvSeq - qSeq
|
|
|
|
| 269 |
var score = -FLT_MAX;
|
| 270 |
var m = -FLT_MAX;
|
| 271 |
var dPart = 0.0;
|
| 272 |
+
var keyAllowed = kj < keyBound{% if hasWindow %} && kj >= keyFloor{% endif %};
|
| 273 |
if (keyAllowed) {
|
| 274 |
let kRowV4 = kvBaseV4 + kj * kvTokenStrideV4;
|
| 275 |
{% if hasMask %}
|
build/webgpu/attn-flash-prefill-cluster.wgsl.jinja
CHANGED
|
@@ -1,4 +1,6 @@
|
|
| 1 |
-
{% set sourceProfile =
|
|
|
|
|
|
|
| 2 |
{% set QSEQ = "params.seq_len" if sourceProfile == 1 else "params.qSeq" %}
|
| 3 |
{% set KVSEQ = "(params.past_len + params.seq_len)" if sourceProfile == 1 else "params.kvSeq" %}
|
| 4 |
{% set IS_CAUSAL = "1u" if sourceProfile == 1 else "params.isCausal" %}
|
|
@@ -7,6 +9,7 @@
|
|
| 7 |
{% set KEY = "cache_keys" if sourceProfile == 1 else "key" %}
|
| 8 |
{% set VALUE = "cache_values" if sourceProfile == 1 else "value" %}
|
| 9 |
{% set OUTPUT = "attn_out" if sourceProfile == 1 else "output" %}
|
|
|
|
| 10 |
{% if useSubgroups is not defined %}{% set useSubgroups = true %}{% endif %}
|
| 11 |
{% if batchNoSgReduction is not defined %}{% set batchNoSgReduction = false %}{% endif %}
|
| 12 |
{% if hasMask is not defined %}{% set hasMask = false %}{% endif %}
|
|
@@ -14,7 +17,6 @@
|
|
| 14 |
{% if maskIsBool is not defined %}{% set maskIsBool = false %}{% endif %}
|
| 15 |
{% if hasSoftcap is not defined %}{% set hasSoftcap = false %}{% endif %}
|
| 16 |
{% if hasHeadSink is not defined %}{% set hasHeadSink = false %}{% endif %}
|
| 17 |
-
{% set Q_HIDDEN = qHidden | default(0) %}
|
| 18 |
{% if quantCacheFormat is not defined %}{% set quantCacheFormat = "" %}{% endif %}
|
| 19 |
{% if useSeqlens is not defined %}{% set useSeqlens = false %}{% endif %}
|
| 20 |
// A windowed cache binds a fixed CAPACITY but keeps only the most recent
|
|
@@ -27,9 +29,6 @@
|
|
| 27 |
{% if useSubgroups %}
|
| 28 |
enable subgroups;
|
| 29 |
{% endif %}
|
| 30 |
-
{% if usesF16 %}
|
| 31 |
-
enable f16;
|
| 32 |
-
{% endif %}
|
| 33 |
{{ env.wgsl.resourceDeclarations }}
|
| 34 |
{% set ST = "f16" if usesF16 else "f32" %}
|
| 35 |
// K/V tiles are staged as f16 when this specialization uses f16, reducing
|
|
@@ -44,16 +43,18 @@ enable f16;
|
|
| 44 |
{% set MASK_ELEMENT = "maskValue" if STAGE_MASK else "attn_mask[maskIndex]" %}
|
| 45 |
{% set MASK_ADDITIVE = "maskValue" if STAGE_MASK else "f32(attn_mask[maskIndex])" %}
|
| 46 |
{% set SLICE_COUNT = ((headDimV4 / LPQ) | int) %}
|
|
|
|
|
|
|
| 47 |
{% set ROPE_LANE_XOR = ((LPQ / 2) | int) %}
|
| 48 |
-
{% set QL =
|
| 49 |
-
{% set KL =
|
| 50 |
{% set RIGHT = causalRightAlign is defined and causalRightAlign %}
|
| 51 |
|
| 52 |
// Tiled flash prefill attention with configurable-width query clusters for
|
| 53 |
// token-major [batch, seq, heads*headDim] attention ops. Each workgroup covers
|
| 54 |
// TILE_Q queries with one LPQ-lane cluster per query; K/V are staged in
|
| 55 |
-
// workgroup memory once per TILE_K tile. Within a cluster, each lane holds
|
| 56 |
-
//
|
| 57 |
// combined with subgroup shuffles when available, or shared-memory reductions
|
| 58 |
// on compatibility variants. Dot products, online softmax, and weighted-value
|
| 59 |
// sums accumulate in f32. Optional masks are broadcast-strided; the
|
|
@@ -68,7 +69,8 @@ const HEAD_DIM: u32 = {{ headDim }}u;
|
|
| 68 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 69 |
{% if QL != "bhsd" %}
|
| 70 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u; // bsh token-major hidden stride
|
| 71 |
-
{%
|
|
|
|
| 72 |
{% if KL != "bhsd" %}
|
| 73 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
| 74 |
{% endif %}
|
|
@@ -83,7 +85,12 @@ const HALF_DIM: u32 = HEAD_DIM / 2u;
|
|
| 83 |
const HALF_LPQ: u32 = LPQ / 2u;
|
| 84 |
{% endif %}
|
| 85 |
const TILE_K: u32 = {{ TILE_K }}u;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
const WG: u32 = TILE_Q * LPQ;
|
|
|
|
| 87 |
{% if MASK_IS_INT %}
|
| 88 |
// Key-keep masks in contrib attention use a finite low logit for a rejected
|
| 89 |
// key. Logical ONNX bool masks use the exclusion sentinel instead, so a fully
|
|
@@ -116,8 +123,12 @@ var<workgroup> red: array<f32, WG>;
|
|
| 116 |
|
| 117 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 118 |
fn scale_value() -> f32 {
|
|
|
|
|
|
|
|
|
|
| 119 |
if (params.scale != 0.0) { return params.scale; }
|
| 120 |
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
|
|
|
| 121 |
}
|
| 122 |
|
| 123 |
{% if quantCacheFormat %}
|
|
@@ -162,13 +173,6 @@ fn load_{{ kind }}4(indexV4: u32, d4: u32, hk: u32) -> vec4<f32> {
|
|
| 162 |
{{ emit_quant_load4(quantCacheFormat, "value", VALUE, "v_scale") }}
|
| 163 |
{% endif %}
|
| 164 |
|
| 165 |
-
{% if hasBias %}
|
| 166 |
-
fn load_bias4(base: u32, d4: u32) -> vec4<f32> {
|
| 167 |
-
let offset = base + d4 * 4u;
|
| 168 |
-
return vec4<f32>(bias[offset], bias[offset + 1u], bias[offset + 2u], bias[offset + 3u]);
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
{% endif %}
|
| 172 |
@compute @workgroup_size(WG, 1, 1)
|
| 173 |
fn main(
|
| 174 |
@builtin(workgroup_id) wg: vec3<u32>,
|
|
@@ -184,74 +188,85 @@ fn main(
|
|
| 184 |
// portability.
|
| 185 |
if (sgSize < LPQ || sgSize % LPQ != 0u) { return; }
|
| 186 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 187 |
let qSub = tid / LPQ;
|
|
|
|
| 188 |
let lane8 = tid % LPQ;
|
| 189 |
-
|
| 190 |
-
let
|
|
|
|
|
|
|
| 191 |
let hKv = h / (Q_HEADS / KV_HEADS);
|
| 192 |
let SCALE = scale_value();
|
| 193 |
|
| 194 |
-
// Per-thread q slice
|
| 195 |
-
|
|
|
|
| 196 |
{% if QL == "bhsd" %}
|
| 197 |
-
let qBase4 = ((b * Q_HEADS + h) * {{ QSEQ }} + qClamped) * HEAD_DIM_V4 + lane8 * SLICE;
|
| 198 |
{% else %}
|
| 199 |
-
let qBase4 = (b * {{ QSEQ }} + qClamped) * {{ Q_STRIDE }} + h * HEAD_DIM_V4 + lane8 * SLICE;
|
| 200 |
{% endif %}
|
| 201 |
{% for c in range(SLICE_COUNT) %}
|
| 202 |
-
var
|
| 203 |
-
{
|
| 204 |
-
|
| 205 |
-
{% endif %}
|
| 206 |
-
var o{{ c }} = vec4<f32>(0.0);
|
| 207 |
{% endfor %}
|
| 208 |
{% if FUSED_ROTARY %}
|
| 209 |
-
// The present-cache keys are already rotary transformed. Transform Q once
|
| 210 |
-
//
|
| 211 |
-
//
|
| 212 |
-
// the paired register directly, while the portable path reloads that vec4.
|
| 213 |
{% if not useSubgroups %}
|
| 214 |
let qHeadBase4 = qBase4 - lane8 * SLICE;
|
| 215 |
let pairedLane = (lane8 + HALF_LPQ) % LPQ;
|
| 216 |
{% endif %}
|
| 217 |
let ropeChunk = (lane8 % HALF_LPQ) * SLICE;
|
| 218 |
let pastLenForRope = select(0u, {{ KVSEQ }} - {{ QSEQ }}, {{ KVSEQ }} >= {{ QSEQ }});
|
| 219 |
-
|
|
|
|
| 220 |
{% for c in range(SLICE_COUNT) %}
|
| 221 |
{% if useSubgroups %}
|
| 222 |
-
let
|
| 223 |
{% else %}
|
| 224 |
-
let
|
| 225 |
-
{% endif %}
|
| 226 |
-
let
|
| 227 |
-
let
|
| 228 |
-
f32(cos_cache[
|
| 229 |
-
f32(cos_cache[
|
| 230 |
-
f32(cos_cache[
|
| 231 |
-
f32(cos_cache[
|
| 232 |
);
|
| 233 |
-
let
|
| 234 |
-
f32(sin_cache[
|
| 235 |
-
f32(sin_cache[
|
| 236 |
-
f32(sin_cache[
|
| 237 |
-
f32(sin_cache[
|
| 238 |
);
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
lane8 < HALF_LPQ
|
| 243 |
);
|
| 244 |
{% endfor %}
|
|
|
|
| 245 |
{% endif %}
|
| 246 |
-
|
| 247 |
-
var
|
|
|
|
|
|
|
| 248 |
{% if useSeqlens %}
|
| 249 |
|
| 250 |
// Resident rows of a windowed cache: the survivors were shifted down to [0, kvActive),
|
| 251 |
// so query/key DISTANCE is unchanged and every bound below reads as if the cache were
|
| 252 |
// exactly kvActive long. Rotary is excluded from this path (it would need the absolute
|
| 253 |
// position, not the cache-relative one), so pastLenForRope keeps the physical length.
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
| 255 |
{% endif %}
|
| 256 |
// Causal ceiling per query; the key loop runs over the workgroup's union range
|
| 257 |
// (uniform trip count), masking out-of-range (query, key) pairs.
|
|
@@ -267,16 +282,18 @@ fn main(
|
|
| 267 |
{% if RIGHT or hasWindow %}
|
| 268 |
let pastLen = select(0u, {{ KVA }} - {{ QSEQ }}, {{ KVA }} >= {{ QSEQ }});
|
| 269 |
{% endif %}
|
|
|
|
| 270 |
{% if hasCausal %}
|
| 271 |
{% if RIGHT %}
|
| 272 |
-
var maxKjV = select({{ KVA }}, min(pastLen + qIdx + 1u, {{ KVA }}), {{ IS_CAUSAL }} != 0u);
|
| 273 |
{% else %}
|
| 274 |
-
var maxKjV = select({{ KVA }}, min(qIdx + 1u, {{ KVA }}), {{ IS_CAUSAL }} != 0u);
|
| 275 |
{% endif %}
|
| 276 |
{% else %}
|
| 277 |
-
var maxKjV = {{ KVA }};
|
| 278 |
{% endif %}
|
| 279 |
-
var minKjV: u32 = 0u;
|
|
|
|
| 280 |
let lastQ = min(wg.x * TILE_Q + TILE_Q - 1u, {{ QSEQ }} - 1u);
|
| 281 |
{% if hasCausal %}
|
| 282 |
{% if RIGHT %}
|
|
@@ -289,16 +306,20 @@ fn main(
|
|
| 289 |
{% endif %}
|
| 290 |
var wgStartV: u32 = 0u;
|
| 291 |
{% if hasWindow %}
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
|
|
|
|
|
|
| 295 |
let lastQAbsP1 = pastLen + lastQ + 1u;
|
| 296 |
wgEndV = min(wgEndV, lastQAbsP1);
|
| 297 |
let firstQAbsP1 = pastLen + wg.x * TILE_Q + 1u;
|
| 298 |
if (firstQAbsP1 > params.windowSize) { wgStartV = firstQAbsP1 - params.windowSize; }
|
| 299 |
{% endif %}
|
| 300 |
-
|
| 301 |
-
let
|
|
|
|
|
|
|
| 302 |
let wgEnd = wgEndV;
|
| 303 |
let wgStart = wgStartV;
|
| 304 |
{% else %}
|
|
@@ -306,6 +327,10 @@ fn main(
|
|
| 306 |
let minKj: u32 = 0u;
|
| 307 |
let wgEnd = {{ KVA }};
|
| 308 |
let wgStart: u32 = 0u;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
{% endif %}
|
| 310 |
{% if KL == "bhsd" %}
|
| 311 |
{% if quantCacheFormat == "int4" %}
|
|
@@ -322,7 +347,7 @@ fn main(
|
|
| 322 |
loop {
|
| 323 |
if (kStart >= wgEnd) { break; }
|
| 324 |
|
| 325 |
-
// Cooperative K/V tile load
|
| 326 |
workgroupBarrier();
|
| 327 |
for (var i: u32 = tid; i < TILE_K * HEAD_DIM_V4; i = i + WG) {
|
| 328 |
let slot = i / HEAD_DIM_V4;
|
|
@@ -362,8 +387,7 @@ fn main(
|
|
| 362 |
}
|
| 363 |
{% endif %}
|
| 364 |
workgroupBarrier();
|
| 365 |
-
// TILE_K
|
| 366 |
-
// multiplying source size, while the q/o register slices remain named.
|
| 367 |
{% if not useSubgroups and batchNoSgReduction %}
|
| 368 |
// First publish every key's partial dot without intervening barriers.
|
| 369 |
var s: array<f32, TILE_K>;
|
|
@@ -418,6 +442,67 @@ fn main(
|
|
| 418 |
{% endif %}
|
| 419 |
}
|
| 420 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
{% else %}
|
| 422 |
var s: array<f32, TILE_K>;
|
| 423 |
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
|
@@ -480,30 +565,55 @@ fn main(
|
|
| 480 |
|
| 481 |
// Per-thread online softmax over the tile. s[kk] is reused to hold the
|
| 482 |
// exponentiated probabilities for the PV accumulation below.
|
| 483 |
-
|
|
|
|
| 484 |
for (var kk: u32 = 1u; kk < TILE_K; kk = kk + 1u) {
|
| 485 |
-
tileMax = max(tileMax, s[kk]);
|
| 486 |
}
|
| 487 |
-
let newMax = max(m, tileMax);
|
| 488 |
-
let corr = select(exp(m - newMax), 0.0, m == NEG_INF);
|
| 489 |
-
var pSum: f32 = 0.0;
|
| 490 |
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 491 |
-
let pk = select(0.0, exp(s[kk] - newMax), s[kk] != NEG_INF);
|
| 492 |
-
s[kk] = pk;
|
| 493 |
-
pSum = pSum + pk;
|
| 494 |
}
|
| 495 |
-
l = l * corr + pSum;
|
| 496 |
-
m = newMax;
|
| 497 |
-
|
| 498 |
-
//
|
| 499 |
-
//
|
| 500 |
-
//
|
| 501 |
-
// tiles
|
| 502 |
-
//
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
let tileInterior = kStart >= minKj && kStart + TILE_K <= maxKj;
|
| 508 |
{% for c in range(SLICE_COUNT) %}
|
| 509 |
{
|
|
@@ -520,35 +630,38 @@ fn main(
|
|
| 520 |
o{{ c }} = acc;
|
| 521 |
}
|
| 522 |
{% endfor %}
|
|
|
|
| 523 |
|
| 524 |
kStart = kStart + TILE_K;
|
| 525 |
}
|
| 526 |
|
| 527 |
-
|
|
|
|
|
|
|
| 528 |
{% if QL == "bhsd" %}
|
| 529 |
-
let outBase4 = ((b * Q_HEADS + h) * {{ QSEQ }} + qIdx) * HEAD_DIM_V4 + lane8 * SLICE;
|
| 530 |
{% else %}
|
| 531 |
-
let outBase4 = (b * {{ QSEQ }} + qIdx) * Q_HIDDEN_V4 + h * HEAD_DIM_V4 + lane8 * SLICE;
|
| 532 |
{% endif %}
|
| 533 |
{% if hasHeadSink %}
|
| 534 |
// The head sink is a learned logit that competes with the keys but carries
|
| 535 |
// no value, so it enters the denominator only and the weighted sum above is
|
| 536 |
-
// untouched. Renormalizing against max(m, sink) keeps the exponentials in
|
| 537 |
// range when the sink dominates a fully-masked row.
|
| 538 |
let sink = f32(head_sink[h]);
|
| 539 |
-
let finalM = max(m, sink);
|
| 540 |
-
let accScale = exp(m - finalM);
|
| 541 |
-
let inv = accScale / (exp(sink - finalM) + l * accScale);
|
| 542 |
{% else %}
|
| 543 |
-
// l == 0 means this query had no probability-bearing key: either its
|
| 544 |
// causal/window range is empty or its logical bool mask rejects every key.
|
| 545 |
// Emit 0 rather than 0/0. Other attention paths enforce the same empty-row
|
| 546 |
// contract by selecting on positive global mass.
|
| 547 |
-
let inv = select(0.0, 1.0 / l, l > 0.0);
|
| 548 |
{% endif %}
|
| 549 |
-
{% macro attention_value(c) %}o{{ c }} * inv{% if hasBias %} + load_bias4(2u * {{ Q_HIDDEN }}u + h * HEAD_DIM, lane8 * SLICE + {{ c }}u){% endif %}{% endmacro %}
|
| 550 |
{% for c in range(SLICE_COUNT) %}
|
| 551 |
-
{{ OUTPUT }}[outBase4 + {{ c }}u] = vec4<{{ scalar }}>({{ attention_value(c) }});
|
| 552 |
{% endfor %}
|
| 553 |
}
|
|
|
|
| 554 |
}
|
|
|
|
| 1 |
+
{% set sourceProfile = sourceProfile if sourceProfile is defined else 0 %}
|
| 2 |
+
{% set scaling = scaling | default("0.0") %}
|
| 3 |
+
{% set qkvStrideV4 = qkvStrideV4 | default(0) %}
|
| 4 |
{% set QSEQ = "params.seq_len" if sourceProfile == 1 else "params.qSeq" %}
|
| 5 |
{% set KVSEQ = "(params.past_len + params.seq_len)" if sourceProfile == 1 else "params.kvSeq" %}
|
| 6 |
{% set IS_CAUSAL = "1u" if sourceProfile == 1 else "params.isCausal" %}
|
|
|
|
| 9 |
{% set KEY = "cache_keys" if sourceProfile == 1 else "key" %}
|
| 10 |
{% set VALUE = "cache_values" if sourceProfile == 1 else "value" %}
|
| 11 |
{% set OUTPUT = "attn_out" if sourceProfile == 1 else "output" %}
|
| 12 |
+
{% if sourceProfile == 1 %}{% set ATTN_SCALE_OVERRIDE = scaling %}{% endif %}
|
| 13 |
{% if useSubgroups is not defined %}{% set useSubgroups = true %}{% endif %}
|
| 14 |
{% if batchNoSgReduction is not defined %}{% set batchNoSgReduction = false %}{% endif %}
|
| 15 |
{% if hasMask is not defined %}{% set hasMask = false %}{% endif %}
|
|
|
|
| 17 |
{% if maskIsBool is not defined %}{% set maskIsBool = false %}{% endif %}
|
| 18 |
{% if hasSoftcap is not defined %}{% set hasSoftcap = false %}{% endif %}
|
| 19 |
{% if hasHeadSink is not defined %}{% set hasHeadSink = false %}{% endif %}
|
|
|
|
| 20 |
{% if quantCacheFormat is not defined %}{% set quantCacheFormat = "" %}{% endif %}
|
| 21 |
{% if useSeqlens is not defined %}{% set useSeqlens = false %}{% endif %}
|
| 22 |
// A windowed cache binds a fixed CAPACITY but keeps only the most recent
|
|
|
|
| 29 |
{% if useSubgroups %}
|
| 30 |
enable subgroups;
|
| 31 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 32 |
{{ env.wgsl.resourceDeclarations }}
|
| 33 |
{% set ST = "f16" if usesF16 else "f32" %}
|
| 34 |
// K/V tiles are staged as f16 when this specialization uses f16, reducing
|
|
|
|
| 43 |
{% set MASK_ELEMENT = "maskValue" if STAGE_MASK else "attn_mask[maskIndex]" %}
|
| 44 |
{% set MASK_ADDITIVE = "maskValue" if STAGE_MASK else "f32(attn_mask[maskIndex])" %}
|
| 45 |
{% set SLICE_COUNT = ((headDimV4 / LPQ) | int) %}
|
| 46 |
+
{% set QPL = (2 if (not usesF16 and SLICE_COUNT <= 4 and TILE_K <= 16 and TILE_Q % 2 == 0) else 1) if useSubgroups else 1 %}
|
| 47 |
+
{% macro qn(name, qi) %}{{ name }}{% if qi > 0 %}_q{{ qi }}{% endif %}{% endmacro %}
|
| 48 |
{% set ROPE_LANE_XOR = ((LPQ / 2) | int) %}
|
| 49 |
+
{% set QL = qLayout if qLayout is defined else layout %}
|
| 50 |
+
{% set KL = kvLayout if kvLayout is defined else layout %}
|
| 51 |
{% set RIGHT = causalRightAlign is defined and causalRightAlign %}
|
| 52 |
|
| 53 |
// Tiled flash prefill attention with configurable-width query clusters for
|
| 54 |
// token-major [batch, seq, heads*headDim] attention ops. Each workgroup covers
|
| 55 |
// TILE_Q queries with one LPQ-lane cluster per query; K/V are staged in
|
| 56 |
+
// workgroup memory once per TILE_K tile. Within a cluster, each lane holds named
|
| 57 |
+
// q/o slices of HEAD_DIM/LPQ dimensions. Per-lane dot fragments are
|
| 58 |
// combined with subgroup shuffles when available, or shared-memory reductions
|
| 59 |
// on compatibility variants. Dot products, online softmax, and weighted-value
|
| 60 |
// sums accumulate in f32. Optional masks are broadcast-strided; the
|
|
|
|
| 69 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 70 |
{% if QL != "bhsd" %}
|
| 71 |
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u; // bsh token-major hidden stride
|
| 72 |
+
{% if sourceProfile == 1 %}const QKV_STRIDE_V4: u32 = {{ qkvStrideV4 }}u; // packed [Q; K; V] input row stride
|
| 73 |
+
{% endif %}{% endif %}
|
| 74 |
{% if KL != "bhsd" %}
|
| 75 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
| 76 |
{% endif %}
|
|
|
|
| 85 |
const HALF_LPQ: u32 = LPQ / 2u;
|
| 86 |
{% endif %}
|
| 87 |
const TILE_K: u32 = {{ TILE_K }}u;
|
| 88 |
+
{% if QPL > 1 %}
|
| 89 |
+
const QPL: u32 = {{ QPL }}u;
|
| 90 |
+
const WG: u32 = (TILE_Q / QPL) * LPQ;
|
| 91 |
+
{% else %}
|
| 92 |
const WG: u32 = TILE_Q * LPQ;
|
| 93 |
+
{% endif %}
|
| 94 |
{% if MASK_IS_INT %}
|
| 95 |
// Key-keep masks in contrib attention use a finite low logit for a rejected
|
| 96 |
// key. Logical ONNX bool masks use the exclusion sentinel instead, so a fully
|
|
|
|
| 123 |
|
| 124 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 125 |
fn scale_value() -> f32 {
|
| 126 |
+
{% if ATTN_SCALE_OVERRIDE is defined %}
|
| 127 |
+
return {{ ATTN_SCALE_OVERRIDE }};
|
| 128 |
+
{% else %}
|
| 129 |
if (params.scale != 0.0) { return params.scale; }
|
| 130 |
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
| 131 |
+
{% endif %}
|
| 132 |
}
|
| 133 |
|
| 134 |
{% if quantCacheFormat %}
|
|
|
|
| 173 |
{{ emit_quant_load4(quantCacheFormat, "value", VALUE, "v_scale") }}
|
| 174 |
{% endif %}
|
| 175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
@compute @workgroup_size(WG, 1, 1)
|
| 177 |
fn main(
|
| 178 |
@builtin(workgroup_id) wg: vec3<u32>,
|
|
|
|
| 188 |
// portability.
|
| 189 |
if (sgSize < LPQ || sgSize % LPQ != 0u) { return; }
|
| 190 |
{% endif %}
|
| 191 |
+
{% if QPL > 1 %}
|
| 192 |
+
let qSub = (tid / LPQ) * QPL;
|
| 193 |
+
{% else %}
|
| 194 |
let qSub = tid / LPQ;
|
| 195 |
+
{% endif %}
|
| 196 |
let lane8 = tid % LPQ;
|
| 197 |
+
{% for qi in range(QPL) %}
|
| 198 |
+
let {{ qn("qIdx", qi) }} = wg.x * TILE_Q + qSub{% if qi > 0 %} + {{ qi }}u{% endif %};
|
| 199 |
+
let {{ qn("qValid", qi) }} = {{ qn("qIdx", qi) }} < {{ QSEQ }} && h < Q_HEADS;
|
| 200 |
+
{% endfor %}
|
| 201 |
let hKv = h / (Q_HEADS / KV_HEADS);
|
| 202 |
let SCALE = scale_value();
|
| 203 |
|
| 204 |
+
// Per-thread q slice and output accumulator, each stored in SLICE named vec4 values.
|
| 205 |
+
{% for qi in range(QPL) %}
|
| 206 |
+
let {{ qn("qClamped", qi) }} = min({{ qn("qIdx", qi) }}, {{ QSEQ }} - 1u);
|
| 207 |
{% if QL == "bhsd" %}
|
| 208 |
+
let {{ qn("qBase4", qi) }} = ((b * Q_HEADS + h) * {{ QSEQ }} + {{ qn("qClamped", qi) }}) * HEAD_DIM_V4 + lane8 * SLICE;
|
| 209 |
{% else %}
|
| 210 |
+
let {{ qn("qBase4", qi) }} = (b * {{ QSEQ }} + {{ qn("qClamped", qi) }}) * {{ Q_STRIDE }} + h * HEAD_DIM_V4 + lane8 * SLICE;
|
| 211 |
{% endif %}
|
| 212 |
{% for c in range(SLICE_COUNT) %}
|
| 213 |
+
var {{ qn("qr" ~ c, qi) }} = vec4<f32>({{ QUERY }}[{{ qn("qBase4", qi) }} + {{ c }}u]);
|
| 214 |
+
var {{ qn("o" ~ c, qi) }} = vec4<f32>(0.0);
|
| 215 |
+
{% endfor %}
|
|
|
|
|
|
|
| 216 |
{% endfor %}
|
| 217 |
{% if FUSED_ROTARY %}
|
| 218 |
+
// The present-cache keys are already rotary transformed. Transform Q once
|
| 219 |
+
// before the K loop, avoiding a separate full-size Q scratch pass. NeoX
|
| 220 |
+
// half-split pairs lanes separated by LPQ/2.
|
|
|
|
| 221 |
{% if not useSubgroups %}
|
| 222 |
let qHeadBase4 = qBase4 - lane8 * SLICE;
|
| 223 |
let pairedLane = (lane8 + HALF_LPQ) % LPQ;
|
| 224 |
{% endif %}
|
| 225 |
let ropeChunk = (lane8 % HALF_LPQ) * SLICE;
|
| 226 |
let pastLenForRope = select(0u, {{ KVSEQ }} - {{ QSEQ }}, {{ KVSEQ }} >= {{ QSEQ }});
|
| 227 |
+
{% for qi in range(QPL) %}
|
| 228 |
+
let {{ qn("ropePosition", qi) }} = pastLenForRope + {{ qn("qClamped", qi) }};
|
| 229 |
{% for c in range(SLICE_COUNT) %}
|
| 230 |
{% if useSubgroups %}
|
| 231 |
+
let {{ qn("pairedQ" ~ c, qi) }} = subgroupShuffleXor({{ qn("qr" ~ c, qi) }}, {{ ROPE_LANE_XOR }}u);
|
| 232 |
{% else %}
|
| 233 |
+
let {{ qn("pairedQ" ~ c, qi) }} = vec4<f32>({{ QUERY }}[qHeadBase4 + pairedLane * SLICE + {{ c }}u]);
|
| 234 |
+
{% endif %}
|
| 235 |
+
let {{ qn("ropeBase" ~ c, qi) }} = {{ qn("ropePosition", qi) }} * HALF_DIM + (ropeChunk + {{ c }}u) * 4u;
|
| 236 |
+
let {{ qn("ropeCos" ~ c, qi) }} = vec4<f32>(
|
| 237 |
+
f32(cos_cache[{{ qn("ropeBase" ~ c, qi) }}]),
|
| 238 |
+
f32(cos_cache[{{ qn("ropeBase" ~ c, qi) }} + 1u]),
|
| 239 |
+
f32(cos_cache[{{ qn("ropeBase" ~ c, qi) }} + 2u]),
|
| 240 |
+
f32(cos_cache[{{ qn("ropeBase" ~ c, qi) }} + 3u])
|
| 241 |
);
|
| 242 |
+
let {{ qn("ropeSin" ~ c, qi) }} = vec4<f32>(
|
| 243 |
+
f32(sin_cache[{{ qn("ropeBase" ~ c, qi) }}]),
|
| 244 |
+
f32(sin_cache[{{ qn("ropeBase" ~ c, qi) }} + 1u]),
|
| 245 |
+
f32(sin_cache[{{ qn("ropeBase" ~ c, qi) }} + 2u]),
|
| 246 |
+
f32(sin_cache[{{ qn("ropeBase" ~ c, qi) }} + 3u])
|
| 247 |
);
|
| 248 |
+
{{ qn("qr" ~ c, qi) }} = select(
|
| 249 |
+
{{ qn("qr" ~ c, qi) }} * {{ qn("ropeCos" ~ c, qi) }} + {{ qn("pairedQ" ~ c, qi) }} * {{ qn("ropeSin" ~ c, qi) }},
|
| 250 |
+
{{ qn("qr" ~ c, qi) }} * {{ qn("ropeCos" ~ c, qi) }} - {{ qn("pairedQ" ~ c, qi) }} * {{ qn("ropeSin" ~ c, qi) }},
|
| 251 |
lane8 < HALF_LPQ
|
| 252 |
);
|
| 253 |
{% endfor %}
|
| 254 |
+
{% endfor %}
|
| 255 |
{% endif %}
|
| 256 |
+
{% for qi in range(QPL) %}
|
| 257 |
+
var {{ qn("m", qi) }}: f32 = NEG_INF;
|
| 258 |
+
var {{ qn("l", qi) }}: f32 = 0.0;
|
| 259 |
+
{% endfor %}
|
| 260 |
{% if useSeqlens %}
|
| 261 |
|
| 262 |
// Resident rows of a windowed cache: the survivors were shifted down to [0, kvActive),
|
| 263 |
// so query/key DISTANCE is unchanged and every bound below reads as if the cache were
|
| 264 |
// exactly kvActive long. Rotary is excluded from this path (it would need the absolute
|
| 265 |
// position, not the cache-relative one), so pastLenForRope keeps the physical length.
|
| 266 |
+
// Clamping below by the query count matches the cache-update passes and the scalar
|
| 267 |
+
// path: a right-padded batch (seqlens_k[b]+1 < qSeq) still appends its whole chunk,
|
| 268 |
+
// so its queries score against all of it.
|
| 269 |
+
let kvActive = min({{ KVSEQ }}, max({{ QSEQ }}, u32(seqlens_k[b]) + 1u));
|
| 270 |
{% endif %}
|
| 271 |
// Causal ceiling per query; the key loop runs over the workgroup's union range
|
| 272 |
// (uniform trip count), masking out-of-range (query, key) pairs.
|
|
|
|
| 282 |
{% if RIGHT or hasWindow %}
|
| 283 |
let pastLen = select(0u, {{ KVA }} - {{ QSEQ }}, {{ KVA }} >= {{ QSEQ }});
|
| 284 |
{% endif %}
|
| 285 |
+
{% for qi in range(QPL) %}
|
| 286 |
{% if hasCausal %}
|
| 287 |
{% if RIGHT %}
|
| 288 |
+
var {{ qn("maxKjV", qi) }} = select({{ KVA }}, min(pastLen + {{ qn("qIdx", qi) }} + 1u, {{ KVA }}), {{ IS_CAUSAL }} != 0u);
|
| 289 |
{% else %}
|
| 290 |
+
var {{ qn("maxKjV", qi) }} = select({{ KVA }}, min({{ qn("qIdx", qi) }} + 1u, {{ KVA }}), {{ IS_CAUSAL }} != 0u);
|
| 291 |
{% endif %}
|
| 292 |
{% else %}
|
| 293 |
+
var {{ qn("maxKjV", qi) }} = {{ KVA }};
|
| 294 |
{% endif %}
|
| 295 |
+
var {{ qn("minKjV", qi) }}: u32 = 0u;
|
| 296 |
+
{% endfor %}
|
| 297 |
let lastQ = min(wg.x * TILE_Q + TILE_Q - 1u, {{ QSEQ }} - 1u);
|
| 298 |
{% if hasCausal %}
|
| 299 |
{% if RIGHT %}
|
|
|
|
| 306 |
{% endif %}
|
| 307 |
var wgStartV: u32 = 0u;
|
| 308 |
{% if hasWindow %}
|
| 309 |
+
{% for qi in range(QPL) %}
|
| 310 |
+
let {{ qn("qAbsP1", qi) }} = pastLen + {{ qn("qIdx", qi) }} + 1u;
|
| 311 |
+
{{ qn("maxKjV", qi) }} = min({{ qn("maxKjV", qi) }}, {{ qn("qAbsP1", qi) }});
|
| 312 |
+
if ({{ qn("qAbsP1", qi) }} > params.windowSize) { {{ qn("minKjV", qi) }} = {{ qn("qAbsP1", qi) }} - params.windowSize; }
|
| 313 |
+
{% endfor %}
|
| 314 |
let lastQAbsP1 = pastLen + lastQ + 1u;
|
| 315 |
wgEndV = min(wgEndV, lastQAbsP1);
|
| 316 |
let firstQAbsP1 = pastLen + wg.x * TILE_Q + 1u;
|
| 317 |
if (firstQAbsP1 > params.windowSize) { wgStartV = firstQAbsP1 - params.windowSize; }
|
| 318 |
{% endif %}
|
| 319 |
+
{% for qi in range(QPL) %}
|
| 320 |
+
let {{ qn("maxKj", qi) }} = {{ qn("maxKjV", qi) }};
|
| 321 |
+
let {{ qn("minKj", qi) }} = {{ qn("minKjV", qi) }};
|
| 322 |
+
{% endfor %}
|
| 323 |
let wgEnd = wgEndV;
|
| 324 |
let wgStart = wgStartV;
|
| 325 |
{% else %}
|
|
|
|
| 327 |
let minKj: u32 = 0u;
|
| 328 |
let wgEnd = {{ KVA }};
|
| 329 |
let wgStart: u32 = 0u;
|
| 330 |
+
{% for qi in range(1, QPL) %}
|
| 331 |
+
let {{ qn("maxKj", qi) }} = maxKj;
|
| 332 |
+
let {{ qn("minKj", qi) }} = minKj;
|
| 333 |
+
{% endfor %}
|
| 334 |
{% endif %}
|
| 335 |
{% if KL == "bhsd" %}
|
| 336 |
{% if quantCacheFormat == "int4" %}
|
|
|
|
| 347 |
loop {
|
| 348 |
if (kStart >= wgEnd) { break; }
|
| 349 |
|
| 350 |
+
// Cooperative K/V tile load with out-of-bounds keys zero-filled.
|
| 351 |
workgroupBarrier();
|
| 352 |
for (var i: u32 = tid; i < TILE_K * HEAD_DIM_V4; i = i + WG) {
|
| 353 |
let slot = i / HEAD_DIM_V4;
|
|
|
|
| 387 |
}
|
| 388 |
{% endif %}
|
| 389 |
workgroupBarrier();
|
| 390 |
+
// TILE_K is a small shader constant; the loop updates the named q/o slices in place.
|
|
|
|
| 391 |
{% if not useSubgroups and batchNoSgReduction %}
|
| 392 |
// First publish every key's partial dot without intervening barriers.
|
| 393 |
var s: array<f32, TILE_K>;
|
|
|
|
| 442 |
{% endif %}
|
| 443 |
}
|
| 444 |
}
|
| 445 |
+
{% elif QPL > 1 %}
|
| 446 |
+
{% for qi in range(QPL) %}
|
| 447 |
+
var {{ qn("s", qi) }}: array<f32, TILE_K>;
|
| 448 |
+
{% endfor %}
|
| 449 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 450 |
+
let kj = kStart + kk;
|
| 451 |
+
{% for qi in range(QPL) %}
|
| 452 |
+
{{ qn("s", qi) }}[kk] = NEG_INF;
|
| 453 |
+
var {{ qn("part", qi) }}: f32 = 0.0;
|
| 454 |
+
{% endfor %}
|
| 455 |
+
let kb = kk * HEAD_DIM_V4 + lane8 * SLICE;
|
| 456 |
+
{% for c in range(SLICE_COUNT) %}
|
| 457 |
+
let kw{{ c }} = vec4<f32>(k_tile[kb + {{ c }}u]);
|
| 458 |
+
{% for qi in range(QPL) %}
|
| 459 |
+
{{ qn("part", qi) }} = {{ qn("part", qi) }} + dot({{ qn("qr" ~ c, qi) }}, kw{{ c }});
|
| 460 |
+
{% endfor %}
|
| 461 |
+
{% endfor %}
|
| 462 |
+
// One butterfly carries every query's partial: the shuffles cost the same
|
| 463 |
+
// whether the payload is one score or QPL of them.
|
| 464 |
+
var partv = vec{{ QPL }}<f32>({% for qi in range(QPL) %}{{ qn("part", qi) }}{% if not loop.last %}, {% endif %}{% endfor %});
|
| 465 |
+
partv = partv + subgroupShuffleXor(partv, 1u);
|
| 466 |
+
{% for shift in [2, 4] %}
|
| 467 |
+
{% if LPQ > shift %}
|
| 468 |
+
partv = partv + subgroupShuffleXor(partv, {{ shift }}u);
|
| 469 |
+
{% endif %}
|
| 470 |
+
{% endfor %}
|
| 471 |
+
{% for qi in range(QPL) %}
|
| 472 |
+
{
|
| 473 |
+
let sc = partv[{{ qi }}];
|
| 474 |
+
if (kj >= {{ qn("minKj", qi) }} && kj < {{ qn("maxKj", qi) }}) {
|
| 475 |
+
{% if hasMask %}
|
| 476 |
+
{% if STAGE_MASK %}
|
| 477 |
+
let maskValue = mask_tile[(qSub + {{ qi }}u) * TILE_K + kk];
|
| 478 |
+
{% else %}
|
| 479 |
+
// Broadcast-strided mask address (a 0 stride collapses that axis; rank-2
|
| 480 |
+
// [q, k] masks set batch/head strides to 0). {{ qn("qClamped", qi) }} keeps the seq index
|
| 481 |
+
// in-bounds for padding queries in the last tile (their output is dropped).
|
| 482 |
+
let maskIndex = b * params.maskBatchStride + h * params.maskHeadStride + {{ qn("qClamped", qi) }} * params.maskSeqStride + kj;
|
| 483 |
+
{% endif %}
|
| 484 |
+
{% if maskIsKeyKeep %}
|
| 485 |
+
// A broadcast key mask uses 1 for a retained key and 0 for padding.
|
| 486 |
+
{{ qn("s", qi) }}[kk] = {{ score_expr("sc") }} + (1.0 - f32({{ MASK_ELEMENT }})) * MASK_NEG;
|
| 487 |
+
{% elif maskIsBool %}
|
| 488 |
+
// Logical bool: a rejected key contributes no softmax mass. Leaving
|
| 489 |
+
// the initialized NEG_INF sentinel in place makes a fully masked row
|
| 490 |
+
// land on the zero-denominator output guard below.
|
| 491 |
+
if ({{ MASK_ELEMENT }} != 0u) {
|
| 492 |
+
{{ qn("s", qi) }}[kk] = {{ score_expr("sc") }};
|
| 493 |
+
} else {
|
| 494 |
+
{{ qn("s", qi) }}[kk] = MASK_NEG;
|
| 495 |
+
}
|
| 496 |
+
{% else %}
|
| 497 |
+
{{ qn("s", qi) }}[kk] = {{ score_expr("sc") }} + {{ MASK_ADDITIVE }};
|
| 498 |
+
{% endif %}
|
| 499 |
+
{% else %}
|
| 500 |
+
{{ qn("s", qi) }}[kk] = {{ score_expr("sc") }};
|
| 501 |
+
{% endif %}
|
| 502 |
+
}
|
| 503 |
+
}
|
| 504 |
+
{% endfor %}
|
| 505 |
+
}
|
| 506 |
{% else %}
|
| 507 |
var s: array<f32, TILE_K>;
|
| 508 |
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
|
|
|
| 565 |
|
| 566 |
// Per-thread online softmax over the tile. s[kk] is reused to hold the
|
| 567 |
// exponentiated probabilities for the PV accumulation below.
|
| 568 |
+
{% for qi in range(QPL) %}
|
| 569 |
+
var {{ qn("tileMax", qi) }}: f32 = {{ qn("s", qi) }}[0];
|
| 570 |
for (var kk: u32 = 1u; kk < TILE_K; kk = kk + 1u) {
|
| 571 |
+
{{ qn("tileMax", qi) }} = max({{ qn("tileMax", qi) }}, {{ qn("s", qi) }}[kk]);
|
| 572 |
}
|
| 573 |
+
let {{ qn("newMax", qi) }} = max({{ qn("m", qi) }}, {{ qn("tileMax", qi) }});
|
| 574 |
+
let {{ qn("corr", qi) }} = select(exp({{ qn("m", qi) }} - {{ qn("newMax", qi) }}), 0.0, {{ qn("m", qi) }} == NEG_INF);
|
| 575 |
+
var {{ qn("pSum", qi) }}: f32 = 0.0;
|
| 576 |
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 577 |
+
let pk = select(0.0, exp({{ qn("s", qi) }}[kk] - {{ qn("newMax", qi) }}), {{ qn("s", qi) }}[kk] != NEG_INF);
|
| 578 |
+
{{ qn("s", qi) }}[kk] = pk;
|
| 579 |
+
{{ qn("pSum", qi) }} = {{ qn("pSum", qi) }} + pk;
|
| 580 |
}
|
| 581 |
+
{{ qn("l", qi) }} = {{ qn("l", qi) }} * {{ qn("corr", qi) }} + {{ qn("pSum", qi) }};
|
| 582 |
+
{{ qn("m", qi) }} = {{ qn("newMax", qi) }};
|
| 583 |
+
{% endfor %}
|
| 584 |
+
// A boundary tile can address V rows outside a query's attended range, and
|
| 585 |
+
// a dynamic-row producer may leave those rows unwritten. Because 0 * NaN is
|
| 586 |
+
// NaN, the guarded loop selects the V operand away for range-excluded keys.
|
| 587 |
+
// Interior tiles use the unguarded FMA chain. Mask exclusion applies to
|
| 588 |
+
// materialized V rows and does not require this range guard.
|
| 589 |
+
{% if QPL > 1 %}
|
| 590 |
+
let tileInterior = {% for qi in range(QPL) %}(kStart >= {{ qn("minKj", qi) }} && kStart + TILE_K <= {{ qn("maxKj", qi) }}){% if not loop.last %} && {% endif %}{% endfor %};
|
| 591 |
+
{% for c in range(SLICE_COUNT) %}
|
| 592 |
+
{
|
| 593 |
+
{% for qi in range(QPL) %}
|
| 594 |
+
var {{ qn("acc", qi) }} = {{ qn("o" ~ c, qi) }} * {{ qn("corr", qi) }};
|
| 595 |
+
{% endfor %}
|
| 596 |
+
if (tileInterior) {
|
| 597 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 598 |
+
let vw = vec4<f32>(v_tile[kk * HEAD_DIM_V4 + lane8 * SLICE + {{ c }}u]);
|
| 599 |
+
{% for qi in range(QPL) %}
|
| 600 |
+
{{ qn("acc", qi) }} = {{ qn("acc", qi) }} + {{ qn("s", qi) }}[kk] * vw;
|
| 601 |
+
{% endfor %}
|
| 602 |
+
}
|
| 603 |
+
} else {
|
| 604 |
+
for (var kk: u32 = 0u; kk < TILE_K; kk = kk + 1u) {
|
| 605 |
+
let vw = vec4<f32>(v_tile[kk * HEAD_DIM_V4 + lane8 * SLICE + {{ c }}u]);
|
| 606 |
+
{% for qi in range(QPL) %}
|
| 607 |
+
{{ qn("acc", qi) }} = {{ qn("acc", qi) }} + {{ qn("s", qi) }}[kk] * select(vec4<f32>(), vw, {{ qn("s", qi) }}[kk] != 0.0);
|
| 608 |
+
{% endfor %}
|
| 609 |
+
}
|
| 610 |
+
}
|
| 611 |
+
{% for qi in range(QPL) %}
|
| 612 |
+
{{ qn("o" ~ c, qi) }} = {{ qn("acc", qi) }};
|
| 613 |
+
{% endfor %}
|
| 614 |
+
}
|
| 615 |
+
{% endfor %}
|
| 616 |
+
{% else %}
|
| 617 |
let tileInterior = kStart >= minKj && kStart + TILE_K <= maxKj;
|
| 618 |
{% for c in range(SLICE_COUNT) %}
|
| 619 |
{
|
|
|
|
| 630 |
o{{ c }} = acc;
|
| 631 |
}
|
| 632 |
{% endfor %}
|
| 633 |
+
{% endif %}
|
| 634 |
|
| 635 |
kStart = kStart + TILE_K;
|
| 636 |
}
|
| 637 |
|
| 638 |
+
{% macro attention_value(c, qi) %}{{ qn("o" ~ c, qi) }} * inv{% endmacro %}
|
| 639 |
+
{% for qi in range(QPL) %}
|
| 640 |
+
if ({{ qn("qValid", qi) }}) {
|
| 641 |
{% if QL == "bhsd" %}
|
| 642 |
+
let outBase4 = ((b * Q_HEADS + h) * {{ QSEQ }} + {{ qn("qIdx", qi) }}) * HEAD_DIM_V4 + lane8 * SLICE;
|
| 643 |
{% else %}
|
| 644 |
+
let outBase4 = (b * {{ QSEQ }} + {{ qn("qIdx", qi) }}) * Q_HIDDEN_V4 + h * HEAD_DIM_V4 + lane8 * SLICE;
|
| 645 |
{% endif %}
|
| 646 |
{% if hasHeadSink %}
|
| 647 |
// The head sink is a learned logit that competes with the keys but carries
|
| 648 |
// no value, so it enters the denominator only and the weighted sum above is
|
| 649 |
+
// untouched. Renormalizing against max({{ qn("m", qi) }}, sink) keeps the exponentials in
|
| 650 |
// range when the sink dominates a fully-masked row.
|
| 651 |
let sink = f32(head_sink[h]);
|
| 652 |
+
let finalM = max({{ qn("m", qi) }}, sink);
|
| 653 |
+
let accScale = exp({{ qn("m", qi) }} - finalM);
|
| 654 |
+
let inv = accScale / (exp(sink - finalM) + {{ qn("l", qi) }} * accScale);
|
| 655 |
{% else %}
|
| 656 |
+
// {{ qn("l", qi) }} == 0 means this query had no probability-bearing key: either its
|
| 657 |
// causal/window range is empty or its logical bool mask rejects every key.
|
| 658 |
// Emit 0 rather than 0/0. Other attention paths enforce the same empty-row
|
| 659 |
// contract by selecting on positive global mass.
|
| 660 |
+
let inv = select(0.0, 1.0 / {{ qn("l", qi) }}, {{ qn("l", qi) }} > 0.0);
|
| 661 |
{% endif %}
|
|
|
|
| 662 |
{% for c in range(SLICE_COUNT) %}
|
| 663 |
+
{{ OUTPUT }}[outBase4 + {{ c }}u] = vec4<{{ scalar }}>({{ attention_value(c, qi) }});
|
| 664 |
{% endfor %}
|
| 665 |
}
|
| 666 |
+
{% endfor %}
|
| 667 |
}
|
build/webgpu/attn-flash-q32-broadcast.wgsl.jinja
CHANGED
|
@@ -9,12 +9,8 @@
|
|
| 9 |
// cancels in softmax. Arbitrary attention masks use the fallback routes.
|
| 10 |
{% set ST = "f16" if usesF16 else "f32" %}
|
| 11 |
{% set components = ["x", "y", "z", "w"] %}
|
| 12 |
-
{% set USE_SUBGROUPS =
|
| 13 |
{% set Q_STEP = qStep if qStep is defined else 32 %}
|
| 14 |
-
{% set CAUSAL = false if (hasCausal is defined and not hasCausal) else true %}
|
| 15 |
-
{% if usesF16 %}
|
| 16 |
-
enable f16;
|
| 17 |
-
{% endif %}
|
| 18 |
{% if USE_SUBGROUPS %}
|
| 19 |
enable subgroups;
|
| 20 |
{% endif %}
|
|
@@ -25,9 +21,6 @@ const Q_HEADS: u32 = {{ qNumHeads }}u;
|
|
| 25 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 26 |
const K_STEP: u32 = {{ kStep }}u;
|
| 27 |
const Q_STEP: u32 = {{ Q_STEP }}u;
|
| 28 |
-
{% if hasBias %}
|
| 29 |
-
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 30 |
-
{% endif %}
|
| 31 |
const Q_HIDDEN_V4: u32 = Q_HEADS * HEAD_DIM_V4;
|
| 32 |
const KV_HIDDEN_V4: u32 = KV_HEADS * HEAD_DIM_V4;
|
| 33 |
const NEG_INF: f32 = -3.4028234663852886e38;
|
|
@@ -50,12 +43,6 @@ fn scale_value() -> f32 {
|
|
| 50 |
}
|
| 51 |
|
| 52 |
|
| 53 |
-
{% if hasBias %}
|
| 54 |
-
fn load_bias4(base: u32, d4: u32) -> vec4<f32> {
|
| 55 |
-
let offset = base + d4 * 4u;
|
| 56 |
-
return vec4<f32>(bias[offset], bias[offset + 1u], bias[offset + 2u], bias[offset + 3u]);
|
| 57 |
-
}
|
| 58 |
-
{% endif %}
|
| 59 |
|
| 60 |
@compute @workgroup_size({{ Q_STEP }}, 1, 1)
|
| 61 |
fn main(
|
|
@@ -86,9 +73,6 @@ fn main(
|
|
| 86 |
for (var d4: u32 = 0u; d4 < HEAD_DIM_V4; d4 = d4 + 1u) {
|
| 87 |
if (valid_q) {
|
| 88 |
q_tile[d4] = query[qBaseV4 + d4];
|
| 89 |
-
{% if hasBias %}
|
| 90 |
-
q_tile[d4] = vec4<{{ ST }}>(vec4<f32>(q_tile[d4]) + load_bias4(h * HEAD_DIM, d4));
|
| 91 |
-
{% endif %}
|
| 92 |
} else {
|
| 93 |
q_tile[d4] = vec4<{{ ST }}>(0.0);
|
| 94 |
}
|
|
@@ -101,14 +85,8 @@ fn main(
|
|
| 101 |
// Causal key ceiling. The key-loop bound (kvEnd) uses the workgroup's LAST query
|
| 102 |
// so every lane shares a uniform trip count (subgroup ops stay reconverged);
|
| 103 |
// each lane masks its own keys past myMaxKj to NEG_INF.
|
| 104 |
-
{% if CAUSAL %}
|
| 105 |
-
let lastQ = min(wg.x * Q_STEP + Q_STEP - 1u, params.qSeq - 1u);
|
| 106 |
-
let kvEnd = select(params.kvSeq, min(lastQ + 1u, params.kvSeq), params.isCausal != 0u);
|
| 107 |
-
let myMaxKj = select(params.kvSeq, min(qi + 1u, params.kvSeq), params.isCausal != 0u);
|
| 108 |
-
{% else %}
|
| 109 |
let kvEnd = params.kvSeq;
|
| 110 |
let myMaxKj = params.kvSeq;
|
| 111 |
-
{% endif %}
|
| 112 |
let kvBase = b * params.kvSeq * KV_HIDDEN_V4 + hKv * HEAD_DIM_V4;
|
| 113 |
let kvKeyStride = KV_HIDDEN_V4;
|
| 114 |
|
|
@@ -236,11 +214,7 @@ fn main(
|
|
| 236 |
if (valid_q) {
|
| 237 |
let outBaseV4 = (b * params.qSeq + qi) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 238 |
for (var d4: u32 = 0u; d4 < HEAD_DIM_V4; d4 = d4 + 1u) {
|
| 239 |
-
{% if hasBias %}
|
| 240 |
-
output[outBaseV4 + d4] = vec4<{{ scalar }}>(o_tile[d4] + load_bias4(2u * {{ qHidden }}u + h * HEAD_DIM, d4));
|
| 241 |
-
{% else %}
|
| 242 |
output[outBaseV4 + d4] = vec4<{{ scalar }}>(o_tile[d4]);
|
| 243 |
-
{% endif %}
|
| 244 |
}
|
| 245 |
}
|
| 246 |
}
|
|
|
|
| 9 |
// cancels in softmax. Arbitrary attention masks use the fallback routes.
|
| 10 |
{% set ST = "f16" if usesF16 else "f32" %}
|
| 11 |
{% set components = ["x", "y", "z", "w"] %}
|
| 12 |
+
{% set USE_SUBGROUPS = useSubgroups if useSubgroups is defined else true %}
|
| 13 |
{% set Q_STEP = qStep if qStep is defined else 32 %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
{% if USE_SUBGROUPS %}
|
| 15 |
enable subgroups;
|
| 16 |
{% endif %}
|
|
|
|
| 21 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 22 |
const K_STEP: u32 = {{ kStep }}u;
|
| 23 |
const Q_STEP: u32 = {{ Q_STEP }}u;
|
|
|
|
|
|
|
|
|
|
| 24 |
const Q_HIDDEN_V4: u32 = Q_HEADS * HEAD_DIM_V4;
|
| 25 |
const KV_HIDDEN_V4: u32 = KV_HEADS * HEAD_DIM_V4;
|
| 26 |
const NEG_INF: f32 = -3.4028234663852886e38;
|
|
|
|
| 43 |
}
|
| 44 |
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
@compute @workgroup_size({{ Q_STEP }}, 1, 1)
|
| 48 |
fn main(
|
|
|
|
| 73 |
for (var d4: u32 = 0u; d4 < HEAD_DIM_V4; d4 = d4 + 1u) {
|
| 74 |
if (valid_q) {
|
| 75 |
q_tile[d4] = query[qBaseV4 + d4];
|
|
|
|
|
|
|
|
|
|
| 76 |
} else {
|
| 77 |
q_tile[d4] = vec4<{{ ST }}>(0.0);
|
| 78 |
}
|
|
|
|
| 85 |
// Causal key ceiling. The key-loop bound (kvEnd) uses the workgroup's LAST query
|
| 86 |
// so every lane shares a uniform trip count (subgroup ops stay reconverged);
|
| 87 |
// each lane masks its own keys past myMaxKj to NEG_INF.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
let kvEnd = params.kvSeq;
|
| 89 |
let myMaxKj = params.kvSeq;
|
|
|
|
| 90 |
let kvBase = b * params.kvSeq * KV_HIDDEN_V4 + hKv * HEAD_DIM_V4;
|
| 91 |
let kvKeyStride = KV_HIDDEN_V4;
|
| 92 |
|
|
|
|
| 214 |
if (valid_q) {
|
| 215 |
let outBaseV4 = (b * params.qSeq + qi) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 216 |
for (var d4: u32 = 0u; d4 < HEAD_DIM_V4; d4 = d4 + 1u) {
|
|
|
|
|
|
|
|
|
|
| 217 |
output[outBaseV4 + d4] = vec4<{{ scalar }}>(o_tile[d4]);
|
|
|
|
| 218 |
}
|
| 219 |
}
|
| 220 |
}
|
build/webgpu/attn-materialized-rowstats-combine-f32.wgsl.jinja
CHANGED
|
@@ -43,10 +43,9 @@ fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
|
| 43 |
|
| 44 |
@compute @workgroup_size(WG, 1, 1)
|
| 45 |
fn main(
|
| 46 |
-
@builtin(global_invocation_id) gid: vec3<u32>
|
| 47 |
-
@builtin(num_workgroups) nwg: vec3<u32>
|
| 48 |
) {
|
| 49 |
-
let row = gid.x + gid.y *
|
| 50 |
if (row >= params.rows) { return; }
|
| 51 |
|
| 52 |
// `row` already runs over (batch, head, query) together, and the partial
|
|
|
|
| 43 |
|
| 44 |
@compute @workgroup_size(WG, 1, 1)
|
| 45 |
fn main(
|
| 46 |
+
@builtin(global_invocation_id) gid: vec3<u32>
|
|
|
|
| 47 |
) {
|
| 48 |
+
let row = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 49 |
if (row >= params.rows) { return; }
|
| 50 |
|
| 51 |
// `row` already runs over (batch, head, query) together, and the partial
|
build/webgpu/attn-materialized-sgmat-f32.wgsl.jinja
CHANGED
|
@@ -9,22 +9,23 @@ enable subgroup_size_control;
|
|
| 9 |
enable chromium_experimental_subgroup_matrix;
|
| 10 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 11 |
|
|
|
|
| 12 |
{{ env.wgsl.resourceDeclarations }}
|
| 13 |
|
| 14 |
-
{% set layout =
|
| 15 |
{% set headMajor = layout == "bhsd" %}
|
| 16 |
-
{% set kvHeadMajor = (
|
| 17 |
-
{% set CAUSAL_UPPER_LEFT =
|
| 18 |
-
{% set CAUSAL = (
|
| 19 |
-
{% macro q_index(row, d) %}(b * params.qSeq + {{ row }}) * HIDDEN + h * HEAD_DIM + {{ d }}{% endmacro %}
|
| 20 |
{% macro kv_index(seq, d) %}{% if kvHeadMajor %}((b * KV_HEADS + h_kv) * params.kvSeq + {{ seq }}) * HEAD_DIM + {{ d }}{% else %}(b * params.kvSeq + {{ seq }}) * KV_HIDDEN + h_kv * HEAD_DIM + {{ d }}{% endif %}{% endmacro %}
|
| 21 |
{% set OUT_ROW_STRIDE = "HEAD_DIM" if headMajor else "HIDDEN" %}
|
| 22 |
{% set KV_ROW_STRIDE = "HEAD_DIM" if kvHeadMajor else "KV_HIDDEN" %}
|
| 23 |
-
{% set scorePhase =
|
| 24 |
-
{% set SCORE_BIAS = scorePhase and
|
| 25 |
-
{% set SCORE_WINDOW = CAUSAL and
|
| 26 |
-
{% set USE_SEQLENS =
|
| 27 |
-
{% set FUSED_SOFTMAX =
|
| 28 |
{% set PRIVATE_ROW_STATS = FUSED_SOFTMAX and (materializedSgmatPrivateRowStats is defined and materializedSgmatPrivateRowStats) %}
|
| 29 |
{% macro score_value(index, guard) %}
|
| 30 |
{% if FUSED_SOFTMAX %}
|
|
@@ -49,7 +50,7 @@ select(0.0, scores[{{ index }}], {{ guard[1] }})
|
|
| 49 |
{% set SUBGROUP_COUNT = SUBGROUP_ROWS * SUBGROUP_COLS %}
|
| 50 |
{% set WORKGROUP_THREADS = SUBGROUP_COUNT * 32 %}
|
| 51 |
{% if hasBias is not defined %}{% set hasBias = false %}{% endif %}
|
| 52 |
-
{% set EMIT_ROW_STATS =
|
| 53 |
{% set DIRECT_SCORE_STORE = materializedSgmatDirectScoreStore and not EMIT_ROW_STATS %}
|
| 54 |
{% set DIRECT_APPLY_STORE = materializedSgmatDirectApplyStore and not hasBias and MT == "f32" %}
|
| 55 |
{% set DIRECT_OUTPUT_STORE = DIRECT_SCORE_STORE if scorePhase else DIRECT_APPLY_STORE %}
|
|
@@ -58,17 +59,14 @@ select(0.0, scores[{{ index }}], {{ guard[1] }})
|
|
| 58 |
and (scorePhase or (not hasBias and MT == "f32"))
|
| 59 |
and not EMIT_ROW_STATS %}
|
| 60 |
{% set ANY_DIRECT_STORE = DIRECT_OUTPUT_STORE or RUNTIME_DIRECT_STORE %}
|
| 61 |
-
{%
|
| 62 |
-
{% macro q_tile_value(index) %}{% if hasBias %}(query[{{ index }}] + bias[h * HEAD_DIM + k]){% else %}query[{{ index }}]{% endif %}{% if SCALE_IN_Q %} * score_scale{% endif %}{% endmacro %}
|
| 63 |
|
| 64 |
const HEADS: u32 = {{ qNumHeads }}u;
|
| 65 |
-
{% if kvNumHeads is defined %}
|
| 66 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 67 |
-
{% else %}
|
| 68 |
-
const KV_HEADS: u32 = HEADS;
|
| 69 |
-
{% endif %}
|
| 70 |
const HEAD_DIM: u32 = {{ headDim }}u;
|
|
|
|
| 71 |
const HIDDEN: u32 = {{ qHidden }}u;
|
|
|
|
| 72 |
{% if hasBias %}
|
| 73 |
/* Packed [Q; K; V] bias. Q folds into the query tile before the GEMM. K is
|
| 74 |
* omitted: expanding (q + bq).(k + bk) leaves a (q + bq).bk term that is
|
|
@@ -158,10 +156,10 @@ fn main(
|
|
| 158 |
let base_A = subtile_idy * SUB_ROWS;
|
| 159 |
let base_B = subtile_idx * SUB_COLS;
|
| 160 |
{% if USE_SEQLENS %}
|
| 161 |
-
//
|
| 162 |
-
//
|
| 163 |
-
//
|
| 164 |
-
let kv_active = min(params.kvSeq, u32(seqlens_k[b]) + 1u);
|
| 165 |
{% endif %}
|
| 166 |
{% if CAUSAL and scorePhase %}
|
| 167 |
// Workgroup-uniform causal skip: a key tile starting past the last row's
|
|
@@ -172,7 +170,8 @@ fn main(
|
|
| 172 |
// the same way: later rows only move the floor further right.
|
| 173 |
{% endif %}
|
| 174 |
let kv_causal_off = i32({% if USE_SEQLENS %}kv_active{% else %}params.kvSeq{% endif %}) - i32(params.qSeq);
|
| 175 |
-
if (i32(n_base) > kv_causal_off + i32(m_base + TILE_M) - 1{% if
|
|
|
|
| 176 |
|| i32(n_base + TILE_N) <= kv_causal_off + i32(m_base) + 1 - i32(params.windowSize){% endif %}) {
|
| 177 |
for (var idx = li; idx < TILE_M * {{ SUBGROUP_COLS }}u; idx += {{ WORKGROUP_THREADS }}u) {
|
| 178 |
let stat_row = m_base + idx / {{ SUBGROUP_COLS }}u;
|
|
@@ -195,11 +194,6 @@ fn main(
|
|
| 195 |
|
| 196 |
{% if scorePhase %}
|
| 197 |
let inner = HEAD_DIM;
|
| 198 |
-
{% if SCALE_IN_Q %}
|
| 199 |
-
// The direct store has no epilogue, so apply the score scale to Q. Direct and
|
| 200 |
-
// guarded store paths then share one pre-scaled query tile.
|
| 201 |
-
let score_scale = {{ attentionScaleExpression }};
|
| 202 |
-
{% endif %}
|
| 203 |
{% else %}
|
| 204 |
let inner = {% if USE_SEQLENS %}kv_active{% else %}params.kvSeq{% endif %};
|
| 205 |
{% if CAUSAL %}
|
|
@@ -240,7 +234,7 @@ fn main(
|
|
| 240 |
{% endif %}
|
| 241 |
{% endif %}
|
| 242 |
for (var k_base = {% if SCORE_WINDOW and not scorePhase %}inner_start{% else %}0u{% endif %}; k_base < {% if CAUSAL and not scorePhase %}inner_bound{% else %}inner{% endif %}; k_base += TILE_K) {
|
| 243 |
-
{% if
|
| 244 |
// Full interior PV tiles can be loaded directly from storage. Query,
|
| 245 |
// reduction, and output-dimension tails use the guarded shared path below.
|
| 246 |
if (
|
|
@@ -278,10 +272,8 @@ fn main(
|
|
| 278 |
{% endif %}
|
| 279 |
|
| 280 |
{% if TILE_M_VALUE == 64 and TILE_N_VALUE == 64 and TILE_K_VALUE == 32 and WORKGROUP_THREADS == 256 %}
|
| 281 |
-
// BM64/BN64/WG256
|
| 282 |
-
//
|
| 283 |
-
// row, preserving the original coalesced load schedule while retaining
|
| 284 |
-
// the generalized K-tail guards.
|
| 285 |
let a_row = li / 4u;
|
| 286 |
let a_col = (li % 4u) * 8u;
|
| 287 |
for (var i = 0u; i < 8u; i++) {
|
|
@@ -353,10 +345,10 @@ fn main(
|
|
| 353 |
let loaded =
|
| 354 |
{{ "f16(" if MT == "f16" else "" }}{{ score_value("score_base + row * params.kvSeq + k", ["tile_row", "row < params.qSeq && k < params.kvSeq"]) }}{{ ")" if MT == "f16" else "" }};
|
| 355 |
{% else %}
|
| 356 |
-
var loaded =
|
| 357 |
if (row < params.qSeq && k < params.kvSeq) {
|
| 358 |
let score_base = (b * HEADS + h) * params.qSeq * params.kvSeq;
|
| 359 |
-
loaded =
|
| 360 |
}
|
| 361 |
{% endif %}
|
| 362 |
tile_A[idx] = loaded;
|
|
@@ -412,18 +404,8 @@ fn main(
|
|
| 412 |
}
|
| 413 |
|
| 414 |
{% if ANY_DIRECT_STORE %}
|
| 415 |
-
{% if RUNTIME_DIRECT_STORE %}
|
| 416 |
-
// Workgroup-uniform (both bases come from workgroup_id alone): an interior tile
|
| 417 |
-
// publishes through the subgroup-matrix collectives and is done; only an edge
|
| 418 |
-
// tile falls through to the scratch round trip and its barriers.
|
| 419 |
-
if (
|
| 420 |
-
m_base + TILE_M <= params.qSeq &&
|
| 421 |
-
n_base + TILE_N <= {% if scorePhase %}params.kvSeq{% else %}HEAD_DIM{% endif %}
|
| 422 |
-
) {
|
| 423 |
-
{% else %}
|
| 424 |
// Every dispatched tile is interior, so the matrices can go straight to
|
| 425 |
// storage without the compact scratch/readback epilogue.
|
| 426 |
-
{% endif %}
|
| 427 |
{% for row_block in range(ROW_BLOCKS) %}
|
| 428 |
{% for col_block in range(COL_BLOCKS) %}
|
| 429 |
{% if scorePhase %}
|
|
@@ -445,10 +427,6 @@ fn main(
|
|
| 445 |
{% endif %}
|
| 446 |
{% endfor %}
|
| 447 |
{% endfor %}
|
| 448 |
-
{% if RUNTIME_DIRECT_STORE %}
|
| 449 |
-
return;
|
| 450 |
-
}
|
| 451 |
-
{% endif %}
|
| 452 |
{% endif %}
|
| 453 |
{% if not DIRECT_OUTPUT_STORE %}
|
| 454 |
let row_in_block = lane / 4u;
|
|
@@ -489,9 +467,7 @@ fn main(
|
|
| 489 |
+ row_in_block * 8u + col_in_block + pair
|
| 490 |
];
|
| 491 |
{% if scorePhase %}
|
| 492 |
-
{% if not SCALE_IN_Q %}
|
| 493 |
let scale = {{ attentionScaleExpression }};
|
| 494 |
-
{% endif %}
|
| 495 |
{% if CAUSAL %}
|
| 496 |
var scored = result * scale;
|
| 497 |
{% if SCORE_BIAS %}
|
|
@@ -502,11 +478,14 @@ fn main(
|
|
| 502 |
];
|
| 503 |
{% endif %}
|
| 504 |
if (i32(col) > kv_causal_off + i32(row)) { scored = -FLT_MAX; }
|
|
|
|
|
|
|
|
|
|
| 505 |
{% if SCORE_WINDOW %}
|
| 506 |
if (i32(col) + i32(params.windowSize) <= kv_causal_off + i32(row)) { scored = -FLT_MAX; }
|
| 507 |
{% endif %}
|
| 508 |
{% else %}
|
| 509 |
-
let scored = result
|
| 510 |
{% endif %}
|
| 511 |
scores[
|
| 512 |
(b * HEADS + h) * params.qSeq * params.kvSeq + row * params.kvSeq + col
|
|
|
|
| 9 |
enable chromium_experimental_subgroup_matrix;
|
| 10 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 11 |
|
| 12 |
+
|
| 13 |
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
|
| 15 |
+
{% set layout = layout | default("bsh") %}
|
| 16 |
{% set headMajor = layout == "bhsd" %}
|
| 17 |
+
{% set kvHeadMajor = (kvLayout | default(layout)) == "bhsd" %}
|
| 18 |
+
{% set CAUSAL_UPPER_LEFT = causalUpperLeft is defined and causalUpperLeft %}
|
| 19 |
+
{% set CAUSAL = (causalRightAlign is defined and causalRightAlign) or CAUSAL_UPPER_LEFT %}
|
| 20 |
+
{% macro q_index(row, d) %}{% if headMajor %}((b * HEADS + h) * params.qSeq + {{ row }}) * HEAD_DIM + {{ d }}{% else %}(b * params.qSeq + {{ row }}) * HIDDEN + h * HEAD_DIM + {{ d }}{% endif %}{% endmacro %}
|
| 21 |
{% macro kv_index(seq, d) %}{% if kvHeadMajor %}((b * KV_HEADS + h_kv) * params.kvSeq + {{ seq }}) * HEAD_DIM + {{ d }}{% else %}(b * params.kvSeq + {{ seq }}) * KV_HIDDEN + h_kv * HEAD_DIM + {{ d }}{% endif %}{% endmacro %}
|
| 22 |
{% set OUT_ROW_STRIDE = "HEAD_DIM" if headMajor else "HIDDEN" %}
|
| 23 |
{% set KV_ROW_STRIDE = "HEAD_DIM" if kvHeadMajor else "KV_HIDDEN" %}
|
| 24 |
+
{% set scorePhase = phase == "score" %}
|
| 25 |
+
{% set SCORE_BIAS = scorePhase and scoreBias is defined and scoreBias %}
|
| 26 |
+
{% set SCORE_WINDOW = CAUSAL and scoreWindow is defined and scoreWindow %}
|
| 27 |
+
{% set USE_SEQLENS = useSeqlens is defined and useSeqlens %}
|
| 28 |
+
{% set FUSED_SOFTMAX = fusedSoftmax is defined and fusedSoftmax %}
|
| 29 |
{% set PRIVATE_ROW_STATS = FUSED_SOFTMAX and (materializedSgmatPrivateRowStats is defined and materializedSgmatPrivateRowStats) %}
|
| 30 |
{% macro score_value(index, guard) %}
|
| 31 |
{% if FUSED_SOFTMAX %}
|
|
|
|
| 50 |
{% set SUBGROUP_COUNT = SUBGROUP_ROWS * SUBGROUP_COLS %}
|
| 51 |
{% set WORKGROUP_THREADS = SUBGROUP_COUNT * 32 %}
|
| 52 |
{% if hasBias is not defined %}{% set hasBias = false %}{% endif %}
|
| 53 |
+
{% set EMIT_ROW_STATS = emitRowStats is defined and emitRowStats %}
|
| 54 |
{% set DIRECT_SCORE_STORE = materializedSgmatDirectScoreStore and not EMIT_ROW_STATS %}
|
| 55 |
{% set DIRECT_APPLY_STORE = materializedSgmatDirectApplyStore and not hasBias and MT == "f32" %}
|
| 56 |
{% set DIRECT_OUTPUT_STORE = DIRECT_SCORE_STORE if scorePhase else DIRECT_APPLY_STORE %}
|
|
|
|
| 59 |
and (scorePhase or (not hasBias and MT == "f32"))
|
| 60 |
and not EMIT_ROW_STATS %}
|
| 61 |
{% set ANY_DIRECT_STORE = DIRECT_OUTPUT_STORE or RUNTIME_DIRECT_STORE %}
|
| 62 |
+
{% macro q_tile_value(index) %}{% if hasBias %}(query[{{ index }}] + bias[h * HEAD_DIM + k]){% else %}query[{{ index }}]{% endif %}{% endmacro %}
|
|
|
|
| 63 |
|
| 64 |
const HEADS: u32 = {{ qNumHeads }}u;
|
|
|
|
| 65 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
|
|
|
|
|
|
|
|
|
| 66 |
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 67 |
+
{% if layout != "bhsd" or hasBias %}
|
| 68 |
const HIDDEN: u32 = {{ qHidden }}u;
|
| 69 |
+
{% endif %}
|
| 70 |
{% if hasBias %}
|
| 71 |
/* Packed [Q; K; V] bias. Q folds into the query tile before the GEMM. K is
|
| 72 |
* omitted: expanding (q + bq).(k + bk) leaves a (q + bq).bk term that is
|
|
|
|
| 156 |
let base_A = subtile_idy * SUB_ROWS;
|
| 157 |
let base_B = subtile_idx * SUB_COLS;
|
| 158 |
{% if USE_SEQLENS %}
|
| 159 |
+
// Resident rows occupy [0, kv_active). Causal and window bounds use this live
|
| 160 |
+
// length while params.kvSeq remains the allocated stride. Clamp by qSeq so a
|
| 161 |
+
// right-padded batch still includes the complete appended query chunk.
|
| 162 |
+
let kv_active = min(params.kvSeq, max(params.qSeq, u32(seqlens_k[b]) + 1u));
|
| 163 |
{% endif %}
|
| 164 |
{% if CAUSAL and scorePhase %}
|
| 165 |
// Workgroup-uniform causal skip: a key tile starting past the last row's
|
|
|
|
| 170 |
// the same way: later rows only move the floor further right.
|
| 171 |
{% endif %}
|
| 172 |
let kv_causal_off = i32({% if USE_SEQLENS %}kv_active{% else %}params.kvSeq{% endif %}) - i32(params.qSeq);
|
| 173 |
+
if (i32(n_base) > kv_causal_off + i32(m_base + TILE_M) - 1{% if USE_SEQLENS %}
|
| 174 |
+
|| n_base >= kv_active{% endif %}{% if SCORE_WINDOW %}
|
| 175 |
|| i32(n_base + TILE_N) <= kv_causal_off + i32(m_base) + 1 - i32(params.windowSize){% endif %}) {
|
| 176 |
for (var idx = li; idx < TILE_M * {{ SUBGROUP_COLS }}u; idx += {{ WORKGROUP_THREADS }}u) {
|
| 177 |
let stat_row = m_base + idx / {{ SUBGROUP_COLS }}u;
|
|
|
|
| 194 |
|
| 195 |
{% if scorePhase %}
|
| 196 |
let inner = HEAD_DIM;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
{% else %}
|
| 198 |
let inner = {% if USE_SEQLENS %}kv_active{% else %}params.kvSeq{% endif %};
|
| 199 |
{% if CAUSAL %}
|
|
|
|
| 234 |
{% endif %}
|
| 235 |
{% endif %}
|
| 236 |
for (var k_base = {% if SCORE_WINDOW and not scorePhase %}inner_start{% else %}0u{% endif %}; k_base < {% if CAUSAL and not scorePhase %}inner_bound{% else %}inner{% endif %}; k_base += TILE_K) {
|
| 237 |
+
{% if phase == "apply" and not FUSED_SOFTMAX and MT == "f32" %}
|
| 238 |
// Full interior PV tiles can be loaded directly from storage. Query,
|
| 239 |
// reduction, and output-dimension tails use the guarded shared path below.
|
| 240 |
if (
|
|
|
|
| 272 |
{% endif %}
|
| 273 |
|
| 274 |
{% if TILE_M_VALUE == 64 and TILE_N_VALUE == 64 and TILE_K_VALUE == 32 and WORKGROUP_THREADS == 256 %}
|
| 275 |
+
// In the BM64/BN64/BK32/WG256 geometry, four adjacent threads each own one
|
| 276 |
+
// contiguous eight-element segment of a row. K-tail guards remain active.
|
|
|
|
|
|
|
| 277 |
let a_row = li / 4u;
|
| 278 |
let a_col = (li % 4u) * 8u;
|
| 279 |
for (var i = 0u; i < 8u; i++) {
|
|
|
|
| 345 |
let loaded =
|
| 346 |
{{ "f16(" if MT == "f16" else "" }}{{ score_value("score_base + row * params.kvSeq + k", ["tile_row", "row < params.qSeq && k < params.kvSeq"]) }}{{ ")" if MT == "f16" else "" }};
|
| 347 |
{% else %}
|
| 348 |
+
var loaded = 0.0;
|
| 349 |
if (row < params.qSeq && k < params.kvSeq) {
|
| 350 |
let score_base = (b * HEADS + h) * params.qSeq * params.kvSeq;
|
| 351 |
+
loaded = scores[score_base + row * params.kvSeq + k];
|
| 352 |
}
|
| 353 |
{% endif %}
|
| 354 |
tile_A[idx] = loaded;
|
|
|
|
| 404 |
}
|
| 405 |
|
| 406 |
{% if ANY_DIRECT_STORE %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
// Every dispatched tile is interior, so the matrices can go straight to
|
| 408 |
// storage without the compact scratch/readback epilogue.
|
|
|
|
| 409 |
{% for row_block in range(ROW_BLOCKS) %}
|
| 410 |
{% for col_block in range(COL_BLOCKS) %}
|
| 411 |
{% if scorePhase %}
|
|
|
|
| 427 |
{% endif %}
|
| 428 |
{% endfor %}
|
| 429 |
{% endfor %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
{% endif %}
|
| 431 |
{% if not DIRECT_OUTPUT_STORE %}
|
| 432 |
let row_in_block = lane / 4u;
|
|
|
|
| 467 |
+ row_in_block * 8u + col_in_block + pair
|
| 468 |
];
|
| 469 |
{% if scorePhase %}
|
|
|
|
| 470 |
let scale = {{ attentionScaleExpression }};
|
|
|
|
| 471 |
{% if CAUSAL %}
|
| 472 |
var scored = result * scale;
|
| 473 |
{% if SCORE_BIAS %}
|
|
|
|
| 478 |
];
|
| 479 |
{% endif %}
|
| 480 |
if (i32(col) > kv_causal_off + i32(row)) { scored = -FLT_MAX; }
|
| 481 |
+
{% if USE_SEQLENS %}
|
| 482 |
+
if (col >= kv_active) { scored = -FLT_MAX; }
|
| 483 |
+
{% endif %}
|
| 484 |
{% if SCORE_WINDOW %}
|
| 485 |
if (i32(col) + i32(params.windowSize) <= kv_causal_off + i32(row)) { scored = -FLT_MAX; }
|
| 486 |
{% endif %}
|
| 487 |
{% else %}
|
| 488 |
+
let scored = result * scale;
|
| 489 |
{% endif %}
|
| 490 |
scores[
|
| 491 |
(b * HEADS + h) * params.qSeq * params.kvSeq + row * params.kvSeq + col
|
build/webgpu/attn-online-scalar.wgsl.jinja
CHANGED
|
@@ -1,28 +1,14 @@
|
|
| 1 |
-
{% if usesF16 %}
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
-
{% set MASK_BATCH = "batch * params.maskBatchStride + " if
|
| 6 |
|
| 7 |
// Online-softmax attention fallback with no feature requirements: one
|
| 8 |
// workgroup per (batch, head, query token) walks the keys serially; the
|
| 9 |
// workgroup cooperates on each q·k dot (tree reduction) and on the running
|
| 10 |
-
// V accumulator, with the online rescale applied per key.
|
| 11 |
-
//
|
| 12 |
// Layout: rank-3 token-major [batch, seq, heads * headDim].
|
| 13 |
-
{% if headsFromParams %}
|
| 14 |
-
// Q/KV head counts are uniforms; the GQA mapping is h / (qHeads / kvHeads).
|
| 15 |
-
{% endif %}
|
| 16 |
-
{% if hasKeyLimit %}
|
| 17 |
-
// params.keyLimit caps the key range when nonzero.
|
| 18 |
-
{% endif %}
|
| 19 |
-
{% if scaleFallbackRsqrt %}
|
| 20 |
// An omitted scale uses 1/sqrt(headDim); explicit zero remains zero through
|
| 21 |
// the scaleIsExplicitZero specialization.
|
| 22 |
-
{% endif %}
|
| 23 |
-
{% if hasBias %}
|
| 24 |
-
// Packed [Q; K; V] bias rows are applied during the serial key walk.
|
| 25 |
-
{% endif %}
|
| 26 |
{% if hasMask %}
|
| 27 |
// Additive per-score attention bias with broadcast strides: a stride of 0
|
| 28 |
// collapses that axis (batch and/or head broadcast).
|
|
@@ -30,10 +16,8 @@ enable f16;
|
|
| 30 |
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 31 |
const Q_HIDDEN: u32 = {{ qHidden }}u;
|
| 32 |
const KV_HIDDEN: u32 = {{ kvHidden }}u;
|
| 33 |
-
{% if not headsFromParams %}
|
| 34 |
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 35 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 36 |
-
{% endif %}
|
| 37 |
{% set qHeads = "params.qHeads" if headsFromParams else "Q_HEADS" %}
|
| 38 |
{% set kvHeads = "params.kvHeads" if headsFromParams else "KV_HEADS" %}
|
| 39 |
{% set scale = scale | default("0.0") %}
|
|
@@ -80,18 +64,12 @@ fn {{ name }}(value: f32, tid: u32) -> f32 {
|
|
| 80 |
|
| 81 |
{{ wgsl_tree_reduce_f32("reduce_sum", "add", "partial", "WG") }}
|
| 82 |
|
| 83 |
-
{% if scaleFallbackRsqrt %}
|
| 84 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 85 |
fn scale_value() -> f32 {
|
| 86 |
if (params.scale != 0.0) { return params.scale; }
|
| 87 |
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
| 88 |
}
|
| 89 |
|
| 90 |
-
{% else %}
|
| 91 |
-
fn scale_value() -> f32 {
|
| 92 |
-
return {{ scale }};
|
| 93 |
-
}
|
| 94 |
-
{% endif %}
|
| 95 |
|
| 96 |
@compute @workgroup_size(WG, 1, 1)
|
| 97 |
fn main(
|
|
@@ -132,12 +110,6 @@ fn main(
|
|
| 132 |
|
| 133 |
var maxKj = params.kvSeq;
|
| 134 |
var minKj: u32 = 0u;
|
| 135 |
-
{% if hasCausal %}
|
| 136 |
-
maxKj = min(maxKj, select(params.kvSeq, query_token + 1u, params.isCausal != 0u));
|
| 137 |
-
{% endif %}
|
| 138 |
-
{% if hasKeyLimit %}
|
| 139 |
-
maxKj = min(maxKj, select(params.kvSeq, params.keyLimit, params.keyLimit > 0u));
|
| 140 |
-
{% endif %}
|
| 141 |
{% if hasWindow %}
|
| 142 |
// local_window_size: the query at relative index
|
| 143 |
// query_token sits at absolute position p = kvSeq - qSeq + query_token, so it
|
|
@@ -156,11 +128,6 @@ fn main(
|
|
| 156 |
for (var d: u32 = tid; d < HEAD_DIM; d = d + WG) {
|
| 157 |
var q_value = f32(query[qBase + d]);
|
| 158 |
var k_value = f32(key[kRow + d]);
|
| 159 |
-
{% if hasBias %}
|
| 160 |
-
let channel = h * HEAD_DIM + d;
|
| 161 |
-
q_value = q_value + f32(bias[channel]);
|
| 162 |
-
k_value = k_value + f32(bias[Q_HIDDEN + channel]);
|
| 163 |
-
{% endif %}
|
| 164 |
partial_dot = partial_dot + q_value * k_value;
|
| 165 |
}
|
| 166 |
|
|
@@ -186,15 +153,14 @@ fn main(
|
|
| 186 |
let probability_numerator = exp(score - running_max);
|
| 187 |
for (var d: u32 = tid; d < HEAD_DIM; d = d + WG) {
|
| 188 |
var v_value = f32(value[kRow + d]);
|
| 189 |
-
{% if hasBias %}
|
| 190 |
-
v_value = v_value + f32(bias[2u * Q_HIDDEN + h * HEAD_DIM + d]);
|
| 191 |
-
{% endif %}
|
| 192 |
running_out[d] = running_out[d] * previous_scale + probability_numerator * v_value;
|
| 193 |
}
|
| 194 |
workgroupBarrier();
|
| 195 |
}
|
| 196 |
|
| 197 |
-
|
|
|
|
|
|
|
| 198 |
for (var d: u32 = tid; d < HEAD_DIM; d = d + WG) {
|
| 199 |
output[qBase + d] = {{ outputScalar }}(running_out[d] * inv_denom);
|
| 200 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
+
{% set MASK_BATCH = "batch * params.maskBatchStride + " if layout == "bsh" else "" %}
|
| 3 |
|
| 4 |
// Online-softmax attention fallback with no feature requirements: one
|
| 5 |
// workgroup per (batch, head, query token) walks the keys serially; the
|
| 6 |
// workgroup cooperates on each q·k dot (tree reduction) and on the running
|
| 7 |
+
// V accumulator, with the online rescale applied per key. This path requires
|
| 8 |
+
// no subgroup or subgroup-matrix features.
|
| 9 |
// Layout: rank-3 token-major [batch, seq, heads * headDim].
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
// An omitted scale uses 1/sqrt(headDim); explicit zero remains zero through
|
| 11 |
// the scaleIsExplicitZero specialization.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
{% if hasMask %}
|
| 13 |
// Additive per-score attention bias with broadcast strides: a stride of 0
|
| 14 |
// collapses that axis (batch and/or head broadcast).
|
|
|
|
| 16 |
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 17 |
const Q_HIDDEN: u32 = {{ qHidden }}u;
|
| 18 |
const KV_HIDDEN: u32 = {{ kvHidden }}u;
|
|
|
|
| 19 |
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 20 |
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
|
|
|
| 21 |
{% set qHeads = "params.qHeads" if headsFromParams else "Q_HEADS" %}
|
| 22 |
{% set kvHeads = "params.kvHeads" if headsFromParams else "KV_HEADS" %}
|
| 23 |
{% set scale = scale | default("0.0") %}
|
|
|
|
| 64 |
|
| 65 |
{{ wgsl_tree_reduce_f32("reduce_sum", "add", "partial", "WG") }}
|
| 66 |
|
|
|
|
| 67 |
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 68 |
fn scale_value() -> f32 {
|
| 69 |
if (params.scale != 0.0) { return params.scale; }
|
| 70 |
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
| 71 |
}
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
@compute @workgroup_size(WG, 1, 1)
|
| 75 |
fn main(
|
|
|
|
| 110 |
|
| 111 |
var maxKj = params.kvSeq;
|
| 112 |
var minKj: u32 = 0u;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
{% if hasWindow %}
|
| 114 |
// local_window_size: the query at relative index
|
| 115 |
// query_token sits at absolute position p = kvSeq - qSeq + query_token, so it
|
|
|
|
| 128 |
for (var d: u32 = tid; d < HEAD_DIM; d = d + WG) {
|
| 129 |
var q_value = f32(query[qBase + d]);
|
| 130 |
var k_value = f32(key[kRow + d]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
partial_dot = partial_dot + q_value * k_value;
|
| 132 |
}
|
| 133 |
|
|
|
|
| 153 |
let probability_numerator = exp(score - running_max);
|
| 154 |
for (var d: u32 = tid; d < HEAD_DIM; d = d + WG) {
|
| 155 |
var v_value = f32(value[kRow + d]);
|
|
|
|
|
|
|
|
|
|
| 156 |
running_out[d] = running_out[d] * previous_scale + probability_numerator * v_value;
|
| 157 |
}
|
| 158 |
workgroupBarrier();
|
| 159 |
}
|
| 160 |
|
| 161 |
+
// A query whose every key is masked to -inf (or whose key range is empty)
|
| 162 |
+
// leaves running_denom at zero; emit a zero row rather than 0 * (1/0) = NaN.
|
| 163 |
+
let inv_denom = select(0.0, 1.0 / running_denom, running_denom > 0.0);
|
| 164 |
for (var d: u32 = tid; d < HEAD_DIM; d = d + WG) {
|
| 165 |
output[qBase + d] = {{ outputScalar }}(running_out[d] * inv_denom);
|
| 166 |
}
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "com.microsoft.GroupQueryAttention",
|
| 3 |
"tunableSpace": {
|
| 4 |
"PREFILL_QUERY_TILE": [16, 32],
|
| 5 |
"PREFILL_LANES_PER_QUERY": [4, 8],
|
|
@@ -134,7 +133,7 @@
|
|
| 134 |
]
|
| 135 |
},
|
| 136 |
"provenance": {
|
| 137 |
-
"notes": "Long-context decode
|
| 138 |
}
|
| 139 |
},
|
| 140 |
{
|
|
@@ -711,9 +710,7 @@
|
|
| 711 |
{
|
| 712 |
"name": "quant-int8-prefill-h32kv8-d128-s512-pathology",
|
| 713 |
"preset": "stress",
|
| 714 |
-
"provenance": {
|
| 715 |
-
"notes": "Production-sized prefill for the int8 KV-cache contract, retained as a performance witness for quantized-cache attention."
|
| 716 |
-
},
|
| 717 |
"attrs": {
|
| 718 |
"num_heads": 32,
|
| 719 |
"kv_num_heads": 8,
|
|
@@ -768,9 +765,7 @@
|
|
| 768 |
{
|
| 769 |
"name": "quant-int4-prefill-h32kv8-d128-s512-pathology",
|
| 770 |
"preset": "stress",
|
| 771 |
-
"provenance": {
|
| 772 |
-
"notes": "Production-sized prefill for the packed int4 KV-cache contract, retained as a performance witness for quantized-cache attention."
|
| 773 |
-
},
|
| 774 |
"attrs": {
|
| 775 |
"num_heads": 32,
|
| 776 |
"kv_num_heads": 8,
|
|
@@ -1048,7 +1043,7 @@
|
|
| 1048 |
"name": "headsink-prefill-h32kv8-d128-s512-generic-pathology",
|
| 1049 |
"preset": "stress",
|
| 1050 |
"provenance": {
|
| 1051 |
-
"notes": "Llama-sized GQA prefill
|
| 1052 |
},
|
| 1053 |
"attrs": { "num_heads": 32, "kv_num_heads": 8 },
|
| 1054 |
"inputs": {
|
|
@@ -1091,7 +1086,7 @@
|
|
| 1091 |
"name": "bias-headsink-prefill-h32kv8-d128-s512-generic-pathology",
|
| 1092 |
"preset": "stress",
|
| 1093 |
"provenance": {
|
| 1094 |
-
"notes": "
|
| 1095 |
},
|
| 1096 |
"attrs": { "num_heads": 32, "kv_num_heads": 8 },
|
| 1097 |
"inputs": {
|
|
@@ -1138,9 +1133,7 @@
|
|
| 1138 |
{
|
| 1139 |
"name": "softcap-prefill-h32kv8-d128-s512-generic-pathology",
|
| 1140 |
"preset": "stress",
|
| 1141 |
-
"provenance": {
|
| 1142 |
-
"notes": "Production prefill with model-used score soft-capping, measuring the capped-score semantic on the past-KV contract."
|
| 1143 |
-
},
|
| 1144 |
"attrs": { "num_heads": 32, "kv_num_heads": 8, "softcap": 30 },
|
| 1145 |
"inputs": {
|
| 1146 |
"queryT": {
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"tunableSpace": {
|
| 3 |
"PREFILL_QUERY_TILE": [16, 32],
|
| 4 |
"PREFILL_LANES_PER_QUERY": [4, 8],
|
|
|
|
| 133 |
]
|
| 134 |
},
|
| 135 |
"provenance": {
|
| 136 |
+
"notes": "Long-context decode yields 64 candidate K partitions, exceeding the 16-partition cap and exercising capped split parallelism plus its merge at head size 128."
|
| 137 |
}
|
| 138 |
},
|
| 139 |
{
|
|
|
|
| 710 |
{
|
| 711 |
"name": "quant-int8-prefill-h32kv8-d128-s512-pathology",
|
| 712 |
"preset": "stress",
|
| 713 |
+
"provenance": { "notes": "A production-sized prefill exercises the int8 KV-cache attention contract." },
|
|
|
|
|
|
|
| 714 |
"attrs": {
|
| 715 |
"num_heads": 32,
|
| 716 |
"kv_num_heads": 8,
|
|
|
|
| 765 |
{
|
| 766 |
"name": "quant-int4-prefill-h32kv8-d128-s512-pathology",
|
| 767 |
"preset": "stress",
|
| 768 |
+
"provenance": { "notes": "A production-sized prefill exercises the packed int4 KV-cache attention contract." },
|
|
|
|
|
|
|
| 769 |
"attrs": {
|
| 770 |
"num_heads": 32,
|
| 771 |
"kv_num_heads": 8,
|
|
|
|
| 1043 |
"name": "headsink-prefill-h32kv8-d128-s512-generic-pathology",
|
| 1044 |
"preset": "stress",
|
| 1045 |
"provenance": {
|
| 1046 |
+
"notes": "A Llama-sized GQA prefill exercises smooth-softmax head sinks on a production attention shape."
|
| 1047 |
},
|
| 1048 |
"attrs": { "num_heads": 32, "kv_num_heads": 8 },
|
| 1049 |
"inputs": {
|
|
|
|
| 1086 |
"name": "bias-headsink-prefill-h32kv8-d128-s512-generic-pathology",
|
| 1087 |
"preset": "stress",
|
| 1088 |
"provenance": {
|
| 1089 |
+
"notes": "A production prefill combining additive bias with smooth-softmax head sinks exercises the generic attention route."
|
| 1090 |
},
|
| 1091 |
"attrs": { "num_heads": 32, "kv_num_heads": 8 },
|
| 1092 |
"inputs": {
|
|
|
|
| 1133 |
{
|
| 1134 |
"name": "softcap-prefill-h32kv8-d128-s512-generic-pathology",
|
| 1135 |
"preset": "stress",
|
| 1136 |
+
"provenance": { "notes": "A production prefill exercises model-used score soft-capping on the past-KV contract." },
|
|
|
|
|
|
|
| 1137 |
"attrs": { "num_heads": 32, "kv_num_heads": 8, "softcap": 30 },
|
| 1138 |
"inputs": {
|
| 1139 |
"queryT": {
|
build/webgpu/gqa-attention.wgsl.jinja
CHANGED
|
@@ -73,9 +73,9 @@ var<workgroup> m_sh: array<f32, 32u>;
|
|
| 73 |
var<workgroup> l_sh: array<f32, 32u>;
|
| 74 |
|
| 75 |
@compute @workgroup_size(WG)
|
| 76 |
-
fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(
|
| 77 |
// One workgroup per query; fold large query grids over x/y.
|
| 78 |
-
let qi = wid.x + wid.y *
|
| 79 |
let total = params.batch * Q_HEADS * params.qSeq;
|
| 80 |
if (qi >= total) { return; }
|
| 81 |
let lane = lid.x;
|
|
@@ -178,7 +178,10 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(num_workgroups) nwg: vec
|
|
| 178 |
smoothTerm = exp(sink - finalM);
|
| 179 |
}
|
| 180 |
let accScale = exp(gm - finalM);
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
// Cross-lane sum of the rescaled V accumulators, written cooperatively.
|
| 184 |
let oBase = (b * params.qSeq + s) * Q_HIDDEN + h * HEAD_DIM;
|
|
@@ -190,13 +193,13 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(num_workgroups) nwg: vec
|
|
| 190 |
}
|
| 191 |
{% else %}
|
| 192 |
@compute @workgroup_size(WG)
|
| 193 |
-
fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(
|
| 194 |
// One thread per query; the grid is 2D-folded so the workgroup count stays
|
| 195 |
-
// within
|
| 196 |
-
//
|
| 197 |
-
//
|
| 198 |
-
//
|
| 199 |
-
let qi = (wid.x + wid.y *
|
| 200 |
let total = params.batch * Q_HEADS * params.qSeq;
|
| 201 |
if (qi >= total) { return; }
|
| 202 |
let s = qi % params.qSeq;
|
|
@@ -277,7 +280,9 @@ fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(num_workgroups) nwg: vec
|
|
| 277 |
smoothTerm = exp(sink - finalM);
|
| 278 |
}
|
| 279 |
let accScale = exp(m - finalM);
|
| 280 |
-
|
|
|
|
|
|
|
| 281 |
|
| 282 |
let oBase = (b * params.qSeq + s) * Q_HIDDEN + h * HEAD_DIM;
|
| 283 |
for (var d = 0u; d < HEAD_DIM; d = d + 1u) { output[oBase + d] = {{ IO }}(acc[d] * invDenom); }
|
|
|
|
| 73 |
var<workgroup> l_sh: array<f32, 32u>;
|
| 74 |
|
| 75 |
@compute @workgroup_size(WG)
|
| 76 |
+
fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
| 77 |
// One workgroup per query; fold large query grids over x/y.
|
| 78 |
+
let qi = wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 79 |
let total = params.batch * Q_HEADS * params.qSeq;
|
| 80 |
if (qi >= total) { return; }
|
| 81 |
let lane = lid.x;
|
|
|
|
| 178 |
smoothTerm = exp(sink - finalM);
|
| 179 |
}
|
| 180 |
let accScale = exp(gm - finalM);
|
| 181 |
+
// A query whose every key is masked out leaves the denominator at zero (no
|
| 182 |
+
// head sink to carry mass); emit a zero row instead of 0 * (1/0) = NaN.
|
| 183 |
+
let denom = smoothTerm + glsum * accScale;
|
| 184 |
+
let invDenom = select(0.0, accScale / denom, denom > 0.0);
|
| 185 |
|
| 186 |
// Cross-lane sum of the rescaled V accumulators, written cooperatively.
|
| 187 |
let oBase = (b * params.qSeq + s) * Q_HIDDEN + h * HEAD_DIM;
|
|
|
|
| 193 |
}
|
| 194 |
{% else %}
|
| 195 |
@compute @workgroup_size(WG)
|
| 196 |
+
fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
| 197 |
// One thread per query; the grid is 2D-folded so the workgroup count stays
|
| 198 |
+
// within the per-axis dispatch fold width for large prefills. Reconstruct the flat
|
| 199 |
+
// thread index from the folded workgroup grid rather than from global_invocation_id,
|
| 200 |
+
// which only spans the clamped axis and would drop the y-folded tail. With no fold
|
| 201 |
+
// wid.y is zero, so this reduces to the ordinary flat index.
|
| 202 |
+
let qi = (wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u) * WG + lid.x;
|
| 203 |
let total = params.batch * Q_HEADS * params.qSeq;
|
| 204 |
if (qi >= total) { return; }
|
| 205 |
let s = qi % params.qSeq;
|
|
|
|
| 280 |
smoothTerm = exp(sink - finalM);
|
| 281 |
}
|
| 282 |
let accScale = exp(m - finalM);
|
| 283 |
+
// Same all-masked guard as the cooperative epilogue above.
|
| 284 |
+
let denom = smoothTerm + l * accScale;
|
| 285 |
+
let invDenom = select(0.0, accScale / denom, denom > 0.0);
|
| 286 |
|
| 287 |
let oBase = (b * params.qSeq + s) * Q_HIDDEN + h * HEAD_DIM;
|
| 288 |
for (var d = 0u; d < HEAD_DIM; d = d + 1u) { output[oBase + d] = {{ IO }}(acc[d] * invDenom); }
|
build/webgpu/gqa-present.wgsl.jinja
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
{% if
|
| 2 |
-
{% set mode =
|
| 3 |
{% elif mode is not defined %}
|
| 4 |
{% set mode = "transpose" %}
|
| 5 |
{% endif %}
|
|
@@ -18,7 +18,8 @@ const WG: u32 = {{ copyWorkgroupSize }}u;
|
|
| 18 |
{% if presentVec4 %}
|
| 19 |
// Transpose copy [batch, token, head*headDim] -> present [batch, head, token,
|
| 20 |
// headDim]. The headDim row is contiguous on both sides and divisible by four,
|
| 21 |
-
// so each invocation copies
|
|
|
|
| 22 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 23 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
| 24 |
{% else %}
|
|
@@ -28,12 +29,11 @@ const KV_HIDDEN: u32 = {{ kvHidden }}u;
|
|
| 28 |
|
| 29 |
@compute @workgroup_size(WG, 1, 1)
|
| 30 |
fn main(
|
| 31 |
-
@builtin(global_invocation_id) gid: vec3<u32>
|
| 32 |
-
@builtin(num_workgroups) nwg: vec3<u32>
|
| 33 |
) {
|
| 34 |
// The dispatch folds oversized one-dimensional grids into x/y. Rebuild the
|
| 35 |
// flat invocation index; this reduces to gid.x when no fold is needed.
|
| 36 |
-
let index = gid.x + gid.y *
|
| 37 |
{% if presentVec4 %}
|
| 38 |
let total = params.batchSize * KV_HEADS * params.kvSeq * HEAD_DIM_V4;
|
| 39 |
if (index >= total) {
|
|
@@ -111,11 +111,9 @@ fn vscale(d: u32, hk: u32) -> f32 { return v_scale[select(0u, hk * HEAD_DIM + d,
|
|
| 111 |
{% endif %}
|
| 112 |
|
| 113 |
@compute @workgroup_size(WG)
|
| 114 |
-
fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
| 115 |
-
{% endif %}{% if cooperativeMode %}@builtin(workgroup_id) wid: vec3<u32>,
|
| 116 |
-
@builtin(local_invocation_id) lid: vec3<u32>
|
| 117 |
-
{% endif %}
|
| 118 |
-
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 119 |
{% macro append_cooperative_walk() %}
|
| 120 |
// Compact cooperative append walk: the thread range spans only the appended
|
| 121 |
// window (batch x kvHead x keySeq rows), not the full cache capacity; the
|
|
@@ -155,7 +153,7 @@ fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 155 |
{% endmacro %}
|
| 156 |
{% macro append_per_row_walk() %}
|
| 157 |
// Per-row append walk: one thread streams its row's contiguous bytes.
|
| 158 |
-
let i = gid.x + gid.y *
|
| 159 |
if (i >= params.count) { return; }
|
| 160 |
let j = i % params.keySeq;
|
| 161 |
let tmp = i / params.keySeq;
|
|
@@ -195,7 +193,7 @@ fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 195 |
// cooperatively: at step s lane l touches element s*WG + l, so adjacent
|
| 196 |
// lanes hit adjacent addresses on both source and destination. Same
|
| 197 |
// dispatch geometry (ceil(rows/WG) workgroups, folded into x/y).
|
| 198 |
-
let wgFlat = wid.x + wid.y *
|
| 199 |
let elemBase = wgFlat * (WG * HEAD_DIM);
|
| 200 |
let totalElems = params.count * HEAD_DIM;
|
| 201 |
{% endif %}
|
|
@@ -265,7 +263,7 @@ fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 265 |
{% endif %}
|
| 266 |
{% elif mode == "copy" %}
|
| 267 |
// Thread i streams one contiguous (batch, kvHead, token) row.
|
| 268 |
-
let i = gid.x + gid.y *
|
| 269 |
if (i >= params.count) { return; }
|
| 270 |
let base = i * HEAD_DIM;
|
| 271 |
for (var d = 0u; d < HEAD_DIM; d = d + 1u) {
|
|
@@ -274,7 +272,7 @@ fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 274 |
}
|
| 275 |
{% elif mode == "merge" %}
|
| 276 |
// Per-row merge walk.
|
| 277 |
-
let i = gid.x + gid.y *
|
| 278 |
if (i >= params.count) { return; }
|
| 279 |
let t = i % params.seq;
|
| 280 |
let tmp = i / params.seq;
|
|
@@ -298,7 +296,7 @@ fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 298 |
{% elif mode == "merge_share" %}
|
| 299 |
// Past and present share the full-capacity stride, so outside-window rows
|
| 300 |
// copy at the same index.
|
| 301 |
-
let i = gid.x + gid.y *
|
| 302 |
if (i >= params.count) { return; }
|
| 303 |
let t = i % params.seq;
|
| 304 |
let tmp = i / params.seq;
|
|
@@ -338,7 +336,7 @@ fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 338 |
// arithmetic. Only RoPE would need the true absolute position, which is why
|
| 339 |
// `windowShiftOk` refuses a rotary request outright rather than silently
|
| 340 |
// rotating at the cache row.
|
| 341 |
-
let i = gid.x + gid.y *
|
| 342 |
if (i >= params.count) { return; }
|
| 343 |
let t = i % params.seq;
|
| 344 |
let tmp = i / params.seq;
|
|
@@ -377,7 +375,7 @@ fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 377 |
{% else %}
|
| 378 |
// The dispatch folds oversized one-dimensional grids into x/y. Rebuild the
|
| 379 |
// flat invocation index; this reduces to gid.x when no fold is needed.
|
| 380 |
-
let i = gid.x + gid.y *
|
| 381 |
if (i >= params.count) { return; }
|
| 382 |
let t = i % params.seq;
|
| 383 |
let tmp = i / params.seq;
|
|
|
|
| 1 |
+
{% if modeSpec is defined %}
|
| 2 |
+
{% set mode = modeSpec %}
|
| 3 |
{% elif mode is not defined %}
|
| 4 |
{% set mode = "transpose" %}
|
| 5 |
{% endif %}
|
|
|
|
| 18 |
{% if presentVec4 %}
|
| 19 |
// Transpose copy [batch, token, head*headDim] -> present [batch, head, token,
|
| 20 |
// headDim]. The headDim row is contiguous on both sides and divisible by four,
|
| 21 |
+
// so each invocation copies one vec4 along d and adjacent invocations write
|
| 22 |
+
// adjacent destination vectors.
|
| 23 |
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 24 |
const KV_HIDDEN_V4: u32 = {{ kvHiddenV4 }}u;
|
| 25 |
{% else %}
|
|
|
|
| 29 |
|
| 30 |
@compute @workgroup_size(WG, 1, 1)
|
| 31 |
fn main(
|
| 32 |
+
@builtin(global_invocation_id) gid: vec3<u32>
|
|
|
|
| 33 |
) {
|
| 34 |
// The dispatch folds oversized one-dimensional grids into x/y. Rebuild the
|
| 35 |
// flat invocation index; this reduces to gid.x when no fold is needed.
|
| 36 |
+
let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 37 |
{% if presentVec4 %}
|
| 38 |
let total = params.batchSize * KV_HEADS * params.kvSeq * HEAD_DIM_V4;
|
| 39 |
if (index >= total) {
|
|
|
|
| 111 |
{% endif %}
|
| 112 |
|
| 113 |
@compute @workgroup_size(WG)
|
| 114 |
+
fn main({% if needsGid %}@builtin(global_invocation_id) gid: vec3<u32>{% if cooperativeMode %},
|
| 115 |
+
{% endif %}{% endif %}{% if cooperativeMode %}@builtin(workgroup_id) wid: vec3<u32>,
|
| 116 |
+
@builtin(local_invocation_id) lid: vec3<u32>{% endif %}) {
|
|
|
|
|
|
|
| 117 |
{% macro append_cooperative_walk() %}
|
| 118 |
// Compact cooperative append walk: the thread range spans only the appended
|
| 119 |
// window (batch x kvHead x keySeq rows), not the full cache capacity; the
|
|
|
|
| 153 |
{% endmacro %}
|
| 154 |
{% macro append_per_row_walk() %}
|
| 155 |
// Per-row append walk: one thread streams its row's contiguous bytes.
|
| 156 |
+
let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 157 |
if (i >= params.count) { return; }
|
| 158 |
let j = i % params.keySeq;
|
| 159 |
let tmp = i / params.keySeq;
|
|
|
|
| 193 |
// cooperatively: at step s lane l touches element s*WG + l, so adjacent
|
| 194 |
// lanes hit adjacent addresses on both source and destination. Same
|
| 195 |
// dispatch geometry (ceil(rows/WG) workgroups, folded into x/y).
|
| 196 |
+
let wgFlat = wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 197 |
let elemBase = wgFlat * (WG * HEAD_DIM);
|
| 198 |
let totalElems = params.count * HEAD_DIM;
|
| 199 |
{% endif %}
|
|
|
|
| 263 |
{% endif %}
|
| 264 |
{% elif mode == "copy" %}
|
| 265 |
// Thread i streams one contiguous (batch, kvHead, token) row.
|
| 266 |
+
let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 267 |
if (i >= params.count) { return; }
|
| 268 |
let base = i * HEAD_DIM;
|
| 269 |
for (var d = 0u; d < HEAD_DIM; d = d + 1u) {
|
|
|
|
| 272 |
}
|
| 273 |
{% elif mode == "merge" %}
|
| 274 |
// Per-row merge walk.
|
| 275 |
+
let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 276 |
if (i >= params.count) { return; }
|
| 277 |
let t = i % params.seq;
|
| 278 |
let tmp = i / params.seq;
|
|
|
|
| 296 |
{% elif mode == "merge_share" %}
|
| 297 |
// Past and present share the full-capacity stride, so outside-window rows
|
| 298 |
// copy at the same index.
|
| 299 |
+
let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 300 |
if (i >= params.count) { return; }
|
| 301 |
let t = i % params.seq;
|
| 302 |
let tmp = i / params.seq;
|
|
|
|
| 336 |
// arithmetic. Only RoPE would need the true absolute position, which is why
|
| 337 |
// `windowShiftOk` refuses a rotary request outright rather than silently
|
| 338 |
// rotating at the cache row.
|
| 339 |
+
let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 340 |
if (i >= params.count) { return; }
|
| 341 |
let t = i % params.seq;
|
| 342 |
let tmp = i / params.seq;
|
|
|
|
| 375 |
{% else %}
|
| 376 |
// The dispatch folds oversized one-dimensional grids into x/y. Rebuild the
|
| 377 |
// flat invocation index; this reduces to gid.x when no fold is needed.
|
| 378 |
+
let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 379 |
if (i >= params.count) { return; }
|
| 380 |
let t = i % params.seq;
|
| 381 |
let tmp = i / params.seq;
|
build/webgpu/gqa-qprep.wgsl.jinja
CHANGED
|
@@ -1,18 +1,12 @@
|
|
| 1 |
{% if usesF16 is defined and usesF16 %}enable f16;
|
| 2 |
{% endif %}{{ env.wgsl.resourceDeclarations }}
|
| 3 |
|
| 4 |
-
//
|
| 5 |
-
//
|
| 6 |
-
// absolute position
|
| 7 |
-
//
|
| 8 |
-
//
|
| 9 |
-
//
|
| 10 |
-
//
|
| 11 |
-
// f16 queries are widened before cos/sin and norm/rotary arithmetic, and the
|
| 12 |
-
// rotation itself always runs in f32 — RoPE precision matters at large
|
| 13 |
-
// positions. The store narrows only for a consumer whose operand tiles are
|
| 14 |
-
// f16 anyway (the subgroup-matrix route), where a wider intermediate would be
|
| 15 |
-
// narrowed at staging regardless; every other consumer keeps the f32 output.
|
| 16 |
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 17 |
const HEAD_DIM_V4: u32 = {{ headDim }}u / 4u;
|
| 18 |
const Q_HEADS: u32 = {{ qHeads }}u;
|
|
@@ -26,11 +20,10 @@ const QK_EPS: f32 = {{ qkEps }};
|
|
| 26 |
{% endif %}
|
| 27 |
|
| 28 |
@compute @workgroup_size(WG)
|
| 29 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 30 |
-
|
| 31 |
-
//
|
| 32 |
-
|
| 33 |
-
let qi = gid.x + gid.y * nwg.x * WG;
|
| 34 |
let total = params.batch * Q_HEADS * params.qSeq;
|
| 35 |
if (qi >= total) { return; }
|
| 36 |
let s = qi % params.qSeq;
|
|
|
|
| 1 |
{% if usesF16 is defined and usesF16 %}enable f16;
|
| 2 |
{% endif %}{{ env.wgsl.resourceDeclarations }}
|
| 3 |
|
| 4 |
+
// One invocation per (batch, query head, query token) applies optional per-head
|
| 5 |
+
// RMS normalization followed by NeoX half-split rotary embedding at the query's
|
| 6 |
+
// absolute position. Present-cache keys are already transformed, so this pass
|
| 7 |
+
// writes only Q in BSH layout. f16 queries are widened for normalization,
|
| 8 |
+
// trigonometry, and rotation. The result narrows only when the following
|
| 9 |
+
// attention pass consumes f16 operand tiles; other configurations store f32.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 11 |
const HEAD_DIM_V4: u32 = {{ headDim }}u / 4u;
|
| 12 |
const Q_HEADS: u32 = {{ qHeads }}u;
|
|
|
|
| 20 |
{% endif %}
|
| 21 |
|
| 22 |
@compute @workgroup_size(WG)
|
| 23 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 24 |
+
// 2D-folded flat index: gid.y carries the high bits past the per-axis dispatch fold width.
|
| 25 |
+
// Reduces to gid.x when the dispatch does not fold.
|
| 26 |
+
let qi = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
|
|
|
| 27 |
let total = params.batch * Q_HEADS * params.qSeq;
|
| 28 |
if (qi >= total) { return; }
|
| 29 |
let s = qi % params.qSeq;
|
build/webgpu/manifest.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,29 +1,99 @@
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.GroupQueryAttention",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"attention-rank4-tiled.wgsl.jinja": "
|
| 11 |
-
"attn-flash-decode-splitk-merge.wgsl.jinja": "
|
| 12 |
-
"attn-flash-decode-splitk.wgsl.jinja": "
|
| 13 |
-
"attn-flash-online.wgsl.jinja": "
|
| 14 |
-
"attn-flash-prefill-cluster.wgsl.jinja": "
|
| 15 |
-
"attn-flash-q32-broadcast.wgsl.jinja": "
|
| 16 |
-
"attn-materialized-rowstats-combine-f32.wgsl.jinja": "
|
| 17 |
-
"attn-materialized-sgmat-f32.wgsl.jinja": "
|
| 18 |
-
"attn-online-scalar.wgsl.jinja": "
|
| 19 |
-
"bench.json": "
|
| 20 |
-
"gqa-attention.wgsl.jinja": "
|
| 21 |
-
"gqa-present.wgsl.jinja": "
|
| 22 |
-
"gqa-qprep.wgsl.jinja": "
|
| 23 |
-
"manifest.json": "
|
| 24 |
-
"test.json": "
|
| 25 |
}
|
| 26 |
},
|
| 27 |
-
"provenance": { "kernel": { "sha": "
|
| 28 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.GroupQueryAttention",
|
| 3 |
+
"id": "_com_microsoft_groupqueryattention_webgpu_1ea0022",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"attention-rank4-tiled.wgsl.jinja": "gYl58ecIVTxJJkvnK/6lNcbPKaRT24cfz5Yx0MxxxxE=",
|
| 11 |
+
"attn-flash-decode-splitk-merge.wgsl.jinja": "y3ubiijNd5rGJa2KVdluBdaoKYAGeqrPVd6KSppJPHw=",
|
| 12 |
+
"attn-flash-decode-splitk.wgsl.jinja": "xRRcDB3IGuE25xq3sG1qVPHRsESDmfA15KFVKlq5l4w=",
|
| 13 |
+
"attn-flash-online.wgsl.jinja": "ontnzJw9RHN7DklqqyavsAiRuKrsnWuEiU+vllbKfGE=",
|
| 14 |
+
"attn-flash-prefill-cluster.wgsl.jinja": "UHb5IdDeGHB/0gsQdAHcfg9FI73nyzk3zoZiIhaWbgI=",
|
| 15 |
+
"attn-flash-q32-broadcast.wgsl.jinja": "U/O9TNr3pXvFP+VZ2EAJLuXaWSz62De0TQEmEiU7TLc=",
|
| 16 |
+
"attn-materialized-rowstats-combine-f32.wgsl.jinja": "zcR02XUVlPyYeLeWH7gLOX65BcozZ1qje9hSIZSOaBA=",
|
| 17 |
+
"attn-materialized-sgmat-f32.wgsl.jinja": "rNruz2BmIcsQeu5eUnQZh41ftfDt/PjqnZiy2XT+9bk=",
|
| 18 |
+
"attn-online-scalar.wgsl.jinja": "AJGUYoMkrTHnwCPxu1gVp2CIrdHlca5PMgr9mgqukz4=",
|
| 19 |
+
"bench.json": "bUGLPxdrsT20K/2ptNbqVs+28i4d2W/9vOyPld+joKs=",
|
| 20 |
+
"gqa-attention.wgsl.jinja": "9JBHgllUq4dekL6Y1uXkyi2NoEYKjHTkHqnhktbgdzY=",
|
| 21 |
+
"gqa-present.wgsl.jinja": "/63mkauF/TKCsH4dxgIVlTZnREzSd/j64WGkvu6i2ls=",
|
| 22 |
+
"gqa-qprep.wgsl.jinja": "kRFlYxr5SdQJlbjmcF/Ii+0w56q0xhF8jyAOKPNx47I=",
|
| 23 |
+
"manifest.json": "U9Xgwr8xQCIMv/BNUNjiaMDyUjEVXkawKtl5Bsnn4UA=",
|
| 24 |
+
"test.json": "GGjXGSZThloU2mCpXKJf9lJVgddpxc8ouDX+JRD5zTs="
|
| 25 |
}
|
| 26 |
},
|
| 27 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 28 |
+
"webgpu": {
|
| 29 |
+
"manifestSpec": "2.0",
|
| 30 |
+
"variants": {
|
| 31 |
+
"qkv_present_materialized_sgmat_f32": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 32 |
+
"past_kv_materialized_sgmat_f32": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 33 |
+
"past_kv_bias_materialized_sgmat_f32": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 34 |
+
"past_kv_materialized_sgmat_f16": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 35 |
+
"past_kv_rotary_materialized_sgmat_f32": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja", "gqa-qprep.wgsl.jinja"],
|
| 36 |
+
"past_kv_rotary_materialized_sgmat_f16": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja", "gqa-qprep.wgsl.jinja"],
|
| 37 |
+
"new_kv_past_materialized_sgmat_f32": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 38 |
+
"window_shift_materialized_sgmat_f32": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 39 |
+
"share_append_materialized_sgmat_f32": ["attn-materialized-rowstats-combine-f32.wgsl.jinja", "attn-materialized-sgmat-f32.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 40 |
+
"new_kv_share_append_split": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 41 |
+
"new_kv_share_append_headsink_split": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 42 |
+
"new_kv_share_append_rotary_split": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 43 |
+
"qkv_present_tiled_nosg": ["attention-rank4-tiled.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 44 |
+
"qkv_present_flash": ["attn-flash-online.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 45 |
+
"qkv_present": ["attn-online-scalar.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 46 |
+
"quant_int8": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 47 |
+
"quant_int4": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 48 |
+
"quant_int8_decode_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 49 |
+
"qkv_present_flash_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 50 |
+
"qkv_present_flash_cluster": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 51 |
+
"past_kv_bias_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 52 |
+
"past_kv_qnorm_rotary_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja", "gqa-qprep.wgsl.jinja"],
|
| 53 |
+
"quant_int8_decode_splitk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 54 |
+
"qkv_present_flash_splitk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 55 |
+
"qkv_present_flash_cluster_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 56 |
+
"past_kv_bias_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 57 |
+
"past_kv_qnorm_rotary_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja", "gqa-qprep.wgsl.jinja"],
|
| 58 |
+
"past_kv_decode_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 59 |
+
"new_kv_past_decode_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 60 |
+
"window_shift_decode_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 61 |
+
"past_kv_decode_splitk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 62 |
+
"new_kv_past_decode_splitk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 63 |
+
"window_shift_decode_splitk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 64 |
+
"past_kv_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 65 |
+
"new_kv_past_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 66 |
+
"past_kv_rotary_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 67 |
+
"past_kv_softcap_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 68 |
+
"past_kv_headsink_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 69 |
+
"past_kv_bias_headsink_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 70 |
+
"window_shift_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 71 |
+
"past_kv_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 72 |
+
"new_kv_past_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 73 |
+
"past_kv_rotary_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 74 |
+
"past_kv_softcap_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 75 |
+
"past_kv_headsink_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 76 |
+
"past_kv_bias_headsink_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 77 |
+
"window_shift_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 78 |
+
"qkv_present_flash_q32_broadcast": ["attn-flash-q32-broadcast.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 79 |
+
"qkv_present_flash_q32_shared": ["attn-flash-q32-broadcast.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 80 |
+
"past_kv": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 81 |
+
"past_kv_rotary": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 82 |
+
"past_kv_qnorm_rotary": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 83 |
+
"new_kv_past": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 84 |
+
"window_shift_append": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 85 |
+
"new_kv_qnorm_rotary": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 86 |
+
"past_kv_bias": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 87 |
+
"past_kv_headsink": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 88 |
+
"past_kv_bias_headsink": ["gqa-attention.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 89 |
+
"quant_int8_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 90 |
+
"quant_int4_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 91 |
+
"quant_int8_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 92 |
+
"quant_int4_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 93 |
+
"share_append_split_decode_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 94 |
+
"share_append_split_decode_splitk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 95 |
+
"share_append_split_flash_prefill": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"],
|
| 96 |
+
"share_append_split_flash_prefill_nosg": ["attn-flash-prefill-cluster.wgsl.jinja", "gqa-present.wgsl.jinja"]
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
}
|
build/webgpu/test.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "com.microsoft.GroupQueryAttention",
|
| 3 |
"fixtureArrays": {
|
| 4 |
"ort_past_key_t_pattern": [0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8],
|
| 5 |
"ort_past_value_t_pattern": [0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3],
|
|
@@ -247,7 +246,7 @@
|
|
| 247 |
"valueT": {
|
| 248 |
"dtype": "float32",
|
| 249 |
"shape": [1, 512, 64],
|
| 250 |
-
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.
|
| 251 |
},
|
| 252 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 253 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
@@ -467,7 +466,7 @@
|
|
| 467 |
"valueT": {
|
| 468 |
"dtype": "float32",
|
| 469 |
"shape": [1, 2048, 128],
|
| 470 |
-
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.
|
| 471 |
},
|
| 472 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2047] } },
|
| 473 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2048] } }
|
|
@@ -495,7 +494,7 @@
|
|
| 495 |
"valueT": {
|
| 496 |
"dtype": "float32",
|
| 497 |
"shape": [1, 1024, 64],
|
| 498 |
-
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.
|
| 499 |
},
|
| 500 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 501 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
@@ -523,15 +522,15 @@
|
|
| 523 |
"valueT": {
|
| 524 |
"dtype": "float16",
|
| 525 |
"shape": [1, 128, 64],
|
| 526 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 527 |
},
|
| 528 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [127] } },
|
| 529 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [128] } }
|
| 530 |
},
|
| 531 |
"outputs": {
|
| 532 |
-
"outputT": { "dtype": "float16", "shape": [1, 128, 128], "tolerance": 0.
|
| 533 |
-
"presentKeyT": { "dtype": "float16", "shape": [1, 1, 128, 64], "tolerance": 0.
|
| 534 |
-
"presentValueT": { "dtype": "float16", "shape": [1, 1, 128, 64], "tolerance": 0.
|
| 535 |
}
|
| 536 |
},
|
| 537 |
{
|
|
@@ -551,15 +550,15 @@
|
|
| 551 |
"valueT": {
|
| 552 |
"dtype": "float16",
|
| 553 |
"shape": [1, 512, 64],
|
| 554 |
-
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.
|
| 555 |
},
|
| 556 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 557 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
| 558 |
},
|
| 559 |
"outputs": {
|
| 560 |
-
"outputT": { "dtype": "float16", "shape": [1, 1, 256], "tolerance": 0.
|
| 561 |
-
"presentKeyT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.
|
| 562 |
-
"presentValueT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.
|
| 563 |
}
|
| 564 |
},
|
| 565 |
{
|
|
@@ -579,15 +578,15 @@
|
|
| 579 |
"valueT": {
|
| 580 |
"dtype": "float16",
|
| 581 |
"shape": [1, 2048, 128],
|
| 582 |
-
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.
|
| 583 |
},
|
| 584 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2047] } },
|
| 585 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2048] } }
|
| 586 |
},
|
| 587 |
"outputs": {
|
| 588 |
-
"outputT": { "dtype": "float16", "shape": [1, 1, 512], "tolerance": 0.
|
| 589 |
-
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 2048, 64], "tolerance": 0.
|
| 590 |
-
"presentValueT": { "dtype": "float16", "shape": [1, 2, 2048, 64], "tolerance": 0.
|
| 591 |
}
|
| 592 |
},
|
| 593 |
{
|
|
@@ -691,7 +690,7 @@
|
|
| 691 |
"valueT": {
|
| 692 |
"dtype": "float32",
|
| 693 |
"shape": [1, 1024, 128],
|
| 694 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 695 |
},
|
| 696 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 697 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
@@ -1210,7 +1209,7 @@
|
|
| 1210 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0038802] } }
|
| 1211 |
},
|
| 1212 |
"outputs": {
|
| 1213 |
-
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.
|
| 1214 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1215 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1216 |
}
|
|
@@ -1276,7 +1275,7 @@
|
|
| 1276 |
}
|
| 1277 |
},
|
| 1278 |
"outputs": {
|
| 1279 |
-
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.
|
| 1280 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1281 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1282 |
}
|
|
@@ -1381,7 +1380,7 @@
|
|
| 1381 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0703979] } }
|
| 1382 |
},
|
| 1383 |
"outputs": {
|
| 1384 |
-
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.
|
| 1385 |
"presentKeyT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 },
|
| 1386 |
"presentValueT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 }
|
| 1387 |
}
|
|
@@ -1447,7 +1446,7 @@
|
|
| 1447 |
}
|
| 1448 |
},
|
| 1449 |
"outputs": {
|
| 1450 |
-
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.
|
| 1451 |
"presentKeyT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 },
|
| 1452 |
"presentValueT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 }
|
| 1453 |
}
|
|
@@ -1508,7 +1507,7 @@
|
|
| 1508 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0039328] } }
|
| 1509 |
},
|
| 1510 |
"outputs": {
|
| 1511 |
-
"outputT": { "dtype": "float32", "shape": [2, 4, 64], "tolerance": 0.
|
| 1512 |
"presentKeyT": { "dtype": "int8", "shape": [2, 2, 4, 16], "tolerance": 0 },
|
| 1513 |
"presentValueT": { "dtype": "int8", "shape": [2, 2, 4, 16], "tolerance": 0 }
|
| 1514 |
}
|
|
@@ -1563,7 +1562,7 @@
|
|
| 1563 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0039328] } }
|
| 1564 |
},
|
| 1565 |
"outputs": {
|
| 1566 |
-
"outputT": { "dtype": "float32", "shape": [1, 8, 128], "tolerance": 0.
|
| 1567 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 8, 64], "tolerance": 0 },
|
| 1568 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 8, 64], "tolerance": 0 }
|
| 1569 |
}
|
|
@@ -1624,7 +1623,7 @@
|
|
| 1624 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0703979] } }
|
| 1625 |
},
|
| 1626 |
"outputs": {
|
| 1627 |
-
"outputT": { "dtype": "float32", "shape": [1, 4, 64], "tolerance": 0.
|
| 1628 |
"presentKeyT": { "dtype": "uint8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1629 |
"presentValueT": { "dtype": "uint8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1630 |
}
|
|
@@ -1803,7 +1802,7 @@
|
|
| 1803 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0038802] } }
|
| 1804 |
},
|
| 1805 |
"outputs": {
|
| 1806 |
-
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.
|
| 1807 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1808 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1809 |
}
|
|
@@ -1849,6 +1848,102 @@
|
|
| 1849 |
"presentValueT": { "dtype": "float32", "shape": [1, 1, 8, 8], "tolerance": 0.0001 }
|
| 1850 |
}
|
| 1851 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1852 |
{
|
| 1853 |
"name": "ort_bias_bcast_prompt_b1q8p8_h2kv1d8",
|
| 1854 |
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
|
@@ -2002,7 +2097,7 @@
|
|
| 2002 |
{
|
| 2003 |
"name": "flashprefill_pastzero_h8kv2_d128_q16",
|
| 2004 |
"provenance": {
|
| 2005 |
-
"notes": "
|
| 2006 |
},
|
| 2007 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.08838834764831845 },
|
| 2008 |
"inputs": {
|
|
@@ -2051,7 +2146,7 @@
|
|
| 2051 |
"pastValueT": {
|
| 2052 |
"dtype": "float32",
|
| 2053 |
"shape": [1, 2, 48, 64],
|
| 2054 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2055 |
},
|
| 2056 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [47] } },
|
| 2057 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [48] } }
|
|
@@ -2136,7 +2231,7 @@
|
|
| 2136 |
"name": "qnorm_rotary_scalar_h8kv2_d256_q1",
|
| 2137 |
"provenance": {
|
| 2138 |
"source": "scope coverage",
|
| 2139 |
-
"test": "
|
| 2140 |
"notes": "headDim=256 requires 34,048 bytes for the cooperative path, exceeding a 32 KiB workgroup-storage tier and exercising the thread-per-query Q-norm route."
|
| 2141 |
},
|
| 2142 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.0625, "do_rotary": 1, "qk_norm_epsilon": 0.00001 },
|
|
@@ -2191,7 +2286,7 @@
|
|
| 2191 |
"name": "f16_qnorm_rotary_scalar_f16wts_h8kv2_d256_q1",
|
| 2192 |
"provenance": {
|
| 2193 |
"source": "scope coverage",
|
| 2194 |
-
"test": "
|
| 2195 |
"notes": "Float16 counterpart to the thread-per-query Q-norm case. It verifies explicit f32 conversion of q_norm_weight, cos_cache, and sin_cache before scalar arithmetic."
|
| 2196 |
},
|
| 2197 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.0625, "do_rotary": 1, "qk_norm_epsilon": 0.00001 },
|
|
@@ -2311,7 +2406,7 @@
|
|
| 2311 |
"pastValueT": {
|
| 2312 |
"dtype": "float32",
|
| 2313 |
"shape": [1, 2, 1024, 64],
|
| 2314 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2315 |
},
|
| 2316 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 2317 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
@@ -2325,7 +2420,7 @@
|
|
| 2325 |
{
|
| 2326 |
"name": "flashdecode_splitk_h8kv2_d128_kv1024",
|
| 2327 |
"provenance": {
|
| 2328 |
-
"notes": "
|
| 2329 |
},
|
| 2330 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.08838834764831845 },
|
| 2331 |
"inputs": {
|
|
@@ -2344,7 +2439,7 @@
|
|
| 2344 |
"pastValueT": {
|
| 2345 |
"dtype": "float32",
|
| 2346 |
"shape": [1, 2, 1024, 128],
|
| 2347 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2348 |
},
|
| 2349 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 2350 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
@@ -2374,13 +2469,13 @@
|
|
| 2374 |
"pastValueT": {
|
| 2375 |
"dtype": "float16",
|
| 2376 |
"shape": [1, 2, 512, 128],
|
| 2377 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2378 |
},
|
| 2379 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 2380 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
| 2381 |
},
|
| 2382 |
"outputs": {
|
| 2383 |
-
"outputT": { "dtype": "float16", "shape": [1, 1, 1024], "tolerance": 0.
|
| 2384 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 512, 128], "tolerance": 0.001 },
|
| 2385 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 512, 128], "tolerance": 0.001 }
|
| 2386 |
}
|
|
@@ -2404,13 +2499,13 @@
|
|
| 2404 |
"pastValueT": {
|
| 2405 |
"dtype": "float16",
|
| 2406 |
"shape": [1, 2, 1024, 128],
|
| 2407 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2408 |
},
|
| 2409 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 2410 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
| 2411 |
},
|
| 2412 |
"outputs": {
|
| 2413 |
-
"outputT": { "dtype": "float16", "shape": [1, 1, 1024], "tolerance": 0.
|
| 2414 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 1024, 128], "tolerance": 0.001 },
|
| 2415 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 1024, 128], "tolerance": 0.001 }
|
| 2416 |
}
|
|
@@ -2464,7 +2559,7 @@
|
|
| 2464 |
"pastValueT": {
|
| 2465 |
"dtype": "float16",
|
| 2466 |
"shape": [1, 2, 512, 64],
|
| 2467 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2468 |
},
|
| 2469 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 2470 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } },
|
|
@@ -2480,7 +2575,7 @@
|
|
| 2480 |
}
|
| 2481 |
},
|
| 2482 |
"outputs": {
|
| 2483 |
-
"outputT": { "dtype": "float16", "shape": [1, 1, 512], "tolerance": 0.
|
| 2484 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 512, 64], "tolerance": 0.001 },
|
| 2485 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 512, 64], "tolerance": 0.001 }
|
| 2486 |
}
|
|
@@ -2504,7 +2599,7 @@
|
|
| 2504 |
"pastValueT": {
|
| 2505 |
"dtype": "float16",
|
| 2506 |
"shape": [1, 2, 32, 64],
|
| 2507 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2508 |
},
|
| 2509 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [31] } },
|
| 2510 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
|
@@ -2544,7 +2639,7 @@
|
|
| 2544 |
"pastValueT": {
|
| 2545 |
"dtype": "float16",
|
| 2546 |
"shape": [1, 2, 32, 64],
|
| 2547 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2548 |
},
|
| 2549 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [31] } },
|
| 2550 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
|
@@ -2630,7 +2725,7 @@
|
|
| 2630 |
"valueT": {
|
| 2631 |
"dtype": "float32",
|
| 2632 |
"shape": [1, 32, 128],
|
| 2633 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2634 |
},
|
| 2635 |
"pastKeyT": {
|
| 2636 |
"dtype": "float32",
|
|
@@ -2640,7 +2735,7 @@
|
|
| 2640 |
"pastValueT": {
|
| 2641 |
"dtype": "float32",
|
| 2642 |
"shape": [1, 2, 32, 64],
|
| 2643 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2644 |
},
|
| 2645 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [63] } },
|
| 2646 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
|
@@ -2668,7 +2763,7 @@
|
|
| 2668 |
"valueT": {
|
| 2669 |
"dtype": "float32",
|
| 2670 |
"shape": [1, 1, 128],
|
| 2671 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2672 |
},
|
| 2673 |
"pastKeyT": {
|
| 2674 |
"dtype": "float32",
|
|
@@ -2678,7 +2773,7 @@
|
|
| 2678 |
"pastValueT": {
|
| 2679 |
"dtype": "float32",
|
| 2680 |
"shape": [1, 2, 1024, 64],
|
| 2681 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2682 |
},
|
| 2683 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } },
|
| 2684 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1025] } }
|
|
@@ -2706,7 +2801,7 @@
|
|
| 2706 |
"valueT": {
|
| 2707 |
"dtype": "float16",
|
| 2708 |
"shape": [1, 32, 128],
|
| 2709 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2710 |
},
|
| 2711 |
"pastKeyT": {
|
| 2712 |
"dtype": "float16",
|
|
@@ -2716,13 +2811,13 @@
|
|
| 2716 |
"pastValueT": {
|
| 2717 |
"dtype": "float16",
|
| 2718 |
"shape": [1, 2, 32, 64],
|
| 2719 |
-
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.
|
| 2720 |
},
|
| 2721 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [63] } },
|
| 2722 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
| 2723 |
},
|
| 2724 |
"outputs": {
|
| 2725 |
-
"outputT": { "dtype": "float16", "shape": [1, 32, 512], "tolerance": 0.
|
| 2726 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 64, 64], "tolerance": 0.001 },
|
| 2727 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 64, 64], "tolerance": 0.001 }
|
| 2728 |
}
|
|
@@ -2872,7 +2967,7 @@
|
|
| 2872 |
"valueT": {
|
| 2873 |
"dtype": "float32",
|
| 2874 |
"shape": [1, 511, 128],
|
| 2875 |
-
"data": { "kind": "fillFloat32", "scale": 0.08, "sinStep": 0.
|
| 2876 |
},
|
| 2877 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [510] } },
|
| 2878 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } }
|
|
@@ -2901,7 +2996,7 @@
|
|
| 2901 |
"valueT": {
|
| 2902 |
"dtype": "float32",
|
| 2903 |
"shape": [1, 64, 256],
|
| 2904 |
-
"data": { "kind": "fillFloat32", "scale": 0.08, "sinStep": 0.
|
| 2905 |
},
|
| 2906 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [63] } },
|
| 2907 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
|
@@ -2932,7 +3027,7 @@
|
|
| 2932 |
"valueT": {
|
| 2933 |
"dtype": "float32",
|
| 2934 |
"shape": [1, 512, 256],
|
| 2935 |
-
"data": { "kind": "fillFloat32", "scale": 0.08, "sinStep": 0.
|
| 2936 |
},
|
| 2937 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 2938 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
@@ -3063,7 +3158,7 @@
|
|
| 3063 |
{
|
| 3064 |
"name": "qkv_prefill_f32_h8kv2_d256_register_boundary",
|
| 3065 |
"provenance": {
|
| 3066 |
-
"notes": "
|
| 3067 |
},
|
| 3068 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.0625, "causal": 0 },
|
| 3069 |
"inputs": {
|
|
@@ -3261,7 +3356,7 @@
|
|
| 3261 |
"provenance": {
|
| 3262 |
"source": "onnxruntime/contrib_ops/webgpu/bert/flash_attention_decode_qkv.wgsl.template",
|
| 3263 |
"test": "head_size=8 split-reduce OOB race class (PR #29593)",
|
| 3264 |
-
"notes": "
|
| 3265 |
},
|
| 3266 |
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 3267 |
"inputs": {
|
|
@@ -3280,7 +3375,7 @@
|
|
| 3280 |
"pastValueT": {
|
| 3281 |
"dtype": "float32",
|
| 3282 |
"shape": [1, 2, 512, 8],
|
| 3283 |
-
"data": { "kind": "fillFloat32", "sinStep": 0.
|
| 3284 |
},
|
| 3285 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 3286 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
@@ -3296,7 +3391,7 @@
|
|
| 3296 |
"provenance": {
|
| 3297 |
"source": "onnxruntime/contrib_ops/webgpu/bert/flash_attention_decode_qkv.wgsl.template",
|
| 3298 |
"test": "head_size=16 split-reduce OOB race class (PR #29593)",
|
| 3299 |
-
"notes": "
|
| 3300 |
},
|
| 3301 |
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 3302 |
"inputs": {
|
|
@@ -3315,7 +3410,7 @@
|
|
| 3315 |
"pastValueT": {
|
| 3316 |
"dtype": "float32",
|
| 3317 |
"shape": [1, 2, 512, 16],
|
| 3318 |
-
"data": { "kind": "fillFloat32", "sinStep": 0.
|
| 3319 |
},
|
| 3320 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 3321 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
@@ -3541,7 +3636,7 @@
|
|
| 3541 |
"provenance": {
|
| 3542 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 3543 |
"test": "GQA head sink on the buffer-sharing append path",
|
| 3544 |
-
"notes": "
|
| 3545 |
}
|
| 3546 |
},
|
| 3547 |
{
|
|
@@ -3627,7 +3722,7 @@
|
|
| 3627 |
{
|
| 3628 |
"name": "prefill_tiled_q31_h8kv2_d36_non_cluster_stride",
|
| 3629 |
"provenance": {
|
| 3630 |
-
"notes": "Head size 36 is
|
| 3631 |
},
|
| 3632 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.16666666666666666, "causal": 0 },
|
| 3633 |
"inputs": {
|
|
@@ -3685,7 +3780,7 @@
|
|
| 3685 |
"presentValueT": { "dtype": "float32", "shape": [1, 2, 32, 64], "tolerance": 0.0001 }
|
| 3686 |
},
|
| 3687 |
"provenance": {
|
| 3688 |
-
"notes": "Logit soft-capping on the
|
| 3689 |
}
|
| 3690 |
},
|
| 3691 |
{
|
|
@@ -3772,7 +3867,7 @@
|
|
| 3772 |
{
|
| 3773 |
"name": "flashprefill_quant_int8_h4kv2_d64_q32",
|
| 3774 |
"provenance": {
|
| 3775 |
-
"notes": "INT8 KV cache on the tiled prefill path.
|
| 3776 |
},
|
| 3777 |
"attrs": {
|
| 3778 |
"num_heads": 4,
|
|
@@ -3883,7 +3978,7 @@
|
|
| 3883 |
"provenance": {
|
| 3884 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 3885 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 3886 |
-
"notes": "
|
| 3887 |
}
|
| 3888 |
},
|
| 3889 |
{
|
|
@@ -3918,7 +4013,7 @@
|
|
| 3918 |
"provenance": {
|
| 3919 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 3920 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 3921 |
-
"notes": "
|
| 3922 |
}
|
| 3923 |
},
|
| 3924 |
{
|
|
@@ -3953,7 +4048,7 @@
|
|
| 3953 |
"provenance": {
|
| 3954 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 3955 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 3956 |
-
"notes": "
|
| 3957 |
}
|
| 3958 |
},
|
| 3959 |
{
|
|
@@ -3988,7 +4083,7 @@
|
|
| 3988 |
"provenance": {
|
| 3989 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 3990 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 3991 |
-
"notes": "
|
| 3992 |
}
|
| 3993 |
},
|
| 3994 |
{
|
|
@@ -4023,7 +4118,7 @@
|
|
| 4023 |
"provenance": {
|
| 4024 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4025 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4026 |
-
"notes": "
|
| 4027 |
}
|
| 4028 |
},
|
| 4029 |
{
|
|
@@ -4058,7 +4153,7 @@
|
|
| 4058 |
"provenance": {
|
| 4059 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4060 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4061 |
-
"notes": "
|
| 4062 |
}
|
| 4063 |
},
|
| 4064 |
{
|
|
@@ -4093,7 +4188,7 @@
|
|
| 4093 |
"provenance": {
|
| 4094 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4095 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4096 |
-
"notes": "
|
| 4097 |
}
|
| 4098 |
},
|
| 4099 |
{
|
|
@@ -4115,7 +4210,7 @@
|
|
| 4115 |
"pastValueT": {
|
| 4116 |
"dtype": "float32",
|
| 4117 |
"shape": [1, 1, 1024, 64],
|
| 4118 |
-
"data": { "kind": "fillFloat32", "sinStep": 0.
|
| 4119 |
},
|
| 4120 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [4095] } },
|
| 4121 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [4096] } }
|
|
@@ -4128,7 +4223,7 @@
|
|
| 4128 |
"provenance": {
|
| 4129 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4130 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4131 |
-
"notes": "Windowed cache at a capacity that clears CACHED_DECODE_MIN_KV_TOKENS, so the split-K decode variant is selected rather than the scalar fallback. T=4096 > C=1024, so the step genuinely evicts and the split-K kernel reads a shifted cache."
|
| 4132 |
}
|
| 4133 |
},
|
| 4134 |
{
|
|
@@ -4502,7 +4597,7 @@
|
|
| 4502 |
"provenance": {
|
| 4503 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4504 |
"test": "GQA buffer-sharing append chunk prefill on the flash cluster kernel",
|
| 4505 |
-
"notes": "
|
| 4506 |
}
|
| 4507 |
},
|
| 4508 |
{
|
|
@@ -4574,7 +4669,206 @@
|
|
| 4574 |
"provenance": {
|
| 4575 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4576 |
"test": "GQA buffer-sharing append chunk prefill on the flash cluster kernel",
|
| 4577 |
-
"notes": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4578 |
}
|
| 4579 |
},
|
| 4580 |
{
|
|
@@ -4611,7 +4905,7 @@
|
|
| 4611 |
{
|
| 4612 |
"name": "splitk_decode_h8kv2_d64_kv2048_f16_ramp_value_scale_lock",
|
| 4613 |
"provenance": {
|
| 4614 |
-
"notes": "
|
| 4615 |
},
|
| 4616 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.125, "causal": 0 },
|
| 4617 |
"inputs": {
|
|
@@ -4642,7 +4936,7 @@
|
|
| 4642 |
{
|
| 4643 |
"name": "flash_prefill_h2kv1_d64_s128_f16_ramp_value_scale_lock",
|
| 4644 |
"provenance": {
|
| 4645 |
-
"notes": "
|
| 4646 |
},
|
| 4647 |
"attrs": { "num_heads": 2, "kv_num_heads": 1, "scale": 0.125, "causal": 0 },
|
| 4648 |
"inputs": {
|
|
@@ -4673,7 +4967,7 @@
|
|
| 4673 |
{
|
| 4674 |
"name": "newkv_past_splitk_h8kv2_d64_q1p1024_ramp_value_scale_lock",
|
| 4675 |
"provenance": {
|
| 4676 |
-
"notes": "
|
| 4677 |
},
|
| 4678 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.125, "local_window_size": 256 },
|
| 4679 |
"inputs": {
|
|
@@ -4710,7 +5004,7 @@
|
|
| 4710 |
{
|
| 4711 |
"name": "newkv_past_f16_flash_h8kv2_d64_q32p32_ramp_value_scale_lock",
|
| 4712 |
"provenance": {
|
| 4713 |
-
"notes": "
|
| 4714 |
},
|
| 4715 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.125, "local_window_size": 16 },
|
| 4716 |
"inputs": {
|
|
@@ -4751,7 +5045,7 @@
|
|
| 4751 |
{
|
| 4752 |
"name": "window_cache_decode_splitk_cap1024_b1q1_h2kv1d64_ramp_value_scale_lock",
|
| 4753 |
"provenance": {
|
| 4754 |
-
"notes": "
|
| 4755 |
},
|
| 4756 |
"attrs": { "num_heads": 2, "kv_num_heads": 1, "sliding_window_cache": 1, "local_window_size": 1024 },
|
| 4757 |
"inputs": {
|
|
@@ -4784,7 +5078,7 @@
|
|
| 4784 |
{
|
| 4785 |
"name": "quant_int8_scalar_prompt_ramp_value_scale_lock",
|
| 4786 |
"provenance": {
|
| 4787 |
-
"notes": "
|
| 4788 |
},
|
| 4789 |
"attrs": {
|
| 4790 |
"num_heads": 2,
|
|
@@ -4821,7 +5115,7 @@
|
|
| 4821 |
{
|
| 4822 |
"name": "quant_int4_scalar_prompt_ramp_value_scale_lock",
|
| 4823 |
"provenance": {
|
| 4824 |
-
"notes": "
|
| 4825 |
},
|
| 4826 |
"attrs": {
|
| 4827 |
"num_heads": 2,
|
|
@@ -5235,6 +5529,40 @@
|
|
| 5235 |
"presentValueT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.001 }
|
| 5236 |
}
|
| 5237 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5238 |
{
|
| 5239 |
"name": "past_kv_materialized_sgmat_f16_floor_q256_p256_h2kv1_d64",
|
| 5240 |
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
|
@@ -5374,6 +5702,39 @@
|
|
| 5374 |
"presentKeyT": { "dtype": "float32", "shape": [2, 1, 256, 64], "tolerance": 0.000001 },
|
| 5375 |
"presentValueT": { "dtype": "float32", "shape": [2, 1, 256, 64], "tolerance": 0.000001 }
|
| 5376 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5377 |
}
|
| 5378 |
]
|
| 5379 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"fixtureArrays": {
|
| 3 |
"ort_past_key_t_pattern": [0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8, 1, 0.2, 0.4, 0.6, 0.8],
|
| 4 |
"ort_past_value_t_pattern": [0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3, 0.6, 0.9, 0.3],
|
|
|
|
| 246 |
"valueT": {
|
| 247 |
"dtype": "float32",
|
| 248 |
"shape": [1, 512, 64],
|
| 249 |
+
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.09817477042, "cosStep": 0.000383495197 }
|
| 250 |
},
|
| 251 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 252 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
|
|
| 466 |
"valueT": {
|
| 467 |
"dtype": "float32",
|
| 468 |
"shape": [1, 2048, 128],
|
| 469 |
+
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.04908738521, "cosStep": 0.00004793689962 }
|
| 470 |
},
|
| 471 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2047] } },
|
| 472 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2048] } }
|
|
|
|
| 494 |
"valueT": {
|
| 495 |
"dtype": "float32",
|
| 496 |
"shape": [1, 1024, 64],
|
| 497 |
+
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.09817477042, "cosStep": 0.0001917475985 }
|
| 498 |
},
|
| 499 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 500 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
|
|
| 522 |
"valueT": {
|
| 523 |
"dtype": "float16",
|
| 524 |
"shape": [1, 128, 64],
|
| 525 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.001533980788 }
|
| 526 |
},
|
| 527 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [127] } },
|
| 528 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [128] } }
|
| 529 |
},
|
| 530 |
"outputs": {
|
| 531 |
+
"outputT": { "dtype": "float16", "shape": [1, 128, 128], "tolerance": 0.004 },
|
| 532 |
+
"presentKeyT": { "dtype": "float16", "shape": [1, 1, 128, 64], "tolerance": 0.004 },
|
| 533 |
+
"presentValueT": { "dtype": "float16", "shape": [1, 1, 128, 64], "tolerance": 0.004 }
|
| 534 |
}
|
| 535 |
},
|
| 536 |
{
|
|
|
|
| 550 |
"valueT": {
|
| 551 |
"dtype": "float16",
|
| 552 |
"shape": [1, 512, 64],
|
| 553 |
+
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.09817477042, "cosStep": 0.000383495197 }
|
| 554 |
},
|
| 555 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 556 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
| 557 |
},
|
| 558 |
"outputs": {
|
| 559 |
+
"outputT": { "dtype": "float16", "shape": [1, 1, 256], "tolerance": 0.004 },
|
| 560 |
+
"presentKeyT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.004 },
|
| 561 |
+
"presentValueT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.004 }
|
| 562 |
}
|
| 563 |
},
|
| 564 |
{
|
|
|
|
| 578 |
"valueT": {
|
| 579 |
"dtype": "float16",
|
| 580 |
"shape": [1, 2048, 128],
|
| 581 |
+
"data": { "kind": "fillFloat32", "scale": 0.18, "sinStep": 0.04908738521, "cosStep": 0.00004793689962 }
|
| 582 |
},
|
| 583 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2047] } },
|
| 584 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2048] } }
|
| 585 |
},
|
| 586 |
"outputs": {
|
| 587 |
+
"outputT": { "dtype": "float16", "shape": [1, 1, 512], "tolerance": 0.004 },
|
| 588 |
+
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 2048, 64], "tolerance": 0.004 },
|
| 589 |
+
"presentValueT": { "dtype": "float16", "shape": [1, 2, 2048, 64], "tolerance": 0.004 }
|
| 590 |
}
|
| 591 |
},
|
| 592 |
{
|
|
|
|
| 690 |
"valueT": {
|
| 691 |
"dtype": "float32",
|
| 692 |
"shape": [1, 1024, 128],
|
| 693 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.04908738521, "cosStep": 0.00009587379924 }
|
| 694 |
},
|
| 695 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 696 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
|
|
| 1209 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0038802] } }
|
| 1210 |
},
|
| 1211 |
"outputs": {
|
| 1212 |
+
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.01 },
|
| 1213 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1214 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1215 |
}
|
|
|
|
| 1275 |
}
|
| 1276 |
},
|
| 1277 |
"outputs": {
|
| 1278 |
+
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.01 },
|
| 1279 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1280 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1281 |
}
|
|
|
|
| 1380 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0703979] } }
|
| 1381 |
},
|
| 1382 |
"outputs": {
|
| 1383 |
+
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.01 },
|
| 1384 |
"presentKeyT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 },
|
| 1385 |
"presentValueT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 }
|
| 1386 |
}
|
|
|
|
| 1446 |
}
|
| 1447 |
},
|
| 1448 |
"outputs": {
|
| 1449 |
+
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.01 },
|
| 1450 |
"presentKeyT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 },
|
| 1451 |
"presentValueT": { "dtype": "uint8", "shape": [1, 1, 4, 4], "tolerance": 0 }
|
| 1452 |
}
|
|
|
|
| 1507 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0039328] } }
|
| 1508 |
},
|
| 1509 |
"outputs": {
|
| 1510 |
+
"outputT": { "dtype": "float32", "shape": [2, 4, 64], "tolerance": 0.01 },
|
| 1511 |
"presentKeyT": { "dtype": "int8", "shape": [2, 2, 4, 16], "tolerance": 0 },
|
| 1512 |
"presentValueT": { "dtype": "int8", "shape": [2, 2, 4, 16], "tolerance": 0 }
|
| 1513 |
}
|
|
|
|
| 1562 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0039328] } }
|
| 1563 |
},
|
| 1564 |
"outputs": {
|
| 1565 |
+
"outputT": { "dtype": "float32", "shape": [1, 8, 128], "tolerance": 0.01 },
|
| 1566 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 8, 64], "tolerance": 0 },
|
| 1567 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 8, 64], "tolerance": 0 }
|
| 1568 |
}
|
|
|
|
| 1623 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0703979] } }
|
| 1624 |
},
|
| 1625 |
"outputs": {
|
| 1626 |
+
"outputT": { "dtype": "float32", "shape": [1, 4, 64], "tolerance": 0.01 },
|
| 1627 |
"presentKeyT": { "dtype": "uint8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1628 |
"presentValueT": { "dtype": "uint8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1629 |
}
|
|
|
|
| 1802 |
"vScaleT": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0038802] } }
|
| 1803 |
},
|
| 1804 |
"outputs": {
|
| 1805 |
+
"outputT": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.01 },
|
| 1806 |
"presentKeyT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 },
|
| 1807 |
"presentValueT": { "dtype": "int8", "shape": [1, 1, 4, 8], "tolerance": 0 }
|
| 1808 |
}
|
|
|
|
| 1848 |
"presentValueT": { "dtype": "float32", "shape": [1, 1, 8, 8], "tolerance": 0.0001 }
|
| 1849 |
}
|
| 1850 |
},
|
| 1851 |
+
{
|
| 1852 |
+
"name": "ort_bias_decode_all_neg_inf_row_zero",
|
| 1853 |
+
"provenance": {
|
| 1854 |
+
"notes": "Cached decode uses two query heads, one KV head, head size 8, eight active cache positions, and no new K/V values. Bias for query head 1 is -Infinity at every key while head 0 remains finite. The masked head's output is zero; the finite head is its softmax-weighted V. `seqlens_k = 7` means eight active keys, not an empty row. Present K/V outputs copy the input cache unchanged."
|
| 1855 |
+
},
|
| 1856 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 1857 |
+
"inputs": {
|
| 1858 |
+
"queryT": {
|
| 1859 |
+
"dtype": "float32",
|
| 1860 |
+
"shape": [1, 1, 16],
|
| 1861 |
+
"data": {
|
| 1862 |
+
"kind": "values",
|
| 1863 |
+
"values": { "$ref": "#/fixtureArrays/ort_sharedkv_decode_b1q1p8_h2kv1d8_input_queryT" }
|
| 1864 |
+
}
|
| 1865 |
+
},
|
| 1866 |
+
"keyT": { "dtype": "float32", "shape": [1, 0, 8], "data": { "kind": "values", "values": [] } },
|
| 1867 |
+
"valueT": { "dtype": "float32", "shape": [1, 0, 8], "data": { "kind": "values", "values": [] } },
|
| 1868 |
+
"pastKeyT": {
|
| 1869 |
+
"dtype": "float32",
|
| 1870 |
+
"shape": [1, 1, 8, 8],
|
| 1871 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_past_key_t_pattern" } }
|
| 1872 |
+
},
|
| 1873 |
+
"pastValueT": {
|
| 1874 |
+
"dtype": "float32",
|
| 1875 |
+
"shape": [1, 1, 8, 8],
|
| 1876 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_past_value_t_pattern" } }
|
| 1877 |
+
},
|
| 1878 |
+
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [7] } },
|
| 1879 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [8] } },
|
| 1880 |
+
"attentionBiasT": {
|
| 1881 |
+
"dtype": "float32",
|
| 1882 |
+
"shape": [1, 2, 1, 8],
|
| 1883 |
+
"data": {
|
| 1884 |
+
"kind": "values",
|
| 1885 |
+
"values": [-0.05, -0.0020574, 0.0341471, 0.0497495, 0.0409297, 0.0098472, -0.035888, -0.0850783, "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity"]
|
| 1886 |
+
}
|
| 1887 |
+
}
|
| 1888 |
+
},
|
| 1889 |
+
"outputs": {
|
| 1890 |
+
"outputT": {
|
| 1891 |
+
"dtype": "float32",
|
| 1892 |
+
"shape": [1, 1, 16],
|
| 1893 |
+
"tolerance": 0.0001,
|
| 1894 |
+
"data": {
|
| 1895 |
+
"kind": "values",
|
| 1896 |
+
"values": [0.6027476, 0.5584169, 0.6388355, 0.6027476, 0.5584169, 0.6388355, 0.6027476, 0.5584169, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
| 1897 |
+
}
|
| 1898 |
+
},
|
| 1899 |
+
"presentKeyT": { "dtype": "float32", "shape": [1, 1, 8, 8], "tolerance": 0.0001 },
|
| 1900 |
+
"presentValueT": { "dtype": "float32", "shape": [1, 1, 8, 8], "tolerance": 0.0001 }
|
| 1901 |
+
}
|
| 1902 |
+
},
|
| 1903 |
+
{
|
| 1904 |
+
"name": "ort_bias_decode_single_finite_key_control",
|
| 1905 |
+
"provenance": {
|
| 1906 |
+
"notes": "Attention bias has one finite key per head and -Infinity elsewhere. Each softmax therefore selects exactly one cached value: head 0 returns row 5 and head 1 returns row 3. This distinguishes a valid one-key denominator from the fully masked behavior in `ort_bias_decode_all_neg_inf_row_zero`."
|
| 1907 |
+
},
|
| 1908 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 1909 |
+
"inputs": {
|
| 1910 |
+
"queryT": {
|
| 1911 |
+
"dtype": "float32",
|
| 1912 |
+
"shape": [1, 1, 16],
|
| 1913 |
+
"data": {
|
| 1914 |
+
"kind": "values",
|
| 1915 |
+
"values": { "$ref": "#/fixtureArrays/ort_sharedkv_decode_b1q1p8_h2kv1d8_input_queryT" }
|
| 1916 |
+
}
|
| 1917 |
+
},
|
| 1918 |
+
"keyT": { "dtype": "float32", "shape": [1, 0, 8], "data": { "kind": "values", "values": [] } },
|
| 1919 |
+
"valueT": { "dtype": "float32", "shape": [1, 0, 8], "data": { "kind": "values", "values": [] } },
|
| 1920 |
+
"pastKeyT": {
|
| 1921 |
+
"dtype": "float32",
|
| 1922 |
+
"shape": [1, 1, 8, 8],
|
| 1923 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_past_key_t_pattern" } }
|
| 1924 |
+
},
|
| 1925 |
+
"pastValueT": {
|
| 1926 |
+
"dtype": "float32",
|
| 1927 |
+
"shape": [1, 1, 8, 8],
|
| 1928 |
+
"data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_past_value_t_pattern" } }
|
| 1929 |
+
},
|
| 1930 |
+
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [7] } },
|
| 1931 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [8] } },
|
| 1932 |
+
"attentionBiasT": {
|
| 1933 |
+
"dtype": "float32",
|
| 1934 |
+
"shape": [1, 2, 1, 8],
|
| 1935 |
+
"data": {
|
| 1936 |
+
"kind": "values",
|
| 1937 |
+
"values": ["-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", 0.0, "-Infinity", "-Infinity", "-Infinity", "-Infinity", "-Infinity", 0.0, "-Infinity", "-Infinity", "-Infinity", "-Infinity"]
|
| 1938 |
+
}
|
| 1939 |
+
}
|
| 1940 |
+
},
|
| 1941 |
+
"outputs": {
|
| 1942 |
+
"outputT": { "dtype": "float32", "shape": [1, 1, 16], "tolerance": 0.0001 },
|
| 1943 |
+
"presentKeyT": { "dtype": "float32", "shape": [1, 1, 8, 8], "tolerance": 0.0001 },
|
| 1944 |
+
"presentValueT": { "dtype": "float32", "shape": [1, 1, 8, 8], "tolerance": 0.0001 }
|
| 1945 |
+
}
|
| 1946 |
+
},
|
| 1947 |
{
|
| 1948 |
"name": "ort_bias_bcast_prompt_b1q8p8_h2kv1d8",
|
| 1949 |
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
|
|
|
| 2097 |
{
|
| 2098 |
"name": "flashprefill_pastzero_h8kv2_d128_q16",
|
| 2099 |
"provenance": {
|
| 2100 |
+
"notes": "A compact shared-KV prefill with 32 query heads, eight KV heads, head size 128, and sequence length 64 exercises grouped attention."
|
| 2101 |
},
|
| 2102 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.08838834764831845 },
|
| 2103 |
"inputs": {
|
|
|
|
| 2146 |
"pastValueT": {
|
| 2147 |
"dtype": "float32",
|
| 2148 |
"shape": [1, 2, 48, 64],
|
| 2149 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.002045307717 }
|
| 2150 |
},
|
| 2151 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [47] } },
|
| 2152 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [48] } }
|
|
|
|
| 2231 |
"name": "qnorm_rotary_scalar_h8kv2_d256_q1",
|
| 2232 |
"provenance": {
|
| 2233 |
"source": "scope coverage",
|
| 2234 |
+
"test": "headDim=256 scalar Q-norm and rotary specialization",
|
| 2235 |
"notes": "headDim=256 requires 34,048 bytes for the cooperative path, exceeding a 32 KiB workgroup-storage tier and exercising the thread-per-query Q-norm route."
|
| 2236 |
},
|
| 2237 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.0625, "do_rotary": 1, "qk_norm_epsilon": 0.00001 },
|
|
|
|
| 2286 |
"name": "f16_qnorm_rotary_scalar_f16wts_h8kv2_d256_q1",
|
| 2287 |
"provenance": {
|
| 2288 |
"source": "scope coverage",
|
| 2289 |
+
"test": "headDim=256 float16-weight scalar Q-norm and rotary specialization",
|
| 2290 |
"notes": "Float16 counterpart to the thread-per-query Q-norm case. It verifies explicit f32 conversion of q_norm_weight, cos_cache, and sin_cache before scalar arithmetic."
|
| 2291 |
},
|
| 2292 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.0625, "do_rotary": 1, "qk_norm_epsilon": 0.00001 },
|
|
|
|
| 2406 |
"pastValueT": {
|
| 2407 |
"dtype": "float32",
|
| 2408 |
"shape": [1, 2, 1024, 64],
|
| 2409 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.00009587379924 }
|
| 2410 |
},
|
| 2411 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 2412 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
|
|
| 2420 |
{
|
| 2421 |
"name": "flashdecode_splitk_h8kv2_d128_kv1024",
|
| 2422 |
"provenance": {
|
| 2423 |
+
"notes": "A compact float32 shared-past decode with 32 query heads, eight KV heads, head size 128, and KV length 2,048 exercises split-K grouped attention."
|
| 2424 |
},
|
| 2425 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.08838834764831845 },
|
| 2426 |
"inputs": {
|
|
|
|
| 2439 |
"pastValueT": {
|
| 2440 |
"dtype": "float32",
|
| 2441 |
"shape": [1, 2, 1024, 128],
|
| 2442 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.04908738521, "cosStep": 0.00004793689962 }
|
| 2443 |
},
|
| 2444 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 2445 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
|
|
|
| 2469 |
"pastValueT": {
|
| 2470 |
"dtype": "float16",
|
| 2471 |
"shape": [1, 2, 512, 128],
|
| 2472 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.04908738521, "cosStep": 0.00009587379924 }
|
| 2473 |
},
|
| 2474 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 2475 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
| 2476 |
},
|
| 2477 |
"outputs": {
|
| 2478 |
+
"outputT": { "dtype": "float16", "shape": [1, 1, 1024], "tolerance": 0.004 },
|
| 2479 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 512, 128], "tolerance": 0.001 },
|
| 2480 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 512, 128], "tolerance": 0.001 }
|
| 2481 |
}
|
|
|
|
| 2499 |
"pastValueT": {
|
| 2500 |
"dtype": "float16",
|
| 2501 |
"shape": [1, 2, 1024, 128],
|
| 2502 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.04908738521, "cosStep": 0.00004793689962 }
|
| 2503 |
},
|
| 2504 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 2505 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
| 2506 |
},
|
| 2507 |
"outputs": {
|
| 2508 |
+
"outputT": { "dtype": "float16", "shape": [1, 1, 1024], "tolerance": 0.004 },
|
| 2509 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 1024, 128], "tolerance": 0.001 },
|
| 2510 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 1024, 128], "tolerance": 0.001 }
|
| 2511 |
}
|
|
|
|
| 2559 |
"pastValueT": {
|
| 2560 |
"dtype": "float16",
|
| 2561 |
"shape": [1, 2, 512, 64],
|
| 2562 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.0001917475985 }
|
| 2563 |
},
|
| 2564 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 2565 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } },
|
|
|
|
| 2575 |
}
|
| 2576 |
},
|
| 2577 |
"outputs": {
|
| 2578 |
+
"outputT": { "dtype": "float16", "shape": [1, 1, 512], "tolerance": 0.004 },
|
| 2579 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 512, 64], "tolerance": 0.001 },
|
| 2580 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 512, 64], "tolerance": 0.001 }
|
| 2581 |
}
|
|
|
|
| 2599 |
"pastValueT": {
|
| 2600 |
"dtype": "float16",
|
| 2601 |
"shape": [1, 2, 32, 64],
|
| 2602 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.003067961576 }
|
| 2603 |
},
|
| 2604 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [31] } },
|
| 2605 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
|
|
|
| 2639 |
"pastValueT": {
|
| 2640 |
"dtype": "float16",
|
| 2641 |
"shape": [1, 2, 32, 64],
|
| 2642 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.003067961576 }
|
| 2643 |
},
|
| 2644 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [31] } },
|
| 2645 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [32] } },
|
|
|
|
| 2725 |
"valueT": {
|
| 2726 |
"dtype": "float32",
|
| 2727 |
"shape": [1, 32, 128],
|
| 2728 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.04908738521, "cosStep": 0.003067961576 }
|
| 2729 |
},
|
| 2730 |
"pastKeyT": {
|
| 2731 |
"dtype": "float32",
|
|
|
|
| 2735 |
"pastValueT": {
|
| 2736 |
"dtype": "float32",
|
| 2737 |
"shape": [1, 2, 32, 64],
|
| 2738 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.003067961576 }
|
| 2739 |
},
|
| 2740 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [63] } },
|
| 2741 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
|
|
|
| 2763 |
"valueT": {
|
| 2764 |
"dtype": "float32",
|
| 2765 |
"shape": [1, 1, 128],
|
| 2766 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.04908738521, "cosStep": 0.09817477042 }
|
| 2767 |
},
|
| 2768 |
"pastKeyT": {
|
| 2769 |
"dtype": "float32",
|
|
|
|
| 2773 |
"pastValueT": {
|
| 2774 |
"dtype": "float32",
|
| 2775 |
"shape": [1, 2, 1024, 64],
|
| 2776 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.00009587379924 }
|
| 2777 |
},
|
| 2778 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } },
|
| 2779 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1025] } }
|
|
|
|
| 2801 |
"valueT": {
|
| 2802 |
"dtype": "float16",
|
| 2803 |
"shape": [1, 32, 128],
|
| 2804 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.04908738521, "cosStep": 0.003067961576 }
|
| 2805 |
},
|
| 2806 |
"pastKeyT": {
|
| 2807 |
"dtype": "float16",
|
|
|
|
| 2811 |
"pastValueT": {
|
| 2812 |
"dtype": "float16",
|
| 2813 |
"shape": [1, 2, 32, 64],
|
| 2814 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.09817477042, "cosStep": 0.003067961576 }
|
| 2815 |
},
|
| 2816 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [63] } },
|
| 2817 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
| 2818 |
},
|
| 2819 |
"outputs": {
|
| 2820 |
+
"outputT": { "dtype": "float16", "shape": [1, 32, 512], "tolerance": 0.004 },
|
| 2821 |
"presentKeyT": { "dtype": "float16", "shape": [1, 2, 64, 64], "tolerance": 0.001 },
|
| 2822 |
"presentValueT": { "dtype": "float16", "shape": [1, 2, 64, 64], "tolerance": 0.001 }
|
| 2823 |
}
|
|
|
|
| 2967 |
"valueT": {
|
| 2968 |
"dtype": "float32",
|
| 2969 |
"shape": [1, 511, 128],
|
| 2970 |
+
"data": { "kind": "fillFloat32", "scale": 0.08, "sinStep": 0.04908738521, "cosStep": 0.0001921228384 }
|
| 2971 |
},
|
| 2972 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [510] } },
|
| 2973 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } }
|
|
|
|
| 2996 |
"valueT": {
|
| 2997 |
"dtype": "float32",
|
| 2998 |
"shape": [1, 64, 256],
|
| 2999 |
+
"data": { "kind": "fillFloat32", "scale": 0.08, "sinStep": 0.02454369261, "cosStep": 0.0007669903939 }
|
| 3000 |
},
|
| 3001 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [63] } },
|
| 3002 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [64] } }
|
|
|
|
| 3027 |
"valueT": {
|
| 3028 |
"dtype": "float32",
|
| 3029 |
"shape": [1, 512, 256],
|
| 3030 |
+
"data": { "kind": "fillFloat32", "scale": 0.08, "sinStep": 0.02454369261, "cosStep": 0.00009587379924 }
|
| 3031 |
},
|
| 3032 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 3033 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
|
|
| 3158 |
{
|
| 3159 |
"name": "qkv_prefill_f32_h8kv2_d256_register_boundary",
|
| 3160 |
"provenance": {
|
| 3161 |
+
"notes": "A QKV prefill with eight query heads, two KV heads, and head size 256 exercises generic online attention on subgroup tiers and shared-memory clustered attention on tiers without subgroups."
|
| 3162 |
},
|
| 3163 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.0625, "causal": 0 },
|
| 3164 |
"inputs": {
|
|
|
|
| 3356 |
"provenance": {
|
| 3357 |
"source": "onnxruntime/contrib_ops/webgpu/bert/flash_attention_decode_qkv.wgsl.template",
|
| 3358 |
"test": "head_size=8 split-reduce OOB race class (PR #29593)",
|
| 3359 |
+
"notes": "Diverges from the upstream test's inputs (inputs.pastValueT fillFloat32 -> fillFloat32); the expected output is recomputed by the CPU reference for the new inputs. A head size below the flash minimum exercises the cooperative fallback and its tile-width-guarded reduction."
|
| 3360 |
},
|
| 3361 |
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 3362 |
"inputs": {
|
|
|
|
| 3375 |
"pastValueT": {
|
| 3376 |
"dtype": "float32",
|
| 3377 |
"shape": [1, 2, 512, 8],
|
| 3378 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.7853981634, "cosStep": 0.001533980788, "scale": 0.5 }
|
| 3379 |
},
|
| 3380 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 3381 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
|
|
| 3391 |
"provenance": {
|
| 3392 |
"source": "onnxruntime/contrib_ops/webgpu/bert/flash_attention_decode_qkv.wgsl.template",
|
| 3393 |
"test": "head_size=16 split-reduce OOB race class (PR #29593)",
|
| 3394 |
+
"notes": "Diverges from the upstream test's inputs (inputs.pastValueT fillFloat32 -> fillFloat32); the expected output is recomputed by the CPU reference for the new inputs. Head size 16 exercises the cooperative fallback at the next four-wide tiny-head boundary."
|
| 3395 |
},
|
| 3396 |
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 3397 |
"inputs": {
|
|
|
|
| 3410 |
"pastValueT": {
|
| 3411 |
"dtype": "float32",
|
| 3412 |
"shape": [1, 2, 512, 16],
|
| 3413 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.3926990817, "cosStep": 0.0007669903939, "scale": 0.5 }
|
| 3414 |
},
|
| 3415 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 3416 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
|
|
|
| 3636 |
"provenance": {
|
| 3637 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 3638 |
"test": "GQA head sink on the buffer-sharing append path",
|
| 3639 |
+
"notes": "One K/V row is appended at index 11 in a capacity-32 cache. The 20 unused rows must not enter attention; `seqlens_k` determines both the append offset and window origin."
|
| 3640 |
}
|
| 3641 |
},
|
| 3642 |
{
|
|
|
|
| 3722 |
{
|
| 3723 |
"name": "prefill_tiled_q31_h8kv2_d36_non_cluster_stride",
|
| 3724 |
"provenance": {
|
| 3725 |
+
"notes": "Head size 36 is divisible by four but not by the 16 values mapped across each query cluster. With 31 queries and eight heads, tiers without subgroups exercise blocked tiled prefill."
|
| 3726 |
},
|
| 3727 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.16666666666666666, "causal": 0 },
|
| 3728 |
"inputs": {
|
|
|
|
| 3780 |
"presentValueT": { "dtype": "float32", "shape": [1, 2, 32, 64], "tolerance": 0.0001 }
|
| 3781 |
},
|
| 3782 |
"provenance": {
|
| 3783 |
+
"notes": "Logit soft-capping on the past-KV prefill path exercises the tiled attention route with a per-score cap before softmax."
|
| 3784 |
}
|
| 3785 |
},
|
| 3786 |
{
|
|
|
|
| 3867 |
{
|
| 3868 |
"name": "flashprefill_quant_int8_h4kv2_d64_q32",
|
| 3869 |
"provenance": {
|
| 3870 |
+
"notes": "INT8 KV cache on the tiled prefill path. Each key is dequantized once into the staged tile and reused by every query in that tile."
|
| 3871 |
},
|
| 3872 |
"attrs": {
|
| 3873 |
"num_heads": 4,
|
|
|
|
| 3978 |
"provenance": {
|
| 3979 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 3980 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 3981 |
+
"notes": "A capacity-8 windowed cache reaches `T = 6` without eviction, so its origin remains zero and unused rows must be cleared."
|
| 3982 |
}
|
| 3983 |
},
|
| 3984 |
{
|
|
|
|
| 4013 |
"provenance": {
|
| 4014 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4015 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4016 |
+
"notes": "At `T = 12` with capacity 8, appending one token evicts one row, writes at row 7, and advances the absolute cache origin to 4."
|
| 4017 |
}
|
| 4018 |
},
|
| 4019 |
{
|
|
|
|
| 4048 |
"provenance": {
|
| 4049 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4050 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4051 |
+
"notes": "Appending four tokens to a capacity-8 cache at `T = 10` evicts two rows, begins the append at row 4, and advances the absolute cache origin to 2."
|
| 4052 |
}
|
| 4053 |
},
|
| 4054 |
{
|
|
|
|
| 4083 |
"provenance": {
|
| 4084 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4085 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4086 |
+
"notes": "Two batches require independent cache origins: batch 0 reaches `T = 4` without eviction, while batch 1 reaches `T = 12`, evicts one row, and uses origin 4."
|
| 4087 |
}
|
| 4088 |
},
|
| 4089 |
{
|
|
|
|
| 4118 |
"provenance": {
|
| 4119 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4120 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4121 |
+
"notes": "A local window of four within a capacity-8 cache combines one-row eviction with a nonzero attention floor derived from the absolute cache origin."
|
| 4122 |
}
|
| 4123 |
},
|
| 4124 |
{
|
|
|
|
| 4153 |
"provenance": {
|
| 4154 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4155 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4156 |
+
"notes": "Cache capacity and local-window size are both four at `T = 10`, exercising a fully occupied sliding-window allocation with a nonzero origin."
|
| 4157 |
}
|
| 4158 |
},
|
| 4159 |
{
|
|
|
|
| 4188 |
"provenance": {
|
| 4189 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4190 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4191 |
+
"notes": "At `T = 64` with capacity 16, the cache origin is 48. The four-to-one grouped-query layout and head size 16 exercise absolute-position translation far beyond the physical buffer."
|
| 4192 |
}
|
| 4193 |
},
|
| 4194 |
{
|
|
|
|
| 4210 |
"pastValueT": {
|
| 4211 |
"dtype": "float32",
|
| 4212 |
"shape": [1, 1, 1024, 64],
|
| 4213 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.09817477042, "cosStep": 0.0001917475985, "scale": 0.5 }
|
| 4214 |
},
|
| 4215 |
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [4095] } },
|
| 4216 |
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [4096] } }
|
|
|
|
| 4223 |
"provenance": {
|
| 4224 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4225 |
"test": "GQA sliding_window_cache (ORT #29904), CPU/CUDA reference semantics",
|
| 4226 |
+
"notes": "Diverges from the upstream test's inputs (inputs.pastValueT fillFloat32 -> fillFloat32); the expected output is recomputed by the CPU reference for the new inputs. Windowed cache at a capacity that clears CACHED_DECODE_MIN_KV_TOKENS, so the split-K decode variant is selected rather than the scalar fallback. T=4096 > C=1024, so the step genuinely evicts and the split-K kernel reads a shifted cache."
|
| 4227 |
}
|
| 4228 |
},
|
| 4229 |
{
|
|
|
|
| 4597 |
"provenance": {
|
| 4598 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4599 |
"test": "GQA buffer-sharing append chunk prefill on the flash cluster kernel",
|
| 4600 |
+
"notes": "A fixed-capacity cache has active length 48 and capacity 64 before a 32-row prefill. The flash route must use the live length for attention and the capacity for batch strides."
|
| 4601 |
}
|
| 4602 |
},
|
| 4603 |
{
|
|
|
|
| 4669 |
"provenance": {
|
| 4670 |
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4671 |
"test": "GQA buffer-sharing append chunk prefill on the flash cluster kernel",
|
| 4672 |
+
"notes": "Two batches share capacity-64 caches but have live lengths 32 and 48. The flash route must track each attention bound independently while retaining the fixed batch stride."
|
| 4673 |
+
}
|
| 4674 |
+
},
|
| 4675 |
+
{
|
| 4676 |
+
"name": "share_append_sgmat_chunk_slack_b1q128cap384_h2kv1d64",
|
| 4677 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 4678 |
+
"inputs": {
|
| 4679 |
+
"queryT": {
|
| 4680 |
+
"dtype": "float32",
|
| 4681 |
+
"shape": [1, 128, 128],
|
| 4682 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27, "scale": 0.5 }
|
| 4683 |
+
},
|
| 4684 |
+
"keyT": { "dtype": "float32", "shape": [1, 128, 64], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
|
| 4685 |
+
"valueT": {
|
| 4686 |
+
"dtype": "float32",
|
| 4687 |
+
"shape": [1, 128, 64],
|
| 4688 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 2.0 }
|
| 4689 |
+
},
|
| 4690 |
+
"pastKeyT": {
|
| 4691 |
+
"dtype": "float32",
|
| 4692 |
+
"shape": [1, 1, 384, 64],
|
| 4693 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.31, "scale": 0.5 }
|
| 4694 |
+
},
|
| 4695 |
+
"pastValueT": {
|
| 4696 |
+
"dtype": "float32",
|
| 4697 |
+
"shape": [1, 1, 384, 64],
|
| 4698 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 4.0 }
|
| 4699 |
+
},
|
| 4700 |
+
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [255] } },
|
| 4701 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } }
|
| 4702 |
+
},
|
| 4703 |
+
"outputs": {
|
| 4704 |
+
"outputT": { "dtype": "float32", "shape": [1, 128, 128], "tolerance": 0.005 },
|
| 4705 |
+
"presentKeyT": { "dtype": "float32", "shape": [1, 1, 384, 64], "tolerance": 0.0001 },
|
| 4706 |
+
"presentValueT": { "dtype": "float32", "shape": [1, 1, 384, 64], "tolerance": 0.0001 }
|
| 4707 |
+
},
|
| 4708 |
+
"provenance": {
|
| 4709 |
+
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4710 |
+
"test": "GQA buffer-sharing chunk append on the materialized subgroup-matrix route",
|
| 4711 |
+
"notes": "128 past rows plus a 128-row appended chunk in a 384-capacity shared buffer: the live length (256) is shorter than the capacity, so the causal bound and the tile skips must come from seqlens_k while every stride stays capacity-sized."
|
| 4712 |
+
}
|
| 4713 |
+
},
|
| 4714 |
+
{
|
| 4715 |
+
"name": "share_append_sgmat_chunk_full_b1q128cap256_h2kv1d128",
|
| 4716 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 4717 |
+
"inputs": {
|
| 4718 |
+
"queryT": {
|
| 4719 |
+
"dtype": "float32",
|
| 4720 |
+
"shape": [1, 128, 256],
|
| 4721 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27, "scale": 0.5 }
|
| 4722 |
+
},
|
| 4723 |
+
"keyT": {
|
| 4724 |
+
"dtype": "float32",
|
| 4725 |
+
"shape": [1, 128, 128],
|
| 4726 |
+
"data": { "kind": "linspace", "start": -1.0, "end": 1.0 }
|
| 4727 |
+
},
|
| 4728 |
+
"valueT": {
|
| 4729 |
+
"dtype": "float32",
|
| 4730 |
+
"shape": [1, 128, 128],
|
| 4731 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 2.0 }
|
| 4732 |
+
},
|
| 4733 |
+
"pastKeyT": {
|
| 4734 |
+
"dtype": "float32",
|
| 4735 |
+
"shape": [1, 1, 256, 128],
|
| 4736 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.31, "scale": 0.5 }
|
| 4737 |
+
},
|
| 4738 |
+
"pastValueT": {
|
| 4739 |
+
"dtype": "float32",
|
| 4740 |
+
"shape": [1, 1, 256, 128],
|
| 4741 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 4.0 }
|
| 4742 |
+
},
|
| 4743 |
+
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [255] } },
|
| 4744 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } }
|
| 4745 |
+
},
|
| 4746 |
+
"outputs": {
|
| 4747 |
+
"outputT": { "dtype": "float32", "shape": [1, 128, 256], "tolerance": 0.005 },
|
| 4748 |
+
"presentKeyT": { "dtype": "float32", "shape": [1, 1, 256, 128], "tolerance": 0.0001 },
|
| 4749 |
+
"presentValueT": { "dtype": "float32", "shape": [1, 1, 256, 128], "tolerance": 0.0001 }
|
| 4750 |
+
},
|
| 4751 |
+
"provenance": {
|
| 4752 |
+
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4753 |
+
"test": "GQA buffer-sharing chunk append on the materialized subgroup-matrix route",
|
| 4754 |
+
"notes": "The append exactly fills the shared capacity at d=128 (two apply column tiles); live length equals capacity, so the seqlens bound and the compile-time bound coincide."
|
| 4755 |
+
}
|
| 4756 |
+
},
|
| 4757 |
+
{
|
| 4758 |
+
"name": "share_append_sgmat_chunk_mixed_batch_b2q128cap384_h2kv1d64",
|
| 4759 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 4760 |
+
"inputs": {
|
| 4761 |
+
"queryT": {
|
| 4762 |
+
"dtype": "float32",
|
| 4763 |
+
"shape": [2, 128, 128],
|
| 4764 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27, "scale": 0.5 }
|
| 4765 |
+
},
|
| 4766 |
+
"keyT": { "dtype": "float32", "shape": [2, 128, 64], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
|
| 4767 |
+
"valueT": {
|
| 4768 |
+
"dtype": "float32",
|
| 4769 |
+
"shape": [2, 128, 64],
|
| 4770 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 2.0 }
|
| 4771 |
+
},
|
| 4772 |
+
"pastKeyT": {
|
| 4773 |
+
"dtype": "float32",
|
| 4774 |
+
"shape": [2, 1, 384, 64],
|
| 4775 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.31, "scale": 0.5 }
|
| 4776 |
+
},
|
| 4777 |
+
"pastValueT": {
|
| 4778 |
+
"dtype": "float32",
|
| 4779 |
+
"shape": [2, 1, 384, 64],
|
| 4780 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 4.0 }
|
| 4781 |
+
},
|
| 4782 |
+
"seqlensKT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [255, 127] } },
|
| 4783 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } }
|
| 4784 |
+
},
|
| 4785 |
+
"outputs": {
|
| 4786 |
+
"outputT": { "dtype": "float32", "shape": [2, 128, 128], "tolerance": 0.005 },
|
| 4787 |
+
"presentKeyT": { "dtype": "float32", "shape": [2, 1, 384, 64], "tolerance": 0.0001 },
|
| 4788 |
+
"presentValueT": { "dtype": "float32", "shape": [2, 1, 384, 64], "tolerance": 0.0001 }
|
| 4789 |
+
},
|
| 4790 |
+
"provenance": {
|
| 4791 |
+
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4792 |
+
"test": "GQA buffer-sharing chunk append on the materialized subgroup-matrix route",
|
| 4793 |
+
"notes": "Per-batch live lengths from seqlens_k: batch 0 has 128 past rows, batch 1 appends into an empty cache, and the same dispatch serves both."
|
| 4794 |
+
}
|
| 4795 |
+
},
|
| 4796 |
+
{
|
| 4797 |
+
"name": "share_append_sgmat_chunk_right_padded_b2q128cap384_h2kv1d64",
|
| 4798 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 4799 |
+
"inputs": {
|
| 4800 |
+
"queryT": {
|
| 4801 |
+
"dtype": "float32",
|
| 4802 |
+
"shape": [2, 128, 128],
|
| 4803 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27, "scale": 0.5 }
|
| 4804 |
+
},
|
| 4805 |
+
"keyT": { "dtype": "float32", "shape": [2, 128, 64], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
|
| 4806 |
+
"valueT": {
|
| 4807 |
+
"dtype": "float32",
|
| 4808 |
+
"shape": [2, 128, 64],
|
| 4809 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 2.0 }
|
| 4810 |
+
},
|
| 4811 |
+
"pastKeyT": {
|
| 4812 |
+
"dtype": "float32",
|
| 4813 |
+
"shape": [2, 1, 384, 64],
|
| 4814 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.31, "scale": 0.5 }
|
| 4815 |
+
},
|
| 4816 |
+
"pastValueT": {
|
| 4817 |
+
"dtype": "float32",
|
| 4818 |
+
"shape": [2, 1, 384, 64],
|
| 4819 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 4.0 }
|
| 4820 |
+
},
|
| 4821 |
+
"seqlensKT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [255, 63] } },
|
| 4822 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } }
|
| 4823 |
+
},
|
| 4824 |
+
"outputs": {
|
| 4825 |
+
"outputT": { "dtype": "float32", "shape": [2, 128, 128], "tolerance": 0.005 },
|
| 4826 |
+
"presentKeyT": { "dtype": "float32", "shape": [2, 1, 384, 64], "tolerance": 0.0001 },
|
| 4827 |
+
"presentValueT": { "dtype": "float32", "shape": [2, 1, 384, 64], "tolerance": 0.0001 }
|
| 4828 |
+
},
|
| 4829 |
+
"provenance": {
|
| 4830 |
+
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4831 |
+
"test": "GQA buffer-sharing chunk append on the materialized subgroup-matrix route",
|
| 4832 |
+
"notes": "Batch 1's live length (64) is shorter than the 128-row query chunk, so its queries left-align against the live region exactly as the flash routes' saturated past length does; batch 0 stays right-aligned."
|
| 4833 |
+
}
|
| 4834 |
+
},
|
| 4835 |
+
{
|
| 4836 |
+
"name": "share_append_sgmat_chunk_qtail_b1q160cap384_h2kv1d64",
|
| 4837 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 4838 |
+
"inputs": {
|
| 4839 |
+
"queryT": {
|
| 4840 |
+
"dtype": "float32",
|
| 4841 |
+
"shape": [1, 160, 128],
|
| 4842 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.27, "scale": 0.5 }
|
| 4843 |
+
},
|
| 4844 |
+
"keyT": { "dtype": "float32", "shape": [1, 160, 64], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
|
| 4845 |
+
"valueT": {
|
| 4846 |
+
"dtype": "float32",
|
| 4847 |
+
"shape": [1, 160, 64],
|
| 4848 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 2.0 }
|
| 4849 |
+
},
|
| 4850 |
+
"pastKeyT": {
|
| 4851 |
+
"dtype": "float32",
|
| 4852 |
+
"shape": [1, 1, 384, 64],
|
| 4853 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.31, "scale": 0.5 }
|
| 4854 |
+
},
|
| 4855 |
+
"pastValueT": {
|
| 4856 |
+
"dtype": "float32",
|
| 4857 |
+
"shape": [1, 1, 384, 64],
|
| 4858 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 4.0 }
|
| 4859 |
+
},
|
| 4860 |
+
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [255] } },
|
| 4861 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [256] } }
|
| 4862 |
+
},
|
| 4863 |
+
"outputs": {
|
| 4864 |
+
"outputT": { "dtype": "float32", "shape": [1, 160, 128], "tolerance": 0.005 },
|
| 4865 |
+
"presentKeyT": { "dtype": "float32", "shape": [1, 1, 384, 64], "tolerance": 0.0001 },
|
| 4866 |
+
"presentValueT": { "dtype": "float32", "shape": [1, 1, 384, 64], "tolerance": 0.0001 }
|
| 4867 |
+
},
|
| 4868 |
+
"provenance": {
|
| 4869 |
+
"source": "onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc",
|
| 4870 |
+
"test": "GQA buffer-sharing chunk append on the materialized subgroup-matrix route",
|
| 4871 |
+
"notes": "q=160 is not a query-tile multiple, so the guarded query tails run together with the live-length ceiling."
|
| 4872 |
}
|
| 4873 |
},
|
| 4874 |
{
|
|
|
|
| 4905 |
{
|
| 4906 |
"name": "splitk_decode_h8kv2_d64_kv2048_f16_ramp_value_scale_lock",
|
| 4907 |
"provenance": {
|
| 4908 |
+
"notes": "A monotone value ramp keeps float16 decode outputs at order-one magnitude, making split-K accumulation, cross-partition rescaling, and the final normalization observable."
|
| 4909 |
},
|
| 4910 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.125, "causal": 0 },
|
| 4911 |
"inputs": {
|
|
|
|
| 4936 |
{
|
| 4937 |
"name": "flash_prefill_h2kv1_d64_s128_f16_ramp_value_scale_lock",
|
| 4938 |
"provenance": {
|
| 4939 |
+
"notes": "A monotone value ramp gives each of 128 causal float16 prefill rows a distinct order-one output, exposing the register-blocked q32 epilogue division and row selection."
|
| 4940 |
},
|
| 4941 |
"attrs": { "num_heads": 2, "kv_num_heads": 1, "scale": 0.125, "causal": 0 },
|
| 4942 |
"inputs": {
|
|
|
|
| 4967 |
{
|
| 4968 |
"name": "newkv_past_splitk_h8kv2_d64_q1p1024_ramp_value_scale_lock",
|
| 4969 |
"provenance": {
|
| 4970 |
+
"notes": "Monotone cached and new value ramps make decode output an order-one function of the final 256 key positions. This exposes the local-window floor, split-K combine, softmax denominator, and cached/new seam."
|
| 4971 |
},
|
| 4972 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.125, "local_window_size": 256 },
|
| 4973 |
"inputs": {
|
|
|
|
| 5004 |
{
|
| 5005 |
"name": "newkv_past_f16_flash_h8kv2_d64_q32p32_ramp_value_scale_lock",
|
| 5006 |
"provenance": {
|
| 5007 |
+
"notes": "Monotone cached and new value ramps make each of 32 float16 prefill rows an order-one function of its 16-key causal window. Early rows cross the cached/new seam, exposing the window floor and flash normalization."
|
| 5008 |
},
|
| 5009 |
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.125, "local_window_size": 16 },
|
| 5010 |
"inputs": {
|
|
|
|
| 5045 |
{
|
| 5046 |
"name": "window_cache_decode_splitk_cap1024_b1q1_h2kv1d64_ramp_value_scale_lock",
|
| 5047 |
"provenance": {
|
| 5048 |
+
"notes": "A monotone past-cache ramp makes the decode output depend at order-one scale on the weighted slot within the shifted window. With `T = 4096` and capacity 1024, both the eviction offset and split-K combine affect the result."
|
| 5049 |
},
|
| 5050 |
"attrs": { "num_heads": 2, "kv_num_heads": 1, "sliding_window_cache": 1, "local_window_size": 1024 },
|
| 5051 |
"inputs": {
|
|
|
|
| 5078 |
{
|
| 5079 |
"name": "quant_int8_scalar_prompt_ramp_value_scale_lock",
|
| 5080 |
"provenance": {
|
| 5081 |
+
"notes": "An INT8 value ramp from 0.5 to 2.0 gives each causal row a distinct order-one output. Tight expected values exercise value-scale dequantization, the softmax denominator, and the scalar apply division."
|
| 5082 |
},
|
| 5083 |
"attrs": {
|
| 5084 |
"num_heads": 2,
|
|
|
|
| 5115 |
{
|
| 5116 |
"name": "quant_int4_scalar_prompt_ramp_value_scale_lock",
|
| 5117 |
"provenance": {
|
| 5118 |
+
"notes": "An INT4 value ramp with scale 0.3 spans the signed nibble range and gives order-one outputs. Tight expected values exercise +8-biased nibble unpacking, dequantization, and normalization."
|
| 5119 |
},
|
| 5120 |
"attrs": {
|
| 5121 |
"num_heads": 2,
|
|
|
|
| 5529 |
"presentValueT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.001 }
|
| 5530 |
}
|
| 5531 |
},
|
| 5532 |
+
{
|
| 5533 |
+
"name": "past_kv_materialized_sgmat_f16_cooperative_q512_p512_h2kv1_d64",
|
| 5534 |
+
"provenance": {
|
| 5535 |
+
"notes": "A 128-row query tile uses 512 workgroup threads and the generic cooperative loader. Both GEMM phases stage tiles with a workgroup-strided linear walk, and the fused-softmax apply narrows each normalized score to float16 when staging it."
|
| 5536 |
+
},
|
| 5537 |
+
"tunables": { "MATERIALIZED_SGMAT_QUERY_TILE": 128 },
|
| 5538 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 5539 |
+
"inputs": {
|
| 5540 |
+
"queryT": {
|
| 5541 |
+
"dtype": "float16",
|
| 5542 |
+
"shape": [1, 512, 128],
|
| 5543 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
|
| 5544 |
+
},
|
| 5545 |
+
"keyT": { "dtype": "float16", "shape": [1, 0, 64], "data": { "kind": "values", "values": [] } },
|
| 5546 |
+
"valueT": { "dtype": "float16", "shape": [1, 0, 64], "data": { "kind": "values", "values": [] } },
|
| 5547 |
+
"pastKeyT": {
|
| 5548 |
+
"dtype": "float16",
|
| 5549 |
+
"shape": [1, 1, 512, 64],
|
| 5550 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
|
| 5551 |
+
},
|
| 5552 |
+
"pastValueT": {
|
| 5553 |
+
"dtype": "float16",
|
| 5554 |
+
"shape": [1, 1, 512, 64],
|
| 5555 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41 }
|
| 5556 |
+
},
|
| 5557 |
+
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [511] } },
|
| 5558 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [512] } }
|
| 5559 |
+
},
|
| 5560 |
+
"outputs": {
|
| 5561 |
+
"outputT": { "dtype": "float16", "shape": [1, 512, 128], "tolerance": 0.03 },
|
| 5562 |
+
"presentKeyT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.001 },
|
| 5563 |
+
"presentValueT": { "dtype": "float16", "shape": [1, 1, 512, 64], "tolerance": 0.001 }
|
| 5564 |
+
}
|
| 5565 |
+
},
|
| 5566 |
{
|
| 5567 |
"name": "past_kv_materialized_sgmat_f16_floor_q256_p256_h2kv1_d64",
|
| 5568 |
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
|
|
|
| 5702 |
"presentKeyT": { "dtype": "float32", "shape": [2, 1, 256, 64], "tolerance": 0.000001 },
|
| 5703 |
"presentValueT": { "dtype": "float32", "shape": [2, 1, 256, 64], "tolerance": 0.000001 }
|
| 5704 |
}
|
| 5705 |
+
},
|
| 5706 |
+
{
|
| 5707 |
+
"name": "flashdecode_splitk_h8kv2_d16_kv1024",
|
| 5708 |
+
"attrs": { "num_heads": 8, "kv_num_heads": 2, "scale": 0.25 },
|
| 5709 |
+
"inputs": {
|
| 5710 |
+
"queryT": {
|
| 5711 |
+
"dtype": "float32",
|
| 5712 |
+
"shape": [1, 1, 128],
|
| 5713 |
+
"data": { "kind": "fillFloat32", "scale": 2.0, "sinStep": 0.011, "cosStep": 0.029 }
|
| 5714 |
+
},
|
| 5715 |
+
"keyT": { "dtype": "float32", "shape": [1, 0, 32], "data": { "kind": "values", "values": [] } },
|
| 5716 |
+
"valueT": { "dtype": "float32", "shape": [1, 0, 32], "data": { "kind": "values", "values": [] } },
|
| 5717 |
+
"pastKeyT": {
|
| 5718 |
+
"dtype": "float32",
|
| 5719 |
+
"shape": [1, 2, 1024, 16],
|
| 5720 |
+
"data": { "kind": "fillFloat32", "scale": 0.2, "sinStep": 0.017, "cosStep": 0.019 }
|
| 5721 |
+
},
|
| 5722 |
+
"pastValueT": {
|
| 5723 |
+
"dtype": "float32",
|
| 5724 |
+
"shape": [1, 2, 1024, 16],
|
| 5725 |
+
"data": { "kind": "fillFloat32", "scale": 3.0, "sinStep": 0.023, "cosStep": 0.013 }
|
| 5726 |
+
},
|
| 5727 |
+
"seqlensKT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1023] } },
|
| 5728 |
+
"totalSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [1024] } }
|
| 5729 |
+
},
|
| 5730 |
+
"outputs": {
|
| 5731 |
+
"outputT": { "dtype": "float32", "shape": [1, 1, 128], "tolerance": 0.0001 },
|
| 5732 |
+
"presentKeyT": { "dtype": "float32", "shape": [1, 2, 1024, 16], "tolerance": 0.0001 },
|
| 5733 |
+
"presentValueT": { "dtype": "float32", "shape": [1, 2, 1024, 16], "tolerance": 0.0001 }
|
| 5734 |
+
},
|
| 5735 |
+
"provenance": {
|
| 5736 |
+
"notes": "Cached decode at head size 16 exercises the split-K route exactly at its minimum head-size boundary."
|
| 5737 |
+
}
|
| 5738 |
}
|
| 5739 |
]
|
| 5740 |
}
|