1use crate::Engine;
6use crate::cache::Cache;
7use cudarc::driver::CudaSlice;
8use memra_gguf::config::ModelConfig;
9
10pub struct PrimeSlabs {
13 pub t_cap: usize,
14 pub h: CudaSlice<f32>,
15 pub x1: CudaSlice<f32>,
16 pub z: CudaSlice<f32>,
17 pub act: CudaSlice<f32>,
18 pub xa: CudaSlice<f32>,
19 pub xb: CudaSlice<f32>,
20 pub h16: CudaSlice<u8>,
21 pub z16: CudaSlice<u8>,
22 pub gate: CudaSlice<f32>, pub up: CudaSlice<f32>, pub ffn_out: CudaSlice<f32>, pub seg_glue: Vec<Option<cudarc::driver::CudaGraph>>,
32 pub mixed: CudaSlice<f32>,
36 pub seg_mid: Vec<Option<cudarc::driver::CudaGraph>>,
37 pub seg_t: usize,
38}
39
40unsafe impl Send for PrimeSlabs {}
43
44fn empty_cache_layers<T>(n: usize) -> Vec<Option<T>> {
45 std::iter::repeat_with(|| None).take(n).collect()
46}
47
48struct PrimeCacheStages<'a> {
53 parent: &'a mut Cache,
54 cut: usize,
55 stage0: Cache,
56 stage1: Cache,
57}
58
59impl<'a> PrimeCacheStages<'a> {
60 fn new(parent: &'a mut Cache, cut: usize) -> Self {
61 let n = parent.kv.len();
62 assert_eq!(parent.recur.len(), n, "cache layer vectors disagree");
63 assert!(cut <= n, "PP-2 cache cut {cut} exceeds {n} layers");
64 let mut kv0 = empty_cache_layers(n);
65 let mut kv1 = empty_cache_layers(n);
66 let mut recur0 = empty_cache_layers(n);
67 let mut recur1 = empty_cache_layers(n);
68 for i in 0..cut {
69 kv0[i] = parent.kv[i].take();
70 recur0[i] = parent.recur[i].take();
71 }
72 for i in cut..n {
73 kv1[i] = parent.kv[i].take();
74 recur1[i] = parent.recur[i].take();
75 }
76 let pos = parent.pos;
77 let max_ctx = parent.max_ctx;
78 Self {
79 parent,
80 cut,
81 stage0: Cache {
82 kv: kv0,
83 recur: recur0,
84 pos,
85 max_ctx,
86 last_logits_dev: None,
87 dflash_taps: None,
88 },
89 stage1: Cache {
90 kv: kv1,
91 recur: recur1,
92 pos,
93 max_ctx,
94 last_logits_dev: None,
95 dflash_taps: None,
96 },
97 }
98 }
99
100 fn parts(&mut self) -> (&mut Cache, &mut Cache) {
101 (&mut self.stage0, &mut self.stage1)
102 }
103}
104
105impl Drop for PrimeCacheStages<'_> {
106 fn drop(&mut self) {
107 let n = self.parent.kv.len();
108 for i in 0..n {
109 let source = if i < self.cut {
110 &mut self.stage0
111 } else {
112 &mut self.stage1
113 };
114 debug_assert!(self.parent.kv[i].is_none());
115 debug_assert!(self.parent.recur[i].is_none());
116 self.parent.kv[i] = source.kv[i].take();
117 self.parent.recur[i] = source.recur[i].take();
118 }
119 self.parent.pos = self.stage0.pos.min(self.stage1.pos);
120 }
121}
122
123pub(crate) struct AttnPre {
125 pub q: cudarc::driver::CudaSlice<f32>,
126 pub k: cudarc::driver::CudaSlice<f32>,
127 pub v: cudarc::driver::CudaSlice<f32>,
128 pub gate: Option<cudarc::driver::CudaSlice<f32>>,
129}
130
131pub(crate) struct GdnPrep {
133 pub hk: usize,
134 pub q_l2: cudarc::driver::CudaSlice<f32>,
135 pub k_l2: cudarc::driver::CudaSlice<f32>,
136 pub v_g: cudarc::driver::CudaSlice<f32>,
137 pub beta: cudarc::driver::CudaSlice<f32>,
138 pub g_log: cudarc::driver::CudaSlice<f32>,
139 pub kb16: Option<cudarc::driver::CudaSlice<u8>>,
140 pub qb16: Option<cudarc::driver::CudaSlice<u8>>,
141}
142
143pub(crate) struct VerifyStreamScratch {
145 pub pos_d: CudaSlice<i32>,
146 pub row_ctrs: Vec<CudaSlice<i32>>,
147}
148use crate::hybrid::{FullAttnLayer, HybridModel, LinearAttnLayer, Mixer, MoeWeights};
149
150struct MoeInputTraceWriter {
151 dir: std::path::PathBuf,
152 index: std::fs::File,
153 payloads: std::collections::HashMap<u16, (std::fs::File, u64)>,
154}
155
156static MOE_INPUT_TRACE_WRITER: std::sync::OnceLock<std::sync::Mutex<Option<MoeInputTraceWriter>>> =
157 std::sync::OnceLock::new();
158
159fn gdec_enabled() -> bool {
162 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
163 *E.get_or_init(|| {
164 std::env::var("MEMRA_MOE_GDEC")
165 .map(|v| v != "0")
166 .unwrap_or(true)
167 })
168}
169
170fn moe_slab_enabled() -> bool {
181 std::env::var("MEMRA_MOE_SLAB").as_deref() != Ok("0")
182}
183
184fn moe_grouped_enabled(_cfg: &ModelConfig, _prefill: bool) -> bool {
188 std::env::var("MEMRA_MOE_GROUPED")
189 .map(|value| value != "0")
190 .unwrap_or(false)
191}
192
193fn moe_prefetch_enabled() -> bool {
196 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
197 *E.get_or_init(|| {
198 std::env::var("MEMRA_MOE_PREFETCH").as_deref() == Ok("1")
199 || crate::spill_pread::worker_enabled()
200 })
201}
202
203fn moe_page_prefetch_window() -> usize {
208 static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
209 *W.get_or_init(|| {
210 page_prefetch_window_from_values(
211 std::env::var("MEMRA_MOE_PAGE_PREFETCH").as_deref() == Ok("1"),
212 std::env::var("MEMRA_MOE_PAGE_PREFETCH_WINDOW")
213 .ok()
214 .as_deref(),
215 )
216 })
217}
218
219fn page_prefetch_window_from_values(enabled: bool, raw_window: Option<&str>) -> usize {
220 if !enabled {
221 return 0;
222 }
223 raw_window.and_then(|value| value.parse().ok()).unwrap_or(1)
224}
225
226fn page_prefetch_positions(position: usize, len: usize, window: usize) -> std::ops::Range<usize> {
230 if window == 0 || position >= len {
231 return len..len;
232 }
233 let (start, count) = if position == 0 {
234 (1, window)
235 } else {
236 (position.saturating_add(window), 1)
237 };
238 let start = start.min(len);
239 start..start.saturating_add(count).min(len)
240}
241
242fn grouped_worker_prefetch_position(order_len: usize, current: Option<usize>) -> Option<usize> {
245 let position = current.map_or(0, |position| position.saturating_add(1));
246 (position < order_len).then_some(position)
247}
248
249fn worker_prefetch_window() -> usize {
254 static WINDOW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
255 *WINDOW.get_or_init(|| {
256 let automatic = crate::spill_pread::configured_depth().saturating_sub(1) / 3;
257 std::env::var("MEMRA_SPILL_WORKER_EXPERT_WINDOW")
258 .ok()
259 .and_then(|value| value.parse::<usize>().ok())
260 .unwrap_or(automatic.max(1))
261 })
262}
263
264fn worker_prefetch_positions(position: usize, len: usize, window: usize) -> std::ops::Range<usize> {
268 if window == 0 || position >= len {
269 return len..len;
270 }
271 let (start, count) = if position == 0 {
272 (0, window)
273 } else {
274 (position.saturating_add(window).saturating_sub(1), 1)
275 };
276 let start = start.min(len);
277 start..start.saturating_add(count).min(len)
278}
279
280fn moe_dev_enabled() -> bool {
285 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
286 *E.get_or_init(|| {
287 std::env::var("MEMRA_MOE_DEV")
288 .map(|v| v != "0")
289 .unwrap_or(true)
290 && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0"))
291 })
292}
293
294fn sigmoid_router_enabled() -> bool {
297 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
298 *E.get_or_init(|| {
299 std::env::var("MEMRA_SIG_ROUTER")
300 .map(|v| v != "0")
301 .unwrap_or(true)
302 })
303}
304
305fn moe_q8_enabled() -> bool {
310 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
311 *E.get_or_init(|| {
312 std::env::var("MEMRA_MOE_Q8")
313 .map(|v| v != "0")
314 .unwrap_or(true)
315 })
316}
317
318fn expert_dp4a_supported(qt: i32) -> bool {
321 qt == crate::QT_Q4_0
322 || qt == crate::QT_IQ3_S
323 || qt == crate::QT_IQ4_XS
324 || qt == crate::QT_Q3_K
325 || qt == crate::QT_Q4_K
326 || qt == crate::QT_Q6_K
327}
328
329fn q8_expert_supported(qt: i32) -> bool {
330 static KQ: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
336 let kq = *KQ.get_or_init(|| {
337 std::env::var("MEMRA_MOE_Q8_KQ")
338 .map(|v| v != "0")
339 .unwrap_or(true)
340 });
341 let nvfp4_q8 = std::env::var("MEMRA_MOE_Q8_NVFP4")
348 .map(|v| v != "0")
349 .unwrap_or(true);
350 qt == crate::QT_IQ3_S
351 || qt == crate::QT_IQ4_XS
352 || (nvfp4_q8 && qt == crate::QT_NVFP4)
353 || (kq && (qt == crate::QT_Q3_K || qt == crate::QT_Q4_K || qt == crate::QT_Q6_K))
354}
355
356fn q8_expert_dec_supported(qt: i32) -> bool {
359 qt == crate::QT_IQ3_S || qt == crate::QT_IQ4_XS || qt == crate::QT_Q4_0
360}
361
362fn f16g_proj_ok(qt: i32, in_f: usize) -> bool {
368 match qt {
369 crate::QT_Q4_0 => in_f % 32 == 0,
370 crate::QT_IQ4_XS | crate::QT_IQ3_S | crate::QT_Q3_K | crate::QT_Q4_K | crate::QT_Q6_K => {
371 in_f % 256 == 0
372 }
373 _ => false,
374 }
375}
376
377fn moe_prewarm_enabled() -> bool {
380 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
381 *E.get_or_init(|| {
382 std::env::var("MEMRA_MOE_PREWARM")
383 .map(|v| v != "0")
384 .unwrap_or(true)
385 })
386}
387
388fn cpu_expert_profile_admit_enabled() -> bool {
392 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
393 *E.get_or_init(|| std::env::var("MEMRA_CPU_EXPERT_FREEZE_PROFILE_ADMIT").as_deref() == Ok("1"))
394}
395
396pub const PRIME_MIN_T: usize = 16;
400const PRIME_PIPE_MICROBATCHES: usize = 8;
401const PRIME_PIPE_MIN_CHUNK: usize = 128;
402const PRIME_PIPE_EDGE_MIN_CHUNK: usize = 64;
403const PRIME_PIPE_LINEAR_WORK: usize = 8;
404
405fn prime_pp2_auto_geometry(n_layers: usize) -> bool {
406 crate::pp::prime_pp_on()
407 && !crate::pp::pp2_streams_off()
408 && crate::pp::pp_cuts(n_layers).is_some_and(|cuts| cuts.len() == 3)
409}
410
411pub fn prime_chunk_tokens(t: usize, n_layers: usize) -> usize {
415 if let Ok(value) = std::env::var("MEMRA_PRIME_CHUNK") {
416 let parsed = value
417 .parse::<usize>()
418 .unwrap_or(crate::cache::PRIME_CHUNK_MAX_TOKENS);
419 return if crate::cache::swa_ring_on() {
420 if parsed == 0 {
421 crate::cache::PRIME_CHUNK_MAX_TOKENS
422 } else {
423 parsed.min(crate::cache::PRIME_CHUNK_MAX_TOKENS)
424 }
425 } else {
426 parsed
427 };
428 }
429 let chunk = crate::cache::PRIME_CHUNK_MAX_TOKENS;
430 if prime_pp2_auto_geometry(n_layers) && t >= 2 * PRIME_PIPE_MIN_CHUNK {
431 chunk.min(
432 t.div_ceil(PRIME_PIPE_MICROBATCHES)
433 .max(PRIME_PIPE_MIN_CHUNK),
434 )
435 } else {
436 chunk
437 }
438}
439
440fn fixed_prime_chunk_ranges(t: usize, chunk: usize) -> Vec<(usize, usize)> {
441 fixed_prime_chunk_ranges_for_ring(t, chunk, crate::cache::swa_ring_on())
442}
443
444fn fixed_prime_chunk_ranges_for_ring(t: usize, chunk: usize, ring_on: bool) -> Vec<(usize, usize)> {
445 if chunk == 0 || t <= chunk {
446 return vec![(0, t)];
447 }
448 let mut ranges = Vec::with_capacity(t.div_ceil(chunk));
449 let mut start = 0usize;
450 while start < t {
451 let mut end = (start + chunk).min(t);
452 if t - end > 0 && t - end < PRIME_MIN_T {
453 if ring_on {
454 let shifted = t - PRIME_MIN_T;
455 end = if shifted > start { shifted } else { t };
456 } else {
457 end = t;
458 }
459 }
460 ranges.push((start, end));
461 start = end;
462 }
463 ranges
464}
465
466fn prime_chunk_work(prefix: usize, total: usize) -> u128 {
467 let prefix = prefix as u128;
468 prefix * (prefix + (PRIME_PIPE_LINEAR_WORK as u128) * (total as u128))
469}
470
471fn dynamic_prime_chunk_ranges(
472 t: usize,
473 fixed_chunk: usize,
474 fixed: &[(usize, usize)],
475) -> Vec<(usize, usize)> {
476 let n = fixed.len();
477 if n < 3 {
478 return fixed.to_vec();
479 }
480
481 let max_first = t - (n - 1) * PRIME_MIN_T;
482 let first = fixed_chunk
483 .div_ceil(2)
484 .max(PRIME_PIPE_EDGE_MIN_CHUNK)
485 .min(max_first);
486 let mut ranges = Vec::with_capacity(n);
487 ranges.push((0, first));
488
489 let first_work = prime_chunk_work(first, t);
490 let work_span = prime_chunk_work(t, t) - first_work;
491 let denominator = (n - 1) as u128;
492 let mut previous = first;
493 for boundary in 1..n - 1 {
494 let target = first_work * denominator + work_span * (boundary as u128);
495 let remaining = n - 1 - boundary;
496 let mut low = previous + PRIME_MIN_T;
497 let mut high = t - remaining * PRIME_MIN_T;
498 while low < high {
499 let mid = low + (high - low) / 2;
500 if prime_chunk_work(mid, t) * denominator >= target {
501 high = mid;
502 } else {
503 low = mid + 1;
504 }
505 }
506 ranges.push((previous, low));
507 previous = low;
508 }
509 ranges.push((previous, t));
510 ranges
511}
512
513pub fn prime_chunk_ranges(t: usize, n_layers: usize) -> Vec<(usize, usize)> {
517 let explicit_chunk = std::env::var_os("MEMRA_PRIME_CHUNK").is_some();
518 let chunk = prime_chunk_tokens(t, n_layers);
519 let fixed = fixed_prime_chunk_ranges(t, chunk);
520 let dynamic = match std::env::var("MEMRA_PRIME_CHUNK_SCHED") {
521 Ok(value) => value == "dynamic",
522 Err(_) => true,
523 };
524 if explicit_chunk || !dynamic || !prime_pp2_auto_geometry(n_layers) {
525 fixed
526 } else {
527 dynamic_prime_chunk_ranges(t, chunk, &fixed)
528 }
529}
530
531impl HybridModel {
532 fn prime_trace_path() -> Option<&'static str> {
537 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
538 P.get_or_init(|| std::env::var("MEMRA_PRIME_TRACE").ok())
539 .as_deref()
540 }
541
542 pub fn forward(
544 &self,
545 e: &Engine,
546 tokens: &[u32],
547 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
548 if self.is_gemma4_e4b() {
549 return self.gemma4_e4b_forward(e, tokens, false);
550 }
551 if self.cfg.gemma4.is_some() {
552 return self.gemma4_forward(e, tokens, false);
553 }
554 let cfg = &self.cfg;
555 let n_embd = cfg.n_embd as usize;
556 let t = tokens.len();
557 let eps = cfg.rms_eps;
558 let pos: Vec<i32> = (0..t as i32).collect();
559 let pos_d = e.htod_i32(&pos)?;
560
561 let mut x = self.embed(e, tokens)?; for (il, layer) in self.layers.iter().enumerate() {
564 let mut h = e.uninit(t * n_embd)?;
566 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
567
568 let mixed = match &layer.mixer {
569 Mixer::Full(fa) => self.full_attn(e, fa, &h, &pos_d, t, il)?,
570 Mixer::Linear(la) => self.linear_attn(e, la, &h, t)?,
571 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
572 };
573
574 let mut x1 = e.uninit(t * n_embd)?;
576 e.add(&x, &mixed, &mut x1, t * n_embd)?;
577
578 let mut z = e.uninit(t * n_embd)?;
580 e.rms_norm(
581 &x1,
582 layer.post_attn_norm.float_data(),
583 &mut z,
584 n_embd,
585 t,
586 eps,
587 )?;
588 let ffn_out = match &layer.ffn {
589 crate::hybrid::Ffn::Dense {
590 ffn_gate,
591 ffn_up,
592 ffn_down,
593 } => {
594 let n_ff = ffn_gate.out_features();
595 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
596 let up = g2.pop().unwrap();
597 let gate = g2.pop().unwrap();
598 let mut act = e.uninit(t * n_ff)?;
599 Self::ffn_act_lim(
604 e,
605 &self.cfg,
606 &gate,
607 &up,
608 1.0,
609 1.0,
610 self.cfg.clamp_shexp_at(il as u32),
611 &mut act,
612 t * n_ff,
613 )?;
614 e.matmul(ffn_down, &act, t)?
615 }
616 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
617 };
618 let mut x2 = e.uninit(t * n_embd)?;
619 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
620 x = x2;
621 }
622
623 let mut hn = e.uninit(t * n_embd)?;
624 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
625 let logits = e.matmul(&self.output, &hn, t)?;
626 Ok(e.dtoh(&logits)?)
627 }
628
629 pub fn forward_last(
635 &self,
636 e: &Engine,
637 tokens: &[u32],
638 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
639 if self.cfg.gemma4.is_some() {
640 return self.gemma4_forward(e, tokens, true);
641 }
642 let cfg = &self.cfg;
643 let n_embd = cfg.n_embd as usize;
644 let t = tokens.len();
645 let eps = cfg.rms_eps;
646 let pos: Vec<i32> = (0..t as i32).collect();
647 let pos_d = e.htod_i32(&pos)?;
648
649 let mut x = self.embed(e, tokens)?; let probe = std::env::var("MEMRA_LAYER_PROBE").is_ok();
653 for (il, layer) in self.layers.iter().enumerate() {
654 let mut h = e.uninit(t * n_embd)?;
655 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
656 if probe {
657 e.stream().synchronize()?;
658 eprintln!("[probe] L{il} norm ok");
659 }
660 let mixed = match &layer.mixer {
661 Mixer::Full(fa) => self.full_attn(e, fa, &h, &pos_d, t, il)?,
662 Mixer::Linear(la) => self.linear_attn(e, la, &h, t)?,
663 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
664 };
665 if probe {
666 e.stream().synchronize()?;
667 eprintln!("[probe] L{il} mixer ok");
668 }
669 let mut x1 = e.uninit(t * n_embd)?;
670 e.add(&x, &mixed, &mut x1, t * n_embd)?;
671 let mut z = e.uninit(t * n_embd)?;
672 e.rms_norm(
673 &x1,
674 layer.post_attn_norm.float_data(),
675 &mut z,
676 n_embd,
677 t,
678 eps,
679 )?;
680 let ffn_out = match &layer.ffn {
681 crate::hybrid::Ffn::Dense {
682 ffn_gate,
683 ffn_up,
684 ffn_down,
685 } => {
686 let n_ff = ffn_gate.out_features();
687 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
688 let up = g2.pop().unwrap();
689 let gate = g2.pop().unwrap();
690 let mut act = e.uninit(t * n_ff)?;
691 Self::ffn_act_lim(
693 e,
694 &self.cfg,
695 &gate,
696 &up,
697 1.0,
698 1.0,
699 self.cfg.clamp_shexp_at(il as u32),
700 &mut act,
701 t * n_ff,
702 )?;
703 e.matmul(ffn_down, &act, t)?
704 }
705 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
706 };
707 if probe {
708 e.stream().synchronize()?;
709 eprintln!("[probe] L{il} ffn ok");
710 }
711 let mut x2 = e.uninit(t * n_embd)?;
712 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
713 x = x2;
714 }
715 let mut hn = e.uninit(t * n_embd)?;
717 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
718 let last = e.view(&hn, t * n_embd); let last_row = last.slice((t - 1) * n_embd..t * n_embd); let mut hlast = e.uninit(n_embd)?;
721 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
722 let logits = e.matmul(&self.output, &hlast, 1)?; Ok(e.dtoh(&logits)?)
724 }
725
726 pub fn prime_cache(
758 &self,
759 e: &Engine,
760 tokens: &[u32],
761 cache: &mut Cache,
762 queued_after: usize,
763 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
764 self.prime_cache_overlaid(e, tokens, cache, queued_after, None)
765 }
766
767 pub fn prime_cache_overlaid(
773 &self,
774 e: &Engine,
775 tokens: &[u32],
776 cache: &mut Cache,
777 queued_after: usize,
778 overlay: Option<&crate::vision::EmbedOverlay>,
779 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
780 let n_embd = self.cfg.n_embd as usize;
781 let t = tokens.len();
782 assert!(
786 t >= PRIME_MIN_T,
787 "prime_cache needs T >= {PRIME_MIN_T} (caller gates)"
788 );
789 assert!(
790 cache.pos + t <= cache.max_ctx,
791 "prime_cache: prompt exceeds cache max_ctx"
792 );
793
794 if self.is_gemma4_e4b() || self.cfg.gemma4.is_some() {
806 if overlay.is_some() {
807 return Err(
808 "vision embedding overlay is qwen35-only (gemma4 prime refuses)".into(),
809 );
810 }
811 if self.is_gemma4_e4b() {
812 return self.gemma4_e4b_prime(e, tokens, cache);
813 }
814 return self.gemma4_prime(e, tokens, cache);
816 }
817 let ranges = prime_chunk_ranges(t, self.layers.len());
818 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
854 let seq_end = if legacy_calllocal {
855 cache.pos + t
856 } else {
857 cache.pos + t + queued_after
858 };
859 if ranges.len() == 1 {
860 return self.prime_chunk(e, tokens, cache, seq_end, 0, overlay);
861 }
862 if crate::pp::prime_pipe_on() && crate::pp::prime_pp_on() && !crate::pp::pp2_streams_off() {
867 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()).filter(|f| f.len() == 3) {
868 if overlay.is_some() {
869 return Err(
870 "vision embedding overlay + pipelined PP prime unsupported (v1); \
871 run the serial prime (single device or MEMRA_PRIME_PIPE=0)"
872 .into(),
873 );
874 }
875 if crate::pp::pp_multi_stream_same_device() {
876 return Err(
877 "prime chunk pipeline refused with 2 stage streams on one device — \
878 that concurrent-stream placement remains quarantined by the deferred \
879 pp flake record. Use one device per stage or MEMRA_PRIME_PIPE=0 for \
880 the serial split."
881 .into(),
882 );
883 }
884 return self.prime_cache_pp2_pipelined(e, tokens, cache, seq_end, &ranges, &fence);
885 }
886 }
887 let mut hiddens = e.uninit(t * n_embd)?;
888 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
889 for &(start, end) in &ranges {
890 let (l, hs, x) =
891 self.prime_chunk(e, &tokens[start..end], cache, seq_end, start, overlay)?;
892 e.copy_into(&mut hiddens, start * n_embd, &x, (end - start) * n_embd)?;
893 last = Some((l, hs));
894 }
895 let (logits, h_seed) = last.unwrap();
896 Ok((logits, h_seed, hiddens))
897 }
898
899 fn prime_cache_pp2_pipelined(
904 &self,
905 e: &Engine,
906 tokens: &[u32],
907 cache: &mut Cache,
908 seq_end: usize,
909 ranges: &[(usize, usize)],
910 fence: &[usize],
911 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
912 debug_assert_eq!(fence.len(), 3);
913 debug_assert!(ranges.len() >= 2);
914 let rt = crate::pp::PpNRt::get(e)?;
915 assert_eq!(
916 rt.n_stages(),
917 2,
918 "prime pipeline requires exactly two PP stages"
919 );
920 let n_embd = self.cfg.n_embd as usize;
921 let t = tokens.len();
922 let initial_base = cache.pos;
923 let caller_stream = e.stream();
924
925 rt.fence_stages_behind(&caller_stream)?;
930 let max_payload = ranges.iter().map(|(s, e)| (e - s) * n_embd).max().unwrap();
931 rt.prepare_overlap_slots(0, max_payload)?;
932
933 let mut hiddens = e.uninit(t * n_embd)?;
934 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
935 let mut stage_caches = PrimeCacheStages::new(cache, fence[1]);
936 let (cache0, cache1) = stage_caches.parts();
937 let (first_start, first_end) = ranges[0];
938 let mut slot = self.prime_pp2_stage0_enqueue(
939 e,
940 rt,
941 &tokens[first_start..first_end],
942 cache0,
943 seq_end,
944 fence,
945 initial_base + first_start,
946 true,
947 )?;
948 cache0.pos = initial_base + first_end;
949
950 for (i, &(start, end)) in ranges.iter().enumerate() {
951 let base = initial_base + start;
952 debug_assert_eq!(
953 cache1.pos, base,
954 "stage 1 must drain chunks in original position order"
955 );
956 let (out, next_slot) = if let Some(&(next_start, next_end)) = ranges.get(i + 1) {
957 let next_base = initial_base + next_start;
958 debug_assert_eq!(
959 cache0.pos, next_base,
960 "stage 0 must issue chunks in original position order"
961 );
962 let cache0_stage = &mut *cache0;
963 std::thread::scope(|scope| -> Result<_, Box<dyn std::error::Error>> {
968 let stage0 = scope.spawn(move || -> Result<usize, String> {
969 let next = self
970 .prime_pp2_stage0_enqueue(
971 e,
972 rt,
973 &tokens[next_start..next_end],
974 cache0_stage,
975 seq_end,
976 fence,
977 next_base,
978 true,
979 )
980 .map_err(|err| err.to_string())?;
981 cache0_stage.pos = initial_base + next_end;
982 Ok(next)
983 });
984 let x = self.prime_pp2_stage1_enqueue(
985 e,
986 rt,
987 slot,
988 end - start,
989 cache1,
990 seq_end,
991 fence,
992 base,
993 true,
994 )?;
995 let out = {
996 rt.bind_stage(1)?;
997 let _st1 = rt.enter(1);
998 let e1 = rt.engine(1, e);
999 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1000 };
1001 let next = stage0
1002 .join()
1003 .map_err(|_| "pipeprime stage-0 host walker panicked")?
1004 .map_err(|err| -> Box<dyn std::error::Error> { err.into() })?;
1005 Ok((out, Some(next)))
1006 })?
1007 } else {
1008 let x = self.prime_pp2_stage1_enqueue(
1009 e,
1010 rt,
1011 slot,
1012 end - start,
1013 cache1,
1014 seq_end,
1015 fence,
1016 base,
1017 true,
1018 )?;
1019 let out = {
1020 rt.bind_stage(1)?;
1021 let _st1 = rt.enter(1);
1022 let e1 = rt.engine(1, e);
1023 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1024 };
1025 (out, None)
1026 };
1027
1028 rt.publish_to(1, &caller_stream)?;
1029 e.copy_into(&mut hiddens, start * n_embd, &out.2, (end - start) * n_embd)?;
1030 last = Some((out.0, out.1));
1031 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1032
1033 if let Some(next) = next_slot {
1034 rt.fence_stages_behind(&caller_stream)?;
1039 slot = next;
1040 }
1041 }
1042
1043 debug_assert_eq!(cache0.pos, initial_base + t);
1044 debug_assert_eq!(cache1.pos, initial_base + t);
1045 let (logits, h_seed) = last.unwrap();
1046 Ok((logits, h_seed, hiddens))
1047 }
1048
1049 fn gdn_hk(e: &Engine, t: usize, num_v: usize, num_k: usize) -> usize {
1056 if Engine::gdn_db_on()
1057 && Engine::gdn_chunked_enabled()
1058 && t >= 16
1059 && e.gdn_mma_enabled(Engine::gdn_chunk_size())
1060 && num_k * 2 == num_v
1061 {
1062 num_k
1063 } else {
1064 num_v
1065 }
1066 }
1067
1068 fn f16out_on(e: &Engine, t: usize) -> bool {
1073 crate::f16_ffi::pp_f16_enabled()
1074 && t >= 16
1075 && !e.verify_exact_on()
1076 && std::env::var("MEMRA_F16OUT").as_deref() != Ok("0")
1077 }
1078
1079 pub fn prime_slabs_get(
1087 &self,
1088 e: &Engine,
1089 t: usize,
1090 n_embd: usize,
1091 n_ff_max: usize,
1092 ) -> Result<std::sync::Arc<std::sync::Mutex<PrimeSlabs>>, Box<dyn std::error::Error>> {
1093 let mut slabs = self.prime_slabs.lock().unwrap();
1094 let dev = e.ctx().ordinal();
1095 let need_new = match slabs.get(&dev) {
1096 None => true,
1097 Some(sl) => sl.lock().unwrap().t_cap < t,
1098 };
1099 if need_new {
1100 slabs.insert(
1101 dev,
1102 std::sync::Arc::new(std::sync::Mutex::new(PrimeSlabs {
1103 t_cap: t,
1104 h: e.uninit(t * n_embd)?,
1105 x1: e.uninit(t * n_embd)?,
1106 z: e.uninit(t * n_embd)?,
1107 act: e.uninit(t * n_ff_max)?,
1108 xa: e.uninit(t * n_embd)?,
1109 xb: e.uninit(t * n_embd)?,
1110 h16: e.alloc_u8_uninit(t * n_embd * 2)?,
1111 z16: e.alloc_u8_uninit(t * n_embd * 2)?,
1112 gate: e.uninit(t * n_ff_max)?,
1113 up: e.uninit(t * n_ff_max)?,
1114 ffn_out: e.uninit(t * n_embd)?,
1115 seg_glue: Vec::new(),
1116 mixed: e.uninit(t * n_embd)?,
1117 seg_mid: Vec::new(),
1118 seg_t: 0,
1119 })),
1120 );
1121 }
1122 Ok(slabs.get(&dev).expect("prime slab inserted").clone())
1123 }
1124
1125 fn prime_chunk(
1129 &self,
1130 e: &Engine,
1131 tokens: &[u32],
1132 cache: &mut Cache,
1133 seq_end: usize,
1134 chunk_off: usize,
1135 overlay: Option<&crate::vision::EmbedOverlay>,
1136 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1137 if crate::pp::pp_host_bounce_active()
1138 && (self.cfg.gemma4.is_some() || !crate::pp::prime_pp_on())
1139 {
1140 return Err(
1141 "prime_chunk: refused with MEMRA_PP_HOST_BOUNCE=1 because this configuration \
1142 has no active prime stage split and would peer-read remote weights; keep \
1143 MEMRA_PRIME_PP enabled and use a PP-prime-supported model"
1144 .into(),
1145 );
1146 }
1147 if self.cfg.gemma4.is_none() && !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
1156 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
1157 if overlay.is_some() {
1158 return Err("vision embedding overlay + PP prime unsupported (v1); \
1159 run single-device or MEMRA_PRIME_PP=0"
1160 .into());
1161 }
1162 return self.prime_chunk_ppn(e, tokens, cache, seq_end, &fence);
1163 }
1164 }
1165 if crate::pp::pp_host_bounce_active() {
1166 return Err(
1167 "prime_chunk: MEMRA_PP_HOST_BOUNCE=1 found no valid prime stage split; \
1168 refusing an unsplit remote-weight walk"
1169 .into(),
1170 );
1171 }
1172 let t = tokens.len();
1173 let base = cache.pos;
1174 debug_assert!(
1175 seq_end >= base + t,
1176 "prime_chunk: seq_end must cover this chunk"
1177 );
1178 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1179 let pos_d = e.htod_i32(&pos)?;
1180
1181 let mut x_embed = self.embed(e, tokens)?; if let Some(ov) = overlay {
1183 let n_embd = self.cfg.n_embd as usize;
1187 for &(pos, row_off, n_rows) in &ov.spans {
1188 let lo = pos.max(chunk_off);
1189 let hi = (pos + n_rows).min(chunk_off + t);
1190 if lo < hi {
1191 let src_row = row_off + (lo - pos);
1192 let view = ov
1193 .rows
1194 .slice(src_row * n_embd..(src_row + (hi - lo)) * n_embd);
1195 e.copy_view_into(
1196 &mut x_embed,
1197 (lo - chunk_off) * n_embd,
1198 &view,
1199 (hi - lo) * n_embd,
1200 )?;
1201 }
1202 }
1203 }
1204 let x = self.prime_layers(
1205 e,
1206 x_embed,
1207 0,
1208 self.layers.len(),
1209 &pos_d,
1210 t,
1211 base,
1212 cache,
1213 seq_end,
1214 )?;
1215 self.prime_chunk_epilogue(e, x, t, cache)
1216 }
1217
1218 #[allow(clippy::too_many_arguments)]
1234 fn prime_layers(
1235 &self,
1236 e: &Engine,
1237 x_in: CudaSlice<f32>,
1238 lo: usize,
1239 hi: usize,
1240 pos_d: &CudaSlice<i32>,
1241 t: usize,
1242 base: usize,
1243 cache: &mut Cache,
1244 seq_end: usize,
1245 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1246 let cfg = &self.cfg;
1247 let n_embd = cfg.n_embd as usize;
1248 let eps = cfg.rms_eps;
1249 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
1253 let n_ff_max = self
1259 .layers
1260 .iter()
1261 .map(|l| match &l.ffn {
1262 crate::hybrid::Ffn::Dense { ffn_gate, .. } => ffn_gate.out_features(),
1263 _ => n_embd,
1264 })
1265 .max()
1266 .unwrap_or(n_embd)
1267 .max(n_embd);
1268 let use_slabs = std::env::var("MEMRA_PRIME_SLABS").as_deref() != Ok("0");
1269 let slab = if use_slabs {
1270 Some(self.prime_slabs_get(e, t, n_embd, n_ff_max)?)
1271 } else {
1272 None
1273 };
1274 let mut slab_guard = slab.as_ref().map(|sl| sl.lock().unwrap());
1275 let mut x_own; type SlabRefs<'a> = (
1277 &'a mut CudaSlice<f32>,
1278 &'a mut CudaSlice<f32>,
1279 &'a mut CudaSlice<f32>,
1280 &'a mut CudaSlice<f32>,
1281 &'a mut CudaSlice<u8>,
1282 &'a mut CudaSlice<u8>,
1283 &'a mut CudaSlice<f32>,
1284 &'a mut CudaSlice<f32>,
1285 &'a mut CudaSlice<f32>,
1286 );
1287 let (mut x_cur, mut x_nxt, sl): (
1288 &mut CudaSlice<f32>,
1289 &mut CudaSlice<f32>,
1290 Option<SlabRefs>,
1291 );
1292 let mut seg: Option<(
1293 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1294 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1295 &mut CudaSlice<f32>,
1296 &mut usize,
1297 )> = None;
1298 let mut x_own2;
1299 match slab_guard.as_mut() {
1300 Some(g) => {
1301 let slabs = &mut **g;
1302 e.copy_into(&mut slabs.xa, 0, &x_in, t * n_embd)?;
1303 let PrimeSlabs {
1304 xa,
1305 xb,
1306 h,
1307 x1,
1308 z,
1309 act,
1310 h16,
1311 z16,
1312 gate,
1313 up,
1314 ffn_out,
1315 seg_glue,
1316 mixed,
1317 seg_mid,
1318 seg_t,
1319 ..
1320 } = slabs;
1321 x_cur = xa;
1322 x_nxt = xb;
1323 seg = Some((seg_glue, seg_mid, mixed, seg_t));
1324 sl = Some((h, x1, z, act, h16, z16, gate, up, ffn_out));
1325 }
1326 None => {
1327 x_own = x_in;
1328 x_own2 = e.uninit(t * n_embd)?;
1329 x_cur = &mut x_own;
1330 x_nxt = &mut x_own2;
1331 sl = None;
1332 }
1333 }
1334 let mut alloc_h;
1335 let mut alloc_x1;
1336 let mut alloc_z;
1337 let mut alloc_act;
1338 let mut alloc_h16;
1339 let mut alloc_z16;
1340 let mut alloc_gate;
1341 let mut alloc_up;
1342 let mut alloc_fo;
1343 let (h, x1, z, act): (
1344 &mut CudaSlice<f32>,
1345 &mut CudaSlice<f32>,
1346 &mut CudaSlice<f32>,
1347 &mut CudaSlice<f32>,
1348 );
1349 let (h16, z16): (&mut CudaSlice<u8>, &mut CudaSlice<u8>);
1350 let (sl_gate, sl_up, sl_fo): (
1351 &mut CudaSlice<f32>,
1352 &mut CudaSlice<f32>,
1353 &mut CudaSlice<f32>,
1354 );
1355 match sl {
1356 Some((a, b, c, d, e16, f16b, g, u, fo)) => {
1357 h = a;
1358 x1 = b;
1359 z = c;
1360 act = d;
1361 h16 = e16;
1362 z16 = f16b;
1363 sl_gate = g;
1364 sl_up = u;
1365 sl_fo = fo;
1366 }
1367 None => {
1368 alloc_h = e.uninit(t * n_embd)?;
1369 alloc_x1 = e.uninit(t * n_embd)?;
1370 alloc_z = e.uninit(t * n_embd)?;
1371 alloc_act = e.uninit(t * n_ff_max)?;
1372 alloc_h16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1373 alloc_z16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1374 alloc_gate = e.uninit(t * n_ff_max)?;
1375 alloc_up = e.uninit(t * n_ff_max)?;
1376 alloc_fo = e.uninit(t * n_embd)?;
1377 h = &mut alloc_h;
1378 x1 = &mut alloc_x1;
1379 z = &mut alloc_z;
1380 act = &mut alloc_act;
1381 h16 = &mut alloc_h16;
1382 z16 = &mut alloc_z16;
1383 sl_gate = &mut alloc_gate;
1384 sl_up = &mut alloc_up;
1385 sl_fo = &mut alloc_fo;
1386 }
1387 }
1388 let n_layers = self.layers.len();
1393 let use_seg = f16fuse
1403 && seg.is_some()
1404 && self.cfg.step35.is_none()
1405 && lo == 0
1406 && hi == n_layers
1407 && std::env::var("MEMRA_PRIME_SEG").as_deref() == Ok("1");
1408 if let Some((sg, sm, _, st)) = seg.as_mut() {
1409 if **st != t {
1410 sg.clear();
1411 sg.extend((0..n_layers).map(|_| None));
1412 sm.clear();
1413 sm.extend((0..n_layers).map(|_| None));
1414 **st = t;
1415 }
1416 }
1417 {
1418 let layer_lo = &self.layers[lo];
1419 if f16fuse {
1420 e.rms_norm_f16out(
1421 x_cur,
1422 layer_lo.attn_norm.float_data(),
1423 h,
1424 h16,
1425 n_embd,
1426 t,
1427 eps,
1428 )?;
1429 } else {
1430 e.rms_norm(x_cur, layer_lo.attn_norm.float_data(), h, n_embd, t, eps)?;
1431 }
1432 }
1433 for il in lo..hi {
1434 let layer = &self.layers[il];
1435 let hx16 = if f16fuse { Some(&*h16) } else { None };
1436 if use_seg {
1437 let (pre, pre16, w_out) = match &layer.mixer {
1440 Mixer::Full(fa) => {
1441 let g3 = match hx16 {
1442 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
1443 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
1444 };
1445 let (pre, pre16) =
1446 self.full_attn_prime_core_inner(e, fa, g3, &pos_d, t, cache, il)?;
1447 (pre, pre16, &fa.wo)
1448 }
1449 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1450 Mixer::Linear(la) => {
1451 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
1452 let g4 = match hx16 {
1453 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
1454 None => e.matmul_group(&ws, h, t)?,
1455 };
1456 let (pre, pre16) =
1457 self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, None)?;
1458 (pre, pre16, &la.ssm_out)
1459 }
1460 };
1461 {
1462 let (_, sm, mslab, _) = seg.as_mut().unwrap();
1463 let pre_n = pre.len() / t;
1464 let xh_pre = match pre16 {
1465 Some(x) => x,
1466 None => e.f16_act(&pre, t * pre_n, pre_n)?,
1467 };
1468 if !e.try_f16_gemm_pre_into(w_out, &xh_pre, t, mslab)? {
1469 let y = e.matmul(w_out, &pre, t)?;
1470 e.copy_into(mslab, 0, &y, t * n_embd)?;
1471 }
1472 if sm[il].is_none() {
1473 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
1474 let w_post = layer.post_attn_norm.float_data();
1475 e.stream().synchronize()?;
1476 e.stream()
1477 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
1478 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
1479 e.add(x_cur, mslab, x1, t * n_embd)?;
1480 e.rms_norm_f16out(x1, w_post, z, z16, n_embd, t, eps)?;
1481 Ok(())
1482 })();
1483 let g = e.stream().end_capture(
1484 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH);
1485 r?;
1486 sm[il] = Some(g?.ok_or("S-mid capture produced no graph")?);
1487 }
1488 sm[il].as_ref().unwrap().launch()?;
1489 }
1490 } else {
1491 let mixed = match &layer.mixer {
1492 Mixer::Full(fa) => {
1493 self.full_attn_prime(e, fa, h, hx16, &pos_d, t, cache, il, seq_end)?
1494 }
1495 Mixer::Linear(la) => self.linear_attn_prime(e, la, h, hx16, t, cache, il)?,
1496 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1497 };
1498 if f16fuse {
1499 e.add_rms_norm_f16out(
1502 x_cur,
1503 &mixed,
1504 layer.post_attn_norm.float_data(),
1505 x1,
1506 z,
1507 z16,
1508 n_embd,
1509 t,
1510 eps,
1511 )?;
1512 } else {
1513 e.add(x_cur, &mixed, x1, t * n_embd)?;
1514 e.rms_norm(x1, layer.post_attn_norm.float_data(), z, n_embd, t, eps)?;
1515 }
1516 }
1517 let zx16 = if f16fuse { Some(&*z16) } else { None };
1518 match &layer.ffn {
1519 crate::hybrid::Ffn::Dense {
1520 ffn_gate,
1521 ffn_up,
1522 ffn_down,
1523 } => {
1524 let n_ff = ffn_gate.out_features();
1525 let mut into_ok = false;
1528 if let Some(xh) = zx16 {
1529 into_ok = e.try_f16_gemm_pre_into(ffn_gate, xh, t, sl_gate)?
1530 && e.try_f16_gemm_pre_into(ffn_up, xh, t, sl_up)?;
1531 }
1532 if !into_ok {
1533 let mut g2 = match zx16 {
1534 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], z, xh, t)?,
1535 None => e.matmul_group(&[ffn_gate, ffn_up], z, t)?,
1536 };
1537 let up_y = g2.pop().unwrap();
1538 let gate_y = g2.pop().unwrap();
1539 e.copy_into(sl_gate, 0, &gate_y, t * n_ff)?;
1540 e.copy_into(sl_up, 0, &up_y, t * n_ff)?;
1541 }
1542 let d_lim = self.cfg.clamp_shexp_at(il as u32);
1547 let act16 = if Self::f16out_on(e, t) && self.cfg.m3.is_none() && d_lim.is_none()
1548 {
1549 let mut a16 = e.alloc_u8_uninit(t * n_ff * 2)?;
1550 e.silu_mul_f16out(sl_gate, sl_up, act, &mut a16, t * n_ff)?;
1551 Some(a16)
1552 } else {
1553 Self::ffn_act_lim(
1554 e,
1555 &self.cfg,
1556 sl_gate,
1557 sl_up,
1558 1.0,
1559 1.0,
1560 d_lim,
1561 act,
1562 t * n_ff,
1563 )?;
1564 None
1565 };
1566 let xh_act = match act16 {
1568 Some(x) => x,
1569 None => e.f16_act(act, t * n_ff, n_ff)?,
1570 };
1571 if !e.try_f16_gemm_pre_into(ffn_down, &xh_act, t, sl_fo)? {
1572 let y = e.matmul(ffn_down, &*act, t)?;
1573 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
1574 }
1575 }
1576 crate::hybrid::Ffn::Moe(m) => {
1577 let y = self.moe_ffn_il_prefill(e, m, z, t, il as u16)?;
1578 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
1579 }
1580 }
1581 if use_seg && il + 1 < hi {
1582 let w_next = self.layers[il + 1].attn_norm.float_data();
1584 let (sg, _, _, _) = seg.as_mut().unwrap();
1585 if sg[il].is_none() {
1586 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
1587 e.stream().synchronize()?;
1588 e.stream()
1589 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
1590 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
1591 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
1592 e.rms_norm_f16out(x_nxt, w_next, h, h16, n_embd, t, eps)?;
1593 Ok(())
1594 })();
1595 let g = e.stream().end_capture(
1596 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
1597 );
1598 r?;
1599 sg[il] = Some(g?.ok_or("S-glue capture produced no graph")?);
1600 }
1601 sg[il].as_ref().unwrap().launch()?;
1602 } else {
1603 if il + 1 < hi {
1604 let w_next = self.layers[il + 1].attn_norm.float_data();
1605 if f16fuse {
1606 e.add_rms_norm_f16out(x1, sl_fo, w_next, x_nxt, h, h16, n_embd, t, eps)?;
1607 } else {
1608 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
1609 e.rms_norm(x_nxt, w_next, h, n_embd, t, eps)?;
1610 }
1611 } else {
1612 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
1613 }
1614 }
1615 if let Some(path) = Self::prime_trace_path() {
1621 let row = (base + t - 1) as usize;
1622 let host = e.dtoh(x_nxt)?;
1623 let last = &host[(t - 1) * n_embd..t * n_embd];
1624 use std::io::Write as _;
1625 let mut f = std::fs::OpenOptions::new()
1626 .create(true)
1627 .append(true)
1628 .open(path)?;
1629 let mut h64: u64 = 0xcbf29ce484222325;
1630 for v in last {
1631 h64 ^= v.to_bits() as u64;
1632 h64 = h64.wrapping_mul(0x100000001b3);
1633 }
1634 writeln!(
1635 f,
1636 "{{\"pos\":{row},\"layer\":{il},\"t\":{t},\"base\":{base},\
1637 \"hash\":\"{h64:016x}\",\"v0\":{:.9e},\"v1\":{:.9e},\"v2\":{:.9e}}}",
1638 last[0], last[1], last[2]
1639 )?;
1640 }
1641 std::mem::swap(&mut x_cur, &mut x_nxt);
1642 }
1643 let mut x = e.uninit(t * n_embd)?;
1645 e.copy_into(&mut x, 0, x_cur, t * n_embd)?;
1646 drop(slab_guard);
1647 Ok(x)
1648 }
1649
1650 fn prime_chunk_epilogue(
1655 &self,
1656 e: &Engine,
1657 x: CudaSlice<f32>,
1658 t: usize,
1659 cache: &mut Cache,
1660 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1661 let n_embd = self.cfg.n_embd as usize;
1662 let eps = self.cfg.rms_eps;
1663 let mut h_seed = e.uninit(n_embd)?;
1667 if !crate::spec::spec_hpost() {
1668 e.copy_view_into(
1669 &mut h_seed,
1670 0,
1671 &x.slice((t - 1) * n_embd..t * n_embd),
1672 n_embd,
1673 )?;
1674 }
1675 let mut hn = e.uninit(t * n_embd)?;
1677 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1678 if crate::spec::spec_hpost() {
1679 e.copy_view_into(
1680 &mut h_seed,
1681 0,
1682 &hn.slice((t - 1) * n_embd..t * n_embd),
1683 n_embd,
1684 )?;
1685 }
1686 let last = e.view(&hn, t * n_embd);
1687 let last_row = last.slice((t - 1) * n_embd..t * n_embd);
1688 let mut hlast = e.uninit(n_embd)?;
1689 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
1690 let logits = e.matmul(&self.output, &hlast, 1)?;
1691 cache.pos += t;
1692 Ok((
1695 e.dtoh(&logits)?,
1696 h_seed,
1697 if crate::spec::spec_hpost() { hn } else { x },
1698 ))
1699 }
1700
1701 fn prime_chunk_ppn(
1725 &self,
1726 e: &Engine,
1727 tokens: &[u32],
1728 cache: &mut Cache,
1729 seq_end: usize,
1730 fence: &[usize],
1731 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1732 let rt = crate::pp::PpNRt::get(e)?;
1733 let n_st = fence.len() - 1;
1734 assert_eq!(
1735 rt.n_stages(),
1736 n_st,
1737 "PpNRt stage count {} != fence stages {n_st}",
1738 rt.n_stages()
1739 );
1740 let n_embd = self.cfg.n_embd as usize;
1741 let t = tokens.len();
1742 let base = cache.pos;
1743 debug_assert!(
1744 seq_end >= base + t,
1745 "prime_chunk_ppn: seq_end must cover this chunk"
1746 );
1747 let payload = t * n_embd;
1748 let caller_stream = e.stream();
1752 rt.fence_stages_behind(&caller_stream)?;
1753
1754 if n_st == 2 {
1755 let slot =
1756 self.prime_pp2_stage0_enqueue(e, rt, tokens, cache, seq_end, fence, base, false)?;
1757 let x =
1758 self.prime_pp2_stage1_enqueue(e, rt, slot, t, cache, seq_end, fence, base, false)?;
1759 let out = {
1760 rt.bind_stage(1)?;
1761 let _st1 = rt.enter(1);
1762 let e1 = rt.engine(1, e);
1763 self.prime_chunk_epilogue(e1, x, t, cache)?
1764 };
1765 rt.publish_to(1, &caller_stream)?;
1766 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1767 return Ok(out);
1768 }
1769
1770 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1771
1772 let mut slot = {
1774 let _st0 = rt.enter(0);
1775 let e0 = rt.engine(0, e);
1776 let pos_d = e0.htod_i32(&pos)?;
1777 let x = self.embed(e0, tokens)?;
1778 let x =
1779 self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
1780 rt.tx(0, &x, payload)?
1781 };
1783
1784 for s in 1..n_st - 1 {
1786 let _st = rt.enter(s);
1787 let es = rt.engine(s, e);
1788 let pos_d = es.htod_i32(&pos)?;
1789 let x = rt.rx(s - 1, slot, payload)?;
1790 let x = self.prime_layers(
1791 es,
1792 x,
1793 fence[s],
1794 fence[s + 1],
1795 &pos_d,
1796 t,
1797 base,
1798 cache,
1799 seq_end,
1800 )?;
1801 slot = rt.tx(s, &x, payload)?;
1802 }
1803
1804 let _stl = rt.enter(n_st - 1);
1806 let el = rt.engine(n_st - 1, e);
1807 let pos_d = el.htod_i32(&pos)?;
1808 let x = rt.rx(n_st - 2, slot, payload)?;
1809 let x = self.prime_layers(
1810 el,
1811 x,
1812 fence[n_st - 1],
1813 fence[n_st],
1814 &pos_d,
1815 t,
1816 base,
1817 cache,
1818 seq_end,
1819 )?;
1820 let out = self.prime_chunk_epilogue(el, x, t, cache)?;
1821 rt.publish_to(n_st - 1, &caller_stream)?;
1827 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1828 Ok(out)
1829 }
1830
1831 fn prime_pp2_stage0_enqueue(
1832 &self,
1833 e: &Engine,
1834 rt: &crate::pp::PpNRt,
1835 tokens: &[u32],
1836 cache: &mut Cache,
1837 seq_end: usize,
1838 fence: &[usize],
1839 base: usize,
1840 pipelined: bool,
1841 ) -> Result<usize, Box<dyn std::error::Error>> {
1842 let t = tokens.len();
1843 let n_embd = self.cfg.n_embd as usize;
1844 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1845 rt.bind_stage(0)?;
1846 let _st0 = rt.enter(0);
1847 let e0 = rt.engine(0, e);
1848 let pos_d = e0.htod_i32(&pos)?;
1849 let x = self.embed(e0, tokens)?;
1850 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
1851 let x = self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
1852 if pipelined {
1853 rt.tx_pipelined(0, &x, t * n_embd)
1854 } else {
1855 rt.tx(0, &x, t * n_embd)
1856 }
1857 }
1858
1859 fn prime_pp2_stage1_enqueue(
1860 &self,
1861 e: &Engine,
1862 rt: &crate::pp::PpNRt,
1863 slot: usize,
1864 t: usize,
1865 cache: &mut Cache,
1866 seq_end: usize,
1867 fence: &[usize],
1868 base: usize,
1869 pipelined: bool,
1870 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1871 let n_embd = self.cfg.n_embd as usize;
1872 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1873 rt.bind_stage(1)?;
1874 let _st1 = rt.enter(1);
1875 let e1 = rt.engine(1, e);
1876 let pos_d = e1.htod_i32(&pos)?;
1877 let x = rt.rx(0, slot, t * n_embd)?;
1878 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
1879 self.prime_layers(e1, x, fence[1], fence[2], &pos_d, t, base, cache, seq_end)
1880 }
1881
1882 pub fn prime_chunk_captured(
1898 &self,
1899 e: &Engine,
1900 x_in: &CudaSlice<f32>,
1901 pos_d: &CudaSlice<i32>,
1902 t: usize,
1903 cache: &mut Cache,
1904 len_d: &CudaSlice<i32>,
1905 logits_out: &mut CudaSlice<f32>,
1906 h_seed_out: &mut CudaSlice<f32>,
1907 ) -> Result<(), Box<dyn std::error::Error>> {
1908 let cfg = &self.cfg;
1909 let n_embd = cfg.n_embd as usize;
1910 let eps = cfg.rms_eps;
1911 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
1912 let mut x = e.uninit(t * n_embd)?;
1913 e.copy_into(&mut x, 0, x_in, t * n_embd)?;
1914 for (il, layer) in self.layers.iter().enumerate() {
1915 let mut h = e.uninit(t * n_embd)?;
1916 let mut hx16: Option<CudaSlice<u8>> = None;
1917 if f16fuse {
1918 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1919 e.rms_norm_f16out(
1920 &x,
1921 layer.attn_norm.float_data(),
1922 &mut h,
1923 &mut b16,
1924 n_embd,
1925 t,
1926 eps,
1927 )?;
1928 hx16 = Some(b16);
1929 } else {
1930 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1931 }
1932 let mixed = match &layer.mixer {
1933 Mixer::Full(fa) => {
1937 self.full_attn_prime(e, fa, &h, hx16.as_ref(), pos_d, t, cache, il, t)?
1938 }
1939 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1940 Mixer::Linear(la) => {
1941 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
1942 let g4 = match hx16.as_ref() {
1943 Some(xh) => e.matmul_group_xh(&ws, &h, xh, t)?,
1944 None => e.matmul_group(&ws, &h, t)?,
1945 };
1946 self.linear_attn_prime_core_pad(e, la, g4, t, cache, il, Some(len_d))?
1947 }
1948 };
1949 let mut x1 = e.uninit(t * n_embd)?;
1950 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1951 let mut z = e.uninit(t * n_embd)?;
1952 let mut zx16: Option<CudaSlice<u8>> = None;
1953 if f16fuse {
1954 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1955 e.rms_norm_f16out(
1956 &x1,
1957 layer.post_attn_norm.float_data(),
1958 &mut z,
1959 &mut b16,
1960 n_embd,
1961 t,
1962 eps,
1963 )?;
1964 zx16 = Some(b16);
1965 } else {
1966 e.rms_norm(
1967 &x1,
1968 layer.post_attn_norm.float_data(),
1969 &mut z,
1970 n_embd,
1971 t,
1972 eps,
1973 )?;
1974 }
1975 let ffn_out = match &layer.ffn {
1976 crate::hybrid::Ffn::Dense {
1977 ffn_gate,
1978 ffn_up,
1979 ffn_down,
1980 } => {
1981 let n_ff = ffn_gate.out_features();
1982 let mut g2 = match &zx16 {
1983 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], &z, xh, t)?,
1984 None => e.matmul_group(&[ffn_gate, ffn_up], &z, t)?,
1985 };
1986 let up = g2.pop().unwrap();
1987 let gate = g2.pop().unwrap();
1988 let mut act = e.uninit(t * n_ff)?;
1989 Self::ffn_act_lim(
1991 e,
1992 &self.cfg,
1993 &gate,
1994 &up,
1995 1.0,
1996 1.0,
1997 self.cfg.clamp_shexp_at(il as u32),
1998 &mut act,
1999 t * n_ff,
2000 )?;
2001 e.matmul(ffn_down, &act, t)?
2002 }
2003 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
2004 };
2005 let mut x2 = e.uninit(t * n_embd)?;
2006 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
2007 x = x2;
2008 }
2009 if !crate::spec::spec_hpost() {
2011 e.row_gather_dev(&x, h_seed_out, len_d, n_embd)?;
2012 }
2013 let mut hn = e.uninit(t * n_embd)?;
2014 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2015 if crate::spec::spec_hpost() {
2016 e.row_gather_dev(&hn, h_seed_out, len_d, n_embd)?;
2017 }
2018 let mut hlast = e.uninit(n_embd)?;
2019 e.row_gather_dev(&hn, &mut hlast, len_d, n_embd)?;
2020 let logits = e.matmul(&self.output, &hlast, 1)?;
2021 let nv = logits.len();
2022 e.copy_into(logits_out, 0, &logits, nv)?;
2023 Ok(())
2024 }
2025
2026 fn step35_prime_batch_on() -> bool {
2027 std::env::var("MEMRA_STEP35_PRIME_BATCH").as_deref() != Ok("0")
2028 }
2029
2030 #[allow(clippy::too_many_arguments)]
2033 fn step35_prime_batch_layers(
2034 &self,
2035 e: &Engine,
2036 mut x: CudaSlice<f32>,
2037 lo: usize,
2038 hi: usize,
2039 ts: &[usize],
2040 offs: &[usize],
2041 pos_ds: &[CudaSlice<i32>],
2042 caches: &mut [&mut Cache],
2043 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2044 let cfg = &self.cfg;
2045 let n_embd = cfg.n_embd as usize;
2046 let eps = cfg.rms_eps;
2047 let b = ts.len();
2048 let total: usize = ts.iter().sum();
2049 let f16fuse = crate::f16_ffi::pp_f16_enabled() && total >= 16;
2050
2051 let split = |e: &Engine,
2052 y: &CudaSlice<f32>,
2053 dim: usize|
2054 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2055 let mut out = Vec::with_capacity(b);
2056 for s in 0..b {
2057 let mut ys = e.uninit(ts[s] * dim)?;
2058 e.copy_view_into(
2059 &mut ys,
2060 0,
2061 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2062 ts[s] * dim,
2063 )?;
2064 out.push(ys);
2065 }
2066 Ok(out)
2067 };
2068
2069 for il in lo..hi {
2070 let layer = &self.layers[il];
2071 let Mixer::Full(fa) = &layer.mixer else {
2072 return Err(format!("step35 layer {il} is not full-attn — corrupt config").into());
2073 };
2074
2075 let mut h = e.uninit(total * n_embd)?;
2076 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2077 if f16fuse {
2078 e.rms_norm_f16out(
2079 &x,
2080 layer.attn_norm.float_data(),
2081 &mut h,
2082 &mut hx16,
2083 n_embd,
2084 total,
2085 eps,
2086 )?;
2087 } else {
2088 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, total, eps)?;
2089 }
2090
2091 let gate_w = fa
2095 .attn_gate
2096 .as_ref()
2097 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
2098 let mut g4 = if f16fuse {
2099 e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, &hx16, total)?
2100 } else {
2101 e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, total)?
2102 };
2103 let gate = g4.pop().unwrap();
2104 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2105 (0..b).map(|_| Vec::with_capacity(3)).collect();
2106 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g4) {
2107 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2108 parts[s].push(ys);
2109 }
2110 }
2111 let gates = split(e, &gate, gate_w.out_features())?;
2112 let geometry = self.step35_geom(il);
2113 let hd = geometry.head_dim_k as usize;
2114 let nh = geometry.n_head as usize;
2115 let mut ag_cat = e.uninit(total * nh * hd)?;
2116 for (s, (g3s, gate)) in parts.into_iter().zip(gates).enumerate() {
2117 let ag = self.step35_attn_pre_wo(
2118 e,
2119 fa,
2120 g3s,
2121 None,
2122 Some(&gate),
2123 &pos_ds[s],
2124 ts[s],
2125 Some(&mut *caches[s]),
2126 il,
2127 ts[s],
2128 )?;
2129 e.copy_into(&mut ag_cat, offs[s] * nh * hd, &ag, ts[s] * nh * hd)?;
2130 }
2131 let mixed = e.matmul(&fa.wo, &ag_cat, total)?;
2132
2133 let mut x1 = e.uninit(total * n_embd)?;
2134 let mut z = e.uninit(total * n_embd)?;
2135 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2136 if f16fuse {
2137 e.add_rms_norm_f16out(
2138 &x,
2139 &mixed,
2140 layer.post_attn_norm.float_data(),
2141 &mut x1,
2142 &mut z,
2143 &mut zx16,
2144 n_embd,
2145 total,
2146 eps,
2147 )?;
2148 } else {
2149 e.add(&x, &mixed, &mut x1, total * n_embd)?;
2150 e.rms_norm(
2151 &x1,
2152 layer.post_attn_norm.float_data(),
2153 &mut z,
2154 n_embd,
2155 total,
2156 eps,
2157 )?;
2158 }
2159
2160 let ffn_out = match &layer.ffn {
2161 crate::hybrid::Ffn::Dense {
2162 ffn_gate,
2163 ffn_up,
2164 ffn_down,
2165 } => {
2166 let n_ff = ffn_gate.out_features();
2167 let mut g2 = if f16fuse {
2168 e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?
2169 } else {
2170 e.matmul_group(&[ffn_gate, ffn_up], &z, total)?
2171 };
2172 let up = g2.pop().unwrap();
2173 let gate = g2.pop().unwrap();
2174 let mut act = e.uninit(total * n_ff)?;
2175 let d_lim = cfg.clamp_shexp_at(il as u32);
2176 if Self::f16out_on(e, total) && cfg.m3.is_none() && d_lim.is_none() {
2177 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2178 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2179 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2180 Some(y) => y,
2181 None => e.matmul(ffn_down, &act, total)?,
2182 }
2183 } else {
2184 Self::ffn_act_lim(
2185 e,
2186 cfg,
2187 &gate,
2188 &up,
2189 1.0,
2190 1.0,
2191 d_lim,
2192 &mut act,
2193 total * n_ff,
2194 )?;
2195 e.matmul(ffn_down, &act, total)?
2196 }
2197 }
2198 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il(e, m, &z, total, il as u16)?,
2199 };
2200 let mut x2 = e.uninit(total * n_embd)?;
2201 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2202 x = x2;
2203 }
2204 Ok(x)
2205 }
2206
2207 fn step35_prime_batch_epilogue(
2208 &self,
2209 e: &Engine,
2210 x: CudaSlice<f32>,
2211 ts: &[usize],
2212 offs: &[usize],
2213 caches: &mut [&mut Cache],
2214 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2215 let n_embd = self.cfg.n_embd as usize;
2216 let total: usize = ts.iter().sum();
2217 let mut hn = e.uninit(total * n_embd)?;
2218 e.rms_norm(
2219 &x,
2220 self.output_norm.float_data(),
2221 &mut hn,
2222 n_embd,
2223 total,
2224 self.cfg.rms_eps,
2225 )?;
2226
2227 let hidden_src = if crate::spec::spec_hpost() { &hn } else { &x };
2228 let mut out = Vec::with_capacity(ts.len());
2229 for s in 0..ts.len() {
2230 let mut hidden = e.uninit(ts[s] * n_embd)?;
2231 e.copy_view_into(
2232 &mut hidden,
2233 0,
2234 &hidden_src.slice(offs[s] * n_embd..(offs[s] + ts[s]) * n_embd),
2235 ts[s] * n_embd,
2236 )?;
2237 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2238 let mut h_seed = e.uninit(n_embd)?;
2239 e.copy_view_into(
2240 &mut h_seed,
2241 0,
2242 &hidden_src.slice(last0..last0 + n_embd),
2243 n_embd,
2244 )?;
2245 let mut hlast = e.uninit(n_embd)?;
2247 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2248 let logits = e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?;
2249 caches[s].pos += ts[s];
2250 out.push((logits, h_seed, hidden));
2251 }
2252 Ok(out)
2253 }
2254
2255 fn step35_prime_cache_batch(
2256 &self,
2257 e: &Engine,
2258 prompts: &[&[u32]],
2259 caches: &mut [&mut Cache],
2260 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2261 if crate::pp::pp_host_bounce_active()
2262 && (!crate::pp::prime_pp_on() || crate::pp::pp_cuts(self.layers.len()).is_none())
2263 {
2264 return Err(
2265 "step35_prime_cache_batch: MEMRA_PP_HOST_BOUNCE=1 requires a valid prime \
2266 stage split; refusing an unsplit remote-weight walk"
2267 .into(),
2268 );
2269 }
2270 if !Self::step35_prime_batch_on() {
2271 return Err("step35 batched prime is disabled (MEMRA_STEP35_PRIME_BATCH=0)".into());
2272 }
2273 if caches.iter().any(|c| c.pos != 0) {
2274 return Err(
2275 "step35 batched prime currently supports complete fresh prompts only; \
2276 continuation/tick chunks require per-request queued_after"
2277 .into(),
2278 );
2279 }
2280
2281 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2282 for &t in &ts {
2283 assert!(
2284 t >= PRIME_MIN_T,
2285 "step35 batched prime needs T >= {PRIME_MIN_T}"
2286 );
2287 }
2288 for (s, c) in caches.iter().enumerate() {
2289 assert!(
2290 ts[s] <= c.max_ctx,
2291 "step35 batched prime exceeds cache max_ctx"
2292 );
2293 }
2294 let offs: Vec<usize> = ts
2295 .iter()
2296 .scan(0usize, |a, &t| {
2297 let o = *a;
2298 *a += t;
2299 Some(o)
2300 })
2301 .collect();
2302 let total: usize = ts.iter().sum();
2303 let payload = total * self.cfg.n_embd as usize;
2304 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2305 let positions: Vec<Vec<i32>> = ts.iter().map(|&t| (0..t as i32).collect()).collect();
2306 let upload_positions =
2307 |e: &Engine| -> Result<Vec<CudaSlice<i32>>, Box<dyn std::error::Error>> {
2308 positions
2309 .iter()
2310 .map(|p| e.htod_i32(p))
2311 .collect::<Result<_, _>>()
2312 };
2313
2314 static ONCE: std::sync::Once = std::sync::Once::new();
2315 ONCE.call_once(|| {
2316 eprintln!(
2317 "[step35-prime-batch] first concat prime: B={} tokens={total}",
2318 prompts.len()
2319 );
2320 });
2321
2322 let out = if !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
2323 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
2324 let rt = crate::pp::PpNRt::get(e)?;
2325 let n_st = fence.len() - 1;
2326 assert_eq!(
2327 rt.n_stages(),
2328 n_st,
2329 "step35 prime batch stage count mismatch"
2330 );
2331 let caller_stream = e.stream();
2332 rt.fence_stages_behind(&caller_stream)?;
2333
2334 let mut slot = {
2335 let _st0 = rt.enter(0);
2336 let e0 = rt.engine(0, e);
2337 let pos_ds = upload_positions(e0)?;
2338 let x = self.embed(e0, &cat_tokens)?;
2339 let x = self.step35_prime_batch_layers(
2340 e0, x, fence[0], fence[1], &ts, &offs, &pos_ds, caches,
2341 )?;
2342 rt.tx(0, &x, payload)?
2343 };
2344 for s in 1..n_st - 1 {
2345 let _st = rt.enter(s);
2346 let es = rt.engine(s, e);
2347 let pos_ds = upload_positions(es)?;
2348 let x = rt.rx(s - 1, slot, payload)?;
2349 let x = self.step35_prime_batch_layers(
2350 es,
2351 x,
2352 fence[s],
2353 fence[s + 1],
2354 &ts,
2355 &offs,
2356 &pos_ds,
2357 caches,
2358 )?;
2359 slot = rt.tx(s, &x, payload)?;
2360 }
2361
2362 let _stl = rt.enter(n_st - 1);
2363 let el = rt.engine(n_st - 1, e);
2364 let pos_ds = upload_positions(el)?;
2365 let x = rt.rx(n_st - 2, slot, payload)?;
2366 let x = self.step35_prime_batch_layers(
2367 el,
2368 x,
2369 fence[n_st - 1],
2370 fence[n_st],
2371 &ts,
2372 &offs,
2373 &pos_ds,
2374 caches,
2375 )?;
2376 let out = self.step35_prime_batch_epilogue(el, x, &ts, &offs, caches)?;
2377 rt.publish_to(n_st - 1, &caller_stream)?;
2378 crate::pp::STEP35_PRIME_BATCH_SPLITS
2379 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2380 out
2381 } else {
2382 let pos_ds = upload_positions(e)?;
2383 let x = self.embed(e, &cat_tokens)?;
2384 let x = self.step35_prime_batch_layers(
2385 e,
2386 x,
2387 0,
2388 self.layers.len(),
2389 &ts,
2390 &offs,
2391 &pos_ds,
2392 caches,
2393 )?;
2394 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2395 }
2396 } else {
2397 let pos_ds = upload_positions(e)?;
2398 let x = self.embed(e, &cat_tokens)?;
2399 let x = self.step35_prime_batch_layers(
2400 e,
2401 x,
2402 0,
2403 self.layers.len(),
2404 &ts,
2405 &offs,
2406 &pos_ds,
2407 caches,
2408 )?;
2409 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2410 };
2411 crate::pp::STEP35_PRIME_BATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2412 Ok(out)
2413 }
2414
2415 pub fn prime_cache_batch(
2432 &self,
2433 e: &Engine,
2434 prompts: &[&[u32]],
2435 caches: &mut [&mut Cache],
2436 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2437 let cfg = &self.cfg;
2438 let n_embd = cfg.n_embd as usize;
2439 let eps = cfg.rms_eps;
2440 let b = prompts.len();
2441 assert!(b >= 1 && b == caches.len());
2442 let pos0s: Vec<usize> = caches.iter().map(|c| c.pos).collect();
2443 let carried = pos0s.iter().any(|&p| p > 0);
2444 if cfg.gemma4.is_some() {
2450 return Err(
2451 "prime_cache_batch: gemma4 has no batched prime core (per-layer \
2452 swa/global geometry, softcapped head) — use gemma4_prime per sequence"
2453 .into(),
2454 );
2455 }
2456 if cfg.step35.is_some() {
2459 return self.step35_prime_cache_batch(e, prompts, caches);
2460 }
2461 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2462 for &t in &ts {
2463 assert!(
2464 t >= PRIME_MIN_T,
2465 "prime_cache_batch needs T >= {PRIME_MIN_T}"
2466 );
2467 }
2468 for (s, c) in caches.iter().enumerate() {
2469 assert!(
2470 c.pos + ts[s] <= c.max_ctx,
2471 "prime_cache_batch: prompt exceeds cache max_ctx"
2472 );
2473 }
2474 let total: usize = ts.iter().sum();
2475 let offs: Vec<usize> = ts
2476 .iter()
2477 .scan(0usize, |a, &t| {
2478 let o = *a;
2479 *a += t;
2480 Some(o)
2481 })
2482 .collect();
2483 let pos_ds: Vec<CudaSlice<i32>> = ts
2485 .iter()
2486 .zip(&pos0s)
2487 .map(|(&t, &p0)| e.htod_i32(&(p0 as i32..(p0 + t) as i32).collect::<Vec<_>>()))
2488 .collect::<Result<_, _>>()?;
2489 let split = |e: &Engine,
2491 y: &CudaSlice<f32>,
2492 dim: usize|
2493 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2494 let mut out = Vec::with_capacity(b);
2495 for s in 0..b {
2496 let mut ys = e.uninit(ts[s] * dim)?;
2497 e.copy_view_into(
2498 &mut ys,
2499 0,
2500 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2501 ts[s] * dim,
2502 )?;
2503 out.push(ys);
2504 }
2505 Ok(out)
2506 };
2507
2508 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2509 let mut x = self.embed(e, &cat_tokens)?; for (il, layer) in self.layers.iter().enumerate() {
2511 let mut h = e.uninit(total * n_embd)?;
2512 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2513 e.rms_norm_f16out(
2514 &x,
2515 layer.attn_norm.float_data(),
2516 &mut h,
2517 &mut hx16,
2518 n_embd,
2519 total,
2520 eps,
2521 )?;
2522 let mut mixed = e.uninit(total * n_embd)?;
2524 match &layer.mixer {
2525 Mixer::Full(fa) => {
2526 let g3 = e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], &h, &hx16, total)?;
2527 let geometry = self.cfg.full_attention_geometry_at(il as u32);
2533 let (n_head, n_head_kv, head_dim) = (
2534 geometry.n_head as usize,
2535 geometry.n_head_kv as usize,
2536 geometry.head_dim_k as usize,
2537 );
2538 let fa_scale = geometry.attention_scale();
2539 let use_favl = !carried
2540 && (2..=8).contains(&b)
2541 && (head_dim == 256 || head_dim == 128)
2542 && geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ
2543 && std::env::var("MEMRA_NOFA").is_err()
2544 && std::env::var("MEMRA_FA_FLOOR").is_err()
2545 && std::env::var("MEMRA_FA_PP_W2").as_deref() != Ok("1")
2546 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0")
2547 && std::env::var("MEMRA_FA_VL").as_deref() != Ok("0");
2548 if use_favl {
2549 let (qf_w, kf_w, vf_w) = (
2550 fa.wq.out_features(),
2551 fa.wk.out_features(),
2552 fa.wv.out_features(),
2553 );
2554 struct APre {
2555 q: CudaSlice<f32>,
2556 gate: Option<CudaSlice<f32>>,
2557 qn: CudaSlice<f32>,
2558 kn: CudaSlice<f32>,
2559 }
2560 let mut aps = Vec::with_capacity(b);
2561 for &t in ts.iter().take(b) {
2562 aps.push(APre {
2563 q: e.uninit(t * n_head * head_dim)?,
2564 gate: Some(e.uninit(t * n_head * head_dim)?),
2565 qn: e.uninit(t * n_head * head_dim)?,
2566 kn: e.uninit(t * n_head_kv * head_dim)?,
2567 });
2568 }
2569 let (kv_dim_k, kv_dim_v, ktb, vtb) = {
2570 let kvl = caches[0].kv[il].as_ref().unwrap();
2571 (kvl.kv_dim_k, kvl.kv_dim_v, kvl.k_tok_bytes, kvl.v_tok_bytes)
2572 };
2573 let pargs: Vec<crate::AttnPreVl> = (0..b)
2574 .map(|s| {
2575 let (o, t) = (offs[s], ts[s]);
2576 let kvl = caches[s].kv[il].as_ref().unwrap();
2577 assert!(
2578 kvl.len == 0 && kvl.len + t <= caches[s].max_ctx,
2579 "prime_cache_batch attn vl: fresh + capacity"
2580 );
2581 crate::AttnPreVl {
2582 qf: e.addr_f32v(&g3[0].slice(o * qf_w..(o + t) * qf_w)),
2583 kf: e.addr_f32v(&g3[1].slice(o * kf_w..(o + t) * kf_w)),
2584 vf: e.addr_f32v(&g3[2].slice(o * vf_w..(o + t) * vf_w)),
2585 q: e.addr_f32(&aps[s].q),
2586 gate: e.addr_f32(aps[s].gate.as_ref().unwrap()),
2587 qn: e.addr_f32(&aps[s].qn),
2588 kn: e.addr_f32(&aps[s].kn),
2589 kc: e.addr_u8(&kvl.k),
2590 vc: e.addr_u8(&kvl.v),
2591 t: t as i32,
2592 pad: 0,
2593 }
2594 })
2595 .collect();
2596 e.attn_pre_vl8(
2597 &pargs,
2598 fa.q_norm.float_data(),
2599 fa.k_norm.float_data(),
2600 head_dim,
2601 geometry.n_rot as usize,
2602 n_head,
2603 n_head_kv,
2604 self.cfg.rms_eps,
2605 geometry.rope_base,
2606 1.0,
2607 kv_dim_k,
2608 kv_dim_v,
2609 ktb,
2610 vtb,
2611 )?;
2612 for s in 0..b {
2613 let kvl = caches[s].kv[il].as_mut().unwrap();
2614 kvl.len += ts[s];
2615 let new_len = kvl.len as i32;
2616 e.set_i32_one(&mut kvl.len_d, new_len)?;
2617 }
2618 let mut attns = Vec::with_capacity(b);
2619 let mut mirrors = Vec::with_capacity(b);
2620 for &t in ts.iter().take(b) {
2621 attns.push(e.uninit(t * n_head * head_dim)?);
2622 let n = t * n_head_kv * head_dim;
2623 mirrors.push((e.alloc_u8_uninit(n * 2)?, e.alloc_u8_uninit(n * 2)?));
2624 }
2625 let fa3_on = match std::env::var("MEMRA_FA3").as_deref() {
2628 Ok("0") => false,
2629 Ok("1") => true,
2630 _ => cfg!(memra_hopper_mma),
2631 };
2632 if fa3_on {
2633 let mut q16s = Vec::with_capacity(b);
2634 let mut v16s = Vec::with_capacity(b);
2635 for s in 0..b {
2636 let t = ts[s];
2637 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
2638 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
2639 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
2640 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
2641 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
2642 e.f32_to_bf16_v(
2643 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
2644 &mut v16,
2645 t * n_head_kv * head_dim,
2646 )?;
2647 q16s.push(q16);
2648 v16s.push((k16, v16));
2649 }
2650 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
2651 let mut kp = qp;
2652 let mut vp = qp;
2653 let mut op = [core::ptr::null_mut::<f32>(); 8];
2654 let mut tsv = [0i32; 8];
2655 for s in 0..b {
2656 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
2657 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
2658 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
2659 op[s] = e.addr_f32(&attns[s]) as *mut f32;
2660 tsv[s] = ts[s] as i32;
2661 }
2662 let rc = unsafe {
2663 crate::fa3_vl_raw(
2664 qp.as_ptr(),
2665 kp.as_ptr(),
2666 vp.as_ptr(),
2667 op.as_ptr(),
2668 tsv.as_ptr(),
2669 b as i32,
2670 n_head as i32,
2671 n_head_kv as i32,
2672 head_dim as i32,
2673 fa_scale,
2674 e.stream().cu_stream() as *mut core::ffi::c_void,
2675 )
2676 };
2677 if rc != 0 {
2678 return Err(format!("memra_fa3_vl rc={rc}").into());
2679 }
2680 } else {
2681 let fargs: Vec<crate::FaSeqVl> = (0..b)
2682 .map(|s| crate::FaSeqVl {
2683 q: e.addr_f32(&aps[s].qn),
2684 k16: e.addr_u8(&mirrors[s].0),
2685 v16: e.addr_u8(&mirrors[s].1),
2686 o: e.addr_f32(&attns[s]),
2687 kf: e.addr_f32(&aps[s].kn),
2688 vf: e.addr_f32v(
2689 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
2690 ),
2691 t: ts[s] as i32,
2692 pad: 0,
2693 })
2694 .collect();
2695 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
2696 }
2697 for (s, attn) in attns.into_iter().enumerate() {
2698 let (attn_g, ag16) = self.full_attn_prime_post_fa(
2699 e,
2700 attn,
2701 &aps[s].gate,
2702 ts[s],
2703 n_head,
2704 head_dim,
2705 )?;
2706 let mut done = false;
2707 if let Some(xh) = &ag16 {
2708 done = e.try_f16_gemm_pre_into_off(
2709 &fa.wo,
2710 xh,
2711 ts[s],
2712 &mut mixed,
2713 offs[s] * n_embd,
2714 )?;
2715 }
2716 if !done {
2717 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
2718 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
2719 }
2720 }
2721 } else {
2722 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2723 (0..b).map(|_| Vec::new()).collect();
2724 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
2725 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2726 parts[s].push(ys);
2727 }
2728 }
2729 for (s, g3s) in parts.into_iter().enumerate() {
2730 let (attn_g, ag16) = self.full_attn_prime_core_inner(
2732 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
2733 )?;
2734 let mut done = false;
2735 if let Some(xh) = &ag16 {
2736 done = e.try_f16_gemm_pre_into_off(
2737 &fa.wo,
2738 xh,
2739 ts[s],
2740 &mut mixed,
2741 offs[s] * n_embd,
2742 )?;
2743 }
2744 if !done {
2745 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
2746 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
2747 }
2748 }
2749 }
2750 }
2751 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2752 Mixer::Linear(la) => {
2753 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2758 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
2759 let outs =
2760 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
2761 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
2762 let (o, t) = (offs[s], ts[s]);
2763 let mut done = false;
2764 if let Some(xh) = &gn16 {
2765 done = e.try_f16_gemm_pre_into_off(
2766 &la.ssm_out,
2767 xh,
2768 t,
2769 &mut mixed,
2770 o * n_embd,
2771 )?;
2772 }
2773 if !done {
2774 let m = e.matmul(&la.ssm_out, &gn, t)?;
2775 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
2776 }
2777 }
2778 }
2779 }
2780 let mut x1 = e.uninit(total * n_embd)?;
2781 let mut z = e.uninit(total * n_embd)?;
2782 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2783 e.add_rms_norm_f16out(
2784 &x,
2785 &mixed,
2786 layer.post_attn_norm.float_data(),
2787 &mut x1,
2788 &mut z,
2789 &mut zx16,
2790 n_embd,
2791 total,
2792 eps,
2793 )?;
2794 let ffn_out = match &layer.ffn {
2795 crate::hybrid::Ffn::Dense {
2796 ffn_gate,
2797 ffn_up,
2798 ffn_down,
2799 } => {
2800 let n_ff = ffn_gate.out_features();
2801 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
2802 let up = g2.pop().unwrap();
2803 let gate = g2.pop().unwrap();
2804 let mut act = e.uninit(total * n_ff)?;
2805 let d_lim = self.cfg.clamp_shexp_at(il as u32);
2809 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
2810 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2811 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2812 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2813 Some(y) => y,
2814 None => e.matmul(ffn_down, &act, total)?,
2815 }
2816 } else {
2817 Self::ffn_act_lim(
2818 e,
2819 &self.cfg,
2820 &gate,
2821 &up,
2822 1.0,
2823 1.0,
2824 d_lim,
2825 &mut act,
2826 total * n_ff,
2827 )?;
2828 e.matmul(ffn_down, &act, total)?
2829 }
2830 }
2831 crate::hybrid::Ffn::Moe(m) => {
2832 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
2833 }
2834 };
2835 let mut x2 = e.uninit(total * n_embd)?;
2836 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2837 x = x2;
2838 }
2839 let mut hn = e.uninit(total * n_embd)?;
2841 e.rms_norm(
2842 &x,
2843 self.output_norm.float_data(),
2844 &mut hn,
2845 n_embd,
2846 total,
2847 eps,
2848 )?;
2849 let mut hcat = e.uninit(b * n_embd)?;
2855 for s in 0..b {
2856 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2857 e.copy_view_into(
2858 &mut hcat,
2859 s * n_embd,
2860 &hn.slice(last0..last0 + n_embd),
2861 n_embd,
2862 )?;
2863 }
2864 let logits_cat = if b >= 2 {
2865 e.try_f16_gemm(&self.output, &hcat, b)?
2866 } else {
2867 None
2868 };
2869 let logits_host: Option<Vec<f32>> = match &logits_cat {
2870 Some(lc) => Some(e.dtoh(lc)?),
2871 None => None,
2872 };
2873 let n_vocab = self.output.out_features();
2874 let mut hidden_all = if crate::spec::spec_hpost() {
2875 split(e, &hn, n_embd)?
2876 } else {
2877 split(e, &x, n_embd)?
2878 };
2879 let mut out = Vec::with_capacity(b);
2880 for s in 0..b {
2881 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2882 let mut h_seed = e.uninit(n_embd)?;
2883 if !crate::spec::spec_hpost() {
2884 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
2885 } else {
2886 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2887 }
2888 let logits = match &logits_host {
2889 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
2890 None => {
2891 let mut hlast = e.uninit(n_embd)?;
2892 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2893 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
2894 }
2895 };
2896 caches[s].pos += ts[s];
2897 out.push((logits, h_seed, hidden_all.remove(0)));
2898 }
2899 Ok(out)
2900 }
2901
2902 #[allow(clippy::too_many_arguments)]
2913 fn full_attn_prime(
2914 &self,
2915 e: &Engine,
2916 fa: &FullAttnLayer,
2917 h: &CudaSlice<f32>,
2918 hx: Option<&CudaSlice<u8>>,
2919 pos_d: &CudaSlice<i32>,
2920 t: usize,
2921 cache: &mut Cache,
2922 il: usize,
2923 seq_end: usize,
2924 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2925 if self.cfg.step35.is_some() {
2926 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
2927 }
2928 let g3 = match hx {
2933 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
2934 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
2935 };
2936 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
2937 }
2938
2939 fn full_attn_prime_core(
2943 &self,
2944 e: &Engine,
2945 fa: &FullAttnLayer,
2946 g3: Vec<CudaSlice<f32>>,
2947 pos_d: &CudaSlice<i32>,
2948 t: usize,
2949 cache: &mut Cache,
2950 il: usize,
2951 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2952 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
2953 if let Some(xh) = &ag16 {
2954 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
2955 return Ok(y);
2956 }
2957 }
2958 Ok(e.matmul(&fa.wo, &attn_g, t)?)
2959 }
2960
2961 fn full_attn_prime_core_inner(
2962 &self,
2963 e: &Engine,
2964 fa: &FullAttnLayer,
2965 g3: Vec<CudaSlice<f32>>,
2966 pos_d: &CudaSlice<i32>,
2967 t: usize,
2968 cache: &mut Cache,
2969 il: usize,
2970 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
2971 let cfg = &self.cfg;
2972 let geometry = cfg.full_attention_geometry_at(il as u32);
2973 let n_head = geometry.n_head as usize;
2974 let n_head_kv = geometry.n_head_kv as usize;
2975 let head_dim = geometry.head_dim_k as usize;
2976 let scale = geometry.attention_scale();
2977 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
2978 let AttnPre { q, k, v, gate } = pre;
2979 let mut attn = e.uninit(t * n_head * head_dim)?;
2980 self.full_attn_prime_fa_dispatch(
2981 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
2982 )?;
2983 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
2984 }
2985
2986 #[allow(clippy::type_complexity)]
2990 fn full_attn_prime_pre_fa(
2991 &self,
2992 e: &Engine,
2993 fa: &FullAttnLayer,
2994 mut g3: Vec<CudaSlice<f32>>,
2995 pos_d: &CudaSlice<i32>,
2996 t: usize,
2997 cache: &mut Cache,
2998 il: usize,
2999 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3000 let cfg = &self.cfg;
3001 let geometry = cfg.full_attention_geometry_at(il as u32);
3002 let n_head = geometry.n_head as usize;
3003 let n_head_kv = geometry.n_head_kv as usize;
3004 let head_dim = geometry.head_dim_k as usize;
3005 let eps = cfg.rms_eps;
3006
3007 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3011 let v = g3.pop().unwrap();
3012 let mut k = g3.pop().unwrap();
3013 let qf = g3.pop().unwrap();
3014 let (mut q, gate) = if gated {
3015 let mut q = e.uninit(t * n_head * head_dim)?;
3016 let mut gate = e.uninit(t * n_head * head_dim)?;
3017 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3018 (q, Some(gate))
3019 } else {
3020 (qf, None)
3021 };
3022
3023 let mut qn = e.uninit(t * n_head * head_dim)?;
3024 e.rms_norm(
3025 &q,
3026 fa.q_norm.float_data(),
3027 &mut qn,
3028 head_dim,
3029 n_head * t,
3030 eps,
3031 )?;
3032 q = qn;
3033 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3034 e.rms_norm(
3035 &k,
3036 fa.k_norm.float_data(),
3037 &mut kn,
3038 head_dim,
3039 n_head_kv * t,
3040 eps,
3041 )?;
3042 k = kn;
3043 let rope_dims = geometry.n_rot as usize;
3044 e.rope_neox(
3045 &mut q,
3046 pos_d,
3047 head_dim,
3048 rope_dims,
3049 n_head,
3050 t,
3051 geometry.rope_base,
3052 1.0,
3053 )?;
3054 e.rope_neox(
3055 &mut k,
3056 pos_d,
3057 head_dim,
3058 rope_dims,
3059 n_head_kv,
3060 t,
3061 geometry.rope_base,
3062 1.0,
3063 )?;
3064
3065 {
3068 let kvl = cache.kv[il].as_mut().unwrap();
3069 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3070 e.append_kv_quantized_rows(
3071 &k,
3072 &v,
3073 &mut kvl.k,
3074 &mut kvl.v,
3075 kvl.len,
3076 t,
3077 kvl.kv_dim_k,
3078 kvl.kv_dim_v,
3079 kvl.k_tok_bytes,
3080 kvl.v_tok_bytes,
3081 crate::Engine::kv_fp8_on(),
3082 )?;
3083 kvl.len += t;
3084 let new_len = kvl.len as i32;
3085 e.set_i32_one(&mut kvl.len_d, new_len)?;
3086 }
3087
3088 let base_len = {
3089 let kvl = cache.kv[il].as_ref().unwrap();
3090 kvl.len - t };
3092 Ok((AttnPre { q, k, v, gate }, base_len))
3093 }
3094
3095 #[allow(clippy::too_many_arguments)]
3102 fn full_attn_prime_fa_dispatch(
3103 &self,
3104 e: &Engine,
3105 q: &CudaSlice<f32>,
3106 k: &CudaSlice<f32>,
3107 v: &CudaSlice<f32>,
3108 attn: &mut CudaSlice<f32>,
3109 base_len: usize,
3110 t: usize,
3111 cache: &mut Cache,
3112 il: usize,
3113 head_dim: usize,
3114 n_head: usize,
3115 n_head_kv: usize,
3116 scale: f32,
3117 ) -> Result<(), Box<dyn std::error::Error>> {
3118 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
3131 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3132 e.sdpa_naive(
3133 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3134 )?;
3135 } else {
3136 e.fa_prefill(
3137 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3138 )?;
3139 }
3140 return Ok(());
3141 }
3142 let kvl = cache.kv[il].as_ref().unwrap();
3143 let t_kv = base_len + t;
3144 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
3145 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
3146 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3150 e.sdpa_naive_quantized_view(
3151 q,
3152 &k_view,
3153 &v_view,
3154 attn,
3155 head_dim,
3156 n_head,
3157 n_head_kv,
3158 t,
3159 t_kv,
3160 scale,
3161 true,
3162 kvl.k_tok_bytes,
3163 kvl.v_tok_bytes,
3164 )?;
3165 return Ok(());
3166 }
3167 let deqw = std::env::var("MEMRA_PRIME_DEQW")
3175 .map(|v| v != "0")
3176 .unwrap_or(true);
3177 if deqw {
3178 e.fa_prefill_view_ws(
3179 q,
3180 &k_view,
3181 &v_view,
3182 attn,
3183 head_dim,
3184 n_head,
3185 n_head_kv,
3186 t,
3187 t_kv,
3188 scale,
3189 true,
3190 kvl.k_tok_bytes,
3191 kvl.v_tok_bytes,
3192 crate::Engine::kv_fp8_on(),
3193 )?;
3194 } else {
3195 e.fa_prefill_view(
3196 q,
3197 &k_view,
3198 &v_view,
3199 attn,
3200 head_dim,
3201 n_head,
3202 n_head_kv,
3203 t,
3204 t_kv,
3205 scale,
3206 true,
3207 kvl.k_tok_bytes,
3208 kvl.v_tok_bytes,
3209 crate::Engine::kv_fp8_on(),
3210 )?;
3211 }
3212 Ok(())
3213 }
3214
3215 fn full_attn_prime_post_fa(
3218 &self,
3219 e: &Engine,
3220 attn: CudaSlice<f32>,
3221 gate: &Option<CudaSlice<f32>>,
3222 t: usize,
3223 n_head: usize,
3224 head_dim: usize,
3225 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3226 let (attn_g, ag16) = match gate {
3227 Some(gate) => {
3228 let n = t * n_head * head_dim;
3229 let mut ag = e.uninit(n)?;
3230 if Self::f16out_on(e, t) {
3231 let mut a16 = e.alloc_u8_uninit(n * 2)?;
3232 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
3233 (ag, Some(a16))
3234 } else {
3235 let mut gsig = e.uninit(n)?;
3236 e.sigmoid(gate, &mut gsig, n)?;
3237 e.mul(&attn, &gsig, &mut ag, n)?;
3238 (ag, None)
3239 }
3240 }
3241 None => (attn, None),
3242 };
3243 Ok((attn_g, ag16))
3244 }
3245
3246 fn linear_attn_prime(
3253 &self,
3254 e: &Engine,
3255 la: &LinearAttnLayer,
3256 h: &CudaSlice<f32>,
3257 hx: Option<&CudaSlice<u8>>,
3258 t: usize,
3259 cache: &mut Cache,
3260 il: usize,
3261 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3262 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3264 let g4 = match hx {
3265 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
3266 None => e.matmul_group(&ws, h, t)?,
3267 };
3268 self.linear_attn_prime_core(e, la, g4, t, cache, il)
3269 }
3270
3271 fn linear_attn_prime_core(
3273 &self,
3274 e: &Engine,
3275 la: &LinearAttnLayer,
3276 mut g4: Vec<CudaSlice<f32>>,
3277 t: usize,
3278 cache: &mut Cache,
3279 il: usize,
3280 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3281 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
3282 }
3283
3284 #[allow(clippy::too_many_arguments)]
3288 fn linear_attn_prime_core_pad_inner(
3289 &self,
3290 e: &Engine,
3291 la: &LinearAttnLayer,
3292 mut g4: Vec<CudaSlice<f32>>,
3293 t: usize,
3294 cache: &mut Cache,
3295 il: usize,
3296 pad_len: Option<&CudaSlice<i32>>,
3297 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3298 let ssm = self.cfg.ssm.as_ref().unwrap();
3300 let d_state = ssm.state_size as usize;
3301 let num_k = ssm.group_count as usize;
3302 let num_v = ssm.time_step_rank as usize;
3303 let key_dim = d_state * num_k;
3304 let value_dim = d_state * num_v;
3305 let conv_dim = key_dim * 2 + value_dim;
3306 let alpha = g4.pop().unwrap(); let beta_raw = g4.pop().unwrap(); let z = g4.pop().unwrap(); let qkv_mixed = g4.pop().unwrap(); self.linear_attn_prime_core_pad_view(
3311 e,
3312 la,
3313 &qkv_mixed.slice(0..t * conv_dim),
3314 &z.slice(0..t * value_dim),
3315 &beta_raw.slice(0..t * num_v),
3316 &alpha.slice(0..t * num_v),
3317 t,
3318 cache,
3319 il,
3320 pad_len,
3321 )
3322 }
3323
3324 #[allow(clippy::too_many_arguments)]
3327 fn linear_attn_gdn_prep(
3328 &self,
3329 e: &Engine,
3330 la: &LinearAttnLayer,
3331 qkv_mixed: &cudarc::driver::CudaView<f32>,
3332 beta_raw: &cudarc::driver::CudaView<f32>,
3333 alpha: &cudarc::driver::CudaView<f32>,
3334 t: usize,
3335 cache: &mut Cache,
3336 il: usize,
3337 pad_len: Option<&CudaSlice<i32>>,
3338 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
3339 let cfg = &self.cfg;
3340 let ssm = cfg.ssm.as_ref().unwrap();
3341 let d_state = ssm.state_size as usize; let num_k = ssm.group_count as usize; let num_v = ssm.time_step_rank as usize; let d_conv = ssm.conv_kernel as usize; let key_dim = d_state * num_k; let value_dim = d_state * num_v; let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
3349 debug_assert!(
3350 t >= d_conv - 1,
3351 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
3352 );
3353
3354 let rl = cache.recur[il].as_mut().unwrap();
3359 let hk = Self::gdn_hk(e, t, num_v, num_k);
3360 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
3361 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
3363 let mut k_g = e.uninit(d_state * hk * t)?;
3364 let mut v_g = e.uninit(d_state * num_v * t)?;
3365 if conv_fuse {
3366 e.ssm_conv1d_gdn_state_pad(
3367 qkv_mixed,
3368 &mut rl.conv_state,
3369 la.ssm_conv1d.float_data(),
3370 &mut q_g,
3371 &mut k_g,
3372 &mut v_g,
3373 conv_dim,
3374 t,
3375 d_conv,
3376 d_state,
3377 num_v,
3378 num_k,
3379 key_dim,
3380 hk,
3381 pad_len,
3382 )?;
3383 } else {
3384 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
3386 qkv_mixed,
3387 &mut rl.conv_state,
3388 la.ssm_conv1d.float_data(),
3389 &mut conv_out,
3390 conv_dim,
3391 t,
3392 d_conv,
3393 pad_len,
3394 )?;
3395 e.qkv_to_gdn_repack(
3396 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
3397 )?;
3398 }
3399 let mut q_l2 = e.uninit(d_state * hk * t)?;
3400 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
3404 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
3405 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
3406 Some(qb)
3407 } else {
3408 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
3409 None
3410 };
3411 let mut k_l2 = e.uninit(d_state * hk * t)?;
3412 let kb16 = if Engine::l2_v2_on(d_state) {
3414 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
3415 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
3416 Some(kb)
3417 } else {
3418 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
3419 None
3420 };
3421 let mut beta = e.uninit(t * num_v)?;
3422 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
3423 let mut g_log = e.uninit(t * num_v)?;
3424 e.gdn_glog_v(
3425 alpha,
3426 la.ssm_dt.float_data(),
3427 la.ssm_a.float_data(),
3428 &mut g_log,
3429 num_v,
3430 t,
3431 )?;
3432 if let Some(len_d) = pad_len {
3433 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
3434 }
3435 Ok(GdnPrep {
3436 hk,
3437 q_l2,
3438 k_l2,
3439 v_g,
3440 beta,
3441 g_log,
3442 kb16,
3443 qb16,
3444 })
3445 }
3446
3447 #[allow(clippy::too_many_arguments)]
3452 fn linear_attn_prime_core_batch(
3453 &self,
3454 e: &Engine,
3455 la: &LinearAttnLayer,
3456 g4: &[CudaSlice<f32>],
3457 offs: &[usize],
3458 ts: &[usize],
3459 caches: &mut [&mut Cache],
3460 il: usize,
3461 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
3462 let ssm = self.cfg.ssm.as_ref().unwrap();
3463 let d_state = ssm.state_size as usize;
3464 let num_k = ssm.group_count as usize;
3465 let num_v = ssm.time_step_rank as usize;
3466 let key_dim = d_state * num_k;
3467 let value_dim = d_state * num_v;
3468 let conv_dim = key_dim * 2 + value_dim;
3469 let eps = self.cfg.rms_eps;
3470 let scale = 1.0 / (d_state as f32).sqrt();
3471 let b = ts.len();
3472 let c = Engine::gdn_chunk_size();
3473 let carried = caches.iter().any(|c| c.pos > 0);
3476 let use_vl = !carried
3477 && (2..=8).contains(&b)
3478 && Engine::gdn_chunked_enabled()
3479 && ts.iter().all(|&t| t >= 16)
3480 && e.gdn_mma_enabled(c)
3481 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
3482 if !use_vl {
3483 return (0..b)
3484 .map(|s| {
3485 let (o, t) = (offs[s], ts[s]);
3486 self.linear_attn_prime_core_pad_view(
3487 e,
3488 la,
3489 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
3490 &g4[1].slice(o * value_dim..(o + t) * value_dim),
3491 &g4[2].slice(o * num_v..(o + t) * num_v),
3492 &g4[3].slice(o * num_v..(o + t) * num_v),
3493 t,
3494 caches[s],
3495 il,
3496 None,
3497 )
3498 })
3499 .collect();
3500 }
3501 struct SeqBufs {
3505 conv_out: CudaSlice<f32>,
3506 q_g: CudaSlice<f32>,
3507 k_g: CudaSlice<f32>,
3508 v_g: CudaSlice<f32>,
3509 q_l2: CudaSlice<f32>,
3510 k_l2: CudaSlice<f32>,
3511 beta: CudaSlice<f32>,
3512 g_log: CudaSlice<f32>,
3513 gn: CudaSlice<f32>,
3514 gn16: CudaSlice<u8>,
3515 }
3516 let d_conv = ssm.conv_kernel as usize;
3517 let f16o = Self::f16out_on(e, 16);
3518 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
3520 let mut pres = Vec::with_capacity(b);
3521 for &t in ts.iter().take(b) {
3522 sb.push(SeqBufs {
3523 conv_out: e.uninit(conv_dim * t)?,
3524 q_g: e.uninit(d_state * hk * t)?,
3525 k_g: e.uninit(d_state * hk * t)?,
3526 v_g: e.uninit(d_state * num_v * t)?,
3527 q_l2: e.uninit(d_state * hk * t)?,
3528 k_l2: e.uninit(d_state * hk * t)?,
3529 beta: e.uninit(t * num_v)?,
3530 g_log: e.uninit(t * num_v)?,
3531 gn: e.uninit(d_state * num_v * t)?,
3532 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
3533 });
3534 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
3535 }
3536 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
3537 .map(|s| {
3538 let (o, t) = (offs[s], ts[s]);
3539 let rl = caches[s].recur[il].as_ref().unwrap();
3540 crate::GdnPrepVl {
3541 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
3542 conv_state: e.addr_f32(&rl.conv_state),
3543 conv_out: e.addr_f32(&sb[s].conv_out),
3544 q_g: e.addr_f32(&sb[s].q_g),
3545 k_g: e.addr_f32(&sb[s].k_g),
3546 v_g: e.addr_f32(&sb[s].v_g),
3547 q_l2: e.addr_f32(&sb[s].q_l2),
3548 k_l2: e.addr_f32(&sb[s].k_l2),
3549 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
3550 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
3551 beta: e.addr_f32(&sb[s].beta),
3552 g_log: e.addr_f32(&sb[s].g_log),
3553 o: e.addr_f32(&pres[s].o),
3554 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
3555 gn: e.addr_f32(&sb[s].gn),
3556 gn16: e.addr_u8(&sb[s].gn16),
3557 kb16: if Engine::l2_v2_on(d_state) {
3558 e.addr_u8(&pres[s].kb16)
3559 } else {
3560 0
3561 },
3562 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
3563 e.addr_u8(&pres[s].qb16)
3564 } else {
3565 0
3566 },
3567 t: t as i32,
3568 pad: 0,
3569 }
3570 })
3571 .collect();
3572 let args: Vec<crate::GdnSeqVl> = (0..b)
3573 .map(|s| {
3574 let rl = caches[s].recur[il].as_ref().unwrap();
3575 crate::GdnSeqVl {
3576 kb16: e.addr_u8(&pres[s].kb16),
3577 gcum: e.addr_f32(&pres[s].gcum),
3578 beta: e.addr_f32(&sb[s].beta),
3579 u: e.addr_f32(&pres[s].u),
3580 wb16: e.addr_u8(&pres[s].wb16),
3581 y: e.addr_u8(&pres[s].y16),
3582 ssnap: e.addr_u8(&pres[s].ssnap16),
3583 state_in: e.addr_f32(&rl.ssm_state),
3584 state_out: e.addr_f32(&rl.ssm_state_alt),
3585 q: e.addr_f32(&sb[s].q_l2),
3586 p: e.addr_f32(&pres[s].p),
3587 o: e.addr_f32(&pres[s].o),
3588 k: e.addr_f32(&sb[s].k_l2),
3589 v: e.addr_f32(&sb[s].v_g),
3590 g: e.addr_f32(&sb[s].g_log),
3591 a: e.addr_f32(&pres[s].a),
3592 w: e.addr_f32(&pres[s].w),
3593 t: ts[s] as i32,
3594 nc: pres[s].nc as i32,
3595 }
3596 })
3597 .collect();
3598 e.gdn_prep_vl8(
3599 &prep_args,
3600 la.ssm_conv1d.float_data(),
3601 la.ssm_dt.float_data(),
3602 la.ssm_a.float_data(),
3603 conv_dim,
3604 d_conv,
3605 d_state,
3606 num_v,
3607 num_k,
3608 key_dim,
3609 hk,
3610 eps,
3611 )?;
3612 if !Engine::l2_v2_on(d_state) {
3615 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
3616 }
3617 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
3619 if !Engine::l2_v2_on(d_state) {
3621 for s in 0..b {
3622 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
3623 }
3624 }
3625 let mut wa = [crate::GdnWVl::default(); 8];
3626 for s in 0..b {
3627 wa[s] = crate::GdnWVl {
3628 qb16: e.addr_u8(&pres[s].qb16),
3629 pb16: e.addr_u8(&pres[s].pb16),
3630 };
3631 }
3632 Some(crate::GdnWVl8(wa))
3633 } else {
3634 None
3635 };
3636 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
3637 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
3638 if f16o {
3639 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
3640 }
3641 let mut out = Vec::with_capacity(b);
3643 for (s, bufs) in sb.into_iter().enumerate() {
3644 let rl = caches[s].recur[il].as_mut().unwrap();
3645 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
3646 let (o, t) = (offs[s], ts[s]);
3647 let SeqBufs { mut gn, gn16, .. } = bufs;
3648 if f16o {
3649 out.push((gn, Some(gn16)));
3650 } else {
3651 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
3652 e.gated_rmsnorm_zv(
3653 &pres[s].o,
3654 la.ssm_norm.float_data(),
3655 &z_v,
3656 &mut gn,
3657 d_state,
3658 num_v * t,
3659 eps,
3660 )?;
3661 out.push((gn, None));
3662 }
3663 }
3664 Ok(out)
3665 }
3666
3667 #[allow(clippy::too_many_arguments)]
3671 fn linear_attn_prime_core_pad_view(
3672 &self,
3673 e: &Engine,
3674 la: &LinearAttnLayer,
3675 qkv_mixed: &cudarc::driver::CudaView<f32>,
3676 z: &cudarc::driver::CudaView<f32>,
3677 beta_raw: &cudarc::driver::CudaView<f32>,
3678 alpha: &cudarc::driver::CudaView<f32>,
3679 t: usize,
3680 cache: &mut Cache,
3681 il: usize,
3682 pad_len: Option<&CudaSlice<i32>>,
3683 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3684 let cfg = &self.cfg;
3685 let ssm = cfg.ssm.as_ref().unwrap();
3686 let d_state = ssm.state_size as usize; let num_v = ssm.time_step_rank as usize; let eps = cfg.rms_eps;
3689 let scale = 1.0 / (d_state as f32).sqrt();
3690
3691 let prep =
3692 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
3693
3694 let mut o = e.uninit(d_state * num_v * t)?;
3700 let rl = cache.recur[il].as_mut().unwrap();
3701 {
3702 let crate::cache::RecurLayer {
3703 ssm_state,
3704 ssm_state_alt,
3705 ..
3706 } = rl;
3707 e.gdn_scan_prefill(
3708 &prep.q_l2,
3709 &prep.k_l2,
3710 &prep.v_g,
3711 &prep.g_log,
3712 &prep.beta,
3713 prep.kb16.as_ref(),
3714 prep.qb16.as_ref(),
3715 ssm_state,
3716 ssm_state_alt,
3717 &mut o,
3718 num_v,
3719 t,
3720 scale,
3721 prep.hk,
3722 )?;
3723 }
3724 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
3725
3726 let mut gn = e.uninit(d_state * num_v * t)?;
3729 let gn16 = if Self::f16out_on(e, t) {
3730 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
3731 e.gated_rmsnorm_f16out_zv(
3732 &o,
3733 la.ssm_norm.float_data(),
3734 z,
3735 &mut gn,
3736 &mut g16,
3737 d_state,
3738 num_v * t,
3739 eps,
3740 )?;
3741 Some(g16)
3742 } else {
3743 e.gated_rmsnorm_zv(
3744 &o,
3745 la.ssm_norm.float_data(),
3746 z,
3747 &mut gn,
3748 d_state,
3749 num_v * t,
3750 eps,
3751 )?;
3752 None
3753 };
3754 Ok((gn, gn16))
3755 }
3756
3757 #[allow(clippy::too_many_arguments)]
3759 fn linear_attn_prime_core_pad(
3760 &self,
3761 e: &Engine,
3762 la: &LinearAttnLayer,
3763 g4: Vec<CudaSlice<f32>>,
3764 t: usize,
3765 cache: &mut Cache,
3766 il: usize,
3767 pad_len: Option<&CudaSlice<i32>>,
3768 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3769 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
3770 if let Some(xh) = &gn16 {
3771 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
3772 return Ok(y);
3773 }
3774 }
3775 Ok(e.matmul(&la.ssm_out, &gn, t)?)
3776 }
3777
3778 pub fn full_attn(
3783 &self,
3784 e: &Engine,
3785 fa: &FullAttnLayer,
3786 h: &CudaSlice<f32>,
3787 pos_d: &CudaSlice<i32>,
3788 t: usize,
3789 il: usize,
3790 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3791 if self.cfg.step35.is_some() {
3792 return self.step35_attn(e, fa, h, pos_d, t, il);
3793 }
3794 let cfg = &self.cfg;
3795 let _n_embd = cfg.n_embd as usize;
3796 let geometry = cfg.full_attention_geometry_at(il as u32);
3797 let n_head = geometry.n_head as usize;
3798 let n_head_kv = geometry.n_head_kv as usize;
3799 let head_dim = geometry.head_dim_k as usize;
3800 let eps = cfg.rms_eps;
3801 let scale = geometry.attention_scale();
3802
3803 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3806 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
3808 let v = g3.pop().unwrap();
3809 let mut k = g3.pop().unwrap();
3810 let qf = g3.pop().unwrap();
3811 let (mut q, gate) = if gated {
3812 let mut q = e.uninit(t * n_head * head_dim)?;
3813 let mut gate = e.uninit(t * n_head * head_dim)?;
3814 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3815 (q, Some(gate))
3816 } else {
3817 (qf, None)
3818 };
3819
3820 let mut qn = e.uninit(t * n_head * head_dim)?;
3822 e.rms_norm(
3823 &q,
3824 fa.q_norm.float_data(),
3825 &mut qn,
3826 head_dim,
3827 n_head * t,
3828 eps,
3829 )?;
3830 q = qn;
3831 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3832 e.rms_norm(
3833 &k,
3834 fa.k_norm.float_data(),
3835 &mut kn,
3836 head_dim,
3837 n_head_kv * t,
3838 eps,
3839 )?;
3840 k = kn;
3841 let rope_dims = geometry.n_rot as usize;
3842 e.rope_neox(
3843 &mut q,
3844 pos_d,
3845 head_dim,
3846 rope_dims,
3847 n_head,
3848 t,
3849 geometry.rope_base,
3850 1.0,
3851 )?;
3852 e.rope_neox(
3853 &mut k,
3854 pos_d,
3855 head_dim,
3856 rope_dims,
3857 n_head_kv,
3858 t,
3859 geometry.rope_base,
3860 1.0,
3861 )?;
3862
3863 let mut attn = e.uninit(t * n_head * head_dim)?;
3865 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3868 e.sdpa_naive(
3870 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3871 )?;
3872 } else {
3873 e.fa_prefill(
3874 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3875 )?;
3876 }
3877
3878 let attn_g = match &gate {
3880 Some(gate) => {
3881 let mut gsig = e.uninit(t * n_head * head_dim)?;
3882 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
3883 let mut ag = e.uninit(t * n_head * head_dim)?;
3884 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
3885 ag
3886 }
3887 None => attn,
3888 };
3889
3890 let o = e.matmul(&fa.wo, &attn_g, t)?;
3892 Ok(o)
3893 }
3894
3895 pub fn linear_attn(
3897 &self,
3898 e: &Engine,
3899 la: &LinearAttnLayer,
3900 h: &CudaSlice<f32>,
3901 t: usize,
3902 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3903 let cfg = &self.cfg;
3904 let _n_embd = cfg.n_embd as usize;
3905 let ssm = cfg.ssm.as_ref().unwrap();
3906 let d_state = ssm.state_size as usize; let num_k = ssm.group_count as usize; let num_v = ssm.time_step_rank as usize; let d_conv = ssm.conv_kernel as usize; let head_k = d_state;
3911 let head_v = d_state;
3912 let key_dim = head_k * num_k; let value_dim = head_v * num_v; let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
3916 let scale = 1.0 / (d_state as f32).sqrt();
3917
3918 let mut g4 = e.matmul_group(
3921 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
3922 h,
3923 t,
3924 )?;
3925 let alpha = g4.pop().unwrap(); let beta_raw = g4.pop().unwrap(); let z = g4.pop().unwrap(); let qkv_mixed = g4.pop().unwrap(); let _ = (head_k, head_v);
3937 let mut q_g = e.uninit(d_state * num_v * t)?;
3938 let mut k_g = e.uninit(d_state * num_v * t)?;
3939 let mut v_g = e.uninit(d_state * num_v * t)?;
3940 e.ssm_conv1d_gdn(
3941 &qkv_mixed,
3942 la.ssm_conv1d.float_data(),
3943 &mut q_g,
3944 &mut k_g,
3945 &mut v_g,
3946 conv_dim,
3947 t,
3948 d_conv,
3949 d_state,
3950 num_v,
3951 num_k,
3952 key_dim,
3953 )?;
3954 let mut q_l2 = e.uninit(d_state * num_v * t)?;
3956 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
3957 let mut k_l2 = e.uninit(d_state * num_v * t)?;
3958 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
3959 let v_gd = v_g;
3960
3961 let mut beta = e.uninit(t * num_v)?;
3964 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
3965 let mut g_log = e.uninit(t * num_v)?;
3967 e.gdn_glog(
3968 &alpha,
3969 la.ssm_dt.float_data(),
3970 la.ssm_a.float_data(),
3971 &mut g_log,
3972 num_v,
3973 t,
3974 )?;
3975
3976 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
3979 let mut o = e.uninit(d_state * num_v * t)?;
3980 e.gdn_scan_prefill(
3981 &q_l2,
3982 &k_l2,
3983 &v_gd,
3984 &g_log,
3985 &beta,
3986 None,
3987 None,
3988 &state_in,
3989 &mut state_out,
3990 &mut o,
3991 num_v,
3992 t,
3993 scale,
3994 num_v,
3995 )?;
3996
3997 let mut gn = e.uninit(d_state * num_v * t)?;
4002 e.gated_rmsnorm(
4003 &o,
4004 la.ssm_norm.float_data(),
4005 &z,
4006 &mut gn,
4007 d_state,
4008 num_v * t,
4009 eps,
4010 )?;
4011
4012 let out = e.matmul(&la.ssm_out, &gn, t)?;
4016 Ok(out)
4017 }
4018}
4019
4020impl HybridModel {
4021 pub fn moe_ffn_il(
4032 &self,
4033 e: &Engine,
4034 m: &MoeWeights,
4035 z: &CudaSlice<f32>,
4036 t: usize,
4037 il: u16,
4038 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4039 Self::moe_ffn_inner(e, m, z, None, t, &self.cfg, il, self.max_moe_block(), false)
4040 }
4041
4042 pub fn moe_ffn_il_prefill(
4045 &self,
4046 e: &Engine,
4047 m: &MoeWeights,
4048 z: &CudaSlice<f32>,
4049 t: usize,
4050 il: u16,
4051 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4052 Self::moe_ffn_inner(e, m, z, None, t, &self.cfg, il, self.max_moe_block(), true)
4053 }
4054
4055 pub fn moe_ffn_il_zq8(
4059 &self,
4060 e: &Engine,
4061 m: &MoeWeights,
4062 z: &CudaSlice<f32>,
4063 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4064 t: usize,
4065 il: u16,
4066 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4067 Self::moe_ffn_inner(e, m, z, zq8, t, &self.cfg, il, self.max_moe_block(), false)
4068 }
4069
4070 pub(crate) fn moe_ffn(
4078 e: &Engine,
4079 m: &MoeWeights,
4080 z: &CudaSlice<f32>,
4081 t: usize,
4082 cfg: &ModelConfig,
4083 il: u16,
4084 max_block: usize,
4085 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4086 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false)
4087 }
4088
4089 #[allow(clippy::too_many_arguments)]
4090 pub(crate) fn moe_ffn_inner(
4091 e: &Engine,
4092 m: &MoeWeights,
4093 z: &CudaSlice<f32>,
4094 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4095 t: usize,
4096 cfg: &ModelConfig,
4097 il: u16,
4098 max_block: usize,
4099 prefill: bool,
4100 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4101 let worker_io = crate::spill_pread::worker_enabled();
4102 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
4103 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
4104 e.with_moe_cache(max_block, |cache, _| {
4105 cache.begin_forward_epoch(il, t);
4106 if worker_io {
4107 cache.begin_worker_scope();
4108 }
4109 Ok(())
4110 })?;
4111 }
4112 if Self::sigmoid_resident_dev_eligible(e, m, cfg) {
4113 let moe = cfg.moe.as_ref().unwrap();
4114 let n_expert = moe.expert_count as usize;
4115 let n_used = moe.expert_used_count as usize;
4116 let sigmoid = cfg.sigmoid_router().unwrap();
4117 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4118 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
4119 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
4120 }
4121 if t > 1 && moe_grouped_enabled(cfg, prefill) {
4124 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
4125 if std::env::var("MEMRA_MOE_GATE").is_ok() {
4130 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
4131 let g_host = e.dtoh(&grouped_out)?;
4132 let s_host = e.dtoh(&seq_out)?;
4133 let g_bytes: &[u8] = unsafe {
4134 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
4135 };
4136 let s_bytes: &[u8] = unsafe {
4137 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
4138 };
4139 if g_bytes == s_bytes {
4140 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
4141 } else {
4142 let diffs = g_host
4143 .iter()
4144 .zip(s_host.iter())
4145 .enumerate()
4146 .filter(|(_, (a, b))| a != b)
4147 .count();
4148 let maxdiff = g_host
4149 .iter()
4150 .zip(s_host.iter())
4151 .map(|(a, b)| (a - b).abs())
4152 .fold(0.0f32, f32::max);
4153 panic!(
4154 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
4155 g_host.len()
4156 );
4157 }
4158 }
4159 return Ok(grouped_out);
4160 }
4161 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
4162 }
4163
4164 fn sigmoid_resident_dev_eligible(e: &Engine, m: &MoeWeights, cfg: &ModelConfig) -> bool {
4165 let Some(moe) = cfg.moe.as_ref() else {
4166 return false;
4167 };
4168 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4171 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
4172 std::env::var("MEMRA_MOE_STATS").is_ok()
4173 || std::env::var("MEMRA_MOE_TRACE").is_ok()
4174 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
4175 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
4176 || std::env::var("MEMRA_MOE_GATE").is_ok()
4177 });
4178 cfg.step35.is_some()
4179 && sigmoid_router_enabled()
4180 && moe_dev_enabled()
4181 && moe_slab_enabled()
4182 && !observation_mode
4183 && moe.expert_used_count <= 8
4184 && m.has_uniform_expert_layout()
4185 && m.gate_exps.macros.is_none()
4186 && m.up_exps.macros.is_none()
4187 && m.down_exps.macros.is_none()
4188 && !m.has_macros
4189 && moe_q8_enabled()
4190 && q8_expert_supported(m.gate_exps.qtype)
4191 && q8_expert_supported(m.up_exps.qtype)
4192 && q8_expert_supported(m.down_exps.qtype)
4193 && m.dev_exps
4194 .as_ref()
4195 .is_some_and(|dev| dev.dev == e.ctx().ordinal())
4196 }
4197
4198 pub(crate) fn moe_ffn_sequential(
4200 e: &Engine,
4201 m: &MoeWeights,
4202 z: &CudaSlice<f32>,
4203 t: usize,
4204 cfg: &ModelConfig,
4205 il: u16,
4206 max_block: usize,
4207 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4208 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
4209 }
4210
4211 fn moe_router_logits(
4215 e: &Engine,
4216 m: &MoeWeights,
4217 z: &CudaSlice<f32>,
4218 t: usize,
4219 cfg: &ModelConfig,
4220 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4221 if t < PRIME_MIN_T {
4222 if crate::router_kernel_on() {
4224 e.router_gemv(
4225 m.gate_inp.float_data(),
4226 z,
4227 cfg.n_embd as usize,
4228 m.gate_exps.n_expert,
4229 t,
4230 )
4231 } else {
4232 e.matmul_decode_exact(&m.gate_inp, z, t)
4233 }
4234 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
4235 e.router_gemv(
4236 m.gate_inp.float_data(),
4237 z,
4238 cfg.n_embd as usize,
4239 m.gate_exps.n_expert,
4240 t,
4241 )
4242 } else {
4243 e.matmul(&m.gate_inp, z, t)
4244 }
4245 }
4246
4247 fn trace_moe_routes(
4251 il: u16,
4252 t: usize,
4253 sel_all: &[u32],
4254 weights: &[f32],
4255 ) -> Result<(), Box<dyn std::error::Error>> {
4256 use std::io::Write as _;
4257 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
4258 let mut f = std::fs::OpenOptions::new()
4259 .create(true)
4260 .append(true)
4261 .open(path)?;
4262 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
4263 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
4264 }
4265 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
4266 let mut f = std::fs::OpenOptions::new()
4267 .create(true)
4268 .append(true)
4269 .open(path)?;
4270 let pairs: Vec<String> = sel_all
4271 .iter()
4272 .zip(weights)
4273 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
4274 .collect();
4275 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
4276 }
4277 Ok(())
4278 }
4279
4280 #[allow(clippy::too_many_arguments)]
4281 fn trace_sigmoid_router_logits(
4282 e: &Engine,
4283 il: u16,
4284 t: usize,
4285 n_expert: usize,
4286 n_used: usize,
4287 logits: &CudaSlice<f32>,
4288 m: &MoeWeights,
4289 (scaling_factor, route_norm): (f32, bool),
4290 ) -> Result<(), Box<dyn std::error::Error>> {
4291 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
4292 return Ok(());
4293 }
4294 let logits = e.dtoh(logits)?;
4295 let active: Vec<u8> = m
4296 .active_experts
4297 .as_ref()
4298 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
4299 .unwrap_or_else(|| vec![1; n_expert]);
4300 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
4301 crate::sigrouter_contract::capture_served_logits(
4302 il as u32,
4303 t,
4304 n_expert,
4305 n_used,
4306 scaling_factor,
4307 route_norm,
4308 &active,
4309 &bias,
4310 &logits,
4311 )?;
4312 Ok(())
4313 }
4314
4315 fn trace_moe_input(
4320 e: &Engine,
4321 il: u16,
4322 t: usize,
4323 n_embd: usize,
4324 z: &CudaSlice<f32>,
4325 ) -> Result<(), Box<dyn std::error::Error>> {
4326 use std::io::Write as _;
4327 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
4328 return Ok(());
4329 };
4330 let host = e.dtoh(z)?;
4331 if host.len() != t * n_embd {
4332 return Err(format!(
4333 "MoE input trace shape mismatch at layer {il}: got {} values, expected {}x{}",
4334 host.len(),
4335 t,
4336 n_embd
4337 )
4338 .into());
4339 }
4340 let bytes = unsafe {
4341 std::slice::from_raw_parts(
4342 host.as_ptr().cast::<u8>(),
4343 host.len() * std::mem::size_of::<f32>(),
4344 )
4345 };
4346 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
4347 let mut state = state
4348 .lock()
4349 .map_err(|_| "MoE input trace writer lock is poisoned")?;
4350 if state.is_none() {
4351 let dir = std::path::PathBuf::from(&dir);
4352 std::fs::create_dir_all(&dir)?;
4353 let index = std::fs::OpenOptions::new()
4354 .create(true)
4355 .append(true)
4356 .open(dir.join("index.jsonl"))?;
4357 *state = Some(MoeInputTraceWriter {
4358 dir,
4359 index,
4360 payloads: std::collections::HashMap::new(),
4361 });
4362 }
4363 let writer = state.as_mut().unwrap();
4364 if writer.dir != std::path::Path::new(&dir) {
4365 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
4366 }
4367 let file_name = format!("layer-{il:03}.f32");
4368 if !writer.payloads.contains_key(&il) {
4369 let payload = std::fs::OpenOptions::new()
4370 .create(true)
4371 .append(true)
4372 .open(writer.dir.join(&file_name))?;
4373 let offset = payload.metadata()?.len();
4374 writer.payloads.insert(il, (payload, offset));
4375 }
4376 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
4377 let row_offset = *offset;
4378 payload.write_all(bytes)?;
4379 *offset += bytes.len() as u64;
4380 writeln!(
4381 writer.index,
4382 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
4383 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
4384 \"payload_bytes\":{}}}",
4385 bytes.len()
4386 )?;
4387 Ok(())
4388 }
4389
4390 #[allow(clippy::too_many_arguments)]
4391 pub(crate) fn moe_ffn_sequential_zq8(
4392 e: &Engine,
4393 m: &MoeWeights,
4394 z: &CudaSlice<f32>,
4395 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4396 t: usize,
4397 cfg: &ModelConfig,
4398 il: u16,
4399 max_block: usize,
4400 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4401 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
4402 let moe = cfg.moe.as_ref().unwrap();
4403 let n_embd = cfg.n_embd as usize; let n_expert = moe.expert_count as usize; let n_used = moe.expert_used_count as usize; let n_ff_exp = moe.expert_ff_length as usize; debug_assert_eq!(m.gate_exps.in_f, n_embd);
4410 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
4411 debug_assert_eq!(m.down_exps.in_f, n_ff_exp); debug_assert_eq!(m.down_exps.out_f, n_embd); debug_assert_eq!(m.gate_exps.n_expert, n_expert);
4414
4415 let lim_exp = cfg.clamp_exp_at(il as u32);
4418 let lim_shexp = cfg.clamp_shexp_at(il as u32);
4419 let use_cache = Engine::moe_cache_enabled();
4420 let uniform_experts = m.has_uniform_expert_layout();
4421 let moe_q8 = uniform_experts
4422 && moe_q8_enabled()
4423 && q8_expert_supported(m.gate_exps.qtype)
4424 && q8_expert_supported(m.up_exps.qtype)
4425 && q8_expert_supported(m.down_exps.qtype);
4426 let cpu_expert_requested = crate::cpu_experts::configured();
4433 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
4434 return Err(std::io::Error::other(
4435 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
4436 )
4437 .into());
4438 }
4439 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
4440 let freeze_cpu_residency = cpu_expert_requested
4446 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
4447 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
4448 .ok()
4449 .and_then(|value| value.parse::<usize>().ok())
4450 .is_some_and(|tokens| tokens > 0);
4451 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
4452 e.freeze_moe_cache();
4453 }
4454 let cache_frozen = use_cache && e.moe_cache_frozen();
4455 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
4456
4457 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4460 if let Some(sig) = cfg.sigmoid_router() {
4461 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
4462 }
4463
4464 let no_exp_macros = m.gate_exps.macros.is_none()
4503 && m.up_exps.macros.is_none()
4504 && m.down_exps.macros.is_none();
4505 if cfg.sigmoid_router().is_none()
4509 && cfg.m3.is_none()
4510 && cfg.hy3.is_none()
4511 && !cfg.swiglu_clamped_at(il as u32)
4512 && no_exp_macros
4513 && t >= PRIME_MIN_T
4514 && m.dev_exps.is_some()
4515 && moe_q8_enabled()
4516 && q8_expert_supported(m.gate_exps.qtype)
4517 && q8_expert_supported(m.up_exps.qtype)
4518 && q8_expert_supported(m.down_exps.qtype)
4519 && std::env::var("MEMRA_MOE_PAIRS")
4520 .map(|v| v != "0")
4521 .unwrap_or(true)
4522 && std::env::var("MEMRA_MOE_STATS").is_err()
4523 {
4524 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
4525 }
4526
4527 let dev_ok = uniform_experts
4545 && cfg.sigmoid_router().is_none()
4546 && cfg.m3.is_none()
4547 && cfg.hy3.is_none()
4548 && !cfg.swiglu_clamped_at(il as u32);
4549 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
4553 || std::env::var("MEMRA_MOE_TRACE").is_ok()
4554 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
4555 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
4556 if dev_ok
4557 && t < PRIME_MIN_T
4558 && m.dev_exps.is_some()
4559 && n_used <= 8
4560 && moe_dev_enabled()
4561 && !observe_routes
4562 {
4563 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
4564 }
4565 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
4566 let row_ok = e.with_moe_cache(max_block, |c, eng| {
4567 if moe_prewarm_enabled() {
4568 c.prewarm_layer(il, m, eng)?;
4569 }
4570 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
4571 })?;
4572 if row_ok {
4573 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
4574 }
4575 }
4576
4577 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
4579 if cpu_hybrid {
4580 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
4581 e,
4582 &logits,
4583 z,
4584 t,
4585 n_expert,
4586 n_used,
4587 m.exp_probs_b.as_deref(),
4588 sig,
4589 m.active_experts.as_deref(),
4590 )?;
4591 (sel, w, Some(input))
4592 } else {
4593 let (sel, w) =
4594 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
4595 (sel, w, None)
4596 }
4597 } else {
4598 let (sel, w) =
4599 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
4600 (sel, w, None)
4601 };
4602 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
4603
4604 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
4608 Self::trace_moe_input(e, il, t, n_embd, z)?;
4609
4610 let worker_disk_prefetch =
4622 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
4623 let promote_worker_h2d =
4624 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
4625 if promote_worker_h2d {
4626 let mut selected_blocks = Vec::with_capacity(n_used * 3);
4627 for &ex in sel_all.iter().take(n_used) {
4628 let ex = ex as u16;
4629 selected_blocks.extend([
4630 BlockId::new(il, PROJ_GATE, ex),
4631 BlockId::new(il, PROJ_UP, ex),
4632 BlockId::new(il, PROJ_DOWN, ex),
4633 ]);
4634 }
4635 for &ex in sel_all.iter().take(n_used) {
4636 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
4637 }
4638 e.with_moe_cache(max_block, |cache, eng| {
4639 cache.promote_worker_reads_at_safe_boundary(
4640 &selected_blocks,
4641 &selected_blocks,
4642 eng,
4643 )?;
4644 Ok(())
4645 })?;
4646 }
4647
4648 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
4651 let mut cnt = vec![0u32; n_expert];
4652 for &s in sel_all.iter() {
4653 cnt[s as usize] += 1;
4654 }
4655 let total = sel_all.len() as f64;
4656 let mut h = 0.0f64;
4657 let mut active = 0usize;
4658 for &c in &cnt {
4659 if c > 0 {
4660 active += 1;
4661 let p = c as f64 / total;
4662 h -= p * p.log2();
4663 }
4664 }
4665 let maxc = cnt.iter().copied().max().unwrap_or(0);
4666 println!(
4667 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
4668 il,
4669 t,
4670 sel_all.len(),
4671 active,
4672 n_expert,
4673 h,
4674 (n_expert as f64).log2(),
4675 total / active.max(1) as f64,
4676 maxc
4677 );
4678 }
4679
4680 let gdec_may_fire = uniform_experts
4693 && use_cache
4694 && n_used <= 8
4695 && gdec_enabled()
4696 && !cfg.swiglu_clamped_at(il as u32);
4697 let slab_local = m
4713 .dev_exps
4714 .as_ref()
4715 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
4716 let slab_bases = slab_local.map(|d| {
4717 use cudarc::driver::DevicePtr;
4718 let s = e.stream();
4719 let (pg, _g0) = d.gate.device_ptr(&s);
4720 let (pu, _g1) = d.up.device_ptr(&s);
4721 let (pd, _g2) = d.down.device_ptr(&s);
4722 (pg as u64, pu as u64, pd as u64)
4723 });
4724 let slab_fused_may_fire = slab_bases.is_some()
4734 && n_used <= 8
4735 && gdec_enabled()
4736 && !cfg.swiglu_clamped_at(il as u32)
4737 && cfg.m3.is_none()
4738 && no_exp_macros
4739 && moe_q8;
4740 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
4743 e.uninit(t * n_embd)?
4744 } else {
4745 e.zeros(t * n_embd)?
4746 };
4747 let cpu_input = if cpu_hybrid {
4750 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
4751 } else {
4752 None
4753 };
4754
4755 let g_len = m.gate_exps.max_expert_bytes(); let u_len = m.up_exps.max_expert_bytes(); let d_len = m.down_exps.max_expert_bytes(); let mut scratch_g: Option<CudaSlice<u8>> = None;
4763 let mut scratch_u: Option<CudaSlice<u8>> = None;
4764 let mut scratch_d: Option<CudaSlice<u8>> = None;
4765 let page_window = moe_page_prefetch_window();
4773
4774 for tok in 0..t {
4777 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
4778 let w = &w_all[tok * n_used..(tok + 1) * n_used];
4779 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
4781
4782 let no_macros = m.gate_exps.macros.is_none()
4796 && m.up_exps.macros.is_none()
4797 && m.down_exps.macros.is_none();
4798 if slab_fused_may_fire {
4808 let (pg, pu, pd) = slab_bases.unwrap();
4809 let mut gp = [0u64; 8];
4810 let mut up = [0u64; 8];
4811 let mut dp = [0u64; 8];
4812 for (j, &ex) in sel.iter().enumerate() {
4813 let ex = ex as usize;
4814 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
4815 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
4816 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
4817 }
4818 let mut wv = [0f32; 8];
4819 wv[..n_used].copy_from_slice(w);
4820 if tok_q8.is_none() {
4821 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
4822 }
4823 let (zq, zd) = tok_q8.as_ref().unwrap();
4824 let act = e.moe_gate_up_silu8_q8(
4825 crate::WPtr8(gp),
4826 crate::WPtr8(up),
4827 zq,
4828 zd,
4829 n_embd,
4830 n_ff_exp,
4831 n_used,
4832 m.gate_exps.qtype,
4833 m.up_exps.qtype,
4834 m.gate_exps.row_bytes,
4835 m.up_exps.row_bytes,
4836 )?;
4837 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
4838 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
4839 e.moe_down8_fma_q8(
4840 crate::WPtr8(dp),
4841 crate::F32x8(wv),
4842 &aq2,
4843 &ad2,
4844 &mut dst,
4845 n_ff_exp,
4846 n_embd,
4847 n_used,
4848 m.down_exps.qtype,
4849 m.down_exps.row_bytes,
4850 )?;
4851 continue;
4852 }
4853 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
4854 if tok_q8.is_none() {
4855 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
4856 }
4857 let (zq, zd) = tok_q8.as_ref().unwrap();
4858 if Self::moe_gdec_token_q8(
4859 e,
4860 m,
4861 il,
4862 max_block,
4863 zq,
4864 zd,
4865 sel,
4866 w,
4867 &mut moe_out,
4868 tok,
4869 n_embd,
4870 n_ff_exp,
4871 n_used,
4872 )? {
4873 continue;
4874 }
4875 } else if gdec_may_fire
4876 && cfg.m3.is_none()
4877 && no_macros
4878 && Self::moe_gdec_token(
4879 e,
4880 m,
4881 il,
4882 max_block,
4883 &zt,
4884 sel,
4885 w,
4886 &mut moe_out,
4887 tok,
4888 n_embd,
4889 n_ff_exp,
4890 n_used,
4891 )?
4892 {
4893 continue;
4894 }
4895
4896 if gdec_may_fire || slab_fused_may_fire {
4902 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
4903 e.memset_zeros_view(&mut row)?;
4904 }
4905
4906 let mut cpu_mask = vec![false; sel.len()];
4912 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
4913 let gpu_resident = if use_cache {
4914 e.with_moe_cache(max_block, |cache, _| {
4915 Ok(sel
4916 .iter()
4917 .map(|&expert| {
4918 let expert = expert as u16;
4919 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
4920 .into_iter()
4921 .filter(|&projection| {
4922 cache
4923 .resident(BlockId::new(il, projection, expert))
4924 .is_some()
4925 })
4926 .count()
4927 })
4928 .collect::<Vec<_>>())
4929 })?
4930 } else {
4931 vec![0; sel.len()]
4932 };
4933 let mut cpu_selected = Vec::new();
4934 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
4935 if gpu_resident[index] != 3 {
4936 cpu_mask[index] = true;
4937 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
4938 let expert = expert as usize;
4939 cpu_selected.push((expert, route_weight));
4940 }
4941 }
4942 if crate::cpu_experts::predictor_enabled() {
4943 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
4947 crate::cpu_experts::predictor_submit(il, row);
4948 }
4949 if cpu_selected.is_empty() {
4950 None
4951 } else {
4952 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
4953 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
4954 .map_err(std::io::Error::other)?;
4955 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
4956 }
4957 } else {
4958 None
4959 };
4960
4961 let worker_window = worker_disk_prefetch
4962 .then(worker_prefetch_window)
4963 .unwrap_or(0);
4964 for (j, &ex) in sel.iter().enumerate() {
4965 if cpu_mask[j] {
4966 continue;
4967 }
4968 let ex = ex as usize;
4969 if let Some(d) = slab_local {
4976 let gl = m.gate_exps.expert_layout(ex);
4977 let ul = m.up_exps.expert_layout(ex);
4978 let dl = m.down_exps.expert_layout(ex);
4979 let (g0, u0, d0) = (
4980 ex * m.gate_exps.expert_stride,
4981 ex * m.up_exps.expert_stride,
4982 ex * m.down_exps.expert_stride,
4983 );
4984 let (gate, up) = if moe_q8 {
4985 if tok_q8.is_none() {
4986 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
4987 }
4988 let (zq, zd) = tok_q8.as_ref().unwrap();
4989 (
4990 e.qmatvec_expert_q8(
4991 &d.gate,
4992 g0..g0 + gl.len,
4993 zq,
4994 zd,
4995 1,
4996 m.gate_exps.in_f,
4997 m.gate_exps.out_f,
4998 gl.qtype,
4999 gl.row_bytes,
5000 )?,
5001 e.qmatvec_expert_q8(
5002 &d.up,
5003 u0..u0 + ul.len,
5004 zq,
5005 zd,
5006 1,
5007 m.up_exps.in_f,
5008 m.up_exps.out_f,
5009 ul.qtype,
5010 ul.row_bytes,
5011 )?,
5012 )
5013 } else {
5014 (
5015 e.qmatvec_view(
5016 &d.gate,
5017 g0..g0 + gl.len,
5018 &zt,
5019 1,
5020 m.gate_exps.in_f,
5021 m.gate_exps.out_f,
5022 gl.qtype,
5023 gl.row_bytes,
5024 )?,
5025 e.qmatvec_view(
5026 &d.up,
5027 u0..u0 + ul.len,
5028 &zt,
5029 1,
5030 m.up_exps.in_f,
5031 m.up_exps.out_f,
5032 ul.qtype,
5033 ul.row_bytes,
5034 )?,
5035 )
5036 };
5037 let mut act = e.uninit(n_ff_exp)?;
5038 Self::ffn_act_lim(
5039 e,
5040 cfg,
5041 &gate,
5042 &up,
5043 m.gate_exps.macro_scale(ex),
5044 m.up_exps.macro_scale(ex),
5045 lim_exp,
5046 &mut act,
5047 n_ff_exp,
5048 )?;
5049 let y = if moe_q8 {
5050 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
5051 e.qmatvec_expert_q8(
5052 &d.down,
5053 d0..d0 + dl.len,
5054 &aq2,
5055 &ad2,
5056 1,
5057 m.down_exps.in_f,
5058 m.down_exps.out_f,
5059 dl.qtype,
5060 dl.row_bytes,
5061 )?
5062 } else {
5063 let actv = act.slice(0..n_ff_exp);
5064 e.qmatvec_view(
5065 &d.down,
5066 d0..d0 + dl.len,
5067 &actv,
5068 1,
5069 m.down_exps.in_f,
5070 m.down_exps.out_f,
5071 dl.qtype,
5072 dl.row_bytes,
5073 )?
5074 };
5075 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5076 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
5077 continue;
5078 }
5079 for next in page_prefetch_positions(j, sel.len(), page_window) {
5080 Self::moe_prefetch_host_expert(sel[next] as usize, m);
5081 }
5082 let keep = [
5083 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
5084 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
5085 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
5086 ];
5087 if worker_disk_prefetch && worker_window > 0 {
5088 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
5089 Self::moe_prefetch_disk_expert(
5090 e,
5091 il,
5092 sel[next] as usize,
5093 m,
5094 max_block,
5095 &keep,
5096 )?;
5097 }
5098 } else if cache_dispatch
5099 && !cpu_hybrid
5100 && moe_prefetch_enabled()
5101 && j + 1 < sel.len()
5102 {
5103 let next = sel[j + 1] as usize;
5104 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
5105 }
5106 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
5107 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
5108 if (gate_q8 || up_q8) && tok_q8.is_none() {
5111 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
5112 }
5113 let gate = if gate_q8 {
5114 let (zq, zd) = tok_q8.as_ref().unwrap();
5115 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
5116 } else {
5117 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
5118 };
5119 let up = if up_q8 {
5120 let (zq, zd) = tok_q8.as_ref().unwrap();
5121 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
5122 } else {
5123 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
5124 };
5125 let mut act = e.uninit(n_ff_exp)?;
5126 Self::ffn_act_lim(
5127 e,
5128 cfg,
5129 &gate,
5130 &up,
5131 m.gate_exps.macro_scale(ex),
5132 m.up_exps.macro_scale(ex),
5133 lim_exp,
5134 &mut act,
5135 n_ff_exp,
5136 )?;
5137 let y = if down_q8 {
5138 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
5139 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
5140 } else {
5141 let actv = act.slice(0..n_ff_exp);
5142 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
5143 };
5144 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5145 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
5147 } else if cache_dispatch {
5148 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
5153 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
5154 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
5156 e,
5157 cfg,
5158 &gate,
5159 &up,
5160 m.gate_exps.macro_scale(ex),
5161 m.up_exps.macro_scale(ex),
5162 lim_exp,
5163 &mut act,
5164 n_ff_exp,
5165 )?;
5166 let actv = act.slice(0..n_ff_exp);
5167 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
5168 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5169 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
5171 } else if cache_frozen {
5172 let gate = Self::moe_frozen_gemm(
5177 e,
5178 il,
5179 PROJ_GATE,
5180 ex,
5181 m,
5182 max_block,
5183 &zt,
5184 &mut scratch_g,
5185 g_len,
5186 )?;
5187 let up = Self::moe_frozen_gemm(
5188 e,
5189 il,
5190 PROJ_UP,
5191 ex,
5192 m,
5193 max_block,
5194 &zt,
5195 &mut scratch_u,
5196 u_len,
5197 )?;
5198 let mut act = e.uninit(n_ff_exp)?;
5199 Self::ffn_act_lim(
5200 e,
5201 cfg,
5202 &gate,
5203 &up,
5204 m.gate_exps.macro_scale(ex),
5205 m.up_exps.macro_scale(ex),
5206 lim_exp,
5207 &mut act,
5208 n_ff_exp,
5209 )?;
5210 let actv = act.slice(0..n_ff_exp);
5211 let y = Self::moe_frozen_gemm(
5212 e,
5213 il,
5214 PROJ_DOWN,
5215 ex,
5216 m,
5217 max_block,
5218 &actv,
5219 &mut scratch_d,
5220 d_len,
5221 )?;
5222 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5223 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
5224 } else {
5225 if scratch_g.is_none() {
5229 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
5230 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
5231 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
5232 }
5233 let (sg, su, sd) = (
5234 scratch_g.as_mut().unwrap(),
5235 scratch_u.as_mut().unwrap(),
5236 scratch_d.as_mut().unwrap(),
5237 );
5238 let gl = m.gate_exps.expert_layout(ex);
5239 let ul = m.up_exps.expert_layout(ex);
5240 let dl = m.down_exps.expert_layout(ex);
5241 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
5242 let gate = e.qmatvec_view(
5243 sg,
5244 0..gl.len,
5245 &zt,
5246 1,
5247 m.gate_exps.in_f,
5248 m.gate_exps.out_f,
5249 gl.qtype,
5250 gl.row_bytes,
5251 )?;
5252
5253 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
5254 let up = e.qmatvec_view(
5255 su,
5256 0..ul.len,
5257 &zt,
5258 1,
5259 m.up_exps.in_f,
5260 m.up_exps.out_f,
5261 ul.qtype,
5262 ul.row_bytes,
5263 )?;
5264
5265 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
5267 e,
5268 cfg,
5269 &gate,
5270 &up,
5271 m.gate_exps.macro_scale(ex),
5272 m.up_exps.macro_scale(ex),
5273 lim_exp,
5274 &mut act,
5275 n_ff_exp,
5276 )?;
5277
5278 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
5279 let actv = act.slice(0..n_ff_exp);
5280 let y = e.qmatvec_view(
5281 sd,
5282 0..dl.len,
5283 &actv,
5284 1,
5285 m.down_exps.in_f,
5286 m.down_exps.out_f,
5287 dl.qtype,
5288 dl.row_bytes,
5289 )?;
5290
5291 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5292 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
5293 }
5294 }
5295 if let Some(worker) = cpu_worker {
5296 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
5297 let cpu_output = e.htod(&cpu_output)?;
5298 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5299 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
5300 }
5301 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
5302 for (j, &ex) in sel.iter().enumerate() {
5303 if cpu_mask[j] {
5304 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
5305 }
5306 }
5307 }
5308 }
5309
5310 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
5315 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
5316 {
5317 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
5326 let (sg_gate, sg_up) = if t == 1 {
5327 match e.matmul_q8_fused2_x(gate_shexp, up_shexp, z)? {
5328 Some(pair) => pair,
5329 None => (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?),
5330 }
5331 } else if verify_t {
5332 (
5333 e.matmul_decode_exact(gate_shexp, z, t)?,
5334 e.matmul_decode_exact(up_shexp, z, t)?,
5335 )
5336 } else {
5337 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
5339 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
5341 e,
5342 cfg,
5343 &sg_gate,
5344 &sg_up,
5345 1.0,
5346 1.0,
5347 lim_shexp,
5348 &mut sa,
5349 t * n_ff_sh,
5350 )?;
5351 let sh = if verify_t {
5352 e.matmul_decode_exact(down_shexp, &sa, t)?
5353 } else {
5354 e.matmul(down_shexp, &sa, t)?
5355 }; let g = match &m.gate_inp_shexp {
5369 Some(gate_inp_shexp) => {
5370 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
5371 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
5372 } else {
5373 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
5374 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
5376 g
5377 }
5378 }
5379 None => e.htod(&vec![1.0f32; t])?,
5380 };
5381 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
5383 }
5384
5385 Ok(moe_out)
5386 }
5387
5388 pub fn stage1_h2d_per_token(&self) -> u64 {
5391 use crate::hybrid::Ffn;
5392 let n_used = self
5393 .cfg
5394 .moe
5395 .as_ref()
5396 .map(|m| m.expert_used_count as u64)
5397 .unwrap_or(0);
5398 let mut bytes = 0u64;
5399 for l in self.layers.iter() {
5400 if let Ffn::Moe(m) = &l.ffn {
5401 bytes += n_used
5402 * (m.gate_exps.max_expert_bytes()
5403 + m.up_exps.max_expert_bytes()
5404 + m.down_exps.max_expert_bytes()) as u64;
5405 }
5406 }
5407 bytes
5408 }
5409
5410 pub(crate) fn max_moe_block(&self) -> usize {
5414 use crate::hybrid::Ffn;
5415 let mut mx = 0usize;
5416 let mut scan = |ffn: &Ffn| {
5417 if let Ffn::Moe(m) = ffn {
5418 mx = mx
5419 .max(m.gate_exps.max_expert_bytes())
5420 .max(m.up_exps.max_expert_bytes())
5421 .max(m.down_exps.max_expert_bytes());
5422 }
5423 };
5424 for l in self.layers.iter() {
5425 scan(&l.ffn);
5426 }
5427 if let Some(mtp) = self.mtp.as_ref() {
5428 scan(&mtp.ffn);
5429 }
5430 mx
5431 }
5432
5433 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
5436 use crate::hybrid::Ffn;
5437 let mut sizes = Vec::new();
5438 let mut scan = |ffn: &Ffn| {
5439 let Ffn::Moe(m) = ffn else { return };
5440 for ex in 0..m.gate_exps.n_expert {
5441 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
5442 continue;
5443 }
5444 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
5445 let len = exps.expert_layout(ex).len;
5446 if len > 0 {
5447 sizes.push(len);
5448 }
5449 }
5450 }
5451 };
5452 for layer in &self.layers {
5453 scan(&layer.ffn);
5454 }
5455 if let Some(mtp) = &self.mtp {
5456 scan(&mtp.ffn);
5457 }
5458 sizes
5459 }
5460
5461 pub fn save_cpu_expert_residency_profile(
5467 &self,
5468 e: &Engine,
5469 path: &std::path::Path,
5470 ) -> Result<(), Box<dyn std::error::Error>> {
5471 let Some(ids) = e.export_moe_residency() else {
5472 return Err("no MoE residency cache to persist".into());
5473 };
5474 let mut body = format!(
5475 "memra-freeze-profile v1 max_block={} blocks={}\n",
5476 self.max_moe_block(),
5477 ids.len()
5478 );
5479 for (layer, proj, ex) in &ids {
5480 body.push_str(&format!("{layer} {proj} {ex}\n"));
5481 }
5482 let tmp = path.with_extension("tmp");
5483 std::fs::write(&tmp, body)?;
5484 std::fs::rename(&tmp, path)?;
5485 println!(
5486 "[moe-cache] freeze profile saved: {} blocks -> {}",
5487 ids.len(),
5488 path.display()
5489 );
5490 Ok(())
5491 }
5492
5493 pub fn restore_cpu_expert_residency_profile(
5497 &self,
5498 e: &Engine,
5499 path: &std::path::Path,
5500 ) -> Result<bool, Box<dyn std::error::Error>> {
5501 use crate::hybrid::Ffn;
5502 use crate::moe_cache::BlockId;
5503 let Ok(content) = std::fs::read_to_string(path) else {
5504 return Ok(false);
5505 };
5506 let mut lines = content.lines();
5507 let Some(header) = lines.next() else {
5508 return Ok(false);
5509 };
5510 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
5511 if !header.starts_with(&expected) {
5512 println!(
5513 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
5514 path.display()
5515 );
5516 return Ok(false);
5517 }
5518 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
5519 std::collections::HashMap::new();
5520 for line in lines {
5521 let mut fields = line.split_whitespace();
5522 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
5523 else {
5524 continue;
5525 };
5526 let (Ok(layer), Ok(proj), Ok(ex)) =
5527 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
5528 else {
5529 continue;
5530 };
5531 by_layer
5532 .entry(layer)
5533 .or_default()
5534 .push(BlockId::new(layer, proj, ex));
5535 }
5536 let requested: usize = by_layer.values().map(Vec::len).sum();
5537 if requested == 0 {
5538 return Ok(false);
5539 }
5540 let max_block = self.max_moe_block();
5541 let mut restaged = 0usize;
5542 let mut stage_layer =
5543 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
5544 let Ffn::Moe(m) = ffn else { return Ok(()) };
5545 let Some(ids) = by_layer.get(&layer_index) else {
5546 return Ok(());
5547 };
5548 e.with_moe_cache(max_block, |cache, eng| {
5549 for id in ids {
5550 if cache.restage_block(*id, m, eng)? {
5551 restaged += 1;
5552 }
5553 }
5554 Ok(())
5555 })
5556 };
5557 for (index, layer) in self.layers.iter().enumerate() {
5558 stage_layer(index as u16, &layer.ffn)?;
5559 }
5560 if let Some(mtp) = self.mtp.as_ref() {
5561 stage_layer(u16::MAX, &mtp.ffn)?;
5562 }
5563 e.freeze_moe_cache();
5564 println!(
5565 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
5566 path.display()
5567 );
5568 Ok(true)
5569 }
5570
5571 pub fn freeze_cpu_expert_residency(
5573 &self,
5574 e: &Engine,
5575 ) -> Result<(), Box<dyn std::error::Error>> {
5576 e.freeze_moe_cache();
5577 Ok(())
5578 }
5579
5580 pub fn ffn_act(
5588 e: &Engine,
5589 cfg: &ModelConfig,
5590 gate: &CudaSlice<f32>,
5591 up: &CudaSlice<f32>,
5592 act: &mut CudaSlice<f32>,
5593 n: usize,
5594 ) -> Result<(), Box<dyn std::error::Error>> {
5595 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
5596 }
5597
5598 #[allow(clippy::too_many_arguments)]
5602 pub(crate) fn ffn_act_scaled(
5603 e: &Engine,
5604 cfg: &ModelConfig,
5605 gate: &CudaSlice<f32>,
5606 up: &CudaSlice<f32>,
5607 gs: f32,
5608 us: f32,
5609 act: &mut CudaSlice<f32>,
5610 n: usize,
5611 ) -> Result<(), Box<dyn std::error::Error>> {
5612 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
5613 }
5614
5615 #[allow(clippy::too_many_arguments)]
5624 pub(crate) fn ffn_act_lim(
5625 e: &Engine,
5626 cfg: &ModelConfig,
5627 gate: &CudaSlice<f32>,
5628 up: &CudaSlice<f32>,
5629 gs: f32,
5630 us: f32,
5631 limit: Option<f32>,
5632 act: &mut CudaSlice<f32>,
5633 n: usize,
5634 ) -> Result<(), Box<dyn std::error::Error>> {
5635 if let Some(m3) = cfg.m3.as_ref() {
5636 debug_assert!(
5637 limit.is_none(),
5638 "m3 swigluoai and step35 clamp are different archs"
5639 );
5640 return e.swigluoai_mul_scaled(
5641 gate,
5642 up,
5643 gs,
5644 us,
5645 m3.swiglu_alpha,
5646 m3.swiglu_limit,
5647 act,
5648 n,
5649 );
5650 }
5651 if let Some(l) = limit {
5652 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
5653 }
5654 if gs == 1.0 && us == 1.0 {
5655 return e.silu_mul(gate, up, act, n);
5656 }
5657 e.silu_mul_scaled(gate, up, gs, us, act, n)
5658 }
5659
5660 fn moe_route(
5666 e: &Engine,
5667 logits: &CudaSlice<f32>,
5668 t: usize,
5669 n_expert: usize,
5670 n_used: usize,
5671 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
5672 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
5673 }
5674
5675 #[allow(clippy::too_many_arguments)]
5683 fn moe_route_sigmoid_cfg(
5684 e: &Engine,
5685 logits: &CudaSlice<f32>,
5686 t: usize,
5687 n_expert: usize,
5688 n_used: usize,
5689 m: &MoeWeights,
5690 (sf, route_norm): (f32, bool),
5691 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
5692 if sigmoid_router_enabled() {
5693 return e.moe_router_sigmoid_topk_host(
5694 logits,
5695 t,
5696 n_expert,
5697 n_used,
5698 m.active_count(),
5699 &m.exp_probs_b_dev,
5700 &m.active_experts_dev,
5701 sf,
5702 route_norm,
5703 );
5704 }
5705 let lg = e.dtoh(logits)?;
5706 Self::moe_route_sigmoid_host(
5707 &lg,
5708 t,
5709 n_expert,
5710 n_used,
5711 m.exp_probs_b.as_deref(),
5712 sf,
5713 route_norm,
5714 m.active_experts.as_deref(),
5715 )
5716 }
5717
5718 fn moe_route_cfg(
5721 e: &Engine,
5722 logits: &CudaSlice<f32>,
5723 t: usize,
5724 n_expert: usize,
5725 n_used: usize,
5726 active: Option<&[bool]>,
5727 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
5728 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
5731 return e.moe_router_topk_host(logits, t, n_expert, n_used);
5732 }
5733 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
5736 let mut w_out = vec![0f32; t * n_used];
5737 for tok in 0..t {
5738 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
5739 let maxl = row
5741 .iter()
5742 .enumerate()
5743 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
5744 .map(|(_, &x)| x)
5745 .fold(f32::NEG_INFINITY, f32::max);
5746 let mut probs = vec![0f32; n_expert];
5747 let mut den = 0f32;
5748 for i in 0..n_expert {
5749 if active.is_some_and(|mask| !mask[i]) {
5750 continue;
5751 }
5752 let x = (row[i] - maxl).exp();
5753 probs[i] = x;
5754 den += x;
5755 }
5756 for p in probs.iter_mut() {
5757 *p /= den;
5758 }
5759 let mut idx: Vec<usize> = (0..n_expert)
5761 .filter(|&i| active.is_none_or(|mask| mask[i]))
5762 .collect();
5763 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
5764 let sl = &idx[..n_used];
5765 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
5766 let mut ws: f32 = wv.iter().sum();
5767 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
5769 *x /= ws;
5770 }
5771 for j in 0..n_used {
5772 sel[tok * n_used + j] = sl[j] as u32;
5773 w_out[tok * n_used + j] = wv[j];
5774 }
5775 }
5776 Ok((sel, w_out))
5777 }
5778
5779 #[allow(clippy::too_many_arguments)]
5780 fn moe_route_sigmoid_with_input(
5781 e: &Engine,
5782 logits: &CudaSlice<f32>,
5783 input: &CudaSlice<f32>,
5784 t: usize,
5785 n_expert: usize,
5786 n_used: usize,
5787 bias: Option<&[f32]>,
5788 (sf, route_norm): (f32, bool),
5789 active: Option<&[bool]>,
5790 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
5791 let (lg, input) = e.dtoh_pair(logits, input)?;
5792 let (sel, w) =
5793 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
5794 Ok((sel, w, input))
5795 }
5796
5797 pub fn start_moe_prefetch_predictor(
5802 &self,
5803 e: &Engine,
5804 cfg: &ModelConfig,
5805 ) -> Result<(), Box<dyn std::error::Error>> {
5806 use crate::hybrid::Ffn;
5807 let Some(sig) = cfg.sigmoid_router() else {
5808 return Err("prefetch predictor requires a sigmoid-router arch".into());
5809 };
5810 let resident: std::collections::HashSet<(u16, u8, u16)> = e
5811 .export_moe_residency()
5812 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
5813 .into_iter()
5814 .collect();
5815 let mut layers = Vec::new();
5816 for (index, layer) in self.layers.iter().enumerate() {
5817 let Ffn::Moe(m) = &layer.ffn else { continue };
5818 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
5819 continue;
5820 };
5821 let router = e.dtoh(data)?;
5822 let n_expert = m.gate_exps.n_expert;
5823 let n_embd = m.gate_exps.in_f;
5824 if router.len() != n_embd * n_expert {
5825 continue;
5826 }
5827 let build = |exps: &crate::model::HostExps| {
5828 (0..n_expert)
5829 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
5830 .collect::<Vec<_>>()
5831 };
5832 layers.push((
5833 index as u16,
5834 crate::cpu_experts::PredictLayerInit {
5835 router,
5836 bias: m.exp_probs_b.clone(),
5837 active: m.active_experts.clone(),
5838 n_embd,
5839 n_used: cfg
5840 .moe
5841 .as_ref()
5842 .map(|moe| moe.expert_used_count as usize)
5843 .ok_or("prefetch predictor requires MoE config")?,
5844 sig,
5845 weights_n_expert: n_expert,
5846 gate: build(&m.gate_exps),
5847 up: build(&m.up_exps),
5848 down: build(&m.down_exps),
5849 },
5850 ));
5851 }
5852 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
5853 }
5854
5855 #[allow(clippy::too_many_arguments)]
5858 pub fn moe_route_sigmoid_host_public(
5859 logits: &[f32],
5860 t: usize,
5861 n_expert: usize,
5862 n_used: usize,
5863 bias: Option<&[f32]>,
5864 sf: f32,
5865 route_norm: bool,
5866 active: Option<&[bool]>,
5867 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
5868 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
5869 }
5870
5871 #[allow(clippy::too_many_arguments)]
5872 fn moe_route_sigmoid_host(
5873 lg: &[f32],
5874 t: usize,
5875 n_expert: usize,
5876 n_used: usize,
5877 bias: Option<&[f32]>,
5878 sf: f32,
5879 route_norm: bool,
5880 active: Option<&[bool]>,
5881 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
5882 let active_count = active
5883 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
5884 .unwrap_or(n_expert);
5885 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
5886 if lg.len() != t * n_expert {
5887 return Err(format!(
5888 "sigmoid router logits length mismatch: got {}, expected {}",
5889 lg.len(),
5890 t * n_expert,
5891 )
5892 .into());
5893 }
5894 let mut sel = vec![0u32; t * n_used];
5895 let mut w_out = vec![0f32; t * n_used];
5896 for tok in 0..t {
5897 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
5898 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
5899 let selsc: Vec<f32> = match bias {
5901 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
5902 None => scores.clone(),
5903 };
5904 let mut idx: Vec<usize> = (0..n_expert)
5905 .filter(|&i| active.is_none_or(|mask| mask[i]))
5906 .collect();
5907 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
5908 let sl = &idx[..n_used];
5909 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
5910 if route_norm {
5911 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
5912 for x in wv.iter_mut() {
5913 *x = *x / ws * sf;
5914 }
5915 } else {
5916 for x in wv.iter_mut() {
5917 *x *= sf;
5918 }
5919 }
5920 for j in 0..n_used {
5921 sel[tok * n_used + j] = sl[j] as u32;
5922 w_out[tok * n_used + j] = wv[j];
5923 }
5924 }
5925 Ok((sel, w_out))
5926 }
5927
5928 #[allow(clippy::too_many_arguments)]
5932 fn moe_ffn_sigmoid_dev(
5933 e: &Engine,
5934 m: &MoeWeights,
5935 z: &CudaSlice<f32>,
5936 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5937 logits: &CudaSlice<f32>,
5938 t: usize,
5939 cfg: &ModelConfig,
5940 il: u16,
5941 (scaling_factor, route_norm): (f32, bool),
5942 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5943 let moe = cfg.moe.as_ref().unwrap();
5944 let n_embd = cfg.n_embd as usize;
5945 let n_expert = moe.expert_count as usize;
5946 let n_used = moe.expert_used_count as usize;
5947 let n_ff_exp = moe.expert_ff_length as usize;
5948 let dev = m.dev_exps.as_ref().unwrap();
5949 debug_assert!(cfg.step35.is_some());
5950 debug_assert_eq!(dev.dev, e.ctx().ordinal());
5951 debug_assert!(m.has_uniform_expert_layout());
5952 debug_assert!(!m.has_macros);
5953
5954 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
5955 logits,
5956 t,
5957 n_expert,
5958 n_used,
5959 m.active_count(),
5960 &m.exp_probs_b_dev,
5961 &m.active_experts_dev,
5962 scaling_factor,
5963 route_norm,
5964 )?;
5965 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
5966 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
5967 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
5968 (combined, combined)
5969 } else {
5970 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
5971 };
5972 let (zq, zd) = match (t, zq8) {
5973 (1, Some((q, d))) => (q.clone(), d.clone()),
5974 _ => e.quantize_q8_1(z, t, n_embd)?,
5975 };
5976 let n_pairs = t * n_used;
5977 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
5978 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
5982 let pair_tok_d = e.htod_i32(&pair_tok)?;
5983 let gate = e.moe_pairs_matvec_q8(
5984 &dev.ptr_row,
5985 0,
5986 &pair_tok_d,
5987 &sel_d,
5988 &zq,
5989 &zd,
5990 n_embd,
5991 n_ff_exp,
5992 n_expert,
5993 n_pairs,
5994 m.gate_exps.qtype,
5995 gate_row_bytes,
5996 )?;
5997 let up = e.moe_pairs_matvec_q8(
5998 &dev.ptr_row,
5999 1,
6000 &pair_tok_d,
6001 &sel_d,
6002 &zq,
6003 &zd,
6004 n_embd,
6005 n_ff_exp,
6006 n_expert,
6007 n_pairs,
6008 m.up_exps.qtype,
6009 up_row_bytes,
6010 )?;
6011 let mut act = e.uninit(n_pairs * n_ff_exp)?;
6012 Self::ffn_act_lim(
6013 e,
6014 cfg,
6015 &gate,
6016 &up,
6017 1.0,
6018 1.0,
6019 cfg.clamp_exp_at(il as u32),
6020 &mut act,
6021 n_pairs * n_ff_exp,
6022 )?;
6023 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
6024 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
6025 let pair_self_d = e.htod_i32(&pair_self)?;
6026 let down = e.moe_pairs_matvec_q8(
6027 &dev.ptr_row,
6028 2,
6029 &pair_self_d,
6030 &sel_d,
6031 &aq2,
6032 &ad2,
6033 n_ff_exp,
6034 n_embd,
6035 n_expert,
6036 n_pairs,
6037 m.down_exps.qtype,
6038 m.down_exps.row_bytes,
6039 )?;
6040 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
6041 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
6042 let tok_off_d = e.htod_i32(&tok_off)?;
6043 let tok_ids_d = e.htod_i32(&tok_ids)?;
6044 let mut output = e.uninit(t * n_embd)?;
6045 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
6046 output
6047 } else {
6048 let act = e.moe_gate_up_silu8_dev_q8_rows(
6049 &dev.ptr_row,
6050 &sel_d,
6051 &zq,
6052 &zd,
6053 t,
6054 n_embd,
6055 n_ff_exp,
6056 n_used,
6057 n_expert,
6058 m.gate_exps.qtype,
6059 m.up_exps.qtype,
6060 gate_row_bytes,
6061 up_row_bytes,
6062 &m.dev_macros,
6063 )?;
6064 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
6065 let mut output = e.uninit(t * n_embd)?;
6066 e.moe_down8_fma_dev_q8_rows_g(
6067 &dev.ptr_row,
6068 &sel_d,
6069 &w_d,
6070 &aq2,
6071 &ad2,
6072 &mut output,
6073 t,
6074 n_ff_exp,
6075 n_embd,
6076 n_used,
6077 n_expert,
6078 m.down_exps.qtype,
6079 m.down_exps.row_bytes,
6080 )?;
6081 output
6082 };
6083
6084 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
6085 eprintln!(
6086 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
6087 cfg.clamp_exp_at(il as u32).is_some(),
6088 dev.gu_il,
6089 );
6090 }
6091 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
6092 Ok(moe_out)
6093 }
6094
6095 fn moe_ffn_pairs(
6104 e: &Engine,
6105 m: &MoeWeights,
6106 z: &CudaSlice<f32>,
6107 logits: &CudaSlice<f32>,
6108 t: usize,
6109 cfg: &ModelConfig,
6110 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6111 let moe = cfg.moe.as_ref().unwrap();
6112 let n_embd = cfg.n_embd as usize;
6113 let n_expert = moe.expert_count as usize;
6114 let n_used = moe.expert_used_count as usize;
6115 let n_ff_exp = moe.expert_ff_length as usize;
6116 debug_assert!(
6121 !cfg.swiglu_clamped_anywhere(),
6122 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
6123 );
6124 let dev = m.dev_exps.as_ref().unwrap();
6125 let (rbg_d, rbu_d) = if dev.gu_il {
6127 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
6128 (sxx, sxx)
6129 } else {
6130 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
6131 };
6132
6133 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
6134 let n_pairs = t * n_used;
6135 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
6138 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
6139 let pair_w: Vec<f32> = w_all.clone();
6140 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
6141 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
6142 let pt = e.htod_i32(&pair_tok)?;
6143 let px = e.htod_i32(&pair_ex)?;
6144 let pw = e.htod(&pair_w)?;
6145 let toff = e.htod_i32(&tok_off)?;
6146 let tids = e.htod_i32(&tok_ids)?;
6147
6148 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
6152 for p in 0..n_pairs {
6153 by_ex[pair_ex[p] as usize].push(p as i32);
6154 }
6155 let mut ex_ids: Vec<i32> = Vec::new();
6156 let mut ex_off: Vec<i32> = vec![0];
6157 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
6158 for (ex, list) in by_ex.iter().enumerate() {
6159 if list.is_empty() {
6160 continue;
6161 }
6162 ex_ids.push(ex as i32);
6163 ex_pairs.extend_from_slice(list);
6164 ex_off.push(ex_pairs.len() as i32);
6165 }
6166 let n_active = ex_ids.len();
6167 let exi = e.htod_i32(&ex_ids)?;
6168 let exo = e.htod_i32(&ex_off)?;
6169 let exp_d = e.htod_i32(&ex_pairs)?;
6170 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6191 let mma_t = *MMA_T.get_or_init(|| {
6192 std::env::var("MEMRA_MOE_MMA_T")
6193 .ok()
6194 .and_then(|v| v.parse().ok())
6195 .unwrap_or(16)
6196 });
6197 let use_mma = std::env::var("MEMRA_MOE_MMA")
6198 .map(|v| v != "0")
6199 .unwrap_or(true)
6200 && t >= mma_t
6201 && q8_expert_dec_supported(m.gate_exps.qtype)
6202 && q8_expert_dec_supported(m.up_exps.qtype)
6203 && q8_expert_dec_supported(m.down_exps.qtype)
6204 && n_embd % 256 == 0
6205 && n_ff_exp % 256 == 0;
6206 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
6222 && q8_expert_dec_supported(m.up_exps.qtype)
6223 && q8_expert_dec_supported(m.down_exps.qtype)
6224 && n_embd % 256 == 0
6225 && n_ff_exp % 256 == 0;
6226 let f16g_mode = crate::moe_f16g_mode();
6227 let f16g = f16g_mode != 0
6228 && t >= mma_t
6229 && (f16g_mode != 3 || !mma_capable)
6230 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
6231 && f16g_proj_ok(m.up_exps.qtype, n_embd)
6232 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
6233 if use_mma || f16g {
6234 let y_down = if f16g {
6242 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
6246 let csr_tok_d = e.htod_i32(&csr_tok)?;
6247 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
6248 let g_csr = e.moe_f16_grouped(
6249 &dev.ptr_row,
6250 0,
6251 n_expert,
6252 &exi,
6253 &ex_off,
6254 &exo,
6255 &z_f16,
6256 &z_s,
6257 n_embd,
6258 n_ff_exp,
6259 n_active,
6260 n_pairs,
6261 m.gate_exps.qtype,
6262 rbg_d,
6263 )?;
6264 let u_csr = e.moe_f16_grouped(
6265 &dev.ptr_row,
6266 1,
6267 n_expert,
6268 &exi,
6269 &ex_off,
6270 &exo,
6271 &z_f16,
6272 &z_s,
6273 n_embd,
6274 n_ff_exp,
6275 n_active,
6276 n_pairs,
6277 m.up_exps.qtype,
6278 rbu_d,
6279 )?;
6280 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
6281 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
6282 let d_csr = e.moe_f16_grouped(
6283 &dev.ptr_row,
6284 2,
6285 n_expert,
6286 &exi,
6287 &ex_off,
6288 &exo,
6289 &a_f16,
6290 &a_s,
6291 n_ff_exp,
6292 n_embd,
6293 n_active,
6294 n_pairs,
6295 m.down_exps.qtype,
6296 m.down_exps.row_bytes,
6297 )?;
6298 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
6299 } else {
6300 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
6302 let gate = e.mmq_iq_experts(
6303 &dev.ptr_row,
6304 0,
6305 n_expert,
6306 &exi,
6307 &exo,
6308 &exp_d,
6309 &pt,
6310 &z_scr,
6311 n_embd,
6312 n_ff_exp,
6313 n_active,
6314 n_pairs,
6315 t,
6316 m.gate_exps.qtype,
6317 rbg_d,
6318 )?;
6319 let up = e.mmq_iq_experts(
6320 &dev.ptr_row,
6321 1,
6322 n_expert,
6323 &exi,
6324 &exo,
6325 &exp_d,
6326 &pt,
6327 &z_scr,
6328 n_embd,
6329 n_ff_exp,
6330 n_active,
6331 n_pairs,
6332 t,
6333 m.up_exps.qtype,
6334 rbu_d,
6335 )?;
6336 let a_scr = if crate::moe_fuse_actq_on() {
6342 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
6343 } else {
6344 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
6345 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
6346 };
6347 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
6348 let pself = e.htod_i32(&pair_self)?;
6349 e.mmq_iq_experts(
6350 &dev.ptr_row,
6351 2,
6352 n_expert,
6353 &exi,
6354 &exo,
6355 &exp_d,
6356 &pself,
6357 &a_scr,
6358 n_ff_exp,
6359 n_embd,
6360 n_active,
6361 n_pairs,
6362 n_pairs,
6363 m.down_exps.qtype,
6364 m.down_exps.row_bytes,
6365 )?
6366 };
6367 let mut moe_out = e.uninit(t * n_embd)?;
6368 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
6369 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6370 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6371 {
6372 let n_ff_sh = gate_shexp.out_features();
6373 let sg_gate = e.matmul(gate_shexp, z, t)?;
6374 let sg_up = e.matmul(up_shexp, z, t)?;
6375 let mut sa = e.uninit(t * n_ff_sh)?;
6376 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
6377 let sh = e.matmul(down_shexp, &sa, t)?;
6378 let g = match &m.gate_inp_shexp {
6384 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
6385 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6386 }
6387 Some(gate_inp_shexp) => {
6388 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6389 let mut g = e.uninit(t)?;
6390 e.sigmoid(&gs, &mut g, t)?;
6391 g
6392 }
6393 None => e.htod(&vec![1.0f32; t])?,
6394 };
6395 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6396 }
6397 return Ok(moe_out);
6398 }
6399
6400 let dec = std::env::var("MEMRA_MOE_DEC")
6403 .map(|v| v != "0")
6404 .unwrap_or(true);
6405 let matvec = |proj,
6406 exi: &_,
6407 exo: &_,
6408 exp_d: &_,
6409 pt: &_,
6410 aq: &_,
6411 ad: &_,
6412 inf,
6413 outf,
6414 qtype,
6415 rb|
6416 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6417 let dec = dec && q8_expert_dec_supported(qtype);
6419 if dec {
6420 e.moe_pairs_matvec_q8_dec(
6421 &dev.ptr_row,
6422 proj,
6423 exi,
6424 exo,
6425 exp_d,
6426 pt,
6427 aq,
6428 ad,
6429 inf,
6430 outf,
6431 n_expert,
6432 n_active,
6433 n_pairs,
6434 qtype,
6435 rb,
6436 )
6437 } else {
6438 e.moe_pairs_matvec_q8_em(
6439 &dev.ptr_row,
6440 proj,
6441 exi,
6442 exo,
6443 exp_d,
6444 pt,
6445 aq,
6446 ad,
6447 inf,
6448 outf,
6449 n_expert,
6450 n_active,
6451 n_pairs,
6452 qtype,
6453 rb,
6454 )
6455 }
6456 };
6457 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
6458 let gate = matvec(
6459 0,
6460 &exi,
6461 &exo,
6462 &exp_d,
6463 &pt,
6464 &zq,
6465 &zd,
6466 n_embd,
6467 n_ff_exp,
6468 m.gate_exps.qtype,
6469 rbg_d,
6470 )?;
6471 let up = matvec(
6472 1,
6473 &exi,
6474 &exo,
6475 &exp_d,
6476 &pt,
6477 &zq,
6478 &zd,
6479 n_embd,
6480 n_ff_exp,
6481 m.up_exps.qtype,
6482 rbu_d,
6483 )?;
6484 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
6485 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
6486 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
6488 let pself = e.htod_i32(&pair_self)?;
6489 let y_down = matvec(
6490 2,
6491 &exi,
6492 &exo,
6493 &exp_d,
6494 &pself,
6495 &aq2,
6496 &ad2,
6497 n_ff_exp,
6498 n_embd,
6499 m.down_exps.qtype,
6500 m.down_exps.row_bytes,
6501 )?;
6502 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
6504
6505 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6509 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6510 {
6511 let n_ff_sh = gate_shexp.out_features();
6512 let step_exact = cfg.step35.is_some();
6516 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
6517 let (sg_gate, sg_up) = if step_exact && t == 1 {
6518 match e.matmul_q8_fused2_x(gate_shexp, up_shexp, z)? {
6519 Some(pair) => pair,
6520 None => (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?),
6521 }
6522 } else if verify_t {
6523 let mut fused = None;
6524 if crate::spec::spec_fused_t()
6525 && (2..=4).contains(&t)
6526 && e.uses_q8_1_fast(gate_shexp)
6527 && e.uses_q8_1_fast(up_shexp)
6528 {
6529 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
6530 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
6531 }
6532 match fused {
6533 Some(pair) => pair,
6534 None => (
6535 e.matmul_decode_exact(gate_shexp, z, t)?,
6536 e.matmul_decode_exact(up_shexp, z, t)?,
6537 ),
6538 }
6539 } else {
6540 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
6541 };
6542 let mut sa = e.uninit(t * n_ff_sh)?;
6543 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
6544 let sh = if verify_t {
6545 e.matmul_decode_exact(down_shexp, &sa, t)?
6546 } else {
6547 e.matmul(down_shexp, &sa, t)?
6548 };
6549 let g = match &m.gate_inp_shexp {
6554 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
6555 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6556 }
6557 Some(gate_inp_shexp) => {
6558 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6559 let mut g = e.uninit(t)?;
6560 e.sigmoid(&gs, &mut g, t)?;
6561 g
6562 }
6563 None => e.htod(&vec![1.0f32; t])?,
6564 };
6565 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6566 }
6567 Ok(moe_out)
6568 }
6569
6570 #[allow(clippy::too_many_arguments)]
6572 #[allow(clippy::too_many_arguments)]
6573 fn moe_ffn_dev(
6574 e: &Engine,
6575 m: &MoeWeights,
6576 z: &CudaSlice<f32>,
6577 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
6578 logits: &CudaSlice<f32>,
6579 t: usize,
6580 cfg: &ModelConfig,
6581 il: u16,
6582 max_block: usize,
6583 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6584 let moe = cfg.moe.as_ref().unwrap();
6585 let n_embd = cfg.n_embd as usize;
6586 let n_expert = moe.expert_count as usize;
6587 let n_used = moe.expert_used_count as usize;
6588 let n_ff_exp = moe.expert_ff_length as usize;
6589 debug_assert!(
6593 cfg.sigmoid_router().is_none(),
6594 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
6595 );
6596 debug_assert!(
6597 !cfg.swiglu_clamped_at(il as u32),
6598 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
6599 );
6600
6601 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
6603 if m.has_macros {
6606 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
6607 }
6608
6609 let mut moe_out = e.uninit(t * n_embd)?;
6611
6612 if let Some(dev) = m.dev_exps.as_ref() {
6615 let (rbg_d, rbu_d) = if dev.gu_il {
6618 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
6619 (sxx, sxx)
6620 } else {
6621 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
6622 };
6623 let q8 = moe_q8_enabled()
6624 && q8_expert_supported(m.gate_exps.qtype)
6625 && q8_expert_supported(m.up_exps.qtype)
6626 && q8_expert_supported(m.down_exps.qtype);
6627 let rows_arm = q8
6636 && t > 1
6637 && crate::spec::spec_m2()
6638 && n_ff_exp == 512
6639 && n_used <= 8
6640 && std::env::var("MEMRA_MOE_DEVQ8_GU")
6641 .map(|v| v.is_empty() || v == "v")
6642 .unwrap_or(true)
6643 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
6644 .map(|v| v.is_empty() || v == "w8h2v")
6645 .unwrap_or(true);
6646 let csr_mode = std::env::var("MEMRA_MOE_CSR")
6655 .ok()
6656 .and_then(|v| v.parse::<i32>().ok())
6657 .unwrap_or(1);
6658 let csr_qt = |qt: i32| qt == crate::QT_IQ4_XS || qt == crate::QT_IQ3_S;
6659 let csr_arm = rows_arm
6660 && csr_mode > 0
6661 && t <= 10
6662 && csr_qt(m.gate_exps.qtype)
6663 && csr_qt(m.up_exps.qtype)
6664 && csr_qt(m.down_exps.qtype);
6665 if csr_arm {
6666 if csr_mode == 2 {
6667 static ENGAGED: std::sync::Once = std::sync::Once::new();
6668 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
6669 }
6670 let n_pairs = t * n_used;
6671 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
6672 let act = e.moe_gate_up_silu8_dev_q8_csr(
6673 &dev.ptr_row,
6674 &sel_d,
6675 &zq,
6676 &zd,
6677 n_pairs,
6678 n_embd,
6679 n_ff_exp,
6680 n_used,
6681 n_expert,
6682 m.gate_exps.qtype,
6683 m.up_exps.qtype,
6684 rbg_d,
6685 rbu_d,
6686 )?;
6687 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
6688 e.moe_down8_fma_dev_q8_rows(
6692 &dev.ptr_row,
6693 &sel_d,
6694 &w_d,
6695 &aq2,
6696 &ad2,
6697 &mut moe_out,
6698 t,
6699 n_ff_exp,
6700 n_embd,
6701 n_used,
6702 n_expert,
6703 m.down_exps.qtype,
6704 m.down_exps.row_bytes,
6705 )?;
6706 if csr_mode == 2 {
6707 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
6709 &dev.ptr_row,
6710 &sel_d,
6711 &zq,
6712 &zd,
6713 t,
6714 n_embd,
6715 n_ff_exp,
6716 n_used,
6717 n_expert,
6718 m.gate_exps.qtype,
6719 m.up_exps.qtype,
6720 rbg_d,
6721 rbu_d,
6722 &m.dev_macros,
6723 )?;
6724 let mut out_r = e.uninit(t * n_embd)?;
6725 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
6726 e.moe_down8_fma_dev_q8_rows(
6727 &dev.ptr_row,
6728 &sel_d,
6729 &w_d,
6730 &aq2r,
6731 &ad2r,
6732 &mut out_r,
6733 t,
6734 n_ff_exp,
6735 n_embd,
6736 n_used,
6737 n_expert,
6738 m.down_exps.qtype,
6739 m.down_exps.row_bytes,
6740 )?;
6741 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
6742 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
6743 let ba = a1
6744 .iter()
6745 .zip(&a2)
6746 .filter(|(x, y)| x.to_bits() != y.to_bits())
6747 .count();
6748 let bo = o1
6749 .iter()
6750 .zip(&o2)
6751 .filter(|(x, y)| x.to_bits() != y.to_bits())
6752 .count();
6753 if ba + bo > 0 {
6754 eprintln!(
6755 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
6756 a1.len(),
6757 o1.len()
6758 );
6759 let sel_h = e.dtoh_i32(&sel_d)?;
6761 let mut shown = 0;
6762 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
6763 if x.to_bits() != y.to_bits() && shown < 4 {
6764 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
6765 let ex = sel_h[p];
6766 let npx = sel_h.iter().filter(|&&v| v == ex).count();
6767 eprintln!(
6768 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
6769 );
6770 shown += 1;
6771 }
6772 }
6773 std::process::exit(3);
6774 }
6775 }
6776 } else if rows_arm {
6777 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
6780 use std::sync::atomic::{AtomicU64, Ordering};
6781 static PAIRS: AtomicU64 = AtomicU64::new(0);
6782 static UNIQ: AtomicU64 = AtomicU64::new(0);
6783 static CALLS: AtomicU64 = AtomicU64::new(0);
6784 let sel_h = e.dtoh_i32(&sel_d)?;
6785 let mut u: Vec<i32> = sel_h.clone();
6786 u.sort_unstable();
6787 u.dedup();
6788 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
6789 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
6790 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
6791 if c % 480 == 0 {
6792 let p = PAIRS.load(Ordering::Relaxed);
6793 let q = UNIQ.load(Ordering::Relaxed);
6794 eprintln!(
6795 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
6796 q as f64 / p as f64
6797 );
6798 }
6799 }
6800 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
6801 let act = e.moe_gate_up_silu8_dev_q8_rows(
6802 &dev.ptr_row,
6803 &sel_d,
6804 &zq,
6805 &zd,
6806 t,
6807 n_embd,
6808 n_ff_exp,
6809 n_used,
6810 n_expert,
6811 m.gate_exps.qtype,
6812 m.up_exps.qtype,
6813 rbg_d,
6814 rbu_d,
6815 &m.dev_macros,
6816 )?;
6817 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
6818 e.moe_down8_fma_dev_q8_rows(
6819 &dev.ptr_row,
6820 &sel_d,
6821 &w_d,
6822 &aq2,
6823 &ad2,
6824 &mut moe_out,
6825 t,
6826 n_ff_exp,
6827 n_embd,
6828 n_used,
6829 n_expert,
6830 m.down_exps.qtype,
6831 m.down_exps.row_bytes,
6832 )?;
6833 } else {
6834 for tok in 0..t {
6835 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
6836 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
6837 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
6838 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6839 if q8 {
6840 let (zq, zd) = match (t, zq8) {
6841 (1, Some((q, d))) => (q.clone(), d.clone()),
6842 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
6843 };
6844 let act = e.moe_gate_up_silu8_dev_q8(
6845 &dev.ptr_row,
6846 &selt,
6847 &zq,
6848 &zd,
6849 n_embd,
6850 n_ff_exp,
6851 n_used,
6852 n_expert,
6853 m.gate_exps.qtype,
6854 m.up_exps.qtype,
6855 rbg_d,
6856 rbu_d,
6857 &m.dev_macros,
6858 )?;
6859 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
6860 e.moe_down8_fma_dev_q8(
6861 &dev.ptr_row,
6862 &selt,
6863 &wt,
6864 &aq2,
6865 &ad2,
6866 &mut dst,
6867 n_ff_exp,
6868 n_embd,
6869 n_used,
6870 n_expert,
6871 m.down_exps.qtype,
6872 m.down_exps.row_bytes,
6873 )?;
6874 } else {
6875 let act = e.moe_gate_up_silu8_dev(
6876 &dev.ptr_row,
6877 &selt,
6878 &zt,
6879 n_embd,
6880 n_ff_exp,
6881 n_used,
6882 n_expert,
6883 m.gate_exps.qtype,
6884 m.up_exps.qtype,
6885 rbg_d,
6886 rbu_d,
6887 &m.dev_macros,
6888 )?;
6889 e.moe_down8_fma_dev(
6890 &dev.ptr_row,
6891 &selt,
6892 &wt,
6893 &act,
6894 &mut dst,
6895 n_ff_exp,
6896 n_embd,
6897 n_used,
6898 n_expert,
6899 m.down_exps.qtype,
6900 m.down_exps.row_bytes,
6901 )?;
6902 }
6903 }
6904 }
6905 } else {
6906 let q8 = moe_q8_enabled()
6913 && q8_expert_supported(m.gate_exps.qtype)
6914 && q8_expert_supported(m.up_exps.qtype)
6915 && q8_expert_supported(m.down_exps.qtype);
6916 e.with_moe_cache(max_block, |c, eng| {
6917 let row = c
6918 .layer_dev_row(il, n_expert, eng)?
6919 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
6920 for tok in 0..t {
6921 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
6922 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
6923 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
6924 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6925 if q8 {
6926 let (zq, zd) = match (t, zq8) {
6927 (1, Some((q, d))) => (q.clone(), d.clone()),
6928 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
6929 };
6930 let act = eng.moe_gate_up_silu8_dev_q8(
6931 row,
6932 &selt,
6933 &zq,
6934 &zd,
6935 n_embd,
6936 n_ff_exp,
6937 n_used,
6938 n_expert,
6939 m.gate_exps.qtype,
6940 m.up_exps.qtype,
6941 m.gate_exps.row_bytes,
6942 m.up_exps.row_bytes,
6943 &m.dev_macros,
6944 )?;
6945 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
6946 eng.moe_down8_fma_dev_q8(
6947 row,
6948 &selt,
6949 &wt,
6950 &aq2,
6951 &ad2,
6952 &mut dst,
6953 n_ff_exp,
6954 n_embd,
6955 n_used,
6956 n_expert,
6957 m.down_exps.qtype,
6958 m.down_exps.row_bytes,
6959 )?;
6960 } else {
6961 let act = eng.moe_gate_up_silu8_dev(
6962 row,
6963 &selt,
6964 &zt,
6965 n_embd,
6966 n_ff_exp,
6967 n_used,
6968 n_expert,
6969 m.gate_exps.qtype,
6970 m.up_exps.qtype,
6971 m.gate_exps.row_bytes,
6972 m.up_exps.row_bytes,
6973 &m.dev_macros,
6974 )?;
6975 eng.moe_down8_fma_dev(
6976 row,
6977 &selt,
6978 &wt,
6979 &act,
6980 &mut dst,
6981 n_ff_exp,
6982 n_embd,
6983 n_used,
6984 n_expert,
6985 m.down_exps.qtype,
6986 m.down_exps.row_bytes,
6987 )?;
6988 }
6989 }
6990 c.hits += (t * 3 * n_used) as u64;
6992 Ok(())
6993 })?;
6994 }
6995
6996 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7001 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7002 {
7003 let n_ff_sh = gate_shexp.out_features();
7004 let verify_t = t > 1 && t < PRIME_MIN_T;
7007 let (sg_gate, sg_up) = if t == 1 {
7008 match e.matmul_q8_fused2_x(gate_shexp, up_shexp, z)? {
7009 Some(pair) => pair,
7010 None => (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?),
7011 }
7012 } else if verify_t {
7013 let mut fused = None;
7017 if crate::spec::spec_fused_t()
7018 && (2..=4).contains(&t)
7019 && e.uses_q8_1_fast(gate_shexp)
7020 && e.uses_q8_1_fast(up_shexp)
7021 {
7022 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7023 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
7024 }
7025 match fused {
7026 Some(pair) => pair,
7027 None => (
7028 e.matmul_decode_exact(gate_shexp, z, t)?,
7029 e.matmul_decode_exact(up_shexp, z, t)?,
7030 ),
7031 }
7032 } else {
7033 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
7034 };
7035 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7037 let sh = if verify_t {
7038 e.matmul_decode_exact(down_shexp, &sa, t)?
7039 } else {
7040 e.matmul(down_shexp, &sa, t)?
7041 };
7042 let g = match &m.gate_inp_shexp {
7046 Some(gate_inp_shexp) => {
7047 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
7050 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7051 } else {
7052 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7053 let mut g = e.uninit(t)?;
7054 e.sigmoid(&gs, &mut g, t)?;
7055 g
7056 }
7057 }
7058 None => e.htod(&vec![1.0f32; t])?,
7059 };
7060 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7061 }
7062
7063 Ok(moe_out)
7064 }
7065
7066 #[allow(clippy::too_many_arguments)]
7076 #[allow(clippy::too_many_arguments)]
7079 fn moe_gdec_token_q8(
7080 e: &Engine,
7081 m: &MoeWeights,
7082 il: u16,
7083 max_block: usize,
7084 zq: &CudaSlice<i8>,
7085 zd: &CudaSlice<f32>,
7086 sel: &[u32],
7087 w: &[f32],
7088 moe_out: &mut CudaSlice<f32>,
7089 tok: usize,
7090 n_embd: usize,
7091 n_ff_exp: usize,
7092 n_used: usize,
7093 ) -> Result<bool, Box<dyn std::error::Error>> {
7094 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
7095 use cudarc::driver::DevicePtr;
7096 let ptrs = e.with_moe_cache(max_block, |c, eng| {
7097 let mut g = [0u64; 8];
7098 let mut u = [0u64; 8];
7099 let mut d = [0u64; 8];
7100 for (j, &ex) in sel.iter().enumerate() {
7101 let ex = ex as u16;
7102 let (Some(sg), Some(su), Some(sd)) = (
7103 c.resident(BlockId::new(il, PROJ_GATE, ex)),
7104 c.resident(BlockId::new(il, PROJ_UP, ex)),
7105 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
7106 ) else {
7107 return Ok(None);
7108 };
7109 let __s = eng.stream();
7110 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
7111 let (pu, _e1) = c.slot(su).device_ptr(&__s);
7112 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
7113 g[j] = pg as u64;
7114 u[j] = pu as u64;
7115 d[j] = pd as u64;
7116 }
7117 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
7118 for &ex in sel {
7119 let ex = ex as u16;
7120 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
7121 c.note_profile_hit(BlockId::new(il, proj, ex));
7122 }
7123 }
7124 }
7125 c.hits += (3 * n_used) as u64;
7126 Ok(Some((g, u, d)))
7127 })?;
7128 let Some((g, u, d)) = ptrs else {
7129 return Ok(false);
7130 };
7131 let mut wv = [0f32; 8];
7132 wv[..n_used].copy_from_slice(w);
7133 let act = e.moe_gate_up_silu8_q8(
7134 crate::WPtr8(g),
7135 crate::WPtr8(u),
7136 zq,
7137 zd,
7138 n_embd,
7139 n_ff_exp,
7140 n_used,
7141 m.gate_exps.qtype,
7142 m.up_exps.qtype,
7143 m.gate_exps.row_bytes,
7144 m.up_exps.row_bytes,
7145 )?;
7146 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
7148 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7149 e.moe_down8_fma_q8(
7150 crate::WPtr8(d),
7151 crate::F32x8(wv),
7152 &aq2,
7153 &ad2,
7154 &mut dst,
7155 n_ff_exp,
7156 n_embd,
7157 n_used,
7158 m.down_exps.qtype,
7159 m.down_exps.row_bytes,
7160 )?;
7161 Ok(true)
7162 }
7163
7164 fn moe_gdec_token(
7165 e: &Engine,
7166 m: &MoeWeights,
7167 il: u16,
7168 max_block: usize,
7169 zt: &cudarc::driver::CudaView<f32>,
7170 sel: &[u32],
7171 w: &[f32],
7172 moe_out: &mut CudaSlice<f32>,
7173 tok: usize,
7174 n_embd: usize,
7175 n_ff_exp: usize,
7176 n_used: usize,
7177 ) -> Result<bool, Box<dyn std::error::Error>> {
7178 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
7179 use cudarc::driver::DevicePtr;
7180 let ptrs = e.with_moe_cache(max_block, |c, eng| {
7182 let mut g = [0u64; 8];
7183 let mut u = [0u64; 8];
7184 let mut d = [0u64; 8];
7185 for (j, &ex) in sel.iter().enumerate() {
7186 let ex = ex as u16;
7187 let (Some(sg), Some(su), Some(sd)) = (
7188 c.resident(BlockId::new(il, PROJ_GATE, ex)),
7189 c.resident(BlockId::new(il, PROJ_UP, ex)),
7190 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
7191 ) else {
7192 return Ok(None);
7193 };
7194 let __s = eng.stream();
7195 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
7196 let (pu, _e1) = c.slot(su).device_ptr(&__s);
7197 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
7198 g[j] = pg as u64;
7199 u[j] = pu as u64;
7200 d[j] = pd as u64;
7201 }
7202 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
7203 for &ex in sel {
7204 let ex = ex as u16;
7205 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
7206 c.note_profile_hit(BlockId::new(il, proj, ex));
7207 }
7208 }
7209 }
7210 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
7212 })?;
7213 let Some((g, u, d)) = ptrs else {
7214 return Ok(false);
7215 };
7216 let mut wv = [0f32; 8];
7217 wv[..n_used].copy_from_slice(w);
7218 let act = e.moe_gate_up_silu8(
7220 crate::WPtr8(g),
7221 crate::WPtr8(u),
7222 zt,
7223 n_embd,
7224 n_ff_exp,
7225 n_used,
7226 m.gate_exps.qtype,
7227 m.up_exps.qtype,
7228 m.gate_exps.row_bytes,
7229 m.up_exps.row_bytes,
7230 )?;
7231 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7232 e.moe_down8_fma_into(
7233 crate::WPtr8(d),
7234 crate::F32x8(wv),
7235 &act,
7236 &mut dst,
7237 n_ff_exp,
7238 n_embd,
7239 n_used,
7240 m.down_exps.qtype,
7241 m.down_exps.row_bytes,
7242 )?;
7243 Ok(true)
7244 }
7245
7246 fn moe_cached_gemm_q8(
7251 e: &Engine,
7252 il: u16,
7253 proj: u8,
7254 ex: usize,
7255 m: &MoeWeights,
7256 max_block: usize,
7257 aq: &CudaSlice<i8>,
7258 ad: &CudaSlice<f32>,
7259 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7260 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
7261 let exps = match proj {
7262 PROJ_GATE => &m.gate_exps,
7263 PROJ_UP => &m.up_exps,
7264 _ => &m.down_exps,
7265 };
7266 let layout = exps.expert_layout(ex);
7267 let id = BlockId::new(il, proj, ex as u16);
7268 let source = exps.expert_source(ex);
7269 e.with_moe_cache(max_block, |c, eng| {
7270 let slot = c.dispatch_source(id, source, eng)?;
7271 let DispatchSlot::Resident(sl) = slot;
7272 let buf = c.slot(sl);
7273 eng.qmatvec_expert_q8(
7274 buf,
7275 0..layout.len,
7276 aq,
7277 ad,
7278 1,
7279 exps.in_f,
7280 exps.out_f,
7281 layout.qtype,
7282 layout.row_bytes,
7283 )
7284 })
7285 }
7286
7287 fn moe_cached_gemm(
7288 e: &Engine,
7289 il: u16,
7290 proj: u8,
7291 ex: usize,
7292 m: &MoeWeights,
7293 max_block: usize,
7294 x: &cudarc::driver::CudaView<f32>,
7295 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7296 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
7297 let exps = match proj {
7298 PROJ_GATE => &m.gate_exps,
7299 PROJ_UP => &m.up_exps,
7300 _ => &m.down_exps,
7301 };
7302 let layout = exps.expert_layout(ex);
7303 let id = BlockId::new(il, proj, ex as u16);
7304 let source = exps.expert_source(ex);
7305 e.with_moe_cache(max_block, |c, eng| {
7307 let slot = c.dispatch_source(id, source, eng)?;
7308 let DispatchSlot::Resident(sl) = slot;
7311 let buf = c.slot(sl);
7312 eng.qmatvec_view(
7313 buf,
7314 0..layout.len,
7315 x,
7316 1,
7317 exps.in_f,
7318 exps.out_f,
7319 layout.qtype,
7320 layout.row_bytes,
7321 )
7322 })
7323 }
7324
7325 fn moe_profile_admit_expert(
7329 e: &Engine,
7330 il: u16,
7331 ex: usize,
7332 m: &MoeWeights,
7333 max_block: usize,
7334 ) -> Result<(), Box<dyn std::error::Error>> {
7335 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
7336 e.with_moe_cache(max_block, |cache, eng| {
7337 for (proj, exps) in [
7338 (PROJ_GATE, &m.gate_exps),
7339 (PROJ_UP, &m.up_exps),
7340 (PROJ_DOWN, &m.down_exps),
7341 ] {
7342 let id = BlockId::new(il, proj, ex as u16);
7343 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
7344 }
7345 Ok(())
7346 })
7347 }
7348
7349 #[allow(clippy::too_many_arguments)]
7352 fn moe_frozen_gemm(
7353 e: &Engine,
7354 il: u16,
7355 proj: u8,
7356 ex: usize,
7357 m: &MoeWeights,
7358 max_block: usize,
7359 x: &cudarc::driver::CudaView<f32>,
7360 scratch: &mut Option<CudaSlice<u8>>,
7361 scratch_len: usize,
7362 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7363 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
7364 let exps = match proj {
7365 PROJ_GATE => &m.gate_exps,
7366 PROJ_UP => &m.up_exps,
7367 _ => &m.down_exps,
7368 };
7369 let layout = exps.expert_layout(ex);
7370 let id = BlockId::new(il, proj, ex as u16);
7371 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
7372 let Some(slot) = cache.resident(id) else {
7373 return Ok(None);
7374 };
7375 let buf = cache.slot(slot);
7376 Ok(Some(eng.qmatvec_view(
7377 buf,
7378 0..layout.len,
7379 x,
7380 1,
7381 exps.in_f,
7382 exps.out_f,
7383 layout.qtype,
7384 layout.row_bytes,
7385 )?))
7386 })? {
7387 return Ok(output);
7388 }
7389 if scratch.is_none() {
7390 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
7391 }
7392 let scratch = scratch.as_mut().unwrap();
7393 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
7394 e.qmatvec_view(
7395 scratch,
7396 0..layout.len,
7397 x,
7398 1,
7399 exps.in_f,
7400 exps.out_f,
7401 layout.qtype,
7402 layout.row_bytes,
7403 )
7404 }
7405
7406 fn moe_prefetch_expert(
7407 e: &Engine,
7408 il: u16,
7409 ex: usize,
7410 m: &MoeWeights,
7411 max_block: usize,
7412 keep: &[crate::moe_cache::BlockId],
7413 ) -> Result<(), Box<dyn std::error::Error>> {
7414 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
7415 e.with_moe_cache(max_block, |c, eng| {
7416 for (proj, exps) in [
7417 (PROJ_GATE, &m.gate_exps),
7418 (PROJ_UP, &m.up_exps),
7419 (PROJ_DOWN, &m.down_exps),
7420 ] {
7421 let id = BlockId::new(il, proj, ex as u16);
7422 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
7423 }
7424 Ok(())
7425 })
7426 }
7427
7428 fn moe_prefetch_disk_expert(
7431 e: &Engine,
7432 il: u16,
7433 ex: usize,
7434 m: &MoeWeights,
7435 max_block: usize,
7436 keep: &[crate::moe_cache::BlockId],
7437 ) -> Result<(), Box<dyn std::error::Error>> {
7438 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
7439 e.with_moe_cache(max_block, |c, eng| {
7440 for (proj, exps) in [
7441 (PROJ_GATE, &m.gate_exps),
7442 (PROJ_UP, &m.up_exps),
7443 (PROJ_DOWN, &m.down_exps),
7444 ] {
7445 let source = exps.expert_source(ex);
7446 if let crate::model::ExpertSource::Disk { .. } = &source {
7447 let id = BlockId::new(il, proj, ex as u16);
7448 let _ = c.prefetch_source(id, source, keep, eng)?;
7449 }
7450 }
7451 Ok(())
7452 })
7453 }
7454
7455 #[inline]
7456 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
7457 let _ = m.gate_exps.prefetch_expert_pages(ex);
7458 let _ = m.up_exps.prefetch_expert_pages(ex);
7459 let _ = m.down_exps.prefetch_expert_pages(ex);
7460 }
7461}
7462
7463impl HybridModel {
7480 #[allow(clippy::too_many_arguments)]
7484 fn moe_ffn_grouped_resident_q8(
7485 e: &Engine,
7486 m: &MoeWeights,
7487 z: &CudaSlice<f32>,
7488 t: usize,
7489 cfg: &ModelConfig,
7490 il: u16,
7491 sel_all: &[u32],
7492 w_all: &[f32],
7493 table: &CudaSlice<u64>,
7494 gu_il: bool,
7495 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7496 let moe = cfg.moe.as_ref().unwrap();
7497 let n_embd = cfg.n_embd as usize;
7498 let n_expert = moe.expert_count as usize;
7499 let n_used = moe.expert_used_count as usize;
7500 let n_ff_exp = moe.expert_ff_length as usize;
7501 let n_pairs = t * n_used;
7502 debug_assert_eq!(sel_all.len(), n_pairs);
7503 debug_assert_eq!(w_all.len(), n_pairs);
7504 debug_assert!(
7505 m.gate_exps.macros.is_none()
7506 && m.up_exps.macros.is_none()
7507 && m.down_exps.macros.is_none(),
7508 "resident grouped q8 does not fold per-expert macro scales",
7509 );
7510
7511 if !cfg.swiglu_clamped_at(il as u32) {
7517 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
7518 let sel_d = e.htod_i32(&sel)?;
7519 let w_d = e.htod(w_all)?;
7520 let (gate_row_bytes, up_row_bytes) = if gu_il {
7521 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7522 (combined, combined)
7523 } else {
7524 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7525 };
7526 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7527 let act = e.moe_gate_up_silu8_dev_q8_rows(
7528 table,
7529 &sel_d,
7530 &zq,
7531 &zd,
7532 t,
7533 n_embd,
7534 n_ff_exp,
7535 n_used,
7536 n_expert,
7537 m.gate_exps.qtype,
7538 m.up_exps.qtype,
7539 gate_row_bytes,
7540 up_row_bytes,
7541 &m.dev_macros,
7542 )?;
7543 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7544 let mut moe_out = e.uninit(t * n_embd)?;
7545 e.moe_down8_fma_dev_q8_rows_g(
7546 table,
7547 &sel_d,
7548 &w_d,
7549 &aq2,
7550 &ad2,
7551 &mut moe_out,
7552 t,
7553 n_ff_exp,
7554 n_embd,
7555 n_used,
7556 n_expert,
7557 m.down_exps.qtype,
7558 m.down_exps.row_bytes,
7559 )?;
7560
7561 if std::env::var("MEMRA_MOE_STATS").is_ok() {
7562 let mut counts = vec![0usize; n_expert];
7563 for &expert in sel_all {
7564 counts[expert as usize] += 1;
7565 }
7566 let mut sizes: Vec<usize> =
7567 counts.into_iter().filter(|&count| count != 0).collect();
7568 sizes.sort_unstable();
7569 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
7570 println!(
7571 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
7572 m_e: min={} median={} mean={mean:.1} max={}",
7573 sizes.len(),
7574 n_expert,
7575 sizes.first().copied().unwrap_or(0),
7576 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
7577 sizes.last().copied().unwrap_or(0),
7578 );
7579 }
7580 return Ok(moe_out);
7581 }
7582
7583 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7587 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
7588 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7589 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7590
7591 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7592 for (pair, &expert) in pair_ex.iter().enumerate() {
7593 by_expert[expert as usize].push(pair as i32);
7594 }
7595
7596 let pair_tok_d = e.htod_i32(&pair_tok)?;
7597 let pair_ex_d = e.htod_i32(&pair_ex)?;
7598 let pair_w_d = e.htod(w_all)?;
7599 let tok_off_d = e.htod_i32(&tok_off)?;
7600 let tok_ids_d = e.htod_i32(&tok_ids)?;
7601
7602 let matvec = |proj: i32,
7603 pair_rows: &CudaSlice<i32>,
7604 aq: &CudaSlice<i8>,
7605 ad: &CudaSlice<f32>,
7606 in_f: usize,
7607 out_f: usize,
7608 qtype: i32,
7609 row_bytes: usize|
7610 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7611 e.moe_pairs_matvec_q8(
7612 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
7613 row_bytes,
7614 )
7615 };
7616
7617 let (gate_row_bytes, up_row_bytes) = if gu_il {
7618 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7619 (combined, combined)
7620 } else {
7621 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7622 };
7623 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7624 let gate = matvec(
7625 0,
7626 &pair_tok_d,
7627 &zq,
7628 &zd,
7629 n_embd,
7630 n_ff_exp,
7631 m.gate_exps.qtype,
7632 gate_row_bytes,
7633 )?;
7634 let up = matvec(
7635 1,
7636 &pair_tok_d,
7637 &zq,
7638 &zd,
7639 n_embd,
7640 n_ff_exp,
7641 m.up_exps.qtype,
7642 up_row_bytes,
7643 )?;
7644 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7645 Self::ffn_act_lim(
7646 e,
7647 cfg,
7648 &gate,
7649 &up,
7650 1.0,
7651 1.0,
7652 cfg.clamp_exp_at(il as u32),
7653 &mut act,
7654 n_pairs * n_ff_exp,
7655 )?;
7656 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7657 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7658 let pair_self_d = e.htod_i32(&pair_self)?;
7659 let down = matvec(
7660 2,
7661 &pair_self_d,
7662 &aq2,
7663 &ad2,
7664 n_ff_exp,
7665 n_embd,
7666 m.down_exps.qtype,
7667 m.down_exps.row_bytes,
7668 )?;
7669 let mut moe_out = e.uninit(t * n_embd)?;
7670 e.moe_pairs_scatter(
7671 &down,
7672 &pair_w_d,
7673 &tok_off_d,
7674 &tok_ids_d,
7675 &mut moe_out,
7676 t,
7677 n_embd,
7678 )?;
7679
7680 if std::env::var("MEMRA_MOE_STATS").is_ok() {
7681 let mut sizes: Vec<usize> = by_expert
7682 .iter()
7683 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
7684 .collect();
7685 sizes.sort_unstable();
7686 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
7687 println!(
7688 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
7689 m_e: min={} median={} mean={mean:.1} max={}",
7690 sizes.len(),
7691 n_expert,
7692 sizes.first().copied().unwrap_or(0),
7693 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
7694 sizes.last().copied().unwrap_or(0),
7695 );
7696 }
7697 Ok(moe_out)
7698 }
7699
7700 fn moe_ffn_grouped_add_shared(
7701 e: &Engine,
7702 m: &MoeWeights,
7703 z: &CudaSlice<f32>,
7704 t: usize,
7705 cfg: &ModelConfig,
7706 il: u16,
7707 moe_out: &mut CudaSlice<f32>,
7708 ) -> Result<(), Box<dyn std::error::Error>> {
7709 let n_embd = cfg.n_embd as usize;
7710 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7711 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7712 {
7713 let n_ff_sh = gate_shexp.out_features();
7714 let sg_gate = e.matmul(gate_shexp, z, t)?;
7715 let sg_up = e.matmul(up_shexp, z, t)?;
7716 let mut sa = e.uninit(t * n_ff_sh)?;
7717 Self::ffn_act_lim(
7718 e,
7719 cfg,
7720 &sg_gate,
7721 &sg_up,
7722 1.0,
7723 1.0,
7724 cfg.clamp_shexp_at(il as u32),
7725 &mut sa,
7726 t * n_ff_sh,
7727 )?;
7728 let sh = e.matmul(down_shexp, &sa, t)?;
7729 let gate = match &m.gate_inp_shexp {
7730 Some(gate_inp_shexp) => {
7731 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
7732 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7733 } else {
7734 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7735 let mut gate = e.uninit(t)?;
7736 e.sigmoid(&raw, &mut gate, t)?;
7737 gate
7738 }
7739 }
7740 None => e.htod(&vec![1.0f32; t])?,
7741 };
7742 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
7743 }
7744 Ok(())
7745 }
7746
7747 pub(crate) fn moe_ffn_grouped(
7750 e: &Engine,
7751 m: &MoeWeights,
7752 z: &CudaSlice<f32>,
7753 t: usize,
7754 cfg: &ModelConfig,
7755 il: u16,
7756 max_block: usize,
7757 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7758 let moe = cfg.moe.as_ref().unwrap();
7759 let n_embd = cfg.n_embd as usize;
7760 let n_expert = moe.expert_count as usize;
7761 let n_used = moe.expert_used_count as usize;
7762 let n_ff_exp = moe.expert_ff_length as usize;
7763 let lim_exp = cfg.clamp_exp_at(il as u32);
7765
7766 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
7770 if let Some(sig) = cfg.sigmoid_router() {
7771 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
7772 }
7773 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
7774 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
7775 } else {
7776 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
7777 };
7778 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
7779 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
7780 Self::trace_moe_input(e, il, t, n_embd, z)?;
7781
7782 let no_exp_macros = m.gate_exps.macros.is_none()
7787 && m.up_exps.macros.is_none()
7788 && m.down_exps.macros.is_none();
7789 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
7790 m.has_uniform_expert_layout()
7791 && no_exp_macros
7792 && moe_q8_enabled()
7793 && q8_expert_supported(m.gate_exps.qtype)
7794 && q8_expert_supported(m.up_exps.qtype)
7795 && q8_expert_supported(m.down_exps.qtype)
7796 && moe_slab_enabled()
7797 && dev.dev == e.ctx().ordinal()
7798 });
7799 if let Some(dev) = resident_q8 {
7800 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
7801 e,
7802 m,
7803 z,
7804 t,
7805 cfg,
7806 il,
7807 &sel_all,
7808 &w_all,
7809 &dev.ptr_row,
7810 dev.gu_il,
7811 )?;
7812 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7813 return Ok(moe_out);
7814 }
7815
7816 struct ExpertGroup {
7820 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
7824 let mut groups: Vec<ExpertGroup> = (0..n_expert)
7825 .map(|_| ExpertGroup {
7826 tok_indices: Vec::new(),
7827 slot_indices: Vec::new(),
7828 weights: Vec::new(),
7829 })
7830 .collect();
7831
7832 for tok in 0..t {
7833 for j in 0..n_used {
7834 let ex = sel_all[tok * n_used + j] as usize;
7835 let w = w_all[tok * n_used + j];
7836 groups[ex].tok_indices.push(tok as i32);
7837 groups[ex].slot_indices.push(j as i32);
7838 groups[ex].weights.push(w);
7839 }
7840 }
7841
7842 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
7845 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
7849 let u_len = m.up_exps.max_expert_bytes();
7850 let d_len = m.down_exps.max_expert_bytes();
7851 let moe_q8 = m.has_uniform_expert_layout()
7852 && moe_q8_enabled()
7853 && q8_expert_supported(m.gate_exps.qtype)
7854 && q8_expert_supported(m.up_exps.qtype)
7855 && q8_expert_supported(m.down_exps.qtype);
7856 let slab_local = m
7859 .dev_exps
7860 .as_ref()
7861 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
7862 let use_cache =
7863 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
7864 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
7867
7868 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
7870 (
7871 Some(e.alloc_u8(g_len)?),
7872 Some(e.alloc_u8(u_len)?),
7873 Some(e.alloc_u8(d_len)?),
7874 )
7875 } else {
7876 (None, None, None)
7877 };
7878
7879 let mut order: Vec<usize> = (0..n_expert)
7890 .filter(|&ex| !groups[ex].tok_indices.is_empty())
7891 .collect();
7892 order.sort_by(|&a, &b| {
7893 groups[b]
7894 .tok_indices
7895 .len()
7896 .cmp(&groups[a].tok_indices.len())
7897 .then(a.cmp(&b))
7898 });
7899 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
7901 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
7902 if worker_disk_prefetch {
7903 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
7904 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
7905 }
7906 }
7907 for (order_pos, &ex) in order.iter().enumerate() {
7908 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
7909 Self::moe_prefetch_host_expert(order[next], m);
7910 }
7911 if worker_disk_prefetch {
7912 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
7913 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
7914 let keep = [
7915 BlockId::new(il, PROJ_GATE, ex as u16),
7916 BlockId::new(il, PROJ_UP, ex as u16),
7917 BlockId::new(il, PROJ_DOWN, ex as u16),
7918 ];
7919 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
7920 }
7921 }
7922 let grp = &groups[ex];
7923 let m_e = grp.tok_indices.len();
7924 m_dist.push(m_e);
7925 let gl = m.gate_exps.expert_layout(ex);
7926 let ul = m.up_exps.expert_layout(ex);
7927 let dl = m.down_exps.expert_layout(ex);
7928
7929 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
7933 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
7934 let dmac = m.down_exps.macro_scale(ex);
7935 let weight_d = if dmac == 1.0 {
7936 e.htod(&grp.weights)?
7937 } else {
7938 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
7939 e.htod(&scaled)?
7940 };
7941
7942 let mut gathered = e.zeros(m_e * n_embd)?;
7944 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
7945 let gv = gathered.slice(0..m_e * n_embd);
7946
7947 let y = if let Some(dev) = slab_local {
7950 let gate_start = ex * m.gate_exps.expert_stride;
7951 let up_start = ex * m.up_exps.expert_stride;
7952 let down_start = ex * m.down_exps.expert_stride;
7953 if grouped_q8 {
7954 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
7955 let gate = e.qmatvec_expert_q8(
7956 &dev.gate,
7957 gate_start..gate_start + gl.len,
7958 &zq,
7959 &zd,
7960 m_e,
7961 m.gate_exps.in_f,
7962 m.gate_exps.out_f,
7963 gl.qtype,
7964 gl.row_bytes,
7965 )?;
7966 let up = e.qmatvec_expert_q8(
7967 &dev.up,
7968 up_start..up_start + ul.len,
7969 &zq,
7970 &zd,
7971 m_e,
7972 m.up_exps.in_f,
7973 m.up_exps.out_f,
7974 ul.qtype,
7975 ul.row_bytes,
7976 )?;
7977 let mut act = e.uninit(m_e * n_ff_exp)?;
7978 Self::ffn_act_lim(
7979 e,
7980 cfg,
7981 &gate,
7982 &up,
7983 m.gate_exps.macro_scale(ex),
7984 m.up_exps.macro_scale(ex),
7985 lim_exp,
7986 &mut act,
7987 m_e * n_ff_exp,
7988 )?;
7989 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
7990 e.qmatvec_expert_q8(
7991 &dev.down,
7992 down_start..down_start + dl.len,
7993 &aq2,
7994 &ad2,
7995 m_e,
7996 m.down_exps.in_f,
7997 m.down_exps.out_f,
7998 dl.qtype,
7999 dl.row_bytes,
8000 )?
8001 } else {
8002 let gate = e.qmatvec_view(
8003 &dev.gate,
8004 gate_start..gate_start + gl.len,
8005 &gv,
8006 m_e,
8007 m.gate_exps.in_f,
8008 m.gate_exps.out_f,
8009 gl.qtype,
8010 gl.row_bytes,
8011 )?;
8012 let up = e.qmatvec_view(
8013 &dev.up,
8014 up_start..up_start + ul.len,
8015 &gv,
8016 m_e,
8017 m.up_exps.in_f,
8018 m.up_exps.out_f,
8019 ul.qtype,
8020 ul.row_bytes,
8021 )?;
8022 let mut act = e.uninit(m_e * n_ff_exp)?;
8023 Self::ffn_act_lim(
8024 e,
8025 cfg,
8026 &gate,
8027 &up,
8028 m.gate_exps.macro_scale(ex),
8029 m.up_exps.macro_scale(ex),
8030 lim_exp,
8031 &mut act,
8032 m_e * n_ff_exp,
8033 )?;
8034 let actv = act.slice(0..m_e * n_ff_exp);
8035 e.qmatvec_view(
8036 &dev.down,
8037 down_start..down_start + dl.len,
8038 &actv,
8039 m_e,
8040 m.down_exps.in_f,
8041 m.down_exps.out_f,
8042 dl.qtype,
8043 dl.row_bytes,
8044 )?
8045 }
8046 } else if use_cache {
8047 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8048 if grouped_q8 {
8049 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
8050 let gate = e.with_moe_cache(max_block, |cache, eng| {
8051 let id = BlockId::new(il, PROJ_GATE, ex as u16);
8052 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
8053 eng.qmatvec_expert_q8(
8054 cache.buf(slot),
8055 0..gl.len,
8056 &zq,
8057 &zd,
8058 m_e,
8059 m.gate_exps.in_f,
8060 m.gate_exps.out_f,
8061 gl.qtype,
8062 gl.row_bytes,
8063 )
8064 })?;
8065 let up = e.with_moe_cache(max_block, |cache, eng| {
8066 let id = BlockId::new(il, PROJ_UP, ex as u16);
8067 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
8068 eng.qmatvec_expert_q8(
8069 cache.buf(slot),
8070 0..ul.len,
8071 &zq,
8072 &zd,
8073 m_e,
8074 m.up_exps.in_f,
8075 m.up_exps.out_f,
8076 ul.qtype,
8077 ul.row_bytes,
8078 )
8079 })?;
8080 let mut act = e.uninit(m_e * n_ff_exp)?;
8081 Self::ffn_act_lim(
8082 e,
8083 cfg,
8084 &gate,
8085 &up,
8086 m.gate_exps.macro_scale(ex),
8087 m.up_exps.macro_scale(ex),
8088 lim_exp,
8089 &mut act,
8090 m_e * n_ff_exp,
8091 )?;
8092 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
8093 e.with_moe_cache(max_block, |cache, eng| {
8094 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
8095 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
8096 eng.qmatvec_expert_q8(
8097 cache.buf(slot),
8098 0..dl.len,
8099 &aq2,
8100 &ad2,
8101 m_e,
8102 m.down_exps.in_f,
8103 m.down_exps.out_f,
8104 dl.qtype,
8105 dl.row_bytes,
8106 )
8107 })?
8108 } else {
8109 let gate = e.with_moe_cache(max_block, |cache, eng| {
8110 let id = BlockId::new(il, PROJ_GATE, ex as u16);
8111 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
8112 eng.qmatvec_view(
8113 cache.buf(slot),
8114 0..gl.len,
8115 &gv,
8116 m_e,
8117 m.gate_exps.in_f,
8118 m.gate_exps.out_f,
8119 gl.qtype,
8120 gl.row_bytes,
8121 )
8122 })?;
8123 let up = e.with_moe_cache(max_block, |cache, eng| {
8124 let id = BlockId::new(il, PROJ_UP, ex as u16);
8125 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
8126 eng.qmatvec_view(
8127 cache.buf(slot),
8128 0..ul.len,
8129 &gv,
8130 m_e,
8131 m.up_exps.in_f,
8132 m.up_exps.out_f,
8133 ul.qtype,
8134 ul.row_bytes,
8135 )
8136 })?;
8137 let mut act = e.uninit(m_e * n_ff_exp)?;
8138 Self::ffn_act_lim(
8139 e,
8140 cfg,
8141 &gate,
8142 &up,
8143 m.gate_exps.macro_scale(ex),
8144 m.up_exps.macro_scale(ex),
8145 lim_exp,
8146 &mut act,
8147 m_e * n_ff_exp,
8148 )?;
8149 let actv = act.slice(0..m_e * n_ff_exp);
8150 e.with_moe_cache(max_block, |cache, eng| {
8151 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
8152 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
8153 eng.qmatvec_view(
8154 cache.buf(slot),
8155 0..dl.len,
8156 &actv,
8157 m_e,
8158 m.down_exps.in_f,
8159 m.down_exps.out_f,
8160 dl.qtype,
8161 dl.row_bytes,
8162 )
8163 })?
8164 }
8165 } else {
8166 let sg = scratch_g.as_mut().unwrap();
8167 let su = scratch_u.as_mut().unwrap();
8168 let sd = scratch_d.as_mut().unwrap();
8169 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
8170 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
8171 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
8172 if grouped_q8 {
8173 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
8174 let gate = e.qmatvec_expert_q8(
8175 sg,
8176 0..gl.len,
8177 &zq,
8178 &zd,
8179 m_e,
8180 m.gate_exps.in_f,
8181 m.gate_exps.out_f,
8182 gl.qtype,
8183 gl.row_bytes,
8184 )?;
8185 let up = e.qmatvec_expert_q8(
8186 su,
8187 0..ul.len,
8188 &zq,
8189 &zd,
8190 m_e,
8191 m.up_exps.in_f,
8192 m.up_exps.out_f,
8193 ul.qtype,
8194 ul.row_bytes,
8195 )?;
8196 let mut act = e.uninit(m_e * n_ff_exp)?;
8197 Self::ffn_act_lim(
8198 e,
8199 cfg,
8200 &gate,
8201 &up,
8202 m.gate_exps.macro_scale(ex),
8203 m.up_exps.macro_scale(ex),
8204 lim_exp,
8205 &mut act,
8206 m_e * n_ff_exp,
8207 )?;
8208 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
8209 e.qmatvec_expert_q8(
8210 sd,
8211 0..dl.len,
8212 &aq2,
8213 &ad2,
8214 m_e,
8215 m.down_exps.in_f,
8216 m.down_exps.out_f,
8217 dl.qtype,
8218 dl.row_bytes,
8219 )?
8220 } else {
8221 let gate = e.qmatvec_view(
8222 sg,
8223 0..gl.len,
8224 &gv,
8225 m_e,
8226 m.gate_exps.in_f,
8227 m.gate_exps.out_f,
8228 gl.qtype,
8229 gl.row_bytes,
8230 )?;
8231 let up = e.qmatvec_view(
8232 su,
8233 0..ul.len,
8234 &gv,
8235 m_e,
8236 m.up_exps.in_f,
8237 m.up_exps.out_f,
8238 ul.qtype,
8239 ul.row_bytes,
8240 )?;
8241 let mut act = e.uninit(m_e * n_ff_exp)?;
8242 Self::ffn_act_lim(
8243 e,
8244 cfg,
8245 &gate,
8246 &up,
8247 m.gate_exps.macro_scale(ex),
8248 m.up_exps.macro_scale(ex),
8249 lim_exp,
8250 &mut act,
8251 m_e * n_ff_exp,
8252 )?;
8253 let actv = act.slice(0..m_e * n_ff_exp);
8254 e.qmatvec_view(
8255 sd,
8256 0..dl.len,
8257 &actv,
8258 m_e,
8259 m.down_exps.in_f,
8260 m.down_exps.out_f,
8261 dl.qtype,
8262 dl.row_bytes,
8263 )?
8264 }
8265 };
8266
8267 e.scatter_slot(
8269 &y,
8270 &tok_idx_d,
8271 &slot_idx_d,
8272 &weight_d,
8273 &mut slot_buf,
8274 &mut wbuf,
8275 n_embd,
8276 n_used,
8277 m_e,
8278 )?;
8279 }
8280
8281 let mut moe_out = e.zeros(t * n_embd)?;
8283 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
8284
8285 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
8287 m_dist.sort_unstable();
8288 let active = m_dist.len();
8289 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
8290 let median = m_dist[active / 2];
8291 let max_m = *m_dist.last().unwrap();
8292 let min_m = m_dist[0];
8293 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
8294 println!(
8295 "moe-grouped il={il} t={t} active={active}/{n_expert} \
8296 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
8297 above_gemm_threshold(>=16)={above16}/{active}"
8298 );
8299 }
8300
8301 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
8302 Ok(moe_out)
8303 }
8304
8305 pub(crate) fn moe_ffn_lockstep(
8312 &self,
8313 e: &Engine,
8314 m: &MoeWeights,
8315 zbatch: &CudaSlice<f32>,
8316 mrows: usize,
8317 il: u16,
8318 max_block: usize,
8319 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8320 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8321 let cfg = &self.cfg;
8322 let moe = cfg.moe.as_ref().unwrap();
8323 let n_embd = cfg.n_embd as usize;
8324 let n_expert = moe.expert_count as usize;
8325 let n_used = moe.expert_used_count as usize;
8326 let n_ff_exp = moe.expert_ff_length as usize;
8327 let lim_exp = cfg.clamp_exp_at(il as u32);
8329 let lim_shexp = cfg.clamp_shexp_at(il as u32);
8330
8331 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
8332 if let Some(sig) = cfg.sigmoid_router() {
8333 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
8334 }
8335 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
8336 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
8337 } else {
8338 Self::moe_route_cfg(
8339 e,
8340 &logits,
8341 mrows,
8342 n_expert,
8343 n_used,
8344 m.active_experts.as_deref(),
8345 )?
8346 };
8347 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
8348
8349 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
8351 Ok((0..n_expert)
8352 .map(|ex| {
8353 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
8354 .into_iter()
8355 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
8356 })
8357 .collect())
8358 })?;
8359
8360 struct Group {
8361 rows: Vec<i32>,
8362 slots: Vec<i32>,
8363 weights: Vec<f32>,
8364 }
8365 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
8366 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
8367 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
8368 Default::default();
8369 for row in 0..mrows {
8370 for j in 0..n_used {
8371 let ex = sel_all[row * n_used + j] as usize;
8372 let w = w_all[row * n_used + j];
8373 if resident_expert[ex] {
8374 let group = groups.entry(ex).or_insert_with(|| Group {
8375 rows: Vec::new(),
8376 slots: Vec::new(),
8377 weights: Vec::new(),
8378 });
8379 group.rows.push(row as i32);
8380 group.slots.push(j as i32);
8381 group.weights.push(w);
8382 } else {
8383 crate::cpu_experts::record_incomplete_gpu_residency(0);
8384 cpu_rows[row].push((ex, w));
8385 cpu_by_expert.entry(ex).or_default().push((row, w));
8386 }
8387 }
8388 }
8389
8390 let host_rows = e.dtoh(zbatch)?;
8396 let rows_ok = crate::cpu_experts::rows_supported();
8397 enum CpuPart {
8398 Single { row: usize },
8399 Rows { rows: Vec<usize> },
8400 }
8401 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
8402 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
8403 if rows_ok {
8404 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
8405 .into_iter()
8406 .filter(|(_, rows)| rows.len() >= 2)
8407 .collect();
8408 shared.sort_by_key(|(ex, _)| *ex);
8409 for (ex, mut row_weights) in shared {
8410 row_weights.sort_by_key(|(row, _)| *row);
8411 let inputs: Vec<(&[f32], f32)> = row_weights
8412 .iter()
8413 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
8414 .collect();
8415 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
8416 .map_err(std::io::Error::other)?;
8417 for &(row, _) in &row_weights {
8418 rows_served.insert((row, ex));
8419 }
8420 tickets.push((
8421 CpuPart::Rows {
8422 rows: row_weights.iter().map(|&(row, _)| row).collect(),
8423 },
8424 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
8425 ));
8426 }
8427 }
8428 for (row, selected) in cpu_rows.iter().enumerate() {
8429 let leftover: Vec<(usize, f32)> = selected
8430 .iter()
8431 .copied()
8432 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
8433 .collect();
8434 if leftover.is_empty() {
8435 continue;
8436 }
8437 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
8438 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
8439 .map_err(std::io::Error::other)?;
8440 tickets.push((
8441 CpuPart::Single { row },
8442 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
8443 ));
8444 }
8445
8446 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
8447 let mut wbuf = e.zeros(mrows * n_used)?;
8448 let mut order: Vec<usize> = groups.keys().copied().collect();
8449 order.sort_by(|&a, &b| {
8450 groups[&b]
8451 .rows
8452 .len()
8453 .cmp(&groups[&a].rows.len())
8454 .then(a.cmp(&b))
8455 });
8456 for &ex in &order {
8457 let group = &groups[&ex];
8458 let m_e = group.rows.len();
8459 let gl = m.gate_exps.expert_layout(ex);
8460 let ul = m.up_exps.expert_layout(ex);
8461 let dl = m.down_exps.expert_layout(ex);
8462 let row_idx_d = e.htod_i32(&group.rows)?;
8463 let slot_idx_d = e.htod_i32(&group.slots)?;
8464 let dmac = m.down_exps.macro_scale(ex);
8465 let weight_d = if dmac == 1.0 {
8466 e.htod(&group.weights)?
8467 } else {
8468 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
8469 e.htod(&scaled)?
8470 };
8471 let mut gathered = e.zeros(m_e * n_embd)?;
8472 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
8473 let gv = gathered.slice(0..m_e * n_embd);
8474 let gate = e.with_moe_cache(max_block, |c, eng| {
8475 let slot = c
8476 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
8477 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
8478 eng.qmatvec_view(
8479 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
8480 0..gl.len,
8481 &gv,
8482 m_e,
8483 m.gate_exps.in_f,
8484 m.gate_exps.out_f,
8485 gl.qtype,
8486 gl.row_bytes,
8487 )
8488 })?;
8489 let up = e.with_moe_cache(max_block, |c, eng| {
8490 let slot = c
8491 .resident(BlockId::new(il, PROJ_UP, ex as u16))
8492 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
8493 eng.qmatvec_view(
8494 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
8495 0..ul.len,
8496 &gv,
8497 m_e,
8498 m.up_exps.in_f,
8499 m.up_exps.out_f,
8500 ul.qtype,
8501 ul.row_bytes,
8502 )
8503 })?;
8504 let mut act = e.zeros(m_e * n_ff_exp)?;
8505 Self::ffn_act_lim(
8506 e,
8507 cfg,
8508 &gate,
8509 &up,
8510 m.gate_exps.macro_scale(ex),
8511 m.up_exps.macro_scale(ex),
8512 lim_exp,
8513 &mut act,
8514 m_e * n_ff_exp,
8515 )?;
8516 let actv = act.slice(0..m_e * n_ff_exp);
8517 let y = e.with_moe_cache(max_block, |c, eng| {
8518 let slot = c
8519 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
8520 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
8521 eng.qmatvec_view(
8522 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
8523 0..dl.len,
8524 &actv,
8525 m_e,
8526 m.down_exps.in_f,
8527 m.down_exps.out_f,
8528 dl.qtype,
8529 dl.row_bytes,
8530 )
8531 })?;
8532 e.scatter_slot(
8533 &y,
8534 &row_idx_d,
8535 &slot_idx_d,
8536 &weight_d,
8537 &mut slot_buf,
8538 &mut wbuf,
8539 n_embd,
8540 n_used,
8541 m_e,
8542 )?;
8543 }
8544 let mut moe_out = e.zeros(mrows * n_embd)?;
8545 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
8546
8547 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
8549 for (part, ticket) in tickets {
8550 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
8551 let mut add_row = |row: usize, chunk: &[f32]| {
8552 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
8553 for (accumulator, value) in sum.iter_mut().zip(chunk) {
8554 *accumulator += value;
8555 }
8556 };
8557 match part {
8558 CpuPart::Single { row } => add_row(row, &cpu_output),
8559 CpuPart::Rows { rows } => {
8560 for (slot, row) in rows.into_iter().enumerate() {
8561 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
8562 }
8563 }
8564 }
8565 }
8566 for (row, sum) in row_sums.into_iter().enumerate() {
8567 let Some(sum) = sum else { continue };
8568 let cpu_output = e.htod(&sum)?;
8569 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
8570 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
8571 }
8572
8573 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8574 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8575 {
8576 let n_ff_sh = gate_shexp.out_features();
8577 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
8578 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
8579 let mut sa = e.zeros(mrows * n_ff_sh)?;
8580 Self::ffn_act_lim(
8581 e,
8582 cfg,
8583 &sg_gate,
8584 &sg_up,
8585 1.0,
8586 1.0,
8587 lim_shexp,
8588 &mut sa,
8589 mrows * n_ff_sh,
8590 )?;
8591 let sh = e.matmul(down_shexp, &sa, mrows)?;
8592 let g = match &m.gate_inp_shexp {
8595 Some(gate_inp_shexp) => {
8596 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
8597 }
8598 None => e.htod(&vec![1.0f32; mrows])?,
8599 };
8600 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
8601 }
8602
8603 Ok(moe_out)
8604 }
8605}
8606
8607impl HybridModel {
8613 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
8615 let g = self.cfg.gemma4.as_ref().unwrap();
8616 let swa = g.swa_pattern[il];
8617 let hd = if swa {
8618 g.key_length_swa
8619 } else {
8620 g.key_length_global
8621 } as usize;
8622 (
8626 hd,
8627 g.head_count_kv[il] as usize,
8628 self.cfg.n_head as usize,
8629 if swa {
8630 g.rope_base_swa
8631 } else {
8632 g.rope_base_global
8633 },
8634 1.0,
8635 swa,
8636 )
8637 }
8638
8639 fn gemma4_suppress(
8643 &self,
8644 e: &Engine,
8645 ld: &mut CudaSlice<f32>,
8646 t: usize,
8647 ) -> Result<(), Box<dyn std::error::Error>> {
8648 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
8649 #[cfg(debug_assertions)]
8654 crate::debug_assert_tensor_stream_device(
8655 ids,
8656 &e.stream(),
8657 "gemma4_suppress.suppress_d",
8658 );
8659 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
8660 }
8661 Ok(())
8662 }
8663
8664 fn gemma4_attn_prime(
8669 &self,
8670 e: &Engine,
8671 fa: &crate::hybrid::FullAttnLayer,
8672 il: usize,
8673 h: &CudaSlice<f32>,
8674 pos_d: &CudaSlice<i32>,
8675 t: usize,
8676 cache: Option<&mut Cache>,
8677 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8678 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
8679 let eps = self.cfg.rms_eps;
8680 let aux = self.gemma4_aux.as_ref().unwrap();
8681 let ones = aux.ones(e);
8682 #[cfg(debug_assertions)]
8683 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
8684
8685 e.mmq_act_begin();
8688 let q0 = e.matmul(&fa.wq, h, t)?; let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
8693 e.matmul(&fa.wv, h, t)?
8694 } else {
8695 e.clone_dtod(&k0)?
8696 };
8697
8698 let mut q = e.uninit(t * nh * hd)?;
8699 let mut k = e.uninit(t * nkv * hd)?;
8700 let mut v = e.uninit(t * nkv * hd)?;
8702 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8706 let emit = t >= 16
8707 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
8708 && *EMIT.get_or_init(|| {
8709 std::env::var("MEMRA_FA_EMIT")
8710 .map(|s| s != "0")
8711 .unwrap_or(true)
8712 });
8713 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
8714 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
8715 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
8716 let v_f16 = emit
8719 && crate::fa_f16pv_on()
8720 && match hd {
8721 512 => true,
8722 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
8723 _ => false,
8724 };
8725 if emit {
8726 e.rms_norm_qkv_w4b(
8727 &q0,
8728 &k0,
8729 &v0,
8730 fa.q_norm.float_data(),
8731 fa.k_norm.float_data(),
8732 ones,
8733 &mut q,
8734 &mut k,
8735 &mut v,
8736 &mut vb,
8737 hd,
8738 nh * t,
8739 nkv * t,
8740 eps,
8741 v_f16,
8742 )?;
8743 } else {
8744 e.rms_norm_qkv(
8745 &q0,
8746 &k0,
8747 &v0,
8748 fa.q_norm.float_data(),
8749 fa.k_norm.float_data(),
8750 ones,
8751 &mut q,
8752 &mut k,
8753 &mut v,
8754 hd,
8755 nh * t,
8756 nkv * t,
8757 eps,
8758 )?;
8759 }
8760
8761 let ff = if swa {
8762 None
8763 } else {
8764 Some(
8765 aux.rope_freqs(e)
8766 .expect("gemma4 global rope needs rope_freqs.weight"),
8767 )
8768 };
8769 #[cfg(debug_assertions)]
8770 if let Some(ff) = ff {
8771 crate::debug_assert_tensor_stream_device(
8772 ff,
8773 &e.stream(),
8774 "gemma4_attn_prime.rope_freqs",
8775 );
8776 }
8777 if emit {
8778 e.rope_neox2_bf16e(
8779 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
8780 )?;
8781 } else {
8782 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
8783 }
8784
8785 if let Some(cache) = cache {
8786 let kvl = cache.kv[il].as_mut().unwrap();
8787 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
8788 e.append_kv_quantized_rows(
8789 &k,
8790 &v,
8791 &mut kvl.k,
8792 &mut kvl.v,
8793 kvl.len,
8794 t,
8795 kvl.kv_dim_k,
8796 kvl.kv_dim_v,
8797 kvl.k_tok_bytes,
8798 kvl.v_tok_bytes,
8799 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
8800 )?;
8801 kvl.len += t;
8802 }
8803 let mut attn = e.zeros(t * nh * hd)?;
8804 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
8808 if swa && t > win {
8809 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
8810 if emit {
8811 e.fa_prefill_w_pre(
8812 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
8813 )?;
8814 } else {
8815 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
8816 }
8817 } else {
8818 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
8819 }
8820 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
8821 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
8822 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
8823 if emit {
8824 e.fa_prefill_hd512_pre(
8825 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
8826 )?;
8827 } else {
8828 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
8829 }
8830 } else {
8831 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
8832 }
8833 Ok(e.matmul(&fa.wo, &attn, t)?)
8834 }
8835
8836 fn gemma4_attn(
8838 &self,
8839 e: &Engine,
8840 fa: &crate::hybrid::FullAttnLayer,
8841 il: usize,
8842 h: &CudaSlice<f32>,
8843 pos_d: &CudaSlice<i32>,
8844 t: usize,
8845 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8846 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None)
8847 }
8848
8849 fn gemma4_moe_q8(
8854 &self,
8855 e: &Engine,
8856 m: &crate::hybrid::MoeWeights,
8857 bits: &crate::hybrid::Gemma4MoeBits,
8858 mq: &(CudaSlice<i8>, CudaSlice<f32>),
8859 router_in: &CudaSlice<f32>,
8860 t: usize,
8861 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8862 let cfg = &self.cfg;
8863 let moe = cfg.moe.as_ref().unwrap();
8864 let n_embd = cfg.n_embd as usize;
8865 let n_expert = moe.expert_count as usize;
8866 let n_used = moe.expert_used_count as usize;
8867 let n_ff_exp = moe.expert_ff_length as usize;
8868 let logits = if crate::router_kernel_on() {
8872 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
8873 } else {
8874 e.matmul(&m.gate_inp, router_in, t)?
8875 };
8876 let dev = m.dev_exps.as_ref().unwrap();
8877 let (sel_d, w_d) =
8878 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
8879 let (zq, zd) = mq;
8880 if t == 1 {
8881 let selv = sel_d.slice(0..n_used);
8882 let wv = w_d.slice(0..n_used);
8883 let act = e.moe_gate_up_gelu8_dev_q8(
8884 &dev.ptr_row,
8885 &selv,
8886 zq,
8887 zd,
8888 n_embd,
8889 n_ff_exp,
8890 n_used,
8891 n_expert,
8892 m.gate_exps.qtype,
8893 m.up_exps.qtype,
8894 m.gate_exps.row_bytes,
8895 m.up_exps.row_bytes,
8896 )?;
8897 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8898 let mut moe_out = e.uninit(n_embd)?;
8899 e.moe_down8_fma_dev_q8(
8900 &dev.ptr_row,
8901 &selv,
8902 &wv,
8903 &aq2,
8904 &ad2,
8905 &mut moe_out.slice_mut(0..n_embd),
8906 n_ff_exp,
8907 n_embd,
8908 n_used,
8909 n_expert,
8910 m.down_exps.qtype,
8911 m.down_exps.row_bytes,
8912 )?;
8913 return Ok(moe_out);
8914 }
8915 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
8916 let act = if csr {
8917 e.moe_gate_up_gelu8_dev_q8_csr(
8918 &dev.ptr_row,
8919 &sel_d,
8920 zq,
8921 zd,
8922 t * n_used,
8923 n_embd,
8924 n_ff_exp,
8925 n_used,
8926 n_expert,
8927 m.gate_exps.qtype,
8928 m.up_exps.qtype,
8929 m.gate_exps.row_bytes,
8930 m.up_exps.row_bytes,
8931 )?
8932 } else {
8933 e.moe_gate_up_gelu8_dev_q8_rows(
8934 &dev.ptr_row,
8935 &sel_d,
8936 zq,
8937 zd,
8938 t,
8939 n_embd,
8940 n_ff_exp,
8941 n_used,
8942 n_expert,
8943 m.gate_exps.qtype,
8944 m.up_exps.qtype,
8945 m.gate_exps.row_bytes,
8946 m.up_exps.row_bytes,
8947 )?
8948 };
8949 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8950 let mut moe_out = e.uninit(t * n_embd)?;
8951 e.moe_down8_fma_dev_q8_rows_g(
8954 &dev.ptr_row,
8955 &sel_d,
8956 &w_d,
8957 &aq2,
8958 &ad2,
8959 &mut moe_out,
8960 t,
8961 n_ff_exp,
8962 n_embd,
8963 n_used,
8964 n_expert,
8965 m.down_exps.qtype,
8966 m.down_exps.row_bytes,
8967 )?;
8968 Ok(moe_out)
8969 }
8970
8971 fn gemma4_moe(
8975 &self,
8976 e: &Engine,
8977 m: &crate::hybrid::MoeWeights,
8978 bits: &crate::hybrid::Gemma4MoeBits,
8979 moe_in: &CudaSlice<f32>,
8980 router_in: &CudaSlice<f32>,
8981 t: usize,
8982 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8983 let cfg = &self.cfg;
8984 let moe = cfg.moe.as_ref().unwrap();
8985 let n_embd = cfg.n_embd as usize;
8986 let n_expert = moe.expert_count as usize;
8987 let n_used = moe.expert_used_count as usize;
8988 let n_ff_exp = moe.expert_ff_length as usize;
8989
8990 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
8994 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
8995 } else {
8996 e.matmul(&m.gate_inp, router_in, t)?
8997 };
8998
8999 if t < PRIME_MIN_T
9004 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
9005 && expert_dp4a_supported(m.gate_exps.qtype)
9006 && expert_dp4a_supported(m.up_exps.qtype)
9007 && expert_dp4a_supported(m.down_exps.qtype)
9008 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
9009 {
9010 let dev = m.dev_exps.as_ref().unwrap();
9011 let (sel_d, w_d) =
9012 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
9013 if t == 1 {
9014 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
9015 let selv = sel_d.slice(0..n_used);
9016 let wv = w_d.slice(0..n_used);
9017 let act = e.moe_gate_up_gelu8_dev_q8(
9018 &dev.ptr_row,
9019 &selv,
9020 &zq,
9021 &zd,
9022 n_embd,
9023 n_ff_exp,
9024 n_used,
9025 n_expert,
9026 m.gate_exps.qtype,
9027 m.up_exps.qtype,
9028 m.gate_exps.row_bytes,
9029 m.up_exps.row_bytes,
9030 )?;
9031 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
9032 let mut moe_out = e.uninit(n_embd)?;
9033 e.moe_down8_fma_dev_q8(
9034 &dev.ptr_row,
9035 &selv,
9036 &wv,
9037 &aq2,
9038 &ad2,
9039 &mut moe_out.slice_mut(0..n_embd),
9040 n_ff_exp,
9041 n_embd,
9042 n_used,
9043 n_expert,
9044 m.down_exps.qtype,
9045 m.down_exps.row_bytes,
9046 )?;
9047 return Ok(moe_out);
9048 }
9049 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
9054 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
9055 let act = if csr {
9056 e.moe_gate_up_gelu8_dev_q8_csr(
9057 &dev.ptr_row,
9058 &sel_d,
9059 &zq,
9060 &zd,
9061 t * n_used,
9062 n_embd,
9063 n_ff_exp,
9064 n_used,
9065 n_expert,
9066 m.gate_exps.qtype,
9067 m.up_exps.qtype,
9068 m.gate_exps.row_bytes,
9069 m.up_exps.row_bytes,
9070 )?
9071 } else {
9072 e.moe_gate_up_gelu8_dev_q8_rows(
9073 &dev.ptr_row,
9074 &sel_d,
9075 &zq,
9076 &zd,
9077 t,
9078 n_embd,
9079 n_ff_exp,
9080 n_used,
9081 n_expert,
9082 m.gate_exps.qtype,
9083 m.up_exps.qtype,
9084 m.gate_exps.row_bytes,
9085 m.up_exps.row_bytes,
9086 )?
9087 };
9088 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
9089 let mut moe_out = e.uninit(t * n_embd)?;
9090 e.moe_down8_fma_dev_q8_rows_g(
9091 &dev.ptr_row,
9092 &sel_d,
9093 &w_d,
9094 &aq2,
9095 &ad2,
9096 &mut moe_out,
9097 t,
9098 n_ff_exp,
9099 n_embd,
9100 n_used,
9101 n_expert,
9102 m.down_exps.qtype,
9103 m.down_exps.row_bytes,
9104 )?;
9105 return Ok(moe_out);
9106 }
9107
9108 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
9109 for (i, &sx) in sel_all.iter().enumerate() {
9110 w_all[i] *= bits.per_expert_scale[sx as usize];
9111 }
9112
9113 if t >= PRIME_MIN_T
9117 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
9118 && expert_dp4a_supported(m.gate_exps.qtype)
9119 && expert_dp4a_supported(m.up_exps.qtype)
9120 && expert_dp4a_supported(m.down_exps.qtype)
9121 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
9122 {
9123 let dev = m.dev_exps.as_ref().unwrap();
9124 let n_pairs = t * n_used;
9125 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
9126 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
9127 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
9128 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
9129 let pt = e.htod_i32(&pair_tok)?;
9130 let pw = e.htod(&w_all)?;
9131 let toff = e.htod_i32(&tok_off)?;
9132 let tids = e.htod_i32(&tok_ids)?;
9133 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
9134 for p in 0..n_pairs {
9135 by_ex[pair_ex[p] as usize].push(p as i32);
9136 }
9137 let mut ex_ids: Vec<i32> = Vec::new();
9138 let mut ex_off: Vec<i32> = vec![0];
9139 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
9140 for (ex, list) in by_ex.iter().enumerate() {
9141 if list.is_empty() {
9142 continue;
9143 }
9144 ex_ids.push(ex as i32);
9145 ex_pairs.extend_from_slice(list);
9146 ex_off.push(ex_pairs.len() as i32);
9147 }
9148 let n_active = ex_ids.len();
9149 let exi = e.htod_i32(&ex_ids)?;
9150 let exo = e.htod_i32(&ex_off)?;
9151 let exp_d = e.htod_i32(&ex_pairs)?;
9152 if crate::moe_f16g_gemma_on()
9160 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
9161 && f16g_proj_ok(m.up_exps.qtype, n_embd)
9162 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
9163 {
9164 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
9165 let csr_tok_d = e.htod_i32(&csr_tok)?;
9166 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
9167 let g_csr = e.moe_f16_grouped(
9168 &dev.ptr_row,
9169 0,
9170 n_expert,
9171 &exi,
9172 &ex_off,
9173 &exo,
9174 &z_f16,
9175 &z_s,
9176 n_embd,
9177 n_ff_exp,
9178 n_active,
9179 n_pairs,
9180 m.gate_exps.qtype,
9181 m.gate_exps.row_bytes,
9182 )?;
9183 let u_csr = e.moe_f16_grouped(
9184 &dev.ptr_row,
9185 1,
9186 n_expert,
9187 &exi,
9188 &ex_off,
9189 &exo,
9190 &z_f16,
9191 &z_s,
9192 n_embd,
9193 n_ff_exp,
9194 n_active,
9195 n_pairs,
9196 m.up_exps.qtype,
9197 m.up_exps.row_bytes,
9198 )?;
9199 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
9200 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
9201 let d_csr = e.moe_f16_grouped(
9202 &dev.ptr_row,
9203 2,
9204 n_expert,
9205 &exi,
9206 &ex_off,
9207 &exo,
9208 &a_f16,
9209 &a_s,
9210 n_ff_exp,
9211 n_embd,
9212 n_active,
9213 n_pairs,
9214 m.down_exps.qtype,
9215 m.down_exps.row_bytes,
9216 )?;
9217 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
9218 let mut moe_out = e.uninit(t * n_embd)?;
9219 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
9220 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
9221 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
9222 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
9223 eprintln!(
9224 "[f16g-debug] post-permute bad={} post-scatter bad={}",
9225 scan(&yd),
9226 scan(&mo)
9227 );
9228 }
9229 return Ok(moe_out);
9230 }
9231 let mma =
9234 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
9235 let (gate, up) = if mma {
9236 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
9237 (
9238 e.mmq_iq_experts(
9239 &dev.ptr_row,
9240 0,
9241 n_expert,
9242 &exi,
9243 &exo,
9244 &exp_d,
9245 &pt,
9246 &z_scr,
9247 n_embd,
9248 n_ff_exp,
9249 n_active,
9250 n_pairs,
9251 t,
9252 m.gate_exps.qtype,
9253 m.gate_exps.row_bytes,
9254 )?,
9255 e.mmq_iq_experts(
9256 &dev.ptr_row,
9257 1,
9258 n_expert,
9259 &exi,
9260 &exo,
9261 &exp_d,
9262 &pt,
9263 &z_scr,
9264 n_embd,
9265 n_ff_exp,
9266 n_active,
9267 n_pairs,
9268 t,
9269 m.up_exps.qtype,
9270 m.up_exps.row_bytes,
9271 )?,
9272 )
9273 } else {
9274 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
9275 (
9276 e.moe_pairs_matvec_q8_dec(
9277 &dev.ptr_row,
9278 0,
9279 &exi,
9280 &exo,
9281 &exp_d,
9282 &pt,
9283 &zq,
9284 &zd,
9285 n_embd,
9286 n_ff_exp,
9287 n_expert,
9288 n_active,
9289 n_pairs,
9290 m.gate_exps.qtype,
9291 m.gate_exps.row_bytes,
9292 )?,
9293 e.moe_pairs_matvec_q8_dec(
9294 &dev.ptr_row,
9295 1,
9296 &exi,
9297 &exo,
9298 &exp_d,
9299 &pt,
9300 &zq,
9301 &zd,
9302 n_embd,
9303 n_ff_exp,
9304 n_expert,
9305 n_active,
9306 n_pairs,
9307 m.up_exps.qtype,
9308 m.up_exps.row_bytes,
9309 )?,
9310 )
9311 };
9312 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
9313 let pself = e.htod_i32(&pair_self)?;
9314 let y_down = if mma {
9326 let in_pad = n_ff_exp.div_ceil(256) * 256;
9327 let a_scr = if crate::moe_fuse_actq_on() {
9328 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
9329 } else {
9330 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
9331 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
9332 };
9333 e.mmq_iq_experts(
9334 &dev.ptr_row,
9335 2,
9336 n_expert,
9337 &exi,
9338 &exo,
9339 &exp_d,
9340 &pself,
9341 &a_scr,
9342 in_pad,
9343 n_embd,
9344 n_active,
9345 n_pairs,
9346 n_pairs,
9347 m.down_exps.qtype,
9348 m.down_exps.row_bytes,
9349 )?
9350 } else {
9351 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
9352 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
9353 e.moe_pairs_matvec_q8_dec(
9354 &dev.ptr_row,
9355 2,
9356 &exi,
9357 &exo,
9358 &exp_d,
9359 &pself,
9360 &aq2,
9361 &ad2,
9362 n_ff_exp,
9363 n_embd,
9364 n_expert,
9365 n_active,
9366 n_pairs,
9367 m.down_exps.qtype,
9368 m.down_exps.row_bytes,
9369 )?
9370 };
9371 let mut moe_out = e.uninit(t * n_embd)?;
9372 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
9373 return Ok(moe_out);
9374 }
9375
9376 let g_len = m.gate_exps.expert_stride;
9377 let u_len = m.up_exps.expert_stride;
9378 let d_len = m.down_exps.expert_stride;
9379 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
9383 let (mut sg, mut su, mut sd) = if dev.is_some() {
9384 (None, None, None)
9385 } else {
9386 (
9387 Some(e.alloc_u8_uninit(g_len)?),
9388 Some(e.alloc_u8_uninit(u_len)?),
9389 Some(e.alloc_u8_uninit(d_len)?),
9390 )
9391 };
9392 let mut moe_out = e.zeros(t * n_embd)?;
9393 for tok in 0..t {
9394 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
9395 let w = &w_all[tok * n_used..(tok + 1) * n_used];
9396 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
9397 for (j, &ex) in sel.iter().enumerate() {
9398 let ex = ex as usize;
9399 let gate = match dev {
9400 Some(d) => e.qmatvec_view(
9401 &d.gate,
9402 ex * g_len..(ex + 1) * g_len,
9403 &zt,
9404 1,
9405 m.gate_exps.in_f,
9406 m.gate_exps.out_f,
9407 m.gate_exps.qtype,
9408 m.gate_exps.row_bytes,
9409 )?,
9410 None => {
9411 let sg = sg.as_mut().unwrap();
9412 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
9413 e.qmatvec_view(
9414 sg,
9415 0..g_len,
9416 &zt,
9417 1,
9418 m.gate_exps.in_f,
9419 m.gate_exps.out_f,
9420 m.gate_exps.qtype,
9421 m.gate_exps.row_bytes,
9422 )?
9423 }
9424 };
9425 let up = match dev {
9426 Some(d) => e.qmatvec_view(
9427 &d.up,
9428 ex * u_len..(ex + 1) * u_len,
9429 &zt,
9430 1,
9431 m.up_exps.in_f,
9432 m.up_exps.out_f,
9433 m.up_exps.qtype,
9434 m.up_exps.row_bytes,
9435 )?,
9436 None => {
9437 let su = su.as_mut().unwrap();
9438 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
9439 e.qmatvec_view(
9440 su,
9441 0..u_len,
9442 &zt,
9443 1,
9444 m.up_exps.in_f,
9445 m.up_exps.out_f,
9446 m.up_exps.qtype,
9447 m.up_exps.row_bytes,
9448 )?
9449 }
9450 };
9451 let mut act = e.uninit(n_ff_exp)?;
9452 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
9453 let actv = act.slice(0..n_ff_exp);
9454 let y = match dev {
9455 Some(d) => e.qmatvec_view(
9456 &d.down,
9457 ex * d_len..(ex + 1) * d_len,
9458 &actv,
9459 1,
9460 m.down_exps.in_f,
9461 m.down_exps.out_f,
9462 m.down_exps.qtype,
9463 m.down_exps.row_bytes,
9464 )?,
9465 None => {
9466 let sd = sd.as_mut().unwrap();
9467 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
9468 e.qmatvec_view(
9469 sd,
9470 0..d_len,
9471 &actv,
9472 1,
9473 m.down_exps.in_f,
9474 m.down_exps.out_f,
9475 m.down_exps.qtype,
9476 m.down_exps.row_bytes,
9477 )?
9478 }
9479 };
9480 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
9481 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
9482 }
9483 }
9484 Ok(moe_out)
9485 }
9486
9487 fn gemma4_layer(
9489 &self,
9490 e: &Engine,
9491 il: usize,
9492 layer: &crate::hybrid::HybridLayer,
9493 x: &CudaSlice<f32>,
9494 pos_d: &CudaSlice<i32>,
9495 t: usize,
9496 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9497 let n_embd = self.cfg.n_embd as usize;
9498 let eps = self.cfg.rms_eps;
9499
9500 let mut h = e.zeros(t * n_embd)?;
9501 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
9502 let Mixer::Full(fa) = &layer.mixer else {
9503 panic!("gemma4 layer {il} not full-attn")
9504 };
9505 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
9506 let mut cur = e.zeros(t * n_embd)?;
9508 e.rms_norm(
9509 &o,
9510 layer.post_attn_norm.float_data(),
9511 &mut cur,
9512 n_embd,
9513 t,
9514 eps,
9515 )?;
9516 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
9517 }
9518
9519 fn gemma4_layer_tail_add(
9523 &self,
9524 e: &Engine,
9525 layer: &crate::hybrid::HybridLayer,
9526 cur: &CudaSlice<f32>,
9527 x: &CudaSlice<f32>,
9528 t: usize,
9529 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9530 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
9531 }
9532
9533 fn gemma4_layer_tail_add_n(
9536 &self,
9537 e: &Engine,
9538 layer: &crate::hybrid::HybridLayer,
9539 cur: &CudaSlice<f32>,
9540 x: &CudaSlice<f32>,
9541 t: usize,
9542 next_norm: Option<&CudaSlice<f32>>,
9543 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
9544 let n_embd = self.cfg.n_embd as usize;
9545 let bits = layer.gemma4.as_ref().unwrap();
9546 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
9547 let mut xn = e.uninit(t * n_embd)?;
9548 match next_norm {
9549 Some(w) => {
9550 let mut hn = e.uninit(t * n_embd)?;
9551 e.add_scale_rms_norm(
9552 &sn,
9553 &attn_out,
9554 bits.layer_scale,
9555 w,
9556 &mut xn,
9557 &mut hn,
9558 n_embd,
9559 t,
9560 self.cfg.rms_eps,
9561 )?;
9562 Ok((xn, Some(hn)))
9563 }
9564 None => {
9565 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
9566 Ok((xn, None))
9567 }
9568 }
9569 }
9570
9571 fn gemma4_layer_tail_core(
9574 &self,
9575 e: &Engine,
9576 layer: &crate::hybrid::HybridLayer,
9577 cur: &CudaSlice<f32>,
9578 x: &CudaSlice<f32>,
9579 t: usize,
9580 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9581 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
9582 }
9583
9584 fn gemma4_layer_tail_core_pn(
9591 &self,
9592 e: &Engine,
9593 layer: &crate::hybrid::HybridLayer,
9594 cur: &CudaSlice<f32>,
9595 x: &CudaSlice<f32>,
9596 t: usize,
9597 pre_norm: Option<&CudaSlice<f32>>,
9598 defer_post_norm: bool,
9599 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9600 let n_embd = self.cfg.n_embd as usize;
9601 let eps = self.cfg.rms_eps;
9602 let bits = layer.gemma4.as_ref().unwrap();
9603
9604 let Some(mbits) = bits.moe_bits.as_ref() else {
9607 let crate::hybrid::Ffn::Dense {
9608 ffn_gate,
9609 ffn_up,
9610 ffn_down,
9611 } = &layer.ffn
9612 else {
9613 panic!("gemma4 dense layer without Dense ffn")
9614 };
9615 let mut attn_out = e.uninit(t * n_embd)?;
9616 let mut zsh = e.uninit(t * n_embd)?;
9617 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
9620 match pre_norm {
9621 Some(wa) if t == 1 => {
9622 zpair = Some(e.rms_pre_add_rms_norm_q8z(
9623 cur,
9624 wa,
9625 x,
9626 bits.ffn_norm.float_data(),
9627 &mut attn_out,
9628 &mut zsh,
9629 n_embd,
9630 t,
9631 eps,
9632 )?);
9633 }
9634 Some(wa) => e.rms_pre_add_rms_norm(
9635 cur,
9636 wa,
9637 x,
9638 bits.ffn_norm.float_data(),
9639 &mut attn_out,
9640 &mut zsh,
9641 n_embd,
9642 t,
9643 eps,
9644 )?,
9645 None => e.add_rms_norm(
9646 cur,
9647 x,
9648 bits.ffn_norm.float_data(),
9649 &mut attn_out,
9650 &mut zsh,
9651 n_embd,
9652 t,
9653 eps,
9654 )?,
9655 }
9656 let n_ff = ffn_gate.out_features();
9657 let (gate, up) = if t == 1 {
9663 let (zq, zd) = match zpair {
9664 Some(p) => p,
9665 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
9666 };
9667 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
9668 Some(p) => p,
9669 None => (
9670 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
9671 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
9672 ),
9673 }
9674 } else {
9675 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9680 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
9681 let fused = if f2b {
9682 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
9683 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
9684 } else {
9685 None
9686 };
9687 match fused {
9688 Some(p) => p,
9689 None => {
9690 e.mmq_act_begin();
9692 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
9693 }
9694 }
9695 };
9696 let mut act = e.uninit(t * n_ff)?;
9697 let f0 = if e.uses_q8_1_fast(ffn_down) {
9700 let upv = e.view(&up, t * n_ff);
9701 let up_all = upv.slice(0..t * n_ff);
9702 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
9703 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
9704 } else {
9705 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
9706 e.matmul(ffn_down, &act, t)?
9707 };
9708 if defer_post_norm {
9709 return Ok((f0, attn_out));
9710 }
9711 let mut sn = e.uninit(t * n_embd)?;
9712 e.rms_norm(
9713 &f0,
9714 bits.post_ffw_norm.float_data(),
9715 &mut sn,
9716 n_embd,
9717 t,
9718 eps,
9719 )?;
9720 return Ok((sn, attn_out));
9721 };
9722
9723 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
9724 let mut attn_out = e.uninit(t * n_embd)?;
9729 let mut router_in = e.uninit(t * n_embd)?;
9730 let fast_moe = match &layer.ffn {
9731 crate::hybrid::Ffn::Moe(m) => {
9732 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
9733 && expert_dp4a_supported(m.gate_exps.qtype)
9734 && expert_dp4a_supported(m.up_exps.qtype)
9735 && expert_dp4a_supported(m.down_exps.qtype)
9736 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
9737 }
9738 _ => false,
9739 };
9740 let q8z = t < PRIME_MIN_T && fast_moe;
9741 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
9742 let (z0, m2) = e.add_rms_norm3_q8z(
9743 cur,
9744 x,
9745 bits.ffn_norm.float_data(),
9746 &mbits.router_scale_pre,
9747 mbits.pre_ffw_norm_2.float_data(),
9748 &mut attn_out,
9749 &mut router_in,
9750 n_embd,
9751 t,
9752 eps,
9753 )?;
9754 (None, Some(z0), Some(m2))
9755 } else {
9756 let mut zsh = e.uninit(t * n_embd)?;
9757 let mut moe_in = e.uninit(t * n_embd)?;
9758 e.add_rms_norm3(
9759 cur,
9760 x,
9761 bits.ffn_norm.float_data(),
9762 &mbits.router_scale_pre,
9763 mbits.pre_ffw_norm_2.float_data(),
9764 &mut attn_out,
9765 &mut zsh,
9766 &mut router_in,
9767 &mut moe_in,
9768 n_embd,
9769 t,
9770 eps,
9771 )?;
9772 (Some((zsh, moe_in)), None, None)
9773 };
9774 let attn_out2 = attn_out;
9775 #[allow(unused_variables)]
9776 let attn_out = &attn_out2;
9777 let n_ff = mbits.shared_gate.out_features();
9778 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
9779 if t == 1 {
9780 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
9781 Some(p) => p,
9782 None => {
9783 let h0 = e.zeros(0)?;
9784 (
9785 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
9786 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
9787 )
9788 }
9789 }
9790 } else {
9791 let h0 = e.zeros(0)?;
9793 (
9794 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
9795 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
9796 )
9797 }
9798 } else {
9799 let (zsh, _) = zsh_f32.as_ref().unwrap();
9800 (
9801 e.matmul(&mbits.shared_gate, zsh, t)?,
9802 e.matmul(&mbits.shared_up, zsh, t)?,
9803 )
9804 };
9805 let mut act = e.uninit(t * n_ff)?;
9806 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
9807 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
9808 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
9809 panic!("gemma4 layer not MoE")
9810 };
9811 let moe0 = match (&moe_q8, &zsh_f32) {
9812 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
9813 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
9814 _ => unreachable!(),
9815 };
9816 let mut mlp = e.uninit(t * n_embd)?;
9818 let mut moe = e.uninit(t * n_embd)?;
9819 e.rms_norm2x(
9820 &mlp0,
9821 &moe0,
9822 mbits.post_ffw_norm_1.float_data(),
9823 mbits.post_ffw_norm_2.float_data(),
9824 &mut mlp,
9825 &mut moe,
9826 n_embd,
9827 t,
9828 eps,
9829 )?;
9830
9831 let mut sum = e.uninit(t * n_embd)?;
9834 let mut sn = e.uninit(t * n_embd)?;
9835 e.add_rms_norm(
9836 &mlp,
9837 &moe,
9838 bits.post_ffw_norm.float_data(),
9839 &mut sum,
9840 &mut sn,
9841 n_embd,
9842 t,
9843 eps,
9844 )?;
9845 Ok((sn, attn_out2))
9846 }
9847
9848 fn gemma4_layer_tail_add_nq(
9850 &self,
9851 e: &Engine,
9852 layer: &crate::hybrid::HybridLayer,
9853 cur: &CudaSlice<f32>,
9854 x: &CudaSlice<f32>,
9855 t: usize,
9856 next_norm: Option<&CudaSlice<f32>>,
9857 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
9858 {
9859 let n_embd = self.cfg.n_embd as usize;
9860 let bits = layer.gemma4.as_ref().unwrap();
9861 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
9862 let mut xn = e.uninit(t * n_embd)?;
9863 match next_norm {
9864 Some(w) => {
9865 let pair = e.add_scale_rms_norm_q8_1(
9866 &sn,
9867 &attn_out,
9868 bits.layer_scale,
9869 w,
9870 &mut xn,
9871 n_embd,
9872 t,
9873 self.cfg.rms_eps,
9874 )?;
9875 Ok((xn, Some(pair)))
9876 }
9877 None => {
9878 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
9879 Ok((xn, None))
9880 }
9881 }
9882 }
9883
9884 fn gemma4_forward(
9887 &self,
9888 e: &Engine,
9889 tokens: &[u32],
9890 last_only: bool,
9891 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9892 if self.is_gemma4_e4b() {
9895 return self.gemma4_e4b_forward(e, tokens, last_only);
9896 }
9897 let n_embd = self.cfg.n_embd as usize;
9898 let t = tokens.len();
9899 let pos: Vec<i32> = (0..t as i32).collect();
9900 let pos_d = e.htod_i32(&pos)?;
9901
9902 let mut x = self.embed(e, tokens)?;
9903 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
9904 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
9907 let stat =
9908 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
9909 let h = e.dtoh(x)?;
9910 let bad = h.iter().filter(|v| !v.is_finite()).count();
9911 let mx = h
9912 .iter()
9913 .filter(|v| v.is_finite())
9914 .fold(0.0f32, |m, v| m.max(v.abs()));
9915 eprintln!(
9916 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
9917 &h[..3]
9918 );
9919 Ok(())
9920 };
9921 if probe {
9922 stat(e, &x, "embed")?;
9923 }
9924 for (il, layer) in self.layers.iter().enumerate() {
9925 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
9926 if probe {
9927 stat(e, &x, &format!("L{il}"))?;
9928 }
9929 }
9930 let mut hn = e.zeros(t * n_embd)?;
9931 e.rms_norm(
9932 &x,
9933 self.output_norm.float_data(),
9934 &mut hn,
9935 n_embd,
9936 t,
9937 self.cfg.rms_eps,
9938 )?;
9939 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
9940 let n_vocab = self.output.out_features();
9941 let logits = if last_only {
9942 let hv = e.view(&hn, t * n_embd);
9943 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
9944 let mut hlast = e.zeros(n_embd)?;
9945 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
9946 let mut ld = e.matmul(&self.output, &hlast, 1)?;
9947 e.softcap(&mut ld, cap, n_vocab)?;
9948 self.gemma4_suppress(e, &mut ld, 1)?;
9949 e.dtoh(&ld)?
9950 } else {
9951 let mut ld = e.matmul(&self.output, &hn, t)?;
9952 e.softcap(&mut ld, cap, t * n_vocab)?;
9953 self.gemma4_suppress(e, &mut ld, t)?;
9954 e.dtoh(&ld)?
9955 };
9956 Ok(logits)
9957 }
9958
9959 pub(crate) fn gemma4_prime(
9964 &self,
9965 e: &Engine,
9966 tokens: &[u32],
9967 cache: &mut Cache,
9968 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9969 if cache.pos != 0 {
9974 return Err(
9975 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
9976 — prime the full prompt in one call or decode tokenwise"
9977 .into(),
9978 );
9979 }
9980 let n_embd = self.cfg.n_embd as usize;
9981 let eps = self.cfg.rms_eps;
9982 let t = tokens.len();
9983 let pos: Vec<i32> = (0..t as i32).collect();
9984 let pos_d = e.htod_i32(&pos)?;
9985 let mut x = self.embed(e, tokens)?;
9986 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
9987 for (il, layer) in self.layers.iter().enumerate() {
9988 let mut h = e.zeros(t * n_embd)?;
9989 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
9990 let Mixer::Full(fa) = &layer.mixer else {
9991 panic!("gemma4 layer not full-attn")
9992 };
9993 let o = self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache))?;
9994 let mut cur = e.zeros(t * n_embd)?;
9995 e.rms_norm(
9996 &o,
9997 layer.post_attn_norm.float_data(),
9998 &mut cur,
9999 n_embd,
10000 t,
10001 eps,
10002 )?;
10003 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
10004 self.dflash_tap(e, cache, il, &x, t)?;
10005 }
10006 cache.pos += t;
10007 let hiddens = e.clone_dtod(&x)?;
10008 let xv = e.view(&x, t * n_embd);
10009 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
10010 let mut h_seed = e.zeros(n_embd)?;
10011 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
10012 let mut hn = e.uninit(n_embd)?;
10013 e.rms_norm(
10014 &h_seed,
10015 self.output_norm.float_data(),
10016 &mut hn,
10017 n_embd,
10018 1,
10019 eps,
10020 )?;
10021 let mut ld = e.matmul(&self.output, &hn, 1)?;
10022 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
10023 e.softcap(&mut ld, cap, self.output.out_features())?;
10024 self.gemma4_suppress(e, &mut ld, 1)?;
10025 let logits = e.dtoh(&ld)?;
10026 Ok((logits, h_seed, hiddens))
10027 }
10028
10029 fn gemma4_decode_attn(
10034 &self,
10035 e: &Engine,
10036 fa: &crate::hybrid::FullAttnLayer,
10037 il: usize,
10038 hq: &CudaSlice<i8>,
10039 hdq: &CudaSlice<f32>,
10040 pos_d: &CudaSlice<i32>,
10041 cache: &mut Cache,
10042 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10043 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10044 let eps = self.cfg.rms_eps;
10045 let aux = self.gemma4_aux.as_ref().unwrap();
10046 let ones = aux.ones(e);
10047 #[cfg(debug_assertions)]
10048 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
10049 let (hq, hdq) = (hq, hdq);
10050 let h0 = e.zeros(0)?;
10051 let h = &h0;
10052 let (q0, k0, v0) = if swa {
10053 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
10054 Some(t3) => t3,
10055 None => (
10056 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
10057 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
10058 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
10059 ),
10060 }
10061 } else {
10062 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
10063 Some(p) => p,
10064 None => (
10065 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
10066 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
10067 ),
10068 };
10069 let v0 = e.clone_dtod(&k0)?;
10070 (q0, k0, v0)
10071 };
10072 let mut q = e.uninit(nh * hd)?;
10073 let mut k = e.uninit(nkv * hd)?;
10074 let mut v = e.uninit(nkv * hd)?;
10075 let ff = if swa {
10078 None
10079 } else {
10080 Some(
10081 aux.rope_freqs(e)
10082 .expect("gemma4 global rope needs rope_freqs.weight"),
10083 )
10084 };
10085 #[cfg(debug_assertions)]
10086 if let Some(ff) = ff {
10087 crate::debug_assert_tensor_stream_device(
10088 ff,
10089 &e.stream(),
10090 "gemma4_decode_attn.rope_freqs",
10091 );
10092 }
10093 e.rms_norm_qkv_rope(
10094 &q0,
10095 &k0,
10096 &v0,
10097 fa.q_norm.float_data(),
10098 fa.k_norm.float_data(),
10099 ones,
10100 &mut q,
10101 &mut k,
10102 &mut v,
10103 hd,
10104 nh,
10105 nkv,
10106 pos_d,
10107 nh,
10108 nkv,
10109 base,
10110 1.0,
10111 ff,
10112 eps,
10113 )?;
10114 let kvl = cache.kv[il].as_mut().unwrap();
10115 e.append_kv_quantized(
10116 &k,
10117 &v,
10118 &mut kvl.k,
10119 &mut kvl.v,
10120 kvl.len,
10121 kvl.kv_dim_k,
10122 kvl.kv_dim_v,
10123 kvl.k_tok_bytes,
10124 kvl.v_tok_bytes,
10125 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
10126 )?;
10127 kvl.len += 1;
10128 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
10132 let mut attn = e.uninit(nh * hd)?;
10133 if !swa
10135 && hd == 512
10136 && kvl.len >= crate::fa512_min_tkv()
10137 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
10138 {
10139 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
10140 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
10141 let base = kvl.len as i32;
10143 e.i32_set_k(&mut kvl.len_d, base)?;
10144 e.fa_decode_rows(
10145 &q,
10146 &kp,
10147 &vp,
10148 &mut attn,
10149 hd,
10150 nh,
10151 nkv,
10152 kvl.len - 1,
10153 1,
10154 scale,
10155 kvl.k_tok_bytes,
10156 kvl.v_tok_bytes,
10157 Some((&kvl.len_d, -1)),
10158 false,
10159 false,
10160 None,
10161 )?;
10162 return Ok(e.matmul(&fa.wo, &attn, 1)?);
10163 }
10164 if swa
10166 && kvl.len > win
10167 && hd == 256
10168 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
10169 {
10170 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
10171 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
10172 let base = kvl.len as i32;
10173 e.i32_set_k(&mut kvl.len_d, base)?;
10174 e.fa_decode_rows_w(
10175 &q,
10176 &kp,
10177 &vp,
10178 &mut attn,
10179 hd,
10180 nh,
10181 nkv,
10182 &kvl.len_d,
10183 -1,
10184 1,
10185 scale,
10186 win,
10187 kvl.k_tok_bytes,
10188 kvl.v_tok_bytes,
10189 None,
10190 )?;
10191 return Ok(e.matmul(&fa.wo, &attn, 1)?);
10192 }
10193 let (off_tok, t_kv) = if swa && kvl.len > win {
10194 (kvl.len - win, win)
10195 } else {
10196 (0, kvl.len)
10197 };
10198 let k_view = e.view_u8_range(
10199 &kvl.k,
10200 off_tok * kvl.k_tok_bytes,
10201 (off_tok + t_kv) * kvl.k_tok_bytes,
10202 );
10203 let v_view = e.view_u8_range(
10204 &kvl.v,
10205 off_tok * kvl.v_tok_bytes,
10206 (off_tok + t_kv) * kvl.v_tok_bytes,
10207 );
10208 e.fa_decode_kvmod(
10209 &q,
10210 &k_view,
10211 &v_view,
10212 &mut attn,
10213 hd,
10214 nh,
10215 nkv,
10216 t_kv,
10217 scale,
10218 kvl.k_tok_bytes,
10219 kvl.v_tok_bytes,
10220 swa && crate::Engine::wkv_on(),
10221 )?;
10222 Ok(e.matmul(&fa.wo, &attn, 1)?)
10223 }
10224
10225 #[allow(clippy::too_many_arguments)]
10232 pub fn gemma4_decode_step_dc(
10233 &self,
10234 e: &Engine,
10235 token_d: &CudaSlice<u32>,
10236 pos_d: &mut CudaSlice<i32>,
10237 embd_gpu: &CudaSlice<u8>,
10238 embd_qt: i32,
10239 embd_rb: usize,
10240 cache: &mut Cache,
10241 n_vocab: usize,
10242 cap_bucket_max: Option<(usize, usize)>,
10243 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
10244 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
10245 self.gemma4_decode_step_dc_into(
10246 e,
10247 token_d,
10248 pos_d,
10249 embd_gpu,
10250 embd_qt,
10251 embd_rb,
10252 cache,
10253 n_vocab,
10254 cap_bucket_max,
10255 &mut tok_out,
10256 )?;
10257 Ok(tok_out)
10258 }
10259
10260 #[allow(clippy::too_many_arguments)]
10263 pub fn gemma4_decode_step_dc_into(
10264 &self,
10265 e: &Engine,
10266 token_d: &CudaSlice<u32>,
10267 pos_d: &mut CudaSlice<i32>,
10268 embd_gpu: &CudaSlice<u8>,
10269 embd_qt: i32,
10270 embd_rb: usize,
10271 cache: &mut Cache,
10272 n_vocab: usize,
10273 cap_bucket_max: Option<(usize, usize)>,
10274 tok_out: &mut CudaSlice<u32>,
10275 ) -> Result<(), Box<dyn std::error::Error>> {
10276 let n_embd = self.cfg.n_embd as usize;
10277 let eps = self.cfg.rms_eps;
10278 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
10279 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
10280 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
10281 let n_layers = self.layers.len();
10282 for (il, layer) in self.layers.iter().enumerate() {
10283 let (hq, hdq) = match h_carry.take() {
10284 Some(p) => p,
10285 None => {
10286 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
10287 }
10288 };
10289 let Mixer::Full(fa) = &layer.mixer else {
10290 panic!("gemma4 layer {il} not full-attn")
10291 };
10292 let o =
10293 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
10294 let mut cur = e.uninit(n_embd)?;
10295 e.rms_norm(
10296 &o,
10297 layer.post_attn_norm.float_data(),
10298 &mut cur,
10299 n_embd,
10300 1,
10301 eps,
10302 )?;
10303 let next_norm = if il + 1 < n_layers {
10304 Some(self.layers[il + 1].attn_norm.float_data())
10305 } else {
10306 None
10307 };
10308 let (xn, hn) = self.gemma4_layer_tail_add_nq(e, layer, &cur, &x, 1, next_norm)?;
10309 x = xn;
10310 h_carry = hn;
10311 }
10312 let mut hn = e.uninit(n_embd)?;
10313 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
10314 let mut logits = e.matmul(&self.output, &hn, 1)?;
10315 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
10317 e.inc_seqlen(pos_d)?;
10318 if cap_bucket_max.is_none() {
10319 cache.pos += 1;
10320 }
10321 Ok(())
10322 }
10323
10324 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
10331 let n_embd = self.cfg.n_embd as usize;
10332 let n_vocab = self.output.out_features();
10333 let n_layers = self.layers.len();
10334 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
10335 for il in 0..n_layers {
10336 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
10337 qmax = qmax.max(nh * hd);
10338 kvmax = kvmax.max(nkv * hd);
10339 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
10340 ffmax = ffmax.max(ffn_gate.out_features());
10341 }
10342 }
10343 Ok(G4DcSlots {
10344 x: e.uninit(n_embd)?,
10345 xn: e.uninit(n_embd)?,
10346 cur: e.uninit(n_embd)?,
10347 hq: e.alloc_i8_uninit(n_embd)?,
10348 hd_: e.uninit(n_embd / 32)?,
10349 q0: e.uninit(qmax)?,
10350 k0: e.uninit(kvmax)?,
10351 v0: e.uninit(kvmax)?,
10352 q: e.uninit(qmax)?,
10353 k: e.uninit(kvmax)?,
10354 v: e.uninit(kvmax)?,
10355 attn: e.uninit(qmax)?,
10356 o: e.uninit(n_embd)?,
10357 attn_out: e.uninit(n_embd)?,
10358 zsh: e.uninit(n_embd)?,
10359 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
10362 zd: e.uninit(n_embd.max(qmax) / 32)?,
10363 gate: e.uninit(ffmax)?,
10364 up: e.uninit(ffmax)?,
10365 act: e.uninit(ffmax)?,
10366 actq: e.alloc_i8_uninit(ffmax)?,
10367 actd: e.uninit(ffmax / 32)?,
10368 f0: e.uninit(n_embd)?,
10369 sn: e.uninit(n_embd)?,
10370 hn: e.uninit(n_embd)?,
10371 logits: e.uninit(n_vocab)?,
10372 })
10373 }
10374
10375 fn g4_matvec_m1_into(
10378 &self,
10379 e: &Engine,
10380 w: &crate::model::GpuTensor,
10381 aq: &CudaSlice<i8>,
10382 ad: &CudaSlice<f32>,
10383 y: &mut CudaSlice<f32>,
10384 ) -> Result<(), Box<dyn std::error::Error>> {
10385 use crate::model::GpuTensor;
10386 let (bytes, qtype, row_bytes, scale, rp) = match w {
10387 GpuTensor::Quant {
10388 bytes,
10389 qtype,
10390 row_bytes,
10391 scale,
10392 rp,
10393 ..
10394 } => (bytes, *qtype, *row_bytes, *scale, *rp),
10395 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
10396 };
10397 let (mbytes, mrp) = match w {
10398 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10399 _ => (bytes, rp),
10400 };
10401 e.qmatvec_mmvq_into(
10402 mbytes,
10403 aq,
10404 ad,
10405 1,
10406 w.in_features(),
10407 w.out_features(),
10408 qtype,
10409 row_bytes,
10410 scale,
10411 mrp,
10412 y,
10413 )
10414 }
10415
10416 #[allow(clippy::too_many_arguments)]
10420 pub fn gemma4_decode_step_dc_slotted(
10421 &self,
10422 e: &Engine,
10423 token_d: &CudaSlice<u32>,
10424 pos_d: &mut CudaSlice<i32>,
10425 embd_gpu: &CudaSlice<u8>,
10426 embd_qt: i32,
10427 embd_rb: usize,
10428 cache: &mut Cache,
10429 n_vocab: usize,
10430 cap_bucket_max: Option<(usize, usize)>,
10431 sl: &mut G4DcSlots,
10432 tok_out: &mut CudaSlice<u32>,
10433 ring: Option<(&mut CudaSlice<u32>, usize)>,
10434 ) -> Result<(), Box<dyn std::error::Error>> {
10435 let n_embd = self.cfg.n_embd as usize;
10436 let eps = self.cfg.rms_eps;
10437 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
10438 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
10439 let n_layers = self.layers.len();
10440 let mut has_carry = false;
10441 for il in 0..n_layers {
10442 if !has_carry {
10443 e.rms_norm_q8_1_into(
10444 &sl.x,
10445 self.layers[il].attn_norm.float_data(),
10446 n_embd,
10447 1,
10448 eps,
10449 &mut sl.hq,
10450 &mut sl.hd_,
10451 )?;
10452 }
10453 has_carry = true;
10454 let layer = &self.layers[il];
10455 let Mixer::Full(fa) = &layer.mixer else {
10456 panic!("gemma4 layer {il} not full-attn")
10457 };
10458 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
10459 e.rms_norm(
10460 &sl.o,
10461 layer.post_attn_norm.float_data(),
10462 &mut sl.cur,
10463 n_embd,
10464 1,
10465 eps,
10466 )?;
10467 let next_norm = if il + 1 < n_layers {
10468 Some(self.layers[il + 1].attn_norm.float_data())
10469 } else {
10470 None
10471 };
10472 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
10473 std::mem::swap(&mut sl.x, &mut sl.xn);
10474 }
10475 e.rms_norm(
10476 &sl.x,
10477 self.output_norm.float_data(),
10478 &mut sl.hn,
10479 n_embd,
10480 1,
10481 eps,
10482 )?;
10483 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
10484 {
10486 let (zq, zd) = (&sl.zq, &sl.zd);
10487 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
10488 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
10489 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
10490 }
10491 self.gemma4_suppress(e, &mut sl.logits, 1)?;
10492 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
10493 if let Some((ring, base)) = ring {
10494 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
10498 }
10499 e.inc_seqlen(pos_d)?;
10500 if cap_bucket_max.is_none() {
10501 cache.pos += 1;
10502 }
10503 Ok(())
10504 }
10505
10506 #[allow(clippy::too_many_arguments)]
10508 fn gemma4_decode_attn_dc_slotted(
10509 &self,
10510 e: &Engine,
10511 fa: &crate::hybrid::FullAttnLayer,
10512 il: usize,
10513 pos_d: &CudaSlice<i32>,
10514 cache: &mut Cache,
10515 cap_bucket_max: Option<(usize, usize)>,
10516 sl: &mut G4DcSlots,
10517 ) -> Result<(), Box<dyn std::error::Error>> {
10518 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10519 let eps = self.cfg.rms_eps;
10520 let aux = self.gemma4_aux.as_ref().unwrap();
10521 let ones = aux.ones(e);
10522 #[cfg(debug_assertions)]
10523 crate::debug_assert_tensor_stream_device(
10524 ones,
10525 &e.stream(),
10526 "gemma4_decode_attn_dc_slotted.ones",
10527 );
10528 {
10529 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
10530 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
10531 if swa {
10532 if !e.matmul_q4_fused3_into(
10533 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
10534 )? {
10535 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
10536 }
10537 } else {
10538 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)? {
10539 return Err("slotted step: fused2 unavailable".into());
10540 }
10541 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
10542 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
10543 }
10544 }
10545 let ff = if swa {
10548 None
10549 } else {
10550 Some(
10551 aux.rope_freqs(e)
10552 .expect("gemma4 global rope needs rope_freqs.weight"),
10553 )
10554 };
10555 #[cfg(debug_assertions)]
10556 if let Some(ff) = ff {
10557 crate::debug_assert_tensor_stream_device(
10558 ff,
10559 &e.stream(),
10560 "gemma4_decode_attn_dc_slotted.rope_freqs",
10561 );
10562 }
10563 let kvl = cache.kv[il].as_mut().unwrap();
10564 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
10565 if crate::Engine::qkv_append_on() {
10566 e.rms_norm_qkv_rope_append_dc(
10568 &sl.q0,
10569 &sl.k0,
10570 &sl.v0,
10571 fa.q_norm.float_data(),
10572 fa.k_norm.float_data(),
10573 ones,
10574 &mut sl.q,
10575 &mut sl.k,
10576 &mut sl.v,
10577 hd,
10578 nh,
10579 nkv,
10580 pos_d,
10581 nh,
10582 nkv,
10583 base,
10584 1.0,
10585 ff,
10586 eps,
10587 &mut kvl.k,
10588 &mut kvl.v,
10589 &kvl.len_d,
10590 kvl.k_tok_bytes,
10591 kvl.v_tok_bytes,
10592 kv_fp8,
10593 )?;
10594 } else {
10595 e.rms_norm_qkv_rope(
10596 &sl.q0,
10597 &sl.k0,
10598 &sl.v0,
10599 fa.q_norm.float_data(),
10600 fa.k_norm.float_data(),
10601 ones,
10602 &mut sl.q,
10603 &mut sl.k,
10604 &mut sl.v,
10605 hd,
10606 nh,
10607 nkv,
10608 pos_d,
10609 nh,
10610 nkv,
10611 base,
10612 1.0,
10613 ff,
10614 eps,
10615 )?;
10616 e.append_kv_quantized_dc(
10617 &sl.k,
10618 &sl.v,
10619 &mut kvl.k,
10620 &mut kvl.v,
10621 &kvl.len_d,
10622 kvl.kv_dim_k,
10623 kvl.kv_dim_v,
10624 kvl.k_tok_bytes,
10625 kvl.v_tok_bytes,
10626 kv_fp8,
10627 )?;
10628 }
10629 e.inc_seqlen(&mut kvl.len_d)?;
10630 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
10631 let k_view = e.view_u8(&kvl.k, kvl.k.len());
10632 let v_view = e.view_u8(&kvl.v, kvl.v.len());
10633 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
10634 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
10635 let mut fa_q8 = false;
10639 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
10640 e.fa_decode_rows(
10641 &sl.q,
10642 &k_view,
10643 &v_view,
10644 &mut sl.attn,
10645 hd,
10646 nh,
10647 nkv,
10648 b_glob - 1,
10649 1,
10650 scale,
10651 kvl.k_tok_bytes,
10652 kvl.v_tok_bytes,
10653 Some((&kvl.len_d, -1)),
10654 false,
10655 false,
10656 Some((&mut sl.zq, &mut sl.zd)),
10657 )?;
10658 fa_q8 = true;
10659 } else if swa && b_swa > win && hd == 256 && rows_on {
10660 e.fa_decode_rows_w(
10661 &sl.q,
10662 &k_view,
10663 &v_view,
10664 &mut sl.attn,
10665 hd,
10666 nh,
10667 nkv,
10668 &kvl.len_d,
10669 -1,
10670 1,
10671 scale,
10672 win,
10673 kvl.k_tok_bytes,
10674 kvl.v_tok_bytes,
10675 Some((&mut sl.zq, &mut sl.zd)),
10676 )?;
10677 fa_q8 = true;
10678 } else {
10679 let b = if swa { b_swa } else { b_glob };
10680 e.fa_decode_dc(
10681 &sl.q,
10682 &k_view,
10683 &v_view,
10684 &mut sl.attn,
10685 hd,
10686 nh,
10687 nkv,
10688 &kvl.len_d,
10689 b,
10690 scale,
10691 kvl.k_tok_bytes,
10692 kvl.v_tok_bytes,
10693 swa && crate::Engine::wkv_on(),
10694 )?;
10695 }
10696 if !fa_q8 {
10697 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
10698 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
10699 }
10700 {
10701 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
10702 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
10703 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
10704 }
10705 Ok(())
10706 }
10707
10708 fn gemma4_layer_tail_slotted(
10711 &self,
10712 e: &Engine,
10713 layer: &crate::hybrid::HybridLayer,
10714 next_norm: Option<&CudaSlice<f32>>,
10715 sl: &mut G4DcSlots,
10716 ) -> Result<(), Box<dyn std::error::Error>> {
10717 let n_embd = self.cfg.n_embd as usize;
10718 let eps = self.cfg.rms_eps;
10719 let bits = layer.gemma4.as_ref().unwrap();
10720 let crate::hybrid::Ffn::Dense {
10721 ffn_gate,
10722 ffn_up,
10723 ffn_down,
10724 } = &layer.ffn
10725 else {
10726 return Err("slotted tail: dense ffn only".into());
10727 };
10728 e.add_rms_norm(
10729 &sl.cur,
10730 &sl.x,
10731 bits.ffn_norm.float_data(),
10732 &mut sl.attn_out,
10733 &mut sl.zsh,
10734 n_embd,
10735 1,
10736 eps,
10737 )?;
10738 let n_ff = ffn_gate.out_features();
10739 {
10740 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
10741 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
10742 }
10743 {
10744 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
10745 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
10746 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)? {
10747 return Err("slotted tail: ffn fused2 unavailable".into());
10748 }
10749 }
10750 debug_assert!(e.uses_q8_1_fast(ffn_down));
10751 {
10752 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
10753 let upv = e.view(upr, n_ff);
10754 let up_all = upv.slice(0..n_ff);
10755 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
10756 e.gelu_tanh_mul_q8_1_into(
10757 gr,
10758 &up_all,
10759 &mut sl.act,
10760 n_ff,
10761 1,
10762 &mut sl.actq,
10763 &mut sl.actd,
10764 )?;
10765 }
10766 {
10767 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
10768 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
10769 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
10770 }
10771 e.rms_norm(
10772 &sl.f0,
10773 bits.post_ffw_norm.float_data(),
10774 &mut sl.sn,
10775 n_embd,
10776 1,
10777 eps,
10778 )?;
10779 match next_norm {
10780 Some(w) => {
10781 e.add_scale_rms_norm_q8_1_into(
10782 &sl.sn,
10783 &sl.attn_out,
10784 bits.layer_scale,
10785 w,
10786 &mut sl.xn,
10787 n_embd,
10788 1,
10789 eps,
10790 &mut sl.hq,
10791 &mut sl.hd_,
10792 )?;
10793 }
10794 None => {
10795 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
10796 }
10797 }
10798 Ok(())
10799 }
10800
10801 #[allow(clippy::too_many_arguments)]
10803 fn gemma4_decode_attn_dc(
10804 &self,
10805 e: &Engine,
10806 fa: &crate::hybrid::FullAttnLayer,
10807 il: usize,
10808 hq: &CudaSlice<i8>,
10809 hdq: &CudaSlice<f32>,
10810 pos_d: &CudaSlice<i32>,
10811 cache: &mut Cache,
10812 cap_bucket_max: Option<(usize, usize)>,
10813 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10814 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10815 let eps = self.cfg.rms_eps;
10816 let aux = self.gemma4_aux.as_ref().unwrap();
10817 let ones = aux.ones(e);
10818 #[cfg(debug_assertions)]
10819 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
10820 let (q0, k0, v0) = if swa {
10821 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
10822 Some(t3) => t3,
10823 None => {
10824 let h0 = e.zeros(0)?;
10825 (
10826 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
10827 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
10828 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
10829 )
10830 }
10831 }
10832 } else {
10833 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
10834 Some(p) => p,
10835 None => {
10836 let h0 = e.zeros(0)?;
10837 (
10838 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
10839 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
10840 )
10841 }
10842 };
10843 let v0 = e.clone_dtod(&k0)?;
10844 (q0, k0, v0)
10845 };
10846 let mut q = e.uninit(nh * hd)?;
10847 let mut k = e.uninit(nkv * hd)?;
10848 let mut v = e.uninit(nkv * hd)?;
10849 let ff = if swa {
10851 None
10852 } else {
10853 Some(
10854 aux.rope_freqs(e)
10855 .expect("gemma4 global rope needs rope_freqs.weight"),
10856 )
10857 };
10858 #[cfg(debug_assertions)]
10859 if let Some(ff) = ff {
10860 crate::debug_assert_tensor_stream_device(
10861 ff,
10862 &e.stream(),
10863 "gemma4_decode_attn_dc.rope_freqs",
10864 );
10865 }
10866 let kvl = cache.kv[il].as_mut().unwrap();
10867 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
10868 if crate::Engine::qkv_append_on() {
10869 e.rms_norm_qkv_rope_append_dc(
10871 &q0,
10872 &k0,
10873 &v0,
10874 fa.q_norm.float_data(),
10875 fa.k_norm.float_data(),
10876 ones,
10877 &mut q,
10878 &mut k,
10879 &mut v,
10880 hd,
10881 nh,
10882 nkv,
10883 pos_d,
10884 nh,
10885 nkv,
10886 base,
10887 1.0,
10888 ff,
10889 eps,
10890 &mut kvl.k,
10891 &mut kvl.v,
10892 &kvl.len_d,
10893 kvl.k_tok_bytes,
10894 kvl.v_tok_bytes,
10895 kv_fp8,
10896 )?;
10897 } else {
10898 e.rms_norm_qkv_rope(
10899 &q0,
10900 &k0,
10901 &v0,
10902 fa.q_norm.float_data(),
10903 fa.k_norm.float_data(),
10904 ones,
10905 &mut q,
10906 &mut k,
10907 &mut v,
10908 hd,
10909 nh,
10910 nkv,
10911 pos_d,
10912 nh,
10913 nkv,
10914 base,
10915 1.0,
10916 ff,
10917 eps,
10918 )?;
10919 e.append_kv_quantized_dc(
10920 &k,
10921 &v,
10922 &mut kvl.k,
10923 &mut kvl.v,
10924 &kvl.len_d,
10925 kvl.kv_dim_k,
10926 kvl.kv_dim_v,
10927 kvl.k_tok_bytes,
10928 kvl.v_tok_bytes,
10929 kv_fp8,
10930 )?;
10931 }
10932 e.inc_seqlen(&mut kvl.len_d)?;
10933 let mut attn = e.uninit(nh * hd)?;
10934 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
10937 match cap_bucket_max {
10942 None => {
10943 kvl.len += 1;
10947 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
10948 if !swa
10949 && hd == 512
10950 && kvl.len >= crate::fa512_min_tkv()
10951 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
10952 {
10953 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
10956 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
10957 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
10958 e.fa_decode_rows(
10959 &q,
10960 &kp,
10961 &vp,
10962 &mut attn,
10963 hd,
10964 nh,
10965 nkv,
10966 kvl.len - 1,
10967 1,
10968 scale,
10969 kvl.k_tok_bytes,
10970 kvl.v_tok_bytes,
10971 Some((&kvl.len_d, -1)),
10972 false,
10973 false,
10974 Some((&mut aq8, &mut ad8)),
10975 )?;
10976 fa_q8 = Some((aq8, ad8));
10977 } else if swa
10978 && kvl.len > win
10979 && hd == 256
10980 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
10981 {
10982 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
10984 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
10985 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
10986 e.fa_decode_rows_w(
10987 &q,
10988 &kp,
10989 &vp,
10990 &mut attn,
10991 hd,
10992 nh,
10993 nkv,
10994 &kvl.len_d,
10995 -1,
10996 1,
10997 scale,
10998 win,
10999 kvl.k_tok_bytes,
11000 kvl.v_tok_bytes,
11001 Some((&mut aq8, &mut ad8)),
11002 )?;
11003 fa_q8 = Some((aq8, ad8));
11004 } else {
11005 let (off_tok, t_kv) = if swa && kvl.len > win {
11006 (kvl.len - win, win)
11007 } else {
11008 (0, kvl.len)
11009 };
11010 let k_view = e.view_u8_range(
11011 &kvl.k,
11012 off_tok * kvl.k_tok_bytes,
11013 (off_tok + t_kv) * kvl.k_tok_bytes,
11014 );
11015 let v_view = e.view_u8_range(
11016 &kvl.v,
11017 off_tok * kvl.v_tok_bytes,
11018 (off_tok + t_kv) * kvl.v_tok_bytes,
11019 );
11020 e.fa_decode_kvmod(
11021 &q,
11022 &k_view,
11023 &v_view,
11024 &mut attn,
11025 hd,
11026 nh,
11027 nkv,
11028 t_kv,
11029 scale,
11030 kvl.k_tok_bytes,
11031 kvl.v_tok_bytes,
11032 swa && crate::Engine::wkv_on(),
11033 )?;
11034 }
11035 }
11036 Some((b_swa, b_glob)) => {
11037 let k_view = e.view_u8(&kvl.k, kvl.k.len());
11043 let v_view = e.view_u8(&kvl.v, kvl.v.len());
11044 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
11045 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11046 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
11047 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
11048 e.fa_decode_rows(
11049 &q,
11050 &k_view,
11051 &v_view,
11052 &mut attn,
11053 hd,
11054 nh,
11055 nkv,
11056 b_glob - 1,
11057 1,
11058 scale,
11059 kvl.k_tok_bytes,
11060 kvl.v_tok_bytes,
11061 Some((&kvl.len_d, -1)),
11062 false,
11063 false,
11064 Some((&mut aq8, &mut ad8)),
11065 )?;
11066 fa_q8 = Some((aq8, ad8));
11067 } else if swa && b_swa > win && hd == 256 && rows_on {
11068 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
11069 e.fa_decode_rows_w(
11070 &q,
11071 &k_view,
11072 &v_view,
11073 &mut attn,
11074 hd,
11075 nh,
11076 nkv,
11077 &kvl.len_d,
11078 -1,
11079 1,
11080 scale,
11081 win,
11082 kvl.k_tok_bytes,
11083 kvl.v_tok_bytes,
11084 Some((&mut aq8, &mut ad8)),
11085 )?;
11086 fa_q8 = Some((aq8, ad8));
11087 } else {
11088 let b = if swa { b_swa } else { b_glob };
11089 e.fa_decode_dc(
11090 &q,
11091 &k_view,
11092 &v_view,
11093 &mut attn,
11094 hd,
11095 nh,
11096 nkv,
11097 &kvl.len_d,
11098 b,
11099 scale,
11100 kvl.k_tok_bytes,
11101 kvl.v_tok_bytes,
11102 swa && crate::Engine::wkv_on(),
11103 )?;
11104 }
11105 }
11106 }
11107 if let Some((aq8, ad8)) = fa_q8 {
11110 let mut y = e.uninit(fa.wo.out_features())?;
11111 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
11112 return Ok(y);
11113 }
11114 Ok(e.matmul(&fa.wo, &attn, 1)?)
11115 }
11116
11117 pub fn gemma4_generate_graph(
11122 &self,
11123 e: &Engine,
11124 prompt_pos: usize,
11125 first_token: u32,
11126 cache: &mut Cache,
11127 max_new: usize,
11128 eos: &[u32],
11129 mut on_token: impl FnMut(u32) -> bool,
11130 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
11131 if self.is_gemma4_e4b() {
11132 return Err(
11133 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
11134 .into(),
11135 );
11136 }
11137 use crate::decode::StopReason;
11138 let n_vocab = self.output.out_features();
11139 let n_embd = self.cfg.n_embd as usize;
11140 let embd_gpu = self
11141 .embd_gpu
11142 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
11143 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
11144 for kvl in cache.kv.iter_mut().flatten() {
11145 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
11146 }
11147 let mut token_d = e.stream().clone_htod(&[first_token])?;
11148 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
11149 let g4 = self.cfg.gemma4.as_ref().unwrap();
11150 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
11151 let nkv_s = g4
11153 .head_count_kv
11154 .iter()
11155 .zip(g4.swa_pattern.iter())
11156 .find(|p| *p.1)
11157 .map(|p| *p.0 as usize)
11158 .unwrap_or(8);
11159 let nkv_g = g4
11160 .head_count_kv
11161 .iter()
11162 .zip(g4.swa_pattern.iter())
11163 .find(|p| !*p.1)
11164 .map(|p| *p.0 as usize)
11165 .unwrap_or(2);
11166 let mut graphs: std::collections::HashMap<
11167 ((bool, usize), (bool, usize), bool, bool),
11168 (
11169 cudarc::driver::CudaGraph,
11170 Vec<Box<dyn std::any::Any + Send>>,
11171 ),
11172 > = Default::default();
11173 let mut slots = self.g4_dc_slots(e)?;
11176 const RING: usize = 64;
11179 const DRAIN: usize = 1;
11185 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
11186 let ring_base = prompt_pos;
11187 let mut out = Vec::with_capacity(max_new);
11188 let mut reason = StopReason::MaxNew;
11189 let mut next = first_token;
11190 let mut captures = 0usize;
11191 for _ in 0..max_new {
11192 out.push(next);
11193 if eos.contains(&next) {
11194 reason = StopReason::Eos;
11195 break;
11196 }
11197 if !on_token(next) {
11198 reason = StopReason::Callback;
11199 break;
11200 }
11201 let t_kv = cache.pos + 1;
11202 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11210 let f512 = crate::fa512_min_tkv();
11211 let key_s = if t_kv > win {
11212 (true, usize::MAX)
11213 } else {
11214 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
11215 };
11216 let (key_g, rung_end) = if t_kv >= f512 {
11217 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
11220 ((true, end), end)
11221 } else {
11222 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
11223 };
11224 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
11225 if !graphs.contains_key(&key) {
11226 let bucket_max = (t_kv, rung_end);
11227 let snap = cache.snapshot(e)?;
11229 let pos_save = e.dtoh_i32_one(&pos_d)?;
11230 let len_save: Vec<Option<i32>> = cache
11231 .kv
11232 .iter()
11233 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
11234 .collect();
11235 let tok_save = e.dtoh_u32_one(&token_d)?;
11236 let graph = {
11241 let tok_ref = &mut token_d;
11242 let pos_ref = &mut pos_d;
11243 let cache_ref = &mut *cache;
11244 let slots_ref = &mut slots;
11245 let ring_ref = &mut ring;
11246 e.capture_graph_retained_flags(
11247 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
11248 |e| {
11249 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
11251 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
11252 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
11253 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
11254 cache_ref, n_vocab, Some(bucket_max),
11255 sl, tok_ref, Some((rg, ring_base)))
11256 })?
11257 };
11258 cache.rollback(e, &snap, 0)?;
11259 e.set_i32_one(&mut pos_d, pos_save)?;
11260 for (il, ls) in len_save.iter().enumerate() {
11261 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
11262 e.set_i32_one(&mut kvl.len_d, *v)?;
11263 }
11264 }
11265 e.set_u32_one(&mut token_d, tok_save)?;
11266 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
11267 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
11268 eprintln!("[graph-census] {c:?}");
11269 }
11270 }
11271 graphs.insert(key, graph);
11272 captures += 1;
11273 }
11274 let mut chunk = 1usize;
11279 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
11280 .ok()
11281 .and_then(|v| v.parse().ok())
11282 .unwrap_or(DRAIN);
11283 while chunk < drain_cap && out.len() + chunk < max_new {
11284 let t_next = cache.pos + 1 + chunk;
11285 let key_s2 = if t_next > win {
11286 (true, usize::MAX)
11287 } else {
11288 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
11289 };
11290 let key_g2 = if t_next >= f512 {
11291 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
11292 } else {
11293 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
11294 };
11295 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
11296 break;
11297 }
11298 chunk += 1;
11299 }
11300 let g = &graphs.get(&key).unwrap().0;
11301 for _ in 0..chunk {
11302 g.launch()?;
11303 }
11304 e.stream().synchronize()?;
11305 let ringh = e.dtoh_u32(&ring)?;
11306 for j in 0..chunk {
11307 let pos_j = cache.pos + j;
11308 let tok_j = ringh[(pos_j - ring_base) % RING];
11309 cache.pos += 0; if j + 1 == chunk {
11311 next = tok_j;
11312 } else {
11313 out.push(tok_j);
11314 if eos.contains(&tok_j) || !on_token(tok_j) {
11315 reason = if eos.contains(&tok_j) {
11316 StopReason::Eos
11317 } else {
11318 StopReason::Callback
11319 };
11320 let keep = cache.pos + j + 1;
11322 e.set_i32_one(&mut pos_d, keep as i32)?;
11323 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
11324 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
11325 kvl.len = keep;
11326 }
11327 cache.pos = keep;
11328 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
11329 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
11330 }
11331 return Ok((out, reason));
11332 }
11333 }
11334 }
11335 cache.pos += chunk;
11336 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
11337 kvl.len += chunk;
11338 }
11339 }
11340 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
11341 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
11342 }
11343 Ok((out, reason))
11344 }
11345
11346 pub(crate) fn gemma4_decode_step_t(
11352 &self,
11353 e: &Engine,
11354 tokens: &[u32],
11355 pos0: usize,
11356 cache: &mut Cache,
11357 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11358 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
11359 }
11360
11361 pub(crate) fn gemma4_decode_step_t_am(
11365 &self,
11366 e: &Engine,
11367 tokens: &[u32],
11368 pos0: usize,
11369 cache: &mut Cache,
11370 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11371 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
11372 let t = tokens.len();
11373 let n_vocab = self.output.out_features();
11374 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
11375 for i in 0..t {
11376 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
11377 }
11378 Ok((e.dtoh_u32(&toks)?, hn))
11379 }
11380
11381 pub(crate) fn gemma4_decode_step_t_am_dev(
11384 &self,
11385 e: &Engine,
11386 tok_d: &CudaSlice<u32>,
11387 t: usize,
11388 pos0: usize,
11389 cache: &mut Cache,
11390 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11391 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
11392 let n_vocab = self.output.out_features();
11393 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
11394 for i in 0..t {
11395 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
11396 }
11397 Ok((vam, hn))
11398 }
11399
11400 pub(crate) fn gemma4_decode_step_t_h(
11403 &self,
11404 e: &Engine,
11405 tokens: &[u32],
11406 pos0: usize,
11407 cache: &mut Cache,
11408 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11409 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
11410 let t = tokens.len();
11411 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
11412 e.softcap(&mut ld, cap, t * self.output.out_features())?;
11413 Ok((e.dtoh(&ld)?, hn))
11414 }
11415
11416 pub(crate) fn verify_stream_scratch(
11419 &self,
11420 e: &Engine,
11421 cap: usize,
11422 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
11423 Ok(VerifyStreamScratch {
11424 pos_d: e.htod_i32(&vec![0i32; cap])?,
11425 row_ctrs: (0..cap)
11426 .map(|_| e.htod_i32(&[0]))
11427 .collect::<Result<_, _>>()?,
11428 })
11429 }
11430
11431 pub(crate) fn gemma4_verify_t_am_stream(
11439 &self,
11440 e: &Engine,
11441 tok_d: &CudaSlice<u32>,
11442 t: usize,
11443 ctr: &CudaSlice<i32>,
11444 hint: usize,
11445 cache: &mut Cache,
11446 scr: &mut VerifyStreamScratch,
11447 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11448 let n_embd = self.cfg.n_embd as usize;
11449 let eps = self.cfg.rms_eps;
11450 assert!(t <= scr.row_ctrs.len() && t <= 64);
11451 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
11452 for i in 0..t {
11453 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
11454 }
11455 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
11456 let embd_gpu = self
11457 .embd_gpu
11458 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
11459 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
11460 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
11461 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
11462 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11463 let n_layers = self.layers.len();
11464 for (il, layer) in self.layers.iter().enumerate() {
11465 let (hq, hdq) = match h_carry.take() {
11466 Some(p) => p,
11467 None => {
11468 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
11469 }
11470 };
11471 let Mixer::Full(fa) = &layer.mixer else {
11472 panic!("gemma4 layer {il} not full-attn")
11473 };
11474 let o = self
11475 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
11476 let mut cur = e.uninit(t * n_embd)?;
11477 e.rms_norm(
11478 &o,
11479 layer.post_attn_norm.float_data(),
11480 &mut cur,
11481 n_embd,
11482 t,
11483 eps,
11484 )?;
11485 let next_norm = if il + 1 < n_layers {
11486 Some(self.layers[il + 1].attn_norm.float_data())
11487 } else {
11488 None
11489 };
11490 let (xn, hn) = self.gemma4_layer_tail_add_nq(e, layer, &cur, &x, t, next_norm)?;
11491 x = xn;
11492 h_carry = hn;
11493 self.dflash_tap(e, cache, il, &x, t)?;
11494 }
11495 let mut hn = e.uninit(t * n_embd)?;
11496 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
11497 let ld = e.matmul(&self.output, &hn, t)?;
11498 let n_vocab = self.output.out_features();
11499 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
11500 for i in 0..t {
11501 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
11502 }
11503 Ok((vam, hn))
11504 }
11505
11506 fn dflash_tap(
11513 &self,
11514 e: &Engine,
11515 cache: &mut Cache,
11516 il: usize,
11517 x: &CudaSlice<f32>,
11518 t: usize,
11519 ) -> Result<(), Box<dyn std::error::Error>> {
11520 let Some(taps) = cache.dflash_taps.as_mut() else {
11521 return Ok(());
11522 };
11523 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
11524 return Ok(());
11525 };
11526 let h = taps.hidden;
11527 let n_taps = taps.layer_ids.len();
11528 debug_assert_eq!(taps.t, t);
11529 let xv = e.view(x, t * h);
11530 for r in 0..t {
11531 let row = xv.slice(r * h..(r + 1) * h);
11532 e.copy_view_into(&mut taps.buf, r * n_taps * h + slot * h, &row, h)?;
11533 }
11534 Ok(())
11535 }
11536
11537 fn gemma4_verify_trunk(
11538 &self,
11539 e: &Engine,
11540 tokens: &[u32],
11541 pos0: usize,
11542 cache: &mut Cache,
11543 tok_dev: Option<&CudaSlice<u32>>,
11544 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11545 let n_embd = self.cfg.n_embd as usize;
11546 let eps = self.cfg.rms_eps;
11547 let t = tokens.len();
11548 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
11549 let pos_d = e.htod_i32(&pos)?;
11550 let mut x = match tok_dev {
11551 Some(td) => {
11552 let embd_gpu = self
11553 .embd_gpu
11554 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
11555 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
11556 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
11557 }
11558 None => e.htod(&self.embd.gather(n_embd, tokens))?,
11559 };
11560 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
11561 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11562 let n_layers = self.layers.len();
11563 for (il, layer) in self.layers.iter().enumerate() {
11564 let (hq, hdq) = match h_carry.take() {
11565 Some(p) => p,
11566 None => {
11567 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
11568 }
11569 };
11570 let Mixer::Full(fa) = &layer.mixer else {
11571 panic!("gemma4 layer {il} not full-attn")
11572 };
11573 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
11574 let mut cur = e.uninit(t * n_embd)?;
11575 e.rms_norm(
11576 &o,
11577 layer.post_attn_norm.float_data(),
11578 &mut cur,
11579 n_embd,
11580 t,
11581 eps,
11582 )?;
11583 let next_norm = if il + 1 < n_layers {
11584 Some(self.layers[il + 1].attn_norm.float_data())
11585 } else {
11586 None
11587 };
11588 let (xn, hn) = self.gemma4_layer_tail_add_nq(e, layer, &cur, &x, t, next_norm)?;
11589 x = xn;
11590 h_carry = hn;
11591 self.dflash_tap(e, cache, il, &x, t)?;
11592 }
11593 let mut hn = e.uninit(t * n_embd)?;
11594 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
11595 let mut ld = e.matmul(&self.output, &hn, t)?;
11596 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
11598 Ok((ld, hn))
11599 }
11600
11601 #[allow(clippy::too_many_arguments)]
11609 fn gemma4_verify_attn_stream(
11610 &self,
11611 e: &Engine,
11612 fa: &crate::hybrid::FullAttnLayer,
11613 il: usize,
11614 hq: &CudaSlice<i8>,
11615 hdq: &CudaSlice<f32>,
11616 pos_d: &CudaSlice<i32>,
11617 t: usize,
11618 cache: &mut Cache,
11619 hint: usize,
11620 row_ctrs: &[CudaSlice<i32>],
11621 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11622 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
11623 let eps = self.cfg.rms_eps;
11624 let aux = self.gemma4_aux.as_ref().unwrap();
11625 let ones = aux.ones(e);
11626 #[cfg(debug_assertions)]
11627 crate::debug_assert_tensor_stream_device(
11628 ones,
11629 &e.stream(),
11630 "gemma4_verify_attn_stream.ones",
11631 );
11632 let h0 = e.zeros(0)?;
11633 let h = &h0;
11634 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11637 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11638 let fused_qkv = if f2b {
11639 if swa {
11640 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
11641 .map(|(a, b, c)| (a, b, Some(c)))
11642 } else {
11643 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
11644 .map(|(a, b)| (a, b, None))
11645 }
11646 } else {
11647 None
11648 };
11649 let (q0, k0, v0) = match fused_qkv {
11650 Some((a, b, cv)) => {
11651 let v = match cv {
11652 Some(c) => c,
11653 None => e.clone_dtod(&b)?,
11654 };
11655 (a, b, v)
11656 }
11657 None => {
11658 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
11659 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
11660 let v0 = if swa {
11661 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
11662 } else {
11663 e.clone_dtod(&k0)?
11664 };
11665 (q0, k0, v0)
11666 }
11667 };
11668 let mut q = e.uninit(t * nh * hd)?;
11669 let mut k = e.uninit(t * nkv * hd)?;
11670 let mut v = e.uninit(t * nkv * hd)?;
11671 let ff = if swa {
11674 None
11675 } else {
11676 Some(
11677 aux.rope_freqs(e)
11678 .expect("gemma4 global rope needs rope_freqs.weight"),
11679 )
11680 };
11681 #[cfg(debug_assertions)]
11682 if let Some(ff) = ff {
11683 crate::debug_assert_tensor_stream_device(
11684 ff,
11685 &e.stream(),
11686 "gemma4_verify_attn_stream.rope_freqs",
11687 );
11688 }
11689 e.rms_norm_qkv_rope(
11690 &q0,
11691 &k0,
11692 &v0,
11693 fa.q_norm.float_data(),
11694 fa.k_norm.float_data(),
11695 ones,
11696 &mut q,
11697 &mut k,
11698 &mut v,
11699 hd,
11700 nh * t,
11701 nkv * t,
11702 pos_d,
11703 nh,
11704 nkv,
11705 base,
11706 1.0,
11707 ff,
11708 eps,
11709 )?;
11710 let kvl = cache.kv[il].as_mut().unwrap();
11711 e.append_kv_quantized_rows_dc(
11713 &k,
11714 &v,
11715 &mut kvl.k,
11716 &mut kvl.v,
11717 &kvl.len_d,
11718 t,
11719 kvl.kv_dim_k,
11720 kvl.kv_dim_v,
11721 kvl.k_tok_bytes,
11722 kvl.v_tok_bytes,
11723 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
11724 )?;
11725 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11728 let mut attn = e.uninit(t * nh * hd)?;
11729 let k_view = e.view_u8(&kvl.k, kvl.k.len());
11730 let v_view = e.view_u8(&kvl.v, kvl.v.len());
11731 if swa && hint + 1 >= win {
11734 e.fa_decode_rows_w(
11737 &q,
11738 &k_view,
11739 &v_view,
11740 &mut attn,
11741 hd,
11742 nh,
11743 nkv,
11744 &kvl.len_d,
11745 0,
11746 t,
11747 scale,
11748 win,
11749 kvl.k_tok_bytes,
11750 kvl.v_tok_bytes,
11751 None,
11752 )?;
11753 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
11754 let bucket = (hint + t + 2)
11767 .next_power_of_two()
11768 .min(crate::fa512_min_tkv().saturating_sub(1));
11769 let qv = e.view(&q, t * nh * hd);
11770 for i in 0..t {
11771 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
11772 let mut q_one = e.uninit(nh * hd)?;
11773 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
11774 let mut a_one = e.uninit(nh * hd)?;
11775 e.fa_decode_dc(
11776 &q_one,
11777 &k_view,
11778 &v_view,
11779 &mut a_one,
11780 hd,
11781 nh,
11782 nkv,
11783 &row_ctrs[i],
11784 bucket,
11785 scale,
11786 kvl.k_tok_bytes,
11787 kvl.v_tok_bytes,
11788 false,
11789 )?;
11790 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
11791 }
11792 } else if hd == 512 {
11793 e.fa_decode_rows(
11796 &q,
11797 &k_view,
11798 &v_view,
11799 &mut attn,
11800 hd,
11801 nh,
11802 nkv,
11803 hint,
11804 t,
11805 scale,
11806 kvl.k_tok_bytes,
11807 kvl.v_tok_bytes,
11808 Some((&kvl.len_d, 0)),
11809 false,
11810 false,
11811 None,
11812 )?;
11813 } else {
11814 e.fa_decode_rows_dc(
11816 &q,
11817 &k_view,
11818 &v_view,
11819 &mut attn,
11820 hd,
11821 nh,
11822 nkv,
11823 &kvl.len_d,
11824 hint + t,
11825 t,
11826 scale,
11827 kvl.k_tok_bytes,
11828 kvl.v_tok_bytes,
11829 0,
11830 swa && crate::Engine::wkv_on(),
11831 )?;
11832 }
11833 Ok(e.matmul(&fa.wo, &attn, t)?)
11834 }
11835
11836 fn gemma4_verify_attn(
11837 &self,
11838 e: &Engine,
11839 fa: &crate::hybrid::FullAttnLayer,
11840 il: usize,
11841 hq: &CudaSlice<i8>,
11842 hdq: &CudaSlice<f32>,
11843 pos_d: &CudaSlice<i32>,
11844 t: usize,
11845 cache: &mut Cache,
11846 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11847 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
11848 let eps = self.cfg.rms_eps;
11849 let aux = self.gemma4_aux.as_ref().unwrap();
11850 let ones = aux.ones(e);
11851 #[cfg(debug_assertions)]
11852 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
11853 let n_embd = self.cfg.n_embd as usize;
11854 let _ = n_embd;
11855
11856 let h0 = e.zeros(0)?;
11857 let h = &h0;
11858 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11861 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11862 let fused_qkv = if f2b {
11863 if swa {
11864 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
11865 .map(|(a, b, c)| (a, b, Some(c)))
11866 } else {
11867 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
11868 .map(|(a, b)| (a, b, None))
11869 }
11870 } else {
11871 None
11872 };
11873 let (q0, k0, v0) = match fused_qkv {
11874 Some((a, b, cv)) => {
11875 let v = match cv {
11876 Some(c) => c,
11877 None => e.clone_dtod(&b)?,
11878 };
11879 (a, b, v)
11880 }
11881 None => {
11882 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
11883 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
11884 let v0 = if swa {
11885 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
11886 } else {
11887 e.clone_dtod(&k0)?
11888 };
11889 (q0, k0, v0)
11890 }
11891 };
11892 let mut q = e.uninit(t * nh * hd)?;
11893 let mut k = e.uninit(t * nkv * hd)?;
11894 let mut v = e.uninit(t * nkv * hd)?;
11895 let ff = if swa {
11898 None
11899 } else {
11900 Some(
11901 aux.rope_freqs(e)
11902 .expect("gemma4 global rope needs rope_freqs.weight"),
11903 )
11904 };
11905 #[cfg(debug_assertions)]
11906 if let Some(ff) = ff {
11907 crate::debug_assert_tensor_stream_device(
11908 ff,
11909 &e.stream(),
11910 "gemma4_verify_attn.rope_freqs",
11911 );
11912 }
11913 e.rms_norm_qkv_rope(
11914 &q0,
11915 &k0,
11916 &v0,
11917 fa.q_norm.float_data(),
11918 fa.k_norm.float_data(),
11919 ones,
11920 &mut q,
11921 &mut k,
11922 &mut v,
11923 hd,
11924 nh * t,
11925 nkv * t,
11926 pos_d,
11927 nh,
11928 nkv,
11929 base,
11930 1.0,
11931 ff,
11932 eps,
11933 )?;
11934 let kvl = cache.kv[il].as_mut().unwrap();
11935 let base_len = kvl.len;
11936 e.append_kv_quantized_rows(
11937 &k,
11938 &v,
11939 &mut kvl.k,
11940 &mut kvl.v,
11941 base_len,
11942 t,
11943 kvl.kv_dim_k,
11944 kvl.kv_dim_v,
11945 kvl.k_tok_bytes,
11946 kvl.v_tok_bytes,
11947 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
11948 )?;
11949 kvl.len += t;
11950 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11951 let mut attn = e.uninit(t * nh * hd)?;
11952 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
11955 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
11958 if rows_ok && (!swa || base_len + t <= win) {
11959 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
11960 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
11961 if hd == 512 {
11962 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
11964 e.fa_decode_rows(
11965 &q,
11966 &k_view,
11967 &v_view,
11968 &mut attn,
11969 hd,
11970 nh,
11971 nkv,
11972 base_len,
11973 t,
11974 scale,
11975 kvl.k_tok_bytes,
11976 kvl.v_tok_bytes,
11977 Some((&kvl.len_d, 0)),
11978 false,
11979 swa && crate::Engine::wkv_on(),
11980 None,
11981 )?;
11982 } else {
11983 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
11987 e.fa_decode_rows_dc(
11988 &q,
11989 &k_view,
11990 &v_view,
11991 &mut attn,
11992 hd,
11993 nh,
11994 nkv,
11995 &kvl.len_d,
11996 base_len + t,
11997 t,
11998 scale,
11999 kvl.k_tok_bytes,
12000 kvl.v_tok_bytes,
12001 0,
12002 swa && crate::Engine::wkv_on(),
12003 )?;
12004 }
12005 return Ok(e.matmul(&fa.wo, &attn, t)?);
12006 }
12007 if hd == 256
12015 && swa
12016 && base_len + 1 >= win
12017 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12018 {
12019 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
12020 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
12021 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
12022 e.fa_decode_rows_w(
12023 &q,
12024 &k_view,
12025 &v_view,
12026 &mut attn,
12027 hd,
12028 nh,
12029 nkv,
12030 &kvl.len_d,
12031 0,
12032 t,
12033 scale,
12034 win,
12035 kvl.k_tok_bytes,
12036 kvl.v_tok_bytes,
12037 None,
12038 )?;
12039 return Ok(e.matmul(&fa.wo, &attn, t)?);
12040 }
12041 for i in 0..t {
12042 let avail = base_len + i + 1;
12043 let (off_tok, t_kv) = if swa && avail > win {
12044 (avail - win, win)
12045 } else {
12046 (0, avail)
12047 };
12048 let k_view = e.view_u8_range(
12049 &kvl.k,
12050 off_tok * kvl.k_tok_bytes,
12051 (off_tok + t_kv) * kvl.k_tok_bytes,
12052 );
12053 let v_view = e.view_u8_range(
12054 &kvl.v,
12055 off_tok * kvl.v_tok_bytes,
12056 (off_tok + t_kv) * kvl.v_tok_bytes,
12057 );
12058 let qi = e.view(&q, t * nh * hd);
12059 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
12060 let mut q_one = e.uninit(nh * hd)?;
12061 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
12062 let mut a_one = e.uninit(nh * hd)?;
12063 if swa
12067 && avail > win
12068 && hd == 256
12069 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12070 {
12071 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
12072 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
12073 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
12074 e.fa_decode_rows_w(
12075 &q_one,
12076 &kp,
12077 &vp,
12078 &mut a_one,
12079 hd,
12080 nh,
12081 nkv,
12082 &kvl.len_d,
12083 0,
12084 1,
12085 scale,
12086 win,
12087 kvl.k_tok_bytes,
12088 kvl.v_tok_bytes,
12089 None,
12090 )?;
12091 } else if !swa
12092 && hd == 512
12093 && avail >= crate::fa512_min_tkv()
12094 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12095 {
12096 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
12097 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
12098 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
12099 e.fa_decode_rows(
12100 &q_one,
12101 &kp,
12102 &vp,
12103 &mut a_one,
12104 hd,
12105 nh,
12106 nkv,
12107 avail - 1,
12108 1,
12109 scale,
12110 kvl.k_tok_bytes,
12111 kvl.v_tok_bytes,
12112 Some((&kvl.len_d, 0)),
12113 false,
12114 false,
12115 None,
12116 )?;
12117 } else {
12118 e.fa_decode_kvmod(
12119 &q_one,
12120 &k_view,
12121 &v_view,
12122 &mut a_one,
12123 hd,
12124 nh,
12125 nkv,
12126 t_kv,
12127 scale,
12128 kvl.k_tok_bytes,
12129 kvl.v_tok_bytes,
12130 swa && crate::Engine::wkv_on(),
12131 )?;
12132 }
12133 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
12134 }
12135 Ok(e.matmul(&fa.wo, &attn, t)?)
12136 }
12137
12138 pub(crate) fn gemma4_decode_step_h(
12141 &self,
12142 e: &Engine,
12143 token: u32,
12144 cache: &mut Cache,
12145 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12146 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
12151 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
12152 }
12153 if crate::pp::pp_cuts(self.layers.len()).is_some() {
12154 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
12155 }
12156 let n_embd = self.cfg.n_embd as usize;
12157 let eps = self.cfg.rms_eps;
12158 let pos_d = e.htod_i32(&[cache.pos as i32])?;
12159 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
12160 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12161 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12164 let n_layers = self.layers.len();
12165 for (il, layer) in self.layers.iter().enumerate() {
12166 let (hq, hdq) = match h_carry.take() {
12167 Some(p) => p,
12168 None => {
12169 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
12170 }
12171 };
12172 let Mixer::Full(fa) = &layer.mixer else {
12173 panic!("gemma4 layer {il} not full-attn")
12174 };
12175 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
12176 let mut cur = e.uninit(n_embd)?;
12177 e.rms_norm(
12178 &o,
12179 layer.post_attn_norm.float_data(),
12180 &mut cur,
12181 n_embd,
12182 1,
12183 eps,
12184 )?;
12185 let next_norm = if il + 1 < n_layers {
12186 Some(self.layers[il + 1].attn_norm.float_data())
12187 } else {
12188 None
12189 };
12190 let (xn, hn) = self.gemma4_layer_tail_add_nq(e, layer, &cur, &x, 1, next_norm)?;
12191 x = xn;
12192 h_carry = hn;
12193 }
12194 let mut hn = e.uninit(n_embd)?;
12195 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12196 let h_seed = e.clone_dtod(&x)?;
12197 let mut ld = e.matmul(&self.output, &hn, 1)?;
12198 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12199 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
12201 let logits = e.dtoh(&ld)?;
12202 cache.pos += 1;
12203 Ok((logits, h_seed))
12204 }
12205
12206 fn gemma4_decode_layers(
12214 &self,
12215 e: &Engine,
12216 mut x: CudaSlice<f32>,
12217 lo: usize,
12218 hi: usize,
12219 pos_d: &CudaSlice<i32>,
12220 cache: &mut Cache,
12221 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12222 let n_embd = self.cfg.n_embd as usize;
12223 let eps = self.cfg.rms_eps;
12224 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12225 for il in lo..hi {
12226 let layer = &self.layers[il];
12227 let (hq, hdq) = match h_carry.take() {
12228 Some(p) => p,
12229 None => {
12231 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
12232 }
12233 };
12234 let Mixer::Full(fa) = &layer.mixer else {
12235 panic!("gemma4 layer {il} not full-attn")
12236 };
12237 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
12238 let mut cur = e.uninit(n_embd)?;
12239 e.rms_norm(
12240 &o,
12241 layer.post_attn_norm.float_data(),
12242 &mut cur,
12243 n_embd,
12244 1,
12245 eps,
12246 )?;
12247 let next_norm = if il + 1 < hi {
12248 Some(self.layers[il + 1].attn_norm.float_data())
12249 } else {
12250 None
12251 };
12252 let (xn, hn) = self.gemma4_layer_tail_add_nq(e, layer, &cur, &x, 1, next_norm)?;
12253 x = xn;
12254 h_carry = hn;
12255 }
12256 Ok(x)
12257 }
12258
12259 fn gemma4_decode_step_h_pp2(
12267 &self,
12268 e: &Engine,
12269 token: u32,
12270 cache: &mut Cache,
12271 split: usize,
12272 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12273 if crate::pp::pp2_streams_off() {
12274 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
12275 }
12276 let rt = crate::pp::Pp2Rt::get(e)?;
12277 let e0 = rt.engine(0, e);
12278 let e1 = rt.engine(1, e);
12279 let n_embd = self.cfg.n_embd as usize;
12280 let eps = self.cfg.rms_eps;
12281 let pos = cache.pos as i32;
12282
12283 let slot = {
12285 let _st0 = rt.enter(0);
12286 let pos_d = e0.htod_i32(&[pos])?;
12287 #[cfg(debug_assertions)]
12288 crate::debug_assert_tensor_stream_device(
12289 &pos_d,
12290 &e0.stream(),
12291 "gemma4_decode_step_h_pp2.stage0.pos_d",
12292 );
12293 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
12294 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12295 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
12296 rt.tx(0, &x, n_embd)?
12297 };
12298
12299 let _st1 = rt.enter(1);
12301 let pos_d = e1.htod_i32(&[pos])?;
12302 #[cfg(debug_assertions)]
12303 crate::debug_assert_tensor_stream_device(
12304 &pos_d,
12305 &e1.stream(),
12306 "gemma4_decode_step_h_pp2.stage1.pos_d",
12307 );
12308 let x = rt.rx(0, slot, n_embd)?;
12309 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
12310
12311 let mut hn = e1.uninit(n_embd)?;
12312 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12313 let h_seed = e1.clone_dtod(&x)?;
12314 let mut ld = e1.matmul(&self.output, &hn, 1)?;
12315 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12316 e1.softcap(&mut ld, cap, self.output.out_features())?;
12317 self.gemma4_suppress(e1, &mut ld, 1)?;
12318 let logits = e1.dtoh(&ld)?;
12319 cache.pos += 1;
12320 Ok((logits, h_seed))
12321 }
12322
12323 fn gemma4_decode_step_h_pp2_samestream(
12326 &self,
12327 e: &Engine,
12328 token: u32,
12329 cache: &mut Cache,
12330 split: usize,
12331 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12332 let n_embd = self.cfg.n_embd as usize;
12333 let eps = self.cfg.rms_eps;
12334 let pos_d = e.htod_i32(&[cache.pos as i32])?;
12335
12336 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
12338 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12339 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
12340
12341 let boundary_tx = e.clone_dtod(&x)?;
12343 let boundary_rx = e.clone_dtod(&boundary_tx)?;
12344
12345 let x =
12347 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
12348
12349 let mut hn = e.uninit(n_embd)?;
12350 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12351 let h_seed = e.clone_dtod(&x)?;
12352 let mut ld = e.matmul(&self.output, &hn, 1)?;
12353 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12354 e.softcap(&mut ld, cap, self.output.out_features())?;
12355 self.gemma4_suppress(e, &mut ld, 1)?;
12356 let logits = e.dtoh(&ld)?;
12357 cache.pos += 1;
12358 Ok((logits, h_seed))
12359 }
12360}
12361
12362impl HybridModel {
12381 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
12384 let geometry = self
12385 .cfg
12386 .layer_geometry(il as u32)
12387 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
12388 debug_assert_eq!(
12389 geometry.attention_gate,
12390 memra_gguf::config::AttentionGateKind::SeparateHead
12391 );
12392 geometry
12393 }
12394
12395 #[allow(clippy::too_many_arguments)]
12455 fn step35_attn_pre_wo(
12456 &self,
12457 e: &Engine,
12458 fa: &FullAttnLayer,
12459 mut g3: Vec<CudaSlice<f32>>,
12460 hg: Option<&CudaSlice<f32>>,
12461 gt_pre: Option<&CudaSlice<f32>>,
12462 pos_d: &CudaSlice<i32>,
12463 t: usize,
12464 cache: Option<&mut Cache>,
12465 il: usize,
12466 seq_end: usize,
12467 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12468 let geometry = self.step35_geom(il);
12469 let hd = geometry.head_dim_k as usize;
12470 let nkv = geometry.n_head_kv as usize;
12471 let nh = geometry.n_head as usize;
12472 let rbase = geometry.rope_base;
12473 let scale = geometry.attention_scale();
12474 let swa = geometry.window.is_some();
12475 let eps = self.cfg.rms_eps;
12476 let win = geometry.window.unwrap_or(0) as usize;
12477 let n_rot = geometry.n_rot as usize;
12478
12479 let v = g3.pop().unwrap();
12480 let k0 = g3.pop().unwrap();
12481 let q0 = g3.pop().unwrap();
12482
12483 let mut q = e.uninit(t * nh * hd)?;
12487 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
12488 let mut k = e.uninit(t * nkv * hd)?;
12489 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
12490 let ff = if geometry.rope_factors {
12491 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
12492 } else {
12493 None
12494 };
12495 #[cfg(debug_assertions)]
12496 if let Some(ff) = ff {
12497 crate::debug_assert_tensor_stream_device(
12498 ff,
12499 &e.stream(),
12500 "step35_attn_pre_wo.rope_freqs",
12501 );
12502 }
12503 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
12504
12505 let mut attn = e.uninit(t * nh * hd)?;
12506 match cache {
12507 Some(cache) => {
12508 let base_len = cache.kv[il].as_ref().unwrap().len;
12509 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
12511 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
12512 let off = if swa {
12513 let raw = base_len.saturating_sub(win - 1);
12514 if legacy_tkv || legacy_calllocal {
12515 raw
12516 } else {
12517 raw & !31usize
12518 }
12519 } else {
12520 0
12521 };
12522 {
12523 let kvl = cache.kv[il].as_mut().unwrap();
12524 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
12525 let write_row = e.prepare_kv_append(kvl, off, t)?;
12526 e.append_kv_quantized_rows(
12527 &k,
12528 &v,
12529 &mut kvl.k,
12530 &mut kvl.v,
12531 write_row,
12532 t,
12533 kvl.kv_dim_k,
12534 kvl.kv_dim_v,
12535 kvl.k_tok_bytes,
12536 kvl.v_tok_bytes,
12537 crate::Engine::kv_fp8_on(),
12538 )?;
12539 kvl.len += t;
12540 let new_len = kvl.len as i32;
12541 e.set_i32_one(&mut kvl.len_d, new_len)?;
12542 }
12543 let kvl = cache.kv[il].as_ref().unwrap();
12544 let t_kv = base_len + t - off;
12567 let physical = kvl.physical_rows(off, off + t_kv)?;
12568 let k_view = e.view_u8_range(
12569 &kvl.k,
12570 physical.start * kvl.k_tok_bytes,
12571 physical.end * kvl.k_tok_bytes,
12572 );
12573 let v_view = e.view_u8_range(
12574 &kvl.v,
12575 physical.start * kvl.v_tok_bytes,
12576 physical.end * kvl.v_tok_bytes,
12577 );
12578 let swa_naive = if legacy_tkv {
12590 t_kv > win
12591 } else {
12592 seq_end > win
12593 };
12594 if swa && swa_naive {
12595 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
12608 e.sdpa_naive_w_quantized_view(
12609 &q,
12610 &k_view,
12611 &v_view,
12612 &mut attn,
12613 hd,
12614 nh,
12615 nkv,
12616 t,
12617 t_kv,
12618 scale,
12619 true,
12620 win,
12621 kvl.k_tok_bytes,
12622 kvl.v_tok_bytes,
12623 )?;
12624 } else {
12625 e.fa_prefill_view_ws_w_hd128(
12626 &q,
12627 &k_view,
12628 &v_view,
12629 &mut attn,
12630 hd,
12631 nh,
12632 nkv,
12633 t,
12634 t_kv,
12635 scale,
12636 true,
12637 win,
12638 kvl.k_tok_bytes,
12639 kvl.v_tok_bytes,
12640 )?;
12641 }
12642 } else if std::env::var("MEMRA_NOFA").is_ok() {
12643 e.sdpa_naive_quantized_view(
12644 &q,
12645 &k_view,
12646 &v_view,
12647 &mut attn,
12648 hd,
12649 nh,
12650 nkv,
12651 t,
12652 t_kv,
12653 scale,
12654 true,
12655 kvl.k_tok_bytes,
12656 kvl.v_tok_bytes,
12657 )?;
12658 } else {
12659 e.fa_prefill_view_ws(
12664 &q,
12665 &k_view,
12666 &v_view,
12667 &mut attn,
12668 hd,
12669 nh,
12670 nkv,
12671 t,
12672 t_kv,
12673 scale,
12674 true,
12675 kvl.k_tok_bytes,
12676 kvl.v_tok_bytes,
12677 crate::Engine::kv_fp8_on(),
12678 )?;
12679 }
12680 }
12681 None => {
12682 debug_assert_eq!(
12687 seq_end, t,
12688 "step35 cacheless prefill is monolithic (seq_end == t)"
12689 );
12690 if swa && seq_end > win {
12691 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
12692 } else if std::env::var("MEMRA_NOFA").is_ok() {
12693 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
12694 } else {
12695 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
12696 }
12697 }
12698 }
12699
12700 let gw = fa
12703 .attn_gate
12704 .as_ref()
12705 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
12706 let gt_owned = if gt_pre.is_none() {
12707 Some(e.matmul(
12708 gw,
12709 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
12710 t,
12711 )?)
12712 } else {
12713 None
12714 };
12715 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
12716 let mut ag = e.uninit(t * nh * hd)?;
12717 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
12718 Ok(ag)
12719 }
12720
12721 pub(crate) fn step35_attn(
12724 &self,
12725 e: &Engine,
12726 fa: &FullAttnLayer,
12727 h: &CudaSlice<f32>,
12728 pos_d: &CudaSlice<i32>,
12729 t: usize,
12730 il: usize,
12731 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12732 let g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
12733 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
12735 Ok(e.matmul(&fa.wo, &ag, t)?)
12736 }
12737
12738 #[allow(clippy::too_many_arguments)]
12745 pub(crate) fn step35_attn_prime(
12746 &self,
12747 e: &Engine,
12748 fa: &FullAttnLayer,
12749 h: &CudaSlice<f32>,
12750 hx: Option<&CudaSlice<u8>>,
12751 pos_d: &CudaSlice<i32>,
12752 t: usize,
12753 cache: &mut Cache,
12754 il: usize,
12755 seq_end: usize,
12756 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12757 let g3 = match hx {
12758 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
12759 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
12760 };
12761 let ag =
12762 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
12763 Ok(e.matmul(&fa.wo, &ag, t)?)
12764 }
12765
12766 #[allow(clippy::too_many_arguments)]
12776 pub(crate) fn step35_decode_attn(
12777 &self,
12778 e: &Engine,
12779 fa: &FullAttnLayer,
12780 il: usize,
12781 h: &CudaSlice<f32>,
12782 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
12783 pos_d: &CudaSlice<i32>,
12784 cache: &mut Cache,
12785 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12786 let geometry = self.step35_geom(il);
12787 let hd = geometry.head_dim_k as usize;
12788 let nkv = geometry.n_head_kv as usize;
12789 let nh = geometry.n_head as usize;
12790 let rbase = geometry.rope_base;
12791 let scale = geometry.attention_scale();
12792 let swa = geometry.window.is_some();
12793 let eps = self.cfg.rms_eps;
12794 let win = geometry.window.unwrap_or(0) as usize;
12795 let n_rot = geometry.n_rot as usize;
12796 let n_embd = self.cfg.n_embd as usize;
12797 let gw = fa
12798 .attn_gate
12799 .as_ref()
12800 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
12801
12802 let (q0, k0, v0, gt) = match pre_q {
12803 Some((hq, hdq)) => {
12804 debug_assert!(
12805 e.uses_q8_1_fast(gw),
12806 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
12807 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
12808 );
12809 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
12810 Some(t3) => t3,
12811 None => (
12812 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
12813 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
12814 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
12815 ),
12816 };
12817 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
12818 (a, b, c, gt)
12819 }
12820 None => {
12821 if e.uses_q8_1_fast(&fa.wq)
12822 && e.uses_q8_1_fast(&fa.wk)
12823 && e.uses_q8_1_fast(&fa.wv)
12824 && e.uses_q8_1_fast(gw)
12825 {
12826 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
12827 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12828 Some(t3) => t3,
12829 None => (
12830 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12831 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12832 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12833 ),
12834 };
12835 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
12836 (a, b, c, gt)
12837 } else {
12838 (
12839 e.matmul(&fa.wq, h, 1)?,
12840 e.matmul(&fa.wk, h, 1)?,
12841 e.matmul(&fa.wv, h, 1)?,
12842 e.matmul(gw, h, 1)?,
12843 )
12844 }
12845 }
12846 };
12847
12848 let mut q = e.uninit(nh * hd)?;
12849 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
12850 let mut k = e.uninit(nkv * hd)?;
12851 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
12852 let ff = if swa {
12853 None
12854 } else {
12855 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
12856 };
12857 #[cfg(debug_assertions)]
12858 if let Some(ff) = ff {
12859 crate::debug_assert_tensor_stream_device(
12860 ff,
12861 &e.stream(),
12862 "step35_decode_attn.rope_freqs",
12863 );
12864 }
12865 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
12866
12867 if std::env::var("MEMRA_NOFA").is_ok() {
12868 return Err(
12869 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
12870 cache; unset MEMRA_NOFA to use fa_decode"
12871 .into(),
12872 );
12873 }
12874 let kvl = cache.kv[il].as_mut().unwrap();
12875 let next_len = kvl.len + 1;
12876 let (off, t_kv) = if swa && next_len > win {
12877 (next_len - win, win)
12878 } else {
12879 (0, next_len)
12880 };
12881 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
12882 e.append_kv_quantized(
12883 &k,
12884 &v0,
12885 &mut kvl.k,
12886 &mut kvl.v,
12887 write_row,
12888 kvl.kv_dim_k,
12889 kvl.kv_dim_v,
12890 kvl.k_tok_bytes,
12891 kvl.v_tok_bytes,
12892 crate::Engine::kv_fp8_on(),
12893 )?;
12894 kvl.len = next_len;
12895 let physical = kvl.physical_rows(off, off + t_kv)?;
12896 let k_view = e.view_u8_range(
12897 &kvl.k,
12898 physical.start * kvl.k_tok_bytes,
12899 physical.end * kvl.k_tok_bytes,
12900 );
12901 let v_view = e.view_u8_range(
12902 &kvl.v,
12903 physical.start * kvl.v_tok_bytes,
12904 physical.end * kvl.v_tok_bytes,
12905 );
12906 let mut attn = e.uninit(nh * hd)?;
12907 e.fa_decode_kvmod(
12908 &q,
12909 &k_view,
12910 &v_view,
12911 &mut attn,
12912 hd,
12913 nh,
12914 nkv,
12915 t_kv,
12916 scale,
12917 kvl.k_tok_bytes,
12918 kvl.v_tok_bytes,
12919 crate::Engine::kv_fp8_on(),
12920 )?;
12921
12922 let mut ag = e.uninit(nh * hd)?;
12923 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
12924 Ok(e.matmul(&fa.wo, &ag, 1)?)
12925 }
12926}
12927
12928impl HybridModel {
12937 pub fn is_gemma4_e4b(&self) -> bool {
12938 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
12939 }
12940
12941 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
12945 let g = self.cfg.gemma4.as_ref().unwrap();
12946 let swa = g.swa_pattern[il];
12947 let hd = if swa {
12948 g.key_length_swa
12949 } else {
12950 g.key_length_global
12951 } as usize;
12952 let Mixer::Full(fa) = &self.layers[il].mixer else {
12953 panic!("e4b layer {il} not full-attn")
12954 };
12955 let nh = fa.wq.out_features() / hd;
12956 let nkv = fa.wk.out_features() / hd;
12957 (
12958 hd,
12959 nkv,
12960 nh,
12961 if swa {
12962 g.rope_base_swa
12963 } else {
12964 g.rope_base_global
12965 },
12966 1.0,
12967 swa,
12968 )
12969 }
12970
12971 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
12973 self.layers[il]
12974 .gemma4
12975 .as_ref()
12976 .and_then(|b| b.e4b.as_ref())
12977 .and_then(|e4| e4.kv_share.map(|t| t as usize))
12978 }
12979
12980 fn gemma4_e4b_inp_pl(
12985 &self,
12986 e: &Engine,
12987 tokens: &[u32],
12988 x_scaled: &CudaSlice<f32>,
12989 t: usize,
12990 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12991 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
12992 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
12993 }
12994
12995 fn gemma4_e4b_inp_pl_dev(
12997 &self,
12998 e: &Engine,
12999 tok_d: &CudaSlice<u32>,
13000 x_scaled: &CudaSlice<f32>,
13001 t: usize,
13002 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13003 let aux = self.gemma4_aux.as_ref().unwrap();
13004 let m = aux.e4b.as_ref().unwrap();
13005 let n_embd = self.cfg.n_embd as usize;
13006 let n_layer = self.layers.len();
13007 let width = m.n_epl * n_layer;
13008 let tbl = m.tok_tbl_gpu.get_or_init(|| {
13009 e.upload_u8(&m.tok_embd_bytes)
13010 .expect("e4b per-layer token table upload")
13011 });
13012 let mut a =
13013 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
13014 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
13015 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
13016 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
13017 let mut pn = e.uninit(t * width)?;
13018 e.rms_norm(
13019 &p,
13020 m.proj_norm.float_data(),
13021 &mut pn,
13022 m.n_epl,
13023 t * n_layer,
13024 self.cfg.rms_eps,
13025 )?;
13026 let mut out = e.uninit(t * width)?;
13027 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
13028 Ok(out)
13029 }
13030
13031 #[allow(clippy::too_many_arguments)]
13036 fn gemma4_e4b_attn(
13037 &self,
13038 e: &Engine,
13039 il: usize,
13040 hq: &CudaSlice<i8>,
13041 hdq: &CudaSlice<f32>,
13042 pos_d: &CudaSlice<i32>,
13043 t: usize,
13044 cache: &mut Cache,
13045 dc_bucket: Option<usize>,
13046 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13047 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
13048 let eps = self.cfg.rms_eps;
13049 let aux = self.gemma4_aux.as_ref().unwrap();
13050 let ones = aux.ones(e);
13051 #[cfg(debug_assertions)]
13052 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
13053 let Mixer::Full(fa) = &self.layers[il].mixer else {
13054 unreachable!()
13055 };
13056 let h0 = e.zeros(0)?;
13060 let h = &h0;
13061
13062 let ff = if swa {
13063 None
13064 } else {
13065 Some(
13066 aux.rope_freqs(e)
13067 .expect("e4b global rope needs rope_freqs.weight"),
13068 )
13069 };
13070 #[cfg(debug_assertions)]
13071 if let Some(ff) = ff {
13072 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
13073 }
13074 let share = self.gemma4_e4b_kv_target(il);
13075 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
13077 let mut q;
13078 if let Some(_tgt) = share {
13079 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
13080 q = e.uninit(t * nh * hd)?;
13081 let mut kdummy = e.uninit(1)?;
13084 let mut vdummy = e.uninit(1)?;
13085 e.rms_norm_qkv_rope(
13086 &q0,
13087 &q0,
13088 &q0,
13089 fa.q_norm.float_data(),
13090 fa.q_norm.float_data(),
13091 ones,
13092 &mut q,
13093 &mut kdummy,
13094 &mut vdummy,
13095 hd,
13096 nh * t,
13097 0,
13098 pos_d,
13099 nh,
13100 1,
13101 base,
13102 1.0,
13103 ff,
13104 eps,
13105 )?;
13106 } else {
13107 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
13111 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
13112 q = e.uninit(t * nh * hd)?;
13113 let mut k = e.uninit(t * nkv * hd)?;
13114 let mut v = e.uninit(t * nkv * hd)?;
13115 if t == 1 && cat.is_some() {
13116 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
13117 e.rms_norm_qkv_rope_cat(
13118 &qkv0,
13119 fa.q_norm.float_data(),
13120 fa.k_norm.float_data(),
13121 ones,
13122 &mut q,
13123 &mut k,
13124 &mut v,
13125 hd,
13126 nh,
13127 nkv,
13128 pos_d,
13129 nh,
13130 nkv,
13131 base,
13132 1.0,
13133 ff,
13134 eps,
13135 )?;
13136 } else {
13137 let (q0, k0, v0) = match if t == 1 {
13138 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
13139 } else {
13140 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13143 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
13144 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
13145 } else {
13146 None
13147 }
13148 } {
13149 Some(triple) => triple,
13150 None => (
13151 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
13152 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
13153 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
13154 ), };
13156 e.rms_norm_qkv_rope(
13159 &q0,
13160 &k0,
13161 &v0,
13162 fa.q_norm.float_data(),
13163 fa.k_norm.float_data(),
13164 ones,
13165 &mut q,
13166 &mut k,
13167 &mut v,
13168 hd,
13169 nh * t,
13170 nkv * t,
13171 pos_d,
13172 nh,
13173 nkv,
13174 base,
13175 1.0,
13176 ff,
13177 eps,
13178 )?;
13179 }
13180 let kvl = cache.kv[il].as_mut().unwrap();
13181 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13185 if dc_bucket.is_some() {
13186 debug_assert!(t == 1);
13191 e.append_kv_quantized_row_dc_inc(
13193 &k,
13194 &v,
13195 &mut kvl.k,
13196 &mut kvl.v,
13197 &mut kvl.len_d,
13198 kvl.kv_dim_k,
13199 kvl.kv_dim_v,
13200 kvl.k_tok_bytes,
13201 kvl.v_tok_bytes,
13202 cls,
13203 )?;
13204 } else {
13205 e.append_kv_quantized_rows(
13206 &k,
13207 &v,
13208 &mut kvl.k,
13209 &mut kvl.v,
13210 kvl.len,
13211 t,
13212 kvl.kv_dim_k,
13213 kvl.kv_dim_v,
13214 kvl.k_tok_bytes,
13215 kvl.v_tok_bytes,
13216 cls,
13217 )?;
13218 kvl.len += t;
13219 }
13220 kv_f32 = Some((k, v));
13221 }
13222 let kvl_idx = share.unwrap_or(il);
13225 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
13226 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13228 let mut attn = e.uninit(t * nh * hd)?;
13229 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
13241 if let Some((kf, vf)) = &kv_f32 {
13242 if hd == 256 && t <= win {
13243 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
13244 return Ok(e.matmul(&fa.wo, &attn, t)?);
13245 }
13246 if hd == 256 && swa && t > win {
13247 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
13248 return Ok(e.matmul(&fa.wo, &attn, t)?);
13249 }
13250 if hd == 512 && !swa {
13251 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
13252 return Ok(e.matmul(&fa.wo, &attn, t)?);
13253 }
13254 } else if share.is_some() {
13255 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13256 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13257 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13258 if hd == 256 && (!swa || t <= win) {
13259 e.fa_prefill_view(
13261 &q,
13262 &k_view,
13263 &v_view,
13264 &mut attn,
13265 hd,
13266 nh,
13267 nkv,
13268 t,
13269 t,
13270 scale,
13271 true,
13272 kvl.k_tok_bytes,
13273 kvl.v_tok_bytes,
13274 g,
13275 )?;
13276 return Ok(e.matmul(&fa.wo, &attn, t)?);
13277 }
13278 let kv_dim = nkv * hd;
13281 let mut kf = e.uninit(t * kv_dim)?;
13282 let mut vf = e.uninit(t * kv_dim)?;
13283 e.fa_dequant_kv_view_f32(
13284 &k_view,
13285 &v_view,
13286 &mut kf,
13287 &mut vf,
13288 kv_dim,
13289 kv_dim,
13290 t,
13291 kvl.k_tok_bytes,
13292 kvl.v_tok_bytes,
13293 g,
13294 )?;
13295 if hd == 512 {
13296 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
13297 } else {
13298 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
13299 }
13300 return Ok(e.matmul(&fa.wo, &attn, t)?);
13301 }
13302 }
13303 if let Some(bucket) = dc_bucket {
13304 assert!(t == 1);
13309 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
13315 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
13316 } else {
13317 bucket
13318 };
13319 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13320 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13321 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13322 if crate::Engine::wpf_level() >= 1 {
13330 e.prefetch_weight_l2(&fa.wo)?;
13331 }
13332 if e.uses_q8_1_fast(&fa.wo) {
13335 let mut oq = e.alloc_i8_uninit(nh * hd)?;
13336 let mut od = e.zeros(nh * hd / 32)?;
13337 e.fa_decode_dc_q8(
13338 &q,
13339 &k_view,
13340 &v_view,
13341 &mut attn,
13342 hd,
13343 nh,
13344 nkv,
13345 &kvl.len_d,
13346 bucket,
13347 scale,
13348 kvl.k_tok_bytes,
13349 kvl.v_tok_bytes,
13350 g,
13351 Some((&mut oq, &mut od)),
13352 )?;
13353 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
13354 }
13355 e.fa_decode_dc(
13356 &q,
13357 &k_view,
13358 &v_view,
13359 &mut attn,
13360 hd,
13361 nh,
13362 nkv,
13363 &kvl.len_d,
13364 bucket,
13365 scale,
13366 kvl.k_tok_bytes,
13367 kvl.v_tok_bytes,
13368 g,
13369 )?;
13370 return Ok(e.matmul(&fa.wo, &attn, t)?);
13371 }
13372 for i in 0..t {
13373 let avail = base_len + i + 1;
13374 let (off_tok, t_kv) = if swa && avail > win {
13375 (avail - win, win)
13376 } else {
13377 (0, avail)
13378 };
13379 let k_view = e.view_u8_range(
13380 &kvl.k,
13381 off_tok * kvl.k_tok_bytes,
13382 (off_tok + t_kv) * kvl.k_tok_bytes,
13383 );
13384 let v_view = e.view_u8_range(
13385 &kvl.v,
13386 off_tok * kvl.v_tok_bytes,
13387 (off_tok + t_kv) * kvl.v_tok_bytes,
13388 );
13389 let qv = e.view(&q, t * nh * hd);
13390 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
13391 let mut q_one = e.uninit(nh * hd)?;
13392 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
13393 let mut a_one = e.uninit(nh * hd)?;
13394 e.fa_decode_kvmod(
13398 &q_one,
13399 &k_view,
13400 &v_view,
13401 &mut a_one,
13402 hd,
13403 nh,
13404 nkv,
13405 t_kv,
13406 scale,
13407 kvl.k_tok_bytes,
13408 kvl.v_tok_bytes,
13409 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
13410 )?;
13411 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
13412 }
13413 Ok(e.matmul(&fa.wo, &attn, t)?)
13414 }
13415
13416 fn gemma4_e4b_trunk(
13421 &self,
13422 e: &Engine,
13423 tokens: &[u32],
13424 pos0: usize,
13425 cache: &mut Cache,
13426 head_last: bool,
13427 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13428 let n_embd = self.cfg.n_embd as usize;
13429 let t = tokens.len();
13430 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
13431 let pos_d = e.htod_i32(&pos)?;
13432 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
13433 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13434 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
13435 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
13436 }
13437
13438 fn gemma4_e4b_trunk_core(
13442 &self,
13443 e: &Engine,
13444 x_in: CudaSlice<f32>,
13445 inp_pl: CudaSlice<f32>,
13446 pos_d: &CudaSlice<i32>,
13447 t: usize,
13448 cache: &mut Cache,
13449 dc_bucket: Option<usize>,
13450 cap_logits: bool,
13451 head_last: bool,
13452 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13453 let n_embd = self.cfg.n_embd as usize;
13454 let eps = self.cfg.rms_eps;
13455 let n_layer = self.layers.len();
13456 let mut x = x_in;
13457 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
13458 let n_epl = aux_e4b.n_epl;
13459
13460 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13466 for il in 0..n_layer {
13467 let layer = &self.layers[il];
13468 let (hq, hdq) = match h_carry.take() {
13469 Some(p) => p,
13470 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
13471 };
13472 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
13473 let bits = layer.gemma4.as_ref().unwrap();
13476 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
13477 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
13488 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
13489 e,
13490 layer,
13491 &o,
13492 &x,
13493 t,
13494 Some(layer.post_attn_norm.float_data()),
13495 fuse_exit,
13496 )?;
13497 let mut resid = e.uninit(t * n_embd)?;
13498 let g = if fuse_exit {
13504 let (rq, rd) = e.rms_pre_add_q8_1(
13506 &sn,
13507 bits.post_ffw_norm.float_data(),
13508 &attn_out,
13509 &mut resid,
13510 n_embd,
13511 t,
13512 self.cfg.rms_eps,
13513 )?;
13514 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
13515 } else {
13516 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
13517 e.matmul(&e4b.inp_gate, &resid, t)?
13518 };
13519 let mut act = e.uninit(t * n_epl)?;
13520 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
13521 let ipv = e.view(&inp_pl, n_epl * n_layer);
13522 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
13523 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
13524 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
13525 } else {
13526 let mut inp_this = e.uninit(t * n_epl)?;
13527 e.copy_rows_strided(
13528 &inp_pl,
13529 &mut inp_this,
13530 n_epl,
13531 t,
13532 n_epl * n_layer,
13533 il * n_epl,
13534 )?;
13535 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
13536 e.matmul(&e4b.proj, &act, t)?
13537 };
13538 let next_norm = if il + 1 < n_layer {
13541 self.layers[il + 1].attn_norm.float_data()
13542 } else {
13543 self.output_norm.float_data()
13544 };
13545 let mut xn = e.uninit(t * n_embd)?;
13546 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
13547 &y,
13548 e4b.post_norm.float_data(),
13549 &resid,
13550 bits.layer_scale,
13551 next_norm,
13552 &mut xn,
13553 n_embd,
13554 t,
13555 eps,
13556 )?;
13557 h_carry = Some(pair);
13558 x = xn;
13559 }
13560 let (oq, odq) = h_carry.take().unwrap();
13564 let h0 = e.zeros(0)?;
13565 let hm = if head_last { 1 } else { t };
13566 let (hq, hd) = if head_last && t > 1 {
13567 let mut q1 = e.uninit_i8(n_embd)?;
13568 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
13569 let nb = n_embd / 32;
13570 let mut d1 = e.uninit(nb)?;
13571 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
13572 (q1, d1)
13573 } else {
13574 (oq, odq)
13575 };
13576 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
13577 if cap_logits {
13581 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
13582 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
13583 }
13584 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
13586 }
13587
13588 pub fn gemma4_e4b_decode_step_t_am_dev(
13595 &self,
13596 e: &Engine,
13597 tok_d: &CudaSlice<u32>,
13598 t: usize,
13599 pos0: usize,
13600 cache: &mut Cache,
13601 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13602 let n_embd = self.cfg.n_embd as usize;
13603 let eps = self.cfg.rms_eps;
13604 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
13605 let pos_d = e.htod_i32(&pos)?;
13606 let embd_gpu = self
13607 .embd_gpu
13608 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13609 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13610 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
13611 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13612 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
13613 let (ld, xp) =
13614 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
13615 let n_vocab = self.output.out_features();
13618 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13619 for i in 0..t {
13620 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13621 }
13622 let mut hn = e.uninit(t * n_embd)?;
13623 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13624 cache.pos += t;
13625 Ok((vam, hn))
13626 }
13627
13628 pub(crate) fn gemma4_e4b_decode_step_t_h(
13631 &self,
13632 e: &Engine,
13633 tokens: &[u32],
13634 pos0: usize,
13635 cache: &mut Cache,
13636 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13637 let n_embd = self.cfg.n_embd as usize;
13638 let eps = self.cfg.rms_eps;
13639 let t = tokens.len();
13640 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
13641 let mut hn = e.uninit(t * n_embd)?;
13642 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13643 cache.pos += t;
13644 Ok((e.dtoh(&ld)?, hn))
13645 }
13646
13647 pub fn gemma4_e4b_decode_step_dcg(
13653 &self,
13654 e: &Engine,
13655 token_d: &mut CudaSlice<u32>,
13656 pos_d: &mut CudaSlice<i32>,
13657 embd_gpu: &CudaSlice<u8>,
13658 embd_qt: i32,
13659 embd_rb: usize,
13660 cache: &mut Cache,
13661 n_vocab: usize,
13662 bucket: usize,
13663 ) -> Result<(), Box<dyn std::error::Error>> {
13664 let n_embd = self.cfg.n_embd as usize;
13665 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
13666 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
13667 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
13668 let (ld, _x) =
13669 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
13670 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
13671 e.inc_seqlen(pos_d)?;
13672 Ok(())
13673 }
13674
13675 #[allow(clippy::too_many_arguments)]
13683 pub fn gemma4_e4b_decode_step_dc(
13684 &self,
13685 e: &Engine,
13686 token_d: &CudaSlice<u32>,
13687 pos_d: &mut CudaSlice<i32>,
13688 embd_gpu: &CudaSlice<u8>,
13689 embd_qt: i32,
13690 embd_rb: usize,
13691 cache: &mut Cache,
13692 n_vocab: usize,
13693 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
13694 let n_embd = self.cfg.n_embd as usize;
13695 let eps = self.cfg.rms_eps;
13696 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
13697 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
13698 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
13699 let (ld, _x) =
13700 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
13701 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
13702 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
13703 e.inc_seqlen(pos_d)?;
13704 cache.pos += 1;
13705 let _ = eps;
13706 Ok(tok_out)
13707 }
13708
13709 pub(crate) fn gemma4_e4b_decode_step_h(
13712 &self,
13713 e: &Engine,
13714 token: u32,
13715 cache: &mut Cache,
13716 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13717 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
13718 let logits = e.dtoh(&ld)?;
13719 cache.pos += 1;
13720 Ok((logits, x))
13721 }
13722
13723 pub(crate) fn gemma4_e4b_prime(
13727 &self,
13728 e: &Engine,
13729 tokens: &[u32],
13730 cache: &mut Cache,
13731 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13732 if cache.pos != 0 {
13735 return Err(
13736 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
13737 call or decode tokenwise"
13738 .into(),
13739 );
13740 }
13741 let n_embd = self.cfg.n_embd as usize;
13742 let t = tokens.len();
13743 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
13744 cache.pos += t;
13745 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
13747 let row = xv.slice((t - 1) * n_embd..t * n_embd);
13748 let mut h_seed = e.uninit(n_embd)?;
13749 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
13750 Ok((last, h_seed, x))
13751 }
13752
13753 pub(crate) fn gemma4_e4b_forward(
13755 &self,
13756 e: &Engine,
13757 tokens: &[u32],
13758 last_only: bool,
13759 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13760 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
13761 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
13762 Ok(e.dtoh(&ld)?) }
13764}
13765
13766#[cfg(test)]
13767mod prime_chunk_schedule_tests {
13768 use super::{
13769 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, dynamic_prime_chunk_ranges, fixed_prime_chunk_ranges,
13770 fixed_prime_chunk_ranges_for_ring,
13771 };
13772
13773 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
13774 ranges.iter().map(|(start, end)| end - start).collect()
13775 }
13776
13777 fn auto_chunk(t: usize) -> usize {
13778 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
13779 }
13780
13781 #[test]
13782 fn fixed_schedule_retains_measured_geometry() {
13783 assert_eq!(
13784 sizes(&fixed_prime_chunk_ranges(461, 128)),
13785 vec![128, 128, 128, 77]
13786 );
13787 assert_eq!(
13788 sizes(&fixed_prime_chunk_ranges(1833, 230)),
13789 vec![230, 230, 230, 230, 230, 230, 230, 223]
13790 );
13791 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
13792 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
13793 assert_eq!(capped, vec![4096, 4088, 16]);
13794 assert!(capped.iter().all(|&rows| rows <= 4096));
13795 assert_eq!(
13796 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
13797 vec![4100],
13798 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
13799 );
13800 }
13801
13802 #[test]
13803 fn dynamic_schedule_matches_registered_shapes() {
13804 let cases = [
13805 (461, vec![64, 141, 132, 124]),
13806 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
13807 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
13808 ];
13809 for (t, expected) in cases {
13810 let chunk = auto_chunk(t);
13811 let fixed = fixed_prime_chunk_ranges(t, chunk);
13812 assert_eq!(
13813 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
13814 expected
13815 );
13816 }
13817 }
13818
13819 #[test]
13820 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
13821 for t in 256..=8192 {
13822 let chunk = auto_chunk(t);
13823 let fixed = fixed_prime_chunk_ranges(t, chunk);
13824 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
13825 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
13826 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
13827 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
13828 for pair in dynamic.windows(2) {
13829 assert_eq!(pair[0].1, pair[1].0, "T={t}");
13830 }
13831 assert!(
13832 dynamic
13833 .iter()
13834 .all(|(start, end)| end - start >= PRIME_MIN_T),
13835 "T={t} sizes={:?}",
13836 sizes(&dynamic)
13837 );
13838 if dynamic.len() >= 3 {
13839 let chunk_sizes = sizes(&dynamic);
13840 assert!(
13841 chunk_sizes[0] < chunk_sizes[1],
13842 "T={t} sizes={chunk_sizes:?}"
13843 );
13844 assert!(
13845 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
13846 "T={t} sizes={chunk_sizes:?}"
13847 );
13848 }
13849 }
13850 }
13851}
13852
13853#[cfg(test)]
13854mod page_prefetch_tests {
13855 use super::{
13856 grouped_worker_prefetch_position, page_prefetch_positions,
13857 page_prefetch_window_from_values, worker_prefetch_positions,
13858 };
13859
13860 #[test]
13861 fn page_prefetch_window_keeps_existing_opt_in_default() {
13862 assert_eq!(page_prefetch_window_from_values(false, None), 0);
13863 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
13864 assert_eq!(page_prefetch_window_from_values(true, None), 1);
13865 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
13866 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
13867 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
13868 }
13869
13870 #[test]
13871 fn rolling_page_prefetch_advises_each_future_expert_once() {
13872 let advised: Vec<_> = (0..7)
13873 .flat_map(|position| page_prefetch_positions(position, 7, 3))
13874 .collect();
13875 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
13876
13877 let one_ahead: Vec<_> = (0..4)
13878 .flat_map(|position| page_prefetch_positions(position, 4, 1))
13879 .collect();
13880 assert_eq!(one_ahead, vec![1, 2, 3]);
13881 assert!(page_prefetch_positions(0, 4, 0).is_empty());
13882 }
13883
13884 #[test]
13885 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
13886 assert_eq!(grouped_worker_prefetch_position(0, None), None);
13887 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
13888 .chain(
13889 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
13890 )
13891 .collect();
13892 assert_eq!(positions, vec![0, 1, 2, 3]);
13893 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
13894 }
13895
13896 #[test]
13897 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
13898 let queued: Vec<_> = (0..8)
13899 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
13900 .collect();
13901 assert_eq!(queued, (0..8).collect::<Vec<_>>());
13902
13903 let one_at_a_time: Vec<_> = (0..4)
13904 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
13905 .collect();
13906 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
13907 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
13908 }
13909}
13910
13911pub struct G4DcSlots {
13912 x: CudaSlice<f32>,
13913 xn: CudaSlice<f32>,
13914 cur: CudaSlice<f32>,
13915 hq: CudaSlice<i8>,
13916 hd_: CudaSlice<f32>,
13917 q0: CudaSlice<f32>,
13918 k0: CudaSlice<f32>,
13919 v0: CudaSlice<f32>,
13920 q: CudaSlice<f32>,
13921 k: CudaSlice<f32>,
13922 v: CudaSlice<f32>,
13923 attn: CudaSlice<f32>,
13924 o: CudaSlice<f32>,
13925 attn_out: CudaSlice<f32>,
13926 zsh: CudaSlice<f32>,
13927 zq: CudaSlice<i8>,
13928 zd: CudaSlice<f32>,
13929 gate: CudaSlice<f32>,
13930 up: CudaSlice<f32>,
13931 act: CudaSlice<f32>,
13932 actq: CudaSlice<i8>,
13933 actd: CudaSlice<f32>,
13934 f0: CudaSlice<f32>,
13935 sn: CudaSlice<f32>,
13936 hn: CudaSlice<f32>,
13937 logits: CudaSlice<f32>,
13938}