Xenova HF Staff commited on
Commit
2500260
·
verified ·
1 Parent(s): fd6745d

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -74,7 +74,7 @@ Every implementation variant requires `shader-f16`; the package has no variant-l
74
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
75
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
76
  - [`test.json`](build/webgpu/test.json) — correctness cases
77
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
78
  - [`attn-flash-decode-splitk-merge.wgsl.jinja`](build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja)
79
  - [`paged-attention.wgsl.jinja`](build/webgpu/paged-attention.wgsl.jinja)
80
  - [`paged-scatter-kv.wgsl.jinja`](build/webgpu/paged-scatter-kv.wgsl.jinja)
@@ -82,7 +82,7 @@ Every implementation variant requires `shader-f16`; the package has no variant-l
82
  ## Use with `@huggingface/kernels`
83
 
84
  ```sh
85
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
86
  ```
87
 
88
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
74
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
75
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
76
  - [`test.json`](build/webgpu/test.json) — correctness cases
77
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
78
  - [`attn-flash-decode-splitk-merge.wgsl.jinja`](build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja)
79
  - [`paged-attention.wgsl.jinja`](build/webgpu/paged-attention.wgsl.jinja)
80
  - [`paged-scatter-kv.wgsl.jinja`](build/webgpu/paged-scatter-kv.wgsl.jinja)
 
82
  ## Use with `@huggingface/kernels`
83
 
84
  ```sh
85
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
86
  ```
87
 
88
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
build/webgpu/attn-flash-decode-splitk-merge.wgsl.jinja CHANGED
@@ -1,9 +1,3 @@
1
- {% if scalar == "f16" %}
2
- enable f16;
3
- {% endif %}
4
- {% if splitQueries is not defined %}{% set splitQueries = false %}{% endif %}
5
- {% if hasGate is not defined %}{% set hasGate = false %}{% endif %}
6
- {% set qSeq = qSeq | default(0) %}
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
  // Split-K flash decode, pass 2 of 2. Combines the per-split un-normalized online
@@ -19,9 +13,6 @@ enable f16;
19
  const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
20
  const Q_HEADS: u32 = {{ qNumHeads }}u;
21
  const NUM_SPLITS: u32 = {{ numSplits }}u;
22
- {% if splitQueries %}
23
- const Q_SEQ: u32 = {{ qSeq }}u;
24
- {% endif %}
25
  const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
26
  // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
27
  // `m - m` finite so an empty lane / all--inf row contributes the exact
@@ -42,6 +33,7 @@ fn shifted_value(value: f32, maxValue: f32) -> f32 {
42
  let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
43
  return select(value - maxValue, 0.0, equalFiniteMax);
44
  }
 
45
  fn exp_shift(value: f32, maxValue: f32) -> f32 {
46
  return exp(shifted_value(value, maxValue));
47
  }
