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") => true,
3090 _ => cfg!(memra_hopper_mma),
3091 };
3092 if fa3_on {
3093 let mut q16s = Vec::with_capacity(b);
3094 let mut v16s = Vec::with_capacity(b);
3095 for s in 0..b {
3096 let t = ts[s];
3097 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
3098 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
3099 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3100 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
3101 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3102 e.f32_to_bf16_v(
3103 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
3104 &mut v16,
3105 t * n_head_kv * head_dim,
3106 )?;
3107 q16s.push(q16);
3108 v16s.push((k16, v16));
3109 }
3110 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
3111 let mut kp = qp;
3112 let mut vp = qp;
3113 let mut op = [core::ptr::null_mut::<f32>(); 8];
3114 let mut tsv = [0i32; 8];
3115 for s in 0..b {
3116 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
3117 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
3118 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
3119 op[s] = e.addr_f32(&attns[s]) as *mut f32;
3120 tsv[s] = ts[s] as i32;
3121 }
3122 let rc = unsafe {
3123 crate::fa3_vl_raw(
3124 qp.as_ptr(),
3125 kp.as_ptr(),
3126 vp.as_ptr(),
3127 op.as_ptr(),
3128 tsv.as_ptr(),
3129 b as i32,
3130 n_head as i32,
3131 n_head_kv as i32,
3132 head_dim as i32,
3133 fa_scale,
3134 e.stream().cu_stream() as *mut core::ffi::c_void,
3135 )
3136 };
3137 if rc != 0 {
3138 return Err(format!("memra_fa3_vl rc={rc}").into());
3139 }
3140 } else {
3141 let fargs: Vec<crate::FaSeqVl> = (0..b)
3142 .map(|s| crate::FaSeqVl {
3143 q: e.addr_f32(&aps[s].qn),
3144 k16: e.addr_u8(&mirrors[s].0),
3145 v16: e.addr_u8(&mirrors[s].1),
3146 o: e.addr_f32(&attns[s]),
3147 kf: e.addr_f32(&aps[s].kn),
3148 vf: e.addr_f32v(
3149 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
3150 ),
3151 t: ts[s] as i32,
3152 pad: 0,
3153 })
3154 .collect();
3155 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
3156 }
3157 for (s, attn) in attns.into_iter().enumerate() {
3158 let (attn_g, ag16) = self.full_attn_prime_post_fa(
3159 e,
3160 attn,
3161 &aps[s].gate,
3162 ts[s],
3163 n_head,
3164 head_dim,
3165 )?;
3166 let mut done = false;
3167 if let Some(xh) = &ag16 {
3168 done = e.try_f16_gemm_pre_into_off(
3169 &fa.wo,
3170 xh,
3171 ts[s],
3172 &mut mixed,
3173 offs[s] * n_embd,
3174 )?;
3175 }
3176 if !done {
3177 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3178 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3179 }
3180 }
3181 } else {
3182 let mut parts: Vec<Vec<CudaSlice<f32>>> =
3183 (0..b).map(|_| Vec::new()).collect();
3184 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
3185 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
3186 parts[s].push(ys);
3187 }
3188 }
3189 for (s, g3s) in parts.into_iter().enumerate() {
3190 let (attn_g, ag16) = self.full_attn_prime_core_inner(
3192 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
3193 )?;
3194 let mut done = false;
3195 if let Some(xh) = &ag16 {
3196 done = e.try_f16_gemm_pre_into_off(
3197 &fa.wo,
3198 xh,
3199 ts[s],
3200 &mut mixed,
3201 offs[s] * n_embd,
3202 )?;
3203 }
3204 if !done {
3205 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3206 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3207 }
3208 }
3209 }
3210 }
3211 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
3212 Mixer::Linear(la) => {
3213 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3218 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
3219 let outs =
3220 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
3221 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
3222 let (o, t) = (offs[s], ts[s]);
3223 let mut done = false;
3224 if let Some(xh) = &gn16 {
3225 done = e.try_f16_gemm_pre_into_off(
3226 &la.ssm_out,
3227 xh,
3228 t,
3229 &mut mixed,
3230 o * n_embd,
3231 )?;
3232 }
3233 if !done {
3234 let m = e.matmul(&la.ssm_out, &gn, t)?;
3235 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
3236 }
3237 }
3238 }
3239 }
3240 let mut x1 = e.uninit(total * n_embd)?;
3241 let mut z = e.uninit(total * n_embd)?;
3242 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3243 e.add_rms_norm_f16out(
3244 &x,
3245 &mixed,
3246 layer.post_attn_norm.float_data(),
3247 &mut x1,
3248 &mut z,
3249 &mut zx16,
3250 n_embd,
3251 total,
3252 eps,
3253 )?;
3254 let ffn_out = match &layer.ffn {
3255 crate::hybrid::Ffn::Dense {
3256 ffn_gate,
3257 ffn_up,
3258 ffn_down,
3259 } => {
3260 let n_ff = ffn_gate.out_features();
3261 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
3262 let up = g2.pop().unwrap();
3263 let gate = g2.pop().unwrap();
3264 let mut act = e.uninit(total * n_ff)?;
3265 let d_lim = self.cfg.clamp_shexp_at(il as u32);
3269 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
3270 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
3271 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
3272 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
3273 Some(y) => y,
3274 None => e.matmul(ffn_down, &act, total)?,
3275 }
3276 } else {
3277 Self::ffn_act_lim(
3278 e,
3279 &self.cfg,
3280 &gate,
3281 &up,
3282 1.0,
3283 1.0,
3284 d_lim,
3285 &mut act,
3286 total * n_ff,
3287 )?;
3288 e.matmul(ffn_down, &act, total)?
3289 }
3290 }
3291 crate::hybrid::Ffn::Moe(m) => {
3292 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
3293 }
3294 };
3295 let mut x2 = e.uninit(total * n_embd)?;
3296 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
3297 x = x2;
3298 }
3299 let mut hn = e.uninit(total * n_embd)?;
3301 e.rms_norm(
3302 &x,
3303 self.output_norm.float_data(),
3304 &mut hn,
3305 n_embd,
3306 total,
3307 eps,
3308 )?;
3309 let mut hcat = e.uninit(b * n_embd)?;
3315 for s in 0..b {
3316 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3317 e.copy_view_into(
3318 &mut hcat,
3319 s * n_embd,
3320 &hn.slice(last0..last0 + n_embd),
3321 n_embd,
3322 )?;
3323 }
3324 let logits_cat = if b >= 2 {
3325 e.try_f16_gemm(&self.output, &hcat, b)?
3326 } else {
3327 None
3328 };
3329 let logits_host: Option<Vec<f32>> = match &logits_cat {
3330 Some(lc) => Some(e.dtoh(lc)?),
3331 None => None,
3332 };
3333 let n_vocab = self.output.out_features();
3334 let mut hidden_all = if crate::spec::spec_hpost() {
3335 split(e, &hn, n_embd)?
3336 } else {
3337 split(e, &x, n_embd)?
3338 };
3339 let mut out = Vec::with_capacity(b);
3340 for s in 0..b {
3341 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3342 let mut h_seed = e.uninit(n_embd)?;
3343 if !crate::spec::spec_hpost() {
3344 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
3345 } else {
3346 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3347 }
3348 let logits = match &logits_host {
3349 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
3350 None => {
3351 let mut hlast = e.uninit(n_embd)?;
3352 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3353 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
3354 }
3355 };
3356 caches[s].pos += ts[s];
3357 out.push((logits, h_seed, hidden_all.remove(0)));
3358 }
3359 Ok(out)
3360 }
3361
3362 #[allow(clippy::too_many_arguments)]
3373 fn full_attn_prime(
3374 &self,
3375 e: &Engine,
3376 fa: &FullAttnLayer,
3377 h: &CudaSlice<f32>,
3378 hx: Option<&CudaSlice<u8>>,
3379 pos_d: &CudaSlice<i32>,
3380 t: usize,
3381 cache: &mut Cache,
3382 il: usize,
3383 seq_end: usize,
3384 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3385 if self.uses_sliding_gated_moe_program() {
3386 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
3387 }
3388 let g3 = match hx {
3393 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
3394 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
3395 };
3396 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
3397 }
3398
3399 fn full_attn_prime_core(
3403 &self,
3404 e: &Engine,
3405 fa: &FullAttnLayer,
3406 g3: Vec<CudaSlice<f32>>,
3407 pos_d: &CudaSlice<i32>,
3408 t: usize,
3409 cache: &mut Cache,
3410 il: usize,
3411 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3412 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
3413 if let Some(xh) = &ag16 {
3414 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
3415 return Ok(y);
3416 }
3417 }
3418 Ok(e.matmul(&fa.wo, &attn_g, t)?)
3419 }
3420
3421 fn full_attn_prime_core_inner(
3422 &self,
3423 e: &Engine,
3424 fa: &FullAttnLayer,
3425 g3: Vec<CudaSlice<f32>>,
3426 pos_d: &CudaSlice<i32>,
3427 t: usize,
3428 cache: &mut Cache,
3429 il: usize,
3430 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3431 let cfg = &self.cfg;
3432 let geometry = cfg.full_attention_geometry_at(il as u32);
3433 let n_head = geometry.n_head as usize;
3434 let n_head_kv = geometry.n_head_kv as usize;
3435 let head_dim = geometry.head_dim_k as usize;
3436 let scale = geometry.attention_scale();
3437 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
3438 let AttnPre { q, k, v, gate } = pre;
3439 let mut attn = e.uninit(t * n_head * head_dim)?;
3440 self.full_attn_prime_fa_dispatch(
3441 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
3442 )?;
3443 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
3444 }
3445
3446 #[allow(clippy::type_complexity)]
3450 fn full_attn_prime_pre_fa(
3451 &self,
3452 e: &Engine,
3453 fa: &FullAttnLayer,
3454 mut g3: Vec<CudaSlice<f32>>,
3455 pos_d: &CudaSlice<i32>,
3456 t: usize,
3457 cache: &mut Cache,
3458 il: usize,
3459 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3460 let cfg = &self.cfg;
3461 let geometry = cfg.full_attention_geometry_at(il as u32);
3462 let n_head = geometry.n_head as usize;
3463 let n_head_kv = geometry.n_head_kv as usize;
3464 let head_dim = geometry.head_dim_k as usize;
3465 let eps = cfg.rms_eps;
3466
3467 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3471 let v = g3.pop().unwrap();
3472 let mut k = g3.pop().unwrap();
3473 let qf = g3.pop().unwrap();
3474 let (mut q, gate) = if gated {
3475 let mut q = e.uninit(t * n_head * head_dim)?;
3476 let mut gate = e.uninit(t * n_head * head_dim)?;
3477 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3478 (q, Some(gate))
3479 } else {
3480 (qf, None)
3481 };
3482
3483 let mut qn = e.uninit(t * n_head * head_dim)?;
3484 e.rms_norm(
3485 &q,
3486 fa.q_norm.float_data(),
3487 &mut qn,
3488 head_dim,
3489 n_head * t,
3490 eps,
3491 )?;
3492 q = qn;
3493 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3494 e.rms_norm(
3495 &k,
3496 fa.k_norm.float_data(),
3497 &mut kn,
3498 head_dim,
3499 n_head_kv * t,
3500 eps,
3501 )?;
3502 k = kn;
3503 let rope_dims = geometry.n_rot as usize;
3504 e.rope_neox(
3505 &mut q,
3506 pos_d,
3507 head_dim,
3508 rope_dims,
3509 n_head,
3510 t,
3511 geometry.rope_base,
3512 1.0,
3513 )?;
3514 e.rope_neox(
3515 &mut k,
3516 pos_d,
3517 head_dim,
3518 rope_dims,
3519 n_head_kv,
3520 t,
3521 geometry.rope_base,
3522 1.0,
3523 )?;
3524
3525 {
3528 let kvl = cache.kv[il].as_mut().unwrap();
3529 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3530 e.append_kv_quantized_rows(
3531 &k,
3532 &v,
3533 &mut kvl.k,
3534 &mut kvl.v,
3535 kvl.len,
3536 t,
3537 kvl.kv_dim_k,
3538 kvl.kv_dim_v,
3539 kvl.k_tok_bytes,
3540 kvl.v_tok_bytes,
3541 crate::Engine::kv_fp8_on(),
3542 )?;
3543 kvl.len += t;
3544 let new_len = kvl.len as i32;
3545 e.set_i32_one(&mut kvl.len_d, new_len)?;
3546 }
3547
3548 let base_len = {
3549 let kvl = cache.kv[il].as_ref().unwrap();
3550 kvl.len - t };
3552 Ok((AttnPre { q, k, v, gate }, base_len))
3553 }
3554
3555 #[allow(clippy::too_many_arguments)]
3562 fn full_attn_prime_fa_dispatch(
3563 &self,
3564 e: &Engine,
3565 q: &CudaSlice<f32>,
3566 k: &CudaSlice<f32>,
3567 v: &CudaSlice<f32>,
3568 attn: &mut CudaSlice<f32>,
3569 base_len: usize,
3570 t: usize,
3571 cache: &mut Cache,
3572 il: usize,
3573 head_dim: usize,
3574 n_head: usize,
3575 n_head_kv: usize,
3576 scale: f32,
3577 ) -> Result<(), Box<dyn std::error::Error>> {
3578 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
3591 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3592 e.sdpa_naive(
3593 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3594 )?;
3595 } else {
3596 e.fa_prefill(
3597 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3598 )?;
3599 }
3600 return Ok(());
3601 }
3602 let kvl = cache.kv[il].as_ref().unwrap();
3603 let t_kv = base_len + t;
3604 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
3605 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
3606 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3610 e.sdpa_naive_quantized_view(
3611 q,
3612 &k_view,
3613 &v_view,
3614 attn,
3615 head_dim,
3616 n_head,
3617 n_head_kv,
3618 t,
3619 t_kv,
3620 scale,
3621 true,
3622 kvl.k_tok_bytes,
3623 kvl.v_tok_bytes,
3624 )?;
3625 return Ok(());
3626 }
3627 let deqw = std::env::var("MEMRA_PRIME_DEQW")
3635 .map(|v| v != "0")
3636 .unwrap_or(true);
3637 if deqw {
3638 e.fa_prefill_view_ws(
3639 q,
3640 &k_view,
3641 &v_view,
3642 attn,
3643 head_dim,
3644 n_head,
3645 n_head_kv,
3646 t,
3647 t_kv,
3648 scale,
3649 true,
3650 kvl.k_tok_bytes,
3651 kvl.v_tok_bytes,
3652 crate::Engine::kv_fp8_on(),
3653 )?;
3654 } else {
3655 e.fa_prefill_view(
3656 q,
3657 &k_view,
3658 &v_view,
3659 attn,
3660 head_dim,
3661 n_head,
3662 n_head_kv,
3663 t,
3664 t_kv,
3665 scale,
3666 true,
3667 kvl.k_tok_bytes,
3668 kvl.v_tok_bytes,
3669 crate::Engine::kv_fp8_on(),
3670 )?;
3671 }
3672 Ok(())
3673 }
3674
3675 fn full_attn_prime_post_fa(
3678 &self,
3679 e: &Engine,
3680 attn: CudaSlice<f32>,
3681 gate: &Option<CudaSlice<f32>>,
3682 t: usize,
3683 n_head: usize,
3684 head_dim: usize,
3685 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3686 let (attn_g, ag16) = match gate {
3687 Some(gate) => {
3688 let n = t * n_head * head_dim;
3689 let mut ag = e.uninit(n)?;
3690 if Self::f16out_on(e, t) {
3691 let mut a16 = e.alloc_u8_uninit(n * 2)?;
3692 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
3693 (ag, Some(a16))
3694 } else {
3695 let mut gsig = e.uninit(n)?;
3696 e.sigmoid(gate, &mut gsig, n)?;
3697 e.mul(&attn, &gsig, &mut ag, n)?;
3698 (ag, None)
3699 }
3700 }
3701 None => (attn, None),
3702 };
3703 Ok((attn_g, ag16))
3704 }
3705
3706 fn linear_attn_prime(
3713 &self,
3714 e: &Engine,
3715 la: &LinearAttnLayer,
3716 h: &CudaSlice<f32>,
3717 hx: Option<&CudaSlice<u8>>,
3718 t: usize,
3719 cache: &mut Cache,
3720 il: usize,
3721 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3722 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3724 let g4 = match hx {
3725 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
3726 None => e.matmul_group(&ws, h, t)?,
3727 };
3728 self.linear_attn_prime_core(e, la, g4, t, cache, il)
3729 }
3730
3731 fn linear_attn_prime_core(
3733 &self,
3734 e: &Engine,
3735 la: &LinearAttnLayer,
3736 mut g4: Vec<CudaSlice<f32>>,
3737 t: usize,
3738 cache: &mut Cache,
3739 il: usize,
3740 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3741 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
3742 }
3743
3744 #[allow(clippy::too_many_arguments)]
3748 fn linear_attn_prime_core_pad_inner(
3749 &self,
3750 e: &Engine,
3751 la: &LinearAttnLayer,
3752 mut g4: Vec<CudaSlice<f32>>,
3753 t: usize,
3754 cache: &mut Cache,
3755 il: usize,
3756 pad_len: Option<&CudaSlice<i32>>,
3757 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3758 let geometry = la.geometry;
3760 let d_state = geometry.key_head_dim as usize;
3761 let num_k = geometry.key_heads as usize;
3762 let num_v = geometry.value_heads as usize;
3763 let key_dim = d_state * num_k;
3764 let value_dim = geometry.value_head_dim as usize * num_v;
3765 let conv_dim = key_dim * 2 + value_dim;
3766 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(
3771 e,
3772 la,
3773 &qkv_mixed.slice(0..t * conv_dim),
3774 &z.slice(0..t * value_dim),
3775 &beta_raw.slice(0..t * num_v),
3776 &alpha.slice(0..t * num_v),
3777 t,
3778 cache,
3779 il,
3780 pad_len,
3781 )
3782 }
3783
3784 #[allow(clippy::too_many_arguments)]
3787 fn linear_attn_gdn_prep(
3788 &self,
3789 e: &Engine,
3790 la: &LinearAttnLayer,
3791 qkv_mixed: &cudarc::driver::CudaView<f32>,
3792 beta_raw: &cudarc::driver::CudaView<f32>,
3793 alpha: &cudarc::driver::CudaView<f32>,
3794 t: usize,
3795 cache: &mut Cache,
3796 il: usize,
3797 pad_len: Option<&CudaSlice<i32>>,
3798 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
3799 let cfg = &self.cfg;
3800 let geometry = la.geometry;
3801 let d_state = geometry.key_head_dim as usize;
3802 let num_k = geometry.key_heads as usize;
3803 let num_v = geometry.value_heads as usize;
3804 let d_conv = geometry.conv_kernel as usize;
3805 let key_dim = d_state * num_k; let value_dim = geometry.value_head_dim as usize * num_v;
3807 let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
3809 debug_assert!(
3810 t >= d_conv - 1,
3811 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
3812 );
3813
3814 let rl = cache.recur[il].as_mut().unwrap();
3819 let hk = Self::gdn_hk(e, t, num_v, num_k);
3820 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
3821 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
3823 let mut k_g = e.uninit(d_state * hk * t)?;
3824 let mut v_g = e.uninit(d_state * num_v * t)?;
3825 if conv_fuse {
3826 e.ssm_conv1d_gdn_state_pad(
3827 qkv_mixed,
3828 &mut rl.conv_state,
3829 la.ssm_conv1d.float_data(),
3830 &mut q_g,
3831 &mut k_g,
3832 &mut v_g,
3833 conv_dim,
3834 t,
3835 d_conv,
3836 d_state,
3837 num_v,
3838 num_k,
3839 key_dim,
3840 hk,
3841 pad_len,
3842 )?;
3843 } else {
3844 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
3846 qkv_mixed,
3847 &mut rl.conv_state,
3848 la.ssm_conv1d.float_data(),
3849 &mut conv_out,
3850 conv_dim,
3851 t,
3852 d_conv,
3853 pad_len,
3854 )?;
3855 e.qkv_to_gdn_repack(
3856 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
3857 )?;
3858 }
3859 let mut q_l2 = e.uninit(d_state * hk * t)?;
3860 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
3864 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
3865 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
3866 Some(qb)
3867 } else {
3868 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
3869 None
3870 };
3871 let mut k_l2 = e.uninit(d_state * hk * t)?;
3872 let kb16 = if Engine::l2_v2_on(d_state) {
3874 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
3875 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
3876 Some(kb)
3877 } else {
3878 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
3879 None
3880 };
3881 let mut beta = e.uninit(t * num_v)?;
3882 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
3883 let mut g_log = e.uninit(t * num_v)?;
3884 e.gdn_glog_v(
3885 alpha,
3886 la.ssm_dt.float_data(),
3887 la.ssm_a.float_data(),
3888 &mut g_log,
3889 num_v,
3890 t,
3891 )?;
3892 if let Some(len_d) = pad_len {
3893 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
3894 }
3895 Ok(GdnPrep {
3896 hk,
3897 q_l2,
3898 k_l2,
3899 v_g,
3900 beta,
3901 g_log,
3902 kb16,
3903 qb16,
3904 })
3905 }
3906
3907 #[allow(clippy::too_many_arguments)]
3912 fn linear_attn_prime_core_batch(
3913 &self,
3914 e: &Engine,
3915 la: &LinearAttnLayer,
3916 g4: &[CudaSlice<f32>],
3917 offs: &[usize],
3918 ts: &[usize],
3919 caches: &mut [&mut Cache],
3920 il: usize,
3921 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
3922 let geometry = la.geometry;
3923 let d_state = geometry.key_head_dim as usize;
3924 let num_k = geometry.key_heads as usize;
3925 let num_v = geometry.value_heads as usize;
3926 let d_conv = geometry.conv_kernel as usize;
3927 let key_dim = d_state * num_k;
3928 let value_dim = geometry.value_head_dim as usize * num_v;
3929 let conv_dim = key_dim * 2 + value_dim;
3930 let eps = self.cfg.rms_eps;
3931 let scale = 1.0 / (d_state as f32).sqrt();
3932 let b = ts.len();
3933 let c = Engine::gdn_chunk_size();
3934 let carried = caches.iter().any(|c| c.pos > 0);
3937 let use_vl = !carried
3938 && (2..=8).contains(&b)
3939 && Engine::gdn_chunked_enabled()
3940 && ts.iter().all(|&t| t >= 16)
3941 && e.gdn_mma_enabled(c)
3942 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
3943 if !use_vl {
3944 return (0..b)
3945 .map(|s| {
3946 let (o, t) = (offs[s], ts[s]);
3947 self.linear_attn_prime_core_pad_view(
3948 e,
3949 la,
3950 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
3951 &g4[1].slice(o * value_dim..(o + t) * value_dim),
3952 &g4[2].slice(o * num_v..(o + t) * num_v),
3953 &g4[3].slice(o * num_v..(o + t) * num_v),
3954 t,
3955 caches[s],
3956 il,
3957 None,
3958 )
3959 })
3960 .collect();
3961 }
3962 struct SeqBufs {
3966 conv_out: CudaSlice<f32>,
3967 q_g: CudaSlice<f32>,
3968 k_g: CudaSlice<f32>,
3969 v_g: CudaSlice<f32>,
3970 q_l2: CudaSlice<f32>,
3971 k_l2: CudaSlice<f32>,
3972 beta: CudaSlice<f32>,
3973 g_log: CudaSlice<f32>,
3974 gn: CudaSlice<f32>,
3975 gn16: CudaSlice<u8>,
3976 }
3977 let f16o = Self::f16out_on(e, 16);
3978 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
3980 let mut pres = Vec::with_capacity(b);
3981 for &t in ts.iter().take(b) {
3982 sb.push(SeqBufs {
3983 conv_out: e.uninit(conv_dim * t)?,
3984 q_g: e.uninit(d_state * hk * t)?,
3985 k_g: e.uninit(d_state * hk * t)?,
3986 v_g: e.uninit(d_state * num_v * t)?,
3987 q_l2: e.uninit(d_state * hk * t)?,
3988 k_l2: e.uninit(d_state * hk * t)?,
3989 beta: e.uninit(t * num_v)?,
3990 g_log: e.uninit(t * num_v)?,
3991 gn: e.uninit(d_state * num_v * t)?,
3992 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
3993 });
3994 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
3995 }
3996 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
3997 .map(|s| {
3998 let (o, t) = (offs[s], ts[s]);
3999 let rl = caches[s].recur[il].as_ref().unwrap();
4000 crate::GdnPrepVl {
4001 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
4002 conv_state: e.addr_f32(&rl.conv_state),
4003 conv_out: e.addr_f32(&sb[s].conv_out),
4004 q_g: e.addr_f32(&sb[s].q_g),
4005 k_g: e.addr_f32(&sb[s].k_g),
4006 v_g: e.addr_f32(&sb[s].v_g),
4007 q_l2: e.addr_f32(&sb[s].q_l2),
4008 k_l2: e.addr_f32(&sb[s].k_l2),
4009 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
4010 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
4011 beta: e.addr_f32(&sb[s].beta),
4012 g_log: e.addr_f32(&sb[s].g_log),
4013 o: e.addr_f32(&pres[s].o),
4014 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
4015 gn: e.addr_f32(&sb[s].gn),
4016 gn16: e.addr_u8(&sb[s].gn16),
4017 kb16: if Engine::l2_v2_on(d_state) {
4018 e.addr_u8(&pres[s].kb16)
4019 } else {
4020 0
4021 },
4022 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
4023 e.addr_u8(&pres[s].qb16)
4024 } else {
4025 0
4026 },
4027 t: t as i32,
4028 pad: 0,
4029 }
4030 })
4031 .collect();
4032 let args: Vec<crate::GdnSeqVl> = (0..b)
4033 .map(|s| {
4034 let rl = caches[s].recur[il].as_ref().unwrap();
4035 crate::GdnSeqVl {
4036 kb16: e.addr_u8(&pres[s].kb16),
4037 gcum: e.addr_f32(&pres[s].gcum),
4038 beta: e.addr_f32(&sb[s].beta),
4039 u: e.addr_f32(&pres[s].u),
4040 wb16: e.addr_u8(&pres[s].wb16),
4041 y: e.addr_u8(&pres[s].y16),
4042 ssnap: e.addr_u8(&pres[s].ssnap16),
4043 state_in: e.addr_f32(&rl.ssm_state),
4044 state_out: e.addr_f32(&rl.ssm_state_alt),
4045 q: e.addr_f32(&sb[s].q_l2),
4046 p: e.addr_f32(&pres[s].p),
4047 o: e.addr_f32(&pres[s].o),
4048 k: e.addr_f32(&sb[s].k_l2),
4049 v: e.addr_f32(&sb[s].v_g),
4050 g: e.addr_f32(&sb[s].g_log),
4051 a: e.addr_f32(&pres[s].a),
4052 w: e.addr_f32(&pres[s].w),
4053 t: ts[s] as i32,
4054 nc: pres[s].nc as i32,
4055 }
4056 })
4057 .collect();
4058 e.gdn_prep_vl8(
4059 &prep_args,
4060 la.ssm_conv1d.float_data(),
4061 la.ssm_dt.float_data(),
4062 la.ssm_a.float_data(),
4063 conv_dim,
4064 d_conv,
4065 d_state,
4066 num_v,
4067 num_k,
4068 key_dim,
4069 hk,
4070 eps,
4071 )?;
4072 if !Engine::l2_v2_on(d_state) {
4075 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
4076 }
4077 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
4079 if !Engine::l2_v2_on(d_state) {
4081 for s in 0..b {
4082 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
4083 }
4084 }
4085 let mut wa = [crate::GdnWVl::default(); 8];
4086 for s in 0..b {
4087 wa[s] = crate::GdnWVl {
4088 qb16: e.addr_u8(&pres[s].qb16),
4089 pb16: e.addr_u8(&pres[s].pb16),
4090 };
4091 }
4092 Some(crate::GdnWVl8(wa))
4093 } else {
4094 None
4095 };
4096 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
4097 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
4098 if f16o {
4099 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
4100 }
4101 let mut out = Vec::with_capacity(b);
4103 for (s, bufs) in sb.into_iter().enumerate() {
4104 let rl = caches[s].recur[il].as_mut().unwrap();
4105 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4106 let (o, t) = (offs[s], ts[s]);
4107 let SeqBufs { mut gn, gn16, .. } = bufs;
4108 if f16o {
4109 out.push((gn, Some(gn16)));
4110 } else {
4111 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
4112 e.gated_rmsnorm_zv(
4113 &pres[s].o,
4114 la.ssm_norm.float_data(),
4115 &z_v,
4116 &mut gn,
4117 d_state,
4118 num_v * t,
4119 eps,
4120 )?;
4121 out.push((gn, None));
4122 }
4123 }
4124 Ok(out)
4125 }
4126
4127 #[allow(clippy::too_many_arguments)]
4131 fn linear_attn_prime_core_pad_view(
4132 &self,
4133 e: &Engine,
4134 la: &LinearAttnLayer,
4135 qkv_mixed: &cudarc::driver::CudaView<f32>,
4136 z: &cudarc::driver::CudaView<f32>,
4137 beta_raw: &cudarc::driver::CudaView<f32>,
4138 alpha: &cudarc::driver::CudaView<f32>,
4139 t: usize,
4140 cache: &mut Cache,
4141 il: usize,
4142 pad_len: Option<&CudaSlice<i32>>,
4143 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4144 let cfg = &self.cfg;
4145 let geometry = la.geometry;
4146 let d_state = geometry.key_head_dim as usize;
4147 let num_v = geometry.value_heads as usize;
4148 let eps = cfg.rms_eps;
4149 let scale = 1.0 / (d_state as f32).sqrt();
4150
4151 let prep =
4152 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
4153
4154 let mut o = e.uninit(d_state * num_v * t)?;
4160 let rl = cache.recur[il].as_mut().unwrap();
4161 {
4162 let crate::cache::RecurLayer {
4163 ssm_state,
4164 ssm_state_alt,
4165 ..
4166 } = rl;
4167 e.gdn_scan_prefill(
4168 &prep.q_l2,
4169 &prep.k_l2,
4170 &prep.v_g,
4171 &prep.g_log,
4172 &prep.beta,
4173 prep.kb16.as_ref(),
4174 prep.qb16.as_ref(),
4175 ssm_state,
4176 ssm_state_alt,
4177 &mut o,
4178 num_v,
4179 t,
4180 scale,
4181 prep.hk,
4182 )?;
4183 }
4184 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4185
4186 let mut gn = e.uninit(d_state * num_v * t)?;
4189 let gn16 = if Self::f16out_on(e, t) {
4190 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
4191 e.gated_rmsnorm_f16out_zv(
4192 &o,
4193 la.ssm_norm.float_data(),
4194 z,
4195 &mut gn,
4196 &mut g16,
4197 d_state,
4198 num_v * t,
4199 eps,
4200 )?;
4201 Some(g16)
4202 } else {
4203 e.gated_rmsnorm_zv(
4204 &o,
4205 la.ssm_norm.float_data(),
4206 z,
4207 &mut gn,
4208 d_state,
4209 num_v * t,
4210 eps,
4211 )?;
4212 None
4213 };
4214 Ok((gn, gn16))
4215 }
4216
4217 #[allow(clippy::too_many_arguments)]
4219 fn linear_attn_prime_core_pad(
4220 &self,
4221 e: &Engine,
4222 la: &LinearAttnLayer,
4223 g4: Vec<CudaSlice<f32>>,
4224 t: usize,
4225 cache: &mut Cache,
4226 il: usize,
4227 pad_len: Option<&CudaSlice<i32>>,
4228 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4229 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
4230 if let Some(xh) = &gn16 {
4231 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
4232 return Ok(y);
4233 }
4234 }
4235 Ok(e.matmul(&la.ssm_out, &gn, t)?)
4236 }
4237
4238 pub fn full_attn(
4243 &self,
4244 e: &Engine,
4245 fa: &FullAttnLayer,
4246 h: &CudaSlice<f32>,
4247 pos_d: &CudaSlice<i32>,
4248 t: usize,
4249 il: usize,
4250 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4251 if self.uses_sliding_gated_moe_program() {
4252 return self.step35_attn(e, fa, h, pos_d, t, il);
4253 }
4254 let cfg = &self.cfg;
4255 let _n_embd = cfg.n_embd as usize;
4256 let geometry = cfg.full_attention_geometry_at(il as u32);
4257 let n_head = geometry.n_head as usize;
4258 let n_head_kv = geometry.n_head_kv as usize;
4259 let head_dim = geometry.head_dim_k as usize;
4260 let eps = cfg.rms_eps;
4261 let scale = geometry.attention_scale();
4262
4263 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
4266 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
4268 let v = g3.pop().unwrap();
4269 let mut k = g3.pop().unwrap();
4270 let qf = g3.pop().unwrap();
4271 let (mut q, gate) = if gated {
4272 let mut q = e.uninit(t * n_head * head_dim)?;
4273 let mut gate = e.uninit(t * n_head * head_dim)?;
4274 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
4275 (q, Some(gate))
4276 } else {
4277 (qf, None)
4278 };
4279
4280 let mut qn = e.uninit(t * n_head * head_dim)?;
4282 e.rms_norm(
4283 &q,
4284 fa.q_norm.float_data(),
4285 &mut qn,
4286 head_dim,
4287 n_head * t,
4288 eps,
4289 )?;
4290 q = qn;
4291 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
4292 e.rms_norm(
4293 &k,
4294 fa.k_norm.float_data(),
4295 &mut kn,
4296 head_dim,
4297 n_head_kv * t,
4298 eps,
4299 )?;
4300 k = kn;
4301 let rope_dims = geometry.n_rot as usize;
4302 e.rope_neox(
4303 &mut q,
4304 pos_d,
4305 head_dim,
4306 rope_dims,
4307 n_head,
4308 t,
4309 geometry.rope_base,
4310 1.0,
4311 )?;
4312 e.rope_neox(
4313 &mut k,
4314 pos_d,
4315 head_dim,
4316 rope_dims,
4317 n_head_kv,
4318 t,
4319 geometry.rope_base,
4320 1.0,
4321 )?;
4322
4323 let mut attn = e.uninit(t * n_head * head_dim)?;
4325 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4328 e.sdpa_naive(
4330 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4331 )?;
4332 } else {
4333 e.fa_prefill(
4334 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4335 )?;
4336 }
4337
4338 let attn_g = match &gate {
4340 Some(gate) => {
4341 let mut gsig = e.uninit(t * n_head * head_dim)?;
4342 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
4343 let mut ag = e.uninit(t * n_head * head_dim)?;
4344 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
4345 ag
4346 }
4347 None => attn,
4348 };
4349
4350 let o = e.matmul(&fa.wo, &attn_g, t)?;
4352 Ok(o)
4353 }
4354
4355 pub fn linear_attn(
4357 &self,
4358 e: &Engine,
4359 la: &LinearAttnLayer,
4360 h: &CudaSlice<f32>,
4361 t: usize,
4362 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4363 let cfg = &self.cfg;
4364 let _n_embd = cfg.n_embd as usize;
4365 let geometry = la.geometry;
4366 let d_state = geometry.key_head_dim as usize;
4367 let num_k = geometry.key_heads as usize;
4368 let num_v = geometry.value_heads as usize;
4369 let d_conv = geometry.conv_kernel as usize;
4370 let head_k = d_state;
4371 let head_v = geometry.value_head_dim as usize;
4372 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;
4376 let scale = 1.0 / (d_state as f32).sqrt();
4377
4378 let mut g4 = e.matmul_group(
4381 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
4382 h,
4383 t,
4384 )?;
4385 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);
4397 let mut q_g = e.uninit(d_state * num_v * t)?;
4398 let mut k_g = e.uninit(d_state * num_v * t)?;
4399 let mut v_g = e.uninit(d_state * num_v * t)?;
4400 e.ssm_conv1d_gdn(
4401 &qkv_mixed,
4402 la.ssm_conv1d.float_data(),
4403 &mut q_g,
4404 &mut k_g,
4405 &mut v_g,
4406 conv_dim,
4407 t,
4408 d_conv,
4409 d_state,
4410 num_v,
4411 num_k,
4412 key_dim,
4413 )?;
4414 let mut q_l2 = e.uninit(d_state * num_v * t)?;
4416 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
4417 let mut k_l2 = e.uninit(d_state * num_v * t)?;
4418 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
4419 let v_gd = v_g;
4420
4421 let mut beta = e.uninit(t * num_v)?;
4424 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
4425 let mut g_log = e.uninit(t * num_v)?;
4427 e.gdn_glog(
4428 &alpha,
4429 la.ssm_dt.float_data(),
4430 la.ssm_a.float_data(),
4431 &mut g_log,
4432 num_v,
4433 t,
4434 )?;
4435
4436 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
4439 let mut o = e.uninit(d_state * num_v * t)?;
4440 e.gdn_scan_prefill(
4441 &q_l2,
4442 &k_l2,
4443 &v_gd,
4444 &g_log,
4445 &beta,
4446 None,
4447 None,
4448 &state_in,
4449 &mut state_out,
4450 &mut o,
4451 num_v,
4452 t,
4453 scale,
4454 num_v,
4455 )?;
4456
4457 let mut gn = e.uninit(d_state * num_v * t)?;
4462 e.gated_rmsnorm(
4463 &o,
4464 la.ssm_norm.float_data(),
4465 &z,
4466 &mut gn,
4467 d_state,
4468 num_v * t,
4469 eps,
4470 )?;
4471
4472 let out = e.matmul(&la.ssm_out, &gn, t)?;
4476 Ok(out)
4477 }
4478}
4479
4480impl HybridModel {
4481 pub fn moe_ffn_il(
4492 &self,
4493 e: &Engine,
4494 m: &MoeWeights,
4495 z: &CudaSlice<f32>,
4496 t: usize,
4497 il: u16,
4498 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4499 Self::moe_ffn_inner(
4500 e,
4501 m,
4502 z,
4503 None,
4504 t,
4505 &self.cfg,
4506 il,
4507 self.max_moe_block(),
4508 false,
4509 None,
4510 self.uses_sliding_gated_moe_program(),
4511 )
4512 }
4513
4514 pub fn moe_ffn_il_prefill(
4517 &self,
4518 e: &Engine,
4519 m: &MoeWeights,
4520 z: &CudaSlice<f32>,
4521 t: usize,
4522 il: u16,
4523 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4524 Self::moe_ffn_inner(
4525 e,
4526 m,
4527 z,
4528 None,
4529 t,
4530 &self.cfg,
4531 il,
4532 self.max_moe_block(),
4533 true,
4534 Some(&self.step_grouped_prefill),
4535 self.uses_sliding_gated_moe_program(),
4536 )
4537 }
4538
4539 pub fn moe_ffn_il_zq8(
4543 &self,
4544 e: &Engine,
4545 m: &MoeWeights,
4546 z: &CudaSlice<f32>,
4547 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4548 t: usize,
4549 il: u16,
4550 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4551 Self::moe_ffn_inner(
4552 e,
4553 m,
4554 z,
4555 zq8,
4556 t,
4557 &self.cfg,
4558 il,
4559 self.max_moe_block(),
4560 false,
4561 None,
4562 self.uses_sliding_gated_moe_program(),
4563 )
4564 }
4565
4566 pub(crate) fn moe_ffn(
4574 e: &Engine,
4575 m: &MoeWeights,
4576 z: &CudaSlice<f32>,
4577 t: usize,
4578 cfg: &ModelConfig,
4579 il: u16,
4580 max_block: usize,
4581 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4582 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false, None, false)
4583 }
4584
4585 #[allow(clippy::too_many_arguments)]
4586 pub(crate) fn moe_ffn_inner(
4587 e: &Engine,
4588 m: &MoeWeights,
4589 z: &CudaSlice<f32>,
4590 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4591 t: usize,
4592 cfg: &ModelConfig,
4593 il: u16,
4594 max_block: usize,
4595 prefill: bool,
4596 grouped_prefill: Option<&std::sync::Mutex<crate::hybrid::StepEpGroupedPrefill>>,
4597 sliding_gated_moe: bool,
4598 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4599 let worker_io = crate::spill_pread::worker_enabled();
4600 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
4601 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
4602 e.with_moe_cache(max_block, |cache, _| {
4603 cache.begin_forward_epoch(il, t);
4604 if worker_io {
4605 cache.begin_worker_scope();
4606 }
4607 Ok(())
4608 })?;
4609 }
4610 if m.step_ep.is_some() || m.step_tp.is_some() {
4611 let moe = cfg
4612 .moe
4613 .as_ref()
4614 .ok_or("Step distributed execution requires MoE model metadata")?;
4615 let n_embd = cfg.n_embd as usize;
4616 let n_expert = moe.expert_count as usize;
4617 let n_used = moe.expert_used_count as usize;
4618 let sigmoid = cfg
4619 .sigmoid_router()
4620 .ok_or("Step distributed execution requires the Step sigmoid router")?;
4621 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4622 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
4623 let grouped_prefill_requested = prefill && step_ep_grouped_prefill_enabled()?;
4624 if grouped_prefill_requested && !step_tp_prefill_enabled()? {
4625 return Err(
4626 "MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into(),
4627 );
4628 }
4629 if grouped_prefill_requested && !step_grouped_prefill_shape(true, prefill, t) {
4630 return Err(format!(
4631 "Step grouped prefill tokens {t} are outside the qualified {}..={} range",
4632 PRIME_MIN_T,
4633 crate::cache::PRIME_CHUNK_MAX_TOKENS,
4634 )
4635 .into());
4636 }
4637 let grouped_decode_shape = step_grouped_decode_shape(prefill, t);
4638 let grouped_prefill_shape =
4639 step_grouped_prefill_shape(grouped_prefill_requested, prefill, t);
4640 if let Some(ep) = m.step_ep.as_ref().filter(|ep| {
4641 ep.grouped_decode.is_some() && (grouped_decode_shape || grouped_prefill_shape)
4642 }) {
4643 let (selected, route_weights) =
4644 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sigmoid)?;
4645 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
4646 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
4647 Self::trace_moe_input(e, il, t, n_embd, z)?;
4648 let selected = selected
4649 .iter()
4650 .map(|&expert| expert as usize)
4651 .collect::<Vec<_>>();
4652
4653 e.stream().synchronize()?;
4656 let execute = |state: &mut crate::hybrid::StepEpGroupedDecode| {
4657 state.projection.set_activation_limit(ep.activation_limit)?;
4658 ep.runtime
4659 .refresh_step_grouped_expert_parallel_gate_from_root_device(
4660 ep.experts.e4m3()?,
4661 &mut state.projection,
4662 z,
4663 t,
4664 &selected,
4665 )?;
4666 ep.runtime.refresh_step_grouped_expert_parallel_combine(
4667 &state.projection,
4668 &mut state.combine,
4669 &route_weights,
4670 )?;
4671 ep.runtime.execute_step_grouped_expert_parallel_gate(
4672 ep.experts.e4m3()?,
4673 &mut state.projection,
4674 )?;
4675 ep.runtime.execute_step_grouped_expert_parallel_combine(
4676 &state.projection,
4677 &mut state.combine,
4678 )?;
4679 let mut output = ep.runtime.copy_step_grouped_expert_parallel_combine_root(
4680 &state.projection,
4681 &state.combine,
4682 e,
4683 )?;
4684 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
4685 if prefill {
4686 e.stream().synchronize()?;
4689 }
4690 eprintln!(
4691 "[step-tp-ep-grouped] execute layer={il} tokens={t} devices={:?} \
4692 attention_layout=tensor-parallel expert_layout=expert-parallel \
4693 expert_transport={} native_p2p=true route_control=host-narrow \
4694 input=root-device projection_workspaces=persistent \
4695 combine=root-device output=owning-stage-device \
4696 prefill={prefill} batched_decode=false capacity={} \
4697 performance_claim=false",
4698 ep.devices,
4699 ep.runtime.transport_label(),
4700 state.projection.max_tokens(),
4701 );
4702 Ok::<_, Box<dyn std::error::Error>>(output)
4703 };
4704
4705 if grouped_prefill_shape {
4706 let grouped_prefill = grouped_prefill
4707 .ok_or("Step grouped prefill has no model-scoped executor")?;
4708 let mut shared = grouped_prefill
4709 .lock()
4710 .map_err(|_| "Step grouped prefill state lock is poisoned")?;
4711 let needs_prepare = shared.state.as_ref().is_none_or(|state| {
4712 state.devices != ep.devices
4713 || state.grouped.projection.max_tokens() < t
4714 || state.grouped.projection.input_width() != n_embd
4715 || state.grouped.projection.expert_width()
4716 != moe.expert_ff_length as usize
4717 });
4718 if needs_prepare {
4719 let seed_input = vec![0.0f32; n_embd];
4720 let seed_selected = &selected[..n_used];
4721 let seed_weights = &route_weights[..n_used];
4722 let projection = ep
4723 .runtime
4724 .prepare_step_grouped_expert_parallel_gate_with_capacity(
4725 ep.experts.e4m3()?,
4726 &seed_input,
4727 1,
4728 seed_selected,
4729 ep.activation_limit,
4730 t,
4731 )?;
4732 let combine = ep.runtime.prepare_step_grouped_expert_parallel_combine(
4733 &projection,
4734 seed_weights,
4735 )?;
4736 shared.state = Some(crate::hybrid::StepEpGroupedPrefillState {
4737 devices: ep.devices.clone(),
4738 grouped: crate::hybrid::StepEpGroupedDecode {
4739 projection,
4740 combine,
4741 },
4742 });
4743 eprintln!(
4744 "[step-tp-ep-grouped-prefill] prepare capacity={t} devices={:?} \
4745 shared_across_layers=true performance_claim=false",
4746 ep.devices,
4747 );
4748 }
4749 return execute(
4750 &mut shared
4751 .state
4752 .as_mut()
4753 .expect("Step grouped prefill state prepared above")
4754 .grouped,
4755 );
4756 }
4757
4758 let mut grouped = ep
4759 .grouped_decode
4760 .as_ref()
4761 .expect("grouped decode presence checked above")
4762 .lock()
4763 .map_err(|_| "Step grouped decode state lock is poisoned")?;
4764 return execute(&mut grouped);
4765 }
4766 if grouped_prefill_shape {
4767 return Err(
4768 "Step grouped prefill requires native-P2P expert-owner device arithmetic"
4769 .into(),
4770 );
4771 }
4772 if t == 1
4777 && crate::tp::step_nvfp4_dev_routes_enabled()?
4778 && crate::tp::step_tp_dev_router_enabled()?
4779 {
4780 if let Some(tp) = &m.step_tp {
4781 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
4782 let (sf, route_norm) = sigmoid;
4783 static D1_ROUTER: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4790 let d1_router = *D1_ROUTER.get_or_init(|| {
4791 std::env::var("MEMRA_DEV1_ROUTER").as_deref() == Ok("1")
4792 });
4793 if d1_router {
4794 let (sf_h, rn_h) = sigmoid;
4795 let n_ex = m.gate_exps.n_expert;
4796 let act_ct = m.active_count();
4797 let _ = tp.runtime.nvfp4_routes_prestage_with(
4798 bank,
4799 e,
4800 z,
4801 |rank1, in1, sel1, w1| {
4802 let mut guard = DEV1_ROUTER_REPS
4803 .lock()
4804 .map_err(|_| "dev1 router replica lock")?;
4805 let (reps, scratch) =
4806 guard.get_or_insert_with(|| (Default::default(), None));
4807 if !reps.contains_key(&il) {
4808 use cudarc::driver::DevicePtr;
4809 let (g1, p1, a1) = (
4810 rank1.htod(&vec![0.0f32; n_ex * n_embd])?,
4811 rank1.htod(&vec![0.0f32; n_ex])?,
4812 rank1.alloc_u8_uninit(n_ex)?,
4813 );
4814 for (src, dst_len, dst) in [
4815 (
4816 {
4817 let s = e.stream();
4818 let (p, _g) =
4819 m.gate_inp.float_data().device_ptr(&s);
4820 p as u64
4821 },
4822 n_ex * n_embd * 4,
4823 {
4824 let s = rank1.stream();
4825 let (p, _g) = g1.device_ptr(&s);
4826 p as u64
4827 },
4828 ),
4829 (
4830 {
4831 let s = e.stream();
4832 let (p, _g) = m.exp_probs_b_dev.device_ptr(&s);
4833 p as u64
4834 },
4835 n_ex * 4,
4836 {
4837 let s = rank1.stream();
4838 let (p, _g) = p1.device_ptr(&s);
4839 p as u64
4840 },
4841 ),
4842 (
4843 {
4844 let s = e.stream();
4845 let (p, _g) =
4846 m.active_experts_dev.device_ptr(&s);
4847 p as u64
4848 },
4849 n_ex,
4850 {
4851 let s = rank1.stream();
4852 let (p, _g) = a1.device_ptr(&s);
4853 p as u64
4854 },
4855 ),
4856 ] {
4857 crate::tp::raw_copy_bytes(dst, src, dst_len, rank1)?;
4858 }
4859 rank1.stream().synchronize()?;
4860 reps.insert(il, (g1, p1, a1));
4861 }
4862 if scratch.is_none() {
4863 *scratch = Some(rank1.htod(&vec![0.0f32; n_ex])?);
4864 }
4865 let (g1, p1, a1) = reps.get(&il).expect("armed above");
4866 let logits1 = scratch.as_mut().expect("armed above");
4867 rank1.router_gemv_into(g1, in1, logits1, n_embd, n_ex, 1)?;
4868 rank1.moe_router_sigmoid_topk_into(
4869 logits1, 1, n_ex, n_used, act_ct, p1, a1, sf_h, rn_h, sel1,
4870 w1,
4871 )?;
4872 Ok(true)
4873 },
4874 )?;
4875 } else {
4876 let _ = tp.runtime.nvfp4_routes_prestage(bank, e, z)?;
4877 }
4878 static SELW: std::sync::Mutex<
4882 Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>,
4883 > = std::sync::Mutex::new(None);
4884 let mut selw = SELW.lock().map_err(|_| "selw lock poisoned")?;
4885 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
4886 *selw = Some((
4887 e.ctx().ordinal(),
4888 e.htod_i32(&vec![0i32; n_used])?,
4889 e.htod(&vec![0.0f32; n_used])?,
4890 ));
4891 }
4892 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
4893 e.moe_router_sigmoid_topk_into(
4894 &logits,
4895 t,
4896 n_expert,
4897 n_used,
4898 m.active_count(),
4899 &m.exp_probs_b_dev,
4900 &m.active_experts_dev,
4901 sf,
4902 route_norm,
4903 sel_d,
4904 w_d,
4905 )?;
4906 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
4907 static SHEXP_OV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4911 let shexp_ov = *SHEXP_OV.get_or_init(|| {
4912 std::env::var("MEMRA_SHEXP_OVERLAP").as_deref() == Ok("1")
4913 });
4914 static SHEXP_D1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4918 let shexp_d1 = *SHEXP_D1.get_or_init(|| {
4919 std::env::var("MEMRA_SHEXP_DEV1").as_deref() == Ok("1")
4920 }) && tp.runtime.rank_engine(1).is_some();
4921 static TAIL3: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4925 let tail3 = *TAIL3
4926 .get_or_init(|| std::env::var("MEMRA_TAIL_ADD3").as_deref() != Ok("0"));
4927 let mut ov_issued = false;
4928 let mut d1_issued = false;
4929 let mut tail_folded = false;
4930 let mut output = if shexp_d1 {
4931 let rank1 = tp.runtime.rank_engine(1).expect("checked above");
4932 tp.runtime
4933 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
4934 bank,
4935 e,
4936 z,
4937 &sel_d,
4938 &w_d,
4939 n_used,
4940 tp.activation_limit,
4941 || {
4942 d1_issued = Self::shexp_dev1_issue(
4943 e, rank1, m, z, cfg, il, n_embd,
4944 )?;
4945 Ok(())
4946 },
4947 )?
4948 } else if shexp_ov {
4949 let post_add = if tail3 {
4954 Self::shexp_overlap_tail_ptrs(e, m, cfg, n_embd)?
4955 } else {
4956 None
4957 };
4958 let used_post = post_add.is_some();
4959 let out = tp
4960 .runtime
4961 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
4962 bank,
4963 e,
4964 z,
4965 &sel_d,
4966 &w_d,
4967 n_used,
4968 tp.activation_limit,
4969 || {
4970 ov_issued =
4971 Self::shexp_overlap_issue(e, m, z, cfg, il, n_embd)?;
4972 Ok(())
4973 },
4974 post_add,
4975 )?;
4976 if used_post && ov_issued {
4981 tail_folded = true; }
4983 out
4984 } else {
4985 tp.runtime.run_tensor_parallel_routes_nvfp4_device_routed(
4986 bank,
4987 e,
4988 z,
4989 &sel_d,
4990 &w_d,
4991 n_used,
4992 tp.activation_limit,
4993 )?
4994 };
4995 if output.len() != t * n_embd {
4996 return Err(format!(
4997 "Step tp routed output has {} values, expected {t}x{n_embd}",
4998 output.len()
4999 )
5000 .into());
5001 }
5002 if tail_folded {
5003 } else if d1_issued {
5005 Self::shexp_dev1_apply(e, &mut output, n_embd)?;
5006 } else if ov_issued {
5007 Self::shexp_overlap_apply(e, &mut output, n_embd)?;
5008 } else {
5009 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5010 }
5011 static DR_LOGGED: std::sync::atomic::AtomicU64 =
5012 std::sync::atomic::AtomicU64::new(0);
5013 let layer_bit = 1u64 << (il as u64 % 64);
5014 if DR_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5015 & layer_bit
5016 == 0
5017 {
5018 eprintln!(
5019 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5020 expert_transport={} native_p2p={} router=device \
5021 activation=host-canonical accumulation=host-canonical \
5022 output=e-device io=device performance_claim=false \
5023 (logged once per layer)",
5024 tp.devices,
5025 tp.runtime.transport_label(),
5026 tp.runtime.native_p2p(),
5027 );
5028 }
5029 return Ok(output);
5030 }
5031 }
5032 }
5033 static ROUTE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5037 static ROUTE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5038 let route_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
5039 let route_started = route_timing.then(std::time::Instant::now);
5040 let (selected, route_weights, input) = Self::moe_route_sigmoid_with_input(
5041 e,
5042 &logits,
5043 z,
5044 t,
5045 n_embd,
5046 n_expert,
5047 n_used,
5048 m.exp_probs_b.as_deref(),
5049 sigmoid,
5050 m.active_experts.as_deref(),
5051 )?;
5052 if let Some(started) = route_started {
5053 use std::sync::atomic::Ordering;
5054 let ns = ROUTE_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
5055 + started.elapsed().as_nanos() as u64;
5056 let calls = ROUTE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
5057 if calls % 430 == 0 {
5058 eprintln!(
5059 "[moe-route-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
5060 ns as f64 / 1.0e6,
5061 ns as f64 / calls as f64 / 1.0e3,
5062 );
5063 }
5064 }
5065 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5066 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5067 Self::trace_moe_input(e, il, t, n_embd, z)?;
5068 let selected = selected
5069 .iter()
5070 .map(|&expert| expert as usize)
5071 .collect::<Vec<_>>();
5072 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5077 if let Some(tp) = &m.step_tp {
5078 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5079 let mut output = tp.runtime.run_tensor_parallel_routes_nvfp4_device_io(
5080 bank,
5081 e,
5082 z,
5083 &selected,
5084 &route_weights,
5085 n_used,
5086 tp.activation_limit,
5087 )?;
5088 if output.len() != t * n_embd {
5089 return Err(format!(
5090 "Step tp routed output has {} values, expected {t}x{n_embd}",
5091 output.len()
5092 )
5093 .into());
5094 }
5095 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5096 static IO_LOGGED: std::sync::atomic::AtomicU64 =
5097 std::sync::atomic::AtomicU64::new(0);
5098 let layer_bit = 1u64 << (il as u64 % 64);
5099 if IO_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5100 & layer_bit
5101 == 0
5102 {
5103 eprintln!(
5104 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5105 expert_transport={} native_p2p={} activation=host-canonical \
5106 accumulation=host-canonical output=e-device io=device \
5107 performance_claim=false (logged once per layer)",
5108 tp.devices,
5109 tp.runtime.transport_label(),
5110 tp.runtime.native_p2p(),
5111 );
5112 }
5113 return Ok(output);
5114 }
5115 }
5116 }
5117 let (routed, mode, devices, transport, native_p2p) = if let Some(tp) = &m.step_tp {
5118 (
5119 match &tp.experts {
5120 crate::hybrid::StepTpExpertBank::E4m3(bank) => {
5121 tp.runtime.run_tensor_parallel_routes(
5122 bank,
5123 &input,
5124 t,
5125 &selected,
5126 &route_weights,
5127 n_used,
5128 )?
5129 }
5130 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => {
5131 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5132 tp.runtime.run_tensor_parallel_routes_nvfp4_device(
5133 bank,
5134 &input,
5135 &selected,
5136 &route_weights,
5137 n_used,
5138 tp.activation_limit,
5139 )?
5140 } else {
5141 tp.runtime.run_tensor_parallel_routes_nvfp4(
5142 bank,
5143 &input,
5144 t,
5145 &selected,
5146 &route_weights,
5147 n_used,
5148 tp.activation_limit,
5149 )?
5150 }
5151 }
5152 },
5153 "tp",
5154 &tp.devices,
5155 tp.runtime.transport_label(),
5156 tp.runtime.native_p2p(),
5157 )
5158 } else {
5159 let ep = m
5160 .step_ep
5161 .as_ref()
5162 .ok_or("Step distributed runtime has no EP or TP state")?;
5163 (
5164 match &ep.experts {
5165 crate::hybrid::StepEpExpertBank::E4m3(bank) => {
5166 ep.runtime.run_routed_experts(
5167 bank,
5168 &input,
5169 t,
5170 &selected,
5171 &route_weights,
5172 n_used,
5173 ep.activation_limit,
5174 )?
5175 }
5176 crate::hybrid::StepEpExpertBank::Nvfp4(bank) => {
5177 ep.runtime.run_routed_experts_nvfp4(
5178 bank,
5179 &input,
5180 t,
5181 &selected,
5182 &route_weights,
5183 n_used,
5184 ep.activation_limit,
5185 )?
5186 }
5187 },
5188 if ep.configured_by_tp { "tp-ep" } else { "ep" },
5189 &ep.devices,
5190 ep.runtime.transport_label(),
5191 ep.runtime.native_p2p(),
5192 )
5193 };
5194 if routed.len() != t * n_embd {
5195 return Err(format!(
5196 "Step {mode} routed output has {} values, expected {t}x{n_embd}",
5197 routed.len()
5198 )
5199 .into());
5200 }
5201 let mut output = e.htod(&routed)?;
5202 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5203 static STEP_LOGGED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5206 let layer_bit = 1u64 << (il as u64 % 64);
5207 if STEP_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed) & layer_bit
5208 == 0
5209 {
5210 eprintln!(
5211 "[step-{mode}] execute layer={il} tokens={t} devices={devices:?} \
5212 expert_transport={transport} native_p2p={native_p2p} \
5213 activation={} accumulation={} output={} \
5214 performance_claim=false (logged once per layer)",
5215 if let Some(ep) = &m.step_ep {
5216 ep.runtime.expert_activation_label()
5217 } else {
5218 "host-canonical"
5219 },
5220 if let Some(ep) = &m.step_ep {
5221 ep.runtime.expert_accumulation_label()
5222 } else {
5223 "host-canonical"
5224 },
5225 if let Some(ep) = &m.step_ep {
5226 ep.runtime.expert_output_label()
5227 } else {
5228 "host-accumulated"
5229 },
5230 );
5231 if let Some(ep) = &m.step_ep {
5232 if let Some(limit) = ep.activation_limit {
5233 eprintln!(
5234 "[step-ep-clamp] execute layer={il} tokens={t} routed_clamp={limit} \
5235 formula=min-silu-times-clamped-up performance_claim=false"
5236 );
5237 }
5238 }
5239 }
5240 return Ok(output);
5241 }
5242 if Self::sigmoid_resident_dev_eligible(e, m, cfg, sliding_gated_moe) {
5243 let moe = cfg.moe.as_ref().unwrap();
5244 let n_expert = moe.expert_count as usize;
5245 let n_used = moe.expert_used_count as usize;
5246 let sigmoid = cfg.sigmoid_router().unwrap();
5247 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5248 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5249 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
5250 }
5251 if t > 1 && moe_grouped_enabled(cfg, prefill) {
5254 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
5255 if std::env::var("MEMRA_MOE_GATE").is_ok() {
5260 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
5261 let g_host = e.dtoh(&grouped_out)?;
5262 let s_host = e.dtoh(&seq_out)?;
5263 let g_bytes: &[u8] = unsafe {
5264 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
5265 };
5266 let s_bytes: &[u8] = unsafe {
5267 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
5268 };
5269 if g_bytes == s_bytes {
5270 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
5271 } else {
5272 let diffs = g_host
5273 .iter()
5274 .zip(s_host.iter())
5275 .enumerate()
5276 .filter(|(_, (a, b))| a != b)
5277 .count();
5278 let maxdiff = g_host
5279 .iter()
5280 .zip(s_host.iter())
5281 .map(|(a, b)| (a - b).abs())
5282 .fold(0.0f32, f32::max);
5283 panic!(
5284 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
5285 g_host.len()
5286 );
5287 }
5288 }
5289 return Ok(grouped_out);
5290 }
5291 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
5292 }
5293
5294 fn sigmoid_resident_dev_eligible(
5295 e: &Engine,
5296 m: &MoeWeights,
5297 cfg: &ModelConfig,
5298 sliding_gated_moe: bool,
5299 ) -> bool {
5300 let Some(moe) = cfg.moe.as_ref() else {
5301 return false;
5302 };
5303 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5306 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
5307 std::env::var("MEMRA_MOE_STATS").is_ok()
5308 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5309 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5310 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
5311 || std::env::var("MEMRA_MOE_GATE").is_ok()
5312 });
5313 let resident_layout_supported = m.dev_exps.as_ref().is_some_and(|dev| {
5314 if dev.dev != e.ctx().ordinal() {
5315 return false;
5316 }
5317 let q8 = moe_q8_enabled()
5318 && q8_expert_supported(m.gate_exps.qtype)
5319 && q8_expert_supported(m.up_exps.qtype)
5320 && q8_expert_supported(m.down_exps.qtype);
5321 let fp8 = dev.fp8_blk.is_some()
5322 && m.gate_exps.qtype == crate::QT_F8_E4M3_BLK
5323 && m.up_exps.qtype == crate::QT_F8_E4M3_BLK
5324 && m.down_exps.qtype == crate::QT_F8_E4M3_BLK;
5325 q8 || fp8
5326 });
5327 sliding_gated_moe
5328 && sigmoid_router_enabled()
5329 && moe_dev_enabled()
5330 && moe_slab_enabled()
5331 && !observation_mode
5332 && moe.expert_used_count <= 8
5333 && m.has_uniform_expert_layout()
5334 && m.gate_exps.macros.is_none()
5335 && m.up_exps.macros.is_none()
5336 && m.down_exps.macros.is_none()
5337 && !m.has_macros
5338 && resident_layout_supported
5339 }
5340
5341 pub(crate) fn moe_ffn_sequential(
5343 e: &Engine,
5344 m: &MoeWeights,
5345 z: &CudaSlice<f32>,
5346 t: usize,
5347 cfg: &ModelConfig,
5348 il: u16,
5349 max_block: usize,
5350 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5351 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
5352 }
5353
5354 fn moe_router_logits(
5358 e: &Engine,
5359 m: &MoeWeights,
5360 z: &CudaSlice<f32>,
5361 t: usize,
5362 cfg: &ModelConfig,
5363 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5364 if t < PRIME_MIN_T {
5365 if crate::router_kernel_on() {
5367 e.router_gemv(
5368 m.gate_inp.float_data(),
5369 z,
5370 cfg.n_embd as usize,
5371 m.gate_exps.n_expert,
5372 t,
5373 )
5374 } else {
5375 e.matmul_decode_exact(&m.gate_inp, z, t)
5376 }
5377 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
5378 e.router_gemv(
5379 m.gate_inp.float_data(),
5380 z,
5381 cfg.n_embd as usize,
5382 m.gate_exps.n_expert,
5383 t,
5384 )
5385 } else {
5386 e.matmul(&m.gate_inp, z, t)
5387 }
5388 }
5389
5390 fn trace_moe_routes(
5394 il: u16,
5395 t: usize,
5396 sel_all: &[u32],
5397 weights: &[f32],
5398 ) -> Result<(), Box<dyn std::error::Error>> {
5399 use std::io::Write as _;
5400 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
5401 let mut f = std::fs::OpenOptions::new()
5402 .create(true)
5403 .append(true)
5404 .open(path)?;
5405 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
5406 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
5407 }
5408 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
5409 let mut f = std::fs::OpenOptions::new()
5410 .create(true)
5411 .append(true)
5412 .open(path)?;
5413 let pairs: Vec<String> = sel_all
5414 .iter()
5415 .zip(weights)
5416 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
5417 .collect();
5418 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
5419 }
5420 Ok(())
5421 }
5422
5423 #[allow(clippy::too_many_arguments)]
5424 fn trace_sigmoid_router_logits(
5425 e: &Engine,
5426 il: u16,
5427 t: usize,
5428 n_expert: usize,
5429 n_used: usize,
5430 logits: &CudaSlice<f32>,
5431 m: &MoeWeights,
5432 (scaling_factor, route_norm): (f32, bool),
5433 ) -> Result<(), Box<dyn std::error::Error>> {
5434 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
5435 return Ok(());
5436 }
5437 let logits = e.dtoh(logits)?;
5438 let active: Vec<u8> = m
5439 .active_experts
5440 .as_ref()
5441 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
5442 .unwrap_or_else(|| vec![1; n_expert]);
5443 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
5444 crate::sigrouter_contract::capture_served_logits(
5445 il as u32,
5446 t,
5447 n_expert,
5448 n_used,
5449 scaling_factor,
5450 route_norm,
5451 &active,
5452 &bias,
5453 &logits,
5454 )?;
5455 Ok(())
5456 }
5457
5458 fn trace_moe_input(
5463 e: &Engine,
5464 il: u16,
5465 t: usize,
5466 n_embd: usize,
5467 z: &CudaSlice<f32>,
5468 ) -> Result<(), Box<dyn std::error::Error>> {
5469 use std::io::Write as _;
5470 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
5471 return Ok(());
5472 };
5473 let values = active_matrix_values(z.len(), t, n_embd, "MoE input trace activation")?;
5474 let host = e.dtoh_view(&z.slice(0..values))?;
5475 let bytes = unsafe {
5476 std::slice::from_raw_parts(
5477 host.as_ptr().cast::<u8>(),
5478 host.len() * std::mem::size_of::<f32>(),
5479 )
5480 };
5481 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
5482 let mut state = state
5483 .lock()
5484 .map_err(|_| "MoE input trace writer lock is poisoned")?;
5485 if state.is_none() {
5486 let dir = std::path::PathBuf::from(&dir);
5487 std::fs::create_dir_all(&dir)?;
5488 let index = std::fs::OpenOptions::new()
5489 .create(true)
5490 .append(true)
5491 .open(dir.join("index.jsonl"))?;
5492 *state = Some(MoeInputTraceWriter {
5493 dir,
5494 index,
5495 payloads: std::collections::HashMap::new(),
5496 });
5497 }
5498 let writer = state.as_mut().unwrap();
5499 if writer.dir != std::path::Path::new(&dir) {
5500 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
5501 }
5502 let file_name = format!("layer-{il:03}.f32");
5503 if !writer.payloads.contains_key(&il) {
5504 let payload = std::fs::OpenOptions::new()
5505 .create(true)
5506 .append(true)
5507 .open(writer.dir.join(&file_name))?;
5508 let offset = payload.metadata()?.len();
5509 writer.payloads.insert(il, (payload, offset));
5510 }
5511 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
5512 let row_offset = *offset;
5513 payload.write_all(bytes)?;
5514 *offset += bytes.len() as u64;
5515 writeln!(
5516 writer.index,
5517 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
5518 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
5519 \"payload_bytes\":{}}}",
5520 bytes.len()
5521 )?;
5522 Ok(())
5523 }
5524
5525 #[allow(clippy::too_many_arguments)]
5526 pub(crate) fn moe_ffn_sequential_zq8(
5527 e: &Engine,
5528 m: &MoeWeights,
5529 z: &CudaSlice<f32>,
5530 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5531 t: usize,
5532 cfg: &ModelConfig,
5533 il: u16,
5534 max_block: usize,
5535 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5536 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
5537 let moe = cfg.moe.as_ref().unwrap();
5538 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);
5545 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
5546 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);
5549
5550 let lim_exp = cfg.clamp_exp_at(il as u32);
5553 let lim_shexp = cfg.clamp_shexp_at(il as u32);
5554 let use_cache = Engine::moe_cache_enabled();
5555 let uniform_experts = m.has_uniform_expert_layout();
5556 let moe_q8 = uniform_experts
5557 && moe_q8_enabled()
5558 && q8_expert_supported(m.gate_exps.qtype)
5559 && q8_expert_supported(m.up_exps.qtype)
5560 && q8_expert_supported(m.down_exps.qtype);
5561 let cpu_expert_requested = crate::cpu_experts::configured();
5568 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
5569 return Err(std::io::Error::other(
5570 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
5571 )
5572 .into());
5573 }
5574 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
5575 let freeze_cpu_residency = cpu_expert_requested
5581 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
5582 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
5583 .ok()
5584 .and_then(|value| value.parse::<usize>().ok())
5585 .is_some_and(|tokens| tokens > 0);
5586 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
5587 e.freeze_moe_cache();
5588 }
5589 let cache_frozen = use_cache && e.moe_cache_frozen();
5590 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
5591
5592 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5595 if let Some(sig) = cfg.sigmoid_router() {
5596 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
5597 }
5598
5599 let no_exp_macros = m.gate_exps.macros.is_none()
5638 && m.up_exps.macros.is_none()
5639 && m.down_exps.macros.is_none();
5640 if cfg.sigmoid_router().is_none()
5644 && cfg.m3.is_none()
5645 && cfg.hy3.is_none()
5646 && !cfg.swiglu_clamped_at(il as u32)
5647 && no_exp_macros
5648 && t > MOE_DEV_MAX_T
5652 && m.dev_exps.is_some()
5653 && moe_q8_enabled()
5654 && q8_expert_supported(m.gate_exps.qtype)
5655 && q8_expert_supported(m.up_exps.qtype)
5656 && q8_expert_supported(m.down_exps.qtype)
5657 && std::env::var("MEMRA_MOE_PAIRS")
5658 .map(|v| v != "0")
5659 .unwrap_or(true)
5660 && std::env::var("MEMRA_MOE_STATS").is_err()
5661 {
5662 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
5663 }
5664
5665 let dev_ok = uniform_experts
5683 && cfg.sigmoid_router().is_none()
5684 && cfg.m3.is_none()
5685 && cfg.hy3.is_none()
5686 && !cfg.swiglu_clamped_at(il as u32);
5687 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
5691 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5692 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5693 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
5694 if dev_ok
5695 && t <= MOE_DEV_MAX_T
5696 && m.dev_exps.is_some()
5697 && n_used <= 8
5698 && moe_dev_enabled()
5699 && !observe_routes
5700 {
5701 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5702 }
5703 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
5704 let row_ok = e.with_moe_cache(max_block, |c, eng| {
5705 if moe_prewarm_enabled() {
5706 c.prewarm_layer(il, m, eng)?;
5707 }
5708 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
5709 })?;
5710 if row_ok {
5711 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5712 }
5713 }
5714
5715 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
5717 if cpu_hybrid {
5718 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
5719 e,
5720 &logits,
5721 z,
5722 t,
5723 n_embd,
5724 n_expert,
5725 n_used,
5726 m.exp_probs_b.as_deref(),
5727 sig,
5728 m.active_experts.as_deref(),
5729 )?;
5730 (sel, w, Some(input))
5731 } else {
5732 let (sel, w) =
5733 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
5734 (sel, w, None)
5735 }
5736 } else {
5737 let (sel, w) =
5738 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
5739 (sel, w, None)
5740 };
5741 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
5742
5743 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
5747 Self::trace_moe_input(e, il, t, n_embd, z)?;
5748
5749 let worker_disk_prefetch =
5761 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
5762 let promote_worker_h2d =
5763 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
5764 if promote_worker_h2d {
5765 let mut selected_blocks = Vec::with_capacity(n_used * 3);
5766 for &ex in sel_all.iter().take(n_used) {
5767 let ex = ex as u16;
5768 selected_blocks.extend([
5769 BlockId::new(il, PROJ_GATE, ex),
5770 BlockId::new(il, PROJ_UP, ex),
5771 BlockId::new(il, PROJ_DOWN, ex),
5772 ]);
5773 }
5774 for &ex in sel_all.iter().take(n_used) {
5775 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
5776 }
5777 e.with_moe_cache(max_block, |cache, eng| {
5778 cache.promote_worker_reads_at_safe_boundary(
5779 &selected_blocks,
5780 &selected_blocks,
5781 eng,
5782 )?;
5783 Ok(())
5784 })?;
5785 }
5786
5787 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
5790 let mut cnt = vec![0u32; n_expert];
5791 for &s in sel_all.iter() {
5792 cnt[s as usize] += 1;
5793 }
5794 let total = sel_all.len() as f64;
5795 let mut h = 0.0f64;
5796 let mut active = 0usize;
5797 for &c in &cnt {
5798 if c > 0 {
5799 active += 1;
5800 let p = c as f64 / total;
5801 h -= p * p.log2();
5802 }
5803 }
5804 let maxc = cnt.iter().copied().max().unwrap_or(0);
5805 println!(
5806 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
5807 il,
5808 t,
5809 sel_all.len(),
5810 active,
5811 n_expert,
5812 h,
5813 (n_expert as f64).log2(),
5814 total / active.max(1) as f64,
5815 maxc
5816 );
5817 }
5818
5819 let gdec_may_fire = uniform_experts
5832 && use_cache
5833 && n_used <= 8
5834 && gdec_enabled()
5835 && !cfg.swiglu_clamped_at(il as u32);
5836 let slab_local = m
5852 .dev_exps
5853 .as_ref()
5854 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
5855 let slab_bases = slab_local.map(|d| {
5856 use cudarc::driver::DevicePtr;
5857 let s = e.stream();
5858 let (pg, _g0) = d.gate.device_ptr(&s);
5859 let (pu, _g1) = d.up.device_ptr(&s);
5860 let (pd, _g2) = d.down.device_ptr(&s);
5861 (pg as u64, pu as u64, pd as u64)
5862 });
5863 let slab_fused_may_fire = slab_bases.is_some()
5873 && n_used <= 8
5874 && gdec_enabled()
5875 && !cfg.swiglu_clamped_at(il as u32)
5876 && cfg.m3.is_none()
5877 && no_exp_macros
5878 && moe_q8;
5879 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
5882 e.uninit(t * n_embd)?
5883 } else {
5884 e.zeros(t * n_embd)?
5885 };
5886 let cpu_input = if cpu_hybrid {
5889 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
5890 } else {
5891 None
5892 };
5893
5894 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;
5902 let mut scratch_u: Option<CudaSlice<u8>> = None;
5903 let mut scratch_d: Option<CudaSlice<u8>> = None;
5904 let page_window = moe_page_prefetch_window();
5912
5913 for tok in 0..t {
5916 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
5917 let w = &w_all[tok * n_used..(tok + 1) * n_used];
5918 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
5920
5921 let no_macros = m.gate_exps.macros.is_none()
5935 && m.up_exps.macros.is_none()
5936 && m.down_exps.macros.is_none();
5937 if slab_fused_may_fire {
5947 let (pg, pu, pd) = slab_bases.unwrap();
5948 let mut gp = [0u64; 8];
5949 let mut up = [0u64; 8];
5950 let mut dp = [0u64; 8];
5951 for (j, &ex) in sel.iter().enumerate() {
5952 let ex = ex as usize;
5953 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
5954 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
5955 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
5956 }
5957 let mut wv = [0f32; 8];
5958 wv[..n_used].copy_from_slice(w);
5959 if tok_q8.is_none() {
5960 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
5961 }
5962 let (zq, zd) = tok_q8.as_ref().unwrap();
5963 let act = e.moe_gate_up_silu8_q8(
5964 crate::WPtr8(gp),
5965 crate::WPtr8(up),
5966 zq,
5967 zd,
5968 n_embd,
5969 n_ff_exp,
5970 n_used,
5971 m.gate_exps.qtype,
5972 m.up_exps.qtype,
5973 m.gate_exps.row_bytes,
5974 m.up_exps.row_bytes,
5975 )?;
5976 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
5977 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
5978 e.moe_down8_fma_q8(
5979 crate::WPtr8(dp),
5980 crate::F32x8(wv),
5981 &aq2,
5982 &ad2,
5983 &mut dst,
5984 n_ff_exp,
5985 n_embd,
5986 n_used,
5987 m.down_exps.qtype,
5988 m.down_exps.row_bytes,
5989 )?;
5990 continue;
5991 }
5992 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
5993 if tok_q8.is_none() {
5994 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
5995 }
5996 let (zq, zd) = tok_q8.as_ref().unwrap();
5997 if Self::moe_gdec_token_q8(
5998 e,
5999 m,
6000 il,
6001 max_block,
6002 zq,
6003 zd,
6004 sel,
6005 w,
6006 &mut moe_out,
6007 tok,
6008 n_embd,
6009 n_ff_exp,
6010 n_used,
6011 )? {
6012 continue;
6013 }
6014 } else if gdec_may_fire
6015 && cfg.m3.is_none()
6016 && no_macros
6017 && Self::moe_gdec_token(
6018 e,
6019 m,
6020 il,
6021 max_block,
6022 &zt,
6023 sel,
6024 w,
6025 &mut moe_out,
6026 tok,
6027 n_embd,
6028 n_ff_exp,
6029 n_used,
6030 )?
6031 {
6032 continue;
6033 }
6034
6035 if gdec_may_fire || slab_fused_may_fire {
6041 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6042 e.memset_zeros_view(&mut row)?;
6043 }
6044
6045 let mut cpu_mask = vec![false; sel.len()];
6051 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
6052 let gpu_resident = if use_cache {
6053 e.with_moe_cache(max_block, |cache, _| {
6054 Ok(sel
6055 .iter()
6056 .map(|&expert| {
6057 let expert = expert as u16;
6058 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
6059 .into_iter()
6060 .filter(|&projection| {
6061 cache
6062 .resident(BlockId::new(il, projection, expert))
6063 .is_some()
6064 })
6065 .count()
6066 })
6067 .collect::<Vec<_>>())
6068 })?
6069 } else {
6070 vec![0; sel.len()]
6071 };
6072 let mut cpu_selected = Vec::new();
6073 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
6074 if gpu_resident[index] != 3 {
6075 cpu_mask[index] = true;
6076 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
6077 let expert = expert as usize;
6078 cpu_selected.push((expert, route_weight));
6079 }
6080 }
6081 if crate::cpu_experts::predictor_enabled() {
6082 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6086 crate::cpu_experts::predictor_submit(il, row);
6087 }
6088 if cpu_selected.is_empty() {
6089 None
6090 } else {
6091 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6092 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
6093 .map_err(std::io::Error::other)?;
6094 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
6095 }
6096 } else {
6097 None
6098 };
6099
6100 let worker_window = worker_disk_prefetch
6101 .then(worker_prefetch_window)
6102 .unwrap_or(0);
6103 for (j, &ex) in sel.iter().enumerate() {
6104 if cpu_mask[j] {
6105 continue;
6106 }
6107 let ex = ex as usize;
6108 if let Some(d) = slab_local {
6115 let gl = m.gate_exps.expert_layout(ex);
6116 let ul = m.up_exps.expert_layout(ex);
6117 let dl = m.down_exps.expert_layout(ex);
6118 let (g0, u0, d0) = (
6119 ex * m.gate_exps.expert_stride,
6120 ex * m.up_exps.expert_stride,
6121 ex * m.down_exps.expert_stride,
6122 );
6123 let (gate, up) = if moe_q8 {
6124 if tok_q8.is_none() {
6125 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6126 }
6127 let (zq, zd) = tok_q8.as_ref().unwrap();
6128 (
6129 e.qmatvec_expert_q8(
6130 &d.gate,
6131 g0..g0 + gl.len,
6132 zq,
6133 zd,
6134 1,
6135 m.gate_exps.in_f,
6136 m.gate_exps.out_f,
6137 gl.qtype,
6138 gl.row_bytes,
6139 )?,
6140 e.qmatvec_expert_q8(
6141 &d.up,
6142 u0..u0 + ul.len,
6143 zq,
6144 zd,
6145 1,
6146 m.up_exps.in_f,
6147 m.up_exps.out_f,
6148 ul.qtype,
6149 ul.row_bytes,
6150 )?,
6151 )
6152 } else {
6153 (
6154 e.qmatvec_view(
6155 &d.gate,
6156 g0..g0 + gl.len,
6157 &zt,
6158 1,
6159 m.gate_exps.in_f,
6160 m.gate_exps.out_f,
6161 gl.qtype,
6162 gl.row_bytes,
6163 )?,
6164 e.qmatvec_view(
6165 &d.up,
6166 u0..u0 + ul.len,
6167 &zt,
6168 1,
6169 m.up_exps.in_f,
6170 m.up_exps.out_f,
6171 ul.qtype,
6172 ul.row_bytes,
6173 )?,
6174 )
6175 };
6176 let mut act = e.uninit(n_ff_exp)?;
6177 Self::ffn_act_lim(
6178 e,
6179 cfg,
6180 &gate,
6181 &up,
6182 m.gate_exps.macro_scale(ex),
6183 m.up_exps.macro_scale(ex),
6184 lim_exp,
6185 &mut act,
6186 n_ff_exp,
6187 )?;
6188 let y = if moe_q8 {
6189 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6190 e.qmatvec_expert_q8(
6191 &d.down,
6192 d0..d0 + dl.len,
6193 &aq2,
6194 &ad2,
6195 1,
6196 m.down_exps.in_f,
6197 m.down_exps.out_f,
6198 dl.qtype,
6199 dl.row_bytes,
6200 )?
6201 } else {
6202 let actv = act.slice(0..n_ff_exp);
6203 e.qmatvec_view(
6204 &d.down,
6205 d0..d0 + dl.len,
6206 &actv,
6207 1,
6208 m.down_exps.in_f,
6209 m.down_exps.out_f,
6210 dl.qtype,
6211 dl.row_bytes,
6212 )?
6213 };
6214 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6215 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6216 continue;
6217 }
6218 for next in page_prefetch_positions(j, sel.len(), page_window) {
6219 Self::moe_prefetch_host_expert(sel[next] as usize, m);
6220 }
6221 let keep = [
6222 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
6223 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
6224 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
6225 ];
6226 if worker_disk_prefetch && worker_window > 0 {
6227 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
6228 Self::moe_prefetch_disk_expert(
6229 e,
6230 il,
6231 sel[next] as usize,
6232 m,
6233 max_block,
6234 &keep,
6235 )?;
6236 }
6237 } else if cache_dispatch
6238 && !cpu_hybrid
6239 && moe_prefetch_enabled()
6240 && j + 1 < sel.len()
6241 {
6242 let next = sel[j + 1] as usize;
6243 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
6244 }
6245 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
6246 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
6247 if (gate_q8 || up_q8) && tok_q8.is_none() {
6250 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6251 }
6252 let gate = if gate_q8 {
6253 let (zq, zd) = tok_q8.as_ref().unwrap();
6254 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
6255 } else {
6256 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
6257 };
6258 let up = if up_q8 {
6259 let (zq, zd) = tok_q8.as_ref().unwrap();
6260 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
6261 } else {
6262 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
6263 };
6264 let mut act = e.uninit(n_ff_exp)?;
6265 Self::ffn_act_lim(
6266 e,
6267 cfg,
6268 &gate,
6269 &up,
6270 m.gate_exps.macro_scale(ex),
6271 m.up_exps.macro_scale(ex),
6272 lim_exp,
6273 &mut act,
6274 n_ff_exp,
6275 )?;
6276 let y = if down_q8 {
6277 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6278 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
6279 } else {
6280 let actv = act.slice(0..n_ff_exp);
6281 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
6282 };
6283 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6284 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6286 } else if cache_dispatch {
6287 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
6292 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
6293 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6295 e,
6296 cfg,
6297 &gate,
6298 &up,
6299 m.gate_exps.macro_scale(ex),
6300 m.up_exps.macro_scale(ex),
6301 lim_exp,
6302 &mut act,
6303 n_ff_exp,
6304 )?;
6305 let actv = act.slice(0..n_ff_exp);
6306 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
6307 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6308 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6310 } else if cache_frozen {
6311 let gate = Self::moe_frozen_gemm(
6316 e,
6317 il,
6318 PROJ_GATE,
6319 ex,
6320 m,
6321 max_block,
6322 &zt,
6323 &mut scratch_g,
6324 g_len,
6325 )?;
6326 let up = Self::moe_frozen_gemm(
6327 e,
6328 il,
6329 PROJ_UP,
6330 ex,
6331 m,
6332 max_block,
6333 &zt,
6334 &mut scratch_u,
6335 u_len,
6336 )?;
6337 let mut act = e.uninit(n_ff_exp)?;
6338 Self::ffn_act_lim(
6339 e,
6340 cfg,
6341 &gate,
6342 &up,
6343 m.gate_exps.macro_scale(ex),
6344 m.up_exps.macro_scale(ex),
6345 lim_exp,
6346 &mut act,
6347 n_ff_exp,
6348 )?;
6349 let actv = act.slice(0..n_ff_exp);
6350 let y = Self::moe_frozen_gemm(
6351 e,
6352 il,
6353 PROJ_DOWN,
6354 ex,
6355 m,
6356 max_block,
6357 &actv,
6358 &mut scratch_d,
6359 d_len,
6360 )?;
6361 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6362 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6363 } else {
6364 if scratch_g.is_none() {
6368 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
6369 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
6370 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
6371 }
6372 let (sg, su, sd) = (
6373 scratch_g.as_mut().unwrap(),
6374 scratch_u.as_mut().unwrap(),
6375 scratch_d.as_mut().unwrap(),
6376 );
6377 let gl = m.gate_exps.expert_layout(ex);
6378 let ul = m.up_exps.expert_layout(ex);
6379 let dl = m.down_exps.expert_layout(ex);
6380 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
6381 let gate = e.qmatvec_view(
6382 sg,
6383 0..gl.len,
6384 &zt,
6385 1,
6386 m.gate_exps.in_f,
6387 m.gate_exps.out_f,
6388 gl.qtype,
6389 gl.row_bytes,
6390 )?;
6391
6392 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
6393 let up = e.qmatvec_view(
6394 su,
6395 0..ul.len,
6396 &zt,
6397 1,
6398 m.up_exps.in_f,
6399 m.up_exps.out_f,
6400 ul.qtype,
6401 ul.row_bytes,
6402 )?;
6403
6404 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6406 e,
6407 cfg,
6408 &gate,
6409 &up,
6410 m.gate_exps.macro_scale(ex),
6411 m.up_exps.macro_scale(ex),
6412 lim_exp,
6413 &mut act,
6414 n_ff_exp,
6415 )?;
6416
6417 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
6418 let actv = act.slice(0..n_ff_exp);
6419 let y = e.qmatvec_view(
6420 sd,
6421 0..dl.len,
6422 &actv,
6423 1,
6424 m.down_exps.in_f,
6425 m.down_exps.out_f,
6426 dl.qtype,
6427 dl.row_bytes,
6428 )?;
6429
6430 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6431 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6432 }
6433 }
6434 if let Some(worker) = cpu_worker {
6435 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
6436 let cpu_output = e.htod(&cpu_output)?;
6437 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6438 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
6439 }
6440 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
6441 for (j, &ex) in sel.iter().enumerate() {
6442 if cpu_mask[j] {
6443 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
6444 }
6445 }
6446 }
6447 }
6448
6449 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6454 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6455 {
6456 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
6465 let (sg_gate, sg_up) = if t == 1 {
6466 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
6467 } else if verify_t {
6468 (
6469 e.matmul_decode_exact(gate_shexp, z, t)?,
6470 e.matmul_decode_exact(up_shexp, z, t)?,
6471 )
6472 } else {
6473 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
6475 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
6477 e,
6478 cfg,
6479 &sg_gate,
6480 &sg_up,
6481 1.0,
6482 1.0,
6483 lim_shexp,
6484 &mut sa,
6485 t * n_ff_sh,
6486 )?;
6487 let sh = if verify_t {
6488 e.matmul_decode_exact(down_shexp, &sa, t)?
6489 } else {
6490 e.matmul(down_shexp, &sa, t)?
6491 }; let g = match &m.gate_inp_shexp {
6505 Some(gate_inp_shexp) => {
6506 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
6507 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6508 } else {
6509 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6510 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
6512 g
6513 }
6514 }
6515 None => e.htod(&vec![1.0f32; t])?,
6516 };
6517 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6519 }
6520
6521 Ok(moe_out)
6522 }
6523
6524 pub fn stage1_h2d_per_token(&self) -> u64 {
6527 use crate::hybrid::Ffn;
6528 let n_used = self
6529 .cfg
6530 .moe
6531 .as_ref()
6532 .map(|m| m.expert_used_count as u64)
6533 .unwrap_or(0);
6534 let mut bytes = 0u64;
6535 for l in self.layers.iter() {
6536 if let Ffn::Moe(m) = &l.ffn {
6537 bytes += n_used
6538 * (m.gate_exps.max_expert_bytes()
6539 + m.up_exps.max_expert_bytes()
6540 + m.down_exps.max_expert_bytes()) as u64;
6541 }
6542 }
6543 bytes
6544 }
6545
6546 pub(crate) fn max_moe_block(&self) -> usize {
6550 use crate::hybrid::Ffn;
6551 let mut mx = 0usize;
6552 let mut scan = |ffn: &Ffn| {
6553 if let Ffn::Moe(m) = ffn {
6554 mx = mx
6555 .max(m.gate_exps.max_expert_bytes())
6556 .max(m.up_exps.max_expert_bytes())
6557 .max(m.down_exps.max_expert_bytes());
6558 }
6559 };
6560 for l in self.layers.iter() {
6561 scan(&l.ffn);
6562 }
6563 if let Some(mtp) = self.mtp.as_ref() {
6564 scan(&mtp.ffn);
6565 }
6566 mx
6567 }
6568
6569 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
6572 use crate::hybrid::Ffn;
6573 let mut sizes = Vec::new();
6574 let mut scan = |ffn: &Ffn| {
6575 let Ffn::Moe(m) = ffn else { return };
6576 for ex in 0..m.gate_exps.n_expert {
6577 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
6578 continue;
6579 }
6580 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
6581 let len = exps.expert_layout(ex).len;
6582 if len > 0 {
6583 sizes.push(len);
6584 }
6585 }
6586 }
6587 };
6588 for layer in &self.layers {
6589 scan(&layer.ffn);
6590 }
6591 if let Some(mtp) = &self.mtp {
6592 scan(&mtp.ffn);
6593 }
6594 sizes
6595 }
6596
6597 pub fn save_cpu_expert_residency_profile(
6603 &self,
6604 e: &Engine,
6605 path: &std::path::Path,
6606 ) -> Result<(), Box<dyn std::error::Error>> {
6607 let Some(ids) = e.export_moe_residency() else {
6608 return Err("no MoE residency cache to persist".into());
6609 };
6610 let mut body = format!(
6611 "memra-freeze-profile v1 max_block={} blocks={}\n",
6612 self.max_moe_block(),
6613 ids.len()
6614 );
6615 for (layer, proj, ex) in &ids {
6616 body.push_str(&format!("{layer} {proj} {ex}\n"));
6617 }
6618 let tmp = path.with_extension("tmp");
6619 std::fs::write(&tmp, body)?;
6620 std::fs::rename(&tmp, path)?;
6621 println!(
6622 "[moe-cache] freeze profile saved: {} blocks -> {}",
6623 ids.len(),
6624 path.display()
6625 );
6626 Ok(())
6627 }
6628
6629 pub fn restore_cpu_expert_residency_profile(
6633 &self,
6634 e: &Engine,
6635 path: &std::path::Path,
6636 ) -> Result<bool, Box<dyn std::error::Error>> {
6637 use crate::hybrid::Ffn;
6638 use crate::moe_cache::BlockId;
6639 let Ok(content) = std::fs::read_to_string(path) else {
6640 return Ok(false);
6641 };
6642 let mut lines = content.lines();
6643 let Some(header) = lines.next() else {
6644 return Ok(false);
6645 };
6646 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
6647 if !header.starts_with(&expected) {
6648 println!(
6649 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
6650 path.display()
6651 );
6652 return Ok(false);
6653 }
6654 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
6655 std::collections::HashMap::new();
6656 for line in lines {
6657 let mut fields = line.split_whitespace();
6658 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
6659 else {
6660 continue;
6661 };
6662 let (Ok(layer), Ok(proj), Ok(ex)) =
6663 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
6664 else {
6665 continue;
6666 };
6667 by_layer
6668 .entry(layer)
6669 .or_default()
6670 .push(BlockId::new(layer, proj, ex));
6671 }
6672 let requested: usize = by_layer.values().map(Vec::len).sum();
6673 if requested == 0 {
6674 return Ok(false);
6675 }
6676 let max_block = self.max_moe_block();
6677 let mut restaged = 0usize;
6678 let mut stage_layer =
6679 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
6680 let Ffn::Moe(m) = ffn else { return Ok(()) };
6681 let Some(ids) = by_layer.get(&layer_index) else {
6682 return Ok(());
6683 };
6684 e.with_moe_cache(max_block, |cache, eng| {
6685 for id in ids {
6686 if cache.restage_block(*id, m, eng)? {
6687 restaged += 1;
6688 }
6689 }
6690 Ok(())
6691 })
6692 };
6693 for (index, layer) in self.layers.iter().enumerate() {
6694 stage_layer(index as u16, &layer.ffn)?;
6695 }
6696 if let Some(mtp) = self.mtp.as_ref() {
6697 stage_layer(u16::MAX, &mtp.ffn)?;
6698 }
6699 e.freeze_moe_cache();
6700 println!(
6701 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
6702 path.display()
6703 );
6704 Ok(true)
6705 }
6706
6707 pub fn freeze_cpu_expert_residency(
6709 &self,
6710 e: &Engine,
6711 ) -> Result<(), Box<dyn std::error::Error>> {
6712 e.freeze_moe_cache();
6713 Ok(())
6714 }
6715
6716 pub fn ffn_act(
6724 e: &Engine,
6725 cfg: &ModelConfig,
6726 gate: &CudaSlice<f32>,
6727 up: &CudaSlice<f32>,
6728 act: &mut CudaSlice<f32>,
6729 n: usize,
6730 ) -> Result<(), Box<dyn std::error::Error>> {
6731 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
6732 }
6733
6734 #[allow(clippy::too_many_arguments)]
6738 pub(crate) fn ffn_act_scaled(
6739 e: &Engine,
6740 cfg: &ModelConfig,
6741 gate: &CudaSlice<f32>,
6742 up: &CudaSlice<f32>,
6743 gs: f32,
6744 us: f32,
6745 act: &mut CudaSlice<f32>,
6746 n: usize,
6747 ) -> Result<(), Box<dyn std::error::Error>> {
6748 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
6749 }
6750
6751 #[allow(clippy::too_many_arguments)]
6760 pub(crate) fn ffn_act_lim(
6761 e: &Engine,
6762 cfg: &ModelConfig,
6763 gate: &CudaSlice<f32>,
6764 up: &CudaSlice<f32>,
6765 gs: f32,
6766 us: f32,
6767 limit: Option<f32>,
6768 act: &mut CudaSlice<f32>,
6769 n: usize,
6770 ) -> Result<(), Box<dyn std::error::Error>> {
6771 if let Some(m3) = cfg.m3.as_ref() {
6772 debug_assert!(
6773 limit.is_none(),
6774 "m3 swigluoai and step35 clamp are different archs"
6775 );
6776 return e.swigluoai_mul_scaled(
6777 gate,
6778 up,
6779 gs,
6780 us,
6781 m3.swiglu_alpha,
6782 m3.swiglu_limit,
6783 act,
6784 n,
6785 );
6786 }
6787 if let Some(l) = limit {
6788 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
6789 }
6790 if gs == 1.0 && us == 1.0 {
6791 return e.silu_mul(gate, up, act, n);
6792 }
6793 e.silu_mul_scaled(gate, up, gs, us, act, n)
6794 }
6795
6796 fn moe_route(
6802 e: &Engine,
6803 logits: &CudaSlice<f32>,
6804 t: usize,
6805 n_expert: usize,
6806 n_used: usize,
6807 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6808 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
6809 }
6810
6811 #[allow(clippy::too_many_arguments)]
6819 fn moe_route_sigmoid_cfg(
6820 e: &Engine,
6821 logits: &CudaSlice<f32>,
6822 t: usize,
6823 n_expert: usize,
6824 n_used: usize,
6825 m: &MoeWeights,
6826 (sf, route_norm): (f32, bool),
6827 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6828 if sigmoid_router_enabled() {
6829 return e.moe_router_sigmoid_topk_host(
6830 logits,
6831 t,
6832 n_expert,
6833 n_used,
6834 m.active_count(),
6835 &m.exp_probs_b_dev,
6836 &m.active_experts_dev,
6837 sf,
6838 route_norm,
6839 );
6840 }
6841 let lg = e.dtoh(logits)?;
6842 Self::moe_route_sigmoid_host(
6843 &lg,
6844 t,
6845 n_expert,
6846 n_used,
6847 m.exp_probs_b.as_deref(),
6848 sf,
6849 route_norm,
6850 m.active_experts.as_deref(),
6851 )
6852 }
6853
6854 fn moe_route_cfg(
6857 e: &Engine,
6858 logits: &CudaSlice<f32>,
6859 t: usize,
6860 n_expert: usize,
6861 n_used: usize,
6862 active: Option<&[bool]>,
6863 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6864 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
6867 return e.moe_router_topk_host(logits, t, n_expert, n_used);
6868 }
6869 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
6872 let mut w_out = vec![0f32; t * n_used];
6873 for tok in 0..t {
6874 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
6875 let maxl = row
6877 .iter()
6878 .enumerate()
6879 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
6880 .map(|(_, &x)| x)
6881 .fold(f32::NEG_INFINITY, f32::max);
6882 let mut probs = vec![0f32; n_expert];
6883 let mut den = 0f32;
6884 for i in 0..n_expert {
6885 if active.is_some_and(|mask| !mask[i]) {
6886 continue;
6887 }
6888 let x = (row[i] - maxl).exp();
6889 probs[i] = x;
6890 den += x;
6891 }
6892 for p in probs.iter_mut() {
6893 *p /= den;
6894 }
6895 let mut idx: Vec<usize> = (0..n_expert)
6897 .filter(|&i| active.is_none_or(|mask| mask[i]))
6898 .collect();
6899 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
6900 let sl = &idx[..n_used];
6901 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
6902 let mut ws: f32 = wv.iter().sum();
6903 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
6905 *x /= ws;
6906 }
6907 for j in 0..n_used {
6908 sel[tok * n_used + j] = sl[j] as u32;
6909 w_out[tok * n_used + j] = wv[j];
6910 }
6911 }
6912 Ok((sel, w_out))
6913 }
6914
6915 #[allow(clippy::too_many_arguments)]
6916 fn moe_route_sigmoid_with_input(
6917 e: &Engine,
6918 logits: &CudaSlice<f32>,
6919 input: &CudaSlice<f32>,
6920 t: usize,
6921 in_features: usize,
6922 n_expert: usize,
6923 n_used: usize,
6924 bias: Option<&[f32]>,
6925 (sf, route_norm): (f32, bool),
6926 active: Option<&[bool]>,
6927 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6928 let logit_values =
6929 active_matrix_values(logits.len(), t, n_expert, "sigmoid router logits")?;
6930 let input_values =
6931 active_matrix_values(input.len(), t, in_features, "sigmoid router input")?;
6932 let (lg, input) = e.dtoh_pair_views(
6933 &logits.slice(0..logit_values),
6934 &input.slice(0..input_values),
6935 )?;
6936 let (sel, w) =
6937 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
6938 Ok((sel, w, input))
6939 }
6940
6941 pub fn start_moe_prefetch_predictor(
6946 &self,
6947 e: &Engine,
6948 cfg: &ModelConfig,
6949 ) -> Result<(), Box<dyn std::error::Error>> {
6950 use crate::hybrid::Ffn;
6951 let Some(sig) = cfg.sigmoid_router() else {
6952 return Err("prefetch predictor requires a sigmoid-router arch".into());
6953 };
6954 let resident: std::collections::HashSet<(u16, u8, u16)> = e
6955 .export_moe_residency()
6956 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
6957 .into_iter()
6958 .collect();
6959 let mut layers = Vec::new();
6960 for (index, layer) in self.layers.iter().enumerate() {
6961 let Ffn::Moe(m) = &layer.ffn else { continue };
6962 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
6963 continue;
6964 };
6965 let router = e.dtoh(data)?;
6966 let n_expert = m.gate_exps.n_expert;
6967 let n_embd = m.gate_exps.in_f;
6968 if router.len() != n_embd * n_expert {
6969 continue;
6970 }
6971 let build = |exps: &crate::model::HostExps| {
6972 (0..n_expert)
6973 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
6974 .collect::<Vec<_>>()
6975 };
6976 layers.push((
6977 index as u16,
6978 crate::cpu_experts::PredictLayerInit {
6979 router,
6980 bias: m.exp_probs_b.clone(),
6981 active: m.active_experts.clone(),
6982 n_embd,
6983 n_used: cfg
6984 .moe
6985 .as_ref()
6986 .map(|moe| moe.expert_used_count as usize)
6987 .ok_or("prefetch predictor requires MoE config")?,
6988 sig,
6989 weights_n_expert: n_expert,
6990 gate: build(&m.gate_exps),
6991 up: build(&m.up_exps),
6992 down: build(&m.down_exps),
6993 },
6994 ));
6995 }
6996 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
6997 }
6998
6999 #[allow(clippy::too_many_arguments)]
7002 pub fn moe_route_sigmoid_host_public(
7003 logits: &[f32],
7004 t: usize,
7005 n_expert: usize,
7006 n_used: usize,
7007 bias: Option<&[f32]>,
7008 sf: f32,
7009 route_norm: bool,
7010 active: Option<&[bool]>,
7011 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7012 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
7013 }
7014
7015 #[allow(clippy::too_many_arguments)]
7016 fn moe_route_sigmoid_host(
7017 lg: &[f32],
7018 t: usize,
7019 n_expert: usize,
7020 n_used: usize,
7021 bias: Option<&[f32]>,
7022 sf: f32,
7023 route_norm: bool,
7024 active: Option<&[bool]>,
7025 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7026 let active_count = active
7027 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
7028 .unwrap_or(n_expert);
7029 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
7030 if lg.len() != t * n_expert {
7031 return Err(format!(
7032 "sigmoid router logits length mismatch: got {}, expected {}",
7033 lg.len(),
7034 t * n_expert,
7035 )
7036 .into());
7037 }
7038 let mut sel = vec![0u32; t * n_used];
7039 let mut w_out = vec![0f32; t * n_used];
7040 for tok in 0..t {
7041 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7042 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
7043 let selsc: Vec<f32> = match bias {
7045 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
7046 None => scores.clone(),
7047 };
7048 let mut idx: Vec<usize> = (0..n_expert)
7049 .filter(|&i| active.is_none_or(|mask| mask[i]))
7050 .collect();
7051 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
7052 let sl = &idx[..n_used];
7053 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
7054 if route_norm {
7055 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
7056 for x in wv.iter_mut() {
7057 *x = *x / ws * sf;
7058 }
7059 } else {
7060 for x in wv.iter_mut() {
7061 *x *= sf;
7062 }
7063 }
7064 for j in 0..n_used {
7065 sel[tok * n_used + j] = sl[j] as u32;
7066 w_out[tok * n_used + j] = wv[j];
7067 }
7068 }
7069 Ok((sel, w_out))
7070 }
7071
7072 #[allow(clippy::too_many_arguments)]
7076 fn moe_ffn_sigmoid_dev(
7077 e: &Engine,
7078 m: &MoeWeights,
7079 z: &CudaSlice<f32>,
7080 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7081 logits: &CudaSlice<f32>,
7082 t: usize,
7083 cfg: &ModelConfig,
7084 il: u16,
7085 (scaling_factor, route_norm): (f32, bool),
7086 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7087 let moe = cfg.moe.as_ref().unwrap();
7088 let n_embd = cfg.n_embd as usize;
7089 let n_expert = moe.expert_count as usize;
7090 let n_used = moe.expert_used_count as usize;
7091 let n_ff_exp = moe.expert_ff_length as usize;
7092 let dev = m.dev_exps.as_ref().unwrap();
7093 debug_assert_eq!(dev.dev, e.ctx().ordinal());
7094 debug_assert!(m.has_uniform_expert_layout());
7095 debug_assert!(!m.has_macros);
7096
7097 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
7098 logits,
7099 t,
7100 n_expert,
7101 n_used,
7102 m.active_count(),
7103 &m.exp_probs_b_dev,
7104 &m.active_experts_dev,
7105 scaling_factor,
7106 route_norm,
7107 )?;
7108 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
7109 if let Some(fp8) = dev.fp8_blk.as_ref() {
7110 debug_assert_eq!(m.gate_exps.qtype, crate::QT_F8_E4M3_BLK);
7111 debug_assert_eq!(m.up_exps.qtype, crate::QT_F8_E4M3_BLK);
7112 debug_assert_eq!(m.down_exps.qtype, crate::QT_F8_E4M3_BLK);
7113 debug_assert_eq!(fp8.gate.rows, m.gate_exps.out_f.div_ceil(128));
7114 debug_assert_eq!(fp8.up.rows, m.up_exps.out_f.div_ceil(128));
7115 debug_assert_eq!(fp8.down.rows, m.down_exps.out_f.div_ceil(128));
7116
7117 let selected = e.dtoh_i32(&sel_d)?;
7124 let route_weights = e.dtoh(&w_d)?;
7125 let mut moe_out = e.zeros(t * n_embd)?;
7126 for tok in 0..t {
7127 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
7128 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7129 for j in 0..n_used {
7130 let pair = tok * n_used + j;
7131 let expert = selected[pair] as usize;
7132 let gate = Self::moe_resident_fp8_e4m3(
7133 e,
7134 &m.gate_exps,
7135 &dev.gate,
7136 &fp8.gate,
7137 expert,
7138 &zt,
7139 1,
7140 )?;
7141 let up = Self::moe_resident_fp8_e4m3(
7142 e, &m.up_exps, &dev.up, &fp8.up, expert, &zt, 1,
7143 )?;
7144 let mut act = e.uninit(n_ff_exp)?;
7145 Self::ffn_act_lim(
7146 e,
7147 cfg,
7148 &gate,
7149 &up,
7150 1.0,
7151 1.0,
7152 cfg.clamp_exp_at(il as u32),
7153 &mut act,
7154 n_ff_exp,
7155 )?;
7156 let act = act.slice(0..n_ff_exp);
7157 let down = Self::moe_resident_fp8_e4m3(
7158 e,
7159 &m.down_exps,
7160 &dev.down,
7161 &fp8.down,
7162 expert,
7163 &act,
7164 1,
7165 )?;
7166 e.axpy_into(&down, route_weights[pair], &mut dst, n_embd)?;
7167 }
7168 }
7169 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7170 eprintln!(
7171 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} \
7172 native=fp8blk-w8a8-e4m3-reference clamp={}",
7173 cfg.clamp_exp_at(il as u32).is_some(),
7174 );
7175 }
7176 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7177 return Ok(moe_out);
7178 }
7179 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
7180 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7181 (combined, combined)
7182 } else {
7183 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7184 };
7185 let (zq, zd) = match (t, zq8) {
7186 (1, Some((q, d))) => (q.clone(), d.clone()),
7187 _ => e.quantize_q8_1(z, t, n_embd)?,
7188 };
7189 let n_pairs = t * n_used;
7190 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
7191 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7195 let pair_tok_d = e.htod_i32(&pair_tok)?;
7196 let gate = e.moe_pairs_matvec_q8(
7197 &dev.ptr_row,
7198 0,
7199 &pair_tok_d,
7200 &sel_d,
7201 &zq,
7202 &zd,
7203 n_embd,
7204 n_ff_exp,
7205 n_expert,
7206 n_pairs,
7207 m.gate_exps.qtype,
7208 gate_row_bytes,
7209 )?;
7210 let up = e.moe_pairs_matvec_q8(
7211 &dev.ptr_row,
7212 1,
7213 &pair_tok_d,
7214 &sel_d,
7215 &zq,
7216 &zd,
7217 n_embd,
7218 n_ff_exp,
7219 n_expert,
7220 n_pairs,
7221 m.up_exps.qtype,
7222 up_row_bytes,
7223 )?;
7224 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7225 Self::ffn_act_lim(
7226 e,
7227 cfg,
7228 &gate,
7229 &up,
7230 1.0,
7231 1.0,
7232 cfg.clamp_exp_at(il as u32),
7233 &mut act,
7234 n_pairs * n_ff_exp,
7235 )?;
7236 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7237 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7238 let pair_self_d = e.htod_i32(&pair_self)?;
7239 let down = e.moe_pairs_matvec_q8(
7240 &dev.ptr_row,
7241 2,
7242 &pair_self_d,
7243 &sel_d,
7244 &aq2,
7245 &ad2,
7246 n_ff_exp,
7247 n_embd,
7248 n_expert,
7249 n_pairs,
7250 m.down_exps.qtype,
7251 m.down_exps.row_bytes,
7252 )?;
7253 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7254 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7255 let tok_off_d = e.htod_i32(&tok_off)?;
7256 let tok_ids_d = e.htod_i32(&tok_ids)?;
7257 let mut output = e.uninit(t * n_embd)?;
7258 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
7259 output
7260 } else {
7261 let act = e.moe_gate_up_silu8_dev_q8_rows(
7262 &dev.ptr_row,
7263 &sel_d,
7264 &zq,
7265 &zd,
7266 t,
7267 n_embd,
7268 n_ff_exp,
7269 n_used,
7270 n_expert,
7271 m.gate_exps.qtype,
7272 m.up_exps.qtype,
7273 gate_row_bytes,
7274 up_row_bytes,
7275 &m.dev_macros,
7276 )?;
7277 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7278 let mut output = e.uninit(t * n_embd)?;
7279 e.moe_down8_fma_dev_q8_rows_g(
7280 &dev.ptr_row,
7281 &sel_d,
7282 &w_d,
7283 &aq2,
7284 &ad2,
7285 &mut output,
7286 t,
7287 n_ff_exp,
7288 n_embd,
7289 n_used,
7290 n_expert,
7291 m.down_exps.qtype,
7292 m.down_exps.row_bytes,
7293 )?;
7294 output
7295 };
7296
7297 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7298 eprintln!(
7299 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
7300 cfg.clamp_exp_at(il as u32).is_some(),
7301 dev.gu_il,
7302 );
7303 }
7304 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7305 Ok(moe_out)
7306 }
7307
7308 #[allow(clippy::too_many_arguments)]
7309 fn moe_resident_fp8_e4m3(
7310 e: &Engine,
7311 exps: &crate::model::HostExps,
7312 bytes: &CudaSlice<u8>,
7313 scales: &crate::hybrid::DevExpertFp8ProjectionScales,
7314 expert: usize,
7315 x: &cudarc::driver::CudaView<f32>,
7316 m: usize,
7317 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7318 let layout = exps.expert_layout(expert);
7319 debug_assert_eq!(layout.qtype, crate::QT_F8_E4M3_BLK);
7320 debug_assert_eq!(scales.rows * scales.cols, scales.expert_stride);
7321 let byte_start = expert * exps.expert_stride;
7322 let scale_start = expert * scales.expert_stride;
7323 let weight = bytes.slice(byte_start..byte_start + layout.len);
7324 let scale = scales
7325 .scales
7326 .slice(scale_start..scale_start + scales.expert_stride);
7327 e.qmatvec_mmq_fp8_blk_view(&weight, &scale, x, m, exps.in_f, exps.out_f)
7328 }
7329
7330 fn moe_ffn_pairs(
7339 e: &Engine,
7340 m: &MoeWeights,
7341 z: &CudaSlice<f32>,
7342 logits: &CudaSlice<f32>,
7343 t: usize,
7344 cfg: &ModelConfig,
7345 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7346 let moe = cfg.moe.as_ref().unwrap();
7347 let n_embd = cfg.n_embd as usize;
7348 let n_expert = moe.expert_count as usize;
7349 let n_used = moe.expert_used_count as usize;
7350 let n_ff_exp = moe.expert_ff_length as usize;
7351 debug_assert!(
7356 !cfg.swiglu_clamped_anywhere(),
7357 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
7358 );
7359 let dev = m.dev_exps.as_ref().unwrap();
7360 let (rbg_d, rbu_d) = if dev.gu_il {
7362 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7363 (sxx, sxx)
7364 } else {
7365 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7366 };
7367
7368 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
7369 let n_pairs = t * n_used;
7370 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
7373 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
7374 let pair_w: Vec<f32> = w_all.clone();
7375 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7376 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7377 let pt = e.htod_i32(&pair_tok)?;
7378 let px = e.htod_i32(&pair_ex)?;
7379 let pw = e.htod(&pair_w)?;
7380 let toff = e.htod_i32(&tok_off)?;
7381 let tids = e.htod_i32(&tok_ids)?;
7382
7383 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7387 for p in 0..n_pairs {
7388 by_ex[pair_ex[p] as usize].push(p as i32);
7389 }
7390 let mut ex_ids: Vec<i32> = Vec::new();
7391 let mut ex_off: Vec<i32> = vec![0];
7392 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
7393 for (ex, list) in by_ex.iter().enumerate() {
7394 if list.is_empty() {
7395 continue;
7396 }
7397 ex_ids.push(ex as i32);
7398 ex_pairs.extend_from_slice(list);
7399 ex_off.push(ex_pairs.len() as i32);
7400 }
7401 let n_active = ex_ids.len();
7402 let exi = e.htod_i32(&ex_ids)?;
7403 let exo = e.htod_i32(&ex_off)?;
7404 let exp_d = e.htod_i32(&ex_pairs)?;
7405 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7426 let mma_t = *MMA_T.get_or_init(|| {
7427 std::env::var("MEMRA_MOE_MMA_T")
7428 .ok()
7429 .and_then(|v| v.parse().ok())
7430 .unwrap_or(16)
7431 });
7432 let use_mma = std::env::var("MEMRA_MOE_MMA")
7433 .map(|v| v != "0")
7434 .unwrap_or(true)
7435 && t >= mma_t
7436 && q8_expert_dec_supported(m.gate_exps.qtype)
7437 && q8_expert_dec_supported(m.up_exps.qtype)
7438 && q8_expert_dec_supported(m.down_exps.qtype)
7439 && n_embd % 256 == 0
7440 && n_ff_exp % 256 == 0;
7441 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
7457 && q8_expert_dec_supported(m.up_exps.qtype)
7458 && q8_expert_dec_supported(m.down_exps.qtype)
7459 && n_embd % 256 == 0
7460 && n_ff_exp % 256 == 0;
7461 let f16g_mode = crate::moe_f16g_mode();
7462 let f16g = f16g_mode != 0
7463 && t >= mma_t
7464 && (f16g_mode != 3 || !mma_capable)
7465 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
7466 && f16g_proj_ok(m.up_exps.qtype, n_embd)
7467 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
7468 if use_mma || f16g {
7469 let y_down = if f16g {
7477 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
7481 let csr_tok_d = e.htod_i32(&csr_tok)?;
7482 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
7483 let g_csr = e.moe_f16_grouped(
7484 &dev.ptr_row,
7485 0,
7486 n_expert,
7487 &exi,
7488 &ex_off,
7489 &exo,
7490 &z_f16,
7491 &z_s,
7492 n_embd,
7493 n_ff_exp,
7494 n_active,
7495 n_pairs,
7496 m.gate_exps.qtype,
7497 rbg_d,
7498 )?;
7499 let u_csr = e.moe_f16_grouped(
7500 &dev.ptr_row,
7501 1,
7502 n_expert,
7503 &exi,
7504 &ex_off,
7505 &exo,
7506 &z_f16,
7507 &z_s,
7508 n_embd,
7509 n_ff_exp,
7510 n_active,
7511 n_pairs,
7512 m.up_exps.qtype,
7513 rbu_d,
7514 )?;
7515 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
7516 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
7517 let d_csr = e.moe_f16_grouped(
7518 &dev.ptr_row,
7519 2,
7520 n_expert,
7521 &exi,
7522 &ex_off,
7523 &exo,
7524 &a_f16,
7525 &a_s,
7526 n_ff_exp,
7527 n_embd,
7528 n_active,
7529 n_pairs,
7530 m.down_exps.qtype,
7531 m.down_exps.row_bytes,
7532 )?;
7533 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
7534 } else {
7535 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
7537 let gate = e.mmq_iq_experts(
7538 &dev.ptr_row,
7539 0,
7540 n_expert,
7541 &exi,
7542 &exo,
7543 &exp_d,
7544 &pt,
7545 &z_scr,
7546 n_embd,
7547 n_ff_exp,
7548 n_active,
7549 n_pairs,
7550 t,
7551 m.gate_exps.qtype,
7552 rbg_d,
7553 )?;
7554 let up = e.mmq_iq_experts(
7555 &dev.ptr_row,
7556 1,
7557 n_expert,
7558 &exi,
7559 &exo,
7560 &exp_d,
7561 &pt,
7562 &z_scr,
7563 n_embd,
7564 n_ff_exp,
7565 n_active,
7566 n_pairs,
7567 t,
7568 m.up_exps.qtype,
7569 rbu_d,
7570 )?;
7571 let a_scr = if crate::moe_fuse_actq_on() {
7577 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
7578 } else {
7579 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7580 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
7581 };
7582 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7583 let pself = e.htod_i32(&pair_self)?;
7584 e.mmq_iq_experts(
7585 &dev.ptr_row,
7586 2,
7587 n_expert,
7588 &exi,
7589 &exo,
7590 &exp_d,
7591 &pself,
7592 &a_scr,
7593 n_ff_exp,
7594 n_embd,
7595 n_active,
7596 n_pairs,
7597 n_pairs,
7598 m.down_exps.qtype,
7599 m.down_exps.row_bytes,
7600 )?
7601 };
7602 let mut moe_out = e.uninit(t * n_embd)?;
7603 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7604 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7605 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7606 {
7607 let n_ff_sh = gate_shexp.out_features();
7608 let sg_gate = e.matmul(gate_shexp, z, t)?;
7609 let sg_up = e.matmul(up_shexp, z, t)?;
7610 let mut sa = e.uninit(t * n_ff_sh)?;
7611 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7612 let sh = e.matmul(down_shexp, &sa, t)?;
7613 let g = match &m.gate_inp_shexp {
7619 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7620 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7621 }
7622 Some(gate_inp_shexp) => {
7623 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7624 let mut g = e.uninit(t)?;
7625 e.sigmoid(&gs, &mut g, t)?;
7626 g
7627 }
7628 None => e.htod(&vec![1.0f32; t])?,
7629 };
7630 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7631 }
7632 return Ok(moe_out);
7633 }
7634
7635 let dec = std::env::var("MEMRA_MOE_DEC")
7638 .map(|v| v != "0")
7639 .unwrap_or(true);
7640 let matvec = |proj,
7641 exi: &_,
7642 exo: &_,
7643 exp_d: &_,
7644 pt: &_,
7645 aq: &_,
7646 ad: &_,
7647 inf,
7648 outf,
7649 qtype,
7650 rb|
7651 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7652 let dec = dec && q8_expert_dec_supported(qtype);
7654 if dec {
7655 e.moe_pairs_matvec_q8_dec(
7656 &dev.ptr_row,
7657 proj,
7658 exi,
7659 exo,
7660 exp_d,
7661 pt,
7662 aq,
7663 ad,
7664 inf,
7665 outf,
7666 n_expert,
7667 n_active,
7668 n_pairs,
7669 qtype,
7670 rb,
7671 )
7672 } else {
7673 e.moe_pairs_matvec_q8_em(
7674 &dev.ptr_row,
7675 proj,
7676 exi,
7677 exo,
7678 exp_d,
7679 pt,
7680 aq,
7681 ad,
7682 inf,
7683 outf,
7684 n_expert,
7685 n_active,
7686 n_pairs,
7687 qtype,
7688 rb,
7689 )
7690 }
7691 };
7692 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7693 let gate = matvec(
7694 0,
7695 &exi,
7696 &exo,
7697 &exp_d,
7698 &pt,
7699 &zq,
7700 &zd,
7701 n_embd,
7702 n_ff_exp,
7703 m.gate_exps.qtype,
7704 rbg_d,
7705 )?;
7706 let up = matvec(
7707 1,
7708 &exi,
7709 &exo,
7710 &exp_d,
7711 &pt,
7712 &zq,
7713 &zd,
7714 n_embd,
7715 n_ff_exp,
7716 m.up_exps.qtype,
7717 rbu_d,
7718 )?;
7719 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7720 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7721 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7723 let pself = e.htod_i32(&pair_self)?;
7724 let y_down = matvec(
7725 2,
7726 &exi,
7727 &exo,
7728 &exp_d,
7729 &pself,
7730 &aq2,
7731 &ad2,
7732 n_ff_exp,
7733 n_embd,
7734 m.down_exps.qtype,
7735 m.down_exps.row_bytes,
7736 )?;
7737 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7739
7740 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7744 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7745 {
7746 let n_ff_sh = gate_shexp.out_features();
7747 let step_exact = true;
7751 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
7752 let (sg_gate, sg_up) = if step_exact && t == 1 {
7753 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, None)?
7754 } else if verify_t {
7755 let mut fused = None;
7756 if crate::spec::spec_fused_t()
7757 && (2..=4).contains(&t)
7758 && e.uses_q8_1_fast(gate_shexp)
7759 && e.uses_q8_1_fast(up_shexp)
7760 {
7761 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7762 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
7763 }
7764 match fused {
7765 Some(pair) => pair,
7766 None => (
7767 e.matmul_decode_exact(gate_shexp, z, t)?,
7768 e.matmul_decode_exact(up_shexp, z, t)?,
7769 ),
7770 }
7771 } else {
7772 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
7773 };
7774 let mut sa = e.uninit(t * n_ff_sh)?;
7775 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7776 let sh = if verify_t {
7777 e.matmul_decode_exact(down_shexp, &sa, t)?
7778 } else {
7779 e.matmul(down_shexp, &sa, t)?
7780 };
7781 let g = match &m.gate_inp_shexp {
7786 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7787 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7788 }
7789 Some(gate_inp_shexp) => {
7790 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7791 let mut g = e.uninit(t)?;
7792 e.sigmoid(&gs, &mut g, t)?;
7793 g
7794 }
7795 None => e.htod(&vec![1.0f32; t])?,
7796 };
7797 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7798 }
7799 Ok(moe_out)
7800 }
7801
7802 #[allow(clippy::too_many_arguments)]
7804 #[allow(clippy::too_many_arguments)]
7805 fn moe_ffn_dev(
7806 e: &Engine,
7807 m: &MoeWeights,
7808 z: &CudaSlice<f32>,
7809 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7810 logits: &CudaSlice<f32>,
7811 t: usize,
7812 cfg: &ModelConfig,
7813 il: u16,
7814 max_block: usize,
7815 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7816 let moe = cfg.moe.as_ref().unwrap();
7817 let n_embd = cfg.n_embd as usize;
7818 let n_expert = moe.expert_count as usize;
7819 let n_used = moe.expert_used_count as usize;
7820 let n_ff_exp = moe.expert_ff_length as usize;
7821 debug_assert!(
7825 cfg.sigmoid_router().is_none(),
7826 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
7827 );
7828 debug_assert!(
7829 !cfg.swiglu_clamped_at(il as u32),
7830 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
7831 );
7832
7833 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
7835 if m.has_macros {
7838 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
7839 }
7840
7841 let mut moe_out = e.uninit(t * n_embd)?;
7843
7844 if let Some(dev) = m.dev_exps.as_ref() {
7847 let (rbg_d, rbu_d) = if dev.gu_il {
7850 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7851 (sxx, sxx)
7852 } else {
7853 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7854 };
7855 let q8 = moe_q8_enabled()
7856 && q8_expert_supported(m.gate_exps.qtype)
7857 && q8_expert_supported(m.up_exps.qtype)
7858 && q8_expert_supported(m.down_exps.qtype);
7859 let rows_arm = q8
7868 && t > 1
7869 && crate::spec::spec_m2()
7870 && n_ff_exp == 512
7871 && n_used <= 8
7872 && std::env::var("MEMRA_MOE_DEVQ8_GU")
7873 .map(|v| v.is_empty() || v == "v")
7874 .unwrap_or(true)
7875 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
7876 .map(|v| v.is_empty() || v == "w8h2v")
7877 .unwrap_or(true);
7878 let csr_mode = std::env::var("MEMRA_MOE_CSR")
7887 .ok()
7888 .and_then(|v| v.parse::<i32>().ok())
7889 .unwrap_or(1);
7890 let csr_nvfp4_probe = std::env::var("MEMRA_MOE_CSR_NVFP4").as_deref() == Ok("1");
7907 let csr_qt = |qt: i32| {
7908 qt == crate::QT_IQ4_XS
7909 || qt == crate::QT_IQ3_S
7910 || (csr_nvfp4_probe && qt == crate::QT_NVFP4)
7911 };
7912 let csr_t_max = if csr_nvfp4_probe { MOE_DEV_MAX_T } else { 10 };
7913 let csr_uniform = m.gate_exps.qtype == m.up_exps.qtype;
7914 let csr_arm = rows_arm
7915 && csr_mode > 0
7916 && t <= csr_t_max
7917 && csr_uniform
7918 && csr_qt(m.gate_exps.qtype)
7919 && csr_qt(m.up_exps.qtype)
7920 && csr_qt(m.down_exps.qtype);
7921 if csr_arm {
7922 if csr_mode == 2 {
7923 static ENGAGED: std::sync::Once = std::sync::Once::new();
7924 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
7925 }
7926 let n_pairs = t * n_used;
7927 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7928 let act = e.moe_gate_up_silu8_dev_q8_csr(
7929 &dev.ptr_row,
7930 &sel_d,
7931 &zq,
7932 &zd,
7933 n_pairs,
7934 n_embd,
7935 n_ff_exp,
7936 n_used,
7937 n_expert,
7938 m.gate_exps.qtype,
7939 m.up_exps.qtype,
7940 rbg_d,
7941 rbu_d,
7942 )?;
7943 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7944 e.moe_down8_fma_dev_q8_rows(
7948 &dev.ptr_row,
7949 &sel_d,
7950 &w_d,
7951 &aq2,
7952 &ad2,
7953 &mut moe_out,
7954 t,
7955 n_ff_exp,
7956 n_embd,
7957 n_used,
7958 n_expert,
7959 m.down_exps.qtype,
7960 m.down_exps.row_bytes,
7961 )?;
7962 if csr_mode == 2 {
7963 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
7965 &dev.ptr_row,
7966 &sel_d,
7967 &zq,
7968 &zd,
7969 t,
7970 n_embd,
7971 n_ff_exp,
7972 n_used,
7973 n_expert,
7974 m.gate_exps.qtype,
7975 m.up_exps.qtype,
7976 rbg_d,
7977 rbu_d,
7978 &m.dev_macros,
7979 )?;
7980 let mut out_r = e.uninit(t * n_embd)?;
7981 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
7982 e.moe_down8_fma_dev_q8_rows(
7983 &dev.ptr_row,
7984 &sel_d,
7985 &w_d,
7986 &aq2r,
7987 &ad2r,
7988 &mut out_r,
7989 t,
7990 n_ff_exp,
7991 n_embd,
7992 n_used,
7993 n_expert,
7994 m.down_exps.qtype,
7995 m.down_exps.row_bytes,
7996 )?;
7997 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
7998 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
7999 let ba = a1
8000 .iter()
8001 .zip(&a2)
8002 .filter(|(x, y)| x.to_bits() != y.to_bits())
8003 .count();
8004 let bo = o1
8005 .iter()
8006 .zip(&o2)
8007 .filter(|(x, y)| x.to_bits() != y.to_bits())
8008 .count();
8009 if ba + bo > 0 {
8010 eprintln!(
8011 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
8012 a1.len(),
8013 o1.len()
8014 );
8015 let sel_h = e.dtoh_i32(&sel_d)?;
8017 let mut shown = 0;
8018 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
8019 if x.to_bits() != y.to_bits() && shown < 4 {
8020 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
8021 let ex = sel_h[p];
8022 let npx = sel_h.iter().filter(|&&v| v == ex).count();
8023 eprintln!(
8024 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
8025 );
8026 shown += 1;
8027 }
8028 }
8029 std::process::exit(3);
8030 }
8031 }
8032 } else if rows_arm {
8033 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
8036 use std::sync::atomic::{AtomicU64, Ordering};
8037 static PAIRS: AtomicU64 = AtomicU64::new(0);
8038 static UNIQ: AtomicU64 = AtomicU64::new(0);
8039 static CALLS: AtomicU64 = AtomicU64::new(0);
8040 let sel_h = e.dtoh_i32(&sel_d)?;
8041 let mut u: Vec<i32> = sel_h.clone();
8042 u.sort_unstable();
8043 u.dedup();
8044 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
8045 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
8046 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
8047 if c % 480 == 0 {
8048 let p = PAIRS.load(Ordering::Relaxed);
8049 let q = UNIQ.load(Ordering::Relaxed);
8050 eprintln!(
8051 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
8052 q as f64 / p as f64
8053 );
8054 }
8055 }
8056 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8057 let act = e.moe_gate_up_silu8_dev_q8_rows(
8058 &dev.ptr_row,
8059 &sel_d,
8060 &zq,
8061 &zd,
8062 t,
8063 n_embd,
8064 n_ff_exp,
8065 n_used,
8066 n_expert,
8067 m.gate_exps.qtype,
8068 m.up_exps.qtype,
8069 rbg_d,
8070 rbu_d,
8071 &m.dev_macros,
8072 )?;
8073 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8074 e.moe_down8_fma_dev_q8_rows(
8075 &dev.ptr_row,
8076 &sel_d,
8077 &w_d,
8078 &aq2,
8079 &ad2,
8080 &mut moe_out,
8081 t,
8082 n_ff_exp,
8083 n_embd,
8084 n_used,
8085 n_expert,
8086 m.down_exps.qtype,
8087 m.down_exps.row_bytes,
8088 )?;
8089 } else {
8090 for tok in 0..t {
8091 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8092 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8093 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8094 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8095 if q8 {
8096 let (zq, zd) = match (t, zq8) {
8097 (1, Some((q, d))) => (q.clone(), d.clone()),
8098 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
8099 };
8100 let act = e.moe_gate_up_silu8_dev_q8(
8101 &dev.ptr_row,
8102 &selt,
8103 &zq,
8104 &zd,
8105 n_embd,
8106 n_ff_exp,
8107 n_used,
8108 n_expert,
8109 m.gate_exps.qtype,
8110 m.up_exps.qtype,
8111 rbg_d,
8112 rbu_d,
8113 &m.dev_macros,
8114 )?;
8115 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8116 e.moe_down8_fma_dev_q8(
8117 &dev.ptr_row,
8118 &selt,
8119 &wt,
8120 &aq2,
8121 &ad2,
8122 &mut dst,
8123 n_ff_exp,
8124 n_embd,
8125 n_used,
8126 n_expert,
8127 m.down_exps.qtype,
8128 m.down_exps.row_bytes,
8129 )?;
8130 } else {
8131 let act = e.moe_gate_up_silu8_dev(
8132 &dev.ptr_row,
8133 &selt,
8134 &zt,
8135 n_embd,
8136 n_ff_exp,
8137 n_used,
8138 n_expert,
8139 m.gate_exps.qtype,
8140 m.up_exps.qtype,
8141 rbg_d,
8142 rbu_d,
8143 &m.dev_macros,
8144 )?;
8145 e.moe_down8_fma_dev(
8146 &dev.ptr_row,
8147 &selt,
8148 &wt,
8149 &act,
8150 &mut dst,
8151 n_ff_exp,
8152 n_embd,
8153 n_used,
8154 n_expert,
8155 m.down_exps.qtype,
8156 m.down_exps.row_bytes,
8157 )?;
8158 }
8159 }
8160 }
8161 } else {
8162 let q8 = moe_q8_enabled()
8169 && q8_expert_supported(m.gate_exps.qtype)
8170 && q8_expert_supported(m.up_exps.qtype)
8171 && q8_expert_supported(m.down_exps.qtype);
8172 e.with_moe_cache(max_block, |c, eng| {
8173 let row = c
8174 .layer_dev_row(il, n_expert, eng)?
8175 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
8176 for tok in 0..t {
8177 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8178 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8179 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8180 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8181 if q8 {
8182 let (zq, zd) = match (t, zq8) {
8183 (1, Some((q, d))) => (q.clone(), d.clone()),
8184 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
8185 };
8186 let act = eng.moe_gate_up_silu8_dev_q8(
8187 row,
8188 &selt,
8189 &zq,
8190 &zd,
8191 n_embd,
8192 n_ff_exp,
8193 n_used,
8194 n_expert,
8195 m.gate_exps.qtype,
8196 m.up_exps.qtype,
8197 m.gate_exps.row_bytes,
8198 m.up_exps.row_bytes,
8199 &m.dev_macros,
8200 )?;
8201 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
8202 eng.moe_down8_fma_dev_q8(
8203 row,
8204 &selt,
8205 &wt,
8206 &aq2,
8207 &ad2,
8208 &mut dst,
8209 n_ff_exp,
8210 n_embd,
8211 n_used,
8212 n_expert,
8213 m.down_exps.qtype,
8214 m.down_exps.row_bytes,
8215 )?;
8216 } else {
8217 let act = eng.moe_gate_up_silu8_dev(
8218 row,
8219 &selt,
8220 &zt,
8221 n_embd,
8222 n_ff_exp,
8223 n_used,
8224 n_expert,
8225 m.gate_exps.qtype,
8226 m.up_exps.qtype,
8227 m.gate_exps.row_bytes,
8228 m.up_exps.row_bytes,
8229 &m.dev_macros,
8230 )?;
8231 eng.moe_down8_fma_dev(
8232 row,
8233 &selt,
8234 &wt,
8235 &act,
8236 &mut dst,
8237 n_ff_exp,
8238 n_embd,
8239 n_used,
8240 n_expert,
8241 m.down_exps.qtype,
8242 m.down_exps.row_bytes,
8243 )?;
8244 }
8245 }
8246 c.hits += (t * 3 * n_used) as u64;
8248 Ok(())
8249 })?;
8250 }
8251
8252 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8257 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8258 {
8259 let n_ff_sh = gate_shexp.out_features();
8260 let verify_t = t > 1 && t < PRIME_MIN_T;
8263 let (sg_gate, sg_up) = if t == 1 {
8264 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
8265 } else if verify_t {
8266 let mut fused = None;
8270 if crate::spec::spec_fused_t()
8271 && (2..=4).contains(&t)
8272 && e.uses_q8_1_fast(gate_shexp)
8273 && e.uses_q8_1_fast(up_shexp)
8274 {
8275 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8276 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8277 }
8278 match fused {
8279 Some(pair) => pair,
8280 None => (
8281 e.matmul_decode_exact(gate_shexp, z, t)?,
8282 e.matmul_decode_exact(up_shexp, z, t)?,
8283 ),
8284 }
8285 } else {
8286 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8287 };
8288 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8290 let sh = if verify_t {
8291 e.matmul_decode_exact(down_shexp, &sa, t)?
8292 } else {
8293 e.matmul(down_shexp, &sa, t)?
8294 };
8295 let g = match &m.gate_inp_shexp {
8299 Some(gate_inp_shexp) => {
8300 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
8303 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8304 } else {
8305 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8306 let mut g = e.uninit(t)?;
8307 e.sigmoid(&gs, &mut g, t)?;
8308 g
8309 }
8310 }
8311 None => e.htod(&vec![1.0f32; t])?,
8312 };
8313 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8314 }
8315
8316 Ok(moe_out)
8317 }
8318
8319 #[allow(clippy::too_many_arguments)]
8329 #[allow(clippy::too_many_arguments)]
8332 fn moe_gdec_token_q8(
8333 e: &Engine,
8334 m: &MoeWeights,
8335 il: u16,
8336 max_block: usize,
8337 zq: &CudaSlice<i8>,
8338 zd: &CudaSlice<f32>,
8339 sel: &[u32],
8340 w: &[f32],
8341 moe_out: &mut CudaSlice<f32>,
8342 tok: usize,
8343 n_embd: usize,
8344 n_ff_exp: usize,
8345 n_used: usize,
8346 ) -> Result<bool, Box<dyn std::error::Error>> {
8347 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8348 use cudarc::driver::DevicePtr;
8349 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8350 let mut g = [0u64; 8];
8351 let mut u = [0u64; 8];
8352 let mut d = [0u64; 8];
8353 for (j, &ex) in sel.iter().enumerate() {
8354 let ex = ex as u16;
8355 let (Some(sg), Some(su), Some(sd)) = (
8356 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8357 c.resident(BlockId::new(il, PROJ_UP, ex)),
8358 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8359 ) else {
8360 return Ok(None);
8361 };
8362 let __s = eng.stream();
8363 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8364 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8365 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8366 g[j] = pg as u64;
8367 u[j] = pu as u64;
8368 d[j] = pd as u64;
8369 }
8370 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8371 for &ex in sel {
8372 let ex = ex as u16;
8373 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8374 c.note_profile_hit(BlockId::new(il, proj, ex));
8375 }
8376 }
8377 }
8378 c.hits += (3 * n_used) as u64;
8379 Ok(Some((g, u, d)))
8380 })?;
8381 let Some((g, u, d)) = ptrs else {
8382 return Ok(false);
8383 };
8384 let mut wv = [0f32; 8];
8385 wv[..n_used].copy_from_slice(w);
8386 let act = e.moe_gate_up_silu8_q8(
8387 crate::WPtr8(g),
8388 crate::WPtr8(u),
8389 zq,
8390 zd,
8391 n_embd,
8392 n_ff_exp,
8393 n_used,
8394 m.gate_exps.qtype,
8395 m.up_exps.qtype,
8396 m.gate_exps.row_bytes,
8397 m.up_exps.row_bytes,
8398 )?;
8399 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8401 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8402 e.moe_down8_fma_q8(
8403 crate::WPtr8(d),
8404 crate::F32x8(wv),
8405 &aq2,
8406 &ad2,
8407 &mut dst,
8408 n_ff_exp,
8409 n_embd,
8410 n_used,
8411 m.down_exps.qtype,
8412 m.down_exps.row_bytes,
8413 )?;
8414 Ok(true)
8415 }
8416
8417 fn moe_gdec_token(
8418 e: &Engine,
8419 m: &MoeWeights,
8420 il: u16,
8421 max_block: usize,
8422 zt: &cudarc::driver::CudaView<f32>,
8423 sel: &[u32],
8424 w: &[f32],
8425 moe_out: &mut CudaSlice<f32>,
8426 tok: usize,
8427 n_embd: usize,
8428 n_ff_exp: usize,
8429 n_used: usize,
8430 ) -> Result<bool, Box<dyn std::error::Error>> {
8431 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8432 use cudarc::driver::DevicePtr;
8433 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8435 let mut g = [0u64; 8];
8436 let mut u = [0u64; 8];
8437 let mut d = [0u64; 8];
8438 for (j, &ex) in sel.iter().enumerate() {
8439 let ex = ex as u16;
8440 let (Some(sg), Some(su), Some(sd)) = (
8441 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8442 c.resident(BlockId::new(il, PROJ_UP, ex)),
8443 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8444 ) else {
8445 return Ok(None);
8446 };
8447 let __s = eng.stream();
8448 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8449 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8450 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8451 g[j] = pg as u64;
8452 u[j] = pu as u64;
8453 d[j] = pd as u64;
8454 }
8455 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8456 for &ex in sel {
8457 let ex = ex as u16;
8458 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8459 c.note_profile_hit(BlockId::new(il, proj, ex));
8460 }
8461 }
8462 }
8463 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
8465 })?;
8466 let Some((g, u, d)) = ptrs else {
8467 return Ok(false);
8468 };
8469 let mut wv = [0f32; 8];
8470 wv[..n_used].copy_from_slice(w);
8471 let act = e.moe_gate_up_silu8(
8473 crate::WPtr8(g),
8474 crate::WPtr8(u),
8475 zt,
8476 n_embd,
8477 n_ff_exp,
8478 n_used,
8479 m.gate_exps.qtype,
8480 m.up_exps.qtype,
8481 m.gate_exps.row_bytes,
8482 m.up_exps.row_bytes,
8483 )?;
8484 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8485 e.moe_down8_fma_into(
8486 crate::WPtr8(d),
8487 crate::F32x8(wv),
8488 &act,
8489 &mut dst,
8490 n_ff_exp,
8491 n_embd,
8492 n_used,
8493 m.down_exps.qtype,
8494 m.down_exps.row_bytes,
8495 )?;
8496 Ok(true)
8497 }
8498
8499 fn moe_cached_gemm_q8(
8504 e: &Engine,
8505 il: u16,
8506 proj: u8,
8507 ex: usize,
8508 m: &MoeWeights,
8509 max_block: usize,
8510 aq: &CudaSlice<i8>,
8511 ad: &CudaSlice<f32>,
8512 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8513 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8514 let exps = match proj {
8515 PROJ_GATE => &m.gate_exps,
8516 PROJ_UP => &m.up_exps,
8517 _ => &m.down_exps,
8518 };
8519 let layout = exps.expert_layout(ex);
8520 let id = BlockId::new(il, proj, ex as u16);
8521 let source = exps.expert_source(ex);
8522 e.with_moe_cache(max_block, |c, eng| {
8523 let slot = c.dispatch_source(id, source, eng)?;
8524 let DispatchSlot::Resident(sl) = slot;
8525 let buf = c.slot(sl);
8526 eng.qmatvec_expert_q8(
8527 buf,
8528 0..layout.len,
8529 aq,
8530 ad,
8531 1,
8532 exps.in_f,
8533 exps.out_f,
8534 layout.qtype,
8535 layout.row_bytes,
8536 )
8537 })
8538 }
8539
8540 fn moe_cached_gemm(
8541 e: &Engine,
8542 il: u16,
8543 proj: u8,
8544 ex: usize,
8545 m: &MoeWeights,
8546 max_block: usize,
8547 x: &cudarc::driver::CudaView<f32>,
8548 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8549 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8550 let exps = match proj {
8551 PROJ_GATE => &m.gate_exps,
8552 PROJ_UP => &m.up_exps,
8553 _ => &m.down_exps,
8554 };
8555 let layout = exps.expert_layout(ex);
8556 let id = BlockId::new(il, proj, ex as u16);
8557 let source = exps.expert_source(ex);
8558 e.with_moe_cache(max_block, |c, eng| {
8560 let slot = c.dispatch_source(id, source, eng)?;
8561 let DispatchSlot::Resident(sl) = slot;
8564 let buf = c.slot(sl);
8565 eng.qmatvec_view(
8566 buf,
8567 0..layout.len,
8568 x,
8569 1,
8570 exps.in_f,
8571 exps.out_f,
8572 layout.qtype,
8573 layout.row_bytes,
8574 )
8575 })
8576 }
8577
8578 fn moe_profile_admit_expert(
8582 e: &Engine,
8583 il: u16,
8584 ex: usize,
8585 m: &MoeWeights,
8586 max_block: usize,
8587 ) -> Result<(), Box<dyn std::error::Error>> {
8588 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8589 e.with_moe_cache(max_block, |cache, eng| {
8590 for (proj, exps) in [
8591 (PROJ_GATE, &m.gate_exps),
8592 (PROJ_UP, &m.up_exps),
8593 (PROJ_DOWN, &m.down_exps),
8594 ] {
8595 let id = BlockId::new(il, proj, ex as u16);
8596 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
8597 }
8598 Ok(())
8599 })
8600 }
8601
8602 #[allow(clippy::too_many_arguments)]
8605 fn moe_frozen_gemm(
8606 e: &Engine,
8607 il: u16,
8608 proj: u8,
8609 ex: usize,
8610 m: &MoeWeights,
8611 max_block: usize,
8612 x: &cudarc::driver::CudaView<f32>,
8613 scratch: &mut Option<CudaSlice<u8>>,
8614 scratch_len: usize,
8615 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8616 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
8617 let exps = match proj {
8618 PROJ_GATE => &m.gate_exps,
8619 PROJ_UP => &m.up_exps,
8620 _ => &m.down_exps,
8621 };
8622 let layout = exps.expert_layout(ex);
8623 let id = BlockId::new(il, proj, ex as u16);
8624 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
8625 let Some(slot) = cache.resident(id) else {
8626 return Ok(None);
8627 };
8628 let buf = cache.slot(slot);
8629 Ok(Some(eng.qmatvec_view(
8630 buf,
8631 0..layout.len,
8632 x,
8633 1,
8634 exps.in_f,
8635 exps.out_f,
8636 layout.qtype,
8637 layout.row_bytes,
8638 )?))
8639 })? {
8640 return Ok(output);
8641 }
8642 if scratch.is_none() {
8643 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
8644 }
8645 let scratch = scratch.as_mut().unwrap();
8646 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
8647 e.qmatvec_view(
8648 scratch,
8649 0..layout.len,
8650 x,
8651 1,
8652 exps.in_f,
8653 exps.out_f,
8654 layout.qtype,
8655 layout.row_bytes,
8656 )
8657 }
8658
8659 fn moe_prefetch_expert(
8660 e: &Engine,
8661 il: u16,
8662 ex: usize,
8663 m: &MoeWeights,
8664 max_block: usize,
8665 keep: &[crate::moe_cache::BlockId],
8666 ) -> Result<(), Box<dyn std::error::Error>> {
8667 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8668 e.with_moe_cache(max_block, |c, eng| {
8669 for (proj, exps) in [
8670 (PROJ_GATE, &m.gate_exps),
8671 (PROJ_UP, &m.up_exps),
8672 (PROJ_DOWN, &m.down_exps),
8673 ] {
8674 let id = BlockId::new(il, proj, ex as u16);
8675 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
8676 }
8677 Ok(())
8678 })
8679 }
8680
8681 fn moe_prefetch_disk_expert(
8684 e: &Engine,
8685 il: u16,
8686 ex: usize,
8687 m: &MoeWeights,
8688 max_block: usize,
8689 keep: &[crate::moe_cache::BlockId],
8690 ) -> Result<(), Box<dyn std::error::Error>> {
8691 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8692 e.with_moe_cache(max_block, |c, eng| {
8693 for (proj, exps) in [
8694 (PROJ_GATE, &m.gate_exps),
8695 (PROJ_UP, &m.up_exps),
8696 (PROJ_DOWN, &m.down_exps),
8697 ] {
8698 let source = exps.expert_source(ex);
8699 if let crate::model::ExpertSource::Disk { .. } = &source {
8700 let id = BlockId::new(il, proj, ex as u16);
8701 let _ = c.prefetch_source(id, source, keep, eng)?;
8702 }
8703 }
8704 Ok(())
8705 })
8706 }
8707
8708 #[inline]
8709 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
8710 let _ = m.gate_exps.prefetch_expert_pages(ex);
8711 let _ = m.up_exps.prefetch_expert_pages(ex);
8712 let _ = m.down_exps.prefetch_expert_pages(ex);
8713 }
8714}
8715
8716impl HybridModel {
8733 #[allow(clippy::too_many_arguments)]
8737 fn moe_ffn_grouped_resident_q8(
8738 e: &Engine,
8739 m: &MoeWeights,
8740 z: &CudaSlice<f32>,
8741 t: usize,
8742 cfg: &ModelConfig,
8743 il: u16,
8744 sel_all: &[u32],
8745 w_all: &[f32],
8746 table: &CudaSlice<u64>,
8747 gu_il: bool,
8748 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8749 let moe = cfg.moe.as_ref().unwrap();
8750 let n_embd = cfg.n_embd as usize;
8751 let n_expert = moe.expert_count as usize;
8752 let n_used = moe.expert_used_count as usize;
8753 let n_ff_exp = moe.expert_ff_length as usize;
8754 let n_pairs = t * n_used;
8755 debug_assert_eq!(sel_all.len(), n_pairs);
8756 debug_assert_eq!(w_all.len(), n_pairs);
8757 debug_assert!(
8758 m.gate_exps.macros.is_none()
8759 && m.up_exps.macros.is_none()
8760 && m.down_exps.macros.is_none(),
8761 "resident grouped q8 does not fold per-expert macro scales",
8762 );
8763
8764 if !cfg.swiglu_clamped_at(il as u32) {
8770 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8771 let sel_d = e.htod_i32(&sel)?;
8772 let w_d = e.htod(w_all)?;
8773 let (gate_row_bytes, up_row_bytes) = if gu_il {
8774 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8775 (combined, combined)
8776 } else {
8777 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8778 };
8779 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8780 let act = e.moe_gate_up_silu8_dev_q8_rows(
8781 table,
8782 &sel_d,
8783 &zq,
8784 &zd,
8785 t,
8786 n_embd,
8787 n_ff_exp,
8788 n_used,
8789 n_expert,
8790 m.gate_exps.qtype,
8791 m.up_exps.qtype,
8792 gate_row_bytes,
8793 up_row_bytes,
8794 &m.dev_macros,
8795 )?;
8796 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8797 let mut moe_out = e.uninit(t * n_embd)?;
8798 e.moe_down8_fma_dev_q8_rows_g(
8799 table,
8800 &sel_d,
8801 &w_d,
8802 &aq2,
8803 &ad2,
8804 &mut moe_out,
8805 t,
8806 n_ff_exp,
8807 n_embd,
8808 n_used,
8809 n_expert,
8810 m.down_exps.qtype,
8811 m.down_exps.row_bytes,
8812 )?;
8813
8814 if std::env::var("MEMRA_MOE_STATS").is_ok() {
8815 let mut counts = vec![0usize; n_expert];
8816 for &expert in sel_all {
8817 counts[expert as usize] += 1;
8818 }
8819 let mut sizes: Vec<usize> =
8820 counts.into_iter().filter(|&count| count != 0).collect();
8821 sizes.sort_unstable();
8822 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
8823 println!(
8824 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
8825 m_e: min={} median={} mean={mean:.1} max={}",
8826 sizes.len(),
8827 n_expert,
8828 sizes.first().copied().unwrap_or(0),
8829 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
8830 sizes.last().copied().unwrap_or(0),
8831 );
8832 }
8833 return Ok(moe_out);
8834 }
8835
8836 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
8840 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8841 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
8842 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
8843
8844 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
8845 for (pair, &expert) in pair_ex.iter().enumerate() {
8846 by_expert[expert as usize].push(pair as i32);
8847 }
8848
8849 let pair_tok_d = e.htod_i32(&pair_tok)?;
8850 let pair_ex_d = e.htod_i32(&pair_ex)?;
8851 let pair_w_d = e.htod(w_all)?;
8852 let tok_off_d = e.htod_i32(&tok_off)?;
8853 let tok_ids_d = e.htod_i32(&tok_ids)?;
8854
8855 let matvec = |proj: i32,
8856 pair_rows: &CudaSlice<i32>,
8857 aq: &CudaSlice<i8>,
8858 ad: &CudaSlice<f32>,
8859 in_f: usize,
8860 out_f: usize,
8861 qtype: i32,
8862 row_bytes: usize|
8863 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8864 e.moe_pairs_matvec_q8(
8865 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
8866 row_bytes,
8867 )
8868 };
8869
8870 let (gate_row_bytes, up_row_bytes) = if gu_il {
8871 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8872 (combined, combined)
8873 } else {
8874 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8875 };
8876 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8877 let gate = matvec(
8878 0,
8879 &pair_tok_d,
8880 &zq,
8881 &zd,
8882 n_embd,
8883 n_ff_exp,
8884 m.gate_exps.qtype,
8885 gate_row_bytes,
8886 )?;
8887 let up = matvec(
8888 1,
8889 &pair_tok_d,
8890 &zq,
8891 &zd,
8892 n_embd,
8893 n_ff_exp,
8894 m.up_exps.qtype,
8895 up_row_bytes,
8896 )?;
8897 let mut act = e.uninit(n_pairs * n_ff_exp)?;
8898 Self::ffn_act_lim(
8899 e,
8900 cfg,
8901 &gate,
8902 &up,
8903 1.0,
8904 1.0,
8905 cfg.clamp_exp_at(il as u32),
8906 &mut act,
8907 n_pairs * n_ff_exp,
8908 )?;
8909 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8910 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
8911 let pair_self_d = e.htod_i32(&pair_self)?;
8912 let down = matvec(
8913 2,
8914 &pair_self_d,
8915 &aq2,
8916 &ad2,
8917 n_ff_exp,
8918 n_embd,
8919 m.down_exps.qtype,
8920 m.down_exps.row_bytes,
8921 )?;
8922 let mut moe_out = e.uninit(t * n_embd)?;
8923 e.moe_pairs_scatter(
8924 &down,
8925 &pair_w_d,
8926 &tok_off_d,
8927 &tok_ids_d,
8928 &mut moe_out,
8929 t,
8930 n_embd,
8931 )?;
8932
8933 if std::env::var("MEMRA_MOE_STATS").is_ok() {
8934 let mut sizes: Vec<usize> = by_expert
8935 .iter()
8936 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
8937 .collect();
8938 sizes.sort_unstable();
8939 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
8940 println!(
8941 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
8942 m_e: min={} median={} mean={mean:.1} max={}",
8943 sizes.len(),
8944 n_expert,
8945 sizes.first().copied().unwrap_or(0),
8946 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
8947 sizes.last().copied().unwrap_or(0),
8948 );
8949 }
8950 Ok(moe_out)
8951 }
8952
8953 #[allow(clippy::too_many_arguments)]
8959 fn shexp_split_matvec(
8960 e: &Engine,
8961 rank1: &Engine,
8962 wg: &CudaSlice<u8>,
8963 wu: &CudaSlice<u8>,
8964 wd: &CudaSlice<u8>,
8965 z: &CudaSlice<f32>,
8966 lim: Option<f32>,
8967 cfg: &ModelConfig,
8968 il: u16,
8969 n_embd: usize,
8970 n_ff_sh: usize,
8971 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
8972 use cudarc::driver::DevicePtr;
8973 if n_ff_sh % 2 != 0 || n_embd % 2 != 0 {
8974 return Ok(None);
8975 }
8976 let hf = n_ff_sh / 2;
8977 let nd = n_embd / 2;
8978 struct Rep {
8979 wg1: CudaSlice<u8>,
8980 wu1: CudaSlice<u8>,
8981 wd1: CudaSlice<u8>,
8982 }
8983 struct SplitWs {
8984 pin_dev: usize,
8985 gate0: CudaSlice<f32>,
8987 up0: CudaSlice<f32>,
8988 act: CudaSlice<f32>,
8989 sh_buf: CudaSlice<f32>,
8990 ev_z: cudarc::driver::CudaEvent,
8991 ev_act0: cudarc::driver::CudaEvent,
8992 z1: CudaSlice<f32>,
8994 g1: CudaSlice<f32>,
8995 u1: CudaSlice<f32>,
8996 a1h: CudaSlice<f32>,
8997 act1: CudaSlice<f32>,
8998 y1: CudaSlice<f32>,
8999 ev_act1: cudarc::driver::CudaEvent,
9000 ev_y1: cudarc::driver::CudaEvent,
9001 raw_act_e: u64,
9002 raw_sh_e: u64,
9003 raw_z1: u64,
9004 raw_a1h: u64,
9005 raw_act1: u64,
9006 raw_y1: u64,
9007 }
9008 static WS: std::sync::Mutex<Option<SplitWs>> = std::sync::Mutex::new(None);
9009 static REPS: std::sync::Mutex<Option<std::collections::HashMap<u64, Rep>>> =
9010 std::sync::Mutex::new(None);
9011 let mut guard = WS.lock().map_err(|_| "shexp split lock is poisoned")?;
9012 let mut reps_guard = REPS.lock().map_err(|_| "shexp reps lock is poisoned")?;
9013 let reps = reps_guard.get_or_insert_with(std::collections::HashMap::new);
9014 let pins = e.ctx().ordinal();
9015 if guard.as_ref().is_none_or(|w| w.pin_dev != pins) {
9016 let (gate0, up0, act, sh_buf, ev_z, ev_act0) = {
9017 let _m = e.gpu.enter_main()?;
9018 (
9019 e.htod(&vec![0.0f32; hf])?,
9020 e.htod(&vec![0.0f32; hf])?,
9021 e.htod(&vec![0.0f32; n_ff_sh])?,
9022 e.htod(&vec![0.0f32; n_embd])?,
9023 e.ctx().new_event(None)?,
9024 e.ctx().new_event(None)?,
9025 )
9026 };
9027 let (z1, g1, u1, a1h, act1, y1, ev_act1, ev_y1) = {
9028 let _r = rank1.gpu.enter_main()?;
9029 (
9030 rank1.htod(&vec![0.0f32; n_embd])?,
9031 rank1.htod(&vec![0.0f32; hf])?,
9032 rank1.htod(&vec![0.0f32; hf])?,
9033 rank1.htod(&vec![0.0f32; hf])?,
9034 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9035 rank1.htod(&vec![0.0f32; nd])?,
9036 rank1.ctx().new_event(None)?,
9037 rank1.ctx().new_event(None)?,
9038 )
9039 };
9040 let (raw_act_e, raw_sh_e) = {
9041 let _m = e.gpu.enter_main()?;
9042 let stream = e.stream();
9043 let (a, _g0) = act.device_ptr(&stream);
9044 let (b, _g1) = sh_buf.device_ptr(&stream);
9045 (a as u64, b as u64)
9046 };
9047 let (raw_z1, raw_a1h, raw_act1, raw_y1) = {
9048 let _r = rank1.gpu.enter_main()?;
9049 let rs = rank1.stream();
9050 let (a, _g0) = z1.device_ptr(&rs);
9051 let (b, _g1) = a1h.device_ptr(&rs);
9052 let (c, _g2) = act1.device_ptr(&rs);
9053 let (d, _g3) = y1.device_ptr(&rs);
9054 (a as u64, b as u64, c as u64, d as u64)
9055 };
9056 *guard = Some(SplitWs {
9057 pin_dev: pins,
9058 gate0,
9059 up0,
9060 act,
9061 sh_buf,
9062 ev_z,
9063 ev_act0,
9064 z1,
9065 g1,
9066 u1,
9067 a1h,
9068 act1,
9069 y1,
9070 ev_act1,
9071 ev_y1,
9072 raw_act_e,
9073 raw_sh_e,
9074 raw_z1,
9075 raw_a1h,
9076 raw_act1,
9077 raw_y1,
9078 });
9079 }
9080 let ws = guard.as_mut().expect("armed above");
9081 let wg_pin = {
9082 let _m = e.gpu.enter_main()?;
9083 let stream = e.stream();
9084 let (p, _g) = wg.device_ptr(&stream);
9085 p as u64
9086 };
9087 if !reps.contains_key(&wg_pin) {
9088 let mut up = |src: &CudaSlice<u8>,
9090 off_bytes: usize,
9091 len: usize|
9092 -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9093 use cudarc::driver::sys;
9094 let sptr = {
9095 let _m = e.gpu.enter_main()?;
9096 let stream = e.stream();
9097 let (p, _g) = src.device_ptr(&stream);
9098 p as u64 + off_bytes as u64
9099 };
9100 let dst = {
9101 let _r = rank1.gpu.enter_main()?;
9102 rank1.alloc_u8_uninit(len)?
9103 };
9104 let dptr = {
9105 let _r = rank1.gpu.enter_main()?;
9106 let rs = rank1.stream();
9107 let (p, _g) = dst.device_ptr(&rs);
9108 p as u64
9109 };
9110 let _r = rank1.gpu.enter_main()?;
9111 let r = unsafe {
9112 sys::cuMemcpyAsync(
9113 dptr as sys::CUdeviceptr,
9114 sptr as sys::CUdeviceptr,
9115 len,
9116 rank1.stream().cu_stream() as sys::CUstream,
9117 )
9118 };
9119 if r != sys::CUresult::CUDA_SUCCESS {
9120 return Err(format!("shexp split replica upload: {r:?}").into());
9121 }
9122 rank1.stream().synchronize()?;
9123 Ok(dst)
9124 };
9125 let wg1 = up(wg, hf * n_embd * 2, hf * n_embd * 2)?;
9126 let wu1 = up(wu, hf * n_embd * 2, hf * n_embd * 2)?;
9127 let wd1 = up(wd, nd * n_ff_sh * 2, nd * n_ff_sh * 2)?;
9128 reps.insert(wg_pin, Rep { wg1, wu1, wd1 });
9129 }
9130 let _ = il;
9131 let raw_z = {
9133 let _m = e.gpu.enter_main()?;
9134 let stream = e.stream();
9135 let (p, _g) = z.device_ptr(&stream);
9136 ws.ev_z.record(&stream)?;
9137 p as u64
9138 };
9139 {
9141 let rep = reps.get(&wg_pin).expect("uploaded above");
9142 let _r = rank1.gpu.enter_main()?;
9143 rank1.stream().wait(&ws.ev_z)?;
9144 crate::tp::raw_copy_bytes(ws.raw_z1, raw_z, n_embd * 4, rank1)?;
9145 let SplitWs {
9146 z1, g1, u1, a1h, ..
9147 } = &mut *ws;
9148 rank1.matvec_bf16_dual_into(&rep.wg1, &rep.wu1, z1, g1, u1, n_embd, hf)?;
9149 Self::ffn_act_lim(rank1, cfg, g1, u1, 1.0, 1.0, lim, a1h, hf)?;
9150 crate::tp::raw_copy_bytes(ws.raw_act1 + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9152 crate::tp::raw_copy_bytes(ws.raw_act_e + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9153 ws.ev_act1.record(&rank1.stream())?;
9154 }
9155 {
9157 let _m = e.gpu.enter_main()?;
9158 let SplitWs {
9159 gate0, up0, act, ..
9160 } = &mut *ws;
9161 let wg_lo = wg.slice(0..hf * n_embd * 2);
9162 let wu_lo = wu.slice(0..hf * n_embd * 2);
9163 e.matvec_bf16_dual_view_into(&wg_lo, &wu_lo, z, gate0, up0, n_embd, hf)?;
9164 Self::ffn_act_lim(e, cfg, gate0, up0, 1.0, 1.0, lim, act, hf)?;
9165 ws.ev_act0.record(&e.stream())?;
9166 }
9167 {
9169 let rep = reps.get(&wg_pin).expect("uploaded above");
9170 let _r = rank1.gpu.enter_main()?;
9171 rank1.stream().wait(&ws.ev_act0)?;
9172 crate::tp::raw_copy_bytes(ws.raw_act1, ws.raw_act_e, hf * 4, rank1)?;
9173 let SplitWs { act1, y1, .. } = &mut *ws;
9174 rank1.matvec_bf16_into(&rep.wd1, act1, y1, n_ff_sh, nd)?;
9175 crate::tp::raw_copy_bytes(ws.raw_sh_e + (nd * 4) as u64, ws.raw_y1, nd * 4, rank1)?;
9176 ws.ev_y1.record(&rank1.stream())?;
9177 }
9178 {
9180 let _m = e.gpu.enter_main()?;
9181 e.stream().wait(&ws.ev_act1)?;
9182 let SplitWs { act, sh_buf, .. } = &mut *ws;
9183 let wd_lo = wd.slice(0..nd * n_ff_sh * 2);
9184 e.matvec_bf16_view_into(&wd_lo, act, sh_buf, n_ff_sh, nd)?;
9185 e.stream().wait(&ws.ev_y1)?;
9186 let mut sh = e.uninit(n_embd)?;
9187 {
9188 let mut dst = sh.slice_mut(0..n_embd);
9189 e.stream()
9190 .memcpy_dtod(&ws.sh_buf.slice(0..n_embd), &mut dst)?;
9191 }
9192 Ok(Some(sh))
9193 }
9194 }
9195
9196 fn shexp_overlap_issue(
9203 e: &Engine,
9204 m: &MoeWeights,
9205 z: &CudaSlice<f32>,
9206 cfg: &ModelConfig,
9207 il: u16,
9208 n_embd: usize,
9209 ) -> Result<bool, Box<dyn std::error::Error>> {
9210 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9211 return Ok(false);
9212 }
9213 let (
9214 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9215 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9216 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9217 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9218 else {
9219 return Ok(false);
9220 };
9221 let n_ff_sh = m
9222 .gate_shexp
9223 .as_ref()
9224 .expect("matched Some above")
9225 .out_features();
9226 let lim = cfg.clamp_shexp_at(il as u32);
9227 let mut guard = SHEXP_OV_WS
9228 .lock()
9229 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9230 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9231 if guard
9232 .as_ref()
9233 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9234 {
9235 *guard = Some((
9236 pins.0,
9237 pins.1,
9238 pins.2,
9239 e.uninit(n_ff_sh)?,
9240 e.uninit(n_embd)?,
9241 ));
9242 }
9243 let (_, _, _, act, sh) = guard.as_mut().expect("armed above");
9244 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9245 e.matvec_bf16_into(wd, act, sh, n_ff_sh, n_embd)?;
9246 drop(guard);
9247 Ok(true)
9248 }
9249
9250 #[allow(clippy::too_many_arguments)]
9256 fn shexp_dev1_issue(
9257 e: &Engine,
9258 rank1: &Engine,
9259 m: &MoeWeights,
9260 z: &CudaSlice<f32>,
9261 cfg: &ModelConfig,
9262 il: u16,
9263 n_embd: usize,
9264 ) -> Result<bool, Box<dyn std::error::Error>> {
9265 use cudarc::driver::DevicePtr;
9266 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9267 return Ok(false);
9268 }
9269 let (
9270 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9271 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9272 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9273 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9274 else {
9275 return Ok(false);
9276 };
9277 let n_ff_sh = m
9278 .gate_shexp
9279 .as_ref()
9280 .expect("matched Some above")
9281 .out_features();
9282 let lim = cfg.clamp_shexp_at(il as u32);
9283 let mut ws_guard = SHEXP_D1_WS
9285 .lock()
9286 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9287 if ws_guard
9288 .as_ref()
9289 .is_none_or(|(k, ..)| *k != (n_embd, n_ff_sh))
9290 {
9291 let (act1, z1, ev_done) = {
9292 let _r1 = rank1.gpu.enter_main()?;
9293 (
9294 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9295 rank1.htod(&vec![0.0f32; n_embd])?,
9296 rank1.ctx().new_event(None)?,
9297 )
9298 };
9299 let (sh_root, ev_z) = {
9300 let _main = e.gpu.enter_main()?;
9301 (e.htod(&vec![0.0f32; n_embd])?, e.ctx().new_event(None)?)
9302 };
9303 *ws_guard = Some(((n_embd, n_ff_sh), act1, z1, sh_root, ev_z, ev_done));
9304 }
9305 let mut reps_guard = SHEXP_D1_REPS
9307 .lock()
9308 .map_err(|_| "shexp dev1 replica lock is poisoned")?;
9309 let reps = reps_guard.get_or_insert_with(Default::default);
9310 if !reps.contains_key(&il) {
9311 let (wg1, wu1, wd1) = {
9312 let _r1 = rank1.gpu.enter_main()?;
9313 (
9314 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9315 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9316 rank1.alloc_u8_uninit(n_embd * n_ff_sh * 2)?,
9317 )
9318 };
9319 for (src, dst) in [(wg, &wg1), (wu, &wu1), (wd, &wd1)] {
9320 let s_ptr = {
9321 let _main = e.gpu.enter_main()?;
9322 let stream = e.stream();
9323 let (p, _g) = src.device_ptr(&stream);
9324 p as u64
9325 };
9326 let d_ptr = {
9327 let _r1 = rank1.gpu.enter_main()?;
9328 let stream = rank1.stream();
9329 let (p, _g) = dst.device_ptr(&stream);
9330 p as u64
9331 };
9332 let _r1 = rank1.gpu.enter_main()?;
9333 crate::tp::raw_copy_bytes(d_ptr, s_ptr, src.len(), rank1)?;
9334 }
9335 {
9336 let _r1 = rank1.gpu.enter_main()?;
9337 rank1.stream().synchronize()?;
9338 }
9339 reps.insert(il, (wg1, wu1, wd1));
9340 }
9341 let (wg1, wu1, wd1) = reps.get(&il).expect("armed above");
9342 let (_, act1, z1, sh_root, ev_z, ev_done) = ws_guard.as_mut().expect("armed above");
9343 let (raw_z, raw_sh) = {
9346 let _main = e.gpu.enter_main()?;
9347 let stream = e.stream();
9348 let (a, _g0) = z.device_ptr(&stream);
9349 let (b, _g1) = sh_root.device_ptr(&stream);
9350 ev_z.record(&stream)?;
9351 (a as u64, b as u64)
9352 };
9353 {
9354 let _r1 = rank1.gpu.enter_main()?;
9355 rank1.stream().wait(ev_z)?;
9356 let raw_z1 = {
9357 let stream = rank1.stream();
9358 let (p, _g) = z1.device_ptr(&stream);
9359 p as u64
9360 };
9361 crate::tp::raw_copy_bytes(raw_z1, raw_z, n_embd * 4, rank1)?;
9362 rank1.matvec_bf16_dual_silu_into(wg1, wu1, z1, act1, n_embd, n_ff_sh, lim)?;
9363 rank1.matvec_bf16_raw_out(wd1, act1, raw_sh, n_ff_sh, n_embd)?;
9367 ev_done.record(&rank1.stream())?;
9368 }
9369 Ok(true)
9370 }
9371
9372 fn shexp_dev1_apply(
9374 e: &Engine,
9375 output: &mut CudaSlice<f32>,
9376 n_embd: usize,
9377 ) -> Result<(), Box<dyn std::error::Error>> {
9378 let guard = SHEXP_D1_WS
9379 .lock()
9380 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9381 let (pin, _, _, sh_root, _, ev_done) =
9382 guard.as_ref().ok_or("shexp dev1 apply without issue")?;
9383 if pin.0 != n_embd {
9384 return Err("shexp dev1 width drifted".into());
9385 }
9386 let _main = e.gpu.enter_main()?;
9387 e.stream().wait(ev_done)?;
9388 static ONES_D1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9389 std::sync::Mutex::new(None);
9390 let mut og = ONES_D1.lock().map_err(|_| "ones lock is poisoned")?;
9391 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9392 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9393 }
9394 let ones = &og.as_ref().expect("armed above").1;
9395 e.add_scaled_rows(sh_root, ones, output, n_embd, 1)?;
9396 Ok(())
9397 }
9398
9399 fn shexp_overlap_tail_ptrs(
9403 e: &Engine,
9404 m: &MoeWeights,
9405 cfg: &ModelConfig,
9406 n_embd: usize,
9407 ) -> Result<Option<(u64, u64)>, Box<dyn std::error::Error>> {
9408 use cudarc::driver::DevicePtr;
9409 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9410 return Ok(None);
9411 }
9412 let (
9413 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9414 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9415 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9416 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9417 else {
9418 return Ok(None);
9419 };
9420 let n_ff_sh = m
9421 .gate_shexp
9422 .as_ref()
9423 .expect("matched Some above")
9424 .out_features();
9425 let mut guard = SHEXP_OV_WS
9426 .lock()
9427 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9428 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9429 if guard
9430 .as_ref()
9431 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9432 {
9433 *guard = Some((
9434 pins.0,
9435 pins.1,
9436 pins.2,
9437 e.uninit(n_ff_sh)?,
9438 e.uninit(n_embd)?,
9439 ));
9440 }
9441 let sh_raw = {
9442 let (_, _, _, _, sh) = guard.as_ref().expect("armed above");
9443 let stream = e.stream();
9444 let (p, _g) = sh.device_ptr(&stream);
9445 p as u64
9446 };
9447 static ONES_T3: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9448 std::sync::Mutex::new(None);
9449 let mut og = ONES_T3.lock().map_err(|_| "ones lock is poisoned")?;
9450 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9451 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9452 }
9453 let ones_raw = {
9454 let stream = e.stream();
9455 let (p, _g) = og.as_ref().expect("armed above").1.device_ptr(&stream);
9456 p as u64
9457 };
9458 Ok(Some((sh_raw, ones_raw)))
9459 }
9460
9461 fn shexp_overlap_apply(
9464 e: &Engine,
9465 output: &mut CudaSlice<f32>,
9466 n_embd: usize,
9467 ) -> Result<(), Box<dyn std::error::Error>> {
9468 let guard = SHEXP_OV_WS
9469 .lock()
9470 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9471 let (_, ne, _, _, sh) = guard.as_ref().ok_or("shexp overlap apply without issue")?;
9472 if *ne != n_embd {
9473 return Err("shexp overlap width drifted".into());
9474 }
9475 static ONES_OV: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9476 std::sync::Mutex::new(None);
9477 let mut og = ONES_OV.lock().map_err(|_| "ones lock is poisoned")?;
9478 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9479 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9480 }
9481 let ones = &og.as_ref().expect("armed above").1;
9482 e.add_scaled_rows(sh, ones, output, n_embd, 1)?;
9483 Ok(())
9484 }
9485
9486 fn moe_ffn_grouped_add_shared(
9487 e: &Engine,
9488 m: &MoeWeights,
9489 z: &CudaSlice<f32>,
9490 t: usize,
9491 cfg: &ModelConfig,
9492 il: u16,
9493 moe_out: &mut CudaSlice<f32>,
9494 ) -> Result<(), Box<dyn std::error::Error>> {
9495 static SHEXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9498 static SHEXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9499 let shexp_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9500 let shexp_started = shexp_timing.then(std::time::Instant::now);
9501 let result = Self::moe_ffn_grouped_add_shared_inner(e, m, z, t, cfg, il, moe_out);
9502 if let Some(started) = shexp_started {
9503 use std::sync::atomic::Ordering;
9504 e.stream().synchronize()?;
9505 let ns = SHEXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9506 + started.elapsed().as_nanos() as u64;
9507 let calls = SHEXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9508 if calls % 430 == 0 {
9509 eprintln!(
9510 "[moe-shexp-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9511 ns as f64 / 1.0e6,
9512 ns as f64 / calls as f64 / 1.0e3,
9513 );
9514 }
9515 }
9516 result
9517 }
9518
9519 #[allow(clippy::too_many_arguments)]
9520 fn moe_ffn_grouped_add_shared_inner(
9521 e: &Engine,
9522 m: &MoeWeights,
9523 z: &CudaSlice<f32>,
9524 t: usize,
9525 cfg: &ModelConfig,
9526 il: u16,
9527 moe_out: &mut CudaSlice<f32>,
9528 ) -> Result<(), Box<dyn std::error::Error>> {
9529 let n_embd = cfg.n_embd as usize;
9530 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
9531 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9532 {
9533 let n_ff_sh = gate_shexp.out_features();
9534 let lim = cfg.clamp_shexp_at(il as u32);
9535 let fused = t == 1
9542 && lim.is_none()
9543 && cfg.m3.is_none()
9544 && e.uses_q8_1_fast(gate_shexp)
9545 && e.uses_q8_1_fast(up_shexp);
9546 let bf16_dual = if t == 1 && crate::Engine::bf16_mmv_on() && n_embd % 8 == 0 {
9549 match (gate_shexp, up_shexp) {
9550 (
9551 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
9552 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
9553 ) => Some((wg, wu)),
9554 _ => None,
9555 }
9556 } else {
9557 None
9558 };
9559 let sh = if let Some((wg, wu)) = bf16_dual {
9560 static SHEXP_WS: std::sync::Mutex<
9564 Option<(
9565 usize,
9566 usize,
9567 usize,
9568 CudaSlice<f32>,
9569 CudaSlice<f32>,
9570 CudaSlice<f32>,
9571 CudaSlice<f32>,
9572 )>,
9573 > = std::sync::Mutex::new(None);
9574 let down_bf16 = match down_shexp {
9575 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9576 _ => None,
9577 };
9578 let mut guard = SHEXP_WS
9579 .lock()
9580 .map_err(|_| "shexp workspace lock is poisoned")?;
9581 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9582 if guard
9583 .as_ref()
9584 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9585 {
9586 *guard = Some((
9587 pins.0,
9588 pins.1,
9589 pins.2,
9590 e.uninit(n_ff_sh)?,
9591 e.uninit(n_ff_sh)?,
9592 e.uninit(n_ff_sh)?,
9593 e.uninit(n_embd)?,
9594 ));
9595 }
9596 {
9599 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9600 let split_on = *ON
9601 .get_or_init(|| std::env::var("MEMRA_SHEXP_SPLIT").as_deref() == Ok("1"));
9602 if split_on {
9603 if let (Some(wd), Some(rank1)) = (
9604 match down_shexp {
9605 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9606 _ => None,
9607 },
9608 m.step_tp.as_ref().and_then(|st| st.runtime.rank_engine(1)),
9609 ) {
9610 if let Some(sh) = Self::shexp_split_matvec(
9611 e, rank1, wg, wu, wd, z, lim, cfg, il, n_embd, n_ff_sh,
9612 )? {
9613 drop(guard);
9614 let gate = match &m.gate_inp_shexp {
9615 Some(gate_inp_shexp) => e.sigmoid_dot_rows(
9616 z,
9617 gate_inp_shexp.float_data(),
9618 n_embd,
9619 t,
9620 )?,
9621 None => e.htod(&vec![1.0f32; t])?,
9622 };
9623 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9624 return Ok(());
9625 }
9626 }
9627 }
9628 }
9629 let (_, _, _, gate, up, act, sh_buf) =
9630 guard.as_mut().expect("shexp workspace initialized above");
9631 if cfg.m3.is_none() {
9632 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9635 let _ = (&gate, &up);
9636 } else {
9637 e.matvec_bf16_dual_into(wg, wu, z, gate, up, n_embd, n_ff_sh)?;
9638 Self::ffn_act_lim(e, cfg, gate, up, 1.0, 1.0, lim, act, n_ff_sh)?;
9639 }
9640 if let Some(down) = down_bf16 {
9641 static FUSE_DA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9648 let fuse_da = *FUSE_DA.get_or_init(|| {
9649 std::env::var("MEMRA_FUSE_DOWN_ADDSCALE").as_deref() != Ok("0")
9650 });
9651 if fuse_da && m.gate_inp_shexp.is_none() {
9652 static ONES1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9653 std::sync::Mutex::new(None);
9654 let mut og = ONES1.lock().map_err(|_| "shexp ones lock is poisoned")?;
9655 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9656 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9657 }
9658 let ones = &og.as_ref().expect("armed above").1;
9659 e.matvec_bf16_down_addscale_into(
9660 down, act, ones, moe_out, n_ff_sh, n_embd,
9661 )?;
9662 return Ok(());
9663 }
9664 e.matvec_bf16_into(down, act, sh_buf, n_ff_sh, n_embd)?;
9665 let sh = e.uninit(n_embd)?;
9666 let mut sh = sh;
9668 {
9669 let mut dst = sh.slice_mut(0..n_embd);
9670 e.stream().memcpy_dtod(&sh_buf.slice(0..n_embd), &mut dst)?;
9671 }
9672 sh
9673 } else {
9674 e.matmul(down_shexp, act, 1)?
9675 }
9676 } else if fused {
9677 let (zq, zd) = e.quantize_q8_1(z, 1, n_embd)?;
9678 let pair = match e.matmul_pre_dual_noscale(gate_shexp, up_shexp, &zq, &zd, 1)? {
9679 Some((gate, up)) => Some((gate, up)),
9680 None => {
9681 match (
9682 e.matmul_pre_noscale(gate_shexp, &zq, &zd, 1)?,
9683 e.matmul_pre_noscale(up_shexp, &zq, &zd, 1)?,
9684 ) {
9685 (Some(gate), Some(up)) => Some((gate, up)),
9686 _ => None,
9687 }
9688 }
9689 };
9690 match pair {
9691 Some(((gate, gs), (up, us))) => {
9692 if e.uses_q8_1_fast(down_shexp) {
9693 let (aq, ad) = e.silu_mul_scaled_q8_1(&gate, &up, gs, us, n_ff_sh)?;
9694 e.matmul_pre(down_shexp, &aq, &ad, &gate, 1)?
9695 } else {
9696 let mut act = e.uninit(n_ff_sh)?;
9697 e.silu_mul_scaled(&gate, &up, gs, us, &mut act, n_ff_sh)?;
9698 e.matmul(down_shexp, &act, 1)?
9699 }
9700 }
9701 None => {
9702 let gate = e.matmul_pre(gate_shexp, &zq, &zd, z, 1)?;
9703 let up = e.matmul_pre(up_shexp, &zq, &zd, z, 1)?;
9704 let mut act = e.uninit(n_ff_sh)?;
9705 Self::ffn_act(e, cfg, &gate, &up, &mut act, n_ff_sh)?;
9706 e.matmul(down_shexp, &act, 1)?
9707 }
9708 }
9709 } else {
9710 let sg_gate = e.matmul(gate_shexp, z, t)?;
9711 let sg_up = e.matmul(up_shexp, z, t)?;
9712 let mut sa = e.uninit(t * n_ff_sh)?;
9713 Self::ffn_act_lim(
9714 e,
9715 cfg,
9716 &sg_gate,
9717 &sg_up,
9718 1.0,
9719 1.0,
9720 lim,
9721 &mut sa,
9722 t * n_ff_sh,
9723 )?;
9724 e.matmul(down_shexp, &sa, t)?
9725 };
9726 let gate = match &m.gate_inp_shexp {
9727 Some(gate_inp_shexp) => {
9728 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
9729 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
9730 } else {
9731 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
9732 let mut gate = e.uninit(t)?;
9733 e.sigmoid(&raw, &mut gate, t)?;
9734 gate
9735 }
9736 }
9737 None if t == 1 => {
9742 static ONES: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9743 std::sync::Mutex::new(None);
9744 let mut guard = ONES.lock().map_err(|_| "shexp ones lock is poisoned")?;
9745 if guard.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9746 *guard = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9747 }
9748 let ones = &guard.as_ref().expect("armed above").1;
9749 e.add_scaled_rows(&sh, ones, moe_out, n_embd, t)?;
9750 return Ok(());
9751 }
9752 None => e.htod(&vec![1.0f32; t])?,
9753 };
9754 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9755 }
9756 Ok(())
9757 }
9758
9759 pub(crate) fn moe_ffn_grouped(
9762 e: &Engine,
9763 m: &MoeWeights,
9764 z: &CudaSlice<f32>,
9765 t: usize,
9766 cfg: &ModelConfig,
9767 il: u16,
9768 max_block: usize,
9769 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9770 let moe = cfg.moe.as_ref().unwrap();
9771 let n_embd = cfg.n_embd as usize;
9772 let n_expert = moe.expert_count as usize;
9773 let n_used = moe.expert_used_count as usize;
9774 let n_ff_exp = moe.expert_ff_length as usize;
9775 let lim_exp = cfg.clamp_exp_at(il as u32);
9777
9778 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
9782 if let Some(sig) = cfg.sigmoid_router() {
9783 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
9784 }
9785 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
9786 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
9787 } else {
9788 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
9789 };
9790 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
9791 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
9792 Self::trace_moe_input(e, il, t, n_embd, z)?;
9793
9794 let no_exp_macros = m.gate_exps.macros.is_none()
9799 && m.up_exps.macros.is_none()
9800 && m.down_exps.macros.is_none();
9801 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
9802 m.has_uniform_expert_layout()
9803 && no_exp_macros
9804 && moe_q8_enabled()
9805 && q8_expert_supported(m.gate_exps.qtype)
9806 && q8_expert_supported(m.up_exps.qtype)
9807 && q8_expert_supported(m.down_exps.qtype)
9808 && moe_slab_enabled()
9809 && dev.dev == e.ctx().ordinal()
9810 });
9811 if let Some(dev) = resident_q8 {
9812 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
9813 e,
9814 m,
9815 z,
9816 t,
9817 cfg,
9818 il,
9819 &sel_all,
9820 &w_all,
9821 &dev.ptr_row,
9822 dev.gu_il,
9823 )?;
9824 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
9825 return Ok(moe_out);
9826 }
9827
9828 struct ExpertGroup {
9832 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
9836 let mut groups: Vec<ExpertGroup> = (0..n_expert)
9837 .map(|_| ExpertGroup {
9838 tok_indices: Vec::new(),
9839 slot_indices: Vec::new(),
9840 weights: Vec::new(),
9841 })
9842 .collect();
9843
9844 for tok in 0..t {
9845 for j in 0..n_used {
9846 let ex = sel_all[tok * n_used + j] as usize;
9847 let w = w_all[tok * n_used + j];
9848 groups[ex].tok_indices.push(tok as i32);
9849 groups[ex].slot_indices.push(j as i32);
9850 groups[ex].weights.push(w);
9851 }
9852 }
9853
9854 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
9857 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
9861 let u_len = m.up_exps.max_expert_bytes();
9862 let d_len = m.down_exps.max_expert_bytes();
9863 let moe_q8 = m.has_uniform_expert_layout()
9864 && moe_q8_enabled()
9865 && q8_expert_supported(m.gate_exps.qtype)
9866 && q8_expert_supported(m.up_exps.qtype)
9867 && q8_expert_supported(m.down_exps.qtype);
9868 let slab_local = m
9871 .dev_exps
9872 .as_ref()
9873 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
9874 let use_cache =
9875 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
9876 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
9879
9880 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
9882 (
9883 Some(e.alloc_u8(g_len)?),
9884 Some(e.alloc_u8(u_len)?),
9885 Some(e.alloc_u8(d_len)?),
9886 )
9887 } else {
9888 (None, None, None)
9889 };
9890
9891 let mut order: Vec<usize> = (0..n_expert)
9902 .filter(|&ex| !groups[ex].tok_indices.is_empty())
9903 .collect();
9904 order.sort_by(|&a, &b| {
9905 groups[b]
9906 .tok_indices
9907 .len()
9908 .cmp(&groups[a].tok_indices.len())
9909 .then(a.cmp(&b))
9910 });
9911 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
9913 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
9914 if worker_disk_prefetch {
9915 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
9916 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
9917 }
9918 }
9919 for (order_pos, &ex) in order.iter().enumerate() {
9920 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
9921 Self::moe_prefetch_host_expert(order[next], m);
9922 }
9923 if worker_disk_prefetch {
9924 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
9925 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
9926 let keep = [
9927 BlockId::new(il, PROJ_GATE, ex as u16),
9928 BlockId::new(il, PROJ_UP, ex as u16),
9929 BlockId::new(il, PROJ_DOWN, ex as u16),
9930 ];
9931 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
9932 }
9933 }
9934 let grp = &groups[ex];
9935 let m_e = grp.tok_indices.len();
9936 m_dist.push(m_e);
9937 let gl = m.gate_exps.expert_layout(ex);
9938 let ul = m.up_exps.expert_layout(ex);
9939 let dl = m.down_exps.expert_layout(ex);
9940
9941 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
9945 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
9946 let dmac = m.down_exps.macro_scale(ex);
9947 let weight_d = if dmac == 1.0 {
9948 e.htod(&grp.weights)?
9949 } else {
9950 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
9951 e.htod(&scaled)?
9952 };
9953
9954 let mut gathered = e.zeros(m_e * n_embd)?;
9956 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
9957 let gv = gathered.slice(0..m_e * n_embd);
9958
9959 let y = if let Some(dev) = slab_local {
9962 let gate_start = ex * m.gate_exps.expert_stride;
9963 let up_start = ex * m.up_exps.expert_stride;
9964 let down_start = ex * m.down_exps.expert_stride;
9965 if grouped_q8 {
9966 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
9967 let gate = e.qmatvec_expert_q8(
9968 &dev.gate,
9969 gate_start..gate_start + gl.len,
9970 &zq,
9971 &zd,
9972 m_e,
9973 m.gate_exps.in_f,
9974 m.gate_exps.out_f,
9975 gl.qtype,
9976 gl.row_bytes,
9977 )?;
9978 let up = e.qmatvec_expert_q8(
9979 &dev.up,
9980 up_start..up_start + ul.len,
9981 &zq,
9982 &zd,
9983 m_e,
9984 m.up_exps.in_f,
9985 m.up_exps.out_f,
9986 ul.qtype,
9987 ul.row_bytes,
9988 )?;
9989 let mut act = e.uninit(m_e * n_ff_exp)?;
9990 Self::ffn_act_lim(
9991 e,
9992 cfg,
9993 &gate,
9994 &up,
9995 m.gate_exps.macro_scale(ex),
9996 m.up_exps.macro_scale(ex),
9997 lim_exp,
9998 &mut act,
9999 m_e * n_ff_exp,
10000 )?;
10001 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10002 e.qmatvec_expert_q8(
10003 &dev.down,
10004 down_start..down_start + dl.len,
10005 &aq2,
10006 &ad2,
10007 m_e,
10008 m.down_exps.in_f,
10009 m.down_exps.out_f,
10010 dl.qtype,
10011 dl.row_bytes,
10012 )?
10013 } else {
10014 let gate = e.qmatvec_view(
10015 &dev.gate,
10016 gate_start..gate_start + gl.len,
10017 &gv,
10018 m_e,
10019 m.gate_exps.in_f,
10020 m.gate_exps.out_f,
10021 gl.qtype,
10022 gl.row_bytes,
10023 )?;
10024 let up = e.qmatvec_view(
10025 &dev.up,
10026 up_start..up_start + ul.len,
10027 &gv,
10028 m_e,
10029 m.up_exps.in_f,
10030 m.up_exps.out_f,
10031 ul.qtype,
10032 ul.row_bytes,
10033 )?;
10034 let mut act = e.uninit(m_e * n_ff_exp)?;
10035 Self::ffn_act_lim(
10036 e,
10037 cfg,
10038 &gate,
10039 &up,
10040 m.gate_exps.macro_scale(ex),
10041 m.up_exps.macro_scale(ex),
10042 lim_exp,
10043 &mut act,
10044 m_e * n_ff_exp,
10045 )?;
10046 let actv = act.slice(0..m_e * n_ff_exp);
10047 e.qmatvec_view(
10048 &dev.down,
10049 down_start..down_start + dl.len,
10050 &actv,
10051 m_e,
10052 m.down_exps.in_f,
10053 m.down_exps.out_f,
10054 dl.qtype,
10055 dl.row_bytes,
10056 )?
10057 }
10058 } else if use_cache {
10059 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10060 if grouped_q8 {
10061 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10062 let gate = e.with_moe_cache(max_block, |cache, eng| {
10063 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10064 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10065 eng.qmatvec_expert_q8(
10066 cache.buf(slot),
10067 0..gl.len,
10068 &zq,
10069 &zd,
10070 m_e,
10071 m.gate_exps.in_f,
10072 m.gate_exps.out_f,
10073 gl.qtype,
10074 gl.row_bytes,
10075 )
10076 })?;
10077 let up = e.with_moe_cache(max_block, |cache, eng| {
10078 let id = BlockId::new(il, PROJ_UP, ex as u16);
10079 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10080 eng.qmatvec_expert_q8(
10081 cache.buf(slot),
10082 0..ul.len,
10083 &zq,
10084 &zd,
10085 m_e,
10086 m.up_exps.in_f,
10087 m.up_exps.out_f,
10088 ul.qtype,
10089 ul.row_bytes,
10090 )
10091 })?;
10092 let mut act = e.uninit(m_e * n_ff_exp)?;
10093 Self::ffn_act_lim(
10094 e,
10095 cfg,
10096 &gate,
10097 &up,
10098 m.gate_exps.macro_scale(ex),
10099 m.up_exps.macro_scale(ex),
10100 lim_exp,
10101 &mut act,
10102 m_e * n_ff_exp,
10103 )?;
10104 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10105 e.with_moe_cache(max_block, |cache, eng| {
10106 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10107 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10108 eng.qmatvec_expert_q8(
10109 cache.buf(slot),
10110 0..dl.len,
10111 &aq2,
10112 &ad2,
10113 m_e,
10114 m.down_exps.in_f,
10115 m.down_exps.out_f,
10116 dl.qtype,
10117 dl.row_bytes,
10118 )
10119 })?
10120 } else {
10121 let gate = e.with_moe_cache(max_block, |cache, eng| {
10122 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10123 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10124 eng.qmatvec_view(
10125 cache.buf(slot),
10126 0..gl.len,
10127 &gv,
10128 m_e,
10129 m.gate_exps.in_f,
10130 m.gate_exps.out_f,
10131 gl.qtype,
10132 gl.row_bytes,
10133 )
10134 })?;
10135 let up = e.with_moe_cache(max_block, |cache, eng| {
10136 let id = BlockId::new(il, PROJ_UP, ex as u16);
10137 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10138 eng.qmatvec_view(
10139 cache.buf(slot),
10140 0..ul.len,
10141 &gv,
10142 m_e,
10143 m.up_exps.in_f,
10144 m.up_exps.out_f,
10145 ul.qtype,
10146 ul.row_bytes,
10147 )
10148 })?;
10149 let mut act = e.uninit(m_e * n_ff_exp)?;
10150 Self::ffn_act_lim(
10151 e,
10152 cfg,
10153 &gate,
10154 &up,
10155 m.gate_exps.macro_scale(ex),
10156 m.up_exps.macro_scale(ex),
10157 lim_exp,
10158 &mut act,
10159 m_e * n_ff_exp,
10160 )?;
10161 let actv = act.slice(0..m_e * n_ff_exp);
10162 e.with_moe_cache(max_block, |cache, eng| {
10163 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10164 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10165 eng.qmatvec_view(
10166 cache.buf(slot),
10167 0..dl.len,
10168 &actv,
10169 m_e,
10170 m.down_exps.in_f,
10171 m.down_exps.out_f,
10172 dl.qtype,
10173 dl.row_bytes,
10174 )
10175 })?
10176 }
10177 } else {
10178 let sg = scratch_g.as_mut().unwrap();
10179 let su = scratch_u.as_mut().unwrap();
10180 let sd = scratch_d.as_mut().unwrap();
10181 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
10182 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
10183 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
10184 if grouped_q8 {
10185 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10186 let gate = e.qmatvec_expert_q8(
10187 sg,
10188 0..gl.len,
10189 &zq,
10190 &zd,
10191 m_e,
10192 m.gate_exps.in_f,
10193 m.gate_exps.out_f,
10194 gl.qtype,
10195 gl.row_bytes,
10196 )?;
10197 let up = e.qmatvec_expert_q8(
10198 su,
10199 0..ul.len,
10200 &zq,
10201 &zd,
10202 m_e,
10203 m.up_exps.in_f,
10204 m.up_exps.out_f,
10205 ul.qtype,
10206 ul.row_bytes,
10207 )?;
10208 let mut act = e.uninit(m_e * n_ff_exp)?;
10209 Self::ffn_act_lim(
10210 e,
10211 cfg,
10212 &gate,
10213 &up,
10214 m.gate_exps.macro_scale(ex),
10215 m.up_exps.macro_scale(ex),
10216 lim_exp,
10217 &mut act,
10218 m_e * n_ff_exp,
10219 )?;
10220 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10221 e.qmatvec_expert_q8(
10222 sd,
10223 0..dl.len,
10224 &aq2,
10225 &ad2,
10226 m_e,
10227 m.down_exps.in_f,
10228 m.down_exps.out_f,
10229 dl.qtype,
10230 dl.row_bytes,
10231 )?
10232 } else {
10233 let gate = e.qmatvec_view(
10234 sg,
10235 0..gl.len,
10236 &gv,
10237 m_e,
10238 m.gate_exps.in_f,
10239 m.gate_exps.out_f,
10240 gl.qtype,
10241 gl.row_bytes,
10242 )?;
10243 let up = e.qmatvec_view(
10244 su,
10245 0..ul.len,
10246 &gv,
10247 m_e,
10248 m.up_exps.in_f,
10249 m.up_exps.out_f,
10250 ul.qtype,
10251 ul.row_bytes,
10252 )?;
10253 let mut act = e.uninit(m_e * n_ff_exp)?;
10254 Self::ffn_act_lim(
10255 e,
10256 cfg,
10257 &gate,
10258 &up,
10259 m.gate_exps.macro_scale(ex),
10260 m.up_exps.macro_scale(ex),
10261 lim_exp,
10262 &mut act,
10263 m_e * n_ff_exp,
10264 )?;
10265 let actv = act.slice(0..m_e * n_ff_exp);
10266 e.qmatvec_view(
10267 sd,
10268 0..dl.len,
10269 &actv,
10270 m_e,
10271 m.down_exps.in_f,
10272 m.down_exps.out_f,
10273 dl.qtype,
10274 dl.row_bytes,
10275 )?
10276 }
10277 };
10278
10279 e.scatter_slot(
10281 &y,
10282 &tok_idx_d,
10283 &slot_idx_d,
10284 &weight_d,
10285 &mut slot_buf,
10286 &mut wbuf,
10287 n_embd,
10288 n_used,
10289 m_e,
10290 )?;
10291 }
10292
10293 let mut moe_out = e.zeros(t * n_embd)?;
10295 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
10296
10297 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
10299 m_dist.sort_unstable();
10300 let active = m_dist.len();
10301 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
10302 let median = m_dist[active / 2];
10303 let max_m = *m_dist.last().unwrap();
10304 let min_m = m_dist[0];
10305 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
10306 println!(
10307 "moe-grouped il={il} t={t} active={active}/{n_expert} \
10308 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
10309 above_gemm_threshold(>=16)={above16}/{active}"
10310 );
10311 }
10312
10313 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10314 Ok(moe_out)
10315 }
10316
10317 pub(crate) fn moe_ffn_lockstep(
10324 &self,
10325 e: &Engine,
10326 m: &MoeWeights,
10327 zbatch: &CudaSlice<f32>,
10328 mrows: usize,
10329 il: u16,
10330 max_block: usize,
10331 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10332 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10333 let cfg = &self.cfg;
10334 let moe = cfg.moe.as_ref().unwrap();
10335 let n_embd = cfg.n_embd as usize;
10336 let n_expert = moe.expert_count as usize;
10337 let n_used = moe.expert_used_count as usize;
10338 let n_ff_exp = moe.expert_ff_length as usize;
10339 let lim_exp = cfg.clamp_exp_at(il as u32);
10341 let lim_shexp = cfg.clamp_shexp_at(il as u32);
10342
10343 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
10344 if let Some(sig) = cfg.sigmoid_router() {
10345 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
10346 }
10347 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10348 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
10349 } else {
10350 Self::moe_route_cfg(
10351 e,
10352 &logits,
10353 mrows,
10354 n_expert,
10355 n_used,
10356 m.active_experts.as_deref(),
10357 )?
10358 };
10359 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
10360
10361 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
10363 Ok((0..n_expert)
10364 .map(|ex| {
10365 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
10366 .into_iter()
10367 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
10368 })
10369 .collect())
10370 })?;
10371
10372 struct Group {
10373 rows: Vec<i32>,
10374 slots: Vec<i32>,
10375 weights: Vec<f32>,
10376 }
10377 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
10378 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
10379 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
10380 Default::default();
10381 for row in 0..mrows {
10382 for j in 0..n_used {
10383 let ex = sel_all[row * n_used + j] as usize;
10384 let w = w_all[row * n_used + j];
10385 if resident_expert[ex] {
10386 let group = groups.entry(ex).or_insert_with(|| Group {
10387 rows: Vec::new(),
10388 slots: Vec::new(),
10389 weights: Vec::new(),
10390 });
10391 group.rows.push(row as i32);
10392 group.slots.push(j as i32);
10393 group.weights.push(w);
10394 } else {
10395 crate::cpu_experts::record_incomplete_gpu_residency(0);
10396 cpu_rows[row].push((ex, w));
10397 cpu_by_expert.entry(ex).or_default().push((row, w));
10398 }
10399 }
10400 }
10401
10402 let host_rows = e.dtoh(zbatch)?;
10408 let rows_ok = crate::cpu_experts::rows_supported();
10409 enum CpuPart {
10410 Single { row: usize },
10411 Rows { rows: Vec<usize> },
10412 }
10413 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
10414 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
10415 if rows_ok {
10416 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
10417 .into_iter()
10418 .filter(|(_, rows)| rows.len() >= 2)
10419 .collect();
10420 shared.sort_by_key(|(ex, _)| *ex);
10421 for (ex, mut row_weights) in shared {
10422 row_weights.sort_by_key(|(row, _)| *row);
10423 let inputs: Vec<(&[f32], f32)> = row_weights
10424 .iter()
10425 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
10426 .collect();
10427 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
10428 .map_err(std::io::Error::other)?;
10429 for &(row, _) in &row_weights {
10430 rows_served.insert((row, ex));
10431 }
10432 tickets.push((
10433 CpuPart::Rows {
10434 rows: row_weights.iter().map(|&(row, _)| row).collect(),
10435 },
10436 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
10437 ));
10438 }
10439 }
10440 for (row, selected) in cpu_rows.iter().enumerate() {
10441 let leftover: Vec<(usize, f32)> = selected
10442 .iter()
10443 .copied()
10444 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
10445 .collect();
10446 if leftover.is_empty() {
10447 continue;
10448 }
10449 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
10450 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
10451 .map_err(std::io::Error::other)?;
10452 tickets.push((
10453 CpuPart::Single { row },
10454 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
10455 ));
10456 }
10457
10458 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
10459 let mut wbuf = e.zeros(mrows * n_used)?;
10460 let mut order: Vec<usize> = groups.keys().copied().collect();
10461 order.sort_by(|&a, &b| {
10462 groups[&b]
10463 .rows
10464 .len()
10465 .cmp(&groups[&a].rows.len())
10466 .then(a.cmp(&b))
10467 });
10468 for &ex in &order {
10469 let group = &groups[&ex];
10470 let m_e = group.rows.len();
10471 let gl = m.gate_exps.expert_layout(ex);
10472 let ul = m.up_exps.expert_layout(ex);
10473 let dl = m.down_exps.expert_layout(ex);
10474 let row_idx_d = e.htod_i32(&group.rows)?;
10475 let slot_idx_d = e.htod_i32(&group.slots)?;
10476 let dmac = m.down_exps.macro_scale(ex);
10477 let weight_d = if dmac == 1.0 {
10478 e.htod(&group.weights)?
10479 } else {
10480 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
10481 e.htod(&scaled)?
10482 };
10483 let mut gathered = e.zeros(m_e * n_embd)?;
10484 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
10485 let gv = gathered.slice(0..m_e * n_embd);
10486 let gate = e.with_moe_cache(max_block, |c, eng| {
10487 let slot = c
10488 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
10489 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10490 eng.qmatvec_view(
10491 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10492 0..gl.len,
10493 &gv,
10494 m_e,
10495 m.gate_exps.in_f,
10496 m.gate_exps.out_f,
10497 gl.qtype,
10498 gl.row_bytes,
10499 )
10500 })?;
10501 let up = e.with_moe_cache(max_block, |c, eng| {
10502 let slot = c
10503 .resident(BlockId::new(il, PROJ_UP, ex as u16))
10504 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10505 eng.qmatvec_view(
10506 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10507 0..ul.len,
10508 &gv,
10509 m_e,
10510 m.up_exps.in_f,
10511 m.up_exps.out_f,
10512 ul.qtype,
10513 ul.row_bytes,
10514 )
10515 })?;
10516 let mut act = e.zeros(m_e * n_ff_exp)?;
10517 Self::ffn_act_lim(
10518 e,
10519 cfg,
10520 &gate,
10521 &up,
10522 m.gate_exps.macro_scale(ex),
10523 m.up_exps.macro_scale(ex),
10524 lim_exp,
10525 &mut act,
10526 m_e * n_ff_exp,
10527 )?;
10528 let actv = act.slice(0..m_e * n_ff_exp);
10529 let y = e.with_moe_cache(max_block, |c, eng| {
10530 let slot = c
10531 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
10532 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10533 eng.qmatvec_view(
10534 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10535 0..dl.len,
10536 &actv,
10537 m_e,
10538 m.down_exps.in_f,
10539 m.down_exps.out_f,
10540 dl.qtype,
10541 dl.row_bytes,
10542 )
10543 })?;
10544 e.scatter_slot(
10545 &y,
10546 &row_idx_d,
10547 &slot_idx_d,
10548 &weight_d,
10549 &mut slot_buf,
10550 &mut wbuf,
10551 n_embd,
10552 n_used,
10553 m_e,
10554 )?;
10555 }
10556 let mut moe_out = e.zeros(mrows * n_embd)?;
10557 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
10558
10559 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
10561 for (part, ticket) in tickets {
10562 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
10563 let mut add_row = |row: usize, chunk: &[f32]| {
10564 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
10565 for (accumulator, value) in sum.iter_mut().zip(chunk) {
10566 *accumulator += value;
10567 }
10568 };
10569 match part {
10570 CpuPart::Single { row } => add_row(row, &cpu_output),
10571 CpuPart::Rows { rows } => {
10572 for (slot, row) in rows.into_iter().enumerate() {
10573 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
10574 }
10575 }
10576 }
10577 }
10578 for (row, sum) in row_sums.into_iter().enumerate() {
10579 let Some(sum) = sum else { continue };
10580 let cpu_output = e.htod(&sum)?;
10581 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
10582 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
10583 }
10584
10585 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
10586 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
10587 {
10588 let n_ff_sh = gate_shexp.out_features();
10589 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
10590 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
10591 let mut sa = e.zeros(mrows * n_ff_sh)?;
10592 Self::ffn_act_lim(
10593 e,
10594 cfg,
10595 &sg_gate,
10596 &sg_up,
10597 1.0,
10598 1.0,
10599 lim_shexp,
10600 &mut sa,
10601 mrows * n_ff_sh,
10602 )?;
10603 let sh = e.matmul(down_shexp, &sa, mrows)?;
10604 let g = match &m.gate_inp_shexp {
10607 Some(gate_inp_shexp) => {
10608 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
10609 }
10610 None => e.htod(&vec![1.0f32; mrows])?,
10611 };
10612 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
10613 }
10614
10615 Ok(moe_out)
10616 }
10617}
10618
10619impl HybridModel {
10625 pub(crate) fn gemma4_rope_dims(&self, il: usize) -> usize {
10639 let g = self
10640 .cfg
10641 .gemma4
10642 .as_ref()
10643 .expect("gemma4_rope_dims on a non-gemma4 config");
10644 if g.swa_pattern[il] {
10645 g.rope_dims_swa as usize
10646 } else {
10647 g.rope_dims_global as usize
10648 }
10649 }
10650
10651 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
10652 let g = self.cfg.gemma4.as_ref().unwrap();
10653 let swa = g.swa_pattern[il];
10654 let hd = if swa {
10655 g.key_length_swa
10656 } else {
10657 g.key_length_global
10658 } as usize;
10659 (
10663 hd,
10664 g.head_count_kv[il] as usize,
10665 self.cfg.n_head as usize,
10666 if swa {
10667 g.rope_base_swa
10668 } else {
10669 g.rope_base_global
10670 },
10671 1.0,
10672 swa,
10673 )
10674 }
10675
10676 pub(crate) fn gemma4_suppress(
10680 &self,
10681 e: &Engine,
10682 ld: &mut CudaSlice<f32>,
10683 t: usize,
10684 ) -> Result<(), Box<dyn std::error::Error>> {
10685 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
10686 #[cfg(debug_assertions)]
10691 crate::debug_assert_tensor_stream_device(
10692 ids,
10693 &e.stream(),
10694 "gemma4_suppress.suppress_d",
10695 );
10696 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
10697 }
10698 Ok(())
10699 }
10700
10701 #[allow(clippy::too_many_arguments)]
10706 fn gemma_fa_one_program() -> bool {
10715 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10716 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_FA_ONE_PROGRAM").as_deref() == Ok("1"))
10717 }
10718
10719 fn gemma4_attn_prime(
10720 &self,
10721 e: &Engine,
10722 fa: &crate::hybrid::FullAttnLayer,
10723 il: usize,
10724 h: &CudaSlice<f32>,
10725 pos_d: &CudaSlice<i32>,
10726 t: usize,
10727 cache: Option<&mut Cache>,
10728 island: Option<&CudaSlice<i32>>,
10729 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10730 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10731 let eps = self.cfg.rms_eps;
10732 let aux = self.gemma4_aux.as_ref().unwrap();
10733 let ones = aux.ones(e);
10734 #[cfg(debug_assertions)]
10735 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
10736
10737 e.mmq_act_begin();
10740 let q0 = e.matmul(&fa.wq, h, t)?; if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10742 let v = e.dtoh(&q0)?;
10743 let nan = v.iter().filter(|x| x.is_nan()).count();
10744 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10745 eprintln!(
10746 "[g4-prime-trace] L0 q0: nan={nan}/{} amax={amax:.3}",
10747 v.len()
10748 );
10749 }
10750 let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
10754 e.matmul(&fa.wv, h, t)?
10755 } else {
10756 e.clone_dtod(&k0)?
10757 };
10758 if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10759 for (tag, buf) in [("k0", &k0), ("v0", &v0)] {
10760 let v = e.dtoh(buf)?;
10761 let nan = v.iter().filter(|x| x.is_nan()).count();
10762 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10763 eprintln!(
10764 "[g4-prime-trace] L0 {tag}: nan={nan}/{} amax={amax:.3}",
10765 v.len()
10766 );
10767 }
10768 }
10769
10770 let mut q = e.uninit(t * nh * hd)?;
10771 let mut k = e.uninit(t * nkv * hd)?;
10772 let mut v = e.uninit(t * nkv * hd)?;
10774 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10778 let emit = island.is_none()
10781 && t >= 16
10782 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
10783 && *EMIT.get_or_init(|| {
10784 std::env::var("MEMRA_FA_EMIT")
10785 .map(|s| s != "0")
10786 .unwrap_or(true)
10787 });
10788 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
10789 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10790 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10791 let v_f16 = emit
10794 && crate::fa_f16pv_on()
10795 && match hd {
10796 512 => true,
10797 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
10798 _ => false,
10799 };
10800 if emit {
10801 e.rms_norm_qkv_w4b(
10802 &q0,
10803 &k0,
10804 &v0,
10805 fa.q_norm.float_data(),
10806 fa.k_norm.float_data(),
10807 ones,
10808 &mut q,
10809 &mut k,
10810 &mut v,
10811 &mut vb,
10812 hd,
10813 nh * t,
10814 nkv * t,
10815 eps,
10816 v_f16,
10817 )?;
10818 } else {
10819 e.rms_norm_qkv(
10820 &q0,
10821 &k0,
10822 &v0,
10823 fa.q_norm.float_data(),
10824 fa.k_norm.float_data(),
10825 ones,
10826 &mut q,
10827 &mut k,
10828 &mut v,
10829 hd,
10830 nh * t,
10831 nkv * t,
10832 eps,
10833 )?;
10834 }
10835
10836 let ff = if swa {
10837 None
10838 } else {
10839 Some(
10840 aux.rope_freqs(e)
10841 .expect("gemma4 global rope needs rope_freqs.weight"),
10842 )
10843 };
10844 #[cfg(debug_assertions)]
10845 if let Some(ff) = ff {
10846 crate::debug_assert_tensor_stream_device(
10847 ff,
10848 &e.stream(),
10849 "gemma4_attn_prime.rope_freqs",
10850 );
10851 }
10852 if emit {
10853 e.rope_neox2_bf16e(
10854 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
10855 )?;
10856 } else {
10857 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
10858 }
10859
10860 if let Some(cache) = cache {
10861 let kvl = cache.kv[il].as_mut().unwrap();
10862 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
10863 e.append_kv_quantized_rows(
10864 &k,
10865 &v,
10866 &mut kvl.k,
10867 &mut kvl.v,
10868 kvl.len,
10869 t,
10870 kvl.kv_dim_k,
10871 kvl.kv_dim_v,
10872 kvl.k_tok_bytes,
10873 kvl.v_tok_bytes,
10874 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
10875 )?;
10876 kvl.len += t;
10877 }
10878 let mut attn = e.zeros(t * nh * hd)?;
10879 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
10883 if let Some(span) = island {
10884 let w = if swa && t > win { win } else { 0 };
10889 e.sdpa_naive_island(&q, &k, &v, &mut attn, span, hd, nh, nkv, t, t, scale, w)?;
10890 } else if swa && (t > win || Self::gemma_fa_one_program()) {
10891 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
10892 if emit {
10893 e.fa_prefill_w_pre(
10894 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
10895 )?;
10896 } else {
10897 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
10898 }
10899 } else {
10900 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
10901 }
10902 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
10903 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
10904 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
10905 if emit {
10906 e.fa_prefill_hd512_pre(
10907 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
10908 )?;
10909 } else {
10910 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
10911 }
10912 } else {
10913 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
10914 }
10915 Ok(e.matmul(&fa.wo, &attn, t)?)
10916 }
10917
10918 fn gemma4_attn(
10920 &self,
10921 e: &Engine,
10922 fa: &crate::hybrid::FullAttnLayer,
10923 il: usize,
10924 h: &CudaSlice<f32>,
10925 pos_d: &CudaSlice<i32>,
10926 t: usize,
10927 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10928 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None, None)
10929 }
10930
10931 fn gemma4_moe_q8(
10936 &self,
10937 e: &Engine,
10938 m: &crate::hybrid::MoeWeights,
10939 bits: &crate::hybrid::Gemma4MoeBits,
10940 mq: &(CudaSlice<i8>, CudaSlice<f32>),
10941 router_in: &CudaSlice<f32>,
10942 t: usize,
10943 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10944 let cfg = &self.cfg;
10945 let moe = cfg.moe.as_ref().unwrap();
10946 let n_embd = cfg.n_embd as usize;
10947 let n_expert = moe.expert_count as usize;
10948 let n_used = moe.expert_used_count as usize;
10949 let n_ff_exp = moe.expert_ff_length as usize;
10950 let logits = if crate::router_kernel_on() {
10954 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
10955 } else {
10956 e.matmul(&m.gate_inp, router_in, t)?
10957 };
10958 let dev = m.dev_exps.as_ref().unwrap();
10959 let (sel_d, w_d) =
10960 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
10961 let (zq, zd) = mq;
10962 if t == 1 {
10963 let selv = sel_d.slice(0..n_used);
10964 let wv = w_d.slice(0..n_used);
10965 let act = e.moe_gate_up_gelu8_dev_q8(
10966 &dev.ptr_row,
10967 &selv,
10968 zq,
10969 zd,
10970 n_embd,
10971 n_ff_exp,
10972 n_used,
10973 n_expert,
10974 m.gate_exps.qtype,
10975 m.up_exps.qtype,
10976 m.gate_exps.row_bytes,
10977 m.up_exps.row_bytes,
10978 )?;
10979 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
10980 let mut moe_out = e.uninit(n_embd)?;
10981 e.moe_down8_fma_dev_q8(
10982 &dev.ptr_row,
10983 &selv,
10984 &wv,
10985 &aq2,
10986 &ad2,
10987 &mut moe_out.slice_mut(0..n_embd),
10988 n_ff_exp,
10989 n_embd,
10990 n_used,
10991 n_expert,
10992 m.down_exps.qtype,
10993 m.down_exps.row_bytes,
10994 )?;
10995 return Ok(moe_out);
10996 }
10997 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
10998 let act = if csr {
10999 e.moe_gate_up_gelu8_dev_q8_csr(
11000 &dev.ptr_row,
11001 &sel_d,
11002 zq,
11003 zd,
11004 t * n_used,
11005 n_embd,
11006 n_ff_exp,
11007 n_used,
11008 n_expert,
11009 m.gate_exps.qtype,
11010 m.up_exps.qtype,
11011 m.gate_exps.row_bytes,
11012 m.up_exps.row_bytes,
11013 )?
11014 } else {
11015 e.moe_gate_up_gelu8_dev_q8_rows(
11016 &dev.ptr_row,
11017 &sel_d,
11018 zq,
11019 zd,
11020 t,
11021 n_embd,
11022 n_ff_exp,
11023 n_used,
11024 n_expert,
11025 m.gate_exps.qtype,
11026 m.up_exps.qtype,
11027 m.gate_exps.row_bytes,
11028 m.up_exps.row_bytes,
11029 )?
11030 };
11031 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11032 let mut moe_out = e.uninit(t * n_embd)?;
11033 e.moe_down8_fma_dev_q8_rows_g(
11036 &dev.ptr_row,
11037 &sel_d,
11038 &w_d,
11039 &aq2,
11040 &ad2,
11041 &mut moe_out,
11042 t,
11043 n_ff_exp,
11044 n_embd,
11045 n_used,
11046 n_expert,
11047 m.down_exps.qtype,
11048 m.down_exps.row_bytes,
11049 )?;
11050 Ok(moe_out)
11051 }
11052
11053 fn gemma4_moe(
11057 &self,
11058 e: &Engine,
11059 m: &crate::hybrid::MoeWeights,
11060 bits: &crate::hybrid::Gemma4MoeBits,
11061 moe_in: &CudaSlice<f32>,
11062 router_in: &CudaSlice<f32>,
11063 t: usize,
11064 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11065 let cfg = &self.cfg;
11066 let moe = cfg.moe.as_ref().unwrap();
11067 let n_embd = cfg.n_embd as usize;
11068 let n_expert = moe.expert_count as usize;
11069 let n_used = moe.expert_used_count as usize;
11070 let n_ff_exp = moe.expert_ff_length as usize;
11071
11072 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
11076 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11077 } else {
11078 e.matmul(&m.gate_inp, router_in, t)?
11079 };
11080
11081 if t < PRIME_MIN_T
11086 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11087 && expert_dp4a_supported(m.gate_exps.qtype)
11088 && expert_dp4a_supported(m.up_exps.qtype)
11089 && expert_dp4a_supported(m.down_exps.qtype)
11090 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11091 {
11092 let dev = m.dev_exps.as_ref().unwrap();
11093 let (sel_d, w_d) =
11094 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11095 if t == 1 {
11096 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
11097 let selv = sel_d.slice(0..n_used);
11098 let wv = w_d.slice(0..n_used);
11099 let act = e.moe_gate_up_gelu8_dev_q8(
11100 &dev.ptr_row,
11101 &selv,
11102 &zq,
11103 &zd,
11104 n_embd,
11105 n_ff_exp,
11106 n_used,
11107 n_expert,
11108 m.gate_exps.qtype,
11109 m.up_exps.qtype,
11110 m.gate_exps.row_bytes,
11111 m.up_exps.row_bytes,
11112 )?;
11113 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11114 let mut moe_out = e.uninit(n_embd)?;
11115 e.moe_down8_fma_dev_q8(
11116 &dev.ptr_row,
11117 &selv,
11118 &wv,
11119 &aq2,
11120 &ad2,
11121 &mut moe_out.slice_mut(0..n_embd),
11122 n_ff_exp,
11123 n_embd,
11124 n_used,
11125 n_expert,
11126 m.down_exps.qtype,
11127 m.down_exps.row_bytes,
11128 )?;
11129 return Ok(moe_out);
11130 }
11131 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11136 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11137 let act = if csr {
11138 e.moe_gate_up_gelu8_dev_q8_csr(
11139 &dev.ptr_row,
11140 &sel_d,
11141 &zq,
11142 &zd,
11143 t * n_used,
11144 n_embd,
11145 n_ff_exp,
11146 n_used,
11147 n_expert,
11148 m.gate_exps.qtype,
11149 m.up_exps.qtype,
11150 m.gate_exps.row_bytes,
11151 m.up_exps.row_bytes,
11152 )?
11153 } else {
11154 e.moe_gate_up_gelu8_dev_q8_rows(
11155 &dev.ptr_row,
11156 &sel_d,
11157 &zq,
11158 &zd,
11159 t,
11160 n_embd,
11161 n_ff_exp,
11162 n_used,
11163 n_expert,
11164 m.gate_exps.qtype,
11165 m.up_exps.qtype,
11166 m.gate_exps.row_bytes,
11167 m.up_exps.row_bytes,
11168 )?
11169 };
11170 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11171 let mut moe_out = e.uninit(t * n_embd)?;
11172 e.moe_down8_fma_dev_q8_rows_g(
11173 &dev.ptr_row,
11174 &sel_d,
11175 &w_d,
11176 &aq2,
11177 &ad2,
11178 &mut moe_out,
11179 t,
11180 n_ff_exp,
11181 n_embd,
11182 n_used,
11183 n_expert,
11184 m.down_exps.qtype,
11185 m.down_exps.row_bytes,
11186 )?;
11187 return Ok(moe_out);
11188 }
11189
11190 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
11191 for (i, &sx) in sel_all.iter().enumerate() {
11192 w_all[i] *= bits.per_expert_scale[sx as usize];
11193 }
11194
11195 if t >= PRIME_MIN_T
11199 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11200 && expert_dp4a_supported(m.gate_exps.qtype)
11201 && expert_dp4a_supported(m.up_exps.qtype)
11202 && expert_dp4a_supported(m.down_exps.qtype)
11203 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
11204 {
11205 let dev = m.dev_exps.as_ref().unwrap();
11206 let n_pairs = t * n_used;
11207 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
11208 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
11209 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
11210 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
11211 let pt = e.htod_i32(&pair_tok)?;
11212 let pw = e.htod(&w_all)?;
11213 let toff = e.htod_i32(&tok_off)?;
11214 let tids = e.htod_i32(&tok_ids)?;
11215 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11216 for p in 0..n_pairs {
11217 by_ex[pair_ex[p] as usize].push(p as i32);
11218 }
11219 let mut ex_ids: Vec<i32> = Vec::new();
11220 let mut ex_off: Vec<i32> = vec![0];
11221 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
11222 for (ex, list) in by_ex.iter().enumerate() {
11223 if list.is_empty() {
11224 continue;
11225 }
11226 ex_ids.push(ex as i32);
11227 ex_pairs.extend_from_slice(list);
11228 ex_off.push(ex_pairs.len() as i32);
11229 }
11230 let n_active = ex_ids.len();
11231 let exi = e.htod_i32(&ex_ids)?;
11232 let exo = e.htod_i32(&ex_off)?;
11233 let exp_d = e.htod_i32(&ex_pairs)?;
11234 if crate::moe_f16g_gemma_on()
11242 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
11243 && f16g_proj_ok(m.up_exps.qtype, n_embd)
11244 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
11245 {
11246 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11247 let csr_tok_d = e.htod_i32(&csr_tok)?;
11248 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
11249 let g_csr = e.moe_f16_grouped(
11250 &dev.ptr_row,
11251 0,
11252 n_expert,
11253 &exi,
11254 &ex_off,
11255 &exo,
11256 &z_f16,
11257 &z_s,
11258 n_embd,
11259 n_ff_exp,
11260 n_active,
11261 n_pairs,
11262 m.gate_exps.qtype,
11263 m.gate_exps.row_bytes,
11264 )?;
11265 let u_csr = e.moe_f16_grouped(
11266 &dev.ptr_row,
11267 1,
11268 n_expert,
11269 &exi,
11270 &ex_off,
11271 &exo,
11272 &z_f16,
11273 &z_s,
11274 n_embd,
11275 n_ff_exp,
11276 n_active,
11277 n_pairs,
11278 m.up_exps.qtype,
11279 m.up_exps.row_bytes,
11280 )?;
11281 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
11282 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
11283 let d_csr = e.moe_f16_grouped(
11284 &dev.ptr_row,
11285 2,
11286 n_expert,
11287 &exi,
11288 &ex_off,
11289 &exo,
11290 &a_f16,
11291 &a_s,
11292 n_ff_exp,
11293 n_embd,
11294 n_active,
11295 n_pairs,
11296 m.down_exps.qtype,
11297 m.down_exps.row_bytes,
11298 )?;
11299 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
11300 let mut moe_out = e.uninit(t * n_embd)?;
11301 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11302 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
11303 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
11304 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
11305 eprintln!(
11306 "[f16g-debug] post-permute bad={} post-scatter bad={}",
11307 scan(&yd),
11308 scan(&mo)
11309 );
11310 }
11311 return Ok(moe_out);
11312 }
11313 let mma =
11316 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
11317 let (gate, up) = if mma {
11318 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
11319 (
11320 e.mmq_iq_experts(
11321 &dev.ptr_row,
11322 0,
11323 n_expert,
11324 &exi,
11325 &exo,
11326 &exp_d,
11327 &pt,
11328 &z_scr,
11329 n_embd,
11330 n_ff_exp,
11331 n_active,
11332 n_pairs,
11333 t,
11334 m.gate_exps.qtype,
11335 m.gate_exps.row_bytes,
11336 )?,
11337 e.mmq_iq_experts(
11338 &dev.ptr_row,
11339 1,
11340 n_expert,
11341 &exi,
11342 &exo,
11343 &exp_d,
11344 &pt,
11345 &z_scr,
11346 n_embd,
11347 n_ff_exp,
11348 n_active,
11349 n_pairs,
11350 t,
11351 m.up_exps.qtype,
11352 m.up_exps.row_bytes,
11353 )?,
11354 )
11355 } else {
11356 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11357 (
11358 e.moe_pairs_matvec_q8_dec(
11359 &dev.ptr_row,
11360 0,
11361 &exi,
11362 &exo,
11363 &exp_d,
11364 &pt,
11365 &zq,
11366 &zd,
11367 n_embd,
11368 n_ff_exp,
11369 n_expert,
11370 n_active,
11371 n_pairs,
11372 m.gate_exps.qtype,
11373 m.gate_exps.row_bytes,
11374 )?,
11375 e.moe_pairs_matvec_q8_dec(
11376 &dev.ptr_row,
11377 1,
11378 &exi,
11379 &exo,
11380 &exp_d,
11381 &pt,
11382 &zq,
11383 &zd,
11384 n_embd,
11385 n_ff_exp,
11386 n_expert,
11387 n_active,
11388 n_pairs,
11389 m.up_exps.qtype,
11390 m.up_exps.row_bytes,
11391 )?,
11392 )
11393 };
11394 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
11395 let pself = e.htod_i32(&pair_self)?;
11396 let y_down = if mma {
11408 let in_pad = n_ff_exp.div_ceil(256) * 256;
11409 let a_scr = if crate::moe_fuse_actq_on() {
11410 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
11411 } else {
11412 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11413 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
11414 };
11415 e.mmq_iq_experts(
11416 &dev.ptr_row,
11417 2,
11418 n_expert,
11419 &exi,
11420 &exo,
11421 &exp_d,
11422 &pself,
11423 &a_scr,
11424 in_pad,
11425 n_embd,
11426 n_active,
11427 n_pairs,
11428 n_pairs,
11429 m.down_exps.qtype,
11430 m.down_exps.row_bytes,
11431 )?
11432 } else {
11433 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11434 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
11435 e.moe_pairs_matvec_q8_dec(
11436 &dev.ptr_row,
11437 2,
11438 &exi,
11439 &exo,
11440 &exp_d,
11441 &pself,
11442 &aq2,
11443 &ad2,
11444 n_ff_exp,
11445 n_embd,
11446 n_expert,
11447 n_active,
11448 n_pairs,
11449 m.down_exps.qtype,
11450 m.down_exps.row_bytes,
11451 )?
11452 };
11453 let mut moe_out = e.uninit(t * n_embd)?;
11454 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11455 return Ok(moe_out);
11456 }
11457
11458 let g_len = m.gate_exps.expert_stride;
11459 let u_len = m.up_exps.expert_stride;
11460 let d_len = m.down_exps.expert_stride;
11461 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
11465 let (mut sg, mut su, mut sd) = if dev.is_some() {
11466 (None, None, None)
11467 } else {
11468 (
11469 Some(e.alloc_u8_uninit(g_len)?),
11470 Some(e.alloc_u8_uninit(u_len)?),
11471 Some(e.alloc_u8_uninit(d_len)?),
11472 )
11473 };
11474 let mut moe_out = e.zeros(t * n_embd)?;
11475 for tok in 0..t {
11476 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
11477 let w = &w_all[tok * n_used..(tok + 1) * n_used];
11478 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
11479 for (j, &ex) in sel.iter().enumerate() {
11480 let ex = ex as usize;
11481 let gate = match dev {
11482 Some(d) => e.qmatvec_view(
11483 &d.gate,
11484 ex * g_len..(ex + 1) * g_len,
11485 &zt,
11486 1,
11487 m.gate_exps.in_f,
11488 m.gate_exps.out_f,
11489 m.gate_exps.qtype,
11490 m.gate_exps.row_bytes,
11491 )?,
11492 None => {
11493 let sg = sg.as_mut().unwrap();
11494 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
11495 e.qmatvec_view(
11496 sg,
11497 0..g_len,
11498 &zt,
11499 1,
11500 m.gate_exps.in_f,
11501 m.gate_exps.out_f,
11502 m.gate_exps.qtype,
11503 m.gate_exps.row_bytes,
11504 )?
11505 }
11506 };
11507 let up = match dev {
11508 Some(d) => e.qmatvec_view(
11509 &d.up,
11510 ex * u_len..(ex + 1) * u_len,
11511 &zt,
11512 1,
11513 m.up_exps.in_f,
11514 m.up_exps.out_f,
11515 m.up_exps.qtype,
11516 m.up_exps.row_bytes,
11517 )?,
11518 None => {
11519 let su = su.as_mut().unwrap();
11520 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
11521 e.qmatvec_view(
11522 su,
11523 0..u_len,
11524 &zt,
11525 1,
11526 m.up_exps.in_f,
11527 m.up_exps.out_f,
11528 m.up_exps.qtype,
11529 m.up_exps.row_bytes,
11530 )?
11531 }
11532 };
11533 let mut act = e.uninit(n_ff_exp)?;
11534 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
11535 let actv = act.slice(0..n_ff_exp);
11536 let y = match dev {
11537 Some(d) => e.qmatvec_view(
11538 &d.down,
11539 ex * d_len..(ex + 1) * d_len,
11540 &actv,
11541 1,
11542 m.down_exps.in_f,
11543 m.down_exps.out_f,
11544 m.down_exps.qtype,
11545 m.down_exps.row_bytes,
11546 )?,
11547 None => {
11548 let sd = sd.as_mut().unwrap();
11549 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
11550 e.qmatvec_view(
11551 sd,
11552 0..d_len,
11553 &actv,
11554 1,
11555 m.down_exps.in_f,
11556 m.down_exps.out_f,
11557 m.down_exps.qtype,
11558 m.down_exps.row_bytes,
11559 )?
11560 }
11561 };
11562 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
11563 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
11564 }
11565 }
11566 Ok(moe_out)
11567 }
11568
11569 fn gemma4_layer(
11571 &self,
11572 e: &Engine,
11573 il: usize,
11574 layer: &crate::hybrid::HybridLayer,
11575 x: &CudaSlice<f32>,
11576 pos_d: &CudaSlice<i32>,
11577 t: usize,
11578 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11579 let n_embd = self.cfg.n_embd as usize;
11580 let eps = self.cfg.rms_eps;
11581
11582 let mut h = e.zeros(t * n_embd)?;
11583 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
11584 let Mixer::Full(fa) = &layer.mixer else {
11585 panic!("gemma4 layer {il} not full-attn")
11586 };
11587 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
11588 let mut cur = e.zeros(t * n_embd)?;
11590 e.rms_norm(
11591 &o,
11592 layer.post_attn_norm.float_data(),
11593 &mut cur,
11594 n_embd,
11595 t,
11596 eps,
11597 )?;
11598 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
11599 }
11600
11601 fn gemma4_layer_tail_add(
11605 &self,
11606 e: &Engine,
11607 layer: &crate::hybrid::HybridLayer,
11608 cur: &CudaSlice<f32>,
11609 x: &CudaSlice<f32>,
11610 t: usize,
11611 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11612 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
11613 }
11614
11615 fn gemma4_layer_tail_add_n(
11618 &self,
11619 e: &Engine,
11620 layer: &crate::hybrid::HybridLayer,
11621 cur: &CudaSlice<f32>,
11622 x: &CudaSlice<f32>,
11623 t: usize,
11624 next_norm: Option<&CudaSlice<f32>>,
11625 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
11626 let n_embd = self.cfg.n_embd as usize;
11627 let bits = layer.gemma4.as_ref().unwrap();
11628 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
11629 let mut xn = e.uninit(t * n_embd)?;
11630 match next_norm {
11631 Some(w) => {
11632 let mut hn = e.uninit(t * n_embd)?;
11633 e.add_scale_rms_norm(
11634 &sn,
11635 &attn_out,
11636 bits.layer_scale,
11637 w,
11638 &mut xn,
11639 &mut hn,
11640 n_embd,
11641 t,
11642 self.cfg.rms_eps,
11643 )?;
11644 Ok((xn, Some(hn)))
11645 }
11646 None => {
11647 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
11648 Ok((xn, None))
11649 }
11650 }
11651 }
11652
11653 fn gemma4_layer_tail_core(
11656 &self,
11657 e: &Engine,
11658 layer: &crate::hybrid::HybridLayer,
11659 cur: &CudaSlice<f32>,
11660 x: &CudaSlice<f32>,
11661 t: usize,
11662 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11663 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
11664 }
11665
11666 fn gemma4_layer_tail_core_pn(
11673 &self,
11674 e: &Engine,
11675 layer: &crate::hybrid::HybridLayer,
11676 cur: &CudaSlice<f32>,
11677 x: &CudaSlice<f32>,
11678 t: usize,
11679 pre_norm: Option<&CudaSlice<f32>>,
11680 defer_post_norm: bool,
11681 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11682 let n_embd = self.cfg.n_embd as usize;
11683 let eps = self.cfg.rms_eps;
11684 let bits = layer.gemma4.as_ref().unwrap();
11685
11686 let Some(mbits) = bits.moe_bits.as_ref() else {
11689 let crate::hybrid::Ffn::Dense {
11690 ffn_gate,
11691 ffn_up,
11692 ffn_down,
11693 } = &layer.ffn
11694 else {
11695 panic!("gemma4 dense layer without Dense ffn")
11696 };
11697 let mut attn_out = e.uninit(t * n_embd)?;
11698 let mut zsh = e.uninit(t * n_embd)?;
11699 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11702 match pre_norm {
11703 Some(wa) if t == 1 => {
11704 zpair = Some(e.rms_pre_add_rms_norm_q8z(
11705 cur,
11706 wa,
11707 x,
11708 bits.ffn_norm.float_data(),
11709 &mut attn_out,
11710 &mut zsh,
11711 n_embd,
11712 t,
11713 eps,
11714 )?);
11715 }
11716 Some(wa) => e.rms_pre_add_rms_norm(
11717 cur,
11718 wa,
11719 x,
11720 bits.ffn_norm.float_data(),
11721 &mut attn_out,
11722 &mut zsh,
11723 n_embd,
11724 t,
11725 eps,
11726 )?,
11727 None => e.add_rms_norm(
11728 cur,
11729 x,
11730 bits.ffn_norm.float_data(),
11731 &mut attn_out,
11732 &mut zsh,
11733 n_embd,
11734 t,
11735 eps,
11736 )?,
11737 }
11738 let n_ff = ffn_gate.out_features();
11739 let (gate, up) = if t == 1 {
11745 let (zq, zd) = match zpair {
11746 Some(p) => p,
11747 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
11748 };
11749 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
11750 Some(p) => p,
11751 None => match e.matmul_nvfp4_fused2(ffn_gate, ffn_up, &zq, &zd, 1)? {
11753 Some(p) => p,
11754 None => (
11755 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
11756 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
11757 ),
11758 },
11759 }
11760 } else {
11761 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11766 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11767 let fused = if f2b {
11768 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
11769 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
11770 } else {
11771 None
11772 };
11773 match fused {
11774 Some(p) => p,
11775 None => {
11776 e.mmq_act_begin();
11778 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
11779 }
11780 }
11781 };
11782 let mut act = e.uninit(t * n_ff)?;
11783 let f0 = if e.uses_q8_1_fast(ffn_down) {
11786 let upv = e.view(&up, t * n_ff);
11787 let up_all = upv.slice(0..t * n_ff);
11788 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
11789 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
11790 } else {
11791 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11792 e.matmul(ffn_down, &act, t)?
11793 };
11794 if defer_post_norm {
11795 return Ok((f0, attn_out));
11796 }
11797 let mut sn = e.uninit(t * n_embd)?;
11798 e.rms_norm(
11799 &f0,
11800 bits.post_ffw_norm.float_data(),
11801 &mut sn,
11802 n_embd,
11803 t,
11804 eps,
11805 )?;
11806 return Ok((sn, attn_out));
11807 };
11808
11809 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
11810 let mut attn_out = e.uninit(t * n_embd)?;
11815 let mut router_in = e.uninit(t * n_embd)?;
11816 let fast_moe = match &layer.ffn {
11817 crate::hybrid::Ffn::Moe(m) => {
11818 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11819 && expert_dp4a_supported(m.gate_exps.qtype)
11820 && expert_dp4a_supported(m.up_exps.qtype)
11821 && expert_dp4a_supported(m.down_exps.qtype)
11822 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11823 }
11824 _ => false,
11825 };
11826 let q8z = t < PRIME_MIN_T && fast_moe;
11827 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
11828 let (z0, m2) = e.add_rms_norm3_q8z(
11829 cur,
11830 x,
11831 bits.ffn_norm.float_data(),
11832 &mbits.router_scale_pre,
11833 mbits.pre_ffw_norm_2.float_data(),
11834 &mut attn_out,
11835 &mut router_in,
11836 n_embd,
11837 t,
11838 eps,
11839 )?;
11840 (None, Some(z0), Some(m2))
11841 } else {
11842 let mut zsh = e.uninit(t * n_embd)?;
11843 let mut moe_in = e.uninit(t * n_embd)?;
11844 e.add_rms_norm3(
11845 cur,
11846 x,
11847 bits.ffn_norm.float_data(),
11848 &mbits.router_scale_pre,
11849 mbits.pre_ffw_norm_2.float_data(),
11850 &mut attn_out,
11851 &mut zsh,
11852 &mut router_in,
11853 &mut moe_in,
11854 n_embd,
11855 t,
11856 eps,
11857 )?;
11858 (Some((zsh, moe_in)), None, None)
11859 };
11860 let attn_out2 = attn_out;
11861 #[allow(unused_variables)]
11862 let attn_out = &attn_out2;
11863 let n_ff = mbits.shared_gate.out_features();
11864 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
11865 if t == 1 {
11866 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
11867 Some(p) => p,
11868 None => match e.matmul_nvfp4_fused2(
11869 &mbits.shared_gate,
11870 &mbits.shared_up,
11871 zq,
11872 zd,
11873 1,
11874 )? {
11875 Some(p) => p,
11876 None => {
11877 let h0 = e.zeros(0)?;
11878 (
11879 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
11880 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
11881 )
11882 }
11883 },
11884 }
11885 } else {
11886 let h0 = e.zeros(0)?;
11888 (
11889 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
11890 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
11891 )
11892 }
11893 } else {
11894 let (zsh, _) = zsh_f32.as_ref().unwrap();
11895 (
11896 e.matmul(&mbits.shared_gate, zsh, t)?,
11897 e.matmul(&mbits.shared_up, zsh, t)?,
11898 )
11899 };
11900 let mut act = e.uninit(t * n_ff)?;
11901 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11902 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
11903 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
11904 panic!("gemma4 layer not MoE")
11905 };
11906 let moe0 = match (&moe_q8, &zsh_f32) {
11907 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
11908 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
11909 _ => unreachable!(),
11910 };
11911 let mut mlp = e.uninit(t * n_embd)?;
11913 let mut moe = e.uninit(t * n_embd)?;
11914 e.rms_norm2x(
11915 &mlp0,
11916 &moe0,
11917 mbits.post_ffw_norm_1.float_data(),
11918 mbits.post_ffw_norm_2.float_data(),
11919 &mut mlp,
11920 &mut moe,
11921 n_embd,
11922 t,
11923 eps,
11924 )?;
11925
11926 let mut sum = e.uninit(t * n_embd)?;
11929 let mut sn = e.uninit(t * n_embd)?;
11930 e.add_rms_norm(
11931 &mlp,
11932 &moe,
11933 bits.post_ffw_norm.float_data(),
11934 &mut sum,
11935 &mut sn,
11936 n_embd,
11937 t,
11938 eps,
11939 )?;
11940 Ok((sn, attn_out2))
11941 }
11942
11943 pub(crate) fn gemma4_layer_tail_add_nq_pn(
11953 &self,
11954 e: &Engine,
11955 layer: &crate::hybrid::HybridLayer,
11956 o: &CudaSlice<f32>,
11957 x: &CudaSlice<f32>,
11958 t: usize,
11959 next_norm: Option<&CudaSlice<f32>>,
11960 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
11961 {
11962 let n_embd = self.cfg.n_embd as usize;
11963 let eps = self.cfg.rms_eps;
11964 let bits = layer.gemma4.as_ref().unwrap();
11965 if Engine::g4_pnfold_on() && matches!(layer.ffn, crate::hybrid::Ffn::Dense { .. }) {
11966 let (f0, attn_out) = self.gemma4_layer_tail_core_pn(
11967 e,
11968 layer,
11969 o,
11970 x,
11971 t,
11972 Some(layer.post_attn_norm.float_data()),
11973 true,
11974 )?;
11975 let mut xn = e.uninit(t * n_embd)?;
11976 return match next_norm {
11977 Some(w) => {
11978 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
11979 &f0,
11980 bits.post_ffw_norm.float_data(),
11981 &attn_out,
11982 bits.layer_scale,
11983 w,
11984 &mut xn,
11985 n_embd,
11986 t,
11987 eps,
11988 )?;
11989 Ok((xn, Some(pair)))
11990 }
11991 None => {
11992 let mut sn = e.uninit(t * n_embd)?;
11993 e.rms_norm(
11994 &f0,
11995 bits.post_ffw_norm.float_data(),
11996 &mut sn,
11997 n_embd,
11998 t,
11999 eps,
12000 )?;
12001 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12002 Ok((xn, None))
12003 }
12004 };
12005 }
12006 let mut cur = e.uninit(t * n_embd)?;
12007 e.rms_norm(
12008 o,
12009 layer.post_attn_norm.float_data(),
12010 &mut cur,
12011 n_embd,
12012 t,
12013 eps,
12014 )?;
12015 self.gemma4_layer_tail_add_nq(e, layer, &cur, x, t, next_norm)
12016 }
12017
12018 pub(crate) fn gemma4_layer_tail_add_nq(
12019 &self,
12020 e: &Engine,
12021 layer: &crate::hybrid::HybridLayer,
12022 cur: &CudaSlice<f32>,
12023 x: &CudaSlice<f32>,
12024 t: usize,
12025 next_norm: Option<&CudaSlice<f32>>,
12026 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12027 {
12028 let n_embd = self.cfg.n_embd as usize;
12029 let bits = layer.gemma4.as_ref().unwrap();
12030 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12031 let mut xn = e.uninit(t * n_embd)?;
12032 match next_norm {
12033 Some(w) => {
12034 let pair = e.add_scale_rms_norm_q8_1(
12035 &sn,
12036 &attn_out,
12037 bits.layer_scale,
12038 w,
12039 &mut xn,
12040 n_embd,
12041 t,
12042 self.cfg.rms_eps,
12043 )?;
12044 Ok((xn, Some(pair)))
12045 }
12046 None => {
12047 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12048 Ok((xn, None))
12049 }
12050 }
12051 }
12052
12053 fn gemma4_forward(
12056 &self,
12057 e: &Engine,
12058 tokens: &[u32],
12059 last_only: bool,
12060 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12061 if self.is_gemma4_e4b() {
12064 return self.gemma4_e4b_forward(e, tokens, last_only);
12065 }
12066 let n_embd = self.cfg.n_embd as usize;
12067 let t = tokens.len();
12068 let pos: Vec<i32> = (0..t as i32).collect();
12069 let pos_d = e.htod_i32(&pos)?;
12070
12071 let mut x = self.embed(e, tokens)?;
12072 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12073 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
12076 let stat =
12077 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
12078 let h = e.dtoh(x)?;
12079 let bad = h.iter().filter(|v| !v.is_finite()).count();
12080 let mx = h
12081 .iter()
12082 .filter(|v| v.is_finite())
12083 .fold(0.0f32, |m, v| m.max(v.abs()));
12084 eprintln!(
12085 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
12086 &h[..3]
12087 );
12088 Ok(())
12089 };
12090 if probe {
12091 stat(e, &x, "embed")?;
12092 }
12093 for (il, layer) in self.layers.iter().enumerate() {
12094 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
12095 if probe {
12096 stat(e, &x, &format!("L{il}"))?;
12097 }
12098 }
12099 let mut hn = e.zeros(t * n_embd)?;
12100 e.rms_norm(
12101 &x,
12102 self.output_norm.float_data(),
12103 &mut hn,
12104 n_embd,
12105 t,
12106 self.cfg.rms_eps,
12107 )?;
12108 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12109 let n_vocab = self.output.out_features();
12110 let logits = if last_only {
12111 let hv = e.view(&hn, t * n_embd);
12112 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
12113 let mut hlast = e.zeros(n_embd)?;
12114 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
12115 let mut ld = e.matmul(&self.output, &hlast, 1)?;
12116 e.softcap(&mut ld, cap, n_vocab)?;
12117 self.gemma4_suppress(e, &mut ld, 1)?;
12118 e.dtoh(&ld)?
12119 } else {
12120 let mut ld = e.matmul(&self.output, &hn, t)?;
12121 e.softcap(&mut ld, cap, t * n_vocab)?;
12122 self.gemma4_suppress(e, &mut ld, t)?;
12123 e.dtoh(&ld)?
12124 };
12125 Ok(logits)
12126 }
12127
12128 pub(crate) fn gemma4_prime(
12133 &self,
12134 e: &Engine,
12135 tokens: &[u32],
12136 cache: &mut Cache,
12137 overlay: Option<&crate::vision::EmbedOverlay>,
12138 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12139 if cache.pos != 0 {
12144 return Err(
12145 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
12146 — prime the full prompt in one call or decode tokenwise"
12147 .into(),
12148 );
12149 }
12150 let n_embd = self.cfg.n_embd as usize;
12151 let eps = self.cfg.rms_eps;
12152 let t = tokens.len();
12153 let pos: Vec<i32> = (0..t as i32).collect();
12154 let pos_d = e.htod_i32(&pos)?;
12155 let mut x = self.embed(e, tokens)?;
12156 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12157 let island: Option<CudaSlice<i32>> = match overlay {
12164 Some(ov) => {
12165 let mut span_id = vec![-1i32; t];
12166 for (i, &(pos, row_off, n_rows)) in ov.spans.iter().enumerate() {
12167 if pos + n_rows > t {
12168 return Err(format!(
12169 "gemma4 overlay span {i} [{pos}, {}) exceeds the prompt ({t})",
12170 pos + n_rows
12171 )
12172 .into());
12173 }
12174 let view = ov.rows.slice(row_off * n_embd..(row_off + n_rows) * n_embd);
12175 e.copy_view_into(&mut x, pos * n_embd, &view, n_rows * n_embd)?;
12176 for s in span_id.iter_mut().skip(pos).take(n_rows) {
12177 *s = i as i32;
12178 }
12179 }
12180 if std::env::var("MEMRA_GV_FORCE_CAUSAL").as_deref() == Ok("1") {
12184 None
12185 } else {
12186 Some(e.htod_i32(&span_id)?)
12187 }
12188 }
12189 None => None,
12190 };
12191 for (il, layer) in self.layers.iter().enumerate() {
12192 let mut h = e.zeros(t * n_embd)?;
12193 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12194 let Mixer::Full(fa) = &layer.mixer else {
12195 panic!("gemma4 layer not full-attn")
12196 };
12197 let trace = il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1");
12198 if trace {
12199 let v = e.dtoh(&h)?;
12200 let nan = v.iter().filter(|x| x.is_nan()).count();
12201 eprintln!("[g4-prime-trace] L0 post-attn_norm: nan={nan}/{}", v.len());
12202 }
12203 let o =
12204 self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache), island.as_ref())?;
12205 if trace {
12206 let v = e.dtoh(&o)?;
12207 let nan = v.iter().filter(|x| x.is_nan()).count();
12208 eprintln!("[g4-prime-trace] L0 post-attn: nan={nan}/{}", v.len());
12209 }
12210 let mut cur = e.zeros(t * n_embd)?;
12211 e.rms_norm(
12212 &o,
12213 layer.post_attn_norm.float_data(),
12214 &mut cur,
12215 n_embd,
12216 t,
12217 eps,
12218 )?;
12219 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
12220 self.dflash_tap(e, cache, il, &x, t)?;
12221 if std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
12223 let h = e.dtoh(&x)?;
12224 let nan = h.iter().filter(|v| v.is_nan()).count();
12225 let amax = h.iter().fold(0f32, |a, v| a.max(v.abs()));
12226 eprintln!(
12227 "[g4-prime-trace] layer {il}: nan={nan}/{} amax={amax:.3}",
12228 h.len()
12229 );
12230 if nan > 0 {
12231 return Err(format!("g4-prime-trace: first NaN at layer {il}").into());
12232 }
12233 }
12234 }
12235 cache.pos += t;
12236 let hiddens = e.clone_dtod(&x)?;
12237 let xv = e.view(&x, t * n_embd);
12238 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
12239 let mut h_seed = e.zeros(n_embd)?;
12240 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
12241 let mut hn = e.uninit(n_embd)?;
12242 e.rms_norm(
12243 &h_seed,
12244 self.output_norm.float_data(),
12245 &mut hn,
12246 n_embd,
12247 1,
12248 eps,
12249 )?;
12250 let mut ld = e.matmul(&self.output, &hn, 1)?;
12251 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12252 e.softcap(&mut ld, cap, self.output.out_features())?;
12253 self.gemma4_suppress(e, &mut ld, 1)?;
12254 let logits = e.dtoh(&ld)?;
12255 Ok((logits, h_seed, hiddens))
12256 }
12257
12258 fn gemma4_decode_attn(
12263 &self,
12264 e: &Engine,
12265 fa: &crate::hybrid::FullAttnLayer,
12266 il: usize,
12267 hq: &CudaSlice<i8>,
12268 hdq: &CudaSlice<f32>,
12269 pos_d: &CudaSlice<i32>,
12270 cache: &mut Cache,
12271 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12272 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12273 let eps = self.cfg.rms_eps;
12274 let aux = self.gemma4_aux.as_ref().unwrap();
12275 let ones = aux.ones(e);
12276 #[cfg(debug_assertions)]
12277 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
12278 let (hq, hdq) = (hq, hdq);
12279 let h0 = e.zeros(0)?;
12280 let h = &h0;
12281 let (q0, k0, v0) = if swa {
12282 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12283 Some(t3) => t3,
12284 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12287 Some((q0, k0)) => {
12288 let v0 = e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?;
12289 (q0, k0, v0)
12290 }
12291 None => (
12292 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12293 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12294 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12295 ),
12296 },
12297 }
12298 } else {
12299 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
12300 Some(p) => p,
12301 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12302 Some(p) => p,
12303 None => (
12304 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12305 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12306 ),
12307 },
12308 };
12309 let v0 = e.clone_dtod(&k0)?;
12310 (q0, k0, v0)
12311 };
12312 let mut q = e.uninit(nh * hd)?;
12313 let mut k = e.uninit(nkv * hd)?;
12314 let mut v = e.uninit(nkv * hd)?;
12315 let ff = if swa {
12318 None
12319 } else {
12320 Some(
12321 aux.rope_freqs(e)
12322 .expect("gemma4 global rope needs rope_freqs.weight"),
12323 )
12324 };
12325 #[cfg(debug_assertions)]
12326 if let Some(ff) = ff {
12327 crate::debug_assert_tensor_stream_device(
12328 ff,
12329 &e.stream(),
12330 "gemma4_decode_attn.rope_freqs",
12331 );
12332 }
12333 let kvl = cache.kv[il].as_mut().unwrap();
12334 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12335 if crate::Engine::qkv_append_on() {
12336 e.rms_norm_qkv_rope_append(
12340 &q0,
12341 &k0,
12342 &v0,
12343 fa.q_norm.float_data(),
12344 fa.k_norm.float_data(),
12345 ones,
12346 &mut q,
12347 &mut k,
12348 &mut v,
12349 hd,
12350 self.gemma4_rope_dims(il),
12351 nh,
12352 nkv,
12353 pos_d,
12354 nh,
12355 nkv,
12356 base,
12357 1.0,
12358 ff,
12359 eps,
12360 &mut kvl.k,
12361 &mut kvl.v,
12362 kvl.len,
12363 kvl.k_tok_bytes,
12364 kvl.v_tok_bytes,
12365 kv_fp8,
12366 )?;
12367 } else {
12368 e.rms_norm_qkv_rope(
12369 &q0,
12370 &k0,
12371 &v0,
12372 fa.q_norm.float_data(),
12373 fa.k_norm.float_data(),
12374 ones,
12375 &mut q,
12376 &mut k,
12377 &mut v,
12378 hd,
12379 self.gemma4_rope_dims(il),
12380 nh,
12381 nkv,
12382 pos_d,
12383 nh,
12384 nkv,
12385 base,
12386 1.0,
12387 ff,
12388 eps,
12389 )?;
12390 e.append_kv_quantized(
12391 &k,
12392 &v,
12393 &mut kvl.k,
12394 &mut kvl.v,
12395 kvl.len,
12396 kvl.kv_dim_k,
12397 kvl.kv_dim_v,
12398 kvl.k_tok_bytes,
12399 kvl.v_tok_bytes,
12400 kv_fp8,
12401 )?;
12402 }
12403 kvl.len += 1;
12404 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12408 let mut attn = e.uninit(nh * hd)?;
12409 if !swa
12411 && hd == 512
12412 && kvl.len >= crate::fa512_min_tkv()
12413 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12414 {
12415 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12416 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12417 let base = kvl.len as i32;
12419 e.i32_set_k(&mut kvl.len_d, base)?;
12420 e.fa_decode_rows(
12421 &q,
12422 &kp,
12423 &vp,
12424 &mut attn,
12425 hd,
12426 nh,
12427 nkv,
12428 kvl.len - 1,
12429 1,
12430 scale,
12431 kvl.k_tok_bytes,
12432 kvl.v_tok_bytes,
12433 Some((&kvl.len_d, -1)),
12434 false,
12435 false,
12436 None,
12437 )?;
12438 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12439 }
12440 if swa
12442 && kvl.len > win
12443 && hd == 256
12444 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12445 {
12446 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12447 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12448 let base = kvl.len as i32;
12449 e.i32_set_k(&mut kvl.len_d, base)?;
12450 e.fa_decode_rows_w(
12451 &q,
12452 &kp,
12453 &vp,
12454 &mut attn,
12455 hd,
12456 nh,
12457 nkv,
12458 &kvl.len_d,
12459 -1,
12460 1,
12461 scale,
12462 win,
12463 kvl.k_tok_bytes,
12464 kvl.v_tok_bytes,
12465 None,
12466 )?;
12467 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12468 }
12469 let (off_tok, t_kv) = if swa && kvl.len > win {
12470 (kvl.len - win, win)
12471 } else {
12472 (0, kvl.len)
12473 };
12474 let k_view = e.view_u8_range(
12475 &kvl.k,
12476 off_tok * kvl.k_tok_bytes,
12477 (off_tok + t_kv) * kvl.k_tok_bytes,
12478 );
12479 let v_view = e.view_u8_range(
12480 &kvl.v,
12481 off_tok * kvl.v_tok_bytes,
12482 (off_tok + t_kv) * kvl.v_tok_bytes,
12483 );
12484 e.fa_decode_kvmod(
12485 &q,
12486 &k_view,
12487 &v_view,
12488 &mut attn,
12489 hd,
12490 nh,
12491 nkv,
12492 t_kv,
12493 scale,
12494 kvl.k_tok_bytes,
12495 kvl.v_tok_bytes,
12496 swa && crate::Engine::wkv_on(),
12497 )?;
12498 Ok(e.matmul(&fa.wo, &attn, 1)?)
12499 }
12500
12501 #[allow(clippy::too_many_arguments)]
12508 pub fn gemma4_decode_step_dc(
12509 &self,
12510 e: &Engine,
12511 token_d: &CudaSlice<u32>,
12512 pos_d: &mut CudaSlice<i32>,
12513 embd_gpu: &CudaSlice<u8>,
12514 embd_qt: i32,
12515 embd_rb: usize,
12516 cache: &mut Cache,
12517 n_vocab: usize,
12518 cap_bucket_max: Option<(usize, usize)>,
12519 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
12520 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
12521 self.gemma4_decode_step_dc_into(
12522 e,
12523 token_d,
12524 pos_d,
12525 embd_gpu,
12526 embd_qt,
12527 embd_rb,
12528 cache,
12529 n_vocab,
12530 cap_bucket_max,
12531 &mut tok_out,
12532 )?;
12533 Ok(tok_out)
12534 }
12535
12536 #[allow(clippy::too_many_arguments)]
12539 pub fn gemma4_decode_step_dc_into(
12540 &self,
12541 e: &Engine,
12542 token_d: &CudaSlice<u32>,
12543 pos_d: &mut CudaSlice<i32>,
12544 embd_gpu: &CudaSlice<u8>,
12545 embd_qt: i32,
12546 embd_rb: usize,
12547 cache: &mut Cache,
12548 n_vocab: usize,
12549 cap_bucket_max: Option<(usize, usize)>,
12550 tok_out: &mut CudaSlice<u32>,
12551 ) -> Result<(), Box<dyn std::error::Error>> {
12552 let n_embd = self.cfg.n_embd as usize;
12553 let eps = self.cfg.rms_eps;
12554 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
12555 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12556 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12557 let n_layers = self.layers.len();
12558 for (il, layer) in self.layers.iter().enumerate() {
12559 let (hq, hdq) = match h_carry.take() {
12560 Some(p) => p,
12561 None => {
12562 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
12563 }
12564 };
12565 let Mixer::Full(fa) = &layer.mixer else {
12566 panic!("gemma4 layer {il} not full-attn")
12567 };
12568 let o =
12569 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
12570 let next_norm = if il + 1 < n_layers {
12571 Some(self.layers[il + 1].attn_norm.float_data())
12572 } else {
12573 None
12574 };
12575 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
12576 x = xn;
12577 h_carry = hn;
12578 }
12579 let mut hn = e.uninit(n_embd)?;
12580 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12581 let mut logits = e.matmul(&self.output, &hn, 1)?;
12582 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
12584 e.inc_seqlen(pos_d)?;
12585 if cap_bucket_max.is_none() {
12586 cache.pos += 1;
12587 }
12588 Ok(())
12589 }
12590
12591 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
12598 let n_embd = self.cfg.n_embd as usize;
12599 let n_vocab = self.output.out_features();
12600 let n_layers = self.layers.len();
12601 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
12602 for il in 0..n_layers {
12603 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
12604 qmax = qmax.max(nh * hd);
12605 kvmax = kvmax.max(nkv * hd);
12606 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
12607 ffmax = ffmax.max(ffn_gate.out_features());
12608 }
12609 }
12610 Ok(G4DcSlots {
12611 x: e.uninit(n_embd)?,
12612 xn: e.uninit(n_embd)?,
12613 cur: e.uninit(n_embd)?,
12614 hq: e.alloc_i8_uninit(n_embd)?,
12615 hd_: e.uninit(n_embd / 32)?,
12616 q0: e.uninit(qmax)?,
12617 k0: e.uninit(kvmax)?,
12618 v0: e.uninit(kvmax)?,
12619 q: e.uninit(qmax)?,
12620 k: e.uninit(kvmax)?,
12621 v: e.uninit(kvmax)?,
12622 attn: e.uninit(qmax)?,
12623 o: e.uninit(n_embd)?,
12624 attn_out: e.uninit(n_embd)?,
12625 zsh: e.uninit(n_embd)?,
12626 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
12629 zd: e.uninit(n_embd.max(qmax) / 32)?,
12630 gate: e.uninit(ffmax)?,
12631 up: e.uninit(ffmax)?,
12632 act: e.uninit(ffmax)?,
12633 actq: e.alloc_i8_uninit(ffmax)?,
12634 actd: e.uninit(ffmax / 32)?,
12635 f0: e.uninit(n_embd)?,
12636 sn: e.uninit(n_embd)?,
12637 hn: e.uninit(n_embd)?,
12638 logits: e.uninit(n_vocab)?,
12639 })
12640 }
12641
12642 fn g4_matvec_m1_into(
12645 &self,
12646 e: &Engine,
12647 w: &crate::model::GpuTensor,
12648 aq: &CudaSlice<i8>,
12649 ad: &CudaSlice<f32>,
12650 y: &mut CudaSlice<f32>,
12651 ) -> Result<(), Box<dyn std::error::Error>> {
12652 use crate::model::GpuTensor;
12653 let (bytes, qtype, row_bytes, scale, rp) = match w {
12654 GpuTensor::Quant {
12655 bytes,
12656 qtype,
12657 row_bytes,
12658 scale,
12659 rp,
12660 ..
12661 } => (bytes, *qtype, *row_bytes, *scale, *rp),
12662 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
12663 };
12664 let (mbytes, mrp) = match w {
12665 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12666 _ => (bytes, rp),
12667 };
12668 e.qmatvec_mmvq_into(
12669 mbytes,
12670 aq,
12671 ad,
12672 1,
12673 w.in_features(),
12674 w.out_features(),
12675 qtype,
12676 row_bytes,
12677 scale,
12678 mrp,
12679 y,
12680 )
12681 }
12682
12683 #[allow(clippy::too_many_arguments)]
12687 pub fn gemma4_decode_step_dc_slotted(
12688 &self,
12689 e: &Engine,
12690 token_d: &CudaSlice<u32>,
12691 pos_d: &mut CudaSlice<i32>,
12692 embd_gpu: &CudaSlice<u8>,
12693 embd_qt: i32,
12694 embd_rb: usize,
12695 cache: &mut Cache,
12696 n_vocab: usize,
12697 cap_bucket_max: Option<(usize, usize)>,
12698 sl: &mut G4DcSlots,
12699 tok_out: &mut CudaSlice<u32>,
12700 ring: Option<(&mut CudaSlice<u32>, usize)>,
12701 ) -> Result<(), Box<dyn std::error::Error>> {
12702 let n_embd = self.cfg.n_embd as usize;
12703 let eps = self.cfg.rms_eps;
12704 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
12705 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
12706 let n_layers = self.layers.len();
12707 let mut has_carry = false;
12708 for il in 0..n_layers {
12709 if !has_carry {
12710 e.rms_norm_q8_1_into(
12711 &sl.x,
12712 self.layers[il].attn_norm.float_data(),
12713 n_embd,
12714 1,
12715 eps,
12716 &mut sl.hq,
12717 &mut sl.hd_,
12718 )?;
12719 }
12720 has_carry = true;
12721 let layer = &self.layers[il];
12722 let Mixer::Full(fa) = &layer.mixer else {
12723 panic!("gemma4 layer {il} not full-attn")
12724 };
12725 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
12726 if !Engine::g4_pnfold_on() {
12729 e.rms_norm(
12730 &sl.o,
12731 layer.post_attn_norm.float_data(),
12732 &mut sl.cur,
12733 n_embd,
12734 1,
12735 eps,
12736 )?;
12737 }
12738 let next_norm = if il + 1 < n_layers {
12739 Some(self.layers[il + 1].attn_norm.float_data())
12740 } else {
12741 None
12742 };
12743 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
12744 std::mem::swap(&mut sl.x, &mut sl.xn);
12745 }
12746 e.rms_norm(
12747 &sl.x,
12748 self.output_norm.float_data(),
12749 &mut sl.hn,
12750 n_embd,
12751 1,
12752 eps,
12753 )?;
12754 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
12755 {
12757 let (zq, zd) = (&sl.zq, &sl.zd);
12758 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
12759 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
12760 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
12761 }
12762 self.gemma4_suppress(e, &mut sl.logits, 1)?;
12763 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
12764 if let Some((ring, base)) = ring {
12765 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
12769 }
12770 e.inc_seqlen(pos_d)?;
12771 if cap_bucket_max.is_none() {
12772 cache.pos += 1;
12773 }
12774 Ok(())
12775 }
12776
12777 #[allow(clippy::too_many_arguments)]
12779 fn gemma4_decode_attn_dc_slotted(
12780 &self,
12781 e: &Engine,
12782 fa: &crate::hybrid::FullAttnLayer,
12783 il: usize,
12784 pos_d: &CudaSlice<i32>,
12785 cache: &mut Cache,
12786 cap_bucket_max: Option<(usize, usize)>,
12787 sl: &mut G4DcSlots,
12788 ) -> Result<(), Box<dyn std::error::Error>> {
12789 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12790 let eps = self.cfg.rms_eps;
12791 let aux = self.gemma4_aux.as_ref().unwrap();
12792 let ones = aux.ones(e);
12793 #[cfg(debug_assertions)]
12794 crate::debug_assert_tensor_stream_device(
12795 ones,
12796 &e.stream(),
12797 "gemma4_decode_attn_dc_slotted.ones",
12798 );
12799 {
12800 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
12801 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
12802 if swa {
12803 if !e.matmul_q4_fused3_into(
12804 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
12805 )? {
12806 if e.matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12810 {
12811 self.g4_matvec_m1_into(e, &fa.wv, hq, hdq, &mut sl.v0)?;
12812 } else {
12813 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
12814 }
12815 }
12816 } else {
12817 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12818 && !e
12819 .matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12820 {
12821 return Err("slotted step: fused2 unavailable".into());
12822 }
12823 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
12824 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
12825 }
12826 }
12827 let ff = if swa {
12830 None
12831 } else {
12832 Some(
12833 aux.rope_freqs(e)
12834 .expect("gemma4 global rope needs rope_freqs.weight"),
12835 )
12836 };
12837 #[cfg(debug_assertions)]
12838 if let Some(ff) = ff {
12839 crate::debug_assert_tensor_stream_device(
12840 ff,
12841 &e.stream(),
12842 "gemma4_decode_attn_dc_slotted.rope_freqs",
12843 );
12844 }
12845 let kvl = cache.kv[il].as_mut().unwrap();
12846 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12847 if crate::Engine::qkv_append_on() {
12848 e.rms_norm_qkv_rope_append_dc(
12850 &sl.q0,
12851 &sl.k0,
12852 &sl.v0,
12853 fa.q_norm.float_data(),
12854 fa.k_norm.float_data(),
12855 ones,
12856 &mut sl.q,
12857 &mut sl.k,
12858 &mut sl.v,
12859 hd,
12860 self.gemma4_rope_dims(il),
12861 nh,
12862 nkv,
12863 pos_d,
12864 nh,
12865 nkv,
12866 base,
12867 1.0,
12868 ff,
12869 eps,
12870 &mut kvl.k,
12871 &mut kvl.v,
12872 &kvl.len_d,
12873 kvl.k_tok_bytes,
12874 kvl.v_tok_bytes,
12875 kv_fp8,
12876 )?;
12877 } else {
12878 e.rms_norm_qkv_rope(
12879 &sl.q0,
12880 &sl.k0,
12881 &sl.v0,
12882 fa.q_norm.float_data(),
12883 fa.k_norm.float_data(),
12884 ones,
12885 &mut sl.q,
12886 &mut sl.k,
12887 &mut sl.v,
12888 hd,
12889 self.gemma4_rope_dims(il),
12890 nh,
12891 nkv,
12892 pos_d,
12893 nh,
12894 nkv,
12895 base,
12896 1.0,
12897 ff,
12898 eps,
12899 )?;
12900 e.append_kv_quantized_dc(
12901 &sl.k,
12902 &sl.v,
12903 &mut kvl.k,
12904 &mut kvl.v,
12905 &kvl.len_d,
12906 kvl.kv_dim_k,
12907 kvl.kv_dim_v,
12908 kvl.k_tok_bytes,
12909 kvl.v_tok_bytes,
12910 kv_fp8,
12911 )?;
12912 }
12913 e.inc_seqlen(&mut kvl.len_d)?;
12914 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
12915 let k_view = e.view_u8(&kvl.k, kvl.k.len());
12916 let v_view = e.view_u8(&kvl.v, kvl.v.len());
12917 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
12918 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12919 let mut fa_q8 = false;
12923 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
12924 e.fa_decode_rows(
12925 &sl.q,
12926 &k_view,
12927 &v_view,
12928 &mut sl.attn,
12929 hd,
12930 nh,
12931 nkv,
12932 b_glob - 1,
12933 1,
12934 scale,
12935 kvl.k_tok_bytes,
12936 kvl.v_tok_bytes,
12937 Some((&kvl.len_d, -1)),
12938 false,
12939 false,
12940 Some((&mut sl.zq, &mut sl.zd)),
12941 )?;
12942 fa_q8 = true;
12943 } else if swa && b_swa > win && hd == 256 && rows_on {
12944 e.fa_decode_rows_w(
12945 &sl.q,
12946 &k_view,
12947 &v_view,
12948 &mut sl.attn,
12949 hd,
12950 nh,
12951 nkv,
12952 &kvl.len_d,
12953 -1,
12954 1,
12955 scale,
12956 win,
12957 kvl.k_tok_bytes,
12958 kvl.v_tok_bytes,
12959 Some((&mut sl.zq, &mut sl.zd)),
12960 )?;
12961 fa_q8 = true;
12962 } else {
12963 let b = if swa { b_swa } else { b_glob };
12964 e.fa_decode_dc(
12965 &sl.q,
12966 &k_view,
12967 &v_view,
12968 &mut sl.attn,
12969 hd,
12970 nh,
12971 nkv,
12972 &kvl.len_d,
12973 b,
12974 scale,
12975 kvl.k_tok_bytes,
12976 kvl.v_tok_bytes,
12977 swa && crate::Engine::wkv_on(),
12978 )?;
12979 }
12980 if !fa_q8 {
12981 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
12982 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
12983 }
12984 {
12985 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
12986 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
12987 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
12988 }
12989 Ok(())
12990 }
12991
12992 fn gemma4_layer_tail_slotted(
12995 &self,
12996 e: &Engine,
12997 layer: &crate::hybrid::HybridLayer,
12998 next_norm: Option<&CudaSlice<f32>>,
12999 sl: &mut G4DcSlots,
13000 ) -> Result<(), Box<dyn std::error::Error>> {
13001 let n_embd = self.cfg.n_embd as usize;
13002 let eps = self.cfg.rms_eps;
13003 let bits = layer.gemma4.as_ref().unwrap();
13004 let crate::hybrid::Ffn::Dense {
13005 ffn_gate,
13006 ffn_up,
13007 ffn_down,
13008 } = &layer.ffn
13009 else {
13010 return Err("slotted tail: dense ffn only".into());
13011 };
13012 let pnfold = Engine::g4_pnfold_on();
13013 if pnfold {
13014 let or = unsafe { &*(&sl.o as *const CudaSlice<f32>) };
13017 let xr = unsafe { &*(&sl.x as *const CudaSlice<f32>) };
13018 e.rms_pre_add_rms_norm_q8z_into(
13019 or,
13020 layer.post_attn_norm.float_data(),
13021 xr,
13022 bits.ffn_norm.float_data(),
13023 &mut sl.attn_out,
13024 &mut sl.zsh,
13025 n_embd,
13026 1,
13027 eps,
13028 &mut sl.zq,
13029 &mut sl.zd,
13030 )?;
13031 } else {
13032 e.add_rms_norm(
13033 &sl.cur,
13034 &sl.x,
13035 bits.ffn_norm.float_data(),
13036 &mut sl.attn_out,
13037 &mut sl.zsh,
13038 n_embd,
13039 1,
13040 eps,
13041 )?;
13042 }
13043 let n_ff = ffn_gate.out_features();
13044 if !pnfold {
13045 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
13046 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13047 }
13048 {
13049 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13050 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13051 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)?
13052 && !e.matmul_nvfp4_fused2_into(
13053 ffn_gate,
13054 ffn_up,
13055 zq,
13056 zd,
13057 &mut sl.gate,
13058 &mut sl.up,
13059 )?
13060 {
13061 return Err("slotted tail: ffn fused2 unavailable".into());
13062 }
13063 }
13064 debug_assert!(e.uses_q8_1_fast(ffn_down));
13065 {
13066 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
13067 let upv = e.view(upr, n_ff);
13068 let up_all = upv.slice(0..n_ff);
13069 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
13070 e.gelu_tanh_mul_q8_1_into(
13071 gr,
13072 &up_all,
13073 &mut sl.act,
13074 n_ff,
13075 1,
13076 &mut sl.actq,
13077 &mut sl.actd,
13078 )?;
13079 }
13080 {
13081 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
13082 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
13083 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
13084 }
13085 if pnfold {
13086 if let Some(w) = next_norm {
13089 let f0r = unsafe { &*(&sl.f0 as *const CudaSlice<f32>) };
13090 let aor = unsafe { &*(&sl.attn_out as *const CudaSlice<f32>) };
13091 e.rms_pre_add_scale_rms_norm_q8_1_into(
13092 f0r,
13093 bits.post_ffw_norm.float_data(),
13094 aor,
13095 bits.layer_scale,
13096 w,
13097 &mut sl.xn,
13098 n_embd,
13099 1,
13100 eps,
13101 &mut sl.hq,
13102 &mut sl.hd_,
13103 )?;
13104 return Ok(());
13105 }
13106 }
13107 e.rms_norm(
13108 &sl.f0,
13109 bits.post_ffw_norm.float_data(),
13110 &mut sl.sn,
13111 n_embd,
13112 1,
13113 eps,
13114 )?;
13115 match next_norm {
13116 Some(w) => {
13117 e.add_scale_rms_norm_q8_1_into(
13118 &sl.sn,
13119 &sl.attn_out,
13120 bits.layer_scale,
13121 w,
13122 &mut sl.xn,
13123 n_embd,
13124 1,
13125 eps,
13126 &mut sl.hq,
13127 &mut sl.hd_,
13128 )?;
13129 }
13130 None => {
13131 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
13132 }
13133 }
13134 Ok(())
13135 }
13136
13137 #[allow(clippy::too_many_arguments)]
13139 fn gemma4_decode_attn_dc(
13140 &self,
13141 e: &Engine,
13142 fa: &crate::hybrid::FullAttnLayer,
13143 il: usize,
13144 hq: &CudaSlice<i8>,
13145 hdq: &CudaSlice<f32>,
13146 pos_d: &CudaSlice<i32>,
13147 cache: &mut Cache,
13148 cap_bucket_max: Option<(usize, usize)>,
13149 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13150 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13151 let eps = self.cfg.rms_eps;
13152 let aux = self.gemma4_aux.as_ref().unwrap();
13153 let ones = aux.ones(e);
13154 #[cfg(debug_assertions)]
13155 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
13156 let (q0, k0, v0) = if swa {
13157 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
13158 Some(t3) => t3,
13159 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13161 Some((q0, k0)) => {
13162 let h0 = e.zeros(0)?;
13163 let v0 = e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?;
13164 (q0, k0, v0)
13165 }
13166 None => {
13167 let h0 = e.zeros(0)?;
13168 (
13169 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13170 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13171 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
13172 )
13173 }
13174 },
13175 }
13176 } else {
13177 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
13178 Some(p) => p,
13179 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13180 Some(p) => p,
13181 None => {
13182 let h0 = e.zeros(0)?;
13183 (
13184 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13185 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13186 )
13187 }
13188 },
13189 };
13190 let v0 = e.clone_dtod(&k0)?;
13191 (q0, k0, v0)
13192 };
13193 let mut q = e.uninit(nh * hd)?;
13194 let mut k = e.uninit(nkv * hd)?;
13195 let mut v = e.uninit(nkv * hd)?;
13196 let ff = if swa {
13198 None
13199 } else {
13200 Some(
13201 aux.rope_freqs(e)
13202 .expect("gemma4 global rope needs rope_freqs.weight"),
13203 )
13204 };
13205 #[cfg(debug_assertions)]
13206 if let Some(ff) = ff {
13207 crate::debug_assert_tensor_stream_device(
13208 ff,
13209 &e.stream(),
13210 "gemma4_decode_attn_dc.rope_freqs",
13211 );
13212 }
13213 let kvl = cache.kv[il].as_mut().unwrap();
13214 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13215 if crate::Engine::qkv_append_on() {
13216 e.rms_norm_qkv_rope_append_dc(
13218 &q0,
13219 &k0,
13220 &v0,
13221 fa.q_norm.float_data(),
13222 fa.k_norm.float_data(),
13223 ones,
13224 &mut q,
13225 &mut k,
13226 &mut v,
13227 hd,
13228 self.gemma4_rope_dims(il),
13229 nh,
13230 nkv,
13231 pos_d,
13232 nh,
13233 nkv,
13234 base,
13235 1.0,
13236 ff,
13237 eps,
13238 &mut kvl.k,
13239 &mut kvl.v,
13240 &kvl.len_d,
13241 kvl.k_tok_bytes,
13242 kvl.v_tok_bytes,
13243 kv_fp8,
13244 )?;
13245 } else {
13246 e.rms_norm_qkv_rope(
13247 &q0,
13248 &k0,
13249 &v0,
13250 fa.q_norm.float_data(),
13251 fa.k_norm.float_data(),
13252 ones,
13253 &mut q,
13254 &mut k,
13255 &mut v,
13256 hd,
13257 self.gemma4_rope_dims(il),
13258 nh,
13259 nkv,
13260 pos_d,
13261 nh,
13262 nkv,
13263 base,
13264 1.0,
13265 ff,
13266 eps,
13267 )?;
13268 e.append_kv_quantized_dc(
13269 &k,
13270 &v,
13271 &mut kvl.k,
13272 &mut kvl.v,
13273 &kvl.len_d,
13274 kvl.kv_dim_k,
13275 kvl.kv_dim_v,
13276 kvl.k_tok_bytes,
13277 kvl.v_tok_bytes,
13278 kv_fp8,
13279 )?;
13280 }
13281 e.inc_seqlen(&mut kvl.len_d)?;
13282 let mut attn = e.uninit(nh * hd)?;
13283 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13286 match cap_bucket_max {
13291 None => {
13292 kvl.len += 1;
13296 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13297 if !swa
13298 && hd == 512
13299 && kvl.len >= crate::fa512_min_tkv()
13300 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13301 {
13302 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13305 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13306 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13307 e.fa_decode_rows(
13308 &q,
13309 &kp,
13310 &vp,
13311 &mut attn,
13312 hd,
13313 nh,
13314 nkv,
13315 kvl.len - 1,
13316 1,
13317 scale,
13318 kvl.k_tok_bytes,
13319 kvl.v_tok_bytes,
13320 Some((&kvl.len_d, -1)),
13321 false,
13322 false,
13323 Some((&mut aq8, &mut ad8)),
13324 )?;
13325 fa_q8 = Some((aq8, ad8));
13326 } else if swa
13327 && kvl.len > win
13328 && hd == 256
13329 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13330 {
13331 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13333 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13334 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13335 e.fa_decode_rows_w(
13336 &q,
13337 &kp,
13338 &vp,
13339 &mut attn,
13340 hd,
13341 nh,
13342 nkv,
13343 &kvl.len_d,
13344 -1,
13345 1,
13346 scale,
13347 win,
13348 kvl.k_tok_bytes,
13349 kvl.v_tok_bytes,
13350 Some((&mut aq8, &mut ad8)),
13351 )?;
13352 fa_q8 = Some((aq8, ad8));
13353 } else {
13354 let (off_tok, t_kv) = if swa && kvl.len > win {
13355 (kvl.len - win, win)
13356 } else {
13357 (0, kvl.len)
13358 };
13359 let k_view = e.view_u8_range(
13360 &kvl.k,
13361 off_tok * kvl.k_tok_bytes,
13362 (off_tok + t_kv) * kvl.k_tok_bytes,
13363 );
13364 let v_view = e.view_u8_range(
13365 &kvl.v,
13366 off_tok * kvl.v_tok_bytes,
13367 (off_tok + t_kv) * kvl.v_tok_bytes,
13368 );
13369 e.fa_decode_kvmod(
13370 &q,
13371 &k_view,
13372 &v_view,
13373 &mut attn,
13374 hd,
13375 nh,
13376 nkv,
13377 t_kv,
13378 scale,
13379 kvl.k_tok_bytes,
13380 kvl.v_tok_bytes,
13381 swa && crate::Engine::wkv_on(),
13382 )?;
13383 }
13384 }
13385 Some((b_swa, b_glob)) => {
13386 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13392 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13393 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13394 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13395 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13396 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13397 e.fa_decode_rows(
13398 &q,
13399 &k_view,
13400 &v_view,
13401 &mut attn,
13402 hd,
13403 nh,
13404 nkv,
13405 b_glob - 1,
13406 1,
13407 scale,
13408 kvl.k_tok_bytes,
13409 kvl.v_tok_bytes,
13410 Some((&kvl.len_d, -1)),
13411 false,
13412 false,
13413 Some((&mut aq8, &mut ad8)),
13414 )?;
13415 fa_q8 = Some((aq8, ad8));
13416 } else if swa && b_swa > win && hd == 256 && rows_on {
13417 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13418 e.fa_decode_rows_w(
13419 &q,
13420 &k_view,
13421 &v_view,
13422 &mut attn,
13423 hd,
13424 nh,
13425 nkv,
13426 &kvl.len_d,
13427 -1,
13428 1,
13429 scale,
13430 win,
13431 kvl.k_tok_bytes,
13432 kvl.v_tok_bytes,
13433 Some((&mut aq8, &mut ad8)),
13434 )?;
13435 fa_q8 = Some((aq8, ad8));
13436 } else {
13437 let b = if swa { b_swa } else { b_glob };
13438 e.fa_decode_dc(
13439 &q,
13440 &k_view,
13441 &v_view,
13442 &mut attn,
13443 hd,
13444 nh,
13445 nkv,
13446 &kvl.len_d,
13447 b,
13448 scale,
13449 kvl.k_tok_bytes,
13450 kvl.v_tok_bytes,
13451 swa && crate::Engine::wkv_on(),
13452 )?;
13453 }
13454 }
13455 }
13456 if let Some((aq8, ad8)) = fa_q8 {
13459 let mut y = e.uninit(fa.wo.out_features())?;
13460 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
13461 return Ok(y);
13462 }
13463 Ok(e.matmul(&fa.wo, &attn, 1)?)
13464 }
13465
13466 pub fn gemma4_generate_graph(
13471 &self,
13472 e: &Engine,
13473 prompt_pos: usize,
13474 first_token: u32,
13475 cache: &mut Cache,
13476 max_new: usize,
13477 eos: &[u32],
13478 mut on_token: impl FnMut(u32) -> bool,
13479 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
13480 if self.is_gemma4_e4b() {
13481 return Err(
13482 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
13483 .into(),
13484 );
13485 }
13486 use crate::decode::StopReason;
13487 let n_vocab = self.output.out_features();
13488 let n_embd = self.cfg.n_embd as usize;
13489 let embd_gpu = self
13490 .embd_gpu
13491 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13492 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13493 for kvl in cache.kv.iter_mut().flatten() {
13494 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
13495 }
13496 let mut token_d = e.stream().clone_htod(&[first_token])?;
13497 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
13498 let g4 = self.cfg.gemma4.as_ref().unwrap();
13499 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
13500 let nkv_s = g4
13502 .head_count_kv
13503 .iter()
13504 .zip(g4.swa_pattern.iter())
13505 .find(|p| *p.1)
13506 .map(|p| *p.0 as usize)
13507 .unwrap_or(8);
13508 let nkv_g = g4
13509 .head_count_kv
13510 .iter()
13511 .zip(g4.swa_pattern.iter())
13512 .find(|p| !*p.1)
13513 .map(|p| *p.0 as usize)
13514 .unwrap_or(2);
13515 let mut graphs: std::collections::HashMap<
13516 ((bool, usize), (bool, usize), bool, bool),
13517 (
13518 cudarc::driver::CudaGraph,
13519 Vec<Box<dyn std::any::Any + Send>>,
13520 ),
13521 > = Default::default();
13522 let mut slots = self.g4_dc_slots(e)?;
13525 const RING: usize = 64;
13528 const DRAIN: usize = 1;
13534 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
13535 let ring_base = prompt_pos;
13536 let mut out = Vec::with_capacity(max_new);
13537 let mut reason = StopReason::MaxNew;
13538 let mut next = first_token;
13539 let mut captures = 0usize;
13540 for _ in 0..max_new {
13541 out.push(next);
13542 if eos.contains(&next) {
13543 reason = StopReason::Eos;
13544 break;
13545 }
13546 if !on_token(next) {
13547 reason = StopReason::Callback;
13548 break;
13549 }
13550 let t_kv = cache.pos + 1;
13551 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13559 let f512 = crate::fa512_min_tkv();
13560 let key_s = if t_kv > win {
13561 (true, usize::MAX)
13562 } else {
13563 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
13564 };
13565 let (key_g, rung_end) = if t_kv >= f512 {
13566 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
13569 ((true, end), end)
13570 } else {
13571 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
13572 };
13573 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
13574 if !graphs.contains_key(&key) {
13575 let bucket_max = (t_kv, rung_end);
13576 let snap = cache.snapshot(e)?;
13578 let pos_save = e.dtoh_i32_one(&pos_d)?;
13579 let len_save: Vec<Option<i32>> = cache
13580 .kv
13581 .iter()
13582 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
13583 .collect();
13584 let tok_save = e.dtoh_u32_one(&token_d)?;
13585 let graph = {
13590 let tok_ref = &mut token_d;
13591 let pos_ref = &mut pos_d;
13592 let cache_ref = &mut *cache;
13593 let slots_ref = &mut slots;
13594 let ring_ref = &mut ring;
13595 e.capture_graph_retained_flags(
13596 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
13597 |e| {
13598 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
13600 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
13601 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
13602 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
13603 cache_ref, n_vocab, Some(bucket_max),
13604 sl, tok_ref, Some((rg, ring_base)))
13605 })?
13606 };
13607 cache.rollback(e, &snap, 0)?;
13608 e.set_i32_one(&mut pos_d, pos_save)?;
13609 for (il, ls) in len_save.iter().enumerate() {
13610 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
13611 e.set_i32_one(&mut kvl.len_d, *v)?;
13612 }
13613 }
13614 e.set_u32_one(&mut token_d, tok_save)?;
13615 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
13616 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
13617 eprintln!("[graph-census] {c:?}");
13618 }
13619 }
13620 graphs.insert(key, graph);
13621 captures += 1;
13622 }
13623 let mut chunk = 1usize;
13628 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
13629 .ok()
13630 .and_then(|v| v.parse().ok())
13631 .unwrap_or(DRAIN);
13632 while chunk < drain_cap && out.len() + chunk < max_new {
13633 let t_next = cache.pos + 1 + chunk;
13634 let key_s2 = if t_next > win {
13635 (true, usize::MAX)
13636 } else {
13637 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
13638 };
13639 let key_g2 = if t_next >= f512 {
13640 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
13641 } else {
13642 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
13643 };
13644 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
13645 break;
13646 }
13647 chunk += 1;
13648 }
13649 let g = &graphs.get(&key).unwrap().0;
13650 for _ in 0..chunk {
13651 g.launch()?;
13652 }
13653 e.stream().synchronize()?;
13654 let ringh = e.dtoh_u32(&ring)?;
13655 for j in 0..chunk {
13656 let pos_j = cache.pos + j;
13657 let tok_j = ringh[(pos_j - ring_base) % RING];
13658 cache.pos += 0; if j + 1 == chunk {
13660 next = tok_j;
13661 } else {
13662 out.push(tok_j);
13663 if eos.contains(&tok_j) || !on_token(tok_j) {
13664 reason = if eos.contains(&tok_j) {
13665 StopReason::Eos
13666 } else {
13667 StopReason::Callback
13668 };
13669 let keep = cache.pos + j + 1;
13671 e.set_i32_one(&mut pos_d, keep as i32)?;
13672 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13673 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
13674 kvl.len = keep;
13675 }
13676 cache.pos = keep;
13677 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13678 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13679 }
13680 return Ok((out, reason));
13681 }
13682 }
13683 }
13684 cache.pos += chunk;
13685 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13686 kvl.len += chunk;
13687 }
13688 }
13689 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13690 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13691 }
13692 Ok((out, reason))
13693 }
13694
13695 pub(crate) fn gemma4_decode_step_t(
13701 &self,
13702 e: &Engine,
13703 tokens: &[u32],
13704 pos0: usize,
13705 cache: &mut Cache,
13706 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13707 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
13708 }
13709
13710 pub(crate) fn gemma4_decode_step_t_am(
13714 &self,
13715 e: &Engine,
13716 tokens: &[u32],
13717 pos0: usize,
13718 cache: &mut Cache,
13719 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13720 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13721 let t = tokens.len();
13722 let n_vocab = self.output.out_features();
13723 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
13724 for i in 0..t {
13725 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
13726 }
13727 Ok((e.dtoh_u32(&toks)?, hn))
13728 }
13729
13730 pub(crate) fn gemma4_decode_step_t_am_dev(
13733 &self,
13734 e: &Engine,
13735 tok_d: &CudaSlice<u32>,
13736 t: usize,
13737 pos0: usize,
13738 cache: &mut Cache,
13739 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13740 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
13741 let n_vocab = self.output.out_features();
13742 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13743 for i in 0..t {
13744 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13745 }
13746 Ok((vam, hn))
13747 }
13748
13749 pub(crate) fn gemma4_decode_step_t_h(
13752 &self,
13753 e: &Engine,
13754 tokens: &[u32],
13755 pos0: usize,
13756 cache: &mut Cache,
13757 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13758 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13759 let t = tokens.len();
13760 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
13761 e.softcap(&mut ld, cap, t * self.output.out_features())?;
13762 Ok((e.dtoh(&ld)?, hn))
13763 }
13764
13765 pub(crate) fn verify_stream_scratch(
13768 &self,
13769 e: &Engine,
13770 cap: usize,
13771 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
13772 Ok(VerifyStreamScratch {
13773 pos_d: e.htod_i32(&vec![0i32; cap])?,
13774 row_ctrs: (0..cap)
13775 .map(|_| e.htod_i32(&[0]))
13776 .collect::<Result<_, _>>()?,
13777 })
13778 }
13779
13780 pub(crate) fn gemma4_verify_t_am_stream(
13788 &self,
13789 e: &Engine,
13790 tok_d: &CudaSlice<u32>,
13791 t: usize,
13792 ctr: &CudaSlice<i32>,
13793 hint: usize,
13794 cache: &mut Cache,
13795 scr: &mut VerifyStreamScratch,
13796 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13797 let n_embd = self.cfg.n_embd as usize;
13798 let eps = self.cfg.rms_eps;
13799 assert!(t <= scr.row_ctrs.len() && t <= 64);
13800 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
13801 for i in 0..t {
13802 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
13803 }
13804 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
13805 let embd_gpu = self
13806 .embd_gpu
13807 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13808 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13809 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
13810 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13811 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13812 let n_layers = self.layers.len();
13813 for (il, layer) in self.layers.iter().enumerate() {
13814 let (hq, hdq) = match h_carry.take() {
13815 Some(p) => p,
13816 None => {
13817 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
13818 }
13819 };
13820 let Mixer::Full(fa) = &layer.mixer else {
13821 panic!("gemma4 layer {il} not full-attn")
13822 };
13823 let o = self
13824 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
13825 let next_norm = if il + 1 < n_layers {
13826 Some(self.layers[il + 1].attn_norm.float_data())
13827 } else {
13828 None
13829 };
13830 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
13831 x = xn;
13832 h_carry = hn;
13833 self.dflash_tap(e, cache, il, &x, t)?;
13834 }
13835 let mut hn = e.uninit(t * n_embd)?;
13836 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13837 let ld = e.matmul(&self.output, &hn, t)?;
13838 let n_vocab = self.output.out_features();
13839 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13840 for i in 0..t {
13841 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13842 }
13843 Ok((vam, hn))
13844 }
13845
13846 pub(crate) fn dflash_tap(
13853 &self,
13854 e: &Engine,
13855 cache: &mut Cache,
13856 il: usize,
13857 x: &CudaSlice<f32>,
13858 t: usize,
13859 ) -> Result<(), Box<dyn std::error::Error>> {
13860 let Some(taps) = cache.dflash_taps.as_mut() else {
13861 return Ok(());
13862 };
13863 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
13864 return Ok(());
13865 };
13866 let h = taps.hidden;
13867 let n_taps = taps.layer_ids.len();
13868 let base = taps.base;
13869 debug_assert!(
13870 base + t <= taps.t,
13871 "tap window {base}+{t} exceeds sink {}",
13872 taps.t
13873 );
13874 let xv = e.view(x, t * h);
13875 for r in 0..t {
13876 let row = xv.slice(r * h..(r + 1) * h);
13877 e.copy_view_into(&mut taps.buf, (base + r) * n_taps * h + slot * h, &row, h)?;
13878 }
13879 Ok(())
13880 }
13881
13882 fn gemma4_verify_trunk(
13883 &self,
13884 e: &Engine,
13885 tokens: &[u32],
13886 pos0: usize,
13887 cache: &mut Cache,
13888 tok_dev: Option<&CudaSlice<u32>>,
13889 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13890 let n_embd = self.cfg.n_embd as usize;
13891 let eps = self.cfg.rms_eps;
13892 let t = tokens.len();
13893 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
13894 let pos_d = e.htod_i32(&pos)?;
13895 let mut x = match tok_dev {
13896 Some(td) => {
13897 let embd_gpu = self
13898 .embd_gpu
13899 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13900 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13901 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
13902 }
13903 None => e.htod(&self.embd.gather(n_embd, tokens))?,
13904 };
13905 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13906 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13907 let n_layers = self.layers.len();
13908 for (il, layer) in self.layers.iter().enumerate() {
13909 let (hq, hdq) = match h_carry.take() {
13910 Some(p) => p,
13911 None => {
13912 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
13913 }
13914 };
13915 let Mixer::Full(fa) = &layer.mixer else {
13916 panic!("gemma4 layer {il} not full-attn")
13917 };
13918 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
13919 let next_norm = if il + 1 < n_layers {
13920 Some(self.layers[il + 1].attn_norm.float_data())
13921 } else {
13922 None
13923 };
13924 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
13925 x = xn;
13926 h_carry = hn;
13927 self.dflash_tap(e, cache, il, &x, t)?;
13928 }
13929 let mut hn = e.uninit(t * n_embd)?;
13930 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13931 let mut ld = e.matmul(&self.output, &hn, t)?;
13932 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
13934 Ok((ld, hn))
13935 }
13936
13937 #[allow(clippy::too_many_arguments)]
13945 fn gemma4_verify_attn_stream(
13946 &self,
13947 e: &Engine,
13948 fa: &crate::hybrid::FullAttnLayer,
13949 il: usize,
13950 hq: &CudaSlice<i8>,
13951 hdq: &CudaSlice<f32>,
13952 pos_d: &CudaSlice<i32>,
13953 t: usize,
13954 cache: &mut Cache,
13955 hint: usize,
13956 row_ctrs: &[CudaSlice<i32>],
13957 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13958 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13959 let eps = self.cfg.rms_eps;
13960 let aux = self.gemma4_aux.as_ref().unwrap();
13961 let ones = aux.ones(e);
13962 #[cfg(debug_assertions)]
13963 crate::debug_assert_tensor_stream_device(
13964 ones,
13965 &e.stream(),
13966 "gemma4_verify_attn_stream.ones",
13967 );
13968 let h0 = e.zeros(0)?;
13969 let h = &h0;
13970 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13973 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
13974 let fused_qkv = if f2b {
13975 if swa {
13976 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
13977 .map(|(a, b, c)| (a, b, Some(c)))
13978 } else {
13979 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
13980 .map(|(a, b)| (a, b, None))
13981 }
13982 } else {
13983 None
13984 };
13985 let (q0, k0, v0) = match fused_qkv {
13986 Some((a, b, cv)) => {
13987 let v = match cv {
13988 Some(c) => c,
13989 None => e.clone_dtod(&b)?,
13990 };
13991 (a, b, v)
13992 }
13993 None => {
13994 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
13995 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
13996 let v0 = if swa {
13997 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
13998 } else {
13999 e.clone_dtod(&k0)?
14000 };
14001 (q0, k0, v0)
14002 }
14003 };
14004 let mut q = e.uninit(t * nh * hd)?;
14005 let mut k = e.uninit(t * nkv * hd)?;
14006 let mut v = e.uninit(t * nkv * hd)?;
14007 let ff = if swa {
14010 None
14011 } else {
14012 Some(
14013 aux.rope_freqs(e)
14014 .expect("gemma4 global rope needs rope_freqs.weight"),
14015 )
14016 };
14017 #[cfg(debug_assertions)]
14018 if let Some(ff) = ff {
14019 crate::debug_assert_tensor_stream_device(
14020 ff,
14021 &e.stream(),
14022 "gemma4_verify_attn_stream.rope_freqs",
14023 );
14024 }
14025 e.rms_norm_qkv_rope(
14026 &q0,
14027 &k0,
14028 &v0,
14029 fa.q_norm.float_data(),
14030 fa.k_norm.float_data(),
14031 ones,
14032 &mut q,
14033 &mut k,
14034 &mut v,
14035 hd,
14036 self.gemma4_rope_dims(il),
14037 nh * t,
14038 nkv * t,
14039 pos_d,
14040 nh,
14041 nkv,
14042 base,
14043 1.0,
14044 ff,
14045 eps,
14046 )?;
14047 let kvl = cache.kv[il].as_mut().unwrap();
14048 e.append_kv_quantized_rows_dc(
14050 &k,
14051 &v,
14052 &mut kvl.k,
14053 &mut kvl.v,
14054 &kvl.len_d,
14055 t,
14056 kvl.kv_dim_k,
14057 kvl.kv_dim_v,
14058 kvl.k_tok_bytes,
14059 kvl.v_tok_bytes,
14060 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14061 )?;
14062 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14065 let mut attn = e.uninit(t * nh * hd)?;
14066 let k_view = e.view_u8(&kvl.k, kvl.k.len());
14067 let v_view = e.view_u8(&kvl.v, kvl.v.len());
14068 if swa && hint + 1 >= win {
14071 e.fa_decode_rows_w(
14074 &q,
14075 &k_view,
14076 &v_view,
14077 &mut attn,
14078 hd,
14079 nh,
14080 nkv,
14081 &kvl.len_d,
14082 0,
14083 t,
14084 scale,
14085 win,
14086 kvl.k_tok_bytes,
14087 kvl.v_tok_bytes,
14088 None,
14089 )?;
14090 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
14091 let bucket = (hint + t + 2)
14104 .next_power_of_two()
14105 .min(crate::fa512_min_tkv().saturating_sub(1));
14106 let qv = e.view(&q, t * nh * hd);
14107 for i in 0..t {
14108 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
14109 let mut q_one = e.uninit(nh * hd)?;
14110 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14111 let mut a_one = e.uninit(nh * hd)?;
14112 e.fa_decode_dc(
14113 &q_one,
14114 &k_view,
14115 &v_view,
14116 &mut a_one,
14117 hd,
14118 nh,
14119 nkv,
14120 &row_ctrs[i],
14121 bucket,
14122 scale,
14123 kvl.k_tok_bytes,
14124 kvl.v_tok_bytes,
14125 false,
14126 )?;
14127 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14128 }
14129 } else if hd == 512 {
14130 e.fa_decode_rows(
14133 &q,
14134 &k_view,
14135 &v_view,
14136 &mut attn,
14137 hd,
14138 nh,
14139 nkv,
14140 hint,
14141 t,
14142 scale,
14143 kvl.k_tok_bytes,
14144 kvl.v_tok_bytes,
14145 Some((&kvl.len_d, 0)),
14146 false,
14147 false,
14148 None,
14149 )?;
14150 } else {
14151 e.fa_decode_rows_dc(
14153 &q,
14154 &k_view,
14155 &v_view,
14156 &mut attn,
14157 hd,
14158 nh,
14159 nkv,
14160 &kvl.len_d,
14161 hint + t,
14162 t,
14163 scale,
14164 kvl.k_tok_bytes,
14165 kvl.v_tok_bytes,
14166 0,
14167 swa && crate::Engine::wkv_on(),
14168 )?;
14169 }
14170 Ok(e.matmul(&fa.wo, &attn, t)?)
14171 }
14172
14173 fn gemma4_verify_attn(
14174 &self,
14175 e: &Engine,
14176 fa: &crate::hybrid::FullAttnLayer,
14177 il: usize,
14178 hq: &CudaSlice<i8>,
14179 hdq: &CudaSlice<f32>,
14180 pos_d: &CudaSlice<i32>,
14181 t: usize,
14182 cache: &mut Cache,
14183 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14184 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14185 let eps = self.cfg.rms_eps;
14186 let aux = self.gemma4_aux.as_ref().unwrap();
14187 let ones = aux.ones(e);
14188 #[cfg(debug_assertions)]
14189 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
14190 let n_embd = self.cfg.n_embd as usize;
14191 let _ = n_embd;
14192
14193 let h0 = e.zeros(0)?;
14194 let h = &h0;
14195 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14198 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14199 let fused_qkv = if f2b {
14200 if swa {
14201 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14202 .map(|(a, b, c)| (a, b, Some(c)))
14203 } else {
14204 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14205 .map(|(a, b)| (a, b, None))
14206 }
14207 } else {
14208 None
14209 };
14210 let (q0, k0, v0) = match fused_qkv {
14211 Some((a, b, cv)) => {
14212 let v = match cv {
14213 Some(c) => c,
14214 None => e.clone_dtod(&b)?,
14215 };
14216 (a, b, v)
14217 }
14218 None => {
14219 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14220 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14221 let v0 = if swa {
14222 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14223 } else {
14224 e.clone_dtod(&k0)?
14225 };
14226 (q0, k0, v0)
14227 }
14228 };
14229 let mut q = e.uninit(t * nh * hd)?;
14230 let mut k = e.uninit(t * nkv * hd)?;
14231 let mut v = e.uninit(t * nkv * hd)?;
14232 let ff = if swa {
14235 None
14236 } else {
14237 Some(
14238 aux.rope_freqs(e)
14239 .expect("gemma4 global rope needs rope_freqs.weight"),
14240 )
14241 };
14242 #[cfg(debug_assertions)]
14243 if let Some(ff) = ff {
14244 crate::debug_assert_tensor_stream_device(
14245 ff,
14246 &e.stream(),
14247 "gemma4_verify_attn.rope_freqs",
14248 );
14249 }
14250 e.rms_norm_qkv_rope(
14251 &q0,
14252 &k0,
14253 &v0,
14254 fa.q_norm.float_data(),
14255 fa.k_norm.float_data(),
14256 ones,
14257 &mut q,
14258 &mut k,
14259 &mut v,
14260 hd,
14261 self.gemma4_rope_dims(il),
14262 nh * t,
14263 nkv * t,
14264 pos_d,
14265 nh,
14266 nkv,
14267 base,
14268 1.0,
14269 ff,
14270 eps,
14271 )?;
14272 let kvl = cache.kv[il].as_mut().unwrap();
14273 let base_len = kvl.len;
14274 e.append_kv_quantized_rows(
14275 &k,
14276 &v,
14277 &mut kvl.k,
14278 &mut kvl.v,
14279 base_len,
14280 t,
14281 kvl.kv_dim_k,
14282 kvl.kv_dim_v,
14283 kvl.k_tok_bytes,
14284 kvl.v_tok_bytes,
14285 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14286 )?;
14287 kvl.len += t;
14288 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14289 let mut attn = e.uninit(t * nh * hd)?;
14290 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
14293 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
14296 if rows_ok && (!swa || base_len + t <= win) {
14297 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14298 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14299 if hd == 512 {
14300 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14302 e.fa_decode_rows(
14303 &q,
14304 &k_view,
14305 &v_view,
14306 &mut attn,
14307 hd,
14308 nh,
14309 nkv,
14310 base_len,
14311 t,
14312 scale,
14313 kvl.k_tok_bytes,
14314 kvl.v_tok_bytes,
14315 Some((&kvl.len_d, 0)),
14316 false,
14317 swa && crate::Engine::wkv_on(),
14318 None,
14319 )?;
14320 } else {
14321 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14325 e.fa_decode_rows_dc(
14326 &q,
14327 &k_view,
14328 &v_view,
14329 &mut attn,
14330 hd,
14331 nh,
14332 nkv,
14333 &kvl.len_d,
14334 base_len + t,
14335 t,
14336 scale,
14337 kvl.k_tok_bytes,
14338 kvl.v_tok_bytes,
14339 0,
14340 swa && crate::Engine::wkv_on(),
14341 )?;
14342 }
14343 return Ok(e.matmul(&fa.wo, &attn, t)?);
14344 }
14345 if hd == 256
14353 && swa
14354 && base_len + 1 >= win
14355 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14356 {
14357 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14358 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14359 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14360 e.fa_decode_rows_w(
14361 &q,
14362 &k_view,
14363 &v_view,
14364 &mut attn,
14365 hd,
14366 nh,
14367 nkv,
14368 &kvl.len_d,
14369 0,
14370 t,
14371 scale,
14372 win,
14373 kvl.k_tok_bytes,
14374 kvl.v_tok_bytes,
14375 None,
14376 )?;
14377 return Ok(e.matmul(&fa.wo, &attn, t)?);
14378 }
14379 for i in 0..t {
14380 let avail = base_len + i + 1;
14381 let (off_tok, t_kv) = if swa && avail > win {
14382 (avail - win, win)
14383 } else {
14384 (0, avail)
14385 };
14386 let k_view = e.view_u8_range(
14387 &kvl.k,
14388 off_tok * kvl.k_tok_bytes,
14389 (off_tok + t_kv) * kvl.k_tok_bytes,
14390 );
14391 let v_view = e.view_u8_range(
14392 &kvl.v,
14393 off_tok * kvl.v_tok_bytes,
14394 (off_tok + t_kv) * kvl.v_tok_bytes,
14395 );
14396 let qi = e.view(&q, t * nh * hd);
14397 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
14398 let mut q_one = e.uninit(nh * hd)?;
14399 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14400 let mut a_one = e.uninit(nh * hd)?;
14401 if swa
14405 && avail > win
14406 && hd == 256
14407 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14408 {
14409 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14410 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14411 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14412 e.fa_decode_rows_w(
14413 &q_one,
14414 &kp,
14415 &vp,
14416 &mut a_one,
14417 hd,
14418 nh,
14419 nkv,
14420 &kvl.len_d,
14421 0,
14422 1,
14423 scale,
14424 win,
14425 kvl.k_tok_bytes,
14426 kvl.v_tok_bytes,
14427 None,
14428 )?;
14429 } else if !swa
14430 && hd == 512
14431 && avail >= crate::fa512_min_tkv()
14432 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14433 {
14434 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14435 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14436 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14437 e.fa_decode_rows(
14438 &q_one,
14439 &kp,
14440 &vp,
14441 &mut a_one,
14442 hd,
14443 nh,
14444 nkv,
14445 avail - 1,
14446 1,
14447 scale,
14448 kvl.k_tok_bytes,
14449 kvl.v_tok_bytes,
14450 Some((&kvl.len_d, 0)),
14451 false,
14452 false,
14453 None,
14454 )?;
14455 } else {
14456 e.fa_decode_kvmod(
14457 &q_one,
14458 &k_view,
14459 &v_view,
14460 &mut a_one,
14461 hd,
14462 nh,
14463 nkv,
14464 t_kv,
14465 scale,
14466 kvl.k_tok_bytes,
14467 kvl.v_tok_bytes,
14468 swa && crate::Engine::wkv_on(),
14469 )?;
14470 }
14471 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14472 }
14473 Ok(e.matmul(&fa.wo, &attn, t)?)
14474 }
14475
14476 pub(crate) fn gemma4_decode_step_h(
14479 &self,
14480 e: &Engine,
14481 token: u32,
14482 cache: &mut Cache,
14483 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14484 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
14489 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
14490 }
14491 if crate::pp::pp_cuts(self.layers.len()).is_some() {
14492 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
14493 }
14494 let n_embd = self.cfg.n_embd as usize;
14495 let eps = self.cfg.rms_eps;
14496 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14497 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14498 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14499 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14502 let n_layers = self.layers.len();
14503 for (il, layer) in self.layers.iter().enumerate() {
14504 let (hq, hdq) = match h_carry.take() {
14505 Some(p) => p,
14506 None => {
14507 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
14508 }
14509 };
14510 let Mixer::Full(fa) = &layer.mixer else {
14511 panic!("gemma4 layer {il} not full-attn")
14512 };
14513 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
14514 let next_norm = if il + 1 < n_layers {
14515 Some(self.layers[il + 1].attn_norm.float_data())
14516 } else {
14517 None
14518 };
14519 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14520 x = xn;
14521 h_carry = hn;
14522 }
14523 let mut hn = e.uninit(n_embd)?;
14524 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14525 let h_seed = e.clone_dtod(&x)?;
14526 let mut ld = e.matmul(&self.output, &hn, 1)?;
14527 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14528 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
14530 let logits = e.dtoh(&ld)?;
14531 cache.pos += 1;
14532 Ok((logits, h_seed))
14533 }
14534
14535 fn gemma4_decode_layers(
14543 &self,
14544 e: &Engine,
14545 mut x: CudaSlice<f32>,
14546 lo: usize,
14547 hi: usize,
14548 pos_d: &CudaSlice<i32>,
14549 cache: &mut Cache,
14550 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14551 let n_embd = self.cfg.n_embd as usize;
14552 let eps = self.cfg.rms_eps;
14553 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14554 for il in lo..hi {
14555 let layer = &self.layers[il];
14556 let (hq, hdq) = match h_carry.take() {
14557 Some(p) => p,
14558 None => {
14560 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
14561 }
14562 };
14563 let Mixer::Full(fa) = &layer.mixer else {
14564 panic!("gemma4 layer {il} not full-attn")
14565 };
14566 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
14567 let next_norm = if il + 1 < hi {
14568 Some(self.layers[il + 1].attn_norm.float_data())
14569 } else {
14570 None
14571 };
14572 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14573 x = xn;
14574 h_carry = hn;
14575 }
14576 Ok(x)
14577 }
14578
14579 fn gemma4_decode_step_h_pp2(
14587 &self,
14588 e: &Engine,
14589 token: u32,
14590 cache: &mut Cache,
14591 split: usize,
14592 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14593 if crate::pp::pp2_streams_off() {
14594 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
14595 }
14596 let rt = crate::pp::Pp2Rt::get(e)?;
14597 let e0 = rt.engine(0, e);
14598 let e1 = rt.engine(1, e);
14599 let n_embd = self.cfg.n_embd as usize;
14600 let eps = self.cfg.rms_eps;
14601 let pos = cache.pos as i32;
14602
14603 let slot = {
14605 let _st0 = rt.enter(0);
14606 let pos_d = e0.htod_i32(&[pos])?;
14607 #[cfg(debug_assertions)]
14608 crate::debug_assert_tensor_stream_device(
14609 &pos_d,
14610 &e0.stream(),
14611 "gemma4_decode_step_h_pp2.stage0.pos_d",
14612 );
14613 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
14614 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14615 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
14616 rt.tx(0, &x, n_embd)?
14617 };
14618
14619 let _st1 = rt.enter(1);
14621 let pos_d = e1.htod_i32(&[pos])?;
14622 #[cfg(debug_assertions)]
14623 crate::debug_assert_tensor_stream_device(
14624 &pos_d,
14625 &e1.stream(),
14626 "gemma4_decode_step_h_pp2.stage1.pos_d",
14627 );
14628 let x = rt.rx(0, slot, n_embd)?;
14629 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
14630
14631 let mut hn = e1.uninit(n_embd)?;
14632 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14633 let h_seed = e1.clone_dtod(&x)?;
14634 let mut ld = e1.matmul(&self.output, &hn, 1)?;
14635 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14636 e1.softcap(&mut ld, cap, self.output.out_features())?;
14637 self.gemma4_suppress(e1, &mut ld, 1)?;
14638 let logits = e1.dtoh(&ld)?;
14639 cache.pos += 1;
14640 Ok((logits, h_seed))
14641 }
14642
14643 fn gemma4_decode_step_h_pp2_samestream(
14646 &self,
14647 e: &Engine,
14648 token: u32,
14649 cache: &mut Cache,
14650 split: usize,
14651 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14652 let n_embd = self.cfg.n_embd as usize;
14653 let eps = self.cfg.rms_eps;
14654 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14655
14656 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14658 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14659 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
14660
14661 let boundary_tx = e.clone_dtod(&x)?;
14663 let boundary_rx = e.clone_dtod(&boundary_tx)?;
14664
14665 let x =
14667 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
14668
14669 let mut hn = e.uninit(n_embd)?;
14670 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14671 let h_seed = e.clone_dtod(&x)?;
14672 let mut ld = e.matmul(&self.output, &hn, 1)?;
14673 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14674 e.softcap(&mut ld, cap, self.output.out_features())?;
14675 self.gemma4_suppress(e, &mut ld, 1)?;
14676 let logits = e.dtoh(&ld)?;
14677 cache.pos += 1;
14678 Ok((logits, h_seed))
14679 }
14680}
14681
14682impl HybridModel {
14701 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
14704 let geometry = self
14705 .cfg
14706 .layer_geometry(il as u32)
14707 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
14708 debug_assert_eq!(
14709 geometry.attention_gate,
14710 memra_gguf::config::AttentionGateKind::SeparateHead
14711 );
14712 geometry
14713 }
14714
14715 #[allow(clippy::too_many_arguments)]
14775 fn step35_attn_pre_wo(
14776 &self,
14777 e: &Engine,
14778 fa: &FullAttnLayer,
14779 mut g3: Vec<CudaSlice<f32>>,
14780 hg: Option<&CudaSlice<f32>>,
14781 gt_pre: Option<&CudaSlice<f32>>,
14782 pos_d: &CudaSlice<i32>,
14783 t: usize,
14784 cache: Option<&mut Cache>,
14785 il: usize,
14786 seq_end: usize,
14787 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14788 let geometry = self.step35_geom(il);
14789 let hd = geometry.head_dim_k as usize;
14790 let nkv = geometry.n_head_kv as usize;
14791 let nh = geometry.n_head as usize;
14792 let rbase = geometry.rope_base;
14793 let scale = geometry.attention_scale();
14794 let swa = geometry.window.is_some();
14795 let eps = self.cfg.rms_eps;
14796 let win = geometry.window.unwrap_or(0) as usize;
14797 let n_rot = geometry.n_rot as usize;
14798
14799 let v = g3.pop().unwrap();
14800 let k0 = g3.pop().unwrap();
14801 let q0 = g3.pop().unwrap();
14802
14803 let mut q = e.uninit(t * nh * hd)?;
14807 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
14808 let mut k = e.uninit(t * nkv * hd)?;
14809 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
14810 let ff = if geometry.rope_factors {
14811 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
14812 } else {
14813 None
14814 };
14815 #[cfg(debug_assertions)]
14816 if let Some(ff) = ff {
14817 crate::debug_assert_tensor_stream_device(
14818 ff,
14819 &e.stream(),
14820 "step35_attn_pre_wo.rope_freqs",
14821 );
14822 }
14823 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
14824
14825 let mut attn = e.uninit(t * nh * hd)?;
14826 match cache {
14827 Some(cache) => {
14828 let base_len = cache.kv[il].as_ref().unwrap().len;
14829 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
14831 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
14832 let off = if swa {
14833 let raw = base_len.saturating_sub(win - 1);
14834 if legacy_tkv || legacy_calllocal {
14835 raw
14836 } else {
14837 raw & !31usize
14838 }
14839 } else {
14840 0
14841 };
14842 {
14843 let kvl = cache.kv[il].as_mut().unwrap();
14844 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
14845 let write_row = e.prepare_kv_append(kvl, off, t)?;
14846 e.append_kv_quantized_rows(
14847 &k,
14848 &v,
14849 &mut kvl.k,
14850 &mut kvl.v,
14851 write_row,
14852 t,
14853 kvl.kv_dim_k,
14854 kvl.kv_dim_v,
14855 kvl.k_tok_bytes,
14856 kvl.v_tok_bytes,
14857 crate::Engine::kv_fp8_on(),
14858 )?;
14859 kvl.len += t;
14860 let new_len = kvl.len as i32;
14861 e.set_i32_one(&mut kvl.len_d, new_len)?;
14862 }
14863 let kvl = cache.kv[il].as_ref().unwrap();
14864 let t_kv = base_len + t - off;
14887 let physical = kvl.physical_rows(off, off + t_kv)?;
14888 let k_view = e.view_u8_range(
14889 &kvl.k,
14890 physical.start * kvl.k_tok_bytes,
14891 physical.end * kvl.k_tok_bytes,
14892 );
14893 let v_view = e.view_u8_range(
14894 &kvl.v,
14895 physical.start * kvl.v_tok_bytes,
14896 physical.end * kvl.v_tok_bytes,
14897 );
14898 let swa_naive = if legacy_tkv {
14910 t_kv > win
14911 } else {
14912 seq_end > win
14913 };
14914 if swa && swa_naive {
14915 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
14928 e.sdpa_naive_w_quantized_view(
14929 &q,
14930 &k_view,
14931 &v_view,
14932 &mut attn,
14933 hd,
14934 nh,
14935 nkv,
14936 t,
14937 t_kv,
14938 scale,
14939 true,
14940 win,
14941 kvl.k_tok_bytes,
14942 kvl.v_tok_bytes,
14943 )?;
14944 } else {
14945 e.fa_prefill_view_ws_w_hd128(
14946 &q,
14947 &k_view,
14948 &v_view,
14949 &mut attn,
14950 hd,
14951 nh,
14952 nkv,
14953 t,
14954 t_kv,
14955 scale,
14956 true,
14957 win,
14958 kvl.k_tok_bytes,
14959 kvl.v_tok_bytes,
14960 )?;
14961 }
14962 } else if std::env::var("MEMRA_NOFA").is_ok() {
14963 e.sdpa_naive_quantized_view(
14964 &q,
14965 &k_view,
14966 &v_view,
14967 &mut attn,
14968 hd,
14969 nh,
14970 nkv,
14971 t,
14972 t_kv,
14973 scale,
14974 true,
14975 kvl.k_tok_bytes,
14976 kvl.v_tok_bytes,
14977 )?;
14978 } else {
14979 e.fa_prefill_view_ws(
14984 &q,
14985 &k_view,
14986 &v_view,
14987 &mut attn,
14988 hd,
14989 nh,
14990 nkv,
14991 t,
14992 t_kv,
14993 scale,
14994 true,
14995 kvl.k_tok_bytes,
14996 kvl.v_tok_bytes,
14997 crate::Engine::kv_fp8_on(),
14998 )?;
14999 }
15000 }
15001 None => {
15002 debug_assert_eq!(
15007 seq_end, t,
15008 "step35 cacheless prefill is monolithic (seq_end == t)"
15009 );
15010 if swa && seq_end > win {
15011 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
15012 } else if std::env::var("MEMRA_NOFA").is_ok() {
15013 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15014 } else {
15015 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15016 }
15017 }
15018 }
15019
15020 let gw = fa
15023 .attn_gate
15024 .as_ref()
15025 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
15026 let gt_owned = if gt_pre.is_none() {
15027 Some(e.matmul(
15028 gw,
15029 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
15030 t,
15031 )?)
15032 } else {
15033 None
15034 };
15035 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
15036 let mut ag = e.uninit(t * nh * hd)?;
15037 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
15038 Ok(ag)
15039 }
15040
15041 pub(crate) fn step35_attn(
15044 &self,
15045 e: &Engine,
15046 fa: &FullAttnLayer,
15047 h: &CudaSlice<f32>,
15048 pos_d: &CudaSlice<i32>,
15049 t: usize,
15050 il: usize,
15051 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15052 let g3 = match self.step35_tp_qkv(e, fa, h, t)? {
15053 Some(g3) => g3,
15054 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15055 };
15056 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
15058 self.step35_o(e, fa, &ag, t)
15059 }
15060
15061 #[allow(clippy::too_many_arguments)]
15068 pub(crate) fn step35_attn_prime(
15069 &self,
15070 e: &Engine,
15071 fa: &FullAttnLayer,
15072 h: &CudaSlice<f32>,
15073 hx: Option<&CudaSlice<u8>>,
15074 pos_d: &CudaSlice<i32>,
15075 t: usize,
15076 cache: &mut Cache,
15077 il: usize,
15078 seq_end: usize,
15079 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15080 if step_tp_prefill_enabled()? && fa.step_tp_qkv.is_some() {
15081 if hx.is_some() {
15082 return Err(
15083 "rank-local Step prefill preserves BF16 activations and refuses the q8_1 \
15084 pre-quantized prime path"
15085 .into(),
15086 );
15087 }
15088 return self.step35_tp_prefill_attn_resident(e, fa, il, h, pos_d, t, cache, seq_end);
15089 }
15090 let g3 = if fa.step_tp_qkv.is_some() {
15091 if hx.is_some() {
15092 return Err(
15093 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
15094 pre-quantized prime path"
15095 .into(),
15096 );
15097 }
15098 self.step35_tp_qkv(e, fa, h, t)?
15099 .expect("Step Q/K/V TP disappeared after the presence check")
15100 } else {
15101 match hx {
15102 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
15103 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15104 }
15105 };
15106 let ag =
15107 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
15108 self.step35_o(e, fa, &ag, t)
15109 }
15110
15111 fn ensure_step_tp_kv_cache(
15112 &self,
15113 e: &Engine,
15114 fa: &FullAttnLayer,
15115 il: usize,
15116 cache: &mut Cache,
15117 ) -> Result<bool, Box<dyn std::error::Error>> {
15118 let tp = fa
15119 .step_tp_qkv
15120 .as_ref()
15121 .ok_or("Step TP cache hydration lost its resident projections")?;
15122 let geometry = self.step35_geom(il);
15123 let window = geometry.window.map(|window| window as usize);
15124 let ranks = tp.runtime.devices().len();
15125 let head_dim = geometry.head_dim_k as usize;
15126 let kv_heads = geometry.n_head_kv as usize;
15127 let max_ctx = cache.max_ctx;
15128
15129 if cache.tp_kv[il].is_some() {
15130 return Ok(false);
15131 }
15132 let local = cache.kv[il]
15133 .as_ref()
15134 .ok_or_else(|| format!("Step TP layer {il} has no owning-stage KV cache"))?;
15135 if local.kv_dim_k != kv_heads * head_dim || local.kv_dim_v != kv_heads * head_dim {
15136 return Err(format!(
15137 "Step TP layer {il} local KV geometry k={} v={} != {}",
15138 local.kv_dim_k,
15139 local.kv_dim_v,
15140 kv_heads * head_dim
15141 )
15142 .into());
15143 }
15144 let resident_start = window
15145 .map(|window| local.len.saturating_sub(window.saturating_sub(1)) & !31usize)
15146 .unwrap_or(0);
15147 let resident_rows = local.len - resident_start;
15148 let physical = local.physical_rows(resident_start, local.len)?;
15149 let k_rows = if resident_rows == 0 {
15150 Vec::new()
15151 } else {
15152 e.dtoh_u8_view(&e.view_u8_range(
15153 &local.k,
15154 physical.start * local.k_tok_bytes,
15155 physical.end * local.k_tok_bytes,
15156 ))?
15157 };
15158 let v_rows = if resident_rows == 0 {
15159 Vec::new()
15160 } else {
15161 e.dtoh_u8_view(&e.view_u8_range(
15162 &local.v,
15163 physical.start * local.v_tok_bytes,
15164 physical.end * local.v_tok_bytes,
15165 ))?
15166 };
15167 let mut distributed = match window {
15168 Some(window) => tp.runtime.allocate_tp_swa_kv_cache(
15169 kv_heads * head_dim,
15170 kv_heads * head_dim,
15171 max_ctx,
15172 window,
15173 )?,
15174 None => tp.runtime.allocate_tp_kv_cache(
15175 kv_heads * head_dim,
15176 kv_heads * head_dim,
15177 max_ctx,
15178 )?,
15179 };
15180 if distributed.k_tok_bytes() * ranks != local.k_tok_bytes
15181 || distributed.v_tok_bytes() * ranks != local.v_tok_bytes
15182 {
15183 return Err(format!(
15184 "Step TP layer {il} distributed/local KV token bytes disagree: \
15185 k={}x{ranks}/{} v={}x{ranks}/{}",
15186 distributed.k_tok_bytes(),
15187 local.k_tok_bytes,
15188 distributed.v_tok_bytes(),
15189 local.v_tok_bytes,
15190 )
15191 .into());
15192 }
15193 tp.runtime.hydrate_tp_kv_cache_from(
15194 &mut distributed,
15195 local.len,
15196 resident_start,
15197 &k_rows,
15198 &v_rows,
15199 )?;
15200 cache.tp_kv[il] = Some(distributed);
15201 Ok(true)
15202 }
15203
15204 #[allow(clippy::too_many_arguments)]
15205 fn step35_tp_prefill_attn_resident(
15206 &self,
15207 e: &Engine,
15208 fa: &FullAttnLayer,
15209 il: usize,
15210 h: &CudaSlice<f32>,
15211 pos_d: &CudaSlice<i32>,
15212 tokens: usize,
15213 cache: &mut Cache,
15214 seq_end: usize,
15215 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15216 let tp = fa
15217 .step_tp_qkv
15218 .as_ref()
15219 .ok_or("Step TP prefill lost its resident projections")?;
15220 let attention = tp
15221 .attention
15222 .as_ref()
15223 .ok_or("Step TP prefill lost its resident attention auxiliaries")?;
15224 let ranks = tp.runtime.devices().len();
15225 if !step_tp_prefill_shape(
15226 true,
15227 tokens,
15228 ranks,
15229 tp.runtime.native_p2p(),
15230 true,
15231 crate::Engine::kv_fp8_on(),
15232 ) {
15233 return Err(format!(
15234 "rank-local Step prefill requires tokens>={PRIME_MIN_T}, TP4 native P2P, \
15235 rank-local attention, and q8_0/q5_1 KV; got tokens={tokens} ranks={ranks} \
15236 native_p2p={} fp8_kv={}",
15237 tp.runtime.native_p2p(),
15238 crate::Engine::kv_fp8_on(),
15239 )
15240 .into());
15241 }
15242 for seam in [
15243 "MEMRA_STEP35_SWA_TKV",
15244 "MEMRA_PRIME_CALLLOCAL",
15245 "MEMRA_PRIME_F32CHUNK0",
15246 ] {
15247 if std::env::var(seam).as_deref() == Ok("1") {
15248 return Err(format!(
15249 "rank-local Step prefill has not qualified the legacy seam {seam}=1"
15250 )
15251 .into());
15252 }
15253 }
15254
15255 let geometry = self.step35_geom(il);
15256 let window = geometry.window.map(|window| window as usize);
15257 let head_dim = geometry.head_dim_k as usize;
15258 let heads = geometry.n_head as usize;
15259 let kv_heads = geometry.n_head_kv as usize;
15260 if heads % ranks != 0 || kv_heads % ranks != 0 {
15261 return Err(format!(
15262 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15263 )
15264 .into());
15265 }
15266 let local_heads = heads / ranks;
15267 let local_kv_heads = kv_heads / ranks;
15268 let local_kv_dim = local_kv_heads * head_dim;
15269 let hidden = self.cfg.n_embd as usize;
15270 let expected_input = tokens
15271 .checked_mul(hidden)
15272 .ok_or("Step TP prefill input size overflow")?;
15273 if h.len() < expected_input {
15274 return Err(format!(
15275 "Step TP prefill input {} is shorter than {tokens}x{hidden}",
15276 h.len()
15277 )
15278 .into());
15279 }
15280 let positions = e.dtoh_i32(pos_d)?;
15281 if positions.len() != tokens {
15282 return Err(format!(
15283 "rank-local Step prefill positions {} != tokens {tokens}",
15284 positions.len()
15285 )
15286 .into());
15287 }
15288
15289 let mut active_input = e.uninit(expected_input)?;
15290 e.copy_view_into(
15291 &mut active_input,
15292 0,
15293 &h.slice(0..expected_input),
15294 expected_input,
15295 )?;
15296 let mut input = tp.runtime.allocate_replicated_device_rows(tokens, hidden)?;
15297 e.stream().synchronize()?;
15302 tp.runtime
15303 .refresh_replicated_device_rows_from_root(&mut input, &active_input)?;
15304 let q_raw = tp
15305 .runtime
15306 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &input)?;
15307 let k_raw = tp
15308 .runtime
15309 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &input)?;
15310 let v_raw = tp
15311 .runtime
15312 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &input)?;
15313 let mut q = Vec::with_capacity(ranks);
15314 let mut k = Vec::with_capacity(ranks);
15315 for rank in 0..ranks {
15316 let engine = tp
15317 .runtime
15318 .rank_engine(rank)
15319 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15320 let _main = engine.gpu.enter_main()?;
15321 let mut q_rank = engine.uninit(tokens * local_heads * head_dim)?;
15322 engine.rms_norm(
15323 &q_raw[rank],
15324 &attention.q_norm[rank],
15325 &mut q_rank,
15326 head_dim,
15327 tokens * local_heads,
15328 self.cfg.rms_eps,
15329 )?;
15330 let mut k_rank = engine.uninit(tokens * local_kv_dim)?;
15331 engine.rms_norm(
15332 &k_raw[rank],
15333 &attention.k_norm[rank],
15334 &mut k_rank,
15335 head_dim,
15336 tokens * local_kv_heads,
15337 self.cfg.rms_eps,
15338 )?;
15339 let position = engine.htod_i32(&positions)?;
15340 let rope_freqs = if geometry.rope_factors {
15341 self.step35_aux
15342 .as_ref()
15343 .and_then(|aux| aux.rope_freqs(engine))
15344 } else {
15345 None
15346 };
15347 engine.rope_neox2(
15348 &mut q_rank,
15349 &mut k_rank,
15350 &position,
15351 head_dim,
15352 geometry.n_rot as usize,
15353 local_heads,
15354 local_kv_heads,
15355 tokens,
15356 geometry.rope_base,
15357 1.0,
15358 rope_freqs,
15359 )?;
15360 q.push(q_rank);
15361 k.push(k_rank);
15362 }
15363
15364 let gate_weight = fa
15365 .attn_gate
15366 .as_ref()
15367 .ok_or("step35 layer is missing attn_gate.weight")?;
15368 let gate = e.dtoh(&e.matmul(gate_weight, h, tokens)?)?;
15369 if gate.len() != tokens * heads {
15370 return Err(format!(
15371 "Step TP layer {il} gate output {} != {tokens}x{heads}",
15372 gate.len()
15373 )
15374 .into());
15375 }
15376
15377 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15378 let base_len = cache.kv[il]
15379 .as_ref()
15380 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15381 .len;
15382 let distributed = cache.tp_kv[il]
15383 .as_ref()
15384 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15385 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15386 return Err(format!(
15387 "Step TP layer {il} cache lengths diverged before prefill: \
15388 local={base_len} distributed={}/{}",
15389 distributed.committed_len(),
15390 distributed.staged_len()
15391 )
15392 .into());
15393 }
15394 let target_len = base_len
15395 .checked_add(tokens)
15396 .ok_or("Step TP prefill cache length overflow")?;
15397 if target_len > cache.max_ctx {
15398 return Err(format!(
15399 "Step TP layer {il} prefill exceeds cache: {base_len}+{tokens}>{}",
15400 cache.max_ctx
15401 )
15402 .into());
15403 }
15404 if seq_end < target_len {
15405 return Err(format!(
15406 "Step TP layer {il} request end {seq_end} precedes chunk end {target_len}"
15407 )
15408 .into());
15409 }
15410
15411 let transaction = cache.tp_kv[il]
15412 .as_mut()
15413 .expect("distributed cache checked above")
15414 .begin_transaction()?;
15415 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15416 cache.tp_kv[il]
15417 .as_mut()
15418 .expect("distributed cache checked above"),
15419 transaction,
15420 &k,
15421 &v_raw,
15422 tokens,
15423 ) {
15424 let _ = tp.runtime.rollback_tp_kv_transaction(
15425 cache.tp_kv[il]
15426 .as_mut()
15427 .expect("distributed cache checked above"),
15428 transaction,
15429 );
15430 return Err(error);
15431 }
15432
15433 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15434 let distributed = cache.tp_kv[il]
15435 .as_ref()
15436 .expect("distributed cache checked above");
15437 let staged_len = distributed.staged_len();
15438 let view_start = window
15439 .map(|window| base_len.saturating_sub(window.saturating_sub(1)) & !31usize)
15440 .unwrap_or(0);
15441 let physical = distributed.physical_range(view_start, staged_len)?;
15442 let t_kv = staged_len - view_start;
15443 let swa_naive = window.is_some_and(|window| seq_end > window);
15444 let mut gated = Vec::with_capacity(ranks);
15445 for rank in 0..ranks {
15446 let engine = tp
15447 .runtime
15448 .rank_engine(rank)
15449 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15450 let _main = engine.gpu.enter_main()?;
15451 let rank_cache = distributed
15452 .rank(rank)
15453 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
15454 let k_view = engine.view_u8_range(
15455 rank_cache.k(),
15456 physical.start * distributed.k_tok_bytes(),
15457 physical.end * distributed.k_tok_bytes(),
15458 );
15459 let v_view = engine.view_u8_range(
15460 rank_cache.v(),
15461 physical.start * distributed.v_tok_bytes(),
15462 physical.end * distributed.v_tok_bytes(),
15463 );
15464 let mut attention_out = engine.uninit(tokens * local_heads * head_dim)?;
15465 if swa_naive {
15466 let window = window.expect("SWA predicate requires a window");
15467 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15468 engine.sdpa_naive_w_quantized_view(
15469 &q[rank],
15470 &k_view,
15471 &v_view,
15472 &mut attention_out,
15473 head_dim,
15474 local_heads,
15475 local_kv_heads,
15476 tokens,
15477 t_kv,
15478 geometry.attention_scale(),
15479 true,
15480 window,
15481 distributed.k_tok_bytes(),
15482 distributed.v_tok_bytes(),
15483 )?;
15484 } else {
15485 engine.fa_prefill_view_ws_w_hd128(
15486 &q[rank],
15487 &k_view,
15488 &v_view,
15489 &mut attention_out,
15490 head_dim,
15491 local_heads,
15492 local_kv_heads,
15493 tokens,
15494 t_kv,
15495 geometry.attention_scale(),
15496 true,
15497 window,
15498 distributed.k_tok_bytes(),
15499 distributed.v_tok_bytes(),
15500 )?;
15501 }
15502 } else if std::env::var("MEMRA_NOFA").is_ok() {
15503 engine.sdpa_naive_quantized_view(
15504 &q[rank],
15505 &k_view,
15506 &v_view,
15507 &mut attention_out,
15508 head_dim,
15509 local_heads,
15510 local_kv_heads,
15511 tokens,
15512 t_kv,
15513 geometry.attention_scale(),
15514 true,
15515 distributed.k_tok_bytes(),
15516 distributed.v_tok_bytes(),
15517 )?;
15518 } else {
15519 engine.fa_prefill_view_ws(
15520 &q[rank],
15521 &k_view,
15522 &v_view,
15523 &mut attention_out,
15524 head_dim,
15525 local_heads,
15526 local_kv_heads,
15527 tokens,
15528 t_kv,
15529 geometry.attention_scale(),
15530 true,
15531 distributed.k_tok_bytes(),
15532 distributed.v_tok_bytes(),
15533 false,
15534 )?;
15535 }
15536
15537 let gate_start = rank * local_heads;
15538 let mut gate_rank = Vec::with_capacity(tokens * local_heads);
15539 for token in 0..tokens {
15540 let start = token * heads + gate_start;
15541 gate_rank.extend_from_slice(&gate[start..start + local_heads]);
15542 }
15543 let gate_rank = engine.htod(&gate_rank)?;
15544 let mut gated_rank = engine.uninit(tokens * local_heads * head_dim)?;
15545 engine.attn_head_gate(
15546 &attention_out,
15547 &gate_rank,
15548 &mut gated_rank,
15549 None,
15550 head_dim,
15551 local_heads,
15552 tokens,
15553 )?;
15554 gated.push(gated_rank);
15555 }
15556 for rank in 1..ranks {
15557 let engine = tp
15558 .runtime
15559 .rank_engine(rank)
15560 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15561 let _main = engine.gpu.enter_main()?;
15562 engine.stream().synchronize()?;
15563 }
15564
15565 let (output, k_shadow, v_shadow) = if tp.runtime.bulk_p2p() {
15566 let output = tp
15567 .runtime
15568 .step_bf16_row_parallel_resident_root_device(&tp.o, &gated, tokens)?;
15569 let k_shadow =
15570 tp.runtime
15571 .gather_native_column_shards_device(&k, tokens, local_kv_dim)?;
15572 let v_shadow =
15573 tp.runtime
15574 .gather_native_column_shards_device(&v_raw, tokens, local_kv_dim)?;
15575 let root = tp
15576 .runtime
15577 .rank_engine(0)
15578 .ok_or("Step TP prefill lost its root engine")?;
15579 let _main = root.gpu.enter_main()?;
15580 root.stream().synchronize()?;
15581 (output, k_shadow, v_shadow)
15582 } else {
15583 let attention = tp.runtime.gather_native_column_shards(
15584 &gated,
15585 tokens,
15586 local_heads * head_dim,
15587 )?;
15588 let output = tp
15589 .runtime
15590 .step_bf16_row_parallel_resident_native(&tp.o, &attention, tokens)?;
15591 let k_shadow = tp
15592 .runtime
15593 .gather_native_column_shards(&k, tokens, local_kv_dim)?;
15594 let v_shadow =
15595 tp.runtime
15596 .gather_native_column_shards(&v_raw, tokens, local_kv_dim)?;
15597 (e.htod(&output)?, e.htod(&k_shadow)?, e.htod(&v_shadow)?)
15598 };
15599 let local = cache.kv[il]
15600 .as_mut()
15601 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
15602 if local.len != base_len {
15603 return Err(format!(
15604 "Step TP layer {il} local cache changed during prefill: \
15605 len={} base={base_len}",
15606 local.len
15607 )
15608 .into());
15609 }
15610 let retain_from = window
15611 .map(|window| {
15612 let staged_retain = staged_len.saturating_sub(window) & !31usize;
15613 let rollback_retain =
15614 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
15615 staged_retain.min(rollback_retain)
15616 })
15617 .unwrap_or(0);
15618 let write_row = e.prepare_kv_append(local, retain_from, tokens)?;
15619 e.append_kv_quantized_rows(
15620 &k_shadow,
15621 &v_shadow,
15622 &mut local.k,
15623 &mut local.v,
15624 write_row,
15625 tokens,
15626 local.kv_dim_k,
15627 local.kv_dim_v,
15628 local.k_tok_bytes,
15629 local.v_tok_bytes,
15630 false,
15631 )?;
15632 local.len = staged_len;
15633 e.set_i32_one(&mut local.len_d, staged_len as i32)?;
15634 Ok(output)
15635 })();
15636
15637 let output = match staged {
15638 Ok(output) => output,
15639 Err(error) => {
15640 let _ = tp.runtime.rollback_tp_kv_transaction(
15641 cache.tp_kv[il]
15642 .as_mut()
15643 .expect("distributed cache checked above"),
15644 transaction,
15645 );
15646 if let Some(local) = cache.kv[il].as_mut() {
15647 local.len = base_len;
15648 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
15649 }
15650 return Err(error);
15651 }
15652 };
15653 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
15654 cache.tp_kv[il]
15655 .as_mut()
15656 .expect("distributed cache checked above"),
15657 transaction,
15658 tokens,
15659 ) {
15660 let _ = tp.runtime.rollback_tp_kv_transaction(
15661 cache.tp_kv[il]
15662 .as_mut()
15663 .expect("distributed cache checked above"),
15664 transaction,
15665 );
15666 let local = cache.kv[il].as_mut().expect("local cache checked above");
15667 local.len = base_len;
15668 e.set_i32_one(&mut local.len_d, base_len as i32)?;
15669 return Err(error);
15670 }
15671
15672 let committed = cache.tp_kv[il]
15673 .as_ref()
15674 .expect("distributed cache checked above")
15675 .committed_len();
15676 let local_len = cache.kv[il]
15677 .as_ref()
15678 .expect("local cache checked above")
15679 .len;
15680 if committed != local_len {
15681 return Err(format!(
15682 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
15683 )
15684 .into());
15685 }
15686 eprintln!(
15687 "[step-tp-prefill-attn] execute layer={} devices={:?} tokens={tokens} \
15688 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
15689 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
15690 attention_scope={} input_path=root-device-replicated gate_tensor_parallel=false \
15691 gate_shards=host-canonical o_tensor_parallel=true local_cache_shadow=true \
15692 cache_commit=chunk transport={} native_p2p=true bulk_p2p={} \
15693 output={} performance_claim=false",
15694 tp.layer,
15695 tp.devices,
15696 hydrated,
15697 if window.is_some() {
15698 "rank-local-swa-ring"
15699 } else {
15700 "rank-local-global"
15701 },
15702 tp.runtime.transport_label(),
15703 tp.runtime.bulk_p2p(),
15704 if tp.runtime.bulk_p2p() {
15705 "root-device"
15706 } else {
15707 "root-readback"
15708 },
15709 );
15710 Ok(output)
15711 }
15712
15713 fn step35_tp_decode_attn_resident(
15714 &self,
15715 e: &Engine,
15716 fa: &FullAttnLayer,
15717 il: usize,
15718 h: &CudaSlice<f32>,
15719 pos_d: &CudaSlice<i32>,
15720 cache: &mut Cache,
15721 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15722 static ATTN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15726 static ATTN_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15727 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15728 let started = timing.then(std::time::Instant::now);
15729 let result = if crate::tp::step_tp_decode_v2_enabled()? {
15730 self.step35_tp_decode_attn_resident_v2(e, fa, il, h, pos_d, cache)
15731 } else {
15732 self.step35_tp_decode_attn_resident_inner(e, fa, il, h, pos_d, cache)
15733 };
15734 if let Some(started) = started {
15735 use std::sync::atomic::Ordering;
15736 let ns = ATTN_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
15737 + started.elapsed().as_nanos() as u64;
15738 let calls = ATTN_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
15739 if calls % 430 == 0 {
15740 eprintln!(
15741 "[step-tp-attn-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
15742 ns as f64 / 1.0e6,
15743 ns as f64 / calls as f64 / 1.0e3,
15744 );
15745 }
15746 }
15747 result
15748 }
15749
15750 #[allow(clippy::too_many_arguments)]
15751 fn step35_tp_decode_attn_resident_inner(
15752 &self,
15753 e: &Engine,
15754 fa: &FullAttnLayer,
15755 il: usize,
15756 h: &CudaSlice<f32>,
15757 pos_d: &CudaSlice<i32>,
15758 cache: &mut Cache,
15759 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15760 static T_POS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15765 static T_QKV: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15766 static T_NORMROPE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15767 static T_GATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15768 static T_APPEND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15769 static T_ATTN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15770 static T_OPROJ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15771 static T_SHADOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15772 static T_PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15773 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15774 fn lap(
15775 runtime: &crate::tp::TpE4m3HostBounce,
15776 e: &Engine,
15777 timer: &std::sync::atomic::AtomicU64,
15778 started: &mut Option<std::time::Instant>,
15779 ) -> Result<(), Box<dyn std::error::Error>> {
15780 let Some(start) = started.as_mut() else {
15781 return Ok(());
15782 };
15783 for rank in 0..runtime.devices().len() {
15784 if let Some(engine) = runtime.rank_engine(rank) {
15785 let _main = engine.gpu.enter_main()?;
15786 engine.stream().synchronize()?;
15787 }
15788 }
15789 e.stream().synchronize()?;
15790 timer.fetch_add(
15791 start.elapsed().as_nanos() as u64,
15792 std::sync::atomic::Ordering::Relaxed,
15793 );
15794 *start = std::time::Instant::now();
15795 Ok(())
15796 }
15797 let tp = fa
15798 .step_tp_qkv
15799 .as_ref()
15800 .ok_or("Step TP decode lost its resident projections")?;
15801 let attention = tp
15802 .attention
15803 .as_ref()
15804 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
15805 if !tp.runtime.native_p2p() {
15806 return Err("rank-local Step attention requires native P2P".into());
15807 }
15808 if crate::Engine::kv_fp8_on() {
15809 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
15810 }
15811
15812 let geometry = self.step35_geom(il);
15813 let window = geometry.window.map(|window| window as usize);
15814 let ranks = tp.runtime.devices().len();
15815 let head_dim = geometry.head_dim_k as usize;
15816 let heads = geometry.n_head as usize;
15817 let kv_heads = geometry.n_head_kv as usize;
15818 if heads % ranks != 0 || kv_heads % ranks != 0 {
15819 return Err(format!(
15820 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15821 )
15822 .into());
15823 }
15824 let local_heads = heads / ranks;
15825 let local_kv_heads = kv_heads / ranks;
15826 let local_kv_dim = local_kv_heads * head_dim;
15827 let max_ctx = cache.max_ctx;
15828
15829 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15830
15831 let base_len = cache.kv[il]
15832 .as_ref()
15833 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15834 .len;
15835 let distributed = cache.tp_kv[il]
15836 .as_ref()
15837 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15838 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15839 return Err(format!(
15840 "Step TP layer {il} cache lengths diverged before decode: \
15841 local={base_len} distributed={}/{}",
15842 distributed.committed_len(),
15843 distributed.staged_len()
15844 )
15845 .into());
15846 }
15847
15848 let mut lap_start = timing.then(std::time::Instant::now);
15849 let positions = e.dtoh_i32(pos_d)?;
15850 if positions.len() != 1 {
15851 return Err(format!(
15852 "rank-local Step decode requires one position, got {}",
15853 positions.len()
15854 )
15855 .into());
15856 }
15857 lap(&tp.runtime, e, &T_POS, &mut lap_start)?;
15858 let (q_raw, k_raw, v_raw, input_path) = if let Some(decode_input) =
15859 attention.decode_input.as_ref()
15860 {
15861 let mut decode_input = decode_input
15862 .lock()
15863 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
15864 e.stream().synchronize()?;
15868 tp.runtime
15869 .refresh_replicated_device_rows_from_root(&mut decode_input, h)?;
15870 let q_raw = tp
15871 .runtime
15872 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &decode_input)?;
15873 let k_raw = tp
15874 .runtime
15875 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &decode_input)?;
15876 let v_raw = tp
15877 .runtime
15878 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &decode_input)?;
15879 (q_raw, k_raw, v_raw, "root-device-replicated")
15880 } else {
15881 let activation = e.dtoh(h)?;
15882 let q_raw =
15883 tp.runtime
15884 .bf16_column_parallel_resident_device_shards(&tp.q, &activation, 1)?;
15885 let k_raw =
15886 tp.runtime
15887 .bf16_column_parallel_resident_device_shards(&tp.k, &activation, 1)?;
15888 let v_raw =
15889 tp.runtime
15890 .bf16_column_parallel_resident_device_shards(&tp.v, &activation, 1)?;
15891 (q_raw, k_raw, v_raw, "host-replicated")
15892 };
15893 lap(&tp.runtime, e, &T_QKV, &mut lap_start)?;
15894 let mut q = Vec::with_capacity(ranks);
15895 let mut k = Vec::with_capacity(ranks);
15896 for rank in 0..ranks {
15897 let engine = tp
15898 .runtime
15899 .rank_engine(rank)
15900 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15901 let _main = engine.gpu.enter_main()?;
15902 let mut q_rank = engine.uninit(local_heads * head_dim)?;
15903 engine.rms_norm(
15904 &q_raw[rank],
15905 &attention.q_norm[rank],
15906 &mut q_rank,
15907 head_dim,
15908 local_heads,
15909 self.cfg.rms_eps,
15910 )?;
15911 let mut k_rank = engine.uninit(local_kv_dim)?;
15912 engine.rms_norm(
15913 &k_raw[rank],
15914 &attention.k_norm[rank],
15915 &mut k_rank,
15916 head_dim,
15917 local_kv_heads,
15918 self.cfg.rms_eps,
15919 )?;
15920 let position = engine.htod_i32(&positions)?;
15921 let rope_freqs = if geometry.rope_factors {
15922 self.step35_aux
15923 .as_ref()
15924 .and_then(|aux| aux.rope_freqs(engine))
15925 } else {
15926 None
15927 };
15928 engine.rope_neox2(
15929 &mut q_rank,
15930 &mut k_rank,
15931 &position,
15932 head_dim,
15933 geometry.n_rot as usize,
15934 local_heads,
15935 local_kv_heads,
15936 1,
15937 geometry.rope_base,
15938 1.0,
15939 rope_freqs,
15940 )?;
15941 q.push(q_rank);
15942 k.push(k_rank);
15943 }
15944 lap(&tp.runtime, e, &T_NORMROPE, &mut lap_start)?;
15945
15946 let gate_weight = fa
15947 .attn_gate
15948 .as_ref()
15949 .ok_or("step35 layer is missing attn_gate.weight")?;
15950 let gate = e.matmul(gate_weight, h, 1)?;
15951 let gate = e.dtoh(&gate)?;
15952 if gate.len() != heads {
15953 return Err(format!("Step TP layer {il} gate output {} != {heads}", gate.len()).into());
15954 }
15955 lap(&tp.runtime, e, &T_GATE, &mut lap_start)?;
15956
15957 let transaction = cache.tp_kv[il]
15958 .as_mut()
15959 .expect("distributed cache checked above")
15960 .begin_transaction()?;
15961 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15962 cache.tp_kv[il]
15963 .as_mut()
15964 .expect("distributed cache checked above"),
15965 transaction,
15966 &k,
15967 &v_raw,
15968 1,
15969 ) {
15970 let _ = tp.runtime.rollback_tp_kv_transaction(
15971 cache.tp_kv[il]
15972 .as_mut()
15973 .expect("distributed cache checked above"),
15974 transaction,
15975 );
15976 return Err(error);
15977 }
15978 lap(&tp.runtime, e, &T_APPEND, &mut lap_start)?;
15979
15980 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15981 let distributed = cache.tp_kv[il]
15982 .as_ref()
15983 .expect("distributed cache checked above");
15984 let staged_len = distributed.staged_len();
15985 let view_start = window
15986 .map(|window| staged_len.saturating_sub(window))
15987 .unwrap_or(0);
15988 let physical = distributed.physical_range(view_start, staged_len)?;
15989 let t_kv = staged_len - view_start;
15990 let mut gated = Vec::with_capacity(ranks);
15991 for rank in 0..ranks {
15992 let engine = tp
15993 .runtime
15994 .rank_engine(rank)
15995 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15996 let _main = engine.gpu.enter_main()?;
15997 let rank_cache = distributed
15998 .rank(rank)
15999 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16000 let k_view = engine.view_u8_range(
16001 rank_cache.k(),
16002 physical.start * distributed.k_tok_bytes(),
16003 physical.end * distributed.k_tok_bytes(),
16004 );
16005 let v_view = engine.view_u8_range(
16006 rank_cache.v(),
16007 physical.start * distributed.v_tok_bytes(),
16008 physical.end * distributed.v_tok_bytes(),
16009 );
16010 let mut attention_out = engine.uninit(local_heads * head_dim)?;
16011 engine.fa_decode_kvmod(
16012 &q[rank],
16013 &k_view,
16014 &v_view,
16015 &mut attention_out,
16016 head_dim,
16017 local_heads,
16018 local_kv_heads,
16019 t_kv,
16020 geometry.attention_scale(),
16021 distributed.k_tok_bytes(),
16022 distributed.v_tok_bytes(),
16023 false,
16024 )?;
16025 let gate_start = rank * local_heads;
16026 let gate_rank = engine.htod(&gate[gate_start..gate_start + local_heads])?;
16027 let mut gated_rank = engine.uninit(local_heads * head_dim)?;
16028 engine.attn_head_gate(
16029 &attention_out,
16030 &gate_rank,
16031 &mut gated_rank,
16032 None,
16033 head_dim,
16034 local_heads,
16035 1,
16036 )?;
16037 gated.push(gated_rank);
16038 }
16039 lap(&tp.runtime, e, &T_ATTN, &mut lap_start)?;
16040
16041 let gathered =
16042 tp.runtime
16043 .gather_native_column_shards(&gated, 1, local_heads * head_dim)?;
16044 let output = tp
16045 .runtime
16046 .step_bf16_row_parallel_resident_native(&tp.o, &gathered, 1)?;
16047 let output = e.htod(&output)?;
16048 lap(&tp.runtime, e, &T_OPROJ, &mut lap_start)?;
16049
16050 let k_shadow = tp
16051 .runtime
16052 .gather_native_column_shards(&k, 1, local_kv_dim)?;
16053 let v_shadow = tp
16054 .runtime
16055 .gather_native_column_shards(&v_raw, 1, local_kv_dim)?;
16056 let k_shadow = e.htod(&k_shadow)?;
16057 let v_shadow = e.htod(&v_shadow)?;
16058 let local = cache.kv[il]
16059 .as_mut()
16060 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16061 if local.len != base_len || base_len + 1 > max_ctx {
16062 return Err(format!(
16063 "Step TP layer {il} local cache changed during decode: \
16064 len={} base={base_len} max={max_ctx}",
16065 local.len
16066 )
16067 .into());
16068 }
16069 let retain_from = window
16070 .map(|window| {
16071 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16072 let rollback_retain =
16073 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16074 staged_retain.min(rollback_retain)
16075 })
16076 .unwrap_or(0);
16077 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16078 e.append_kv_quantized(
16079 &k_shadow,
16080 &v_shadow,
16081 &mut local.k,
16082 &mut local.v,
16083 write_row,
16084 local.kv_dim_k,
16085 local.kv_dim_v,
16086 local.k_tok_bytes,
16087 local.v_tok_bytes,
16088 false,
16089 )?;
16090 local.len = base_len + 1;
16091 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16092 Ok(output)
16093 })();
16094
16095 let output = match staged {
16096 Ok(output) => output,
16097 Err(error) => {
16098 let _ = tp.runtime.rollback_tp_kv_transaction(
16099 cache.tp_kv[il]
16100 .as_mut()
16101 .expect("distributed cache checked above"),
16102 transaction,
16103 );
16104 if let Some(local) = cache.kv[il].as_mut() {
16105 local.len = base_len;
16106 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16107 }
16108 return Err(error);
16109 }
16110 };
16111 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16112 cache.tp_kv[il]
16113 .as_mut()
16114 .expect("distributed cache checked above"),
16115 transaction,
16116 1,
16117 ) {
16118 let _ = tp.runtime.rollback_tp_kv_transaction(
16119 cache.tp_kv[il]
16120 .as_mut()
16121 .expect("distributed cache checked above"),
16122 transaction,
16123 );
16124 let local = cache.kv[il].as_mut().expect("local cache checked above");
16125 local.len = base_len;
16126 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16127 return Err(error);
16128 }
16129
16130 let committed = cache.tp_kv[il]
16131 .as_ref()
16132 .expect("distributed cache checked above")
16133 .committed_len();
16134 let local_len = cache.kv[il]
16135 .as_ref()
16136 .expect("local cache checked above")
16137 .len;
16138 if committed != local_len {
16139 return Err(format!(
16140 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16141 )
16142 .into());
16143 }
16144 lap(&tp.runtime, e, &T_SHADOW, &mut lap_start)?;
16145 if timing {
16146 use std::sync::atomic::Ordering;
16147 let calls = T_PHASE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16148 if calls % 430 == 0 {
16149 let avg = |t: &std::sync::atomic::AtomicU64| {
16150 t.load(Ordering::Relaxed) as f64 / calls as f64 / 1.0e3
16151 };
16152 eprintln!(
16153 "[step-tp-attn-phase] calls={calls} avg_us pos={:.1} qkv={:.1} \
16154 normrope={:.1} gate={:.1} append={:.1} attn={:.1} oproj={:.1} shadow={:.1}",
16155 avg(&T_POS),
16156 avg(&T_QKV),
16157 avg(&T_NORMROPE),
16158 avg(&T_GATE),
16159 avg(&T_APPEND),
16160 avg(&T_ATTN),
16161 avg(&T_OPROJ),
16162 avg(&T_SHADOW),
16163 );
16164 }
16165 }
16166 eprintln!(
16167 "[step-tp-attn] execute layer={} devices={:?} tokens=1 \
16168 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16169 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16170 attention_scope={} input_path={} kv_physical_rows={} \
16171 gate_tensor_parallel=false gate_shards=host-canonical o_tensor_parallel=true \
16172 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16173 bulk_p2p={} output=root-readback performance_claim=false",
16174 tp.layer,
16175 tp.devices,
16176 hydrated,
16177 if window.is_some() {
16178 "rank-local-swa-ring"
16179 } else {
16180 "rank-local-global"
16181 },
16182 input_path,
16183 cache.tp_kv[il]
16184 .as_ref()
16185 .expect("distributed cache checked above")
16186 .physical_capacity(),
16187 tp.runtime.transport_label(),
16188 tp.runtime.bulk_p2p(),
16189 );
16190 Ok(output)
16191 }
16192
16193 #[allow(clippy::too_many_arguments)]
16200 pub(crate) fn step35_verify_qkv_precompute(
16205 &self,
16206 e: &Engine,
16207 il: usize,
16208 h_t: &CudaSlice<f32>,
16209 t: usize,
16210 ) -> Result<bool, Box<dyn std::error::Error>> {
16211 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16212 return Ok(false);
16213 };
16214 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16215 return Ok(false);
16216 };
16217 let Some(attention) = tp.attention.as_ref() else {
16218 return Ok(false);
16219 };
16220 if !tp.runtime.native_p2p() || !crate::tp::step_tp_qkv_fused_enabled()? {
16221 return Ok(false);
16222 }
16223 let geometry = self.step35_geom(il);
16224 let heads = geometry.n_head as usize;
16225 let ws_index = tp
16226 .runtime
16227 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16228 let gate_shards = attention
16229 .gate_shards_bf16
16230 .as_deref()
16231 .map(crate::tp::StepTpGateShards::Bf16);
16232 tp.runtime.decode_v2_input_qkv_tcol(
16233 ws_index,
16234 e,
16235 h_t,
16236 t,
16237 &tp.q,
16238 &tp.k,
16239 &tp.v,
16240 gate_shards,
16241 )?;
16242 Ok(true)
16243 }
16244
16245 pub(crate) fn step35_verify_oproj_tcol(
16250 &self,
16251 e: &Engine,
16252 il: usize,
16253 t: usize,
16254 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16255 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16256 return Err("tcol o_proj join expects full attention".into());
16257 };
16258 let tp = fa
16259 .step_tp_qkv
16260 .as_ref()
16261 .ok_or("tcol o_proj join lost its resident projections")?;
16262 let heads = self.step35_geom(il).n_head as usize;
16263 let ws_index = tp
16264 .runtime
16265 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16266 tp.runtime.decode_v2_oproj_tcol(ws_index, e, &tp.o, t)
16267 }
16268
16269 pub(crate) fn step35_verify_moe_t2(
16275 &self,
16276 e: &Engine,
16277 il: usize,
16278 z2: &CudaSlice<f32>,
16279 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16280 let layer = &self.layers[il];
16281 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
16282 return Ok(None);
16283 };
16284 let Some(tp) = m.step_tp.as_ref() else {
16285 return Ok(None);
16286 };
16287 let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts else {
16288 return Ok(None);
16289 };
16290 if !crate::tp::step_nvfp4_dev_routes_enabled()?
16291 || !crate::tp::step_tp_dev_router_enabled()?
16292 || !crate::tp::nvfp4_bank_v2_on()
16293 {
16294 return Ok(None);
16295 }
16296 let cfg = &self.cfg;
16297 let Some(moe) = cfg.moe.as_ref() else {
16298 return Ok(None);
16299 };
16300 let Some((sf, route_norm)) = cfg.sigmoid_router() else {
16301 return Ok(None);
16302 };
16303 let n_embd = cfg.n_embd as usize;
16304 let n_expert = moe.expert_count as usize;
16305 let n_used = moe.expert_used_count as usize;
16306 if z2.len() < 2 * n_embd {
16307 return Err("verify moe t2 geometry".into());
16308 }
16309 let logits = Self::moe_router_logits(e, m, z2, 2, cfg)?;
16310 static SELW2: std::sync::Mutex<Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>> =
16312 std::sync::Mutex::new(None);
16313 let mut selw = SELW2.lock().map_err(|_| "selw2 lock poisoned")?;
16314 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
16315 *selw = Some((
16316 e.ctx().ordinal(),
16317 e.htod_i32(&vec![0i32; 2 * n_used])?,
16318 e.htod(&vec![0.0f32; 2 * n_used])?,
16319 ));
16320 }
16321 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
16322 e.moe_router_sigmoid_topk_into(
16323 &logits,
16324 2,
16325 n_expert,
16326 n_used,
16327 m.active_count(),
16328 &m.exp_probs_b_dev,
16329 &m.active_experts_dev,
16330 sf,
16331 route_norm,
16332 sel_d,
16333 w_d,
16334 )?;
16335 let mut out2 = tp
16336 .runtime
16337 .run_tensor_parallel_routes_nvfp4_device_routed_t2(
16338 bank,
16339 e,
16340 z2,
16341 sel_d,
16342 w_d,
16343 n_used,
16344 tp.activation_limit,
16345 )?;
16346 let mut z_row = e.uninit(n_embd)?;
16348 let mut out_row = e.uninit(n_embd)?;
16349 for c in 0..2 {
16350 e.dtod_copy_view(&z2.slice(c * n_embd..(c + 1) * n_embd), &mut z_row)?;
16351 e.dtod_copy_view(&out2.slice(c * n_embd..(c + 1) * n_embd), &mut out_row)?;
16352 Self::moe_ffn_grouped_add_shared(e, m, &z_row, 1, cfg, il as u16, &mut out_row)?;
16353 e.dtod_copy_into(&out_row, &mut out2, c * n_embd)?;
16354 }
16355 Ok(Some(out2))
16356 }
16357
16358 fn step35_tp_decode_attn_resident_v2(
16359 &self,
16360 e: &Engine,
16361 fa: &FullAttnLayer,
16362 il: usize,
16363 h: &CudaSlice<f32>,
16364 pos_d: &CudaSlice<i32>,
16365 cache: &mut Cache,
16366 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16367 let tp = fa
16368 .step_tp_qkv
16369 .as_ref()
16370 .ok_or("Step TP decode lost its resident projections")?;
16371 let attention = tp
16372 .attention
16373 .as_ref()
16374 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
16375 if !tp.runtime.native_p2p() {
16376 return Err("rank-local Step attention requires native P2P".into());
16377 }
16378 if crate::Engine::kv_fp8_on() {
16379 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
16380 }
16381
16382 let geometry = self.step35_geom(il);
16383 let window = geometry.window.map(|window| window as usize);
16384 let ranks = tp.runtime.devices().len();
16385 let head_dim = geometry.head_dim_k as usize;
16386 let heads = geometry.n_head as usize;
16387 let kv_heads = geometry.n_head_kv as usize;
16388 if heads % ranks != 0 || kv_heads % ranks != 0 {
16389 return Err(format!(
16390 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
16391 )
16392 .into());
16393 }
16394 let local_heads = heads / ranks;
16395 let local_kv_heads = kv_heads / ranks;
16396 let max_ctx = cache.max_ctx;
16397
16398 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
16399
16400 let base_len = cache.kv[il]
16401 .as_ref()
16402 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
16403 .len;
16404 {
16405 let distributed = cache.tp_kv[il]
16406 .as_ref()
16407 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
16408 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
16409 return Err(format!(
16410 "Step TP layer {il} cache lengths diverged before decode: \
16411 local={base_len} distributed={}/{}",
16412 distributed.committed_len(),
16413 distributed.staged_len()
16414 )
16415 .into());
16416 }
16417 }
16418 if pos_d.len() != 1 {
16419 return Err(format!(
16420 "rank-local Step decode requires one position, got {}",
16421 pos_d.len()
16422 )
16423 .into());
16424 }
16425
16426 let decode_input = attention
16427 .decode_input
16428 .as_ref()
16429 .ok_or("Step TP decode v2 requires the replicated decode input")?;
16430 let mut decode_input = decode_input
16431 .lock()
16432 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16433
16434 let use_gate_shards = (attention.gate_shards.is_some()
16439 || attention.gate_shards_bf16.is_some())
16440 && crate::tp::step_tp_qkv_fused_enabled()?;
16441 let gate_raw = if use_gate_shards {
16442 None
16443 } else {
16444 let gate_weight = fa
16445 .attn_gate
16446 .as_ref()
16447 .ok_or("step35 layer is missing attn_gate.weight")?;
16448 let gate_raw = e.matmul(gate_weight, h, 1)?;
16449 if gate_raw.len() != heads {
16450 return Err(format!(
16451 "Step TP layer {il} gate output {} != {heads}",
16452 gate_raw.len()
16453 )
16454 .into());
16455 }
16456 Some(gate_raw)
16457 };
16458
16459 let ws_index = tp
16460 .runtime
16461 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16462 let mut ws_guard = tp
16463 .runtime
16464 .decode_v2_workspace()
16465 .lock()
16466 .map_err(|_| "Step TP decode v2 workspace lock is poisoned")?;
16467 let ws = ws_guard
16468 .get_mut(ws_index)
16469 .ok_or("Step TP decode v2 workspace missing after ensure")?;
16470
16471 let mut rope_freqs = Vec::with_capacity(ranks);
16472 for rank in 0..ranks {
16473 let engine = tp
16474 .runtime
16475 .rank_engine(rank)
16476 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16477 rope_freqs.push(if geometry.rope_factors {
16478 self.step35_aux
16479 .as_ref()
16480 .and_then(|aux| aux.rope_freqs(engine))
16481 } else {
16482 None
16483 });
16484 }
16485 let staged_next = base_len + 1;
16492 let t_kv_eff = window
16493 .map(|window| staged_next.min(window))
16494 .unwrap_or(staged_next);
16495 let dcw = crate::tp::step_tp_dcw_enabled()? && use_gate_shards && t_kv_eff >= 96 && {
16496 let (write_row, would_rebase) = cache.tp_kv[il]
16497 .as_ref()
16498 .expect("distributed cache checked above")
16499 .peek_append_ring(1)?;
16500 if !would_rebase {
16501 let base = (base_len - write_row) as i32;
16503 let distributed = cache.tp_kv[il]
16504 .as_mut()
16505 .expect("distributed cache checked above");
16506 for rank in 0..ranks {
16507 let engine = tp.runtime.rank_engine(rank).ok_or_else(|| {
16508 format!("Step TP layer {il} has no engine for rank {rank}")
16509 })?;
16510 let _main = engine.gpu.enter_main()?;
16511 let rank_cache = distributed
16512 .rank_mut(rank)
16513 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16514 if rank_cache.base_d().is_none() {
16515 rank_cache.arm_base_d(engine.htod_i32(&[base])?);
16516 }
16517 }
16518 }
16519 !would_rebase
16520 };
16521 let fuse_rope = dcw
16522 && crate::tp::fuse_rope_append_on()
16523 && head_dim == 128
16524 && cache.tp_kv[il]
16525 .as_ref()
16526 .map(|d| d.kv_dim_k() == d.kv_dim_v() && d.kv_dim_k() == local_kv_heads * head_dim)
16527 .unwrap_or(false);
16528
16529 let tcol_col = crate::tp::take_verify_tcol();
16530 tp.runtime.decode_v2_input_qkv(
16531 ws,
16532 e,
16533 h,
16534 pos_d,
16535 gate_raw.as_ref(),
16536 if !use_gate_shards {
16537 None
16538 } else if let Some(shards) = attention.gate_shards.as_deref() {
16539 Some(crate::tp::StepTpGateShards::F32(shards))
16540 } else {
16541 attention
16542 .gate_shards_bf16
16543 .as_deref()
16544 .map(crate::tp::StepTpGateShards::Bf16)
16545 },
16546 &mut decode_input,
16547 &tp.q,
16548 &tp.k,
16549 &tp.v,
16550 &attention.q_norm,
16551 &attention.k_norm,
16552 head_dim,
16553 geometry.n_rot as usize,
16554 geometry.rope_base,
16555 &rope_freqs,
16556 self.cfg.rms_eps,
16557 fuse_rope,
16558 tcol_col,
16559 )?;
16560
16561 let transaction = cache.tp_kv[il]
16562 .as_mut()
16563 .expect("distributed cache checked above")
16564 .begin_transaction()?;
16565 let append_result = tp.runtime.append_tp_kv_transaction_inner(
16566 cache.tp_kv[il]
16567 .as_mut()
16568 .expect("distributed cache checked above"),
16569 transaction,
16570 &ws.k,
16571 &ws.v_raw,
16572 1,
16573 dcw,
16574 );
16575 if let Err(error) = append_result {
16576 let _ = tp.runtime.rollback_tp_kv_transaction(
16577 cache.tp_kv[il]
16578 .as_mut()
16579 .expect("distributed cache checked above"),
16580 transaction,
16581 );
16582 return Err(error);
16583 }
16584
16585 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16586 let (staged_len, physical, k_tok_bytes_c, v_tok_bytes_c, capacity) = {
16589 let distributed = cache.tp_kv[il]
16590 .as_ref()
16591 .expect("distributed cache checked above");
16592 let staged_len = distributed.staged_len();
16593 let view_start = window
16594 .map(|window| staged_len.saturating_sub(window))
16595 .unwrap_or(0);
16596 (
16597 staged_len,
16598 distributed.physical_range(view_start, staged_len)?,
16599 distributed.k_tok_bytes(),
16600 distributed.v_tok_bytes(),
16601 distributed.physical_capacity(),
16602 )
16603 };
16604 let view_start = window
16605 .map(|window| staged_len.saturating_sub(window))
16606 .unwrap_or(0);
16607 let t_kv = staged_len - view_start;
16608 for rank in 0..ranks {
16609 let engine = tp
16610 .runtime
16611 .rank_engine(rank)
16612 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16613 let _main = engine.gpu.enter_main()?;
16614 if dcw {
16615 {
16619 let distributed_mut = cache.tp_kv[il]
16620 .as_mut()
16621 .expect("distributed cache checked above");
16622 let (kv_dim_k, kv_dim_v) =
16623 (distributed_mut.kv_dim_k(), distributed_mut.kv_dim_v());
16624 let (k_tok_bytes, v_tok_bytes) =
16625 (distributed_mut.k_tok_bytes(), distributed_mut.v_tok_bytes());
16626 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
16627 format!("Step TP layer {il} has no KV cache rank {rank}")
16628 })?;
16629 let (k_plane, v_plane, len_d, base_d) =
16630 rank_cache.planes_and_counters_mut();
16631 if fuse_rope {
16632 let same_dev = engine.ctx().ordinal() == e.ctx().ordinal();
16635 let crate::tp::StepTpDecodeV2Ws {
16636 q_raw,
16637 k_raw,
16638 v_raw,
16639 q,
16640 k,
16641 pos,
16642 pos_stage,
16643 fuse_ctr,
16644 ..
16645 } = &mut *ws;
16646 let pos_ref: &CudaSlice<i32> = if same_dev {
16650 pos_stage
16651 .as_ref()
16652 .ok_or("step TP decode v2 pos stage not armed")?
16653 } else {
16654 &pos[rank]
16655 };
16656 engine.qk_norm_rope_append_inc_dcw(
16657 &q_raw[rank],
16658 &k_raw[rank],
16659 &v_raw[rank],
16660 &attention.q_norm[rank],
16661 &attention.k_norm[rank],
16662 &mut q[rank],
16663 &mut k[rank],
16664 pos_ref,
16665 k_plane,
16666 v_plane,
16667 len_d,
16668 base_d,
16669 &mut fuse_ctr[rank],
16670 kv_dim_k,
16671 kv_dim_v,
16672 k_tok_bytes,
16673 v_tok_bytes,
16674 head_dim,
16675 geometry.n_rot as usize,
16676 local_heads,
16677 local_kv_heads,
16678 self.cfg.rms_eps,
16679 geometry.rope_base,
16680 1.0,
16681 rope_freqs[rank],
16682 )?;
16683 } else {
16684 engine.append_kv_quantized_dcw(
16685 &ws.k[rank],
16686 &ws.v_raw[rank],
16687 k_plane,
16688 v_plane,
16689 len_d,
16690 base_d,
16691 kv_dim_k,
16692 kv_dim_v,
16693 k_tok_bytes,
16694 v_tok_bytes,
16695 )?;
16696 }
16697 if !fuse_rope {
16698 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
16699 format!("Step TP layer {il} has no KV cache rank {rank}")
16700 })?;
16701 engine.inc_i32(rank_cache.len_d_mut())?;
16702 }
16703 }
16704 let distributed = cache.tp_kv[il]
16705 .as_ref()
16706 .expect("distributed cache checked above");
16707 let rank_cache = distributed
16708 .rank(rank)
16709 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16710 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes_c);
16711 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes_c);
16712 {
16713 let crate::tp::StepTpDecodeV2Ws { q, gate, gated, .. } = &mut *ws;
16716 engine.fa_decode_dcw(
16717 &q[rank],
16718 &k_ring,
16719 &v_ring,
16720 &mut gated[rank],
16721 head_dim,
16722 local_heads,
16723 local_kv_heads,
16724 rank_cache.len_d(),
16725 rank_cache.base_d(),
16726 window.unwrap_or(0),
16727 t_kv,
16728 geometry.attention_scale(),
16729 k_tok_bytes_c,
16730 v_tok_bytes_c,
16731 Some(&gate[rank]),
16732 )?;
16733 }
16734 continue;
16735 }
16736 let distributed = cache.tp_kv[il]
16737 .as_ref()
16738 .expect("distributed cache checked above");
16739 let rank_cache = distributed
16740 .rank(rank)
16741 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16742 let k_view = engine.view_u8_range(
16743 rank_cache.k(),
16744 physical.start * k_tok_bytes_c,
16745 physical.end * k_tok_bytes_c,
16746 );
16747 let v_view = engine.view_u8_range(
16748 rank_cache.v(),
16749 physical.start * v_tok_bytes_c,
16750 physical.end * v_tok_bytes_c,
16751 );
16752 engine.fa_decode_kvmod(
16753 &ws.q[rank],
16754 &k_view,
16755 &v_view,
16756 &mut ws.attn_out[rank],
16757 head_dim,
16758 local_heads,
16759 local_kv_heads,
16760 t_kv,
16761 geometry.attention_scale(),
16762 k_tok_bytes_c,
16763 v_tok_bytes_c,
16764 false,
16765 )?;
16766 engine.attn_head_gate(
16767 &ws.attn_out[rank],
16768 &ws.gate[rank],
16769 &mut ws.gated[rank],
16770 None,
16771 head_dim,
16772 local_heads,
16773 1,
16774 )?;
16775 }
16776
16777 let output = if let Some(col) = crate::tp::take_tcol_oproj_defer() {
16784 if tp.runtime.decode_v2_oproj_tcol_eligible(ws, &tp.o) {
16785 tp.runtime.decode_v2_stash_gated(ws, e, col)?;
16786 crate::tp::set_tcol_oproj_stashed();
16787 e.uninit(ws.o_out)?
16788 } else {
16789 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
16790 }
16791 } else {
16792 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
16793 };
16794
16795 let local = cache.kv[il]
16799 .as_mut()
16800 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16801 if local.len != base_len || base_len + 1 > max_ctx {
16802 return Err(format!(
16803 "Step TP layer {il} local cache changed during decode: \
16804 len={} base={base_len} max={max_ctx}",
16805 local.len
16806 )
16807 .into());
16808 }
16809 if crate::tp::no_local_shadow_on() {
16810 local.len = base_len + 1;
16813 if !crate::tp::len_mirror_lazy_on() {
16817 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16818 }
16819 } else {
16820 let retain_from = window
16821 .map(|window| {
16822 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16823 let rollback_retain =
16824 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16825 staged_retain.min(rollback_retain)
16826 })
16827 .unwrap_or(0);
16828 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16829 e.append_kv_quantized(
16830 &ws.k_shadow,
16831 &ws.v_shadow,
16832 &mut local.k,
16833 &mut local.v,
16834 write_row,
16835 local.kv_dim_k,
16836 local.kv_dim_v,
16837 local.k_tok_bytes,
16838 local.v_tok_bytes,
16839 false,
16840 )?;
16841 local.len = base_len + 1;
16842 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16843 }
16844 Ok(output)
16845 })();
16846
16847 let output = match staged {
16848 Ok(output) => output,
16849 Err(error) => {
16850 let _ = tp.runtime.rollback_tp_kv_transaction(
16851 cache.tp_kv[il]
16852 .as_mut()
16853 .expect("distributed cache checked above"),
16854 transaction,
16855 );
16856 if let Some(local) = cache.kv[il].as_mut() {
16857 local.len = base_len;
16858 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16859 }
16860 return Err(error);
16861 }
16862 };
16863 let lazy_commit = fuse_rope && crate::tp::len_mirror_lazy_on();
16868 if lazy_commit {
16869 if let Err(error) = tp.runtime.commit_tp_kv_transaction_external(
16870 cache.tp_kv[il]
16871 .as_mut()
16872 .expect("distributed cache checked above"),
16873 transaction,
16874 1,
16875 ) {
16876 let _ = tp.runtime.rollback_tp_kv_transaction(
16877 cache.tp_kv[il]
16878 .as_mut()
16879 .expect("distributed cache checked above"),
16880 transaction,
16881 );
16882 let local = cache.kv[il].as_mut().expect("local cache checked above");
16883 local.len = base_len;
16884 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16885 return Err(error);
16886 }
16887 } else if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16888 cache.tp_kv[il]
16889 .as_mut()
16890 .expect("distributed cache checked above"),
16891 transaction,
16892 1,
16893 ) {
16894 let _ = tp.runtime.rollback_tp_kv_transaction(
16895 cache.tp_kv[il]
16896 .as_mut()
16897 .expect("distributed cache checked above"),
16898 transaction,
16899 );
16900 let local = cache.kv[il].as_mut().expect("local cache checked above");
16901 local.len = base_len;
16902 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16903 return Err(error);
16904 }
16905
16906 let committed = cache.tp_kv[il]
16907 .as_ref()
16908 .expect("distributed cache checked above")
16909 .committed_len();
16910 let local_len = cache.kv[il]
16911 .as_ref()
16912 .expect("local cache checked above")
16913 .len;
16914 if committed != local_len {
16915 return Err(format!(
16916 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16917 )
16918 .into());
16919 }
16920 static V2_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
16921 if !V2_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
16922 eprintln!(
16923 "[step-tp-attn-v2] execute layer={} devices={:?} tokens=1 driver=v2 \
16924 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16925 kv_cache_distributed=true kv_cache_hydrated={hydrated} \
16926 attention_tensor_parallel=true attention_scope={} \
16927 input_path=root-device-replicated gate_tensor_parallel=false \
16928 gate_shards=device-staged o_tensor_parallel=true o_reduce=root-device \
16929 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16930 bulk_p2p={} workspace=persistent ordering=evented output=e-device \
16931 performance_claim=false (logged once; every decode layer runs this driver)",
16932 tp.layer,
16933 tp.devices,
16934 if window.is_some() {
16935 "rank-local-swa-ring"
16936 } else {
16937 "rank-local-global"
16938 },
16939 tp.runtime.transport_label(),
16940 tp.runtime.bulk_p2p(),
16941 );
16942 }
16943 Ok(output)
16944 }
16945
16946 #[allow(clippy::too_many_arguments)]
16956 pub(crate) fn step35_decode_attn(
16957 &self,
16958 e: &Engine,
16959 fa: &FullAttnLayer,
16960 il: usize,
16961 h: &CudaSlice<f32>,
16962 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
16963 pos_d: &CudaSlice<i32>,
16964 cache: &mut Cache,
16965 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16966 if fa
16967 .step_tp_qkv
16968 .as_ref()
16969 .is_some_and(|tp| tp.attention.is_some())
16970 {
16971 if pre_q.is_some() {
16972 return Err(
16973 "rank-local Step attention preserves BF16 activations and refuses the q8_1 \
16974 pre-quantized decode path"
16975 .into(),
16976 );
16977 }
16978 return self.step35_tp_decode_attn_resident(e, fa, il, h, pos_d, cache);
16979 }
16980
16981 let geometry = self.step35_geom(il);
16982 let hd = geometry.head_dim_k as usize;
16983 let nkv = geometry.n_head_kv as usize;
16984 let nh = geometry.n_head as usize;
16985 let rbase = geometry.rope_base;
16986 let scale = geometry.attention_scale();
16987 let swa = geometry.window.is_some();
16988 let eps = self.cfg.rms_eps;
16989 let win = geometry.window.unwrap_or(0) as usize;
16990 let n_rot = geometry.n_rot as usize;
16991 let n_embd = self.cfg.n_embd as usize;
16992 let gw = fa
16993 .attn_gate
16994 .as_ref()
16995 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
16996
16997 let tp_qkv = if fa.step_tp_qkv.is_some() {
16998 if pre_q.is_some() {
16999 return Err(
17000 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
17001 pre-quantized decode path"
17002 .into(),
17003 );
17004 }
17005 self.step35_tp_qkv(e, fa, h, 1)?
17006 } else {
17007 None
17008 };
17009
17010 let (q0, k0, v0, gt) = match tp_qkv {
17011 Some(mut g3) => {
17012 let v = g3.pop().unwrap();
17013 let k = g3.pop().unwrap();
17014 let q = g3.pop().unwrap();
17015 let gt = e.matmul(gw, h, 1)?;
17016 (q, k, v, gt)
17017 }
17018 None => match pre_q {
17019 Some((hq, hdq)) => {
17020 debug_assert!(
17021 e.uses_q8_1_fast(gw),
17022 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
17023 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
17024 );
17025 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
17026 Some(t3) => t3,
17027 None => (
17028 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
17029 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
17030 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
17031 ),
17032 };
17033 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
17034 (a, b, c, gt)
17035 }
17036 None => {
17037 if e.uses_q8_1_fast(&fa.wq)
17038 && e.uses_q8_1_fast(&fa.wk)
17039 && e.uses_q8_1_fast(&fa.wv)
17040 && e.uses_q8_1_fast(gw)
17041 {
17042 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
17043 let (a, b, c) =
17044 match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
17045 Some(t3) => t3,
17046 None => (
17047 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
17048 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
17049 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
17050 ),
17051 };
17052 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
17053 (a, b, c, gt)
17054 } else {
17055 (
17056 e.matmul(&fa.wq, h, 1)?,
17057 e.matmul(&fa.wk, h, 1)?,
17058 e.matmul(&fa.wv, h, 1)?,
17059 e.matmul(gw, h, 1)?,
17060 )
17061 }
17062 }
17063 },
17064 };
17065
17066 let mut q = e.uninit(nh * hd)?;
17067 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
17068 let mut k = e.uninit(nkv * hd)?;
17069 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
17070 let ff = if swa {
17071 None
17072 } else {
17073 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
17074 };
17075 #[cfg(debug_assertions)]
17076 if let Some(ff) = ff {
17077 crate::debug_assert_tensor_stream_device(
17078 ff,
17079 &e.stream(),
17080 "step35_decode_attn.rope_freqs",
17081 );
17082 }
17083 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
17084
17085 if std::env::var("MEMRA_NOFA").is_ok() {
17086 return Err(
17087 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
17088 cache; unset MEMRA_NOFA to use fa_decode"
17089 .into(),
17090 );
17091 }
17092 let kvl = cache.kv[il].as_mut().unwrap();
17093 let next_len = kvl.len + 1;
17094 let (off, t_kv) = if swa && next_len > win {
17095 (next_len - win, win)
17096 } else {
17097 (0, next_len)
17098 };
17099 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
17100 e.append_kv_quantized(
17101 &k,
17102 &v0,
17103 &mut kvl.k,
17104 &mut kvl.v,
17105 write_row,
17106 kvl.kv_dim_k,
17107 kvl.kv_dim_v,
17108 kvl.k_tok_bytes,
17109 kvl.v_tok_bytes,
17110 crate::Engine::kv_fp8_on(),
17111 )?;
17112 kvl.len = next_len;
17113 let physical = kvl.physical_rows(off, off + t_kv)?;
17114 let k_view = e.view_u8_range(
17115 &kvl.k,
17116 physical.start * kvl.k_tok_bytes,
17117 physical.end * kvl.k_tok_bytes,
17118 );
17119 let v_view = e.view_u8_range(
17120 &kvl.v,
17121 physical.start * kvl.v_tok_bytes,
17122 physical.end * kvl.v_tok_bytes,
17123 );
17124 let mut attn = e.uninit(nh * hd)?;
17125 e.fa_decode_kvmod(
17126 &q,
17127 &k_view,
17128 &v_view,
17129 &mut attn,
17130 hd,
17131 nh,
17132 nkv,
17133 t_kv,
17134 scale,
17135 kvl.k_tok_bytes,
17136 kvl.v_tok_bytes,
17137 crate::Engine::kv_fp8_on(),
17138 )?;
17139
17140 let mut ag = e.uninit(nh * hd)?;
17141 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
17142 self.step35_o(e, fa, &ag, 1)
17143 }
17144}
17145
17146impl HybridModel {
17155 pub fn is_gemma4_e4b(&self) -> bool {
17156 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
17157 }
17158
17159 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
17163 let g = self.cfg.gemma4.as_ref().unwrap();
17164 let swa = g.swa_pattern[il];
17165 let hd = if swa {
17166 g.key_length_swa
17167 } else {
17168 g.key_length_global
17169 } as usize;
17170 let Mixer::Full(fa) = &self.layers[il].mixer else {
17171 panic!("e4b layer {il} not full-attn")
17172 };
17173 let nh = fa.wq.out_features() / hd;
17174 let nkv = fa.wk.out_features() / hd;
17175 (
17176 hd,
17177 nkv,
17178 nh,
17179 if swa {
17180 g.rope_base_swa
17181 } else {
17182 g.rope_base_global
17183 },
17184 1.0,
17185 swa,
17186 )
17187 }
17188
17189 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
17191 self.layers[il]
17192 .gemma4
17193 .as_ref()
17194 .and_then(|b| b.e4b.as_ref())
17195 .and_then(|e4| e4.kv_share.map(|t| t as usize))
17196 }
17197
17198 fn gemma4_e4b_inp_pl(
17203 &self,
17204 e: &Engine,
17205 tokens: &[u32],
17206 x_scaled: &CudaSlice<f32>,
17207 t: usize,
17208 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17209 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
17210 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
17211 }
17212
17213 fn gemma4_e4b_inp_pl_dev(
17215 &self,
17216 e: &Engine,
17217 tok_d: &CudaSlice<u32>,
17218 x_scaled: &CudaSlice<f32>,
17219 t: usize,
17220 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17221 let aux = self.gemma4_aux.as_ref().unwrap();
17222 let m = aux.e4b.as_ref().unwrap();
17223 let n_embd = self.cfg.n_embd as usize;
17224 let n_layer = self.layers.len();
17225 let width = m.n_epl * n_layer;
17226 let tbl = m.tok_tbl_gpu.get_or_init(|| {
17227 e.upload_u8(&m.tok_embd_bytes)
17228 .expect("e4b per-layer token table upload")
17229 });
17230 let mut a =
17231 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
17232 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
17233 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
17234 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
17235 let mut pn = e.uninit(t * width)?;
17236 e.rms_norm(
17237 &p,
17238 m.proj_norm.float_data(),
17239 &mut pn,
17240 m.n_epl,
17241 t * n_layer,
17242 self.cfg.rms_eps,
17243 )?;
17244 let mut out = e.uninit(t * width)?;
17245 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
17246 Ok(out)
17247 }
17248
17249 #[allow(clippy::too_many_arguments)]
17254 fn gemma4_e4b_attn(
17255 &self,
17256 e: &Engine,
17257 il: usize,
17258 hq: &CudaSlice<i8>,
17259 hdq: &CudaSlice<f32>,
17260 pos_d: &CudaSlice<i32>,
17261 t: usize,
17262 cache: &mut Cache,
17263 dc_bucket: Option<usize>,
17264 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17265 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
17266 let eps = self.cfg.rms_eps;
17267 let aux = self.gemma4_aux.as_ref().unwrap();
17268 let ones = aux.ones(e);
17269 #[cfg(debug_assertions)]
17270 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
17271 let Mixer::Full(fa) = &self.layers[il].mixer else {
17272 unreachable!()
17273 };
17274 let h0 = e.zeros(0)?;
17278 let h = &h0;
17279
17280 let ff = if swa {
17281 None
17282 } else {
17283 Some(
17284 aux.rope_freqs(e)
17285 .expect("e4b global rope needs rope_freqs.weight"),
17286 )
17287 };
17288 #[cfg(debug_assertions)]
17289 if let Some(ff) = ff {
17290 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
17291 }
17292 let share = self.gemma4_e4b_kv_target(il);
17293 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
17295 let mut q;
17296 if let Some(_tgt) = share {
17297 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
17298 q = e.uninit(t * nh * hd)?;
17299 let mut kdummy = e.uninit(1)?;
17302 let mut vdummy = e.uninit(1)?;
17303 e.rms_norm_qkv_rope(
17304 &q0,
17305 &q0,
17306 &q0,
17307 fa.q_norm.float_data(),
17308 fa.q_norm.float_data(),
17309 ones,
17310 &mut q,
17311 &mut kdummy,
17312 &mut vdummy,
17313 hd,
17314 self.gemma4_rope_dims(il),
17315 nh * t,
17316 0,
17317 pos_d,
17318 nh,
17319 1,
17320 base,
17321 1.0,
17322 ff,
17323 eps,
17324 )?;
17325 } else {
17326 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
17330 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
17331 q = e.uninit(t * nh * hd)?;
17332 let mut k = e.uninit(t * nkv * hd)?;
17333 let mut v = e.uninit(t * nkv * hd)?;
17334 if t == 1 && cat.is_some() {
17335 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
17336 e.rms_norm_qkv_rope_cat(
17337 &qkv0,
17338 fa.q_norm.float_data(),
17339 fa.k_norm.float_data(),
17340 ones,
17341 &mut q,
17342 &mut k,
17343 &mut v,
17344 hd,
17345 self.gemma4_rope_dims(il),
17346 nh,
17347 nkv,
17348 pos_d,
17349 nh,
17350 nkv,
17351 base,
17352 1.0,
17353 ff,
17354 eps,
17355 )?;
17356 } else {
17357 let (q0, k0, v0) = match if t == 1 {
17358 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
17359 } else {
17360 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17363 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
17364 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
17365 } else {
17366 None
17367 }
17368 } {
17369 Some(triple) => triple,
17370 None => (
17371 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
17372 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
17373 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
17374 ), };
17376 e.rms_norm_qkv_rope(
17379 &q0,
17380 &k0,
17381 &v0,
17382 fa.q_norm.float_data(),
17383 fa.k_norm.float_data(),
17384 ones,
17385 &mut q,
17386 &mut k,
17387 &mut v,
17388 hd,
17389 self.gemma4_rope_dims(il),
17390 nh * t,
17391 nkv * t,
17392 pos_d,
17393 nh,
17394 nkv,
17395 base,
17396 1.0,
17397 ff,
17398 eps,
17399 )?;
17400 }
17401 let kvl = cache.kv[il].as_mut().unwrap();
17402 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17406 if dc_bucket.is_some() {
17407 debug_assert!(t == 1);
17412 e.append_kv_quantized_row_dc_inc(
17414 &k,
17415 &v,
17416 &mut kvl.k,
17417 &mut kvl.v,
17418 &mut kvl.len_d,
17419 kvl.kv_dim_k,
17420 kvl.kv_dim_v,
17421 kvl.k_tok_bytes,
17422 kvl.v_tok_bytes,
17423 cls,
17424 )?;
17425 } else {
17426 e.append_kv_quantized_rows(
17427 &k,
17428 &v,
17429 &mut kvl.k,
17430 &mut kvl.v,
17431 kvl.len,
17432 t,
17433 kvl.kv_dim_k,
17434 kvl.kv_dim_v,
17435 kvl.k_tok_bytes,
17436 kvl.v_tok_bytes,
17437 cls,
17438 )?;
17439 kvl.len += t;
17440 }
17441 kv_f32 = Some((k, v));
17442 }
17443 let kvl_idx = share.unwrap_or(il);
17446 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
17447 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
17449 let mut attn = e.uninit(t * nh * hd)?;
17450 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
17462 if let Some((kf, vf)) = &kv_f32 {
17463 if hd == 256 && t <= win {
17464 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17465 return Ok(e.matmul(&fa.wo, &attn, t)?);
17466 }
17467 if hd == 256 && swa && t > win {
17468 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
17469 return Ok(e.matmul(&fa.wo, &attn, t)?);
17470 }
17471 if hd == 512 && !swa {
17472 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17473 return Ok(e.matmul(&fa.wo, &attn, t)?);
17474 }
17475 } else if share.is_some() {
17476 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17477 let k_view = e.view_u8(&kvl.k, kvl.k.len());
17478 let v_view = e.view_u8(&kvl.v, kvl.v.len());
17479 if hd == 256 && (!swa || t <= win) {
17480 e.fa_prefill_view(
17482 &q,
17483 &k_view,
17484 &v_view,
17485 &mut attn,
17486 hd,
17487 nh,
17488 nkv,
17489 t,
17490 t,
17491 scale,
17492 true,
17493 kvl.k_tok_bytes,
17494 kvl.v_tok_bytes,
17495 g,
17496 )?;
17497 return Ok(e.matmul(&fa.wo, &attn, t)?);
17498 }
17499 let kv_dim = nkv * hd;
17502 let mut kf = e.uninit(t * kv_dim)?;
17503 let mut vf = e.uninit(t * kv_dim)?;
17504 e.fa_dequant_kv_view_f32(
17505 &k_view,
17506 &v_view,
17507 &mut kf,
17508 &mut vf,
17509 kv_dim,
17510 kv_dim,
17511 t,
17512 kvl.k_tok_bytes,
17513 kvl.v_tok_bytes,
17514 g,
17515 )?;
17516 if hd == 512 {
17517 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17518 } else {
17519 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
17520 }
17521 return Ok(e.matmul(&fa.wo, &attn, t)?);
17522 }
17523 }
17524 if let Some(bucket) = dc_bucket {
17525 assert!(t == 1);
17530 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
17536 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
17537 } else {
17538 bucket
17539 };
17540 let k_view = e.view_u8(&kvl.k, kvl.k.len());
17541 let v_view = e.view_u8(&kvl.v, kvl.v.len());
17542 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17543 if crate::Engine::wpf_level() >= 1 {
17551 e.prefetch_weight_l2(&fa.wo)?;
17552 }
17553 if e.uses_q8_1_fast(&fa.wo) {
17556 let mut oq = e.alloc_i8_uninit(nh * hd)?;
17557 let mut od = e.zeros(nh * hd / 32)?;
17558 e.fa_decode_dc_q8(
17559 &q,
17560 &k_view,
17561 &v_view,
17562 &mut attn,
17563 hd,
17564 nh,
17565 nkv,
17566 &kvl.len_d,
17567 bucket,
17568 scale,
17569 kvl.k_tok_bytes,
17570 kvl.v_tok_bytes,
17571 g,
17572 Some((&mut oq, &mut od)),
17573 )?;
17574 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
17575 }
17576 e.fa_decode_dc(
17577 &q,
17578 &k_view,
17579 &v_view,
17580 &mut attn,
17581 hd,
17582 nh,
17583 nkv,
17584 &kvl.len_d,
17585 bucket,
17586 scale,
17587 kvl.k_tok_bytes,
17588 kvl.v_tok_bytes,
17589 g,
17590 )?;
17591 return Ok(e.matmul(&fa.wo, &attn, t)?);
17592 }
17593 for i in 0..t {
17594 let avail = base_len + i + 1;
17595 let (off_tok, t_kv) = if swa && avail > win {
17596 (avail - win, win)
17597 } else {
17598 (0, avail)
17599 };
17600 let k_view = e.view_u8_range(
17601 &kvl.k,
17602 off_tok * kvl.k_tok_bytes,
17603 (off_tok + t_kv) * kvl.k_tok_bytes,
17604 );
17605 let v_view = e.view_u8_range(
17606 &kvl.v,
17607 off_tok * kvl.v_tok_bytes,
17608 (off_tok + t_kv) * kvl.v_tok_bytes,
17609 );
17610 let qv = e.view(&q, t * nh * hd);
17611 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
17612 let mut q_one = e.uninit(nh * hd)?;
17613 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
17614 let mut a_one = e.uninit(nh * hd)?;
17615 e.fa_decode_kvmod(
17619 &q_one,
17620 &k_view,
17621 &v_view,
17622 &mut a_one,
17623 hd,
17624 nh,
17625 nkv,
17626 t_kv,
17627 scale,
17628 kvl.k_tok_bytes,
17629 kvl.v_tok_bytes,
17630 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
17631 )?;
17632 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
17633 }
17634 Ok(e.matmul(&fa.wo, &attn, t)?)
17635 }
17636
17637 fn gemma4_e4b_trunk(
17642 &self,
17643 e: &Engine,
17644 tokens: &[u32],
17645 pos0: usize,
17646 cache: &mut Cache,
17647 head_last: bool,
17648 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17649 let n_embd = self.cfg.n_embd as usize;
17650 let t = tokens.len();
17651 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
17652 let pos_d = e.htod_i32(&pos)?;
17653 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
17654 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
17655 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
17656 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
17657 }
17658
17659 fn gemma4_e4b_trunk_core(
17663 &self,
17664 e: &Engine,
17665 x_in: CudaSlice<f32>,
17666 inp_pl: CudaSlice<f32>,
17667 pos_d: &CudaSlice<i32>,
17668 t: usize,
17669 cache: &mut Cache,
17670 dc_bucket: Option<usize>,
17671 cap_logits: bool,
17672 head_last: bool,
17673 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17674 let n_embd = self.cfg.n_embd as usize;
17675 let eps = self.cfg.rms_eps;
17676 let n_layer = self.layers.len();
17677 let mut x = x_in;
17678 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
17679 let n_epl = aux_e4b.n_epl;
17680
17681 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
17687 for il in 0..n_layer {
17688 let layer = &self.layers[il];
17689 let (hq, hdq) = match h_carry.take() {
17690 Some(p) => p,
17691 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
17692 };
17693 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
17694 let bits = layer.gemma4.as_ref().unwrap();
17697 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
17698 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
17709 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
17710 e,
17711 layer,
17712 &o,
17713 &x,
17714 t,
17715 Some(layer.post_attn_norm.float_data()),
17716 fuse_exit,
17717 )?;
17718 let mut resid = e.uninit(t * n_embd)?;
17719 let g = if fuse_exit {
17725 let (rq, rd) = e.rms_pre_add_q8_1(
17727 &sn,
17728 bits.post_ffw_norm.float_data(),
17729 &attn_out,
17730 &mut resid,
17731 n_embd,
17732 t,
17733 self.cfg.rms_eps,
17734 )?;
17735 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
17736 } else {
17737 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
17738 e.matmul(&e4b.inp_gate, &resid, t)?
17739 };
17740 let mut act = e.uninit(t * n_epl)?;
17741 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
17742 let ipv = e.view(&inp_pl, n_epl * n_layer);
17743 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
17744 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
17745 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
17746 } else {
17747 let mut inp_this = e.uninit(t * n_epl)?;
17748 e.copy_rows_strided(
17749 &inp_pl,
17750 &mut inp_this,
17751 n_epl,
17752 t,
17753 n_epl * n_layer,
17754 il * n_epl,
17755 )?;
17756 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
17757 e.matmul(&e4b.proj, &act, t)?
17758 };
17759 let next_norm = if il + 1 < n_layer {
17762 self.layers[il + 1].attn_norm.float_data()
17763 } else {
17764 self.output_norm.float_data()
17765 };
17766 let mut xn = e.uninit(t * n_embd)?;
17767 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
17768 &y,
17769 e4b.post_norm.float_data(),
17770 &resid,
17771 bits.layer_scale,
17772 next_norm,
17773 &mut xn,
17774 n_embd,
17775 t,
17776 eps,
17777 )?;
17778 h_carry = Some(pair);
17779 x = xn;
17780 }
17781 let (oq, odq) = h_carry.take().unwrap();
17785 let h0 = e.zeros(0)?;
17786 let hm = if head_last { 1 } else { t };
17787 let (hq, hd) = if head_last && t > 1 {
17788 let mut q1 = e.uninit_i8(n_embd)?;
17789 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
17790 let nb = n_embd / 32;
17791 let mut d1 = e.uninit(nb)?;
17792 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
17793 (q1, d1)
17794 } else {
17795 (oq, odq)
17796 };
17797 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
17798 if cap_logits {
17802 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
17803 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
17804 }
17805 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
17807 }
17808
17809 pub fn gemma4_e4b_decode_step_t_am_dev(
17816 &self,
17817 e: &Engine,
17818 tok_d: &CudaSlice<u32>,
17819 t: usize,
17820 pos0: usize,
17821 cache: &mut Cache,
17822 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17823 let n_embd = self.cfg.n_embd as usize;
17824 let eps = self.cfg.rms_eps;
17825 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
17826 let pos_d = e.htod_i32(&pos)?;
17827 let embd_gpu = self
17828 .embd_gpu
17829 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
17830 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
17831 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
17832 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
17833 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
17834 let (ld, xp) =
17835 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
17836 let n_vocab = self.output.out_features();
17839 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
17840 for i in 0..t {
17841 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
17842 }
17843 let mut hn = e.uninit(t * n_embd)?;
17844 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
17845 cache.pos += t;
17846 Ok((vam, hn))
17847 }
17848
17849 pub(crate) fn gemma4_e4b_decode_step_t_h(
17852 &self,
17853 e: &Engine,
17854 tokens: &[u32],
17855 pos0: usize,
17856 cache: &mut Cache,
17857 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17858 let n_embd = self.cfg.n_embd as usize;
17859 let eps = self.cfg.rms_eps;
17860 let t = tokens.len();
17861 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
17862 let mut hn = e.uninit(t * n_embd)?;
17863 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
17864 cache.pos += t;
17865 Ok((e.dtoh(&ld)?, hn))
17866 }
17867
17868 pub fn gemma4_e4b_decode_step_dcg(
17874 &self,
17875 e: &Engine,
17876 token_d: &mut CudaSlice<u32>,
17877 pos_d: &mut CudaSlice<i32>,
17878 embd_gpu: &CudaSlice<u8>,
17879 embd_qt: i32,
17880 embd_rb: usize,
17881 cache: &mut Cache,
17882 n_vocab: usize,
17883 bucket: usize,
17884 ) -> Result<(), Box<dyn std::error::Error>> {
17885 let n_embd = self.cfg.n_embd as usize;
17886 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
17887 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
17888 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
17889 let (ld, _x) =
17890 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
17891 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
17892 e.inc_seqlen(pos_d)?;
17893 Ok(())
17894 }
17895
17896 #[allow(clippy::too_many_arguments)]
17904 pub fn gemma4_e4b_decode_step_dc(
17905 &self,
17906 e: &Engine,
17907 token_d: &CudaSlice<u32>,
17908 pos_d: &mut CudaSlice<i32>,
17909 embd_gpu: &CudaSlice<u8>,
17910 embd_qt: i32,
17911 embd_rb: usize,
17912 cache: &mut Cache,
17913 n_vocab: usize,
17914 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
17915 let n_embd = self.cfg.n_embd as usize;
17916 let eps = self.cfg.rms_eps;
17917 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
17918 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
17919 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
17920 let (ld, _x) =
17921 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
17922 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
17923 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
17924 e.inc_seqlen(pos_d)?;
17925 cache.pos += 1;
17926 let _ = eps;
17927 Ok(tok_out)
17928 }
17929
17930 pub(crate) fn gemma4_e4b_decode_step_h(
17933 &self,
17934 e: &Engine,
17935 token: u32,
17936 cache: &mut Cache,
17937 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17938 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
17939 let logits = e.dtoh(&ld)?;
17940 cache.pos += 1;
17941 Ok((logits, x))
17942 }
17943
17944 pub(crate) fn gemma4_e4b_prime(
17948 &self,
17949 e: &Engine,
17950 tokens: &[u32],
17951 cache: &mut Cache,
17952 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17953 if cache.pos != 0 {
17956 return Err(
17957 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
17958 call or decode tokenwise"
17959 .into(),
17960 );
17961 }
17962 let n_embd = self.cfg.n_embd as usize;
17963 let t = tokens.len();
17964 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
17965 cache.pos += t;
17966 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
17968 let row = xv.slice((t - 1) * n_embd..t * n_embd);
17969 let mut h_seed = e.uninit(n_embd)?;
17970 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
17971 Ok((last, h_seed, x))
17972 }
17973
17974 pub(crate) fn gemma4_e4b_forward(
17976 &self,
17977 e: &Engine,
17978 tokens: &[u32],
17979 last_only: bool,
17980 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
17981 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
17982 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
17983 Ok(e.dtoh(&ld)?) }
17985}
17986
17987#[cfg(test)]
17988mod prime_chunk_schedule_tests {
17989 use super::{
17990 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, active_matrix_values, dynamic_prime_chunk_ranges,
17991 fixed_prime_chunk_ranges, fixed_prime_chunk_ranges_for_ring, parse_step_ep_grouped_prefill,
17992 parse_step_tp_prefill, step_grouped_decode_shape, step_grouped_prefill_shape,
17993 step_tp_prefill_shape, validate_step_prime_batch_modes,
17994 };
17995
17996 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
17997 ranges.iter().map(|(start, end)| end - start).collect()
17998 }
17999
18000 fn auto_chunk(t: usize) -> usize {
18001 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
18002 }
18003
18004 #[test]
18005 fn active_matrix_prefix_scopes_reused_prime_slabs() {
18006 assert_eq!(
18007 active_matrix_values(40 * 4096, 29, 4096, "activation").unwrap(),
18008 29 * 4096
18009 );
18010 assert_eq!(
18011 active_matrix_values(29 * 4096, 29, 4096, "activation").unwrap(),
18012 29 * 4096
18013 );
18014 assert_eq!(
18015 active_matrix_values(29 * 4096, 24, 4096, "activation").unwrap(),
18016 24 * 4096
18017 );
18018 assert!(active_matrix_values(28 * 4096, 29, 4096, "activation").is_err());
18019 assert!(active_matrix_values(usize::MAX, usize::MAX, 2, "activation").is_err());
18020 }
18021
18022 #[test]
18023 fn step_tp_prefill_batch_refuses_before_scheduler_fallback() {
18024 assert!(validate_step_prime_batch_modes(false, false).is_ok());
18025
18026 let grouped_without_tp = validate_step_prime_batch_modes(false, true).unwrap_err();
18027 assert!(grouped_without_tp.contains("requires MEMRA_STEP_TP_PREFILL=1"));
18028
18029 for grouped in [false, true] {
18030 let err = validate_step_prime_batch_modes(true, grouped).unwrap_err();
18031 assert!(err.contains("did not clear the live-server performance gate"));
18032 assert!(err.contains("per-session grouped prefill"));
18033 }
18034 }
18035
18036 #[test]
18037 fn step_grouped_path_is_eager_single_token_only() {
18038 assert!(step_grouped_decode_shape(false, 1));
18039 assert!(!step_grouped_decode_shape(true, 1));
18040 assert!(!step_grouped_decode_shape(false, 2));
18041 assert!(!step_grouped_decode_shape(true, 2));
18042 }
18043
18044 #[test]
18045 fn step_grouped_prefill_door_is_strict_and_capacity_bounded() {
18046 assert!(!parse_step_ep_grouped_prefill(None).unwrap());
18047 assert!(!parse_step_ep_grouped_prefill(Some("")).unwrap());
18048 assert!(!parse_step_ep_grouped_prefill(Some("0")).unwrap());
18049 assert!(parse_step_ep_grouped_prefill(Some("1")).unwrap());
18050 assert!(parse_step_ep_grouped_prefill(Some("true")).is_err());
18051 assert!(parse_step_ep_grouped_prefill(Some("2")).is_err());
18052
18053 assert!(step_grouped_prefill_shape(true, true, PRIME_MIN_T));
18054 assert!(step_grouped_prefill_shape(
18055 true,
18056 true,
18057 crate::cache::PRIME_CHUNK_MAX_TOKENS,
18058 ));
18059 assert!(!step_grouped_prefill_shape(true, true, PRIME_MIN_T - 1,));
18060 assert!(!step_grouped_prefill_shape(
18061 true,
18062 true,
18063 crate::cache::PRIME_CHUNK_MAX_TOKENS + 1,
18064 ));
18065 assert!(!step_grouped_prefill_shape(false, true, PRIME_MIN_T));
18066 assert!(!step_grouped_prefill_shape(true, false, PRIME_MIN_T));
18067 }
18068
18069 #[test]
18070 fn step_tp_prefill_door_is_strict_and_default_off() {
18071 assert!(!parse_step_tp_prefill(None).unwrap());
18072 assert!(!parse_step_tp_prefill(Some("")).unwrap());
18073 assert!(!parse_step_tp_prefill(Some("0")).unwrap());
18074 assert!(parse_step_tp_prefill(Some("1")).unwrap());
18075 assert!(parse_step_tp_prefill(Some("true")).is_err());
18076 assert!(parse_step_tp_prefill(Some("2")).is_err());
18077 }
18078
18079 #[test]
18080 fn step_tp_prefill_requires_the_qualified_tp4_shape() {
18081 assert!(step_tp_prefill_shape(
18082 true,
18083 PRIME_MIN_T,
18084 4,
18085 true,
18086 true,
18087 false,
18088 ));
18089 assert!(!step_tp_prefill_shape(
18090 false,
18091 PRIME_MIN_T,
18092 4,
18093 true,
18094 true,
18095 false,
18096 ));
18097 assert!(!step_tp_prefill_shape(
18098 true,
18099 PRIME_MIN_T - 1,
18100 4,
18101 true,
18102 true,
18103 false,
18104 ));
18105 assert!(!step_tp_prefill_shape(
18106 true,
18107 PRIME_MIN_T,
18108 2,
18109 true,
18110 true,
18111 false,
18112 ));
18113 assert!(!step_tp_prefill_shape(
18114 true,
18115 PRIME_MIN_T,
18116 4,
18117 false,
18118 true,
18119 false,
18120 ));
18121 assert!(!step_tp_prefill_shape(
18122 true,
18123 PRIME_MIN_T,
18124 4,
18125 true,
18126 false,
18127 false,
18128 ));
18129 assert!(!step_tp_prefill_shape(
18130 true,
18131 PRIME_MIN_T,
18132 4,
18133 true,
18134 true,
18135 true,
18136 ));
18137 }
18138
18139 #[test]
18140 fn fixed_schedule_retains_measured_geometry() {
18141 assert_eq!(
18142 sizes(&fixed_prime_chunk_ranges(461, 128)),
18143 vec![128, 128, 128, 77]
18144 );
18145 assert_eq!(
18146 sizes(&fixed_prime_chunk_ranges(1833, 230)),
18147 vec![230, 230, 230, 230, 230, 230, 230, 223]
18148 );
18149 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
18150 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
18151 assert_eq!(capped, vec![4096, 4088, 16]);
18152 assert!(capped.iter().all(|&rows| rows <= 4096));
18153 assert_eq!(
18154 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
18155 vec![4100],
18156 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
18157 );
18158 }
18159
18160 #[test]
18161 fn dynamic_schedule_matches_registered_shapes() {
18162 let cases = [
18163 (461, vec![64, 141, 132, 124]),
18164 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
18165 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
18166 ];
18167 for (t, expected) in cases {
18168 let chunk = auto_chunk(t);
18169 let fixed = fixed_prime_chunk_ranges(t, chunk);
18170 assert_eq!(
18171 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
18172 expected
18173 );
18174 }
18175 }
18176
18177 #[test]
18178 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
18179 for t in 256..=8192 {
18180 let chunk = auto_chunk(t);
18181 let fixed = fixed_prime_chunk_ranges(t, chunk);
18182 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
18183 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
18184 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
18185 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
18186 for pair in dynamic.windows(2) {
18187 assert_eq!(pair[0].1, pair[1].0, "T={t}");
18188 }
18189 assert!(
18190 dynamic
18191 .iter()
18192 .all(|(start, end)| end - start >= PRIME_MIN_T),
18193 "T={t} sizes={:?}",
18194 sizes(&dynamic)
18195 );
18196 if dynamic.len() >= 3 {
18197 let chunk_sizes = sizes(&dynamic);
18198 assert!(
18199 chunk_sizes[0] < chunk_sizes[1],
18200 "T={t} sizes={chunk_sizes:?}"
18201 );
18202 assert!(
18203 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
18204 "T={t} sizes={chunk_sizes:?}"
18205 );
18206 }
18207 }
18208 }
18209}
18210
18211#[cfg(test)]
18212mod page_prefetch_tests {
18213 use super::{
18214 grouped_worker_prefetch_position, page_prefetch_positions,
18215 page_prefetch_window_from_values, worker_prefetch_positions,
18216 };
18217
18218 #[test]
18219 fn page_prefetch_window_keeps_existing_opt_in_default() {
18220 assert_eq!(page_prefetch_window_from_values(false, None), 0);
18221 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
18222 assert_eq!(page_prefetch_window_from_values(true, None), 1);
18223 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
18224 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
18225 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
18226 }
18227
18228 #[test]
18229 fn rolling_page_prefetch_advises_each_future_expert_once() {
18230 let advised: Vec<_> = (0..7)
18231 .flat_map(|position| page_prefetch_positions(position, 7, 3))
18232 .collect();
18233 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
18234
18235 let one_ahead: Vec<_> = (0..4)
18236 .flat_map(|position| page_prefetch_positions(position, 4, 1))
18237 .collect();
18238 assert_eq!(one_ahead, vec![1, 2, 3]);
18239 assert!(page_prefetch_positions(0, 4, 0).is_empty());
18240 }
18241
18242 #[test]
18243 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
18244 assert_eq!(grouped_worker_prefetch_position(0, None), None);
18245 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
18246 .chain(
18247 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
18248 )
18249 .collect();
18250 assert_eq!(positions, vec![0, 1, 2, 3]);
18251 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
18252 }
18253
18254 #[test]
18255 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
18256 let queued: Vec<_> = (0..8)
18257 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
18258 .collect();
18259 assert_eq!(queued, (0..8).collect::<Vec<_>>());
18260
18261 let one_at_a_time: Vec<_> = (0..4)
18262 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
18263 .collect();
18264 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
18265 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
18266 }
18267}
18268
18269pub struct G4DcSlots {
18270 x: CudaSlice<f32>,
18271 xn: CudaSlice<f32>,
18272 cur: CudaSlice<f32>,
18273 hq: CudaSlice<i8>,
18274 hd_: CudaSlice<f32>,
18275 q0: CudaSlice<f32>,
18276 k0: CudaSlice<f32>,
18277 v0: CudaSlice<f32>,
18278 q: CudaSlice<f32>,
18279 k: CudaSlice<f32>,
18280 v: CudaSlice<f32>,
18281 attn: CudaSlice<f32>,
18282 o: CudaSlice<f32>,
18283 attn_out: CudaSlice<f32>,
18284 zsh: CudaSlice<f32>,
18285 zq: CudaSlice<i8>,
18286 zd: CudaSlice<f32>,
18287 gate: CudaSlice<f32>,
18288 up: CudaSlice<f32>,
18289 act: CudaSlice<f32>,
18290 actq: CudaSlice<i8>,
18291 actd: CudaSlice<f32>,
18292 f0: CudaSlice<f32>,
18293 sn: CudaSlice<f32>,
18294 hn: CudaSlice<f32>,
18295 logits: CudaSlice<f32>,
18296}
18297
18298pub struct Step35TokenGraphState {
18303 pub graphs: Vec<(usize, crate::tp::TokenGraph)>,
18305 pub token_d: cudarc::driver::CudaSlice<u32>,
18306 pub pos_d: cudarc::driver::CudaSlice<i32>,
18307 pub logits_stage: cudarc::driver::CudaSlice<f32>,
18308 pub x: cudarc::driver::CudaSlice<f32>,
18313 pub x1: cudarc::driver::CudaSlice<f32>,
18314 pub mixed_stage: cudarc::driver::CudaSlice<f32>,
18315 pub sh_stage: cudarc::driver::CudaSlice<f32>,
18316 pub k_shadow_stage: cudarc::driver::CudaSlice<f32>,
18317 pub v_shadow_stage: cudarc::driver::CudaSlice<f32>,
18318 pub router_logits: cudarc::driver::CudaSlice<f32>,
18321 pub shexp_gate: cudarc::driver::CudaSlice<f32>,
18322 pub shexp_up: cudarc::driver::CudaSlice<f32>,
18323 pub shexp_act: cudarc::driver::CudaSlice<f32>,
18324 pub gate_sig: cudarc::driver::CudaSlice<f32>,
18325 pub dense_z: cudarc::driver::CudaSlice<f32>,
18326 pub dense_gate: cudarc::driver::CudaSlice<f32>,
18327 pub dense_up: cudarc::driver::CudaSlice<f32>,
18328 pub dense_act: cudarc::driver::CudaSlice<f32>,
18329 pub hn: cudarc::driver::CudaSlice<f32>,
18330 pub probe_mixed: cudarc::driver::CudaSlice<f32>,
18333 pub probe_x: cudarc::driver::CudaSlice<f32>,
18334 pub token_hist: cudarc::driver::CudaSlice<u32>,
18337 pub hist_idx: cudarc::driver::CudaSlice<i32>,
18338}
18339
18340impl HybridModel {
18341 pub(crate) fn step35_token_graph_step(
18352 &self,
18353 e: &Engine,
18354 token: u32,
18355 cache: &mut Cache,
18356 ) -> Result<Option<(Vec<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
18357 if !self.uses_sliding_gated_moe_program()
18358 || !crate::tp::step_tp_graph_enabled()?
18359 || !crate::tp::step_tp_dcw_enabled()?
18360 || !crate::tp::step_tp_qkv_fused_enabled()?
18361 || !crate::tp::step_tp_dev_router_enabled()?
18362 || !crate::tp::step_nvfp4_dev_routes_enabled()?
18363 {
18364 return Ok(None);
18365 }
18366 let n_embd = self.cfg.n_embd as usize;
18367 let n_vocab = self.cfg.n_vocab as usize;
18368 let eps = self.cfg.rms_eps;
18369 let n_layers = self.layers.len();
18370 let pos = cache.pos;
18371 let staged_next = pos + 1;
18372 if staged_next < 96 {
18373 return Ok(None); }
18375
18376 for il in 0..n_layers {
18379 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
18380 return Ok(None); };
18382 if tp_kv.peek_append_ring(1)?.1 {
18383 return Ok(None);
18384 }
18385 }
18386
18387 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
18390 if !fa_vec {
18391 return Ok(None);
18392 }
18393 let sp = crate::fa_split_keys(staged_next, 8);
18394 let bucket_max = (n_splits * sp).max(staged_next);
18395
18396 let mut state_guard = self
18397 .step35_token_graph
18398 .lock()
18399 .map_err(|_| "step35 token graph lock is poisoned")?;
18400 if state_guard.is_none() {
18401 let _main = e.gpu.enter_main()?;
18402 let n_expert = self
18403 .cfg
18404 .moe
18405 .as_ref()
18406 .map(|m| m.expert_count as usize)
18407 .unwrap_or(0);
18408 let n_ff_sh = self
18409 .layers
18410 .iter()
18411 .find_map(|l| match &l.ffn {
18412 crate::hybrid::Ffn::Moe(m) => m.gate_shexp.as_ref().map(|g| g.out_features()),
18413 _ => None,
18414 })
18415 .unwrap_or(0);
18416 let n_ff_dense = self
18417 .layers
18418 .iter()
18419 .find_map(|l| match &l.ffn {
18420 crate::hybrid::Ffn::Dense { ffn_gate, .. } => Some(ffn_gate.out_features()),
18421 _ => None,
18422 })
18423 .unwrap_or(0);
18424 *state_guard = Some(Step35TokenGraphState {
18425 graphs: Vec::new(),
18426 token_d: e.stream().clone_htod(&[0u32])?,
18427 pos_d: e.htod_i32(&[pos as i32])?,
18428 logits_stage: e.htod(&vec![0.0f32; n_vocab])?,
18429 x: e.htod(&vec![0.0f32; n_embd])?,
18430 x1: e.htod(&vec![0.0f32; n_embd])?,
18431 mixed_stage: e.htod(&vec![0.0f32; n_embd])?,
18432 sh_stage: e.htod(&vec![0.0f32; n_embd])?,
18433 k_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
18434 v_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
18435 router_logits: e.htod(&vec![0.0f32; n_expert.max(1)])?,
18436 shexp_gate: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18437 shexp_up: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18438 shexp_act: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18439 gate_sig: e.htod(&vec![1.0f32; 1])?,
18440 dense_z: e.htod(&vec![0.0f32; n_embd])?,
18441 dense_gate: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18442 dense_up: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18443 dense_act: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18444 hn: e.htod(&vec![0.0f32; n_embd])?,
18445 probe_mixed: e.htod(&vec![0.0f32; n_embd])?,
18446 probe_x: e.htod(&vec![0.0f32; n_embd])?,
18447 token_hist: e.stream().clone_htod(&[0u32; 16])?,
18448 hist_idx: e.htod_i32(&[0])?,
18449 });
18450 }
18451 let state = state_guard.as_mut().expect("state armed above");
18452 {
18456 let _main = e.gpu.enter_main()?;
18457 let Step35TokenGraphState {
18458 logits_stage,
18459 token_d,
18460 ..
18461 } = &mut *state;
18462 e.argmax_token_device_into(logits_stage, token_d, n_vocab)?;
18463 }
18464
18465 if state.graphs.is_empty() {
18470 self.step35_token_graph_build(e, cache, state, bucket_max)?;
18473 }
18474 {
18475 let (b, g) = state.graphs.first_mut().expect("graph built above");
18476 if *b != bucket_max {
18477 g.retarget_bucket(bucket_max)?;
18478 *b = bucket_max;
18479 }
18480 }
18481 let graph = state
18482 .graphs
18483 .first()
18484 .map(|(_, g)| g)
18485 .expect("graph built above");
18486
18487 let tg_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
18488 let t_fence = tg_timing.then(std::time::Instant::now);
18489 {
18494 let fa0 = match &self.layers[0].mixer {
18495 Mixer::Full(fa) => fa,
18496 _ => return Err("step35 token graph expects full-attention layers".into()),
18497 };
18498 let tp0 = fa0
18499 .step_tp_qkv
18500 .as_ref()
18501 .ok_or("step35 token graph lost its TP state")?;
18502 for rank in 0..tp0.runtime.devices().len() {
18503 let engine = tp0
18504 .runtime
18505 .rank_engine(rank)
18506 .ok_or("step35 token graph lost a rank engine")?;
18507 let _main = engine.gpu.enter_main()?;
18508 engine.stream().synchronize()?;
18509 }
18510 }
18511
18512 {
18514 let _main = e.gpu.enter_main()?;
18515 e.set_u32_one(&mut state.token_d, token)?;
18516 e.set_i32_one(&mut state.pos_d, pos as i32)?;
18517 }
18518 let t_launch = tg_timing.then(std::time::Instant::now);
18519 graph.launch(e)?;
18520 let t_book = tg_timing.then(std::time::Instant::now);
18521 for il in 0..n_layers {
18526 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
18527 let transaction = tp_kv.begin_transaction()?;
18528 let fa = match &self.layers[il].mixer {
18529 Mixer::Full(fa) => fa,
18530 _ => return Err("step35 token graph expects full-attention layers".into()),
18531 };
18532 let tp = fa
18533 .step_tp_qkv
18534 .as_ref()
18535 .ok_or("step35 token graph lost its TP state")?;
18536 let empty: [CudaSlice<f32>; 0] = [];
18539 tp.runtime.append_tp_kv_transaction_inner(
18540 tp_kv,
18541 transaction,
18542 &empty,
18543 &empty,
18544 1,
18545 true,
18546 )?;
18547 tp.runtime
18548 .commit_tp_kv_transaction_external(tp_kv, transaction, 1)?;
18549 if let Some(local) = cache.kv[il].as_mut() {
18551 local.len = pos + 1;
18552 let _main = e.gpu.enter_main()?;
18553 e.set_i32_one(&mut local.len_d, (pos + 1) as i32)?;
18554 }
18555 }
18556 cache.pos = pos + 1;
18557 let t_sync = tg_timing.then(std::time::Instant::now);
18558 let (logits, h_seed) = {
18559 let _main = e.gpu.enter_main()?;
18560 e.stream().synchronize()?;
18561 (e.dtoh(&state.logits_stage)?, e.clone_dtod(&state.x)?)
18562 };
18563 if let (Some(f), Some(l), Some(b), Some(sy)) = (t_fence, t_launch, t_book, t_sync) {
18564 use std::sync::atomic::{AtomicU64, Ordering};
18565 static NS: [AtomicU64; 5] = [
18566 AtomicU64::new(0),
18567 AtomicU64::new(0),
18568 AtomicU64::new(0),
18569 AtomicU64::new(0),
18570 AtomicU64::new(0),
18571 ];
18572 static CALLS: AtomicU64 = AtomicU64::new(0);
18573 let now = std::time::Instant::now();
18574 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;
18580 if calls % 100 == 0 {
18581 let avg = |i: usize| NS[i].load(Ordering::Relaxed) as f64 / calls as f64 / 1e3;
18582 eprintln!(
18583 "[tg-timing] calls={calls} fence_us={:.0} launch_us={:.0} book_us={:.0} \
18584 syncdtoh_us={:.0} total_us={:.0}",
18585 avg(0),
18586 avg(1),
18587 avg(2),
18588 avg(3),
18589 avg(4)
18590 );
18591 }
18592 }
18593 if std::env::var("MEMRA_TG_PROBE_LAYER").is_ok() {
18595 use std::io::Write;
18596 let (pm, px) = {
18597 let _main = e.gpu.enter_main()?;
18598 (e.dtoh(&state.probe_mixed)?, e.dtoh(&state.probe_x)?)
18599 };
18600 for (path, data) in [
18601 ("/root/tg-probe-mixed.bin", &pm),
18602 ("/root/tg-probe-x.bin", &px),
18603 ] {
18604 let mut fo = std::fs::OpenOptions::new()
18605 .create(true)
18606 .append(true)
18607 .open(path)?;
18608 for v in data {
18609 fo.write_all(&v.to_le_bytes())?;
18610 }
18611 }
18612 }
18613 if let Ok(path) = std::env::var("MEMRA_DUMP_HN") {
18616 let hh = {
18617 let _main = e.gpu.enter_main()?;
18618 e.dtoh(&state.hn)?
18619 };
18620 use std::io::Write;
18621 let mut fo = std::fs::OpenOptions::new()
18622 .create(true)
18623 .append(true)
18624 .open(path)?;
18625 for v in &hh {
18626 fo.write_all(&v.to_le_bytes())?;
18627 }
18628 }
18629 if std::env::var("MEMRA_STEP_TP_GRAPH_DEBUG").as_deref() == Ok("1") {
18632 for il in [0usize, 1, 44] {
18633 let tp_kv = cache.tp_kv[il].as_ref().expect("eligibility checked above");
18634 let host_len = tp_kv.staged_len();
18635 let fa = match &self.layers[il].mixer {
18636 Mixer::Full(fa) => fa,
18637 _ => continue,
18638 };
18639 let tp = fa
18640 .step_tp_qkv
18641 .as_ref()
18642 .ok_or("step35 token graph lost its TP state")?;
18643 for rank in 0..tp.runtime.devices().len() {
18644 let engine = tp
18645 .runtime
18646 .rank_engine(rank)
18647 .ok_or("step35 token graph lost a rank engine")?;
18648 let rank_cache = tp_kv.rank(rank).ok_or("debug rank cache missing")?;
18649 let _main = engine.gpu.enter_main()?;
18650 engine.stream().synchronize()?;
18651 let len_d = engine.dtoh_i32_one(rank_cache.len_d())?;
18652 let base_d = match rank_cache.base_d() {
18653 Some(b) => engine.dtoh_i32_one(b)?,
18654 None => -1,
18655 };
18656 eprintln!(
18657 "[graph-debug] pos={pos} il={il} rank={rank} host_len={host_len} \
18658 len_d={len_d} base_d={base_d}"
18659 );
18660 }
18661 }
18662 }
18663 Ok(Some((logits, h_seed)))
18664 }
18665
18666 pub(crate) fn head_split_matvec(
18672 &self,
18673 e: &Engine,
18674 hn: &CudaSlice<f32>,
18675 ) -> Result<Option<Vec<f32>>, Box<dyn std::error::Error>> {
18676 if self.head_split_fill_device(e, hn)?.is_none() {
18677 return Ok(None);
18678 }
18679 let guard = HEAD_SPLIT_WS
18680 .lock()
18681 .map_err(|_| "head split lock is poisoned")?;
18682 let ws = guard.as_ref().expect("filled above");
18683 let _main = e.gpu.enter_main()?;
18684 Ok(Some(e.dtoh(&ws.logits_e)?))
18685 }
18686
18687 fn head_split_fill_device(
18691 &self,
18692 e: &Engine,
18693 hn: &CudaSlice<f32>,
18694 ) -> Result<Option<()>, Box<dyn std::error::Error>> {
18695 use cudarc::driver::DevicePtr;
18696 let crate::model::GpuTensor::FloatBf16 { data: head, .. } = &self.output else {
18697 return Ok(None);
18698 };
18699 let Some(rank1) = self.layers.first().and_then(|l| match &l.mixer {
18700 Mixer::Full(fa) => fa
18701 .step_tp_qkv
18702 .as_ref()
18703 .and_then(|tp| tp.runtime.rank_engine(1)),
18704 _ => None,
18705 }) else {
18706 return Ok(None);
18707 };
18708 let n_embd = self.cfg.n_embd as usize;
18709 let n_vocab = self.cfg.n_vocab as usize;
18710 let half = n_vocab / 2;
18711 let mut guard = HEAD_SPLIT_WS
18712 .lock()
18713 .map_err(|_| "head split lock is poisoned")?;
18714 let pin = {
18715 let _main = e.gpu.enter_main()?;
18716 let stream = e.stream();
18717 let (ptr, _g) = head.device_ptr(&stream);
18718 ptr as u64
18719 };
18720 if guard.as_ref().is_none_or(|ws| ws.pin != pin) {
18721 let hi_rows = n_vocab - half;
18723 let (w1, hn1, y1, ev_done) = {
18724 let _r1 = rank1.gpu.enter_main()?;
18725 (
18726 rank1.alloc_u8_uninit(hi_rows * n_embd * 2)?,
18727 rank1.htod(&vec![0.0f32; n_embd])?,
18728 rank1.htod(&vec![0.0f32; hi_rows])?,
18729 rank1.ctx().new_event(None)?,
18730 )
18731 };
18732 {
18733 use cudarc::driver::sys;
18734 let src = pin + (half * n_embd * 2) as u64;
18735 let dst = {
18736 let _r1 = rank1.gpu.enter_main()?;
18737 let rstream = rank1.stream();
18738 let (d, _g) = w1.device_ptr(&rstream);
18739 d as u64
18740 };
18741 let _r1 = rank1.gpu.enter_main()?;
18742 let r = unsafe {
18743 sys::cuMemcpyAsync(
18744 dst as sys::CUdeviceptr,
18745 src as sys::CUdeviceptr,
18746 hi_rows * n_embd * 2,
18747 rank1.stream().cu_stream() as sys::CUstream,
18748 )
18749 };
18750 if r != sys::CUresult::CUDA_SUCCESS {
18751 return Err(format!("head split replica upload: {r:?}").into());
18752 }
18753 rank1.stream().synchronize()?;
18754 }
18755 let (logits_e, ev_hn) = {
18756 let _main = e.gpu.enter_main()?;
18757 (e.htod(&vec![0.0f32; n_vocab])?, e.ctx().new_event(None)?)
18758 };
18759 let (raw_hn1, raw_y1) = {
18760 let _r1 = rank1.gpu.enter_main()?;
18761 let rstream = rank1.stream();
18762 let (a, _g0) = hn1.device_ptr(&rstream);
18763 let (b, _g1) = y1.device_ptr(&rstream);
18764 (a as u64, b as u64)
18765 };
18766 let raw_logits_hi = {
18767 let _main = e.gpu.enter_main()?;
18768 let stream = e.stream();
18769 let (l, _g) = logits_e.device_ptr(&stream);
18770 l as u64 + (half * 4) as u64
18771 };
18772 *guard = Some(HeadSplit {
18773 pin,
18774 w1,
18775 hn1,
18776 y1,
18777 logits_e,
18778 ev_hn,
18779 ev_done,
18780 raw_hn1,
18781 raw_y1,
18782 raw_logits_hi,
18783 });
18784 }
18785 let ws = guard.as_mut().expect("armed above");
18786 let hi_rows = n_vocab - half;
18787 let raw_hn = {
18789 let _main = e.gpu.enter_main()?;
18790 let stream = e.stream();
18791 let (h, _g) = hn.device_ptr(&stream);
18792 ws.ev_hn.record(&stream)?;
18793 h as u64
18794 };
18795 {
18796 let _r1 = rank1.gpu.enter_main()?;
18797 rank1.stream().wait(&ws.ev_hn)?;
18798 crate::tp::raw_copy_bytes(ws.raw_hn1, raw_hn, n_embd * 4, rank1)?;
18799 let HeadSplit { w1, hn1, y1, .. } = &mut *ws;
18800 rank1.matvec_bf16_into(w1, hn1, y1, n_embd, hi_rows)?;
18801 crate::tp::raw_copy_bytes(ws.raw_logits_hi, ws.raw_y1, hi_rows * 4, rank1)?;
18802 ws.ev_done.record(&rank1.stream())?;
18803 }
18804 {
18805 let _main = e.gpu.enter_main()?;
18806 let head_lo = head.slice(0..half * n_embd * 2);
18807 let HeadSplit { logits_e, .. } = &mut *ws;
18808 e.matvec_bf16_view_into(&head_lo, hn, logits_e, n_embd, half)?;
18810 e.stream().wait(&ws.ev_done)?;
18811 Ok(Some(()))
18812 }
18813 }
18814
18815 pub(crate) fn head_split_argmax_device(
18820 &self,
18821 e: &Engine,
18822 hn: &CudaSlice<f32>,
18823 token_d: &mut CudaSlice<u32>,
18824 ) -> Result<bool, Box<dyn std::error::Error>> {
18825 if self.head_split_fill_device(e, hn)?.is_none() {
18826 return Ok(false);
18827 }
18828 let n_vocab = self.cfg.n_vocab as usize;
18829 let guard = HEAD_SPLIT_WS
18830 .lock()
18831 .map_err(|_| "head split lock is poisoned")?;
18832 let ws = guard.as_ref().expect("filled above");
18833 let _main = e.gpu.enter_main()?;
18834 e.argmax_token_device_into(&ws.logits_e, token_d, n_vocab)?;
18835 Ok(true)
18836 }
18837
18838 pub(crate) fn head_split_logits_dtoh(
18841 &self,
18842 e: &Engine,
18843 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
18844 let guard = HEAD_SPLIT_WS
18845 .lock()
18846 .map_err(|_| "head split lock is poisoned")?;
18847 let ws = guard.as_ref().ok_or("head split logits not armed")?;
18848 let _main = e.gpu.enter_main()?;
18849 Ok(e.dtoh(&ws.logits_e)?)
18850 }
18851
18852 pub fn step35_token_graph_chunk(
18861 &self,
18862 e: &Engine,
18863 token: u32,
18864 k_target: usize,
18865 cache: &mut Cache,
18866 ) -> Result<Option<(Vec<u32>, Vec<f32>)>, Box<dyn std::error::Error>> {
18867 if !self.uses_sliding_gated_moe_program()
18868 || !crate::tp::step_tp_graph_enabled()?
18869 || !crate::tp::step_tp_dcw_enabled()?
18870 || !crate::tp::step_tp_qkv_fused_enabled()?
18871 || !crate::tp::step_tp_dev_router_enabled()?
18872 || !crate::tp::step_nvfp4_dev_routes_enabled()?
18873 {
18874 return Ok(None);
18875 }
18876 let n_layers = self.layers.len();
18877 let pos = cache.pos;
18878 let staged_next = pos + 1;
18879 if staged_next < 96 {
18880 return Ok(None);
18881 }
18882 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
18885 if !fa_vec {
18886 return Ok(None);
18887 }
18888 let sp = crate::fa_split_keys(staged_next, 8);
18889 let bucket_max = (n_splits * sp).max(staged_next);
18890 let to_boundary = bucket_max.saturating_sub(staged_next) + 1;
18891 let mut k = k_target.min(to_boundary).min(16);
18892 if k < 2 {
18893 return Ok(None);
18894 }
18895 for il in 0..n_layers {
18897 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
18898 return Ok(None);
18899 };
18900 while k >= 2 && tp_kv.peek_append_ring(k)?.1 {
18901 k -= 1;
18902 }
18903 if k < 2 {
18904 return Ok(None);
18905 }
18906 }
18907
18908 let mut state_guard = self
18909 .step35_token_graph
18910 .lock()
18911 .map_err(|_| "step35 token graph lock is poisoned")?;
18912 let Some(state) = state_guard.as_mut() else {
18913 return Ok(None); };
18915 if state.graphs.is_empty() {
18916 return Ok(None);
18917 }
18918 {
18919 let (b, g) = state.graphs.first_mut().expect("checked above");
18920 if *b != bucket_max {
18921 g.retarget_bucket(bucket_max)?;
18922 *b = bucket_max;
18923 }
18924 }
18925 let graph = state.graphs.first().map(|(_, g)| g).expect("checked above");
18926
18927 {
18929 let fa0 = match &self.layers[0].mixer {
18930 Mixer::Full(fa) => fa,
18931 _ => return Err("step35 token graph expects full-attention layers".into()),
18932 };
18933 let tp0 = fa0
18934 .step_tp_qkv
18935 .as_ref()
18936 .ok_or("step35 token graph lost its TP state")?;
18937 for rank in 0..tp0.runtime.devices().len() {
18938 let engine = tp0
18939 .runtime
18940 .rank_engine(rank)
18941 .ok_or("step35 token graph lost a rank engine")?;
18942 let _main = engine.gpu.enter_main()?;
18943 engine.stream().synchronize()?;
18944 }
18945 }
18946
18947 {
18950 let _main = e.gpu.enter_main()?;
18951 e.set_u32_one(&mut state.token_d, token)?;
18952 e.set_i32_one(&mut state.pos_d, pos as i32)?;
18953 e.set_i32_one(&mut state.hist_idx, 0)?;
18954 }
18955 for _ in 0..k {
18956 graph.launch(e)?;
18957 }
18958 for il in 0..n_layers {
18960 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
18961 let transaction = tp_kv.begin_transaction()?;
18962 let fa = match &self.layers[il].mixer {
18963 Mixer::Full(fa) => fa,
18964 _ => return Err("step35 token graph expects full-attention layers".into()),
18965 };
18966 let tp = fa
18967 .step_tp_qkv
18968 .as_ref()
18969 .ok_or("step35 token graph lost its TP state")?;
18970 let empty: [CudaSlice<f32>; 0] = [];
18971 tp.runtime.append_tp_kv_transaction_inner(
18972 tp_kv,
18973 transaction,
18974 &empty,
18975 &empty,
18976 k,
18977 true,
18978 )?;
18979 tp.runtime
18980 .commit_tp_kv_transaction_external(tp_kv, transaction, k)?;
18981 if let Some(local) = cache.kv[il].as_mut() {
18982 local.len = pos + k;
18983 let _main = e.gpu.enter_main()?;
18984 e.set_i32_one(&mut local.len_d, (pos + k) as i32)?;
18985 }
18986 }
18987 cache.pos = pos + k;
18988 let (hist, logits) = {
18989 let _main = e.gpu.enter_main()?;
18990 e.stream().synchronize()?;
18991 (e.dtoh_u32(&state.token_hist)?, e.dtoh(&state.logits_stage)?)
18992 };
18993 Ok(Some((hist[..k].to_vec(), logits)))
18994 }
18995}
18996
18997impl HybridModel {
18998 #[allow(clippy::too_many_arguments)]
19003 fn step35_token_graph_build(
19004 &self,
19005 e: &Engine,
19006 cache: &mut Cache,
19007 state: &mut Step35TokenGraphState,
19008 bucket_max: usize,
19009 ) -> Result<(), Box<dyn std::error::Error>> {
19010 use cudarc::driver::DevicePtr;
19011 let n_embd = self.cfg.n_embd as usize;
19012 let eps = self.cfg.rms_eps;
19013 let n_layers = self.layers.len();
19014 let started = std::time::Instant::now();
19015 if !crate::router_kernel_on() {
19016 return Err(
19017 "step35 token graph requires the router kernel (MEMRA_ROUTER_KERNEL=0)".into(),
19018 );
19019 }
19020 if !Engine::bf16_mmv_on() || n_embd % 8 != 0 {
19021 return Err("step35 token graph requires MEMRA_BF16_MMV bf16-resident matvecs".into());
19022 }
19023
19024 let embd_gpu = self
19026 .embd_gpu_try(e)
19027 .ok_or("step35 token graph could not upload the device embed table")?;
19028 let embd_qtype = match self.embd.ggml_type {
19029 memra_gguf::GgmlType::BF16 => crate::QT_BF16,
19030 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
19031 other => return Err(format!("token graph embed dtype {other:?} unhandled").into()),
19032 };
19033 let embd_row_bytes = self.embd.raw.len() / self.cfg.n_vocab as usize;
19034
19035 let (p_mixed, p_kshadow, p_vshadow) = {
19037 let _main = e.gpu.enter_main()?;
19038 let stream = e.stream();
19039 let (a, _g) = state.mixed_stage.device_ptr(&stream);
19040 let (b, _g) = state.k_shadow_stage.device_ptr(&stream);
19041 let (c, _g) = state.v_shadow_stage.device_ptr(&stream);
19042 (a as u64, b as u64, c as u64)
19043 };
19044
19045 crate::tp::token_graph_build_begin()?;
19046 let mut group_id: u32 = 0;
19047 for il in 0..n_layers {
19048 let layer = &self.layers[il];
19049 let fa = match &layer.mixer {
19050 Mixer::Full(fa) => fa,
19051 _ => return Err("step35 token graph expects full-attention layers".into()),
19052 };
19053 let tp = fa
19054 .step_tp_qkv
19055 .as_ref()
19056 .ok_or("step35 token graph lost its TP state")?;
19057 let attention = tp
19058 .attention
19059 .as_ref()
19060 .ok_or("step35 token graph lost its attention aux")?;
19061 let geometry = self.step35_geom(il);
19062 let window = geometry.window.map(|w| w as usize);
19063 let head_dim = geometry.head_dim_k as usize;
19064 let heads = geometry.n_head as usize;
19065 let kv_heads = geometry.n_head_kv as usize;
19066 let ranks = tp.runtime.devices().len();
19067 let local_heads = heads / ranks;
19068 let local_kv_heads = kv_heads / ranks;
19069 let layer_bucket = window.map(|w| bucket_max.min(w)).unwrap_or(bucket_max);
19070 let use_gate_shards =
19071 attention.gate_shards.is_some() || attention.gate_shards_bf16.is_some();
19072 if !use_gate_shards {
19073 return Err("step35 token graph requires the fused gate shards".into());
19074 }
19075
19076 let ws_index = tp
19077 .runtime
19078 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
19079 let ws_mutex = tp.runtime.decode_v2_workspace();
19080 let mut ws_guard = ws_mutex
19081 .lock()
19082 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
19083 let ws = ws_guard
19084 .get_mut(ws_index)
19085 .ok_or("step TP decode v2 workspace missing after ensure")?;
19086 tp.runtime
19087 .decode_v2_arm_token_mirrors(ws, p_mixed, (p_kshadow, p_vshadow))?;
19088 let mut rope_freqs = Vec::with_capacity(ranks);
19089 for rank in 0..ranks {
19090 let engine = tp
19091 .runtime
19092 .rank_engine(rank)
19093 .ok_or("step35 token graph lost a rank engine")?;
19094 rope_freqs.push(if geometry.rope_factors {
19095 self.step35_aux
19096 .as_ref()
19097 .and_then(|aux| aux.rope_freqs(engine))
19098 } else {
19099 None
19100 });
19101 }
19102 let gate_shards_arg = if let Some(shards) = attention.gate_shards.as_deref() {
19103 Some(crate::tp::StepTpGateShards::F32(shards))
19104 } else {
19105 attention
19106 .gate_shards_bf16
19107 .as_deref()
19108 .map(crate::tp::StepTpGateShards::Bf16)
19109 };
19110
19111 let decode_input = attention
19113 .decode_input
19114 .as_ref()
19115 .ok_or("step35 token graph requires the replicated decode input")?;
19116 let mut decode_input = decode_input
19117 .lock()
19118 .map_err(|_| "replicated decode input lock is poisoned")?;
19119 if ws.h_stage.is_none() {
19121 return Err(
19122 "step35 token graph requires the stage flow armed (run eager dcw first)".into(),
19123 );
19124 }
19125 {
19126 let state_x = &mut state.x;
19127 let token_d = &state.token_d;
19128 let pos_d = &state.pos_d;
19129 crate::tp::graph_section(e, None, || {
19130 let _main = e.gpu.enter_main()?;
19131 if il == 0 {
19132 e.embed_gather_device_into(
19133 embd_gpu,
19134 token_d,
19135 state_x,
19136 n_embd,
19137 embd_qtype,
19138 embd_row_bytes,
19139 )?;
19140 }
19141 {
19142 let h_stage = ws.h_stage.as_mut().expect("stage armed checked above");
19143 e.rms_norm(
19144 state_x,
19145 layer.attn_norm.float_data(),
19146 h_stage,
19147 n_embd,
19148 1,
19149 eps,
19150 )?;
19151 }
19152 {
19153 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
19154 let mut dst = pos_stage.slice_mut(0..1);
19155 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
19156 }
19157 Ok(())
19158 })?;
19159 }
19160
19161 group_id += 1;
19163 for rank in 0..ranks {
19164 let engine = tp
19165 .runtime
19166 .rank_engine(rank)
19167 .ok_or("step35 token graph lost a rank engine")?;
19168 {
19169 let ceiling = window
19174 .map(|w| cache.max_ctx.min(w))
19175 .unwrap_or(cache.max_ctx);
19176 let _main = engine.gpu.enter_main()?;
19177 engine.fa_dcw_pool_ensure(
19178 head_dim,
19179 local_heads,
19180 local_kv_heads,
19181 ceiling.min(2048),
19182 )?;
19183 engine.fa_dcw_pool_ensure(head_dim, local_heads, local_kv_heads, ceiling)?;
19184 engine.fa_dcw_pool_ensure(
19185 head_dim,
19186 local_heads,
19187 local_kv_heads,
19188 layer_bucket,
19189 )?;
19190 }
19191 let runtime = &tp.runtime;
19192 let q_norm = &attention.q_norm;
19193 let k_norm = &attention.k_norm;
19194 let gate_ref = gate_shards_arg.as_ref();
19195 crate::tp::graph_section(engine, Some(group_id), || {
19196 runtime.decode_v2_input_qkv_rank(
19197 ws,
19198 &state.pos_d,
19199 &mut decode_input,
19200 &tp.q,
19201 &tp.k,
19202 &tp.v,
19203 q_norm,
19204 k_norm,
19205 head_dim,
19206 geometry.n_rot as usize,
19207 geometry.rope_base,
19208 &rope_freqs,
19209 eps,
19210 gate_ref,
19211 true,
19212 false,
19213 rank,
19214 None,
19215 )?;
19216 let distributed = cache.tp_kv[il]
19219 .as_mut()
19220 .ok_or("step35 token graph lost a TP cache")?;
19221 let (kv_dim_k, kv_dim_v) = (distributed.kv_dim_k(), distributed.kv_dim_v());
19222 let (ktb, vtb) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
19223 let capacity = distributed.physical_capacity();
19224 {
19225 let rank_cache = distributed
19226 .rank_mut(rank)
19227 .ok_or("step35 token graph lost a rank cache")?;
19228 let (k_plane, v_plane, len_d, base_d) =
19229 rank_cache.planes_and_counters_mut();
19230 engine.append_kv_quantized_dcw(
19231 &ws.k[rank],
19232 &ws.v_raw[rank],
19233 k_plane,
19234 v_plane,
19235 len_d,
19236 base_d,
19237 kv_dim_k,
19238 kv_dim_v,
19239 ktb,
19240 vtb,
19241 )?;
19242 }
19243 {
19244 let rank_cache = distributed
19245 .rank_mut(rank)
19246 .ok_or("step35 token graph lost a rank cache")?;
19247 engine.inc_i32(rank_cache.len_d_mut())?;
19248 }
19249 let rank_cache = distributed
19250 .rank(rank)
19251 .ok_or("step35 token graph lost a rank cache")?;
19252 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * ktb);
19253 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * vtb);
19254 engine.fa_decode_dcw(
19259 &ws.q[rank],
19260 &k_ring,
19261 &v_ring,
19262 &mut ws.attn_out[rank],
19263 head_dim,
19264 local_heads,
19265 local_kv_heads,
19266 rank_cache.len_d(),
19267 rank_cache.base_d(),
19268 window.unwrap_or(0),
19269 layer_bucket,
19270 geometry.attention_scale(),
19271 ktb,
19272 vtb,
19273 None,
19274 )?;
19275 engine.attn_head_gate(
19276 &ws.attn_out[rank],
19277 &ws.gate[rank],
19278 &mut ws.gated[rank],
19279 None,
19280 head_dim,
19281 local_heads,
19282 1,
19283 )?;
19284 runtime.decode_v2_finish_rank_partial(ws, &tp.o, true, rank)?;
19285 Ok(())
19286 })?;
19287 }
19288
19289 {
19291 let root = tp
19292 .runtime
19293 .rank_engine(0)
19294 .ok_or("step35 token graph lost the root engine")?;
19295 let runtime = &tp.runtime;
19296 crate::tp::graph_section(root, None, || runtime.decode_v2_finish_root_fused(ws))?;
19297 }
19298 drop(ws_guard);
19299 drop(decode_input);
19300
19301 let probe_layer: Option<usize> = std::env::var("MEMRA_TG_PROBE_LAYER")
19302 .ok()
19303 .and_then(|v| v.parse().ok());
19304 if probe_layer == Some(il) {
19305 let Step35TokenGraphState {
19306 mixed_stage,
19307 probe_mixed,
19308 ..
19309 } = &mut *state;
19310 crate::tp::graph_section(e, None, || {
19311 let _main = e.gpu.enter_main()?;
19312 let mut dst = probe_mixed.slice_mut(0..n_embd);
19313 e.stream()
19314 .memcpy_dtod(&mixed_stage.slice(0..n_embd), &mut dst)?;
19315 Ok(())
19316 })?;
19317 }
19318
19319 match &layer.ffn {
19321 crate::hybrid::Ffn::Dense {
19322 ffn_gate,
19323 ffn_up,
19324 ffn_down,
19325 } => {
19326 let n_ff = ffn_gate.out_features();
19327 let lim = self.cfg.clamp_shexp_at(il as u32);
19328 if lim.is_some() {
19332 return Err("step35 token graph dense FFN with clamp unsupported".into());
19333 }
19334 let (wg_d, wu_d, wd_d) = match (ffn_gate, ffn_up, ffn_down) {
19335 (
19336 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
19337 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
19338 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
19339 ) => (wg, wu, wd),
19340 _ => {
19341 return Err(
19342 "step35 token graph dense FFN requires bf16-resident weights"
19343 .into(),
19344 );
19345 }
19346 };
19347 crate::tp::graph_section(e, None, || {
19348 let _main = e.gpu.enter_main()?;
19349 let Step35TokenGraphState {
19350 x,
19351 x1,
19352 mixed_stage,
19353 dense_z,
19354 dense_gate,
19355 dense_up,
19356 dense_act,
19357 sh_stage,
19358 ..
19359 } = &mut *state;
19360 e.add_rms_norm(
19361 x,
19362 mixed_stage,
19363 layer.post_attn_norm.float_data(),
19364 x1,
19365 dense_z,
19366 n_embd,
19367 1,
19368 eps,
19369 )?;
19370 e.matvec_bf16_into(wg_d, dense_z, dense_gate, n_embd, n_ff)?;
19374 e.matvec_bf16_into(wu_d, dense_z, dense_up, n_embd, n_ff)?;
19375 Self::ffn_act_lim(
19376 e, &self.cfg, dense_gate, dense_up, 1.0, 1.0, lim, dense_act, n_ff,
19377 )?;
19378 e.matvec_bf16_into(wd_d, dense_act, sh_stage, n_ff, n_embd)?;
19379 e.add(x1, sh_stage, x, n_embd)?;
19380 Ok(())
19381 })?;
19382 }
19383 crate::hybrid::Ffn::Moe(m) => {
19384 let moe = self
19385 .cfg
19386 .moe
19387 .as_ref()
19388 .ok_or("step35 token graph needs moe cfg")?;
19389 let n_expert = moe.expert_count as usize;
19390 let n_used = moe.expert_used_count as usize;
19391 let sigmoid = self
19392 .cfg
19393 .sigmoid_router()
19394 .ok_or("step35 token graph needs the sigmoid router")?;
19395 let step_tp = m
19396 .step_tp
19397 .as_ref()
19398 .ok_or("step35 token graph needs TP experts")?;
19399 let bank = match &step_tp.experts {
19400 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => bank,
19401 _ => return Err("step35 token graph needs the NVFP4 bank".into()),
19402 };
19403 let routes_ws_mutex = bank.device_workspace_handle();
19404 let mut routes_guard = routes_ws_mutex
19405 .lock()
19406 .map_err(|_| "routes workspace lock is poisoned")?;
19407 let routes_ws = routes_guard
19408 .as_mut()
19409 .ok_or("step35 token graph requires the routes workspace warmed")?;
19410 routes_ws.arm_stages(e, bank.input_width, n_used)?;
19411 step_tp.runtime.routes_arm_raw(bank, routes_ws)?;
19412 let p_z = {
19413 let root = step_tp
19414 .runtime
19415 .rank_engine(0)
19416 .ok_or("routes root engine missing")?;
19417 let _main = root.gpu.enter_main()?;
19418 let stream = root.stream();
19419 let in_stage = routes_ws
19420 .in_stage_handle()
19421 .ok_or("routes in stage not armed")?;
19422 let (a, _g) = in_stage.device_ptr(&stream);
19423 a as u64
19424 };
19425 let local_out = bank.expert_width / ranks;
19426
19427 crate::tp::graph_section(e, None, || {
19429 let _main = e.gpu.enter_main()?;
19430 {
19431 let in_stage = routes_ws
19432 .in_stage_mut()
19433 .ok_or("routes in stage not armed")?;
19434 let Step35TokenGraphState {
19435 x, x1, mixed_stage, ..
19436 } = &mut *state;
19437 e.add_rms_norm(
19438 x,
19439 mixed_stage,
19440 layer.post_attn_norm.float_data(),
19441 x1,
19442 in_stage,
19443 n_embd,
19444 1,
19445 eps,
19446 )?;
19447 }
19448 {
19449 let z_ref = routes_ws
19450 .in_stage_handle()
19451 .ok_or("routes in stage not armed")?;
19452 e.router_gemv_into(
19453 m.gate_inp.float_data(),
19454 z_ref,
19455 &mut state.router_logits,
19456 n_embd,
19457 n_expert,
19458 1,
19459 )?;
19460 }
19461 let (sel_e, w_e) = routes_ws
19462 .dev_route_e_mut()
19463 .ok_or("routes staging not armed")?;
19464 e.moe_router_sigmoid_topk_into(
19465 &state.router_logits,
19466 1,
19467 n_expert,
19468 n_used,
19469 m.active_count(),
19470 &m.exp_probs_b_dev,
19471 &m.active_experts_dev,
19472 sigmoid.0,
19473 sigmoid.1,
19474 sel_e,
19475 w_e,
19476 )?;
19477 Ok(())
19478 })?;
19479
19480 group_id += 1;
19482 for rank in 0..ranks {
19483 let engine = step_tp
19484 .runtime
19485 .rank_engine(rank)
19486 .ok_or("routes rank engine missing")?;
19487 let runtime = &step_tp.runtime;
19488 crate::tp::graph_section(engine, Some(group_id), || {
19489 runtime.routes_rank_section(
19490 bank,
19491 routes_ws,
19492 p_z,
19493 local_out,
19494 n_used,
19495 step_tp.activation_limit,
19496 rank,
19497 )
19498 })?;
19499 }
19500
19501 {
19503 let root = step_tp
19504 .runtime
19505 .rank_engine(0)
19506 .ok_or("routes root engine missing")?;
19507 let runtime = &step_tp.runtime;
19508 crate::tp::graph_section(root, None, || {
19509 runtime.routes_root_section(bank, routes_ws)
19510 })?;
19511 }
19512
19513 let lim_sh = self.cfg.clamp_shexp_at(il as u32);
19517 let (wg_sh, wu_sh, wd_sh) = match (&m.gate_shexp, &m.up_shexp, &m.down_shexp) {
19518 (
19519 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
19520 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
19521 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
19522 ) => (wg, wu, wd),
19523 _ => {
19524 return Err(
19525 "step35 token graph shexp requires bf16-resident weights".into()
19526 );
19527 }
19528 };
19529 let n_ff_sh = m
19530 .gate_shexp
19531 .as_ref()
19532 .expect("matched Some above")
19533 .out_features();
19534 let gate_inp_shexp = m.gate_inp_shexp.as_ref();
19537 crate::tp::graph_section(e, None, || {
19538 let _main = e.gpu.enter_main()?;
19539 let (z_ref, out_stage) = routes_ws
19540 .in_and_out_stages_mut()
19541 .ok_or("routes stages not armed")?;
19542 let Step35TokenGraphState {
19543 x,
19544 x1,
19545 sh_stage,
19546 shexp_gate,
19547 shexp_up,
19548 shexp_act,
19549 gate_sig,
19550 ..
19551 } = &mut *state;
19552 e.matvec_bf16_dual_into(
19553 wg_sh, wu_sh, z_ref, shexp_gate, shexp_up, n_embd, n_ff_sh,
19554 )?;
19555 Self::ffn_act_lim(
19556 e, &self.cfg, shexp_gate, shexp_up, 1.0, 1.0, lim_sh, shexp_act,
19557 n_ff_sh,
19558 )?;
19559 e.matvec_bf16_into(wd_sh, shexp_act, sh_stage, n_ff_sh, n_embd)?;
19560 if let Some(gate_w) = gate_inp_shexp {
19561 e.sigmoid_dot_rows_into(
19562 z_ref,
19563 gate_w.float_data(),
19564 gate_sig,
19565 n_embd,
19566 1,
19567 )?;
19568 }
19569 e.add_scaled_rows(sh_stage, gate_sig, out_stage, n_embd, 1)?;
19570 e.add(x1, out_stage, x, n_embd)?;
19571 Ok(())
19572 })?;
19573 }
19574 }
19575 if probe_layer == Some(il) {
19576 let Step35TokenGraphState { x, probe_x, .. } = &mut *state;
19577 crate::tp::graph_section(e, None, || {
19578 let _main = e.gpu.enter_main()?;
19579 let mut dst = probe_x.slice_mut(0..n_embd);
19580 e.stream().memcpy_dtod(&x.slice(0..n_embd), &mut dst)?;
19581 Ok(())
19582 })?;
19583 }
19584 }
19585
19586 let head = match &self.output {
19588 crate::model::GpuTensor::FloatBf16 { data, .. } => data,
19589 _ => return Err("step35 token graph head requires the bf16-resident output".into()),
19590 };
19591 crate::tp::graph_section(e, None, || {
19592 let _main = e.gpu.enter_main()?;
19593 let Step35TokenGraphState {
19594 x,
19595 hn,
19596 logits_stage,
19597 token_d,
19598 pos_d,
19599 token_hist,
19600 hist_idx,
19601 ..
19602 } = &mut *state;
19603 e.rms_norm(x, self.output_norm.float_data(), hn, n_embd, 1, eps)?;
19604 e.matvec_bf16_into(head, hn, logits_stage, n_embd, self.cfg.n_vocab as usize)?;
19605 e.argmax_token_device_into(logits_stage, token_d, self.cfg.n_vocab as usize)?;
19611 e.u32_hist_append(token_d, token_hist, hist_idx)?;
19612 e.inc_i32(pos_d)?;
19613 Ok(())
19614 })?;
19615
19616 let graph = crate::tp::token_graph_build_finish()?;
19617 state.graphs.push((bucket_max, graph));
19618 eprintln!(
19619 "[step35-token-graph] built bucket={bucket_max} layers={n_layers} \
19620 build_ms={:.0} performance_claim=false",
19621 started.elapsed().as_secs_f64() * 1e3
19622 );
19623 Ok(())
19624 }
19625}