memra_engine/model.rs
1//! Dense transformer model: loads GGUF weights to GPU (Stage-1: dequant→f32), runs the
2//! shared full-attention + SwiGLU forward graph. Arch-agnostic via ModelConfig; this path is
3//! exactly the dense-transformer graph (qwen3) and the full-attention layers of hybrids.
4
5use crate::{
6 Engine, QT_BF16, QT_F32, QT_F8_E4M3, QT_IQ3_S, QT_IQ4_XS, QT_NVFP4, QT_NVFP4_RP, QT_Q2_K,
7 QT_Q3_K, QT_Q4_0, QT_Q4_K, QT_Q5_K, QT_Q6_K, QT_Q8_0,
8};
9use memra_gguf::config::ModelConfig;
10use memra_gguf::source::{DiskExtent, GgufSource, TensorSource};
11use memra_gguf::{dequant, GgmlType, GgufFile};
12use cudarc::driver::CudaSlice;
13use std::collections::HashMap;
14
15/// RESIDENCY CENSUS (lane/fp8-decode-v1, 2026-08-05) — per-qtype tally of the 2D matmul weights
16/// that actually went resident, keyed by `QT_*`. The FP8-ST decode arm's whole claim is about
17/// WHICH container the checkpoint's projections end up in, and the two candidate containers
18/// differ in bytes (e4m3 1.0 B/w vs the Q8_0 re-encode 1.0625 B/w). Before this instrument the
19/// only evidence available was end-to-end tok/s, which cannot distinguish "the arm ran and was
20/// flat" from "the arm never engaged" — the exact ambiguity in this lane's first loadprobe pair.
21/// Slot = qtype index; `.0` = tensor count, `.1` = resident bytes.
22static RESIDENCY_CENSUS: [(std::sync::atomic::AtomicUsize, std::sync::atomic::AtomicU64); 16] = {
23 #[allow(clippy::declare_interior_mutable_const)]
24 const Z: (std::sync::atomic::AtomicUsize, std::sync::atomic::AtomicU64) =
25 (std::sync::atomic::AtomicUsize::new(0), std::sync::atomic::AtomicU64::new(0));
26 [Z; 16]
27};
28
29fn residency_census_note(qtype: i32, bytes: usize) {
30 use std::sync::atomic::Ordering::Relaxed;
31 if let Some(slot) = RESIDENCY_CENSUS.get(qtype as usize) {
32 slot.0.fetch_add(1, Relaxed);
33 slot.1.fetch_add(bytes as u64, Relaxed);
34 }
35}
36
37/// Human-readable residency census: one line per qtype that took at least one 2D weight, plus a
38/// total. Callers print it right after load — see `run-gen`'s `MEMRA_RESIDENCY_CENSUS=1`.
39pub fn residency_census_report() -> String {
40 use std::sync::atomic::Ordering::Relaxed;
41 let name = |q: usize| -> &'static str {
42 match q as i32 {
43 QT_Q8_0 => "Q8_0", QT_Q4_K => "Q4_K", QT_Q6_K => "Q6_K", QT_Q5_K => "Q5_K",
44 QT_Q3_K => "Q3_K", QT_IQ4_XS => "IQ4_XS", QT_IQ3_S => "IQ3_S", QT_NVFP4 => "NVFP4",
45 QT_F32 => "F32", QT_NVFP4_RP => "NVFP4_RP", QT_F8_E4M3 => "F8_E4M3",
46 QT_BF16 => "BF16", QT_Q4_0 => "Q4_0", QT_Q2_K => "Q2_K", _ => "?",
47 }
48 };
49 let mut out = String::from("residency census (2D matmul weights, resident container):\n");
50 let (mut tn, mut tb) = (0usize, 0u64);
51 for (q, slot) in RESIDENCY_CENSUS.iter().enumerate() {
52 let (n, b) = (slot.0.load(Relaxed), slot.1.load(Relaxed));
53 if n == 0 { continue; }
54 tn += n; tb += b;
55 out += &format!(" {:>9}: {:>4} tensors {:>9.3} MiB\n", name(q), n,
56 b as f64 / (1024.0 * 1024.0));
57 }
58 out += &format!(" {:>9}: {:>4} tensors {:>9.3} MiB", "TOTAL", tn,
59 tb as f64 / (1024.0 * 1024.0));
60 out
61}
62
63/// A weight tensor resident on GPU. Quantized weights stay in GGUF block bytes (`Quant`);
64/// small non-quant tensors (norms, sometimes embed/lm_head) are kept dequantized as f32 (`Float`).
65/// This keeps VRAM ~= on-disk quant size (fixes the f32-on-load OOM).
66pub enum GpuTensor {
67 Quant {
68 bytes: CudaSlice<u8>,
69 qtype: i32,
70 row_bytes: usize,
71 ne: Vec<u64>,
72 scale: f32,
73 /// SPLIT-PLANE walk-order repack (A6, 2026-07-04): NVFP4 matmul weights are repacked at
74 /// load into [quant plane out_f x in_f/64 x 32B][scale plane out_f x in_f/64 x 4B] — same
75 /// bytes, same total size, but a lane's per-group weight read becomes ONE 16B-aligned
76 /// LDG.128 + a dense 4B scale word instead of 5 scattered 4B LDGs at 36B stride (the "18B
77 /// straggle"). Every consumer kernel has an `_rp` twin (bit-identical: pure byte
78 /// permutation, same dot order). `rp=false` = original GGUF block layout (all other
79 /// dtypes, MoE-staged expert bytes, MEMRA_RP=0 escape).
80 rp: bool,
81 /// CUTLASS NVFP4 prefill operand (repacked B + swizzled SFB), built ALONGSIDE `bytes` at load
82 /// when MEMRA_FP4_CUTLASS is set. `bytes` stays raw GGUF so decode (MMVQ/dp4a) is untouched;
83 /// prefill (m>=128) reads this. Only ever Some for NVFP4 weights under cfg(memra_cutlass).
84 #[cfg(memra_cutlass)]
85 cutlass: Option<CutlassWeight>,
86 /// FP8-ACT PREFILL operand (MEMRA_PP_FP8=1, probe verdict 2026-07-08): the checkpoint's RAW
87 /// e4m3 bytes + per-tensor f32 weight_scale, stashed ALONGSIDE the Q8_0 re-encode for the
88 /// F8-E4M3-origin 2D projections (~1 B/w extra on those layers). `bytes` stays Q8_0 so
89 /// decode (dp4a/MMVQ) is untouched; only the m>=16 prefill dispatch (cuBLASLt FP8 TN,
90 /// fp8_ffi.rs) reads this. None unless the env is set at load (zero VRAM cost by default).
91 fp8: Option<Fp8Weight>,
92 /// Q4_0 SPLIT-PLANE MIRROR (2026-07-10, the 18B-straggle cure for decode): qs plane
93 /// [out_f x nblk x 16B] + d plane [out_f x nblk x 2B] built device-side at model load
94 /// (q4_0_split_rp_build) for decode-hot trunk weights. Raw `bytes` stay resident —
95 /// prefill (gemm/MMQ) and Stage-A read those; the m<=8 mmvq/batched/fused dispatch
96 /// reads this when present (`_rp` twins; microprobe m=1 1.34x, m=3 1.17x, bitwise).
97 /// None everywhere except where the arch-load hook opted in (VRAM cost = weight size).
98 rp4: Option<CudaSlice<u8>>,
99 /// FP16 DEQUANT MIRROR (MEMRA_PP_F16=1, probe 2026-07-26): row-major fp16 of a 2D Q8_0
100 /// projection, built device-side at load (f16_ffi::build_q8_f16). `bytes` stay Q8_0 so
101 /// decode is untouched; the m>=16 prefill dispatch (cuBLASLt FP16 TN, 611-687 TF vs
102 /// MMQ's ~200 TF class) reads this. None unless the env is set (VRAM = 2 B/w extra).
103 f16: Option<CudaSlice<u8>>,
104 },
105 Float {
106 data: CudaSlice<f32>,
107 ne: Vec<u64>,
108 },
109 /// BF16-RESIDENT full-precision matmul weight (MEMRA_FULL_PREC only). Holds the checkpoint's raw
110 /// bf16 bytes (`u8`, little-endian u16 pairs) — 2 B/w vs the 4 B/w a `Float` f32 materialization
111 /// would cost, so the 9B trunk stays ~18GB in VRAM instead of ~36GB. Consumed via dequant-on-use:
112 /// each matmul expands this to a transient f32 scratch and rides the SAME cuBLASLt f32 GEMV the
113 /// `Float` arm uses (bit-identical to a load-time bf16->f32 dequant, just deferred). Never a norm
114 /// (norms stay `Float` f32); never on a fast/GEMM/MMQ path (uses_q8_1_fast/gemm_supports = false).
115 FloatBf16 {
116 data: CudaSlice<u8>,
117 ne: Vec<u64>,
118 },
119}
120
121/// FP8-native prefill operand: raw checkpoint e4m3 codes `[out_f, in_f]` row-major (EXACT — the
122/// weight side of the FP8 GEMM does no re-quantization) + its weight scale(s). Per-tensor class:
123/// `scale` is the dequant scalar folded into the GEMM's scale pointer together with the per-batch
124/// activation scale, `blk == None`. Block-128 class (Qwen official FP8): `blk == Some` and
125/// `scale == 1.0` — see `Fp8BlockScales` for the resident layout contract.
126pub struct Fp8Weight {
127 pub bytes: CudaSlice<u8>,
128 pub scale: f32,
129 pub blk: Option<Fp8BlockScales>,
130}
131
132/// Device-resident block-128 weight-scale grid for an e4m3 operand (B1b, lane fp8st 2026-08-03).
133///
134/// STORAGE LAYOUT (the canonical device layout every future consumer builds from): a flat f32
135/// buffer in the CHECKPOINT'S on-disk order — row-major `[rows = ceil(out_f/128),
136/// cols = ceil(in_f/128)]`, so `scales[ob * cols + kb]` scales the 128x128 weight tile at
137/// output-block `ob`, input-block `kb` (uploaded verbatim from `memra_gguf::source::F8BlockGrid`,
138/// no permutation — one host decode, one htod). Rationale: (1) the per-block-dequant mmvq twin
139/// (qmatvec_e4m3_mmvq extension, DECISION.md B1) indexes `(o >> 7) * cols + (e >> 7)` — natural
140/// in this order; (2) for cuBLASLt BLK128x128 the weight `[out, in]` row-major is the TN GEMM's
141/// column-major `[k=in, n=out]` A operand, and this same linear order IS that view's column-major
142/// block grid with ld = cols(=kblk) — probe P1 (`probe/fp8_lt_blk_probe.cu`) verifies whether
143/// sm_120 accepts it directly; if Lt wants a different order, the reorder happens at the GEMM
144/// plan build, NOT here. NO KERNEL CONSUMES THIS YET: the loader keeps every block-128 tensor's
145/// decode/prefill on the Q8_0 re-encode until the consuming kernels land (try_fp8_gemm skips
146/// blk operands; the QT_F8_E4M3 one-copy arm rejects them). This struct's job is bytes+scales
147/// resident and correct.
148pub struct Fp8BlockScales {
149 pub scales: CudaSlice<f32>,
150 pub rows: usize, // ceil(out_f/128)
151 pub cols: usize, // ceil(in_f/128)
152}
153
154/// Host-side split-plane repack of NVFP4 GGUF block bytes (A6). Input: out_f rows of in_f/64
155/// 36-byte blocks ([4B UE4M3 scales][32B packed e2m1]). Output (same length): quant plane
156/// (out_f x nsb64 x 32B) followed by scale plane (out_f x nsb64 x 4B). Pure byte permutation.
157pub fn repack_nvfp4_split(bytes: &[u8], out_f: usize) -> Vec<u8> {
158 let row_bytes = bytes.len() / out_f;
159 let nsb64 = row_bytes / 36;
160 debug_assert_eq!(
161 row_bytes % 36,
162 0,
163 "NVFP4 row_bytes must be a multiple of 36"
164 );
165 let qplane = out_f * nsb64 * 32;
166 let mut rp = vec![0u8; bytes.len()];
167 for o in 0..out_f {
168 for s in 0..nsb64 {
169 let src = &bytes[o * row_bytes + s * 36..o * row_bytes + s * 36 + 36];
170 rp[qplane + (o * nsb64 + s) * 4..qplane + (o * nsb64 + s) * 4 + 4]
171 .copy_from_slice(&src[0..4]);
172 rp[(o * nsb64 + s) * 32..(o * nsb64 + s) * 32 + 32].copy_from_slice(&src[4..36]);
173 }
174 }
175 rp
176}
177
178/// Inverse of `repack_nvfp4_split` (the roundtrip gate).
179pub fn unpack_nvfp4_split(rp: &[u8], out_f: usize) -> Vec<u8> {
180 let row_bytes = rp.len() / out_f;
181 let nsb64 = row_bytes / 36;
182 let qplane = out_f * nsb64 * 32;
183 let mut back = vec![0u8; rp.len()];
184 for o in 0..out_f {
185 for s in 0..nsb64 {
186 back[o * row_bytes + s * 36..o * row_bytes + s * 36 + 4].copy_from_slice(
187 &rp[qplane + (o * nsb64 + s) * 4..qplane + (o * nsb64 + s) * 4 + 4],
188 );
189 back[o * row_bytes + s * 36 + 4..o * row_bytes + s * 36 + 36]
190 .copy_from_slice(&rp[(o * nsb64 + s) * 32..(o * nsb64 + s) * 32 + 32]);
191 }
192 }
193 back
194}
195
196/// A6 repack seam: default ON, `MEMRA_RP=0` restores the GGUF block layout everywhere (rollback/A-B).
197pub fn rp_enabled() -> bool {
198 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
199 *ON.get_or_init(|| std::env::var("MEMRA_RP").map(|v| v != "0").unwrap_or(true))
200}
201
202/// FULL-PRECISION LOADER MODE (MEMRA_FULL_PREC=1, default OFF — MTP-heal research platform).
203/// Bypasses the standing loader law (large BF16/F8 -> Q8_0/NVFP4 re-encode, the "Float-poison"
204/// tripwire). Under this flag every weight loads as Float and compute rides the Stage-A f32 oracle
205/// path end to end — SLOW IS FINE, this mode exists for exactness (the MTP acceptance CEILING at
206/// full precision), not speed. Large 2D matmul weights stay bf16-resident (`GpuTensor::FloatBf16`)
207/// with dequant-on-use so the 9B (~18GB bf16) + f32 activations fit 24GB instead of blowing to
208/// ~38GB as an all-f32 materialization. The Float-poison tripwire warnings are CORRECT behavior
209/// here and are suppressed. See docs/FLAGS.md and HANDOVER "MEMRA DUAL-SHAPE".
210pub fn full_prec_enabled() -> bool {
211 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
212 *ON.get_or_init(|| {
213 std::env::var("MEMRA_FULL_PREC")
214 .map(|v| v == "1")
215 .unwrap_or(false)
216 })
217}
218
219/// LOADER-LAW allowlist (loadersweep audit 2026-07-08): 2D Float tensors that are DELIBERATELY
220/// Float despite being matmul-class. Every entry needs an audit rationale — this list silences
221/// the tripwire below, so an unjustified entry re-opens the trap.
222/// * ffn_gate_inp (MoE router, 35B GGUF F32 [2048,256] / M3 ST F32 [6144,64]): the router's
223/// top-k SELECTION is discontinuous — quantizing shifts logits and flips expert choice (a
224/// class change, not an FP-order change). llama.cpp keeps every router F32 (its converter
225/// forces F32) so Float is bench-parity, it sits on NO all-or-nothing predicate, and the
226/// decode-exact contract is already built around its cuBLASLt path
227/// (hybrid_forward.rs moe_ffn_sequential_zq8 router comment).
228fn float_2d_audited(name: &str) -> bool {
229 name.ends_with("ffn_gate_inp.weight")
230}
231
232/// Once-per-name-pattern loader-law warning (`blk.{il}.` collapses to `blk.*.` so a 48-layer
233/// offender prints ONE line, not 48). See the call site in `load_from_source` for the law.
234fn warn_float_2d_once(name: &str, ne: &[u64], src_type: GgmlType) {
235 use std::sync::{Mutex, OnceLock};
236 static SEEN: OnceLock<Mutex<std::collections::HashSet<String>>> = OnceLock::new();
237 let pat = match name.strip_prefix("blk.").and_then(|r| r.split_once('.')) {
238 Some((_, suffix)) => format!("blk.*.{suffix}"),
239 None => name.to_string(),
240 };
241 let mut seen = SEEN
242 .get_or_init(|| Mutex::new(std::collections::HashSet::new()))
243 .lock()
244 .unwrap();
245 if seen.insert(pat.clone()) {
246 eprintln!(
247 "[loader-law] WARNING: {pat} loads as 2D Float ne={ne:?} (src {src_type:?}) — \
248 a Float matmul weight rides cuBLAS f32 GEMV and poisons all-or-nothing q8-fast \
249 predicates (uses_q8_1_fast/mixer_in_q8_1_fast). If matmul-class: Q8_0-encode at \
250 load (model.rs ssm arm / source.rs BF16+F8 gates). If deliberately Float: add \
251 it to float_2d_audited with the audit rationale."
252 );
253 }
254}
255
256/// CUTLASS-layout NVFP4 weight (B operand) for the prefill FP4 GEMM. Built once at load from the raw
257/// GGUF bytes (de-interleave + SFB swizzle). Coexists with the raw `bytes` (decode reads bytes).
258#[cfg(memra_cutlass)]
259pub struct CutlassWeight {
260 /// Plain K-contiguous packed e2m1, [out_f, in_f/2] bytes.
261 pub b_packed: CudaSlice<u8>,
262 /// Swizzled SFB (CUTLASS SfAtom layout), sized via cutlass_sfb_size(out_f, in_f).
263 pub sfb_swizzled: CudaSlice<u8>,
264}
265
266impl GpuTensor {
267 pub fn ne(&self) -> &[u64] {
268 match self {
269 GpuTensor::Quant { ne, .. } => ne,
270 GpuTensor::Float { ne, .. } => ne,
271 GpuTensor::FloatBf16 { ne, .. } => ne,
272 }
273 }
274 pub fn in_features(&self) -> usize {
275 self.ne()[0] as usize
276 }
277 pub fn out_features(&self) -> usize {
278 self.ne()[1] as usize
279 }
280 /// Per-tensor post-matmul macro-scale (NVFP4 carries scale != 1.0; all others -> 1.0, a no-op).
281 /// Used by the fused SwiGLU epilogue to fold the gate/up scale into one kernel.
282 pub fn scale(&self) -> f32 {
283 match self {
284 GpuTensor::Quant { scale, .. } => *scale,
285 GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => 1.0,
286 }
287 }
288
289 /// Load a tensor, keeping quant types packed and float types as f32. (GGUF entry point —
290 /// thin wrapper over the source-agnostic `load_from_source`; behavior is unchanged.)
291 pub fn load(e: &Engine, g: &GgufFile, name: &str) -> Result<Self, Box<dyn std::error::Error>> {
292 Self::load_from_source(e, &GgufSource(g), name)
293 }
294
295 /// Source-agnostic load: works from any `TensorSource` (GGUF or safetensors). The engine's
296 /// forward graph only ever asks for ggml-style names; the source maps them to its own layout.
297 ///
298 /// RESIDENCY CENSUS (lane/fp8-decode-v1, 2026-08-05): the wrapper tallies what each 2D
299 /// matmul weight ACTUALLY became — resident qtype + resident bytes — so the FP8-ST decode
300 /// arm's claim ("e4m3 stays native instead of paying the Q8_0-slab tax") is a measured
301 /// per-checkpoint fact rather than an assumption about the checkpoint's dtype mix. Read it
302 /// with `residency_census_report()`; zero cost when never read.
303 pub fn load_from_source(
304 e: &Engine,
305 src: &dyn TensorSource,
306 name: &str,
307 ) -> Result<Self, Box<dyn std::error::Error>> {
308 let t = Self::load_from_source_inner(e, src, name)?;
309 if let GpuTensor::Quant { qtype, bytes, ne, .. } = &t {
310 if ne.len() == 2 {
311 residency_census_note(*qtype, bytes.len());
312 }
313 }
314 Ok(t)
315 }
316
317 fn load_from_source_inner(
318 e: &Engine,
319 src: &dyn TensorSource,
320 name: &str,
321 ) -> Result<Self, Box<dyn std::error::Error>> {
322 // A1 DIRECT NVFP4 IMPORT (2026-07-04): a PLAIN modelopt/Reza NVFP4 weight from a
323 // safetensors source repacks straight into the A6 split-plane resident layout in ONE host
324 // pass (nvfp4_repack::repack_modelopt_to_split — the scale plane is the file's
325 // weight_scale bytes verbatim), never materializing the GGUF 36B-block intermediate.
326 // The GGUF hop remains only for MEMRA_ST_DIRECT=0 (rollback/A-B seam — byte-identical
327 // resident weights either way), MEMRA_RP=0, the hybrid V-reorder transforms, and the
328 // opt-in CUTLASS resident operand (which is built from raw GGUF-layout bytes).
329 let cutlass_wants_raw = cfg!(memra_cutlass) && std::env::var("MEMRA_FP4_CUTLASS").is_ok();
330 let st_direct = std::env::var("MEMRA_ST_DIRECT")
331 .map(|v| v != "0")
332 .unwrap_or(true);
333 if rp_enabled() && st_direct && !cutlass_wants_raw {
334 if let Some(nv) = src.find_nvfp4_native(name) {
335 if nv.in_f % 64 == 0 && nv.out_f > 0 {
336 // Same post-matmul macro-scale sibling lookup as the GGUF-layout arm below.
337 let stem = name.strip_suffix(".weight").unwrap_or(name);
338 let scale = match src.find(&format!("{stem}.scale")) {
339 Some(sv) => f32::from_le_bytes(sv.bytes[..4].try_into().unwrap()),
340 None => 1.0,
341 };
342 let bytes =
343 e.htod_bytes(&memra_gguf::nvfp4_repack::repack_modelopt_to_split(
344 nv.wbytes, nv.wscale, nv.out_f, nv.in_f,
345 ))?;
346 return Ok(GpuTensor::Quant {
347 bytes,
348 qtype: QT_NVFP4,
349 row_bytes: nv.in_f / 64 * 36,
350 ne: vec![nv.in_f as u64, nv.out_f as u64],
351 scale,
352 rp: true,
353 #[cfg(memra_cutlass)]
354 cutlass: None,
355 fp8: None, f16: None,
356 rp4: None,
357 });
358 }
359 }
360 }
361 // E4M3-DIRECT (DEFAULT since lane/fp8-decode-v1 2026-08-05; MEMRA_ST_E4M3=0 rolls back to the
362 // Q8_0 slab. Introduced default-off by lane e4m3dec 2026-07-08): F8-E4M3-origin 2D projections keep
363 // the checkpoint's RAW e4m3 device bytes + per-tensor weight_scale as the ONE resident copy
364 // (QT_F8_E4M3) instead of the Q8_0 re-encode — decode dequants e4m3 in-kernel
365 // (qmatvec_e4m3_mmvq, the checkpoint's own precision, no lossy re-quant hop), prefill
366 // (m>=16) rides the cuBLASLt FP8 GEMM on the SAME bytes (try_fp8_gemm). Frees the Q8_0
367 // duplicate the MEMRA_PP_FP8 stash needed (~3.4GB on the NV-27B) — full FP8 prefill coverage
368 // with no VRAM budget. Placed BEFORE `find` so the host-side F8->Q8_0 re-encode is skipped
369 // entirely (faster load). in_f%32 is the q8_1 activation block gate (every F8 projection in
370 // the NV-27B satisfies it; a violator falls through to the Q8_0 arm unchanged).
371 // BLOCK-128 GATE: the QT_F8_E4M3 decode kernel family (qmatvec_e4m3_mmvq + batched
372 // twins) consumes ONE scalar weight scale — dispatching a block-128 operand through it
373 // would silently dequant every tile with scale 1.0. Until the per-block-dequant mmvq
374 // twin lands (DECISION.md B1 second half), block-128 tensors fall through to the Q8_0
375 // re-encode (correct floor); their raw bytes+scales still go resident via the
376 // MEMRA_PP_FP8 stash arm below for the P1 GEMM work.
377 if crate::fp8_ffi::st_e4m3_enabled() {
378 if let Some(f8) = src.find_fp8_native(name) {
379 if f8.blk.is_none() && f8.in_f % 32 == 0 && f8.out_f > 0 {
380 return Ok(GpuTensor::Quant {
381 bytes: e.htod_bytes(&f8.bytes)?,
382 qtype: crate::QT_F8_E4M3,
383 row_bytes: f8.in_f,
384 ne: vec![f8.in_f as u64, f8.out_f as u64],
385 scale: f8.scale,
386 rp: false,
387 #[cfg(memra_cutlass)]
388 cutlass: None,
389 fp8: None, f16: None,
390 rp4: None,
391 });
392 }
393 }
394 }
395 // ARM B' — GPU BLOCK-128 DEQUANT (MEMRA_FP8_BLK_GPU=1, default OFF; lane fp8-gemm-arm
396 // 2026-08-03). A block-128 FP8 checkpoint (Qwen official FP8 / DeepSeek-V3 lineage)
397 // currently loads via the host path: full f32 dequant of the tensor (f8_deq_f32) then a
398 // host Q8_0 re-encode (f32_to_q8_0) — correct, but a serial CPU pass over every byte of
399 // every projection. This arm does the same math on the GPU in ONE pass
400 // (cu/fp8_blk_dequant.cu): upload the raw e4m3 codes + the scale grid, write Q8_0
401 // blocks directly. BYTE-IDENTICAL to the host path (kernel-check [fp8-blk-gpu] arm
402 // asserts it on ragged and aligned shapes), so the resident tensor, the MMQ/MMVQ
403 // dispatch, and decode are all bit-for-bit unchanged — this is a LOAD-TIME
404 // optimization only, not a numeric config change.
405 //
406 // Placed BEFORE `find` for exactly the reason the MEMRA_ST_E4M3 arm above is: `find`
407 // would otherwise do the host dequant+re-encode we are replacing. Per-tensor and
408 // per-row scale classes are NOT touched (find_fp8_native returns blk=None / None for
409 // them) and neither are V-reorder Transform targets (find_fp8_native rejects those with
410 // a grid — the permutation invalidates the on-disk grid, so they keep the host path).
411 //
412 // NO st_e4m3 EXCLUSION (lane/fp8-decode-v1 2026-08-05): this arm used to carry
413 // `&& !st_e4m3_enabled()`, written when MEMRA_ST_E4M3 was default OFF and meant only as
414 // "the native arm above already claimed this tensor". Once native residency became the
415 // DEFAULT that condition would have been true on every run and silently disabled ARM B'
416 // for the whole block-128 class — the exact silent-slow-path landmine the flags doctrine
417 // forbids. The two arms are already disjoint by construction and need no cross-gate: the
418 // arm above returns only when `f8.blk.is_none()`, this one runs only when `f8.blk` is
419 // Some, so a tensor that reaches here was never eligible for native residency.
420 if crate::fp8_ffi::fp8_blk_gpu_enabled() {
421 if let Some(f8) = src.find_fp8_native(name) {
422 if let Some(grid) = f8.blk.as_ref() {
423 let (in_f, out_f) = (f8.in_f, f8.out_f);
424 if in_f % 32 == 0 && out_f > 0 && f8.bytes.len() == out_f * in_f {
425 let bytes =
426 e.fp8_blk_dequant_q8_0(&f8.bytes, &grid.scales, out_f, in_f)?;
427 return Ok(GpuTensor::Quant {
428 bytes,
429 qtype: QT_Q8_0,
430 row_bytes: in_f / 32 * 34,
431 ne: vec![in_f as u64, out_f as u64],
432 scale: 1.0,
433 rp: false,
434 #[cfg(memra_cutlass)]
435 cutlass: None,
436 fp8: None, f16: None,
437 rp4: None,
438 });
439 }
440 }
441 }
442 }
443 let mut v = src
444 .find(name)
445 .unwrap_or_else(|| panic!("missing tensor {name}"));
446 // MEMRA_KQ_NVFP4=1 (opt-in, 2026-07-08): re-encode Q4_K/Q5_K 2D matmul weights to NVFP4 at
447 // load. The k-quant mmvq family runs at 61-70% of the bandwidth wall on this rig (measured
448 // BOTH engines — the kernels share ancestry) while the in-house NVFP4 path runs at 96%.
449 // The daily GGUF's quant mix was chosen for llama's kernels, not ours: Q4_K -> NVFP4 is
450 // 4-bit -> 4-bit at +26pp kernel efficiency; Q5_K -> NVFP4 also drops bytes (0.69 -> 0.56
451 // B/w) at a small real re-quant cost (5 -> 4 bit; gates + acceptance arbitrate). Q6_K/Q8_0
452 // excluded (6/8-bit -> 4-bit is a real quality cliff — the lm_head stays untouched).
453 // MEMRA_KQ_NVFP4 (opt-in SPEED-OVER-QUALITY mode, measured 2026-07-08 on the 9B):
454 // =2 (Q4_K+Q5_K -> NVFP4): +3.9% plain decode (129.5 -> 134.5, the Q5 bytes win),
455 // acceptance tax ~3pts on hard content (p2 74.0 -> 70.7, p3 66.9 -> 64.9).
456 // =1 (Q4_K only): NO perf gain AND still ~3pts tax — Q4_K is ASYMMETRIC (6-bit
457 // scale+min per 32); NVFP4 is symmetric e2m1: dropping the zero-point is real
458 // error even 4-bit -> 4-bit. The "same bpw = same class" assumption is FALSE
459 // across asymmetric/symmetric formats. Kept only for the record.
460 let kq = std::env::var("MEMRA_KQ_NVFP4")
461 .ok()
462 .and_then(|x| x.parse::<u8>().ok())
463 .unwrap_or(0);
464 if (kq >= 1 && v.ggml_type == GgmlType::Q4_K || kq >= 2 && v.ggml_type == GgmlType::Q5_K)
465 && v.ne.len() == 2
466 && v.ne[0] % 64 == 0
467 && !name.starts_with("output")
468 {
469 let n: u64 = v.ne.iter().product();
470 let f32v = dequant::dequantize(v.ggml_type, &v.bytes, n as usize);
471 let packed = memra_gguf::nvfp4_repack::f32_to_nvfp4(&f32v);
472 v = memra_gguf::source::TensorView {
473 bytes: std::borrow::Cow::Owned(packed),
474 ggml_type: GgmlType::NVFP4,
475 ne: v.ne.clone(),
476 };
477 }
478 let qtype = match v.ggml_type {
479 GgmlType::Q8_0 => Some(QT_Q8_0),
480 GgmlType::Q4_K => Some(QT_Q4_K),
481 GgmlType::Q6_K => Some(QT_Q6_K),
482 GgmlType::Q5_K => Some(QT_Q5_K),
483 GgmlType::Q3_K => Some(QT_Q3_K),
484 GgmlType::IQ4_XS => Some(QT_IQ4_XS),
485 GgmlType::IQ3_S => Some(QT_IQ3_S),
486 GgmlType::NVFP4 => Some(QT_NVFP4),
487 GgmlType::Q4_0 => Some(QT_Q4_0),
488 // F32/F16/BF16 (the dtypes safetensors carries) -> Float path below.
489 _ => None,
490 };
491 match qtype {
492 Some(qt) => {
493 let out_f = v.ne[1] as usize;
494 let row_bytes = v.bytes.len() / out_f;
495 // NVFP4 two-level scale: per-16 ue4m3 micro-scale is in the dequant; the per-tensor
496 // F32 macro-scale lives in a sibling "<stem>.scale" tensor, applied POST-matmul
497 // (llama build_lora_mm: ggml_mul(res, w_s)). ".input_scale" is the W4A4 activation
498 // scale — UNUSED on our W4A16/f32 path. Only NVFP4 carries it; others -> 1.0 (no-op).
499 let scale = if qt == QT_NVFP4 {
500 let stem = name.strip_suffix(".weight").unwrap_or(name);
501 match src.find(&format!("{stem}.scale")) {
502 Some(sv) => f32::from_le_bytes(sv.bytes[..4].try_into().unwrap()),
503 None => 1.0,
504 }
505 } else {
506 1.0
507 };
508 // A6 SPLIT-PLANE repack: NVFP4 2-D matmul weights upload in walk-order layout
509 // (host-side permutation before htod — zero VRAM spike, layer-streamed by
510 // construction). Every consumer kernel dispatches its `_rp` twin off the flag.
511 let rp = qt == QT_NVFP4
512 && v.ne.len() == 2
513 && (v.ne[0] as usize) % 64 == 0
514 && v.bytes.len() % out_f == 0
515 && (v.bytes.len() / out_f) % 36 == 0
516 && rp_enabled();
517 let bytes = if rp {
518 e.htod_bytes(&repack_nvfp4_split(&v.bytes, out_f))?
519 } else {
520 e.htod_bytes(&v.bytes)?
521 };
522 // CUTLASS NVFP4 prefill operand, built from the RAW GGUF bytes (a temp raw upload
523 // when the resident `bytes` are repacked). Gated: only NVFP4 weights, only when
524 // MEMRA_FP4_CUTLASS is set, only under cfg(memra_cutlass). in_f%64==0 is the NVFP4
525 // K-block constraint (same as the dispatch).
526 #[cfg(memra_cutlass)]
527 let cutlass = {
528 let in_f = v.ne[0] as usize;
529 // Skip the resident repack when OTF is requested (per-call repack instead) — the
530 // resident path ~doubles NVFP4 weight VRAM and OOMs larger models (e.g. 27B/24GB).
531 if qt == QT_NVFP4
532 && in_f % 64 == 0
533 && v.ne.len() == 2
534 && std::env::var("MEMRA_FP4_CUTLASS").is_ok()
535 && std::env::var("MEMRA_FP4_CUTLASS_OTF").is_err()
536 {
537 let raw_dev;
538 let src_dev = if rp {
539 raw_dev = e.htod_bytes(&v.bytes)?;
540 &raw_dev
541 } else {
542 &bytes
543 };
544 let (b_packed, sfb_swizzled) =
545 e.build_cutlass_weight(src_dev, out_f, in_f, row_bytes)?;
546 Some(CutlassWeight {
547 b_packed,
548 sfb_swizzled,
549 })
550 } else {
551 None
552 }
553 };
554 // FP8-ACT PREFILL operand (MEMRA_PP_FP8=1): for F8-E4M3-sourced projections (they
555 // surface as Q8_0 from the source's re-encode) ALSO stash the raw e4m3 device
556 // bytes + weight_scale. The source guarantees byte order matches `v` (the
557 // Transform arm's V-reorder is baked into both); the ne check guards a mixup.
558 // VRAM BUDGET (24GB rigs, 2026-07-08): the stash duplicates every F8-origin
559 // projection (~+3.4GB on the 27B) — fine on the 96GB box, OOM here. The stash
560 // spends from MEMRA_PP_FP8_BUDGET_MB (default 1536); once spent, remaining
561 // tensors ride the old path. Load order is layer order, so the budget covers a
562 // PREFIX of layers — coverage (and the prefill win) scales with the budget.
563 // MEMRA_FP8_MMQ=1 (lane/fp8-mmq) admits the SAME stash for the block-128 class:
564 // the per-block MMQ prefill kernel is that class's consumer, and it needs exactly
565 // what this arm makes resident (raw e4m3 bytes + the verbatim f32 grid). It shares
566 // the budget accounting below, so a 24GB rig still caps the duplicate.
567 let fp8 = if qt == QT_Q8_0
568 && (crate::fp8_ffi::pp_fp8_enabled() || crate::fp8_ffi::fp8_mmq_enabled())
569 {
570 match src.find_fp8_native(name) {
571 Some(f8)
572 if v.ne.len() == 2
573 && f8.in_f as u64 == v.ne[0]
574 && f8.out_f as u64 == v.ne[1] =>
575 {
576 use std::sync::atomic::{AtomicUsize, Ordering};
577 static FP8_SPENT: AtomicUsize = AtomicUsize::new(0);
578 static FP8_BUDGET: std::sync::OnceLock<usize> =
579 std::sync::OnceLock::new();
580 let budget = *FP8_BUDGET.get_or_init(|| {
581 std::env::var("MEMRA_PP_FP8_BUDGET_MB")
582 .ok()
583 .and_then(|v| v.parse::<usize>().ok())
584 .unwrap_or(1536)
585 << 20
586 });
587 let sz = f8.bytes.len();
588 if FP8_SPENT.fetch_add(sz, Ordering::Relaxed) + sz <= budget {
589 // Block-128 grid rides along resident (checkpoint order,
590 // Fp8BlockScales layout contract). try_fp8_gemm still skips blk
591 // operands (cuBLASLt takes no block grid on sm_120, P1-VERDICT);
592 // try_fp8_blk_mmq is their consumer under MEMRA_FP8_MMQ=1.
593 let blk = match f8.blk {
594 Some(g) => Some(Fp8BlockScales {
595 scales: e.htod(&g.scales)?,
596 rows: g.rows,
597 cols: g.cols,
598 }),
599 None => None,
600 };
601 Some(Fp8Weight {
602 bytes: e.htod_bytes(&f8.bytes)?,
603 scale: f8.scale,
604 blk,
605 })
606 } else {
607 FP8_SPENT.fetch_sub(sz, Ordering::Relaxed);
608 None
609 }
610 }
611 _ => None,
612 }
613 } else {
614 None
615 };
616 Ok(GpuTensor::Quant {
617 bytes,
618 qtype: qt,
619 row_bytes,
620 ne: v.ne.clone(),
621 scale,
622 rp,
623 #[cfg(memra_cutlass)]
624 cutlass,
625 fp8,
626 rp4: None,
627 f16: None,
628 })
629 }
630 None => {
631 let n: u64 = v.ne.iter().product();
632 // FULL-PRECISION MODE (MEMRA_FULL_PREC): NO re-encodes. Large 2D bf16 matmul weights
633 // stay bf16-resident (FloatBf16, dequant-on-use) so the trunk fits VRAM; everything
634 // else (small 2D, 1D norms, F16/F32) rides the exact f32 Float path below. The ssm
635 // Q8_0 re-encode and the Float-poison tripwire are BYPASSED here (both are the loader
636 // law this mode exists to suspend — the warnings would be correct but noise).
637 if full_prec_enabled() {
638 // Only bf16 sources take the resident-bf16 arm; F16/F32 fall through to f32 Float
639 // (exact, and tiny/absent in the bf16 ST checkpoints this mode targets). The 1M
640 // threshold keeps small tensors (norms, gate_inp) on the proven f32 path — only
641 // the big trunk matrices need the 2 B/w VRAM saving.
642 if v.ggml_type == GgmlType::BF16 && v.ne.len() == 2 && n >= 1_000_000 {
643 let data = e.htod_bytes(&v.bytes)?; // raw bf16 bytes, u16 LE pairs
644 return Ok(GpuTensor::FloatBf16 {
645 data,
646 ne: v.ne.clone(),
647 });
648 }
649 let f32v = dequant::dequantize(v.ggml_type, &v.bytes, n as usize);
650 return Ok(GpuTensor::Float {
651 data: e.htod(&f32v)?,
652 ne: v.ne.clone(),
653 });
654 }
655 let f32v = dequant::dequantize(v.ggml_type, &v.bytes, n as usize);
656 // ssm_beta/ssm_alpha stored F32 (the 35B GGUF): Q8_0-encode at load. F32 here
657 // fails `mixer_in_q8_1_fast` for the whole linear-attn mixer -> every linear
658 // layer falls off the fused norm+quantize chain onto cuBLAS f32 GEMV pairs
659 // (the NV-27B in_proj_a/b lesson, same all-or-nothing capability check; nsys
660 // 35B: 100 dot+reduce launches/token). Q8_0 of an F32 source is the same
661 // class-lossless step every 9B GGUF already ships for these tensors.
662 if v.ne.len() == 2
663 && v.ne[0] % 32 == 0
664 && (name.ends_with("ssm_beta.weight") || name.ends_with("ssm_alpha.weight")
665 // E4B per_layer_model_proj (F16 [2560, 10752]): matmul-class — the
666 // loader-law recipe (2026-07-12). As Float it rode cuBLAS f32 whose
667 // m=1-vs-m=16 FP-order gap seeds inp_pl noise into EVERY layer's PLE
668 // tail; the 42-layer stack amplifies it to logit maxdiff ~27 and the
669 // chat-prompt prefill-vs-decode argmax gate fails.
670 || name.ends_with("per_layer_model_proj.weight"))
671 {
672 let q8 = memra_gguf::nvfp4_repack::f32_to_q8_0(&f32v);
673 return GpuTensor::from_quant_bytes(
674 e,
675 &q8,
676 GgmlType::Q8_0,
677 v.ne[0],
678 v.ne[1],
679 1.0,
680 );
681 }
682 // LOADER-LAW TRIPWIRE (loadersweep 2026-07-08): a 2D Float tensor with both dims
683 // >= 16 is almost certainly MATMUL-class, and a Float matmul weight (a) rides
684 // cuBLAS f32 GEMV pairs (dot_kernel + reduce_1Block in nsys) and (b) fails
685 // uses_q8_1_fast, poisoning every ALL-OR-NOTHING fast-path predicate it sits on
686 // (mixer_in_q8_1_fast etc.) — the trap that cost measurable perf 4 times (NV-27B
687 // in_proj_a/b BF16, 35B ssm_beta/alpha F32, M3 shexp cousin, M3 BF16 lm_head).
688 // Fix recipe: name-gated f32_to_q8_0 encode at load (see the ssm arm above /
689 // source.rs BF16+F8 gates). Norm-class tensors are 1D or have a dim < 16
690 // (conv1d ne[0]=4) and never reach this warning.
691 if v.ne.len() == 2 && v.ne[0] >= 16 && v.ne[1] >= 16 && !float_2d_audited(name) {
692 warn_float_2d_once(name, &v.ne, v.ggml_type);
693 }
694 // F32/F16/BF16 (or as-yet-unhandled quant): dequant to f32. Small tensors only.
695 Ok(GpuTensor::Float {
696 data: e.htod(&f32v)?,
697 ne: v.ne.clone(),
698 })
699 }
700 }
701 }
702
703 /// Build a Quant tensor directly from raw ggml block bytes (FR-Spec self-trim: byte-level row
704 /// gather from an already-loaded weight — rows in every ggml quant are independent, so a
705 /// contiguous per-row byte copy is a lossless "trim"). `ne0` = in_features, `ne1` = rows.
706 pub fn from_quant_bytes(
707 e: &Engine,
708 bytes: &[u8],
709 ty: GgmlType,
710 ne0: u64,
711 ne1: u64,
712 scale: f32,
713 ) -> Result<Self, Box<dyn std::error::Error>> {
714 let qt = match ty {
715 GgmlType::Q8_0 => QT_Q8_0,
716 GgmlType::Q4_K => QT_Q4_K,
717 GgmlType::Q6_K => QT_Q6_K,
718 GgmlType::Q5_K => QT_Q5_K,
719 GgmlType::Q3_K => QT_Q3_K,
720 GgmlType::IQ4_XS => QT_IQ4_XS,
721 GgmlType::IQ3_S => QT_IQ3_S,
722 GgmlType::NVFP4 => QT_NVFP4,
723 GgmlType::Q4_0 => QT_Q4_0,
724 other => panic!("from_quant_bytes: unsupported dtype {other:?}"),
725 };
726 let row_bytes = bytes.len() / ne1 as usize;
727 // Same A6 repack as load_from_source: callers pass GGUF-layout host bytes (the FR-Spec
728 // self-trim row-gathers from the source file bytes, which are always original layout).
729 let rp = qt == QT_NVFP4 && ne0 % 64 == 0 && row_bytes % 36 == 0 && rp_enabled();
730 let dev = if rp {
731 e.htod_bytes(&repack_nvfp4_split(bytes, ne1 as usize))?
732 } else {
733 e.htod_bytes(bytes)?
734 };
735 Ok(GpuTensor::Quant {
736 bytes: dev,
737 qtype: qt,
738 row_bytes,
739 ne: vec![ne0, ne1],
740 scale,
741 rp,
742 #[cfg(memra_cutlass)]
743 cutlass: None,
744 fp8: None, f16: None,
745 rp4: None,
746 })
747 }
748
749 pub fn load_opt(
750 e: &Engine,
751 g: &GgufFile,
752 name: &str,
753 ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
754 Self::load_opt_from_source(e, &GgufSource(g), name)
755 }
756
757 pub fn load_opt_from_source(
758 e: &Engine,
759 src: &dyn TensorSource,
760 name: &str,
761 ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
762 if src.has(name) {
763 Ok(Some(Self::load_from_source(e, src, name)?))
764 } else {
765 Ok(None)
766 }
767 }
768
769 /// Accessor for tensors that MUST be f32 (norm weights). Panics if quantized.
770 pub fn float_data(&self) -> &CudaSlice<f32> {
771 match self {
772 GpuTensor::Float { data, .. } => data,
773 GpuTensor::Quant { .. } => panic!("expected float tensor (norm), got quantized"),
774 GpuTensor::FloatBf16 { .. } => {
775 panic!("expected f32 float tensor (norm), got bf16-resident matmul weight")
776 }
777 }
778 }
779}
780
781pub struct Layer {
782 pub attn_norm: GpuTensor,
783 pub wq: GpuTensor,
784 pub wk: GpuTensor,
785 pub wv: GpuTensor,
786 pub wo: GpuTensor,
787 pub q_norm: Option<GpuTensor>,
788 pub k_norm: Option<GpuTensor>,
789 pub ffn_norm: GpuTensor,
790 /// FFN: dense SwiGLU or routed MoE (OLMoE — dense attention + MoE FFN). Reuses the hybrid
791 /// `Ffn` enum + `load_ffn` so the routed-expert forward is shared with `HybridModel::moe_ffn`.
792 pub ffn: crate::hybrid::Ffn,
793}
794
795/// Host-resident embedding table for row gather (dequant only the needed token rows).
796pub struct EmbedHost {
797 pub raw: Vec<u8>,
798 pub ggml_type: GgmlType,
799 pub n_embd: usize,
800}
801impl EmbedHost {
802 pub fn from_gguf(g: &GgufFile, name: &str) -> Self {
803 Self::from_source(&GgufSource(g), name)
804 }
805 pub fn from_source(src: &dyn TensorSource, name: &str) -> Self {
806 let v = src
807 .find(name)
808 .unwrap_or_else(|| panic!("missing embed {name}"));
809 EmbedHost {
810 raw: v.bytes.to_vec(),
811 ggml_type: v.ggml_type,
812 n_embd: v.ne[0] as usize,
813 }
814 }
815 /// QT int + row_bytes for this embed table's dtype (for the device embed-gather kernel).
816 /// CUDA-GRAPH-PLAN Phase 1. Mirrors the GpuTensor qtype mapping.
817 pub fn qt_and_row_bytes(&self, n_embd: usize) -> (i32, usize) {
818 let (blk, tsize) = self.ggml_type.block_and_type_size();
819 let row_bytes = (n_embd as u64 / blk * tsize) as usize;
820 let qt = match self.ggml_type {
821 GgmlType::Q8_0 => QT_Q8_0,
822 GgmlType::Q4_K => QT_Q4_K,
823 GgmlType::Q6_K => QT_Q6_K,
824 GgmlType::Q5_K => QT_Q5_K,
825 GgmlType::Q3_K => QT_Q3_K,
826 GgmlType::IQ4_XS => QT_IQ4_XS,
827 GgmlType::IQ3_S => QT_IQ3_S,
828 GgmlType::NVFP4 => QT_NVFP4,
829 GgmlType::F32 => QT_F32,
830 // BF16 embed table (FULL_PREC research mode: qwen35-9b-hf) — device gather does the
831 // exact bits<<16 expansion; 2 B/elem resident instead of an f32-doubled table.
832 GgmlType::BF16 => QT_BF16,
833 other => panic!("embed_gather: unsupported dtype {other:?}"),
834 };
835 (qt, row_bytes)
836 }
837
838 /// Gather rows for tokens -> [T, n_embd] f32. Dequant per-row from raw bytes.
839 pub fn gather(&self, n_embd: usize, tokens: &[u32]) -> Vec<f32> {
840 let (blk, tsize) = self.ggml_type.block_and_type_size();
841 let row_bytes = (n_embd as u64 / blk * tsize) as usize;
842 let mut x = vec![0f32; tokens.len() * n_embd];
843 for (ti, &tok) in tokens.iter().enumerate() {
844 let off = tok as usize * row_bytes;
845 let row = dequant::dequantize(self.ggml_type, &self.raw[off..off + row_bytes], n_embd);
846 x[ti * n_embd..ti * n_embd + n_embd].copy_from_slice(&row);
847 }
848 x
849 }
850}
851
852pub struct Model {
853 pub cfg: ModelConfig,
854 pub embd: EmbedHost,
855 pub output_norm: GpuTensor,
856 pub output: GpuTensor,
857 pub layers: Vec<Layer>,
858}
859
860impl Model {
861 /// Load a dense (vanilla-transformer) model from GGUF. Thin wrapper over
862 /// `load_dense_from_source`. Panics if the arch has SSM/MoE layers.
863 pub fn load_dense(e: &Engine, g: &GgufFile) -> Result<Self, Box<dyn std::error::Error>> {
864 Self::load_dense_from_source(e, &GgufSource(g))
865 }
866
867 /// Load a dense-attention model from any `TensorSource` — GGUF or a safetensors HF checkpoint.
868 /// The whole loop speaks ggml names; the source maps them. The FFN is dense SwiGLU OR routed MoE
869 /// (OLMoE: dense full-attention + MoE FFN). Panics on hybrid (SSM) arches — use the hybrid path.
870 pub fn load_dense_from_source(
871 e: &Engine,
872 src: &dyn TensorSource,
873 ) -> Result<Self, Box<dyn std::error::Error>> {
874 let cfg = src.config();
875 assert!(
876 cfg.full_attention_interval == 0,
877 "model has linear-attn layers; use hybrid path"
878 );
879 // FP8-KV per-model door: OFF everywhere by default (explicit MEMRA_KV_FP8 wins).
880 // The 2026-07-12 9B "+0.7-4% scaling with depth" did NOT reproduce on the
881 // 2026-07-28 build (12k A/B: fp8 117.0/118.2 vs q8 119.3/119.2 = −1%; d1736
882 // flat; the fa-v3/f16pv/PDL stack moved underneath it). Adoption reverted by
883 // measurement — fp8-KV's remaining value is bytes (~45% smaller KV) for
884 // ctx-limited serving, not speed. Gates all green under both formats.
885 crate::KV_FP8_FORCE.store(0, std::sync::atomic::Ordering::Relaxed);
886
887 let embd = EmbedHost::from_source(src, "token_embd.weight");
888 let output_norm = GpuTensor::load_from_source(e, src, "output_norm.weight")?;
889 // tied embeddings: fall back to tok_embd if output.weight absent (OLMoE has untied output).
890 let output = if src.has("output.weight") {
891 GpuTensor::load_from_source(e, src, "output.weight")?
892 } else {
893 GpuTensor::load_from_source(e, src, "token_embd.weight")?
894 };
895
896 let mut layers = Vec::with_capacity(cfg.n_layer as usize);
897 for il in 0..cfg.n_layer {
898 let p = |s: &str| format!("blk.{il}.{s}");
899 let hy3_dense_ffn = cfg
900 .hy3
901 .as_ref()
902 .is_some_and(|h| il < h.first_k_dense_replace);
903 let ffn = if hy3_dense_ffn {
904 crate::hybrid::Ffn::Dense {
905 ffn_gate: GpuTensor::load_from_source(e, src, &p("ffn_gate.weight"))?,
906 ffn_up: GpuTensor::load_from_source(e, src, &p("ffn_up.weight"))?,
907 ffn_down: GpuTensor::load_from_source(e, src, &p("ffn_down.weight"))?,
908 }
909 } else {
910 crate::hybrid::load_ffn(e, src, &cfg, il, None)?
911 };
912 layers.push(Layer {
913 attn_norm: GpuTensor::load_from_source(e, src, &p("attn_norm.weight"))?,
914 wq: GpuTensor::load_from_source(e, src, &p("attn_q.weight"))?,
915 wk: GpuTensor::load_from_source(e, src, &p("attn_k.weight"))?,
916 wv: GpuTensor::load_from_source(e, src, &p("attn_v.weight"))?,
917 wo: GpuTensor::load_from_source(e, src, &p("attn_output.weight"))?,
918 q_norm: GpuTensor::load_opt_from_source(e, src, &p("attn_q_norm.weight"))?,
919 k_norm: GpuTensor::load_opt_from_source(e, src, &p("attn_k_norm.weight"))?,
920 ffn_norm: GpuTensor::load_from_source(e, src, &p("ffn_norm.weight"))?,
921 ffn,
922 });
923 }
924 Ok(Model {
925 cfg,
926 embd,
927 output_norm,
928 output,
929 layers,
930 })
931 }
932
933 /// Largest expert block (bytes) across all MoE layers — the fixed cache-slot size (mirrors
934 /// `HybridModel::max_moe_block`). 0 for a dense (non-MoE) model.
935 pub(crate) fn max_moe_block(&self) -> usize {
936 use crate::hybrid::Ffn;
937 let mut mx = 0usize;
938 for l in &self.layers {
939 if let Ffn::Moe(m) = &l.ffn {
940 mx = mx
941 .max(m.gate_exps.max_expert_bytes())
942 .max(m.up_exps.max_expert_bytes())
943 .max(m.down_exps.max_expert_bytes());
944 }
945 }
946 mx
947 }
948
949 /// Gather embedding rows into f32 [T, n_embd] (token-major) by dequantizing only the needed
950 /// rows from the host-side embedding bytes (token_embd is [n_embd, n_vocab], row per token).
951 pub fn embed_tokens(
952 &self,
953 e: &Engine,
954 tokens: &[u32],
955 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
956 let n_embd = self.cfg.n_embd as usize;
957 let x = self.embd.gather(n_embd, tokens);
958 Ok(e.htod(&x)?)
959 }
960}
961
962pub type TensorMap = HashMap<String, GpuTensor>;
963
964/// One layer's stacked 256-expert tensor, raw GGUF quant bytes held HOST-RESIDENT.
965///
966/// EDGE-1: these bytes are NEVER uploaded at load (uploading 29.75GB would OOM a 24GB GPU —
967/// this is BUG-4). Per token, only the 8 routed experts are staged H2D into a small GPU scratch.
968///
969/// ne = [in_f, out_f, n_expert]; the expert axis (ne[2]) is the slowest/highest-stride axis, so
970/// expert `e` occupies the CONTIGUOUS byte block `bytes[e*expert_stride .. (e+1)*expert_stride]`.
971///
972/// THE 3D FIX: GpuTensor::load computes `row_bytes = raw.len()/ne[1]`, which for a stacked 3D
973/// tensor ignores the 256-expert axis and is 256x too large (gate_exps -> 430080 instead of 1680).
974/// load() here uses `row_bytes = raw.len() / (out_f * n_expert)` (= 1680 gate/up, 544 down).
975/// Host byte storage for the expert blocks. Default = a pageable `Vec<u8>` (current behavior). Under
976/// MEMRA_MOE_PINNED (auto-on when MEMRA_MOE_CACHE is set), the bytes live in CUDA pinned host memory so
977/// the miss-path `memcpy_htod` is a true DMA, not a pageable bounce copy (MOE-SLRU-PLAN §C.1).
978///
979/// CAVEAT (§C.1): `alloc_pinned` uses CU_MEMHOSTALLOC_WRITECOMBINED — great for H2D-only (the expert
980/// bytes are never read by the CPU on the hot path), but write-combined memory is SLOW for CPU reads.
981/// A future CPU-VNNI cold-expert fallback must NOT read from this buffer.
982pub enum HostBuf {
983 Paged(Vec<u8>),
984 /// Pinned host memory. We keep the `PinnedHostSlice` alive (it owns the allocation; Drop frees it)
985 /// AND cache its raw base pointer + len so the hot-path `as_bytes()` needs no per-call event sync.
986 Pinned {
987 slice: std::sync::Arc<cudarc::driver::PinnedHostSlice<u8>>,
988 base: *const u8,
989 len: usize,
990 },
991 /// Alias into a shared pinned slab (ST pinned tier): `owner` keeps the slab alive; `base`/`len`
992 /// select this expert's window. Same DMA class as `Pinned`.
993 PinnedAlias {
994 owner: std::sync::Arc<HostBuf>,
995 base: *const u8,
996 len: usize,
997 },
998 /// SPILLING-PLAN §1, Tier 2 (disk): the bytes live in an mmap'd region of the GGUF file, NOT in
999 /// RAM. `map` is `MAP_SHARED`, no `MAP_POPULATE` — zero upfront copy. The first `memcpy_htod` of
1000 /// this slice page-faults → NVMe read → DMA (the demand-fault disk path). `off`/`len` select this
1001 /// expert's contiguous block within the shared file mmap. Bit-identical to `Paged`/`Pinned` —
1002 /// those copied FROM exactly these on-disk bytes, so the GEMM result is unchanged.
1003 Mmap {
1004 map: std::sync::Arc<memmap2::Mmap>,
1005 /// The same opened inode backing `map`. It must outlive the loader source so future explicit
1006 /// positioned reads cannot accidentally reopen a replaced path.
1007 file: std::sync::Arc<std::fs::File>,
1008 /// Absolute byte offset within both the whole-file mmap and `file`.
1009 off: usize,
1010 len: usize,
1011 },
1012}
1013// SAFETY: `base` is a stable pinned-host pointer owned by `slice`; the buffer is written once at load
1014// then only READ for H2D. HostExps is shared `&` across the (single per-Engine) forward, so Send/Sync
1015// mirror the underlying PinnedHostSlice (which is already Send+Sync). The `Mmap` arm holds
1016// `Arc<Mmap>` + `Arc<File>` (both Send+Sync) plus plain usize fields, so it does not weaken bounds.
1017unsafe impl Send for HostBuf {}
1018unsafe impl Sync for HostBuf {}
1019impl HostBuf {
1020 #[inline]
1021 pub fn as_bytes(&self) -> &[u8] {
1022 match self {
1023 HostBuf::Paged(v) => v.as_slice(),
1024 // SAFETY: base+len are the pinned allocation's stable extent; written once at load, then
1025 // read-only. We avoid `as_slice()` here because it would synchronize the buffer's event
1026 // on every hot-path call.
1027 HostBuf::Pinned { base, len, .. } => unsafe { std::slice::from_raw_parts(*base, *len) },
1028 HostBuf::PinnedAlias { base, len, .. } => unsafe {
1029 std::slice::from_raw_parts(*base, *len)
1030 },
1031 // Slicing the mmap is the same `&[u8]` the kernel DMAs; the read page-faults the NVMe.
1032 HostBuf::Mmap { map, off, len, .. } => &map[*off..*off + *len],
1033 }
1034 }
1035 #[inline]
1036 pub fn len(&self) -> usize {
1037 match self {
1038 HostBuf::Paged(v) => v.len(),
1039 HostBuf::Pinned { len, .. } => *len,
1040 HostBuf::PinnedAlias { len, .. } => *len,
1041 HostBuf::Mmap { len, .. } => *len,
1042 }
1043 }
1044
1045 /// Best-effort OS read-ahead for a future mmap-backed expert range. This does not touch or
1046 /// copy the bytes, so the zero-copy ownership contract is unchanged. Non-mmap buffers are
1047 /// already resident and need no advice. Kept fallible-at-the-OS but non-fatal at the call site:
1048 /// an unsupported/pressured kernel simply leaves the normal demand-fault path in place.
1049 #[inline]
1050 pub fn advise_willneed(&self, rel_off: usize, len: usize) -> bool {
1051 let HostBuf::Mmap {
1052 map,
1053 off,
1054 len: extent,
1055 ..
1056 } = self
1057 else {
1058 return false;
1059 };
1060 if len == 0 || rel_off > *extent || len > *extent - rel_off {
1061 return false;
1062 }
1063 #[cfg(unix)]
1064 {
1065 map.advise_range(memmap2::Advice::WillNeed, *off + rel_off, len)
1066 .is_ok()
1067 }
1068 #[cfg(not(unix))]
1069 {
1070 let _ = (map, off);
1071 false
1072 }
1073 }
1074
1075 #[inline]
1076 fn expert_source(&self, rel_off: usize, len: usize) -> ExpertSource<'_> {
1077 debug_assert!(rel_off <= self.len() && len <= self.len() - rel_off);
1078 match self {
1079 HostBuf::Mmap { map, file, off, .. } => {
1080 let offset = *off + rel_off;
1081 ExpertSource::Disk {
1082 file,
1083 offset: offset as u64,
1084 len,
1085 fallback: &map[offset..offset + len],
1086 keepalive: ExpertKeepalive::Mmap(map.clone()),
1087 }
1088 }
1089 HostBuf::Pinned { slice, .. } => ExpertSource::Memory {
1090 bytes: &self.as_bytes()[rel_off..rel_off + len],
1091 keepalive: Some(ExpertKeepalive::Pinned(slice.clone())),
1092 },
1093 HostBuf::PinnedAlias { owner, .. } => ExpertSource::Memory {
1094 bytes: &self.as_bytes()[rel_off..rel_off + len],
1095 keepalive: Some(ExpertKeepalive::Buffer(owner.clone())),
1096 },
1097 HostBuf::Paged(_) => ExpertSource::Memory {
1098 bytes: &self.as_bytes()[rel_off..rel_off + len],
1099 // CUDA stages pageable input before returning from the async-copy API. Only true
1100 // pinned and mmap-backed sources need an explicit lifetime owner in the cache.
1101 keepalive: None,
1102 },
1103 }
1104 }
1105}
1106
1107/// Clonable ownership retained by asynchronous cache transfers. The payload is intentionally never
1108/// read: keeping it alive is the contract.
1109#[allow(dead_code)]
1110pub(crate) enum ExpertKeepalive {
1111 Pinned(std::sync::Arc<cudarc::driver::PinnedHostSlice<u8>>),
1112 Buffer(std::sync::Arc<HostBuf>),
1113 Mmap(std::sync::Arc<memmap2::Mmap>),
1114}
1115
1116/// Source-aware view of one expert block. The mmap fallback remains the byte oracle; retaining the
1117/// opened file enables a later explicit-read backend without changing tensor layout or numerics.
1118pub(crate) enum ExpertSource<'a> {
1119 Memory {
1120 bytes: &'a [u8],
1121 keepalive: Option<ExpertKeepalive>,
1122 },
1123 Disk {
1124 file: &'a std::sync::Arc<std::fs::File>,
1125 offset: u64,
1126 len: usize,
1127 fallback: &'a [u8],
1128 keepalive: ExpertKeepalive,
1129 },
1130}
1131
1132/// One layer's stacked 256-expert tensor, raw GGUF quant bytes held HOST-RESIDENT.
1133///
1134/// EDGE-1: these bytes are NEVER uploaded at load (uploading 29.75GB would OOM a 24GB GPU —
1135/// this is BUG-4). Per token, only the 8 routed experts are staged H2D into a small GPU scratch.
1136///
1137/// ne = [in_f, out_f, n_expert]; the expert axis (ne[2]) is the slowest/highest-stride axis, so
1138/// expert `e` occupies the CONTIGUOUS byte block `bytes[e*expert_stride .. (e+1)*expert_stride]`.
1139///
1140/// THE 3D FIX: GpuTensor::load computes `row_bytes = raw.len()/ne[1]`, which for a stacked 3D
1141/// tensor ignores the 256-expert axis and is 256x too large (gate_exps -> 430080 instead of 1680).
1142/// load() here uses `row_bytes = raw.len() / (out_f * n_expert)` (= 1680 gate/up, 544 down).
1143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1144pub struct ExpertLayout {
1145 pub offset: usize,
1146 pub len: usize,
1147 pub qtype: i32,
1148 pub row_bytes: usize,
1149}
1150
1151fn staged_expert_qtype(ty: GgmlType) -> Option<i32> {
1152 Some(match ty {
1153 GgmlType::Q8_0 => QT_Q8_0,
1154 GgmlType::Q2_K => QT_Q2_K,
1155 GgmlType::Q4_K => QT_Q4_K,
1156 GgmlType::Q6_K => QT_Q6_K,
1157 GgmlType::Q5_K => QT_Q5_K,
1158 GgmlType::Q3_K => QT_Q3_K,
1159 GgmlType::IQ4_XS => QT_IQ4_XS,
1160 GgmlType::IQ3_S => QT_IQ3_S,
1161 GgmlType::NVFP4 => QT_NVFP4,
1162 GgmlType::F32 => QT_F32,
1163 GgmlType::BF16 => QT_BF16,
1164 _ => return None,
1165 })
1166}
1167
1168fn staged_expert_row_bytes(ty: GgmlType, in_f: usize) -> Option<usize> {
1169 staged_expert_qtype(ty)?;
1170 let (block, type_size) = ty.block_and_type_size();
1171 assert_eq!(
1172 in_f as u64 % block,
1173 0,
1174 "expert row width {in_f} is not divisible by {ty:?} block {block}"
1175 );
1176 Some((in_f as u64 / block * type_size) as usize)
1177}
1178
1179fn find_expert_disk_strict(
1180 src: &dyn TensorSource,
1181 name: &str,
1182) -> Result<Option<DiskExtent>, Box<dyn std::error::Error>> {
1183 if let Some(extent) = src.find_expert_disk(name) {
1184 return Ok(Some(extent));
1185 }
1186 if src.find_expert_mmap(name).is_some() {
1187 return Err(std::io::Error::new(
1188 std::io::ErrorKind::InvalidData,
1189 format!(
1190 "expert tensor {name} exposes legacy find_expert_mmap without find_expert_disk; \
1191 disk-backed expert loading requires a retained Arc<File>"
1192 ),
1193 )
1194 .into());
1195 }
1196 Ok(None)
1197}
1198
1199pub struct HostExps {
1200 pub bytes: HostBuf, // raw GGUF block bytes (host); per-token DMA src for the 8 routed exps
1201 /// SPILLING-PLAN §1.1: per-expert backing tier. `None` => the layer fits in one `bytes` store and
1202 /// every expert slices it (the unchanged in-RAM path). `Some` => per-expert split: the hottest
1203 /// experts are `Pinned` (Tier 1, fast async DMA), the rest `Mmap` into the GGUF (Tier 2, disk
1204 /// demand-fault). `expert_bytes(e)` resolves `tiers[e]` if present, else slices `bytes`.
1205 pub tiers: Option<Vec<HostBuf>>,
1206 pub qtype: i32, // QT_Q6_K (gate/up) | QT_Q8_0 (down)
1207 pub in_f: usize, // ne[0] (gate/up = 2048, down = 512)
1208 pub out_f: usize, // ne[1] (gate/up = 512, down = 2048)
1209 pub n_expert: usize, // ne[2] = 256
1210 pub row_bytes: usize, // raw.len()/(out_f*n_expert) -> 1680 (gate/up) / 544 (down)
1211 pub expert_stride: usize, // raw.len()/n_expert -> 860160 (gate/up) / 1114112 (down)
1212 /// Per-expert encoding metadata when experts in this projection do not share one dtype/layout.
1213 /// `None` preserves the existing uniform slab contract and every resident/fused fast path.
1214 /// `Some` routes through the per-expert staged/cache path, using each entry's qtype/row size.
1215 pub layouts: Option<Vec<ExpertLayout>>,
1216 /// Per-expert post-matmul macro-scale (ModelOpt NVFP4 `weight_scale_2`, one scalar per expert
1217 /// tensor). `None` => all 1.0 (GGUF experts; block scales carry everything). The MoE forward
1218 /// folds gate/up macros into the activation epilogue (gs/us) and the down macro into the
1219 /// per-expert accumulate weight.
1220 pub macros: Option<Vec<f32>>,
1221}
1222
1223impl HostExps {
1224 /// Load a stacked 3D expert tensor, keeping its quant bytes on the HOST. `e` supplies the CUDA
1225 /// context for the optional pinned allocation (§C.1). Default storage is pageable `Vec<u8>`
1226 /// (identical to the prior behavior); pinned is chosen when MEMRA_MOE_PINNED or MEMRA_MOE_CACHE is set.
1227 pub fn load(e: &Engine, g: &GgufFile, name: &str) -> Result<Self, Box<dyn std::error::Error>> {
1228 Self::load_stacked_from_source(e, &GgufSource(g), name)
1229 }
1230
1231 /// Load a STACKED 3D expert tensor (`ne=[in_f,out_f,n_expert]`) from any source. GGUF stores the
1232 /// experts this way; the source returns the same mmap bytes (`GgufSource::find` == `tensor_data`),
1233 /// so the GGUF path is byte-identical to the prior direct-`GgufFile` loader. (Safetensors stores N
1234 /// 2D tensors instead — those go through `load_from_source`, which gathers them.)
1235 /// Row-range variant for FUSED stacked tensors (gemma4 ffn_gate_up_exps: gate = rows
1236 /// [0,ff), up = [ff,2ff) per expert — llama-graph view convention). Copies only the range.
1237 pub fn load_stacked_split_from_source(
1238 e: &Engine,
1239 src: &dyn TensorSource,
1240 name: &str,
1241 row0: usize,
1242 row1: usize,
1243 ) -> Result<Self, Box<dyn std::error::Error>> {
1244 let t = src
1245 .find(name)
1246 .unwrap_or_else(|| panic!("missing exps tensor {name}"));
1247 assert_eq!(t.ne.len(), 3, "{name} is not 3D (ne={:?})", t.ne);
1248 let qtype = match t.ggml_type {
1249 GgmlType::Q8_0 => QT_Q8_0,
1250 GgmlType::Q4_K => QT_Q4_K,
1251 GgmlType::Q6_K => QT_Q6_K,
1252 GgmlType::Q5_K => QT_Q5_K,
1253 GgmlType::Q3_K => QT_Q3_K,
1254 GgmlType::IQ4_XS => QT_IQ4_XS,
1255 GgmlType::IQ3_S => QT_IQ3_S,
1256 GgmlType::NVFP4 => QT_NVFP4,
1257 GgmlType::Q4_0 => QT_Q4_0,
1258 other => panic!("exps {name} unsupported quant {other:?}"),
1259 };
1260 let raw: &[u8] = &t.bytes;
1261 let in_f = t.ne[0] as usize;
1262 let out_full = t.ne[1] as usize;
1263 let n_expert = t.ne[2] as usize;
1264 let full_stride = raw.len() / n_expert;
1265 let row_bytes = raw.len() / (out_full * n_expert);
1266 assert_eq!(full_stride, out_full * row_bytes, "{name} stride mismatch");
1267 let out_f = row1 - row0;
1268 let expert_stride = out_f * row_bytes;
1269 let mut buf = vec![0u8; n_expert * expert_stride];
1270 for ex in 0..n_expert {
1271 let s0 = ex * full_stride + row0 * row_bytes;
1272 buf[ex * expert_stride..(ex + 1) * expert_stride]
1273 .copy_from_slice(&raw[s0..s0 + expert_stride]);
1274 }
1275 let pinned = std::env::var("MEMRA_MOE_PINNED").is_ok()
1276 || std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0");
1277 let bytes = if pinned {
1278 let mut pn = unsafe { e.ctx().alloc_pinned::<u8>(buf.len())? };
1279 {
1280 let dst = pn.as_mut_slice()?;
1281 dst.copy_from_slice(&buf);
1282 }
1283 let base = pn.as_ptr()? as *const u8;
1284 let len = buf.len();
1285 HostBuf::Pinned {
1286 slice: std::sync::Arc::new(pn),
1287 base,
1288 len,
1289 }
1290 } else {
1291 HostBuf::Paged(buf)
1292 };
1293 Ok(HostExps {
1294 bytes,
1295 tiers: None,
1296 qtype,
1297 in_f,
1298 out_f,
1299 n_expert,
1300 row_bytes,
1301 expert_stride,
1302 layouts: None,
1303 macros: None,
1304 })
1305 }
1306
1307 /// Stacked per-expert macro-scale sidecar: `blk.N.ffn_{proj}_exps.scale` f32 [n_expert]
1308 /// (the qwen3.6 NVFP4 converter emits one per stacked expert tensor — compressed-tensors
1309 /// global scales, inverted to multipliers). Absent (every k-quant GGUF) => None.
1310 /// NOTE gemma4 consumes ffn_down_exps.scale through its OWN router-fold (Gemma4MoeBits) —
1311 /// its MoE forward does not read HostExps::macros, so a Some here is inert there.
1312 fn stacked_macros(src: &dyn TensorSource, name: &str) -> Option<Vec<f32>> {
1313 let stem = name.strip_suffix(".weight")?;
1314 let sv = src.find(&format!("{stem}.scale"))?;
1315 if sv.ggml_type != GgmlType::F32 { return None; }
1316 let macros: Vec<f32> = sv.bytes.chunks_exact(4)
1317 .map(|c| f32::from_le_bytes(c.try_into().unwrap())).collect();
1318 if macros.iter().all(|&m| m == 1.0) { None } else { Some(macros) }
1319 }
1320
1321 pub fn load_stacked_from_source(e: &Engine, src: &dyn TensorSource, name: &str)
1322 -> Result<Self, Box<dyn std::error::Error>> {
1323 let t = src.find(name).unwrap_or_else(|| panic!("missing exps tensor {name}"));
1324 assert_eq!(t.ne.len(), 3, "{name} is not a 3D stacked-expert tensor (ne={:?})", t.ne);
1325 // MMAP-BACKED SPILL TIER (Hy3 repack dir, 2026-07-09): when the source's on-disk layout IS
1326 // already the engine's expert layout (one expert-axis-slowest slab file per (layer, proj),
1327 // the transcoder's contract), back the HostExps with `HostBuf::Mmap` directly — ZERO host
1328 // copy. The default copy path below would pin/allocate the WHOLE stacked slab (80.5 GB for
1329 // Hy3-REAP50 on a 60 GB host = the M3 first-load OOM class); the mmap tier instead lets the
1330 // page cache carry the hot expert mass (RAM tier) and demand-faults the overflow from NVMe,
1331 // exactly like the proven M3 `.memra-repack` path (model.rs NVFP4 disk arm). Bit-identity:
1332 // `expert_bytes(e)` slices the same on-disk bytes the copy would have staged. The SLRU VRAM
1333 // cache stacks on top unchanged. The configured whole-map advice is applied at source open.
1334 if let Some(DiskExtent {
1335 map,
1336 file,
1337 offset,
1338 len,
1339 }) = find_expert_disk_strict(src, name)?
1340 {
1341 let off = usize::try_from(offset)
1342 .map_err(|_| format!("{name} disk offset {offset} does not fit usize"))?;
1343 let qtype = match t.ggml_type {
1344 GgmlType::Q8_0 => QT_Q8_0,
1345 GgmlType::Q4_K => QT_Q4_K,
1346 GgmlType::Q6_K => QT_Q6_K,
1347 GgmlType::Q5_K => QT_Q5_K,
1348 GgmlType::Q3_K => QT_Q3_K,
1349 GgmlType::IQ4_XS => QT_IQ4_XS,
1350 GgmlType::IQ3_S => QT_IQ3_S,
1351 GgmlType::NVFP4 => QT_NVFP4,
1352 GgmlType::Q4_0 => QT_Q4_0,
1353 other => panic!("exps {name} unsupported quant {other:?}"),
1354 };
1355 let in_f = t.ne[0] as usize;
1356 let out_f = t.ne[1] as usize;
1357 let n_expert = t.ne[2] as usize;
1358 let expert_stride = len / n_expert;
1359 let row_bytes = len / (out_f * n_expert);
1360 assert_eq!(expert_stride, out_f * row_bytes,
1361 "{name} stride mismatch: stride={expert_stride} out_f={out_f} row_bytes={row_bytes}");
1362 assert_eq!(
1363 len,
1364 n_expert * expert_stride,
1365 "{name} mmap len != n_expert*stride"
1366 );
1367 return Ok(HostExps {
1368 bytes: HostBuf::Mmap { map, file, off, len },
1369 tiers: None, qtype, in_f, out_f, n_expert, row_bytes, expert_stride,
1370 layouts: None, macros: Self::stacked_macros(src, name),
1371 });
1372 }
1373 let raw: &[u8] = &t.bytes;
1374 // All quant types the staged-expert qmatvec can decode (dp4a-fast or Stage-A f32).
1375 let qtype = match t.ggml_type {
1376 GgmlType::Q8_0 => QT_Q8_0,
1377 GgmlType::Q4_K => QT_Q4_K,
1378 GgmlType::Q6_K => QT_Q6_K,
1379 GgmlType::Q5_K => QT_Q5_K,
1380 GgmlType::Q3_K => QT_Q3_K,
1381 GgmlType::IQ4_XS => QT_IQ4_XS,
1382 GgmlType::IQ3_S => QT_IQ3_S,
1383 GgmlType::NVFP4 => QT_NVFP4,
1384 GgmlType::Q4_0 => QT_Q4_0,
1385 other => panic!("exps {name} unsupported quant {other:?}"),
1386 };
1387 let in_f = t.ne[0] as usize;
1388 let out_f = t.ne[1] as usize;
1389 let n_expert = t.ne[2] as usize;
1390 // VERIFIED: gate/up Q6_K total/256 = 860160; row = total/(512*256) = 1680.
1391 // down Q8_0 total/256 = 1114112; row = total/(2048*256) = 544.
1392 let expert_stride = raw.len() / n_expert;
1393 let row_bytes = raw.len() / (out_f * n_expert);
1394 // sanity: expert_stride must equal out_f * row_bytes exactly (catches a dim mixup)
1395 assert_eq!(
1396 expert_stride,
1397 out_f * row_bytes,
1398 "{name} stride mismatch: stride={expert_stride} out_f={out_f} row_bytes={row_bytes}"
1399 );
1400
1401 let pinned = std::env::var("MEMRA_MOE_PINNED").is_ok()
1402 || std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0");
1403 let bytes = if pinned {
1404 // alloc pinned host memory, copy the GGUF block bytes in once, cache the base pointer.
1405 let mut p = unsafe { e.ctx().alloc_pinned::<u8>(raw.len())? };
1406 {
1407 let dst = p.as_mut_slice()?;
1408 dst.copy_from_slice(raw);
1409 }
1410 let base = p.as_ptr()? as *const u8; // syncs once here at load; stable afterward
1411 let len = raw.len();
1412 HostBuf::Pinned {
1413 slice: std::sync::Arc::new(p),
1414 base,
1415 len,
1416 }
1417 } else {
1418 HostBuf::Paged(raw.to_vec())
1419 };
1420 Ok(HostExps { bytes, tiers: None, qtype, in_f, out_f, n_expert, row_bytes,
1421 expert_stride, layouts: None, macros: Self::stacked_macros(src, name) })
1422 }
1423
1424 /// SPILLING-PLAN §1.1, §2 step 4: load a stacked 3D expert tensor with a PER-EXPERT tier split.
1425 /// Under `MEMRA_SPILL_DISK`, the hottest experts (greedy in expert order, until the shared pinned
1426 /// budget in `ctx` is exhausted) get `HostBuf::Pinned` (Tier 1, fast async DMA); every remaining
1427 /// expert is `HostBuf::Mmap` into the GGUF (Tier 2, demand-faulted from disk on first H2D). The
1428 /// resulting bytes are bit-identical to the in-RAM path either way — `qmatvec_view` is untouched.
1429 ///
1430 /// `ctx.file_map` is ONE shared `MAP_SHARED` mmap of the whole GGUF (`Arc`-cloned per spilled
1431 /// expert), so the 120 expert tensors of a 40-layer MoE never open the file more than once.
1432 pub fn load_tiered(
1433 e: &Engine,
1434 g: &GgufFile,
1435 name: &str,
1436 ctx: &mut crate::spill::SpillCtx,
1437 ) -> Result<Self, Box<dyn std::error::Error>> {
1438 let t = g
1439 .find(name)
1440 .unwrap_or_else(|| panic!("missing exps tensor {name}"));
1441 assert_eq!(
1442 t.ne.len(),
1443 3,
1444 "{name} is not a 3D stacked-expert tensor (ne={:?})",
1445 t.ne
1446 );
1447 let raw = g.tensor_data(t);
1448 let qtype = match t.ggml_type {
1449 GgmlType::Q8_0 => QT_Q8_0,
1450 GgmlType::Q4_K => QT_Q4_K,
1451 GgmlType::Q6_K => QT_Q6_K,
1452 GgmlType::Q5_K => QT_Q5_K,
1453 GgmlType::Q3_K => QT_Q3_K,
1454 GgmlType::IQ4_XS => QT_IQ4_XS,
1455 GgmlType::IQ3_S => QT_IQ3_S,
1456 GgmlType::NVFP4 => QT_NVFP4,
1457 GgmlType::Q4_0 => QT_Q4_0,
1458 other => panic!("exps {name} unsupported quant {other:?}"),
1459 };
1460 let in_f = t.ne[0] as usize;
1461 let out_f = t.ne[1] as usize;
1462 let n_expert = t.ne[2] as usize;
1463 let expert_stride = raw.len() / n_expert;
1464 let row_bytes = raw.len() / (out_f * n_expert);
1465 assert_eq!(
1466 expert_stride,
1467 out_f * row_bytes,
1468 "{name} stride mismatch: stride={expert_stride} out_f={out_f} row_bytes={row_bytes}"
1469 );
1470
1471 // Absolute file offset of this tensor's data (start of expert 0); each expert is the next
1472 // `expert_stride` bytes. The `Mmap` arm slices `ctx.file_map` at these offsets.
1473 let (file_start, _file_end) = g.tensor_file_range(t);
1474
1475 // Per-expert tier decision under the shared running budget. `bytes` keeps a 0-byte sentinel
1476 // (`Paged(empty)`) since every read now goes through `tiers`.
1477 let mut tiers = Vec::with_capacity(n_expert);
1478 for ex in 0..n_expert {
1479 let blk = &raw[ex * expert_stride..(ex + 1) * expert_stride];
1480 let file_off = file_start + ex * expert_stride;
1481 tiers.push(crate::spill::place_expert(ctx, e, blk, file_off)?);
1482 }
1483 Ok(HostExps {
1484 bytes: HostBuf::Paged(Vec::new()), // unused when `tiers` is Some
1485 tiers: Some(tiers),
1486 qtype, in_f, out_f, n_expert, row_bytes, expert_stride, layouts: None,
1487 macros: Self::stacked_macros(&GgufSource(g), name),
1488 })
1489 }
1490
1491 /// MoE expert GATHER from a `TensorSource` (the safetensors path; ST-MOE-PLAN §1.3). GGUF stacks
1492 /// all experts into ONE 3D tensor; HF stores them as N separate 2D tensors
1493 /// `model.layers.{il}.mlp.experts.{e}.{gate,up,down}_proj.weight`. `find` returns `None` for the
1494 /// ggml `*_exps` name on purpose, so the experts are gathered out-of-band here.
1495 ///
1496 /// PATH A (load-time only, no quantize): each HF 2D expert tensor is dequantized to f32 and the
1497 /// per-expert blocks are concatenated expert-axis-slowest into ONE contiguous buffer — exactly the
1498 /// layout `expert_bytes(e)` slices and the staged `qmatvec_view` (qtype=QT_F32) reads. The same
1499 /// `expert_stride == out_f*row_bytes` invariant as the GGUF path is asserted at the end.
1500 ///
1501 /// `ggml_exps_name` is `blk.{il}.ffn_{gate,up,down}_exps.weight`; it is split to recover `il` and
1502 /// the proj. `n_expert` comes from `cfg.moe`. The HF per-expert literal `mlp.experts.{e}.{p}_proj`
1503 /// is the qwen3moe / olmoe layout (a future arch with `block_sparse_moe.experts.*` would need a
1504 /// branch in `hf_expert_name`).
1505 pub fn load_from_source(
1506 e: &Engine,
1507 src: &dyn TensorSource,
1508 ggml_exps_name: &str,
1509 n_expert: usize,
1510 ) -> Result<Self, Box<dyn std::error::Error>> {
1511 // Recover il + proj from `blk.{il}.ffn_{gate,up,down}_exps.weight`.
1512 let rest = ggml_exps_name
1513 .strip_prefix("blk.")
1514 .unwrap_or_else(|| panic!("not a blk.* name: {ggml_exps_name}"));
1515 let (il_s, suffix) = rest.split_once('.').unwrap();
1516 let il: u32 = il_s.parse().unwrap();
1517 let proj = match suffix {
1518 "ffn_gate_exps.weight" => "gate",
1519 "ffn_up_exps.weight" => "up",
1520 "ffn_down_exps.weight" => "down",
1521 other => panic!("not a *_exps suffix: {other}"),
1522 };
1523
1524 // A mixed-precision safetensors/repack source exposes experts as separate 2D tensors.
1525 // Detect a dtype/layout change before the uniform gather paths normalize the whole layer
1526 // to one encoding. Uniform checkpoints take the unchanged optimized path below.
1527 let mut signatures = Vec::with_capacity(n_expert);
1528 let active = src.active_experts(il);
1529 for ex in 0..n_expert {
1530 if active.is_some_and(|mask| !mask[ex]) {
1531 signatures.push((i32::MIN, 0));
1532 continue;
1533 }
1534 let name = format!("blk.{il}.ffn_{proj}_exps.{ex}.weight");
1535 if let Some(nv) = src.find_nvfp4_native(&name) {
1536 signatures.push((QT_NVFP4, nv.in_f / 64 * 36));
1537 } else {
1538 let v = src
1539 .find(&name)
1540 .unwrap_or_else(|| panic!("missing expert tensor {name}"));
1541 let in_f = v.ne[0] as usize;
1542 signatures.push(match staged_expert_row_bytes(v.ggml_type, in_f) {
1543 Some(row_bytes) => (staged_expert_qtype(v.ggml_type).unwrap(), row_bytes),
1544 None => (QT_F32, in_f * 4),
1545 });
1546 }
1547 }
1548 let mixed_layout = signatures.windows(2).any(|pair| pair[0] != pair[1]);
1549 if src.preserve_expert_encodings() && !mixed_layout {
1550 if let Some(uniform) = Self::load_uniform_mmap_from_source(src, il, proj, n_expert)? {
1551 return Ok(uniform);
1552 }
1553 }
1554 if src.preserve_expert_encodings() || mixed_layout {
1555 return Self::load_mixed_from_source(src, il, proj, n_expert);
1556 }
1557
1558 // PATH B (NVFP4-NATIVE GATHER, 2026-07-05): when the source exposes the experts as packed
1559 // ModelOpt/Reza NVFP4 (find_nvfp4_native), keep them QUANTIZED — repack each expert's
1560 // modelopt bytes to the GGUF 36B-block layout the staged qmatvec decodes, and concatenate.
1561 // No f32 blow-up: a 129GB checkpoint gathers to ~the same bytes instead of ~8x (which is
1562 // what makes MiniMax-M3 REAP50 loadable on a 60GB-RAM host at all, with spill on top).
1563 // Per-expert `weight_scale_2` macros go to `macros` (folded post-matmul by the MoE forward).
1564 {
1565 let name0 = format!("blk.{il}.ffn_{proj}_exps.0.weight");
1566 if let Some(nv0) = src.find_nvfp4_native(&name0) {
1567 let (in_f, out_f) = (nv0.in_f, nv0.out_f);
1568 let row_bytes = in_f / 64 * 36;
1569 let expert_stride = out_f * row_bytes;
1570 // ST DISK TIER (2026-07-06, the MiniMax OOM fix): when the total expert bytes
1571 // exceed host RAM (M3 REAP50 = 122GB repacked on a 60GB host, first-load host-OOM
1572 // at layer ~24), repack each layer ONCE into an on-disk cache file next to the
1573 // checkpoint and mmap it (HostBuf::Mmap, MAP_SHARED no-populate — the same tier-2
1574 // mechanism the GGUF spill path uses). Reloads hit the cache (size-checked), pay
1575 // zero repack. MEMRA_ST_REPACK_DISK=0 forces the old in-RAM gather.
1576 let disk = std::env::var("MEMRA_ST_REPACK_DISK")
1577 .map(|v| v != "0")
1578 .unwrap_or(true)
1579 && src.st_dir().is_some();
1580 let cache_path = src.st_dir().map(|d| {
1581 let cd = d.join(".memra-repack");
1582 let _ = std::fs::create_dir_all(&cd);
1583 cd.join(format!("blk{il}-{proj}-{n_expert}x{out_f}x{in_f}.nvfp4"))
1584 });
1585 let total = n_expert * expert_stride;
1586 let mut macros = vec![1.0f32; n_expert];
1587 let read_macros = |macros: &mut Vec<f32>| {
1588 for ex in 0..n_expert {
1589 let stem = format!("blk.{il}.ffn_{proj}_exps.{ex}");
1590 if let Some(sv) = src.find(&format!("{stem}.scale")) {
1591 macros[ex] = f32::from_le_bytes(sv.bytes[..4].try_into().unwrap());
1592 }
1593 }
1594 };
1595 let bytes = if disk {
1596 let cp = cache_path.as_ref().unwrap();
1597 let fresh = std::fs::metadata(cp)
1598 .map(|m| m.len() as usize == total)
1599 .unwrap_or(false);
1600 if !fresh {
1601 // stream one expert at a time to disk — peak RAM = one expert (~8MB)
1602 use std::io::Write;
1603 let mut f = std::io::BufWriter::new(std::fs::File::create(cp)?);
1604 for ex in 0..n_expert {
1605 let name = format!("blk.{il}.ffn_{proj}_exps.{ex}.weight");
1606 let nv = src.find_nvfp4_native(&name).unwrap_or_else(|| {
1607 panic!("expert {name} lost NVFP4-native mid-gather")
1608 });
1609 assert_eq!(
1610 (nv.in_f, nv.out_f),
1611 (in_f, out_f),
1612 "expert {ex} dims ({},{}) != expert 0 ({in_f},{out_f})",
1613 nv.in_f,
1614 nv.out_f
1615 );
1616 f.write_all(&memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
1617 nv.wbytes, nv.wscale, out_f, in_f,
1618 ))?;
1619 }
1620 f.flush()?;
1621 }
1622 read_macros(&mut macros);
1623 let file = std::sync::Arc::new(std::fs::File::open(cp)?);
1624 let map = unsafe { memmap2::Mmap::map(file.as_ref())? };
1625 assert_eq!(map.len(), total, "repack cache {cp:?} size mismatch");
1626 // Default random preserves the original policy; normal lets Linux readahead
1627 // within each multi-megabyte expert on the spill-bound path.
1628 let _ = memra_gguf::source::apply_expert_mmap_advice(&map);
1629 let map = std::sync::Arc::new(map);
1630 // ST PINNED TIER (2026-07-07, the M3 1.5-tok/s lever): mmap-only backing makes
1631 // every SLRU miss a page-cache (or NVMe) synchronous read into the H2D copy.
1632 // Pin as many experts as the live budget allows (same MemBudget probe + 0.6
1633 // MemAvailable cap as the GGUF spill tier) — pinned pages upload via true
1634 // async DMA at full PCIe. Budget is GLOBAL across layers (first-come: earlier
1635 // layers pin first; routing is roughly uniform so early-layer bias is benign).
1636 // MEMRA_ST_PINNED=0 disables (pure-mmap, the 2026-07-06 behavior).
1637 // DEFAULT OFF (2026-07-07 measured): with a 122GB expert set on 60GB RAM,
1638 // pinning 26GB EVICTED the page cache backing the mmap tier — every unpinned
1639 // expert faulted cold from NVMe and gen fell 1.5 -> 0.05 tok/s (30x WORSE).
1640 // Pinning only pays when (total - pinned) fits page cache; here it never can.
1641 // MEMRA_ST_PINNED=1 opt-in for fits-in-RAM checkpoints (e.g. REAP-heavier cuts).
1642 let tiers = if std::env::var("MEMRA_ST_PINNED")
1643 .map(|v| v == "1")
1644 .unwrap_or(false)
1645 {
1646 static PIN_BUDGET: std::sync::OnceLock<std::sync::Mutex<usize>> =
1647 std::sync::OnceLock::new();
1648 let budget = PIN_BUDGET.get_or_init(|| {
1649 let b = crate::spill::MemBudget::probe(e)
1650 .map(|b| b.free_pinnable_ram)
1651 .unwrap_or(0);
1652 eprintln!("[st-spill] pinned budget {:.1} GB", b as f64 / 1e9);
1653 std::sync::Mutex::new(b)
1654 });
1655 let mut rem = budget.lock().unwrap();
1656 // ONE pinned slab per file prefix (n_pin experts contiguous): 1 alloc +
1657 // 1 bulk copy instead of n_pin small allocs (per-expert cudaHostAllocs
1658 // stalled the 122GB M3 load >10min).
1659 let n_pin = (*rem / expert_stride).min(n_expert);
1660 if n_pin == 0 {
1661 None
1662 } else {
1663 let slab_len = n_pin * expert_stride;
1664 let mut pn = unsafe { e.ctx().alloc_pinned::<u8>(slab_len)? };
1665 {
1666 let dst = pn.as_mut_slice()?;
1667 dst.copy_from_slice(&map[..slab_len]);
1668 }
1669 let base = pn.as_ptr()? as *const u8;
1670 *rem -= slab_len;
1671 let slab = std::sync::Arc::new(HostBuf::Pinned {
1672 slice: std::sync::Arc::new(pn),
1673 base,
1674 len: slab_len,
1675 });
1676 let mut tiers: Vec<HostBuf> = Vec::with_capacity(n_expert);
1677 for ex in 0..n_expert {
1678 let off = ex * expert_stride;
1679 if ex < n_pin {
1680 tiers.push(HostBuf::PinnedAlias {
1681 owner: slab.clone(),
1682 base: unsafe { base.add(off) },
1683 len: expert_stride,
1684 });
1685 } else {
1686 tiers.push(HostBuf::Mmap {
1687 map: map.clone(),
1688 file: file.clone(),
1689 off,
1690 len: expert_stride,
1691 });
1692 }
1693 }
1694 Some(tiers)
1695 }
1696 } else {
1697 None
1698 };
1699 if let Some(tiers) = tiers {
1700 let all_one = macros.iter().all(|&m| m == 1.0);
1701 return Ok(HostExps {
1702 bytes: HostBuf::Mmap {
1703 map,
1704 file,
1705 off: 0,
1706 len: total,
1707 },
1708 tiers: Some(tiers),
1709 qtype: QT_NVFP4,
1710 in_f,
1711 out_f,
1712 n_expert,
1713 row_bytes,
1714 expert_stride,
1715 layouts: None,
1716 macros: if all_one { None } else { Some(macros) },
1717 });
1718 }
1719 HostBuf::Mmap {
1720 map,
1721 file,
1722 off: 0,
1723 len: total,
1724 }
1725 } else {
1726 let mut buf: Vec<u8> = Vec::with_capacity(total);
1727 for ex in 0..n_expert {
1728 let name = format!("blk.{il}.ffn_{proj}_exps.{ex}.weight");
1729 let nv = src.find_nvfp4_native(&name).unwrap_or_else(|| {
1730 panic!("expert {name} lost NVFP4-native mid-gather")
1731 });
1732 assert_eq!(
1733 (nv.in_f, nv.out_f),
1734 (in_f, out_f),
1735 "expert {ex} dims ({},{}) != expert 0 ({in_f},{out_f})",
1736 nv.in_f,
1737 nv.out_f
1738 );
1739 buf.extend_from_slice(&memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
1740 nv.wbytes, nv.wscale, out_f, in_f,
1741 ));
1742 }
1743 assert_eq!(buf.len(), total);
1744 read_macros(&mut macros);
1745 let pinned = std::env::var("MEMRA_MOE_PINNED").is_ok()
1746 || std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0");
1747 if pinned {
1748 let mut p = unsafe { e.ctx().alloc_pinned::<u8>(buf.len())? };
1749 {
1750 let dst = p.as_mut_slice()?;
1751 dst.copy_from_slice(&buf);
1752 }
1753 let base = p.as_ptr()? as *const u8;
1754 let len = buf.len();
1755 HostBuf::Pinned {
1756 slice: std::sync::Arc::new(p),
1757 base,
1758 len,
1759 }
1760 } else {
1761 HostBuf::Paged(buf)
1762 }
1763 };
1764 let all_one = macros.iter().all(|&m| m == 1.0);
1765 return Ok(HostExps {
1766 bytes,
1767 tiers: None,
1768 qtype: QT_NVFP4,
1769 in_f,
1770 out_f,
1771 n_expert,
1772 row_bytes,
1773 expert_stride,
1774 layouts: None,
1775 macros: if all_one { None } else { Some(macros) },
1776 });
1777 }
1778 }
1779
1780 // expert 0 fixes (in_f, out_f); every later expert must match (catches a layer/arch mixup).
1781 let mut buf: Vec<u8> = Vec::new();
1782 let mut in_f = 0usize;
1783 let mut out_f = 0usize;
1784 for ex in 0..n_expert {
1785 // Per-expert ggml name; the source maps it to the HF expert tensor (ST-MOE-PLAN §1.3).
1786 let name = format!("blk.{il}.ffn_{proj}_exps.{ex}.weight");
1787 let v = src
1788 .find(&name)
1789 .unwrap_or_else(|| panic!("missing expert tensor {name}"));
1790 assert_eq!(v.ne.len(), 2, "expert {name} is not 2D (ne={:?})", v.ne);
1791 let (cur_in, cur_out) = (v.ne[0] as usize, v.ne[1] as usize);
1792 if ex == 0 {
1793 in_f = cur_in;
1794 out_f = cur_out;
1795 } else {
1796 assert_eq!(
1797 (cur_in, cur_out),
1798 (in_f, out_f),
1799 "expert {ex} dims {:?} != expert 0 [{in_f},{out_f}]",
1800 (cur_in, cur_out)
1801 );
1802 }
1803 // PATH A: dequant the 2D expert (F32/F16/BF16) to f32, append its bytes verbatim. The
1804 // dequantized [out_f, in_f] row-major f32 block is exactly one expert_stride slow→fast.
1805 let n = cur_in * cur_out;
1806 let f32v = dequant::dequantize(v.ggml_type, &v.bytes, n);
1807 buf.reserve(n * 4);
1808 for f in &f32v {
1809 buf.extend_from_slice(&f.to_le_bytes());
1810 }
1811 }
1812 let row_bytes = in_f * 4; // one out-row = in_f contiguous f32s
1813 let expert_stride = out_f * row_bytes;
1814 assert_eq!(
1815 buf.len(),
1816 n_expert * expert_stride,
1817 "{ggml_exps_name} gather size {} != n_expert*stride {}",
1818 buf.len(),
1819 n_expert * expert_stride
1820 );
1821 // Hold to the identical invariant as the GGUF path (ST-MOE-PLAN §1.3 step 4).
1822 assert_eq!(expert_stride, out_f * row_bytes,
1823 "{ggml_exps_name} stride mismatch: stride={expert_stride} out_f={out_f} row_bytes={row_bytes}");
1824
1825 // Same pinned-vs-paged choice as the GGUF loader (the bytes are H2D-only on the hot path).
1826 let pinned = std::env::var("MEMRA_MOE_PINNED").is_ok()
1827 || std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0");
1828 let bytes = if pinned {
1829 let mut p = unsafe { e.ctx().alloc_pinned::<u8>(buf.len())? };
1830 {
1831 let dst = p.as_mut_slice()?;
1832 dst.copy_from_slice(&buf);
1833 }
1834 let base = p.as_ptr()? as *const u8;
1835 let len = buf.len();
1836 HostBuf::Pinned {
1837 slice: std::sync::Arc::new(p),
1838 base,
1839 len,
1840 }
1841 } else {
1842 HostBuf::Paged(buf)
1843 };
1844 Ok(HostExps {
1845 bytes,
1846 tiers: None,
1847 qtype: QT_F32,
1848 in_f,
1849 out_f,
1850 n_expert,
1851 row_bytes,
1852 expert_stride,
1853 layouts: None,
1854 macros: None,
1855 })
1856 }
1857
1858 /// Coalesce a uniform v2 overlay back into the existing stacked-slab contract without copying.
1859 /// The artifact stores one record per original expert for coverage validation, but a full-bank
1860 /// uniform arm writes those records contiguously into one file. Keeping `layouts=None` preserves
1861 /// the uniform fused kernels while `HostBuf::Mmap` keeps the >RAM artifact zero-copy.
1862 fn load_uniform_mmap_from_source(
1863 src: &dyn TensorSource,
1864 il: u32,
1865 proj: &str,
1866 n_expert: usize,
1867 ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
1868 if src
1869 .active_experts(il)
1870 .is_some_and(|mask| mask.iter().any(|&active| !active))
1871 {
1872 return Ok(None);
1873 }
1874 let mut first_map = None;
1875 let mut first_file = None;
1876 let mut base_offset = 0u64;
1877 let mut expert_stride = 0usize;
1878 let mut in_f = 0usize;
1879 let mut out_f = 0usize;
1880 let mut qtype = 0i32;
1881 let mut row_bytes = 0usize;
1882 let mut macros = vec![1.0f32; n_expert];
1883 for ex in 0..n_expert {
1884 let stem = format!("blk.{il}.ffn_{proj}_exps.{ex}");
1885 let name = format!("{stem}.weight");
1886 let Some(DiskExtent {
1887 map,
1888 file,
1889 offset,
1890 len,
1891 }) = find_expert_disk_strict(src, &name)?
1892 else {
1893 return Ok(None);
1894 };
1895 let Some(v) = src.find(&name) else {
1896 return Ok(None);
1897 };
1898 if v.ne.len() != 2 {
1899 return Ok(None);
1900 }
1901 let (cur_in, cur_out) = (v.ne[0] as usize, v.ne[1] as usize);
1902 let Some(cur_row_bytes) = staged_expert_row_bytes(v.ggml_type, cur_in) else {
1903 return Ok(None);
1904 };
1905 let cur_qtype = staged_expert_qtype(v.ggml_type).unwrap();
1906 if ex == 0 {
1907 base_offset = offset;
1908 expert_stride = len;
1909 in_f = cur_in;
1910 out_f = cur_out;
1911 qtype = cur_qtype;
1912 row_bytes = cur_row_bytes;
1913 first_map = Some(map);
1914 first_file = Some(file);
1915 } else if !std::sync::Arc::ptr_eq(first_map.as_ref().unwrap(), &map)
1916 || !std::sync::Arc::ptr_eq(first_file.as_ref().unwrap(), &file)
1917 || offset != base_offset + (ex * expert_stride) as u64
1918 || len != expert_stride
1919 || (cur_in, cur_out, cur_qtype, cur_row_bytes) != (in_f, out_f, qtype, row_bytes)
1920 {
1921 return Ok(None);
1922 }
1923 if let Some(scale) = src.find(&format!("{stem}.scale")) {
1924 macros[ex] = f32::from_le_bytes(scale.bytes[..4].try_into().unwrap());
1925 }
1926 }
1927 assert_eq!(expert_stride, out_f * row_bytes);
1928 let total = n_expert * expert_stride;
1929 let off = usize::try_from(base_offset)
1930 .map_err(|_| format!("uniform expert disk offset {base_offset} does not fit usize"))?;
1931 let all_one = macros.iter().all(|&scale| scale == 1.0);
1932 Ok(Some(HostExps {
1933 bytes: HostBuf::Mmap {
1934 map: first_map.unwrap(),
1935 file: first_file.unwrap(),
1936 off,
1937 len: total,
1938 },
1939 tiers: None,
1940 qtype,
1941 in_f,
1942 out_f,
1943 n_expert,
1944 row_bytes,
1945 expert_stride,
1946 layouts: None,
1947 macros: if all_one { None } else { Some(macros) },
1948 }))
1949 }
1950
1951 fn load_mixed_from_source(
1952 src: &dyn TensorSource,
1953 il: u32,
1954 proj: &str,
1955 n_expert: usize,
1956 ) -> Result<Self, Box<dyn std::error::Error>> {
1957 let mut tiers = Vec::with_capacity(n_expert);
1958 let mut layouts = Vec::with_capacity(n_expert);
1959 let mut macros = vec![1.0f32; n_expert];
1960 let mut in_f = 0usize;
1961 let mut out_f = 0usize;
1962 let active = src.active_experts(il);
1963 let mut first_active = None;
1964
1965 for ex in 0..n_expert {
1966 if active.is_some_and(|mask| !mask[ex]) {
1967 layouts.push(ExpertLayout {
1968 offset: 0,
1969 len: 0,
1970 qtype: QT_F32,
1971 row_bytes: 0,
1972 });
1973 tiers.push(HostBuf::Paged(Vec::new()));
1974 continue;
1975 }
1976 let name = format!("blk.{il}.ffn_{proj}_exps.{ex}.weight");
1977 let stem = format!("blk.{il}.ffn_{proj}_exps.{ex}");
1978 if let Some(scale) = src.find(&format!("{stem}.scale")) {
1979 macros[ex] = f32::from_le_bytes(scale.bytes[..4].try_into().unwrap());
1980 }
1981 let (host, byte_len, qtype, row_bytes, cur_in, cur_out) = if let Some(DiskExtent {
1982 map,
1983 file,
1984 offset,
1985 len,
1986 }) =
1987 find_expert_disk_strict(src, &name)?
1988 {
1989 let v = src
1990 .find(&name)
1991 .unwrap_or_else(|| panic!("missing expert tensor {name}"));
1992 assert_eq!(v.ne.len(), 2, "expert {name} is not 2D (ne={:?})", v.ne);
1993 let (cur_in, cur_out) = (v.ne[0] as usize, v.ne[1] as usize);
1994 let row_bytes = staged_expert_row_bytes(v.ggml_type, cur_in).ok_or_else(|| {
1995 format!("mmap expert {name} has unsupported qtype {:?}", v.ggml_type)
1996 })?;
1997 let off = usize::try_from(offset).map_err(|_| {
1998 format!("expert {name} disk offset {offset} does not fit usize")
1999 })?;
2000 (
2001 HostBuf::Mmap {
2002 map,
2003 file,
2004 off,
2005 len,
2006 },
2007 len,
2008 staged_expert_qtype(v.ggml_type).unwrap(),
2009 row_bytes,
2010 cur_in,
2011 cur_out,
2012 )
2013 } else if let Some(nv) = src.find_nvfp4_native(&name) {
2014 let bytes = memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
2015 nv.wbytes, nv.wscale, nv.out_f, nv.in_f,
2016 );
2017 let row_bytes = nv.in_f / 64 * 36;
2018 let byte_len = bytes.len();
2019 (
2020 HostBuf::Paged(bytes),
2021 byte_len,
2022 QT_NVFP4,
2023 row_bytes,
2024 nv.in_f,
2025 nv.out_f,
2026 )
2027 } else {
2028 let v = src
2029 .find(&name)
2030 .unwrap_or_else(|| panic!("missing expert tensor {name}"));
2031 assert_eq!(v.ne.len(), 2, "expert {name} is not 2D (ne={:?})", v.ne);
2032 let (cur_in, cur_out) = (v.ne[0] as usize, v.ne[1] as usize);
2033 if let Some(row_bytes) = staged_expert_row_bytes(v.ggml_type, cur_in) {
2034 let bytes = v.bytes.into_owned();
2035 let byte_len = bytes.len();
2036 (
2037 HostBuf::Paged(bytes),
2038 byte_len,
2039 staged_expert_qtype(v.ggml_type).unwrap(),
2040 row_bytes,
2041 cur_in,
2042 cur_out,
2043 )
2044 } else {
2045 let f32v = dequant::dequantize(v.ggml_type, &v.bytes, cur_in * cur_out);
2046 let mut bytes = Vec::with_capacity(f32v.len() * 4);
2047 for f in f32v {
2048 bytes.extend_from_slice(&f.to_le_bytes());
2049 }
2050 let byte_len = bytes.len();
2051 (
2052 HostBuf::Paged(bytes),
2053 byte_len,
2054 QT_F32,
2055 cur_in * 4,
2056 cur_in,
2057 cur_out,
2058 )
2059 }
2060 };
2061
2062 if first_active.is_none() {
2063 in_f = cur_in;
2064 out_f = cur_out;
2065 first_active = Some(ex);
2066 } else {
2067 assert_eq!(
2068 (cur_in, cur_out),
2069 (in_f, out_f),
2070 "expert {ex} dims ({cur_in},{cur_out}) != first active expert ({in_f},{out_f})"
2071 );
2072 }
2073 assert_eq!(
2074 byte_len,
2075 cur_out * row_bytes,
2076 "expert {name} bytes {byte_len} != out_f*row_bytes {}",
2077 cur_out * row_bytes
2078 );
2079 layouts.push(ExpertLayout {
2080 offset: 0,
2081 len: byte_len,
2082 qtype,
2083 row_bytes,
2084 });
2085 tiers.push(host);
2086 }
2087
2088 let first = layouts[*first_active
2089 .as_ref()
2090 .expect("expert mask pruned every expert")];
2091 let expert_stride = layouts.iter().map(|layout| layout.len).max().unwrap_or(0);
2092 let all_one = macros.iter().all(|&scale| scale == 1.0);
2093 Ok(HostExps {
2094 bytes: HostBuf::Paged(Vec::new()),
2095 tiers: Some(tiers),
2096 qtype: first.qtype,
2097 in_f,
2098 out_f,
2099 n_expert,
2100 row_bytes: first.row_bytes,
2101 expert_stride,
2102 layouts: Some(layouts),
2103 macros: if all_one { None } else { Some(macros) },
2104 })
2105 }
2106
2107 /// Host byte slice for expert `e` (the H2D DMA source). Contiguous block, offset honored.
2108 /// Resolves the per-expert tier when spilling is active (`tiers` Some), else slices the single
2109 /// Per-expert post-matmul macro-scale (1.0 when absent).
2110 #[inline]
2111 pub fn macro_scale(&self, e: usize) -> f32 {
2112 self.macros.as_ref().map(|m| m[e]).unwrap_or(1.0)
2113 }
2114
2115 #[inline]
2116 pub fn is_uniform_layout(&self) -> bool {
2117 self.layouts.is_none()
2118 }
2119
2120 #[inline]
2121 pub fn expert_layout(&self, e: usize) -> ExpertLayout {
2122 debug_assert!(
2123 e < self.n_expert,
2124 "expert index {e} >= n_expert {}",
2125 self.n_expert
2126 );
2127 self.layouts
2128 .as_ref()
2129 .map(|layouts| layouts[e])
2130 .unwrap_or(ExpertLayout {
2131 offset: e * self.expert_stride,
2132 len: self.expert_stride,
2133 qtype: self.qtype,
2134 row_bytes: self.row_bytes,
2135 })
2136 }
2137
2138 #[inline]
2139 pub fn max_expert_bytes(&self) -> usize {
2140 self.layouts
2141 .as_ref()
2142 .and_then(|layouts| layouts.iter().map(|layout| layout.len).max())
2143 .unwrap_or(self.expert_stride)
2144 }
2145
2146 /// backing store (unchanged in-RAM path). Each `tiers[e]` is exactly one expert's stride.
2147 #[inline]
2148 pub fn expert_bytes(&self, e: usize) -> &[u8] {
2149 let layout = self.expert_layout(e);
2150 match &self.tiers {
2151 Some(tiers) => {
2152 debug_assert_eq!(tiers[e].len(), layout.len);
2153 tiers[e].as_bytes()
2154 }
2155 None => &self.bytes.as_bytes()[layout.offset..layout.offset + layout.len],
2156 }
2157 }
2158
2159 /// Source-aware twin of `expert_bytes`. Per-expert tiers already point at one exact block, while
2160 /// a uniform slab needs the expert layout offset added to its base. Keeping those cases separate
2161 /// prevents expert `e` from being offset twice when a tier vector is present.
2162 #[inline]
2163 pub(crate) fn expert_source(&self, e: usize) -> ExpertSource<'_> {
2164 let layout = self.expert_layout(e);
2165 match &self.tiers {
2166 Some(tiers) => tiers[e].expert_source(0, layout.len),
2167 None => self.bytes.expert_source(layout.offset, layout.len),
2168 }
2169 }
2170
2171 /// Hint that expert `e` will be staged soon. Uniform slabs advise only this expert's window;
2172 /// mixed/pruned layouts advise the selected per-expert mmap. Returns false for resident or
2173 /// empty buffers and on unsupported kernels; callers always retain the demand-fault fallback.
2174 #[inline]
2175 pub fn prefetch_expert_pages(&self, e: usize) -> bool {
2176 let layout = self.expert_layout(e);
2177 match &self.tiers {
2178 Some(tiers) => tiers[e].advise_willneed(0, layout.len),
2179 None => self.bytes.advise_willneed(layout.offset, layout.len),
2180 }
2181 }
2182}
2183
2184#[cfg(test)]
2185mod tests {
2186 use super::{
2187 repack_nvfp4_split, unpack_nvfp4_split, ExpertKeepalive, ExpertSource, HostBuf, HostExps,
2188 QT_BF16, QT_NVFP4, QT_Q2_K, QT_Q4_K,
2189 };
2190 use memra_gguf::nvfp4_repack::{repack_modelopt_to_gguf, repack_modelopt_to_split};
2191 use memra_gguf::source::{DiskExtent, TensorSource, TensorView};
2192 use memra_gguf::{config::ModelConfig, GgmlType};
2193 use std::borrow::Cow;
2194
2195 struct MixedExpertSource {
2196 bf16: Vec<u8>,
2197 q4k: Vec<u8>,
2198 }
2199
2200 impl TensorSource for MixedExpertSource {
2201 fn config(&self) -> ModelConfig {
2202 panic!("unused by HostExps mixed-loader test")
2203 }
2204
2205 fn find(&self, name: &str) -> Option<TensorView<'_>> {
2206 let (bytes, ggml_type) = if name == "blk.0.ffn_gate_exps.0.weight" {
2207 (&self.bf16, GgmlType::BF16)
2208 } else if name == "blk.0.ffn_gate_exps.1.weight" {
2209 (&self.q4k, GgmlType::Q4_K)
2210 } else {
2211 return None;
2212 };
2213 Some(TensorView {
2214 bytes: Cow::Borrowed(bytes),
2215 ggml_type,
2216 ne: vec![256, 2],
2217 })
2218 }
2219 }
2220
2221 struct PrunedExpertSource {
2222 q2k: Vec<u8>,
2223 nvfp4: Vec<u8>,
2224 active: Vec<bool>,
2225 }
2226
2227 struct MmapExpertSource {
2228 file: std::sync::Arc<std::fs::File>,
2229 map: std::sync::Arc<memmap2::Mmap>,
2230 base_offset: usize,
2231 expert_len: usize,
2232 }
2233
2234 struct LegacyMmapExpertSource {
2235 map: std::sync::Arc<memmap2::Mmap>,
2236 expert_len: usize,
2237 }
2238
2239 impl TensorSource for MmapExpertSource {
2240 fn config(&self) -> ModelConfig {
2241 panic!("unused by HostExps mmap-loader test")
2242 }
2243 fn preserve_expert_encodings(&self) -> bool {
2244 true
2245 }
2246 fn find(&self, name: &str) -> Option<TensorView<'_>> {
2247 let ex = match name {
2248 "blk.0.ffn_gate_exps.0.weight" => 0,
2249 "blk.0.ffn_gate_exps.1.weight" => 1,
2250 _ => return None,
2251 };
2252 let off = self.base_offset + ex * self.expert_len;
2253 Some(TensorView {
2254 bytes: Cow::Borrowed(&self.map[off..off + self.expert_len]),
2255 ggml_type: GgmlType::Q2_K,
2256 ne: vec![256, 2],
2257 })
2258 }
2259 fn find_expert_disk(&self, name: &str) -> Option<DiskExtent> {
2260 let ex = match name {
2261 "blk.0.ffn_gate_exps.0.weight" => 0,
2262 "blk.0.ffn_gate_exps.1.weight" => 1,
2263 _ => return None,
2264 };
2265 Some(DiskExtent {
2266 map: self.map.clone(),
2267 file: self.file.clone(),
2268 offset: (self.base_offset + ex * self.expert_len) as u64,
2269 len: self.expert_len,
2270 })
2271 }
2272 }
2273
2274 impl TensorSource for LegacyMmapExpertSource {
2275 fn config(&self) -> ModelConfig {
2276 panic!("unused by legacy mmap guard test")
2277 }
2278 fn preserve_expert_encodings(&self) -> bool {
2279 true
2280 }
2281 fn find(&self, name: &str) -> Option<TensorView<'_>> {
2282 let ex = match name {
2283 "blk.0.ffn_gate_exps.0.weight" => 0,
2284 "blk.0.ffn_gate_exps.1.weight" => 1,
2285 _ => return None,
2286 };
2287 let off = ex * self.expert_len;
2288 Some(TensorView {
2289 bytes: Cow::Borrowed(&self.map[off..off + self.expert_len]),
2290 ggml_type: GgmlType::Q2_K,
2291 ne: vec![256, 2],
2292 })
2293 }
2294 fn find_expert_mmap(
2295 &self,
2296 name: &str,
2297 ) -> Option<(std::sync::Arc<memmap2::Mmap>, usize, usize)> {
2298 let ex = match name {
2299 "blk.0.ffn_gate_exps.0.weight" => 0,
2300 "blk.0.ffn_gate_exps.1.weight" => 1,
2301 _ => return None,
2302 };
2303 Some((self.map.clone(), ex * self.expert_len, self.expert_len))
2304 }
2305 }
2306
2307 impl TensorSource for PrunedExpertSource {
2308 fn config(&self) -> ModelConfig {
2309 panic!("unused by HostExps pruned-loader test")
2310 }
2311 fn active_experts(&self, layer: u32) -> Option<&[bool]> {
2312 (layer == 0).then_some(self.active.as_slice())
2313 }
2314 fn find(&self, name: &str) -> Option<TensorView<'_>> {
2315 let (bytes, ggml_type) = match name {
2316 "blk.0.ffn_gate_exps.0.weight" => (&self.q2k, GgmlType::Q2_K),
2317 "blk.0.ffn_gate_exps.2.weight" => (&self.nvfp4, GgmlType::NVFP4),
2318 _ => return None,
2319 };
2320 Some(TensorView {
2321 bytes: Cow::Borrowed(bytes),
2322 ggml_type,
2323 ne: vec![256, 2],
2324 })
2325 }
2326 }
2327
2328 /// A1 direct-import gate (engine side): the fused modelopt->split repack must be byte-for-byte
2329 /// the composition of the two passes it replaces (modelopt->GGUF blocks, then the A6
2330 /// split-plane repack). Also pins the split roundtrip on the same buffers.
2331 #[test]
2332 fn direct_split_equals_chained() {
2333 for (out_f, in_f) in [(1usize, 64usize), (3, 128), (5, 320), (8, 1024)] {
2334 let mut w = vec![0u8; out_f * in_f / 2];
2335 let mut s = vec![0u8; out_f * in_f / 16];
2336 for (i, b) in w.iter_mut().enumerate() {
2337 *b = ((i * 41 + 7) & 0xFF) as u8;
2338 }
2339 for (i, b) in s.iter_mut().enumerate() {
2340 *b = (0x20 + ((i * 11 + 5) % 0x50)) as u8;
2341 }
2342 let gguf = repack_modelopt_to_gguf(&w, &s, out_f, in_f);
2343 let chained = repack_nvfp4_split(&gguf, out_f);
2344 let direct = repack_modelopt_to_split(&w, &s, out_f, in_f);
2345 assert_eq!(
2346 direct, chained,
2347 "fused != chained at out_f={out_f} in_f={in_f}"
2348 );
2349 assert_eq!(
2350 unpack_nvfp4_split(&direct, out_f),
2351 gguf,
2352 "split roundtrip broken at out_f={out_f} in_f={in_f}"
2353 );
2354 }
2355 }
2356
2357 #[test]
2358 fn mixed_expert_loader_keeps_each_encoding_and_extent() {
2359 let source = MixedExpertSource {
2360 bf16: vec![0x5a; 256 * 2 * 2],
2361 q4k: vec![0xa5; 2 * 144],
2362 };
2363 let exps = HostExps::load_mixed_from_source(&source, 0, "gate", 2).unwrap();
2364 assert!(!exps.is_uniform_layout());
2365 assert_eq!(exps.max_expert_bytes(), 1024);
2366 assert_eq!(exps.expert_layout(0).qtype, QT_BF16);
2367 assert_eq!(exps.expert_layout(0).row_bytes, 512);
2368 assert_eq!(exps.expert_layout(0).len, 1024);
2369 assert_eq!(exps.expert_layout(1).qtype, QT_Q4_K);
2370 assert_eq!(exps.expert_layout(1).row_bytes, 144);
2371 assert_eq!(exps.expert_layout(1).len, 288);
2372 assert_eq!(exps.expert_bytes(0), source.bf16);
2373 assert_eq!(exps.expert_bytes(1), source.q4k);
2374 match exps.expert_source(1) {
2375 ExpertSource::Memory { bytes, .. } => assert_eq!(bytes, source.q4k),
2376 ExpertSource::Disk { .. } => panic!("paged expert unexpectedly became disk-backed"),
2377 }
2378 }
2379
2380 #[test]
2381 fn mixed_expert_loader_omits_masked_expert_bytes() {
2382 let source = PrunedExpertSource {
2383 q2k: vec![0x22; 2 * 84],
2384 nvfp4: vec![0x44; 2 * 4 * 36],
2385 active: vec![true, false, true],
2386 };
2387 let exps = HostExps::load_mixed_from_source(&source, 0, "gate", 3).unwrap();
2388 assert_eq!(exps.expert_layout(0).qtype, QT_Q2_K);
2389 assert_eq!(exps.expert_layout(0).row_bytes, 84);
2390 assert_eq!(exps.expert_layout(1).len, 0);
2391 assert_eq!(exps.expert_bytes(1), &[]);
2392 assert_eq!(exps.expert_layout(2).qtype, QT_NVFP4);
2393 assert_eq!(exps.expert_layout(2).row_bytes, 4 * 36);
2394 }
2395
2396 #[test]
2397 fn mixed_expert_loader_keeps_mmap_backing_zero_copy() {
2398 let path = std::env::temp_dir().join(format!("memra-mixed-mmap-{}", std::process::id()));
2399 let base_offset = 3usize;
2400 let expert_len = 2 * 84;
2401 let mut bytes = vec![0xE1; base_offset];
2402 bytes.extend(vec![0x31; expert_len]);
2403 bytes.extend(vec![0x72; expert_len]);
2404 std::fs::write(&path, &bytes).unwrap();
2405 let file = std::sync::Arc::new(std::fs::File::open(&path).unwrap());
2406 let map = std::sync::Arc::new(unsafe { memmap2::Mmap::map(file.as_ref()).unwrap() });
2407 let source = MmapExpertSource {
2408 file: file.clone(),
2409 map,
2410 base_offset,
2411 expert_len,
2412 };
2413 let exps = HostExps::load_mixed_from_source(&source, 0, "gate", 2).unwrap();
2414 assert!(matches!(
2415 exps.tiers.as_ref().unwrap()[0],
2416 HostBuf::Mmap { .. }
2417 ));
2418 assert!(matches!(
2419 exps.tiers.as_ref().unwrap()[1],
2420 HostBuf::Mmap { .. }
2421 ));
2422 assert_eq!(
2423 exps.expert_bytes(0),
2424 &bytes[base_offset..base_offset + expert_len]
2425 );
2426 assert_eq!(exps.expert_bytes(1), &bytes[base_offset + expert_len..]);
2427 match exps.expert_source(1) {
2428 ExpertSource::Disk {
2429 file: got_file,
2430 offset,
2431 len,
2432 fallback,
2433 keepalive,
2434 } => {
2435 assert!(std::sync::Arc::ptr_eq(got_file, &file));
2436 assert_eq!(offset, (base_offset + expert_len) as u64);
2437 assert_eq!(len, expert_len);
2438 assert_eq!(fallback, &bytes[base_offset + expert_len..]);
2439 match keepalive {
2440 ExpertKeepalive::Mmap(owner) => {
2441 assert!(std::sync::Arc::ptr_eq(&owner, &source.map));
2442 }
2443 _ => panic!("mmap expert did not retain its mmap owner"),
2444 }
2445 }
2446 ExpertSource::Memory { .. } => panic!("mixed mmap tier lost its disk extent"),
2447 }
2448 #[cfg(unix)]
2449 assert!(exps.prefetch_expert_pages(1));
2450 std::fs::remove_file(path).ok();
2451 }
2452
2453 #[test]
2454 fn tiered_expert_source_does_not_double_apply_layout_offset() {
2455 let path =
2456 std::env::temp_dir().join(format!("memra-tiered-source-offset-{}", std::process::id()));
2457 let base_offset = 7usize;
2458 let expert_len = 2 * 84;
2459 let mut bytes = vec![0xE3; base_offset];
2460 bytes.extend(vec![0x41; expert_len]);
2461 bytes.extend(vec![0x82; expert_len]);
2462 std::fs::write(&path, &bytes).unwrap();
2463 let file = std::sync::Arc::new(std::fs::File::open(&path).unwrap());
2464 let map = std::sync::Arc::new(unsafe { memmap2::Mmap::map(file.as_ref()).unwrap() });
2465 let exps = HostExps {
2466 bytes: HostBuf::Paged(Vec::new()),
2467 tiers: Some(vec![
2468 HostBuf::Mmap {
2469 map: map.clone(),
2470 file: file.clone(),
2471 off: base_offset,
2472 len: expert_len,
2473 },
2474 HostBuf::Mmap {
2475 map,
2476 file: file.clone(),
2477 off: base_offset + expert_len,
2478 len: expert_len,
2479 },
2480 ]),
2481 qtype: QT_Q2_K,
2482 in_f: 256,
2483 out_f: 2,
2484 n_expert: 2,
2485 row_bytes: 84,
2486 expert_stride: expert_len,
2487 layouts: None,
2488 macros: None,
2489 };
2490
2491 // `expert_layout(1).offset == expert_len`, but tier 1 already starts at expert 1.
2492 assert_eq!(exps.expert_layout(1).offset, expert_len);
2493 match exps.expert_source(1) {
2494 ExpertSource::Disk {
2495 offset,
2496 len,
2497 fallback,
2498 ..
2499 } => {
2500 assert_eq!(offset, (base_offset + expert_len) as u64);
2501 assert_eq!(len, expert_len);
2502 assert_eq!(fallback, &bytes[base_offset + expert_len..]);
2503 }
2504 ExpertSource::Memory { .. } => panic!("tiered mmap expert lost its disk extent"),
2505 }
2506 std::fs::remove_file(path).ok();
2507 }
2508
2509 #[test]
2510 fn legacy_mmap_source_requires_retained_file_extent() {
2511 let path =
2512 std::env::temp_dir().join(format!("memra-legacy-mmap-source-{}", std::process::id()));
2513 let expert_len = 2 * 84;
2514 std::fs::write(&path, vec![0x64; 2 * expert_len]).unwrap();
2515 let file = std::fs::File::open(&path).unwrap();
2516 let map = std::sync::Arc::new(unsafe { memmap2::Mmap::map(&file).unwrap() });
2517 let source = LegacyMmapExpertSource { map, expert_len };
2518
2519 let err = match HostExps::load_uniform_mmap_from_source(&source, 0, "gate", 2) {
2520 Ok(_) => panic!("legacy mmap-only source silently fell back instead of failing"),
2521 Err(err) => err,
2522 };
2523 let message = err.to_string();
2524 assert!(
2525 message.contains("legacy find_expert_mmap without find_expert_disk"),
2526 "{message}"
2527 );
2528 assert!(message.contains("retained Arc<File>"), "{message}");
2529 std::fs::remove_file(path).ok();
2530 }
2531
2532 #[test]
2533 fn uniform_expert_loader_coalesces_contiguous_mmap() {
2534 let path = std::env::temp_dir().join(format!("memra-uniform-mmap-{}", std::process::id()));
2535 let base_offset = 5usize;
2536 let expert_len = 2 * 84;
2537 let mut bytes = vec![0xE2; base_offset];
2538 bytes.extend(vec![0x19; expert_len]);
2539 bytes.extend(vec![0x91; expert_len]);
2540 std::fs::write(&path, &bytes).unwrap();
2541 let file = std::sync::Arc::new(std::fs::File::open(&path).unwrap());
2542 let map = std::sync::Arc::new(unsafe { memmap2::Mmap::map(file.as_ref()).unwrap() });
2543 let source = MmapExpertSource {
2544 file: file.clone(),
2545 map,
2546 base_offset,
2547 expert_len,
2548 };
2549 let exps = HostExps::load_uniform_mmap_from_source(&source, 0, "gate", 2)
2550 .unwrap()
2551 .expect("contiguous mmap should coalesce");
2552 assert!(exps.is_uniform_layout());
2553 assert!(matches!(&exps.bytes, HostBuf::Mmap { .. }));
2554 assert_eq!(exps.expert_stride, expert_len);
2555 assert_eq!(
2556 exps.expert_bytes(0),
2557 &bytes[base_offset..base_offset + expert_len]
2558 );
2559 assert_eq!(exps.expert_bytes(1), &bytes[base_offset + expert_len..]);
2560 match exps.expert_source(1) {
2561 ExpertSource::Disk {
2562 file: got_file,
2563 offset,
2564 len,
2565 fallback,
2566 ..
2567 } => {
2568 assert!(std::sync::Arc::ptr_eq(got_file, &file));
2569 assert_eq!(offset, (base_offset + expert_len) as u64);
2570 assert_eq!(len, expert_len);
2571 assert_eq!(fallback, &bytes[base_offset + expert_len..]);
2572 }
2573 ExpertSource::Memory { .. } => panic!("uniform mmap slab lost its disk extent"),
2574 }
2575 #[cfg(unix)]
2576 assert!(exps.prefetch_expert_pages(1));
2577 std::fs::remove_file(path).ok();
2578 }
2579}