@@ -50,21 +42,14 @@ fn main(
50
  @builtin(workgroup_id) wg: vec3<u32>,
51
  @builtin(local_invocation_id) lid: vec3<u32>
52
  ) {
53
- {% if splitQueries %}
54
- let queryToken = wg.x;
55
- {% endif %}
56
  let h = wg.y;
57
  let b = wg.z;
58
  let d4 = lid.x;
59
- if (h >= Q_HEADS || d4 >= HEAD_DIM_V4{% if splitQueries %} || queryToken >= Q_SEQ{% endif %}) {
60
  return;
61
  }
62
 
63
- {% if splitQueries %}
64
- let mdBase = ((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS;
65
- {% else %}
66
  let mdBase = (b * Q_HEADS + h) * NUM_SPLITS;
67
- {% endif %}
68
  var globalMax = -FLT_MAX;
69
  for (var s: u32 = 0u; s < NUM_SPLITS; s = s + 1u) {
70
  globalMax = max(globalMax, partial_stats[mdBase + s].x);
@@ -75,28 +60,15 @@ fn main(
75
  let stats = partial_stats[mdBase + s];
76
  let w = exp_shift(stats.x, globalMax);
77
  globalDenom = globalDenom + stats.y * w;
78
- {% if splitQueries %}
79
- let pBase = (((b * Q_SEQ + queryToken) * Q_HEADS + h) * NUM_SPLITS + s) * HEAD_DIM_V4;
80
- {% else %}
81
  let pBase = ((b * Q_HEADS + h) * NUM_SPLITS + s) * HEAD_DIM_V4;
82
- {% endif %}
83
  outV = outV + partial_out[pBase + d4] * w;
84
  }
85
 
86
- {% if splitQueries %}
87
- let qBaseV4 = (b * Q_SEQ + queryToken) * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
88
- {% else %}
89
  let qBaseV4 = b * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
90
- {% endif %}
91
  // A split that covers no keys writes vec2(-FLT_MAX, 0), so when every split is empty —
92
  // a query whose window admits nothing — globalMax stays -FLT_MAX, exp_shift(x, x) is 1,
93
  // and globalDenom lands on exactly 0. Emit 0 rather than dividing so an all-empty
94
  // row has a defined zero result.
95
  var outValue = select(vec4<f32>(0.0), outV / globalDenom, globalDenom > 0.0);
96
- {% if hasGate %}
97
- // The gated route multiplies the normalized attention output elementwise by its gate.
98
- let gateV = vec4<f32>(gate[qBaseV4 + d4]);
99
- outValue = outValue * (vec4<f32>(1.0) / (vec4<f32>(1.0) + exp(-gateV)));
100
- {% endif %}
101
  output[qBaseV4 + d4] = vec4<{{ scalar }}>(outValue);
102
  }
 
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Split-K flash decode, pass 2 of 2. Combines the per-split un-normalized online
 
13
  const HEAD_DIM_V4: u32 = {{ headDimV4 }}u;
14
  const Q_HEADS: u32 = {{ qNumHeads }}u;
15
  const NUM_SPLITS: u32 = {{ numSplits }}u;
 
 
 
16
  const Q_HIDDEN_V4: u32 = {{ qHiddenV4 }}u;
17
  // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
18
  // `m - m` finite so an empty lane / all--inf row contributes the exact
 
33
  let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
34
  return select(value - maxValue, 0.0, equalFiniteMax);
35
  }
36
+
37
  fn exp_shift(value: f32, maxValue: f32) -> f32 {
38
  return exp(shifted_value(value, maxValue));
39
  }
 
42
  @builtin(workgroup_id) wg: vec3<u32>,
43
  @builtin(local_invocation_id) lid: vec3<u32>
44
  ) {
 
 
 
45
  let h = wg.y;
46
  let b = wg.z;
47
  let d4 = lid.x;
48
+ if (h >= Q_HEADS || d4 >= HEAD_DIM_V4) {
49
  return;
50
  }
51
 
 
 
 
52
  let mdBase = (b * Q_HEADS + h) * NUM_SPLITS;
 
53
  var globalMax = -FLT_MAX;
54
  for (var s: u32 = 0u; s < NUM_SPLITS; s = s + 1u) {
55
  globalMax = max(globalMax, partial_stats[mdBase + s].x);
 
60
  let stats = partial_stats[mdBase + s];
61
  let w = exp_shift(stats.x, globalMax);
62
  globalDenom = globalDenom + stats.y * w;
 
 
 
63
  let pBase = ((b * Q_HEADS + h) * NUM_SPLITS + s) * HEAD_DIM_V4;
 
64
  outV = outV + partial_out[pBase + d4] * w;
65
  }
66
 
 
 
 
67
  let qBaseV4 = b * Q_HIDDEN_V4 + h * HEAD_DIM_V4;
 
68
  // A split that covers no keys writes vec2(-FLT_MAX, 0), so when every split is empty —
69
  // a query whose window admits nothing — globalMax stays -FLT_MAX, exp_shift(x, x) is 1,
70
  // and globalDenom lands on exactly 0. Emit 0 rather than dividing so an all-empty
71
  // row has a defined zero result.
72
  var outValue = select(vec4<f32>(0.0), outV / globalDenom, globalDenom > 0.0);
 
 
 
 
 
73
  output[qBaseV4 + d4] = vec4<{{ scalar }}>(outValue);
74
  }
build/webgpu/bench.json CHANGED
@@ -38,7 +38,7 @@
38
  "name": "paged-decode-llama-b16-past2048-h32kv32-d128",
39
  "preset": "model",
40
  "provenance": {
41
- "notes": "Llama class defaults (32 query heads, no GQA, head_dim 128) decoding 16 concurrent sequences with 2048 tokens of paged KV each."
42
  },
43
  "vars": { "dtype": "float16", "batch": 16, "past": 2048, "heads": 32, "kvHeads": 32, "headDim": 128 },
44
  "attrs": { "num_heads": 32, "kv_num_heads": 32 },
@@ -57,12 +57,9 @@
57
  "cumulativeSequenceLengthT": {
58
  "shape": [17],
59
  "dtype": "int32",
60
- "dist": "linearMod",
61
- "seed": 7605,
62
- "step": 1,
63
- "mod": 17
64
  },
65
- "pastSeqlensT": { "shape": [16], "dtype": "int32", "dist": "constant", "value": 2048 },
66
  "blockTableT": {
67
  "shape": [16, 128],
68
  "dtype": "int32",
@@ -81,7 +78,7 @@
81
  "name": "paged-decode-mistral-b16-past4096-h32kv8-d128",
82
  "preset": "model",
83
  "provenance": {
84
- "notes": "Mistral class defaults (32 query heads, 8 key/value heads, head_dim 128) at 4096 tokens of history -- the GQA case, where the cache is a quarter the size for the same context."
85
  },
86
  "vars": { "dtype": "float16", "batch": 16, "past": 4096, "heads": 32, "kvHeads": 8, "headDim": 128 },
87
  "attrs": { "num_heads": 32, "kv_num_heads": 8 },
@@ -94,12 +91,9 @@
94
  "cumulativeSequenceLengthT": {
95
  "shape": [17],
96
  "dtype": "int32",
97
- "dist": "linearMod",
98
- "seed": 7705,
99
- "step": 1,
100
- "mod": 17
101
  },
102
- "pastSeqlensT": { "shape": [16], "dtype": "int32", "dist": "constant", "value": 4096 },
103
  "blockTableT": {
104
  "shape": [16, 256],
105
  "dtype": "int32",
@@ -118,7 +112,7 @@
118
  "name": "paged-decode-gemma3-b8-past2048-h8kv4-d256",
119
  "preset": "model",
120
  "provenance": {
121
- "notes": "Gemma3 class defaults (8 query heads, 4 key/value heads, head_dim 256) -- few heads but the widest head dimension in this set."
122
  },
123
  "vars": { "dtype": "float16", "batch": 8, "past": 2048, "heads": 8, "kvHeads": 4, "headDim": 256 },
124
  "attrs": { "num_heads": 8, "kv_num_heads": 4 },
@@ -131,12 +125,9 @@
131
  "cumulativeSequenceLengthT": {
132
  "shape": [9],
133
  "dtype": "int32",
134
- "dist": "linearMod",
135
- "seed": 7805,
136
- "step": 1,
137
- "mod": 9
138
  },
139
- "pastSeqlensT": { "shape": [8], "dtype": "int32", "dist": "constant", "value": 2048 },
140
  "blockTableT": {
141
  "shape": [8, 128],
142
  "dtype": "int32",
@@ -150,6 +141,92 @@
150
  "bench": {
151
  "metrics": [{ "type": "bandwidth", "value": "2 * args.batch * args.kvHeads * args.past * args.headDim * 2" }]
152
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
  ]
155
  }
 
38
  "name": "paged-decode-llama-b16-past2048-h32kv32-d128",
39
  "preset": "model",
40
  "provenance": {
41
+ "notes": "Llama class defaults (32 query heads, no GQA, head_dim 128) decoding 16 concurrent sequences with 2048 tokens of paged KV each. Context counts include this step: past_seqlens is context - 1 and the exclusive prefix [0, ..., batch] assigns one new token per sequence. All logical blocks map to distinct physical blocks, including the final append slot."
42
  },
43
  "vars": { "dtype": "float16", "batch": 16, "past": 2048, "heads": 32, "kvHeads": 32, "headDim": 128 },
44
  "attrs": { "num_heads": 32, "kv_num_heads": 32 },
 
57
  "cumulativeSequenceLengthT": {
58
  "shape": [17],
59
  "dtype": "int32",
60
+ "data": { "kind": "linspace", "start": 0, "end": 16 }
 
 
 
61
  },
62
+ "pastSeqlensT": { "shape": [16], "dtype": "int32", "dist": "constant", "value": 2047 },
63
  "blockTableT": {
64
  "shape": [16, 128],
65
  "dtype": "int32",
 
78
  "name": "paged-decode-mistral-b16-past4096-h32kv8-d128",
79
  "preset": "model",
80
  "provenance": {
81
+ "notes": "Mistral class defaults (32 query heads, 8 key/value heads, head_dim 128) at 4096 tokens per sequence including the current token -- the GQA case, where the cache is a quarter the size for the same context. Context counts include this step: past_seqlens is context - 1 and the exclusive prefix [0, ..., batch] assigns one new token per sequence. All logical blocks map to distinct physical blocks, including the final append slot."
82
  },
83
  "vars": { "dtype": "float16", "batch": 16, "past": 4096, "heads": 32, "kvHeads": 8, "headDim": 128 },
84
  "attrs": { "num_heads": 32, "kv_num_heads": 8 },
 
91
  "cumulativeSequenceLengthT": {
92
  "shape": [17],
93
  "dtype": "int32",
94
+ "data": { "kind": "linspace", "start": 0, "end": 16 }
 
 
 
95
  },
96
+ "pastSeqlensT": { "shape": [16], "dtype": "int32", "dist": "constant", "value": 4095 },
97
  "blockTableT": {
98
  "shape": [16, 256],
99
  "dtype": "int32",
 
112
  "name": "paged-decode-gemma3-b8-past2048-h8kv4-d256",
113
  "preset": "model",
114
  "provenance": {
115
+ "notes": "Gemma3 class defaults (8 query heads, 4 key/value heads, head_dim 256) -- few heads but the widest head dimension in this set. Context counts include this step: past_seqlens is context - 1 and the exclusive prefix [0, ..., batch] assigns one new token per sequence. All logical blocks map to distinct physical blocks, including the final append slot."
116
  },
117
  "vars": { "dtype": "float16", "batch": 8, "past": 2048, "heads": 8, "kvHeads": 4, "headDim": 256 },
118
  "attrs": { "num_heads": 8, "kv_num_heads": 4 },
 
125
  "cumulativeSequenceLengthT": {
126
  "shape": [9],
127
  "dtype": "int32",
128
+ "data": { "kind": "linspace", "start": 0, "end": 8 }
 
 
 
129
  },
130
+ "pastSeqlensT": { "shape": [8], "dtype": "int32", "dist": "constant", "value": 2047 },
131
  "blockTableT": {
132
  "shape": [8, 128],
133
  "dtype": "int32",
 
141
  "bench": {
142
  "metrics": [{ "type": "bandwidth", "value": "2 * args.batch * args.kvHeads * args.past * args.headDim * 2" }]
143
  }
144
+ },
145
+ {
146
+ "name": "scatter-fold-wg1-independent-sequences",
147
+ "provenance": {
148
+ "notes": "A large scatter workload crosses the WebGPU dispatch-grid limit. One-token sequences bound attention cost; nonzero dyadic cycles expose missing tail writes and misplaced cache/value elements."
149
+ },
150
+ "attrs": { "num_heads": 8, "kv_num_heads": 8 },
151
+ "tunables": { "WORKGROUP_SIZE": 64, "SCATTER_WORKGROUP_SIZE": 1 },
152
+ "inputs": {
153
+ "queryT": { "dtype": "float16", "shape": [64, 1024], "data": { "kind": "constant", "value": 0.0 } },
154
+ "keyT": {
155
+ "dtype": "float16",
156
+ "shape": [64, 1024],
157
+ "data": { "kind": "cycle", "values": [1.0, 2.0, 4.0, -1.0, -2.0] }
158
+ },
159
+ "valueT": {
160
+ "dtype": "float16",
161
+ "shape": [64, 1024],
162
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] }
163
+ },
164
+ "keyCacheT": { "dtype": "float16", "shape": [64, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
165
+ "valueCacheT": { "dtype": "float16", "shape": [64, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
166
+ "cumulativeSequenceLengthT": {
167
+ "dtype": "int32",
168
+ "shape": [65],
169
+ "data": { "kind": "linspace", "start": 0, "end": 64 }
170
+ },
171
+ "pastSeqlensT": { "dtype": "int32", "shape": [64], "data": { "kind": "constant", "value": 0 } },
172
+ "blockTableT": { "dtype": "int32", "shape": [64, 1], "data": { "kind": "linspace", "start": 0, "end": 63 } }
173
+ },
174
+ "outputs": {
175
+ "outputT": { "dtype": "float16", "shape": [64, 1024] },
176
+ "keyCacheT": { "dtype": "float16", "shape": [64, 1, 8, 128] },
177
+ "valueCacheT": { "dtype": "float16", "shape": [64, 1, 8, 128] }
178
+ },
179
+ "preset": "edge",
180
+ "vars": { "tokens": 64, "heads": 8, "headDim": 128 },
181
+ "tunableSpace": { "WORKGROUP_SIZE": [64], "SCATTER_WORKGROUP_SIZE": [1] },
182
+ "bench": {
183
+ "metrics": [
184
+ { "type": "bandwidth", "value": "6 * args.tokens * args.heads * args.headDim * 2", "provenance": "logical" }
185
+ ]
186
+ }
187
+ },
188
+ {
189
+ "name": "scatter-fold-wg4-independent-sequences",
190
+ "provenance": {
191
+ "notes": "A large scatter workload crosses the WebGPU dispatch-grid limit. One-token sequences bound attention cost; nonzero dyadic cycles expose missing tail writes and misplaced cache/value elements."
192
+ },
193
+ "attrs": { "num_heads": 8, "kv_num_heads": 8 },
194
+ "tunables": { "WORKGROUP_SIZE": 64, "SCATTER_WORKGROUP_SIZE": 4 },
195
+ "inputs": {
196
+ "queryT": { "dtype": "float16", "shape": [256, 1024], "data": { "kind": "constant", "value": 0.0 } },
197
+ "keyT": {
198
+ "dtype": "float16",
199
+ "shape": [256, 1024],
200
+ "data": { "kind": "cycle", "values": [1.0, 2.0, 4.0, -1.0, -2.0] }
201
+ },
202
+ "valueT": {
203
+ "dtype": "float16",
204
+ "shape": [256, 1024],
205
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] }
206
+ },
207
+ "keyCacheT": { "dtype": "float16", "shape": [256, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
208
+ "valueCacheT": { "dtype": "float16", "shape": [256, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
209
+ "cumulativeSequenceLengthT": {
210
+ "dtype": "int32",
211
+ "shape": [257],
212
+ "data": { "kind": "linspace", "start": 0, "end": 256 }
213
+ },
214
+ "pastSeqlensT": { "dtype": "int32", "shape": [256], "data": { "kind": "constant", "value": 0 } },
215
+ "blockTableT": { "dtype": "int32", "shape": [256, 1], "data": { "kind": "linspace", "start": 0, "end": 255 } }
216
+ },
217
+ "outputs": {
218
+ "outputT": { "dtype": "float16", "shape": [256, 1024] },
219
+ "keyCacheT": { "dtype": "float16", "shape": [256, 1, 8, 128] },
220
+ "valueCacheT": { "dtype": "float16", "shape": [256, 1, 8, 128] }
221
+ },
222
+ "preset": "edge",
223
+ "vars": { "tokens": 256, "heads": 8, "headDim": 128 },
224
+ "tunableSpace": { "WORKGROUP_SIZE": [64], "SCATTER_WORKGROUP_SIZE": [4] },
225
+ "bench": {
226
+ "metrics": [
227
+ { "type": "bandwidth", "value": "6 * args.tokens * args.heads * args.headDim * 2", "provenance": "logical" }
228
+ ]
229
+ }
230
  }
231
  ]
232
  }
build/webgpu/manifest.json CHANGED
@@ -86,51 +86,43 @@
86
  "headDim": "headSize",
87
  "qNumHeads": "attrs.num_heads",
88
  "qHiddenV4": "qHidden / 4",
89
- "hasBias": false,
90
  "outVec": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\""
91
  },
92
  "when": ["pagedContractOk", "dispatchFits"],
93
  "bindings": {
94
- "key": { "arg": "keyT", "buffer": "read-only-storage", "elementType": "$aScalar" },
95
- "value": { "arg": "valueT", "buffer": "read-only-storage", "elementType": "$aScalar" },
96
- "key_cache": { "arg": "keyCacheT", "buffer": "storage", "elementType": "$aScalar" },
97
- "value_cache": { "arg": "valueCacheT", "buffer": "storage", "elementType": "$aScalar" },
98
- "cumulative_sequence_length": {
99
- "arg": "cumulativeSequenceLengthT",
100
- "buffer": "read-only-storage",
101
- "elementType": "i32"
102
- },
103
- "past_seqlens": { "arg": "pastSeqlensT", "buffer": "read-only-storage", "elementType": "i32" },
104
- "block_table": { "arg": "blockTableT", "buffer": "read-only-storage", "elementType": "i32" },
105
  "params": {
106
- "buffer": "uniform",
107
  "struct": [
108
  { "name": "batchSize", "type": "u32", "value": "batchSize" },
109
  { "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }
110
  ]
111
  },
112
- "slot_mapping": { "arg": "slotMappingT", "buffer": "read-only-storage", "elementType": "i32" },
113
- "params_2": {
114
  "name": "params",
115
- "buffer": "uniform",
116
  "struct": [{ "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }]
117
  },
118
- "query": { "arg": "queryT", "buffer": "read-only-storage", "elementType": "$aScalar" },
119
- "key_cache_2": {
120
  "arg": "keyCacheT",
121
  "name": "key_cache",
122
  "buffer": "read-only-storage",
123
  "elementType": "$cacheVec"
124
  },
125
- "value_cache_2": {
126
  "arg": "valueCacheT",
127
  "name": "value_cache",
128
  "buffer": "read-only-storage",
129
  "elementType": "$cacheVec"
130
  },
131
- "params_3": {
132
  "name": "params",
133
- "buffer": "uniform",
134
  "struct": [
135
  { "name": "batchSize", "type": "u32", "value": "batchSize" },
136
  { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
@@ -167,14 +159,14 @@
167
  "derive": { "splitK": true },
168
  "bindings": [
169
  "query",
170
- "key_cache_2",
171
- "value_cache_2",
172
  "cumulative_sequence_length",
173
  "past_seqlens",
174
  "block_table",
175
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
176
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
177
- "params_3"
178
  ],
179
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
180
  },
@@ -224,13 +216,13 @@
224
  "shader": "paged-attention.wgsl.jinja",
225
  "bindings": [
226
  "query",
227
- "key_cache_2",
228
- "value_cache_2",
229
  "cumulative_sequence_length",
230
  "past_seqlens",
231
  "block_table",
232
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
233
- "params_3"
234
  ],
235
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
236
  }
@@ -251,7 +243,7 @@
251
  "id": "scatter",
252
  "name": "PagedAttention.ScatterKV",
253
  "shader": "paged-scatter-kv.wgsl.jinja",
254
- "bindings": ["key", "value", "key_cache", "value_cache", "slot_mapping", "params_2"],
255
  "dispatch": {
256
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
257
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
@@ -265,14 +257,14 @@
265
  "derive": { "splitK": true },
266
  "bindings": [
267
  "query",
268
- "key_cache_2",
269
- "value_cache_2",
270
  "cumulative_sequence_length",
271
  "past_seqlens",
272
  "block_table",
273
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
274
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
275
- "params_3"
276
  ],
277
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
278
  },
@@ -309,7 +301,7 @@
309
  "id": "scatter",
310
  "name": "PagedAttention.ScatterKV",
311
  "shader": "paged-scatter-kv.wgsl.jinja",
312
- "bindings": ["key", "value", "key_cache", "value_cache", "slot_mapping", "params_2"],
313
  "dispatch": {
314
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
315
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
@@ -322,13 +314,13 @@
322
  "shader": "paged-attention.wgsl.jinja",
323
  "bindings": [
324
  "query",
325
- "key_cache_2",
326
- "value_cache_2",
327
  "cumulative_sequence_length",
328
  "past_seqlens",
329
  "block_table",
330
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
331
- "params_3"
332
  ],
333
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
334
  }
@@ -363,14 +355,14 @@
363
  "derive": { "splitK": true },
364
  "bindings": [
365
  "query",
366
- "key_cache_2",
367
- "value_cache_2",
368
  "cumulative_sequence_length",
369
  "past_seqlens",
370
  "block_table",
371
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
372
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
373
- "params_3"
374
  ],
375
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
376
  },
@@ -420,13 +412,13 @@
420
  "shader": "paged-attention.wgsl.jinja",
421
  "bindings": [
422
  "query",
423
- "key_cache_2",
424
- "value_cache_2",
425
  "cumulative_sequence_length",
426
  "past_seqlens",
427
  "block_table",
428
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
429
- "params_3"
430
  ],
431
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
432
  }
@@ -447,7 +439,7 @@
447
  "id": "scatter",
448
  "name": "PagedAttention.ScatterKV",
449
  "shader": "paged-scatter-kv.wgsl.jinja",
450
- "bindings": ["query", "key_cache", "value_cache", "slot_mapping", "params_2"],
451
  "dispatch": {
452
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
453
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
@@ -461,14 +453,14 @@
461
  "derive": { "splitK": true },
462
  "bindings": [
463
  "query",
464
- "key_cache_2",
465
- "value_cache_2",
466
  "cumulative_sequence_length",
467
  "past_seqlens",
468
  "block_table",
469
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
470
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
471
- "params_3"
472
  ],
473
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
474
  },
@@ -505,7 +497,7 @@
505
  "id": "scatter",
506
  "name": "PagedAttention.ScatterKV",
507
  "shader": "paged-scatter-kv.wgsl.jinja",
508
- "bindings": ["query", "key_cache", "value_cache", "slot_mapping", "params_2"],
509
  "dispatch": {
510
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
511
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
@@ -518,13 +510,13 @@
518
  "shader": "paged-attention.wgsl.jinja",
519
  "bindings": [
520
  "query",
521
- "key_cache_2",
522
- "value_cache_2",
523
  "cumulative_sequence_length",
524
  "past_seqlens",
525
  "block_table",
526
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
527
- "params_3"
528
  ],
529
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
530
  }
 
86
  "headDim": "headSize",
87
  "qNumHeads": "attrs.num_heads",
88
  "qHiddenV4": "qHidden / 4",
 
89
  "outVec": "\"vec4<f16>\" if dtypes.T == \"f16\" else \"vec4<f32>\""
90
  },
91
  "when": ["pagedContractOk", "dispatchFits"],
92
  "bindings": {
93
+ "key": { "arg": "keyT", "elementType": "$aScalar" },
94
+ "value": { "arg": "valueT", "elementType": "$aScalar" },
95
+ "key_cache": { "arg": "keyCacheT", "elementType": "$aScalar" },
96
+ "value_cache": { "arg": "valueCacheT", "elementType": "$aScalar" },
97
+ "cumulative_sequence_length": { "arg": "cumulativeSequenceLengthT", "elementType": "i32" },
98
+ "past_seqlens": { "arg": "pastSeqlensT", "elementType": "i32" },
99
+ "block_table": { "arg": "blockTableT", "elementType": "i32" },
 
 
 
 
100
  "params": {
 
101
  "struct": [
102
  { "name": "batchSize", "type": "u32", "value": "batchSize" },
103
  { "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }
104
  ]
105
  },
106
+ "slot_mapping": { "arg": "slotMappingT", "elementType": "i32" },
107
+ "params_scatter": {
108
  "name": "params",
 
109
  "struct": [{ "name": "scatterCount", "type": "u32", "value": "tokenCount * attrs.kv_num_heads * headSize" }]
110
  },
111
+ "query": { "arg": "queryT", "elementType": "$aScalar" },
112
+ "key_cache_main": {
113
  "arg": "keyCacheT",
114
  "name": "key_cache",
115
  "buffer": "read-only-storage",
116
  "elementType": "$cacheVec"
117
  },
118
+ "value_cache_main": {
119
  "arg": "valueCacheT",
120
  "name": "value_cache",
121
  "buffer": "read-only-storage",
122
  "elementType": "$cacheVec"
123
  },
124
+ "params_main": {
125
  "name": "params",
 
126
  "struct": [
127
  { "name": "batchSize", "type": "u32", "value": "batchSize" },
128
  { "name": "scale", "type": "f32", "value": "attrs.scale if has(attrs, \"scale\") else 0" }
 
159
  "derive": { "splitK": true },
160
  "bindings": [
161
  "query",
162
+ "key_cache_main",
163
+ "value_cache_main",
164
  "cumulative_sequence_length",
165
  "past_seqlens",
166
  "block_table",
167
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
168
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
169
+ "params_main"
170
  ],
171
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
172
  },
 
216
  "shader": "paged-attention.wgsl.jinja",
217
  "bindings": [
218
  "query",
219
+ "key_cache_main",
220
+ "value_cache_main",
221
  "cumulative_sequence_length",
222
  "past_seqlens",
223
  "block_table",
224
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
225
+ "params_main"
226
  ],
227
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
228
  }
 
243
  "id": "scatter",
244
  "name": "PagedAttention.ScatterKV",
245
  "shader": "paged-scatter-kv.wgsl.jinja",
246
+ "bindings": ["key", "value", "key_cache", "value_cache", "slot_mapping", "params_scatter"],
247
  "dispatch": {
248
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
249
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
 
257
  "derive": { "splitK": true },
258
  "bindings": [
259
  "query",
260
+ "key_cache_main",
261
+ "value_cache_main",
262
  "cumulative_sequence_length",
263
  "past_seqlens",
264
  "block_table",
265
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
266
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
267
+ "params_main"
268
  ],
269
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
270
  },
 
301
  "id": "scatter",
302
  "name": "PagedAttention.ScatterKV",
303
  "shader": "paged-scatter-kv.wgsl.jinja",
304
+ "bindings": ["key", "value", "key_cache", "value_cache", "slot_mapping", "params_scatter"],
305
  "dispatch": {
306
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
307
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
 
314
  "shader": "paged-attention.wgsl.jinja",
315
  "bindings": [
316
  "query",
317
+ "key_cache_main",
318
+ "value_cache_main",
319
  "cumulative_sequence_length",
320
  "past_seqlens",
321
  "block_table",
322
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
323
+ "params_main"
324
  ],
325
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
326
  }
 
355
  "derive": { "splitK": true },
356
  "bindings": [
357
  "query",
358
+ "key_cache_main",
359
+ "value_cache_main",
360
  "cumulative_sequence_length",
361
  "past_seqlens",
362
  "block_table",
363
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
364
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
365
+ "params_main"
366
  ],
367
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
368
  },
 
412
  "shader": "paged-attention.wgsl.jinja",
413
  "bindings": [
414
  "query",
415
+ "key_cache_main",
416
+ "value_cache_main",
417
  "cumulative_sequence_length",
418
  "past_seqlens",
419
  "block_table",
420
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
421
+ "params_main"
422
  ],
423
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
424
  }
 
439
  "id": "scatter",
440
  "name": "PagedAttention.ScatterKV",
441
  "shader": "paged-scatter-kv.wgsl.jinja",
442
+ "bindings": ["query", "key_cache", "value_cache", "slot_mapping", "params_scatter"],
443
  "dispatch": {
444
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
445
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
 
453
  "derive": { "splitK": true },
454
  "bindings": [
455
  "query",
456
+ "key_cache_main",
457
+ "value_cache_main",
458
  "cumulative_sequence_length",
459
  "past_seqlens",
460
  "block_table",
461
  { "scratch": "partialOut", "name": "partial_out", "elementType": "vec4<f32>" },
462
  { "scratch": "partialStats", "name": "partial_stats", "elementType": "vec2<f32>" },
463
+ "params_main"
464
  ],
465
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads", "z": "numSplits" }
466
  },
 
497
  "id": "scatter",
498
  "name": "PagedAttention.ScatterKV",
499
  "shader": "paged-scatter-kv.wgsl.jinja",
500
+ "bindings": ["query", "key_cache", "value_cache", "slot_mapping", "params_scatter"],
501
  "dispatch": {
502
  "x": "min(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
503
  "y": "ceilDiv(ceilDiv((tokenCount * attrs.kv_num_heads * headSize), (tunables.SCATTER_WORKGROUP_SIZE)), 65535)",
 
510
  "shader": "paged-attention.wgsl.jinja",
511
  "bindings": [
512
  "query",
513
+ "key_cache_main",
514
+ "value_cache_main",
515
  "cumulative_sequence_length",
516
  "past_seqlens",
517
  "block_table",
518
  { "arg": "outputT", "name": "output", "elementType": "$aScalar" },
519
+ "params_main"
520
  ],
521
  "dispatch": { "x": "tokenCount", "y": "attrs.kv_num_heads" }
522
  }
build/webgpu/metadata.json CHANGED
@@ -1,23 +1,23 @@
1
  {
2
  "name": "com.microsoft.PagedAttention",
3
- "id": "_com_microsoft_pagedattention_webgpu_a50bcf6",
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": "uOgPgm5HioxH0ha2Z7Q0b/8qESsCmwKAS/198ogYX3I=",
11
- "bench.json": "n1VW3Kl/whbn6CsIdfs0MHNrvUTskejhRkd5jqAAGmk=",
12
- "manifest.json": "T7kLnpUD+K08/2E51GlZEK8v9YsGXliBIVnku1LRgRM=",
13
- "paged-attention.wgsl.jinja": "NS4JduPGQ8x2YxgIap9fKuggGtyqse0u503A7rvzSM4=",
14
- "paged-scatter-kv.wgsl.jinja": "EEb8eRJICdQOiQPsThu7rGm97W8iYFFH5zL3vArnQ8Q=",
15
- "test.json": "UtJ6CP4M4fFlZm0lZdIrcX+8AxB/5gIC88fXZb6Iw5Q="
16
  }
17
  },
18
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
19
  "webgpu": {
20
- "manifestSpec": "2.0",
21
  "variants": {
22
  "separate_derived_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "paged-attention.wgsl.jinja", "paged-scatter-kv.wgsl.jinja"],
23
  "separate_derived": ["paged-attention.wgsl.jinja", "paged-scatter-kv.wgsl.jinja"],
 
1
  {
2
  "name": "com.microsoft.PagedAttention",
3
+ "id": "_com_microsoft_pagedattention_webgpu_bcb1627",
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": "P0yN9VRWOBKM6lMTWKDX7eKuxcmVjj3YX+eRlYgyG0w=",
11
+ "bench.json": "wiVFqyO9vEyYhD/i3BuWEwEMtorPGtDpjrJQSTX+VEU=",
12
+ "manifest.json": "RVVRT3P/Ix1RKhWszk/4atE5P78+OEHtHAkSyDYe1v0=",
13
+ "paged-attention.wgsl.jinja": "Tkqnp3/PxodwRygVuawTRrPNrS2WDP+n/IYU6693Hog=",
14
+ "paged-scatter-kv.wgsl.jinja": "Xq8l6iRmNLXlx510ylhkcIj4AQSbb5jM5M0/A1IOp7Q=",
15
+ "test.json": "zGwuobgxFob47dayYp0kP1EbTZE9CSCKd0pFZjptm40="
16
  }
17
  },
18
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
19
  "webgpu": {
20
+ "manifestSpec": "2.1",
21
  "variants": {
22
  "separate_derived_splitk": ["attn-flash-decode-splitk-merge.wgsl.jinja", "paged-attention.wgsl.jinja", "paged-scatter-kv.wgsl.jinja"],
23
  "separate_derived": ["paged-attention.wgsl.jinja", "paged-scatter-kv.wgsl.jinja"],
build/webgpu/paged-attention.wgsl.jinja CHANGED
@@ -1,7 +1,6 @@
1
  {% set splitK = splitK is defined and splitK %}
2
  {% set keyLo = "splitStart" if splitK else "0u" %}
3
  {% set keyHi = "splitEnd" if splitK else "key_bound" %}
4
- enable f16;
5
  {{ env.wgsl.resourceDeclarations }}
6
 
7
  // com.microsoft.PagedAttention, attention pass.
@@ -56,6 +55,7 @@ fn shifted_value(value: f32, maxValue: f32) -> f32 {
56
  let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
57
  return select(value - maxValue, 0.0, equalFiniteMax);
58
  }
 
59
  fn exp_shift(value: f32, maxValue: f32) -> f32 {
60
  return exp(shifted_value(value, maxValue));
61
  }
@@ -78,59 +78,6 @@ var<workgroup> key_rows: array<u32, WG>;
78
  // Both the subgroup and portable barrier-tree engines return the same merged
79
  // pair to every invocation. Repeated merges require a workgroup barrier between
80
  // calls before their shared partial storage is reused.
81
- {% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
82
- {% if combineSubgroups %}
83
- // Cross-subgroup merge that assumes nothing about which invocations share a
84
- // subgroup or how many subgroups there are: each subgroup's elected lane
85
- // publishes the subgroup pair in the slot at its OWN invocation index and sets
86
- // that index's bit in a workgroup bitmask; thread 0 then folds exactly the
87
- // published slots, in ascending index order (the online (m, d) merge is not
88
- // float-associative, so the order is fixed), and clears the mask for the next
89
- // call as it reads it. Workgroup memory starts zeroed, so the mask needs no
90
- // setup. Same three collectives as a single-subgroup reduce, two barriers.
91
- var<workgroup> partialM: array<f32, WG>;
92
- var<workgroup> partialD: array<f32, WG>;
93
- var<workgroup> leaderMask: array<atomic<u32>, (WG + 31u) / 32u>;
94
- var<workgroup> combinedMD: vec2<f32>;
95
-
96
- // When the whole workgroup is one subgroup the subgroup reduce already covers
97
- // it (no barriers, no shared state). `subgroup_size` is the size of the current
98
- // subgroup and uniform, so the test is exact and may guard the barriers below.
99
- fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
100
- let sgM = subgroupMax(m);
101
- // A lane with no elements contributes d == 0 (exact identity). A +inf
102
- // element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
103
- // landed in d via exp(NaN); both survive the merge and are detected by the
104
- // code after the reduction.
105
- let sgD = subgroupAdd(d * exp_shift(m, sgM));
106
- if (sgSize == WG) {
107
- return vec2<f32>(sgM, sgD);
108
- }
109
- if (subgroupElect()) {
110
- partialM[lidx] = sgM;
111
- partialD[lidx] = sgD;
112
- atomicOr(&leaderMask[lidx / 32u], 1u << (lidx % 32u));
113
- }
114
- workgroupBarrier();
115
- if (lidx == 0u) {
116
- var accM = -FLT_MAX;
117
- var accD = 0.0;
118
- for (var w = 0u; w < (WG + 31u) / 32u; w = w + 1u) {
119
- var bits = atomicExchange(&leaderMask[w], 0u);
120
- while (bits != 0u) {
121
- let slot = w * 32u + firstTrailingBit(bits);
122
- bits = bits & (bits - 1u);
123
- let mNew = max(accM, partialM[slot]);
124
- accD = accD * exp_shift(accM, mNew) + partialD[slot] * exp_shift(partialM[slot], mNew);
125
- accM = mNew;
126
- }
127
- }
128
- combinedMD = vec2<f32>(accM, accD);
129
- }
130
- workgroupBarrier();
131
- return combinedMD;
132
- }
133
- {% else %}
134
  {% set mdStreamed = mdStreams is defined %}
135
  {% set mdStreams = mdStreams if mdStreams is defined else 1 %}
136
  {% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
@@ -195,16 +142,12 @@ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
195
  return merged;
196
  }
197
  {% endif %}
198
- {% endif %}
199
-
200
 
201
- {% if ATTN_SCALE_DIM is not defined %}{% set ATTN_SCALE_DIM = "HEAD_DIM" %}{% endif %}
202
- fn scale_value() -> f32 {
203
  if (params.scale != 0.0) { return params.scale; }
204
  return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
205
  }
206
 
207
-
208
  {% macro varlen_sequence_of_token(buffer="cumulative_sequence_length") %}
209
  fn sequence_of_token(token: u32, batch_size: u32) -> u32 {
210
  for (var b: u32 = 0u; b < batch_size; b = b + 1u) {
@@ -213,23 +156,17 @@ fn sequence_of_token(token: u32, batch_size: u32) -> u32 {
213
  }
214
  }
215
  return batch_size - 1u;
216
- }
217
- {%- endmacro %}
218
-
219
  {% macro paged_sequence_index(withLookup=true) %}
220
- {% if withLookup %}
221
  {{ varlen_sequence_of_token() }}
222
- {% endif %}
223
 
224
  // Physical element base of one (slot, kv head) row of the paged cache, whose
225
  // layout is (num_blocks, block_size, kv_num_heads, head_size).
226
  fn paged_row_base(block_id: u32, slot_in_block: u32, kv_head: u32) -> u32 {
227
  return ((block_id * BLOCK_SIZE + slot_in_block) * KV_HEADS + kv_head) * HEAD_DIM;
228
  }
229
- {%- endmacro %}
230
-
231
  {{ paged_sequence_index() }}
232
-
233
  @compute @workgroup_size(WG, 1, 1)
234
  fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
235
  let token = wg.x;
@@ -403,4 +340,4 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
403
  {% endfor %}
404
  }
405
  }
406
- {%- endif %}
 
1
  {% set splitK = splitK is defined and splitK %}
2
  {% set keyLo = "splitStart" if splitK else "0u" %}
3
  {% set keyHi = "splitEnd" if splitK else "key_bound" %}
 
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  // com.microsoft.PagedAttention, attention pass.
 
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
  }
 
78
  // Both the subgroup and portable barrier-tree engines return the same merged
79
  // pair to every invocation. Repeated merges require a workgroup barrier between
80
  // calls before their shared partial storage is reused.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  {% set mdStreamed = mdStreams is defined %}
82
  {% set mdStreams = mdStreams if mdStreams is defined else 1 %}
83
  {% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
 
142
  return merged;
143
  }
144
  {% endif %}
 
 
145
 
146
+ {% set ATTN_SCALE_DIM = "HEAD_DIM" %}fn scale_value() -> f32 {
 
147
  if (params.scale != 0.0) { return params.scale; }
148
  return inverseSqrt(f32({{ ATTN_SCALE_DIM }}));
149
  }
150
 
 
151
  {% macro varlen_sequence_of_token(buffer="cumulative_sequence_length") %}
152
  fn sequence_of_token(token: u32, batch_size: u32) -> u32 {
153
  for (var b: u32 = 0u; b < batch_size; b = b + 1u) {
 
156
  }
157
  }
158
  return batch_size - 1u;
159
+ }{% endmacro %}
 
 
160
  {% macro paged_sequence_index(withLookup=true) %}
 
161
  {{ varlen_sequence_of_token() }}
 
162
 
163
  // Physical element base of one (slot, kv head) row of the paged cache, whose
164
  // layout is (num_blocks, block_size, kv_num_heads, head_size).
165
  fn paged_row_base(block_id: u32, slot_in_block: u32, kv_head: u32) -> u32 {
166
  return ((block_id * BLOCK_SIZE + slot_in_block) * KV_HEADS + kv_head) * HEAD_DIM;
167
  }
168
+ {% endmacro %}
 
169
  {{ paged_sequence_index() }}
 
170
  @compute @workgroup_size(WG, 1, 1)
171
  fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
172
  let token = wg.x;
 
340
  {% endfor %}
341
  }
342
  }
343
+ {% endif %}
build/webgpu/paged-scatter-kv.wgsl.jinja CHANGED
@@ -1,37 +1,11 @@
1
- {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
- {% if note == "dispatch-limit" %}
3
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; gid.y carries the high portion of the output index.
11
- {% elif note == "vec4-limit" %}
12
- // 2D-folded flat vec4 index: gid.y carries the high bits past the dispatch's
13
- // per-axis workgroup fold width (the dispatch caps x and spills into y).
14
- {% elif note == "element-limit" %}
15
- // 2D-folded flat element index: gid.y carries the high bits past the
16
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
- enable f16;
35
  {{ env.wgsl.resourceDeclarations }}
36
 
37
  // com.microsoft.PagedAttention, KV scatter pass.
@@ -62,26 +36,22 @@ fn sequence_of_token(token: u32, batch_size: u32) -> u32 {
62
  }
63
  }
64
  return batch_size - 1u;
65
- }
66
- {%- endmacro %}
67
-
68
  {% macro paged_sequence_index(withLookup=true) %}
69
  {% if withLookup %}
70
  {{ varlen_sequence_of_token() }}
71
- {% endif %}
72
 
 
73
  // Physical element base of one (slot, kv head) row of the paged cache, whose
74
  // layout is (num_blocks, block_size, kv_num_heads, head_size).
75
  fn paged_row_base(block_id: u32, slot_in_block: u32, kv_head: u32) -> u32 {
76
  return ((block_id * BLOCK_SIZE + slot_in_block) * KV_HEADS + kv_head) * HEAD_DIM;
77
  }
78
- {%- endmacro %}
79
-
80
  {{ paged_sequence_index(withLookup=not hasSlotMapping) }}
81
-
82
  @compute @workgroup_size({{ tunables.SCATTER_WORKGROUP_SIZE }})
83
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
84
- {{ flat_index_2d(bound="params.scatterCount", note="element-limit") }}
85
  let dim = i % HEAD_DIM;
86
  let kv_head = (i / HEAD_DIM) % KV_HEADS;
87
  let token = i / (KV_HEADS * HEAD_DIM);
 
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
  // com.microsoft.PagedAttention, KV scatter pass.
 
36
  }
37
  }
38
  return batch_size - 1u;
39
+ }{% endmacro %}
 
 
40
  {% macro paged_sequence_index(withLookup=true) %}
