Skip to main content

singe_kernel/cpu/
attention.rs

1//! Prefill/decode attention kernels and related attention variants.
2
3#[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
30/// Causal left-window attention with contiguous grouped-query head mapping.
31///
32/// For a query at absolute position `q_abs`, this attends only to keys with
33/// `key_abs <= q_abs` and `key_abs + window > q_abs`.
34/// This is not the symmetric local attention mask used by transformer-lab's sliding-window components.
35pub 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}