1use crate::Engine;
13use cudarc::driver::{CudaSlice, DevicePtr, DevicePtrMut};
14use std::os::raw::c_void;
15
16pub static MLA_DECODE_SPLIT_DISPATCHES: std::sync::atomic::AtomicU64 =
19 std::sync::atomic::AtomicU64::new(0);
20
21fn mla_decode_split_on() -> bool {
28 std::env::var("MEMRA_MLA_DECODE_SPLIT").as_deref() == Ok("1")
29}
30
31fn mla_decode_split_for(blocks: usize, out_dim: usize) -> Option<i32> {
37 if !mla_decode_split_on() || blocks == 0 || blocks >= 1024 {
38 return None;
39 }
40 let want = 1024usize.div_ceil(blocks);
41 let cap = (out_dim / 32).max(1);
42 let split = want.min(cap);
43 if split <= 1 { None } else { Some(split as i32) }
44}
45
46fn mla_split_announce(kind: &str, t_q: usize, n_head: usize, split: i32) {
47 use std::sync::atomic::Ordering;
48 if MLA_DECODE_SPLIT_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
49 eprintln!(
50 "[mla-decode-split] engaged {kind} t={t_q} heads={n_head} split={split} \
51 (output-range split of the (token, head) blocks; MEMRA_MLA_DECODE_SPLIT=1)"
52 );
53 }
54}
55
56unsafe extern "C" {
57 pub fn memra_mla_rope_interleaved_f32(
58 x: *mut f32,
59 n_pos: i32,
60 n_vec: i32,
61 d_rope: i32,
62 positions: *const i32,
63 base: f32,
64 stream: *mut c_void,
65 ) -> i32;
66 pub fn memra_mla_split_latent_f32(
67 kv: *const f32,
68 c_kv: *mut f32,
69 k_pe: *mut f32,
70 t: i32,
71 kv_rank: i32,
72 d_rope: i32,
73 stream: *mut c_void,
74 ) -> i32;
75 pub fn memra_mla_append_latent_f32(
76 cache: *mut f32,
77 c_kv: *const f32,
78 k_pe: *const f32,
79 slot: i32,
80 t: i32,
81 kv_rank: i32,
82 d_rope: i32,
83 stream: *mut c_void,
84 ) -> i32;
85 pub fn memra_mla_absorb_q_f32(
86 q_nope: *const f32,
87 wk_b: *const f32,
88 q_lat: *mut f32,
89 t_q: i32,
90 n_head: i32,
91 d_nope: i32,
92 kv_rank: i32,
93 stream: *mut c_void,
94 ) -> i32;
95 pub fn memra_mla_decompress_v_f32(
96 o_lat: *const f32,
97 wv_b: *const f32,
98 out: *mut f32,
99 t_q: i32,
100 n_head: i32,
101 d_v: i32,
102 kv_rank: i32,
103 stream: *mut c_void,
104 ) -> i32;
105 #[allow(clippy::too_many_arguments)]
109 pub fn memra_mla_absorb_q_split_f32(
110 q_nope: *const f32,
111 wk_b: *const f32,
112 q_lat: *mut f32,
113 t_q: i32,
114 n_head: i32,
115 d_nope: i32,
116 kv_rank: i32,
117 split: i32,
118 stream: *mut c_void,
119 ) -> i32;
120 #[allow(clippy::too_many_arguments)]
122 pub fn memra_mla_decompress_v_split_f32(
123 o_lat: *const f32,
124 wv_b: *const f32,
125 out: *mut f32,
126 t_q: i32,
127 n_head: i32,
128 d_v: i32,
129 kv_rank: i32,
130 split: i32,
131 stream: *mut c_void,
132 ) -> i32;
133 pub fn memra_mla_attn_absorbed_f32(
134 q_lat: *const f32,
135 q_pe: *const f32,
136 cache: *const f32,
137 o_lat: *mut f32,
138 n_head: i32,
139 kv_rank: i32,
140 d_rope: i32,
141 t_q: i32,
142 t_kv: i32,
143 scale: f32,
144 stream: *mut c_void,
145 ) -> i32;
146 pub fn memra_mla_index_append_ring_f32(
147 plane: *mut f32,
148 a: *const f32,
149 b: *const f32,
150 slot: i32,
151 t: i32,
152 wa: i32,
153 wb: i32,
154 rows: i32,
155 stream: *mut c_void,
156 ) -> i32;
157 pub fn memra_mla_kpool_pool_keys_f32(
158 state: *const f32,
159 ape: *const f32,
160 pool_keys: *mut f32,
161 pool_begin: i32,
162 n_pools: i32,
163 pool: i32,
164 d: i32,
165 state_rows: i32,
166 stream: *mut c_void,
167 ) -> i32;
168 pub fn memra_mla_kpool_score_f32(
169 q: *const f32,
170 pool_keys: *const f32,
171 hw: *const f32,
172 score: *mut f32,
173 t_q: i32,
174 heads: i32,
175 d: i32,
176 n_pools: i32,
177 pool: i32,
178 first_pos: i32,
179 qk_scale: f32,
180 head_scale: f32,
181 stream: *mut c_void,
182 ) -> i32;
183 pub fn memra_mla_kpool_score_ref_f32(
184 q: *const f32,
185 pool_keys: *const f32,
186 hw: *const f32,
187 score: *mut f32,
188 t_q: i32,
189 heads: i32,
190 d: i32,
191 n_pools: i32,
192 pool: i32,
193 first_pos: i32,
194 qk_scale: f32,
195 head_scale: f32,
196 stream: *mut c_void,
197 ) -> i32;
198 pub fn memra_mla_kpool_select_f32(
199 score: *const f32,
200 idx: *mut i32,
201 t_q: i32,
202 n_pools: i32,
203 pool: i32,
204 select_k: i32,
205 width: i32,
206 first_pos: i32,
207 always_tail: i32,
208 stream: *mut c_void,
209 ) -> i32;
210 pub fn memra_mla_kpool_select_ref_f32(
211 score: *const f32,
212 idx: *mut i32,
213 t_q: i32,
214 n_pools: i32,
215 pool: i32,
216 select_k: i32,
217 width: i32,
218 first_pos: i32,
219 always_tail: i32,
220 stream: *mut c_void,
221 ) -> i32;
222 pub fn memra_mla_attn_gathered_f32(
223 q_lat: *const f32,
224 q_pe: *const f32,
225 cache: *const f32,
226 idx: *const i32,
227 o_lat: *mut f32,
228 n_head: i32,
229 kv_rank: i32,
230 d_rope: i32,
231 t_q: i32,
232 n_slots: i32,
233 scale: f32,
234 stream: *mut c_void,
235 ) -> i32;
236 fn memra_bf16_gemm_sb(
241 w_bf16: *const c_void,
242 x_bf16: *const c_void,
243 y: *mut c_void,
244 m: i32,
245 n: i32,
246 k: i32,
247 x_rs: i64,
248 x_bs: i64,
249 y_rs: i64,
250 y_bs: i64,
251 batch: i32,
252 y_is_bf16: i32,
253 ws: *mut c_void,
254 ws_bytes: usize,
255 stream: *mut c_void,
256 ) -> i32;
257}
258
259type Res<T> = Result<T, Box<dyn std::error::Error>>;
260
261fn ck(what: &str, rc: i32) -> Res<()> {
264 if rc == 0 {
265 return Ok(());
266 }
267 let detail = match rc {
268 40001 => " (d_rope must be even — interleaved rope rotates (2j, 2j+1) pairs)",
269 40002 => " (kv_rank exceeds the kernel's MLA_MAX_RANK shared-memory ceiling)",
270 40003 => " (d_rope exceeds the kernel's MLA_MAX_ROPE ceiling)",
271 40004 => " (t_q > t_kv — queries must be a suffix of the latent cache)",
272 40010 => " (k-pool size out of range — 1..=MLA_MAX_POOL)",
273 40011 => " (indexer head count out of range — 1..=1024, one thread per head)",
274 40012 => " (t_q * n_pools exceeds the grid.x contract)",
275 40017 => " (indexer head dim must be positive)",
276 40013 => {
277 " (always_select_tail=false: queries before the first complete pool would have an \
278 empty candidate set, which the memra-reference oracle refuses outright)"
279 }
280 40014 => " (index-list width is narrower than select_k * pool + pool - 1)",
281 40015 => " (empty gathered candidate list — a zero softmax denominator)",
282 r if (10000..20000).contains(&r) => " (cudaError)",
283 _ => "",
284 };
285 Err(format!("mla kernel `{what}` failed: rc {rc}{detail}").into())
286}
287
288impl Engine {
289 pub fn mla_rope_interleaved(
294 &self,
295 x: &mut CudaSlice<f32>,
296 pos_d: &CudaSlice<i32>,
297 n_pos: usize,
298 n_vec: usize,
299 d_rope: usize,
300 base: f32,
301 ) -> Res<()> {
302 if d_rope == 0 {
303 return Ok(());
304 }
305 let s = self.stream();
306 unsafe {
307 ck(
308 "rope_interleaved",
309 memra_mla_rope_interleaved_f32(
310 x.device_ptr_mut(&s).0 as *mut f32,
311 n_pos as i32,
312 n_vec as i32,
313 d_rope as i32,
314 pos_d.device_ptr(&s).0 as *const i32,
315 base,
316 s.cu_stream() as *mut c_void,
317 ),
318 )
319 }
320 }
321
322 pub fn mla_split_latent(
324 &self,
325 kv: &CudaSlice<f32>,
326 c_kv: &mut CudaSlice<f32>,
327 k_pe: &mut CudaSlice<f32>,
328 t: usize,
329 kv_rank: usize,
330 d_rope: usize,
331 ) -> Res<()> {
332 let s = self.stream();
333 unsafe {
334 ck(
335 "split_latent",
336 memra_mla_split_latent_f32(
337 kv.device_ptr(&s).0 as *const f32,
338 c_kv.device_ptr_mut(&s).0 as *mut f32,
339 k_pe.device_ptr_mut(&s).0 as *mut f32,
340 t as i32,
341 kv_rank as i32,
342 d_rope as i32,
343 s.cu_stream() as *mut c_void,
344 ),
345 )
346 }
347 }
348
349 #[allow(clippy::too_many_arguments)] pub fn mla_append_latent(
352 &self,
353 cache: &mut CudaSlice<f32>,
354 c_kv: &CudaSlice<f32>,
355 k_pe: &CudaSlice<f32>,
356 slot: usize,
357 t: usize,
358 kv_rank: usize,
359 d_rope: usize,
360 ) -> Res<()> {
361 let s = self.stream();
362 unsafe {
363 ck(
364 "append_latent",
365 memra_mla_append_latent_f32(
366 cache.device_ptr_mut(&s).0 as *mut f32,
367 c_kv.device_ptr(&s).0 as *const f32,
368 k_pe.device_ptr(&s).0 as *const f32,
369 slot as i32,
370 t as i32,
371 kv_rank as i32,
372 d_rope as i32,
373 s.cu_stream() as *mut c_void,
374 ),
375 )
376 }
377 }
378
379 #[allow(clippy::too_many_arguments)] pub fn mla_absorb_q(
382 &self,
383 q_nope: &CudaSlice<f32>,
384 wk_b: &CudaSlice<f32>,
385 q_lat: &mut CudaSlice<f32>,
386 t_q: usize,
387 n_head: usize,
388 d_nope: usize,
389 kv_rank: usize,
390 ) -> Res<()> {
391 let s = self.stream();
392 if let Some(split) = mla_decode_split_for(t_q * n_head, kv_rank) {
394 mla_split_announce("absorb_q", t_q, n_head, split);
395 return unsafe {
396 ck(
397 "absorb_q_split",
398 memra_mla_absorb_q_split_f32(
399 q_nope.device_ptr(&s).0 as *const f32,
400 wk_b.device_ptr(&s).0 as *const f32,
401 q_lat.device_ptr_mut(&s).0 as *mut f32,
402 t_q as i32,
403 n_head as i32,
404 d_nope as i32,
405 kv_rank as i32,
406 split,
407 s.cu_stream() as *mut c_void,
408 ),
409 )
410 };
411 }
412 unsafe {
413 ck(
414 "absorb_q",
415 memra_mla_absorb_q_f32(
416 q_nope.device_ptr(&s).0 as *const f32,
417 wk_b.device_ptr(&s).0 as *const f32,
418 q_lat.device_ptr_mut(&s).0 as *mut f32,
419 t_q as i32,
420 n_head as i32,
421 d_nope as i32,
422 kv_rank as i32,
423 s.cu_stream() as *mut c_void,
424 ),
425 )
426 }
427 }
428
429 #[allow(clippy::too_many_arguments)] pub fn mla_decompress_v(
432 &self,
433 o_lat: &CudaSlice<f32>,
434 wv_b: &CudaSlice<f32>,
435 out: &mut CudaSlice<f32>,
436 t_q: usize,
437 n_head: usize,
438 d_v: usize,
439 kv_rank: usize,
440 ) -> Res<()> {
441 let s = self.stream();
442 if let Some(split) = mla_decode_split_for(t_q * n_head, d_v) {
444 mla_split_announce("decompress_v", t_q, n_head, split);
445 return unsafe {
446 ck(
447 "decompress_v_split",
448 memra_mla_decompress_v_split_f32(
449 o_lat.device_ptr(&s).0 as *const f32,
450 wv_b.device_ptr(&s).0 as *const f32,
451 out.device_ptr_mut(&s).0 as *mut f32,
452 t_q as i32,
453 n_head as i32,
454 d_v as i32,
455 kv_rank as i32,
456 split,
457 s.cu_stream() as *mut c_void,
458 ),
459 )
460 };
461 }
462 unsafe {
463 ck(
464 "decompress_v",
465 memra_mla_decompress_v_f32(
466 o_lat.device_ptr(&s).0 as *const f32,
467 wv_b.device_ptr(&s).0 as *const f32,
468 out.device_ptr_mut(&s).0 as *mut f32,
469 t_q as i32,
470 n_head as i32,
471 d_v as i32,
472 kv_rank as i32,
473 s.cu_stream() as *mut c_void,
474 ),
475 )
476 }
477 }
478
479 #[allow(clippy::too_many_arguments)] pub fn mla_attn_absorbed(
483 &self,
484 q_lat: &CudaSlice<f32>,
485 q_pe: &CudaSlice<f32>,
486 cache: &CudaSlice<f32>,
487 o_lat: &mut CudaSlice<f32>,
488 n_head: usize,
489 kv_rank: usize,
490 d_rope: usize,
491 t_q: usize,
492 t_kv: usize,
493 scale: f32,
494 ) -> Res<()> {
495 let s = self.stream();
496 unsafe {
497 ck(
498 "attn_absorbed",
499 memra_mla_attn_absorbed_f32(
500 q_lat.device_ptr(&s).0 as *const f32,
501 q_pe.device_ptr(&s).0 as *const f32,
502 cache.device_ptr(&s).0 as *const f32,
503 o_lat.device_ptr_mut(&s).0 as *mut f32,
504 n_head as i32,
505 kv_rank as i32,
506 d_rope as i32,
507 t_q as i32,
508 t_kv as i32,
509 scale,
510 s.cu_stream() as *mut c_void,
511 ),
512 )
513 }
514 }
515}
516
517impl Engine {
521 #[allow(clippy::too_many_arguments)]
534 pub fn mla_kpool_pool_keys(
535 &self,
536 state: &CudaSlice<f32>,
537 ape: &CudaSlice<f32>,
538 pool_keys: &mut CudaSlice<f32>,
539 pool_begin: usize,
540 n_pools: usize,
541 pool: usize,
542 d: usize,
543 state_rows: usize,
544 ) -> Res<()> {
545 let s = self.stream();
546 unsafe {
547 ck(
548 "kpool_pool_keys",
549 memra_mla_kpool_pool_keys_f32(
550 state.device_ptr(&s).0 as *const f32,
551 ape.device_ptr(&s).0 as *const f32,
552 pool_keys.device_ptr_mut(&s).0 as *mut f32,
553 pool_begin as i32,
554 n_pools as i32,
555 pool as i32,
556 d as i32,
557 state_rows as i32,
558 s.cu_stream() as *mut c_void,
559 ),
560 )
561 }
562 }
563
564 #[allow(clippy::too_many_arguments)]
571 pub fn mla_index_append(
576 &self,
577 plane: &mut CudaSlice<f32>,
578 a: &CudaSlice<f32>,
579 b: &CudaSlice<f32>,
580 src_row: usize,
581 slot: usize,
582 t: usize,
583 wa: usize,
584 wb: usize,
585 rows: usize,
586 ) -> Res<()> {
587 let s = self.stream();
588 unsafe {
589 ck(
590 "index_append_ring",
591 memra_mla_index_append_ring_f32(
592 plane.device_ptr_mut(&s).0 as *mut f32,
593 (a.device_ptr(&s).0 as *const f32).add(src_row * wa),
594 (b.device_ptr(&s).0 as *const f32).add(src_row * wb),
595 slot as i32,
596 t as i32,
597 wa as i32,
598 wb as i32,
599 rows as i32,
600 s.cu_stream() as *mut c_void,
601 ),
602 )
603 }
604 }
605
606 #[allow(clippy::too_many_arguments)]
618 pub fn mla_kpool_score(
619 &self,
620 q: &CudaSlice<f32>,
621 pool_keys: &CudaSlice<f32>,
622 head_weights: &CudaSlice<f32>,
623 score: &mut CudaSlice<f32>,
624 t_q: usize,
625 heads: usize,
626 d: usize,
627 n_pools: usize,
628 pool: usize,
629 first_pos: usize,
630 qk_scale: f32,
631 head_scale: f32,
632 ) -> Res<()> {
633 let s = self.stream();
634 unsafe {
635 ck(
636 "kpool_score",
637 memra_mla_kpool_score_f32(
638 q.device_ptr(&s).0 as *const f32,
639 pool_keys.device_ptr(&s).0 as *const f32,
640 head_weights.device_ptr(&s).0 as *const f32,
641 score.device_ptr_mut(&s).0 as *mut f32,
642 t_q as i32,
643 heads as i32,
644 d as i32,
645 n_pools as i32,
646 pool as i32,
647 first_pos as i32,
648 qk_scale,
649 head_scale,
650 s.cu_stream() as *mut c_void,
651 ),
652 )
653 }
654 }
655
656 #[allow(clippy::too_many_arguments)]
661 pub fn mla_kpool_score_ref(
662 &self,
663 q: &CudaSlice<f32>,
664 pool_keys: &CudaSlice<f32>,
665 head_weights: &CudaSlice<f32>,
666 score: &mut CudaSlice<f32>,
667 t_q: usize,
668 heads: usize,
669 d: usize,
670 n_pools: usize,
671 pool: usize,
672 first_pos: usize,
673 qk_scale: f32,
674 head_scale: f32,
675 ) -> Res<()> {
676 let s = self.stream();
677 unsafe {
678 ck(
679 "kpool_score_ref",
680 memra_mla_kpool_score_ref_f32(
681 q.device_ptr(&s).0 as *const f32,
682 pool_keys.device_ptr(&s).0 as *const f32,
683 head_weights.device_ptr(&s).0 as *const f32,
684 score.device_ptr_mut(&s).0 as *mut f32,
685 t_q as i32,
686 heads as i32,
687 d as i32,
688 n_pools as i32,
689 pool as i32,
690 first_pos as i32,
691 qk_scale,
692 head_scale,
693 s.cu_stream() as *mut c_void,
694 ),
695 )
696 }
697 }
698
699 #[allow(clippy::too_many_arguments)]
705 pub fn mla_kpool_select(
706 &self,
707 score: &CudaSlice<f32>,
708 idx: &mut CudaSlice<i32>,
709 t_q: usize,
710 n_pools: usize,
711 pool: usize,
712 select_k: usize,
713 width: usize,
714 first_pos: usize,
715 always_tail: bool,
716 ) -> Res<()> {
717 let s = self.stream();
718 unsafe {
719 ck(
720 "kpool_select",
721 memra_mla_kpool_select_f32(
722 score.device_ptr(&s).0 as *const f32,
723 idx.device_ptr_mut(&s).0 as *mut i32,
724 t_q as i32,
725 n_pools as i32,
726 pool as i32,
727 select_k as i32,
728 width as i32,
729 first_pos as i32,
730 i32::from(always_tail),
731 s.cu_stream() as *mut c_void,
732 ),
733 )
734 }
735 }
736
737 #[allow(clippy::too_many_arguments)]
742 pub fn mla_kpool_select_ref(
743 &self,
744 score: &CudaSlice<f32>,
745 idx: &mut CudaSlice<i32>,
746 t_q: usize,
747 n_pools: usize,
748 pool: usize,
749 select_k: usize,
750 width: usize,
751 first_pos: usize,
752 always_tail: bool,
753 ) -> Res<()> {
754 let s = self.stream();
755 unsafe {
756 ck(
757 "kpool_select_ref",
758 memra_mla_kpool_select_ref_f32(
759 score.device_ptr(&s).0 as *const f32,
760 idx.device_ptr_mut(&s).0 as *mut i32,
761 t_q as i32,
762 n_pools as i32,
763 pool as i32,
764 select_k as i32,
765 width as i32,
766 first_pos as i32,
767 i32::from(always_tail),
768 s.cu_stream() as *mut c_void,
769 ),
770 )
771 }
772 }
773
774 #[allow(clippy::too_many_arguments)]
792 pub fn mla_bf16_gemm_sb_raw(
793 &self,
794 w_bf16: &CudaSlice<u8>,
795 x_bf16: &CudaSlice<u8>,
796 y_ptr: u64,
797 m: usize,
798 n: usize,
799 k: usize,
800 x_rs: usize,
801 x_bs: usize,
802 y_rs: usize,
803 y_bs: usize,
804 batch: usize,
805 y_bf16: bool,
806 ) -> Res<i32> {
807 let mut guard = self.f16_scratch.lock().unwrap();
809 if guard.is_none() {
810 *guard = Some(crate::f16_ffi::F16Scratch::with_capacity(self, 2)?);
811 }
812 let s_scr = guard.as_mut().unwrap();
813 let s = self.stream();
814 let rc = unsafe {
815 memra_bf16_gemm_sb(
816 w_bf16.device_ptr(&s).0 as *const c_void,
817 x_bf16.device_ptr(&s).0 as *const c_void,
818 y_ptr as *mut c_void,
819 m as i32,
820 n as i32,
821 k as i32,
822 x_rs as i64,
823 x_bs as i64,
824 y_rs as i64,
825 y_bs as i64,
826 batch as i32,
827 i32::from(y_bf16),
828 s_scr.ws.device_ptr_mut(&s).0 as *mut c_void,
829 crate::f16_ffi::F16_WS_BYTES,
830 s.cu_stream() as *mut c_void,
831 )
832 };
833 Ok(rc)
834 }
835
836 #[allow(clippy::too_many_arguments)]
840 pub fn mla_bf16_gemm_sb_bf16out(
841 &self,
842 w_bf16: &CudaSlice<u8>,
843 x_bf16: &CudaSlice<u8>,
844 y_bf16: &mut CudaSlice<u8>,
845 m: usize,
846 n: usize,
847 k: usize,
848 x_rs: usize,
849 x_bs: usize,
850 y_rs: usize,
851 y_bs: usize,
852 batch: usize,
853 ) -> Res<bool> {
854 let s = self.stream();
855 let (y_ptr, _gy) = y_bf16.device_ptr_mut(&s);
856 let rc = self.mla_bf16_gemm_sb_raw(
857 w_bf16, x_bf16, y_ptr, m, n, k, x_rs, x_bs, y_rs, y_bs, batch, true,
858 )?;
859 match rc {
860 0 => Ok(true),
861 r if (20000..30000).contains(&r) => Ok(false),
862 r => Err(format!(
863 "mla bf16 strided-batched GEMM (bf16 out) failed: rc {r} \
864 (m={m} n={n} k={k} batch={batch})"
865 )
866 .into()),
867 }
868 }
869
870 #[allow(clippy::too_many_arguments)]
873 pub fn mla_bf16_gemm_sb_f32out(
874 &self,
875 w_bf16: &CudaSlice<u8>,
876 x_bf16: &CudaSlice<u8>,
877 y_f32: &mut CudaSlice<f32>,
878 m: usize,
879 n: usize,
880 k: usize,
881 x_rs: usize,
882 x_bs: usize,
883 y_rs: usize,
884 y_bs: usize,
885 batch: usize,
886 ) -> Res<bool> {
887 let s = self.stream();
888 let (y_ptr, _gy) = y_f32.device_ptr_mut(&s);
889 let rc = self.mla_bf16_gemm_sb_raw(
890 w_bf16, x_bf16, y_ptr, m, n, k, x_rs, x_bs, y_rs, y_bs, batch, false,
891 )?;
892 match rc {
893 0 => Ok(true),
894 r if (20000..30000).contains(&r) => Ok(false),
895 r => Err(format!(
896 "mla bf16 strided-batched GEMM (f32 out) failed: rc {r} \
897 (m={m} n={n} k={k} batch={batch})"
898 )
899 .into()),
900 }
901 }
902
903 #[allow(clippy::too_many_arguments)]
906 pub fn mla_attn_gathered(
907 &self,
908 q_lat: &CudaSlice<f32>,
909 q_pe: &CudaSlice<f32>,
910 cache: &CudaSlice<f32>,
911 idx: &CudaSlice<i32>,
912 o_lat: &mut CudaSlice<f32>,
913 n_head: usize,
914 kv_rank: usize,
915 d_rope: usize,
916 t_q: usize,
917 n_slots: usize,
918 scale: f32,
919 ) -> Res<()> {
920 let s = self.stream();
921 unsafe {
922 ck(
923 "attn_gathered",
924 memra_mla_attn_gathered_f32(
925 q_lat.device_ptr(&s).0 as *const f32,
926 q_pe.device_ptr(&s).0 as *const f32,
927 cache.device_ptr(&s).0 as *const f32,
928 idx.device_ptr(&s).0 as *const i32,
929 o_lat.device_ptr_mut(&s).0 as *mut f32,
930 n_head as i32,
931 kv_rank as i32,
932 d_rope as i32,
933 t_q as i32,
934 n_slots as i32,
935 scale,
936 s.cu_stream() as *mut c_void,
937 ),
938 )
939 }
940 }
941}