Xenova HF Staff commited on
Commit
d882115
·
verified ·
1 Parent(s): 0d00cf0

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -1,3 +1,112 @@
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.DecoderMaskedMultiHeadAttention
10
+
11
+ `com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
12
+
13
+ ## Description
14
+
15
+ Single-token decode attention, self and cross, excluding the QKV projection. Cross attention reads whole encoder key and value tensors; self attention appends this token to a capacity-sized cache whose live length arrives in `past_sequence_length`. The mask is additive: a zero entry adds `mask_filter_value` rather than removing the key. The optional `qk` output is the scaled score row before the softmax, and is produced for cross attention only.
16
+
17
+ See the [ONNX Runtime `DecoderMaskedMultiHeadAttention` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.DecoderMaskedMultiHeadAttention) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `queryT` | `query` | `T` | `3` | — | Query row for the single decoded token, with shape `(batch_size, 1, num_heads * head_size)`. | required |
24
+ | `keyT` | `key` | `T` | — | — | Cross attention: the full encoder keys with shape `(batch_size, num_heads, kv_sequence_length, head_size)`. Self attention: this token's key row with shape `(batch_size, 1, num_heads * head_size)`. | required |
25
+ | `valueT` | `value` | `T` | — | — | Cross attention: the full encoder values, shaped like `key`. Self attention: this token's value row, shaped like `key`. | required |
26
+ | `maskIndexT` | `mask_index` | `M` | `2` | — | Key-padding mask with shape `(batch_size, total_sequence_length)`. A zero entry ADDS `mask_filter_value` to that key's score; it does not remove the key. | optional |
27
+ | `attentionBiasT` | `attention_bias` | `T` | `4` | — | Additive score bias with shape `(batch_size or 1, num_heads or 1, 1, total_sequence_length)`. Self attention only. | optional |
28
+ | `pastKeyT` | `past_key` | `T` | `4` | — | Key cache at capacity, with shape `(batch_size, num_heads, max_sequence_length, head_size)`. Its presence selects self attention. | optional |
29
+ | `pastValueT` | `past_value` | `T` | `4` | — | Value cache at capacity, shaped like `past_key`. | optional |
30
+ | `pastSequenceLengthT` | `past_sequence_length` | `M` | `1` | — | Live cache length before this token, as a single value. It selects the cache row this token is written to and bounds the key loop, so it is read from tensor data rather than baked. | optional |
31
+
32
+ ## Outputs
33
+
34
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
35
+ | --- | --- | --- | --- | --- | --- | --- |
36
+ | `outputT` | `output` | `T` | `3` | `[queryT[0], 1, queryT[2]]` | Attention result with shape `(batch_size, 1, num_heads * head_size)`. | required |
37
+ | `presentKeyT` | `present_key` | `T` | `4` | same as `pastKeyT` | Key cache after appending this token, at capacity. Every row other than the appended one keeps its past value. | optional |
38
+ | `presentValueT` | `present_value` | `T` | `4` | same as `pastValueT` | Value cache after appending this token, shaped like `present_key`. | optional |
39
+ | `qkT` | `qk` | `QK` | `4` | `[queryT[0], num_heads, 1, keyT[2]]` | Scaled `Q * K^T` with shape `(batch_size, num_heads, 1, kv_sequence_length)`, taken after the mask and BEFORE the softmax despite the schema calling it normalized. Cross attention only. | optional |
40
+
41
+ ## Attributes
42
+
43
+ Attributes and default values (overridable per request):
44
+
45
+ | Attribute | Default | Description |
46
+ | --- | --- | --- |
47
+ | `mask_filter_value` | `-10000` | Value added to the score of a key whose mask entry is zero. |
48
+ | `num_heads` | — | Number of attention heads. The head size is the query hidden size divided by this count. |
49
+ | `output_qk` | — | Set to 1 to emit the pre-softmax `qk` output. It must agree with whether the caller supplies that output. |
50
+ | `past_present_share_buffer` | — | Set to 1 when the past and present caches are one capacity-sized buffer. Self attention requires it; cross attention has no cache and leaves it at 0. |
51
+ | `scale` | — | Multiplier applied to `Q * K^T`. Omitted or zero selects the default `1 / sqrt(head_size)`. |
52
+
53
+ ## Type constraints
54
+
55
+ | Variable | Allowed dtypes |
56
+ | --- | --- |
57
+ | `T` | `float32`, `float16` |
58
+ | `QK` | `float32`, `float16` |
59
+ | `M` | `int32` |
60
+
61
+ ## Implementation variants
62
+
63
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
64
+
65
+ - `cross_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
66
+ - `cross_qk_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
67
+ - `cross_mask_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
68
+ - `cross_mask_qk_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
69
+ - `cross_nosg_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
70
+ - `cross_qk_nosg_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
71
+ - `cross_mask_nosg_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
72
+ - `cross_mask_qk_nosg_direct` — A single key partition writes normalized attention directly, removing partial buffers and the merge dispatch while preserving masks and diagnostic-score outputs.
73
+
74
+ ## Device requirements
75
+
76
+ Some implementation variants require `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
77
+
78
+ ## Files
79
+
80
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
81
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
82
+ - [`test.json`](build/webgpu/test.json) — correctness cases
83
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
84
+ - [`attn-flash-decode-splitk-merge.wgsl.jinja`](build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja)
85
+ - [`attn-flash-decode-splitk.wgsl.jinja`](build/webgpu/attn-flash-decode-splitk.wgsl.jinja)
86
+ - [`dmmha-present.wgsl.jinja`](build/webgpu/dmmha-present.wgsl.jinja)
87
+
88
+ ## Use with `@huggingface/kernels`
89
+
90
+ ```sh
91
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
92
+ ```
93
+
94
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
95
+
96
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
97
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
98
+
99
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
100
+
101
+ ```js
102
+ import { getKernel } from "@huggingface/kernels";
103
+
104
+ const kernel = await getKernel("webgpu-kernels/com.microsoft.DecoderMaskedMultiHeadAttention", { version: 1 });
105
+ const { outputT } = await kernel({
106
+ queryT: { data: queryTData, shape: [1, 1, 32] },
107
+ keyT: { data: keyTData, shape: [1, 2, 1, 16] },
108
+ valueT: { data: valueTData, shape: [1, 2, 1, 16] },
109
+ }, {
110
+ attrs: { num_heads: 2 },
111
+ });
112
+ ```
build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if splitQueries is not defined %}{% set splitQueries = false %}{% endif %}
2
+ {% set qSeq = 0 %}
3
+ {{ env.wgsl.resourceDeclarations }}
4
+
5
+ // Split-K flash decode, pass 2 of 2. Combines the per-split un-normalized online
6
+ // states (max, denom, weighted V sum) written by the split pass into the final
7
+ // attention output for each query token, using the online softmax merge:
8
+ // globalMax = max_s partialMax[s]
9
+ // globalDen = sum_s partialDenom[s] * exp(partialMax[s] - globalMax)
10
+ // out[d] = (sum_s partialOut[s, d] * exp(partialMax[s] - globalMax)) / globalDen
11
+ // One workgroup per (batch, head); one thread per output vec4 channel. The
12
+ // per-split (max, denom) scan is tiny (NUM_SPLITS is small), so every thread
13
+ // recomputes it locally and keeps the merge state in registers.
14
+ // Output layout: rank-4 [batch, heads, seq, headDim], matching the split pass.
15
+ const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
16
+ const Q_HEADS: u32 = {{ qNumHeads }}u;
17
+ const NUM_SPLITS: u32 = {{ numSplits }}u;
18
+ {% if splitQueries %}
19
+ const Q_SEQ: u32 = {{ qSeq }}u;
20
+ {% endif %}
21
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
22
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
23
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
24
+ // a zero final denominator according to their public semantics. Using -inf
25
+ // here changes +inf-row behavior.
26
+ const FLT_MAX: f32 = 3.4028234663852886e38;
27
+
28
+ fn is_finite_f32(value: f32) -> bool {
29
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
30
+ }
31
+
32
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
33
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
34
+ // fold to 0, which would silently turn +inf rows finite — the explicit
35
+ // equality test keeps the NaN propagation of the serial kernels.
36
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
37
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
38
+ return select(value - maxValue, 0.0, equalFiniteMax);
39
+ }
40
+
41
+ fn exp_shift(value: f32, maxValue: f32) -> f32 {
42
+ return exp(shifted_value(value, maxValue));
43
+ }
44
+ @compute @workgroup_size(HEAD_DIM_V4, 1, 1)
45
+ fn main(
46
+ @builtin(workgroup_id) wg: vec3<u32>,
47
+ @builtin(local_invocation_id) lid: vec3<u32>
48
+ ) {
49
+ {% if splitQueries %}
50
+ let queryToken = wg.x;
51
+ {% endif %}
52
+ let h = wg.y;
53
+ let b = wg.z;
54
+ let d4 = lid.x;
55
+ if (h >= Q_HEADS || d4 >= HEAD_DIM_V4{% if splitQueries %} || queryToken >= Q_SEQ{% endif %}) {
56
+ return;
57
+ }
58
+
59
+ {% if splitQueries %}
60
+ let mdBase = ((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS;
61
+ {% else %}
62
+ let mdBase = (b * Q_HEADS + h) * NUM_SPLITS;
63
+ {% endif %}
64
+ var globalMax = -FLT_MAX;
65
+ for (var s: u32 = 0u; s < NUM_SPLITS; s = s + 1u) {
66
+ globalMax = max(globalMax, partial_stats[mdBase + s].x);
67
+ }
68
+ var globalDenom = 0.0;
69
+ var outV = vec4<f32>(0.0);
70
+ for (var s: u32 = 0u; s < NUM_SPLITS; s = s + 1u) {
71
+ let stats = partial_stats[mdBase + s];
72
+ let w = exp_shift(stats.x, globalMax);
73
+ globalDenom = globalDenom + stats.y * w;
74
+ {% if splitQueries %}
75
+ let pBase = (((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS + s) * HEAD_DIM_V4;
76
+ {% else %}
77
+ let pBase = ((b * Q_HEADS + h) * NUM_SPLITS + s) * HEAD_DIM_V4;
78
+ {% endif %}
79
+ outV = outV + partial_out[pBase + d4] * w;
80
+ }
81
+
82
+ {% if splitQueries %}
83
+ let qBaseV4 = ((b * Q_HEADS + h) * Q_SEQ + queryToken) * HEAD_DIM_V4;
84
+ {% else %}
85
+ let qBaseV4 = (b * Q_HEADS + h) * HEAD_DIM_V4;
86
+ {% endif %}
87
+ // A split that covers no keys writes vec2(-FLT_MAX, 0), so when every split is empty —
88
+ // a query whose window admits nothing — globalMax stays -FLT_MAX, exp_shift(x, x) is 1,
89
+ // and globalDenom lands on exactly 0. Emit 0 rather than dividing so an all-empty
90
+ // row has a defined zero result.
91
+ var outValue = select(vec4<f32>(0.0), outV / globalDenom, globalDenom > 0.0);
92
+ output[qBaseV4 + d4] = vec4<{{ scalar }}>(outValue);
93
+ }
build/webgpu/attn-flash-decode-splitk.wgsl.jinja ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if useSubgroups is not defined %}{% set useSubgroups = true %}{% endif %}
2
+ {% if splitQueries is not defined %}{% set splitQueries = false %}{% endif %}
3
+ {% if cacheSeqlens is not defined %}{% set cacheSeqlens = false %}{% endif %}
4
+ {% if hasMask is not defined %}{% set hasMask = false %}{% endif %}
5
+ {% if hasKeyPadMask is not defined %}{% set hasKeyPadMask = false %}{% endif %}
6
+ {% if emitQk is not defined %}{% set emitQk = false %}{% endif %}
7
+ {% if cacheSeqlensIndex is not defined %}{% set cacheSeqlensIndex = "b" %}{% endif %}
8
+ {% if maskFilterValue is not defined %}{% set maskFilterValue = 0.0 %}{% endif %}
9
+ {% if qkScalar is not defined %}{% set qkScalar = "f32" %}{% endif %}
10
+ {% set qSeq = 0 %}
11
+ {% set splitKWorkgroupSize = workgroupSizeSpec if workgroupSizeSpec is defined else tunables.WORKGROUP_SIZE %}
12
+ {% if useSubgroups %}
13
+ enable subgroups;
14
+ {% endif %}
15
+ {{ env.wgsl.resourceDeclarations }}
16
+
17
+ // Split-K flash attention. Single-partition direct output skips the merge pass.
18
+ // Otherwise this is the first of two passes. This geometry
19
+ // handles decode and short-query, long-context prefill inputs.
20
+ //
21
+ // The non-split flash decode launches only `batch * numHeads` workgroups, each
22
+ // sweeping the whole KV sequence serially in WG-key tiles. This pass splits the
23
+ // KV sequence into `NUM_SPLITS` contiguous ranges and gives each range its own
24
+ // workgroup, so `batch * numHeads * NUM_SPLITS` workgroups run the tiled online
25
+ // softmax in parallel. Each workgroup emits the *un-normalized* online state for
26
+ // its range — the running (max, denom) and the softmax-weighted V sum before the
27
+ // final divide — and the merge pass combines the per-split states with the online
28
+ // rule.
29
+ // Layout: rank-4 [batch, heads, seq, headDim] for Q/K/V.
30
+ const HEAD_DIM: u32 = {{ headDim }}u;
31
+ const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
32
+ const Q_HEADS: u32 = {{ qNumHeads }}u;
33
+ const KV_HEADS: u32 = {{ kvNumHeads }}u;
34
+ const WG: u32 = {{ splitKWorkgroupSize }}u;
35
+ const NUM_SPLITS: u32 = {{ numSplits }}u;
36
+ {% if splitQueries %}
37
+ const Q_SEQ: u32 = {{ qSeq }}u;
38
+ {% endif %}
39
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
40
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
41
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
42
+ // a zero final denominator according to their public semantics. Using -inf
43
+ // here changes +inf-row behavior.
44
+ const FLT_MAX: f32 = 3.4028234663852886e38;
45
+
46
+ fn is_finite_f32(value: f32) -> bool {
47
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
48
+ }
49
+
50
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
51
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
52
+ // fold to 0, which would silently turn +inf rows finite — the explicit
53
+ // equality test keeps the NaN propagation of the serial kernels.
54
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
55
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
56
+ return select(value - maxValue, 0.0, equalFiniteMax);
57
+ }
58
+
59
+ fn exp_shift(value: f32, maxValue: f32) -> f32 {
60
+ return exp(shifted_value(value, maxValue));
61
+ }
62
+
63
+ var<workgroup> q_shared: array<vec4<f32>, HEAD_DIM_V4>;
64
+ var<workgroup> running_out: array<vec4<f32>, HEAD_DIM_V4>;
65
+ var<workgroup> probs: array<f32, WG>;
66
+ {% set coopQk = useSubgroups and headDimV4 >= 8 and not (usesF16 and headDimV4 <= 32) and (allowCooperativeQk if allowCooperativeQk is defined else true) %}
67
+ {% set jGroups = (splitKWorkgroupSize / headDimV4)|int %}
68
+ {% set jSplitV = (splitKWorkgroupSize % headDimV4 == 0) and (jGroups >= 2) %}
69
+ {% if coopQk %}
70
+ var<workgroup> sval_sh: array<f32, WG>;
71
+ {% endif %}
72
+ {% if jSplitV %}
73
+ var<workgroup> vacc_sh: array<vec4<f32>, WG>;
74
+ {% endif %}
75
+ {% set combineSubgroups = useSubgroups %}
76
+ // Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
77
+ // mNew = max(m1, m2)
78
+ // dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
79
+ // Both the subgroup and portable barrier-tree engines return the same merged
80
+ // pair to every invocation. Repeated merges require a workgroup barrier between
81
+ // calls before their shared partial storage is reused.
82
+ {% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
83
+ {% if combineSubgroups %}
84
+ // Cross-subgroup merge that assumes nothing about which invocations share a
85
+ // subgroup or how many subgroups there are: each subgroup's elected lane
86
+ // publishes the subgroup pair in the slot at its OWN invocation index and sets
87
+ // that index's bit in a workgroup bitmask; thread 0 then folds exactly the
88
+ // published slots, in ascending index order (the online (m, d) merge is not
89
+ // float-associative, so the order is fixed), and clears the mask for the next
90
+ // call as it reads it. Workgroup memory starts zeroed, so the mask needs no
91
+ // setup. Same three collectives as a single-subgroup reduce, two barriers.
92
+ var<workgroup> partialM: array<f32, WG>;
93
+ var<workgroup> partialD: array<f32, WG>;
94
+ var<workgroup> leaderMask: array<atomic<u32>, (WG + 31u) / 32u>;
95
+ var<workgroup> combinedMD: vec2<f32>;
96
+
97
+ // When the whole workgroup is one subgroup the subgroup reduce already covers
98
+ // it (no barriers, no shared state). `subgroup_size` is the size of the current
99
+ // subgroup and uniform, so the test is exact and may guard the barriers below.
100
+ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
101
+ let sgM = subgroupMax(m);
102
+ // A lane with no elements contributes d == 0 (exact identity). A +inf
103
+ // element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
104
+ // landed in d via exp(NaN); both survive the merge and are detected by the
105
+ // code after the reduction.
106
+ let sgD = subgroupAdd(d * exp_shift(m, sgM));
107
+ if (sgSize == WG) {
108
+ return vec2<f32>(sgM, sgD);
109
+ }
110
+ if (subgroupElect()) {
111
+ partialM[lidx] = sgM;
112
+ partialD[lidx] = sgD;
113
+ atomicOr(&leaderMask[lidx / 32u], 1u << (lidx % 32u));
114
+ }
115
+ workgroupBarrier();
116
+ if (lidx == 0u) {
117
+ var accM = -FLT_MAX;
118
+ var accD = 0.0;
119
+ for (var w = 0u; w < (WG + 31u) / 32u; w = w + 1u) {
120
+ var bits = atomicExchange(&leaderMask[w], 0u);
121
+ while (bits != 0u) {
122
+ let slot = w * 32u + firstTrailingBit(bits);
123
+ bits = bits & (bits - 1u);
124
+ let mNew = max(accM, partialM[slot]);
125
+ accD = accD * exp_shift(accM, mNew) + partialD[slot] * exp_shift(partialM[slot], mNew);
126
+ accM = mNew;
127
+ }
128
+ }
129
+ combinedMD = vec2<f32>(accM, accD);
130
+ }
131
+ workgroupBarrier();
132
+ return combinedMD;
133
+ }
134
+ {% else %}
135
+ {% set mdExtent = "WG" %}
136
+ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
137
+ var<workgroup> partialD: array<f32, {{ mdExtent }}>;
138
+
139
+ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
140
+ partialM[lidx] = m;
141
+ partialD[lidx] = d;
142
+ workgroupBarrier();
143
+ var stride = WG / 2u;
144
+ loop {
145
+ if (stride == 0u) {
146
+ break;
147
+ }
148
+ if (lidx < stride) {
149
+ let m1 = partialM[lidx];
150
+ let d1 = partialD[lidx];
151
+ let m2 = partialM[lidx + stride];
152
+ let d2 = partialD[lidx + stride];
153
+ let mNew = max(m1, m2);
154
+ partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
155
+ partialM[lidx] = mNew;
156
+ }
157
+ workgroupBarrier();
158
+ stride = stride / 2u;
159
+ }
160
+ let merged = vec2<f32>(partialM[0], partialD[0]);
161
+ // Trailing barrier so back-to-back calls cannot race a next call's partial
162
+ // stores against this call's reads of slot 0.
163
+ workgroupBarrier();
164
+ return merged;
165
+ }
166
+ {% endif %}
167
+
168
+ {% set ATTN_SCALE_DIM = "HEAD_DIM" %}fn scale_value() -> f32 {
169
+ if (params.scale != 0.0) { return params.scale; }
170
+ return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
171
+ }
172
+
173
+ fn load_key4(indexV4: u32) -> vec4<f32> {
174
+ return vec4<f32>(key[indexV4]);
175
+ }
176
+
177
+ fn load_value4(indexV4: u32) -> vec4<f32> {
178
+ return vec4<f32>(value[indexV4]);
179
+ }
180
+
181
+ @compute @workgroup_size(WG, 1, 1)
182
+ fn main(
183
+ @builtin(workgroup_id) wg: vec3<u32>,
184
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
185
+ @builtin(subgroup_size) sgSize: u32{% endif %}
186
+ ) {
187
+ {% if useSubgroups %}
188
+ // Subgroup tiles partition the fixed workgroup exactly. The advertised range
189
+ // is validated before dispatch; retain this uniform guard for implementations that
190
+ // choose an intermediate width at pipeline execution time.
191
+ if (sgSize == 0u || sgSize > WG || WG % sgSize != 0u) { return; }
192
+ {% endif %}
193
+ {% if splitQueries %}
194
+ let queryToken = wg.x / NUM_SPLITS;
195
+ let split = wg.x % NUM_SPLITS;
196
+ {% else %}
197
+ let split = wg.x;
198
+ {% endif %}
199
+ let h = wg.y;
200
+ let b = wg.z;
201
+ if (h >= Q_HEADS || split >= NUM_SPLITS{% if splitQueries %} || queryToken >= Q_SEQ{% endif %}) {
202
+ return;
203
+ }
204
+ let tid = lid.x;
205
+ let hKv = h / (Q_HEADS / KV_HEADS);
206
+ let cacheSeq = params.kvSeq;
207
+ {% if cacheSeqlens %}
208
+ // Buffer-sharing caches retain their capacity in the physical BNSH stride;
209
+ // seqlens_k supplies the active end independently for each batch.
210
+ let kvSeq = min(cacheSeq, u32(seqlens_k[{{ cacheSeqlensIndex }}]) + 1u);
211
+ {% else %}
212
+ let kvSeq = cacheSeq;
213
+ {% endif %}
214
+
215
+ // Query row (decode uses token zero; short-query prefill folds the token into wg.x).
216
+ {% if splitQueries %}
217
+ let qBaseV4 = ((b * Q_HEADS + h) * Q_SEQ + queryToken) * HEAD_DIM_V4;
218
+ {% else %}
219
+ let qBaseV4 = (b * Q_HEADS + h) * HEAD_DIM_V4;
220
+ {% endif %}
221
+ let kvBaseV4 = (b * KV_HEADS + hKv) * cacheSeq * HEAD_DIM_V4;
222
+ let kvTokenStrideV4 = HEAD_DIM_V4;
223
+
224
+ // Contiguous KV range owned by this split. Ceil division lets the last split
225
+ // absorb any remainder; empty ranges write identity partials and are ignored
226
+ // by the merge pass.
227
+ let keysPerSplit = (kvSeq + NUM_SPLITS - 1u) / NUM_SPLITS;
228
+ let splitStart = split * keysPerSplit;
229
+ var splitEnd = splitStart + keysPerSplit;
230
+ if (splitEnd > kvSeq) {
231
+ splitEnd = kvSeq;
232
+ }
233
+
234
+ for (var d4 = tid; d4 < HEAD_DIM_V4; d4 = d4 + WG) {
235
+ var qv = vec4<f32>(query[qBaseV4 + d4]);
236
+ q_shared[d4] = qv;
237
+ running_out[d4] = vec4<f32>(0.0);
238
+ }
239
+ workgroupBarrier();
240
+ let scale = scale_value();
241
+ var runningMax = -FLT_MAX;
242
+ var runningDenom = 0.0;
243
+
244
+ var kjBase = splitStart;
245
+ loop {
246
+ if (kjBase >= splitEnd) {
247
+ break;
248
+ }
249
+ let kj = kjBase + tid;
250
+ var keyAllowed = kj < splitEnd;
251
+ let tileCount = min(WG, splitEnd - kjBase);
252
+
253
+ var score = -FLT_MAX;
254
+ var m = -FLT_MAX;
255
+ var dPart = 0.0;
256
+ {% if coopQk %}
257
+ // Cooperative Q.K: one subgroup per key, lanes splitting HEAD_DIM_V4, then a hardware
258
+ // subgroupAdd — turns the per-thread HEAD_DIM_V4-long dependent dot chain into a few
259
+ // strided vec4 dots + one reduce. Uniform trip count keeps subgroupAdd in uniform flow.
260
+ let sgPerWg = WG / sgSize;
261
+ let qkRounds = (tileCount + sgPerWg - 1u) / sgPerWg;
262
+ let lane = tid % sgSize;
263
+ let sgInWg = tid / sgSize;
264
+ for (var rr: u32 = 0u; rr < qkRounds; rr = rr + 1u) {
265
+ let j = rr * sgPerWg + sgInWg;
266
+ var accS: f32 = 0.0;
267
+ if (j < tileCount) {
268
+ let kRowV4 = kvBaseV4 + (kjBase + j) * kvTokenStrideV4;
269
+ for (var d4: u32 = lane; d4 < HEAD_DIM_V4; d4 = d4 + sgSize) {
270
+ accS = accS + dot(q_shared[d4], load_key4(kRowV4 + d4));
271
+ }
272
+ }
273
+ let sj = subgroupAdd(accS);
274
+ if (lane == 0u && j < tileCount) {
275
+ sval_sh[j] = sj;
276
+ }
277
+ }
278
+ workgroupBarrier();
279
+ if (keyAllowed) {
280
+ score = sval_sh[tid] * scale;
281
+ m = score;
282
+ dPart = 1.0;
283
+ }
284
+ {% else %}
285
+ if (keyAllowed) {
286
+ let kRowV4 = kvBaseV4 + kj * kvTokenStrideV4;
287
+ var acc: f32 = 0.0;
288
+ for (var d4: u32 = 0u; d4 < HEAD_DIM_V4; d4 = d4 + 1u) {
289
+ acc = acc + dot(q_shared[d4], load_key4(kRowV4 + d4));
290
+ }
291
+ score = acc * scale;
292
+ m = score;
293
+ dPart = 1.0;
294
+ }
295
+ {% endif %}
296
+ {% if hasMask %}
297
+ if (keyAllowed) {
298
+ {% if splitQueries %}
299
+ let maskQuery = queryToken;
300
+ {% else %}
301
+ let maskQuery = 0u;
302
+ {% endif %}
303
+ let maskIndex = b * params.maskBatchStride + h * params.maskHeadStride + maskQuery * params.maskSeqStride + kj;
304
+ score = score + f32(attn_mask[maskIndex]);
305
+ m = score;
306
+ }
307
+ {% endif %}{% if hasKeyPadMask %}
308
+ if (keyAllowed) {
309
+ // A zero entry is padding: it ADDS the filter value rather than removing the
310
+ // key, so the row stays a (heavily skewed) average instead of collapsing.
311
+ // The mask row width is the live total length, which is exactly kvSeq.
312
+ score = score + select(f32({{ maskFilterValue }}), 0.0, key_pad_mask[b * kvSeq + kj] > 0);
313
+ m = score;
314
+ }
315
+ {% endif %}{% if emitQk %}
316
+ if (keyAllowed) {
317
+ // The pre-softmax score row, after scaling and every additive term. Splits own
318
+ // disjoint key ranges, so the whole row is written exactly once per (b, h).
319
+ qk[(b * Q_HEADS + h) * kvSeq + kj] = {{ qkScalar }}(score);
320
+ }
321
+ {% endif %}
322
+ let tile = combine_partials(m, dPart, tid{% if useSubgroups %}, sgSize{% endif %});
323
+
324
+ // Merge one key tile's online-softmax (maximum, denominator) partial into the
325
+ // running state, then store the per-key probabilities consumed by V accumulation.
326
+ let newMax = max(runningMax, tile.x);
327
+ let correction = exp_shift(runningMax, newMax);
328
+ runningDenom = runningDenom * correction + tile.y * exp_shift(tile.x, newMax);
329
+ runningMax = newMax;
330
+
331
+ var prob = 0.0;
332
+ if (keyAllowed) {
333
+ prob = exp_shift(score, newMax);
334
+ }
335
+ probs[tid] = prob;
336
+ workgroupBarrier();
337
+
338
+ {% if jSplitV %}
339
+ // j-split V accumulation: thread (jg, d4v) sums keys j == jg mod
340
+ // J_GROUPS for dim block d4v into a register, then the groups combine
341
+ // through shared memory so all lanes participate.
342
+ const J_GROUPS: u32 = {{ jGroups }}u;
343
+ let jg = tid / HEAD_DIM_V4;
344
+ let d4v = tid % HEAD_DIM_V4;
345
+ var vacc = vec4<f32>(0.0);
346
+ var jj = jg;
347
+ loop {
348
+ if (jj >= tileCount) { break; }
349
+ vacc = vacc + probs[jj] * load_value4(
350
+ kvBaseV4 + (kjBase + jj) * kvTokenStrideV4 + d4v );
351
+ jj = jj + J_GROUPS;
352
+ }
353
+ vacc_sh[tid] = vacc;
354
+ workgroupBarrier();
355
+ for (var d4: u32 = tid; d4 < HEAD_DIM_V4; d4 = d4 + WG) {
356
+ var a4 = running_out[d4] * correction;
357
+ for (var g: u32 = 0u; g < J_GROUPS; g = g + 1u) {
358
+ a4 = a4 + vacc_sh[g * HEAD_DIM_V4 + d4];
359
+ }
360
+ running_out[d4] = a4;
361
+ }
362
+ workgroupBarrier();
363
+ {% else %}
364
+ for (var d4: u32 = tid; d4 < HEAD_DIM_V4; d4 = d4 + WG) {
365
+ var vSum = vec4<f32>(0.0);
366
+ for (var i: u32 = 0u; i < tileCount; i = i + 1u) {
367
+ vSum = vSum + probs[i] * load_value4(
368
+ kvBaseV4 + (kjBase + i) * kvTokenStrideV4 + d4 );
369
+ }
370
+ running_out[d4] = running_out[d4] * correction + vSum;
371
+ }
372
+ workgroupBarrier();
373
+ {% endif %}
374
+
375
+ kjBase = kjBase + WG;
376
+ }
377
+
378
+ {% if directOutput is defined and directOutput %}
379
+ // The manifest admits this epilogue only for one partition and no post-merge
380
+ // bias or gate. Preserve the merge's finite/nonfinite weighting and zero-row
381
+ // behavior before narrowing once to the output dtype.
382
+ let finalWeight = exp_shift(runningMax, max(-FLT_MAX, runningMax));
383
+ let finalDenom = runningDenom * finalWeight;
384
+ for (var d4: u32 = tid; d4 < HEAD_DIM_V4; d4 = d4 + WG) {
385
+ let finalOut = running_out[d4] * finalWeight;
386
+ output[qBaseV4 + d4] = vec4<{{ "f16" if usesF16 else "f32" }}>(
387
+ select(vec4<f32>(0.0), finalOut / finalDenom, finalDenom > 0.0));
388
+ }
389
+ {% else %}
390
+ // Emit un-normalized partials for (b, h, split): the merge pass divides.
391
+ {% if splitQueries %}
392
+ let partialBase = (((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS + split) * HEAD_DIM_V4;
393
+ {% else %}
394
+ let partialBase = ((b * Q_HEADS + h) * NUM_SPLITS + split) * HEAD_DIM_V4;
395
+ {% endif %}
396
+ for (var d4: u32 = tid; d4 < HEAD_DIM_V4; d4 = d4 + WG) {
397
+ partial_out[partialBase + d4] = running_out[d4];
398
+ }
399
+ if (tid == 0u) {
400
+ {% if splitQueries %}
401
+ let mdBase = ((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS + split;
402
+ {% else %}
403
+ let mdBase = (b * Q_HEADS + h) * NUM_SPLITS + split;
404
+ {% endif %}
405
+ // (max, denom) travel together to the merge, so they share one buffer as an
406
+ // interleaved vec2 rather than costing two bindings. Interleaved, not two
407
+ // halves, so the index needs no region size — and the merge reads both
408
+ // fields of a split in a single load.
409
+ partial_stats[mdBase] = vec2<f32>(runningMax, runningDenom);
410
+ }
411
+ {% endif %}
412
+ }
build/webgpu/bench.json ADDED
@@ -0,0 +1,971 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tunableSpace": { "THREAD_QK_OUTPUT_WORKGROUPS": [0, 2] },
3
+ "cases": [
4
+ {
5
+ "name": "cross-whisper-tiny-b1-h6-kv1500-d64-qk-f32",
6
+ "preset": "model",
7
+ "vars": { "elemBytes": 4, "qkBytes": 4 },
8
+ "attrs": { "num_heads": 6, "output_qk": 1 },
9
+ "inputs": {
10
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 301, "scale": 0.2 },
11
+ "keyT": { "shape": [1, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 302, "scale": 0.2 },
12
+ "valueT": { "shape": [1, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 303, "scale": 0.2 }
13
+ },
14
+ "outputs": {
15
+ "outputT": { "shape": [1, 1, 384], "dtype": "float32" },
16
+ "qkT": { "shape": [1, 6, 1, 1500], "dtype": "float32" }
17
+ },
18
+ "bench": {
19
+ "metrics": [
20
+ {
21
+ "type": "bandwidth",
22
+ "value": "(1 * 6 * 1500 * 64 * 2 + 1 * 384 * 2) * args.elemBytes + 1 * 6 * 1500 * args.qkBytes"
23
+ }
24
+ ]
25
+ }
26
+ },
27
+ {
28
+ "name": "cross-whisper-large-b1-h20-kv1500-d64-qk-f16",
29
+ "preset": "model",
30
+ "vars": { "elemBytes": 2, "qkBytes": 4 },
31
+ "attrs": { "num_heads": 20, "output_qk": 1 },
32
+ "inputs": {
33
+ "queryT": { "shape": [1, 1, 1280], "dtype": "float16", "dist": "normal", "seed": 304, "scale": 0.2 },
34
+ "keyT": { "shape": [1, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 305, "scale": 0.2 },
35
+ "valueT": { "shape": [1, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 306, "scale": 0.2 }
36
+ },
37
+ "outputs": {
38
+ "outputT": { "shape": [1, 1, 1280], "dtype": "float16" },
39
+ "qkT": { "shape": [1, 20, 1, 1500], "dtype": "float32" }
40
+ },
41
+ "bench": {
42
+ "metrics": [
43
+ {
44
+ "type": "bandwidth",
45
+ "value": "(1 * 20 * 1500 * 64 * 2 + 1 * 1280 * 2) * args.elemBytes + 1 * 20 * 1500 * args.qkBytes"
46
+ }
47
+ ]
48
+ }
49
+ },
50
+ {
51
+ "name": "cross-whisper-large-b4-h20-kv1500-d64-qk-f16",
52
+ "preset": "model",
53
+ "vars": { "elemBytes": 2, "qkBytes": 4 },
54
+ "attrs": { "num_heads": 20, "output_qk": 1 },
55
+ "inputs": {
56
+ "queryT": { "shape": [4, 1, 1280], "dtype": "float16", "dist": "normal", "seed": 307, "scale": 0.2 },
57
+ "keyT": { "shape": [4, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 308, "scale": 0.2 },
58
+ "valueT": { "shape": [4, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 309, "scale": 0.2 }
59
+ },
60
+ "outputs": {
61
+ "outputT": { "shape": [4, 1, 1280], "dtype": "float16" },
62
+ "qkT": { "shape": [4, 20, 1, 1500], "dtype": "float32" }
63
+ },
64
+ "bench": {
65
+ "metrics": [
66
+ {
67
+ "type": "bandwidth",
68
+ "value": "(4 * 20 * 1500 * 64 * 2 + 4 * 1280 * 2) * args.elemBytes + 4 * 20 * 1500 * args.qkBytes"
69
+ }
70
+ ]
71
+ }
72
+ },
73
+ {
74
+ "name": "cross-whisper-large-b8-h20-kv1500-d64-f16",
75
+ "preset": "model",
76
+ "vars": { "elemBytes": 2, "qkBytes": 0 },
77
+ "attrs": { "num_heads": 20 },
78
+ "inputs": {
79
+ "queryT": { "shape": [8, 1, 1280], "dtype": "float16", "dist": "normal", "seed": 310, "scale": 0.2 },
80
+ "keyT": { "shape": [8, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 311, "scale": 0.2 },
81
+ "valueT": { "shape": [8, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 312, "scale": 0.2 }
82
+ },
83
+ "outputs": { "outputT": { "shape": [8, 1, 1280], "dtype": "float16" } },
84
+ "bench": {
85
+ "metrics": [{ "type": "bandwidth", "value": "(8 * 20 * 1500 * 64 * 2 + 8 * 1280 * 2) * args.elemBytes" }]
86
+ }
87
+ },
88
+ {
89
+ "name": "cross-whisper-large-b8-h20-kv1500-d64-f32",
90
+ "preset": "model",
91
+ "vars": { "elemBytes": 4, "qkBytes": 0 },
92
+ "attrs": { "num_heads": 20 },
93
+ "inputs": {
94
+ "queryT": { "shape": [8, 1, 1280], "dtype": "float32", "dist": "normal", "seed": 313, "scale": 0.2 },
95
+ "keyT": { "shape": [8, 20, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 314, "scale": 0.2 },
96
+ "valueT": { "shape": [8, 20, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 315, "scale": 0.2 }
97
+ },
98
+ "outputs": { "outputT": { "shape": [8, 1, 1280], "dtype": "float32" } },
99
+ "bench": {
100
+ "metrics": [{ "type": "bandwidth", "value": "(8 * 20 * 1500 * 64 * 2 + 8 * 1280 * 2) * args.elemBytes" }]
101
+ }
102
+ },
103
+ {
104
+ "name": "cross-whisper-large-b16-h20-kv1500-d64-f16",
105
+ "preset": "stress",
106
+ "vars": { "elemBytes": 2, "qkBytes": 0 },
107
+ "attrs": { "num_heads": 20 },
108
+ "inputs": {
109
+ "queryT": { "shape": [16, 1, 1280], "dtype": "float16", "dist": "normal", "seed": 316, "scale": 0.2 },
110
+ "keyT": { "shape": [16, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 317, "scale": 0.2 },
111
+ "valueT": { "shape": [16, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 318, "scale": 0.2 }
112
+ },
113
+ "outputs": { "outputT": { "shape": [16, 1, 1280], "dtype": "float16" } },
114
+ "bench": {
115
+ "metrics": [{ "type": "bandwidth", "value": "(16 * 20 * 1500 * 64 * 2 + 16 * 1280 * 2) * args.elemBytes" }]
116
+ }
117
+ },
118
+ {
119
+ "name": "cross-whisper-large-b8-h20-kv1500-d64-mask-f16",
120
+ "preset": "model",
121
+ "vars": { "elemBytes": 2, "qkBytes": 0 },
122
+ "attrs": { "num_heads": 20 },
123
+ "inputs": {
124
+ "queryT": { "shape": [8, 1, 1280], "dtype": "float16", "dist": "normal", "seed": 319, "scale": 0.2 },
125
+ "keyT": { "shape": [8, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 320, "scale": 0.2 },
126
+ "valueT": { "shape": [8, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 321, "scale": 0.2 },
127
+ "maskIndexT": { "shape": [8, 1500], "dtype": "int32", "data": { "kind": "cycle", "values": [1, 1, 1, 0] } }
128
+ },
129
+ "outputs": { "outputT": { "shape": [8, 1, 1280], "dtype": "float16" } },
130
+ "bench": {
131
+ "metrics": [{ "type": "bandwidth", "value": "(8 * 20 * 1500 * 64 * 2 + 8 * 1280 * 2) * args.elemBytes" }]
132
+ }
133
+ },
134
+ {
135
+ "name": "cross-whisper-large-b8-h20-kv1500-d64-qk-f16",
136
+ "preset": "model",
137
+ "vars": { "elemBytes": 2, "qkBytes": 4 },
138
+ "attrs": { "num_heads": 20, "output_qk": 1 },
139
+ "inputs": {
140
+ "queryT": { "shape": [8, 1, 1280], "dtype": "float16", "dist": "normal", "seed": 322, "scale": 0.2 },
141
+ "keyT": { "shape": [8, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 323, "scale": 0.2 },
142
+ "valueT": { "shape": [8, 20, 1500, 64], "dtype": "float16", "dist": "normal", "seed": 324, "scale": 0.2 }
143
+ },
144
+ "outputs": {
145
+ "outputT": { "shape": [8, 1, 1280], "dtype": "float16" },
146
+ "qkT": { "shape": [8, 20, 1, 1500], "dtype": "float32" }
147
+ },
148
+ "bench": {
149
+ "metrics": [
150
+ {
151
+ "type": "bandwidth",
152
+ "value": "(8 * 20 * 1500 * 64 * 2 + 8 * 1280 * 2) * args.elemBytes + 8 * 20 * 1500 * args.qkBytes"
153
+ }
154
+ ]
155
+ }
156
+ },
157
+ {
158
+ "name": "cross-b1-h6-kv256-d64-f32",
159
+ "preset": "smoke",
160
+ "vars": { "elemBytes": 4, "qkBytes": 0 },
161
+ "attrs": { "num_heads": 6 },
162
+ "inputs": {
163
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
164
+ "keyT": { "shape": [1, 6, 256, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
165
+ "valueT": { "shape": [1, 6, 256, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
166
+ },
167
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float32" } },
168
+ "bench": {
169
+ "metrics": [{ "type": "bandwidth", "value": "(1 * 6 * 256 * 64 * 2 + 1 * 384 * 2) * args.elemBytes" }]
170
+ }
171
+ },
172
+ {
173
+ "name": "cross-b2-h8-kv512-d128-f32",
174
+ "preset": "smoke",
175
+ "vars": { "elemBytes": 4, "qkBytes": 0 },
176
+ "attrs": { "num_heads": 8 },
177
+ "inputs": {
178
+ "queryT": { "shape": [2, 1, 1024], "dtype": "float32", "dist": "normal", "seed": 328, "scale": 0.2 },
179
+ "keyT": { "shape": [2, 8, 512, 128], "dtype": "float32", "dist": "normal", "seed": 329, "scale": 0.2 },
180
+ "valueT": { "shape": [2, 8, 512, 128], "dtype": "float32", "dist": "normal", "seed": 330, "scale": 0.2 }
181
+ },
182
+ "outputs": { "outputT": { "shape": [2, 1, 1024], "dtype": "float32" } },
183
+ "bench": {
184
+ "metrics": [{ "type": "bandwidth", "value": "(2 * 8 * 512 * 128 * 2 + 2 * 1024 * 2) * args.elemBytes" }]
185
+ }
186
+ },
187
+ {
188
+ "name": "self-whisper-base-b1-h8-cap448-d64-past1-f32",
189
+ "preset": "model",
190
+ "vars": { "elemBytes": 4 },
191
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
192
+ "inputs": {
193
+ "queryT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 331, "scale": 0.2 },
194
+ "keyT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 332, "scale": 0.2 },
195
+ "valueT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 333, "scale": 0.2 },
196
+ "pastKeyT": { "shape": [1, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 334, "scale": 0.2 },
197
+ "pastValueT": { "shape": [1, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 335, "scale": 0.2 },
198
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1] } }
199
+ },
200
+ "outputs": {
201
+ "outputT": { "shape": [1, 1, 512], "dtype": "float32" },
202
+ "presentKeyT": { "shape": [1, 8, 448, 64], "dtype": "float32" },
203
+ "presentValueT": { "shape": [1, 8, 448, 64], "dtype": "float32" }
204
+ },
205
+ "bench": {
206
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 229376 + 2 * 1024 + 2 * 1 * 512) * args.elemBytes" }]
207
+ }
208
+ },
209
+ {
210
+ "name": "self-whisper-base-b1-h8-cap448-d64-past223-f32",
211
+ "preset": "model",
212
+ "vars": { "elemBytes": 4 },
213
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
214
+ "inputs": {
215
+ "queryT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 336, "scale": 0.2 },
216
+ "keyT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 337, "scale": 0.2 },
217
+ "valueT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 338, "scale": 0.2 },
218
+ "pastKeyT": { "shape": [1, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 339, "scale": 0.2 },
219
+ "pastValueT": { "shape": [1, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 340, "scale": 0.2 },
220
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [223] } }
221
+ },
222
+ "outputs": {
223
+ "outputT": { "shape": [1, 1, 512], "dtype": "float32" },
224
+ "presentKeyT": { "shape": [1, 8, 448, 64], "dtype": "float32" },
225
+ "presentValueT": { "shape": [1, 8, 448, 64], "dtype": "float32" }
226
+ },
227
+ "bench": {
228
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 229376 + 2 * 114688 + 2 * 1 * 512) * args.elemBytes" }]
229
+ }
230
+ },
231
+ {
232
+ "name": "self-whisper-base-b1-h8-cap448-d64-past447-f32",
233
+ "preset": "model",
234
+ "vars": { "elemBytes": 4 },
235
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
236
+ "inputs": {
237
+ "queryT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 341, "scale": 0.2 },
238
+ "keyT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 342, "scale": 0.2 },
239
+ "valueT": { "shape": [1, 1, 512], "dtype": "float32", "dist": "normal", "seed": 343, "scale": 0.2 },
240
+ "pastKeyT": { "shape": [1, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 344, "scale": 0.2 },
241
+ "pastValueT": { "shape": [1, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 345, "scale": 0.2 },
242
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [446] } }
243
+ },
244
+ "outputs": {
245
+ "outputT": { "shape": [1, 1, 512], "dtype": "float32" },
246
+ "presentKeyT": { "shape": [1, 8, 448, 64], "dtype": "float32" },
247
+ "presentValueT": { "shape": [1, 8, 448, 64], "dtype": "float32" }
248
+ },
249
+ "bench": {
250
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 229376 + 2 * 228864 + 2 * 1 * 512) * args.elemBytes" }]
251
+ }
252
+ },
253
+ {
254
+ "name": "self-whisper-base-b4-h8-cap448-d64-past223-f16",
255
+ "preset": "model",
256
+ "vars": { "elemBytes": 2 },
257
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
258
+ "inputs": {
259
+ "queryT": { "shape": [4, 1, 512], "dtype": "float16", "dist": "normal", "seed": 346, "scale": 0.2 },
260
+ "keyT": { "shape": [4, 1, 512], "dtype": "float16", "dist": "normal", "seed": 347, "scale": 0.2 },
261
+ "valueT": { "shape": [4, 1, 512], "dtype": "float16", "dist": "normal", "seed": 348, "scale": 0.2 },
262
+ "pastKeyT": { "shape": [4, 8, 448, 64], "dtype": "float16", "dist": "normal", "seed": 349, "scale": 0.2 },
263
+ "pastValueT": { "shape": [4, 8, 448, 64], "dtype": "float16", "dist": "normal", "seed": 350, "scale": 0.2 },
264
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [223] } }
265
+ },
266
+ "outputs": {
267
+ "outputT": { "shape": [4, 1, 512], "dtype": "float16" },
268
+ "presentKeyT": { "shape": [4, 8, 448, 64], "dtype": "float16" },
269
+ "presentValueT": { "shape": [4, 8, 448, 64], "dtype": "float16" }
270
+ },
271
+ "bench": {
272
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 917504 + 2 * 458752 + 2 * 4 * 512) * args.elemBytes" }]
273
+ }
274
+ },
275
+ {
276
+ "name": "self-b32-h8-cap448-d64-past223-f32",
277
+ "preset": "model",
278
+ "vars": { "elemBytes": 4 },
279
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
280
+ "inputs": {
281
+ "queryT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 351, "scale": 0.2 },
282
+ "keyT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 352, "scale": 0.2 },
283
+ "valueT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 353, "scale": 0.2 },
284
+ "pastKeyT": { "shape": [32, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 354, "scale": 0.2 },
285
+ "pastValueT": { "shape": [32, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 355, "scale": 0.2 },
286
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [223] } }
287
+ },
288
+ "outputs": {
289
+ "outputT": { "shape": [32, 1, 512], "dtype": "float32" },
290
+ "presentKeyT": { "shape": [32, 8, 448, 64], "dtype": "float32" },
291
+ "presentValueT": { "shape": [32, 8, 448, 64], "dtype": "float32" }
292
+ },
293
+ "bench": {
294
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 7340032 + 2 * 3670016 + 2 * 32 * 512) * args.elemBytes" }]
295
+ }
296
+ },
297
+ {
298
+ "name": "self-b32-h8-cap448-d64-past223-f16",
299
+ "preset": "model",
300
+ "vars": { "elemBytes": 2 },
301
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
302
+ "inputs": {
303
+ "queryT": { "shape": [32, 1, 512], "dtype": "float16", "dist": "normal", "seed": 356, "scale": 0.2 },
304
+ "keyT": { "shape": [32, 1, 512], "dtype": "float16", "dist": "normal", "seed": 357, "scale": 0.2 },
305
+ "valueT": { "shape": [32, 1, 512], "dtype": "float16", "dist": "normal", "seed": 358, "scale": 0.2 },
306
+ "pastKeyT": { "shape": [32, 8, 448, 64], "dtype": "float16", "dist": "normal", "seed": 359, "scale": 0.2 },
307
+ "pastValueT": { "shape": [32, 8, 448, 64], "dtype": "float16", "dist": "normal", "seed": 360, "scale": 0.2 },
308
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [223] } }
309
+ },
310
+ "outputs": {
311
+ "outputT": { "shape": [32, 1, 512], "dtype": "float16" },
312
+ "presentKeyT": { "shape": [32, 8, 448, 64], "dtype": "float16" },
313
+ "presentValueT": { "shape": [32, 8, 448, 64], "dtype": "float16" }
314
+ },
315
+ "bench": {
316
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 7340032 + 2 * 3670016 + 2 * 32 * 512) * args.elemBytes" }]
317
+ }
318
+ },
319
+ {
320
+ "name": "self-b32-h8-cap448-d64-past447-f32",
321
+ "preset": "stress",
322
+ "vars": { "elemBytes": 4 },
323
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
324
+ "inputs": {
325
+ "queryT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 361, "scale": 0.2 },
326
+ "keyT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 362, "scale": 0.2 },
327
+ "valueT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 363, "scale": 0.2 },
328
+ "pastKeyT": { "shape": [32, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 364, "scale": 0.2 },
329
+ "pastValueT": { "shape": [32, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 365, "scale": 0.2 },
330
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [446] } }
331
+ },
332
+ "outputs": {
333
+ "outputT": { "shape": [32, 1, 512], "dtype": "float32" },
334
+ "presentKeyT": { "shape": [32, 8, 448, 64], "dtype": "float32" },
335
+ "presentValueT": { "shape": [32, 8, 448, 64], "dtype": "float32" }
336
+ },
337
+ "bench": {
338
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 7340032 + 2 * 7323648 + 2 * 32 * 512) * args.elemBytes" }]
339
+ }
340
+ },
341
+ {
342
+ "name": "self-b32-h8-cap448-d64-past223-mask-f32",
343
+ "preset": "model",
344
+ "vars": { "elemBytes": 4 },
345
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
346
+ "inputs": {
347
+ "queryT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 366, "scale": 0.2 },
348
+ "keyT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 367, "scale": 0.2 },
349
+ "valueT": { "shape": [32, 1, 512], "dtype": "float32", "dist": "normal", "seed": 368, "scale": 0.2 },
350
+ "maskIndexT": { "shape": [32, 224], "dtype": "int32", "data": { "kind": "cycle", "values": [1, 1, 1, 0] } },
351
+ "pastKeyT": { "shape": [32, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 369, "scale": 0.2 },
352
+ "pastValueT": { "shape": [32, 8, 448, 64], "dtype": "float32", "dist": "normal", "seed": 370, "scale": 0.2 },
353
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [223] } }
354
+ },
355
+ "outputs": {
356
+ "outputT": { "shape": [32, 1, 512], "dtype": "float32" },
357
+ "presentKeyT": { "shape": [32, 8, 448, 64], "dtype": "float32" },
358
+ "presentValueT": { "shape": [32, 8, 448, 64], "dtype": "float32" }
359
+ },
360
+ "bench": {
361
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 7340032 + 2 * 3670016 + 2 * 32 * 512) * args.elemBytes" }]
362
+ }
363
+ },
364
+ {
365
+ "name": "self-b16-h12-cap2048-d64-past1023-f16",
366
+ "preset": "stress",
367
+ "vars": { "elemBytes": 2 },
368
+ "attrs": { "num_heads": 12, "past_present_share_buffer": 1 },
369
+ "inputs": {
370
+ "queryT": { "shape": [16, 1, 768], "dtype": "float16", "dist": "normal", "seed": 371, "scale": 0.2 },
371
+ "keyT": { "shape": [16, 1, 768], "dtype": "float16", "dist": "normal", "seed": 372, "scale": 0.2 },
372
+ "valueT": { "shape": [16, 1, 768], "dtype": "float16", "dist": "normal", "seed": 373, "scale": 0.2 },
373
+ "pastKeyT": { "shape": [16, 12, 2048, 64], "dtype": "float16", "dist": "normal", "seed": 374, "scale": 0.2 },
374
+ "pastValueT": { "shape": [16, 12, 2048, 64], "dtype": "float16", "dist": "normal", "seed": 375, "scale": 0.2 },
375
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [1023] } }
376
+ },
377
+ "outputs": {
378
+ "outputT": { "shape": [16, 1, 768], "dtype": "float16" },
379
+ "presentKeyT": { "shape": [16, 12, 2048, 64], "dtype": "float16" },
380
+ "presentValueT": { "shape": [16, 12, 2048, 64], "dtype": "float16" }
381
+ },
382
+ "bench": {
383
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 25165824 + 2 * 12582912 + 2 * 16 * 768) * args.elemBytes" }]
384
+ }
385
+ },
386
+ {
387
+ "name": "self-b1-h4-cap64-d64-past31-f32",
388
+ "preset": "smoke",
389
+ "vars": { "elemBytes": 4 },
390
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
391
+ "inputs": {
392
+ "queryT": { "shape": [1, 1, 256], "dtype": "float32", "dist": "normal", "seed": 376, "scale": 0.2 },
393
+ "keyT": { "shape": [1, 1, 256], "dtype": "float32", "dist": "normal", "seed": 377, "scale": 0.2 },
394
+ "valueT": { "shape": [1, 1, 256], "dtype": "float32", "dist": "normal", "seed": 378, "scale": 0.2 },
395
+ "pastKeyT": { "shape": [1, 4, 64, 64], "dtype": "float32", "dist": "normal", "seed": 379, "scale": 0.2 },
396
+ "pastValueT": { "shape": [1, 4, 64, 64], "dtype": "float32", "dist": "normal", "seed": 380, "scale": 0.2 },
397
+ "pastSequenceLengthT": { "shape": [1], "dtype": "int32", "data": { "kind": "values", "values": [31] } }
398
+ },
399
+ "outputs": {
400
+ "outputT": { "shape": [1, 1, 256], "dtype": "float32" },
401
+ "presentKeyT": { "shape": [1, 4, 64, 64], "dtype": "float32" },
402
+ "presentValueT": { "shape": [1, 4, 64, 64], "dtype": "float32" }
403
+ },
404
+ "bench": {
405
+ "metrics": [{ "type": "bandwidth", "value": "(4 * 16384 + 2 * 8192 + 2 * 1 * 256) * args.elemBytes" }]
406
+ }
407
+ },
408
+ {
409
+ "name": "cross-b1-h6-kv16-d64-f32",
410
+ "preset": "smoke",
411
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 16, "headDim": 64, "maskBytes": 0 },
412
+ "attrs": { "num_heads": 6 },
413
+ "inputs": {
414
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
415
+ "keyT": { "shape": [1, 6, 16, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
416
+ "valueT": { "shape": [1, 6, 16, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
417
+ },
418
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float32" } },
419
+ "bench": {
420
+ "metrics": [
421
+ {
422
+ "type": "bandwidth",
423
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
424
+ "provenance": "logical"
425
+ }
426
+ ]
427
+ }
428
+ },
429
+ {
430
+ "name": "cross-b1-h6-kv16-d64-f16",
431
+ "preset": "smoke",
432
+ "vars": { "elemBytes": 2, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 16, "headDim": 64, "maskBytes": 0 },
433
+ "attrs": { "num_heads": 6 },
434
+ "inputs": {
435
+ "queryT": { "shape": [1, 1, 384], "dtype": "float16", "dist": "normal", "seed": 325, "scale": 0.2 },
436
+ "keyT": { "shape": [1, 6, 16, 64], "dtype": "float16", "dist": "normal", "seed": 326, "scale": 0.2 },
437
+ "valueT": { "shape": [1, 6, 16, 64], "dtype": "float16", "dist": "normal", "seed": 327, "scale": 0.2 }
438
+ },
439
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float16" } },
440
+ "bench": {
441
+ "metrics": [
442
+ {
443
+ "type": "bandwidth",
444
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
445
+ "provenance": "logical"
446
+ }
447
+ ]
448
+ }
449
+ },
450
+ {
451
+ "name": "cross-b1-h6-kv64-d64-f32",
452
+ "preset": "smoke",
453
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 64, "headDim": 64, "maskBytes": 0 },
454
+ "attrs": { "num_heads": 6 },
455
+ "inputs": {
456
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
457
+ "keyT": { "shape": [1, 6, 64, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
458
+ "valueT": { "shape": [1, 6, 64, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
459
+ },
460
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float32" } },
461
+ "bench": {
462
+ "metrics": [
463
+ {
464
+ "type": "bandwidth",
465
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
466
+ "provenance": "logical"
467
+ }
468
+ ]
469
+ }
470
+ },
471
+ {
472
+ "name": "cross-b1-h6-kv64-d64-f16",
473
+ "preset": "smoke",
474
+ "vars": { "elemBytes": 2, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 64, "headDim": 64, "maskBytes": 0 },
475
+ "attrs": { "num_heads": 6 },
476
+ "inputs": {
477
+ "queryT": { "shape": [1, 1, 384], "dtype": "float16", "dist": "normal", "seed": 325, "scale": 0.2 },
478
+ "keyT": { "shape": [1, 6, 64, 64], "dtype": "float16", "dist": "normal", "seed": 326, "scale": 0.2 },
479
+ "valueT": { "shape": [1, 6, 64, 64], "dtype": "float16", "dist": "normal", "seed": 327, "scale": 0.2 }
480
+ },
481
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float16" } },
482
+ "bench": {
483
+ "metrics": [
484
+ {
485
+ "type": "bandwidth",
486
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
487
+ "provenance": "logical"
488
+ }
489
+ ]
490
+ }
491
+ },
492
+ {
493
+ "name": "cross-b1-h6-kv255-d64-f32",
494
+ "preset": "smoke",
495
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 255, "headDim": 64, "maskBytes": 0 },
496
+ "attrs": { "num_heads": 6 },
497
+ "inputs": {
498
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
499
+ "keyT": { "shape": [1, 6, 255, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
500
+ "valueT": { "shape": [1, 6, 255, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
501
+ },
502
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float32" } },
503
+ "bench": {
504
+ "metrics": [
505
+ {
506
+ "type": "bandwidth",
507
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
508
+ "provenance": "logical"
509
+ }
510
+ ]
511
+ }
512
+ },
513
+ {
514
+ "name": "cross-b1-h6-kv255-d64-f16",
515
+ "preset": "smoke",
516
+ "vars": { "elemBytes": 2, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 255, "headDim": 64, "maskBytes": 0 },
517
+ "attrs": { "num_heads": 6 },
518
+ "inputs": {
519
+ "queryT": { "shape": [1, 1, 384], "dtype": "float16", "dist": "normal", "seed": 325, "scale": 0.2 },
520
+ "keyT": { "shape": [1, 6, 255, 64], "dtype": "float16", "dist": "normal", "seed": 326, "scale": 0.2 },
521
+ "valueT": { "shape": [1, 6, 255, 64], "dtype": "float16", "dist": "normal", "seed": 327, "scale": 0.2 }
522
+ },
523
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float16" } },
524
+ "bench": {
525
+ "metrics": [
526
+ {
527
+ "type": "bandwidth",
528
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
529
+ "provenance": "logical"
530
+ }
531
+ ]
532
+ }
533
+ },
534
+ {
535
+ "name": "cross-b1-h6-kv257-d64-f32",
536
+ "preset": "smoke",
537
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 257, "headDim": 64, "maskBytes": 0 },
538
+ "attrs": { "num_heads": 6 },
539
+ "inputs": {
540
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
541
+ "keyT": { "shape": [1, 6, 257, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
542
+ "valueT": { "shape": [1, 6, 257, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
543
+ },
544
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float32" } },
545
+ "bench": {
546
+ "metrics": [
547
+ {
548
+ "type": "bandwidth",
549
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
550
+ "provenance": "logical"
551
+ }
552
+ ]
553
+ }
554
+ },
555
+ {
556
+ "name": "cross-b1-h6-kv257-d64-f16",
557
+ "preset": "smoke",
558
+ "vars": { "elemBytes": 2, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 257, "headDim": 64, "maskBytes": 0 },
559
+ "attrs": { "num_heads": 6 },
560
+ "inputs": {
561
+ "queryT": { "shape": [1, 1, 384], "dtype": "float16", "dist": "normal", "seed": 325, "scale": 0.2 },
562
+ "keyT": { "shape": [1, 6, 257, 64], "dtype": "float16", "dist": "normal", "seed": 326, "scale": 0.2 },
563
+ "valueT": { "shape": [1, 6, 257, 64], "dtype": "float16", "dist": "normal", "seed": 327, "scale": 0.2 }
564
+ },
565
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float16" } },
566
+ "bench": {
567
+ "metrics": [
568
+ {
569
+ "type": "bandwidth",
570
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
571
+ "provenance": "logical"
572
+ }
573
+ ]
574
+ }
575
+ },
576
+ {
577
+ "name": "cross-mask-qk-b1-h6-kv64-d64-f32",
578
+ "preset": "smoke",
579
+ "vars": { "elemBytes": 4, "qkBytes": 1536, "batch": 1, "heads": 6, "keys": 64, "headDim": 64, "maskBytes": 256 },
580
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
581
+ "inputs": {
582
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
583
+ "keyT": { "shape": [1, 6, 64, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
584
+ "valueT": { "shape": [1, 6, 64, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 },
585
+ "maskIndexT": { "dtype": "int32", "shape": [1, 64], "data": { "kind": "cycle", "values": [1, 0, 1] } }
586
+ },
587
+ "outputs": {
588
+ "outputT": { "shape": [1, 1, 384], "dtype": "float32" },
589
+ "qkT": { "dtype": "float32", "shape": [1, 6, 1, 64] }
590
+ },
591
+ "bench": {
592
+ "metrics": [
593
+ {
594
+ "type": "bandwidth",
595
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
596
+ "provenance": "logical"
597
+ }
598
+ ]
599
+ }
600
+ },
601
+ {
602
+ "name": "cross-mask-qk-b1-h6-kv64-d64-f16",
603
+ "preset": "smoke",
604
+ "vars": { "elemBytes": 2, "qkBytes": 768, "batch": 1, "heads": 6, "keys": 64, "headDim": 64, "maskBytes": 256 },
605
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
606
+ "inputs": {
607
+ "queryT": { "shape": [1, 1, 384], "dtype": "float16", "dist": "normal", "seed": 325, "scale": 0.2 },
608
+ "keyT": { "shape": [1, 6, 64, 64], "dtype": "float16", "dist": "normal", "seed": 326, "scale": 0.2 },
609
+ "valueT": { "shape": [1, 6, 64, 64], "dtype": "float16", "dist": "normal", "seed": 327, "scale": 0.2 },
610
+ "maskIndexT": { "dtype": "int32", "shape": [1, 64], "data": { "kind": "cycle", "values": [1, 0, 1] } }
611
+ },
612
+ "outputs": {
613
+ "outputT": { "shape": [1, 1, 384], "dtype": "float16" },
614
+ "qkT": { "dtype": "float16", "shape": [1, 6, 1, 64] }
615
+ },
616
+ "bench": {
617
+ "metrics": [
618
+ {
619
+ "type": "bandwidth",
620
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
621
+ "provenance": "logical"
622
+ }
623
+ ]
624
+ }
625
+ },
626
+ {
627
+ "name": "cross-mask-qk-b1-h6-kv257-d64-f32",
628
+ "preset": "smoke",
629
+ "vars": { "elemBytes": 4, "qkBytes": 6168, "batch": 1, "heads": 6, "keys": 257, "headDim": 64, "maskBytes": 1028 },
630
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
631
+ "inputs": {
632
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
633
+ "keyT": { "shape": [1, 6, 257, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
634
+ "valueT": { "shape": [1, 6, 257, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 },
635
+ "maskIndexT": { "dtype": "int32", "shape": [1, 257], "data": { "kind": "cycle", "values": [1, 0, 1] } }
636
+ },
637
+ "outputs": {
638
+ "outputT": { "shape": [1, 1, 384], "dtype": "float32" },
639
+ "qkT": { "dtype": "float32", "shape": [1, 6, 1, 257] }
640
+ },
641
+ "bench": {
642
+ "metrics": [
643
+ {
644
+ "type": "bandwidth",
645
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
646
+ "provenance": "logical"
647
+ }
648
+ ]
649
+ }
650
+ },
651
+ {
652
+ "name": "cross-mask-qk-b1-h6-kv257-d64-f16",
653
+ "preset": "smoke",
654
+ "vars": { "elemBytes": 2, "qkBytes": 3084, "batch": 1, "heads": 6, "keys": 257, "headDim": 64, "maskBytes": 1028 },
655
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
656
+ "inputs": {
657
+ "queryT": { "shape": [1, 1, 384], "dtype": "float16", "dist": "normal", "seed": 325, "scale": 0.2 },
658
+ "keyT": { "shape": [1, 6, 257, 64], "dtype": "float16", "dist": "normal", "seed": 326, "scale": 0.2 },
659
+ "valueT": { "shape": [1, 6, 257, 64], "dtype": "float16", "dist": "normal", "seed": 327, "scale": 0.2 },
660
+ "maskIndexT": { "dtype": "int32", "shape": [1, 257], "data": { "kind": "cycle", "values": [1, 0, 1] } }
661
+ },
662
+ "outputs": {
663
+ "outputT": { "shape": [1, 1, 384], "dtype": "float16" },
664
+ "qkT": { "dtype": "float16", "shape": [1, 6, 1, 257] }
665
+ },
666
+ "bench": {
667
+ "metrics": [
668
+ {
669
+ "type": "bandwidth",
670
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes + args.maskBytes + args.qkBytes",
671
+ "provenance": "logical"
672
+ }
673
+ ]
674
+ }
675
+ },
676
+ {
677
+ "name": "cross-b1-h6-kv256-d32-f32",
678
+ "preset": "smoke",
679
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 256, "headDim": 32 },
680
+ "attrs": { "num_heads": 6 },
681
+ "inputs": {
682
+ "queryT": { "shape": [1, 1, 192], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
683
+ "keyT": { "shape": [1, 6, 256, 32], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
684
+ "valueT": { "shape": [1, 6, 256, 32], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
685
+ },
686
+ "outputs": { "outputT": { "shape": [1, 1, 192], "dtype": "float32" } },
687
+ "bench": {
688
+ "metrics": [
689
+ {
690
+ "type": "bandwidth",
691
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
692
+ "provenance": "logical"
693
+ }
694
+ ]
695
+ }
696
+ },
697
+ {
698
+ "name": "cross-b1-h6-kv256-d128-f32",
699
+ "preset": "smoke",
700
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 256, "headDim": 128 },
701
+ "attrs": { "num_heads": 6 },
702
+ "inputs": {
703
+ "queryT": { "shape": [1, 1, 768], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
704
+ "keyT": { "shape": [1, 6, 256, 128], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
705
+ "valueT": { "shape": [1, 6, 256, 128], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
706
+ },
707
+ "outputs": { "outputT": { "shape": [1, 1, 768], "dtype": "float32" } },
708
+ "bench": {
709
+ "metrics": [
710
+ {
711
+ "type": "bandwidth",
712
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
713
+ "provenance": "logical"
714
+ }
715
+ ]
716
+ }
717
+ },
718
+ {
719
+ "name": "cross-b1-h6-kv256-d256-f32",
720
+ "preset": "smoke",
721
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 256, "headDim": 256 },
722
+ "attrs": { "num_heads": 6 },
723
+ "inputs": {
724
+ "queryT": { "shape": [1, 1, 1536], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
725
+ "keyT": { "shape": [1, 6, 256, 256], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
726
+ "valueT": { "shape": [1, 6, 256, 256], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
727
+ },
728
+ "outputs": { "outputT": { "shape": [1, 1, 1536], "dtype": "float32" } },
729
+ "bench": {
730
+ "metrics": [
731
+ {
732
+ "type": "bandwidth",
733
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
734
+ "provenance": "logical"
735
+ }
736
+ ]
737
+ }
738
+ },
739
+ {
740
+ "name": "cross-b1-h6-kv1500-d64-f32",
741
+ "preset": "smoke",
742
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 6, "keys": 1500, "headDim": 64 },
743
+ "attrs": { "num_heads": 6 },
744
+ "inputs": {
745
+ "queryT": { "shape": [1, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
746
+ "keyT": { "shape": [1, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
747
+ "valueT": { "shape": [1, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
748
+ },
749
+ "outputs": { "outputT": { "shape": [1, 1, 384], "dtype": "float32" } },
750
+ "bench": {
751
+ "metrics": [
752
+ {
753
+ "type": "bandwidth",
754
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
755
+ "provenance": "logical"
756
+ }
757
+ ]
758
+ }
759
+ },
760
+ {
761
+ "name": "cross-b2-h6-kv1500-d64-f32",
762
+ "preset": "smoke",
763
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 2, "heads": 6, "keys": 1500, "headDim": 64 },
764
+ "attrs": { "num_heads": 6 },
765
+ "inputs": {
766
+ "queryT": { "shape": [2, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
767
+ "keyT": { "shape": [2, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
768
+ "valueT": { "shape": [2, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
769
+ },
770
+ "outputs": { "outputT": { "shape": [2, 1, 384], "dtype": "float32" } },
771
+ "bench": {
772
+ "metrics": [
773
+ {
774
+ "type": "bandwidth",
775
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
776
+ "provenance": "logical"
777
+ }
778
+ ]
779
+ }
780
+ },
781
+ {
782
+ "name": "cross-b4-h6-kv1500-d64-f32",
783
+ "preset": "smoke",
784
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 4, "heads": 6, "keys": 1500, "headDim": 64 },
785
+ "attrs": { "num_heads": 6 },
786
+ "inputs": {
787
+ "queryT": { "shape": [4, 1, 384], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
788
+ "keyT": { "shape": [4, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
789
+ "valueT": { "shape": [4, 6, 1500, 64], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
790
+ },
791
+ "outputs": { "outputT": { "shape": [4, 1, 384], "dtype": "float32" } },
792
+ "bench": {
793
+ "metrics": [
794
+ {
795
+ "type": "bandwidth",
796
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
797
+ "provenance": "logical"
798
+ }
799
+ ]
800
+ }
801
+ },
802
+ {
803
+ "name": "qk-policy-b1-h4-kv256-d512-f32",
804
+ "preset": "smoke",
805
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 4, "keys": 256, "headDim": 512 },
806
+ "attrs": { "num_heads": 4 },
807
+ "inputs": {
808
+ "queryT": { "shape": [1, 1, 2048], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
809
+ "keyT": { "shape": [1, 4, 256, 512], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
810
+ "valueT": { "shape": [1, 4, 256, 512], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
811
+ },
812
+ "outputs": { "outputT": { "shape": [1, 1, 2048], "dtype": "float32" } },
813
+ "bench": {
814
+ "metrics": [
815
+ {
816
+ "type": "bandwidth",
817
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
818
+ "provenance": "logical"
819
+ }
820
+ ]
821
+ }
822
+ },
823
+ {
824
+ "name": "qk-policy-b1-h2-kv256-d1024-f32",
825
+ "preset": "smoke",
826
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 2, "keys": 256, "headDim": 1024 },
827
+ "attrs": { "num_heads": 2 },
828
+ "inputs": {
829
+ "queryT": { "shape": [1, 1, 2048], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
830
+ "keyT": { "shape": [1, 2, 256, 1024], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
831
+ "valueT": { "shape": [1, 2, 256, 1024], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
832
+ },
833
+ "outputs": { "outputT": { "shape": [1, 1, 2048], "dtype": "float32" } },
834
+ "bench": {
835
+ "metrics": [
836
+ {
837
+ "type": "bandwidth",
838
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
839
+ "provenance": "logical"
840
+ }
841
+ ]
842
+ }
843
+ },
844
+ {
845
+ "name": "qk-policy-b1-h15-kv256-d128-f32",
846
+ "preset": "smoke",
847
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 15, "keys": 256, "headDim": 128 },
848
+ "attrs": { "num_heads": 15 },
849
+ "inputs": {
850
+ "queryT": { "shape": [1, 1, 1920], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
851
+ "keyT": { "shape": [1, 15, 256, 128], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
852
+ "valueT": { "shape": [1, 15, 256, 128], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
853
+ },
854
+ "outputs": { "outputT": { "shape": [1, 1, 1920], "dtype": "float32" } },
855
+ "bench": {
856
+ "metrics": [
857
+ {
858
+ "type": "bandwidth",
859
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
860
+ "provenance": "logical"
861
+ }
862
+ ]
863
+ }
864
+ },
865
+ {
866
+ "name": "qk-policy-b1-h16-kv256-d128-f32",
867
+ "preset": "smoke",
868
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 16, "keys": 256, "headDim": 128 },
869
+ "attrs": { "num_heads": 16 },
870
+ "inputs": {
871
+ "queryT": { "shape": [1, 1, 2048], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
872
+ "keyT": { "shape": [1, 16, 256, 128], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
873
+ "valueT": { "shape": [1, 16, 256, 128], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
874
+ },
875
+ "outputs": { "outputT": { "shape": [1, 1, 2048], "dtype": "float32" } },
876
+ "bench": {
877
+ "metrics": [
878
+ {
879
+ "type": "bandwidth",
880
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
881
+ "provenance": "logical"
882
+ }
883
+ ]
884
+ }
885
+ },
886
+ {
887
+ "name": "qk-policy-b1-h17-kv256-d128-f32",
888
+ "preset": "smoke",
889
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 17, "keys": 256, "headDim": 128 },
890
+ "attrs": { "num_heads": 17 },
891
+ "inputs": {
892
+ "queryT": { "shape": [1, 1, 2176], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
893
+ "keyT": { "shape": [1, 17, 256, 128], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
894
+ "valueT": { "shape": [1, 17, 256, 128], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
895
+ },
896
+ "outputs": { "outputT": { "shape": [1, 1, 2176], "dtype": "float32" } },
897
+ "bench": {
898
+ "metrics": [
899
+ {
900
+ "type": "bandwidth",
901
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
902
+ "provenance": "logical"
903
+ }
904
+ ]
905
+ }
906
+ },
907
+ {
908
+ "name": "qk-policy-b1-h15-kv1500-d128-f32",
909
+ "preset": "smoke",
910
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 15, "keys": 1500, "headDim": 128 },
911
+ "attrs": { "num_heads": 15 },
912
+ "inputs": {
913
+ "queryT": { "shape": [1, 1, 1920], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
914
+ "keyT": { "shape": [1, 15, 1500, 128], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
915
+ "valueT": { "shape": [1, 15, 1500, 128], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
916
+ },
917
+ "outputs": { "outputT": { "shape": [1, 1, 1920], "dtype": "float32" } },
918
+ "bench": {
919
+ "metrics": [
920
+ {
921
+ "type": "bandwidth",
922
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
923
+ "provenance": "logical"
924
+ }
925
+ ]
926
+ }
927
+ },
928
+ {
929
+ "name": "qk-policy-b1-h16-kv1500-d128-f32",
930
+ "preset": "smoke",
931
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 16, "keys": 1500, "headDim": 128 },
932
+ "attrs": { "num_heads": 16 },
933
+ "inputs": {
934
+ "queryT": { "shape": [1, 1, 2048], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
935
+ "keyT": { "shape": [1, 16, 1500, 128], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
936
+ "valueT": { "shape": [1, 16, 1500, 128], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
937
+ },
938
+ "outputs": { "outputT": { "shape": [1, 1, 2048], "dtype": "float32" } },
939
+ "bench": {
940
+ "metrics": [
941
+ {
942
+ "type": "bandwidth",
943
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
944
+ "provenance": "logical"
945
+ }
946
+ ]
947
+ }
948
+ },
949
+ {
950
+ "name": "qk-policy-b1-h17-kv1500-d128-f32",
951
+ "preset": "smoke",
952
+ "vars": { "elemBytes": 4, "qkBytes": 0, "batch": 1, "heads": 17, "keys": 1500, "headDim": 128 },
953
+ "attrs": { "num_heads": 17 },
954
+ "inputs": {
955
+ "queryT": { "shape": [1, 1, 2176], "dtype": "float32", "dist": "normal", "seed": 325, "scale": 0.2 },
956
+ "keyT": { "shape": [1, 17, 1500, 128], "dtype": "float32", "dist": "normal", "seed": 326, "scale": 0.2 },
957
+ "valueT": { "shape": [1, 17, 1500, 128], "dtype": "float32", "dist": "normal", "seed": 327, "scale": 0.2 }
958
+ },
959
+ "outputs": { "outputT": { "shape": [1, 1, 2176], "dtype": "float32" } },
960
+ "bench": {
961
+ "metrics": [
962
+ {
963
+ "type": "bandwidth",
964
+ "value": "2 * args.batch * args.heads * args.headDim * (args.keys + 1) * args.elemBytes",
965
+ "provenance": "logical"
966
+ }
967
+ ]
968
+ }
969
+ }
970
+ ]
971
+ }
build/webgpu/dmmha-present.wgsl.jinja ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
+ // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
6
+ if ({{ name }} >= {{ bound }}) {
7
+ return;
8
+ }{% endmacro %}
9
+ {{ env.wgsl.resourceDeclarations }}
10
+
11
+ // Cache maintenance for decode self attention, in two roles from one template.
12
+ //
13
+ // retain — copy the whole past buffer into the present buffer at capacity.
14
+ // The rows beyond the live length are copied too: the operator's
15
+ // contract is that present equals past everywhere except the one
16
+ // appended row, and upstream's own kernel memcpys the full buffer.
17
+ // The pass is a pure copy, so it declares `viewAlias`: it is elided
18
+ // when `past_key` and `present_key` resolve to one buffer.
19
+ // append — write this token's key and value rows at index past_sequence_length,
20
+ // which arrives as tensor data and so is read here rather than baked.
21
+ //
22
+ // Key and value share one dispatch: they have identical geometry, and one
23
+ // invocation touching both halves keeps the index arithmetic in registers.
24
+ const WG: u32 = {{ workgroupSize }}u;
25
+ {% if appendRow %}
26
+ const HEAD_SIZE_V4: u32 = {{ headSizeV4 }}u;
27
+ const NUM_HEADS: u32 = {{ numHeads }}u;
28
+ const HIDDEN_V4: u32 = NUM_HEADS * HEAD_SIZE_V4;
29
+
30
+ @compute @workgroup_size(WG)
31
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
32
+ // One invocation owns one vec4 of one (batch, head) row.
33
+ {{ flat_index_2d("WG", "index", "params.rowCountV4") }}
34
+ let d4 = index % HEAD_SIZE_V4;
35
+ let head = (index / HEAD_SIZE_V4) % NUM_HEADS;
36
+ let batch = index / HIDDEN_V4;
37
+ // past_sequence_length is a single value shared by every batch row.
38
+ let slot = u32(past_sequence_length[0]);
39
+ if (slot >= params.capacity) {
40
+ return;
41
+ }
42
+ let cacheIndex = ((batch * NUM_HEADS + head) * params.capacity + slot) * HEAD_SIZE_V4 + d4;
43
+ present_key[cacheIndex] = key[index];
44
+ present_value[cacheIndex] = value[index];
45
+ }
46
+ {% else %}
47
+ // Each lane stages ITEMS words of each cache before storing any of them, so that
48
+ // many loads are in flight before it has to wait. One word per lane leaves the
49
+ // copy latency-bound wherever the caches stay resident in cache; where the copy is
50
+ // DRAM-bound the depth is neutral. Consecutive lanes still read consecutive words;
51
+ // the stride between a lane's own items is a whole workgroup. ITEMS is bounded so
52
+ // the dispatch keeps a floor number of workgroups, since raising it divides them.
53
+ const ITEMS: u32 = {{ itemsPerLane }}u;
54
+
55
+ @compute @workgroup_size(WG)
56
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
57
+ let block = (wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u) * WG * ITEMS + lid.x;
58
+ var stagedKey: array<vec4<{{ cacheScalar }}>, ITEMS>;
59
+ var stagedValue: array<vec4<{{ cacheScalar }}>, ITEMS>;
60
+ {% for i in range(itemsPerLane) %}
61
+ {
62
+ let index = block + {{ i }}u * WG;
63
+ if (index < params.countV4) {
64
+ stagedKey[{{ i }}] = past_key[index];
65
+ stagedValue[{{ i }}] = past_value[index];
66
+ }
67
+ }
68
+ {% endfor %}
69
+ {% for i in range(itemsPerLane) %}
70
+ {
71
+ let index = block + {{ i }}u * WG;
72
+ if (index < params.countV4) {
73
+ present_key[index] = stagedKey[{{ i }}];
74
+ present_value[index] = stagedValue[{{ i }}];
75
+ }
76
+ }
77
+ {% endfor %}
78
+ }
79
+ {% endif %}
build/webgpu/manifest.json ADDED
@@ -0,0 +1,1535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "com.microsoft",
3
+ "name": "DecoderMaskedMultiHeadAttention",
4
+ "sinceVersion": 1,
5
+ "inputs": {
6
+ "queryT": { "onnx": "query", "dtype": "T", "rank": 3 },
7
+ "keyT": { "onnx": "key", "dtype": "T" },
8
+ "valueT": { "onnx": "value", "dtype": "T" },
9
+ "maskIndexT": { "onnx": "mask_index", "dtype": "M", "rank": 2, "optional": true },
10
+ "attentionBiasT": { "onnx": "attention_bias", "dtype": "T", "rank": 4, "optional": true },
11
+ "pastKeyT": { "onnx": "past_key", "dtype": "T", "rank": 4, "optional": true },
12
+ "pastValueT": { "onnx": "past_value", "dtype": "T", "rank": 4, "optional": true },
13
+ "pastSequenceLengthT": { "onnx": "past_sequence_length", "dtype": "M", "rank": 1, "optional": true }
14
+ },
15
+ "outputs": {
16
+ "outputT": {
17
+ "onnx": "output",
18
+ "dtype": "T",
19
+ "rank": 3,
20
+ "shape": "[dim(shapes.queryT, 0), 1, dim(shapes.queryT, 2)]"
21
+ },
22
+ "presentKeyT": { "onnx": "present_key", "dtype": "T", "rank": 4, "optional": true, "shape": "shapes.pastKeyT" },
23
+ "presentValueT": {
24
+ "onnx": "present_value",
25
+ "dtype": "T",
26
+ "rank": 4,
27
+ "optional": true,
28
+ "shape": "shapes.pastValueT"
29
+ },
30
+ "qkT": {
31
+ "onnx": "qk",
32
+ "dtype": "QK",
33
+ "rank": 4,
34
+ "optional": true,
35
+ "shape": "[dim(shapes.queryT, 0), attrs.num_heads, 1, dim(shapes.keyT, 2)]"
36
+ }
37
+ },
38
+ "attributes": {
39
+ "num_heads": {},
40
+ "past_present_share_buffer": {},
41
+ "scale": {},
42
+ "mask_filter_value": { "default": -10000 },
43
+ "output_qk": {}
44
+ },
45
+ "attributeConstraints": { "num_heads": { "required": true } },
46
+ "typeConstraints": { "T": ["float32", "float16"], "QK": ["float32", "float16"], "M": ["int32"] },
47
+ "tunables": {
48
+ "WORKGROUP_SIZE": { "default": 256 },
49
+ "COPY_WORKGROUP_SIZE": { "default": 256 },
50
+ "COPY_ITEMS_PER_LANE": { "default": 8 },
51
+ "COPY_MIN_WORKGROUPS": { "default": 64 },
52
+ "MAX_SPLITS": { "default": 8 },
53
+ "KEYS_PER_SPLIT": { "default": 256 },
54
+ "THREAD_QK_OUTPUT_WORKGROUPS": { "default": 2 }
55
+ },
56
+ "derive": {
57
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
58
+ "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
59
+ "batchSize": "dim(shapes.queryT, 0)",
60
+ "hiddenSize": "dim(shapes.queryT, 2)",
61
+ "numHeads": "attrs.num_heads",
62
+ "headSize": "hiddenSize / max(1, numHeads)",
63
+ "headDimV4": "headSize / 4",
64
+ "isCross": "not present.pastKeyT and not present.pastValueT",
65
+ "crossKvLen": "dim(shapes.keyT, 2) if (isCross and ranks.keyT == 4) else 0",
66
+ "cacheCapacity": "dim(shapes.pastKeyT, 2) if present.pastKeyT else 0",
67
+ "physicalKeys": "crossKvLen if isCross else cacheCapacity",
68
+ "qkRequested": "attrs.output_qk != 0 if has(attrs, \"output_qk\") else false",
69
+ "floatDtypeOk": "(tensorDtypes.queryT == \"float32\" or tensorDtypes.queryT == \"float16\") and f16Ok(tensorDtypes.queryT) and tensorDtypes.keyT == tensorDtypes.queryT and tensorDtypes.valueT == tensorDtypes.queryT and tensorDtypes.outputT == tensorDtypes.queryT",
70
+ "geometryOk": "ranks.queryT == 3 and dim(shapes.queryT, 1) == 1 and numHeads > 0 and hiddenSize > 0 and hiddenSize % numHeads == 0 and headSize > 0 and headSize % 4 == 0",
71
+ "outputShapeOk": "ranks.outputT == 3 and dim(shapes.outputT, 0) == batchSize and dim(shapes.outputT, 1) == 1 and dim(shapes.outputT, 2) == hiddenSize",
72
+ "workgroupOk": "tunables.WORKGROUP_SIZE > 0 and tunables.WORKGROUP_SIZE <= deviceWorkgroupCap and pow2ceil(tunables.WORKGROUP_SIZE) == tunables.WORKGROUP_SIZE and headDimV4 <= deviceWorkgroupCap and tunables.COPY_WORKGROUP_SIZE > 0 and tunables.COPY_WORKGROUP_SIZE <= deviceWorkgroupCap",
73
+ "workgroupStorageOk": "32 * headDimV4 + 24 * tunables.WORKGROUP_SIZE <= device.limits.maxComputeWorkgroupStorageSize",
74
+ "numSplits": "max(1, min(tunables.MAX_SPLITS, ceilDiv(physicalKeys, tunables.KEYS_PER_SPLIT)))",
75
+ "splitScratchBytes": "batchSize * numHeads * numSplits * headSize * 4",
76
+ "splitStatsBytes": "2 * batchSize * numHeads * numSplits * 4",
77
+ "scratchFits": "splitScratchBytes <= device.limits.maxStorageBufferBindingSize and splitScratchBytes <= device.limits.maxBufferSize and splitStatsBytes <= device.limits.maxStorageBufferBindingSize and splitStatsBytes <= device.limits.maxBufferSize",
78
+ "dispatchFits": "batchSize <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and numHeads <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and numSplits <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
79
+ "coreOk": "floatDtypeOk and geometryOk and outputShapeOk and workgroupOk and workgroupStorageOk and scratchFits and dispatchFits and physicalKeys > 0",
80
+ "crossOk": "coreOk and isCross and ranks.keyT == 4 and ranks.valueT == 4 and dim(shapes.keyT, 0) == batchSize and dim(shapes.keyT, 1) == numHeads and dim(shapes.keyT, 3) == headSize and sameShape(shapes.valueT, shapes.keyT) and not present.attentionBiasT and not present.pastSequenceLengthT and not present.presentKeyT and not present.presentValueT",
81
+ "cacheDtypeOk": "tensorDtypes.pastKeyT == tensorDtypes.queryT and tensorDtypes.pastValueT == tensorDtypes.queryT and tensorDtypes.presentKeyT == tensorDtypes.queryT and tensorDtypes.presentValueT == tensorDtypes.queryT and tensorDtypes.pastSequenceLengthT == \"int32\"",
82
+ "selfOk": "coreOk and not isCross and present.pastValueT and present.pastSequenceLengthT and present.presentKeyT and present.presentValueT and attrs.past_present_share_buffer == 1 and ranks.keyT == 3 and ranks.valueT == 3 and dim(shapes.keyT, 0) == batchSize and dim(shapes.keyT, 1) == 1 and dim(shapes.keyT, 2) == hiddenSize and sameShape(shapes.valueT, shapes.keyT) and ranks.pastKeyT == 4 and dim(shapes.pastKeyT, 0) == batchSize and dim(shapes.pastKeyT, 1) == numHeads and dim(shapes.pastKeyT, 3) == headSize and cacheCapacity > 1 and sameShape(shapes.pastValueT, shapes.pastKeyT) and sameShape(shapes.presentKeyT, shapes.pastKeyT) and sameShape(shapes.presentValueT, shapes.pastKeyT) and numel(shapes.pastSequenceLengthT) == 1 and cacheDtypeOk and not present.qkT and not qkRequested",
83
+ "maskShapeOk": "present.maskIndexT and ranks.maskIndexT == 2 and dim(shapes.maskIndexT, 0) == batchSize and tensorDtypes.maskIndexT == \"int32\" and (dim(shapes.maskIndexT, 1) == crossKvLen if isCross else (dim(shapes.maskIndexT, 1) >= 1 and dim(shapes.maskIndexT, 1) <= cacheCapacity))",
84
+ "attnBiasShapeOk": "present.attentionBiasT and ranks.attentionBiasT == 4 and tensorDtypes.attentionBiasT == tensorDtypes.queryT and dim(shapes.attentionBiasT, 2) == 1 and (dim(shapes.attentionBiasT, 0) == 1 or dim(shapes.attentionBiasT, 0) == batchSize) and (dim(shapes.attentionBiasT, 1) == 1 or dim(shapes.attentionBiasT, 1) == numHeads) and dim(shapes.attentionBiasT, 3) >= 1 and dim(shapes.attentionBiasT, 3) <= cacheCapacity",
85
+ "qkShapeOk": "present.qkT and qkRequested and ranks.qkT == 4 and (tensorDtypes.qkT == \"float32\" or tensorDtypes.qkT == \"float16\") and f16Ok(tensorDtypes.qkT) and dim(shapes.qkT, 0) == batchSize and dim(shapes.qkT, 1) == numHeads and dim(shapes.qkT, 2) == 1 and dim(shapes.qkT, 3) == crossKvLen",
86
+ "crossPlainOk": "crossOk and not present.maskIndexT and not present.qkT and not qkRequested",
87
+ "crossMaskOk": "crossOk and maskShapeOk and not present.qkT and not qkRequested",
88
+ "crossQkOk": "crossOk and not present.maskIndexT and qkShapeOk",
89
+ "crossMaskQkOk": "crossOk and maskShapeOk and qkShapeOk",
90
+ "selfPlainOk": "selfOk and not present.maskIndexT and not present.attentionBiasT",
91
+ "selfMaskOk": "selfOk and maskShapeOk and not present.attentionBiasT",
92
+ "selfBiasOk": "selfOk and not present.maskIndexT and attnBiasShapeOk",
93
+ "selfMaskBiasOk": "selfOk and maskShapeOk and attnBiasShapeOk",
94
+ "subgroupSplitKOk": "device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize >= 8 and device.adapterInfo.subgroupMinSize % 8 == 0 and device.adapterInfo.subgroupMaxSize % 8 == 0 and device.adapterInfo.subgroupMaxSize <= tunables.WORKGROUP_SIZE and tunables.WORKGROUP_SIZE % device.adapterInfo.subgroupMinSize == 0 and tunables.WORKGROUP_SIZE % device.adapterInfo.subgroupMaxSize == 0",
95
+ "subgroupSplitKPreferred": "subgroupSplitKOk and not narrowSubgroupRange",
96
+ "attnScalar": "\"f16\" if tensorDtypes.queryT == \"float16\" else \"f32\"",
97
+ "attnVec4": "\"vec4<f16>\" if tensorDtypes.queryT == \"float16\" else \"vec4<f32>\"",
98
+ "attnMaskElement": "\"f16\" if tensorDtypes.queryT == \"float16\" else \"f32\"",
99
+ "qkScalarType": "\"f16\" if tensorDtypes.qkT == \"float16\" else \"f32\"",
100
+ "attnBiasWidth": "dim(shapes.attentionBiasT, 3) if present.attentionBiasT else 0",
101
+ "attnBiasHeads": "dim(shapes.attentionBiasT, 1) if present.attentionBiasT else 0",
102
+ "attnBiasBatch": "dim(shapes.attentionBiasT, 0) if present.attentionBiasT else 0",
103
+ "cacheWordsV4": "numel(shapes.pastKeyT) / 4 if present.pastKeyT else 0",
104
+ "retainBlockWords": "tunables.COPY_WORKGROUP_SIZE * tunables.COPY_MIN_WORKGROUPS",
105
+ "retainOccupancyCap": "pick([[cacheWordsV4 >= 8 * retainBlockWords, 8], [cacheWordsV4 >= 4 * retainBlockWords, 4], [cacheWordsV4 >= 2 * retainBlockWords, 2]], 1)",
106
+ "retainItemsPerLane": "max(1, min(tunables.COPY_ITEMS_PER_LANE, retainOccupancyCap))"
107
+ },
108
+ "bindings": {
109
+ "query": { "arg": "queryT", "elementType": "$attnVec4" },
110
+ "key_cross": { "arg": "keyT", "name": "key", "elementType": "$attnVec4" },
111
+ "value_cross": { "arg": "valueT", "name": "value", "elementType": "$attnVec4" },
112
+ "key_cache": { "arg": "presentKeyT", "name": "key", "buffer": "read-only-storage", "elementType": "$attnVec4" },
113
+ "value_cache": {
114
+ "arg": "presentValueT",
115
+ "name": "value",
116
+ "buffer": "read-only-storage",
117
+ "elementType": "$attnVec4"
118
+ },
119
+ "key_row": { "arg": "keyT", "name": "key", "elementType": "$attnVec4" },
120
+ "value_row": { "arg": "valueT", "name": "value", "elementType": "$attnVec4" },
121
+ "past_key": { "arg": "pastKeyT", "elementType": "$attnVec4" },
122
+ "past_value": { "arg": "pastValueT", "elementType": "$attnVec4" },
123
+ "present_key": { "arg": "presentKeyT", "elementType": "$attnVec4" },
124
+ "present_value": { "arg": "presentValueT", "elementType": "$attnVec4" },
125
+ "seqlens_k": { "arg": "pastSequenceLengthT", "name": "seqlens_k", "elementType": "i32" },
126
+ "past_sequence_length": { "arg": "pastSequenceLengthT", "elementType": "i32" },
127
+ "key_pad_mask": { "arg": "maskIndexT", "name": "key_pad_mask", "elementType": "i32" },
128
+ "attn_mask": { "arg": "attentionBiasT", "name": "attn_mask", "elementType": "$attnMaskElement" },
129
+ "qk": { "arg": "qkT", "elementType": "$qkScalarType" },
130
+ "partial_out": { "scratch": "partialOut", "elementType": "vec4<f32>" },
131
+ "partial_stats": { "scratch": "partialStats", "elementType": "vec2<f32>" },
132
+ "partial_out_read": {
133
+ "scratch": "partialOut",
134
+ "name": "partial_out",
135
+ "buffer": "read-only-storage",
136
+ "elementType": "vec4<f32>"
137
+ },
138
+ "partial_stats_read": {
139
+ "scratch": "partialStats",
140
+ "name": "partial_stats",
141
+ "buffer": "read-only-storage",
142
+ "elementType": "vec2<f32>"
143
+ },
144
+ "output": { "arg": "outputT", "elementType": "$attnVec4" },
145
+ "params_cross": {
146
+ "name": "params",
147
+ "struct": [
148
+ { "name": "kvSeq", "type": "u32", "value": "crossKvLen" },
149
+ { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
150
+ ]
151
+ },
152
+ "params_self": {
153
+ "name": "params",
154
+ "struct": [
155
+ { "name": "kvSeq", "type": "u32", "value": "cacheCapacity" },
156
+ { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
157
+ ]
158
+ },
159
+ "params_self_bias": {
160
+ "name": "params",
161
+ "struct": [
162
+ { "name": "kvSeq", "type": "u32", "value": "cacheCapacity" },
163
+ { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" },
164
+ {
165
+ "name": "maskBatchStride",
166
+ "type": "u32",
167
+ "value": "0 if attnBiasBatch == 1 else attnBiasHeads * attnBiasWidth"
168
+ },
169
+ { "name": "maskHeadStride", "type": "u32", "value": "0 if attnBiasHeads == 1 else attnBiasWidth" },
170
+ { "name": "maskSeqStride", "type": "u32", "value": "0" }
171
+ ]
172
+ },
173
+ "params_retain": {
174
+ "name": "params",
175
+ "struct": [{ "name": "countV4", "type": "u32", "value": "numel(shapes.pastKeyT) / 4" }]
176
+ },
177
+ "params_append": {
178
+ "name": "params",
179
+ "struct": [
180
+ { "name": "rowCountV4", "type": "u32", "value": "batchSize * hiddenSize / 4" },
181
+ { "name": "capacity", "type": "u32", "value": "cacheCapacity" }
182
+ ]
183
+ }
184
+ },
185
+ "variants": [
186
+ {
187
+ "id": "cross_direct",
188
+ "priority": 31,
189
+ "when": ["crossPlainOk", "subgroupSplitKOk", "numSplits == 1"],
190
+ "demoteWhen": ["not subgroupSplitKPreferred"],
191
+ "requires": { "features": ["subgroups"] },
192
+ "passes": [
193
+ {
194
+ "id": "split_attention",
195
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitK",
196
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
197
+ "derive": {
198
+ "layout": "\"bhsd\"",
199
+ "splitQueries": false,
200
+ "qNumHeads": "numHeads",
201
+ "kvNumHeads": "numHeads",
202
+ "headDim": "headSize",
203
+ "headDimV4": "headDimV4",
204
+ "numSplits": "numSplits",
205
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
206
+ "hasKeyPadMask": "present.maskIndexT",
207
+ "emitQk": "present.qkT",
208
+ "useSubgroups": true,
209
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
210
+ },
211
+ "bindings": ["query", "key_cross", "value_cross", "params_cross", "output"],
212
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
213
+ }
214
+ ],
215
+ "derive": { "directOutput": true }
216
+ },
217
+ {
218
+ "id": "cross",
219
+ "priority": 30,
220
+ "when": ["crossPlainOk", "subgroupSplitKOk"],
221
+ "demoteWhen": ["not subgroupSplitKPreferred"],
222
+ "requires": { "features": ["subgroups"] },
223
+ "intermediates": [
224
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
225
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
226
+ ],
227
+ "passes": [
228
+ {
229
+ "id": "split_attention",
230
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitK",
231
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
232
+ "derive": {
233
+ "layout": "\"bhsd\"",
234
+ "splitQueries": false,
235
+ "qNumHeads": "numHeads",
236
+ "kvNumHeads": "numHeads",
237
+ "headDim": "headSize",
238
+ "headDimV4": "headDimV4",
239
+ "numSplits": "numSplits",
240
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
241
+ "hasKeyPadMask": "present.maskIndexT",
242
+ "emitQk": "present.qkT",
243
+ "useSubgroups": true,
244
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
245
+ },
246
+ "bindings": ["query", "key_cross", "value_cross", "partial_out", "partial_stats", "params_cross"],
247
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
248
+ },
249
+ {
250
+ "id": "merge",
251
+ "name": "DecoderMaskedMultiHeadAttention.CrossMerge",
252
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
253
+ "derive": {
254
+ "layout": "\"bhsd\"",
255
+ "splitQueries": false,
256
+ "scalar": "attnScalar",
257
+ "qNumHeads": "numHeads",
258
+ "headDimV4": "headDimV4",
259
+ "numSplits": "numSplits"
260
+ },
261
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
262
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
263
+ }
264
+ ]
265
+ },
266
+ {
267
+ "id": "cross_qk_direct",
268
+ "priority": 31,
269
+ "when": ["crossQkOk", "subgroupSplitKOk", "numSplits == 1"],
270
+ "demoteWhen": ["not subgroupSplitKPreferred"],
271
+ "requires": { "features": ["subgroups"] },
272
+ "passes": [
273
+ {
274
+ "id": "split_attention",
275
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQk",
276
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
277
+ "derive": {
278
+ "layout": "\"bhsd\"",
279
+ "splitQueries": false,
280
+ "qNumHeads": "numHeads",
281
+ "kvNumHeads": "numHeads",
282
+ "headDim": "headSize",
283
+ "headDimV4": "headDimV4",
284
+ "numSplits": "numSplits",
285
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
286
+ "hasKeyPadMask": "present.maskIndexT",
287
+ "emitQk": "present.qkT",
288
+ "qkScalar": "qkScalarType",
289
+ "useSubgroups": true,
290
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
291
+ },
292
+ "bindings": ["query", "key_cross", "value_cross", "qk", "params_cross", "output"],
293
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
294
+ }
295
+ ],
296
+ "derive": { "directOutput": true }
297
+ },
298
+ {
299
+ "id": "cross_qk",
300
+ "priority": 30,
301
+ "when": ["crossQkOk", "subgroupSplitKOk"],
302
+ "demoteWhen": ["not subgroupSplitKPreferred"],
303
+ "requires": { "features": ["subgroups"] },
304
+ "intermediates": [
305
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
306
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
307
+ ],
308
+ "passes": [
309
+ {
310
+ "id": "split_attention",
311
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQk",
312
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
313
+ "derive": {
314
+ "layout": "\"bhsd\"",
315
+ "splitQueries": false,
316
+ "qNumHeads": "numHeads",
317
+ "kvNumHeads": "numHeads",
318
+ "headDim": "headSize",
319
+ "headDimV4": "headDimV4",
320
+ "numSplits": "numSplits",
321
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
322
+ "hasKeyPadMask": "present.maskIndexT",
323
+ "emitQk": "present.qkT",
324
+ "qkScalar": "qkScalarType",
325
+ "useSubgroups": true,
326
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
327
+ },
328
+ "bindings": ["query", "key_cross", "value_cross", "qk", "partial_out", "partial_stats", "params_cross"],
329
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
330
+ },
331
+ {
332
+ "id": "merge",
333
+ "name": "DecoderMaskedMultiHeadAttention.CrossMerge",
334
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
335
+ "derive": {
336
+ "layout": "\"bhsd\"",
337
+ "splitQueries": false,
338
+ "scalar": "attnScalar",
339
+ "qNumHeads": "numHeads",
340
+ "headDimV4": "headDimV4",
341
+ "numSplits": "numSplits"
342
+ },
343
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
344
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
345
+ }
346
+ ]
347
+ },
348
+ {
349
+ "id": "cross_mask_direct",
350
+ "priority": 31,
351
+ "when": ["crossMaskOk", "subgroupSplitKOk", "numSplits == 1"],
352
+ "demoteWhen": ["not subgroupSplitKPreferred"],
353
+ "requires": { "features": ["subgroups"] },
354
+ "passes": [
355
+ {
356
+ "id": "split_attention",
357
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitK",
358
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
359
+ "derive": {
360
+ "layout": "\"bhsd\"",
361
+ "splitQueries": false,
362
+ "qNumHeads": "numHeads",
363
+ "kvNumHeads": "numHeads",
364
+ "headDim": "headSize",
365
+ "headDimV4": "headDimV4",
366
+ "numSplits": "numSplits",
367
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
368
+ "hasKeyPadMask": "present.maskIndexT",
369
+ "maskFilterValue": "attrs.mask_filter_value",
370
+ "emitQk": "present.qkT",
371
+ "useSubgroups": true,
372
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
373
+ },
374
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "params_cross", "output"],
375
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
376
+ }
377
+ ],
378
+ "derive": { "directOutput": true }
379
+ },
380
+ {
381
+ "id": "cross_mask",
382
+ "priority": 30,
383
+ "when": ["crossMaskOk", "subgroupSplitKOk"],
384
+ "demoteWhen": ["not subgroupSplitKPreferred"],
385
+ "requires": { "features": ["subgroups"] },
386
+ "intermediates": [
387
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
388
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
389
+ ],
390
+ "passes": [
391
+ {
392
+ "id": "split_attention",
393
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitK",
394
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
395
+ "derive": {
396
+ "layout": "\"bhsd\"",
397
+ "splitQueries": false,
398
+ "qNumHeads": "numHeads",
399
+ "kvNumHeads": "numHeads",
400
+ "headDim": "headSize",
401
+ "headDimV4": "headDimV4",
402
+ "numSplits": "numSplits",
403
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
404
+ "hasKeyPadMask": "present.maskIndexT",
405
+ "maskFilterValue": "attrs.mask_filter_value",
406
+ "emitQk": "present.qkT",
407
+ "useSubgroups": true,
408
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
409
+ },
410
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "partial_out", "partial_stats", "params_cross"],
411
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
412
+ },
413
+ {
414
+ "id": "merge",
415
+ "name": "DecoderMaskedMultiHeadAttention.CrossMerge",
416
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
417
+ "derive": {
418
+ "layout": "\"bhsd\"",
419
+ "splitQueries": false,
420
+ "scalar": "attnScalar",
421
+ "qNumHeads": "numHeads",
422
+ "headDimV4": "headDimV4",
423
+ "numSplits": "numSplits"
424
+ },
425
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
426
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
427
+ }
428
+ ]
429
+ },
430
+ {
431
+ "id": "cross_mask_qk_direct",
432
+ "priority": 31,
433
+ "when": ["crossMaskQkOk", "subgroupSplitKOk", "numSplits == 1"],
434
+ "demoteWhen": ["not subgroupSplitKPreferred"],
435
+ "requires": { "features": ["subgroups"] },
436
+ "passes": [
437
+ {
438
+ "id": "split_attention",
439
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQk",
440
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
441
+ "derive": {
442
+ "layout": "\"bhsd\"",
443
+ "splitQueries": false,
444
+ "qNumHeads": "numHeads",
445
+ "kvNumHeads": "numHeads",
446
+ "headDim": "headSize",
447
+ "headDimV4": "headDimV4",
448
+ "numSplits": "numSplits",
449
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
450
+ "hasKeyPadMask": "present.maskIndexT",
451
+ "maskFilterValue": "attrs.mask_filter_value",
452
+ "emitQk": "present.qkT",
453
+ "qkScalar": "qkScalarType",
454
+ "useSubgroups": true,
455
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
456
+ },
457
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "qk", "params_cross", "output"],
458
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
459
+ }
460
+ ],
461
+ "derive": { "directOutput": true }
462
+ },
463
+ {
464
+ "id": "cross_mask_qk",
465
+ "priority": 30,
466
+ "when": ["crossMaskQkOk", "subgroupSplitKOk"],
467
+ "demoteWhen": ["not subgroupSplitKPreferred"],
468
+ "requires": { "features": ["subgroups"] },
469
+ "intermediates": [
470
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
471
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
472
+ ],
473
+ "passes": [
474
+ {
475
+ "id": "split_attention",
476
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQk",
477
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
478
+ "derive": {
479
+ "layout": "\"bhsd\"",
480
+ "splitQueries": false,
481
+ "qNumHeads": "numHeads",
482
+ "kvNumHeads": "numHeads",
483
+ "headDim": "headSize",
484
+ "headDimV4": "headDimV4",
485
+ "numSplits": "numSplits",
486
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
487
+ "hasKeyPadMask": "present.maskIndexT",
488
+ "maskFilterValue": "attrs.mask_filter_value",
489
+ "emitQk": "present.qkT",
490
+ "qkScalar": "qkScalarType",
491
+ "useSubgroups": true,
492
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
493
+ },
494
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "qk", "partial_out", "partial_stats", "params_cross"],
495
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
496
+ },
497
+ {
498
+ "id": "merge",
499
+ "name": "DecoderMaskedMultiHeadAttention.CrossMerge",
500
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
501
+ "derive": {
502
+ "layout": "\"bhsd\"",
503
+ "splitQueries": false,
504
+ "scalar": "attnScalar",
505
+ "qNumHeads": "numHeads",
506
+ "headDimV4": "headDimV4",
507
+ "numSplits": "numSplits"
508
+ },
509
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
510
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
511
+ }
512
+ ]
513
+ },
514
+ {
515
+ "id": "cross_nosg_direct",
516
+ "priority": 21,
517
+ "when": ["crossPlainOk", "numSplits == 1"],
518
+ "requires": {},
519
+ "passes": [
520
+ {
521
+ "id": "split_attention",
522
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKNoSg",
523
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
524
+ "derive": {
525
+ "layout": "\"bhsd\"",
526
+ "splitQueries": false,
527
+ "qNumHeads": "numHeads",
528
+ "kvNumHeads": "numHeads",
529
+ "headDim": "headSize",
530
+ "headDimV4": "headDimV4",
531
+ "numSplits": "numSplits",
532
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
533
+ "hasKeyPadMask": "present.maskIndexT",
534
+ "emitQk": "present.qkT",
535
+ "useSubgroups": false,
536
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
537
+ },
538
+ "bindings": ["query", "key_cross", "value_cross", "params_cross", "output"],
539
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
540
+ }
541
+ ],
542
+ "derive": { "directOutput": true }
543
+ },
544
+ {
545
+ "id": "cross_nosg",
546
+ "priority": 20,
547
+ "when": ["crossPlainOk"],
548
+ "requires": {},
549
+ "intermediates": [
550
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
551
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
552
+ ],
553
+ "passes": [
554
+ {
555
+ "id": "split_attention",
556
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKNoSg",
557
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
558
+ "derive": {
559
+ "layout": "\"bhsd\"",
560
+ "splitQueries": false,
561
+ "qNumHeads": "numHeads",
562
+ "kvNumHeads": "numHeads",
563
+ "headDim": "headSize",
564
+ "headDimV4": "headDimV4",
565
+ "numSplits": "numSplits",
566
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
567
+ "hasKeyPadMask": "present.maskIndexT",
568
+ "emitQk": "present.qkT",
569
+ "useSubgroups": false,
570
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
571
+ },
572
+ "bindings": ["query", "key_cross", "value_cross", "partial_out", "partial_stats", "params_cross"],
573
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
574
+ },
575
+ {
576
+ "id": "merge",
577
+ "name": "DecoderMaskedMultiHeadAttention.CrossMergeNoSg",
578
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
579
+ "derive": {
580
+ "layout": "\"bhsd\"",
581
+ "splitQueries": false,
582
+ "scalar": "attnScalar",
583
+ "qNumHeads": "numHeads",
584
+ "headDimV4": "headDimV4",
585
+ "numSplits": "numSplits"
586
+ },
587
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
588
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
589
+ }
590
+ ]
591
+ },
592
+ {
593
+ "id": "cross_qk_nosg_direct",
594
+ "priority": 21,
595
+ "when": ["crossQkOk", "numSplits == 1"],
596
+ "requires": {},
597
+ "passes": [
598
+ {
599
+ "id": "split_attention",
600
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQkNoSg",
601
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
602
+ "derive": {
603
+ "layout": "\"bhsd\"",
604
+ "splitQueries": false,
605
+ "qNumHeads": "numHeads",
606
+ "kvNumHeads": "numHeads",
607
+ "headDim": "headSize",
608
+ "headDimV4": "headDimV4",
609
+ "numSplits": "numSplits",
610
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
611
+ "hasKeyPadMask": "present.maskIndexT",
612
+ "emitQk": "present.qkT",
613
+ "qkScalar": "qkScalarType",
614
+ "useSubgroups": false,
615
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
616
+ },
617
+ "bindings": ["query", "key_cross", "value_cross", "qk", "params_cross", "output"],
618
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
619
+ }
620
+ ],
621
+ "derive": { "directOutput": true }
622
+ },
623
+ {
624
+ "id": "cross_qk_nosg",
625
+ "priority": 20,
626
+ "when": ["crossQkOk"],
627
+ "requires": {},
628
+ "intermediates": [
629
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
630
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
631
+ ],
632
+ "passes": [
633
+ {
634
+ "id": "split_attention",
635
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQkNoSg",
636
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
637
+ "derive": {
638
+ "layout": "\"bhsd\"",
639
+ "splitQueries": false,
640
+ "qNumHeads": "numHeads",
641
+ "kvNumHeads": "numHeads",
642
+ "headDim": "headSize",
643
+ "headDimV4": "headDimV4",
644
+ "numSplits": "numSplits",
645
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
646
+ "hasKeyPadMask": "present.maskIndexT",
647
+ "emitQk": "present.qkT",
648
+ "qkScalar": "qkScalarType",
649
+ "useSubgroups": false,
650
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
651
+ },
652
+ "bindings": ["query", "key_cross", "value_cross", "qk", "partial_out", "partial_stats", "params_cross"],
653
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
654
+ },
655
+ {
656
+ "id": "merge",
657
+ "name": "DecoderMaskedMultiHeadAttention.CrossMergeNoSg",
658
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
659
+ "derive": {
660
+ "layout": "\"bhsd\"",
661
+ "splitQueries": false,
662
+ "scalar": "attnScalar",
663
+ "qNumHeads": "numHeads",
664
+ "headDimV4": "headDimV4",
665
+ "numSplits": "numSplits"
666
+ },
667
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
668
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
669
+ }
670
+ ]
671
+ },
672
+ {
673
+ "id": "cross_mask_nosg_direct",
674
+ "priority": 21,
675
+ "when": ["crossMaskOk", "numSplits == 1"],
676
+ "requires": {},
677
+ "passes": [
678
+ {
679
+ "id": "split_attention",
680
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKNoSg",
681
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
682
+ "derive": {
683
+ "layout": "\"bhsd\"",
684
+ "splitQueries": false,
685
+ "qNumHeads": "numHeads",
686
+ "kvNumHeads": "numHeads",
687
+ "headDim": "headSize",
688
+ "headDimV4": "headDimV4",
689
+ "numSplits": "numSplits",
690
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
691
+ "hasKeyPadMask": "present.maskIndexT",
692
+ "maskFilterValue": "attrs.mask_filter_value",
693
+ "emitQk": "present.qkT",
694
+ "useSubgroups": false,
695
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
696
+ },
697
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "params_cross", "output"],
698
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
699
+ }
700
+ ],
701
+ "derive": { "directOutput": true }
702
+ },
703
+ {
704
+ "id": "cross_mask_nosg",
705
+ "priority": 20,
706
+ "when": ["crossMaskOk"],
707
+ "requires": {},
708
+ "intermediates": [
709
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
710
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
711
+ ],
712
+ "passes": [
713
+ {
714
+ "id": "split_attention",
715
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKNoSg",
716
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
717
+ "derive": {
718
+ "layout": "\"bhsd\"",
719
+ "splitQueries": false,
720
+ "qNumHeads": "numHeads",
721
+ "kvNumHeads": "numHeads",
722
+ "headDim": "headSize",
723
+ "headDimV4": "headDimV4",
724
+ "numSplits": "numSplits",
725
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
726
+ "hasKeyPadMask": "present.maskIndexT",
727
+ "maskFilterValue": "attrs.mask_filter_value",
728
+ "emitQk": "present.qkT",
729
+ "useSubgroups": false,
730
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
731
+ },
732
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "partial_out", "partial_stats", "params_cross"],
733
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
734
+ },
735
+ {
736
+ "id": "merge",
737
+ "name": "DecoderMaskedMultiHeadAttention.CrossMergeNoSg",
738
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
739
+ "derive": {
740
+ "layout": "\"bhsd\"",
741
+ "splitQueries": false,
742
+ "scalar": "attnScalar",
743
+ "qNumHeads": "numHeads",
744
+ "headDimV4": "headDimV4",
745
+ "numSplits": "numSplits"
746
+ },
747
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
748
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
749
+ }
750
+ ]
751
+ },
752
+ {
753
+ "id": "cross_mask_qk_nosg_direct",
754
+ "priority": 21,
755
+ "when": ["crossMaskQkOk", "numSplits == 1"],
756
+ "requires": {},
757
+ "passes": [
758
+ {
759
+ "id": "split_attention",
760
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQkNoSg",
761
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
762
+ "derive": {
763
+ "layout": "\"bhsd\"",
764
+ "splitQueries": false,
765
+ "qNumHeads": "numHeads",
766
+ "kvNumHeads": "numHeads",
767
+ "headDim": "headSize",
768
+ "headDimV4": "headDimV4",
769
+ "numSplits": "numSplits",
770
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
771
+ "hasKeyPadMask": "present.maskIndexT",
772
+ "maskFilterValue": "attrs.mask_filter_value",
773
+ "emitQk": "present.qkT",
774
+ "qkScalar": "qkScalarType",
775
+ "useSubgroups": false,
776
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
777
+ },
778
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "qk", "params_cross", "output"],
779
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
780
+ }
781
+ ],
782
+ "derive": { "directOutput": true }
783
+ },
784
+ {
785
+ "id": "cross_mask_qk_nosg",
786
+ "priority": 20,
787
+ "when": ["crossMaskQkOk"],
788
+ "requires": {},
789
+ "intermediates": [
790
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
791
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
792
+ ],
793
+ "passes": [
794
+ {
795
+ "id": "split_attention",
796
+ "name": "DecoderMaskedMultiHeadAttention.CrossSplitKQkNoSg",
797
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
798
+ "derive": {
799
+ "layout": "\"bhsd\"",
800
+ "splitQueries": false,
801
+ "qNumHeads": "numHeads",
802
+ "kvNumHeads": "numHeads",
803
+ "headDim": "headSize",
804
+ "headDimV4": "headDimV4",
805
+ "numSplits": "numSplits",
806
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
807
+ "hasKeyPadMask": "present.maskIndexT",
808
+ "maskFilterValue": "attrs.mask_filter_value",
809
+ "emitQk": "present.qkT",
810
+ "qkScalar": "qkScalarType",
811
+ "useSubgroups": false,
812
+ "allowCooperativeQk": "not useSubgroups or usesF16 or headDimV4 < 8 or batchSize * hiddenSize / 4 > tunables.THREAD_QK_OUTPUT_WORKGROUPS * tunables.WORKGROUP_SIZE"
813
+ },
814
+ "bindings": ["query", "key_cross", "value_cross", "key_pad_mask", "qk", "partial_out", "partial_stats", "params_cross"],
815
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
816
+ },
817
+ {
818
+ "id": "merge",
819
+ "name": "DecoderMaskedMultiHeadAttention.CrossMergeNoSg",
820
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
821
+ "derive": {
822
+ "layout": "\"bhsd\"",
823
+ "splitQueries": false,
824
+ "scalar": "attnScalar",
825
+ "qNumHeads": "numHeads",
826
+ "headDimV4": "headDimV4",
827
+ "numSplits": "numSplits"
828
+ },
829
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
830
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
831
+ }
832
+ ]
833
+ },
834
+ {
835
+ "id": "self",
836
+ "priority": 30,
837
+ "when": ["selfPlainOk", "subgroupSplitKOk"],
838
+ "demoteWhen": ["not subgroupSplitKPreferred"],
839
+ "requires": { "features": ["subgroups"] },
840
+ "intermediates": [
841
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
842
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
843
+ ],
844
+ "passes": [
845
+ {
846
+ "id": "present_retain",
847
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
848
+ "shader": "dmmha-present.wgsl.jinja",
849
+ "derive": {
850
+ "appendRow": false,
851
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
852
+ "itemsPerLane": "retainItemsPerLane",
853
+ "cacheScalar": "attnScalar"
854
+ },
855
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
856
+ "dispatch": {
857
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
858
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
859
+ "z": 1
860
+ },
861
+ "viewAlias": [
862
+ { "input": "past_key", "output": "present_key" },
863
+ { "input": "past_value", "output": "present_value" }
864
+ ]
865
+ },
866
+ {
867
+ "id": "present_append",
868
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
869
+ "shader": "dmmha-present.wgsl.jinja",
870
+ "derive": {
871
+ "appendRow": true,
872
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
873
+ "headSizeV4": "headDimV4",
874
+ "numHeads": "numHeads"
875
+ },
876
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
877
+ "dispatch": {
878
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
879
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
880
+ "z": 1
881
+ }
882
+ },
883
+ {
884
+ "id": "split_attention",
885
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitK",
886
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
887
+ "derive": {
888
+ "layout": "\"bhsd\"",
889
+ "splitQueries": false,
890
+ "qNumHeads": "numHeads",
891
+ "kvNumHeads": "numHeads",
892
+ "headDim": "headSize",
893
+ "headDimV4": "headDimV4",
894
+ "numSplits": "numSplits",
895
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
896
+ "cacheSeqlens": true,
897
+ "cacheSeqlensIndex": "\"0u\"",
898
+ "hasKeyPadMask": "present.maskIndexT",
899
+ "hasMask": "present.attentionBiasT"
900
+ },
901
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "partial_out", "partial_stats", "params_self"],
902
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
903
+ },
904
+ {
905
+ "id": "merge",
906
+ "name": "DecoderMaskedMultiHeadAttention.SelfMerge",
907
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
908
+ "derive": {
909
+ "layout": "\"bhsd\"",
910
+ "splitQueries": false,
911
+ "scalar": "attnScalar",
912
+ "qNumHeads": "numHeads",
913
+ "headDimV4": "headDimV4",
914
+ "numSplits": "numSplits"
915
+ },
916
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
917
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
918
+ }
919
+ ]
920
+ },
921
+ {
922
+ "id": "self_bias",
923
+ "priority": 30,
924
+ "when": ["selfBiasOk", "subgroupSplitKOk"],
925
+ "demoteWhen": ["not subgroupSplitKPreferred"],
926
+ "requires": { "features": ["subgroups"] },
927
+ "intermediates": [
928
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
929
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
930
+ ],
931
+ "passes": [
932
+ {
933
+ "id": "present_retain",
934
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
935
+ "shader": "dmmha-present.wgsl.jinja",
936
+ "derive": {
937
+ "appendRow": false,
938
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
939
+ "itemsPerLane": "retainItemsPerLane",
940
+ "cacheScalar": "attnScalar"
941
+ },
942
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
943
+ "dispatch": {
944
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
945
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
946
+ "z": 1
947
+ },
948
+ "viewAlias": [
949
+ { "input": "past_key", "output": "present_key" },
950
+ { "input": "past_value", "output": "present_value" }
951
+ ]
952
+ },
953
+ {
954
+ "id": "present_append",
955
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
956
+ "shader": "dmmha-present.wgsl.jinja",
957
+ "derive": {
958
+ "appendRow": true,
959
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
960
+ "headSizeV4": "headDimV4",
961
+ "numHeads": "numHeads"
962
+ },
963
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
964
+ "dispatch": {
965
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
966
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
967
+ "z": 1
968
+ }
969
+ },
970
+ {
971
+ "id": "split_attention",
972
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitK",
973
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
974
+ "derive": {
975
+ "layout": "\"bhsd\"",
976
+ "splitQueries": false,
977
+ "qNumHeads": "numHeads",
978
+ "kvNumHeads": "numHeads",
979
+ "headDim": "headSize",
980
+ "headDimV4": "headDimV4",
981
+ "numSplits": "numSplits",
982
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
983
+ "cacheSeqlens": true,
984
+ "cacheSeqlensIndex": "\"0u\"",
985
+ "hasKeyPadMask": "present.maskIndexT",
986
+ "hasMask": "present.attentionBiasT"
987
+ },
988
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "attn_mask", "partial_out", "partial_stats", "params_self_bias"],
989
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
990
+ },
991
+ {
992
+ "id": "merge",
993
+ "name": "DecoderMaskedMultiHeadAttention.SelfMerge",
994
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
995
+ "derive": {
996
+ "layout": "\"bhsd\"",
997
+ "splitQueries": false,
998
+ "scalar": "attnScalar",
999
+ "qNumHeads": "numHeads",
1000
+ "headDimV4": "headDimV4",
1001
+ "numSplits": "numSplits"
1002
+ },
1003
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
1004
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1005
+ }
1006
+ ]
1007
+ },
1008
+ {
1009
+ "id": "self_mask",
1010
+ "priority": 30,
1011
+ "when": ["selfMaskOk", "subgroupSplitKOk"],
1012
+ "demoteWhen": ["not subgroupSplitKPreferred"],
1013
+ "requires": { "features": ["subgroups"] },
1014
+ "intermediates": [
1015
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
1016
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
1017
+ ],
1018
+ "passes": [
1019
+ {
1020
+ "id": "present_retain",
1021
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
1022
+ "shader": "dmmha-present.wgsl.jinja",
1023
+ "derive": {
1024
+ "appendRow": false,
1025
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1026
+ "itemsPerLane": "retainItemsPerLane",
1027
+ "cacheScalar": "attnScalar"
1028
+ },
1029
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
1030
+ "dispatch": {
1031
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1032
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1033
+ "z": 1
1034
+ },
1035
+ "viewAlias": [
1036
+ { "input": "past_key", "output": "present_key" },
1037
+ { "input": "past_value", "output": "present_value" }
1038
+ ]
1039
+ },
1040
+ {
1041
+ "id": "present_append",
1042
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
1043
+ "shader": "dmmha-present.wgsl.jinja",
1044
+ "derive": {
1045
+ "appendRow": true,
1046
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1047
+ "headSizeV4": "headDimV4",
1048
+ "numHeads": "numHeads"
1049
+ },
1050
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
1051
+ "dispatch": {
1052
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1053
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1054
+ "z": 1
1055
+ }
1056
+ },
1057
+ {
1058
+ "id": "split_attention",
1059
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitK",
1060
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
1061
+ "derive": {
1062
+ "layout": "\"bhsd\"",
1063
+ "splitQueries": false,
1064
+ "qNumHeads": "numHeads",
1065
+ "kvNumHeads": "numHeads",
1066
+ "headDim": "headSize",
1067
+ "headDimV4": "headDimV4",
1068
+ "numSplits": "numSplits",
1069
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
1070
+ "cacheSeqlens": true,
1071
+ "cacheSeqlensIndex": "\"0u\"",
1072
+ "hasKeyPadMask": "present.maskIndexT",
1073
+ "maskFilterValue": "attrs.mask_filter_value",
1074
+ "hasMask": "present.attentionBiasT"
1075
+ },
1076
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "key_pad_mask", "partial_out", "partial_stats", "params_self"],
1077
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1078
+ },
1079
+ {
1080
+ "id": "merge",
1081
+ "name": "DecoderMaskedMultiHeadAttention.SelfMerge",
1082
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
1083
+ "derive": {
1084
+ "layout": "\"bhsd\"",
1085
+ "splitQueries": false,
1086
+ "scalar": "attnScalar",
1087
+ "qNumHeads": "numHeads",
1088
+ "headDimV4": "headDimV4",
1089
+ "numSplits": "numSplits"
1090
+ },
1091
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
1092
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1093
+ }
1094
+ ]
1095
+ },
1096
+ {
1097
+ "id": "self_mask_bias",
1098
+ "priority": 30,
1099
+ "when": ["selfMaskBiasOk", "subgroupSplitKOk"],
1100
+ "demoteWhen": ["not subgroupSplitKPreferred"],
1101
+ "requires": { "features": ["subgroups"] },
1102
+ "intermediates": [
1103
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
1104
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
1105
+ ],
1106
+ "passes": [
1107
+ {
1108
+ "id": "present_retain",
1109
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
1110
+ "shader": "dmmha-present.wgsl.jinja",
1111
+ "derive": {
1112
+ "appendRow": false,
1113
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1114
+ "itemsPerLane": "retainItemsPerLane",
1115
+ "cacheScalar": "attnScalar"
1116
+ },
1117
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
1118
+ "dispatch": {
1119
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1120
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1121
+ "z": 1
1122
+ },
1123
+ "viewAlias": [
1124
+ { "input": "past_key", "output": "present_key" },
1125
+ { "input": "past_value", "output": "present_value" }
1126
+ ]
1127
+ },
1128
+ {
1129
+ "id": "present_append",
1130
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
1131
+ "shader": "dmmha-present.wgsl.jinja",
1132
+ "derive": {
1133
+ "appendRow": true,
1134
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1135
+ "headSizeV4": "headDimV4",
1136
+ "numHeads": "numHeads"
1137
+ },
1138
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
1139
+ "dispatch": {
1140
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1141
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1142
+ "z": 1
1143
+ }
1144
+ },
1145
+ {
1146
+ "id": "split_attention",
1147
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitK",
1148
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
1149
+ "derive": {
1150
+ "layout": "\"bhsd\"",
1151
+ "splitQueries": false,
1152
+ "qNumHeads": "numHeads",
1153
+ "kvNumHeads": "numHeads",
1154
+ "headDim": "headSize",
1155
+ "headDimV4": "headDimV4",
1156
+ "numSplits": "numSplits",
1157
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
1158
+ "cacheSeqlens": true,
1159
+ "cacheSeqlensIndex": "\"0u\"",
1160
+ "hasKeyPadMask": "present.maskIndexT",
1161
+ "maskFilterValue": "attrs.mask_filter_value",
1162
+ "hasMask": "present.attentionBiasT"
1163
+ },
1164
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "key_pad_mask", "attn_mask", "partial_out", "partial_stats", "params_self_bias"],
1165
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1166
+ },
1167
+ {
1168
+ "id": "merge",
1169
+ "name": "DecoderMaskedMultiHeadAttention.SelfMerge",
1170
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
1171
+ "derive": {
1172
+ "layout": "\"bhsd\"",
1173
+ "splitQueries": false,
1174
+ "scalar": "attnScalar",
1175
+ "qNumHeads": "numHeads",
1176
+ "headDimV4": "headDimV4",
1177
+ "numSplits": "numSplits"
1178
+ },
1179
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
1180
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1181
+ }
1182
+ ]
1183
+ },
1184
+ {
1185
+ "id": "self_nosg",
1186
+ "priority": 20,
1187
+ "when": ["selfPlainOk"],
1188
+ "requires": {},
1189
+ "intermediates": [
1190
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
1191
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
1192
+ ],
1193
+ "passes": [
1194
+ {
1195
+ "id": "present_retain",
1196
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
1197
+ "shader": "dmmha-present.wgsl.jinja",
1198
+ "derive": {
1199
+ "appendRow": false,
1200
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1201
+ "itemsPerLane": "retainItemsPerLane",
1202
+ "cacheScalar": "attnScalar"
1203
+ },
1204
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
1205
+ "dispatch": {
1206
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1207
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1208
+ "z": 1
1209
+ },
1210
+ "viewAlias": [
1211
+ { "input": "past_key", "output": "present_key" },
1212
+ { "input": "past_value", "output": "present_value" }
1213
+ ]
1214
+ },
1215
+ {
1216
+ "id": "present_append",
1217
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
1218
+ "shader": "dmmha-present.wgsl.jinja",
1219
+ "derive": {
1220
+ "appendRow": true,
1221
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1222
+ "headSizeV4": "headDimV4",
1223
+ "numHeads": "numHeads"
1224
+ },
1225
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
1226
+ "dispatch": {
1227
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1228
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1229
+ "z": 1
1230
+ }
1231
+ },
1232
+ {
1233
+ "id": "split_attention",
1234
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitKNoSg",
1235
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
1236
+ "derive": {
1237
+ "layout": "\"bhsd\"",
1238
+ "splitQueries": false,
1239
+ "qNumHeads": "numHeads",
1240
+ "kvNumHeads": "numHeads",
1241
+ "headDim": "headSize",
1242
+ "headDimV4": "headDimV4",
1243
+ "numSplits": "numSplits",
1244
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
1245
+ "cacheSeqlens": true,
1246
+ "cacheSeqlensIndex": "\"0u\"",
1247
+ "hasKeyPadMask": "present.maskIndexT",
1248
+ "hasMask": "present.attentionBiasT",
1249
+ "useSubgroups": false
1250
+ },
1251
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "partial_out", "partial_stats", "params_self"],
1252
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1253
+ },
1254
+ {
1255
+ "id": "merge",
1256
+ "name": "DecoderMaskedMultiHeadAttention.SelfMergeNoSg",
1257
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
1258
+ "derive": {
1259
+ "layout": "\"bhsd\"",
1260
+ "splitQueries": false,
1261
+ "scalar": "attnScalar",
1262
+ "qNumHeads": "numHeads",
1263
+ "headDimV4": "headDimV4",
1264
+ "numSplits": "numSplits"
1265
+ },
1266
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
1267
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1268
+ }
1269
+ ]
1270
+ },
1271
+ {
1272
+ "id": "self_bias_nosg",
1273
+ "priority": 20,
1274
+ "when": ["selfBiasOk"],
1275
+ "requires": {},
1276
+ "intermediates": [
1277
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
1278
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
1279
+ ],
1280
+ "passes": [
1281
+ {
1282
+ "id": "present_retain",
1283
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
1284
+ "shader": "dmmha-present.wgsl.jinja",
1285
+ "derive": {
1286
+ "appendRow": false,
1287
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1288
+ "itemsPerLane": "retainItemsPerLane",
1289
+ "cacheScalar": "attnScalar"
1290
+ },
1291
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
1292
+ "dispatch": {
1293
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1294
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1295
+ "z": 1
1296
+ },
1297
+ "viewAlias": [
1298
+ { "input": "past_key", "output": "present_key" },
1299
+ { "input": "past_value", "output": "present_value" }
1300
+ ]
1301
+ },
1302
+ {
1303
+ "id": "present_append",
1304
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
1305
+ "shader": "dmmha-present.wgsl.jinja",
1306
+ "derive": {
1307
+ "appendRow": true,
1308
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1309
+ "headSizeV4": "headDimV4",
1310
+ "numHeads": "numHeads"
1311
+ },
1312
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
1313
+ "dispatch": {
1314
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1315
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1316
+ "z": 1
1317
+ }
1318
+ },
1319
+ {
1320
+ "id": "split_attention",
1321
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitKNoSg",
1322
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
1323
+ "derive": {
1324
+ "layout": "\"bhsd\"",
1325
+ "splitQueries": false,
1326
+ "qNumHeads": "numHeads",
1327
+ "kvNumHeads": "numHeads",
1328
+ "headDim": "headSize",
1329
+ "headDimV4": "headDimV4",
1330
+ "numSplits": "numSplits",
1331
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
1332
+ "cacheSeqlens": true,
1333
+ "cacheSeqlensIndex": "\"0u\"",
1334
+ "hasKeyPadMask": "present.maskIndexT",
1335
+ "hasMask": "present.attentionBiasT",
1336
+ "useSubgroups": false
1337
+ },
1338
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "attn_mask", "partial_out", "partial_stats", "params_self_bias"],
1339
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1340
+ },
1341
+ {
1342
+ "id": "merge",
1343
+ "name": "DecoderMaskedMultiHeadAttention.SelfMergeNoSg",
1344
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
1345
+ "derive": {
1346
+ "layout": "\"bhsd\"",
1347
+ "splitQueries": false,
1348
+ "scalar": "attnScalar",
1349
+ "qNumHeads": "numHeads",
1350
+ "headDimV4": "headDimV4",
1351
+ "numSplits": "numSplits"
1352
+ },
1353
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
1354
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1355
+ }
1356
+ ]
1357
+ },
1358
+ {
1359
+ "id": "self_mask_nosg",
1360
+ "priority": 20,
1361
+ "when": ["selfMaskOk"],
1362
+ "requires": {},
1363
+ "intermediates": [
1364
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
1365
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
1366
+ ],
1367
+ "passes": [
1368
+ {
1369
+ "id": "present_retain",
1370
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
1371
+ "shader": "dmmha-present.wgsl.jinja",
1372
+ "derive": {
1373
+ "appendRow": false,
1374
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1375
+ "itemsPerLane": "retainItemsPerLane",
1376
+ "cacheScalar": "attnScalar"
1377
+ },
1378
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
1379
+ "dispatch": {
1380
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1381
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1382
+ "z": 1
1383
+ },
1384
+ "viewAlias": [
1385
+ { "input": "past_key", "output": "present_key" },
1386
+ { "input": "past_value", "output": "present_value" }
1387
+ ]
1388
+ },
1389
+ {
1390
+ "id": "present_append",
1391
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
1392
+ "shader": "dmmha-present.wgsl.jinja",
1393
+ "derive": {
1394
+ "appendRow": true,
1395
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1396
+ "headSizeV4": "headDimV4",
1397
+ "numHeads": "numHeads"
1398
+ },
1399
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
1400
+ "dispatch": {
1401
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1402
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1403
+ "z": 1
1404
+ }
1405
+ },
1406
+ {
1407
+ "id": "split_attention",
1408
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitKNoSg",
1409
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
1410
+ "derive": {
1411
+ "layout": "\"bhsd\"",
1412
+ "splitQueries": false,
1413
+ "qNumHeads": "numHeads",
1414
+ "kvNumHeads": "numHeads",
1415
+ "headDim": "headSize",
1416
+ "headDimV4": "headDimV4",
1417
+ "numSplits": "numSplits",
1418
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
1419
+ "cacheSeqlens": true,
1420
+ "cacheSeqlensIndex": "\"0u\"",
1421
+ "hasKeyPadMask": "present.maskIndexT",
1422
+ "maskFilterValue": "attrs.mask_filter_value",
1423
+ "hasMask": "present.attentionBiasT",
1424
+ "useSubgroups": false
1425
+ },
1426
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "key_pad_mask", "partial_out", "partial_stats", "params_self"],
1427
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1428
+ },
1429
+ {
1430
+ "id": "merge",
1431
+ "name": "DecoderMaskedMultiHeadAttention.SelfMergeNoSg",
1432
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
1433
+ "derive": {
1434
+ "layout": "\"bhsd\"",
1435
+ "splitQueries": false,
1436
+ "scalar": "attnScalar",
1437
+ "qNumHeads": "numHeads",
1438
+ "headDimV4": "headDimV4",
1439
+ "numSplits": "numSplits"
1440
+ },
1441
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
1442
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1443
+ }
1444
+ ]
1445
+ },
1446
+ {
1447
+ "id": "self_mask_bias_nosg",
1448
+ "priority": 20,
1449
+ "when": ["selfMaskBiasOk"],
1450
+ "requires": {},
1451
+ "intermediates": [
1452
+ { "id": "partialOut", "dtype": "float32", "shape": "[batchSize * numHeads * numSplits * headSize]" },
1453
+ { "id": "partialStats", "dtype": "float32", "shape": "[2 * batchSize * numHeads * numSplits]" }
1454
+ ],
1455
+ "passes": [
1456
+ {
1457
+ "id": "present_retain",
1458
+ "name": "DecoderMaskedMultiHeadAttention.PresentRetain",
1459
+ "shader": "dmmha-present.wgsl.jinja",
1460
+ "derive": {
1461
+ "appendRow": false,
1462
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1463
+ "itemsPerLane": "retainItemsPerLane",
1464
+ "cacheScalar": "attnScalar"
1465
+ },
1466
+ "bindings": ["past_key", "past_value", "present_key", "present_value", "params_retain"],
1467
+ "dispatch": {
1468
+ "x": "min(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1469
+ "y": "ceilDiv(ceilDiv(cacheWordsV4, tunables.COPY_WORKGROUP_SIZE * retainItemsPerLane), 65535)",
1470
+ "z": 1
1471
+ },
1472
+ "viewAlias": [
1473
+ { "input": "past_key", "output": "present_key" },
1474
+ { "input": "past_value", "output": "present_value" }
1475
+ ]
1476
+ },
1477
+ {
1478
+ "id": "present_append",
1479
+ "name": "DecoderMaskedMultiHeadAttention.PresentAppend",
1480
+ "shader": "dmmha-present.wgsl.jinja",
1481
+ "derive": {
1482
+ "appendRow": true,
1483
+ "workgroupSize": "tunables.COPY_WORKGROUP_SIZE",
1484
+ "headSizeV4": "headDimV4",
1485
+ "numHeads": "numHeads"
1486
+ },
1487
+ "bindings": ["key_row", "value_row", "past_sequence_length", "present_key", "present_value", "params_append"],
1488
+ "dispatch": {
1489
+ "x": "min(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1490
+ "y": "ceilDiv(ceilDiv((batchSize * hiddenSize / 4), (tunables.COPY_WORKGROUP_SIZE)), 65535)",
1491
+ "z": 1
1492
+ }
1493
+ },
1494
+ {
1495
+ "id": "split_attention",
1496
+ "name": "DecoderMaskedMultiHeadAttention.SelfSplitKNoSg",
1497
+ "shader": "attn-flash-decode-splitk.wgsl.jinja",
1498
+ "derive": {
1499
+ "layout": "\"bhsd\"",
1500
+ "splitQueries": false,
1501
+ "qNumHeads": "numHeads",
1502
+ "kvNumHeads": "numHeads",
1503
+ "headDim": "headSize",
1504
+ "headDimV4": "headDimV4",
1505
+ "numSplits": "numSplits",
1506
+ "usesF16": "tensorDtypes.queryT == \"float16\"",
1507
+ "cacheSeqlens": true,
1508
+ "cacheSeqlensIndex": "\"0u\"",
1509
+ "hasKeyPadMask": "present.maskIndexT",
1510
+ "maskFilterValue": "attrs.mask_filter_value",
1511
+ "hasMask": "present.attentionBiasT",
1512
+ "useSubgroups": false
1513
+ },
1514
+ "bindings": ["query", "key_cache", "value_cache", "seqlens_k", "key_pad_mask", "attn_mask", "partial_out", "partial_stats", "params_self_bias"],
1515
+ "dispatch": { "x": "numSplits", "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1516
+ },
1517
+ {
1518
+ "id": "merge",
1519
+ "name": "DecoderMaskedMultiHeadAttention.SelfMergeNoSg",
1520
+ "shader": "attn-flash-decode-splitk-merge.wgsl.jinja",
1521
+ "derive": {
1522
+ "layout": "\"bhsd\"",
1523
+ "splitQueries": false,
1524
+ "scalar": "attnScalar",
1525
+ "qNumHeads": "numHeads",
1526
+ "headDimV4": "headDimV4",
1527
+ "numSplits": "numSplits"
1528
+ },
1529
+ "bindings": ["partial_out_read", "partial_stats_read", "output"],
1530
+ "dispatch": { "x": 1, "y": "attrs.num_heads", "z": "dim(shapes.queryT, 0)" }
1531
+ }
1532
+ ]
1533
+ }
1534
+ ]
1535
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "com.microsoft.DecoderMaskedMultiHeadAttention",
3
+ "id": "_com_microsoft_decodermaskedmultiheadattention_webgpu_d93bf4a",
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": "7fi0SKKDfYjYG+l9OHNcodIeSFH5zXKMIJi9iv32rZo=",
11
+ "attn-flash-decode-splitk.wgsl.jinja": "BFL68/1x2lxrN/lTCgC5cy0bmEnkeKQksPtZKwhLE7A=",
12
+ "bench.json": "Kow9t33DDsCfO1hnX4CBJ3bKQkH284pj5sKvwirbabA=",
13
+ "dmmha-present.wgsl.jinja": "OCydRlEFGVeylOtabFeuAxrofZ/c+uqe5o1RgyWx9fU=",
14
+ "manifest.json": "OemoYbwPetpiqBxosVOlV2e20zGYfgzT4zv0y4HdcOc=",
15
+ "test.json": "2CVKz1jxKg77lGzgcrIBiv1FqPQNSORMuRdC2qRXNyw="
16
+ }
17
+ },
18
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
19
+ "webgpu": {
20
+ "manifestSpec": "2.1",
21
+ "variants": {
22
+ "cross_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
23
+ "cross": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
24
+ "cross_qk_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
25
+ "cross_qk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
26
+ "cross_mask_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
27
+ "cross_mask": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
28
+ "cross_mask_qk_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
29
+ "cross_mask_qk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
30
+ "cross_nosg_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
31
+ "cross_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
32
+ "cross_qk_nosg_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
33
+ "cross_qk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
34
+ "cross_mask_nosg_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
35
+ "cross_mask_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
36
+ "cross_mask_qk_nosg_direct": ["attn-flash-decode-splitk.wgsl.jinja"],
37
+ "cross_mask_qk_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja"],
38
+ "self": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"],
39
+ "self_bias": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"],
40
+ "self_mask": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"],
41
+ "self_mask_bias": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"],
42
+ "self_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"],
43
+ "self_bias_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"],
44
+ "self_mask_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"],
45
+ "self_mask_bias_nosg": ["attn-flash-decode-splitk-merge.wgsl.jinja", "attn-flash-decode-splitk.wgsl.jinja", "dmmha-present.wgsl.jinja"]
46
+ }
47
+ }
48
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cases": [
3
+ {
4
+ "name": "cross_plain_b1_h4_kv16_d16",
5
+ "provenance": {
6
+ "notes": "Cross attention with no mask and no diagnostic row: the minimum contract, where every encoder key contributes."
7
+ },
8
+ "attrs": { "num_heads": 4 },
9
+ "inputs": {
10
+ "queryT": {
11
+ "dtype": "float32",
12
+ "shape": [1, 1, 64],
13
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
14
+ },
15
+ "keyT": {
16
+ "dtype": "float32",
17
+ "shape": [1, 4, 16, 16],
18
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
19
+ },
20
+ "valueT": {
21
+ "dtype": "float32",
22
+ "shape": [1, 4, 16, 16],
23
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
24
+ }
25
+ },
26
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 } }
27
+ },
28
+ {
29
+ "name": "cross_plain_b2_h6_kv40_d32",
30
+ "provenance": {
31
+ "notes": "Cross attention over a key count that is not a multiple of the split tile, so the last partition absorbs the remainder."
32
+ },
33
+ "attrs": { "num_heads": 6 },
34
+ "inputs": {
35
+ "queryT": {
36
+ "dtype": "float32",
37
+ "shape": [2, 1, 192],
38
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
39
+ },
40
+ "keyT": {
41
+ "dtype": "float32",
42
+ "shape": [2, 6, 40, 32],
43
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
44
+ },
45
+ "valueT": {
46
+ "dtype": "float32",
47
+ "shape": [2, 6, 40, 32],
48
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
49
+ }
50
+ },
51
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 1, 192], "tolerance": 0.00002 } }
52
+ },
53
+ {
54
+ "name": "cross_mask_b1_h4_kv16_d16",
55
+ "provenance": {
56
+ "notes": "Cross attention with an alternating key-padding mask. A zero entry adds the filter value, so a masked key keeps a tiny but non-zero weight."
57
+ },
58
+ "attrs": { "num_heads": 4 },
59
+ "inputs": {
60
+ "queryT": {
61
+ "dtype": "float32",
62
+ "shape": [1, 1, 64],
63
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
64
+ },
65
+ "keyT": {
66
+ "dtype": "float32",
67
+ "shape": [1, 4, 16, 16],
68
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
69
+ },
70
+ "valueT": {
71
+ "dtype": "float32",
72
+ "shape": [1, 4, 16, 16],
73
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
74
+ },
75
+ "maskIndexT": { "dtype": "int32", "shape": [1, 16], "data": { "kind": "cycle", "values": [1, 0] } }
76
+ },
77
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 } }
78
+ },
79
+ {
80
+ "name": "cross_mask_all_padded_b1_h2_kv8_d16",
81
+ "provenance": {
82
+ "notes": "Every mask entry is zero. The additive filter leaves the row uniform, where a knockout mask would return zeros; this case separates the two readings."
83
+ },
84
+ "attrs": { "num_heads": 2 },
85
+ "inputs": {
86
+ "queryT": {
87
+ "dtype": "float32",
88
+ "shape": [1, 1, 32],
89
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
90
+ },
91
+ "keyT": {
92
+ "dtype": "float32",
93
+ "shape": [1, 2, 8, 16],
94
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
95
+ },
96
+ "valueT": {
97
+ "dtype": "float32",
98
+ "shape": [1, 2, 8, 16],
99
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
100
+ },
101
+ "maskIndexT": { "dtype": "int32", "shape": [1, 8], "data": { "kind": "constant", "value": 0 } }
102
+ },
103
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 32], "tolerance": 0.00002 } }
104
+ },
105
+ {
106
+ "name": "cross_mask_filter_value_b1_h4_kv12_d16",
107
+ "provenance": {
108
+ "notes": "A non-default mask filter value, small enough that masked keys still carry visible weight."
109
+ },
110
+ "attrs": { "num_heads": 4, "mask_filter_value": -4 },
111
+ "inputs": {
112
+ "queryT": {
113
+ "dtype": "float32",
114
+ "shape": [1, 1, 64],
115
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
116
+ },
117
+ "keyT": {
118
+ "dtype": "float32",
119
+ "shape": [1, 4, 12, 16],
120
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
121
+ },
122
+ "valueT": {
123
+ "dtype": "float32",
124
+ "shape": [1, 4, 12, 16],
125
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
126
+ },
127
+ "maskIndexT": { "dtype": "int32", "shape": [1, 12], "data": { "kind": "cycle", "values": [1, 1, 0] } }
128
+ },
129
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 } }
130
+ },
131
+ {
132
+ "name": "cross_qk_b1_h4_kv16_d16",
133
+ "provenance": {
134
+ "notes": "Cross attention emitting the pre-softmax score row. The values are the scaled dot products, so a post-softmax reading would show every row summing to one."
135
+ },
136
+ "attrs": { "num_heads": 4, "output_qk": 1 },
137
+ "inputs": {
138
+ "queryT": {
139
+ "dtype": "float32",
140
+ "shape": [1, 1, 64],
141
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
142
+ },
143
+ "keyT": {
144
+ "dtype": "float32",
145
+ "shape": [1, 4, 16, 16],
146
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
147
+ },
148
+ "valueT": {
149
+ "dtype": "float32",
150
+ "shape": [1, 4, 16, 16],
151
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
152
+ }
153
+ },
154
+ "outputs": {
155
+ "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 },
156
+ "qkT": { "dtype": "float32", "shape": [1, 4, 1, 16], "tolerance": 0.00002 }
157
+ }
158
+ },
159
+ {
160
+ "name": "cross_mask_qk_b1_h4_kv20_d16",
161
+ "provenance": {
162
+ "notes": "The score row is emitted after the mask is added, so the masked columns sit a filter value below their neighbours."
163
+ },
164
+ "attrs": { "num_heads": 4, "output_qk": 1 },
165
+ "inputs": {
166
+ "queryT": {
167
+ "dtype": "float32",
168
+ "shape": [1, 1, 64],
169
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
170
+ },
171
+ "keyT": {
172
+ "dtype": "float32",
173
+ "shape": [1, 4, 20, 16],
174
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
175
+ },
176
+ "valueT": {
177
+ "dtype": "float32",
178
+ "shape": [1, 4, 20, 16],
179
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
180
+ },
181
+ "maskIndexT": { "dtype": "int32", "shape": [1, 20], "data": { "kind": "cycle", "values": [1, 0, 1] } }
182
+ },
183
+ "outputs": {
184
+ "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 },
185
+ "qkT": { "dtype": "float32", "shape": [1, 4, 1, 20], "tolerance": 0.00002 }
186
+ }
187
+ },
188
+ {
189
+ "name": "cross_qk_scale_b2_h3_kv24_d32",
190
+ "provenance": {
191
+ "notes": "An explicit scale overrides the default reciprocal square root, and the emitted score row shows it directly."
192
+ },
193
+ "attrs": { "num_heads": 3, "scale": 0.125, "output_qk": 1 },
194
+ "inputs": {
195
+ "queryT": {
196
+ "dtype": "float32",
197
+ "shape": [2, 1, 96],
198
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
199
+ },
200
+ "keyT": {
201
+ "dtype": "float32",
202
+ "shape": [2, 3, 24, 32],
203
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
204
+ },
205
+ "valueT": {
206
+ "dtype": "float32",
207
+ "shape": [2, 3, 24, 32],
208
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
209
+ }
210
+ },
211
+ "outputs": {
212
+ "outputT": { "dtype": "float32", "shape": [2, 1, 96], "tolerance": 0.00002 },
213
+ "qkT": { "dtype": "float32", "shape": [2, 3, 1, 24], "tolerance": 0.00002 }
214
+ }
215
+ },
216
+ {
217
+ "name": "cross_plain_b1_h2_kv1_d16",
218
+ "provenance": {
219
+ "notes": "A single encoder key: the softmax is degenerate and the output is that key's value row."
220
+ },
221
+ "attrs": { "num_heads": 2 },
222
+ "inputs": {
223
+ "queryT": {
224
+ "dtype": "float32",
225
+ "shape": [1, 1, 32],
226
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
227
+ },
228
+ "keyT": {
229
+ "dtype": "float32",
230
+ "shape": [1, 2, 1, 16],
231
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
232
+ },
233
+ "valueT": {
234
+ "dtype": "float32",
235
+ "shape": [1, 2, 1, 16],
236
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
237
+ }
238
+ },
239
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 32], "tolerance": 0.00002 } }
240
+ },
241
+ {
242
+ "name": "cross_plain_b1_h1_kv1500_d64",
243
+ "provenance": {
244
+ "notes": "Whisper-scale cross attention geometry at one head, where the key count is far larger than the head dimension."
245
+ },
246
+ "attrs": { "num_heads": 1 },
247
+ "inputs": {
248
+ "queryT": {
249
+ "dtype": "float32",
250
+ "shape": [1, 1, 64],
251
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
252
+ },
253
+ "keyT": {
254
+ "dtype": "float32",
255
+ "shape": [1, 1, 1500, 64],
256
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
257
+ },
258
+ "valueT": {
259
+ "dtype": "float32",
260
+ "shape": [1, 1, 1500, 64],
261
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
262
+ }
263
+ },
264
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00005 } }
265
+ },
266
+ {
267
+ "name": "cross_head_size_four_b1_h4_kv8_d4",
268
+ "provenance": {
269
+ "notes": "Cross-attention with 4 heads and head width 4, the minimum that fills one four-element vector, over an 8-token key/value cache."
270
+ },
271
+ "attrs": { "num_heads": 4 },
272
+ "inputs": {
273
+ "queryT": {
274
+ "dtype": "float32",
275
+ "shape": [1, 1, 16],
276
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
277
+ },
278
+ "keyT": {
279
+ "dtype": "float32",
280
+ "shape": [1, 4, 8, 4],
281
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
282
+ },
283
+ "valueT": {
284
+ "dtype": "float32",
285
+ "shape": [1, 4, 8, 4],
286
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
287
+ }
288
+ },
289
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 16], "tolerance": 0.00002 } }
290
+ },
291
+ {
292
+ "name": "cross_plain_f16_b1_h4_kv16_d16",
293
+ "provenance": {
294
+ "notes": "The float16 twin of the plain cross route: operands and output narrow, while the score and online softmax stay float32."
295
+ },
296
+ "attrs": { "num_heads": 4 },
297
+ "inputs": {
298
+ "queryT": {
299
+ "dtype": "float16",
300
+ "shape": [1, 1, 64],
301
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
302
+ },
303
+ "keyT": {
304
+ "dtype": "float16",
305
+ "shape": [1, 4, 16, 16],
306
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
307
+ },
308
+ "valueT": {
309
+ "dtype": "float16",
310
+ "shape": [1, 4, 16, 16],
311
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
312
+ }
313
+ },
314
+ "outputs": { "outputT": { "dtype": "float16", "shape": [1, 1, 64], "tolerance": 0.003 } }
315
+ },
316
+ {
317
+ "name": "cross_mask_qk_f16_b1_h4_kv20_d16",
318
+ "provenance": {
319
+ "notes": "Float16 operands with a float32 score row, the combination the schema allows by typing the diagnostic output independently."
320
+ },
321
+ "attrs": { "num_heads": 4, "output_qk": 1 },
322
+ "inputs": {
323
+ "queryT": {
324
+ "dtype": "float16",
325
+ "shape": [1, 1, 64],
326
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
327
+ },
328
+ "keyT": {
329
+ "dtype": "float16",
330
+ "shape": [1, 4, 20, 16],
331
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
332
+ },
333
+ "valueT": {
334
+ "dtype": "float16",
335
+ "shape": [1, 4, 20, 16],
336
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
337
+ },
338
+ "maskIndexT": { "dtype": "int32", "shape": [1, 20], "data": { "kind": "cycle", "values": [1, 0, 1] } }
339
+ },
340
+ "outputs": {
341
+ "outputT": { "dtype": "float16", "shape": [1, 1, 64], "tolerance": 0.003 },
342
+ "qkT": { "dtype": "float32", "shape": [1, 4, 1, 20], "tolerance": 0.003 }
343
+ }
344
+ },
345
+ {
346
+ "name": "cross_qk_f16_row_b1_h4_kv16_d16",
347
+ "provenance": { "notes": "A float16 score row, so the diagnostic output narrows with the operands." },
348
+ "attrs": { "num_heads": 4, "output_qk": 1 },
349
+ "inputs": {
350
+ "queryT": {
351
+ "dtype": "float16",
352
+ "shape": [1, 1, 64],
353
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
354
+ },
355
+ "keyT": {
356
+ "dtype": "float16",
357
+ "shape": [1, 4, 16, 16],
358
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
359
+ },
360
+ "valueT": {
361
+ "dtype": "float16",
362
+ "shape": [1, 4, 16, 16],
363
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
364
+ }
365
+ },
366
+ "outputs": {
367
+ "outputT": { "dtype": "float16", "shape": [1, 1, 64], "tolerance": 0.003 },
368
+ "qkT": { "dtype": "float16", "shape": [1, 4, 1, 16], "tolerance": 0.002 }
369
+ }
370
+ },
371
+ {
372
+ "name": "self_plain_b1_h4_cap8_d16_past3",
373
+ "provenance": {
374
+ "notes": "Self attention appending token four into a cache of capacity eight. Only the first four rows are attended; the rest keep their past values."
375
+ },
376
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
377
+ "inputs": {
378
+ "queryT": {
379
+ "dtype": "float32",
380
+ "shape": [1, 1, 64],
381
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
382
+ },
383
+ "keyT": {
384
+ "dtype": "float32",
385
+ "shape": [1, 1, 64],
386
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
387
+ },
388
+ "valueT": {
389
+ "dtype": "float32",
390
+ "shape": [1, 1, 64],
391
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
392
+ },
393
+ "pastKeyT": {
394
+ "dtype": "float32",
395
+ "shape": [1, 4, 8, 16],
396
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
397
+ },
398
+ "pastValueT": {
399
+ "dtype": "float32",
400
+ "shape": [1, 4, 8, 16],
401
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
402
+ },
403
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [3] } }
404
+ },
405
+ "outputs": {
406
+ "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 },
407
+ "presentKeyT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 },
408
+ "presentValueT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 }
409
+ }
410
+ },
411
+ {
412
+ "name": "self_plain_b1_h4_cap8_d16_past0",
413
+ "provenance": {
414
+ "notes": "An empty cache: the appended token is the only key, so the output is its own value row."
415
+ },
416
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
417
+ "inputs": {
418
+ "queryT": {
419
+ "dtype": "float32",
420
+ "shape": [1, 1, 64],
421
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
422
+ },
423
+ "keyT": {
424
+ "dtype": "float32",
425
+ "shape": [1, 1, 64],
426
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
427
+ },
428
+ "valueT": {
429
+ "dtype": "float32",
430
+ "shape": [1, 1, 64],
431
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
432
+ },
433
+ "pastKeyT": {
434
+ "dtype": "float32",
435
+ "shape": [1, 4, 8, 16],
436
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
437
+ },
438
+ "pastValueT": {
439
+ "dtype": "float32",
440
+ "shape": [1, 4, 8, 16],
441
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
442
+ },
443
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [0] } }
444
+ },
445
+ "outputs": {
446
+ "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 },
447
+ "presentKeyT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 },
448
+ "presentValueT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 }
449
+ }
450
+ },
451
+ {
452
+ "name": "self_plain_b1_h4_cap8_d16_past6",
453
+ "provenance": {
454
+ "notes": "Self-attention with a shared past/present buffer sized for 8 tokens; 6 past tokens plus the appended token leave one row unused. 4 heads, head width 16."
455
+ },
456
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
457
+ "inputs": {
458
+ "queryT": {
459
+ "dtype": "float32",
460
+ "shape": [1, 1, 64],
461
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
462
+ },
463
+ "keyT": {
464
+ "dtype": "float32",
465
+ "shape": [1, 1, 64],
466
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
467
+ },
468
+ "valueT": {
469
+ "dtype": "float32",
470
+ "shape": [1, 1, 64],
471
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
472
+ },
473
+ "pastKeyT": {
474
+ "dtype": "float32",
475
+ "shape": [1, 4, 8, 16],
476
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
477
+ },
478
+ "pastValueT": {
479
+ "dtype": "float32",
480
+ "shape": [1, 4, 8, 16],
481
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
482
+ },
483
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [6] } }
484
+ },
485
+ "outputs": {
486
+ "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 },
487
+ "presentKeyT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 },
488
+ "presentValueT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 }
489
+ }
490
+ },
491
+ {
492
+ "name": "self_mask_b2_h4_cap16_d16_past9",
493
+ "provenance": {
494
+ "notes": "A key-padding mask over the live length only; the cache rows past it are outside the mask and outside the softmax."
495
+ },
496
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
497
+ "inputs": {
498
+ "queryT": {
499
+ "dtype": "float32",
500
+ "shape": [2, 1, 64],
501
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
502
+ },
503
+ "keyT": {
504
+ "dtype": "float32",
505
+ "shape": [2, 1, 64],
506
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
507
+ },
508
+ "valueT": {
509
+ "dtype": "float32",
510
+ "shape": [2, 1, 64],
511
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
512
+ },
513
+ "maskIndexT": { "dtype": "int32", "shape": [2, 10], "data": { "kind": "cycle", "values": [1, 1, 0] } },
514
+ "pastKeyT": {
515
+ "dtype": "float32",
516
+ "shape": [2, 4, 16, 16],
517
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
518
+ },
519
+ "pastValueT": {
520
+ "dtype": "float32",
521
+ "shape": [2, 4, 16, 16],
522
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
523
+ },
524
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [9] } }
525
+ },
526
+ "outputs": {
527
+ "outputT": { "dtype": "float32", "shape": [2, 1, 64], "tolerance": 0.00002 },
528
+ "presentKeyT": { "dtype": "float32", "shape": [2, 4, 16, 16], "tolerance": 0.000001 },
529
+ "presentValueT": { "dtype": "float32", "shape": [2, 4, 16, 16], "tolerance": 0.000001 }
530
+ }
531
+ },
532
+ {
533
+ "name": "self_bias_b1_h4_cap16_d16_past5",
534
+ "provenance": {
535
+ "notes": "An additive score bias broadcast across batch and heads, the shape upstream's own self-attention test uses."
536
+ },
537
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
538
+ "inputs": {
539
+ "queryT": {
540
+ "dtype": "float32",
541
+ "shape": [1, 1, 64],
542
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
543
+ },
544
+ "keyT": {
545
+ "dtype": "float32",
546
+ "shape": [1, 1, 64],
547
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
548
+ },
549
+ "valueT": {
550
+ "dtype": "float32",
551
+ "shape": [1, 1, 64],
552
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
553
+ },
554
+ "attentionBiasT": {
555
+ "dtype": "float32",
556
+ "shape": [1, 1, 1, 6],
557
+ "data": { "kind": "fillFloat32", "sinStep": 0.37, "cosStep": 0.19, "scale": 0.3 }
558
+ },
559
+ "pastKeyT": {
560
+ "dtype": "float32",
561
+ "shape": [1, 4, 16, 16],
562
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
563
+ },
564
+ "pastValueT": {
565
+ "dtype": "float32",
566
+ "shape": [1, 4, 16, 16],
567
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
568
+ },
569
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [5] } }
570
+ },
571
+ "outputs": {
572
+ "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 },
573
+ "presentKeyT": { "dtype": "float32", "shape": [1, 4, 16, 16], "tolerance": 0.000001 },
574
+ "presentValueT": { "dtype": "float32", "shape": [1, 4, 16, 16], "tolerance": 0.000001 }
575
+ }
576
+ },
577
+ {
578
+ "name": "self_bias_per_head_b2_h4_cap12_d16_past7",
579
+ "provenance": {
580
+ "notes": "A score bias with a real head dimension, so the head stride is non-zero and a broadcast reading would mix heads."
581
+ },
582
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
583
+ "inputs": {
584
+ "queryT": {
585
+ "dtype": "float32",
586
+ "shape": [2, 1, 64],
587
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
588
+ },
589
+ "keyT": {
590
+ "dtype": "float32",
591
+ "shape": [2, 1, 64],
592
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
593
+ },
594
+ "valueT": {
595
+ "dtype": "float32",
596
+ "shape": [2, 1, 64],
597
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
598
+ },
599
+ "attentionBiasT": {
600
+ "dtype": "float32",
601
+ "shape": [1, 4, 1, 8],
602
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.41, "scale": 0.3 }
603
+ },
604
+ "pastKeyT": {
605
+ "dtype": "float32",
606
+ "shape": [2, 4, 12, 16],
607
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
608
+ },
609
+ "pastValueT": {
610
+ "dtype": "float32",
611
+ "shape": [2, 4, 12, 16],
612
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
613
+ },
614
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [7] } }
615
+ },
616
+ "outputs": {
617
+ "outputT": { "dtype": "float32", "shape": [2, 1, 64], "tolerance": 0.00002 },
618
+ "presentKeyT": { "dtype": "float32", "shape": [2, 4, 12, 16], "tolerance": 0.000001 },
619
+ "presentValueT": { "dtype": "float32", "shape": [2, 4, 12, 16], "tolerance": 0.000001 }
620
+ }
621
+ },
622
+ {
623
+ "name": "self_mask_bias_b2_h4_cap16_d16_past9",
624
+ "provenance": {
625
+ "notes": "Both additive terms at once. The bias is added before the mask, matching the reference's term order."
626
+ },
627
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
628
+ "inputs": {
629
+ "queryT": {
630
+ "dtype": "float32",
631
+ "shape": [2, 1, 64],
632
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
633
+ },
634
+ "keyT": {
635
+ "dtype": "float32",
636
+ "shape": [2, 1, 64],
637
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
638
+ },
639
+ "valueT": {
640
+ "dtype": "float32",
641
+ "shape": [2, 1, 64],
642
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
643
+ },
644
+ "maskIndexT": { "dtype": "int32", "shape": [2, 10], "data": { "kind": "cycle", "values": [1, 0] } },
645
+ "attentionBiasT": {
646
+ "dtype": "float32",
647
+ "shape": [2, 4, 1, 10],
648
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.53, "scale": 0.3 }
649
+ },
650
+ "pastKeyT": {
651
+ "dtype": "float32",
652
+ "shape": [2, 4, 16, 16],
653
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
654
+ },
655
+ "pastValueT": {
656
+ "dtype": "float32",
657
+ "shape": [2, 4, 16, 16],
658
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
659
+ },
660
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [9] } }
661
+ },
662
+ "outputs": {
663
+ "outputT": { "dtype": "float32", "shape": [2, 1, 64], "tolerance": 0.00002 },
664
+ "presentKeyT": { "dtype": "float32", "shape": [2, 4, 16, 16], "tolerance": 0.000001 },
665
+ "presentValueT": { "dtype": "float32", "shape": [2, 4, 16, 16], "tolerance": 0.000001 }
666
+ }
667
+ },
668
+ {
669
+ "name": "self_bias_batch_only_b2_h4_cap12_d16_past5",
670
+ "provenance": {
671
+ "notes": "A score bias with a batch dimension but a broadcast head dimension, the mixed stride pair."
672
+ },
673
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
674
+ "inputs": {
675
+ "queryT": {
676
+ "dtype": "float32",
677
+ "shape": [2, 1, 64],
678
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
679
+ },
680
+ "keyT": {
681
+ "dtype": "float32",
682
+ "shape": [2, 1, 64],
683
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
684
+ },
685
+ "valueT": {
686
+ "dtype": "float32",
687
+ "shape": [2, 1, 64],
688
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
689
+ },
690
+ "attentionBiasT": {
691
+ "dtype": "float32",
692
+ "shape": [2, 1, 1, 6],
693
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.31, "scale": 0.3 }
694
+ },
695
+ "pastKeyT": {
696
+ "dtype": "float32",
697
+ "shape": [2, 4, 12, 16],
698
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
699
+ },
700
+ "pastValueT": {
701
+ "dtype": "float32",
702
+ "shape": [2, 4, 12, 16],
703
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
704
+ },
705
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [5] } }
706
+ },
707
+ "outputs": {
708
+ "outputT": { "dtype": "float32", "shape": [2, 1, 64], "tolerance": 0.00002 },
709
+ "presentKeyT": { "dtype": "float32", "shape": [2, 4, 12, 16], "tolerance": 0.000001 },
710
+ "presentValueT": { "dtype": "float32", "shape": [2, 4, 12, 16], "tolerance": 0.000001 }
711
+ }
712
+ },
713
+ {
714
+ "name": "self_scale_b1_h4_cap8_d16_past3",
715
+ "provenance": {
716
+ "notes": "An explicit scale on the self route, overriding the reciprocal square root of the head size."
717
+ },
718
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1, "scale": 0.2 },
719
+ "inputs": {
720
+ "queryT": {
721
+ "dtype": "float32",
722
+ "shape": [1, 1, 64],
723
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
724
+ },
725
+ "keyT": {
726
+ "dtype": "float32",
727
+ "shape": [1, 1, 64],
728
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
729
+ },
730
+ "valueT": {
731
+ "dtype": "float32",
732
+ "shape": [1, 1, 64],
733
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
734
+ },
735
+ "pastKeyT": {
736
+ "dtype": "float32",
737
+ "shape": [1, 4, 8, 16],
738
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
739
+ },
740
+ "pastValueT": {
741
+ "dtype": "float32",
742
+ "shape": [1, 4, 8, 16],
743
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
744
+ },
745
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [3] } }
746
+ },
747
+ "outputs": {
748
+ "outputT": { "dtype": "float32", "shape": [1, 1, 64], "tolerance": 0.00002 },
749
+ "presentKeyT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 },
750
+ "presentValueT": { "dtype": "float32", "shape": [1, 4, 8, 16], "tolerance": 0.000001 }
751
+ }
752
+ },
753
+ {
754
+ "name": "self_plain_b1_h8_cap448_d64_past223",
755
+ "provenance": { "notes": "Whisper-base decoder self-attention geometry, with the cache half filled." },
756
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
757
+ "inputs": {
758
+ "queryT": {
759
+ "dtype": "float32",
760
+ "shape": [1, 1, 512],
761
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
762
+ },
763
+ "keyT": {
764
+ "dtype": "float32",
765
+ "shape": [1, 1, 512],
766
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
767
+ },
768
+ "valueT": {
769
+ "dtype": "float32",
770
+ "shape": [1, 1, 512],
771
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
772
+ },
773
+ "pastKeyT": {
774
+ "dtype": "float32",
775
+ "shape": [1, 8, 448, 64],
776
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
777
+ },
778
+ "pastValueT": {
779
+ "dtype": "float32",
780
+ "shape": [1, 8, 448, 64],
781
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
782
+ },
783
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [223] } }
784
+ },
785
+ "outputs": {
786
+ "outputT": { "dtype": "float32", "shape": [1, 1, 512], "tolerance": 0.00005 },
787
+ "presentKeyT": { "dtype": "float32", "shape": [1, 8, 448, 64], "tolerance": 0.000001 },
788
+ "presentValueT": { "dtype": "float32", "shape": [1, 8, 448, 64], "tolerance": 0.000001 }
789
+ }
790
+ },
791
+ {
792
+ "name": "self_mask_all_padded_b1_h2_cap8_d16_past3",
793
+ "provenance": {
794
+ "notes": "Every live key is masked. The additive filter keeps the row a uniform average rather than collapsing it to zeros."
795
+ },
796
+ "attrs": { "num_heads": 2, "past_present_share_buffer": 1 },
797
+ "inputs": {
798
+ "queryT": {
799
+ "dtype": "float32",
800
+ "shape": [1, 1, 32],
801
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
802
+ },
803
+ "keyT": {
804
+ "dtype": "float32",
805
+ "shape": [1, 1, 32],
806
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
807
+ },
808
+ "valueT": {
809
+ "dtype": "float32",
810
+ "shape": [1, 1, 32],
811
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
812
+ },
813
+ "maskIndexT": { "dtype": "int32", "shape": [1, 4], "data": { "kind": "constant", "value": 0 } },
814
+ "pastKeyT": {
815
+ "dtype": "float32",
816
+ "shape": [1, 2, 8, 16],
817
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
818
+ },
819
+ "pastValueT": {
820
+ "dtype": "float32",
821
+ "shape": [1, 2, 8, 16],
822
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
823
+ },
824
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [3] } }
825
+ },
826
+ "outputs": {
827
+ "outputT": { "dtype": "float32", "shape": [1, 1, 32], "tolerance": 0.00002 },
828
+ "presentKeyT": { "dtype": "float32", "shape": [1, 2, 8, 16], "tolerance": 0.000001 },
829
+ "presentValueT": { "dtype": "float32", "shape": [1, 2, 8, 16], "tolerance": 0.000001 }
830
+ }
831
+ },
832
+ {
833
+ "name": "self_retain_stage4_b2_h8_cap448_d64_past31",
834
+ "provenance": {
835
+ "notes": "Self-attention with a shared past/present buffer sized for 448 tokens across 2 batches; 31 past tokens are extended by the appended token. 8 heads, head width 64."
836
+ },
837
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
838
+ "inputs": {
839
+ "queryT": {
840
+ "dtype": "float32",
841
+ "shape": [2, 1, 512],
842
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
843
+ },
844
+ "keyT": {
845
+ "dtype": "float32",
846
+ "shape": [2, 1, 512],
847
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
848
+ },
849
+ "valueT": {
850
+ "dtype": "float32",
851
+ "shape": [2, 1, 512],
852
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
853
+ },
854
+ "pastKeyT": {
855
+ "dtype": "float32",
856
+ "shape": [2, 8, 448, 64],
857
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
858
+ },
859
+ "pastValueT": {
860
+ "dtype": "float32",
861
+ "shape": [2, 8, 448, 64],
862
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
863
+ },
864
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [31] } }
865
+ },
866
+ "outputs": {
867
+ "outputT": { "dtype": "float32", "shape": [2, 1, 512], "tolerance": 0.00005 },
868
+ "presentKeyT": { "dtype": "float32", "shape": [2, 8, 448, 64], "tolerance": 0.000001 },
869
+ "presentValueT": { "dtype": "float32", "shape": [2, 8, 448, 64], "tolerance": 0.000001 }
870
+ }
871
+ },
872
+ {
873
+ "name": "self_retain_stage8_f16_b4_h8_cap448_d64_past223",
874
+ "provenance": {
875
+ "notes": "A cache large enough for the retain copy to stage the full eight words per lane, the deepest rung, in float16."
876
+ },
877
+ "attrs": { "num_heads": 8, "past_present_share_buffer": 1 },
878
+ "inputs": {
879
+ "queryT": {
880
+ "dtype": "float16",
881
+ "shape": [4, 1, 512],
882
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
883
+ },
884
+ "keyT": {
885
+ "dtype": "float16",
886
+ "shape": [4, 1, 512],
887
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
888
+ },
889
+ "valueT": {
890
+ "dtype": "float16",
891
+ "shape": [4, 1, 512],
892
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
893
+ },
894
+ "pastKeyT": {
895
+ "dtype": "float16",
896
+ "shape": [4, 8, 448, 64],
897
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
898
+ },
899
+ "pastValueT": {
900
+ "dtype": "float16",
901
+ "shape": [4, 8, 448, 64],
902
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
903
+ },
904
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [223] } }
905
+ },
906
+ "outputs": {
907
+ "outputT": { "dtype": "float16", "shape": [4, 1, 512], "tolerance": 0.003 },
908
+ "presentKeyT": { "dtype": "float16", "shape": [4, 8, 448, 64], "tolerance": 0.001 },
909
+ "presentValueT": { "dtype": "float16", "shape": [4, 8, 448, 64], "tolerance": 0.001 }
910
+ }
911
+ },
912
+ {
913
+ "name": "self_plain_f16_b1_h4_cap8_d16_past3",
914
+ "provenance": {
915
+ "notes": "The float16 twin of the plain self route, including the float16 cache the append pass writes."
916
+ },
917
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
918
+ "inputs": {
919
+ "queryT": {
920
+ "dtype": "float16",
921
+ "shape": [1, 1, 64],
922
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
923
+ },
924
+ "keyT": {
925
+ "dtype": "float16",
926
+ "shape": [1, 1, 64],
927
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
928
+ },
929
+ "valueT": {
930
+ "dtype": "float16",
931
+ "shape": [1, 1, 64],
932
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
933
+ },
934
+ "pastKeyT": {
935
+ "dtype": "float16",
936
+ "shape": [1, 4, 8, 16],
937
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
938
+ },
939
+ "pastValueT": {
940
+ "dtype": "float16",
941
+ "shape": [1, 4, 8, 16],
942
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
943
+ },
944
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [3] } }
945
+ },
946
+ "outputs": {
947
+ "outputT": { "dtype": "float16", "shape": [1, 1, 64], "tolerance": 0.003 },
948
+ "presentKeyT": { "dtype": "float16", "shape": [1, 4, 8, 16], "tolerance": 0.001 },
949
+ "presentValueT": { "dtype": "float16", "shape": [1, 4, 8, 16], "tolerance": 0.001 }
950
+ }
951
+ },
952
+ {
953
+ "name": "self_mask_bias_f16_b2_h4_cap16_d16_past9",
954
+ "provenance": {
955
+ "notes": "Float16 operands with both additive terms, so the bias narrows with the tensor type while the score stays float32."
956
+ },
957
+ "attrs": { "num_heads": 4, "past_present_share_buffer": 1 },
958
+ "inputs": {
959
+ "queryT": {
960
+ "dtype": "float16",
961
+ "shape": [2, 1, 64],
962
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
963
+ },
964
+ "keyT": {
965
+ "dtype": "float16",
966
+ "shape": [2, 1, 64],
967
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
968
+ },
969
+ "valueT": {
970
+ "dtype": "float16",
971
+ "shape": [2, 1, 64],
972
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
973
+ },
974
+ "maskIndexT": { "dtype": "int32", "shape": [2, 10], "data": { "kind": "cycle", "values": [1, 0] } },
975
+ "attentionBiasT": {
976
+ "dtype": "float16",
977
+ "shape": [2, 4, 1, 10],
978
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.53, "scale": 0.3 }
979
+ },
980
+ "pastKeyT": {
981
+ "dtype": "float16",
982
+ "shape": [2, 4, 16, 16],
983
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.13 }
984
+ },
985
+ "pastValueT": {
986
+ "dtype": "float16",
987
+ "shape": [2, 4, 16, 16],
988
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.29, "offset": 0.7 }
989
+ },
990
+ "pastSequenceLengthT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [9] } }
991
+ },
992
+ "outputs": {
993
+ "outputT": { "dtype": "float16", "shape": [2, 1, 64], "tolerance": 0.003 },
994
+ "presentKeyT": { "dtype": "float16", "shape": [2, 4, 16, 16], "tolerance": 0.001 },
995
+ "presentValueT": { "dtype": "float16", "shape": [2, 4, 16, 16], "tolerance": 0.001 }
996
+ }
997
+ },
998
+ {
999
+ "name": "cross_single_split_boundary_kv255_d64_float32",
1000
+ "provenance": {
1001
+ "notes": "Adjacent key lengths straddle the default computed split count; direct and split/merge must preserve the same full attention row."
1002
+ },
1003
+ "attrs": { "num_heads": 6 },
1004
+ "inputs": {
1005
+ "queryT": {
1006
+ "dtype": "float32",
1007
+ "shape": [1, 1, 384],
1008
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1009
+ },
1010
+ "keyT": {
1011
+ "dtype": "float32",
1012
+ "shape": [1, 6, 255, 64],
1013
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1014
+ },
1015
+ "valueT": {
1016
+ "dtype": "float32",
1017
+ "shape": [1, 6, 255, 64],
1018
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1019
+ }
1020
+ },
1021
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 } }
1022
+ },
1023
+ {
1024
+ "name": "cross_single_split_boundary_kv255_d64_float16",
1025
+ "provenance": {
1026
+ "notes": "Adjacent key lengths straddle the default computed split count; direct and split/merge must preserve the same full attention row."
1027
+ },
1028
+ "attrs": { "num_heads": 6 },
1029
+ "inputs": {
1030
+ "queryT": {
1031
+ "dtype": "float16",
1032
+ "shape": [1, 1, 384],
1033
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1034
+ },
1035
+ "keyT": {
1036
+ "dtype": "float16",
1037
+ "shape": [1, 6, 255, 64],
1038
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1039
+ },
1040
+ "valueT": {
1041
+ "dtype": "float16",
1042
+ "shape": [1, 6, 255, 64],
1043
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1044
+ }
1045
+ },
1046
+ "outputs": { "outputT": { "dtype": "float16", "shape": [1, 1, 384], "tolerance": 0.003 } }
1047
+ },
1048
+ {
1049
+ "name": "cross_single_split_boundary_kv256_d64_float32",
1050
+ "provenance": {
1051
+ "notes": "Adjacent key lengths straddle the default computed split count; direct and split/merge must preserve the same full attention row."
1052
+ },
1053
+ "attrs": { "num_heads": 6 },
1054
+ "inputs": {
1055
+ "queryT": {
1056
+ "dtype": "float32",
1057
+ "shape": [1, 1, 384],
1058
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1059
+ },
1060
+ "keyT": {
1061
+ "dtype": "float32",
1062
+ "shape": [1, 6, 256, 64],
1063
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1064
+ },
1065
+ "valueT": {
1066
+ "dtype": "float32",
1067
+ "shape": [1, 6, 256, 64],
1068
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1069
+ }
1070
+ },
1071
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 } }
1072
+ },
1073
+ {
1074
+ "name": "cross_single_split_boundary_kv256_d64_float16",
1075
+ "provenance": {
1076
+ "notes": "Adjacent key lengths straddle the default computed split count; direct and split/merge must preserve the same full attention row."
1077
+ },
1078
+ "attrs": { "num_heads": 6 },
1079
+ "inputs": {
1080
+ "queryT": {
1081
+ "dtype": "float16",
1082
+ "shape": [1, 1, 384],
1083
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1084
+ },
1085
+ "keyT": {
1086
+ "dtype": "float16",
1087
+ "shape": [1, 6, 256, 64],
1088
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1089
+ },
1090
+ "valueT": {
1091
+ "dtype": "float16",
1092
+ "shape": [1, 6, 256, 64],
1093
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1094
+ }
1095
+ },
1096
+ "outputs": { "outputT": { "dtype": "float16", "shape": [1, 1, 384], "tolerance": 0.003 } }
1097
+ },
1098
+ {
1099
+ "name": "cross_single_split_boundary_kv257_d64_float32",
1100
+ "provenance": {
1101
+ "notes": "Adjacent key lengths straddle the default computed split count; direct and split/merge must preserve the same full attention row."
1102
+ },
1103
+ "attrs": { "num_heads": 6 },
1104
+ "inputs": {
1105
+ "queryT": {
1106
+ "dtype": "float32",
1107
+ "shape": [1, 1, 384],
1108
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1109
+ },
1110
+ "keyT": {
1111
+ "dtype": "float32",
1112
+ "shape": [1, 6, 257, 64],
1113
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1114
+ },
1115
+ "valueT": {
1116
+ "dtype": "float32",
1117
+ "shape": [1, 6, 257, 64],
1118
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1119
+ }
1120
+ },
1121
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 } }
1122
+ },
1123
+ {
1124
+ "name": "cross_single_split_boundary_kv257_d64_float16",
1125
+ "provenance": {
1126
+ "notes": "Adjacent key lengths straddle the default computed split count; direct and split/merge must preserve the same full attention row."
1127
+ },
1128
+ "attrs": { "num_heads": 6 },
1129
+ "inputs": {
1130
+ "queryT": {
1131
+ "dtype": "float16",
1132
+ "shape": [1, 1, 384],
1133
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1134
+ },
1135
+ "keyT": {
1136
+ "dtype": "float16",
1137
+ "shape": [1, 6, 257, 64],
1138
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1139
+ },
1140
+ "valueT": {
1141
+ "dtype": "float16",
1142
+ "shape": [1, 6, 257, 64],
1143
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1144
+ }
1145
+ },
1146
+ "outputs": { "outputT": { "dtype": "float16", "shape": [1, 1, 384], "tolerance": 0.003 } }
1147
+ },
1148
+ {
1149
+ "name": "cross_single_split_mask_qk_kv64_d64_float32",
1150
+ "provenance": {
1151
+ "notes": "Single-partition cross attention emits the scaled, finitely masked pre-softmax QK row while directly normalizing its output."
1152
+ },
1153
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
1154
+ "inputs": {
1155
+ "queryT": {
1156
+ "dtype": "float32",
1157
+ "shape": [1, 1, 384],
1158
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1159
+ },
1160
+ "keyT": {
1161
+ "dtype": "float32",
1162
+ "shape": [1, 6, 64, 64],
1163
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1164
+ },
1165
+ "valueT": {
1166
+ "dtype": "float32",
1167
+ "shape": [1, 6, 64, 64],
1168
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1169
+ },
1170
+ "maskIndexT": { "dtype": "int32", "shape": [1, 64], "data": { "kind": "cycle", "values": [1, 0, 1] } }
1171
+ },
1172
+ "outputs": {
1173
+ "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 },
1174
+ "qkT": { "dtype": "float32", "shape": [1, 6, 1, 64], "tolerance": 0.00002 }
1175
+ }
1176
+ },
1177
+ {
1178
+ "name": "cross_single_split_mask_qk_kv64_d64_float16",
1179
+ "provenance": {
1180
+ "notes": "Single-partition cross attention emits the scaled, finitely masked pre-softmax QK row while directly normalizing its output."
1181
+ },
1182
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
1183
+ "inputs": {
1184
+ "queryT": {
1185
+ "dtype": "float16",
1186
+ "shape": [1, 1, 384],
1187
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1188
+ },
1189
+ "keyT": {
1190
+ "dtype": "float16",
1191
+ "shape": [1, 6, 64, 64],
1192
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1193
+ },
1194
+ "valueT": {
1195
+ "dtype": "float16",
1196
+ "shape": [1, 6, 64, 64],
1197
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1198
+ },
1199
+ "maskIndexT": { "dtype": "int32", "shape": [1, 64], "data": { "kind": "cycle", "values": [1, 0, 1] } }
1200
+ },
1201
+ "outputs": {
1202
+ "outputT": { "dtype": "float16", "shape": [1, 1, 384], "tolerance": 0.003 },
1203
+ "qkT": { "dtype": "float16", "shape": [1, 6, 1, 64], "tolerance": 0.003 }
1204
+ }
1205
+ },
1206
+ {
1207
+ "name": "cross_multi_split_mask_qk_kv257_d64_float32",
1208
+ "provenance": {
1209
+ "notes": "Cross-attention with 6 heads and head width 64, a 257-length key/value cache (256 plus one), a padding mask, and a custom mask_filter_value of -4. The diagnostic qkT output is requested alongside outputT."
1210
+ },
1211
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
1212
+ "inputs": {
1213
+ "queryT": {
1214
+ "dtype": "float32",
1215
+ "shape": [1, 1, 384],
1216
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1217
+ },
1218
+ "keyT": {
1219
+ "dtype": "float32",
1220
+ "shape": [1, 6, 257, 64],
1221
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1222
+ },
1223
+ "valueT": {
1224
+ "dtype": "float32",
1225
+ "shape": [1, 6, 257, 64],
1226
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1227
+ },
1228
+ "maskIndexT": { "dtype": "int32", "shape": [1, 257], "data": { "kind": "cycle", "values": [1, 0, 1] } }
1229
+ },
1230
+ "outputs": {
1231
+ "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 },
1232
+ "qkT": { "dtype": "float32", "shape": [1, 6, 1, 257], "tolerance": 0.00002 }
1233
+ }
1234
+ },
1235
+ {
1236
+ "name": "cross_multi_split_mask_qk_kv257_d64_float16",
1237
+ "provenance": {
1238
+ "notes": "Cross-attention with 6 heads and head width 64, a 257-length key/value cache (256 plus one), a padding mask, and a custom mask_filter_value of -4, with float16 query/key/value/output. The diagnostic qkT output is requested alongside outputT."
1239
+ },
1240
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
1241
+ "inputs": {
1242
+ "queryT": {
1243
+ "dtype": "float16",
1244
+ "shape": [1, 1, 384],
1245
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1246
+ },
1247
+ "keyT": {
1248
+ "dtype": "float16",
1249
+ "shape": [1, 6, 257, 64],
1250
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1251
+ },
1252
+ "valueT": {
1253
+ "dtype": "float16",
1254
+ "shape": [1, 6, 257, 64],
1255
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1256
+ },
1257
+ "maskIndexT": { "dtype": "int32", "shape": [1, 257], "data": { "kind": "cycle", "values": [1, 0, 1] } }
1258
+ },
1259
+ "outputs": {
1260
+ "outputT": { "dtype": "float16", "shape": [1, 1, 384], "tolerance": 0.003 },
1261
+ "qkT": { "dtype": "float16", "shape": [1, 6, 1, 257], "tolerance": 0.003 }
1262
+ }
1263
+ },
1264
+ {
1265
+ "name": "qk_policy_b1_h4_kv256_d512_f32",
1266
+ "provenance": {
1267
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1268
+ },
1269
+ "attrs": { "num_heads": 4 },
1270
+ "inputs": {
1271
+ "queryT": {
1272
+ "dtype": "float32",
1273
+ "shape": [1, 1, 2048],
1274
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1275
+ },
1276
+ "keyT": {
1277
+ "dtype": "float32",
1278
+ "shape": [1, 4, 256, 512],
1279
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1280
+ },
1281
+ "valueT": {
1282
+ "dtype": "float32",
1283
+ "shape": [1, 4, 256, 512],
1284
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1285
+ }
1286
+ },
1287
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 2048], "tolerance": 0.00002 } }
1288
+ },
1289
+ {
1290
+ "name": "qk_policy_b1_h2_kv256_d1024_f32",
1291
+ "provenance": {
1292
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1293
+ },
1294
+ "attrs": { "num_heads": 2 },
1295
+ "inputs": {
1296
+ "queryT": {
1297
+ "dtype": "float32",
1298
+ "shape": [1, 1, 2048],
1299
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1300
+ },
1301
+ "keyT": {
1302
+ "dtype": "float32",
1303
+ "shape": [1, 2, 256, 1024],
1304
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1305
+ },
1306
+ "valueT": {
1307
+ "dtype": "float32",
1308
+ "shape": [1, 2, 256, 1024],
1309
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1310
+ }
1311
+ },
1312
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 2048], "tolerance": 0.00002 } }
1313
+ },
1314
+ {
1315
+ "name": "qk_policy_b1_h1_kv256_d2048_f32",
1316
+ "provenance": {
1317
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1318
+ },
1319
+ "attrs": { "num_heads": 1 },
1320
+ "inputs": {
1321
+ "queryT": {
1322
+ "dtype": "float32",
1323
+ "shape": [1, 1, 2048],
1324
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1325
+ },
1326
+ "keyT": {
1327
+ "dtype": "float32",
1328
+ "shape": [1, 1, 256, 2048],
1329
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1330
+ },
1331
+ "valueT": {
1332
+ "dtype": "float32",
1333
+ "shape": [1, 1, 256, 2048],
1334
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1335
+ }
1336
+ },
1337
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 2048], "tolerance": 0.00002 } },
1338
+ "requires": {
1339
+ "limits": {
1340
+ "maxComputeInvocationsPerWorkgroup": 512,
1341
+ "maxComputeWorkgroupSizeX": 512,
1342
+ "maxComputeWorkgroupStorageSize": 22528
1343
+ }
1344
+ }
1345
+ },
1346
+ {
1347
+ "name": "qk_policy_b1_h15_kv257_d128_f32",
1348
+ "provenance": {
1349
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1350
+ },
1351
+ "attrs": { "num_heads": 15 },
1352
+ "inputs": {
1353
+ "queryT": {
1354
+ "dtype": "float32",
1355
+ "shape": [1, 1, 1920],
1356
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1357
+ },
1358
+ "keyT": {
1359
+ "dtype": "float32",
1360
+ "shape": [1, 15, 257, 128],
1361
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1362
+ },
1363
+ "valueT": {
1364
+ "dtype": "float32",
1365
+ "shape": [1, 15, 257, 128],
1366
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1367
+ }
1368
+ },
1369
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 1920], "tolerance": 0.00002 } }
1370
+ },
1371
+ {
1372
+ "name": "qk_policy_b1_h16_kv257_d128_f32",
1373
+ "provenance": {
1374
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1375
+ },
1376
+ "attrs": { "num_heads": 16 },
1377
+ "inputs": {
1378
+ "queryT": {
1379
+ "dtype": "float32",
1380
+ "shape": [1, 1, 2048],
1381
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1382
+ },
1383
+ "keyT": {
1384
+ "dtype": "float32",
1385
+ "shape": [1, 16, 257, 128],
1386
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1387
+ },
1388
+ "valueT": {
1389
+ "dtype": "float32",
1390
+ "shape": [1, 16, 257, 128],
1391
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1392
+ }
1393
+ },
1394
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 2048], "tolerance": 0.00002 } }
1395
+ },
1396
+ {
1397
+ "name": "qk_policy_b1_h17_kv257_d128_f32",
1398
+ "provenance": {
1399
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1400
+ },
1401
+ "attrs": { "num_heads": 17 },
1402
+ "inputs": {
1403
+ "queryT": {
1404
+ "dtype": "float32",
1405
+ "shape": [1, 1, 2176],
1406
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1407
+ },
1408
+ "keyT": {
1409
+ "dtype": "float32",
1410
+ "shape": [1, 17, 257, 128],
1411
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1412
+ },
1413
+ "valueT": {
1414
+ "dtype": "float32",
1415
+ "shape": [1, 17, 257, 128],
1416
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1417
+ }
1418
+ },
1419
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 2176], "tolerance": 0.00002 } }
1420
+ },
1421
+ {
1422
+ "name": "qk_policy_b1_h6_kv256_d32_f32",
1423
+ "provenance": {
1424
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1425
+ },
1426
+ "attrs": { "num_heads": 6 },
1427
+ "inputs": {
1428
+ "queryT": {
1429
+ "dtype": "float32",
1430
+ "shape": [1, 1, 192],
1431
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1432
+ },
1433
+ "keyT": {
1434
+ "dtype": "float32",
1435
+ "shape": [1, 6, 256, 32],
1436
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1437
+ },
1438
+ "valueT": {
1439
+ "dtype": "float32",
1440
+ "shape": [1, 6, 256, 32],
1441
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1442
+ }
1443
+ },
1444
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 192], "tolerance": 0.00002 } }
1445
+ },
1446
+ {
1447
+ "name": "qk_policy_b1_h6_kv256_d256_f32",
1448
+ "provenance": {
1449
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1450
+ },
1451
+ "attrs": { "num_heads": 6 },
1452
+ "inputs": {
1453
+ "queryT": {
1454
+ "dtype": "float32",
1455
+ "shape": [1, 1, 1536],
1456
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1457
+ },
1458
+ "keyT": {
1459
+ "dtype": "float32",
1460
+ "shape": [1, 6, 256, 256],
1461
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1462
+ },
1463
+ "valueT": {
1464
+ "dtype": "float32",
1465
+ "shape": [1, 6, 256, 256],
1466
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1467
+ }
1468
+ },
1469
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 1536], "tolerance": 0.00002 } }
1470
+ },
1471
+ {
1472
+ "name": "qk_policy_b1_h6_kv1500_d64_f32",
1473
+ "provenance": {
1474
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1475
+ },
1476
+ "attrs": { "num_heads": 6 },
1477
+ "inputs": {
1478
+ "queryT": {
1479
+ "dtype": "float32",
1480
+ "shape": [1, 1, 384],
1481
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1482
+ },
1483
+ "keyT": {
1484
+ "dtype": "float32",
1485
+ "shape": [1, 6, 1500, 64],
1486
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1487
+ },
1488
+ "valueT": {
1489
+ "dtype": "float32",
1490
+ "shape": [1, 6, 1500, 64],
1491
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1492
+ }
1493
+ },
1494
+ "outputs": { "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 } }
1495
+ },
1496
+ {
1497
+ "name": "qk_policy_b4_h6_kv1500_d128_f32",
1498
+ "provenance": {
1499
+ "notes": "QK policy boundary or head-width fixture, checked against the CPU reference at the f32 attention tolerance."
1500
+ },
1501
+ "attrs": { "num_heads": 6 },
1502
+ "inputs": {
1503
+ "queryT": {
1504
+ "dtype": "float32",
1505
+ "shape": [4, 1, 768],
1506
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1507
+ },
1508
+ "keyT": {
1509
+ "dtype": "float32",
1510
+ "shape": [4, 6, 1500, 128],
1511
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1512
+ },
1513
+ "valueT": {
1514
+ "dtype": "float32",
1515
+ "shape": [4, 6, 1500, 128],
1516
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1517
+ }
1518
+ },
1519
+ "outputs": { "outputT": { "dtype": "float32", "shape": [4, 1, 768], "tolerance": 0.00002 } }
1520
+ },
1521
+ {
1522
+ "name": "qk_policy_f32_query_f16_scores_kv64_d64",
1523
+ "provenance": {
1524
+ "notes": "Mixed-dtype diagnostic scores exercise the f32 QK reduction at the mixed-QK half-output tolerance, on both sides of the split boundary."
1525
+ },
1526
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
1527
+ "inputs": {
1528
+ "queryT": {
1529
+ "dtype": "float32",
1530
+ "shape": [1, 1, 384],
1531
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1532
+ },
1533
+ "keyT": {
1534
+ "dtype": "float32",
1535
+ "shape": [1, 6, 64, 64],
1536
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1537
+ },
1538
+ "valueT": {
1539
+ "dtype": "float32",
1540
+ "shape": [1, 6, 64, 64],
1541
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1542
+ },
1543
+ "maskIndexT": { "dtype": "int32", "shape": [1, 64], "data": { "kind": "cycle", "values": [1, 0, 1] } }
1544
+ },
1545
+ "outputs": {
1546
+ "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 },
1547
+ "qkT": { "dtype": "float16", "shape": [1, 6, 1, 64], "tolerance": 0.002 }
1548
+ }
1549
+ },
1550
+ {
1551
+ "name": "qk_policy_f32_query_f16_scores_kv257_d64",
1552
+ "provenance": {
1553
+ "notes": "Mixed-dtype diagnostic scores exercise the f32 QK reduction at the mixed-QK half-output tolerance, on both sides of the split boundary."
1554
+ },
1555
+ "attrs": { "num_heads": 6, "output_qk": 1, "mask_filter_value": -4 },
1556
+ "inputs": {
1557
+ "queryT": {
1558
+ "dtype": "float32",
1559
+ "shape": [1, 1, 384],
1560
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
1561
+ },
1562
+ "keyT": {
1563
+ "dtype": "float32",
1564
+ "shape": [1, 6, 257, 64],
1565
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23 }
1566
+ },
1567
+ "valueT": {
1568
+ "dtype": "float32",
1569
+ "shape": [1, 6, 257, 64],
1570
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "offset": 0.7 }
1571
+ },
1572
+ "maskIndexT": { "dtype": "int32", "shape": [1, 257], "data": { "kind": "cycle", "values": [1, 0, 1] } }
1573
+ },
1574
+ "outputs": {
1575
+ "outputT": { "dtype": "float32", "shape": [1, 1, 384], "tolerance": 0.00002 },
1576
+ "qkT": { "dtype": "float16", "shape": [1, 6, 1, 257], "tolerance": 0.002 }
1577
+ }
1578
+ }
1579
+ ]
1580
+ }