memra_engine/hybrid.rs
1//! Qwen3.5/3.6 hybrid model: linear-attention (Gated DeltaNet) layers + periodic full-attention
2//! layers + SwiGLU FFN. Loads weights, runs the forward, dual cache. Builds on the validated
3//! conv1d + gdn_scan kernels (M2/M3) and the dense full-attn path (M0).
4
5use crate::Engine;
6use crate::model::{EmbedHost, GpuTensor, HostExps};
7use cudarc::driver::CudaSlice;
8use memra_gguf::config::{LayerKind, MlaConfig, ModelConfig};
9use memra_gguf::source::{GgufSource, TensorSource};
10use memra_gguf::{GgmlType, GgufFile};
11use std::collections::HashMap;
12
13// Source-agnostic load helpers (GGUF or safetensors). The GGUF wrappers below keep `load()`
14// byte-identical; only the source object differs.
15fn load_t(
16 e: &Engine,
17 src: &dyn TensorSource,
18 name: &str,
19) -> Result<GpuTensor, Box<dyn std::error::Error>> {
20 GpuTensor::load_from_source(e, src, name)
21}
22fn load_opt(
23 e: &Engine,
24 src: &dyn TensorSource,
25 name: &str,
26) -> Result<Option<GpuTensor>, Box<dyn std::error::Error>> {
27 GpuTensor::load_opt_from_source(e, src, name)
28}
29
30struct ResidencyBytes {
31 experts: HashMap<usize, usize>,
32 rest: usize,
33 saw_experts: bool,
34}
35
36fn block_index(name: &str) -> Option<usize> {
37 name.strip_prefix("blk.")?.split('.').next()?.parse().ok()
38}
39
40fn residency_bytes_by_device<'a>(
41 tensors: impl IntoIterator<Item = (&'a str, usize)>,
42 layer_devices: &[usize],
43 primary_device: usize,
44) -> ResidencyBytes {
45 let mut out = ResidencyBytes {
46 experts: HashMap::new(),
47 rest: 0,
48 saw_experts: false,
49 };
50 for (name, bytes) in tensors {
51 if name.starts_with("blk.") && name.contains("_exps.") {
52 let device = block_index(name)
53 .and_then(|il| layer_devices.get(il).copied())
54 .unwrap_or(primary_device);
55 *out.experts.entry(device).or_default() += bytes;
56 out.saw_experts = true;
57 } else {
58 out.rest += bytes;
59 }
60 }
61 out
62}
63
64/// Load-local resident-expert capacity decisions. PP stages on distinct devices are charged only
65/// for their own layer slices; co-located stages share a device key and are charged together.
66pub(crate) struct ResidentPlan {
67 primary_device: usize,
68 layer_devices: Vec<usize>,
69 layer_counts: HashMap<usize, usize>,
70 exact_expert_bytes: Option<HashMap<usize, usize>>,
71 trunk_bytes: usize,
72 decisions: HashMap<usize, bool>,
73 pp: bool,
74}
75
76impl ResidentPlan {
77 fn from_layout(
78 src: &dyn TensorSource,
79 primary_device: usize,
80 layer_devices: Vec<usize>,
81 pp: bool,
82 ) -> Self {
83 let mut layer_counts = HashMap::new();
84 for &device in &layer_devices {
85 *layer_counts.entry(device).or_default() += 1;
86 }
87 let (exact_expert_bytes, trunk_bytes) = match src.gguf() {
88 Some(g) => {
89 let bytes = residency_bytes_by_device(
90 g.tensors
91 .iter()
92 .map(|t| (t.name.as_str(), t.n_bytes as usize)),
93 &layer_devices,
94 primary_device,
95 );
96 if bytes.saw_experts {
97 (Some(bytes.experts), bytes.rest)
98 } else {
99 (None, 0)
100 }
101 }
102 None => (None, 0),
103 };
104 Self {
105 primary_device,
106 layer_devices,
107 layer_counts,
108 exact_expert_bytes,
109 trunk_bytes,
110 decisions: HashMap::new(),
111 pp,
112 }
113 }
114
115 pub(crate) fn unsharded(e: &Engine, src: &dyn TensorSource, cfg: &ModelConfig) -> Self {
116 let device = e.ctx().ordinal();
117 Self::from_layout(src, device, vec![device; cfg.n_layer as usize], false)
118 }
119
120 pub(crate) fn pp(
121 e: &Engine,
122 src: &dyn TensorSource,
123 cfg: &ModelConfig,
124 n_trunk: usize,
125 ) -> Result<Self, Box<dyn std::error::Error>> {
126 let primary = e.ctx().ordinal();
127 let Some(_fence) = crate::pp::pp_cuts(n_trunk) else {
128 return Ok(Self::unsharded(e, src, cfg));
129 };
130 let mut layer_devices = vec![primary; cfg.n_layer as usize];
131 for (il, device) in layer_devices.iter_mut().take(n_trunk).enumerate() {
132 *device = crate::pp::layer_engine(e, n_trunk, il)?.ctx().ordinal();
133 }
134 Ok(Self::from_layout(src, primary, layer_devices, true))
135 }
136
137 fn should_reside(&mut self, e: &Engine, il: usize, per_layer: usize) -> bool {
138 let device = self
139 .layer_devices
140 .get(il)
141 .copied()
142 .unwrap_or(self.primary_device);
143 debug_assert_eq!(e.ctx().ordinal(), device);
144 if let Some(&decision) = self.decisions.get(&device) {
145 return decision;
146 }
147 if std::env::var("MEMRA_MOE_RESIDENT").as_deref() == Ok("0") {
148 self.decisions.insert(device, false);
149 return false;
150 }
151 let (free, _total) = match e.ctx().mem_get_info() {
152 Ok(v) => v,
153 Err(_) => {
154 self.decisions.insert(device, false);
155 return false;
156 }
157 };
158 let projected = self
159 .exact_expert_bytes
160 .as_ref()
161 .map(|bytes| bytes.get(&device).copied().unwrap_or(0))
162 .unwrap_or(per_layer * self.layer_counts.get(&device).copied().unwrap_or(1));
163 let budget = std::env::var("MEMRA_MOE_RESIDENT_GB")
164 .ok()
165 .and_then(|v| v.parse::<f64>().ok())
166 .map(|gb| (gb * 1e9) as usize)
167 .unwrap_or_else(|| {
168 let reserve = std::env::var("MEMRA_MOE_RESIDENT_HEADROOM_GB")
169 .ok()
170 .and_then(|v| v.parse::<f64>().ok())
171 .map(|gb| (gb * 1e9) as usize)
172 .unwrap_or(2_000_000_000);
173 (free as usize).saturating_sub(self.trunk_bytes + reserve)
174 });
175 let ok = projected <= budget;
176 eprintln!(
177 "[moe] resident-experts decision ({}dev{}): experts {:.2}GB + trunk {:.2}GB vs free {:.2}GB (expert budget {:.2}GB) -> {}",
178 if self.pp { "PP " } else { "" },
179 device,
180 projected as f64 / 1e9,
181 self.trunk_bytes as f64 / 1e9,
182 free as f64 / 1e9,
183 budget as f64 / 1e9,
184 if ok { "RESIDENT" } else { "SLRU cache" }
185 );
186 self.decisions.insert(device, ok);
187 ok
188 }
189}
190
191/// Load the mixer (full-attn, linear-attn, or MLA) for block `il`. Shared by the trunk loop and
192/// the MTP head. `kind` overrides cfg.layer_kind (the MTP/NextN block is ALWAYS full-attn
193/// regardless of the periodic interval — its GGUF carries attn_q/k/v, not ssm_*/attn_qkv).
194/// `mla` is the Arch gate: `Some` only for glm-dsa (cfg.mla) — every layer of an MLA model,
195/// INCLUDING its NextN/MTP block (dense MLA, no indexer), takes the Mla arm.
196///
197/// `sep_gate` = `ModelConfig::attn_gate_separate_at(il)`: load step35's separate head-wise
198/// `attn_gate.weight` onto the full-attn arm. It is passed in rather than read off a cfg so the
199/// MTP/draft call sites (which build a synthetic cfg) opt in explicitly.
200fn load_mixer_kind(
201 e: &Engine,
202 src: &dyn TensorSource,
203 il: u32,
204 kind: LayerKind,
205 mla: Option<&MlaConfig>,
206 sep_gate: bool,
207) -> Result<Mixer, Box<dyn std::error::Error>> {
208 let p = |s: &str| format!("blk.{il}.{s}");
209 if let Some(m) = mla {
210 assert_eq!(
211 kind,
212 LayerKind::FullAttention,
213 "MLA layers are full-attention class"
214 );
215 return Ok(Mixer::Mla(MlaAttnLayer::load(e, src, il, m)?));
216 }
217 Ok(match kind {
218 LayerKind::FullAttention => Mixer::Full(FullAttnLayer {
219 wq: load_t(e, src, &p("attn_q.weight"))?,
220 wk: load_t(e, src, &p("attn_k.weight"))?,
221 // gemma4 global layers ship NO v_proj (attention_k_eq_v): V = the K projection
222 // output pre-rope (llama gemma4.cpp: `Vcur = wv ? mm(wv,cur) : Kcur`). Loading
223 // wv := wk reproduces that exactly with zero forward changes; the gemma forward
224 // adds the weightless V rms_norm (R7 part 2).
225 wv: match load_opt(e, src, &p("attn_v.weight"))? {
226 Some(v) => v,
227 None => load_t(e, src, &p("attn_k.weight"))?,
228 },
229 wo: load_t(e, src, &p("attn_output.weight"))?,
230 q_norm: load_t(e, src, &p("attn_q_norm.weight"))?,
231 k_norm: load_t(e, src, &p("attn_k_norm.weight"))?,
232 // step35: REQUIRED when the arch says so — a missing gate would silently drop the
233 // per-head sigmoid and produce plausible-but-wrong logits, so this is load_t not
234 // load_opt. Step-3.7-Flash ships it on all 45 blocks (width = that layer's n_head).
235 attn_gate: if sep_gate {
236 Some(load_t(e, src, &p("attn_gate.weight"))?)
237 } else {
238 None
239 },
240 }),
241 LayerKind::LinearAttention => Mixer::Linear(LinearAttnLayer {
242 wqkv: load_t(e, src, &p("attn_qkv.weight"))?,
243 wqkv_gate: load_t(e, src, &p("attn_gate.weight"))?,
244 ssm_beta: load_t(e, src, &p("ssm_beta.weight"))?,
245 ssm_alpha: load_t(e, src, &p("ssm_alpha.weight"))?,
246 ssm_a: load_t(e, src, &p("ssm_a"))?,
247 ssm_dt: load_t(e, src, &p("ssm_dt.bias"))?,
248 ssm_conv1d: load_t(e, src, &p("ssm_conv1d.weight"))?,
249 ssm_norm: load_t(e, src, &p("ssm_norm.weight"))?,
250 ssm_out: load_t(e, src, &p("ssm_out.weight"))?,
251 }),
252 })
253}
254
255/// Load the FFN (dense SwiGLU or routed MoE) for block `il`. Source-agnostic (GGUF or safetensors
256/// via `TensorSource`); shared by the hybrid trunk/MTP loops AND the dense-attention MoE path (OLMoE).
257/// Shared-expert tensors are OPTIONAL (`load_opt`): qwen35moe has them, OLMoE/vanilla-MoE do not.
258/// When `spill` is `Some` (MEMRA_SPILL_DISK on) AND the source is the GGUF on disk, MoE experts load
259/// through the per-expert tier split (`HostExps::load_tiered`: hottest pinned, rest mmap'd from disk);
260/// otherwise experts take the all-host / gather path. Spill tiering is GGUF-only (needs the file mmap).
261pub(crate) fn load_ffn(
262 e: &Engine,
263 src: &dyn TensorSource,
264 cfg: &ModelConfig,
265 il: u32,
266 spill: Option<(&GgufFile, &mut crate::spill::SpillCtx)>,
267 resident: &mut ResidentPlan,
268) -> Result<Ffn, Box<dyn std::error::Error>> {
269 let p = |s: &str| format!("blk.{il}.{s}");
270 // MiniMax-M3: moe_layer_freq[il]==0 -> this layer is a DENSE-FFN layer (layers 0..2) even
271 // though the arch is MoE; force the Dense arm (its mlp.{p}_proj names map via ggml_to_hf).
272 // Hy3: `first_k_dense_replace` leading layers are dense-FFN (REAP50: layer 0 only).
273 let dense_override = cfg.m3.as_ref()
274 .is_some_and(|m| m.moe_layer_freq.get(il as usize).copied() == Some(0))
275 || cfg.hy3.as_ref().is_some_and(|h| il < h.first_k_dense_replace)
276 // glm-dsa: leading_dense_block_count layers (GLM-5.2: 3) are dense-FFN
277 || cfg.mla.as_ref().is_some_and(|m| il < m.first_k_dense_replace)
278 // step35: leading_dense_block_count (Step-3.7-Flash: 3) — blocks 0-2 ship
279 // ffn_gate/up/down and NO ffn_gate_inp, so the MoE arm's load_t would fail.
280 || cfg.step35.as_ref().is_some_and(|s| il < s.first_k_dense_replace)
281 // gemma4 DENSE variants (31B/E4B): the arch is MoE-capable but the file ships no
282 // expert tensors at all — tensor presence decides.
283 || (cfg.gemma4.is_some() && !src.has(&p("ffn_gate_exps.weight"))
284 && !src.has(&p("ffn_gate_up_exps.weight")))
285 // A NextN/MTP BLOCK can be DENSE inside a MoE trunk. Step-3.7-Flash's standalone drafter
286 // (Step3.7-flash-mtp-Q8_0.gguf) ships blk.45/46/47 with `ffn_gate/up/down.weight` and NO
287 // `ffn_gate_inp`/`ffn_*_exps`, while the same file's config declares expert_count=288 (it
288 // carries the TRUNK's hparams) — so the MoE arm's `load_t("ffn_gate_exps.weight")` would
289 // fail on a perfectly well-formed file. Scoped to `il >= n_trunk` and gated on tensor
290 // presence: only an MTP block can take this door, so no trunk MoE layer's dispatch can
291 // shift. (qwen35's MTP block IS MoE and keeps the MoE arm — it ships the expert slabs.)
292 || (cfg.nextn_predict_layers > 0
293 && il >= cfg.n_layer.saturating_sub(cfg.nextn_predict_layers)
294 && src.has(&p("ffn_gate.weight"))
295 && !src.has(&p("ffn_gate_exps.weight"))
296 && !src.has(&p("ffn_gate_up_exps.weight")));
297 Ok(
298 if let Some(moe) = cfg.moe.as_ref().filter(|_| !dense_override) {
299 let n_expert = moe.expert_count as usize;
300 // Expert loader. `spill` carries an optional (GgufFile, SpillCtx) — only the GGUF on-disk
301 // path can tier (it needs the file mmap); safetensors always gathers/stacks all-host.
302 // - spill Some -> per-expert tier split (hottest pinned, rest mmap'd from the GGUF).
303 // - GGUF 3D stacked name resolves -> load_stacked_from_source (all-host).
304 // - else (safetensors) -> gather N separate 2D expert tensors.
305 let (gate_exps, up_exps, down_exps) = match spill {
306 Some((g, ctx)) => (
307 HostExps::load_tiered(e, g, &p("ffn_gate_exps.weight"), ctx)?,
308 HostExps::load_tiered(e, g, &p("ffn_up_exps.weight"), ctx)?,
309 HostExps::load_tiered(e, g, &p("ffn_down_exps.weight"), ctx)?,
310 ),
311 None => {
312 let exps =
313 |e: &Engine, n: &str| -> Result<HostExps, Box<dyn std::error::Error>> {
314 if src.has(n) {
315 HostExps::load_stacked_from_source(e, src, n)
316 } else {
317 HostExps::load_from_source(e, src, n, n_expert)
318 }
319 };
320 // gemma4: gate+up ship FUSED (ffn_gate_up_exps, gate rows first) — split at load.
321 let fused = p("ffn_gate_up_exps.weight");
322 if !src.has(&p("ffn_gate_exps.weight")) && src.has(&fused) {
323 let ff = moe.expert_ff_length as usize;
324 (
325 HostExps::load_stacked_split_from_source(e, src, &fused, 0, ff)?,
326 HostExps::load_stacked_split_from_source(e, src, &fused, ff, 2 * ff)?,
327 exps(e, &p("ffn_down_exps.weight"))?,
328 )
329 } else {
330 (
331 exps(e, &p("ffn_gate_exps.weight"))?,
332 exps(e, &p("ffn_up_exps.weight"))?,
333 exps(e, &p("ffn_down_exps.weight"))?,
334 )
335 }
336 }
337 };
338 // FITS-VRAM RESIDENT EXPERTS: upload this layer's 3 expert slabs when the owning
339 // device's budget (MEMRA_MOE_RESIDENT_GB override; default = free VRAM minus the file's
340 // non-expert bytes minus a measured headroom reserve) covers the expert bytes assigned
341 // to that device, summed exactly from the GGUF header. Decision is made once per device
342 // (first MoE layer there). Failure to fit => None => the SLRU spill machinery.
343 let dev_exps =
344 build_dev_exps(e, resident, il as usize, &gate_exps, &up_exps, &down_exps)?;
345 // Device macro row [3*n_expert]: gate, up, down (ones when the artifact carries none).
346 let mut macro_row = vec![1.0f32; 3 * n_expert];
347 for (slot, exps) in [(0usize, &gate_exps), (1, &up_exps), (2, &down_exps)] {
348 if let Some(ms) = exps.macros.as_ref() {
349 macro_row[slot * n_expert..(slot + 1) * n_expert].copy_from_slice(ms);
350 }
351 }
352 let has_macros = macro_row.iter().any(|&m| m != 1.0);
353 let dev_macros = e.htod(¯o_row)?;
354 // e_score_correction_bias (sigmoid routing): retain the host oracle row and upload a
355 // zero-filled device row when absent so the token loop never allocates or transfers it.
356 let exp_probs_b = src
357 .find(&p("exp_probs_b.bias"))
358 .map(|v| memra_gguf::dequant::dequantize(v.ggml_type, &v.bytes, n_expert));
359 let active_experts = src.active_experts(il).map(<[bool]>::to_vec);
360 let route_bias = exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
361 let active_row: Vec<u8> = active_experts
362 .as_ref()
363 .map(|mask| mask.iter().map(|&is_active| u8::from(is_active)).collect())
364 .unwrap_or_else(|| vec![1; n_expert]);
365 let exp_probs_b_dev = e.htod(&route_bias)?;
366 let active_experts_dev = e.htod_bytes(&active_row)?;
367 Ffn::Moe(MoeWeights {
368 gate_inp: load_t(e, src, &p("ffn_gate_inp.weight"))?,
369 gate_inp_shexp: load_opt(e, src, &p("ffn_gate_inp_shexp.weight"))?,
370 exp_probs_b,
371 exp_probs_b_dev,
372 active_experts,
373 active_experts_dev,
374 gate_exps,
375 up_exps,
376 down_exps,
377 gate_shexp: load_opt(e, src, &p("ffn_gate_shexp.weight"))?,
378 up_shexp: load_opt(e, src, &p("ffn_up_shexp.weight"))?,
379 down_shexp: load_opt(e, src, &p("ffn_down_shexp.weight"))?,
380 dev_exps,
381 dev_macros,
382 has_macros,
383 })
384 } else {
385 Ffn::Dense {
386 ffn_gate: load_t(e, src, &p("ffn_gate.weight"))?,
387 ffn_up: load_t(e, src, &p("ffn_up.weight"))?,
388 ffn_down: load_t(e, src, &p("ffn_down.weight"))?,
389 }
390 },
391 )
392}
393
394/// Decide + build the resident expert slabs for one layer. Budget check runs once per device,
395/// RESIDENT-IF-FITS (2026-08-02, research/residency-cap-20260802/): the bank is resident when
396/// its EXACT byte total (summed from the GGUF header — UD-quants make per-layer bytes
397/// non-uniform, Ornith-35B blk.0 is +7% over the mean, so first-layer x n_layer misprojects)
398/// plus the file's non-expert bytes plus a measured headroom reserve fits free VRAM. The old
399/// default (0.80 x free vs first-layer x n_layer) reserved 20% of the card (4.8GB on 24GB)
400/// and spilled the Ornith-35B bank that fits — a priced -33% decode / -54% prefill. Measured
401/// need beside the weights at board shape is ~1.7GB (CUDA ctx + KV + workspace); reserve
402/// default 2.0GB, machine-specific override `MEMRA_MOE_RESIDENT_HEADROOM_GB` (VRAM-budget
403/// class). `MEMRA_MOE_RESIDENT_GB` stays the absolute expert-budget override;
404/// MEMRA_MOE_RESIDENT=0 forces the SLRU path. Fits => every subsequent layer on that device
405/// uploads too.
406fn build_dev_exps(
407 e: &Engine,
408 resident: &mut ResidentPlan,
409 il: usize,
410 gate: &HostExps,
411 up: &HostExps,
412 down: &HostExps,
413) -> Result<Option<crate::hybrid::DevExps>, Box<dyn std::error::Error>> {
414 // The resident pointer-table kernels take one qtype/row stride per projection. Mixed-expert
415 // layers stay on the metadata-aware staged/SLRU paths until those kernels group by layout.
416 if !gate.is_uniform_layout() || !up.is_uniform_layout() || !down.is_uniform_layout() {
417 return Ok(None);
418 }
419 let per_layer =
420 gate.bytes.as_bytes().len() + up.bytes.as_bytes().len() + down.bytes.as_bytes().len();
421 if gate.tiers.is_some() {
422 return Ok(None); // tiered/spill loads keep the cache path
423 }
424 let fits = resident.should_reside(e, il, per_layer);
425 if !fits {
426 return Ok(None);
427 }
428 use cudarc::driver::DevicePtr;
429 let gu_il = std::env::var("MEMRA_MOE_GU_IL").as_deref() == Ok("1")
430 && gate.out_f == up.out_f
431 && gate.in_f == up.in_f;
432 let n_expert = gate.n_expert;
433 let (g, u) = if gu_il {
434 // interleave gate/up rows: [ex][row o] = gate-row-o bytes ++ up-row-o bytes.
435 let (rbg, rbu) = (gate.row_bytes, up.row_bytes);
436 let n_rows = gate.out_f;
437 let gb = gate.bytes.as_bytes();
438 let ub = up.bytes.as_bytes();
439 let mut il = vec![0u8; n_expert * n_rows * (rbg + rbu)];
440 for ex in 0..n_expert {
441 for o in 0..n_rows {
442 let dst = (ex * n_rows + o) * (rbg + rbu);
443 let sg = ex * gate.expert_stride + o * rbg;
444 let su = ex * up.expert_stride + o * rbu;
445 il[dst..dst + rbg].copy_from_slice(&gb[sg..sg + rbg]);
446 il[dst + rbg..dst + rbg + rbu].copy_from_slice(&ub[su..su + rbu]);
447 }
448 }
449 let ild = e.htod_bytes_padded(&il, 8)?;
450 // `up` slot points into the same buffer via ptr math; keep a tiny placeholder alloc so
451 // the struct shape is unchanged (the table below carries the real pointers).
452 (ild, e.htod_bytes(&[0u8; 16])?)
453 } else {
454 (
455 e.htod_bytes_padded(gate.bytes.as_bytes(), 8)?,
456 e.htod_bytes_padded(up.bytes.as_bytes(), 8)?,
457 )
458 };
459 // 144B tail slack (2026-07-31, g26 prefill lever): the ragged-k expert MMA walks
460 // whole 256-val superblocks — the LAST row's final partial superblock overreads up
461 // to 144B past the slab (harmless bytes: the act's zero-padded k-range multiplies
462 // every overread weight to zero; the slack only prevents the OOB fault).
463 let d = e.htod_bytes_padded(down.bytes.as_bytes(), 144)?;
464 let mut host = vec![0u64; 3 * n_expert];
465 let (pg, pu, pd) = {
466 let __s_e0 = e.stream();
467 let (pg, _e0) = g.device_ptr(&__s_e0);
468 let __s_e1 = e.stream();
469 let (pu, _e1) = u.device_ptr(&__s_e1);
470 let __s_e2 = e.stream();
471 let (pd, _e2) = d.device_ptr(&__s_e2);
472 (pg as u64, pu as u64, pd as u64)
473 };
474 for ex in 0..n_expert {
475 if gu_il {
476 let stride = gate.out_f * (gate.row_bytes + up.row_bytes);
477 host[ex] = pg + (ex * stride) as u64;
478 host[n_expert + ex] = pg + (ex * stride + gate.row_bytes) as u64;
479 } else {
480 host[ex] = pg + (ex * gate.expert_stride) as u64;
481 host[n_expert + ex] = pu + (ex * up.expert_stride) as u64;
482 }
483 host[2 * n_expert + ex] = pd + (ex * down.expert_stride) as u64;
484 }
485 if gu_il {
486 eprintln!("[moe] gate/up dev slab INTERLEAVED (MEMRA_MOE_GU_IL)");
487 }
488 let ptr_row = e.htod_u64(&host)?;
489 Ok(Some(crate::hybrid::DevExps {
490 gate: g,
491 up: u,
492 down: d,
493 ptr_row,
494 gu_il,
495 dev: e.ctx().ordinal(),
496 }))
497}
498
499pub struct FullAttnLayer {
500 pub wq: GpuTensor,
501 pub wk: GpuTensor,
502 pub wv: GpuTensor,
503 pub wo: GpuTensor,
504 pub q_norm: GpuTensor,
505 pub k_norm: GpuTensor,
506 /// step35-class SEPARATE head-wise attention gate: `blk.N.attn_gate.weight [n_embd, n_head_l]`
507 /// where `n_head_l` is this layer's query-head count (64 full / 96 SWA on Step-3.7-Flash, so
508 /// the width VARIES per layer). Produces one pre-sigmoid scalar per head from the
509 /// post-attn_norm hidden state; the forward broadcasts sigmoid(gate) over head_dim and
510 /// multiplies attn_out before wo (upstream `step35.cpp:267-285`).
511 ///
512 /// `None` for every other arch. Do NOT confuse with `LinearAttnLayer::wqkv_gate`, which reads
513 /// the SAME tensor name on qwen35's SSM layers but is a different mechanism (a full-width
514 /// z-gate, not a per-head scalar), nor with the qwen35 FUSED gate packed inside wq that
515 /// `ModelConfig::attn_out_gate()` / `q_gate_split` handle.
516 pub attn_gate: Option<GpuTensor>,
517}
518
519/// Latent-KV geometry for one MLA layer, resolved at load from `MlaConfig` (glm-dsa). The KV
520/// cache stores ONE `latent_dim`-wide row per token per layer: [rmsnorm(c_kv) | rope(k_pe)];
521/// V is the first `kv_rank` elements of the SAME row (no V plane). All heads stream it (MQA).
522#[derive(Clone, Copy, Debug)]
523pub struct MlaGeom {
524 pub n_head: usize, // 64 — query heads; n_head_kv semantics = 1
525 pub d_nope: usize, // 192 — qk nope head dim (absorb GEMM K)
526 pub d_rope: usize, // 64 — decoupled rope width (q_pe / k_pe)
527 pub d_v: usize, // 256 — v head dim after wv_b decompression
528 pub kv_rank: usize, // 512 — latent rank (absorbed qk dim, AV accumulator width)
529 pub latent_dim: usize, // 576 = kv_rank + d_rope — the cache row / K width
530 pub scale: f32, // 1/sqrt(d_nope + d_rope) = 1/16 — NOT 1/sqrt(latent_dim)
531}
532
533/// GLM-5.2 MLA attention block (DESIGN.md §3.1 mapping). INCREMENT 2: loader-only — the
534/// projections + latent-cache geometry land on device; forward arms (prefill/decode/dc/graph)
535/// are increment 4. The CPU oracle for those arms is `crate::mla` (naive ≡ absorbed, proven).
536pub struct MlaAttnLayer {
537 pub wq_a: GpuTensor, // attn_q_a.weight [H -> Lq] (q down-projection)
538 pub q_a_norm: GpuTensor, // attn_q_a_norm.weight [Lq]
539 pub wq_b: GpuTensor, // attn_q_b.weight [Lq -> N*(nope+rope)] (q up, per head [nope|rope])
540 pub wkv_a: GpuTensor, // attn_kv_a_mqa.weight [H -> Lkv+rope] (latent row producer)
541 pub kv_a_norm: GpuTensor, // attn_kv_a_norm.weight [Lkv] (c_kv rms; k_pe is NOT normed)
542 pub wk_b: GpuTensor, // attn_k_b.weight [nope, Lkv, N] 3D — TRANSPOSED nope slice of
543 // kv_b (conversion split): the per-head absorb GEMM operand
544 pub wv_b: GpuTensor, // attn_v_b.weight [Lkv, V, N] 3D — the post-softmax decompress
545 pub wo: GpuTensor, // attn_output.weight [N*V -> H]
546 pub geom: MlaGeom,
547}
548
549impl MlaAttnLayer {
550 /// Load one MLA attention block to device. `attn_kv_b` (the unsplit tensor, when present)
551 /// is intentionally NOT loaded — v1 runs absorbed-form everywhere; the MHA-prefill arm that
552 /// would consume it is a later arc (DESIGN.md §3.1 "unused v1").
553 ///
554 /// NOTE (increment-3+): wk_b/wv_b are 3D. The F32 fixture rides the Float path (exact, full
555 /// ne kept). Quantized 3D tensors would mis-derive `row_bytes` in the generic 2D Quant arm
556 /// (out_f = ne[1] only) — the real-weights loader must split per head or flatten ne[1]*ne[2]
557 /// before the batched-GEMM kernels consume them. Guarded by the assert below.
558 pub fn load(
559 e: &Engine,
560 src: &dyn TensorSource,
561 il: u32,
562 m: &MlaConfig,
563 ) -> Result<Self, Box<dyn std::error::Error>> {
564 let p = |s: &str| format!("blk.{il}.{s}");
565 let geom = MlaGeom {
566 n_head: 0, // patched below from wq_b's out width (metadata cross-check)
567 d_nope: m.qk_nope_head_dim as usize,
568 d_rope: m.qk_rope_head_dim as usize,
569 d_v: m.v_head_dim as usize,
570 kv_rank: m.kv_lora_rank as usize,
571 latent_dim: m.latent_dim() as usize,
572 scale: m.scale(),
573 };
574 let wq_a = load_t(e, src, &p("attn_q_a.weight"))?;
575 let wq_b = load_t(e, src, &p("attn_q_b.weight"))?;
576 let wkv_a = load_t(e, src, &p("attn_kv_a_mqa.weight"))?;
577 let wk_b = load_t(e, src, &p("attn_k_b.weight"))?;
578 let wv_b = load_t(e, src, &p("attn_v_b.weight"))?;
579 let wo = load_t(e, src, &p("attn_output.weight"))?;
580 // shape audit at load (fail loudly, not as garbage activations later):
581 let n_head = wq_b.out_features() / (geom.d_nope + geom.d_rope);
582 assert_eq!(
583 wq_b.out_features(),
584 n_head * (geom.d_nope + geom.d_rope),
585 "wq_b out {} not a multiple of qk_head_dim {}",
586 wq_b.out_features(),
587 geom.d_nope + geom.d_rope
588 );
589 assert_eq!(
590 wq_a.in_features(),
591 wkv_a.in_features(),
592 "q_a/kv_a hidden mismatch"
593 );
594 assert_eq!(
595 wq_b.in_features(),
596 m.q_lora_rank as usize,
597 "wq_b in != q_lora_rank"
598 );
599 assert_eq!(
600 wkv_a.out_features(),
601 geom.latent_dim,
602 "wkv_a out != kv_lora_rank + rope"
603 );
604 assert_eq!(
605 wk_b.ne(),
606 &[geom.d_nope as u64, geom.kv_rank as u64, n_head as u64],
607 "attn_k_b must be the TRANSPOSED (nope, kv_rank, head) conversion split"
608 );
609 assert_eq!(
610 wv_b.ne(),
611 &[geom.kv_rank as u64, geom.d_v as u64, n_head as u64],
612 "attn_v_b must be the (kv_rank, v, head) conversion split"
613 );
614 assert_eq!(
615 wo.in_features(),
616 n_head * geom.d_v,
617 "wo in != n_head * v_head_dim"
618 );
619 Ok(MlaAttnLayer {
620 wq_a,
621 q_a_norm: load_t(e, src, &p("attn_q_a_norm.weight"))?,
622 wq_b,
623 wkv_a,
624 kv_a_norm: load_t(e, src, &p("attn_kv_a_norm.weight"))?,
625 wk_b,
626 wv_b,
627 wo,
628 geom: MlaGeom { n_head, ..geom },
629 })
630 }
631}
632
633/// Increment-2 guard: every forward-path `match` on `Mixer` routes Mla here until increment 4
634/// lands the MLA kernels. Loading a glm-dsa model works; running it panics with THIS message
635/// instead of garbage math. Zero behavior change for Full/Linear arches (arm never taken).
636#[track_caller]
637pub(crate) fn mla_forward_unimplemented() -> ! {
638 panic!(
639 "Mixer::Mla has no forward arm yet — glm-dsa is loader-only in increment 2; \
640 the CUDA forward lands in increment 4 (research/mla-bringup-20260801/DESIGN.md §4)"
641 )
642}
643
644pub struct LinearAttnLayer {
645 pub wqkv: GpuTensor, // [n_embd, conv_dim] -> qkv_mixed
646 pub wqkv_gate: GpuTensor, // [n_embd, value_dim] -> z
647 pub ssm_beta: GpuTensor, // [n_embd, num_v_heads]
648 pub ssm_alpha: GpuTensor, // [n_embd, num_v_heads]
649 pub ssm_a: GpuTensor, // [num_v_heads] (pre-negated -exp(A_log))
650 pub ssm_dt: GpuTensor, // [num_v_heads] bias
651 pub ssm_conv1d: GpuTensor, // [d_conv, conv_dim]
652 pub ssm_norm: GpuTensor, // [head_v_dim]
653 pub ssm_out: GpuTensor, // [value_dim, n_embd]
654}
655
656pub enum Mixer {
657 Full(FullAttnLayer),
658 Linear(LinearAttnLayer),
659 /// glm-dsa MLA block (loader-only in increment 2; forward = increment 4).
660 Mla(MlaAttnLayer),
661}
662
663/// MoE weights for one layer. Router + shared expert stay GPU-RESIDENT (tiny); the routed
664/// experts stay HOST-RESIDENT (HostExps) and are staged per-token (EDGE-1).
665///
666/// The shared-expert fields are `Option`: qwen35moe carries a shared expert, but OLMoE (and most
667/// vanilla MoE) have none (`shared_expert_intermediate_size` absent) — those layers `load_opt` the
668/// shexp tensors to `None` (ST-MOE-PLAN §1.3, §3.2). When `None` the shared-expert branch is skipped.
669pub struct MoeWeights {
670 pub gate_inp: GpuTensor, // F32 [n_embd, n_expert] router (GPU resident, Float)
671 pub gate_inp_shexp: Option<GpuTensor>, // F32 [n_embd] 1-D shared gate dot (qwen35moe only)
672 /// DeepSeek-V3/MiniMax-M3 `e_score_correction_bias` [n_expert]: added to the sigmoid scores
673 /// for expert SELECTION only; the routing weights use the un-biased scores. The host row is
674 /// the rollback oracle; the device row is zero-filled when the checkpoint carries no bias.
675 pub exp_probs_b: Option<Vec<f32>>,
676 pub exp_probs_b_dev: CudaSlice<f32>,
677 /// Original-width router mask for physically pruned expert overlays. Inactive ids never enter
678 /// top-k, so their absent weight files cannot be dispatched. The device row is all ones when
679 /// no overlay mask exists.
680 pub active_experts: Option<Vec<bool>>,
681 pub active_experts_dev: CudaSlice<u8>,
682 pub gate_exps: HostExps, // [n_embd, n_ff_exp, n_expert] (HOST)
683 pub up_exps: HostExps, // [n_embd, n_ff_exp, n_expert] (HOST)
684 pub down_exps: HostExps, // [n_ff_exp, n_embd, n_expert] TRANSPOSED (HOST)
685 pub gate_shexp: Option<GpuTensor>,
686 pub up_shexp: Option<GpuTensor>,
687 pub down_shexp: Option<GpuTensor>,
688 /// FITS-VRAM RESIDENT EXPERTS (2026-07-06): when the WHOLE model's expert bytes fit the VRAM
689 /// budget, each (proj) slab is uploaded once as a contiguous device buffer and the fused
690 /// _dev kernels take base+ex*stride pointers — no SLRU, no dispatch, no residency checks
691 /// (llama's full-offload regime; measured 169.55 vs memra's cache path 28.5 on the local 35B).
692 /// None => the SLRU host-expert machinery (the spill regime, where it WINS vs llama's
693 /// CPU-offload degradation). Decided at load in `load_ffn` (MEMRA_MOE_RESIDENT=0 forces off).
694 pub dev_exps: Option<DevExps>,
695 /// Per-expert post-matmul macro-scales on DEVICE: [3*n_expert] f32 in (gate, up, down)
696 /// order — all 1.0 unless the checkpoint carries compressed-tensors NVFP4 global scales
697 /// (unsloth qwen3.6 class). The _dev gate_up epilogues multiply unconditionally (x*1.0f
698 /// is bit-exact — zero change for macro-free artifacts); the down fold is one
699 /// moe_w_scale_by_expert launch gated on `has_macros`.
700 pub dev_macros: cudarc::driver::CudaSlice<f32>,
701 pub has_macros: bool,
702}
703
704impl MoeWeights {
705 #[inline]
706 pub fn has_uniform_expert_layout(&self) -> bool {
707 self.gate_exps.is_uniform_layout()
708 && self.up_exps.is_uniform_layout()
709 && self.down_exps.is_uniform_layout()
710 }
711
712 #[inline]
713 pub fn active_count(&self) -> usize {
714 self.active_experts
715 .as_ref()
716 .map(|mask| mask.iter().filter(|&&active| active).count())
717 .unwrap_or(self.gate_exps.n_expert)
718 }
719}
720
721/// Device-resident expert slabs for one layer (gate/up/down) + the prebuilt [3, n_expert]
722/// pointer row the _dev kernels consume.
723pub struct DevExps {
724 pub gate: CudaSlice<u8>,
725 pub up: CudaSlice<u8>,
726 pub down: CudaSlice<u8>,
727 /// [3*n_expert] u64 device row: gate ptrs, up ptrs, down ptrs (proj-major like layer_dev_row).
728 pub ptr_row: CudaSlice<u64>,
729 /// The CUDA device ordinal these slabs live on (the OWNING stage's device under the PP
730 /// sharded loader — cx-503b sizes and `layer_engine` places per device). Consumers that
731 /// dispatch from a DIFFERENT device must NOT dereference the slabs: an m=1 qmatvec over
732 /// peer-read expert bytes is the measured 34-150x slow class (research/pp-prefill-20260807
733 /// anatomy), strictly worse than SLRU staging. The sequential arm's slab-locality gate
734 /// (lane/pp-leverb) keys on this field; the per-stage prime walker makes every layer's
735 /// slab local by construction.
736 pub dev: usize,
737 /// WALL-GAP ARC (MEMRA_MOE_GU_IL=1): gate/up rows INTERLEAVED in one slab — row o of gate at
738 /// base + o*(rb_g+rb_u), up at +rb_g. Consumers on the dev path must use (rb_g+rb_u) as the
739 /// row stride for BOTH projections (see MoeWeights::dev_rb_gu). One contiguous 1760B stream
740 /// per (expert,row) instead of two scattered 880B streams — the measured 56%-of-wall fix
741 /// candidate. Kernels unchanged (stride is already a parameter everywhere).
742 pub gu_il: bool,
743}
744
745/// Per-layer FFN: dense SwiGLU (qwen35) or 256-expert MoE (qwen35moe).
746pub enum Ffn {
747 Dense {
748 ffn_gate: GpuTensor,
749 ffn_up: GpuTensor,
750 ffn_down: GpuTensor,
751 },
752 Moe(MoeWeights),
753}
754
755pub struct HybridLayer {
756 pub attn_norm: GpuTensor,
757 pub post_attn_norm: GpuTensor, // "post_attention_norm" = PRE-FFN norm
758 pub mixer: Mixer,
759 pub ffn: Ffn,
760 pub gemma4: Option<Gemma4LayerBits>,
761}
762
763/// Gemma-4 per-layer extras (R8 wiring, HANDOVER "R8 VERIFIED WIRING"): the parallel shared
764/// FFN branch, the four extra norms, the router prologue scale vector, per-expert output
765/// scales, and the layer output scalar.
766pub struct Gemma4LayerBits {
767 pub ffn_norm: GpuTensor, // ffn pre-norm (dense: THE ffn norm; moe: shared branch)
768 pub post_ffw_norm: GpuTensor, // combined post (before the attn_out residual)
769 /// MoE-layer extras (None on the dense gemma4 variants — 31B/E4B): the parallel shared
770 /// branch norms + tensors, the router prologue vector, per-expert output scales.
771 pub moe_bits: Option<Gemma4MoeBits>,
772 pub layer_scale: f32, // layer_output_scale [1]
773 /// E4B extras (None on 26B/31B): the per-layer-embedding tail block + KV-share target.
774 pub e4b: Option<Gemma4E4bLayer>,
775}
776
777/// gemma-4 E4B per-layer bits (see research/gemma4-bringup/e4b-arch-map.md):
778/// tail block cur += rms_norm(proj . (gelu(inp_gate . cur) * inp_pl[il]), post_norm)
779/// and the KV-share map — layers il >= n_layer-shared_kv_layers have NO own k/v projections
780/// and attend the cache of layer (n_layer-shared) - (swa ? 2 : 1) with their own Q.
781pub struct Gemma4E4bLayer {
782 pub inp_gate: GpuTensor, // blk.N.inp_gate [n_embd, n_epl]
783 pub proj: GpuTensor, // blk.N.proj [n_epl, n_embd]
784 pub post_norm: GpuTensor, // blk.N.post_norm [n_embd]
785 /// wave-4b: wq|wk|wv concatenated along OUT (one Q4_0 matvec at t=1 instead of the
786 /// fused3 3-subgrid launch). Built at the mirror hook from the GPU byte planes (rows
787 /// are independent in Q4_0, so an out-dim concat is a byte concat); own-KV layers only.
788 pub qkv_cat: Option<GpuTensor>,
789 /// Some(target_layer) on KV-shared layers (wk/wv here are the TARGET layer's tensors,
790 /// loaded for shape symmetry only — the forward must skip k/v compute + append and read
791 /// the target's cache; TODO dedupe the duplicate weight upload ~63MB).
792 pub kv_share: Option<u32>,
793}
794
795/// gemma-4 E4B model-level per-layer-embedding tensors (prologue inputs). The token table
796/// stays HOST-side raw GGUF bytes at load (Q6_K [n_epl*n_layer, n_vocab], ~2.3GB VRAM when
797/// uploaded — the forward arc decides resident-vs-gather placement).
798pub struct Gemma4E4bModel {
799 /// device copy of the per-layer token table, uploaded on first use (the 26B embd_gpu
800 /// pattern — keeps the ~2.3GB off load-critical paths that never decode).
801 pub tok_tbl_gpu: std::sync::OnceLock<CudaSlice<u8>>,
802 pub tok_embd_bytes: Vec<u8>,
803 pub tok_embd_qt: i32,
804 pub tok_embd_row_bytes: usize,
805 pub model_proj: GpuTensor, // per_layer_model_proj [n_embd, n_epl*n_layer] F16
806 pub proj_norm: GpuTensor, // per_layer_proj_norm [n_epl]
807 pub n_epl: usize,
808}
809
810pub struct Gemma4MoeBits {
811 pub post_ffw_norm_1: GpuTensor, // shared-branch post
812 pub pre_ffw_norm_2: GpuTensor, // moe-branch pre
813 pub post_ffw_norm_2: GpuTensor, // moe-branch post
814 pub shared_gate: GpuTensor,
815 pub shared_up: GpuTensor,
816 pub shared_down: GpuTensor,
817 /// ffn_gate_inp.scale [n_embd] PRE-multiplied by 1/sqrt(n_embd) at load: the router
818 /// prologue (weightless rms_norm x 1/sqrt(n_embd) x scale-vec) collapses to ONE rms_norm
819 /// with this as the norm weight (x_hat * (v*s) vs llama's (x_hat*s)*v — one reassociation;
820 /// the argmax gate arbitrates).
821 pub router_scale_pre: CudaSlice<f32>,
822 pub per_expert_scale: Vec<f32>, // ffn_down_exps.scale [n_expert] (host)
823 pub per_expert_scale_d: CudaSlice<f32>, // device copy (router-weight fold kernel)
824}
825
826/// Qwen3.5 NextN/MTP head: a full transformer block (attn+FFN, same tensors as a trunk layer)
827/// plus the MTP glue (enorm/hnorm/eh_proj that fold the next-token embedding into the trunk
828/// hidden, and an optional shared_head_norm/head). Loaded from blk.{n_trunk}.* — the block the
829/// trunk loop drops. Used for speculative decode (drafts 1 token per call). See research/mtp/MTP-PLAN.md.
830pub struct MtpHead {
831 pub enorm: GpuTensor, // blk.N.nextn.enorm — RMSNorm of the next-token embedding
832 pub hnorm: GpuTensor, // blk.N.nextn.hnorm — RMSNorm of the trunk hidden
833 pub eh_proj: GpuTensor, // blk.N.nextn.eh_proj [2*n_embd, n_embd]: [e_norm; h_norm] -> n_embd
834 pub attn_norm: GpuTensor, // blk.N.attn_norm
835 pub post_attn_norm: GpuTensor, // blk.N.post_attention_norm (pre-FFN)
836 pub mixer: Mixer, // full-attn block (qwen35 MTP block is full-attn)
837 pub ffn: Ffn, // Dense or Moe, same loader as trunk
838 pub shared_head_norm: Option<GpuTensor>, // blk.N.nextn.shared_head_norm (else reuse output_norm)
839 pub shared_head_head: Option<GpuTensor>, // blk.N.nextn.shared_head (else reuse output)
840 /// FR-Spec draft->target vocab map: the draft lm_head is TRIMMED to the highest-frequency
841 /// tokens (e.g. 32768 rows of the full 248320-row head); `d2t[draft_idx]` = the target vocab
842 /// token id of trimmed row `draft_idx`. `None` for a full-vocab head (identity map). Host-side:
843 /// the draft argmax already lands on host as one u32, so the map is a single Vec index.
844 pub d2t: Option<Vec<u32>>,
845 /// DISTILLED-STUDENT geometry (None = the natural NextN block at trunk shape). A distilled
846 /// draft (StudentSV) runs the same block structure at a narrower inner width with fewer
847 /// heads, then up-projects back to n_embd (`out_up`) — the chain carrier and the head input
848 /// stay at n_embd, so the trunk/verify interface is unchanged. Selected by the presence of
849 /// `blk.N.nextn.out_up.weight` in a MEMRA_MTP_DRAFT file.
850 pub geom: Option<DraftGeom>,
851 /// step35: the DRAFT BLOCK's RESOLVED per-layer geometry (`None` for every arch whose
852 /// geometry is uniform). Without it the head forward would use the trunk's max-derived
853 /// scalars and compute wrong attention — and the failure mode is plausible-but-wrong drafts
854 /// (tanked acceptance, correct output), exactly what the exactness gates cannot see.
855 pub step35: Option<Step35MtpGeom>,
856}
857
858/// step35 MTP-block geometry, RESOLVED at load time from the file that actually carries the
859/// block's own `Step35Config` arrays.
860///
861/// Why resolved and not "look it up per forward from the model's cfg": Step-3.7-Flash ships MTP
862/// as a SEPARATE GGUF, and the two files disagree about which layers exist. The trunk artifact
863/// declares `block_count=45` / `nextn_predict_layers=0`, so its per-layer arrays hold 45 entries
864/// (0..=44) and `Step35Config::n_head(45)` falls off the end into the `.last()` fallback — index
865/// 44, which is a FULL-attn layer at 64 heads. The draft file declares `block_count=48` /
866/// `nextn=3` and its arrays' index 45 is the truth: SWA, 96 heads (matching that file's
867/// `blk.45.attn_q.weight [4096, 12288]` = 96*128 and `blk.45.attn_gate.weight [4096, 96]`).
868/// Receipt: `research/step37-bringup-20260802/raw/gguf-header-stepfun-mtp-q8-20260802.txt` plus
869/// the tail dump in `research/step37-p2-20260806/raw/` — `head_count[43..48] = [96, 64, 96, 96,
870/// 96]`, `sliding_window_pattern[43..48] = [True, False, True, True, True]`.
871#[derive(Debug, Clone)]
872pub struct Step35MtpGeom {
873 /// Block index inside the file that carries it (45 for Step-3.7-Flash). Diagnostics only.
874 pub il: u32,
875 pub n_head: usize, // 96 on Step-3.7-Flash's MTP block (SWA-type)
876 pub n_head_kv: usize, // 8
877 pub n_rot: usize, // 128 (SWA keeps the unhalved rotary width)
878 pub rope_base: f32, // 1e4 (SWA base, not the trunk's 5e6 global)
879 pub swa: bool, // true
880 pub window: usize, // 512
881 /// This block's `swiglu_clamp_shexp` limit. The MTP block's FFN is a DENSE SwiGLU, and
882 /// upstream's one `build_ffn` serves both the dense MLP and the shared expert off the
883 /// SHEXP array (llama-graph.cpp:1751) — so a dense MTP block keys off shexp, not exp.
884 /// 0.0 (`None`) on Step-3.7-Flash's block 45; live (16.0) only on trunk layers 43-44.
885 pub clamp_shexp: Option<f32>,
886}
887
888impl Step35MtpGeom {
889 /// Resolve block `il` from the geometry table of the file that OWNS that block.
890 pub fn resolve(cfg: &ModelConfig, il: u32) -> Self {
891 let s = cfg
892 .step35
893 .as_ref()
894 .expect("step35 MTP geometry needs step35 config");
895 let geometry = cfg
896 .layer_geometry(il)
897 .unwrap_or_else(|| panic!("step35 MTP block {il} has no geometry-table row"));
898 assert_eq!(
899 geometry.attention_gate,
900 memra_gguf::config::AttentionGateKind::SeparateHead,
901 "step35 MTP block {il} lost its separate attention gate"
902 );
903 Step35MtpGeom {
904 il,
905 n_head: geometry.n_head as usize,
906 n_head_kv: geometry.n_head_kv as usize,
907 n_rot: geometry.n_rot as usize,
908 rope_base: geometry.rope_base,
909 swa: geometry.window.is_some(),
910 window: geometry.window.unwrap_or(s.sliding_window) as usize,
911 clamp_shexp: s.clamp_shexp(il),
912 }
913 }
914}
915
916/// Draft-head geometry override for a distilled (narrower) student block.
917pub struct DraftGeom {
918 pub d_inner: usize, // block inner width (eh_proj out / attn / ffn), e.g. 2048
919 pub n_head: usize, // draft attention heads (head_dim = main head_dim)
920 pub n_head_kv: usize,
921 pub out_up: GpuTensor, // [d_inner -> n_embd]: carrier + head input up-projection
922}
923
924/// Which tensor is the DRAFT lm_head, for a standalone NextN/MTP draft GGUF whose block index is
925/// `n`. Preference order is the artifact's, not ours — upstream step35.cpp:553 is
926/// `layer.nextn.shared_head_head ? layer.nextn.shared_head_head : model.output`.
927///
928/// Split out of `MtpHead::load_draft` purely so it is unit-testable: the loader needs a CUDA
929/// device and a multi-GB file, while the failure this guards is invisible to every exactness gate
930/// (a wrong head still produces CORRECT output — the verify arbitrates — it just accepts nothing).
931/// `has` is the tensor-presence predicate (`src.has`).
932pub fn draft_head_tensor(has: impl Fn(&str) -> bool, n: u32) -> String {
933 let own = format!("blk.{n}.nextn.shared_head_head.weight");
934 if has(&own) {
935 return own;
936 }
937 // Legacy name kept as a probe so anything that ever matched it still does; no shipped
938 // artifact or upstream mapping uses it (see the `load_draft` note).
939 let legacy = format!("blk.{n}.nextn.shared_head.weight");
940 if has(&legacy) {
941 return legacy;
942 }
943 // FR-Spec / tied-head drafts: the file-level head IS the draft head.
944 "output.weight".to_string()
945}
946
947impl MtpHead {
948 /// Load an MTP/NextN head from a STANDALONE draft GGUF (MEMRA_MTP_DRAFT override). The draft
949 /// file carries ONLY the NextN block (blk.N.nextn.* glue + attn/ffn) plus its own lm_head
950 /// (`output.weight`) — which for an FR-Spec draft is TRIMMED to the top-frequency rows, with
951 /// a `d2t` (i32/i64) tensor mapping trimmed-row index -> target vocab token id. Draft-token
952 /// embedding still uses the MAIN model's token_embd (identical weights, saves VRAM), so the
953 /// draft file's full-vocab token_embd copy is ignored.
954 pub fn load_draft(
955 e: &Engine,
956 g: &GgufFile,
957 main_cfg: &ModelConfig,
958 ) -> Result<Self, Box<dyn std::error::Error>> {
959 let src = GgufSource(g);
960 let dcfg = src.config();
961 // NextN block index INSIDE THE DRAFT FILE (its block_count includes the trunk numbering).
962 // Graceful error, not assert: the server's `+draft` attach path surfaces this to the
963 // user (a gemma-assistant draft or any non-NextN GGUF lands here; a panic killed the
964 // whole worker — serve-smoke find, 2026-07-30).
965 if dcfg.nextn_predict_layers == 0 {
966 return Err(format!(
967 "draft GGUF has no nextn_predict_layers (arch {:?}) — not a NextN/MTP regime \
968 draft; gemma assistant drafters attach via MEMRA_DRAFT, not '+draft'",
969 g.arch()
970 )
971 .into());
972 }
973 let n = dcfg.n_layer - dcfg.nextn_predict_layers;
974 let p = |s: &str| format!("blk.{n}.{s}");
975
976 // Distilled student (narrow block + out_up) vs natural NextN clone. The interface dims
977 // (n_embd in/out, head_dim for the shared rope kernel) must match the main model; a
978 // student may shrink the inner width and head counts.
979 let student = src.has(&p("nextn.out_up.weight"));
980 assert_eq!(dcfg.n_embd, main_cfg.n_embd, "draft n_embd != model n_embd");
981 assert_eq!(
982 dcfg.head_dim_k, main_cfg.head_dim_k,
983 "draft head_dim != model head_dim"
984 );
985 // step35: geometry is PER-LAYER, so "same shape as the trunk" is the wrong question — the
986 // draft block at il=45 is an SWA-type block (96 q heads, 128 rotary dims, rope base 1e4)
987 // while the trunk's full-attn layers are 64/64/5e6. Resolve the block's geometry from the
988 // DRAFT FILE's own arrays (the trunk artifact's arrays stop at index 44 — see
989 // `Step35MtpGeom`'s note) and verify it against the block's real tensor shapes. The dims
990 // that must still agree with the trunk are the INTERFACE ones (n_embd, head_dim, KV width).
991 let step35 = match (main_cfg.step35.as_ref(), dcfg.step35.as_ref()) {
992 (Some(_), Some(_)) => {
993 let g = Step35MtpGeom::resolve(&dcfg, n);
994 // ne is inner-fastest: ne[0] = in_features, ne[1] = out_features for a [in, out] 2D.
995 let out_f = |t: &str| -> Option<usize> {
996 src.find(&p(t))
997 .and_then(|v| v.ne.get(1).copied())
998 .map(|x| x as usize)
999 };
1000 let hd = dcfg.head_dim_k as usize;
1001 let wq_out =
1002 out_f("attn_q.weight").ok_or("step35 draft block has no attn_q.weight")?;
1003 assert_eq!(
1004 wq_out,
1005 g.n_head * hd,
1006 "step35 draft blk.{n}: attn_q out {wq_out} != n_head({}) * head_dim({hd}) — \
1007 the draft file's head_count array disagrees with its own tensors",
1008 g.n_head
1009 );
1010 // The SEPARATE head-wise gate is [n_embd, n_head_l] — one scalar per head. Its
1011 // width is the second independent witness of this block's head count.
1012 let wg_out = out_f("attn_gate.weight")
1013 .ok_or("step35 draft block has no attn_gate.weight (head-wise gate)")?;
1014 assert_eq!(
1015 wg_out, g.n_head,
1016 "step35 draft blk.{n}: attn_gate out {wg_out} != n_head({})",
1017 g.n_head
1018 );
1019 // The draft attends its OWN scratch, but `MtpScratch::new` sizes those rows from
1020 // the TRUNK cfg's `n_head_kv` (for step35, the max over its per-layer array).
1021 // Compare against exactly that value, not a per-layer accessor.
1022 assert_eq!(
1023 g.n_head_kv, main_cfg.n_head_kv as usize,
1024 "step35 draft blk.{n} KV heads {} != trunk n_head_kv {} — the MTP scratch \
1025 rows are sized from the trunk cfg, so a differing draft KV width would \
1026 write past the row",
1027 g.n_head_kv, main_cfg.n_head_kv
1028 );
1029 eprintln!(
1030 "[mtp-draft] step35 MTP geometry blk.{n}: n_head={} n_head_kv={} n_rot={} \
1031 rope_base={:.0} swa={} window={}",
1032 g.n_head, g.n_head_kv, g.n_rot, g.rope_base, g.swa, g.window
1033 );
1034 Some(g)
1035 }
1036 (Some(_), None) => {
1037 return Err(format!(
1038 "MEMRA_MTP_DRAFT points at a non-step35 GGUF (arch {:?}) but the model is \
1039 step35 — the draft block's per-layer geometry is unknowable from the trunk \
1040 config (its arrays stop at the trunk's last layer)",
1041 g.arch()
1042 )
1043 .into());
1044 }
1045 (None, Some(_)) => {
1046 return Err("MEMRA_MTP_DRAFT is a step35 draft but the model is not step35".into());
1047 }
1048 (None, None) => None,
1049 };
1050 if step35.is_none() && !student {
1051 // The head forward runs with the MAIN model's cfg — the draft block must be the
1052 // same shape or the forward is garbage.
1053 assert_eq!(dcfg.n_head, main_cfg.n_head, "draft n_head != model n_head");
1054 assert_eq!(
1055 dcfg.n_head_kv, main_cfg.n_head_kv,
1056 "draft n_head_kv != model n_head_kv"
1057 );
1058 }
1059
1060 // Draft lm_head. PREFERENCE ORDER IS THE ARTIFACT'S, NOT OURS (upstream step35.cpp:553
1061 // `layer.nextn.shared_head_head ? ... : model.output`): a NextN block owns its OWN head,
1062 // and only a file that omits it falls back to the file-level `output.weight`.
1063 //
1064 // MEASURED ON THE SHIPPED ARTIFACT (Step3.7-flash-mtp-Q8_0.gguf, byte hashes in
1065 // research/step37-p2-20260806/raw/draft-head-tensor-hashes-20260807.txt): the file carries
1066 // BOTH, they are DIFFERENT matrices, and the three MTP blocks' heads differ from each
1067 // other too —
1068 // output.weight sha 3eec5831… <- the TRUNK lm_head, re-quantized
1069 // blk.45.nextn.shared_head_head.weight sha c90b907b… <- block 45's own head
1070 // blk.46 … sha a22d2957…
1071 // blk.47 … sha 4b21e137…
1072 // The tell: this file's top-level `output_norm.weight` is BYTE-IDENTICAL to the trunk
1073 // artifact's (both sha d7526f44…), i.e. the top level is a copy of the trunk's output
1074 // stack, present so the draft gguf stands alone. Reading it as the draft head projects
1075 // the MTP block's hidden through the TRUNK's head — coherent-looking drafts the verify
1076 // never accepts. Receipt: acceptance 0/248 across K=1..8 with self-consistency PASS
1077 // (raw/mtp-draft-20260806T212902Z.log) — the exact failure class run_spec.rs's
1078 // "acceptance == 0 with identical output" WARNING exists to catch.
1079 //
1080 // FR-Spec drafts (trimmed [n_embd, draft_vocab] + d2t) publish the trimmed head as the
1081 // file-level `output.weight` and carry no `nextn.shared_head_head`, so they keep the
1082 // fallback — hence preference, not replacement.
1083 // Name choice is factored into `draft_head_tensor` so it is testable WITHOUT a GPU or a
1084 // 3.5 GB artifact (this whole function needs both). Getting it wrong is invisible to
1085 // every exactness gate, so the choice itself is pinned by a unit test.
1086 let head_name = draft_head_tensor(|t| src.has(t), n);
1087 let head = load_t(e, &src, &head_name)?;
1088 let head_norm = match load_opt(e, &src, &p("nextn.shared_head_norm.weight"))? {
1089 Some(t) => Some(t),
1090 None => load_opt(e, &src, "output_norm.weight")?,
1091 };
1092
1093 // d2t: draft-row -> target-token-id map (absolute ids, verified against the tokenizer).
1094 let d2t: Option<Vec<u32>> = g.find("d2t").map(|t| {
1095 let bytes = g.tensor_data(t);
1096 match t.ggml_type {
1097 GgmlType::I32 => bytes
1098 .chunks_exact(4)
1099 .map(|c| i32::from_le_bytes(c.try_into().unwrap()) as u32)
1100 .collect(),
1101 GgmlType::I64 => bytes
1102 .chunks_exact(8)
1103 .map(|c| i64::from_le_bytes(c.try_into().unwrap()) as u32)
1104 .collect(),
1105 other => panic!("d2t must be I32/I64, got {other:?}"),
1106 }
1107 });
1108 if let Some(map) = &d2t {
1109 assert_eq!(
1110 map.len(),
1111 head.out_features(),
1112 "d2t len {} != draft head rows {}",
1113 map.len(),
1114 head.out_features()
1115 );
1116 let n_vocab = main_cfg.n_vocab as u64;
1117 assert!(
1118 map.iter().all(|&t| (t as u64) < n_vocab),
1119 "d2t contains token id >= model n_vocab {n_vocab}"
1120 );
1121 }
1122 let eh_proj = load_t(e, &src, &p("nextn.eh_proj.weight"))?;
1123 // defensive load gates (review feedback): a malformed student gguf fails HERE with a
1124 // named assert, not later as garbage drafts. eh_proj consumes concat(e_norm, h_norm).
1125 assert_eq!(
1126 eh_proj.in_features(),
1127 2 * main_cfg.n_embd as usize,
1128 "eh_proj in dim != 2*n_embd"
1129 );
1130 let geom = if student {
1131 let out_up = load_t(e, &src, &p("nextn.out_up.weight"))?;
1132 let d_inner = eh_proj.out_features();
1133 assert_eq!(
1134 out_up.out_features(),
1135 main_cfg.n_embd as usize,
1136 "out_up out dim != n_embd"
1137 );
1138 assert_eq!(
1139 out_up.in_features(),
1140 d_inner,
1141 "out_up in dim != eh_proj out dim (d_inner)"
1142 );
1143 assert!(
1144 dcfg.n_head >= 1 && dcfg.n_head_kv >= 1 && dcfg.n_head % dcfg.n_head_kv == 0,
1145 "student head counts malformed ({}/{})",
1146 dcfg.n_head,
1147 dcfg.n_head_kv
1148 );
1149 Some(DraftGeom {
1150 d_inner,
1151 n_head: dcfg.n_head as usize,
1152 n_head_kv: dcfg.n_head_kv as usize,
1153 out_up,
1154 })
1155 } else {
1156 None
1157 };
1158 // Log the name WITHOUT the blk.{n}. prefix (already printed) so the line reads
1159 // `source=nextn.shared_head_head` vs `source=output.weight` — the one-glance receipt
1160 // that the head choice went the right way on this artifact.
1161 let blk_prefix = format!("blk.{n}.");
1162 let head_src = head_name.strip_prefix(&blk_prefix).unwrap_or(&head_name);
1163 eprintln!(
1164 "[mtp-draft] external draft head: blk.{n}, source={}, head_vocab={}{}{}",
1165 head_src,
1166 head.out_features(),
1167 if d2t.is_some() {
1168 " (trimmed, d2t map)"
1169 } else {
1170 " (full)"
1171 },
1172 match &geom {
1173 Some(g) => format!(
1174 " (student d_inner={} heads={}/{})",
1175 g.d_inner, g.n_head, g.n_head_kv
1176 ),
1177 None => String::new(),
1178 }
1179 );
1180
1181 let mut resident = ResidentPlan::unsharded(e, &src, &dcfg);
1182 Ok(MtpHead {
1183 enorm: load_t(e, &src, &p("nextn.enorm.weight"))?,
1184 hnorm: load_t(e, &src, &p("nextn.hnorm.weight"))?,
1185 eh_proj,
1186 attn_norm: load_t(e, &src, &p("attn_norm.weight"))?,
1187 post_attn_norm: load_opt(e, &src, &p("post_attention_norm.weight"))?
1188 .or(load_opt(e, &src, &p("ffn_norm.weight"))?)
1189 .expect("draft NextN block needs post_attention_norm or ffn_norm"),
1190 mixer: load_mixer_kind(
1191 e,
1192 &src,
1193 n,
1194 LayerKind::FullAttention,
1195 dcfg.mla.as_ref(),
1196 dcfg.attn_gate_separate_at(n),
1197 )?,
1198 ffn: load_ffn(e, &src, &dcfg, n, None, &mut resident)?,
1199 shared_head_norm: head_norm,
1200 shared_head_head: Some(head),
1201 d2t,
1202 geom,
1203 step35,
1204 })
1205 }
1206}
1207
1208/// gemma4 model-level auxiliaries.
1209pub struct GemmaAux {
1210 /// rope_freqs.weight [hd_global/2] freq factors — global layers' RoPE (R9).
1211 /// Keep one copy on every PP device: global layers on either side of the cut read it.
1212 pub rope_freqs: Option<Vec<(usize, CudaSlice<f32>)>>,
1213 /// all-ones norm weight [512] (max head_dim) — the weightless rms_norms (R7 V-norm).
1214 /// Keep one copy on every PP device: every full-attention layer reads it.
1215 pub ones: Vec<(usize, CudaSlice<f32>)>,
1216 /// tokenizer suppress_tokens uploaded once (None when the model ships none) — masked to
1217 /// -inf on every logits row before argmax/sampling (12B QAT ships two control ids).
1218 pub suppress_d: Option<(CudaSlice<i32>, usize)>,
1219 /// E4B per-layer-embedding model tensors (None on 26B/31B).
1220 pub e4b: Option<Gemma4E4bModel>,
1221}
1222
1223impl GemmaAux {
1224 pub fn rope_freqs(&self, e: &Engine) -> Option<&CudaSlice<f32>> {
1225 self.rope_freqs.as_ref().map(|copies| {
1226 let dev = e.ctx().ordinal();
1227 &copies
1228 .iter()
1229 .find(|(d, _)| *d == dev)
1230 .unwrap_or_else(|| panic!("gemma4 rope_freqs has no local copy for device {dev}"))
1231 .1
1232 })
1233 }
1234
1235 pub fn ones(&self, e: &Engine) -> &CudaSlice<f32> {
1236 let dev = e.ctx().ordinal();
1237 &self
1238 .ones
1239 .iter()
1240 .find(|(d, _)| *d == dev)
1241 .unwrap_or_else(|| panic!("gemma4 ones has no local copy for device {dev}"))
1242 .1
1243 }
1244}
1245
1246/// step35 model-level auxiliaries. Deliberately NOT folded into `GemmaAux`: every gemma4 path
1247/// does `gemma4_aux.as_ref().unwrap()` and would then also fire on a step35 model.
1248pub struct Step35Aux {
1249 /// `rope_freqs.weight [n_rot_full/2]` llama3-style freq factors. Upstream applies them to
1250 /// FULL-attention layers ONLY (`rope_factors = is_swa ? nullptr : get_rope_factors(...)`,
1251 /// step35.cpp:246) — the SWA layers pass a null factor pointer. Step-3.7-Flash ships [64] F32.
1252 /// Keep one copy on every PP device: this model-level tensor is read by full-attention
1253 /// layers on both sides of the cut, and a primary-only copy would be a mapped peer read.
1254 pub rope_freqs: Option<Vec<(usize, CudaSlice<f32>)>>,
1255}
1256
1257impl Step35Aux {
1258 pub fn rope_freqs(&self, e: &Engine) -> Option<&CudaSlice<f32>> {
1259 self.rope_freqs.as_ref().map(|copies| {
1260 let dev = e.ctx().ordinal();
1261 &copies
1262 .iter()
1263 .find(|(d, _)| *d == dev)
1264 .unwrap_or_else(|| panic!("step35 rope_freqs has no local copy for device {dev}"))
1265 .1
1266 })
1267 }
1268}
1269
1270pub struct HybridModel {
1271 pub cfg: ModelConfig,
1272 pub embd: EmbedHost,
1273 pub output_norm: GpuTensor,
1274 pub output: GpuTensor,
1275 pub layers: Vec<HybridLayer>,
1276 pub mtp: Option<MtpHead>, // NextN spec-decode head (None if nextn_predict_layers == 0)
1277 /// Lazily-uploaded DEVICE copy of the raw embed table (spec/graph hot loops gather rows
1278 /// on-device instead of host-dequant + htod). ~0.5GB; uploaded once on first use.
1279 pub embd_gpu: std::sync::OnceLock<cudarc::driver::CudaSlice<u8>>,
1280 pub gemma4_aux: Option<GemmaAux>,
1281 /// step35 (Step-3.7-Flash) model auxiliaries — `Some` iff `cfg.step35.is_some()`.
1282 pub step35_aux: Option<Step35Aux>,
1283 /// PRIME ACTIVATION SLABS (piecewise-graph foundation, 2026-07-26): the layer loop's
1284 /// seven trunk transients live in RESIDENT per-model buffers instead of per-call pool
1285 /// allocs — kills ~224 alloc/free API calls per prime AND freezes the Lt GEMM operand
1286 /// addresses (nvjet's alignment-variant kernels become run-to-run stable once their
1287 /// pointers stop moving). Sized on first prime to the largest T seen. The map lock covers
1288 /// lookup/grow only; each device owns a separate slab lock so PP stages on distinct
1289 /// devices can drive their host-synchronized layer walks concurrently.
1290 pub prime_slabs: std::sync::Mutex<
1291 std::collections::HashMap<
1292 usize,
1293 std::sync::Arc<std::sync::Mutex<crate::hybrid_forward::PrimeSlabs>>,
1294 >,
1295 >,
1296}
1297
1298impl HybridModel {
1299 /// Load a hybrid (qwen35) model from GGUF. Thin byte-identical wrapper over `load_from_source`.
1300 pub fn load(e: &Engine, g: &GgufFile) -> Result<Self, Box<dyn std::error::Error>> {
1301 Self::load_from_source(e, &GgufSource(g))
1302 }
1303
1304 /// Plain-generation loader. `run-gen` never calls the optional draft head, so avoid loading
1305 /// its weights and expert bank while preserving the model config and all trunk semantics.
1306 pub fn load_without_mtp(e: &Engine, g: &GgufFile) -> Result<Self, Box<dyn std::error::Error>> {
1307 Self::load_from_source_impl(e, &GgufSource(g), false)
1308 }
1309
1310 /// Load a hybrid model from any `TensorSource` (GGUF or a safetensors HF checkpoint). The whole
1311 /// loop speaks ggml names; the source maps them (and, for safetensors, applies the SSM value
1312 /// transforms via the owned-buffer seam). The forward graph is untouched.
1313 pub fn load_from_source(
1314 e: &Engine,
1315 src: &dyn TensorSource,
1316 ) -> Result<Self, Box<dyn std::error::Error>> {
1317 Self::load_from_source_impl(e, src, true)
1318 }
1319
1320 /// Source-backed twin of `load_without_mtp`, used by the safetensors/repack `run-gen` path.
1321 pub fn load_from_source_without_mtp(
1322 e: &Engine,
1323 src: &dyn TensorSource,
1324 ) -> Result<Self, Box<dyn std::error::Error>> {
1325 Self::load_from_source_impl(e, src, false)
1326 }
1327
1328 fn load_from_source_impl(
1329 e: &Engine,
1330 src: &dyn TensorSource,
1331 load_mtp: bool,
1332 ) -> Result<Self, Box<dyn std::error::Error>> {
1333 let cfg = src.config();
1334 assert!(cfg.arch.is_hybrid(), "not a hybrid arch");
1335 // The host-expf probe guards HOST-oracle correctness, not the device arm: the device
1336 // top-k path never calls host expf at serve time (vendored scalar, deterministic), so
1337 // the device default must not fail-close on a rig whose libm merely differs. Hard-fail
1338 // only when the =0 host-oracle arm — the one whose served bytes depend on host libm —
1339 // is selected; the default arm logs a WARN so replay/oracle tooling knows host-side
1340 // comparisons are unavailable on this host.
1341 if cfg.sigmoid_router().is_some() {
1342 let host_oracle = std::env::var("MEMRA_SIG_ROUTER").as_deref() == Ok("0");
1343 match crate::sigrouter_contract::verify_host_expf() {
1344 Ok(()) => {}
1345 Err(e) if host_oracle => return Err(e.into()),
1346 Err(e) => eprintln!(
1347 "[sigrouter] WARN: host expf probe mismatch ({e}); device routing is \
1348 unaffected, but host-oracle replay/comparison cells are invalid on this host"
1349 ),
1350 }
1351 }
1352 // SPEC-SERVING stream-k key, per model, set at LOAD so it governs the PRIME too
1353 // (2026-07-27; explicit MEMRA_MMQ_SK wins). The former per-process timing selector
1354 // made knife-edge prime shapes BIMODAL across independent boots and was removed
1355 // 2026-08-14. Big dense (n_embd >= 3500) still forces tiling under spec intent;
1356 // MoE/small models defer to the deterministic fail-closed TILE form unless
1357 // MEMRA_MMQ_SK_FORM pins a separately measured arm.
1358 // An earlier attempt set this in generate_spec_gemma — too late, the prime's
1359 // GEMMs had already selected their form.
1360 if std::env::var("MEMRA_DRAFT").is_ok() && std::env::var("MEMRA_MMQ_SK").is_err() {
1361 let force = if cfg.n_embd >= 3500 { 0i8 } else { -1i8 };
1362 crate::MMQ_SK_FORCE.store(force, std::sync::atomic::Ordering::Relaxed);
1363 }
1364 // FP8-KV door: OFF for every hybrid-path model (35B: fp8 format-gates its v3
1365 // dp4a lane, −2% measured 2026-07-12; gemma keys its KV formats independently
1366 // of this flag). The 9B dense loader is the only ON site.
1367 crate::KV_FP8_FORCE.store(0, std::sync::atomic::Ordering::Relaxed);
1368
1369 // B0 FIX (hoisted): cfg.n_layer == block_count INCLUDES the MTP/NextN block(s)
1370 // (41 for the 35B-MoE); the trunk is n_layer - nextn. Computed before any tensor
1371 // upload because the M2 sharded loader (crate::pp::layer_engine) places tensors
1372 // by the trunk stage map.
1373 let n_trunk = (cfg.n_layer - cfg.nextn_predict_layers) as usize;
1374 crate::pp::init_model_transport(e, &cfg, n_trunk)?;
1375 let embd = EmbedHost::from_source(src, "token_embd.weight");
1376 // M2 increment 2 (weight sharding): output_norm + lm head upload through the LAST
1377 // stage's engine — the stage that runs them (outside the pp door / MEMRA_PP_SHARD=0
1378 // this is the primary engine, byte-identical to the M1 loader).
1379 let e_head = crate::pp::layer_engine(e, n_trunk, n_trunk - 1)?;
1380 let output_norm = load_t(e_head, src, "output_norm.weight")?;
1381 // tied embeddings: fall back to tok_embd if output.weight absent.
1382 let mut output = if src.has("output.weight") {
1383 load_t(e_head, src, "output.weight")?
1384 } else {
1385 load_t(e_head, src, "token_embd.weight")?
1386 };
1387 let mut resident = ResidentPlan::pp(e, src, &cfg, n_trunk)?;
1388
1389 // SPILLING-PLAN §2: build the tiered-spill context ONCE, before loading any experts, but
1390 // only for a MoE model with the disk tier forced on (`MEMRA_SPILL_DISK`). It probes free VRAM
1391 // + host RAM at runtime (never hardcoded) and opens one shared GGUF mmap; all expert tensors
1392 // draw down its single pinned-RAM budget (hottest pinned, the rest mmap'd from disk). When
1393 // unset/dense this stays `None` and the load takes the byte-identical all-host path.
1394 // Disk spill is GGUF-only (needs the on-disk file mmap); src.gguf() is None for safetensors.
1395 let gguf: Option<&GgufFile> = src.gguf();
1396 // expert_count > 0: Arch::Gemma4 carries cfg.moe = Some on its DENSE variants too
1397 // (the 2026-07-14 discriminator-bug class) — a dense 31B/E4B under the spill env
1398 // would otherwise probe budgets + open an expert mmap it never consumes.
1399 let mut spill: Option<crate::spill::SpillCtx> = if cfg
1400 .moe
1401 .as_ref()
1402 .is_some_and(|m| m.expert_count > 0)
1403 && crate::spill::disk_tier_enabled()
1404 && gguf.is_some()
1405 {
1406 let budget = crate::spill::MemBudget::probe(e)?;
1407 let ctx = crate::spill::SpillCtx::open(gguf.unwrap(), &budget)?;
1408 eprintln!(
1409 "[spill] disk tier ON: free_vram={} MiB free_pinnable_ram={} MiB (MemAvailable*resolved_frac)",
1410 budget.free_vram >> 20,
1411 budget.free_pinnable_ram >> 20
1412 );
1413 Some(ctx)
1414 } else {
1415 None
1416 };
1417
1418 // Running the MTP block as a trunk layer is wrong; iterate only the trunk layers
1419 // (n_trunk hoisted above). 9B (nextn=0): n_trunk = 32. 35B-MoE (nextn=1): 40.
1420 let mut layers = Vec::with_capacity(n_trunk);
1421 for il in 0..n_trunk as u32 {
1422 let p = |s: &str| format!("blk.{il}.{s}");
1423 // M2 weight sharding: this layer's tensors upload through the OWNING stage's
1424 // engine (shadowed `e`) — the bring-up remote peer-read placement dies here.
1425 // Door shut / MEMRA_PP_SHARD=0: `layer_engine` returns the primary (no change).
1426 let e = crate::pp::layer_engine(e, n_trunk, il as usize)?;
1427 // attn_norm always; post_attention_norm is the pre-FFN norm in qwen35
1428 layers.push(HybridLayer {
1429 attn_norm: load_t(e, src, &p("attn_norm.weight"))?,
1430 post_attn_norm: load_opt(e, src, &p("post_attention_norm.weight"))?
1431 .or(load_opt(e, src, &p("ffn_norm.weight"))?)
1432 .expect("need post_attention_norm or ffn_norm"),
1433 mixer: {
1434 // E4B KV-shared layers ship NO attn_k/attn_v — load the SHARE TARGET's
1435 // k/v tensors for shape symmetry (forward skips k/v compute there and
1436 // reads the target layer's cache; see Gemma4E4bLayer::kv_share).
1437 let g4_shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
1438 let kv_from = n_trunk as u32 - g4_shared;
1439 if g4_shared > 0
1440 && il >= kv_from
1441 && !src.has(&format!("blk.{il}.attn_k.weight"))
1442 {
1443 let g4 = cfg.gemma4.as_ref().unwrap();
1444 let swa = g4.swa_pattern.get(il as usize).copied().unwrap_or(true);
1445 let tgt = kv_from - if swa { 2 } else { 1 };
1446 let tp = |s: &str| format!("blk.{tgt}.{s}");
1447 Mixer::Full(FullAttnLayer {
1448 wq: load_t(e, src, &p("attn_q.weight"))?,
1449 wk: load_t(e, src, &tp("attn_k.weight"))?,
1450 wv: load_t(e, src, &tp("attn_v.weight"))?,
1451 wo: load_t(e, src, &p("attn_output.weight"))?,
1452 q_norm: load_t(e, src, &p("attn_q_norm.weight"))?,
1453 k_norm: load_t(e, src, &tp("attn_k_norm.weight"))?,
1454 attn_gate: None, // gemma4 has no separate head-wise gate
1455 })
1456 } else {
1457 load_mixer_kind(
1458 e,
1459 src,
1460 il,
1461 cfg.layer_kind(il),
1462 cfg.mla.as_ref(),
1463 cfg.attn_gate_separate_at(il),
1464 )?
1465 }
1466 },
1467 ffn: load_ffn(
1468 e,
1469 src,
1470 &cfg,
1471 il,
1472 spill.as_mut().map(|c| (gguf.unwrap(), c)),
1473 &mut resident,
1474 )?,
1475 gemma4: if cfg.gemma4.is_some() {
1476 let scalar = |n: &str| -> f32 {
1477 let t = src.find(&p(n)).unwrap_or_else(|| panic!("missing {n}"));
1478 memra_gguf::dequant::dequantize(t.ggml_type, &t.bytes, 1)[0]
1479 };
1480 let vecf = |n: &str| -> Vec<f32> {
1481 let t = src.find(&p(n)).unwrap_or_else(|| panic!("missing {n}"));
1482 memra_gguf::dequant::dequantize(
1483 t.ggml_type,
1484 &t.bytes,
1485 t.ne.iter().product::<u64>() as usize,
1486 )
1487 };
1488 let moe_bits = if src.find(&p("ffn_gate_inp.scale")).is_some() {
1489 Some(crate::hybrid::Gemma4MoeBits {
1490 post_ffw_norm_1: load_t(e, src, &p("post_ffw_norm_1.weight"))?,
1491 pre_ffw_norm_2: load_t(e, src, &p("pre_ffw_norm_2.weight"))?,
1492 post_ffw_norm_2: load_t(e, src, &p("post_ffw_norm_2.weight"))?,
1493 shared_gate: load_t(e, src, &p("ffn_gate.weight"))?,
1494 shared_up: load_t(e, src, &p("ffn_up.weight"))?,
1495 shared_down: load_t(e, src, &p("ffn_down.weight"))?,
1496 router_scale_pre: {
1497 let inv = 1.0 / (cfg.n_embd as f32).sqrt();
1498 let v: Vec<f32> =
1499 vecf("ffn_gate_inp.scale").iter().map(|x| x * inv).collect();
1500 e.htod(&v)?
1501 },
1502 per_expert_scale: vecf("ffn_down_exps.scale"),
1503 per_expert_scale_d: e.htod(&vecf("ffn_down_exps.scale"))?,
1504 })
1505 } else {
1506 None
1507 };
1508 // E4B extras (tensor-presence: blk.N.inp_gate only exists on E4B)
1509 let e4b = if src.has(&p("inp_gate.weight")) {
1510 let g4 = cfg.gemma4.as_ref().unwrap();
1511 let kv_from = n_trunk as u32 - g4.shared_kv_layers;
1512 let kv_share = if g4.shared_kv_layers > 0 && il >= kv_from {
1513 let swa = g4.swa_pattern.get(il as usize).copied().unwrap_or(true);
1514 Some(kv_from - if swa { 2 } else { 1 })
1515 } else {
1516 None
1517 };
1518 Some(crate::hybrid::Gemma4E4bLayer {
1519 inp_gate: load_t(e, src, &p("inp_gate.weight"))?,
1520 proj: load_t(e, src, &p("proj.weight"))?,
1521 post_norm: load_t(e, src, &p("post_norm.weight"))?,
1522 kv_share,
1523 qkv_cat: None, // built at the mirror hook (wave 4b)
1524 })
1525 } else {
1526 None
1527 };
1528 Some(Gemma4LayerBits {
1529 ffn_norm: load_t(e, src, &p("ffn_norm.weight"))?,
1530 post_ffw_norm: load_t(e, src, &p("post_ffw_norm.weight"))?,
1531 moe_bits,
1532 layer_scale: scalar("layer_output_scale.weight"),
1533 e4b,
1534 })
1535 } else {
1536 None
1537 },
1538 });
1539 }
1540
1541 // MTP/NextN head: load the block the trunk loop drops (il = n_trunk). It is a full
1542 // transformer block PLUS the nextn.{enorm,hnorm,eh_proj} glue. Only when nextn>0 and the
1543 // eh_proj tensor actually exists in the file (some MTP GGUFs ship the draft separately).
1544 let mtp = if load_mtp && cfg.nextn_predict_layers > 0 {
1545 let n = n_trunk as u32;
1546 let p = |s: &str| format!("blk.{n}.{s}");
1547 match src.has(&p("nextn.eh_proj.weight")) {
1548 true => Some(MtpHead {
1549 enorm: load_t(e, src, &p("nextn.enorm.weight"))?,
1550 hnorm: load_t(e, src, &p("nextn.hnorm.weight"))?,
1551 eh_proj: load_t(e, src, &p("nextn.eh_proj.weight"))?,
1552 attn_norm: load_t(e, src, &p("attn_norm.weight"))?,
1553 post_attn_norm: load_opt(e, src, &p("post_attention_norm.weight"))?
1554 .or(load_opt(e, src, &p("ffn_norm.weight"))?)
1555 .expect("MTP block needs post_attention_norm or ffn_norm"),
1556 mixer: load_mixer_kind(
1557 e,
1558 src,
1559 n,
1560 LayerKind::FullAttention,
1561 cfg.mla.as_ref(),
1562 cfg.attn_gate_separate_at(n),
1563 )?,
1564 ffn: load_ffn(
1565 e,
1566 src,
1567 &cfg,
1568 n,
1569 spill.as_mut().map(|c| (gguf.unwrap(), c)),
1570 &mut resident,
1571 )?,
1572 shared_head_norm: load_opt(e, src, &p("nextn.shared_head_norm.weight"))?,
1573 // `nextn.shared_head_head` is the name the convert script and upstream both
1574 // use (LLM_TENSOR_NEXTN_SHARED_HEAD_HEAD -> "blk.%d.nextn.shared_head_head");
1575 // `nextn.shared_head` is a name no shipped artifact carries, so this arm was
1576 // silently always-None and every embedded-MTP model fell back to the trunk
1577 // `self.output` in `mtp_head_forward_dev` op 12. Harmless for qwen35-family
1578 // heads that genuinely tie to the trunk head; wrong for any artifact that
1579 // ships its own — which the StepFun step35 drafter does (see `load_draft`).
1580 // Keep the old name as a fallback so nothing that did match still does.
1581 shared_head_head: load_opt(e, src, &p("nextn.shared_head_head.weight"))?
1582 .or(load_opt(e, src, &p("nextn.shared_head.weight"))?),
1583 d2t: None,
1584 geom: None,
1585 // EMBEDDED MTP block: same file, so its own arrays cover index `n`.
1586 step35: cfg.step35.as_ref().map(|_| Step35MtpGeom::resolve(&cfg, n)),
1587 }),
1588 false => None, // nextn>0 but no embedded eh_proj (external draft GGUF) -> no head
1589 }
1590 } else {
1591 None
1592 };
1593
1594 // MEMRA_MTP_DRAFT=<path.gguf>: REPLACE the MTP head with one loaded from a standalone
1595 // draft GGUF (e.g. an FR-Spec trimmed-vocab draft). Verify-based spec decode stays exact
1596 // regardless of the draft — a different draft only changes WHICH tokens get proposed.
1597 let mtp = if load_mtp {
1598 match std::env::var("MEMRA_MTP_DRAFT") {
1599 Ok(path) if !path.is_empty() => {
1600 eprintln!("[mtp-draft] loading external MTP draft: {path}");
1601 let dg = GgufFile::open(&path)?;
1602 Some(MtpHead::load_draft(e, &dg, &cfg)?)
1603 }
1604 _ => mtp,
1605 }
1606 } else {
1607 None
1608 };
1609
1610 // MEMRA_FRSPEC_TRIM=<frspec.gguf>: SELF-TRIMMED draft head. Reads ONLY the d2t ranked-token
1611 // list from the given file and gathers those rows from the MAIN model's own output.weight
1612 // bytes (quantized rows are independent — a byte-level row gather, zero requant). The MTP
1613 // block, norms, and head quant all stay main-model, so there is no cross-file quality
1614 // mismatch (the external Q4_K draft file measured -15pts acceptance vs the native block).
1615 // Draft lm_head reads drop vocab/32768-fold; verify stays full-vocab -> exactness unchanged.
1616 // FULL_PREC (MTP-heal ceiling): the self-trim gathers rows into `from_quant_bytes` (Quant
1617 // only) and, more to the point, the full-precision ceiling wants the model's NATURAL full
1618 // head — trimming the draft vocab is a speed lever, not part of the exactness measurement.
1619 // Disable trim under the flag (documented resolution, §item 2).
1620 let trim_env = if load_mtp {
1621 std::env::var("MEMRA_FRSPEC_TRIM")
1622 } else {
1623 Err(std::env::VarError::NotPresent)
1624 };
1625 if crate::model::full_prec_enabled()
1626 && trim_env.as_deref().map(|p| !p.is_empty()).unwrap_or(false)
1627 {
1628 eprintln!(
1629 "[frspec-trim] DISABLED under MEMRA_FULL_PREC — using the natural full MTP head"
1630 );
1631 }
1632 let mtp = match (
1633 if crate::model::full_prec_enabled() {
1634 Err(std::env::VarError::NotPresent)
1635 } else {
1636 trim_env
1637 },
1638 mtp,
1639 ) {
1640 (Ok(path), Some(mut head)) if !path.is_empty() => {
1641 // Two artifact forms: the d2t GGUF container, or a plain `.txt` (one token id
1642 // per line, rank order — frspec-owngen writes both). The text form keeps the
1643 // fully-safetensors serving path free of GGUF entirely.
1644 let d2t: Vec<u32> = if path.ends_with(".txt") {
1645 std::fs::read_to_string(&path)?
1646 .lines()
1647 .filter_map(|l| l.trim().parse::<u32>().ok())
1648 .collect()
1649 } else {
1650 let tg = GgufFile::open(&path)?;
1651 let d2t_t = tg
1652 .find("d2t")
1653 .expect("MEMRA_FRSPEC_TRIM file has no d2t tensor");
1654 let d2t_bytes = tg.tensor_data(d2t_t);
1655 match d2t_t.ggml_type {
1656 GgmlType::I32 => d2t_bytes
1657 .chunks_exact(4)
1658 .map(|c| i32::from_le_bytes(c.try_into().unwrap()) as u32)
1659 .collect(),
1660 GgmlType::I64 => d2t_bytes
1661 .chunks_exact(8)
1662 .map(|c| i64::from_le_bytes(c.try_into().unwrap()) as u32)
1663 .collect(),
1664 other => panic!("d2t must be I32/I64, got {other:?}"),
1665 }
1666 };
1667 let v = src
1668 .find("output.weight")
1669 .or_else(|| src.find("token_embd.weight"))
1670 .expect("model has no output.weight for FR-Spec trim");
1671 let out_f = v.ne[1] as usize;
1672 let row_bytes = v.bytes.len() / out_f;
1673 assert!(
1674 d2t.iter().all(|&t| (t as usize) < out_f),
1675 "d2t token id >= lm_head rows {out_f}"
1676 );
1677 let mut gathered = Vec::with_capacity(d2t.len() * row_bytes);
1678 for &t in &d2t {
1679 let off = t as usize * row_bytes;
1680 gathered.extend_from_slice(&v.bytes[off..off + row_bytes]);
1681 }
1682 let trimmed = GpuTensor::from_quant_bytes(
1683 e,
1684 &gathered,
1685 v.ggml_type,
1686 v.ne[0],
1687 d2t.len() as u64,
1688 /*nvfp4 macro-scale*/
1689 match src.find("output.scale") {
1690 Some(sv) => f32::from_le_bytes(sv.bytes[..4].try_into().unwrap()),
1691 None => 1.0,
1692 },
1693 )?;
1694 eprintln!(
1695 "[frspec-trim] self-trimmed head: {} rows of main output.weight ({:?})",
1696 d2t.len(),
1697 v.ggml_type
1698 );
1699 head.shared_head_head = Some(trimmed);
1700 head.d2t = Some(d2t);
1701 Some(head)
1702 }
1703 (_, m) => m,
1704 };
1705
1706 if let Some(ctx) = spill.as_ref() {
1707 eprintln!(
1708 "[spill] experts placed: {} pinned (Tier 1), {} mmap'd from disk (Tier 2, {} MiB)",
1709 ctx.n_pinned,
1710 ctx.n_mmap,
1711 ctx.mmap_bytes >> 20
1712 );
1713 }
1714
1715 // FA v4 GQA CAPACITY GUARD (2026-08-06, lane/122b-bringup): fa_v4_smem sizes its
1716 // per-warp Q arrays q_ints[8][64]/q_d[8][8] for gqa<=8 — every model before the
1717 // 122B-A10B (32 Q heads / 2 KV heads = gqa 16) fit. At gqa>8 the (32,gqa,1) block's
1718 // warps 8..15 write q_ints[wy] PAST the array into the k_ints/k_d K tile, corrupting
1719 // scores -> all-NaN decode logits (receipts: research/122b-bringup-20260806/, arm
1720 // battery: v4/deep MISMATCH+NaN, v3/v2/smem/reg/scalar all MATCH). The hd512 lane
1721 // already carries its own capacity guard at dispatch ("gqa <= 16 = fa_v4_smem_512's
1722 // q-array capacity"); hd256 v4 never got one. Key FA_V4_MAX_DEFAULT=0 at load so
1723 // EVERY v4 dispatch site (eager, rows-verify, dc, rows_dc, windowed, seqs) flips to
1724 // the v3 lane together — decode/verify stay kernel-family-identical (the parity law).
1725 // Explicit MEMRA_FA_V4_MAX env still wins (diagnostic seam). The real v4 gqa16
1726 // extension is a kernel change gated on its own battery + perf receipts (fix brief
1727 // in research/122b-bringup-20260806/VERDICT.md).
1728 if cfg.n_head_kv > 0 && cfg.n_head / cfg.n_head_kv > 8 {
1729 crate::FA_V4_MAX_DEFAULT.store(0, std::sync::atomic::Ordering::Relaxed);
1730 eprintln!(
1731 "[fa] v4 decode family disabled: gqa {} > fa_v4_smem capacity 8 (v3 lane serves)",
1732 cfg.n_head / cfg.n_head_kv
1733 );
1734 }
1735
1736 if cfg.gemma4.is_some() {
1737 // gemma4 fa-vec crossover default (measured sweep 2026-07-10; env overrides).
1738 crate::FA_VEC_MIN_DEFAULT.store(1, std::sync::atomic::Ordering::Relaxed);
1739 // windowed split per gemma variant (2026-07-12 sweeps): MoE 26B = 32 (grid-limited
1740 // t=1 under the raw-e4m3 sV ceiling), dense 31B = 64 (37.13 vs 36.87 at 1.7k, N=2).
1741 // DISCRIMINATOR FIX (2026-07-14): Arch::Gemma4 is in is_moe(), so cfg.moe is
1742 // Some (expert_count 0) on the DENSE 31B/E4B too — `cfg.moe.is_some()` keyed
1743 // every "per-variant" default to the 26B values and the dense arms of the
1744 // 2026-07-12 sweeps (SPW 64, SP512 32) never actually reached the 31B. Key on
1745 // expert_count instead.
1746 let real_moe = cfg.moe.as_ref().is_some_and(|m| m.expert_count > 0);
1747 crate::FA_SPW_DEFAULT.store(
1748 if real_moe { 32 } else { 64 },
1749 std::sync::atomic::Ordering::Relaxed,
1750 );
1751 // hd512 global split per variant (26B=16 landed 2026-07-11; 31B=32 swept 2026-07-12).
1752 crate::FA_SP512_DEFAULT.store(
1753 if real_moe { 16 } else { 32 },
1754 std::sync::atomic::Ordering::Relaxed,
1755 );
1756 // gemma4 router w8 RE-ARBITRATED 2026-08-01 (g26 decode dig): the 2026-07-31
1757 // knife-edge that stored false here was single-synthetic-prompt roulette — on 6
1758 // real prompts the w8 twin's gate outcome is IDENTICAL to the lone-warp form
1759 // (5 MATCH/5 MATCH; the one MISMATCH prompt fails both arms with the same
1760 // argmax pair, router-independent). w8 = +13% g26 decode (182->206 tok/s x3
1761 // interleaved, H100). Receipts: research/g26-decode-20260801/. gemma4 now rides
1762 // the global default (true); MEMRA_ROUTER_V2=0 is the rollback seam.
1763 // fused t=1 pair/triple mr1 per variant (2026-07-14 DRAM-duty arc: dense +1.1%
1764 // short / +0.6% depth on 31B; MoE 26B −1.2% — stays mr2).
1765 crate::FUSED_MR1_DEFAULT.store(!real_moe, std::sync::atomic::Ordering::Relaxed);
1766 // gemma4 rms_norm block 1024 (single-row 2816-col norms; battery-arbitrated per model).
1767 crate::RMS_BLOCK_DEFAULT.store(1024, std::sync::atomic::Ordering::Relaxed);
1768 // gemma4 fa split ladder (d1736 sweep; see fa_split_keys).
1769 crate::FA_SP_GEMMA.store(true, std::sync::atomic::Ordering::Relaxed);
1770 // depth fa: PARITY LAW (2026-07-10) — decode and verify share the rows_w/rows_dpl16
1771 // kernel symbols (decode t=1), so lane choice is freely tunable; v4 measured the
1772 // depth winner. Seams: MEMRA_FA_V4_MAX / MEMRA_FA_SMEM_TKV / MEMRA_GEMMA_ROWS_W.
1773 }
1774 // gemma4: the dc serving loop + spec draft gather read the device embed table every
1775 // step — upload it AT LOAD (OnceLock init) so first-use cost never lands in a timed span.
1776 let force_embd_gpu = cfg.gemma4.is_some();
1777 let gemma4_aux = if cfg.gemma4.is_some() {
1778 let rope_freqs = match src.find("rope_freqs.weight") {
1779 Some(t) => {
1780 let host = memra_gguf::dequant::dequantize(
1781 t.ggml_type,
1782 &t.bytes,
1783 t.ne.iter().product::<u64>() as usize,
1784 );
1785 let mut copies = Vec::new();
1786 if let Some(fence) = crate::pp::pp_cuts(n_trunk) {
1787 for s in 0..fence.len() - 1 {
1788 let owner = crate::pp::layer_engine(e, n_trunk, fence[s])?;
1789 let dev = owner.ctx().ordinal();
1790 if copies.iter().all(|(d, _)| *d != dev) {
1791 copies.push((dev, owner.htod(&host)?));
1792 }
1793 }
1794 } else {
1795 copies.push((e.ctx().ordinal(), e.htod(&host)?));
1796 }
1797 Some(copies)
1798 }
1799 None => None,
1800 };
1801 // E4B per-layer-embedding model tensors (tensor-presence gated).
1802 let e4b = match src.find("per_layer_token_embd.weight") {
1803 Some(t) => {
1804 let n_epl = cfg
1805 .gemma4
1806 .as_ref()
1807 .map(|g| g.n_embd_per_layer as usize)
1808 .unwrap_or(0);
1809 let row = t.ne[0] as usize; // n_epl * n_layer
1810 let row_bytes = t.bytes.len() / (t.ne[1] as usize);
1811 eprintln!(
1812 "[gemma4-e4b] per-layer-embed model detected (n_epl={n_epl}, row {row}) — \
1813 first-light forward (eager decode + prime); dc/graph/spec unwired \
1814 (HANDOVER-E4B.md)"
1815 );
1816 Some(crate::hybrid::Gemma4E4bModel {
1817 tok_tbl_gpu: std::sync::OnceLock::new(),
1818 tok_embd_bytes: t.bytes.to_vec(),
1819 tok_embd_qt: match t.ggml_type {
1820 memra_gguf::GgmlType::Q6_K => crate::QT_Q6_K,
1821 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
1822 other => panic!("e4b per-layer tok embd: unhandled dtype {other:?}"),
1823 },
1824 tok_embd_row_bytes: row_bytes,
1825 model_proj: load_t(e, src, "per_layer_model_proj.weight")?,
1826 proj_norm: load_t(e, src, "per_layer_proj_norm.weight")?,
1827 n_epl,
1828 })
1829 }
1830 None => None,
1831 };
1832 let suppress_d = {
1833 let sup = &cfg.gemma4.as_ref().unwrap().suppress_tokens;
1834 if sup.is_empty() {
1835 None
1836 } else {
1837 let ids: Vec<i32> = sup.iter().map(|&x| x as i32).collect();
1838 eprintln!(
1839 "[gemma4] suppress_tokens: {} ids masked at sampling",
1840 ids.len()
1841 );
1842 Some((e.htod_i32(&ids)?, ids.len()))
1843 }
1844 };
1845 let ones_host = [1.0f32; 512];
1846 let mut ones = Vec::new();
1847 if let Some(fence) = crate::pp::pp_cuts(n_trunk) {
1848 for s in 0..fence.len() - 1 {
1849 let owner = crate::pp::layer_engine(e, n_trunk, fence[s])?;
1850 let dev = owner.ctx().ordinal();
1851 if ones.iter().all(|(d, _)| *d != dev) {
1852 ones.push((dev, owner.htod(&ones_host)?));
1853 }
1854 }
1855 } else {
1856 ones.push((e.ctx().ordinal(), e.htod(&ones_host)?));
1857 }
1858 Some(GemmaAux {
1859 rope_freqs,
1860 ones,
1861 suppress_d,
1862 e4b,
1863 })
1864 } else {
1865 None
1866 };
1867 // step35: rope_freqs.weight [n_rot_full/2] — FULL-attn layers only (SWA passes null).
1868 // Loaded by tensor presence, not required: the key is absent on a sibling without
1869 // llama3-style scaling, and `None` is the correct "no factors" signal for rope_neox2.
1870 let step35_aux = if cfg.step35.is_some() {
1871 let rope_freqs = match src.find("rope_freqs.weight") {
1872 Some(t) => {
1873 let host = memra_gguf::dequant::dequantize(
1874 t.ggml_type,
1875 &t.bytes,
1876 t.ne.iter().product::<u64>() as usize,
1877 );
1878 let mut copies = Vec::new();
1879 if let Some(fence) = crate::pp::pp_cuts(n_trunk) {
1880 for s in 0..fence.len() - 1 {
1881 let owner = crate::pp::layer_engine(e, n_trunk, fence[s])?;
1882 let dev = owner.ctx().ordinal();
1883 if copies.iter().all(|(d, _)| *d != dev) {
1884 copies.push((dev, owner.htod(&host)?));
1885 }
1886 }
1887 } else {
1888 copies.push((e.ctx().ordinal(), e.htod(&host)?));
1889 }
1890 Some(copies)
1891 }
1892 None => None,
1893 };
1894 Some(Step35Aux { rope_freqs })
1895 } else {
1896 None
1897 };
1898 let mut layers = layers;
1899 // Q8_0 SPLIT-PLANE DECODE MIRRORS (2026-07-26, the H100 lane): Q8_0-trunk models
1900 // (Qwen3.5-9B class) stream their whole weight mass through the 34B-stride GGUF
1901 // layout — ncu on H100 held Max Bandwidth at 41-46% (Mem Busy 66-76%) from sector
1902 // overfetch. Mirrors route the m<=16 mmvq/batched decode family to the aligned-16B
1903 // `_rp` twins (bit-identical). VRAM cost == the mirrored trunk (~model size), so
1904 // DEFAULT ON only on the Hopper lane (80GB); MEMRA_Q8RP=1/0 overrides either way.
1905 {
1906 let q8rp_on = match std::env::var("MEMRA_Q8RP").as_deref() {
1907 Ok("0") => false,
1908 Ok(_) => true,
1909 Err(_) => cfg!(memra_hopper_mma),
1910 };
1911 // K-quant split-plane mirrors (q4_K/q6_K, 2026-08-01 H100 coalescing fix) ride
1912 // the same trunk walk under their own seam (MEMRA_KQRP, default = hopper lane).
1913 let kqrp_on = crate::Engine::kqrp_enabled();
1914 if q8rp_on || kqrp_on {
1915 // f16 prefill mirrors, PER-MODEL argmax-gate arbitration (round 45): on the
1916 // qwen Q8_0 dense class the f16-prefill-vs-int8-decode gap (maxdiff ~0.67)
1917 // flips the run-gen argmax gate on real prompts (board-2048: 485 vs 332,
1918 // deterministic x5) — gate-violating defaults don't ship. gemma (Q4_0) and
1919 // the MoE hybrids hold MATCH on the same prompt and keep their mirrors.
1920 // MEMRA_PP_F16=1 forces (diagnostic seam); =0 still kills everywhere.
1921 let f16_model_ok = cfg.gemma4.is_some()
1922 || cfg.moe.is_some()
1923 || std::env::var("MEMRA_PP_F16").as_deref() == Ok("1");
1924 let mut nmir = 0usize;
1925 // M2 weight sharding: mirrors are the DECODE weights on these paths — each
1926 // builds through its layer's OWNING stage engine (`e_ref` param), so the
1927 // mirror lands on the device that dereferences it.
1928 let mut mir = |e_ref: &crate::Engine,
1929 w: &mut crate::model::GpuTensor|
1930 -> Result<(), Box<dyn std::error::Error>> {
1931 let before = matches!(w, crate::model::GpuTensor::Quant { rp4: Some(_), .. });
1932 if q8rp_on {
1933 e_ref.build_q8_rp4(w)?;
1934 }
1935 if kqrp_on {
1936 e_ref.build_q4k_rp4(w)?;
1937 e_ref.build_q6k_rp4(w)?;
1938 }
1939 // Q6_K mirrors are model-CLASS-agnostic (round 47): no MMQ arm exists for
1940 // Q6_K — the fallback dequant-GEMM is ~10x the f16 lane (q27's prefill
1941 // wall). The qwen-dense argmax-flip evidence (round 45) was the Q8_0
1942 // mirror specifically; Q6_K admission is arbitrated by its own gate runs.
1943 let q6k = matches!(w, crate::model::GpuTensor::Quant { qtype, .. }
1944 if *qtype == crate::QT_Q6_K);
1945 if q8rp_on && crate::f16_ffi::pp_f16_enabled() && (f16_model_ok || q6k) {
1946 e_ref.build_q8_f16(w)?;
1947 }
1948 if !before && matches!(w, crate::model::GpuTensor::Quant { rp4: Some(_), .. }) {
1949 nmir += 1;
1950 }
1951 Ok(())
1952 };
1953 for (il, layer) in layers.iter_mut().enumerate() {
1954 let el = crate::pp::layer_engine(e, n_trunk, il)?;
1955 match &mut layer.mixer {
1956 Mixer::Full(fa) => {
1957 for w in [&mut fa.wq, &mut fa.wk, &mut fa.wv, &mut fa.wo] {
1958 mir(el, w)?;
1959 }
1960 }
1961 Mixer::Linear(la) => {
1962 for w in [
1963 &mut la.wqkv,
1964 &mut la.wqkv_gate,
1965 &mut la.ssm_beta,
1966 &mut la.ssm_alpha,
1967 &mut la.ssm_out,
1968 ] {
1969 mir(el, w)?;
1970 }
1971 }
1972 // MLA: no decode mirrors in increment 2 (its kernels arrive in inc 4;
1973 // mirror admission is arbitrated there with measurements).
1974 Mixer::Mla(_) => {}
1975 }
1976 if let Ffn::Dense {
1977 ffn_gate,
1978 ffn_up,
1979 ffn_down,
1980 } = &mut layer.ffn
1981 {
1982 for w in [ffn_gate, ffn_up, ffn_down] {
1983 mir(el, w)?;
1984 }
1985 }
1986 }
1987 mir(e_head, &mut output)?;
1988 if nmir > 0 {
1989 eprintln!("[q8rp] split-plane decode mirrors built: {nmir} tensors");
1990 }
1991 // Q4_K f16 prefill mirrors (round 49): Q4_K joins the q6k carve-out —
1992 // model-class-agnostic admission, arbitrated by per-model argmax gates
1993 // (the round-45 flip evidence was the Q8_0 mirror on qwen-dense; the q27
1994 // Q4_K bulk rides mul_mat_q_q45k int8-MMA, which the Lt f16 lane beats at
1995 // large m — campaign-A precedent). SECOND pass over the trunk so the shared
1996 // MEMRA_PP_F16_BUDGET_MB keeps FULL Q6_K coverage as its floor: Q6_K mirrors
1997 // replace a ~10x dequant-GEMM (no MMQ arm exists), Q4_K mirrors upgrade a
1998 // working int8-MMA arm — a joint walk would evict late-layer Q6_K mirrors
1999 // for the weaker lever. Layer-order prefix within the Q4_K class.
2000 // Round 49b: Q5_K (q27's 48 ssm_out — the last mul_mat_q_q45k class) rides
2001 // a THIRD pass strictly after all Q4_K, so the default-budget composition
2002 // (and its banked gates) stays byte-identical: the 32GB default is exhausted
2003 // by the Q4_K pass; Q5_K mirrors only light up under a raised
2004 // MEMRA_PP_F16_BUDGET_MB (machine-specific config).
2005 if q8rp_on && crate::f16_ffi::pp_f16_enabled() {
2006 for (want, tag) in [(crate::QT_Q4_K, "q4kf16"), (crate::QT_Q5_K, "q5kf16")] {
2007 let (mut n4, mut b4) = (0usize, 0usize);
2008 let mut mirk =
2009 |e_ref: &crate::Engine,
2010 w: &mut crate::model::GpuTensor|
2011 -> Result<(), Box<dyn std::error::Error>> {
2012 if matches!(w, crate::model::GpuTensor::Quant { qtype, f16: None, .. }
2013 if *qtype == want)
2014 {
2015 e_ref.build_q8_f16(w)?;
2016 if let crate::model::GpuTensor::Quant { f16: Some(m), .. } = w {
2017 n4 += 1;
2018 b4 += m.len();
2019 }
2020 }
2021 Ok(())
2022 };
2023 for (il, layer) in layers.iter_mut().enumerate() {
2024 let el = crate::pp::layer_engine(e, n_trunk, il)?;
2025 match &mut layer.mixer {
2026 Mixer::Full(fa) => {
2027 for w in [&mut fa.wq, &mut fa.wk, &mut fa.wv, &mut fa.wo] {
2028 mirk(el, w)?;
2029 }
2030 }
2031 Mixer::Linear(la) => {
2032 for w in [
2033 &mut la.wqkv,
2034 &mut la.wqkv_gate,
2035 &mut la.ssm_beta,
2036 &mut la.ssm_alpha,
2037 &mut la.ssm_out,
2038 ] {
2039 mirk(el, w)?;
2040 }
2041 }
2042 Mixer::Mla(_) => {} // no mirrors in increment 2 (see above)
2043 }
2044 if let Ffn::Dense {
2045 ffn_gate,
2046 ffn_up,
2047 ffn_down,
2048 } = &mut layer.ffn
2049 {
2050 for w in [ffn_gate, ffn_up, ffn_down] {
2051 mirk(el, w)?;
2052 }
2053 }
2054 }
2055 mirk(e_head, &mut output)?;
2056 if n4 > 0 {
2057 eprintln!(
2058 "[{tag}] prefill fp16 mirrors built: {n4} tensors \
2059 ({} MB)",
2060 b4 >> 20
2061 );
2062 }
2063 }
2064 }
2065 }
2066 }
2067 // Q4_0 SPLIT-PLANE DECODE MIRRORS (2026-07-10, MEMRA_Q4RP seam): gemma-4 MoE-class trunk
2068 // (26B — attn wq/wk/wv/wo + the parallel shared FFN triple). The 18B GGUF block stride
2069 // costs ~25-35% decode bandwidth in sector overfetch (rp_q4_probe: m=1 1.34x, m=3 1.17x,
2070 // bitwise); the mirror (~0.7GB for the 26B) fixes the m<=8 mmvq/batched/fused family.
2071 // Dense 31B is NOT mirrored (its 15GB trunk mirror does not fit 24GB — the full layout
2072 // swap is the follow-up arc); raw bytes stay for prefill/gemm/Stage-A either way.
2073 if cfg.gemma4.is_some() && crate::Engine::q4rp_enabled() {
2074 let mut nmir = 0usize;
2075 for (il, layer) in layers.iter_mut().enumerate() {
2076 // M2 weight sharding: mirrors/concats build through the owning stage engine.
2077 let e = crate::pp::layer_engine(e, n_trunk, il)?;
2078 // 26B MoE-class trunk (moe_bits) OR the E4B dense trunk (e4b bits). E4B mirror
2079 // arithmetic: attn ~7.5MB/layer (shared layers skip wk/wv via build's no-op on
2080 // duplicate mirrors is NOT automatic — they alias the target's tensors as
2081 // separate GpuTensors, so their mirrors double ~1.5MB/shared-layer; acceptable)
2082 // + dense ffn 3 x 2560x10240 Q4_0 ~44MB + inp_gate/proj ~0.75MB => ~2.2GB for
2083 // the 5.2GB model; 24GB card holds model+mirror+KV with >14GB headroom.
2084 // Dense 31B stays unmirrored (15GB mirror does not fit) — its arm is the
2085 // layout-swap follow-up.
2086 let is_moe26 = layer.gemma4.as_ref().is_some_and(|g| g.moe_bits.is_some());
2087 let is_e4b = layer.gemma4.as_ref().is_some_and(|g| g.e4b.is_some());
2088 if !(is_moe26 || is_e4b) {
2089 continue;
2090 }
2091 if let Mixer::Full(fa) = &mut layer.mixer {
2092 for w in [&mut fa.wq, &mut fa.wk, &mut fa.wv, &mut fa.wo] {
2093 e.build_q4_rp4(w)?;
2094 nmir += 1;
2095 }
2096 }
2097 if is_e4b {
2098 // wave-4b: own-KV layers get the wq|wk|wv OUT-concat (one matvec at t=1).
2099 let own_kv = layer
2100 .gemma4
2101 .as_ref()
2102 .unwrap()
2103 .e4b
2104 .as_ref()
2105 .is_some_and(|e4| e4.kv_share.is_none());
2106 if own_kv {
2107 if let Mixer::Full(fa) = &layer.mixer {
2108 if let Some(mut cat) = e.build_q4_out_concat3(&fa.wq, &fa.wk, &fa.wv)? {
2109 e.build_q4_rp4(&mut cat)?;
2110 nmir += 1;
2111 layer.gemma4.as_mut().unwrap().e4b.as_mut().unwrap().qkv_cat =
2112 Some(cat);
2113 }
2114 }
2115 }
2116 if let Ffn::Dense {
2117 ffn_gate,
2118 ffn_up,
2119 ffn_down,
2120 } = &mut layer.ffn
2121 {
2122 for w in [ffn_gate, ffn_up, ffn_down] {
2123 e.build_q4_rp4(w)?;
2124 nmir += 1;
2125 }
2126 }
2127 let e4 = layer.gemma4.as_mut().unwrap().e4b.as_mut().unwrap();
2128 for w in [&mut e4.inp_gate, &mut e4.proj] {
2129 e.build_q4_rp4(w)?;
2130 nmir += 1;
2131 }
2132 }
2133 if let Some(mb) = layer.gemma4.as_mut().unwrap().moe_bits.as_mut() {
2134 for w in [&mut mb.shared_gate, &mut mb.shared_up, &mut mb.shared_down] {
2135 e.build_q4_rp4(w)?;
2136 nmir += 1;
2137 }
2138 }
2139 }
2140 if nmir > 0 {
2141 eprintln!("[q4rp] split-plane decode mirrors built: {nmir} trunk tensors");
2142 }
2143 // DENSE gemma (31B / E4B trunks): the trunk is too big to MIRROR on 24GB, so the
2144 // split layout replaces the GGUF bytes IN PLACE (zero steady-state VRAM; the 31B
2145 // profile put 76% of decode on the non-rp q4_0 matvecs). Every consumer routes
2146 // off the tensor's rp flag: mmvq/batched `_rp` twins + qmatvec_gemm_q4_0_rp
2147 // prefill. The Stage-A f32 oracle reads GGUF layout, so the swap is gated on the
2148 // fast path being active (MEMRA_FAST=0 keeps GGUF bytes end to end — exact oracle).
2149 let fast_on = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
2150 if fast_on {
2151 let mut nswap = 0usize;
2152 let mut nf16 = 0usize;
2153 // f16 prefill mirrors (campaign A, 2026-07-31): built from the GGUF Q4_0
2154 // bytes BEFORE the in-place rp swap destroys that layout. Same Lt lane and
2155 // budget env as the qwen Q8_0 mirrors (MEMRA_PP_F16 / MEMRA_PP_F16_BUDGET_MB;
2156 // Hopper default ON, sm_120a default OFF — the 24GB card can't carry them).
2157 // Per-model (battery-keyed, 2026-07-31, REAL-prompt gates — the fox-repeat
2158 // family is layout-lottery degenerate and was retired from campaign gates):
2159 // 12B pp1736 8.3k -> 17.1k MATCH; 31B pp1736 4.8k -> 7.6k MATCH but ONLY
2160 // with the full-trunk mirror (420 tensors ~53GB — set
2161 // MEMRA_PP_F16_BUDGET_MB=57344 on 80GB boxes; the default 32GB partial
2162 // mirror measured FLAT there). MEMRA_Q4F16=1|0 forces either way.
2163 let q4f16_model_ok = matches!(cfg.n_embd, 3840 | 5376); // 12B | 31B geometry
2164 let f16_on = match std::env::var("MEMRA_Q4F16").as_deref() {
2165 Ok("1") => crate::f16_ffi::pp_f16_enabled(),
2166 Ok("0") => false,
2167 _ => crate::f16_ffi::pp_f16_enabled() && q4f16_model_ok,
2168 };
2169 for (il, layer) in layers.iter_mut().enumerate() {
2170 // M2 weight sharding: swap/mirror through the owning stage engine.
2171 let e = crate::pp::layer_engine(e, n_trunk, il)?;
2172 let dense_gemma = layer.gemma4.as_ref().is_some_and(|g| g.moe_bits.is_none());
2173 if !dense_gemma {
2174 continue;
2175 }
2176 if let Mixer::Full(fa) = &mut layer.mixer {
2177 for w in [&mut fa.wq, &mut fa.wk, &mut fa.wv, &mut fa.wo] {
2178 if f16_on {
2179 e.build_q8_f16(w)?;
2180 if matches!(w, crate::model::GpuTensor::Quant { f16: Some(_), .. })
2181 {
2182 nf16 += 1;
2183 }
2184 }
2185 if e.build_q4_rp_swap(w)? {
2186 nswap += 1;
2187 }
2188 }
2189 }
2190 if let Ffn::Dense {
2191 ffn_gate,
2192 ffn_up,
2193 ffn_down,
2194 } = &mut layer.ffn
2195 {
2196 for w in [ffn_gate, ffn_up, ffn_down] {
2197 if f16_on {
2198 e.build_q8_f16(w)?;
2199 if matches!(w, crate::model::GpuTensor::Quant { f16: Some(_), .. })
2200 {
2201 nf16 += 1;
2202 }
2203 }
2204 if e.build_q4_rp_swap(w)? {
2205 nswap += 1;
2206 }
2207 }
2208 }
2209 }
2210 if nswap > 0 {
2211 eprintln!("[q4rp] split-plane IN-PLACE swap: {nswap} dense trunk tensors");
2212 }
2213 if nf16 > 0 {
2214 eprintln!("[q4f16] prefill fp16 mirrors built: {nf16} dense trunk tensors");
2215 }
2216 }
2217 }
2218 let model = HybridModel {
2219 cfg,
2220 embd,
2221 output_norm,
2222 output,
2223 layers,
2224 mtp,
2225 embd_gpu: std::sync::OnceLock::new(),
2226 gemma4_aux,
2227 step35_aux,
2228 prime_slabs: std::sync::Mutex::new(std::collections::HashMap::new()),
2229 };
2230 e.configure_moe_cache_layout(model.moe_cache_block_sizes());
2231 if force_embd_gpu {
2232 let _ = model
2233 .embd_gpu
2234 .get_or_init(|| e.upload_u8(&model.embd.raw).expect("embed table upload"));
2235 }
2236 // M2 LOAD BARRIER (pp door open at load): uploads + mirror builds above ran on
2237 // the loading engines' worker streams; the first decode consumer runs on OTHER
2238 // streams with no event between them. Synchronize every stage context once so
2239 // no consumer can ever read a half-built tensor (the 2026-08-02 split5 ref=0.0
2240 // head-mirror find). No-op with the door shut.
2241 crate::pp::sync_stages_after_load(e, n_trunk)?;
2242 Ok(model)
2243 }
2244
2245 /// Force the device embed table resident, FALLIBLY (F5 right-size ladder,
2246 /// 2026-08-05). The lazy `embd_gpu.get_or_init(.. expect ..)` sites panic the
2247 /// GPU worker on OOM; on a VRAM-tight rig a right-sized spec session that
2248 /// "fits" can leave too little for this ~hundreds-of-MB upload and die on its
2249 /// first prefill (observed: research/specpool-20260804/server-ladder-miss.log).
2250 /// The server calls this after each ladder landing so the biggest lazy
2251 /// transient surfaces as a catchable Err (shrink further / fall back) instead
2252 /// of a panic. No-op when the host-gather door (MEMRA_EMBED_DEV=0) is open or
2253 /// the table is already resident.
2254 pub fn ensure_embed_resident(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
2255 if std::env::var("MEMRA_EMBED_DEV").as_deref() == Ok("0") {
2256 return Ok(());
2257 }
2258 if self.embd_gpu.get().is_none() {
2259 let buf = e.upload_u8(&self.embd.raw)?;
2260 let _ = self.embd_gpu.set(buf); // racing set = already resident; fine
2261 }
2262 Ok(())
2263 }
2264
2265 pub fn embed(
2266 &self,
2267 e: &Engine,
2268 tokens: &[u32],
2269 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2270 let n_embd = self.cfg.n_embd as usize;
2271 // DEVICE embed gather (round 30; the gemma4 machinery adopted for every model):
2272 // resident quantized table + gather kernel — replaces the CPU row gather + 31MB
2273 // pageable HtoD (2.2ms at T=2048, the lane's largest host stall). Same d*q
2274 // dequant math as the CPU gather; the greedy-stream A/B arbitrates.
2275 // MEMRA_EMBED_DEV=0 reverts.
2276 if std::env::var("MEMRA_EMBED_DEV").as_deref() != Ok("0") {
2277 let tbl = self
2278 .embd_gpu
2279 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
2280 let tok_d = e.htod_u32_v(tokens)?;
2281 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
2282 return e.embed_gather_device_td(tbl, &tok_d, tokens.len(), n_embd, qt, rb);
2283 }
2284 let x = self.embd.gather(n_embd, tokens);
2285 Ok(e.htod(&x)?)
2286 }
2287}
2288
2289#[cfg(test)]
2290mod residency_tests {
2291 use super::residency_bytes_by_device;
2292
2293 #[test]
2294 fn pp_residency_counts_only_each_devices_expert_slice() {
2295 let tensors = [
2296 ("blk.0.ffn_gate_exps.weight", 10usize),
2297 ("blk.0.ffn_up_exps.weight", 20),
2298 ("blk.1.ffn_down_exps.weight", 30),
2299 ("blk.2.ffn_gate_exps.weight", 40),
2300 ("blk.3.ffn_up_exps.weight", 50),
2301 ("blk.0.attn_q.weight", 7),
2302 ("output.weight", 11),
2303 ];
2304 let bytes = residency_bytes_by_device(tensors, &[0, 0, 1, 1], 0);
2305 assert_eq!(bytes.experts.get(&0), Some(&60));
2306 assert_eq!(bytes.experts.get(&1), Some(&90));
2307 assert_eq!(bytes.rest, 18);
2308 assert!(bytes.saw_experts);
2309 }
2310
2311 #[test]
2312 fn pp_residency_combines_stages_that_share_one_device() {
2313 let tensors = [
2314 ("blk.0.ffn_gate_exps.weight", 10usize),
2315 ("blk.1.ffn_gate_exps.weight", 20),
2316 ("blk.2.ffn_gate_exps.weight", 30),
2317 ("blk.3.ffn_gate_exps.weight", 40),
2318 ];
2319 let bytes = residency_bytes_by_device(tensors, &[0, 0, 0, 0], 0);
2320 assert_eq!(bytes.experts.get(&0), Some(&100));
2321 assert_eq!(bytes.experts.len(), 1);
2322 }
2323}
2324
2325#[cfg(test)]
2326mod draft_head_tests {
2327 use super::draft_head_tensor;
2328
2329 /// Names present in the real Step-3.7-Flash MTP drafter (Step3.7-flash-mtp-Q8_0.gguf), as
2330 /// enumerated by the on-disk byte probe in
2331 /// research/step37-p2-20260806/raw/draft-head-tensor-hashes-20260807.txt.
2332 /// Both candidate heads exist in that file with IDENTICAL [4096, 128896] Q8_0 shape, so no
2333 /// shape or dtype check can distinguish them — only the sha256 of the payload could, and it
2334 /// showed them to be different matrices (blk.45 head c90b907b… vs output.weight 3eec5831…).
2335 const STEP37_DRAFTER: &[&str] = &[
2336 "output.weight",
2337 "output_norm.weight",
2338 "token_embd.weight",
2339 "blk.45.nextn.shared_head_norm.weight",
2340 "blk.45.nextn.shared_head_head.weight",
2341 "blk.46.nextn.shared_head_head.weight",
2342 "blk.47.nextn.shared_head_head.weight",
2343 ];
2344
2345 fn present(names: &'static [&'static str]) -> impl Fn(&str) -> bool {
2346 move |t: &str| names.contains(&t)
2347 }
2348
2349 /// THE REGRESSION. Reading `output.weight` off this drafter cost acceptance 0/248 across
2350 /// K=1..8 with self-consistency PASS at every K — correct output, dead speculation, no gate
2351 /// red (raw/mtp-draft-20260806T212902Z.log). The drafter's top-level output stack is a
2352 /// re-quantized COPY OF THE TRUNK'S (its output_norm is byte-identical to the trunk's,
2353 /// d7526f44…), so it is the standalone-decode head, not the MTP head. Preferring
2354 /// blk.45.nextn.shared_head_head took K=1 to 14/18 = 77.8%
2355 /// (raw/mtp-draft-PASS-20260806T215132Z.log).
2356 #[test]
2357 fn step37_drafter_prefers_the_blocks_own_nextn_head_over_file_level_output() {
2358 assert_eq!(
2359 draft_head_tensor(present(STEP37_DRAFTER), 45),
2360 "blk.45.nextn.shared_head_head.weight"
2361 );
2362 }
2363
2364 /// Each NextN block owns a DIFFERENT head (c90b907b / a22d2957 / 4b21e137 — a shared head
2365 /// would have collided), so the name must be built from the block index, never hardcoded.
2366 /// This is what multi-block chaining (45->46->47) will index when it lands.
2367 #[test]
2368 fn each_nextn_block_selects_its_own_head() {
2369 for n in 45..=47u32 {
2370 assert_eq!(
2371 draft_head_tensor(present(STEP37_DRAFTER), n),
2372 format!("blk.{n}.nextn.shared_head_head.weight")
2373 );
2374 }
2375 }
2376
2377 /// FR-Spec / tied-head drafts publish the (possibly vocab-trimmed) head as the file-level
2378 /// `output.weight` and ship no nextn head. They must keep working — hence preference, not
2379 /// replacement.
2380 #[test]
2381 fn draft_without_a_nextn_head_falls_back_to_file_level_output() {
2382 let fr_spec: &[&str] = &["output.weight", "output_norm.weight", "d2t.weight"];
2383 assert_eq!(draft_head_tensor(present(fr_spec), 45), "output.weight");
2384 }
2385
2386 /// The legacy `nextn.shared_head` probe sits between the two: no shipped artifact and no
2387 /// upstream mapping uses it (upstream is LLM_TENSOR_NEXTN_SHARED_HEAD_HEAD ->
2388 /// "blk.%d.nextn.shared_head_head"), but anything that ever matched it still must, and it
2389 /// must never win over the real name.
2390 #[test]
2391 fn legacy_shared_head_is_probed_but_loses_to_shared_head_head() {
2392 let legacy_only: &[&str] = &["output.weight", "blk.45.nextn.shared_head.weight"];
2393 assert_eq!(
2394 draft_head_tensor(present(legacy_only), 45),
2395 "blk.45.nextn.shared_head.weight"
2396 );
2397
2398 let both: &[&str] = &[
2399 "output.weight",
2400 "blk.45.nextn.shared_head.weight",
2401 "blk.45.nextn.shared_head_head.weight",
2402 ];
2403 assert_eq!(
2404 draft_head_tensor(present(both), 45),
2405 "blk.45.nextn.shared_head_head.weight"
2406 );
2407 }
2408
2409 /// A drafter whose nextn head belongs to a DIFFERENT block must not be borrowed: asking for
2410 /// block 45 in a file that only carries 46/47 falls back rather than silently mismatching
2411 /// the geometry the trunk verified against.
2412 #[test]
2413 fn a_different_blocks_nextn_head_is_never_borrowed() {
2414 let wrong_block: &[&str] = &[
2415 "output.weight",
2416 "blk.46.nextn.shared_head_head.weight",
2417 "blk.47.nextn.shared_head_head.weight",
2418 ];
2419 assert_eq!(draft_head_tensor(present(wrong_block), 45), "output.weight");
2420 }
2421}