1use crate::Engine;
6use crate::cache::Cache;
7use cudarc::driver::CudaSlice;
8use memra_gguf::config::ModelConfig;
9
10pub struct PrimeSlabs {
14 pub t_cap: usize,
15 pub h: CudaSlice<f32>,
16 pub x1: CudaSlice<f32>,
17 pub z: CudaSlice<f32>,
18 pub act: CudaSlice<f32>,
19 pub xa: CudaSlice<f32>,
20 pub xb: CudaSlice<f32>,
21 pub h16: CudaSlice<u8>,
22 pub z16: CudaSlice<u8>,
23 pub gate: CudaSlice<f32>, pub up: CudaSlice<f32>, pub ffn_out: CudaSlice<f32>, pub seg_glue: Vec<Option<cudarc::driver::CudaGraph>>,
33 pub mixed: CudaSlice<f32>,
37 pub seg_mid: Vec<Option<cudarc::driver::CudaGraph>>,
38 pub seg_t: usize,
39}
40
41unsafe impl Send for PrimeSlabs {}
44
45fn shexp_gate_up_t1(
54 e: &Engine,
55 gate_shexp: &crate::model::GpuTensor,
56 up_shexp: &crate::model::GpuTensor,
57 z: &CudaSlice<f32>,
58 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
59) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
60 let is_nvfp4 = |w: &crate::model::GpuTensor| matches!(w, crate::model::GpuTensor::Quant { qtype, .. } if *qtype == crate::QT_NVFP4);
61 if is_nvfp4(gate_shexp) && is_nvfp4(up_shexp) {
62 let pair = match zq8 {
66 Some((zq, zd)) => e.matmul_nvfp4_fused2(gate_shexp, up_shexp, zq, zd, 1)?,
67 None => {
68 let (zq, zd) = e.quantize_q8_1(z, 1, gate_shexp.in_features())?;
69 e.matmul_nvfp4_fused2(gate_shexp, up_shexp, &zq, &zd, 1)?
70 }
71 };
72 if let Some(pair) = pair {
73 return Ok(pair);
74 }
75 }
76 match e.matmul_q8_fused2_x(gate_shexp, up_shexp, z)? {
77 Some(pair) => Ok(pair),
78 None => Ok((e.matmul(gate_shexp, z, 1)?, e.matmul(up_shexp, z, 1)?)),
79 }
80}
81
82fn active_matrix_values(
83 available: usize,
84 rows: usize,
85 columns: usize,
86 label: &str,
87) -> Result<usize, String> {
88 let required = rows
89 .checked_mul(columns)
90 .ok_or_else(|| format!("{label} shape overflows: {rows}x{columns}"))?;
91 if available < required {
92 return Err(format!(
93 "{label} has {available} values, fewer than the active {rows}x{columns} ({required})"
94 ));
95 }
96 Ok(required)
97}
98
99fn step_grouped_decode_shape(prefill: bool, tokens: usize) -> bool {
100 !prefill && tokens == 1
101}
102
103fn parse_step_ep_grouped_prefill(value: Option<&str>) -> Result<bool, String> {
104 match value {
105 None | Some("") | Some("0") => Ok(false),
106 Some("1") => Ok(true),
107 Some(value) => Err(format!(
108 "MEMRA_STEP_EP_GROUPED_PREFILL={value:?} is invalid; expected 0 or 1"
109 )),
110 }
111}
112
113fn step_ep_grouped_prefill_enabled() -> Result<bool, String> {
114 parse_step_ep_grouped_prefill(
115 std::env::var("MEMRA_STEP_EP_GROUPED_PREFILL")
116 .ok()
117 .as_deref(),
118 )
119}
120
121fn step_grouped_prefill_shape(enabled: bool, prefill: bool, tokens: usize) -> bool {
122 enabled && prefill && (PRIME_MIN_T..=crate::cache::PRIME_CHUNK_MAX_TOKENS).contains(&tokens)
123}
124
125fn parse_step_tp_prefill(value: Option<&str>) -> Result<bool, String> {
126 match value {
127 None | Some("") | Some("0") => Ok(false),
128 Some("1") => Ok(true),
129 Some(value) => Err(format!(
130 "MEMRA_STEP_TP_PREFILL={value:?} is invalid; expected 0 or 1"
131 )),
132 }
133}
134
135fn step_tp_prefill_enabled() -> Result<bool, String> {
136 parse_step_tp_prefill(std::env::var("MEMRA_STEP_TP_PREFILL").ok().as_deref())
137}
138
139fn validate_step_prime_batch_modes(tp_prefill: bool, grouped_prefill: bool) -> Result<(), String> {
140 if grouped_prefill && !tp_prefill {
141 return Err("MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into());
142 }
143 if tp_prefill {
144 return Err(
145 "Step TP4 cross-request prime batching did not clear the live-server performance \
146 gate; use per-session grouped prefill"
147 .into(),
148 );
149 }
150 Ok(())
151}
152
153fn step_tp_prefill_shape(
154 enabled: bool,
155 tokens: usize,
156 ranks: usize,
157 native_p2p: bool,
158 has_rank_local_attention: bool,
159 fp8_kv: bool,
160) -> bool {
161 enabled
162 && tokens >= PRIME_MIN_T
163 && ranks == 4
164 && native_p2p
165 && has_rank_local_attention
166 && !fp8_kv
167}
168
169fn empty_cache_layers<T>(n: usize) -> Vec<Option<T>> {
170 std::iter::repeat_with(|| None).take(n).collect()
171}
172
173struct PrimeCacheStages<'a> {
178 parent: &'a mut Cache,
179 cut: usize,
180 stage0: Cache,
181 stage1: Cache,
182}
183
184impl<'a> PrimeCacheStages<'a> {
185 fn new(parent: &'a mut Cache, cut: usize) -> Self {
186 let n = parent.kv.len();
187 assert_eq!(parent.recur.len(), n, "cache layer vectors disagree");
188 assert!(cut <= n, "PP-2 cache cut {cut} exceeds {n} layers");
189 let mut kv0 = empty_cache_layers(n);
190 let mut kv1 = empty_cache_layers(n);
191 let mut tp_kv0 = empty_cache_layers(n);
192 let mut tp_kv1 = empty_cache_layers(n);
193 let mut recur0 = empty_cache_layers(n);
194 let mut recur1 = empty_cache_layers(n);
195 for i in 0..cut {
196 kv0[i] = parent.kv[i].take();
197 tp_kv0[i] = parent.tp_kv[i].take();
198 recur0[i] = parent.recur[i].take();
199 }
200 for i in cut..n {
201 kv1[i] = parent.kv[i].take();
202 tp_kv1[i] = parent.tp_kv[i].take();
203 recur1[i] = parent.recur[i].take();
204 }
205 let pos = parent.pos;
206 let max_ctx = parent.max_ctx;
207 Self {
208 parent,
209 cut,
210 stage0: Cache {
211 kv: kv0,
212 tp_kv: tp_kv0,
213 recur: recur0,
214 pos,
215 max_ctx,
216 last_logits_dev: None,
217 dflash_taps: None,
218 },
219 stage1: Cache {
220 kv: kv1,
221 tp_kv: tp_kv1,
222 recur: recur1,
223 pos,
224 max_ctx,
225 last_logits_dev: None,
226 dflash_taps: None,
227 },
228 }
229 }
230
231 fn parts(&mut self) -> (&mut Cache, &mut Cache) {
232 (&mut self.stage0, &mut self.stage1)
233 }
234}
235
236impl Drop for PrimeCacheStages<'_> {
237 fn drop(&mut self) {
238 let n = self.parent.kv.len();
239 for i in 0..n {
240 let source = if i < self.cut {
241 &mut self.stage0
242 } else {
243 &mut self.stage1
244 };
245 debug_assert!(self.parent.kv[i].is_none());
246 debug_assert!(self.parent.tp_kv[i].is_none());
247 debug_assert!(self.parent.recur[i].is_none());
248 self.parent.kv[i] = source.kv[i].take();
249 self.parent.tp_kv[i] = source.tp_kv[i].take();
250 self.parent.recur[i] = source.recur[i].take();
251 }
252 self.parent.pos = self.stage0.pos.min(self.stage1.pos);
253 }
254}
255
256pub(crate) struct AttnPre {
258 pub q: cudarc::driver::CudaSlice<f32>,
259 pub k: cudarc::driver::CudaSlice<f32>,
260 pub v: cudarc::driver::CudaSlice<f32>,
261 pub gate: Option<cudarc::driver::CudaSlice<f32>>,
262}
263
264pub(crate) struct GdnPrep {
266 pub hk: usize,
267 pub q_l2: cudarc::driver::CudaSlice<f32>,
268 pub k_l2: cudarc::driver::CudaSlice<f32>,
269 pub v_g: cudarc::driver::CudaSlice<f32>,
270 pub beta: cudarc::driver::CudaSlice<f32>,
271 pub g_log: cudarc::driver::CudaSlice<f32>,
272 pub kb16: Option<cudarc::driver::CudaSlice<u8>>,
273 pub qb16: Option<cudarc::driver::CudaSlice<u8>>,
274}
275
276pub(crate) struct VerifyStreamScratch {
278 pub pos_d: CudaSlice<i32>,
279 pub row_ctrs: Vec<CudaSlice<i32>>,
280}
281use crate::hybrid::{FullAttnLayer, HybridModel, LinearAttnLayer, Mixer, MoeWeights};
282
283struct MoeInputTraceWriter {
284 dir: std::path::PathBuf,
285 index: std::fs::File,
286 payloads: std::collections::HashMap<u16, (std::fs::File, u64)>,
287}
288
289static MOE_INPUT_TRACE_WRITER: std::sync::OnceLock<std::sync::Mutex<Option<MoeInputTraceWriter>>> =
290 std::sync::OnceLock::new();
291
292fn gdec_enabled() -> bool {
295 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
296 *E.get_or_init(|| {
297 std::env::var("MEMRA_MOE_GDEC")
298 .map(|v| v != "0")
299 .unwrap_or(true)
300 })
301}
302
303fn moe_slab_enabled() -> bool {
314 std::env::var("MEMRA_MOE_SLAB").as_deref() != Ok("0")
315}
316
317fn moe_grouped_enabled(_cfg: &ModelConfig, _prefill: bool) -> bool {
321 std::env::var("MEMRA_MOE_GROUPED")
322 .map(|value| value != "0")
323 .unwrap_or(false)
324}
325
326fn moe_prefetch_enabled() -> bool {
329 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
330 *E.get_or_init(|| {
331 std::env::var("MEMRA_MOE_PREFETCH").as_deref() == Ok("1")
332 || crate::spill_pread::worker_enabled()
333 })
334}
335
336fn moe_page_prefetch_window() -> usize {
341 static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
342 *W.get_or_init(|| {
343 page_prefetch_window_from_values(
344 std::env::var("MEMRA_MOE_PAGE_PREFETCH").as_deref() == Ok("1"),
345 std::env::var("MEMRA_MOE_PAGE_PREFETCH_WINDOW")
346 .ok()
347 .as_deref(),
348 )
349 })
350}
351
352fn page_prefetch_window_from_values(enabled: bool, raw_window: Option<&str>) -> usize {
353 if !enabled {
354 return 0;
355 }
356 raw_window.and_then(|value| value.parse().ok()).unwrap_or(1)
357}
358
359fn page_prefetch_positions(position: usize, len: usize, window: usize) -> std::ops::Range<usize> {
363 if window == 0 || position >= len {
364 return len..len;
365 }
366 let (start, count) = if position == 0 {
367 (1, window)
368 } else {
369 (position.saturating_add(window), 1)
370 };
371 let start = start.min(len);
372 start..start.saturating_add(count).min(len)
373}
374
375fn grouped_worker_prefetch_position(order_len: usize, current: Option<usize>) -> Option<usize> {
378 let position = current.map_or(0, |position| position.saturating_add(1));
379 (position < order_len).then_some(position)
380}
381
382fn worker_prefetch_window() -> usize {
387 static WINDOW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
388 *WINDOW.get_or_init(|| {
389 let automatic = crate::spill_pread::configured_depth().saturating_sub(1) / 3;
390 std::env::var("MEMRA_SPILL_WORKER_EXPERT_WINDOW")
391 .ok()
392 .and_then(|value| value.parse::<usize>().ok())
393 .unwrap_or(automatic.max(1))
394 })
395}
396
397fn worker_prefetch_positions(position: usize, len: usize, window: usize) -> std::ops::Range<usize> {
401 if window == 0 || position >= len {
402 return len..len;
403 }
404 let (start, count) = if position == 0 {
405 (0, window)
406 } else {
407 (position.saturating_add(window).saturating_sub(1), 1)
408 };
409 let start = start.min(len);
410 start..start.saturating_add(count).min(len)
411}
412
413fn moe_dev_enabled() -> bool {
418 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
419 *E.get_or_init(|| {
420 std::env::var("MEMRA_MOE_DEV")
421 .map(|v| v != "0")
422 .unwrap_or(true)
423 && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0"))
424 })
425}
426
427fn sigmoid_router_enabled() -> bool {
430 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
431 *E.get_or_init(|| {
432 std::env::var("MEMRA_SIG_ROUTER")
433 .map(|v| v != "0")
434 .unwrap_or(true)
435 })
436}
437
438fn moe_q8_enabled() -> bool {
443 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
444 *E.get_or_init(|| {
445 std::env::var("MEMRA_MOE_Q8")
446 .map(|v| v != "0")
447 .unwrap_or(true)
448 })
449}
450
451fn expert_dp4a_supported(qt: i32) -> bool {
454 qt == crate::QT_Q4_0
455 || qt == crate::QT_IQ3_S
456 || qt == crate::QT_IQ4_XS
457 || qt == crate::QT_Q3_K
458 || qt == crate::QT_Q4_K
459 || qt == crate::QT_Q6_K
460}
461
462fn q8_expert_supported(qt: i32) -> bool {
463 static KQ: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
469 let kq = *KQ.get_or_init(|| {
470 std::env::var("MEMRA_MOE_Q8_KQ")
471 .map(|v| v != "0")
472 .unwrap_or(true)
473 });
474 let nvfp4_q8 = std::env::var("MEMRA_MOE_Q8_NVFP4")
481 .map(|v| v != "0")
482 .unwrap_or(true);
483 qt == crate::QT_IQ3_S
484 || qt == crate::QT_IQ4_XS
485 || (nvfp4_q8 && qt == crate::QT_NVFP4)
486 || (kq && (qt == crate::QT_Q3_K || qt == crate::QT_Q4_K || qt == crate::QT_Q6_K))
487}
488
489fn q8_expert_dec_supported(qt: i32) -> bool {
492 qt == crate::QT_IQ3_S || qt == crate::QT_IQ4_XS || qt == crate::QT_Q4_0
493}
494
495fn f16g_proj_ok(qt: i32, in_f: usize) -> bool {
501 match qt {
502 crate::QT_Q4_0 => in_f % 32 == 0,
503 crate::QT_IQ4_XS | crate::QT_IQ3_S | crate::QT_Q3_K | crate::QT_Q4_K | crate::QT_Q6_K => {
504 in_f % 256 == 0
505 }
506 crate::QT_NVFP4 => in_f % 64 == 0,
511 _ => false,
512 }
513}
514
515fn moe_prewarm_enabled() -> bool {
518 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
519 *E.get_or_init(|| {
520 std::env::var("MEMRA_MOE_PREWARM")
521 .map(|v| v != "0")
522 .unwrap_or(true)
523 })
524}
525
526fn cpu_expert_profile_admit_enabled() -> bool {
530 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
531 *E.get_or_init(|| std::env::var("MEMRA_CPU_EXPERT_FREEZE_PROFILE_ADMIT").as_deref() == Ok("1"))
532}
533
534pub const PRIME_MIN_T: usize = 16;
538
539const MOE_DEV_MAX_T: usize = 16;
548const PRIME_PIPE_MICROBATCHES: usize = 8;
549const PRIME_PIPE_MIN_CHUNK: usize = 128;
550const PRIME_PIPE_EDGE_MIN_CHUNK: usize = 64;
551const PRIME_PIPE_LINEAR_WORK: usize = 8;
552
553fn prime_pp2_auto_geometry(n_layers: usize) -> bool {
554 crate::pp::prime_pp_on()
555 && !crate::pp::pp2_streams_off()
556 && crate::pp::pp_cuts(n_layers).is_some_and(|cuts| cuts.len() == 3)
557}
558
559pub fn prime_chunk_tokens(t: usize, n_layers: usize) -> usize {
563 if let Ok(value) = std::env::var("MEMRA_PRIME_CHUNK") {
564 let parsed = value
565 .parse::<usize>()
566 .unwrap_or(crate::cache::PRIME_CHUNK_MAX_TOKENS);
567 return if crate::cache::swa_ring_on() {
568 if parsed == 0 {
569 crate::cache::PRIME_CHUNK_MAX_TOKENS
570 } else {
571 parsed.min(crate::cache::PRIME_CHUNK_MAX_TOKENS)
572 }
573 } else {
574 parsed
575 };
576 }
577 let chunk = crate::cache::PRIME_CHUNK_MAX_TOKENS;
578 if prime_pp2_auto_geometry(n_layers) && t >= 2 * PRIME_PIPE_MIN_CHUNK {
579 chunk.min(
580 t.div_ceil(PRIME_PIPE_MICROBATCHES)
581 .max(PRIME_PIPE_MIN_CHUNK),
582 )
583 } else {
584 chunk
585 }
586}
587
588fn fixed_prime_chunk_ranges(t: usize, chunk: usize) -> Vec<(usize, usize)> {
589 fixed_prime_chunk_ranges_for_ring(t, chunk, crate::cache::swa_ring_on())
590}
591
592fn fixed_prime_chunk_ranges_for_ring(t: usize, chunk: usize, ring_on: bool) -> Vec<(usize, usize)> {
593 if chunk == 0 || t <= chunk {
594 return vec![(0, t)];
595 }
596 let mut ranges = Vec::with_capacity(t.div_ceil(chunk));
597 let mut start = 0usize;
598 while start < t {
599 let mut end = (start + chunk).min(t);
600 if t - end > 0 && t - end < PRIME_MIN_T {
601 if ring_on {
602 let shifted = t - PRIME_MIN_T;
603 end = if shifted > start { shifted } else { t };
604 } else {
605 end = t;
606 }
607 }
608 ranges.push((start, end));
609 start = end;
610 }
611 ranges
612}
613
614fn prime_chunk_work(prefix: usize, total: usize) -> u128 {
615 let prefix = prefix as u128;
616 prefix * (prefix + (PRIME_PIPE_LINEAR_WORK as u128) * (total as u128))
617}
618
619fn dynamic_prime_chunk_ranges(
620 t: usize,
621 fixed_chunk: usize,
622 fixed: &[(usize, usize)],
623) -> Vec<(usize, usize)> {
624 let n = fixed.len();
625 if n < 3 {
626 return fixed.to_vec();
627 }
628
629 let max_first = t - (n - 1) * PRIME_MIN_T;
630 let first = fixed_chunk
631 .div_ceil(2)
632 .max(PRIME_PIPE_EDGE_MIN_CHUNK)
633 .min(max_first);
634 let mut ranges = Vec::with_capacity(n);
635 ranges.push((0, first));
636
637 let first_work = prime_chunk_work(first, t);
638 let work_span = prime_chunk_work(t, t) - first_work;
639 let denominator = (n - 1) as u128;
640 let mut previous = first;
641 for boundary in 1..n - 1 {
642 let target = first_work * denominator + work_span * (boundary as u128);
643 let remaining = n - 1 - boundary;
644 let mut low = previous + PRIME_MIN_T;
645 let mut high = t - remaining * PRIME_MIN_T;
646 while low < high {
647 let mid = low + (high - low) / 2;
648 if prime_chunk_work(mid, t) * denominator >= target {
649 high = mid;
650 } else {
651 low = mid + 1;
652 }
653 }
654 ranges.push((previous, low));
655 previous = low;
656 }
657 ranges.push((previous, t));
658 ranges
659}
660
661pub fn prime_chunk_ranges(t: usize, n_layers: usize) -> Vec<(usize, usize)> {
665 let explicit_chunk = std::env::var_os("MEMRA_PRIME_CHUNK").is_some();
666 let chunk = prime_chunk_tokens(t, n_layers);
667 let fixed = fixed_prime_chunk_ranges(t, chunk);
668 let dynamic = match std::env::var("MEMRA_PRIME_CHUNK_SCHED") {
669 Ok(value) => value == "dynamic",
670 Err(_) => true,
671 };
672 if explicit_chunk || !dynamic || !prime_pp2_auto_geometry(n_layers) {
673 fixed
674 } else {
675 dynamic_prime_chunk_ranges(t, chunk, &fixed)
676 }
677}
678
679struct HeadSplit {
680 pin: u64,
681 w1: CudaSlice<u8>,
682 hn1: CudaSlice<f32>,
683 y1: CudaSlice<f32>,
684 logits_e: CudaSlice<f32>,
685 ev_hn: cudarc::driver::CudaEvent,
686 ev_done: cudarc::driver::CudaEvent,
687 raw_hn1: u64,
688 raw_y1: u64,
689 raw_logits_hi: u64,
690}
691static HEAD_SPLIT_WS: std::sync::Mutex<Option<HeadSplit>> = std::sync::Mutex::new(None);
693
694#[allow(clippy::type_complexity)]
698static DEV1_ROUTER_REPS: std::sync::Mutex<
699 Option<(
700 std::collections::HashMap<u16, (CudaSlice<f32>, CudaSlice<f32>, CudaSlice<u8>)>,
701 Option<CudaSlice<f32>>,
702 )>,
703> = std::sync::Mutex::new(None);
704
705#[allow(clippy::type_complexity)]
709static SHEXP_D1_REPS: std::sync::Mutex<
710 Option<std::collections::HashMap<u16, (CudaSlice<u8>, CudaSlice<u8>, CudaSlice<u8>)>>,
711> = std::sync::Mutex::new(None);
712#[allow(clippy::type_complexity)]
713static SHEXP_D1_WS: std::sync::Mutex<
714 Option<(
715 (usize, usize),
716 CudaSlice<f32>,
717 CudaSlice<f32>,
718 CudaSlice<f32>,
719 cudarc::driver::CudaEvent,
720 cudarc::driver::CudaEvent,
721 )>,
722> = std::sync::Mutex::new(None);
723
724static SHEXP_OV_WS: std::sync::Mutex<
726 Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>,
727> = std::sync::Mutex::new(None);
728
729impl HybridModel {
730 fn step35_tp_qkv(
731 &self,
732 e: &Engine,
733 fa: &FullAttnLayer,
734 h: &CudaSlice<f32>,
735 t: usize,
736 ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
737 let Some(tp) = fa.step_tp_qkv.as_ref() else {
738 return Ok(None);
739 };
740 let values = active_matrix_values(
741 h.len(),
742 t,
743 self.cfg.n_embd as usize,
744 "Step TP QKV activation",
745 )?;
746 let host = e.dtoh_view(&h.slice(0..values))?;
747 let q = if tp.runtime.native_p2p() {
748 tp.runtime
749 .bf16_column_parallel_resident_native(&tp.q, &host, t)?
750 } else {
751 tp.runtime
752 .bf16_column_parallel_resident(&tp.q, &host, t)?
753 .gathered
754 };
755 let k = if tp.runtime.native_p2p() {
756 tp.runtime
757 .bf16_column_parallel_resident_native(&tp.k, &host, t)?
758 } else {
759 tp.runtime
760 .bf16_column_parallel_resident(&tp.k, &host, t)?
761 .gathered
762 };
763 let v = if tp.runtime.native_p2p() {
764 tp.runtime
765 .bf16_column_parallel_resident_native(&tp.v, &host, t)?
766 } else {
767 tp.runtime
768 .bf16_column_parallel_resident(&tp.v, &host, t)?
769 .gathered
770 };
771 eprintln!(
772 "[step-tp-qkv] execute layer={} devices={:?} tokens={t} projections=qkv \
773 qkv_tensor_parallel=true attention_local=true kv_local=true output_local=true \
774 transport={} native_p2p={} bulk_p2p={} activation=host-canonical \
775 output=root-readback \
776 performance_claim=false",
777 tp.layer,
778 tp.devices,
779 tp.runtime.transport_label(),
780 tp.runtime.native_p2p(),
781 tp.runtime.bulk_p2p(),
782 );
783 Ok(Some(vec![e.htod(&q)?, e.htod(&k)?, e.htod(&v)?]))
784 }
785
786 fn step35_tp_o(
787 &self,
788 e: &Engine,
789 fa: &FullAttnLayer,
790 activation: &CudaSlice<f32>,
791 tokens: usize,
792 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
793 let Some(tp) = fa.step_tp_qkv.as_ref() else {
794 return Ok(None);
795 };
796 let host = e.dtoh(activation)?;
797 let output = if tp.runtime.native_p2p() {
798 tp.runtime
799 .step_bf16_row_parallel_resident_native(&tp.o, &host, tokens)?
800 } else {
801 tp.runtime
802 .step_bf16_row_parallel_resident(&tp.o, &host, tokens)?
803 };
804 eprintln!(
805 "[step-tp-o] execute layer={} devices={:?} tokens={tokens} projection=o \
806 o_tensor_parallel=true attention_local=true kv_local=true transport={} \
807 native_p2p={} bulk_p2p={} activation=host-canonical \
808 reduction=global-tp8-block-order \
809 output=root-readback performance_claim=false",
810 tp.layer,
811 tp.devices,
812 tp.runtime.transport_label(),
813 tp.runtime.native_p2p(),
814 tp.runtime.bulk_p2p(),
815 );
816 Ok(Some(e.htod(&output)?))
817 }
818
819 fn step35_o(
820 &self,
821 e: &Engine,
822 fa: &FullAttnLayer,
823 activation: &CudaSlice<f32>,
824 tokens: usize,
825 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
826 match self.step35_tp_o(e, fa, activation, tokens)? {
827 Some(output) => Ok(output),
828 None => e.matmul(&fa.wo, activation, tokens),
829 }
830 }
831
832 fn prime_trace_path() -> Option<&'static str> {
837 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
838 P.get_or_init(|| std::env::var("MEMRA_PRIME_TRACE").ok())
839 .as_deref()
840 }
841
842 fn prime_anatomy_on() -> bool {
848 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
849 *E.get_or_init(|| std::env::var("MEMRA_PRIME_ANATOMY").as_deref() == Ok("1"))
850 }
851
852 fn prime_anatomy_slots() -> &'static [std::sync::atomic::AtomicU64; 5] {
853 static S: [std::sync::atomic::AtomicU64; 5] = [
854 std::sync::atomic::AtomicU64::new(0), std::sync::atomic::AtomicU64::new(0), std::sync::atomic::AtomicU64::new(0), std::sync::atomic::AtomicU64::new(0), std::sync::atomic::AtomicU64::new(0), ];
860 &S
861 }
862
863 pub fn forward(
865 &self,
866 e: &Engine,
867 tokens: &[u32],
868 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
869 if self.is_gemma4_e4b() {
870 return self.gemma4_e4b_forward(e, tokens, false);
871 }
872 if self.uses_gemma_program() {
873 return self.gemma4_forward(e, tokens, false);
874 }
875 let cfg = &self.cfg;
876 let n_embd = cfg.n_embd as usize;
877 let t = tokens.len();
878 let eps = cfg.rms_eps;
879 let pos: Vec<i32> = (0..t as i32).collect();
880 let pos_d = e.htod_i32(&pos)?;
881
882 let mut x = self.embed(e, tokens)?; for (il, layer) in self.layers.iter().enumerate() {
885 let mut h = e.uninit(t * n_embd)?;
887 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
888
889 let mixed = match &layer.mixer {
890 Mixer::Full(fa) => self.full_attn(e, fa, &h, &pos_d, t, il)?,
891 Mixer::Linear(la) => self.linear_attn(e, la, &h, t)?,
892 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
893 };
894
895 let mut x1 = e.uninit(t * n_embd)?;
897 e.add(&x, &mixed, &mut x1, t * n_embd)?;
898
899 let mut z = e.uninit(t * n_embd)?;
901 e.rms_norm(
902 &x1,
903 layer.post_attn_norm.float_data(),
904 &mut z,
905 n_embd,
906 t,
907 eps,
908 )?;
909 let ffn_out = match &layer.ffn {
910 crate::hybrid::Ffn::Dense {
911 ffn_gate,
912 ffn_up,
913 ffn_down,
914 } => {
915 let n_ff = ffn_gate.out_features();
916 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
917 let up = g2.pop().unwrap();
918 let gate = g2.pop().unwrap();
919 let mut act = e.uninit(t * n_ff)?;
920 Self::ffn_act_lim(
925 e,
926 &self.cfg,
927 &gate,
928 &up,
929 1.0,
930 1.0,
931 self.cfg.clamp_shexp_at(il as u32),
932 &mut act,
933 t * n_ff,
934 )?;
935 e.matmul(ffn_down, &act, t)?
936 }
937 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
938 };
939 let mut x2 = e.uninit(t * n_embd)?;
940 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
941 x = x2;
942 }
943
944 let mut hn = e.uninit(t * n_embd)?;
945 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
946 let logits = e.matmul(&self.output, &hn, t)?;
947 Ok(e.dtoh(&logits)?)
948 }
949
950 pub fn forward_last(
956 &self,
957 e: &Engine,
958 tokens: &[u32],
959 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
960 if self.uses_gemma_program() {
961 return self.gemma4_forward(e, tokens, true);
962 }
963 let cfg = &self.cfg;
964 let n_embd = cfg.n_embd as usize;
965 let t = tokens.len();
966 let eps = cfg.rms_eps;
967 let pos: Vec<i32> = (0..t as i32).collect();
968 let pos_d = e.htod_i32(&pos)?;
969
970 let mut x = self.embed(e, tokens)?; let probe = std::env::var("MEMRA_LAYER_PROBE").is_ok();
974 let anat = Self::prime_anatomy_on();
975 let mut anat_last = if anat {
976 e.stream().synchronize()?;
977 Some(std::time::Instant::now())
978 } else {
979 None
980 };
981 macro_rules! anat_mark {
982 ($slot:expr) => {
983 if let Some(ts) = anat_last.as_mut() {
984 e.stream().synchronize()?;
985 Self::prime_anatomy_slots()[$slot].fetch_add(
986 ts.elapsed().as_nanos() as u64,
987 std::sync::atomic::Ordering::Relaxed,
988 );
989 *ts = std::time::Instant::now();
990 }
991 };
992 }
993 for (il, layer) in self.layers.iter().enumerate() {
994 let mut h = e.uninit(t * n_embd)?;
995 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
996 if probe {
997 e.stream().synchronize()?;
998 eprintln!("[probe] L{il} norm ok");
999 }
1000 anat_mark!(4);
1001 let mixed = match &layer.mixer {
1002 Mixer::Full(fa) => {
1003 let y = self.full_attn(e, fa, &h, &pos_d, t, il)?;
1004 anat_mark!(0);
1005 y
1006 }
1007 Mixer::Linear(la) => {
1008 let y = self.linear_attn(e, la, &h, t)?;
1009 anat_mark!(1);
1010 y
1011 }
1012 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1013 };
1014 if probe {
1015 e.stream().synchronize()?;
1016 eprintln!("[probe] L{il} mixer ok");
1017 }
1018 let mut x1 = e.uninit(t * n_embd)?;
1019 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1020 let mut z = e.uninit(t * n_embd)?;
1021 e.rms_norm(
1022 &x1,
1023 layer.post_attn_norm.float_data(),
1024 &mut z,
1025 n_embd,
1026 t,
1027 eps,
1028 )?;
1029 anat_mark!(4);
1030 let ffn_out = match &layer.ffn {
1031 crate::hybrid::Ffn::Dense {
1032 ffn_gate,
1033 ffn_up,
1034 ffn_down,
1035 } => {
1036 let n_ff = ffn_gate.out_features();
1037 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1038 let up = g2.pop().unwrap();
1039 let gate = g2.pop().unwrap();
1040 let mut act = e.uninit(t * n_ff)?;
1041 Self::ffn_act_lim(
1043 e,
1044 &self.cfg,
1045 &gate,
1046 &up,
1047 1.0,
1048 1.0,
1049 self.cfg.clamp_shexp_at(il as u32),
1050 &mut act,
1051 t * n_ff,
1052 )?;
1053 let y = e.matmul(ffn_down, &act, t)?;
1054 anat_mark!(3);
1055 y
1056 }
1057 crate::hybrid::Ffn::Moe(m) => {
1058 let y = self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?;
1059 anat_mark!(2);
1060 y
1061 }
1062 };
1063 if probe {
1064 e.stream().synchronize()?;
1065 eprintln!("[probe] L{il} ffn ok");
1066 }
1067 let mut x2 = e.uninit(t * n_embd)?;
1068 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1069 x = x2;
1070 }
1071 if anat {
1072 let s = Self::prime_anatomy_slots();
1073 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
1074 eprintln!(
1075 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
1076 dense={:.1} norms_adds={:.1} (t={t}, forward_last)",
1077 ms(0),
1078 ms(1),
1079 ms(2),
1080 ms(3),
1081 ms(4)
1082 );
1083 }
1084 let mut hn = e.uninit(t * n_embd)?;
1086 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1087 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)?;
1090 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
1091 let logits = e.matmul(&self.output, &hlast, 1)?; Ok(e.dtoh(&logits)?)
1093 }
1094
1095 pub fn prime_cache(
1127 &self,
1128 e: &Engine,
1129 tokens: &[u32],
1130 cache: &mut Cache,
1131 queued_after: usize,
1132 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1133 self.prime_cache_overlaid(e, tokens, cache, queued_after, None)
1134 }
1135
1136 pub fn prime_cache_overlaid(
1142 &self,
1143 e: &Engine,
1144 tokens: &[u32],
1145 cache: &mut Cache,
1146 queued_after: usize,
1147 overlay: Option<&crate::vision::EmbedOverlay>,
1148 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1149 let n_embd = self.cfg.n_embd as usize;
1150 let t = tokens.len();
1151 assert!(
1155 t >= PRIME_MIN_T,
1156 "prime_cache needs T >= {PRIME_MIN_T} (caller gates)"
1157 );
1158 assert!(
1159 cache.pos + t <= cache.max_ctx,
1160 "prime_cache: prompt exceeds cache max_ctx"
1161 );
1162
1163 if self.is_gemma4_e4b() || self.uses_gemma_program() {
1175 if self.is_gemma4_e4b() {
1176 if overlay.is_some() {
1177 return Err(
1178 "vision embedding overlay is unsupported on gemma4 E4B (PLE prime)".into(),
1179 );
1180 }
1181 return self.gemma4_e4b_prime(e, tokens, cache);
1182 }
1183 return self.gemma4_prime(e, tokens, cache, overlay);
1188 }
1189 let ranges = prime_chunk_ranges(t, self.layers.len());
1190 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
1226 let seq_end = if legacy_calllocal {
1227 cache.pos + t
1228 } else {
1229 cache.pos + t + queued_after
1230 };
1231 if ranges.len() == 1 {
1232 return self.prime_chunk(e, tokens, cache, seq_end, 0, overlay);
1233 }
1234 if crate::pp::prime_pipe_on() && crate::pp::prime_pp_on() && !crate::pp::pp2_streams_off() {
1239 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()).filter(|f| f.len() == 3) {
1240 if overlay.is_some() {
1241 return Err(
1242 "vision embedding overlay + pipelined PP prime unsupported (v1); \
1243 run the serial prime (single device or MEMRA_PRIME_PIPE=0)"
1244 .into(),
1245 );
1246 }
1247 if crate::pp::pp_multi_stream_same_device() {
1248 return Err(
1249 "prime chunk pipeline refused with 2 stage streams on one device — \
1250 that concurrent-stream placement remains quarantined by the deferred \
1251 pp flake record. Use one device per stage or MEMRA_PRIME_PIPE=0 for \
1252 the serial split."
1253 .into(),
1254 );
1255 }
1256 return self.prime_cache_pp2_pipelined(e, tokens, cache, seq_end, &ranges, &fence);
1257 }
1258 }
1259 let mut hiddens = e.uninit(t * n_embd)?;
1260 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1261 for &(start, end) in &ranges {
1262 if let Some(taps) = cache.dflash_taps.as_mut() {
1264 taps.base = start;
1265 }
1266 let (l, hs, x) =
1267 self.prime_chunk(e, &tokens[start..end], cache, seq_end, start, overlay)?;
1268 e.copy_into(&mut hiddens, start * n_embd, &x, (end - start) * n_embd)?;
1269 last = Some((l, hs));
1270 }
1271 let (logits, h_seed) = last.unwrap();
1272 Ok((logits, h_seed, hiddens))
1273 }
1274
1275 fn prime_cache_pp2_pipelined(
1280 &self,
1281 e: &Engine,
1282 tokens: &[u32],
1283 cache: &mut Cache,
1284 seq_end: usize,
1285 ranges: &[(usize, usize)],
1286 fence: &[usize],
1287 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1288 debug_assert_eq!(fence.len(), 3);
1289 debug_assert!(ranges.len() >= 2);
1290 let rt = crate::pp::PpNRt::get(e)?;
1291 assert_eq!(
1292 rt.n_stages(),
1293 2,
1294 "prime pipeline requires exactly two PP stages"
1295 );
1296 let n_embd = self.cfg.n_embd as usize;
1297 let t = tokens.len();
1298 let initial_base = cache.pos;
1299 let caller_stream = e.stream();
1300
1301 rt.fence_stages_behind(&caller_stream)?;
1306 let max_payload = ranges.iter().map(|(s, e)| (e - s) * n_embd).max().unwrap();
1307 rt.prepare_overlap_slots(0, max_payload)?;
1308
1309 let mut hiddens = e.uninit(t * n_embd)?;
1310 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1311 let mut stage_caches = PrimeCacheStages::new(cache, fence[1]);
1312 let (cache0, cache1) = stage_caches.parts();
1313 let (first_start, first_end) = ranges[0];
1314 let mut slot = self.prime_pp2_stage0_enqueue(
1315 e,
1316 rt,
1317 &tokens[first_start..first_end],
1318 cache0,
1319 seq_end,
1320 fence,
1321 initial_base + first_start,
1322 true,
1323 )?;
1324 cache0.pos = initial_base + first_end;
1325
1326 for (i, &(start, end)) in ranges.iter().enumerate() {
1327 let base = initial_base + start;
1328 debug_assert_eq!(
1329 cache1.pos, base,
1330 "stage 1 must drain chunks in original position order"
1331 );
1332 let (out, next_slot) = if let Some(&(next_start, next_end)) = ranges.get(i + 1) {
1333 let next_base = initial_base + next_start;
1334 debug_assert_eq!(
1335 cache0.pos, next_base,
1336 "stage 0 must issue chunks in original position order"
1337 );
1338 let cache0_stage = &mut *cache0;
1339 std::thread::scope(|scope| -> Result<_, Box<dyn std::error::Error>> {
1344 let stage0 = scope.spawn(move || -> Result<usize, String> {
1345 let next = self
1346 .prime_pp2_stage0_enqueue(
1347 e,
1348 rt,
1349 &tokens[next_start..next_end],
1350 cache0_stage,
1351 seq_end,
1352 fence,
1353 next_base,
1354 true,
1355 )
1356 .map_err(|err| err.to_string())?;
1357 cache0_stage.pos = initial_base + next_end;
1358 Ok(next)
1359 });
1360 let x = self.prime_pp2_stage1_enqueue(
1361 e,
1362 rt,
1363 slot,
1364 end - start,
1365 cache1,
1366 seq_end,
1367 fence,
1368 base,
1369 true,
1370 )?;
1371 let out = {
1372 rt.bind_stage(1)?;
1373 let _st1 = rt.enter(1);
1374 let e1 = rt.engine(1, e);
1375 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1376 };
1377 let next = stage0
1378 .join()
1379 .map_err(|_| "pipeprime stage-0 host walker panicked")?
1380 .map_err(|err| -> Box<dyn std::error::Error> { err.into() })?;
1381 Ok((out, Some(next)))
1382 })?
1383 } else {
1384 let x = self.prime_pp2_stage1_enqueue(
1385 e,
1386 rt,
1387 slot,
1388 end - start,
1389 cache1,
1390 seq_end,
1391 fence,
1392 base,
1393 true,
1394 )?;
1395 let out = {
1396 rt.bind_stage(1)?;
1397 let _st1 = rt.enter(1);
1398 let e1 = rt.engine(1, e);
1399 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1400 };
1401 (out, None)
1402 };
1403
1404 rt.publish_to(1, &caller_stream)?;
1405 e.copy_into(&mut hiddens, start * n_embd, &out.2, (end - start) * n_embd)?;
1406 last = Some((out.0, out.1));
1407 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1408
1409 if let Some(next) = next_slot {
1410 rt.fence_stages_behind(&caller_stream)?;
1415 slot = next;
1416 }
1417 }
1418
1419 debug_assert_eq!(cache0.pos, initial_base + t);
1420 debug_assert_eq!(cache1.pos, initial_base + t);
1421 let (logits, h_seed) = last.unwrap();
1422 Ok((logits, h_seed, hiddens))
1423 }
1424
1425 fn gdn_hk(e: &Engine, t: usize, num_v: usize, num_k: usize) -> usize {
1432 if Engine::gdn_db_on()
1433 && Engine::gdn_chunked_enabled()
1434 && t >= 16
1435 && e.gdn_mma_enabled(Engine::gdn_chunk_size())
1436 && num_k * 2 == num_v
1437 {
1438 num_k
1439 } else {
1440 num_v
1441 }
1442 }
1443
1444 fn f16out_on(e: &Engine, t: usize) -> bool {
1449 crate::f16_ffi::pp_f16_enabled()
1450 && t >= 16
1451 && !e.verify_exact_on()
1452 && std::env::var("MEMRA_F16OUT").as_deref() != Ok("0")
1453 }
1454
1455 pub fn prime_slabs_get(
1463 &self,
1464 e: &Engine,
1465 t: usize,
1466 n_embd: usize,
1467 n_ff_max: usize,
1468 ) -> Result<std::sync::Arc<std::sync::Mutex<PrimeSlabs>>, Box<dyn std::error::Error>> {
1469 let mut slabs = self.prime_slabs.lock().unwrap();
1470 let dev = e.ctx().ordinal();
1471 let need_new = match slabs.get(&dev) {
1472 None => true,
1473 Some(sl) => sl.lock().unwrap().t_cap < t,
1474 };
1475 if need_new {
1476 slabs.insert(
1477 dev,
1478 std::sync::Arc::new(std::sync::Mutex::new(PrimeSlabs {
1479 t_cap: t,
1480 h: e.uninit(t * n_embd)?,
1481 x1: e.uninit(t * n_embd)?,
1482 z: e.uninit(t * n_embd)?,
1483 act: e.uninit(t * n_ff_max)?,
1484 xa: e.uninit(t * n_embd)?,
1485 xb: e.uninit(t * n_embd)?,
1486 h16: e.alloc_u8_uninit(t * n_embd * 2)?,
1487 z16: e.alloc_u8_uninit(t * n_embd * 2)?,
1488 gate: e.uninit(t * n_ff_max)?,
1489 up: e.uninit(t * n_ff_max)?,
1490 ffn_out: e.uninit(t * n_embd)?,
1491 seg_glue: Vec::new(),
1492 mixed: e.uninit(t * n_embd)?,
1493 seg_mid: Vec::new(),
1494 seg_t: 0,
1495 })),
1496 );
1497 }
1498 Ok(slabs.get(&dev).expect("prime slab inserted").clone())
1499 }
1500
1501 fn prime_chunk(
1505 &self,
1506 e: &Engine,
1507 tokens: &[u32],
1508 cache: &mut Cache,
1509 seq_end: usize,
1510 chunk_off: usize,
1511 overlay: Option<&crate::vision::EmbedOverlay>,
1512 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1513 if crate::pp::pp_host_bounce_active()
1514 && (self.uses_gemma_program() || !crate::pp::prime_pp_on())
1515 {
1516 return Err(
1517 "prime_chunk: refused with MEMRA_PP_HOST_BOUNCE=1 because this configuration \
1518 has no active prime stage split and would peer-read remote weights; keep \
1519 MEMRA_PRIME_PP enabled and use a PP-prime-supported model"
1520 .into(),
1521 );
1522 }
1523 if !self.uses_gemma_program() && !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
1532 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
1533 if overlay.is_some() {
1534 return Err("vision embedding overlay + PP prime unsupported (v1); \
1535 run single-device or MEMRA_PRIME_PP=0"
1536 .into());
1537 }
1538 return self.prime_chunk_ppn(e, tokens, cache, seq_end, &fence);
1539 }
1540 }
1541 if crate::pp::pp_host_bounce_active() {
1542 return Err(
1543 "prime_chunk: MEMRA_PP_HOST_BOUNCE=1 found no valid prime stage split; \
1544 refusing an unsplit remote-weight walk"
1545 .into(),
1546 );
1547 }
1548 let t = tokens.len();
1549 let base = cache.pos;
1550 debug_assert!(
1551 seq_end >= base + t,
1552 "prime_chunk: seq_end must cover this chunk"
1553 );
1554 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1555 let pos_d = e.htod_i32(&pos)?;
1556
1557 let mut x_embed = self.embed(e, tokens)?; if let Some(ov) = overlay {
1559 let n_embd = self.cfg.n_embd as usize;
1563 for &(pos, row_off, n_rows) in &ov.spans {
1564 let lo = pos.max(chunk_off);
1565 let hi = (pos + n_rows).min(chunk_off + t);
1566 if lo < hi {
1567 let src_row = row_off + (lo - pos);
1568 let view = ov
1569 .rows
1570 .slice(src_row * n_embd..(src_row + (hi - lo)) * n_embd);
1571 e.copy_view_into(
1572 &mut x_embed,
1573 (lo - chunk_off) * n_embd,
1574 &view,
1575 (hi - lo) * n_embd,
1576 )?;
1577 }
1578 }
1579 }
1580 let x = self.prime_layers(
1581 e,
1582 x_embed,
1583 0,
1584 self.layers.len(),
1585 &pos_d,
1586 t,
1587 base,
1588 cache,
1589 seq_end,
1590 )?;
1591 self.prime_chunk_epilogue(e, x, t, cache)
1592 }
1593
1594 #[allow(clippy::too_many_arguments)]
1610 fn prime_layers(
1611 &self,
1612 e: &Engine,
1613 x_in: CudaSlice<f32>,
1614 lo: usize,
1615 hi: usize,
1616 pos_d: &CudaSlice<i32>,
1617 t: usize,
1618 base: usize,
1619 cache: &mut Cache,
1620 seq_end: usize,
1621 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1622 let cfg = &self.cfg;
1623 let n_embd = cfg.n_embd as usize;
1624 let eps = cfg.rms_eps;
1625 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
1629 let n_ff_max = self
1636 .layers
1637 .iter()
1638 .map(|l| match &l.ffn {
1639 crate::hybrid::Ffn::Dense { ffn_gate, .. } => ffn_gate.out_features(),
1640 _ => n_embd,
1641 })
1642 .max()
1643 .unwrap_or(n_embd)
1644 .max(n_embd);
1645 let use_slabs = std::env::var("MEMRA_PRIME_SLABS").as_deref() != Ok("0");
1646 let slab = if use_slabs {
1647 Some(self.prime_slabs_get(e, t, n_embd, n_ff_max)?)
1648 } else {
1649 None
1650 };
1651 let mut slab_guard = slab.as_ref().map(|sl| sl.lock().unwrap());
1652 let mut x_own; type SlabRefs<'a> = (
1654 &'a mut CudaSlice<f32>,
1655 &'a mut CudaSlice<f32>,
1656 &'a mut CudaSlice<f32>,
1657 &'a mut CudaSlice<f32>,
1658 &'a mut CudaSlice<u8>,
1659 &'a mut CudaSlice<u8>,
1660 &'a mut CudaSlice<f32>,
1661 &'a mut CudaSlice<f32>,
1662 &'a mut CudaSlice<f32>,
1663 );
1664 let (mut x_cur, mut x_nxt, sl): (
1665 &mut CudaSlice<f32>,
1666 &mut CudaSlice<f32>,
1667 Option<SlabRefs>,
1668 );
1669 let mut seg: Option<(
1670 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1671 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1672 &mut CudaSlice<f32>,
1673 &mut usize,
1674 )> = None;
1675 let mut x_own2;
1676 match slab_guard.as_mut() {
1677 Some(g) => {
1678 let slabs = &mut **g;
1679 e.copy_into(&mut slabs.xa, 0, &x_in, t * n_embd)?;
1680 let PrimeSlabs {
1681 xa,
1682 xb,
1683 h,
1684 x1,
1685 z,
1686 act,
1687 h16,
1688 z16,
1689 gate,
1690 up,
1691 ffn_out,
1692 seg_glue,
1693 mixed,
1694 seg_mid,
1695 seg_t,
1696 ..
1697 } = slabs;
1698 x_cur = xa;
1699 x_nxt = xb;
1700 seg = Some((seg_glue, seg_mid, mixed, seg_t));
1701 sl = Some((h, x1, z, act, h16, z16, gate, up, ffn_out));
1702 }
1703 None => {
1704 x_own = x_in;
1705 x_own2 = e.uninit(t * n_embd)?;
1706 x_cur = &mut x_own;
1707 x_nxt = &mut x_own2;
1708 sl = None;
1709 }
1710 }
1711 let mut alloc_h;
1712 let mut alloc_x1;
1713 let mut alloc_z;
1714 let mut alloc_act;
1715 let mut alloc_h16;
1716 let mut alloc_z16;
1717 let mut alloc_gate;
1718 let mut alloc_up;
1719 let mut alloc_fo;
1720 let (h, x1, z, act): (
1721 &mut CudaSlice<f32>,
1722 &mut CudaSlice<f32>,
1723 &mut CudaSlice<f32>,
1724 &mut CudaSlice<f32>,
1725 );
1726 let (h16, z16): (&mut CudaSlice<u8>, &mut CudaSlice<u8>);
1727 let (sl_gate, sl_up, sl_fo): (
1728 &mut CudaSlice<f32>,
1729 &mut CudaSlice<f32>,
1730 &mut CudaSlice<f32>,
1731 );
1732 match sl {
1733 Some((a, b, c, d, e16, f16b, g, u, fo)) => {
1734 h = a;
1735 x1 = b;
1736 z = c;
1737 act = d;
1738 h16 = e16;
1739 z16 = f16b;
1740 sl_gate = g;
1741 sl_up = u;
1742 sl_fo = fo;
1743 }
1744 None => {
1745 alloc_h = e.uninit(t * n_embd)?;
1746 alloc_x1 = e.uninit(t * n_embd)?;
1747 alloc_z = e.uninit(t * n_embd)?;
1748 alloc_act = e.uninit(t * n_ff_max)?;
1749 alloc_h16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1750 alloc_z16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1751 alloc_gate = e.uninit(t * n_ff_max)?;
1752 alloc_up = e.uninit(t * n_ff_max)?;
1753 alloc_fo = e.uninit(t * n_embd)?;
1754 h = &mut alloc_h;
1755 x1 = &mut alloc_x1;
1756 z = &mut alloc_z;
1757 act = &mut alloc_act;
1758 h16 = &mut alloc_h16;
1759 z16 = &mut alloc_z16;
1760 sl_gate = &mut alloc_gate;
1761 sl_up = &mut alloc_up;
1762 sl_fo = &mut alloc_fo;
1763 }
1764 }
1765 let n_layers = self.layers.len();
1770 let use_seg = f16fuse
1780 && seg.is_some()
1781 && !self.uses_sliding_gated_moe_program()
1782 && lo == 0
1783 && hi == n_layers
1784 && std::env::var("MEMRA_PRIME_SEG").as_deref() == Ok("1");
1785 if let Some((sg, sm, _, st)) = seg.as_mut() {
1786 if **st != t {
1787 sg.clear();
1788 sg.extend((0..n_layers).map(|_| None));
1789 sm.clear();
1790 sm.extend((0..n_layers).map(|_| None));
1791 **st = t;
1792 }
1793 }
1794 {
1795 let layer_lo = &self.layers[lo];
1796 if f16fuse {
1797 e.rms_norm_f16out(
1798 x_cur,
1799 layer_lo.attn_norm.float_data(),
1800 h,
1801 h16,
1802 n_embd,
1803 t,
1804 eps,
1805 )?;
1806 } else {
1807 e.rms_norm(x_cur, layer_lo.attn_norm.float_data(), h, n_embd, t, eps)?;
1808 }
1809 }
1810 let anat = Self::prime_anatomy_on();
1811 let mut anat_last = if anat {
1812 e.stream().synchronize()?;
1813 Some(std::time::Instant::now())
1814 } else {
1815 None
1816 };
1817 macro_rules! anat_mark {
1819 ($slot:expr) => {
1820 if let Some(ts) = anat_last.as_mut() {
1821 e.stream().synchronize()?;
1822 Self::prime_anatomy_slots()[$slot].fetch_add(
1823 ts.elapsed().as_nanos() as u64,
1824 std::sync::atomic::Ordering::Relaxed,
1825 );
1826 *ts = std::time::Instant::now();
1827 }
1828 };
1829 }
1830 for il in lo..hi {
1831 let layer = &self.layers[il];
1832 let hx16 = if f16fuse { Some(&*h16) } else { None };
1833 if use_seg {
1834 let (pre, pre16, w_out) = match &layer.mixer {
1837 Mixer::Full(fa) => {
1838 let g3 = match hx16 {
1839 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
1840 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
1841 };
1842 let (pre, pre16) =
1843 self.full_attn_prime_core_inner(e, fa, g3, &pos_d, t, cache, il)?;
1844 (pre, pre16, &fa.wo)
1845 }
1846 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1847 Mixer::Linear(la) => {
1848 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
1849 let g4 = match hx16 {
1850 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
1851 None => e.matmul_group(&ws, h, t)?,
1852 };
1853 let (pre, pre16) =
1854 self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, None)?;
1855 (pre, pre16, &la.ssm_out)
1856 }
1857 };
1858 {
1859 let (_, sm, mslab, _) = seg.as_mut().unwrap();
1860 let pre_n = pre.len() / t;
1861 let xh_pre = match pre16 {
1862 Some(x) => x,
1863 None => e.f16_act(&pre, t * pre_n, pre_n)?,
1864 };
1865 if !e.try_f16_gemm_pre_into(w_out, &xh_pre, t, mslab)? {
1866 let y = e.matmul(w_out, &pre, t)?;
1867 e.copy_into(mslab, 0, &y, t * n_embd)?;
1868 }
1869 if sm[il].is_none() {
1870 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
1871 let w_post = layer.post_attn_norm.float_data();
1872 e.stream().synchronize()?;
1873 e.stream()
1874 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
1875 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
1876 e.add(x_cur, mslab, x1, t * n_embd)?;
1877 e.rms_norm_f16out(x1, w_post, z, z16, n_embd, t, eps)?;
1878 Ok(())
1879 })();
1880 let g = e.stream().end_capture(
1881 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH);
1882 r?;
1883 sm[il] = Some(g?.ok_or("S-mid capture produced no graph")?);
1884 }
1885 sm[il].as_ref().unwrap().launch()?;
1886 }
1887 } else {
1888 let mixed = match &layer.mixer {
1889 Mixer::Full(fa) => {
1890 let y =
1891 self.full_attn_prime(e, fa, h, hx16, &pos_d, t, cache, il, seq_end)?;
1892 anat_mark!(0);
1893 y
1894 }
1895 Mixer::Linear(la) => {
1896 let y = self.linear_attn_prime(e, la, h, hx16, t, cache, il)?;
1897 anat_mark!(1);
1898 y
1899 }
1900 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1901 };
1902 if f16fuse {
1903 e.add_rms_norm_f16out(
1906 x_cur,
1907 &mixed,
1908 layer.post_attn_norm.float_data(),
1909 x1,
1910 z,
1911 z16,
1912 n_embd,
1913 t,
1914 eps,
1915 )?;
1916 } else {
1917 e.add(x_cur, &mixed, x1, t * n_embd)?;
1918 e.rms_norm(x1, layer.post_attn_norm.float_data(), z, n_embd, t, eps)?;
1919 }
1920 anat_mark!(4);
1921 }
1922 let zx16 = if f16fuse { Some(&*z16) } else { None };
1923 match &layer.ffn {
1924 crate::hybrid::Ffn::Dense {
1925 ffn_gate,
1926 ffn_up,
1927 ffn_down,
1928 } => {
1929 let n_ff = ffn_gate.out_features();
1930 let mut into_ok = false;
1933 if let Some(xh) = zx16 {
1934 into_ok = e.try_f16_gemm_pre_into(ffn_gate, xh, t, sl_gate)?
1935 && e.try_f16_gemm_pre_into(ffn_up, xh, t, sl_up)?;
1936 }
1937 if !into_ok {
1938 let mut g2 = match zx16 {
1939 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], z, xh, t)?,
1940 None => e.matmul_group(&[ffn_gate, ffn_up], z, t)?,
1941 };
1942 let up_y = g2.pop().unwrap();
1943 let gate_y = g2.pop().unwrap();
1944 e.copy_into(sl_gate, 0, &gate_y, t * n_ff)?;
1945 e.copy_into(sl_up, 0, &up_y, t * n_ff)?;
1946 }
1947 let d_lim = self.cfg.clamp_shexp_at(il as u32);
1952 let act16 = if Self::f16out_on(e, t) && self.cfg.m3.is_none() && d_lim.is_none()
1953 {
1954 let mut a16 = e.alloc_u8_uninit(t * n_ff * 2)?;
1955 e.silu_mul_f16out(sl_gate, sl_up, act, &mut a16, t * n_ff)?;
1956 Some(a16)
1957 } else {
1958 Self::ffn_act_lim(
1959 e,
1960 &self.cfg,
1961 sl_gate,
1962 sl_up,
1963 1.0,
1964 1.0,
1965 d_lim,
1966 act,
1967 t * n_ff,
1968 )?;
1969 None
1970 };
1971 let xh_act = match act16 {
1973 Some(x) => x,
1974 None => e.f16_act(act, t * n_ff, n_ff)?,
1975 };
1976 if !e.try_f16_gemm_pre_into(ffn_down, &xh_act, t, sl_fo)? {
1977 let y = e.matmul(ffn_down, &*act, t)?;
1978 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
1979 }
1980 }
1981 crate::hybrid::Ffn::Moe(m) => {
1982 let y = self.moe_ffn_il_prefill(e, m, z, t, il as u16)?;
1983 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
1984 anat_mark!(2);
1985 }
1986 }
1987 if let (crate::hybrid::Ffn::Dense { .. }, true) = (&layer.ffn, anat) {
1988 anat_mark!(3);
1989 }
1990 if use_seg && il + 1 < hi {
1991 let w_next = self.layers[il + 1].attn_norm.float_data();
1993 let (sg, _, _, _) = seg.as_mut().unwrap();
1994 if sg[il].is_none() {
1995 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
1996 e.stream().synchronize()?;
1997 e.stream()
1998 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
1999 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2000 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2001 e.rms_norm_f16out(x_nxt, w_next, h, h16, n_embd, t, eps)?;
2002 Ok(())
2003 })();
2004 let g = e.stream().end_capture(
2005 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
2006 );
2007 r?;
2008 sg[il] = Some(g?.ok_or("S-glue capture produced no graph")?);
2009 }
2010 sg[il].as_ref().unwrap().launch()?;
2011 } else {
2012 if il + 1 < hi {
2013 let w_next = self.layers[il + 1].attn_norm.float_data();
2014 if f16fuse {
2015 e.add_rms_norm_f16out(x1, sl_fo, w_next, x_nxt, h, h16, n_embd, t, eps)?;
2016 } else {
2017 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2018 e.rms_norm(x_nxt, w_next, h, n_embd, t, eps)?;
2019 }
2020 } else {
2021 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2022 }
2023 }
2024 anat_mark!(4);
2025 if let Some(path) = Self::prime_trace_path() {
2031 let row = (base + t - 1) as usize;
2032 let host = e.dtoh(x_nxt)?;
2033 let last = &host[(t - 1) * n_embd..t * n_embd];
2034 use std::io::Write as _;
2035 let mut f = std::fs::OpenOptions::new()
2036 .create(true)
2037 .append(true)
2038 .open(path)?;
2039 let mut h64: u64 = 0xcbf29ce484222325;
2040 for v in last {
2041 h64 ^= v.to_bits() as u64;
2042 h64 = h64.wrapping_mul(0x100000001b3);
2043 }
2044 writeln!(
2045 f,
2046 "{{\"pos\":{row},\"layer\":{il},\"t\":{t},\"base\":{base},\
2047 \"hash\":\"{h64:016x}\",\"v0\":{:.9e},\"v1\":{:.9e},\"v2\":{:.9e}}}",
2048 last[0], last[1], last[2]
2049 )?;
2050 }
2051 self.dflash_tap(e, cache, il, x_nxt, t)?;
2054 std::mem::swap(&mut x_cur, &mut x_nxt);
2055 }
2056 if anat {
2057 let s = Self::prime_anatomy_slots();
2058 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
2059 eprintln!(
2060 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
2061 dense={:.1} norms_adds={:.1} (t={t}, layers {lo}..{hi})",
2062 ms(0),
2063 ms(1),
2064 ms(2),
2065 ms(3),
2066 ms(4)
2067 );
2068 }
2069 let mut x = e.uninit(t * n_embd)?;
2071 e.copy_into(&mut x, 0, x_cur, t * n_embd)?;
2072 drop(slab_guard);
2073 Ok(x)
2074 }
2075
2076 fn prime_chunk_epilogue(
2081 &self,
2082 e: &Engine,
2083 x: CudaSlice<f32>,
2084 t: usize,
2085 cache: &mut Cache,
2086 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2087 let n_embd = self.cfg.n_embd as usize;
2088 let eps = self.cfg.rms_eps;
2089 let mut h_seed = e.uninit(n_embd)?;
2093 if !crate::spec::spec_hpost() {
2094 e.copy_view_into(
2095 &mut h_seed,
2096 0,
2097 &x.slice((t - 1) * n_embd..t * n_embd),
2098 n_embd,
2099 )?;
2100 }
2101 let mut hn = e.uninit(t * n_embd)?;
2103 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2104 if crate::spec::spec_hpost() {
2105 e.copy_view_into(
2106 &mut h_seed,
2107 0,
2108 &hn.slice((t - 1) * n_embd..t * n_embd),
2109 n_embd,
2110 )?;
2111 }
2112 let last = e.view(&hn, t * n_embd);
2113 let last_row = last.slice((t - 1) * n_embd..t * n_embd);
2114 let mut hlast = e.uninit(n_embd)?;
2115 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
2116 let logits = e.matmul(&self.output, &hlast, 1)?;
2117 cache.pos += t;
2118 Ok((
2121 e.dtoh(&logits)?,
2122 h_seed,
2123 if crate::spec::spec_hpost() { hn } else { x },
2124 ))
2125 }
2126
2127 fn prime_chunk_ppn(
2151 &self,
2152 e: &Engine,
2153 tokens: &[u32],
2154 cache: &mut Cache,
2155 seq_end: usize,
2156 fence: &[usize],
2157 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2158 let rt = crate::pp::PpNRt::get(e)?;
2159 let n_st = fence.len() - 1;
2160 assert_eq!(
2161 rt.n_stages(),
2162 n_st,
2163 "PpNRt stage count {} != fence stages {n_st}",
2164 rt.n_stages()
2165 );
2166 let n_embd = self.cfg.n_embd as usize;
2167 let t = tokens.len();
2168 let base = cache.pos;
2169 debug_assert!(
2170 seq_end >= base + t,
2171 "prime_chunk_ppn: seq_end must cover this chunk"
2172 );
2173 let payload = t * n_embd;
2174 let caller_stream = e.stream();
2178 rt.fence_stages_behind(&caller_stream)?;
2179
2180 if n_st == 2 {
2181 let slot =
2182 self.prime_pp2_stage0_enqueue(e, rt, tokens, cache, seq_end, fence, base, false)?;
2183 let x =
2184 self.prime_pp2_stage1_enqueue(e, rt, slot, t, cache, seq_end, fence, base, false)?;
2185 let out = {
2186 rt.bind_stage(1)?;
2187 let _st1 = rt.enter(1);
2188 let e1 = rt.engine(1, e);
2189 self.prime_chunk_epilogue(e1, x, t, cache)?
2190 };
2191 rt.publish_to(1, &caller_stream)?;
2192 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2193 return Ok(out);
2194 }
2195
2196 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2197
2198 let mut slot = {
2200 let _st0 = rt.enter(0);
2201 let e0 = rt.engine(0, e);
2202 let pos_d = e0.htod_i32(&pos)?;
2203 let x = self.embed(e0, tokens)?;
2204 let x =
2205 self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2206 rt.tx(0, &x, payload)?
2207 };
2209
2210 for s in 1..n_st - 1 {
2212 let _st = rt.enter(s);
2213 let es = rt.engine(s, e);
2214 let pos_d = es.htod_i32(&pos)?;
2215 let x = rt.rx(s - 1, slot, payload)?;
2216 let x = self.prime_layers(
2217 es,
2218 x,
2219 fence[s],
2220 fence[s + 1],
2221 &pos_d,
2222 t,
2223 base,
2224 cache,
2225 seq_end,
2226 )?;
2227 slot = rt.tx(s, &x, payload)?;
2228 }
2229
2230 let _stl = rt.enter(n_st - 1);
2232 let el = rt.engine(n_st - 1, e);
2233 let pos_d = el.htod_i32(&pos)?;
2234 let x = rt.rx(n_st - 2, slot, payload)?;
2235 let x = self.prime_layers(
2236 el,
2237 x,
2238 fence[n_st - 1],
2239 fence[n_st],
2240 &pos_d,
2241 t,
2242 base,
2243 cache,
2244 seq_end,
2245 )?;
2246 let out = self.prime_chunk_epilogue(el, x, t, cache)?;
2247 rt.publish_to(n_st - 1, &caller_stream)?;
2253 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2254 Ok(out)
2255 }
2256
2257 fn prime_pp2_stage0_enqueue(
2258 &self,
2259 e: &Engine,
2260 rt: &crate::pp::PpNRt,
2261 tokens: &[u32],
2262 cache: &mut Cache,
2263 seq_end: usize,
2264 fence: &[usize],
2265 base: usize,
2266 pipelined: bool,
2267 ) -> Result<usize, Box<dyn std::error::Error>> {
2268 let t = tokens.len();
2269 let n_embd = self.cfg.n_embd as usize;
2270 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2271 rt.bind_stage(0)?;
2272 let _st0 = rt.enter(0);
2273 let e0 = rt.engine(0, e);
2274 let pos_d = e0.htod_i32(&pos)?;
2275 let x = self.embed(e0, tokens)?;
2276 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2277 let x = self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2278 if pipelined {
2279 rt.tx_pipelined(0, &x, t * n_embd)
2280 } else {
2281 rt.tx(0, &x, t * n_embd)
2282 }
2283 }
2284
2285 fn prime_pp2_stage1_enqueue(
2286 &self,
2287 e: &Engine,
2288 rt: &crate::pp::PpNRt,
2289 slot: usize,
2290 t: usize,
2291 cache: &mut Cache,
2292 seq_end: usize,
2293 fence: &[usize],
2294 base: usize,
2295 pipelined: bool,
2296 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2297 let n_embd = self.cfg.n_embd as usize;
2298 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2299 rt.bind_stage(1)?;
2300 let _st1 = rt.enter(1);
2301 let e1 = rt.engine(1, e);
2302 let pos_d = e1.htod_i32(&pos)?;
2303 let x = rt.rx(0, slot, t * n_embd)?;
2304 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2305 self.prime_layers(e1, x, fence[1], fence[2], &pos_d, t, base, cache, seq_end)
2306 }
2307
2308 pub fn prime_chunk_captured(
2324 &self,
2325 e: &Engine,
2326 x_in: &CudaSlice<f32>,
2327 pos_d: &CudaSlice<i32>,
2328 t: usize,
2329 cache: &mut Cache,
2330 len_d: &CudaSlice<i32>,
2331 logits_out: &mut CudaSlice<f32>,
2332 h_seed_out: &mut CudaSlice<f32>,
2333 ) -> Result<(), Box<dyn std::error::Error>> {
2334 let cfg = &self.cfg;
2335 let n_embd = cfg.n_embd as usize;
2336 let eps = cfg.rms_eps;
2337 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
2338 let mut x = e.uninit(t * n_embd)?;
2339 e.copy_into(&mut x, 0, x_in, t * n_embd)?;
2340 for (il, layer) in self.layers.iter().enumerate() {
2341 let mut h = e.uninit(t * n_embd)?;
2342 let mut hx16: Option<CudaSlice<u8>> = None;
2343 if f16fuse {
2344 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2345 e.rms_norm_f16out(
2346 &x,
2347 layer.attn_norm.float_data(),
2348 &mut h,
2349 &mut b16,
2350 n_embd,
2351 t,
2352 eps,
2353 )?;
2354 hx16 = Some(b16);
2355 } else {
2356 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
2357 }
2358 let mixed = match &layer.mixer {
2359 Mixer::Full(fa) => {
2363 self.full_attn_prime(e, fa, &h, hx16.as_ref(), pos_d, t, cache, il, t)?
2364 }
2365 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2366 Mixer::Linear(la) => {
2367 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2368 let g4 = match hx16.as_ref() {
2369 Some(xh) => e.matmul_group_xh(&ws, &h, xh, t)?,
2370 None => e.matmul_group(&ws, &h, t)?,
2371 };
2372 self.linear_attn_prime_core_pad(e, la, g4, t, cache, il, Some(len_d))?
2373 }
2374 };
2375 let mut x1 = e.uninit(t * n_embd)?;
2376 e.add(&x, &mixed, &mut x1, t * n_embd)?;
2377 let mut z = e.uninit(t * n_embd)?;
2378 let mut zx16: Option<CudaSlice<u8>> = None;
2379 if f16fuse {
2380 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2381 e.rms_norm_f16out(
2382 &x1,
2383 layer.post_attn_norm.float_data(),
2384 &mut z,
2385 &mut b16,
2386 n_embd,
2387 t,
2388 eps,
2389 )?;
2390 zx16 = Some(b16);
2391 } else {
2392 e.rms_norm(
2393 &x1,
2394 layer.post_attn_norm.float_data(),
2395 &mut z,
2396 n_embd,
2397 t,
2398 eps,
2399 )?;
2400 }
2401 let ffn_out = match &layer.ffn {
2402 crate::hybrid::Ffn::Dense {
2403 ffn_gate,
2404 ffn_up,
2405 ffn_down,
2406 } => {
2407 let n_ff = ffn_gate.out_features();
2408 let mut g2 = match &zx16 {
2409 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], &z, xh, t)?,
2410 None => e.matmul_group(&[ffn_gate, ffn_up], &z, t)?,
2411 };
2412 let up = g2.pop().unwrap();
2413 let gate = g2.pop().unwrap();
2414 let mut act = e.uninit(t * n_ff)?;
2415 Self::ffn_act_lim(
2417 e,
2418 &self.cfg,
2419 &gate,
2420 &up,
2421 1.0,
2422 1.0,
2423 self.cfg.clamp_shexp_at(il as u32),
2424 &mut act,
2425 t * n_ff,
2426 )?;
2427 e.matmul(ffn_down, &act, t)?
2428 }
2429 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
2430 };
2431 let mut x2 = e.uninit(t * n_embd)?;
2432 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
2433 x = x2;
2434 }
2435 if !crate::spec::spec_hpost() {
2437 e.row_gather_dev(&x, h_seed_out, len_d, n_embd)?;
2438 }
2439 let mut hn = e.uninit(t * n_embd)?;
2440 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2441 if crate::spec::spec_hpost() {
2442 e.row_gather_dev(&hn, h_seed_out, len_d, n_embd)?;
2443 }
2444 let mut hlast = e.uninit(n_embd)?;
2445 e.row_gather_dev(&hn, &mut hlast, len_d, n_embd)?;
2446 let logits = e.matmul(&self.output, &hlast, 1)?;
2447 let nv = logits.len();
2448 e.copy_into(logits_out, 0, &logits, nv)?;
2449 Ok(())
2450 }
2451
2452 fn step35_prime_batch_on() -> bool {
2453 std::env::var("MEMRA_STEP35_PRIME_BATCH").as_deref() != Ok("0")
2454 }
2455
2456 #[allow(clippy::too_many_arguments)]
2459 fn step35_prime_batch_layers(
2460 &self,
2461 e: &Engine,
2462 mut x: CudaSlice<f32>,
2463 lo: usize,
2464 hi: usize,
2465 ts: &[usize],
2466 offs: &[usize],
2467 pos_ds: &[CudaSlice<i32>],
2468 caches: &mut [&mut Cache],
2469 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2470 let cfg = &self.cfg;
2471 let n_embd = cfg.n_embd as usize;
2472 let eps = cfg.rms_eps;
2473 let b = ts.len();
2474 let total: usize = ts.iter().sum();
2475 let f16fuse = crate::f16_ffi::pp_f16_enabled() && total >= 16;
2476
2477 let split = |e: &Engine,
2478 y: &CudaSlice<f32>,
2479 dim: usize|
2480 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2481 let mut out = Vec::with_capacity(b);
2482 for s in 0..b {
2483 let mut ys = e.uninit(ts[s] * dim)?;
2484 e.copy_view_into(
2485 &mut ys,
2486 0,
2487 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2488 ts[s] * dim,
2489 )?;
2490 out.push(ys);
2491 }
2492 Ok(out)
2493 };
2494
2495 for il in lo..hi {
2496 let layer = &self.layers[il];
2497 let Mixer::Full(fa) = &layer.mixer else {
2498 return Err(format!("step35 layer {il} is not full-attn — corrupt config").into());
2499 };
2500
2501 let mut h = e.uninit(total * n_embd)?;
2502 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2503 if f16fuse {
2504 e.rms_norm_f16out(
2505 &x,
2506 layer.attn_norm.float_data(),
2507 &mut h,
2508 &mut hx16,
2509 n_embd,
2510 total,
2511 eps,
2512 )?;
2513 } else {
2514 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, total, eps)?;
2515 }
2516
2517 let gate_w = fa
2521 .attn_gate
2522 .as_ref()
2523 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
2524 let mut g4 = if f16fuse {
2525 e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, &hx16, total)?
2526 } else {
2527 e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, total)?
2528 };
2529 let gate = g4.pop().unwrap();
2530 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2531 (0..b).map(|_| Vec::with_capacity(3)).collect();
2532 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g4) {
2533 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2534 parts[s].push(ys);
2535 }
2536 }
2537 let gates = split(e, &gate, gate_w.out_features())?;
2538 let geometry = self.step35_geom(il);
2539 let hd = geometry.head_dim_k as usize;
2540 let nh = geometry.n_head as usize;
2541 let mut ag_cat = e.uninit(total * nh * hd)?;
2542 for (s, (g3s, gate)) in parts.into_iter().zip(gates).enumerate() {
2543 let ag = self.step35_attn_pre_wo(
2544 e,
2545 fa,
2546 g3s,
2547 None,
2548 Some(&gate),
2549 &pos_ds[s],
2550 ts[s],
2551 Some(&mut *caches[s]),
2552 il,
2553 ts[s],
2554 )?;
2555 e.copy_into(&mut ag_cat, offs[s] * nh * hd, &ag, ts[s] * nh * hd)?;
2556 }
2557 let mixed = e.matmul(&fa.wo, &ag_cat, total)?;
2558
2559 let mut x1 = e.uninit(total * n_embd)?;
2560 let mut z = e.uninit(total * n_embd)?;
2561 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2562 if f16fuse {
2563 e.add_rms_norm_f16out(
2564 &x,
2565 &mixed,
2566 layer.post_attn_norm.float_data(),
2567 &mut x1,
2568 &mut z,
2569 &mut zx16,
2570 n_embd,
2571 total,
2572 eps,
2573 )?;
2574 } else {
2575 e.add(&x, &mixed, &mut x1, total * n_embd)?;
2576 e.rms_norm(
2577 &x1,
2578 layer.post_attn_norm.float_data(),
2579 &mut z,
2580 n_embd,
2581 total,
2582 eps,
2583 )?;
2584 }
2585
2586 let ffn_out = match &layer.ffn {
2587 crate::hybrid::Ffn::Dense {
2588 ffn_gate,
2589 ffn_up,
2590 ffn_down,
2591 } => {
2592 let n_ff = ffn_gate.out_features();
2593 let mut g2 = if f16fuse {
2594 e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?
2595 } else {
2596 e.matmul_group(&[ffn_gate, ffn_up], &z, total)?
2597 };
2598 let up = g2.pop().unwrap();
2599 let gate = g2.pop().unwrap();
2600 let mut act = e.uninit(total * n_ff)?;
2601 let d_lim = cfg.clamp_shexp_at(il as u32);
2602 if Self::f16out_on(e, total) && cfg.m3.is_none() && d_lim.is_none() {
2603 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2604 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2605 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2606 Some(y) => y,
2607 None => e.matmul(ffn_down, &act, total)?,
2608 }
2609 } else {
2610 Self::ffn_act_lim(
2611 e,
2612 cfg,
2613 &gate,
2614 &up,
2615 1.0,
2616 1.0,
2617 d_lim,
2618 &mut act,
2619 total * n_ff,
2620 )?;
2621 e.matmul(ffn_down, &act, total)?
2622 }
2623 }
2624 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il(e, m, &z, total, il as u16)?,
2625 };
2626 let mut x2 = e.uninit(total * n_embd)?;
2627 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2628 x = x2;
2629 }
2630 Ok(x)
2631 }
2632
2633 fn step35_prime_batch_epilogue(
2634 &self,
2635 e: &Engine,
2636 x: CudaSlice<f32>,
2637 ts: &[usize],
2638 offs: &[usize],
2639 caches: &mut [&mut Cache],
2640 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2641 let n_embd = self.cfg.n_embd as usize;
2642 let total: usize = ts.iter().sum();
2643 let mut hn = e.uninit(total * n_embd)?;
2644 e.rms_norm(
2645 &x,
2646 self.output_norm.float_data(),
2647 &mut hn,
2648 n_embd,
2649 total,
2650 self.cfg.rms_eps,
2651 )?;
2652
2653 let hidden_src = if crate::spec::spec_hpost() { &hn } else { &x };
2654 let mut out = Vec::with_capacity(ts.len());
2655 for s in 0..ts.len() {
2656 let mut hidden = e.uninit(ts[s] * n_embd)?;
2657 e.copy_view_into(
2658 &mut hidden,
2659 0,
2660 &hidden_src.slice(offs[s] * n_embd..(offs[s] + ts[s]) * n_embd),
2661 ts[s] * n_embd,
2662 )?;
2663 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2664 let mut h_seed = e.uninit(n_embd)?;
2665 e.copy_view_into(
2666 &mut h_seed,
2667 0,
2668 &hidden_src.slice(last0..last0 + n_embd),
2669 n_embd,
2670 )?;
2671 let mut hlast = e.uninit(n_embd)?;
2673 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2674 let logits = e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?;
2675 caches[s].pos += ts[s];
2676 out.push((logits, h_seed, hidden));
2677 }
2678 Ok(out)
2679 }
2680
2681 fn step35_prime_cache_batch(
2682 &self,
2683 e: &Engine,
2684 prompts: &[&[u32]],
2685 caches: &mut [&mut Cache],
2686 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2687 validate_step_prime_batch_modes(
2688 step_tp_prefill_enabled()?,
2689 step_ep_grouped_prefill_enabled()?,
2690 )?;
2691 if crate::pp::pp_host_bounce_active()
2692 && (!crate::pp::prime_pp_on() || crate::pp::pp_cuts(self.layers.len()).is_none())
2693 {
2694 return Err(
2695 "step35_prime_cache_batch: MEMRA_PP_HOST_BOUNCE=1 requires a valid prime \
2696 stage split; refusing an unsplit remote-weight walk"
2697 .into(),
2698 );
2699 }
2700 if !Self::step35_prime_batch_on() {
2701 return Err("step35 batched prime is disabled (MEMRA_STEP35_PRIME_BATCH=0)".into());
2702 }
2703 if caches.iter().any(|c| c.pos != 0) {
2704 return Err(
2705 "step35 batched prime currently supports complete fresh prompts only; \
2706 continuation/tick chunks require per-request queued_after"
2707 .into(),
2708 );
2709 }
2710
2711 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2712 for &t in &ts {
2713 assert!(
2714 t >= PRIME_MIN_T,
2715 "step35 batched prime needs T >= {PRIME_MIN_T}"
2716 );
2717 }
2718 for (s, c) in caches.iter().enumerate() {
2719 assert!(
2720 ts[s] <= c.max_ctx,
2721 "step35 batched prime exceeds cache max_ctx"
2722 );
2723 }
2724 let offs: Vec<usize> = ts
2725 .iter()
2726 .scan(0usize, |a, &t| {
2727 let o = *a;
2728 *a += t;
2729 Some(o)
2730 })
2731 .collect();
2732 let total: usize = ts.iter().sum();
2733 let payload = total * self.cfg.n_embd as usize;
2734 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2735 let positions: Vec<Vec<i32>> = ts.iter().map(|&t| (0..t as i32).collect()).collect();
2736 let upload_positions =
2737 |e: &Engine| -> Result<Vec<CudaSlice<i32>>, Box<dyn std::error::Error>> {
2738 positions
2739 .iter()
2740 .map(|p| e.htod_i32(p))
2741 .collect::<Result<_, _>>()
2742 };
2743
2744 static ONCE: std::sync::Once = std::sync::Once::new();
2745 ONCE.call_once(|| {
2746 eprintln!(
2747 "[step35-prime-batch] first concat prime: B={} tokens={total}",
2748 prompts.len()
2749 );
2750 });
2751
2752 let out = if !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
2753 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
2754 let rt = crate::pp::PpNRt::get(e)?;
2755 let n_st = fence.len() - 1;
2756 assert_eq!(
2757 rt.n_stages(),
2758 n_st,
2759 "step35 prime batch stage count mismatch"
2760 );
2761 let caller_stream = e.stream();
2762 rt.fence_stages_behind(&caller_stream)?;
2763
2764 let mut slot = {
2765 let _st0 = rt.enter(0);
2766 let e0 = rt.engine(0, e);
2767 let pos_ds = upload_positions(e0)?;
2768 let x = self.embed(e0, &cat_tokens)?;
2769 let x = self.step35_prime_batch_layers(
2770 e0, x, fence[0], fence[1], &ts, &offs, &pos_ds, caches,
2771 )?;
2772 rt.tx(0, &x, payload)?
2773 };
2774 for s in 1..n_st - 1 {
2775 let _st = rt.enter(s);
2776 let es = rt.engine(s, e);
2777 let pos_ds = upload_positions(es)?;
2778 let x = rt.rx(s - 1, slot, payload)?;
2779 let x = self.step35_prime_batch_layers(
2780 es,
2781 x,
2782 fence[s],
2783 fence[s + 1],
2784 &ts,
2785 &offs,
2786 &pos_ds,
2787 caches,
2788 )?;
2789 slot = rt.tx(s, &x, payload)?;
2790 }
2791
2792 let _stl = rt.enter(n_st - 1);
2793 let el = rt.engine(n_st - 1, e);
2794 let pos_ds = upload_positions(el)?;
2795 let x = rt.rx(n_st - 2, slot, payload)?;
2796 let x = self.step35_prime_batch_layers(
2797 el,
2798 x,
2799 fence[n_st - 1],
2800 fence[n_st],
2801 &ts,
2802 &offs,
2803 &pos_ds,
2804 caches,
2805 )?;
2806 let out = self.step35_prime_batch_epilogue(el, x, &ts, &offs, caches)?;
2807 rt.publish_to(n_st - 1, &caller_stream)?;
2808 crate::pp::STEP35_PRIME_BATCH_SPLITS
2809 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2810 out
2811 } else {
2812 let pos_ds = upload_positions(e)?;
2813 let x = self.embed(e, &cat_tokens)?;
2814 let x = self.step35_prime_batch_layers(
2815 e,
2816 x,
2817 0,
2818 self.layers.len(),
2819 &ts,
2820 &offs,
2821 &pos_ds,
2822 caches,
2823 )?;
2824 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2825 }
2826 } else {
2827 let pos_ds = upload_positions(e)?;
2828 let x = self.embed(e, &cat_tokens)?;
2829 let x = self.step35_prime_batch_layers(
2830 e,
2831 x,
2832 0,
2833 self.layers.len(),
2834 &ts,
2835 &offs,
2836 &pos_ds,
2837 caches,
2838 )?;
2839 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2840 };
2841 crate::pp::STEP35_PRIME_BATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2842 Ok(out)
2843 }
2844
2845 pub fn prime_cache_batch(
2862 &self,
2863 e: &Engine,
2864 prompts: &[&[u32]],
2865 caches: &mut [&mut Cache],
2866 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2867 if crate::pp::pp_cuts(self.layers.len()).is_some()
2868 && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
2869 {
2870 return Err("pipeline rewrite is not qualified for batched prime".into());
2871 }
2872 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::CarriedPrime) {
2873 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::DecodeEager) {
2874 return Err("neither batched-prime nor eager rewrite is qualified".into());
2875 }
2876 if prompts.len() != caches.len() {
2877 return Err("prime fallback prompt/cache shape mismatch".into());
2878 }
2879 static ONCE: std::sync::Once = std::sync::Once::new();
2880 ONCE.call_once(|| {
2881 eprintln!(
2882 "[rewrite] carried-prime.v1 unqualified; using individual native eager primes"
2883 );
2884 });
2885 return prompts
2886 .iter()
2887 .copied()
2888 .zip(caches.iter_mut())
2889 .map(|(prompt, cache)| self.prime_cache(e, prompt, cache, 0))
2890 .collect();
2891 }
2892 let cfg = &self.cfg;
2893 let n_embd = cfg.n_embd as usize;
2894 let eps = cfg.rms_eps;
2895 let b = prompts.len();
2896 assert!(b >= 1 && b == caches.len());
2897 let pos0s: Vec<usize> = caches.iter().map(|c| c.pos).collect();
2898 let carried = pos0s.iter().any(|&p| p > 0);
2899 if self.uses_gemma_program() {
2905 return Err(
2906 "prime_cache_batch: gemma4 has no batched prime core (per-layer \
2907 swa/global geometry, softcapped head) — use gemma4_prime per sequence"
2908 .into(),
2909 );
2910 }
2911 if self.uses_sliding_gated_moe_program() {
2914 return self.step35_prime_cache_batch(e, prompts, caches);
2915 }
2916 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2917 for &t in &ts {
2918 assert!(
2919 t >= PRIME_MIN_T,
2920 "prime_cache_batch needs T >= {PRIME_MIN_T}"
2921 );
2922 }
2923 for (s, c) in caches.iter().enumerate() {
2924 assert!(
2925 c.pos + ts[s] <= c.max_ctx,
2926 "prime_cache_batch: prompt exceeds cache max_ctx"
2927 );
2928 }
2929 let total: usize = ts.iter().sum();
2930 let offs: Vec<usize> = ts
2931 .iter()
2932 .scan(0usize, |a, &t| {
2933 let o = *a;
2934 *a += t;
2935 Some(o)
2936 })
2937 .collect();
2938 let pos_ds: Vec<CudaSlice<i32>> = ts
2940 .iter()
2941 .zip(&pos0s)
2942 .map(|(&t, &p0)| e.htod_i32(&(p0 as i32..(p0 + t) as i32).collect::<Vec<_>>()))
2943 .collect::<Result<_, _>>()?;
2944 let split = |e: &Engine,
2946 y: &CudaSlice<f32>,
2947 dim: usize|
2948 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2949 let mut out = Vec::with_capacity(b);
2950 for s in 0..b {
2951 let mut ys = e.uninit(ts[s] * dim)?;
2952 e.copy_view_into(
2953 &mut ys,
2954 0,
2955 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2956 ts[s] * dim,
2957 )?;
2958 out.push(ys);
2959 }
2960 Ok(out)
2961 };
2962
2963 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2964 let mut x = self.embed(e, &cat_tokens)?; for (il, layer) in self.layers.iter().enumerate() {
2966 let mut h = e.uninit(total * n_embd)?;
2967 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2968 e.rms_norm_f16out(
2969 &x,
2970 layer.attn_norm.float_data(),
2971 &mut h,
2972 &mut hx16,
2973 n_embd,
2974 total,
2975 eps,
2976 )?;
2977 let mut mixed = e.uninit(total * n_embd)?;
2979 match &layer.mixer {
2980 Mixer::Full(fa) => {
2981 let g3 = e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], &h, &hx16, total)?;
2982 let geometry = self.cfg.full_attention_geometry_at(il as u32);
2988 let (n_head, n_head_kv, head_dim) = (
2989 geometry.n_head as usize,
2990 geometry.n_head_kv as usize,
2991 geometry.head_dim_k as usize,
2992 );
2993 let fa_scale = geometry.attention_scale();
2994 let use_favl = !carried
2995 && (2..=8).contains(&b)
2996 && (head_dim == 256 || head_dim == 128)
2997 && geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ
2998 && std::env::var("MEMRA_NOFA").is_err()
2999 && std::env::var("MEMRA_FA_FLOOR").is_err()
3000 && std::env::var("MEMRA_FA_PP_W2").as_deref() != Ok("1")
3001 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0")
3002 && std::env::var("MEMRA_FA_VL").as_deref() != Ok("0");
3003 if use_favl {
3004 let (qf_w, kf_w, vf_w) = (
3005 fa.wq.out_features(),
3006 fa.wk.out_features(),
3007 fa.wv.out_features(),
3008 );
3009 memra_gguf::config::check_fused_q_gate_extent(qf_w, head_dim, n_head, 1)?;
3014 struct APre {
3015 q: CudaSlice<f32>,
3016 gate: Option<CudaSlice<f32>>,
3017 qn: CudaSlice<f32>,
3018 kn: CudaSlice<f32>,
3019 }
3020 let mut aps = Vec::with_capacity(b);
3021 for &t in ts.iter().take(b) {
3022 aps.push(APre {
3023 q: e.uninit(t * n_head * head_dim)?,
3024 gate: Some(e.uninit(t * n_head * head_dim)?),
3025 qn: e.uninit(t * n_head * head_dim)?,
3026 kn: e.uninit(t * n_head_kv * head_dim)?,
3027 });
3028 }
3029 let (kv_dim_k, kv_dim_v, ktb, vtb) = {
3030 let kvl = caches[0].kv[il].as_ref().unwrap();
3031 (kvl.kv_dim_k, kvl.kv_dim_v, kvl.k_tok_bytes, kvl.v_tok_bytes)
3032 };
3033 let pargs: Vec<crate::AttnPreVl> = (0..b)
3034 .map(|s| {
3035 let (o, t) = (offs[s], ts[s]);
3036 let kvl = caches[s].kv[il].as_ref().unwrap();
3037 assert!(
3038 kvl.len == 0 && kvl.len + t <= caches[s].max_ctx,
3039 "prime_cache_batch attn vl: fresh + capacity"
3040 );
3041 crate::AttnPreVl {
3042 qf: e.addr_f32v(&g3[0].slice(o * qf_w..(o + t) * qf_w)),
3043 kf: e.addr_f32v(&g3[1].slice(o * kf_w..(o + t) * kf_w)),
3044 vf: e.addr_f32v(&g3[2].slice(o * vf_w..(o + t) * vf_w)),
3045 q: e.addr_f32(&aps[s].q),
3046 gate: e.addr_f32(aps[s].gate.as_ref().unwrap()),
3047 qn: e.addr_f32(&aps[s].qn),
3048 kn: e.addr_f32(&aps[s].kn),
3049 kc: e.addr_u8(&kvl.k),
3050 vc: e.addr_u8(&kvl.v),
3051 t: t as i32,
3052 pad: 0,
3053 }
3054 })
3055 .collect();
3056 e.attn_pre_vl8(
3057 &pargs,
3058 fa.q_norm.float_data(),
3059 fa.k_norm.float_data(),
3060 head_dim,
3061 geometry.n_rot as usize,
3062 n_head,
3063 n_head_kv,
3064 self.cfg.rms_eps,
3065 geometry.rope_base,
3066 1.0,
3067 kv_dim_k,
3068 kv_dim_v,
3069 ktb,
3070 vtb,
3071 )?;
3072 for s in 0..b {
3073 let kvl = caches[s].kv[il].as_mut().unwrap();
3074 kvl.len += ts[s];
3075 let new_len = kvl.len as i32;
3076 e.set_i32_one(&mut kvl.len_d, new_len)?;
3077 }
3078 let mut attns = Vec::with_capacity(b);
3079 let mut mirrors = Vec::with_capacity(b);
3080 for &t in ts.iter().take(b) {
3081 attns.push(e.uninit(t * n_head * head_dim)?);
3082 let n = t * n_head_kv * head_dim;
3083 mirrors.push((e.alloc_u8_uninit(n * 2)?, e.alloc_u8_uninit(n * 2)?));
3084 }
3085 let fa3_on = match std::env::var("MEMRA_FA3").as_deref() {
3088 Ok("0") => false,
3089 Ok("1") => {
3093 crate::refuse_portable_force(
3094 "MEMRA_FA3=1",
3095 "the sm_90a fa3/bf16 kernels",
3096 );
3097 true
3098 }
3099 _ => cfg!(memra_hopper_mma),
3100 };
3101 if fa3_on {
3102 let mut q16s = Vec::with_capacity(b);
3103 let mut v16s = Vec::with_capacity(b);
3104 for s in 0..b {
3105 let t = ts[s];
3106 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
3107 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
3108 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3109 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
3110 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3111 e.f32_to_bf16_v(
3112 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
3113 &mut v16,
3114 t * n_head_kv * head_dim,
3115 )?;
3116 q16s.push(q16);
3117 v16s.push((k16, v16));
3118 }
3119 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
3120 let mut kp = qp;
3121 let mut vp = qp;
3122 let mut op = [core::ptr::null_mut::<f32>(); 8];
3123 let mut tsv = [0i32; 8];
3124 for s in 0..b {
3125 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
3126 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
3127 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
3128 op[s] = e.addr_f32(&attns[s]) as *mut f32;
3129 tsv[s] = ts[s] as i32;
3130 }
3131 let rc = unsafe {
3132 crate::fa3_vl_raw(
3133 qp.as_ptr(),
3134 kp.as_ptr(),
3135 vp.as_ptr(),
3136 op.as_ptr(),
3137 tsv.as_ptr(),
3138 b as i32,
3139 n_head as i32,
3140 n_head_kv as i32,
3141 head_dim as i32,
3142 fa_scale,
3143 e.stream().cu_stream() as *mut core::ffi::c_void,
3144 )
3145 };
3146 if rc != 0 {
3147 return Err(format!("memra_fa3_vl rc={rc}").into());
3148 }
3149 } else {
3150 let fargs: Vec<crate::FaSeqVl> = (0..b)
3151 .map(|s| crate::FaSeqVl {
3152 q: e.addr_f32(&aps[s].qn),
3153 k16: e.addr_u8(&mirrors[s].0),
3154 v16: e.addr_u8(&mirrors[s].1),
3155 o: e.addr_f32(&attns[s]),
3156 kf: e.addr_f32(&aps[s].kn),
3157 vf: e.addr_f32v(
3158 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
3159 ),
3160 t: ts[s] as i32,
3161 pad: 0,
3162 })
3163 .collect();
3164 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
3165 }
3166 for (s, attn) in attns.into_iter().enumerate() {
3167 let (attn_g, ag16) = self.full_attn_prime_post_fa(
3168 e,
3169 attn,
3170 &aps[s].gate,
3171 ts[s],
3172 n_head,
3173 head_dim,
3174 )?;
3175 let mut done = false;
3176 if let Some(xh) = &ag16 {
3177 done = e.try_f16_gemm_pre_into_off(
3178 &fa.wo,
3179 xh,
3180 ts[s],
3181 &mut mixed,
3182 offs[s] * n_embd,
3183 )?;
3184 }
3185 if !done {
3186 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3187 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3188 }
3189 }
3190 } else {
3191 let mut parts: Vec<Vec<CudaSlice<f32>>> =
3192 (0..b).map(|_| Vec::new()).collect();
3193 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
3194 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
3195 parts[s].push(ys);
3196 }
3197 }
3198 for (s, g3s) in parts.into_iter().enumerate() {
3199 let (attn_g, ag16) = self.full_attn_prime_core_inner(
3201 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
3202 )?;
3203 let mut done = false;
3204 if let Some(xh) = &ag16 {
3205 done = e.try_f16_gemm_pre_into_off(
3206 &fa.wo,
3207 xh,
3208 ts[s],
3209 &mut mixed,
3210 offs[s] * n_embd,
3211 )?;
3212 }
3213 if !done {
3214 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3215 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3216 }
3217 }
3218 }
3219 }
3220 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
3221 Mixer::Linear(la) => {
3222 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3227 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
3228 let outs =
3229 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
3230 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
3231 let (o, t) = (offs[s], ts[s]);
3232 let mut done = false;
3233 if let Some(xh) = &gn16 {
3234 done = e.try_f16_gemm_pre_into_off(
3235 &la.ssm_out,
3236 xh,
3237 t,
3238 &mut mixed,
3239 o * n_embd,
3240 )?;
3241 }
3242 if !done {
3243 let m = e.matmul(&la.ssm_out, &gn, t)?;
3244 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
3245 }
3246 }
3247 }
3248 }
3249 let mut x1 = e.uninit(total * n_embd)?;
3250 let mut z = e.uninit(total * n_embd)?;
3251 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3252 e.add_rms_norm_f16out(
3253 &x,
3254 &mixed,
3255 layer.post_attn_norm.float_data(),
3256 &mut x1,
3257 &mut z,
3258 &mut zx16,
3259 n_embd,
3260 total,
3261 eps,
3262 )?;
3263 let ffn_out = match &layer.ffn {
3264 crate::hybrid::Ffn::Dense {
3265 ffn_gate,
3266 ffn_up,
3267 ffn_down,
3268 } => {
3269 let n_ff = ffn_gate.out_features();
3270 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
3271 let up = g2.pop().unwrap();
3272 let gate = g2.pop().unwrap();
3273 let mut act = e.uninit(total * n_ff)?;
3274 let d_lim = self.cfg.clamp_shexp_at(il as u32);
3278 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
3279 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
3280 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
3281 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
3282 Some(y) => y,
3283 None => e.matmul(ffn_down, &act, total)?,
3284 }
3285 } else {
3286 Self::ffn_act_lim(
3287 e,
3288 &self.cfg,
3289 &gate,
3290 &up,
3291 1.0,
3292 1.0,
3293 d_lim,
3294 &mut act,
3295 total * n_ff,
3296 )?;
3297 e.matmul(ffn_down, &act, total)?
3298 }
3299 }
3300 crate::hybrid::Ffn::Moe(m) => {
3301 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
3302 }
3303 };
3304 let mut x2 = e.uninit(total * n_embd)?;
3305 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
3306 x = x2;
3307 }
3308 let mut hn = e.uninit(total * n_embd)?;
3310 e.rms_norm(
3311 &x,
3312 self.output_norm.float_data(),
3313 &mut hn,
3314 n_embd,
3315 total,
3316 eps,
3317 )?;
3318 let mut hcat = e.uninit(b * n_embd)?;
3324 for s in 0..b {
3325 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3326 e.copy_view_into(
3327 &mut hcat,
3328 s * n_embd,
3329 &hn.slice(last0..last0 + n_embd),
3330 n_embd,
3331 )?;
3332 }
3333 let logits_cat = if b >= 2 {
3334 e.try_f16_gemm(&self.output, &hcat, b)?
3335 } else {
3336 None
3337 };
3338 let logits_host: Option<Vec<f32>> = match &logits_cat {
3339 Some(lc) => Some(e.dtoh(lc)?),
3340 None => None,
3341 };
3342 let n_vocab = self.output.out_features();
3343 let mut hidden_all = if crate::spec::spec_hpost() {
3344 split(e, &hn, n_embd)?
3345 } else {
3346 split(e, &x, n_embd)?
3347 };
3348 let mut out = Vec::with_capacity(b);
3349 for s in 0..b {
3350 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3351 let mut h_seed = e.uninit(n_embd)?;
3352 if !crate::spec::spec_hpost() {
3353 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
3354 } else {
3355 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3356 }
3357 let logits = match &logits_host {
3358 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
3359 None => {
3360 let mut hlast = e.uninit(n_embd)?;
3361 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3362 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
3363 }
3364 };
3365 caches[s].pos += ts[s];
3366 out.push((logits, h_seed, hidden_all.remove(0)));
3367 }
3368 Ok(out)
3369 }
3370
3371 #[allow(clippy::too_many_arguments)]
3382 fn full_attn_prime(
3383 &self,
3384 e: &Engine,
3385 fa: &FullAttnLayer,
3386 h: &CudaSlice<f32>,
3387 hx: Option<&CudaSlice<u8>>,
3388 pos_d: &CudaSlice<i32>,
3389 t: usize,
3390 cache: &mut Cache,
3391 il: usize,
3392 seq_end: usize,
3393 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3394 if self.uses_sliding_gated_moe_program() {
3395 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
3396 }
3397 let g3 = match hx {
3402 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
3403 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
3404 };
3405 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
3406 }
3407
3408 fn full_attn_prime_core(
3412 &self,
3413 e: &Engine,
3414 fa: &FullAttnLayer,
3415 g3: Vec<CudaSlice<f32>>,
3416 pos_d: &CudaSlice<i32>,
3417 t: usize,
3418 cache: &mut Cache,
3419 il: usize,
3420 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3421 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
3422 if let Some(xh) = &ag16 {
3423 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
3424 return Ok(y);
3425 }
3426 }
3427 Ok(e.matmul(&fa.wo, &attn_g, t)?)
3428 }
3429
3430 fn full_attn_prime_core_inner(
3431 &self,
3432 e: &Engine,
3433 fa: &FullAttnLayer,
3434 g3: Vec<CudaSlice<f32>>,
3435 pos_d: &CudaSlice<i32>,
3436 t: usize,
3437 cache: &mut Cache,
3438 il: usize,
3439 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3440 let cfg = &self.cfg;
3441 let geometry = cfg.full_attention_geometry_at(il as u32);
3442 let n_head = geometry.n_head as usize;
3443 let n_head_kv = geometry.n_head_kv as usize;
3444 let head_dim = geometry.head_dim_k as usize;
3445 let scale = geometry.attention_scale();
3446 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
3447 let AttnPre { q, k, v, gate } = pre;
3448 let mut attn = e.uninit(t * n_head * head_dim)?;
3449 self.full_attn_prime_fa_dispatch(
3450 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
3451 )?;
3452 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
3453 }
3454
3455 #[allow(clippy::type_complexity)]
3459 fn full_attn_prime_pre_fa(
3460 &self,
3461 e: &Engine,
3462 fa: &FullAttnLayer,
3463 mut g3: Vec<CudaSlice<f32>>,
3464 pos_d: &CudaSlice<i32>,
3465 t: usize,
3466 cache: &mut Cache,
3467 il: usize,
3468 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3469 let cfg = &self.cfg;
3470 let geometry = cfg.full_attention_geometry_at(il as u32);
3471 let n_head = geometry.n_head as usize;
3472 let n_head_kv = geometry.n_head_kv as usize;
3473 let head_dim = geometry.head_dim_k as usize;
3474 let eps = cfg.rms_eps;
3475
3476 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3480 let v = g3.pop().unwrap();
3481 let mut k = g3.pop().unwrap();
3482 let qf = g3.pop().unwrap();
3483 let (mut q, gate) = if gated {
3484 let mut q = e.uninit(t * n_head * head_dim)?;
3485 let mut gate = e.uninit(t * n_head * head_dim)?;
3486 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3487 (q, Some(gate))
3488 } else {
3489 (qf, None)
3490 };
3491
3492 let mut qn = e.uninit(t * n_head * head_dim)?;
3493 e.rms_norm(
3494 &q,
3495 fa.q_norm.float_data(),
3496 &mut qn,
3497 head_dim,
3498 n_head * t,
3499 eps,
3500 )?;
3501 q = qn;
3502 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3503 e.rms_norm(
3504 &k,
3505 fa.k_norm.float_data(),
3506 &mut kn,
3507 head_dim,
3508 n_head_kv * t,
3509 eps,
3510 )?;
3511 k = kn;
3512 let rope_dims = geometry.n_rot as usize;
3513 e.rope_neox(
3514 &mut q,
3515 pos_d,
3516 head_dim,
3517 rope_dims,
3518 n_head,
3519 t,
3520 geometry.rope_base,
3521 1.0,
3522 )?;
3523 e.rope_neox(
3524 &mut k,
3525 pos_d,
3526 head_dim,
3527 rope_dims,
3528 n_head_kv,
3529 t,
3530 geometry.rope_base,
3531 1.0,
3532 )?;
3533
3534 {
3537 let kvl = cache.kv[il].as_mut().unwrap();
3538 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3539 e.append_kv_quantized_rows(
3540 &k,
3541 &v,
3542 &mut kvl.k,
3543 &mut kvl.v,
3544 kvl.len,
3545 t,
3546 kvl.kv_dim_k,
3547 kvl.kv_dim_v,
3548 kvl.k_tok_bytes,
3549 kvl.v_tok_bytes,
3550 crate::Engine::kv_fp8_on(),
3551 )?;
3552 kvl.len += t;
3553 let new_len = kvl.len as i32;
3554 e.set_i32_one(&mut kvl.len_d, new_len)?;
3555 }
3556
3557 let base_len = {
3558 let kvl = cache.kv[il].as_ref().unwrap();
3559 kvl.len - t };
3561 Ok((AttnPre { q, k, v, gate }, base_len))
3562 }
3563
3564 #[allow(clippy::too_many_arguments)]
3571 fn full_attn_prime_fa_dispatch(
3572 &self,
3573 e: &Engine,
3574 q: &CudaSlice<f32>,
3575 k: &CudaSlice<f32>,
3576 v: &CudaSlice<f32>,
3577 attn: &mut CudaSlice<f32>,
3578 base_len: usize,
3579 t: usize,
3580 cache: &mut Cache,
3581 il: usize,
3582 head_dim: usize,
3583 n_head: usize,
3584 n_head_kv: usize,
3585 scale: f32,
3586 ) -> Result<(), Box<dyn std::error::Error>> {
3587 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
3600 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3601 e.sdpa_naive(
3602 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3603 )?;
3604 } else {
3605 e.fa_prefill(
3606 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3607 )?;
3608 }
3609 return Ok(());
3610 }
3611 let kvl = cache.kv[il].as_ref().unwrap();
3612 let t_kv = base_len + t;
3613 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
3614 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
3615 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3619 e.sdpa_naive_quantized_view(
3620 q,
3621 &k_view,
3622 &v_view,
3623 attn,
3624 head_dim,
3625 n_head,
3626 n_head_kv,
3627 t,
3628 t_kv,
3629 scale,
3630 true,
3631 kvl.k_tok_bytes,
3632 kvl.v_tok_bytes,
3633 )?;
3634 return Ok(());
3635 }
3636 let deqw = std::env::var("MEMRA_PRIME_DEQW")
3644 .map(|v| v != "0")
3645 .unwrap_or(true);
3646 if deqw {
3647 e.fa_prefill_view_ws(
3648 q,
3649 &k_view,
3650 &v_view,
3651 attn,
3652 head_dim,
3653 n_head,
3654 n_head_kv,
3655 t,
3656 t_kv,
3657 scale,
3658 true,
3659 kvl.k_tok_bytes,
3660 kvl.v_tok_bytes,
3661 crate::Engine::kv_fp8_on(),
3662 )?;
3663 } else {
3664 e.fa_prefill_view(
3665 q,
3666 &k_view,
3667 &v_view,
3668 attn,
3669 head_dim,
3670 n_head,
3671 n_head_kv,
3672 t,
3673 t_kv,
3674 scale,
3675 true,
3676 kvl.k_tok_bytes,
3677 kvl.v_tok_bytes,
3678 crate::Engine::kv_fp8_on(),
3679 )?;
3680 }
3681 Ok(())
3682 }
3683
3684 fn full_attn_prime_post_fa(
3687 &self,
3688 e: &Engine,
3689 attn: CudaSlice<f32>,
3690 gate: &Option<CudaSlice<f32>>,
3691 t: usize,
3692 n_head: usize,
3693 head_dim: usize,
3694 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3695 let (attn_g, ag16) = match gate {
3696 Some(gate) => {
3697 let n = t * n_head * head_dim;
3698 let mut ag = e.uninit(n)?;
3699 if Self::f16out_on(e, t) {
3700 let mut a16 = e.alloc_u8_uninit(n * 2)?;
3701 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
3702 (ag, Some(a16))
3703 } else {
3704 let mut gsig = e.uninit(n)?;
3705 e.sigmoid(gate, &mut gsig, n)?;
3706 e.mul(&attn, &gsig, &mut ag, n)?;
3707 (ag, None)
3708 }
3709 }
3710 None => (attn, None),
3711 };
3712 Ok((attn_g, ag16))
3713 }
3714
3715 fn linear_attn_prime(
3722 &self,
3723 e: &Engine,
3724 la: &LinearAttnLayer,
3725 h: &CudaSlice<f32>,
3726 hx: Option<&CudaSlice<u8>>,
3727 t: usize,
3728 cache: &mut Cache,
3729 il: usize,
3730 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3731 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3733 let g4 = match hx {
3734 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
3735 None => e.matmul_group(&ws, h, t)?,
3736 };
3737 self.linear_attn_prime_core(e, la, g4, t, cache, il)
3738 }
3739
3740 fn linear_attn_prime_core(
3742 &self,
3743 e: &Engine,
3744 la: &LinearAttnLayer,
3745 mut g4: Vec<CudaSlice<f32>>,
3746 t: usize,
3747 cache: &mut Cache,
3748 il: usize,
3749 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3750 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
3751 }
3752
3753 #[allow(clippy::too_many_arguments)]
3757 fn linear_attn_prime_core_pad_inner(
3758 &self,
3759 e: &Engine,
3760 la: &LinearAttnLayer,
3761 mut g4: Vec<CudaSlice<f32>>,
3762 t: usize,
3763 cache: &mut Cache,
3764 il: usize,
3765 pad_len: Option<&CudaSlice<i32>>,
3766 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3767 let geometry = la.geometry;
3769 let d_state = geometry.key_head_dim as usize;
3770 let num_k = geometry.key_heads as usize;
3771 let num_v = geometry.value_heads as usize;
3772 let key_dim = d_state * num_k;
3773 let value_dim = geometry.value_head_dim as usize * num_v;
3774 let conv_dim = key_dim * 2 + value_dim;
3775 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(
3780 e,
3781 la,
3782 &qkv_mixed.slice(0..t * conv_dim),
3783 &z.slice(0..t * value_dim),
3784 &beta_raw.slice(0..t * num_v),
3785 &alpha.slice(0..t * num_v),
3786 t,
3787 cache,
3788 il,
3789 pad_len,
3790 )
3791 }
3792
3793 #[allow(clippy::too_many_arguments)]
3796 fn linear_attn_gdn_prep(
3797 &self,
3798 e: &Engine,
3799 la: &LinearAttnLayer,
3800 qkv_mixed: &cudarc::driver::CudaView<f32>,
3801 beta_raw: &cudarc::driver::CudaView<f32>,
3802 alpha: &cudarc::driver::CudaView<f32>,
3803 t: usize,
3804 cache: &mut Cache,
3805 il: usize,
3806 pad_len: Option<&CudaSlice<i32>>,
3807 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
3808 let cfg = &self.cfg;
3809 let geometry = la.geometry;
3810 let d_state = geometry.key_head_dim as usize;
3811 let num_k = geometry.key_heads as usize;
3812 let num_v = geometry.value_heads as usize;
3813 let d_conv = geometry.conv_kernel as usize;
3814 let key_dim = d_state * num_k; let value_dim = geometry.value_head_dim as usize * num_v;
3816 let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
3818 debug_assert!(
3819 t >= d_conv - 1,
3820 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
3821 );
3822
3823 let rl = cache.recur[il].as_mut().unwrap();
3828 let hk = Self::gdn_hk(e, t, num_v, num_k);
3829 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
3830 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
3832 let mut k_g = e.uninit(d_state * hk * t)?;
3833 let mut v_g = e.uninit(d_state * num_v * t)?;
3834 if conv_fuse {
3835 e.ssm_conv1d_gdn_state_pad(
3836 qkv_mixed,
3837 &mut rl.conv_state,
3838 la.ssm_conv1d.float_data(),
3839 &mut q_g,
3840 &mut k_g,
3841 &mut v_g,
3842 conv_dim,
3843 t,
3844 d_conv,
3845 d_state,
3846 num_v,
3847 num_k,
3848 key_dim,
3849 hk,
3850 pad_len,
3851 )?;
3852 } else {
3853 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
3855 qkv_mixed,
3856 &mut rl.conv_state,
3857 la.ssm_conv1d.float_data(),
3858 &mut conv_out,
3859 conv_dim,
3860 t,
3861 d_conv,
3862 pad_len,
3863 )?;
3864 e.qkv_to_gdn_repack(
3865 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
3866 )?;
3867 }
3868 let mut q_l2 = e.uninit(d_state * hk * t)?;
3869 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
3873 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
3874 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
3875 Some(qb)
3876 } else {
3877 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
3878 None
3879 };
3880 let mut k_l2 = e.uninit(d_state * hk * t)?;
3881 let kb16 = if Engine::l2_v2_on(d_state) {
3883 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
3884 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
3885 Some(kb)
3886 } else {
3887 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
3888 None
3889 };
3890 let mut beta = e.uninit(t * num_v)?;
3891 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
3892 let mut g_log = e.uninit(t * num_v)?;
3893 e.gdn_glog_v(
3894 alpha,
3895 la.ssm_dt.float_data(),
3896 la.ssm_a.float_data(),
3897 &mut g_log,
3898 num_v,
3899 t,
3900 )?;
3901 if let Some(len_d) = pad_len {
3902 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
3903 }
3904 Ok(GdnPrep {
3905 hk,
3906 q_l2,
3907 k_l2,
3908 v_g,
3909 beta,
3910 g_log,
3911 kb16,
3912 qb16,
3913 })
3914 }
3915
3916 #[allow(clippy::too_many_arguments)]
3921 fn linear_attn_prime_core_batch(
3922 &self,
3923 e: &Engine,
3924 la: &LinearAttnLayer,
3925 g4: &[CudaSlice<f32>],
3926 offs: &[usize],
3927 ts: &[usize],
3928 caches: &mut [&mut Cache],
3929 il: usize,
3930 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
3931 let geometry = la.geometry;
3932 let d_state = geometry.key_head_dim as usize;
3933 let num_k = geometry.key_heads as usize;
3934 let num_v = geometry.value_heads as usize;
3935 let d_conv = geometry.conv_kernel as usize;
3936 let key_dim = d_state * num_k;
3937 let value_dim = geometry.value_head_dim as usize * num_v;
3938 let conv_dim = key_dim * 2 + value_dim;
3939 let eps = self.cfg.rms_eps;
3940 let scale = 1.0 / (d_state as f32).sqrt();
3941 let b = ts.len();
3942 let c = Engine::gdn_chunk_size();
3943 let carried = caches.iter().any(|c| c.pos > 0);
3946 let use_vl = !carried
3947 && (2..=8).contains(&b)
3948 && Engine::gdn_chunked_enabled()
3949 && ts.iter().all(|&t| t >= 16)
3950 && e.gdn_mma_enabled(c)
3951 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
3952 if !use_vl {
3953 return (0..b)
3954 .map(|s| {
3955 let (o, t) = (offs[s], ts[s]);
3956 self.linear_attn_prime_core_pad_view(
3957 e,
3958 la,
3959 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
3960 &g4[1].slice(o * value_dim..(o + t) * value_dim),
3961 &g4[2].slice(o * num_v..(o + t) * num_v),
3962 &g4[3].slice(o * num_v..(o + t) * num_v),
3963 t,
3964 caches[s],
3965 il,
3966 None,
3967 )
3968 })
3969 .collect();
3970 }
3971 struct SeqBufs {
3975 conv_out: CudaSlice<f32>,
3976 q_g: CudaSlice<f32>,
3977 k_g: CudaSlice<f32>,
3978 v_g: CudaSlice<f32>,
3979 q_l2: CudaSlice<f32>,
3980 k_l2: CudaSlice<f32>,
3981 beta: CudaSlice<f32>,
3982 g_log: CudaSlice<f32>,
3983 gn: CudaSlice<f32>,
3984 gn16: CudaSlice<u8>,
3985 }
3986 let f16o = Self::f16out_on(e, 16);
3987 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
3989 let mut pres = Vec::with_capacity(b);
3990 for &t in ts.iter().take(b) {
3991 sb.push(SeqBufs {
3992 conv_out: e.uninit(conv_dim * t)?,
3993 q_g: e.uninit(d_state * hk * t)?,
3994 k_g: e.uninit(d_state * hk * t)?,
3995 v_g: e.uninit(d_state * num_v * t)?,
3996 q_l2: e.uninit(d_state * hk * t)?,
3997 k_l2: e.uninit(d_state * hk * t)?,
3998 beta: e.uninit(t * num_v)?,
3999 g_log: e.uninit(t * num_v)?,
4000 gn: e.uninit(d_state * num_v * t)?,
4001 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
4002 });
4003 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
4004 }
4005 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
4006 .map(|s| {
4007 let (o, t) = (offs[s], ts[s]);
4008 let rl = caches[s].recur[il].as_ref().unwrap();
4009 crate::GdnPrepVl {
4010 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
4011 conv_state: e.addr_f32(&rl.conv_state),
4012 conv_out: e.addr_f32(&sb[s].conv_out),
4013 q_g: e.addr_f32(&sb[s].q_g),
4014 k_g: e.addr_f32(&sb[s].k_g),
4015 v_g: e.addr_f32(&sb[s].v_g),
4016 q_l2: e.addr_f32(&sb[s].q_l2),
4017 k_l2: e.addr_f32(&sb[s].k_l2),
4018 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
4019 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
4020 beta: e.addr_f32(&sb[s].beta),
4021 g_log: e.addr_f32(&sb[s].g_log),
4022 o: e.addr_f32(&pres[s].o),
4023 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
4024 gn: e.addr_f32(&sb[s].gn),
4025 gn16: e.addr_u8(&sb[s].gn16),
4026 kb16: if Engine::l2_v2_on(d_state) {
4027 e.addr_u8(&pres[s].kb16)
4028 } else {
4029 0
4030 },
4031 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
4032 e.addr_u8(&pres[s].qb16)
4033 } else {
4034 0
4035 },
4036 t: t as i32,
4037 pad: 0,
4038 }
4039 })
4040 .collect();
4041 let args: Vec<crate::GdnSeqVl> = (0..b)
4042 .map(|s| {
4043 let rl = caches[s].recur[il].as_ref().unwrap();
4044 crate::GdnSeqVl {
4045 kb16: e.addr_u8(&pres[s].kb16),
4046 gcum: e.addr_f32(&pres[s].gcum),
4047 beta: e.addr_f32(&sb[s].beta),
4048 u: e.addr_f32(&pres[s].u),
4049 wb16: e.addr_u8(&pres[s].wb16),
4050 y: e.addr_u8(&pres[s].y16),
4051 ssnap: e.addr_u8(&pres[s].ssnap16),
4052 state_in: e.addr_f32(&rl.ssm_state),
4053 state_out: e.addr_f32(&rl.ssm_state_alt),
4054 q: e.addr_f32(&sb[s].q_l2),
4055 p: e.addr_f32(&pres[s].p),
4056 o: e.addr_f32(&pres[s].o),
4057 k: e.addr_f32(&sb[s].k_l2),
4058 v: e.addr_f32(&sb[s].v_g),
4059 g: e.addr_f32(&sb[s].g_log),
4060 a: e.addr_f32(&pres[s].a),
4061 w: e.addr_f32(&pres[s].w),
4062 t: ts[s] as i32,
4063 nc: pres[s].nc as i32,
4064 }
4065 })
4066 .collect();
4067 e.gdn_prep_vl8(
4068 &prep_args,
4069 la.ssm_conv1d.float_data(),
4070 la.ssm_dt.float_data(),
4071 la.ssm_a.float_data(),
4072 conv_dim,
4073 d_conv,
4074 d_state,
4075 num_v,
4076 num_k,
4077 key_dim,
4078 hk,
4079 eps,
4080 )?;
4081 if !Engine::l2_v2_on(d_state) {
4084 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
4085 }
4086 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
4088 if !Engine::l2_v2_on(d_state) {
4090 for s in 0..b {
4091 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
4092 }
4093 }
4094 let mut wa = [crate::GdnWVl::default(); 8];
4095 for s in 0..b {
4096 wa[s] = crate::GdnWVl {
4097 qb16: e.addr_u8(&pres[s].qb16),
4098 pb16: e.addr_u8(&pres[s].pb16),
4099 };
4100 }
4101 Some(crate::GdnWVl8(wa))
4102 } else {
4103 None
4104 };
4105 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
4106 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
4107 if f16o {
4108 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
4109 }
4110 let mut out = Vec::with_capacity(b);
4112 for (s, bufs) in sb.into_iter().enumerate() {
4113 let rl = caches[s].recur[il].as_mut().unwrap();
4114 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4115 let (o, t) = (offs[s], ts[s]);
4116 let SeqBufs { mut gn, gn16, .. } = bufs;
4117 if f16o {
4118 out.push((gn, Some(gn16)));
4119 } else {
4120 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
4121 e.gated_rmsnorm_zv(
4122 &pres[s].o,
4123 la.ssm_norm.float_data(),
4124 &z_v,
4125 &mut gn,
4126 d_state,
4127 num_v * t,
4128 eps,
4129 )?;
4130 out.push((gn, None));
4131 }
4132 }
4133 Ok(out)
4134 }
4135
4136 #[allow(clippy::too_many_arguments)]
4140 fn linear_attn_prime_core_pad_view(
4141 &self,
4142 e: &Engine,
4143 la: &LinearAttnLayer,
4144 qkv_mixed: &cudarc::driver::CudaView<f32>,
4145 z: &cudarc::driver::CudaView<f32>,
4146 beta_raw: &cudarc::driver::CudaView<f32>,
4147 alpha: &cudarc::driver::CudaView<f32>,
4148 t: usize,
4149 cache: &mut Cache,
4150 il: usize,
4151 pad_len: Option<&CudaSlice<i32>>,
4152 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4153 let cfg = &self.cfg;
4154 let geometry = la.geometry;
4155 let d_state = geometry.key_head_dim as usize;
4156 let num_v = geometry.value_heads as usize;
4157 let eps = cfg.rms_eps;
4158 let scale = 1.0 / (d_state as f32).sqrt();
4159
4160 let prep =
4161 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
4162
4163 let mut o = e.uninit(d_state * num_v * t)?;
4169 let rl = cache.recur[il].as_mut().unwrap();
4170 {
4171 let crate::cache::RecurLayer {
4172 ssm_state,
4173 ssm_state_alt,
4174 ..
4175 } = rl;
4176 e.gdn_scan_prefill(
4177 &prep.q_l2,
4178 &prep.k_l2,
4179 &prep.v_g,
4180 &prep.g_log,
4181 &prep.beta,
4182 prep.kb16.as_ref(),
4183 prep.qb16.as_ref(),
4184 ssm_state,
4185 ssm_state_alt,
4186 &mut o,
4187 num_v,
4188 t,
4189 scale,
4190 prep.hk,
4191 )?;
4192 }
4193 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4194
4195 let mut gn = e.uninit(d_state * num_v * t)?;
4198 let gn16 = if Self::f16out_on(e, t) {
4199 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
4200 e.gated_rmsnorm_f16out_zv(
4201 &o,
4202 la.ssm_norm.float_data(),
4203 z,
4204 &mut gn,
4205 &mut g16,
4206 d_state,
4207 num_v * t,
4208 eps,
4209 )?;
4210 Some(g16)
4211 } else {
4212 e.gated_rmsnorm_zv(
4213 &o,
4214 la.ssm_norm.float_data(),
4215 z,
4216 &mut gn,
4217 d_state,
4218 num_v * t,
4219 eps,
4220 )?;
4221 None
4222 };
4223 Ok((gn, gn16))
4224 }
4225
4226 #[allow(clippy::too_many_arguments)]
4228 fn linear_attn_prime_core_pad(
4229 &self,
4230 e: &Engine,
4231 la: &LinearAttnLayer,
4232 g4: Vec<CudaSlice<f32>>,
4233 t: usize,
4234 cache: &mut Cache,
4235 il: usize,
4236 pad_len: Option<&CudaSlice<i32>>,
4237 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4238 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
4239 if let Some(xh) = &gn16 {
4240 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
4241 return Ok(y);
4242 }
4243 }
4244 Ok(e.matmul(&la.ssm_out, &gn, t)?)
4245 }
4246
4247 pub fn full_attn(
4252 &self,
4253 e: &Engine,
4254 fa: &FullAttnLayer,
4255 h: &CudaSlice<f32>,
4256 pos_d: &CudaSlice<i32>,
4257 t: usize,
4258 il: usize,
4259 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4260 if self.uses_sliding_gated_moe_program() {
4261 return self.step35_attn(e, fa, h, pos_d, t, il);
4262 }
4263 let cfg = &self.cfg;
4264 let _n_embd = cfg.n_embd as usize;
4265 let geometry = cfg.full_attention_geometry_at(il as u32);
4266 let n_head = geometry.n_head as usize;
4267 let n_head_kv = geometry.n_head_kv as usize;
4268 let head_dim = geometry.head_dim_k as usize;
4269 let eps = cfg.rms_eps;
4270 let scale = geometry.attention_scale();
4271
4272 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
4275 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
4277 let v = g3.pop().unwrap();
4278 let mut k = g3.pop().unwrap();
4279 let qf = g3.pop().unwrap();
4280 let (mut q, gate) = if gated {
4281 let mut q = e.uninit(t * n_head * head_dim)?;
4282 let mut gate = e.uninit(t * n_head * head_dim)?;
4283 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
4284 (q, Some(gate))
4285 } else {
4286 (qf, None)
4287 };
4288
4289 let mut qn = e.uninit(t * n_head * head_dim)?;
4291 e.rms_norm(
4292 &q,
4293 fa.q_norm.float_data(),
4294 &mut qn,
4295 head_dim,
4296 n_head * t,
4297 eps,
4298 )?;
4299 q = qn;
4300 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
4301 e.rms_norm(
4302 &k,
4303 fa.k_norm.float_data(),
4304 &mut kn,
4305 head_dim,
4306 n_head_kv * t,
4307 eps,
4308 )?;
4309 k = kn;
4310 let rope_dims = geometry.n_rot as usize;
4311 e.rope_neox(
4312 &mut q,
4313 pos_d,
4314 head_dim,
4315 rope_dims,
4316 n_head,
4317 t,
4318 geometry.rope_base,
4319 1.0,
4320 )?;
4321 e.rope_neox(
4322 &mut k,
4323 pos_d,
4324 head_dim,
4325 rope_dims,
4326 n_head_kv,
4327 t,
4328 geometry.rope_base,
4329 1.0,
4330 )?;
4331
4332 let mut attn = e.uninit(t * n_head * head_dim)?;
4334 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4337 e.sdpa_naive(
4339 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4340 )?;
4341 } else {
4342 e.fa_prefill(
4343 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4344 )?;
4345 }
4346
4347 let attn_g = match &gate {
4349 Some(gate) => {
4350 let mut gsig = e.uninit(t * n_head * head_dim)?;
4351 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
4352 let mut ag = e.uninit(t * n_head * head_dim)?;
4353 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
4354 ag
4355 }
4356 None => attn,
4357 };
4358
4359 let o = e.matmul(&fa.wo, &attn_g, t)?;
4361 Ok(o)
4362 }
4363
4364 pub fn linear_attn(
4366 &self,
4367 e: &Engine,
4368 la: &LinearAttnLayer,
4369 h: &CudaSlice<f32>,
4370 t: usize,
4371 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4372 let cfg = &self.cfg;
4373 let _n_embd = cfg.n_embd as usize;
4374 let geometry = la.geometry;
4375 let d_state = geometry.key_head_dim as usize;
4376 let num_k = geometry.key_heads as usize;
4377 let num_v = geometry.value_heads as usize;
4378 let d_conv = geometry.conv_kernel as usize;
4379 let head_k = d_state;
4380 let head_v = geometry.value_head_dim as usize;
4381 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;
4385 let scale = 1.0 / (d_state as f32).sqrt();
4386
4387 let mut g4 = e.matmul_group(
4390 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
4391 h,
4392 t,
4393 )?;
4394 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);
4406 let mut q_g = e.uninit(d_state * num_v * t)?;
4407 let mut k_g = e.uninit(d_state * num_v * t)?;
4408 let mut v_g = e.uninit(d_state * num_v * t)?;
4409 e.ssm_conv1d_gdn(
4410 &qkv_mixed,
4411 la.ssm_conv1d.float_data(),
4412 &mut q_g,
4413 &mut k_g,
4414 &mut v_g,
4415 conv_dim,
4416 t,
4417 d_conv,
4418 d_state,
4419 num_v,
4420 num_k,
4421 key_dim,
4422 )?;
4423 let mut q_l2 = e.uninit(d_state * num_v * t)?;
4425 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
4426 let mut k_l2 = e.uninit(d_state * num_v * t)?;
4427 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
4428 let v_gd = v_g;
4429
4430 let mut beta = e.uninit(t * num_v)?;
4433 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
4434 let mut g_log = e.uninit(t * num_v)?;
4436 e.gdn_glog(
4437 &alpha,
4438 la.ssm_dt.float_data(),
4439 la.ssm_a.float_data(),
4440 &mut g_log,
4441 num_v,
4442 t,
4443 )?;
4444
4445 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
4448 let mut o = e.uninit(d_state * num_v * t)?;
4449 e.gdn_scan_prefill(
4450 &q_l2,
4451 &k_l2,
4452 &v_gd,
4453 &g_log,
4454 &beta,
4455 None,
4456 None,
4457 &state_in,
4458 &mut state_out,
4459 &mut o,
4460 num_v,
4461 t,
4462 scale,
4463 num_v,
4464 )?;
4465
4466 let mut gn = e.uninit(d_state * num_v * t)?;
4471 e.gated_rmsnorm(
4472 &o,
4473 la.ssm_norm.float_data(),
4474 &z,
4475 &mut gn,
4476 d_state,
4477 num_v * t,
4478 eps,
4479 )?;
4480
4481 let out = e.matmul(&la.ssm_out, &gn, t)?;
4485 Ok(out)
4486 }
4487}
4488
4489impl HybridModel {
4490 pub fn moe_ffn_il(
4501 &self,
4502 e: &Engine,
4503 m: &MoeWeights,
4504 z: &CudaSlice<f32>,
4505 t: usize,
4506 il: u16,
4507 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4508 Self::moe_ffn_inner(
4509 e,
4510 m,
4511 z,
4512 None,
4513 t,
4514 &self.cfg,
4515 il,
4516 self.max_moe_block(),
4517 false,
4518 None,
4519 self.uses_sliding_gated_moe_program(),
4520 )
4521 }
4522
4523 pub fn moe_ffn_il_prefill(
4526 &self,
4527 e: &Engine,
4528 m: &MoeWeights,
4529 z: &CudaSlice<f32>,
4530 t: usize,
4531 il: u16,
4532 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4533 Self::moe_ffn_inner(
4534 e,
4535 m,
4536 z,
4537 None,
4538 t,
4539 &self.cfg,
4540 il,
4541 self.max_moe_block(),
4542 true,
4543 Some(&self.step_grouped_prefill),
4544 self.uses_sliding_gated_moe_program(),
4545 )
4546 }
4547
4548 pub fn moe_ffn_il_zq8(
4552 &self,
4553 e: &Engine,
4554 m: &MoeWeights,
4555 z: &CudaSlice<f32>,
4556 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4557 t: usize,
4558 il: u16,
4559 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4560 Self::moe_ffn_inner(
4561 e,
4562 m,
4563 z,
4564 zq8,
4565 t,
4566 &self.cfg,
4567 il,
4568 self.max_moe_block(),
4569 false,
4570 None,
4571 self.uses_sliding_gated_moe_program(),
4572 )
4573 }
4574
4575 pub(crate) fn moe_ffn(
4583 e: &Engine,
4584 m: &MoeWeights,
4585 z: &CudaSlice<f32>,
4586 t: usize,
4587 cfg: &ModelConfig,
4588 il: u16,
4589 max_block: usize,
4590 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4591 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false, None, false)
4592 }
4593
4594 #[allow(clippy::too_many_arguments)]
4595 pub(crate) fn moe_ffn_inner(
4596 e: &Engine,
4597 m: &MoeWeights,
4598 z: &CudaSlice<f32>,
4599 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4600 t: usize,
4601 cfg: &ModelConfig,
4602 il: u16,
4603 max_block: usize,
4604 prefill: bool,
4605 grouped_prefill: Option<&std::sync::Mutex<crate::hybrid::StepEpGroupedPrefill>>,
4606 sliding_gated_moe: bool,
4607 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4608 let worker_io = crate::spill_pread::worker_enabled();
4609 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
4610 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
4611 e.with_moe_cache(max_block, |cache, _| {
4612 cache.begin_forward_epoch(il, t);
4613 if worker_io {
4614 cache.begin_worker_scope();
4615 }
4616 Ok(())
4617 })?;
4618 }
4619 if m.step_ep.is_some() || m.step_tp.is_some() {
4620 let moe = cfg
4621 .moe
4622 .as_ref()
4623 .ok_or("Step distributed execution requires MoE model metadata")?;
4624 let n_embd = cfg.n_embd as usize;
4625 let n_expert = moe.expert_count as usize;
4626 let n_used = moe.expert_used_count as usize;
4627 let sigmoid = cfg
4628 .sigmoid_router()
4629 .ok_or("Step distributed execution requires the Step sigmoid router")?;
4630 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4631 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
4632 let grouped_prefill_requested = prefill && step_ep_grouped_prefill_enabled()?;
4633 if grouped_prefill_requested && !step_tp_prefill_enabled()? {
4634 return Err(
4635 "MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into(),
4636 );
4637 }
4638 if grouped_prefill_requested && !step_grouped_prefill_shape(true, prefill, t) {
4639 return Err(format!(
4640 "Step grouped prefill tokens {t} are outside the qualified {}..={} range",
4641 PRIME_MIN_T,
4642 crate::cache::PRIME_CHUNK_MAX_TOKENS,
4643 )
4644 .into());
4645 }
4646 let grouped_decode_shape = step_grouped_decode_shape(prefill, t);
4647 let grouped_prefill_shape =
4648 step_grouped_prefill_shape(grouped_prefill_requested, prefill, t);
4649 if let Some(ep) = m.step_ep.as_ref().filter(|ep| {
4650 ep.grouped_decode.is_some() && (grouped_decode_shape || grouped_prefill_shape)
4651 }) {
4652 let (selected, route_weights) =
4653 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sigmoid)?;
4654 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
4655 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
4656 Self::trace_moe_input(e, il, t, n_embd, z)?;
4657 let selected = selected
4658 .iter()
4659 .map(|&expert| expert as usize)
4660 .collect::<Vec<_>>();
4661
4662 e.stream().synchronize()?;
4665 let execute = |state: &mut crate::hybrid::StepEpGroupedDecode| {
4666 state.projection.set_activation_limit(ep.activation_limit)?;
4667 ep.runtime
4668 .refresh_step_grouped_expert_parallel_gate_from_root_device(
4669 ep.experts.e4m3()?,
4670 &mut state.projection,
4671 z,
4672 t,
4673 &selected,
4674 )?;
4675 ep.runtime.refresh_step_grouped_expert_parallel_combine(
4676 &state.projection,
4677 &mut state.combine,
4678 &route_weights,
4679 )?;
4680 ep.runtime.execute_step_grouped_expert_parallel_gate(
4681 ep.experts.e4m3()?,
4682 &mut state.projection,
4683 )?;
4684 ep.runtime.execute_step_grouped_expert_parallel_combine(
4685 &state.projection,
4686 &mut state.combine,
4687 )?;
4688 let mut output = ep.runtime.copy_step_grouped_expert_parallel_combine_root(
4689 &state.projection,
4690 &state.combine,
4691 e,
4692 )?;
4693 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
4694 if prefill {
4695 e.stream().synchronize()?;
4698 }
4699 eprintln!(
4700 "[step-tp-ep-grouped] execute layer={il} tokens={t} devices={:?} \
4701 attention_layout=tensor-parallel expert_layout=expert-parallel \
4702 expert_transport={} native_p2p=true route_control=host-narrow \
4703 input=root-device projection_workspaces=persistent \
4704 combine=root-device output=owning-stage-device \
4705 prefill={prefill} batched_decode=false capacity={} \
4706 performance_claim=false",
4707 ep.devices,
4708 ep.runtime.transport_label(),
4709 state.projection.max_tokens(),
4710 );
4711 Ok::<_, Box<dyn std::error::Error>>(output)
4712 };
4713
4714 if grouped_prefill_shape {
4715 let grouped_prefill = grouped_prefill
4716 .ok_or("Step grouped prefill has no model-scoped executor")?;
4717 let mut shared = grouped_prefill
4718 .lock()
4719 .map_err(|_| "Step grouped prefill state lock is poisoned")?;
4720 let needs_prepare = shared.state.as_ref().is_none_or(|state| {
4721 state.devices != ep.devices
4722 || state.grouped.projection.max_tokens() < t
4723 || state.grouped.projection.input_width() != n_embd
4724 || state.grouped.projection.expert_width()
4725 != moe.expert_ff_length as usize
4726 });
4727 if needs_prepare {
4728 let seed_input = vec![0.0f32; n_embd];
4729 let seed_selected = &selected[..n_used];
4730 let seed_weights = &route_weights[..n_used];
4731 let projection = ep
4732 .runtime
4733 .prepare_step_grouped_expert_parallel_gate_with_capacity(
4734 ep.experts.e4m3()?,
4735 &seed_input,
4736 1,
4737 seed_selected,
4738 ep.activation_limit,
4739 t,
4740 )?;
4741 let combine = ep.runtime.prepare_step_grouped_expert_parallel_combine(
4742 &projection,
4743 seed_weights,
4744 )?;
4745 shared.state = Some(crate::hybrid::StepEpGroupedPrefillState {
4746 devices: ep.devices.clone(),
4747 grouped: crate::hybrid::StepEpGroupedDecode {
4748 projection,
4749 combine,
4750 },
4751 });
4752 eprintln!(
4753 "[step-tp-ep-grouped-prefill] prepare capacity={t} devices={:?} \
4754 shared_across_layers=true performance_claim=false",
4755 ep.devices,
4756 );
4757 }
4758 return execute(
4759 &mut shared
4760 .state
4761 .as_mut()
4762 .expect("Step grouped prefill state prepared above")
4763 .grouped,
4764 );
4765 }
4766
4767 let mut grouped = ep
4768 .grouped_decode
4769 .as_ref()
4770 .expect("grouped decode presence checked above")
4771 .lock()
4772 .map_err(|_| "Step grouped decode state lock is poisoned")?;
4773 return execute(&mut grouped);
4774 }
4775 if grouped_prefill_shape {
4776 return Err(
4777 "Step grouped prefill requires native-P2P expert-owner device arithmetic"
4778 .into(),
4779 );
4780 }
4781 if t == 1
4786 && crate::tp::step_nvfp4_dev_routes_enabled()?
4787 && crate::tp::step_tp_dev_router_enabled()?
4788 {
4789 if let Some(tp) = &m.step_tp {
4790 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
4791 let (sf, route_norm) = sigmoid;
4792 static D1_ROUTER: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4799 let d1_router = *D1_ROUTER.get_or_init(|| {
4800 std::env::var("MEMRA_DEV1_ROUTER").as_deref() == Ok("1")
4801 });
4802 if d1_router {
4803 let (sf_h, rn_h) = sigmoid;
4804 let n_ex = m.gate_exps.n_expert;
4805 let act_ct = m.active_count();
4806 let _ = tp.runtime.nvfp4_routes_prestage_with(
4807 bank,
4808 e,
4809 z,
4810 |rank1, in1, sel1, w1| {
4811 let mut guard = DEV1_ROUTER_REPS
4812 .lock()
4813 .map_err(|_| "dev1 router replica lock")?;
4814 let (reps, scratch) =
4815 guard.get_or_insert_with(|| (Default::default(), None));
4816 if !reps.contains_key(&il) {
4817 use cudarc::driver::DevicePtr;
4818 let (g1, p1, a1) = (
4819 rank1.htod(&vec![0.0f32; n_ex * n_embd])?,
4820 rank1.htod(&vec![0.0f32; n_ex])?,
4821 rank1.alloc_u8_uninit(n_ex)?,
4822 );
4823 for (src, dst_len, dst) in [
4824 (
4825 {
4826 let s = e.stream();
4827 let (p, _g) =
4828 m.gate_inp.float_data().device_ptr(&s);
4829 p as u64
4830 },
4831 n_ex * n_embd * 4,
4832 {
4833 let s = rank1.stream();
4834 let (p, _g) = g1.device_ptr(&s);
4835 p as u64
4836 },
4837 ),
4838 (
4839 {
4840 let s = e.stream();
4841 let (p, _g) = m.exp_probs_b_dev.device_ptr(&s);
4842 p as u64
4843 },
4844 n_ex * 4,
4845 {
4846 let s = rank1.stream();
4847 let (p, _g) = p1.device_ptr(&s);
4848 p as u64
4849 },
4850 ),
4851 (
4852 {
4853 let s = e.stream();
4854 let (p, _g) =
4855 m.active_experts_dev.device_ptr(&s);
4856 p as u64
4857 },
4858 n_ex,
4859 {
4860 let s = rank1.stream();
4861 let (p, _g) = a1.device_ptr(&s);
4862 p as u64
4863 },
4864 ),
4865 ] {
4866 crate::tp::raw_copy_bytes(dst, src, dst_len, rank1)?;
4867 }
4868 rank1.stream().synchronize()?;
4869 reps.insert(il, (g1, p1, a1));
4870 }
4871 if scratch.is_none() {
4872 *scratch = Some(rank1.htod(&vec![0.0f32; n_ex])?);
4873 }
4874 let (g1, p1, a1) = reps.get(&il).expect("armed above");
4875 let logits1 = scratch.as_mut().expect("armed above");
4876 rank1.router_gemv_into(g1, in1, logits1, n_embd, n_ex, 1)?;
4877 rank1.moe_router_sigmoid_topk_into(
4878 logits1, 1, n_ex, n_used, act_ct, p1, a1, sf_h, rn_h, sel1,
4879 w1,
4880 )?;
4881 Ok(true)
4882 },
4883 )?;
4884 } else {
4885 let _ = tp.runtime.nvfp4_routes_prestage(bank, e, z)?;
4886 }
4887 static SELW: std::sync::Mutex<
4891 Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>,
4892 > = std::sync::Mutex::new(None);
4893 let mut selw = SELW.lock().map_err(|_| "selw lock poisoned")?;
4894 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
4895 *selw = Some((
4896 e.ctx().ordinal(),
4897 e.htod_i32(&vec![0i32; n_used])?,
4898 e.htod(&vec![0.0f32; n_used])?,
4899 ));
4900 }
4901 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
4902 e.moe_router_sigmoid_topk_into(
4903 &logits,
4904 t,
4905 n_expert,
4906 n_used,
4907 m.active_count(),
4908 &m.exp_probs_b_dev,
4909 &m.active_experts_dev,
4910 sf,
4911 route_norm,
4912 sel_d,
4913 w_d,
4914 )?;
4915 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
4916 static SHEXP_OV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4920 let shexp_ov = *SHEXP_OV.get_or_init(|| {
4921 std::env::var("MEMRA_SHEXP_OVERLAP").as_deref() == Ok("1")
4922 });
4923 static SHEXP_D1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4927 let shexp_d1 = *SHEXP_D1.get_or_init(|| {
4928 std::env::var("MEMRA_SHEXP_DEV1").as_deref() == Ok("1")
4929 }) && tp.runtime.rank_engine(1).is_some();
4930 static TAIL3: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4934 let tail3 = *TAIL3
4935 .get_or_init(|| std::env::var("MEMRA_TAIL_ADD3").as_deref() != Ok("0"));
4936 let mut ov_issued = false;
4937 let mut d1_issued = false;
4938 let mut tail_folded = false;
4939 let mut output = if shexp_d1 {
4940 let rank1 = tp.runtime.rank_engine(1).expect("checked above");
4941 tp.runtime
4942 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
4943 bank,
4944 e,
4945 z,
4946 &sel_d,
4947 &w_d,
4948 n_used,
4949 tp.activation_limit,
4950 || {
4951 d1_issued = Self::shexp_dev1_issue(
4952 e, rank1, m, z, cfg, il, n_embd,
4953 )?;
4954 Ok(())
4955 },
4956 )?
4957 } else if shexp_ov {
4958 let post_add = if tail3 {
4963 Self::shexp_overlap_tail_ptrs(e, m, cfg, n_embd)?
4964 } else {
4965 None
4966 };
4967 let used_post = post_add.is_some();
4968 let out = tp
4969 .runtime
4970 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
4971 bank,
4972 e,
4973 z,
4974 &sel_d,
4975 &w_d,
4976 n_used,
4977 tp.activation_limit,
4978 || {
4979 ov_issued =
4980 Self::shexp_overlap_issue(e, m, z, cfg, il, n_embd)?;
4981 Ok(())
4982 },
4983 post_add,
4984 )?;
4985 if used_post && ov_issued {
4990 tail_folded = true; }
4992 out
4993 } else {
4994 tp.runtime.run_tensor_parallel_routes_nvfp4_device_routed(
4995 bank,
4996 e,
4997 z,
4998 &sel_d,
4999 &w_d,
5000 n_used,
5001 tp.activation_limit,
5002 )?
5003 };
5004 if output.len() != t * n_embd {
5005 return Err(format!(
5006 "Step tp routed output has {} values, expected {t}x{n_embd}",
5007 output.len()
5008 )
5009 .into());
5010 }
5011 if tail_folded {
5012 } else if d1_issued {
5014 Self::shexp_dev1_apply(e, &mut output, n_embd)?;
5015 } else if ov_issued {
5016 Self::shexp_overlap_apply(e, &mut output, n_embd)?;
5017 } else {
5018 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5019 }
5020 static DR_LOGGED: std::sync::atomic::AtomicU64 =
5021 std::sync::atomic::AtomicU64::new(0);
5022 let layer_bit = 1u64 << (il as u64 % 64);
5023 if DR_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5024 & layer_bit
5025 == 0
5026 {
5027 eprintln!(
5028 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5029 expert_transport={} native_p2p={} router=device \
5030 activation=host-canonical accumulation=host-canonical \
5031 output=e-device io=device performance_claim=false \
5032 (logged once per layer)",
5033 tp.devices,
5034 tp.runtime.transport_label(),
5035 tp.runtime.native_p2p(),
5036 );
5037 }
5038 return Ok(output);
5039 }
5040 }
5041 }
5042 static ROUTE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5046 static ROUTE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5047 let route_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
5048 let route_started = route_timing.then(std::time::Instant::now);
5049 let (selected, route_weights, input) = Self::moe_route_sigmoid_with_input(
5050 e,
5051 &logits,
5052 z,
5053 t,
5054 n_embd,
5055 n_expert,
5056 n_used,
5057 m.exp_probs_b.as_deref(),
5058 sigmoid,
5059 m.active_experts.as_deref(),
5060 )?;
5061 if let Some(started) = route_started {
5062 use std::sync::atomic::Ordering;
5063 let ns = ROUTE_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
5064 + started.elapsed().as_nanos() as u64;
5065 let calls = ROUTE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
5066 if calls % 430 == 0 {
5067 eprintln!(
5068 "[moe-route-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
5069 ns as f64 / 1.0e6,
5070 ns as f64 / calls as f64 / 1.0e3,
5071 );
5072 }
5073 }
5074 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5075 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5076 Self::trace_moe_input(e, il, t, n_embd, z)?;
5077 let selected = selected
5078 .iter()
5079 .map(|&expert| expert as usize)
5080 .collect::<Vec<_>>();
5081 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5086 if let Some(tp) = &m.step_tp {
5087 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5088 let mut output = tp.runtime.run_tensor_parallel_routes_nvfp4_device_io(
5089 bank,
5090 e,
5091 z,
5092 &selected,
5093 &route_weights,
5094 n_used,
5095 tp.activation_limit,
5096 )?;
5097 if output.len() != t * n_embd {
5098 return Err(format!(
5099 "Step tp routed output has {} values, expected {t}x{n_embd}",
5100 output.len()
5101 )
5102 .into());
5103 }
5104 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5105 static IO_LOGGED: std::sync::atomic::AtomicU64 =
5106 std::sync::atomic::AtomicU64::new(0);
5107 let layer_bit = 1u64 << (il as u64 % 64);
5108 if IO_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5109 & layer_bit
5110 == 0
5111 {
5112 eprintln!(
5113 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5114 expert_transport={} native_p2p={} activation=host-canonical \
5115 accumulation=host-canonical output=e-device io=device \
5116 performance_claim=false (logged once per layer)",
5117 tp.devices,
5118 tp.runtime.transport_label(),
5119 tp.runtime.native_p2p(),
5120 );
5121 }
5122 return Ok(output);
5123 }
5124 }
5125 }
5126 let (routed, mode, devices, transport, native_p2p) = if let Some(tp) = &m.step_tp {
5127 (
5128 match &tp.experts {
5129 crate::hybrid::StepTpExpertBank::E4m3(bank) => {
5130 tp.runtime.run_tensor_parallel_routes(
5131 bank,
5132 &input,
5133 t,
5134 &selected,
5135 &route_weights,
5136 n_used,
5137 )?
5138 }
5139 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => {
5140 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5141 tp.runtime.run_tensor_parallel_routes_nvfp4_device(
5142 bank,
5143 &input,
5144 &selected,
5145 &route_weights,
5146 n_used,
5147 tp.activation_limit,
5148 )?
5149 } else {
5150 tp.runtime.run_tensor_parallel_routes_nvfp4(
5151 bank,
5152 &input,
5153 t,
5154 &selected,
5155 &route_weights,
5156 n_used,
5157 tp.activation_limit,
5158 )?
5159 }
5160 }
5161 },
5162 "tp",
5163 &tp.devices,
5164 tp.runtime.transport_label(),
5165 tp.runtime.native_p2p(),
5166 )
5167 } else {
5168 let ep = m
5169 .step_ep
5170 .as_ref()
5171 .ok_or("Step distributed runtime has no EP or TP state")?;
5172 (
5173 match &ep.experts {
5174 crate::hybrid::StepEpExpertBank::E4m3(bank) => {
5175 ep.runtime.run_routed_experts(
5176 bank,
5177 &input,
5178 t,
5179 &selected,
5180 &route_weights,
5181 n_used,
5182 ep.activation_limit,
5183 )?
5184 }
5185 crate::hybrid::StepEpExpertBank::Nvfp4(bank) => {
5186 ep.runtime.run_routed_experts_nvfp4(
5187 bank,
5188 &input,
5189 t,
5190 &selected,
5191 &route_weights,
5192 n_used,
5193 ep.activation_limit,
5194 )?
5195 }
5196 },
5197 if ep.configured_by_tp { "tp-ep" } else { "ep" },
5198 &ep.devices,
5199 ep.runtime.transport_label(),
5200 ep.runtime.native_p2p(),
5201 )
5202 };
5203 if routed.len() != t * n_embd {
5204 return Err(format!(
5205 "Step {mode} routed output has {} values, expected {t}x{n_embd}",
5206 routed.len()
5207 )
5208 .into());
5209 }
5210 let mut output = e.htod(&routed)?;
5211 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5212 static STEP_LOGGED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5215 let layer_bit = 1u64 << (il as u64 % 64);
5216 if STEP_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed) & layer_bit
5217 == 0
5218 {
5219 eprintln!(
5220 "[step-{mode}] execute layer={il} tokens={t} devices={devices:?} \
5221 expert_transport={transport} native_p2p={native_p2p} \
5222 activation={} accumulation={} output={} \
5223 performance_claim=false (logged once per layer)",
5224 if let Some(ep) = &m.step_ep {
5225 ep.runtime.expert_activation_label()
5226 } else {
5227 "host-canonical"
5228 },
5229 if let Some(ep) = &m.step_ep {
5230 ep.runtime.expert_accumulation_label()
5231 } else {
5232 "host-canonical"
5233 },
5234 if let Some(ep) = &m.step_ep {
5235 ep.runtime.expert_output_label()
5236 } else {
5237 "host-accumulated"
5238 },
5239 );
5240 if let Some(ep) = &m.step_ep {
5241 if let Some(limit) = ep.activation_limit {
5242 eprintln!(
5243 "[step-ep-clamp] execute layer={il} tokens={t} routed_clamp={limit} \
5244 formula=min-silu-times-clamped-up performance_claim=false"
5245 );
5246 }
5247 }
5248 }
5249 return Ok(output);
5250 }
5251 if Self::sigmoid_resident_dev_eligible(e, m, cfg, sliding_gated_moe) {
5252 let moe = cfg.moe.as_ref().unwrap();
5253 let n_expert = moe.expert_count as usize;
5254 let n_used = moe.expert_used_count as usize;
5255 let sigmoid = cfg.sigmoid_router().unwrap();
5256 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5257 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5258 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
5259 }
5260 if t > 1 && moe_grouped_enabled(cfg, prefill) {
5263 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
5264 if std::env::var("MEMRA_MOE_GATE").is_ok() {
5269 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
5270 let g_host = e.dtoh(&grouped_out)?;
5271 let s_host = e.dtoh(&seq_out)?;
5272 let g_bytes: &[u8] = unsafe {
5273 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
5274 };
5275 let s_bytes: &[u8] = unsafe {
5276 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
5277 };
5278 if g_bytes == s_bytes {
5279 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
5280 } else {
5281 let diffs = g_host
5282 .iter()
5283 .zip(s_host.iter())
5284 .enumerate()
5285 .filter(|(_, (a, b))| a != b)
5286 .count();
5287 let maxdiff = g_host
5288 .iter()
5289 .zip(s_host.iter())
5290 .map(|(a, b)| (a - b).abs())
5291 .fold(0.0f32, f32::max);
5292 panic!(
5293 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
5294 g_host.len()
5295 );
5296 }
5297 }
5298 return Ok(grouped_out);
5299 }
5300 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
5301 }
5302
5303 fn sigmoid_resident_dev_eligible(
5304 e: &Engine,
5305 m: &MoeWeights,
5306 cfg: &ModelConfig,
5307 sliding_gated_moe: bool,
5308 ) -> bool {
5309 let Some(moe) = cfg.moe.as_ref() else {
5310 return false;
5311 };
5312 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5315 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
5316 std::env::var("MEMRA_MOE_STATS").is_ok()
5317 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5318 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5319 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
5320 || std::env::var("MEMRA_MOE_GATE").is_ok()
5321 });
5322 let resident_layout_supported = m.dev_exps.as_ref().is_some_and(|dev| {
5323 if dev.dev != e.ctx().ordinal() {
5324 return false;
5325 }
5326 let q8 = moe_q8_enabled()
5327 && q8_expert_supported(m.gate_exps.qtype)
5328 && q8_expert_supported(m.up_exps.qtype)
5329 && q8_expert_supported(m.down_exps.qtype);
5330 let fp8 = dev.fp8_blk.is_some()
5331 && m.gate_exps.qtype == crate::QT_F8_E4M3_BLK
5332 && m.up_exps.qtype == crate::QT_F8_E4M3_BLK
5333 && m.down_exps.qtype == crate::QT_F8_E4M3_BLK;
5334 q8 || fp8
5335 });
5336 sliding_gated_moe
5337 && sigmoid_router_enabled()
5338 && moe_dev_enabled()
5339 && moe_slab_enabled()
5340 && !observation_mode
5341 && moe.expert_used_count <= 8
5342 && m.has_uniform_expert_layout()
5343 && m.gate_exps.macros.is_none()
5344 && m.up_exps.macros.is_none()
5345 && m.down_exps.macros.is_none()
5346 && !m.has_macros
5347 && resident_layout_supported
5348 }
5349
5350 pub(crate) fn moe_ffn_sequential(
5352 e: &Engine,
5353 m: &MoeWeights,
5354 z: &CudaSlice<f32>,
5355 t: usize,
5356 cfg: &ModelConfig,
5357 il: u16,
5358 max_block: usize,
5359 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5360 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
5361 }
5362
5363 fn moe_router_logits(
5367 e: &Engine,
5368 m: &MoeWeights,
5369 z: &CudaSlice<f32>,
5370 t: usize,
5371 cfg: &ModelConfig,
5372 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5373 if t < PRIME_MIN_T {
5374 if crate::router_kernel_on() {
5376 e.router_gemv(
5377 m.gate_inp.float_data(),
5378 z,
5379 cfg.n_embd as usize,
5380 m.gate_exps.n_expert,
5381 t,
5382 )
5383 } else {
5384 e.matmul_decode_exact(&m.gate_inp, z, t)
5385 }
5386 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
5387 e.router_gemv(
5388 m.gate_inp.float_data(),
5389 z,
5390 cfg.n_embd as usize,
5391 m.gate_exps.n_expert,
5392 t,
5393 )
5394 } else {
5395 e.matmul(&m.gate_inp, z, t)
5396 }
5397 }
5398
5399 fn trace_moe_routes(
5403 il: u16,
5404 t: usize,
5405 sel_all: &[u32],
5406 weights: &[f32],
5407 ) -> Result<(), Box<dyn std::error::Error>> {
5408 use std::io::Write as _;
5409 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
5410 let mut f = std::fs::OpenOptions::new()
5411 .create(true)
5412 .append(true)
5413 .open(path)?;
5414 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
5415 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
5416 }
5417 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
5418 let mut f = std::fs::OpenOptions::new()
5419 .create(true)
5420 .append(true)
5421 .open(path)?;
5422 let pairs: Vec<String> = sel_all
5423 .iter()
5424 .zip(weights)
5425 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
5426 .collect();
5427 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
5428 }
5429 Ok(())
5430 }
5431
5432 #[allow(clippy::too_many_arguments)]
5433 fn trace_sigmoid_router_logits(
5434 e: &Engine,
5435 il: u16,
5436 t: usize,
5437 n_expert: usize,
5438 n_used: usize,
5439 logits: &CudaSlice<f32>,
5440 m: &MoeWeights,
5441 (scaling_factor, route_norm): (f32, bool),
5442 ) -> Result<(), Box<dyn std::error::Error>> {
5443 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
5444 return Ok(());
5445 }
5446 let logits = e.dtoh(logits)?;
5447 let active: Vec<u8> = m
5448 .active_experts
5449 .as_ref()
5450 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
5451 .unwrap_or_else(|| vec![1; n_expert]);
5452 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
5453 crate::sigrouter_contract::capture_served_logits(
5454 il as u32,
5455 t,
5456 n_expert,
5457 n_used,
5458 scaling_factor,
5459 route_norm,
5460 &active,
5461 &bias,
5462 &logits,
5463 )?;
5464 Ok(())
5465 }
5466
5467 fn trace_moe_input(
5472 e: &Engine,
5473 il: u16,
5474 t: usize,
5475 n_embd: usize,
5476 z: &CudaSlice<f32>,
5477 ) -> Result<(), Box<dyn std::error::Error>> {
5478 use std::io::Write as _;
5479 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
5480 return Ok(());
5481 };
5482 let values = active_matrix_values(z.len(), t, n_embd, "MoE input trace activation")?;
5483 let host = e.dtoh_view(&z.slice(0..values))?;
5484 let bytes = unsafe {
5485 std::slice::from_raw_parts(
5486 host.as_ptr().cast::<u8>(),
5487 host.len() * std::mem::size_of::<f32>(),
5488 )
5489 };
5490 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
5491 let mut state = state
5492 .lock()
5493 .map_err(|_| "MoE input trace writer lock is poisoned")?;
5494 if state.is_none() {
5495 let dir = std::path::PathBuf::from(&dir);
5496 std::fs::create_dir_all(&dir)?;
5497 let index = std::fs::OpenOptions::new()
5498 .create(true)
5499 .append(true)
5500 .open(dir.join("index.jsonl"))?;
5501 *state = Some(MoeInputTraceWriter {
5502 dir,
5503 index,
5504 payloads: std::collections::HashMap::new(),
5505 });
5506 }
5507 let writer = state.as_mut().unwrap();
5508 if writer.dir != std::path::Path::new(&dir) {
5509 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
5510 }
5511 let file_name = format!("layer-{il:03}.f32");
5512 if !writer.payloads.contains_key(&il) {
5513 let payload = std::fs::OpenOptions::new()
5514 .create(true)
5515 .append(true)
5516 .open(writer.dir.join(&file_name))?;
5517 let offset = payload.metadata()?.len();
5518 writer.payloads.insert(il, (payload, offset));
5519 }
5520 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
5521 let row_offset = *offset;
5522 payload.write_all(bytes)?;
5523 *offset += bytes.len() as u64;
5524 writeln!(
5525 writer.index,
5526 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
5527 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
5528 \"payload_bytes\":{}}}",
5529 bytes.len()
5530 )?;
5531 Ok(())
5532 }
5533
5534 #[allow(clippy::too_many_arguments)]
5535 pub(crate) fn moe_ffn_sequential_zq8(
5536 e: &Engine,
5537 m: &MoeWeights,
5538 z: &CudaSlice<f32>,
5539 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5540 t: usize,
5541 cfg: &ModelConfig,
5542 il: u16,
5543 max_block: usize,
5544 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5545 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
5546 let moe = cfg.moe.as_ref().unwrap();
5547 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);
5554 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
5555 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);
5558
5559 let lim_exp = cfg.clamp_exp_at(il as u32);
5562 let lim_shexp = cfg.clamp_shexp_at(il as u32);
5563 let use_cache = Engine::moe_cache_enabled();
5564 let uniform_experts = m.has_uniform_expert_layout();
5565 let moe_q8 = uniform_experts
5566 && moe_q8_enabled()
5567 && q8_expert_supported(m.gate_exps.qtype)
5568 && q8_expert_supported(m.up_exps.qtype)
5569 && q8_expert_supported(m.down_exps.qtype);
5570 let cpu_expert_requested = crate::cpu_experts::configured();
5577 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
5578 return Err(std::io::Error::other(
5579 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
5580 )
5581 .into());
5582 }
5583 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
5584 let freeze_cpu_residency = cpu_expert_requested
5590 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
5591 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
5592 .ok()
5593 .and_then(|value| value.parse::<usize>().ok())
5594 .is_some_and(|tokens| tokens > 0);
5595 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
5596 e.freeze_moe_cache();
5597 }
5598 let cache_frozen = use_cache && e.moe_cache_frozen();
5599 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
5600
5601 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5604 if let Some(sig) = cfg.sigmoid_router() {
5605 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
5606 }
5607
5608 let no_exp_macros = m.gate_exps.macros.is_none()
5647 && m.up_exps.macros.is_none()
5648 && m.down_exps.macros.is_none();
5649 if cfg.sigmoid_router().is_none()
5653 && cfg.m3.is_none()
5654 && cfg.hy3.is_none()
5655 && !cfg.swiglu_clamped_at(il as u32)
5656 && no_exp_macros
5657 && t > MOE_DEV_MAX_T
5661 && m.dev_exps.is_some()
5662 && moe_q8_enabled()
5663 && q8_expert_supported(m.gate_exps.qtype)
5664 && q8_expert_supported(m.up_exps.qtype)
5665 && q8_expert_supported(m.down_exps.qtype)
5666 && std::env::var("MEMRA_MOE_PAIRS")
5667 .map(|v| v != "0")
5668 .unwrap_or(true)
5669 && std::env::var("MEMRA_MOE_STATS").is_err()
5670 {
5671 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
5672 }
5673
5674 let dev_ok = uniform_experts
5692 && cfg.sigmoid_router().is_none()
5693 && cfg.m3.is_none()
5694 && cfg.hy3.is_none()
5695 && !cfg.swiglu_clamped_at(il as u32);
5696 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
5700 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5701 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5702 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
5703 if dev_ok
5704 && t <= MOE_DEV_MAX_T
5705 && m.dev_exps.is_some()
5706 && n_used <= 8
5707 && moe_dev_enabled()
5708 && !observe_routes
5709 {
5710 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5711 }
5712 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
5713 let row_ok = e.with_moe_cache(max_block, |c, eng| {
5714 if moe_prewarm_enabled() {
5715 c.prewarm_layer(il, m, eng)?;
5716 }
5717 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
5718 })?;
5719 if row_ok {
5720 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5721 }
5722 }
5723
5724 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
5726 if cpu_hybrid {
5727 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
5728 e,
5729 &logits,
5730 z,
5731 t,
5732 n_embd,
5733 n_expert,
5734 n_used,
5735 m.exp_probs_b.as_deref(),
5736 sig,
5737 m.active_experts.as_deref(),
5738 )?;
5739 (sel, w, Some(input))
5740 } else {
5741 let (sel, w) =
5742 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
5743 (sel, w, None)
5744 }
5745 } else {
5746 let (sel, w) =
5747 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
5748 (sel, w, None)
5749 };
5750 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
5751
5752 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
5756 Self::trace_moe_input(e, il, t, n_embd, z)?;
5757
5758 let worker_disk_prefetch =
5770 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
5771 let promote_worker_h2d =
5772 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
5773 if promote_worker_h2d {
5774 let mut selected_blocks = Vec::with_capacity(n_used * 3);
5775 for &ex in sel_all.iter().take(n_used) {
5776 let ex = ex as u16;
5777 selected_blocks.extend([
5778 BlockId::new(il, PROJ_GATE, ex),
5779 BlockId::new(il, PROJ_UP, ex),
5780 BlockId::new(il, PROJ_DOWN, ex),
5781 ]);
5782 }
5783 for &ex in sel_all.iter().take(n_used) {
5784 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
5785 }
5786 e.with_moe_cache(max_block, |cache, eng| {
5787 cache.promote_worker_reads_at_safe_boundary(
5788 &selected_blocks,
5789 &selected_blocks,
5790 eng,
5791 )?;
5792 Ok(())
5793 })?;
5794 }
5795
5796 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
5799 let mut cnt = vec![0u32; n_expert];
5800 for &s in sel_all.iter() {
5801 cnt[s as usize] += 1;
5802 }
5803 let total = sel_all.len() as f64;
5804 let mut h = 0.0f64;
5805 let mut active = 0usize;
5806 for &c in &cnt {
5807 if c > 0 {
5808 active += 1;
5809 let p = c as f64 / total;
5810 h -= p * p.log2();
5811 }
5812 }
5813 let maxc = cnt.iter().copied().max().unwrap_or(0);
5814 println!(
5815 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
5816 il,
5817 t,
5818 sel_all.len(),
5819 active,
5820 n_expert,
5821 h,
5822 (n_expert as f64).log2(),
5823 total / active.max(1) as f64,
5824 maxc
5825 );
5826 }
5827
5828 let gdec_may_fire = uniform_experts
5841 && use_cache
5842 && n_used <= 8
5843 && gdec_enabled()
5844 && !cfg.swiglu_clamped_at(il as u32);
5845 let slab_local = m
5861 .dev_exps
5862 .as_ref()
5863 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
5864 let slab_bases = slab_local.map(|d| {
5865 use cudarc::driver::DevicePtr;
5866 let s = e.stream();
5867 let (pg, _g0) = d.gate.device_ptr(&s);
5868 let (pu, _g1) = d.up.device_ptr(&s);
5869 let (pd, _g2) = d.down.device_ptr(&s);
5870 (pg as u64, pu as u64, pd as u64)
5871 });
5872 let slab_fused_may_fire = slab_bases.is_some()
5882 && n_used <= 8
5883 && gdec_enabled()
5884 && !cfg.swiglu_clamped_at(il as u32)
5885 && cfg.m3.is_none()
5886 && no_exp_macros
5887 && moe_q8;
5888 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
5891 e.uninit(t * n_embd)?
5892 } else {
5893 e.zeros(t * n_embd)?
5894 };
5895 let cpu_input = if cpu_hybrid {
5898 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
5899 } else {
5900 None
5901 };
5902
5903 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;
5911 let mut scratch_u: Option<CudaSlice<u8>> = None;
5912 let mut scratch_d: Option<CudaSlice<u8>> = None;
5913 let page_window = moe_page_prefetch_window();
5921
5922 for tok in 0..t {
5925 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
5926 let w = &w_all[tok * n_used..(tok + 1) * n_used];
5927 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
5929
5930 let no_macros = m.gate_exps.macros.is_none()
5944 && m.up_exps.macros.is_none()
5945 && m.down_exps.macros.is_none();
5946 if slab_fused_may_fire {
5956 let (pg, pu, pd) = slab_bases.unwrap();
5957 let mut gp = [0u64; 8];
5958 let mut up = [0u64; 8];
5959 let mut dp = [0u64; 8];
5960 for (j, &ex) in sel.iter().enumerate() {
5961 let ex = ex as usize;
5962 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
5963 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
5964 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
5965 }
5966 let mut wv = [0f32; 8];
5967 wv[..n_used].copy_from_slice(w);
5968 if tok_q8.is_none() {
5969 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
5970 }
5971 let (zq, zd) = tok_q8.as_ref().unwrap();
5972 let act = e.moe_gate_up_silu8_q8(
5973 crate::WPtr8(gp),
5974 crate::WPtr8(up),
5975 zq,
5976 zd,
5977 n_embd,
5978 n_ff_exp,
5979 n_used,
5980 m.gate_exps.qtype,
5981 m.up_exps.qtype,
5982 m.gate_exps.row_bytes,
5983 m.up_exps.row_bytes,
5984 )?;
5985 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
5986 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5987 e.moe_down8_fma_q8(
5988 crate::WPtr8(dp),
5989 crate::F32x8(wv),
5990 &aq2,
5991 &ad2,
5992 &mut dst,
5993 n_ff_exp,
5994 n_embd,
5995 n_used,
5996 m.down_exps.qtype,
5997 m.down_exps.row_bytes,
5998 )?;
5999 continue;
6000 }
6001 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
6002 if tok_q8.is_none() {
6003 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6004 }
6005 let (zq, zd) = tok_q8.as_ref().unwrap();
6006 if Self::moe_gdec_token_q8(
6007 e,
6008 m,
6009 il,
6010 max_block,
6011 zq,
6012 zd,
6013 sel,
6014 w,
6015 &mut moe_out,
6016 tok,
6017 n_embd,
6018 n_ff_exp,
6019 n_used,
6020 )? {
6021 continue;
6022 }
6023 } else if gdec_may_fire
6024 && cfg.m3.is_none()
6025 && no_macros
6026 && Self::moe_gdec_token(
6027 e,
6028 m,
6029 il,
6030 max_block,
6031 &zt,
6032 sel,
6033 w,
6034 &mut moe_out,
6035 tok,
6036 n_embd,
6037 n_ff_exp,
6038 n_used,
6039 )?
6040 {
6041 continue;
6042 }
6043
6044 if gdec_may_fire || slab_fused_may_fire {
6050 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6051 e.memset_zeros_view(&mut row)?;
6052 }
6053
6054 let mut cpu_mask = vec![false; sel.len()];
6060 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
6061 let gpu_resident = if use_cache {
6062 e.with_moe_cache(max_block, |cache, _| {
6063 Ok(sel
6064 .iter()
6065 .map(|&expert| {
6066 let expert = expert as u16;
6067 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
6068 .into_iter()
6069 .filter(|&projection| {
6070 cache
6071 .resident(BlockId::new(il, projection, expert))
6072 .is_some()
6073 })
6074 .count()
6075 })
6076 .collect::<Vec<_>>())
6077 })?
6078 } else {
6079 vec![0; sel.len()]
6080 };
6081 let mut cpu_selected = Vec::new();
6082 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
6083 if gpu_resident[index] != 3 {
6084 cpu_mask[index] = true;
6085 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
6086 let expert = expert as usize;
6087 cpu_selected.push((expert, route_weight));
6088 }
6089 }
6090 if crate::cpu_experts::predictor_enabled() {
6091 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6095 crate::cpu_experts::predictor_submit(il, row);
6096 }
6097 if cpu_selected.is_empty() {
6098 None
6099 } else {
6100 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6101 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
6102 .map_err(std::io::Error::other)?;
6103 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
6104 }
6105 } else {
6106 None
6107 };
6108
6109 let worker_window = worker_disk_prefetch
6110 .then(worker_prefetch_window)
6111 .unwrap_or(0);
6112 for (j, &ex) in sel.iter().enumerate() {
6113 if cpu_mask[j] {
6114 continue;
6115 }
6116 let ex = ex as usize;
6117 if let Some(d) = slab_local {
6124 let gl = m.gate_exps.expert_layout(ex);
6125 let ul = m.up_exps.expert_layout(ex);
6126 let dl = m.down_exps.expert_layout(ex);
6127 let (g0, u0, d0) = (
6128 ex * m.gate_exps.expert_stride,
6129 ex * m.up_exps.expert_stride,
6130 ex * m.down_exps.expert_stride,
6131 );
6132 let (gate, up) = if moe_q8 {
6133 if tok_q8.is_none() {
6134 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6135 }
6136 let (zq, zd) = tok_q8.as_ref().unwrap();
6137 (
6138 e.qmatvec_expert_q8(
6139 &d.gate,
6140 g0..g0 + gl.len,
6141 zq,
6142 zd,
6143 1,
6144 m.gate_exps.in_f,
6145 m.gate_exps.out_f,
6146 gl.qtype,
6147 gl.row_bytes,
6148 )?,
6149 e.qmatvec_expert_q8(
6150 &d.up,
6151 u0..u0 + ul.len,
6152 zq,
6153 zd,
6154 1,
6155 m.up_exps.in_f,
6156 m.up_exps.out_f,
6157 ul.qtype,
6158 ul.row_bytes,
6159 )?,
6160 )
6161 } else {
6162 (
6163 e.qmatvec_view(
6164 &d.gate,
6165 g0..g0 + gl.len,
6166 &zt,
6167 1,
6168 m.gate_exps.in_f,
6169 m.gate_exps.out_f,
6170 gl.qtype,
6171 gl.row_bytes,
6172 )?,
6173 e.qmatvec_view(
6174 &d.up,
6175 u0..u0 + ul.len,
6176 &zt,
6177 1,
6178 m.up_exps.in_f,
6179 m.up_exps.out_f,
6180 ul.qtype,
6181 ul.row_bytes,
6182 )?,
6183 )
6184 };
6185 let mut act = e.uninit(n_ff_exp)?;
6186 Self::ffn_act_lim(
6187 e,
6188 cfg,
6189 &gate,
6190 &up,
6191 m.gate_exps.macro_scale(ex),
6192 m.up_exps.macro_scale(ex),
6193 lim_exp,
6194 &mut act,
6195 n_ff_exp,
6196 )?;
6197 let y = if moe_q8 {
6198 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6199 e.qmatvec_expert_q8(
6200 &d.down,
6201 d0..d0 + dl.len,
6202 &aq2,
6203 &ad2,
6204 1,
6205 m.down_exps.in_f,
6206 m.down_exps.out_f,
6207 dl.qtype,
6208 dl.row_bytes,
6209 )?
6210 } else {
6211 let actv = act.slice(0..n_ff_exp);
6212 e.qmatvec_view(
6213 &d.down,
6214 d0..d0 + dl.len,
6215 &actv,
6216 1,
6217 m.down_exps.in_f,
6218 m.down_exps.out_f,
6219 dl.qtype,
6220 dl.row_bytes,
6221 )?
6222 };
6223 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6224 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6225 continue;
6226 }
6227 for next in page_prefetch_positions(j, sel.len(), page_window) {
6228 Self::moe_prefetch_host_expert(sel[next] as usize, m);
6229 }
6230 let keep = [
6231 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
6232 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
6233 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
6234 ];
6235 if worker_disk_prefetch && worker_window > 0 {
6236 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
6237 Self::moe_prefetch_disk_expert(
6238 e,
6239 il,
6240 sel[next] as usize,
6241 m,
6242 max_block,
6243 &keep,
6244 )?;
6245 }
6246 } else if cache_dispatch
6247 && !cpu_hybrid
6248 && moe_prefetch_enabled()
6249 && j + 1 < sel.len()
6250 {
6251 let next = sel[j + 1] as usize;
6252 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
6253 }
6254 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
6255 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
6256 if (gate_q8 || up_q8) && tok_q8.is_none() {
6259 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6260 }
6261 let gate = if gate_q8 {
6262 let (zq, zd) = tok_q8.as_ref().unwrap();
6263 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
6264 } else {
6265 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
6266 };
6267 let up = if up_q8 {
6268 let (zq, zd) = tok_q8.as_ref().unwrap();
6269 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
6270 } else {
6271 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
6272 };
6273 let mut act = e.uninit(n_ff_exp)?;
6274 Self::ffn_act_lim(
6275 e,
6276 cfg,
6277 &gate,
6278 &up,
6279 m.gate_exps.macro_scale(ex),
6280 m.up_exps.macro_scale(ex),
6281 lim_exp,
6282 &mut act,
6283 n_ff_exp,
6284 )?;
6285 let y = if down_q8 {
6286 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6287 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
6288 } else {
6289 let actv = act.slice(0..n_ff_exp);
6290 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
6291 };
6292 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6293 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6295 } else if cache_dispatch {
6296 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
6301 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
6302 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6304 e,
6305 cfg,
6306 &gate,
6307 &up,
6308 m.gate_exps.macro_scale(ex),
6309 m.up_exps.macro_scale(ex),
6310 lim_exp,
6311 &mut act,
6312 n_ff_exp,
6313 )?;
6314 let actv = act.slice(0..n_ff_exp);
6315 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
6316 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6317 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6319 } else if cache_frozen {
6320 let gate = Self::moe_frozen_gemm(
6325 e,
6326 il,
6327 PROJ_GATE,
6328 ex,
6329 m,
6330 max_block,
6331 &zt,
6332 &mut scratch_g,
6333 g_len,
6334 )?;
6335 let up = Self::moe_frozen_gemm(
6336 e,
6337 il,
6338 PROJ_UP,
6339 ex,
6340 m,
6341 max_block,
6342 &zt,
6343 &mut scratch_u,
6344 u_len,
6345 )?;
6346 let mut act = e.uninit(n_ff_exp)?;
6347 Self::ffn_act_lim(
6348 e,
6349 cfg,
6350 &gate,
6351 &up,
6352 m.gate_exps.macro_scale(ex),
6353 m.up_exps.macro_scale(ex),
6354 lim_exp,
6355 &mut act,
6356 n_ff_exp,
6357 )?;
6358 let actv = act.slice(0..n_ff_exp);
6359 let y = Self::moe_frozen_gemm(
6360 e,
6361 il,
6362 PROJ_DOWN,
6363 ex,
6364 m,
6365 max_block,
6366 &actv,
6367 &mut scratch_d,
6368 d_len,
6369 )?;
6370 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6371 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6372 } else {
6373 if scratch_g.is_none() {
6377 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
6378 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
6379 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
6380 }
6381 let (sg, su, sd) = (
6382 scratch_g.as_mut().unwrap(),
6383 scratch_u.as_mut().unwrap(),
6384 scratch_d.as_mut().unwrap(),
6385 );
6386 let gl = m.gate_exps.expert_layout(ex);
6387 let ul = m.up_exps.expert_layout(ex);
6388 let dl = m.down_exps.expert_layout(ex);
6389 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
6390 let gate = e.qmatvec_view(
6391 sg,
6392 0..gl.len,
6393 &zt,
6394 1,
6395 m.gate_exps.in_f,
6396 m.gate_exps.out_f,
6397 gl.qtype,
6398 gl.row_bytes,
6399 )?;
6400
6401 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
6402 let up = e.qmatvec_view(
6403 su,
6404 0..ul.len,
6405 &zt,
6406 1,
6407 m.up_exps.in_f,
6408 m.up_exps.out_f,
6409 ul.qtype,
6410 ul.row_bytes,
6411 )?;
6412
6413 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6415 e,
6416 cfg,
6417 &gate,
6418 &up,
6419 m.gate_exps.macro_scale(ex),
6420 m.up_exps.macro_scale(ex),
6421 lim_exp,
6422 &mut act,
6423 n_ff_exp,
6424 )?;
6425
6426 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
6427 let actv = act.slice(0..n_ff_exp);
6428 let y = e.qmatvec_view(
6429 sd,
6430 0..dl.len,
6431 &actv,
6432 1,
6433 m.down_exps.in_f,
6434 m.down_exps.out_f,
6435 dl.qtype,
6436 dl.row_bytes,
6437 )?;
6438
6439 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6440 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6441 }
6442 }
6443 if let Some(worker) = cpu_worker {
6444 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
6445 let cpu_output = e.htod(&cpu_output)?;
6446 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6447 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
6448 }
6449 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
6450 for (j, &ex) in sel.iter().enumerate() {
6451 if cpu_mask[j] {
6452 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
6453 }
6454 }
6455 }
6456 }
6457
6458 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6463 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6464 {
6465 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
6474 let (sg_gate, sg_up) = if t == 1 {
6475 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
6476 } else if verify_t {
6477 (
6478 e.matmul_decode_exact(gate_shexp, z, t)?,
6479 e.matmul_decode_exact(up_shexp, z, t)?,
6480 )
6481 } else {
6482 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
6484 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
6486 e,
6487 cfg,
6488 &sg_gate,
6489 &sg_up,
6490 1.0,
6491 1.0,
6492 lim_shexp,
6493 &mut sa,
6494 t * n_ff_sh,
6495 )?;
6496 let sh = if verify_t {
6497 e.matmul_decode_exact(down_shexp, &sa, t)?
6498 } else {
6499 e.matmul(down_shexp, &sa, t)?
6500 }; let g = match &m.gate_inp_shexp {
6514 Some(gate_inp_shexp) => {
6515 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
6516 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6517 } else {
6518 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6519 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
6521 g
6522 }
6523 }
6524 None => e.htod(&vec![1.0f32; t])?,
6525 };
6526 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6528 }
6529
6530 Ok(moe_out)
6531 }
6532
6533 pub fn stage1_h2d_per_token(&self) -> u64 {
6536 use crate::hybrid::Ffn;
6537 let n_used = self
6538 .cfg
6539 .moe
6540 .as_ref()
6541 .map(|m| m.expert_used_count as u64)
6542 .unwrap_or(0);
6543 let mut bytes = 0u64;
6544 for l in self.layers.iter() {
6545 if let Ffn::Moe(m) = &l.ffn {
6546 bytes += n_used
6547 * (m.gate_exps.max_expert_bytes()
6548 + m.up_exps.max_expert_bytes()
6549 + m.down_exps.max_expert_bytes()) as u64;
6550 }
6551 }
6552 bytes
6553 }
6554
6555 pub(crate) fn max_moe_block(&self) -> usize {
6559 use crate::hybrid::Ffn;
6560 let mut mx = 0usize;
6561 let mut scan = |ffn: &Ffn| {
6562 if let Ffn::Moe(m) = ffn {
6563 mx = mx
6564 .max(m.gate_exps.max_expert_bytes())
6565 .max(m.up_exps.max_expert_bytes())
6566 .max(m.down_exps.max_expert_bytes());
6567 }
6568 };
6569 for l in self.layers.iter() {
6570 scan(&l.ffn);
6571 }
6572 if let Some(mtp) = self.mtp.as_ref() {
6573 scan(&mtp.ffn);
6574 }
6575 mx
6576 }
6577
6578 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
6581 use crate::hybrid::Ffn;
6582 let mut sizes = Vec::new();
6583 let mut scan = |ffn: &Ffn| {
6584 let Ffn::Moe(m) = ffn else { return };
6585 for ex in 0..m.gate_exps.n_expert {
6586 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
6587 continue;
6588 }
6589 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
6590 let len = exps.expert_layout(ex).len;
6591 if len > 0 {
6592 sizes.push(len);
6593 }
6594 }
6595 }
6596 };
6597 for layer in &self.layers {
6598 scan(&layer.ffn);
6599 }
6600 if let Some(mtp) = &self.mtp {
6601 scan(&mtp.ffn);
6602 }
6603 sizes
6604 }
6605
6606 pub fn save_cpu_expert_residency_profile(
6612 &self,
6613 e: &Engine,
6614 path: &std::path::Path,
6615 ) -> Result<(), Box<dyn std::error::Error>> {
6616 let Some(ids) = e.export_moe_residency() else {
6617 return Err("no MoE residency cache to persist".into());
6618 };
6619 let mut body = format!(
6620 "memra-freeze-profile v1 max_block={} blocks={}\n",
6621 self.max_moe_block(),
6622 ids.len()
6623 );
6624 for (layer, proj, ex) in &ids {
6625 body.push_str(&format!("{layer} {proj} {ex}\n"));
6626 }
6627 let tmp = path.with_extension("tmp");
6628 std::fs::write(&tmp, body)?;
6629 std::fs::rename(&tmp, path)?;
6630 println!(
6631 "[moe-cache] freeze profile saved: {} blocks -> {}",
6632 ids.len(),
6633 path.display()
6634 );
6635 Ok(())
6636 }
6637
6638 pub fn restore_cpu_expert_residency_profile(
6642 &self,
6643 e: &Engine,
6644 path: &std::path::Path,
6645 ) -> Result<bool, Box<dyn std::error::Error>> {
6646 use crate::hybrid::Ffn;
6647 use crate::moe_cache::BlockId;
6648 let Ok(content) = std::fs::read_to_string(path) else {
6649 return Ok(false);
6650 };
6651 let mut lines = content.lines();
6652 let Some(header) = lines.next() else {
6653 return Ok(false);
6654 };
6655 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
6656 if !header.starts_with(&expected) {
6657 println!(
6658 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
6659 path.display()
6660 );
6661 return Ok(false);
6662 }
6663 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
6664 std::collections::HashMap::new();
6665 for line in lines {
6666 let mut fields = line.split_whitespace();
6667 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
6668 else {
6669 continue;
6670 };
6671 let (Ok(layer), Ok(proj), Ok(ex)) =
6672 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
6673 else {
6674 continue;
6675 };
6676 by_layer
6677 .entry(layer)
6678 .or_default()
6679 .push(BlockId::new(layer, proj, ex));
6680 }
6681 let requested: usize = by_layer.values().map(Vec::len).sum();
6682 if requested == 0 {
6683 return Ok(false);
6684 }
6685 let max_block = self.max_moe_block();
6686 let mut restaged = 0usize;
6687 let mut stage_layer =
6688 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
6689 let Ffn::Moe(m) = ffn else { return Ok(()) };
6690 let Some(ids) = by_layer.get(&layer_index) else {
6691 return Ok(());
6692 };
6693 e.with_moe_cache(max_block, |cache, eng| {
6694 for id in ids {
6695 if cache.restage_block(*id, m, eng)? {
6696 restaged += 1;
6697 }
6698 }
6699 Ok(())
6700 })
6701 };
6702 for (index, layer) in self.layers.iter().enumerate() {
6703 stage_layer(index as u16, &layer.ffn)?;
6704 }
6705 if let Some(mtp) = self.mtp.as_ref() {
6706 stage_layer(u16::MAX, &mtp.ffn)?;
6707 }
6708 e.freeze_moe_cache();
6709 println!(
6710 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
6711 path.display()
6712 );
6713 Ok(true)
6714 }
6715
6716 pub fn freeze_cpu_expert_residency(
6718 &self,
6719 e: &Engine,
6720 ) -> Result<(), Box<dyn std::error::Error>> {
6721 e.freeze_moe_cache();
6722 Ok(())
6723 }
6724
6725 pub fn ffn_act(
6733 e: &Engine,
6734 cfg: &ModelConfig,
6735 gate: &CudaSlice<f32>,
6736 up: &CudaSlice<f32>,
6737 act: &mut CudaSlice<f32>,
6738 n: usize,
6739 ) -> Result<(), Box<dyn std::error::Error>> {
6740 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
6741 }
6742
6743 #[allow(clippy::too_many_arguments)]
6747 pub(crate) fn ffn_act_scaled(
6748 e: &Engine,
6749 cfg: &ModelConfig,
6750 gate: &CudaSlice<f32>,
6751 up: &CudaSlice<f32>,
6752 gs: f32,
6753 us: f32,
6754 act: &mut CudaSlice<f32>,
6755 n: usize,
6756 ) -> Result<(), Box<dyn std::error::Error>> {
6757 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
6758 }
6759
6760 #[allow(clippy::too_many_arguments)]
6769 pub(crate) fn ffn_act_lim(
6770 e: &Engine,
6771 cfg: &ModelConfig,
6772 gate: &CudaSlice<f32>,
6773 up: &CudaSlice<f32>,
6774 gs: f32,
6775 us: f32,
6776 limit: Option<f32>,
6777 act: &mut CudaSlice<f32>,
6778 n: usize,
6779 ) -> Result<(), Box<dyn std::error::Error>> {
6780 if let Some(m3) = cfg.m3.as_ref() {
6781 debug_assert!(
6782 limit.is_none(),
6783 "m3 swigluoai and step35 clamp are different archs"
6784 );
6785 return e.swigluoai_mul_scaled(
6786 gate,
6787 up,
6788 gs,
6789 us,
6790 m3.swiglu_alpha,
6791 m3.swiglu_limit,
6792 act,
6793 n,
6794 );
6795 }
6796 if let Some(l) = limit {
6797 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
6798 }
6799 if gs == 1.0 && us == 1.0 {
6800 return e.silu_mul(gate, up, act, n);
6801 }
6802 e.silu_mul_scaled(gate, up, gs, us, act, n)
6803 }
6804
6805 fn moe_route(
6811 e: &Engine,
6812 logits: &CudaSlice<f32>,
6813 t: usize,
6814 n_expert: usize,
6815 n_used: usize,
6816 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6817 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
6818 }
6819
6820 #[allow(clippy::too_many_arguments)]
6828 fn moe_route_sigmoid_cfg(
6829 e: &Engine,
6830 logits: &CudaSlice<f32>,
6831 t: usize,
6832 n_expert: usize,
6833 n_used: usize,
6834 m: &MoeWeights,
6835 (sf, route_norm): (f32, bool),
6836 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6837 if sigmoid_router_enabled() {
6838 return e.moe_router_sigmoid_topk_host(
6839 logits,
6840 t,
6841 n_expert,
6842 n_used,
6843 m.active_count(),
6844 &m.exp_probs_b_dev,
6845 &m.active_experts_dev,
6846 sf,
6847 route_norm,
6848 );
6849 }
6850 let lg = e.dtoh(logits)?;
6851 Self::moe_route_sigmoid_host(
6852 &lg,
6853 t,
6854 n_expert,
6855 n_used,
6856 m.exp_probs_b.as_deref(),
6857 sf,
6858 route_norm,
6859 m.active_experts.as_deref(),
6860 )
6861 }
6862
6863 fn moe_route_cfg(
6866 e: &Engine,
6867 logits: &CudaSlice<f32>,
6868 t: usize,
6869 n_expert: usize,
6870 n_used: usize,
6871 active: Option<&[bool]>,
6872 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6873 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
6876 return e.moe_router_topk_host(logits, t, n_expert, n_used);
6877 }
6878 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
6881 let mut w_out = vec![0f32; t * n_used];
6882 for tok in 0..t {
6883 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
6884 let maxl = row
6886 .iter()
6887 .enumerate()
6888 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
6889 .map(|(_, &x)| x)
6890 .fold(f32::NEG_INFINITY, f32::max);
6891 let mut probs = vec![0f32; n_expert];
6892 let mut den = 0f32;
6893 for i in 0..n_expert {
6894 if active.is_some_and(|mask| !mask[i]) {
6895 continue;
6896 }
6897 let x = (row[i] - maxl).exp();
6898 probs[i] = x;
6899 den += x;
6900 }
6901 for p in probs.iter_mut() {
6902 *p /= den;
6903 }
6904 let mut idx: Vec<usize> = (0..n_expert)
6906 .filter(|&i| active.is_none_or(|mask| mask[i]))
6907 .collect();
6908 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
6909 let sl = &idx[..n_used];
6910 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
6911 let mut ws: f32 = wv.iter().sum();
6912 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
6914 *x /= ws;
6915 }
6916 for j in 0..n_used {
6917 sel[tok * n_used + j] = sl[j] as u32;
6918 w_out[tok * n_used + j] = wv[j];
6919 }
6920 }
6921 Ok((sel, w_out))
6922 }
6923
6924 #[allow(clippy::too_many_arguments)]
6925 fn moe_route_sigmoid_with_input(
6926 e: &Engine,
6927 logits: &CudaSlice<f32>,
6928 input: &CudaSlice<f32>,
6929 t: usize,
6930 in_features: usize,
6931 n_expert: usize,
6932 n_used: usize,
6933 bias: Option<&[f32]>,
6934 (sf, route_norm): (f32, bool),
6935 active: Option<&[bool]>,
6936 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6937 let logit_values =
6938 active_matrix_values(logits.len(), t, n_expert, "sigmoid router logits")?;
6939 let input_values =
6940 active_matrix_values(input.len(), t, in_features, "sigmoid router input")?;
6941 let (lg, input) = e.dtoh_pair_views(
6942 &logits.slice(0..logit_values),
6943 &input.slice(0..input_values),
6944 )?;
6945 let (sel, w) =
6946 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
6947 Ok((sel, w, input))
6948 }
6949
6950 pub fn start_moe_prefetch_predictor(
6955 &self,
6956 e: &Engine,
6957 cfg: &ModelConfig,
6958 ) -> Result<(), Box<dyn std::error::Error>> {
6959 use crate::hybrid::Ffn;
6960 let Some(sig) = cfg.sigmoid_router() else {
6961 return Err("prefetch predictor requires a sigmoid-router arch".into());
6962 };
6963 let resident: std::collections::HashSet<(u16, u8, u16)> = e
6964 .export_moe_residency()
6965 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
6966 .into_iter()
6967 .collect();
6968 let mut layers = Vec::new();
6969 for (index, layer) in self.layers.iter().enumerate() {
6970 let Ffn::Moe(m) = &layer.ffn else { continue };
6971 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
6972 continue;
6973 };
6974 let router = e.dtoh(data)?;
6975 let n_expert = m.gate_exps.n_expert;
6976 let n_embd = m.gate_exps.in_f;
6977 if router.len() != n_embd * n_expert {
6978 continue;
6979 }
6980 let build = |exps: &crate::model::HostExps| {
6981 (0..n_expert)
6982 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
6983 .collect::<Vec<_>>()
6984 };
6985 layers.push((
6986 index as u16,
6987 crate::cpu_experts::PredictLayerInit {
6988 router,
6989 bias: m.exp_probs_b.clone(),
6990 active: m.active_experts.clone(),
6991 n_embd,
6992 n_used: cfg
6993 .moe
6994 .as_ref()
6995 .map(|moe| moe.expert_used_count as usize)
6996 .ok_or("prefetch predictor requires MoE config")?,
6997 sig,
6998 weights_n_expert: n_expert,
6999 gate: build(&m.gate_exps),
7000 up: build(&m.up_exps),
7001 down: build(&m.down_exps),
7002 },
7003 ));
7004 }
7005 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
7006 }
7007
7008 #[allow(clippy::too_many_arguments)]
7011 pub fn moe_route_sigmoid_host_public(
7012 logits: &[f32],
7013 t: usize,
7014 n_expert: usize,
7015 n_used: usize,
7016 bias: Option<&[f32]>,
7017 sf: f32,
7018 route_norm: bool,
7019 active: Option<&[bool]>,
7020 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7021 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
7022 }
7023
7024 #[allow(clippy::too_many_arguments)]
7025 fn moe_route_sigmoid_host(
7026 lg: &[f32],
7027 t: usize,
7028 n_expert: usize,
7029 n_used: usize,
7030 bias: Option<&[f32]>,
7031 sf: f32,
7032 route_norm: bool,
7033 active: Option<&[bool]>,
7034 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7035 let active_count = active
7036 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
7037 .unwrap_or(n_expert);
7038 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
7039 if lg.len() != t * n_expert {
7040 return Err(format!(
7041 "sigmoid router logits length mismatch: got {}, expected {}",
7042 lg.len(),
7043 t * n_expert,
7044 )
7045 .into());
7046 }
7047 let mut sel = vec![0u32; t * n_used];
7048 let mut w_out = vec![0f32; t * n_used];
7049 for tok in 0..t {
7050 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7051 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
7052 let selsc: Vec<f32> = match bias {
7054 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
7055 None => scores.clone(),
7056 };
7057 let mut idx: Vec<usize> = (0..n_expert)
7058 .filter(|&i| active.is_none_or(|mask| mask[i]))
7059 .collect();
7060 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
7061 let sl = &idx[..n_used];
7062 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
7063 if route_norm {
7064 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
7065 for x in wv.iter_mut() {
7066 *x = *x / ws * sf;
7067 }
7068 } else {
7069 for x in wv.iter_mut() {
7070 *x *= sf;
7071 }
7072 }
7073 for j in 0..n_used {
7074 sel[tok * n_used + j] = sl[j] as u32;
7075 w_out[tok * n_used + j] = wv[j];
7076 }
7077 }
7078 Ok((sel, w_out))
7079 }
7080
7081 #[allow(clippy::too_many_arguments)]
7085 fn moe_ffn_sigmoid_dev(
7086 e: &Engine,
7087 m: &MoeWeights,
7088 z: &CudaSlice<f32>,
7089 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7090 logits: &CudaSlice<f32>,
7091 t: usize,
7092 cfg: &ModelConfig,
7093 il: u16,
7094 (scaling_factor, route_norm): (f32, bool),
7095 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7096 let moe = cfg.moe.as_ref().unwrap();
7097 let n_embd = cfg.n_embd as usize;
7098 let n_expert = moe.expert_count as usize;
7099 let n_used = moe.expert_used_count as usize;
7100 let n_ff_exp = moe.expert_ff_length as usize;
7101 let dev = m.dev_exps.as_ref().unwrap();
7102 debug_assert_eq!(dev.dev, e.ctx().ordinal());
7103 debug_assert!(m.has_uniform_expert_layout());
7104 debug_assert!(!m.has_macros);
7105
7106 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
7107 logits,
7108 t,
7109 n_expert,
7110 n_used,
7111 m.active_count(),
7112 &m.exp_probs_b_dev,
7113 &m.active_experts_dev,
7114 scaling_factor,
7115 route_norm,
7116 )?;
7117 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
7118 if let Some(fp8) = dev.fp8_blk.as_ref() {
7119 debug_assert_eq!(m.gate_exps.qtype, crate::QT_F8_E4M3_BLK);
7120 debug_assert_eq!(m.up_exps.qtype, crate::QT_F8_E4M3_BLK);
7121 debug_assert_eq!(m.down_exps.qtype, crate::QT_F8_E4M3_BLK);
7122 debug_assert_eq!(fp8.gate.rows, m.gate_exps.out_f.div_ceil(128));
7123 debug_assert_eq!(fp8.up.rows, m.up_exps.out_f.div_ceil(128));
7124 debug_assert_eq!(fp8.down.rows, m.down_exps.out_f.div_ceil(128));
7125
7126 let selected = e.dtoh_i32(&sel_d)?;
7133 let route_weights = e.dtoh(&w_d)?;
7134 let mut moe_out = e.zeros(t * n_embd)?;
7135 for tok in 0..t {
7136 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
7137 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7138 for j in 0..n_used {
7139 let pair = tok * n_used + j;
7140 let expert = selected[pair] as usize;
7141 let gate = Self::moe_resident_fp8_e4m3(
7142 e,
7143 &m.gate_exps,
7144 &dev.gate,
7145 &fp8.gate,
7146 expert,
7147 &zt,
7148 1,
7149 )?;
7150 let up = Self::moe_resident_fp8_e4m3(
7151 e, &m.up_exps, &dev.up, &fp8.up, expert, &zt, 1,
7152 )?;
7153 let mut act = e.uninit(n_ff_exp)?;
7154 Self::ffn_act_lim(
7155 e,
7156 cfg,
7157 &gate,
7158 &up,
7159 1.0,
7160 1.0,
7161 cfg.clamp_exp_at(il as u32),
7162 &mut act,
7163 n_ff_exp,
7164 )?;
7165 let act = act.slice(0..n_ff_exp);
7166 let down = Self::moe_resident_fp8_e4m3(
7167 e,
7168 &m.down_exps,
7169 &dev.down,
7170 &fp8.down,
7171 expert,
7172 &act,
7173 1,
7174 )?;
7175 e.axpy_into(&down, route_weights[pair], &mut dst, n_embd)?;
7176 }
7177 }
7178 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7179 eprintln!(
7180 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} \
7181 native=fp8blk-w8a8-e4m3-reference clamp={}",
7182 cfg.clamp_exp_at(il as u32).is_some(),
7183 );
7184 }
7185 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7186 return Ok(moe_out);
7187 }
7188 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
7189 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7190 (combined, combined)
7191 } else {
7192 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7193 };
7194 let (zq, zd) = match (t, zq8) {
7195 (1, Some((q, d))) => (q.clone(), d.clone()),
7196 _ => e.quantize_q8_1(z, t, n_embd)?,
7197 };
7198 let n_pairs = t * n_used;
7199 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
7200 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7204 let pair_tok_d = e.htod_i32(&pair_tok)?;
7205 let gate = e.moe_pairs_matvec_q8(
7206 &dev.ptr_row,
7207 0,
7208 &pair_tok_d,
7209 &sel_d,
7210 &zq,
7211 &zd,
7212 n_embd,
7213 n_ff_exp,
7214 n_expert,
7215 n_pairs,
7216 m.gate_exps.qtype,
7217 gate_row_bytes,
7218 )?;
7219 let up = e.moe_pairs_matvec_q8(
7220 &dev.ptr_row,
7221 1,
7222 &pair_tok_d,
7223 &sel_d,
7224 &zq,
7225 &zd,
7226 n_embd,
7227 n_ff_exp,
7228 n_expert,
7229 n_pairs,
7230 m.up_exps.qtype,
7231 up_row_bytes,
7232 )?;
7233 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7234 Self::ffn_act_lim(
7235 e,
7236 cfg,
7237 &gate,
7238 &up,
7239 1.0,
7240 1.0,
7241 cfg.clamp_exp_at(il as u32),
7242 &mut act,
7243 n_pairs * n_ff_exp,
7244 )?;
7245 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7246 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7247 let pair_self_d = e.htod_i32(&pair_self)?;
7248 let down = e.moe_pairs_matvec_q8(
7249 &dev.ptr_row,
7250 2,
7251 &pair_self_d,
7252 &sel_d,
7253 &aq2,
7254 &ad2,
7255 n_ff_exp,
7256 n_embd,
7257 n_expert,
7258 n_pairs,
7259 m.down_exps.qtype,
7260 m.down_exps.row_bytes,
7261 )?;
7262 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7263 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7264 let tok_off_d = e.htod_i32(&tok_off)?;
7265 let tok_ids_d = e.htod_i32(&tok_ids)?;
7266 let mut output = e.uninit(t * n_embd)?;
7267 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
7268 output
7269 } else {
7270 let act = e.moe_gate_up_silu8_dev_q8_rows(
7271 &dev.ptr_row,
7272 &sel_d,
7273 &zq,
7274 &zd,
7275 t,
7276 n_embd,
7277 n_ff_exp,
7278 n_used,
7279 n_expert,
7280 m.gate_exps.qtype,
7281 m.up_exps.qtype,
7282 gate_row_bytes,
7283 up_row_bytes,
7284 &m.dev_macros,
7285 )?;
7286 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7287 let mut output = e.uninit(t * n_embd)?;
7288 e.moe_down8_fma_dev_q8_rows_g(
7289 &dev.ptr_row,
7290 &sel_d,
7291 &w_d,
7292 &aq2,
7293 &ad2,
7294 &mut output,
7295 t,
7296 n_ff_exp,
7297 n_embd,
7298 n_used,
7299 n_expert,
7300 m.down_exps.qtype,
7301 m.down_exps.row_bytes,
7302 )?;
7303 output
7304 };
7305
7306 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7307 eprintln!(
7308 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
7309 cfg.clamp_exp_at(il as u32).is_some(),
7310 dev.gu_il,
7311 );
7312 }
7313 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7314 Ok(moe_out)
7315 }
7316
7317 #[allow(clippy::too_many_arguments)]
7318 fn moe_resident_fp8_e4m3(
7319 e: &Engine,
7320 exps: &crate::model::HostExps,
7321 bytes: &CudaSlice<u8>,
7322 scales: &crate::hybrid::DevExpertFp8ProjectionScales,
7323 expert: usize,
7324 x: &cudarc::driver::CudaView<f32>,
7325 m: usize,
7326 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7327 let layout = exps.expert_layout(expert);
7328 debug_assert_eq!(layout.qtype, crate::QT_F8_E4M3_BLK);
7329 debug_assert_eq!(scales.rows * scales.cols, scales.expert_stride);
7330 let byte_start = expert * exps.expert_stride;
7331 let scale_start = expert * scales.expert_stride;
7332 let weight = bytes.slice(byte_start..byte_start + layout.len);
7333 let scale = scales
7334 .scales
7335 .slice(scale_start..scale_start + scales.expert_stride);
7336 e.qmatvec_mmq_fp8_blk_view(&weight, &scale, x, m, exps.in_f, exps.out_f)
7337 }
7338
7339 fn moe_ffn_pairs(
7348 e: &Engine,
7349 m: &MoeWeights,
7350 z: &CudaSlice<f32>,
7351 logits: &CudaSlice<f32>,
7352 t: usize,
7353 cfg: &ModelConfig,
7354 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7355 let moe = cfg.moe.as_ref().unwrap();
7356 let n_embd = cfg.n_embd as usize;
7357 let n_expert = moe.expert_count as usize;
7358 let n_used = moe.expert_used_count as usize;
7359 let n_ff_exp = moe.expert_ff_length as usize;
7360 debug_assert!(
7365 !cfg.swiglu_clamped_anywhere(),
7366 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
7367 );
7368 let dev = m.dev_exps.as_ref().unwrap();
7369 let (rbg_d, rbu_d) = if dev.gu_il {
7371 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7372 (sxx, sxx)
7373 } else {
7374 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7375 };
7376
7377 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
7378 let n_pairs = t * n_used;
7379 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
7382 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
7383 let pair_w: Vec<f32> = w_all.clone();
7384 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7385 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7386 let pt = e.htod_i32(&pair_tok)?;
7387 let px = e.htod_i32(&pair_ex)?;
7388 let pw = e.htod(&pair_w)?;
7389 let toff = e.htod_i32(&tok_off)?;
7390 let tids = e.htod_i32(&tok_ids)?;
7391
7392 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7396 for p in 0..n_pairs {
7397 by_ex[pair_ex[p] as usize].push(p as i32);
7398 }
7399 let mut ex_ids: Vec<i32> = Vec::new();
7400 let mut ex_off: Vec<i32> = vec![0];
7401 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
7402 for (ex, list) in by_ex.iter().enumerate() {
7403 if list.is_empty() {
7404 continue;
7405 }
7406 ex_ids.push(ex as i32);
7407 ex_pairs.extend_from_slice(list);
7408 ex_off.push(ex_pairs.len() as i32);
7409 }
7410 let n_active = ex_ids.len();
7411 let exi = e.htod_i32(&ex_ids)?;
7412 let exo = e.htod_i32(&ex_off)?;
7413 let exp_d = e.htod_i32(&ex_pairs)?;
7414 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7435 let mma_t = *MMA_T.get_or_init(|| {
7436 std::env::var("MEMRA_MOE_MMA_T")
7437 .ok()
7438 .and_then(|v| v.parse().ok())
7439 .unwrap_or(16)
7440 });
7441 let use_mma = std::env::var("MEMRA_MOE_MMA")
7442 .map(|v| v != "0")
7443 .unwrap_or(true)
7444 && t >= mma_t
7445 && q8_expert_dec_supported(m.gate_exps.qtype)
7446 && q8_expert_dec_supported(m.up_exps.qtype)
7447 && q8_expert_dec_supported(m.down_exps.qtype)
7448 && n_embd % 256 == 0
7449 && n_ff_exp % 256 == 0;
7450 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
7466 && q8_expert_dec_supported(m.up_exps.qtype)
7467 && q8_expert_dec_supported(m.down_exps.qtype)
7468 && n_embd % 256 == 0
7469 && n_ff_exp % 256 == 0;
7470 let f16g_mode = crate::moe_f16g_mode();
7471 let f16g = f16g_mode != 0
7472 && t >= mma_t
7473 && (f16g_mode != 3 || !mma_capable)
7474 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
7475 && f16g_proj_ok(m.up_exps.qtype, n_embd)
7476 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
7477 if use_mma || f16g {
7478 let y_down = if f16g {
7486 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
7490 let csr_tok_d = e.htod_i32(&csr_tok)?;
7491 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
7492 let g_csr = e.moe_f16_grouped(
7493 &dev.ptr_row,
7494 0,
7495 n_expert,
7496 &exi,
7497 &ex_off,
7498 &exo,
7499 &z_f16,
7500 &z_s,
7501 n_embd,
7502 n_ff_exp,
7503 n_active,
7504 n_pairs,
7505 m.gate_exps.qtype,
7506 rbg_d,
7507 )?;
7508 let u_csr = e.moe_f16_grouped(
7509 &dev.ptr_row,
7510 1,
7511 n_expert,
7512 &exi,
7513 &ex_off,
7514 &exo,
7515 &z_f16,
7516 &z_s,
7517 n_embd,
7518 n_ff_exp,
7519 n_active,
7520 n_pairs,
7521 m.up_exps.qtype,
7522 rbu_d,
7523 )?;
7524 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
7525 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
7526 let d_csr = e.moe_f16_grouped(
7527 &dev.ptr_row,
7528 2,
7529 n_expert,
7530 &exi,
7531 &ex_off,
7532 &exo,
7533 &a_f16,
7534 &a_s,
7535 n_ff_exp,
7536 n_embd,
7537 n_active,
7538 n_pairs,
7539 m.down_exps.qtype,
7540 m.down_exps.row_bytes,
7541 )?;
7542 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
7543 } else {
7544 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
7546 let gate = e.mmq_iq_experts(
7547 &dev.ptr_row,
7548 0,
7549 n_expert,
7550 &exi,
7551 &exo,
7552 &exp_d,
7553 &pt,
7554 &z_scr,
7555 n_embd,
7556 n_ff_exp,
7557 n_active,
7558 n_pairs,
7559 t,
7560 m.gate_exps.qtype,
7561 rbg_d,
7562 )?;
7563 let up = e.mmq_iq_experts(
7564 &dev.ptr_row,
7565 1,
7566 n_expert,
7567 &exi,
7568 &exo,
7569 &exp_d,
7570 &pt,
7571 &z_scr,
7572 n_embd,
7573 n_ff_exp,
7574 n_active,
7575 n_pairs,
7576 t,
7577 m.up_exps.qtype,
7578 rbu_d,
7579 )?;
7580 let a_scr = if crate::moe_fuse_actq_on() {
7586 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
7587 } else {
7588 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7589 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
7590 };
7591 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7592 let pself = e.htod_i32(&pair_self)?;
7593 e.mmq_iq_experts(
7594 &dev.ptr_row,
7595 2,
7596 n_expert,
7597 &exi,
7598 &exo,
7599 &exp_d,
7600 &pself,
7601 &a_scr,
7602 n_ff_exp,
7603 n_embd,
7604 n_active,
7605 n_pairs,
7606 n_pairs,
7607 m.down_exps.qtype,
7608 m.down_exps.row_bytes,
7609 )?
7610 };
7611 let mut moe_out = e.uninit(t * n_embd)?;
7612 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7613 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7614 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7615 {
7616 let n_ff_sh = gate_shexp.out_features();
7617 let sg_gate = e.matmul(gate_shexp, z, t)?;
7618 let sg_up = e.matmul(up_shexp, z, t)?;
7619 let mut sa = e.uninit(t * n_ff_sh)?;
7620 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7621 let sh = e.matmul(down_shexp, &sa, t)?;
7622 let g = match &m.gate_inp_shexp {
7628 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7629 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7630 }
7631 Some(gate_inp_shexp) => {
7632 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7633 let mut g = e.uninit(t)?;
7634 e.sigmoid(&gs, &mut g, t)?;
7635 g
7636 }
7637 None => e.htod(&vec![1.0f32; t])?,
7638 };
7639 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7640 }
7641 return Ok(moe_out);
7642 }
7643
7644 let dec = std::env::var("MEMRA_MOE_DEC")
7647 .map(|v| v != "0")
7648 .unwrap_or(true);
7649 let matvec = |proj,
7650 exi: &_,
7651 exo: &_,
7652 exp_d: &_,
7653 pt: &_,
7654 aq: &_,
7655 ad: &_,
7656 inf,
7657 outf,
7658 qtype,
7659 rb|
7660 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7661 let dec = dec && q8_expert_dec_supported(qtype);
7663 if dec {
7664 e.moe_pairs_matvec_q8_dec(
7665 &dev.ptr_row,
7666 proj,
7667 exi,
7668 exo,
7669 exp_d,
7670 pt,
7671 aq,
7672 ad,
7673 inf,
7674 outf,
7675 n_expert,
7676 n_active,
7677 n_pairs,
7678 qtype,
7679 rb,
7680 )
7681 } else {
7682 e.moe_pairs_matvec_q8_em(
7683 &dev.ptr_row,
7684 proj,
7685 exi,
7686 exo,
7687 exp_d,
7688 pt,
7689 aq,
7690 ad,
7691 inf,
7692 outf,
7693 n_expert,
7694 n_active,
7695 n_pairs,
7696 qtype,
7697 rb,
7698 )
7699 }
7700 };
7701 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7702 let gate = matvec(
7703 0,
7704 &exi,
7705 &exo,
7706 &exp_d,
7707 &pt,
7708 &zq,
7709 &zd,
7710 n_embd,
7711 n_ff_exp,
7712 m.gate_exps.qtype,
7713 rbg_d,
7714 )?;
7715 let up = matvec(
7716 1,
7717 &exi,
7718 &exo,
7719 &exp_d,
7720 &pt,
7721 &zq,
7722 &zd,
7723 n_embd,
7724 n_ff_exp,
7725 m.up_exps.qtype,
7726 rbu_d,
7727 )?;
7728 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7729 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7730 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7732 let pself = e.htod_i32(&pair_self)?;
7733 let y_down = matvec(
7734 2,
7735 &exi,
7736 &exo,
7737 &exp_d,
7738 &pself,
7739 &aq2,
7740 &ad2,
7741 n_ff_exp,
7742 n_embd,
7743 m.down_exps.qtype,
7744 m.down_exps.row_bytes,
7745 )?;
7746 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7748
7749 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7753 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7754 {
7755 let n_ff_sh = gate_shexp.out_features();
7756 let step_exact = true;
7760 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
7761 let (sg_gate, sg_up) = if step_exact && t == 1 {
7762 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, None)?
7763 } else if verify_t {
7764 let mut fused = None;
7765 if crate::spec::spec_fused_t()
7766 && (2..=4).contains(&t)
7767 && e.uses_q8_1_fast(gate_shexp)
7768 && e.uses_q8_1_fast(up_shexp)
7769 {
7770 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7771 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
7772 }
7773 match fused {
7774 Some(pair) => pair,
7775 None => (
7776 e.matmul_decode_exact(gate_shexp, z, t)?,
7777 e.matmul_decode_exact(up_shexp, z, t)?,
7778 ),
7779 }
7780 } else {
7781 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
7782 };
7783 let mut sa = e.uninit(t * n_ff_sh)?;
7784 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7785 let sh = if verify_t {
7786 e.matmul_decode_exact(down_shexp, &sa, t)?
7787 } else {
7788 e.matmul(down_shexp, &sa, t)?
7789 };
7790 let g = match &m.gate_inp_shexp {
7795 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7796 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7797 }
7798 Some(gate_inp_shexp) => {
7799 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7800 let mut g = e.uninit(t)?;
7801 e.sigmoid(&gs, &mut g, t)?;
7802 g
7803 }
7804 None => e.htod(&vec![1.0f32; t])?,
7805 };
7806 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7807 }
7808 Ok(moe_out)
7809 }
7810
7811 #[allow(clippy::too_many_arguments)]
7813 #[allow(clippy::too_many_arguments)]
7814 fn moe_ffn_dev(
7815 e: &Engine,
7816 m: &MoeWeights,
7817 z: &CudaSlice<f32>,
7818 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7819 logits: &CudaSlice<f32>,
7820 t: usize,
7821 cfg: &ModelConfig,
7822 il: u16,
7823 max_block: usize,
7824 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7825 let moe = cfg.moe.as_ref().unwrap();
7826 let n_embd = cfg.n_embd as usize;
7827 let n_expert = moe.expert_count as usize;
7828 let n_used = moe.expert_used_count as usize;
7829 let n_ff_exp = moe.expert_ff_length as usize;
7830 debug_assert!(
7834 cfg.sigmoid_router().is_none(),
7835 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
7836 );
7837 debug_assert!(
7838 !cfg.swiglu_clamped_at(il as u32),
7839 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
7840 );
7841
7842 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
7844 if m.has_macros {
7847 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
7848 }
7849
7850 let mut moe_out = e.uninit(t * n_embd)?;
7852
7853 if let Some(dev) = m.dev_exps.as_ref() {
7856 let (rbg_d, rbu_d) = if dev.gu_il {
7859 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7860 (sxx, sxx)
7861 } else {
7862 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7863 };
7864 let q8 = moe_q8_enabled()
7865 && q8_expert_supported(m.gate_exps.qtype)
7866 && q8_expert_supported(m.up_exps.qtype)
7867 && q8_expert_supported(m.down_exps.qtype);
7868 let rows_arm = q8
7877 && t > 1
7878 && crate::spec::spec_m2()
7879 && n_ff_exp == 512
7880 && n_used <= 8
7881 && std::env::var("MEMRA_MOE_DEVQ8_GU")
7882 .map(|v| v.is_empty() || v == "v")
7883 .unwrap_or(true)
7884 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
7885 .map(|v| v.is_empty() || v == "w8h2v")
7886 .unwrap_or(true);
7887 let csr_mode = std::env::var("MEMRA_MOE_CSR")
7896 .ok()
7897 .and_then(|v| v.parse::<i32>().ok())
7898 .unwrap_or(1);
7899 let csr_nvfp4_probe = std::env::var("MEMRA_MOE_CSR_NVFP4").as_deref() == Ok("1");
7916 let csr_qt = |qt: i32| {
7917 qt == crate::QT_IQ4_XS
7918 || qt == crate::QT_IQ3_S
7919 || (csr_nvfp4_probe && qt == crate::QT_NVFP4)
7920 };
7921 let csr_t_max = if csr_nvfp4_probe { MOE_DEV_MAX_T } else { 10 };
7922 let csr_uniform = m.gate_exps.qtype == m.up_exps.qtype;
7923 let csr_arm = rows_arm
7924 && csr_mode > 0
7925 && t <= csr_t_max
7926 && csr_uniform
7927 && csr_qt(m.gate_exps.qtype)
7928 && csr_qt(m.up_exps.qtype)
7929 && csr_qt(m.down_exps.qtype);
7930 if csr_arm {
7931 if csr_mode == 2 {
7932 static ENGAGED: std::sync::Once = std::sync::Once::new();
7933 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
7934 }
7935 let n_pairs = t * n_used;
7936 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7937 let act = e.moe_gate_up_silu8_dev_q8_csr(
7938 &dev.ptr_row,
7939 &sel_d,
7940 &zq,
7941 &zd,
7942 n_pairs,
7943 n_embd,
7944 n_ff_exp,
7945 n_used,
7946 n_expert,
7947 m.gate_exps.qtype,
7948 m.up_exps.qtype,
7949 rbg_d,
7950 rbu_d,
7951 )?;
7952 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7953 e.moe_down8_fma_dev_q8_rows(
7957 &dev.ptr_row,
7958 &sel_d,
7959 &w_d,
7960 &aq2,
7961 &ad2,
7962 &mut moe_out,
7963 t,
7964 n_ff_exp,
7965 n_embd,
7966 n_used,
7967 n_expert,
7968 m.down_exps.qtype,
7969 m.down_exps.row_bytes,
7970 )?;
7971 if csr_mode == 2 {
7972 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
7974 &dev.ptr_row,
7975 &sel_d,
7976 &zq,
7977 &zd,
7978 t,
7979 n_embd,
7980 n_ff_exp,
7981 n_used,
7982 n_expert,
7983 m.gate_exps.qtype,
7984 m.up_exps.qtype,
7985 rbg_d,
7986 rbu_d,
7987 &m.dev_macros,
7988 )?;
7989 let mut out_r = e.uninit(t * n_embd)?;
7990 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
7991 e.moe_down8_fma_dev_q8_rows(
7992 &dev.ptr_row,
7993 &sel_d,
7994 &w_d,
7995 &aq2r,
7996 &ad2r,
7997 &mut out_r,
7998 t,
7999 n_ff_exp,
8000 n_embd,
8001 n_used,
8002 n_expert,
8003 m.down_exps.qtype,
8004 m.down_exps.row_bytes,
8005 )?;
8006 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
8007 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
8008 let ba = a1
8009 .iter()
8010 .zip(&a2)
8011 .filter(|(x, y)| x.to_bits() != y.to_bits())
8012 .count();
8013 let bo = o1
8014 .iter()
8015 .zip(&o2)
8016 .filter(|(x, y)| x.to_bits() != y.to_bits())
8017 .count();
8018 if ba + bo > 0 {
8019 eprintln!(
8020 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
8021 a1.len(),
8022 o1.len()
8023 );
8024 let sel_h = e.dtoh_i32(&sel_d)?;
8026 let mut shown = 0;
8027 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
8028 if x.to_bits() != y.to_bits() && shown < 4 {
8029 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
8030 let ex = sel_h[p];
8031 let npx = sel_h.iter().filter(|&&v| v == ex).count();
8032 eprintln!(
8033 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
8034 );
8035 shown += 1;
8036 }
8037 }
8038 std::process::exit(3);
8039 }
8040 }
8041 } else if rows_arm {
8042 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
8045 use std::sync::atomic::{AtomicU64, Ordering};
8046 static PAIRS: AtomicU64 = AtomicU64::new(0);
8047 static UNIQ: AtomicU64 = AtomicU64::new(0);
8048 static CALLS: AtomicU64 = AtomicU64::new(0);
8049 let sel_h = e.dtoh_i32(&sel_d)?;
8050 let mut u: Vec<i32> = sel_h.clone();
8051 u.sort_unstable();
8052 u.dedup();
8053 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
8054 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
8055 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
8056 if c % 480 == 0 {
8057 let p = PAIRS.load(Ordering::Relaxed);
8058 let q = UNIQ.load(Ordering::Relaxed);
8059 eprintln!(
8060 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
8061 q as f64 / p as f64
8062 );
8063 }
8064 }
8065 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8066 let act = e.moe_gate_up_silu8_dev_q8_rows(
8067 &dev.ptr_row,
8068 &sel_d,
8069 &zq,
8070 &zd,
8071 t,
8072 n_embd,
8073 n_ff_exp,
8074 n_used,
8075 n_expert,
8076 m.gate_exps.qtype,
8077 m.up_exps.qtype,
8078 rbg_d,
8079 rbu_d,
8080 &m.dev_macros,
8081 )?;
8082 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8083 e.moe_down8_fma_dev_q8_rows(
8084 &dev.ptr_row,
8085 &sel_d,
8086 &w_d,
8087 &aq2,
8088 &ad2,
8089 &mut moe_out,
8090 t,
8091 n_ff_exp,
8092 n_embd,
8093 n_used,
8094 n_expert,
8095 m.down_exps.qtype,
8096 m.down_exps.row_bytes,
8097 )?;
8098 } else {
8099 for tok in 0..t {
8100 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8101 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8102 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8103 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8104 if q8 {
8105 let (zq, zd) = match (t, zq8) {
8106 (1, Some((q, d))) => (q.clone(), d.clone()),
8107 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
8108 };
8109 let act = e.moe_gate_up_silu8_dev_q8(
8110 &dev.ptr_row,
8111 &selt,
8112 &zq,
8113 &zd,
8114 n_embd,
8115 n_ff_exp,
8116 n_used,
8117 n_expert,
8118 m.gate_exps.qtype,
8119 m.up_exps.qtype,
8120 rbg_d,
8121 rbu_d,
8122 &m.dev_macros,
8123 )?;
8124 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8125 e.moe_down8_fma_dev_q8(
8126 &dev.ptr_row,
8127 &selt,
8128 &wt,
8129 &aq2,
8130 &ad2,
8131 &mut dst,
8132 n_ff_exp,
8133 n_embd,
8134 n_used,
8135 n_expert,
8136 m.down_exps.qtype,
8137 m.down_exps.row_bytes,
8138 )?;
8139 } else {
8140 let act = e.moe_gate_up_silu8_dev(
8141 &dev.ptr_row,
8142 &selt,
8143 &zt,
8144 n_embd,
8145 n_ff_exp,
8146 n_used,
8147 n_expert,
8148 m.gate_exps.qtype,
8149 m.up_exps.qtype,
8150 rbg_d,
8151 rbu_d,
8152 &m.dev_macros,
8153 )?;
8154 e.moe_down8_fma_dev(
8155 &dev.ptr_row,
8156 &selt,
8157 &wt,
8158 &act,
8159 &mut dst,
8160 n_ff_exp,
8161 n_embd,
8162 n_used,
8163 n_expert,
8164 m.down_exps.qtype,
8165 m.down_exps.row_bytes,
8166 )?;
8167 }
8168 }
8169 }
8170 } else {
8171 let q8 = moe_q8_enabled()
8178 && q8_expert_supported(m.gate_exps.qtype)
8179 && q8_expert_supported(m.up_exps.qtype)
8180 && q8_expert_supported(m.down_exps.qtype);
8181 e.with_moe_cache(max_block, |c, eng| {
8182 let row = c
8183 .layer_dev_row(il, n_expert, eng)?
8184 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
8185 for tok in 0..t {
8186 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8187 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8188 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8189 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8190 if q8 {
8191 let (zq, zd) = match (t, zq8) {
8192 (1, Some((q, d))) => (q.clone(), d.clone()),
8193 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
8194 };
8195 let act = eng.moe_gate_up_silu8_dev_q8(
8196 row,
8197 &selt,
8198 &zq,
8199 &zd,
8200 n_embd,
8201 n_ff_exp,
8202 n_used,
8203 n_expert,
8204 m.gate_exps.qtype,
8205 m.up_exps.qtype,
8206 m.gate_exps.row_bytes,
8207 m.up_exps.row_bytes,
8208 &m.dev_macros,
8209 )?;
8210 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
8211 eng.moe_down8_fma_dev_q8(
8212 row,
8213 &selt,
8214 &wt,
8215 &aq2,
8216 &ad2,
8217 &mut dst,
8218 n_ff_exp,
8219 n_embd,
8220 n_used,
8221 n_expert,
8222 m.down_exps.qtype,
8223 m.down_exps.row_bytes,
8224 )?;
8225 } else {
8226 let act = eng.moe_gate_up_silu8_dev(
8227 row,
8228 &selt,
8229 &zt,
8230 n_embd,
8231 n_ff_exp,
8232 n_used,
8233 n_expert,
8234 m.gate_exps.qtype,
8235 m.up_exps.qtype,
8236 m.gate_exps.row_bytes,
8237 m.up_exps.row_bytes,
8238 &m.dev_macros,
8239 )?;
8240 eng.moe_down8_fma_dev(
8241 row,
8242 &selt,
8243 &wt,
8244 &act,
8245 &mut dst,
8246 n_ff_exp,
8247 n_embd,
8248 n_used,
8249 n_expert,
8250 m.down_exps.qtype,
8251 m.down_exps.row_bytes,
8252 )?;
8253 }
8254 }
8255 c.hits += (t * 3 * n_used) as u64;
8257 Ok(())
8258 })?;
8259 }
8260
8261 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8266 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8267 {
8268 let n_ff_sh = gate_shexp.out_features();
8269 let verify_t = t > 1 && t < PRIME_MIN_T;
8272 let (sg_gate, sg_up) = if t == 1 {
8273 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
8274 } else if verify_t {
8275 let mut fused = None;
8279 if crate::spec::spec_fused_t()
8280 && (2..=4).contains(&t)
8281 && e.uses_q8_1_fast(gate_shexp)
8282 && e.uses_q8_1_fast(up_shexp)
8283 {
8284 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8285 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8286 }
8287 match fused {
8288 Some(pair) => pair,
8289 None => (
8290 e.matmul_decode_exact(gate_shexp, z, t)?,
8291 e.matmul_decode_exact(up_shexp, z, t)?,
8292 ),
8293 }
8294 } else {
8295 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8296 };
8297 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8299 let sh = if verify_t {
8300 e.matmul_decode_exact(down_shexp, &sa, t)?
8301 } else {
8302 e.matmul(down_shexp, &sa, t)?
8303 };
8304 let g = match &m.gate_inp_shexp {
8308 Some(gate_inp_shexp) => {
8309 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
8312 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8313 } else {
8314 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8315 let mut g = e.uninit(t)?;
8316 e.sigmoid(&gs, &mut g, t)?;
8317 g
8318 }
8319 }
8320 None => e.htod(&vec![1.0f32; t])?,
8321 };
8322 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8323 }
8324
8325 Ok(moe_out)
8326 }
8327
8328 #[allow(clippy::too_many_arguments)]
8338 #[allow(clippy::too_many_arguments)]
8341 fn moe_gdec_token_q8(
8342 e: &Engine,
8343 m: &MoeWeights,
8344 il: u16,
8345 max_block: usize,
8346 zq: &CudaSlice<i8>,
8347 zd: &CudaSlice<f32>,
8348 sel: &[u32],
8349 w: &[f32],
8350 moe_out: &mut CudaSlice<f32>,
8351 tok: usize,
8352 n_embd: usize,
8353 n_ff_exp: usize,
8354 n_used: usize,
8355 ) -> Result<bool, Box<dyn std::error::Error>> {
8356 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8357 use cudarc::driver::DevicePtr;
8358 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8359 let mut g = [0u64; 8];
8360 let mut u = [0u64; 8];
8361 let mut d = [0u64; 8];
8362 for (j, &ex) in sel.iter().enumerate() {
8363 let ex = ex as u16;
8364 let (Some(sg), Some(su), Some(sd)) = (
8365 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8366 c.resident(BlockId::new(il, PROJ_UP, ex)),
8367 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8368 ) else {
8369 return Ok(None);
8370 };
8371 let __s = eng.stream();
8372 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8373 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8374 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8375 g[j] = pg as u64;
8376 u[j] = pu as u64;
8377 d[j] = pd as u64;
8378 }
8379 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8380 for &ex in sel {
8381 let ex = ex as u16;
8382 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8383 c.note_profile_hit(BlockId::new(il, proj, ex));
8384 }
8385 }
8386 }
8387 c.hits += (3 * n_used) as u64;
8388 Ok(Some((g, u, d)))
8389 })?;
8390 let Some((g, u, d)) = ptrs else {
8391 return Ok(false);
8392 };
8393 let mut wv = [0f32; 8];
8394 wv[..n_used].copy_from_slice(w);
8395 let act = e.moe_gate_up_silu8_q8(
8396 crate::WPtr8(g),
8397 crate::WPtr8(u),
8398 zq,
8399 zd,
8400 n_embd,
8401 n_ff_exp,
8402 n_used,
8403 m.gate_exps.qtype,
8404 m.up_exps.qtype,
8405 m.gate_exps.row_bytes,
8406 m.up_exps.row_bytes,
8407 )?;
8408 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8410 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8411 e.moe_down8_fma_q8(
8412 crate::WPtr8(d),
8413 crate::F32x8(wv),
8414 &aq2,
8415 &ad2,
8416 &mut dst,
8417 n_ff_exp,
8418 n_embd,
8419 n_used,
8420 m.down_exps.qtype,
8421 m.down_exps.row_bytes,
8422 )?;
8423 Ok(true)
8424 }
8425
8426 fn moe_gdec_token(
8427 e: &Engine,
8428 m: &MoeWeights,
8429 il: u16,
8430 max_block: usize,
8431 zt: &cudarc::driver::CudaView<f32>,
8432 sel: &[u32],
8433 w: &[f32],
8434 moe_out: &mut CudaSlice<f32>,
8435 tok: usize,
8436 n_embd: usize,
8437 n_ff_exp: usize,
8438 n_used: usize,
8439 ) -> Result<bool, Box<dyn std::error::Error>> {
8440 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8441 use cudarc::driver::DevicePtr;
8442 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8444 let mut g = [0u64; 8];
8445 let mut u = [0u64; 8];
8446 let mut d = [0u64; 8];
8447 for (j, &ex) in sel.iter().enumerate() {
8448 let ex = ex as u16;
8449 let (Some(sg), Some(su), Some(sd)) = (
8450 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8451 c.resident(BlockId::new(il, PROJ_UP, ex)),
8452 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8453 ) else {
8454 return Ok(None);
8455 };
8456 let __s = eng.stream();
8457 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8458 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8459 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8460 g[j] = pg as u64;
8461 u[j] = pu as u64;
8462 d[j] = pd as u64;
8463 }
8464 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8465 for &ex in sel {
8466 let ex = ex as u16;
8467 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8468 c.note_profile_hit(BlockId::new(il, proj, ex));
8469 }
8470 }
8471 }
8472 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
8474 })?;
8475 let Some((g, u, d)) = ptrs else {
8476 return Ok(false);
8477 };
8478 let mut wv = [0f32; 8];
8479 wv[..n_used].copy_from_slice(w);
8480 let act = e.moe_gate_up_silu8(
8482 crate::WPtr8(g),
8483 crate::WPtr8(u),
8484 zt,
8485 n_embd,
8486 n_ff_exp,
8487 n_used,
8488 m.gate_exps.qtype,
8489 m.up_exps.qtype,
8490 m.gate_exps.row_bytes,
8491 m.up_exps.row_bytes,
8492 )?;
8493 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8494 e.moe_down8_fma_into(
8495 crate::WPtr8(d),
8496 crate::F32x8(wv),
8497 &act,
8498 &mut dst,
8499 n_ff_exp,
8500 n_embd,
8501 n_used,
8502 m.down_exps.qtype,
8503 m.down_exps.row_bytes,
8504 )?;
8505 Ok(true)
8506 }
8507
8508 fn moe_cached_gemm_q8(
8513 e: &Engine,
8514 il: u16,
8515 proj: u8,
8516 ex: usize,
8517 m: &MoeWeights,
8518 max_block: usize,
8519 aq: &CudaSlice<i8>,
8520 ad: &CudaSlice<f32>,
8521 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8522 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8523 let exps = match proj {
8524 PROJ_GATE => &m.gate_exps,
8525 PROJ_UP => &m.up_exps,
8526 _ => &m.down_exps,
8527 };
8528 let layout = exps.expert_layout(ex);
8529 let id = BlockId::new(il, proj, ex as u16);
8530 let source = exps.expert_source(ex);
8531 e.with_moe_cache(max_block, |c, eng| {
8532 let slot = c.dispatch_source(id, source, eng)?;
8533 let DispatchSlot::Resident(sl) = slot;
8534 let buf = c.slot(sl);
8535 eng.qmatvec_expert_q8(
8536 buf,
8537 0..layout.len,
8538 aq,
8539 ad,
8540 1,
8541 exps.in_f,
8542 exps.out_f,
8543 layout.qtype,
8544 layout.row_bytes,
8545 )
8546 })
8547 }
8548
8549 fn moe_cached_gemm(
8550 e: &Engine,
8551 il: u16,
8552 proj: u8,
8553 ex: usize,
8554 m: &MoeWeights,
8555 max_block: usize,
8556 x: &cudarc::driver::CudaView<f32>,
8557 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8558 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8559 let exps = match proj {
8560 PROJ_GATE => &m.gate_exps,
8561 PROJ_UP => &m.up_exps,
8562 _ => &m.down_exps,
8563 };
8564 let layout = exps.expert_layout(ex);
8565 let id = BlockId::new(il, proj, ex as u16);
8566 let source = exps.expert_source(ex);
8567 e.with_moe_cache(max_block, |c, eng| {
8569 let slot = c.dispatch_source(id, source, eng)?;
8570 let DispatchSlot::Resident(sl) = slot;
8573 let buf = c.slot(sl);
8574 eng.qmatvec_view(
8575 buf,
8576 0..layout.len,
8577 x,
8578 1,
8579 exps.in_f,
8580 exps.out_f,
8581 layout.qtype,
8582 layout.row_bytes,
8583 )
8584 })
8585 }
8586
8587 fn moe_profile_admit_expert(
8591 e: &Engine,
8592 il: u16,
8593 ex: usize,
8594 m: &MoeWeights,
8595 max_block: usize,
8596 ) -> Result<(), Box<dyn std::error::Error>> {
8597 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8598 e.with_moe_cache(max_block, |cache, eng| {
8599 for (proj, exps) in [
8600 (PROJ_GATE, &m.gate_exps),
8601 (PROJ_UP, &m.up_exps),
8602 (PROJ_DOWN, &m.down_exps),
8603 ] {
8604 let id = BlockId::new(il, proj, ex as u16);
8605 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
8606 }
8607 Ok(())
8608 })
8609 }
8610
8611 #[allow(clippy::too_many_arguments)]
8614 fn moe_frozen_gemm(
8615 e: &Engine,
8616 il: u16,
8617 proj: u8,
8618 ex: usize,
8619 m: &MoeWeights,
8620 max_block: usize,
8621 x: &cudarc::driver::CudaView<f32>,
8622 scratch: &mut Option<CudaSlice<u8>>,
8623 scratch_len: usize,
8624 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8625 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
8626 let exps = match proj {
8627 PROJ_GATE => &m.gate_exps,
8628 PROJ_UP => &m.up_exps,
8629 _ => &m.down_exps,
8630 };
8631 let layout = exps.expert_layout(ex);
8632 let id = BlockId::new(il, proj, ex as u16);
8633 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
8634 let Some(slot) = cache.resident(id) else {
8635 return Ok(None);
8636 };
8637 let buf = cache.slot(slot);
8638 Ok(Some(eng.qmatvec_view(
8639 buf,
8640 0..layout.len,
8641 x,
8642 1,
8643 exps.in_f,
8644 exps.out_f,
8645 layout.qtype,
8646 layout.row_bytes,
8647 )?))
8648 })? {
8649 return Ok(output);
8650 }
8651 if scratch.is_none() {
8652 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
8653 }
8654 let scratch = scratch.as_mut().unwrap();
8655 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
8656 e.qmatvec_view(
8657 scratch,
8658 0..layout.len,
8659 x,
8660 1,
8661 exps.in_f,
8662 exps.out_f,
8663 layout.qtype,
8664 layout.row_bytes,
8665 )
8666 }
8667
8668 fn moe_prefetch_expert(
8669 e: &Engine,
8670 il: u16,
8671 ex: usize,
8672 m: &MoeWeights,
8673 max_block: usize,
8674 keep: &[crate::moe_cache::BlockId],
8675 ) -> Result<(), Box<dyn std::error::Error>> {
8676 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8677 e.with_moe_cache(max_block, |c, eng| {
8678 for (proj, exps) in [
8679 (PROJ_GATE, &m.gate_exps),
8680 (PROJ_UP, &m.up_exps),
8681 (PROJ_DOWN, &m.down_exps),
8682 ] {
8683 let id = BlockId::new(il, proj, ex as u16);
8684 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
8685 }
8686 Ok(())
8687 })
8688 }
8689
8690 fn moe_prefetch_disk_expert(
8693 e: &Engine,
8694 il: u16,
8695 ex: usize,
8696 m: &MoeWeights,
8697 max_block: usize,
8698 keep: &[crate::moe_cache::BlockId],
8699 ) -> Result<(), Box<dyn std::error::Error>> {
8700 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8701 e.with_moe_cache(max_block, |c, eng| {
8702 for (proj, exps) in [
8703 (PROJ_GATE, &m.gate_exps),
8704 (PROJ_UP, &m.up_exps),
8705 (PROJ_DOWN, &m.down_exps),
8706 ] {
8707 let source = exps.expert_source(ex);
8708 if let crate::model::ExpertSource::Disk { .. } = &source {
8709 let id = BlockId::new(il, proj, ex as u16);
8710 let _ = c.prefetch_source(id, source, keep, eng)?;
8711 }
8712 }
8713 Ok(())
8714 })
8715 }
8716
8717 #[inline]
8718 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
8719 let _ = m.gate_exps.prefetch_expert_pages(ex);
8720 let _ = m.up_exps.prefetch_expert_pages(ex);
8721 let _ = m.down_exps.prefetch_expert_pages(ex);
8722 }
8723}
8724
8725impl HybridModel {
8742 #[allow(clippy::too_many_arguments)]
8746 fn moe_ffn_grouped_resident_q8(
8747 e: &Engine,
8748 m: &MoeWeights,
8749 z: &CudaSlice<f32>,
8750 t: usize,
8751 cfg: &ModelConfig,
8752 il: u16,
8753 sel_all: &[u32],
8754 w_all: &[f32],
8755 table: &CudaSlice<u64>,
8756 gu_il: bool,
8757 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8758 let moe = cfg.moe.as_ref().unwrap();
8759 let n_embd = cfg.n_embd as usize;
8760 let n_expert = moe.expert_count as usize;
8761 let n_used = moe.expert_used_count as usize;
8762 let n_ff_exp = moe.expert_ff_length as usize;
8763 let n_pairs = t * n_used;
8764 debug_assert_eq!(sel_all.len(), n_pairs);
8765 debug_assert_eq!(w_all.len(), n_pairs);
8766 debug_assert!(
8767 m.gate_exps.macros.is_none()
8768 && m.up_exps.macros.is_none()
8769 && m.down_exps.macros.is_none(),
8770 "resident grouped q8 does not fold per-expert macro scales",
8771 );
8772
8773 if !cfg.swiglu_clamped_at(il as u32) {
8779 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8780 let sel_d = e.htod_i32(&sel)?;
8781 let w_d = e.htod(w_all)?;
8782 let (gate_row_bytes, up_row_bytes) = if gu_il {
8783 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8784 (combined, combined)
8785 } else {
8786 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8787 };
8788 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8789 let act = e.moe_gate_up_silu8_dev_q8_rows(
8790 table,
8791 &sel_d,
8792 &zq,
8793 &zd,
8794 t,
8795 n_embd,
8796 n_ff_exp,
8797 n_used,
8798 n_expert,
8799 m.gate_exps.qtype,
8800 m.up_exps.qtype,
8801 gate_row_bytes,
8802 up_row_bytes,
8803 &m.dev_macros,
8804 )?;
8805 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8806 let mut moe_out = e.uninit(t * n_embd)?;
8807 e.moe_down8_fma_dev_q8_rows_g(
8808 table,
8809 &sel_d,
8810 &w_d,
8811 &aq2,
8812 &ad2,
8813 &mut moe_out,
8814 t,
8815 n_ff_exp,
8816 n_embd,
8817 n_used,
8818 n_expert,
8819 m.down_exps.qtype,
8820 m.down_exps.row_bytes,
8821 )?;
8822
8823 if std::env::var("MEMRA_MOE_STATS").is_ok() {
8824 let mut counts = vec![0usize; n_expert];
8825 for &expert in sel_all {
8826 counts[expert as usize] += 1;
8827 }
8828 let mut sizes: Vec<usize> =
8829 counts.into_iter().filter(|&count| count != 0).collect();
8830 sizes.sort_unstable();
8831 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
8832 println!(
8833 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
8834 m_e: min={} median={} mean={mean:.1} max={}",
8835 sizes.len(),
8836 n_expert,
8837 sizes.first().copied().unwrap_or(0),
8838 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
8839 sizes.last().copied().unwrap_or(0),
8840 );
8841 }
8842 return Ok(moe_out);
8843 }
8844
8845 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
8849 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8850 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
8851 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
8852
8853 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
8854 for (pair, &expert) in pair_ex.iter().enumerate() {
8855 by_expert[expert as usize].push(pair as i32);
8856 }
8857
8858 let pair_tok_d = e.htod_i32(&pair_tok)?;
8859 let pair_ex_d = e.htod_i32(&pair_ex)?;
8860 let pair_w_d = e.htod(w_all)?;
8861 let tok_off_d = e.htod_i32(&tok_off)?;
8862 let tok_ids_d = e.htod_i32(&tok_ids)?;
8863
8864 let matvec = |proj: i32,
8865 pair_rows: &CudaSlice<i32>,
8866 aq: &CudaSlice<i8>,
8867 ad: &CudaSlice<f32>,
8868 in_f: usize,
8869 out_f: usize,
8870 qtype: i32,
8871 row_bytes: usize|
8872 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8873 e.moe_pairs_matvec_q8(
8874 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
8875 row_bytes,
8876 )
8877 };
8878
8879 let (gate_row_bytes, up_row_bytes) = if gu_il {
8880 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8881 (combined, combined)
8882 } else {
8883 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8884 };
8885 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8886 let gate = matvec(
8887 0,
8888 &pair_tok_d,
8889 &zq,
8890 &zd,
8891 n_embd,
8892 n_ff_exp,
8893 m.gate_exps.qtype,
8894 gate_row_bytes,
8895 )?;
8896 let up = matvec(
8897 1,
8898 &pair_tok_d,
8899 &zq,
8900 &zd,
8901 n_embd,
8902 n_ff_exp,
8903 m.up_exps.qtype,
8904 up_row_bytes,
8905 )?;
8906 let mut act = e.uninit(n_pairs * n_ff_exp)?;
8907 Self::ffn_act_lim(
8908 e,
8909 cfg,
8910 &gate,
8911 &up,
8912 1.0,
8913 1.0,
8914 cfg.clamp_exp_at(il as u32),
8915 &mut act,
8916 n_pairs * n_ff_exp,
8917 )?;
8918 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8919 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
8920 let pair_self_d = e.htod_i32(&pair_self)?;
8921 let down = matvec(
8922 2,
8923 &pair_self_d,
8924 &aq2,
8925 &ad2,
8926 n_ff_exp,
8927 n_embd,
8928 m.down_exps.qtype,
8929 m.down_exps.row_bytes,
8930 )?;
8931 let mut moe_out = e.uninit(t * n_embd)?;
8932 e.moe_pairs_scatter(
8933 &down,
8934 &pair_w_d,
8935 &tok_off_d,
8936 &tok_ids_d,
8937 &mut moe_out,
8938 t,
8939 n_embd,
8940 )?;
8941
8942 if std::env::var("MEMRA_MOE_STATS").is_ok() {
8943 let mut sizes: Vec<usize> = by_expert
8944 .iter()
8945 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
8946 .collect();
8947 sizes.sort_unstable();
8948 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
8949 println!(
8950 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
8951 m_e: min={} median={} mean={mean:.1} max={}",
8952 sizes.len(),
8953 n_expert,
8954 sizes.first().copied().unwrap_or(0),
8955 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
8956 sizes.last().copied().unwrap_or(0),
8957 );
8958 }
8959 Ok(moe_out)
8960 }
8961
8962 #[allow(clippy::too_many_arguments)]
8968 fn shexp_split_matvec(
8969 e: &Engine,
8970 rank1: &Engine,
8971 wg: &CudaSlice<u8>,
8972 wu: &CudaSlice<u8>,
8973 wd: &CudaSlice<u8>,
8974 z: &CudaSlice<f32>,
8975 lim: Option<f32>,
8976 cfg: &ModelConfig,
8977 il: u16,
8978 n_embd: usize,
8979 n_ff_sh: usize,
8980 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
8981 use cudarc::driver::DevicePtr;
8982 if n_ff_sh % 2 != 0 || n_embd % 2 != 0 {
8983 return Ok(None);
8984 }
8985 let hf = n_ff_sh / 2;
8986 let nd = n_embd / 2;
8987 struct Rep {
8988 wg1: CudaSlice<u8>,
8989 wu1: CudaSlice<u8>,
8990 wd1: CudaSlice<u8>,
8991 }
8992 struct SplitWs {
8993 pin_dev: usize,
8994 gate0: CudaSlice<f32>,
8996 up0: CudaSlice<f32>,
8997 act: CudaSlice<f32>,
8998 sh_buf: CudaSlice<f32>,
8999 ev_z: cudarc::driver::CudaEvent,
9000 ev_act0: cudarc::driver::CudaEvent,
9001 z1: CudaSlice<f32>,
9003 g1: CudaSlice<f32>,
9004 u1: CudaSlice<f32>,
9005 a1h: CudaSlice<f32>,
9006 act1: CudaSlice<f32>,
9007 y1: CudaSlice<f32>,
9008 ev_act1: cudarc::driver::CudaEvent,
9009 ev_y1: cudarc::driver::CudaEvent,
9010 raw_act_e: u64,
9011 raw_sh_e: u64,
9012 raw_z1: u64,
9013 raw_a1h: u64,
9014 raw_act1: u64,
9015 raw_y1: u64,
9016 }
9017 static WS: std::sync::Mutex<Option<SplitWs>> = std::sync::Mutex::new(None);
9018 static REPS: std::sync::Mutex<Option<std::collections::HashMap<u64, Rep>>> =
9019 std::sync::Mutex::new(None);
9020 let mut guard = WS.lock().map_err(|_| "shexp split lock is poisoned")?;
9021 let mut reps_guard = REPS.lock().map_err(|_| "shexp reps lock is poisoned")?;
9022 let reps = reps_guard.get_or_insert_with(std::collections::HashMap::new);
9023 let pins = e.ctx().ordinal();
9024 if guard.as_ref().is_none_or(|w| w.pin_dev != pins) {
9025 let (gate0, up0, act, sh_buf, ev_z, ev_act0) = {
9026 let _m = e.gpu.enter_main()?;
9027 (
9028 e.htod(&vec![0.0f32; hf])?,
9029 e.htod(&vec![0.0f32; hf])?,
9030 e.htod(&vec![0.0f32; n_ff_sh])?,
9031 e.htod(&vec![0.0f32; n_embd])?,
9032 e.ctx().new_event(None)?,
9033 e.ctx().new_event(None)?,
9034 )
9035 };
9036 let (z1, g1, u1, a1h, act1, y1, ev_act1, ev_y1) = {
9037 let _r = rank1.gpu.enter_main()?;
9038 (
9039 rank1.htod(&vec![0.0f32; n_embd])?,
9040 rank1.htod(&vec![0.0f32; hf])?,
9041 rank1.htod(&vec![0.0f32; hf])?,
9042 rank1.htod(&vec![0.0f32; hf])?,
9043 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9044 rank1.htod(&vec![0.0f32; nd])?,
9045 rank1.ctx().new_event(None)?,
9046 rank1.ctx().new_event(None)?,
9047 )
9048 };
9049 let (raw_act_e, raw_sh_e) = {
9050 let _m = e.gpu.enter_main()?;
9051 let stream = e.stream();
9052 let (a, _g0) = act.device_ptr(&stream);
9053 let (b, _g1) = sh_buf.device_ptr(&stream);
9054 (a as u64, b as u64)
9055 };
9056 let (raw_z1, raw_a1h, raw_act1, raw_y1) = {
9057 let _r = rank1.gpu.enter_main()?;
9058 let rs = rank1.stream();
9059 let (a, _g0) = z1.device_ptr(&rs);
9060 let (b, _g1) = a1h.device_ptr(&rs);
9061 let (c, _g2) = act1.device_ptr(&rs);
9062 let (d, _g3) = y1.device_ptr(&rs);
9063 (a as u64, b as u64, c as u64, d as u64)
9064 };
9065 *guard = Some(SplitWs {
9066 pin_dev: pins,
9067 gate0,
9068 up0,
9069 act,
9070 sh_buf,
9071 ev_z,
9072 ev_act0,
9073 z1,
9074 g1,
9075 u1,
9076 a1h,
9077 act1,
9078 y1,
9079 ev_act1,
9080 ev_y1,
9081 raw_act_e,
9082 raw_sh_e,
9083 raw_z1,
9084 raw_a1h,
9085 raw_act1,
9086 raw_y1,
9087 });
9088 }
9089 let ws = guard.as_mut().expect("armed above");
9090 let wg_pin = {
9091 let _m = e.gpu.enter_main()?;
9092 let stream = e.stream();
9093 let (p, _g) = wg.device_ptr(&stream);
9094 p as u64
9095 };
9096 if !reps.contains_key(&wg_pin) {
9097 let mut up = |src: &CudaSlice<u8>,
9099 off_bytes: usize,
9100 len: usize|
9101 -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9102 use cudarc::driver::sys;
9103 let sptr = {
9104 let _m = e.gpu.enter_main()?;
9105 let stream = e.stream();
9106 let (p, _g) = src.device_ptr(&stream);
9107 p as u64 + off_bytes as u64
9108 };
9109 let dst = {
9110 let _r = rank1.gpu.enter_main()?;
9111 rank1.alloc_u8_uninit(len)?
9112 };
9113 let dptr = {
9114 let _r = rank1.gpu.enter_main()?;
9115 let rs = rank1.stream();
9116 let (p, _g) = dst.device_ptr(&rs);
9117 p as u64
9118 };
9119 let _r = rank1.gpu.enter_main()?;
9120 let r = unsafe {
9121 sys::cuMemcpyAsync(
9122 dptr as sys::CUdeviceptr,
9123 sptr as sys::CUdeviceptr,
9124 len,
9125 rank1.stream().cu_stream() as sys::CUstream,
9126 )
9127 };
9128 if r != sys::CUresult::CUDA_SUCCESS {
9129 return Err(format!("shexp split replica upload: {r:?}").into());
9130 }
9131 rank1.stream().synchronize()?;
9132 Ok(dst)
9133 };
9134 let wg1 = up(wg, hf * n_embd * 2, hf * n_embd * 2)?;
9135 let wu1 = up(wu, hf * n_embd * 2, hf * n_embd * 2)?;
9136 let wd1 = up(wd, nd * n_ff_sh * 2, nd * n_ff_sh * 2)?;
9137 reps.insert(wg_pin, Rep { wg1, wu1, wd1 });
9138 }
9139 let _ = il;
9140 let raw_z = {
9142 let _m = e.gpu.enter_main()?;
9143 let stream = e.stream();
9144 let (p, _g) = z.device_ptr(&stream);
9145 ws.ev_z.record(&stream)?;
9146 p as u64
9147 };
9148 {
9150 let rep = reps.get(&wg_pin).expect("uploaded above");
9151 let _r = rank1.gpu.enter_main()?;
9152 rank1.stream().wait(&ws.ev_z)?;
9153 crate::tp::raw_copy_bytes(ws.raw_z1, raw_z, n_embd * 4, rank1)?;
9154 let SplitWs {
9155 z1, g1, u1, a1h, ..
9156 } = &mut *ws;
9157 rank1.matvec_bf16_dual_into(&rep.wg1, &rep.wu1, z1, g1, u1, n_embd, hf)?;
9158 Self::ffn_act_lim(rank1, cfg, g1, u1, 1.0, 1.0, lim, a1h, hf)?;
9159 crate::tp::raw_copy_bytes(ws.raw_act1 + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9161 crate::tp::raw_copy_bytes(ws.raw_act_e + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9162 ws.ev_act1.record(&rank1.stream())?;
9163 }
9164 {
9166 let _m = e.gpu.enter_main()?;
9167 let SplitWs {
9168 gate0, up0, act, ..
9169 } = &mut *ws;
9170 let wg_lo = wg.slice(0..hf * n_embd * 2);
9171 let wu_lo = wu.slice(0..hf * n_embd * 2);
9172 e.matvec_bf16_dual_view_into(&wg_lo, &wu_lo, z, gate0, up0, n_embd, hf)?;
9173 Self::ffn_act_lim(e, cfg, gate0, up0, 1.0, 1.0, lim, act, hf)?;
9174 ws.ev_act0.record(&e.stream())?;
9175 }
9176 {
9178 let rep = reps.get(&wg_pin).expect("uploaded above");
9179 let _r = rank1.gpu.enter_main()?;
9180 rank1.stream().wait(&ws.ev_act0)?;
9181 crate::tp::raw_copy_bytes(ws.raw_act1, ws.raw_act_e, hf * 4, rank1)?;
9182 let SplitWs { act1, y1, .. } = &mut *ws;
9183 rank1.matvec_bf16_into(&rep.wd1, act1, y1, n_ff_sh, nd)?;
9184 crate::tp::raw_copy_bytes(ws.raw_sh_e + (nd * 4) as u64, ws.raw_y1, nd * 4, rank1)?;
9185 ws.ev_y1.record(&rank1.stream())?;
9186 }
9187 {
9189 let _m = e.gpu.enter_main()?;
9190 e.stream().wait(&ws.ev_act1)?;
9191 let SplitWs { act, sh_buf, .. } = &mut *ws;
9192 let wd_lo = wd.slice(0..nd * n_ff_sh * 2);
9193 e.matvec_bf16_view_into(&wd_lo, act, sh_buf, n_ff_sh, nd)?;
9194 e.stream().wait(&ws.ev_y1)?;
9195 let mut sh = e.uninit(n_embd)?;
9196 {
9197 let mut dst = sh.slice_mut(0..n_embd);
9198 e.stream()
9199 .memcpy_dtod(&ws.sh_buf.slice(0..n_embd), &mut dst)?;
9200 }
9201 Ok(Some(sh))
9202 }
9203 }
9204
9205 fn shexp_overlap_issue(
9212 e: &Engine,
9213 m: &MoeWeights,
9214 z: &CudaSlice<f32>,
9215 cfg: &ModelConfig,
9216 il: u16,
9217 n_embd: usize,
9218 ) -> Result<bool, Box<dyn std::error::Error>> {
9219 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9220 return Ok(false);
9221 }
9222 let (
9223 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9224 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9225 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9226 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9227 else {
9228 return Ok(false);
9229 };
9230 let n_ff_sh = m
9231 .gate_shexp
9232 .as_ref()
9233 .expect("matched Some above")
9234 .out_features();
9235 let lim = cfg.clamp_shexp_at(il as u32);
9236 let mut guard = SHEXP_OV_WS
9237 .lock()
9238 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9239 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9240 if guard
9241 .as_ref()
9242 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9243 {
9244 *guard = Some((
9245 pins.0,
9246 pins.1,
9247 pins.2,
9248 e.uninit(n_ff_sh)?,
9249 e.uninit(n_embd)?,
9250 ));
9251 }
9252 let (_, _, _, act, sh) = guard.as_mut().expect("armed above");
9253 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9254 e.matvec_bf16_into(wd, act, sh, n_ff_sh, n_embd)?;
9255 drop(guard);
9256 Ok(true)
9257 }
9258
9259 #[allow(clippy::too_many_arguments)]
9265 fn shexp_dev1_issue(
9266 e: &Engine,
9267 rank1: &Engine,
9268 m: &MoeWeights,
9269 z: &CudaSlice<f32>,
9270 cfg: &ModelConfig,
9271 il: u16,
9272 n_embd: usize,
9273 ) -> Result<bool, Box<dyn std::error::Error>> {
9274 use cudarc::driver::DevicePtr;
9275 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9276 return Ok(false);
9277 }
9278 let (
9279 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9280 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9281 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9282 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9283 else {
9284 return Ok(false);
9285 };
9286 let n_ff_sh = m
9287 .gate_shexp
9288 .as_ref()
9289 .expect("matched Some above")
9290 .out_features();
9291 let lim = cfg.clamp_shexp_at(il as u32);
9292 let mut ws_guard = SHEXP_D1_WS
9294 .lock()
9295 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9296 if ws_guard
9297 .as_ref()
9298 .is_none_or(|(k, ..)| *k != (n_embd, n_ff_sh))
9299 {
9300 let (act1, z1, ev_done) = {
9301 let _r1 = rank1.gpu.enter_main()?;
9302 (
9303 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9304 rank1.htod(&vec![0.0f32; n_embd])?,
9305 rank1.ctx().new_event(None)?,
9306 )
9307 };
9308 let (sh_root, ev_z) = {
9309 let _main = e.gpu.enter_main()?;
9310 (e.htod(&vec![0.0f32; n_embd])?, e.ctx().new_event(None)?)
9311 };
9312 *ws_guard = Some(((n_embd, n_ff_sh), act1, z1, sh_root, ev_z, ev_done));
9313 }
9314 let mut reps_guard = SHEXP_D1_REPS
9316 .lock()
9317 .map_err(|_| "shexp dev1 replica lock is poisoned")?;
9318 let reps = reps_guard.get_or_insert_with(Default::default);
9319 if !reps.contains_key(&il) {
9320 let (wg1, wu1, wd1) = {
9321 let _r1 = rank1.gpu.enter_main()?;
9322 (
9323 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9324 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9325 rank1.alloc_u8_uninit(n_embd * n_ff_sh * 2)?,
9326 )
9327 };
9328 for (src, dst) in [(wg, &wg1), (wu, &wu1), (wd, &wd1)] {
9329 let s_ptr = {
9330 let _main = e.gpu.enter_main()?;
9331 let stream = e.stream();
9332 let (p, _g) = src.device_ptr(&stream);
9333 p as u64
9334 };
9335 let d_ptr = {
9336 let _r1 = rank1.gpu.enter_main()?;
9337 let stream = rank1.stream();
9338 let (p, _g) = dst.device_ptr(&stream);
9339 p as u64
9340 };
9341 let _r1 = rank1.gpu.enter_main()?;
9342 crate::tp::raw_copy_bytes(d_ptr, s_ptr, src.len(), rank1)?;
9343 }
9344 {
9345 let _r1 = rank1.gpu.enter_main()?;
9346 rank1.stream().synchronize()?;
9347 }
9348 reps.insert(il, (wg1, wu1, wd1));
9349 }
9350 let (wg1, wu1, wd1) = reps.get(&il).expect("armed above");
9351 let (_, act1, z1, sh_root, ev_z, ev_done) = ws_guard.as_mut().expect("armed above");
9352 let (raw_z, raw_sh) = {
9355 let _main = e.gpu.enter_main()?;
9356 let stream = e.stream();
9357 let (a, _g0) = z.device_ptr(&stream);
9358 let (b, _g1) = sh_root.device_ptr(&stream);
9359 ev_z.record(&stream)?;
9360 (a as u64, b as u64)
9361 };
9362 {
9363 let _r1 = rank1.gpu.enter_main()?;
9364 rank1.stream().wait(ev_z)?;
9365 let raw_z1 = {
9366 let stream = rank1.stream();
9367 let (p, _g) = z1.device_ptr(&stream);
9368 p as u64
9369 };
9370 crate::tp::raw_copy_bytes(raw_z1, raw_z, n_embd * 4, rank1)?;
9371 rank1.matvec_bf16_dual_silu_into(wg1, wu1, z1, act1, n_embd, n_ff_sh, lim)?;
9372 rank1.matvec_bf16_raw_out(wd1, act1, raw_sh, n_ff_sh, n_embd)?;
9376 ev_done.record(&rank1.stream())?;
9377 }
9378 Ok(true)
9379 }
9380
9381 fn shexp_dev1_apply(
9383 e: &Engine,
9384 output: &mut CudaSlice<f32>,
9385 n_embd: usize,
9386 ) -> Result<(), Box<dyn std::error::Error>> {
9387 let guard = SHEXP_D1_WS
9388 .lock()
9389 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9390 let (pin, _, _, sh_root, _, ev_done) =
9391 guard.as_ref().ok_or("shexp dev1 apply without issue")?;
9392 if pin.0 != n_embd {
9393 return Err("shexp dev1 width drifted".into());
9394 }
9395 let _main = e.gpu.enter_main()?;
9396 e.stream().wait(ev_done)?;
9397 static ONES_D1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9398 std::sync::Mutex::new(None);
9399 let mut og = ONES_D1.lock().map_err(|_| "ones lock is poisoned")?;
9400 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9401 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9402 }
9403 let ones = &og.as_ref().expect("armed above").1;
9404 e.add_scaled_rows(sh_root, ones, output, n_embd, 1)?;
9405 Ok(())
9406 }
9407
9408 fn shexp_overlap_tail_ptrs(
9412 e: &Engine,
9413 m: &MoeWeights,
9414 cfg: &ModelConfig,
9415 n_embd: usize,
9416 ) -> Result<Option<(u64, u64)>, Box<dyn std::error::Error>> {
9417 use cudarc::driver::DevicePtr;
9418 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9419 return Ok(None);
9420 }
9421 let (
9422 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9423 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9424 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9425 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9426 else {
9427 return Ok(None);
9428 };
9429 let n_ff_sh = m
9430 .gate_shexp
9431 .as_ref()
9432 .expect("matched Some above")
9433 .out_features();
9434 let mut guard = SHEXP_OV_WS
9435 .lock()
9436 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9437 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9438 if guard
9439 .as_ref()
9440 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9441 {
9442 *guard = Some((
9443 pins.0,
9444 pins.1,
9445 pins.2,
9446 e.uninit(n_ff_sh)?,
9447 e.uninit(n_embd)?,
9448 ));
9449 }
9450 let sh_raw = {
9451 let (_, _, _, _, sh) = guard.as_ref().expect("armed above");
9452 let stream = e.stream();
9453 let (p, _g) = sh.device_ptr(&stream);
9454 p as u64
9455 };
9456 static ONES_T3: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9457 std::sync::Mutex::new(None);
9458 let mut og = ONES_T3.lock().map_err(|_| "ones lock is poisoned")?;
9459 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9460 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9461 }
9462 let ones_raw = {
9463 let stream = e.stream();
9464 let (p, _g) = og.as_ref().expect("armed above").1.device_ptr(&stream);
9465 p as u64
9466 };
9467 Ok(Some((sh_raw, ones_raw)))
9468 }
9469
9470 fn shexp_overlap_apply(
9473 e: &Engine,
9474 output: &mut CudaSlice<f32>,
9475 n_embd: usize,
9476 ) -> Result<(), Box<dyn std::error::Error>> {
9477 let guard = SHEXP_OV_WS
9478 .lock()
9479 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9480 let (_, ne, _, _, sh) = guard.as_ref().ok_or("shexp overlap apply without issue")?;
9481 if *ne != n_embd {
9482 return Err("shexp overlap width drifted".into());
9483 }
9484 static ONES_OV: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9485 std::sync::Mutex::new(None);
9486 let mut og = ONES_OV.lock().map_err(|_| "ones lock is poisoned")?;
9487 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9488 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9489 }
9490 let ones = &og.as_ref().expect("armed above").1;
9491 e.add_scaled_rows(sh, ones, output, n_embd, 1)?;
9492 Ok(())
9493 }
9494
9495 fn moe_ffn_grouped_add_shared(
9496 e: &Engine,
9497 m: &MoeWeights,
9498 z: &CudaSlice<f32>,
9499 t: usize,
9500 cfg: &ModelConfig,
9501 il: u16,
9502 moe_out: &mut CudaSlice<f32>,
9503 ) -> Result<(), Box<dyn std::error::Error>> {
9504 static SHEXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9507 static SHEXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9508 let shexp_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9509 let shexp_started = shexp_timing.then(std::time::Instant::now);
9510 let result = Self::moe_ffn_grouped_add_shared_inner(e, m, z, t, cfg, il, moe_out);
9511 if let Some(started) = shexp_started {
9512 use std::sync::atomic::Ordering;
9513 e.stream().synchronize()?;
9514 let ns = SHEXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9515 + started.elapsed().as_nanos() as u64;
9516 let calls = SHEXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9517 if calls % 430 == 0 {
9518 eprintln!(
9519 "[moe-shexp-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9520 ns as f64 / 1.0e6,
9521 ns as f64 / calls as f64 / 1.0e3,
9522 );
9523 }
9524 }
9525 result
9526 }
9527
9528 #[allow(clippy::too_many_arguments)]
9529 fn moe_ffn_grouped_add_shared_inner(
9530 e: &Engine,
9531 m: &MoeWeights,
9532 z: &CudaSlice<f32>,
9533 t: usize,
9534 cfg: &ModelConfig,
9535 il: u16,
9536 moe_out: &mut CudaSlice<f32>,
9537 ) -> Result<(), Box<dyn std::error::Error>> {
9538 let n_embd = cfg.n_embd as usize;
9539 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
9540 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9541 {
9542 let n_ff_sh = gate_shexp.out_features();
9543 let lim = cfg.clamp_shexp_at(il as u32);
9544 let fused = t == 1
9551 && lim.is_none()
9552 && cfg.m3.is_none()
9553 && e.uses_q8_1_fast(gate_shexp)
9554 && e.uses_q8_1_fast(up_shexp);
9555 let bf16_dual = if t == 1 && crate::Engine::bf16_mmv_on() && n_embd % 8 == 0 {
9558 match (gate_shexp, up_shexp) {
9559 (
9560 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
9561 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
9562 ) => Some((wg, wu)),
9563 _ => None,
9564 }
9565 } else {
9566 None
9567 };
9568 let sh = if let Some((wg, wu)) = bf16_dual {
9569 static SHEXP_WS: std::sync::Mutex<
9573 Option<(
9574 usize,
9575 usize,
9576 usize,
9577 CudaSlice<f32>,
9578 CudaSlice<f32>,
9579 CudaSlice<f32>,
9580 CudaSlice<f32>,
9581 )>,
9582 > = std::sync::Mutex::new(None);
9583 let down_bf16 = match down_shexp {
9584 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9585 _ => None,
9586 };
9587 let mut guard = SHEXP_WS
9588 .lock()
9589 .map_err(|_| "shexp workspace lock is poisoned")?;
9590 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9591 if guard
9592 .as_ref()
9593 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9594 {
9595 *guard = Some((
9596 pins.0,
9597 pins.1,
9598 pins.2,
9599 e.uninit(n_ff_sh)?,
9600 e.uninit(n_ff_sh)?,
9601 e.uninit(n_ff_sh)?,
9602 e.uninit(n_embd)?,
9603 ));
9604 }
9605 {
9608 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9609 let split_on = *ON
9610 .get_or_init(|| std::env::var("MEMRA_SHEXP_SPLIT").as_deref() == Ok("1"));
9611 if split_on {
9612 if let (Some(wd), Some(rank1)) = (
9613 match down_shexp {
9614 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9615 _ => None,
9616 },
9617 m.step_tp.as_ref().and_then(|st| st.runtime.rank_engine(1)),
9618 ) {
9619 if let Some(sh) = Self::shexp_split_matvec(
9620 e, rank1, wg, wu, wd, z, lim, cfg, il, n_embd, n_ff_sh,
9621 )? {
9622 drop(guard);
9623 let gate = match &m.gate_inp_shexp {
9624 Some(gate_inp_shexp) => e.sigmoid_dot_rows(
9625 z,
9626 gate_inp_shexp.float_data(),
9627 n_embd,
9628 t,
9629 )?,
9630 None => e.htod(&vec![1.0f32; t])?,
9631 };
9632 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9633 return Ok(());
9634 }
9635 }
9636 }
9637 }
9638 let (_, _, _, gate, up, act, sh_buf) =
9639 guard.as_mut().expect("shexp workspace initialized above");
9640 if cfg.m3.is_none() {
9641 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9644 let _ = (&gate, &up);
9645 } else {
9646 e.matvec_bf16_dual_into(wg, wu, z, gate, up, n_embd, n_ff_sh)?;
9647 Self::ffn_act_lim(e, cfg, gate, up, 1.0, 1.0, lim, act, n_ff_sh)?;
9648 }
9649 if let Some(down) = down_bf16 {
9650 static FUSE_DA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9657 let fuse_da = *FUSE_DA.get_or_init(|| {
9658 std::env::var("MEMRA_FUSE_DOWN_ADDSCALE").as_deref() != Ok("0")
9659 });
9660 if fuse_da && m.gate_inp_shexp.is_none() {
9661 static ONES1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9662 std::sync::Mutex::new(None);
9663 let mut og = ONES1.lock().map_err(|_| "shexp ones lock is poisoned")?;
9664 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9665 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9666 }
9667 let ones = &og.as_ref().expect("armed above").1;
9668 e.matvec_bf16_down_addscale_into(
9669 down, act, ones, moe_out, n_ff_sh, n_embd,
9670 )?;
9671 return Ok(());
9672 }
9673 e.matvec_bf16_into(down, act, sh_buf, n_ff_sh, n_embd)?;
9674 let sh = e.uninit(n_embd)?;
9675 let mut sh = sh;
9677 {
9678 let mut dst = sh.slice_mut(0..n_embd);
9679 e.stream().memcpy_dtod(&sh_buf.slice(0..n_embd), &mut dst)?;
9680 }
9681 sh
9682 } else {
9683 e.matmul(down_shexp, act, 1)?
9684 }
9685 } else if fused {
9686 let (zq, zd) = e.quantize_q8_1(z, 1, n_embd)?;
9687 let pair = match e.matmul_pre_dual_noscale(gate_shexp, up_shexp, &zq, &zd, 1)? {
9688 Some((gate, up)) => Some((gate, up)),
9689 None => {
9690 match (
9691 e.matmul_pre_noscale(gate_shexp, &zq, &zd, 1)?,
9692 e.matmul_pre_noscale(up_shexp, &zq, &zd, 1)?,
9693 ) {
9694 (Some(gate), Some(up)) => Some((gate, up)),
9695 _ => None,
9696 }
9697 }
9698 };
9699 match pair {
9700 Some(((gate, gs), (up, us))) => {
9701 if e.uses_q8_1_fast(down_shexp) {
9702 let (aq, ad) = e.silu_mul_scaled_q8_1(&gate, &up, gs, us, n_ff_sh)?;
9703 e.matmul_pre(down_shexp, &aq, &ad, &gate, 1)?
9704 } else {
9705 let mut act = e.uninit(n_ff_sh)?;
9706 e.silu_mul_scaled(&gate, &up, gs, us, &mut act, n_ff_sh)?;
9707 e.matmul(down_shexp, &act, 1)?
9708 }
9709 }
9710 None => {
9711 let gate = e.matmul_pre(gate_shexp, &zq, &zd, z, 1)?;
9712 let up = e.matmul_pre(up_shexp, &zq, &zd, z, 1)?;
9713 let mut act = e.uninit(n_ff_sh)?;
9714 Self::ffn_act(e, cfg, &gate, &up, &mut act, n_ff_sh)?;
9715 e.matmul(down_shexp, &act, 1)?
9716 }
9717 }
9718 } else {
9719 let sg_gate = e.matmul(gate_shexp, z, t)?;
9720 let sg_up = e.matmul(up_shexp, z, t)?;
9721 let mut sa = e.uninit(t * n_ff_sh)?;
9722 Self::ffn_act_lim(
9723 e,
9724 cfg,
9725 &sg_gate,
9726 &sg_up,
9727 1.0,
9728 1.0,
9729 lim,
9730 &mut sa,
9731 t * n_ff_sh,
9732 )?;
9733 e.matmul(down_shexp, &sa, t)?
9734 };
9735 let gate = match &m.gate_inp_shexp {
9736 Some(gate_inp_shexp) => {
9737 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
9738 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
9739 } else {
9740 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
9741 let mut gate = e.uninit(t)?;
9742 e.sigmoid(&raw, &mut gate, t)?;
9743 gate
9744 }
9745 }
9746 None if t == 1 => {
9751 static ONES: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9752 std::sync::Mutex::new(None);
9753 let mut guard = ONES.lock().map_err(|_| "shexp ones lock is poisoned")?;
9754 if guard.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9755 *guard = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9756 }
9757 let ones = &guard.as_ref().expect("armed above").1;
9758 e.add_scaled_rows(&sh, ones, moe_out, n_embd, t)?;
9759 return Ok(());
9760 }
9761 None => e.htod(&vec![1.0f32; t])?,
9762 };
9763 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9764 }
9765 Ok(())
9766 }
9767
9768 pub(crate) fn moe_ffn_grouped(
9771 e: &Engine,
9772 m: &MoeWeights,
9773 z: &CudaSlice<f32>,
9774 t: usize,
9775 cfg: &ModelConfig,
9776 il: u16,
9777 max_block: usize,
9778 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9779 let moe = cfg.moe.as_ref().unwrap();
9780 let n_embd = cfg.n_embd as usize;
9781 let n_expert = moe.expert_count as usize;
9782 let n_used = moe.expert_used_count as usize;
9783 let n_ff_exp = moe.expert_ff_length as usize;
9784 let lim_exp = cfg.clamp_exp_at(il as u32);
9786
9787 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
9791 if let Some(sig) = cfg.sigmoid_router() {
9792 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
9793 }
9794 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
9795 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
9796 } else {
9797 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
9798 };
9799 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
9800 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
9801 Self::trace_moe_input(e, il, t, n_embd, z)?;
9802
9803 let no_exp_macros = m.gate_exps.macros.is_none()
9808 && m.up_exps.macros.is_none()
9809 && m.down_exps.macros.is_none();
9810 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
9811 m.has_uniform_expert_layout()
9812 && no_exp_macros
9813 && moe_q8_enabled()
9814 && q8_expert_supported(m.gate_exps.qtype)
9815 && q8_expert_supported(m.up_exps.qtype)
9816 && q8_expert_supported(m.down_exps.qtype)
9817 && moe_slab_enabled()
9818 && dev.dev == e.ctx().ordinal()
9819 });
9820 if let Some(dev) = resident_q8 {
9821 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
9822 e,
9823 m,
9824 z,
9825 t,
9826 cfg,
9827 il,
9828 &sel_all,
9829 &w_all,
9830 &dev.ptr_row,
9831 dev.gu_il,
9832 )?;
9833 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
9834 return Ok(moe_out);
9835 }
9836
9837 struct ExpertGroup {
9841 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
9845 let mut groups: Vec<ExpertGroup> = (0..n_expert)
9846 .map(|_| ExpertGroup {
9847 tok_indices: Vec::new(),
9848 slot_indices: Vec::new(),
9849 weights: Vec::new(),
9850 })
9851 .collect();
9852
9853 for tok in 0..t {
9854 for j in 0..n_used {
9855 let ex = sel_all[tok * n_used + j] as usize;
9856 let w = w_all[tok * n_used + j];
9857 groups[ex].tok_indices.push(tok as i32);
9858 groups[ex].slot_indices.push(j as i32);
9859 groups[ex].weights.push(w);
9860 }
9861 }
9862
9863 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
9866 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
9870 let u_len = m.up_exps.max_expert_bytes();
9871 let d_len = m.down_exps.max_expert_bytes();
9872 let moe_q8 = m.has_uniform_expert_layout()
9873 && moe_q8_enabled()
9874 && q8_expert_supported(m.gate_exps.qtype)
9875 && q8_expert_supported(m.up_exps.qtype)
9876 && q8_expert_supported(m.down_exps.qtype);
9877 let slab_local = m
9880 .dev_exps
9881 .as_ref()
9882 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
9883 let use_cache =
9884 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
9885 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
9888
9889 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
9891 (
9892 Some(e.alloc_u8(g_len)?),
9893 Some(e.alloc_u8(u_len)?),
9894 Some(e.alloc_u8(d_len)?),
9895 )
9896 } else {
9897 (None, None, None)
9898 };
9899
9900 let mut order: Vec<usize> = (0..n_expert)
9911 .filter(|&ex| !groups[ex].tok_indices.is_empty())
9912 .collect();
9913 order.sort_by(|&a, &b| {
9914 groups[b]
9915 .tok_indices
9916 .len()
9917 .cmp(&groups[a].tok_indices.len())
9918 .then(a.cmp(&b))
9919 });
9920 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
9922 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
9923 if worker_disk_prefetch {
9924 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
9925 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
9926 }
9927 }
9928 for (order_pos, &ex) in order.iter().enumerate() {
9929 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
9930 Self::moe_prefetch_host_expert(order[next], m);
9931 }
9932 if worker_disk_prefetch {
9933 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
9934 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
9935 let keep = [
9936 BlockId::new(il, PROJ_GATE, ex as u16),
9937 BlockId::new(il, PROJ_UP, ex as u16),
9938 BlockId::new(il, PROJ_DOWN, ex as u16),
9939 ];
9940 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
9941 }
9942 }
9943 let grp = &groups[ex];
9944 let m_e = grp.tok_indices.len();
9945 m_dist.push(m_e);
9946 let gl = m.gate_exps.expert_layout(ex);
9947 let ul = m.up_exps.expert_layout(ex);
9948 let dl = m.down_exps.expert_layout(ex);
9949
9950 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
9954 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
9955 let dmac = m.down_exps.macro_scale(ex);
9956 let weight_d = if dmac == 1.0 {
9957 e.htod(&grp.weights)?
9958 } else {
9959 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
9960 e.htod(&scaled)?
9961 };
9962
9963 let mut gathered = e.zeros(m_e * n_embd)?;
9965 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
9966 let gv = gathered.slice(0..m_e * n_embd);
9967
9968 let y = if let Some(dev) = slab_local {
9971 let gate_start = ex * m.gate_exps.expert_stride;
9972 let up_start = ex * m.up_exps.expert_stride;
9973 let down_start = ex * m.down_exps.expert_stride;
9974 if grouped_q8 {
9975 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
9976 let gate = e.qmatvec_expert_q8(
9977 &dev.gate,
9978 gate_start..gate_start + gl.len,
9979 &zq,
9980 &zd,
9981 m_e,
9982 m.gate_exps.in_f,
9983 m.gate_exps.out_f,
9984 gl.qtype,
9985 gl.row_bytes,
9986 )?;
9987 let up = e.qmatvec_expert_q8(
9988 &dev.up,
9989 up_start..up_start + ul.len,
9990 &zq,
9991 &zd,
9992 m_e,
9993 m.up_exps.in_f,
9994 m.up_exps.out_f,
9995 ul.qtype,
9996 ul.row_bytes,
9997 )?;
9998 let mut act = e.uninit(m_e * n_ff_exp)?;
9999 Self::ffn_act_lim(
10000 e,
10001 cfg,
10002 &gate,
10003 &up,
10004 m.gate_exps.macro_scale(ex),
10005 m.up_exps.macro_scale(ex),
10006 lim_exp,
10007 &mut act,
10008 m_e * n_ff_exp,
10009 )?;
10010 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10011 e.qmatvec_expert_q8(
10012 &dev.down,
10013 down_start..down_start + dl.len,
10014 &aq2,
10015 &ad2,
10016 m_e,
10017 m.down_exps.in_f,
10018 m.down_exps.out_f,
10019 dl.qtype,
10020 dl.row_bytes,
10021 )?
10022 } else {
10023 let gate = e.qmatvec_view(
10024 &dev.gate,
10025 gate_start..gate_start + gl.len,
10026 &gv,
10027 m_e,
10028 m.gate_exps.in_f,
10029 m.gate_exps.out_f,
10030 gl.qtype,
10031 gl.row_bytes,
10032 )?;
10033 let up = e.qmatvec_view(
10034 &dev.up,
10035 up_start..up_start + ul.len,
10036 &gv,
10037 m_e,
10038 m.up_exps.in_f,
10039 m.up_exps.out_f,
10040 ul.qtype,
10041 ul.row_bytes,
10042 )?;
10043 let mut act = e.uninit(m_e * n_ff_exp)?;
10044 Self::ffn_act_lim(
10045 e,
10046 cfg,
10047 &gate,
10048 &up,
10049 m.gate_exps.macro_scale(ex),
10050 m.up_exps.macro_scale(ex),
10051 lim_exp,
10052 &mut act,
10053 m_e * n_ff_exp,
10054 )?;
10055 let actv = act.slice(0..m_e * n_ff_exp);
10056 e.qmatvec_view(
10057 &dev.down,
10058 down_start..down_start + dl.len,
10059 &actv,
10060 m_e,
10061 m.down_exps.in_f,
10062 m.down_exps.out_f,
10063 dl.qtype,
10064 dl.row_bytes,
10065 )?
10066 }
10067 } else if use_cache {
10068 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10069 if grouped_q8 {
10070 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10071 let gate = e.with_moe_cache(max_block, |cache, eng| {
10072 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10073 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10074 eng.qmatvec_expert_q8(
10075 cache.buf(slot),
10076 0..gl.len,
10077 &zq,
10078 &zd,
10079 m_e,
10080 m.gate_exps.in_f,
10081 m.gate_exps.out_f,
10082 gl.qtype,
10083 gl.row_bytes,
10084 )
10085 })?;
10086 let up = e.with_moe_cache(max_block, |cache, eng| {
10087 let id = BlockId::new(il, PROJ_UP, ex as u16);
10088 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10089 eng.qmatvec_expert_q8(
10090 cache.buf(slot),
10091 0..ul.len,
10092 &zq,
10093 &zd,
10094 m_e,
10095 m.up_exps.in_f,
10096 m.up_exps.out_f,
10097 ul.qtype,
10098 ul.row_bytes,
10099 )
10100 })?;
10101 let mut act = e.uninit(m_e * n_ff_exp)?;
10102 Self::ffn_act_lim(
10103 e,
10104 cfg,
10105 &gate,
10106 &up,
10107 m.gate_exps.macro_scale(ex),
10108 m.up_exps.macro_scale(ex),
10109 lim_exp,
10110 &mut act,
10111 m_e * n_ff_exp,
10112 )?;
10113 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10114 e.with_moe_cache(max_block, |cache, eng| {
10115 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10116 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10117 eng.qmatvec_expert_q8(
10118 cache.buf(slot),
10119 0..dl.len,
10120 &aq2,
10121 &ad2,
10122 m_e,
10123 m.down_exps.in_f,
10124 m.down_exps.out_f,
10125 dl.qtype,
10126 dl.row_bytes,
10127 )
10128 })?
10129 } else {
10130 let gate = e.with_moe_cache(max_block, |cache, eng| {
10131 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10132 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10133 eng.qmatvec_view(
10134 cache.buf(slot),
10135 0..gl.len,
10136 &gv,
10137 m_e,
10138 m.gate_exps.in_f,
10139 m.gate_exps.out_f,
10140 gl.qtype,
10141 gl.row_bytes,
10142 )
10143 })?;
10144 let up = e.with_moe_cache(max_block, |cache, eng| {
10145 let id = BlockId::new(il, PROJ_UP, ex as u16);
10146 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10147 eng.qmatvec_view(
10148 cache.buf(slot),
10149 0..ul.len,
10150 &gv,
10151 m_e,
10152 m.up_exps.in_f,
10153 m.up_exps.out_f,
10154 ul.qtype,
10155 ul.row_bytes,
10156 )
10157 })?;
10158 let mut act = e.uninit(m_e * n_ff_exp)?;
10159 Self::ffn_act_lim(
10160 e,
10161 cfg,
10162 &gate,
10163 &up,
10164 m.gate_exps.macro_scale(ex),
10165 m.up_exps.macro_scale(ex),
10166 lim_exp,
10167 &mut act,
10168 m_e * n_ff_exp,
10169 )?;
10170 let actv = act.slice(0..m_e * n_ff_exp);
10171 e.with_moe_cache(max_block, |cache, eng| {
10172 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10173 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10174 eng.qmatvec_view(
10175 cache.buf(slot),
10176 0..dl.len,
10177 &actv,
10178 m_e,
10179 m.down_exps.in_f,
10180 m.down_exps.out_f,
10181 dl.qtype,
10182 dl.row_bytes,
10183 )
10184 })?
10185 }
10186 } else {
10187 let sg = scratch_g.as_mut().unwrap();
10188 let su = scratch_u.as_mut().unwrap();
10189 let sd = scratch_d.as_mut().unwrap();
10190 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
10191 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
10192 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
10193 if grouped_q8 {
10194 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10195 let gate = e.qmatvec_expert_q8(
10196 sg,
10197 0..gl.len,
10198 &zq,
10199 &zd,
10200 m_e,
10201 m.gate_exps.in_f,
10202 m.gate_exps.out_f,
10203 gl.qtype,
10204 gl.row_bytes,
10205 )?;
10206 let up = e.qmatvec_expert_q8(
10207 su,
10208 0..ul.len,
10209 &zq,
10210 &zd,
10211 m_e,
10212 m.up_exps.in_f,
10213 m.up_exps.out_f,
10214 ul.qtype,
10215 ul.row_bytes,
10216 )?;
10217 let mut act = e.uninit(m_e * n_ff_exp)?;
10218 Self::ffn_act_lim(
10219 e,
10220 cfg,
10221 &gate,
10222 &up,
10223 m.gate_exps.macro_scale(ex),
10224 m.up_exps.macro_scale(ex),
10225 lim_exp,
10226 &mut act,
10227 m_e * n_ff_exp,
10228 )?;
10229 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10230 e.qmatvec_expert_q8(
10231 sd,
10232 0..dl.len,
10233 &aq2,
10234 &ad2,
10235 m_e,
10236 m.down_exps.in_f,
10237 m.down_exps.out_f,
10238 dl.qtype,
10239 dl.row_bytes,
10240 )?
10241 } else {
10242 let gate = e.qmatvec_view(
10243 sg,
10244 0..gl.len,
10245 &gv,
10246 m_e,
10247 m.gate_exps.in_f,
10248 m.gate_exps.out_f,
10249 gl.qtype,
10250 gl.row_bytes,
10251 )?;
10252 let up = e.qmatvec_view(
10253 su,
10254 0..ul.len,
10255 &gv,
10256 m_e,
10257 m.up_exps.in_f,
10258 m.up_exps.out_f,
10259 ul.qtype,
10260 ul.row_bytes,
10261 )?;
10262 let mut act = e.uninit(m_e * n_ff_exp)?;
10263 Self::ffn_act_lim(
10264 e,
10265 cfg,
10266 &gate,
10267 &up,
10268 m.gate_exps.macro_scale(ex),
10269 m.up_exps.macro_scale(ex),
10270 lim_exp,
10271 &mut act,
10272 m_e * n_ff_exp,
10273 )?;
10274 let actv = act.slice(0..m_e * n_ff_exp);
10275 e.qmatvec_view(
10276 sd,
10277 0..dl.len,
10278 &actv,
10279 m_e,
10280 m.down_exps.in_f,
10281 m.down_exps.out_f,
10282 dl.qtype,
10283 dl.row_bytes,
10284 )?
10285 }
10286 };
10287
10288 e.scatter_slot(
10290 &y,
10291 &tok_idx_d,
10292 &slot_idx_d,
10293 &weight_d,
10294 &mut slot_buf,
10295 &mut wbuf,
10296 n_embd,
10297 n_used,
10298 m_e,
10299 )?;
10300 }
10301
10302 let mut moe_out = e.zeros(t * n_embd)?;
10304 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
10305
10306 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
10308 m_dist.sort_unstable();
10309 let active = m_dist.len();
10310 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
10311 let median = m_dist[active / 2];
10312 let max_m = *m_dist.last().unwrap();
10313 let min_m = m_dist[0];
10314 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
10315 println!(
10316 "moe-grouped il={il} t={t} active={active}/{n_expert} \
10317 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
10318 above_gemm_threshold(>=16)={above16}/{active}"
10319 );
10320 }
10321
10322 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10323 Ok(moe_out)
10324 }
10325
10326 pub(crate) fn moe_ffn_lockstep(
10333 &self,
10334 e: &Engine,
10335 m: &MoeWeights,
10336 zbatch: &CudaSlice<f32>,
10337 mrows: usize,
10338 il: u16,
10339 max_block: usize,
10340 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10341 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10342 let cfg = &self.cfg;
10343 let moe = cfg.moe.as_ref().unwrap();
10344 let n_embd = cfg.n_embd as usize;
10345 let n_expert = moe.expert_count as usize;
10346 let n_used = moe.expert_used_count as usize;
10347 let n_ff_exp = moe.expert_ff_length as usize;
10348 let lim_exp = cfg.clamp_exp_at(il as u32);
10350 let lim_shexp = cfg.clamp_shexp_at(il as u32);
10351
10352 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
10353 if let Some(sig) = cfg.sigmoid_router() {
10354 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
10355 }
10356 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10357 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
10358 } else {
10359 Self::moe_route_cfg(
10360 e,
10361 &logits,
10362 mrows,
10363 n_expert,
10364 n_used,
10365 m.active_experts.as_deref(),
10366 )?
10367 };
10368 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
10369
10370 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
10372 Ok((0..n_expert)
10373 .map(|ex| {
10374 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
10375 .into_iter()
10376 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
10377 })
10378 .collect())
10379 })?;
10380
10381 struct Group {
10382 rows: Vec<i32>,
10383 slots: Vec<i32>,
10384 weights: Vec<f32>,
10385 }
10386 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
10387 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
10388 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
10389 Default::default();
10390 for row in 0..mrows {
10391 for j in 0..n_used {
10392 let ex = sel_all[row * n_used + j] as usize;
10393 let w = w_all[row * n_used + j];
10394 if resident_expert[ex] {
10395 let group = groups.entry(ex).or_insert_with(|| Group {
10396 rows: Vec::new(),
10397 slots: Vec::new(),
10398 weights: Vec::new(),
10399 });
10400 group.rows.push(row as i32);
10401 group.slots.push(j as i32);
10402 group.weights.push(w);
10403 } else {
10404 crate::cpu_experts::record_incomplete_gpu_residency(0);
10405 cpu_rows[row].push((ex, w));
10406 cpu_by_expert.entry(ex).or_default().push((row, w));
10407 }
10408 }
10409 }
10410
10411 let host_rows = e.dtoh(zbatch)?;
10417 let rows_ok = crate::cpu_experts::rows_supported();
10418 enum CpuPart {
10419 Single { row: usize },
10420 Rows { rows: Vec<usize> },
10421 }
10422 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
10423 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
10424 if rows_ok {
10425 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
10426 .into_iter()
10427 .filter(|(_, rows)| rows.len() >= 2)
10428 .collect();
10429 shared.sort_by_key(|(ex, _)| *ex);
10430 for (ex, mut row_weights) in shared {
10431 row_weights.sort_by_key(|(row, _)| *row);
10432 let inputs: Vec<(&[f32], f32)> = row_weights
10433 .iter()
10434 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
10435 .collect();
10436 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
10437 .map_err(std::io::Error::other)?;
10438 for &(row, _) in &row_weights {
10439 rows_served.insert((row, ex));
10440 }
10441 tickets.push((
10442 CpuPart::Rows {
10443 rows: row_weights.iter().map(|&(row, _)| row).collect(),
10444 },
10445 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
10446 ));
10447 }
10448 }
10449 for (row, selected) in cpu_rows.iter().enumerate() {
10450 let leftover: Vec<(usize, f32)> = selected
10451 .iter()
10452 .copied()
10453 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
10454 .collect();
10455 if leftover.is_empty() {
10456 continue;
10457 }
10458 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
10459 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
10460 .map_err(std::io::Error::other)?;
10461 tickets.push((
10462 CpuPart::Single { row },
10463 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
10464 ));
10465 }
10466
10467 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
10468 let mut wbuf = e.zeros(mrows * n_used)?;
10469 let mut order: Vec<usize> = groups.keys().copied().collect();
10470 order.sort_by(|&a, &b| {
10471 groups[&b]
10472 .rows
10473 .len()
10474 .cmp(&groups[&a].rows.len())
10475 .then(a.cmp(&b))
10476 });
10477 for &ex in &order {
10478 let group = &groups[&ex];
10479 let m_e = group.rows.len();
10480 let gl = m.gate_exps.expert_layout(ex);
10481 let ul = m.up_exps.expert_layout(ex);
10482 let dl = m.down_exps.expert_layout(ex);
10483 let row_idx_d = e.htod_i32(&group.rows)?;
10484 let slot_idx_d = e.htod_i32(&group.slots)?;
10485 let dmac = m.down_exps.macro_scale(ex);
10486 let weight_d = if dmac == 1.0 {
10487 e.htod(&group.weights)?
10488 } else {
10489 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
10490 e.htod(&scaled)?
10491 };
10492 let mut gathered = e.zeros(m_e * n_embd)?;
10493 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
10494 let gv = gathered.slice(0..m_e * n_embd);
10495 let gate = e.with_moe_cache(max_block, |c, eng| {
10496 let slot = c
10497 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
10498 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10499 eng.qmatvec_view(
10500 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10501 0..gl.len,
10502 &gv,
10503 m_e,
10504 m.gate_exps.in_f,
10505 m.gate_exps.out_f,
10506 gl.qtype,
10507 gl.row_bytes,
10508 )
10509 })?;
10510 let up = e.with_moe_cache(max_block, |c, eng| {
10511 let slot = c
10512 .resident(BlockId::new(il, PROJ_UP, ex as u16))
10513 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10514 eng.qmatvec_view(
10515 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10516 0..ul.len,
10517 &gv,
10518 m_e,
10519 m.up_exps.in_f,
10520 m.up_exps.out_f,
10521 ul.qtype,
10522 ul.row_bytes,
10523 )
10524 })?;
10525 let mut act = e.zeros(m_e * n_ff_exp)?;
10526 Self::ffn_act_lim(
10527 e,
10528 cfg,
10529 &gate,
10530 &up,
10531 m.gate_exps.macro_scale(ex),
10532 m.up_exps.macro_scale(ex),
10533 lim_exp,
10534 &mut act,
10535 m_e * n_ff_exp,
10536 )?;
10537 let actv = act.slice(0..m_e * n_ff_exp);
10538 let y = e.with_moe_cache(max_block, |c, eng| {
10539 let slot = c
10540 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
10541 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10542 eng.qmatvec_view(
10543 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10544 0..dl.len,
10545 &actv,
10546 m_e,
10547 m.down_exps.in_f,
10548 m.down_exps.out_f,
10549 dl.qtype,
10550 dl.row_bytes,
10551 )
10552 })?;
10553 e.scatter_slot(
10554 &y,
10555 &row_idx_d,
10556 &slot_idx_d,
10557 &weight_d,
10558 &mut slot_buf,
10559 &mut wbuf,
10560 n_embd,
10561 n_used,
10562 m_e,
10563 )?;
10564 }
10565 let mut moe_out = e.zeros(mrows * n_embd)?;
10566 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
10567
10568 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
10570 for (part, ticket) in tickets {
10571 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
10572 let mut add_row = |row: usize, chunk: &[f32]| {
10573 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
10574 for (accumulator, value) in sum.iter_mut().zip(chunk) {
10575 *accumulator += value;
10576 }
10577 };
10578 match part {
10579 CpuPart::Single { row } => add_row(row, &cpu_output),
10580 CpuPart::Rows { rows } => {
10581 for (slot, row) in rows.into_iter().enumerate() {
10582 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
10583 }
10584 }
10585 }
10586 }
10587 for (row, sum) in row_sums.into_iter().enumerate() {
10588 let Some(sum) = sum else { continue };
10589 let cpu_output = e.htod(&sum)?;
10590 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
10591 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
10592 }
10593
10594 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
10595 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
10596 {
10597 let n_ff_sh = gate_shexp.out_features();
10598 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
10599 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
10600 let mut sa = e.zeros(mrows * n_ff_sh)?;
10601 Self::ffn_act_lim(
10602 e,
10603 cfg,
10604 &sg_gate,
10605 &sg_up,
10606 1.0,
10607 1.0,
10608 lim_shexp,
10609 &mut sa,
10610 mrows * n_ff_sh,
10611 )?;
10612 let sh = e.matmul(down_shexp, &sa, mrows)?;
10613 let g = match &m.gate_inp_shexp {
10616 Some(gate_inp_shexp) => {
10617 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
10618 }
10619 None => e.htod(&vec![1.0f32; mrows])?,
10620 };
10621 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
10622 }
10623
10624 Ok(moe_out)
10625 }
10626}
10627
10628impl HybridModel {
10634 pub(crate) fn gemma4_rope_dims(&self, il: usize) -> usize {
10648 let g = self
10649 .cfg
10650 .gemma4
10651 .as_ref()
10652 .expect("gemma4_rope_dims on a non-gemma4 config");
10653 if g.swa_pattern[il] {
10654 g.rope_dims_swa as usize
10655 } else {
10656 g.rope_dims_global as usize
10657 }
10658 }
10659
10660 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
10661 let g = self.cfg.gemma4.as_ref().unwrap();
10662 let swa = g.swa_pattern[il];
10663 let hd = if swa {
10664 g.key_length_swa
10665 } else {
10666 g.key_length_global
10667 } as usize;
10668 (
10672 hd,
10673 g.head_count_kv[il] as usize,
10674 self.cfg.n_head as usize,
10675 if swa {
10676 g.rope_base_swa
10677 } else {
10678 g.rope_base_global
10679 },
10680 1.0,
10681 swa,
10682 )
10683 }
10684
10685 pub(crate) fn gemma4_suppress(
10689 &self,
10690 e: &Engine,
10691 ld: &mut CudaSlice<f32>,
10692 t: usize,
10693 ) -> Result<(), Box<dyn std::error::Error>> {
10694 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
10695 #[cfg(debug_assertions)]
10700 crate::debug_assert_tensor_stream_device(
10701 ids,
10702 &e.stream(),
10703 "gemma4_suppress.suppress_d",
10704 );
10705 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
10706 }
10707 Ok(())
10708 }
10709
10710 #[allow(clippy::too_many_arguments)]
10715 fn gemma_fa_one_program() -> bool {
10724 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10725 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_FA_ONE_PROGRAM").as_deref() == Ok("1"))
10726 }
10727
10728 fn gemma4_attn_prime(
10729 &self,
10730 e: &Engine,
10731 fa: &crate::hybrid::FullAttnLayer,
10732 il: usize,
10733 h: &CudaSlice<f32>,
10734 pos_d: &CudaSlice<i32>,
10735 t: usize,
10736 cache: Option<&mut Cache>,
10737 island: Option<&CudaSlice<i32>>,
10738 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10739 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10740 let eps = self.cfg.rms_eps;
10741 let aux = self.gemma4_aux.as_ref().unwrap();
10742 let ones = aux.ones(e);
10743 #[cfg(debug_assertions)]
10744 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
10745
10746 e.mmq_act_begin();
10749 let q0 = e.matmul(&fa.wq, h, t)?; if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10751 let v = e.dtoh(&q0)?;
10752 let nan = v.iter().filter(|x| x.is_nan()).count();
10753 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10754 eprintln!(
10755 "[g4-prime-trace] L0 q0: nan={nan}/{} amax={amax:.3}",
10756 v.len()
10757 );
10758 }
10759 let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
10763 e.matmul(&fa.wv, h, t)?
10764 } else {
10765 e.clone_dtod(&k0)?
10766 };
10767 if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10768 for (tag, buf) in [("k0", &k0), ("v0", &v0)] {
10769 let v = e.dtoh(buf)?;
10770 let nan = v.iter().filter(|x| x.is_nan()).count();
10771 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10772 eprintln!(
10773 "[g4-prime-trace] L0 {tag}: nan={nan}/{} amax={amax:.3}",
10774 v.len()
10775 );
10776 }
10777 }
10778
10779 let mut q = e.uninit(t * nh * hd)?;
10780 let mut k = e.uninit(t * nkv * hd)?;
10781 let mut v = e.uninit(t * nkv * hd)?;
10783 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10787 let emit = island.is_none()
10790 && t >= 16
10791 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
10792 && *EMIT.get_or_init(|| {
10793 std::env::var("MEMRA_FA_EMIT")
10794 .map(|s| s != "0")
10795 .unwrap_or(true)
10796 });
10797 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
10798 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10799 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10800 let v_f16 = emit
10803 && crate::fa_f16pv_on()
10804 && match hd {
10805 512 => true,
10806 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
10807 _ => false,
10808 };
10809 if emit {
10810 e.rms_norm_qkv_w4b(
10811 &q0,
10812 &k0,
10813 &v0,
10814 fa.q_norm.float_data(),
10815 fa.k_norm.float_data(),
10816 ones,
10817 &mut q,
10818 &mut k,
10819 &mut v,
10820 &mut vb,
10821 hd,
10822 nh * t,
10823 nkv * t,
10824 eps,
10825 v_f16,
10826 )?;
10827 } else {
10828 e.rms_norm_qkv(
10829 &q0,
10830 &k0,
10831 &v0,
10832 fa.q_norm.float_data(),
10833 fa.k_norm.float_data(),
10834 ones,
10835 &mut q,
10836 &mut k,
10837 &mut v,
10838 hd,
10839 nh * t,
10840 nkv * t,
10841 eps,
10842 )?;
10843 }
10844
10845 let ff = if swa {
10846 None
10847 } else {
10848 Some(
10849 aux.rope_freqs(e)
10850 .expect("gemma4 global rope needs rope_freqs.weight"),
10851 )
10852 };
10853 #[cfg(debug_assertions)]
10854 if let Some(ff) = ff {
10855 crate::debug_assert_tensor_stream_device(
10856 ff,
10857 &e.stream(),
10858 "gemma4_attn_prime.rope_freqs",
10859 );
10860 }
10861 if emit {
10862 e.rope_neox2_bf16e(
10863 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
10864 )?;
10865 } else {
10866 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
10867 }
10868
10869 if let Some(cache) = cache {
10870 let kvl = cache.kv[il].as_mut().unwrap();
10871 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
10872 e.append_kv_quantized_rows(
10873 &k,
10874 &v,
10875 &mut kvl.k,
10876 &mut kvl.v,
10877 kvl.len,
10878 t,
10879 kvl.kv_dim_k,
10880 kvl.kv_dim_v,
10881 kvl.k_tok_bytes,
10882 kvl.v_tok_bytes,
10883 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
10884 )?;
10885 kvl.len += t;
10886 }
10887 let mut attn = e.zeros(t * nh * hd)?;
10888 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
10892 if let Some(span) = island {
10893 let w = if swa && t > win { win } else { 0 };
10898 e.sdpa_naive_island(&q, &k, &v, &mut attn, span, hd, nh, nkv, t, t, scale, w)?;
10899 } else if swa && (t > win || Self::gemma_fa_one_program()) {
10900 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
10901 if emit {
10902 e.fa_prefill_w_pre(
10903 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
10904 )?;
10905 } else {
10906 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
10907 }
10908 } else {
10909 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
10910 }
10911 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
10912 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
10913 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
10914 if emit {
10915 e.fa_prefill_hd512_pre(
10916 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
10917 )?;
10918 } else {
10919 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
10920 }
10921 } else {
10922 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
10923 }
10924 Ok(e.matmul(&fa.wo, &attn, t)?)
10925 }
10926
10927 fn gemma4_attn(
10929 &self,
10930 e: &Engine,
10931 fa: &crate::hybrid::FullAttnLayer,
10932 il: usize,
10933 h: &CudaSlice<f32>,
10934 pos_d: &CudaSlice<i32>,
10935 t: usize,
10936 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10937 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None, None)
10938 }
10939
10940 fn gemma4_moe_q8(
10945 &self,
10946 e: &Engine,
10947 m: &crate::hybrid::MoeWeights,
10948 bits: &crate::hybrid::Gemma4MoeBits,
10949 mq: &(CudaSlice<i8>, CudaSlice<f32>),
10950 router_in: &CudaSlice<f32>,
10951 t: usize,
10952 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10953 let cfg = &self.cfg;
10954 let moe = cfg.moe.as_ref().unwrap();
10955 let n_embd = cfg.n_embd as usize;
10956 let n_expert = moe.expert_count as usize;
10957 let n_used = moe.expert_used_count as usize;
10958 let n_ff_exp = moe.expert_ff_length as usize;
10959 let logits = if crate::router_kernel_on() {
10963 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
10964 } else {
10965 e.matmul(&m.gate_inp, router_in, t)?
10966 };
10967 let dev = m.dev_exps.as_ref().unwrap();
10968 let (sel_d, w_d) =
10969 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
10970 let (zq, zd) = mq;
10971 if t == 1 {
10972 let selv = sel_d.slice(0..n_used);
10973 let wv = w_d.slice(0..n_used);
10974 let act = e.moe_gate_up_gelu8_dev_q8(
10975 &dev.ptr_row,
10976 &selv,
10977 zq,
10978 zd,
10979 n_embd,
10980 n_ff_exp,
10981 n_used,
10982 n_expert,
10983 m.gate_exps.qtype,
10984 m.up_exps.qtype,
10985 m.gate_exps.row_bytes,
10986 m.up_exps.row_bytes,
10987 )?;
10988 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
10989 let mut moe_out = e.uninit(n_embd)?;
10990 e.moe_down8_fma_dev_q8(
10991 &dev.ptr_row,
10992 &selv,
10993 &wv,
10994 &aq2,
10995 &ad2,
10996 &mut moe_out.slice_mut(0..n_embd),
10997 n_ff_exp,
10998 n_embd,
10999 n_used,
11000 n_expert,
11001 m.down_exps.qtype,
11002 m.down_exps.row_bytes,
11003 )?;
11004 return Ok(moe_out);
11005 }
11006 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11007 let act = if csr {
11008 e.moe_gate_up_gelu8_dev_q8_csr(
11009 &dev.ptr_row,
11010 &sel_d,
11011 zq,
11012 zd,
11013 t * n_used,
11014 n_embd,
11015 n_ff_exp,
11016 n_used,
11017 n_expert,
11018 m.gate_exps.qtype,
11019 m.up_exps.qtype,
11020 m.gate_exps.row_bytes,
11021 m.up_exps.row_bytes,
11022 )?
11023 } else {
11024 e.moe_gate_up_gelu8_dev_q8_rows(
11025 &dev.ptr_row,
11026 &sel_d,
11027 zq,
11028 zd,
11029 t,
11030 n_embd,
11031 n_ff_exp,
11032 n_used,
11033 n_expert,
11034 m.gate_exps.qtype,
11035 m.up_exps.qtype,
11036 m.gate_exps.row_bytes,
11037 m.up_exps.row_bytes,
11038 )?
11039 };
11040 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11041 let mut moe_out = e.uninit(t * n_embd)?;
11042 e.moe_down8_fma_dev_q8_rows_g(
11045 &dev.ptr_row,
11046 &sel_d,
11047 &w_d,
11048 &aq2,
11049 &ad2,
11050 &mut moe_out,
11051 t,
11052 n_ff_exp,
11053 n_embd,
11054 n_used,
11055 n_expert,
11056 m.down_exps.qtype,
11057 m.down_exps.row_bytes,
11058 )?;
11059 Ok(moe_out)
11060 }
11061
11062 fn gemma4_moe(
11066 &self,
11067 e: &Engine,
11068 m: &crate::hybrid::MoeWeights,
11069 bits: &crate::hybrid::Gemma4MoeBits,
11070 moe_in: &CudaSlice<f32>,
11071 router_in: &CudaSlice<f32>,
11072 t: usize,
11073 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11074 let cfg = &self.cfg;
11075 let moe = cfg.moe.as_ref().unwrap();
11076 let n_embd = cfg.n_embd as usize;
11077 let n_expert = moe.expert_count as usize;
11078 let n_used = moe.expert_used_count as usize;
11079 let n_ff_exp = moe.expert_ff_length as usize;
11080
11081 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
11085 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11086 } else {
11087 e.matmul(&m.gate_inp, router_in, t)?
11088 };
11089
11090 if t < PRIME_MIN_T
11095 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11096 && expert_dp4a_supported(m.gate_exps.qtype)
11097 && expert_dp4a_supported(m.up_exps.qtype)
11098 && expert_dp4a_supported(m.down_exps.qtype)
11099 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11100 {
11101 let dev = m.dev_exps.as_ref().unwrap();
11102 let (sel_d, w_d) =
11103 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11104 if t == 1 {
11105 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
11106 let selv = sel_d.slice(0..n_used);
11107 let wv = w_d.slice(0..n_used);
11108 let act = e.moe_gate_up_gelu8_dev_q8(
11109 &dev.ptr_row,
11110 &selv,
11111 &zq,
11112 &zd,
11113 n_embd,
11114 n_ff_exp,
11115 n_used,
11116 n_expert,
11117 m.gate_exps.qtype,
11118 m.up_exps.qtype,
11119 m.gate_exps.row_bytes,
11120 m.up_exps.row_bytes,
11121 )?;
11122 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11123 let mut moe_out = e.uninit(n_embd)?;
11124 e.moe_down8_fma_dev_q8(
11125 &dev.ptr_row,
11126 &selv,
11127 &wv,
11128 &aq2,
11129 &ad2,
11130 &mut moe_out.slice_mut(0..n_embd),
11131 n_ff_exp,
11132 n_embd,
11133 n_used,
11134 n_expert,
11135 m.down_exps.qtype,
11136 m.down_exps.row_bytes,
11137 )?;
11138 return Ok(moe_out);
11139 }
11140 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11145 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11146 let act = if csr {
11147 e.moe_gate_up_gelu8_dev_q8_csr(
11148 &dev.ptr_row,
11149 &sel_d,
11150 &zq,
11151 &zd,
11152 t * n_used,
11153 n_embd,
11154 n_ff_exp,
11155 n_used,
11156 n_expert,
11157 m.gate_exps.qtype,
11158 m.up_exps.qtype,
11159 m.gate_exps.row_bytes,
11160 m.up_exps.row_bytes,
11161 )?
11162 } else {
11163 e.moe_gate_up_gelu8_dev_q8_rows(
11164 &dev.ptr_row,
11165 &sel_d,
11166 &zq,
11167 &zd,
11168 t,
11169 n_embd,
11170 n_ff_exp,
11171 n_used,
11172 n_expert,
11173 m.gate_exps.qtype,
11174 m.up_exps.qtype,
11175 m.gate_exps.row_bytes,
11176 m.up_exps.row_bytes,
11177 )?
11178 };
11179 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11180 let mut moe_out = e.uninit(t * n_embd)?;
11181 e.moe_down8_fma_dev_q8_rows_g(
11182 &dev.ptr_row,
11183 &sel_d,
11184 &w_d,
11185 &aq2,
11186 &ad2,
11187 &mut moe_out,
11188 t,
11189 n_ff_exp,
11190 n_embd,
11191 n_used,
11192 n_expert,
11193 m.down_exps.qtype,
11194 m.down_exps.row_bytes,
11195 )?;
11196 return Ok(moe_out);
11197 }
11198
11199 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
11200 for (i, &sx) in sel_all.iter().enumerate() {
11201 w_all[i] *= bits.per_expert_scale[sx as usize];
11202 }
11203
11204 if t >= PRIME_MIN_T
11208 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11209 && expert_dp4a_supported(m.gate_exps.qtype)
11210 && expert_dp4a_supported(m.up_exps.qtype)
11211 && expert_dp4a_supported(m.down_exps.qtype)
11212 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
11213 {
11214 let dev = m.dev_exps.as_ref().unwrap();
11215 let n_pairs = t * n_used;
11216 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
11217 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
11218 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
11219 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
11220 let pt = e.htod_i32(&pair_tok)?;
11221 let pw = e.htod(&w_all)?;
11222 let toff = e.htod_i32(&tok_off)?;
11223 let tids = e.htod_i32(&tok_ids)?;
11224 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11225 for p in 0..n_pairs {
11226 by_ex[pair_ex[p] as usize].push(p as i32);
11227 }
11228 let mut ex_ids: Vec<i32> = Vec::new();
11229 let mut ex_off: Vec<i32> = vec![0];
11230 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
11231 for (ex, list) in by_ex.iter().enumerate() {
11232 if list.is_empty() {
11233 continue;
11234 }
11235 ex_ids.push(ex as i32);
11236 ex_pairs.extend_from_slice(list);
11237 ex_off.push(ex_pairs.len() as i32);
11238 }
11239 let n_active = ex_ids.len();
11240 let exi = e.htod_i32(&ex_ids)?;
11241 let exo = e.htod_i32(&ex_off)?;
11242 let exp_d = e.htod_i32(&ex_pairs)?;
11243 if crate::moe_f16g_gemma_on()
11251 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
11252 && f16g_proj_ok(m.up_exps.qtype, n_embd)
11253 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
11254 {
11255 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11256 let csr_tok_d = e.htod_i32(&csr_tok)?;
11257 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
11258 let g_csr = e.moe_f16_grouped(
11259 &dev.ptr_row,
11260 0,
11261 n_expert,
11262 &exi,
11263 &ex_off,
11264 &exo,
11265 &z_f16,
11266 &z_s,
11267 n_embd,
11268 n_ff_exp,
11269 n_active,
11270 n_pairs,
11271 m.gate_exps.qtype,
11272 m.gate_exps.row_bytes,
11273 )?;
11274 let u_csr = e.moe_f16_grouped(
11275 &dev.ptr_row,
11276 1,
11277 n_expert,
11278 &exi,
11279 &ex_off,
11280 &exo,
11281 &z_f16,
11282 &z_s,
11283 n_embd,
11284 n_ff_exp,
11285 n_active,
11286 n_pairs,
11287 m.up_exps.qtype,
11288 m.up_exps.row_bytes,
11289 )?;
11290 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
11291 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
11292 let d_csr = e.moe_f16_grouped(
11293 &dev.ptr_row,
11294 2,
11295 n_expert,
11296 &exi,
11297 &ex_off,
11298 &exo,
11299 &a_f16,
11300 &a_s,
11301 n_ff_exp,
11302 n_embd,
11303 n_active,
11304 n_pairs,
11305 m.down_exps.qtype,
11306 m.down_exps.row_bytes,
11307 )?;
11308 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
11309 let mut moe_out = e.uninit(t * n_embd)?;
11310 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11311 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
11312 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
11313 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
11314 eprintln!(
11315 "[f16g-debug] post-permute bad={} post-scatter bad={}",
11316 scan(&yd),
11317 scan(&mo)
11318 );
11319 }
11320 return Ok(moe_out);
11321 }
11322 let mma =
11325 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
11326 let (gate, up) = if mma {
11327 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
11328 (
11329 e.mmq_iq_experts(
11330 &dev.ptr_row,
11331 0,
11332 n_expert,
11333 &exi,
11334 &exo,
11335 &exp_d,
11336 &pt,
11337 &z_scr,
11338 n_embd,
11339 n_ff_exp,
11340 n_active,
11341 n_pairs,
11342 t,
11343 m.gate_exps.qtype,
11344 m.gate_exps.row_bytes,
11345 )?,
11346 e.mmq_iq_experts(
11347 &dev.ptr_row,
11348 1,
11349 n_expert,
11350 &exi,
11351 &exo,
11352 &exp_d,
11353 &pt,
11354 &z_scr,
11355 n_embd,
11356 n_ff_exp,
11357 n_active,
11358 n_pairs,
11359 t,
11360 m.up_exps.qtype,
11361 m.up_exps.row_bytes,
11362 )?,
11363 )
11364 } else {
11365 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11366 (
11367 e.moe_pairs_matvec_q8_dec(
11368 &dev.ptr_row,
11369 0,
11370 &exi,
11371 &exo,
11372 &exp_d,
11373 &pt,
11374 &zq,
11375 &zd,
11376 n_embd,
11377 n_ff_exp,
11378 n_expert,
11379 n_active,
11380 n_pairs,
11381 m.gate_exps.qtype,
11382 m.gate_exps.row_bytes,
11383 )?,
11384 e.moe_pairs_matvec_q8_dec(
11385 &dev.ptr_row,
11386 1,
11387 &exi,
11388 &exo,
11389 &exp_d,
11390 &pt,
11391 &zq,
11392 &zd,
11393 n_embd,
11394 n_ff_exp,
11395 n_expert,
11396 n_active,
11397 n_pairs,
11398 m.up_exps.qtype,
11399 m.up_exps.row_bytes,
11400 )?,
11401 )
11402 };
11403 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
11404 let pself = e.htod_i32(&pair_self)?;
11405 let y_down = if mma {
11417 let in_pad = n_ff_exp.div_ceil(256) * 256;
11418 let a_scr = if crate::moe_fuse_actq_on() {
11419 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
11420 } else {
11421 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11422 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
11423 };
11424 e.mmq_iq_experts(
11425 &dev.ptr_row,
11426 2,
11427 n_expert,
11428 &exi,
11429 &exo,
11430 &exp_d,
11431 &pself,
11432 &a_scr,
11433 in_pad,
11434 n_embd,
11435 n_active,
11436 n_pairs,
11437 n_pairs,
11438 m.down_exps.qtype,
11439 m.down_exps.row_bytes,
11440 )?
11441 } else {
11442 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11443 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
11444 e.moe_pairs_matvec_q8_dec(
11445 &dev.ptr_row,
11446 2,
11447 &exi,
11448 &exo,
11449 &exp_d,
11450 &pself,
11451 &aq2,
11452 &ad2,
11453 n_ff_exp,
11454 n_embd,
11455 n_expert,
11456 n_active,
11457 n_pairs,
11458 m.down_exps.qtype,
11459 m.down_exps.row_bytes,
11460 )?
11461 };
11462 let mut moe_out = e.uninit(t * n_embd)?;
11463 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11464 return Ok(moe_out);
11465 }
11466
11467 let g_len = m.gate_exps.expert_stride;
11468 let u_len = m.up_exps.expert_stride;
11469 let d_len = m.down_exps.expert_stride;
11470 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
11474 let (mut sg, mut su, mut sd) = if dev.is_some() {
11475 (None, None, None)
11476 } else {
11477 (
11478 Some(e.alloc_u8_uninit(g_len)?),
11479 Some(e.alloc_u8_uninit(u_len)?),
11480 Some(e.alloc_u8_uninit(d_len)?),
11481 )
11482 };
11483 let mut moe_out = e.zeros(t * n_embd)?;
11484 for tok in 0..t {
11485 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
11486 let w = &w_all[tok * n_used..(tok + 1) * n_used];
11487 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
11488 for (j, &ex) in sel.iter().enumerate() {
11489 let ex = ex as usize;
11490 let gate = match dev {
11491 Some(d) => e.qmatvec_view(
11492 &d.gate,
11493 ex * g_len..(ex + 1) * g_len,
11494 &zt,
11495 1,
11496 m.gate_exps.in_f,
11497 m.gate_exps.out_f,
11498 m.gate_exps.qtype,
11499 m.gate_exps.row_bytes,
11500 )?,
11501 None => {
11502 let sg = sg.as_mut().unwrap();
11503 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
11504 e.qmatvec_view(
11505 sg,
11506 0..g_len,
11507 &zt,
11508 1,
11509 m.gate_exps.in_f,
11510 m.gate_exps.out_f,
11511 m.gate_exps.qtype,
11512 m.gate_exps.row_bytes,
11513 )?
11514 }
11515 };
11516 let up = match dev {
11517 Some(d) => e.qmatvec_view(
11518 &d.up,
11519 ex * u_len..(ex + 1) * u_len,
11520 &zt,
11521 1,
11522 m.up_exps.in_f,
11523 m.up_exps.out_f,
11524 m.up_exps.qtype,
11525 m.up_exps.row_bytes,
11526 )?,
11527 None => {
11528 let su = su.as_mut().unwrap();
11529 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
11530 e.qmatvec_view(
11531 su,
11532 0..u_len,
11533 &zt,
11534 1,
11535 m.up_exps.in_f,
11536 m.up_exps.out_f,
11537 m.up_exps.qtype,
11538 m.up_exps.row_bytes,
11539 )?
11540 }
11541 };
11542 let mut act = e.uninit(n_ff_exp)?;
11543 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
11544 let actv = act.slice(0..n_ff_exp);
11545 let y = match dev {
11546 Some(d) => e.qmatvec_view(
11547 &d.down,
11548 ex * d_len..(ex + 1) * d_len,
11549 &actv,
11550 1,
11551 m.down_exps.in_f,
11552 m.down_exps.out_f,
11553 m.down_exps.qtype,
11554 m.down_exps.row_bytes,
11555 )?,
11556 None => {
11557 let sd = sd.as_mut().unwrap();
11558 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
11559 e.qmatvec_view(
11560 sd,
11561 0..d_len,
11562 &actv,
11563 1,
11564 m.down_exps.in_f,
11565 m.down_exps.out_f,
11566 m.down_exps.qtype,
11567 m.down_exps.row_bytes,
11568 )?
11569 }
11570 };
11571 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
11572 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
11573 }
11574 }
11575 Ok(moe_out)
11576 }
11577
11578 fn gemma4_layer(
11580 &self,
11581 e: &Engine,
11582 il: usize,
11583 layer: &crate::hybrid::HybridLayer,
11584 x: &CudaSlice<f32>,
11585 pos_d: &CudaSlice<i32>,
11586 t: usize,
11587 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11588 let n_embd = self.cfg.n_embd as usize;
11589 let eps = self.cfg.rms_eps;
11590
11591 let mut h = e.zeros(t * n_embd)?;
11592 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
11593 let Mixer::Full(fa) = &layer.mixer else {
11594 panic!("gemma4 layer {il} not full-attn")
11595 };
11596 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
11597 let mut cur = e.zeros(t * n_embd)?;
11599 e.rms_norm(
11600 &o,
11601 layer.post_attn_norm.float_data(),
11602 &mut cur,
11603 n_embd,
11604 t,
11605 eps,
11606 )?;
11607 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
11608 }
11609
11610 fn gemma4_layer_tail_add(
11614 &self,
11615 e: &Engine,
11616 layer: &crate::hybrid::HybridLayer,
11617 cur: &CudaSlice<f32>,
11618 x: &CudaSlice<f32>,
11619 t: usize,
11620 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11621 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
11622 }
11623
11624 fn gemma4_layer_tail_add_n(
11627 &self,
11628 e: &Engine,
11629 layer: &crate::hybrid::HybridLayer,
11630 cur: &CudaSlice<f32>,
11631 x: &CudaSlice<f32>,
11632 t: usize,
11633 next_norm: Option<&CudaSlice<f32>>,
11634 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
11635 let n_embd = self.cfg.n_embd as usize;
11636 let bits = layer.gemma4.as_ref().unwrap();
11637 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
11638 let mut xn = e.uninit(t * n_embd)?;
11639 match next_norm {
11640 Some(w) => {
11641 let mut hn = e.uninit(t * n_embd)?;
11642 e.add_scale_rms_norm(
11643 &sn,
11644 &attn_out,
11645 bits.layer_scale,
11646 w,
11647 &mut xn,
11648 &mut hn,
11649 n_embd,
11650 t,
11651 self.cfg.rms_eps,
11652 )?;
11653 Ok((xn, Some(hn)))
11654 }
11655 None => {
11656 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
11657 Ok((xn, None))
11658 }
11659 }
11660 }
11661
11662 fn gemma4_layer_tail_core(
11665 &self,
11666 e: &Engine,
11667 layer: &crate::hybrid::HybridLayer,
11668 cur: &CudaSlice<f32>,
11669 x: &CudaSlice<f32>,
11670 t: usize,
11671 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11672 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
11673 }
11674
11675 fn gemma4_layer_tail_core_pn(
11682 &self,
11683 e: &Engine,
11684 layer: &crate::hybrid::HybridLayer,
11685 cur: &CudaSlice<f32>,
11686 x: &CudaSlice<f32>,
11687 t: usize,
11688 pre_norm: Option<&CudaSlice<f32>>,
11689 defer_post_norm: bool,
11690 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11691 let n_embd = self.cfg.n_embd as usize;
11692 let eps = self.cfg.rms_eps;
11693 let bits = layer.gemma4.as_ref().unwrap();
11694
11695 let Some(mbits) = bits.moe_bits.as_ref() else {
11698 let crate::hybrid::Ffn::Dense {
11699 ffn_gate,
11700 ffn_up,
11701 ffn_down,
11702 } = &layer.ffn
11703 else {
11704 panic!("gemma4 dense layer without Dense ffn")
11705 };
11706 let mut attn_out = e.uninit(t * n_embd)?;
11707 let mut zsh = e.uninit(t * n_embd)?;
11708 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11711 match pre_norm {
11712 Some(wa) if t == 1 => {
11713 zpair = Some(e.rms_pre_add_rms_norm_q8z(
11714 cur,
11715 wa,
11716 x,
11717 bits.ffn_norm.float_data(),
11718 &mut attn_out,
11719 &mut zsh,
11720 n_embd,
11721 t,
11722 eps,
11723 )?);
11724 }
11725 Some(wa) => e.rms_pre_add_rms_norm(
11726 cur,
11727 wa,
11728 x,
11729 bits.ffn_norm.float_data(),
11730 &mut attn_out,
11731 &mut zsh,
11732 n_embd,
11733 t,
11734 eps,
11735 )?,
11736 None => e.add_rms_norm(
11737 cur,
11738 x,
11739 bits.ffn_norm.float_data(),
11740 &mut attn_out,
11741 &mut zsh,
11742 n_embd,
11743 t,
11744 eps,
11745 )?,
11746 }
11747 let n_ff = ffn_gate.out_features();
11748 let (gate, up) = if t == 1 {
11754 let (zq, zd) = match zpair {
11755 Some(p) => p,
11756 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
11757 };
11758 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
11759 Some(p) => p,
11760 None => match e.matmul_nvfp4_fused2(ffn_gate, ffn_up, &zq, &zd, 1)? {
11762 Some(p) => p,
11763 None => (
11764 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
11765 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
11766 ),
11767 },
11768 }
11769 } else {
11770 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11775 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11776 let fused = if f2b {
11777 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
11778 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
11779 } else {
11780 None
11781 };
11782 match fused {
11783 Some(p) => p,
11784 None => {
11785 e.mmq_act_begin();
11787 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
11788 }
11789 }
11790 };
11791 let mut act = e.uninit(t * n_ff)?;
11792 let f0 = if e.uses_q8_1_fast(ffn_down) {
11795 let upv = e.view(&up, t * n_ff);
11796 let up_all = upv.slice(0..t * n_ff);
11797 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
11798 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
11799 } else {
11800 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11801 e.matmul(ffn_down, &act, t)?
11802 };
11803 if defer_post_norm {
11804 return Ok((f0, attn_out));
11805 }
11806 let mut sn = e.uninit(t * n_embd)?;
11807 e.rms_norm(
11808 &f0,
11809 bits.post_ffw_norm.float_data(),
11810 &mut sn,
11811 n_embd,
11812 t,
11813 eps,
11814 )?;
11815 return Ok((sn, attn_out));
11816 };
11817
11818 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
11819 let mut attn_out = e.uninit(t * n_embd)?;
11824 let mut router_in = e.uninit(t * n_embd)?;
11825 let fast_moe = match &layer.ffn {
11826 crate::hybrid::Ffn::Moe(m) => {
11827 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11828 && expert_dp4a_supported(m.gate_exps.qtype)
11829 && expert_dp4a_supported(m.up_exps.qtype)
11830 && expert_dp4a_supported(m.down_exps.qtype)
11831 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11832 }
11833 _ => false,
11834 };
11835 let q8z = t < PRIME_MIN_T && fast_moe;
11836 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
11837 let (z0, m2) = e.add_rms_norm3_q8z(
11838 cur,
11839 x,
11840 bits.ffn_norm.float_data(),
11841 &mbits.router_scale_pre,
11842 mbits.pre_ffw_norm_2.float_data(),
11843 &mut attn_out,
11844 &mut router_in,
11845 n_embd,
11846 t,
11847 eps,
11848 )?;
11849 (None, Some(z0), Some(m2))
11850 } else {
11851 let mut zsh = e.uninit(t * n_embd)?;
11852 let mut moe_in = e.uninit(t * n_embd)?;
11853 e.add_rms_norm3(
11854 cur,
11855 x,
11856 bits.ffn_norm.float_data(),
11857 &mbits.router_scale_pre,
11858 mbits.pre_ffw_norm_2.float_data(),
11859 &mut attn_out,
11860 &mut zsh,
11861 &mut router_in,
11862 &mut moe_in,
11863 n_embd,
11864 t,
11865 eps,
11866 )?;
11867 (Some((zsh, moe_in)), None, None)
11868 };
11869 let attn_out2 = attn_out;
11870 #[allow(unused_variables)]
11871 let attn_out = &attn_out2;
11872 let n_ff = mbits.shared_gate.out_features();
11873 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
11874 if t == 1 {
11875 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
11876 Some(p) => p,
11877 None => match e.matmul_nvfp4_fused2(
11878 &mbits.shared_gate,
11879 &mbits.shared_up,
11880 zq,
11881 zd,
11882 1,
11883 )? {
11884 Some(p) => p,
11885 None => {
11886 let h0 = e.zeros(0)?;
11887 (
11888 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
11889 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
11890 )
11891 }
11892 },
11893 }
11894 } else {
11895 let h0 = e.zeros(0)?;
11897 (
11898 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
11899 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
11900 )
11901 }
11902 } else {
11903 let (zsh, _) = zsh_f32.as_ref().unwrap();
11904 (
11905 e.matmul(&mbits.shared_gate, zsh, t)?,
11906 e.matmul(&mbits.shared_up, zsh, t)?,
11907 )
11908 };
11909 let mut act = e.uninit(t * n_ff)?;
11910 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11911 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
11912 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
11913 panic!("gemma4 layer not MoE")
11914 };
11915 let moe0 = match (&moe_q8, &zsh_f32) {
11916 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
11917 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
11918 _ => unreachable!(),
11919 };
11920 let mut mlp = e.uninit(t * n_embd)?;
11922 let mut moe = e.uninit(t * n_embd)?;
11923 e.rms_norm2x(
11924 &mlp0,
11925 &moe0,
11926 mbits.post_ffw_norm_1.float_data(),
11927 mbits.post_ffw_norm_2.float_data(),
11928 &mut mlp,
11929 &mut moe,
11930 n_embd,
11931 t,
11932 eps,
11933 )?;
11934
11935 let mut sum = e.uninit(t * n_embd)?;
11938 let mut sn = e.uninit(t * n_embd)?;
11939 e.add_rms_norm(
11940 &mlp,
11941 &moe,
11942 bits.post_ffw_norm.float_data(),
11943 &mut sum,
11944 &mut sn,
11945 n_embd,
11946 t,
11947 eps,
11948 )?;
11949 Ok((sn, attn_out2))
11950 }
11951
11952 pub(crate) fn gemma4_layer_tail_add_nq_pn(
11962 &self,
11963 e: &Engine,
11964 layer: &crate::hybrid::HybridLayer,
11965 o: &CudaSlice<f32>,
11966 x: &CudaSlice<f32>,
11967 t: usize,
11968 next_norm: Option<&CudaSlice<f32>>,
11969 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
11970 {
11971 let n_embd = self.cfg.n_embd as usize;
11972 let eps = self.cfg.rms_eps;
11973 let bits = layer.gemma4.as_ref().unwrap();
11974 if Engine::g4_pnfold_on() && matches!(layer.ffn, crate::hybrid::Ffn::Dense { .. }) {
11975 let (f0, attn_out) = self.gemma4_layer_tail_core_pn(
11976 e,
11977 layer,
11978 o,
11979 x,
11980 t,
11981 Some(layer.post_attn_norm.float_data()),
11982 true,
11983 )?;
11984 let mut xn = e.uninit(t * n_embd)?;
11985 return match next_norm {
11986 Some(w) => {
11987 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
11988 &f0,
11989 bits.post_ffw_norm.float_data(),
11990 &attn_out,
11991 bits.layer_scale,
11992 w,
11993 &mut xn,
11994 n_embd,
11995 t,
11996 eps,
11997 )?;
11998 Ok((xn, Some(pair)))
11999 }
12000 None => {
12001 let mut sn = e.uninit(t * n_embd)?;
12002 e.rms_norm(
12003 &f0,
12004 bits.post_ffw_norm.float_data(),
12005 &mut sn,
12006 n_embd,
12007 t,
12008 eps,
12009 )?;
12010 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12011 Ok((xn, None))
12012 }
12013 };
12014 }
12015 let mut cur = e.uninit(t * n_embd)?;
12016 e.rms_norm(
12017 o,
12018 layer.post_attn_norm.float_data(),
12019 &mut cur,
12020 n_embd,
12021 t,
12022 eps,
12023 )?;
12024 self.gemma4_layer_tail_add_nq(e, layer, &cur, x, t, next_norm)
12025 }
12026
12027 pub(crate) fn gemma4_layer_tail_add_nq(
12028 &self,
12029 e: &Engine,
12030 layer: &crate::hybrid::HybridLayer,
12031 cur: &CudaSlice<f32>,
12032 x: &CudaSlice<f32>,
12033 t: usize,
12034 next_norm: Option<&CudaSlice<f32>>,
12035 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12036 {
12037 let n_embd = self.cfg.n_embd as usize;
12038 let bits = layer.gemma4.as_ref().unwrap();
12039 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12040 let mut xn = e.uninit(t * n_embd)?;
12041 match next_norm {
12042 Some(w) => {
12043 let pair = e.add_scale_rms_norm_q8_1(
12044 &sn,
12045 &attn_out,
12046 bits.layer_scale,
12047 w,
12048 &mut xn,
12049 n_embd,
12050 t,
12051 self.cfg.rms_eps,
12052 )?;
12053 Ok((xn, Some(pair)))
12054 }
12055 None => {
12056 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12057 Ok((xn, None))
12058 }
12059 }
12060 }
12061
12062 fn gemma4_forward(
12065 &self,
12066 e: &Engine,
12067 tokens: &[u32],
12068 last_only: bool,
12069 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12070 if self.is_gemma4_e4b() {
12073 return self.gemma4_e4b_forward(e, tokens, last_only);
12074 }
12075 let n_embd = self.cfg.n_embd as usize;
12076 let t = tokens.len();
12077 let pos: Vec<i32> = (0..t as i32).collect();
12078 let pos_d = e.htod_i32(&pos)?;
12079
12080 let mut x = self.embed(e, tokens)?;
12081 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12082 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
12085 let stat =
12086 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
12087 let h = e.dtoh(x)?;
12088 let bad = h.iter().filter(|v| !v.is_finite()).count();
12089 let mx = h
12090 .iter()
12091 .filter(|v| v.is_finite())
12092 .fold(0.0f32, |m, v| m.max(v.abs()));
12093 eprintln!(
12094 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
12095 &h[..3]
12096 );
12097 Ok(())
12098 };
12099 if probe {
12100 stat(e, &x, "embed")?;
12101 }
12102 for (il, layer) in self.layers.iter().enumerate() {
12103 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
12104 if probe {
12105 stat(e, &x, &format!("L{il}"))?;
12106 }
12107 }
12108 let mut hn = e.zeros(t * n_embd)?;
12109 e.rms_norm(
12110 &x,
12111 self.output_norm.float_data(),
12112 &mut hn,
12113 n_embd,
12114 t,
12115 self.cfg.rms_eps,
12116 )?;
12117 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12118 let n_vocab = self.output.out_features();
12119 let logits = if last_only {
12120 let hv = e.view(&hn, t * n_embd);
12121 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
12122 let mut hlast = e.zeros(n_embd)?;
12123 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
12124 let mut ld = e.matmul(&self.output, &hlast, 1)?;
12125 e.softcap(&mut ld, cap, n_vocab)?;
12126 self.gemma4_suppress(e, &mut ld, 1)?;
12127 e.dtoh(&ld)?
12128 } else {
12129 let mut ld = e.matmul(&self.output, &hn, t)?;
12130 e.softcap(&mut ld, cap, t * n_vocab)?;
12131 self.gemma4_suppress(e, &mut ld, t)?;
12132 e.dtoh(&ld)?
12133 };
12134 Ok(logits)
12135 }
12136
12137 pub(crate) fn gemma4_prime(
12142 &self,
12143 e: &Engine,
12144 tokens: &[u32],
12145 cache: &mut Cache,
12146 overlay: Option<&crate::vision::EmbedOverlay>,
12147 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12148 if cache.pos != 0 {
12153 return Err(
12154 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
12155 — prime the full prompt in one call or decode tokenwise"
12156 .into(),
12157 );
12158 }
12159 let n_embd = self.cfg.n_embd as usize;
12160 let eps = self.cfg.rms_eps;
12161 let t = tokens.len();
12162 let pos: Vec<i32> = (0..t as i32).collect();
12163 let pos_d = e.htod_i32(&pos)?;
12164 let mut x = self.embed(e, tokens)?;
12165 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12166 let island: Option<CudaSlice<i32>> = match overlay {
12173 Some(ov) => {
12174 let mut span_id = vec![-1i32; t];
12175 for (i, &(pos, row_off, n_rows)) in ov.spans.iter().enumerate() {
12176 if pos + n_rows > t {
12177 return Err(format!(
12178 "gemma4 overlay span {i} [{pos}, {}) exceeds the prompt ({t})",
12179 pos + n_rows
12180 )
12181 .into());
12182 }
12183 let view = ov.rows.slice(row_off * n_embd..(row_off + n_rows) * n_embd);
12184 e.copy_view_into(&mut x, pos * n_embd, &view, n_rows * n_embd)?;
12185 for s in span_id.iter_mut().skip(pos).take(n_rows) {
12186 *s = i as i32;
12187 }
12188 }
12189 if std::env::var("MEMRA_GV_FORCE_CAUSAL").as_deref() == Ok("1") {
12193 None
12194 } else {
12195 Some(e.htod_i32(&span_id)?)
12196 }
12197 }
12198 None => None,
12199 };
12200 for (il, layer) in self.layers.iter().enumerate() {
12201 let mut h = e.zeros(t * n_embd)?;
12202 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12203 let Mixer::Full(fa) = &layer.mixer else {
12204 panic!("gemma4 layer not full-attn")
12205 };
12206 let trace = il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1");
12207 if trace {
12208 let v = e.dtoh(&h)?;
12209 let nan = v.iter().filter(|x| x.is_nan()).count();
12210 eprintln!("[g4-prime-trace] L0 post-attn_norm: nan={nan}/{}", v.len());
12211 }
12212 let o =
12213 self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache), island.as_ref())?;
12214 if trace {
12215 let v = e.dtoh(&o)?;
12216 let nan = v.iter().filter(|x| x.is_nan()).count();
12217 eprintln!("[g4-prime-trace] L0 post-attn: nan={nan}/{}", v.len());
12218 }
12219 let mut cur = e.zeros(t * n_embd)?;
12220 e.rms_norm(
12221 &o,
12222 layer.post_attn_norm.float_data(),
12223 &mut cur,
12224 n_embd,
12225 t,
12226 eps,
12227 )?;
12228 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
12229 self.dflash_tap(e, cache, il, &x, t)?;
12230 if std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
12232 let h = e.dtoh(&x)?;
12233 let nan = h.iter().filter(|v| v.is_nan()).count();
12234 let amax = h.iter().fold(0f32, |a, v| a.max(v.abs()));
12235 eprintln!(
12236 "[g4-prime-trace] layer {il}: nan={nan}/{} amax={amax:.3}",
12237 h.len()
12238 );
12239 if nan > 0 {
12240 return Err(format!("g4-prime-trace: first NaN at layer {il}").into());
12241 }
12242 }
12243 }
12244 cache.pos += t;
12245 let hiddens = e.clone_dtod(&x)?;
12246 let xv = e.view(&x, t * n_embd);
12247 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
12248 let mut h_seed = e.zeros(n_embd)?;
12249 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
12250 let mut hn = e.uninit(n_embd)?;
12251 e.rms_norm(
12252 &h_seed,
12253 self.output_norm.float_data(),
12254 &mut hn,
12255 n_embd,
12256 1,
12257 eps,
12258 )?;
12259 let mut ld = e.matmul(&self.output, &hn, 1)?;
12260 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12261 e.softcap(&mut ld, cap, self.output.out_features())?;
12262 self.gemma4_suppress(e, &mut ld, 1)?;
12263 let logits = e.dtoh(&ld)?;
12264 Ok((logits, h_seed, hiddens))
12265 }
12266
12267 fn gemma4_decode_attn(
12272 &self,
12273 e: &Engine,
12274 fa: &crate::hybrid::FullAttnLayer,
12275 il: usize,
12276 hq: &CudaSlice<i8>,
12277 hdq: &CudaSlice<f32>,
12278 pos_d: &CudaSlice<i32>,
12279 cache: &mut Cache,
12280 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12281 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12282 let eps = self.cfg.rms_eps;
12283 let aux = self.gemma4_aux.as_ref().unwrap();
12284 let ones = aux.ones(e);
12285 #[cfg(debug_assertions)]
12286 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
12287 let (hq, hdq) = (hq, hdq);
12288 let h0 = e.zeros(0)?;
12289 let h = &h0;
12290 let (q0, k0, v0) = if swa {
12291 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12292 Some(t3) => t3,
12293 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12296 Some((q0, k0)) => {
12297 let v0 = e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?;
12298 (q0, k0, v0)
12299 }
12300 None => (
12301 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12302 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12303 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12304 ),
12305 },
12306 }
12307 } else {
12308 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
12309 Some(p) => p,
12310 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12311 Some(p) => p,
12312 None => (
12313 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12314 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12315 ),
12316 },
12317 };
12318 let v0 = e.clone_dtod(&k0)?;
12319 (q0, k0, v0)
12320 };
12321 let mut q = e.uninit(nh * hd)?;
12322 let mut k = e.uninit(nkv * hd)?;
12323 let mut v = e.uninit(nkv * hd)?;
12324 let ff = if swa {
12327 None
12328 } else {
12329 Some(
12330 aux.rope_freqs(e)
12331 .expect("gemma4 global rope needs rope_freqs.weight"),
12332 )
12333 };
12334 #[cfg(debug_assertions)]
12335 if let Some(ff) = ff {
12336 crate::debug_assert_tensor_stream_device(
12337 ff,
12338 &e.stream(),
12339 "gemma4_decode_attn.rope_freqs",
12340 );
12341 }
12342 let kvl = cache.kv[il].as_mut().unwrap();
12343 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12344 if crate::Engine::qkv_append_on() {
12345 e.rms_norm_qkv_rope_append(
12349 &q0,
12350 &k0,
12351 &v0,
12352 fa.q_norm.float_data(),
12353 fa.k_norm.float_data(),
12354 ones,
12355 &mut q,
12356 &mut k,
12357 &mut v,
12358 hd,
12359 self.gemma4_rope_dims(il),
12360 nh,
12361 nkv,
12362 pos_d,
12363 nh,
12364 nkv,
12365 base,
12366 1.0,
12367 ff,
12368 eps,
12369 &mut kvl.k,
12370 &mut kvl.v,
12371 kvl.len,
12372 kvl.k_tok_bytes,
12373 kvl.v_tok_bytes,
12374 kv_fp8,
12375 )?;
12376 } else {
12377 e.rms_norm_qkv_rope(
12378 &q0,
12379 &k0,
12380 &v0,
12381 fa.q_norm.float_data(),
12382 fa.k_norm.float_data(),
12383 ones,
12384 &mut q,
12385 &mut k,
12386 &mut v,
12387 hd,
12388 self.gemma4_rope_dims(il),
12389 nh,
12390 nkv,
12391 pos_d,
12392 nh,
12393 nkv,
12394 base,
12395 1.0,
12396 ff,
12397 eps,
12398 )?;
12399 e.append_kv_quantized(
12400 &k,
12401 &v,
12402 &mut kvl.k,
12403 &mut kvl.v,
12404 kvl.len,
12405 kvl.kv_dim_k,
12406 kvl.kv_dim_v,
12407 kvl.k_tok_bytes,
12408 kvl.v_tok_bytes,
12409 kv_fp8,
12410 )?;
12411 }
12412 kvl.len += 1;
12413 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12417 let mut attn = e.uninit(nh * hd)?;
12418 if !swa
12420 && hd == 512
12421 && kvl.len >= crate::fa512_min_tkv()
12422 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12423 {
12424 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12425 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12426 let base = kvl.len as i32;
12428 e.i32_set_k(&mut kvl.len_d, base)?;
12429 e.fa_decode_rows(
12430 &q,
12431 &kp,
12432 &vp,
12433 &mut attn,
12434 hd,
12435 nh,
12436 nkv,
12437 kvl.len - 1,
12438 1,
12439 scale,
12440 kvl.k_tok_bytes,
12441 kvl.v_tok_bytes,
12442 Some((&kvl.len_d, -1)),
12443 false,
12444 false,
12445 None,
12446 )?;
12447 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12448 }
12449 if swa
12451 && kvl.len > win
12452 && hd == 256
12453 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12454 {
12455 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12456 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12457 let base = kvl.len as i32;
12458 e.i32_set_k(&mut kvl.len_d, base)?;
12459 e.fa_decode_rows_w(
12460 &q,
12461 &kp,
12462 &vp,
12463 &mut attn,
12464 hd,
12465 nh,
12466 nkv,
12467 &kvl.len_d,
12468 -1,
12469 1,
12470 scale,
12471 win,
12472 kvl.k_tok_bytes,
12473 kvl.v_tok_bytes,
12474 None,
12475 )?;
12476 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12477 }
12478 let (off_tok, t_kv) = if swa && kvl.len > win {
12479 (kvl.len - win, win)
12480 } else {
12481 (0, kvl.len)
12482 };
12483 let k_view = e.view_u8_range(
12484 &kvl.k,
12485 off_tok * kvl.k_tok_bytes,
12486 (off_tok + t_kv) * kvl.k_tok_bytes,
12487 );
12488 let v_view = e.view_u8_range(
12489 &kvl.v,
12490 off_tok * kvl.v_tok_bytes,
12491 (off_tok + t_kv) * kvl.v_tok_bytes,
12492 );
12493 e.fa_decode_kvmod(
12494 &q,
12495 &k_view,
12496 &v_view,
12497 &mut attn,
12498 hd,
12499 nh,
12500 nkv,
12501 t_kv,
12502 scale,
12503 kvl.k_tok_bytes,
12504 kvl.v_tok_bytes,
12505 swa && crate::Engine::wkv_on(),
12506 )?;
12507 Ok(e.matmul(&fa.wo, &attn, 1)?)
12508 }
12509
12510 #[allow(clippy::too_many_arguments)]
12517 pub fn gemma4_decode_step_dc(
12518 &self,
12519 e: &Engine,
12520 token_d: &CudaSlice<u32>,
12521 pos_d: &mut CudaSlice<i32>,
12522 embd_gpu: &CudaSlice<u8>,
12523 embd_qt: i32,
12524 embd_rb: usize,
12525 cache: &mut Cache,
12526 n_vocab: usize,
12527 cap_bucket_max: Option<(usize, usize)>,
12528 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
12529 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
12530 self.gemma4_decode_step_dc_into(
12531 e,
12532 token_d,
12533 pos_d,
12534 embd_gpu,
12535 embd_qt,
12536 embd_rb,
12537 cache,
12538 n_vocab,
12539 cap_bucket_max,
12540 &mut tok_out,
12541 )?;
12542 Ok(tok_out)
12543 }
12544
12545 #[allow(clippy::too_many_arguments)]
12548 pub fn gemma4_decode_step_dc_into(
12549 &self,
12550 e: &Engine,
12551 token_d: &CudaSlice<u32>,
12552 pos_d: &mut CudaSlice<i32>,
12553 embd_gpu: &CudaSlice<u8>,
12554 embd_qt: i32,
12555 embd_rb: usize,
12556 cache: &mut Cache,
12557 n_vocab: usize,
12558 cap_bucket_max: Option<(usize, usize)>,
12559 tok_out: &mut CudaSlice<u32>,
12560 ) -> Result<(), Box<dyn std::error::Error>> {
12561 let n_embd = self.cfg.n_embd as usize;
12562 let eps = self.cfg.rms_eps;
12563 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
12564 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12565 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12566 let n_layers = self.layers.len();
12567 for (il, layer) in self.layers.iter().enumerate() {
12568 let (hq, hdq) = match h_carry.take() {
12569 Some(p) => p,
12570 None => {
12571 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
12572 }
12573 };
12574 let Mixer::Full(fa) = &layer.mixer else {
12575 panic!("gemma4 layer {il} not full-attn")
12576 };
12577 let o =
12578 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
12579 let next_norm = if il + 1 < n_layers {
12580 Some(self.layers[il + 1].attn_norm.float_data())
12581 } else {
12582 None
12583 };
12584 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
12585 x = xn;
12586 h_carry = hn;
12587 }
12588 let mut hn = e.uninit(n_embd)?;
12589 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12590 let mut logits = e.matmul(&self.output, &hn, 1)?;
12591 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
12593 e.inc_seqlen(pos_d)?;
12594 if cap_bucket_max.is_none() {
12595 cache.pos += 1;
12596 }
12597 Ok(())
12598 }
12599
12600 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
12607 let n_embd = self.cfg.n_embd as usize;
12608 let n_vocab = self.output.out_features();
12609 let n_layers = self.layers.len();
12610 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
12611 for il in 0..n_layers {
12612 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
12613 qmax = qmax.max(nh * hd);
12614 kvmax = kvmax.max(nkv * hd);
12615 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
12616 ffmax = ffmax.max(ffn_gate.out_features());
12617 }
12618 }
12619 Ok(G4DcSlots {
12620 x: e.uninit(n_embd)?,
12621 xn: e.uninit(n_embd)?,
12622 cur: e.uninit(n_embd)?,
12623 hq: e.alloc_i8_uninit(n_embd)?,
12624 hd_: e.uninit(n_embd / 32)?,
12625 q0: e.uninit(qmax)?,
12626 k0: e.uninit(kvmax)?,
12627 v0: e.uninit(kvmax)?,
12628 q: e.uninit(qmax)?,
12629 k: e.uninit(kvmax)?,
12630 v: e.uninit(kvmax)?,
12631 attn: e.uninit(qmax)?,
12632 o: e.uninit(n_embd)?,
12633 attn_out: e.uninit(n_embd)?,
12634 zsh: e.uninit(n_embd)?,
12635 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
12638 zd: e.uninit(n_embd.max(qmax) / 32)?,
12639 gate: e.uninit(ffmax)?,
12640 up: e.uninit(ffmax)?,
12641 act: e.uninit(ffmax)?,
12642 actq: e.alloc_i8_uninit(ffmax)?,
12643 actd: e.uninit(ffmax / 32)?,
12644 f0: e.uninit(n_embd)?,
12645 sn: e.uninit(n_embd)?,
12646 hn: e.uninit(n_embd)?,
12647 logits: e.uninit(n_vocab)?,
12648 })
12649 }
12650
12651 fn g4_matvec_m1_into(
12654 &self,
12655 e: &Engine,
12656 w: &crate::model::GpuTensor,
12657 aq: &CudaSlice<i8>,
12658 ad: &CudaSlice<f32>,
12659 y: &mut CudaSlice<f32>,
12660 ) -> Result<(), Box<dyn std::error::Error>> {
12661 use crate::model::GpuTensor;
12662 let (bytes, qtype, row_bytes, scale, rp) = match w {
12663 GpuTensor::Quant {
12664 bytes,
12665 qtype,
12666 row_bytes,
12667 scale,
12668 rp,
12669 ..
12670 } => (bytes, *qtype, *row_bytes, *scale, *rp),
12671 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
12672 };
12673 let (mbytes, mrp) = match w {
12674 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12675 _ => (bytes, rp),
12676 };
12677 e.qmatvec_mmvq_into(
12678 mbytes,
12679 aq,
12680 ad,
12681 1,
12682 w.in_features(),
12683 w.out_features(),
12684 qtype,
12685 row_bytes,
12686 scale,
12687 mrp,
12688 y,
12689 )
12690 }
12691
12692 #[allow(clippy::too_many_arguments)]
12696 pub fn gemma4_decode_step_dc_slotted(
12697 &self,
12698 e: &Engine,
12699 token_d: &CudaSlice<u32>,
12700 pos_d: &mut CudaSlice<i32>,
12701 embd_gpu: &CudaSlice<u8>,
12702 embd_qt: i32,
12703 embd_rb: usize,
12704 cache: &mut Cache,
12705 n_vocab: usize,
12706 cap_bucket_max: Option<(usize, usize)>,
12707 sl: &mut G4DcSlots,
12708 tok_out: &mut CudaSlice<u32>,
12709 ring: Option<(&mut CudaSlice<u32>, usize)>,
12710 ) -> Result<(), Box<dyn std::error::Error>> {
12711 let n_embd = self.cfg.n_embd as usize;
12712 let eps = self.cfg.rms_eps;
12713 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
12714 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
12715 let n_layers = self.layers.len();
12716 let mut has_carry = false;
12717 for il in 0..n_layers {
12718 if !has_carry {
12719 e.rms_norm_q8_1_into(
12720 &sl.x,
12721 self.layers[il].attn_norm.float_data(),
12722 n_embd,
12723 1,
12724 eps,
12725 &mut sl.hq,
12726 &mut sl.hd_,
12727 )?;
12728 }
12729 has_carry = true;
12730 let layer = &self.layers[il];
12731 let Mixer::Full(fa) = &layer.mixer else {
12732 panic!("gemma4 layer {il} not full-attn")
12733 };
12734 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
12735 if !Engine::g4_pnfold_on() {
12738 e.rms_norm(
12739 &sl.o,
12740 layer.post_attn_norm.float_data(),
12741 &mut sl.cur,
12742 n_embd,
12743 1,
12744 eps,
12745 )?;
12746 }
12747 let next_norm = if il + 1 < n_layers {
12748 Some(self.layers[il + 1].attn_norm.float_data())
12749 } else {
12750 None
12751 };
12752 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
12753 std::mem::swap(&mut sl.x, &mut sl.xn);
12754 }
12755 e.rms_norm(
12756 &sl.x,
12757 self.output_norm.float_data(),
12758 &mut sl.hn,
12759 n_embd,
12760 1,
12761 eps,
12762 )?;
12763 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
12764 {
12766 let (zq, zd) = (&sl.zq, &sl.zd);
12767 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
12768 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
12769 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
12770 }
12771 self.gemma4_suppress(e, &mut sl.logits, 1)?;
12772 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
12773 if let Some((ring, base)) = ring {
12774 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
12778 }
12779 e.inc_seqlen(pos_d)?;
12780 if cap_bucket_max.is_none() {
12781 cache.pos += 1;
12782 }
12783 Ok(())
12784 }
12785
12786 #[allow(clippy::too_many_arguments)]
12788 fn gemma4_decode_attn_dc_slotted(
12789 &self,
12790 e: &Engine,
12791 fa: &crate::hybrid::FullAttnLayer,
12792 il: usize,
12793 pos_d: &CudaSlice<i32>,
12794 cache: &mut Cache,
12795 cap_bucket_max: Option<(usize, usize)>,
12796 sl: &mut G4DcSlots,
12797 ) -> Result<(), Box<dyn std::error::Error>> {
12798 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12799 let eps = self.cfg.rms_eps;
12800 let aux = self.gemma4_aux.as_ref().unwrap();
12801 let ones = aux.ones(e);
12802 #[cfg(debug_assertions)]
12803 crate::debug_assert_tensor_stream_device(
12804 ones,
12805 &e.stream(),
12806 "gemma4_decode_attn_dc_slotted.ones",
12807 );
12808 {
12809 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
12810 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
12811 if swa {
12812 if !e.matmul_q4_fused3_into(
12813 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
12814 )? {
12815 if e.matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12819 {
12820 self.g4_matvec_m1_into(e, &fa.wv, hq, hdq, &mut sl.v0)?;
12821 } else {
12822 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
12823 }
12824 }
12825 } else {
12826 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12827 && !e
12828 .matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12829 {
12830 return Err("slotted step: fused2 unavailable".into());
12831 }
12832 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
12833 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
12834 }
12835 }
12836 let ff = if swa {
12839 None
12840 } else {
12841 Some(
12842 aux.rope_freqs(e)
12843 .expect("gemma4 global rope needs rope_freqs.weight"),
12844 )
12845 };
12846 #[cfg(debug_assertions)]
12847 if let Some(ff) = ff {
12848 crate::debug_assert_tensor_stream_device(
12849 ff,
12850 &e.stream(),
12851 "gemma4_decode_attn_dc_slotted.rope_freqs",
12852 );
12853 }
12854 let kvl = cache.kv[il].as_mut().unwrap();
12855 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12856 if crate::Engine::qkv_append_on() {
12857 e.rms_norm_qkv_rope_append_dc(
12859 &sl.q0,
12860 &sl.k0,
12861 &sl.v0,
12862 fa.q_norm.float_data(),
12863 fa.k_norm.float_data(),
12864 ones,
12865 &mut sl.q,
12866 &mut sl.k,
12867 &mut sl.v,
12868 hd,
12869 self.gemma4_rope_dims(il),
12870 nh,
12871 nkv,
12872 pos_d,
12873 nh,
12874 nkv,
12875 base,
12876 1.0,
12877 ff,
12878 eps,
12879 &mut kvl.k,
12880 &mut kvl.v,
12881 &kvl.len_d,
12882 kvl.k_tok_bytes,
12883 kvl.v_tok_bytes,
12884 kv_fp8,
12885 )?;
12886 } else {
12887 e.rms_norm_qkv_rope(
12888 &sl.q0,
12889 &sl.k0,
12890 &sl.v0,
12891 fa.q_norm.float_data(),
12892 fa.k_norm.float_data(),
12893 ones,
12894 &mut sl.q,
12895 &mut sl.k,
12896 &mut sl.v,
12897 hd,
12898 self.gemma4_rope_dims(il),
12899 nh,
12900 nkv,
12901 pos_d,
12902 nh,
12903 nkv,
12904 base,
12905 1.0,
12906 ff,
12907 eps,
12908 )?;
12909 e.append_kv_quantized_dc(
12910 &sl.k,
12911 &sl.v,
12912 &mut kvl.k,
12913 &mut kvl.v,
12914 &kvl.len_d,
12915 kvl.kv_dim_k,
12916 kvl.kv_dim_v,
12917 kvl.k_tok_bytes,
12918 kvl.v_tok_bytes,
12919 kv_fp8,
12920 )?;
12921 }
12922 e.inc_seqlen(&mut kvl.len_d)?;
12923 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
12924 let k_view = e.view_u8(&kvl.k, kvl.k.len());
12925 let v_view = e.view_u8(&kvl.v, kvl.v.len());
12926 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
12927 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12928 let mut fa_q8 = false;
12932 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
12933 e.fa_decode_rows(
12934 &sl.q,
12935 &k_view,
12936 &v_view,
12937 &mut sl.attn,
12938 hd,
12939 nh,
12940 nkv,
12941 b_glob - 1,
12942 1,
12943 scale,
12944 kvl.k_tok_bytes,
12945 kvl.v_tok_bytes,
12946 Some((&kvl.len_d, -1)),
12947 false,
12948 false,
12949 Some((&mut sl.zq, &mut sl.zd)),
12950 )?;
12951 fa_q8 = true;
12952 } else if swa && b_swa > win && hd == 256 && rows_on {
12953 e.fa_decode_rows_w(
12954 &sl.q,
12955 &k_view,
12956 &v_view,
12957 &mut sl.attn,
12958 hd,
12959 nh,
12960 nkv,
12961 &kvl.len_d,
12962 -1,
12963 1,
12964 scale,
12965 win,
12966 kvl.k_tok_bytes,
12967 kvl.v_tok_bytes,
12968 Some((&mut sl.zq, &mut sl.zd)),
12969 )?;
12970 fa_q8 = true;
12971 } else {
12972 let b = if swa { b_swa } else { b_glob };
12973 e.fa_decode_dc(
12974 &sl.q,
12975 &k_view,
12976 &v_view,
12977 &mut sl.attn,
12978 hd,
12979 nh,
12980 nkv,
12981 &kvl.len_d,
12982 b,
12983 scale,
12984 kvl.k_tok_bytes,
12985 kvl.v_tok_bytes,
12986 swa && crate::Engine::wkv_on(),
12987 )?;
12988 }
12989 if !fa_q8 {
12990 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
12991 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
12992 }
12993 {
12994 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
12995 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
12996 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
12997 }
12998 Ok(())
12999 }
13000
13001 fn gemma4_layer_tail_slotted(
13004 &self,
13005 e: &Engine,
13006 layer: &crate::hybrid::HybridLayer,
13007 next_norm: Option<&CudaSlice<f32>>,
13008 sl: &mut G4DcSlots,
13009 ) -> Result<(), Box<dyn std::error::Error>> {
13010 let n_embd = self.cfg.n_embd as usize;
13011 let eps = self.cfg.rms_eps;
13012 let bits = layer.gemma4.as_ref().unwrap();
13013 let crate::hybrid::Ffn::Dense {
13014 ffn_gate,
13015 ffn_up,
13016 ffn_down,
13017 } = &layer.ffn
13018 else {
13019 return Err("slotted tail: dense ffn only".into());
13020 };
13021 let pnfold = Engine::g4_pnfold_on();
13022 if pnfold {
13023 let or = unsafe { &*(&sl.o as *const CudaSlice<f32>) };
13026 let xr = unsafe { &*(&sl.x as *const CudaSlice<f32>) };
13027 e.rms_pre_add_rms_norm_q8z_into(
13028 or,
13029 layer.post_attn_norm.float_data(),
13030 xr,
13031 bits.ffn_norm.float_data(),
13032 &mut sl.attn_out,
13033 &mut sl.zsh,
13034 n_embd,
13035 1,
13036 eps,
13037 &mut sl.zq,
13038 &mut sl.zd,
13039 )?;
13040 } else {
13041 e.add_rms_norm(
13042 &sl.cur,
13043 &sl.x,
13044 bits.ffn_norm.float_data(),
13045 &mut sl.attn_out,
13046 &mut sl.zsh,
13047 n_embd,
13048 1,
13049 eps,
13050 )?;
13051 }
13052 let n_ff = ffn_gate.out_features();
13053 if !pnfold {
13054 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
13055 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13056 }
13057 {
13058 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13059 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13060 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)?
13061 && !e.matmul_nvfp4_fused2_into(
13062 ffn_gate,
13063 ffn_up,
13064 zq,
13065 zd,
13066 &mut sl.gate,
13067 &mut sl.up,
13068 )?
13069 {
13070 return Err("slotted tail: ffn fused2 unavailable".into());
13071 }
13072 }
13073 debug_assert!(e.uses_q8_1_fast(ffn_down));
13074 {
13075 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
13076 let upv = e.view(upr, n_ff);
13077 let up_all = upv.slice(0..n_ff);
13078 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
13079 e.gelu_tanh_mul_q8_1_into(
13080 gr,
13081 &up_all,
13082 &mut sl.act,
13083 n_ff,
13084 1,
13085 &mut sl.actq,
13086 &mut sl.actd,
13087 )?;
13088 }
13089 {
13090 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
13091 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
13092 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
13093 }
13094 if pnfold {
13095 if let Some(w) = next_norm {
13098 let f0r = unsafe { &*(&sl.f0 as *const CudaSlice<f32>) };
13099 let aor = unsafe { &*(&sl.attn_out as *const CudaSlice<f32>) };
13100 e.rms_pre_add_scale_rms_norm_q8_1_into(
13101 f0r,
13102 bits.post_ffw_norm.float_data(),
13103 aor,
13104 bits.layer_scale,
13105 w,
13106 &mut sl.xn,
13107 n_embd,
13108 1,
13109 eps,
13110 &mut sl.hq,
13111 &mut sl.hd_,
13112 )?;
13113 return Ok(());
13114 }
13115 }
13116 e.rms_norm(
13117 &sl.f0,
13118 bits.post_ffw_norm.float_data(),
13119 &mut sl.sn,
13120 n_embd,
13121 1,
13122 eps,
13123 )?;
13124 match next_norm {
13125 Some(w) => {
13126 e.add_scale_rms_norm_q8_1_into(
13127 &sl.sn,
13128 &sl.attn_out,
13129 bits.layer_scale,
13130 w,
13131 &mut sl.xn,
13132 n_embd,
13133 1,
13134 eps,
13135 &mut sl.hq,
13136 &mut sl.hd_,
13137 )?;
13138 }
13139 None => {
13140 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
13141 }
13142 }
13143 Ok(())
13144 }
13145
13146 #[allow(clippy::too_many_arguments)]
13148 fn gemma4_decode_attn_dc(
13149 &self,
13150 e: &Engine,
13151 fa: &crate::hybrid::FullAttnLayer,
13152 il: usize,
13153 hq: &CudaSlice<i8>,
13154 hdq: &CudaSlice<f32>,
13155 pos_d: &CudaSlice<i32>,
13156 cache: &mut Cache,
13157 cap_bucket_max: Option<(usize, usize)>,
13158 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13159 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13160 let eps = self.cfg.rms_eps;
13161 let aux = self.gemma4_aux.as_ref().unwrap();
13162 let ones = aux.ones(e);
13163 #[cfg(debug_assertions)]
13164 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
13165 let (q0, k0, v0) = if swa {
13166 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
13167 Some(t3) => t3,
13168 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13170 Some((q0, k0)) => {
13171 let h0 = e.zeros(0)?;
13172 let v0 = e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?;
13173 (q0, k0, v0)
13174 }
13175 None => {
13176 let h0 = e.zeros(0)?;
13177 (
13178 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13179 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13180 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
13181 )
13182 }
13183 },
13184 }
13185 } else {
13186 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
13187 Some(p) => p,
13188 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13189 Some(p) => p,
13190 None => {
13191 let h0 = e.zeros(0)?;
13192 (
13193 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13194 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13195 )
13196 }
13197 },
13198 };
13199 let v0 = e.clone_dtod(&k0)?;
13200 (q0, k0, v0)
13201 };
13202 let mut q = e.uninit(nh * hd)?;
13203 let mut k = e.uninit(nkv * hd)?;
13204 let mut v = e.uninit(nkv * hd)?;
13205 let ff = if swa {
13207 None
13208 } else {
13209 Some(
13210 aux.rope_freqs(e)
13211 .expect("gemma4 global rope needs rope_freqs.weight"),
13212 )
13213 };
13214 #[cfg(debug_assertions)]
13215 if let Some(ff) = ff {
13216 crate::debug_assert_tensor_stream_device(
13217 ff,
13218 &e.stream(),
13219 "gemma4_decode_attn_dc.rope_freqs",
13220 );
13221 }
13222 let kvl = cache.kv[il].as_mut().unwrap();
13223 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13224 if crate::Engine::qkv_append_on() {
13225 e.rms_norm_qkv_rope_append_dc(
13227 &q0,
13228 &k0,
13229 &v0,
13230 fa.q_norm.float_data(),
13231 fa.k_norm.float_data(),
13232 ones,
13233 &mut q,
13234 &mut k,
13235 &mut v,
13236 hd,
13237 self.gemma4_rope_dims(il),
13238 nh,
13239 nkv,
13240 pos_d,
13241 nh,
13242 nkv,
13243 base,
13244 1.0,
13245 ff,
13246 eps,
13247 &mut kvl.k,
13248 &mut kvl.v,
13249 &kvl.len_d,
13250 kvl.k_tok_bytes,
13251 kvl.v_tok_bytes,
13252 kv_fp8,
13253 )?;
13254 } else {
13255 e.rms_norm_qkv_rope(
13256 &q0,
13257 &k0,
13258 &v0,
13259 fa.q_norm.float_data(),
13260 fa.k_norm.float_data(),
13261 ones,
13262 &mut q,
13263 &mut k,
13264 &mut v,
13265 hd,
13266 self.gemma4_rope_dims(il),
13267 nh,
13268 nkv,
13269 pos_d,
13270 nh,
13271 nkv,
13272 base,
13273 1.0,
13274 ff,
13275 eps,
13276 )?;
13277 e.append_kv_quantized_dc(
13278 &k,
13279 &v,
13280 &mut kvl.k,
13281 &mut kvl.v,
13282 &kvl.len_d,
13283 kvl.kv_dim_k,
13284 kvl.kv_dim_v,
13285 kvl.k_tok_bytes,
13286 kvl.v_tok_bytes,
13287 kv_fp8,
13288 )?;
13289 }
13290 e.inc_seqlen(&mut kvl.len_d)?;
13291 let mut attn = e.uninit(nh * hd)?;
13292 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13295 match cap_bucket_max {
13300 None => {
13301 kvl.len += 1;
13305 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13306 if !swa
13307 && hd == 512
13308 && kvl.len >= crate::fa512_min_tkv()
13309 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13310 {
13311 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13314 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13315 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13316 e.fa_decode_rows(
13317 &q,
13318 &kp,
13319 &vp,
13320 &mut attn,
13321 hd,
13322 nh,
13323 nkv,
13324 kvl.len - 1,
13325 1,
13326 scale,
13327 kvl.k_tok_bytes,
13328 kvl.v_tok_bytes,
13329 Some((&kvl.len_d, -1)),
13330 false,
13331 false,
13332 Some((&mut aq8, &mut ad8)),
13333 )?;
13334 fa_q8 = Some((aq8, ad8));
13335 } else if swa
13336 && kvl.len > win
13337 && hd == 256
13338 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13339 {
13340 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13342 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13343 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13344 e.fa_decode_rows_w(
13345 &q,
13346 &kp,
13347 &vp,
13348 &mut attn,
13349 hd,
13350 nh,
13351 nkv,
13352 &kvl.len_d,
13353 -1,
13354 1,
13355 scale,
13356 win,
13357 kvl.k_tok_bytes,
13358 kvl.v_tok_bytes,
13359 Some((&mut aq8, &mut ad8)),
13360 )?;
13361 fa_q8 = Some((aq8, ad8));
13362 } else {
13363 let (off_tok, t_kv) = if swa && kvl.len > win {
13364 (kvl.len - win, win)
13365 } else {
13366 (0, kvl.len)
13367 };
13368 let k_view = e.view_u8_range(
13369 &kvl.k,
13370 off_tok * kvl.k_tok_bytes,
13371 (off_tok + t_kv) * kvl.k_tok_bytes,
13372 );
13373 let v_view = e.view_u8_range(
13374 &kvl.v,
13375 off_tok * kvl.v_tok_bytes,
13376 (off_tok + t_kv) * kvl.v_tok_bytes,
13377 );
13378 e.fa_decode_kvmod(
13379 &q,
13380 &k_view,
13381 &v_view,
13382 &mut attn,
13383 hd,
13384 nh,
13385 nkv,
13386 t_kv,
13387 scale,
13388 kvl.k_tok_bytes,
13389 kvl.v_tok_bytes,
13390 swa && crate::Engine::wkv_on(),
13391 )?;
13392 }
13393 }
13394 Some((b_swa, b_glob)) => {
13395 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13401 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13402 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13403 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13404 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13405 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13406 e.fa_decode_rows(
13407 &q,
13408 &k_view,
13409 &v_view,
13410 &mut attn,
13411 hd,
13412 nh,
13413 nkv,
13414 b_glob - 1,
13415 1,
13416 scale,
13417 kvl.k_tok_bytes,
13418 kvl.v_tok_bytes,
13419 Some((&kvl.len_d, -1)),
13420 false,
13421 false,
13422 Some((&mut aq8, &mut ad8)),
13423 )?;
13424 fa_q8 = Some((aq8, ad8));
13425 } else if swa && b_swa > win && hd == 256 && rows_on {
13426 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13427 e.fa_decode_rows_w(
13428 &q,
13429 &k_view,
13430 &v_view,
13431 &mut attn,
13432 hd,
13433 nh,
13434 nkv,
13435 &kvl.len_d,
13436 -1,
13437 1,
13438 scale,
13439 win,
13440 kvl.k_tok_bytes,
13441 kvl.v_tok_bytes,
13442 Some((&mut aq8, &mut ad8)),
13443 )?;
13444 fa_q8 = Some((aq8, ad8));
13445 } else {
13446 let b = if swa { b_swa } else { b_glob };
13447 e.fa_decode_dc(
13448 &q,
13449 &k_view,
13450 &v_view,
13451 &mut attn,
13452 hd,
13453 nh,
13454 nkv,
13455 &kvl.len_d,
13456 b,
13457 scale,
13458 kvl.k_tok_bytes,
13459 kvl.v_tok_bytes,
13460 swa && crate::Engine::wkv_on(),
13461 )?;
13462 }
13463 }
13464 }
13465 if let Some((aq8, ad8)) = fa_q8 {
13468 let mut y = e.uninit(fa.wo.out_features())?;
13469 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
13470 return Ok(y);
13471 }
13472 Ok(e.matmul(&fa.wo, &attn, 1)?)
13473 }
13474
13475 pub fn gemma4_generate_graph(
13480 &self,
13481 e: &Engine,
13482 prompt_pos: usize,
13483 first_token: u32,
13484 cache: &mut Cache,
13485 max_new: usize,
13486 eos: &[u32],
13487 mut on_token: impl FnMut(u32) -> bool,
13488 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
13489 if self.is_gemma4_e4b() {
13490 return Err(
13491 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
13492 .into(),
13493 );
13494 }
13495 use crate::decode::StopReason;
13496 let n_vocab = self.output.out_features();
13497 let n_embd = self.cfg.n_embd as usize;
13498 let embd_gpu = self
13499 .embd_gpu
13500 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13501 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13502 for kvl in cache.kv.iter_mut().flatten() {
13503 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
13504 }
13505 let mut token_d = e.stream().clone_htod(&[first_token])?;
13506 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
13507 let g4 = self.cfg.gemma4.as_ref().unwrap();
13508 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
13509 let nkv_s = g4
13511 .head_count_kv
13512 .iter()
13513 .zip(g4.swa_pattern.iter())
13514 .find(|p| *p.1)
13515 .map(|p| *p.0 as usize)
13516 .unwrap_or(8);
13517 let nkv_g = g4
13518 .head_count_kv
13519 .iter()
13520 .zip(g4.swa_pattern.iter())
13521 .find(|p| !*p.1)
13522 .map(|p| *p.0 as usize)
13523 .unwrap_or(2);
13524 let mut graphs: std::collections::HashMap<
13525 ((bool, usize), (bool, usize), bool, bool),
13526 (
13527 cudarc::driver::CudaGraph,
13528 Vec<Box<dyn std::any::Any + Send>>,
13529 ),
13530 > = Default::default();
13531 let mut slots = self.g4_dc_slots(e)?;
13534 const RING: usize = 64;
13537 const DRAIN: usize = 1;
13543 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
13544 let ring_base = prompt_pos;
13545 let mut out = Vec::with_capacity(max_new);
13546 let mut reason = StopReason::MaxNew;
13547 let mut next = first_token;
13548 let mut captures = 0usize;
13549 for _ in 0..max_new {
13550 out.push(next);
13551 if eos.contains(&next) {
13552 reason = StopReason::Eos;
13553 break;
13554 }
13555 if !on_token(next) {
13556 reason = StopReason::Callback;
13557 break;
13558 }
13559 let t_kv = cache.pos + 1;
13560 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13568 let f512 = crate::fa512_min_tkv();
13569 let key_s = if t_kv > win {
13570 (true, usize::MAX)
13571 } else {
13572 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
13573 };
13574 let (key_g, rung_end) = if t_kv >= f512 {
13575 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
13578 ((true, end), end)
13579 } else {
13580 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
13581 };
13582 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
13583 if !graphs.contains_key(&key) {
13584 let bucket_max = (t_kv, rung_end);
13585 let snap = cache.snapshot(e)?;
13587 let pos_save = e.dtoh_i32_one(&pos_d)?;
13588 let len_save: Vec<Option<i32>> = cache
13589 .kv
13590 .iter()
13591 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
13592 .collect();
13593 let tok_save = e.dtoh_u32_one(&token_d)?;
13594 let graph = {
13599 let tok_ref = &mut token_d;
13600 let pos_ref = &mut pos_d;
13601 let cache_ref = &mut *cache;
13602 let slots_ref = &mut slots;
13603 let ring_ref = &mut ring;
13604 e.capture_graph_retained_flags(
13605 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
13606 |e| {
13607 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
13609 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
13610 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
13611 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
13612 cache_ref, n_vocab, Some(bucket_max),
13613 sl, tok_ref, Some((rg, ring_base)))
13614 })?
13615 };
13616 cache.rollback(e, &snap, 0)?;
13617 e.set_i32_one(&mut pos_d, pos_save)?;
13618 for (il, ls) in len_save.iter().enumerate() {
13619 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
13620 e.set_i32_one(&mut kvl.len_d, *v)?;
13621 }
13622 }
13623 e.set_u32_one(&mut token_d, tok_save)?;
13624 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
13625 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
13626 eprintln!("[graph-census] {c:?}");
13627 }
13628 }
13629 graphs.insert(key, graph);
13630 captures += 1;
13631 }
13632 let mut chunk = 1usize;
13637 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
13638 .ok()
13639 .and_then(|v| v.parse().ok())
13640 .unwrap_or(DRAIN);
13641 while chunk < drain_cap && out.len() + chunk < max_new {
13642 let t_next = cache.pos + 1 + chunk;
13643 let key_s2 = if t_next > win {
13644 (true, usize::MAX)
13645 } else {
13646 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
13647 };
13648 let key_g2 = if t_next >= f512 {
13649 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
13650 } else {
13651 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
13652 };
13653 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
13654 break;
13655 }
13656 chunk += 1;
13657 }
13658 let g = &graphs.get(&key).unwrap().0;
13659 for _ in 0..chunk {
13660 g.launch()?;
13661 }
13662 e.stream().synchronize()?;
13663 let ringh = e.dtoh_u32(&ring)?;
13664 for j in 0..chunk {
13665 let pos_j = cache.pos + j;
13666 let tok_j = ringh[(pos_j - ring_base) % RING];
13667 cache.pos += 0; if j + 1 == chunk {
13669 next = tok_j;
13670 } else {
13671 out.push(tok_j);
13672 if eos.contains(&tok_j) || !on_token(tok_j) {
13673 reason = if eos.contains(&tok_j) {
13674 StopReason::Eos
13675 } else {
13676 StopReason::Callback
13677 };
13678 let keep = cache.pos + j + 1;
13680 e.set_i32_one(&mut pos_d, keep as i32)?;
13681 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13682 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
13683 kvl.len = keep;
13684 }
13685 cache.pos = keep;
13686 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13687 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13688 }
13689 return Ok((out, reason));
13690 }
13691 }
13692 }
13693 cache.pos += chunk;
13694 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13695 kvl.len += chunk;
13696 }
13697 }
13698 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13699 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13700 }
13701 Ok((out, reason))
13702 }
13703
13704 pub(crate) fn gemma4_decode_step_t(
13710 &self,
13711 e: &Engine,
13712 tokens: &[u32],
13713 pos0: usize,
13714 cache: &mut Cache,
13715 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13716 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
13717 }
13718
13719 pub(crate) fn gemma4_decode_step_t_am(
13723 &self,
13724 e: &Engine,
13725 tokens: &[u32],
13726 pos0: usize,
13727 cache: &mut Cache,
13728 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13729 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13730 let t = tokens.len();
13731 let n_vocab = self.output.out_features();
13732 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
13733 for i in 0..t {
13734 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
13735 }
13736 Ok((e.dtoh_u32(&toks)?, hn))
13737 }
13738
13739 pub(crate) fn gemma4_decode_step_t_am_dev(
13742 &self,
13743 e: &Engine,
13744 tok_d: &CudaSlice<u32>,
13745 t: usize,
13746 pos0: usize,
13747 cache: &mut Cache,
13748 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13749 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
13750 let n_vocab = self.output.out_features();
13751 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13752 for i in 0..t {
13753 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13754 }
13755 Ok((vam, hn))
13756 }
13757
13758 pub(crate) fn gemma4_decode_step_t_h(
13761 &self,
13762 e: &Engine,
13763 tokens: &[u32],
13764 pos0: usize,
13765 cache: &mut Cache,
13766 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13767 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13768 let t = tokens.len();
13769 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
13770 e.softcap(&mut ld, cap, t * self.output.out_features())?;
13771 Ok((e.dtoh(&ld)?, hn))
13772 }
13773
13774 pub(crate) fn verify_stream_scratch(
13777 &self,
13778 e: &Engine,
13779 cap: usize,
13780 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
13781 Ok(VerifyStreamScratch {
13782 pos_d: e.htod_i32(&vec![0i32; cap])?,
13783 row_ctrs: (0..cap)
13784 .map(|_| e.htod_i32(&[0]))
13785 .collect::<Result<_, _>>()?,
13786 })
13787 }
13788
13789 pub(crate) fn gemma4_verify_t_am_stream(
13797 &self,
13798 e: &Engine,
13799 tok_d: &CudaSlice<u32>,
13800 t: usize,
13801 ctr: &CudaSlice<i32>,
13802 hint: usize,
13803 cache: &mut Cache,
13804 scr: &mut VerifyStreamScratch,
13805 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13806 let n_embd = self.cfg.n_embd as usize;
13807 let eps = self.cfg.rms_eps;
13808 assert!(t <= scr.row_ctrs.len() && t <= 64);
13809 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
13810 for i in 0..t {
13811 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
13812 }
13813 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
13814 let embd_gpu = self
13815 .embd_gpu
13816 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13817 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13818 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
13819 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13820 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13821 let n_layers = self.layers.len();
13822 for (il, layer) in self.layers.iter().enumerate() {
13823 let (hq, hdq) = match h_carry.take() {
13824 Some(p) => p,
13825 None => {
13826 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
13827 }
13828 };
13829 let Mixer::Full(fa) = &layer.mixer else {
13830 panic!("gemma4 layer {il} not full-attn")
13831 };
13832 let o = self
13833 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
13834 let next_norm = if il + 1 < n_layers {
13835 Some(self.layers[il + 1].attn_norm.float_data())
13836 } else {
13837 None
13838 };
13839 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
13840 x = xn;
13841 h_carry = hn;
13842 self.dflash_tap(e, cache, il, &x, t)?;
13843 }
13844 let mut hn = e.uninit(t * n_embd)?;
13845 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13846 let ld = e.matmul(&self.output, &hn, t)?;
13847 let n_vocab = self.output.out_features();
13848 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13849 for i in 0..t {
13850 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13851 }
13852 Ok((vam, hn))
13853 }
13854
13855 pub(crate) fn dflash_tap(
13862 &self,
13863 e: &Engine,
13864 cache: &mut Cache,
13865 il: usize,
13866 x: &CudaSlice<f32>,
13867 t: usize,
13868 ) -> Result<(), Box<dyn std::error::Error>> {
13869 let Some(taps) = cache.dflash_taps.as_mut() else {
13870 return Ok(());
13871 };
13872 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
13873 return Ok(());
13874 };
13875 let h = taps.hidden;
13876 let n_taps = taps.layer_ids.len();
13877 let base = taps.base;
13878 debug_assert!(
13879 base + t <= taps.t,
13880 "tap window {base}+{t} exceeds sink {}",
13881 taps.t
13882 );
13883 let xv = e.view(x, t * h);
13884 for r in 0..t {
13885 let row = xv.slice(r * h..(r + 1) * h);
13886 e.copy_view_into(&mut taps.buf, (base + r) * n_taps * h + slot * h, &row, h)?;
13887 }
13888 Ok(())
13889 }
13890
13891 fn gemma4_verify_trunk(
13892 &self,
13893 e: &Engine,
13894 tokens: &[u32],
13895 pos0: usize,
13896 cache: &mut Cache,
13897 tok_dev: Option<&CudaSlice<u32>>,
13898 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13899 let n_embd = self.cfg.n_embd as usize;
13900 let eps = self.cfg.rms_eps;
13901 let t = tokens.len();
13902 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
13903 let pos_d = e.htod_i32(&pos)?;
13904 let mut x = match tok_dev {
13905 Some(td) => {
13906 let embd_gpu = self
13907 .embd_gpu
13908 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13909 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13910 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
13911 }
13912 None => e.htod(&self.embd.gather(n_embd, tokens))?,
13913 };
13914 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13915 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13916 let n_layers = self.layers.len();
13917 for (il, layer) in self.layers.iter().enumerate() {
13918 let (hq, hdq) = match h_carry.take() {
13919 Some(p) => p,
13920 None => {
13921 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
13922 }
13923 };
13924 let Mixer::Full(fa) = &layer.mixer else {
13925 panic!("gemma4 layer {il} not full-attn")
13926 };
13927 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
13928 let next_norm = if il + 1 < n_layers {
13929 Some(self.layers[il + 1].attn_norm.float_data())
13930 } else {
13931 None
13932 };
13933 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
13934 x = xn;
13935 h_carry = hn;
13936 self.dflash_tap(e, cache, il, &x, t)?;
13937 }
13938 let mut hn = e.uninit(t * n_embd)?;
13939 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13940 let mut ld = e.matmul(&self.output, &hn, t)?;
13941 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
13943 Ok((ld, hn))
13944 }
13945
13946 #[allow(clippy::too_many_arguments)]
13954 fn gemma4_verify_attn_stream(
13955 &self,
13956 e: &Engine,
13957 fa: &crate::hybrid::FullAttnLayer,
13958 il: usize,
13959 hq: &CudaSlice<i8>,
13960 hdq: &CudaSlice<f32>,
13961 pos_d: &CudaSlice<i32>,
13962 t: usize,
13963 cache: &mut Cache,
13964 hint: usize,
13965 row_ctrs: &[CudaSlice<i32>],
13966 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13967 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13968 let eps = self.cfg.rms_eps;
13969 let aux = self.gemma4_aux.as_ref().unwrap();
13970 let ones = aux.ones(e);
13971 #[cfg(debug_assertions)]
13972 crate::debug_assert_tensor_stream_device(
13973 ones,
13974 &e.stream(),
13975 "gemma4_verify_attn_stream.ones",
13976 );
13977 let h0 = e.zeros(0)?;
13978 let h = &h0;
13979 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13982 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
13983 let fused_qkv = if f2b {
13984 if swa {
13985 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
13986 .map(|(a, b, c)| (a, b, Some(c)))
13987 } else {
13988 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
13989 .map(|(a, b)| (a, b, None))
13990 }
13991 } else {
13992 None
13993 };
13994 let (q0, k0, v0) = match fused_qkv {
13995 Some((a, b, cv)) => {
13996 let v = match cv {
13997 Some(c) => c,
13998 None => e.clone_dtod(&b)?,
13999 };
14000 (a, b, v)
14001 }
14002 None => {
14003 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14004 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14005 let v0 = if swa {
14006 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14007 } else {
14008 e.clone_dtod(&k0)?
14009 };
14010 (q0, k0, v0)
14011 }
14012 };
14013 let mut q = e.uninit(t * nh * hd)?;
14014 let mut k = e.uninit(t * nkv * hd)?;
14015 let mut v = e.uninit(t * nkv * hd)?;
14016 let ff = if swa {
14019 None
14020 } else {
14021 Some(
14022 aux.rope_freqs(e)
14023 .expect("gemma4 global rope needs rope_freqs.weight"),
14024 )
14025 };
14026 #[cfg(debug_assertions)]
14027 if let Some(ff) = ff {
14028 crate::debug_assert_tensor_stream_device(
14029 ff,
14030 &e.stream(),
14031 "gemma4_verify_attn_stream.rope_freqs",
14032 );
14033 }
14034 e.rms_norm_qkv_rope(
14035 &q0,
14036 &k0,
14037 &v0,
14038 fa.q_norm.float_data(),
14039 fa.k_norm.float_data(),
14040 ones,
14041 &mut q,
14042 &mut k,
14043 &mut v,
14044 hd,
14045 self.gemma4_rope_dims(il),
14046 nh * t,
14047 nkv * t,
14048 pos_d,
14049 nh,
14050 nkv,
14051 base,
14052 1.0,
14053 ff,
14054 eps,
14055 )?;
14056 let kvl = cache.kv[il].as_mut().unwrap();
14057 e.append_kv_quantized_rows_dc(
14059 &k,
14060 &v,
14061 &mut kvl.k,
14062 &mut kvl.v,
14063 &kvl.len_d,
14064 t,
14065 kvl.kv_dim_k,
14066 kvl.kv_dim_v,
14067 kvl.k_tok_bytes,
14068 kvl.v_tok_bytes,
14069 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14070 )?;
14071 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14074 let mut attn = e.uninit(t * nh * hd)?;
14075 let k_view = e.view_u8(&kvl.k, kvl.k.len());
14076 let v_view = e.view_u8(&kvl.v, kvl.v.len());
14077 if swa && hint + 1 >= win {
14080 e.fa_decode_rows_w(
14083 &q,
14084 &k_view,
14085 &v_view,
14086 &mut attn,
14087 hd,
14088 nh,
14089 nkv,
14090 &kvl.len_d,
14091 0,
14092 t,
14093 scale,
14094 win,
14095 kvl.k_tok_bytes,
14096 kvl.v_tok_bytes,
14097 None,
14098 )?;
14099 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
14100 let bucket = (hint + t + 2)
14113 .next_power_of_two()
14114 .min(crate::fa512_min_tkv().saturating_sub(1));
14115 let qv = e.view(&q, t * nh * hd);
14116 for i in 0..t {
14117 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
14118 let mut q_one = e.uninit(nh * hd)?;
14119 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14120 let mut a_one = e.uninit(nh * hd)?;
14121 e.fa_decode_dc(
14122 &q_one,
14123 &k_view,
14124 &v_view,
14125 &mut a_one,
14126 hd,
14127 nh,
14128 nkv,
14129 &row_ctrs[i],
14130 bucket,
14131 scale,
14132 kvl.k_tok_bytes,
14133 kvl.v_tok_bytes,
14134 false,
14135 )?;
14136 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14137 }
14138 } else if hd == 512 {
14139 e.fa_decode_rows(
14142 &q,
14143 &k_view,
14144 &v_view,
14145 &mut attn,
14146 hd,
14147 nh,
14148 nkv,
14149 hint,
14150 t,
14151 scale,
14152 kvl.k_tok_bytes,
14153 kvl.v_tok_bytes,
14154 Some((&kvl.len_d, 0)),
14155 false,
14156 false,
14157 None,
14158 )?;
14159 } else {
14160 e.fa_decode_rows_dc(
14162 &q,
14163 &k_view,
14164 &v_view,
14165 &mut attn,
14166 hd,
14167 nh,
14168 nkv,
14169 &kvl.len_d,
14170 hint + t,
14171 t,
14172 scale,
14173 kvl.k_tok_bytes,
14174 kvl.v_tok_bytes,
14175 0,
14176 swa && crate::Engine::wkv_on(),
14177 )?;
14178 }
14179 Ok(e.matmul(&fa.wo, &attn, t)?)
14180 }
14181
14182 fn gemma4_verify_attn(
14183 &self,
14184 e: &Engine,
14185 fa: &crate::hybrid::FullAttnLayer,
14186 il: usize,
14187 hq: &CudaSlice<i8>,
14188 hdq: &CudaSlice<f32>,
14189 pos_d: &CudaSlice<i32>,
14190 t: usize,
14191 cache: &mut Cache,
14192 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14193 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14194 let eps = self.cfg.rms_eps;
14195 let aux = self.gemma4_aux.as_ref().unwrap();
14196 let ones = aux.ones(e);
14197 #[cfg(debug_assertions)]
14198 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
14199 let n_embd = self.cfg.n_embd as usize;
14200 let _ = n_embd;
14201
14202 let h0 = e.zeros(0)?;
14203 let h = &h0;
14204 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14207 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14208 let fused_qkv = if f2b {
14209 if swa {
14210 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14211 .map(|(a, b, c)| (a, b, Some(c)))
14212 } else {
14213 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14214 .map(|(a, b)| (a, b, None))
14215 }
14216 } else {
14217 None
14218 };
14219 let (q0, k0, v0) = match fused_qkv {
14220 Some((a, b, cv)) => {
14221 let v = match cv {
14222 Some(c) => c,
14223 None => e.clone_dtod(&b)?,
14224 };
14225 (a, b, v)
14226 }
14227 None => {
14228 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14229 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14230 let v0 = if swa {
14231 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14232 } else {
14233 e.clone_dtod(&k0)?
14234 };
14235 (q0, k0, v0)
14236 }
14237 };
14238 let mut q = e.uninit(t * nh * hd)?;
14239 let mut k = e.uninit(t * nkv * hd)?;
14240 let mut v = e.uninit(t * nkv * hd)?;
14241 let ff = if swa {
14244 None
14245 } else {
14246 Some(
14247 aux.rope_freqs(e)
14248 .expect("gemma4 global rope needs rope_freqs.weight"),
14249 )
14250 };
14251 #[cfg(debug_assertions)]
14252 if let Some(ff) = ff {
14253 crate::debug_assert_tensor_stream_device(
14254 ff,
14255 &e.stream(),
14256 "gemma4_verify_attn.rope_freqs",
14257 );
14258 }
14259 e.rms_norm_qkv_rope(
14260 &q0,
14261 &k0,
14262 &v0,
14263 fa.q_norm.float_data(),
14264 fa.k_norm.float_data(),
14265 ones,
14266 &mut q,
14267 &mut k,
14268 &mut v,
14269 hd,
14270 self.gemma4_rope_dims(il),
14271 nh * t,
14272 nkv * t,
14273 pos_d,
14274 nh,
14275 nkv,
14276 base,
14277 1.0,
14278 ff,
14279 eps,
14280 )?;
14281 let kvl = cache.kv[il].as_mut().unwrap();
14282 let base_len = kvl.len;
14283 e.append_kv_quantized_rows(
14284 &k,
14285 &v,
14286 &mut kvl.k,
14287 &mut kvl.v,
14288 base_len,
14289 t,
14290 kvl.kv_dim_k,
14291 kvl.kv_dim_v,
14292 kvl.k_tok_bytes,
14293 kvl.v_tok_bytes,
14294 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14295 )?;
14296 kvl.len += t;
14297 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14298 let mut attn = e.uninit(t * nh * hd)?;
14299 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
14302 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
14305 if rows_ok && (!swa || base_len + t <= win) {
14306 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14307 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14308 if hd == 512 {
14309 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14311 e.fa_decode_rows(
14312 &q,
14313 &k_view,
14314 &v_view,
14315 &mut attn,
14316 hd,
14317 nh,
14318 nkv,
14319 base_len,
14320 t,
14321 scale,
14322 kvl.k_tok_bytes,
14323 kvl.v_tok_bytes,
14324 Some((&kvl.len_d, 0)),
14325 false,
14326 swa && crate::Engine::wkv_on(),
14327 None,
14328 )?;
14329 } else {
14330 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14334 e.fa_decode_rows_dc(
14335 &q,
14336 &k_view,
14337 &v_view,
14338 &mut attn,
14339 hd,
14340 nh,
14341 nkv,
14342 &kvl.len_d,
14343 base_len + t,
14344 t,
14345 scale,
14346 kvl.k_tok_bytes,
14347 kvl.v_tok_bytes,
14348 0,
14349 swa && crate::Engine::wkv_on(),
14350 )?;
14351 }
14352 return Ok(e.matmul(&fa.wo, &attn, t)?);
14353 }
14354 if hd == 256
14362 && swa
14363 && base_len + 1 >= win
14364 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14365 {
14366 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14367 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14368 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14369 e.fa_decode_rows_w(
14370 &q,
14371 &k_view,
14372 &v_view,
14373 &mut attn,
14374 hd,
14375 nh,
14376 nkv,
14377 &kvl.len_d,
14378 0,
14379 t,
14380 scale,
14381 win,
14382 kvl.k_tok_bytes,
14383 kvl.v_tok_bytes,
14384 None,
14385 )?;
14386 return Ok(e.matmul(&fa.wo, &attn, t)?);
14387 }
14388 for i in 0..t {
14389 let avail = base_len + i + 1;
14390 let (off_tok, t_kv) = if swa && avail > win {
14391 (avail - win, win)
14392 } else {
14393 (0, avail)
14394 };
14395 let k_view = e.view_u8_range(
14396 &kvl.k,
14397 off_tok * kvl.k_tok_bytes,
14398 (off_tok + t_kv) * kvl.k_tok_bytes,
14399 );
14400 let v_view = e.view_u8_range(
14401 &kvl.v,
14402 off_tok * kvl.v_tok_bytes,
14403 (off_tok + t_kv) * kvl.v_tok_bytes,
14404 );
14405 let qi = e.view(&q, t * nh * hd);
14406 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
14407 let mut q_one = e.uninit(nh * hd)?;
14408 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14409 let mut a_one = e.uninit(nh * hd)?;
14410 if swa
14414 && avail > win
14415 && hd == 256
14416 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14417 {
14418 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14419 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14420 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14421 e.fa_decode_rows_w(
14422 &q_one,
14423 &kp,
14424 &vp,
14425 &mut a_one,
14426 hd,
14427 nh,
14428 nkv,
14429 &kvl.len_d,
14430 0,
14431 1,
14432 scale,
14433 win,
14434 kvl.k_tok_bytes,
14435 kvl.v_tok_bytes,
14436 None,
14437 )?;
14438 } else if !swa
14439 && hd == 512
14440 && avail >= crate::fa512_min_tkv()
14441 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14442 {
14443 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14444 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14445 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14446 e.fa_decode_rows(
14447 &q_one,
14448 &kp,
14449 &vp,
14450 &mut a_one,
14451 hd,
14452 nh,
14453 nkv,
14454 avail - 1,
14455 1,
14456 scale,
14457 kvl.k_tok_bytes,
14458 kvl.v_tok_bytes,
14459 Some((&kvl.len_d, 0)),
14460 false,
14461 false,
14462 None,
14463 )?;
14464 } else {
14465 e.fa_decode_kvmod(
14466 &q_one,
14467 &k_view,
14468 &v_view,
14469 &mut a_one,
14470 hd,
14471 nh,
14472 nkv,
14473 t_kv,
14474 scale,
14475 kvl.k_tok_bytes,
14476 kvl.v_tok_bytes,
14477 swa && crate::Engine::wkv_on(),
14478 )?;
14479 }
14480 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14481 }
14482 Ok(e.matmul(&fa.wo, &attn, t)?)
14483 }
14484
14485 pub(crate) fn gemma4_decode_step_h(
14488 &self,
14489 e: &Engine,
14490 token: u32,
14491 cache: &mut Cache,
14492 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14493 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
14498 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
14499 }
14500 if crate::pp::pp_cuts(self.layers.len()).is_some() {
14501 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
14502 }
14503 let n_embd = self.cfg.n_embd as usize;
14504 let eps = self.cfg.rms_eps;
14505 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14506 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14507 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14508 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14511 let n_layers = self.layers.len();
14512 for (il, layer) in self.layers.iter().enumerate() {
14513 let (hq, hdq) = match h_carry.take() {
14514 Some(p) => p,
14515 None => {
14516 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
14517 }
14518 };
14519 let Mixer::Full(fa) = &layer.mixer else {
14520 panic!("gemma4 layer {il} not full-attn")
14521 };
14522 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
14523 let next_norm = if il + 1 < n_layers {
14524 Some(self.layers[il + 1].attn_norm.float_data())
14525 } else {
14526 None
14527 };
14528 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14529 x = xn;
14530 h_carry = hn;
14531 }
14532 let mut hn = e.uninit(n_embd)?;
14533 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14534 let h_seed = e.clone_dtod(&x)?;
14535 let mut ld = e.matmul(&self.output, &hn, 1)?;
14536 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14537 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
14539 let logits = e.dtoh(&ld)?;
14540 cache.pos += 1;
14541 Ok((logits, h_seed))
14542 }
14543
14544 fn gemma4_decode_layers(
14552 &self,
14553 e: &Engine,
14554 mut x: CudaSlice<f32>,
14555 lo: usize,
14556 hi: usize,
14557 pos_d: &CudaSlice<i32>,
14558 cache: &mut Cache,
14559 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14560 let n_embd = self.cfg.n_embd as usize;
14561 let eps = self.cfg.rms_eps;
14562 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14563 for il in lo..hi {
14564 let layer = &self.layers[il];
14565 let (hq, hdq) = match h_carry.take() {
14566 Some(p) => p,
14567 None => {
14569 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
14570 }
14571 };
14572 let Mixer::Full(fa) = &layer.mixer else {
14573 panic!("gemma4 layer {il} not full-attn")
14574 };
14575 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
14576 let next_norm = if il + 1 < hi {
14577 Some(self.layers[il + 1].attn_norm.float_data())
14578 } else {
14579 None
14580 };
14581 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14582 x = xn;
14583 h_carry = hn;
14584 }
14585 Ok(x)
14586 }
14587
14588 fn gemma4_decode_step_h_pp2(
14596 &self,
14597 e: &Engine,
14598 token: u32,
14599 cache: &mut Cache,
14600 split: usize,
14601 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14602 if crate::pp::pp2_streams_off() {
14603 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
14604 }
14605 let rt = crate::pp::Pp2Rt::get(e)?;
14606 let e0 = rt.engine(0, e);
14607 let e1 = rt.engine(1, e);
14608 let n_embd = self.cfg.n_embd as usize;
14609 let eps = self.cfg.rms_eps;
14610 let pos = cache.pos as i32;
14611
14612 let slot = {
14614 let _st0 = rt.enter(0);
14615 let pos_d = e0.htod_i32(&[pos])?;
14616 #[cfg(debug_assertions)]
14617 crate::debug_assert_tensor_stream_device(
14618 &pos_d,
14619 &e0.stream(),
14620 "gemma4_decode_step_h_pp2.stage0.pos_d",
14621 );
14622 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
14623 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14624 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
14625 rt.tx(0, &x, n_embd)?
14626 };
14627
14628 let _st1 = rt.enter(1);
14630 let pos_d = e1.htod_i32(&[pos])?;
14631 #[cfg(debug_assertions)]
14632 crate::debug_assert_tensor_stream_device(
14633 &pos_d,
14634 &e1.stream(),
14635 "gemma4_decode_step_h_pp2.stage1.pos_d",
14636 );
14637 let x = rt.rx(0, slot, n_embd)?;
14638 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
14639
14640 let mut hn = e1.uninit(n_embd)?;
14641 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14642 let h_seed = e1.clone_dtod(&x)?;
14643 let mut ld = e1.matmul(&self.output, &hn, 1)?;
14644 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14645 e1.softcap(&mut ld, cap, self.output.out_features())?;
14646 self.gemma4_suppress(e1, &mut ld, 1)?;
14647 let logits = e1.dtoh(&ld)?;
14648 cache.pos += 1;
14649 Ok((logits, h_seed))
14650 }
14651
14652 fn gemma4_decode_step_h_pp2_samestream(
14655 &self,
14656 e: &Engine,
14657 token: u32,
14658 cache: &mut Cache,
14659 split: usize,
14660 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14661 let n_embd = self.cfg.n_embd as usize;
14662 let eps = self.cfg.rms_eps;
14663 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14664
14665 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14667 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14668 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
14669
14670 let boundary_tx = e.clone_dtod(&x)?;
14672 let boundary_rx = e.clone_dtod(&boundary_tx)?;
14673
14674 let x =
14676 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
14677
14678 let mut hn = e.uninit(n_embd)?;
14679 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14680 let h_seed = e.clone_dtod(&x)?;
14681 let mut ld = e.matmul(&self.output, &hn, 1)?;
14682 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14683 e.softcap(&mut ld, cap, self.output.out_features())?;
14684 self.gemma4_suppress(e, &mut ld, 1)?;
14685 let logits = e.dtoh(&ld)?;
14686 cache.pos += 1;
14687 Ok((logits, h_seed))
14688 }
14689}
14690
14691impl HybridModel {
14710 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
14713 let geometry = self
14714 .cfg
14715 .layer_geometry(il as u32)
14716 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
14717 debug_assert_eq!(
14718 geometry.attention_gate,
14719 memra_gguf::config::AttentionGateKind::SeparateHead
14720 );
14721 geometry
14722 }
14723
14724 #[allow(clippy::too_many_arguments)]
14784 fn step35_attn_pre_wo(
14785 &self,
14786 e: &Engine,
14787 fa: &FullAttnLayer,
14788 mut g3: Vec<CudaSlice<f32>>,
14789 hg: Option<&CudaSlice<f32>>,
14790 gt_pre: Option<&CudaSlice<f32>>,
14791 pos_d: &CudaSlice<i32>,
14792 t: usize,
14793 cache: Option<&mut Cache>,
14794 il: usize,
14795 seq_end: usize,
14796 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14797 let geometry = self.step35_geom(il);
14798 let hd = geometry.head_dim_k as usize;
14799 let nkv = geometry.n_head_kv as usize;
14800 let nh = geometry.n_head as usize;
14801 let rbase = geometry.rope_base;
14802 let scale = geometry.attention_scale();
14803 let swa = geometry.window.is_some();
14804 let eps = self.cfg.rms_eps;
14805 let win = geometry.window.unwrap_or(0) as usize;
14806 let n_rot = geometry.n_rot as usize;
14807
14808 let v = g3.pop().unwrap();
14809 let k0 = g3.pop().unwrap();
14810 let q0 = g3.pop().unwrap();
14811
14812 let mut q = e.uninit(t * nh * hd)?;
14816 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
14817 let mut k = e.uninit(t * nkv * hd)?;
14818 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
14819 let ff = if geometry.rope_factors {
14820 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
14821 } else {
14822 None
14823 };
14824 #[cfg(debug_assertions)]
14825 if let Some(ff) = ff {
14826 crate::debug_assert_tensor_stream_device(
14827 ff,
14828 &e.stream(),
14829 "step35_attn_pre_wo.rope_freqs",
14830 );
14831 }
14832 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
14833
14834 let mut attn = e.uninit(t * nh * hd)?;
14835 match cache {
14836 Some(cache) => {
14837 let base_len = cache.kv[il].as_ref().unwrap().len;
14838 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
14840 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
14841 let off = if swa {
14842 let raw = base_len.saturating_sub(win - 1);
14843 if legacy_tkv || legacy_calllocal {
14844 raw
14845 } else {
14846 raw & !31usize
14847 }
14848 } else {
14849 0
14850 };
14851 {
14852 let kvl = cache.kv[il].as_mut().unwrap();
14853 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
14854 let write_row = e.prepare_kv_append(kvl, off, t)?;
14855 e.append_kv_quantized_rows(
14856 &k,
14857 &v,
14858 &mut kvl.k,
14859 &mut kvl.v,
14860 write_row,
14861 t,
14862 kvl.kv_dim_k,
14863 kvl.kv_dim_v,
14864 kvl.k_tok_bytes,
14865 kvl.v_tok_bytes,
14866 crate::Engine::kv_fp8_on(),
14867 )?;
14868 kvl.len += t;
14869 let new_len = kvl.len as i32;
14870 e.set_i32_one(&mut kvl.len_d, new_len)?;
14871 }
14872 let kvl = cache.kv[il].as_ref().unwrap();
14873 let t_kv = base_len + t - off;
14896 let physical = kvl.physical_rows(off, off + t_kv)?;
14897 let k_view = e.view_u8_range(
14898 &kvl.k,
14899 physical.start * kvl.k_tok_bytes,
14900 physical.end * kvl.k_tok_bytes,
14901 );
14902 let v_view = e.view_u8_range(
14903 &kvl.v,
14904 physical.start * kvl.v_tok_bytes,
14905 physical.end * kvl.v_tok_bytes,
14906 );
14907 let swa_naive = if legacy_tkv {
14919 t_kv > win
14920 } else {
14921 seq_end > win
14922 };
14923 if swa && swa_naive {
14924 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
14937 e.sdpa_naive_w_quantized_view(
14938 &q,
14939 &k_view,
14940 &v_view,
14941 &mut attn,
14942 hd,
14943 nh,
14944 nkv,
14945 t,
14946 t_kv,
14947 scale,
14948 true,
14949 win,
14950 kvl.k_tok_bytes,
14951 kvl.v_tok_bytes,
14952 )?;
14953 } else {
14954 e.fa_prefill_view_ws_w_hd128(
14955 &q,
14956 &k_view,
14957 &v_view,
14958 &mut attn,
14959 hd,
14960 nh,
14961 nkv,
14962 t,
14963 t_kv,
14964 scale,
14965 true,
14966 win,
14967 kvl.k_tok_bytes,
14968 kvl.v_tok_bytes,
14969 )?;
14970 }
14971 } else if std::env::var("MEMRA_NOFA").is_ok() {
14972 e.sdpa_naive_quantized_view(
14973 &q,
14974 &k_view,
14975 &v_view,
14976 &mut attn,
14977 hd,
14978 nh,
14979 nkv,
14980 t,
14981 t_kv,
14982 scale,
14983 true,
14984 kvl.k_tok_bytes,
14985 kvl.v_tok_bytes,
14986 )?;
14987 } else {
14988 e.fa_prefill_view_ws(
14993 &q,
14994 &k_view,
14995 &v_view,
14996 &mut attn,
14997 hd,
14998 nh,
14999 nkv,
15000 t,
15001 t_kv,
15002 scale,
15003 true,
15004 kvl.k_tok_bytes,
15005 kvl.v_tok_bytes,
15006 crate::Engine::kv_fp8_on(),
15007 )?;
15008 }
15009 }
15010 None => {
15011 debug_assert_eq!(
15016 seq_end, t,
15017 "step35 cacheless prefill is monolithic (seq_end == t)"
15018 );
15019 if swa && seq_end > win {
15020 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
15021 } else if std::env::var("MEMRA_NOFA").is_ok() {
15022 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15023 } else {
15024 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15025 }
15026 }
15027 }
15028
15029 let gw = fa
15032 .attn_gate
15033 .as_ref()
15034 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
15035 let gt_owned = if gt_pre.is_none() {
15036 Some(e.matmul(
15037 gw,
15038 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
15039 t,
15040 )?)
15041 } else {
15042 None
15043 };
15044 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
15045 let mut ag = e.uninit(t * nh * hd)?;
15046 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
15047 Ok(ag)
15048 }
15049
15050 pub(crate) fn step35_attn(
15053 &self,
15054 e: &Engine,
15055 fa: &FullAttnLayer,
15056 h: &CudaSlice<f32>,
15057 pos_d: &CudaSlice<i32>,
15058 t: usize,
15059 il: usize,
15060 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15061 let g3 = match self.step35_tp_qkv(e, fa, h, t)? {
15062 Some(g3) => g3,
15063 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15064 };
15065 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
15067 self.step35_o(e, fa, &ag, t)
15068 }
15069
15070 #[allow(clippy::too_many_arguments)]
15077 pub(crate) fn step35_attn_prime(
15078 &self,
15079 e: &Engine,
15080 fa: &FullAttnLayer,
15081 h: &CudaSlice<f32>,
15082 hx: Option<&CudaSlice<u8>>,
15083 pos_d: &CudaSlice<i32>,
15084 t: usize,
15085 cache: &mut Cache,
15086 il: usize,
15087 seq_end: usize,
15088 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15089 if step_tp_prefill_enabled()? && fa.step_tp_qkv.is_some() {
15090 if hx.is_some() {
15091 return Err(
15092 "rank-local Step prefill preserves BF16 activations and refuses the q8_1 \
15093 pre-quantized prime path"
15094 .into(),
15095 );
15096 }
15097 return self.step35_tp_prefill_attn_resident(e, fa, il, h, pos_d, t, cache, seq_end);
15098 }
15099 let g3 = if fa.step_tp_qkv.is_some() {
15100 if hx.is_some() {
15101 return Err(
15102 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
15103 pre-quantized prime path"
15104 .into(),
15105 );
15106 }
15107 self.step35_tp_qkv(e, fa, h, t)?
15108 .expect("Step Q/K/V TP disappeared after the presence check")
15109 } else {
15110 match hx {
15111 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
15112 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15113 }
15114 };
15115 let ag =
15116 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
15117 self.step35_o(e, fa, &ag, t)
15118 }
15119
15120 fn ensure_step_tp_kv_cache(
15121 &self,
15122 e: &Engine,
15123 fa: &FullAttnLayer,
15124 il: usize,
15125 cache: &mut Cache,
15126 ) -> Result<bool, Box<dyn std::error::Error>> {
15127 let tp = fa
15128 .step_tp_qkv
15129 .as_ref()
15130 .ok_or("Step TP cache hydration lost its resident projections")?;
15131 let geometry = self.step35_geom(il);
15132 let window = geometry.window.map(|window| window as usize);
15133 let ranks = tp.runtime.devices().len();
15134 let head_dim = geometry.head_dim_k as usize;
15135 let kv_heads = geometry.n_head_kv as usize;
15136 let max_ctx = cache.max_ctx;
15137
15138 if cache.tp_kv[il].is_some() {
15139 return Ok(false);
15140 }
15141 let local = cache.kv[il]
15142 .as_ref()
15143 .ok_or_else(|| format!("Step TP layer {il} has no owning-stage KV cache"))?;
15144 if local.kv_dim_k != kv_heads * head_dim || local.kv_dim_v != kv_heads * head_dim {
15145 return Err(format!(
15146 "Step TP layer {il} local KV geometry k={} v={} != {}",
15147 local.kv_dim_k,
15148 local.kv_dim_v,
15149 kv_heads * head_dim
15150 )
15151 .into());
15152 }
15153 let resident_start = window
15154 .map(|window| local.len.saturating_sub(window.saturating_sub(1)) & !31usize)
15155 .unwrap_or(0);
15156 let resident_rows = local.len - resident_start;
15157 let physical = local.physical_rows(resident_start, local.len)?;
15158 let k_rows = if resident_rows == 0 {
15159 Vec::new()
15160 } else {
15161 e.dtoh_u8_view(&e.view_u8_range(
15162 &local.k,
15163 physical.start * local.k_tok_bytes,
15164 physical.end * local.k_tok_bytes,
15165 ))?
15166 };
15167 let v_rows = if resident_rows == 0 {
15168 Vec::new()
15169 } else {
15170 e.dtoh_u8_view(&e.view_u8_range(
15171 &local.v,
15172 physical.start * local.v_tok_bytes,
15173 physical.end * local.v_tok_bytes,
15174 ))?
15175 };
15176 let mut distributed = match window {
15177 Some(window) => tp.runtime.allocate_tp_swa_kv_cache(
15178 kv_heads * head_dim,
15179 kv_heads * head_dim,
15180 max_ctx,
15181 window,
15182 )?,
15183 None => tp.runtime.allocate_tp_kv_cache(
15184 kv_heads * head_dim,
15185 kv_heads * head_dim,
15186 max_ctx,
15187 )?,
15188 };
15189 if distributed.k_tok_bytes() * ranks != local.k_tok_bytes
15190 || distributed.v_tok_bytes() * ranks != local.v_tok_bytes
15191 {
15192 return Err(format!(
15193 "Step TP layer {il} distributed/local KV token bytes disagree: \
15194 k={}x{ranks}/{} v={}x{ranks}/{}",
15195 distributed.k_tok_bytes(),
15196 local.k_tok_bytes,
15197 distributed.v_tok_bytes(),
15198 local.v_tok_bytes,
15199 )
15200 .into());
15201 }
15202 tp.runtime.hydrate_tp_kv_cache_from(
15203 &mut distributed,
15204 local.len,
15205 resident_start,
15206 &k_rows,
15207 &v_rows,
15208 )?;
15209 cache.tp_kv[il] = Some(distributed);
15210 Ok(true)
15211 }
15212
15213 #[allow(clippy::too_many_arguments)]
15214 fn step35_tp_prefill_attn_resident(
15215 &self,
15216 e: &Engine,
15217 fa: &FullAttnLayer,
15218 il: usize,
15219 h: &CudaSlice<f32>,
15220 pos_d: &CudaSlice<i32>,
15221 tokens: usize,
15222 cache: &mut Cache,
15223 seq_end: usize,
15224 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15225 let tp = fa
15226 .step_tp_qkv
15227 .as_ref()
15228 .ok_or("Step TP prefill lost its resident projections")?;
15229 let attention = tp
15230 .attention
15231 .as_ref()
15232 .ok_or("Step TP prefill lost its resident attention auxiliaries")?;
15233 let ranks = tp.runtime.devices().len();
15234 if !step_tp_prefill_shape(
15235 true,
15236 tokens,
15237 ranks,
15238 tp.runtime.native_p2p(),
15239 true,
15240 crate::Engine::kv_fp8_on(),
15241 ) {
15242 return Err(format!(
15243 "rank-local Step prefill requires tokens>={PRIME_MIN_T}, TP4 native P2P, \
15244 rank-local attention, and q8_0/q5_1 KV; got tokens={tokens} ranks={ranks} \
15245 native_p2p={} fp8_kv={}",
15246 tp.runtime.native_p2p(),
15247 crate::Engine::kv_fp8_on(),
15248 )
15249 .into());
15250 }
15251 for seam in [
15252 "MEMRA_STEP35_SWA_TKV",
15253 "MEMRA_PRIME_CALLLOCAL",
15254 "MEMRA_PRIME_F32CHUNK0",
15255 ] {
15256 if std::env::var(seam).as_deref() == Ok("1") {
15257 return Err(format!(
15258 "rank-local Step prefill has not qualified the legacy seam {seam}=1"
15259 )
15260 .into());
15261 }
15262 }
15263
15264 let geometry = self.step35_geom(il);
15265 let window = geometry.window.map(|window| window as usize);
15266 let head_dim = geometry.head_dim_k as usize;
15267 let heads = geometry.n_head as usize;
15268 let kv_heads = geometry.n_head_kv as usize;
15269 if heads % ranks != 0 || kv_heads % ranks != 0 {
15270 return Err(format!(
15271 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15272 )
15273 .into());
15274 }
15275 let local_heads = heads / ranks;
15276 let local_kv_heads = kv_heads / ranks;
15277 let local_kv_dim = local_kv_heads * head_dim;
15278 let hidden = self.cfg.n_embd as usize;
15279 let expected_input = tokens
15280 .checked_mul(hidden)
15281 .ok_or("Step TP prefill input size overflow")?;
15282 if h.len() < expected_input {
15283 return Err(format!(
15284 "Step TP prefill input {} is shorter than {tokens}x{hidden}",
15285 h.len()
15286 )
15287 .into());
15288 }
15289 let positions = e.dtoh_i32(pos_d)?;
15290 if positions.len() != tokens {
15291 return Err(format!(
15292 "rank-local Step prefill positions {} != tokens {tokens}",
15293 positions.len()
15294 )
15295 .into());
15296 }
15297
15298 let mut active_input = e.uninit(expected_input)?;
15299 e.copy_view_into(
15300 &mut active_input,
15301 0,
15302 &h.slice(0..expected_input),
15303 expected_input,
15304 )?;
15305 let mut input = tp.runtime.allocate_replicated_device_rows(tokens, hidden)?;
15306 e.stream().synchronize()?;
15311 tp.runtime
15312 .refresh_replicated_device_rows_from_root(&mut input, &active_input)?;
15313 let q_raw = tp
15314 .runtime
15315 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &input)?;
15316 let k_raw = tp
15317 .runtime
15318 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &input)?;
15319 let v_raw = tp
15320 .runtime
15321 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &input)?;
15322 let mut q = Vec::with_capacity(ranks);
15323 let mut k = Vec::with_capacity(ranks);
15324 for rank in 0..ranks {
15325 let engine = tp
15326 .runtime
15327 .rank_engine(rank)
15328 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15329 let _main = engine.gpu.enter_main()?;
15330 let mut q_rank = engine.uninit(tokens * local_heads * head_dim)?;
15331 engine.rms_norm(
15332 &q_raw[rank],
15333 &attention.q_norm[rank],
15334 &mut q_rank,
15335 head_dim,
15336 tokens * local_heads,
15337 self.cfg.rms_eps,
15338 )?;
15339 let mut k_rank = engine.uninit(tokens * local_kv_dim)?;
15340 engine.rms_norm(
15341 &k_raw[rank],
15342 &attention.k_norm[rank],
15343 &mut k_rank,
15344 head_dim,
15345 tokens * local_kv_heads,
15346 self.cfg.rms_eps,
15347 )?;
15348 let position = engine.htod_i32(&positions)?;
15349 let rope_freqs = if geometry.rope_factors {
15350 self.step35_aux
15351 .as_ref()
15352 .and_then(|aux| aux.rope_freqs(engine))
15353 } else {
15354 None
15355 };
15356 engine.rope_neox2(
15357 &mut q_rank,
15358 &mut k_rank,
15359 &position,
15360 head_dim,
15361 geometry.n_rot as usize,
15362 local_heads,
15363 local_kv_heads,
15364 tokens,
15365 geometry.rope_base,
15366 1.0,
15367 rope_freqs,
15368 )?;
15369 q.push(q_rank);
15370 k.push(k_rank);
15371 }
15372
15373 let gate_weight = fa
15374 .attn_gate
15375 .as_ref()
15376 .ok_or("step35 layer is missing attn_gate.weight")?;
15377 let gate = e.dtoh(&e.matmul(gate_weight, h, tokens)?)?;
15378 if gate.len() != tokens * heads {
15379 return Err(format!(
15380 "Step TP layer {il} gate output {} != {tokens}x{heads}",
15381 gate.len()
15382 )
15383 .into());
15384 }
15385
15386 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15387 let base_len = cache.kv[il]
15388 .as_ref()
15389 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15390 .len;
15391 let distributed = cache.tp_kv[il]
15392 .as_ref()
15393 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15394 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15395 return Err(format!(
15396 "Step TP layer {il} cache lengths diverged before prefill: \
15397 local={base_len} distributed={}/{}",
15398 distributed.committed_len(),
15399 distributed.staged_len()
15400 )
15401 .into());
15402 }
15403 let target_len = base_len
15404 .checked_add(tokens)
15405 .ok_or("Step TP prefill cache length overflow")?;
15406 if target_len > cache.max_ctx {
15407 return Err(format!(
15408 "Step TP layer {il} prefill exceeds cache: {base_len}+{tokens}>{}",
15409 cache.max_ctx
15410 )
15411 .into());
15412 }
15413 if seq_end < target_len {
15414 return Err(format!(
15415 "Step TP layer {il} request end {seq_end} precedes chunk end {target_len}"
15416 )
15417 .into());
15418 }
15419
15420 let transaction = cache.tp_kv[il]
15421 .as_mut()
15422 .expect("distributed cache checked above")
15423 .begin_transaction()?;
15424 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15425 cache.tp_kv[il]
15426 .as_mut()
15427 .expect("distributed cache checked above"),
15428 transaction,
15429 &k,
15430 &v_raw,
15431 tokens,
15432 ) {
15433 let _ = tp.runtime.rollback_tp_kv_transaction(
15434 cache.tp_kv[il]
15435 .as_mut()
15436 .expect("distributed cache checked above"),
15437 transaction,
15438 );
15439 return Err(error);
15440 }
15441
15442 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15443 let distributed = cache.tp_kv[il]
15444 .as_ref()
15445 .expect("distributed cache checked above");
15446 let staged_len = distributed.staged_len();
15447 let view_start = window
15448 .map(|window| base_len.saturating_sub(window.saturating_sub(1)) & !31usize)
15449 .unwrap_or(0);
15450 let physical = distributed.physical_range(view_start, staged_len)?;
15451 let t_kv = staged_len - view_start;
15452 let swa_naive = window.is_some_and(|window| seq_end > window);
15453 let mut gated = Vec::with_capacity(ranks);
15454 for rank in 0..ranks {
15455 let engine = tp
15456 .runtime
15457 .rank_engine(rank)
15458 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15459 let _main = engine.gpu.enter_main()?;
15460 let rank_cache = distributed
15461 .rank(rank)
15462 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
15463 let k_view = engine.view_u8_range(
15464 rank_cache.k(),
15465 physical.start * distributed.k_tok_bytes(),
15466 physical.end * distributed.k_tok_bytes(),
15467 );
15468 let v_view = engine.view_u8_range(
15469 rank_cache.v(),
15470 physical.start * distributed.v_tok_bytes(),
15471 physical.end * distributed.v_tok_bytes(),
15472 );
15473 let mut attention_out = engine.uninit(tokens * local_heads * head_dim)?;
15474 if swa_naive {
15475 let window = window.expect("SWA predicate requires a window");
15476 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15477 engine.sdpa_naive_w_quantized_view(
15478 &q[rank],
15479 &k_view,
15480 &v_view,
15481 &mut attention_out,
15482 head_dim,
15483 local_heads,
15484 local_kv_heads,
15485 tokens,
15486 t_kv,
15487 geometry.attention_scale(),
15488 true,
15489 window,
15490 distributed.k_tok_bytes(),
15491 distributed.v_tok_bytes(),
15492 )?;
15493 } else {
15494 engine.fa_prefill_view_ws_w_hd128(
15495 &q[rank],
15496 &k_view,
15497 &v_view,
15498 &mut attention_out,
15499 head_dim,
15500 local_heads,
15501 local_kv_heads,
15502 tokens,
15503 t_kv,
15504 geometry.attention_scale(),
15505 true,
15506 window,
15507 distributed.k_tok_bytes(),
15508 distributed.v_tok_bytes(),
15509 )?;
15510 }
15511 } else if std::env::var("MEMRA_NOFA").is_ok() {
15512 engine.sdpa_naive_quantized_view(
15513 &q[rank],
15514 &k_view,
15515 &v_view,
15516 &mut attention_out,
15517 head_dim,
15518 local_heads,
15519 local_kv_heads,
15520 tokens,
15521 t_kv,
15522 geometry.attention_scale(),
15523 true,
15524 distributed.k_tok_bytes(),
15525 distributed.v_tok_bytes(),
15526 )?;
15527 } else {
15528 engine.fa_prefill_view_ws(
15529 &q[rank],
15530 &k_view,
15531 &v_view,
15532 &mut attention_out,
15533 head_dim,
15534 local_heads,
15535 local_kv_heads,
15536 tokens,
15537 t_kv,
15538 geometry.attention_scale(),
15539 true,
15540 distributed.k_tok_bytes(),
15541 distributed.v_tok_bytes(),
15542 false,
15543 )?;
15544 }
15545
15546 let gate_start = rank * local_heads;
15547 let mut gate_rank = Vec::with_capacity(tokens * local_heads);
15548 for token in 0..tokens {
15549 let start = token * heads + gate_start;
15550 gate_rank.extend_from_slice(&gate[start..start + local_heads]);
15551 }
15552 let gate_rank = engine.htod(&gate_rank)?;
15553 let mut gated_rank = engine.uninit(tokens * local_heads * head_dim)?;
15554 engine.attn_head_gate(
15555 &attention_out,
15556 &gate_rank,
15557 &mut gated_rank,
15558 None,
15559 head_dim,
15560 local_heads,
15561 tokens,
15562 )?;
15563 gated.push(gated_rank);
15564 }
15565 for rank in 1..ranks {
15566 let engine = tp
15567 .runtime
15568 .rank_engine(rank)
15569 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15570 let _main = engine.gpu.enter_main()?;
15571 engine.stream().synchronize()?;
15572 }
15573
15574 let (output, k_shadow, v_shadow) = if tp.runtime.bulk_p2p() {
15575 let output = tp
15576 .runtime
15577 .step_bf16_row_parallel_resident_root_device(&tp.o, &gated, tokens)?;
15578 let k_shadow =
15579 tp.runtime
15580 .gather_native_column_shards_device(&k, tokens, local_kv_dim)?;
15581 let v_shadow =
15582 tp.runtime
15583 .gather_native_column_shards_device(&v_raw, tokens, local_kv_dim)?;
15584 let root = tp
15585 .runtime
15586 .rank_engine(0)
15587 .ok_or("Step TP prefill lost its root engine")?;
15588 let _main = root.gpu.enter_main()?;
15589 root.stream().synchronize()?;
15590 (output, k_shadow, v_shadow)
15591 } else {
15592 let attention = tp.runtime.gather_native_column_shards(
15593 &gated,
15594 tokens,
15595 local_heads * head_dim,
15596 )?;
15597 let output = tp
15598 .runtime
15599 .step_bf16_row_parallel_resident_native(&tp.o, &attention, tokens)?;
15600 let k_shadow = tp
15601 .runtime
15602 .gather_native_column_shards(&k, tokens, local_kv_dim)?;
15603 let v_shadow =
15604 tp.runtime
15605 .gather_native_column_shards(&v_raw, tokens, local_kv_dim)?;
15606 (e.htod(&output)?, e.htod(&k_shadow)?, e.htod(&v_shadow)?)
15607 };
15608 let local = cache.kv[il]
15609 .as_mut()
15610 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
15611 if local.len != base_len {
15612 return Err(format!(
15613 "Step TP layer {il} local cache changed during prefill: \
15614 len={} base={base_len}",
15615 local.len
15616 )
15617 .into());
15618 }
15619 let retain_from = window
15620 .map(|window| {
15621 let staged_retain = staged_len.saturating_sub(window) & !31usize;
15622 let rollback_retain =
15623 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
15624 staged_retain.min(rollback_retain)
15625 })
15626 .unwrap_or(0);
15627 let write_row = e.prepare_kv_append(local, retain_from, tokens)?;
15628 e.append_kv_quantized_rows(
15629 &k_shadow,
15630 &v_shadow,
15631 &mut local.k,
15632 &mut local.v,
15633 write_row,
15634 tokens,
15635 local.kv_dim_k,
15636 local.kv_dim_v,
15637 local.k_tok_bytes,
15638 local.v_tok_bytes,
15639 false,
15640 )?;
15641 local.len = staged_len;
15642 e.set_i32_one(&mut local.len_d, staged_len as i32)?;
15643 Ok(output)
15644 })();
15645
15646 let output = match staged {
15647 Ok(output) => output,
15648 Err(error) => {
15649 let _ = tp.runtime.rollback_tp_kv_transaction(
15650 cache.tp_kv[il]
15651 .as_mut()
15652 .expect("distributed cache checked above"),
15653 transaction,
15654 );
15655 if let Some(local) = cache.kv[il].as_mut() {
15656 local.len = base_len;
15657 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
15658 }
15659 return Err(error);
15660 }
15661 };
15662 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
15663 cache.tp_kv[il]
15664 .as_mut()
15665 .expect("distributed cache checked above"),
15666 transaction,
15667 tokens,
15668 ) {
15669 let _ = tp.runtime.rollback_tp_kv_transaction(
15670 cache.tp_kv[il]
15671 .as_mut()
15672 .expect("distributed cache checked above"),
15673 transaction,
15674 );
15675 let local = cache.kv[il].as_mut().expect("local cache checked above");
15676 local.len = base_len;
15677 e.set_i32_one(&mut local.len_d, base_len as i32)?;
15678 return Err(error);
15679 }
15680
15681 let committed = cache.tp_kv[il]
15682 .as_ref()
15683 .expect("distributed cache checked above")
15684 .committed_len();
15685 let local_len = cache.kv[il]
15686 .as_ref()
15687 .expect("local cache checked above")
15688 .len;
15689 if committed != local_len {
15690 return Err(format!(
15691 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
15692 )
15693 .into());
15694 }
15695 eprintln!(
15696 "[step-tp-prefill-attn] execute layer={} devices={:?} tokens={tokens} \
15697 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
15698 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
15699 attention_scope={} input_path=root-device-replicated gate_tensor_parallel=false \
15700 gate_shards=host-canonical o_tensor_parallel=true local_cache_shadow=true \
15701 cache_commit=chunk transport={} native_p2p=true bulk_p2p={} \
15702 output={} performance_claim=false",
15703 tp.layer,
15704 tp.devices,
15705 hydrated,
15706 if window.is_some() {
15707 "rank-local-swa-ring"
15708 } else {
15709 "rank-local-global"
15710 },
15711 tp.runtime.transport_label(),
15712 tp.runtime.bulk_p2p(),
15713 if tp.runtime.bulk_p2p() {
15714 "root-device"
15715 } else {
15716 "root-readback"
15717 },
15718 );
15719 Ok(output)
15720 }
15721
15722 fn step35_tp_decode_attn_resident(
15723 &self,
15724 e: &Engine,
15725 fa: &FullAttnLayer,
15726 il: usize,
15727 h: &CudaSlice<f32>,
15728 pos_d: &CudaSlice<i32>,
15729 cache: &mut Cache,
15730 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15731 static ATTN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15735 static ATTN_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15736 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15737 let started = timing.then(std::time::Instant::now);
15738 let result = if crate::tp::step_tp_decode_v2_enabled()? {
15739 self.step35_tp_decode_attn_resident_v2(e, fa, il, h, pos_d, cache)
15740 } else {
15741 self.step35_tp_decode_attn_resident_inner(e, fa, il, h, pos_d, cache)
15742 };
15743 if let Some(started) = started {
15744 use std::sync::atomic::Ordering;
15745 let ns = ATTN_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
15746 + started.elapsed().as_nanos() as u64;
15747 let calls = ATTN_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
15748 if calls % 430 == 0 {
15749 eprintln!(
15750 "[step-tp-attn-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
15751 ns as f64 / 1.0e6,
15752 ns as f64 / calls as f64 / 1.0e3,
15753 );
15754 }
15755 }
15756 result
15757 }
15758
15759 #[allow(clippy::too_many_arguments)]
15760 fn step35_tp_decode_attn_resident_inner(
15761 &self,
15762 e: &Engine,
15763 fa: &FullAttnLayer,
15764 il: usize,
15765 h: &CudaSlice<f32>,
15766 pos_d: &CudaSlice<i32>,
15767 cache: &mut Cache,
15768 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15769 static T_POS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15774 static T_QKV: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15775 static T_NORMROPE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15776 static T_GATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15777 static T_APPEND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15778 static T_ATTN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15779 static T_OPROJ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15780 static T_SHADOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15781 static T_PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15782 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15783 fn lap(
15784 runtime: &crate::tp::TpE4m3HostBounce,
15785 e: &Engine,
15786 timer: &std::sync::atomic::AtomicU64,
15787 started: &mut Option<std::time::Instant>,
15788 ) -> Result<(), Box<dyn std::error::Error>> {
15789 let Some(start) = started.as_mut() else {
15790 return Ok(());
15791 };
15792 for rank in 0..runtime.devices().len() {
15793 if let Some(engine) = runtime.rank_engine(rank) {
15794 let _main = engine.gpu.enter_main()?;
15795 engine.stream().synchronize()?;
15796 }
15797 }
15798 e.stream().synchronize()?;
15799 timer.fetch_add(
15800 start.elapsed().as_nanos() as u64,
15801 std::sync::atomic::Ordering::Relaxed,
15802 );
15803 *start = std::time::Instant::now();
15804 Ok(())
15805 }
15806 let tp = fa
15807 .step_tp_qkv
15808 .as_ref()
15809 .ok_or("Step TP decode lost its resident projections")?;
15810 let attention = tp
15811 .attention
15812 .as_ref()
15813 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
15814 if !tp.runtime.native_p2p() {
15815 return Err("rank-local Step attention requires native P2P".into());
15816 }
15817 if crate::Engine::kv_fp8_on() {
15818 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
15819 }
15820
15821 let geometry = self.step35_geom(il);
15822 let window = geometry.window.map(|window| window as usize);
15823 let ranks = tp.runtime.devices().len();
15824 let head_dim = geometry.head_dim_k as usize;
15825 let heads = geometry.n_head as usize;
15826 let kv_heads = geometry.n_head_kv as usize;
15827 if heads % ranks != 0 || kv_heads % ranks != 0 {
15828 return Err(format!(
15829 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15830 )
15831 .into());
15832 }
15833 let local_heads = heads / ranks;
15834 let local_kv_heads = kv_heads / ranks;
15835 let local_kv_dim = local_kv_heads * head_dim;
15836 let max_ctx = cache.max_ctx;
15837
15838 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15839
15840 let base_len = cache.kv[il]
15841 .as_ref()
15842 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15843 .len;
15844 let distributed = cache.tp_kv[il]
15845 .as_ref()
15846 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15847 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15848 return Err(format!(
15849 "Step TP layer {il} cache lengths diverged before decode: \
15850 local={base_len} distributed={}/{}",
15851 distributed.committed_len(),
15852 distributed.staged_len()
15853 )
15854 .into());
15855 }
15856
15857 let mut lap_start = timing.then(std::time::Instant::now);
15858 let positions = e.dtoh_i32(pos_d)?;
15859 if positions.len() != 1 {
15860 return Err(format!(
15861 "rank-local Step decode requires one position, got {}",
15862 positions.len()
15863 )
15864 .into());
15865 }
15866 lap(&tp.runtime, e, &T_POS, &mut lap_start)?;
15867 let (q_raw, k_raw, v_raw, input_path) = if let Some(decode_input) =
15868 attention.decode_input.as_ref()
15869 {
15870 let mut decode_input = decode_input
15871 .lock()
15872 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
15873 e.stream().synchronize()?;
15877 tp.runtime
15878 .refresh_replicated_device_rows_from_root(&mut decode_input, h)?;
15879 let q_raw = tp
15880 .runtime
15881 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &decode_input)?;
15882 let k_raw = tp
15883 .runtime
15884 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &decode_input)?;
15885 let v_raw = tp
15886 .runtime
15887 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &decode_input)?;
15888 (q_raw, k_raw, v_raw, "root-device-replicated")
15889 } else {
15890 let activation = e.dtoh(h)?;
15891 let q_raw =
15892 tp.runtime
15893 .bf16_column_parallel_resident_device_shards(&tp.q, &activation, 1)?;
15894 let k_raw =
15895 tp.runtime
15896 .bf16_column_parallel_resident_device_shards(&tp.k, &activation, 1)?;
15897 let v_raw =
15898 tp.runtime
15899 .bf16_column_parallel_resident_device_shards(&tp.v, &activation, 1)?;
15900 (q_raw, k_raw, v_raw, "host-replicated")
15901 };
15902 lap(&tp.runtime, e, &T_QKV, &mut lap_start)?;
15903 let mut q = Vec::with_capacity(ranks);
15904 let mut k = Vec::with_capacity(ranks);
15905 for rank in 0..ranks {
15906 let engine = tp
15907 .runtime
15908 .rank_engine(rank)
15909 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15910 let _main = engine.gpu.enter_main()?;
15911 let mut q_rank = engine.uninit(local_heads * head_dim)?;
15912 engine.rms_norm(
15913 &q_raw[rank],
15914 &attention.q_norm[rank],
15915 &mut q_rank,
15916 head_dim,
15917 local_heads,
15918 self.cfg.rms_eps,
15919 )?;
15920 let mut k_rank = engine.uninit(local_kv_dim)?;
15921 engine.rms_norm(
15922 &k_raw[rank],
15923 &attention.k_norm[rank],
15924 &mut k_rank,
15925 head_dim,
15926 local_kv_heads,
15927 self.cfg.rms_eps,
15928 )?;
15929 let position = engine.htod_i32(&positions)?;
15930 let rope_freqs = if geometry.rope_factors {
15931 self.step35_aux
15932 .as_ref()
15933 .and_then(|aux| aux.rope_freqs(engine))
15934 } else {
15935 None
15936 };
15937 engine.rope_neox2(
15938 &mut q_rank,
15939 &mut k_rank,
15940 &position,
15941 head_dim,
15942 geometry.n_rot as usize,
15943 local_heads,
15944 local_kv_heads,
15945 1,
15946 geometry.rope_base,
15947 1.0,
15948 rope_freqs,
15949 )?;
15950 q.push(q_rank);
15951 k.push(k_rank);
15952 }
15953 lap(&tp.runtime, e, &T_NORMROPE, &mut lap_start)?;
15954
15955 let gate_weight = fa
15956 .attn_gate
15957 .as_ref()
15958 .ok_or("step35 layer is missing attn_gate.weight")?;
15959 let gate = e.matmul(gate_weight, h, 1)?;
15960 let gate = e.dtoh(&gate)?;
15961 if gate.len() != heads {
15962 return Err(format!("Step TP layer {il} gate output {} != {heads}", gate.len()).into());
15963 }
15964 lap(&tp.runtime, e, &T_GATE, &mut lap_start)?;
15965
15966 let transaction = cache.tp_kv[il]
15967 .as_mut()
15968 .expect("distributed cache checked above")
15969 .begin_transaction()?;
15970 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15971 cache.tp_kv[il]
15972 .as_mut()
15973 .expect("distributed cache checked above"),
15974 transaction,
15975 &k,
15976 &v_raw,
15977 1,
15978 ) {
15979 let _ = tp.runtime.rollback_tp_kv_transaction(
15980 cache.tp_kv[il]
15981 .as_mut()
15982 .expect("distributed cache checked above"),
15983 transaction,
15984 );
15985 return Err(error);
15986 }
15987 lap(&tp.runtime, e, &T_APPEND, &mut lap_start)?;
15988
15989 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15990 let distributed = cache.tp_kv[il]
15991 .as_ref()
15992 .expect("distributed cache checked above");
15993 let staged_len = distributed.staged_len();
15994 let view_start = window
15995 .map(|window| staged_len.saturating_sub(window))
15996 .unwrap_or(0);
15997 let physical = distributed.physical_range(view_start, staged_len)?;
15998 let t_kv = staged_len - view_start;
15999 let mut gated = Vec::with_capacity(ranks);
16000 for rank in 0..ranks {
16001 let engine = tp
16002 .runtime
16003 .rank_engine(rank)
16004 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16005 let _main = engine.gpu.enter_main()?;
16006 let rank_cache = distributed
16007 .rank(rank)
16008 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16009 let k_view = engine.view_u8_range(
16010 rank_cache.k(),
16011 physical.start * distributed.k_tok_bytes(),
16012 physical.end * distributed.k_tok_bytes(),
16013 );
16014 let v_view = engine.view_u8_range(
16015 rank_cache.v(),
16016 physical.start * distributed.v_tok_bytes(),
16017 physical.end * distributed.v_tok_bytes(),
16018 );
16019 let mut attention_out = engine.uninit(local_heads * head_dim)?;
16020 engine.fa_decode_kvmod(
16021 &q[rank],
16022 &k_view,
16023 &v_view,
16024 &mut attention_out,
16025 head_dim,
16026 local_heads,
16027 local_kv_heads,
16028 t_kv,
16029 geometry.attention_scale(),
16030 distributed.k_tok_bytes(),
16031 distributed.v_tok_bytes(),
16032 false,
16033 )?;
16034 let gate_start = rank * local_heads;
16035 let gate_rank = engine.htod(&gate[gate_start..gate_start + local_heads])?;
16036 let mut gated_rank = engine.uninit(local_heads * head_dim)?;
16037 engine.attn_head_gate(
16038 &attention_out,
16039 &gate_rank,
16040 &mut gated_rank,
16041 None,
16042 head_dim,
16043 local_heads,
16044 1,
16045 )?;
16046 gated.push(gated_rank);
16047 }
16048 lap(&tp.runtime, e, &T_ATTN, &mut lap_start)?;
16049
16050 let gathered =
16051 tp.runtime
16052 .gather_native_column_shards(&gated, 1, local_heads * head_dim)?;
16053 let output = tp
16054 .runtime
16055 .step_bf16_row_parallel_resident_native(&tp.o, &gathered, 1)?;
16056 let output = e.htod(&output)?;
16057 lap(&tp.runtime, e, &T_OPROJ, &mut lap_start)?;
16058
16059 let k_shadow = tp
16060 .runtime
16061 .gather_native_column_shards(&k, 1, local_kv_dim)?;
16062 let v_shadow = tp
16063 .runtime
16064 .gather_native_column_shards(&v_raw, 1, local_kv_dim)?;
16065 let k_shadow = e.htod(&k_shadow)?;
16066 let v_shadow = e.htod(&v_shadow)?;
16067 let local = cache.kv[il]
16068 .as_mut()
16069 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16070 if local.len != base_len || base_len + 1 > max_ctx {
16071 return Err(format!(
16072 "Step TP layer {il} local cache changed during decode: \
16073 len={} base={base_len} max={max_ctx}",
16074 local.len
16075 )
16076 .into());
16077 }
16078 let retain_from = window
16079 .map(|window| {
16080 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16081 let rollback_retain =
16082 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16083 staged_retain.min(rollback_retain)
16084 })
16085 .unwrap_or(0);
16086 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16087 e.append_kv_quantized(
16088 &k_shadow,
16089 &v_shadow,
16090 &mut local.k,
16091 &mut local.v,
16092 write_row,
16093 local.kv_dim_k,
16094 local.kv_dim_v,
16095 local.k_tok_bytes,
16096 local.v_tok_bytes,
16097 false,
16098 )?;
16099 local.len = base_len + 1;
16100 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16101 Ok(output)
16102 })();
16103
16104 let output = match staged {
16105 Ok(output) => output,
16106 Err(error) => {
16107 let _ = tp.runtime.rollback_tp_kv_transaction(
16108 cache.tp_kv[il]
16109 .as_mut()
16110 .expect("distributed cache checked above"),
16111 transaction,
16112 );
16113 if let Some(local) = cache.kv[il].as_mut() {
16114 local.len = base_len;
16115 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16116 }
16117 return Err(error);
16118 }
16119 };
16120 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16121 cache.tp_kv[il]
16122 .as_mut()
16123 .expect("distributed cache checked above"),
16124 transaction,
16125 1,
16126 ) {
16127 let _ = tp.runtime.rollback_tp_kv_transaction(
16128 cache.tp_kv[il]
16129 .as_mut()
16130 .expect("distributed cache checked above"),
16131 transaction,
16132 );
16133 let local = cache.kv[il].as_mut().expect("local cache checked above");
16134 local.len = base_len;
16135 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16136 return Err(error);
16137 }
16138
16139 let committed = cache.tp_kv[il]
16140 .as_ref()
16141 .expect("distributed cache checked above")
16142 .committed_len();
16143 let local_len = cache.kv[il]
16144 .as_ref()
16145 .expect("local cache checked above")
16146 .len;
16147 if committed != local_len {
16148 return Err(format!(
16149 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16150 )
16151 .into());
16152 }
16153 lap(&tp.runtime, e, &T_SHADOW, &mut lap_start)?;
16154 if timing {
16155 use std::sync::atomic::Ordering;
16156 let calls = T_PHASE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16157 if calls % 430 == 0 {
16158 let avg = |t: &std::sync::atomic::AtomicU64| {
16159 t.load(Ordering::Relaxed) as f64 / calls as f64 / 1.0e3
16160 };
16161 eprintln!(
16162 "[step-tp-attn-phase] calls={calls} avg_us pos={:.1} qkv={:.1} \
16163 normrope={:.1} gate={:.1} append={:.1} attn={:.1} oproj={:.1} shadow={:.1}",
16164 avg(&T_POS),
16165 avg(&T_QKV),
16166 avg(&T_NORMROPE),
16167 avg(&T_GATE),
16168 avg(&T_APPEND),
16169 avg(&T_ATTN),
16170 avg(&T_OPROJ),
16171 avg(&T_SHADOW),
16172 );
16173 }
16174 }
16175 eprintln!(
16176 "[step-tp-attn] execute layer={} devices={:?} tokens=1 \
16177 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16178 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16179 attention_scope={} input_path={} kv_physical_rows={} \
16180 gate_tensor_parallel=false gate_shards=host-canonical o_tensor_parallel=true \
16181 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16182 bulk_p2p={} output=root-readback performance_claim=false",
16183 tp.layer,
16184 tp.devices,
16185 hydrated,
16186 if window.is_some() {
16187 "rank-local-swa-ring"
16188 } else {
16189 "rank-local-global"
16190 },
16191 input_path,
16192 cache.tp_kv[il]
16193 .as_ref()
16194 .expect("distributed cache checked above")
16195 .physical_capacity(),
16196 tp.runtime.transport_label(),
16197 tp.runtime.bulk_p2p(),
16198 );
16199 Ok(output)
16200 }
16201
16202 #[allow(clippy::too_many_arguments)]
16209 pub(crate) fn step35_verify_qkv_precompute(
16214 &self,
16215 e: &Engine,
16216 il: usize,
16217 h_t: &CudaSlice<f32>,
16218 t: usize,
16219 ) -> Result<bool, Box<dyn std::error::Error>> {
16220 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16221 return Ok(false);
16222 };
16223 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16224 return Ok(false);
16225 };
16226 let Some(attention) = tp.attention.as_ref() else {
16227 return Ok(false);
16228 };
16229 if !tp.runtime.native_p2p() || !crate::tp::step_tp_qkv_fused_enabled()? {
16230 return Ok(false);
16231 }
16232 let geometry = self.step35_geom(il);
16233 let heads = geometry.n_head as usize;
16234 let ws_index = tp
16235 .runtime
16236 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16237 let gate_shards = attention
16238 .gate_shards_bf16
16239 .as_deref()
16240 .map(crate::tp::StepTpGateShards::Bf16);
16241 tp.runtime.decode_v2_input_qkv_tcol(
16242 ws_index,
16243 e,
16244 h_t,
16245 t,
16246 &tp.q,
16247 &tp.k,
16248 &tp.v,
16249 gate_shards,
16250 )?;
16251 Ok(true)
16252 }
16253
16254 pub(crate) fn step35_verify_oproj_tcol(
16259 &self,
16260 e: &Engine,
16261 il: usize,
16262 t: usize,
16263 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16264 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16265 return Err("tcol o_proj join expects full attention".into());
16266 };
16267 let tp = fa
16268 .step_tp_qkv
16269 .as_ref()
16270 .ok_or("tcol o_proj join lost its resident projections")?;
16271 let heads = self.step35_geom(il).n_head as usize;
16272 let ws_index = tp
16273 .runtime
16274 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16275 tp.runtime.decode_v2_oproj_tcol(ws_index, e, &tp.o, t)
16276 }
16277
16278 pub(crate) fn step35_verify_moe_t2(
16284 &self,
16285 e: &Engine,
16286 il: usize,
16287 z2: &CudaSlice<f32>,
16288 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16289 let layer = &self.layers[il];
16290 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
16291 return Ok(None);
16292 };
16293 let Some(tp) = m.step_tp.as_ref() else {
16294 return Ok(None);
16295 };
16296 let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts else {
16297 return Ok(None);
16298 };
16299 if !crate::tp::step_nvfp4_dev_routes_enabled()?
16300 || !crate::tp::step_tp_dev_router_enabled()?
16301 || !crate::tp::nvfp4_bank_v2_on()
16302 || bank.ep2
16303 {
16304 return Ok(None);
16305 }
16306 let cfg = &self.cfg;
16307 let Some(moe) = cfg.moe.as_ref() else {
16308 return Ok(None);
16309 };
16310 let Some((sf, route_norm)) = cfg.sigmoid_router() else {
16311 return Ok(None);
16312 };
16313 let n_embd = cfg.n_embd as usize;
16314 let n_expert = moe.expert_count as usize;
16315 let n_used = moe.expert_used_count as usize;
16316 if z2.len() < 2 * n_embd {
16317 return Err("verify moe t2 geometry".into());
16318 }
16319 let logits = Self::moe_router_logits(e, m, z2, 2, cfg)?;
16320 static SELW2: std::sync::Mutex<Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>> =
16322 std::sync::Mutex::new(None);
16323 let mut selw = SELW2.lock().map_err(|_| "selw2 lock poisoned")?;
16324 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
16325 *selw = Some((
16326 e.ctx().ordinal(),
16327 e.htod_i32(&vec![0i32; 2 * n_used])?,
16328 e.htod(&vec![0.0f32; 2 * n_used])?,
16329 ));
16330 }
16331 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
16332 e.moe_router_sigmoid_topk_into(
16333 &logits,
16334 2,
16335 n_expert,
16336 n_used,
16337 m.active_count(),
16338 &m.exp_probs_b_dev,
16339 &m.active_experts_dev,
16340 sf,
16341 route_norm,
16342 sel_d,
16343 w_d,
16344 )?;
16345 let mut out2 = tp
16346 .runtime
16347 .run_tensor_parallel_routes_nvfp4_device_routed_t2(
16348 bank,
16349 e,
16350 z2,
16351 sel_d,
16352 w_d,
16353 n_used,
16354 tp.activation_limit,
16355 )?;
16356 let mut z_row = e.uninit(n_embd)?;
16358 let mut out_row = e.uninit(n_embd)?;
16359 for c in 0..2 {
16360 e.dtod_copy_view(&z2.slice(c * n_embd..(c + 1) * n_embd), &mut z_row)?;
16361 e.dtod_copy_view(&out2.slice(c * n_embd..(c + 1) * n_embd), &mut out_row)?;
16362 Self::moe_ffn_grouped_add_shared(e, m, &z_row, 1, cfg, il as u16, &mut out_row)?;
16363 e.dtod_copy_into(&out_row, &mut out2, c * n_embd)?;
16364 }
16365 Ok(Some(out2))
16366 }
16367
16368 fn step35_tp_decode_attn_resident_v2(
16369 &self,
16370 e: &Engine,
16371 fa: &FullAttnLayer,
16372 il: usize,
16373 h: &CudaSlice<f32>,
16374 pos_d: &CudaSlice<i32>,
16375 cache: &mut Cache,
16376 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16377 let tp = fa
16378 .step_tp_qkv
16379 .as_ref()
16380 .ok_or("Step TP decode lost its resident projections")?;
16381 let attention = tp
16382 .attention
16383 .as_ref()
16384 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
16385 if !tp.runtime.native_p2p() {
16386 return Err("rank-local Step attention requires native P2P".into());
16387 }
16388 if crate::Engine::kv_fp8_on() {
16389 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
16390 }
16391
16392 let geometry = self.step35_geom(il);
16393 let window = geometry.window.map(|window| window as usize);
16394 let ranks = tp.runtime.devices().len();
16395 let head_dim = geometry.head_dim_k as usize;
16396 let heads = geometry.n_head as usize;
16397 let kv_heads = geometry.n_head_kv as usize;
16398 if heads % ranks != 0 || kv_heads % ranks != 0 {
16399 return Err(format!(
16400 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
16401 )
16402 .into());
16403 }
16404 let local_heads = heads / ranks;
16405 let local_kv_heads = kv_heads / ranks;
16406 let max_ctx = cache.max_ctx;
16407
16408 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
16409
16410 let base_len = cache.kv[il]
16411 .as_ref()
16412 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
16413 .len;
16414 {
16415 let distributed = cache.tp_kv[il]
16416 .as_ref()
16417 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
16418 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
16419 return Err(format!(
16420 "Step TP layer {il} cache lengths diverged before decode: \
16421 local={base_len} distributed={}/{}",
16422 distributed.committed_len(),
16423 distributed.staged_len()
16424 )
16425 .into());
16426 }
16427 }
16428 if pos_d.len() != 1 {
16429 return Err(format!(
16430 "rank-local Step decode requires one position, got {}",
16431 pos_d.len()
16432 )
16433 .into());
16434 }
16435
16436 let decode_input = attention
16437 .decode_input
16438 .as_ref()
16439 .ok_or("Step TP decode v2 requires the replicated decode input")?;
16440 let mut decode_input = decode_input
16441 .lock()
16442 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16443
16444 let use_gate_shards = (attention.gate_shards.is_some()
16449 || attention.gate_shards_bf16.is_some())
16450 && crate::tp::step_tp_qkv_fused_enabled()?;
16451 let gate_raw = if use_gate_shards {
16452 None
16453 } else {
16454 let gate_weight = fa
16455 .attn_gate
16456 .as_ref()
16457 .ok_or("step35 layer is missing attn_gate.weight")?;
16458 let gate_raw = e.matmul(gate_weight, h, 1)?;
16459 if gate_raw.len() != heads {
16460 return Err(format!(
16461 "Step TP layer {il} gate output {} != {heads}",
16462 gate_raw.len()
16463 )
16464 .into());
16465 }
16466 Some(gate_raw)
16467 };
16468
16469 let ws_index = tp
16470 .runtime
16471 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16472 let mut ws_guard = tp
16473 .runtime
16474 .decode_v2_workspace()
16475 .lock()
16476 .map_err(|_| "Step TP decode v2 workspace lock is poisoned")?;
16477 let ws = ws_guard
16478 .get_mut(ws_index)
16479 .ok_or("Step TP decode v2 workspace missing after ensure")?;
16480
16481 let mut rope_freqs = Vec::with_capacity(ranks);
16482 for rank in 0..ranks {
16483 let engine = tp
16484 .runtime
16485 .rank_engine(rank)
16486 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16487 rope_freqs.push(if geometry.rope_factors {
16488 self.step35_aux
16489 .as_ref()
16490 .and_then(|aux| aux.rope_freqs(engine))
16491 } else {
16492 None
16493 });
16494 }
16495 let staged_next = base_len + 1;
16502 let t_kv_eff = window
16503 .map(|window| staged_next.min(window))
16504 .unwrap_or(staged_next);
16505 let dcw = crate::tp::step_tp_dcw_enabled()? && use_gate_shards && t_kv_eff >= 96 && {
16506 let (write_row, would_rebase) = cache.tp_kv[il]
16507 .as_ref()
16508 .expect("distributed cache checked above")
16509 .peek_append_ring(1)?;
16510 if !would_rebase {
16511 let base = (base_len - write_row) as i32;
16513 let distributed = cache.tp_kv[il]
16514 .as_mut()
16515 .expect("distributed cache checked above");
16516 for rank in 0..ranks {
16517 let engine = tp.runtime.rank_engine(rank).ok_or_else(|| {
16518 format!("Step TP layer {il} has no engine for rank {rank}")
16519 })?;
16520 let _main = engine.gpu.enter_main()?;
16521 let rank_cache = distributed
16522 .rank_mut(rank)
16523 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16524 if rank_cache.base_d().is_none() {
16525 rank_cache.arm_base_d(engine.htod_i32(&[base])?);
16526 }
16527 }
16528 }
16529 !would_rebase
16530 };
16531 let fuse_rope = dcw
16532 && crate::tp::fuse_rope_append_on()
16533 && head_dim == 128
16534 && cache.tp_kv[il]
16535 .as_ref()
16536 .map(|d| d.kv_dim_k() == d.kv_dim_v() && d.kv_dim_k() == local_kv_heads * head_dim)
16537 .unwrap_or(false);
16538
16539 let tcol_col = crate::tp::take_verify_tcol();
16540 tp.runtime.decode_v2_input_qkv(
16541 ws,
16542 e,
16543 h,
16544 pos_d,
16545 gate_raw.as_ref(),
16546 if !use_gate_shards {
16547 None
16548 } else if let Some(shards) = attention.gate_shards.as_deref() {
16549 Some(crate::tp::StepTpGateShards::F32(shards))
16550 } else {
16551 attention
16552 .gate_shards_bf16
16553 .as_deref()
16554 .map(crate::tp::StepTpGateShards::Bf16)
16555 },
16556 &mut decode_input,
16557 &tp.q,
16558 &tp.k,
16559 &tp.v,
16560 &attention.q_norm,
16561 &attention.k_norm,
16562 head_dim,
16563 geometry.n_rot as usize,
16564 geometry.rope_base,
16565 &rope_freqs,
16566 self.cfg.rms_eps,
16567 fuse_rope,
16568 tcol_col,
16569 )?;
16570
16571 let transaction = cache.tp_kv[il]
16572 .as_mut()
16573 .expect("distributed cache checked above")
16574 .begin_transaction()?;
16575 let append_result = tp.runtime.append_tp_kv_transaction_inner(
16576 cache.tp_kv[il]
16577 .as_mut()
16578 .expect("distributed cache checked above"),
16579 transaction,
16580 &ws.k,
16581 &ws.v_raw,
16582 1,
16583 dcw,
16584 );
16585 if let Err(error) = append_result {
16586 let _ = tp.runtime.rollback_tp_kv_transaction(
16587 cache.tp_kv[il]
16588 .as_mut()
16589 .expect("distributed cache checked above"),
16590 transaction,
16591 );
16592 return Err(error);
16593 }
16594
16595 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16596 let (staged_len, physical, k_tok_bytes_c, v_tok_bytes_c, capacity) = {
16599 let distributed = cache.tp_kv[il]
16600 .as_ref()
16601 .expect("distributed cache checked above");
16602 let staged_len = distributed.staged_len();
16603 let view_start = window
16604 .map(|window| staged_len.saturating_sub(window))
16605 .unwrap_or(0);
16606 (
16607 staged_len,
16608 distributed.physical_range(view_start, staged_len)?,
16609 distributed.k_tok_bytes(),
16610 distributed.v_tok_bytes(),
16611 distributed.physical_capacity(),
16612 )
16613 };
16614 let view_start = window
16615 .map(|window| staged_len.saturating_sub(window))
16616 .unwrap_or(0);
16617 let t_kv = staged_len - view_start;
16618 for rank in 0..ranks {
16619 let engine = tp
16620 .runtime
16621 .rank_engine(rank)
16622 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16623 let _main = engine.gpu.enter_main()?;
16624 if dcw {
16625 {
16629 let distributed_mut = cache.tp_kv[il]
16630 .as_mut()
16631 .expect("distributed cache checked above");
16632 let (kv_dim_k, kv_dim_v) =
16633 (distributed_mut.kv_dim_k(), distributed_mut.kv_dim_v());
16634 let (k_tok_bytes, v_tok_bytes) =
16635 (distributed_mut.k_tok_bytes(), distributed_mut.v_tok_bytes());
16636 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
16637 format!("Step TP layer {il} has no KV cache rank {rank}")
16638 })?;
16639 let (k_plane, v_plane, len_d, base_d) =
16640 rank_cache.planes_and_counters_mut();
16641 if fuse_rope {
16642 let same_dev = engine.ctx().ordinal() == e.ctx().ordinal();
16645 let crate::tp::StepTpDecodeV2Ws {
16646 q_raw,
16647 k_raw,
16648 v_raw,
16649 q,
16650 k,
16651 pos,
16652 pos_stage,
16653 fuse_ctr,
16654 ..
16655 } = &mut *ws;
16656 let pos_ref: &CudaSlice<i32> = if same_dev {
16660 pos_stage
16661 .as_ref()
16662 .ok_or("step TP decode v2 pos stage not armed")?
16663 } else {
16664 &pos[rank]
16665 };
16666 engine.qk_norm_rope_append_inc_dcw(
16667 &q_raw[rank],
16668 &k_raw[rank],
16669 &v_raw[rank],
16670 &attention.q_norm[rank],
16671 &attention.k_norm[rank],
16672 &mut q[rank],
16673 &mut k[rank],
16674 pos_ref,
16675 k_plane,
16676 v_plane,
16677 len_d,
16678 base_d,
16679 &mut fuse_ctr[rank],
16680 kv_dim_k,
16681 kv_dim_v,
16682 k_tok_bytes,
16683 v_tok_bytes,
16684 head_dim,
16685 geometry.n_rot as usize,
16686 local_heads,
16687 local_kv_heads,
16688 self.cfg.rms_eps,
16689 geometry.rope_base,
16690 1.0,
16691 rope_freqs[rank],
16692 )?;
16693 } else {
16694 engine.append_kv_quantized_dcw(
16695 &ws.k[rank],
16696 &ws.v_raw[rank],
16697 k_plane,
16698 v_plane,
16699 len_d,
16700 base_d,
16701 kv_dim_k,
16702 kv_dim_v,
16703 k_tok_bytes,
16704 v_tok_bytes,
16705 )?;
16706 }
16707 if !fuse_rope {
16708 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
16709 format!("Step TP layer {il} has no KV cache rank {rank}")
16710 })?;
16711 engine.inc_i32(rank_cache.len_d_mut())?;
16712 }
16713 }
16714 let distributed = cache.tp_kv[il]
16715 .as_ref()
16716 .expect("distributed cache checked above");
16717 let rank_cache = distributed
16718 .rank(rank)
16719 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16720 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes_c);
16721 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes_c);
16722 {
16723 let crate::tp::StepTpDecodeV2Ws { q, gate, gated, .. } = &mut *ws;
16726 engine.fa_decode_dcw(
16727 &q[rank],
16728 &k_ring,
16729 &v_ring,
16730 &mut gated[rank],
16731 head_dim,
16732 local_heads,
16733 local_kv_heads,
16734 rank_cache.len_d(),
16735 rank_cache.base_d(),
16736 window.unwrap_or(0),
16737 t_kv,
16738 geometry.attention_scale(),
16739 k_tok_bytes_c,
16740 v_tok_bytes_c,
16741 Some(&gate[rank]),
16742 )?;
16743 }
16744 continue;
16745 }
16746 let distributed = cache.tp_kv[il]
16747 .as_ref()
16748 .expect("distributed cache checked above");
16749 let rank_cache = distributed
16750 .rank(rank)
16751 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16752 let k_view = engine.view_u8_range(
16753 rank_cache.k(),
16754 physical.start * k_tok_bytes_c,
16755 physical.end * k_tok_bytes_c,
16756 );
16757 let v_view = engine.view_u8_range(
16758 rank_cache.v(),
16759 physical.start * v_tok_bytes_c,
16760 physical.end * v_tok_bytes_c,
16761 );
16762 engine.fa_decode_kvmod(
16763 &ws.q[rank],
16764 &k_view,
16765 &v_view,
16766 &mut ws.attn_out[rank],
16767 head_dim,
16768 local_heads,
16769 local_kv_heads,
16770 t_kv,
16771 geometry.attention_scale(),
16772 k_tok_bytes_c,
16773 v_tok_bytes_c,
16774 false,
16775 )?;
16776 engine.attn_head_gate(
16777 &ws.attn_out[rank],
16778 &ws.gate[rank],
16779 &mut ws.gated[rank],
16780 None,
16781 head_dim,
16782 local_heads,
16783 1,
16784 )?;
16785 }
16786
16787 let output = if let Some(col) = crate::tp::take_tcol_oproj_defer() {
16794 if tp.runtime.decode_v2_oproj_tcol_eligible(ws, &tp.o) {
16795 tp.runtime.decode_v2_stash_gated(ws, e, col)?;
16796 crate::tp::set_tcol_oproj_stashed();
16797 e.uninit(ws.o_out)?
16798 } else {
16799 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
16800 }
16801 } else {
16802 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
16803 };
16804
16805 let local = cache.kv[il]
16809 .as_mut()
16810 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16811 if local.len != base_len || base_len + 1 > max_ctx {
16812 return Err(format!(
16813 "Step TP layer {il} local cache changed during decode: \
16814 len={} base={base_len} max={max_ctx}",
16815 local.len
16816 )
16817 .into());
16818 }
16819 if crate::tp::no_local_shadow_on() {
16820 local.len = base_len + 1;
16823 if !crate::tp::len_mirror_lazy_on() {
16827 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16828 }
16829 } else {
16830 let retain_from = window
16831 .map(|window| {
16832 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16833 let rollback_retain =
16834 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16835 staged_retain.min(rollback_retain)
16836 })
16837 .unwrap_or(0);
16838 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16839 e.append_kv_quantized(
16840 &ws.k_shadow,
16841 &ws.v_shadow,
16842 &mut local.k,
16843 &mut local.v,
16844 write_row,
16845 local.kv_dim_k,
16846 local.kv_dim_v,
16847 local.k_tok_bytes,
16848 local.v_tok_bytes,
16849 false,
16850 )?;
16851 local.len = base_len + 1;
16852 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16853 }
16854 Ok(output)
16855 })();
16856
16857 let output = match staged {
16858 Ok(output) => output,
16859 Err(error) => {
16860 let _ = tp.runtime.rollback_tp_kv_transaction(
16861 cache.tp_kv[il]
16862 .as_mut()
16863 .expect("distributed cache checked above"),
16864 transaction,
16865 );
16866 if let Some(local) = cache.kv[il].as_mut() {
16867 local.len = base_len;
16868 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16869 }
16870 return Err(error);
16871 }
16872 };
16873 let lazy_commit = fuse_rope && crate::tp::len_mirror_lazy_on();
16878 if lazy_commit {
16879 if let Err(error) = tp.runtime.commit_tp_kv_transaction_external(
16880 cache.tp_kv[il]
16881 .as_mut()
16882 .expect("distributed cache checked above"),
16883 transaction,
16884 1,
16885 ) {
16886 let _ = tp.runtime.rollback_tp_kv_transaction(
16887 cache.tp_kv[il]
16888 .as_mut()
16889 .expect("distributed cache checked above"),
16890 transaction,
16891 );
16892 let local = cache.kv[il].as_mut().expect("local cache checked above");
16893 local.len = base_len;
16894 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16895 return Err(error);
16896 }
16897 } else if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16898 cache.tp_kv[il]
16899 .as_mut()
16900 .expect("distributed cache checked above"),
16901 transaction,
16902 1,
16903 ) {
16904 let _ = tp.runtime.rollback_tp_kv_transaction(
16905 cache.tp_kv[il]
16906 .as_mut()
16907 .expect("distributed cache checked above"),
16908 transaction,
16909 );
16910 let local = cache.kv[il].as_mut().expect("local cache checked above");
16911 local.len = base_len;
16912 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16913 return Err(error);
16914 }
16915
16916 let committed = cache.tp_kv[il]
16917 .as_ref()
16918 .expect("distributed cache checked above")
16919 .committed_len();
16920 let local_len = cache.kv[il]
16921 .as_ref()
16922 .expect("local cache checked above")
16923 .len;
16924 if committed != local_len {
16925 return Err(format!(
16926 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16927 )
16928 .into());
16929 }
16930 static V2_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
16931 if !V2_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
16932 eprintln!(
16933 "[step-tp-attn-v2] execute layer={} devices={:?} tokens=1 driver=v2 \
16934 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16935 kv_cache_distributed=true kv_cache_hydrated={hydrated} \
16936 attention_tensor_parallel=true attention_scope={} \
16937 input_path=root-device-replicated gate_tensor_parallel=false \
16938 gate_shards=device-staged o_tensor_parallel=true o_reduce=root-device \
16939 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16940 bulk_p2p={} workspace=persistent ordering=evented output=e-device \
16941 performance_claim=false (logged once; every decode layer runs this driver)",
16942 tp.layer,
16943 tp.devices,
16944 if window.is_some() {
16945 "rank-local-swa-ring"
16946 } else {
16947 "rank-local-global"
16948 },
16949 tp.runtime.transport_label(),
16950 tp.runtime.bulk_p2p(),
16951 );
16952 }
16953 Ok(output)
16954 }
16955
16956 #[allow(clippy::too_many_arguments)]
16966 pub(crate) fn step35_decode_attn(
16967 &self,
16968 e: &Engine,
16969 fa: &FullAttnLayer,
16970 il: usize,
16971 h: &CudaSlice<f32>,
16972 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
16973 pos_d: &CudaSlice<i32>,
16974 cache: &mut Cache,
16975 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16976 if fa
16977 .step_tp_qkv
16978 .as_ref()
16979 .is_some_and(|tp| tp.attention.is_some())
16980 {
16981 if pre_q.is_some() {
16982 return Err(
16983 "rank-local Step attention preserves BF16 activations and refuses the q8_1 \
16984 pre-quantized decode path"
16985 .into(),
16986 );
16987 }
16988 return self.step35_tp_decode_attn_resident(e, fa, il, h, pos_d, cache);
16989 }
16990
16991 let geometry = self.step35_geom(il);
16992 let hd = geometry.head_dim_k as usize;
16993 let nkv = geometry.n_head_kv as usize;
16994 let nh = geometry.n_head as usize;
16995 let rbase = geometry.rope_base;
16996 let scale = geometry.attention_scale();
16997 let swa = geometry.window.is_some();
16998 let eps = self.cfg.rms_eps;
16999 let win = geometry.window.unwrap_or(0) as usize;
17000 let n_rot = geometry.n_rot as usize;
17001 let n_embd = self.cfg.n_embd as usize;
17002 let gw = fa
17003 .attn_gate
17004 .as_ref()
17005 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
17006
17007 let tp_qkv = if fa.step_tp_qkv.is_some() {
17008 if pre_q.is_some() {
17009 return Err(
17010 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
17011 pre-quantized decode path"
17012 .into(),
17013 );
17014 }
17015 self.step35_tp_qkv(e, fa, h, 1)?
17016 } else {
17017 None
17018 };
17019
17020 let (q0, k0, v0, gt) = match tp_qkv {
17021 Some(mut g3) => {
17022 let v = g3.pop().unwrap();
17023 let k = g3.pop().unwrap();
17024 let q = g3.pop().unwrap();
17025 let gt = e.matmul(gw, h, 1)?;
17026 (q, k, v, gt)
17027 }
17028 None => match pre_q {
17029 Some((hq, hdq)) => {
17030 debug_assert!(
17031 e.uses_q8_1_fast(gw),
17032 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
17033 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
17034 );
17035 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
17036 Some(t3) => t3,
17037 None => (
17038 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
17039 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
17040 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
17041 ),
17042 };
17043 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
17044 (a, b, c, gt)
17045 }
17046 None => {
17047 if e.uses_q8_1_fast(&fa.wq)
17048 && e.uses_q8_1_fast(&fa.wk)
17049 && e.uses_q8_1_fast(&fa.wv)
17050 && e.uses_q8_1_fast(gw)
17051 {
17052 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
17053 let (a, b, c) =
17054 match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
17055 Some(t3) => t3,
17056 None => (
17057 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
17058 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
17059 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
17060 ),
17061 };
17062 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
17063 (a, b, c, gt)
17064 } else {
17065 (
17066 e.matmul(&fa.wq, h, 1)?,
17067 e.matmul(&fa.wk, h, 1)?,
17068 e.matmul(&fa.wv, h, 1)?,
17069 e.matmul(gw, h, 1)?,
17070 )
17071 }
17072 }
17073 },
17074 };
17075
17076 let mut q = e.uninit(nh * hd)?;
17077 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
17078 let mut k = e.uninit(nkv * hd)?;
17079 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
17080 let ff = if swa {
17081 None
17082 } else {
17083 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
17084 };
17085 #[cfg(debug_assertions)]
17086 if let Some(ff) = ff {
17087 crate::debug_assert_tensor_stream_device(
17088 ff,
17089 &e.stream(),
17090 "step35_decode_attn.rope_freqs",
17091 );
17092 }
17093 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
17094
17095 if std::env::var("MEMRA_NOFA").is_ok() {
17096 return Err(
17097 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
17098 cache; unset MEMRA_NOFA to use fa_decode"
17099 .into(),
17100 );
17101 }
17102 let kvl = cache.kv[il].as_mut().unwrap();
17103 let next_len = kvl.len + 1;
17104 let (off, t_kv) = if swa && next_len > win {
17105 (next_len - win, win)
17106 } else {
17107 (0, next_len)
17108 };
17109 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
17110 e.append_kv_quantized(
17111 &k,
17112 &v0,
17113 &mut kvl.k,
17114 &mut kvl.v,
17115 write_row,
17116 kvl.kv_dim_k,
17117 kvl.kv_dim_v,
17118 kvl.k_tok_bytes,
17119 kvl.v_tok_bytes,
17120 crate::Engine::kv_fp8_on(),
17121 )?;
17122 kvl.len = next_len;
17123 let physical = kvl.physical_rows(off, off + t_kv)?;
17124 let k_view = e.view_u8_range(
17125 &kvl.k,
17126 physical.start * kvl.k_tok_bytes,
17127 physical.end * kvl.k_tok_bytes,
17128 );
17129 let v_view = e.view_u8_range(
17130 &kvl.v,
17131 physical.start * kvl.v_tok_bytes,
17132 physical.end * kvl.v_tok_bytes,
17133 );
17134 let mut attn = e.uninit(nh * hd)?;
17135 e.fa_decode_kvmod(
17136 &q,
17137 &k_view,
17138 &v_view,
17139 &mut attn,
17140 hd,
17141 nh,
17142 nkv,
17143 t_kv,
17144 scale,
17145 kvl.k_tok_bytes,
17146 kvl.v_tok_bytes,
17147 crate::Engine::kv_fp8_on(),
17148 )?;
17149
17150 let mut ag = e.uninit(nh * hd)?;
17151 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
17152 self.step35_o(e, fa, &ag, 1)
17153 }
17154}
17155
17156impl HybridModel {
17165 pub fn is_gemma4_e4b(&self) -> bool {
17166 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
17167 }
17168
17169 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
17173 let g = self.cfg.gemma4.as_ref().unwrap();
17174 let swa = g.swa_pattern[il];
17175 let hd = if swa {
17176 g.key_length_swa
17177 } else {
17178 g.key_length_global
17179 } as usize;
17180 let Mixer::Full(fa) = &self.layers[il].mixer else {
17181 panic!("e4b layer {il} not full-attn")
17182 };
17183 let nh = fa.wq.out_features() / hd;
17184 let nkv = fa.wk.out_features() / hd;
17185 (
17186 hd,
17187 nkv,
17188 nh,
17189 if swa {
17190 g.rope_base_swa
17191 } else {
17192 g.rope_base_global
17193 },
17194 1.0,
17195 swa,
17196 )
17197 }
17198
17199 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
17201 self.layers[il]
17202 .gemma4
17203 .as_ref()
17204 .and_then(|b| b.e4b.as_ref())
17205 .and_then(|e4| e4.kv_share.map(|t| t as usize))
17206 }
17207
17208 fn gemma4_e4b_inp_pl(
17213 &self,
17214 e: &Engine,
17215 tokens: &[u32],
17216 x_scaled: &CudaSlice<f32>,
17217 t: usize,
17218 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17219 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
17220 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
17221 }
17222
17223 fn gemma4_e4b_inp_pl_dev(
17225 &self,
17226 e: &Engine,
17227 tok_d: &CudaSlice<u32>,
17228 x_scaled: &CudaSlice<f32>,
17229 t: usize,
17230 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17231 let aux = self.gemma4_aux.as_ref().unwrap();
17232 let m = aux.e4b.as_ref().unwrap();
17233 let n_embd = self.cfg.n_embd as usize;
17234 let n_layer = self.layers.len();
17235 let width = m.n_epl * n_layer;
17236 let tbl = m.tok_tbl_gpu.get_or_init(|| {
17237 e.upload_u8(&m.tok_embd_bytes)
17238 .expect("e4b per-layer token table upload")
17239 });
17240 let mut a =
17241 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
17242 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
17243 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
17244 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
17245 let mut pn = e.uninit(t * width)?;
17246 e.rms_norm(
17247 &p,
17248 m.proj_norm.float_data(),
17249 &mut pn,
17250 m.n_epl,
17251 t * n_layer,
17252 self.cfg.rms_eps,
17253 )?;
17254 let mut out = e.uninit(t * width)?;
17255 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
17256 Ok(out)
17257 }
17258
17259 #[allow(clippy::too_many_arguments)]
17264 fn gemma4_e4b_attn(
17265 &self,
17266 e: &Engine,
17267 il: usize,
17268 hq: &CudaSlice<i8>,
17269 hdq: &CudaSlice<f32>,
17270 pos_d: &CudaSlice<i32>,
17271 t: usize,
17272 cache: &mut Cache,
17273 dc_bucket: Option<usize>,
17274 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17275 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
17276 let eps = self.cfg.rms_eps;
17277 let aux = self.gemma4_aux.as_ref().unwrap();
17278 let ones = aux.ones(e);
17279 #[cfg(debug_assertions)]
17280 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
17281 let Mixer::Full(fa) = &self.layers[il].mixer else {
17282 unreachable!()
17283 };
17284 let h0 = e.zeros(0)?;
17288 let h = &h0;
17289
17290 let ff = if swa {
17291 None
17292 } else {
17293 Some(
17294 aux.rope_freqs(e)
17295 .expect("e4b global rope needs rope_freqs.weight"),
17296 )
17297 };
17298 #[cfg(debug_assertions)]
17299 if let Some(ff) = ff {
17300 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
17301 }
17302 let share = self.gemma4_e4b_kv_target(il);
17303 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
17305 let mut q;
17306 if let Some(_tgt) = share {
17307 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
17308 q = e.uninit(t * nh * hd)?;
17309 let mut kdummy = e.uninit(1)?;
17312 let mut vdummy = e.uninit(1)?;
17313 e.rms_norm_qkv_rope(
17314 &q0,
17315 &q0,
17316 &q0,
17317 fa.q_norm.float_data(),
17318 fa.q_norm.float_data(),
17319 ones,
17320 &mut q,
17321 &mut kdummy,
17322 &mut vdummy,
17323 hd,
17324 self.gemma4_rope_dims(il),
17325 nh * t,
17326 0,
17327 pos_d,
17328 nh,
17329 1,
17330 base,
17331 1.0,
17332 ff,
17333 eps,
17334 )?;
17335 } else {
17336 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
17340 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
17341 q = e.uninit(t * nh * hd)?;
17342 let mut k = e.uninit(t * nkv * hd)?;
17343 let mut v = e.uninit(t * nkv * hd)?;
17344 if t == 1 && cat.is_some() {
17345 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
17346 e.rms_norm_qkv_rope_cat(
17347 &qkv0,
17348 fa.q_norm.float_data(),
17349 fa.k_norm.float_data(),
17350 ones,
17351 &mut q,
17352 &mut k,
17353 &mut v,
17354 hd,
17355 self.gemma4_rope_dims(il),
17356 nh,
17357 nkv,
17358 pos_d,
17359 nh,
17360 nkv,
17361 base,
17362 1.0,
17363 ff,
17364 eps,
17365 )?;
17366 } else {
17367 let (q0, k0, v0) = match if t == 1 {
17368 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
17369 } else {
17370 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17373 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
17374 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
17375 } else {
17376 None
17377 }
17378 } {
17379 Some(triple) => triple,
17380 None => (
17381 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
17382 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
17383 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
17384 ), };
17386 e.rms_norm_qkv_rope(
17389 &q0,
17390 &k0,
17391 &v0,
17392 fa.q_norm.float_data(),
17393 fa.k_norm.float_data(),
17394 ones,
17395 &mut q,
17396 &mut k,
17397 &mut v,
17398 hd,
17399 self.gemma4_rope_dims(il),
17400 nh * t,
17401 nkv * t,
17402 pos_d,
17403 nh,
17404 nkv,
17405 base,
17406 1.0,
17407 ff,
17408 eps,
17409 )?;
17410 }
17411 let kvl = cache.kv[il].as_mut().unwrap();
17412 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17416 if dc_bucket.is_some() {
17417 debug_assert!(t == 1);
17422 e.append_kv_quantized_row_dc_inc(
17424 &k,
17425 &v,
17426 &mut kvl.k,
17427 &mut kvl.v,
17428 &mut kvl.len_d,
17429 kvl.kv_dim_k,
17430 kvl.kv_dim_v,
17431 kvl.k_tok_bytes,
17432 kvl.v_tok_bytes,
17433 cls,
17434 )?;
17435 } else {
17436 e.append_kv_quantized_rows(
17437 &k,
17438 &v,
17439 &mut kvl.k,
17440 &mut kvl.v,
17441 kvl.len,
17442 t,
17443 kvl.kv_dim_k,
17444 kvl.kv_dim_v,
17445 kvl.k_tok_bytes,
17446 kvl.v_tok_bytes,
17447 cls,
17448 )?;
17449 kvl.len += t;
17450 }
17451 kv_f32 = Some((k, v));
17452 }
17453 let kvl_idx = share.unwrap_or(il);
17456 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
17457 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
17459 let mut attn = e.uninit(t * nh * hd)?;
17460 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
17472 if let Some((kf, vf)) = &kv_f32 {
17473 if hd == 256 && t <= win {
17474 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17475 return Ok(e.matmul(&fa.wo, &attn, t)?);
17476 }
17477 if hd == 256 && swa && t > win {
17478 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
17479 return Ok(e.matmul(&fa.wo, &attn, t)?);
17480 }
17481 if hd == 512 && !swa {
17482 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17483 return Ok(e.matmul(&fa.wo, &attn, t)?);
17484 }
17485 } else if share.is_some() {
17486 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17487 let k_view = e.view_u8(&kvl.k, kvl.k.len());
17488 let v_view = e.view_u8(&kvl.v, kvl.v.len());
17489 if hd == 256 && (!swa || t <= win) {
17490 e.fa_prefill_view(
17492 &q,
17493 &k_view,
17494 &v_view,
17495 &mut attn,
17496 hd,
17497 nh,
17498 nkv,
17499 t,
17500 t,
17501 scale,
17502 true,
17503 kvl.k_tok_bytes,
17504 kvl.v_tok_bytes,
17505 g,
17506 )?;
17507 return Ok(e.matmul(&fa.wo, &attn, t)?);
17508 }
17509 let kv_dim = nkv * hd;
17512 let mut kf = e.uninit(t * kv_dim)?;
17513 let mut vf = e.uninit(t * kv_dim)?;
17514 e.fa_dequant_kv_view_f32(
17515 &k_view,
17516 &v_view,
17517 &mut kf,
17518 &mut vf,
17519 kv_dim,
17520 kv_dim,
17521 t,
17522 kvl.k_tok_bytes,
17523 kvl.v_tok_bytes,
17524 g,
17525 )?;
17526 if hd == 512 {
17527 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17528 } else {
17529 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
17530 }
17531 return Ok(e.matmul(&fa.wo, &attn, t)?);
17532 }
17533 }
17534 if let Some(bucket) = dc_bucket {
17535 assert!(t == 1);
17540 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
17546 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
17547 } else {
17548 bucket
17549 };
17550 let k_view = e.view_u8(&kvl.k, kvl.k.len());
17551 let v_view = e.view_u8(&kvl.v, kvl.v.len());
17552 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17553 if crate::Engine::wpf_level() >= 1 {
17561 e.prefetch_weight_l2(&fa.wo)?;
17562 }
17563 if e.uses_q8_1_fast(&fa.wo) {
17566 let mut oq = e.alloc_i8_uninit(nh * hd)?;
17567 let mut od = e.zeros(nh * hd / 32)?;
17568 e.fa_decode_dc_q8(
17569 &q,
17570 &k_view,
17571 &v_view,
17572 &mut attn,
17573 hd,
17574 nh,
17575 nkv,
17576 &kvl.len_d,
17577 bucket,
17578 scale,
17579 kvl.k_tok_bytes,
17580 kvl.v_tok_bytes,
17581 g,
17582 Some((&mut oq, &mut od)),
17583 )?;
17584 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
17585 }
17586 e.fa_decode_dc(
17587 &q,
17588 &k_view,
17589 &v_view,
17590 &mut attn,
17591 hd,
17592 nh,
17593 nkv,
17594 &kvl.len_d,
17595 bucket,
17596 scale,
17597 kvl.k_tok_bytes,
17598 kvl.v_tok_bytes,
17599 g,
17600 )?;
17601 return Ok(e.matmul(&fa.wo, &attn, t)?);
17602 }
17603 for i in 0..t {
17604 let avail = base_len + i + 1;
17605 let (off_tok, t_kv) = if swa && avail > win {
17606 (avail - win, win)
17607 } else {
17608 (0, avail)
17609 };
17610 let k_view = e.view_u8_range(
17611 &kvl.k,
17612 off_tok * kvl.k_tok_bytes,
17613 (off_tok + t_kv) * kvl.k_tok_bytes,
17614 );
17615 let v_view = e.view_u8_range(
17616 &kvl.v,
17617 off_tok * kvl.v_tok_bytes,
17618 (off_tok + t_kv) * kvl.v_tok_bytes,
17619 );
17620 let qv = e.view(&q, t * nh * hd);
17621 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
17622 let mut q_one = e.uninit(nh * hd)?;
17623 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
17624 let mut a_one = e.uninit(nh * hd)?;
17625 e.fa_decode_kvmod(
17629 &q_one,
17630 &k_view,
17631 &v_view,
17632 &mut a_one,
17633 hd,
17634 nh,
17635 nkv,
17636 t_kv,
17637 scale,
17638 kvl.k_tok_bytes,
17639 kvl.v_tok_bytes,
17640 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
17641 )?;
17642 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
17643 }
17644 Ok(e.matmul(&fa.wo, &attn, t)?)
17645 }
17646
17647 fn gemma4_e4b_trunk(
17652 &self,
17653 e: &Engine,
17654 tokens: &[u32],
17655 pos0: usize,
17656 cache: &mut Cache,
17657 head_last: bool,
17658 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17659 let n_embd = self.cfg.n_embd as usize;
17660 let t = tokens.len();
17661 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
17662 let pos_d = e.htod_i32(&pos)?;
17663 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
17664 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
17665 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
17666 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
17667 }
17668
17669 fn gemma4_e4b_trunk_core(
17673 &self,
17674 e: &Engine,
17675 x_in: CudaSlice<f32>,
17676 inp_pl: CudaSlice<f32>,
17677 pos_d: &CudaSlice<i32>,
17678 t: usize,
17679 cache: &mut Cache,
17680 dc_bucket: Option<usize>,
17681 cap_logits: bool,
17682 head_last: bool,
17683 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17684 let n_embd = self.cfg.n_embd as usize;
17685 let eps = self.cfg.rms_eps;
17686 let n_layer = self.layers.len();
17687 let mut x = x_in;
17688 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
17689 let n_epl = aux_e4b.n_epl;
17690
17691 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
17697 for il in 0..n_layer {
17698 let layer = &self.layers[il];
17699 let (hq, hdq) = match h_carry.take() {
17700 Some(p) => p,
17701 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
17702 };
17703 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
17704 let bits = layer.gemma4.as_ref().unwrap();
17707 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
17708 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
17719 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
17720 e,
17721 layer,
17722 &o,
17723 &x,
17724 t,
17725 Some(layer.post_attn_norm.float_data()),
17726 fuse_exit,
17727 )?;
17728 let mut resid = e.uninit(t * n_embd)?;
17729 let g = if fuse_exit {
17735 let (rq, rd) = e.rms_pre_add_q8_1(
17737 &sn,
17738 bits.post_ffw_norm.float_data(),
17739 &attn_out,
17740 &mut resid,
17741 n_embd,
17742 t,
17743 self.cfg.rms_eps,
17744 )?;
17745 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
17746 } else {
17747 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
17748 e.matmul(&e4b.inp_gate, &resid, t)?
17749 };
17750 let mut act = e.uninit(t * n_epl)?;
17751 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
17752 let ipv = e.view(&inp_pl, n_epl * n_layer);
17753 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
17754 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
17755 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
17756 } else {
17757 let mut inp_this = e.uninit(t * n_epl)?;
17758 e.copy_rows_strided(
17759 &inp_pl,
17760 &mut inp_this,
17761 n_epl,
17762 t,
17763 n_epl * n_layer,
17764 il * n_epl,
17765 )?;
17766 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
17767 e.matmul(&e4b.proj, &act, t)?
17768 };
17769 let next_norm = if il + 1 < n_layer {
17772 self.layers[il + 1].attn_norm.float_data()
17773 } else {
17774 self.output_norm.float_data()
17775 };
17776 let mut xn = e.uninit(t * n_embd)?;
17777 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
17778 &y,
17779 e4b.post_norm.float_data(),
17780 &resid,
17781 bits.layer_scale,
17782 next_norm,
17783 &mut xn,
17784 n_embd,
17785 t,
17786 eps,
17787 )?;
17788 h_carry = Some(pair);
17789 x = xn;
17790 }
17791 let (oq, odq) = h_carry.take().unwrap();
17795 let h0 = e.zeros(0)?;
17796 let hm = if head_last { 1 } else { t };
17797 let (hq, hd) = if head_last && t > 1 {
17798 let mut q1 = e.uninit_i8(n_embd)?;
17799 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
17800 let nb = n_embd / 32;
17801 let mut d1 = e.uninit(nb)?;
17802 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
17803 (q1, d1)
17804 } else {
17805 (oq, odq)
17806 };
17807 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
17808 if cap_logits {
17812 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
17813 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
17814 }
17815 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
17817 }
17818
17819 pub fn gemma4_e4b_decode_step_t_am_dev(
17826 &self,
17827 e: &Engine,
17828 tok_d: &CudaSlice<u32>,
17829 t: usize,
17830 pos0: usize,
17831 cache: &mut Cache,
17832 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17833 let n_embd = self.cfg.n_embd as usize;
17834 let eps = self.cfg.rms_eps;
17835 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
17836 let pos_d = e.htod_i32(&pos)?;
17837 let embd_gpu = self
17838 .embd_gpu
17839 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
17840 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
17841 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
17842 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
17843 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
17844 let (ld, xp) =
17845 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
17846 let n_vocab = self.output.out_features();
17849 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
17850 for i in 0..t {
17851 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
17852 }
17853 let mut hn = e.uninit(t * n_embd)?;
17854 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
17855 cache.pos += t;
17856 Ok((vam, hn))
17857 }
17858
17859 pub(crate) fn gemma4_e4b_decode_step_t_h(
17862 &self,
17863 e: &Engine,
17864 tokens: &[u32],
17865 pos0: usize,
17866 cache: &mut Cache,
17867 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17868 let n_embd = self.cfg.n_embd as usize;
17869 let eps = self.cfg.rms_eps;
17870 let t = tokens.len();
17871 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
17872 let mut hn = e.uninit(t * n_embd)?;
17873 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
17874 cache.pos += t;
17875 Ok((e.dtoh(&ld)?, hn))
17876 }
17877
17878 pub fn gemma4_e4b_decode_step_dcg(
17884 &self,
17885 e: &Engine,
17886 token_d: &mut CudaSlice<u32>,
17887 pos_d: &mut CudaSlice<i32>,
17888 embd_gpu: &CudaSlice<u8>,
17889 embd_qt: i32,
17890 embd_rb: usize,
17891 cache: &mut Cache,
17892 n_vocab: usize,
17893 bucket: usize,
17894 ) -> Result<(), Box<dyn std::error::Error>> {
17895 let n_embd = self.cfg.n_embd as usize;
17896 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
17897 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
17898 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
17899 let (ld, _x) =
17900 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
17901 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
17902 e.inc_seqlen(pos_d)?;
17903 Ok(())
17904 }
17905
17906 #[allow(clippy::too_many_arguments)]
17914 pub fn gemma4_e4b_decode_step_dc(
17915 &self,
17916 e: &Engine,
17917 token_d: &CudaSlice<u32>,
17918 pos_d: &mut CudaSlice<i32>,
17919 embd_gpu: &CudaSlice<u8>,
17920 embd_qt: i32,
17921 embd_rb: usize,
17922 cache: &mut Cache,
17923 n_vocab: usize,
17924 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
17925 let n_embd = self.cfg.n_embd as usize;
17926 let eps = self.cfg.rms_eps;
17927 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
17928 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
17929 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
17930 let (ld, _x) =
17931 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
17932 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
17933 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
17934 e.inc_seqlen(pos_d)?;
17935 cache.pos += 1;
17936 let _ = eps;
17937 Ok(tok_out)
17938 }
17939
17940 pub(crate) fn gemma4_e4b_decode_step_h(
17943 &self,
17944 e: &Engine,
17945 token: u32,
17946 cache: &mut Cache,
17947 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17948 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
17949 let logits = e.dtoh(&ld)?;
17950 cache.pos += 1;
17951 Ok((logits, x))
17952 }
17953
17954 pub(crate) fn gemma4_e4b_prime(
17958 &self,
17959 e: &Engine,
17960 tokens: &[u32],
17961 cache: &mut Cache,
17962 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17963 if cache.pos != 0 {
17966 return Err(
17967 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
17968 call or decode tokenwise"
17969 .into(),
17970 );
17971 }
17972 let n_embd = self.cfg.n_embd as usize;
17973 let t = tokens.len();
17974 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
17975 cache.pos += t;
17976 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
17978 let row = xv.slice((t - 1) * n_embd..t * n_embd);
17979 let mut h_seed = e.uninit(n_embd)?;
17980 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
17981 Ok((last, h_seed, x))
17982 }
17983
17984 pub(crate) fn gemma4_e4b_forward(
17986 &self,
17987 e: &Engine,
17988 tokens: &[u32],
17989 last_only: bool,
17990 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
17991 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
17992 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
17993 Ok(e.dtoh(&ld)?) }
17995}
17996
17997#[cfg(test)]
17998mod prime_chunk_schedule_tests {
17999 use super::{
18000 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, active_matrix_values, dynamic_prime_chunk_ranges,
18001 fixed_prime_chunk_ranges, fixed_prime_chunk_ranges_for_ring, parse_step_ep_grouped_prefill,
18002 parse_step_tp_prefill, step_grouped_decode_shape, step_grouped_prefill_shape,
18003 step_tp_prefill_shape, validate_step_prime_batch_modes,
18004 };
18005
18006 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
18007 ranges.iter().map(|(start, end)| end - start).collect()
18008 }
18009
18010 fn auto_chunk(t: usize) -> usize {
18011 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
18012 }
18013
18014 #[test]
18015 fn active_matrix_prefix_scopes_reused_prime_slabs() {
18016 assert_eq!(
18017 active_matrix_values(40 * 4096, 29, 4096, "activation").unwrap(),
18018 29 * 4096
18019 );
18020 assert_eq!(
18021 active_matrix_values(29 * 4096, 29, 4096, "activation").unwrap(),
18022 29 * 4096
18023 );
18024 assert_eq!(
18025 active_matrix_values(29 * 4096, 24, 4096, "activation").unwrap(),
18026 24 * 4096
18027 );
18028 assert!(active_matrix_values(28 * 4096, 29, 4096, "activation").is_err());
18029 assert!(active_matrix_values(usize::MAX, usize::MAX, 2, "activation").is_err());
18030 }
18031
18032 #[test]
18033 fn step_tp_prefill_batch_refuses_before_scheduler_fallback() {
18034 assert!(validate_step_prime_batch_modes(false, false).is_ok());
18035
18036 let grouped_without_tp = validate_step_prime_batch_modes(false, true).unwrap_err();
18037 assert!(grouped_without_tp.contains("requires MEMRA_STEP_TP_PREFILL=1"));
18038
18039 for grouped in [false, true] {
18040 let err = validate_step_prime_batch_modes(true, grouped).unwrap_err();
18041 assert!(err.contains("did not clear the live-server performance gate"));
18042 assert!(err.contains("per-session grouped prefill"));
18043 }
18044 }
18045
18046 #[test]
18047 fn step_grouped_path_is_eager_single_token_only() {
18048 assert!(step_grouped_decode_shape(false, 1));
18049 assert!(!step_grouped_decode_shape(true, 1));
18050 assert!(!step_grouped_decode_shape(false, 2));
18051 assert!(!step_grouped_decode_shape(true, 2));
18052 }
18053
18054 #[test]
18055 fn step_grouped_prefill_door_is_strict_and_capacity_bounded() {
18056 assert!(!parse_step_ep_grouped_prefill(None).unwrap());
18057 assert!(!parse_step_ep_grouped_prefill(Some("")).unwrap());
18058 assert!(!parse_step_ep_grouped_prefill(Some("0")).unwrap());
18059 assert!(parse_step_ep_grouped_prefill(Some("1")).unwrap());
18060 assert!(parse_step_ep_grouped_prefill(Some("true")).is_err());
18061 assert!(parse_step_ep_grouped_prefill(Some("2")).is_err());
18062
18063 assert!(step_grouped_prefill_shape(true, true, PRIME_MIN_T));
18064 assert!(step_grouped_prefill_shape(
18065 true,
18066 true,
18067 crate::cache::PRIME_CHUNK_MAX_TOKENS,
18068 ));
18069 assert!(!step_grouped_prefill_shape(true, true, PRIME_MIN_T - 1,));
18070 assert!(!step_grouped_prefill_shape(
18071 true,
18072 true,
18073 crate::cache::PRIME_CHUNK_MAX_TOKENS + 1,
18074 ));
18075 assert!(!step_grouped_prefill_shape(false, true, PRIME_MIN_T));
18076 assert!(!step_grouped_prefill_shape(true, false, PRIME_MIN_T));
18077 }
18078
18079 #[test]
18080 fn step_tp_prefill_door_is_strict_and_default_off() {
18081 assert!(!parse_step_tp_prefill(None).unwrap());
18082 assert!(!parse_step_tp_prefill(Some("")).unwrap());
18083 assert!(!parse_step_tp_prefill(Some("0")).unwrap());
18084 assert!(parse_step_tp_prefill(Some("1")).unwrap());
18085 assert!(parse_step_tp_prefill(Some("true")).is_err());
18086 assert!(parse_step_tp_prefill(Some("2")).is_err());
18087 }
18088
18089 #[test]
18090 fn step_tp_prefill_requires_the_qualified_tp4_shape() {
18091 assert!(step_tp_prefill_shape(
18092 true,
18093 PRIME_MIN_T,
18094 4,
18095 true,
18096 true,
18097 false,
18098 ));
18099 assert!(!step_tp_prefill_shape(
18100 false,
18101 PRIME_MIN_T,
18102 4,
18103 true,
18104 true,
18105 false,
18106 ));
18107 assert!(!step_tp_prefill_shape(
18108 true,
18109 PRIME_MIN_T - 1,
18110 4,
18111 true,
18112 true,
18113 false,
18114 ));
18115 assert!(!step_tp_prefill_shape(
18116 true,
18117 PRIME_MIN_T,
18118 2,
18119 true,
18120 true,
18121 false,
18122 ));
18123 assert!(!step_tp_prefill_shape(
18124 true,
18125 PRIME_MIN_T,
18126 4,
18127 false,
18128 true,
18129 false,
18130 ));
18131 assert!(!step_tp_prefill_shape(
18132 true,
18133 PRIME_MIN_T,
18134 4,
18135 true,
18136 false,
18137 false,
18138 ));
18139 assert!(!step_tp_prefill_shape(
18140 true,
18141 PRIME_MIN_T,
18142 4,
18143 true,
18144 true,
18145 true,
18146 ));
18147 }
18148
18149 #[test]
18150 fn fixed_schedule_retains_measured_geometry() {
18151 assert_eq!(
18152 sizes(&fixed_prime_chunk_ranges(461, 128)),
18153 vec![128, 128, 128, 77]
18154 );
18155 assert_eq!(
18156 sizes(&fixed_prime_chunk_ranges(1833, 230)),
18157 vec![230, 230, 230, 230, 230, 230, 230, 223]
18158 );
18159 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
18160 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
18161 assert_eq!(capped, vec![4096, 4088, 16]);
18162 assert!(capped.iter().all(|&rows| rows <= 4096));
18163 assert_eq!(
18164 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
18165 vec![4100],
18166 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
18167 );
18168 }
18169
18170 #[test]
18171 fn dynamic_schedule_matches_registered_shapes() {
18172 let cases = [
18173 (461, vec![64, 141, 132, 124]),
18174 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
18175 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
18176 ];
18177 for (t, expected) in cases {
18178 let chunk = auto_chunk(t);
18179 let fixed = fixed_prime_chunk_ranges(t, chunk);
18180 assert_eq!(
18181 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
18182 expected
18183 );
18184 }
18185 }
18186
18187 #[test]
18188 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
18189 for t in 256..=8192 {
18190 let chunk = auto_chunk(t);
18191 let fixed = fixed_prime_chunk_ranges(t, chunk);
18192 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
18193 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
18194 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
18195 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
18196 for pair in dynamic.windows(2) {
18197 assert_eq!(pair[0].1, pair[1].0, "T={t}");
18198 }
18199 assert!(
18200 dynamic
18201 .iter()
18202 .all(|(start, end)| end - start >= PRIME_MIN_T),
18203 "T={t} sizes={:?}",
18204 sizes(&dynamic)
18205 );
18206 if dynamic.len() >= 3 {
18207 let chunk_sizes = sizes(&dynamic);
18208 assert!(
18209 chunk_sizes[0] < chunk_sizes[1],
18210 "T={t} sizes={chunk_sizes:?}"
18211 );
18212 assert!(
18213 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
18214 "T={t} sizes={chunk_sizes:?}"
18215 );
18216 }
18217 }
18218 }
18219}
18220
18221#[cfg(test)]
18222mod page_prefetch_tests {
18223 use super::{
18224 grouped_worker_prefetch_position, page_prefetch_positions,
18225 page_prefetch_window_from_values, worker_prefetch_positions,
18226 };
18227
18228 #[test]
18229 fn page_prefetch_window_keeps_existing_opt_in_default() {
18230 assert_eq!(page_prefetch_window_from_values(false, None), 0);
18231 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
18232 assert_eq!(page_prefetch_window_from_values(true, None), 1);
18233 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
18234 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
18235 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
18236 }
18237
18238 #[test]
18239 fn rolling_page_prefetch_advises_each_future_expert_once() {
18240 let advised: Vec<_> = (0..7)
18241 .flat_map(|position| page_prefetch_positions(position, 7, 3))
18242 .collect();
18243 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
18244
18245 let one_ahead: Vec<_> = (0..4)
18246 .flat_map(|position| page_prefetch_positions(position, 4, 1))
18247 .collect();
18248 assert_eq!(one_ahead, vec![1, 2, 3]);
18249 assert!(page_prefetch_positions(0, 4, 0).is_empty());
18250 }
18251
18252 #[test]
18253 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
18254 assert_eq!(grouped_worker_prefetch_position(0, None), None);
18255 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
18256 .chain(
18257 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
18258 )
18259 .collect();
18260 assert_eq!(positions, vec![0, 1, 2, 3]);
18261 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
18262 }
18263
18264 #[test]
18265 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
18266 let queued: Vec<_> = (0..8)
18267 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
18268 .collect();
18269 assert_eq!(queued, (0..8).collect::<Vec<_>>());
18270
18271 let one_at_a_time: Vec<_> = (0..4)
18272 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
18273 .collect();
18274 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
18275 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
18276 }
18277}
18278
18279pub struct G4DcSlots {
18280 x: CudaSlice<f32>,
18281 xn: CudaSlice<f32>,
18282 cur: CudaSlice<f32>,
18283 hq: CudaSlice<i8>,
18284 hd_: CudaSlice<f32>,
18285 q0: CudaSlice<f32>,
18286 k0: CudaSlice<f32>,
18287 v0: CudaSlice<f32>,
18288 q: CudaSlice<f32>,
18289 k: CudaSlice<f32>,
18290 v: CudaSlice<f32>,
18291 attn: CudaSlice<f32>,
18292 o: CudaSlice<f32>,
18293 attn_out: CudaSlice<f32>,
18294 zsh: CudaSlice<f32>,
18295 zq: CudaSlice<i8>,
18296 zd: CudaSlice<f32>,
18297 gate: CudaSlice<f32>,
18298 up: CudaSlice<f32>,
18299 act: CudaSlice<f32>,
18300 actq: CudaSlice<i8>,
18301 actd: CudaSlice<f32>,
18302 f0: CudaSlice<f32>,
18303 sn: CudaSlice<f32>,
18304 hn: CudaSlice<f32>,
18305 logits: CudaSlice<f32>,
18306}
18307
18308pub struct Step35TokenGraphState {
18313 pub graphs: Vec<(usize, crate::tp::TokenGraph)>,
18315 pub token_d: cudarc::driver::CudaSlice<u32>,
18316 pub pos_d: cudarc::driver::CudaSlice<i32>,
18317 pub logits_stage: cudarc::driver::CudaSlice<f32>,
18318 pub x: cudarc::driver::CudaSlice<f32>,
18323 pub x1: cudarc::driver::CudaSlice<f32>,
18324 pub mixed_stage: cudarc::driver::CudaSlice<f32>,
18325 pub sh_stage: cudarc::driver::CudaSlice<f32>,
18326 pub k_shadow_stage: cudarc::driver::CudaSlice<f32>,
18327 pub v_shadow_stage: cudarc::driver::CudaSlice<f32>,
18328 pub router_logits: cudarc::driver::CudaSlice<f32>,
18331 pub shexp_gate: cudarc::driver::CudaSlice<f32>,
18332 pub shexp_up: cudarc::driver::CudaSlice<f32>,
18333 pub shexp_act: cudarc::driver::CudaSlice<f32>,
18334 pub gate_sig: cudarc::driver::CudaSlice<f32>,
18335 pub dense_z: cudarc::driver::CudaSlice<f32>,
18336 pub dense_gate: cudarc::driver::CudaSlice<f32>,
18337 pub dense_up: cudarc::driver::CudaSlice<f32>,
18338 pub dense_act: cudarc::driver::CudaSlice<f32>,
18339 pub hn: cudarc::driver::CudaSlice<f32>,
18340 pub probe_mixed: cudarc::driver::CudaSlice<f32>,
18343 pub probe_x: cudarc::driver::CudaSlice<f32>,
18344 pub token_hist: cudarc::driver::CudaSlice<u32>,
18347 pub hist_idx: cudarc::driver::CudaSlice<i32>,
18348}
18349
18350impl HybridModel {
18351 pub(crate) fn step35_token_graph_step(
18362 &self,
18363 e: &Engine,
18364 token: u32,
18365 cache: &mut Cache,
18366 ) -> Result<Option<(Vec<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
18367 if !self.uses_sliding_gated_moe_program()
18368 || !crate::tp::step_tp_graph_enabled()?
18369 || !crate::tp::step_tp_dcw_enabled()?
18370 || !crate::tp::step_tp_qkv_fused_enabled()?
18371 || !crate::tp::step_tp_dev_router_enabled()?
18372 || !crate::tp::step_nvfp4_dev_routes_enabled()?
18373 {
18374 return Ok(None);
18375 }
18376 let n_embd = self.cfg.n_embd as usize;
18377 let n_vocab = self.cfg.n_vocab as usize;
18378 let eps = self.cfg.rms_eps;
18379 let n_layers = self.layers.len();
18380 let pos = cache.pos;
18381 let staged_next = pos + 1;
18382 if staged_next < 96 {
18383 return Ok(None); }
18385
18386 for il in 0..n_layers {
18389 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
18390 return Ok(None); };
18392 if tp_kv.peek_append_ring(1)?.1 {
18393 return Ok(None);
18394 }
18395 }
18396
18397 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
18400 if !fa_vec {
18401 return Ok(None);
18402 }
18403 let sp = crate::fa_split_keys(staged_next, 8);
18404 let bucket_max = (n_splits * sp).max(staged_next);
18405
18406 let mut state_guard = self
18407 .step35_token_graph
18408 .lock()
18409 .map_err(|_| "step35 token graph lock is poisoned")?;
18410 if state_guard.is_none() {
18411 let _main = e.gpu.enter_main()?;
18412 let n_expert = self
18413 .cfg
18414 .moe
18415 .as_ref()
18416 .map(|m| m.expert_count as usize)
18417 .unwrap_or(0);
18418 let n_ff_sh = self
18419 .layers
18420 .iter()
18421 .find_map(|l| match &l.ffn {
18422 crate::hybrid::Ffn::Moe(m) => m.gate_shexp.as_ref().map(|g| g.out_features()),
18423 _ => None,
18424 })
18425 .unwrap_or(0);
18426 let n_ff_dense = self
18427 .layers
18428 .iter()
18429 .find_map(|l| match &l.ffn {
18430 crate::hybrid::Ffn::Dense { ffn_gate, .. } => Some(ffn_gate.out_features()),
18431 _ => None,
18432 })
18433 .unwrap_or(0);
18434 *state_guard = Some(Step35TokenGraphState {
18435 graphs: Vec::new(),
18436 token_d: e.stream().clone_htod(&[0u32])?,
18437 pos_d: e.htod_i32(&[pos as i32])?,
18438 logits_stage: e.htod(&vec![0.0f32; n_vocab])?,
18439 x: e.htod(&vec![0.0f32; n_embd])?,
18440 x1: e.htod(&vec![0.0f32; n_embd])?,
18441 mixed_stage: e.htod(&vec![0.0f32; n_embd])?,
18442 sh_stage: e.htod(&vec![0.0f32; n_embd])?,
18443 k_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
18444 v_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
18445 router_logits: e.htod(&vec![0.0f32; n_expert.max(1)])?,
18446 shexp_gate: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18447 shexp_up: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18448 shexp_act: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18449 gate_sig: e.htod(&vec![1.0f32; 1])?,
18450 dense_z: e.htod(&vec![0.0f32; n_embd])?,
18451 dense_gate: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18452 dense_up: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18453 dense_act: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18454 hn: e.htod(&vec![0.0f32; n_embd])?,
18455 probe_mixed: e.htod(&vec![0.0f32; n_embd])?,
18456 probe_x: e.htod(&vec![0.0f32; n_embd])?,
18457 token_hist: e.stream().clone_htod(&[0u32; 16])?,
18458 hist_idx: e.htod_i32(&[0])?,
18459 });
18460 }
18461 let state = state_guard.as_mut().expect("state armed above");
18462 {
18466 let _main = e.gpu.enter_main()?;
18467 let Step35TokenGraphState {
18468 logits_stage,
18469 token_d,
18470 ..
18471 } = &mut *state;
18472 e.argmax_token_device_into(logits_stage, token_d, n_vocab)?;
18473 }
18474
18475 if state.graphs.is_empty() {
18480 self.step35_token_graph_build(e, cache, state, bucket_max)?;
18483 }
18484 {
18485 let (b, g) = state.graphs.first_mut().expect("graph built above");
18486 if *b != bucket_max {
18487 g.retarget_bucket(bucket_max)?;
18488 *b = bucket_max;
18489 }
18490 }
18491 let graph = state
18492 .graphs
18493 .first()
18494 .map(|(_, g)| g)
18495 .expect("graph built above");
18496
18497 let tg_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
18498 let t_fence = tg_timing.then(std::time::Instant::now);
18499 {
18504 let fa0 = match &self.layers[0].mixer {
18505 Mixer::Full(fa) => fa,
18506 _ => return Err("step35 token graph expects full-attention layers".into()),
18507 };
18508 let tp0 = fa0
18509 .step_tp_qkv
18510 .as_ref()
18511 .ok_or("step35 token graph lost its TP state")?;
18512 for rank in 0..tp0.runtime.devices().len() {
18513 let engine = tp0
18514 .runtime
18515 .rank_engine(rank)
18516 .ok_or("step35 token graph lost a rank engine")?;
18517 let _main = engine.gpu.enter_main()?;
18518 engine.stream().synchronize()?;
18519 }
18520 }
18521
18522 {
18524 let _main = e.gpu.enter_main()?;
18525 e.set_u32_one(&mut state.token_d, token)?;
18526 e.set_i32_one(&mut state.pos_d, pos as i32)?;
18527 }
18528 let t_launch = tg_timing.then(std::time::Instant::now);
18529 graph.launch(e)?;
18530 let t_book = tg_timing.then(std::time::Instant::now);
18531 for il in 0..n_layers {
18536 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
18537 let transaction = tp_kv.begin_transaction()?;
18538 let fa = match &self.layers[il].mixer {
18539 Mixer::Full(fa) => fa,
18540 _ => return Err("step35 token graph expects full-attention layers".into()),
18541 };
18542 let tp = fa
18543 .step_tp_qkv
18544 .as_ref()
18545 .ok_or("step35 token graph lost its TP state")?;
18546 let empty: [CudaSlice<f32>; 0] = [];
18549 tp.runtime.append_tp_kv_transaction_inner(
18550 tp_kv,
18551 transaction,
18552 &empty,
18553 &empty,
18554 1,
18555 true,
18556 )?;
18557 tp.runtime
18558 .commit_tp_kv_transaction_external(tp_kv, transaction, 1)?;
18559 if let Some(local) = cache.kv[il].as_mut() {
18561 local.len = pos + 1;
18562 let _main = e.gpu.enter_main()?;
18563 e.set_i32_one(&mut local.len_d, (pos + 1) as i32)?;
18564 }
18565 }
18566 cache.pos = pos + 1;
18567 let t_sync = tg_timing.then(std::time::Instant::now);
18568 let (logits, h_seed) = {
18569 let _main = e.gpu.enter_main()?;
18570 e.stream().synchronize()?;
18571 (e.dtoh(&state.logits_stage)?, e.clone_dtod(&state.x)?)
18572 };
18573 if let (Some(f), Some(l), Some(b), Some(sy)) = (t_fence, t_launch, t_book, t_sync) {
18574 use std::sync::atomic::{AtomicU64, Ordering};
18575 static NS: [AtomicU64; 5] = [
18576 AtomicU64::new(0),
18577 AtomicU64::new(0),
18578 AtomicU64::new(0),
18579 AtomicU64::new(0),
18580 AtomicU64::new(0),
18581 ];
18582 static CALLS: AtomicU64 = AtomicU64::new(0);
18583 let now = std::time::Instant::now();
18584 NS[0].fetch_add((l - f).as_nanos() as u64, Ordering::Relaxed); NS[1].fetch_add((b - l).as_nanos() as u64, Ordering::Relaxed); NS[2].fetch_add((sy - b).as_nanos() as u64, Ordering::Relaxed); NS[3].fetch_add((now - sy).as_nanos() as u64, Ordering::Relaxed); NS[4].fetch_add((now - f).as_nanos() as u64, Ordering::Relaxed); let calls = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
18590 if calls % 100 == 0 {
18591 let avg = |i: usize| NS[i].load(Ordering::Relaxed) as f64 / calls as f64 / 1e3;
18592 eprintln!(
18593 "[tg-timing] calls={calls} fence_us={:.0} launch_us={:.0} book_us={:.0} \
18594 syncdtoh_us={:.0} total_us={:.0}",
18595 avg(0),
18596 avg(1),
18597 avg(2),
18598 avg(3),
18599 avg(4)
18600 );
18601 }
18602 }
18603 if std::env::var("MEMRA_TG_PROBE_LAYER").is_ok() {
18605 use std::io::Write;
18606 let (pm, px) = {
18607 let _main = e.gpu.enter_main()?;
18608 (e.dtoh(&state.probe_mixed)?, e.dtoh(&state.probe_x)?)
18609 };
18610 for (path, data) in [
18611 ("/root/tg-probe-mixed.bin", &pm),
18612 ("/root/tg-probe-x.bin", &px),
18613 ] {
18614 let mut fo = std::fs::OpenOptions::new()
18615 .create(true)
18616 .append(true)
18617 .open(path)?;
18618 for v in data {
18619 fo.write_all(&v.to_le_bytes())?;
18620 }
18621 }
18622 }
18623 if let Ok(path) = std::env::var("MEMRA_DUMP_HN") {
18626 let hh = {
18627 let _main = e.gpu.enter_main()?;
18628 e.dtoh(&state.hn)?
18629 };
18630 use std::io::Write;
18631 let mut fo = std::fs::OpenOptions::new()
18632 .create(true)
18633 .append(true)
18634 .open(path)?;
18635 for v in &hh {
18636 fo.write_all(&v.to_le_bytes())?;
18637 }
18638 }
18639 if std::env::var("MEMRA_STEP_TP_GRAPH_DEBUG").as_deref() == Ok("1") {
18642 for il in [0usize, 1, 44] {
18643 let tp_kv = cache.tp_kv[il].as_ref().expect("eligibility checked above");
18644 let host_len = tp_kv.staged_len();
18645 let fa = match &self.layers[il].mixer {
18646 Mixer::Full(fa) => fa,
18647 _ => continue,
18648 };
18649 let tp = fa
18650 .step_tp_qkv
18651 .as_ref()
18652 .ok_or("step35 token graph lost its TP state")?;
18653 for rank in 0..tp.runtime.devices().len() {
18654 let engine = tp
18655 .runtime
18656 .rank_engine(rank)
18657 .ok_or("step35 token graph lost a rank engine")?;
18658 let rank_cache = tp_kv.rank(rank).ok_or("debug rank cache missing")?;
18659 let _main = engine.gpu.enter_main()?;
18660 engine.stream().synchronize()?;
18661 let len_d = engine.dtoh_i32_one(rank_cache.len_d())?;
18662 let base_d = match rank_cache.base_d() {
18663 Some(b) => engine.dtoh_i32_one(b)?,
18664 None => -1,
18665 };
18666 eprintln!(
18667 "[graph-debug] pos={pos} il={il} rank={rank} host_len={host_len} \
18668 len_d={len_d} base_d={base_d}"
18669 );
18670 }
18671 }
18672 }
18673 Ok(Some((logits, h_seed)))
18674 }
18675
18676 pub(crate) fn head_split_matvec(
18682 &self,
18683 e: &Engine,
18684 hn: &CudaSlice<f32>,
18685 ) -> Result<Option<Vec<f32>>, Box<dyn std::error::Error>> {
18686 if self.head_split_fill_device(e, hn)?.is_none() {
18687 return Ok(None);
18688 }
18689 let guard = HEAD_SPLIT_WS
18690 .lock()
18691 .map_err(|_| "head split lock is poisoned")?;
18692 let ws = guard.as_ref().expect("filled above");
18693 let _main = e.gpu.enter_main()?;
18694 Ok(Some(e.dtoh(&ws.logits_e)?))
18695 }
18696
18697 fn head_split_fill_device(
18701 &self,
18702 e: &Engine,
18703 hn: &CudaSlice<f32>,
18704 ) -> Result<Option<()>, Box<dyn std::error::Error>> {
18705 use cudarc::driver::DevicePtr;
18706 let crate::model::GpuTensor::FloatBf16 { data: head, .. } = &self.output else {
18707 return Ok(None);
18708 };
18709 let Some(rank1) = self.layers.first().and_then(|l| match &l.mixer {
18710 Mixer::Full(fa) => fa
18711 .step_tp_qkv
18712 .as_ref()
18713 .and_then(|tp| tp.runtime.rank_engine(1)),
18714 _ => None,
18715 }) else {
18716 return Ok(None);
18717 };
18718 let n_embd = self.cfg.n_embd as usize;
18719 let n_vocab = self.cfg.n_vocab as usize;
18720 let half = n_vocab / 2;
18721 let mut guard = HEAD_SPLIT_WS
18722 .lock()
18723 .map_err(|_| "head split lock is poisoned")?;
18724 let pin = {
18725 let _main = e.gpu.enter_main()?;
18726 let stream = e.stream();
18727 let (ptr, _g) = head.device_ptr(&stream);
18728 ptr as u64
18729 };
18730 if guard.as_ref().is_none_or(|ws| ws.pin != pin) {
18731 let hi_rows = n_vocab - half;
18733 let (w1, hn1, y1, ev_done) = {
18734 let _r1 = rank1.gpu.enter_main()?;
18735 (
18736 rank1.alloc_u8_uninit(hi_rows * n_embd * 2)?,
18737 rank1.htod(&vec![0.0f32; n_embd])?,
18738 rank1.htod(&vec![0.0f32; hi_rows])?,
18739 rank1.ctx().new_event(None)?,
18740 )
18741 };
18742 {
18743 use cudarc::driver::sys;
18744 let src = pin + (half * n_embd * 2) as u64;
18745 let dst = {
18746 let _r1 = rank1.gpu.enter_main()?;
18747 let rstream = rank1.stream();
18748 let (d, _g) = w1.device_ptr(&rstream);
18749 d as u64
18750 };
18751 let _r1 = rank1.gpu.enter_main()?;
18752 let r = unsafe {
18753 sys::cuMemcpyAsync(
18754 dst as sys::CUdeviceptr,
18755 src as sys::CUdeviceptr,
18756 hi_rows * n_embd * 2,
18757 rank1.stream().cu_stream() as sys::CUstream,
18758 )
18759 };
18760 if r != sys::CUresult::CUDA_SUCCESS {
18761 return Err(format!("head split replica upload: {r:?}").into());
18762 }
18763 rank1.stream().synchronize()?;
18764 }
18765 let (logits_e, ev_hn) = {
18766 let _main = e.gpu.enter_main()?;
18767 (e.htod(&vec![0.0f32; n_vocab])?, e.ctx().new_event(None)?)
18768 };
18769 let (raw_hn1, raw_y1) = {
18770 let _r1 = rank1.gpu.enter_main()?;
18771 let rstream = rank1.stream();
18772 let (a, _g0) = hn1.device_ptr(&rstream);
18773 let (b, _g1) = y1.device_ptr(&rstream);
18774 (a as u64, b as u64)
18775 };
18776 let raw_logits_hi = {
18777 let _main = e.gpu.enter_main()?;
18778 let stream = e.stream();
18779 let (l, _g) = logits_e.device_ptr(&stream);
18780 l as u64 + (half * 4) as u64
18781 };
18782 *guard = Some(HeadSplit {
18783 pin,
18784 w1,
18785 hn1,
18786 y1,
18787 logits_e,
18788 ev_hn,
18789 ev_done,
18790 raw_hn1,
18791 raw_y1,
18792 raw_logits_hi,
18793 });
18794 }
18795 let ws = guard.as_mut().expect("armed above");
18796 let hi_rows = n_vocab - half;
18797 let raw_hn = {
18799 let _main = e.gpu.enter_main()?;
18800 let stream = e.stream();
18801 let (h, _g) = hn.device_ptr(&stream);
18802 ws.ev_hn.record(&stream)?;
18803 h as u64
18804 };
18805 {
18806 let _r1 = rank1.gpu.enter_main()?;
18807 rank1.stream().wait(&ws.ev_hn)?;
18808 crate::tp::raw_copy_bytes(ws.raw_hn1, raw_hn, n_embd * 4, rank1)?;
18809 let HeadSplit { w1, hn1, y1, .. } = &mut *ws;
18810 rank1.matvec_bf16_into(w1, hn1, y1, n_embd, hi_rows)?;
18811 crate::tp::raw_copy_bytes(ws.raw_logits_hi, ws.raw_y1, hi_rows * 4, rank1)?;
18812 ws.ev_done.record(&rank1.stream())?;
18813 }
18814 {
18815 let _main = e.gpu.enter_main()?;
18816 let head_lo = head.slice(0..half * n_embd * 2);
18817 let HeadSplit { logits_e, .. } = &mut *ws;
18818 e.matvec_bf16_view_into(&head_lo, hn, logits_e, n_embd, half)?;
18820 e.stream().wait(&ws.ev_done)?;
18821 Ok(Some(()))
18822 }
18823 }
18824
18825 pub(crate) fn head_split_argmax_device(
18830 &self,
18831 e: &Engine,
18832 hn: &CudaSlice<f32>,
18833 token_d: &mut CudaSlice<u32>,
18834 ) -> Result<bool, Box<dyn std::error::Error>> {
18835 if self.head_split_fill_device(e, hn)?.is_none() {
18836 return Ok(false);
18837 }
18838 let n_vocab = self.cfg.n_vocab as usize;
18839 let guard = HEAD_SPLIT_WS
18840 .lock()
18841 .map_err(|_| "head split lock is poisoned")?;
18842 let ws = guard.as_ref().expect("filled above");
18843 let _main = e.gpu.enter_main()?;
18844 e.argmax_token_device_into(&ws.logits_e, token_d, n_vocab)?;
18845 Ok(true)
18846 }
18847
18848 pub(crate) fn head_split_logits_dtoh(
18851 &self,
18852 e: &Engine,
18853 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
18854 let guard = HEAD_SPLIT_WS
18855 .lock()
18856 .map_err(|_| "head split lock is poisoned")?;
18857 let ws = guard.as_ref().ok_or("head split logits not armed")?;
18858 let _main = e.gpu.enter_main()?;
18859 Ok(e.dtoh(&ws.logits_e)?)
18860 }
18861
18862 pub fn step35_token_graph_chunk(
18871 &self,
18872 e: &Engine,
18873 token: u32,
18874 k_target: usize,
18875 cache: &mut Cache,
18876 ) -> Result<Option<(Vec<u32>, Vec<f32>)>, Box<dyn std::error::Error>> {
18877 if !self.uses_sliding_gated_moe_program()
18878 || !crate::tp::step_tp_graph_enabled()?
18879 || !crate::tp::step_tp_dcw_enabled()?
18880 || !crate::tp::step_tp_qkv_fused_enabled()?
18881 || !crate::tp::step_tp_dev_router_enabled()?
18882 || !crate::tp::step_nvfp4_dev_routes_enabled()?
18883 {
18884 return Ok(None);
18885 }
18886 let n_layers = self.layers.len();
18887 let pos = cache.pos;
18888 let staged_next = pos + 1;
18889 if staged_next < 96 {
18890 return Ok(None);
18891 }
18892 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
18895 if !fa_vec {
18896 return Ok(None);
18897 }
18898 let sp = crate::fa_split_keys(staged_next, 8);
18899 let bucket_max = (n_splits * sp).max(staged_next);
18900 let to_boundary = bucket_max.saturating_sub(staged_next) + 1;
18901 let mut k = k_target.min(to_boundary).min(16);
18902 if k < 2 {
18903 return Ok(None);
18904 }
18905 for il in 0..n_layers {
18907 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
18908 return Ok(None);
18909 };
18910 while k >= 2 && tp_kv.peek_append_ring(k)?.1 {
18911 k -= 1;
18912 }
18913 if k < 2 {
18914 return Ok(None);
18915 }
18916 }
18917
18918 let mut state_guard = self
18919 .step35_token_graph
18920 .lock()
18921 .map_err(|_| "step35 token graph lock is poisoned")?;
18922 let Some(state) = state_guard.as_mut() else {
18923 return Ok(None); };
18925 if state.graphs.is_empty() {
18926 return Ok(None);
18927 }
18928 {
18929 let (b, g) = state.graphs.first_mut().expect("checked above");
18930 if *b != bucket_max {
18931 g.retarget_bucket(bucket_max)?;
18932 *b = bucket_max;
18933 }
18934 }
18935 let graph = state.graphs.first().map(|(_, g)| g).expect("checked above");
18936
18937 {
18939 let fa0 = match &self.layers[0].mixer {
18940 Mixer::Full(fa) => fa,
18941 _ => return Err("step35 token graph expects full-attention layers".into()),
18942 };
18943 let tp0 = fa0
18944 .step_tp_qkv
18945 .as_ref()
18946 .ok_or("step35 token graph lost its TP state")?;
18947 for rank in 0..tp0.runtime.devices().len() {
18948 let engine = tp0
18949 .runtime
18950 .rank_engine(rank)
18951 .ok_or("step35 token graph lost a rank engine")?;
18952 let _main = engine.gpu.enter_main()?;
18953 engine.stream().synchronize()?;
18954 }
18955 }
18956
18957 {
18960 let _main = e.gpu.enter_main()?;
18961 e.set_u32_one(&mut state.token_d, token)?;
18962 e.set_i32_one(&mut state.pos_d, pos as i32)?;
18963 e.set_i32_one(&mut state.hist_idx, 0)?;
18964 }
18965 for _ in 0..k {
18966 graph.launch(e)?;
18967 }
18968 for il in 0..n_layers {
18970 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
18971 let transaction = tp_kv.begin_transaction()?;
18972 let fa = match &self.layers[il].mixer {
18973 Mixer::Full(fa) => fa,
18974 _ => return Err("step35 token graph expects full-attention layers".into()),
18975 };
18976 let tp = fa
18977 .step_tp_qkv
18978 .as_ref()
18979 .ok_or("step35 token graph lost its TP state")?;
18980 let empty: [CudaSlice<f32>; 0] = [];
18981 tp.runtime.append_tp_kv_transaction_inner(
18982 tp_kv,
18983 transaction,
18984 &empty,
18985 &empty,
18986 k,
18987 true,
18988 )?;
18989 tp.runtime
18990 .commit_tp_kv_transaction_external(tp_kv, transaction, k)?;
18991 if let Some(local) = cache.kv[il].as_mut() {
18992 local.len = pos + k;
18993 let _main = e.gpu.enter_main()?;
18994 e.set_i32_one(&mut local.len_d, (pos + k) as i32)?;
18995 }
18996 }
18997 cache.pos = pos + k;
18998 let (hist, logits) = {
18999 let _main = e.gpu.enter_main()?;
19000 e.stream().synchronize()?;
19001 (e.dtoh_u32(&state.token_hist)?, e.dtoh(&state.logits_stage)?)
19002 };
19003 Ok(Some((hist[..k].to_vec(), logits)))
19004 }
19005}
19006
19007impl HybridModel {
19008 #[allow(clippy::too_many_arguments)]
19013 fn step35_token_graph_build(
19014 &self,
19015 e: &Engine,
19016 cache: &mut Cache,
19017 state: &mut Step35TokenGraphState,
19018 bucket_max: usize,
19019 ) -> Result<(), Box<dyn std::error::Error>> {
19020 use cudarc::driver::DevicePtr;
19021 let n_embd = self.cfg.n_embd as usize;
19022 let eps = self.cfg.rms_eps;
19023 let n_layers = self.layers.len();
19024 let started = std::time::Instant::now();
19025 if !crate::router_kernel_on() {
19026 return Err(
19027 "step35 token graph requires the router kernel (MEMRA_ROUTER_KERNEL=0)".into(),
19028 );
19029 }
19030 if !Engine::bf16_mmv_on() || n_embd % 8 != 0 {
19031 return Err("step35 token graph requires MEMRA_BF16_MMV bf16-resident matvecs".into());
19032 }
19033
19034 let embd_gpu = self
19036 .embd_gpu_try(e)
19037 .ok_or("step35 token graph could not upload the device embed table")?;
19038 let embd_qtype = match self.embd.ggml_type {
19039 memra_gguf::GgmlType::BF16 => crate::QT_BF16,
19040 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
19041 other => return Err(format!("token graph embed dtype {other:?} unhandled").into()),
19042 };
19043 let embd_row_bytes = self.embd.raw.len() / self.cfg.n_vocab as usize;
19044
19045 let (p_mixed, p_kshadow, p_vshadow) = {
19047 let _main = e.gpu.enter_main()?;
19048 let stream = e.stream();
19049 let (a, _g) = state.mixed_stage.device_ptr(&stream);
19050 let (b, _g) = state.k_shadow_stage.device_ptr(&stream);
19051 let (c, _g) = state.v_shadow_stage.device_ptr(&stream);
19052 (a as u64, b as u64, c as u64)
19053 };
19054
19055 crate::tp::token_graph_build_begin()?;
19056 let mut group_id: u32 = 0;
19057 for il in 0..n_layers {
19058 let layer = &self.layers[il];
19059 let fa = match &layer.mixer {
19060 Mixer::Full(fa) => fa,
19061 _ => return Err("step35 token graph expects full-attention layers".into()),
19062 };
19063 let tp = fa
19064 .step_tp_qkv
19065 .as_ref()
19066 .ok_or("step35 token graph lost its TP state")?;
19067 let attention = tp
19068 .attention
19069 .as_ref()
19070 .ok_or("step35 token graph lost its attention aux")?;
19071 let geometry = self.step35_geom(il);
19072 let window = geometry.window.map(|w| w as usize);
19073 let head_dim = geometry.head_dim_k as usize;
19074 let heads = geometry.n_head as usize;
19075 let kv_heads = geometry.n_head_kv as usize;
19076 let ranks = tp.runtime.devices().len();
19077 let local_heads = heads / ranks;
19078 let local_kv_heads = kv_heads / ranks;
19079 let layer_bucket = window.map(|w| bucket_max.min(w)).unwrap_or(bucket_max);
19080 let use_gate_shards =
19081 attention.gate_shards.is_some() || attention.gate_shards_bf16.is_some();
19082 if !use_gate_shards {
19083 return Err("step35 token graph requires the fused gate shards".into());
19084 }
19085
19086 let ws_index = tp
19087 .runtime
19088 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
19089 let ws_mutex = tp.runtime.decode_v2_workspace();
19090 let mut ws_guard = ws_mutex
19091 .lock()
19092 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
19093 let ws = ws_guard
19094 .get_mut(ws_index)
19095 .ok_or("step TP decode v2 workspace missing after ensure")?;
19096 tp.runtime
19097 .decode_v2_arm_token_mirrors(ws, p_mixed, (p_kshadow, p_vshadow))?;
19098 let mut rope_freqs = Vec::with_capacity(ranks);
19099 for rank in 0..ranks {
19100 let engine = tp
19101 .runtime
19102 .rank_engine(rank)
19103 .ok_or("step35 token graph lost a rank engine")?;
19104 rope_freqs.push(if geometry.rope_factors {
19105 self.step35_aux
19106 .as_ref()
19107 .and_then(|aux| aux.rope_freqs(engine))
19108 } else {
19109 None
19110 });
19111 }
19112 let gate_shards_arg = if let Some(shards) = attention.gate_shards.as_deref() {
19113 Some(crate::tp::StepTpGateShards::F32(shards))
19114 } else {
19115 attention
19116 .gate_shards_bf16
19117 .as_deref()
19118 .map(crate::tp::StepTpGateShards::Bf16)
19119 };
19120
19121 let decode_input = attention
19123 .decode_input
19124 .as_ref()
19125 .ok_or("step35 token graph requires the replicated decode input")?;
19126 let mut decode_input = decode_input
19127 .lock()
19128 .map_err(|_| "replicated decode input lock is poisoned")?;
19129 if ws.h_stage.is_none() {
19131 return Err(
19132 "step35 token graph requires the stage flow armed (run eager dcw first)".into(),
19133 );
19134 }
19135 {
19136 let state_x = &mut state.x;
19137 let token_d = &state.token_d;
19138 let pos_d = &state.pos_d;
19139 crate::tp::graph_section(e, None, || {
19140 let _main = e.gpu.enter_main()?;
19141 if il == 0 {
19142 e.embed_gather_device_into(
19143 embd_gpu,
19144 token_d,
19145 state_x,
19146 n_embd,
19147 embd_qtype,
19148 embd_row_bytes,
19149 )?;
19150 }
19151 {
19152 let h_stage = ws.h_stage.as_mut().expect("stage armed checked above");
19153 e.rms_norm(
19154 state_x,
19155 layer.attn_norm.float_data(),
19156 h_stage,
19157 n_embd,
19158 1,
19159 eps,
19160 )?;
19161 }
19162 {
19163 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
19164 let mut dst = pos_stage.slice_mut(0..1);
19165 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
19166 }
19167 Ok(())
19168 })?;
19169 }
19170
19171 group_id += 1;
19173 for rank in 0..ranks {
19174 let engine = tp
19175 .runtime
19176 .rank_engine(rank)
19177 .ok_or("step35 token graph lost a rank engine")?;
19178 {
19179 let ceiling = window
19184 .map(|w| cache.max_ctx.min(w))
19185 .unwrap_or(cache.max_ctx);
19186 let _main = engine.gpu.enter_main()?;
19187 engine.fa_dcw_pool_ensure(
19188 head_dim,
19189 local_heads,
19190 local_kv_heads,
19191 ceiling.min(2048),
19192 )?;
19193 engine.fa_dcw_pool_ensure(head_dim, local_heads, local_kv_heads, ceiling)?;
19194 engine.fa_dcw_pool_ensure(
19195 head_dim,
19196 local_heads,
19197 local_kv_heads,
19198 layer_bucket,
19199 )?;
19200 }
19201 let runtime = &tp.runtime;
19202 let q_norm = &attention.q_norm;
19203 let k_norm = &attention.k_norm;
19204 let gate_ref = gate_shards_arg.as_ref();
19205 crate::tp::graph_section(engine, Some(group_id), || {
19206 runtime.decode_v2_input_qkv_rank(
19207 ws,
19208 &state.pos_d,
19209 &mut decode_input,
19210 &tp.q,
19211 &tp.k,
19212 &tp.v,
19213 q_norm,
19214 k_norm,
19215 head_dim,
19216 geometry.n_rot as usize,
19217 geometry.rope_base,
19218 &rope_freqs,
19219 eps,
19220 gate_ref,
19221 true,
19222 false,
19223 rank,
19224 None,
19225 )?;
19226 let distributed = cache.tp_kv[il]
19229 .as_mut()
19230 .ok_or("step35 token graph lost a TP cache")?;
19231 let (kv_dim_k, kv_dim_v) = (distributed.kv_dim_k(), distributed.kv_dim_v());
19232 let (ktb, vtb) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
19233 let capacity = distributed.physical_capacity();
19234 {
19235 let rank_cache = distributed
19236 .rank_mut(rank)
19237 .ok_or("step35 token graph lost a rank cache")?;
19238 let (k_plane, v_plane, len_d, base_d) =
19239 rank_cache.planes_and_counters_mut();
19240 engine.append_kv_quantized_dcw(
19241 &ws.k[rank],
19242 &ws.v_raw[rank],
19243 k_plane,
19244 v_plane,
19245 len_d,
19246 base_d,
19247 kv_dim_k,
19248 kv_dim_v,
19249 ktb,
19250 vtb,
19251 )?;
19252 }
19253 {
19254 let rank_cache = distributed
19255 .rank_mut(rank)
19256 .ok_or("step35 token graph lost a rank cache")?;
19257 engine.inc_i32(rank_cache.len_d_mut())?;
19258 }
19259 let rank_cache = distributed
19260 .rank(rank)
19261 .ok_or("step35 token graph lost a rank cache")?;
19262 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * ktb);
19263 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * vtb);
19264 engine.fa_decode_dcw(
19269 &ws.q[rank],
19270 &k_ring,
19271 &v_ring,
19272 &mut ws.attn_out[rank],
19273 head_dim,
19274 local_heads,
19275 local_kv_heads,
19276 rank_cache.len_d(),
19277 rank_cache.base_d(),
19278 window.unwrap_or(0),
19279 layer_bucket,
19280 geometry.attention_scale(),
19281 ktb,
19282 vtb,
19283 None,
19284 )?;
19285 engine.attn_head_gate(
19286 &ws.attn_out[rank],
19287 &ws.gate[rank],
19288 &mut ws.gated[rank],
19289 None,
19290 head_dim,
19291 local_heads,
19292 1,
19293 )?;
19294 runtime.decode_v2_finish_rank_partial(ws, &tp.o, true, rank)?;
19295 Ok(())
19296 })?;
19297 }
19298
19299 {
19301 let root = tp
19302 .runtime
19303 .rank_engine(0)
19304 .ok_or("step35 token graph lost the root engine")?;
19305 let runtime = &tp.runtime;
19306 crate::tp::graph_section(root, None, || runtime.decode_v2_finish_root_fused(ws))?;
19307 }
19308 drop(ws_guard);
19309 drop(decode_input);
19310
19311 let probe_layer: Option<usize> = std::env::var("MEMRA_TG_PROBE_LAYER")
19312 .ok()
19313 .and_then(|v| v.parse().ok());
19314 if probe_layer == Some(il) {
19315 let Step35TokenGraphState {
19316 mixed_stage,
19317 probe_mixed,
19318 ..
19319 } = &mut *state;
19320 crate::tp::graph_section(e, None, || {
19321 let _main = e.gpu.enter_main()?;
19322 let mut dst = probe_mixed.slice_mut(0..n_embd);
19323 e.stream()
19324 .memcpy_dtod(&mixed_stage.slice(0..n_embd), &mut dst)?;
19325 Ok(())
19326 })?;
19327 }
19328
19329 match &layer.ffn {
19331 crate::hybrid::Ffn::Dense {
19332 ffn_gate,
19333 ffn_up,
19334 ffn_down,
19335 } => {
19336 let n_ff = ffn_gate.out_features();
19337 let lim = self.cfg.clamp_shexp_at(il as u32);
19338 if lim.is_some() {
19342 return Err("step35 token graph dense FFN with clamp unsupported".into());
19343 }
19344 let (wg_d, wu_d, wd_d) = match (ffn_gate, ffn_up, ffn_down) {
19345 (
19346 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
19347 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
19348 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
19349 ) => (wg, wu, wd),
19350 _ => {
19351 return Err(
19352 "step35 token graph dense FFN requires bf16-resident weights"
19353 .into(),
19354 );
19355 }
19356 };
19357 crate::tp::graph_section(e, None, || {
19358 let _main = e.gpu.enter_main()?;
19359 let Step35TokenGraphState {
19360 x,
19361 x1,
19362 mixed_stage,
19363 dense_z,
19364 dense_gate,
19365 dense_up,
19366 dense_act,
19367 sh_stage,
19368 ..
19369 } = &mut *state;
19370 e.add_rms_norm(
19371 x,
19372 mixed_stage,
19373 layer.post_attn_norm.float_data(),
19374 x1,
19375 dense_z,
19376 n_embd,
19377 1,
19378 eps,
19379 )?;
19380 e.matvec_bf16_into(wg_d, dense_z, dense_gate, n_embd, n_ff)?;
19384 e.matvec_bf16_into(wu_d, dense_z, dense_up, n_embd, n_ff)?;
19385 Self::ffn_act_lim(
19386 e, &self.cfg, dense_gate, dense_up, 1.0, 1.0, lim, dense_act, n_ff,
19387 )?;
19388 e.matvec_bf16_into(wd_d, dense_act, sh_stage, n_ff, n_embd)?;
19389 e.add(x1, sh_stage, x, n_embd)?;
19390 Ok(())
19391 })?;
19392 }
19393 crate::hybrid::Ffn::Moe(m) => {
19394 let moe = self
19395 .cfg
19396 .moe
19397 .as_ref()
19398 .ok_or("step35 token graph needs moe cfg")?;
19399 let n_expert = moe.expert_count as usize;
19400 let n_used = moe.expert_used_count as usize;
19401 let sigmoid = self
19402 .cfg
19403 .sigmoid_router()
19404 .ok_or("step35 token graph needs the sigmoid router")?;
19405 let step_tp = m
19406 .step_tp
19407 .as_ref()
19408 .ok_or("step35 token graph needs TP experts")?;
19409 let bank = match &step_tp.experts {
19410 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => bank,
19411 _ => return Err("step35 token graph needs the NVFP4 bank".into()),
19412 };
19413 let routes_ws_mutex = bank.device_workspace_handle();
19414 let mut routes_guard = routes_ws_mutex
19415 .lock()
19416 .map_err(|_| "routes workspace lock is poisoned")?;
19417 let routes_ws = routes_guard
19418 .as_mut()
19419 .ok_or("step35 token graph requires the routes workspace warmed")?;
19420 routes_ws.arm_stages(e, bank.input_width, n_used)?;
19421 step_tp.runtime.routes_arm_raw(bank, routes_ws)?;
19422 let p_z = {
19423 let root = step_tp
19424 .runtime
19425 .rank_engine(0)
19426 .ok_or("routes root engine missing")?;
19427 let _main = root.gpu.enter_main()?;
19428 let stream = root.stream();
19429 let in_stage = routes_ws
19430 .in_stage_handle()
19431 .ok_or("routes in stage not armed")?;
19432 let (a, _g) = in_stage.device_ptr(&stream);
19433 a as u64
19434 };
19435 let local_out = bank.expert_width / ranks;
19436
19437 crate::tp::graph_section(e, None, || {
19439 let _main = e.gpu.enter_main()?;
19440 {
19441 let in_stage = routes_ws
19442 .in_stage_mut()
19443 .ok_or("routes in stage not armed")?;
19444 let Step35TokenGraphState {
19445 x, x1, mixed_stage, ..
19446 } = &mut *state;
19447 e.add_rms_norm(
19448 x,
19449 mixed_stage,
19450 layer.post_attn_norm.float_data(),
19451 x1,
19452 in_stage,
19453 n_embd,
19454 1,
19455 eps,
19456 )?;
19457 }
19458 {
19459 let z_ref = routes_ws
19460 .in_stage_handle()
19461 .ok_or("routes in stage not armed")?;
19462 e.router_gemv_into(
19463 m.gate_inp.float_data(),
19464 z_ref,
19465 &mut state.router_logits,
19466 n_embd,
19467 n_expert,
19468 1,
19469 )?;
19470 }
19471 let (sel_e, w_e) = routes_ws
19472 .dev_route_e_mut()
19473 .ok_or("routes staging not armed")?;
19474 e.moe_router_sigmoid_topk_into(
19475 &state.router_logits,
19476 1,
19477 n_expert,
19478 n_used,
19479 m.active_count(),
19480 &m.exp_probs_b_dev,
19481 &m.active_experts_dev,
19482 sigmoid.0,
19483 sigmoid.1,
19484 sel_e,
19485 w_e,
19486 )?;
19487 Ok(())
19488 })?;
19489
19490 group_id += 1;
19492 for rank in 0..ranks {
19493 let engine = step_tp
19494 .runtime
19495 .rank_engine(rank)
19496 .ok_or("routes rank engine missing")?;
19497 let runtime = &step_tp.runtime;
19498 crate::tp::graph_section(engine, Some(group_id), || {
19499 runtime.routes_rank_section(
19500 bank,
19501 routes_ws,
19502 p_z,
19503 local_out,
19504 n_used,
19505 step_tp.activation_limit,
19506 rank,
19507 )
19508 })?;
19509 }
19510
19511 {
19513 let root = step_tp
19514 .runtime
19515 .rank_engine(0)
19516 .ok_or("routes root engine missing")?;
19517 let runtime = &step_tp.runtime;
19518 crate::tp::graph_section(root, None, || {
19519 runtime.routes_root_section(bank, routes_ws)
19520 })?;
19521 }
19522
19523 let lim_sh = self.cfg.clamp_shexp_at(il as u32);
19527 let (wg_sh, wu_sh, wd_sh) = match (&m.gate_shexp, &m.up_shexp, &m.down_shexp) {
19528 (
19529 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
19530 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
19531 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
19532 ) => (wg, wu, wd),
19533 _ => {
19534 return Err(
19535 "step35 token graph shexp requires bf16-resident weights".into()
19536 );
19537 }
19538 };
19539 let n_ff_sh = m
19540 .gate_shexp
19541 .as_ref()
19542 .expect("matched Some above")
19543 .out_features();
19544 let gate_inp_shexp = m.gate_inp_shexp.as_ref();
19547 crate::tp::graph_section(e, None, || {
19548 let _main = e.gpu.enter_main()?;
19549 let (z_ref, out_stage) = routes_ws
19550 .in_and_out_stages_mut()
19551 .ok_or("routes stages not armed")?;
19552 let Step35TokenGraphState {
19553 x,
19554 x1,
19555 sh_stage,
19556 shexp_gate,
19557 shexp_up,
19558 shexp_act,
19559 gate_sig,
19560 ..
19561 } = &mut *state;
19562 e.matvec_bf16_dual_into(
19563 wg_sh, wu_sh, z_ref, shexp_gate, shexp_up, n_embd, n_ff_sh,
19564 )?;
19565 Self::ffn_act_lim(
19566 e, &self.cfg, shexp_gate, shexp_up, 1.0, 1.0, lim_sh, shexp_act,
19567 n_ff_sh,
19568 )?;
19569 e.matvec_bf16_into(wd_sh, shexp_act, sh_stage, n_ff_sh, n_embd)?;
19570 if let Some(gate_w) = gate_inp_shexp {
19571 e.sigmoid_dot_rows_into(
19572 z_ref,
19573 gate_w.float_data(),
19574 gate_sig,
19575 n_embd,
19576 1,
19577 )?;
19578 }
19579 e.add_scaled_rows(sh_stage, gate_sig, out_stage, n_embd, 1)?;
19580 e.add(x1, out_stage, x, n_embd)?;
19581 Ok(())
19582 })?;
19583 }
19584 }
19585 if probe_layer == Some(il) {
19586 let Step35TokenGraphState { x, probe_x, .. } = &mut *state;
19587 crate::tp::graph_section(e, None, || {
19588 let _main = e.gpu.enter_main()?;
19589 let mut dst = probe_x.slice_mut(0..n_embd);
19590 e.stream().memcpy_dtod(&x.slice(0..n_embd), &mut dst)?;
19591 Ok(())
19592 })?;
19593 }
19594 }
19595
19596 let head = match &self.output {
19598 crate::model::GpuTensor::FloatBf16 { data, .. } => data,
19599 _ => return Err("step35 token graph head requires the bf16-resident output".into()),
19600 };
19601 crate::tp::graph_section(e, None, || {
19602 let _main = e.gpu.enter_main()?;
19603 let Step35TokenGraphState {
19604 x,
19605 hn,
19606 logits_stage,
19607 token_d,
19608 pos_d,
19609 token_hist,
19610 hist_idx,
19611 ..
19612 } = &mut *state;
19613 e.rms_norm(x, self.output_norm.float_data(), hn, n_embd, 1, eps)?;
19614 e.matvec_bf16_into(head, hn, logits_stage, n_embd, self.cfg.n_vocab as usize)?;
19615 e.argmax_token_device_into(logits_stage, token_d, self.cfg.n_vocab as usize)?;
19621 e.u32_hist_append(token_d, token_hist, hist_idx)?;
19622 e.inc_i32(pos_d)?;
19623 Ok(())
19624 })?;
19625
19626 let graph = crate::tp::token_graph_build_finish()?;
19627 state.graphs.push((bucket_max, graph));
19628 eprintln!(
19629 "[step35-token-graph] built bucket={bucket_max} layers={n_layers} \
19630 build_ms={:.0} performance_claim=false",
19631 started.elapsed().as_secs_f64() * 1e3
19632 );
19633 Ok(())
19634 }
19635}