41
  {% if withLookup %}
42
  {{ varlen_sequence_of_token() }}
 
43
 
44
+ {% endif %}
45
  // Physical element base of one (slot, kv head) row of the paged cache, whose
46
  // layout is (num_blocks, block_size, kv_num_heads, head_size).
47
  fn paged_row_base(block_id: u32, slot_in_block: u32, kv_head: u32) -> u32 {
48
  return ((block_id * BLOCK_SIZE + slot_in_block) * KV_HEADS + kv_head) * HEAD_DIM;
49
  }
50
+ {% endmacro %}
 
51
  {{ paged_sequence_index(withLookup=not hasSlotMapping) }}
 
52
  @compute @workgroup_size({{ tunables.SCATTER_WORKGROUP_SIZE }})
53
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
54
+ {{ flat_index_2d(tunables.SCATTER_WORKGROUP_SIZE, bound="params.scatterCount") }}
55
  let dim = i % HEAD_DIM;
56
  let kv_head = (i / HEAD_DIM) % KV_HEADS;
57
  let token = i / (KV_HEADS * HEAD_DIM);
build/webgpu/test.json CHANGED
@@ -686,7 +686,7 @@
686
  {
687
  "name": "splitk_slot_mapping",
688
  "provenance": {
689
- "notes": "A 201-key history over a 256-key table creates two split-K ranges, while explicit slot mapping selects the corresponding cache-scatter path."
690
  },
691
  "attrs": { "num_heads": 2, "kv_num_heads": 1 },
692
  "inputs": {
@@ -842,6 +842,341 @@
842
  }
843
  },
