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_gemm_sk(
1009 w_q4_0: *const core::ffi::c_void,
1010 act_scratch: *const core::ffi::c_void,
1011 y: *mut f32,
1012 fixup_scratch: *mut core::ffi::c_void,
1013 in_f: i32,
1014 out_f: i32,
1015 n_tokens: i32,
1016 stream: *mut core::ffi::c_void,
1017 rp: i32,
1018 ) -> i32;
1019
1020 pub fn memra_mmq_iq_experts_act_bytes(in_f: i32, n_tokens: i32) -> usize;
1023 pub fn memra_mmq_iq_quantize_act(
1025 act_f32: *const f32,
1026 act_scratch: *mut core::ffi::c_void,
1027 in_f: i32,
1028 n_tokens: i32,
1029 stream: *mut core::ffi::c_void,
1030 ) -> i32;
1031 pub fn memra_mmq_iq_fused_act_quant(
1035 gate: *const f32,
1036 up: *const f32,
1037 act_scratch: *mut core::ffi::c_void,
1038 in_f: i32,
1039 n_tokens: i32,
1040 act_kind: i32,
1041 stream: *mut core::ffi::c_void,
1042 ) -> i32;
1043 pub fn memra_mmq_iq4xs_dense(
1052 w_blocks: *const core::ffi::c_void,
1053 act_f32: *const f32,
1054 y: *mut f32,
1055 in_f: i32,
1056 out_f: i32,
1057 n_tokens: i32,
1058 row_bytes: i64,
1059 act_scratch: *mut core::ffi::c_void,
1060 stream: *mut core::ffi::c_void,
1061 ) -> i32;
1062 pub fn memra_mmq_iq_experts(
1063 table: *const u64,
1064 proj: i32,
1065 n_expert: i32,
1066 ex_ids: *const i32,
1067 ex_off: *const i32,
1068 ex_pairs: *const i32,
1069 pair_tok: *const i32,
1070 act_scratch: *const core::ffi::c_void,
1071 y: *mut f32,
1072 in_f: i32,
1073 out_f: i32,
1074 n_active: i32,
1075 n_tokens: i32,
1076 qtype: i32,
1077 row_bytes: i64,
1078 stream: *mut core::ffi::c_void,
1079 ) -> i32;
1080
1081 pub fn memra_moe_f16g_dequant(
1083 table: *const u64,
1084 proj: i32,
1085 n_expert: i32,
1086 ex_ids: *const i32,
1087 w_f16: *mut core::ffi::c_void,
1088 in_f: i32,
1089 out_f: i32,
1090 n_active: i32,
1091 qtype: i32,
1092 row_bytes: i64,
1093 stream: *mut core::ffi::c_void,
1094 ) -> i32;
1095 pub fn memra_moe_f16g_gather_act(
1096 x: *const f32,
1097 pair_tok_or_null: *const i32,
1098 act_f16: *mut core::ffi::c_void,
1099 row_scale: *mut f32,
1100 in_f: i32,
1101 n_pairs: i32,
1102 stream: *mut core::ffi::c_void,
1103 ) -> i32;
1104 pub fn memra_moe_f16g_h2f_scaled(
1105 src_f16: *const core::ffi::c_void,
1106 dst: *mut f32,
1107 row_scale: *const f32,
1108 ncols: i32,
1109 nrows: i32,
1110 stream: *mut core::ffi::c_void,
1111 ) -> i32;
1112 pub fn memra_moe_f16g_gemm(
1113 w_f16: *const core::ffi::c_void,
1114 act_f16: *const core::ffi::c_void,
1115 y_f16: *mut core::ffi::c_void,
1116 ex_off_host: *const i32,
1117 n_active: i32,
1118 in_f: i32,
1119 out_f: i32,
1120 stream: *mut core::ffi::c_void,
1121 ) -> i32;
1122 pub fn memra_moe_f16g_h2f(
1123 src_f16: *const core::ffi::c_void,
1124 dst: *mut f32,
1125 n: usize,
1126 stream: *mut core::ffi::c_void,
1127 ) -> i32;
1128 pub fn memra_moe_f16g_gemm_sk(
1137 w_f16: *const core::ffi::c_void,
1138 act_f16: *const core::ffi::c_void,
1139 y_f32: *mut f32,
1140 row_scale: *const f32,
1141 ex_off_dev: *const i32,
1142 ex_off_host: *const i32,
1143 n_active: i32,
1144 max_m: i32,
1145 in_f: i32,
1146 out_f: i32,
1147 shape_sel: i32,
1148 cross: i32,
1149 tail: i32,
1150 stream: *mut core::ffi::c_void,
1151 ) -> i32;
1152 pub fn memra_moe_kq_gemm_sk(
1159 table: *const u64,
1160 proj: i32,
1161 n_expert: i32,
1162 ex_ids: *const i32,
1163 act_f16: *const core::ffi::c_void,
1164 y_f32: *mut f32,
1165 row_scale: *const f32,
1166 ex_off_dev: *const i32,
1167 ex_off_host: *const i32,
1168 n_active: i32,
1169 max_m: i32,
1170 in_f: i32,
1171 out_f: i32,
1172 qtype: i32,
1173 cross: i32,
1174 tail: i32,
1175 row_bytes: i64,
1176 stream: *mut core::ffi::c_void,
1177 ) -> i32;
1178}
1179
1180pub fn mmq_w4a8_enabled() -> bool {
1189 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1190 *ON.get_or_init(|| {
1191 std::env::var("MEMRA_MMQ_W4A8")
1192 .map(|v| v != "0")
1193 .unwrap_or(true)
1194 })
1195}
1196
1197pub fn mmq_residual_k() -> i32 {
1206 std::env::var("MEMRA_MMQ_RESIDUAL_K")
1207 .ok()
1208 .and_then(|v| v.parse::<i32>().ok())
1209 .unwrap_or(0)
1210 .clamp(0, 64)
1211}
1212
1213pub fn mmq_q8_enabled() -> bool {
1219 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1220 *ON.get_or_init(|| {
1224 std::env::var("MEMRA_PP_Q8MMQ")
1225 .map(|v| v != "0")
1226 .unwrap_or(true)
1227 })
1228}
1229
1230pub fn mmq_iq4xs_enabled() -> bool {
1238 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1239 *ON.get_or_init(|| {
1240 std::env::var("MEMRA_PP_IQMMQ")
1241 .map(|v| v != "0")
1242 .unwrap_or(true)
1243 })
1244}
1245
1246pub fn mmq_q4_enabled() -> bool {
1252 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1253 *ON.get_or_init(|| {
1254 std::env::var("MEMRA_PP_Q4MMQ")
1255 .map(|v| v != "0")
1256 .unwrap_or(true)
1257 })
1258}
1259
1260fn nvfp4_use_w4a8(rp: bool, w4a8_explicit: bool, w4a8_default: bool, mmq_explicit: bool) -> bool {
1261 rp || w4a8_explicit || (w4a8_default && !mmq_explicit)
1264}
1265
1266#[cfg(test)]
1267mod b200_dry_policy_tests {
1268 use super::nvfp4_use_w4a8;
1269
1270 #[test]
1271 fn nvfp4_default_and_explicit_routes_do_not_reach_sm100_stubs() {
1272 assert!(nvfp4_use_w4a8(false, false, true, false));
1273 assert!(!nvfp4_use_w4a8(false, false, true, true));
1274 assert!(nvfp4_use_w4a8(false, true, true, true));
1275 assert!(nvfp4_use_w4a8(true, false, false, true));
1276 assert!(!nvfp4_use_w4a8(false, false, false, false));
1277 }
1278}
1279
1280impl Engine {
1281 pub fn mmq_supports(&self, w: &crate::model::GpuTensor) -> bool {
1284 use crate::model::GpuTensor;
1285 if crate::portable_mma_gated() {
1286 return false;
1287 }
1288 let mmq_opt_in = std::env::var("MEMRA_MMQ").is_ok();
1289 match w {
1290 GpuTensor::Quant { qtype, rp, .. } if *qtype == crate::QT_NVFP4 && *rp => {
1297 !cfg!(memra_portable_cuda)
1298 && mmq_w4a8_enabled()
1299 && w.in_features().is_multiple_of(64)
1300 }
1301 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_NVFP4 => {
1306 !cfg!(memra_portable_cuda)
1307 && (mmq_w4a8_enabled() || mmq_opt_in)
1308 && w.in_features().is_multiple_of(64)
1309 }
1310 GpuTensor::Quant { qtype, .. }
1311 if *qtype == crate::QT_Q4_K || *qtype == crate::QT_Q5_K =>
1312 {
1313 (mmq_w4a8_enabled() || mmq_opt_in) && w.in_features().is_multiple_of(256)
1314 }
1315 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q8_0 => {
1320 mmq_q8_enabled() && w.in_features().is_multiple_of(256)
1321 }
1322 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q4_0 => {
1327 mmq_q4_enabled() && w.in_features().is_multiple_of(256)
1328 }
1329 GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_IQ4_XS => {
1335 mmq_iq4xs_enabled()
1336 && Self::iq_fast_enabled()
1337 && w.in_features().is_multiple_of(256)
1338 }
1339 _ => false,
1340 }
1341 }
1342
1343 pub fn qmatvec_mmq(
1346 &self,
1347 w: &crate::model::GpuTensor,
1348 x: &CudaSlice<f32>,
1349 m: usize,
1350 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1351 use crate::model::GpuTensor;
1352 let (in_f, out_f) = (w.in_features(), w.out_features());
1353 let GpuTensor::Quant {
1354 bytes,
1355 scale,
1356 qtype,
1357 rp,
1358 ..
1359 } = w
1360 else {
1361 return Err("qmatvec_mmq: not a Quant tensor".into());
1362 };
1363 let w4a8_explicit = std::env::var("MEMRA_MMQ_W4A8")
1368 .map(|v| v != "0")
1369 .unwrap_or(false);
1370 let use_w4a8 = nvfp4_use_w4a8(
1371 *rp,
1372 w4a8_explicit,
1373 mmq_w4a8_enabled(),
1374 std::env::var("MEMRA_MMQ").is_ok(),
1375 );
1376 match *qtype {
1377 q if q == crate::QT_NVFP4 && use_w4a8 => {
1380 self.qmatvec_mmq_nvfp4_w4a8(bytes, x, m, in_f, out_f, *scale, *rp)
1381 }
1382 q if q == crate::QT_NVFP4 => self.qmatvec_mmq_nvfp4(bytes, x, m, in_f, out_f, *scale),
1383 q if q == crate::QT_Q4_K || q == crate::QT_Q5_K => {
1384 let mut y = self.qmatvec_mmq_q45k_raw(bytes, x, m, in_f, out_f, q)?;
1385 if *scale != 1.0 {
1386 self.scale_inplace(&mut y, *scale, m * out_f)?;
1387 }
1388 Ok(y)
1389 }
1390 q if q == crate::QT_Q8_0 => {
1391 if cfg!(memra_hopper_mma)
1397 && out_f % 64 == 0
1398 && crate::wgmma_gemm_enabled()
1399 && let GpuTensor::Quant { rp4: Some(m4), .. } = w
1400 {
1401 let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
1402 let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, &aq, &ad, m, in_f, out_f)?;
1403 if *scale != 1.0 {
1404 self.scale_inplace(&mut y, *scale, m * out_f)?;
1405 }
1406 return Ok(y);
1407 }
1408 let mut y = self.qmatvec_mmq_q8_0_raw(bytes, x, m, in_f, out_f)?;
1409 if *scale != 1.0 {
1410 self.scale_inplace(&mut y, *scale, m * out_f)?;
1411 }
1412 Ok(y)
1413 }
1414 q if q == crate::QT_Q4_0 => {
1415 let mut y = self.qmatvec_mmq_q4_0_raw(bytes, x, m, in_f, out_f, *rp)?;
1416 if *scale != 1.0 {
1417 self.scale_inplace(&mut y, *scale, m * out_f)?;
1418 }
1419 Ok(y)
1420 }
1421 q if q == crate::QT_IQ4_XS => {
1422 let GpuTensor::Quant { row_bytes, .. } = w else {
1423 unreachable!()
1424 };
1425 let mut y = self.qmatvec_mmq_iq4xs_raw(bytes, x, m, in_f, out_f, *row_bytes)?;
1426 if *scale != 1.0 {
1427 self.scale_inplace(&mut y, *scale, m * out_f)?;
1428 }
1429 Ok(y)
1430 }
1431 q => Err(format!("qmatvec_mmq: unsupported qtype {q}").into()),
1432 }
1433 }
1434
1435 pub fn qmatvec_mmq_iq4xs_raw(
1437 &self,
1438 bytes: &CudaSlice<u8>,
1439 x: &CudaSlice<f32>,
1440 m: usize,
1441 in_f: usize,
1442 out_f: usize,
1443 row_bytes: usize,
1444 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1445 assert!(
1446 in_f.is_multiple_of(256),
1447 "MMQ IQ4_XS requires in_f % 256 == 0, got {in_f}"
1448 );
1449 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, m as i32) };
1450 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1451 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1452 {
1453 let stream = self.gpu.stream();
1454 let (w_p, _gw) = bytes.device_ptr(&stream);
1455 let (x_p, _gx) = x.device_ptr(&stream);
1456 let (y_p, _gy) = y.device_ptr_mut(&stream);
1457 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1458 let rc = unsafe {
1459 memra_mmq_iq4xs_dense(
1460 w_p as *const core::ffi::c_void,
1461 x_p as *const f32,
1462 y_p as *mut f32,
1463 in_f as i32,
1464 out_f as i32,
1465 m as i32,
1466 row_bytes as i64,
1467 s_p as *mut core::ffi::c_void,
1468 stream.cu_stream() as *mut core::ffi::c_void,
1469 )
1470 };
1471 if rc != 0 {
1472 return Err(format!("memra_mmq_iq4xs_dense rc={rc}").into());
1473 }
1474 }
1475 Ok(y)
1476 }
1477
1478 pub fn qmatvec_mmq_q45k_raw(
1483 &self,
1484 bytes: &CudaSlice<u8>,
1485 x: &CudaSlice<f32>,
1486 m: usize,
1487 in_f: usize,
1488 out_f: usize,
1489 qtype: i32,
1490 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1491 assert!(
1492 in_f.is_multiple_of(256),
1493 "MMQ Q4_K/Q5_K requires in_f % 256 == 0, got {in_f}"
1494 );
1495 let act_bytes = unsafe { memra_mmq_q45k_act_bytes(in_f as i32, m as i32) };
1496 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1497 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1498 {
1499 let stream = self.gpu.stream();
1500 let (w_p, _gw) = bytes.device_ptr(&stream);
1501 let (x_p, _gx) = x.device_ptr(&stream);
1502 let (y_p, _gy) = y.device_ptr_mut(&stream);
1503 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1504 let launcher = if qtype == crate::QT_Q4_K {
1505 memra_mmq_q4_K
1506 } else {
1507 memra_mmq_q5_K
1508 };
1509 let rc = unsafe {
1510 launcher(
1511 w_p as *const core::ffi::c_void,
1512 x_p as *const f32,
1513 y_p as *mut f32,
1514 in_f as i32,
1515 out_f as i32,
1516 m as i32,
1517 s_p as *mut core::ffi::c_void,
1518 stream.cu_stream() as *mut core::ffi::c_void,
1519 )
1520 };
1521 if rc != 0 {
1522 return Err(format!("memra_mmq_q45k(qtype={qtype}) rc={rc}").into());
1523 }
1524 }
1525 Ok(y)
1526 }
1527
1528 pub fn qmatvec_mmq_q8_0_raw(
1531 &self,
1532 bytes: &CudaSlice<u8>,
1533 x: &CudaSlice<f32>,
1534 m: usize,
1535 in_f: usize,
1536 out_f: usize,
1537 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1538 assert!(
1539 in_f.is_multiple_of(32),
1540 "MMQ Q8_0 requires in_f % 32 == 0, got {in_f}"
1541 );
1542 let act_bytes = unsafe { memra_mmq_q8_0_act_bytes(in_f as i32, m as i32) };
1543 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1544 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1545 {
1546 let stream = self.gpu.stream();
1547 let (w_p, _gw) = bytes.device_ptr(&stream);
1548 let (x_p, _gx) = x.device_ptr(&stream);
1549 let (y_p, _gy) = y.device_ptr_mut(&stream);
1550 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1551 let rc = unsafe {
1552 memra_mmq_q8_0(
1553 w_p as *const core::ffi::c_void,
1554 x_p as *const f32,
1555 y_p as *mut f32,
1556 in_f as i32,
1557 out_f as i32,
1558 m as i32,
1559 s_p as *mut core::ffi::c_void,
1560 stream.cu_stream() as *mut core::ffi::c_void,
1561 )
1562 };
1563 if rc != 0 {
1564 return Err(format!("memra_mmq_q8_0 rc={rc}").into());
1565 }
1566 }
1567 Ok(y)
1568 }
1569
1570 pub fn accprobe_act_bytes(&self, in_f: usize, m: usize) -> usize {
1573 unsafe { memra_accprobe_act_bytes(in_f as i32, m as i32) }
1574 }
1575
1576 pub fn accprobe_gemm(
1583 &self,
1584 w_q8_0: &CudaSlice<u8>,
1585 act_q: &CudaSlice<u8>,
1586 m: usize,
1587 in_f: usize,
1588 out_f: usize,
1589 f32acc: bool,
1590 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1591 assert!(
1592 in_f.is_multiple_of(32),
1593 "accprobe requires in_f % 32 == 0, got {in_f}"
1594 );
1595 assert!(
1596 act_q.len() >= self.accprobe_act_bytes(in_f, m),
1597 "accprobe act_q too small: {} < {}",
1598 act_q.len(),
1599 self.accprobe_act_bytes(in_f, m)
1600 );
1601 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1602 {
1603 let stream = self.gpu.stream();
1604 let (w_p, _gw) = w_q8_0.device_ptr(&stream);
1605 let (a_p, _ga) = act_q.device_ptr(&stream);
1606 let (y_p, _gy) = y.device_ptr_mut(&stream);
1607 let f = if f32acc {
1608 memra_accprobe_gemm_f32
1609 } else {
1610 memra_accprobe_gemm_s32
1611 };
1612 let rc = unsafe {
1613 f(
1614 w_p as *const core::ffi::c_void,
1615 a_p as *const core::ffi::c_void,
1616 y_p as *mut f32,
1617 in_f as i32,
1618 out_f as i32,
1619 m as i32,
1620 stream.cu_stream() as *mut core::ffi::c_void,
1621 )
1622 };
1623 if rc != 0 {
1624 let arm = if f32acc { "f32" } else { "s32" };
1625 return Err(format!("memra_accprobe_gemm_{arm} rc={rc}").into());
1626 }
1627 }
1628 Ok(y)
1629 }
1630
1631 pub fn mmq_act_begin(&self) {
1637 use std::sync::atomic::Ordering;
1638 MMQ_ACT_EPOCH.fetch_add(1, Ordering::Relaxed);
1639 *MMQ_ACT_SLOT.lock().unwrap() = None;
1640 }
1641
1642 pub fn qmatvec_mmq_q4_0_raw(
1646 &self,
1647 bytes: &CudaSlice<u8>,
1648 x: &CudaSlice<f32>,
1649 m: usize,
1650 in_f: usize,
1651 out_f: usize,
1652 rp: bool,
1653 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1654 use std::sync::atomic::Ordering;
1655 assert!(
1656 in_f.is_multiple_of(32),
1657 "MMQ Q4_0 requires in_f % 32 == 0, got {in_f}"
1658 );
1659 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1660 let stream = self.gpu.stream();
1661 let (x_p, _gx) = x.device_ptr(&stream);
1662 let epoch = MMQ_ACT_EPOCH.load(Ordering::Relaxed);
1663 let mut slot = MMQ_ACT_SLOT.lock().unwrap();
1665 let hit = matches!(&*slot,
1666 Some((e, p, mm, inf, _)) if *e == epoch && *p == x_p && *mm == m && *inf == in_f);
1667 if !hit {
1668 let act_bytes = unsafe { memra_mmq_q4_0_act_bytes(in_f as i32, m as i32) };
1669 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1670 {
1671 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1672 let rc = unsafe {
1673 memra_mmq_q4_0_quant_act(
1674 x_p as *const f32,
1675 s_p as *mut core::ffi::c_void,
1676 in_f as i32,
1677 m as i32,
1678 stream.cu_stream() as *mut core::ffi::c_void,
1679 )
1680 };
1681 if rc != 0 {
1682 return Err(
1683 format!("memra_mmq_q4_0_quant_act(in_f={in_f}, m={m}) rc={rc}").into(),
1684 );
1685 }
1686 }
1687 *slot = Some((epoch, x_p, m, in_f, scratch));
1688 }
1689 let scratch = &slot.as_ref().unwrap().4;
1690 {
1691 let (w_p, _gw) = bytes.device_ptr(&stream);
1692 let (y_p, _gy) = y.device_ptr_mut(&stream);
1693 let (s_p, _gs) = scratch.device_ptr(&stream);
1694 static SK_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1717 let sk = match crate::MMQ_SK_FORCE.load(std::sync::atomic::Ordering::Relaxed) {
1718 0 => false,
1719 1 => true,
1720 _ => *SK_ON.get_or_init(|| {
1721 std::env::var("MEMRA_MMQ_SK")
1722 .map(|v| v != "0")
1723 .unwrap_or(!cfg!(memra_hopper_mma))
1724 }),
1725 };
1726 let rc = if sk {
1727 let mut fx = MMQ_FIXUP_SLOT.lock().unwrap();
1728 if fx.is_none() {
1729 let nb = unsafe { memra_mmq_q4_0_fixup_bytes() };
1730 *fx = Some(self.alloc_uninit::<u8>(nb)?);
1731 }
1732 let (f_p, _gf) = fx.as_mut().unwrap().device_ptr_mut(&stream);
1733 unsafe {
1734 memra_mmq_q4_0_gemm_sk(
1735 w_p as *const core::ffi::c_void,
1736 s_p as *const core::ffi::c_void,
1737 y_p as *mut f32,
1738 f_p as *mut core::ffi::c_void,
1739 in_f as i32,
1740 out_f as i32,
1741 m as i32,
1742 stream.cu_stream() as *mut core::ffi::c_void,
1743 rp as i32,
1744 )
1745 }
1746 } else {
1747 unsafe {
1748 memra_mmq_q4_0_gemm(
1749 w_p as *const core::ffi::c_void,
1750 s_p as *const core::ffi::c_void,
1751 y_p as *mut f32,
1752 in_f as i32,
1753 out_f as i32,
1754 m as i32,
1755 stream.cu_stream() as *mut core::ffi::c_void,
1756 rp as i32,
1757 )
1758 }
1759 };
1760 if rc != 0 {
1761 return Err(format!(
1762 "memra_mmq_q4_0_gemm(rp={rp}, in_f={in_f}, out_f={out_f}, m={m}, wbytes={}) rc={rc}",
1763 bytes.len()
1764 )
1765 .into());
1766 }
1767 }
1768 Ok(y)
1769 }
1770
1771 pub fn qmatvec_mmq_nvfp4(
1777 &self,
1778 bytes: &CudaSlice<u8>,
1779 x: &CudaSlice<f32>,
1780 m: usize,
1781 in_f: usize,
1782 out_f: usize,
1783 scale: f32,
1784 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1785 self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, scale)
1786 }
1787
1788 pub fn qmatvec_mmq_nvfp4_raw(
1790 &self,
1791 bytes: &CudaSlice<u8>,
1792 x: &CudaSlice<f32>,
1793 m: usize,
1794 in_f: usize,
1795 out_f: usize,
1796 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1797 self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, 1.0)
1798 }
1799
1800 pub fn qmatvec_mmq_nvfp4_raw_v1(
1804 &self,
1805 bytes: &CudaSlice<u8>,
1806 x: &CudaSlice<f32>,
1807 m: usize,
1808 in_f: usize,
1809 out_f: usize,
1810 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1811 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, false, 0)
1812 }
1813
1814 pub fn qmatvec_mmq_nvfp4_raw_res(
1816 &self,
1817 bytes: &CudaSlice<u8>,
1818 x: &CudaSlice<f32>,
1819 m: usize,
1820 in_f: usize,
1821 out_f: usize,
1822 residual_k: i32,
1823 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1824 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, true, residual_k)
1825 }
1826
1827 fn qmatvec_mmq_nvfp4_scaled(
1828 &self,
1829 bytes: &CudaSlice<u8>,
1830 x: &CudaSlice<f32>,
1831 m: usize,
1832 in_f: usize,
1833 out_f: usize,
1834 scale: f32,
1835 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1836 self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, scale, true, mmq_residual_k())
1837 }
1838
1839 #[allow(clippy::too_many_arguments)] fn qmatvec_mmq_nvfp4_inner(
1841 &self,
1842 bytes: &CudaSlice<u8>,
1843 x: &CudaSlice<f32>,
1844 m: usize,
1845 in_f: usize,
1846 out_f: usize,
1847 scale: f32,
1848 per_token_scale: bool,
1849 residual_k: i32,
1850 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1851 assert!(
1852 in_f.is_multiple_of(64),
1853 "MMQ NVFP4 requires in_f % 64 == 0, got {in_f}"
1854 );
1855 let act_bytes = unsafe { memra_mmq_nvfp4_act_bytes(in_f as i32, m as i32) };
1856 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1857 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1858 {
1859 let stream = self.gpu.stream();
1860 let (w_p, _gw) = bytes.device_ptr(&stream);
1861 let (x_p, _gx) = x.device_ptr(&stream);
1862 let (y_p, _gy) = y.device_ptr_mut(&stream);
1863 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1864 let rc = unsafe {
1865 memra_mmq_nvfp4_ex2(
1866 w_p as *const core::ffi::c_void,
1867 x_p as *const f32,
1868 y_p as *mut f32,
1869 in_f as i32,
1870 out_f as i32,
1871 m as i32,
1872 s_p as *mut core::ffi::c_void,
1873 stream.cu_stream() as *mut core::ffi::c_void,
1874 scale,
1875 per_token_scale as i32,
1876 residual_k,
1877 )
1878 };
1879 if rc != 0 {
1880 return Err(format!("memra_mmq_nvfp4_ex2 rc={rc}").into());
1881 }
1882 }
1883 Ok(y)
1884 }
1885
1886 #[allow(clippy::too_many_arguments)] pub fn qmatvec_mmq_nvfp4_w4a8(
1892 &self,
1893 bytes: &CudaSlice<u8>,
1894 x: &CudaSlice<f32>,
1895 m: usize,
1896 in_f: usize,
1897 out_f: usize,
1898 scale: f32,
1899 rp: bool,
1900 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1901 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, scale, rp)
1902 }
1903
1904 pub fn qmatvec_mmq_nvfp4_w4a8_raw(
1906 &self,
1907 bytes: &CudaSlice<u8>,
1908 x: &CudaSlice<f32>,
1909 m: usize,
1910 in_f: usize,
1911 out_f: usize,
1912 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1913 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, false)
1914 }
1915
1916 pub fn qmatvec_mmq_nvfp4_w4a8_raw_rp(
1919 &self,
1920 bytes: &CudaSlice<u8>,
1921 x: &CudaSlice<f32>,
1922 m: usize,
1923 in_f: usize,
1924 out_f: usize,
1925 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1926 self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, true)
1927 }
1928
1929 #[allow(clippy::too_many_arguments)] fn qmatvec_mmq_nvfp4_w4a8_scaled(
1931 &self,
1932 bytes: &CudaSlice<u8>,
1933 x: &CudaSlice<f32>,
1934 m: usize,
1935 in_f: usize,
1936 out_f: usize,
1937 scale: f32,
1938 rp: bool,
1939 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1940 static F8F4: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1944 let f8f4 = *F8F4.get_or_init(|| std::env::var("MEMRA_MMQ_F8F4").as_deref() == Ok("1"));
1945 assert!(
1946 in_f.is_multiple_of(64),
1947 "MMQ NVFP4 W4A8 requires in_f % 64 == 0, got {in_f}"
1948 );
1949 let act_bytes = unsafe { memra_mmq_nvfp4_w4a8_act_bytes(in_f as i32, m as i32) };
1950 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1951 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1952 {
1953 let stream = self.gpu.stream();
1954 let (w_p, _gw) = bytes.device_ptr(&stream);
1955 let (x_p, _gx) = x.device_ptr(&stream);
1956 let (y_p, _gy) = y.device_ptr_mut(&stream);
1957 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1958 let rc = unsafe {
1960 if f8f4 {
1961 memra_mmq_nvfp4_f8f4(
1962 w_p as *const core::ffi::c_void,
1963 x_p as *const f32,
1964 y_p as *mut f32,
1965 in_f as i32,
1966 out_f as i32,
1967 m as i32,
1968 s_p as *mut core::ffi::c_void,
1969 stream.cu_stream() as *mut core::ffi::c_void,
1970 scale,
1971 rp as i32,
1972 )
1973 } else {
1974 memra_mmq_nvfp4_w4a8(
1975 w_p as *const core::ffi::c_void,
1976 x_p as *const f32,
1977 y_p as *mut f32,
1978 in_f as i32,
1979 out_f as i32,
1980 m as i32,
1981 s_p as *mut core::ffi::c_void,
1982 stream.cu_stream() as *mut core::ffi::c_void,
1983 scale,
1984 rp as i32,
1985 )
1986 }
1987 };
1988 if rc != 0 {
1989 return Err(format!("memra_mmq_nvfp4_w4a8(f8f4={f8f4}) rc={rc}").into());
1990 }
1991 }
1992 Ok(y)
1993 }
1994
1995 pub fn qmatvec_mmq_fp8_blk(
1999 &self,
2000 w_e4m3: &CudaSlice<u8>,
2001 blk_scales: &CudaSlice<f32>,
2002 x: &CudaSlice<f32>,
2003 m: usize,
2004 in_f: usize,
2005 out_f: usize,
2006 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2007 self.qmatvec_mmq_fp8_blk_scaled(w_e4m3, blk_scales, x, m, in_f, out_f, 1.0)
2008 }
2009
2010 #[allow(clippy::too_many_arguments)] pub fn qmatvec_mmq_fp8_blk_scaled(
2012 &self,
2013 w_e4m3: &CudaSlice<u8>,
2014 blk_scales: &CudaSlice<f32>,
2015 x: &CudaSlice<f32>,
2016 m: usize,
2017 in_f: usize,
2018 out_f: usize,
2019 scale: f32,
2020 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2021 if cfg!(memra_sm100_tcgen05) && std::env::var("MEMRA_FP8_MMQ").as_deref() != Ok("1") {
2022 return Err(
2023 "B200 block-FP8 tcgen05 is NativeReference but not tuned; set \
2024 MEMRA_FP8_MMQ=1 only for explicit qualification or research (the pinned \
2025 pp1483 receipt measured 0.173x the established fallback)"
2026 .into(),
2027 );
2028 }
2029 assert!(
2030 in_f.is_multiple_of(16),
2031 "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
2032 );
2033 #[allow(clippy::manual_div_ceil)]
2034 let want_scales = ((out_f + 127) / 128) * ((in_f + 127) / 128);
2036 assert!(
2037 blk_scales.len() >= want_scales,
2038 "blk_scales too small: {} < {want_scales}",
2039 blk_scales.len()
2040 );
2041 assert!(
2042 w_e4m3.len() >= out_f * in_f,
2043 "e4m3 plane too small: {} < {}",
2044 w_e4m3.len(),
2045 out_f * in_f
2046 );
2047 let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2048 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2049 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2050 {
2051 let stream = self.gpu.stream();
2052 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2053 let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2054 let (x_p, _gx) = x.device_ptr(&stream);
2055 let (y_p, _gy) = y.device_ptr_mut(&stream);
2056 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2057 let rc = unsafe {
2058 memra_mmq_fp8_blk(
2059 w_p as *const core::ffi::c_void,
2060 sc_p as *const f32,
2061 x_p as *const f32,
2062 y_p as *mut f32,
2063 in_f as i32,
2064 out_f as i32,
2065 m as i32,
2066 s_p as *mut core::ffi::c_void,
2067 stream.cu_stream() as *mut core::ffi::c_void,
2068 scale,
2069 )
2070 };
2071 if rc != 0 {
2072 return Err(format!("memra_mmq_fp8_blk rc={rc}").into());
2073 }
2074 }
2075 Ok(y)
2076 }
2077
2078 pub fn qmatvec_mmq_fp8_blk_view(
2083 &self,
2084 w_e4m3: &CudaView<'_, u8>,
2085 blk_scales: &CudaView<'_, f32>,
2086 x: &CudaView<'_, f32>,
2087 m: usize,
2088 in_f: usize,
2089 out_f: usize,
2090 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2091 if cfg!(memra_sm100_tcgen05) && std::env::var("MEMRA_FP8_MMQ").as_deref() != Ok("1") {
2092 return Err(
2093 "B200 block-FP8 tcgen05 is NativeReference but not tuned; set \
2094 MEMRA_FP8_MMQ=1 only for explicit qualification or research (the pinned \
2095 pp1483 receipt measured 0.173x the established fallback)"
2096 .into(),
2097 );
2098 }
2099 assert!(
2100 in_f.is_multiple_of(16),
2101 "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
2102 );
2103 let want_scales = out_f.div_ceil(128) * in_f.div_ceil(128);
2104 assert!(
2105 blk_scales.len() >= want_scales,
2106 "blk_scales view too small: {} < {want_scales}",
2107 blk_scales.len()
2108 );
2109 assert!(
2110 w_e4m3.len() >= out_f * in_f,
2111 "e4m3 view too small: {} < {}",
2112 w_e4m3.len(),
2113 out_f * in_f
2114 );
2115 assert!(
2116 x.len() >= m * in_f,
2117 "activation view too small: {} < {}",
2118 x.len(),
2119 m * in_f
2120 );
2121
2122 let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2123 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2124 let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2125 {
2126 let stream = self.gpu.stream();
2127 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2128 let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2129 let (x_p, _gx) = x.device_ptr(&stream);
2130 let (y_p, _gy) = y.device_ptr_mut(&stream);
2131 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2132 let rc = unsafe {
2133 memra_mmq_fp8_blk(
2134 w_p as *const core::ffi::c_void,
2135 sc_p as *const f32,
2136 x_p as *const f32,
2137 y_p as *mut f32,
2138 in_f as i32,
2139 out_f as i32,
2140 m as i32,
2141 s_p as *mut core::ffi::c_void,
2142 stream.cu_stream() as *mut core::ffi::c_void,
2143 1.0,
2144 )
2145 };
2146 if rc != 0 {
2147 return Err(format!("memra_mmq_fp8_blk(view) rc={rc}").into());
2148 }
2149 }
2150 Ok(y)
2151 }
2152
2153 pub fn fp8_blk_nan_count(
2157 &self,
2158 w_e4m3: &CudaSlice<u8>,
2159 ) -> Result<u32, Box<dyn std::error::Error>> {
2160 let mut cnt = self.htod_u32_v(&[0u32])?;
2161 let n = w_e4m3.len();
2162 {
2163 let stream = self.gpu.stream();
2164 let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2165 let (c_p, _gc) = cnt.device_ptr_mut(&stream);
2166 let rc = unsafe {
2167 memra_fp8_blk_count_nan(
2168 w_p as *const core::ffi::c_void,
2169 n,
2170 c_p as *mut u32,
2171 stream.cu_stream() as *mut core::ffi::c_void,
2172 )
2173 };
2174 if rc != 0 {
2175 return Err(format!("memra_fp8_blk_count_nan rc={rc}").into());
2176 }
2177 }
2178 Ok(self.dtoh_u32(&cnt)?[0])
2179 }
2180
2181 pub fn mmq_iq_quantize_act(
2184 &self,
2185 x: &CudaSlice<f32>,
2186 in_f: usize,
2187 n_tokens: usize,
2188 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2189 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2190 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2191 {
2192 let stream = self.gpu.stream();
2193 let (x_p, _gx) = x.device_ptr(&stream);
2194 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2195 let rc = unsafe {
2196 memra_mmq_iq_quantize_act(
2197 x_p as *const f32,
2198 s_p as *mut core::ffi::c_void,
2199 in_f as i32,
2200 n_tokens as i32,
2201 stream.cu_stream() as *mut core::ffi::c_void,
2202 )
2203 };
2204 if rc != 0 {
2205 return Err(format!("memra_mmq_iq_quantize_act rc={rc}").into());
2206 }
2207 }
2208 Ok(scratch)
2209 }
2210
2211 pub fn mmq_iq_fused_act_quant(
2217 &self,
2218 gate: &CudaSlice<f32>,
2219 up: &CudaSlice<f32>,
2220 in_f: usize,
2221 n_tokens: usize,
2222 act_kind: i32,
2223 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2224 let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2225 let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2226 {
2227 let stream = self.gpu.stream();
2228 let (g_p, _gg) = gate.device_ptr(&stream);
2229 let (u_p, _gu) = up.device_ptr(&stream);
2230 let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2231 let rc = unsafe {
2232 memra_mmq_iq_fused_act_quant(
2233 g_p as *const f32,
2234 u_p as *const f32,
2235 s_p as *mut core::ffi::c_void,
2236 in_f as i32,
2237 n_tokens as i32,
2238 act_kind,
2239 stream.cu_stream() as *mut core::ffi::c_void,
2240 )
2241 };
2242 if rc != 0 {
2243 return Err(format!("memra_mmq_iq_fused_act_quant rc={rc}").into());
2244 }
2245 }
2246 Ok(scratch)
2247 }
2248
2249 #[allow(clippy::too_many_arguments)]
2253 pub fn mmq_iq_experts(
2254 &self,
2255 table: &CudaSlice<u64>,
2256 proj: i32,
2257 n_expert: usize,
2258 ex_ids: &CudaSlice<i32>,
2259 ex_off: &CudaSlice<i32>,
2260 ex_pairs: &CudaSlice<i32>,
2261 pair_tok: &CudaSlice<i32>,
2262 act_scratch: &CudaSlice<u8>,
2263 in_f: usize,
2264 out_f: usize,
2265 n_active: usize,
2266 n_pairs: usize,
2267 n_tokens: usize,
2268 qtype: i32,
2269 row_bytes: usize,
2270 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2271 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2272 {
2273 let stream = self.gpu.stream();
2274 let (tab_p, _g0) = table.device_ptr(&stream);
2275 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2276 let (eo_p, _g2) = ex_off.device_ptr(&stream);
2277 let (ep_p, _g3) = ex_pairs.device_ptr(&stream);
2278 let (pt_p, _g4) = pair_tok.device_ptr(&stream);
2279 let (as_p, _g5) = act_scratch.device_ptr(&stream);
2280 let (y_p, _g6) = y.device_ptr_mut(&stream);
2281 let rc = unsafe {
2282 memra_mmq_iq_experts(
2283 tab_p as *const u64,
2284 proj,
2285 n_expert as i32,
2286 ei_p as *const i32,
2287 eo_p as *const i32,
2288 ep_p as *const i32,
2289 pt_p as *const i32,
2290 as_p as *const core::ffi::c_void,
2291 y_p as *mut f32,
2292 in_f as i32,
2293 out_f as i32,
2294 n_active as i32,
2295 n_tokens as i32,
2296 qtype,
2297 row_bytes as i64,
2298 stream.cu_stream() as *mut core::ffi::c_void,
2299 )
2300 };
2301 if rc != 0 {
2302 return Err(format!("memra_mmq_iq_experts rc={rc}").into());
2303 }
2304 }
2305 Ok(y)
2306 }
2307
2308 pub fn moe_f16g_act(
2313 &self,
2314 x: &CudaSlice<f32>,
2315 pair_tok: Option<&CudaSlice<i32>>,
2316 in_f: usize,
2317 n_pairs: usize,
2318 ) -> Result<(CudaSlice<u8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2319 let mut act = self.alloc_uninit::<u8>(n_pairs * in_f * 2)?;
2320 let mut scales = self.alloc_uninit::<f32>(n_pairs)?;
2321 {
2322 let stream = self.gpu.stream();
2323 let (x_p, _gx) = x.device_ptr(&stream);
2324 let pt_p = match pair_tok {
2325 Some(pt) => {
2326 let (p, _g) = pt.device_ptr(&stream);
2327 p as *const i32
2328 }
2329 None => std::ptr::null(),
2330 };
2331 let (a_p, _ga) = act.device_ptr_mut(&stream);
2332 let (s_p, _gs) = scales.device_ptr_mut(&stream);
2333 let rc = unsafe {
2334 memra_moe_f16g_gather_act(
2335 x_p as *const f32,
2336 pt_p,
2337 a_p as *mut core::ffi::c_void,
2338 s_p as *mut f32,
2339 in_f as i32,
2340 n_pairs as i32,
2341 stream.cu_stream() as *mut core::ffi::c_void,
2342 )
2343 };
2344 if rc != 0 {
2345 return Err(format!("memra_moe_f16g_gather_act rc={rc}").into());
2346 }
2347 }
2348 Ok((act, scales))
2349 }
2350
2351 #[allow(clippy::too_many_arguments)]
2361 pub fn bind_runtime_device(&self, ordinal: i32) -> Result<(), Box<dyn std::error::Error>> {
2366 let rc = unsafe { memra_bind_device(ordinal) };
2367 if rc != 0 {
2368 return Err(format!("cudaSetDevice({ordinal}) rc={rc}").into());
2369 }
2370 Ok(())
2371 }
2372
2373 #[allow(clippy::too_many_arguments)]
2374 #[allow(clippy::manual_is_multiple_of)] pub fn moe_f16_grouped(
2377 &self,
2378 table: &CudaSlice<u64>,
2379 proj: i32,
2380 n_expert: usize,
2381 ex_ids: &CudaSlice<i32>,
2382 ex_off_host: &[i32],
2383 ex_off_dev: &CudaSlice<i32>,
2384 act_f16: &CudaSlice<u8>,
2385 act_scale: &CudaSlice<f32>,
2386 in_f: usize,
2387 out_f: usize,
2388 n_active: usize,
2389 n_pairs: usize,
2390 qtype: i32,
2391 row_bytes: usize,
2392 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2393 let sk = crate::moe_f16g_mode() >= 2 && in_f.is_multiple_of(32);
2394 let (shape_sel, cross) = crate::moe_f16g_sk_params();
2402 if sk
2403 && shape_sel >= 0
2404 && crate::moe_f16g_direct_on(qtype)
2405 && (qtype == crate::QT_Q4_K
2406 || qtype == crate::QT_Q6_K
2407 || qtype == crate::QT_IQ4_XS
2408 || qtype == crate::QT_IQ3_S
2409 || qtype == crate::QT_NVFP4
2410 || qtype == crate::QT_NVFP4_V2)
2414 && in_f % (if qtype == crate::QT_NVFP4 || qtype == crate::QT_NVFP4_V2 { 64 } else { 256 }) == 0
2417 && n_active <= 512
2418 && n_active > 0
2419 {
2420 let max_m = ex_off_host
2421 .windows(2)
2422 .map(|w| w[1] - w[0])
2423 .max()
2424 .unwrap_or(0);
2425 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2426 {
2427 let stream = self.gpu.stream();
2428 let (tab_p, _g0) = table.device_ptr(&stream);
2429 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2430 let (a_p, _g2) = act_f16.device_ptr(&stream);
2431 let (s_p, _g3) = act_scale.device_ptr(&stream);
2432 let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2433 let (y_p, _g5) = y.device_ptr_mut(&stream);
2434 let rc = unsafe {
2435 memra_moe_kq_gemm_sk(
2436 tab_p as *const u64,
2437 proj,
2438 n_expert as i32,
2439 ei_p as *const i32,
2440 a_p as *const core::ffi::c_void,
2441 y_p as *mut f32,
2442 s_p as *const f32,
2443 off_p as *const i32,
2444 ex_off_host.as_ptr(),
2445 n_active as i32,
2446 max_m,
2447 in_f as i32,
2448 out_f as i32,
2449 qtype,
2450 cross,
2451 crate::moe_f16g_tail_on() as i32,
2452 row_bytes as i64,
2453 stream.cu_stream() as *mut core::ffi::c_void,
2454 )
2455 };
2456 if rc != 0 {
2457 return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2458 }
2459 }
2460 return Ok(y);
2461 }
2462 if !sk {
2466 static WARM: std::sync::Once = std::sync::Once::new();
2467 let mut warm_err = None;
2468 WARM.call_once(|| {
2469 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2470 let w = self.alloc_uninit::<u8>(2 * 32 * 64 * 2)?;
2471 let a = self.alloc_uninit::<u8>(4 * 64 * 2)?;
2472 let mut yw = self.alloc_uninit::<u8>(4 * 32 * 2)?;
2473 let off = [0i32, 2, 4];
2474 let stream = self.gpu.stream();
2475 let (w_p, _a1) = w.device_ptr(&stream);
2476 let (a_p, _a2) = a.device_ptr(&stream);
2477 let (y_p, _a3) = yw.device_ptr_mut(&stream);
2478 let rc = unsafe {
2479 memra_moe_f16g_gemm(
2480 w_p as *const core::ffi::c_void,
2481 a_p as *const core::ffi::c_void,
2482 y_p as *mut core::ffi::c_void,
2483 off.as_ptr(),
2484 2,
2485 64,
2486 32,
2487 stream.cu_stream() as *mut core::ffi::c_void,
2488 )
2489 };
2490 if rc != 0 {
2491 return Err(format!("f16g warmup rc={rc}").into());
2492 }
2493 self.gpu.stream().synchronize()?;
2494 Ok(())
2495 })();
2496 if let Err(e) = r {
2497 warm_err = Some(e.to_string());
2498 }
2499 });
2500 if let Some(we) = warm_err {
2501 return Err(we.into());
2502 }
2503 }
2504 let w_bytes = n_active * out_f * in_f * 2;
2505 let mut w_f16 = self.alloc_uninit::<u8>(w_bytes)?;
2506 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2507 {
2508 let stream = self.gpu.stream();
2509 let (tab_p, _g0) = table.device_ptr(&stream);
2510 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2511 let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2512 let rc = unsafe {
2513 memra_moe_f16g_dequant(
2514 tab_p as *const u64,
2515 proj,
2516 n_expert as i32,
2517 ei_p as *const i32,
2518 w_p as *mut core::ffi::c_void,
2519 in_f as i32,
2520 out_f as i32,
2521 n_active as i32,
2522 qtype,
2523 row_bytes as i64,
2524 stream.cu_stream() as *mut core::ffi::c_void,
2525 )
2526 };
2527 if rc != 0 {
2528 return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2529 }
2530 let (a_p, _g3) = act_f16.device_ptr(&stream);
2531 let (s_p, _g6) = act_scale.device_ptr(&stream);
2532 let (y_p, _g5) = y.device_ptr_mut(&stream);
2533 if sk {
2534 let max_m = ex_off_host
2535 .windows(2)
2536 .map(|w| w[1] - w[0])
2537 .max()
2538 .unwrap_or(0);
2539 let (off_p, _g7) = ex_off_dev.device_ptr(&stream);
2540 let (shape_sel, cross) = crate::moe_f16g_sk_params();
2541 let rc = unsafe {
2542 memra_moe_f16g_gemm_sk(
2543 w_p as *const core::ffi::c_void,
2544 a_p as *const core::ffi::c_void,
2545 y_p as *mut f32,
2546 s_p as *const f32,
2547 off_p as *const i32,
2548 ex_off_host.as_ptr(),
2549 n_active as i32,
2550 max_m,
2551 in_f as i32,
2552 out_f as i32,
2553 shape_sel,
2554 cross,
2555 crate::moe_f16g_tail_on() as i32,
2556 stream.cu_stream() as *mut core::ffi::c_void,
2557 )
2558 };
2559 if rc != 0 {
2560 return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2561 }
2562 } else {
2563 let mut y16 = self.alloc_uninit::<u8>(n_pairs * out_f * 2)?;
2564 let (y16_p, _g4) = y16.device_ptr_mut(&stream);
2565 let rc = unsafe {
2566 memra_moe_f16g_gemm(
2567 w_p as *const core::ffi::c_void,
2568 a_p as *const core::ffi::c_void,
2569 y16_p as *mut core::ffi::c_void,
2570 ex_off_host.as_ptr(),
2571 n_active as i32,
2572 in_f as i32,
2573 out_f as i32,
2574 stream.cu_stream() as *mut core::ffi::c_void,
2575 )
2576 };
2577 if rc != 0 {
2578 return Err(format!("memra_moe_f16g_gemm rc={rc}").into());
2579 }
2580 let rc = unsafe {
2581 memra_moe_f16g_h2f_scaled(
2582 y16_p as *const core::ffi::c_void,
2583 y_p as *mut f32,
2584 s_p as *const f32,
2585 out_f as i32,
2586 n_pairs as i32,
2587 stream.cu_stream() as *mut core::ffi::c_void,
2588 )
2589 };
2590 if rc != 0 {
2591 return Err(format!("memra_moe_f16g_h2f_scaled rc={rc}").into());
2592 }
2593 }
2594 }
2595 if !sk {
2600 self.gpu.stream().synchronize()?;
2601 }
2602 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
2603 let wn = n_active * out_f * in_f;
2605 let an = n_pairs * in_f;
2606 let mut wf = self.alloc_uninit::<f32>(wn)?;
2607 let mut af = self.alloc_uninit::<f32>(an)?;
2608 {
2609 let stream = self.gpu.stream();
2610 let (w_p, _a) = w_f16.device_ptr(&stream);
2611 let (a_p, _b) = act_f16.device_ptr(&stream);
2612 let (wf_p, _c) = wf.device_ptr_mut(&stream);
2613 let (af_p, _d) = af.device_ptr_mut(&stream);
2614 unsafe {
2615 memra_moe_f16g_h2f(
2616 w_p as *const core::ffi::c_void,
2617 wf_p as *mut f32,
2618 wn,
2619 stream.cu_stream() as *mut core::ffi::c_void,
2620 );
2621 memra_moe_f16g_h2f(
2622 a_p as *const core::ffi::c_void,
2623 af_p as *mut f32,
2624 an,
2625 stream.cu_stream() as *mut core::ffi::c_void,
2626 );
2627 }
2628 }
2629 let (wh, ah, yh) = (self.dtoh(&wf)?, self.dtoh(&af)?, self.dtoh(&y)?);
2630 let scan = |v: &[f32]| -> (usize, f32) {
2631 let bad = v.iter().filter(|x| !x.is_finite()).count();
2632 let mx = v
2633 .iter()
2634 .filter(|x| x.is_finite())
2635 .fold(0.0f32, |m, x| m.max(x.abs()));
2636 (bad, mx)
2637 };
2638 let (wb, wm) = scan(&wh);
2639 let (ab, am) = scan(&ah);
2640 let (yb, ym) = scan(&yh);
2641 eprintln!(
2642 "[f16g-debug] proj={proj} w: bad={wb} max={wm:.3e} | act: bad={ab} \
2643 max={am:.3e} | y: bad={yb} max={ym:.3e} (na={n_active} np={n_pairs} \
2644 in={in_f} out={out_f})"
2645 );
2646 }
2647 Ok(y)
2648 }
2649
2650 #[allow(clippy::too_many_arguments)]
2657 pub fn moe_f16g_gemm_sk_raw(
2658 &self,
2659 w_f16: &CudaSlice<u8>,
2660 act_f16: &CudaSlice<u8>,
2661 row_scale: &CudaSlice<f32>,
2662 ex_off_host: &[i32],
2663 ex_off_dev: &CudaSlice<i32>,
2664 in_f: usize,
2665 out_f: usize,
2666 n_pairs: usize,
2667 shape_sel: i32,
2668 cross: i32,
2669 tail: i32,
2670 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2671 let n_active = ex_off_host.len() - 1;
2672 let max_m = ex_off_host
2673 .windows(2)
2674 .map(|w| w[1] - w[0])
2675 .max()
2676 .unwrap_or(0);
2677 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2678 {
2679 let stream = self.gpu.stream();
2680 let (w_p, _g0) = w_f16.device_ptr(&stream);
2681 let (a_p, _g1) = act_f16.device_ptr(&stream);
2682 let (s_p, _g2) = row_scale.device_ptr(&stream);
2683 let (off_p, _g3) = ex_off_dev.device_ptr(&stream);
2684 let (y_p, _g4) = y.device_ptr_mut(&stream);
2685 let rc = unsafe {
2686 memra_moe_f16g_gemm_sk(
2687 w_p as *const core::ffi::c_void,
2688 a_p as *const core::ffi::c_void,
2689 y_p as *mut f32,
2690 s_p as *const f32,
2691 off_p as *const i32,
2692 ex_off_host.as_ptr(),
2693 n_active as i32,
2694 max_m,
2695 in_f as i32,
2696 out_f as i32,
2697 shape_sel,
2698 cross,
2699 tail,
2700 stream.cu_stream() as *mut core::ffi::c_void,
2701 )
2702 };
2703 if rc != 0 {
2704 return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2705 }
2706 }
2707 Ok(y)
2708 }
2709
2710 #[allow(clippy::too_many_arguments)]
2715 pub fn moe_kq_gemm_sk_raw(
2716 &self,
2717 table: &CudaSlice<u64>,
2718 proj: i32,
2719 n_expert: usize,
2720 ex_ids: &CudaSlice<i32>,
2721 act_f16: &CudaSlice<u8>,
2722 row_scale: &CudaSlice<f32>,
2723 ex_off_host: &[i32],
2724 ex_off_dev: &CudaSlice<i32>,
2725 in_f: usize,
2726 out_f: usize,
2727 n_pairs: usize,
2728 qtype: i32,
2729 row_bytes: usize,
2730 cross: i32,
2731 tail: i32,
2732 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2733 let n_active = ex_off_host.len() - 1;
2734 let max_m = ex_off_host
2735 .windows(2)
2736 .map(|w| w[1] - w[0])
2737 .max()
2738 .unwrap_or(0);
2739 let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2740 {
2741 let stream = self.gpu.stream();
2742 let (tab_p, _g0) = table.device_ptr(&stream);
2743 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2744 let (a_p, _g2) = act_f16.device_ptr(&stream);
2745 let (s_p, _g3) = row_scale.device_ptr(&stream);
2746 let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2747 let (y_p, _g5) = y.device_ptr_mut(&stream);
2748 let rc = unsafe {
2749 memra_moe_kq_gemm_sk(
2750 tab_p as *const u64,
2751 proj,
2752 n_expert as i32,
2753 ei_p as *const i32,
2754 a_p as *const core::ffi::c_void,
2755 y_p as *mut f32,
2756 s_p as *const f32,
2757 off_p as *const i32,
2758 ex_off_host.as_ptr(),
2759 n_active as i32,
2760 max_m,
2761 in_f as i32,
2762 out_f as i32,
2763 qtype,
2764 cross,
2765 tail,
2766 row_bytes as i64,
2767 stream.cu_stream() as *mut core::ffi::c_void,
2768 )
2769 };
2770 if rc != 0 {
2771 return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2772 }
2773 }
2774 Ok(y)
2775 }
2776
2777 #[allow(clippy::too_many_arguments)] pub fn moe_f16g_dequant_raw(
2782 &self,
2783 table: &CudaSlice<u64>,
2784 proj: i32,
2785 n_expert: usize,
2786 ex_ids: &CudaSlice<i32>,
2787 in_f: usize,
2788 out_f: usize,
2789 n_active: usize,
2790 qtype: i32,
2791 row_bytes: usize,
2792 ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2793 let mut w_f16 = self.alloc_uninit::<u8>(n_active * out_f * in_f * 2)?;
2794 {
2795 let stream = self.gpu.stream();
2796 let (tab_p, _g0) = table.device_ptr(&stream);
2797 let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2798 let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2799 let rc = unsafe {
2800 memra_moe_f16g_dequant(
2801 tab_p as *const u64,
2802 proj,
2803 n_expert as i32,
2804 ei_p as *const i32,
2805 w_p as *mut core::ffi::c_void,
2806 in_f as i32,
2807 out_f as i32,
2808 n_active as i32,
2809 qtype,
2810 row_bytes as i64,
2811 stream.cu_stream() as *mut core::ffi::c_void,
2812 )
2813 };
2814 if rc != 0 {
2815 return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2816 }
2817 }
2818 Ok(w_f16)
2819 }
2820}