1#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct SlidingWindowAttentionConfig {
5 pub batch: usize,
6 pub query_len: usize,
7 pub key_len: usize,
8 pub heads: usize,
9 pub kv_heads: usize,
10 pub head_dim: usize,
11 pub query_start: usize,
12 pub key_start: usize,
13 pub window: usize,
14 pub query_batch_stride: usize,
15 pub key_batch_stride: usize,
16 pub value_batch_stride: usize,
17 pub output_batch_stride: usize,
18 pub query_sequence_stride: usize,
19 pub key_sequence_stride: usize,
20 pub value_sequence_stride: usize,
21 pub output_sequence_stride: usize,
22 pub query_head_stride: usize,
23 pub key_head_stride: usize,
24 pub value_head_stride: usize,
25 pub output_head_stride: usize,
26 pub scale: f32,
27 pub output_scale: f32,
28}
29
30pub fn sliding_window_attention(
36 query: &[f32],
37 key: &[f32],
38 value: &[f32],
39 config: SlidingWindowAttentionConfig,
40) -> Vec<f32> {
41 let q_features = config.heads * config.head_dim;
42 let kv_features = config.kv_heads * config.head_dim;
43 let query_item_len = config.query_len * q_features;
44 let key_item_len = config.key_len * kv_features;
45 let output_item_len = config.query_len * q_features;
46 let query_group_size = config.heads / config.kv_heads;
47 let mut out = vec![0.0; config.batch * output_item_len];
48 for batch in 0..config.batch {
49 let query_base = batch * query_item_len;
50 let key_base = batch * key_item_len;
51 let output_base = batch * output_item_len;
52 for q_index in 0..config.query_len {
53 let q_abs = config.query_start + q_index;
54 for head in 0..config.heads {
55 let kv_head = head / query_group_size;
56 let mut max_score = f32::NEG_INFINITY;
57 let mut scores = vec![f32::NEG_INFINITY; config.key_len];
58 for (key_index, score_slot) in scores.iter_mut().enumerate() {
59 let key_abs = config.key_start + key_index;
60 if key_abs > q_abs || key_abs + config.window <= q_abs {
61 continue;
62 }
63 let mut score = 0.0f32;
64 for dim in 0..config.head_dim {
65 let q_offset =
66 query_base + q_index * q_features + head * config.head_dim + dim;
67 let k_offset =
68 key_base + key_index * kv_features + kv_head * config.head_dim + dim;
69 score += query[q_offset] * key[k_offset];
70 }
71 score *= config.scale;
72 *score_slot = score;
73 max_score = max_score.max(score);
74 }
75 if !max_score.is_finite() {
76 continue;
77 }
78 let mut denom = 0.0f32;
79 for score in &scores {
80 if score.is_finite() {
81 denom += (*score - max_score).exp();
82 }
83 }
84 for dim in 0..config.head_dim {
85 let mut sum = 0.0f32;
86 for (key_index, score) in scores.iter().copied().enumerate() {
87 if score.is_finite() {
88 let weight = (score - max_score).exp() / denom;
89 let v_offset = key_base
90 + key_index * kv_features
91 + kv_head * config.head_dim
92 + dim;
93 sum += weight * value[v_offset];
94 }
95 }
96 out[output_base + q_index * q_features + head * config.head_dim + dim] =
97 sum * config.output_scale;
98 }
99 }
100 }
101 }
102 out
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn sliding_window_attention_f32_uses_contiguous_gqa_groups() {
111 let config = SlidingWindowAttentionConfig {
112 batch: 1,
113 query_len: 1,
114 key_len: 1,
115 heads: 4,
116 kv_heads: 2,
117 head_dim: 1,
118 query_start: 0,
119 key_start: 0,
120 window: 1,
121 query_batch_stride: 4,
122 key_batch_stride: 2,
123 value_batch_stride: 2,
124 output_batch_stride: 4,
125 query_sequence_stride: 4,
126 key_sequence_stride: 2,
127 value_sequence_stride: 2,
128 output_sequence_stride: 4,
129 query_head_stride: 1,
130 key_head_stride: 1,
131 value_head_stride: 1,
132 output_head_stride: 1,
133 scale: 1.0,
134 output_scale: 1.0,
135 };
136 let query = vec![1.0f32; 4];
137 let key = vec![1.0f32, 1.0];
138 let value = vec![10.0f32, 20.0];
139
140 let out = sliding_window_attention(&query, &key, &value, config);
141
142 assert_eq!(out, vec![10.0, 10.0, 20.0, 20.0]);
143 }
144
145 #[test]
146 fn sliding_window_attention_uses_contiguous_gqa_groups() {
147 let query = vec![1.0f32; 4];
148 let key = vec![1.0f32, 1.0];
149 let value = vec![10.0f32, 20.0];
150
151 let out =
152 sliding_window_attention_f32(&query, &key, &value, 1, 1, 1, 4, 2, 1, 0, 0, 1, 1.0, 1.0);
153
154 assert_eq!(out, vec![10.0, 10.0, 20.0, 20.0]);
155 }
156
157 #[test]
158 fn sliding_window_attention_is_causal_left_not_symmetric() {
159 let config = SlidingWindowAttentionConfig {
160 batch: 1,
161 query_len: 1,
162 key_len: 3,
163 heads: 1,
164 kv_heads: 1,
165 head_dim: 1,
166 query_start: 1,
167 key_start: 0,
168 window: 1,
169 query_batch_stride: 1,
170 key_batch_stride: 3,
171 value_batch_stride: 3,
172 output_batch_stride: 1,
173 query_sequence_stride: 1,
174 key_sequence_stride: 1,
175 value_sequence_stride: 1,
176 output_sequence_stride: 1,
177 query_head_stride: 1,
178 key_head_stride: 1,
179 value_head_stride: 1,
180 output_head_stride: 1,
181 scale: 1.0,
182 output_scale: 1.0,
183 };
184 let query = vec![1.0f32];
185 let key = vec![1.0f32, 1.0, 1.0];
186 let value = vec![10.0f32, 20.0, 30.0];
187
188 let out = sliding_window_attention(&query, &key, &value, config);
189
190 assert_eq!(out, vec![20.0]);
191 }
192}
193
194pub fn fused_neighborhood_attention_f32(
195 query: &[f32],
196 key: &[f32],
197 value: &[f32],
198 batch: usize,
199 seq_len: usize,
200 heads: usize,
201 head_dim: usize,
202 kernel_size: usize,
203 dilation: usize,
204 scale: f32,
205) -> Vec<f32> {
206 let mut out = vec![0.0f32; batch * heads * seq_len * head_dim];
207 let half_kernel = kernel_size / 2;
208 let radius = half_kernel * dilation;
209 for batch_index in 0..batch {
210 for head in 0..heads {
211 let batch_head_base = ((batch_index * heads + head) * seq_len) * head_dim;
212 for query_index in 0..seq_len {
213 let lower = query_index.saturating_sub(radius);
214 let upper = (query_index + radius).min(seq_len - 1);
215 let mut scores = Vec::new();
216 for key_index in lower..=upper {
217 if key_index.abs_diff(query_index) % dilation != 0 {
218 continue;
219 }
220 let mut score = 0.0f32;
221 for dim in 0..head_dim {
222 let query_offset = batch_head_base + query_index * head_dim + dim;
223 let key_offset = batch_head_base + key_index * head_dim + dim;
224 score += query[query_offset] * key[key_offset];
225 }
226 scores.push((key_index, score * scale));
227 }
228 let max_score = scores
229 .iter()
230 .map(|(_, score)| *score)
231 .fold(f32::NEG_INFINITY, f32::max);
232 let denom = scores
233 .iter()
234 .map(|(_, score)| (*score - max_score).exp())
235 .sum::<f32>();
236 for dim in 0..head_dim {
237 let mut sum = 0.0f32;
238 for (key_index, score) in scores.iter().copied() {
239 let weight = (score - max_score).exp() / denom;
240 let value_offset = batch_head_base + key_index * head_dim + dim;
241 sum += weight * value[value_offset];
242 }
243 out[batch_head_base + query_index * head_dim + dim] = sum;
244 }
245 }
246 }
247 }
248 out
249}
250
251#[derive(Clone, Copy, Debug, PartialEq)]
252pub struct HcaConfig {
253 pub batch: usize,
254 pub seq_len: usize,
255 pub hidden_dim: usize,
256 pub heads: usize,
257 pub head_dim: usize,
258 pub compression_block: usize,
259 pub groups: usize,
260 pub group_dim: usize,
261}
262
263#[derive(Clone, Copy, Debug, PartialEq)]
264pub struct CsaCompressConfig {
265 pub batch: usize,
266 pub seq_len: usize,
267 pub hidden_dim: usize,
268 pub head_dim: usize,
269 pub compression_block: usize,
270}
271
272#[derive(Clone, Copy, Debug, PartialEq)]
273pub struct CsaLightningIndexerConfig {
274 pub batch: usize,
275 pub query_len: usize,
276 pub blocks: usize,
277 pub index_heads: usize,
278 pub index_dim: usize,
279}
280
281#[derive(Clone, Copy, Debug, PartialEq)]
282pub struct CsaTopkSelectorConfig {
283 pub batch: usize,
284 pub query_len: usize,
285 pub blocks: usize,
286 pub head_dim: usize,
287 pub top_k: usize,
288 pub compression_block: usize,
289}
290
291#[derive(Clone, Copy, Debug, PartialEq)]
292pub struct CsaSharedMqaConfig {
293 pub batch: usize,
294 pub query_len: usize,
295 pub heads: usize,
296 pub head_dim: usize,
297 pub kv_len: usize,
298 pub scale: f32,
299}
300
301#[derive(Clone, Copy, Debug, PartialEq)]
302pub struct MsaBlockMaxConfig {
303 pub batch: usize,
304 pub rows: usize,
305 pub key_len: usize,
306 pub block_size: usize,
307}
308
309#[derive(Clone, Copy, Debug, PartialEq)]
310pub struct MsaSelectedTokenPositionsConfig {
311 pub batch: usize,
312 pub rows: usize,
313 pub selected_blocks: usize,
314 pub block_size: usize,
315 pub seq_len: usize,
316}
317
318#[derive(Clone, Copy, Debug, PartialEq)]
319pub struct MsaSelectTopkBlocksConfig {
320 pub batch: usize,
321 pub rows: usize,
322 pub blocks: usize,
323 pub top_k: usize,
324}
325
326#[derive(Clone, Copy, Debug, PartialEq)]
327pub struct MsaGatheredGqaConfig {
328 pub batch: usize,
329 pub query_len: usize,
330 pub key_len: usize,
331 pub heads: usize,
332 pub kv_heads: usize,
333 pub head_dim: usize,
334 pub selected_keys: usize,
335 pub scale: f32,
336}
337
338pub fn minimax_sparse_attention_block_max_f32(
339 scores: &[f32],
340 config: MsaBlockMaxConfig,
341) -> Vec<f32> {
342 let blocks = config.key_len.div_ceil(config.block_size);
343 let mut out = vec![f32::NEG_INFINITY; config.batch * config.rows * blocks];
344 for batch_index in 0..config.batch {
345 for row in 0..config.rows {
346 for block in 0..blocks {
347 let start = block * config.block_size;
348 let end = (start + config.block_size).min(config.key_len);
349 let mut max_score = f32::NEG_INFINITY;
350 for key_index in start..end {
351 let offset = (batch_index * config.rows + row) * config.key_len + key_index;
352 max_score = max_score.max(scores[offset]);
353 }
354 out[(batch_index * config.rows + row) * blocks + block] = max_score;
355 }
356 }
357 }
358 out
359}
360
361pub fn minimax_sparse_attention_gathered_gqa_f32(
362 query: &[f32],
363 key: &[f32],
364 value: &[f32],
365 positions: &[i32],
366 valid_mask: &[i32],
367 config: MsaGatheredGqaConfig,
368) -> Vec<f32> {
369 let mut out = vec![0.0f32; config.batch * config.query_len * config.heads * config.head_dim];
370 let group_size = config.heads / config.kv_heads;
371 for batch_index in 0..config.batch {
372 for query_index in 0..config.query_len {
373 for head in 0..config.heads {
374 let kv_head = head / group_size;
375 let sparse_base = ((batch_index * config.kv_heads + kv_head) * config.query_len
376 + query_index)
377 * config.selected_keys;
378 let mut max_score = f32::NEG_INFINITY;
379 let mut any_valid = false;
380 for selected in 0..config.selected_keys {
381 if valid_mask[sparse_base + selected] == 0 {
382 continue;
383 }
384 any_valid = true;
385 let key_index = positions[sparse_base + selected] as usize;
386 let mut score = 0.0f32;
387 for dim in 0..config.head_dim {
388 let query_offset =
389 ((batch_index * config.query_len + query_index) * config.heads + head)
390 * config.head_dim
391 + dim;
392 let key_offset = ((batch_index * config.kv_heads + kv_head)
393 * config.key_len
394 + key_index)
395 * config.head_dim
396 + dim;
397 score += query[query_offset] * key[key_offset];
398 }
399 score *= config.scale;
400 max_score = max_score.max(score);
401 }
402 if !any_valid {
403 continue;
404 }
405 let mut denom = 0.0f32;
406 for selected in 0..config.selected_keys {
407 if valid_mask[sparse_base + selected] == 0 {
408 continue;
409 }
410 let key_index = positions[sparse_base + selected] as usize;
411 let mut score = 0.0f32;
412 for dim in 0..config.head_dim {
413 let query_offset =
414 ((batch_index * config.query_len + query_index) * config.heads + head)
415 * config.head_dim
416 + dim;
417 let key_offset = ((batch_index * config.kv_heads + kv_head)
418 * config.key_len
419 + key_index)
420 * config.head_dim
421 + dim;
422 score += query[query_offset] * key[key_offset];
423 }
424 denom += (score * config.scale - max_score).exp();
425 }
426 for dim in 0..config.head_dim {
427 let mut sum = 0.0f32;
428 for selected in 0..config.selected_keys {
429 if valid_mask[sparse_base + selected] == 0 {
430 continue;
431 }
432 let key_index = positions[sparse_base + selected] as usize;
433 let mut score = 0.0f32;
434 for score_dim in 0..config.head_dim {
435 let query_offset = ((batch_index * config.query_len + query_index)
436 * config.heads
437 + head)
438 * config.head_dim
439 + score_dim;
440 let key_offset = ((batch_index * config.kv_heads + kv_head)
441 * config.key_len
442 + key_index)
443 * config.head_dim
444 + score_dim;
445 score += query[query_offset] * key[key_offset];
446 }
447 let weight = (score * config.scale - max_score).exp() / denom;
448 let value_offset = ((batch_index * config.kv_heads + kv_head)
449 * config.key_len
450 + key_index)
451 * config.head_dim
452 + dim;
453 sum += weight * value[value_offset];
454 }
455 let output_offset =
456 ((batch_index * config.query_len + query_index) * config.heads + head)
457 * config.head_dim
458 + dim;
459 out[output_offset] = sum;
460 }
461 }
462 }
463 }
464 out
465}
466
467pub fn minimax_sparse_attention_select_topk_blocks_f32(
468 block_scores: &[f32],
469 local_blocks: &[i32],
470 config: MsaSelectTopkBlocksConfig,
471) -> Vec<i32> {
472 let mut out = vec![-1i32; config.batch * config.rows * config.top_k];
473 let k_eff = config.top_k.min(config.blocks);
474 for batch_index in 0..config.batch {
475 for row in 0..config.rows {
476 let local = local_blocks[batch_index * config.rows + row]
477 .clamp(0, config.blocks as i32 - 1) as usize;
478 let output_base = (batch_index * config.rows + row) * config.top_k;
479 if k_eff == 0 {
480 continue;
481 }
482 out[output_base] = local as i32;
483 for slot in 1..k_eff {
484 let rank_target = slot - 1;
485 let mut selected = -1i32;
486 for candidate in 0..config.blocks {
487 if candidate == local {
488 continue;
489 }
490 let candidate_score =
491 block_scores[(batch_index * config.rows + row) * config.blocks + candidate];
492 let mut rank = 0usize;
493 for other in 0..config.blocks {
494 if other == local || other == candidate {
495 continue;
496 }
497 let other_score =
498 block_scores[(batch_index * config.rows + row) * config.blocks + other];
499 if other_score > candidate_score
500 || (other_score == candidate_score && other < candidate)
501 {
502 rank += 1;
503 }
504 }
505 if rank == rank_target {
506 selected = candidate as i32;
507 break;
508 }
509 }
510 out[output_base + slot] = selected;
511 }
512 }
513 }
514 out
515}
516
517pub fn minimax_sparse_attention_selected_token_positions_i32(
518 selected: &[i32],
519 query_positions: &[i32],
520 config: MsaSelectedTokenPositionsConfig,
521) -> (Vec<i32>, Vec<i32>) {
522 let expanded = config.selected_blocks * config.block_size;
523 let mut positions = vec![0i32; config.batch * config.rows * expanded];
524 let mut valid = vec![0i32; config.batch * config.rows * expanded];
525 for batch_index in 0..config.batch {
526 for row in 0..config.rows {
527 let query_position = query_positions[batch_index * config.rows + row];
528 for selected_block in 0..config.selected_blocks {
529 let block = selected
530 [(batch_index * config.rows + row) * config.selected_blocks + selected_block];
531 for local_key in 0..config.block_size {
532 let output_offset = (batch_index * config.rows + row) * expanded
533 + selected_block * config.block_size
534 + local_key;
535 let raw_position = block * config.block_size as i32 + local_key as i32;
536 let clamped_position = raw_position.clamp(0, config.seq_len as i32 - 1);
537 positions[output_offset] = clamped_position;
538 valid[output_offset] = i32::from(
539 raw_position >= 0
540 && raw_position < config.seq_len as i32
541 && raw_position <= query_position,
542 );
543 }
544 }
545 }
546 }
547 (positions, valid)
548}
549
550pub fn compressed_sparse_attention_compress_f32(
551 hidden: &[f32],
552 weight_a_kv: &[f32],
553 weight_b_kv: &[f32],
554 weight_a_z: &[f32],
555 weight_b_z: &[f32],
556 bias_a: &[f32],
557 bias_b: &[f32],
558 config: CsaCompressConfig,
559) -> Vec<f32> {
560 let blocks = config.seq_len / config.compression_block;
561 let mut out = vec![0.0f32; config.batch * blocks * config.head_dim];
562 for batch_index in 0..config.batch {
563 for block in 0..blocks {
564 for dim in 0..config.head_dim {
565 let mut logits = vec![0.0f32; config.compression_block * 2];
566 let mut values = vec![0.0f32; config.compression_block * 2];
567 let mut max_logit = f32::NEG_INFINITY;
568 for row in 0..config.compression_block {
569 let token = block * config.compression_block + row;
570 let hidden_base = (batch_index * config.seq_len + token) * config.hidden_dim;
571 let mut value = 0.0f32;
572 let mut logit = bias_a[row * config.head_dim + dim];
573 for hidden_dim in 0..config.hidden_dim {
574 let hidden_value = hidden[hidden_base + hidden_dim];
575 value += hidden_value * weight_a_kv[hidden_dim * config.head_dim + dim];
576 logit += hidden_value * weight_a_z[hidden_dim * config.head_dim + dim];
577 }
578 logits[row] = logit;
579 values[row] = value;
580 max_logit = max_logit.max(logit);
581 }
582 for row in 0..config.compression_block {
583 let slot = config.compression_block + row;
584 if block == 0 {
585 logits[slot] = f32::NEG_INFINITY;
586 values[slot] = 0.0;
587 continue;
588 }
589 let token = (block - 1) * config.compression_block + row;
590 let hidden_base = (batch_index * config.seq_len + token) * config.hidden_dim;
591 let mut value = 0.0f32;
592 let mut logit = bias_b[row * config.head_dim + dim];
593 for hidden_dim in 0..config.hidden_dim {
594 let hidden_value = hidden[hidden_base + hidden_dim];
595 value += hidden_value * weight_b_kv[hidden_dim * config.head_dim + dim];
596 logit += hidden_value * weight_b_z[hidden_dim * config.head_dim + dim];
597 }
598 logits[slot] = logit;
599 values[slot] = value;
600 max_logit = max_logit.max(logit);
601 }
602
603 let mut denom = 0.0f32;
604 for logit in &logits {
605 denom += (*logit - max_logit).exp();
606 }
607 let mut sum = 0.0f32;
608 for row in 0..config.compression_block * 2 {
609 sum += ((logits[row] - max_logit).exp() / denom) * values[row];
610 }
611 out[(batch_index * blocks + block) * config.head_dim + dim] = sum;
612 }
613 }
614 }
615 out
616}
617
618pub fn compressed_sparse_attention_lightning_indexer_f32(
619 indexer_query: &[f32],
620 indexer_key: &[f32],
621 indexer_weight: &[f32],
622 config: CsaLightningIndexerConfig,
623) -> Vec<f32> {
624 let mut out = vec![0.0f32; config.batch * config.query_len * config.blocks];
625 for batch_index in 0..config.batch {
626 for query_index in 0..config.query_len {
627 for block in 0..config.blocks {
628 let mut score = 0.0f32;
629 for index_head in 0..config.index_heads {
630 let mut inner = 0.0f32;
631 for dim in 0..config.index_dim {
632 let query_offset = (((batch_index * config.query_len + query_index)
633 * config.index_heads
634 + index_head)
635 * config.index_dim)
636 + dim;
637 let key_offset =
638 (batch_index * config.blocks + block) * config.index_dim + dim;
639 inner += indexer_query[query_offset] * indexer_key[key_offset];
640 }
641 let weight_offset = (batch_index * config.query_len + query_index)
642 * config.index_heads
643 + index_head;
644 score += indexer_weight[weight_offset] * inner.max(0.0);
645 }
646 out[(batch_index * config.query_len + query_index) * config.blocks + block] = score;
647 }
648 }
649 }
650 out
651}
652
653pub fn compressed_sparse_attention_topk_selector_f32(
654 scores: &[f32],
655 compressed_kv: &[f32],
656 query_positions: &[i32],
657 config: CsaTopkSelectorConfig,
658) -> (Vec<f32>, Vec<i32>) {
659 let mut out = vec![0.0f32; config.batch * config.query_len * config.top_k * config.head_dim];
660 let mut selected = vec![-1i32; config.batch * config.query_len * config.top_k];
661 for batch_index in 0..config.batch {
662 for query_index in 0..config.query_len {
663 let query_position = query_positions[batch_index * config.query_len + query_index];
664 let n_valid =
665 ((query_position.max(0) as usize) / config.compression_block).min(config.blocks);
666 let k_actual = config.top_k.min(n_valid);
667 for slot in 0..k_actual {
668 let mut selected_block = -1i32;
669 for candidate in 0..n_valid {
670 let candidate_score = scores[(batch_index * config.query_len + query_index)
671 * config.blocks
672 + candidate];
673 let mut rank = 0usize;
674 for other in 0..n_valid {
675 if other == candidate {
676 continue;
677 }
678 let other_score = scores[(batch_index * config.query_len + query_index)
679 * config.blocks
680 + other];
681 if other_score > candidate_score
682 || (other_score == candidate_score && other < candidate)
683 {
684 rank += 1;
685 }
686 }
687 if rank == slot {
688 selected_block = candidate as i32;
689 break;
690 }
691 }
692 selected[(batch_index * config.query_len + query_index) * config.top_k + slot] =
693 selected_block;
694 for dim in 0..config.head_dim {
695 let output_offset =
696 (((batch_index * config.query_len + query_index) * config.top_k + slot)
697 * config.head_dim)
698 + dim;
699 let source_offset = (batch_index * config.blocks + selected_block as usize)
700 * config.head_dim
701 + dim;
702 out[output_offset] = compressed_kv[source_offset];
703 }
704 }
705 }
706 }
707 (out, selected)
708}
709
710pub fn compressed_sparse_attention_shared_mqa_f32(
711 query: &[f32],
712 kv_entries: &[f32],
713 valid_mask: &[i32],
714 sink: &[f32],
715 config: CsaSharedMqaConfig,
716) -> Vec<f32> {
717 let mut out = vec![0.0f32; config.batch * config.query_len * config.heads * config.head_dim];
718 for batch_index in 0..config.batch {
719 for query_index in 0..config.query_len {
720 let mask_base = (batch_index * config.query_len + query_index) * config.kv_len;
721 for (head, sink_value) in sink.iter().copied().enumerate().take(config.heads) {
722 let mut max_score = sink_value;
723 for key_index in 0..config.kv_len {
724 if valid_mask[mask_base + key_index] == 0 {
725 continue;
726 }
727 let mut score = 0.0f32;
728 for dim in 0..config.head_dim {
729 let query_offset =
730 ((batch_index * config.query_len + query_index) * config.heads + head)
731 * config.head_dim
732 + dim;
733 let key_offset = ((batch_index * config.query_len + query_index)
734 * config.kv_len
735 + key_index)
736 * config.head_dim
737 + dim;
738 score += query[query_offset] * kv_entries[key_offset];
739 }
740 max_score = max_score.max(score * config.scale);
741 }
742 let mut denom = (sink_value - max_score).exp();
743 for key_index in 0..config.kv_len {
744 if valid_mask[mask_base + key_index] == 0 {
745 continue;
746 }
747 let mut score = 0.0f32;
748 for dim in 0..config.head_dim {
749 let query_offset =
750 ((batch_index * config.query_len + query_index) * config.heads + head)
751 * config.head_dim
752 + dim;
753 let key_offset = ((batch_index * config.query_len + query_index)
754 * config.kv_len
755 + key_index)
756 * config.head_dim
757 + dim;
758 score += query[query_offset] * kv_entries[key_offset];
759 }
760 denom += (score * config.scale - max_score).exp();
761 }
762 for dim in 0..config.head_dim {
763 let mut sum = 0.0f32;
764 for key_index in 0..config.kv_len {
765 if valid_mask[mask_base + key_index] == 0 {
766 continue;
767 }
768 let mut score = 0.0f32;
769 for score_dim in 0..config.head_dim {
770 let query_offset = ((batch_index * config.query_len + query_index)
771 * config.heads
772 + head)
773 * config.head_dim
774 + score_dim;
775 let key_offset = ((batch_index * config.query_len + query_index)
776 * config.kv_len
777 + key_index)
778 * config.head_dim
779 + score_dim;
780 score += query[query_offset] * kv_entries[key_offset];
781 }
782 let weight = (score * config.scale - max_score).exp() / denom;
783 let value_offset = ((batch_index * config.query_len + query_index)
784 * config.kv_len
785 + key_index)
786 * config.head_dim
787 + dim;
788 sum += weight * kv_entries[value_offset];
789 }
790 let output_offset =
791 ((batch_index * config.query_len + query_index) * config.heads + head)
792 * config.head_dim
793 + dim;
794 out[output_offset] = sum;
795 }
796 }
797 }
798 }
799 out
800}
801
802pub fn heavily_compressed_attention_compress_f32(
803 hidden: &[f32],
804 weight_kv: &[f32],
805 weight_z: &[f32],
806 bias: &[f32],
807 config: HcaConfig,
808) -> Vec<f32> {
809 let blocks = config.seq_len / config.compression_block;
810 let mut out = vec![0.0f32; config.batch * blocks * config.head_dim];
811 for batch_index in 0..config.batch {
812 for block in 0..blocks {
813 for dim in 0..config.head_dim {
814 let mut logits = vec![0.0f32; config.compression_block];
815 let mut values = vec![0.0f32; config.compression_block];
816 let mut max_logit = f32::NEG_INFINITY;
817 for row in 0..config.compression_block {
818 let token = block * config.compression_block + row;
819 let hidden_base = (batch_index * config.seq_len + token) * config.hidden_dim;
820 let mut value = 0.0f32;
821 let mut logit = bias[row * config.head_dim + dim];
822 for hidden_dim in 0..config.hidden_dim {
823 let hidden_value = hidden[hidden_base + hidden_dim];
824 value += hidden_value * weight_kv[hidden_dim * config.head_dim + dim];
825 logit += hidden_value * weight_z[hidden_dim * config.head_dim + dim];
826 }
827 logits[row] = logit;
828 values[row] = value;
829 max_logit = max_logit.max(logit);
830 }
831
832 let mut denom = 0.0f32;
833 for logit in &logits {
834 denom += (*logit - max_logit).exp();
835 }
836 let mut sum = 0.0f32;
837 for row in 0..config.compression_block {
838 sum += ((logits[row] - max_logit).exp() / denom) * values[row];
839 }
840 out[(batch_index * blocks + block) * config.head_dim + dim] = sum;
841 }
842 }
843 }
844 out
845}
846
847pub fn heavily_compressed_attention_f32(
848 query: &[f32],
849 compressed_kv: &[f32],
850 weight_group: &[f32],
851 weight_final: &[f32],
852 config: HcaConfig,
853) -> Vec<f32> {
854 let blocks = config.seq_len / config.compression_block;
855 let heads_per_group = config.heads / config.groups;
856 let mut out = vec![0.0f32; config.batch * config.seq_len * config.hidden_dim];
857
858 for batch_index in 0..config.batch {
859 for query_index in 0..config.seq_len {
860 let visible_blocks = (query_index / config.compression_block).min(blocks);
861 let mut heads = vec![0.0f32; config.heads * config.head_dim];
862 if visible_blocks > 0 {
863 for head in 0..config.heads {
864 let mut scores = vec![0.0f32; visible_blocks];
865 let mut max_score = f32::NEG_INFINITY;
866 for (block, score_slot) in scores.iter_mut().enumerate().take(visible_blocks) {
867 let mut score = 0.0f32;
868 for dim in 0..config.head_dim {
869 let query_offset = ((batch_index * config.seq_len + query_index)
870 * config.heads
871 + head)
872 * config.head_dim
873 + dim;
874 let kv_offset = (batch_index * blocks + block) * config.head_dim + dim;
875 score += query[query_offset] * compressed_kv[kv_offset];
876 }
877 score /= (config.head_dim as f32).sqrt();
878 *score_slot = score;
879 max_score = max_score.max(score);
880 }
881 let denom = scores
882 .iter()
883 .map(|score| (*score - max_score).exp())
884 .sum::<f32>();
885 for dim in 0..config.head_dim {
886 let mut sum = 0.0f32;
887 for (block, score) in scores.iter().copied().enumerate() {
888 let weight = (score - max_score).exp() / denom;
889 let kv_offset = (batch_index * blocks + block) * config.head_dim + dim;
890 sum += weight * compressed_kv[kv_offset];
891 }
892 heads[head * config.head_dim + dim] = sum;
893 }
894 }
895 }
896
897 let mut inter = vec![0.0f32; config.groups * config.group_dim];
898 for group in 0..config.groups {
899 for group_out in 0..config.group_dim {
900 let mut sum = 0.0f32;
901 for local_head in 0..heads_per_group {
902 for dim in 0..config.head_dim {
903 let flat = local_head * config.head_dim + dim;
904 let head = group * heads_per_group + local_head;
905 let weight_offset = (group * heads_per_group * config.head_dim + flat)
906 * config.group_dim
907 + group_out;
908 sum +=
909 heads[head * config.head_dim + dim] * weight_group[weight_offset];
910 }
911 }
912 inter[group * config.group_dim + group_out] = sum;
913 }
914 }
915
916 for output_dim in 0..config.hidden_dim {
917 let mut sum = 0.0f32;
918 for inter_dim in 0..config.groups * config.group_dim {
919 sum +=
920 inter[inter_dim] * weight_final[inter_dim * config.hidden_dim + output_dim];
921 }
922 out[(batch_index * config.seq_len + query_index) * config.hidden_dim
923 + output_dim] = sum;
924 }
925 }
926 }
927 out
928}
929
930pub fn multi_token_attention_f32(
931 scores: &[f32],
932 weight: &[f32],
933 bias: Option<&[f32]>,
934 batch: usize,
935 channels_in: usize,
936 channels_out: usize,
937 seq_len: usize,
938 kernel_h: usize,
939 kernel_w: usize,
940 stride_h: usize,
941 stride_w: usize,
942 padding_h: usize,
943 padding_w: usize,
944 dilation_h: usize,
945 dilation_w: usize,
946 groups: usize,
947 sparse: bool,
948) -> Vec<f32> {
949 let output_h = conv_output_len(seq_len, kernel_h, stride_h, padding_h, dilation_h);
950 let output_w = conv_output_len(seq_len, kernel_w, stride_w, padding_w, dilation_w);
951 assert_eq!(output_h, output_w);
952 let mut probabilities = vec![0.0f32; batch * channels_in * seq_len * seq_len];
953 for batch_index in 0..batch {
954 for channel in 0..channels_in {
955 for row in 0..seq_len {
956 let base = ((batch_index * channels_in + channel) * seq_len + row) * seq_len;
957 if sparse {
958 let row_scores = &scores[base..base + row + 1];
959 let tau = sparsemax_tau(row_scores);
960 for col in 0..=row {
961 probabilities[base + col] = (scores[base + col] - tau).max(0.0);
962 }
963 } else {
964 let max_score = (0..=row)
965 .map(|col| scores[base + col])
966 .fold(f32::NEG_INFINITY, f32::max);
967 let denom = (0..=row)
968 .map(|col| (scores[base + col] - max_score).exp())
969 .sum::<f32>();
970 for col in 0..=row {
971 probabilities[base + col] = (scores[base + col] - max_score).exp() / denom;
972 }
973 }
974 }
975 }
976 }
977
978 let channels_in_per_group = channels_in / groups;
979 let channels_out_per_group = channels_out / groups;
980 let mut out = vec![0.0f32; batch * channels_out * output_h * output_w];
981 for batch_index in 0..batch {
982 for output_channel in 0..channels_out {
983 let group = output_channel / channels_out_per_group;
984 for output_row in 0..output_h {
985 for output_col in 0..output_w {
986 let out_offset = ((batch_index * channels_out + output_channel) * output_h
987 + output_row)
988 * output_w
989 + output_col;
990 if output_col > output_row {
991 continue;
992 }
993 let mut sum = bias.map_or(0.0f32, |bias| bias[output_channel]);
994 for local_input_channel in 0..channels_in_per_group {
995 let input_channel = group * channels_in_per_group + local_input_channel;
996 for kernel_row in 0..kernel_h {
997 let input_row = output_row * stride_h + kernel_row * dilation_h;
998 let Some(input_row) = input_row.checked_sub(padding_h) else {
999 continue;
1000 };
1001 if input_row >= seq_len {
1002 continue;
1003 }
1004 for kernel_col in 0..kernel_w {
1005 let input_col = output_col * stride_w + kernel_col * dilation_w;
1006 let Some(input_col) = input_col.checked_sub(padding_w) else {
1007 continue;
1008 };
1009 if input_col >= seq_len || input_col > input_row {
1010 continue;
1011 }
1012 let prob_offset = ((batch_index * channels_in + input_channel)
1013 * seq_len
1014 + input_row)
1015 * seq_len
1016 + input_col;
1017 let weight_offset = ((output_channel * channels_in_per_group
1018 + local_input_channel)
1019 * kernel_h
1020 + kernel_row)
1021 * kernel_w
1022 + kernel_col;
1023 sum += probabilities[prob_offset] * weight[weight_offset];
1024 }
1025 }
1026 }
1027 out[out_offset] = sum;
1028 }
1029 }
1030 }
1031 }
1032 out
1033}
1034
1035pub fn sparsemax_tau(values: &[f32]) -> f32 {
1036 let mut sorted = values.to_vec();
1037 sorted.sort_by(|left, right| right.partial_cmp(left).unwrap());
1038 let mut sum = 0.0f32;
1039 let mut support = 0usize;
1040 for (index, value) in sorted.iter().copied().enumerate() {
1041 sum += value;
1042 let threshold = (sum - 1.0) / (index + 1) as f32;
1043 if value > threshold {
1044 support = index + 1;
1045 }
1046 }
1047 let support_sum = sorted.iter().take(support).sum::<f32>();
1048 (support_sum - 1.0) / support as f32
1049}
1050
1051pub fn conv_output_len(
1052 input: usize,
1053 kernel: usize,
1054 stride: usize,
1055 padding: usize,
1056 dilation: usize,
1057) -> usize {
1058 let effective_kernel = dilation * (kernel - 1) + 1;
1059 let padded = input + padding * 2;
1060 if padded < effective_kernel {
1061 return 0;
1062 }
1063 (padded - effective_kernel) / stride + 1
1064}
1065
1066pub fn sliding_window_attention_f32(
1067 query: &[f32],
1068 key: &[f32],
1069 value: &[f32],
1070 batch: usize,
1071 query_len: usize,
1072 key_len: usize,
1073 heads: usize,
1074 kv_heads: usize,
1075 head_dim: usize,
1076 query_start: usize,
1077 key_start: usize,
1078 window: usize,
1079 scale: f32,
1080 output_scale: f32,
1081) -> Vec<f32> {
1082 let q_features = heads * head_dim;
1083 let kv_features = kv_heads * head_dim;
1084 let query_group_size = heads / kv_heads;
1085 let mut out = vec![0.0f32; batch * query_len * q_features];
1086 for batch_index in 0..batch {
1087 for query_index in 0..query_len {
1088 let query_abs = query_start + query_index;
1089 for head in 0..heads {
1090 let kv_head = head / query_group_size;
1091 let mut scores = Vec::new();
1092 for key_index in 0..key_len {
1093 let key_abs = key_start + key_index;
1094 if key_abs > query_abs || key_abs + window <= query_abs {
1095 continue;
1096 }
1097 let mut score = 0.0f32;
1098 for dim in 0..head_dim {
1099 let query_offset = batch_index * query_len * q_features
1100 + query_index * q_features
1101 + head * head_dim
1102 + dim;
1103 let key_offset = batch_index * key_len * kv_features
1104 + key_index * kv_features
1105 + kv_head * head_dim
1106 + dim;
1107 score += query[query_offset] * key[key_offset];
1108 }
1109 scores.push((key_index, score * scale));
1110 }
1111 if scores.is_empty() {
1112 continue;
1113 }
1114 let max_score = scores
1115 .iter()
1116 .map(|(_, score)| *score)
1117 .fold(f32::NEG_INFINITY, f32::max);
1118 let denom = scores
1119 .iter()
1120 .map(|(_, score)| (*score - max_score).exp())
1121 .sum::<f32>();
1122 for dim in 0..head_dim {
1123 let mut sum = 0.0f32;
1124 for &(key_index, score) in &scores {
1125 let weight = (score - max_score).exp() / denom;
1126 let value_offset = batch_index * key_len * kv_features
1127 + key_index * kv_features
1128 + kv_head * head_dim
1129 + dim;
1130 sum += weight * value[value_offset];
1131 }
1132 let output_offset = batch_index * query_len * q_features
1133 + query_index * q_features
1134 + head * head_dim
1135 + dim;
1136 out[output_offset] = sum * output_scale;
1137 }
1138 }
1139 }
1140 }
1141 out
1142}
1143
1144pub fn paged_kv_decode_attention_f32(
1145 query: &[f32],
1146 key_cache: &[f32],
1147 value_cache: &[f32],
1148 actual_seq_lens: &[i32],
1149 block_table: &[u32],
1150 batch: usize,
1151 heads: usize,
1152 kv_heads: usize,
1153 head_dim: usize,
1154 block_size: usize,
1155 block_table_batch_stride: usize,
1156 scale: f32,
1157) -> Vec<f32> {
1158 let mut out = vec![0.0f32; batch * heads * head_dim];
1159 let query_group_size = heads / kv_heads;
1160 let cache_token_stride = kv_heads * head_dim;
1161 let cache_block_stride = block_size * cache_token_stride;
1162 for batch_index in 0..batch {
1163 let seq_len = actual_seq_lens[batch_index] as usize;
1164 for head in 0..heads {
1165 let kv_head = head / query_group_size;
1166 let mut scores = Vec::with_capacity(seq_len);
1167 for key_index in 0..seq_len {
1168 let block = block_table
1169 [batch_index * block_table_batch_stride + key_index / block_size]
1170 as usize;
1171 let block_token = key_index % block_size;
1172 let mut score = 0.0f32;
1173 for dim in 0..head_dim {
1174 let query_offset = (batch_index * heads + head) * head_dim + dim;
1175 let key_offset = block * cache_block_stride
1176 + block_token * cache_token_stride
1177 + kv_head * head_dim
1178 + dim;
1179 score += query[query_offset] * key_cache[key_offset];
1180 }
1181 scores.push(score * scale);
1182 }
1183 if scores.is_empty() {
1184 continue;
1185 }
1186 let max_score = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
1187 let denom = scores
1188 .iter()
1189 .copied()
1190 .map(|score| (score - max_score).exp())
1191 .sum::<f32>();
1192 for dim in 0..head_dim {
1193 let mut sum = 0.0f32;
1194 for (key_index, score) in scores.iter().copied().enumerate() {
1195 let block = block_table
1196 [batch_index * block_table_batch_stride + key_index / block_size]
1197 as usize;
1198 let block_token = key_index % block_size;
1199 let value_offset = block * cache_block_stride
1200 + block_token * cache_token_stride
1201 + kv_head * head_dim
1202 + dim;
1203 sum += (score - max_score).exp() / denom * value_cache[value_offset];
1204 }
1205 out[(batch_index * heads + head) * head_dim + dim] = sum;
1206 }
1207 }
1208 }
1209 out
1210}
1211
1212pub fn paged_kv_prefill_attention_f32(
1213 query: &[f32],
1214 key_cache: &[f32],
1215 value_cache: &[f32],
1216 actual_seq_lens_q: &[i32],
1217 actual_seq_lens_kv: &[i32],
1218 actual_seq_offsets: &[i32],
1219 block_table: &[u32],
1220 batch: usize,
1221 total_query_tokens: usize,
1222 heads: usize,
1223 kv_heads: usize,
1224 head_dim: usize,
1225 block_size: usize,
1226 block_table_batch_stride: usize,
1227 causal: bool,
1228 scale: f32,
1229) -> (Vec<f32>, Vec<f32>) {
1230 let mut out = vec![0.0f32; total_query_tokens * heads * head_dim];
1231 let mut lse = vec![0.0f32; total_query_tokens * heads];
1232 let query_group_size = heads / kv_heads;
1233 let features = heads * head_dim;
1234 let cache_token_stride = kv_heads * head_dim;
1235 let cache_block_stride = block_size * cache_token_stride;
1236 for batch_index in 0..batch {
1237 let seq_len_q = actual_seq_lens_q[batch_index] as usize;
1238 let seq_len_kv = actual_seq_lens_kv[batch_index] as usize;
1239 let seq_offset = actual_seq_offsets[batch_index] as usize;
1240 for query_index in 0..seq_len_q {
1241 let global_token = seq_offset + query_index;
1242 for head in 0..heads {
1243 let kv_head = head / query_group_size;
1244 let mut scores = Vec::new();
1245 for key_index in 0..seq_len_kv {
1246 if causal && key_index > query_index {
1247 continue;
1248 }
1249 let block = block_table
1250 [batch_index * block_table_batch_stride + key_index / block_size]
1251 as usize;
1252 let block_token = key_index % block_size;
1253 let mut score = 0.0f32;
1254 for dim in 0..head_dim {
1255 let query_offset = global_token * features + head * head_dim + dim;
1256 let key_offset = block * cache_block_stride
1257 + block_token * cache_token_stride
1258 + kv_head * head_dim
1259 + dim;
1260 score += query[query_offset] * key_cache[key_offset];
1261 }
1262 scores.push((key_index, score * scale));
1263 }
1264 if scores.is_empty() {
1265 lse[global_token * heads + head] = -1.0e20;
1266 continue;
1267 }
1268 let max_score = scores
1269 .iter()
1270 .map(|(_, score)| *score)
1271 .fold(f32::NEG_INFINITY, f32::max);
1272 let denom = scores
1273 .iter()
1274 .map(|(_, score)| (*score - max_score).exp())
1275 .sum::<f32>();
1276 lse[global_token * heads + head] = max_score + denom.ln();
1277 for dim in 0..head_dim {
1278 let mut sum = 0.0f32;
1279 for &(key_index, score) in &scores {
1280 let block = block_table
1281 [batch_index * block_table_batch_stride + key_index / block_size]
1282 as usize;
1283 let block_token = key_index % block_size;
1284 let value_offset = block * cache_block_stride
1285 + block_token * cache_token_stride
1286 + kv_head * head_dim
1287 + dim;
1288 sum += (score - max_score).exp() / denom * value_cache[value_offset];
1289 }
1290 out[global_token * features + head * head_dim + dim] = sum;
1291 }
1292 }
1293 }
1294 }
1295 (out, lse)
1296}
1297
1298pub fn ragged_kv_prefill_attention_f32(
1299 query: &[f32],
1300 key: &[f32],
1301 value: &[f32],
1302 actual_seq_lens_q: &[i32],
1303 actual_seq_lens_kv: &[i32],
1304 actual_seq_offsets: &[i32],
1305 batch: usize,
1306 total_query_tokens: usize,
1307 heads: usize,
1308 kv_heads: usize,
1309 head_dim: usize,
1310 causal: bool,
1311 scale: f32,
1312) -> (Vec<f32>, Vec<f32>) {
1313 let mut out = vec![0.0f32; total_query_tokens * heads * head_dim];
1314 let mut lse = vec![0.0f32; total_query_tokens * heads];
1315 let query_group_size = heads / kv_heads;
1316 let features = heads * head_dim;
1317 let kv_features = kv_heads * head_dim;
1318 for batch_index in 0..batch {
1319 let seq_len_q = actual_seq_lens_q[batch_index] as usize;
1320 let seq_len_kv = actual_seq_lens_kv[batch_index] as usize;
1321 let seq_offset = actual_seq_offsets[batch_index] as usize;
1322 for query_index in 0..seq_len_q {
1323 let global_token = seq_offset + query_index;
1324 for head in 0..heads {
1325 let kv_head = head / query_group_size;
1326 let mut scores = Vec::new();
1327 for key_index in 0..seq_len_kv {
1328 if causal && key_index > query_index {
1329 continue;
1330 }
1331 let mut score = 0.0f32;
1332 for dim in 0..head_dim {
1333 let query_offset = global_token * features + head * head_dim + dim;
1334 let key_offset =
1335 (seq_offset + key_index) * kv_features + kv_head * head_dim + dim;
1336 score += query[query_offset] * key[key_offset];
1337 }
1338 scores.push((key_index, score * scale));
1339 }
1340 if scores.is_empty() {
1341 lse[global_token * heads + head] = -1.0e20;
1342 continue;
1343 }
1344 let max_score = scores
1345 .iter()
1346 .map(|(_, score)| *score)
1347 .fold(f32::NEG_INFINITY, f32::max);
1348 let denom = scores
1349 .iter()
1350 .map(|(_, score)| (*score - max_score).exp())
1351 .sum::<f32>();
1352 lse[global_token * heads + head] = max_score + denom.ln();
1353 for dim in 0..head_dim {
1354 let mut sum = 0.0f32;
1355 for &(key_index, score) in &scores {
1356 let value_offset =
1357 (seq_offset + key_index) * kv_features + kv_head * head_dim + dim;
1358 sum += (score - max_score).exp() / denom * value[value_offset];
1359 }
1360 out[global_token * features + head * head_dim + dim] = sum;
1361 }
1362 }
1363 }
1364 }
1365 (out, lse)
1366}
1367
1368pub fn mla_decode_lse_f32(
1369 query: &[f32],
1370 query_pe: &[f32],
1371 key_value: &[f32],
1372 key_pe: &[f32],
1373 batch: usize,
1374 key_len: usize,
1375 heads: usize,
1376 head_dim: usize,
1377 pe_dim: usize,
1378 scale: f32,
1379) -> (Vec<f32>, Vec<f32>) {
1380 let q_features = heads * head_dim;
1381 let qpe_features = heads * pe_dim;
1382 let mut out = vec![0.0f32; batch * q_features];
1383 let mut lse = vec![0.0f32; batch * heads];
1384 for batch_index in 0..batch {
1385 for head in 0..heads {
1386 let mut scores = Vec::new();
1387 for key_index in 0..key_len {
1388 let mut score = 0.0f32;
1389 for dim in 0..head_dim {
1390 let query_offset = batch_index * q_features + head * head_dim + dim;
1391 let key_offset = batch_index * key_len * head_dim + key_index * head_dim + dim;
1392 score += query[query_offset] * key_value[key_offset];
1393 }
1394 for dim in 0..pe_dim {
1395 let query_offset = batch_index * qpe_features + head * pe_dim + dim;
1396 let key_offset = batch_index * key_len * pe_dim + key_index * pe_dim + dim;
1397 score += query_pe[query_offset] * key_pe[key_offset];
1398 }
1399 scores.push((key_index, score * scale));
1400 }
1401 let max_score = scores
1402 .iter()
1403 .map(|(_, score)| *score)
1404 .fold(f32::NEG_INFINITY, f32::max);
1405 let denom = scores
1406 .iter()
1407 .map(|(_, score)| (*score - max_score).exp())
1408 .sum::<f32>();
1409 lse[batch_index * heads + head] = max_score + denom.ln();
1410 for dim in 0..head_dim {
1411 let mut sum = 0.0f32;
1412 for &(key_index, score) in &scores {
1413 let weight = (score - max_score).exp() / denom;
1414 let value_offset =
1415 batch_index * key_len * head_dim + key_index * head_dim + dim;
1416 sum += weight * key_value[value_offset];
1417 }
1418 out[batch_index * q_features + head * head_dim + dim] = sum;
1419 }
1420 }
1421 }
1422 (out, lse)
1423}
1424
1425pub fn paged_mla_decode_attention_f32(
1426 query: &[f32],
1427 query_pe: &[f32],
1428 key_value_cache: &[f32],
1429 key_pe_cache: &[f32],
1430 actual_seq_lens: &[i32],
1431 block_table: &[u32],
1432 batch: usize,
1433 heads: usize,
1434 head_dim: usize,
1435 pe_dim: usize,
1436 block_size: usize,
1437 block_table_batch_stride: usize,
1438 scale: f32,
1439 output_scale: f32,
1440) -> (Vec<f32>, Vec<f32>) {
1441 let q_features = heads * head_dim;
1442 let qpe_features = heads * pe_dim;
1443 let cache_block_stride = block_size * head_dim;
1444 let pe_cache_block_stride = block_size * pe_dim;
1445 let mut out = vec![0.0f32; batch * q_features];
1446 let mut lse = vec![0.0f32; batch * heads];
1447 for batch_index in 0..batch {
1448 let seq_len = actual_seq_lens[batch_index] as usize;
1449 for head in 0..heads {
1450 let mut scores = Vec::with_capacity(seq_len);
1451 for key_index in 0..seq_len {
1452 let block = block_table
1453 [batch_index * block_table_batch_stride + key_index / block_size]
1454 as usize;
1455 let block_token = key_index % block_size;
1456 let mut score = 0.0f32;
1457 for dim in 0..head_dim {
1458 let query_offset = batch_index * q_features + head * head_dim + dim;
1459 let key_offset = block * cache_block_stride + block_token * head_dim + dim;
1460 score += query[query_offset] * key_value_cache[key_offset];
1461 }
1462 for dim in 0..pe_dim {
1463 let query_offset = batch_index * qpe_features + head * pe_dim + dim;
1464 let key_offset = block * pe_cache_block_stride + block_token * pe_dim + dim;
1465 score += query_pe[query_offset] * key_pe_cache[key_offset];
1466 }
1467 scores.push((key_index, score * scale));
1468 }
1469 if scores.is_empty() {
1470 lse[batch_index * heads + head] = -1.0e20;
1471 continue;
1472 }
1473 let max_score = scores
1474 .iter()
1475 .map(|(_, score)| *score)
1476 .fold(f32::NEG_INFINITY, f32::max);
1477 let denom = scores
1478 .iter()
1479 .map(|(_, score)| (*score - max_score).exp())
1480 .sum::<f32>();
1481 lse[batch_index * heads + head] = max_score + denom.ln();
1482 for dim in 0..head_dim {
1483 let mut sum = 0.0f32;
1484 for &(key_index, score) in &scores {
1485 let block = block_table
1486 [batch_index * block_table_batch_stride + key_index / block_size]
1487 as usize;
1488 let block_token = key_index % block_size;
1489 let value_offset = block * cache_block_stride + block_token * head_dim + dim;
1490 sum += (score - max_score).exp() / denom * key_value_cache[value_offset];
1491 }
1492 out[batch_index * q_features + head * head_dim + dim] = sum * output_scale;
1493 }
1494 }
1495 }
1496 (out, lse)
1497}
1498
1499pub fn mla_prefill_f32(
1500 query: &[f32],
1501 query_pe: &[f32],
1502 key: &[f32],
1503 key_pe: &[f32],
1504 value: &[f32],
1505 batch: usize,
1506 query_len: usize,
1507 key_len: usize,
1508 heads: usize,
1509 kv_heads: usize,
1510 head_dim: usize,
1511 pe_dim: usize,
1512 scale: f32,
1513) -> Vec<f32> {
1514 let q_features = heads * head_dim;
1515 let qpe_features = heads * pe_dim;
1516 let kv_features = kv_heads * head_dim;
1517 let kpe_features = kv_heads * pe_dim;
1518 let query_group_size = heads / kv_heads;
1519 let mut out = vec![0.0f32; batch * query_len * q_features];
1520 for batch_index in 0..batch {
1521 for head in 0..heads {
1522 let kv_head = head / query_group_size;
1523 for query_index in 0..query_len {
1524 let mut scores = Vec::new();
1525 for key_index in 0..key_len {
1526 if key_index > query_index {
1527 continue;
1528 }
1529 let mut score = 0.0f32;
1530 for dim in 0..head_dim {
1531 let query_offset = batch_index * query_len * q_features
1532 + query_index * q_features
1533 + head * head_dim
1534 + dim;
1535 let key_offset = batch_index * key_len * kv_features
1536 + key_index * kv_features
1537 + kv_head * head_dim
1538 + dim;
1539 score += query[query_offset] * key[key_offset];
1540 }
1541 for dim in 0..pe_dim {
1542 let query_offset = batch_index * query_len * qpe_features
1543 + query_index * qpe_features
1544 + head * pe_dim
1545 + dim;
1546 let key_offset = batch_index * key_len * kpe_features
1547 + key_index * kpe_features
1548 + kv_head * pe_dim
1549 + dim;
1550 score += query_pe[query_offset] * key_pe[key_offset];
1551 }
1552 scores.push((key_index, score * scale));
1553 }
1554 let max_score = scores
1555 .iter()
1556 .map(|(_, score)| *score)
1557 .fold(f32::NEG_INFINITY, f32::max);
1558 let denom = scores
1559 .iter()
1560 .map(|(_, score)| (*score - max_score).exp())
1561 .sum::<f32>();
1562 for dim in 0..head_dim {
1563 let mut sum = 0.0f32;
1564 for &(key_index, score) in &scores {
1565 let weight = (score - max_score).exp() / denom;
1566 let value_offset = batch_index * key_len * kv_features
1567 + key_index * kv_features
1568 + kv_head * head_dim
1569 + dim;
1570 sum += weight * value[value_offset];
1571 }
1572 let output_offset = batch_index * query_len * q_features
1573 + query_index * q_features
1574 + head * head_dim
1575 + dim;
1576 out[output_offset] = sum;
1577 }
1578 }
1579 }
1580 }
1581 out
1582}
1583
1584pub fn sparse_mla_prefill_f32(
1585 query: &[f32],
1586 query_pe: &[f32],
1587 key: &[f32],
1588 key_pe: &[f32],
1589 value: &[f32],
1590 indices: &[i32],
1591 batch: usize,
1592 query_len: usize,
1593 key_len: usize,
1594 heads: usize,
1595 kv_heads: usize,
1596 head_dim: usize,
1597 pe_dim: usize,
1598 topk: usize,
1599 scale: f32,
1600) -> Vec<f32> {
1601 let q_features = heads * head_dim;
1602 let qpe_features = heads * pe_dim;
1603 let kv_features = kv_heads * head_dim;
1604 let query_group_size = heads / kv_heads;
1605 let mut out = vec![0.0f32; batch * query_len * q_features];
1606 for batch_index in 0..batch {
1607 for head in 0..heads {
1608 let kv_head = head / query_group_size;
1609 for query_index in 0..query_len {
1610 let mut scores = Vec::new();
1611 for topk_index in 0..topk {
1612 let index_offset = batch_index * query_len * kv_heads * topk
1613 + query_index * kv_heads * topk
1614 + kv_head * topk
1615 + topk_index;
1616 let key_index = indices[index_offset];
1617 if key_index < 0 {
1618 continue;
1619 }
1620 let key_index = key_index as usize;
1621 if key_index >= key_len || key_index > query_index {
1622 continue;
1623 }
1624 let mut score = 0.0f32;
1625 for dim in 0..head_dim {
1626 let query_offset = batch_index * query_len * q_features
1627 + query_index * q_features
1628 + head * head_dim
1629 + dim;
1630 let key_offset = batch_index * key_len * kv_features
1631 + key_index * kv_features
1632 + kv_head * head_dim
1633 + dim;
1634 score += query[query_offset] * key[key_offset];
1635 }
1636 for dim in 0..pe_dim {
1637 let query_offset = batch_index * query_len * qpe_features
1638 + query_index * qpe_features
1639 + head * pe_dim
1640 + dim;
1641 let key_offset = batch_index * key_len * pe_dim + key_index * pe_dim + dim;
1642 score += query_pe[query_offset] * key_pe[key_offset];
1643 }
1644 scores.push((key_index, score * scale));
1645 }
1646 if scores.is_empty() {
1647 continue;
1648 }
1649 let max_score = scores
1650 .iter()
1651 .map(|(_, score)| *score)
1652 .fold(f32::NEG_INFINITY, f32::max);
1653 let denom = scores
1654 .iter()
1655 .map(|(_, score)| (*score - max_score).exp())
1656 .sum::<f32>();
1657 for dim in 0..head_dim {
1658 let mut sum = 0.0f32;
1659 for &(key_index, score) in &scores {
1660 let weight = (score - max_score).exp() / denom;
1661 let value_offset = batch_index * key_len * kv_features
1662 + key_index * kv_features
1663 + kv_head * head_dim
1664 + dim;
1665 sum += weight * value[value_offset];
1666 }
1667 let output_offset = batch_index * query_len * q_features
1668 + query_index * q_features
1669 + head * head_dim
1670 + dim;
1671 out[output_offset] = sum;
1672 }
1673 }
1674 }
1675 }
1676 out
1677}
1678
1679pub fn fmha_prefill_lse_f32(
1680 query: &[f32],
1681 key: &[f32],
1682 value: &[f32],
1683 batch: usize,
1684 query_len: usize,
1685 key_len: usize,
1686 heads: usize,
1687 kv_heads: usize,
1688 head_dim: usize,
1689 scale: f32,
1690 causal: bool,
1691) -> (Vec<f32>, Vec<f32>) {
1692 let q_features = heads * head_dim;
1693 let kv_features = kv_heads * head_dim;
1694 let query_group_size = heads / kv_heads;
1695 let mut out = vec![0.0f32; batch * query_len * q_features];
1696 let mut lse = vec![0.0f32; batch * heads * query_len];
1697 for batch_index in 0..batch {
1698 for head in 0..heads {
1699 let kv_head = head / query_group_size;
1700 for query_index in 0..query_len {
1701 let mut scores = Vec::new();
1702 for key_index in 0..key_len {
1703 if causal && key_index > query_index {
1704 continue;
1705 }
1706 let mut score = 0.0f32;
1707 for dim in 0..head_dim {
1708 let query_offset = batch_index * query_len * q_features
1709 + query_index * q_features
1710 + head * head_dim
1711 + dim;
1712 let key_offset = batch_index * key_len * kv_features
1713 + key_index * kv_features
1714 + kv_head * head_dim
1715 + dim;
1716 score += query[query_offset] * key[key_offset];
1717 }
1718 scores.push((key_index, score * scale));
1719 }
1720 let max_score = scores
1721 .iter()
1722 .map(|(_, score)| *score)
1723 .fold(f32::NEG_INFINITY, f32::max);
1724 let denom = scores
1725 .iter()
1726 .map(|(_, score)| (*score - max_score).exp())
1727 .sum::<f32>();
1728 lse[batch_index * heads * query_len + head * query_len + query_index] =
1729 max_score + denom.ln();
1730 for dim in 0..head_dim {
1731 let mut sum = 0.0f32;
1732 for &(key_index, score) in &scores {
1733 let weight = (score - max_score).exp() / denom;
1734 let value_offset = batch_index * key_len * kv_features
1735 + key_index * kv_features
1736 + kv_head * head_dim
1737 + dim;
1738 sum += weight * value[value_offset];
1739 }
1740 let output_offset = batch_index * query_len * q_features
1741 + query_index * q_features
1742 + head * head_dim
1743 + dim;
1744 out[output_offset] = sum;
1745 }
1746 }
1747 }
1748 }
1749 (out, lse)
1750}
1751
1752pub fn softcapped_window_attention_f32(
1753 query: &[f32],
1754 key: &[f32],
1755 value: &[f32],
1756 batch: usize,
1757 query_len: usize,
1758 key_len: usize,
1759 heads: usize,
1760 kv_heads: usize,
1761 head_dim: usize,
1762 scale: f32,
1763 causal: bool,
1764 window_size: usize,
1765 soft_cap: Option<f32>,
1766) -> Vec<f32> {
1767 let query_head_stride = query_len * head_dim;
1768 let key_head_stride = key_len * head_dim;
1769 let query_group_size = heads / kv_heads;
1770 let mut out = vec![0.0f32; batch * heads * query_len * head_dim];
1771 for batch_index in 0..batch {
1772 for head in 0..heads {
1773 let kv_head = head / query_group_size;
1774 for query_index in 0..query_len {
1775 let mut scores = Vec::new();
1776 for key_index in 0..key_len {
1777 if causal && key_index > query_index {
1778 continue;
1779 }
1780 if window_size > 0
1781 && (key_index + window_size < query_index
1782 || key_index > query_index + window_size)
1783 {
1784 continue;
1785 }
1786 let mut score = 0.0f32;
1787 for dim in 0..head_dim {
1788 let query_offset = batch_index * heads * query_head_stride
1789 + head * query_head_stride
1790 + query_index * head_dim
1791 + dim;
1792 let key_offset = batch_index * kv_heads * key_head_stride
1793 + kv_head * key_head_stride
1794 + key_index * head_dim
1795 + dim;
1796 score += query[query_offset] * key[key_offset];
1797 }
1798 let mut score = score * scale;
1799 if let Some(soft_cap) = soft_cap {
1800 score = (score / soft_cap).tanh() * soft_cap;
1801 }
1802 scores.push((key_index, score));
1803 }
1804 if scores.is_empty() {
1805 continue;
1806 }
1807 let max_score = scores
1808 .iter()
1809 .map(|(_, score)| *score)
1810 .fold(f32::NEG_INFINITY, f32::max);
1811 let denom = scores
1812 .iter()
1813 .map(|(_, score)| (*score - max_score).exp())
1814 .sum::<f32>();
1815 for dim in 0..head_dim {
1816 let mut sum = 0.0f32;
1817 for &(key_index, score) in &scores {
1818 let weight = (score - max_score).exp() / denom;
1819 let value_offset = batch_index * kv_heads * key_head_stride
1820 + kv_head * key_head_stride
1821 + key_index * head_dim
1822 + dim;
1823 sum += weight * value[value_offset];
1824 }
1825 let output_offset = batch_index * heads * query_head_stride
1826 + head * query_head_stride
1827 + query_index * head_dim
1828 + dim;
1829 out[output_offset] = sum;
1830 }
1831 }
1832 }
1833 }
1834 out
1835}
1836
1837pub fn softcapped_window_decode_lse_f32(
1838 query: &[f32],
1839 key: &[f32],
1840 value: &[f32],
1841 batch: usize,
1842 key_len: usize,
1843 heads: usize,
1844 kv_heads: usize,
1845 head_dim: usize,
1846 scale: f32,
1847 window_size: usize,
1848 soft_cap: Option<f32>,
1849) -> (Vec<f32>, Vec<f32>) {
1850 let key_head_stride = key_len * head_dim;
1851 let query_group_size = heads / kv_heads;
1852 let mut out = vec![0.0f32; batch * heads * head_dim];
1853 let mut lse = vec![0.0f32; batch * heads];
1854 let query_position = key_len - 1;
1855 for batch_index in 0..batch {
1856 for head in 0..heads {
1857 let kv_head = head / query_group_size;
1858 let mut scores = Vec::new();
1859 for key_index in 0..key_len {
1860 if window_size > 0 && key_index + window_size < query_position {
1861 continue;
1862 }
1863 let mut score = 0.0f32;
1864 for dim in 0..head_dim {
1865 let query_offset = batch_index * heads * head_dim + head * head_dim + dim;
1866 let key_offset = batch_index * kv_heads * key_head_stride
1867 + kv_head * key_head_stride
1868 + key_index * head_dim
1869 + dim;
1870 score += query[query_offset] * key[key_offset];
1871 }
1872 let mut score = score * scale;
1873 if let Some(soft_cap) = soft_cap {
1874 score = (score / soft_cap).tanh() * soft_cap;
1875 }
1876 scores.push((key_index, score));
1877 }
1878 let max_score = scores
1879 .iter()
1880 .map(|(_, score)| *score)
1881 .fold(f32::NEG_INFINITY, f32::max);
1882 let denom = scores
1883 .iter()
1884 .map(|(_, score)| (*score - max_score).exp())
1885 .sum::<f32>();
1886 lse[batch_index * heads + head] = max_score + denom.ln();
1887 for dim in 0..head_dim {
1888 let mut sum = 0.0f32;
1889 for &(key_index, score) in &scores {
1890 let weight = (score - max_score).exp() / denom;
1891 let value_offset = batch_index * kv_heads * key_head_stride
1892 + kv_head * key_head_stride
1893 + key_index * head_dim
1894 + dim;
1895 sum += weight * value[value_offset];
1896 }
1897 let output_offset = batch_index * heads * head_dim + head * head_dim + dim;
1898 out[output_offset] = sum;
1899 }
1900 }
1901 }
1902 (out, lse)
1903}
1904
1905pub fn attention_sink_decode_f32(
1906 query: &[f32],
1907 key: &[f32],
1908 value: &[f32],
1909 sinks: &[f32],
1910 batch: usize,
1911 key_len: usize,
1912 heads: usize,
1913 kv_heads: usize,
1914 head_dim: usize,
1915 start_q: usize,
1916 window: usize,
1917 scale: f32,
1918) -> Vec<f32> {
1919 let q_features = heads * head_dim;
1920 let kv_features = kv_heads * head_dim;
1921 let query_group_size = heads / kv_heads;
1922 let mut out = vec![0.0f32; batch * q_features];
1923 for batch_index in 0..batch {
1924 for head in 0..heads {
1925 let kv_head = head / query_group_size;
1926 let window_start = if window == 0 {
1927 0
1928 } else {
1929 (start_q + 1).saturating_sub(window)
1930 };
1931 let mut scores = vec![sinks[head]];
1932 let mut key_indices = Vec::new();
1933 for key_index in 0..key_len {
1934 if key_index > start_q || key_index < window_start {
1935 continue;
1936 }
1937 let mut score = 0.0f32;
1938 for dim in 0..head_dim {
1939 let query_offset = batch_index * q_features + head * head_dim + dim;
1940 let key_offset = batch_index * key_len * kv_features
1941 + key_index * kv_features
1942 + kv_head * head_dim
1943 + dim;
1944 score += query[query_offset] * key[key_offset];
1945 }
1946 scores.push(score * scale);
1947 key_indices.push(key_index);
1948 }
1949 let max_score = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
1950 let weights = scores
1951 .iter()
1952 .copied()
1953 .map(|score| (score - max_score).exp())
1954 .collect::<Vec<_>>();
1955 let denom = weights.iter().sum::<f32>();
1956 for (index, key_index) in key_indices.iter().copied().enumerate() {
1957 let weight = weights[index + 1] / denom;
1958 for dim in 0..head_dim {
1959 let value_offset = batch_index * key_len * kv_features
1960 + key_index * kv_features
1961 + kv_head * head_dim
1962 + dim;
1963 out[batch_index * q_features + head * head_dim + dim] +=
1964 weight * value[value_offset];
1965 }
1966 }
1967 }
1968 }
1969 out
1970}
1971
1972pub fn attention_sink_prefill_f32(
1973 query: &[f32],
1974 key: &[f32],
1975 value: &[f32],
1976 sinks: &[f32],
1977 batch: usize,
1978 query_len: usize,
1979 key_len: usize,
1980 heads: usize,
1981 kv_heads: usize,
1982 head_dim: usize,
1983 start_q: usize,
1984 window: usize,
1985 scale: f32,
1986 causal: bool,
1987) -> Vec<f32> {
1988 let q_features = heads * head_dim;
1989 let kv_features = kv_heads * head_dim;
1990 let query_group_size = heads / kv_heads;
1991 let mut out = vec![0.0f32; batch * query_len * q_features];
1992 for batch_index in 0..batch {
1993 for query_index in 0..query_len {
1994 let query_pos = start_q + query_index;
1995 let window_start = if window == 0 {
1996 0
1997 } else {
1998 query_pos.saturating_sub(window - 1)
1999 };
2000 for (head, sink_value) in sinks.iter().copied().enumerate().take(heads) {
2001 let kv_head = head / query_group_size;
2002 let mut scores = vec![sink_value];
2003 let mut key_indices = Vec::new();
2004 for key_index in 0..key_len {
2005 if (causal && key_index > query_pos)
2006 || (window != 0 && key_index < window_start)
2007 {
2008 continue;
2009 }
2010 let mut score = 0.0f32;
2011 for dim in 0..head_dim {
2012 let query_offset = batch_index * query_len * q_features
2013 + query_index * q_features
2014 + head * head_dim
2015 + dim;
2016 let key_offset = batch_index * key_len * kv_features
2017 + key_index * kv_features
2018 + kv_head * head_dim
2019 + dim;
2020 score += query[query_offset] * key[key_offset];
2021 }
2022 scores.push(score * scale);
2023 key_indices.push(key_index);
2024 }
2025 let max_score = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
2026 let weights = scores
2027 .iter()
2028 .copied()
2029 .map(|score| (score - max_score).exp())
2030 .collect::<Vec<_>>();
2031 let denom = weights.iter().sum::<f32>();
2032 for (index, key_index) in key_indices.iter().copied().enumerate() {
2033 let weight = weights[index + 1] / denom;
2034 for dim in 0..head_dim {
2035 let value_offset = batch_index * key_len * kv_features
2036 + key_index * kv_features
2037 + kv_head * head_dim
2038 + dim;
2039 let output_offset = batch_index * query_len * q_features
2040 + query_index * q_features
2041 + head * head_dim
2042 + dim;
2043 out[output_offset] += weight * value[value_offset];
2044 }
2045 }
2046 }
2047 }
2048 }
2049 out
2050}
2051
2052pub fn splitk_reduce_f32(
2053 attn: &[f32],
2054 lse: &[f32],
2055 batch: usize,
2056 heads: usize,
2057 splits: usize,
2058 head_dim: usize,
2059) -> Vec<f32> {
2060 let mut out = vec![0.0f32; batch * heads * head_dim];
2061 for batch_index in 0..batch {
2062 for head in 0..heads {
2063 let max_lse = (0..splits)
2064 .map(|split| lse[batch_index * heads * splits + head * splits + split])
2065 .fold(f32::NEG_INFINITY, f32::max);
2066 let denom = (0..splits)
2067 .map(|split| {
2068 (lse[batch_index * heads * splits + head * splits + split] - max_lse).exp()
2069 })
2070 .sum::<f32>();
2071 for dim in 0..head_dim {
2072 let mut sum = 0.0f32;
2073 for split in 0..splits {
2074 let weight =
2075 (lse[batch_index * heads * splits + head * splits + split] - max_lse).exp();
2076 let attn_offset = batch_index * heads * splits * head_dim
2077 + head * splits * head_dim
2078 + split * head_dim
2079 + dim;
2080 sum += weight * attn[attn_offset];
2081 }
2082 out[batch_index * heads * head_dim + head * head_dim + dim] = sum / denom;
2083 }
2084 }
2085 }
2086 out
2087}