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 % 16 != 0
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 pub fn memra_mmq_nvfp4_act_bytes(in_f: i32, n_tokens: i32) -> usize;
742 pub fn memra_mmq_nvfp4(
749 w_nvfp4_blocks: *const core::ffi::c_void,
750 act_f32: *const f32,
751 y: *mut f32,
752 in_f: i32,
753 out_f: i32,
754 n_tokens: i32,
755 act_scratch: *mut core::ffi::c_void,
756 stream: *mut core::ffi::c_void,
757 out_scale: f32,
758 ) -> i32;
759 pub fn memra_mmq_nvfp4_ex(
765 w_nvfp4_blocks: *const core::ffi::c_void,
766 act_f32: *const f32,
767 y: *mut f32,
768 in_f: i32,
769 out_f: i32,
770 n_tokens: i32,
771 act_scratch: *mut core::ffi::c_void,
772 stream: *mut core::ffi::c_void,
773 out_scale: f32,
774 per_token_scale: i32,
775 ) -> i32;
776 pub fn memra_mmq_nvfp4_ex2(
782 w_nvfp4_blocks: *const core::ffi::c_void,
783 act_f32: *const f32,
784 y: *mut f32,
785 in_f: i32,
786 out_f: i32,
787 n_tokens: i32,
788 act_scratch: *mut core::ffi::c_void,
789 stream: *mut core::ffi::c_void,
790 out_scale: f32,
791 per_token_scale: i32,
792 residual_k: i32,
793 ) -> i32;
794 pub fn memra_mmq_nvfp4_w4a8_act_bytes(in_f: i32, n_tokens: i32) -> usize;
796 pub fn memra_mmq_nvfp4_w4a8(
804 w_nvfp4_blocks: *const core::ffi::c_void,
805 act_f32: *const f32,
806 y: *mut f32,
807 in_f: i32,
808 out_f: i32,
809 n_tokens: i32,
810 act_scratch: *mut core::ffi::c_void,
811 stream: *mut core::ffi::c_void,
812 out_scale: f32,
813 rp: i32,
814 ) -> i32;
815 pub fn memra_mmq_nvfp4_f8f4_act_bytes(in_f: i32, n_tokens: i32) -> usize;
817 pub fn memra_mmq_nvfp4_f8f4(
823 w_nvfp4_blocks: *const core::ffi::c_void,
824 act_f32: *const f32,
825 y: *mut f32,
826 in_f: i32,
827 out_f: i32,
828 n_tokens: i32,
829 act_scratch: *mut core::ffi::c_void,
830 stream: *mut core::ffi::c_void,
831 out_scale: f32,
832 rp: i32,
833 ) -> i32;
834 pub fn memra_mmq_fp8_blk_act_bytes(in_f: i32, n_tokens: i32) -> usize;
837 pub fn memra_mmq_fp8_blk_quantize_act(
838 act_f32: *const f32,
839 act_scratch: *mut core::ffi::c_void,
840 in_f: i32,
841 n_tokens: i32,
842 stream: *mut core::ffi::c_void,
843 ) -> i32;
844 pub fn memra_mmq_fp8_blk_grouped(
845 bank_codes: *const core::ffi::c_void,
846 bank_scales: *const f32,
847 ex_ids: *const i32,
848 ex_off: *const i32,
849 ex_pairs: *const i32,
850 pair_tok: *const i32,
851 act_scratch: *const core::ffi::c_void,
852 y: *mut f32,
853 in_f: i32,
854 out_f: i32,
855 n_expert: i32,
856 n_active: i32,
857 n_pairs: i32,
858 n_tokens: i32,
859 code_stride: usize,
860 scale_stride: usize,
861 stream: *mut core::ffi::c_void,
862 out_scale: f32,
863 ) -> i32;
864 pub fn memra_mmq_fp8_blk_scale_rows(out_f: i32) -> i32;
866 pub fn memra_mmq_fp8_blk_scale_cols(in_f: i32) -> i32;
867 pub fn memra_mmq_fp8_blk(
874 w_e4m3: *const core::ffi::c_void,
875 blk_scales: *const f32,
876 act_f32: *const f32,
877 y: *mut f32,
878 in_f: i32,
879 out_f: i32,
880 n_tokens: i32,
881 act_scratch: *mut core::ffi::c_void,
882 stream: *mut core::ffi::c_void,
883 out_scale: f32,
884 ) -> i32;
885 pub fn memra_fp8_blk_count_nan(
889 w_e4m3: *const core::ffi::c_void,
890 nbytes: usize,
891 out_count: *mut u32,
892 stream: *mut core::ffi::c_void,
893 ) -> i32;
894 pub fn memra_mmq_q45k_act_bytes(in_f: i32, n_tokens: i32) -> usize;
896 pub fn memra_mmq_q4_K(
899 w_q4k_blocks: *const core::ffi::c_void,
900 act_f32: *const f32,
901 y: *mut f32,
902 in_f: i32,
903 out_f: i32,
904 n_tokens: i32,
905 act_scratch: *mut core::ffi::c_void,
906 stream: *mut core::ffi::c_void,
907 ) -> i32;
908 pub fn memra_mmq_q5_K(
910 w_q5k_blocks: *const core::ffi::c_void,
911 act_f32: *const f32,
912 y: *mut f32,
913 in_f: i32,
914 out_f: i32,
915 n_tokens: i32,
916 act_scratch: *mut core::ffi::c_void,
917 stream: *mut core::ffi::c_void,
918 ) -> i32;
919
920 pub fn memra_mmq_q8_0_act_bytes(in_f: i32, n_tokens: i32) -> usize;
922 pub fn memra_mmq_q8_0(
926 w_q8_0_blocks: *const core::ffi::c_void,
927 act_f32: *const f32,
928 y: *mut f32,
929 in_f: i32,
930 out_f: i32,
931 n_tokens: i32,
932 act_scratch: *mut core::ffi::c_void,
933 stream: *mut core::ffi::c_void,
934 ) -> i32;
935
936 pub fn memra_accprobe_act_bytes(in_f: i32, n_tokens: i32) -> usize;
945 pub fn memra_accprobe_gemm_s32(
947 w_q8_0_blocks: *const core::ffi::c_void,
948 act_q: *const core::ffi::c_void,
949 y: *mut f32,
950 in_f: i32,
951 out_f: i32,
952 n_tokens: i32,
953 stream: *mut core::ffi::c_void,
954 ) -> i32;
955 pub fn memra_accprobe_gemm_f32(
957 w_q8_0_blocks: *const core::ffi::c_void,
958 act_q: *const core::ffi::c_void,
959 y: *mut f32,
960 in_f: i32,
961 out_f: i32,
962 n_tokens: i32,
963 stream: *mut core::ffi::c_void,
964 ) -> i32;
965
966 pub fn memra_mmq_q4_0_act_bytes(in_f: i32, n_tokens: i32) -> usize;
968 pub fn memra_mmq_q4_0(
974 w_q4_0: *const core::ffi::c_void,
975 act_f32: *const f32,
976 y: *mut f32,
977 in_f: i32,
978 out_f: i32,
979 n_tokens: i32,
980 act_scratch: *mut core::ffi::c_void,
981 stream: *mut core::ffi::c_void,
982 rp: i32,
983 ) -> i32;
984 pub fn memra_mmq_q4_0_quant_act(
986 act_f32: *const f32,
987 act_scratch: *mut core::ffi::c_void,
988 in_f: i32,
989 n_tokens: i32,
990 stream: *mut core::ffi::c_void,
991 ) -> i32;
992 pub fn memra_mmq_q4_0_gemm(
994 w_q4_0: *const core::ffi::c_void,
995 act_scratch: *const core::ffi::c_void,
996 y: *mut f32,
997 in_f: i32,
998 out_f: i32,
999 n_tokens: i32,
1000 stream: *mut core::ffi::c_void,
1001 rp: i32,
1002 ) -> i32;
1003 pub fn memra_mmq_q4_0_fixup_bytes() -> usize;
1005 pub fn memra_mmq_q4_0_set_clc(force: i32) -> i32;
1010 pub fn memra_mmq_q4_0_gemm_sk(
1013 w_q4_0: *const core::ffi::c_void,
1014 act_scratch: *const core::ffi::c_void,
1015 y: *mut f32,
1016 fixup_scratch: *mut core::ffi::c_void,
1017 in_f: i32,
1018 out_f: i32,
1019 n_tokens: i32,
1020 stream: *mut core::ffi::c_void,
1021 rp: i32,
1022 ) -> i32;
1023
1024 pub fn memra_mmq_iq_experts_act_bytes(in_f: i32, n_tokens: i32) -> usize;
1027 pub fn memra_mmq_iq_quantize_act(
1029 act_f32: *const f32,
1030 act_scratch: *mut core::ffi::c_void,
1031 in_f: i32,
1032 n_tokens: i32,
1033 stream: *mut core::ffi::c_void,
1034 ) -> i32;
1035 pub fn memra_mmq_iq_fused_act_quant(
1039 gate: *const f32,
1040 up: *const f32,
1041 act_scratch: *mut core::ffi::c_void,
1042 in_f: i32,
1043 n_tokens: i32,
1044 act_kind: i32,
1045 stream: *mut core::ffi::c_void,
1046 ) -> i32;
1047 pub fn memra_mmq_iq4xs_dense(
1056 w_blocks: *const core::ffi::c_void,
1057 act_f32: *const f32,
1058 y: *mut f32,
1059 in_f: i32,
1060 out_f: i32,
1061 n_tokens: i32,
1062 row_bytes: i64,
1063 act_scratch: *mut core::ffi::c_void,
1064 stream: *mut core::ffi::c_void,
1065 ) -> i32;
1066 pub fn memra_mmq_iq_experts(
1067 table: *const u64,
1068 proj: i32,
1069 n_expert: i32,
1070 ex_ids: *const i32,
1071 ex_off: *const i32,
1072 ex_pairs: *const i32,
1073 pair_tok: *const i32,
1074 act_scratch: *const core::ffi::c_void,
1075 y: *mut f32,
1076 in_f: i32,
1077 out_f: i32,
1078 n_active: i32,
1079 n_tokens: i32,
1080 qtype: i32,
1081 row_bytes: i64,
1082 stream: *mut core::ffi::c_void,
1083 ) -> i32;
1084
1085 pub fn memra_moe_f16g_dequant(
1087 table: *const u64,
1088 proj: i32,
1089 n_expert: i32,
1090 ex_ids: *const i32,
1091 w_f16: *mut core::ffi::c_void,
1092 in_f: i32,
1093 out_f: i32,
1094 n_active: i32,
1095 qtype: i32,
1096 row_bytes: i64,
1097 stream: *mut core::ffi::c_void,
1098 ) -> i32;
1099 pub fn memra_moe_f16g_gather_act(
1100 x: *const f32,
1101 pair_tok_or_null: *const i32,
1102 act_f16: *mut core::ffi::c_void,
1103 row_scale: *mut f32,
1104 in_f: i32,
1105 n_pairs: i32,
1106 stream: *mut core::ffi::c_void,
1107 ) -> i32;
1108 pub fn memra_moe_f16g_h2f_scaled(
1109 src_f16: *const core::ffi::c_void,
1110 dst: *mut f32,
1111 row_scale: *const f32,
1112 ncols: i32,
1113 nrows: i32,
1114 stream: *mut core::ffi::c_void,
1115 ) -> i32;
1116 pub fn memra_moe_f16g_gemm(
1117 w_f16: *const core::ffi::c_void,
1118 act_f16: *const core::ffi::c_void,
1119 y_f16: *mut core::ffi::c_void,
1120 ex_off_host: *const i32,
1121 n_active: i32,
1122 in_f: i32,
1123 out_f: i32,
1124 stream: *mut core::ffi::c_void,
1125 ) -> i32;
1126 pub fn memra_moe_f16g_h2f(
1127 src_f16: *const core::ffi::c_void,
1128 dst: *mut f32,
1129 n: usize,
1130 stream: *mut core::ffi::c_void,
1131 ) -> i32;
1132 pub fn memra_moe_f16g_gemm_sk(
1141 w_f16: *const core::ffi::c_void,
1142 act_f16: *const core::ffi::c_void,
1143 y_f32: *mut f32,
1144 row_scale: *const f32,
1145 ex_off_dev: *const i32,
1146 ex_off_host: *const i32,
1147 n_active: i32,
1148 max_m: i32,
1149 in_f: i32,
1150 out_f: i32,
1151 shape_sel: i32,
1152 cross: i32,
1153 tail: i32,
1154 stream: *mut core::ffi::c_void,
1155 ) -> i32;
1156 pub fn memra_moe_kq_gemm_sk(
1163 table: *const u64,
1164 proj: i32,
1165 n_expert: i32,
1166 ex_ids: *const i32,
1167 act_f16: *const core::ffi::c_void,
1168 y_f32: *mut f32,
1169 row_scale: *const f32,
1170 ex_off_dev: *const i32,
1171 ex_off_host: *const i32,
1172 n_active: i32,
1173 max_m: i32,
1174 in_f: i32,
1175 out_f: i32,
1176 qtype: i32,
1177 cross: i32,
1178 tail: i32,
1179 row_bytes: i64,
1180 stream: *mut core::ffi::c_void,
1181 ) -> i32;
1182}
1183
1184pub fn mmq_w4a8_enabled() -> bool {
1193 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1194 *ON.get_or_init(|| {
1195 std::env::var("MEMRA_MMQ_W4A8")
1196 .map(|v| v != "0")
1197 .unwrap_or(true)
1198 })
1199}
1200
1201pub fn mmq_residual_k() -> i32 {
1210 std::env::var("MEMRA_MMQ_RESIDUAL_K")
1211 .ok()
1212 .and_then(|v| v.parse::<i32>().ok())
1213 .unwrap_or(0)
1214 .clamp(0, 64)
1215}
1216
1217pub fn mmq_q8_enabled() -> bool {
1223 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1224 *ON.get_or_init(|| {
1228 std::env::var("MEMRA_PP_Q8MMQ")
1229 .map(|v| v != "0")
1230 .unwrap_or(true)
1231 })
1232}
1233
1234pub fn mmq_iq4xs_enabled() -> bool {
1242 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1243 *ON.get_or_init(|| {
1244 std::env::var("MEMRA_PP_IQMMQ")
1245 .map(|v| v != "0")
1246 .unwrap_or(true)
1247 })
1248}
1249
1250pub fn mmq_q4_enabled() -> bool {
1256 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1257 *ON.get_or_init(|| {
1258 std::env::var("MEMRA_PP_Q4MMQ")
1259 .map(|v| v != "0")
1260 .unwrap_or(true)
1261 })
1262}
1263
1264impl Engine {
1265 pub fn mmq_supports(&self, w: &crate::model::GpuTensor) -> bool {
1268 use crate::model::GpuTensor;
1269 if crate::portable_mma_gated() {
1270 return false;
1271 }
1272 let mmq_opt_in = std::env::var("MEMRA_MMQ").is_ok();
1273 match w {
1274 GpuTensor::Quant { qtype, rp, .. } if *qtype == crate::QT_NVFP4 && *rp => {
1282 !cfg!(memra_portable_cuda) && mmq_w4a8_enabled() && w.in_features() % 64 == 0
1283 }
1284 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_NVFP4 => {
1286 !cfg!(memra_portable_cuda)
1287 && (mmq_w4a8_enabled() || mmq_opt_in)
1288 && w.in_features() % 64 == 0
1289 }
1290 GpuTensor::Quant { qtype, .. }
1291 if *qtype == crate::QT_Q4_K || *qtype == crate::QT_Q5_K =>
1292 {
1293 (mmq_w4a8_enabled() || mmq_opt_in) && w.in_features() % 256 == 0
1294 }
1295 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q8_0 => {
1300 mmq_q8_enabled() && w.in_features() % 256 == 0
1301 }
1302 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q4_0 => {
1307 mmq_q4_enabled() && w.in_features() % 256 == 0
1308 }
1309 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_IQ4_XS => {
1315 mmq_iq4xs_enabled() && Self::iq_fast_enabled() && w.in_features() % 256 == 0
1316 }
1317 _ => false,
1318 }
1319 }
1320
1321 pub fn qmatvec_mmq(
1324 &self,
1325 w: &crate::model::GpuTensor,
1326 x: &CudaSlice<f32>,
1327 m: usize,
1328 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1329 use crate::model::GpuTensor;
1330 let (in_f, out_f) = (w.in_features(), w.out_features());
1331 let GpuTensor::Quant {
1332 bytes,
1333 scale,
1334 qtype,
1335 rp,
1336 ..
1337 } = w
1338 else {
1339 return Err("qmatvec_mmq: not a Quant tensor".into());
1340 };
1341 let w4a8_explicit = std::env::var("MEMRA_MMQ_W4A8")
1346 .map(|v| v != "0")
1347 .unwrap_or(false);
1348 let use_w4a8 =
1349 *rp || w4a8_explicit || (mmq_w4a8_enabled() && std::env::var("MEMRA_MMQ").is_err());
1350 match *qtype {
1351 q if q == crate::QT_NVFP4 && use_w4a8 => {
1354 self.qmatvec_mmq_nvfp4_w4a8(bytes, x, m, in_f, out_f, *scale, *rp)
1355 }
1356 q if q == crate::QT_NVFP4 => self.qmatvec_mmq_nvfp4(bytes, x, m, in_f, out_f, *scale),
1357 q if q == crate::QT_Q4_K || q == crate::QT_Q5_K => {
1358 let mut y = self.qmatvec_mmq_q45k_raw(bytes, x, m, in_f, out_f, q)?;
1359 if *scale != 1.0 {
1360 self.scale_inplace(&mut y, *scale, m * out_f)?;
1361 }
1362 Ok(y)
1363 }
1364 q if q == crate::QT_Q8_0 => {
1365 if cfg!(memra_hopper_mma) && out_f % 64 == 0 && crate::wgmma_gemm_enabled() {
1371 if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
1372 let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
1373 let mut y =
1374 self.qmatvec_gemm_q8_0_wgmma_raw(m4, &aq, &ad, m, in_f, out_f)?;
1375 if *scale != 1.0 {
1376 self.scale_inplace(&mut y, *scale, m * out_f)?;
1377 }
1378 return Ok(y);
1379 }
1380 }
1381 let mut y = self.qmatvec_mmq_q8_0_raw(bytes, x, m, in_f, out_f)?;
1382 if *scale != 1.0 {
1383 self.scale_inplace(&mut y, *scale, m * out_f)?;
1384 }
1385 Ok(y)
1386 }
1387 q if q == crate::QT_Q4_0 => {
1388 let mut y = self.qmatvec_mmq_q4_0_raw(bytes, x, m, in_f, out_f, *rp)?;
1389 if *scale != 1.0 {
1390 self.scale_inplace(&mut y, *scale, m * out_f)?;
1391 }
1392 Ok(y)
1393 }
1394 q if q == crate::QT_IQ4_XS => {
1395 let GpuTensor::Quant { row_bytes, .. } = w else {
1396 unreachable!()
1397 };
1398 let mut y = self.qmatvec_mmq_iq4xs_raw(bytes, x, m, in_f, out_f, *row_bytes)?;
1399 if *scale != 1.0 {
1400 self.scale_inplace(&mut y, *scale, m * out_f)?;
1401 }
1402 Ok(y)
1403 }
1404 q => Err(format!("qmatvec_mmq: unsupported qtype {q}").into()),
1405 }
1406 }
1407
1408 pub fn qmatvec_mmq_iq4xs_raw(
1410 &self,
1411 bytes: &CudaSlice<u8>,
1412 x: &CudaSlice<f32>,
1413 m: usize,
1414 in_f: usize,
1415 out_f: usize,
1416 row_bytes: usize,
1417 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1418 assert!(
1419 in_f % 256 == 0,
1420 "MMQ IQ4_XS requires in_f % 256 == 0, got {in_f}"
1421 );
1422 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, m as i32) };
1423 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1424 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1425 {
1426 let stream = self.gpu.stream();
1427 let (w_p, _gw) = bytes.device_ptr(&stream);
1428 let (x_p, _gx) = x.device_ptr(&stream);
1429 let (y_p, _gy) = y.device_ptr_mut(&stream);
1430 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1431 let rc = unsafe {
1432 memra_mmq_iq4xs_dense(
1433 w_p as *const core::ffi::c_void,
1434 x_p as *const f32,
1435 y_p as *mut f32,
1436 in_f as i32,
1437 out_f as i32,
1438 m as i32,
1439 row_bytes as i64,
1440 s_p as *mut core::ffi::c_void,
1441 stream.cu_stream() as *mut core::ffi::c_void,
1442 )
1443 };
1444 if rc != 0 {
1445 return Err(format!("memra_mmq_iq4xs_dense rc={rc}").into());
1446 }
1447 }
1448 Ok(y)
1449 }
1450
1451 pub fn qmatvec_mmq_q45k_raw(
1456 &self,
1457 bytes: &CudaSlice<u8>,
1458 x: &CudaSlice<f32>,
1459 m: usize,
1460 in_f: usize,
1461 out_f: usize,
1462 qtype: i32,
1463 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1464 assert!(
1465 in_f % 256 == 0,
1466 "MMQ Q4_K/Q5_K requires in_f % 256 == 0, got {in_f}"
1467 );
1468 let act_bytes = unsafe { memra_mmq_q45k_act_bytes(in_f as i32, m as i32) };
1469 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1470 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1471 {
1472 let stream = self.gpu.stream();
1473 let (w_p, _gw) = bytes.device_ptr(&stream);
1474 let (x_p, _gx) = x.device_ptr(&stream);
1475 let (y_p, _gy) = y.device_ptr_mut(&stream);
1476 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1477 let launcher = if qtype == crate::QT_Q4_K {
1478 memra_mmq_q4_K
1479 } else {
1480 memra_mmq_q5_K
1481 };
1482 let rc = unsafe {
1483 launcher(
1484 w_p as *const core::ffi::c_void,
1485 x_p as *const f32,
1486 y_p as *mut f32,
1487 in_f as i32,
1488 out_f as i32,
1489 m as i32,
1490 s_p as *mut core::ffi::c_void,
1491 stream.cu_stream() as *mut core::ffi::c_void,
1492 )
1493 };
1494 if rc != 0 {
1495 return Err(format!("memra_mmq_q45k(qtype={qtype}) rc={rc}").into());
1496 }
1497 }
1498 Ok(y)
1499 }
1500
1501 pub fn qmatvec_mmq_q8_0_raw(
1504 &self,
1505 bytes: &CudaSlice<u8>,
1506 x: &CudaSlice<f32>,
1507 m: usize,
1508 in_f: usize,
1509 out_f: usize,
1510 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1511 assert!(
1512 in_f % 32 == 0,
1513 "MMQ Q8_0 requires in_f % 32 == 0, got {in_f}"
1514 );
1515 let act_bytes = unsafe { memra_mmq_q8_0_act_bytes(in_f as i32, m as i32) };
1516 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1517 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1518 {
1519 let stream = self.gpu.stream();
1520 let (w_p, _gw) = bytes.device_ptr(&stream);
1521 let (x_p, _gx) = x.device_ptr(&stream);
1522 let (y_p, _gy) = y.device_ptr_mut(&stream);
1523 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1524 let rc = unsafe {
1525 memra_mmq_q8_0(
1526 w_p as *const core::ffi::c_void,
1527 x_p as *const f32,
1528 y_p as *mut f32,
1529 in_f as i32,
1530 out_f as i32,
1531 m as i32,
1532 s_p as *mut core::ffi::c_void,
1533 stream.cu_stream() as *mut core::ffi::c_void,
1534 )
1535 };
1536 if rc != 0 {
1537 return Err(format!("memra_mmq_q8_0 rc={rc}").into());
1538 }
1539 }
1540 Ok(y)
1541 }
1542
1543 pub fn accprobe_act_bytes(&self, in_f: usize, m: usize) -> usize {
1546 unsafe { memra_accprobe_act_bytes(in_f as i32, m as i32) }
1547 }
1548
1549 pub fn accprobe_gemm(
1556 &self,
1557 w_q8_0: &CudaSlice<u8>,
1558 act_q: &CudaSlice<u8>,
1559 m: usize,
1560 in_f: usize,
1561 out_f: usize,
1562 f32acc: bool,
1563 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1564 assert!(
1565 in_f % 32 == 0,
1566 "accprobe requires in_f % 32 == 0, got {in_f}"
1567 );
1568 assert!(
1569 act_q.len() >= self.accprobe_act_bytes(in_f, m),
1570 "accprobe act_q too small: {} < {}",
1571 act_q.len(),
1572 self.accprobe_act_bytes(in_f, m)
1573 );
1574 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1575 {
1576 let stream = self.gpu.stream();
1577 let (w_p, _gw) = w_q8_0.device_ptr(&stream);
1578 let (a_p, _ga) = act_q.device_ptr(&stream);
1579 let (y_p, _gy) = y.device_ptr_mut(&stream);
1580 let f = if f32acc {
1581 memra_accprobe_gemm_f32
1582 } else {
1583 memra_accprobe_gemm_s32
1584 };
1585 let rc = unsafe {
1586 f(
1587 w_p as *const core::ffi::c_void,
1588 a_p as *const core::ffi::c_void,
1589 y_p as *mut f32,
1590 in_f as i32,
1591 out_f as i32,
1592 m as i32,
1593 stream.cu_stream() as *mut core::ffi::c_void,
1594 )
1595 };
1596 if rc != 0 {
1597 let arm = if f32acc { "f32" } else { "s32" };
1598 return Err(format!("memra_accprobe_gemm_{arm} rc={rc}").into());
1599 }
1600 }
1601 Ok(y)
1602 }
1603
1604 pub fn mmq_act_begin(&self) {
1610 use std::sync::atomic::Ordering;
1611 MMQ_ACT_EPOCH.fetch_add(1, Ordering::Relaxed);
1612 *MMQ_ACT_SLOT.lock().unwrap() = None;
1613 }
1614
1615 pub fn qmatvec_mmq_q4_0_raw(
1619 &self,
1620 bytes: &CudaSlice<u8>,
1621 x: &CudaSlice<f32>,
1622 m: usize,
1623 in_f: usize,
1624 out_f: usize,
1625 rp: bool,
1626 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1627 use std::sync::atomic::Ordering;
1628 assert!(
1629 in_f % 32 == 0,
1630 "MMQ Q4_0 requires in_f % 32 == 0, got {in_f}"
1631 );
1632 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1633 let stream = self.gpu.stream();
1634 let (x_p, _gx) = x.device_ptr(&stream);
1635 let epoch = MMQ_ACT_EPOCH.load(Ordering::Relaxed);
1636 let mut slot = MMQ_ACT_SLOT.lock().unwrap();
1638 let hit = matches!(&*slot,
1639 Some((e, p, mm, inf, _)) if *e == epoch && *p == x_p as u64 && *mm == m && *inf == in_f);
1640 if !hit {
1641 let act_bytes = unsafe { memra_mmq_q4_0_act_bytes(in_f as i32, m as i32) };
1642 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1643 {
1644 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1645 let rc = unsafe {
1646 memra_mmq_q4_0_quant_act(
1647 x_p as *const f32,
1648 s_p as *mut core::ffi::c_void,
1649 in_f as i32,
1650 m as i32,
1651 stream.cu_stream() as *mut core::ffi::c_void,
1652 )
1653 };
1654 if rc != 0 {
1655 return Err(
1656 format!("memra_mmq_q4_0_quant_act(in_f={in_f}, m={m}) rc={rc}").into(),
1657 );
1658 }
1659 }
1660 *slot = Some((epoch, x_p as u64, m, in_f, scratch));
1661 }
1662 let scratch = &slot.as_ref().unwrap().4;
1663 {
1664 let (w_p, _gw) = bytes.device_ptr(&stream);
1665 let (y_p, _gy) = y.device_ptr_mut(&stream);
1666 let (s_p, _gs) = scratch.device_ptr(&stream);
1667 static SK_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1690 let sk = match crate::MMQ_SK_FORCE.load(std::sync::atomic::Ordering::Relaxed) {
1691 0 => false,
1692 1 => true,
1693 _ => *SK_ON.get_or_init(|| {
1694 std::env::var("MEMRA_MMQ_SK")
1695 .map(|v| v != "0")
1696 .unwrap_or(!cfg!(memra_hopper_mma))
1697 }),
1698 };
1699 let rc = if sk {
1700 let mut fx = MMQ_FIXUP_SLOT.lock().unwrap();
1701 if fx.is_none() {
1702 let nb = unsafe { memra_mmq_q4_0_fixup_bytes() };
1703 *fx = Some(self.alloc_uninit::<u8>(nb)?);
1704 }
1705 let (f_p, _gf) = fx.as_mut().unwrap().device_ptr_mut(&stream);
1706 unsafe {
1707 memra_mmq_q4_0_gemm_sk(
1708 w_p as *const core::ffi::c_void,
1709 s_p as *const core::ffi::c_void,
1710 y_p as *mut f32,
1711 f_p as *mut core::ffi::c_void,
1712 in_f as i32,
1713 out_f as i32,
1714 m as i32,
1715 stream.cu_stream() as *mut core::ffi::c_void,
1716 rp as i32,
1717 )
1718 }
1719 } else {
1720 unsafe {
1721 memra_mmq_q4_0_gemm(
1722 w_p as *const core::ffi::c_void,
1723 s_p as *const core::ffi::c_void,
1724 y_p as *mut f32,
1725 in_f as i32,
1726 out_f as i32,
1727 m as i32,
1728 stream.cu_stream() as *mut core::ffi::c_void,
1729 rp as i32,
1730 )
1731 }
1732 };
1733 if rc != 0 {
1734 return Err(format!(
1735 "memra_mmq_q4_0_gemm(rp={rp}, in_f={in_f}, out_f={out_f}, m={m}, wbytes={}) rc={rc}",
1736 bytes.len()
1737 )
1738 .into());
1739 }
1740 }
1741 Ok(y)
1742 }
1743
1744 pub fn qmatvec_mmq_nvfp4(
1750 &self,
1751 bytes: &CudaSlice<u8>,
1752 x: &CudaSlice<f32>,
1753 m: usize,
1754 in_f: usize,
1755 out_f: usize,
1756 scale: f32,
1757 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1758 self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, scale)
1759 }
1760
1761 pub fn qmatvec_mmq_nvfp4_raw(
1763 &self,
1764 bytes: &CudaSlice<u8>,
1765 x: &CudaSlice<f32>,
1766 m: usize,
1767 in_f: usize,
1768 out_f: usize,
1769 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1770 self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, 1.0)
1771 }
1772
1773 pub fn qmatvec_mmq_nvfp4_raw_v1(
1777 &self,
1778 bytes: &CudaSlice<u8>,
1779 x: &CudaSlice<f32>,
1780 m: usize,
1781 in_f: usize,
1782 out_f: usize,
1783 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1784 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, false, 0)
1785 }
1786
1787 pub fn qmatvec_mmq_nvfp4_raw_res(
1789 &self,
1790 bytes: &CudaSlice<u8>,
1791 x: &CudaSlice<f32>,
1792 m: usize,
1793 in_f: usize,
1794 out_f: usize,
1795 residual_k: i32,
1796 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1797 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, true, residual_k)
1798 }
1799
1800 fn qmatvec_mmq_nvfp4_scaled(
1801 &self,
1802 bytes: &CudaSlice<u8>,
1803 x: &CudaSlice<f32>,
1804 m: usize,
1805 in_f: usize,
1806 out_f: usize,
1807 scale: f32,
1808 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1809 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, scale, true, mmq_residual_k())
1810 }
1811
1812 fn qmatvec_mmq_nvfp4_inner(
1813 &self,
1814 bytes: &CudaSlice<u8>,
1815 x: &CudaSlice<f32>,
1816 m: usize,
1817 in_f: usize,
1818 out_f: usize,
1819 scale: f32,
1820 per_token_scale: bool,
1821 residual_k: i32,
1822 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1823 assert!(
1824 in_f % 64 == 0,
1825 "MMQ NVFP4 requires in_f % 64 == 0, got {in_f}"
1826 );
1827 let act_bytes = unsafe { memra_mmq_nvfp4_act_bytes(in_f as i32, m as i32) };
1828 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1829 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1830 {
1831 let stream = self.gpu.stream();
1832 let (w_p, _gw) = bytes.device_ptr(&stream);
1833 let (x_p, _gx) = x.device_ptr(&stream);
1834 let (y_p, _gy) = y.device_ptr_mut(&stream);
1835 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1836 let rc = unsafe {
1837 memra_mmq_nvfp4_ex2(
1838 w_p as *const core::ffi::c_void,
1839 x_p as *const f32,
1840 y_p as *mut f32,
1841 in_f as i32,
1842 out_f as i32,
1843 m as i32,
1844 s_p as *mut core::ffi::c_void,
1845 stream.cu_stream() as *mut core::ffi::c_void,
1846 scale,
1847 per_token_scale as i32,
1848 residual_k,
1849 )
1850 };
1851 if rc != 0 {
1852 return Err(format!("memra_mmq_nvfp4_ex2 rc={rc}").into());
1853 }
1854 }
1855 Ok(y)
1856 }
1857
1858 pub fn qmatvec_mmq_nvfp4_w4a8(
1863 &self,
1864 bytes: &CudaSlice<u8>,
1865 x: &CudaSlice<f32>,
1866 m: usize,
1867 in_f: usize,
1868 out_f: usize,
1869 scale: f32,
1870 rp: bool,
1871 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1872 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, scale, rp)
1873 }
1874
1875 pub fn qmatvec_mmq_nvfp4_w4a8_raw(
1877 &self,
1878 bytes: &CudaSlice<u8>,
1879 x: &CudaSlice<f32>,
1880 m: usize,
1881 in_f: usize,
1882 out_f: usize,
1883 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1884 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, false)
1885 }
1886
1887 pub fn qmatvec_mmq_nvfp4_w4a8_raw_rp(
1890 &self,
1891 bytes: &CudaSlice<u8>,
1892 x: &CudaSlice<f32>,
1893 m: usize,
1894 in_f: usize,
1895 out_f: usize,
1896 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1897 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, true)
1898 }
1899
1900 fn qmatvec_mmq_nvfp4_w4a8_scaled(
1901 &self,
1902 bytes: &CudaSlice<u8>,
1903 x: &CudaSlice<f32>,
1904 m: usize,
1905 in_f: usize,
1906 out_f: usize,
1907 scale: f32,
1908 rp: bool,
1909 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1910 assert!(
1911 in_f % 64 == 0,
1912 "MMQ NVFP4 W4A8 requires in_f % 64 == 0, got {in_f}"
1913 );
1914 let act_bytes = unsafe { memra_mmq_nvfp4_w4a8_act_bytes(in_f as i32, m as i32) };
1915 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1916 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1917 {
1918 let stream = self.gpu.stream();
1919 let (w_p, _gw) = bytes.device_ptr(&stream);
1920 let (x_p, _gx) = x.device_ptr(&stream);
1921 let (y_p, _gy) = y.device_ptr_mut(&stream);
1922 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1923 static F8F4: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1926 let f8f4 = *F8F4.get_or_init(|| std::env::var("MEMRA_MMQ_F8F4").as_deref() == Ok("1"));
1927 let rc = unsafe {
1928 if f8f4 {
1929 memra_mmq_nvfp4_f8f4(
1930 w_p as *const core::ffi::c_void,
1931 x_p as *const f32,
1932 y_p as *mut f32,
1933 in_f as i32,
1934 out_f as i32,
1935 m as i32,
1936 s_p as *mut core::ffi::c_void,
1937 stream.cu_stream() as *mut core::ffi::c_void,
1938 scale,
1939 rp as i32,
1940 )
1941 } else {
1942 memra_mmq_nvfp4_w4a8(
1943 w_p as *const core::ffi::c_void,
1944 x_p as *const f32,
1945 y_p as *mut f32,
1946 in_f as i32,
1947 out_f as i32,
1948 m as i32,
1949 s_p as *mut core::ffi::c_void,
1950 stream.cu_stream() as *mut core::ffi::c_void,
1951 scale,
1952 rp as i32,
1953 )
1954 }
1955 };
1956 if rc != 0 {
1957 return Err(format!("memra_mmq_nvfp4_w4a8(f8f4={f8f4}) rc={rc}").into());
1958 }
1959 }
1960 Ok(y)
1961 }
1962
1963 pub fn qmatvec_mmq_fp8_blk(
1967 &self,
1968 w_e4m3: &CudaSlice<u8>,
1969 blk_scales: &CudaSlice<f32>,
1970 x: &CudaSlice<f32>,
1971 m: usize,
1972 in_f: usize,
1973 out_f: usize,
1974 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1975 self.qmatvec_mmq_fp8_blk_scaled(w_e4m3, blk_scales, x, m, in_f, out_f, 1.0)
1976 }
1977
1978 pub fn qmatvec_mmq_fp8_blk_scaled(
1979 &self,
1980 w_e4m3: &CudaSlice<u8>,
1981 blk_scales: &CudaSlice<f32>,
1982 x: &CudaSlice<f32>,
1983 m: usize,
1984 in_f: usize,
1985 out_f: usize,
1986 scale: f32,
1987 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1988 assert!(
1989 in_f % 16 == 0,
1990 "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
1991 );
1992 let want_scales = ((out_f + 127) / 128) * ((in_f + 127) / 128);
1993 assert!(
1994 blk_scales.len() >= want_scales,
1995 "blk_scales too small: {} < {want_scales}",
1996 blk_scales.len()
1997 );
1998 assert!(
1999 w_e4m3.len() >= out_f * in_f,
2000 "e4m3 plane too small: {} < {}",
2001 w_e4m3.len(),
2002 out_f * in_f
2003 );
2004 let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2005 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2006 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2007 {
2008 let stream = self.gpu.stream();
2009 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2010 let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2011 let (x_p, _gx) = x.device_ptr(&stream);
2012 let (y_p, _gy) = y.device_ptr_mut(&stream);
2013 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2014 let rc = unsafe {
2015 memra_mmq_fp8_blk(
2016 w_p as *const core::ffi::c_void,
2017 sc_p as *const f32,
2018 x_p as *const f32,
2019 y_p as *mut f32,
2020 in_f as i32,
2021 out_f as i32,
2022 m as i32,
2023 s_p as *mut core::ffi::c_void,
2024 stream.cu_stream() as *mut core::ffi::c_void,
2025 scale,
2026 )
2027 };
2028 if rc != 0 {
2029 return Err(format!("memra_mmq_fp8_blk rc={rc}").into());
2030 }
2031 }
2032 Ok(y)
2033 }
2034
2035 pub fn qmatvec_mmq_fp8_blk_view(
2040 &self,
2041 w_e4m3: &CudaView<'_, u8>,
2042 blk_scales: &CudaView<'_, f32>,
2043 x: &CudaView<'_, f32>,
2044 m: usize,
2045 in_f: usize,
2046 out_f: usize,
2047 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2048 assert!(
2049 in_f % 16 == 0,
2050 "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
2051 );
2052 let want_scales = out_f.div_ceil(128) * in_f.div_ceil(128);
2053 assert!(
2054 blk_scales.len() >= want_scales,
2055 "blk_scales view too small: {} < {want_scales}",
2056 blk_scales.len()
2057 );
2058 assert!(
2059 w_e4m3.len() >= out_f * in_f,
2060 "e4m3 view too small: {} < {}",
2061 w_e4m3.len(),
2062 out_f * in_f
2063 );
2064 assert!(
2065 x.len() >= m * in_f,
2066 "activation view too small: {} < {}",
2067 x.len(),
2068 m * in_f
2069 );
2070
2071 let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2072 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2073 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2074 {
2075 let stream = self.gpu.stream();
2076 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2077 let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2078 let (x_p, _gx) = x.device_ptr(&stream);
2079 let (y_p, _gy) = y.device_ptr_mut(&stream);
2080 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2081 let rc = unsafe {
2082 memra_mmq_fp8_blk(
2083 w_p as *const core::ffi::c_void,
2084 sc_p as *const f32,
2085 x_p as *const f32,
2086 y_p as *mut f32,
2087 in_f as i32,
2088 out_f as i32,
2089 m as i32,
2090 s_p as *mut core::ffi::c_void,
2091 stream.cu_stream() as *mut core::ffi::c_void,
2092 1.0,
2093 )
2094 };
2095 if rc != 0 {
2096 return Err(format!("memra_mmq_fp8_blk(view) rc={rc}").into());
2097 }
2098 }
2099 Ok(y)
2100 }
2101
2102 pub fn fp8_blk_nan_count(
2106 &self,
2107 w_e4m3: &CudaSlice<u8>,
2108 ) -> Result<u32, Box<dyn std::error::Error>> {
2109 let mut cnt = self.htod_u32_v(&[0u32])?;
2110 let n = w_e4m3.len();
2111 {
2112 let stream = self.gpu.stream();
2113 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2114 let (c_p, _gc) = cnt.device_ptr_mut(&stream);
2115 let rc = unsafe {
2116 memra_fp8_blk_count_nan(
2117 w_p as *const core::ffi::c_void,
2118 n,
2119 c_p as *mut u32,
2120 stream.cu_stream() as *mut core::ffi::c_void,
2121 )
2122 };
2123 if rc != 0 {
2124 return Err(format!("memra_fp8_blk_count_nan rc={rc}").into());
2125 }
2126 }
2127 Ok(self.dtoh_u32(&cnt)?[0])
2128 }
2129
2130 pub fn mmq_iq_quantize_act(
2133 &self,
2134 x: &CudaSlice<f32>,
2135 in_f: usize,
2136 n_tokens: usize,
2137 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2138 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2139 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2140 {
2141 let stream = self.gpu.stream();
2142 let (x_p, _gx) = x.device_ptr(&stream);
2143 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2144 let rc = unsafe {
2145 memra_mmq_iq_quantize_act(
2146 x_p as *const f32,
2147 s_p as *mut core::ffi::c_void,
2148 in_f as i32,
2149 n_tokens as i32,
2150 stream.cu_stream() as *mut core::ffi::c_void,
2151 )
2152 };
2153 if rc != 0 {
2154 return Err(format!("memra_mmq_iq_quantize_act rc={rc}").into());
2155 }
2156 }
2157 Ok(scratch)
2158 }
2159
2160 pub fn mmq_iq_fused_act_quant(
2166 &self,
2167 gate: &CudaSlice<f32>,
2168 up: &CudaSlice<f32>,
2169 in_f: usize,
2170 n_tokens: usize,
2171 act_kind: i32,
2172 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2173 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2174 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2175 {
2176 let stream = self.gpu.stream();
2177 let (g_p, _gg) = gate.device_ptr(&stream);
2178 let (u_p, _gu) = up.device_ptr(&stream);
2179 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2180 let rc = unsafe {
2181 memra_mmq_iq_fused_act_quant(
2182 g_p as *const f32,
2183 u_p as *const f32,
2184 s_p as *mut core::ffi::c_void,
2185 in_f as i32,
2186 n_tokens as i32,
2187 act_kind,
2188 stream.cu_stream() as *mut core::ffi::c_void,
2189 )
2190 };
2191 if rc != 0 {
2192 return Err(format!("memra_mmq_iq_fused_act_quant rc={rc}").into());
2193 }
2194 }
2195 Ok(scratch)
2196 }
2197
2198 #[allow(clippy::too_many_arguments)]
2202 pub fn mmq_iq_experts(
2203 &self,
2204 table: &CudaSlice<u64>,
2205 proj: i32,
2206 n_expert: usize,
2207 ex_ids: &CudaSlice<i32>,
2208 ex_off: &CudaSlice<i32>,
2209 ex_pairs: &CudaSlice<i32>,
2210 pair_tok: &CudaSlice<i32>,
2211 act_scratch: &CudaSlice<u8>,
2212 in_f: usize,
2213 out_f: usize,
2214 n_active: usize,
2215 n_pairs: usize,
2216 n_tokens: usize,
2217 qtype: i32,
2218 row_bytes: usize,
2219 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2220 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2221 {
2222 let stream = self.gpu.stream();
2223 let (tab_p, _g0) = table.device_ptr(&stream);
2224 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2225 let (eo_p, _g2) = ex_off.device_ptr(&stream);
2226 let (ep_p, _g3) = ex_pairs.device_ptr(&stream);
2227 let (pt_p, _g4) = pair_tok.device_ptr(&stream);
2228 let (as_p, _g5) = act_scratch.device_ptr(&stream);
2229 let (y_p, _g6) = y.device_ptr_mut(&stream);
2230 let rc = unsafe {
2231 memra_mmq_iq_experts(
2232 tab_p as *const u64,
2233 proj,
2234 n_expert as i32,
2235 ei_p as *const i32,
2236 eo_p as *const i32,
2237 ep_p as *const i32,
2238 pt_p as *const i32,
2239 as_p as *const core::ffi::c_void,
2240 y_p as *mut f32,
2241 in_f as i32,
2242 out_f as i32,
2243 n_active as i32,
2244 n_tokens as i32,
2245 qtype,
2246 row_bytes as i64,
2247 stream.cu_stream() as *mut core::ffi::c_void,
2248 )
2249 };
2250 if rc != 0 {
2251 return Err(format!("memra_mmq_iq_experts rc={rc}").into());
2252 }
2253 }
2254 Ok(y)
2255 }
2256
2257 pub fn moe_f16g_act(
2262 &self,
2263 x: &CudaSlice<f32>,
2264 pair_tok: Option<&CudaSlice<i32>>,
2265 in_f: usize,
2266 n_pairs: usize,
2267 ) -> Result<(CudaSlice<u8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2268 let mut act = self.alloc_uninit::<u8>(n_pairs * in_f * 2)?;
2269 let mut scales = self.alloc_uninit::<f32>(n_pairs)?;
2270 {
2271 let stream = self.gpu.stream();
2272 let (x_p, _gx) = x.device_ptr(&stream);
2273 let pt_p = match pair_tok {
2274 Some(pt) => {
2275 let (p, _g) = pt.device_ptr(&stream);
2276 p as *const i32
2277 }
2278 None => std::ptr::null(),
2279 };
2280 let (a_p, _ga) = act.device_ptr_mut(&stream);
2281 let (s_p, _gs) = scales.device_ptr_mut(&stream);
2282 let rc = unsafe {
2283 memra_moe_f16g_gather_act(
2284 x_p as *const f32,
2285 pt_p,
2286 a_p as *mut core::ffi::c_void,
2287 s_p as *mut f32,
2288 in_f as i32,
2289 n_pairs as i32,
2290 stream.cu_stream() as *mut core::ffi::c_void,
2291 )
2292 };
2293 if rc != 0 {
2294 return Err(format!("memra_moe_f16g_gather_act rc={rc}").into());
2295 }
2296 }
2297 Ok((act, scales))
2298 }
2299
2300 #[allow(clippy::too_many_arguments)]
2310 pub fn moe_f16_grouped(
2311 &self,
2312 table: &CudaSlice<u64>,
2313 proj: i32,
2314 n_expert: usize,
2315 ex_ids: &CudaSlice<i32>,
2316 ex_off_host: &[i32],
2317 ex_off_dev: &CudaSlice<i32>,
2318 act_f16: &CudaSlice<u8>,
2319 act_scale: &CudaSlice<f32>,
2320 in_f: usize,
2321 out_f: usize,
2322 n_active: usize,
2323 n_pairs: usize,
2324 qtype: i32,
2325 row_bytes: usize,
2326 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2327 let sk = crate::moe_f16g_mode() >= 2 && in_f % 32 == 0;
2328 let (shape_sel, cross) = crate::moe_f16g_sk_params();
2336 if sk
2337 && shape_sel >= 0
2338 && crate::moe_f16g_direct_on(qtype)
2339 && (qtype == crate::QT_Q4_K
2340 || qtype == crate::QT_Q6_K
2341 || qtype == crate::QT_IQ4_XS
2342 || qtype == crate::QT_IQ3_S
2343 || qtype == crate::QT_NVFP4)
2344 && in_f % (if qtype == crate::QT_NVFP4 { 64 } else { 256 }) == 0
2347 && n_active <= 512
2348 && n_active > 0
2349 {
2350 let max_m = ex_off_host
2351 .windows(2)
2352 .map(|w| w[1] - w[0])
2353 .max()
2354 .unwrap_or(0);
2355 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2356 {
2357 let stream = self.gpu.stream();
2358 let (tab_p, _g0) = table.device_ptr(&stream);
2359 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2360 let (a_p, _g2) = act_f16.device_ptr(&stream);
2361 let (s_p, _g3) = act_scale.device_ptr(&stream);
2362 let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2363 let (y_p, _g5) = y.device_ptr_mut(&stream);
2364 let rc = unsafe {
2365 memra_moe_kq_gemm_sk(
2366 tab_p as *const u64,
2367 proj,
2368 n_expert as i32,
2369 ei_p as *const i32,
2370 a_p as *const core::ffi::c_void,
2371 y_p as *mut f32,
2372 s_p as *const f32,
2373 off_p as *const i32,
2374 ex_off_host.as_ptr(),
2375 n_active as i32,
2376 max_m,
2377 in_f as i32,
2378 out_f as i32,
2379 qtype,
2380 cross,
2381 crate::moe_f16g_tail_on() as i32,
2382 row_bytes as i64,
2383 stream.cu_stream() as *mut core::ffi::c_void,
2384 )
2385 };
2386 if rc != 0 {
2387 return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2388 }
2389 }
2390 return Ok(y);
2391 }
2392 if !sk {
2396 static WARM: std::sync::Once = std::sync::Once::new();
2397 let mut warm_err = None;
2398 WARM.call_once(|| {
2399 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2400 let w = self.alloc_uninit::<u8>(2 * 32 * 64 * 2)?;
2401 let a = self.alloc_uninit::<u8>(4 * 64 * 2)?;
2402 let mut yw = self.alloc_uninit::<u8>(4 * 32 * 2)?;
2403 let off = [0i32, 2, 4];
2404 let stream = self.gpu.stream();
2405 let (w_p, _a1) = w.device_ptr(&stream);
2406 let (a_p, _a2) = a.device_ptr(&stream);
2407 let (y_p, _a3) = yw.device_ptr_mut(&stream);
2408 let rc = unsafe {
2409 memra_moe_f16g_gemm(
2410 w_p as *const core::ffi::c_void,
2411 a_p as *const core::ffi::c_void,
2412 y_p as *mut core::ffi::c_void,
2413 off.as_ptr(),
2414 2,
2415 64,
2416 32,
2417 stream.cu_stream() as *mut core::ffi::c_void,
2418 )
2419 };
2420 if rc != 0 {
2421 return Err(format!("f16g warmup rc={rc}").into());
2422 }
2423 self.gpu.stream().synchronize()?;
2424 Ok(())
2425 })();
2426 if let Err(e) = r {
2427 warm_err = Some(e.to_string());
2428 }
2429 });
2430 if let Some(we) = warm_err {
2431 return Err(we.into());
2432 }
2433 }
2434 let w_bytes = n_active * out_f * in_f * 2;
2435 let mut w_f16 = self.alloc_uninit::<u8>(w_bytes)?;
2436 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2437 {
2438 let stream = self.gpu.stream();
2439 let (tab_p, _g0) = table.device_ptr(&stream);
2440 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2441 let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2442 let rc = unsafe {
2443 memra_moe_f16g_dequant(
2444 tab_p as *const u64,
2445 proj,
2446 n_expert as i32,
2447 ei_p as *const i32,
2448 w_p as *mut core::ffi::c_void,
2449 in_f as i32,
2450 out_f as i32,
2451 n_active as i32,
2452 qtype,
2453 row_bytes as i64,
2454 stream.cu_stream() as *mut core::ffi::c_void,
2455 )
2456 };
2457 if rc != 0 {
2458 return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2459 }
2460 let (a_p, _g3) = act_f16.device_ptr(&stream);
2461 let (s_p, _g6) = act_scale.device_ptr(&stream);
2462 let (y_p, _g5) = y.device_ptr_mut(&stream);
2463 if sk {
2464 let max_m = ex_off_host
2465 .windows(2)
2466 .map(|w| w[1] - w[0])
2467 .max()
2468 .unwrap_or(0);
2469 let (off_p, _g7) = ex_off_dev.device_ptr(&stream);
2470 let (shape_sel, cross) = crate::moe_f16g_sk_params();
2471 let rc = unsafe {
2472 memra_moe_f16g_gemm_sk(
2473 w_p as *const core::ffi::c_void,
2474 a_p as *const core::ffi::c_void,
2475 y_p as *mut f32,
2476 s_p as *const f32,
2477 off_p as *const i32,
2478 ex_off_host.as_ptr(),
2479 n_active as i32,
2480 max_m,
2481 in_f as i32,
2482 out_f as i32,
2483 shape_sel,
2484 cross,
2485 crate::moe_f16g_tail_on() as i32,
2486 stream.cu_stream() as *mut core::ffi::c_void,
2487 )
2488 };
2489 if rc != 0 {
2490 return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2491 }
2492 } else {
2493 let mut y16 = self.alloc_uninit::<u8>(n_pairs * out_f * 2)?;
2494 let (y16_p, _g4) = y16.device_ptr_mut(&stream);
2495 let rc = unsafe {
2496 memra_moe_f16g_gemm(
2497 w_p as *const core::ffi::c_void,
2498 a_p as *const core::ffi::c_void,
2499 y16_p as *mut core::ffi::c_void,
2500 ex_off_host.as_ptr(),
2501 n_active as i32,
2502 in_f as i32,
2503 out_f as i32,
2504 stream.cu_stream() as *mut core::ffi::c_void,
2505 )
2506 };
2507 if rc != 0 {
2508 return Err(format!("memra_moe_f16g_gemm rc={rc}").into());
2509 }
2510 let rc = unsafe {
2511 memra_moe_f16g_h2f_scaled(
2512 y16_p as *const core::ffi::c_void,
2513 y_p as *mut f32,
2514 s_p as *const f32,
2515 out_f as i32,
2516 n_pairs 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_h2f_scaled rc={rc}").into());
2522 }
2523 }
2524 }
2525 if !sk {
2530 self.gpu.stream().synchronize()?;
2531 }
2532 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
2533 let wn = n_active * out_f * in_f;
2535 let an = n_pairs * in_f;
2536 let mut wf = self.alloc_uninit::<f32>(wn)?;
2537 let mut af = self.alloc_uninit::<f32>(an)?;
2538 {
2539 let stream = self.gpu.stream();
2540 let (w_p, _a) = w_f16.device_ptr(&stream);
2541 let (a_p, _b) = act_f16.device_ptr(&stream);
2542 let (wf_p, _c) = wf.device_ptr_mut(&stream);
2543 let (af_p, _d) = af.device_ptr_mut(&stream);
2544 unsafe {
2545 memra_moe_f16g_h2f(
2546 w_p as *const core::ffi::c_void,
2547 wf_p as *mut f32,
2548 wn,
2549 stream.cu_stream() as *mut core::ffi::c_void,
2550 );
2551 memra_moe_f16g_h2f(
2552 a_p as *const core::ffi::c_void,
2553 af_p as *mut f32,
2554 an,
2555 stream.cu_stream() as *mut core::ffi::c_void,
2556 );
2557 }
2558 }
2559 let (wh, ah, yh) = (self.dtoh(&wf)?, self.dtoh(&af)?, self.dtoh(&y)?);
2560 let scan = |v: &[f32]| -> (usize, f32) {
2561 let bad = v.iter().filter(|x| !x.is_finite()).count();
2562 let mx = v
2563 .iter()
2564 .filter(|x| x.is_finite())
2565 .fold(0.0f32, |m, x| m.max(x.abs()));
2566 (bad, mx)
2567 };
2568 let (wb, wm) = scan(&wh);
2569 let (ab, am) = scan(&ah);
2570 let (yb, ym) = scan(&yh);
2571 eprintln!(
2572 "[f16g-debug] proj={proj} w: bad={wb} max={wm:.3e} | act: bad={ab} \
2573 max={am:.3e} | y: bad={yb} max={ym:.3e} (na={n_active} np={n_pairs} \
2574 in={in_f} out={out_f})"
2575 );
2576 }
2577 Ok(y)
2578 }
2579
2580 #[allow(clippy::too_many_arguments)]
2587 pub fn moe_f16g_gemm_sk_raw(
2588 &self,
2589 w_f16: &CudaSlice<u8>,
2590 act_f16: &CudaSlice<u8>,
2591 row_scale: &CudaSlice<f32>,
2592 ex_off_host: &[i32],
2593 ex_off_dev: &CudaSlice<i32>,
2594 in_f: usize,
2595 out_f: usize,
2596 n_pairs: usize,
2597 shape_sel: i32,
2598 cross: i32,
2599 tail: i32,
2600 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2601 let n_active = ex_off_host.len() - 1;
2602 let max_m = ex_off_host
2603 .windows(2)
2604 .map(|w| w[1] - w[0])
2605 .max()
2606 .unwrap_or(0);
2607 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2608 {
2609 let stream = self.gpu.stream();
2610 let (w_p, _g0) = w_f16.device_ptr(&stream);
2611 let (a_p, _g1) = act_f16.device_ptr(&stream);
2612 let (s_p, _g2) = row_scale.device_ptr(&stream);
2613 let (off_p, _g3) = ex_off_dev.device_ptr(&stream);
2614 let (y_p, _g4) = y.device_ptr_mut(&stream);
2615 let rc = unsafe {
2616 memra_moe_f16g_gemm_sk(
2617 w_p as *const core::ffi::c_void,
2618 a_p as *const core::ffi::c_void,
2619 y_p as *mut f32,
2620 s_p as *const f32,
2621 off_p as *const i32,
2622 ex_off_host.as_ptr(),
2623 n_active as i32,
2624 max_m,
2625 in_f as i32,
2626 out_f as i32,
2627 shape_sel,
2628 cross,
2629 tail,
2630 stream.cu_stream() as *mut core::ffi::c_void,
2631 )
2632 };
2633 if rc != 0 {
2634 return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2635 }
2636 }
2637 Ok(y)
2638 }
2639
2640 #[allow(clippy::too_many_arguments)]
2645 pub fn moe_kq_gemm_sk_raw(
2646 &self,
2647 table: &CudaSlice<u64>,
2648 proj: i32,
2649 n_expert: usize,
2650 ex_ids: &CudaSlice<i32>,
2651 act_f16: &CudaSlice<u8>,
2652 row_scale: &CudaSlice<f32>,
2653 ex_off_host: &[i32],
2654 ex_off_dev: &CudaSlice<i32>,
2655 in_f: usize,
2656 out_f: usize,
2657 n_pairs: usize,
2658 qtype: i32,
2659 row_bytes: usize,
2660 cross: i32,
2661 tail: i32,
2662 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2663 let n_active = ex_off_host.len() - 1;
2664 let max_m = ex_off_host
2665 .windows(2)
2666 .map(|w| w[1] - w[0])
2667 .max()
2668 .unwrap_or(0);
2669 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2670 {
2671 let stream = self.gpu.stream();
2672 let (tab_p, _g0) = table.device_ptr(&stream);
2673 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2674 let (a_p, _g2) = act_f16.device_ptr(&stream);
2675 let (s_p, _g3) = row_scale.device_ptr(&stream);
2676 let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2677 let (y_p, _g5) = y.device_ptr_mut(&stream);
2678 let rc = unsafe {
2679 memra_moe_kq_gemm_sk(
2680 tab_p as *const u64,
2681 proj,
2682 n_expert as i32,
2683 ei_p as *const i32,
2684 a_p as *const core::ffi::c_void,
2685 y_p as *mut f32,
2686 s_p as *const f32,
2687 off_p as *const i32,
2688 ex_off_host.as_ptr(),
2689 n_active as i32,
2690 max_m,
2691 in_f as i32,
2692 out_f as i32,
2693 qtype,
2694 cross,
2695 tail,
2696 row_bytes as i64,
2697 stream.cu_stream() as *mut core::ffi::c_void,
2698 )
2699 };
2700 if rc != 0 {
2701 return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2702 }
2703 }
2704 Ok(y)
2705 }
2706
2707 pub fn moe_f16g_dequant_raw(
2711 &self,
2712 table: &CudaSlice<u64>,
2713 proj: i32,
2714 n_expert: usize,
2715 ex_ids: &CudaSlice<i32>,
2716 in_f: usize,
2717 out_f: usize,
2718 n_active: usize,
2719 qtype: i32,
2720 row_bytes: usize,
2721 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2722 let mut w_f16 = self.alloc_uninit::<u8>(n_active * out_f * in_f * 2)?;
2723 {
2724 let stream = self.gpu.stream();
2725 let (tab_p, _g0) = table.device_ptr(&stream);
2726 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2727 let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2728 let rc = unsafe {
2729 memra_moe_f16g_dequant(
2730 tab_p as *const u64,
2731 proj,
2732 n_expert as i32,
2733 ei_p as *const i32,
2734 w_p as *mut core::ffi::c_void,
2735 in_f as i32,
2736 out_f as i32,
2737 n_active as i32,
2738 qtype,
2739 row_bytes as i64,
2740 stream.cu_stream() as *mut core::ffi::c_void,
2741 )
2742 };
2743 if rc != 0 {
2744 return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2745 }
2746 }
2747 Ok(w_f16)
2748 }
2749}