sync 2e7068faf55e
Browse files- README.md +95 -0
- build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja +113 -0
- build/webgpu/bench.json +156 -0
- build/webgpu/manifest.json +792 -0
- build/webgpu/metadata.json +20 -0
- build/webgpu/paged-attention.wgsl.jinja +419 -0
- build/webgpu/paged-scatter-kv.wgsl.jinja +121 -0
- build/webgpu/test.json +803 -0
README.md
CHANGED
|
@@ -1,3 +1,98 @@
|
|
| 1 |
---
|
|
|
|
| 2 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
library_name: kernels
|
| 3 |
license: apache-2.0
|
| 4 |
+
tags:
|
| 5 |
+
- kernel
|
| 6 |
+
- webgpu
|
| 7 |
+
- wgsl
|
| 8 |
---
|
| 9 |
+
# com.microsoft.PagedAttention
|
| 10 |
+
|
| 11 |
+
`com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Attention over a block-based (paged) KV cache: `cumulative_sequence_length` marks the sequence boundaries and `block_table` maps a sequence's history onto scattered blocks. This step's K/V are scattered into the cache, then attended with that history. Grouped-query heads, `scale`, packed `[Q|K|V]`, `slot_mapping`, and float16 cache storage are supported; the cache outputs alias the input caches and are updated in place. Rotary embeddings, softcap, local windows, LATENT layout, narrower value heads, quantized KV, head sinks, q/k normalization, scales, and attention metadata are not implemented.
|
| 16 |
+
|
| 17 |
+
See the [ONNX Runtime `PagedAttention` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.PagedAttention) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `query` | `queryT` | `T` | same as logical dtype | `2` | — | Packed queries of shape `(num_tokens, num_heads * head_size)`, or `(num_tokens, (num_heads + 2 * kv_num_heads) * head_size)` when `key` and `value` are absent and Q, K and V share one row. | required |
|
| 24 |
+
| `key` | `keyT` | `T` | same as logical dtype | `2` | — | Keys of shape `(num_tokens, kv_num_heads * head_size)`. Absent means `query` carries packed `[Q\|K\|V]`. | optional |
|
| 25 |
+
| `value` | `valueT` | `T` | same as logical dtype | `2` | — | Values of shape `(num_tokens, kv_num_heads * head_size)`. Present exactly when `key` is. | optional |
|
| 26 |
+
| `key_cache` | `keyCacheT` | `T` | same as logical dtype | `4` | — | Block-based key cache of shape `(num_blocks, block_size, kv_num_heads, head_size)`, updated in place. | required |
|
| 27 |
+
| `value_cache` | `valueCacheT` | `T` | same as logical dtype | `4` | — | Block-based value cache with the same shape as `key_cache`, updated in place. | required |
|
| 28 |
+
| `cumulative_sequence_length` | `cumulativeSequenceLengthT` | `S` | `int32` | `1` | — | Exclusive prefix sums of the per-sequence token counts, shape `(batch_size + 1)`; sequence `b` owns packed tokens `[cum[b], cum[b+1])`. | required |
|
| 29 |
+
| `past_seqlens` | `pastSeqlensT` | `S` | `int32` | `1` | — | Cached history length per sequence, shape `(batch_size)`. | required |
|
| 30 |
+
| `block_table` | `blockTableT` | `S` | `int32` | `2` | — | Physical block index per sequence and logical block, shape `(batch_size, max_blocks_per_sequence)`. | required |
|
| 31 |
+
| `slot_mapping` | `slotMappingT` | `S` | `int32` | `1` | — | Flat destination slot, `block_id * block_size + offset`, for each token; `-1` suppresses that token's cache write. When omitted, the slot is derived from `past_seqlens`. `block_table` remains required because it defines the read path. | optional |
|
| 32 |
+
|
| 33 |
+
## Outputs
|
| 34 |
+
|
| 35 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 36 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 37 |
+
| `output` | `outputT` | `T` | `2` | derived; see description | Attention output of shape `(num_tokens, num_heads * head_size)`. | required |
|
| 38 |
+
| `key_cache` | `keyCacheT` | `T` | `4` | same as `key_cache` | Optional return alias for the updated in-place key cache. The runtime updates both caches together even when only this alias is requested. | optional |
|
| 39 |
+
| `value_cache` | `valueCacheT` | `T` | `4` | same as `value_cache` | Optional return alias for the updated in-place value cache. The runtime updates both caches together even when only this alias is requested. | optional |
|
| 40 |
+
|
| 41 |
+
## Attributes
|
| 42 |
+
|
| 43 |
+
Attributes and default values (overridable per request):
|
| 44 |
+
|
| 45 |
+
| Attribute | Default | Description |
|
| 46 |
+
| --- | --- | --- |
|
| 47 |
+
| `is_causal` | `1` | Whether to apply causal masking. This package supports only value 1. Older ORT schema revisions omit this attribute and are always causal. |
|
| 48 |
+
| `kv_num_heads` | — | Number of key/value heads. |
|
| 49 |
+
| `num_heads` | — | Number of query heads. |
|
| 50 |
+
| `scale` | — | Scale applied to query-key products; zero or omission selects `1 / sqrt(head_size)`. |
|
| 51 |
+
|
| 52 |
+
## Type constraints
|
| 53 |
+
|
| 54 |
+
| Variable | Allowed dtypes |
|
| 55 |
+
| --- | --- |
|
| 56 |
+
| `T` | `float16` |
|
| 57 |
+
| `S` | `int32` |
|
| 58 |
+
|
| 59 |
+
## Device requirements
|
| 60 |
+
|
| 61 |
+
Every implementation variant requires `shader-f16`; the package has no variant-level fallback without that capability.
|
| 62 |
+
|
| 63 |
+
## Files
|
| 64 |
+
|
| 65 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 66 |
+
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 67 |
+
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 68 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
| 69 |
+
- [`attn-flash-decode-splitk-merge.wgsl.jinja`](build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja)
|
| 70 |
+
- [`paged-attention.wgsl.jinja`](build/webgpu/paged-attention.wgsl.jinja)
|
| 71 |
+
- [`paged-scatter-kv.wgsl.jinja`](build/webgpu/paged-scatter-kv.wgsl.jinja)
|
| 72 |
+
|
| 73 |
+
## Use with `@huggingface/kernels`
|
| 74 |
+
|
| 75 |
+
The loader derives every required output's shape and logical dtype from the manifest contract and this call.
|
| 76 |
+
It then allocates the result tensors automatically.
|
| 77 |
+
|
| 78 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 79 |
+
|
| 80 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 81 |
+
|
| 82 |
+
```js
|
| 83 |
+
import { getKernel } from "@huggingface/kernels";
|
| 84 |
+
|
| 85 |
+
const kernel = await getKernel("webgpu-kernels/com.microsoft.PagedAttention", { version: 1 });
|
| 86 |
+
const { keyCacheT, valueCacheT, outputT } = await kernel({
|
| 87 |
+
queryT: { data: queryTData, shape: [2, 4] },
|
| 88 |
+
keyT: { data: keyTData, shape: [2, 2] },
|
| 89 |
+
valueT: { data: valueTData, shape: [2, 2] },
|
| 90 |
+
keyCacheT: { data: keyCacheTData, shape: [3, 2, 1, 2] },
|
| 91 |
+
valueCacheT: { data: valueCacheTData, shape: [3, 2, 1, 2] },
|
| 92 |
+
cumulativeSequenceLengthT: { data: cumulativeSequenceLengthTData, shape: [2] },
|
| 93 |
+
pastSeqlensT: { data: pastSeqlensTData, shape: [1] },
|
| 94 |
+
blockTableT: { data: blockTableTData, shape: [1, 3] },
|
| 95 |
+
}, {
|
| 96 |
+
attrs: { num_heads: 2, kv_num_heads: 1 },
|
| 97 |
+
});
|
| 98 |
+
```
|
build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% if scalar == "f16" %}
|
| 2 |
+
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
|
| 9 |
+
// states (max, denom, weighted V sum) written by the split pass into the final
|
| 10 |
+
// attention output for each query token, using the online softmax merge:
|
| 11 |
+
// globalMax = max_s partialMax[s]
|
| 12 |
+
// globalDen = sum_s partialDenom[s] * exp(partialMax[s] - globalMax)
|
| 13 |
+
// out[d] = (sum_s partialOut[s, d] * exp(partialMax[s] - globalMax)) / globalDen
|
| 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 |
+
// Output layout: token-major [batch, seq, heads * headDim], matching the split pass.
|
| 18 |
+
const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
|
| 19 |
+
const Q_HEADS: u32 = {{ qNumHeads }}u;
|
| 20 |
+
const NUM_SPLITS: u32 = {{ numSplits }}u;
|
| 21 |
+
{% if splitQueries %}
|
| 22 |
+
const Q_SEQ: u32 = {{ qSeq }}u;
|
| 23 |
+
{% endif %}
|
| 24 |
+
const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
|
| 25 |
+
{% if hasBias %}
|
| 26 |
+
// Packed [Q; K; V] bias rows. The V bias is token-independent, so the split
|
| 27 |
+
// pass leaves it off and it is added here once after the final normalize,
|
| 28 |
+
// matching the non-split flash-decode semantics exactly.
|
| 29 |
+
const HEAD_DIM: u32 = {{ headDim }}u;
|
| 30 |
+
const Q_HIDDEN: u32 = {{ qHidden }}u;
|
| 31 |
+
{% endif %}
|
| 32 |
+
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 33 |
+
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 34 |
+
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
| 35 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 36 |
+
// here changes +inf-row behavior.
|
| 37 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 38 |
+
|
| 39 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 40 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 44 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 45 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 46 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 47 |
+
fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
| 48 |
+
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 49 |
+
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 50 |
+
}
|
| 51 |
+
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 52 |
+
return exp(shifted_value(value, maxValue));
|
| 53 |
+
}
|
| 54 |
+
@compute @workgroup_size(HEAD_DIM_V4, 1, 1)
|
| 55 |
+
fn main(
|
| 56 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 57 |
+
@builtin(local_invocation_id) lid: vec3<u32>
|
| 58 |
+
) {
|
| 59 |
+
{% if splitQueries %}
|
| 60 |
+
let queryToken = wg.x;
|
| 61 |
+
{% endif %}
|
| 62 |
+
let h = wg.y;
|
| 63 |
+
let b = wg.z;
|
| 64 |
+
let d4 = lid.x;
|
| 65 |
+
if (h >= Q_HEADS || d4 >= HEAD_DIM_V4{% if splitQueries %} || queryToken >= Q_SEQ{% endif %}) {
|
| 66 |
+
return;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
{% if splitQueries %}
|
| 70 |
+
let mdBase = ((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS;
|
| 71 |
+
{% else %}
|
| 72 |
+
let mdBase = (b * Q_HEADS + h) * NUM_SPLITS;
|
| 73 |
+
{% endif %}
|
| 74 |
+
var globalMax = -FLT_MAX;
|
| 75 |
+
for (var s: u32 = 0u; s < NUM_SPLITS; s = s + 1u) {
|
| 76 |
+
globalMax = max(globalMax, partial_stats[mdBase + s].x);
|
| 77 |
+
}
|
| 78 |
+
var globalDenom = 0.0;
|
| 79 |
+
var outV = vec4<f32>(0.0);
|
| 80 |
+
for (var s: u32 = 0u; s < NUM_SPLITS; s = s + 1u) {
|
| 81 |
+
let stats = partial_stats[mdBase + s];
|
| 82 |
+
let w = exp_shift(stats.x, globalMax);
|
| 83 |
+
globalDenom = globalDenom + stats.y * w;
|
| 84 |
+
{% if splitQueries %}
|
| 85 |
+
let pBase = (((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS + s) * HEAD_DIM_V4;
|
| 86 |
+
{% else %}
|
| 87 |
+
let pBase = ((b * Q_HEADS + h) * NUM_SPLITS + s) * HEAD_DIM_V4;
|
| 88 |
+
{% endif %}
|
| 89 |
+
outV = outV + partial_out[pBase + d4] * w;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
{% if splitQueries %}
|
| 93 |
+
let qBaseV4 = (b * Q_SEQ + queryToken) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 94 |
+
{% else %}
|
| 95 |
+
let qBaseV4 = b * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
|
| 96 |
+
{% endif %}
|
| 97 |
+
// A split that covers no keys writes vec2(-FLT_MAX, 0), so when every split is empty —
|
| 98 |
+
// a query whose window admits nothing — globalMax stays -FLT_MAX, exp_shift(x, x) is 1,
|
| 99 |
+
// and globalDenom lands on exactly 0. Emit 0 rather than dividing so an all-empty
|
| 100 |
+
// row has a defined zero result.
|
| 101 |
+
var outValue = select(vec4<f32>(0.0), outV / globalDenom, globalDenom > 0.0);
|
| 102 |
+
{% if hasBias %}
|
| 103 |
+
// V bias row base: 2 * qHidden (skip the packed Q and K bias blocks) + this head.
|
| 104 |
+
let vBiasBase = 2u * Q_HIDDEN + h * HEAD_DIM + d4 * 4u;
|
| 105 |
+
outValue = outValue + vec4<f32>(bias[vBiasBase], bias[vBiasBase + 1u], bias[vBiasBase + 2u], bias[vBiasBase + 3u]);
|
| 106 |
+
{% endif %}
|
| 107 |
+
{% if hasGate %}
|
| 108 |
+
// Qwen3.5 full attention gates the normalized attention output elementwise.
|
| 109 |
+
let gateV = vec4<f32>(gate[qBaseV4 + d4]);
|
| 110 |
+
outValue = outValue * (vec4<f32>(1.0) / (vec4<f32>(1.0) + exp(-gateV)));
|
| 111 |
+
{% endif %}
|
| 112 |
+
output[qBaseV4 + d4] = vec4<{{ scalar }}>(outValue);
|
| 113 |
+
}
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.PagedAttention",
|
| 3 |
+
"tunableSpace": {
|
| 4 |
+
"WORKGROUP_SIZE": [32, 64, 128],
|
| 5 |
+
"SCATTER_WORKGROUP_SIZE": [64, 128, 256],
|
| 6 |
+
"SPLIT_TARGET_WORKGROUPS": [512, 1024, 2048, 4096],
|
| 7 |
+
"SPLIT_MIN_KEYS": [64, 128, 256],
|
| 8 |
+
"MAX_SPLITS": [8, 16, 32]
|
| 9 |
+
},
|
| 10 |
+
"cases": [
|
| 11 |
+
{
|
| 12 |
+
"name": "paged-decode-b8-past1024-h32kv8-d128",
|
| 13 |
+
"preset": "smoke",
|
| 14 |
+
"vars": { "dtype": "float16", "batch": 8, "past": 1024, "heads": 32, "kvHeads": 8, "headDim": 128 },
|
| 15 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 8 },
|
| 16 |
+
"inputs": {
|
| 17 |
+
"queryT": { "shape": [8, 4096], "dtype": "float16", "dist": "normal", "seed": 9201, "scale": 1 },
|
| 18 |
+
"keyT": { "shape": [8, 1024], "dtype": "float16", "dist": "normal", "seed": 9202, "scale": 1 },
|
| 19 |
+
"valueT": { "shape": [8, 1024], "dtype": "float16", "dist": "normal", "seed": 9203, "scale": 1 },
|
| 20 |
+
"keyCacheT": { "shape": [512, 16, 8, 128], "dtype": "float16", "dist": "normal", "seed": 9204, "scale": 1 },
|
| 21 |
+
"valueCacheT": { "shape": [512, 16, 8, 128], "dtype": "float16", "dist": "normal", "seed": 9205, "scale": 1 },
|
| 22 |
+
"cumulativeSequenceLengthT": {
|
| 23 |
+
"shape": [9],
|
| 24 |
+
"dtype": "int32",
|
| 25 |
+
"data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5, 6, 7, 8] }
|
| 26 |
+
},
|
| 27 |
+
"pastSeqlensT": { "shape": [8], "dtype": "int32", "dist": "constant", "value": 1023 },
|
| 28 |
+
"blockTableT": { "shape": [8, 64], "dtype": "int32", "dist": "linearMod", "seed": 9208, "step": 1, "mod": 512 }
|
| 29 |
+
},
|
| 30 |
+
"outputs": { "outputT": { "shape": [8, 4096], "dtype": "float16" } },
|
| 31 |
+
"bench": {
|
| 32 |
+
"metrics": [{ "type": "bandwidth", "value": "2 * args.batch * args.kvHeads * args.past * args.headDim * 2" }]
|
| 33 |
+
},
|
| 34 |
+
"provenance": {
|
| 35 |
+
"notes": "The KV bytes are counted off named vars like the model cases, not hardcoded: the literal this replaced used the QUERY head count for a grouped-query shape and four bytes for a float16 cache, so it over-stated the traffic eightfold and the case reported an impossible rate."
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"name": "paged-decode-llama-b16-past2048-h32kv32-d128",
|
| 40 |
+
"preset": "model",
|
| 41 |
+
"provenance": {
|
| 42 |
+
"notes": "Llama class defaults (32 query heads, no GQA, head_dim 128) decoding 16 concurrent sequences with 2048 tokens of paged KV each."
|
| 43 |
+
},
|
| 44 |
+
"vars": { "dtype": "float16", "batch": 16, "past": 2048, "heads": 32, "kvHeads": 32, "headDim": 128 },
|
| 45 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 32 },
|
| 46 |
+
"inputs": {
|
| 47 |
+
"queryT": { "shape": [16, 4096], "dtype": "float16", "dist": "normal", "seed": 7600, "scale": 0.3 },
|
| 48 |
+
"keyT": { "shape": [16, 4096], "dtype": "float16", "dist": "normal", "seed": 7601, "scale": 0.3 },
|
| 49 |
+
"valueT": { "shape": [16, 4096], "dtype": "float16", "dist": "normal", "seed": 7602, "scale": 0.3 },
|
| 50 |
+
"keyCacheT": { "shape": [2048, 16, 32, 128], "dtype": "float16", "dist": "normal", "seed": 7603, "scale": 0.3 },
|
| 51 |
+
"valueCacheT": {
|
| 52 |
+
"shape": [2048, 16, 32, 128],
|
| 53 |
+
"dtype": "float16",
|
| 54 |
+
"dist": "normal",
|
| 55 |
+
"seed": 7604,
|
| 56 |
+
"scale": 0.3
|
| 57 |
+
},
|
| 58 |
+
"cumulativeSequenceLengthT": {
|
| 59 |
+
"shape": [17],
|
| 60 |
+
"dtype": "int32",
|
| 61 |
+
"dist": "linearMod",
|
| 62 |
+
"seed": 7605,
|
| 63 |
+
"step": 1,
|
| 64 |
+
"mod": 17
|
| 65 |
+
},
|
| 66 |
+
"pastSeqlensT": { "shape": [16], "dtype": "int32", "dist": "constant", "value": 2048 },
|
| 67 |
+
"blockTableT": {
|
| 68 |
+
"shape": [16, 128],
|
| 69 |
+
"dtype": "int32",
|
| 70 |
+
"dist": "linearMod",
|
| 71 |
+
"seed": 7606,
|
| 72 |
+
"step": 1,
|
| 73 |
+
"mod": 2048
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"outputs": { "outputT": { "shape": [16, 4096], "dtype": "float16" } },
|
| 77 |
+
"bench": {
|
| 78 |
+
"metrics": [{ "type": "bandwidth", "value": "2 * args.batch * args.kvHeads * args.past * args.headDim * 2" }]
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"name": "paged-decode-mistral-b16-past4096-h32kv8-d128",
|
| 83 |
+
"preset": "model",
|
| 84 |
+
"provenance": {
|
| 85 |
+
"notes": "Mistral class defaults (32 query heads, 8 key/value heads, head_dim 128) at 4096 tokens of history -- the GQA case, where the cache is a quarter the size for the same context."
|
| 86 |
+
},
|
| 87 |
+
"vars": { "dtype": "float16", "batch": 16, "past": 4096, "heads": 32, "kvHeads": 8, "headDim": 128 },
|
| 88 |
+
"attrs": { "num_heads": 32, "kv_num_heads": 8 },
|
| 89 |
+
"inputs": {
|
| 90 |
+
"queryT": { "shape": [16, 4096], "dtype": "float16", "dist": "normal", "seed": 7700, "scale": 0.3 },
|
| 91 |
+
"keyT": { "shape": [16, 1024], "dtype": "float16", "dist": "normal", "seed": 7701, "scale": 0.3 },
|
| 92 |
+
"valueT": { "shape": [16, 1024], "dtype": "float16", "dist": "normal", "seed": 7702, "scale": 0.3 },
|
| 93 |
+
"keyCacheT": { "shape": [4096, 16, 8, 128], "dtype": "float16", "dist": "normal", "seed": 7703, "scale": 0.3 },
|
| 94 |
+
"valueCacheT": { "shape": [4096, 16, 8, 128], "dtype": "float16", "dist": "normal", "seed": 7704, "scale": 0.3 },
|
| 95 |
+
"cumulativeSequenceLengthT": {
|
| 96 |
+
"shape": [17],
|
| 97 |
+
"dtype": "int32",
|
| 98 |
+
"dist": "linearMod",
|
| 99 |
+
"seed": 7705,
|
| 100 |
+
"step": 1,
|
| 101 |
+
"mod": 17
|
| 102 |
+
},
|
| 103 |
+
"pastSeqlensT": { "shape": [16], "dtype": "int32", "dist": "constant", "value": 4096 },
|
| 104 |
+
"blockTableT": {
|
| 105 |
+
"shape": [16, 256],
|
| 106 |
+
"dtype": "int32",
|
| 107 |
+
"dist": "linearMod",
|
| 108 |
+
"seed": 7706,
|
| 109 |
+
"step": 1,
|
| 110 |
+
"mod": 4096
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"outputs": { "outputT": { "shape": [16, 4096], "dtype": "float16" } },
|
| 114 |
+
"bench": {
|
| 115 |
+
"metrics": [{ "type": "bandwidth", "value": "2 * args.batch * args.kvHeads * args.past * args.headDim * 2" }]
|
| 116 |
+
}
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"name": "paged-decode-gemma3-b8-past2048-h8kv4-d256",
|
| 120 |
+
"preset": "model",
|
| 121 |
+
"provenance": {
|
| 122 |
+
"notes": "Gemma3 class defaults (8 query heads, 4 key/value heads, head_dim 256) -- few heads but the widest head dimension in this set."
|
| 123 |
+
},
|
| 124 |
+
"vars": { "dtype": "float16", "batch": 8, "past": 2048, "heads": 8, "kvHeads": 4, "headDim": 256 },
|
| 125 |
+
"attrs": { "num_heads": 8, "kv_num_heads": 4 },
|
| 126 |
+
"inputs": {
|
| 127 |
+
"queryT": { "shape": [8, 2048], "dtype": "float16", "dist": "normal", "seed": 7800, "scale": 0.3 },
|
| 128 |
+
"keyT": { "shape": [8, 1024], "dtype": "float16", "dist": "normal", "seed": 7801, "scale": 0.3 },
|
| 129 |
+
"valueT": { "shape": [8, 1024], "dtype": "float16", "dist": "normal", "seed": 7802, "scale": 0.3 },
|
| 130 |
+
"keyCacheT": { "shape": [1024, 16, 4, 256], "dtype": "float16", "dist": "normal", "seed": 7803, "scale": 0.3 },
|
| 131 |
+
"valueCacheT": { "shape": [1024, 16, 4, 256], "dtype": "float16", "dist": "normal", "seed": 7804, "scale": 0.3 },
|
| 132 |
+
"cumulativeSequenceLengthT": {
|
| 133 |
+
"shape": [9],
|
| 134 |
+
"dtype": "int32",
|
| 135 |
+
"dist": "linearMod",
|
| 136 |
+
"seed": 7805,
|
| 137 |
+
"step": 1,
|
| 138 |
+
"mod": 9
|
| 139 |
+
},
|
| 140 |
+
"pastSeqlensT": { "shape": [8], "dtype": "int32", "dist": "constant", "value": 2048 },
|
| 141 |
+
"blockTableT": {
|
| 142 |
+
"shape": [8, 128],
|
| 143 |
+
"dtype": "int32",
|
| 144 |
+
"dist": "linearMod",
|
| 145 |
+
"seed": 7806,
|
| 146 |
+
"step": 1,
|
| 147 |
+
"mod": 1024
|
| 148 |
+
}
|
| 149 |
+
},
|
| 150 |
+
"outputs": { "outputT": { "shape": [8, 2048], "dtype": "float16" } },
|
| 151 |
+
"bench": {
|
| 152 |
+
"metrics": [{ "type": "bandwidth", "value": "2 * args.batch * args.kvHeads * args.past * args.headDim * 2" }]
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
]
|
| 156 |
+
}
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "com.microsoft",
|
| 3 |
+
"name": "PagedAttention",
|
| 4 |
+
"sinceVersion": 1,
|
| 5 |
+
"description": "Attention over a block-based (paged) KV cache: `cumulative_sequence_length` marks the sequence boundaries and `block_table` maps a sequence's history onto scattered blocks. This step's K/V are scattered into the cache, then attended with that history. Grouped-query heads, `scale`, packed `[Q|K|V]`, `slot_mapping`, and float16 cache storage are supported; the cache outputs alias the input caches and are updated in place. Rotary embeddings, softcap, local windows, LATENT layout, narrower value heads, quantized KV, head sinks, q/k normalization, scales, and attention metadata are not implemented.",
|
| 6 |
+
"inputs": [
|
| 7 |
+
{
|
| 8 |
+
"role": "query",
|
| 9 |
+
"dtype": "T",
|
| 10 |
+
"rank": 2,
|
| 11 |
+
"description": "Packed queries of shape `(num_tokens, num_heads * head_size)`, or `(num_tokens, (num_heads + 2 * kv_num_heads) * head_size)` when `key` and `value` are absent and Q, K and V share one row."
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"role": "key",
|
| 15 |
+
"dtype": "T",
|
| 16 |
+
"rank": 2,
|
| 17 |
+
"optional": true,
|
| 18 |
+
"description": "Keys of shape `(num_tokens, kv_num_heads * head_size)`. Absent means `query` carries packed `[Q|K|V]`."
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"role": "value",
|
| 22 |
+
"dtype": "T",
|
| 23 |
+
"rank": 2,
|
| 24 |
+
"optional": true,
|
| 25 |
+
"description": "Values of shape `(num_tokens, kv_num_heads * head_size)`. Present exactly when `key` is."
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"role": "key_cache",
|
| 29 |
+
"dtype": "T",
|
| 30 |
+
"rank": 4,
|
| 31 |
+
"description": "Block-based key cache of shape `(num_blocks, block_size, kv_num_heads, head_size)`, updated in place."
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"role": "value_cache",
|
| 35 |
+
"dtype": "T",
|
| 36 |
+
"rank": 4,
|
| 37 |
+
"description": "Block-based value cache with the same shape as `key_cache`, updated in place."
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"role": "cumulative_sequence_length",
|
| 41 |
+
"dtype": "S",
|
| 42 |
+
"rank": 1,
|
| 43 |
+
"description": "Exclusive prefix sums of the per-sequence token counts, shape `(batch_size + 1)`; sequence `b` owns packed tokens `[cum[b], cum[b+1])`."
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"role": "past_seqlens",
|
| 47 |
+
"dtype": "S",
|
| 48 |
+
"rank": 1,
|
| 49 |
+
"description": "Cached history length per sequence, shape `(batch_size)`."
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"role": "block_table",
|
| 53 |
+
"dtype": "S",
|
| 54 |
+
"rank": 2,
|
| 55 |
+
"description": "Physical block index per sequence and logical block, shape `(batch_size, max_blocks_per_sequence)`."
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"role": "slot_mapping",
|
| 59 |
+
"dtype": "S",
|
| 60 |
+
"rank": 1,
|
| 61 |
+
"optional": true,
|
| 62 |
+
"description": "Flat destination slot, `block_id * block_size + offset`, for each token; `-1` suppresses that token's cache write. When omitted, the slot is derived from `past_seqlens`. `block_table` remains required because it defines the read path."
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"outputs": [
|
| 66 |
+
{
|
| 67 |
+
"role": "output",
|
| 68 |
+
"dtype": "T",
|
| 69 |
+
"rank": 2,
|
| 70 |
+
"shape": "[dim(shapes.queryT, 0), attrs.num_heads * headSize]",
|
| 71 |
+
"description": "Attention output of shape `(num_tokens, num_heads * head_size)`."
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"role": "key_cache",
|
| 75 |
+
"dtype": "T",
|
| 76 |
+
"rank": 4,
|
| 77 |
+
"shape": "shapes.keyCacheT",
|
| 78 |
+
"optional": true,
|
| 79 |
+
"description": "Optional return alias for the updated in-place key cache. The runtime updates both caches together even when only this alias is requested."
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"role": "value_cache",
|
| 83 |
+
"dtype": "T",
|
| 84 |
+
"rank": 4,
|
| 85 |
+
"shape": "shapes.valueCacheT",
|
| 86 |
+
"optional": true,
|
| 87 |
+
"description": "Optional return alias for the updated in-place value cache. The runtime updates both caches together even when only this alias is requested."
|
| 88 |
+
}
|
| 89 |
+
],
|
| 90 |
+
"attributes": { "is_causal": 1 },
|
| 91 |
+
"attributeDescriptions": {
|
| 92 |
+
"is_causal": "Whether to apply causal masking. This package supports only value 1. Older ORT schema revisions omit this attribute and are always causal.",
|
| 93 |
+
"kv_num_heads": "Number of key/value heads.",
|
| 94 |
+
"num_heads": "Number of query heads.",
|
| 95 |
+
"scale": "Scale applied to query-key products; zero or omission selects `1 / sqrt(head_size)`."
|
| 96 |
+
},
|
| 97 |
+
"attributeConstraints": {
|
| 98 |
+
"is_causal": { "values": [1] },
|
| 99 |
+
"kv_num_heads": { "required": true },
|
| 100 |
+
"num_heads": { "required": true }
|
| 101 |
+
},
|
| 102 |
+
"typeConstraints": { "T": ["float16"], "S": ["int32"] },
|
| 103 |
+
"args": {
|
| 104 |
+
"queryT": { "kind": "tensor", "semantic": "query", "role": "input" },
|
| 105 |
+
"keyT": { "kind": "tensor", "semantic": "key", "role": "input", "required": false },
|
| 106 |
+
"valueT": { "kind": "tensor", "semantic": "value", "role": "input", "required": false },
|
| 107 |
+
"keyCacheT": { "kind": "tensor", "semantic": "key_cache", "role": "inout" },
|
| 108 |
+
"valueCacheT": { "kind": "tensor", "semantic": "value_cache", "role": "inout" },
|
| 109 |
+
"cumulativeSequenceLengthT": {
|
| 110 |
+
"kind": "tensor",
|
| 111 |
+
"semantic": "cumulative_sequence_length",
|
| 112 |
+
"role": "input",
|
| 113 |
+
"dtype": "int32"
|
| 114 |
+
},
|
| 115 |
+
"pastSeqlensT": { "kind": "tensor", "semantic": "past_seqlens", "role": "input", "dtype": "int32" },
|
| 116 |
+
"blockTableT": { "kind": "tensor", "semantic": "block_table", "role": "input", "dtype": "int32" },
|
| 117 |
+
"slotMappingT": {
|
| 118 |
+
"kind": "tensor",
|
| 119 |
+
"semantic": "slot_mapping",
|
| 120 |
+
"role": "input",
|
| 121 |
+
"dtype": "int32",
|
| 122 |
+
"required": false
|
| 123 |
+
},
|
| 124 |
+
"outputT": { "kind": "tensor", "semantic": "output", "role": "output" }
|
| 125 |
+
},
|
| 126 |
+
"tunables": {
|
| 127 |
+
"WORKGROUP_SIZE": 64,
|
| 128 |
+
"SCATTER_WORKGROUP_SIZE": 64,
|
| 129 |
+
"MAX_SPLITS": 16,
|
| 130 |
+
"SPLIT_MIN_KEYS": 128,
|
| 131 |
+
"SPLIT_TARGET_WORKGROUPS": 1024
|
| 132 |
+
},
|
| 133 |
+
"derive": {
|
| 134 |
+
"tokenCount": "dim(shapes.queryT, 0)",
|
| 135 |
+
"blockSize": "dim(shapes.keyCacheT, 1)",
|
| 136 |
+
"headSize": "dim(shapes.keyCacheT, 3)",
|
| 137 |
+
"maxBlocks": "dim(shapes.blockTableT, 1)",
|
| 138 |
+
"batchSize": "dim(shapes.cumulativeSequenceLengthT, 0) - 1",
|
| 139 |
+
"qHidden": "attrs.num_heads * headSize",
|
| 140 |
+
"kvHidden": "attrs.kv_num_heads * headSize",
|
| 141 |
+
"qPerKv": "attrs.num_heads / max(1, attrs.kv_num_heads)",
|
| 142 |
+
"headVec": "headSize / 4",
|
| 143 |
+
"cacheVec4Ok": "headSize % 4 == 0",
|
| 144 |
+
"packedStride": "qHidden + 2 * kvHidden",
|
| 145 |
+
"cacheShapeOk": "ranks.keyCacheT == 4 and ranks.valueCacheT == 4 and sameShape(shapes.valueCacheT, shapes.keyCacheT) and dim(shapes.keyCacheT, 2) == attrs.kv_num_heads and blockSize > 0 and headSize > 0 and tensorDtypes.keyCacheT == tensorDtypes.queryT and tensorDtypes.valueCacheT == tensorDtypes.queryT",
|
| 146 |
+
"headLayoutOk": "attrs.num_heads > 0 and attrs.kv_num_heads > 0 and attrs.num_heads % attrs.kv_num_heads == 0",
|
| 147 |
+
"scheduleShapeOk": "ranks.cumulativeSequenceLengthT == 1 and ranks.pastSeqlensT == 1 and ranks.blockTableT == 2 and batchSize >= 1 and dim(shapes.pastSeqlensT, 0) == batchSize and dim(shapes.blockTableT, 0) == batchSize and maxBlocks >= 1",
|
| 148 |
+
"ioShapeOk": "ranks.queryT == 2 and ranks.outputT == 2 and dim(shapes.outputT, 0) == tokenCount and dim(shapes.outputT, 1) == qHidden and tensorDtypes.outputT == tensorDtypes.queryT and f16Ok(tensorDtypes.queryT)",
|
| 149 |
+
"separateKv": "present.keyT and present.valueT and ranks.keyT == 2 and ranks.valueT == 2 and dim(shapes.keyT, 0) == tokenCount and dim(shapes.valueT, 0) == tokenCount and dim(shapes.keyT, 1) == kvHidden and dim(shapes.valueT, 1) == kvHidden and tensorDtypes.keyT == tensorDtypes.queryT and tensorDtypes.valueT == tensorDtypes.queryT and dim(shapes.queryT, 1) == qHidden",
|
| 150 |
+
"packedKv": "not present.keyT and not present.valueT and dim(shapes.queryT, 1) == packedStride",
|
| 151 |
+
"slotShapeOk": "dim(shapes.slotMappingT, 0) == tokenCount and ranks.slotMappingT == 1 if present.slotMappingT else true",
|
| 152 |
+
"pagedContractOk": "cacheShapeOk and headLayoutOk and scheduleShapeOk and ioShapeOk and slotShapeOk",
|
| 153 |
+
"dispatchFits": "tokenCount <= device.limits.maxComputeWorkgroupsPerDimension and attrs.num_heads <= device.limits.maxComputeWorkgroupsPerDimension and tunables.WORKGROUP_SIZE <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.SCATTER_WORKGROUP_SIZE <= device.limits.maxComputeInvocationsPerWorkgroup",
|
| 154 |
+
"pagedBaseWorkgroups": "tokenCount * attrs.kv_num_heads",
|
| 155 |
+
"pagedKeyCeiling": "maxBlocks * blockSize",
|
| 156 |
+
"pagedSplitWg": "min(256, max(32, pow2ceil(headVec)))",
|
| 157 |
+
"pagedSplitCap": "max(1, min(tunables.MAX_SPLITS, pagedKeyCeiling / tunables.SPLIT_MIN_KEYS))",
|
| 158 |
+
"numSplits": "max(1, min(pagedSplitCap, ceilDiv(tunables.SPLIT_TARGET_WORKGROUPS, max(1, pagedBaseWorkgroups))))",
|
| 159 |
+
"pagedSplitScratchBytes": "tokenCount * attrs.num_heads * numSplits * headSize * 4",
|
| 160 |
+
"pagedSplitStatsBytes": "2 * tokenCount * attrs.num_heads * numSplits * 4",
|
| 161 |
+
"pagedSplitFits": "numSplits >= 2 and cacheVec4Ok and headVec >= 1 and pagedSplitScratchBytes <= device.limits.maxStorageBufferBindingSize and pagedSplitScratchBytes <= device.limits.maxBufferSize and pagedSplitStatsBytes <= device.limits.maxStorageBufferBindingSize and pagedSplitStatsBytes <= device.limits.maxBufferSize and numSplits <= device.limits.maxComputeWorkgroupsPerDimension and headVec <= device.limits.maxComputeInvocationsPerWorkgroup and headVec <= device.limits.maxComputeWorkgroupSizeX and pagedSplitWg <= device.limits.maxComputeInvocationsPerWorkgroup and pagedSplitWg <= device.limits.maxComputeWorkgroupSizeX"
|
| 162 |
+
},
|
| 163 |
+
"constants": {
|
| 164 |
+
"aScalar": "dtypes.T",
|
| 165 |
+
"scalar": "dtypes.T",
|
| 166 |
+
"numHeads": "attrs.num_heads",
|
| 167 |
+
"kvNumHeads": "attrs.kv_num_heads",
|
| 168 |
+
"headSize": "headSize",
|
| 169 |
+
"blockSize": "blockSize",
|
| 170 |
+
"maxBlocks": "maxBlocks",
|
| 171 |
+
"qHidden": "qHidden",
|
| 172 |
+
"kvHidden": "kvHidden",
|
| 173 |
+
"packedStride": "packedStride",
|
| 174 |
+
"packedQkv": "not present.keyT",
|
| 175 |
+
"hasSlotMapping": "present.slotMappingT",
|
| 176 |
+
"qPerKv": "qPerKv",
|
| 177 |
+
"attnWorkgroup": "tunables.WORKGROUP_SIZE",
|
| 178 |
+
"headVec": "headVec",
|
| 179 |
+
"cacheVec": "(\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\") if cacheVec4Ok else dtypes.T",
|
| 180 |
+
"cacheVec4Ok": "cacheVec4Ok",
|
| 181 |
+
"numSplits": "numSplits",
|
| 182 |
+
"headDimV4": "headVec",
|
| 183 |
+
"headDim": "headSize",
|
| 184 |
+
"qNumHeads": "attrs.num_heads",
|
| 185 |
+
"qHiddenV4": "qHidden / 4",
|
| 186 |
+
"hasBias": false,
|
| 187 |
+
"outVec": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\""
|
| 188 |
+
},
|
| 189 |
+
"bindingSets": {
|
| 190 |
+
"scatterSeparateDerived": [
|
| 191 |
+
{
|
| 192 |
+
"name": "key",
|
| 193 |
+
"arg": "keyT",
|
| 194 |
+
"semantic": "key",
|
| 195 |
+
"buffer": { "type": "read-only-storage" },
|
| 196 |
+
"elementType": "$aScalar"
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"name": "value",
|
| 200 |
+
"arg": "valueT",
|
| 201 |
+
"semantic": "value",
|
| 202 |
+
"buffer": { "type": "read-only-storage" },
|
| 203 |
+
"elementType": "$aScalar"
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"name": "key_cache",
|
| 207 |
+
"arg": "keyCacheT",
|
| 208 |
+
"semantic": "key_cache",
|
| 209 |
+
"buffer": { "type": "storage" },
|
| 210 |
+
"elementType": "$aScalar"
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"name": "value_cache",
|
| 214 |
+
"arg": "valueCacheT",
|
| 215 |
+
"semantic": "value_cache",
|
| 216 |
+
"buffer": { "type": "storage" },
|
| 217 |
+
"elementType": "$aScalar"
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"name": "cumulative_sequence_length",
|
| 221 |
+
"arg": "cumulativeSequenceLengthT",
|
| 222 |
+
"semantic": "cumulative_sequence_length",
|
| 223 |
+
"buffer": { "type": "read-only-storage" },
|
| 224 |
+
"elementType": "i32"
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"name": "past_seqlens",
|
| 228 |
+
"arg": "pastSeqlensT",
|
| 229 |
+
"semantic": "past_seqlens",
|
| 230 |
+
"buffer": { "type": "read-only-storage" },
|
| 231 |
+
"elementType": "i32"
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"name": "block_table",
|
| 235 |
+
"arg": "blockTableT",
|
| 236 |
+
"semantic": "block_table",
|
| 237 |
+
"buffer": { "type": "read-only-storage" },
|
| 238 |
+
"elementType": "i32"
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"name": "params",
|
| 242 |
+
"semantic": "kernel.params",
|
| 243 |
+
"buffer": { "type": "uniform" },
|
| 244 |
+
"struct": {
|
| 245 |
+
"name": "Params",
|
| 246 |
+
"fields": [
|
| 247 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 248 |
+
{ "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }
|
| 249 |
+
]
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
],
|
| 253 |
+
"scatterSeparateSlot": [
|
| 254 |
+
{
|
| 255 |
+
"name": "key",
|
| 256 |
+
"arg": "keyT",
|
| 257 |
+
"semantic": "key",
|
| 258 |
+
"buffer": { "type": "read-only-storage" },
|
| 259 |
+
"elementType": "$aScalar"
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"name": "value",
|
| 263 |
+
"arg": "valueT",
|
| 264 |
+
"semantic": "value",
|
| 265 |
+
"buffer": { "type": "read-only-storage" },
|
| 266 |
+
"elementType": "$aScalar"
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"name": "key_cache",
|
| 270 |
+
"arg": "keyCacheT",
|
| 271 |
+
"semantic": "key_cache",
|
| 272 |
+
"buffer": { "type": "storage" },
|
| 273 |
+
"elementType": "$aScalar"
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"name": "value_cache",
|
| 277 |
+
"arg": "valueCacheT",
|
| 278 |
+
"semantic": "value_cache",
|
| 279 |
+
"buffer": { "type": "storage" },
|
| 280 |
+
"elementType": "$aScalar"
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"name": "slot_mapping",
|
| 284 |
+
"arg": "slotMappingT",
|
| 285 |
+
"semantic": "slot_mapping",
|
| 286 |
+
"buffer": { "type": "read-only-storage" },
|
| 287 |
+
"elementType": "i32"
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"name": "params",
|
| 291 |
+
"semantic": "kernel.params",
|
| 292 |
+
"buffer": { "type": "uniform" },
|
| 293 |
+
"struct": {
|
| 294 |
+
"name": "Params",
|
| 295 |
+
"fields": [{ "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }]
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
],
|
| 299 |
+
"scatterPackedDerived": [
|
| 300 |
+
{
|
| 301 |
+
"name": "query",
|
| 302 |
+
"arg": "queryT",
|
| 303 |
+
"semantic": "query",
|
| 304 |
+
"buffer": { "type": "read-only-storage" },
|
| 305 |
+
"elementType": "$aScalar"
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"name": "key_cache",
|
| 309 |
+
"arg": "keyCacheT",
|
| 310 |
+
"semantic": "key_cache",
|
| 311 |
+
"buffer": { "type": "storage" },
|
| 312 |
+
"elementType": "$aScalar"
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"name": "value_cache",
|
| 316 |
+
"arg": "valueCacheT",
|
| 317 |
+
"semantic": "value_cache",
|
| 318 |
+
"buffer": { "type": "storage" },
|
| 319 |
+
"elementType": "$aScalar"
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"name": "cumulative_sequence_length",
|
| 323 |
+
"arg": "cumulativeSequenceLengthT",
|
| 324 |
+
"semantic": "cumulative_sequence_length",
|
| 325 |
+
"buffer": { "type": "read-only-storage" },
|
| 326 |
+
"elementType": "i32"
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
"name": "past_seqlens",
|
| 330 |
+
"arg": "pastSeqlensT",
|
| 331 |
+
"semantic": "past_seqlens",
|
| 332 |
+
"buffer": { "type": "read-only-storage" },
|
| 333 |
+
"elementType": "i32"
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"name": "block_table",
|
| 337 |
+
"arg": "blockTableT",
|
| 338 |
+
"semantic": "block_table",
|
| 339 |
+
"buffer": { "type": "read-only-storage" },
|
| 340 |
+
"elementType": "i32"
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"name": "params",
|
| 344 |
+
"semantic": "kernel.params",
|
| 345 |
+
"buffer": { "type": "uniform" },
|
| 346 |
+
"struct": {
|
| 347 |
+
"name": "Params",
|
| 348 |
+
"fields": [
|
| 349 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 350 |
+
{ "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }
|
| 351 |
+
]
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
],
|
| 355 |
+
"scatterPackedSlot": [
|
| 356 |
+
{
|
| 357 |
+
"name": "query",
|
| 358 |
+
"arg": "queryT",
|
| 359 |
+
"semantic": "query",
|
| 360 |
+
"buffer": { "type": "read-only-storage" },
|
| 361 |
+
"elementType": "$aScalar"
|
| 362 |
+
},
|
| 363 |
+
{
|
| 364 |
+
"name": "key_cache",
|
| 365 |
+
"arg": "keyCacheT",
|
| 366 |
+
"semantic": "key_cache",
|
| 367 |
+
"buffer": { "type": "storage" },
|
| 368 |
+
"elementType": "$aScalar"
|
| 369 |
+
},
|
| 370 |
+
{
|
| 371 |
+
"name": "value_cache",
|
| 372 |
+
"arg": "valueCacheT",
|
| 373 |
+
"semantic": "value_cache",
|
| 374 |
+
"buffer": { "type": "storage" },
|
| 375 |
+
"elementType": "$aScalar"
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"name": "slot_mapping",
|
| 379 |
+
"arg": "slotMappingT",
|
| 380 |
+
"semantic": "slot_mapping",
|
| 381 |
+
"buffer": { "type": "read-only-storage" },
|
| 382 |
+
"elementType": "i32"
|
| 383 |
+
},
|
| 384 |
+
{
|
| 385 |
+
"name": "params",
|
| 386 |
+
"semantic": "kernel.params",
|
| 387 |
+
"buffer": { "type": "uniform" },
|
| 388 |
+
"struct": {
|
| 389 |
+
"name": "Params",
|
| 390 |
+
"fields": [{ "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }]
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
],
|
| 394 |
+
"attention": [
|
| 395 |
+
{
|
| 396 |
+
"name": "query",
|
| 397 |
+
"arg": "queryT",
|
| 398 |
+
"semantic": "query",
|
| 399 |
+
"buffer": { "type": "read-only-storage" },
|
| 400 |
+
"elementType": "$aScalar"
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"name": "key_cache",
|
| 404 |
+
"arg": "keyCacheT",
|
| 405 |
+
"semantic": "key_cache",
|
| 406 |
+
"buffer": { "type": "read-only-storage" },
|
| 407 |
+
"elementType": "$cacheVec"
|
| 408 |
+
},
|
| 409 |
+
{
|
| 410 |
+
"name": "value_cache",
|
| 411 |
+
"arg": "valueCacheT",
|
| 412 |
+
"semantic": "value_cache",
|
| 413 |
+
"buffer": { "type": "read-only-storage" },
|
| 414 |
+
"elementType": "$cacheVec"
|
| 415 |
+
},
|
| 416 |
+
{
|
| 417 |
+
"name": "cumulative_sequence_length",
|
| 418 |
+
"arg": "cumulativeSequenceLengthT",
|
| 419 |
+
"semantic": "cumulative_sequence_length",
|
| 420 |
+
"buffer": { "type": "read-only-storage" },
|
| 421 |
+
"elementType": "i32"
|
| 422 |
+
},
|
| 423 |
+
{
|
| 424 |
+
"name": "past_seqlens",
|
| 425 |
+
"arg": "pastSeqlensT",
|
| 426 |
+
"semantic": "past_seqlens",
|
| 427 |
+
"buffer": { "type": "read-only-storage" },
|
| 428 |
+
"elementType": "i32"
|
| 429 |
+
},
|
| 430 |
+
{
|
| 431 |
+
"name": "block_table",
|
| 432 |
+
"arg": "blockTableT",
|
| 433 |
+
"semantic": "block_table",
|
| 434 |
+
"buffer": { "type": "read-only-storage" },
|
| 435 |
+
"elementType": "i32"
|
| 436 |
+
},
|
| 437 |
+
{
|
| 438 |
+
"name": "output",
|
| 439 |
+
"arg": "outputT",
|
| 440 |
+
"semantic": "output",
|
| 441 |
+
"buffer": { "type": "storage" },
|
| 442 |
+
"elementType": "$aScalar"
|
| 443 |
+
},
|
| 444 |
+
{
|
| 445 |
+
"name": "params",
|
| 446 |
+
"semantic": "kernel.params",
|
| 447 |
+
"buffer": { "type": "uniform" },
|
| 448 |
+
"struct": {
|
| 449 |
+
"name": "Params",
|
| 450 |
+
"fields": [
|
| 451 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 452 |
+
{ "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
|
| 453 |
+
]
|
| 454 |
+
}
|
| 455 |
+
}
|
| 456 |
+
],
|
| 457 |
+
"splitAttention": [
|
| 458 |
+
{
|
| 459 |
+
"name": "query",
|
| 460 |
+
"arg": "queryT",
|
| 461 |
+
"semantic": "query",
|
| 462 |
+
"buffer": { "type": "read-only-storage" },
|
| 463 |
+
"elementType": "$aScalar"
|
| 464 |
+
},
|
| 465 |
+
{
|
| 466 |
+
"name": "key_cache",
|
| 467 |
+
"arg": "keyCacheT",
|
| 468 |
+
"semantic": "key_cache",
|
| 469 |
+
"buffer": { "type": "read-only-storage" },
|
| 470 |
+
"elementType": "$cacheVec"
|
| 471 |
+
},
|
| 472 |
+
{
|
| 473 |
+
"name": "value_cache",
|
| 474 |
+
"arg": "valueCacheT",
|
| 475 |
+
"semantic": "value_cache",
|
| 476 |
+
"buffer": { "type": "read-only-storage" },
|
| 477 |
+
"elementType": "$cacheVec"
|
| 478 |
+
},
|
| 479 |
+
{
|
| 480 |
+
"name": "cumulative_sequence_length",
|
| 481 |
+
"arg": "cumulativeSequenceLengthT",
|
| 482 |
+
"semantic": "cumulative_sequence_length",
|
| 483 |
+
"buffer": { "type": "read-only-storage" },
|
| 484 |
+
"elementType": "i32"
|
| 485 |
+
},
|
| 486 |
+
{
|
| 487 |
+
"name": "past_seqlens",
|
| 488 |
+
"arg": "pastSeqlensT",
|
| 489 |
+
"semantic": "past_seqlens",
|
| 490 |
+
"buffer": { "type": "read-only-storage" },
|
| 491 |
+
"elementType": "i32"
|
| 492 |
+
},
|
| 493 |
+
{
|
| 494 |
+
"name": "block_table",
|
| 495 |
+
"arg": "blockTableT",
|
| 496 |
+
"semantic": "block_table",
|
| 497 |
+
"buffer": { "type": "read-only-storage" },
|
| 498 |
+
"elementType": "i32"
|
| 499 |
+
},
|
| 500 |
+
{ "name": "partial_out", "semantic": "partialOut", "buffer": { "type": "storage" }, "elementType": "vec4<f32>" },
|
| 501 |
+
{
|
| 502 |
+
"name": "partial_stats",
|
| 503 |
+
"semantic": "partialStats",
|
| 504 |
+
"buffer": { "type": "storage" },
|
| 505 |
+
"elementType": "vec2<f32>"
|
| 506 |
+
},
|
| 507 |
+
{
|
| 508 |
+
"name": "params",
|
| 509 |
+
"semantic": "kernel.params",
|
| 510 |
+
"buffer": { "type": "uniform" },
|
| 511 |
+
"struct": {
|
| 512 |
+
"name": "Params",
|
| 513 |
+
"fields": [
|
| 514 |
+
{ "name": "batchSize", "type": "u32", "value": "batchSize" },
|
| 515 |
+
{ "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
|
| 516 |
+
]
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
],
|
| 520 |
+
"splitMerge": [
|
| 521 |
+
{
|
| 522 |
+
"name": "partial_out",
|
| 523 |
+
"semantic": "partialOut",
|
| 524 |
+
"buffer": { "type": "read-only-storage" },
|
| 525 |
+
"elementType": "vec4<f32>"
|
| 526 |
+
},
|
| 527 |
+
{
|
| 528 |
+
"name": "partial_stats",
|
| 529 |
+
"semantic": "partialStats",
|
| 530 |
+
"buffer": { "type": "read-only-storage" },
|
| 531 |
+
"elementType": "vec2<f32>"
|
| 532 |
+
},
|
| 533 |
+
{
|
| 534 |
+
"name": "output",
|
| 535 |
+
"arg": "outputT",
|
| 536 |
+
"semantic": "output",
|
| 537 |
+
"buffer": { "type": "storage" },
|
| 538 |
+
"elementType": "$outVec"
|
| 539 |
+
}
|
| 540 |
+
]
|
| 541 |
+
},
|
| 542 |
+
"variants": [
|
| 543 |
+
{
|
| 544 |
+
"id": "separate_derived_splitk",
|
| 545 |
+
"description": "Splits each token's key history into contiguous ranges, one workgroup per range, and merges the per-range online-softmax states. It serves grouped-query decode shapes, where one workgroup per `(token, KV head)` leaves too few workgroups to fill the device.",
|
| 546 |
+
"priority": 10,
|
| 547 |
+
"requires": { "features": ["shader-f16"] },
|
| 548 |
+
"when": ["pagedContractOk", "dispatchFits", "pagedSplitFits", "separateKv", "not present.slotMappingT"],
|
| 549 |
+
"constants": { "attnWorkgroup": "pagedSplitWg" },
|
| 550 |
+
"intermediates": [
|
| 551 |
+
{ "id": "partialOut", "dtype": "float32", "shape": "[tokenCount * attrs.num_heads * numSplits * headSize]" },
|
| 552 |
+
{ "id": "partialStats", "dtype": "float32", "shape": "[2 * tokenCount * attrs.num_heads * numSplits]" }
|
| 553 |
+
],
|
| 554 |
+
"passes": [
|
| 555 |
+
{
|
| 556 |
+
"id": "scatter",
|
| 557 |
+
"name": "PagedAttention.ScatterKV",
|
| 558 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 559 |
+
"bindings": "scatterSeparateDerived",
|
| 560 |
+
"dispatch": {
|
| 561 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 562 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 563 |
+
}
|
| 564 |
+
},
|
| 565 |
+
{
|
| 566 |
+
"id": "split_attention",
|
| 567 |
+
"name": "PagedAttention.AttendSplitK",
|
| 568 |
+
"source": { "shader": "paged-attention.wgsl.jinja", "inputs": { "splitK": true } },
|
| 569 |
+
"bindings": "splitAttention",
|
| 570 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
|
| 571 |
+
},
|
| 572 |
+
{
|
| 573 |
+
"id": "merge",
|
| 574 |
+
"name": "PagedAttention.AttendSplitKMerge",
|
| 575 |
+
"source": { "shader": "attn-flash-decode-splitk-merge.wgsl.jinja", "inputs": { "layout": "\"bsh\"" } },
|
| 576 |
+
"bindings": "splitMerge",
|
| 577 |
+
"dispatch": { "x": 1, "y": "attrs.num_heads", "z": "tokenCount" }
|
| 578 |
+
}
|
| 579 |
+
]
|
| 580 |
+
},
|
| 581 |
+
{
|
| 582 |
+
"id": "separate_derived",
|
| 583 |
+
"requires": { "features": ["shader-f16"] },
|
| 584 |
+
"when": ["pagedContractOk", "dispatchFits", "separateKv", "not present.slotMappingT"],
|
| 585 |
+
"passes": [
|
| 586 |
+
{
|
| 587 |
+
"id": "scatter",
|
| 588 |
+
"name": "PagedAttention.ScatterKV",
|
| 589 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 590 |
+
"bindings": "scatterSeparateDerived",
|
| 591 |
+
"dispatch": {
|
| 592 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 593 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 594 |
+
}
|
| 595 |
+
},
|
| 596 |
+
{
|
| 597 |
+
"id": "main",
|
| 598 |
+
"name": "PagedAttention.Attend",
|
| 599 |
+
"shader": "paged-attention.wgsl.jinja",
|
| 600 |
+
"bindings": "attention",
|
| 601 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
|
| 602 |
+
}
|
| 603 |
+
]
|
| 604 |
+
},
|
| 605 |
+
{
|
| 606 |
+
"id": "separate_slot_splitk",
|
| 607 |
+
"description": "Splits each token's key history into contiguous ranges, one workgroup per range, and merges the per-range online-softmax states. It serves grouped-query decode shapes, where one workgroup per `(token, KV head)` leaves too few workgroups to fill the device.",
|
| 608 |
+
"priority": 10,
|
| 609 |
+
"requires": { "features": ["shader-f16"] },
|
| 610 |
+
"when": ["pagedContractOk", "dispatchFits", "pagedSplitFits", "separateKv", "present.slotMappingT"],
|
| 611 |
+
"constants": { "attnWorkgroup": "pagedSplitWg" },
|
| 612 |
+
"intermediates": [
|
| 613 |
+
{ "id": "partialOut", "dtype": "float32", "shape": "[tokenCount * attrs.num_heads * numSplits * headSize]" },
|
| 614 |
+
{ "id": "partialStats", "dtype": "float32", "shape": "[2 * tokenCount * attrs.num_heads * numSplits]" }
|
| 615 |
+
],
|
| 616 |
+
"passes": [
|
| 617 |
+
{
|
| 618 |
+
"id": "scatter",
|
| 619 |
+
"name": "PagedAttention.ScatterKV",
|
| 620 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 621 |
+
"bindings": "scatterSeparateSlot",
|
| 622 |
+
"dispatch": {
|
| 623 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 624 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 625 |
+
}
|
| 626 |
+
},
|
| 627 |
+
{
|
| 628 |
+
"id": "split_attention",
|
| 629 |
+
"name": "PagedAttention.AttendSplitK",
|
| 630 |
+
"source": { "shader": "paged-attention.wgsl.jinja", "inputs": { "splitK": true } },
|
| 631 |
+
"bindings": "splitAttention",
|
| 632 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
|
| 633 |
+
},
|
| 634 |
+
{
|
| 635 |
+
"id": "merge",
|
| 636 |
+
"name": "PagedAttention.AttendSplitKMerge",
|
| 637 |
+
"source": { "shader": "attn-flash-decode-splitk-merge.wgsl.jinja", "inputs": { "layout": "\"bsh\"" } },
|
| 638 |
+
"bindings": "splitMerge",
|
| 639 |
+
"dispatch": { "x": 1, "y": "attrs.num_heads", "z": "tokenCount" }
|
| 640 |
+
}
|
| 641 |
+
]
|
| 642 |
+
},
|
| 643 |
+
{
|
| 644 |
+
"id": "separate_slot",
|
| 645 |
+
"requires": { "features": ["shader-f16"] },
|
| 646 |
+
"when": ["pagedContractOk", "dispatchFits", "separateKv", "present.slotMappingT"],
|
| 647 |
+
"passes": [
|
| 648 |
+
{
|
| 649 |
+
"id": "scatter",
|
| 650 |
+
"name": "PagedAttention.ScatterKV",
|
| 651 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 652 |
+
"bindings": "scatterSeparateSlot",
|
| 653 |
+
"dispatch": {
|
| 654 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 655 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 656 |
+
}
|
| 657 |
+
},
|
| 658 |
+
{
|
| 659 |
+
"id": "main",
|
| 660 |
+
"name": "PagedAttention.Attend",
|
| 661 |
+
"shader": "paged-attention.wgsl.jinja",
|
| 662 |
+
"bindings": "attention",
|
| 663 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
|
| 664 |
+
}
|
| 665 |
+
]
|
| 666 |
+
},
|
| 667 |
+
{
|
| 668 |
+
"id": "packed_derived_splitk",
|
| 669 |
+
"description": "Splits each token's key history into contiguous ranges, one workgroup per range, and merges the per-range online-softmax states. It serves grouped-query decode shapes, where one workgroup per `(token, KV head)` leaves too few workgroups to fill the device.",
|
| 670 |
+
"priority": 10,
|
| 671 |
+
"requires": { "features": ["shader-f16"] },
|
| 672 |
+
"when": ["pagedContractOk", "dispatchFits", "pagedSplitFits", "packedKv", "not present.slotMappingT"],
|
| 673 |
+
"constants": { "attnWorkgroup": "pagedSplitWg" },
|
| 674 |
+
"intermediates": [
|
| 675 |
+
{ "id": "partialOut", "dtype": "float32", "shape": "[tokenCount * attrs.num_heads * numSplits * headSize]" },
|
| 676 |
+
{ "id": "partialStats", "dtype": "float32", "shape": "[2 * tokenCount * attrs.num_heads * numSplits]" }
|
| 677 |
+
],
|
| 678 |
+
"passes": [
|
| 679 |
+
{
|
| 680 |
+
"id": "scatter",
|
| 681 |
+
"name": "PagedAttention.ScatterKV",
|
| 682 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 683 |
+
"bindings": "scatterPackedDerived",
|
| 684 |
+
"dispatch": {
|
| 685 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 686 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 687 |
+
}
|
| 688 |
+
},
|
| 689 |
+
{
|
| 690 |
+
"id": "split_attention",
|
| 691 |
+
"name": "PagedAttention.AttendSplitK",
|
| 692 |
+
"source": { "shader": "paged-attention.wgsl.jinja", "inputs": { "splitK": true } },
|
| 693 |
+
"bindings": "splitAttention",
|
| 694 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
|
| 695 |
+
},
|
| 696 |
+
{
|
| 697 |
+
"id": "merge",
|
| 698 |
+
"name": "PagedAttention.AttendSplitKMerge",
|
| 699 |
+
"source": { "shader": "attn-flash-decode-splitk-merge.wgsl.jinja", "inputs": { "layout": "\"bsh\"" } },
|
| 700 |
+
"bindings": "splitMerge",
|
| 701 |
+
"dispatch": { "x": 1, "y": "attrs.num_heads", "z": "tokenCount" }
|
| 702 |
+
}
|
| 703 |
+
]
|
| 704 |
+
},
|
| 705 |
+
{
|
| 706 |
+
"id": "packed_derived",
|
| 707 |
+
"requires": { "features": ["shader-f16"] },
|
| 708 |
+
"when": ["pagedContractOk", "dispatchFits", "packedKv", "not present.slotMappingT"],
|
| 709 |
+
"passes": [
|
| 710 |
+
{
|
| 711 |
+
"id": "scatter",
|
| 712 |
+
"name": "PagedAttention.ScatterKV",
|
| 713 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 714 |
+
"bindings": "scatterPackedDerived",
|
| 715 |
+
"dispatch": {
|
| 716 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 717 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 718 |
+
}
|
| 719 |
+
},
|
| 720 |
+
{
|
| 721 |
+
"id": "main",
|
| 722 |
+
"name": "PagedAttention.Attend",
|
| 723 |
+
"shader": "paged-attention.wgsl.jinja",
|
| 724 |
+
"bindings": "attention",
|
| 725 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
|
| 726 |
+
}
|
| 727 |
+
]
|
| 728 |
+
},
|
| 729 |
+
{
|
| 730 |
+
"id": "packed_slot_splitk",
|
| 731 |
+
"description": "Splits each token's key history into contiguous ranges, one workgroup per range, and merges the per-range online-softmax states. It serves grouped-query decode shapes, where one workgroup per `(token, KV head)` leaves too few workgroups to fill the device.",
|
| 732 |
+
"priority": 10,
|
| 733 |
+
"requires": { "features": ["shader-f16"] },
|
| 734 |
+
"when": ["pagedContractOk", "dispatchFits", "pagedSplitFits", "packedKv", "present.slotMappingT"],
|
| 735 |
+
"constants": { "attnWorkgroup": "pagedSplitWg" },
|
| 736 |
+
"intermediates": [
|
| 737 |
+
{ "id": "partialOut", "dtype": "float32", "shape": "[tokenCount * attrs.num_heads * numSplits * headSize]" },
|
| 738 |
+
{ "id": "partialStats", "dtype": "float32", "shape": "[2 * tokenCount * attrs.num_heads * numSplits]" }
|
| 739 |
+
],
|
| 740 |
+
"passes": [
|
| 741 |
+
{
|
| 742 |
+
"id": "scatter",
|
| 743 |
+
"name": "PagedAttention.ScatterKV",
|
| 744 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 745 |
+
"bindings": "scatterPackedSlot",
|
| 746 |
+
"dispatch": {
|
| 747 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 748 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 749 |
+
}
|
| 750 |
+
},
|
| 751 |
+
{
|
| 752 |
+
"id": "split_attention",
|
| 753 |
+
"name": "PagedAttention.AttendSplitK",
|
| 754 |
+
"source": { "shader": "paged-attention.wgsl.jinja", "inputs": { "splitK": true } },
|
| 755 |
+
"bindings": "splitAttention",
|
| 756 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
|
| 757 |
+
},
|
| 758 |
+
{
|
| 759 |
+
"id": "merge",
|
| 760 |
+
"name": "PagedAttention.AttendSplitKMerge",
|
| 761 |
+
"source": { "shader": "attn-flash-decode-splitk-merge.wgsl.jinja", "inputs": { "layout": "\"bsh\"" } },
|
| 762 |
+
"bindings": "splitMerge",
|
| 763 |
+
"dispatch": { "x": 1, "y": "attrs.num_heads", "z": "tokenCount" }
|
| 764 |
+
}
|
| 765 |
+
]
|
| 766 |
+
},
|
| 767 |
+
{
|
| 768 |
+
"id": "packed_slot",
|
| 769 |
+
"requires": { "features": ["shader-f16"] },
|
| 770 |
+
"when": ["pagedContractOk", "dispatchFits", "packedKv", "present.slotMappingT"],
|
| 771 |
+
"passes": [
|
| 772 |
+
{
|
| 773 |
+
"id": "scatter",
|
| 774 |
+
"name": "PagedAttention.ScatterKV",
|
| 775 |
+
"shader": "paged-scatter-kv.wgsl.jinja",
|
| 776 |
+
"bindings": "scatterPackedSlot",
|
| 777 |
+
"dispatch": {
|
| 778 |
+
"threads": "tokenCount * attrs.kv_num_heads * headSize",
|
| 779 |
+
"workgroupSize": "tunables.SCATTER_WORKGROUP_SIZE"
|
| 780 |
+
}
|
| 781 |
+
},
|
| 782 |
+
{
|
| 783 |
+
"id": "main",
|
| 784 |
+
"name": "PagedAttention.Attend",
|
| 785 |
+
"shader": "paged-attention.wgsl.jinja",
|
| 786 |
+
"bindings": "attention",
|
| 787 |
+
"dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
|
| 788 |
+
}
|
| 789 |
+
]
|
| 790 |
+
}
|
| 791 |
+
]
|
| 792 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "com.microsoft.PagedAttention",
|
| 3 |
+
"id": "_com_microsoft_pagedattention_webgpu_da91370",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"attn-flash-decode-splitk-merge.wgsl.jinja": "8RI/yP6118n/rLRo0OG9NJ+kZ2xdmJyj093Tw8C07nc=",
|
| 11 |
+
"bench.json": "UwS/SwZvyxZNimhsfEODD8GUpSN1saYbJFO+2AJpBFE=",
|
| 12 |
+
"manifest.json": "4OMoAQRJcGm7t8TLViM098p15V0i788gvT/vpkfbt1c=",
|
| 13 |
+
"paged-attention.wgsl.jinja": "LcAWM3XNSdLIc67tQSUJ6v9Sa4QcRIetF3cnNwkdu9c=",
|
| 14 |
+
"paged-scatter-kv.wgsl.jinja": "WiuV+h3AOSDU0ENfvZ5iequjvL9TFR3h1gCQgGswPRA=",
|
| 15 |
+
"test.json": "AZ89NKbsfzyghJ0lIz57f3tm0r9H62tEPhY1uP5yWEM="
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
|
| 19 |
+
"webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.PagedAttention" }
|
| 20 |
+
}
|
build/webgpu/paged-attention.wgsl.jinja
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% set splitK = source.splitK is defined and source.splitK %}
|
| 2 |
+
{% set keyLo = "splitStart" if splitK else "0u" %}
|
| 3 |
+
{% set keyHi = "splitEnd" if splitK else "key_bound" %}
|
| 4 |
+
enable f16;
|
| 5 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 6 |
+
|
| 7 |
+
// com.microsoft.PagedAttention, attention pass.
|
| 8 |
+
// One workgroup per (packed token, KV head). The workgroup sweeps that token's
|
| 9 |
+
// KV history in tiles of WG keys with one key per thread, merging each tile into
|
| 10 |
+
// a running online softmax, so the score row is never materialized.
|
| 11 |
+
// A workgroup serves every query head that shares its KV head, reusing each
|
| 12 |
+
// cache read across the group. Q_PER_KV == 1 reduces to ordinary attention.
|
| 13 |
+
// The history is not contiguous: key j of sequence b lives in physical block
|
| 14 |
+
// block_table[b, j / BLOCK_SIZE] at offset j % BLOCK_SIZE, which is the whole
|
| 15 |
+
// point of a paged cache. Each tile resolves that indirection once per key and
|
| 16 |
+
// parks the resulting row bases in workgroup memory, so the value accumulation
|
| 17 |
+
// below re-reads a base instead of re-walking the block table for every head
|
| 18 |
+
// dimension.
|
| 19 |
+
// Masking is causal by construction rather than by comparison: a token's key
|
| 20 |
+
// bound is its own absolute position plus one, so keys beyond it are never
|
| 21 |
+
// visited.
|
| 22 |
+
{% if splitK %}
|
| 23 |
+
// Split-K partitions the key axis into NUM_SPLITS contiguous ranges. Each range
|
| 24 |
+
// writes its unnormalized online state (max, denominator, and weighted V sum),
|
| 25 |
+
// and the merge pass combines those states before the final division. An empty
|
| 26 |
+
// range writes the (-FLT_MAX, 0) identity expected by the merge.
|
| 27 |
+
{% endif %}
|
| 28 |
+
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 29 |
+
const HEAD_DIM: u32 = {{ headSize }}u;
|
| 30 |
+
// Where the head dimension divides into four, the cache is read four elements at
|
| 31 |
+
// a time, reducing the number of dependent cache loads. Other head sizes use the
|
| 32 |
+
// scalar walk, with the binding element type specialized to match.
|
| 33 |
+
{% if cacheVec4Ok %}
|
| 34 |
+
const HEAD_VEC: u32 = {{ headVec }}u;
|
| 35 |
+
{% endif %}
|
| 36 |
+
const BLOCK_SIZE: u32 = {{ blockSize }}u;
|
| 37 |
+
const MAX_BLOCKS: u32 = {{ maxBlocks }}u;
|
| 38 |
+
const Q_STRIDE: u32 = {{ packedStride if packedQkv else qHidden }}u;
|
| 39 |
+
const Q_PER_KV: u32 = {{ qPerKv }}u;
|
| 40 |
+
// WG is both the key-tile width and the thread count that walks the head axis.
|
| 41 |
+
const WG: u32 = {{ attnWorkgroup }}u;
|
| 42 |
+
{% if splitK %}
|
| 43 |
+
const NUM_SPLITS: u32 = {{ numSplits }}u;
|
| 44 |
+
const Q_HEADS: u32 = {{ numHeads }}u;
|
| 45 |
+
{% endif %}
|
| 46 |
+
|
| 47 |
+
// FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
|
| 48 |
+
// `m - m` finite so an empty lane / all--inf row contributes the exact
|
| 49 |
+
// accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
|
| 50 |
+
// a zero final denominator according to their public semantics. Using -inf
|
| 51 |
+
// here changes +inf-row behavior.
|
| 52 |
+
const FLT_MAX: f32 = 3.4028234663852886e38;
|
| 53 |
+
|
| 54 |
+
fn is_finite_f32(value: f32) -> bool {
|
| 55 |
+
return select(false, value <= FLT_MAX, value >= -FLT_MAX);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
|
| 59 |
+
// exactly at the row max. `x - x` on an infinite max is a legal fast-math
|
| 60 |
+
// fold to 0, which would silently turn +inf rows finite — the explicit
|
| 61 |
+
// equality test keeps the NaN propagation of the serial kernels.
|
| 62 |
+
fn shifted_value(value: f32, maxValue: f32) -> f32 {
|
| 63 |
+
let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
|
| 64 |
+
return select(value - maxValue, 0.0, equalFiniteMax);
|
| 65 |
+
}
|
| 66 |
+
fn exp_shift(value: f32, maxValue: f32) -> f32 {
|
| 67 |
+
return exp(shifted_value(value, maxValue));
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
{% if cacheVec4Ok %}
|
| 71 |
+
var<workgroup> q_shared: array<vec4<f32>, HEAD_VEC * Q_PER_KV>;
|
| 72 |
+
var<workgroup> running_out: array<vec4<f32>, HEAD_VEC * Q_PER_KV>;
|
| 73 |
+
{% else %}
|
| 74 |
+
var<workgroup> q_shared: array<f32, HEAD_DIM * Q_PER_KV>;
|
| 75 |
+
var<workgroup> running_out: array<f32, HEAD_DIM * Q_PER_KV>;
|
| 76 |
+
{% endif %}
|
| 77 |
+
var<workgroup> probs: array<f32, WG * Q_PER_KV>;
|
| 78 |
+
// One resolved cache row base per key of the current tile.
|
| 79 |
+
var<workgroup> key_rows: array<u32, WG>;
|
| 80 |
+
|
| 81 |
+
{% set mdStreams = qPerKv %}
|
| 82 |
+
// Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
|
| 83 |
+
// mNew = max(m1, m2)
|
| 84 |
+
// dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
|
| 85 |
+
// Both the subgroup and portable barrier-tree engines return the same merged
|
| 86 |
+
// pair to every invocation. Repeated merges require a workgroup barrier between
|
| 87 |
+
// calls before their shared partial storage is reused.
|
| 88 |
+
{% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
|
| 89 |
+
{% if combineSubgroups %}
|
| 90 |
+
// Per-subgroup partials are published into a deterministic slot: the subgroup's
|
| 91 |
+
// ordinal index within the workgroup (lidx / sgSize). The online (m, d) merge
|
| 92 |
+
// is not float-associative, so thread 0 must fold partials in a fixed order.
|
| 93 |
+
// Subgroups partition a workgroup into contiguous ordinal ranges on supported
|
| 94 |
+
// backends, so the ordinal slot is unique per subgroup and every slot in
|
| 95 |
+
// [0, subgroupCount) is written (each subgroup elects one leader).
|
| 96 |
+
// Sized for the worst case of one partial per invocation.
|
| 97 |
+
var<workgroup> partialM: array<f32, WG>;
|
| 98 |
+
var<workgroup> partialD: array<f32, WG>;
|
| 99 |
+
var<workgroup> combinedMD: vec2<f32>;
|
| 100 |
+
|
| 101 |
+
// When the whole workgroup is one subgroup the subgroup reduce already covers
|
| 102 |
+
// it (no barriers, no shared state); otherwise subgroup leaders publish
|
| 103 |
+
// partials through shared memory and thread 0 folds them in ordinal order.
|
| 104 |
+
fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
|
| 105 |
+
let sgM = subgroupMax(m);
|
| 106 |
+
// A lane with no elements contributes d == 0 (exact identity). A +inf
|
| 107 |
+
// element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
|
| 108 |
+
// landed in d via exp(NaN); both survive the merge and are detected by the
|
| 109 |
+
// code after the reduction.
|
| 110 |
+
let sgD = subgroupAdd(d * exp_shift(m, sgM));
|
| 111 |
+
if (sgSize == WG) {
|
| 112 |
+
return vec2<f32>(sgM, sgD);
|
| 113 |
+
}
|
| 114 |
+
let subgroupCount = (WG + sgSize - 1u) / sgSize;
|
| 115 |
+
// Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
|
| 116 |
+
// fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
|
| 117 |
+
// subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
|
| 118 |
+
// flash-attention loop that re-uses this shared memory each iteration — would
|
| 119 |
+
// otherwise read stale shared memory. Identity makes such a slot a no-op.
|
| 120 |
+
// (max identity = -FLT_MAX, denom identity = 0.)
|
| 121 |
+
if (lidx < subgroupCount) {
|
| 122 |
+
partialM[lidx] = -FLT_MAX;
|
| 123 |
+
partialD[lidx] = 0.0;
|
| 124 |
+
}
|
| 125 |
+
workgroupBarrier();
|
| 126 |
+
if (subgroupElect()) {
|
| 127 |
+
let slot = lidx / sgSize;
|
| 128 |
+
partialM[slot] = sgM;
|
| 129 |
+
partialD[slot] = sgD;
|
| 130 |
+
}
|
| 131 |
+
workgroupBarrier();
|
| 132 |
+
if (lidx == 0u) {
|
| 133 |
+
var accM = -FLT_MAX;
|
| 134 |
+
var accD = 0.0;
|
| 135 |
+
for (var i = 0u; i < subgroupCount; i = i + 1u) {
|
| 136 |
+
let mNew = max(accM, partialM[i]);
|
| 137 |
+
accD = accD * exp_shift(accM, mNew) + partialD[i] * exp_shift(partialM[i], mNew);
|
| 138 |
+
accM = mNew;
|
| 139 |
+
}
|
| 140 |
+
combinedMD = vec2<f32>(accM, accD);
|
| 141 |
+
}
|
| 142 |
+
workgroupBarrier();
|
| 143 |
+
return combinedMD;
|
| 144 |
+
}
|
| 145 |
+
{% else %}
|
| 146 |
+
{% set mdStreamed = mdStreams is defined %}
|
| 147 |
+
{% set mdStreams = mdStreams if mdStreams is defined else 1 %}
|
| 148 |
+
{% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
|
| 149 |
+
var<workgroup> partialM: array<f32, {{ mdExtent }}>;
|
| 150 |
+
var<workgroup> partialD: array<f32, {{ mdExtent }}>;
|
| 151 |
+
{% if mdStreamed %}
|
| 152 |
+
|
| 153 |
+
// In-place fold of {{ mdStreams }} streams. The caller stores its per-thread
|
| 154 |
+
// partials into partialM/partialD first and reads the merged pair of stream s
|
| 155 |
+
// from slot s * WG afterwards.
|
| 156 |
+
fn combine_partials_streams(lidx: u32) {
|
| 157 |
+
workgroupBarrier();
|
| 158 |
+
var stride = WG / 2u;
|
| 159 |
+
loop {
|
| 160 |
+
if (stride == 0u) {
|
| 161 |
+
break;
|
| 162 |
+
}
|
| 163 |
+
if (lidx < stride) {
|
| 164 |
+
{% for s in range(mdStreams) %}
|
| 165 |
+
{
|
| 166 |
+
let slot = {{ s }}u * WG + lidx;
|
| 167 |
+
let m1 = partialM[slot];
|
| 168 |
+
let d1 = partialD[slot];
|
| 169 |
+
let m2 = partialM[slot + stride];
|
| 170 |
+
let d2 = partialD[slot + stride];
|
| 171 |
+
let mNew = max(m1, m2);
|
| 172 |
+
partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 173 |
+
partialM[slot] = mNew;
|
| 174 |
+
}
|
| 175 |
+
{% endfor %}
|
| 176 |
+
}
|
| 177 |
+
workgroupBarrier();
|
| 178 |
+
stride = stride / 2u;
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
{% else %}
|
| 182 |
+
|
| 183 |
+
fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
|
| 184 |
+
partialM[lidx] = m;
|
| 185 |
+
partialD[lidx] = d;
|
| 186 |
+
workgroupBarrier();
|
| 187 |
+
var stride = WG / 2u;
|
| 188 |
+
loop {
|
| 189 |
+
if (stride == 0u) {
|
| 190 |
+
break;
|
| 191 |
+
}
|
| 192 |
+
if (lidx < stride) {
|
| 193 |
+
let m1 = partialM[lidx];
|
| 194 |
+
let d1 = partialD[lidx];
|
| 195 |
+
let m2 = partialM[lidx + stride];
|
| 196 |
+
let d2 = partialD[lidx + stride];
|
| 197 |
+
let mNew = max(m1, m2);
|
| 198 |
+
partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
|
| 199 |
+
partialM[lidx] = mNew;
|
| 200 |
+
}
|
| 201 |
+
workgroupBarrier();
|
| 202 |
+
stride = stride / 2u;
|
| 203 |
+
}
|
| 204 |
+
let merged = vec2<f32>(partialM[0], partialD[0]);
|
| 205 |
+
// Trailing barrier so back-to-back calls cannot race a next call's partial
|
| 206 |
+
// stores against this call's reads of slot 0.
|
| 207 |
+
workgroupBarrier();
|
| 208 |
+
return merged;
|
| 209 |
+
}
|
| 210 |
+
{% endif %}
|
| 211 |
+
{% endif %}
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
{% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
|
| 215 |
+
fn scale_value() -> f32 {
|
| 216 |
+
if (params.scale != 0.0) { return params.scale; }
|
| 217 |
+
return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
{% macro varlen_sequence_of_token(buffer="cumulative_sequence_length") %}
|
| 222 |
+
fn sequence_of_token(token: u32, batch_size: u32) -> u32 {
|
| 223 |
+
for (var b: u32 = 0u; b < batch_size; b = b + 1u) {
|
| 224 |
+
if (token < u32({{ buffer }}[b + 1u])) {
|
| 225 |
+
return b;
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
return batch_size - 1u;
|
| 229 |
+
}
|
| 230 |
+
{%- endmacro %}
|
| 231 |
+
|
| 232 |
+
{% macro paged_sequence_index(withLookup=true) %}
|
| 233 |
+
{% if withLookup %}
|
| 234 |
+
{{ varlen_sequence_of_token() }}
|
| 235 |
+
{% endif %}
|
| 236 |
+
|
| 237 |
+
// Physical element base of one (slot, kv head) row of the paged cache, whose
|
| 238 |
+
// layout is (num_blocks, block_size, kv_num_heads, head_size).
|
| 239 |
+
fn paged_row_base(block_id: u32, slot_in_block: u32, kv_head: u32) -> u32 {
|
| 240 |
+
return ((block_id * BLOCK_SIZE + slot_in_block) * KV_HEADS + kv_head) * HEAD_DIM;
|
| 241 |
+
}
|
| 242 |
+
{%- endmacro %}
|
| 243 |
+
|
| 244 |
+
{{ paged_sequence_index() }}
|
| 245 |
+
|
| 246 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 247 |
+
fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
| 248 |
+
let token = wg.x;
|
| 249 |
+
let kv_head = wg.y;
|
| 250 |
+
let head0 = kv_head * Q_PER_KV;
|
| 251 |
+
let tid = lid.x;
|
| 252 |
+
|
| 253 |
+
let sequence = sequence_of_token(token, params.batchSize);
|
| 254 |
+
let local_token = token - u32(cumulative_sequence_length[sequence]);
|
| 255 |
+
// Keys 0 .. past + local_token are visible to this token; the +1 makes the
|
| 256 |
+
// bound exclusive.
|
| 257 |
+
let key_bound = u32(past_seqlens[sequence]) + local_token + 1u;
|
| 258 |
+
let scale = scale_value();
|
| 259 |
+
{% if splitK %}
|
| 260 |
+
// Contiguous, equal-length key ranges. The last range is short and a range past
|
| 261 |
+
// this token's history is empty; both fall out of the tile loop below.
|
| 262 |
+
let split = wg.z;
|
| 263 |
+
let splitLen = (key_bound + NUM_SPLITS - 1u) / NUM_SPLITS;
|
| 264 |
+
let splitStart = split * splitLen;
|
| 265 |
+
let splitEnd = min(key_bound, splitStart + splitLen);
|
| 266 |
+
{% endif %}
|
| 267 |
+
|
| 268 |
+
{% if cacheVec4Ok %}
|
| 269 |
+
for (var dv: u32 = tid; dv < HEAD_VEC * Q_PER_KV; dv = dv + WG) {
|
| 270 |
+
let g = dv / HEAD_VEC;
|
| 271 |
+
let lane = dv - g * HEAD_VEC;
|
| 272 |
+
let qb = token * Q_STRIDE + (head0 + g) * HEAD_DIM + lane * 4u;
|
| 273 |
+
q_shared[dv] = vec4<f32>(f32(query[qb]), f32(query[qb + 1u]), f32(query[qb + 2u]), f32(query[qb + 3u]));
|
| 274 |
+
running_out[dv] = vec4<f32>(0.0);
|
| 275 |
+
}
|
| 276 |
+
{% else %}
|
| 277 |
+
for (var d: u32 = tid; d < HEAD_DIM * Q_PER_KV; d = d + WG) {
|
| 278 |
+
let g = d / HEAD_DIM;
|
| 279 |
+
let dim = d - g * HEAD_DIM;
|
| 280 |
+
q_shared[d] = f32(query[token * Q_STRIDE + (head0 + g) * HEAD_DIM + dim]);
|
| 281 |
+
running_out[d] = 0.0;
|
| 282 |
+
}
|
| 283 |
+
{% endif %}
|
| 284 |
+
workgroupBarrier();
|
| 285 |
+
|
| 286 |
+
{% for g in range(qPerKv) %}
|
| 287 |
+
var runningMax_{{ g }} = -FLT_MAX;
|
| 288 |
+
var runningDenom_{{ g }} = 0.0;
|
| 289 |
+
{% endfor %}
|
| 290 |
+
|
| 291 |
+
var tileBase: u32 = {{ keyLo }};
|
| 292 |
+
loop {
|
| 293 |
+
if (tileBase >= {{ keyHi }}) {
|
| 294 |
+
break;
|
| 295 |
+
}
|
| 296 |
+
let kj = tileBase + tid;
|
| 297 |
+
let keyAllowed = kj < {{ keyHi }};
|
| 298 |
+
|
| 299 |
+
{% for g in range(qPerKv) %}
|
| 300 |
+
var score_{{ g }} = -FLT_MAX;
|
| 301 |
+
{% endfor %}
|
| 302 |
+
if (keyAllowed) {
|
| 303 |
+
let block_id = u32(block_table[sequence * MAX_BLOCKS + kj / BLOCK_SIZE]);
|
| 304 |
+
let row = paged_row_base(block_id, kj % BLOCK_SIZE, kv_head);
|
| 305 |
+
key_rows[tid] = row;
|
| 306 |
+
{% for g in range(qPerKv) %}
|
| 307 |
+
var acc_{{ g }} = 0.0;
|
| 308 |
+
{% endfor %}
|
| 309 |
+
// One cache read, every query head in the group.
|
| 310 |
+
{% if cacheVec4Ok %}
|
| 311 |
+
let row_v = row / 4u;
|
| 312 |
+
for (var dv: u32 = 0u; dv < HEAD_VEC; dv = dv + 1u) {
|
| 313 |
+
let kv = vec4<f32>(key_cache[row_v + dv]);
|
| 314 |
+
{% for g in range(qPerKv) %}
|
| 315 |
+
acc_{{ g }} = acc_{{ g }} + dot(q_shared[{{ g }}u * HEAD_VEC + dv], kv);
|
| 316 |
+
{% endfor %}
|
| 317 |
+
}
|
| 318 |
+
{% else %}
|
| 319 |
+
for (var d: u32 = 0u; d < HEAD_DIM; d = d + 1u) {
|
| 320 |
+
let kv = f32(key_cache[row + d]);
|
| 321 |
+
{% for g in range(qPerKv) %}
|
| 322 |
+
acc_{{ g }} = acc_{{ g }} + q_shared[{{ g }}u * HEAD_DIM + d] * kv;
|
| 323 |
+
{% endfor %}
|
| 324 |
+
}
|
| 325 |
+
{% endif %}
|
| 326 |
+
{% for g in range(qPerKv) %}
|
| 327 |
+
score_{{ g }} = acc_{{ g }} * scale;
|
| 328 |
+
{% endfor %}
|
| 329 |
+
}
|
| 330 |
+
// All Q_PER_KV reductions ride one barrier tree.
|
| 331 |
+
{% for g in range(qPerKv) %}
|
| 332 |
+
partialM[{{ g }}u * WG + tid] = score_{{ g }};
|
| 333 |
+
partialD[{{ g }}u * WG + tid] = select(0.0, 1.0, keyAllowed);
|
| 334 |
+
{% endfor %}
|
| 335 |
+
combine_partials_streams(tid);
|
| 336 |
+
{% for g in range(qPerKv) %}
|
| 337 |
+
let tile_{{ g }} = vec2<f32>(partialM[{{ g }}u * WG], partialD[{{ g }}u * WG]);
|
| 338 |
+
{% endfor %}
|
| 339 |
+
workgroupBarrier();
|
| 340 |
+
|
| 341 |
+
{% for g in range(qPerKv) %}
|
| 342 |
+
let newMax_{{ g }} = max(runningMax_{{ g }}, tile_{{ g }}.x);
|
| 343 |
+
let correction_{{ g }} = exp_shift(runningMax_{{ g }}, newMax_{{ g }});
|
| 344 |
+
runningDenom_{{ g }} = runningDenom_{{ g }} * correction_{{ g }} + tile_{{ g }}.y * exp_shift(tile_{{ g }}.x, newMax_{{ g }});
|
| 345 |
+
runningMax_{{ g }} = newMax_{{ g }};
|
| 346 |
+
probs[{{ g }}u * WG + tid] = select(0.0, exp_shift(score_{{ g }}, newMax_{{ g }}), keyAllowed);
|
| 347 |
+
{% endfor %}
|
| 348 |
+
workgroupBarrier();
|
| 349 |
+
|
| 350 |
+
// running_out[d] is owned by the same thread across every tile (tid = d mod
|
| 351 |
+
// WG), so this rescale-and-accumulate needs no further synchronization.
|
| 352 |
+
let tileCount = min(WG, {{ keyHi }} - tileBase);
|
| 353 |
+
{% set W = "HEAD_VEC" if cacheVec4Ok else "HEAD_DIM" %}
|
| 354 |
+
for (var dv: u32 = tid; dv < {{ W }}; dv = dv + WG) {
|
| 355 |
+
{% for g in range(qPerKv) %}
|
| 356 |
+
var vSum_{{ g }} = {{ "vec4<f32>(0.0)" if cacheVec4Ok else "0.0" }};
|
| 357 |
+
{% endfor %}
|
| 358 |
+
// One cache read, every query head in the group, exactly as the scores.
|
| 359 |
+
for (var i: u32 = 0u; i < tileCount; i = i + 1u) {
|
| 360 |
+
{% if cacheVec4Ok %}
|
| 361 |
+
let vv = vec4<f32>(value_cache[key_rows[i] / 4u + dv]);
|
| 362 |
+
{% else %}
|
| 363 |
+
let vv = f32(value_cache[key_rows[i] + dv]);
|
| 364 |
+
{% endif %}
|
| 365 |
+
{% for g in range(qPerKv) %}
|
| 366 |
+
vSum_{{ g }} = vSum_{{ g }} + probs[{{ g }}u * WG + i] * vv;
|
| 367 |
+
{% endfor %}
|
| 368 |
+
}
|
| 369 |
+
{% for g in range(qPerKv) %}
|
| 370 |
+
running_out[{{ g }}u * {{ W }} + dv] = running_out[{{ g }}u * {{ W }} + dv] * correction_{{ g }} + vSum_{{ g }};
|
| 371 |
+
{% endfor %}
|
| 372 |
+
}
|
| 373 |
+
// Orders this tile's probs and key_rows reads before the next tile writes them.
|
| 374 |
+
workgroupBarrier();
|
| 375 |
+
|
| 376 |
+
tileBase = tileBase + WG;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
{% if splitK %}
|
| 380 |
+
// This range's un-normalized state, in the layout the shared merge reads. The
|
| 381 |
+
// divide by the denominator is the merge's job: it belongs after every range
|
| 382 |
+
// has been folded, not once per range. Split-K always uses the vec4 cache.
|
| 383 |
+
{% for g in range(qPerKv) %}
|
| 384 |
+
{
|
| 385 |
+
let slot_{{ g }} = (token * Q_HEADS + head0 + {{ g }}u) * NUM_SPLITS + split;
|
| 386 |
+
let pBase_{{ g }} = slot_{{ g }} * HEAD_VEC;
|
| 387 |
+
for (var dv: u32 = tid; dv < HEAD_VEC; dv = dv + WG) {
|
| 388 |
+
partial_out[pBase_{{ g }} + dv] = running_out[{{ g }}u * HEAD_VEC + dv];
|
| 389 |
+
}
|
| 390 |
+
if (tid == 0u) {
|
| 391 |
+
partial_stats[slot_{{ g }}] = vec2<f32>(runningMax_{{ g }}, runningDenom_{{ g }});
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
{% endfor %}
|
| 395 |
+
}
|
| 396 |
+
{% else %}
|
| 397 |
+
// A token always attends at least its own key, so the denominator is positive
|
| 398 |
+
// unless every score was non-finite; guard anyway rather than emit NaN.
|
| 399 |
+
{% for g in range(qPerKv) %}
|
| 400 |
+
let invDenom_{{ g }} = select(0.0, 1.0 / runningDenom_{{ g }}, runningDenom_{{ g }} > 0.0);
|
| 401 |
+
{% endfor %}
|
| 402 |
+
for (var dv: u32 = tid; dv < {{ W }}; dv = dv + WG) {
|
| 403 |
+
{% for g in range(qPerKv) %}
|
| 404 |
+
{
|
| 405 |
+
let out_v = running_out[{{ g }}u * {{ W }} + dv] * invDenom_{{ g }};
|
| 406 |
+
{% if cacheVec4Ok %}
|
| 407 |
+
let ob = token * {{ qHidden }}u + (head0 + {{ g }}u) * HEAD_DIM + dv * 4u;
|
| 408 |
+
output[ob] = {{ scalar }}(out_v.x);
|
| 409 |
+
output[ob + 1u] = {{ scalar }}(out_v.y);
|
| 410 |
+
output[ob + 2u] = {{ scalar }}(out_v.z);
|
| 411 |
+
output[ob + 3u] = {{ scalar }}(out_v.w);
|
| 412 |
+
{% else %}
|
| 413 |
+
output[token * {{ qHidden }}u + (head0 + {{ g }}u) * HEAD_DIM + dv] = {{ scalar }}(out_v);
|
| 414 |
+
{% endif %}
|
| 415 |
+
}
|
| 416 |
+
{% endfor %}
|
| 417 |
+
}
|
| 418 |
+
}
|
| 419 |
+
{%- endif %}
|
build/webgpu/paged-scatter-kv.wgsl.jinja
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
|
| 2 |
+
{% if note == "dispatch-limit" %}
|
| 3 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 4 |
+
// maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
|
| 5 |
+
{% elif note == "limit" %}
|
| 6 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
+
// maxComputeWorkgroupsPerDimension limit.
|
| 8 |
+
{% elif note == "device-axis" %}
|
| 9 |
+
// The flat dispatch is folded across x/y at the device's per-axis workgroup
|
| 10 |
+
// limit; gid.y carries the high portion of the output index.
|
| 11 |
+
{% elif note == "vec4-limit" %}
|
| 12 |
+
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
+
// maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
|
| 14 |
+
{% elif note == "element-limit" %}
|
| 15 |
+
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
+
// maxComputeWorkgroupsPerDimension limit.
|
| 17 |
+
{% elif note == "dispatch" %}
|
| 18 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
+
// maxComputeWorkgroupsPerDimension dispatch limit.
|
| 20 |
+
{% endif %}
|
| 21 |
+
{% if bound == "" %}
|
| 22 |
+
let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 23 |
+
{%- elif guardInline %}
|
| 24 |
+
let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 25 |
+
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
+
{%- else %}
|
| 27 |
+
let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 28 |
+
if ({{ name }} >= {{ bound }}) {
|
| 29 |
+
return;
|
| 30 |
+
}
|
| 31 |
+
{%- endif %}
|
| 32 |
+
{% endmacro %}
|
| 33 |
+
|
| 34 |
+
enable f16;
|
| 35 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 36 |
+
|
| 37 |
+
// com.microsoft.PagedAttention, KV scatter pass.
|
| 38 |
+
// Writes this step's keys and values into the block-based cache before the
|
| 39 |
+
// attention pass reads it. One invocation moves one (token, kv head, dim)
|
| 40 |
+
// element of both caches.
|
| 41 |
+
const KV_HEADS: u32 = {{ kvNumHeads }}u;
|
| 42 |
+
const HEAD_DIM: u32 = {{ headSize }}u;
|
| 43 |
+
const BLOCK_SIZE: u32 = {{ blockSize }}u;
|
| 44 |
+
{% if not hasSlotMapping %}
|
| 45 |
+
const MAX_BLOCKS: u32 = {{ maxBlocks }}u;
|
| 46 |
+
{% endif %}
|
| 47 |
+
{% if packedQkv %}
|
| 48 |
+
// Packed layout: query carries [Q | K | V] on one row, so K starts after the
|
| 49 |
+
// query heads and V after the key heads.
|
| 50 |
+
const PACKED_STRIDE: u32 = {{ packedStride }}u;
|
| 51 |
+
const PACKED_K_OFFSET: u32 = {{ qHidden }}u;
|
| 52 |
+
const PACKED_V_OFFSET: u32 = {{ qHidden }}u + {{ kvHidden }}u;
|
| 53 |
+
{% else %}
|
| 54 |
+
const KV_STRIDE: u32 = {{ kvHidden }}u;
|
| 55 |
+
{% endif %}
|
| 56 |
+
|
| 57 |
+
{% macro varlen_sequence_of_token(buffer="cumulative_sequence_length") %}
|
| 58 |
+
fn sequence_of_token(token: u32, batch_size: u32) -> u32 {
|
| 59 |
+
for (var b: u32 = 0u; b < batch_size; b = b + 1u) {
|
| 60 |
+
if (token < u32({{ buffer }}[b + 1u])) {
|
| 61 |
+
return b;
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
return batch_size - 1u;
|
| 65 |
+
}
|
| 66 |
+
{%- endmacro %}
|
| 67 |
+
|
| 68 |
+
{% macro paged_sequence_index(withLookup=true) %}
|
| 69 |
+
{% if withLookup %}
|
| 70 |
+
{{ varlen_sequence_of_token() }}
|
| 71 |
+
{% endif %}
|
| 72 |
+
|
| 73 |
+
// Physical element base of one (slot, kv head) row of the paged cache, whose
|
| 74 |
+
// layout is (num_blocks, block_size, kv_num_heads, head_size).
|
| 75 |
+
fn paged_row_base(block_id: u32, slot_in_block: u32, kv_head: u32) -> u32 {
|
| 76 |
+
return ((block_id * BLOCK_SIZE + slot_in_block) * KV_HEADS + kv_head) * HEAD_DIM;
|
| 77 |
+
}
|
| 78 |
+
{%- endmacro %}
|
| 79 |
+
|
| 80 |
+
{{ paged_sequence_index(withLookup=not hasSlotMapping) }}
|
| 81 |
+
|
| 82 |
+
@compute @workgroup_size({{ tunables.SCATTER_WORKGROUP_SIZE }})
|
| 83 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
|
| 84 |
+
{{ flat_index_2d(bound="params.scatterCount", note="element-limit") }}
|
| 85 |
+
let dim = i % HEAD_DIM;
|
| 86 |
+
let kv_head = (i / HEAD_DIM) % KV_HEADS;
|
| 87 |
+
let token = i / (KV_HEADS * HEAD_DIM);
|
| 88 |
+
|
| 89 |
+
{% if hasSlotMapping %}
|
| 90 |
+
// slot_mapping gives the flat cache slot directly, and -1 means "do not store
|
| 91 |
+
// this token" -- the scheduler uses it to suppress writes for a prefix-cache
|
| 92 |
+
// hit or a rejected speculative token. block_table still defines the read path.
|
| 93 |
+
let slot = slot_mapping[token];
|
| 94 |
+
if (slot < 0) {
|
| 95 |
+
return;
|
| 96 |
+
}
|
| 97 |
+
let block_id = u32(slot) / BLOCK_SIZE;
|
| 98 |
+
let slot_in_block = u32(slot) % BLOCK_SIZE;
|
| 99 |
+
{% else %}
|
| 100 |
+
// Derived slot: the token's absolute position in its sequence's cache is the
|
| 101 |
+
// past length plus its offset among this step's tokens.
|
| 102 |
+
let sequence = sequence_of_token(token, params.batchSize);
|
| 103 |
+
let local_token = token - u32(cumulative_sequence_length[sequence]);
|
| 104 |
+
let absolute_slot = u32(past_seqlens[sequence]) + local_token;
|
| 105 |
+
let block_id = u32(block_table[sequence * MAX_BLOCKS + absolute_slot / BLOCK_SIZE]);
|
| 106 |
+
let slot_in_block = absolute_slot % BLOCK_SIZE;
|
| 107 |
+
{% endif %}
|
| 108 |
+
|
| 109 |
+
{% if packedQkv %}
|
| 110 |
+
let source = token * PACKED_STRIDE + kv_head * HEAD_DIM + dim;
|
| 111 |
+
let key_value = query[source + PACKED_K_OFFSET];
|
| 112 |
+
let value_value = query[source + PACKED_V_OFFSET];
|
| 113 |
+
{% else %}
|
| 114 |
+
let source = token * KV_STRIDE + kv_head * HEAD_DIM + dim;
|
| 115 |
+
let key_value = key[source];
|
| 116 |
+
let value_value = value[source];
|
| 117 |
+
{% endif %}
|
| 118 |
+
let destination = paged_row_base(block_id, slot_in_block, kv_head) + dim;
|
| 119 |
+
key_cache[destination] = key_value;
|
| 120 |
+
value_cache[destination] = value_value;
|
| 121 |
+
}
|
build/webgpu/test.json
ADDED
|
@@ -0,0 +1,803 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.PagedAttention",
|
| 3 |
+
"cases": [
|
| 4 |
+
{
|
| 5 |
+
"name": "two_sequences_gqa",
|
| 6 |
+
"provenance": {
|
| 7 |
+
"notes": "Two packed sequences with different histories. The first sequence's two new tokens land at absolute slots 3 and 4, so the scatter crosses a block boundary and the attention reads two physical blocks."
|
| 8 |
+
},
|
| 9 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 10 |
+
"inputs": {
|
| 11 |
+
"queryT": {
|
| 12 |
+
"dtype": "float16",
|
| 13 |
+
"shape": [5, 32],
|
| 14 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 15 |
+
},
|
| 16 |
+
"keyT": {
|
| 17 |
+
"dtype": "float16",
|
| 18 |
+
"shape": [5, 16],
|
| 19 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 20 |
+
},
|
| 21 |
+
"valueT": {
|
| 22 |
+
"dtype": "float16",
|
| 23 |
+
"shape": [5, 16],
|
| 24 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 25 |
+
},
|
| 26 |
+
"keyCacheT": {
|
| 27 |
+
"dtype": "float16",
|
| 28 |
+
"shape": [6, 4, 2, 8],
|
| 29 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 30 |
+
},
|
| 31 |
+
"valueCacheT": {
|
| 32 |
+
"dtype": "float16",
|
| 33 |
+
"shape": [6, 4, 2, 8],
|
| 34 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 35 |
+
},
|
| 36 |
+
"cumulativeSequenceLengthT": {
|
| 37 |
+
"dtype": "int32",
|
| 38 |
+
"shape": [3],
|
| 39 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 40 |
+
},
|
| 41 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 42 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } }
|
| 43 |
+
},
|
| 44 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"name": "two_sequences_packed_qkv",
|
| 48 |
+
"provenance": { "notes": "Same schedule with Q, K and V packed on one query row." },
|
| 49 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 50 |
+
"inputs": {
|
| 51 |
+
"queryT": {
|
| 52 |
+
"dtype": "float16",
|
| 53 |
+
"shape": [5, 64],
|
| 54 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 55 |
+
},
|
| 56 |
+
"keyCacheT": {
|
| 57 |
+
"dtype": "float16",
|
| 58 |
+
"shape": [6, 4, 2, 8],
|
| 59 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 60 |
+
},
|
| 61 |
+
"valueCacheT": {
|
| 62 |
+
"dtype": "float16",
|
| 63 |
+
"shape": [6, 4, 2, 8],
|
| 64 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 65 |
+
},
|
| 66 |
+
"cumulativeSequenceLengthT": {
|
| 67 |
+
"dtype": "int32",
|
| 68 |
+
"shape": [3],
|
| 69 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 70 |
+
},
|
| 71 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 72 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } }
|
| 73 |
+
},
|
| 74 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"name": "two_sequences_key_cache_output",
|
| 78 |
+
"provenance": {
|
| 79 |
+
"notes": "Requests only the updated key-cache alias; the paired schema value-cache output remains internal."
|
| 80 |
+
},
|
| 81 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 82 |
+
"inputs": {
|
| 83 |
+
"queryT": {
|
| 84 |
+
"dtype": "float16",
|
| 85 |
+
"shape": [5, 32],
|
| 86 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 87 |
+
},
|
| 88 |
+
"keyT": {
|
| 89 |
+
"dtype": "float16",
|
| 90 |
+
"shape": [5, 16],
|
| 91 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 92 |
+
},
|
| 93 |
+
"valueT": {
|
| 94 |
+
"dtype": "float16",
|
| 95 |
+
"shape": [5, 16],
|
| 96 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 97 |
+
},
|
| 98 |
+
"keyCacheT": {
|
| 99 |
+
"dtype": "float16",
|
| 100 |
+
"shape": [6, 4, 2, 8],
|
| 101 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 102 |
+
},
|
| 103 |
+
"valueCacheT": {
|
| 104 |
+
"dtype": "float16",
|
| 105 |
+
"shape": [6, 4, 2, 8],
|
| 106 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 107 |
+
},
|
| 108 |
+
"cumulativeSequenceLengthT": {
|
| 109 |
+
"dtype": "int32",
|
| 110 |
+
"shape": [3],
|
| 111 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 112 |
+
},
|
| 113 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 114 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } }
|
| 115 |
+
},
|
| 116 |
+
"outputs": {
|
| 117 |
+
"outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 },
|
| 118 |
+
"keyCacheT": { "dtype": "float16", "shape": [6, 4, 2, 8], "tolerance": 0.00001, "relTolerance": 0.0001 }
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"name": "explicit_scale",
|
| 123 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2, "scale": 0.125, "is_causal": 1 },
|
| 124 |
+
"inputs": {
|
| 125 |
+
"queryT": {
|
| 126 |
+
"dtype": "float16",
|
| 127 |
+
"shape": [5, 32],
|
| 128 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 129 |
+
},
|
| 130 |
+
"keyT": {
|
| 131 |
+
"dtype": "float16",
|
| 132 |
+
"shape": [5, 16],
|
| 133 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 134 |
+
},
|
| 135 |
+
"valueT": {
|
| 136 |
+
"dtype": "float16",
|
| 137 |
+
"shape": [5, 16],
|
| 138 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 139 |
+
},
|
| 140 |
+
"keyCacheT": {
|
| 141 |
+
"dtype": "float16",
|
| 142 |
+
"shape": [6, 4, 2, 8],
|
| 143 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 144 |
+
},
|
| 145 |
+
"valueCacheT": {
|
| 146 |
+
"dtype": "float16",
|
| 147 |
+
"shape": [6, 4, 2, 8],
|
| 148 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 149 |
+
},
|
| 150 |
+
"cumulativeSequenceLengthT": {
|
| 151 |
+
"dtype": "int32",
|
| 152 |
+
"shape": [3],
|
| 153 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 154 |
+
},
|
| 155 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 156 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } }
|
| 157 |
+
},
|
| 158 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"name": "slot_mapping_explicit",
|
| 162 |
+
"provenance": {
|
| 163 |
+
"notes": "slot_mapping names each token's flat cache slot directly; these values reproduce the derived mapping, and this case requests only the updated value-cache alias."
|
| 164 |
+
},
|
| 165 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 166 |
+
"inputs": {
|
| 167 |
+
"queryT": {
|
| 168 |
+
"dtype": "float16",
|
| 169 |
+
"shape": [5, 32],
|
| 170 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 171 |
+
},
|
| 172 |
+
"keyT": {
|
| 173 |
+
"dtype": "float16",
|
| 174 |
+
"shape": [5, 16],
|
| 175 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 176 |
+
},
|
| 177 |
+
"valueT": {
|
| 178 |
+
"dtype": "float16",
|
| 179 |
+
"shape": [5, 16],
|
| 180 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 181 |
+
},
|
| 182 |
+
"keyCacheT": {
|
| 183 |
+
"dtype": "float16",
|
| 184 |
+
"shape": [6, 4, 2, 8],
|
| 185 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 186 |
+
},
|
| 187 |
+
"valueCacheT": {
|
| 188 |
+
"dtype": "float16",
|
| 189 |
+
"shape": [6, 4, 2, 8],
|
| 190 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 191 |
+
},
|
| 192 |
+
"cumulativeSequenceLengthT": {
|
| 193 |
+
"dtype": "int32",
|
| 194 |
+
"shape": [3],
|
| 195 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 196 |
+
},
|
| 197 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 198 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } },
|
| 199 |
+
"slotMappingT": { "dtype": "int32", "shape": [5], "data": { "kind": "values", "values": [3, 4, 12, 13, 14] } }
|
| 200 |
+
},
|
| 201 |
+
"outputs": {
|
| 202 |
+
"outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 },
|
| 203 |
+
"valueCacheT": { "dtype": "float16", "shape": [6, 4, 2, 8], "tolerance": 0.00001, "relTolerance": 0.0001 }
|
| 204 |
+
}
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"name": "slot_mapping_skips_token",
|
| 208 |
+
"provenance": {
|
| 209 |
+
"notes": "A slot of -1 suppresses that token's cache write, which a scheduler uses for a prefix-cache hit or a rejected speculative token. The cache slot keeps its prior contents and attention still reads it."
|
| 210 |
+
},
|
| 211 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 212 |
+
"inputs": {
|
| 213 |
+
"queryT": {
|
| 214 |
+
"dtype": "float16",
|
| 215 |
+
"shape": [5, 32],
|
| 216 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 217 |
+
},
|
| 218 |
+
"keyT": {
|
| 219 |
+
"dtype": "float16",
|
| 220 |
+
"shape": [5, 16],
|
| 221 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 222 |
+
},
|
| 223 |
+
"valueT": {
|
| 224 |
+
"dtype": "float16",
|
| 225 |
+
"shape": [5, 16],
|
| 226 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 227 |
+
},
|
| 228 |
+
"keyCacheT": {
|
| 229 |
+
"dtype": "float16",
|
| 230 |
+
"shape": [6, 4, 2, 8],
|
| 231 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 232 |
+
},
|
| 233 |
+
"valueCacheT": {
|
| 234 |
+
"dtype": "float16",
|
| 235 |
+
"shape": [6, 4, 2, 8],
|
| 236 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 237 |
+
},
|
| 238 |
+
"cumulativeSequenceLengthT": {
|
| 239 |
+
"dtype": "int32",
|
| 240 |
+
"shape": [3],
|
| 241 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 242 |
+
},
|
| 243 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 244 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } },
|
| 245 |
+
"slotMappingT": { "dtype": "int32", "shape": [5], "data": { "kind": "values", "values": [3, -1, 12, 13, 14] } }
|
| 246 |
+
},
|
| 247 |
+
"outputs": {
|
| 248 |
+
"outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 },
|
| 249 |
+
"keyCacheT": { "dtype": "float16", "shape": [6, 4, 2, 8], "tolerance": 0.00001, "relTolerance": 0.0001 },
|
| 250 |
+
"valueCacheT": { "dtype": "float16", "shape": [6, 4, 2, 8], "tolerance": 0.00001, "relTolerance": 0.0001 }
|
| 251 |
+
}
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"name": "packed_qkv_slot_mapping",
|
| 255 |
+
"provenance": {
|
| 256 |
+
"notes": "Packed [Q|K|V] with an explicit slot mapping, so the scatter reads K and V out of the query row and binds no schedule tensors at all."
|
| 257 |
+
},
|
| 258 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 259 |
+
"inputs": {
|
| 260 |
+
"queryT": {
|
| 261 |
+
"dtype": "float16",
|
| 262 |
+
"shape": [5, 64],
|
| 263 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 264 |
+
},
|
| 265 |
+
"keyCacheT": {
|
| 266 |
+
"dtype": "float16",
|
| 267 |
+
"shape": [6, 4, 2, 8],
|
| 268 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 269 |
+
},
|
| 270 |
+
"valueCacheT": {
|
| 271 |
+
"dtype": "float16",
|
| 272 |
+
"shape": [6, 4, 2, 8],
|
| 273 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 274 |
+
},
|
| 275 |
+
"cumulativeSequenceLengthT": {
|
| 276 |
+
"dtype": "int32",
|
| 277 |
+
"shape": [3],
|
| 278 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 279 |
+
},
|
| 280 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 281 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } },
|
| 282 |
+
"slotMappingT": { "dtype": "int32", "shape": [5], "data": { "kind": "values", "values": [3, 4, 12, 13, 14] } }
|
| 283 |
+
},
|
| 284 |
+
"outputs": {
|
| 285 |
+
"outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 },
|
| 286 |
+
"keyCacheT": { "dtype": "float16", "shape": [6, 4, 2, 8], "tolerance": 0.00001, "relTolerance": 0.0001 },
|
| 287 |
+
"valueCacheT": { "dtype": "float16", "shape": [6, 4, 2, 8], "tolerance": 0.00001, "relTolerance": 0.0001 }
|
| 288 |
+
}
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
"name": "single_sequence_decode",
|
| 292 |
+
"provenance": {
|
| 293 |
+
"notes": "One decode token whose history spans two full blocks and lands at the start of the third."
|
| 294 |
+
},
|
| 295 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 296 |
+
"inputs": {
|
| 297 |
+
"queryT": {
|
| 298 |
+
"dtype": "float16",
|
| 299 |
+
"shape": [1, 32],
|
| 300 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 301 |
+
},
|
| 302 |
+
"keyT": {
|
| 303 |
+
"dtype": "float16",
|
| 304 |
+
"shape": [1, 16],
|
| 305 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 306 |
+
},
|
| 307 |
+
"valueT": {
|
| 308 |
+
"dtype": "float16",
|
| 309 |
+
"shape": [1, 16],
|
| 310 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 311 |
+
},
|
| 312 |
+
"keyCacheT": {
|
| 313 |
+
"dtype": "float16",
|
| 314 |
+
"shape": [3, 4, 2, 8],
|
| 315 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 316 |
+
},
|
| 317 |
+
"valueCacheT": {
|
| 318 |
+
"dtype": "float16",
|
| 319 |
+
"shape": [3, 4, 2, 8],
|
| 320 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 321 |
+
},
|
| 322 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
|
| 323 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [7] } },
|
| 324 |
+
"blockTableT": { "dtype": "int32", "shape": [1, 3], "data": { "kind": "values", "values": [0, 1, 2] } }
|
| 325 |
+
},
|
| 326 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [1, 32], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
"name": "equal_head_counts",
|
| 330 |
+
"provenance": { "notes": "num_heads equals kv_num_heads, so the grouped-query mapping is the identity." },
|
| 331 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 4 },
|
| 332 |
+
"inputs": {
|
| 333 |
+
"queryT": {
|
| 334 |
+
"dtype": "float16",
|
| 335 |
+
"shape": [5, 32],
|
| 336 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 337 |
+
},
|
| 338 |
+
"keyT": {
|
| 339 |
+
"dtype": "float16",
|
| 340 |
+
"shape": [5, 32],
|
| 341 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 342 |
+
},
|
| 343 |
+
"valueT": {
|
| 344 |
+
"dtype": "float16",
|
| 345 |
+
"shape": [5, 32],
|
| 346 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 347 |
+
},
|
| 348 |
+
"keyCacheT": {
|
| 349 |
+
"dtype": "float16",
|
| 350 |
+
"shape": [6, 4, 4, 8],
|
| 351 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 352 |
+
},
|
| 353 |
+
"valueCacheT": {
|
| 354 |
+
"dtype": "float16",
|
| 355 |
+
"shape": [6, 4, 4, 8],
|
| 356 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 357 |
+
},
|
| 358 |
+
"cumulativeSequenceLengthT": {
|
| 359 |
+
"dtype": "int32",
|
| 360 |
+
"shape": [3],
|
| 361 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 362 |
+
},
|
| 363 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 364 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } }
|
| 365 |
+
},
|
| 366 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 367 |
+
},
|
| 368 |
+
{
|
| 369 |
+
"name": "long_history_multi_tile",
|
| 370 |
+
"provenance": {
|
| 371 |
+
"notes": "A 71-key history exceeds one workgroup tile, and the block table is deliberately not the identity, so the online-softmax merge runs over multiple tiles rather than a single one."
|
| 372 |
+
},
|
| 373 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 374 |
+
"inputs": {
|
| 375 |
+
"queryT": {
|
| 376 |
+
"dtype": "float16",
|
| 377 |
+
"shape": [1, 16],
|
| 378 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 379 |
+
},
|
| 380 |
+
"keyT": {
|
| 381 |
+
"dtype": "float16",
|
| 382 |
+
"shape": [1, 8],
|
| 383 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 384 |
+
},
|
| 385 |
+
"valueT": {
|
| 386 |
+
"dtype": "float16",
|
| 387 |
+
"shape": [1, 8],
|
| 388 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 389 |
+
},
|
| 390 |
+
"keyCacheT": {
|
| 391 |
+
"dtype": "float16",
|
| 392 |
+
"shape": [9, 8, 1, 8],
|
| 393 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 394 |
+
},
|
| 395 |
+
"valueCacheT": {
|
| 396 |
+
"dtype": "float16",
|
| 397 |
+
"shape": [9, 8, 1, 8],
|
| 398 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 399 |
+
},
|
| 400 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
|
| 401 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [70] } },
|
| 402 |
+
"blockTableT": {
|
| 403 |
+
"dtype": "int32",
|
| 404 |
+
"shape": [1, 9],
|
| 405 |
+
"data": { "kind": "values", "values": [5, 2, 8, 0, 7, 1, 6, 3, 4] }
|
| 406 |
+
}
|
| 407 |
+
},
|
| 408 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [1, 16], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 409 |
+
},
|
| 410 |
+
{
|
| 411 |
+
"name": "head_size_16",
|
| 412 |
+
"provenance": {
|
| 413 |
+
"notes": "A head dimension wider than the tile's per-thread share, so the accumulator loop strides."
|
| 414 |
+
},
|
| 415 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 416 |
+
"inputs": {
|
| 417 |
+
"queryT": {
|
| 418 |
+
"dtype": "float16",
|
| 419 |
+
"shape": [3, 32],
|
| 420 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 421 |
+
},
|
| 422 |
+
"keyT": {
|
| 423 |
+
"dtype": "float16",
|
| 424 |
+
"shape": [3, 16],
|
| 425 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 426 |
+
},
|
| 427 |
+
"valueT": {
|
| 428 |
+
"dtype": "float16",
|
| 429 |
+
"shape": [3, 16],
|
| 430 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 431 |
+
},
|
| 432 |
+
"keyCacheT": {
|
| 433 |
+
"dtype": "float16",
|
| 434 |
+
"shape": [4, 4, 1, 16],
|
| 435 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 436 |
+
},
|
| 437 |
+
"valueCacheT": {
|
| 438 |
+
"dtype": "float16",
|
| 439 |
+
"shape": [4, 4, 1, 16],
|
| 440 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 441 |
+
},
|
| 442 |
+
"cumulativeSequenceLengthT": {
|
| 443 |
+
"dtype": "int32",
|
| 444 |
+
"shape": [3],
|
| 445 |
+
"data": { "kind": "values", "values": [0, 2, 3] }
|
| 446 |
+
},
|
| 447 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [1, 5] } },
|
| 448 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 2], "data": { "kind": "values", "values": [0, 1, 2, 3] } }
|
| 449 |
+
},
|
| 450 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [3, 32], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"name": "f16_two_sequences",
|
| 454 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 455 |
+
"inputs": {
|
| 456 |
+
"queryT": {
|
| 457 |
+
"dtype": "float16",
|
| 458 |
+
"shape": [5, 32],
|
| 459 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 460 |
+
},
|
| 461 |
+
"keyT": {
|
| 462 |
+
"dtype": "float16",
|
| 463 |
+
"shape": [5, 16],
|
| 464 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 465 |
+
},
|
| 466 |
+
"valueT": {
|
| 467 |
+
"dtype": "float16",
|
| 468 |
+
"shape": [5, 16],
|
| 469 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 470 |
+
},
|
| 471 |
+
"keyCacheT": {
|
| 472 |
+
"dtype": "float16",
|
| 473 |
+
"shape": [6, 4, 2, 8],
|
| 474 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 475 |
+
},
|
| 476 |
+
"valueCacheT": {
|
| 477 |
+
"dtype": "float16",
|
| 478 |
+
"shape": [6, 4, 2, 8],
|
| 479 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 480 |
+
},
|
| 481 |
+
"cumulativeSequenceLengthT": {
|
| 482 |
+
"dtype": "int32",
|
| 483 |
+
"shape": [3],
|
| 484 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 485 |
+
},
|
| 486 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 487 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } }
|
| 488 |
+
},
|
| 489 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.004, "relTolerance": 0.01 } }
|
| 490 |
+
},
|
| 491 |
+
{
|
| 492 |
+
"name": "f16_packed_qkv",
|
| 493 |
+
"attrs": { "num_heads": 4, "kv_num_heads": 2 },
|
| 494 |
+
"inputs": {
|
| 495 |
+
"queryT": {
|
| 496 |
+
"dtype": "float16",
|
| 497 |
+
"shape": [5, 64],
|
| 498 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 499 |
+
},
|
| 500 |
+
"keyCacheT": {
|
| 501 |
+
"dtype": "float16",
|
| 502 |
+
"shape": [6, 4, 2, 8],
|
| 503 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 504 |
+
},
|
| 505 |
+
"valueCacheT": {
|
| 506 |
+
"dtype": "float16",
|
| 507 |
+
"shape": [6, 4, 2, 8],
|
| 508 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 509 |
+
},
|
| 510 |
+
"cumulativeSequenceLengthT": {
|
| 511 |
+
"dtype": "int32",
|
| 512 |
+
"shape": [3],
|
| 513 |
+
"data": { "kind": "values", "values": [0, 2, 5] }
|
| 514 |
+
},
|
| 515 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } },
|
| 516 |
+
"blockTableT": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [0, 1, 2, 3, 4, 5] } }
|
| 517 |
+
},
|
| 518 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [5, 32], "tolerance": 0.004, "relTolerance": 0.01 } }
|
| 519 |
+
},
|
| 520 |
+
{
|
| 521 |
+
"name": "pinned_paged_history",
|
| 522 |
+
"provenance": {
|
| 523 |
+
"notes": "Expected values computed by an independent implementation written from the ONNX Runtime schema text alone, with every tensor value rounded to its declared float16 dtype before evaluation, so this case checks the trusted reference as well as the kernels. The block table [2, 0, 1] is deliberately not the identity, so a kernel that ignored it would read the wrong blocks; the two new tokens land in blocks 0 and 1 respectively."
|
| 524 |
+
},
|
| 525 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 526 |
+
"inputs": {
|
| 527 |
+
"queryT": {
|
| 528 |
+
"dtype": "float16",
|
| 529 |
+
"shape": [2, 4],
|
| 530 |
+
"data": { "kind": "values", "values": [0.7854, 1.0234, 1.039, 0.8123, 0.3859, -0.1448, -0.6562, -1.028] }
|
| 531 |
+
},
|
| 532 |
+
"keyT": {
|
| 533 |
+
"dtype": "float16",
|
| 534 |
+
"shape": [2, 2],
|
| 535 |
+
"data": { "kind": "values", "values": [-0.3046, -0.0781, 0.1597, 0.3877] }
|
| 536 |
+
},
|
| 537 |
+
"valueT": {
|
| 538 |
+
"dtype": "float16",
|
| 539 |
+
"shape": [2, 2],
|
| 540 |
+
"data": { "kind": "values", "values": [0.5647, 0.756, 0.8391, 0.7958] }
|
| 541 |
+
},
|
| 542 |
+
"keyCacheT": {
|
| 543 |
+
"dtype": "float16",
|
| 544 |
+
"shape": [3, 2, 1, 2],
|
| 545 |
+
"data": {
|
| 546 |
+
"kind": "values",
|
| 547 |
+
"values": [0.2751, 0.3295, 0.3637, 0.3794, 0.3798, 0.369, 0.3514, 0.3311, 0.3118, 0.296, 0.2849, 0.2785]
|
| 548 |
+
}
|
| 549 |
+
},
|
| 550 |
+
"valueCacheT": {
|
| 551 |
+
"dtype": "float16",
|
| 552 |
+
"shape": [3, 2, 1, 2],
|
| 553 |
+
"data": {
|
| 554 |
+
"kind": "values",
|
| 555 |
+
"values": [-0.0755, 0.1377, 0.2257, 0.1768, 0.0339, -0.1232, -0.2093, -0.1675, 0.0079, 0.2677, 0.5238, 0.6804]
|
| 556 |
+
}
|
| 557 |
+
},
|
| 558 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 2] } },
|
| 559 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [2] } },
|
| 560 |
+
"blockTableT": { "dtype": "int32", "shape": [1, 3], "data": { "kind": "values", "values": [2, 0, 1] } }
|
| 561 |
+
},
|
| 562 |
+
"outputs": {
|
| 563 |
+
"outputT": {
|
| 564 |
+
"dtype": "float16",
|
| 565 |
+
"shape": [2, 4],
|
| 566 |
+
"data": {
|
| 567 |
+
"kind": "values",
|
| 568 |
+
"values": [0.32763671875, 0.5322265625, 0.32470703125, 0.52978515625, 0.4765625, 0.6181640625, 0.4970703125, 0.64599609375]
|
| 569 |
+
},
|
| 570 |
+
"tolerance": 0.000001,
|
| 571 |
+
"relTolerance": 0.00001
|
| 572 |
+
},
|
| 573 |
+
"keyCacheT": {
|
| 574 |
+
"dtype": "float16",
|
| 575 |
+
"shape": [3, 2, 1, 2],
|
| 576 |
+
"data": {
|
| 577 |
+
"kind": "values",
|
| 578 |
+
"values": [-0.3046, -0.0781, 0.1597, 0.3877, 0.3798, 0.369, 0.3514, 0.3311, 0.3118, 0.296, 0.2849, 0.2785]
|
| 579 |
+
},
|
| 580 |
+
"tolerance": 0.000001,
|
| 581 |
+
"relTolerance": 0.000001
|
| 582 |
+
},
|
| 583 |
+
"valueCacheT": {
|
| 584 |
+
"dtype": "float16",
|
| 585 |
+
"shape": [3, 2, 1, 2],
|
| 586 |
+
"data": {
|
| 587 |
+
"kind": "values",
|
| 588 |
+
"values": [0.5647, 0.756, 0.8391, 0.7958, 0.0339, -0.1232, -0.2093, -0.1675, 0.0079, 0.2677, 0.5238, 0.6804]
|
| 589 |
+
},
|
| 590 |
+
"tolerance": 0.000001,
|
| 591 |
+
"relTolerance": 0.000001
|
| 592 |
+
}
|
| 593 |
+
}
|
| 594 |
+
},
|
| 595 |
+
{
|
| 596 |
+
"name": "splitk_multi_split_history",
|
| 597 |
+
"provenance": {
|
| 598 |
+
"notes": "A 301-key history over a 512-key block table, which is what puts the split-K schedule above its two-split floor: four contiguous key ranges of 76 keys each cross a workgroup tile boundary, so every range runs the tile loop more than once and the merge folds four non-empty online states. Grouped-query (2 query heads per KV head) so the split epilogue writes both streams."
|
| 599 |
+
},
|
| 600 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 601 |
+
"inputs": {
|
| 602 |
+
"queryT": {
|
| 603 |
+
"dtype": "float16",
|
| 604 |
+
"shape": [1, 16],
|
| 605 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 606 |
+
},
|
| 607 |
+
"keyT": {
|
| 608 |
+
"dtype": "float16",
|
| 609 |
+
"shape": [1, 8],
|
| 610 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 611 |
+
},
|
| 612 |
+
"valueT": {
|
| 613 |
+
"dtype": "float16",
|
| 614 |
+
"shape": [1, 8],
|
| 615 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 616 |
+
},
|
| 617 |
+
"keyCacheT": {
|
| 618 |
+
"dtype": "float16",
|
| 619 |
+
"shape": [64, 8, 1, 8],
|
| 620 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 621 |
+
},
|
| 622 |
+
"valueCacheT": {
|
| 623 |
+
"dtype": "float16",
|
| 624 |
+
"shape": [64, 8, 1, 8],
|
| 625 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 626 |
+
},
|
| 627 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
|
| 628 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [300] } },
|
| 629 |
+
"blockTableT": {
|
| 630 |
+
"dtype": "int32",
|
| 631 |
+
"shape": [1, 64],
|
| 632 |
+
"data": {
|
| 633 |
+
"kind": "values",
|
| 634 |
+
"values": [3, 10, 17, 24, 31, 38, 45, 52, 59, 2, 9, 16, 23, 30, 37, 44, 51, 58, 1, 8, 15, 22, 29, 36, 43, 50, 57, 0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 6, 13, 20, 27, 34, 41, 48, 55, 62, 5, 12, 19, 26, 33, 40, 47, 54, 61, 4, 11, 18, 25, 32, 39, 46, 53, 60]
|
| 635 |
+
}
|
| 636 |
+
}
|
| 637 |
+
},
|
| 638 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [1, 16], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 639 |
+
},
|
| 640 |
+
{
|
| 641 |
+
"name": "splitk_empty_trailing_split",
|
| 642 |
+
"provenance": {
|
| 643 |
+
"notes": "One key of history over a 256-key block table: the split schedule is two ranges of one key each, so the second range covers nothing. It is the split that must write the (-FLT_MAX, 0) identity rather than a normalized row, and the merge must fold that identity away."
|
| 644 |
+
},
|
| 645 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 646 |
+
"inputs": {
|
| 647 |
+
"queryT": {
|
| 648 |
+
"dtype": "float16",
|
| 649 |
+
"shape": [1, 16],
|
| 650 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 651 |
+
},
|
| 652 |
+
"keyT": {
|
| 653 |
+
"dtype": "float16",
|
| 654 |
+
"shape": [1, 8],
|
| 655 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 656 |
+
},
|
| 657 |
+
"valueT": {
|
| 658 |
+
"dtype": "float16",
|
| 659 |
+
"shape": [1, 8],
|
| 660 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 661 |
+
},
|
| 662 |
+
"keyCacheT": {
|
| 663 |
+
"dtype": "float16",
|
| 664 |
+
"shape": [32, 8, 1, 8],
|
| 665 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 666 |
+
},
|
| 667 |
+
"valueCacheT": {
|
| 668 |
+
"dtype": "float16",
|
| 669 |
+
"shape": [32, 8, 1, 8],
|
| 670 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 671 |
+
},
|
| 672 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
|
| 673 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 674 |
+
"blockTableT": {
|
| 675 |
+
"dtype": "int32",
|
| 676 |
+
"shape": [1, 32],
|
| 677 |
+
"data": {
|
| 678 |
+
"kind": "values",
|
| 679 |
+
"values": [5, 12, 19, 26, 1, 8, 15, 22, 29, 4, 11, 18, 25, 0, 7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30]
|
| 680 |
+
}
|
| 681 |
+
}
|
| 682 |
+
},
|
| 683 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [1, 16], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 684 |
+
},
|
| 685 |
+
{
|
| 686 |
+
"name": "splitk_slot_mapping",
|
| 687 |
+
"provenance": {
|
| 688 |
+
"notes": "The split-K schedule with an explicit slot mapping instead of a derived write slot: same 201-key history over a 256-key block table, so the two split ranges both run and the scatter pass takes the slot-mapping arm."
|
| 689 |
+
},
|
| 690 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 691 |
+
"inputs": {
|
| 692 |
+
"queryT": {
|
| 693 |
+
"dtype": "float16",
|
| 694 |
+
"shape": [1, 16],
|
| 695 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 696 |
+
},
|
| 697 |
+
"keyT": {
|
| 698 |
+
"dtype": "float16",
|
| 699 |
+
"shape": [1, 8],
|
| 700 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.13, "scale": 0.8 }
|
| 701 |
+
},
|
| 702 |
+
"valueT": {
|
| 703 |
+
"dtype": "float16",
|
| 704 |
+
"shape": [1, 8],
|
| 705 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.07, "scale": 0.7 }
|
| 706 |
+
},
|
| 707 |
+
"keyCacheT": {
|
| 708 |
+
"dtype": "float16",
|
| 709 |
+
"shape": [32, 8, 1, 8],
|
| 710 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 711 |
+
},
|
| 712 |
+
"valueCacheT": {
|
| 713 |
+
"dtype": "float16",
|
| 714 |
+
"shape": [32, 8, 1, 8],
|
| 715 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 716 |
+
},
|
| 717 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
|
| 718 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [200] } },
|
| 719 |
+
"blockTableT": {
|
| 720 |
+
"dtype": "int32",
|
| 721 |
+
"shape": [1, 32],
|
| 722 |
+
"data": {
|
| 723 |
+
"kind": "values",
|
| 724 |
+
"values": [7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30, 5, 12, 19, 26, 1, 8, 15, 22, 29, 4, 11, 18, 25, 0]
|
| 725 |
+
}
|
| 726 |
+
},
|
| 727 |
+
"slotMappingT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [176] } }
|
| 728 |
+
},
|
| 729 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [1, 16], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 730 |
+
},
|
| 731 |
+
{
|
| 732 |
+
"name": "splitk_packed_qkv",
|
| 733 |
+
"provenance": {
|
| 734 |
+
"notes": "The split-K schedule reading a packed [Q; K; V] query row, so the split pass strides the packed query layout while the merge writes the unpacked output."
|
| 735 |
+
},
|
| 736 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 737 |
+
"inputs": {
|
| 738 |
+
"queryT": {
|
| 739 |
+
"dtype": "float16",
|
| 740 |
+
"shape": [1, 32],
|
| 741 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 742 |
+
},
|
| 743 |
+
"keyCacheT": {
|
| 744 |
+
"dtype": "float16",
|
| 745 |
+
"shape": [32, 8, 1, 8],
|
| 746 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 747 |
+
},
|
| 748 |
+
"valueCacheT": {
|
| 749 |
+
"dtype": "float16",
|
| 750 |
+
"shape": [32, 8, 1, 8],
|
| 751 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 752 |
+
},
|
| 753 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
|
| 754 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [200] } },
|
| 755 |
+
"blockTableT": {
|
| 756 |
+
"dtype": "int32",
|
| 757 |
+
"shape": [1, 32],
|
| 758 |
+
"data": {
|
| 759 |
+
"kind": "values",
|
| 760 |
+
"values": [9, 16, 23, 30, 5, 12, 19, 26, 1, 8, 15, 22, 29, 4, 11, 18, 25, 0, 7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2]
|
| 761 |
+
}
|
| 762 |
+
}
|
| 763 |
+
},
|
| 764 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [1, 16], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 765 |
+
},
|
| 766 |
+
{
|
| 767 |
+
"name": "splitk_packed_qkv_slot_mapping",
|
| 768 |
+
"provenance": {
|
| 769 |
+
"notes": "The split-K schedule with both a packed query row and an explicit slot mapping, the last of the four scatter/query combinations the family covers."
|
| 770 |
+
},
|
| 771 |
+
"attrs": { "num_heads": 2, "kv_num_heads": 1 },
|
| 772 |
+
"inputs": {
|
| 773 |
+
"queryT": {
|
| 774 |
+
"dtype": "float16",
|
| 775 |
+
"shape": [1, 32],
|
| 776 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.9 }
|
| 777 |
+
},
|
| 778 |
+
"keyCacheT": {
|
| 779 |
+
"dtype": "float16",
|
| 780 |
+
"shape": [32, 8, 1, 8],
|
| 781 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.6 }
|
| 782 |
+
},
|
| 783 |
+
"valueCacheT": {
|
| 784 |
+
"dtype": "float16",
|
| 785 |
+
"shape": [32, 8, 1, 8],
|
| 786 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 0.5 }
|
| 787 |
+
},
|
| 788 |
+
"cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
|
| 789 |
+
"pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [200] } },
|
| 790 |
+
"blockTableT": {
|
| 791 |
+
"dtype": "int32",
|
| 792 |
+
"shape": [1, 32],
|
| 793 |
+
"data": {
|
| 794 |
+
"kind": "values",
|
| 795 |
+
"values": [11, 18, 25, 0, 7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30, 5, 12, 19, 26, 1, 8, 15, 22, 29, 4]
|
| 796 |
+
}
|
| 797 |
+
},
|
| 798 |
+
"slotMappingT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [208] } }
|
| 799 |
+
},
|
| 800 |
+
"outputs": { "outputT": { "dtype": "float16", "shape": [1, 16], "tolerance": 0.00001, "relTolerance": 0.0001 } }
|
| 801 |
+
}
|
| 802 |
+
]
|
| 803 |
+
}
|