844
  "outputs": { "outputT": { "dtype": "float16", "shape": [1, 2048], "tolerance": 0.00001, "relTolerance": 0.0001 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
845
  }
846
  ]
847
  }
 
686
  {
687
  "name": "splitk_slot_mapping",
688
  "provenance": {
689
+ "notes": "A 201-key history over a 256-key cache table with explicit slot mapping checks attention over multiple key ranges and cache publication."
690
  },
691
  "attrs": { "num_heads": 2, "kv_num_heads": 1 },
692
  "inputs": {
 
842
  }
843
  },
844
  "outputs": { "outputT": { "dtype": "float16", "shape": [1, 2048], "tolerance": 0.00001, "relTolerance": 0.0001 } }
845
+ },
846
+ {
847
+ "name": "scatter_fold_wg1_independent_sequences",
848
+ "provenance": {
849
+ "notes": "A large scatter workload crosses the WebGPU dispatch-grid limit. One-token sequences bound attention cost; nonzero dyadic cycles expose missing tail writes and misplaced cache/value elements."
850
+ },
851
+ "attrs": { "num_heads": 8, "kv_num_heads": 8 },
852
+ "tunables": { "WORKGROUP_SIZE": 64, "SCATTER_WORKGROUP_SIZE": 1 },
853
+ "inputs": {
854
+ "queryT": { "dtype": "float16", "shape": [64, 1024], "data": { "kind": "constant", "value": 0.0 } },
855
+ "keyT": {
856
+ "dtype": "float16",
857
+ "shape": [64, 1024],
858
+ "data": { "kind": "cycle", "values": [1.0, 2.0, 4.0, -1.0, -2.0] }
859
+ },
860
+ "valueT": {
861
+ "dtype": "float16",
862
+ "shape": [64, 1024],
863
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] }
864
+ },
865
+ "keyCacheT": { "dtype": "float16", "shape": [64, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
866
+ "valueCacheT": { "dtype": "float16", "shape": [64, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
867
+ "cumulativeSequenceLengthT": {
868
+ "dtype": "int32",
869
+ "shape": [65],
870
+ "data": { "kind": "linspace", "start": 0, "end": 64 }
871
+ },
872
+ "pastSeqlensT": { "dtype": "int32", "shape": [64], "data": { "kind": "constant", "value": 0 } },
873
+ "blockTableT": { "dtype": "int32", "shape": [64, 1], "data": { "kind": "linspace", "start": 0, "end": 63 } }
874
+ },
875
+ "outputs": {
876
+ "outputT": {
877
+ "dtype": "float16",
878
+ "shape": [64, 1024],
879
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] },
880
+ "tolerance": 0,
881
+ "relTolerance": 0
882
+ },
883
+ "keyCacheT": {
884
+ "dtype": "float16",
885
+ "shape": [64, 1, 8, 128],
886
+ "data": { "kind": "cycle", "values": [1.0, 2.0, 4.0, -1.0, -2.0] },
887
+ "tolerance": 0,
888
+ "relTolerance": 0
889
+ },
890
+ "valueCacheT": {
891
+ "dtype": "float16",
892
+ "shape": [64, 1, 8, 128],
893
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] },
894
+ "tolerance": 0,
895
+ "relTolerance": 0
896
+ }
897
+ }
898
+ },
899
+ {
900
+ "name": "scatter_fold_wg4_independent_sequences",
901
+ "provenance": {
902
+ "notes": "A large scatter workload crosses the WebGPU dispatch-grid limit. One-token sequences bound attention cost; nonzero dyadic cycles expose missing tail writes and misplaced cache/value elements."
903
+ },
904
+ "attrs": { "num_heads": 8, "kv_num_heads": 8 },
905
+ "tunables": { "WORKGROUP_SIZE": 64, "SCATTER_WORKGROUP_SIZE": 4 },
906
+ "inputs": {
907
+ "queryT": { "dtype": "float16", "shape": [256, 1024], "data": { "kind": "constant", "value": 0.0 } },
908
+ "keyT": {
909
+ "dtype": "float16",
910
+ "shape": [256, 1024],
911
+ "data": { "kind": "cycle", "values": [1.0, 2.0, 4.0, -1.0, -2.0] }
912
+ },
913
+ "valueT": {
914
+ "dtype": "float16",
915
+ "shape": [256, 1024],
916
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] }
917
+ },
918
+ "keyCacheT": { "dtype": "float16", "shape": [256, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
919
+ "valueCacheT": { "dtype": "float16", "shape": [256, 1, 8, 128], "data": { "kind": "constant", "value": 0.0 } },
920
+ "cumulativeSequenceLengthT": {
921
+ "dtype": "int32",
922
+ "shape": [257],
923
+ "data": { "kind": "linspace", "start": 0, "end": 256 }
924
+ },
925
+ "pastSeqlensT": { "dtype": "int32", "shape": [256], "data": { "kind": "constant", "value": 0 } },
926
+ "blockTableT": { "dtype": "int32", "shape": [256, 1], "data": { "kind": "linspace", "start": 0, "end": 255 } }
927
+ },
928
+ "outputs": {
929
+ "outputT": {
930
+ "dtype": "float16",
931
+ "shape": [256, 1024],
932
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] },
933
+ "tolerance": 0,
934
+ "relTolerance": 0
935
+ },
936
+ "keyCacheT": {
937
+ "dtype": "float16",
938
+ "shape": [256, 1, 8, 128],
939
+ "data": { "kind": "cycle", "values": [1.0, 2.0, 4.0, -1.0, -2.0] },
940
+ "tolerance": 0,
941
+ "relTolerance": 0
942
+ },
943
+ "valueCacheT": {
944
+ "dtype": "float16",
945
+ "shape": [256, 1, 8, 128],
946
+ "data": { "kind": "cycle", "values": [2.0, -4.0, 8.0, 1.0, -2.0, 4.0, -1.0] },
947
+ "tolerance": 0,
948
+ "relTolerance": 0
949
+ }
950
+ }
951
+ },
952
+ {
953
+ "name": "ort_paged_ragged_empty_request_b2_h8kv4_d128",
954
+ "attrs": { "num_heads": 8, "kv_num_heads": 4 },
955
+ "inputs": {
956
+ "queryT": {
957
+ "dtype": "float16",
958
+ "shape": [2, 1024],
959
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 0.5 }
960
+ },
961
+ "keyT": {
962
+ "dtype": "float16",
963
+ "shape": [2, 512],
964
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.031, "scale": 0.5 }
965
+ },
966
+ "valueT": {
967
+ "dtype": "float16",
968
+ "shape": [2, 512],
969
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.037, "scale": 0.5 }
970
+ },
971
+ "keyCacheT": {
972
+ "dtype": "float16",
973
+ "shape": [4, 8, 4, 128],
974
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 0.25 }
975
+ },
976
+ "valueCacheT": {
977
+ "dtype": "float16",
978
+ "shape": [4, 8, 4, 128],
979
+ "data": { "kind": "fillFloat32", "sinStep": 0.007, "cosStep": 0.041, "scale": 0.25 }
980
+ },
981
+ "cumulativeSequenceLengthT": {
982
+ "dtype": "int32",
983
+ "shape": [3],
984
+ "data": { "kind": "values", "values": [0, 2, 2] }
985
+ },
986
+ "pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 0] } },
987
+ "blockTableT": { "dtype": "int32", "shape": [2, 2], "data": { "kind": "values", "values": [2, 0, 3, 1] } }
988
+ },
989
+ "outputs": { "outputT": { "dtype": "float16", "shape": [2, 1024], "tolerance": 0.005 } }
990
+ },
991
+ {
992
+ "name": "ort_paged_ragged_new_seqlens_1_2_b2_h8kv4_d128",
993
+ "attrs": { "num_heads": 8, "kv_num_heads": 4 },
994
+ "inputs": {
995
+ "queryT": {
996
+ "dtype": "float16",
997
+ "shape": [3, 1024],
998
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.027, "scale": 0.5 }
999
+ },
1000
+ "keyT": {
1001
+ "dtype": "float16",
1002
+ "shape": [3, 512],
1003
+ "data": { "kind": "fillFloat32", "sinStep": 0.021, "cosStep": 0.033, "scale": 0.5 }
1004
+ },
1005
+ "valueT": {
1006
+ "dtype": "float16",
1007
+ "shape": [3, 512],
1008
+ "data": { "kind": "fillFloat32", "sinStep": 0.015, "cosStep": 0.039, "scale": 0.5 }
1009
+ },
1010
+ "keyCacheT": {
1011
+ "dtype": "float16",
1012
+ "shape": [4, 8, 4, 128],
1013
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 0.25 }
1014
+ },
1015
+ "valueCacheT": {
1016
+ "dtype": "float16",
1017
+ "shape": [4, 8, 4, 128],
1018
+ "data": { "kind": "fillFloat32", "sinStep": 0.007, "cosStep": 0.041, "scale": 0.25 }
1019
+ },
1020
+ "cumulativeSequenceLengthT": {
1021
+ "dtype": "int32",
1022
+ "shape": [3],
1023
+ "data": { "kind": "values", "values": [0, 1, 3] }
1024
+ },
1025
+ "pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 0] } },
1026
+ "blockTableT": { "dtype": "int32", "shape": [2, 2], "data": { "kind": "values", "values": [2, 0, 3, 1] } }
1027
+ },
1028
+ "outputs": { "outputT": { "dtype": "float16", "shape": [3, 1024], "tolerance": 0.005 } }
1029
+ },
1030
+ {
1031
+ "name": "ort_paged_prefill_multibatch_varlen_b2t48_h2kv2d128",
1032
+ "attrs": { "num_heads": 2, "kv_num_heads": 2 },
1033
+ "inputs": {
1034
+ "queryT": {
1035
+ "dtype": "float16",
1036
+ "shape": [48, 128],
1037
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.023, "scale": 0.4 }
1038
+ },
1039
+ "keyT": {
1040
+ "dtype": "float16",
1041
+ "shape": [48, 128],
1042
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 0.4 }
1043
+ },
1044
+ "valueT": {
1045
+ "dtype": "float16",
1046
+ "shape": [48, 128],
1047
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.031, "scale": 0.4 }
1048
+ },
1049
+ "keyCacheT": {
1050
+ "dtype": "float16",
1051
+ "shape": [6, 16, 2, 64],
1052
+ "data": { "kind": "fillFloat32", "sinStep": 0.007, "cosStep": 0.037, "scale": 0.2 }
1053
+ },
1054
+ "valueCacheT": {
1055
+ "dtype": "float16",
1056
+ "shape": [6, 16, 2, 64],
1057
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.041, "scale": 0.2 }
1058
+ },
1059
+ "cumulativeSequenceLengthT": {
1060
+ "dtype": "int32",
1061
+ "shape": [3],
1062
+ "data": { "kind": "values", "values": [0, 32, 48] }
1063
+ },
1064
+ "pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 0] } },
1065
+ "blockTableT": { "dtype": "int32", "shape": [2, 2], "data": { "kind": "values", "values": [4, 1, 5, 0] } }
1066
+ },
1067
+ "outputs": { "outputT": { "dtype": "float16", "shape": [48, 128], "tolerance": 0.005 } }
1068
+ },
1069
+ {
1070
+ "name": "ort_paged_empty_tokens_copy_caches",
1071
+ "attrs": { "num_heads": 1, "kv_num_heads": 1 },
1072
+ "inputs": {
1073
+ "queryT": { "dtype": "float16", "shape": [0, 8], "data": { "kind": "values", "values": [] } },
1074
+ "keyT": { "dtype": "float16", "shape": [0, 8], "data": { "kind": "values", "values": [] } },
1075
+ "valueT": { "dtype": "float16", "shape": [0, 8], "data": { "kind": "values", "values": [] } },
1076
+ "keyCacheT": {
1077
+ "dtype": "float16",
1078
+ "shape": [2, 4, 1, 8],
1079
+ "data": { "kind": "fillFloat32", "sinStep": 0.09, "cosStep": 0.13, "scale": 0.5 }
1080
+ },
1081
+ "valueCacheT": {
1082
+ "dtype": "float16",
1083
+ "shape": [2, 4, 1, 8],
1084
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.17, "scale": 0.5 }
1085
+ },
1086
+ "cumulativeSequenceLengthT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [0, 0] } },
1087
+ "pastSeqlensT": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [0] } },
1088
+ "blockTableT": { "dtype": "int32", "shape": [1, 1], "data": { "kind": "values", "values": [0] } }
1089
+ },
1090
+ "outputs": {
1091
+ "outputT": { "dtype": "float16", "shape": [0, 8], "data": { "kind": "values", "values": [] } },
1092
+ "keyCacheT": { "dtype": "float16", "shape": [2, 4, 1, 8], "tolerance": 0.001 },
1093
+ "valueCacheT": { "dtype": "float16", "shape": [2, 4, 1, 8], "tolerance": 0.001 }
1094
+ }
1095
+ },
1096
+ {
1097
+ "name": "ort_paged_mixed_prefill_decode_b2t5_past2_4_h2kv1d8",
1098
+ "attrs": { "num_heads": 2, "kv_num_heads": 1 },
1099
+ "inputs": {
1100
+ "queryT": {
1101
+ "dtype": "float16",
1102
+ "shape": [5, 16],
1103
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1104
+ },
1105
+ "keyT": {
1106
+ "dtype": "float16",
1107
+ "shape": [5, 8],
1108
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.5 }
1109
+ },
1110
+ "valueT": {
1111
+ "dtype": "float16",
1112
+ "shape": [5, 8],
1113
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.37, "scale": 0.5 }
1114
+ },
1115
+ "keyCacheT": {
1116
+ "dtype": "float16",
1117
+ "shape": [4, 4, 1, 8],
1118
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.43, "scale": 0.25 }
1119
+ },
1120
+ "valueCacheT": {
1121
+ "dtype": "float16",
1122
+ "shape": [4, 4, 1, 8],
1123
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.47, "scale": 0.25 }
1124
+ },
1125
+ "cumulativeSequenceLengthT": {
1126
+ "dtype": "int32",
1127
+ "shape": [3],
1128
+ "data": { "kind": "values", "values": [0, 3, 5] }
1129
+ },
1130
+ "pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [2, 4] } },
1131
+ "blockTableT": { "dtype": "int32", "shape": [2, 2], "data": { "kind": "values", "values": [3, 1, 0, 2] } }
1132
+ },
1133
+ "outputs": { "outputT": { "dtype": "float16", "shape": [5, 16], "tolerance": 0.005 } }
1134
+ },
1135
+ {
1136
+ "name": "ort_paged_splitk_decode_ragged_batch_b2_past200_400_h2kv1d64",
1137
+ "attrs": { "num_heads": 2, "kv_num_heads": 1 },
1138
+ "inputs": {
1139
+ "queryT": {
1140
+ "dtype": "float16",
1141
+ "shape": [2, 128],
1142
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.4 }
1143
+ },
1144
+ "keyT": {
1145
+ "dtype": "float16",
1146
+ "shape": [2, 64],
1147
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 0.4 }
1148
+ },
1149
+ "valueT": {
1150
+ "dtype": "float16",
1151
+ "shape": [2, 64],
1152
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.031, "scale": 0.4 }
1153
+ },
1154
+ "keyCacheT": {
1155
+ "dtype": "float16",
1156
+ "shape": [32, 32, 1, 64],
1157
+ "data": { "kind": "fillFloat32", "sinStep": 0.003, "cosStep": 0.047, "scale": 0.2 }
1158
+ },
1159
+ "valueCacheT": {
1160
+ "dtype": "float16",
1161
+ "shape": [32, 32, 1, 64],
1162
+ "data": { "kind": "fillFloat32", "sinStep": 0.005, "cosStep": 0.053, "scale": 0.2 }
1163
+ },
1164
+ "cumulativeSequenceLengthT": {
1165
+ "dtype": "int32",
1166
+ "shape": [3],
1167
+ "data": { "kind": "values", "values": [0, 1, 2] }
1168
+ },
1169
+ "pastSeqlensT": { "dtype": "int32", "shape": [2], "data": { "kind": "values", "values": [200, 400] } },
1170
+ "blockTableT": {
1171
+ "dtype": "int32",
1172
+ "shape": [2, 16],
1173
+ "data": {
1174
+ "kind": "values",
1175
+ "values": [3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30, 5, 12, 1, 8, 15, 22, 29, 4, 11, 18, 25, 0, 7, 14, 21, 28, 19, 26]
1176
+ }
1177
+ }
1178
+ },
1179
+ "outputs": { "outputT": { "dtype": "float16", "shape": [2, 128], "tolerance": 0.00043 } }
1180
  }
1181
  ]
1182
  }