Skip to main content

memra_engine/
mmq_ffi.rs

1//! FFI to the MMQ prefill GEMMs (cu/mmq_fp4.cu + cu/mmq_q45k.cu) — vendored floor kernels.
2//!
3//! NVFP4: the 5150-pp512 kernel from llama.cpp, ggml-decoupled into a static lib with a C-ABI host
4//! launcher. The launcher quantizes the f32 activation to block_fp4_mmq internally (llama's 2-level
5//! FP8-e8m0/UE4M3 scale = the accurate W4A8-via-FP8 path that fixes memra's W4A4 maxdiff 1.46), then
6//! launches the native mxf4nvf4 block-scale tensor-core mma.
7//!
8//! Q4_K/Q5_K: llama's k-quant int8-MMA MMQ (dequant to int8 at tile-load, q8_1 DS4 activation with
9//! the (d, sum) pair that feeds the k-quant min-offset term, shared m16n8k32 s8 mma inner loop).
10//! Replaces the hand-rolled qmatvec_gemm k-quant GEMMs that dominate prefill (32% + 28% busy).
11//!
12//! All dispatched behind MEMRA_MMQ=1. Always built (no external deps) — unlike cutlass_ffi which is
13//! MEMRA_CUTLASS-gated.
14
15use crate::Engine;
16use cudarc::driver::{CudaSlice, CudaView, DevicePtr, DevicePtrMut};
17
18/// Quantize-once seam state (see `Engine::mmq_act_begin`): window epoch + one cached
19/// (epoch, act_ptr, m, in_f, D4 scratch) slot. Slot drops (freeing the scratch) on each new window.
20static 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);
24/// Stream-k fixup scratch (lazy; sized once per process — one slot per SM).
25static MMQ_FIXUP_SLOT: std::sync::Mutex<Option<cudarc::driver::CudaSlice<u8>>> =
26    std::sync::Mutex::new(None);
27
28/// Model-agnostic expert-major CSR used by grouped projection backends.
29///
30/// `ex_pairs` is a permutation of pair-major output rows. `pair_tok[pair]` selects the activation
31/// row consumed by that pair. Model adapters supply route choices; this type builds, validates,
32/// owns, and uploads the expert-major schedule.
33pub 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    /// Build an expert-major schedule from token-major top-k route choices.
44    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    /// Build an expert-major schedule with an explicit activation row for every output pair.
70    ///
71    /// This is used by chained grouped projections: gate/up pairs select token rows, while the
72    /// down projection selects the corresponding pair-major activation row.
73    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    /// Allocate stable device storage for schedules up to the supplied logical maxima.
309    ///
310    /// `refresh` fills prefixes of these buffers. The grouped kernel receives only the active
311    /// lengths, so route changes do not change any device pointer or allocate in the hot path.
312    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
433/// Caller-owned persistent buffers for grouped block-E4M3 projections.
434///
435/// Allocation and routing-plan upload happen outside the hot projection path. `quantize` and
436/// `project` overwrite their complete buffers and therefore introduce no per-call allocations.
437pub 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    /// Quantize an active prefix while retaining the workspace's stable capacity pointers.
483    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    /// Bytes needed for the block_fp4_mmq activation scratch for (in_f, n_tokens).
742    pub fn memra_mmq_nvfp4_act_bytes(in_f: i32, n_tokens: i32) -> usize;
743    /// Run the NVFP4 W4A4 MMQ prefill GEMM. y[n_tokens, out_f] = act[n_tokens, in_f] @ W[out_f, in_f]^T.
744    ///   W_nvfp4_blocks : raw memra NVFP4 weight rows (block_nvfp4 36B blocks, in_f/64 per row).
745    ///   act_f32        : f32 activation [n_tokens, in_f] (contiguous).
746    ///   y              : f32 output [n_tokens, out_f].
747    ///   act_scratch    : pre-alloc'd quant buffer >= memra_mmq_nvfp4_act_bytes(in_f, n_tokens).
748    /// Returns 0 on success, else (1000 + cudaError).
749    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    /// Same as `memra_mmq_nvfp4`, plus the activation-quantizer selector.
761    ///   per_token_scale = 1: two-level scaling (per-token row amax folded into the GEMM epilogue
762    ///     + per-sub-block UE4M3). This is what `memra_mmq_nvfp4` does.
763    ///   per_token_scale = 0: the v1 sub-block-only quantizer, retained as the numeric oracle so
764    ///     kernel-check can measure what the row scale bought, and as the rollback seam.
765    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    /// Same as `memra_mmq_nvfp4_ex`, plus the residual high-precision channel count.
778    ///   residual_k = 0: off.
779    ///   residual_k > 0: the k largest-magnitude activation channels (ranked across the batch) are
780    ///     zeroed before quantization and their exact f32 contribution is added back as a rank-k
781    ///     correction. Requires per_token_scale = 1. Clamped to MMQ_MAX_RESIDUAL_K (64).
782    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    /// Bytes needed for the block_q8_1_mmq activation scratch for the NVFP4 W4A8 path.
796    pub fn memra_mmq_nvfp4_w4a8_act_bytes(in_f: i32, n_tokens: i32) -> usize;
797    /// Run the NVFP4 W4A8 MMQ prefill GEMM (STAGE 2 accuracy-safe rung). Same fast MMQ tile as
798    /// memra_mmq_nvfp4 (W4A4) but the non-Blackwell int8 pair: weight FP4 LUT-dequantized to int8 at
799    /// tile-load, activation stays q8_1 int8 (D4, the same quant class as the default int8 GEMM).
800    /// `rp`: 0 = GGUF 36B-block weight layout, 1 = A6 split-plane repack (the resident decode
801    /// layout). The rp tile loader is a pure address remap of the GGUF loader (same dequant math,
802    /// same FP op order) — output is bit-identical either way.
803    /// Same contract as memra_mmq_nvfp4 otherwise. Returns 0 or (1000 + cudaError).
804    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    /// Bytes for the block_e4m3_mmq activation scratch (footprint-identical to block_q8_1_mmq).
817    pub fn memra_mmq_nvfp4_f8f4_act_bytes(in_f: i32, n_tokens: i32) -> usize;
818    /// R-B W4A8-FP8 MMQ prefill GEMM (research/prefill-mxf8f6f4-design.md): NVFP4 per-16 scales
819    /// fold into e4m3 weight VALUES at tile load; e4m3 activations; ONE kind::f8f6f4 m16n8k32
820    /// MMA (381-TF class) where the int8 path issues two imma k16. NEW NUMERIC CONFIG — own
821    /// battery. Same contract/rp semantics as memra_mmq_nvfp4_w4a8. Returns 0 / 1000+cudaError /
822    /// 2000+cudaError.
823    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    /// Bytes for the per-block FP8 MMQ activation scratch (delegates to the F8F4 sizing — the
836    /// two arms deliberately share ONE activation format, `block_e4m3_mmq`).
837    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    /// Scale-grid dims for an [out_f x in_f] block-128 FP8 tensor (ceil-div by 128).
866    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    /// PER-BLOCK FP8 MMQ prefill GEMM (cu/mmq_fp8_blk.cu, P1 option (b)): consumes the
869    /// Qwen-official e4m3 weight bytes + the per-[128x128] f32 scale grid DIRECTLY. The weight
870    /// side is never re-quantized (the checkpoint bytes are the MMA A operand), so unlike ARM A's
871    /// per-tensor fold there is no precision loss; unlike ARM B' it does not land on the Q8_0
872    /// floor. `blk_scales` is device f32 [ceil(out_f/128) x ceil(in_f/128)], row-major.
873    /// Requires in_f % 16 == 0. Returns 0 / 1 (bad dims) / 1000+cudaError / 2000+cudaError.
874    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    /// Count e4m3 NaN codes (magnitude 0x7F) in a device weight buffer. Those decode to NaN in
887    /// hardware but to 0.0 in the host/ARM B' convention, so a tensor containing any must NOT
888    /// ride `memra_mmq_fp8_blk`. `out_count` is a device u32 (zeroed by the call).
889    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    /// Bytes needed for the block_q8_1_mmq activation scratch (shared by Q4_K and Q5_K).
896    pub fn memra_mmq_q45k_act_bytes(in_f: i32, n_tokens: i32) -> usize;
897    /// Run the Q4_K W4A8 MMQ prefill GEMM. Same contract as memra_mmq_nvfp4 (raw ggml block_q4_K
898    /// weight rows, in_f/256 144B superblocks per row). Returns 0 or (1000 + cudaError).
899    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    /// Run the Q5_K W4A8 MMQ prefill GEMM (176B superblocks). Same contract as memra_mmq_q4_K.
910    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    /// Bytes needed for the block_q8_1_mmq (D4) activation scratch for the Q8_0 MMQ path.
922    pub fn memra_mmq_q8_0_act_bytes(in_f: i32, n_tokens: i32) -> usize;
923    /// Run the Q8_0 int8-MMA MMQ prefill GEMM (MEMRA_PP_Q8MMQ). Conventional xy-tiling only (no fixup
924    /// scratch). Weight = raw ggml block_q8_0 rows (34B blocks, in_f/32 per row); activation is
925    /// quantized internally to q8_1 D4. Requires in_f % 32 == 0. Returns 0 or (1000 + cudaError).
926    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    // ---- Q1 accumulator instrument (cu/mmq_q8_0_f32acc.cu, lane/fp8-v3-gate) ----
938    // The Q8_0 MMQ floor's GEMM with the accumulator as its ONE free variable: arm S32 is the
939    // floor's `mma...s32.s8.s8.s32`, arm F32 is the same m16n8k32 shape and the same A/B/D fragment
940    // ABI with `mma...kind::f8f6f4...f32.e4m3.e4m3.f32` — the op cu/mmq_fp8_blk.cu accumulates in.
941    // Both take a PRE-QUANTIZED block_q8_1_mmq activation buffer, so the measurement is GEMM-only
942    // and cannot differ by a quantizer. Research instrument only: no dispatch seam, and neither arm's
943    // output is a numeric claim (see the TU header).
944    /// Activation-scratch bytes for the accumulator instrument (same padding rule as the floor).
945    pub fn memra_accprobe_act_bytes(in_f: i32, n_tokens: i32) -> usize;
946    /// ARM S32 — the floor's GEMM verbatim, s32 accumulate. Returns 0, 1, or 1000+cudaError.
947    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    /// ARM F32 — byte-identical kernel, f32 accumulate over the e4m3 reading of the same bytes.
957    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    /// Bytes needed for the block_q8_1_mmq (D4) activation scratch for the Q4_0 MMQ path.
968    pub fn memra_mmq_q4_0_act_bytes(in_f: i32, n_tokens: i32) -> usize;
969    /// Run the Q4_0 int8-MMA MMQ prefill GEMM (MEMRA_PP_Q4MMQ). Nibbles dequant to int8 at
970    /// tile-load (the -8 zero-point folds into the quants, D4 epilogue — same accuracy class as
971    /// the Q8_0 MMQ). `rp`: 0 = raw ggml 18B blocks, 1 = MEMRA_Q4RP split-plane repack (qs plane +
972    /// fp16 d plane) — pure address remap, bit-identical output either way. Requires
973    /// in_f % 32 == 0. Returns 0 or (1000 + cudaError).
974    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    /// Quantize-only entry (quantize-once seam): f32 activation -> block_q8_1_mmq scratch.
986    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    /// GEMM-only entry: consumes a pre-quantized scratch (from memra_mmq_q4_0_quant_act).
994    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    /// Stream-k fixup scratch bytes (one [MMQ_X x MMQ_Y] f32 slot per SM).
1005    pub fn memra_mmq_q4_0_fixup_bytes() -> usize;
1006    /// Force the CLC work-stealing arm: 1 = on, 0 = off (static grid), -1 = MEMRA_MMQ_CLC env
1007    /// default. Schedule-only swap of the xy-tiling kernel — bit-identical output by
1008    /// construction (perf-frontier lever #1). Returns 1 when the CLC kernel is compiled in
1009    /// (SM_100+ gencode), 0 on sm_89/90a builds (force is a no-op there; static grid always).
1010    pub fn memra_mmq_q4_0_set_clc(force: i32) -> i32;
1011    /// Stream-k GEMM entry: deterministic form selection, with the SK form itself
1012    /// falling back to tiling when wave efficiency is at least 90%.
1013    pub fn memra_mmq_q4_0_gemm_sk(
1014        w_q4_0: *const core::ffi::c_void,
1015        act_scratch: *const core::ffi::c_void,
1016        y: *mut f32,
1017        fixup_scratch: *mut core::ffi::c_void,
1018        in_f: i32,
1019        out_f: i32,
1020        n_tokens: i32,
1021        stream: *mut core::ffi::c_void,
1022        rp: i32,
1023    ) -> i32;
1024
1025    // ---- IQ3_S / IQ4_XS expert-segmented int8-MMA MMQ (cu/mmq_iq_experts.cu, MEMRA_MOE_MMA) ----
1026    /// Bytes for the token-major block_q8_1_mmq activation scratch (in_f, n_tokens).
1027    pub fn memra_mmq_iq_experts_act_bytes(in_f: i32, n_tokens: i32) -> usize;
1028    /// Quantize token-major f32 activation [n_tokens, in_f] -> block_q8_1_mmq (D4). Returns 0 or 1000+err.
1029    pub fn memra_mmq_iq_quantize_act(
1030        act_f32: *const f32,
1031        act_scratch: *mut core::ffi::c_void,
1032        in_f: i32,
1033        n_tokens: i32,
1034        stream: *mut core::ffi::c_void,
1035    ) -> i32;
1036    /// Fused act-epilogue: silu/gelu(gate)*up + q8_1_mmq (D4) quantize in ONE launch — no f32 act
1037    /// buffer. gate/up pair-major [n_tokens, in_f]; scratch identical to memra_mmq_iq_quantize_act.
1038    /// act_kind: 0=silu*mul, 1=gelu_tanh*mul. Byte-identical to the two-pass path (kernel-check gated).
1039    pub fn memra_mmq_iq_fused_act_quant(
1040        gate: *const f32,
1041        up: *const f32,
1042        act_scratch: *mut core::ffi::c_void,
1043        in_f: i32,
1044        n_tokens: i32,
1045        act_kind: i32,
1046        stream: *mut core::ffi::c_void,
1047    ) -> i32;
1048    /// Expert-segmented IQ MMA MMQ. Same CSR shape as moe_pairs_matvec_q8_dec: `table` = [3,n_expert]
1049    /// device slab ptrs, CSR ex_ids/ex_off/ex_pairs group pairs by expert, pair_tok gathers the
1050    /// activation row. y = [n_pairs, out_f] pair-major. `act_scratch` pre-quantized over n_tokens.
1051    /// qtype: 5=IQ4_XS, 6=IQ3_S. Returns 0 or 1000+cudaError.
1052    /// Dense-trunk IQ4_XS MMQ (lane/kquant-tile-loaders): the dense analog of the expert
1053    /// kernel for non-expert IQ4_XS 2-D matmuls (the KAT-Coder trunk class). Quantizes the
1054    /// f32 activation to D4 q8_1_mmq internally; `act_scratch` sized by
1055    /// `memra_mmq_iq_experts_act_bytes`. Requires in_f % 256 == 0.
1056    pub fn memra_mmq_iq4xs_dense(
1057        w_blocks: *const core::ffi::c_void,
1058        act_f32: *const f32,
1059        y: *mut f32,
1060        in_f: i32,
1061        out_f: i32,
1062        n_tokens: i32,
1063        row_bytes: i64,
1064        act_scratch: *mut core::ffi::c_void,
1065        stream: *mut core::ffi::c_void,
1066    ) -> i32;
1067    pub fn memra_mmq_iq_experts(
1068        table: *const u64,
1069        proj: i32,
1070        n_expert: i32,
1071        ex_ids: *const i32,
1072        ex_off: *const i32,
1073        ex_pairs: *const i32,
1074        pair_tok: *const i32,
1075        act_scratch: *const core::ffi::c_void,
1076        y: *mut f32,
1077        in_f: i32,
1078        out_f: i32,
1079        n_active: i32,
1080        n_tokens: i32,
1081        qtype: i32,
1082        row_bytes: i64,
1083        stream: *mut core::ffi::c_void,
1084    ) -> i32;
1085
1086    // ---- MoE grouped f16 GEMM (cu/moe_f16_grouped.cu, round 46 arc 2) ----
1087    pub fn memra_moe_f16g_dequant(
1088        table: *const u64,
1089        proj: i32,
1090        n_expert: i32,
1091        ex_ids: *const i32,
1092        w_f16: *mut core::ffi::c_void,
1093        in_f: i32,
1094        out_f: i32,
1095        n_active: i32,
1096        qtype: i32,
1097        row_bytes: i64,
1098        stream: *mut core::ffi::c_void,
1099    ) -> i32;
1100    pub fn memra_moe_f16g_gather_act(
1101        x: *const f32,
1102        pair_tok_or_null: *const i32,
1103        act_f16: *mut core::ffi::c_void,
1104        row_scale: *mut f32,
1105        in_f: i32,
1106        n_pairs: i32,
1107        stream: *mut core::ffi::c_void,
1108    ) -> i32;
1109    pub fn memra_moe_f16g_h2f_scaled(
1110        src_f16: *const core::ffi::c_void,
1111        dst: *mut f32,
1112        row_scale: *const f32,
1113        ncols: i32,
1114        nrows: i32,
1115        stream: *mut core::ffi::c_void,
1116    ) -> i32;
1117    pub fn memra_moe_f16g_gemm(
1118        w_f16: *const core::ffi::c_void,
1119        act_f16: *const core::ffi::c_void,
1120        y_f16: *mut core::ffi::c_void,
1121        ex_off_host: *const i32,
1122        n_active: i32,
1123        in_f: i32,
1124        out_f: i32,
1125        stream: *mut core::ffi::c_void,
1126    ) -> i32;
1127    pub fn memra_moe_f16g_h2f(
1128        src_f16: *const core::ffi::c_void,
1129        dst: *mut f32,
1130        n: usize,
1131        stream: *mut core::ffi::c_void,
1132    ) -> i32;
1133    // Single-kernel grouped GEMM (MEMRA_MOE_F16G=2, rounds 49+51): on OUR stream, f32 C with
1134    // the act row-scale folded in — no cublas internal-stream race, no sync. Round 51 runs it
1135    // as a persistent problem-visitor over the real tiles with two tile forms (32x64 tail
1136    // / 128x64x64 3-stage): shape_sel < 0 = the round-49 grid-scan kernel (rollback
1137    // arm); else groups with m_e >= cross ride the 128 form. ex_off_host sizes the visitor
1138    // grids host-side (the offsets are already there at the call site — no extra transfer).
1139    // tail != 0 (lane/sk-tail-form): sub-cross groups ride the DEEP tail (32x64x64 3-stage);
1140    // 0 = the round-51 2-stage 32x64x32 (MEMRA_F16G_TAIL=0 rollback). Byte-identical arms.
1141    pub fn memra_moe_f16g_gemm_sk(
1142        w_f16: *const core::ffi::c_void,
1143        act_f16: *const core::ffi::c_void,
1144        y_f32: *mut f32,
1145        row_scale: *const f32,
1146        ex_off_dev: *const i32,
1147        ex_off_host: *const i32,
1148        n_active: i32,
1149        max_m: i32,
1150        in_f: i32,
1151        out_f: i32,
1152        shape_sel: i32,
1153        cross: i32,
1154        tail: i32,
1155        stream: *mut core::ffi::c_void,
1156    ) -> i32;
1157    // DIRECT-FROM-QUANT sk visitor grouped GEMM (lane/kquant-tile-loaders + iq-direct-loaders):
1158    // the visitor forms with the B (weight) tiles dequanted in-register from the expert
1159    // superblocks — no f16 dequant workspace pass. Bit-identical to the workspace path by
1160    // construction (kernel-check "f16g-kq-direct"). qtype: QT_Q4_K | QT_Q6_K | QT_IQ4_XS |
1161    // QT_IQ3_S; rc=2 = not admitted here (caller keeps the dequant-workspace path).
1162    // tail: as memra_moe_f16g_gemm_sk.
1163    pub fn memra_moe_kq_gemm_sk(
1164        table: *const u64,
1165        proj: i32,
1166        n_expert: i32,
1167        ex_ids: *const i32,
1168        act_f16: *const core::ffi::c_void,
1169        y_f32: *mut f32,
1170        row_scale: *const f32,
1171        ex_off_dev: *const i32,
1172        ex_off_host: *const i32,
1173        n_active: i32,
1174        max_m: i32,
1175        in_f: i32,
1176        out_f: i32,
1177        qtype: i32,
1178        cross: i32,
1179        tail: i32,
1180        row_bytes: i64,
1181        stream: *mut core::ffi::c_void,
1182    ) -> i32;
1183}
1184
1185/// W4A8-MMQ DEFAULT-FLIP seam (2026-07-05): the vendored MMQ prefill suite is DEFAULT-ON — NVFP4
1186/// takes the W4A8 MMQ tile (same int8 accuracy class as the int8 GEMM it replaces, all exactness
1187/// gates hold, ~1.9x pp512; the rp tile-loader arm coexists with the A6 split-plane repack) and
1188/// Q4_K/Q5_K take the vendored k-quant int8-MMA MMQ (also int8-class; gated with W4A8 in the same
1189/// battery — the predecessor's `MEMRA_MMQ_W4A8=1` arm engaged BOTH, this flip preserves exactly
1190/// that measured config). `MEMRA_MMQ_W4A8=0` = escape hatch back to the int8 GEMM prefill
1191/// everywhere. `MEMRA_MMQ=1` additionally switches GGUF-layout NVFP4 to the W4A4 mxf4nvf4 tile
1192/// (speed/accuracy tradeoff opt-in, unchanged).
1193pub fn mmq_w4a8_enabled() -> bool {
1194    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1195    *ON.get_or_init(|| {
1196        std::env::var("MEMRA_MMQ_W4A8")
1197            .map(|v| v != "0")
1198            .unwrap_or(true)
1199    })
1200}
1201
1202/// Residual high-precision activation channels for the W4A4 MMQ prefill path.
1203/// `MEMRA_MMQ_RESIDUAL_K=<k>` keeps the k largest-magnitude activation channels out of the e2m1
1204/// quantized path and adds their exact f32 contribution back as a rank-k correction. k=0 (default)
1205/// is off; the kernel clamps to MMQ_MAX_RESIDUAL_K (64).
1206///
1207/// Read LIVE per call, not OnceLock'd, for the same reason `MEMRA_MMQ` is: the W4A4 exactness gate
1208/// sweeps arms inside ONE process against ONE set of loaded weights, and a cached first read would
1209/// pin every later arm to whatever the first one saw.
1210pub fn mmq_residual_k() -> i32 {
1211    std::env::var("MEMRA_MMQ_RESIDUAL_K")
1212        .ok()
1213        .and_then(|v| v.parse::<i32>().ok())
1214        .unwrap_or(0)
1215        .clamp(0, 64)
1216}
1217
1218/// Q8_0 MMQ prefill seam (lane/ppmmq lever 2, DEFAULT ON since 2026-07-09 — `MEMRA_PP_Q8MMQ=0`
1219/// reverts): routes Q8_0 dense
1220/// projections (m>=16) through the vendored int8-MMA MMQ (cu/mmq_q8_0.cu) instead of the hand-rolled
1221/// `qmatvec_gemm_q8_0` tiling GEMM. Its own numeric config (MMA f32 reduction order != the tiling
1222/// GEMM's) — gated with the full exactness battery. Default OFF until the battery is green.
1223pub fn mmq_q8_enabled() -> bool {
1224    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1225    // Promotion battery (2026-07-09): argmax MATCH on 35B p1/p2/p3 + 9B p2/p3 (p4-16k OOMs
1226    // identically with and without the flag — pre-existing gate capacity limit, not this seam);
1227    // kernel-check ALL GREEN; run-spec K=1..8 PASS on 9B+35B. 35B pp 2456->3069 free-clock.
1228    *ON.get_or_init(|| {
1229        std::env::var("MEMRA_PP_Q8MMQ")
1230            .map(|v| v != "0")
1231            .unwrap_or(true)
1232    })
1233}
1234
1235/// IQ4_XS dense-trunk MMQ prefill seam (lane/kquant-tile-loaders, 2026-08-02): routes
1236/// NON-expert IQ4_XS 2-D projections (m>=16) through the vendored-machinery int8-MMA dense
1237/// MMQ (cu/mmq_iq_experts.cu `mmq_iq4xs_dense_kernel`) instead of the per-column dp4a grid
1238/// — the KAT-Coder prefill wall (0.169x vs llama; zero weight reuse across tokens,
1239/// research/kat-anomaly-20260802 §6). Its own numeric config (MMA reduction order) — gated
1240/// with the full exactness battery. m=1..15 decode/verify keep dp4a (dispatch parity).
1241/// `MEMRA_PP_IQMMQ=0` reverts.
1242pub fn mmq_iq4xs_enabled() -> bool {
1243    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1244    *ON.get_or_init(|| {
1245        std::env::var("MEMRA_PP_IQMMQ")
1246            .map(|v| v != "0")
1247            .unwrap_or(true)
1248    })
1249}
1250
1251/// Q4_0 MMQ prefill seam (gemma-4-12B lane, 2026-07-22): routes Q4_0 dense projections (m>=16)
1252/// through the vendored int8-MMA MMQ (cu/mmq_q4_0.cu) instead of the hand-rolled
1253/// `qmatvec_gemm_q4_0[_rp]` tiling GEMM (measured 77% of the 12B prime pass). Its own numeric
1254/// config (MMA f32 reduction order != the tiling GEMM's) — gated with the full exactness battery
1255/// before default-flip; `MEMRA_PP_Q4MMQ=0` reverts.
1256pub fn mmq_q4_enabled() -> bool {
1257    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1258    *ON.get_or_init(|| {
1259        std::env::var("MEMRA_PP_Q4MMQ")
1260            .map(|v| v != "0")
1261            .unwrap_or(true)
1262    })
1263}
1264
1265impl Engine {
1266    /// True if `w` should take a vendored MMQ GEMM under the current env policy (see
1267    /// `mmq_w4a8_enabled`): NVFP4 needs in_f % 64 == 0, Q4_K/Q5_K need in_f % 256 == 0.
1268    pub fn mmq_supports(&self, w: &crate::model::GpuTensor) -> bool {
1269        use crate::model::GpuTensor;
1270        if crate::portable_mma_gated() {
1271            return false;
1272        }
1273        let mmq_opt_in = std::env::var("MEMRA_MMQ").is_ok();
1274        match w {
1275            // A6 split-plane repacked NVFP4: ONLY the W4A8 loader has an rp arm (pure address
1276            // remap, bit-identical output — mmq_nvfp4_w4a8.cu load_tiles_nvfp4_w4a8<is_rp>).
1277            // The W4A4 loader (mmq_fp4.cu load_tiles_nvfp4_nvfp4) reads 36B GGUF blocks only,
1278            // so an rp weight with W4A8 disabled falls through to the rp-ported int8 GEMM.
1279            // NVFP4 W4A8/W4A4 launchers use .kind::f8f6f4 / mxf4nvf4 tile MMA — sm_100a+/
1280            // sm_120a-only. On every portable build (incl. the 90a Hopper-MMA lane) they are
1281            // fail-closed link stubs (build.rs), so never offer them here.
1282            GpuTensor::Quant { qtype, rp, .. } if *qtype == crate::QT_NVFP4 && *rp => {
1283                !cfg!(memra_portable_cuda)
1284                    && mmq_w4a8_enabled()
1285                    && w.in_features().is_multiple_of(64)
1286            }
1287            // GGUF-layout NVFP4 (MEMRA_RP=0): W4A8 (default-on) or the explicit W4A4 opt-in.
1288            GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_NVFP4 => {
1289                !cfg!(memra_portable_cuda)
1290                    && (mmq_w4a8_enabled() || mmq_opt_in)
1291                    && w.in_features().is_multiple_of(64)
1292            }
1293            GpuTensor::Quant { qtype, .. }
1294                if *qtype == crate::QT_Q4_K || *qtype == crate::QT_Q5_K =>
1295            {
1296                (mmq_w4a8_enabled() || mmq_opt_in) && w.in_features().is_multiple_of(256)
1297            }
1298            // Q8_0 dense projections (35B attn/ssm/shexp): opt-in only (MEMRA_PP_Q8MMQ=1), its own
1299            // numeric config vs qmatvec_gemm_q8_0. in_f % 256 == 0: MMQ_ITER_K=256 loads 8-block
1300            // groups, so a non-multiple row would read a garbage weight tail (fp16 d bytes can be
1301            // NaN-pattern, and NaN * 0-padded-activation = NaN — the 26B ffn_down lesson).
1302            GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q8_0 => {
1303                mmq_q8_enabled() && w.in_features().is_multiple_of(256)
1304            }
1305            // Q4_0 dense projections (gemma QAT ggufs): MEMRA_PP_Q4MMQ seam. Both weight layouts
1306            // (raw 18B blocks and the MEMRA_Q4RP split-plane repack) have loader arms. Same
1307            // in_f % 256 == 0 tail rule as Q8_0 (26B ffn_down in_f=2112 NaN'd on the %32 gate);
1308            // non-multiples fall back to the hand-rolled qmatvec_gemm_q4_0[_rp].
1309            GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_Q4_0 => {
1310                mmq_q4_enabled() && w.in_features().is_multiple_of(256)
1311            }
1312            // IQ4_XS dense projections (KAT-Coder trunk): m>=16 prefill only — decode and
1313            // spec-verify (m<16) keep the qmatvec_iq4_XS_dp4a per-column program (the
1314            // kat-anomaly dispatch-parity law). Requires the dp4a fast path itself enabled:
1315            // MEMRA_IQ_FAST=0 (the Stage-A oracle rollback) must also kill this arm so the
1316            // rollback stays a full-path seam. in_f % 256: MMQ_ITER_K walks whole superblocks.
1317            GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_IQ4_XS => {
1318                mmq_iq4xs_enabled()
1319                    && Self::iq_fast_enabled()
1320                    && w.in_features().is_multiple_of(256)
1321            }
1322            _ => false,
1323        }
1324    }
1325
1326    /// Unified vendored-MMQ dispatch: routes to the NVFP4 or Q4_K/Q5_K launcher by qtype.
1327    /// Caller MUST have checked `mmq_supports(w)`. `x` is the RAW f32 activation.
1328    pub fn qmatvec_mmq(
1329        &self,
1330        w: &crate::model::GpuTensor,
1331        x: &CudaSlice<f32>,
1332        m: usize,
1333    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1334        use crate::model::GpuTensor;
1335        let (in_f, out_f) = (w.in_features(), w.out_features());
1336        let GpuTensor::Quant {
1337            bytes,
1338            scale,
1339            qtype,
1340            rp,
1341            ..
1342        } = w
1343        else {
1344            return Err("qmatvec_mmq: not a Quant tensor".into());
1345        };
1346        // NVFP4 tile choice: W4A8 (accuracy-safe int8 pair, DEFAULT since the flip) vs W4A4
1347        // (mxf4nvf4 mma, explicit MEMRA_MMQ=1 speed/accuracy tradeoff). An rp weight ALWAYS takes
1348        // W4A8 — only its loader has the split-plane arm (pure address remap, bit-identical).
1349        // Explicit MEMRA_MMQ_W4A8=1 still overrides a simultaneous MEMRA_MMQ=1 (predecessor rule).
1350        let w4a8_explicit = std::env::var("MEMRA_MMQ_W4A8")
1351            .map(|v| v != "0")
1352            .unwrap_or(false);
1353        let use_w4a8 =
1354            *rp || w4a8_explicit || (mmq_w4a8_enabled() && std::env::var("MEMRA_MMQ").is_err());
1355        match *qtype {
1356            // STAGE 2: the accuracy-safe int8 W4A8 MMQ tile (weight FP4->int8 dequant + q8_1
1357            // activation) — handles BOTH weight layouts (rp = A6 split-plane vs GGUF blocks).
1358            q if q == crate::QT_NVFP4 && use_w4a8 => {
1359                self.qmatvec_mmq_nvfp4_w4a8(bytes, x, m, in_f, out_f, *scale, *rp)
1360            }
1361            q if q == crate::QT_NVFP4 => self.qmatvec_mmq_nvfp4(bytes, x, m, in_f, out_f, *scale),
1362            q if q == crate::QT_Q4_K || q == crate::QT_Q5_K => {
1363                let mut y = self.qmatvec_mmq_q45k_raw(bytes, x, m, in_f, out_f, q)?;
1364                if *scale != 1.0 {
1365                    self.scale_inplace(&mut y, *scale, m * out_f)?;
1366                }
1367                Ok(y)
1368            }
1369            q if q == crate::QT_Q8_0 => {
1370                // wgmma arm (sm_90a, task 8): OPT-IN via MEMRA_WGMMA=1 — v0 measured 3845
1371                // vs MMQ 8692 tok/s pp512 (2026-07-26 N=5), so MMQ stays the default until
1372                // the pipelined wgmma wins. Reads the rp4 split-plane mirror + the engine's
1373                // q8_1 activation planes. Same numeric class as MMQ (exact s32 per 32-block,
1374                // one f32 fold per block, ascending K) — kernel-check tolerance-gated.
1375                if cfg!(memra_hopper_mma)
1376                    && out_f % 64 == 0
1377                    && crate::wgmma_gemm_enabled()
1378                    && let GpuTensor::Quant { rp4: Some(m4), .. } = w
1379                {
1380                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
1381                    let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, &aq, &ad, m, in_f, out_f)?;
1382                    if *scale != 1.0 {
1383                        self.scale_inplace(&mut y, *scale, m * out_f)?;
1384                    }
1385                    return Ok(y);
1386                }
1387                let mut y = self.qmatvec_mmq_q8_0_raw(bytes, x, m, in_f, out_f)?;
1388                if *scale != 1.0 {
1389                    self.scale_inplace(&mut y, *scale, m * out_f)?;
1390                }
1391                Ok(y)
1392            }
1393            q if q == crate::QT_Q4_0 => {
1394                let mut y = self.qmatvec_mmq_q4_0_raw(bytes, x, m, in_f, out_f, *rp)?;
1395                if *scale != 1.0 {
1396                    self.scale_inplace(&mut y, *scale, m * out_f)?;
1397                }
1398                Ok(y)
1399            }
1400            q if q == crate::QT_IQ4_XS => {
1401                let GpuTensor::Quant { row_bytes, .. } = w else {
1402                    unreachable!()
1403                };
1404                let mut y = self.qmatvec_mmq_iq4xs_raw(bytes, x, m, in_f, out_f, *row_bytes)?;
1405                if *scale != 1.0 {
1406                    self.scale_inplace(&mut y, *scale, m * out_f)?;
1407                }
1408                Ok(y)
1409            }
1410            q => Err(format!("qmatvec_mmq: unsupported qtype {q}").into()),
1411        }
1412    }
1413
1414    /// Bare IQ4_XS dense MMQ launch (no macro-scale) — also the kernel_check gate entry.
1415    pub fn qmatvec_mmq_iq4xs_raw(
1416        &self,
1417        bytes: &CudaSlice<u8>,
1418        x: &CudaSlice<f32>,
1419        m: usize,
1420        in_f: usize,
1421        out_f: usize,
1422        row_bytes: usize,
1423    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1424        assert!(
1425            in_f.is_multiple_of(256),
1426            "MMQ IQ4_XS requires in_f % 256 == 0, got {in_f}"
1427        );
1428        let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, m as i32) };
1429        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1430        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1431        {
1432            let stream = self.gpu.stream();
1433            let (w_p, _gw) = bytes.device_ptr(&stream);
1434            let (x_p, _gx) = x.device_ptr(&stream);
1435            let (y_p, _gy) = y.device_ptr_mut(&stream);
1436            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1437            let rc = unsafe {
1438                memra_mmq_iq4xs_dense(
1439                    w_p as *const core::ffi::c_void,
1440                    x_p as *const f32,
1441                    y_p as *mut f32,
1442                    in_f as i32,
1443                    out_f as i32,
1444                    m as i32,
1445                    row_bytes as i64,
1446                    s_p as *mut core::ffi::c_void,
1447                    stream.cu_stream() as *mut core::ffi::c_void,
1448                )
1449            };
1450            if rc != 0 {
1451                return Err(format!("memra_mmq_iq4xs_dense rc={rc}").into());
1452            }
1453        }
1454        Ok(y)
1455    }
1456
1457    /// Bare Q4_K/Q5_K MMQ launch (no macro-scale) — also the kernel_check accuracy-gate entry.
1458    /// Conventional xy-tiling only (the vendored stream-K arm — MEMRA_MMQ_STREAMK — was removed
1459    /// 2026-07-08: 1.11x per-GEMM but its k-split f32 reorder flipped the model argmax gate;
1460    /// rig5090.jsonl 2026-07-03 has the record).
1461    pub fn qmatvec_mmq_q45k_raw(
1462        &self,
1463        bytes: &CudaSlice<u8>,
1464        x: &CudaSlice<f32>,
1465        m: usize,
1466        in_f: usize,
1467        out_f: usize,
1468        qtype: i32,
1469    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1470        assert!(
1471            in_f.is_multiple_of(256),
1472            "MMQ Q4_K/Q5_K requires in_f % 256 == 0, got {in_f}"
1473        );
1474        let act_bytes = unsafe { memra_mmq_q45k_act_bytes(in_f as i32, m as i32) };
1475        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1476        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1477        {
1478            let stream = self.gpu.stream();
1479            let (w_p, _gw) = bytes.device_ptr(&stream);
1480            let (x_p, _gx) = x.device_ptr(&stream);
1481            let (y_p, _gy) = y.device_ptr_mut(&stream);
1482            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1483            let launcher = if qtype == crate::QT_Q4_K {
1484                memra_mmq_q4_K
1485            } else {
1486                memra_mmq_q5_K
1487            };
1488            let rc = unsafe {
1489                launcher(
1490                    w_p as *const core::ffi::c_void,
1491                    x_p as *const f32,
1492                    y_p as *mut f32,
1493                    in_f as i32,
1494                    out_f as i32,
1495                    m as i32,
1496                    s_p as *mut core::ffi::c_void,
1497                    stream.cu_stream() as *mut core::ffi::c_void,
1498                )
1499            };
1500            if rc != 0 {
1501                return Err(format!("memra_mmq_q45k(qtype={qtype}) rc={rc}").into());
1502            }
1503        }
1504        Ok(y)
1505    }
1506
1507    /// Bare Q8_0 int8-MMA MMQ launch (no macro-scale) — the kernel_check accuracy-gate entry and
1508    /// the `qmatvec_mmq` dispatch body. Conventional xy-tiling only (no stream-K / fixup scratch).
1509    pub fn qmatvec_mmq_q8_0_raw(
1510        &self,
1511        bytes: &CudaSlice<u8>,
1512        x: &CudaSlice<f32>,
1513        m: usize,
1514        in_f: usize,
1515        out_f: usize,
1516    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1517        assert!(
1518            in_f.is_multiple_of(32),
1519            "MMQ Q8_0 requires in_f % 32 == 0, got {in_f}"
1520        );
1521        let act_bytes = unsafe { memra_mmq_q8_0_act_bytes(in_f as i32, m as i32) };
1522        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1523        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1524        {
1525            let stream = self.gpu.stream();
1526            let (w_p, _gw) = bytes.device_ptr(&stream);
1527            let (x_p, _gx) = x.device_ptr(&stream);
1528            let (y_p, _gy) = y.device_ptr_mut(&stream);
1529            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1530            let rc = unsafe {
1531                memra_mmq_q8_0(
1532                    w_p as *const core::ffi::c_void,
1533                    x_p as *const f32,
1534                    y_p as *mut f32,
1535                    in_f as i32,
1536                    out_f as i32,
1537                    m as i32,
1538                    s_p as *mut core::ffi::c_void,
1539                    stream.cu_stream() as *mut core::ffi::c_void,
1540                )
1541            };
1542            if rc != 0 {
1543                return Err(format!("memra_mmq_q8_0 rc={rc}").into());
1544            }
1545        }
1546        Ok(y)
1547    }
1548
1549    /// Accumulator-instrument bytes for a pre-quantized block_q8_1_mmq activation buffer
1550    /// (cu/mmq_q8_0_f32acc.cu). The caller synthesizes that buffer itself — see `accprobe_gemm`.
1551    pub fn accprobe_act_bytes(&self, in_f: usize, m: usize) -> usize {
1552        unsafe { memra_accprobe_act_bytes(in_f as i32, m as i32) }
1553    }
1554
1555    /// Run one arm of the Q1 accumulator instrument. `f32acc=false` is the Q8_0 MMQ floor's GEMM
1556    /// verbatim (s32 accumulate); `f32acc=true` is the byte-identical kernel with the f8f6f4 f32
1557    /// accumulate. `act_q` is a PRE-QUANTIZED block_q8_1_mmq buffer of at least
1558    /// `accprobe_act_bytes(in_f, m)` bytes — keeping the quantizer out of the timed region is the
1559    /// point, so this wrapper does not build it. Research instrument: the output is not a numeric
1560    /// claim.
1561    pub fn accprobe_gemm(
1562        &self,
1563        w_q8_0: &CudaSlice<u8>,
1564        act_q: &CudaSlice<u8>,
1565        m: usize,
1566        in_f: usize,
1567        out_f: usize,
1568        f32acc: bool,
1569    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1570        assert!(
1571            in_f.is_multiple_of(32),
1572            "accprobe requires in_f % 32 == 0, got {in_f}"
1573        );
1574        assert!(
1575            act_q.len() >= self.accprobe_act_bytes(in_f, m),
1576            "accprobe act_q too small: {} < {}",
1577            act_q.len(),
1578            self.accprobe_act_bytes(in_f, m)
1579        );
1580        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1581        {
1582            let stream = self.gpu.stream();
1583            let (w_p, _gw) = w_q8_0.device_ptr(&stream);
1584            let (a_p, _ga) = act_q.device_ptr(&stream);
1585            let (y_p, _gy) = y.device_ptr_mut(&stream);
1586            let f = if f32acc {
1587                memra_accprobe_gemm_f32
1588            } else {
1589                memra_accprobe_gemm_s32
1590            };
1591            let rc = unsafe {
1592                f(
1593                    w_p as *const core::ffi::c_void,
1594                    a_p as *const core::ffi::c_void,
1595                    y_p as *mut f32,
1596                    in_f as i32,
1597                    out_f as i32,
1598                    m as i32,
1599                    stream.cu_stream() as *mut core::ffi::c_void,
1600                )
1601            };
1602            if rc != 0 {
1603                let arm = if f32acc { "f32" } else { "s32" };
1604                return Err(format!("memra_accprobe_gemm_{arm} rc={rc}").into());
1605            }
1606        }
1607        Ok(y)
1608    }
1609
1610    /// Open a quantize-once sharing window for the NEXT activation (quantize-once seam): sibling
1611    /// Q4_0 MMQ matmuls on the SAME input (q/k/v; gate/up) quantize its D4 scratch once. Safe by
1612    /// construction: a hit requires the same window epoch AND the same (ptr, m, in_f) — the caller
1613    /// opens a window while it holds the shared input alive, so its address can neither change nor
1614    /// be recycled inside the window. Paths that never call this never hit the cache.
1615    pub fn mmq_act_begin(&self) {
1616        use std::sync::atomic::Ordering;
1617        MMQ_ACT_EPOCH.fetch_add(1, Ordering::Relaxed);
1618        *MMQ_ACT_SLOT.lock().unwrap() = None;
1619    }
1620
1621    /// Bare Q4_0 int8-MMA MMQ launch (no macro-scale) — the kernel_check accuracy-gate entry and
1622    /// the `qmatvec_mmq` dispatch body. `rp` selects the weight layout (MEMRA_Q4RP split-plane vs
1623    /// raw ggml 18B blocks) — pure address remap, bit-identical output.
1624    pub fn qmatvec_mmq_q4_0_raw(
1625        &self,
1626        bytes: &CudaSlice<u8>,
1627        x: &CudaSlice<f32>,
1628        m: usize,
1629        in_f: usize,
1630        out_f: usize,
1631        rp: bool,
1632    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1633        use std::sync::atomic::Ordering;
1634        assert!(
1635            in_f.is_multiple_of(32),
1636            "MMQ Q4_0 requires in_f % 32 == 0, got {in_f}"
1637        );
1638        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1639        let stream = self.gpu.stream();
1640        let (x_p, _gx) = x.device_ptr(&stream);
1641        let epoch = MMQ_ACT_EPOCH.load(Ordering::Relaxed);
1642        // quantize-once: reuse the window's scratch when the SAME activation comes back.
1643        let mut slot = MMQ_ACT_SLOT.lock().unwrap();
1644        let hit = matches!(&*slot,
1645            Some((e, p, mm, inf, _)) if *e == epoch && *p == x_p && *mm == m && *inf == in_f);
1646        if !hit {
1647            let act_bytes = unsafe { memra_mmq_q4_0_act_bytes(in_f as i32, m as i32) };
1648            let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1649            {
1650                let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1651                let rc = unsafe {
1652                    memra_mmq_q4_0_quant_act(
1653                        x_p as *const f32,
1654                        s_p as *mut core::ffi::c_void,
1655                        in_f as i32,
1656                        m as i32,
1657                        stream.cu_stream() as *mut core::ffi::c_void,
1658                    )
1659                };
1660                if rc != 0 {
1661                    return Err(
1662                        format!("memra_mmq_q4_0_quant_act(in_f={in_f}, m={m}) rc={rc}").into(),
1663                    );
1664                }
1665            }
1666            *slot = Some((epoch, x_p, m, in_f, scratch));
1667        }
1668        let scratch = &slot.as_ref().unwrap().4;
1669        {
1670            let (w_p, _gw) = bytes.device_ptr(&stream);
1671            let (y_p, _gy) = y.device_ptr_mut(&stream);
1672            let (s_p, _gs) = scratch.device_ptr(&stream);
1673            // Stream-k arm (DEFAULT since 2026-07-23; MEMRA_MMQ_SK=0 reverts to xy-tiling):
1674            // small-batch tail-wave fix — the sk entry itself falls back to (bit-identical)
1675            // tiling at >=90% wave efficiency. Band-class fold order below that. Gate: 12B
1676            // pp512 +3.3% (1.005x vs llama), pp1736 +1.0%; 31B +0.5%; D512 sentinel MATCH.
1677            //
1678            // SPEC-SERVING FLIP (2026-07-27, the f16pv/wkv acceptance-law pattern): with
1679            // MEMRA_DRAFT set, big dense models force tiling while MoE/small models defer
1680            // to the fail-closed TILE form. The former shape-timing autotune was removed 2026-08-14:
1681            // its per-process timing coin selected different fold orders on independent
1682            // boots. On the measured 82-SM 5090, TILE is both faster and higher-acceptance
1683            // for the 26B depth cell. Every other hardware class requires its own gate
1684            // before selecting SK without an explicit form override.
1685            // MEMRA_MMQ_SK controls entry and MEMRA_MMQ_SK_FORM pins the numerical form.
1686            // HOPPER DEFAULT OFF (2026-07-31, #23): on sm_90a the SK arm computes WRONG
1687            // values for the 26B a4b's non-rp Q4_0 shapes once the prefill width crosses
1688            // 256 (prefill argmax garbage, maxdiff ~10; MEMRA_MMQ_SK=0 -> MATCH,
1689            // one-variable kill x confirmed on-box). The SK split/fixup is SM-count
1690            // dependent (132 vs 170) — until the kernel is
1691            // fixed for that class, Hopper fails CLOSED to the bit-identical xy-tiling
1692            // (cost on the healthy models: g12 -1.4%, g31 -0.6% prefill, N=3 on-box).
1693            // sm_120a keeps the SK entry on (rig-divergence law). MEMRA_MMQ_SK=1 forces
1694            // entry; MEMRA_MMQ_SK_FORM=sk forces the actual SK numerical form.
1695            static SK_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1696            let sk = match crate::MMQ_SK_FORCE.load(std::sync::atomic::Ordering::Relaxed) {
1697                0 => false,
1698                1 => true,
1699                _ => *SK_ON.get_or_init(|| {
1700                    std::env::var("MEMRA_MMQ_SK")
1701                        .map(|v| v != "0")
1702                        .unwrap_or(!cfg!(memra_hopper_mma))
1703                }),
1704            };
1705            let rc = if sk {
1706                let mut fx = MMQ_FIXUP_SLOT.lock().unwrap();
1707                if fx.is_none() {
1708                    let nb = unsafe { memra_mmq_q4_0_fixup_bytes() };
1709                    *fx = Some(self.alloc_uninit::<u8>(nb)?);
1710                }
1711                let (f_p, _gf) = fx.as_mut().unwrap().device_ptr_mut(&stream);
1712                unsafe {
1713                    memra_mmq_q4_0_gemm_sk(
1714                        w_p as *const core::ffi::c_void,
1715                        s_p as *const core::ffi::c_void,
1716                        y_p as *mut f32,
1717                        f_p as *mut core::ffi::c_void,
1718                        in_f as i32,
1719                        out_f as i32,
1720                        m as i32,
1721                        stream.cu_stream() as *mut core::ffi::c_void,
1722                        rp as i32,
1723                    )
1724                }
1725            } else {
1726                unsafe {
1727                    memra_mmq_q4_0_gemm(
1728                        w_p as *const core::ffi::c_void,
1729                        s_p as *const core::ffi::c_void,
1730                        y_p as *mut f32,
1731                        in_f as i32,
1732                        out_f as i32,
1733                        m as i32,
1734                        stream.cu_stream() as *mut core::ffi::c_void,
1735                        rp as i32,
1736                    )
1737                }
1738            };
1739            if rc != 0 {
1740                return Err(format!(
1741                    "memra_mmq_q4_0_gemm(rp={rp}, in_f={in_f}, out_f={out_f}, m={m}, wbytes={}) rc={rc}",
1742                    bytes.len()
1743                )
1744                .into());
1745            }
1746        }
1747        Ok(y)
1748    }
1749
1750    /// Run the vendored NVFP4 MMQ prefill GEMM from raw weight bytes + f32 activation.
1751    /// y[m, out_f] = x[m, in_f] @ W^T. The per-tensor NVFP4 macro-scale is FOLDED into the MMQ
1752    /// write-back epilogue (was a separate scale_inplace launch + full y round-trip per matmul).
1753    /// Same elementwise multiply -> bit-identical to the two-launch form.
1754    /// `x` is the RAW f32 activation (the launcher quantizes it to block_fp4_mmq internally).
1755    pub fn qmatvec_mmq_nvfp4(
1756        &self,
1757        bytes: &CudaSlice<u8>,
1758        x: &CudaSlice<f32>,
1759        m: usize,
1760        in_f: usize,
1761        out_f: usize,
1762        scale: f32,
1763    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1764        self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, scale)
1765    }
1766
1767    /// Bare MMQ launch (no macro-scale) — for the kernel_check accuracy gate.
1768    pub fn qmatvec_mmq_nvfp4_raw(
1769        &self,
1770        bytes: &CudaSlice<u8>,
1771        x: &CudaSlice<f32>,
1772        m: usize,
1773        in_f: usize,
1774        out_f: usize,
1775    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1776        self.qmatvec_mmq_nvfp4_scaled(bytes, x, m, in_f, out_f, 1.0)
1777    }
1778
1779    /// Bare MMQ launch on the PRE-PORT activation quantizer (per-sub-block UE4M3 scale only, no
1780    /// per-token row amax). The numeric oracle for the two-level quantizer: kernel-check runs both
1781    /// and reports the accuracy delta, so the port's value is measured rather than asserted.
1782    pub fn qmatvec_mmq_nvfp4_raw_v1(
1783        &self,
1784        bytes: &CudaSlice<u8>,
1785        x: &CudaSlice<f32>,
1786        m: usize,
1787        in_f: usize,
1788        out_f: usize,
1789    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1790        self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, false, 0)
1791    }
1792
1793    /// Bare MMQ launch with an explicit residual-channel count — for the kernel-check k sweep.
1794    pub fn qmatvec_mmq_nvfp4_raw_res(
1795        &self,
1796        bytes: &CudaSlice<u8>,
1797        x: &CudaSlice<f32>,
1798        m: usize,
1799        in_f: usize,
1800        out_f: usize,
1801        residual_k: i32,
1802    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1803        self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, 1.0, true, residual_k)
1804    }
1805
1806    fn qmatvec_mmq_nvfp4_scaled(
1807        &self,
1808        bytes: &CudaSlice<u8>,
1809        x: &CudaSlice<f32>,
1810        m: usize,
1811        in_f: usize,
1812        out_f: usize,
1813        scale: f32,
1814    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1815        self.qmatvec_mmq_nvfp4_inner(bytes, x, m, in_f, out_f, scale, true, mmq_residual_k())
1816    }
1817
1818    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1819    fn qmatvec_mmq_nvfp4_inner(
1820        &self,
1821        bytes: &CudaSlice<u8>,
1822        x: &CudaSlice<f32>,
1823        m: usize,
1824        in_f: usize,
1825        out_f: usize,
1826        scale: f32,
1827        per_token_scale: bool,
1828        residual_k: i32,
1829    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1830        assert!(
1831            in_f.is_multiple_of(64),
1832            "MMQ NVFP4 requires in_f % 64 == 0, got {in_f}"
1833        );
1834        let act_bytes = unsafe { memra_mmq_nvfp4_act_bytes(in_f as i32, m as i32) };
1835        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1836        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1837        {
1838            let stream = self.gpu.stream();
1839            let (w_p, _gw) = bytes.device_ptr(&stream);
1840            let (x_p, _gx) = x.device_ptr(&stream);
1841            let (y_p, _gy) = y.device_ptr_mut(&stream);
1842            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1843            let rc = unsafe {
1844                memra_mmq_nvfp4_ex2(
1845                    w_p as *const core::ffi::c_void,
1846                    x_p as *const f32,
1847                    y_p as *mut f32,
1848                    in_f as i32,
1849                    out_f as i32,
1850                    m as i32,
1851                    s_p as *mut core::ffi::c_void,
1852                    stream.cu_stream() as *mut core::ffi::c_void,
1853                    scale,
1854                    per_token_scale as i32,
1855                    residual_k,
1856                )
1857            };
1858            if rc != 0 {
1859                return Err(format!("memra_mmq_nvfp4_ex2 rc={rc}").into());
1860            }
1861        }
1862        Ok(y)
1863    }
1864
1865    /// STAGE 2 W4A8 MMQ NVFP4: same tile as the W4A4 path, but weight FP4 is LUT-dequantized to
1866    /// int8 at tile-load and the activation stays q8_1 int8 — the accuracy-safe rung. Macro-scale
1867    /// folded into the write-back epilogue (bit-identical to a post-matmul scale_inplace).
1868    /// `rp` selects the weight layout (A6 split-plane vs GGUF blocks) — bit-identical output.
1869    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1870    pub fn qmatvec_mmq_nvfp4_w4a8(
1871        &self,
1872        bytes: &CudaSlice<u8>,
1873        x: &CudaSlice<f32>,
1874        m: usize,
1875        in_f: usize,
1876        out_f: usize,
1877        scale: f32,
1878        rp: bool,
1879    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1880        self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, scale, rp)
1881    }
1882
1883    /// Bare W4A8 MMQ launch (no macro-scale, GGUF layout) — for the kernel_check accuracy gate.
1884    pub fn qmatvec_mmq_nvfp4_w4a8_raw(
1885        &self,
1886        bytes: &CudaSlice<u8>,
1887        x: &CudaSlice<f32>,
1888        m: usize,
1889        in_f: usize,
1890        out_f: usize,
1891    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1892        self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, false)
1893    }
1894
1895    /// Bare W4A8 MMQ launch on an A6 split-plane repacked weight — the rp-loader bit-identity gate
1896    /// compares this against `qmatvec_mmq_nvfp4_w4a8_raw` on the same weight.
1897    pub fn qmatvec_mmq_nvfp4_w4a8_raw_rp(
1898        &self,
1899        bytes: &CudaSlice<u8>,
1900        x: &CudaSlice<f32>,
1901        m: usize,
1902        in_f: usize,
1903        out_f: usize,
1904    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1905        self.qmatvec_mmq_nvfp4_w4a8_scaled(bytes, x, m, in_f, out_f, 1.0, true)
1906    }
1907
1908    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1909    fn qmatvec_mmq_nvfp4_w4a8_scaled(
1910        &self,
1911        bytes: &CudaSlice<u8>,
1912        x: &CudaSlice<f32>,
1913        m: usize,
1914        in_f: usize,
1915        out_f: usize,
1916        scale: f32,
1917        rp: bool,
1918    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1919        assert!(
1920            in_f.is_multiple_of(64),
1921            "MMQ NVFP4 W4A8 requires in_f % 64 == 0, got {in_f}"
1922        );
1923        let act_bytes = unsafe { memra_mmq_nvfp4_w4a8_act_bytes(in_f as i32, m as i32) };
1924        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
1925        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
1926        {
1927            let stream = self.gpu.stream();
1928            let (w_p, _gw) = bytes.device_ptr(&stream);
1929            let (x_p, _gx) = x.device_ptr(&stream);
1930            let (y_p, _gy) = y.device_ptr_mut(&stream);
1931            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
1932            // MEMRA_MMQ_F8F4=1: the R-B W4A8-FP8 tile (own numeric config; battery-gated seam).
1933            // Scratch layouts are footprint-identical, so only the entry point swaps.
1934            static F8F4: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1935            let f8f4 = *F8F4.get_or_init(|| std::env::var("MEMRA_MMQ_F8F4").as_deref() == Ok("1"));
1936            let rc = unsafe {
1937                if f8f4 {
1938                    memra_mmq_nvfp4_f8f4(
1939                        w_p as *const core::ffi::c_void,
1940                        x_p as *const f32,
1941                        y_p as *mut f32,
1942                        in_f as i32,
1943                        out_f as i32,
1944                        m as i32,
1945                        s_p as *mut core::ffi::c_void,
1946                        stream.cu_stream() as *mut core::ffi::c_void,
1947                        scale,
1948                        rp as i32,
1949                    )
1950                } else {
1951                    memra_mmq_nvfp4_w4a8(
1952                        w_p as *const core::ffi::c_void,
1953                        x_p as *const f32,
1954                        y_p as *mut f32,
1955                        in_f as i32,
1956                        out_f as i32,
1957                        m as i32,
1958                        s_p as *mut core::ffi::c_void,
1959                        stream.cu_stream() as *mut core::ffi::c_void,
1960                        scale,
1961                        rp as i32,
1962                    )
1963                }
1964            };
1965            if rc != 0 {
1966                return Err(format!("memra_mmq_nvfp4_w4a8(f8f4={f8f4}) rc={rc}").into());
1967            }
1968        }
1969        Ok(y)
1970    }
1971
1972    /// PER-BLOCK FP8 MMQ prefill GEMM (cu/mmq_fp8_blk.cu). `w_e4m3` is the raw checkpoint e4m3
1973    /// plane [out_f x in_f] and `blk_scales` the device f32 grid [ceil(out_f/128) x
1974    /// ceil(in_f/128)] — no re-quantization of either.
1975    pub fn qmatvec_mmq_fp8_blk(
1976        &self,
1977        w_e4m3: &CudaSlice<u8>,
1978        blk_scales: &CudaSlice<f32>,
1979        x: &CudaSlice<f32>,
1980        m: usize,
1981        in_f: usize,
1982        out_f: usize,
1983    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1984        self.qmatvec_mmq_fp8_blk_scaled(w_e4m3, blk_scales, x, m, in_f, out_f, 1.0)
1985    }
1986
1987    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1988    pub fn qmatvec_mmq_fp8_blk_scaled(
1989        &self,
1990        w_e4m3: &CudaSlice<u8>,
1991        blk_scales: &CudaSlice<f32>,
1992        x: &CudaSlice<f32>,
1993        m: usize,
1994        in_f: usize,
1995        out_f: usize,
1996        scale: f32,
1997    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1998        assert!(
1999            in_f.is_multiple_of(16),
2000            "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
2001        );
2002        #[allow(clippy::manual_div_ceil)]
2003        // allow: explicit (n + k - 1) / k is the load-bearing sizing form, kept textually identical to the kernel-side math
2004        let want_scales = ((out_f + 127) / 128) * ((in_f + 127) / 128);
2005        assert!(
2006            blk_scales.len() >= want_scales,
2007            "blk_scales too small: {} < {want_scales}",
2008            blk_scales.len()
2009        );
2010        assert!(
2011            w_e4m3.len() >= out_f * in_f,
2012            "e4m3 plane too small: {} < {}",
2013            w_e4m3.len(),
2014            out_f * in_f
2015        );
2016        let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2017        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2018        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2019        {
2020            let stream = self.gpu.stream();
2021            let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2022            let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2023            let (x_p, _gx) = x.device_ptr(&stream);
2024            let (y_p, _gy) = y.device_ptr_mut(&stream);
2025            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2026            let rc = unsafe {
2027                memra_mmq_fp8_blk(
2028                    w_p as *const core::ffi::c_void,
2029                    sc_p as *const f32,
2030                    x_p as *const f32,
2031                    y_p as *mut f32,
2032                    in_f as i32,
2033                    out_f as i32,
2034                    m as i32,
2035                    s_p as *mut core::ffi::c_void,
2036                    stream.cu_stream() as *mut core::ffi::c_void,
2037                    scale,
2038                )
2039            };
2040            if rc != 0 {
2041                return Err(format!("memra_mmq_fp8_blk rc={rc}").into());
2042            }
2043        }
2044        Ok(y)
2045    }
2046
2047    /// View-backed twin of `qmatvec_mmq_fp8_blk`. Resident expert banks remain in their
2048    /// layer-wide allocations while the selected expert and token rows are passed as views.
2049    /// The CUDA launcher still performs dynamic E4M3 activation quantization; no Q8 activation
2050    /// sidecar is created.
2051    pub fn qmatvec_mmq_fp8_blk_view(
2052        &self,
2053        w_e4m3: &CudaView<'_, u8>,
2054        blk_scales: &CudaView<'_, f32>,
2055        x: &CudaView<'_, f32>,
2056        m: usize,
2057        in_f: usize,
2058        out_f: usize,
2059    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2060        assert!(
2061            in_f.is_multiple_of(16),
2062            "per-block FP8 MMQ requires in_f % 16 == 0, got {in_f}"
2063        );
2064        let want_scales = out_f.div_ceil(128) * in_f.div_ceil(128);
2065        assert!(
2066            blk_scales.len() >= want_scales,
2067            "blk_scales view too small: {} < {want_scales}",
2068            blk_scales.len()
2069        );
2070        assert!(
2071            w_e4m3.len() >= out_f * in_f,
2072            "e4m3 view too small: {} < {}",
2073            w_e4m3.len(),
2074            out_f * in_f
2075        );
2076        assert!(
2077            x.len() >= m * in_f,
2078            "activation view too small: {} < {}",
2079            x.len(),
2080            m * in_f
2081        );
2082
2083        let act_bytes = unsafe { memra_mmq_fp8_blk_act_bytes(in_f as i32, m as i32) };
2084        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2085        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2086        {
2087            let stream = self.gpu.stream();
2088            let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2089            let (sc_p, _gsc) = blk_scales.device_ptr(&stream);
2090            let (x_p, _gx) = x.device_ptr(&stream);
2091            let (y_p, _gy) = y.device_ptr_mut(&stream);
2092            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2093            let rc = unsafe {
2094                memra_mmq_fp8_blk(
2095                    w_p as *const core::ffi::c_void,
2096                    sc_p as *const f32,
2097                    x_p as *const f32,
2098                    y_p as *mut f32,
2099                    in_f as i32,
2100                    out_f as i32,
2101                    m as i32,
2102                    s_p as *mut core::ffi::c_void,
2103                    stream.cu_stream() as *mut core::ffi::c_void,
2104                    1.0,
2105                )
2106            };
2107            if rc != 0 {
2108                return Err(format!("memra_mmq_fp8_blk(view) rc={rc}").into());
2109            }
2110        }
2111        Ok(y)
2112    }
2113
2114    /// Count e4m3 NaN codes (magnitude 0x7F) in a device e4m3 plane. 0 is the precondition for
2115    /// routing that tensor through `qmatvec_mmq_fp8_blk` (hardware decodes them to NaN, the
2116    /// host/ARM B' reference to 0.0).
2117    pub fn fp8_blk_nan_count(
2118        &self,
2119        w_e4m3: &CudaSlice<u8>,
2120    ) -> Result<u32, Box<dyn std::error::Error>> {
2121        let mut cnt = self.htod_u32_v(&[0u32])?;
2122        let n = w_e4m3.len();
2123        {
2124            let stream = self.gpu.stream();
2125            let (w_p, _gw) = w_e4m3.device_ptr(&stream);
2126            let (c_p, _gc) = cnt.device_ptr_mut(&stream);
2127            let rc = unsafe {
2128                memra_fp8_blk_count_nan(
2129                    w_p as *const core::ffi::c_void,
2130                    n,
2131                    c_p as *mut u32,
2132                    stream.cu_stream() as *mut core::ffi::c_void,
2133                )
2134            };
2135            if rc != 0 {
2136                return Err(format!("memra_fp8_blk_count_nan rc={rc}").into());
2137            }
2138        }
2139        Ok(self.dtoh_u32(&cnt)?[0])
2140    }
2141
2142    /// Quantize token-major f32 activation [n_tokens, in_f] to the block_q8_1_mmq (D4) scratch the
2143    /// IQ expert-MMA kernel consumes. Returns the scratch buffer (one per proj input per layer).
2144    pub fn mmq_iq_quantize_act(
2145        &self,
2146        x: &CudaSlice<f32>,
2147        in_f: usize,
2148        n_tokens: usize,
2149    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2150        let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2151        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2152        {
2153            let stream = self.gpu.stream();
2154            let (x_p, _gx) = x.device_ptr(&stream);
2155            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2156            let rc = unsafe {
2157                memra_mmq_iq_quantize_act(
2158                    x_p as *const f32,
2159                    s_p as *mut core::ffi::c_void,
2160                    in_f as i32,
2161                    n_tokens as i32,
2162                    stream.cu_stream() as *mut core::ffi::c_void,
2163                )
2164            };
2165            if rc != 0 {
2166                return Err(format!("memra_mmq_iq_quantize_act rc={rc}").into());
2167            }
2168        }
2169        Ok(scratch)
2170    }
2171
2172    /// Fused act-epilogue (research lever #3): silu/gelu(gate)*up + D4 quantize in one launch —
2173    /// replaces moe_pairs_{silu,gelu}_mul + mmq_iq_quantize_act without materializing the f32 act
2174    /// buffer (saves one full write + one full read pass over [n_pairs x n_ff]). Scratch bytes are
2175    /// BYTE-IDENTICAL to the two-pass path (kernel-check `iq fused act+quant` gates it).
2176    /// `act_kind`: 0 = silu*mul (qwen35moe), 1 = gelu_tanh*mul (gemma4).
2177    pub fn mmq_iq_fused_act_quant(
2178        &self,
2179        gate: &CudaSlice<f32>,
2180        up: &CudaSlice<f32>,
2181        in_f: usize,
2182        n_tokens: usize,
2183        act_kind: i32,
2184    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2185        let act_bytes = unsafe { memra_mmq_iq_experts_act_bytes(in_f as i32, n_tokens as i32) };
2186        let mut scratch = self.alloc_uninit::<u8>(act_bytes)?;
2187        {
2188            let stream = self.gpu.stream();
2189            let (g_p, _gg) = gate.device_ptr(&stream);
2190            let (u_p, _gu) = up.device_ptr(&stream);
2191            let (s_p, _gs) = scratch.device_ptr_mut(&stream);
2192            let rc = unsafe {
2193                memra_mmq_iq_fused_act_quant(
2194                    g_p as *const f32,
2195                    u_p as *const f32,
2196                    s_p as *mut core::ffi::c_void,
2197                    in_f as i32,
2198                    n_tokens as i32,
2199                    act_kind,
2200                    stream.cu_stream() as *mut core::ffi::c_void,
2201                )
2202            };
2203            if rc != 0 {
2204                return Err(format!("memra_mmq_iq_fused_act_quant rc={rc}").into());
2205            }
2206        }
2207        Ok(scratch)
2208    }
2209
2210    /// Expert-segmented IQ3_S/IQ4_XS int8-MMA MMQ (the m16n8k16.s8 analog of moe_pairs_matvec_q8_dec).
2211    /// Same CSR inputs (table/ex_ids/ex_off/ex_pairs/pair_tok) + a pre-quantized q8_1_mmq activation
2212    /// scratch (from `mmq_iq_quantize_act` over n_tokens). y = [n_pairs, out_f] pair-major.
2213    #[allow(clippy::too_many_arguments)]
2214    pub fn mmq_iq_experts(
2215        &self,
2216        table: &CudaSlice<u64>,
2217        proj: i32,
2218        n_expert: usize,
2219        ex_ids: &CudaSlice<i32>,
2220        ex_off: &CudaSlice<i32>,
2221        ex_pairs: &CudaSlice<i32>,
2222        pair_tok: &CudaSlice<i32>,
2223        act_scratch: &CudaSlice<u8>,
2224        in_f: usize,
2225        out_f: usize,
2226        n_active: usize,
2227        n_pairs: usize,
2228        n_tokens: usize,
2229        qtype: i32,
2230        row_bytes: usize,
2231    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2232        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2233        {
2234            let stream = self.gpu.stream();
2235            let (tab_p, _g0) = table.device_ptr(&stream);
2236            let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2237            let (eo_p, _g2) = ex_off.device_ptr(&stream);
2238            let (ep_p, _g3) = ex_pairs.device_ptr(&stream);
2239            let (pt_p, _g4) = pair_tok.device_ptr(&stream);
2240            let (as_p, _g5) = act_scratch.device_ptr(&stream);
2241            let (y_p, _g6) = y.device_ptr_mut(&stream);
2242            let rc = unsafe {
2243                memra_mmq_iq_experts(
2244                    tab_p as *const u64,
2245                    proj,
2246                    n_expert as i32,
2247                    ei_p as *const i32,
2248                    eo_p as *const i32,
2249                    ep_p as *const i32,
2250                    pt_p as *const i32,
2251                    as_p as *const core::ffi::c_void,
2252                    y_p as *mut f32,
2253                    in_f as i32,
2254                    out_f as i32,
2255                    n_active as i32,
2256                    n_tokens as i32,
2257                    qtype,
2258                    row_bytes as i64,
2259                    stream.cu_stream() as *mut core::ffi::c_void,
2260                )
2261            };
2262            if rc != 0 {
2263                return Err(format!("memra_mmq_iq_experts rc={rc}").into());
2264            }
2265        }
2266        Ok(y)
2267    }
2268
2269    /// Gather+convert the activation to f16 pair-major [n_pairs, in_f] for the grouped
2270    /// GEMM, normalized per row by its amax (raw f16 overflows on gemma's activation
2271    /// spikes — round 46 NaN find). Returns (act_f16, row_scales) — the scales fold back
2272    /// into the GEMM output. `pair_tok` = None when the input is already pair-major.
2273    pub fn moe_f16g_act(
2274        &self,
2275        x: &CudaSlice<f32>,
2276        pair_tok: Option<&CudaSlice<i32>>,
2277        in_f: usize,
2278        n_pairs: usize,
2279    ) -> Result<(CudaSlice<u8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2280        let mut act = self.alloc_uninit::<u8>(n_pairs * in_f * 2)?;
2281        let mut scales = self.alloc_uninit::<f32>(n_pairs)?;
2282        {
2283            let stream = self.gpu.stream();
2284            let (x_p, _gx) = x.device_ptr(&stream);
2285            let pt_p = match pair_tok {
2286                Some(pt) => {
2287                    let (p, _g) = pt.device_ptr(&stream);
2288                    p as *const i32
2289                }
2290                None => std::ptr::null(),
2291            };
2292            let (a_p, _ga) = act.device_ptr_mut(&stream);
2293            let (s_p, _gs) = scales.device_ptr_mut(&stream);
2294            let rc = unsafe {
2295                memra_moe_f16g_gather_act(
2296                    x_p as *const f32,
2297                    pt_p,
2298                    a_p as *mut core::ffi::c_void,
2299                    s_p as *mut f32,
2300                    in_f as i32,
2301                    n_pairs as i32,
2302                    stream.cu_stream() as *mut core::ffi::c_void,
2303                )
2304            };
2305            if rc != 0 {
2306                return Err(format!("memra_moe_f16g_gather_act rc={rc}").into());
2307            }
2308        }
2309        Ok((act, scales))
2310    }
2311
2312    /// One projection through the grouped f16 lane: dequant the active experts' rows to an
2313    /// f16 workspace, then ONE grouped GEMM over the CSR groups (variable m per expert).
2314    /// y = f32 [n_pairs, out_f] pair-major — same layout as mmq_iq_experts.
2315    /// MEMRA_MOE_F16G=1: cublasGemmGroupedBatchedEx (+ h2f pass + per-projection sync — the
2316    /// grouped API runs on internal streams unordered with ours, round-47 ledger).
2317    /// MEMRA_MOE_F16G=2: single-kernel grouped GEMM on the engine stream (round 49) — the
2318    /// row scale folds into the kernel epilogue; no f16 C, no h2f, NO sync (ordered by
2319    /// construction). f16-MIRROR numeric class either way (argmax/spec gated, not
2320    /// byte-identity). Errors on unsupported qtype (caller keeps the MMQ arm as fallback).
2321    #[allow(clippy::too_many_arguments)]
2322    /// Bind the RUNTIME API's current device to `ordinal`. Every raw `<<<>>>` launch in the
2323    /// grouped-MoE FFI follows this, not cudarc's pushed driver context — mandatory before
2324    /// calling the FFI on a non-root rank engine (the TP2 grouped prime), a mismatch is
2325    /// cudaErrorInvalidValue.
2326    pub fn bind_runtime_device(&self, ordinal: i32) -> Result<(), Box<dyn std::error::Error>> {
2327        let rc = unsafe { memra_bind_device(ordinal) };
2328        if rc != 0 {
2329            return Err(format!("cudaSetDevice({ordinal}) rc={rc}").into());
2330        }
2331        Ok(())
2332    }
2333
2334    #[allow(clippy::too_many_arguments)]
2335    // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
2336    #[allow(clippy::manual_is_multiple_of)] // allow: divisor is runtime-derived; the modulo form keeps a zero divisor loud (a panic), where is_multiple_of would return false silently
2337    pub fn moe_f16_grouped(
2338        &self,
2339        table: &CudaSlice<u64>,
2340        proj: i32,
2341        n_expert: usize,
2342        ex_ids: &CudaSlice<i32>,
2343        ex_off_host: &[i32],
2344        ex_off_dev: &CudaSlice<i32>,
2345        act_f16: &CudaSlice<u8>,
2346        act_scale: &CudaSlice<f32>,
2347        in_f: usize,
2348        out_f: usize,
2349        n_active: usize,
2350        n_pairs: usize,
2351        qtype: i32,
2352        row_bytes: usize,
2353    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2354        let sk = crate::moe_f16g_mode() >= 2 && in_f.is_multiple_of(32);
2355        // DIRECT-FROM-QUANT lane (lane/kquant-tile-loaders + lane/iq-direct-loaders, default
2356        // ON — MEMRA_F16G_DIRECT=0 is the rollback seam): Q4_K/Q6_K/IQ4_XS/IQ3_S expert
2357        // projections skip the dequant-workspace pass entirely; the sk visitor forms dequant
2358        // B tiles in-register from the superblocks. Bit-identical to the workspace path by
2359        // construction (kernel-check "f16g-kq-direct") — this is a pure data-movement change,
2360        // not a numeric-class change. Admission mirrors the C-side guards; the grid-scan
2361        // rollback arm (MEMRA_F16G_SK=0) keeps the workspace.
2362        let (shape_sel, cross) = crate::moe_f16g_sk_params();
2363        if sk
2364            && shape_sel >= 0
2365            && crate::moe_f16g_direct_on(qtype)
2366            && (qtype == crate::QT_Q4_K
2367                || qtype == crate::QT_Q6_K
2368                || qtype == crate::QT_IQ4_XS
2369                || qtype == crate::QT_IQ3_S
2370                || qtype == crate::QT_NVFP4
2371                // v2 slot-major banks read through the same direct lane (kq_fetch's v2 branch),
2372                // which is what keeps the grouped prime off the 1.5 GB/projection dequant
2373                // workspace it otherwise falls back to.
2374                || qtype == crate::QT_NVFP4_V2)
2375            // NVFP4 walks 64-value blocks (its 16-value window is one UE4M3 sub-block);
2376            // the kq/IQ classes walk 256-value superblocks. Mirrors the C-side guard.
2377            && in_f % (if qtype == crate::QT_NVFP4 || qtype == crate::QT_NVFP4_V2 { 64 } else { 256 }) == 0
2378            && n_active <= 512
2379            && n_active > 0
2380        {
2381            let max_m = ex_off_host
2382                .windows(2)
2383                .map(|w| w[1] - w[0])
2384                .max()
2385                .unwrap_or(0);
2386            let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2387            {
2388                let stream = self.gpu.stream();
2389                let (tab_p, _g0) = table.device_ptr(&stream);
2390                let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2391                let (a_p, _g2) = act_f16.device_ptr(&stream);
2392                let (s_p, _g3) = act_scale.device_ptr(&stream);
2393                let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2394                let (y_p, _g5) = y.device_ptr_mut(&stream);
2395                let rc = unsafe {
2396                    memra_moe_kq_gemm_sk(
2397                        tab_p as *const u64,
2398                        proj,
2399                        n_expert as i32,
2400                        ei_p as *const i32,
2401                        a_p as *const core::ffi::c_void,
2402                        y_p as *mut f32,
2403                        s_p as *const f32,
2404                        off_p as *const i32,
2405                        ex_off_host.as_ptr(),
2406                        n_active as i32,
2407                        max_m,
2408                        in_f as i32,
2409                        out_f as i32,
2410                        qtype,
2411                        cross,
2412                        crate::moe_f16g_tail_on() as i32,
2413                        row_bytes as i64,
2414                        stream.cu_stream() as *mut core::ffi::c_void,
2415                    )
2416                };
2417                if rc != 0 {
2418                    return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2419                }
2420            }
2421            return Ok(y);
2422        }
2423        // one-time cublas grouped init (algo heuristics + module load cost ~10% of a cold
2424        // g26 prime when paid inside the first projection): a tiny dummy grouped GEMM at
2425        // first use, synced, so the real prime runs warm. The =2 path never touches cublas.
2426        if !sk {
2427            static WARM: std::sync::Once = std::sync::Once::new();
2428            let mut warm_err = None;
2429            WARM.call_once(|| {
2430                let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2431                    let w = self.alloc_uninit::<u8>(2 * 32 * 64 * 2)?;
2432                    let a = self.alloc_uninit::<u8>(4 * 64 * 2)?;
2433                    let mut yw = self.alloc_uninit::<u8>(4 * 32 * 2)?;
2434                    let off = [0i32, 2, 4];
2435                    let stream = self.gpu.stream();
2436                    let (w_p, _a1) = w.device_ptr(&stream);
2437                    let (a_p, _a2) = a.device_ptr(&stream);
2438                    let (y_p, _a3) = yw.device_ptr_mut(&stream);
2439                    let rc = unsafe {
2440                        memra_moe_f16g_gemm(
2441                            w_p as *const core::ffi::c_void,
2442                            a_p as *const core::ffi::c_void,
2443                            y_p as *mut core::ffi::c_void,
2444                            off.as_ptr(),
2445                            2,
2446                            64,
2447                            32,
2448                            stream.cu_stream() as *mut core::ffi::c_void,
2449                        )
2450                    };
2451                    if rc != 0 {
2452                        return Err(format!("f16g warmup rc={rc}").into());
2453                    }
2454                    self.gpu.stream().synchronize()?;
2455                    Ok(())
2456                })();
2457                if let Err(e) = r {
2458                    warm_err = Some(e.to_string());
2459                }
2460            });
2461            if let Some(we) = warm_err {
2462                return Err(we.into());
2463            }
2464        }
2465        let w_bytes = n_active * out_f * in_f * 2;
2466        let mut w_f16 = self.alloc_uninit::<u8>(w_bytes)?;
2467        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2468        {
2469            let stream = self.gpu.stream();
2470            let (tab_p, _g0) = table.device_ptr(&stream);
2471            let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2472            let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2473            let rc = unsafe {
2474                memra_moe_f16g_dequant(
2475                    tab_p as *const u64,
2476                    proj,
2477                    n_expert as i32,
2478                    ei_p as *const i32,
2479                    w_p as *mut core::ffi::c_void,
2480                    in_f as i32,
2481                    out_f as i32,
2482                    n_active as i32,
2483                    qtype,
2484                    row_bytes as i64,
2485                    stream.cu_stream() as *mut core::ffi::c_void,
2486                )
2487            };
2488            if rc != 0 {
2489                return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2490            }
2491            let (a_p, _g3) = act_f16.device_ptr(&stream);
2492            let (s_p, _g6) = act_scale.device_ptr(&stream);
2493            let (y_p, _g5) = y.device_ptr_mut(&stream);
2494            if sk {
2495                let max_m = ex_off_host
2496                    .windows(2)
2497                    .map(|w| w[1] - w[0])
2498                    .max()
2499                    .unwrap_or(0);
2500                let (off_p, _g7) = ex_off_dev.device_ptr(&stream);
2501                let (shape_sel, cross) = crate::moe_f16g_sk_params();
2502                let rc = unsafe {
2503                    memra_moe_f16g_gemm_sk(
2504                        w_p as *const core::ffi::c_void,
2505                        a_p as *const core::ffi::c_void,
2506                        y_p as *mut f32,
2507                        s_p as *const f32,
2508                        off_p as *const i32,
2509                        ex_off_host.as_ptr(),
2510                        n_active as i32,
2511                        max_m,
2512                        in_f as i32,
2513                        out_f as i32,
2514                        shape_sel,
2515                        cross,
2516                        crate::moe_f16g_tail_on() as i32,
2517                        stream.cu_stream() as *mut core::ffi::c_void,
2518                    )
2519                };
2520                if rc != 0 {
2521                    return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2522                }
2523            } else {
2524                let mut y16 = self.alloc_uninit::<u8>(n_pairs * out_f * 2)?;
2525                let (y16_p, _g4) = y16.device_ptr_mut(&stream);
2526                let rc = unsafe {
2527                    memra_moe_f16g_gemm(
2528                        w_p as *const core::ffi::c_void,
2529                        a_p as *const core::ffi::c_void,
2530                        y16_p as *mut core::ffi::c_void,
2531                        ex_off_host.as_ptr(),
2532                        n_active as i32,
2533                        in_f as i32,
2534                        out_f as i32,
2535                        stream.cu_stream() as *mut core::ffi::c_void,
2536                    )
2537                };
2538                if rc != 0 {
2539                    return Err(format!("memra_moe_f16g_gemm rc={rc}").into());
2540                }
2541                let rc = unsafe {
2542                    memra_moe_f16g_h2f_scaled(
2543                        y16_p as *const core::ffi::c_void,
2544                        y_p as *mut f32,
2545                        s_p as *const f32,
2546                        out_f as i32,
2547                        n_pairs as i32,
2548                        stream.cu_stream() as *mut core::ffi::c_void,
2549                    )
2550                };
2551                if rc != 0 {
2552                    return Err(format!("memra_moe_f16g_h2f_scaled rc={rc}").into());
2553                }
2554            }
2555        }
2556        // MODE 1 ONLY: cublasGemmGroupedBatchedEx issues through internal streams NOT ordered
2557        // with ours (round 46: NaN race, clean under sync — 205=205 MATCH). Full sync per
2558        // projection. Mode 2 (single kernel, our stream) is ordered by construction — no sync,
2559        // that is the point of this arc.
2560        if !sk {
2561            self.gpu.stream().synchronize()?;
2562        }
2563        if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
2564            // FULL NaN/Inf scan of w, act (through h2f) and y — localizes the corrupt stage.
2565            let wn = n_active * out_f * in_f;
2566            let an = n_pairs * in_f;
2567            let mut wf = self.alloc_uninit::<f32>(wn)?;
2568            let mut af = self.alloc_uninit::<f32>(an)?;
2569            {
2570                let stream = self.gpu.stream();
2571                let (w_p, _a) = w_f16.device_ptr(&stream);
2572                let (a_p, _b) = act_f16.device_ptr(&stream);
2573                let (wf_p, _c) = wf.device_ptr_mut(&stream);
2574                let (af_p, _d) = af.device_ptr_mut(&stream);
2575                unsafe {
2576                    memra_moe_f16g_h2f(
2577                        w_p as *const core::ffi::c_void,
2578                        wf_p as *mut f32,
2579                        wn,
2580                        stream.cu_stream() as *mut core::ffi::c_void,
2581                    );
2582                    memra_moe_f16g_h2f(
2583                        a_p as *const core::ffi::c_void,
2584                        af_p as *mut f32,
2585                        an,
2586                        stream.cu_stream() as *mut core::ffi::c_void,
2587                    );
2588                }
2589            }
2590            let (wh, ah, yh) = (self.dtoh(&wf)?, self.dtoh(&af)?, self.dtoh(&y)?);
2591            let scan = |v: &[f32]| -> (usize, f32) {
2592                let bad = v.iter().filter(|x| !x.is_finite()).count();
2593                let mx = v
2594                    .iter()
2595                    .filter(|x| x.is_finite())
2596                    .fold(0.0f32, |m, x| m.max(x.abs()));
2597                (bad, mx)
2598            };
2599            let (wb, wm) = scan(&wh);
2600            let (ab, am) = scan(&ah);
2601            let (yb, ym) = scan(&yh);
2602            eprintln!(
2603                "[f16g-debug] proj={proj} w: bad={wb} max={wm:.3e} | act: bad={ab} \
2604                       max={am:.3e} | y: bad={yb} max={ym:.3e} (na={n_active} np={n_pairs} \
2605                       in={in_f} out={out_f})"
2606            );
2607        }
2608        Ok(y)
2609    }
2610
2611    /// Raw sk grouped-GEMM entry for kernel-check ("f16g-sk" section): explicit shape/cross
2612    /// instead of the env policy. shape_sel < 0 = the round-49 grid-scan rollback arm; else
2613    /// the round-51 problem-visitor split at `cross` (1 forces all-128, i32::MAX all-32).
2614    /// tail: 1 = the deep tail (32x64x64 3-stage, lane/sk-tail-form) on sub-cross groups,
2615    /// 0 = the round-51 2-stage 32x64x32 tail.
2616    /// w_f16 = [n_active][out_f][in_f] f16 bytes, act_f16 = [n_pairs][in_f] f16 bytes.
2617    #[allow(clippy::too_many_arguments)]
2618    pub fn moe_f16g_gemm_sk_raw(
2619        &self,
2620        w_f16: &CudaSlice<u8>,
2621        act_f16: &CudaSlice<u8>,
2622        row_scale: &CudaSlice<f32>,
2623        ex_off_host: &[i32],
2624        ex_off_dev: &CudaSlice<i32>,
2625        in_f: usize,
2626        out_f: usize,
2627        n_pairs: usize,
2628        shape_sel: i32,
2629        cross: i32,
2630        tail: i32,
2631    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2632        let n_active = ex_off_host.len() - 1;
2633        let max_m = ex_off_host
2634            .windows(2)
2635            .map(|w| w[1] - w[0])
2636            .max()
2637            .unwrap_or(0);
2638        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2639        {
2640            let stream = self.gpu.stream();
2641            let (w_p, _g0) = w_f16.device_ptr(&stream);
2642            let (a_p, _g1) = act_f16.device_ptr(&stream);
2643            let (s_p, _g2) = row_scale.device_ptr(&stream);
2644            let (off_p, _g3) = ex_off_dev.device_ptr(&stream);
2645            let (y_p, _g4) = y.device_ptr_mut(&stream);
2646            let rc = unsafe {
2647                memra_moe_f16g_gemm_sk(
2648                    w_p as *const core::ffi::c_void,
2649                    a_p as *const core::ffi::c_void,
2650                    y_p as *mut f32,
2651                    s_p as *const f32,
2652                    off_p as *const i32,
2653                    ex_off_host.as_ptr(),
2654                    n_active as i32,
2655                    max_m,
2656                    in_f as i32,
2657                    out_f as i32,
2658                    shape_sel,
2659                    cross,
2660                    tail,
2661                    stream.cu_stream() as *mut core::ffi::c_void,
2662                )
2663            };
2664            if rc != 0 {
2665                return Err(format!("memra_moe_f16g_gemm_sk rc={rc}").into());
2666            }
2667        }
2668        Ok(y)
2669    }
2670
2671    /// Raw direct-from-quant sk grouped-GEMM entry for kernel-check ("f16g-kq-direct"):
2672    /// explicit cross/tail instead of the env policy. `table` = device u64 pointer table
2673    /// (proj-major, [n_proj][n_expert] — same contract as moe_f16_grouped), `ex_ids` =
2674    /// active-expert ids (device). Visitor forms only (the C side rejects anything else).
2675    #[allow(clippy::too_many_arguments)]
2676    pub fn moe_kq_gemm_sk_raw(
2677        &self,
2678        table: &CudaSlice<u64>,
2679        proj: i32,
2680        n_expert: usize,
2681        ex_ids: &CudaSlice<i32>,
2682        act_f16: &CudaSlice<u8>,
2683        row_scale: &CudaSlice<f32>,
2684        ex_off_host: &[i32],
2685        ex_off_dev: &CudaSlice<i32>,
2686        in_f: usize,
2687        out_f: usize,
2688        n_pairs: usize,
2689        qtype: i32,
2690        row_bytes: usize,
2691        cross: i32,
2692        tail: i32,
2693    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2694        let n_active = ex_off_host.len() - 1;
2695        let max_m = ex_off_host
2696            .windows(2)
2697            .map(|w| w[1] - w[0])
2698            .max()
2699            .unwrap_or(0);
2700        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2701        {
2702            let stream = self.gpu.stream();
2703            let (tab_p, _g0) = table.device_ptr(&stream);
2704            let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2705            let (a_p, _g2) = act_f16.device_ptr(&stream);
2706            let (s_p, _g3) = row_scale.device_ptr(&stream);
2707            let (off_p, _g4) = ex_off_dev.device_ptr(&stream);
2708            let (y_p, _g5) = y.device_ptr_mut(&stream);
2709            let rc = unsafe {
2710                memra_moe_kq_gemm_sk(
2711                    tab_p as *const u64,
2712                    proj,
2713                    n_expert as i32,
2714                    ei_p as *const i32,
2715                    a_p as *const core::ffi::c_void,
2716                    y_p as *mut f32,
2717                    s_p as *const f32,
2718                    off_p as *const i32,
2719                    ex_off_host.as_ptr(),
2720                    n_active as i32,
2721                    max_m,
2722                    in_f as i32,
2723                    out_f as i32,
2724                    qtype,
2725                    cross,
2726                    tail,
2727                    row_bytes as i64,
2728                    stream.cu_stream() as *mut core::ffi::c_void,
2729                )
2730            };
2731            if rc != 0 {
2732                return Err(format!("memra_moe_kq_gemm_sk rc={rc}").into());
2733            }
2734        }
2735        Ok(y)
2736    }
2737
2738    /// Raw dequant-workspace entry for kernel-check: dequant the active experts' rows to a
2739    /// fresh f16 workspace via the same kernel `moe_f16_grouped` uses (the direct loaders'
2740    /// bitwise reference).
2741    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
2742    pub fn moe_f16g_dequant_raw(
2743        &self,
2744        table: &CudaSlice<u64>,
2745        proj: i32,
2746        n_expert: usize,
2747        ex_ids: &CudaSlice<i32>,
2748        in_f: usize,
2749        out_f: usize,
2750        n_active: usize,
2751        qtype: i32,
2752        row_bytes: usize,
2753    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2754        let mut w_f16 = self.alloc_uninit::<u8>(n_active * out_f * in_f * 2)?;
2755        {
2756            let stream = self.gpu.stream();
2757            let (tab_p, _g0) = table.device_ptr(&stream);
2758            let (ei_p, _g1) = ex_ids.device_ptr(&stream);
2759            let (w_p, _g2) = w_f16.device_ptr_mut(&stream);
2760            let rc = unsafe {
2761                memra_moe_f16g_dequant(
2762                    tab_p as *const u64,
2763                    proj,
2764                    n_expert as i32,
2765                    ei_p as *const i32,
2766                    w_p as *mut core::ffi::c_void,
2767                    in_f as i32,
2768                    out_f as i32,
2769                    n_active as i32,
2770                    qtype,
2771                    row_bytes as i64,
2772                    stream.cu_stream() as *mut core::ffi::c_void,
2773                )
2774            };
2775            if rc != 0 {
2776                return Err(format!("memra_moe_f16g_dequant rc={rc}").into());
2777            }
2778        }
2779        Ok(w_f16)
2780    }
2781}