1use crate::Engine;
16use cudarc::driver::{CudaSlice, CudaView, DevicePtr, DevicePtrMut};
17
18static MMQ_ACT_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
21#[allow(clippy::type_complexity)]
22static MMQ_ACT_SLOT: std::sync::Mutex<Option<(u64, u64, usize, usize, CudaSlice<u8>)>> =
23 std::sync::Mutex::new(None);
24static MMQ_FIXUP_SLOT: std::sync::Mutex<Option<cudarc::driver::CudaSlice<u8>>> =
26 std::sync::Mutex::new(None);
27
28pub struct ExpertCsr {
34 ex_ids: Vec<i32>,
35 ex_off: Vec<i32>,
36 ex_pairs: Vec<i32>,
37 pair_tok: Vec<i32>,
38 n_expert: usize,
39 n_tokens: usize,
40}
41
42impl ExpertCsr {
43 pub fn from_token_routes(
45 n_expert: usize,
46 n_tokens: usize,
47 experts_per_token: usize,
48 selected: &[usize],
49 ) -> Result<Self, String> {
50 if experts_per_token == 0 {
51 return Err("grouped expert CSR experts/token must be nonzero".into());
52 }
53 let n_pairs = n_tokens
54 .checked_mul(experts_per_token)
55 .ok_or("grouped expert CSR route count overflow")?;
56 if selected.len() != n_pairs {
57 return Err(format!(
58 "grouped expert CSR selected routes {} != {n_tokens}x{experts_per_token} \
59 ({n_pairs})",
60 selected.len()
61 ));
62 }
63 let pair_tok = (0..n_pairs)
64 .map(|pair| pair / experts_per_token)
65 .collect::<Vec<_>>();
66 Self::from_pair_rows(n_expert, n_tokens, selected, &pair_tok)
67 }
68
69 pub fn from_pair_rows(
74 n_expert: usize,
75 n_tokens: usize,
76 selected: &[usize],
77 pair_tok: &[usize],
78 ) -> Result<Self, String> {
79 if n_expert == 0 || n_tokens == 0 || selected.is_empty() {
80 return Err("grouped expert CSR requires non-empty experts, tokens, and pairs".into());
81 }
82 if n_expert > i32::MAX as usize
83 || n_tokens > i32::MAX as usize
84 || selected.len() > i32::MAX as usize
85 {
86 return Err("grouped expert CSR dimensions exceed the i32 kernel ABI".into());
87 }
88 if pair_tok.len() != selected.len() {
89 return Err(format!(
90 "grouped expert CSR pair rows {} != selected routes {}",
91 pair_tok.len(),
92 selected.len()
93 ));
94 }
95
96 let mut counts = vec![0usize; n_expert];
97 for &expert in selected {
98 let count = counts.get_mut(expert).ok_or_else(|| {
99 format!("grouped expert CSR expert {expert} outside 0..{n_expert}")
100 })?;
101 *count += 1;
102 }
103 if let Some(&token) = pair_tok.iter().find(|&&token| token >= n_tokens) {
104 return Err(format!(
105 "grouped expert CSR token {token} outside 0..{n_tokens}"
106 ));
107 }
108
109 let mut prefix = vec![0usize; n_expert + 1];
110 for expert in 0..n_expert {
111 prefix[expert + 1] = prefix[expert] + counts[expert];
112 }
113 let mut ex_ids = Vec::with_capacity(n_expert.min(selected.len()));
114 let mut ex_off = Vec::with_capacity(ex_ids.capacity() + 1);
115 for expert in 0..n_expert {
116 if counts[expert] != 0 {
117 ex_ids.push(expert as i32);
118 ex_off.push(prefix[expert] as i32);
119 }
120 }
121 ex_off.push(selected.len() as i32);
122
123 let mut cursor = prefix[..n_expert].to_vec();
124 let mut ex_pairs = vec![0i32; selected.len()];
125 for (pair, &expert) in selected.iter().enumerate() {
126 ex_pairs[cursor[expert]] = pair as i32;
127 cursor[expert] += 1;
128 }
129 let pair_tok = pair_tok.iter().map(|&token| token as i32).collect();
130 Self::from_parts(n_expert, n_tokens, ex_ids, ex_off, ex_pairs, pair_tok)
131 }
132
133 fn from_parts(
134 n_expert: usize,
135 n_tokens: usize,
136 ex_ids: Vec<i32>,
137 ex_off: Vec<i32>,
138 ex_pairs: Vec<i32>,
139 pair_tok: Vec<i32>,
140 ) -> Result<Self, String> {
141 if n_expert == 0
142 || n_tokens == 0
143 || ex_ids.is_empty()
144 || ex_pairs.is_empty()
145 || n_expert > i32::MAX as usize
146 || n_tokens > i32::MAX as usize
147 || ex_pairs.len() > i32::MAX as usize
148 {
149 return Err("grouped expert CSR requires non-empty experts, tokens, and pairs".into());
150 }
151 if ex_off.len() != ex_ids.len() + 1 || ex_off.first() != Some(&0) {
152 return Err(format!(
153 "grouped expert CSR offsets {} != active experts {} + 1 or do not start at zero",
154 ex_off.len(),
155 ex_ids.len()
156 ));
157 }
158 let n_pairs = i32::try_from(ex_pairs.len())
159 .map_err(|_| "grouped expert CSR pair count exceeds i32")?;
160 if pair_tok.len() != ex_pairs.len() || ex_off.last().copied() != Some(n_pairs) {
161 return Err(format!(
162 "grouped expert CSR pair lengths offsets_end={:?} pairs={} pair_tok={}",
163 ex_off.last(),
164 ex_pairs.len(),
165 pair_tok.len()
166 ));
167 }
168 for pair in ex_ids.windows(2) {
169 if pair[0] >= pair[1] {
170 return Err("grouped expert CSR expert ids must be strictly increasing".into());
171 }
172 }
173 if ex_ids
174 .iter()
175 .any(|&expert| expert < 0 || expert as usize >= n_expert)
176 {
177 return Err(format!(
178 "grouped expert CSR expert id outside 0..{n_expert}: {ex_ids:?}"
179 ));
180 }
181 let mut seen = vec![false; ex_pairs.len()];
182 for &pair in &ex_pairs {
183 if pair < 0 || pair as usize >= ex_pairs.len() {
184 return Err(format!(
185 "grouped expert CSR pair {pair} outside 0..{}",
186 ex_pairs.len()
187 ));
188 }
189 if std::mem::replace(&mut seen[pair as usize], true) {
190 return Err(format!(
191 "grouped expert CSR pair {pair} appears more than once"
192 ));
193 }
194 let token = pair_tok[pair as usize];
195 if token < 0 || token as usize >= n_tokens {
196 return Err(format!(
197 "grouped expert CSR token {token} outside 0..{n_tokens}"
198 ));
199 }
200 }
201 for offsets in ex_off.windows(2) {
202 if offsets[0] >= offsets[1] {
203 return Err("grouped expert CSR segments must be non-empty and increasing".into());
204 }
205 }
206 Ok(Self {
207 ex_ids,
208 ex_off,
209 ex_pairs,
210 pair_tok,
211 n_expert,
212 n_tokens,
213 })
214 }
215
216 pub fn upload(&self, engine: &Engine) -> Result<DeviceExpertCsr, Box<dyn std::error::Error>> {
217 Ok(DeviceExpertCsr {
218 ex_ids: engine.htod_i32(&self.ex_ids)?,
219 ex_off: engine.htod_i32(&self.ex_off)?,
220 ex_pairs: engine.htod_i32(&self.ex_pairs)?,
221 pair_tok: engine.htod_i32(&self.pair_tok)?,
222 n_expert: self.n_expert,
223 active_experts: self.ex_ids.len(),
224 n_tokens: self.n_tokens,
225 n_pairs: self.ex_pairs.len(),
226 max_tokens: self.n_tokens,
227 max_pairs: self.ex_pairs.len(),
228 })
229 }
230}
231
232pub struct DeviceExpertCsr {
233 ex_ids: CudaSlice<i32>,
234 ex_off: CudaSlice<i32>,
235 ex_pairs: CudaSlice<i32>,
236 pair_tok: CudaSlice<i32>,
237 n_expert: usize,
238 active_experts: usize,
239 n_tokens: usize,
240 n_pairs: usize,
241 max_tokens: usize,
242 max_pairs: usize,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
246struct DeviceExpertCsrCapacity {
247 n_expert: usize,
248 max_active_experts: usize,
249 max_tokens: usize,
250 max_pairs: usize,
251}
252
253fn validate_device_expert_csr_capacity(
254 n_expert: usize,
255 max_tokens: usize,
256 max_pairs: usize,
257) -> Result<DeviceExpertCsrCapacity, String> {
258 if n_expert == 0
259 || max_tokens == 0
260 || max_pairs == 0
261 || n_expert > i32::MAX as usize
262 || max_tokens > i32::MAX as usize
263 || max_pairs > i32::MAX as usize
264 {
265 return Err(format!(
266 "invalid device expert CSR capacity experts={n_expert} tokens={max_tokens} \
267 pairs={max_pairs}"
268 ));
269 }
270 Ok(DeviceExpertCsrCapacity {
271 n_expert,
272 max_active_experts: n_expert.min(max_pairs),
273 max_tokens,
274 max_pairs,
275 })
276}
277
278fn validate_device_expert_csr_refresh(
279 capacity: DeviceExpertCsrCapacity,
280 n_expert: usize,
281 active_experts: usize,
282 n_tokens: usize,
283 n_pairs: usize,
284) -> Result<(), String> {
285 if n_expert != capacity.n_expert {
286 return Err(format!(
287 "device expert CSR expert count changed {n_expert} != {}",
288 capacity.n_expert
289 ));
290 }
291 if active_experts == 0
292 || n_tokens == 0
293 || n_pairs == 0
294 || active_experts > capacity.max_active_experts
295 || n_tokens > capacity.max_tokens
296 || n_pairs > capacity.max_pairs
297 {
298 return Err(format!(
299 "device expert CSR active shape experts={active_experts} tokens={n_tokens} \
300 pairs={n_pairs} exceeds capacity experts={} tokens={} pairs={}",
301 capacity.max_active_experts, capacity.max_tokens, capacity.max_pairs
302 ));
303 }
304 Ok(())
305}
306
307impl DeviceExpertCsr {
308 pub fn with_capacity(
313 engine: &Engine,
314 n_expert: usize,
315 max_tokens: usize,
316 max_pairs: usize,
317 ) -> Result<Self, Box<dyn std::error::Error>> {
318 let capacity = validate_device_expert_csr_capacity(n_expert, max_tokens, max_pairs)?;
319 Ok(Self {
320 ex_ids: engine.htod_i32(&vec![0; capacity.max_active_experts])?,
321 ex_off: engine.htod_i32(&vec![0; capacity.max_active_experts + 1])?,
322 ex_pairs: engine.htod_i32(&vec![0; capacity.max_pairs])?,
323 pair_tok: engine.htod_i32(&vec![0; capacity.max_pairs])?,
324 n_expert,
325 active_experts: 0,
326 n_tokens: 0,
327 n_pairs: 0,
328 max_tokens,
329 max_pairs,
330 })
331 }
332
333 pub fn refresh(
334 &mut self,
335 engine: &Engine,
336 csr: &ExpertCsr,
337 ) -> Result<(), Box<dyn std::error::Error>> {
338 let capacity =
339 validate_device_expert_csr_capacity(self.n_expert, self.max_tokens, self.max_pairs)?;
340 validate_device_expert_csr_refresh(
341 capacity,
342 csr.n_expert,
343 csr.ex_ids.len(),
344 csr.n_tokens,
345 csr.ex_pairs.len(),
346 )?;
347 let device = engine.ctx().ordinal();
348 if self.ex_ids.ordinal() != device
349 || self.ex_off.ordinal() != device
350 || self.ex_pairs.ordinal() != device
351 || self.pair_tok.ordinal() != device
352 {
353 return Err(
354 format!("device expert CSR capacity is not resident on device {device}").into(),
355 );
356 }
357 engine.htod_i32_into(&mut self.ex_ids, &csr.ex_ids)?;
358 engine.htod_i32_into(&mut self.ex_off, &csr.ex_off)?;
359 engine.htod_i32_into(&mut self.ex_pairs, &csr.ex_pairs)?;
360 engine.htod_i32_into(&mut self.pair_tok, &csr.pair_tok)?;
361 self.active_experts = csr.ex_ids.len();
362 self.n_tokens = csr.n_tokens;
363 self.n_pairs = csr.ex_pairs.len();
364 Ok(())
365 }
366
367 pub fn clear(&mut self) {
368 self.active_experts = 0;
369 self.n_tokens = 0;
370 self.n_pairs = 0;
371 }
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
375struct GroupedFp8WorkspaceShape {
376 activation_len: usize,
377 output_len: usize,
378}
379
380fn validate_grouped_fp8_workspace_shape(
381 in_features: usize,
382 out_features: usize,
383 n_tokens: usize,
384 n_pairs: usize,
385) -> Result<GroupedFp8WorkspaceShape, String> {
386 if in_features == 0
387 || out_features == 0
388 || n_tokens == 0
389 || n_pairs == 0
390 || !in_features.is_multiple_of(16)
391 || in_features > i32::MAX as usize
392 || out_features > i32::MAX as usize
393 || n_tokens > i32::MAX as usize
394 || n_pairs > i32::MAX as usize
395 {
396 return Err(format!(
397 "invalid grouped FP8 workspace in={in_features} out={out_features} \
398 tokens={n_tokens} pairs={n_pairs}"
399 ));
400 }
401 let activation_len = n_tokens
402 .checked_mul(in_features)
403 .ok_or("grouped FP8 activation length overflow")?;
404 let output_len = n_pairs
405 .checked_mul(out_features)
406 .ok_or("grouped FP8 output length overflow")?;
407 Ok(GroupedFp8WorkspaceShape {
408 activation_len,
409 output_len,
410 })
411}
412
413fn validate_grouped_fp8_workspace_active_shape(
414 in_features: usize,
415 out_features: usize,
416 max_tokens: usize,
417 max_pairs: usize,
418 n_tokens: usize,
419 n_pairs: usize,
420) -> Result<GroupedFp8WorkspaceShape, String> {
421 validate_grouped_fp8_workspace_shape(in_features, out_features, max_tokens, max_pairs)?;
422 let active =
423 validate_grouped_fp8_workspace_shape(in_features, out_features, n_tokens, n_pairs)?;
424 if n_tokens > max_tokens || n_pairs > max_pairs {
425 return Err(format!(
426 "grouped FP8 active shape tokens={n_tokens} pairs={n_pairs} exceeds capacity \
427 tokens={max_tokens} pairs={max_pairs}"
428 ));
429 }
430 Ok(active)
431}
432
433pub struct Fp8GroupedWorkspace {
438 act_scratch: CudaSlice<u8>,
439 output: CudaSlice<f32>,
440 in_features: usize,
441 out_features: usize,
442 n_tokens: usize,
443 n_pairs: usize,
444 max_tokens: usize,
445 max_pairs: usize,
446}
447
448impl Fp8GroupedWorkspace {
449 pub fn new(
450 engine: &Engine,
451 in_features: usize,
452 out_features: usize,
453 n_tokens: usize,
454 n_pairs: usize,
455 ) -> Result<Self, Box<dyn std::error::Error>> {
456 let shape =
457 validate_grouped_fp8_workspace_shape(in_features, out_features, n_tokens, n_pairs)?;
458 let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_features as i32, n_tokens as i32) };
459 if act_bytes == 0 {
460 return Err("grouped FP8 activation scratch size is zero".into());
461 }
462 Ok(Self {
463 act_scratch: engine.alloc_u8_uninit(act_bytes)?,
464 output: engine.uninit(shape.output_len)?,
465 in_features,
466 out_features,
467 n_tokens,
468 n_pairs,
469 max_tokens: n_tokens,
470 max_pairs: n_pairs,
471 })
472 }
473
474 pub fn quantize(
475 &mut self,
476 engine: &Engine,
477 activations: &CudaSlice<f32>,
478 ) -> Result<(), Box<dyn std::error::Error>> {
479 self.quantize_for_shape(engine, activations, self.n_tokens, self.n_pairs)
480 }
481
482 pub fn quantize_for_shape(
484 &mut self,
485 engine: &Engine,
486 activations: &CudaSlice<f32>,
487 n_tokens: usize,
488 n_pairs: usize,
489 ) -> Result<(), Box<dyn std::error::Error>> {
490 let shape = validate_grouped_fp8_workspace_active_shape(
491 self.in_features,
492 self.out_features,
493 self.max_tokens,
494 self.max_pairs,
495 n_tokens,
496 n_pairs,
497 )?;
498 let device = engine.ctx().ordinal();
499 if activations.len() < shape.activation_len
500 || activations.ordinal() != device
501 || self.act_scratch.ordinal() != device
502 {
503 return Err(format!(
504 "grouped FP8 activation len/device {}/{} does not cover {}x{} on device {}",
505 activations.len(),
506 activations.ordinal(),
507 n_tokens,
508 self.in_features,
509 device,
510 )
511 .into());
512 }
513 let stream = engine.gpu.stream();
514 let (x_p, _gx) = activations.device_ptr(&stream);
515 let (scratch_p, _gs) = self.act_scratch.device_ptr_mut(&stream);
516 let rc = unsafe {
517 memra_mmq_fp8_blk_quantize_act(
518 x_p as *const f32,
519 scratch_p as *mut core::ffi::c_void,
520 self.in_features as i32,
521 n_tokens as i32,
522 stream.cu_stream() as *mut core::ffi::c_void,
523 )
524 };
525 if rc != 0 {
526 return Err(format!("memra_mmq_fp8_blk_quantize_act rc={rc}").into());
527 }
528 self.n_tokens = n_tokens;
529 self.n_pairs = n_pairs;
530 Ok(())
531 }
532
533 #[allow(clippy::too_many_arguments)]
534 pub fn project(
535 &mut self,
536 engine: &Engine,
537 bank_codes: &CudaSlice<u8>,
538 bank_scales: &CudaSlice<f32>,
539 csr: &DeviceExpertCsr,
540 code_stride: usize,
541 scale_stride: usize,
542 out_scale: f32,
543 ) -> Result<(), Box<dyn std::error::Error>> {
544 if csr.n_tokens != self.n_tokens || csr.n_pairs != self.n_pairs {
545 return Err(format!(
546 "grouped FP8 CSR/workspace mismatch tokens {} != {}, pairs {} != {}",
547 csr.n_tokens, self.n_tokens, csr.n_pairs, self.n_pairs
548 )
549 .into());
550 }
551 let want_code_stride = self
552 .in_features
553 .checked_mul(self.out_features)
554 .ok_or("grouped FP8 code stride overflow")?;
555 let want_scale_stride = self.in_features.div_ceil(128) * self.out_features.div_ceil(128);
556 if code_stride < want_code_stride || scale_stride < want_scale_stride {
557 return Err(format!(
558 "grouped FP8 expert strides codes {code_stride} < {want_code_stride}, \
559 scales {scale_stride} < {want_scale_stride}"
560 )
561 .into());
562 }
563 let code_count = csr
564 .n_expert
565 .checked_mul(code_stride)
566 .ok_or("grouped FP8 expert code count overflow")?;
567 let scale_count = csr
568 .n_expert
569 .checked_mul(scale_stride)
570 .ok_or("grouped FP8 expert scale count overflow")?;
571 if bank_codes.len() < code_count || bank_scales.len() < scale_count {
572 return Err(format!(
573 "grouped FP8 expert bank too small codes {} < {}, scales {} < {}",
574 bank_codes.len(),
575 code_count,
576 bank_scales.len(),
577 scale_count,
578 )
579 .into());
580 }
581 if !out_scale.is_finite() {
582 return Err(format!("grouped FP8 output scale is not finite: {out_scale}").into());
583 }
584 let device = engine.ctx().ordinal();
585 if bank_codes.ordinal() != device
586 || bank_scales.ordinal() != device
587 || csr.ex_ids.ordinal() != device
588 || csr.ex_off.ordinal() != device
589 || csr.ex_pairs.ordinal() != device
590 || csr.pair_tok.ordinal() != device
591 || self.act_scratch.ordinal() != device
592 || self.output.ordinal() != device
593 {
594 return Err(format!(
595 "grouped FP8 bank, CSR, and workspace must all reside on device {device}"
596 )
597 .into());
598 }
599 let stream = engine.gpu.stream();
600 let (codes_p, _gc) = bank_codes.device_ptr(&stream);
601 let (scales_p, _gs) = bank_scales.device_ptr(&stream);
602 let (ids_p, _gi) = csr.ex_ids.device_ptr(&stream);
603 let (off_p, _go) = csr.ex_off.device_ptr(&stream);
604 let (pairs_p, _gp) = csr.ex_pairs.device_ptr(&stream);
605 let (tok_p, _gt) = csr.pair_tok.device_ptr(&stream);
606 let (act_p, _ga) = self.act_scratch.device_ptr(&stream);
607 let (output_p, _gy) = self.output.device_ptr_mut(&stream);
608 let rc = unsafe {
609 memra_mmq_fp8_blk_grouped(
610 codes_p as *const core::ffi::c_void,
611 scales_p as *const f32,
612 ids_p as *const i32,
613 off_p as *const i32,
614 pairs_p as *const i32,
615 tok_p as *const i32,
616 act_p as *const core::ffi::c_void,
617 output_p as *mut f32,
618 self.in_features as i32,
619 self.out_features as i32,
620 csr.n_expert as i32,
621 csr.active_experts as i32,
622 csr.n_pairs as i32,
623 csr.n_tokens as i32,
624 code_stride,
625 scale_stride,
626 stream.cu_stream() as *mut core::ffi::c_void,
627 out_scale,
628 )
629 };
630 if rc != 0 {
631 return Err(format!("memra_mmq_fp8_blk_grouped rc={rc}").into());
632 }
633 Ok(())
634 }
635
636 pub fn output(&self) -> &CudaSlice<f32> {
637 &self.output
638 }
639
640 pub fn output_len(&self) -> usize {
641 self.n_pairs * self.out_features
642 }
643}
644
645#[cfg(test)]
646mod grouped_fp8_tests {
647 use super::{
648 DeviceExpertCsrCapacity, ExpertCsr, GroupedFp8WorkspaceShape,
649 validate_device_expert_csr_capacity, validate_device_expert_csr_refresh,
650 validate_grouped_fp8_workspace_active_shape, validate_grouped_fp8_workspace_shape,
651 };
652
653 #[test]
654 fn token_routes_build_stable_expert_major_csr() {
655 let csr = ExpertCsr::from_token_routes(4, 2, 3, &[2, 0, 2, 1, 0, 3]).unwrap();
656 assert_eq!(csr.ex_ids, vec![0, 1, 2, 3]);
657 assert_eq!(csr.ex_off, vec![0, 2, 3, 5, 6]);
658 assert_eq!(csr.ex_pairs, vec![1, 4, 3, 0, 2, 5]);
659 assert_eq!(csr.pair_tok, vec![0, 0, 0, 1, 1, 1]);
660 }
661
662 #[test]
663 fn explicit_pair_rows_remain_indexed_by_pair_id() {
664 let csr = ExpertCsr::from_pair_rows(2, 3, &[1, 0, 1], &[2, 0, 1]).unwrap();
665 assert_eq!(csr.ex_ids, vec![0, 1]);
666 assert_eq!(csr.ex_off, vec![0, 1, 3]);
667 assert_eq!(csr.ex_pairs, vec![1, 0, 2]);
668 assert_eq!(csr.pair_tok, vec![2, 0, 1]);
669 }
670
671 #[test]
672 fn csr_validation_rejects_bad_routes_and_parts() {
673 assert!(ExpertCsr::from_token_routes(4, 2, 3, &[0, 1]).is_err());
674 assert!(ExpertCsr::from_pair_rows(2, 1, &[2], &[0]).is_err());
675 assert!(ExpertCsr::from_pair_rows(2, 1, &[0], &[1]).is_err());
676 assert!(
677 ExpertCsr::from_parts(2, 2, vec![0, 1], vec![0, 1, 2], vec![0, 0], vec![0, 1]).is_err()
678 );
679 assert!(
680 ExpertCsr::from_parts(2, 2, vec![1, 0], vec![0, 1, 2], vec![0, 1], vec![0, 1]).is_err()
681 );
682 }
683
684 #[test]
685 fn csr_segments_are_not_limited_to_one_kernel_tile() {
686 let selected = vec![0usize; 17];
687 let rows = (0..17).collect::<Vec<_>>();
688 let csr = ExpertCsr::from_pair_rows(1, 17, &selected, &rows).unwrap();
689 assert_eq!(csr.ex_off, vec![0, 17]);
690 assert_eq!(csr.ex_pairs, (0..17).collect::<Vec<i32>>());
691 }
692
693 #[test]
694 fn workspace_shape_validation_is_pure_and_checked() {
695 assert_eq!(
696 validate_grouped_fp8_workspace_shape(4096, 1280, 2, 16).unwrap(),
697 GroupedFp8WorkspaceShape {
698 activation_len: 8192,
699 output_len: 20480,
700 }
701 );
702 assert!(validate_grouped_fp8_workspace_shape(15, 128, 1, 1).is_err());
703 assert!(validate_grouped_fp8_workspace_shape(i32::MAX as usize + 1, 128, 1, 1,).is_err());
704 }
705
706 #[test]
707 fn device_csr_capacity_admits_smaller_dynamic_schedules() {
708 let capacity = validate_device_expert_csr_capacity(72, 8, 64).unwrap();
709 assert_eq!(
710 capacity,
711 DeviceExpertCsrCapacity {
712 n_expert: 72,
713 max_active_experts: 64,
714 max_tokens: 8,
715 max_pairs: 64,
716 }
717 );
718 validate_device_expert_csr_refresh(capacity, 72, 5, 3, 17).unwrap();
719 assert!(validate_device_expert_csr_refresh(capacity, 72, 5, 9, 17).is_err());
720 assert!(validate_device_expert_csr_refresh(capacity, 72, 5, 3, 65).is_err());
721 assert!(validate_device_expert_csr_refresh(capacity, 71, 5, 3, 17).is_err());
722 assert!(validate_device_expert_csr_refresh(capacity, 72, 0, 3, 17).is_err());
723 }
724
725 #[test]
726 fn grouped_workspace_capacity_accepts_only_bounded_active_shapes() {
727 assert_eq!(
728 validate_grouped_fp8_workspace_active_shape(4096, 1280, 8, 64, 3, 17).unwrap(),
729 GroupedFp8WorkspaceShape {
730 activation_len: 3 * 4096,
731 output_len: 17 * 1280,
732 }
733 );
734 assert!(validate_grouped_fp8_workspace_active_shape(4096, 1280, 8, 64, 9, 17).is_err());
735 assert!(validate_grouped_fp8_workspace_active_shape(4096, 1280, 8, 64, 3, 65).is_err());
736 }
737}
738
739unsafe extern "C" {
740 fn memra_bind_device(dev: i32) -> i32;
741 pub fn memra_mmq_nvfp4_act_bytes(in_f: i32, n_tokens: i32) -> usize;
743 pub fn memra_mmq_nvfp4(
750 w_nvfp4_blocks: *const core::ffi::c_void,
751 act_f32: *const f32,
752 y: *mut f32,
753 in_f: i32,
754 out_f: i32,
755 n_tokens: i32,
756 act_scratch: *mut core::ffi::c_void,
757 stream: *mut core::ffi::c_void,
758 out_scale: f32,
759 ) -> i32;
760 pub fn memra_mmq_nvfp4_ex(
766 w_nvfp4_blocks: *const core::ffi::c_void,
767 act_f32: *const f32,
768 y: *mut f32,
769 in_f: i32,
770 out_f: i32,
771 n_tokens: i32,
772 act_scratch: *mut core::ffi::c_void,
773 stream: *mut core::ffi::c_void,
774 out_scale: f32,
775 per_token_scale: i32,
776 ) -> i32;
777 pub fn memra_mmq_nvfp4_ex2(
783 w_nvfp4_blocks: *const core::ffi::c_void,
784 act_f32: *const f32,
785 y: *mut f32,
786 in_f: i32,
787 out_f: i32,
788 n_tokens: i32,
789 act_scratch: *mut core::ffi::c_void,
790 stream: *mut core::ffi::c_void,
791 out_scale: f32,
792 per_token_scale: i32,
793 residual_k: i32,
794 ) -> i32;
795 pub fn memra_mmq_nvfp4_w4a8_act_bytes(in_f: i32, n_tokens: i32) -> usize;
797 pub fn memra_mmq_nvfp4_w4a8(
805 w_nvfp4_blocks: *const core::ffi::c_void,
806 act_f32: *const f32,
807 y: *mut f32,
808 in_f: i32,
809 out_f: i32,
810 n_tokens: i32,
811 act_scratch: *mut core::ffi::c_void,
812 stream: *mut core::ffi::c_void,
813 out_scale: f32,
814 rp: i32,
815 ) -> i32;
816 pub fn memra_mmq_nvfp4_f8f4_act_bytes(in_f: i32, n_tokens: i32) -> usize;
818 pub fn memra_mmq_nvfp4_f8f4(
824 w_nvfp4_blocks: *const core::ffi::c_void,
825 act_f32: *const f32,
826 y: *mut f32,
827 in_f: i32,
828 out_f: i32,
829 n_tokens: i32,
830 act_scratch: *mut core::ffi::c_void,
831 stream: *mut core::ffi::c_void,
832 out_scale: f32,
833 rp: i32,
834 ) -> i32;
835 pub fn memra_mmq_fp8_blk_act_bytes(in_f: i32, n_tokens: i32) -> usize;
838 pub fn memra_mmq_fp8_blk_quantize_act(
839 act_f32: *const f32,
840 act_scratch: *mut core::ffi::c_void,
841 in_f: i32,
842 n_tokens: i32,
843 stream: *mut core::ffi::c_void,
844 ) -> i32;
845 pub fn memra_mmq_fp8_blk_grouped(
846 bank_codes: *const core::ffi::c_void,
847 bank_scales: *const f32,
848 ex_ids: *const i32,
849 ex_off: *const i32,
850 ex_pairs: *const i32,
851 pair_tok: *const i32,
852 act_scratch: *const core::ffi::c_void,
853 y: *mut f32,
854 in_f: i32,
855 out_f: i32,
856 n_expert: i32,
857 n_active: i32,
858 n_pairs: i32,
859 n_tokens: i32,
860 code_stride: usize,
861 scale_stride: usize,
862 stream: *mut core::ffi::c_void,
863 out_scale: f32,
864 ) -> i32;
865 pub fn memra_mmq_fp8_blk_scale_rows(out_f: i32) -> i32;
867 pub fn memra_mmq_fp8_blk_scale_cols(in_f: i32) -> i32;
868 pub fn memra_mmq_fp8_blk(
875 w_e4m3: *const core::ffi::c_void,
876 blk_scales: *const f32,
877 act_f32: *const f32,
878 y: *mut f32,
879 in_f: i32,
880 out_f: i32,
881 n_tokens: i32,
882 act_scratch: *mut core::ffi::c_void,
883 stream: *mut core::ffi::c_void,
884 out_scale: f32,
885 ) -> i32;
886 pub fn memra_fp8_blk_count_nan(
890 w_e4m3: *const core::ffi::c_void,
891 nbytes: usize,
892 out_count: *mut u32,
893 stream: *mut core::ffi::c_void,
894 ) -> i32;
895 pub fn memra_mmq_q45k_act_bytes(in_f: i32, n_tokens: i32) -> usize;
897 pub fn memra_mmq_q4_K(
900 w_q4k_blocks: *const core::ffi::c_void,
901 act_f32: *const f32,
902 y: *mut f32,
903 in_f: i32,
904 out_f: i32,
905 n_tokens: i32,
906 act_scratch: *mut core::ffi::c_void,
907 stream: *mut core::ffi::c_void,
908 ) -> i32;
909 pub fn memra_mmq_q5_K(
911 w_q5k_blocks: *const core::ffi::c_void,
912 act_f32: *const f32,
913 y: *mut f32,
914 in_f: i32,
915 out_f: i32,
916 n_tokens: i32,
917 act_scratch: *mut core::ffi::c_void,
918 stream: *mut core::ffi::c_void,
919 ) -> i32;
920
921 pub fn memra_mmq_q8_0_act_bytes(in_f: i32, n_tokens: i32) -> usize;
923 pub fn memra_mmq_q8_0(
927 w_q8_0_blocks: *const core::ffi::c_void,
928 act_f32: *const f32,
929 y: *mut f32,
930 in_f: i32,
931 out_f: i32,
932 n_tokens: i32,
933 act_scratch: *mut core::ffi::c_void,
934 stream: *mut core::ffi::c_void,
935 ) -> i32;
936
937 pub fn memra_accprobe_act_bytes(in_f: i32, n_tokens: i32) -> usize;
946 pub fn memra_accprobe_gemm_s32(
948 w_q8_0_blocks: *const core::ffi::c_void,
949 act_q: *const core::ffi::c_void,
950 y: *mut f32,
951 in_f: i32,
952 out_f: i32,
953 n_tokens: i32,
954 stream: *mut core::ffi::c_void,
955 ) -> i32;
956 pub fn memra_accprobe_gemm_f32(
958 w_q8_0_blocks: *const core::ffi::c_void,
959 act_q: *const core::ffi::c_void,
960 y: *mut f32,
961 in_f: i32,
962 out_f: i32,
963 n_tokens: i32,
964 stream: *mut core::ffi::c_void,
965 ) -> i32;
966
967 pub fn memra_mmq_q4_0_act_bytes(in_f: i32, n_tokens: i32) -> usize;
969 pub fn memra_mmq_q4_0(
975 w_q4_0: *const core::ffi::c_void,
976 act_f32: *const f32,
977 y: *mut f32,
978 in_f: i32,
979 out_f: i32,
980 n_tokens: i32,
981 act_scratch: *mut core::ffi::c_void,
982 stream: *mut core::ffi::c_void,
983 rp: i32,
984 ) -> i32;
985 pub fn memra_mmq_q4_0_quant_act(
987 act_f32: *const f32,
988 act_scratch: *mut core::ffi::c_void,
989 in_f: i32,
990 n_tokens: i32,
991 stream: *mut core::ffi::c_void,
992 ) -> i32;
993 pub fn memra_mmq_q4_0_gemm(
995 w_q4_0: *const core::ffi::c_void,
996 act_scratch: *const core::ffi::c_void,
997 y: *mut f32,
998 in_f: i32,
999 out_f: i32,
1000 n_tokens: i32,
1001 stream: *mut core::ffi::c_void,
1002 rp: i32,
1003 ) -> i32;
1004 pub fn memra_mmq_q4_0_fixup_bytes() -> usize;
1006 pub fn memra_mmq_q4_0_set_clc(force: i32) -> i32;
1011 pub fn memra_mmq_q4_0_gemm_sk(
1014 w_q4_0: *const core::ffi::c_void,
1015 act_scratch: *const core::ffi::c_void,
1016 y: *mut f32,
1017 fixup_scratch: *mut core::ffi::c_void,
1018 in_f: i32,
1019 out_f: i32,
1020 n_tokens: i32,
1021 stream: *mut core::ffi::c_void,
1022 rp: i32,
1023 ) -> i32;
1024
1025 pub fn memra_mmq_iq_experts_act_bytes(in_f: i32, n_tokens: i32) -> usize;
1028 pub fn memra_mmq_iq_quantize_act(
1030 act_f32: *const f32,
1031 act_scratch: *mut core::ffi::c_void,
1032 in_f: i32,
1033 n_tokens: i32,
1034 stream: *mut core::ffi::c_void,
1035 ) -> i32;
1036 pub fn memra_mmq_iq_fused_act_quant(
1040 gate: *const f32,
1041 up: *const f32,
1042 act_scratch: *mut core::ffi::c_void,
1043 in_f: i32,
1044 n_tokens: i32,
1045 act_kind: i32,
1046 stream: *mut core::ffi::c_void,
1047 ) -> i32;
1048 pub fn memra_mmq_iq4xs_dense(
1057 w_blocks: *const core::ffi::c_void,
1058 act_f32: *const f32,
1059 y: *mut f32,
1060 in_f: i32,
1061 out_f: i32,
1062 n_tokens: i32,
1063 row_bytes: i64,
1064 act_scratch: *mut core::ffi::c_void,
1065 stream: *mut core::ffi::c_void,
1066 ) -> i32;
1067 pub fn memra_mmq_iq_experts(
1068 table: *const u64,
1069 proj: i32,
1070 n_expert: i32,
1071 ex_ids: *const i32,
1072 ex_off: *const i32,
1073 ex_pairs: *const i32,
1074 pair_tok: *const i32,
1075 act_scratch: *const core::ffi::c_void,
1076 y: *mut f32,
1077 in_f: i32,
1078 out_f: i32,
1079 n_active: i32,
1080 n_tokens: i32,
1081 qtype: i32,
1082 row_bytes: i64,
1083 stream: *mut core::ffi::c_void,
1084 ) -> i32;
1085
1086 pub fn memra_moe_f16g_dequant(
1088 table: *const u64,
1089 proj: i32,
1090 n_expert: i32,
1091 ex_ids: *const i32,
1092 w_f16: *mut core::ffi::c_void,
1093 in_f: i32,
1094 out_f: i32,
1095 n_active: i32,
1096 qtype: i32,
1097 row_bytes: i64,
1098 stream: *mut core::ffi::c_void,
1099 ) -> i32;
1100 pub fn memra_moe_f16g_gather_act(
1101 x: *const f32,
1102 pair_tok_or_null: *const i32,
1103 act_f16: *mut core::ffi::c_void,
1104 row_scale: *mut f32,
1105 in_f: i32,
1106 n_pairs: i32,
1107 stream: *mut core::ffi::c_void,
1108 ) -> i32;
1109 pub fn memra_moe_f16g_h2f_scaled(
1110 src_f16: *const core::ffi::c_void,
1111 dst: *mut f32,
1112 row_scale: *const f32,
1113 ncols: i32,
1114 nrows: i32,
1115 stream: *mut core::ffi::c_void,
1116 ) -> i32;
1117 pub fn memra_moe_f16g_gemm(
1118 w_f16: *const core::ffi::c_void,
1119 act_f16: *const core::ffi::c_void,
1120 y_f16: *mut core::ffi::c_void,
1121 ex_off_host: *const i32,
1122 n_active: i32,
1123 in_f: i32,
1124 out_f: i32,
1125 stream: *mut core::ffi::c_void,
1126 ) -> i32;
1127 pub fn memra_moe_f16g_h2f(
1128 src_f16: *const core::ffi::c_void,
1129 dst: *mut f32,
1130 n: usize,
1131 stream: *mut core::ffi::c_void,
1132 ) -> i32;
1133 pub fn memra_moe_f16g_gemm_sk(
1142 w_f16: *const core::ffi::c_void,
1143 act_f16: *const core::ffi::c_void,
1144 y_f32: *mut f32,
1145 row_scale: *const f32,
1146 ex_off_dev: *const i32,
1147 ex_off_host: *const i32,
1148 n_active: i32,
1149 max_m: i32,
1150 in_f: i32,
1151 out_f: i32,
1152 shape_sel: i32,
1153 cross: i32,
1154 tail: i32,
1155 stream: *mut core::ffi::c_void,
1156 ) -> i32;
1157 pub fn memra_moe_kq_gemm_sk(
1164 table: *const u64,
1165 proj: i32,
1166 n_expert: i32,
1167 ex_ids: *const i32,
1168 act_f16: *const core::ffi::c_void,
1169 y_f32: *mut f32,
1170 row_scale: *const f32,
1171 ex_off_dev: *const i32,
1172 ex_off_host: *const i32,
1173 n_active: i32,
1174 max_m: i32,
1175 in_f: i32,
1176 out_f: i32,
1177 qtype: i32,
1178 cross: i32,
1179 tail: i32,
1180 row_bytes: i64,
1181 stream: *mut core::ffi::c_void,
1182 ) -> i32;
1183}
1184
1185pub fn mmq_w4a8_enabled() -> bool {
1194 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1195 *ON.get_or_init(|| {
1196 std::env::var("MEMRA_MMQ_W4A8")
1197 .map(|v| v != "0")
1198 .unwrap_or(true)
1199 })
1200}
1201
1202pub fn mmq_residual_k() -> i32 {
1211 std::env::var("MEMRA_MMQ_RESIDUAL_K")
1212 .ok()
1213 .and_then(|v| v.parse::<i32>().ok())
1214 .unwrap_or(0)
1215 .clamp(0, 64)
1216}
1217
1218pub fn mmq_q8_enabled() -> bool {
1224 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1225 *ON.get_or_init(|| {
1229 std::env::var("MEMRA_PP_Q8MMQ")
1230 .map(|v| v != "0")
1231 .unwrap_or(true)
1232 })
1233}
1234
1235pub fn mmq_iq4xs_enabled() -> bool {
1243 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1244 *ON.get_or_init(|| {
1245 std::env::var("MEMRA_PP_IQMMQ")
1246 .map(|v| v != "0")
1247 .unwrap_or(true)
1248 })
1249}
1250
1251pub fn mmq_q4_enabled() -> bool {
1257 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1258 *ON.get_or_init(|| {
1259 std::env::var("MEMRA_PP_Q4MMQ")
1260 .map(|v| v != "0")
1261 .unwrap_or(true)
1262 })
1263}
1264
1265impl Engine {
1266 pub fn mmq_supports(&self, w: &crate::model::GpuTensor) -> bool {
1269 use crate::model::GpuTensor;
1270 if crate::portable_mma_gated() {
1271 return false;
1272 }
1273 let mmq_opt_in = std::env::var("MEMRA_MMQ").is_ok();
1274 match w {
1275 GpuTensor::Quant { qtype, rp, .. } if *qtype == crate::QT_NVFP4 && *rp => {
1283 !cfg!(memra_portable_cuda)
1284 && mmq_w4a8_enabled()
1285 && w.in_features().is_multiple_of(64)
1286 }
1287 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_NVFP4 => {
1289 !cfg!(memra_portable_cuda)
1290 && (mmq_w4a8_enabled() || mmq_opt_in)
1291 && w.in_features().is_multiple_of(64)
1292 }
1293 GpuTensor::Quant { qtype, .. }
1294 if *qtype == crate::QT_Q4_K || *qtype == crate::QT_Q5_K =>
1295 {
1296 (mmq_w4a8_enabled() || mmq_opt_in) && w.in_features().is_multiple_of(256)
1297 }
1298 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q8_0 => {
1303 mmq_q8_enabled() && w.in_features().is_multiple_of(256)
1304 }
1305 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q4_0 => {
1310 mmq_q4_enabled() && w.in_features().is_multiple_of(256)
1311 }
1312 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_IQ4_XS => {
1318 mmq_iq4xs_enabled()
1319 && Self::iq_fast_enabled()
1320 && w.in_features().is_multiple_of(256)
1321 }
1322 _ => false,
1323 }
1324 }
1325
1326 pub fn qmatvec_mmq(
1329 &self,
1330 w: &crate::model::GpuTensor,
1331 x: &CudaSlice<f32>,
1332 m: usize,
1333 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1334 use crate::model::GpuTensor;
1335 let (in_f, out_f) = (w.in_features(), w.out_features());
1336 let GpuTensor::Quant {
1337 bytes,
1338 scale,
1339 qtype,
1340 rp,
1341 ..
1342 } = w
1343 else {
1344 return Err("qmatvec_mmq: not a Quant tensor".into());
1345 };
1346 let w4a8_explicit = std::env::var("MEMRA_MMQ_W4A8")
1351 .map(|v| v != "0")
1352 .unwrap_or(false);
1353 let use_w4a8 =
1354 *rp || w4a8_explicit || (mmq_w4a8_enabled() && std::env::var("MEMRA_MMQ").is_err());
1355 match *qtype {
1356 q if q == crate::QT_NVFP4 && use_w4a8 => {
1359 self.qmatvec_mmq_nvfp4_w4a8(bytes, x, m, in_f, out_f, *scale, *rp)
1360 }
1361 q if q == crate::QT_NVFP4 => self.qmatvec_mmq_nvfp4(bytes, x, m, in_f, out_f, *scale),
1362 q if q == crate::QT_Q4_K || q == crate::QT_Q5_K => {
1363 let mut y = self.qmatvec_mmq_q45k_raw(bytes, x, m, in_f, out_f, q)?;
1364 if *scale != 1.0 {
1365 self.scale_inplace(&mut y, *scale, m * out_f)?;
1366 }
1367 Ok(y)
1368 }
1369 q if q == crate::QT_Q8_0 => {
1370 if cfg!(memra_hopper_mma)
1376 && out_f % 64 == 0
1377 && crate::wgmma_gemm_enabled()
1378 && let GpuTensor::Quant { rp4: Some(m4), .. } = w
1379 {
1380 let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
1381 let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, &aq, &ad, m, in_f, out_f)?;
1382 if *scale != 1.0 {
1383 self.scale_inplace(&mut y, *scale, m * out_f)?;
1384 }
1385 return Ok(y);
1386 }
1387 let mut y = self.qmatvec_mmq_q8_0_raw(bytes, x, m, in_f, out_f)?;
1388 if *scale != 1.0 {
1389 self.scale_inplace(&mut y, *scale, m * out_f)?;
1390 }
1391 Ok(y)
1392 }
1393 q if q == crate::QT_Q4_0 => {
1394 let mut y = self.qmatvec_mmq_q4_0_raw(bytes, x, m, in_f, out_f, *rp)?;
1395 if *scale != 1.0 {
1396 self.scale_inplace(&mut y, *scale, m * out_f)?;
1397 }
1398 Ok(y)
1399 }
1400 q if q == crate::QT_IQ4_XS => {
1401 let GpuTensor::Quant { row_bytes, .. } = w else {
1402 unreachable!()
1403 };
1404 let mut y = self.qmatvec_mmq_iq4xs_raw(bytes, x, m, in_f, out_f, *row_bytes)?;
1405 if *scale != 1.0 {
1406 self.scale_inplace(&mut y, *scale, m * out_f)?;
1407 }
1408 Ok(y)
1409 }
1410 q => Err(format!("qmatvec_mmq: unsupported qtype {q}").into()),
1411 }
1412 }
1413
1414 pub fn qmatvec_mmq_iq4xs_raw(
1416 &self,
1417 bytes: &CudaSlice<u8>,
1418 x: &CudaSlice<f32>,
1419 m: usize,
1420 in_f: usize,
1421 out_f: usize,
1422 row_bytes: usize,
1423 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1424 assert!(
1425 in_f.is_multiple_of(256),
1426 "MMQ IQ4_XS requires in_f % 256 == 0, got {in_f}"
1427 );
1428 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, m as i32) };
1429 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1430 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1431 {
1432 let stream = self.gpu.stream();
1433 let (w_p, _gw) = bytes.device_ptr(&stream);
1434 let (x_p, _gx) = x.device_ptr(&stream);
1435 let (y_p, _gy) = y.device_ptr_mut(&stream);
1436 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1437 let rc = unsafe {
1438 memra_mmq_iq4xs_dense(
1439 w_p as *const core::ffi::c_void,
1440 x_p as *const f32,
1441 y_p as *mut f32,
1442 in_f as i32,
1443 out_f as i32,
1444 m as i32,
1445 row_bytes as i64,
1446 s_p as *mut core::ffi::c_void,
1447 stream.cu_stream() as *mut core::ffi::c_void,
1448 )
1449 };
1450 if rc != 0 {
1451 return Err(format!("memra_mmq_iq4xs_dense rc={rc}").into());
1452 }
1453 }
1454 Ok(y)
1455 }
1456
1457 pub fn qmatvec_mmq_q45k_raw(
1462 &self,
1463 bytes: &CudaSlice<u8>,
1464 x: &CudaSlice<f32>,
1465 m: usize,
1466 in_f: usize,
1467 out_f: usize,
1468 qtype: i32,
1469 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1470 assert!(
1471 in_f.is_multiple_of(256),
1472 "MMQ Q4_K/Q5_K requires in_f % 256 == 0, got {in_f}"
1473 );
1474 let act_bytes = unsafe { memra_mmq_q45k_act_bytes(in_f as i32, m as i32) };
1475 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1476 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1477 {
1478 let stream = self.gpu.stream();
1479 let (w_p, _gw) = bytes.device_ptr(&stream);
1480 let (x_p, _gx) = x.device_ptr(&stream);
1481 let (y_p, _gy) = y.device_ptr_mut(&stream);
1482 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1483 let launcher = if qtype == crate::QT_Q4_K {
1484 memra_mmq_q4_K
1485 } else {
1486 memra_mmq_q5_K
1487 };
1488 let rc = unsafe {
1489 launcher(
1490 w_p as *const core::ffi::c_void,
1491 x_p as *const f32,
1492 y_p as *mut f32,
1493 in_f as i32,
1494 out_f as i32,
1495 m as i32,
1496 s_p as *mut core::ffi::c_void,
1497 stream.cu_stream() as *mut core::ffi::c_void,
1498 )
1499 };
1500 if rc != 0 {
1501 return Err(format!("memra_mmq_q45k(qtype={qtype}) rc={rc}").into());
1502 }
1503 }
1504 Ok(y)
1505 }
1506
1507 pub fn qmatvec_mmq_q8_0_raw(
1510 &self,
1511 bytes: &CudaSlice<u8>,
1512 x: &CudaSlice<f32>,
1513 m: usize,
1514 in_f: usize,
1515 out_f: usize,
1516 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1517 assert!(
1518 in_f.is_multiple_of(32),
1519 "MMQ Q8_0 requires in_f % 32 == 0, got {in_f}"
1520 );
1521 let act_bytes = unsafe { memra_mmq_q8_0_act_bytes(in_f as i32, m as i32) };
1522 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1523 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1524 {
1525 let stream = self.gpu.stream();
1526 let (w_p, _gw) = bytes.device_ptr(&stream);
1527 let (x_p, _gx) = x.device_ptr(&stream);
1528 let (y_p, _gy) = y.device_ptr_mut(&stream);
1529 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1530 let rc = unsafe {
1531 memra_mmq_q8_0(
1532 w_p as *const core::ffi::c_void,
1533 x_p as *const f32,
1534 y_p as *mut f32,
1535 in_f as i32,
1536 out_f as i32,
1537 m as i32,
1538 s_p as *mut core::ffi::c_void,
1539 stream.cu_stream() as *mut core::ffi::c_void,
1540 )
1541 };
1542 if rc != 0 {
1543 return Err(format!("memra_mmq_q8_0 rc={rc}").into());
1544 }
1545 }
1546 Ok(y)
1547 }
1548
1549 pub fn accprobe_act_bytes(&self, in_f: usize, m: usize) -> usize {
1552 unsafe { memra_accprobe_act_bytes(in_f as i32, m as i32) }
1553 }
1554
1555 pub fn accprobe_gemm(
1562 &self,
1563 w_q8_0: &CudaSlice<u8>,
1564 act_q: &CudaSlice<u8>,
1565 m: usize,
1566 in_f: usize,
1567 out_f: usize,
1568 f32acc: bool,
1569 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1570 assert!(
1571 in_f.is_multiple_of(32),
1572 "accprobe requires in_f % 32 == 0, got {in_f}"
1573 );
1574 assert!(
1575 act_q.len() >= self.accprobe_act_bytes(in_f, m),
1576 "accprobe act_q too small: {} < {}",
1577 act_q.len(),
1578 self.accprobe_act_bytes(in_f, m)
1579 );
1580 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1581 {
1582 let stream = self.gpu.stream();
1583 let (w_p, _gw) = w_q8_0.device_ptr(&stream);
1584 let (a_p, _ga) = act_q.device_ptr(&stream);
1585 let (y_p, _gy) = y.device_ptr_mut(&stream);
1586 let f = if f32acc {
1587 memra_accprobe_gemm_f32
1588 } else {
1589 memra_accprobe_gemm_s32
1590 };
1591 let rc = unsafe {
1592 f(
1593 w_p as *const core::ffi::c_void,
1594 a_p as *const core::ffi::c_void,
1595 y_p as *mut f32,
1596 in_f as i32,
1597 out_f as i32,
1598 m as i32,
1599 stream.cu_stream() as *mut core::ffi::c_void,
1600 )
1601 };
1602 if rc != 0 {
1603 let arm = if f32acc { "f32" } else { "s32" };
1604 return Err(format!("memra_accprobe_gemm_{arm} rc={rc}").into());
1605 }
1606 }
1607 Ok(y)
1608 }
1609
1610 pub fn mmq_act_begin(&self) {
1616 use std::sync::atomic::Ordering;
1617 MMQ_ACT_EPOCH.fetch_add(1, Ordering::Relaxed);
1618 *MMQ_ACT_SLOT.lock().unwrap() = None;
1619 }
1620
1621 pub fn qmatvec_mmq_q4_0_raw(
1625 &self,
1626 bytes: &CudaSlice<u8>,
1627 x: &CudaSlice<f32>,
1628 m: usize,
1629 in_f: usize,
1630 out_f: usize,
1631 rp: bool,
1632 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1633 use std::sync::atomic::Ordering;
1634 assert!(
1635 in_f.is_multiple_of(32),
1636 "MMQ Q4_0 requires in_f % 32 == 0, got {in_f}"
1637 );
1638 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1639 let stream = self.gpu.stream();
1640 let (x_p, _gx) = x.device_ptr(&stream);
1641 let epoch = MMQ_ACT_EPOCH.load(Ordering::Relaxed);
1642 let mut slot = MMQ_ACT_SLOT.lock().unwrap();
1644 let hit = matches!(&*slot,
1645 Some((e, p, mm, inf, _)) if *e == epoch && *p == x_p && *mm == m && *inf == in_f);
1646 if !hit {
1647 let act_bytes = unsafe { memra_mmq_q4_0_act_bytes(in_f as i32, m as i32) };
1648 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1649 {
1650 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1651 let rc = unsafe {
1652 memra_mmq_q4_0_quant_act(
1653 x_p as *const f32,
1654 s_p as *mut core::ffi::c_void,
1655 in_f as i32,
1656 m as i32,
1657 stream.cu_stream() as *mut core::ffi::c_void,
1658 )
1659 };
1660 if rc != 0 {
1661 return Err(
1662 format!("memra_mmq_q4_0_quant_act(in_f={in_f}, m={m}) rc={rc}").into(),
1663 );
1664 }
1665 }
1666 *slot = Some((epoch, x_p, m, in_f, scratch));
1667 }
1668 let scratch = &slot.as_ref().unwrap().4;
1669 {
1670 let (w_p, _gw) = bytes.device_ptr(&stream);
1671 let (y_p, _gy) = y.device_ptr_mut(&stream);
1672 let (s_p, _gs) = scratch.device_ptr(&stream);
1673 static SK_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1696 let sk = match crate::MMQ_SK_FORCE.load(std::sync::atomic::Ordering::Relaxed) {
1697 0 => false,
1698 1 => true,
1699 _ => *SK_ON.get_or_init(|| {
1700 std::env::var("MEMRA_MMQ_SK")
1701 .map(|v| v != "0")
1702 .unwrap_or(!cfg!(memra_hopper_mma))
1703 }),
1704 };
1705 let rc = if sk {
1706 let mut fx = MMQ_FIXUP_SLOT.lock().unwrap();
1707 if fx.is_none() {
1708 let nb = unsafe { memra_mmq_q4_0_fixup_bytes() };
1709 *fx = Some(self.alloc_uninit::<u8>(nb)?);
1710 }
1711 let (f_p, _gf) = fx.as_mut().unwrap().device_ptr_mut(&stream);
1712 unsafe {
1713 memra_mmq_q4_0_gemm_sk(
1714 w_p as *const core::ffi::c_void,
1715 s_p as *const core::ffi::c_void,
1716 y_p as *mut f32,
1717 f_p as *mut core::ffi::c_void,
1718 in_f as i32,
1719 out_f as i32,
1720 m as i32,
1721 stream.cu_stream() as *mut core::ffi::c_void,
1722 rp as i32,
1723 )
1724 }
1725 } else {
1726 unsafe {
1727 memra_mmq_q4_0_gemm(
1728 w_p as *const core::ffi::c_void,
1729 s_p as *const core::ffi::c_void,
1730 y_p as *mut f32,
1731 in_f as i32,
1732 out_f as i32,
1733 m as i32,
1734 stream.cu_stream() as *mut core::ffi::c_void,
1735 rp as i32,
1736 )
1737 }
1738 };
1739 if rc != 0 {
1740 return Err(format!(
1741 "memra_mmq_q4_0_gemm(rp={rp}, in_f={in_f}, out_f={out_f}, m={m}, wbytes={}) rc={rc}",
1742 bytes.len()
1743 )
1744 .into());
1745 }
1746 }
1747 Ok(y)
1748 }
1749
1750 pub fn qmatvec_mmq_nvfp4(
1756 &self,
1757 bytes: &CudaSlice<u8>,
1758 x: &CudaSlice<f32>,
1759 m: usize,
1760 in_f: usize,
1761 out_f: usize,
1762 scale: f32,
1763 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1764 self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, scale)
1765 }
1766
1767 pub fn qmatvec_mmq_nvfp4_raw(
1769 &self,
1770 bytes: &CudaSlice<u8>,
1771 x: &CudaSlice<f32>,
1772 m: usize,
1773 in_f: usize,
1774 out_f: usize,
1775 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1776 self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, 1.0)
1777 }
1778
1779 pub fn qmatvec_mmq_nvfp4_raw_v1(
1783 &self,
1784 bytes: &CudaSlice<u8>,
1785 x: &CudaSlice<f32>,
1786 m: usize,
1787 in_f: usize,
1788 out_f: usize,
1789 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1790 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, false, 0)
1791 }
1792
1793 pub fn qmatvec_mmq_nvfp4_raw_res(
1795 &self,
1796 bytes: &CudaSlice<u8>,
1797 x: &CudaSlice<f32>,
1798 m: usize,
1799 in_f: usize,
1800 out_f: usize,
1801 residual_k: i32,
1802 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1803 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, true, residual_k)
1804 }
1805
1806 fn qmatvec_mmq_nvfp4_scaled(
1807 &self,
1808 bytes: &CudaSlice<u8>,
1809 x: &CudaSlice<f32>,
1810 m: usize,
1811 in_f: usize,
1812 out_f: usize,
1813 scale: f32,
1814 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1815 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, scale, true, mmq_residual_k())
1816 }
1817
1818 #[allow(clippy::too_many_arguments)] fn qmatvec_mmq_nvfp4_inner(
1820 &self,
1821 bytes: &CudaSlice<u8>,
1822 x: &CudaSlice<f32>,
1823 m: usize,
1824 in_f: usize,
1825 out_f: usize,
1826 scale: f32,
1827 per_token_scale: bool,
1828 residual_k: i32,
1829 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1830 assert!(
1831 in_f.is_multiple_of(64),
1832 "MMQ NVFP4 requires in_f % 64 == 0, got {in_f}"
1833 );
1834 let act_bytes = unsafe { memra_mmq_nvfp4_act_bytes(in_f as i32, m as i32) };
1835 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1836 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1837 {
1838 let stream = self.gpu.stream();
1839 let (w_p, _gw) = bytes.device_ptr(&stream);
1840 let (x_p, _gx) = x.device_ptr(&stream);
1841 let (y_p, _gy) = y.device_ptr_mut(&stream);
1842 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1843 let rc = unsafe {
1844 memra_mmq_nvfp4_ex2(
1845 w_p as *const core::ffi::c_void,
1846 x_p as *const f32,
1847 y_p as *mut f32,
1848 in_f as i32,
1849 out_f as i32,
1850 m as i32,
1851 s_p as *mut core::ffi::c_void,
1852 stream.cu_stream() as *mut core::ffi::c_void,
1853 scale,
1854 per_token_scale as i32,
1855 residual_k,
1856 )
1857 };
1858 if rc != 0 {
1859 return Err(format!("memra_mmq_nvfp4_ex2 rc={rc}").into());
1860 }
1861 }
1862 Ok(y)
1863 }
1864
1865 #[allow(clippy::too_many_arguments)] pub fn qmatvec_mmq_nvfp4_w4a8(
1871 &self,
1872 bytes: &CudaSlice<u8>,
1873 x: &CudaSlice<f32>,
1874 m: usize,
1875 in_f: usize,
1876 out_f: usize,
1877 scale: f32,
1878 rp: bool,
1879 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1880 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, scale, rp)
1881 }
1882
1883 pub fn qmatvec_mmq_nvfp4_w4a8_raw(
1885 &self,
1886 bytes: &CudaSlice<u8>,
1887 x: &CudaSlice<f32>,
1888 m: usize,
1889 in_f: usize,
1890 out_f: usize,
1891 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1892 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, false)
1893 }
1894
1895 pub fn qmatvec_mmq_nvfp4_w4a8_raw_rp(
1898 &self,
1899 bytes: &CudaSlice<u8>,
1900 x: &CudaSlice<f32>,
1901 m: usize,
1902 in_f: usize,
1903 out_f: usize,
1904 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1905 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, true)
1906 }
1907
1908 #[allow(clippy::too_many_arguments)] fn qmatvec_mmq_nvfp4_w4a8_scaled(
1910 &self,
1911 bytes: &CudaSlice<u8>,
1912 x: &CudaSlice<f32>,
1913 m: usize,
1914 in_f: usize,
1915 out_f: usize,
1916 scale: f32,
1917 rp: bool,
1918 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1919 assert!(
1920 in_f.is_multiple_of(64),
1921 "MMQ NVFP4 W4A8 requires in_f % 64 == 0, got {in_f}"
1922 );
1923 let act_bytes = unsafe { memra_mmq_nvfp4_w4a8_act_bytes(in_f as i32, m as i32) };
1924 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1925 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1926 {
1927 let stream = self.gpu.stream();
1928 let (w_p, _gw) = bytes.device_ptr(&stream);
1929 let (x_p, _gx) = x.device_ptr(&stream);
1930 let (y_p, _gy) = y.device_ptr_mut(&stream);
1931 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1932 static F8F4: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1935 let f8f4 = *F8F4.get_or_init(|| std::env::var("MEMRA_MMQ_F8F4").as_deref() == Ok("1"));
1936 let rc = unsafe {
1937 if f8f4 {
1938 memra_mmq_nvfp4_f8f4(
1939 w_p as *const core::ffi::c_void,
1940 x_p as *const f32,
1941 y_p as *mut f32,
1942 in_f as i32,
1943 out_f as i32,
1944 m as i32,
1945 s_p as *mut core::ffi::c_void,
1946 stream.cu_stream() as *mut core::ffi::c_void,
1947 scale,
1948 rp as i32,
1949 )
1950 } else {
1951 memra_mmq_nvfp4_w4a8(
1952 w_p as *const core::ffi::c_void,
1953 x_p as *const f32,
1954 y_p as *mut f32,
1955 in_f as i32,
1956 out_f as i32,
1957 m as i32,
1958 s_p as *mut core::ffi::c_void,
1959 stream.cu_stream() as *mut core::ffi::c_void,
1960 scale,
1961 rp as i32,
1962 )
1963 }
1964 };
1965 if rc != 0 {
1966 return Err(format!("memra_mmq_nvfp4_w4a8(f8f4={f8f4}) rc={rc}").into());
1967 }
1968 }
1969 Ok(y)
1970 }
1971
1972 pub fn qmatvec_mmq_fp8_blk(
1976 &self,
1977 w_e4m3: &CudaSlice<u8>,
1978 blk_scales: &CudaSlice<f32>,
1979 x: &CudaSlice<f32>,
1980 m: usize,
1981 in_f: usize,
1982 out_f: usize,
1983 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1984 self.qmatvec_mmq_fp8_blk_scaled(w_e4m3, blk_scales, x, m, in_f, out_f, 1.0)
1985 }
1986
1987 #[allow(clippy::too_many_arguments)] pub fn qmatvec_mmq_fp8_blk_scaled(
1989 &self,
1990 w_e4m3: &CudaSlice<u8>,
1991 blk_scales: &CudaSlice<f32>,
1992 x: &CudaSlice<f32>,
1993 m: usize,
1994 in_f: usize,
1995 out_f: usize,
1996 scale: f32,
1997 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1998 assert!(
1999 in_f.is_multiple_of(16),
2000 "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
2001 );
2002 #[allow(clippy::manual_div_ceil)]
2003 let want_scales = ((out_f + 127) / 128) * ((in_f + 127) / 128);
2005 assert!(
2006 blk_scales.len() >= want_scales,
2007 "blk_scales too small: {} < {want_scales}",
2008 blk_scales.len()
2009 );
2010 assert!(
2011 w_e4m3.len() >= out_f * in_f,
2012 "e4m3 plane too small: {} < {}",
2013 w_e4m3.len(),
2014 out_f * in_f
2015 );
2016 let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2017 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2018 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2019 {
2020 let stream = self.gpu.stream();
2021 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2022 let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2023 let (x_p, _gx) = x.device_ptr(&stream);
2024 let (y_p, _gy) = y.device_ptr_mut(&stream);
2025 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2026 let rc = unsafe {
2027 memra_mmq_fp8_blk(
2028 w_p as *const core::ffi::c_void,
2029 sc_p as *const f32,
2030 x_p as *const f32,
2031 y_p as *mut f32,
2032 in_f as i32,
2033 out_f as i32,
2034 m as i32,
2035 s_p as *mut core::ffi::c_void,
2036 stream.cu_stream() as *mut core::ffi::c_void,
2037 scale,
2038 )
2039 };
2040 if rc != 0 {
2041 return Err(format!("memra_mmq_fp8_blk rc={rc}").into());
2042 }
2043 }
2044 Ok(y)
2045 }
2046
2047 pub fn qmatvec_mmq_fp8_blk_view(
2052 &self,
2053 w_e4m3: &CudaView<'_, u8>,
2054 blk_scales: &CudaView<'_, f32>,
2055 x: &CudaView<'_, f32>,
2056 m: usize,
2057 in_f: usize,
2058 out_f: usize,
2059 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2060 assert!(
2061 in_f.is_multiple_of(16),
2062 "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
2063 );
2064 let want_scales = out_f.div_ceil(128) * in_f.div_ceil(128);
2065 assert!(
2066 blk_scales.len() >= want_scales,
2067 "blk_scales view too small: {} < {want_scales}",
2068 blk_scales.len()
2069 );
2070 assert!(
2071 w_e4m3.len() >= out_f * in_f,
2072 "e4m3 view too small: {} < {}",
2073 w_e4m3.len(),
2074 out_f * in_f
2075 );
2076 assert!(
2077 x.len() >= m * in_f,
2078 "activation view too small: {} < {}",
2079 x.len(),
2080 m * in_f
2081 );
2082
2083 let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2084 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2085 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2086 {
2087 let stream = self.gpu.stream();
2088 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2089 let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2090 let (x_p, _gx) = x.device_ptr(&stream);
2091 let (y_p, _gy) = y.device_ptr_mut(&stream);
2092 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2093 let rc = unsafe {
2094 memra_mmq_fp8_blk(
2095 w_p as *const core::ffi::c_void,
2096 sc_p as *const f32,
2097 x_p as *const f32,
2098 y_p as *mut f32,
2099 in_f as i32,
2100 out_f as i32,
2101 m as i32,
2102 s_p as *mut core::ffi::c_void,
2103 stream.cu_stream() as *mut core::ffi::c_void,
2104 1.0,
2105 )
2106 };
2107 if rc != 0 {
2108 return Err(format!("memra_mmq_fp8_blk(view) rc={rc}").into());
2109 }
2110 }
2111 Ok(y)
2112 }
2113
2114 pub fn fp8_blk_nan_count(
2118 &self,
2119 w_e4m3: &CudaSlice<u8>,
2120 ) -> Result<u32, Box<dyn std::error::Error>> {
2121 let mut cnt = self.htod_u32_v(&[0u32])?;
2122 let n = w_e4m3.len();
2123 {
2124 let stream = self.gpu.stream();
2125 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2126 let (c_p, _gc) = cnt.device_ptr_mut(&stream);
2127 let rc = unsafe {
2128 memra_fp8_blk_count_nan(
2129 w_p as *const core::ffi::c_void,
2130 n,
2131 c_p as *mut u32,
2132 stream.cu_stream() as *mut core::ffi::c_void,
2133 )
2134 };
2135 if rc != 0 {
2136 return Err(format!("memra_fp8_blk_count_nan rc={rc}").into());
2137 }
2138 }
2139 Ok(self.dtoh_u32(&cnt)?[0])
2140 }
2141
2142 pub fn mmq_iq_quantize_act(
2145 &self,
2146 x: &CudaSlice<f32>,
2147 in_f: usize,
2148 n_tokens: usize,
2149 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2150 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2151 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2152 {
2153 let stream = self.gpu.stream();
2154 let (x_p, _gx) = x.device_ptr(&stream);
2155 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2156 let rc = unsafe {
2157 memra_mmq_iq_quantize_act(
2158 x_p as *const f32,
2159 s_p as *mut core::ffi::c_void,
2160 in_f as i32,
2161 n_tokens as i32,
2162 stream.cu_stream() as *mut core::ffi::c_void,
2163 )
2164 };
2165 if rc != 0 {
2166 return Err(format!("memra_mmq_iq_quantize_act rc={rc}").into());
2167 }
2168 }
2169 Ok(scratch)
2170 }
2171
2172 pub fn mmq_iq_fused_act_quant(
2178 &self,
2179 gate: &CudaSlice<f32>,
2180 up: &CudaSlice<f32>,
2181 in_f: usize,
2182 n_tokens: usize,
2183 act_kind: i32,
2184 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2185 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2186 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2187 {
2188 let stream = self.gpu.stream();
2189 let (g_p, _gg) = gate.device_ptr(&stream);
2190 let (u_p, _gu) = up.device_ptr(&stream);
2191 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2192 let rc = unsafe {
2193 memra_mmq_iq_fused_act_quant(
2194 g_p as *const f32,
2195 u_p as *const f32,
2196 s_p as *mut core::ffi::c_void,
2197 in_f as i32,
2198 n_tokens as i32,
2199 act_kind,
2200 stream.cu_stream() as *mut core::ffi::c_void,
2201 )
2202 };
2203 if rc != 0 {
2204 return Err(format!("memra_mmq_iq_fused_act_quant rc={rc}").into());
2205 }
2206 }
2207 Ok(scratch)
2208 }
2209
2210 #[allow(clippy::too_many_arguments)]
2214 pub fn mmq_iq_experts(
2215 &self,
2216 table: &CudaSlice<u64>,
2217 proj: i32,
2218 n_expert: usize,
2219 ex_ids: &CudaSlice<i32>,
2220 ex_off: &CudaSlice<i32>,
2221 ex_pairs: &CudaSlice<i32>,
2222 pair_tok: &CudaSlice<i32>,
2223 act_scratch: &CudaSlice<u8>,
2224 in_f: usize,
2225 out_f: usize,
2226 n_active: usize,
2227 n_pairs: usize,
2228 n_tokens: usize,
2229 qtype: i32,
2230 row_bytes: usize,
2231 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2232 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2233 {
2234 let stream = self.gpu.stream();
2235 let (tab_p, _g0) = table.device_ptr(&stream);
2236 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2237 let (eo_p, _g2) = ex_off.device_ptr(&stream);
2238 let (ep_p, _g3) = ex_pairs.device_ptr(&stream);
2239 let (pt_p, _g4) = pair_tok.device_ptr(&stream);
2240 let (as_p, _g5) = act_scratch.device_ptr(&stream);
2241 let (y_p, _g6) = y.device_ptr_mut(&stream);
2242 let rc = unsafe {
2243 memra_mmq_iq_experts(
2244 tab_p as *const u64,
2245 proj,
2246 n_expert as i32,
2247 ei_p as *const i32,
2248 eo_p as *const i32,
2249 ep_p as *const i32,
2250 pt_p as *const i32,
2251 as_p as *const core::ffi::c_void,
2252 y_p as *mut f32,
2253 in_f as i32,
2254 out_f as i32,
2255 n_active as i32,
2256 n_tokens as i32,
2257 qtype,
2258 row_bytes as i64,
2259 stream.cu_stream() as *mut core::ffi::c_void,
2260 )
2261 };
2262 if rc != 0 {
2263 return Err(format!("memra_mmq_iq_experts rc={rc}").into());
2264 }
2265 }
2266 Ok(y)
2267 }
2268
2269 pub fn moe_f16g_act(
2274 &self,
2275 x: &CudaSlice<f32>,
2276 pair_tok: Option<&CudaSlice<i32>>,
2277 in_f: usize,
2278 n_pairs: usize,
2279 ) -> Result<(CudaSlice<u8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2280 let mut act = self.alloc_uninit::<u8>(n_pairs * in_f * 2)?;
2281 let mut scales = self.alloc_uninit::<f32>(n_pairs)?;
2282 {
2283 let stream = self.gpu.stream();
2284 let (x_p, _gx) = x.device_ptr(&stream);
2285 let pt_p = match pair_tok {
2286 Some(pt) => {
2287 let (p, _g) = pt.device_ptr(&stream);
2288 p as *const i32
2289 }
2290 None => std::ptr::null(),
2291 };
2292 let (a_p, _ga) = act.device_ptr_mut(&stream);
2293 let (s_p, _gs) = scales.device_ptr_mut(&stream);
2294 let rc = unsafe {
2295 memra_moe_f16g_gather_act(
2296 x_p as *const f32,
2297 pt_p,
2298 a_p as *mut core::ffi::c_void,
2299 s_p as *mut f32,
2300 in_f as i32,
2301 n_pairs as i32,
2302 stream.cu_stream() as *mut core::ffi::c_void,
2303 )
2304 };
2305 if rc != 0 {
2306 return Err(format!("memra_moe_f16g_gather_act rc={rc}").into());
2307 }
2308 }
2309 Ok((act, scales))
2310 }
2311
2312 #[allow(clippy::too_many_arguments)]
2322 pub fn bind_runtime_device(&self, ordinal: i32) -> Result<(), Box<dyn std::error::Error>> {
2327 let rc = unsafe { memra_bind_device(ordinal) };
2328 if rc != 0 {
2329 return Err(format!("cudaSetDevice({ordinal}) rc={rc}").into());
2330 }
2331 Ok(())
2332 }
2333
2334 #[allow(clippy::too_many_arguments)]
2335 #[allow(clippy::manual_is_multiple_of)] pub fn moe_f16_grouped(
2338 &self,
2339 table: &CudaSlice<u64>,
2340 proj: i32,
2341 n_expert: usize,
2342 ex_ids: &CudaSlice<i32>,
2343 ex_off_host: &[i32],
2344 ex_off_dev: &CudaSlice<i32>,
2345 act_f16: &CudaSlice<u8>,
2346 act_scale: &CudaSlice<f32>,
2347 in_f: usize,
2348 out_f: usize,
2349 n_active: usize,
2350 n_pairs: usize,
2351 qtype: i32,
2352 row_bytes: usize,
2353 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2354 let sk = crate::moe_f16g_mode() >= 2 && in_f.is_multiple_of(32);
2355 let (shape_sel, cross) = crate::moe_f16g_sk_params();
2363 if sk
2364 && shape_sel >= 0
2365 && crate::moe_f16g_direct_on(qtype)
2366 && (qtype == crate::QT_Q4_K
2367 || qtype == crate::QT_Q6_K
2368 || qtype == crate::QT_IQ4_XS
2369 || qtype == crate::QT_IQ3_S
2370 || qtype == crate::QT_NVFP4
2371 || qtype == crate::QT_NVFP4_V2)
2375 && in_f % (if qtype == crate::QT_NVFP4 || qtype == crate::QT_NVFP4_V2 { 64 } else { 256 }) == 0
2378 && n_active <= 512
2379 && n_active > 0
2380 {
2381 let max_m = ex_off_host
2382 .windows(2)
2383 .map(|w| w[1] - w[0])
2384 .max()
2385 .unwrap_or(0);
2386 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2387 {
2388 let stream = self.gpu.stream();
2389 let (tab_p, _g0) = table.device_ptr(&stream);
2390 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2391 let (a_p, _g2) = act_f16.device_ptr(&stream);
2392 let (s_p, _g3) = act_scale.device_ptr(&stream);
2393 let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2394 let (y_p, _g5) = y.device_ptr_mut(&stream);
2395 let rc = unsafe {
2396 memra_moe_kq_gemm_sk(
2397 tab_p as *const u64,
2398 proj,
2399 n_expert as i32,
2400 ei_p as *const i32,
2401 a_p as *const core::ffi::c_void,
2402 y_p as *mut f32,
2403 s_p as *const f32,
2404 off_p as *const i32,
2405 ex_off_host.as_ptr(),
2406 n_active as i32,
2407 max_m,
2408 in_f as i32,
2409 out_f as i32,
2410 qtype,
2411 cross,
2412 crate::moe_f16g_tail_on() as i32,
2413 row_bytes as i64,
2414 stream.cu_stream() as *mut core::ffi::c_void,
2415 )
2416 };
2417 if rc != 0 {
2418 return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2419 }
2420 }
2421 return Ok(y);
2422 }
2423 if !sk {
2427 static WARM: std::sync::Once = std::sync::Once::new();
2428 let mut warm_err = None;
2429 WARM.call_once(|| {
2430 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2431 let w = self.alloc_uninit::<u8>(2 * 32 * 64 * 2)?;
2432 let a = self.alloc_uninit::<u8>(4 * 64 * 2)?;
2433 let mut yw = self.alloc_uninit::<u8>(4 * 32 * 2)?;
2434 let off = [0i32, 2, 4];
2435 let stream = self.gpu.stream();
2436 let (w_p, _a1) = w.device_ptr(&stream);
2437 let (a_p, _a2) = a.device_ptr(&stream);
2438 let (y_p, _a3) = yw.device_ptr_mut(&stream);
2439 let rc = unsafe {
2440 memra_moe_f16g_gemm(
2441 w_p as *const core::ffi::c_void,
2442 a_p as *const core::ffi::c_void,
2443 y_p as *mut core::ffi::c_void,
2444 off.as_ptr(),
2445 2,
2446 64,
2447 32,
2448 stream.cu_stream() as *mut core::ffi::c_void,
2449 )
2450 };
2451 if rc != 0 {
2452 return Err(format!("f16g warmup rc={rc}").into());
2453 }
2454 self.gpu.stream().synchronize()?;
2455 Ok(())
2456 })();
2457 if let Err(e) = r {
2458 warm_err = Some(e.to_string());
2459 }
2460 });
2461 if let Some(we) = warm_err {
2462 return Err(we.into());
2463 }
2464 }
2465 let w_bytes = n_active * out_f * in_f * 2;
2466 let mut w_f16 = self.alloc_uninit::<u8>(w_bytes)?;
2467 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2468 {
2469 let stream = self.gpu.stream();
2470 let (tab_p, _g0) = table.device_ptr(&stream);
2471 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2472 let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2473 let rc = unsafe {
2474 memra_moe_f16g_dequant(
2475 tab_p as *const u64,
2476 proj,
2477 n_expert as i32,
2478 ei_p as *const i32,
2479 w_p as *mut core::ffi::c_void,
2480 in_f as i32,
2481 out_f as i32,
2482 n_active as i32,
2483 qtype,
2484 row_bytes as i64,
2485 stream.cu_stream() as *mut core::ffi::c_void,
2486 )
2487 };
2488 if rc != 0 {
2489 return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2490 }
2491 let (a_p, _g3) = act_f16.device_ptr(&stream);
2492 let (s_p, _g6) = act_scale.device_ptr(&stream);
2493 let (y_p, _g5) = y.device_ptr_mut(&stream);
2494 if sk {
2495 let max_m = ex_off_host
2496 .windows(2)
2497 .map(|w| w[1] - w[0])
2498 .max()
2499 .unwrap_or(0);
2500 let (off_p, _g7) = ex_off_dev.device_ptr(&stream);
2501 let (shape_sel, cross) = crate::moe_f16g_sk_params();
2502 let rc = unsafe {
2503 memra_moe_f16g_gemm_sk(
2504 w_p as *const core::ffi::c_void,
2505 a_p as *const core::ffi::c_void,
2506 y_p as *mut f32,
2507 s_p as *const f32,
2508 off_p as *const i32,
2509 ex_off_host.as_ptr(),
2510 n_active as i32,
2511 max_m,
2512 in_f as i32,
2513 out_f as i32,
2514 shape_sel,
2515 cross,
2516 crate::moe_f16g_tail_on() as i32,
2517 stream.cu_stream() as *mut core::ffi::c_void,
2518 )
2519 };
2520 if rc != 0 {
2521 return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2522 }
2523 } else {
2524 let mut y16 = self.alloc_uninit::<u8>(n_pairs * out_f * 2)?;
2525 let (y16_p, _g4) = y16.device_ptr_mut(&stream);
2526 let rc = unsafe {
2527 memra_moe_f16g_gemm(
2528 w_p as *const core::ffi::c_void,
2529 a_p as *const core::ffi::c_void,
2530 y16_p as *mut core::ffi::c_void,
2531 ex_off_host.as_ptr(),
2532 n_active as i32,
2533 in_f as i32,
2534 out_f as i32,
2535 stream.cu_stream() as *mut core::ffi::c_void,
2536 )
2537 };
2538 if rc != 0 {
2539 return Err(format!("memra_moe_f16g_gemm rc={rc}").into());
2540 }
2541 let rc = unsafe {
2542 memra_moe_f16g_h2f_scaled(
2543 y16_p as *const core::ffi::c_void,
2544 y_p as *mut f32,
2545 s_p as *const f32,
2546 out_f as i32,
2547 n_pairs as i32,
2548 stream.cu_stream() as *mut core::ffi::c_void,
2549 )
2550 };
2551 if rc != 0 {
2552 return Err(format!("memra_moe_f16g_h2f_scaled rc={rc}").into());
2553 }
2554 }
2555 }
2556 if !sk {
2561 self.gpu.stream().synchronize()?;
2562 }
2563 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
2564 let wn = n_active * out_f * in_f;
2566 let an = n_pairs * in_f;
2567 let mut wf = self.alloc_uninit::<f32>(wn)?;
2568 let mut af = self.alloc_uninit::<f32>(an)?;
2569 {
2570 let stream = self.gpu.stream();
2571 let (w_p, _a) = w_f16.device_ptr(&stream);
2572 let (a_p, _b) = act_f16.device_ptr(&stream);
2573 let (wf_p, _c) = wf.device_ptr_mut(&stream);
2574 let (af_p, _d) = af.device_ptr_mut(&stream);
2575 unsafe {
2576 memra_moe_f16g_h2f(
2577 w_p as *const core::ffi::c_void,
2578 wf_p as *mut f32,
2579 wn,
2580 stream.cu_stream() as *mut core::ffi::c_void,
2581 );
2582 memra_moe_f16g_h2f(
2583 a_p as *const core::ffi::c_void,
2584 af_p as *mut f32,
2585 an,
2586 stream.cu_stream() as *mut core::ffi::c_void,
2587 );
2588 }
2589 }
2590 let (wh, ah, yh) = (self.dtoh(&wf)?, self.dtoh(&af)?, self.dtoh(&y)?);
2591 let scan = |v: &[f32]| -> (usize, f32) {
2592 let bad = v.iter().filter(|x| !x.is_finite()).count();
2593 let mx = v
2594 .iter()
2595 .filter(|x| x.is_finite())
2596 .fold(0.0f32, |m, x| m.max(x.abs()));
2597 (bad, mx)
2598 };
2599 let (wb, wm) = scan(&wh);
2600 let (ab, am) = scan(&ah);
2601 let (yb, ym) = scan(&yh);
2602 eprintln!(
2603 "[f16g-debug] proj={proj} w: bad={wb} max={wm:.3e} | act: bad={ab} \
2604 max={am:.3e} | y: bad={yb} max={ym:.3e} (na={n_active} np={n_pairs} \
2605 in={in_f} out={out_f})"
2606 );
2607 }
2608 Ok(y)
2609 }
2610
2611 #[allow(clippy::too_many_arguments)]
2618 pub fn moe_f16g_gemm_sk_raw(
2619 &self,
2620 w_f16: &CudaSlice<u8>,
2621 act_f16: &CudaSlice<u8>,
2622 row_scale: &CudaSlice<f32>,
2623 ex_off_host: &[i32],
2624 ex_off_dev: &CudaSlice<i32>,
2625 in_f: usize,
2626 out_f: usize,
2627 n_pairs: usize,
2628 shape_sel: i32,
2629 cross: i32,
2630 tail: i32,
2631 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2632 let n_active = ex_off_host.len() - 1;
2633 let max_m = ex_off_host
2634 .windows(2)
2635 .map(|w| w[1] - w[0])
2636 .max()
2637 .unwrap_or(0);
2638 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2639 {
2640 let stream = self.gpu.stream();
2641 let (w_p, _g0) = w_f16.device_ptr(&stream);
2642 let (a_p, _g1) = act_f16.device_ptr(&stream);
2643 let (s_p, _g2) = row_scale.device_ptr(&stream);
2644 let (off_p, _g3) = ex_off_dev.device_ptr(&stream);
2645 let (y_p, _g4) = y.device_ptr_mut(&stream);
2646 let rc = unsafe {
2647 memra_moe_f16g_gemm_sk(
2648 w_p as *const core::ffi::c_void,
2649 a_p as *const core::ffi::c_void,
2650 y_p as *mut f32,
2651 s_p as *const f32,
2652 off_p as *const i32,
2653 ex_off_host.as_ptr(),
2654 n_active as i32,
2655 max_m,
2656 in_f as i32,
2657 out_f as i32,
2658 shape_sel,
2659 cross,
2660 tail,
2661 stream.cu_stream() as *mut core::ffi::c_void,
2662 )
2663 };
2664 if rc != 0 {
2665 return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2666 }
2667 }
2668 Ok(y)
2669 }
2670
2671 #[allow(clippy::too_many_arguments)]
2676 pub fn moe_kq_gemm_sk_raw(
2677 &self,
2678 table: &CudaSlice<u64>,
2679 proj: i32,
2680 n_expert: usize,
2681 ex_ids: &CudaSlice<i32>,
2682 act_f16: &CudaSlice<u8>,
2683 row_scale: &CudaSlice<f32>,
2684 ex_off_host: &[i32],
2685 ex_off_dev: &CudaSlice<i32>,
2686 in_f: usize,
2687 out_f: usize,
2688 n_pairs: usize,
2689 qtype: i32,
2690 row_bytes: usize,
2691 cross: i32,
2692 tail: i32,
2693 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2694 let n_active = ex_off_host.len() - 1;
2695 let max_m = ex_off_host
2696 .windows(2)
2697 .map(|w| w[1] - w[0])
2698 .max()
2699 .unwrap_or(0);
2700 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2701 {
2702 let stream = self.gpu.stream();
2703 let (tab_p, _g0) = table.device_ptr(&stream);
2704 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2705 let (a_p, _g2) = act_f16.device_ptr(&stream);
2706 let (s_p, _g3) = row_scale.device_ptr(&stream);
2707 let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2708 let (y_p, _g5) = y.device_ptr_mut(&stream);
2709 let rc = unsafe {
2710 memra_moe_kq_gemm_sk(
2711 tab_p as *const u64,
2712 proj,
2713 n_expert as i32,
2714 ei_p as *const i32,
2715 a_p as *const core::ffi::c_void,
2716 y_p as *mut f32,
2717 s_p as *const f32,
2718 off_p as *const i32,
2719 ex_off_host.as_ptr(),
2720 n_active as i32,
2721 max_m,
2722 in_f as i32,
2723 out_f as i32,
2724 qtype,
2725 cross,
2726 tail,
2727 row_bytes as i64,
2728 stream.cu_stream() as *mut core::ffi::c_void,
2729 )
2730 };
2731 if rc != 0 {
2732 return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2733 }
2734 }
2735 Ok(y)
2736 }
2737
2738 #[allow(clippy::too_many_arguments)] pub fn moe_f16g_dequant_raw(
2743 &self,
2744 table: &CudaSlice<u64>,
2745 proj: i32,
2746 n_expert: usize,
2747 ex_ids: &CudaSlice<i32>,
2748 in_f: usize,
2749 out_f: usize,
2750 n_active: usize,
2751 qtype: i32,
2752 row_bytes: usize,
2753 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2754 let mut w_f16 = self.alloc_uninit::<u8>(n_active * out_f * in_f * 2)?;
2755 {
2756 let stream = self.gpu.stream();
2757 let (tab_p, _g0) = table.device_ptr(&stream);
2758 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2759 let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2760 let rc = unsafe {
2761 memra_moe_f16g_dequant(
2762 tab_p as *const u64,
2763 proj,
2764 n_expert as i32,
2765 ei_p as *const i32,
2766 w_p as *mut core::ffi::c_void,
2767 in_f as i32,
2768 out_f as i32,
2769 n_active as i32,
2770 qtype,
2771 row_bytes as i64,
2772 stream.cu_stream() as *mut core::ffi::c_void,
2773 )
2774 };
2775 if rc != 0 {
2776 return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2777 }
2778 }
2779 Ok(w_f16)
2780 }
2781}