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