1pub fn compute_cu_seqlens_q(
21 items: &[(String, Vec<u32>, usize, bool)],
22) -> (Vec<usize>, Vec<u32>, usize) {
23 let q_lens: Vec<usize> = items.iter().map(|it| it.1.len()).collect();
24 let mut cu_seqlens_q: Vec<u32> = Vec::with_capacity(items.len() + 1);
25 cu_seqlens_q.push(0);
26 for &l in &q_lens {
27 let prev = *cu_seqlens_q.last().unwrap();
28 cu_seqlens_q.push(prev + l as u32);
29 }
30 let m_total = *cu_seqlens_q.last().unwrap() as usize;
31 (q_lens, cu_seqlens_q, m_total)
32}
33
34pub fn compute_pos_offsets(items: &[(String, Vec<u32>, usize, bool)]) -> Vec<u32> {
39 items.iter().map(|it| it.2 as u32).collect()
40}
41
42pub fn compute_max_kv_len(items: &[(String, Vec<u32>, usize, bool)]) -> usize {
46 items.iter().map(|it| it.2 + it.1.len()).max().unwrap_or(0)
47}
48
49pub fn concat_q_tokens(items: &[(String, Vec<u32>, usize, bool)]) -> Vec<u32> {
53 items.iter().flat_map(|it| it.1.iter().copied()).collect()
54}
55
56pub fn stack_block_tables<F: Fn(&str) -> Vec<u32>>(
66 items: &[(String, Vec<u32>, usize, bool)],
67 max_blocks_per_seq: usize,
68 lookup: F,
69) -> Vec<u32> {
70 let mut stacked: Vec<u32> = vec![0u32; items.len() * max_blocks_per_seq];
71 for (i, (cid, _, _, _)) in items.iter().enumerate() {
72 let blocks = lookup(cid);
73 let n_to_copy = blocks.len().min(max_blocks_per_seq);
74 stacked[i * max_blocks_per_seq..i * max_blocks_per_seq + n_to_copy]
75 .copy_from_slice(&blocks[..n_to_copy]);
76 }
77 stacked
78}
79
80pub fn compute_final_indices(
85 items: &[(String, Vec<u32>, usize, bool)],
86 cu_seqlens_q: &[u32],
87) -> Vec<(usize, usize)> {
88 items
89 .iter()
90 .enumerate()
91 .filter(|(_, it)| it.3)
92 .map(|(orig_idx, it)| {
93 let last_token_local = it.1.len() - 1;
94 let global = (cu_seqlens_q[orig_idx] as usize) + last_token_local;
95 (orig_idx, global)
96 })
97 .collect()
98}
99
100pub fn unified_attention_launch_key(
109 total_q_tokens: usize,
110 num_seqs: usize,
111 max_kv_len: usize,
112 split_k_attn: Option<bool>,
113) -> u64 {
114 let use_split_k = split_k_attn
115 .unwrap_or_else(|| total_q_tokens <= 64 && (num_seqs <= 4 || max_kv_len >= 768));
116 if use_split_k {
117 let num_splits = match max_kv_len {
118 kv if kv <= 384 => 2usize,
119 kv if kv <= 1024 => 4,
120 kv if kv <= 2048 => 8,
121 _ => 16,
122 };
123 let chunk = (max_kv_len + num_splits - 1) / num_splits;
124 return 0x7370_6c69_7400_0000u64 ^ ((num_splits as u64) << 32) ^ (chunk.max(1) as u64);
125 }
126
127 let shared_kv = ferrum_kernels::backend::attention_score_capacity_bucket(max_kv_len);
128 0x7368_6d65_6d00_0000u64 ^ (shared_kv as u64)
129}
130
131pub fn unified_graph_key(
145 m_total: usize,
146 num_seqs: usize,
147 attention_launch_key: u64,
148 final_indices: &[(usize, usize)],
149) -> u64 {
150 scoped_unified_graph_key(
151 0x6675_6c6c_5f67_7261,
152 m_total,
153 num_seqs,
154 attention_launch_key,
155 final_indices,
156 )
157}
158
159pub fn unified_layers_only_graph_key(
163 m_total: usize,
164 num_seqs: usize,
165 attention_launch_key: u64,
166) -> u64 {
167 scoped_unified_graph_key(
168 0x6c61_7965_725f_6772,
169 m_total,
170 num_seqs,
171 attention_launch_key,
172 &[],
173 )
174}
175
176pub fn unified_lm_head_eager_graph_key(
179 m_total: usize,
180 num_seqs: usize,
181 attention_launch_key: u64,
182 final_indices: &[(usize, usize)],
183) -> u64 {
184 scoped_unified_graph_key(
185 0x6c6d_6865_5f65_6772,
186 m_total,
187 num_seqs,
188 attention_launch_key,
189 final_indices,
190 )
191}
192
193fn scoped_unified_graph_key(
194 scope_tag: u64,
195 m_total: usize,
196 num_seqs: usize,
197 attention_launch_key: u64,
198 final_indices: &[(usize, usize)],
199) -> u64 {
200 fn feed(mut hash: u64, value: u64) -> u64 {
201 hash ^= value;
202 hash = hash.wrapping_mul(0x100000001b3);
203 hash
204 }
205
206 let mut hash = 0xcbf29ce484222325u64;
207 hash = feed(hash, scope_tag);
208 hash = feed(hash, m_total as u64);
209 hash = feed(hash, num_seqs as u64);
210 hash = feed(hash, attention_launch_key);
211 hash = feed(hash, final_indices.len() as u64);
212 for &(orig_idx, global_idx) in final_indices {
213 hash = feed(hash, orig_idx as u64);
214 hash = feed(hash, global_idx as u64);
215 }
216 (1u64 << 63) | (hash & !(1u64 << 63))
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 fn item(
224 cid: &str,
225 q_len: usize,
226 pos: usize,
227 final_chunk: bool,
228 ) -> (String, Vec<u32>, usize, bool) {
229 (cid.to_string(), vec![0u32; q_len], pos, final_chunk)
230 }
231
232 #[test]
233 fn cu_seqlens_q_mixed_lengths() {
234 let items = vec![
235 item("a", 5, 0, true),
236 item("b", 1, 100, true),
237 item("c", 3, 10, false),
238 ];
239 let (q_lens, cu, m_total) = compute_cu_seqlens_q(&items);
240 assert_eq!(q_lens, vec![5, 1, 3]);
241 assert_eq!(cu, vec![0, 5, 6, 9]);
242 assert_eq!(m_total, 9);
243 }
244
245 #[test]
246 fn pos_offsets_and_max_kv_len() {
247 let items = vec![
248 item("a", 5, 0, true),
249 item("b", 1, 100, true),
250 item("c", 3, 10, false),
251 ];
252 assert_eq!(compute_pos_offsets(&items), vec![0u32, 100, 10]);
253 assert_eq!(compute_max_kv_len(&items), 101); }
255
256 #[test]
257 fn final_indices_only_final_chunks() {
258 let items = vec![
259 item("a", 5, 0, true), item("b", 1, 100, true), item("c", 3, 10, false), ];
263 let (_, cu, _) = compute_cu_seqlens_q(&items);
264 let fi = compute_final_indices(&items, &cu);
265 assert_eq!(fi, vec![(0, 4), (1, 5)]);
266 }
267
268 #[test]
269 fn graph_key_high_bit_set() {
270 let launch_key = unified_attention_launch_key(32, 4, 128, None);
271 let k = unified_graph_key(32, 4, launch_key, &[(0, 0), (1, 1), (2, 2), (3, 3)]);
272 assert!(k & (1u64 << 63) != 0, "high bit must be set");
273 let legacy = ((32u64) << 32) | 4u64;
275 assert_ne!(k, legacy);
276 }
277
278 #[test]
279 fn attention_launch_key_coalesces_non_split_k_within_power_of_two_bucket() {
280 let short = unified_attention_launch_key(16, 16, 129, None);
281 let longer_in_bucket = unified_attention_launch_key(16, 16, 256, None);
282 let next_bucket = unified_attention_launch_key(16, 16, 257, None);
283
284 assert_eq!(short, longer_in_bucket);
285 assert_ne!(short, next_bucket);
286 }
287
288 #[test]
289 fn attention_launch_key_keeps_split_k_chunk_shape() {
290 let short = unified_attention_launch_key(2, 2, 128, None);
291 let longer = unified_attention_launch_key(2, 2, 256, None);
292 let forced_off = unified_attention_launch_key(2, 2, 128, Some(false));
293
294 assert_ne!(short, longer);
295 assert_ne!(short, forced_off);
296 }
297
298 #[test]
299 fn graph_key_uses_attention_launch_shape_and_final_offsets() {
300 let decode_final = vec![(0, 0), (1, 1)];
301 let same_launch_short = unified_attention_launch_key(16, 16, 129, None);
302 let same_launch_long = unified_attention_launch_key(16, 16, 256, None);
303 let next_bucket = unified_attention_launch_key(16, 16, 257, None);
304 let same_grid_short_kv = unified_graph_key(16, 16, same_launch_short, &decode_final);
305 let same_grid_long_kv = unified_graph_key(16, 16, same_launch_long, &decode_final);
306 let different_launch = unified_graph_key(16, 16, next_bucket, &decode_final);
307 assert_eq!(same_grid_short_kv, same_grid_long_kv);
308 assert_ne!(same_grid_short_kv, different_launch);
309
310 let prefill_final = vec![(0, 4), (1, 5)];
311 let prefill_launch = unified_attention_launch_key(6, 2, 128, None);
312 let different_final_offsets = unified_graph_key(6, 2, prefill_launch, &prefill_final);
313 let same_grid_other_offsets = unified_graph_key(6, 2, prefill_launch, &[(0, 2), (1, 5)]);
314 assert_ne!(different_final_offsets, same_grid_other_offsets);
315
316 let no_sample = unified_graph_key(6, 2, prefill_launch, &[]);
317 assert_ne!(different_final_offsets, no_sample);
318 }
319
320 #[test]
321 fn graph_key_distinguishes_capture_scope() {
322 let launch_short = unified_attention_launch_key(6, 2, 128, None);
323 let launch_long = unified_attention_launch_key(6, 2, 640, None);
324 let full_no_sample = unified_graph_key(6, 2, launch_short, &[]);
325 let layers_only = unified_layers_only_graph_key(6, 2, launch_short);
326 let lm_head_eager = unified_lm_head_eager_graph_key(6, 2, launch_short, &[(0, 4), (1, 5)]);
327 assert_ne!(full_no_sample, layers_only);
328 assert_ne!(full_no_sample, lm_head_eager);
329 assert_ne!(layers_only, lm_head_eager);
330 assert_ne!(
331 layers_only,
332 unified_layers_only_graph_key(6, 2, launch_long)
333 );
334 assert_ne!(
335 lm_head_eager,
336 unified_lm_head_eager_graph_key(6, 2, launch_long, &[(0, 4), (1, 5)])
337 );
338 }
339
340 #[test]
341 fn stack_block_tables_pads_and_truncates() {
342 let items = vec![item("a", 1, 0, true), item("b", 1, 0, true)];
343 let stacked = stack_block_tables(&items, 3, |cid| match cid {
345 "a" => vec![10u32, 11u32],
346 "b" => vec![20u32, 21u32, 22u32, 23u32, 24u32],
347 _ => unreachable!(),
348 });
349 assert_eq!(stacked, vec![10, 11, 0, 20, 21, 22]);
352 }
353
354 #[test]
355 fn empty_items() {
356 let items: Vec<(String, Vec<u32>, usize, bool)> = Vec::new();
357 let (q_lens, cu, m_total) = compute_cu_seqlens_q(&items);
358 assert!(q_lens.is_empty());
359 assert_eq!(cu, vec![0]);
360 assert_eq!(m_total, 0);
361 }
362}