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, gdn_grid: bool) -> Vec<(usize, usize)> {
671 let explicit_chunk = std::env::var_os("MEMRA_PRIME_CHUNK").is_some();
672 let chunk = prime_chunk_tokens(t, n_layers);
673 let fixed = fixed_prime_chunk_ranges(t, chunk);
674 let dynamic = match std::env::var("MEMRA_PRIME_CHUNK_SCHED") {
675 Ok(value) => value == "dynamic",
676 Err(_) => true,
677 };
678 if explicit_chunk {
679 return fixed;
680 }
681 let ranges = if !dynamic || !prime_pp2_auto_geometry(n_layers) {
682 fixed
683 } else {
684 dynamic_prime_chunk_ranges(t, chunk, &fixed)
685 };
686 if gdn_grid && std::env::var("MEMRA_PRIME_GRID_ALIGN").as_deref() != Ok("0") {
690 align_prime_ranges_to_gdn(&ranges, t, Engine::gdn_chunk_size())
691 } else {
692 ranges
693 }
694}
695
696pub fn align_prime_ranges_to_gdn(
716 ranges: &[(usize, usize)],
717 t: usize,
718 c: usize,
719) -> Vec<(usize, usize)> {
720 if c == 0 || ranges.len() < 2 {
721 return ranges.to_vec();
722 }
723 let mut out: Vec<(usize, usize)> = Vec::with_capacity(ranges.len());
724 let mut start = 0usize;
725 for (i, &(_, end)) in ranges.iter().enumerate() {
726 let e = if i + 1 == ranges.len() {
727 t
728 } else {
729 end / c * c
730 };
731 if e > start {
732 out.push((start, e));
733 start = e;
734 } }
736 debug_assert_eq!(out.last().map(|&(_, e)| e), Some(t));
737 out
738}
739
740struct HeadSplit {
741 pin: u64,
742 w1: CudaSlice<u8>,
743 hn1: CudaSlice<f32>,
744 y1: CudaSlice<f32>,
745 logits_e: CudaSlice<f32>,
746 ev_hn: cudarc::driver::CudaEvent,
747 ev_done: cudarc::driver::CudaEvent,
748 raw_hn1: u64,
749 raw_y1: u64,
750 raw_logits_hi: u64,
751}
752static HEAD_SPLIT_WS: std::sync::Mutex<Option<HeadSplit>> = std::sync::Mutex::new(None);
754
755#[allow(clippy::type_complexity)]
759static DEV1_ROUTER_REPS: std::sync::Mutex<
760 Option<(
761 std::collections::HashMap<u16, (CudaSlice<f32>, CudaSlice<f32>, CudaSlice<u8>)>,
762 Option<CudaSlice<f32>>,
763 )>,
764> = std::sync::Mutex::new(None);
765
766#[allow(clippy::type_complexity)]
770static SHEXP_D1_REPS: std::sync::Mutex<
771 Option<std::collections::HashMap<u16, (CudaSlice<u8>, CudaSlice<u8>, CudaSlice<u8>)>>,
772> = std::sync::Mutex::new(None);
773#[allow(clippy::type_complexity)]
774static SHEXP_D1_WS: std::sync::Mutex<
775 Option<(
776 (usize, usize),
777 CudaSlice<f32>,
778 CudaSlice<f32>,
779 CudaSlice<f32>,
780 cudarc::driver::CudaEvent,
781 cudarc::driver::CudaEvent,
782 )>,
783> = std::sync::Mutex::new(None);
784
785static SHEXP_OV_WS: std::sync::Mutex<
787 Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>,
788> = std::sync::Mutex::new(None);
789
790impl HybridModel {
791 pub fn gdn_prime_grid_on(&self) -> bool {
797 Engine::gdn_chunked_enabled()
798 && self
799 .layers
800 .iter()
801 .any(|l| matches!(l.mixer, crate::hybrid::Mixer::Linear(_)))
802 }
803
804 fn step35_tp_device_resident(e: &Engine, tp: &crate::hybrid::StepTpQkv) -> bool {
809 tp.runtime.native_p2p() && tp.runtime.root_shares_ctx(e)
810 }
811
812 fn step35_tp_qkv(
813 &self,
814 e: &Engine,
815 fa: &FullAttnLayer,
816 h: &CudaSlice<f32>,
817 t: usize,
818 ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
819 let Some(tp) = fa.step_tp_qkv.as_ref() else {
820 return Ok(None);
821 };
822 let values = active_matrix_values(
823 h.len(),
824 t,
825 self.cfg.n_embd as usize,
826 "Step TP QKV activation",
827 )?;
828 if Self::step35_tp_device_resident(e, tp) {
837 e.stream().synchronize()?;
840 let q = tp
841 .runtime
842 .bf16_column_parallel_resident_native_device(&tp.q, h, t)?;
843 let k = tp
844 .runtime
845 .bf16_column_parallel_resident_native_device(&tp.k, h, t)?;
846 let v = tp
847 .runtime
848 .bf16_column_parallel_resident_native_device(&tp.v, h, t)?;
849 Self::step35_tp_log_once(tp, "qkv", "device-resident");
850 return Ok(Some(vec![q, k, v]));
851 }
852 let host = e.dtoh_view(&h.slice(0..values))?;
853 let q = if tp.runtime.native_p2p() {
854 tp.runtime
855 .bf16_column_parallel_resident_native(&tp.q, &host, t)?
856 } else {
857 tp.runtime
858 .bf16_column_parallel_resident(&tp.q, &host, t)?
859 .gathered
860 };
861 let k = if tp.runtime.native_p2p() {
862 tp.runtime
863 .bf16_column_parallel_resident_native(&tp.k, &host, t)?
864 } else {
865 tp.runtime
866 .bf16_column_parallel_resident(&tp.k, &host, t)?
867 .gathered
868 };
869 let v = if tp.runtime.native_p2p() {
870 tp.runtime
871 .bf16_column_parallel_resident_native(&tp.v, &host, t)?
872 } else {
873 tp.runtime
874 .bf16_column_parallel_resident(&tp.v, &host, t)?
875 .gathered
876 };
877 Self::step35_tp_log_once(tp, "qkv", "host-canonical");
878 Ok(Some(vec![e.htod(&q)?, e.htod(&k)?, e.htod(&v)?]))
879 }
880
881 fn step35_tp_log_once(tp: &crate::hybrid::StepTpQkv, proj: &str, activation: &'static str) {
885 use std::sync::atomic::{AtomicBool, Ordering};
886 static LOGGED: [AtomicBool; 4] = [
887 AtomicBool::new(false),
888 AtomicBool::new(false),
889 AtomicBool::new(false),
890 AtomicBool::new(false),
891 ];
892 let idx = 2 * usize::from(proj == "o") + usize::from(activation == "device-resident");
893 if LOGGED[idx].swap(true, Ordering::Relaxed) {
894 return;
895 }
896 eprintln!(
897 "[step-tp-{proj}] execute layer={} devices={:?} projections={proj} \
898 tensor_parallel=true attention_local=true kv_local=true transport={} \
899 native_p2p={} bulk_p2p={} activation={activation} \
900 output={} performance_claim=false (logged once per transport)",
901 tp.layer,
902 tp.devices,
903 tp.runtime.transport_label(),
904 tp.runtime.native_p2p(),
905 tp.runtime.bulk_p2p(),
906 if activation == "device-resident" {
907 "root-resident"
908 } else {
909 "root-readback"
910 },
911 );
912 }
913
914 fn step35_tp_o(
915 &self,
916 e: &Engine,
917 fa: &FullAttnLayer,
918 activation: &CudaSlice<f32>,
919 tokens: usize,
920 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
921 let Some(tp) = fa.step_tp_qkv.as_ref() else {
922 return Ok(None);
923 };
924 if Self::step35_tp_device_resident(e, tp) {
928 e.stream().synchronize()?; let output = tp
930 .runtime
931 .step_bf16_row_parallel_resident_native_device(&tp.o, activation, tokens)?;
932 Self::step35_tp_log_once(tp, "o", "device-resident");
933 return Ok(Some(output));
934 }
935 let host = e.dtoh(activation)?;
936 let output = if tp.runtime.native_p2p() {
937 tp.runtime
938 .step_bf16_row_parallel_resident_native(&tp.o, &host, tokens)?
939 } else {
940 tp.runtime
941 .step_bf16_row_parallel_resident(&tp.o, &host, tokens)?
942 };
943 Self::step35_tp_log_once(tp, "o", "host-canonical");
944 Ok(Some(e.htod(&output)?))
945 }
946
947 fn step35_o(
948 &self,
949 e: &Engine,
950 fa: &FullAttnLayer,
951 activation: &CudaSlice<f32>,
952 tokens: usize,
953 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
954 match self.step35_tp_o(e, fa, activation, tokens)? {
955 Some(output) => Ok(output),
956 None => e.matmul(&fa.wo, activation, tokens),
957 }
958 }
959
960 fn prime_trace_path() -> Option<&'static str> {
965 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
966 P.get_or_init(|| std::env::var("MEMRA_PRIME_TRACE").ok())
967 .as_deref()
968 }
969
970 fn prime_anatomy_on() -> bool {
976 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
977 *E.get_or_init(|| std::env::var("MEMRA_PRIME_ANATOMY").as_deref() == Ok("1"))
978 }
979
980 fn prime_anatomy_slots() -> &'static [std::sync::atomic::AtomicU64; 5] {
981 static S: [std::sync::atomic::AtomicU64; 5] = [
982 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), ];
988 &S
989 }
990
991 pub fn forward(
993 &self,
994 e: &Engine,
995 tokens: &[u32],
996 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
997 if self.is_gemma4_e4b() {
998 return self.gemma4_e4b_forward(e, tokens, false);
999 }
1000 if self.uses_gemma_program() {
1001 return self.gemma4_forward(e, tokens, false);
1002 }
1003 let cfg = &self.cfg;
1004 let n_embd = cfg.n_embd as usize;
1005 let t = tokens.len();
1006 let eps = cfg.rms_eps;
1007 let pos: Vec<i32> = (0..t as i32).collect();
1008 let pos_d = e.htod_i32(&pos)?;
1009
1010 let mut x = self.embed(e, tokens)?; for (il, layer) in self.layers.iter().enumerate() {
1013 let mut h = e.uninit(t * n_embd)?;
1015 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1016
1017 let mixed = match &layer.mixer {
1018 Mixer::Full(fa) => self.full_attn(e, fa, &h, &pos_d, t, il)?,
1019 Mixer::Linear(la) => self.linear_attn(e, la, &h, t)?,
1020 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1021 };
1022
1023 let mut x1 = e.uninit(t * n_embd)?;
1025 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1026
1027 let mut z = e.uninit(t * n_embd)?;
1029 e.rms_norm(
1030 &x1,
1031 layer.post_attn_norm.float_data(),
1032 &mut z,
1033 n_embd,
1034 t,
1035 eps,
1036 )?;
1037 let ffn_out = match &layer.ffn {
1038 crate::hybrid::Ffn::Dense {
1039 ffn_gate,
1040 ffn_up,
1041 ffn_down,
1042 } => {
1043 let n_ff = ffn_gate.out_features();
1044 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1045 let up = g2.pop().unwrap();
1046 let gate = g2.pop().unwrap();
1047 let mut act = e.uninit(t * n_ff)?;
1048 Self::ffn_act_lim(
1053 e,
1054 &self.cfg,
1055 &gate,
1056 &up,
1057 1.0,
1058 1.0,
1059 self.cfg.clamp_shexp_at(il as u32),
1060 &mut act,
1061 t * n_ff,
1062 )?;
1063 e.matmul(ffn_down, &act, t)?
1064 }
1065 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
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
1072 let mut hn = e.uninit(t * n_embd)?;
1073 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1074 let logits = e.matmul(&self.output, &hn, t)?;
1075 Ok(e.dtoh(&logits)?)
1076 }
1077
1078 pub fn forward_last(
1084 &self,
1085 e: &Engine,
1086 tokens: &[u32],
1087 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
1088 if self.uses_gemma_program() {
1089 return self.gemma4_forward(e, tokens, true);
1090 }
1091 let cfg = &self.cfg;
1092 let n_embd = cfg.n_embd as usize;
1093 let t = tokens.len();
1094 let eps = cfg.rms_eps;
1095 let pos: Vec<i32> = (0..t as i32).collect();
1096 let pos_d = e.htod_i32(&pos)?;
1097
1098 let mut x = self.embed(e, tokens)?; let probe = std::env::var("MEMRA_LAYER_PROBE").is_ok();
1102 let anat = Self::prime_anatomy_on();
1103 let mut anat_last = if anat {
1104 e.stream().synchronize()?;
1105 Some(std::time::Instant::now())
1106 } else {
1107 None
1108 };
1109 macro_rules! anat_mark {
1110 ($slot:expr) => {
1111 if let Some(ts) = anat_last.as_mut() {
1112 e.stream().synchronize()?;
1113 Self::prime_anatomy_slots()[$slot].fetch_add(
1114 ts.elapsed().as_nanos() as u64,
1115 std::sync::atomic::Ordering::Relaxed,
1116 );
1117 *ts = std::time::Instant::now();
1118 }
1119 };
1120 }
1121 for (il, layer) in self.layers.iter().enumerate() {
1122 let mut h = e.uninit(t * n_embd)?;
1123 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1124 if probe {
1125 e.stream().synchronize()?;
1126 eprintln!("[probe] L{il} norm ok");
1127 }
1128 anat_mark!(4);
1129 let mixed = match &layer.mixer {
1130 Mixer::Full(fa) => {
1131 let y = self.full_attn(e, fa, &h, &pos_d, t, il)?;
1132 anat_mark!(0);
1133 y
1134 }
1135 Mixer::Linear(la) => {
1136 let y = self.linear_attn(e, la, &h, t)?;
1137 anat_mark!(1);
1138 y
1139 }
1140 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1141 };
1142 if probe {
1143 e.stream().synchronize()?;
1144 eprintln!("[probe] L{il} mixer ok");
1145 }
1146 let mut x1 = e.uninit(t * n_embd)?;
1147 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1148 let mut z = e.uninit(t * n_embd)?;
1149 e.rms_norm(
1150 &x1,
1151 layer.post_attn_norm.float_data(),
1152 &mut z,
1153 n_embd,
1154 t,
1155 eps,
1156 )?;
1157 anat_mark!(4);
1158 let ffn_out = match &layer.ffn {
1159 crate::hybrid::Ffn::Dense {
1160 ffn_gate,
1161 ffn_up,
1162 ffn_down,
1163 } => {
1164 let n_ff = ffn_gate.out_features();
1165 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1166 let up = g2.pop().unwrap();
1167 let gate = g2.pop().unwrap();
1168 let mut act = e.uninit(t * n_ff)?;
1169 Self::ffn_act_lim(
1171 e,
1172 &self.cfg,
1173 &gate,
1174 &up,
1175 1.0,
1176 1.0,
1177 self.cfg.clamp_shexp_at(il as u32),
1178 &mut act,
1179 t * n_ff,
1180 )?;
1181 let y = e.matmul(ffn_down, &act, t)?;
1182 anat_mark!(3);
1183 y
1184 }
1185 crate::hybrid::Ffn::Moe(m) => {
1186 let y = self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?;
1187 anat_mark!(2);
1188 y
1189 }
1190 };
1191 if probe {
1192 e.stream().synchronize()?;
1193 eprintln!("[probe] L{il} ffn ok");
1194 }
1195 let mut x2 = e.uninit(t * n_embd)?;
1196 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1197 x = x2;
1198 }
1199 if anat {
1200 let s = Self::prime_anatomy_slots();
1201 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
1202 eprintln!(
1203 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
1204 dense={:.1} norms_adds={:.1} (t={t}, forward_last)",
1205 ms(0),
1206 ms(1),
1207 ms(2),
1208 ms(3),
1209 ms(4)
1210 );
1211 }
1212 let mut hn = e.uninit(t * n_embd)?;
1214 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1215 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)?;
1218 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
1219 let logits = e.matmul(&self.output, &hlast, 1)?; Ok(e.dtoh(&logits)?)
1221 }
1222
1223 pub fn prime_cache(
1255 &self,
1256 e: &Engine,
1257 tokens: &[u32],
1258 cache: &mut Cache,
1259 queued_after: usize,
1260 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1261 self.prime_cache_overlaid(e, tokens, cache, queued_after, None)
1262 }
1263
1264 pub fn prime_cache_overlaid(
1270 &self,
1271 e: &Engine,
1272 tokens: &[u32],
1273 cache: &mut Cache,
1274 queued_after: usize,
1275 overlay: Option<&crate::vision::EmbedOverlay>,
1276 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1277 let n_embd = self.cfg.n_embd as usize;
1278 let t = tokens.len();
1279 assert!(
1283 t >= PRIME_MIN_T,
1284 "prime_cache needs T >= {PRIME_MIN_T} (caller gates)"
1285 );
1286 assert!(
1287 cache.pos + t <= cache.max_ctx,
1288 "prime_cache: prompt exceeds cache max_ctx"
1289 );
1290
1291 if self.is_gemma4_e4b() || self.uses_gemma_program() {
1303 if self.is_gemma4_e4b() {
1304 if overlay.is_some() {
1305 return Err(
1306 "vision embedding overlay is unsupported on gemma4 E4B (PLE prime)".into(),
1307 );
1308 }
1309 return self.gemma4_e4b_prime(e, tokens, cache);
1310 }
1311 return self.gemma4_prime(e, tokens, cache, overlay);
1316 }
1317 let ranges = prime_chunk_ranges(t, self.layers.len(), self.gdn_prime_grid_on());
1318 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
1354 let seq_end = if legacy_calllocal {
1355 cache.pos + t
1356 } else {
1357 cache.pos + t + queued_after
1358 };
1359 if ranges.len() == 1 {
1360 return self.prime_chunk(e, tokens, cache, seq_end, 0, overlay);
1361 }
1362 if crate::pp::prime_pipe_on() && crate::pp::prime_pp_on() && !crate::pp::pp2_streams_off() {
1367 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()).filter(|f| f.len() == 3) {
1368 if overlay.is_some() {
1369 return Err(
1370 "vision embedding overlay + pipelined PP prime unsupported (v1); \
1371 run the serial prime (single device or MEMRA_PRIME_PIPE=0)"
1372 .into(),
1373 );
1374 }
1375 if crate::pp::pp_multi_stream_same_device() {
1376 return Err(
1377 "prime chunk pipeline refused with 2 stage streams on one device — \
1378 that concurrent-stream placement remains quarantined by the deferred \
1379 pp flake record. Use one device per stage or MEMRA_PRIME_PIPE=0 for \
1380 the serial split."
1381 .into(),
1382 );
1383 }
1384 return self.prime_cache_pp2_pipelined(e, tokens, cache, seq_end, &ranges, &fence);
1385 }
1386 }
1387 let mut hiddens = e.uninit(t * n_embd)?;
1388 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1389 for &(start, end) in &ranges {
1390 if let Some(taps) = cache.dflash_taps.as_mut() {
1392 taps.base = start;
1393 }
1394 let (l, hs, x) =
1395 self.prime_chunk(e, &tokens[start..end], cache, seq_end, start, overlay)?;
1396 e.copy_into(&mut hiddens, start * n_embd, &x, (end - start) * n_embd)?;
1397 last = Some((l, hs));
1398 }
1399 let (logits, h_seed) = last.unwrap();
1400 Ok((logits, h_seed, hiddens))
1401 }
1402
1403 fn prime_cache_pp2_pipelined(
1408 &self,
1409 e: &Engine,
1410 tokens: &[u32],
1411 cache: &mut Cache,
1412 seq_end: usize,
1413 ranges: &[(usize, usize)],
1414 fence: &[usize],
1415 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1416 debug_assert_eq!(fence.len(), 3);
1417 debug_assert!(ranges.len() >= 2);
1418 let rt = crate::pp::PpNRt::get(e)?;
1419 assert_eq!(
1420 rt.n_stages(),
1421 2,
1422 "prime pipeline requires exactly two PP stages"
1423 );
1424 let n_embd = self.cfg.n_embd as usize;
1425 let t = tokens.len();
1426 let initial_base = cache.pos;
1427 let caller_stream = e.stream();
1428
1429 rt.fence_stages_behind(&caller_stream)?;
1434 let max_payload = ranges.iter().map(|(s, e)| (e - s) * n_embd).max().unwrap();
1435 rt.prepare_overlap_slots(0, max_payload)?;
1436
1437 let mut hiddens = e.uninit(t * n_embd)?;
1438 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1439 let mut stage_caches = PrimeCacheStages::new(cache, fence[1]);
1440 let (cache0, cache1) = stage_caches.parts();
1441 let (first_start, first_end) = ranges[0];
1442 let mut slot = self.prime_pp2_stage0_enqueue(
1443 e,
1444 rt,
1445 &tokens[first_start..first_end],
1446 cache0,
1447 seq_end,
1448 fence,
1449 initial_base + first_start,
1450 true,
1451 )?;
1452 cache0.pos = initial_base + first_end;
1453
1454 for (i, &(start, end)) in ranges.iter().enumerate() {
1455 let base = initial_base + start;
1456 debug_assert_eq!(
1457 cache1.pos, base,
1458 "stage 1 must drain chunks in original position order"
1459 );
1460 let (out, next_slot) = if let Some(&(next_start, next_end)) = ranges.get(i + 1) {
1461 let next_base = initial_base + next_start;
1462 debug_assert_eq!(
1463 cache0.pos, next_base,
1464 "stage 0 must issue chunks in original position order"
1465 );
1466 let cache0_stage = &mut *cache0;
1467 std::thread::scope(|scope| -> Result<_, Box<dyn std::error::Error>> {
1472 let stage0 = scope.spawn(move || -> Result<usize, String> {
1473 let next = self
1474 .prime_pp2_stage0_enqueue(
1475 e,
1476 rt,
1477 &tokens[next_start..next_end],
1478 cache0_stage,
1479 seq_end,
1480 fence,
1481 next_base,
1482 true,
1483 )
1484 .map_err(|err| err.to_string())?;
1485 cache0_stage.pos = initial_base + next_end;
1486 Ok(next)
1487 });
1488 let x = self.prime_pp2_stage1_enqueue(
1489 e,
1490 rt,
1491 slot,
1492 end - start,
1493 cache1,
1494 seq_end,
1495 fence,
1496 base,
1497 true,
1498 )?;
1499 let out = {
1500 rt.bind_stage(1)?;
1501 let _st1 = rt.enter(1);
1502 let e1 = rt.engine(1, e);
1503 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1504 };
1505 let next = stage0
1506 .join()
1507 .map_err(|_| "pipeprime stage-0 host walker panicked")?
1508 .map_err(|err| -> Box<dyn std::error::Error> { err.into() })?;
1509 Ok((out, Some(next)))
1510 })?
1511 } else {
1512 let x = self.prime_pp2_stage1_enqueue(
1513 e,
1514 rt,
1515 slot,
1516 end - start,
1517 cache1,
1518 seq_end,
1519 fence,
1520 base,
1521 true,
1522 )?;
1523 let out = {
1524 rt.bind_stage(1)?;
1525 let _st1 = rt.enter(1);
1526 let e1 = rt.engine(1, e);
1527 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1528 };
1529 (out, None)
1530 };
1531
1532 rt.publish_to(1, &caller_stream)?;
1533 e.copy_into(&mut hiddens, start * n_embd, &out.2, (end - start) * n_embd)?;
1534 last = Some((out.0, out.1));
1535 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1536
1537 if let Some(next) = next_slot {
1538 rt.fence_stages_behind(&caller_stream)?;
1543 slot = next;
1544 }
1545 }
1546
1547 debug_assert_eq!(cache0.pos, initial_base + t);
1548 debug_assert_eq!(cache1.pos, initial_base + t);
1549 let (logits, h_seed) = last.unwrap();
1550 Ok((logits, h_seed, hiddens))
1551 }
1552
1553 fn gdn_hk(e: &Engine, t: usize, num_v: usize, num_k: usize) -> usize {
1560 if Engine::gdn_db_on()
1561 && Engine::gdn_chunked_enabled()
1562 && t >= 16
1563 && e.gdn_mma_enabled(Engine::gdn_chunk_size())
1564 && num_k * 2 == num_v
1565 {
1566 num_k
1567 } else {
1568 num_v
1569 }
1570 }
1571
1572 fn f16out_on(e: &Engine, t: usize) -> bool {
1577 crate::f16_ffi::pp_f16_enabled()
1578 && t >= 16
1579 && !e.verify_exact_on()
1580 && std::env::var("MEMRA_F16OUT").as_deref() != Ok("0")
1581 }
1582
1583 pub fn prime_slabs_get(
1591 &self,
1592 e: &Engine,
1593 t: usize,
1594 n_embd: usize,
1595 n_ff_max: usize,
1596 ) -> Result<std::sync::Arc<std::sync::Mutex<PrimeSlabs>>, Box<dyn std::error::Error>> {
1597 let mut slabs = self.prime_slabs.lock().unwrap();
1598 let dev = e.ctx().ordinal();
1599 let need_new = match slabs.get(&dev) {
1600 None => true,
1601 Some(sl) => sl.lock().unwrap().t_cap < t,
1602 };
1603 if need_new {
1604 slabs.insert(
1605 dev,
1606 std::sync::Arc::new(std::sync::Mutex::new(PrimeSlabs {
1607 t_cap: t,
1608 h: e.uninit(t * n_embd)?,
1609 x1: e.uninit(t * n_embd)?,
1610 z: e.uninit(t * n_embd)?,
1611 act: e.uninit(t * n_ff_max)?,
1612 xa: e.uninit(t * n_embd)?,
1613 xb: e.uninit(t * n_embd)?,
1614 h16: e.alloc_u8_uninit(t * n_embd * 2)?,
1615 z16: e.alloc_u8_uninit(t * n_embd * 2)?,
1616 gate: e.uninit(t * n_ff_max)?,
1617 up: e.uninit(t * n_ff_max)?,
1618 ffn_out: e.uninit(t * n_embd)?,
1619 seg_glue: Vec::new(),
1620 mixed: e.uninit(t * n_embd)?,
1621 seg_mid: Vec::new(),
1622 seg_t: 0,
1623 })),
1624 );
1625 }
1626 Ok(slabs.get(&dev).expect("prime slab inserted").clone())
1627 }
1628
1629 fn prime_chunk(
1633 &self,
1634 e: &Engine,
1635 tokens: &[u32],
1636 cache: &mut Cache,
1637 seq_end: usize,
1638 chunk_off: usize,
1639 overlay: Option<&crate::vision::EmbedOverlay>,
1640 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1641 if crate::pp::pp_host_bounce_active()
1642 && (self.uses_gemma_program() || !crate::pp::prime_pp_on())
1643 {
1644 return Err(
1645 "prime_chunk: refused with MEMRA_PP_HOST_BOUNCE=1 because this configuration \
1646 has no active prime stage split and would peer-read remote weights; keep \
1647 MEMRA_PRIME_PP enabled and use a PP-prime-supported model"
1648 .into(),
1649 );
1650 }
1651 if !self.uses_gemma_program() && !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
1660 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
1661 if overlay.is_some() {
1662 return Err("vision embedding overlay + PP prime unsupported (v1); \
1663 run single-device or MEMRA_PRIME_PP=0"
1664 .into());
1665 }
1666 return self.prime_chunk_ppn(e, tokens, cache, seq_end, &fence);
1667 }
1668 }
1669 if crate::pp::pp_host_bounce_active() {
1670 return Err(
1671 "prime_chunk: MEMRA_PP_HOST_BOUNCE=1 found no valid prime stage split; \
1672 refusing an unsplit remote-weight walk"
1673 .into(),
1674 );
1675 }
1676 let t = tokens.len();
1677 let base = cache.pos;
1678 debug_assert!(
1679 seq_end >= base + t,
1680 "prime_chunk: seq_end must cover this chunk"
1681 );
1682 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1683 let pos_d = e.htod_i32(&pos)?;
1684
1685 let mut x_embed = self.embed(e, tokens)?; if let Some(ov) = overlay {
1687 let n_embd = self.cfg.n_embd as usize;
1691 for &(pos, row_off, n_rows) in &ov.spans {
1692 let lo = pos.max(chunk_off);
1693 let hi = (pos + n_rows).min(chunk_off + t);
1694 if lo < hi {
1695 let src_row = row_off + (lo - pos);
1696 let view = ov
1697 .rows
1698 .slice(src_row * n_embd..(src_row + (hi - lo)) * n_embd);
1699 e.copy_view_into(
1700 &mut x_embed,
1701 (lo - chunk_off) * n_embd,
1702 &view,
1703 (hi - lo) * n_embd,
1704 )?;
1705 }
1706 }
1707 }
1708 let x = self.prime_layers(
1709 e,
1710 x_embed,
1711 0,
1712 self.layers.len(),
1713 &pos_d,
1714 t,
1715 base,
1716 cache,
1717 seq_end,
1718 )?;
1719 self.prime_chunk_epilogue(e, x, t, cache)
1720 }
1721
1722 #[allow(clippy::too_many_arguments)]
1738 fn prime_layers(
1739 &self,
1740 e: &Engine,
1741 x_in: CudaSlice<f32>,
1742 lo: usize,
1743 hi: usize,
1744 pos_d: &CudaSlice<i32>,
1745 t: usize,
1746 base: usize,
1747 cache: &mut Cache,
1748 seq_end: usize,
1749 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1750 let cfg = &self.cfg;
1751 let n_embd = cfg.n_embd as usize;
1752 let eps = cfg.rms_eps;
1753 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
1757 let n_ff_max = self
1764 .layers
1765 .iter()
1766 .map(|l| match &l.ffn {
1767 crate::hybrid::Ffn::Dense { ffn_gate, .. } => ffn_gate.out_features(),
1768 _ => n_embd,
1769 })
1770 .max()
1771 .unwrap_or(n_embd)
1772 .max(n_embd);
1773 let use_slabs = std::env::var("MEMRA_PRIME_SLABS").as_deref() != Ok("0");
1774 let slab = if use_slabs {
1775 Some(self.prime_slabs_get(e, t, n_embd, n_ff_max)?)
1776 } else {
1777 None
1778 };
1779 let mut slab_guard = slab.as_ref().map(|sl| sl.lock().unwrap());
1780 let mut x_own; type SlabRefs<'a> = (
1782 &'a mut CudaSlice<f32>,
1783 &'a mut CudaSlice<f32>,
1784 &'a mut CudaSlice<f32>,
1785 &'a mut CudaSlice<f32>,
1786 &'a mut CudaSlice<u8>,
1787 &'a mut CudaSlice<u8>,
1788 &'a mut CudaSlice<f32>,
1789 &'a mut CudaSlice<f32>,
1790 &'a mut CudaSlice<f32>,
1791 );
1792 let (mut x_cur, mut x_nxt, sl): (
1793 &mut CudaSlice<f32>,
1794 &mut CudaSlice<f32>,
1795 Option<SlabRefs>,
1796 );
1797 let mut seg: Option<(
1798 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1799 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1800 &mut CudaSlice<f32>,
1801 &mut usize,
1802 )> = None;
1803 let mut x_own2;
1804 match slab_guard.as_mut() {
1805 Some(g) => {
1806 let slabs = &mut **g;
1807 e.copy_into(&mut slabs.xa, 0, &x_in, t * n_embd)?;
1808 let PrimeSlabs {
1809 xa,
1810 xb,
1811 h,
1812 x1,
1813 z,
1814 act,
1815 h16,
1816 z16,
1817 gate,
1818 up,
1819 ffn_out,
1820 seg_glue,
1821 mixed,
1822 seg_mid,
1823 seg_t,
1824 ..
1825 } = slabs;
1826 x_cur = xa;
1827 x_nxt = xb;
1828 seg = Some((seg_glue, seg_mid, mixed, seg_t));
1829 sl = Some((h, x1, z, act, h16, z16, gate, up, ffn_out));
1830 }
1831 None => {
1832 x_own = x_in;
1833 x_own2 = e.uninit(t * n_embd)?;
1834 x_cur = &mut x_own;
1835 x_nxt = &mut x_own2;
1836 sl = None;
1837 }
1838 }
1839 let mut alloc_h;
1840 let mut alloc_x1;
1841 let mut alloc_z;
1842 let mut alloc_act;
1843 let mut alloc_h16;
1844 let mut alloc_z16;
1845 let mut alloc_gate;
1846 let mut alloc_up;
1847 let mut alloc_fo;
1848 let (h, x1, z, act): (
1849 &mut CudaSlice<f32>,
1850 &mut CudaSlice<f32>,
1851 &mut CudaSlice<f32>,
1852 &mut CudaSlice<f32>,
1853 );
1854 let (h16, z16): (&mut CudaSlice<u8>, &mut CudaSlice<u8>);
1855 let (sl_gate, sl_up, sl_fo): (
1856 &mut CudaSlice<f32>,
1857 &mut CudaSlice<f32>,
1858 &mut CudaSlice<f32>,
1859 );
1860 match sl {
1861 Some((a, b, c, d, e16, f16b, g, u, fo)) => {
1862 h = a;
1863 x1 = b;
1864 z = c;
1865 act = d;
1866 h16 = e16;
1867 z16 = f16b;
1868 sl_gate = g;
1869 sl_up = u;
1870 sl_fo = fo;
1871 }
1872 None => {
1873 alloc_h = e.uninit(t * n_embd)?;
1874 alloc_x1 = e.uninit(t * n_embd)?;
1875 alloc_z = e.uninit(t * n_embd)?;
1876 alloc_act = e.uninit(t * n_ff_max)?;
1877 alloc_h16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1878 alloc_z16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1879 alloc_gate = e.uninit(t * n_ff_max)?;
1880 alloc_up = e.uninit(t * n_ff_max)?;
1881 alloc_fo = e.uninit(t * n_embd)?;
1882 h = &mut alloc_h;
1883 x1 = &mut alloc_x1;
1884 z = &mut alloc_z;
1885 act = &mut alloc_act;
1886 h16 = &mut alloc_h16;
1887 z16 = &mut alloc_z16;
1888 sl_gate = &mut alloc_gate;
1889 sl_up = &mut alloc_up;
1890 sl_fo = &mut alloc_fo;
1891 }
1892 }
1893 let n_layers = self.layers.len();
1898 let use_seg = f16fuse
1908 && seg.is_some()
1909 && !self.uses_sliding_gated_moe_program()
1910 && lo == 0
1911 && hi == n_layers
1912 && std::env::var("MEMRA_PRIME_SEG").as_deref() == Ok("1");
1913 if let Some((sg, sm, _, st)) = seg.as_mut() {
1914 if **st != t {
1915 sg.clear();
1916 sg.extend((0..n_layers).map(|_| None));
1917 sm.clear();
1918 sm.extend((0..n_layers).map(|_| None));
1919 **st = t;
1920 }
1921 }
1922 {
1923 let layer_lo = &self.layers[lo];
1924 if f16fuse {
1925 e.rms_norm_f16out(
1926 x_cur,
1927 layer_lo.attn_norm.float_data(),
1928 h,
1929 h16,
1930 n_embd,
1931 t,
1932 eps,
1933 )?;
1934 } else {
1935 e.rms_norm(x_cur, layer_lo.attn_norm.float_data(), h, n_embd, t, eps)?;
1936 }
1937 }
1938 let anat = Self::prime_anatomy_on();
1939 let mut anat_last = if anat {
1940 e.stream().synchronize()?;
1941 Some(std::time::Instant::now())
1942 } else {
1943 None
1944 };
1945 macro_rules! anat_mark {
1947 ($slot:expr) => {
1948 if let Some(ts) = anat_last.as_mut() {
1949 e.stream().synchronize()?;
1950 Self::prime_anatomy_slots()[$slot].fetch_add(
1951 ts.elapsed().as_nanos() as u64,
1952 std::sync::atomic::Ordering::Relaxed,
1953 );
1954 *ts = std::time::Instant::now();
1955 }
1956 };
1957 }
1958 for il in lo..hi {
1959 let layer = &self.layers[il];
1960 let hx16 = if f16fuse { Some(&*h16) } else { None };
1961 if use_seg {
1962 let (pre, pre16, w_out) = match &layer.mixer {
1965 Mixer::Full(fa) => {
1966 let g3 = match hx16 {
1967 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
1968 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
1969 };
1970 let (pre, pre16) =
1971 self.full_attn_prime_core_inner(e, fa, g3, &pos_d, t, cache, il)?;
1972 (pre, pre16, &fa.wo)
1973 }
1974 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1975 Mixer::Linear(la) => {
1976 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
1977 let g4 = match hx16 {
1978 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
1979 None => e.matmul_group(&ws, h, t)?,
1980 };
1981 let (pre, pre16) =
1982 self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, None)?;
1983 (pre, pre16, &la.ssm_out)
1984 }
1985 };
1986 {
1987 let (_, sm, mslab, _) = seg.as_mut().unwrap();
1988 let pre_n = pre.len() / t;
1989 let xh_pre = match pre16 {
1990 Some(x) => x,
1991 None => e.f16_act(&pre, t * pre_n, pre_n)?,
1992 };
1993 if !e.try_f16_gemm_pre_into(w_out, &xh_pre, t, mslab)? {
1994 let y = e.matmul(w_out, &pre, t)?;
1995 e.copy_into(mslab, 0, &y, t * n_embd)?;
1996 }
1997 if sm[il].is_none() {
1998 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
1999 let w_post = layer.post_attn_norm.float_data();
2000 e.stream().synchronize()?;
2001 e.stream()
2002 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2003 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2004 e.add(x_cur, mslab, x1, t * n_embd)?;
2005 e.rms_norm_f16out(x1, w_post, z, z16, n_embd, t, eps)?;
2006 Ok(())
2007 })();
2008 let g = e.stream().end_capture(
2009 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH);
2010 r?;
2011 sm[il] = Some(g?.ok_or("S-mid capture produced no graph")?);
2012 }
2013 sm[il].as_ref().unwrap().launch()?;
2014 }
2015 } else {
2016 let mixed = match &layer.mixer {
2017 Mixer::Full(fa) => {
2018 let y =
2019 self.full_attn_prime(e, fa, h, hx16, &pos_d, t, cache, il, seq_end)?;
2020 anat_mark!(0);
2021 y
2022 }
2023 Mixer::Linear(la) => {
2024 let y = self.linear_attn_prime(e, la, h, hx16, t, cache, il)?;
2025 anat_mark!(1);
2026 y
2027 }
2028 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2029 };
2030 if f16fuse {
2031 e.add_rms_norm_f16out(
2034 x_cur,
2035 &mixed,
2036 layer.post_attn_norm.float_data(),
2037 x1,
2038 z,
2039 z16,
2040 n_embd,
2041 t,
2042 eps,
2043 )?;
2044 } else {
2045 e.add(x_cur, &mixed, x1, t * n_embd)?;
2046 e.rms_norm(x1, layer.post_attn_norm.float_data(), z, n_embd, t, eps)?;
2047 }
2048 anat_mark!(4);
2049 }
2050 let zx16 = if f16fuse { Some(&*z16) } else { None };
2051 match &layer.ffn {
2052 crate::hybrid::Ffn::Dense {
2053 ffn_gate,
2054 ffn_up,
2055 ffn_down,
2056 } => {
2057 let n_ff = ffn_gate.out_features();
2058 let mut into_ok = false;
2061 if let Some(xh) = zx16 {
2062 into_ok = e.try_f16_gemm_pre_into(ffn_gate, xh, t, sl_gate)?
2063 && e.try_f16_gemm_pre_into(ffn_up, xh, t, sl_up)?;
2064 }
2065 if !into_ok {
2066 let mut g2 = match zx16 {
2067 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], z, xh, t)?,
2068 None => e.matmul_group(&[ffn_gate, ffn_up], z, t)?,
2069 };
2070 let up_y = g2.pop().unwrap();
2071 let gate_y = g2.pop().unwrap();
2072 e.copy_into(sl_gate, 0, &gate_y, t * n_ff)?;
2073 e.copy_into(sl_up, 0, &up_y, t * n_ff)?;
2074 }
2075 let d_lim = self.cfg.clamp_shexp_at(il as u32);
2080 let act16 = if Self::f16out_on(e, t) && self.cfg.m3.is_none() && d_lim.is_none()
2081 {
2082 let mut a16 = e.alloc_u8_uninit(t * n_ff * 2)?;
2083 e.silu_mul_f16out(sl_gate, sl_up, act, &mut a16, t * n_ff)?;
2084 Some(a16)
2085 } else {
2086 Self::ffn_act_lim(
2087 e,
2088 &self.cfg,
2089 sl_gate,
2090 sl_up,
2091 1.0,
2092 1.0,
2093 d_lim,
2094 act,
2095 t * n_ff,
2096 )?;
2097 None
2098 };
2099 let xh_act = match act16 {
2101 Some(x) => x,
2102 None => e.f16_act(act, t * n_ff, n_ff)?,
2103 };
2104 if !e.try_f16_gemm_pre_into(ffn_down, &xh_act, t, sl_fo)? {
2105 let y = e.matmul(ffn_down, &*act, t)?;
2106 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2107 }
2108 }
2109 crate::hybrid::Ffn::Moe(m) => {
2110 let y = self.moe_ffn_il_prefill(e, m, z, t, il as u16)?;
2111 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2112 anat_mark!(2);
2113 }
2114 }
2115 if let (crate::hybrid::Ffn::Dense { .. }, true) = (&layer.ffn, anat) {
2116 anat_mark!(3);
2117 }
2118 if use_seg && il + 1 < hi {
2119 let w_next = self.layers[il + 1].attn_norm.float_data();
2121 let (sg, _, _, _) = seg.as_mut().unwrap();
2122 if sg[il].is_none() {
2123 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
2124 e.stream().synchronize()?;
2125 e.stream()
2126 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2127 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2128 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2129 e.rms_norm_f16out(x_nxt, w_next, h, h16, n_embd, t, eps)?;
2130 Ok(())
2131 })();
2132 let g = e.stream().end_capture(
2133 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
2134 );
2135 r?;
2136 sg[il] = Some(g?.ok_or("S-glue capture produced no graph")?);
2137 }
2138 sg[il].as_ref().unwrap().launch()?;
2139 } else {
2140 if il + 1 < hi {
2141 let w_next = self.layers[il + 1].attn_norm.float_data();
2142 if f16fuse {
2143 e.add_rms_norm_f16out(x1, sl_fo, w_next, x_nxt, h, h16, n_embd, t, eps)?;
2144 } else {
2145 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2146 e.rms_norm(x_nxt, w_next, h, n_embd, t, eps)?;
2147 }
2148 } else {
2149 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2150 }
2151 }
2152 anat_mark!(4);
2153 if let Some(path) = Self::prime_trace_path() {
2159 let row = (base + t - 1) as usize;
2160 let host = e.dtoh(x_nxt)?;
2161 let last = &host[(t - 1) * n_embd..t * n_embd];
2162 use std::io::Write as _;
2163 let mut f = std::fs::OpenOptions::new()
2164 .create(true)
2165 .append(true)
2166 .open(path)?;
2167 let mut h64: u64 = 0xcbf29ce484222325;
2168 for v in last {
2169 h64 ^= v.to_bits() as u64;
2170 h64 = h64.wrapping_mul(0x100000001b3);
2171 }
2172 writeln!(
2173 f,
2174 "{{\"pos\":{row},\"layer\":{il},\"t\":{t},\"base\":{base},\
2175 \"hash\":\"{h64:016x}\",\"v0\":{:.9e},\"v1\":{:.9e},\"v2\":{:.9e}}}",
2176 last[0], last[1], last[2]
2177 )?;
2178 }
2179 self.dflash_tap(e, cache, il, x_nxt, t)?;
2182 std::mem::swap(&mut x_cur, &mut x_nxt);
2183 }
2184 if anat {
2185 let s = Self::prime_anatomy_slots();
2186 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
2187 eprintln!(
2188 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
2189 dense={:.1} norms_adds={:.1} (t={t}, layers {lo}..{hi})",
2190 ms(0),
2191 ms(1),
2192 ms(2),
2193 ms(3),
2194 ms(4)
2195 );
2196 }
2197 let mut x = e.uninit(t * n_embd)?;
2199 e.copy_into(&mut x, 0, x_cur, t * n_embd)?;
2200 drop(slab_guard);
2201 Ok(x)
2202 }
2203
2204 fn prime_chunk_epilogue(
2209 &self,
2210 e: &Engine,
2211 x: CudaSlice<f32>,
2212 t: usize,
2213 cache: &mut Cache,
2214 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2215 let n_embd = self.cfg.n_embd as usize;
2216 let eps = self.cfg.rms_eps;
2217 let mut h_seed = e.uninit(n_embd)?;
2221 if !crate::spec::spec_hpost() {
2222 e.copy_view_into(
2223 &mut h_seed,
2224 0,
2225 &x.slice((t - 1) * n_embd..t * n_embd),
2226 n_embd,
2227 )?;
2228 }
2229 let mut hn = e.uninit(t * n_embd)?;
2231 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2232 if crate::spec::spec_hpost() {
2233 e.copy_view_into(
2234 &mut h_seed,
2235 0,
2236 &hn.slice((t - 1) * n_embd..t * n_embd),
2237 n_embd,
2238 )?;
2239 }
2240 let last = e.view(&hn, t * n_embd);
2241 let last_row = last.slice((t - 1) * n_embd..t * n_embd);
2242 let mut hlast = e.uninit(n_embd)?;
2243 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
2244 let logits = e.matmul(&self.output, &hlast, 1)?;
2245 cache.pos += t;
2246 Ok((
2249 e.dtoh(&logits)?,
2250 h_seed,
2251 if crate::spec::spec_hpost() { hn } else { x },
2252 ))
2253 }
2254
2255 fn prime_chunk_ppn(
2279 &self,
2280 e: &Engine,
2281 tokens: &[u32],
2282 cache: &mut Cache,
2283 seq_end: usize,
2284 fence: &[usize],
2285 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2286 let rt = crate::pp::PpNRt::get(e)?;
2287 let n_st = fence.len() - 1;
2288 assert_eq!(
2289 rt.n_stages(),
2290 n_st,
2291 "PpNRt stage count {} != fence stages {n_st}",
2292 rt.n_stages()
2293 );
2294 let n_embd = self.cfg.n_embd as usize;
2295 let t = tokens.len();
2296 let base = cache.pos;
2297 debug_assert!(
2298 seq_end >= base + t,
2299 "prime_chunk_ppn: seq_end must cover this chunk"
2300 );
2301 let payload = t * n_embd;
2302 let caller_stream = e.stream();
2306 rt.fence_stages_behind(&caller_stream)?;
2307
2308 if n_st == 2 {
2309 let slot =
2310 self.prime_pp2_stage0_enqueue(e, rt, tokens, cache, seq_end, fence, base, false)?;
2311 let x =
2312 self.prime_pp2_stage1_enqueue(e, rt, slot, t, cache, seq_end, fence, base, false)?;
2313 let out = {
2314 rt.bind_stage(1)?;
2315 let _st1 = rt.enter(1);
2316 let e1 = rt.engine(1, e);
2317 self.prime_chunk_epilogue(e1, x, t, cache)?
2318 };
2319 rt.publish_to(1, &caller_stream)?;
2320 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2321 return Ok(out);
2322 }
2323
2324 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2325
2326 let mut slot = {
2328 let _st0 = rt.enter(0);
2329 let e0 = rt.engine(0, e);
2330 let pos_d = e0.htod_i32(&pos)?;
2331 let x = self.embed(e0, tokens)?;
2332 let x =
2333 self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2334 rt.tx(0, &x, payload)?
2335 };
2337
2338 for s in 1..n_st - 1 {
2340 let _st = rt.enter(s);
2341 let es = rt.engine(s, e);
2342 let pos_d = es.htod_i32(&pos)?;
2343 let x = rt.rx(s - 1, slot, payload)?;
2344 let x = self.prime_layers(
2345 es,
2346 x,
2347 fence[s],
2348 fence[s + 1],
2349 &pos_d,
2350 t,
2351 base,
2352 cache,
2353 seq_end,
2354 )?;
2355 slot = rt.tx(s, &x, payload)?;
2356 }
2357
2358 let _stl = rt.enter(n_st - 1);
2360 let el = rt.engine(n_st - 1, e);
2361 let pos_d = el.htod_i32(&pos)?;
2362 let x = rt.rx(n_st - 2, slot, payload)?;
2363 let x = self.prime_layers(
2364 el,
2365 x,
2366 fence[n_st - 1],
2367 fence[n_st],
2368 &pos_d,
2369 t,
2370 base,
2371 cache,
2372 seq_end,
2373 )?;
2374 let out = self.prime_chunk_epilogue(el, x, t, cache)?;
2375 rt.publish_to(n_st - 1, &caller_stream)?;
2381 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2382 Ok(out)
2383 }
2384
2385 fn prime_pp2_stage0_enqueue(
2386 &self,
2387 e: &Engine,
2388 rt: &crate::pp::PpNRt,
2389 tokens: &[u32],
2390 cache: &mut Cache,
2391 seq_end: usize,
2392 fence: &[usize],
2393 base: usize,
2394 pipelined: bool,
2395 ) -> Result<usize, Box<dyn std::error::Error>> {
2396 let t = tokens.len();
2397 let n_embd = self.cfg.n_embd as usize;
2398 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2399 rt.bind_stage(0)?;
2400 let _st0 = rt.enter(0);
2401 let e0 = rt.engine(0, e);
2402 let pos_d = e0.htod_i32(&pos)?;
2403 let x = self.embed(e0, tokens)?;
2404 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2405 let x = self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2406 if pipelined {
2407 rt.tx_pipelined(0, &x, t * n_embd)
2408 } else {
2409 rt.tx(0, &x, t * n_embd)
2410 }
2411 }
2412
2413 fn prime_pp2_stage1_enqueue(
2414 &self,
2415 e: &Engine,
2416 rt: &crate::pp::PpNRt,
2417 slot: usize,
2418 t: usize,
2419 cache: &mut Cache,
2420 seq_end: usize,
2421 fence: &[usize],
2422 base: usize,
2423 pipelined: bool,
2424 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2425 let n_embd = self.cfg.n_embd as usize;
2426 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2427 rt.bind_stage(1)?;
2428 let _st1 = rt.enter(1);
2429 let e1 = rt.engine(1, e);
2430 let pos_d = e1.htod_i32(&pos)?;
2431 let x = rt.rx(0, slot, t * n_embd)?;
2432 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2433 self.prime_layers(e1, x, fence[1], fence[2], &pos_d, t, base, cache, seq_end)
2434 }
2435
2436 pub fn prime_chunk_captured(
2452 &self,
2453 e: &Engine,
2454 x_in: &CudaSlice<f32>,
2455 pos_d: &CudaSlice<i32>,
2456 t: usize,
2457 cache: &mut Cache,
2458 len_d: &CudaSlice<i32>,
2459 logits_out: &mut CudaSlice<f32>,
2460 h_seed_out: &mut CudaSlice<f32>,
2461 ) -> Result<(), Box<dyn std::error::Error>> {
2462 let cfg = &self.cfg;
2463 let n_embd = cfg.n_embd as usize;
2464 let eps = cfg.rms_eps;
2465 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
2466 let mut x = e.uninit(t * n_embd)?;
2467 e.copy_into(&mut x, 0, x_in, t * n_embd)?;
2468 for (il, layer) in self.layers.iter().enumerate() {
2469 let mut h = e.uninit(t * n_embd)?;
2470 let mut hx16: Option<CudaSlice<u8>> = None;
2471 if f16fuse {
2472 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2473 e.rms_norm_f16out(
2474 &x,
2475 layer.attn_norm.float_data(),
2476 &mut h,
2477 &mut b16,
2478 n_embd,
2479 t,
2480 eps,
2481 )?;
2482 hx16 = Some(b16);
2483 } else {
2484 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
2485 }
2486 let mixed = match &layer.mixer {
2487 Mixer::Full(fa) => {
2491 self.full_attn_prime(e, fa, &h, hx16.as_ref(), pos_d, t, cache, il, t)?
2492 }
2493 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2494 Mixer::Linear(la) => {
2495 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2496 let g4 = match hx16.as_ref() {
2497 Some(xh) => e.matmul_group_xh(&ws, &h, xh, t)?,
2498 None => e.matmul_group(&ws, &h, t)?,
2499 };
2500 self.linear_attn_prime_core_pad(e, la, g4, t, cache, il, Some(len_d))?
2501 }
2502 };
2503 let mut x1 = e.uninit(t * n_embd)?;
2504 e.add(&x, &mixed, &mut x1, t * n_embd)?;
2505 let mut z = e.uninit(t * n_embd)?;
2506 let mut zx16: Option<CudaSlice<u8>> = None;
2507 if f16fuse {
2508 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2509 e.rms_norm_f16out(
2510 &x1,
2511 layer.post_attn_norm.float_data(),
2512 &mut z,
2513 &mut b16,
2514 n_embd,
2515 t,
2516 eps,
2517 )?;
2518 zx16 = Some(b16);
2519 } else {
2520 e.rms_norm(
2521 &x1,
2522 layer.post_attn_norm.float_data(),
2523 &mut z,
2524 n_embd,
2525 t,
2526 eps,
2527 )?;
2528 }
2529 let ffn_out = match &layer.ffn {
2530 crate::hybrid::Ffn::Dense {
2531 ffn_gate,
2532 ffn_up,
2533 ffn_down,
2534 } => {
2535 let n_ff = ffn_gate.out_features();
2536 let mut g2 = match &zx16 {
2537 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], &z, xh, t)?,
2538 None => e.matmul_group(&[ffn_gate, ffn_up], &z, t)?,
2539 };
2540 let up = g2.pop().unwrap();
2541 let gate = g2.pop().unwrap();
2542 let mut act = e.uninit(t * n_ff)?;
2543 Self::ffn_act_lim(
2545 e,
2546 &self.cfg,
2547 &gate,
2548 &up,
2549 1.0,
2550 1.0,
2551 self.cfg.clamp_shexp_at(il as u32),
2552 &mut act,
2553 t * n_ff,
2554 )?;
2555 e.matmul(ffn_down, &act, t)?
2556 }
2557 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
2558 };
2559 let mut x2 = e.uninit(t * n_embd)?;
2560 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
2561 x = x2;
2562 }
2563 if !crate::spec::spec_hpost() {
2565 e.row_gather_dev(&x, h_seed_out, len_d, n_embd)?;
2566 }
2567 let mut hn = e.uninit(t * n_embd)?;
2568 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2569 if crate::spec::spec_hpost() {
2570 e.row_gather_dev(&hn, h_seed_out, len_d, n_embd)?;
2571 }
2572 let mut hlast = e.uninit(n_embd)?;
2573 e.row_gather_dev(&hn, &mut hlast, len_d, n_embd)?;
2574 let logits = e.matmul(&self.output, &hlast, 1)?;
2575 let nv = logits.len();
2576 e.copy_into(logits_out, 0, &logits, nv)?;
2577 Ok(())
2578 }
2579
2580 fn step35_prime_batch_on() -> bool {
2581 std::env::var("MEMRA_STEP35_PRIME_BATCH").as_deref() != Ok("0")
2582 }
2583
2584 #[allow(clippy::too_many_arguments)]
2587 fn step35_prime_batch_layers(
2588 &self,
2589 e: &Engine,
2590 mut x: CudaSlice<f32>,
2591 lo: usize,
2592 hi: usize,
2593 ts: &[usize],
2594 offs: &[usize],
2595 pos_ds: &[CudaSlice<i32>],
2596 caches: &mut [&mut Cache],
2597 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2598 let cfg = &self.cfg;
2599 let n_embd = cfg.n_embd as usize;
2600 let eps = cfg.rms_eps;
2601 let b = ts.len();
2602 let total: usize = ts.iter().sum();
2603 let f16fuse = crate::f16_ffi::pp_f16_enabled() && total >= 16;
2604
2605 let split = |e: &Engine,
2606 y: &CudaSlice<f32>,
2607 dim: usize|
2608 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2609 let mut out = Vec::with_capacity(b);
2610 for s in 0..b {
2611 let mut ys = e.uninit(ts[s] * dim)?;
2612 e.copy_view_into(
2613 &mut ys,
2614 0,
2615 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2616 ts[s] * dim,
2617 )?;
2618 out.push(ys);
2619 }
2620 Ok(out)
2621 };
2622
2623 for il in lo..hi {
2624 let layer = &self.layers[il];
2625 let Mixer::Full(fa) = &layer.mixer else {
2626 return Err(format!("step35 layer {il} is not full-attn — corrupt config").into());
2627 };
2628
2629 let mut h = e.uninit(total * n_embd)?;
2630 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2631 if f16fuse {
2632 e.rms_norm_f16out(
2633 &x,
2634 layer.attn_norm.float_data(),
2635 &mut h,
2636 &mut hx16,
2637 n_embd,
2638 total,
2639 eps,
2640 )?;
2641 } else {
2642 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, total, eps)?;
2643 }
2644
2645 let gate_w = fa
2649 .attn_gate
2650 .as_ref()
2651 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
2652 let mut g4 = if f16fuse {
2653 e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, &hx16, total)?
2654 } else {
2655 e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, total)?
2656 };
2657 let gate = g4.pop().unwrap();
2658 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2659 (0..b).map(|_| Vec::with_capacity(3)).collect();
2660 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g4) {
2661 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2662 parts[s].push(ys);
2663 }
2664 }
2665 let gates = split(e, &gate, gate_w.out_features())?;
2666 let geometry = self.step35_geom(il);
2667 let hd = geometry.head_dim_k as usize;
2668 let nh = geometry.n_head as usize;
2669 let mut ag_cat = e.uninit(total * nh * hd)?;
2670 for (s, (g3s, gate)) in parts.into_iter().zip(gates).enumerate() {
2671 let ag = self.step35_attn_pre_wo(
2672 e,
2673 fa,
2674 g3s,
2675 None,
2676 Some(&gate),
2677 &pos_ds[s],
2678 ts[s],
2679 Some(&mut *caches[s]),
2680 il,
2681 ts[s],
2682 )?;
2683 e.copy_into(&mut ag_cat, offs[s] * nh * hd, &ag, ts[s] * nh * hd)?;
2684 }
2685 let mixed = e.matmul(&fa.wo, &ag_cat, total)?;
2686
2687 let mut x1 = e.uninit(total * n_embd)?;
2688 let mut z = e.uninit(total * n_embd)?;
2689 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2690 if f16fuse {
2691 e.add_rms_norm_f16out(
2692 &x,
2693 &mixed,
2694 layer.post_attn_norm.float_data(),
2695 &mut x1,
2696 &mut z,
2697 &mut zx16,
2698 n_embd,
2699 total,
2700 eps,
2701 )?;
2702 } else {
2703 e.add(&x, &mixed, &mut x1, total * n_embd)?;
2704 e.rms_norm(
2705 &x1,
2706 layer.post_attn_norm.float_data(),
2707 &mut z,
2708 n_embd,
2709 total,
2710 eps,
2711 )?;
2712 }
2713
2714 let ffn_out = match &layer.ffn {
2715 crate::hybrid::Ffn::Dense {
2716 ffn_gate,
2717 ffn_up,
2718 ffn_down,
2719 } => {
2720 let n_ff = ffn_gate.out_features();
2721 let mut g2 = if f16fuse {
2722 e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?
2723 } else {
2724 e.matmul_group(&[ffn_gate, ffn_up], &z, total)?
2725 };
2726 let up = g2.pop().unwrap();
2727 let gate = g2.pop().unwrap();
2728 let mut act = e.uninit(total * n_ff)?;
2729 let d_lim = cfg.clamp_shexp_at(il as u32);
2730 if Self::f16out_on(e, total) && cfg.m3.is_none() && d_lim.is_none() {
2731 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2732 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2733 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2734 Some(y) => y,
2735 None => e.matmul(ffn_down, &act, total)?,
2736 }
2737 } else {
2738 Self::ffn_act_lim(
2739 e,
2740 cfg,
2741 &gate,
2742 &up,
2743 1.0,
2744 1.0,
2745 d_lim,
2746 &mut act,
2747 total * n_ff,
2748 )?;
2749 e.matmul(ffn_down, &act, total)?
2750 }
2751 }
2752 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il(e, m, &z, total, il as u16)?,
2753 };
2754 let mut x2 = e.uninit(total * n_embd)?;
2755 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2756 x = x2;
2757 }
2758 Ok(x)
2759 }
2760
2761 fn step35_prime_batch_epilogue(
2762 &self,
2763 e: &Engine,
2764 x: CudaSlice<f32>,
2765 ts: &[usize],
2766 offs: &[usize],
2767 caches: &mut [&mut Cache],
2768 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2769 let n_embd = self.cfg.n_embd as usize;
2770 let total: usize = ts.iter().sum();
2771 let mut hn = e.uninit(total * n_embd)?;
2772 e.rms_norm(
2773 &x,
2774 self.output_norm.float_data(),
2775 &mut hn,
2776 n_embd,
2777 total,
2778 self.cfg.rms_eps,
2779 )?;
2780
2781 let hidden_src = if crate::spec::spec_hpost() { &hn } else { &x };
2782 let mut out = Vec::with_capacity(ts.len());
2783 for s in 0..ts.len() {
2784 let mut hidden = e.uninit(ts[s] * n_embd)?;
2785 e.copy_view_into(
2786 &mut hidden,
2787 0,
2788 &hidden_src.slice(offs[s] * n_embd..(offs[s] + ts[s]) * n_embd),
2789 ts[s] * n_embd,
2790 )?;
2791 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2792 let mut h_seed = e.uninit(n_embd)?;
2793 e.copy_view_into(
2794 &mut h_seed,
2795 0,
2796 &hidden_src.slice(last0..last0 + n_embd),
2797 n_embd,
2798 )?;
2799 let mut hlast = e.uninit(n_embd)?;
2801 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2802 let logits = e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?;
2803 caches[s].pos += ts[s];
2804 out.push((logits, h_seed, hidden));
2805 }
2806 Ok(out)
2807 }
2808
2809 fn step35_prime_cache_batch(
2810 &self,
2811 e: &Engine,
2812 prompts: &[&[u32]],
2813 caches: &mut [&mut Cache],
2814 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2815 validate_step_prime_batch_modes(
2816 step_tp_prefill_enabled()?,
2817 step_ep_grouped_prefill_enabled()?,
2818 )?;
2819 if crate::pp::pp_host_bounce_active()
2820 && (!crate::pp::prime_pp_on() || crate::pp::pp_cuts(self.layers.len()).is_none())
2821 {
2822 return Err(
2823 "step35_prime_cache_batch: MEMRA_PP_HOST_BOUNCE=1 requires a valid prime \
2824 stage split; refusing an unsplit remote-weight walk"
2825 .into(),
2826 );
2827 }
2828 if !Self::step35_prime_batch_on() {
2829 return Err("step35 batched prime is disabled (MEMRA_STEP35_PRIME_BATCH=0)".into());
2830 }
2831 if caches.iter().any(|c| c.pos != 0) {
2832 return Err(
2833 "step35 batched prime currently supports complete fresh prompts only; \
2834 continuation/tick chunks require per-request queued_after"
2835 .into(),
2836 );
2837 }
2838
2839 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2840 for &t in &ts {
2841 assert!(
2842 t >= PRIME_MIN_T,
2843 "step35 batched prime needs T >= {PRIME_MIN_T}"
2844 );
2845 }
2846 for (s, c) in caches.iter().enumerate() {
2847 assert!(
2848 ts[s] <= c.max_ctx,
2849 "step35 batched prime exceeds cache max_ctx"
2850 );
2851 }
2852 let offs: Vec<usize> = ts
2853 .iter()
2854 .scan(0usize, |a, &t| {
2855 let o = *a;
2856 *a += t;
2857 Some(o)
2858 })
2859 .collect();
2860 let total: usize = ts.iter().sum();
2861 let payload = total * self.cfg.n_embd as usize;
2862 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2863 let positions: Vec<Vec<i32>> = ts.iter().map(|&t| (0..t as i32).collect()).collect();
2864 let upload_positions =
2865 |e: &Engine| -> Result<Vec<CudaSlice<i32>>, Box<dyn std::error::Error>> {
2866 positions
2867 .iter()
2868 .map(|p| e.htod_i32(p))
2869 .collect::<Result<_, _>>()
2870 };
2871
2872 static ONCE: std::sync::Once = std::sync::Once::new();
2873 ONCE.call_once(|| {
2874 eprintln!(
2875 "[step35-prime-batch] first concat prime: B={} tokens={total}",
2876 prompts.len()
2877 );
2878 });
2879
2880 let out = if !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
2881 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
2882 let rt = crate::pp::PpNRt::get(e)?;
2883 let n_st = fence.len() - 1;
2884 assert_eq!(
2885 rt.n_stages(),
2886 n_st,
2887 "step35 prime batch stage count mismatch"
2888 );
2889 let caller_stream = e.stream();
2890 rt.fence_stages_behind(&caller_stream)?;
2891
2892 let mut slot = {
2893 let _st0 = rt.enter(0);
2894 let e0 = rt.engine(0, e);
2895 let pos_ds = upload_positions(e0)?;
2896 let x = self.embed(e0, &cat_tokens)?;
2897 let x = self.step35_prime_batch_layers(
2898 e0, x, fence[0], fence[1], &ts, &offs, &pos_ds, caches,
2899 )?;
2900 rt.tx(0, &x, payload)?
2901 };
2902 for s in 1..n_st - 1 {
2903 let _st = rt.enter(s);
2904 let es = rt.engine(s, e);
2905 let pos_ds = upload_positions(es)?;
2906 let x = rt.rx(s - 1, slot, payload)?;
2907 let x = self.step35_prime_batch_layers(
2908 es,
2909 x,
2910 fence[s],
2911 fence[s + 1],
2912 &ts,
2913 &offs,
2914 &pos_ds,
2915 caches,
2916 )?;
2917 slot = rt.tx(s, &x, payload)?;
2918 }
2919
2920 let _stl = rt.enter(n_st - 1);
2921 let el = rt.engine(n_st - 1, e);
2922 let pos_ds = upload_positions(el)?;
2923 let x = rt.rx(n_st - 2, slot, payload)?;
2924 let x = self.step35_prime_batch_layers(
2925 el,
2926 x,
2927 fence[n_st - 1],
2928 fence[n_st],
2929 &ts,
2930 &offs,
2931 &pos_ds,
2932 caches,
2933 )?;
2934 let out = self.step35_prime_batch_epilogue(el, x, &ts, &offs, caches)?;
2935 rt.publish_to(n_st - 1, &caller_stream)?;
2936 crate::pp::STEP35_PRIME_BATCH_SPLITS
2937 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2938 out
2939 } else {
2940 let pos_ds = upload_positions(e)?;
2941 let x = self.embed(e, &cat_tokens)?;
2942 let x = self.step35_prime_batch_layers(
2943 e,
2944 x,
2945 0,
2946 self.layers.len(),
2947 &ts,
2948 &offs,
2949 &pos_ds,
2950 caches,
2951 )?;
2952 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2953 }
2954 } else {
2955 let pos_ds = upload_positions(e)?;
2956 let x = self.embed(e, &cat_tokens)?;
2957 let x = self.step35_prime_batch_layers(
2958 e,
2959 x,
2960 0,
2961 self.layers.len(),
2962 &ts,
2963 &offs,
2964 &pos_ds,
2965 caches,
2966 )?;
2967 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2968 };
2969 crate::pp::STEP35_PRIME_BATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2970 Ok(out)
2971 }
2972
2973 pub fn prime_cache_batch(
2990 &self,
2991 e: &Engine,
2992 prompts: &[&[u32]],
2993 caches: &mut [&mut Cache],
2994 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2995 if crate::pp::pp_cuts(self.layers.len()).is_some()
2996 && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
2997 {
2998 return Err("pipeline rewrite is not qualified for batched prime".into());
2999 }
3000 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::CarriedPrime) {
3001 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::DecodeEager) {
3002 return Err("neither batched-prime nor eager rewrite is qualified".into());
3003 }
3004 if prompts.len() != caches.len() {
3005 return Err("prime fallback prompt/cache shape mismatch".into());
3006 }
3007 static ONCE: std::sync::Once = std::sync::Once::new();
3008 ONCE.call_once(|| {
3009 eprintln!(
3010 "[rewrite] carried-prime.v1 unqualified; using individual native eager primes"
3011 );
3012 });
3013 return prompts
3014 .iter()
3015 .copied()
3016 .zip(caches.iter_mut())
3017 .map(|(prompt, cache)| self.prime_cache(e, prompt, cache, 0))
3018 .collect();
3019 }
3020 let cfg = &self.cfg;
3021 let n_embd = cfg.n_embd as usize;
3022 let eps = cfg.rms_eps;
3023 let b = prompts.len();
3024 assert!(b >= 1 && b == caches.len());
3025 let pos0s: Vec<usize> = caches.iter().map(|c| c.pos).collect();
3026 let carried = pos0s.iter().any(|&p| p > 0);
3027 if self.uses_gemma_program() {
3033 return Err(
3034 "prime_cache_batch: gemma4 has no batched prime core (per-layer \
3035 swa/global geometry, softcapped head) — use gemma4_prime per sequence"
3036 .into(),
3037 );
3038 }
3039 if self.uses_sliding_gated_moe_program() {
3042 return self.step35_prime_cache_batch(e, prompts, caches);
3043 }
3044 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
3045 for &t in &ts {
3046 assert!(
3047 t >= PRIME_MIN_T,
3048 "prime_cache_batch needs T >= {PRIME_MIN_T}"
3049 );
3050 }
3051 for (s, c) in caches.iter().enumerate() {
3052 assert!(
3053 c.pos + ts[s] <= c.max_ctx,
3054 "prime_cache_batch: prompt exceeds cache max_ctx"
3055 );
3056 }
3057 let total: usize = ts.iter().sum();
3058 let offs: Vec<usize> = ts
3059 .iter()
3060 .scan(0usize, |a, &t| {
3061 let o = *a;
3062 *a += t;
3063 Some(o)
3064 })
3065 .collect();
3066 let pos_ds: Vec<CudaSlice<i32>> = ts
3068 .iter()
3069 .zip(&pos0s)
3070 .map(|(&t, &p0)| e.htod_i32(&(p0 as i32..(p0 + t) as i32).collect::<Vec<_>>()))
3071 .collect::<Result<_, _>>()?;
3072 let split = |e: &Engine,
3074 y: &CudaSlice<f32>,
3075 dim: usize|
3076 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3077 let mut out = Vec::with_capacity(b);
3078 for s in 0..b {
3079 let mut ys = e.uninit(ts[s] * dim)?;
3080 e.copy_view_into(
3081 &mut ys,
3082 0,
3083 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
3084 ts[s] * dim,
3085 )?;
3086 out.push(ys);
3087 }
3088 Ok(out)
3089 };
3090
3091 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
3092 let mut x = self.embed(e, &cat_tokens)?; for (il, layer) in self.layers.iter().enumerate() {
3094 let mut h = e.uninit(total * n_embd)?;
3095 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3096 e.rms_norm_f16out(
3097 &x,
3098 layer.attn_norm.float_data(),
3099 &mut h,
3100 &mut hx16,
3101 n_embd,
3102 total,
3103 eps,
3104 )?;
3105 let mut mixed = e.uninit(total * n_embd)?;
3107 match &layer.mixer {
3108 Mixer::Full(fa) => {
3109 let g3 = e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], &h, &hx16, total)?;
3110 let geometry = self.cfg.full_attention_geometry_at(il as u32);
3116 let (n_head, n_head_kv, head_dim) = (
3117 geometry.n_head as usize,
3118 geometry.n_head_kv as usize,
3119 geometry.head_dim_k as usize,
3120 );
3121 let fa_scale = geometry.attention_scale();
3122 let use_favl = !carried
3123 && (2..=8).contains(&b)
3124 && (head_dim == 256 || head_dim == 128)
3125 && geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ
3126 && std::env::var("MEMRA_NOFA").is_err()
3127 && std::env::var("MEMRA_FA_FLOOR").is_err()
3128 && std::env::var("MEMRA_FA_PP_W2").as_deref() != Ok("1")
3129 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0")
3130 && std::env::var("MEMRA_FA_VL").as_deref() != Ok("0");
3131 if use_favl {
3132 let (qf_w, kf_w, vf_w) = (
3133 fa.wq.out_features(),
3134 fa.wk.out_features(),
3135 fa.wv.out_features(),
3136 );
3137 memra_gguf::config::check_fused_q_gate_extent(qf_w, head_dim, n_head, 1)?;
3142 struct APre {
3143 q: CudaSlice<f32>,
3144 gate: Option<CudaSlice<f32>>,
3145 qn: CudaSlice<f32>,
3146 kn: CudaSlice<f32>,
3147 }
3148 let mut aps = Vec::with_capacity(b);
3149 for &t in ts.iter().take(b) {
3150 aps.push(APre {
3151 q: e.uninit(t * n_head * head_dim)?,
3152 gate: Some(e.uninit(t * n_head * head_dim)?),
3153 qn: e.uninit(t * n_head * head_dim)?,
3154 kn: e.uninit(t * n_head_kv * head_dim)?,
3155 });
3156 }
3157 let (kv_dim_k, kv_dim_v, ktb, vtb) = {
3158 let kvl = caches[0].kv[il].as_ref().unwrap();
3159 (kvl.kv_dim_k, kvl.kv_dim_v, kvl.k_tok_bytes, kvl.v_tok_bytes)
3160 };
3161 let pargs: Vec<crate::AttnPreVl> = (0..b)
3162 .map(|s| {
3163 let (o, t) = (offs[s], ts[s]);
3164 let kvl = caches[s].kv[il].as_ref().unwrap();
3165 assert!(
3166 kvl.len == 0 && kvl.len + t <= caches[s].max_ctx,
3167 "prime_cache_batch attn vl: fresh + capacity"
3168 );
3169 crate::AttnPreVl {
3170 qf: e.addr_f32v(&g3[0].slice(o * qf_w..(o + t) * qf_w)),
3171 kf: e.addr_f32v(&g3[1].slice(o * kf_w..(o + t) * kf_w)),
3172 vf: e.addr_f32v(&g3[2].slice(o * vf_w..(o + t) * vf_w)),
3173 q: e.addr_f32(&aps[s].q),
3174 gate: e.addr_f32(aps[s].gate.as_ref().unwrap()),
3175 qn: e.addr_f32(&aps[s].qn),
3176 kn: e.addr_f32(&aps[s].kn),
3177 kc: e.addr_u8(&kvl.k),
3178 vc: e.addr_u8(&kvl.v),
3179 t: t as i32,
3180 pad: 0,
3181 }
3182 })
3183 .collect();
3184 e.attn_pre_vl8(
3185 &pargs,
3186 fa.q_norm.float_data(),
3187 fa.k_norm.float_data(),
3188 head_dim,
3189 geometry.n_rot as usize,
3190 n_head,
3191 n_head_kv,
3192 self.cfg.rms_eps,
3193 geometry.rope_base,
3194 1.0,
3195 kv_dim_k,
3196 kv_dim_v,
3197 ktb,
3198 vtb,
3199 )?;
3200 for s in 0..b {
3201 let kvl = caches[s].kv[il].as_mut().unwrap();
3202 kvl.len += ts[s];
3203 let new_len = kvl.len as i32;
3204 e.set_i32_one(&mut kvl.len_d, new_len)?;
3205 }
3206 let mut attns = Vec::with_capacity(b);
3207 let mut mirrors = Vec::with_capacity(b);
3208 for &t in ts.iter().take(b) {
3209 attns.push(e.uninit(t * n_head * head_dim)?);
3210 let n = t * n_head_kv * head_dim;
3211 mirrors.push((e.alloc_u8_uninit(n * 2)?, e.alloc_u8_uninit(n * 2)?));
3212 }
3213 let fa3_on = match std::env::var("MEMRA_FA3").as_deref() {
3216 Ok("0") => false,
3217 Ok("1") => {
3221 crate::refuse_portable_force(
3222 "MEMRA_FA3=1",
3223 "the sm_90a fa3/bf16 kernels",
3224 );
3225 true
3226 }
3227 _ => cfg!(memra_hopper_mma),
3228 };
3229 if fa3_on {
3230 let mut q16s = Vec::with_capacity(b);
3231 let mut v16s = Vec::with_capacity(b);
3232 for s in 0..b {
3233 let t = ts[s];
3234 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
3235 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
3236 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3237 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
3238 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3239 e.f32_to_bf16_v(
3240 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
3241 &mut v16,
3242 t * n_head_kv * head_dim,
3243 )?;
3244 q16s.push(q16);
3245 v16s.push((k16, v16));
3246 }
3247 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
3248 let mut kp = qp;
3249 let mut vp = qp;
3250 let mut op = [core::ptr::null_mut::<f32>(); 8];
3251 let mut tsv = [0i32; 8];
3252 for s in 0..b {
3253 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
3254 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
3255 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
3256 op[s] = e.addr_f32(&attns[s]) as *mut f32;
3257 tsv[s] = ts[s] as i32;
3258 }
3259 let rc = unsafe {
3260 crate::fa3_vl_raw(
3261 qp.as_ptr(),
3262 kp.as_ptr(),
3263 vp.as_ptr(),
3264 op.as_ptr(),
3265 tsv.as_ptr(),
3266 b as i32,
3267 n_head as i32,
3268 n_head_kv as i32,
3269 head_dim as i32,
3270 fa_scale,
3271 e.stream().cu_stream() as *mut core::ffi::c_void,
3272 )
3273 };
3274 if rc != 0 {
3275 return Err(format!("memra_fa3_vl rc={rc}").into());
3276 }
3277 } else {
3278 let fargs: Vec<crate::FaSeqVl> = (0..b)
3279 .map(|s| crate::FaSeqVl {
3280 q: e.addr_f32(&aps[s].qn),
3281 k16: e.addr_u8(&mirrors[s].0),
3282 v16: e.addr_u8(&mirrors[s].1),
3283 o: e.addr_f32(&attns[s]),
3284 kf: e.addr_f32(&aps[s].kn),
3285 vf: e.addr_f32v(
3286 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
3287 ),
3288 t: ts[s] as i32,
3289 pad: 0,
3290 })
3291 .collect();
3292 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
3293 }
3294 for (s, attn) in attns.into_iter().enumerate() {
3295 let (attn_g, ag16) = self.full_attn_prime_post_fa(
3296 e,
3297 attn,
3298 &aps[s].gate,
3299 ts[s],
3300 n_head,
3301 head_dim,
3302 )?;
3303 let mut done = false;
3304 if let Some(xh) = &ag16 {
3305 done = e.try_f16_gemm_pre_into_off(
3306 &fa.wo,
3307 xh,
3308 ts[s],
3309 &mut mixed,
3310 offs[s] * n_embd,
3311 )?;
3312 }
3313 if !done {
3314 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3315 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3316 }
3317 }
3318 } else {
3319 let mut parts: Vec<Vec<CudaSlice<f32>>> =
3320 (0..b).map(|_| Vec::new()).collect();
3321 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
3322 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
3323 parts[s].push(ys);
3324 }
3325 }
3326 for (s, g3s) in parts.into_iter().enumerate() {
3327 let (attn_g, ag16) = self.full_attn_prime_core_inner(
3329 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
3330 )?;
3331 let mut done = false;
3332 if let Some(xh) = &ag16 {
3333 done = e.try_f16_gemm_pre_into_off(
3334 &fa.wo,
3335 xh,
3336 ts[s],
3337 &mut mixed,
3338 offs[s] * n_embd,
3339 )?;
3340 }
3341 if !done {
3342 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3343 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3344 }
3345 }
3346 }
3347 }
3348 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
3349 Mixer::Linear(la) => {
3350 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3355 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
3356 let outs =
3357 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
3358 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
3359 let (o, t) = (offs[s], ts[s]);
3360 let mut done = false;
3361 if let Some(xh) = &gn16 {
3362 done = e.try_f16_gemm_pre_into_off(
3363 &la.ssm_out,
3364 xh,
3365 t,
3366 &mut mixed,
3367 o * n_embd,
3368 )?;
3369 }
3370 if !done {
3371 let m = e.matmul(&la.ssm_out, &gn, t)?;
3372 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
3373 }
3374 }
3375 }
3376 }
3377 let mut x1 = e.uninit(total * n_embd)?;
3378 let mut z = e.uninit(total * n_embd)?;
3379 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3380 e.add_rms_norm_f16out(
3381 &x,
3382 &mixed,
3383 layer.post_attn_norm.float_data(),
3384 &mut x1,
3385 &mut z,
3386 &mut zx16,
3387 n_embd,
3388 total,
3389 eps,
3390 )?;
3391 let ffn_out = match &layer.ffn {
3392 crate::hybrid::Ffn::Dense {
3393 ffn_gate,
3394 ffn_up,
3395 ffn_down,
3396 } => {
3397 let n_ff = ffn_gate.out_features();
3398 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
3399 let up = g2.pop().unwrap();
3400 let gate = g2.pop().unwrap();
3401 let mut act = e.uninit(total * n_ff)?;
3402 let d_lim = self.cfg.clamp_shexp_at(il as u32);
3406 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
3407 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
3408 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
3409 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
3410 Some(y) => y,
3411 None => e.matmul(ffn_down, &act, total)?,
3412 }
3413 } else {
3414 Self::ffn_act_lim(
3415 e,
3416 &self.cfg,
3417 &gate,
3418 &up,
3419 1.0,
3420 1.0,
3421 d_lim,
3422 &mut act,
3423 total * n_ff,
3424 )?;
3425 e.matmul(ffn_down, &act, total)?
3426 }
3427 }
3428 crate::hybrid::Ffn::Moe(m) => {
3429 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
3430 }
3431 };
3432 let mut x2 = e.uninit(total * n_embd)?;
3433 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
3434 x = x2;
3435 }
3436 let mut hn = e.uninit(total * n_embd)?;
3438 e.rms_norm(
3439 &x,
3440 self.output_norm.float_data(),
3441 &mut hn,
3442 n_embd,
3443 total,
3444 eps,
3445 )?;
3446 let mut hcat = e.uninit(b * n_embd)?;
3452 for s in 0..b {
3453 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3454 e.copy_view_into(
3455 &mut hcat,
3456 s * n_embd,
3457 &hn.slice(last0..last0 + n_embd),
3458 n_embd,
3459 )?;
3460 }
3461 let logits_cat = if b >= 2 {
3462 e.try_f16_gemm(&self.output, &hcat, b)?
3463 } else {
3464 None
3465 };
3466 let logits_host: Option<Vec<f32>> = match &logits_cat {
3467 Some(lc) => Some(e.dtoh(lc)?),
3468 None => None,
3469 };
3470 let n_vocab = self.output.out_features();
3471 let mut hidden_all = if crate::spec::spec_hpost() {
3472 split(e, &hn, n_embd)?
3473 } else {
3474 split(e, &x, n_embd)?
3475 };
3476 let mut out = Vec::with_capacity(b);
3477 for s in 0..b {
3478 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3479 let mut h_seed = e.uninit(n_embd)?;
3480 if !crate::spec::spec_hpost() {
3481 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
3482 } else {
3483 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3484 }
3485 let logits = match &logits_host {
3486 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
3487 None => {
3488 let mut hlast = e.uninit(n_embd)?;
3489 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3490 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
3491 }
3492 };
3493 caches[s].pos += ts[s];
3494 out.push((logits, h_seed, hidden_all.remove(0)));
3495 }
3496 Ok(out)
3497 }
3498
3499 #[allow(clippy::too_many_arguments)]
3510 fn full_attn_prime(
3511 &self,
3512 e: &Engine,
3513 fa: &FullAttnLayer,
3514 h: &CudaSlice<f32>,
3515 hx: Option<&CudaSlice<u8>>,
3516 pos_d: &CudaSlice<i32>,
3517 t: usize,
3518 cache: &mut Cache,
3519 il: usize,
3520 seq_end: usize,
3521 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3522 if self.uses_sliding_gated_moe_program() {
3523 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
3524 }
3525 let g3 = match hx {
3530 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
3531 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
3532 };
3533 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
3534 }
3535
3536 fn full_attn_prime_core(
3540 &self,
3541 e: &Engine,
3542 fa: &FullAttnLayer,
3543 g3: Vec<CudaSlice<f32>>,
3544 pos_d: &CudaSlice<i32>,
3545 t: usize,
3546 cache: &mut Cache,
3547 il: usize,
3548 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3549 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
3550 if let Some(xh) = &ag16 {
3551 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
3552 return Ok(y);
3553 }
3554 }
3555 Ok(e.matmul(&fa.wo, &attn_g, t)?)
3556 }
3557
3558 fn full_attn_prime_core_inner(
3559 &self,
3560 e: &Engine,
3561 fa: &FullAttnLayer,
3562 g3: Vec<CudaSlice<f32>>,
3563 pos_d: &CudaSlice<i32>,
3564 t: usize,
3565 cache: &mut Cache,
3566 il: usize,
3567 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3568 let cfg = &self.cfg;
3569 let geometry = cfg.full_attention_geometry_at(il as u32);
3570 let n_head = geometry.n_head as usize;
3571 let n_head_kv = geometry.n_head_kv as usize;
3572 let head_dim = geometry.head_dim_k as usize;
3573 let scale = geometry.attention_scale();
3574 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
3575 let AttnPre { q, k, v, gate } = pre;
3576 let mut attn = e.uninit(t * n_head * head_dim)?;
3577 self.full_attn_prime_fa_dispatch(
3578 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
3579 )?;
3580 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
3581 }
3582
3583 #[allow(clippy::type_complexity)]
3587 fn full_attn_prime_pre_fa(
3588 &self,
3589 e: &Engine,
3590 fa: &FullAttnLayer,
3591 mut g3: Vec<CudaSlice<f32>>,
3592 pos_d: &CudaSlice<i32>,
3593 t: usize,
3594 cache: &mut Cache,
3595 il: usize,
3596 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3597 let cfg = &self.cfg;
3598 let geometry = cfg.full_attention_geometry_at(il as u32);
3599 let n_head = geometry.n_head as usize;
3600 let n_head_kv = geometry.n_head_kv as usize;
3601 let head_dim = geometry.head_dim_k as usize;
3602 let eps = cfg.rms_eps;
3603
3604 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3608 let v = g3.pop().unwrap();
3609 let mut k = g3.pop().unwrap();
3610 let qf = g3.pop().unwrap();
3611 let (mut q, gate) = if gated {
3612 let mut q = e.uninit(t * n_head * head_dim)?;
3613 let mut gate = e.uninit(t * n_head * head_dim)?;
3614 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3615 (q, Some(gate))
3616 } else {
3617 (qf, None)
3618 };
3619
3620 let mut qn = e.uninit(t * n_head * head_dim)?;
3621 e.rms_norm(
3622 &q,
3623 fa.q_norm.float_data(),
3624 &mut qn,
3625 head_dim,
3626 n_head * t,
3627 eps,
3628 )?;
3629 q = qn;
3630 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3631 e.rms_norm(
3632 &k,
3633 fa.k_norm.float_data(),
3634 &mut kn,
3635 head_dim,
3636 n_head_kv * t,
3637 eps,
3638 )?;
3639 k = kn;
3640 let rope_dims = geometry.n_rot as usize;
3641 e.rope_neox(
3642 &mut q,
3643 pos_d,
3644 head_dim,
3645 rope_dims,
3646 n_head,
3647 t,
3648 geometry.rope_base,
3649 1.0,
3650 )?;
3651 e.rope_neox(
3652 &mut k,
3653 pos_d,
3654 head_dim,
3655 rope_dims,
3656 n_head_kv,
3657 t,
3658 geometry.rope_base,
3659 1.0,
3660 )?;
3661
3662 {
3665 let kvl = cache.kv[il].as_mut().unwrap();
3666 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3667 e.append_kv_quantized_rows(
3668 &k,
3669 &v,
3670 &mut kvl.k,
3671 &mut kvl.v,
3672 kvl.len,
3673 t,
3674 kvl.kv_dim_k,
3675 kvl.kv_dim_v,
3676 kvl.k_tok_bytes,
3677 kvl.v_tok_bytes,
3678 crate::Engine::kv_fp8_on(),
3679 )?;
3680 kvl.len += t;
3681 let new_len = kvl.len as i32;
3682 e.set_i32_one(&mut kvl.len_d, new_len)?;
3683 }
3684
3685 let base_len = {
3686 let kvl = cache.kv[il].as_ref().unwrap();
3687 kvl.len - t };
3689 Ok((AttnPre { q, k, v, gate }, base_len))
3690 }
3691
3692 #[allow(clippy::too_many_arguments)]
3699 fn full_attn_prime_fa_dispatch(
3700 &self,
3701 e: &Engine,
3702 q: &CudaSlice<f32>,
3703 k: &CudaSlice<f32>,
3704 v: &CudaSlice<f32>,
3705 attn: &mut CudaSlice<f32>,
3706 base_len: usize,
3707 t: usize,
3708 cache: &mut Cache,
3709 il: usize,
3710 head_dim: usize,
3711 n_head: usize,
3712 n_head_kv: usize,
3713 scale: f32,
3714 ) -> Result<(), Box<dyn std::error::Error>> {
3715 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
3728 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3729 e.sdpa_naive(
3730 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3731 )?;
3732 } else {
3733 e.fa_prefill(
3734 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3735 )?;
3736 }
3737 return Ok(());
3738 }
3739 let kvl = cache.kv[il].as_ref().unwrap();
3740 let t_kv = base_len + t;
3741 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
3742 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
3743 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3747 e.sdpa_naive_quantized_view(
3748 q,
3749 &k_view,
3750 &v_view,
3751 attn,
3752 head_dim,
3753 n_head,
3754 n_head_kv,
3755 t,
3756 t_kv,
3757 scale,
3758 true,
3759 kvl.k_tok_bytes,
3760 kvl.v_tok_bytes,
3761 )?;
3762 return Ok(());
3763 }
3764 let deqw = std::env::var("MEMRA_PRIME_DEQW")
3772 .map(|v| v != "0")
3773 .unwrap_or(true);
3774 if deqw {
3775 e.fa_prefill_view_ws(
3776 q,
3777 &k_view,
3778 &v_view,
3779 attn,
3780 head_dim,
3781 n_head,
3782 n_head_kv,
3783 t,
3784 t_kv,
3785 scale,
3786 true,
3787 kvl.k_tok_bytes,
3788 kvl.v_tok_bytes,
3789 crate::Engine::kv_fp8_on(),
3790 )?;
3791 } else {
3792 e.fa_prefill_view(
3793 q,
3794 &k_view,
3795 &v_view,
3796 attn,
3797 head_dim,
3798 n_head,
3799 n_head_kv,
3800 t,
3801 t_kv,
3802 scale,
3803 true,
3804 kvl.k_tok_bytes,
3805 kvl.v_tok_bytes,
3806 crate::Engine::kv_fp8_on(),
3807 )?;
3808 }
3809 Ok(())
3810 }
3811
3812 fn full_attn_prime_post_fa(
3815 &self,
3816 e: &Engine,
3817 attn: CudaSlice<f32>,
3818 gate: &Option<CudaSlice<f32>>,
3819 t: usize,
3820 n_head: usize,
3821 head_dim: usize,
3822 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3823 let (attn_g, ag16) = match gate {
3824 Some(gate) => {
3825 let n = t * n_head * head_dim;
3826 let mut ag = e.uninit(n)?;
3827 if Self::f16out_on(e, t) {
3828 let mut a16 = e.alloc_u8_uninit(n * 2)?;
3829 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
3830 (ag, Some(a16))
3831 } else {
3832 let mut gsig = e.uninit(n)?;
3833 e.sigmoid(gate, &mut gsig, n)?;
3834 e.mul(&attn, &gsig, &mut ag, n)?;
3835 (ag, None)
3836 }
3837 }
3838 None => (attn, None),
3839 };
3840 Ok((attn_g, ag16))
3841 }
3842
3843 fn linear_attn_prime(
3850 &self,
3851 e: &Engine,
3852 la: &LinearAttnLayer,
3853 h: &CudaSlice<f32>,
3854 hx: Option<&CudaSlice<u8>>,
3855 t: usize,
3856 cache: &mut Cache,
3857 il: usize,
3858 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3859 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3861 let g4 = match hx {
3862 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
3863 None => e.matmul_group(&ws, h, t)?,
3864 };
3865 self.linear_attn_prime_core(e, la, g4, t, cache, il)
3866 }
3867
3868 fn linear_attn_prime_core(
3870 &self,
3871 e: &Engine,
3872 la: &LinearAttnLayer,
3873 mut g4: Vec<CudaSlice<f32>>,
3874 t: usize,
3875 cache: &mut Cache,
3876 il: usize,
3877 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3878 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
3879 }
3880
3881 #[allow(clippy::too_many_arguments)]
3885 fn linear_attn_prime_core_pad_inner(
3886 &self,
3887 e: &Engine,
3888 la: &LinearAttnLayer,
3889 mut g4: Vec<CudaSlice<f32>>,
3890 t: usize,
3891 cache: &mut Cache,
3892 il: usize,
3893 pad_len: Option<&CudaSlice<i32>>,
3894 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3895 let geometry = la.geometry;
3897 let d_state = geometry.key_head_dim as usize;
3898 let num_k = geometry.key_heads as usize;
3899 let num_v = geometry.value_heads as usize;
3900 let key_dim = d_state * num_k;
3901 let value_dim = geometry.value_head_dim as usize * num_v;
3902 let conv_dim = key_dim * 2 + value_dim;
3903 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(
3908 e,
3909 la,
3910 &qkv_mixed.slice(0..t * conv_dim),
3911 &z.slice(0..t * value_dim),
3912 &beta_raw.slice(0..t * num_v),
3913 &alpha.slice(0..t * num_v),
3914 t,
3915 cache,
3916 il,
3917 pad_len,
3918 )
3919 }
3920
3921 #[allow(clippy::too_many_arguments)]
3924 fn linear_attn_gdn_prep(
3925 &self,
3926 e: &Engine,
3927 la: &LinearAttnLayer,
3928 qkv_mixed: &cudarc::driver::CudaView<f32>,
3929 beta_raw: &cudarc::driver::CudaView<f32>,
3930 alpha: &cudarc::driver::CudaView<f32>,
3931 t: usize,
3932 cache: &mut Cache,
3933 il: usize,
3934 pad_len: Option<&CudaSlice<i32>>,
3935 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
3936 let cfg = &self.cfg;
3937 let geometry = la.geometry;
3938 let d_state = geometry.key_head_dim as usize;
3939 let num_k = geometry.key_heads as usize;
3940 let num_v = geometry.value_heads as usize;
3941 let d_conv = geometry.conv_kernel as usize;
3942 let key_dim = d_state * num_k; let value_dim = geometry.value_head_dim as usize * num_v;
3944 let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
3946 debug_assert!(
3947 t >= d_conv - 1,
3948 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
3949 );
3950
3951 let rl = cache.recur[il].as_mut().unwrap();
3956 let hk = Self::gdn_hk(e, t, num_v, num_k);
3957 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
3958 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
3960 let mut k_g = e.uninit(d_state * hk * t)?;
3961 let mut v_g = e.uninit(d_state * num_v * t)?;
3962 if conv_fuse {
3963 e.ssm_conv1d_gdn_state_pad(
3964 qkv_mixed,
3965 &mut rl.conv_state,
3966 la.ssm_conv1d.float_data(),
3967 &mut q_g,
3968 &mut k_g,
3969 &mut v_g,
3970 conv_dim,
3971 t,
3972 d_conv,
3973 d_state,
3974 num_v,
3975 num_k,
3976 key_dim,
3977 hk,
3978 pad_len,
3979 )?;
3980 } else {
3981 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
3983 qkv_mixed,
3984 &mut rl.conv_state,
3985 la.ssm_conv1d.float_data(),
3986 &mut conv_out,
3987 conv_dim,
3988 t,
3989 d_conv,
3990 pad_len,
3991 )?;
3992 e.qkv_to_gdn_repack(
3993 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
3994 )?;
3995 }
3996 let mut q_l2 = e.uninit(d_state * hk * t)?;
3997 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
4001 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4002 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
4003 Some(qb)
4004 } else {
4005 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
4006 None
4007 };
4008 let mut k_l2 = e.uninit(d_state * hk * t)?;
4009 let kb16 = if Engine::l2_v2_on(d_state) {
4011 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4012 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
4013 Some(kb)
4014 } else {
4015 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
4016 None
4017 };
4018 let mut beta = e.uninit(t * num_v)?;
4019 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
4020 let mut g_log = e.uninit(t * num_v)?;
4021 e.gdn_glog_v(
4022 alpha,
4023 la.ssm_dt.float_data(),
4024 la.ssm_a.float_data(),
4025 &mut g_log,
4026 num_v,
4027 t,
4028 )?;
4029 if let Some(len_d) = pad_len {
4030 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
4031 }
4032 Ok(GdnPrep {
4033 hk,
4034 q_l2,
4035 k_l2,
4036 v_g,
4037 beta,
4038 g_log,
4039 kb16,
4040 qb16,
4041 })
4042 }
4043
4044 #[allow(clippy::too_many_arguments)]
4049 fn linear_attn_prime_core_batch(
4050 &self,
4051 e: &Engine,
4052 la: &LinearAttnLayer,
4053 g4: &[CudaSlice<f32>],
4054 offs: &[usize],
4055 ts: &[usize],
4056 caches: &mut [&mut Cache],
4057 il: usize,
4058 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
4059 let geometry = la.geometry;
4060 let d_state = geometry.key_head_dim as usize;
4061 let num_k = geometry.key_heads as usize;
4062 let num_v = geometry.value_heads as usize;
4063 let d_conv = geometry.conv_kernel as usize;
4064 let key_dim = d_state * num_k;
4065 let value_dim = geometry.value_head_dim as usize * num_v;
4066 let conv_dim = key_dim * 2 + value_dim;
4067 let eps = self.cfg.rms_eps;
4068 let scale = 1.0 / (d_state as f32).sqrt();
4069 let b = ts.len();
4070 let c = Engine::gdn_chunk_size();
4071 let carried = caches.iter().any(|c| c.pos > 0);
4074 let use_vl = !carried
4075 && (2..=8).contains(&b)
4076 && Engine::gdn_chunked_enabled()
4077 && ts.iter().all(|&t| t >= 16)
4078 && e.gdn_mma_enabled(c)
4079 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
4080 if !use_vl {
4081 return (0..b)
4082 .map(|s| {
4083 let (o, t) = (offs[s], ts[s]);
4084 self.linear_attn_prime_core_pad_view(
4085 e,
4086 la,
4087 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
4088 &g4[1].slice(o * value_dim..(o + t) * value_dim),
4089 &g4[2].slice(o * num_v..(o + t) * num_v),
4090 &g4[3].slice(o * num_v..(o + t) * num_v),
4091 t,
4092 caches[s],
4093 il,
4094 None,
4095 )
4096 })
4097 .collect();
4098 }
4099 struct SeqBufs {
4103 conv_out: CudaSlice<f32>,
4104 q_g: CudaSlice<f32>,
4105 k_g: CudaSlice<f32>,
4106 v_g: CudaSlice<f32>,
4107 q_l2: CudaSlice<f32>,
4108 k_l2: CudaSlice<f32>,
4109 beta: CudaSlice<f32>,
4110 g_log: CudaSlice<f32>,
4111 gn: CudaSlice<f32>,
4112 gn16: CudaSlice<u8>,
4113 }
4114 let f16o = Self::f16out_on(e, 16);
4115 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
4117 let mut pres = Vec::with_capacity(b);
4118 for &t in ts.iter().take(b) {
4119 sb.push(SeqBufs {
4120 conv_out: e.uninit(conv_dim * t)?,
4121 q_g: e.uninit(d_state * hk * t)?,
4122 k_g: e.uninit(d_state * hk * t)?,
4123 v_g: e.uninit(d_state * num_v * t)?,
4124 q_l2: e.uninit(d_state * hk * t)?,
4125 k_l2: e.uninit(d_state * hk * t)?,
4126 beta: e.uninit(t * num_v)?,
4127 g_log: e.uninit(t * num_v)?,
4128 gn: e.uninit(d_state * num_v * t)?,
4129 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
4130 });
4131 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
4132 }
4133 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
4134 .map(|s| {
4135 let (o, t) = (offs[s], ts[s]);
4136 let rl = caches[s].recur[il].as_ref().unwrap();
4137 crate::GdnPrepVl {
4138 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
4139 conv_state: e.addr_f32(&rl.conv_state),
4140 conv_out: e.addr_f32(&sb[s].conv_out),
4141 q_g: e.addr_f32(&sb[s].q_g),
4142 k_g: e.addr_f32(&sb[s].k_g),
4143 v_g: e.addr_f32(&sb[s].v_g),
4144 q_l2: e.addr_f32(&sb[s].q_l2),
4145 k_l2: e.addr_f32(&sb[s].k_l2),
4146 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
4147 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
4148 beta: e.addr_f32(&sb[s].beta),
4149 g_log: e.addr_f32(&sb[s].g_log),
4150 o: e.addr_f32(&pres[s].o),
4151 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
4152 gn: e.addr_f32(&sb[s].gn),
4153 gn16: e.addr_u8(&sb[s].gn16),
4154 kb16: if Engine::l2_v2_on(d_state) {
4155 e.addr_u8(&pres[s].kb16)
4156 } else {
4157 0
4158 },
4159 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
4160 e.addr_u8(&pres[s].qb16)
4161 } else {
4162 0
4163 },
4164 t: t as i32,
4165 pad: 0,
4166 }
4167 })
4168 .collect();
4169 let args: Vec<crate::GdnSeqVl> = (0..b)
4170 .map(|s| {
4171 let rl = caches[s].recur[il].as_ref().unwrap();
4172 crate::GdnSeqVl {
4173 kb16: e.addr_u8(&pres[s].kb16),
4174 gcum: e.addr_f32(&pres[s].gcum),
4175 beta: e.addr_f32(&sb[s].beta),
4176 u: e.addr_f32(&pres[s].u),
4177 wb16: e.addr_u8(&pres[s].wb16),
4178 y: e.addr_u8(&pres[s].y16),
4179 ssnap: e.addr_u8(&pres[s].ssnap16),
4180 state_in: e.addr_f32(&rl.ssm_state),
4181 state_out: e.addr_f32(&rl.ssm_state_alt),
4182 q: e.addr_f32(&sb[s].q_l2),
4183 p: e.addr_f32(&pres[s].p),
4184 o: e.addr_f32(&pres[s].o),
4185 k: e.addr_f32(&sb[s].k_l2),
4186 v: e.addr_f32(&sb[s].v_g),
4187 g: e.addr_f32(&sb[s].g_log),
4188 a: e.addr_f32(&pres[s].a),
4189 w: e.addr_f32(&pres[s].w),
4190 t: ts[s] as i32,
4191 nc: pres[s].nc as i32,
4192 }
4193 })
4194 .collect();
4195 e.gdn_prep_vl8(
4196 &prep_args,
4197 la.ssm_conv1d.float_data(),
4198 la.ssm_dt.float_data(),
4199 la.ssm_a.float_data(),
4200 conv_dim,
4201 d_conv,
4202 d_state,
4203 num_v,
4204 num_k,
4205 key_dim,
4206 hk,
4207 eps,
4208 )?;
4209 if !Engine::l2_v2_on(d_state) {
4212 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
4213 }
4214 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
4216 if !Engine::l2_v2_on(d_state) {
4218 for s in 0..b {
4219 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
4220 }
4221 }
4222 let mut wa = [crate::GdnWVl::default(); 8];
4223 for s in 0..b {
4224 wa[s] = crate::GdnWVl {
4225 qb16: e.addr_u8(&pres[s].qb16),
4226 pb16: e.addr_u8(&pres[s].pb16),
4227 };
4228 }
4229 Some(crate::GdnWVl8(wa))
4230 } else {
4231 None
4232 };
4233 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
4234 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
4235 if f16o {
4236 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
4237 }
4238 let mut out = Vec::with_capacity(b);
4240 for (s, bufs) in sb.into_iter().enumerate() {
4241 let rl = caches[s].recur[il].as_mut().unwrap();
4242 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4243 let (o, t) = (offs[s], ts[s]);
4244 let SeqBufs { mut gn, gn16, .. } = bufs;
4245 if f16o {
4246 out.push((gn, Some(gn16)));
4247 } else {
4248 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
4249 e.gated_rmsnorm_zv(
4250 &pres[s].o,
4251 la.ssm_norm.float_data(),
4252 &z_v,
4253 &mut gn,
4254 d_state,
4255 num_v * t,
4256 eps,
4257 )?;
4258 out.push((gn, None));
4259 }
4260 }
4261 Ok(out)
4262 }
4263
4264 #[allow(clippy::too_many_arguments)]
4268 fn linear_attn_prime_core_pad_view(
4269 &self,
4270 e: &Engine,
4271 la: &LinearAttnLayer,
4272 qkv_mixed: &cudarc::driver::CudaView<f32>,
4273 z: &cudarc::driver::CudaView<f32>,
4274 beta_raw: &cudarc::driver::CudaView<f32>,
4275 alpha: &cudarc::driver::CudaView<f32>,
4276 t: usize,
4277 cache: &mut Cache,
4278 il: usize,
4279 pad_len: Option<&CudaSlice<i32>>,
4280 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4281 let cfg = &self.cfg;
4282 let geometry = la.geometry;
4283 let d_state = geometry.key_head_dim as usize;
4284 let num_v = geometry.value_heads as usize;
4285 let eps = cfg.rms_eps;
4286 let scale = 1.0 / (d_state as f32).sqrt();
4287
4288 let prep =
4289 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
4290
4291 let mut o = e.uninit(d_state * num_v * t)?;
4297 let rl = cache.recur[il].as_mut().unwrap();
4298 {
4299 let crate::cache::RecurLayer {
4300 ssm_state,
4301 ssm_state_alt,
4302 ..
4303 } = rl;
4304 e.gdn_scan_prefill(
4305 &prep.q_l2,
4306 &prep.k_l2,
4307 &prep.v_g,
4308 &prep.g_log,
4309 &prep.beta,
4310 prep.kb16.as_ref(),
4311 prep.qb16.as_ref(),
4312 ssm_state,
4313 ssm_state_alt,
4314 &mut o,
4315 num_v,
4316 t,
4317 scale,
4318 prep.hk,
4319 )?;
4320 }
4321 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4322
4323 let mut gn = e.uninit(d_state * num_v * t)?;
4326 let gn16 = if Self::f16out_on(e, t) {
4327 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
4328 e.gated_rmsnorm_f16out_zv(
4329 &o,
4330 la.ssm_norm.float_data(),
4331 z,
4332 &mut gn,
4333 &mut g16,
4334 d_state,
4335 num_v * t,
4336 eps,
4337 )?;
4338 Some(g16)
4339 } else {
4340 e.gated_rmsnorm_zv(
4341 &o,
4342 la.ssm_norm.float_data(),
4343 z,
4344 &mut gn,
4345 d_state,
4346 num_v * t,
4347 eps,
4348 )?;
4349 None
4350 };
4351 Ok((gn, gn16))
4352 }
4353
4354 #[allow(clippy::too_many_arguments)]
4356 fn linear_attn_prime_core_pad(
4357 &self,
4358 e: &Engine,
4359 la: &LinearAttnLayer,
4360 g4: Vec<CudaSlice<f32>>,
4361 t: usize,
4362 cache: &mut Cache,
4363 il: usize,
4364 pad_len: Option<&CudaSlice<i32>>,
4365 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4366 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
4367 if let Some(xh) = &gn16 {
4368 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
4369 return Ok(y);
4370 }
4371 }
4372 Ok(e.matmul(&la.ssm_out, &gn, t)?)
4373 }
4374
4375 pub fn full_attn(
4380 &self,
4381 e: &Engine,
4382 fa: &FullAttnLayer,
4383 h: &CudaSlice<f32>,
4384 pos_d: &CudaSlice<i32>,
4385 t: usize,
4386 il: usize,
4387 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4388 if self.uses_sliding_gated_moe_program() {
4389 return self.step35_attn(e, fa, h, pos_d, t, il);
4390 }
4391 let cfg = &self.cfg;
4392 let _n_embd = cfg.n_embd as usize;
4393 let geometry = cfg.full_attention_geometry_at(il as u32);
4394 let n_head = geometry.n_head as usize;
4395 let n_head_kv = geometry.n_head_kv as usize;
4396 let head_dim = geometry.head_dim_k as usize;
4397 let eps = cfg.rms_eps;
4398 let scale = geometry.attention_scale();
4399
4400 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
4403 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
4405 let v = g3.pop().unwrap();
4406 let mut k = g3.pop().unwrap();
4407 let qf = g3.pop().unwrap();
4408 let (mut q, gate) = if gated {
4409 let mut q = e.uninit(t * n_head * head_dim)?;
4410 let mut gate = e.uninit(t * n_head * head_dim)?;
4411 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
4412 (q, Some(gate))
4413 } else {
4414 (qf, None)
4415 };
4416
4417 let mut qn = e.uninit(t * n_head * head_dim)?;
4419 e.rms_norm(
4420 &q,
4421 fa.q_norm.float_data(),
4422 &mut qn,
4423 head_dim,
4424 n_head * t,
4425 eps,
4426 )?;
4427 q = qn;
4428 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
4429 e.rms_norm(
4430 &k,
4431 fa.k_norm.float_data(),
4432 &mut kn,
4433 head_dim,
4434 n_head_kv * t,
4435 eps,
4436 )?;
4437 k = kn;
4438 let rope_dims = geometry.n_rot as usize;
4439 e.rope_neox(
4440 &mut q,
4441 pos_d,
4442 head_dim,
4443 rope_dims,
4444 n_head,
4445 t,
4446 geometry.rope_base,
4447 1.0,
4448 )?;
4449 e.rope_neox(
4450 &mut k,
4451 pos_d,
4452 head_dim,
4453 rope_dims,
4454 n_head_kv,
4455 t,
4456 geometry.rope_base,
4457 1.0,
4458 )?;
4459
4460 let mut attn = e.uninit(t * n_head * head_dim)?;
4462 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4465 e.sdpa_naive(
4467 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4468 )?;
4469 } else {
4470 e.fa_prefill(
4471 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4472 )?;
4473 }
4474
4475 let attn_g = match &gate {
4477 Some(gate) => {
4478 let mut gsig = e.uninit(t * n_head * head_dim)?;
4479 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
4480 let mut ag = e.uninit(t * n_head * head_dim)?;
4481 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
4482 ag
4483 }
4484 None => attn,
4485 };
4486
4487 let o = e.matmul(&fa.wo, &attn_g, t)?;
4489 Ok(o)
4490 }
4491
4492 pub fn linear_attn(
4494 &self,
4495 e: &Engine,
4496 la: &LinearAttnLayer,
4497 h: &CudaSlice<f32>,
4498 t: usize,
4499 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4500 let cfg = &self.cfg;
4501 let _n_embd = cfg.n_embd as usize;
4502 let geometry = la.geometry;
4503 let d_state = geometry.key_head_dim as usize;
4504 let num_k = geometry.key_heads as usize;
4505 let num_v = geometry.value_heads as usize;
4506 let d_conv = geometry.conv_kernel as usize;
4507 let head_k = d_state;
4508 let head_v = geometry.value_head_dim as usize;
4509 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;
4513 let scale = 1.0 / (d_state as f32).sqrt();
4514
4515 let mut g4 = e.matmul_group(
4518 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
4519 h,
4520 t,
4521 )?;
4522 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);
4534 let mut q_g = e.uninit(d_state * num_v * t)?;
4535 let mut k_g = e.uninit(d_state * num_v * t)?;
4536 let mut v_g = e.uninit(d_state * num_v * t)?;
4537 e.ssm_conv1d_gdn(
4538 &qkv_mixed,
4539 la.ssm_conv1d.float_data(),
4540 &mut q_g,
4541 &mut k_g,
4542 &mut v_g,
4543 conv_dim,
4544 t,
4545 d_conv,
4546 d_state,
4547 num_v,
4548 num_k,
4549 key_dim,
4550 )?;
4551 let mut q_l2 = e.uninit(d_state * num_v * t)?;
4553 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
4554 let mut k_l2 = e.uninit(d_state * num_v * t)?;
4555 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
4556 let v_gd = v_g;
4557
4558 let mut beta = e.uninit(t * num_v)?;
4561 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
4562 let mut g_log = e.uninit(t * num_v)?;
4564 e.gdn_glog(
4565 &alpha,
4566 la.ssm_dt.float_data(),
4567 la.ssm_a.float_data(),
4568 &mut g_log,
4569 num_v,
4570 t,
4571 )?;
4572
4573 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
4576 let mut o = e.uninit(d_state * num_v * t)?;
4577 e.gdn_scan_prefill(
4578 &q_l2,
4579 &k_l2,
4580 &v_gd,
4581 &g_log,
4582 &beta,
4583 None,
4584 None,
4585 &state_in,
4586 &mut state_out,
4587 &mut o,
4588 num_v,
4589 t,
4590 scale,
4591 num_v,
4592 )?;
4593
4594 let mut gn = e.uninit(d_state * num_v * t)?;
4599 e.gated_rmsnorm(
4600 &o,
4601 la.ssm_norm.float_data(),
4602 &z,
4603 &mut gn,
4604 d_state,
4605 num_v * t,
4606 eps,
4607 )?;
4608
4609 let out = e.matmul(&la.ssm_out, &gn, t)?;
4613 Ok(out)
4614 }
4615}
4616
4617impl HybridModel {
4618 pub fn moe_ffn_il(
4629 &self,
4630 e: &Engine,
4631 m: &MoeWeights,
4632 z: &CudaSlice<f32>,
4633 t: usize,
4634 il: u16,
4635 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4636 Self::moe_ffn_inner(
4637 e,
4638 m,
4639 z,
4640 None,
4641 t,
4642 &self.cfg,
4643 il,
4644 self.max_moe_block(),
4645 false,
4646 None,
4647 self.uses_sliding_gated_moe_program(),
4648 )
4649 }
4650
4651 pub fn moe_ffn_il_prefill(
4654 &self,
4655 e: &Engine,
4656 m: &MoeWeights,
4657 z: &CudaSlice<f32>,
4658 t: usize,
4659 il: u16,
4660 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4661 Self::moe_ffn_inner(
4662 e,
4663 m,
4664 z,
4665 None,
4666 t,
4667 &self.cfg,
4668 il,
4669 self.max_moe_block(),
4670 true,
4671 Some(&self.step_grouped_prefill),
4672 self.uses_sliding_gated_moe_program(),
4673 )
4674 }
4675
4676 pub fn moe_ffn_il_zq8(
4680 &self,
4681 e: &Engine,
4682 m: &MoeWeights,
4683 z: &CudaSlice<f32>,
4684 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4685 t: usize,
4686 il: u16,
4687 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4688 Self::moe_ffn_inner(
4689 e,
4690 m,
4691 z,
4692 zq8,
4693 t,
4694 &self.cfg,
4695 il,
4696 self.max_moe_block(),
4697 false,
4698 None,
4699 self.uses_sliding_gated_moe_program(),
4700 )
4701 }
4702
4703 pub(crate) fn moe_ffn(
4711 e: &Engine,
4712 m: &MoeWeights,
4713 z: &CudaSlice<f32>,
4714 t: usize,
4715 cfg: &ModelConfig,
4716 il: u16,
4717 max_block: usize,
4718 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4719 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false, None, false)
4720 }
4721
4722 #[allow(clippy::too_many_arguments)]
4723 pub(crate) fn moe_ffn_inner(
4724 e: &Engine,
4725 m: &MoeWeights,
4726 z: &CudaSlice<f32>,
4727 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4728 t: usize,
4729 cfg: &ModelConfig,
4730 il: u16,
4731 max_block: usize,
4732 prefill: bool,
4733 grouped_prefill: Option<&std::sync::Mutex<crate::hybrid::StepEpGroupedPrefill>>,
4734 sliding_gated_moe: bool,
4735 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4736 let worker_io = crate::spill_pread::worker_enabled();
4737 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
4738 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
4739 e.with_moe_cache(max_block, |cache, _| {
4740 cache.begin_forward_epoch(il, t);
4741 if worker_io {
4742 cache.begin_worker_scope();
4743 }
4744 Ok(())
4745 })?;
4746 }
4747 if m.step_ep.is_some() || m.step_tp.is_some() {
4748 let moe = cfg
4749 .moe
4750 .as_ref()
4751 .ok_or("Step distributed execution requires MoE model metadata")?;
4752 let n_embd = cfg.n_embd as usize;
4753 let n_expert = moe.expert_count as usize;
4754 let n_used = moe.expert_used_count as usize;
4755 let sigmoid = cfg
4756 .sigmoid_router()
4757 .ok_or("Step distributed execution requires the Step sigmoid router")?;
4758 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4759 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
4760 let grouped_prefill_requested = prefill && step_ep_grouped_prefill_enabled()?;
4761 if grouped_prefill_requested && !step_tp_prefill_enabled()? {
4762 return Err(
4763 "MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into(),
4764 );
4765 }
4766 if grouped_prefill_requested && !step_grouped_prefill_shape(true, prefill, t) {
4767 return Err(format!(
4768 "Step grouped prefill tokens {t} are outside the qualified {}..={} range",
4769 PRIME_MIN_T,
4770 crate::cache::PRIME_CHUNK_MAX_TOKENS,
4771 )
4772 .into());
4773 }
4774 let grouped_decode_shape = step_grouped_decode_shape(prefill, t);
4775 let grouped_prefill_shape =
4776 step_grouped_prefill_shape(grouped_prefill_requested, prefill, t);
4777 if let Some(ep) = m.step_ep.as_ref().filter(|ep| {
4778 ep.grouped_decode.is_some() && (grouped_decode_shape || grouped_prefill_shape)
4779 }) {
4780 let (selected, route_weights) =
4781 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sigmoid)?;
4782 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
4783 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
4784 Self::trace_moe_input(e, il, t, n_embd, z)?;
4785 let selected = selected
4786 .iter()
4787 .map(|&expert| expert as usize)
4788 .collect::<Vec<_>>();
4789
4790 e.stream().synchronize()?;
4793 let execute = |state: &mut crate::hybrid::StepEpGroupedDecode| {
4794 state.projection.set_activation_limit(ep.activation_limit)?;
4795 ep.runtime
4796 .refresh_step_grouped_expert_parallel_gate_from_root_device(
4797 ep.experts.e4m3()?,
4798 &mut state.projection,
4799 z,
4800 t,
4801 &selected,
4802 )?;
4803 ep.runtime.refresh_step_grouped_expert_parallel_combine(
4804 &state.projection,
4805 &mut state.combine,
4806 &route_weights,
4807 )?;
4808 ep.runtime.execute_step_grouped_expert_parallel_gate(
4809 ep.experts.e4m3()?,
4810 &mut state.projection,
4811 )?;
4812 ep.runtime.execute_step_grouped_expert_parallel_combine(
4813 &state.projection,
4814 &mut state.combine,
4815 )?;
4816 let mut output = ep.runtime.copy_step_grouped_expert_parallel_combine_root(
4817 &state.projection,
4818 &state.combine,
4819 e,
4820 )?;
4821 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
4822 if prefill {
4823 e.stream().synchronize()?;
4826 }
4827 eprintln!(
4828 "[step-tp-ep-grouped] execute layer={il} tokens={t} devices={:?} \
4829 attention_layout=tensor-parallel expert_layout=expert-parallel \
4830 expert_transport={} native_p2p=true route_control=host-narrow \
4831 input=root-device projection_workspaces=persistent \
4832 combine=root-device output=owning-stage-device \
4833 prefill={prefill} batched_decode=false capacity={} \
4834 performance_claim=false",
4835 ep.devices,
4836 ep.runtime.transport_label(),
4837 state.projection.max_tokens(),
4838 );
4839 Ok::<_, Box<dyn std::error::Error>>(output)
4840 };
4841
4842 if grouped_prefill_shape {
4843 let grouped_prefill = grouped_prefill
4844 .ok_or("Step grouped prefill has no model-scoped executor")?;
4845 let mut shared = grouped_prefill
4846 .lock()
4847 .map_err(|_| "Step grouped prefill state lock is poisoned")?;
4848 let needs_prepare = shared.state.as_ref().is_none_or(|state| {
4849 state.devices != ep.devices
4850 || state.grouped.projection.max_tokens() < t
4851 || state.grouped.projection.input_width() != n_embd
4852 || state.grouped.projection.expert_width()
4853 != moe.expert_ff_length as usize
4854 });
4855 if needs_prepare {
4856 let seed_input = vec![0.0f32; n_embd];
4857 let seed_selected = &selected[..n_used];
4858 let seed_weights = &route_weights[..n_used];
4859 let projection = ep
4860 .runtime
4861 .prepare_step_grouped_expert_parallel_gate_with_capacity(
4862 ep.experts.e4m3()?,
4863 &seed_input,
4864 1,
4865 seed_selected,
4866 ep.activation_limit,
4867 t,
4868 )?;
4869 let combine = ep.runtime.prepare_step_grouped_expert_parallel_combine(
4870 &projection,
4871 seed_weights,
4872 )?;
4873 shared.state = Some(crate::hybrid::StepEpGroupedPrefillState {
4874 devices: ep.devices.clone(),
4875 grouped: crate::hybrid::StepEpGroupedDecode {
4876 projection,
4877 combine,
4878 },
4879 });
4880 eprintln!(
4881 "[step-tp-ep-grouped-prefill] prepare capacity={t} devices={:?} \
4882 shared_across_layers=true performance_claim=false",
4883 ep.devices,
4884 );
4885 }
4886 return execute(
4887 &mut shared
4888 .state
4889 .as_mut()
4890 .expect("Step grouped prefill state prepared above")
4891 .grouped,
4892 );
4893 }
4894
4895 let mut grouped = ep
4896 .grouped_decode
4897 .as_ref()
4898 .expect("grouped decode presence checked above")
4899 .lock()
4900 .map_err(|_| "Step grouped decode state lock is poisoned")?;
4901 return execute(&mut grouped);
4902 }
4903 if grouped_prefill_shape {
4904 return Err(
4905 "Step grouped prefill requires native-P2P expert-owner device arithmetic"
4906 .into(),
4907 );
4908 }
4909 if t == 1
4914 && crate::tp::step_nvfp4_dev_routes_enabled()?
4915 && crate::tp::step_tp_dev_router_enabled()?
4916 {
4917 if let Some(tp) = &m.step_tp {
4918 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
4919 let (sf, route_norm) = sigmoid;
4920 static D1_ROUTER: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4927 let d1_router = *D1_ROUTER.get_or_init(|| {
4928 std::env::var("MEMRA_DEV1_ROUTER").as_deref() == Ok("1")
4929 });
4930 if d1_router {
4931 let (sf_h, rn_h) = sigmoid;
4932 let n_ex = m.gate_exps.n_expert;
4933 let act_ct = m.active_count();
4934 let _ = tp.runtime.nvfp4_routes_prestage_with(
4935 bank,
4936 e,
4937 z,
4938 |rank1, in1, sel1, w1| {
4939 let mut guard = DEV1_ROUTER_REPS
4940 .lock()
4941 .map_err(|_| "dev1 router replica lock")?;
4942 let (reps, scratch) =
4943 guard.get_or_insert_with(|| (Default::default(), None));
4944 if !reps.contains_key(&il) {
4945 use cudarc::driver::DevicePtr;
4946 let (g1, p1, a1) = (
4947 rank1.htod(&vec![0.0f32; n_ex * n_embd])?,
4948 rank1.htod(&vec![0.0f32; n_ex])?,
4949 rank1.alloc_u8_uninit(n_ex)?,
4950 );
4951 for (src, dst_len, dst) in [
4952 (
4953 {
4954 let s = e.stream();
4955 let (p, _g) =
4956 m.gate_inp.float_data().device_ptr(&s);
4957 p as u64
4958 },
4959 n_ex * n_embd * 4,
4960 {
4961 let s = rank1.stream();
4962 let (p, _g) = g1.device_ptr(&s);
4963 p as u64
4964 },
4965 ),
4966 (
4967 {
4968 let s = e.stream();
4969 let (p, _g) = m.exp_probs_b_dev.device_ptr(&s);
4970 p as u64
4971 },
4972 n_ex * 4,
4973 {
4974 let s = rank1.stream();
4975 let (p, _g) = p1.device_ptr(&s);
4976 p as u64
4977 },
4978 ),
4979 (
4980 {
4981 let s = e.stream();
4982 let (p, _g) =
4983 m.active_experts_dev.device_ptr(&s);
4984 p as u64
4985 },
4986 n_ex,
4987 {
4988 let s = rank1.stream();
4989 let (p, _g) = a1.device_ptr(&s);
4990 p as u64
4991 },
4992 ),
4993 ] {
4994 crate::tp::raw_copy_bytes(dst, src, dst_len, rank1)?;
4995 }
4996 rank1.stream().synchronize()?;
4997 reps.insert(il, (g1, p1, a1));
4998 }
4999 if scratch.is_none() {
5000 *scratch = Some(rank1.htod(&vec![0.0f32; n_ex])?);
5001 }
5002 let (g1, p1, a1) = reps.get(&il).expect("armed above");
5003 let logits1 = scratch.as_mut().expect("armed above");
5004 rank1.router_gemv_into(g1, in1, logits1, n_embd, n_ex, 1)?;
5005 rank1.moe_router_sigmoid_topk_into(
5006 logits1, 1, n_ex, n_used, act_ct, p1, a1, sf_h, rn_h, sel1,
5007 w1,
5008 )?;
5009 Ok(true)
5010 },
5011 )?;
5012 } else {
5013 let _ = tp.runtime.nvfp4_routes_prestage(bank, e, z)?;
5014 }
5015 static SELW: std::sync::Mutex<
5019 Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>,
5020 > = std::sync::Mutex::new(None);
5021 let mut selw = SELW.lock().map_err(|_| "selw lock poisoned")?;
5022 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
5023 *selw = Some((
5024 e.ctx().ordinal(),
5025 e.htod_i32(&vec![0i32; n_used])?,
5026 e.htod(&vec![0.0f32; n_used])?,
5027 ));
5028 }
5029 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
5030 e.moe_router_sigmoid_topk_into(
5031 &logits,
5032 t,
5033 n_expert,
5034 n_used,
5035 m.active_count(),
5036 &m.exp_probs_b_dev,
5037 &m.active_experts_dev,
5038 sf,
5039 route_norm,
5040 sel_d,
5041 w_d,
5042 )?;
5043 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
5044 static SHEXP_OV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5048 let shexp_ov = *SHEXP_OV.get_or_init(|| {
5049 std::env::var("MEMRA_SHEXP_OVERLAP").as_deref() == Ok("1")
5050 });
5051 static SHEXP_D1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5055 let shexp_d1 = *SHEXP_D1.get_or_init(|| {
5056 std::env::var("MEMRA_SHEXP_DEV1").as_deref() == Ok("1")
5057 }) && tp.runtime.rank_engine(1).is_some();
5058 static TAIL3: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5062 let tail3 = *TAIL3
5063 .get_or_init(|| std::env::var("MEMRA_TAIL_ADD3").as_deref() != Ok("0"));
5064 let mut ov_issued = false;
5065 let mut d1_issued = false;
5066 let mut tail_folded = false;
5067 let mut output = if shexp_d1 {
5068 let rank1 = tp.runtime.rank_engine(1).expect("checked above");
5069 tp.runtime
5070 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
5071 bank,
5072 e,
5073 z,
5074 &sel_d,
5075 &w_d,
5076 n_used,
5077 tp.activation_limit,
5078 || {
5079 d1_issued = Self::shexp_dev1_issue(
5080 e, rank1, m, z, cfg, il, n_embd,
5081 )?;
5082 Ok(())
5083 },
5084 )?
5085 } else if shexp_ov {
5086 let post_add = if tail3 {
5091 Self::shexp_overlap_tail_ptrs(e, m, cfg, n_embd)?
5092 } else {
5093 None
5094 };
5095 let used_post = post_add.is_some();
5096 let out = tp
5097 .runtime
5098 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
5099 bank,
5100 e,
5101 z,
5102 &sel_d,
5103 &w_d,
5104 n_used,
5105 tp.activation_limit,
5106 || {
5107 ov_issued =
5108 Self::shexp_overlap_issue(e, m, z, cfg, il, n_embd)?;
5109 Ok(())
5110 },
5111 post_add,
5112 )?;
5113 if used_post && ov_issued {
5118 tail_folded = true; }
5120 out
5121 } else {
5122 tp.runtime.run_tensor_parallel_routes_nvfp4_device_routed(
5123 bank,
5124 e,
5125 z,
5126 &sel_d,
5127 &w_d,
5128 n_used,
5129 tp.activation_limit,
5130 )?
5131 };
5132 if output.len() != t * n_embd {
5133 return Err(format!(
5134 "Step tp routed output has {} values, expected {t}x{n_embd}",
5135 output.len()
5136 )
5137 .into());
5138 }
5139 if tail_folded {
5140 } else if d1_issued {
5142 Self::shexp_dev1_apply(e, &mut output, n_embd)?;
5143 } else if ov_issued {
5144 Self::shexp_overlap_apply(e, &mut output, n_embd)?;
5145 } else {
5146 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5147 }
5148 static DR_LOGGED: std::sync::atomic::AtomicU64 =
5149 std::sync::atomic::AtomicU64::new(0);
5150 let layer_bit = 1u64 << (il as u64 % 64);
5151 if DR_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5152 & layer_bit
5153 == 0
5154 {
5155 eprintln!(
5156 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5157 expert_transport={} native_p2p={} router=device \
5158 activation=host-canonical accumulation=host-canonical \
5159 output=e-device io=device performance_claim=false \
5160 (logged once per layer)",
5161 tp.devices,
5162 tp.runtime.transport_label(),
5163 tp.runtime.native_p2p(),
5164 );
5165 }
5166 return Ok(output);
5167 }
5168 }
5169 }
5170 static ROUTE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5174 static ROUTE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5175 let route_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
5176 let route_started = route_timing.then(std::time::Instant::now);
5177 let (selected, route_weights, input) = Self::moe_route_sigmoid_with_input(
5178 e,
5179 &logits,
5180 z,
5181 t,
5182 n_embd,
5183 n_expert,
5184 n_used,
5185 m.exp_probs_b.as_deref(),
5186 sigmoid,
5187 m.active_experts.as_deref(),
5188 )?;
5189 if let Some(started) = route_started {
5190 use std::sync::atomic::Ordering;
5191 let ns = ROUTE_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
5192 + started.elapsed().as_nanos() as u64;
5193 let calls = ROUTE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
5194 if calls % 430 == 0 {
5195 eprintln!(
5196 "[moe-route-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
5197 ns as f64 / 1.0e6,
5198 ns as f64 / calls as f64 / 1.0e3,
5199 );
5200 }
5201 }
5202 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5203 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5204 Self::trace_moe_input(e, il, t, n_embd, z)?;
5205 let selected = selected
5206 .iter()
5207 .map(|&expert| expert as usize)
5208 .collect::<Vec<_>>();
5209 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5214 if let Some(tp) = &m.step_tp {
5215 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5216 let mut output = tp.runtime.run_tensor_parallel_routes_nvfp4_device_io(
5217 bank,
5218 e,
5219 z,
5220 &selected,
5221 &route_weights,
5222 n_used,
5223 tp.activation_limit,
5224 )?;
5225 if output.len() != t * n_embd {
5226 return Err(format!(
5227 "Step tp routed output has {} values, expected {t}x{n_embd}",
5228 output.len()
5229 )
5230 .into());
5231 }
5232 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5233 static IO_LOGGED: std::sync::atomic::AtomicU64 =
5234 std::sync::atomic::AtomicU64::new(0);
5235 let layer_bit = 1u64 << (il as u64 % 64);
5236 if IO_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5237 & layer_bit
5238 == 0
5239 {
5240 eprintln!(
5241 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5242 expert_transport={} native_p2p={} activation=host-canonical \
5243 accumulation=host-canonical output=e-device io=device \
5244 performance_claim=false (logged once per layer)",
5245 tp.devices,
5246 tp.runtime.transport_label(),
5247 tp.runtime.native_p2p(),
5248 );
5249 }
5250 return Ok(output);
5251 }
5252 }
5253 }
5254 let (routed, mode, devices, transport, native_p2p) = if let Some(tp) = &m.step_tp {
5255 (
5256 match &tp.experts {
5257 crate::hybrid::StepTpExpertBank::E4m3(bank) => {
5258 tp.runtime.run_tensor_parallel_routes(
5259 bank,
5260 &input,
5261 t,
5262 &selected,
5263 &route_weights,
5264 n_used,
5265 )?
5266 }
5267 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => {
5268 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5269 tp.runtime.run_tensor_parallel_routes_nvfp4_device(
5270 bank,
5271 &input,
5272 &selected,
5273 &route_weights,
5274 n_used,
5275 tp.activation_limit,
5276 )?
5277 } else {
5278 tp.runtime.run_tensor_parallel_routes_nvfp4(
5279 bank,
5280 &input,
5281 t,
5282 &selected,
5283 &route_weights,
5284 n_used,
5285 tp.activation_limit,
5286 )?
5287 }
5288 }
5289 },
5290 "tp",
5291 &tp.devices,
5292 tp.runtime.transport_label(),
5293 tp.runtime.native_p2p(),
5294 )
5295 } else {
5296 let ep = m
5297 .step_ep
5298 .as_ref()
5299 .ok_or("Step distributed runtime has no EP or TP state")?;
5300 (
5301 match &ep.experts {
5302 crate::hybrid::StepEpExpertBank::E4m3(bank) => {
5303 ep.runtime.run_routed_experts(
5304 bank,
5305 &input,
5306 t,
5307 &selected,
5308 &route_weights,
5309 n_used,
5310 ep.activation_limit,
5311 )?
5312 }
5313 crate::hybrid::StepEpExpertBank::Nvfp4(bank) => {
5314 ep.runtime.run_routed_experts_nvfp4(
5315 bank,
5316 &input,
5317 t,
5318 &selected,
5319 &route_weights,
5320 n_used,
5321 ep.activation_limit,
5322 )?
5323 }
5324 },
5325 if ep.configured_by_tp { "tp-ep" } else { "ep" },
5326 &ep.devices,
5327 ep.runtime.transport_label(),
5328 ep.runtime.native_p2p(),
5329 )
5330 };
5331 if routed.len() != t * n_embd {
5332 return Err(format!(
5333 "Step {mode} routed output has {} values, expected {t}x{n_embd}",
5334 routed.len()
5335 )
5336 .into());
5337 }
5338 let mut output = e.htod(&routed)?;
5339 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5340 static STEP_LOGGED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5343 let layer_bit = 1u64 << (il as u64 % 64);
5344 if STEP_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed) & layer_bit
5345 == 0
5346 {
5347 eprintln!(
5348 "[step-{mode}] execute layer={il} tokens={t} devices={devices:?} \
5349 expert_transport={transport} native_p2p={native_p2p} \
5350 activation={} accumulation={} output={} \
5351 performance_claim=false (logged once per layer)",
5352 if let Some(ep) = &m.step_ep {
5353 ep.runtime.expert_activation_label()
5354 } else {
5355 "host-canonical"
5356 },
5357 if let Some(ep) = &m.step_ep {
5358 ep.runtime.expert_accumulation_label()
5359 } else {
5360 "host-canonical"
5361 },
5362 if let Some(ep) = &m.step_ep {
5363 ep.runtime.expert_output_label()
5364 } else {
5365 "host-accumulated"
5366 },
5367 );
5368 if let Some(ep) = &m.step_ep {
5369 if let Some(limit) = ep.activation_limit {
5370 eprintln!(
5371 "[step-ep-clamp] execute layer={il} tokens={t} routed_clamp={limit} \
5372 formula=min-silu-times-clamped-up performance_claim=false"
5373 );
5374 }
5375 }
5376 }
5377 return Ok(output);
5378 }
5379 if Self::sigmoid_resident_dev_eligible(e, m, cfg, sliding_gated_moe) {
5380 let moe = cfg.moe.as_ref().unwrap();
5381 let n_expert = moe.expert_count as usize;
5382 let n_used = moe.expert_used_count as usize;
5383 let sigmoid = cfg.sigmoid_router().unwrap();
5384 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5385 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5386 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
5387 }
5388 if t > 1 && moe_grouped_enabled(cfg, prefill) {
5391 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
5392 if std::env::var("MEMRA_MOE_GATE").is_ok() {
5397 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
5398 let g_host = e.dtoh(&grouped_out)?;
5399 let s_host = e.dtoh(&seq_out)?;
5400 let g_bytes: &[u8] = unsafe {
5401 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
5402 };
5403 let s_bytes: &[u8] = unsafe {
5404 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
5405 };
5406 if g_bytes == s_bytes {
5407 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
5408 } else {
5409 let diffs = g_host
5410 .iter()
5411 .zip(s_host.iter())
5412 .enumerate()
5413 .filter(|(_, (a, b))| a != b)
5414 .count();
5415 let maxdiff = g_host
5416 .iter()
5417 .zip(s_host.iter())
5418 .map(|(a, b)| (a - b).abs())
5419 .fold(0.0f32, f32::max);
5420 panic!(
5421 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
5422 g_host.len()
5423 );
5424 }
5425 }
5426 return Ok(grouped_out);
5427 }
5428 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
5429 }
5430
5431 fn sigmoid_resident_dev_eligible(
5432 e: &Engine,
5433 m: &MoeWeights,
5434 cfg: &ModelConfig,
5435 sliding_gated_moe: bool,
5436 ) -> bool {
5437 let Some(moe) = cfg.moe.as_ref() else {
5438 return false;
5439 };
5440 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5443 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
5444 std::env::var("MEMRA_MOE_STATS").is_ok()
5445 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5446 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5447 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
5448 || std::env::var("MEMRA_MOE_GATE").is_ok()
5449 });
5450 let resident_layout_supported = m.dev_exps.as_ref().is_some_and(|dev| {
5451 if dev.dev != e.ctx().ordinal() {
5452 return false;
5453 }
5454 let q8 = moe_q8_enabled()
5455 && q8_expert_supported(m.gate_exps.qtype)
5456 && q8_expert_supported(m.up_exps.qtype)
5457 && q8_expert_supported(m.down_exps.qtype);
5458 let fp8 = dev.fp8_blk.is_some()
5459 && m.gate_exps.qtype == crate::QT_F8_E4M3_BLK
5460 && m.up_exps.qtype == crate::QT_F8_E4M3_BLK
5461 && m.down_exps.qtype == crate::QT_F8_E4M3_BLK;
5462 q8 || fp8
5463 });
5464 sliding_gated_moe
5465 && sigmoid_router_enabled()
5466 && moe_dev_enabled()
5467 && moe_slab_enabled()
5468 && !observation_mode
5469 && moe.expert_used_count <= 8
5470 && m.has_uniform_expert_layout()
5471 && m.gate_exps.macros.is_none()
5472 && m.up_exps.macros.is_none()
5473 && m.down_exps.macros.is_none()
5474 && !m.has_macros
5475 && resident_layout_supported
5476 }
5477
5478 pub(crate) fn moe_ffn_sequential(
5480 e: &Engine,
5481 m: &MoeWeights,
5482 z: &CudaSlice<f32>,
5483 t: usize,
5484 cfg: &ModelConfig,
5485 il: u16,
5486 max_block: usize,
5487 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5488 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
5489 }
5490
5491 fn moe_router_logits(
5495 e: &Engine,
5496 m: &MoeWeights,
5497 z: &CudaSlice<f32>,
5498 t: usize,
5499 cfg: &ModelConfig,
5500 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5501 if t < PRIME_MIN_T {
5502 if crate::router_kernel_on() {
5504 e.router_gemv(
5505 m.gate_inp.float_data(),
5506 z,
5507 cfg.n_embd as usize,
5508 m.gate_exps.n_expert,
5509 t,
5510 )
5511 } else {
5512 e.matmul_decode_exact(&m.gate_inp, z, t)
5513 }
5514 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
5515 e.router_gemv(
5516 m.gate_inp.float_data(),
5517 z,
5518 cfg.n_embd as usize,
5519 m.gate_exps.n_expert,
5520 t,
5521 )
5522 } else {
5523 e.matmul(&m.gate_inp, z, t)
5524 }
5525 }
5526
5527 fn trace_moe_routes(
5531 il: u16,
5532 t: usize,
5533 sel_all: &[u32],
5534 weights: &[f32],
5535 ) -> Result<(), Box<dyn std::error::Error>> {
5536 use std::io::Write as _;
5537 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
5538 let mut f = std::fs::OpenOptions::new()
5539 .create(true)
5540 .append(true)
5541 .open(path)?;
5542 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
5543 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
5544 }
5545 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
5546 let mut f = std::fs::OpenOptions::new()
5547 .create(true)
5548 .append(true)
5549 .open(path)?;
5550 let pairs: Vec<String> = sel_all
5551 .iter()
5552 .zip(weights)
5553 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
5554 .collect();
5555 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
5556 }
5557 Ok(())
5558 }
5559
5560 #[allow(clippy::too_many_arguments)]
5561 fn trace_sigmoid_router_logits(
5562 e: &Engine,
5563 il: u16,
5564 t: usize,
5565 n_expert: usize,
5566 n_used: usize,
5567 logits: &CudaSlice<f32>,
5568 m: &MoeWeights,
5569 (scaling_factor, route_norm): (f32, bool),
5570 ) -> Result<(), Box<dyn std::error::Error>> {
5571 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
5572 return Ok(());
5573 }
5574 let logits = e.dtoh(logits)?;
5575 let active: Vec<u8> = m
5576 .active_experts
5577 .as_ref()
5578 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
5579 .unwrap_or_else(|| vec![1; n_expert]);
5580 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
5581 crate::sigrouter_contract::capture_served_logits(
5582 il as u32,
5583 t,
5584 n_expert,
5585 n_used,
5586 scaling_factor,
5587 route_norm,
5588 &active,
5589 &bias,
5590 &logits,
5591 )?;
5592 Ok(())
5593 }
5594
5595 fn trace_moe_input(
5600 e: &Engine,
5601 il: u16,
5602 t: usize,
5603 n_embd: usize,
5604 z: &CudaSlice<f32>,
5605 ) -> Result<(), Box<dyn std::error::Error>> {
5606 use std::io::Write as _;
5607 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
5608 return Ok(());
5609 };
5610 let values = active_matrix_values(z.len(), t, n_embd, "MoE input trace activation")?;
5611 let host = e.dtoh_view(&z.slice(0..values))?;
5612 let bytes = unsafe {
5613 std::slice::from_raw_parts(
5614 host.as_ptr().cast::<u8>(),
5615 host.len() * std::mem::size_of::<f32>(),
5616 )
5617 };
5618 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
5619 let mut state = state
5620 .lock()
5621 .map_err(|_| "MoE input trace writer lock is poisoned")?;
5622 if state.is_none() {
5623 let dir = std::path::PathBuf::from(&dir);
5624 std::fs::create_dir_all(&dir)?;
5625 let index = std::fs::OpenOptions::new()
5626 .create(true)
5627 .append(true)
5628 .open(dir.join("index.jsonl"))?;
5629 *state = Some(MoeInputTraceWriter {
5630 dir,
5631 index,
5632 payloads: std::collections::HashMap::new(),
5633 });
5634 }
5635 let writer = state.as_mut().unwrap();
5636 if writer.dir != std::path::Path::new(&dir) {
5637 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
5638 }
5639 let file_name = format!("layer-{il:03}.f32");
5640 if !writer.payloads.contains_key(&il) {
5641 let payload = std::fs::OpenOptions::new()
5642 .create(true)
5643 .append(true)
5644 .open(writer.dir.join(&file_name))?;
5645 let offset = payload.metadata()?.len();
5646 writer.payloads.insert(il, (payload, offset));
5647 }
5648 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
5649 let row_offset = *offset;
5650 payload.write_all(bytes)?;
5651 *offset += bytes.len() as u64;
5652 writeln!(
5653 writer.index,
5654 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
5655 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
5656 \"payload_bytes\":{}}}",
5657 bytes.len()
5658 )?;
5659 Ok(())
5660 }
5661
5662 #[allow(clippy::too_many_arguments)]
5663 pub(crate) fn moe_ffn_sequential_zq8(
5664 e: &Engine,
5665 m: &MoeWeights,
5666 z: &CudaSlice<f32>,
5667 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5668 t: usize,
5669 cfg: &ModelConfig,
5670 il: u16,
5671 max_block: usize,
5672 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5673 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
5674 let moe = cfg.moe.as_ref().unwrap();
5675 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);
5682 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
5683 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);
5686
5687 let lim_exp = cfg.clamp_exp_at(il as u32);
5690 let lim_shexp = cfg.clamp_shexp_at(il as u32);
5691 let use_cache = Engine::moe_cache_enabled();
5692 let uniform_experts = m.has_uniform_expert_layout();
5693 let moe_q8 = uniform_experts
5694 && moe_q8_enabled()
5695 && q8_expert_supported(m.gate_exps.qtype)
5696 && q8_expert_supported(m.up_exps.qtype)
5697 && q8_expert_supported(m.down_exps.qtype);
5698 let cpu_expert_requested = crate::cpu_experts::configured();
5705 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
5706 return Err(std::io::Error::other(
5707 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
5708 )
5709 .into());
5710 }
5711 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
5712 let freeze_cpu_residency = cpu_expert_requested
5718 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
5719 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
5720 .ok()
5721 .and_then(|value| value.parse::<usize>().ok())
5722 .is_some_and(|tokens| tokens > 0);
5723 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
5724 e.freeze_moe_cache();
5725 }
5726 let cache_frozen = use_cache && e.moe_cache_frozen();
5727 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
5728
5729 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5732 if let Some(sig) = cfg.sigmoid_router() {
5733 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
5734 }
5735
5736 let no_exp_macros = m.gate_exps.macros.is_none()
5775 && m.up_exps.macros.is_none()
5776 && m.down_exps.macros.is_none();
5777 if cfg.sigmoid_router().is_none()
5781 && cfg.m3.is_none()
5782 && cfg.hy3.is_none()
5783 && !cfg.swiglu_clamped_at(il as u32)
5784 && no_exp_macros
5785 && t > MOE_DEV_MAX_T
5789 && m.dev_exps.is_some()
5790 && moe_q8_enabled()
5791 && q8_expert_supported(m.gate_exps.qtype)
5792 && q8_expert_supported(m.up_exps.qtype)
5793 && q8_expert_supported(m.down_exps.qtype)
5794 && std::env::var("MEMRA_MOE_PAIRS")
5795 .map(|v| v != "0")
5796 .unwrap_or(true)
5797 && std::env::var("MEMRA_MOE_STATS").is_err()
5798 {
5799 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
5800 }
5801
5802 let dev_ok = uniform_experts
5820 && cfg.sigmoid_router().is_none()
5821 && cfg.m3.is_none()
5822 && cfg.hy3.is_none()
5823 && !cfg.swiglu_clamped_at(il as u32);
5824 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
5828 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5829 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5830 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
5831 if dev_ok
5832 && t <= MOE_DEV_MAX_T
5833 && m.dev_exps.is_some()
5834 && n_used <= 8
5835 && moe_dev_enabled()
5836 && !observe_routes
5837 {
5838 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5839 }
5840 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
5841 let row_ok = e.with_moe_cache(max_block, |c, eng| {
5842 if moe_prewarm_enabled() {
5843 c.prewarm_layer(il, m, eng)?;
5844 }
5845 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
5846 })?;
5847 if row_ok {
5848 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5849 }
5850 }
5851
5852 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
5854 if cpu_hybrid {
5855 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
5856 e,
5857 &logits,
5858 z,
5859 t,
5860 n_embd,
5861 n_expert,
5862 n_used,
5863 m.exp_probs_b.as_deref(),
5864 sig,
5865 m.active_experts.as_deref(),
5866 )?;
5867 (sel, w, Some(input))
5868 } else {
5869 let (sel, w) =
5870 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
5871 (sel, w, None)
5872 }
5873 } else {
5874 let (sel, w) =
5875 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
5876 (sel, w, None)
5877 };
5878 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
5879
5880 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
5884 Self::trace_moe_input(e, il, t, n_embd, z)?;
5885
5886 let worker_disk_prefetch =
5898 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
5899 let promote_worker_h2d =
5900 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
5901 if promote_worker_h2d {
5902 let mut selected_blocks = Vec::with_capacity(n_used * 3);
5903 for &ex in sel_all.iter().take(n_used) {
5904 let ex = ex as u16;
5905 selected_blocks.extend([
5906 BlockId::new(il, PROJ_GATE, ex),
5907 BlockId::new(il, PROJ_UP, ex),
5908 BlockId::new(il, PROJ_DOWN, ex),
5909 ]);
5910 }
5911 for &ex in sel_all.iter().take(n_used) {
5912 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
5913 }
5914 e.with_moe_cache(max_block, |cache, eng| {
5915 cache.promote_worker_reads_at_safe_boundary(
5916 &selected_blocks,
5917 &selected_blocks,
5918 eng,
5919 )?;
5920 Ok(())
5921 })?;
5922 }
5923
5924 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
5927 let mut cnt = vec![0u32; n_expert];
5928 for &s in sel_all.iter() {
5929 cnt[s as usize] += 1;
5930 }
5931 let total = sel_all.len() as f64;
5932 let mut h = 0.0f64;
5933 let mut active = 0usize;
5934 for &c in &cnt {
5935 if c > 0 {
5936 active += 1;
5937 let p = c as f64 / total;
5938 h -= p * p.log2();
5939 }
5940 }
5941 let maxc = cnt.iter().copied().max().unwrap_or(0);
5942 println!(
5943 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
5944 il,
5945 t,
5946 sel_all.len(),
5947 active,
5948 n_expert,
5949 h,
5950 (n_expert as f64).log2(),
5951 total / active.max(1) as f64,
5952 maxc
5953 );
5954 }
5955
5956 let gdec_may_fire = uniform_experts
5969 && use_cache
5970 && n_used <= 8
5971 && gdec_enabled()
5972 && !cfg.swiglu_clamped_at(il as u32);
5973 let slab_local = m
5989 .dev_exps
5990 .as_ref()
5991 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
5992 let slab_bases = slab_local.map(|d| {
5993 use cudarc::driver::DevicePtr;
5994 let s = e.stream();
5995 let (pg, _g0) = d.gate.device_ptr(&s);
5996 let (pu, _g1) = d.up.device_ptr(&s);
5997 let (pd, _g2) = d.down.device_ptr(&s);
5998 (pg as u64, pu as u64, pd as u64)
5999 });
6000 let slab_fused_may_fire = slab_bases.is_some()
6010 && n_used <= 8
6011 && gdec_enabled()
6012 && !cfg.swiglu_clamped_at(il as u32)
6013 && cfg.m3.is_none()
6014 && no_exp_macros
6015 && moe_q8;
6016 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
6019 e.uninit(t * n_embd)?
6020 } else {
6021 e.zeros(t * n_embd)?
6022 };
6023 let cpu_input = if cpu_hybrid {
6026 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
6027 } else {
6028 None
6029 };
6030
6031 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;
6039 let mut scratch_u: Option<CudaSlice<u8>> = None;
6040 let mut scratch_d: Option<CudaSlice<u8>> = None;
6041 let page_window = moe_page_prefetch_window();
6049
6050 for tok in 0..t {
6053 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
6054 let w = &w_all[tok * n_used..(tok + 1) * n_used];
6055 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
6057
6058 let no_macros = m.gate_exps.macros.is_none()
6072 && m.up_exps.macros.is_none()
6073 && m.down_exps.macros.is_none();
6074 if slab_fused_may_fire {
6084 let (pg, pu, pd) = slab_bases.unwrap();
6085 let mut gp = [0u64; 8];
6086 let mut up = [0u64; 8];
6087 let mut dp = [0u64; 8];
6088 for (j, &ex) in sel.iter().enumerate() {
6089 let ex = ex as usize;
6090 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
6091 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
6092 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
6093 }
6094 let mut wv = [0f32; 8];
6095 wv[..n_used].copy_from_slice(w);
6096 if tok_q8.is_none() {
6097 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6098 }
6099 let (zq, zd) = tok_q8.as_ref().unwrap();
6100 let act = e.moe_gate_up_silu8_q8(
6101 crate::WPtr8(gp),
6102 crate::WPtr8(up),
6103 zq,
6104 zd,
6105 n_embd,
6106 n_ff_exp,
6107 n_used,
6108 m.gate_exps.qtype,
6109 m.up_exps.qtype,
6110 m.gate_exps.row_bytes,
6111 m.up_exps.row_bytes,
6112 )?;
6113 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
6114 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6115 e.moe_down8_fma_q8(
6116 crate::WPtr8(dp),
6117 crate::F32x8(wv),
6118 &aq2,
6119 &ad2,
6120 &mut dst,
6121 n_ff_exp,
6122 n_embd,
6123 n_used,
6124 m.down_exps.qtype,
6125 m.down_exps.row_bytes,
6126 )?;
6127 continue;
6128 }
6129 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
6130 if tok_q8.is_none() {
6131 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6132 }
6133 let (zq, zd) = tok_q8.as_ref().unwrap();
6134 if Self::moe_gdec_token_q8(
6135 e,
6136 m,
6137 il,
6138 max_block,
6139 zq,
6140 zd,
6141 sel,
6142 w,
6143 &mut moe_out,
6144 tok,
6145 n_embd,
6146 n_ff_exp,
6147 n_used,
6148 )? {
6149 continue;
6150 }
6151 } else if gdec_may_fire
6152 && cfg.m3.is_none()
6153 && no_macros
6154 && Self::moe_gdec_token(
6155 e,
6156 m,
6157 il,
6158 max_block,
6159 &zt,
6160 sel,
6161 w,
6162 &mut moe_out,
6163 tok,
6164 n_embd,
6165 n_ff_exp,
6166 n_used,
6167 )?
6168 {
6169 continue;
6170 }
6171
6172 if gdec_may_fire || slab_fused_may_fire {
6178 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6179 e.memset_zeros_view(&mut row)?;
6180 }
6181
6182 let mut cpu_mask = vec![false; sel.len()];
6188 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
6189 let gpu_resident = if use_cache {
6190 e.with_moe_cache(max_block, |cache, _| {
6191 Ok(sel
6192 .iter()
6193 .map(|&expert| {
6194 let expert = expert as u16;
6195 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
6196 .into_iter()
6197 .filter(|&projection| {
6198 cache
6199 .resident(BlockId::new(il, projection, expert))
6200 .is_some()
6201 })
6202 .count()
6203 })
6204 .collect::<Vec<_>>())
6205 })?
6206 } else {
6207 vec![0; sel.len()]
6208 };
6209 let mut cpu_selected = Vec::new();
6210 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
6211 if gpu_resident[index] != 3 {
6212 cpu_mask[index] = true;
6213 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
6214 let expert = expert as usize;
6215 cpu_selected.push((expert, route_weight));
6216 }
6217 }
6218 if crate::cpu_experts::predictor_enabled() {
6219 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6223 crate::cpu_experts::predictor_submit(il, row);
6224 }
6225 if cpu_selected.is_empty() {
6226 None
6227 } else {
6228 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6229 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
6230 .map_err(std::io::Error::other)?;
6231 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
6232 }
6233 } else {
6234 None
6235 };
6236
6237 let worker_window = worker_disk_prefetch
6238 .then(worker_prefetch_window)
6239 .unwrap_or(0);
6240 for (j, &ex) in sel.iter().enumerate() {
6241 if cpu_mask[j] {
6242 continue;
6243 }
6244 let ex = ex as usize;
6245 if let Some(d) = slab_local {
6252 let gl = m.gate_exps.expert_layout(ex);
6253 let ul = m.up_exps.expert_layout(ex);
6254 let dl = m.down_exps.expert_layout(ex);
6255 let (g0, u0, d0) = (
6256 ex * m.gate_exps.expert_stride,
6257 ex * m.up_exps.expert_stride,
6258 ex * m.down_exps.expert_stride,
6259 );
6260 let (gate, up) = if moe_q8 {
6261 if tok_q8.is_none() {
6262 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6263 }
6264 let (zq, zd) = tok_q8.as_ref().unwrap();
6265 (
6266 e.qmatvec_expert_q8(
6267 &d.gate,
6268 g0..g0 + gl.len,
6269 zq,
6270 zd,
6271 1,
6272 m.gate_exps.in_f,
6273 m.gate_exps.out_f,
6274 gl.qtype,
6275 gl.row_bytes,
6276 )?,
6277 e.qmatvec_expert_q8(
6278 &d.up,
6279 u0..u0 + ul.len,
6280 zq,
6281 zd,
6282 1,
6283 m.up_exps.in_f,
6284 m.up_exps.out_f,
6285 ul.qtype,
6286 ul.row_bytes,
6287 )?,
6288 )
6289 } else {
6290 (
6291 e.qmatvec_view(
6292 &d.gate,
6293 g0..g0 + gl.len,
6294 &zt,
6295 1,
6296 m.gate_exps.in_f,
6297 m.gate_exps.out_f,
6298 gl.qtype,
6299 gl.row_bytes,
6300 )?,
6301 e.qmatvec_view(
6302 &d.up,
6303 u0..u0 + ul.len,
6304 &zt,
6305 1,
6306 m.up_exps.in_f,
6307 m.up_exps.out_f,
6308 ul.qtype,
6309 ul.row_bytes,
6310 )?,
6311 )
6312 };
6313 let mut act = e.uninit(n_ff_exp)?;
6314 Self::ffn_act_lim(
6315 e,
6316 cfg,
6317 &gate,
6318 &up,
6319 m.gate_exps.macro_scale(ex),
6320 m.up_exps.macro_scale(ex),
6321 lim_exp,
6322 &mut act,
6323 n_ff_exp,
6324 )?;
6325 let y = if moe_q8 {
6326 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6327 e.qmatvec_expert_q8(
6328 &d.down,
6329 d0..d0 + dl.len,
6330 &aq2,
6331 &ad2,
6332 1,
6333 m.down_exps.in_f,
6334 m.down_exps.out_f,
6335 dl.qtype,
6336 dl.row_bytes,
6337 )?
6338 } else {
6339 let actv = act.slice(0..n_ff_exp);
6340 e.qmatvec_view(
6341 &d.down,
6342 d0..d0 + dl.len,
6343 &actv,
6344 1,
6345 m.down_exps.in_f,
6346 m.down_exps.out_f,
6347 dl.qtype,
6348 dl.row_bytes,
6349 )?
6350 };
6351 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6352 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6353 continue;
6354 }
6355 for next in page_prefetch_positions(j, sel.len(), page_window) {
6356 Self::moe_prefetch_host_expert(sel[next] as usize, m);
6357 }
6358 let keep = [
6359 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
6360 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
6361 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
6362 ];
6363 if worker_disk_prefetch && worker_window > 0 {
6364 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
6365 Self::moe_prefetch_disk_expert(
6366 e,
6367 il,
6368 sel[next] as usize,
6369 m,
6370 max_block,
6371 &keep,
6372 )?;
6373 }
6374 } else if cache_dispatch
6375 && !cpu_hybrid
6376 && moe_prefetch_enabled()
6377 && j + 1 < sel.len()
6378 {
6379 let next = sel[j + 1] as usize;
6380 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
6381 }
6382 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
6383 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
6384 if (gate_q8 || up_q8) && tok_q8.is_none() {
6387 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6388 }
6389 let gate = if gate_q8 {
6390 let (zq, zd) = tok_q8.as_ref().unwrap();
6391 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
6392 } else {
6393 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
6394 };
6395 let up = if up_q8 {
6396 let (zq, zd) = tok_q8.as_ref().unwrap();
6397 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
6398 } else {
6399 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
6400 };
6401 let mut act = e.uninit(n_ff_exp)?;
6402 Self::ffn_act_lim(
6403 e,
6404 cfg,
6405 &gate,
6406 &up,
6407 m.gate_exps.macro_scale(ex),
6408 m.up_exps.macro_scale(ex),
6409 lim_exp,
6410 &mut act,
6411 n_ff_exp,
6412 )?;
6413 let y = if down_q8 {
6414 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6415 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
6416 } else {
6417 let actv = act.slice(0..n_ff_exp);
6418 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
6419 };
6420 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6421 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6423 } else if cache_dispatch {
6424 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
6429 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
6430 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6432 e,
6433 cfg,
6434 &gate,
6435 &up,
6436 m.gate_exps.macro_scale(ex),
6437 m.up_exps.macro_scale(ex),
6438 lim_exp,
6439 &mut act,
6440 n_ff_exp,
6441 )?;
6442 let actv = act.slice(0..n_ff_exp);
6443 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
6444 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6445 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6447 } else if cache_frozen {
6448 let gate = Self::moe_frozen_gemm(
6453 e,
6454 il,
6455 PROJ_GATE,
6456 ex,
6457 m,
6458 max_block,
6459 &zt,
6460 &mut scratch_g,
6461 g_len,
6462 )?;
6463 let up = Self::moe_frozen_gemm(
6464 e,
6465 il,
6466 PROJ_UP,
6467 ex,
6468 m,
6469 max_block,
6470 &zt,
6471 &mut scratch_u,
6472 u_len,
6473 )?;
6474 let mut act = e.uninit(n_ff_exp)?;
6475 Self::ffn_act_lim(
6476 e,
6477 cfg,
6478 &gate,
6479 &up,
6480 m.gate_exps.macro_scale(ex),
6481 m.up_exps.macro_scale(ex),
6482 lim_exp,
6483 &mut act,
6484 n_ff_exp,
6485 )?;
6486 let actv = act.slice(0..n_ff_exp);
6487 let y = Self::moe_frozen_gemm(
6488 e,
6489 il,
6490 PROJ_DOWN,
6491 ex,
6492 m,
6493 max_block,
6494 &actv,
6495 &mut scratch_d,
6496 d_len,
6497 )?;
6498 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6499 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6500 } else {
6501 if scratch_g.is_none() {
6505 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
6506 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
6507 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
6508 }
6509 let (sg, su, sd) = (
6510 scratch_g.as_mut().unwrap(),
6511 scratch_u.as_mut().unwrap(),
6512 scratch_d.as_mut().unwrap(),
6513 );
6514 let gl = m.gate_exps.expert_layout(ex);
6515 let ul = m.up_exps.expert_layout(ex);
6516 let dl = m.down_exps.expert_layout(ex);
6517 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
6518 let gate = e.qmatvec_view(
6519 sg,
6520 0..gl.len,
6521 &zt,
6522 1,
6523 m.gate_exps.in_f,
6524 m.gate_exps.out_f,
6525 gl.qtype,
6526 gl.row_bytes,
6527 )?;
6528
6529 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
6530 let up = e.qmatvec_view(
6531 su,
6532 0..ul.len,
6533 &zt,
6534 1,
6535 m.up_exps.in_f,
6536 m.up_exps.out_f,
6537 ul.qtype,
6538 ul.row_bytes,
6539 )?;
6540
6541 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6543 e,
6544 cfg,
6545 &gate,
6546 &up,
6547 m.gate_exps.macro_scale(ex),
6548 m.up_exps.macro_scale(ex),
6549 lim_exp,
6550 &mut act,
6551 n_ff_exp,
6552 )?;
6553
6554 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
6555 let actv = act.slice(0..n_ff_exp);
6556 let y = e.qmatvec_view(
6557 sd,
6558 0..dl.len,
6559 &actv,
6560 1,
6561 m.down_exps.in_f,
6562 m.down_exps.out_f,
6563 dl.qtype,
6564 dl.row_bytes,
6565 )?;
6566
6567 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6568 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6569 }
6570 }
6571 if let Some(worker) = cpu_worker {
6572 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
6573 let cpu_output = e.htod(&cpu_output)?;
6574 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6575 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
6576 }
6577 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
6578 for (j, &ex) in sel.iter().enumerate() {
6579 if cpu_mask[j] {
6580 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
6581 }
6582 }
6583 }
6584 }
6585
6586 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6591 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6592 {
6593 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
6602 let (sg_gate, sg_up) = if t == 1 {
6603 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
6604 } else if verify_t {
6605 (
6606 e.matmul_decode_exact(gate_shexp, z, t)?,
6607 e.matmul_decode_exact(up_shexp, z, t)?,
6608 )
6609 } else {
6610 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
6612 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
6614 e,
6615 cfg,
6616 &sg_gate,
6617 &sg_up,
6618 1.0,
6619 1.0,
6620 lim_shexp,
6621 &mut sa,
6622 t * n_ff_sh,
6623 )?;
6624 let sh = if verify_t {
6625 e.matmul_decode_exact(down_shexp, &sa, t)?
6626 } else {
6627 e.matmul(down_shexp, &sa, t)?
6628 }; let g = match &m.gate_inp_shexp {
6642 Some(gate_inp_shexp) => {
6643 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
6644 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6645 } else {
6646 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6647 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
6649 g
6650 }
6651 }
6652 None => e.htod(&vec![1.0f32; t])?,
6653 };
6654 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6656 }
6657
6658 Ok(moe_out)
6659 }
6660
6661 pub fn stage1_h2d_per_token(&self) -> u64 {
6664 use crate::hybrid::Ffn;
6665 let n_used = self
6666 .cfg
6667 .moe
6668 .as_ref()
6669 .map(|m| m.expert_used_count as u64)
6670 .unwrap_or(0);
6671 let mut bytes = 0u64;
6672 for l in self.layers.iter() {
6673 if let Ffn::Moe(m) = &l.ffn {
6674 bytes += n_used
6675 * (m.gate_exps.max_expert_bytes()
6676 + m.up_exps.max_expert_bytes()
6677 + m.down_exps.max_expert_bytes()) as u64;
6678 }
6679 }
6680 bytes
6681 }
6682
6683 pub(crate) fn max_moe_block(&self) -> usize {
6687 use crate::hybrid::Ffn;
6688 let mut mx = 0usize;
6689 let mut scan = |ffn: &Ffn| {
6690 if let Ffn::Moe(m) = ffn {
6691 mx = mx
6692 .max(m.gate_exps.max_expert_bytes())
6693 .max(m.up_exps.max_expert_bytes())
6694 .max(m.down_exps.max_expert_bytes());
6695 }
6696 };
6697 for l in self.layers.iter() {
6698 scan(&l.ffn);
6699 }
6700 if let Some(mtp) = self.mtp.as_ref() {
6701 scan(&mtp.ffn);
6702 }
6703 mx
6704 }
6705
6706 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
6709 use crate::hybrid::Ffn;
6710 let mut sizes = Vec::new();
6711 let mut scan = |ffn: &Ffn| {
6712 let Ffn::Moe(m) = ffn else { return };
6713 for ex in 0..m.gate_exps.n_expert {
6714 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
6715 continue;
6716 }
6717 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
6718 let len = exps.expert_layout(ex).len;
6719 if len > 0 {
6720 sizes.push(len);
6721 }
6722 }
6723 }
6724 };
6725 for layer in &self.layers {
6726 scan(&layer.ffn);
6727 }
6728 if let Some(mtp) = &self.mtp {
6729 scan(&mtp.ffn);
6730 }
6731 sizes
6732 }
6733
6734 pub fn save_cpu_expert_residency_profile(
6740 &self,
6741 e: &Engine,
6742 path: &std::path::Path,
6743 ) -> Result<(), Box<dyn std::error::Error>> {
6744 let Some(ids) = e.export_moe_residency() else {
6745 return Err("no MoE residency cache to persist".into());
6746 };
6747 let mut body = format!(
6748 "memra-freeze-profile v1 max_block={} blocks={}\n",
6749 self.max_moe_block(),
6750 ids.len()
6751 );
6752 for (layer, proj, ex) in &ids {
6753 body.push_str(&format!("{layer} {proj} {ex}\n"));
6754 }
6755 let tmp = path.with_extension("tmp");
6756 std::fs::write(&tmp, body)?;
6757 std::fs::rename(&tmp, path)?;
6758 println!(
6759 "[moe-cache] freeze profile saved: {} blocks -> {}",
6760 ids.len(),
6761 path.display()
6762 );
6763 Ok(())
6764 }
6765
6766 pub fn restore_cpu_expert_residency_profile(
6770 &self,
6771 e: &Engine,
6772 path: &std::path::Path,
6773 ) -> Result<bool, Box<dyn std::error::Error>> {
6774 use crate::hybrid::Ffn;
6775 use crate::moe_cache::BlockId;
6776 let Ok(content) = std::fs::read_to_string(path) else {
6777 return Ok(false);
6778 };
6779 let mut lines = content.lines();
6780 let Some(header) = lines.next() else {
6781 return Ok(false);
6782 };
6783 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
6784 if !header.starts_with(&expected) {
6785 println!(
6786 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
6787 path.display()
6788 );
6789 return Ok(false);
6790 }
6791 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
6792 std::collections::HashMap::new();
6793 for line in lines {
6794 let mut fields = line.split_whitespace();
6795 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
6796 else {
6797 continue;
6798 };
6799 let (Ok(layer), Ok(proj), Ok(ex)) =
6800 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
6801 else {
6802 continue;
6803 };
6804 by_layer
6805 .entry(layer)
6806 .or_default()
6807 .push(BlockId::new(layer, proj, ex));
6808 }
6809 let requested: usize = by_layer.values().map(Vec::len).sum();
6810 if requested == 0 {
6811 return Ok(false);
6812 }
6813 let max_block = self.max_moe_block();
6814 let mut restaged = 0usize;
6815 let mut stage_layer =
6816 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
6817 let Ffn::Moe(m) = ffn else { return Ok(()) };
6818 let Some(ids) = by_layer.get(&layer_index) else {
6819 return Ok(());
6820 };
6821 e.with_moe_cache(max_block, |cache, eng| {
6822 for id in ids {
6823 if cache.restage_block(*id, m, eng)? {
6824 restaged += 1;
6825 }
6826 }
6827 Ok(())
6828 })
6829 };
6830 for (index, layer) in self.layers.iter().enumerate() {
6831 stage_layer(index as u16, &layer.ffn)?;
6832 }
6833 if let Some(mtp) = self.mtp.as_ref() {
6834 stage_layer(u16::MAX, &mtp.ffn)?;
6835 }
6836 e.freeze_moe_cache();
6837 println!(
6838 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
6839 path.display()
6840 );
6841 Ok(true)
6842 }
6843
6844 pub fn freeze_cpu_expert_residency(
6846 &self,
6847 e: &Engine,
6848 ) -> Result<(), Box<dyn std::error::Error>> {
6849 e.freeze_moe_cache();
6850 Ok(())
6851 }
6852
6853 pub fn ffn_act(
6861 e: &Engine,
6862 cfg: &ModelConfig,
6863 gate: &CudaSlice<f32>,
6864 up: &CudaSlice<f32>,
6865 act: &mut CudaSlice<f32>,
6866 n: usize,
6867 ) -> Result<(), Box<dyn std::error::Error>> {
6868 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
6869 }
6870
6871 #[allow(clippy::too_many_arguments)]
6875 pub(crate) fn ffn_act_scaled(
6876 e: &Engine,
6877 cfg: &ModelConfig,
6878 gate: &CudaSlice<f32>,
6879 up: &CudaSlice<f32>,
6880 gs: f32,
6881 us: f32,
6882 act: &mut CudaSlice<f32>,
6883 n: usize,
6884 ) -> Result<(), Box<dyn std::error::Error>> {
6885 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
6886 }
6887
6888 #[allow(clippy::too_many_arguments)]
6897 pub(crate) fn ffn_act_lim(
6898 e: &Engine,
6899 cfg: &ModelConfig,
6900 gate: &CudaSlice<f32>,
6901 up: &CudaSlice<f32>,
6902 gs: f32,
6903 us: f32,
6904 limit: Option<f32>,
6905 act: &mut CudaSlice<f32>,
6906 n: usize,
6907 ) -> Result<(), Box<dyn std::error::Error>> {
6908 if let Some(m3) = cfg.m3.as_ref() {
6909 debug_assert!(
6910 limit.is_none(),
6911 "m3 swigluoai and step35 clamp are different archs"
6912 );
6913 return e.swigluoai_mul_scaled(
6914 gate,
6915 up,
6916 gs,
6917 us,
6918 m3.swiglu_alpha,
6919 m3.swiglu_limit,
6920 act,
6921 n,
6922 );
6923 }
6924 if let Some(l) = limit {
6925 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
6926 }
6927 if gs == 1.0 && us == 1.0 {
6928 return e.silu_mul(gate, up, act, n);
6929 }
6930 e.silu_mul_scaled(gate, up, gs, us, act, n)
6931 }
6932
6933 fn moe_route(
6939 e: &Engine,
6940 logits: &CudaSlice<f32>,
6941 t: usize,
6942 n_expert: usize,
6943 n_used: usize,
6944 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6945 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
6946 }
6947
6948 #[allow(clippy::too_many_arguments)]
6956 fn moe_route_sigmoid_cfg(
6957 e: &Engine,
6958 logits: &CudaSlice<f32>,
6959 t: usize,
6960 n_expert: usize,
6961 n_used: usize,
6962 m: &MoeWeights,
6963 (sf, route_norm): (f32, bool),
6964 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6965 if sigmoid_router_enabled() {
6966 return e.moe_router_sigmoid_topk_host(
6967 logits,
6968 t,
6969 n_expert,
6970 n_used,
6971 m.active_count(),
6972 &m.exp_probs_b_dev,
6973 &m.active_experts_dev,
6974 sf,
6975 route_norm,
6976 );
6977 }
6978 let lg = e.dtoh(logits)?;
6979 Self::moe_route_sigmoid_host(
6980 &lg,
6981 t,
6982 n_expert,
6983 n_used,
6984 m.exp_probs_b.as_deref(),
6985 sf,
6986 route_norm,
6987 m.active_experts.as_deref(),
6988 )
6989 }
6990
6991 fn moe_route_cfg(
6994 e: &Engine,
6995 logits: &CudaSlice<f32>,
6996 t: usize,
6997 n_expert: usize,
6998 n_used: usize,
6999 active: Option<&[bool]>,
7000 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7001 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
7004 return e.moe_router_topk_host(logits, t, n_expert, n_used);
7005 }
7006 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
7009 let mut w_out = vec![0f32; t * n_used];
7010 for tok in 0..t {
7011 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7012 let maxl = row
7014 .iter()
7015 .enumerate()
7016 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
7017 .map(|(_, &x)| x)
7018 .fold(f32::NEG_INFINITY, f32::max);
7019 let mut probs = vec![0f32; n_expert];
7020 let mut den = 0f32;
7021 for i in 0..n_expert {
7022 if active.is_some_and(|mask| !mask[i]) {
7023 continue;
7024 }
7025 let x = (row[i] - maxl).exp();
7026 probs[i] = x;
7027 den += x;
7028 }
7029 for p in probs.iter_mut() {
7030 *p /= den;
7031 }
7032 let mut idx: Vec<usize> = (0..n_expert)
7034 .filter(|&i| active.is_none_or(|mask| mask[i]))
7035 .collect();
7036 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
7037 let sl = &idx[..n_used];
7038 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
7039 let mut ws: f32 = wv.iter().sum();
7040 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
7042 *x /= ws;
7043 }
7044 for j in 0..n_used {
7045 sel[tok * n_used + j] = sl[j] as u32;
7046 w_out[tok * n_used + j] = wv[j];
7047 }
7048 }
7049 Ok((sel, w_out))
7050 }
7051
7052 #[allow(clippy::too_many_arguments)]
7053 fn moe_route_sigmoid_with_input(
7054 e: &Engine,
7055 logits: &CudaSlice<f32>,
7056 input: &CudaSlice<f32>,
7057 t: usize,
7058 in_features: usize,
7059 n_expert: usize,
7060 n_used: usize,
7061 bias: Option<&[f32]>,
7062 (sf, route_norm): (f32, bool),
7063 active: Option<&[bool]>,
7064 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
7065 let logit_values =
7066 active_matrix_values(logits.len(), t, n_expert, "sigmoid router logits")?;
7067 let input_values =
7068 active_matrix_values(input.len(), t, in_features, "sigmoid router input")?;
7069 let (lg, input) = e.dtoh_pair_views(
7070 &logits.slice(0..logit_values),
7071 &input.slice(0..input_values),
7072 )?;
7073 let (sel, w) =
7074 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
7075 Ok((sel, w, input))
7076 }
7077
7078 pub fn start_moe_prefetch_predictor(
7083 &self,
7084 e: &Engine,
7085 cfg: &ModelConfig,
7086 ) -> Result<(), Box<dyn std::error::Error>> {
7087 use crate::hybrid::Ffn;
7088 let Some(sig) = cfg.sigmoid_router() else {
7089 return Err("prefetch predictor requires a sigmoid-router arch".into());
7090 };
7091 let resident: std::collections::HashSet<(u16, u8, u16)> = e
7092 .export_moe_residency()
7093 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
7094 .into_iter()
7095 .collect();
7096 let mut layers = Vec::new();
7097 for (index, layer) in self.layers.iter().enumerate() {
7098 let Ffn::Moe(m) = &layer.ffn else { continue };
7099 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
7100 continue;
7101 };
7102 let router = e.dtoh(data)?;
7103 let n_expert = m.gate_exps.n_expert;
7104 let n_embd = m.gate_exps.in_f;
7105 if router.len() != n_embd * n_expert {
7106 continue;
7107 }
7108 let build = |exps: &crate::model::HostExps| {
7109 (0..n_expert)
7110 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
7111 .collect::<Vec<_>>()
7112 };
7113 layers.push((
7114 index as u16,
7115 crate::cpu_experts::PredictLayerInit {
7116 router,
7117 bias: m.exp_probs_b.clone(),
7118 active: m.active_experts.clone(),
7119 n_embd,
7120 n_used: cfg
7121 .moe
7122 .as_ref()
7123 .map(|moe| moe.expert_used_count as usize)
7124 .ok_or("prefetch predictor requires MoE config")?,
7125 sig,
7126 weights_n_expert: n_expert,
7127 gate: build(&m.gate_exps),
7128 up: build(&m.up_exps),
7129 down: build(&m.down_exps),
7130 },
7131 ));
7132 }
7133 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
7134 }
7135
7136 #[allow(clippy::too_many_arguments)]
7139 pub fn moe_route_sigmoid_host_public(
7140 logits: &[f32],
7141 t: usize,
7142 n_expert: usize,
7143 n_used: usize,
7144 bias: Option<&[f32]>,
7145 sf: f32,
7146 route_norm: bool,
7147 active: Option<&[bool]>,
7148 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7149 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
7150 }
7151
7152 #[allow(clippy::too_many_arguments)]
7153 fn moe_route_sigmoid_host(
7154 lg: &[f32],
7155 t: usize,
7156 n_expert: usize,
7157 n_used: usize,
7158 bias: Option<&[f32]>,
7159 sf: f32,
7160 route_norm: bool,
7161 active: Option<&[bool]>,
7162 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7163 let active_count = active
7164 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
7165 .unwrap_or(n_expert);
7166 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
7167 if lg.len() != t * n_expert {
7168 return Err(format!(
7169 "sigmoid router logits length mismatch: got {}, expected {}",
7170 lg.len(),
7171 t * n_expert,
7172 )
7173 .into());
7174 }
7175 let mut sel = vec![0u32; t * n_used];
7176 let mut w_out = vec![0f32; t * n_used];
7177 for tok in 0..t {
7178 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7179 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
7180 let selsc: Vec<f32> = match bias {
7182 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
7183 None => scores.clone(),
7184 };
7185 let mut idx: Vec<usize> = (0..n_expert)
7186 .filter(|&i| active.is_none_or(|mask| mask[i]))
7187 .collect();
7188 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
7189 let sl = &idx[..n_used];
7190 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
7191 if route_norm {
7192 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
7193 for x in wv.iter_mut() {
7194 *x = *x / ws * sf;
7195 }
7196 } else {
7197 for x in wv.iter_mut() {
7198 *x *= sf;
7199 }
7200 }
7201 for j in 0..n_used {
7202 sel[tok * n_used + j] = sl[j] as u32;
7203 w_out[tok * n_used + j] = wv[j];
7204 }
7205 }
7206 Ok((sel, w_out))
7207 }
7208
7209 #[allow(clippy::too_many_arguments)]
7213 fn moe_ffn_sigmoid_dev(
7214 e: &Engine,
7215 m: &MoeWeights,
7216 z: &CudaSlice<f32>,
7217 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7218 logits: &CudaSlice<f32>,
7219 t: usize,
7220 cfg: &ModelConfig,
7221 il: u16,
7222 (scaling_factor, route_norm): (f32, bool),
7223 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7224 let moe = cfg.moe.as_ref().unwrap();
7225 let n_embd = cfg.n_embd as usize;
7226 let n_expert = moe.expert_count as usize;
7227 let n_used = moe.expert_used_count as usize;
7228 let n_ff_exp = moe.expert_ff_length as usize;
7229 let dev = m.dev_exps.as_ref().unwrap();
7230 debug_assert_eq!(dev.dev, e.ctx().ordinal());
7231 debug_assert!(m.has_uniform_expert_layout());
7232 debug_assert!(!m.has_macros);
7233
7234 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
7235 logits,
7236 t,
7237 n_expert,
7238 n_used,
7239 m.active_count(),
7240 &m.exp_probs_b_dev,
7241 &m.active_experts_dev,
7242 scaling_factor,
7243 route_norm,
7244 )?;
7245 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
7246 if let Some(fp8) = dev.fp8_blk.as_ref() {
7247 debug_assert_eq!(m.gate_exps.qtype, crate::QT_F8_E4M3_BLK);
7248 debug_assert_eq!(m.up_exps.qtype, crate::QT_F8_E4M3_BLK);
7249 debug_assert_eq!(m.down_exps.qtype, crate::QT_F8_E4M3_BLK);
7250 debug_assert_eq!(fp8.gate.rows, m.gate_exps.out_f.div_ceil(128));
7251 debug_assert_eq!(fp8.up.rows, m.up_exps.out_f.div_ceil(128));
7252 debug_assert_eq!(fp8.down.rows, m.down_exps.out_f.div_ceil(128));
7253
7254 let selected = e.dtoh_i32(&sel_d)?;
7261 let route_weights = e.dtoh(&w_d)?;
7262 let mut moe_out = e.zeros(t * n_embd)?;
7263 for tok in 0..t {
7264 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
7265 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7266 for j in 0..n_used {
7267 let pair = tok * n_used + j;
7268 let expert = selected[pair] as usize;
7269 let gate = Self::moe_resident_fp8_e4m3(
7270 e,
7271 &m.gate_exps,
7272 &dev.gate,
7273 &fp8.gate,
7274 expert,
7275 &zt,
7276 1,
7277 )?;
7278 let up = Self::moe_resident_fp8_e4m3(
7279 e, &m.up_exps, &dev.up, &fp8.up, expert, &zt, 1,
7280 )?;
7281 let mut act = e.uninit(n_ff_exp)?;
7282 Self::ffn_act_lim(
7283 e,
7284 cfg,
7285 &gate,
7286 &up,
7287 1.0,
7288 1.0,
7289 cfg.clamp_exp_at(il as u32),
7290 &mut act,
7291 n_ff_exp,
7292 )?;
7293 let act = act.slice(0..n_ff_exp);
7294 let down = Self::moe_resident_fp8_e4m3(
7295 e,
7296 &m.down_exps,
7297 &dev.down,
7298 &fp8.down,
7299 expert,
7300 &act,
7301 1,
7302 )?;
7303 e.axpy_into(&down, route_weights[pair], &mut dst, n_embd)?;
7304 }
7305 }
7306 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7307 eprintln!(
7308 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} \
7309 native=fp8blk-w8a8-e4m3-reference clamp={}",
7310 cfg.clamp_exp_at(il as u32).is_some(),
7311 );
7312 }
7313 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7314 return Ok(moe_out);
7315 }
7316 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
7317 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7318 (combined, combined)
7319 } else {
7320 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7321 };
7322 let (zq, zd) = match (t, zq8) {
7323 (1, Some((q, d))) => (q.clone(), d.clone()),
7324 _ => e.quantize_q8_1(z, t, n_embd)?,
7325 };
7326 let n_pairs = t * n_used;
7327 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
7328 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7332 let pair_tok_d = e.htod_i32(&pair_tok)?;
7333 let gate = e.moe_pairs_matvec_q8(
7334 &dev.ptr_row,
7335 0,
7336 &pair_tok_d,
7337 &sel_d,
7338 &zq,
7339 &zd,
7340 n_embd,
7341 n_ff_exp,
7342 n_expert,
7343 n_pairs,
7344 m.gate_exps.qtype,
7345 gate_row_bytes,
7346 )?;
7347 let up = e.moe_pairs_matvec_q8(
7348 &dev.ptr_row,
7349 1,
7350 &pair_tok_d,
7351 &sel_d,
7352 &zq,
7353 &zd,
7354 n_embd,
7355 n_ff_exp,
7356 n_expert,
7357 n_pairs,
7358 m.up_exps.qtype,
7359 up_row_bytes,
7360 )?;
7361 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7362 Self::ffn_act_lim(
7363 e,
7364 cfg,
7365 &gate,
7366 &up,
7367 1.0,
7368 1.0,
7369 cfg.clamp_exp_at(il as u32),
7370 &mut act,
7371 n_pairs * n_ff_exp,
7372 )?;
7373 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7374 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7375 let pair_self_d = e.htod_i32(&pair_self)?;
7376 let down = e.moe_pairs_matvec_q8(
7377 &dev.ptr_row,
7378 2,
7379 &pair_self_d,
7380 &sel_d,
7381 &aq2,
7382 &ad2,
7383 n_ff_exp,
7384 n_embd,
7385 n_expert,
7386 n_pairs,
7387 m.down_exps.qtype,
7388 m.down_exps.row_bytes,
7389 )?;
7390 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7391 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7392 let tok_off_d = e.htod_i32(&tok_off)?;
7393 let tok_ids_d = e.htod_i32(&tok_ids)?;
7394 let mut output = e.uninit(t * n_embd)?;
7395 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
7396 output
7397 } else {
7398 let act = e.moe_gate_up_silu8_dev_q8_rows(
7399 &dev.ptr_row,
7400 &sel_d,
7401 &zq,
7402 &zd,
7403 t,
7404 n_embd,
7405 n_ff_exp,
7406 n_used,
7407 n_expert,
7408 m.gate_exps.qtype,
7409 m.up_exps.qtype,
7410 gate_row_bytes,
7411 up_row_bytes,
7412 &m.dev_macros,
7413 )?;
7414 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7415 let mut output = e.uninit(t * n_embd)?;
7416 e.moe_down8_fma_dev_q8_rows_g(
7417 &dev.ptr_row,
7418 &sel_d,
7419 &w_d,
7420 &aq2,
7421 &ad2,
7422 &mut output,
7423 t,
7424 n_ff_exp,
7425 n_embd,
7426 n_used,
7427 n_expert,
7428 m.down_exps.qtype,
7429 m.down_exps.row_bytes,
7430 )?;
7431 output
7432 };
7433
7434 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7435 eprintln!(
7436 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
7437 cfg.clamp_exp_at(il as u32).is_some(),
7438 dev.gu_il,
7439 );
7440 }
7441 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7442 Ok(moe_out)
7443 }
7444
7445 #[allow(clippy::too_many_arguments)]
7446 fn moe_resident_fp8_e4m3(
7447 e: &Engine,
7448 exps: &crate::model::HostExps,
7449 bytes: &CudaSlice<u8>,
7450 scales: &crate::hybrid::DevExpertFp8ProjectionScales,
7451 expert: usize,
7452 x: &cudarc::driver::CudaView<f32>,
7453 m: usize,
7454 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7455 let layout = exps.expert_layout(expert);
7456 debug_assert_eq!(layout.qtype, crate::QT_F8_E4M3_BLK);
7457 debug_assert_eq!(scales.rows * scales.cols, scales.expert_stride);
7458 let byte_start = expert * exps.expert_stride;
7459 let scale_start = expert * scales.expert_stride;
7460 let weight = bytes.slice(byte_start..byte_start + layout.len);
7461 let scale = scales
7462 .scales
7463 .slice(scale_start..scale_start + scales.expert_stride);
7464 e.qmatvec_mmq_fp8_blk_view(&weight, &scale, x, m, exps.in_f, exps.out_f)
7465 }
7466
7467 fn moe_ffn_pairs(
7476 e: &Engine,
7477 m: &MoeWeights,
7478 z: &CudaSlice<f32>,
7479 logits: &CudaSlice<f32>,
7480 t: usize,
7481 cfg: &ModelConfig,
7482 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7483 let moe = cfg.moe.as_ref().unwrap();
7484 let n_embd = cfg.n_embd as usize;
7485 let n_expert = moe.expert_count as usize;
7486 let n_used = moe.expert_used_count as usize;
7487 let n_ff_exp = moe.expert_ff_length as usize;
7488 debug_assert!(
7493 !cfg.swiglu_clamped_anywhere(),
7494 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
7495 );
7496 let dev = m.dev_exps.as_ref().unwrap();
7497 let (rbg_d, rbu_d) = if dev.gu_il {
7499 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7500 (sxx, sxx)
7501 } else {
7502 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7503 };
7504
7505 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
7506 let n_pairs = t * n_used;
7507 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
7510 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
7511 let pair_w: Vec<f32> = w_all.clone();
7512 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7513 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7514 let pt = e.htod_i32(&pair_tok)?;
7515 let px = e.htod_i32(&pair_ex)?;
7516 let pw = e.htod(&pair_w)?;
7517 let toff = e.htod_i32(&tok_off)?;
7518 let tids = e.htod_i32(&tok_ids)?;
7519
7520 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7524 for p in 0..n_pairs {
7525 by_ex[pair_ex[p] as usize].push(p as i32);
7526 }
7527 let mut ex_ids: Vec<i32> = Vec::new();
7528 let mut ex_off: Vec<i32> = vec![0];
7529 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
7530 for (ex, list) in by_ex.iter().enumerate() {
7531 if list.is_empty() {
7532 continue;
7533 }
7534 ex_ids.push(ex as i32);
7535 ex_pairs.extend_from_slice(list);
7536 ex_off.push(ex_pairs.len() as i32);
7537 }
7538 let n_active = ex_ids.len();
7539 let exi = e.htod_i32(&ex_ids)?;
7540 let exo = e.htod_i32(&ex_off)?;
7541 let exp_d = e.htod_i32(&ex_pairs)?;
7542 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7563 let mma_t = *MMA_T.get_or_init(|| {
7564 std::env::var("MEMRA_MOE_MMA_T")
7565 .ok()
7566 .and_then(|v| v.parse().ok())
7567 .unwrap_or(16)
7568 });
7569 let use_mma = std::env::var("MEMRA_MOE_MMA")
7570 .map(|v| v != "0")
7571 .unwrap_or(true)
7572 && t >= mma_t
7573 && q8_expert_dec_supported(m.gate_exps.qtype)
7574 && q8_expert_dec_supported(m.up_exps.qtype)
7575 && q8_expert_dec_supported(m.down_exps.qtype)
7576 && n_embd % 256 == 0
7577 && n_ff_exp % 256 == 0;
7578 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
7594 && q8_expert_dec_supported(m.up_exps.qtype)
7595 && q8_expert_dec_supported(m.down_exps.qtype)
7596 && n_embd % 256 == 0
7597 && n_ff_exp % 256 == 0;
7598 let f16g_mode = crate::moe_f16g_mode();
7599 let f16g = f16g_mode != 0
7600 && t >= mma_t
7601 && (f16g_mode != 3 || !mma_capable)
7602 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
7603 && f16g_proj_ok(m.up_exps.qtype, n_embd)
7604 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
7605 if use_mma || f16g {
7606 let y_down = if f16g {
7614 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
7618 let csr_tok_d = e.htod_i32(&csr_tok)?;
7619 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
7620 let g_csr = e.moe_f16_grouped(
7621 &dev.ptr_row,
7622 0,
7623 n_expert,
7624 &exi,
7625 &ex_off,
7626 &exo,
7627 &z_f16,
7628 &z_s,
7629 n_embd,
7630 n_ff_exp,
7631 n_active,
7632 n_pairs,
7633 m.gate_exps.qtype,
7634 rbg_d,
7635 )?;
7636 let u_csr = e.moe_f16_grouped(
7637 &dev.ptr_row,
7638 1,
7639 n_expert,
7640 &exi,
7641 &ex_off,
7642 &exo,
7643 &z_f16,
7644 &z_s,
7645 n_embd,
7646 n_ff_exp,
7647 n_active,
7648 n_pairs,
7649 m.up_exps.qtype,
7650 rbu_d,
7651 )?;
7652 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
7653 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
7654 let d_csr = e.moe_f16_grouped(
7655 &dev.ptr_row,
7656 2,
7657 n_expert,
7658 &exi,
7659 &ex_off,
7660 &exo,
7661 &a_f16,
7662 &a_s,
7663 n_ff_exp,
7664 n_embd,
7665 n_active,
7666 n_pairs,
7667 m.down_exps.qtype,
7668 m.down_exps.row_bytes,
7669 )?;
7670 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
7671 } else {
7672 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
7674 let gate = e.mmq_iq_experts(
7675 &dev.ptr_row,
7676 0,
7677 n_expert,
7678 &exi,
7679 &exo,
7680 &exp_d,
7681 &pt,
7682 &z_scr,
7683 n_embd,
7684 n_ff_exp,
7685 n_active,
7686 n_pairs,
7687 t,
7688 m.gate_exps.qtype,
7689 rbg_d,
7690 )?;
7691 let up = e.mmq_iq_experts(
7692 &dev.ptr_row,
7693 1,
7694 n_expert,
7695 &exi,
7696 &exo,
7697 &exp_d,
7698 &pt,
7699 &z_scr,
7700 n_embd,
7701 n_ff_exp,
7702 n_active,
7703 n_pairs,
7704 t,
7705 m.up_exps.qtype,
7706 rbu_d,
7707 )?;
7708 let a_scr = if crate::moe_fuse_actq_on() {
7714 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
7715 } else {
7716 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7717 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
7718 };
7719 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7720 let pself = e.htod_i32(&pair_self)?;
7721 e.mmq_iq_experts(
7722 &dev.ptr_row,
7723 2,
7724 n_expert,
7725 &exi,
7726 &exo,
7727 &exp_d,
7728 &pself,
7729 &a_scr,
7730 n_ff_exp,
7731 n_embd,
7732 n_active,
7733 n_pairs,
7734 n_pairs,
7735 m.down_exps.qtype,
7736 m.down_exps.row_bytes,
7737 )?
7738 };
7739 let mut moe_out = e.uninit(t * n_embd)?;
7740 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7741 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7742 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7743 {
7744 let n_ff_sh = gate_shexp.out_features();
7745 let sg_gate = e.matmul(gate_shexp, z, t)?;
7746 let sg_up = e.matmul(up_shexp, z, t)?;
7747 let mut sa = e.uninit(t * n_ff_sh)?;
7748 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7749 let sh = e.matmul(down_shexp, &sa, t)?;
7750 let g = match &m.gate_inp_shexp {
7756 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7757 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7758 }
7759 Some(gate_inp_shexp) => {
7760 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7761 let mut g = e.uninit(t)?;
7762 e.sigmoid(&gs, &mut g, t)?;
7763 g
7764 }
7765 None => e.htod(&vec![1.0f32; t])?,
7766 };
7767 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7768 }
7769 return Ok(moe_out);
7770 }
7771
7772 let dec = std::env::var("MEMRA_MOE_DEC")
7775 .map(|v| v != "0")
7776 .unwrap_or(true);
7777 let matvec = |proj,
7778 exi: &_,
7779 exo: &_,
7780 exp_d: &_,
7781 pt: &_,
7782 aq: &_,
7783 ad: &_,
7784 inf,
7785 outf,
7786 qtype,
7787 rb|
7788 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7789 let dec = dec && q8_expert_dec_supported(qtype);
7791 if dec {
7792 e.moe_pairs_matvec_q8_dec(
7793 &dev.ptr_row,
7794 proj,
7795 exi,
7796 exo,
7797 exp_d,
7798 pt,
7799 aq,
7800 ad,
7801 inf,
7802 outf,
7803 n_expert,
7804 n_active,
7805 n_pairs,
7806 qtype,
7807 rb,
7808 )
7809 } else {
7810 e.moe_pairs_matvec_q8_em(
7811 &dev.ptr_row,
7812 proj,
7813 exi,
7814 exo,
7815 exp_d,
7816 pt,
7817 aq,
7818 ad,
7819 inf,
7820 outf,
7821 n_expert,
7822 n_active,
7823 n_pairs,
7824 qtype,
7825 rb,
7826 )
7827 }
7828 };
7829 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7830 let gate = matvec(
7831 0,
7832 &exi,
7833 &exo,
7834 &exp_d,
7835 &pt,
7836 &zq,
7837 &zd,
7838 n_embd,
7839 n_ff_exp,
7840 m.gate_exps.qtype,
7841 rbg_d,
7842 )?;
7843 let up = matvec(
7844 1,
7845 &exi,
7846 &exo,
7847 &exp_d,
7848 &pt,
7849 &zq,
7850 &zd,
7851 n_embd,
7852 n_ff_exp,
7853 m.up_exps.qtype,
7854 rbu_d,
7855 )?;
7856 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7857 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7858 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7860 let pself = e.htod_i32(&pair_self)?;
7861 let y_down = matvec(
7862 2,
7863 &exi,
7864 &exo,
7865 &exp_d,
7866 &pself,
7867 &aq2,
7868 &ad2,
7869 n_ff_exp,
7870 n_embd,
7871 m.down_exps.qtype,
7872 m.down_exps.row_bytes,
7873 )?;
7874 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7876
7877 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7881 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7882 {
7883 let n_ff_sh = gate_shexp.out_features();
7884 let step_exact = true;
7888 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
7889 let (sg_gate, sg_up) = if step_exact && t == 1 {
7890 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, None)?
7891 } else if verify_t {
7892 let mut fused = None;
7893 if crate::spec::spec_fused_t()
7894 && (2..=4).contains(&t)
7895 && e.uses_q8_1_fast(gate_shexp)
7896 && e.uses_q8_1_fast(up_shexp)
7897 {
7898 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7899 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
7900 }
7901 match fused {
7902 Some(pair) => pair,
7903 None => (
7904 e.matmul_decode_exact(gate_shexp, z, t)?,
7905 e.matmul_decode_exact(up_shexp, z, t)?,
7906 ),
7907 }
7908 } else {
7909 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
7910 };
7911 let mut sa = e.uninit(t * n_ff_sh)?;
7912 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7913 let sh = if verify_t {
7914 e.matmul_decode_exact(down_shexp, &sa, t)?
7915 } else {
7916 e.matmul(down_shexp, &sa, t)?
7917 };
7918 let g = match &m.gate_inp_shexp {
7923 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7924 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7925 }
7926 Some(gate_inp_shexp) => {
7927 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7928 let mut g = e.uninit(t)?;
7929 e.sigmoid(&gs, &mut g, t)?;
7930 g
7931 }
7932 None => e.htod(&vec![1.0f32; t])?,
7933 };
7934 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7935 }
7936 Ok(moe_out)
7937 }
7938
7939 #[allow(clippy::too_many_arguments)]
7941 #[allow(clippy::too_many_arguments)]
7942 fn moe_ffn_dev(
7943 e: &Engine,
7944 m: &MoeWeights,
7945 z: &CudaSlice<f32>,
7946 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7947 logits: &CudaSlice<f32>,
7948 t: usize,
7949 cfg: &ModelConfig,
7950 il: u16,
7951 max_block: usize,
7952 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7953 let moe = cfg.moe.as_ref().unwrap();
7954 let n_embd = cfg.n_embd as usize;
7955 let n_expert = moe.expert_count as usize;
7956 let n_used = moe.expert_used_count as usize;
7957 let n_ff_exp = moe.expert_ff_length as usize;
7958 debug_assert!(
7962 cfg.sigmoid_router().is_none(),
7963 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
7964 );
7965 debug_assert!(
7966 !cfg.swiglu_clamped_at(il as u32),
7967 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
7968 );
7969
7970 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
7972 if m.has_macros {
7975 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
7976 }
7977
7978 let mut moe_out = e.uninit(t * n_embd)?;
7980
7981 if let Some(dev) = m.dev_exps.as_ref() {
7984 let (rbg_d, rbu_d) = if dev.gu_il {
7987 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7988 (sxx, sxx)
7989 } else {
7990 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7991 };
7992 let q8 = moe_q8_enabled()
7993 && q8_expert_supported(m.gate_exps.qtype)
7994 && q8_expert_supported(m.up_exps.qtype)
7995 && q8_expert_supported(m.down_exps.qtype);
7996 let rows_arm = q8
8005 && t > 1
8006 && crate::spec::spec_m2()
8007 && n_ff_exp == 512
8008 && n_used <= 8
8009 && std::env::var("MEMRA_MOE_DEVQ8_GU")
8010 .map(|v| v.is_empty() || v == "v")
8011 .unwrap_or(true)
8012 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
8013 .map(|v| v.is_empty() || v == "w8h2v")
8014 .unwrap_or(true);
8015 let csr_mode = std::env::var("MEMRA_MOE_CSR")
8024 .ok()
8025 .and_then(|v| v.parse::<i32>().ok())
8026 .unwrap_or(1);
8027 let csr_nvfp4_probe = std::env::var("MEMRA_MOE_CSR_NVFP4").as_deref() == Ok("1");
8044 let csr_qt = |qt: i32| {
8045 qt == crate::QT_IQ4_XS
8046 || qt == crate::QT_IQ3_S
8047 || (csr_nvfp4_probe && qt == crate::QT_NVFP4)
8048 };
8049 let csr_t_max = if csr_nvfp4_probe { MOE_DEV_MAX_T } else { 10 };
8050 let csr_uniform = m.gate_exps.qtype == m.up_exps.qtype;
8051 let csr_arm = rows_arm
8052 && csr_mode > 0
8053 && t <= csr_t_max
8054 && csr_uniform
8055 && csr_qt(m.gate_exps.qtype)
8056 && csr_qt(m.up_exps.qtype)
8057 && csr_qt(m.down_exps.qtype);
8058 if csr_arm {
8059 if csr_mode == 2 {
8060 static ENGAGED: std::sync::Once = std::sync::Once::new();
8061 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
8062 }
8063 let n_pairs = t * n_used;
8064 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8065 let act = e.moe_gate_up_silu8_dev_q8_csr(
8066 &dev.ptr_row,
8067 &sel_d,
8068 &zq,
8069 &zd,
8070 n_pairs,
8071 n_embd,
8072 n_ff_exp,
8073 n_used,
8074 n_expert,
8075 m.gate_exps.qtype,
8076 m.up_exps.qtype,
8077 rbg_d,
8078 rbu_d,
8079 )?;
8080 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8081 e.moe_down8_fma_dev_q8_rows(
8085 &dev.ptr_row,
8086 &sel_d,
8087 &w_d,
8088 &aq2,
8089 &ad2,
8090 &mut moe_out,
8091 t,
8092 n_ff_exp,
8093 n_embd,
8094 n_used,
8095 n_expert,
8096 m.down_exps.qtype,
8097 m.down_exps.row_bytes,
8098 )?;
8099 if csr_mode == 2 {
8100 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
8102 &dev.ptr_row,
8103 &sel_d,
8104 &zq,
8105 &zd,
8106 t,
8107 n_embd,
8108 n_ff_exp,
8109 n_used,
8110 n_expert,
8111 m.gate_exps.qtype,
8112 m.up_exps.qtype,
8113 rbg_d,
8114 rbu_d,
8115 &m.dev_macros,
8116 )?;
8117 let mut out_r = e.uninit(t * n_embd)?;
8118 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
8119 e.moe_down8_fma_dev_q8_rows(
8120 &dev.ptr_row,
8121 &sel_d,
8122 &w_d,
8123 &aq2r,
8124 &ad2r,
8125 &mut out_r,
8126 t,
8127 n_ff_exp,
8128 n_embd,
8129 n_used,
8130 n_expert,
8131 m.down_exps.qtype,
8132 m.down_exps.row_bytes,
8133 )?;
8134 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
8135 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
8136 let ba = a1
8137 .iter()
8138 .zip(&a2)
8139 .filter(|(x, y)| x.to_bits() != y.to_bits())
8140 .count();
8141 let bo = o1
8142 .iter()
8143 .zip(&o2)
8144 .filter(|(x, y)| x.to_bits() != y.to_bits())
8145 .count();
8146 if ba + bo > 0 {
8147 eprintln!(
8148 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
8149 a1.len(),
8150 o1.len()
8151 );
8152 let sel_h = e.dtoh_i32(&sel_d)?;
8154 let mut shown = 0;
8155 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
8156 if x.to_bits() != y.to_bits() && shown < 4 {
8157 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
8158 let ex = sel_h[p];
8159 let npx = sel_h.iter().filter(|&&v| v == ex).count();
8160 eprintln!(
8161 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
8162 );
8163 shown += 1;
8164 }
8165 }
8166 std::process::exit(3);
8167 }
8168 }
8169 } else if rows_arm {
8170 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
8173 use std::sync::atomic::{AtomicU64, Ordering};
8174 static PAIRS: AtomicU64 = AtomicU64::new(0);
8175 static UNIQ: AtomicU64 = AtomicU64::new(0);
8176 static CALLS: AtomicU64 = AtomicU64::new(0);
8177 let sel_h = e.dtoh_i32(&sel_d)?;
8178 let mut u: Vec<i32> = sel_h.clone();
8179 u.sort_unstable();
8180 u.dedup();
8181 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
8182 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
8183 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
8184 if c % 480 == 0 {
8185 let p = PAIRS.load(Ordering::Relaxed);
8186 let q = UNIQ.load(Ordering::Relaxed);
8187 eprintln!(
8188 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
8189 q as f64 / p as f64
8190 );
8191 }
8192 }
8193 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8194 let act = e.moe_gate_up_silu8_dev_q8_rows(
8195 &dev.ptr_row,
8196 &sel_d,
8197 &zq,
8198 &zd,
8199 t,
8200 n_embd,
8201 n_ff_exp,
8202 n_used,
8203 n_expert,
8204 m.gate_exps.qtype,
8205 m.up_exps.qtype,
8206 rbg_d,
8207 rbu_d,
8208 &m.dev_macros,
8209 )?;
8210 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8211 e.moe_down8_fma_dev_q8_rows(
8212 &dev.ptr_row,
8213 &sel_d,
8214 &w_d,
8215 &aq2,
8216 &ad2,
8217 &mut moe_out,
8218 t,
8219 n_ff_exp,
8220 n_embd,
8221 n_used,
8222 n_expert,
8223 m.down_exps.qtype,
8224 m.down_exps.row_bytes,
8225 )?;
8226 } else {
8227 for tok in 0..t {
8228 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8229 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8230 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8231 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8232 if q8 {
8233 let (zq, zd) = match (t, zq8) {
8234 (1, Some((q, d))) => (q.clone(), d.clone()),
8235 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
8236 };
8237 let act = e.moe_gate_up_silu8_dev_q8(
8238 &dev.ptr_row,
8239 &selt,
8240 &zq,
8241 &zd,
8242 n_embd,
8243 n_ff_exp,
8244 n_used,
8245 n_expert,
8246 m.gate_exps.qtype,
8247 m.up_exps.qtype,
8248 rbg_d,
8249 rbu_d,
8250 &m.dev_macros,
8251 )?;
8252 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8253 e.moe_down8_fma_dev_q8(
8254 &dev.ptr_row,
8255 &selt,
8256 &wt,
8257 &aq2,
8258 &ad2,
8259 &mut dst,
8260 n_ff_exp,
8261 n_embd,
8262 n_used,
8263 n_expert,
8264 m.down_exps.qtype,
8265 m.down_exps.row_bytes,
8266 )?;
8267 } else {
8268 let act = e.moe_gate_up_silu8_dev(
8269 &dev.ptr_row,
8270 &selt,
8271 &zt,
8272 n_embd,
8273 n_ff_exp,
8274 n_used,
8275 n_expert,
8276 m.gate_exps.qtype,
8277 m.up_exps.qtype,
8278 rbg_d,
8279 rbu_d,
8280 &m.dev_macros,
8281 )?;
8282 e.moe_down8_fma_dev(
8283 &dev.ptr_row,
8284 &selt,
8285 &wt,
8286 &act,
8287 &mut dst,
8288 n_ff_exp,
8289 n_embd,
8290 n_used,
8291 n_expert,
8292 m.down_exps.qtype,
8293 m.down_exps.row_bytes,
8294 )?;
8295 }
8296 }
8297 }
8298 } else {
8299 let q8 = moe_q8_enabled()
8306 && q8_expert_supported(m.gate_exps.qtype)
8307 && q8_expert_supported(m.up_exps.qtype)
8308 && q8_expert_supported(m.down_exps.qtype);
8309 e.with_moe_cache(max_block, |c, eng| {
8310 let row = c
8311 .layer_dev_row(il, n_expert, eng)?
8312 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
8313 for tok in 0..t {
8314 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8315 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8316 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8317 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8318 if q8 {
8319 let (zq, zd) = match (t, zq8) {
8320 (1, Some((q, d))) => (q.clone(), d.clone()),
8321 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
8322 };
8323 let act = eng.moe_gate_up_silu8_dev_q8(
8324 row,
8325 &selt,
8326 &zq,
8327 &zd,
8328 n_embd,
8329 n_ff_exp,
8330 n_used,
8331 n_expert,
8332 m.gate_exps.qtype,
8333 m.up_exps.qtype,
8334 m.gate_exps.row_bytes,
8335 m.up_exps.row_bytes,
8336 &m.dev_macros,
8337 )?;
8338 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
8339 eng.moe_down8_fma_dev_q8(
8340 row,
8341 &selt,
8342 &wt,
8343 &aq2,
8344 &ad2,
8345 &mut dst,
8346 n_ff_exp,
8347 n_embd,
8348 n_used,
8349 n_expert,
8350 m.down_exps.qtype,
8351 m.down_exps.row_bytes,
8352 )?;
8353 } else {
8354 let act = eng.moe_gate_up_silu8_dev(
8355 row,
8356 &selt,
8357 &zt,
8358 n_embd,
8359 n_ff_exp,
8360 n_used,
8361 n_expert,
8362 m.gate_exps.qtype,
8363 m.up_exps.qtype,
8364 m.gate_exps.row_bytes,
8365 m.up_exps.row_bytes,
8366 &m.dev_macros,
8367 )?;
8368 eng.moe_down8_fma_dev(
8369 row,
8370 &selt,
8371 &wt,
8372 &act,
8373 &mut dst,
8374 n_ff_exp,
8375 n_embd,
8376 n_used,
8377 n_expert,
8378 m.down_exps.qtype,
8379 m.down_exps.row_bytes,
8380 )?;
8381 }
8382 }
8383 c.hits += (t * 3 * n_used) as u64;
8385 Ok(())
8386 })?;
8387 }
8388
8389 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8394 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8395 {
8396 let n_ff_sh = gate_shexp.out_features();
8397 let verify_t = t > 1 && t < PRIME_MIN_T;
8400 let (sg_gate, sg_up) = if t == 1 {
8401 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
8402 } else if verify_t {
8403 let mut fused = None;
8407 if crate::spec::spec_fused_t()
8408 && (2..=4).contains(&t)
8409 && e.uses_q8_1_fast(gate_shexp)
8410 && e.uses_q8_1_fast(up_shexp)
8411 {
8412 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8413 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8414 }
8415 match fused {
8416 Some(pair) => pair,
8417 None => (
8418 e.matmul_decode_exact(gate_shexp, z, t)?,
8419 e.matmul_decode_exact(up_shexp, z, t)?,
8420 ),
8421 }
8422 } else {
8423 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8424 };
8425 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8427 let sh = if verify_t {
8428 e.matmul_decode_exact(down_shexp, &sa, t)?
8429 } else {
8430 e.matmul(down_shexp, &sa, t)?
8431 };
8432 let g = match &m.gate_inp_shexp {
8436 Some(gate_inp_shexp) => {
8437 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
8440 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8441 } else {
8442 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8443 let mut g = e.uninit(t)?;
8444 e.sigmoid(&gs, &mut g, t)?;
8445 g
8446 }
8447 }
8448 None => e.htod(&vec![1.0f32; t])?,
8449 };
8450 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8451 }
8452
8453 Ok(moe_out)
8454 }
8455
8456 #[allow(clippy::too_many_arguments)]
8466 #[allow(clippy::too_many_arguments)]
8469 fn moe_gdec_token_q8(
8470 e: &Engine,
8471 m: &MoeWeights,
8472 il: u16,
8473 max_block: usize,
8474 zq: &CudaSlice<i8>,
8475 zd: &CudaSlice<f32>,
8476 sel: &[u32],
8477 w: &[f32],
8478 moe_out: &mut CudaSlice<f32>,
8479 tok: usize,
8480 n_embd: usize,
8481 n_ff_exp: usize,
8482 n_used: usize,
8483 ) -> Result<bool, Box<dyn std::error::Error>> {
8484 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8485 use cudarc::driver::DevicePtr;
8486 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8487 let mut g = [0u64; 8];
8488 let mut u = [0u64; 8];
8489 let mut d = [0u64; 8];
8490 for (j, &ex) in sel.iter().enumerate() {
8491 let ex = ex as u16;
8492 let (Some(sg), Some(su), Some(sd)) = (
8493 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8494 c.resident(BlockId::new(il, PROJ_UP, ex)),
8495 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8496 ) else {
8497 return Ok(None);
8498 };
8499 let __s = eng.stream();
8500 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8501 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8502 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8503 g[j] = pg as u64;
8504 u[j] = pu as u64;
8505 d[j] = pd as u64;
8506 }
8507 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8508 for &ex in sel {
8509 let ex = ex as u16;
8510 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8511 c.note_profile_hit(BlockId::new(il, proj, ex));
8512 }
8513 }
8514 }
8515 c.hits += (3 * n_used) as u64;
8516 Ok(Some((g, u, d)))
8517 })?;
8518 let Some((g, u, d)) = ptrs else {
8519 return Ok(false);
8520 };
8521 let mut wv = [0f32; 8];
8522 wv[..n_used].copy_from_slice(w);
8523 let act = e.moe_gate_up_silu8_q8(
8524 crate::WPtr8(g),
8525 crate::WPtr8(u),
8526 zq,
8527 zd,
8528 n_embd,
8529 n_ff_exp,
8530 n_used,
8531 m.gate_exps.qtype,
8532 m.up_exps.qtype,
8533 m.gate_exps.row_bytes,
8534 m.up_exps.row_bytes,
8535 )?;
8536 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8538 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8539 e.moe_down8_fma_q8(
8540 crate::WPtr8(d),
8541 crate::F32x8(wv),
8542 &aq2,
8543 &ad2,
8544 &mut dst,
8545 n_ff_exp,
8546 n_embd,
8547 n_used,
8548 m.down_exps.qtype,
8549 m.down_exps.row_bytes,
8550 )?;
8551 Ok(true)
8552 }
8553
8554 fn moe_gdec_token(
8555 e: &Engine,
8556 m: &MoeWeights,
8557 il: u16,
8558 max_block: usize,
8559 zt: &cudarc::driver::CudaView<f32>,
8560 sel: &[u32],
8561 w: &[f32],
8562 moe_out: &mut CudaSlice<f32>,
8563 tok: usize,
8564 n_embd: usize,
8565 n_ff_exp: usize,
8566 n_used: usize,
8567 ) -> Result<bool, Box<dyn std::error::Error>> {
8568 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8569 use cudarc::driver::DevicePtr;
8570 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8572 let mut g = [0u64; 8];
8573 let mut u = [0u64; 8];
8574 let mut d = [0u64; 8];
8575 for (j, &ex) in sel.iter().enumerate() {
8576 let ex = ex as u16;
8577 let (Some(sg), Some(su), Some(sd)) = (
8578 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8579 c.resident(BlockId::new(il, PROJ_UP, ex)),
8580 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8581 ) else {
8582 return Ok(None);
8583 };
8584 let __s = eng.stream();
8585 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8586 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8587 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8588 g[j] = pg as u64;
8589 u[j] = pu as u64;
8590 d[j] = pd as u64;
8591 }
8592 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8593 for &ex in sel {
8594 let ex = ex as u16;
8595 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8596 c.note_profile_hit(BlockId::new(il, proj, ex));
8597 }
8598 }
8599 }
8600 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
8602 })?;
8603 let Some((g, u, d)) = ptrs else {
8604 return Ok(false);
8605 };
8606 let mut wv = [0f32; 8];
8607 wv[..n_used].copy_from_slice(w);
8608 let act = e.moe_gate_up_silu8(
8610 crate::WPtr8(g),
8611 crate::WPtr8(u),
8612 zt,
8613 n_embd,
8614 n_ff_exp,
8615 n_used,
8616 m.gate_exps.qtype,
8617 m.up_exps.qtype,
8618 m.gate_exps.row_bytes,
8619 m.up_exps.row_bytes,
8620 )?;
8621 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8622 e.moe_down8_fma_into(
8623 crate::WPtr8(d),
8624 crate::F32x8(wv),
8625 &act,
8626 &mut dst,
8627 n_ff_exp,
8628 n_embd,
8629 n_used,
8630 m.down_exps.qtype,
8631 m.down_exps.row_bytes,
8632 )?;
8633 Ok(true)
8634 }
8635
8636 fn moe_cached_gemm_q8(
8641 e: &Engine,
8642 il: u16,
8643 proj: u8,
8644 ex: usize,
8645 m: &MoeWeights,
8646 max_block: usize,
8647 aq: &CudaSlice<i8>,
8648 ad: &CudaSlice<f32>,
8649 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8650 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8651 let exps = match proj {
8652 PROJ_GATE => &m.gate_exps,
8653 PROJ_UP => &m.up_exps,
8654 _ => &m.down_exps,
8655 };
8656 let layout = exps.expert_layout(ex);
8657 let id = BlockId::new(il, proj, ex as u16);
8658 let source = exps.expert_source(ex);
8659 e.with_moe_cache(max_block, |c, eng| {
8660 let slot = c.dispatch_source(id, source, eng)?;
8661 let DispatchSlot::Resident(sl) = slot;
8662 let buf = c.slot(sl);
8663 eng.qmatvec_expert_q8(
8664 buf,
8665 0..layout.len,
8666 aq,
8667 ad,
8668 1,
8669 exps.in_f,
8670 exps.out_f,
8671 layout.qtype,
8672 layout.row_bytes,
8673 )
8674 })
8675 }
8676
8677 fn moe_cached_gemm(
8678 e: &Engine,
8679 il: u16,
8680 proj: u8,
8681 ex: usize,
8682 m: &MoeWeights,
8683 max_block: usize,
8684 x: &cudarc::driver::CudaView<f32>,
8685 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8686 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8687 let exps = match proj {
8688 PROJ_GATE => &m.gate_exps,
8689 PROJ_UP => &m.up_exps,
8690 _ => &m.down_exps,
8691 };
8692 let layout = exps.expert_layout(ex);
8693 let id = BlockId::new(il, proj, ex as u16);
8694 let source = exps.expert_source(ex);
8695 e.with_moe_cache(max_block, |c, eng| {
8697 let slot = c.dispatch_source(id, source, eng)?;
8698 let DispatchSlot::Resident(sl) = slot;
8701 let buf = c.slot(sl);
8702 eng.qmatvec_view(
8703 buf,
8704 0..layout.len,
8705 x,
8706 1,
8707 exps.in_f,
8708 exps.out_f,
8709 layout.qtype,
8710 layout.row_bytes,
8711 )
8712 })
8713 }
8714
8715 fn moe_profile_admit_expert(
8719 e: &Engine,
8720 il: u16,
8721 ex: usize,
8722 m: &MoeWeights,
8723 max_block: usize,
8724 ) -> Result<(), Box<dyn std::error::Error>> {
8725 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8726 e.with_moe_cache(max_block, |cache, eng| {
8727 for (proj, exps) in [
8728 (PROJ_GATE, &m.gate_exps),
8729 (PROJ_UP, &m.up_exps),
8730 (PROJ_DOWN, &m.down_exps),
8731 ] {
8732 let id = BlockId::new(il, proj, ex as u16);
8733 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
8734 }
8735 Ok(())
8736 })
8737 }
8738
8739 #[allow(clippy::too_many_arguments)]
8742 fn moe_frozen_gemm(
8743 e: &Engine,
8744 il: u16,
8745 proj: u8,
8746 ex: usize,
8747 m: &MoeWeights,
8748 max_block: usize,
8749 x: &cudarc::driver::CudaView<f32>,
8750 scratch: &mut Option<CudaSlice<u8>>,
8751 scratch_len: usize,
8752 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8753 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
8754 let exps = match proj {
8755 PROJ_GATE => &m.gate_exps,
8756 PROJ_UP => &m.up_exps,
8757 _ => &m.down_exps,
8758 };
8759 let layout = exps.expert_layout(ex);
8760 let id = BlockId::new(il, proj, ex as u16);
8761 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
8762 let Some(slot) = cache.resident(id) else {
8763 return Ok(None);
8764 };
8765 let buf = cache.slot(slot);
8766 Ok(Some(eng.qmatvec_view(
8767 buf,
8768 0..layout.len,
8769 x,
8770 1,
8771 exps.in_f,
8772 exps.out_f,
8773 layout.qtype,
8774 layout.row_bytes,
8775 )?))
8776 })? {
8777 return Ok(output);
8778 }
8779 if scratch.is_none() {
8780 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
8781 }
8782 let scratch = scratch.as_mut().unwrap();
8783 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
8784 e.qmatvec_view(
8785 scratch,
8786 0..layout.len,
8787 x,
8788 1,
8789 exps.in_f,
8790 exps.out_f,
8791 layout.qtype,
8792 layout.row_bytes,
8793 )
8794 }
8795
8796 fn moe_prefetch_expert(
8797 e: &Engine,
8798 il: u16,
8799 ex: usize,
8800 m: &MoeWeights,
8801 max_block: usize,
8802 keep: &[crate::moe_cache::BlockId],
8803 ) -> Result<(), Box<dyn std::error::Error>> {
8804 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8805 e.with_moe_cache(max_block, |c, eng| {
8806 for (proj, exps) in [
8807 (PROJ_GATE, &m.gate_exps),
8808 (PROJ_UP, &m.up_exps),
8809 (PROJ_DOWN, &m.down_exps),
8810 ] {
8811 let id = BlockId::new(il, proj, ex as u16);
8812 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
8813 }
8814 Ok(())
8815 })
8816 }
8817
8818 fn moe_prefetch_disk_expert(
8821 e: &Engine,
8822 il: u16,
8823 ex: usize,
8824 m: &MoeWeights,
8825 max_block: usize,
8826 keep: &[crate::moe_cache::BlockId],
8827 ) -> Result<(), Box<dyn std::error::Error>> {
8828 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8829 e.with_moe_cache(max_block, |c, eng| {
8830 for (proj, exps) in [
8831 (PROJ_GATE, &m.gate_exps),
8832 (PROJ_UP, &m.up_exps),
8833 (PROJ_DOWN, &m.down_exps),
8834 ] {
8835 let source = exps.expert_source(ex);
8836 if let crate::model::ExpertSource::Disk { .. } = &source {
8837 let id = BlockId::new(il, proj, ex as u16);
8838 let _ = c.prefetch_source(id, source, keep, eng)?;
8839 }
8840 }
8841 Ok(())
8842 })
8843 }
8844
8845 #[inline]
8846 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
8847 let _ = m.gate_exps.prefetch_expert_pages(ex);
8848 let _ = m.up_exps.prefetch_expert_pages(ex);
8849 let _ = m.down_exps.prefetch_expert_pages(ex);
8850 }
8851}
8852
8853impl HybridModel {
8870 #[allow(clippy::too_many_arguments)]
8874 fn moe_ffn_grouped_resident_q8(
8875 e: &Engine,
8876 m: &MoeWeights,
8877 z: &CudaSlice<f32>,
8878 t: usize,
8879 cfg: &ModelConfig,
8880 il: u16,
8881 sel_all: &[u32],
8882 w_all: &[f32],
8883 table: &CudaSlice<u64>,
8884 gu_il: bool,
8885 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8886 let moe = cfg.moe.as_ref().unwrap();
8887 let n_embd = cfg.n_embd as usize;
8888 let n_expert = moe.expert_count as usize;
8889 let n_used = moe.expert_used_count as usize;
8890 let n_ff_exp = moe.expert_ff_length as usize;
8891 let n_pairs = t * n_used;
8892 debug_assert_eq!(sel_all.len(), n_pairs);
8893 debug_assert_eq!(w_all.len(), n_pairs);
8894 debug_assert!(
8895 m.gate_exps.macros.is_none()
8896 && m.up_exps.macros.is_none()
8897 && m.down_exps.macros.is_none(),
8898 "resident grouped q8 does not fold per-expert macro scales",
8899 );
8900
8901 if !cfg.swiglu_clamped_at(il as u32) {
8907 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8908 let sel_d = e.htod_i32(&sel)?;
8909 let w_d = e.htod(w_all)?;
8910 let (gate_row_bytes, up_row_bytes) = if gu_il {
8911 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8912 (combined, combined)
8913 } else {
8914 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8915 };
8916 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8917 let act = e.moe_gate_up_silu8_dev_q8_rows(
8918 table,
8919 &sel_d,
8920 &zq,
8921 &zd,
8922 t,
8923 n_embd,
8924 n_ff_exp,
8925 n_used,
8926 n_expert,
8927 m.gate_exps.qtype,
8928 m.up_exps.qtype,
8929 gate_row_bytes,
8930 up_row_bytes,
8931 &m.dev_macros,
8932 )?;
8933 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8934 let mut moe_out = e.uninit(t * n_embd)?;
8935 e.moe_down8_fma_dev_q8_rows_g(
8936 table,
8937 &sel_d,
8938 &w_d,
8939 &aq2,
8940 &ad2,
8941 &mut moe_out,
8942 t,
8943 n_ff_exp,
8944 n_embd,
8945 n_used,
8946 n_expert,
8947 m.down_exps.qtype,
8948 m.down_exps.row_bytes,
8949 )?;
8950
8951 if std::env::var("MEMRA_MOE_STATS").is_ok() {
8952 let mut counts = vec![0usize; n_expert];
8953 for &expert in sel_all {
8954 counts[expert as usize] += 1;
8955 }
8956 let mut sizes: Vec<usize> =
8957 counts.into_iter().filter(|&count| count != 0).collect();
8958 sizes.sort_unstable();
8959 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
8960 println!(
8961 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
8962 m_e: min={} median={} mean={mean:.1} max={}",
8963 sizes.len(),
8964 n_expert,
8965 sizes.first().copied().unwrap_or(0),
8966 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
8967 sizes.last().copied().unwrap_or(0),
8968 );
8969 }
8970 return Ok(moe_out);
8971 }
8972
8973 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
8977 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8978 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
8979 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
8980
8981 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
8982 for (pair, &expert) in pair_ex.iter().enumerate() {
8983 by_expert[expert as usize].push(pair as i32);
8984 }
8985
8986 let pair_tok_d = e.htod_i32(&pair_tok)?;
8987 let pair_ex_d = e.htod_i32(&pair_ex)?;
8988 let pair_w_d = e.htod(w_all)?;
8989 let tok_off_d = e.htod_i32(&tok_off)?;
8990 let tok_ids_d = e.htod_i32(&tok_ids)?;
8991
8992 let matvec = |proj: i32,
8993 pair_rows: &CudaSlice<i32>,
8994 aq: &CudaSlice<i8>,
8995 ad: &CudaSlice<f32>,
8996 in_f: usize,
8997 out_f: usize,
8998 qtype: i32,
8999 row_bytes: usize|
9000 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9001 e.moe_pairs_matvec_q8(
9002 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
9003 row_bytes,
9004 )
9005 };
9006
9007 let (gate_row_bytes, up_row_bytes) = if gu_il {
9008 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
9009 (combined, combined)
9010 } else {
9011 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
9012 };
9013 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
9014 let gate = matvec(
9015 0,
9016 &pair_tok_d,
9017 &zq,
9018 &zd,
9019 n_embd,
9020 n_ff_exp,
9021 m.gate_exps.qtype,
9022 gate_row_bytes,
9023 )?;
9024 let up = matvec(
9025 1,
9026 &pair_tok_d,
9027 &zq,
9028 &zd,
9029 n_embd,
9030 n_ff_exp,
9031 m.up_exps.qtype,
9032 up_row_bytes,
9033 )?;
9034 let mut act = e.uninit(n_pairs * n_ff_exp)?;
9035 Self::ffn_act_lim(
9036 e,
9037 cfg,
9038 &gate,
9039 &up,
9040 1.0,
9041 1.0,
9042 cfg.clamp_exp_at(il as u32),
9043 &mut act,
9044 n_pairs * n_ff_exp,
9045 )?;
9046 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
9047 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
9048 let pair_self_d = e.htod_i32(&pair_self)?;
9049 let down = matvec(
9050 2,
9051 &pair_self_d,
9052 &aq2,
9053 &ad2,
9054 n_ff_exp,
9055 n_embd,
9056 m.down_exps.qtype,
9057 m.down_exps.row_bytes,
9058 )?;
9059 let mut moe_out = e.uninit(t * n_embd)?;
9060 e.moe_pairs_scatter(
9061 &down,
9062 &pair_w_d,
9063 &tok_off_d,
9064 &tok_ids_d,
9065 &mut moe_out,
9066 t,
9067 n_embd,
9068 )?;
9069
9070 if std::env::var("MEMRA_MOE_STATS").is_ok() {
9071 let mut sizes: Vec<usize> = by_expert
9072 .iter()
9073 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
9074 .collect();
9075 sizes.sort_unstable();
9076 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
9077 println!(
9078 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
9079 m_e: min={} median={} mean={mean:.1} max={}",
9080 sizes.len(),
9081 n_expert,
9082 sizes.first().copied().unwrap_or(0),
9083 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
9084 sizes.last().copied().unwrap_or(0),
9085 );
9086 }
9087 Ok(moe_out)
9088 }
9089
9090 #[allow(clippy::too_many_arguments)]
9096 fn shexp_split_matvec(
9097 e: &Engine,
9098 rank1: &Engine,
9099 wg: &CudaSlice<u8>,
9100 wu: &CudaSlice<u8>,
9101 wd: &CudaSlice<u8>,
9102 z: &CudaSlice<f32>,
9103 lim: Option<f32>,
9104 cfg: &ModelConfig,
9105 il: u16,
9106 n_embd: usize,
9107 n_ff_sh: usize,
9108 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
9109 use cudarc::driver::DevicePtr;
9110 if n_ff_sh % 2 != 0 || n_embd % 2 != 0 {
9111 return Ok(None);
9112 }
9113 let hf = n_ff_sh / 2;
9114 let nd = n_embd / 2;
9115 struct Rep {
9116 wg1: CudaSlice<u8>,
9117 wu1: CudaSlice<u8>,
9118 wd1: CudaSlice<u8>,
9119 }
9120 struct SplitWs {
9121 pin_dev: usize,
9122 gate0: CudaSlice<f32>,
9124 up0: CudaSlice<f32>,
9125 act: CudaSlice<f32>,
9126 sh_buf: CudaSlice<f32>,
9127 ev_z: cudarc::driver::CudaEvent,
9128 ev_act0: cudarc::driver::CudaEvent,
9129 z1: CudaSlice<f32>,
9131 g1: CudaSlice<f32>,
9132 u1: CudaSlice<f32>,
9133 a1h: CudaSlice<f32>,
9134 act1: CudaSlice<f32>,
9135 y1: CudaSlice<f32>,
9136 ev_act1: cudarc::driver::CudaEvent,
9137 ev_y1: cudarc::driver::CudaEvent,
9138 raw_act_e: u64,
9139 raw_sh_e: u64,
9140 raw_z1: u64,
9141 raw_a1h: u64,
9142 raw_act1: u64,
9143 raw_y1: u64,
9144 }
9145 static WS: std::sync::Mutex<Option<SplitWs>> = std::sync::Mutex::new(None);
9146 static REPS: std::sync::Mutex<Option<std::collections::HashMap<u64, Rep>>> =
9147 std::sync::Mutex::new(None);
9148 let mut guard = WS.lock().map_err(|_| "shexp split lock is poisoned")?;
9149 let mut reps_guard = REPS.lock().map_err(|_| "shexp reps lock is poisoned")?;
9150 let reps = reps_guard.get_or_insert_with(std::collections::HashMap::new);
9151 let pins = e.ctx().ordinal();
9152 if guard.as_ref().is_none_or(|w| w.pin_dev != pins) {
9153 let (gate0, up0, act, sh_buf, ev_z, ev_act0) = {
9154 let _m = e.gpu.enter_main()?;
9155 (
9156 e.htod(&vec![0.0f32; hf])?,
9157 e.htod(&vec![0.0f32; hf])?,
9158 e.htod(&vec![0.0f32; n_ff_sh])?,
9159 e.htod(&vec![0.0f32; n_embd])?,
9160 e.ctx().new_event(None)?,
9161 e.ctx().new_event(None)?,
9162 )
9163 };
9164 let (z1, g1, u1, a1h, act1, y1, ev_act1, ev_y1) = {
9165 let _r = rank1.gpu.enter_main()?;
9166 (
9167 rank1.htod(&vec![0.0f32; n_embd])?,
9168 rank1.htod(&vec![0.0f32; hf])?,
9169 rank1.htod(&vec![0.0f32; hf])?,
9170 rank1.htod(&vec![0.0f32; hf])?,
9171 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9172 rank1.htod(&vec![0.0f32; nd])?,
9173 rank1.ctx().new_event(None)?,
9174 rank1.ctx().new_event(None)?,
9175 )
9176 };
9177 let (raw_act_e, raw_sh_e) = {
9178 let _m = e.gpu.enter_main()?;
9179 let stream = e.stream();
9180 let (a, _g0) = act.device_ptr(&stream);
9181 let (b, _g1) = sh_buf.device_ptr(&stream);
9182 (a as u64, b as u64)
9183 };
9184 let (raw_z1, raw_a1h, raw_act1, raw_y1) = {
9185 let _r = rank1.gpu.enter_main()?;
9186 let rs = rank1.stream();
9187 let (a, _g0) = z1.device_ptr(&rs);
9188 let (b, _g1) = a1h.device_ptr(&rs);
9189 let (c, _g2) = act1.device_ptr(&rs);
9190 let (d, _g3) = y1.device_ptr(&rs);
9191 (a as u64, b as u64, c as u64, d as u64)
9192 };
9193 *guard = Some(SplitWs {
9194 pin_dev: pins,
9195 gate0,
9196 up0,
9197 act,
9198 sh_buf,
9199 ev_z,
9200 ev_act0,
9201 z1,
9202 g1,
9203 u1,
9204 a1h,
9205 act1,
9206 y1,
9207 ev_act1,
9208 ev_y1,
9209 raw_act_e,
9210 raw_sh_e,
9211 raw_z1,
9212 raw_a1h,
9213 raw_act1,
9214 raw_y1,
9215 });
9216 }
9217 let ws = guard.as_mut().expect("armed above");
9218 let wg_pin = {
9219 let _m = e.gpu.enter_main()?;
9220 let stream = e.stream();
9221 let (p, _g) = wg.device_ptr(&stream);
9222 p as u64
9223 };
9224 if !reps.contains_key(&wg_pin) {
9225 let mut up = |src: &CudaSlice<u8>,
9227 off_bytes: usize,
9228 len: usize|
9229 -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9230 use cudarc::driver::sys;
9231 let sptr = {
9232 let _m = e.gpu.enter_main()?;
9233 let stream = e.stream();
9234 let (p, _g) = src.device_ptr(&stream);
9235 p as u64 + off_bytes as u64
9236 };
9237 let dst = {
9238 let _r = rank1.gpu.enter_main()?;
9239 rank1.alloc_u8_uninit(len)?
9240 };
9241 let dptr = {
9242 let _r = rank1.gpu.enter_main()?;
9243 let rs = rank1.stream();
9244 let (p, _g) = dst.device_ptr(&rs);
9245 p as u64
9246 };
9247 let _r = rank1.gpu.enter_main()?;
9248 let r = unsafe {
9249 sys::cuMemcpyAsync(
9250 dptr as sys::CUdeviceptr,
9251 sptr as sys::CUdeviceptr,
9252 len,
9253 rank1.stream().cu_stream() as sys::CUstream,
9254 )
9255 };
9256 if r != sys::CUresult::CUDA_SUCCESS {
9257 return Err(format!("shexp split replica upload: {r:?}").into());
9258 }
9259 rank1.stream().synchronize()?;
9260 Ok(dst)
9261 };
9262 let wg1 = up(wg, hf * n_embd * 2, hf * n_embd * 2)?;
9263 let wu1 = up(wu, hf * n_embd * 2, hf * n_embd * 2)?;
9264 let wd1 = up(wd, nd * n_ff_sh * 2, nd * n_ff_sh * 2)?;
9265 reps.insert(wg_pin, Rep { wg1, wu1, wd1 });
9266 }
9267 let _ = il;
9268 let raw_z = {
9270 let _m = e.gpu.enter_main()?;
9271 let stream = e.stream();
9272 let (p, _g) = z.device_ptr(&stream);
9273 ws.ev_z.record(&stream)?;
9274 p as u64
9275 };
9276 {
9278 let rep = reps.get(&wg_pin).expect("uploaded above");
9279 let _r = rank1.gpu.enter_main()?;
9280 rank1.stream().wait(&ws.ev_z)?;
9281 crate::tp::raw_copy_bytes(ws.raw_z1, raw_z, n_embd * 4, rank1)?;
9282 let SplitWs {
9283 z1, g1, u1, a1h, ..
9284 } = &mut *ws;
9285 rank1.matvec_bf16_dual_into(&rep.wg1, &rep.wu1, z1, g1, u1, n_embd, hf)?;
9286 Self::ffn_act_lim(rank1, cfg, g1, u1, 1.0, 1.0, lim, a1h, hf)?;
9287 crate::tp::raw_copy_bytes(ws.raw_act1 + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9289 crate::tp::raw_copy_bytes(ws.raw_act_e + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9290 ws.ev_act1.record(&rank1.stream())?;
9291 }
9292 {
9294 let _m = e.gpu.enter_main()?;
9295 let SplitWs {
9296 gate0, up0, act, ..
9297 } = &mut *ws;
9298 let wg_lo = wg.slice(0..hf * n_embd * 2);
9299 let wu_lo = wu.slice(0..hf * n_embd * 2);
9300 e.matvec_bf16_dual_view_into(&wg_lo, &wu_lo, z, gate0, up0, n_embd, hf)?;
9301 Self::ffn_act_lim(e, cfg, gate0, up0, 1.0, 1.0, lim, act, hf)?;
9302 ws.ev_act0.record(&e.stream())?;
9303 }
9304 {
9306 let rep = reps.get(&wg_pin).expect("uploaded above");
9307 let _r = rank1.gpu.enter_main()?;
9308 rank1.stream().wait(&ws.ev_act0)?;
9309 crate::tp::raw_copy_bytes(ws.raw_act1, ws.raw_act_e, hf * 4, rank1)?;
9310 let SplitWs { act1, y1, .. } = &mut *ws;
9311 rank1.matvec_bf16_into(&rep.wd1, act1, y1, n_ff_sh, nd)?;
9312 crate::tp::raw_copy_bytes(ws.raw_sh_e + (nd * 4) as u64, ws.raw_y1, nd * 4, rank1)?;
9313 ws.ev_y1.record(&rank1.stream())?;
9314 }
9315 {
9317 let _m = e.gpu.enter_main()?;
9318 e.stream().wait(&ws.ev_act1)?;
9319 let SplitWs { act, sh_buf, .. } = &mut *ws;
9320 let wd_lo = wd.slice(0..nd * n_ff_sh * 2);
9321 e.matvec_bf16_view_into(&wd_lo, act, sh_buf, n_ff_sh, nd)?;
9322 e.stream().wait(&ws.ev_y1)?;
9323 let mut sh = e.uninit(n_embd)?;
9324 {
9325 let mut dst = sh.slice_mut(0..n_embd);
9326 e.stream()
9327 .memcpy_dtod(&ws.sh_buf.slice(0..n_embd), &mut dst)?;
9328 }
9329 Ok(Some(sh))
9330 }
9331 }
9332
9333 fn shexp_overlap_issue(
9340 e: &Engine,
9341 m: &MoeWeights,
9342 z: &CudaSlice<f32>,
9343 cfg: &ModelConfig,
9344 il: u16,
9345 n_embd: usize,
9346 ) -> Result<bool, Box<dyn std::error::Error>> {
9347 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9348 return Ok(false);
9349 }
9350 let (
9351 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9352 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9353 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9354 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9355 else {
9356 return Ok(false);
9357 };
9358 let n_ff_sh = m
9359 .gate_shexp
9360 .as_ref()
9361 .expect("matched Some above")
9362 .out_features();
9363 let lim = cfg.clamp_shexp_at(il as u32);
9364 let mut guard = SHEXP_OV_WS
9365 .lock()
9366 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9367 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9368 if guard
9369 .as_ref()
9370 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9371 {
9372 *guard = Some((
9373 pins.0,
9374 pins.1,
9375 pins.2,
9376 e.uninit(n_ff_sh)?,
9377 e.uninit(n_embd)?,
9378 ));
9379 }
9380 let (_, _, _, act, sh) = guard.as_mut().expect("armed above");
9381 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9382 e.matvec_bf16_into(wd, act, sh, n_ff_sh, n_embd)?;
9383 drop(guard);
9384 Ok(true)
9385 }
9386
9387 #[allow(clippy::too_many_arguments)]
9393 fn shexp_dev1_issue(
9394 e: &Engine,
9395 rank1: &Engine,
9396 m: &MoeWeights,
9397 z: &CudaSlice<f32>,
9398 cfg: &ModelConfig,
9399 il: u16,
9400 n_embd: usize,
9401 ) -> Result<bool, Box<dyn std::error::Error>> {
9402 use cudarc::driver::DevicePtr;
9403 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9404 return Ok(false);
9405 }
9406 let (
9407 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9408 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9409 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9410 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9411 else {
9412 return Ok(false);
9413 };
9414 let n_ff_sh = m
9415 .gate_shexp
9416 .as_ref()
9417 .expect("matched Some above")
9418 .out_features();
9419 let lim = cfg.clamp_shexp_at(il as u32);
9420 let mut ws_guard = SHEXP_D1_WS
9422 .lock()
9423 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9424 if ws_guard
9425 .as_ref()
9426 .is_none_or(|(k, ..)| *k != (n_embd, n_ff_sh))
9427 {
9428 let (act1, z1, ev_done) = {
9429 let _r1 = rank1.gpu.enter_main()?;
9430 (
9431 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9432 rank1.htod(&vec![0.0f32; n_embd])?,
9433 rank1.ctx().new_event(None)?,
9434 )
9435 };
9436 let (sh_root, ev_z) = {
9437 let _main = e.gpu.enter_main()?;
9438 (e.htod(&vec![0.0f32; n_embd])?, e.ctx().new_event(None)?)
9439 };
9440 *ws_guard = Some(((n_embd, n_ff_sh), act1, z1, sh_root, ev_z, ev_done));
9441 }
9442 let mut reps_guard = SHEXP_D1_REPS
9444 .lock()
9445 .map_err(|_| "shexp dev1 replica lock is poisoned")?;
9446 let reps = reps_guard.get_or_insert_with(Default::default);
9447 if !reps.contains_key(&il) {
9448 let (wg1, wu1, wd1) = {
9449 let _r1 = rank1.gpu.enter_main()?;
9450 (
9451 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9452 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9453 rank1.alloc_u8_uninit(n_embd * n_ff_sh * 2)?,
9454 )
9455 };
9456 for (src, dst) in [(wg, &wg1), (wu, &wu1), (wd, &wd1)] {
9457 let s_ptr = {
9458 let _main = e.gpu.enter_main()?;
9459 let stream = e.stream();
9460 let (p, _g) = src.device_ptr(&stream);
9461 p as u64
9462 };
9463 let d_ptr = {
9464 let _r1 = rank1.gpu.enter_main()?;
9465 let stream = rank1.stream();
9466 let (p, _g) = dst.device_ptr(&stream);
9467 p as u64
9468 };
9469 let _r1 = rank1.gpu.enter_main()?;
9470 crate::tp::raw_copy_bytes(d_ptr, s_ptr, src.len(), rank1)?;
9471 }
9472 {
9473 let _r1 = rank1.gpu.enter_main()?;
9474 rank1.stream().synchronize()?;
9475 }
9476 reps.insert(il, (wg1, wu1, wd1));
9477 }
9478 let (wg1, wu1, wd1) = reps.get(&il).expect("armed above");
9479 let (_, act1, z1, sh_root, ev_z, ev_done) = ws_guard.as_mut().expect("armed above");
9480 let (raw_z, raw_sh) = {
9483 let _main = e.gpu.enter_main()?;
9484 let stream = e.stream();
9485 let (a, _g0) = z.device_ptr(&stream);
9486 let (b, _g1) = sh_root.device_ptr(&stream);
9487 ev_z.record(&stream)?;
9488 (a as u64, b as u64)
9489 };
9490 {
9491 let _r1 = rank1.gpu.enter_main()?;
9492 rank1.stream().wait(ev_z)?;
9493 let raw_z1 = {
9494 let stream = rank1.stream();
9495 let (p, _g) = z1.device_ptr(&stream);
9496 p as u64
9497 };
9498 crate::tp::raw_copy_bytes(raw_z1, raw_z, n_embd * 4, rank1)?;
9499 rank1.matvec_bf16_dual_silu_into(wg1, wu1, z1, act1, n_embd, n_ff_sh, lim)?;
9500 rank1.matvec_bf16_raw_out(wd1, act1, raw_sh, n_ff_sh, n_embd)?;
9504 ev_done.record(&rank1.stream())?;
9505 }
9506 Ok(true)
9507 }
9508
9509 fn shexp_dev1_apply(
9511 e: &Engine,
9512 output: &mut CudaSlice<f32>,
9513 n_embd: usize,
9514 ) -> Result<(), Box<dyn std::error::Error>> {
9515 let guard = SHEXP_D1_WS
9516 .lock()
9517 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9518 let (pin, _, _, sh_root, _, ev_done) =
9519 guard.as_ref().ok_or("shexp dev1 apply without issue")?;
9520 if pin.0 != n_embd {
9521 return Err("shexp dev1 width drifted".into());
9522 }
9523 let _main = e.gpu.enter_main()?;
9524 e.stream().wait(ev_done)?;
9525 static ONES_D1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9526 std::sync::Mutex::new(None);
9527 let mut og = ONES_D1.lock().map_err(|_| "ones lock is poisoned")?;
9528 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9529 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9530 }
9531 let ones = &og.as_ref().expect("armed above").1;
9532 e.add_scaled_rows(sh_root, ones, output, n_embd, 1)?;
9533 Ok(())
9534 }
9535
9536 fn shexp_overlap_tail_ptrs(
9540 e: &Engine,
9541 m: &MoeWeights,
9542 cfg: &ModelConfig,
9543 n_embd: usize,
9544 ) -> Result<Option<(u64, u64)>, Box<dyn std::error::Error>> {
9545 use cudarc::driver::DevicePtr;
9546 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9547 return Ok(None);
9548 }
9549 let (
9550 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9551 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9552 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9553 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9554 else {
9555 return Ok(None);
9556 };
9557 let n_ff_sh = m
9558 .gate_shexp
9559 .as_ref()
9560 .expect("matched Some above")
9561 .out_features();
9562 let mut guard = SHEXP_OV_WS
9563 .lock()
9564 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9565 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9566 if guard
9567 .as_ref()
9568 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9569 {
9570 *guard = Some((
9571 pins.0,
9572 pins.1,
9573 pins.2,
9574 e.uninit(n_ff_sh)?,
9575 e.uninit(n_embd)?,
9576 ));
9577 }
9578 let sh_raw = {
9579 let (_, _, _, _, sh) = guard.as_ref().expect("armed above");
9580 let stream = e.stream();
9581 let (p, _g) = sh.device_ptr(&stream);
9582 p as u64
9583 };
9584 static ONES_T3: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9585 std::sync::Mutex::new(None);
9586 let mut og = ONES_T3.lock().map_err(|_| "ones lock is poisoned")?;
9587 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9588 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9589 }
9590 let ones_raw = {
9591 let stream = e.stream();
9592 let (p, _g) = og.as_ref().expect("armed above").1.device_ptr(&stream);
9593 p as u64
9594 };
9595 Ok(Some((sh_raw, ones_raw)))
9596 }
9597
9598 fn shexp_overlap_apply(
9601 e: &Engine,
9602 output: &mut CudaSlice<f32>,
9603 n_embd: usize,
9604 ) -> Result<(), Box<dyn std::error::Error>> {
9605 let guard = SHEXP_OV_WS
9606 .lock()
9607 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9608 let (_, ne, _, _, sh) = guard.as_ref().ok_or("shexp overlap apply without issue")?;
9609 if *ne != n_embd {
9610 return Err("shexp overlap width drifted".into());
9611 }
9612 static ONES_OV: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9613 std::sync::Mutex::new(None);
9614 let mut og = ONES_OV.lock().map_err(|_| "ones lock is poisoned")?;
9615 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9616 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9617 }
9618 let ones = &og.as_ref().expect("armed above").1;
9619 e.add_scaled_rows(sh, ones, output, n_embd, 1)?;
9620 Ok(())
9621 }
9622
9623 fn moe_ffn_grouped_add_shared(
9624 e: &Engine,
9625 m: &MoeWeights,
9626 z: &CudaSlice<f32>,
9627 t: usize,
9628 cfg: &ModelConfig,
9629 il: u16,
9630 moe_out: &mut CudaSlice<f32>,
9631 ) -> Result<(), Box<dyn std::error::Error>> {
9632 static SHEXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9635 static SHEXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9636 let shexp_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9637 let shexp_started = shexp_timing.then(std::time::Instant::now);
9638 let result = Self::moe_ffn_grouped_add_shared_inner(e, m, z, t, cfg, il, moe_out);
9639 if let Some(started) = shexp_started {
9640 use std::sync::atomic::Ordering;
9641 e.stream().synchronize()?;
9642 let ns = SHEXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9643 + started.elapsed().as_nanos() as u64;
9644 let calls = SHEXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9645 if calls % 430 == 0 {
9646 eprintln!(
9647 "[moe-shexp-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9648 ns as f64 / 1.0e6,
9649 ns as f64 / calls as f64 / 1.0e3,
9650 );
9651 }
9652 }
9653 result
9654 }
9655
9656 #[allow(clippy::too_many_arguments)]
9657 fn moe_ffn_grouped_add_shared_inner(
9658 e: &Engine,
9659 m: &MoeWeights,
9660 z: &CudaSlice<f32>,
9661 t: usize,
9662 cfg: &ModelConfig,
9663 il: u16,
9664 moe_out: &mut CudaSlice<f32>,
9665 ) -> Result<(), Box<dyn std::error::Error>> {
9666 let n_embd = cfg.n_embd as usize;
9667 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
9668 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9669 {
9670 let n_ff_sh = gate_shexp.out_features();
9671 let lim = cfg.clamp_shexp_at(il as u32);
9672 let fused = t == 1
9679 && lim.is_none()
9680 && cfg.m3.is_none()
9681 && e.uses_q8_1_fast(gate_shexp)
9682 && e.uses_q8_1_fast(up_shexp);
9683 let bf16_dual = if t == 1 && crate::Engine::bf16_mmv_on() && n_embd % 8 == 0 {
9686 match (gate_shexp, up_shexp) {
9687 (
9688 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
9689 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
9690 ) => Some((wg, wu)),
9691 _ => None,
9692 }
9693 } else {
9694 None
9695 };
9696 let sh = if let Some((wg, wu)) = bf16_dual {
9697 static SHEXP_WS: std::sync::Mutex<
9701 Option<(
9702 usize,
9703 usize,
9704 usize,
9705 CudaSlice<f32>,
9706 CudaSlice<f32>,
9707 CudaSlice<f32>,
9708 CudaSlice<f32>,
9709 )>,
9710 > = std::sync::Mutex::new(None);
9711 let down_bf16 = match down_shexp {
9712 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9713 _ => None,
9714 };
9715 let mut guard = SHEXP_WS
9716 .lock()
9717 .map_err(|_| "shexp workspace lock is poisoned")?;
9718 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9719 if guard
9720 .as_ref()
9721 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9722 {
9723 *guard = Some((
9724 pins.0,
9725 pins.1,
9726 pins.2,
9727 e.uninit(n_ff_sh)?,
9728 e.uninit(n_ff_sh)?,
9729 e.uninit(n_ff_sh)?,
9730 e.uninit(n_embd)?,
9731 ));
9732 }
9733 {
9736 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9737 let split_on = *ON
9738 .get_or_init(|| std::env::var("MEMRA_SHEXP_SPLIT").as_deref() == Ok("1"));
9739 if split_on {
9740 if let (Some(wd), Some(rank1)) = (
9741 match down_shexp {
9742 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9743 _ => None,
9744 },
9745 m.step_tp.as_ref().and_then(|st| st.runtime.rank_engine(1)),
9746 ) {
9747 if let Some(sh) = Self::shexp_split_matvec(
9748 e, rank1, wg, wu, wd, z, lim, cfg, il, n_embd, n_ff_sh,
9749 )? {
9750 drop(guard);
9751 let gate = match &m.gate_inp_shexp {
9752 Some(gate_inp_shexp) => e.sigmoid_dot_rows(
9753 z,
9754 gate_inp_shexp.float_data(),
9755 n_embd,
9756 t,
9757 )?,
9758 None => e.htod(&vec![1.0f32; t])?,
9759 };
9760 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9761 return Ok(());
9762 }
9763 }
9764 }
9765 }
9766 let (_, _, _, gate, up, act, sh_buf) =
9767 guard.as_mut().expect("shexp workspace initialized above");
9768 if cfg.m3.is_none() {
9769 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9772 let _ = (&gate, &up);
9773 } else {
9774 e.matvec_bf16_dual_into(wg, wu, z, gate, up, n_embd, n_ff_sh)?;
9775 Self::ffn_act_lim(e, cfg, gate, up, 1.0, 1.0, lim, act, n_ff_sh)?;
9776 }
9777 if let Some(down) = down_bf16 {
9778 static FUSE_DA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9785 let fuse_da = *FUSE_DA.get_or_init(|| {
9786 std::env::var("MEMRA_FUSE_DOWN_ADDSCALE").as_deref() != Ok("0")
9787 });
9788 if fuse_da && m.gate_inp_shexp.is_none() {
9789 static ONES1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9790 std::sync::Mutex::new(None);
9791 let mut og = ONES1.lock().map_err(|_| "shexp ones lock is poisoned")?;
9792 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9793 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9794 }
9795 let ones = &og.as_ref().expect("armed above").1;
9796 e.matvec_bf16_down_addscale_into(
9797 down, act, ones, moe_out, n_ff_sh, n_embd,
9798 )?;
9799 return Ok(());
9800 }
9801 e.matvec_bf16_into(down, act, sh_buf, n_ff_sh, n_embd)?;
9802 let sh = e.uninit(n_embd)?;
9803 let mut sh = sh;
9805 {
9806 let mut dst = sh.slice_mut(0..n_embd);
9807 e.stream().memcpy_dtod(&sh_buf.slice(0..n_embd), &mut dst)?;
9808 }
9809 sh
9810 } else {
9811 e.matmul(down_shexp, act, 1)?
9812 }
9813 } else if fused {
9814 let (zq, zd) = e.quantize_q8_1(z, 1, n_embd)?;
9815 let pair = match e.matmul_pre_dual_noscale(gate_shexp, up_shexp, &zq, &zd, 1)? {
9816 Some((gate, up)) => Some((gate, up)),
9817 None => {
9818 match (
9819 e.matmul_pre_noscale(gate_shexp, &zq, &zd, 1)?,
9820 e.matmul_pre_noscale(up_shexp, &zq, &zd, 1)?,
9821 ) {
9822 (Some(gate), Some(up)) => Some((gate, up)),
9823 _ => None,
9824 }
9825 }
9826 };
9827 match pair {
9828 Some(((gate, gs), (up, us))) => {
9829 if e.uses_q8_1_fast(down_shexp) {
9830 let (aq, ad) = e.silu_mul_scaled_q8_1(&gate, &up, gs, us, n_ff_sh)?;
9831 e.matmul_pre(down_shexp, &aq, &ad, &gate, 1)?
9832 } else {
9833 let mut act = e.uninit(n_ff_sh)?;
9834 e.silu_mul_scaled(&gate, &up, gs, us, &mut act, n_ff_sh)?;
9835 e.matmul(down_shexp, &act, 1)?
9836 }
9837 }
9838 None => {
9839 let gate = e.matmul_pre(gate_shexp, &zq, &zd, z, 1)?;
9840 let up = e.matmul_pre(up_shexp, &zq, &zd, z, 1)?;
9841 let mut act = e.uninit(n_ff_sh)?;
9842 Self::ffn_act(e, cfg, &gate, &up, &mut act, n_ff_sh)?;
9843 e.matmul(down_shexp, &act, 1)?
9844 }
9845 }
9846 } else {
9847 let sg_gate = e.matmul(gate_shexp, z, t)?;
9848 let sg_up = e.matmul(up_shexp, z, t)?;
9849 let mut sa = e.uninit(t * n_ff_sh)?;
9850 Self::ffn_act_lim(
9851 e,
9852 cfg,
9853 &sg_gate,
9854 &sg_up,
9855 1.0,
9856 1.0,
9857 lim,
9858 &mut sa,
9859 t * n_ff_sh,
9860 )?;
9861 e.matmul(down_shexp, &sa, t)?
9862 };
9863 let gate = match &m.gate_inp_shexp {
9864 Some(gate_inp_shexp) => {
9865 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
9866 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
9867 } else {
9868 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
9869 let mut gate = e.uninit(t)?;
9870 e.sigmoid(&raw, &mut gate, t)?;
9871 gate
9872 }
9873 }
9874 None if t == 1 => {
9879 static ONES: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9880 std::sync::Mutex::new(None);
9881 let mut guard = ONES.lock().map_err(|_| "shexp ones lock is poisoned")?;
9882 if guard.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9883 *guard = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9884 }
9885 let ones = &guard.as_ref().expect("armed above").1;
9886 e.add_scaled_rows(&sh, ones, moe_out, n_embd, t)?;
9887 return Ok(());
9888 }
9889 None => e.htod(&vec![1.0f32; t])?,
9890 };
9891 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9892 }
9893 Ok(())
9894 }
9895
9896 pub(crate) fn moe_ffn_grouped(
9899 e: &Engine,
9900 m: &MoeWeights,
9901 z: &CudaSlice<f32>,
9902 t: usize,
9903 cfg: &ModelConfig,
9904 il: u16,
9905 max_block: usize,
9906 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9907 let moe = cfg.moe.as_ref().unwrap();
9908 let n_embd = cfg.n_embd as usize;
9909 let n_expert = moe.expert_count as usize;
9910 let n_used = moe.expert_used_count as usize;
9911 let n_ff_exp = moe.expert_ff_length as usize;
9912 let lim_exp = cfg.clamp_exp_at(il as u32);
9914
9915 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
9919 if let Some(sig) = cfg.sigmoid_router() {
9920 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
9921 }
9922 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
9923 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
9924 } else {
9925 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
9926 };
9927 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
9928 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
9929 Self::trace_moe_input(e, il, t, n_embd, z)?;
9930
9931 let no_exp_macros = m.gate_exps.macros.is_none()
9936 && m.up_exps.macros.is_none()
9937 && m.down_exps.macros.is_none();
9938 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
9939 m.has_uniform_expert_layout()
9940 && no_exp_macros
9941 && moe_q8_enabled()
9942 && q8_expert_supported(m.gate_exps.qtype)
9943 && q8_expert_supported(m.up_exps.qtype)
9944 && q8_expert_supported(m.down_exps.qtype)
9945 && moe_slab_enabled()
9946 && dev.dev == e.ctx().ordinal()
9947 });
9948 if let Some(dev) = resident_q8 {
9949 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
9950 e,
9951 m,
9952 z,
9953 t,
9954 cfg,
9955 il,
9956 &sel_all,
9957 &w_all,
9958 &dev.ptr_row,
9959 dev.gu_il,
9960 )?;
9961 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
9962 return Ok(moe_out);
9963 }
9964
9965 struct ExpertGroup {
9969 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
9973 let mut groups: Vec<ExpertGroup> = (0..n_expert)
9974 .map(|_| ExpertGroup {
9975 tok_indices: Vec::new(),
9976 slot_indices: Vec::new(),
9977 weights: Vec::new(),
9978 })
9979 .collect();
9980
9981 for tok in 0..t {
9982 for j in 0..n_used {
9983 let ex = sel_all[tok * n_used + j] as usize;
9984 let w = w_all[tok * n_used + j];
9985 groups[ex].tok_indices.push(tok as i32);
9986 groups[ex].slot_indices.push(j as i32);
9987 groups[ex].weights.push(w);
9988 }
9989 }
9990
9991 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
9994 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
9998 let u_len = m.up_exps.max_expert_bytes();
9999 let d_len = m.down_exps.max_expert_bytes();
10000 let moe_q8 = m.has_uniform_expert_layout()
10001 && moe_q8_enabled()
10002 && q8_expert_supported(m.gate_exps.qtype)
10003 && q8_expert_supported(m.up_exps.qtype)
10004 && q8_expert_supported(m.down_exps.qtype);
10005 let slab_local = m
10008 .dev_exps
10009 .as_ref()
10010 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
10011 let use_cache =
10012 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
10013 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
10016
10017 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
10019 (
10020 Some(e.alloc_u8(g_len)?),
10021 Some(e.alloc_u8(u_len)?),
10022 Some(e.alloc_u8(d_len)?),
10023 )
10024 } else {
10025 (None, None, None)
10026 };
10027
10028 let mut order: Vec<usize> = (0..n_expert)
10039 .filter(|&ex| !groups[ex].tok_indices.is_empty())
10040 .collect();
10041 order.sort_by(|&a, &b| {
10042 groups[b]
10043 .tok_indices
10044 .len()
10045 .cmp(&groups[a].tok_indices.len())
10046 .then(a.cmp(&b))
10047 });
10048 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
10050 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
10051 if worker_disk_prefetch {
10052 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
10053 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
10054 }
10055 }
10056 for (order_pos, &ex) in order.iter().enumerate() {
10057 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
10058 Self::moe_prefetch_host_expert(order[next], m);
10059 }
10060 if worker_disk_prefetch {
10061 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
10062 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10063 let keep = [
10064 BlockId::new(il, PROJ_GATE, ex as u16),
10065 BlockId::new(il, PROJ_UP, ex as u16),
10066 BlockId::new(il, PROJ_DOWN, ex as u16),
10067 ];
10068 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
10069 }
10070 }
10071 let grp = &groups[ex];
10072 let m_e = grp.tok_indices.len();
10073 m_dist.push(m_e);
10074 let gl = m.gate_exps.expert_layout(ex);
10075 let ul = m.up_exps.expert_layout(ex);
10076 let dl = m.down_exps.expert_layout(ex);
10077
10078 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
10082 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
10083 let dmac = m.down_exps.macro_scale(ex);
10084 let weight_d = if dmac == 1.0 {
10085 e.htod(&grp.weights)?
10086 } else {
10087 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
10088 e.htod(&scaled)?
10089 };
10090
10091 let mut gathered = e.zeros(m_e * n_embd)?;
10093 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
10094 let gv = gathered.slice(0..m_e * n_embd);
10095
10096 let y = if let Some(dev) = slab_local {
10099 let gate_start = ex * m.gate_exps.expert_stride;
10100 let up_start = ex * m.up_exps.expert_stride;
10101 let down_start = ex * m.down_exps.expert_stride;
10102 if grouped_q8 {
10103 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10104 let gate = e.qmatvec_expert_q8(
10105 &dev.gate,
10106 gate_start..gate_start + gl.len,
10107 &zq,
10108 &zd,
10109 m_e,
10110 m.gate_exps.in_f,
10111 m.gate_exps.out_f,
10112 gl.qtype,
10113 gl.row_bytes,
10114 )?;
10115 let up = e.qmatvec_expert_q8(
10116 &dev.up,
10117 up_start..up_start + ul.len,
10118 &zq,
10119 &zd,
10120 m_e,
10121 m.up_exps.in_f,
10122 m.up_exps.out_f,
10123 ul.qtype,
10124 ul.row_bytes,
10125 )?;
10126 let mut act = e.uninit(m_e * n_ff_exp)?;
10127 Self::ffn_act_lim(
10128 e,
10129 cfg,
10130 &gate,
10131 &up,
10132 m.gate_exps.macro_scale(ex),
10133 m.up_exps.macro_scale(ex),
10134 lim_exp,
10135 &mut act,
10136 m_e * n_ff_exp,
10137 )?;
10138 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10139 e.qmatvec_expert_q8(
10140 &dev.down,
10141 down_start..down_start + dl.len,
10142 &aq2,
10143 &ad2,
10144 m_e,
10145 m.down_exps.in_f,
10146 m.down_exps.out_f,
10147 dl.qtype,
10148 dl.row_bytes,
10149 )?
10150 } else {
10151 let gate = e.qmatvec_view(
10152 &dev.gate,
10153 gate_start..gate_start + gl.len,
10154 &gv,
10155 m_e,
10156 m.gate_exps.in_f,
10157 m.gate_exps.out_f,
10158 gl.qtype,
10159 gl.row_bytes,
10160 )?;
10161 let up = e.qmatvec_view(
10162 &dev.up,
10163 up_start..up_start + ul.len,
10164 &gv,
10165 m_e,
10166 m.up_exps.in_f,
10167 m.up_exps.out_f,
10168 ul.qtype,
10169 ul.row_bytes,
10170 )?;
10171 let mut act = e.uninit(m_e * n_ff_exp)?;
10172 Self::ffn_act_lim(
10173 e,
10174 cfg,
10175 &gate,
10176 &up,
10177 m.gate_exps.macro_scale(ex),
10178 m.up_exps.macro_scale(ex),
10179 lim_exp,
10180 &mut act,
10181 m_e * n_ff_exp,
10182 )?;
10183 let actv = act.slice(0..m_e * n_ff_exp);
10184 e.qmatvec_view(
10185 &dev.down,
10186 down_start..down_start + dl.len,
10187 &actv,
10188 m_e,
10189 m.down_exps.in_f,
10190 m.down_exps.out_f,
10191 dl.qtype,
10192 dl.row_bytes,
10193 )?
10194 }
10195 } else if use_cache {
10196 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10197 if grouped_q8 {
10198 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10199 let gate = e.with_moe_cache(max_block, |cache, eng| {
10200 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10201 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10202 eng.qmatvec_expert_q8(
10203 cache.buf(slot),
10204 0..gl.len,
10205 &zq,
10206 &zd,
10207 m_e,
10208 m.gate_exps.in_f,
10209 m.gate_exps.out_f,
10210 gl.qtype,
10211 gl.row_bytes,
10212 )
10213 })?;
10214 let up = e.with_moe_cache(max_block, |cache, eng| {
10215 let id = BlockId::new(il, PROJ_UP, ex as u16);
10216 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10217 eng.qmatvec_expert_q8(
10218 cache.buf(slot),
10219 0..ul.len,
10220 &zq,
10221 &zd,
10222 m_e,
10223 m.up_exps.in_f,
10224 m.up_exps.out_f,
10225 ul.qtype,
10226 ul.row_bytes,
10227 )
10228 })?;
10229 let mut act = e.uninit(m_e * n_ff_exp)?;
10230 Self::ffn_act_lim(
10231 e,
10232 cfg,
10233 &gate,
10234 &up,
10235 m.gate_exps.macro_scale(ex),
10236 m.up_exps.macro_scale(ex),
10237 lim_exp,
10238 &mut act,
10239 m_e * n_ff_exp,
10240 )?;
10241 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10242 e.with_moe_cache(max_block, |cache, eng| {
10243 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10244 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10245 eng.qmatvec_expert_q8(
10246 cache.buf(slot),
10247 0..dl.len,
10248 &aq2,
10249 &ad2,
10250 m_e,
10251 m.down_exps.in_f,
10252 m.down_exps.out_f,
10253 dl.qtype,
10254 dl.row_bytes,
10255 )
10256 })?
10257 } else {
10258 let gate = e.with_moe_cache(max_block, |cache, eng| {
10259 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10260 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10261 eng.qmatvec_view(
10262 cache.buf(slot),
10263 0..gl.len,
10264 &gv,
10265 m_e,
10266 m.gate_exps.in_f,
10267 m.gate_exps.out_f,
10268 gl.qtype,
10269 gl.row_bytes,
10270 )
10271 })?;
10272 let up = e.with_moe_cache(max_block, |cache, eng| {
10273 let id = BlockId::new(il, PROJ_UP, ex as u16);
10274 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10275 eng.qmatvec_view(
10276 cache.buf(slot),
10277 0..ul.len,
10278 &gv,
10279 m_e,
10280 m.up_exps.in_f,
10281 m.up_exps.out_f,
10282 ul.qtype,
10283 ul.row_bytes,
10284 )
10285 })?;
10286 let mut act = e.uninit(m_e * n_ff_exp)?;
10287 Self::ffn_act_lim(
10288 e,
10289 cfg,
10290 &gate,
10291 &up,
10292 m.gate_exps.macro_scale(ex),
10293 m.up_exps.macro_scale(ex),
10294 lim_exp,
10295 &mut act,
10296 m_e * n_ff_exp,
10297 )?;
10298 let actv = act.slice(0..m_e * n_ff_exp);
10299 e.with_moe_cache(max_block, |cache, eng| {
10300 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10301 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10302 eng.qmatvec_view(
10303 cache.buf(slot),
10304 0..dl.len,
10305 &actv,
10306 m_e,
10307 m.down_exps.in_f,
10308 m.down_exps.out_f,
10309 dl.qtype,
10310 dl.row_bytes,
10311 )
10312 })?
10313 }
10314 } else {
10315 let sg = scratch_g.as_mut().unwrap();
10316 let su = scratch_u.as_mut().unwrap();
10317 let sd = scratch_d.as_mut().unwrap();
10318 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
10319 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
10320 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
10321 if grouped_q8 {
10322 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10323 let gate = e.qmatvec_expert_q8(
10324 sg,
10325 0..gl.len,
10326 &zq,
10327 &zd,
10328 m_e,
10329 m.gate_exps.in_f,
10330 m.gate_exps.out_f,
10331 gl.qtype,
10332 gl.row_bytes,
10333 )?;
10334 let up = e.qmatvec_expert_q8(
10335 su,
10336 0..ul.len,
10337 &zq,
10338 &zd,
10339 m_e,
10340 m.up_exps.in_f,
10341 m.up_exps.out_f,
10342 ul.qtype,
10343 ul.row_bytes,
10344 )?;
10345 let mut act = e.uninit(m_e * n_ff_exp)?;
10346 Self::ffn_act_lim(
10347 e,
10348 cfg,
10349 &gate,
10350 &up,
10351 m.gate_exps.macro_scale(ex),
10352 m.up_exps.macro_scale(ex),
10353 lim_exp,
10354 &mut act,
10355 m_e * n_ff_exp,
10356 )?;
10357 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10358 e.qmatvec_expert_q8(
10359 sd,
10360 0..dl.len,
10361 &aq2,
10362 &ad2,
10363 m_e,
10364 m.down_exps.in_f,
10365 m.down_exps.out_f,
10366 dl.qtype,
10367 dl.row_bytes,
10368 )?
10369 } else {
10370 let gate = e.qmatvec_view(
10371 sg,
10372 0..gl.len,
10373 &gv,
10374 m_e,
10375 m.gate_exps.in_f,
10376 m.gate_exps.out_f,
10377 gl.qtype,
10378 gl.row_bytes,
10379 )?;
10380 let up = e.qmatvec_view(
10381 su,
10382 0..ul.len,
10383 &gv,
10384 m_e,
10385 m.up_exps.in_f,
10386 m.up_exps.out_f,
10387 ul.qtype,
10388 ul.row_bytes,
10389 )?;
10390 let mut act = e.uninit(m_e * n_ff_exp)?;
10391 Self::ffn_act_lim(
10392 e,
10393 cfg,
10394 &gate,
10395 &up,
10396 m.gate_exps.macro_scale(ex),
10397 m.up_exps.macro_scale(ex),
10398 lim_exp,
10399 &mut act,
10400 m_e * n_ff_exp,
10401 )?;
10402 let actv = act.slice(0..m_e * n_ff_exp);
10403 e.qmatvec_view(
10404 sd,
10405 0..dl.len,
10406 &actv,
10407 m_e,
10408 m.down_exps.in_f,
10409 m.down_exps.out_f,
10410 dl.qtype,
10411 dl.row_bytes,
10412 )?
10413 }
10414 };
10415
10416 e.scatter_slot(
10418 &y,
10419 &tok_idx_d,
10420 &slot_idx_d,
10421 &weight_d,
10422 &mut slot_buf,
10423 &mut wbuf,
10424 n_embd,
10425 n_used,
10426 m_e,
10427 )?;
10428 }
10429
10430 let mut moe_out = e.zeros(t * n_embd)?;
10432 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
10433
10434 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
10436 m_dist.sort_unstable();
10437 let active = m_dist.len();
10438 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
10439 let median = m_dist[active / 2];
10440 let max_m = *m_dist.last().unwrap();
10441 let min_m = m_dist[0];
10442 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
10443 println!(
10444 "moe-grouped il={il} t={t} active={active}/{n_expert} \
10445 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
10446 above_gemm_threshold(>=16)={above16}/{active}"
10447 );
10448 }
10449
10450 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10451 Ok(moe_out)
10452 }
10453
10454 pub(crate) fn moe_ffn_lockstep(
10461 &self,
10462 e: &Engine,
10463 m: &MoeWeights,
10464 zbatch: &CudaSlice<f32>,
10465 mrows: usize,
10466 il: u16,
10467 max_block: usize,
10468 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10469 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10470 let cfg = &self.cfg;
10471 let moe = cfg.moe.as_ref().unwrap();
10472 let n_embd = cfg.n_embd as usize;
10473 let n_expert = moe.expert_count as usize;
10474 let n_used = moe.expert_used_count as usize;
10475 let n_ff_exp = moe.expert_ff_length as usize;
10476 let lim_exp = cfg.clamp_exp_at(il as u32);
10478 let lim_shexp = cfg.clamp_shexp_at(il as u32);
10479
10480 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
10481 if let Some(sig) = cfg.sigmoid_router() {
10482 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
10483 }
10484 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10485 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
10486 } else {
10487 Self::moe_route_cfg(
10488 e,
10489 &logits,
10490 mrows,
10491 n_expert,
10492 n_used,
10493 m.active_experts.as_deref(),
10494 )?
10495 };
10496 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
10497
10498 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
10500 Ok((0..n_expert)
10501 .map(|ex| {
10502 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
10503 .into_iter()
10504 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
10505 })
10506 .collect())
10507 })?;
10508
10509 struct Group {
10510 rows: Vec<i32>,
10511 slots: Vec<i32>,
10512 weights: Vec<f32>,
10513 }
10514 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
10515 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
10516 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
10517 Default::default();
10518 for row in 0..mrows {
10519 for j in 0..n_used {
10520 let ex = sel_all[row * n_used + j] as usize;
10521 let w = w_all[row * n_used + j];
10522 if resident_expert[ex] {
10523 let group = groups.entry(ex).or_insert_with(|| Group {
10524 rows: Vec::new(),
10525 slots: Vec::new(),
10526 weights: Vec::new(),
10527 });
10528 group.rows.push(row as i32);
10529 group.slots.push(j as i32);
10530 group.weights.push(w);
10531 } else {
10532 crate::cpu_experts::record_incomplete_gpu_residency(0);
10533 cpu_rows[row].push((ex, w));
10534 cpu_by_expert.entry(ex).or_default().push((row, w));
10535 }
10536 }
10537 }
10538
10539 let host_rows = e.dtoh(zbatch)?;
10545 let rows_ok = crate::cpu_experts::rows_supported();
10546 enum CpuPart {
10547 Single { row: usize },
10548 Rows { rows: Vec<usize> },
10549 }
10550 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
10551 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
10552 if rows_ok {
10553 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
10554 .into_iter()
10555 .filter(|(_, rows)| rows.len() >= 2)
10556 .collect();
10557 shared.sort_by_key(|(ex, _)| *ex);
10558 for (ex, mut row_weights) in shared {
10559 row_weights.sort_by_key(|(row, _)| *row);
10560 let inputs: Vec<(&[f32], f32)> = row_weights
10561 .iter()
10562 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
10563 .collect();
10564 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
10565 .map_err(std::io::Error::other)?;
10566 for &(row, _) in &row_weights {
10567 rows_served.insert((row, ex));
10568 }
10569 tickets.push((
10570 CpuPart::Rows {
10571 rows: row_weights.iter().map(|&(row, _)| row).collect(),
10572 },
10573 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
10574 ));
10575 }
10576 }
10577 for (row, selected) in cpu_rows.iter().enumerate() {
10578 let leftover: Vec<(usize, f32)> = selected
10579 .iter()
10580 .copied()
10581 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
10582 .collect();
10583 if leftover.is_empty() {
10584 continue;
10585 }
10586 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
10587 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
10588 .map_err(std::io::Error::other)?;
10589 tickets.push((
10590 CpuPart::Single { row },
10591 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
10592 ));
10593 }
10594
10595 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
10596 let mut wbuf = e.zeros(mrows * n_used)?;
10597 let mut order: Vec<usize> = groups.keys().copied().collect();
10598 order.sort_by(|&a, &b| {
10599 groups[&b]
10600 .rows
10601 .len()
10602 .cmp(&groups[&a].rows.len())
10603 .then(a.cmp(&b))
10604 });
10605 for &ex in &order {
10606 let group = &groups[&ex];
10607 let m_e = group.rows.len();
10608 let gl = m.gate_exps.expert_layout(ex);
10609 let ul = m.up_exps.expert_layout(ex);
10610 let dl = m.down_exps.expert_layout(ex);
10611 let row_idx_d = e.htod_i32(&group.rows)?;
10612 let slot_idx_d = e.htod_i32(&group.slots)?;
10613 let dmac = m.down_exps.macro_scale(ex);
10614 let weight_d = if dmac == 1.0 {
10615 e.htod(&group.weights)?
10616 } else {
10617 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
10618 e.htod(&scaled)?
10619 };
10620 let mut gathered = e.zeros(m_e * n_embd)?;
10621 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
10622 let gv = gathered.slice(0..m_e * n_embd);
10623 let gate = e.with_moe_cache(max_block, |c, eng| {
10624 let slot = c
10625 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
10626 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10627 eng.qmatvec_view(
10628 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10629 0..gl.len,
10630 &gv,
10631 m_e,
10632 m.gate_exps.in_f,
10633 m.gate_exps.out_f,
10634 gl.qtype,
10635 gl.row_bytes,
10636 )
10637 })?;
10638 let up = e.with_moe_cache(max_block, |c, eng| {
10639 let slot = c
10640 .resident(BlockId::new(il, PROJ_UP, ex as u16))
10641 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10642 eng.qmatvec_view(
10643 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10644 0..ul.len,
10645 &gv,
10646 m_e,
10647 m.up_exps.in_f,
10648 m.up_exps.out_f,
10649 ul.qtype,
10650 ul.row_bytes,
10651 )
10652 })?;
10653 let mut act = e.zeros(m_e * n_ff_exp)?;
10654 Self::ffn_act_lim(
10655 e,
10656 cfg,
10657 &gate,
10658 &up,
10659 m.gate_exps.macro_scale(ex),
10660 m.up_exps.macro_scale(ex),
10661 lim_exp,
10662 &mut act,
10663 m_e * n_ff_exp,
10664 )?;
10665 let actv = act.slice(0..m_e * n_ff_exp);
10666 let y = e.with_moe_cache(max_block, |c, eng| {
10667 let slot = c
10668 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
10669 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10670 eng.qmatvec_view(
10671 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10672 0..dl.len,
10673 &actv,
10674 m_e,
10675 m.down_exps.in_f,
10676 m.down_exps.out_f,
10677 dl.qtype,
10678 dl.row_bytes,
10679 )
10680 })?;
10681 e.scatter_slot(
10682 &y,
10683 &row_idx_d,
10684 &slot_idx_d,
10685 &weight_d,
10686 &mut slot_buf,
10687 &mut wbuf,
10688 n_embd,
10689 n_used,
10690 m_e,
10691 )?;
10692 }
10693 let mut moe_out = e.zeros(mrows * n_embd)?;
10694 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
10695
10696 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
10698 for (part, ticket) in tickets {
10699 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
10700 let mut add_row = |row: usize, chunk: &[f32]| {
10701 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
10702 for (accumulator, value) in sum.iter_mut().zip(chunk) {
10703 *accumulator += value;
10704 }
10705 };
10706 match part {
10707 CpuPart::Single { row } => add_row(row, &cpu_output),
10708 CpuPart::Rows { rows } => {
10709 for (slot, row) in rows.into_iter().enumerate() {
10710 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
10711 }
10712 }
10713 }
10714 }
10715 for (row, sum) in row_sums.into_iter().enumerate() {
10716 let Some(sum) = sum else { continue };
10717 let cpu_output = e.htod(&sum)?;
10718 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
10719 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
10720 }
10721
10722 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
10723 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
10724 {
10725 let n_ff_sh = gate_shexp.out_features();
10726 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
10727 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
10728 let mut sa = e.zeros(mrows * n_ff_sh)?;
10729 Self::ffn_act_lim(
10730 e,
10731 cfg,
10732 &sg_gate,
10733 &sg_up,
10734 1.0,
10735 1.0,
10736 lim_shexp,
10737 &mut sa,
10738 mrows * n_ff_sh,
10739 )?;
10740 let sh = e.matmul(down_shexp, &sa, mrows)?;
10741 let g = match &m.gate_inp_shexp {
10744 Some(gate_inp_shexp) => {
10745 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
10746 }
10747 None => e.htod(&vec![1.0f32; mrows])?,
10748 };
10749 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
10750 }
10751
10752 Ok(moe_out)
10753 }
10754}
10755
10756impl HybridModel {
10762 pub(crate) fn gemma4_rope_dims(&self, il: usize) -> usize {
10776 let g = self
10777 .cfg
10778 .gemma4
10779 .as_ref()
10780 .expect("gemma4_rope_dims on a non-gemma4 config");
10781 if g.swa_pattern[il] {
10782 g.rope_dims_swa as usize
10783 } else {
10784 g.rope_dims_global as usize
10785 }
10786 }
10787
10788 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
10789 let g = self.cfg.gemma4.as_ref().unwrap();
10790 let swa = g.swa_pattern[il];
10791 let hd = if swa {
10792 g.key_length_swa
10793 } else {
10794 g.key_length_global
10795 } as usize;
10796 (
10800 hd,
10801 g.head_count_kv[il] as usize,
10802 self.cfg.n_head as usize,
10803 if swa {
10804 g.rope_base_swa
10805 } else {
10806 g.rope_base_global
10807 },
10808 1.0,
10809 swa,
10810 )
10811 }
10812
10813 pub(crate) fn gemma4_suppress(
10817 &self,
10818 e: &Engine,
10819 ld: &mut CudaSlice<f32>,
10820 t: usize,
10821 ) -> Result<(), Box<dyn std::error::Error>> {
10822 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
10823 #[cfg(debug_assertions)]
10828 crate::debug_assert_tensor_stream_device(
10829 ids,
10830 &e.stream(),
10831 "gemma4_suppress.suppress_d",
10832 );
10833 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
10834 }
10835 Ok(())
10836 }
10837
10838 #[allow(clippy::too_many_arguments)]
10843 fn gemma_fa_one_program() -> bool {
10852 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10853 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_FA_ONE_PROGRAM").as_deref() == Ok("1"))
10854 }
10855
10856 fn gemma4_attn_prime(
10857 &self,
10858 e: &Engine,
10859 fa: &crate::hybrid::FullAttnLayer,
10860 il: usize,
10861 h: &CudaSlice<f32>,
10862 pos_d: &CudaSlice<i32>,
10863 t: usize,
10864 cache: Option<&mut Cache>,
10865 island: Option<&CudaSlice<i32>>,
10866 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10867 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10868 let eps = self.cfg.rms_eps;
10869 let aux = self.gemma4_aux.as_ref().unwrap();
10870 let ones = aux.ones(e);
10871 #[cfg(debug_assertions)]
10872 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
10873
10874 e.mmq_act_begin();
10877 let q0 = e.matmul(&fa.wq, h, t)?; if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10879 let v = e.dtoh(&q0)?;
10880 let nan = v.iter().filter(|x| x.is_nan()).count();
10881 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10882 eprintln!(
10883 "[g4-prime-trace] L0 q0: nan={nan}/{} amax={amax:.3}",
10884 v.len()
10885 );
10886 }
10887 let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
10891 e.matmul(&fa.wv, h, t)?
10892 } else {
10893 e.clone_dtod(&k0)?
10894 };
10895 if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10896 for (tag, buf) in [("k0", &k0), ("v0", &v0)] {
10897 let v = e.dtoh(buf)?;
10898 let nan = v.iter().filter(|x| x.is_nan()).count();
10899 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10900 eprintln!(
10901 "[g4-prime-trace] L0 {tag}: nan={nan}/{} amax={amax:.3}",
10902 v.len()
10903 );
10904 }
10905 }
10906
10907 let mut q = e.uninit(t * nh * hd)?;
10908 let mut k = e.uninit(t * nkv * hd)?;
10909 let mut v = e.uninit(t * nkv * hd)?;
10911 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10915 let emit = island.is_none()
10918 && t >= 16
10919 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
10920 && *EMIT.get_or_init(|| {
10921 std::env::var("MEMRA_FA_EMIT")
10922 .map(|s| s != "0")
10923 .unwrap_or(true)
10924 });
10925 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
10926 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10927 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10928 let v_f16 = emit
10931 && crate::fa_f16pv_on()
10932 && match hd {
10933 512 => true,
10934 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
10935 _ => false,
10936 };
10937 if emit {
10938 e.rms_norm_qkv_w4b(
10939 &q0,
10940 &k0,
10941 &v0,
10942 fa.q_norm.float_data(),
10943 fa.k_norm.float_data(),
10944 ones,
10945 &mut q,
10946 &mut k,
10947 &mut v,
10948 &mut vb,
10949 hd,
10950 nh * t,
10951 nkv * t,
10952 eps,
10953 v_f16,
10954 )?;
10955 } else {
10956 e.rms_norm_qkv(
10957 &q0,
10958 &k0,
10959 &v0,
10960 fa.q_norm.float_data(),
10961 fa.k_norm.float_data(),
10962 ones,
10963 &mut q,
10964 &mut k,
10965 &mut v,
10966 hd,
10967 nh * t,
10968 nkv * t,
10969 eps,
10970 )?;
10971 }
10972
10973 let ff = if swa {
10974 None
10975 } else {
10976 Some(
10977 aux.rope_freqs(e)
10978 .expect("gemma4 global rope needs rope_freqs.weight"),
10979 )
10980 };
10981 #[cfg(debug_assertions)]
10982 if let Some(ff) = ff {
10983 crate::debug_assert_tensor_stream_device(
10984 ff,
10985 &e.stream(),
10986 "gemma4_attn_prime.rope_freqs",
10987 );
10988 }
10989 if emit {
10990 e.rope_neox2_bf16e(
10991 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
10992 )?;
10993 } else {
10994 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
10995 }
10996
10997 if let Some(cache) = cache {
10998 let kvl = cache.kv[il].as_mut().unwrap();
10999 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
11000 e.append_kv_quantized_rows(
11001 &k,
11002 &v,
11003 &mut kvl.k,
11004 &mut kvl.v,
11005 kvl.len,
11006 t,
11007 kvl.kv_dim_k,
11008 kvl.kv_dim_v,
11009 kvl.k_tok_bytes,
11010 kvl.v_tok_bytes,
11011 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
11012 )?;
11013 kvl.len += t;
11014 }
11015 let mut attn = e.zeros(t * nh * hd)?;
11016 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11020 if let Some(span) = island {
11021 let w = if swa && t > win { win } else { 0 };
11026 e.sdpa_naive_island(&q, &k, &v, &mut attn, span, hd, nh, nkv, t, t, scale, w)?;
11027 } else if swa && (t > win || Self::gemma_fa_one_program()) {
11028 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11029 if emit {
11030 e.fa_prefill_w_pre(
11031 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
11032 )?;
11033 } else {
11034 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11035 }
11036 } else {
11037 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11038 }
11039 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11040 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11041 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
11042 if emit {
11043 e.fa_prefill_hd512_pre(
11044 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
11045 )?;
11046 } else {
11047 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11048 }
11049 } else {
11050 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11051 }
11052 Ok(e.matmul(&fa.wo, &attn, t)?)
11053 }
11054
11055 fn gemma4_attn(
11057 &self,
11058 e: &Engine,
11059 fa: &crate::hybrid::FullAttnLayer,
11060 il: usize,
11061 h: &CudaSlice<f32>,
11062 pos_d: &CudaSlice<i32>,
11063 t: usize,
11064 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11065 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None, None)
11066 }
11067
11068 fn gemma4_moe_q8(
11073 &self,
11074 e: &Engine,
11075 m: &crate::hybrid::MoeWeights,
11076 bits: &crate::hybrid::Gemma4MoeBits,
11077 mq: &(CudaSlice<i8>, CudaSlice<f32>),
11078 router_in: &CudaSlice<f32>,
11079 t: usize,
11080 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11081 let cfg = &self.cfg;
11082 let moe = cfg.moe.as_ref().unwrap();
11083 let n_embd = cfg.n_embd as usize;
11084 let n_expert = moe.expert_count as usize;
11085 let n_used = moe.expert_used_count as usize;
11086 let n_ff_exp = moe.expert_ff_length as usize;
11087 let logits = if crate::router_kernel_on() {
11091 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11092 } else {
11093 e.matmul(&m.gate_inp, router_in, t)?
11094 };
11095 let dev = m.dev_exps.as_ref().unwrap();
11096 let (sel_d, w_d) =
11097 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11098 let (zq, zd) = mq;
11099 if t == 1 {
11100 let selv = sel_d.slice(0..n_used);
11101 let wv = w_d.slice(0..n_used);
11102 let act = e.moe_gate_up_gelu8_dev_q8(
11103 &dev.ptr_row,
11104 &selv,
11105 zq,
11106 zd,
11107 n_embd,
11108 n_ff_exp,
11109 n_used,
11110 n_expert,
11111 m.gate_exps.qtype,
11112 m.up_exps.qtype,
11113 m.gate_exps.row_bytes,
11114 m.up_exps.row_bytes,
11115 )?;
11116 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11117 let mut moe_out = e.uninit(n_embd)?;
11118 e.moe_down8_fma_dev_q8(
11119 &dev.ptr_row,
11120 &selv,
11121 &wv,
11122 &aq2,
11123 &ad2,
11124 &mut moe_out.slice_mut(0..n_embd),
11125 n_ff_exp,
11126 n_embd,
11127 n_used,
11128 n_expert,
11129 m.down_exps.qtype,
11130 m.down_exps.row_bytes,
11131 )?;
11132 return Ok(moe_out);
11133 }
11134 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11135 let act = if csr {
11136 e.moe_gate_up_gelu8_dev_q8_csr(
11137 &dev.ptr_row,
11138 &sel_d,
11139 zq,
11140 zd,
11141 t * n_used,
11142 n_embd,
11143 n_ff_exp,
11144 n_used,
11145 n_expert,
11146 m.gate_exps.qtype,
11147 m.up_exps.qtype,
11148 m.gate_exps.row_bytes,
11149 m.up_exps.row_bytes,
11150 )?
11151 } else {
11152 e.moe_gate_up_gelu8_dev_q8_rows(
11153 &dev.ptr_row,
11154 &sel_d,
11155 zq,
11156 zd,
11157 t,
11158 n_embd,
11159 n_ff_exp,
11160 n_used,
11161 n_expert,
11162 m.gate_exps.qtype,
11163 m.up_exps.qtype,
11164 m.gate_exps.row_bytes,
11165 m.up_exps.row_bytes,
11166 )?
11167 };
11168 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11169 let mut moe_out = e.uninit(t * n_embd)?;
11170 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 Ok(moe_out)
11188 }
11189
11190 fn gemma4_moe(
11194 &self,
11195 e: &Engine,
11196 m: &crate::hybrid::MoeWeights,
11197 bits: &crate::hybrid::Gemma4MoeBits,
11198 moe_in: &CudaSlice<f32>,
11199 router_in: &CudaSlice<f32>,
11200 t: usize,
11201 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11202 let cfg = &self.cfg;
11203 let moe = cfg.moe.as_ref().unwrap();
11204 let n_embd = cfg.n_embd as usize;
11205 let n_expert = moe.expert_count as usize;
11206 let n_used = moe.expert_used_count as usize;
11207 let n_ff_exp = moe.expert_ff_length as usize;
11208
11209 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
11213 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11214 } else {
11215 e.matmul(&m.gate_inp, router_in, t)?
11216 };
11217
11218 if t < PRIME_MIN_T
11223 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11224 && expert_dp4a_supported(m.gate_exps.qtype)
11225 && expert_dp4a_supported(m.up_exps.qtype)
11226 && expert_dp4a_supported(m.down_exps.qtype)
11227 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11228 {
11229 let dev = m.dev_exps.as_ref().unwrap();
11230 let (sel_d, w_d) =
11231 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11232 if t == 1 {
11233 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
11234 let selv = sel_d.slice(0..n_used);
11235 let wv = w_d.slice(0..n_used);
11236 let act = e.moe_gate_up_gelu8_dev_q8(
11237 &dev.ptr_row,
11238 &selv,
11239 &zq,
11240 &zd,
11241 n_embd,
11242 n_ff_exp,
11243 n_used,
11244 n_expert,
11245 m.gate_exps.qtype,
11246 m.up_exps.qtype,
11247 m.gate_exps.row_bytes,
11248 m.up_exps.row_bytes,
11249 )?;
11250 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11251 let mut moe_out = e.uninit(n_embd)?;
11252 e.moe_down8_fma_dev_q8(
11253 &dev.ptr_row,
11254 &selv,
11255 &wv,
11256 &aq2,
11257 &ad2,
11258 &mut moe_out.slice_mut(0..n_embd),
11259 n_ff_exp,
11260 n_embd,
11261 n_used,
11262 n_expert,
11263 m.down_exps.qtype,
11264 m.down_exps.row_bytes,
11265 )?;
11266 return Ok(moe_out);
11267 }
11268 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11273 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11274 let act = if csr {
11275 e.moe_gate_up_gelu8_dev_q8_csr(
11276 &dev.ptr_row,
11277 &sel_d,
11278 &zq,
11279 &zd,
11280 t * n_used,
11281 n_embd,
11282 n_ff_exp,
11283 n_used,
11284 n_expert,
11285 m.gate_exps.qtype,
11286 m.up_exps.qtype,
11287 m.gate_exps.row_bytes,
11288 m.up_exps.row_bytes,
11289 )?
11290 } else {
11291 e.moe_gate_up_gelu8_dev_q8_rows(
11292 &dev.ptr_row,
11293 &sel_d,
11294 &zq,
11295 &zd,
11296 t,
11297 n_embd,
11298 n_ff_exp,
11299 n_used,
11300 n_expert,
11301 m.gate_exps.qtype,
11302 m.up_exps.qtype,
11303 m.gate_exps.row_bytes,
11304 m.up_exps.row_bytes,
11305 )?
11306 };
11307 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11308 let mut moe_out = e.uninit(t * n_embd)?;
11309 e.moe_down8_fma_dev_q8_rows_g(
11310 &dev.ptr_row,
11311 &sel_d,
11312 &w_d,
11313 &aq2,
11314 &ad2,
11315 &mut moe_out,
11316 t,
11317 n_ff_exp,
11318 n_embd,
11319 n_used,
11320 n_expert,
11321 m.down_exps.qtype,
11322 m.down_exps.row_bytes,
11323 )?;
11324 return Ok(moe_out);
11325 }
11326
11327 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
11328 for (i, &sx) in sel_all.iter().enumerate() {
11329 w_all[i] *= bits.per_expert_scale[sx as usize];
11330 }
11331
11332 if t >= PRIME_MIN_T
11336 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11337 && expert_dp4a_supported(m.gate_exps.qtype)
11338 && expert_dp4a_supported(m.up_exps.qtype)
11339 && expert_dp4a_supported(m.down_exps.qtype)
11340 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
11341 {
11342 let dev = m.dev_exps.as_ref().unwrap();
11343 let n_pairs = t * n_used;
11344 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
11345 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
11346 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
11347 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
11348 let pt = e.htod_i32(&pair_tok)?;
11349 let pw = e.htod(&w_all)?;
11350 let toff = e.htod_i32(&tok_off)?;
11351 let tids = e.htod_i32(&tok_ids)?;
11352 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11353 for p in 0..n_pairs {
11354 by_ex[pair_ex[p] as usize].push(p as i32);
11355 }
11356 let mut ex_ids: Vec<i32> = Vec::new();
11357 let mut ex_off: Vec<i32> = vec![0];
11358 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
11359 for (ex, list) in by_ex.iter().enumerate() {
11360 if list.is_empty() {
11361 continue;
11362 }
11363 ex_ids.push(ex as i32);
11364 ex_pairs.extend_from_slice(list);
11365 ex_off.push(ex_pairs.len() as i32);
11366 }
11367 let n_active = ex_ids.len();
11368 let exi = e.htod_i32(&ex_ids)?;
11369 let exo = e.htod_i32(&ex_off)?;
11370 let exp_d = e.htod_i32(&ex_pairs)?;
11371 if crate::moe_f16g_gemma_on()
11379 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
11380 && f16g_proj_ok(m.up_exps.qtype, n_embd)
11381 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
11382 {
11383 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11384 let csr_tok_d = e.htod_i32(&csr_tok)?;
11385 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
11386 let g_csr = e.moe_f16_grouped(
11387 &dev.ptr_row,
11388 0,
11389 n_expert,
11390 &exi,
11391 &ex_off,
11392 &exo,
11393 &z_f16,
11394 &z_s,
11395 n_embd,
11396 n_ff_exp,
11397 n_active,
11398 n_pairs,
11399 m.gate_exps.qtype,
11400 m.gate_exps.row_bytes,
11401 )?;
11402 let u_csr = e.moe_f16_grouped(
11403 &dev.ptr_row,
11404 1,
11405 n_expert,
11406 &exi,
11407 &ex_off,
11408 &exo,
11409 &z_f16,
11410 &z_s,
11411 n_embd,
11412 n_ff_exp,
11413 n_active,
11414 n_pairs,
11415 m.up_exps.qtype,
11416 m.up_exps.row_bytes,
11417 )?;
11418 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
11419 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
11420 let d_csr = e.moe_f16_grouped(
11421 &dev.ptr_row,
11422 2,
11423 n_expert,
11424 &exi,
11425 &ex_off,
11426 &exo,
11427 &a_f16,
11428 &a_s,
11429 n_ff_exp,
11430 n_embd,
11431 n_active,
11432 n_pairs,
11433 m.down_exps.qtype,
11434 m.down_exps.row_bytes,
11435 )?;
11436 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
11437 let mut moe_out = e.uninit(t * n_embd)?;
11438 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11439 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
11440 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
11441 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
11442 eprintln!(
11443 "[f16g-debug] post-permute bad={} post-scatter bad={}",
11444 scan(&yd),
11445 scan(&mo)
11446 );
11447 }
11448 return Ok(moe_out);
11449 }
11450 let mma =
11453 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
11454 let (gate, up) = if mma {
11455 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
11456 (
11457 e.mmq_iq_experts(
11458 &dev.ptr_row,
11459 0,
11460 n_expert,
11461 &exi,
11462 &exo,
11463 &exp_d,
11464 &pt,
11465 &z_scr,
11466 n_embd,
11467 n_ff_exp,
11468 n_active,
11469 n_pairs,
11470 t,
11471 m.gate_exps.qtype,
11472 m.gate_exps.row_bytes,
11473 )?,
11474 e.mmq_iq_experts(
11475 &dev.ptr_row,
11476 1,
11477 n_expert,
11478 &exi,
11479 &exo,
11480 &exp_d,
11481 &pt,
11482 &z_scr,
11483 n_embd,
11484 n_ff_exp,
11485 n_active,
11486 n_pairs,
11487 t,
11488 m.up_exps.qtype,
11489 m.up_exps.row_bytes,
11490 )?,
11491 )
11492 } else {
11493 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11494 (
11495 e.moe_pairs_matvec_q8_dec(
11496 &dev.ptr_row,
11497 0,
11498 &exi,
11499 &exo,
11500 &exp_d,
11501 &pt,
11502 &zq,
11503 &zd,
11504 n_embd,
11505 n_ff_exp,
11506 n_expert,
11507 n_active,
11508 n_pairs,
11509 m.gate_exps.qtype,
11510 m.gate_exps.row_bytes,
11511 )?,
11512 e.moe_pairs_matvec_q8_dec(
11513 &dev.ptr_row,
11514 1,
11515 &exi,
11516 &exo,
11517 &exp_d,
11518 &pt,
11519 &zq,
11520 &zd,
11521 n_embd,
11522 n_ff_exp,
11523 n_expert,
11524 n_active,
11525 n_pairs,
11526 m.up_exps.qtype,
11527 m.up_exps.row_bytes,
11528 )?,
11529 )
11530 };
11531 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
11532 let pself = e.htod_i32(&pair_self)?;
11533 let y_down = if mma {
11545 let in_pad = n_ff_exp.div_ceil(256) * 256;
11546 let a_scr = if crate::moe_fuse_actq_on() {
11547 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
11548 } else {
11549 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11550 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
11551 };
11552 e.mmq_iq_experts(
11553 &dev.ptr_row,
11554 2,
11555 n_expert,
11556 &exi,
11557 &exo,
11558 &exp_d,
11559 &pself,
11560 &a_scr,
11561 in_pad,
11562 n_embd,
11563 n_active,
11564 n_pairs,
11565 n_pairs,
11566 m.down_exps.qtype,
11567 m.down_exps.row_bytes,
11568 )?
11569 } else {
11570 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11571 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
11572 e.moe_pairs_matvec_q8_dec(
11573 &dev.ptr_row,
11574 2,
11575 &exi,
11576 &exo,
11577 &exp_d,
11578 &pself,
11579 &aq2,
11580 &ad2,
11581 n_ff_exp,
11582 n_embd,
11583 n_expert,
11584 n_active,
11585 n_pairs,
11586 m.down_exps.qtype,
11587 m.down_exps.row_bytes,
11588 )?
11589 };
11590 let mut moe_out = e.uninit(t * n_embd)?;
11591 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11592 return Ok(moe_out);
11593 }
11594
11595 let g_len = m.gate_exps.expert_stride;
11596 let u_len = m.up_exps.expert_stride;
11597 let d_len = m.down_exps.expert_stride;
11598 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
11602 let (mut sg, mut su, mut sd) = if dev.is_some() {
11603 (None, None, None)
11604 } else {
11605 (
11606 Some(e.alloc_u8_uninit(g_len)?),
11607 Some(e.alloc_u8_uninit(u_len)?),
11608 Some(e.alloc_u8_uninit(d_len)?),
11609 )
11610 };
11611 let mut moe_out = e.zeros(t * n_embd)?;
11612 for tok in 0..t {
11613 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
11614 let w = &w_all[tok * n_used..(tok + 1) * n_used];
11615 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
11616 for (j, &ex) in sel.iter().enumerate() {
11617 let ex = ex as usize;
11618 let gate = match dev {
11619 Some(d) => e.qmatvec_view(
11620 &d.gate,
11621 ex * g_len..(ex + 1) * g_len,
11622 &zt,
11623 1,
11624 m.gate_exps.in_f,
11625 m.gate_exps.out_f,
11626 m.gate_exps.qtype,
11627 m.gate_exps.row_bytes,
11628 )?,
11629 None => {
11630 let sg = sg.as_mut().unwrap();
11631 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
11632 e.qmatvec_view(
11633 sg,
11634 0..g_len,
11635 &zt,
11636 1,
11637 m.gate_exps.in_f,
11638 m.gate_exps.out_f,
11639 m.gate_exps.qtype,
11640 m.gate_exps.row_bytes,
11641 )?
11642 }
11643 };
11644 let up = match dev {
11645 Some(d) => e.qmatvec_view(
11646 &d.up,
11647 ex * u_len..(ex + 1) * u_len,
11648 &zt,
11649 1,
11650 m.up_exps.in_f,
11651 m.up_exps.out_f,
11652 m.up_exps.qtype,
11653 m.up_exps.row_bytes,
11654 )?,
11655 None => {
11656 let su = su.as_mut().unwrap();
11657 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
11658 e.qmatvec_view(
11659 su,
11660 0..u_len,
11661 &zt,
11662 1,
11663 m.up_exps.in_f,
11664 m.up_exps.out_f,
11665 m.up_exps.qtype,
11666 m.up_exps.row_bytes,
11667 )?
11668 }
11669 };
11670 let mut act = e.uninit(n_ff_exp)?;
11671 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
11672 let actv = act.slice(0..n_ff_exp);
11673 let y = match dev {
11674 Some(d) => e.qmatvec_view(
11675 &d.down,
11676 ex * d_len..(ex + 1) * d_len,
11677 &actv,
11678 1,
11679 m.down_exps.in_f,
11680 m.down_exps.out_f,
11681 m.down_exps.qtype,
11682 m.down_exps.row_bytes,
11683 )?,
11684 None => {
11685 let sd = sd.as_mut().unwrap();
11686 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
11687 e.qmatvec_view(
11688 sd,
11689 0..d_len,
11690 &actv,
11691 1,
11692 m.down_exps.in_f,
11693 m.down_exps.out_f,
11694 m.down_exps.qtype,
11695 m.down_exps.row_bytes,
11696 )?
11697 }
11698 };
11699 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
11700 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
11701 }
11702 }
11703 Ok(moe_out)
11704 }
11705
11706 fn gemma4_layer(
11708 &self,
11709 e: &Engine,
11710 il: usize,
11711 layer: &crate::hybrid::HybridLayer,
11712 x: &CudaSlice<f32>,
11713 pos_d: &CudaSlice<i32>,
11714 t: usize,
11715 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11716 let n_embd = self.cfg.n_embd as usize;
11717 let eps = self.cfg.rms_eps;
11718
11719 let mut h = e.zeros(t * n_embd)?;
11720 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
11721 let Mixer::Full(fa) = &layer.mixer else {
11722 panic!("gemma4 layer {il} not full-attn")
11723 };
11724 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
11725 let mut cur = e.zeros(t * n_embd)?;
11727 e.rms_norm(
11728 &o,
11729 layer.post_attn_norm.float_data(),
11730 &mut cur,
11731 n_embd,
11732 t,
11733 eps,
11734 )?;
11735 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
11736 }
11737
11738 fn gemma4_layer_tail_add(
11742 &self,
11743 e: &Engine,
11744 layer: &crate::hybrid::HybridLayer,
11745 cur: &CudaSlice<f32>,
11746 x: &CudaSlice<f32>,
11747 t: usize,
11748 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11749 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
11750 }
11751
11752 fn gemma4_layer_tail_add_n(
11755 &self,
11756 e: &Engine,
11757 layer: &crate::hybrid::HybridLayer,
11758 cur: &CudaSlice<f32>,
11759 x: &CudaSlice<f32>,
11760 t: usize,
11761 next_norm: Option<&CudaSlice<f32>>,
11762 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
11763 let n_embd = self.cfg.n_embd as usize;
11764 let bits = layer.gemma4.as_ref().unwrap();
11765 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
11766 let mut xn = e.uninit(t * n_embd)?;
11767 match next_norm {
11768 Some(w) => {
11769 let mut hn = e.uninit(t * n_embd)?;
11770 e.add_scale_rms_norm(
11771 &sn,
11772 &attn_out,
11773 bits.layer_scale,
11774 w,
11775 &mut xn,
11776 &mut hn,
11777 n_embd,
11778 t,
11779 self.cfg.rms_eps,
11780 )?;
11781 Ok((xn, Some(hn)))
11782 }
11783 None => {
11784 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
11785 Ok((xn, None))
11786 }
11787 }
11788 }
11789
11790 fn gemma4_layer_tail_core(
11793 &self,
11794 e: &Engine,
11795 layer: &crate::hybrid::HybridLayer,
11796 cur: &CudaSlice<f32>,
11797 x: &CudaSlice<f32>,
11798 t: usize,
11799 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11800 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
11801 }
11802
11803 fn gemma4_layer_tail_core_pn(
11810 &self,
11811 e: &Engine,
11812 layer: &crate::hybrid::HybridLayer,
11813 cur: &CudaSlice<f32>,
11814 x: &CudaSlice<f32>,
11815 t: usize,
11816 pre_norm: Option<&CudaSlice<f32>>,
11817 defer_post_norm: bool,
11818 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11819 let n_embd = self.cfg.n_embd as usize;
11820 let eps = self.cfg.rms_eps;
11821 let bits = layer.gemma4.as_ref().unwrap();
11822
11823 let Some(mbits) = bits.moe_bits.as_ref() else {
11826 let crate::hybrid::Ffn::Dense {
11827 ffn_gate,
11828 ffn_up,
11829 ffn_down,
11830 } = &layer.ffn
11831 else {
11832 panic!("gemma4 dense layer without Dense ffn")
11833 };
11834 let mut attn_out = e.uninit(t * n_embd)?;
11835 let mut zsh = e.uninit(t * n_embd)?;
11836 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11839 match pre_norm {
11840 Some(wa) if t == 1 => {
11841 zpair = Some(e.rms_pre_add_rms_norm_q8z(
11842 cur,
11843 wa,
11844 x,
11845 bits.ffn_norm.float_data(),
11846 &mut attn_out,
11847 &mut zsh,
11848 n_embd,
11849 t,
11850 eps,
11851 )?);
11852 }
11853 Some(wa) => e.rms_pre_add_rms_norm(
11854 cur,
11855 wa,
11856 x,
11857 bits.ffn_norm.float_data(),
11858 &mut attn_out,
11859 &mut zsh,
11860 n_embd,
11861 t,
11862 eps,
11863 )?,
11864 None => e.add_rms_norm(
11865 cur,
11866 x,
11867 bits.ffn_norm.float_data(),
11868 &mut attn_out,
11869 &mut zsh,
11870 n_embd,
11871 t,
11872 eps,
11873 )?,
11874 }
11875 let n_ff = ffn_gate.out_features();
11876 let (gate, up) = if t == 1 {
11882 let (zq, zd) = match zpair {
11883 Some(p) => p,
11884 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
11885 };
11886 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
11887 Some(p) => p,
11888 None => match e.matmul_nvfp4_fused2(ffn_gate, ffn_up, &zq, &zd, 1)? {
11890 Some(p) => p,
11891 None => (
11892 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
11893 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
11894 ),
11895 },
11896 }
11897 } else {
11898 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11903 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11904 let fused = if f2b {
11905 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
11906 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
11907 } else {
11908 None
11909 };
11910 match fused {
11911 Some(p) => p,
11912 None => {
11913 e.mmq_act_begin();
11915 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
11916 }
11917 }
11918 };
11919 let mut act = e.uninit(t * n_ff)?;
11920 let f0 = if e.uses_q8_1_fast(ffn_down) {
11923 let upv = e.view(&up, t * n_ff);
11924 let up_all = upv.slice(0..t * n_ff);
11925 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
11926 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
11927 } else {
11928 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11929 e.matmul(ffn_down, &act, t)?
11930 };
11931 if defer_post_norm {
11932 return Ok((f0, attn_out));
11933 }
11934 let mut sn = e.uninit(t * n_embd)?;
11935 e.rms_norm(
11936 &f0,
11937 bits.post_ffw_norm.float_data(),
11938 &mut sn,
11939 n_embd,
11940 t,
11941 eps,
11942 )?;
11943 return Ok((sn, attn_out));
11944 };
11945
11946 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
11947 let mut attn_out = e.uninit(t * n_embd)?;
11952 let mut router_in = e.uninit(t * n_embd)?;
11953 let fast_moe = match &layer.ffn {
11954 crate::hybrid::Ffn::Moe(m) => {
11955 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11956 && expert_dp4a_supported(m.gate_exps.qtype)
11957 && expert_dp4a_supported(m.up_exps.qtype)
11958 && expert_dp4a_supported(m.down_exps.qtype)
11959 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11960 }
11961 _ => false,
11962 };
11963 let q8z = t < PRIME_MIN_T && fast_moe;
11964 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
11965 let (z0, m2) = e.add_rms_norm3_q8z(
11966 cur,
11967 x,
11968 bits.ffn_norm.float_data(),
11969 &mbits.router_scale_pre,
11970 mbits.pre_ffw_norm_2.float_data(),
11971 &mut attn_out,
11972 &mut router_in,
11973 n_embd,
11974 t,
11975 eps,
11976 )?;
11977 (None, Some(z0), Some(m2))
11978 } else {
11979 let mut zsh = e.uninit(t * n_embd)?;
11980 let mut moe_in = e.uninit(t * n_embd)?;
11981 e.add_rms_norm3(
11982 cur,
11983 x,
11984 bits.ffn_norm.float_data(),
11985 &mbits.router_scale_pre,
11986 mbits.pre_ffw_norm_2.float_data(),
11987 &mut attn_out,
11988 &mut zsh,
11989 &mut router_in,
11990 &mut moe_in,
11991 n_embd,
11992 t,
11993 eps,
11994 )?;
11995 (Some((zsh, moe_in)), None, None)
11996 };
11997 let attn_out2 = attn_out;
11998 #[allow(unused_variables)]
11999 let attn_out = &attn_out2;
12000 let n_ff = mbits.shared_gate.out_features();
12001 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
12002 if t == 1 {
12003 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
12004 Some(p) => p,
12005 None => match e.matmul_nvfp4_fused2(
12006 &mbits.shared_gate,
12007 &mbits.shared_up,
12008 zq,
12009 zd,
12010 1,
12011 )? {
12012 Some(p) => p,
12013 None => {
12014 let h0 = e.zeros(0)?;
12015 (
12016 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
12017 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
12018 )
12019 }
12020 },
12021 }
12022 } else {
12023 let h0 = e.zeros(0)?;
12025 (
12026 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
12027 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
12028 )
12029 }
12030 } else {
12031 let (zsh, _) = zsh_f32.as_ref().unwrap();
12032 (
12033 e.matmul(&mbits.shared_gate, zsh, t)?,
12034 e.matmul(&mbits.shared_up, zsh, t)?,
12035 )
12036 };
12037 let mut act = e.uninit(t * n_ff)?;
12038 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
12039 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
12040 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
12041 panic!("gemma4 layer not MoE")
12042 };
12043 let moe0 = match (&moe_q8, &zsh_f32) {
12044 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
12045 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
12046 _ => unreachable!(),
12047 };
12048 let mut mlp = e.uninit(t * n_embd)?;
12050 let mut moe = e.uninit(t * n_embd)?;
12051 e.rms_norm2x(
12052 &mlp0,
12053 &moe0,
12054 mbits.post_ffw_norm_1.float_data(),
12055 mbits.post_ffw_norm_2.float_data(),
12056 &mut mlp,
12057 &mut moe,
12058 n_embd,
12059 t,
12060 eps,
12061 )?;
12062
12063 let mut sum = e.uninit(t * n_embd)?;
12066 let mut sn = e.uninit(t * n_embd)?;
12067 e.add_rms_norm(
12068 &mlp,
12069 &moe,
12070 bits.post_ffw_norm.float_data(),
12071 &mut sum,
12072 &mut sn,
12073 n_embd,
12074 t,
12075 eps,
12076 )?;
12077 Ok((sn, attn_out2))
12078 }
12079
12080 pub(crate) fn gemma4_layer_tail_add_nq_pn(
12090 &self,
12091 e: &Engine,
12092 layer: &crate::hybrid::HybridLayer,
12093 o: &CudaSlice<f32>,
12094 x: &CudaSlice<f32>,
12095 t: usize,
12096 next_norm: Option<&CudaSlice<f32>>,
12097 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12098 {
12099 let n_embd = self.cfg.n_embd as usize;
12100 let eps = self.cfg.rms_eps;
12101 let bits = layer.gemma4.as_ref().unwrap();
12102 if Engine::g4_pnfold_on() && matches!(layer.ffn, crate::hybrid::Ffn::Dense { .. }) {
12103 let (f0, attn_out) = self.gemma4_layer_tail_core_pn(
12104 e,
12105 layer,
12106 o,
12107 x,
12108 t,
12109 Some(layer.post_attn_norm.float_data()),
12110 true,
12111 )?;
12112 let mut xn = e.uninit(t * n_embd)?;
12113 return match next_norm {
12114 Some(w) => {
12115 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
12116 &f0,
12117 bits.post_ffw_norm.float_data(),
12118 &attn_out,
12119 bits.layer_scale,
12120 w,
12121 &mut xn,
12122 n_embd,
12123 t,
12124 eps,
12125 )?;
12126 Ok((xn, Some(pair)))
12127 }
12128 None => {
12129 let mut sn = e.uninit(t * n_embd)?;
12130 e.rms_norm(
12131 &f0,
12132 bits.post_ffw_norm.float_data(),
12133 &mut sn,
12134 n_embd,
12135 t,
12136 eps,
12137 )?;
12138 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12139 Ok((xn, None))
12140 }
12141 };
12142 }
12143 let mut cur = e.uninit(t * n_embd)?;
12144 e.rms_norm(
12145 o,
12146 layer.post_attn_norm.float_data(),
12147 &mut cur,
12148 n_embd,
12149 t,
12150 eps,
12151 )?;
12152 self.gemma4_layer_tail_add_nq(e, layer, &cur, x, t, next_norm)
12153 }
12154
12155 pub(crate) fn gemma4_layer_tail_add_nq(
12156 &self,
12157 e: &Engine,
12158 layer: &crate::hybrid::HybridLayer,
12159 cur: &CudaSlice<f32>,
12160 x: &CudaSlice<f32>,
12161 t: usize,
12162 next_norm: Option<&CudaSlice<f32>>,
12163 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12164 {
12165 let n_embd = self.cfg.n_embd as usize;
12166 let bits = layer.gemma4.as_ref().unwrap();
12167 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12168 let mut xn = e.uninit(t * n_embd)?;
12169 match next_norm {
12170 Some(w) => {
12171 let pair = e.add_scale_rms_norm_q8_1(
12172 &sn,
12173 &attn_out,
12174 bits.layer_scale,
12175 w,
12176 &mut xn,
12177 n_embd,
12178 t,
12179 self.cfg.rms_eps,
12180 )?;
12181 Ok((xn, Some(pair)))
12182 }
12183 None => {
12184 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12185 Ok((xn, None))
12186 }
12187 }
12188 }
12189
12190 fn gemma4_forward(
12193 &self,
12194 e: &Engine,
12195 tokens: &[u32],
12196 last_only: bool,
12197 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12198 if self.is_gemma4_e4b() {
12201 return self.gemma4_e4b_forward(e, tokens, last_only);
12202 }
12203 let n_embd = self.cfg.n_embd as usize;
12204 let t = tokens.len();
12205 let pos: Vec<i32> = (0..t as i32).collect();
12206 let pos_d = e.htod_i32(&pos)?;
12207
12208 let mut x = self.embed(e, tokens)?;
12209 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12210 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
12213 let stat =
12214 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
12215 let h = e.dtoh(x)?;
12216 let bad = h.iter().filter(|v| !v.is_finite()).count();
12217 let mx = h
12218 .iter()
12219 .filter(|v| v.is_finite())
12220 .fold(0.0f32, |m, v| m.max(v.abs()));
12221 eprintln!(
12222 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
12223 &h[..3]
12224 );
12225 Ok(())
12226 };
12227 if probe {
12228 stat(e, &x, "embed")?;
12229 }
12230 for (il, layer) in self.layers.iter().enumerate() {
12231 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
12232 if probe {
12233 stat(e, &x, &format!("L{il}"))?;
12234 }
12235 }
12236 let mut hn = e.zeros(t * n_embd)?;
12237 e.rms_norm(
12238 &x,
12239 self.output_norm.float_data(),
12240 &mut hn,
12241 n_embd,
12242 t,
12243 self.cfg.rms_eps,
12244 )?;
12245 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12246 let n_vocab = self.output.out_features();
12247 let logits = if last_only {
12248 let hv = e.view(&hn, t * n_embd);
12249 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
12250 let mut hlast = e.zeros(n_embd)?;
12251 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
12252 let mut ld = e.matmul(&self.output, &hlast, 1)?;
12253 e.softcap(&mut ld, cap, n_vocab)?;
12254 self.gemma4_suppress(e, &mut ld, 1)?;
12255 e.dtoh(&ld)?
12256 } else {
12257 let mut ld = e.matmul(&self.output, &hn, t)?;
12258 e.softcap(&mut ld, cap, t * n_vocab)?;
12259 self.gemma4_suppress(e, &mut ld, t)?;
12260 e.dtoh(&ld)?
12261 };
12262 Ok(logits)
12263 }
12264
12265 pub(crate) fn gemma4_prime(
12270 &self,
12271 e: &Engine,
12272 tokens: &[u32],
12273 cache: &mut Cache,
12274 overlay: Option<&crate::vision::EmbedOverlay>,
12275 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12276 if cache.pos != 0 {
12281 return Err(
12282 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
12283 — prime the full prompt in one call or decode tokenwise"
12284 .into(),
12285 );
12286 }
12287 let n_embd = self.cfg.n_embd as usize;
12288 let eps = self.cfg.rms_eps;
12289 let t = tokens.len();
12290 let pos: Vec<i32> = (0..t as i32).collect();
12291 let pos_d = e.htod_i32(&pos)?;
12292 let mut x = self.embed(e, tokens)?;
12293 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12294 let island: Option<CudaSlice<i32>> = match overlay {
12301 Some(ov) => {
12302 let mut span_id = vec![-1i32; t];
12303 for (i, &(pos, row_off, n_rows)) in ov.spans.iter().enumerate() {
12304 if pos + n_rows > t {
12305 return Err(format!(
12306 "gemma4 overlay span {i} [{pos}, {}) exceeds the prompt ({t})",
12307 pos + n_rows
12308 )
12309 .into());
12310 }
12311 let view = ov.rows.slice(row_off * n_embd..(row_off + n_rows) * n_embd);
12312 e.copy_view_into(&mut x, pos * n_embd, &view, n_rows * n_embd)?;
12313 for s in span_id.iter_mut().skip(pos).take(n_rows) {
12314 *s = i as i32;
12315 }
12316 }
12317 if std::env::var("MEMRA_GV_FORCE_CAUSAL").as_deref() == Ok("1") {
12321 None
12322 } else {
12323 Some(e.htod_i32(&span_id)?)
12324 }
12325 }
12326 None => None,
12327 };
12328 for (il, layer) in self.layers.iter().enumerate() {
12329 let mut h = e.zeros(t * n_embd)?;
12330 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12331 let Mixer::Full(fa) = &layer.mixer else {
12332 panic!("gemma4 layer not full-attn")
12333 };
12334 let trace = il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1");
12335 if trace {
12336 let v = e.dtoh(&h)?;
12337 let nan = v.iter().filter(|x| x.is_nan()).count();
12338 eprintln!("[g4-prime-trace] L0 post-attn_norm: nan={nan}/{}", v.len());
12339 }
12340 let o =
12341 self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache), island.as_ref())?;
12342 if trace {
12343 let v = e.dtoh(&o)?;
12344 let nan = v.iter().filter(|x| x.is_nan()).count();
12345 eprintln!("[g4-prime-trace] L0 post-attn: nan={nan}/{}", v.len());
12346 }
12347 let mut cur = e.zeros(t * n_embd)?;
12348 e.rms_norm(
12349 &o,
12350 layer.post_attn_norm.float_data(),
12351 &mut cur,
12352 n_embd,
12353 t,
12354 eps,
12355 )?;
12356 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
12357 self.dflash_tap(e, cache, il, &x, t)?;
12358 if std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
12360 let h = e.dtoh(&x)?;
12361 let nan = h.iter().filter(|v| v.is_nan()).count();
12362 let amax = h.iter().fold(0f32, |a, v| a.max(v.abs()));
12363 eprintln!(
12364 "[g4-prime-trace] layer {il}: nan={nan}/{} amax={amax:.3}",
12365 h.len()
12366 );
12367 if nan > 0 {
12368 return Err(format!("g4-prime-trace: first NaN at layer {il}").into());
12369 }
12370 }
12371 }
12372 cache.pos += t;
12373 let hiddens = e.clone_dtod(&x)?;
12374 let xv = e.view(&x, t * n_embd);
12375 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
12376 let mut h_seed = e.zeros(n_embd)?;
12377 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
12378 let mut hn = e.uninit(n_embd)?;
12379 e.rms_norm(
12380 &h_seed,
12381 self.output_norm.float_data(),
12382 &mut hn,
12383 n_embd,
12384 1,
12385 eps,
12386 )?;
12387 let mut ld = e.matmul(&self.output, &hn, 1)?;
12388 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12389 e.softcap(&mut ld, cap, self.output.out_features())?;
12390 self.gemma4_suppress(e, &mut ld, 1)?;
12391 let logits = e.dtoh(&ld)?;
12392 Ok((logits, h_seed, hiddens))
12393 }
12394
12395 fn gemma4_decode_attn(
12400 &self,
12401 e: &Engine,
12402 fa: &crate::hybrid::FullAttnLayer,
12403 il: usize,
12404 hq: &CudaSlice<i8>,
12405 hdq: &CudaSlice<f32>,
12406 pos_d: &CudaSlice<i32>,
12407 cache: &mut Cache,
12408 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12409 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12410 let eps = self.cfg.rms_eps;
12411 let aux = self.gemma4_aux.as_ref().unwrap();
12412 let ones = aux.ones(e);
12413 #[cfg(debug_assertions)]
12414 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
12415 let (hq, hdq) = (hq, hdq);
12416 let h0 = e.zeros(0)?;
12417 let h = &h0;
12418 let (q0, k0, v0) = if swa {
12419 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12420 Some(t3) => t3,
12421 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12424 Some((q0, k0)) => {
12425 let v0 = e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?;
12426 (q0, k0, v0)
12427 }
12428 None => (
12429 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12430 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12431 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12432 ),
12433 },
12434 }
12435 } else {
12436 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
12437 Some(p) => p,
12438 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12439 Some(p) => p,
12440 None => (
12441 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12442 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12443 ),
12444 },
12445 };
12446 let v0 = e.clone_dtod(&k0)?;
12447 (q0, k0, v0)
12448 };
12449 let mut q = e.uninit(nh * hd)?;
12450 let mut k = e.uninit(nkv * hd)?;
12451 let mut v = e.uninit(nkv * hd)?;
12452 let ff = if swa {
12455 None
12456 } else {
12457 Some(
12458 aux.rope_freqs(e)
12459 .expect("gemma4 global rope needs rope_freqs.weight"),
12460 )
12461 };
12462 #[cfg(debug_assertions)]
12463 if let Some(ff) = ff {
12464 crate::debug_assert_tensor_stream_device(
12465 ff,
12466 &e.stream(),
12467 "gemma4_decode_attn.rope_freqs",
12468 );
12469 }
12470 let kvl = cache.kv[il].as_mut().unwrap();
12471 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12472 if crate::Engine::qkv_append_on() {
12473 e.rms_norm_qkv_rope_append(
12477 &q0,
12478 &k0,
12479 &v0,
12480 fa.q_norm.float_data(),
12481 fa.k_norm.float_data(),
12482 ones,
12483 &mut q,
12484 &mut k,
12485 &mut v,
12486 hd,
12487 self.gemma4_rope_dims(il),
12488 nh,
12489 nkv,
12490 pos_d,
12491 nh,
12492 nkv,
12493 base,
12494 1.0,
12495 ff,
12496 eps,
12497 &mut kvl.k,
12498 &mut kvl.v,
12499 kvl.len,
12500 kvl.k_tok_bytes,
12501 kvl.v_tok_bytes,
12502 kv_fp8,
12503 )?;
12504 } else {
12505 e.rms_norm_qkv_rope(
12506 &q0,
12507 &k0,
12508 &v0,
12509 fa.q_norm.float_data(),
12510 fa.k_norm.float_data(),
12511 ones,
12512 &mut q,
12513 &mut k,
12514 &mut v,
12515 hd,
12516 self.gemma4_rope_dims(il),
12517 nh,
12518 nkv,
12519 pos_d,
12520 nh,
12521 nkv,
12522 base,
12523 1.0,
12524 ff,
12525 eps,
12526 )?;
12527 e.append_kv_quantized(
12528 &k,
12529 &v,
12530 &mut kvl.k,
12531 &mut kvl.v,
12532 kvl.len,
12533 kvl.kv_dim_k,
12534 kvl.kv_dim_v,
12535 kvl.k_tok_bytes,
12536 kvl.v_tok_bytes,
12537 kv_fp8,
12538 )?;
12539 }
12540 kvl.len += 1;
12541 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12545 let mut attn = e.uninit(nh * hd)?;
12546 if !swa
12548 && hd == 512
12549 && kvl.len >= crate::fa512_min_tkv()
12550 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12551 {
12552 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12553 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12554 let base = kvl.len as i32;
12556 e.i32_set_k(&mut kvl.len_d, base)?;
12557 e.fa_decode_rows(
12558 &q,
12559 &kp,
12560 &vp,
12561 &mut attn,
12562 hd,
12563 nh,
12564 nkv,
12565 kvl.len - 1,
12566 1,
12567 scale,
12568 kvl.k_tok_bytes,
12569 kvl.v_tok_bytes,
12570 Some((&kvl.len_d, -1)),
12571 false,
12572 false,
12573 None,
12574 )?;
12575 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12576 }
12577 if swa
12579 && kvl.len > win
12580 && hd == 256
12581 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12582 {
12583 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12584 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12585 let base = kvl.len as i32;
12586 e.i32_set_k(&mut kvl.len_d, base)?;
12587 e.fa_decode_rows_w(
12588 &q,
12589 &kp,
12590 &vp,
12591 &mut attn,
12592 hd,
12593 nh,
12594 nkv,
12595 &kvl.len_d,
12596 -1,
12597 1,
12598 scale,
12599 win,
12600 kvl.k_tok_bytes,
12601 kvl.v_tok_bytes,
12602 None,
12603 )?;
12604 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12605 }
12606 let (off_tok, t_kv) = if swa && kvl.len > win {
12607 (kvl.len - win, win)
12608 } else {
12609 (0, kvl.len)
12610 };
12611 let k_view = e.view_u8_range(
12612 &kvl.k,
12613 off_tok * kvl.k_tok_bytes,
12614 (off_tok + t_kv) * kvl.k_tok_bytes,
12615 );
12616 let v_view = e.view_u8_range(
12617 &kvl.v,
12618 off_tok * kvl.v_tok_bytes,
12619 (off_tok + t_kv) * kvl.v_tok_bytes,
12620 );
12621 e.fa_decode_kvmod(
12622 &q,
12623 &k_view,
12624 &v_view,
12625 &mut attn,
12626 hd,
12627 nh,
12628 nkv,
12629 t_kv,
12630 scale,
12631 kvl.k_tok_bytes,
12632 kvl.v_tok_bytes,
12633 swa && crate::Engine::wkv_on(),
12634 )?;
12635 Ok(e.matmul(&fa.wo, &attn, 1)?)
12636 }
12637
12638 #[allow(clippy::too_many_arguments)]
12645 pub fn gemma4_decode_step_dc(
12646 &self,
12647 e: &Engine,
12648 token_d: &CudaSlice<u32>,
12649 pos_d: &mut CudaSlice<i32>,
12650 embd_gpu: &CudaSlice<u8>,
12651 embd_qt: i32,
12652 embd_rb: usize,
12653 cache: &mut Cache,
12654 n_vocab: usize,
12655 cap_bucket_max: Option<(usize, usize)>,
12656 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
12657 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
12658 self.gemma4_decode_step_dc_into(
12659 e,
12660 token_d,
12661 pos_d,
12662 embd_gpu,
12663 embd_qt,
12664 embd_rb,
12665 cache,
12666 n_vocab,
12667 cap_bucket_max,
12668 &mut tok_out,
12669 )?;
12670 Ok(tok_out)
12671 }
12672
12673 #[allow(clippy::too_many_arguments)]
12676 pub fn gemma4_decode_step_dc_into(
12677 &self,
12678 e: &Engine,
12679 token_d: &CudaSlice<u32>,
12680 pos_d: &mut CudaSlice<i32>,
12681 embd_gpu: &CudaSlice<u8>,
12682 embd_qt: i32,
12683 embd_rb: usize,
12684 cache: &mut Cache,
12685 n_vocab: usize,
12686 cap_bucket_max: Option<(usize, usize)>,
12687 tok_out: &mut CudaSlice<u32>,
12688 ) -> Result<(), Box<dyn std::error::Error>> {
12689 let n_embd = self.cfg.n_embd as usize;
12690 let eps = self.cfg.rms_eps;
12691 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
12692 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12693 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12694 let n_layers = self.layers.len();
12695 for (il, layer) in self.layers.iter().enumerate() {
12696 let (hq, hdq) = match h_carry.take() {
12697 Some(p) => p,
12698 None => {
12699 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
12700 }
12701 };
12702 let Mixer::Full(fa) = &layer.mixer else {
12703 panic!("gemma4 layer {il} not full-attn")
12704 };
12705 let o =
12706 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
12707 let next_norm = if il + 1 < n_layers {
12708 Some(self.layers[il + 1].attn_norm.float_data())
12709 } else {
12710 None
12711 };
12712 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
12713 x = xn;
12714 h_carry = hn;
12715 }
12716 let mut hn = e.uninit(n_embd)?;
12717 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12718 let mut logits = e.matmul(&self.output, &hn, 1)?;
12719 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
12721 e.inc_seqlen(pos_d)?;
12722 if cap_bucket_max.is_none() {
12723 cache.pos += 1;
12724 }
12725 Ok(())
12726 }
12727
12728 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
12735 let n_embd = self.cfg.n_embd as usize;
12736 let n_vocab = self.output.out_features();
12737 let n_layers = self.layers.len();
12738 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
12739 for il in 0..n_layers {
12740 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
12741 qmax = qmax.max(nh * hd);
12742 kvmax = kvmax.max(nkv * hd);
12743 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
12744 ffmax = ffmax.max(ffn_gate.out_features());
12745 }
12746 }
12747 Ok(G4DcSlots {
12748 x: e.uninit(n_embd)?,
12749 xn: e.uninit(n_embd)?,
12750 cur: e.uninit(n_embd)?,
12751 hq: e.alloc_i8_uninit(n_embd)?,
12752 hd_: e.uninit(n_embd / 32)?,
12753 q0: e.uninit(qmax)?,
12754 k0: e.uninit(kvmax)?,
12755 v0: e.uninit(kvmax)?,
12756 q: e.uninit(qmax)?,
12757 k: e.uninit(kvmax)?,
12758 v: e.uninit(kvmax)?,
12759 attn: e.uninit(qmax)?,
12760 o: e.uninit(n_embd)?,
12761 attn_out: e.uninit(n_embd)?,
12762 zsh: e.uninit(n_embd)?,
12763 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
12766 zd: e.uninit(n_embd.max(qmax) / 32)?,
12767 gate: e.uninit(ffmax)?,
12768 up: e.uninit(ffmax)?,
12769 act: e.uninit(ffmax)?,
12770 actq: e.alloc_i8_uninit(ffmax)?,
12771 actd: e.uninit(ffmax / 32)?,
12772 f0: e.uninit(n_embd)?,
12773 sn: e.uninit(n_embd)?,
12774 hn: e.uninit(n_embd)?,
12775 logits: e.uninit(n_vocab)?,
12776 })
12777 }
12778
12779 fn g4_matvec_m1_into(
12782 &self,
12783 e: &Engine,
12784 w: &crate::model::GpuTensor,
12785 aq: &CudaSlice<i8>,
12786 ad: &CudaSlice<f32>,
12787 y: &mut CudaSlice<f32>,
12788 ) -> Result<(), Box<dyn std::error::Error>> {
12789 use crate::model::GpuTensor;
12790 let (bytes, qtype, row_bytes, scale, rp) = match w {
12791 GpuTensor::Quant {
12792 bytes,
12793 qtype,
12794 row_bytes,
12795 scale,
12796 rp,
12797 ..
12798 } => (bytes, *qtype, *row_bytes, *scale, *rp),
12799 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
12800 };
12801 let (mbytes, mrp) = match w {
12802 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12803 _ => (bytes, rp),
12804 };
12805 e.qmatvec_mmvq_into(
12806 mbytes,
12807 aq,
12808 ad,
12809 1,
12810 w.in_features(),
12811 w.out_features(),
12812 qtype,
12813 row_bytes,
12814 scale,
12815 mrp,
12816 y,
12817 )
12818 }
12819
12820 #[allow(clippy::too_many_arguments)]
12824 pub fn gemma4_decode_step_dc_slotted(
12825 &self,
12826 e: &Engine,
12827 token_d: &CudaSlice<u32>,
12828 pos_d: &mut CudaSlice<i32>,
12829 embd_gpu: &CudaSlice<u8>,
12830 embd_qt: i32,
12831 embd_rb: usize,
12832 cache: &mut Cache,
12833 n_vocab: usize,
12834 cap_bucket_max: Option<(usize, usize)>,
12835 sl: &mut G4DcSlots,
12836 tok_out: &mut CudaSlice<u32>,
12837 ring: Option<(&mut CudaSlice<u32>, usize)>,
12838 ) -> Result<(), Box<dyn std::error::Error>> {
12839 let n_embd = self.cfg.n_embd as usize;
12840 let eps = self.cfg.rms_eps;
12841 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
12842 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
12843 let n_layers = self.layers.len();
12844 let mut has_carry = false;
12845 for il in 0..n_layers {
12846 if !has_carry {
12847 e.rms_norm_q8_1_into(
12848 &sl.x,
12849 self.layers[il].attn_norm.float_data(),
12850 n_embd,
12851 1,
12852 eps,
12853 &mut sl.hq,
12854 &mut sl.hd_,
12855 )?;
12856 }
12857 has_carry = true;
12858 let layer = &self.layers[il];
12859 let Mixer::Full(fa) = &layer.mixer else {
12860 panic!("gemma4 layer {il} not full-attn")
12861 };
12862 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
12863 if !Engine::g4_pnfold_on() {
12866 e.rms_norm(
12867 &sl.o,
12868 layer.post_attn_norm.float_data(),
12869 &mut sl.cur,
12870 n_embd,
12871 1,
12872 eps,
12873 )?;
12874 }
12875 let next_norm = if il + 1 < n_layers {
12876 Some(self.layers[il + 1].attn_norm.float_data())
12877 } else {
12878 None
12879 };
12880 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
12881 std::mem::swap(&mut sl.x, &mut sl.xn);
12882 }
12883 e.rms_norm(
12884 &sl.x,
12885 self.output_norm.float_data(),
12886 &mut sl.hn,
12887 n_embd,
12888 1,
12889 eps,
12890 )?;
12891 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
12892 {
12894 let (zq, zd) = (&sl.zq, &sl.zd);
12895 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
12896 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
12897 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
12898 }
12899 self.gemma4_suppress(e, &mut sl.logits, 1)?;
12900 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
12901 if let Some((ring, base)) = ring {
12902 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
12906 }
12907 e.inc_seqlen(pos_d)?;
12908 if cap_bucket_max.is_none() {
12909 cache.pos += 1;
12910 }
12911 Ok(())
12912 }
12913
12914 #[allow(clippy::too_many_arguments)]
12916 fn gemma4_decode_attn_dc_slotted(
12917 &self,
12918 e: &Engine,
12919 fa: &crate::hybrid::FullAttnLayer,
12920 il: usize,
12921 pos_d: &CudaSlice<i32>,
12922 cache: &mut Cache,
12923 cap_bucket_max: Option<(usize, usize)>,
12924 sl: &mut G4DcSlots,
12925 ) -> Result<(), Box<dyn std::error::Error>> {
12926 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12927 let eps = self.cfg.rms_eps;
12928 let aux = self.gemma4_aux.as_ref().unwrap();
12929 let ones = aux.ones(e);
12930 #[cfg(debug_assertions)]
12931 crate::debug_assert_tensor_stream_device(
12932 ones,
12933 &e.stream(),
12934 "gemma4_decode_attn_dc_slotted.ones",
12935 );
12936 {
12937 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
12938 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
12939 if swa {
12940 if !e.matmul_q4_fused3_into(
12941 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
12942 )? {
12943 if e.matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12947 {
12948 self.g4_matvec_m1_into(e, &fa.wv, hq, hdq, &mut sl.v0)?;
12949 } else {
12950 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
12951 }
12952 }
12953 } else {
12954 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12955 && !e
12956 .matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12957 {
12958 return Err("slotted step: fused2 unavailable".into());
12959 }
12960 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
12961 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
12962 }
12963 }
12964 let ff = if swa {
12967 None
12968 } else {
12969 Some(
12970 aux.rope_freqs(e)
12971 .expect("gemma4 global rope needs rope_freqs.weight"),
12972 )
12973 };
12974 #[cfg(debug_assertions)]
12975 if let Some(ff) = ff {
12976 crate::debug_assert_tensor_stream_device(
12977 ff,
12978 &e.stream(),
12979 "gemma4_decode_attn_dc_slotted.rope_freqs",
12980 );
12981 }
12982 let kvl = cache.kv[il].as_mut().unwrap();
12983 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12984 if crate::Engine::qkv_append_on() {
12985 e.rms_norm_qkv_rope_append_dc(
12987 &sl.q0,
12988 &sl.k0,
12989 &sl.v0,
12990 fa.q_norm.float_data(),
12991 fa.k_norm.float_data(),
12992 ones,
12993 &mut sl.q,
12994 &mut sl.k,
12995 &mut sl.v,
12996 hd,
12997 self.gemma4_rope_dims(il),
12998 nh,
12999 nkv,
13000 pos_d,
13001 nh,
13002 nkv,
13003 base,
13004 1.0,
13005 ff,
13006 eps,
13007 &mut kvl.k,
13008 &mut kvl.v,
13009 &kvl.len_d,
13010 kvl.k_tok_bytes,
13011 kvl.v_tok_bytes,
13012 kv_fp8,
13013 )?;
13014 } else {
13015 e.rms_norm_qkv_rope(
13016 &sl.q0,
13017 &sl.k0,
13018 &sl.v0,
13019 fa.q_norm.float_data(),
13020 fa.k_norm.float_data(),
13021 ones,
13022 &mut sl.q,
13023 &mut sl.k,
13024 &mut sl.v,
13025 hd,
13026 self.gemma4_rope_dims(il),
13027 nh,
13028 nkv,
13029 pos_d,
13030 nh,
13031 nkv,
13032 base,
13033 1.0,
13034 ff,
13035 eps,
13036 )?;
13037 e.append_kv_quantized_dc(
13038 &sl.k,
13039 &sl.v,
13040 &mut kvl.k,
13041 &mut kvl.v,
13042 &kvl.len_d,
13043 kvl.kv_dim_k,
13044 kvl.kv_dim_v,
13045 kvl.k_tok_bytes,
13046 kvl.v_tok_bytes,
13047 kv_fp8,
13048 )?;
13049 }
13050 e.inc_seqlen(&mut kvl.len_d)?;
13051 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
13052 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13053 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13054 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13055 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13056 let mut fa_q8 = false;
13060 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13061 e.fa_decode_rows(
13062 &sl.q,
13063 &k_view,
13064 &v_view,
13065 &mut sl.attn,
13066 hd,
13067 nh,
13068 nkv,
13069 b_glob - 1,
13070 1,
13071 scale,
13072 kvl.k_tok_bytes,
13073 kvl.v_tok_bytes,
13074 Some((&kvl.len_d, -1)),
13075 false,
13076 false,
13077 Some((&mut sl.zq, &mut sl.zd)),
13078 )?;
13079 fa_q8 = true;
13080 } else if swa && b_swa > win && hd == 256 && rows_on {
13081 e.fa_decode_rows_w(
13082 &sl.q,
13083 &k_view,
13084 &v_view,
13085 &mut sl.attn,
13086 hd,
13087 nh,
13088 nkv,
13089 &kvl.len_d,
13090 -1,
13091 1,
13092 scale,
13093 win,
13094 kvl.k_tok_bytes,
13095 kvl.v_tok_bytes,
13096 Some((&mut sl.zq, &mut sl.zd)),
13097 )?;
13098 fa_q8 = true;
13099 } else {
13100 let b = if swa { b_swa } else { b_glob };
13101 e.fa_decode_dc(
13102 &sl.q,
13103 &k_view,
13104 &v_view,
13105 &mut sl.attn,
13106 hd,
13107 nh,
13108 nkv,
13109 &kvl.len_d,
13110 b,
13111 scale,
13112 kvl.k_tok_bytes,
13113 kvl.v_tok_bytes,
13114 swa && crate::Engine::wkv_on(),
13115 )?;
13116 }
13117 if !fa_q8 {
13118 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
13119 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
13120 }
13121 {
13122 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13123 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13124 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
13125 }
13126 Ok(())
13127 }
13128
13129 fn gemma4_layer_tail_slotted(
13132 &self,
13133 e: &Engine,
13134 layer: &crate::hybrid::HybridLayer,
13135 next_norm: Option<&CudaSlice<f32>>,
13136 sl: &mut G4DcSlots,
13137 ) -> Result<(), Box<dyn std::error::Error>> {
13138 let n_embd = self.cfg.n_embd as usize;
13139 let eps = self.cfg.rms_eps;
13140 let bits = layer.gemma4.as_ref().unwrap();
13141 let crate::hybrid::Ffn::Dense {
13142 ffn_gate,
13143 ffn_up,
13144 ffn_down,
13145 } = &layer.ffn
13146 else {
13147 return Err("slotted tail: dense ffn only".into());
13148 };
13149 let pnfold = Engine::g4_pnfold_on();
13150 if pnfold {
13151 let or = unsafe { &*(&sl.o as *const CudaSlice<f32>) };
13154 let xr = unsafe { &*(&sl.x as *const CudaSlice<f32>) };
13155 e.rms_pre_add_rms_norm_q8z_into(
13156 or,
13157 layer.post_attn_norm.float_data(),
13158 xr,
13159 bits.ffn_norm.float_data(),
13160 &mut sl.attn_out,
13161 &mut sl.zsh,
13162 n_embd,
13163 1,
13164 eps,
13165 &mut sl.zq,
13166 &mut sl.zd,
13167 )?;
13168 } else {
13169 e.add_rms_norm(
13170 &sl.cur,
13171 &sl.x,
13172 bits.ffn_norm.float_data(),
13173 &mut sl.attn_out,
13174 &mut sl.zsh,
13175 n_embd,
13176 1,
13177 eps,
13178 )?;
13179 }
13180 let n_ff = ffn_gate.out_features();
13181 if !pnfold {
13182 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
13183 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13184 }
13185 {
13186 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13187 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13188 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)?
13189 && !e.matmul_nvfp4_fused2_into(
13190 ffn_gate,
13191 ffn_up,
13192 zq,
13193 zd,
13194 &mut sl.gate,
13195 &mut sl.up,
13196 )?
13197 {
13198 return Err("slotted tail: ffn fused2 unavailable".into());
13199 }
13200 }
13201 debug_assert!(e.uses_q8_1_fast(ffn_down));
13202 {
13203 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
13204 let upv = e.view(upr, n_ff);
13205 let up_all = upv.slice(0..n_ff);
13206 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
13207 e.gelu_tanh_mul_q8_1_into(
13208 gr,
13209 &up_all,
13210 &mut sl.act,
13211 n_ff,
13212 1,
13213 &mut sl.actq,
13214 &mut sl.actd,
13215 )?;
13216 }
13217 {
13218 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
13219 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
13220 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
13221 }
13222 if pnfold {
13223 if let Some(w) = next_norm {
13226 let f0r = unsafe { &*(&sl.f0 as *const CudaSlice<f32>) };
13227 let aor = unsafe { &*(&sl.attn_out as *const CudaSlice<f32>) };
13228 e.rms_pre_add_scale_rms_norm_q8_1_into(
13229 f0r,
13230 bits.post_ffw_norm.float_data(),
13231 aor,
13232 bits.layer_scale,
13233 w,
13234 &mut sl.xn,
13235 n_embd,
13236 1,
13237 eps,
13238 &mut sl.hq,
13239 &mut sl.hd_,
13240 )?;
13241 return Ok(());
13242 }
13243 }
13244 e.rms_norm(
13245 &sl.f0,
13246 bits.post_ffw_norm.float_data(),
13247 &mut sl.sn,
13248 n_embd,
13249 1,
13250 eps,
13251 )?;
13252 match next_norm {
13253 Some(w) => {
13254 e.add_scale_rms_norm_q8_1_into(
13255 &sl.sn,
13256 &sl.attn_out,
13257 bits.layer_scale,
13258 w,
13259 &mut sl.xn,
13260 n_embd,
13261 1,
13262 eps,
13263 &mut sl.hq,
13264 &mut sl.hd_,
13265 )?;
13266 }
13267 None => {
13268 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
13269 }
13270 }
13271 Ok(())
13272 }
13273
13274 #[allow(clippy::too_many_arguments)]
13276 fn gemma4_decode_attn_dc(
13277 &self,
13278 e: &Engine,
13279 fa: &crate::hybrid::FullAttnLayer,
13280 il: usize,
13281 hq: &CudaSlice<i8>,
13282 hdq: &CudaSlice<f32>,
13283 pos_d: &CudaSlice<i32>,
13284 cache: &mut Cache,
13285 cap_bucket_max: Option<(usize, usize)>,
13286 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13287 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13288 let eps = self.cfg.rms_eps;
13289 let aux = self.gemma4_aux.as_ref().unwrap();
13290 let ones = aux.ones(e);
13291 #[cfg(debug_assertions)]
13292 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
13293 let (q0, k0, v0) = if swa {
13294 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
13295 Some(t3) => t3,
13296 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13298 Some((q0, k0)) => {
13299 let h0 = e.zeros(0)?;
13300 let v0 = e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?;
13301 (q0, k0, v0)
13302 }
13303 None => {
13304 let h0 = e.zeros(0)?;
13305 (
13306 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13307 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13308 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
13309 )
13310 }
13311 },
13312 }
13313 } else {
13314 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
13315 Some(p) => p,
13316 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13317 Some(p) => p,
13318 None => {
13319 let h0 = e.zeros(0)?;
13320 (
13321 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13322 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13323 )
13324 }
13325 },
13326 };
13327 let v0 = e.clone_dtod(&k0)?;
13328 (q0, k0, v0)
13329 };
13330 let mut q = e.uninit(nh * hd)?;
13331 let mut k = e.uninit(nkv * hd)?;
13332 let mut v = e.uninit(nkv * hd)?;
13333 let ff = if swa {
13335 None
13336 } else {
13337 Some(
13338 aux.rope_freqs(e)
13339 .expect("gemma4 global rope needs rope_freqs.weight"),
13340 )
13341 };
13342 #[cfg(debug_assertions)]
13343 if let Some(ff) = ff {
13344 crate::debug_assert_tensor_stream_device(
13345 ff,
13346 &e.stream(),
13347 "gemma4_decode_attn_dc.rope_freqs",
13348 );
13349 }
13350 let kvl = cache.kv[il].as_mut().unwrap();
13351 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13352 if crate::Engine::qkv_append_on() {
13353 e.rms_norm_qkv_rope_append_dc(
13355 &q0,
13356 &k0,
13357 &v0,
13358 fa.q_norm.float_data(),
13359 fa.k_norm.float_data(),
13360 ones,
13361 &mut q,
13362 &mut k,
13363 &mut v,
13364 hd,
13365 self.gemma4_rope_dims(il),
13366 nh,
13367 nkv,
13368 pos_d,
13369 nh,
13370 nkv,
13371 base,
13372 1.0,
13373 ff,
13374 eps,
13375 &mut kvl.k,
13376 &mut kvl.v,
13377 &kvl.len_d,
13378 kvl.k_tok_bytes,
13379 kvl.v_tok_bytes,
13380 kv_fp8,
13381 )?;
13382 } else {
13383 e.rms_norm_qkv_rope(
13384 &q0,
13385 &k0,
13386 &v0,
13387 fa.q_norm.float_data(),
13388 fa.k_norm.float_data(),
13389 ones,
13390 &mut q,
13391 &mut k,
13392 &mut v,
13393 hd,
13394 self.gemma4_rope_dims(il),
13395 nh,
13396 nkv,
13397 pos_d,
13398 nh,
13399 nkv,
13400 base,
13401 1.0,
13402 ff,
13403 eps,
13404 )?;
13405 e.append_kv_quantized_dc(
13406 &k,
13407 &v,
13408 &mut kvl.k,
13409 &mut kvl.v,
13410 &kvl.len_d,
13411 kvl.kv_dim_k,
13412 kvl.kv_dim_v,
13413 kvl.k_tok_bytes,
13414 kvl.v_tok_bytes,
13415 kv_fp8,
13416 )?;
13417 }
13418 e.inc_seqlen(&mut kvl.len_d)?;
13419 let mut attn = e.uninit(nh * hd)?;
13420 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13423 match cap_bucket_max {
13428 None => {
13429 kvl.len += 1;
13433 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13434 if !swa
13435 && hd == 512
13436 && kvl.len >= crate::fa512_min_tkv()
13437 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13438 {
13439 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13442 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13443 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13444 e.fa_decode_rows(
13445 &q,
13446 &kp,
13447 &vp,
13448 &mut attn,
13449 hd,
13450 nh,
13451 nkv,
13452 kvl.len - 1,
13453 1,
13454 scale,
13455 kvl.k_tok_bytes,
13456 kvl.v_tok_bytes,
13457 Some((&kvl.len_d, -1)),
13458 false,
13459 false,
13460 Some((&mut aq8, &mut ad8)),
13461 )?;
13462 fa_q8 = Some((aq8, ad8));
13463 } else if swa
13464 && kvl.len > win
13465 && hd == 256
13466 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13467 {
13468 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13470 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13471 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13472 e.fa_decode_rows_w(
13473 &q,
13474 &kp,
13475 &vp,
13476 &mut attn,
13477 hd,
13478 nh,
13479 nkv,
13480 &kvl.len_d,
13481 -1,
13482 1,
13483 scale,
13484 win,
13485 kvl.k_tok_bytes,
13486 kvl.v_tok_bytes,
13487 Some((&mut aq8, &mut ad8)),
13488 )?;
13489 fa_q8 = Some((aq8, ad8));
13490 } else {
13491 let (off_tok, t_kv) = if swa && kvl.len > win {
13492 (kvl.len - win, win)
13493 } else {
13494 (0, kvl.len)
13495 };
13496 let k_view = e.view_u8_range(
13497 &kvl.k,
13498 off_tok * kvl.k_tok_bytes,
13499 (off_tok + t_kv) * kvl.k_tok_bytes,
13500 );
13501 let v_view = e.view_u8_range(
13502 &kvl.v,
13503 off_tok * kvl.v_tok_bytes,
13504 (off_tok + t_kv) * kvl.v_tok_bytes,
13505 );
13506 e.fa_decode_kvmod(
13507 &q,
13508 &k_view,
13509 &v_view,
13510 &mut attn,
13511 hd,
13512 nh,
13513 nkv,
13514 t_kv,
13515 scale,
13516 kvl.k_tok_bytes,
13517 kvl.v_tok_bytes,
13518 swa && crate::Engine::wkv_on(),
13519 )?;
13520 }
13521 }
13522 Some((b_swa, b_glob)) => {
13523 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13529 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13530 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13531 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13532 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13533 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13534 e.fa_decode_rows(
13535 &q,
13536 &k_view,
13537 &v_view,
13538 &mut attn,
13539 hd,
13540 nh,
13541 nkv,
13542 b_glob - 1,
13543 1,
13544 scale,
13545 kvl.k_tok_bytes,
13546 kvl.v_tok_bytes,
13547 Some((&kvl.len_d, -1)),
13548 false,
13549 false,
13550 Some((&mut aq8, &mut ad8)),
13551 )?;
13552 fa_q8 = Some((aq8, ad8));
13553 } else if swa && b_swa > win && hd == 256 && rows_on {
13554 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13555 e.fa_decode_rows_w(
13556 &q,
13557 &k_view,
13558 &v_view,
13559 &mut attn,
13560 hd,
13561 nh,
13562 nkv,
13563 &kvl.len_d,
13564 -1,
13565 1,
13566 scale,
13567 win,
13568 kvl.k_tok_bytes,
13569 kvl.v_tok_bytes,
13570 Some((&mut aq8, &mut ad8)),
13571 )?;
13572 fa_q8 = Some((aq8, ad8));
13573 } else {
13574 let b = if swa { b_swa } else { b_glob };
13575 e.fa_decode_dc(
13576 &q,
13577 &k_view,
13578 &v_view,
13579 &mut attn,
13580 hd,
13581 nh,
13582 nkv,
13583 &kvl.len_d,
13584 b,
13585 scale,
13586 kvl.k_tok_bytes,
13587 kvl.v_tok_bytes,
13588 swa && crate::Engine::wkv_on(),
13589 )?;
13590 }
13591 }
13592 }
13593 if let Some((aq8, ad8)) = fa_q8 {
13596 let mut y = e.uninit(fa.wo.out_features())?;
13597 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
13598 return Ok(y);
13599 }
13600 Ok(e.matmul(&fa.wo, &attn, 1)?)
13601 }
13602
13603 pub fn gemma4_generate_graph(
13608 &self,
13609 e: &Engine,
13610 prompt_pos: usize,
13611 first_token: u32,
13612 cache: &mut Cache,
13613 max_new: usize,
13614 eos: &[u32],
13615 mut on_token: impl FnMut(u32) -> bool,
13616 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
13617 if self.is_gemma4_e4b() {
13618 return Err(
13619 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
13620 .into(),
13621 );
13622 }
13623 use crate::decode::StopReason;
13624 let n_vocab = self.output.out_features();
13625 let n_embd = self.cfg.n_embd as usize;
13626 let embd_gpu = self
13627 .embd_gpu
13628 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13629 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13630 for kvl in cache.kv.iter_mut().flatten() {
13631 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
13632 }
13633 let mut token_d = e.stream().clone_htod(&[first_token])?;
13634 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
13635 let g4 = self.cfg.gemma4.as_ref().unwrap();
13636 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
13637 let nkv_s = g4
13639 .head_count_kv
13640 .iter()
13641 .zip(g4.swa_pattern.iter())
13642 .find(|p| *p.1)
13643 .map(|p| *p.0 as usize)
13644 .unwrap_or(8);
13645 let nkv_g = g4
13646 .head_count_kv
13647 .iter()
13648 .zip(g4.swa_pattern.iter())
13649 .find(|p| !*p.1)
13650 .map(|p| *p.0 as usize)
13651 .unwrap_or(2);
13652 let mut graphs: std::collections::HashMap<
13653 ((bool, usize), (bool, usize), bool, bool),
13654 (
13655 cudarc::driver::CudaGraph,
13656 Vec<Box<dyn std::any::Any + Send>>,
13657 ),
13658 > = Default::default();
13659 let mut slots = self.g4_dc_slots(e)?;
13662 const RING: usize = 64;
13665 const DRAIN: usize = 1;
13671 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
13672 let ring_base = prompt_pos;
13673 let mut out = Vec::with_capacity(max_new);
13674 let mut reason = StopReason::MaxNew;
13675 let mut next = first_token;
13676 let mut captures = 0usize;
13677 for _ in 0..max_new {
13678 out.push(next);
13679 if eos.contains(&next) {
13680 reason = StopReason::Eos;
13681 break;
13682 }
13683 if !on_token(next) {
13684 reason = StopReason::Callback;
13685 break;
13686 }
13687 let t_kv = cache.pos + 1;
13688 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13696 let f512 = crate::fa512_min_tkv();
13697 let key_s = if t_kv > win {
13698 (true, usize::MAX)
13699 } else {
13700 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
13701 };
13702 let (key_g, rung_end) = if t_kv >= f512 {
13703 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
13706 ((true, end), end)
13707 } else {
13708 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
13709 };
13710 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
13711 if !graphs.contains_key(&key) {
13712 let bucket_max = (t_kv, rung_end);
13713 let snap = cache.snapshot(e)?;
13715 let pos_save = e.dtoh_i32_one(&pos_d)?;
13716 let len_save: Vec<Option<i32>> = cache
13717 .kv
13718 .iter()
13719 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
13720 .collect();
13721 let tok_save = e.dtoh_u32_one(&token_d)?;
13722 let graph = {
13727 let tok_ref = &mut token_d;
13728 let pos_ref = &mut pos_d;
13729 let cache_ref = &mut *cache;
13730 let slots_ref = &mut slots;
13731 let ring_ref = &mut ring;
13732 e.capture_graph_retained_flags(
13733 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
13734 |e| {
13735 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
13737 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
13738 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
13739 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
13740 cache_ref, n_vocab, Some(bucket_max),
13741 sl, tok_ref, Some((rg, ring_base)))
13742 })?
13743 };
13744 cache.rollback(e, &snap, 0)?;
13745 e.set_i32_one(&mut pos_d, pos_save)?;
13746 for (il, ls) in len_save.iter().enumerate() {
13747 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
13748 e.set_i32_one(&mut kvl.len_d, *v)?;
13749 }
13750 }
13751 e.set_u32_one(&mut token_d, tok_save)?;
13752 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
13753 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
13754 eprintln!("[graph-census] {c:?}");
13755 }
13756 }
13757 graphs.insert(key, graph);
13758 captures += 1;
13759 }
13760 let mut chunk = 1usize;
13765 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
13766 .ok()
13767 .and_then(|v| v.parse().ok())
13768 .unwrap_or(DRAIN);
13769 while chunk < drain_cap && out.len() + chunk < max_new {
13770 let t_next = cache.pos + 1 + chunk;
13771 let key_s2 = if t_next > win {
13772 (true, usize::MAX)
13773 } else {
13774 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
13775 };
13776 let key_g2 = if t_next >= f512 {
13777 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
13778 } else {
13779 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
13780 };
13781 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
13782 break;
13783 }
13784 chunk += 1;
13785 }
13786 let g = &graphs.get(&key).unwrap().0;
13787 for _ in 0..chunk {
13788 g.launch()?;
13789 }
13790 e.stream().synchronize()?;
13791 let ringh = e.dtoh_u32(&ring)?;
13792 for j in 0..chunk {
13793 let pos_j = cache.pos + j;
13794 let tok_j = ringh[(pos_j - ring_base) % RING];
13795 cache.pos += 0; if j + 1 == chunk {
13797 next = tok_j;
13798 } else {
13799 out.push(tok_j);
13800 if eos.contains(&tok_j) || !on_token(tok_j) {
13801 reason = if eos.contains(&tok_j) {
13802 StopReason::Eos
13803 } else {
13804 StopReason::Callback
13805 };
13806 let keep = cache.pos + j + 1;
13808 e.set_i32_one(&mut pos_d, keep as i32)?;
13809 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13810 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
13811 kvl.len = keep;
13812 }
13813 cache.pos = keep;
13814 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13815 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13816 }
13817 return Ok((out, reason));
13818 }
13819 }
13820 }
13821 cache.pos += chunk;
13822 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13823 kvl.len += chunk;
13824 }
13825 }
13826 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13827 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13828 }
13829 Ok((out, reason))
13830 }
13831
13832 pub(crate) fn gemma4_decode_step_t(
13838 &self,
13839 e: &Engine,
13840 tokens: &[u32],
13841 pos0: usize,
13842 cache: &mut Cache,
13843 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13844 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
13845 }
13846
13847 pub(crate) fn gemma4_decode_step_t_am(
13851 &self,
13852 e: &Engine,
13853 tokens: &[u32],
13854 pos0: usize,
13855 cache: &mut Cache,
13856 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13857 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13858 let t = tokens.len();
13859 let n_vocab = self.output.out_features();
13860 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
13861 for i in 0..t {
13862 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
13863 }
13864 Ok((e.dtoh_u32(&toks)?, hn))
13865 }
13866
13867 pub(crate) fn gemma4_decode_step_t_am_dev(
13870 &self,
13871 e: &Engine,
13872 tok_d: &CudaSlice<u32>,
13873 t: usize,
13874 pos0: usize,
13875 cache: &mut Cache,
13876 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13877 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
13878 let n_vocab = self.output.out_features();
13879 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13880 for i in 0..t {
13881 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13882 }
13883 Ok((vam, hn))
13884 }
13885
13886 pub(crate) fn gemma4_decode_step_t_h(
13889 &self,
13890 e: &Engine,
13891 tokens: &[u32],
13892 pos0: usize,
13893 cache: &mut Cache,
13894 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13895 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13896 let t = tokens.len();
13897 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
13898 e.softcap(&mut ld, cap, t * self.output.out_features())?;
13899 Ok((e.dtoh(&ld)?, hn))
13900 }
13901
13902 pub(crate) fn verify_stream_scratch(
13905 &self,
13906 e: &Engine,
13907 cap: usize,
13908 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
13909 Ok(VerifyStreamScratch {
13910 pos_d: e.htod_i32(&vec![0i32; cap])?,
13911 row_ctrs: (0..cap)
13912 .map(|_| e.htod_i32(&[0]))
13913 .collect::<Result<_, _>>()?,
13914 })
13915 }
13916
13917 pub(crate) fn gemma4_verify_t_am_stream(
13925 &self,
13926 e: &Engine,
13927 tok_d: &CudaSlice<u32>,
13928 t: usize,
13929 ctr: &CudaSlice<i32>,
13930 hint: usize,
13931 cache: &mut Cache,
13932 scr: &mut VerifyStreamScratch,
13933 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13934 let n_embd = self.cfg.n_embd as usize;
13935 let eps = self.cfg.rms_eps;
13936 assert!(t <= scr.row_ctrs.len() && t <= 64);
13937 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
13938 for i in 0..t {
13939 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
13940 }
13941 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
13942 let embd_gpu = self
13943 .embd_gpu
13944 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13945 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13946 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
13947 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13948 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13949 let n_layers = self.layers.len();
13950 for (il, layer) in self.layers.iter().enumerate() {
13951 let (hq, hdq) = match h_carry.take() {
13952 Some(p) => p,
13953 None => {
13954 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
13955 }
13956 };
13957 let Mixer::Full(fa) = &layer.mixer else {
13958 panic!("gemma4 layer {il} not full-attn")
13959 };
13960 let o = self
13961 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
13962 let next_norm = if il + 1 < n_layers {
13963 Some(self.layers[il + 1].attn_norm.float_data())
13964 } else {
13965 None
13966 };
13967 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
13968 x = xn;
13969 h_carry = hn;
13970 self.dflash_tap(e, cache, il, &x, t)?;
13971 }
13972 let mut hn = e.uninit(t * n_embd)?;
13973 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13974 let ld = e.matmul(&self.output, &hn, t)?;
13975 let n_vocab = self.output.out_features();
13976 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13977 for i in 0..t {
13978 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13979 }
13980 Ok((vam, hn))
13981 }
13982
13983 pub(crate) fn dflash_tap(
13990 &self,
13991 e: &Engine,
13992 cache: &mut Cache,
13993 il: usize,
13994 x: &CudaSlice<f32>,
13995 t: usize,
13996 ) -> Result<(), Box<dyn std::error::Error>> {
13997 let Some(taps) = cache.dflash_taps.as_mut() else {
13998 return Ok(());
13999 };
14000 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
14001 return Ok(());
14002 };
14003 let h = taps.hidden;
14004 let n_taps = taps.layer_ids.len();
14005 let base = taps.base;
14006 debug_assert!(
14007 base + t <= taps.t,
14008 "tap window {base}+{t} exceeds sink {}",
14009 taps.t
14010 );
14011 let xv = e.view(x, t * h);
14012 for r in 0..t {
14013 let row = xv.slice(r * h..(r + 1) * h);
14014 e.copy_view_into(&mut taps.buf, (base + r) * n_taps * h + slot * h, &row, h)?;
14015 }
14016 Ok(())
14017 }
14018
14019 fn gemma4_verify_trunk(
14020 &self,
14021 e: &Engine,
14022 tokens: &[u32],
14023 pos0: usize,
14024 cache: &mut Cache,
14025 tok_dev: Option<&CudaSlice<u32>>,
14026 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14027 let n_embd = self.cfg.n_embd as usize;
14028 let eps = self.cfg.rms_eps;
14029 let t = tokens.len();
14030 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
14031 let pos_d = e.htod_i32(&pos)?;
14032 let mut x = match tok_dev {
14033 Some(td) => {
14034 let embd_gpu = self
14035 .embd_gpu
14036 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
14037 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
14038 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
14039 }
14040 None => e.htod(&self.embd.gather(n_embd, tokens))?,
14041 };
14042 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
14043 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14044 let n_layers = self.layers.len();
14045 for (il, layer) in self.layers.iter().enumerate() {
14046 let (hq, hdq) = match h_carry.take() {
14047 Some(p) => p,
14048 None => {
14049 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
14050 }
14051 };
14052 let Mixer::Full(fa) = &layer.mixer else {
14053 panic!("gemma4 layer {il} not full-attn")
14054 };
14055 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
14056 let next_norm = if il + 1 < n_layers {
14057 Some(self.layers[il + 1].attn_norm.float_data())
14058 } else {
14059 None
14060 };
14061 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
14062 x = xn;
14063 h_carry = hn;
14064 self.dflash_tap(e, cache, il, &x, t)?;
14065 }
14066 let mut hn = e.uninit(t * n_embd)?;
14067 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14068 let mut ld = e.matmul(&self.output, &hn, t)?;
14069 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
14071 Ok((ld, hn))
14072 }
14073
14074 #[allow(clippy::too_many_arguments)]
14082 fn gemma4_verify_attn_stream(
14083 &self,
14084 e: &Engine,
14085 fa: &crate::hybrid::FullAttnLayer,
14086 il: usize,
14087 hq: &CudaSlice<i8>,
14088 hdq: &CudaSlice<f32>,
14089 pos_d: &CudaSlice<i32>,
14090 t: usize,
14091 cache: &mut Cache,
14092 hint: usize,
14093 row_ctrs: &[CudaSlice<i32>],
14094 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14095 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14096 let eps = self.cfg.rms_eps;
14097 let aux = self.gemma4_aux.as_ref().unwrap();
14098 let ones = aux.ones(e);
14099 #[cfg(debug_assertions)]
14100 crate::debug_assert_tensor_stream_device(
14101 ones,
14102 &e.stream(),
14103 "gemma4_verify_attn_stream.ones",
14104 );
14105 let h0 = e.zeros(0)?;
14106 let h = &h0;
14107 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14110 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14111 let fused_qkv = if f2b {
14112 if swa {
14113 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14114 .map(|(a, b, c)| (a, b, Some(c)))
14115 } else {
14116 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14117 .map(|(a, b)| (a, b, None))
14118 }
14119 } else {
14120 None
14121 };
14122 let (q0, k0, v0) = match fused_qkv {
14123 Some((a, b, cv)) => {
14124 let v = match cv {
14125 Some(c) => c,
14126 None => e.clone_dtod(&b)?,
14127 };
14128 (a, b, v)
14129 }
14130 None => {
14131 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14132 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14133 let v0 = if swa {
14134 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14135 } else {
14136 e.clone_dtod(&k0)?
14137 };
14138 (q0, k0, v0)
14139 }
14140 };
14141 let mut q = e.uninit(t * nh * hd)?;
14142 let mut k = e.uninit(t * nkv * hd)?;
14143 let mut v = e.uninit(t * nkv * hd)?;
14144 let ff = if swa {
14147 None
14148 } else {
14149 Some(
14150 aux.rope_freqs(e)
14151 .expect("gemma4 global rope needs rope_freqs.weight"),
14152 )
14153 };
14154 #[cfg(debug_assertions)]
14155 if let Some(ff) = ff {
14156 crate::debug_assert_tensor_stream_device(
14157 ff,
14158 &e.stream(),
14159 "gemma4_verify_attn_stream.rope_freqs",
14160 );
14161 }
14162 e.rms_norm_qkv_rope(
14163 &q0,
14164 &k0,
14165 &v0,
14166 fa.q_norm.float_data(),
14167 fa.k_norm.float_data(),
14168 ones,
14169 &mut q,
14170 &mut k,
14171 &mut v,
14172 hd,
14173 self.gemma4_rope_dims(il),
14174 nh * t,
14175 nkv * t,
14176 pos_d,
14177 nh,
14178 nkv,
14179 base,
14180 1.0,
14181 ff,
14182 eps,
14183 )?;
14184 let kvl = cache.kv[il].as_mut().unwrap();
14185 e.append_kv_quantized_rows_dc(
14187 &k,
14188 &v,
14189 &mut kvl.k,
14190 &mut kvl.v,
14191 &kvl.len_d,
14192 t,
14193 kvl.kv_dim_k,
14194 kvl.kv_dim_v,
14195 kvl.k_tok_bytes,
14196 kvl.v_tok_bytes,
14197 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14198 )?;
14199 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14202 let mut attn = e.uninit(t * nh * hd)?;
14203 let k_view = e.view_u8(&kvl.k, kvl.k.len());
14204 let v_view = e.view_u8(&kvl.v, kvl.v.len());
14205 if swa && hint + 1 >= win {
14208 e.fa_decode_rows_w(
14211 &q,
14212 &k_view,
14213 &v_view,
14214 &mut attn,
14215 hd,
14216 nh,
14217 nkv,
14218 &kvl.len_d,
14219 0,
14220 t,
14221 scale,
14222 win,
14223 kvl.k_tok_bytes,
14224 kvl.v_tok_bytes,
14225 None,
14226 )?;
14227 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
14228 let bucket = (hint + t + 2)
14241 .next_power_of_two()
14242 .min(crate::fa512_min_tkv().saturating_sub(1));
14243 let qv = e.view(&q, t * nh * hd);
14244 for i in 0..t {
14245 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
14246 let mut q_one = e.uninit(nh * hd)?;
14247 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14248 let mut a_one = e.uninit(nh * hd)?;
14249 e.fa_decode_dc(
14250 &q_one,
14251 &k_view,
14252 &v_view,
14253 &mut a_one,
14254 hd,
14255 nh,
14256 nkv,
14257 &row_ctrs[i],
14258 bucket,
14259 scale,
14260 kvl.k_tok_bytes,
14261 kvl.v_tok_bytes,
14262 false,
14263 )?;
14264 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14265 }
14266 } else if hd == 512 {
14267 e.fa_decode_rows(
14270 &q,
14271 &k_view,
14272 &v_view,
14273 &mut attn,
14274 hd,
14275 nh,
14276 nkv,
14277 hint,
14278 t,
14279 scale,
14280 kvl.k_tok_bytes,
14281 kvl.v_tok_bytes,
14282 Some((&kvl.len_d, 0)),
14283 false,
14284 false,
14285 None,
14286 )?;
14287 } else {
14288 e.fa_decode_rows_dc(
14290 &q,
14291 &k_view,
14292 &v_view,
14293 &mut attn,
14294 hd,
14295 nh,
14296 nkv,
14297 &kvl.len_d,
14298 hint + t,
14299 t,
14300 scale,
14301 kvl.k_tok_bytes,
14302 kvl.v_tok_bytes,
14303 0,
14304 swa && crate::Engine::wkv_on(),
14305 )?;
14306 }
14307 Ok(e.matmul(&fa.wo, &attn, t)?)
14308 }
14309
14310 fn gemma4_verify_attn(
14311 &self,
14312 e: &Engine,
14313 fa: &crate::hybrid::FullAttnLayer,
14314 il: usize,
14315 hq: &CudaSlice<i8>,
14316 hdq: &CudaSlice<f32>,
14317 pos_d: &CudaSlice<i32>,
14318 t: usize,
14319 cache: &mut Cache,
14320 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14321 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14322 let eps = self.cfg.rms_eps;
14323 let aux = self.gemma4_aux.as_ref().unwrap();
14324 let ones = aux.ones(e);
14325 #[cfg(debug_assertions)]
14326 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
14327 let n_embd = self.cfg.n_embd as usize;
14328 let _ = n_embd;
14329
14330 let h0 = e.zeros(0)?;
14331 let h = &h0;
14332 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14335 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14336 let fused_qkv = if f2b {
14337 if swa {
14338 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14339 .map(|(a, b, c)| (a, b, Some(c)))
14340 } else {
14341 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14342 .map(|(a, b)| (a, b, None))
14343 }
14344 } else {
14345 None
14346 };
14347 let (q0, k0, v0) = match fused_qkv {
14348 Some((a, b, cv)) => {
14349 let v = match cv {
14350 Some(c) => c,
14351 None => e.clone_dtod(&b)?,
14352 };
14353 (a, b, v)
14354 }
14355 None => {
14356 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14357 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14358 let v0 = if swa {
14359 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14360 } else {
14361 e.clone_dtod(&k0)?
14362 };
14363 (q0, k0, v0)
14364 }
14365 };
14366 let mut q = e.uninit(t * nh * hd)?;
14367 let mut k = e.uninit(t * nkv * hd)?;
14368 let mut v = e.uninit(t * nkv * hd)?;
14369 let ff = if swa {
14372 None
14373 } else {
14374 Some(
14375 aux.rope_freqs(e)
14376 .expect("gemma4 global rope needs rope_freqs.weight"),
14377 )
14378 };
14379 #[cfg(debug_assertions)]
14380 if let Some(ff) = ff {
14381 crate::debug_assert_tensor_stream_device(
14382 ff,
14383 &e.stream(),
14384 "gemma4_verify_attn.rope_freqs",
14385 );
14386 }
14387 e.rms_norm_qkv_rope(
14388 &q0,
14389 &k0,
14390 &v0,
14391 fa.q_norm.float_data(),
14392 fa.k_norm.float_data(),
14393 ones,
14394 &mut q,
14395 &mut k,
14396 &mut v,
14397 hd,
14398 self.gemma4_rope_dims(il),
14399 nh * t,
14400 nkv * t,
14401 pos_d,
14402 nh,
14403 nkv,
14404 base,
14405 1.0,
14406 ff,
14407 eps,
14408 )?;
14409 let kvl = cache.kv[il].as_mut().unwrap();
14410 let base_len = kvl.len;
14411 e.append_kv_quantized_rows(
14412 &k,
14413 &v,
14414 &mut kvl.k,
14415 &mut kvl.v,
14416 base_len,
14417 t,
14418 kvl.kv_dim_k,
14419 kvl.kv_dim_v,
14420 kvl.k_tok_bytes,
14421 kvl.v_tok_bytes,
14422 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14423 )?;
14424 kvl.len += t;
14425 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14426 let mut attn = e.uninit(t * nh * hd)?;
14427 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
14430 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
14433 if rows_ok && (!swa || base_len + t <= win) {
14434 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14435 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14436 if hd == 512 {
14437 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14439 e.fa_decode_rows(
14440 &q,
14441 &k_view,
14442 &v_view,
14443 &mut attn,
14444 hd,
14445 nh,
14446 nkv,
14447 base_len,
14448 t,
14449 scale,
14450 kvl.k_tok_bytes,
14451 kvl.v_tok_bytes,
14452 Some((&kvl.len_d, 0)),
14453 false,
14454 swa && crate::Engine::wkv_on(),
14455 None,
14456 )?;
14457 } else {
14458 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14462 e.fa_decode_rows_dc(
14463 &q,
14464 &k_view,
14465 &v_view,
14466 &mut attn,
14467 hd,
14468 nh,
14469 nkv,
14470 &kvl.len_d,
14471 base_len + t,
14472 t,
14473 scale,
14474 kvl.k_tok_bytes,
14475 kvl.v_tok_bytes,
14476 0,
14477 swa && crate::Engine::wkv_on(),
14478 )?;
14479 }
14480 return Ok(e.matmul(&fa.wo, &attn, t)?);
14481 }
14482 if hd == 256
14490 && swa
14491 && base_len + 1 >= win
14492 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14493 {
14494 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14495 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14496 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14497 e.fa_decode_rows_w(
14498 &q,
14499 &k_view,
14500 &v_view,
14501 &mut attn,
14502 hd,
14503 nh,
14504 nkv,
14505 &kvl.len_d,
14506 0,
14507 t,
14508 scale,
14509 win,
14510 kvl.k_tok_bytes,
14511 kvl.v_tok_bytes,
14512 None,
14513 )?;
14514 return Ok(e.matmul(&fa.wo, &attn, t)?);
14515 }
14516 for i in 0..t {
14517 let avail = base_len + i + 1;
14518 let (off_tok, t_kv) = if swa && avail > win {
14519 (avail - win, win)
14520 } else {
14521 (0, avail)
14522 };
14523 let k_view = e.view_u8_range(
14524 &kvl.k,
14525 off_tok * kvl.k_tok_bytes,
14526 (off_tok + t_kv) * kvl.k_tok_bytes,
14527 );
14528 let v_view = e.view_u8_range(
14529 &kvl.v,
14530 off_tok * kvl.v_tok_bytes,
14531 (off_tok + t_kv) * kvl.v_tok_bytes,
14532 );
14533 let qi = e.view(&q, t * nh * hd);
14534 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
14535 let mut q_one = e.uninit(nh * hd)?;
14536 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14537 let mut a_one = e.uninit(nh * hd)?;
14538 if swa
14542 && avail > win
14543 && hd == 256
14544 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14545 {
14546 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14547 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14548 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14549 e.fa_decode_rows_w(
14550 &q_one,
14551 &kp,
14552 &vp,
14553 &mut a_one,
14554 hd,
14555 nh,
14556 nkv,
14557 &kvl.len_d,
14558 0,
14559 1,
14560 scale,
14561 win,
14562 kvl.k_tok_bytes,
14563 kvl.v_tok_bytes,
14564 None,
14565 )?;
14566 } else if !swa
14567 && hd == 512
14568 && avail >= crate::fa512_min_tkv()
14569 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14570 {
14571 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14572 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14573 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14574 e.fa_decode_rows(
14575 &q_one,
14576 &kp,
14577 &vp,
14578 &mut a_one,
14579 hd,
14580 nh,
14581 nkv,
14582 avail - 1,
14583 1,
14584 scale,
14585 kvl.k_tok_bytes,
14586 kvl.v_tok_bytes,
14587 Some((&kvl.len_d, 0)),
14588 false,
14589 false,
14590 None,
14591 )?;
14592 } else {
14593 e.fa_decode_kvmod(
14594 &q_one,
14595 &k_view,
14596 &v_view,
14597 &mut a_one,
14598 hd,
14599 nh,
14600 nkv,
14601 t_kv,
14602 scale,
14603 kvl.k_tok_bytes,
14604 kvl.v_tok_bytes,
14605 swa && crate::Engine::wkv_on(),
14606 )?;
14607 }
14608 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14609 }
14610 Ok(e.matmul(&fa.wo, &attn, t)?)
14611 }
14612
14613 pub(crate) fn gemma4_decode_step_h(
14616 &self,
14617 e: &Engine,
14618 token: u32,
14619 cache: &mut Cache,
14620 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14621 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
14626 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
14627 }
14628 if crate::pp::pp_cuts(self.layers.len()).is_some() {
14629 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
14630 }
14631 let n_embd = self.cfg.n_embd as usize;
14632 let eps = self.cfg.rms_eps;
14633 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14634 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14635 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14636 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14639 let n_layers = self.layers.len();
14640 for (il, layer) in self.layers.iter().enumerate() {
14641 let (hq, hdq) = match h_carry.take() {
14642 Some(p) => p,
14643 None => {
14644 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
14645 }
14646 };
14647 let Mixer::Full(fa) = &layer.mixer else {
14648 panic!("gemma4 layer {il} not full-attn")
14649 };
14650 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
14651 let next_norm = if il + 1 < n_layers {
14652 Some(self.layers[il + 1].attn_norm.float_data())
14653 } else {
14654 None
14655 };
14656 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14657 x = xn;
14658 h_carry = hn;
14659 }
14660 let mut hn = e.uninit(n_embd)?;
14661 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14662 let h_seed = e.clone_dtod(&x)?;
14663 let mut ld = e.matmul(&self.output, &hn, 1)?;
14664 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14665 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
14667 let logits = e.dtoh(&ld)?;
14668 cache.pos += 1;
14669 Ok((logits, h_seed))
14670 }
14671
14672 fn gemma4_decode_layers(
14680 &self,
14681 e: &Engine,
14682 mut x: CudaSlice<f32>,
14683 lo: usize,
14684 hi: usize,
14685 pos_d: &CudaSlice<i32>,
14686 cache: &mut Cache,
14687 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14688 let n_embd = self.cfg.n_embd as usize;
14689 let eps = self.cfg.rms_eps;
14690 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14691 for il in lo..hi {
14692 let layer = &self.layers[il];
14693 let (hq, hdq) = match h_carry.take() {
14694 Some(p) => p,
14695 None => {
14697 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
14698 }
14699 };
14700 let Mixer::Full(fa) = &layer.mixer else {
14701 panic!("gemma4 layer {il} not full-attn")
14702 };
14703 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
14704 let next_norm = if il + 1 < hi {
14705 Some(self.layers[il + 1].attn_norm.float_data())
14706 } else {
14707 None
14708 };
14709 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14710 x = xn;
14711 h_carry = hn;
14712 }
14713 Ok(x)
14714 }
14715
14716 fn gemma4_decode_step_h_pp2(
14724 &self,
14725 e: &Engine,
14726 token: u32,
14727 cache: &mut Cache,
14728 split: usize,
14729 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14730 if crate::pp::pp2_streams_off() {
14731 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
14732 }
14733 let rt = crate::pp::Pp2Rt::get(e)?;
14734 let e0 = rt.engine(0, e);
14735 let e1 = rt.engine(1, e);
14736 let n_embd = self.cfg.n_embd as usize;
14737 let eps = self.cfg.rms_eps;
14738 let pos = cache.pos as i32;
14739
14740 let slot = {
14742 let _st0 = rt.enter(0);
14743 let pos_d = e0.htod_i32(&[pos])?;
14744 #[cfg(debug_assertions)]
14745 crate::debug_assert_tensor_stream_device(
14746 &pos_d,
14747 &e0.stream(),
14748 "gemma4_decode_step_h_pp2.stage0.pos_d",
14749 );
14750 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
14751 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14752 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
14753 rt.tx(0, &x, n_embd)?
14754 };
14755
14756 let _st1 = rt.enter(1);
14758 let pos_d = e1.htod_i32(&[pos])?;
14759 #[cfg(debug_assertions)]
14760 crate::debug_assert_tensor_stream_device(
14761 &pos_d,
14762 &e1.stream(),
14763 "gemma4_decode_step_h_pp2.stage1.pos_d",
14764 );
14765 let x = rt.rx(0, slot, n_embd)?;
14766 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
14767
14768 let mut hn = e1.uninit(n_embd)?;
14769 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14770 let h_seed = e1.clone_dtod(&x)?;
14771 let mut ld = e1.matmul(&self.output, &hn, 1)?;
14772 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14773 e1.softcap(&mut ld, cap, self.output.out_features())?;
14774 self.gemma4_suppress(e1, &mut ld, 1)?;
14775 let logits = e1.dtoh(&ld)?;
14776 cache.pos += 1;
14777 Ok((logits, h_seed))
14778 }
14779
14780 fn gemma4_decode_step_h_pp2_samestream(
14783 &self,
14784 e: &Engine,
14785 token: u32,
14786 cache: &mut Cache,
14787 split: usize,
14788 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14789 let n_embd = self.cfg.n_embd as usize;
14790 let eps = self.cfg.rms_eps;
14791 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14792
14793 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14795 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14796 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
14797
14798 let boundary_tx = e.clone_dtod(&x)?;
14800 let boundary_rx = e.clone_dtod(&boundary_tx)?;
14801
14802 let x =
14804 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
14805
14806 let mut hn = e.uninit(n_embd)?;
14807 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14808 let h_seed = e.clone_dtod(&x)?;
14809 let mut ld = e.matmul(&self.output, &hn, 1)?;
14810 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14811 e.softcap(&mut ld, cap, self.output.out_features())?;
14812 self.gemma4_suppress(e, &mut ld, 1)?;
14813 let logits = e.dtoh(&ld)?;
14814 cache.pos += 1;
14815 Ok((logits, h_seed))
14816 }
14817}
14818
14819impl HybridModel {
14838 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
14841 let geometry = self
14842 .cfg
14843 .layer_geometry(il as u32)
14844 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
14845 debug_assert_eq!(
14846 geometry.attention_gate,
14847 memra_gguf::config::AttentionGateKind::SeparateHead
14848 );
14849 geometry
14850 }
14851
14852 #[allow(clippy::too_many_arguments)]
14912 fn step35_attn_pre_wo(
14913 &self,
14914 e: &Engine,
14915 fa: &FullAttnLayer,
14916 mut g3: Vec<CudaSlice<f32>>,
14917 hg: Option<&CudaSlice<f32>>,
14918 gt_pre: Option<&CudaSlice<f32>>,
14919 pos_d: &CudaSlice<i32>,
14920 t: usize,
14921 cache: Option<&mut Cache>,
14922 il: usize,
14923 seq_end: usize,
14924 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14925 let geometry = self.step35_geom(il);
14926 let hd = geometry.head_dim_k as usize;
14927 let nkv = geometry.n_head_kv as usize;
14928 let nh = geometry.n_head as usize;
14929 let rbase = geometry.rope_base;
14930 let scale = geometry.attention_scale();
14931 let swa = geometry.window.is_some();
14932 let eps = self.cfg.rms_eps;
14933 let win = geometry.window.unwrap_or(0) as usize;
14934 let n_rot = geometry.n_rot as usize;
14935
14936 let v = g3.pop().unwrap();
14937 let k0 = g3.pop().unwrap();
14938 let q0 = g3.pop().unwrap();
14939
14940 let mut q = e.uninit(t * nh * hd)?;
14944 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
14945 let mut k = e.uninit(t * nkv * hd)?;
14946 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
14947 let ff = if geometry.rope_factors {
14948 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
14949 } else {
14950 None
14951 };
14952 #[cfg(debug_assertions)]
14953 if let Some(ff) = ff {
14954 crate::debug_assert_tensor_stream_device(
14955 ff,
14956 &e.stream(),
14957 "step35_attn_pre_wo.rope_freqs",
14958 );
14959 }
14960 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
14961
14962 let mut attn = e.uninit(t * nh * hd)?;
14963 match cache {
14964 Some(cache) => {
14965 let base_len = cache.kv[il].as_ref().unwrap().len;
14966 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
14968 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
14969 let off = if swa {
14970 let raw = base_len.saturating_sub(win - 1);
14971 if legacy_tkv || legacy_calllocal {
14972 raw
14973 } else {
14974 raw & !31usize
14975 }
14976 } else {
14977 0
14978 };
14979 {
14980 let kvl = cache.kv[il].as_mut().unwrap();
14981 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
14982 let write_row = e.prepare_kv_append(kvl, off, t)?;
14983 e.append_kv_quantized_rows(
14984 &k,
14985 &v,
14986 &mut kvl.k,
14987 &mut kvl.v,
14988 write_row,
14989 t,
14990 kvl.kv_dim_k,
14991 kvl.kv_dim_v,
14992 kvl.k_tok_bytes,
14993 kvl.v_tok_bytes,
14994 crate::Engine::kv_fp8_on(),
14995 )?;
14996 kvl.len += t;
14997 let new_len = kvl.len as i32;
14998 e.set_i32_one(&mut kvl.len_d, new_len)?;
14999 }
15000 let kvl = cache.kv[il].as_ref().unwrap();
15001 let t_kv = base_len + t - off;
15024 let physical = kvl.physical_rows(off, off + t_kv)?;
15025 let k_view = e.view_u8_range(
15026 &kvl.k,
15027 physical.start * kvl.k_tok_bytes,
15028 physical.end * kvl.k_tok_bytes,
15029 );
15030 let v_view = e.view_u8_range(
15031 &kvl.v,
15032 physical.start * kvl.v_tok_bytes,
15033 physical.end * kvl.v_tok_bytes,
15034 );
15035 let swa_naive = if legacy_tkv {
15047 t_kv > win
15048 } else {
15049 seq_end > win
15050 };
15051 if swa && swa_naive {
15052 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15065 e.sdpa_naive_w_quantized_view(
15066 &q,
15067 &k_view,
15068 &v_view,
15069 &mut attn,
15070 hd,
15071 nh,
15072 nkv,
15073 t,
15074 t_kv,
15075 scale,
15076 true,
15077 win,
15078 kvl.k_tok_bytes,
15079 kvl.v_tok_bytes,
15080 )?;
15081 } else {
15082 e.fa_prefill_view_ws_w_hd128(
15083 &q,
15084 &k_view,
15085 &v_view,
15086 &mut attn,
15087 hd,
15088 nh,
15089 nkv,
15090 t,
15091 t_kv,
15092 scale,
15093 true,
15094 win,
15095 kvl.k_tok_bytes,
15096 kvl.v_tok_bytes,
15097 )?;
15098 }
15099 } else if std::env::var("MEMRA_NOFA").is_ok() {
15100 e.sdpa_naive_quantized_view(
15101 &q,
15102 &k_view,
15103 &v_view,
15104 &mut attn,
15105 hd,
15106 nh,
15107 nkv,
15108 t,
15109 t_kv,
15110 scale,
15111 true,
15112 kvl.k_tok_bytes,
15113 kvl.v_tok_bytes,
15114 )?;
15115 } else {
15116 e.fa_prefill_view_ws(
15121 &q,
15122 &k_view,
15123 &v_view,
15124 &mut attn,
15125 hd,
15126 nh,
15127 nkv,
15128 t,
15129 t_kv,
15130 scale,
15131 true,
15132 kvl.k_tok_bytes,
15133 kvl.v_tok_bytes,
15134 crate::Engine::kv_fp8_on(),
15135 )?;
15136 }
15137 }
15138 None => {
15139 debug_assert_eq!(
15144 seq_end, t,
15145 "step35 cacheless prefill is monolithic (seq_end == t)"
15146 );
15147 if swa && seq_end > win {
15148 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
15149 } else if std::env::var("MEMRA_NOFA").is_ok() {
15150 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15151 } else {
15152 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15153 }
15154 }
15155 }
15156
15157 let gw = fa
15160 .attn_gate
15161 .as_ref()
15162 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
15163 let gt_owned = if gt_pre.is_none() {
15164 Some(e.matmul(
15165 gw,
15166 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
15167 t,
15168 )?)
15169 } else {
15170 None
15171 };
15172 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
15173 let mut ag = e.uninit(t * nh * hd)?;
15174 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
15175 Ok(ag)
15176 }
15177
15178 pub(crate) fn step35_attn(
15181 &self,
15182 e: &Engine,
15183 fa: &FullAttnLayer,
15184 h: &CudaSlice<f32>,
15185 pos_d: &CudaSlice<i32>,
15186 t: usize,
15187 il: usize,
15188 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15189 let g3 = match self.step35_tp_qkv(e, fa, h, t)? {
15190 Some(g3) => g3,
15191 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15192 };
15193 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
15195 self.step35_o(e, fa, &ag, t)
15196 }
15197
15198 #[allow(clippy::too_many_arguments)]
15205 pub(crate) fn step35_attn_prime(
15206 &self,
15207 e: &Engine,
15208 fa: &FullAttnLayer,
15209 h: &CudaSlice<f32>,
15210 hx: Option<&CudaSlice<u8>>,
15211 pos_d: &CudaSlice<i32>,
15212 t: usize,
15213 cache: &mut Cache,
15214 il: usize,
15215 seq_end: usize,
15216 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15217 if step_tp_prefill_enabled()? && fa.step_tp_qkv.is_some() {
15218 if hx.is_some() {
15219 return Err(
15220 "rank-local Step prefill preserves BF16 activations and refuses the q8_1 \
15221 pre-quantized prime path"
15222 .into(),
15223 );
15224 }
15225 return self.step35_tp_prefill_attn_resident(e, fa, il, h, pos_d, t, cache, seq_end);
15226 }
15227 let g3 = if fa.step_tp_qkv.is_some() {
15228 if hx.is_some() {
15229 return Err(
15230 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
15231 pre-quantized prime path"
15232 .into(),
15233 );
15234 }
15235 self.step35_tp_qkv(e, fa, h, t)?
15236 .expect("Step Q/K/V TP disappeared after the presence check")
15237 } else {
15238 match hx {
15239 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
15240 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15241 }
15242 };
15243 let ag =
15244 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
15245 self.step35_o(e, fa, &ag, t)
15246 }
15247
15248 fn ensure_step_tp_kv_cache(
15249 &self,
15250 e: &Engine,
15251 fa: &FullAttnLayer,
15252 il: usize,
15253 cache: &mut Cache,
15254 ) -> Result<bool, Box<dyn std::error::Error>> {
15255 let tp = fa
15256 .step_tp_qkv
15257 .as_ref()
15258 .ok_or("Step TP cache hydration lost its resident projections")?;
15259 let geometry = self.step35_geom(il);
15260 let window = geometry.window.map(|window| window as usize);
15261 let ranks = tp.runtime.devices().len();
15262 let head_dim = geometry.head_dim_k as usize;
15263 let kv_heads = geometry.n_head_kv as usize;
15264 let max_ctx = cache.max_ctx;
15265
15266 if cache.tp_kv[il].is_some() {
15267 return Ok(false);
15268 }
15269 let local = cache.kv[il]
15270 .as_ref()
15271 .ok_or_else(|| format!("Step TP layer {il} has no owning-stage KV cache"))?;
15272 if local.kv_dim_k != kv_heads * head_dim || local.kv_dim_v != kv_heads * head_dim {
15273 return Err(format!(
15274 "Step TP layer {il} local KV geometry k={} v={} != {}",
15275 local.kv_dim_k,
15276 local.kv_dim_v,
15277 kv_heads * head_dim
15278 )
15279 .into());
15280 }
15281 let resident_start = window
15282 .map(|window| local.len.saturating_sub(window.saturating_sub(1)) & !31usize)
15283 .unwrap_or(0);
15284 let resident_rows = local.len - resident_start;
15285 let physical = local.physical_rows(resident_start, local.len)?;
15286 let k_rows = if resident_rows == 0 {
15287 Vec::new()
15288 } else {
15289 e.dtoh_u8_view(&e.view_u8_range(
15290 &local.k,
15291 physical.start * local.k_tok_bytes,
15292 physical.end * local.k_tok_bytes,
15293 ))?
15294 };
15295 let v_rows = if resident_rows == 0 {
15296 Vec::new()
15297 } else {
15298 e.dtoh_u8_view(&e.view_u8_range(
15299 &local.v,
15300 physical.start * local.v_tok_bytes,
15301 physical.end * local.v_tok_bytes,
15302 ))?
15303 };
15304 let mut distributed = match window {
15305 Some(window) => tp.runtime.allocate_tp_swa_kv_cache(
15306 kv_heads * head_dim,
15307 kv_heads * head_dim,
15308 max_ctx,
15309 window,
15310 )?,
15311 None => tp.runtime.allocate_tp_kv_cache(
15312 kv_heads * head_dim,
15313 kv_heads * head_dim,
15314 max_ctx,
15315 )?,
15316 };
15317 if distributed.k_tok_bytes() * ranks != local.k_tok_bytes
15318 || distributed.v_tok_bytes() * ranks != local.v_tok_bytes
15319 {
15320 return Err(format!(
15321 "Step TP layer {il} distributed/local KV token bytes disagree: \
15322 k={}x{ranks}/{} v={}x{ranks}/{}",
15323 distributed.k_tok_bytes(),
15324 local.k_tok_bytes,
15325 distributed.v_tok_bytes(),
15326 local.v_tok_bytes,
15327 )
15328 .into());
15329 }
15330 tp.runtime.hydrate_tp_kv_cache_from(
15331 &mut distributed,
15332 local.len,
15333 resident_start,
15334 &k_rows,
15335 &v_rows,
15336 )?;
15337 cache.tp_kv[il] = Some(distributed);
15338 Ok(true)
15339 }
15340
15341 #[allow(clippy::too_many_arguments)]
15342 fn step35_tp_prefill_attn_resident(
15343 &self,
15344 e: &Engine,
15345 fa: &FullAttnLayer,
15346 il: usize,
15347 h: &CudaSlice<f32>,
15348 pos_d: &CudaSlice<i32>,
15349 tokens: usize,
15350 cache: &mut Cache,
15351 seq_end: usize,
15352 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15353 let tp = fa
15354 .step_tp_qkv
15355 .as_ref()
15356 .ok_or("Step TP prefill lost its resident projections")?;
15357 let attention = tp
15358 .attention
15359 .as_ref()
15360 .ok_or("Step TP prefill lost its resident attention auxiliaries")?;
15361 let ranks = tp.runtime.devices().len();
15362 if !step_tp_prefill_shape(
15363 true,
15364 tokens,
15365 ranks,
15366 tp.runtime.native_p2p(),
15367 true,
15368 crate::Engine::kv_fp8_on(),
15369 ) {
15370 return Err(format!(
15371 "rank-local Step prefill requires tokens>={PRIME_MIN_T}, TP4 native P2P, \
15372 rank-local attention, and q8_0/q5_1 KV; got tokens={tokens} ranks={ranks} \
15373 native_p2p={} fp8_kv={}",
15374 tp.runtime.native_p2p(),
15375 crate::Engine::kv_fp8_on(),
15376 )
15377 .into());
15378 }
15379 for seam in [
15380 "MEMRA_STEP35_SWA_TKV",
15381 "MEMRA_PRIME_CALLLOCAL",
15382 "MEMRA_PRIME_F32CHUNK0",
15383 ] {
15384 if std::env::var(seam).as_deref() == Ok("1") {
15385 return Err(format!(
15386 "rank-local Step prefill has not qualified the legacy seam {seam}=1"
15387 )
15388 .into());
15389 }
15390 }
15391
15392 let geometry = self.step35_geom(il);
15393 let window = geometry.window.map(|window| window as usize);
15394 let head_dim = geometry.head_dim_k as usize;
15395 let heads = geometry.n_head as usize;
15396 let kv_heads = geometry.n_head_kv as usize;
15397 if heads % ranks != 0 || kv_heads % ranks != 0 {
15398 return Err(format!(
15399 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15400 )
15401 .into());
15402 }
15403 let local_heads = heads / ranks;
15404 let local_kv_heads = kv_heads / ranks;
15405 let local_kv_dim = local_kv_heads * head_dim;
15406 let hidden = self.cfg.n_embd as usize;
15407 let expected_input = tokens
15408 .checked_mul(hidden)
15409 .ok_or("Step TP prefill input size overflow")?;
15410 if h.len() < expected_input {
15411 return Err(format!(
15412 "Step TP prefill input {} is shorter than {tokens}x{hidden}",
15413 h.len()
15414 )
15415 .into());
15416 }
15417 let positions = e.dtoh_i32(pos_d)?;
15418 if positions.len() != tokens {
15419 return Err(format!(
15420 "rank-local Step prefill positions {} != tokens {tokens}",
15421 positions.len()
15422 )
15423 .into());
15424 }
15425
15426 let mut active_input = e.uninit(expected_input)?;
15427 e.copy_view_into(
15428 &mut active_input,
15429 0,
15430 &h.slice(0..expected_input),
15431 expected_input,
15432 )?;
15433 let mut input = tp.runtime.allocate_replicated_device_rows(tokens, hidden)?;
15434 e.stream().synchronize()?;
15439 tp.runtime
15440 .refresh_replicated_device_rows_from_root(&mut input, &active_input)?;
15441 let q_raw = tp
15442 .runtime
15443 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &input)?;
15444 let k_raw = tp
15445 .runtime
15446 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &input)?;
15447 let v_raw = tp
15448 .runtime
15449 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &input)?;
15450 let mut q = Vec::with_capacity(ranks);
15451 let mut k = Vec::with_capacity(ranks);
15452 for rank in 0..ranks {
15453 let engine = tp
15454 .runtime
15455 .rank_engine(rank)
15456 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15457 let _main = engine.gpu.enter_main()?;
15458 let mut q_rank = engine.uninit(tokens * local_heads * head_dim)?;
15459 engine.rms_norm(
15460 &q_raw[rank],
15461 &attention.q_norm[rank],
15462 &mut q_rank,
15463 head_dim,
15464 tokens * local_heads,
15465 self.cfg.rms_eps,
15466 )?;
15467 let mut k_rank = engine.uninit(tokens * local_kv_dim)?;
15468 engine.rms_norm(
15469 &k_raw[rank],
15470 &attention.k_norm[rank],
15471 &mut k_rank,
15472 head_dim,
15473 tokens * local_kv_heads,
15474 self.cfg.rms_eps,
15475 )?;
15476 let position = engine.htod_i32(&positions)?;
15477 let rope_freqs = if geometry.rope_factors {
15478 self.step35_aux
15479 .as_ref()
15480 .and_then(|aux| aux.rope_freqs(engine))
15481 } else {
15482 None
15483 };
15484 engine.rope_neox2(
15485 &mut q_rank,
15486 &mut k_rank,
15487 &position,
15488 head_dim,
15489 geometry.n_rot as usize,
15490 local_heads,
15491 local_kv_heads,
15492 tokens,
15493 geometry.rope_base,
15494 1.0,
15495 rope_freqs,
15496 )?;
15497 q.push(q_rank);
15498 k.push(k_rank);
15499 }
15500
15501 let gate_weight = fa
15502 .attn_gate
15503 .as_ref()
15504 .ok_or("step35 layer is missing attn_gate.weight")?;
15505 let gate = e.dtoh(&e.matmul(gate_weight, h, tokens)?)?;
15506 if gate.len() != tokens * heads {
15507 return Err(format!(
15508 "Step TP layer {il} gate output {} != {tokens}x{heads}",
15509 gate.len()
15510 )
15511 .into());
15512 }
15513
15514 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15515 let base_len = cache.kv[il]
15516 .as_ref()
15517 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15518 .len;
15519 let distributed = cache.tp_kv[il]
15520 .as_ref()
15521 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15522 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15523 return Err(format!(
15524 "Step TP layer {il} cache lengths diverged before prefill: \
15525 local={base_len} distributed={}/{}",
15526 distributed.committed_len(),
15527 distributed.staged_len()
15528 )
15529 .into());
15530 }
15531 let target_len = base_len
15532 .checked_add(tokens)
15533 .ok_or("Step TP prefill cache length overflow")?;
15534 if target_len > cache.max_ctx {
15535 return Err(format!(
15536 "Step TP layer {il} prefill exceeds cache: {base_len}+{tokens}>{}",
15537 cache.max_ctx
15538 )
15539 .into());
15540 }
15541 if seq_end < target_len {
15542 return Err(format!(
15543 "Step TP layer {il} request end {seq_end} precedes chunk end {target_len}"
15544 )
15545 .into());
15546 }
15547
15548 let transaction = cache.tp_kv[il]
15549 .as_mut()
15550 .expect("distributed cache checked above")
15551 .begin_transaction()?;
15552 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15553 cache.tp_kv[il]
15554 .as_mut()
15555 .expect("distributed cache checked above"),
15556 transaction,
15557 &k,
15558 &v_raw,
15559 tokens,
15560 ) {
15561 let _ = tp.runtime.rollback_tp_kv_transaction(
15562 cache.tp_kv[il]
15563 .as_mut()
15564 .expect("distributed cache checked above"),
15565 transaction,
15566 );
15567 return Err(error);
15568 }
15569
15570 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15571 let distributed = cache.tp_kv[il]
15572 .as_ref()
15573 .expect("distributed cache checked above");
15574 let staged_len = distributed.staged_len();
15575 let view_start = window
15576 .map(|window| base_len.saturating_sub(window.saturating_sub(1)) & !31usize)
15577 .unwrap_or(0);
15578 let physical = distributed.physical_range(view_start, staged_len)?;
15579 let t_kv = staged_len - view_start;
15580 let swa_naive = window.is_some_and(|window| seq_end > window);
15581 let mut gated = Vec::with_capacity(ranks);
15582 for rank in 0..ranks {
15583 let engine = tp
15584 .runtime
15585 .rank_engine(rank)
15586 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15587 let _main = engine.gpu.enter_main()?;
15588 let rank_cache = distributed
15589 .rank(rank)
15590 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
15591 let k_view = engine.view_u8_range(
15592 rank_cache.k(),
15593 physical.start * distributed.k_tok_bytes(),
15594 physical.end * distributed.k_tok_bytes(),
15595 );
15596 let v_view = engine.view_u8_range(
15597 rank_cache.v(),
15598 physical.start * distributed.v_tok_bytes(),
15599 physical.end * distributed.v_tok_bytes(),
15600 );
15601 let mut attention_out = engine.uninit(tokens * local_heads * head_dim)?;
15602 if swa_naive {
15603 let window = window.expect("SWA predicate requires a window");
15604 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15605 engine.sdpa_naive_w_quantized_view(
15606 &q[rank],
15607 &k_view,
15608 &v_view,
15609 &mut attention_out,
15610 head_dim,
15611 local_heads,
15612 local_kv_heads,
15613 tokens,
15614 t_kv,
15615 geometry.attention_scale(),
15616 true,
15617 window,
15618 distributed.k_tok_bytes(),
15619 distributed.v_tok_bytes(),
15620 )?;
15621 } else {
15622 engine.fa_prefill_view_ws_w_hd128(
15623 &q[rank],
15624 &k_view,
15625 &v_view,
15626 &mut attention_out,
15627 head_dim,
15628 local_heads,
15629 local_kv_heads,
15630 tokens,
15631 t_kv,
15632 geometry.attention_scale(),
15633 true,
15634 window,
15635 distributed.k_tok_bytes(),
15636 distributed.v_tok_bytes(),
15637 )?;
15638 }
15639 } else if std::env::var("MEMRA_NOFA").is_ok() {
15640 engine.sdpa_naive_quantized_view(
15641 &q[rank],
15642 &k_view,
15643 &v_view,
15644 &mut attention_out,
15645 head_dim,
15646 local_heads,
15647 local_kv_heads,
15648 tokens,
15649 t_kv,
15650 geometry.attention_scale(),
15651 true,
15652 distributed.k_tok_bytes(),
15653 distributed.v_tok_bytes(),
15654 )?;
15655 } else {
15656 engine.fa_prefill_view_ws(
15657 &q[rank],
15658 &k_view,
15659 &v_view,
15660 &mut attention_out,
15661 head_dim,
15662 local_heads,
15663 local_kv_heads,
15664 tokens,
15665 t_kv,
15666 geometry.attention_scale(),
15667 true,
15668 distributed.k_tok_bytes(),
15669 distributed.v_tok_bytes(),
15670 false,
15671 )?;
15672 }
15673
15674 let gate_start = rank * local_heads;
15675 let mut gate_rank = Vec::with_capacity(tokens * local_heads);
15676 for token in 0..tokens {
15677 let start = token * heads + gate_start;
15678 gate_rank.extend_from_slice(&gate[start..start + local_heads]);
15679 }
15680 let gate_rank = engine.htod(&gate_rank)?;
15681 let mut gated_rank = engine.uninit(tokens * local_heads * head_dim)?;
15682 engine.attn_head_gate(
15683 &attention_out,
15684 &gate_rank,
15685 &mut gated_rank,
15686 None,
15687 head_dim,
15688 local_heads,
15689 tokens,
15690 )?;
15691 gated.push(gated_rank);
15692 }
15693 for rank in 1..ranks {
15694 let engine = tp
15695 .runtime
15696 .rank_engine(rank)
15697 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15698 let _main = engine.gpu.enter_main()?;
15699 engine.stream().synchronize()?;
15700 }
15701
15702 let (output, k_shadow, v_shadow) = if tp.runtime.bulk_p2p() {
15703 let output = tp
15704 .runtime
15705 .step_bf16_row_parallel_resident_root_device(&tp.o, &gated, tokens)?;
15706 let k_shadow =
15707 tp.runtime
15708 .gather_native_column_shards_device(&k, tokens, local_kv_dim)?;
15709 let v_shadow =
15710 tp.runtime
15711 .gather_native_column_shards_device(&v_raw, tokens, local_kv_dim)?;
15712 let root = tp
15713 .runtime
15714 .rank_engine(0)
15715 .ok_or("Step TP prefill lost its root engine")?;
15716 let _main = root.gpu.enter_main()?;
15717 root.stream().synchronize()?;
15718 (output, k_shadow, v_shadow)
15719 } else {
15720 let attention = tp.runtime.gather_native_column_shards(
15721 &gated,
15722 tokens,
15723 local_heads * head_dim,
15724 )?;
15725 let output = tp
15726 .runtime
15727 .step_bf16_row_parallel_resident_native(&tp.o, &attention, tokens)?;
15728 let k_shadow = tp
15729 .runtime
15730 .gather_native_column_shards(&k, tokens, local_kv_dim)?;
15731 let v_shadow =
15732 tp.runtime
15733 .gather_native_column_shards(&v_raw, tokens, local_kv_dim)?;
15734 (e.htod(&output)?, e.htod(&k_shadow)?, e.htod(&v_shadow)?)
15735 };
15736 let local = cache.kv[il]
15737 .as_mut()
15738 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
15739 if local.len != base_len {
15740 return Err(format!(
15741 "Step TP layer {il} local cache changed during prefill: \
15742 len={} base={base_len}",
15743 local.len
15744 )
15745 .into());
15746 }
15747 let retain_from = window
15748 .map(|window| {
15749 let staged_retain = staged_len.saturating_sub(window) & !31usize;
15750 let rollback_retain =
15751 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
15752 staged_retain.min(rollback_retain)
15753 })
15754 .unwrap_or(0);
15755 let write_row = e.prepare_kv_append(local, retain_from, tokens)?;
15756 e.append_kv_quantized_rows(
15757 &k_shadow,
15758 &v_shadow,
15759 &mut local.k,
15760 &mut local.v,
15761 write_row,
15762 tokens,
15763 local.kv_dim_k,
15764 local.kv_dim_v,
15765 local.k_tok_bytes,
15766 local.v_tok_bytes,
15767 false,
15768 )?;
15769 local.len = staged_len;
15770 e.set_i32_one(&mut local.len_d, staged_len as i32)?;
15771 Ok(output)
15772 })();
15773
15774 let output = match staged {
15775 Ok(output) => output,
15776 Err(error) => {
15777 let _ = tp.runtime.rollback_tp_kv_transaction(
15778 cache.tp_kv[il]
15779 .as_mut()
15780 .expect("distributed cache checked above"),
15781 transaction,
15782 );
15783 if let Some(local) = cache.kv[il].as_mut() {
15784 local.len = base_len;
15785 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
15786 }
15787 return Err(error);
15788 }
15789 };
15790 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
15791 cache.tp_kv[il]
15792 .as_mut()
15793 .expect("distributed cache checked above"),
15794 transaction,
15795 tokens,
15796 ) {
15797 let _ = tp.runtime.rollback_tp_kv_transaction(
15798 cache.tp_kv[il]
15799 .as_mut()
15800 .expect("distributed cache checked above"),
15801 transaction,
15802 );
15803 let local = cache.kv[il].as_mut().expect("local cache checked above");
15804 local.len = base_len;
15805 e.set_i32_one(&mut local.len_d, base_len as i32)?;
15806 return Err(error);
15807 }
15808
15809 let committed = cache.tp_kv[il]
15810 .as_ref()
15811 .expect("distributed cache checked above")
15812 .committed_len();
15813 let local_len = cache.kv[il]
15814 .as_ref()
15815 .expect("local cache checked above")
15816 .len;
15817 if committed != local_len {
15818 return Err(format!(
15819 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
15820 )
15821 .into());
15822 }
15823 eprintln!(
15824 "[step-tp-prefill-attn] execute layer={} devices={:?} tokens={tokens} \
15825 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
15826 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
15827 attention_scope={} input_path=root-device-replicated gate_tensor_parallel=false \
15828 gate_shards=host-canonical o_tensor_parallel=true local_cache_shadow=true \
15829 cache_commit=chunk transport={} native_p2p=true bulk_p2p={} \
15830 output={} performance_claim=false",
15831 tp.layer,
15832 tp.devices,
15833 hydrated,
15834 if window.is_some() {
15835 "rank-local-swa-ring"
15836 } else {
15837 "rank-local-global"
15838 },
15839 tp.runtime.transport_label(),
15840 tp.runtime.bulk_p2p(),
15841 if tp.runtime.bulk_p2p() {
15842 "root-device"
15843 } else {
15844 "root-readback"
15845 },
15846 );
15847 Ok(output)
15848 }
15849
15850 fn step35_tp_decode_attn_resident(
15851 &self,
15852 e: &Engine,
15853 fa: &FullAttnLayer,
15854 il: usize,
15855 h: &CudaSlice<f32>,
15856 pos_d: &CudaSlice<i32>,
15857 cache: &mut Cache,
15858 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15859 static ATTN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15863 static ATTN_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15864 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15865 let started = timing.then(std::time::Instant::now);
15866 let result = if crate::tp::step_tp_decode_v2_enabled()? {
15867 self.step35_tp_decode_attn_resident_v2(e, fa, il, h, pos_d, cache)
15868 } else {
15869 self.step35_tp_decode_attn_resident_inner(e, fa, il, h, pos_d, cache)
15870 };
15871 if let Some(started) = started {
15872 use std::sync::atomic::Ordering;
15873 let ns = ATTN_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
15874 + started.elapsed().as_nanos() as u64;
15875 let calls = ATTN_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
15876 if calls % 430 == 0 {
15877 eprintln!(
15878 "[step-tp-attn-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
15879 ns as f64 / 1.0e6,
15880 ns as f64 / calls as f64 / 1.0e3,
15881 );
15882 }
15883 }
15884 result
15885 }
15886
15887 #[allow(clippy::too_many_arguments)]
15888 fn step35_tp_decode_attn_resident_inner(
15889 &self,
15890 e: &Engine,
15891 fa: &FullAttnLayer,
15892 il: usize,
15893 h: &CudaSlice<f32>,
15894 pos_d: &CudaSlice<i32>,
15895 cache: &mut Cache,
15896 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15897 static T_POS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15902 static T_QKV: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15903 static T_NORMROPE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15904 static T_GATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15905 static T_APPEND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15906 static T_ATTN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15907 static T_OPROJ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15908 static T_SHADOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15909 static T_PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15910 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15911 fn lap(
15912 runtime: &crate::tp::TpE4m3HostBounce,
15913 e: &Engine,
15914 timer: &std::sync::atomic::AtomicU64,
15915 started: &mut Option<std::time::Instant>,
15916 ) -> Result<(), Box<dyn std::error::Error>> {
15917 let Some(start) = started.as_mut() else {
15918 return Ok(());
15919 };
15920 for rank in 0..runtime.devices().len() {
15921 if let Some(engine) = runtime.rank_engine(rank) {
15922 let _main = engine.gpu.enter_main()?;
15923 engine.stream().synchronize()?;
15924 }
15925 }
15926 e.stream().synchronize()?;
15927 timer.fetch_add(
15928 start.elapsed().as_nanos() as u64,
15929 std::sync::atomic::Ordering::Relaxed,
15930 );
15931 *start = std::time::Instant::now();
15932 Ok(())
15933 }
15934 let tp = fa
15935 .step_tp_qkv
15936 .as_ref()
15937 .ok_or("Step TP decode lost its resident projections")?;
15938 let attention = tp
15939 .attention
15940 .as_ref()
15941 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
15942 if !tp.runtime.native_p2p() {
15943 return Err("rank-local Step attention requires native P2P".into());
15944 }
15945 if crate::Engine::kv_fp8_on() {
15946 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
15947 }
15948
15949 let geometry = self.step35_geom(il);
15950 let window = geometry.window.map(|window| window as usize);
15951 let ranks = tp.runtime.devices().len();
15952 let head_dim = geometry.head_dim_k as usize;
15953 let heads = geometry.n_head as usize;
15954 let kv_heads = geometry.n_head_kv as usize;
15955 if heads % ranks != 0 || kv_heads % ranks != 0 {
15956 return Err(format!(
15957 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15958 )
15959 .into());
15960 }
15961 let local_heads = heads / ranks;
15962 let local_kv_heads = kv_heads / ranks;
15963 let local_kv_dim = local_kv_heads * head_dim;
15964 let max_ctx = cache.max_ctx;
15965
15966 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15967
15968 let base_len = cache.kv[il]
15969 .as_ref()
15970 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15971 .len;
15972 let distributed = cache.tp_kv[il]
15973 .as_ref()
15974 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15975 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15976 return Err(format!(
15977 "Step TP layer {il} cache lengths diverged before decode: \
15978 local={base_len} distributed={}/{}",
15979 distributed.committed_len(),
15980 distributed.staged_len()
15981 )
15982 .into());
15983 }
15984
15985 let mut lap_start = timing.then(std::time::Instant::now);
15986 let positions = e.dtoh_i32(pos_d)?;
15987 if positions.len() != 1 {
15988 return Err(format!(
15989 "rank-local Step decode requires one position, got {}",
15990 positions.len()
15991 )
15992 .into());
15993 }
15994 lap(&tp.runtime, e, &T_POS, &mut lap_start)?;
15995 let (q_raw, k_raw, v_raw, input_path) = if let Some(decode_input) =
15996 attention.decode_input.as_ref()
15997 {
15998 let mut decode_input = decode_input
15999 .lock()
16000 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16001 e.stream().synchronize()?;
16005 tp.runtime
16006 .refresh_replicated_device_rows_from_root(&mut decode_input, h)?;
16007 let q_raw = tp
16008 .runtime
16009 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &decode_input)?;
16010 let k_raw = tp
16011 .runtime
16012 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &decode_input)?;
16013 let v_raw = tp
16014 .runtime
16015 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &decode_input)?;
16016 (q_raw, k_raw, v_raw, "root-device-replicated")
16017 } else {
16018 let activation = e.dtoh(h)?;
16019 let q_raw =
16020 tp.runtime
16021 .bf16_column_parallel_resident_device_shards(&tp.q, &activation, 1)?;
16022 let k_raw =
16023 tp.runtime
16024 .bf16_column_parallel_resident_device_shards(&tp.k, &activation, 1)?;
16025 let v_raw =
16026 tp.runtime
16027 .bf16_column_parallel_resident_device_shards(&tp.v, &activation, 1)?;
16028 (q_raw, k_raw, v_raw, "host-replicated")
16029 };
16030 lap(&tp.runtime, e, &T_QKV, &mut lap_start)?;
16031 let mut q = Vec::with_capacity(ranks);
16032 let mut k = Vec::with_capacity(ranks);
16033 for rank in 0..ranks {
16034 let engine = tp
16035 .runtime
16036 .rank_engine(rank)
16037 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16038 let _main = engine.gpu.enter_main()?;
16039 let mut q_rank = engine.uninit(local_heads * head_dim)?;
16040 engine.rms_norm(
16041 &q_raw[rank],
16042 &attention.q_norm[rank],
16043 &mut q_rank,
16044 head_dim,
16045 local_heads,
16046 self.cfg.rms_eps,
16047 )?;
16048 let mut k_rank = engine.uninit(local_kv_dim)?;
16049 engine.rms_norm(
16050 &k_raw[rank],
16051 &attention.k_norm[rank],
16052 &mut k_rank,
16053 head_dim,
16054 local_kv_heads,
16055 self.cfg.rms_eps,
16056 )?;
16057 let position = engine.htod_i32(&positions)?;
16058 let rope_freqs = if geometry.rope_factors {
16059 self.step35_aux
16060 .as_ref()
16061 .and_then(|aux| aux.rope_freqs(engine))
16062 } else {
16063 None
16064 };
16065 engine.rope_neox2(
16066 &mut q_rank,
16067 &mut k_rank,
16068 &position,
16069 head_dim,
16070 geometry.n_rot as usize,
16071 local_heads,
16072 local_kv_heads,
16073 1,
16074 geometry.rope_base,
16075 1.0,
16076 rope_freqs,
16077 )?;
16078 q.push(q_rank);
16079 k.push(k_rank);
16080 }
16081 lap(&tp.runtime, e, &T_NORMROPE, &mut lap_start)?;
16082
16083 let gate_weight = fa
16084 .attn_gate
16085 .as_ref()
16086 .ok_or("step35 layer is missing attn_gate.weight")?;
16087 let gate = e.matmul(gate_weight, h, 1)?;
16088 let gate = e.dtoh(&gate)?;
16089 if gate.len() != heads {
16090 return Err(format!("Step TP layer {il} gate output {} != {heads}", gate.len()).into());
16091 }
16092 lap(&tp.runtime, e, &T_GATE, &mut lap_start)?;
16093
16094 let transaction = cache.tp_kv[il]
16095 .as_mut()
16096 .expect("distributed cache checked above")
16097 .begin_transaction()?;
16098 if let Err(error) = tp.runtime.append_tp_kv_transaction(
16099 cache.tp_kv[il]
16100 .as_mut()
16101 .expect("distributed cache checked above"),
16102 transaction,
16103 &k,
16104 &v_raw,
16105 1,
16106 ) {
16107 let _ = tp.runtime.rollback_tp_kv_transaction(
16108 cache.tp_kv[il]
16109 .as_mut()
16110 .expect("distributed cache checked above"),
16111 transaction,
16112 );
16113 return Err(error);
16114 }
16115 lap(&tp.runtime, e, &T_APPEND, &mut lap_start)?;
16116
16117 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16118 let distributed = cache.tp_kv[il]
16119 .as_ref()
16120 .expect("distributed cache checked above");
16121 let staged_len = distributed.staged_len();
16122 let view_start = window
16123 .map(|window| staged_len.saturating_sub(window))
16124 .unwrap_or(0);
16125 let physical = distributed.physical_range(view_start, staged_len)?;
16126 let t_kv = staged_len - view_start;
16127 let mut gated = Vec::with_capacity(ranks);
16128 for rank in 0..ranks {
16129 let engine = tp
16130 .runtime
16131 .rank_engine(rank)
16132 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16133 let _main = engine.gpu.enter_main()?;
16134 let rank_cache = distributed
16135 .rank(rank)
16136 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16137 let k_view = engine.view_u8_range(
16138 rank_cache.k(),
16139 physical.start * distributed.k_tok_bytes(),
16140 physical.end * distributed.k_tok_bytes(),
16141 );
16142 let v_view = engine.view_u8_range(
16143 rank_cache.v(),
16144 physical.start * distributed.v_tok_bytes(),
16145 physical.end * distributed.v_tok_bytes(),
16146 );
16147 let mut attention_out = engine.uninit(local_heads * head_dim)?;
16148 engine.fa_decode_kvmod(
16149 &q[rank],
16150 &k_view,
16151 &v_view,
16152 &mut attention_out,
16153 head_dim,
16154 local_heads,
16155 local_kv_heads,
16156 t_kv,
16157 geometry.attention_scale(),
16158 distributed.k_tok_bytes(),
16159 distributed.v_tok_bytes(),
16160 false,
16161 )?;
16162 let gate_start = rank * local_heads;
16163 let gate_rank = engine.htod(&gate[gate_start..gate_start + local_heads])?;
16164 let mut gated_rank = engine.uninit(local_heads * head_dim)?;
16165 engine.attn_head_gate(
16166 &attention_out,
16167 &gate_rank,
16168 &mut gated_rank,
16169 None,
16170 head_dim,
16171 local_heads,
16172 1,
16173 )?;
16174 gated.push(gated_rank);
16175 }
16176 lap(&tp.runtime, e, &T_ATTN, &mut lap_start)?;
16177
16178 let gathered =
16179 tp.runtime
16180 .gather_native_column_shards(&gated, 1, local_heads * head_dim)?;
16181 let output = tp
16182 .runtime
16183 .step_bf16_row_parallel_resident_native(&tp.o, &gathered, 1)?;
16184 let output = e.htod(&output)?;
16185 lap(&tp.runtime, e, &T_OPROJ, &mut lap_start)?;
16186
16187 let k_shadow = tp
16188 .runtime
16189 .gather_native_column_shards(&k, 1, local_kv_dim)?;
16190 let v_shadow = tp
16191 .runtime
16192 .gather_native_column_shards(&v_raw, 1, local_kv_dim)?;
16193 let k_shadow = e.htod(&k_shadow)?;
16194 let v_shadow = e.htod(&v_shadow)?;
16195 let local = cache.kv[il]
16196 .as_mut()
16197 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16198 if local.len != base_len || base_len + 1 > max_ctx {
16199 return Err(format!(
16200 "Step TP layer {il} local cache changed during decode: \
16201 len={} base={base_len} max={max_ctx}",
16202 local.len
16203 )
16204 .into());
16205 }
16206 let retain_from = window
16207 .map(|window| {
16208 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16209 let rollback_retain =
16210 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16211 staged_retain.min(rollback_retain)
16212 })
16213 .unwrap_or(0);
16214 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16215 e.append_kv_quantized(
16216 &k_shadow,
16217 &v_shadow,
16218 &mut local.k,
16219 &mut local.v,
16220 write_row,
16221 local.kv_dim_k,
16222 local.kv_dim_v,
16223 local.k_tok_bytes,
16224 local.v_tok_bytes,
16225 false,
16226 )?;
16227 local.len = base_len + 1;
16228 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16229 Ok(output)
16230 })();
16231
16232 let output = match staged {
16233 Ok(output) => output,
16234 Err(error) => {
16235 let _ = tp.runtime.rollback_tp_kv_transaction(
16236 cache.tp_kv[il]
16237 .as_mut()
16238 .expect("distributed cache checked above"),
16239 transaction,
16240 );
16241 if let Some(local) = cache.kv[il].as_mut() {
16242 local.len = base_len;
16243 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16244 }
16245 return Err(error);
16246 }
16247 };
16248 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16249 cache.tp_kv[il]
16250 .as_mut()
16251 .expect("distributed cache checked above"),
16252 transaction,
16253 1,
16254 ) {
16255 let _ = tp.runtime.rollback_tp_kv_transaction(
16256 cache.tp_kv[il]
16257 .as_mut()
16258 .expect("distributed cache checked above"),
16259 transaction,
16260 );
16261 let local = cache.kv[il].as_mut().expect("local cache checked above");
16262 local.len = base_len;
16263 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16264 return Err(error);
16265 }
16266
16267 let committed = cache.tp_kv[il]
16268 .as_ref()
16269 .expect("distributed cache checked above")
16270 .committed_len();
16271 let local_len = cache.kv[il]
16272 .as_ref()
16273 .expect("local cache checked above")
16274 .len;
16275 if committed != local_len {
16276 return Err(format!(
16277 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16278 )
16279 .into());
16280 }
16281 lap(&tp.runtime, e, &T_SHADOW, &mut lap_start)?;
16282 if timing {
16283 use std::sync::atomic::Ordering;
16284 let calls = T_PHASE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16285 if calls % 430 == 0 {
16286 let avg = |t: &std::sync::atomic::AtomicU64| {
16287 t.load(Ordering::Relaxed) as f64 / calls as f64 / 1.0e3
16288 };
16289 eprintln!(
16290 "[step-tp-attn-phase] calls={calls} avg_us pos={:.1} qkv={:.1} \
16291 normrope={:.1} gate={:.1} append={:.1} attn={:.1} oproj={:.1} shadow={:.1}",
16292 avg(&T_POS),
16293 avg(&T_QKV),
16294 avg(&T_NORMROPE),
16295 avg(&T_GATE),
16296 avg(&T_APPEND),
16297 avg(&T_ATTN),
16298 avg(&T_OPROJ),
16299 avg(&T_SHADOW),
16300 );
16301 }
16302 }
16303 eprintln!(
16304 "[step-tp-attn] execute layer={} devices={:?} tokens=1 \
16305 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16306 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16307 attention_scope={} input_path={} kv_physical_rows={} \
16308 gate_tensor_parallel=false gate_shards=host-canonical o_tensor_parallel=true \
16309 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16310 bulk_p2p={} output=root-readback performance_claim=false",
16311 tp.layer,
16312 tp.devices,
16313 hydrated,
16314 if window.is_some() {
16315 "rank-local-swa-ring"
16316 } else {
16317 "rank-local-global"
16318 },
16319 input_path,
16320 cache.tp_kv[il]
16321 .as_ref()
16322 .expect("distributed cache checked above")
16323 .physical_capacity(),
16324 tp.runtime.transport_label(),
16325 tp.runtime.bulk_p2p(),
16326 );
16327 Ok(output)
16328 }
16329
16330 #[allow(clippy::too_many_arguments)]
16337 pub(crate) fn step35_verify_qkv_precompute(
16342 &self,
16343 e: &Engine,
16344 il: usize,
16345 h_t: &CudaSlice<f32>,
16346 t: usize,
16347 ) -> Result<bool, Box<dyn std::error::Error>> {
16348 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16349 return Ok(false);
16350 };
16351 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16352 return Ok(false);
16353 };
16354 let Some(attention) = tp.attention.as_ref() else {
16355 return Ok(false);
16356 };
16357 if !tp.runtime.native_p2p() || !crate::tp::step_tp_qkv_fused_enabled()? {
16358 return Ok(false);
16359 }
16360 let geometry = self.step35_geom(il);
16361 let heads = geometry.n_head as usize;
16362 let ws_index = tp
16363 .runtime
16364 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16365 let gate_shards = attention
16366 .gate_shards_bf16
16367 .as_deref()
16368 .map(crate::tp::StepTpGateShards::Bf16);
16369 tp.runtime.decode_v2_input_qkv_tcol(
16370 ws_index,
16371 e,
16372 h_t,
16373 t,
16374 &tp.q,
16375 &tp.k,
16376 &tp.v,
16377 gate_shards,
16378 )?;
16379 Ok(true)
16380 }
16381
16382 pub(crate) fn step35_verify_oproj_tcol(
16387 &self,
16388 e: &Engine,
16389 il: usize,
16390 t: usize,
16391 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16392 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16393 return Err("tcol o_proj join expects full attention".into());
16394 };
16395 let tp = fa
16396 .step_tp_qkv
16397 .as_ref()
16398 .ok_or("tcol o_proj join lost its resident projections")?;
16399 let heads = self.step35_geom(il).n_head as usize;
16400 let ws_index = tp
16401 .runtime
16402 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16403 tp.runtime.decode_v2_oproj_tcol(ws_index, e, &tp.o, t)
16404 }
16405
16406 pub(crate) fn step35_spec_fa2_precheck(
16413 &self,
16414 cache: &Cache,
16415 il: usize,
16416 pos0: usize,
16417 ) -> Result<bool, Box<dyn std::error::Error>> {
16418 fn nope(clause: &str, il: usize, pos0: usize) -> bool {
16421 static DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16422 static SEEN: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
16423 if *DBG.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_DEBUG").as_deref() == Ok("1")) {
16424 let mut seen = SEEN.lock().unwrap();
16425 if !seen.iter().any(|c| *c == clause) {
16426 seen.push(Box::leak(clause.to_string().into_boxed_str()));
16428 eprintln!("[spec-fa2] precheck FAIL clause={clause} il={il} pos0={pos0}");
16429 }
16430 }
16431 false
16432 }
16433 static ONLY: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
16435 if let Some(only) =
16436 ONLY.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_LAYER").ok()?.parse().ok())
16437 {
16438 if *only != il {
16439 return Ok(false);
16440 }
16441 }
16442 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16443 return Ok(nope("mixer", il, pos0));
16444 };
16445 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16446 return Ok(nope("step_tp", il, pos0));
16447 };
16448 let Some(attention) = tp.attention.as_ref() else {
16449 return Ok(nope("attention", il, pos0));
16450 };
16451 if !tp.runtime.native_p2p()
16452 || crate::Engine::kv_fp8_on()
16453 || !crate::tp::step_tp_dcw_enabled()?
16454 || !crate::tp::step_tp_qkv_fused_enabled()?
16455 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16456 {
16457 return Ok(nope("runtime-doors", il, pos0));
16458 }
16459 let geometry = self.step35_geom(il);
16460 let head_dim = geometry.head_dim_k as usize;
16461 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16462 return Ok(nope("fa-class", il, pos0));
16463 }
16464 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16465 return Ok(nope("tp-kv", il, pos0));
16466 };
16467 if distributed.staged_len() != pos0 {
16468 return Ok(nope("staged-len", il, pos0));
16469 }
16470 let (_, would_rebase) = distributed.peek_append_ring(2)?;
16473 if would_rebase {
16474 return Ok(nope("rebase", il, pos0));
16475 }
16476 let window = geometry.window.map(|w| w as usize);
16477 if let Some(w) = window {
16485 if pos0 + 2 > w {
16486 return Ok(nope("swa-capped", il, pos0));
16487 }
16488 }
16489 let (t0, t1) = (pos0 + 1, pos0 + 2);
16493 if t0 < 96 {
16494 return Ok(nope("dcw-floor", il, pos0));
16495 }
16496 if std::env::var("MEMRA_NO_FA_VEC").is_ok() || t0 < crate::fa_vec_min_tkv() {
16497 return Ok(nope("vec-floor", il, pos0));
16498 }
16499 let ranks = tp.runtime.devices().len();
16505 let local_kv_heads = (geometry.n_head_kv as usize / ranks).max(1);
16506 let sp0 = crate::fa_split_keys_pub(t0, local_kv_heads);
16507 let sp1 = crate::fa_split_keys_pub(t1, local_kv_heads);
16508 if sp0 != sp1 {
16509 return Ok(nope("partition-sp", il, pos0));
16510 }
16511 let (ns0, ns1) = (t0.div_ceil(sp0), t1.div_ceil(sp1));
16512 if ns0 != ns1 {
16513 return Ok(nope("partition-ns", il, pos0));
16514 }
16515 if t0.div_ceil(ns0) != t1.div_ceil(ns1) {
16516 return Ok(nope("partition-per", il, pos0));
16517 }
16518 Ok(true)
16519 }
16520
16521 pub(crate) fn step35_verify_spec_fa2_join(
16525 &self,
16526 e: &Engine,
16527 il: usize,
16528 cache: &Cache,
16529 pos0: usize,
16530 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16531 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16532 return Err("spec fa2 join expects full attention".into());
16533 };
16534 let tp = fa
16535 .step_tp_qkv
16536 .as_ref()
16537 .ok_or("spec fa2 join lost its resident projections")?;
16538 let geometry = self.step35_geom(il);
16539 let heads = geometry.n_head as usize;
16540 let head_dim = geometry.head_dim_k as usize;
16541 let window = geometry.window.map(|w| w as usize);
16542 let bucket = window.map(|w| (pos0 + 2).min(w)).unwrap_or(pos0 + 2);
16545 let distributed = cache.tp_kv[il]
16546 .as_ref()
16547 .ok_or("spec fa2 join lost its distributed KV cache")?;
16548 let ws_index = tp
16549 .runtime
16550 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16551 tp.runtime.decode_v2_spec_fa2_join(
16552 ws_index,
16553 e,
16554 &tp.o,
16555 distributed,
16556 head_dim,
16557 window.unwrap_or(0),
16558 bucket,
16559 geometry.attention_scale(),
16560 )
16561 }
16562
16563 pub(crate) fn step35_verify_moe_tn(
16569 &self,
16570 e: &Engine,
16571 il: usize,
16572 z_t: &CudaSlice<f32>,
16573 t: usize,
16574 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16575 let layer = &self.layers[il];
16576 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
16577 return Ok(None);
16578 };
16579 let Some(tp) = m.step_tp.as_ref() else {
16580 return Ok(None);
16581 };
16582 let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts else {
16583 return Ok(None);
16584 };
16585 if !crate::tp::step_nvfp4_dev_routes_enabled()?
16586 || !crate::tp::step_tp_dev_router_enabled()?
16587 || !crate::tp::nvfp4_bank_v2_on()
16588 || bank.ep2
16589 {
16590 return Ok(None);
16591 }
16592 let cfg = &self.cfg;
16593 let Some(moe) = cfg.moe.as_ref() else {
16594 return Ok(None);
16595 };
16596 let Some((sf, route_norm)) = cfg.sigmoid_router() else {
16597 return Ok(None);
16598 };
16599 let n_embd = cfg.n_embd as usize;
16600 let n_expert = moe.expert_count as usize;
16601 let n_used = moe.expert_used_count as usize;
16602 if t < 2 || t > 8 || z_t.len() < t * n_embd {
16603 return Err("verify moe t-row geometry".into());
16604 }
16605 let trace = std::env::var("MEMRA_TN_TRACE").as_deref() == Ok("1");
16606 if trace {
16607 eprintln!("[tn-trace] il={il} t={t} logits");
16608 }
16609 let logits = Self::moe_router_logits(e, m, z_t, t, cfg)?;
16610 static SELW2: std::sync::Mutex<Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>> =
16613 std::sync::Mutex::new(None);
16614 let mut selw = SELW2.lock().map_err(|_| "selw2 lock poisoned")?;
16615 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
16616 *selw = Some((
16617 e.ctx().ordinal(),
16618 e.htod_i32(&vec![0i32; 8 * n_used])?,
16619 e.htod(&vec![0.0f32; 8 * n_used])?,
16620 ));
16621 }
16622 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
16623 if trace {
16624 eprintln!("[tn-trace] il={il} topk logits_len={}", logits.len());
16625 }
16626 e.moe_router_sigmoid_topk_into(
16627 &logits,
16628 t,
16629 n_expert,
16630 n_used,
16631 m.active_count(),
16632 &m.exp_probs_b_dev,
16633 &m.active_experts_dev,
16634 sf,
16635 route_norm,
16636 sel_d,
16637 w_d,
16638 )?;
16639 if trace {
16640 eprintln!("[tn-trace] il={il} driver");
16641 }
16642 let mut out_t = tp
16643 .runtime
16644 .run_tensor_parallel_routes_nvfp4_device_routed_tn(
16645 bank,
16646 e,
16647 z_t,
16648 sel_d,
16649 w_d,
16650 t,
16651 n_used,
16652 tp.activation_limit,
16653 )?;
16654 if trace {
16655 eprintln!("[tn-trace] il={il} shexp out_t={}", out_t.len());
16656 }
16657 let mut z_row = e.uninit(n_embd)?;
16659 let mut out_row = e.uninit(n_embd)?;
16660 for c in 0..t {
16661 e.dtod_copy_view(&z_t.slice(c * n_embd..(c + 1) * n_embd), &mut z_row)?;
16662 e.dtod_copy_view(&out_t.slice(c * n_embd..(c + 1) * n_embd), &mut out_row)?;
16663 Self::moe_ffn_grouped_add_shared(e, m, &z_row, 1, cfg, il as u16, &mut out_row)?;
16664 e.dtod_copy_into(&out_row, &mut out_t, c * n_embd)?;
16665 }
16666 Ok(Some(out_t))
16667 }
16668
16669 fn step35_tp_decode_attn_resident_v2(
16670 &self,
16671 e: &Engine,
16672 fa: &FullAttnLayer,
16673 il: usize,
16674 h: &CudaSlice<f32>,
16675 pos_d: &CudaSlice<i32>,
16676 cache: &mut Cache,
16677 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16678 let tp = fa
16679 .step_tp_qkv
16680 .as_ref()
16681 .ok_or("Step TP decode lost its resident projections")?;
16682 let attention = tp
16683 .attention
16684 .as_ref()
16685 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
16686 if !tp.runtime.native_p2p() {
16687 return Err("rank-local Step attention requires native P2P".into());
16688 }
16689 if crate::Engine::kv_fp8_on() {
16690 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
16691 }
16692
16693 let geometry = self.step35_geom(il);
16694 let window = geometry.window.map(|window| window as usize);
16695 let ranks = tp.runtime.devices().len();
16696 let head_dim = geometry.head_dim_k as usize;
16697 let heads = geometry.n_head as usize;
16698 let kv_heads = geometry.n_head_kv as usize;
16699 if heads % ranks != 0 || kv_heads % ranks != 0 {
16700 return Err(format!(
16701 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
16702 )
16703 .into());
16704 }
16705 let local_heads = heads / ranks;
16706 let local_kv_heads = kv_heads / ranks;
16707 let max_ctx = cache.max_ctx;
16708
16709 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
16710
16711 let base_len = cache.kv[il]
16712 .as_ref()
16713 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
16714 .len;
16715 {
16716 let distributed = cache.tp_kv[il]
16717 .as_ref()
16718 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
16719 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
16720 return Err(format!(
16721 "Step TP layer {il} cache lengths diverged before decode: \
16722 local={base_len} distributed={}/{}",
16723 distributed.committed_len(),
16724 distributed.staged_len()
16725 )
16726 .into());
16727 }
16728 }
16729 if pos_d.len() != 1 {
16730 return Err(format!(
16731 "rank-local Step decode requires one position, got {}",
16732 pos_d.len()
16733 )
16734 .into());
16735 }
16736
16737 let decode_input = attention
16738 .decode_input
16739 .as_ref()
16740 .ok_or("Step TP decode v2 requires the replicated decode input")?;
16741 let mut decode_input = decode_input
16742 .lock()
16743 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16744
16745 let use_gate_shards = (attention.gate_shards.is_some()
16750 || attention.gate_shards_bf16.is_some())
16751 && crate::tp::step_tp_qkv_fused_enabled()?;
16752 let gate_raw = if use_gate_shards {
16753 None
16754 } else {
16755 let gate_weight = fa
16756 .attn_gate
16757 .as_ref()
16758 .ok_or("step35 layer is missing attn_gate.weight")?;
16759 let gate_raw = e.matmul(gate_weight, h, 1)?;
16760 if gate_raw.len() != heads {
16761 return Err(format!(
16762 "Step TP layer {il} gate output {} != {heads}",
16763 gate_raw.len()
16764 )
16765 .into());
16766 }
16767 Some(gate_raw)
16768 };
16769
16770 let ws_index = tp
16771 .runtime
16772 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16773 let mut ws_guard = tp
16774 .runtime
16775 .decode_v2_workspace()
16776 .lock()
16777 .map_err(|_| "Step TP decode v2 workspace lock is poisoned")?;
16778 let ws = ws_guard
16779 .get_mut(ws_index)
16780 .ok_or("Step TP decode v2 workspace missing after ensure")?;
16781
16782 let mut rope_freqs = Vec::with_capacity(ranks);
16783 for rank in 0..ranks {
16784 let engine = tp
16785 .runtime
16786 .rank_engine(rank)
16787 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16788 rope_freqs.push(if geometry.rope_factors {
16789 self.step35_aux
16790 .as_ref()
16791 .and_then(|aux| aux.rope_freqs(engine))
16792 } else {
16793 None
16794 });
16795 }
16796 let staged_next = base_len + 1;
16803 let t_kv_eff = window
16804 .map(|window| staged_next.min(window))
16805 .unwrap_or(staged_next);
16806 let dcw = crate::tp::step_tp_dcw_enabled()? && use_gate_shards && t_kv_eff >= 96 && {
16807 let (write_row, would_rebase) = cache.tp_kv[il]
16808 .as_ref()
16809 .expect("distributed cache checked above")
16810 .peek_append_ring(1)?;
16811 if !would_rebase {
16812 let base = (base_len - write_row) as i32;
16814 let distributed = cache.tp_kv[il]
16815 .as_mut()
16816 .expect("distributed cache checked above");
16817 for rank in 0..ranks {
16818 let engine = tp.runtime.rank_engine(rank).ok_or_else(|| {
16819 format!("Step TP layer {il} has no engine for rank {rank}")
16820 })?;
16821 let _main = engine.gpu.enter_main()?;
16822 let rank_cache = distributed
16823 .rank_mut(rank)
16824 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16825 if rank_cache.base_d().is_none() {
16826 rank_cache.arm_base_d(engine.htod_i32(&[base])?);
16827 }
16828 }
16829 }
16830 !would_rebase
16831 };
16832 let fuse_rope = dcw
16833 && crate::tp::fuse_rope_append_on()
16834 && head_dim == 128
16835 && cache.tp_kv[il]
16836 .as_ref()
16837 .map(|d| d.kv_dim_k() == d.kv_dim_v() && d.kv_dim_k() == local_kv_heads * head_dim)
16838 .unwrap_or(false);
16839
16840 let tcol_col = crate::tp::take_verify_tcol();
16841 let fa2_col = crate::tp::take_spec_fa2_defer();
16848 tp.runtime.decode_v2_input_qkv(
16849 ws,
16850 e,
16851 h,
16852 pos_d,
16853 gate_raw.as_ref(),
16854 if !use_gate_shards {
16855 None
16856 } else if let Some(shards) = attention.gate_shards.as_deref() {
16857 Some(crate::tp::StepTpGateShards::F32(shards))
16858 } else {
16859 attention
16860 .gate_shards_bf16
16861 .as_deref()
16862 .map(crate::tp::StepTpGateShards::Bf16)
16863 },
16864 &mut decode_input,
16865 &tp.q,
16866 &tp.k,
16867 &tp.v,
16868 &attention.q_norm,
16869 &attention.k_norm,
16870 head_dim,
16871 geometry.n_rot as usize,
16872 geometry.rope_base,
16873 &rope_freqs,
16874 self.cfg.rms_eps,
16875 fuse_rope,
16876 tcol_col,
16877 )?;
16878
16879 let transaction = cache.tp_kv[il]
16880 .as_mut()
16881 .expect("distributed cache checked above")
16882 .begin_transaction()?;
16883 let append_result = tp.runtime.append_tp_kv_transaction_inner(
16884 cache.tp_kv[il]
16885 .as_mut()
16886 .expect("distributed cache checked above"),
16887 transaction,
16888 &ws.k,
16889 &ws.v_raw,
16890 1,
16891 dcw,
16892 );
16893 if let Err(error) = append_result {
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 return Err(error);
16901 }
16902
16903 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16904 let (staged_len, physical, k_tok_bytes_c, v_tok_bytes_c, capacity) = {
16907 let distributed = cache.tp_kv[il]
16908 .as_ref()
16909 .expect("distributed cache checked above");
16910 let staged_len = distributed.staged_len();
16911 let view_start = window
16912 .map(|window| staged_len.saturating_sub(window))
16913 .unwrap_or(0);
16914 (
16915 staged_len,
16916 distributed.physical_range(view_start, staged_len)?,
16917 distributed.k_tok_bytes(),
16918 distributed.v_tok_bytes(),
16919 distributed.physical_capacity(),
16920 )
16921 };
16922 let view_start = window
16923 .map(|window| staged_len.saturating_sub(window))
16924 .unwrap_or(0);
16925 let t_kv = staged_len - view_start;
16926 for rank in 0..ranks {
16927 let engine = tp
16928 .runtime
16929 .rank_engine(rank)
16930 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16931 let _main = engine.gpu.enter_main()?;
16932 if dcw {
16933 {
16937 let distributed_mut = cache.tp_kv[il]
16938 .as_mut()
16939 .expect("distributed cache checked above");
16940 let (kv_dim_k, kv_dim_v) =
16941 (distributed_mut.kv_dim_k(), distributed_mut.kv_dim_v());
16942 let (k_tok_bytes, v_tok_bytes) =
16943 (distributed_mut.k_tok_bytes(), distributed_mut.v_tok_bytes());
16944 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
16945 format!("Step TP layer {il} has no KV cache rank {rank}")
16946 })?;
16947 let (k_plane, v_plane, len_d, base_d) =
16948 rank_cache.planes_and_counters_mut();
16949 if fuse_rope {
16950 let same_dev = engine.ctx().ordinal() == e.ctx().ordinal();
16953 let crate::tp::StepTpDecodeV2Ws {
16954 q_raw,
16955 k_raw,
16956 v_raw,
16957 q,
16958 k,
16959 pos,
16960 pos_stage,
16961 fuse_ctr,
16962 ..
16963 } = &mut *ws;
16964 let pos_ref: &CudaSlice<i32> = if same_dev {
16968 pos_stage
16969 .as_ref()
16970 .ok_or("step TP decode v2 pos stage not armed")?
16971 } else {
16972 &pos[rank]
16973 };
16974 engine.qk_norm_rope_append_inc_dcw(
16975 &q_raw[rank],
16976 &k_raw[rank],
16977 &v_raw[rank],
16978 &attention.q_norm[rank],
16979 &attention.k_norm[rank],
16980 &mut q[rank],
16981 &mut k[rank],
16982 pos_ref,
16983 k_plane,
16984 v_plane,
16985 len_d,
16986 base_d,
16987 &mut fuse_ctr[rank],
16988 kv_dim_k,
16989 kv_dim_v,
16990 k_tok_bytes,
16991 v_tok_bytes,
16992 head_dim,
16993 geometry.n_rot as usize,
16994 local_heads,
16995 local_kv_heads,
16996 self.cfg.rms_eps,
16997 geometry.rope_base,
16998 1.0,
16999 rope_freqs[rank],
17000 )?;
17001 } else {
17002 engine.append_kv_quantized_dcw(
17003 &ws.k[rank],
17004 &ws.v_raw[rank],
17005 k_plane,
17006 v_plane,
17007 len_d,
17008 base_d,
17009 kv_dim_k,
17010 kv_dim_v,
17011 k_tok_bytes,
17012 v_tok_bytes,
17013 )?;
17014 }
17015 if !fuse_rope {
17016 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
17017 format!("Step TP layer {il} has no KV cache rank {rank}")
17018 })?;
17019 engine.inc_i32(rank_cache.len_d_mut())?;
17020 }
17021 }
17022 let distributed = cache.tp_kv[il]
17023 .as_ref()
17024 .expect("distributed cache checked above");
17025 let rank_cache = distributed
17026 .rank(rank)
17027 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17028 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes_c);
17029 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes_c);
17030 if fa2_col.is_some() {
17031 continue;
17034 }
17035 {
17036 let crate::tp::StepTpDecodeV2Ws { q, gate, gated, .. } = &mut *ws;
17039 engine.fa_decode_dcw(
17040 &q[rank],
17041 &k_ring,
17042 &v_ring,
17043 &mut gated[rank],
17044 head_dim,
17045 local_heads,
17046 local_kv_heads,
17047 rank_cache.len_d(),
17048 rank_cache.base_d(),
17049 window.unwrap_or(0),
17050 t_kv,
17051 geometry.attention_scale(),
17052 k_tok_bytes_c,
17053 v_tok_bytes_c,
17054 Some(&gate[rank]),
17055 )?;
17056 }
17057 continue;
17058 }
17059 let distributed = cache.tp_kv[il]
17060 .as_ref()
17061 .expect("distributed cache checked above");
17062 let rank_cache = distributed
17063 .rank(rank)
17064 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17065 let k_view = engine.view_u8_range(
17066 rank_cache.k(),
17067 physical.start * k_tok_bytes_c,
17068 physical.end * k_tok_bytes_c,
17069 );
17070 let v_view = engine.view_u8_range(
17071 rank_cache.v(),
17072 physical.start * v_tok_bytes_c,
17073 physical.end * v_tok_bytes_c,
17074 );
17075 engine.fa_decode_kvmod(
17076 &ws.q[rank],
17077 &k_view,
17078 &v_view,
17079 &mut ws.attn_out[rank],
17080 head_dim,
17081 local_heads,
17082 local_kv_heads,
17083 t_kv,
17084 geometry.attention_scale(),
17085 k_tok_bytes_c,
17086 v_tok_bytes_c,
17087 false,
17088 )?;
17089 engine.attn_head_gate(
17090 &ws.attn_out[rank],
17091 &ws.gate[rank],
17092 &mut ws.gated[rank],
17093 None,
17094 head_dim,
17095 local_heads,
17096 1,
17097 )?;
17098 }
17099
17100 let output = if let Some(col) = fa2_col.filter(|_| dcw) {
17107 tp.runtime.decode_v2_stash_fa2(ws, e, col)?;
17111 crate::tp::set_spec_fa2_stashed();
17112 e.uninit(ws.o_out)?
17113 } else if let Some(col) = crate::tp::take_tcol_oproj_defer() {
17114 if tp.runtime.decode_v2_oproj_tcol_eligible(ws, &tp.o) {
17115 tp.runtime.decode_v2_stash_gated(ws, e, col)?;
17116 crate::tp::set_tcol_oproj_stashed();
17117 e.uninit(ws.o_out)?
17118 } else {
17119 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17120 }
17121 } else {
17122 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17123 };
17124
17125 let local = cache.kv[il]
17129 .as_mut()
17130 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
17131 if local.len != base_len || base_len + 1 > max_ctx {
17132 return Err(format!(
17133 "Step TP layer {il} local cache changed during decode: \
17134 len={} base={base_len} max={max_ctx}",
17135 local.len
17136 )
17137 .into());
17138 }
17139 if crate::tp::no_local_shadow_on() {
17140 local.len = base_len + 1;
17143 if !crate::tp::len_mirror_lazy_on() {
17147 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17148 }
17149 } else {
17150 let retain_from = window
17151 .map(|window| {
17152 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
17153 let rollback_retain =
17154 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
17155 staged_retain.min(rollback_retain)
17156 })
17157 .unwrap_or(0);
17158 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
17159 e.append_kv_quantized(
17160 &ws.k_shadow,
17161 &ws.v_shadow,
17162 &mut local.k,
17163 &mut local.v,
17164 write_row,
17165 local.kv_dim_k,
17166 local.kv_dim_v,
17167 local.k_tok_bytes,
17168 local.v_tok_bytes,
17169 false,
17170 )?;
17171 local.len = base_len + 1;
17172 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17173 }
17174 Ok(output)
17175 })();
17176
17177 let output = match staged {
17178 Ok(output) => output,
17179 Err(error) => {
17180 let _ = tp.runtime.rollback_tp_kv_transaction(
17181 cache.tp_kv[il]
17182 .as_mut()
17183 .expect("distributed cache checked above"),
17184 transaction,
17185 );
17186 if let Some(local) = cache.kv[il].as_mut() {
17187 local.len = base_len;
17188 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
17189 }
17190 return Err(error);
17191 }
17192 };
17193 let lazy_commit = fuse_rope && crate::tp::len_mirror_lazy_on();
17198 if lazy_commit {
17199 if let Err(error) = tp.runtime.commit_tp_kv_transaction_external(
17200 cache.tp_kv[il]
17201 .as_mut()
17202 .expect("distributed cache checked above"),
17203 transaction,
17204 1,
17205 ) {
17206 let _ = tp.runtime.rollback_tp_kv_transaction(
17207 cache.tp_kv[il]
17208 .as_mut()
17209 .expect("distributed cache checked above"),
17210 transaction,
17211 );
17212 let local = cache.kv[il].as_mut().expect("local cache checked above");
17213 local.len = base_len;
17214 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17215 return Err(error);
17216 }
17217 } else if let Err(error) = tp.runtime.commit_tp_kv_transaction(
17218 cache.tp_kv[il]
17219 .as_mut()
17220 .expect("distributed cache checked above"),
17221 transaction,
17222 1,
17223 ) {
17224 let _ = tp.runtime.rollback_tp_kv_transaction(
17225 cache.tp_kv[il]
17226 .as_mut()
17227 .expect("distributed cache checked above"),
17228 transaction,
17229 );
17230 let local = cache.kv[il].as_mut().expect("local cache checked above");
17231 local.len = base_len;
17232 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17233 return Err(error);
17234 }
17235
17236 let committed = cache.tp_kv[il]
17237 .as_ref()
17238 .expect("distributed cache checked above")
17239 .committed_len();
17240 let local_len = cache.kv[il]
17241 .as_ref()
17242 .expect("local cache checked above")
17243 .len;
17244 if committed != local_len {
17245 return Err(format!(
17246 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
17247 )
17248 .into());
17249 }
17250 static V2_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
17251 if !V2_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
17252 eprintln!(
17253 "[step-tp-attn-v2] execute layer={} devices={:?} tokens=1 driver=v2 \
17254 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
17255 kv_cache_distributed=true kv_cache_hydrated={hydrated} \
17256 attention_tensor_parallel=true attention_scope={} \
17257 input_path=root-device-replicated gate_tensor_parallel=false \
17258 gate_shards=device-staged o_tensor_parallel=true o_reduce=root-device \
17259 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
17260 bulk_p2p={} workspace=persistent ordering=evented output=e-device \
17261 performance_claim=false (logged once; every decode layer runs this driver)",
17262 tp.layer,
17263 tp.devices,
17264 if window.is_some() {
17265 "rank-local-swa-ring"
17266 } else {
17267 "rank-local-global"
17268 },
17269 tp.runtime.transport_label(),
17270 tp.runtime.bulk_p2p(),
17271 );
17272 }
17273 Ok(output)
17274 }
17275
17276 #[allow(clippy::too_many_arguments)]
17286 pub(crate) fn step35_decode_attn(
17287 &self,
17288 e: &Engine,
17289 fa: &FullAttnLayer,
17290 il: usize,
17291 h: &CudaSlice<f32>,
17292 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
17293 pos_d: &CudaSlice<i32>,
17294 cache: &mut Cache,
17295 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17296 if fa
17297 .step_tp_qkv
17298 .as_ref()
17299 .is_some_and(|tp| tp.attention.is_some())
17300 {
17301 if pre_q.is_some() {
17302 return Err(
17303 "rank-local Step attention preserves BF16 activations and refuses the q8_1 \
17304 pre-quantized decode path"
17305 .into(),
17306 );
17307 }
17308 return self.step35_tp_decode_attn_resident(e, fa, il, h, pos_d, cache);
17309 }
17310
17311 let geometry = self.step35_geom(il);
17312 let hd = geometry.head_dim_k as usize;
17313 let nkv = geometry.n_head_kv as usize;
17314 let nh = geometry.n_head as usize;
17315 let rbase = geometry.rope_base;
17316 let scale = geometry.attention_scale();
17317 let swa = geometry.window.is_some();
17318 let eps = self.cfg.rms_eps;
17319 let win = geometry.window.unwrap_or(0) as usize;
17320 let n_rot = geometry.n_rot as usize;
17321 let n_embd = self.cfg.n_embd as usize;
17322 let gw = fa
17323 .attn_gate
17324 .as_ref()
17325 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
17326
17327 let tp_qkv = if fa.step_tp_qkv.is_some() {
17328 if pre_q.is_some() {
17329 return Err(
17330 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
17331 pre-quantized decode path"
17332 .into(),
17333 );
17334 }
17335 self.step35_tp_qkv(e, fa, h, 1)?
17336 } else {
17337 None
17338 };
17339
17340 let (q0, k0, v0, gt) = match tp_qkv {
17341 Some(mut g3) => {
17342 let v = g3.pop().unwrap();
17343 let k = g3.pop().unwrap();
17344 let q = g3.pop().unwrap();
17345 let gt = e.matmul(gw, h, 1)?;
17346 (q, k, v, gt)
17347 }
17348 None => match pre_q {
17349 Some((hq, hdq)) => {
17350 debug_assert!(
17351 e.uses_q8_1_fast(gw),
17352 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
17353 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
17354 );
17355 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
17356 Some(t3) => t3,
17357 None => (
17358 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
17359 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
17360 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
17361 ),
17362 };
17363 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
17364 (a, b, c, gt)
17365 }
17366 None => {
17367 if e.uses_q8_1_fast(&fa.wq)
17368 && e.uses_q8_1_fast(&fa.wk)
17369 && e.uses_q8_1_fast(&fa.wv)
17370 && e.uses_q8_1_fast(gw)
17371 {
17372 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
17373 let (a, b, c) =
17374 match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
17375 Some(t3) => t3,
17376 None => (
17377 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
17378 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
17379 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
17380 ),
17381 };
17382 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
17383 (a, b, c, gt)
17384 } else {
17385 (
17386 e.matmul(&fa.wq, h, 1)?,
17387 e.matmul(&fa.wk, h, 1)?,
17388 e.matmul(&fa.wv, h, 1)?,
17389 e.matmul(gw, h, 1)?,
17390 )
17391 }
17392 }
17393 },
17394 };
17395
17396 let mut q = e.uninit(nh * hd)?;
17397 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
17398 let mut k = e.uninit(nkv * hd)?;
17399 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
17400 let ff = if swa {
17401 None
17402 } else {
17403 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
17404 };
17405 #[cfg(debug_assertions)]
17406 if let Some(ff) = ff {
17407 crate::debug_assert_tensor_stream_device(
17408 ff,
17409 &e.stream(),
17410 "step35_decode_attn.rope_freqs",
17411 );
17412 }
17413 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
17414
17415 if std::env::var("MEMRA_NOFA").is_ok() {
17416 return Err(
17417 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
17418 cache; unset MEMRA_NOFA to use fa_decode"
17419 .into(),
17420 );
17421 }
17422 let kvl = cache.kv[il].as_mut().unwrap();
17423 let next_len = kvl.len + 1;
17424 let (off, t_kv) = if swa && next_len > win {
17425 (next_len - win, win)
17426 } else {
17427 (0, next_len)
17428 };
17429 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
17430 e.append_kv_quantized(
17431 &k,
17432 &v0,
17433 &mut kvl.k,
17434 &mut kvl.v,
17435 write_row,
17436 kvl.kv_dim_k,
17437 kvl.kv_dim_v,
17438 kvl.k_tok_bytes,
17439 kvl.v_tok_bytes,
17440 crate::Engine::kv_fp8_on(),
17441 )?;
17442 kvl.len = next_len;
17443 let physical = kvl.physical_rows(off, off + t_kv)?;
17444 let k_view = e.view_u8_range(
17445 &kvl.k,
17446 physical.start * kvl.k_tok_bytes,
17447 physical.end * kvl.k_tok_bytes,
17448 );
17449 let v_view = e.view_u8_range(
17450 &kvl.v,
17451 physical.start * kvl.v_tok_bytes,
17452 physical.end * kvl.v_tok_bytes,
17453 );
17454 let mut attn = e.uninit(nh * hd)?;
17455 e.fa_decode_kvmod(
17456 &q,
17457 &k_view,
17458 &v_view,
17459 &mut attn,
17460 hd,
17461 nh,
17462 nkv,
17463 t_kv,
17464 scale,
17465 kvl.k_tok_bytes,
17466 kvl.v_tok_bytes,
17467 crate::Engine::kv_fp8_on(),
17468 )?;
17469
17470 let mut ag = e.uninit(nh * hd)?;
17471 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
17472 self.step35_o(e, fa, &ag, 1)
17473 }
17474}
17475
17476impl HybridModel {
17485 pub fn is_gemma4_e4b(&self) -> bool {
17486 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
17487 }
17488
17489 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
17493 let g = self.cfg.gemma4.as_ref().unwrap();
17494 let swa = g.swa_pattern[il];
17495 let hd = if swa {
17496 g.key_length_swa
17497 } else {
17498 g.key_length_global
17499 } as usize;
17500 let Mixer::Full(fa) = &self.layers[il].mixer else {
17501 panic!("e4b layer {il} not full-attn")
17502 };
17503 let nh = fa.wq.out_features() / hd;
17504 let nkv = fa.wk.out_features() / hd;
17505 (
17506 hd,
17507 nkv,
17508 nh,
17509 if swa {
17510 g.rope_base_swa
17511 } else {
17512 g.rope_base_global
17513 },
17514 1.0,
17515 swa,
17516 )
17517 }
17518
17519 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
17521 self.layers[il]
17522 .gemma4
17523 .as_ref()
17524 .and_then(|b| b.e4b.as_ref())
17525 .and_then(|e4| e4.kv_share.map(|t| t as usize))
17526 }
17527
17528 fn gemma4_e4b_inp_pl(
17533 &self,
17534 e: &Engine,
17535 tokens: &[u32],
17536 x_scaled: &CudaSlice<f32>,
17537 t: usize,
17538 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17539 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
17540 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
17541 }
17542
17543 fn gemma4_e4b_inp_pl_dev(
17545 &self,
17546 e: &Engine,
17547 tok_d: &CudaSlice<u32>,
17548 x_scaled: &CudaSlice<f32>,
17549 t: usize,
17550 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17551 let aux = self.gemma4_aux.as_ref().unwrap();
17552 let m = aux.e4b.as_ref().unwrap();
17553 let n_embd = self.cfg.n_embd as usize;
17554 let n_layer = self.layers.len();
17555 let width = m.n_epl * n_layer;
17556 let tbl = m.tok_tbl_gpu.get_or_init(|| {
17557 e.upload_u8(&m.tok_embd_bytes)
17558 .expect("e4b per-layer token table upload")
17559 });
17560 let mut a =
17561 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
17562 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
17563 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
17564 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
17565 let mut pn = e.uninit(t * width)?;
17566 e.rms_norm(
17567 &p,
17568 m.proj_norm.float_data(),
17569 &mut pn,
17570 m.n_epl,
17571 t * n_layer,
17572 self.cfg.rms_eps,
17573 )?;
17574 let mut out = e.uninit(t * width)?;
17575 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
17576 Ok(out)
17577 }
17578
17579 #[allow(clippy::too_many_arguments)]
17584 fn gemma4_e4b_attn(
17585 &self,
17586 e: &Engine,
17587 il: usize,
17588 hq: &CudaSlice<i8>,
17589 hdq: &CudaSlice<f32>,
17590 pos_d: &CudaSlice<i32>,
17591 t: usize,
17592 cache: &mut Cache,
17593 dc_bucket: Option<usize>,
17594 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17595 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
17596 let eps = self.cfg.rms_eps;
17597 let aux = self.gemma4_aux.as_ref().unwrap();
17598 let ones = aux.ones(e);
17599 #[cfg(debug_assertions)]
17600 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
17601 let Mixer::Full(fa) = &self.layers[il].mixer else {
17602 unreachable!()
17603 };
17604 let h0 = e.zeros(0)?;
17608 let h = &h0;
17609
17610 let ff = if swa {
17611 None
17612 } else {
17613 Some(
17614 aux.rope_freqs(e)
17615 .expect("e4b global rope needs rope_freqs.weight"),
17616 )
17617 };
17618 #[cfg(debug_assertions)]
17619 if let Some(ff) = ff {
17620 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
17621 }
17622 let share = self.gemma4_e4b_kv_target(il);
17623 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
17625 let mut q;
17626 if let Some(_tgt) = share {
17627 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
17628 q = e.uninit(t * nh * hd)?;
17629 let mut kdummy = e.uninit(1)?;
17632 let mut vdummy = e.uninit(1)?;
17633 e.rms_norm_qkv_rope(
17634 &q0,
17635 &q0,
17636 &q0,
17637 fa.q_norm.float_data(),
17638 fa.q_norm.float_data(),
17639 ones,
17640 &mut q,
17641 &mut kdummy,
17642 &mut vdummy,
17643 hd,
17644 self.gemma4_rope_dims(il),
17645 nh * t,
17646 0,
17647 pos_d,
17648 nh,
17649 1,
17650 base,
17651 1.0,
17652 ff,
17653 eps,
17654 )?;
17655 } else {
17656 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
17660 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
17661 q = e.uninit(t * nh * hd)?;
17662 let mut k = e.uninit(t * nkv * hd)?;
17663 let mut v = e.uninit(t * nkv * hd)?;
17664 if t == 1 && cat.is_some() {
17665 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
17666 e.rms_norm_qkv_rope_cat(
17667 &qkv0,
17668 fa.q_norm.float_data(),
17669 fa.k_norm.float_data(),
17670 ones,
17671 &mut q,
17672 &mut k,
17673 &mut v,
17674 hd,
17675 self.gemma4_rope_dims(il),
17676 nh,
17677 nkv,
17678 pos_d,
17679 nh,
17680 nkv,
17681 base,
17682 1.0,
17683 ff,
17684 eps,
17685 )?;
17686 } else {
17687 let (q0, k0, v0) = match if t == 1 {
17688 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
17689 } else {
17690 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17693 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
17694 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
17695 } else {
17696 None
17697 }
17698 } {
17699 Some(triple) => triple,
17700 None => (
17701 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
17702 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
17703 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
17704 ), };
17706 e.rms_norm_qkv_rope(
17709 &q0,
17710 &k0,
17711 &v0,
17712 fa.q_norm.float_data(),
17713 fa.k_norm.float_data(),
17714 ones,
17715 &mut q,
17716 &mut k,
17717 &mut v,
17718 hd,
17719 self.gemma4_rope_dims(il),
17720 nh * t,
17721 nkv * t,
17722 pos_d,
17723 nh,
17724 nkv,
17725 base,
17726 1.0,
17727 ff,
17728 eps,
17729 )?;
17730 }
17731 let kvl = cache.kv[il].as_mut().unwrap();
17732 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17736 if dc_bucket.is_some() {
17737 debug_assert!(t == 1);
17742 e.append_kv_quantized_row_dc_inc(
17744 &k,
17745 &v,
17746 &mut kvl.k,
17747 &mut kvl.v,
17748 &mut kvl.len_d,
17749 kvl.kv_dim_k,
17750 kvl.kv_dim_v,
17751 kvl.k_tok_bytes,
17752 kvl.v_tok_bytes,
17753 cls,
17754 )?;
17755 } else {
17756 e.append_kv_quantized_rows(
17757 &k,
17758 &v,
17759 &mut kvl.k,
17760 &mut kvl.v,
17761 kvl.len,
17762 t,
17763 kvl.kv_dim_k,
17764 kvl.kv_dim_v,
17765 kvl.k_tok_bytes,
17766 kvl.v_tok_bytes,
17767 cls,
17768 )?;
17769 kvl.len += t;
17770 }
17771 kv_f32 = Some((k, v));
17772 }
17773 let kvl_idx = share.unwrap_or(il);
17776 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
17777 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
17779 let mut attn = e.uninit(t * nh * hd)?;
17780 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
17792 if let Some((kf, vf)) = &kv_f32 {
17793 if hd == 256 && t <= win {
17794 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17795 return Ok(e.matmul(&fa.wo, &attn, t)?);
17796 }
17797 if hd == 256 && swa && t > win {
17798 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
17799 return Ok(e.matmul(&fa.wo, &attn, t)?);
17800 }
17801 if hd == 512 && !swa {
17802 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17803 return Ok(e.matmul(&fa.wo, &attn, t)?);
17804 }
17805 } else if share.is_some() {
17806 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17807 let k_view = e.view_u8(&kvl.k, kvl.k.len());
17808 let v_view = e.view_u8(&kvl.v, kvl.v.len());
17809 if hd == 256 && (!swa || t <= win) {
17810 e.fa_prefill_view(
17812 &q,
17813 &k_view,
17814 &v_view,
17815 &mut attn,
17816 hd,
17817 nh,
17818 nkv,
17819 t,
17820 t,
17821 scale,
17822 true,
17823 kvl.k_tok_bytes,
17824 kvl.v_tok_bytes,
17825 g,
17826 )?;
17827 return Ok(e.matmul(&fa.wo, &attn, t)?);
17828 }
17829 let kv_dim = nkv * hd;
17832 let mut kf = e.uninit(t * kv_dim)?;
17833 let mut vf = e.uninit(t * kv_dim)?;
17834 e.fa_dequant_kv_view_f32(
17835 &k_view,
17836 &v_view,
17837 &mut kf,
17838 &mut vf,
17839 kv_dim,
17840 kv_dim,
17841 t,
17842 kvl.k_tok_bytes,
17843 kvl.v_tok_bytes,
17844 g,
17845 )?;
17846 if hd == 512 {
17847 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
17848 } else {
17849 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
17850 }
17851 return Ok(e.matmul(&fa.wo, &attn, t)?);
17852 }
17853 }
17854 if let Some(bucket) = dc_bucket {
17855 assert!(t == 1);
17860 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
17866 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
17867 } else {
17868 bucket
17869 };
17870 let k_view = e.view_u8(&kvl.k, kvl.k.len());
17871 let v_view = e.view_u8(&kvl.v, kvl.v.len());
17872 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
17873 if crate::Engine::wpf_level() >= 1 {
17881 e.prefetch_weight_l2(&fa.wo)?;
17882 }
17883 if e.uses_q8_1_fast(&fa.wo) {
17886 let mut oq = e.alloc_i8_uninit(nh * hd)?;
17887 let mut od = e.zeros(nh * hd / 32)?;
17888 e.fa_decode_dc_q8(
17889 &q,
17890 &k_view,
17891 &v_view,
17892 &mut attn,
17893 hd,
17894 nh,
17895 nkv,
17896 &kvl.len_d,
17897 bucket,
17898 scale,
17899 kvl.k_tok_bytes,
17900 kvl.v_tok_bytes,
17901 g,
17902 Some((&mut oq, &mut od)),
17903 )?;
17904 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
17905 }
17906 e.fa_decode_dc(
17907 &q,
17908 &k_view,
17909 &v_view,
17910 &mut attn,
17911 hd,
17912 nh,
17913 nkv,
17914 &kvl.len_d,
17915 bucket,
17916 scale,
17917 kvl.k_tok_bytes,
17918 kvl.v_tok_bytes,
17919 g,
17920 )?;
17921 return Ok(e.matmul(&fa.wo, &attn, t)?);
17922 }
17923 for i in 0..t {
17924 let avail = base_len + i + 1;
17925 let (off_tok, t_kv) = if swa && avail > win {
17926 (avail - win, win)
17927 } else {
17928 (0, avail)
17929 };
17930 let k_view = e.view_u8_range(
17931 &kvl.k,
17932 off_tok * kvl.k_tok_bytes,
17933 (off_tok + t_kv) * kvl.k_tok_bytes,
17934 );
17935 let v_view = e.view_u8_range(
17936 &kvl.v,
17937 off_tok * kvl.v_tok_bytes,
17938 (off_tok + t_kv) * kvl.v_tok_bytes,
17939 );
17940 let qv = e.view(&q, t * nh * hd);
17941 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
17942 let mut q_one = e.uninit(nh * hd)?;
17943 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
17944 let mut a_one = e.uninit(nh * hd)?;
17945 e.fa_decode_kvmod(
17949 &q_one,
17950 &k_view,
17951 &v_view,
17952 &mut a_one,
17953 hd,
17954 nh,
17955 nkv,
17956 t_kv,
17957 scale,
17958 kvl.k_tok_bytes,
17959 kvl.v_tok_bytes,
17960 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
17961 )?;
17962 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
17963 }
17964 Ok(e.matmul(&fa.wo, &attn, t)?)
17965 }
17966
17967 fn gemma4_e4b_trunk(
17972 &self,
17973 e: &Engine,
17974 tokens: &[u32],
17975 pos0: usize,
17976 cache: &mut Cache,
17977 head_last: bool,
17978 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
17979 let n_embd = self.cfg.n_embd as usize;
17980 let t = tokens.len();
17981 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
17982 let pos_d = e.htod_i32(&pos)?;
17983 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
17984 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
17985 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
17986 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
17987 }
17988
17989 fn gemma4_e4b_trunk_core(
17993 &self,
17994 e: &Engine,
17995 x_in: CudaSlice<f32>,
17996 inp_pl: CudaSlice<f32>,
17997 pos_d: &CudaSlice<i32>,
17998 t: usize,
17999 cache: &mut Cache,
18000 dc_bucket: Option<usize>,
18001 cap_logits: bool,
18002 head_last: bool,
18003 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18004 let n_embd = self.cfg.n_embd as usize;
18005 let eps = self.cfg.rms_eps;
18006 let n_layer = self.layers.len();
18007 let mut x = x_in;
18008 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
18009 let n_epl = aux_e4b.n_epl;
18010
18011 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
18017 for il in 0..n_layer {
18018 let layer = &self.layers[il];
18019 let (hq, hdq) = match h_carry.take() {
18020 Some(p) => p,
18021 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
18022 };
18023 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
18024 let bits = layer.gemma4.as_ref().unwrap();
18027 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
18028 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
18039 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
18040 e,
18041 layer,
18042 &o,
18043 &x,
18044 t,
18045 Some(layer.post_attn_norm.float_data()),
18046 fuse_exit,
18047 )?;
18048 let mut resid = e.uninit(t * n_embd)?;
18049 let g = if fuse_exit {
18055 let (rq, rd) = e.rms_pre_add_q8_1(
18057 &sn,
18058 bits.post_ffw_norm.float_data(),
18059 &attn_out,
18060 &mut resid,
18061 n_embd,
18062 t,
18063 self.cfg.rms_eps,
18064 )?;
18065 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
18066 } else {
18067 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
18068 e.matmul(&e4b.inp_gate, &resid, t)?
18069 };
18070 let mut act = e.uninit(t * n_epl)?;
18071 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
18072 let ipv = e.view(&inp_pl, n_epl * n_layer);
18073 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
18074 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
18075 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
18076 } else {
18077 let mut inp_this = e.uninit(t * n_epl)?;
18078 e.copy_rows_strided(
18079 &inp_pl,
18080 &mut inp_this,
18081 n_epl,
18082 t,
18083 n_epl * n_layer,
18084 il * n_epl,
18085 )?;
18086 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
18087 e.matmul(&e4b.proj, &act, t)?
18088 };
18089 let next_norm = if il + 1 < n_layer {
18092 self.layers[il + 1].attn_norm.float_data()
18093 } else {
18094 self.output_norm.float_data()
18095 };
18096 let mut xn = e.uninit(t * n_embd)?;
18097 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
18098 &y,
18099 e4b.post_norm.float_data(),
18100 &resid,
18101 bits.layer_scale,
18102 next_norm,
18103 &mut xn,
18104 n_embd,
18105 t,
18106 eps,
18107 )?;
18108 h_carry = Some(pair);
18109 x = xn;
18110 }
18111 let (oq, odq) = h_carry.take().unwrap();
18115 let h0 = e.zeros(0)?;
18116 let hm = if head_last { 1 } else { t };
18117 let (hq, hd) = if head_last && t > 1 {
18118 let mut q1 = e.uninit_i8(n_embd)?;
18119 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
18120 let nb = n_embd / 32;
18121 let mut d1 = e.uninit(nb)?;
18122 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
18123 (q1, d1)
18124 } else {
18125 (oq, odq)
18126 };
18127 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
18128 if cap_logits {
18132 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
18133 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
18134 }
18135 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
18137 }
18138
18139 pub fn gemma4_e4b_decode_step_t_am_dev(
18146 &self,
18147 e: &Engine,
18148 tok_d: &CudaSlice<u32>,
18149 t: usize,
18150 pos0: usize,
18151 cache: &mut Cache,
18152 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18153 let n_embd = self.cfg.n_embd as usize;
18154 let eps = self.cfg.rms_eps;
18155 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
18156 let pos_d = e.htod_i32(&pos)?;
18157 let embd_gpu = self
18158 .embd_gpu
18159 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
18160 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
18161 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
18162 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
18163 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
18164 let (ld, xp) =
18165 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
18166 let n_vocab = self.output.out_features();
18169 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
18170 for i in 0..t {
18171 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
18172 }
18173 let mut hn = e.uninit(t * n_embd)?;
18174 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18175 cache.pos += t;
18176 Ok((vam, hn))
18177 }
18178
18179 pub(crate) fn gemma4_e4b_decode_step_t_h(
18182 &self,
18183 e: &Engine,
18184 tokens: &[u32],
18185 pos0: usize,
18186 cache: &mut Cache,
18187 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18188 let n_embd = self.cfg.n_embd as usize;
18189 let eps = self.cfg.rms_eps;
18190 let t = tokens.len();
18191 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
18192 let mut hn = e.uninit(t * n_embd)?;
18193 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18194 cache.pos += t;
18195 Ok((e.dtoh(&ld)?, hn))
18196 }
18197
18198 pub fn gemma4_e4b_decode_step_dcg(
18204 &self,
18205 e: &Engine,
18206 token_d: &mut CudaSlice<u32>,
18207 pos_d: &mut CudaSlice<i32>,
18208 embd_gpu: &CudaSlice<u8>,
18209 embd_qt: i32,
18210 embd_rb: usize,
18211 cache: &mut Cache,
18212 n_vocab: usize,
18213 bucket: usize,
18214 ) -> Result<(), Box<dyn std::error::Error>> {
18215 let n_embd = self.cfg.n_embd as usize;
18216 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
18217 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
18218 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
18219 let (ld, _x) =
18220 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
18221 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
18222 e.inc_seqlen(pos_d)?;
18223 Ok(())
18224 }
18225
18226 #[allow(clippy::too_many_arguments)]
18234 pub fn gemma4_e4b_decode_step_dc(
18235 &self,
18236 e: &Engine,
18237 token_d: &CudaSlice<u32>,
18238 pos_d: &mut CudaSlice<i32>,
18239 embd_gpu: &CudaSlice<u8>,
18240 embd_qt: i32,
18241 embd_rb: usize,
18242 cache: &mut Cache,
18243 n_vocab: usize,
18244 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
18245 let n_embd = self.cfg.n_embd as usize;
18246 let eps = self.cfg.rms_eps;
18247 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
18248 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
18249 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
18250 let (ld, _x) =
18251 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
18252 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
18253 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
18254 e.inc_seqlen(pos_d)?;
18255 cache.pos += 1;
18256 let _ = eps;
18257 Ok(tok_out)
18258 }
18259
18260 pub(crate) fn gemma4_e4b_decode_step_h(
18263 &self,
18264 e: &Engine,
18265 token: u32,
18266 cache: &mut Cache,
18267 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18268 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
18269 let logits = e.dtoh(&ld)?;
18270 cache.pos += 1;
18271 Ok((logits, x))
18272 }
18273
18274 pub(crate) fn gemma4_e4b_prime(
18278 &self,
18279 e: &Engine,
18280 tokens: &[u32],
18281 cache: &mut Cache,
18282 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18283 if cache.pos != 0 {
18286 return Err(
18287 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
18288 call or decode tokenwise"
18289 .into(),
18290 );
18291 }
18292 let n_embd = self.cfg.n_embd as usize;
18293 let t = tokens.len();
18294 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
18295 cache.pos += t;
18296 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
18298 let row = xv.slice((t - 1) * n_embd..t * n_embd);
18299 let mut h_seed = e.uninit(n_embd)?;
18300 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
18301 Ok((last, h_seed, x))
18302 }
18303
18304 pub(crate) fn gemma4_e4b_forward(
18306 &self,
18307 e: &Engine,
18308 tokens: &[u32],
18309 last_only: bool,
18310 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
18311 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
18312 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
18313 Ok(e.dtoh(&ld)?) }
18315}
18316
18317#[cfg(test)]
18318mod prime_chunk_schedule_tests {
18319 use super::{
18320 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, active_matrix_values, align_prime_ranges_to_gdn,
18321 dynamic_prime_chunk_ranges, fixed_prime_chunk_ranges, fixed_prime_chunk_ranges_for_ring,
18322 parse_step_ep_grouped_prefill, parse_step_tp_prefill, step_grouped_decode_shape,
18323 step_grouped_prefill_shape, step_tp_prefill_shape, validate_step_prime_batch_modes,
18324 };
18325
18326 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
18327 ranges.iter().map(|(start, end)| end - start).collect()
18328 }
18329
18330 fn auto_chunk(t: usize) -> usize {
18331 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
18332 }
18333
18334 #[test]
18339 fn auto_prime_ranges_align_to_the_gdn_grid() {
18340 let c = 32usize; let assert_covers = |ranges: &[(usize, usize)], t: usize| {
18342 assert_eq!(ranges.first().map(|&(s, _)| s), Some(0));
18343 assert_eq!(ranges.last().map(|&(_, e)| e), Some(t));
18344 for w in ranges.windows(2) {
18345 assert_eq!(w[0].1, w[1].0, "ranges must stay contiguous");
18346 }
18347 assert!(ranges.iter().all(|&(s, e)| e > s), "no empty range");
18348 };
18349
18350 let t = 9510usize;
18353 let fill = auto_chunk(t);
18354 let fixed = fixed_prime_chunk_ranges(t, fill);
18355 assert!(
18356 fixed[..fixed.len() - 1].iter().any(|&(_, e)| e % c != 0),
18357 "broken arm vanished: fixed auto boundaries all landed on-grid"
18358 );
18359 let dynamic = dynamic_prime_chunk_ranges(t, fill, &fixed);
18360 assert!(
18361 dynamic[..dynamic.len() - 1]
18362 .iter()
18363 .any(|&(_, e)| e % c != 0),
18364 "broken arm vanished: dynamic auto boundaries all landed on-grid"
18365 );
18366
18367 for ranges in [&fixed, &dynamic] {
18368 let aligned = align_prime_ranges_to_gdn(ranges, t, c);
18369 assert_covers(&aligned, t);
18370 for &(_, e) in &aligned[..aligned.len() - 1] {
18371 assert_eq!(e % c, 0, "internal boundary {e} off the {c}-grid");
18372 }
18373 for (&(_, a), &(_, b)) in aligned.iter().zip(ranges.iter()) {
18375 assert!(a <= b && b - a < c);
18376 }
18377 }
18378
18379 let tight = vec![(0usize, 33usize), (33, 40), (40, 200)];
18382 let aligned = align_prime_ranges_to_gdn(&tight, 200, c);
18383 assert_covers(&aligned, 200);
18384 assert_eq!(aligned, vec![(0, 32), (32, 200)]);
18385
18386 assert_eq!(align_prime_ranges_to_gdn(&[(0, 200)], 200, c), [(0, 200)]);
18388 assert_eq!(align_prime_ranges_to_gdn(&tight, 200, 0), tight.as_slice());
18389 let on_grid = vec![(0usize, 128usize), (128, 256), (256, 300)];
18390 assert_eq!(
18391 align_prime_ranges_to_gdn(&on_grid, 300, c),
18392 on_grid.as_slice()
18393 );
18394 }
18395
18396 #[test]
18397 fn active_matrix_prefix_scopes_reused_prime_slabs() {
18398 assert_eq!(
18399 active_matrix_values(40 * 4096, 29, 4096, "activation").unwrap(),
18400 29 * 4096
18401 );
18402 assert_eq!(
18403 active_matrix_values(29 * 4096, 29, 4096, "activation").unwrap(),
18404 29 * 4096
18405 );
18406 assert_eq!(
18407 active_matrix_values(29 * 4096, 24, 4096, "activation").unwrap(),
18408 24 * 4096
18409 );
18410 assert!(active_matrix_values(28 * 4096, 29, 4096, "activation").is_err());
18411 assert!(active_matrix_values(usize::MAX, usize::MAX, 2, "activation").is_err());
18412 }
18413
18414 #[test]
18415 fn step_tp_prefill_batch_refuses_before_scheduler_fallback() {
18416 assert!(validate_step_prime_batch_modes(false, false).is_ok());
18417
18418 let grouped_without_tp = validate_step_prime_batch_modes(false, true).unwrap_err();
18419 assert!(grouped_without_tp.contains("requires MEMRA_STEP_TP_PREFILL=1"));
18420
18421 for grouped in [false, true] {
18422 let err = validate_step_prime_batch_modes(true, grouped).unwrap_err();
18423 assert!(err.contains("did not clear the live-server performance gate"));
18424 assert!(err.contains("per-session grouped prefill"));
18425 }
18426 }
18427
18428 #[test]
18429 fn step_grouped_path_is_eager_single_token_only() {
18430 assert!(step_grouped_decode_shape(false, 1));
18431 assert!(!step_grouped_decode_shape(true, 1));
18432 assert!(!step_grouped_decode_shape(false, 2));
18433 assert!(!step_grouped_decode_shape(true, 2));
18434 }
18435
18436 #[test]
18437 fn step_grouped_prefill_door_is_strict_and_capacity_bounded() {
18438 assert!(!parse_step_ep_grouped_prefill(None).unwrap());
18439 assert!(!parse_step_ep_grouped_prefill(Some("")).unwrap());
18440 assert!(!parse_step_ep_grouped_prefill(Some("0")).unwrap());
18441 assert!(parse_step_ep_grouped_prefill(Some("1")).unwrap());
18442 assert!(parse_step_ep_grouped_prefill(Some("true")).is_err());
18443 assert!(parse_step_ep_grouped_prefill(Some("2")).is_err());
18444
18445 assert!(step_grouped_prefill_shape(true, true, PRIME_MIN_T));
18446 assert!(step_grouped_prefill_shape(
18447 true,
18448 true,
18449 crate::cache::PRIME_CHUNK_MAX_TOKENS,
18450 ));
18451 assert!(!step_grouped_prefill_shape(true, true, PRIME_MIN_T - 1,));
18452 assert!(!step_grouped_prefill_shape(
18453 true,
18454 true,
18455 crate::cache::PRIME_CHUNK_MAX_TOKENS + 1,
18456 ));
18457 assert!(!step_grouped_prefill_shape(false, true, PRIME_MIN_T));
18458 assert!(!step_grouped_prefill_shape(true, false, PRIME_MIN_T));
18459 }
18460
18461 #[test]
18462 fn step_tp_prefill_door_is_strict_and_default_off() {
18463 assert!(!parse_step_tp_prefill(None).unwrap());
18464 assert!(!parse_step_tp_prefill(Some("")).unwrap());
18465 assert!(!parse_step_tp_prefill(Some("0")).unwrap());
18466 assert!(parse_step_tp_prefill(Some("1")).unwrap());
18467 assert!(parse_step_tp_prefill(Some("true")).is_err());
18468 assert!(parse_step_tp_prefill(Some("2")).is_err());
18469 }
18470
18471 #[test]
18472 fn step_tp_prefill_requires_the_qualified_tp4_shape() {
18473 assert!(step_tp_prefill_shape(
18474 true,
18475 PRIME_MIN_T,
18476 4,
18477 true,
18478 true,
18479 false,
18480 ));
18481 assert!(!step_tp_prefill_shape(
18482 false,
18483 PRIME_MIN_T,
18484 4,
18485 true,
18486 true,
18487 false,
18488 ));
18489 assert!(!step_tp_prefill_shape(
18490 true,
18491 PRIME_MIN_T - 1,
18492 4,
18493 true,
18494 true,
18495 false,
18496 ));
18497 assert!(!step_tp_prefill_shape(
18498 true,
18499 PRIME_MIN_T,
18500 2,
18501 true,
18502 true,
18503 false,
18504 ));
18505 assert!(!step_tp_prefill_shape(
18506 true,
18507 PRIME_MIN_T,
18508 4,
18509 false,
18510 true,
18511 false,
18512 ));
18513 assert!(!step_tp_prefill_shape(
18514 true,
18515 PRIME_MIN_T,
18516 4,
18517 true,
18518 false,
18519 false,
18520 ));
18521 assert!(!step_tp_prefill_shape(
18522 true,
18523 PRIME_MIN_T,
18524 4,
18525 true,
18526 true,
18527 true,
18528 ));
18529 }
18530
18531 #[test]
18532 fn fixed_schedule_retains_measured_geometry() {
18533 assert_eq!(
18534 sizes(&fixed_prime_chunk_ranges(461, 128)),
18535 vec![128, 128, 128, 77]
18536 );
18537 assert_eq!(
18538 sizes(&fixed_prime_chunk_ranges(1833, 230)),
18539 vec![230, 230, 230, 230, 230, 230, 230, 223]
18540 );
18541 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
18542 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
18543 assert_eq!(capped, vec![4096, 4088, 16]);
18544 assert!(capped.iter().all(|&rows| rows <= 4096));
18545 assert_eq!(
18546 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
18547 vec![4100],
18548 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
18549 );
18550 }
18551
18552 #[test]
18553 fn dynamic_schedule_matches_registered_shapes() {
18554 let cases = [
18555 (461, vec![64, 141, 132, 124]),
18556 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
18557 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
18558 ];
18559 for (t, expected) in cases {
18560 let chunk = auto_chunk(t);
18561 let fixed = fixed_prime_chunk_ranges(t, chunk);
18562 assert_eq!(
18563 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
18564 expected
18565 );
18566 }
18567 }
18568
18569 #[test]
18570 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
18571 for t in 256..=8192 {
18572 let chunk = auto_chunk(t);
18573 let fixed = fixed_prime_chunk_ranges(t, chunk);
18574 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
18575 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
18576 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
18577 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
18578 for pair in dynamic.windows(2) {
18579 assert_eq!(pair[0].1, pair[1].0, "T={t}");
18580 }
18581 assert!(
18582 dynamic
18583 .iter()
18584 .all(|(start, end)| end - start >= PRIME_MIN_T),
18585 "T={t} sizes={:?}",
18586 sizes(&dynamic)
18587 );
18588 if dynamic.len() >= 3 {
18589 let chunk_sizes = sizes(&dynamic);
18590 assert!(
18591 chunk_sizes[0] < chunk_sizes[1],
18592 "T={t} sizes={chunk_sizes:?}"
18593 );
18594 assert!(
18595 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
18596 "T={t} sizes={chunk_sizes:?}"
18597 );
18598 }
18599 }
18600 }
18601}
18602
18603#[cfg(test)]
18604mod page_prefetch_tests {
18605 use super::{
18606 grouped_worker_prefetch_position, page_prefetch_positions,
18607 page_prefetch_window_from_values, worker_prefetch_positions,
18608 };
18609
18610 #[test]
18611 fn page_prefetch_window_keeps_existing_opt_in_default() {
18612 assert_eq!(page_prefetch_window_from_values(false, None), 0);
18613 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
18614 assert_eq!(page_prefetch_window_from_values(true, None), 1);
18615 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
18616 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
18617 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
18618 }
18619
18620 #[test]
18621 fn rolling_page_prefetch_advises_each_future_expert_once() {
18622 let advised: Vec<_> = (0..7)
18623 .flat_map(|position| page_prefetch_positions(position, 7, 3))
18624 .collect();
18625 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
18626
18627 let one_ahead: Vec<_> = (0..4)
18628 .flat_map(|position| page_prefetch_positions(position, 4, 1))
18629 .collect();
18630 assert_eq!(one_ahead, vec![1, 2, 3]);
18631 assert!(page_prefetch_positions(0, 4, 0).is_empty());
18632 }
18633
18634 #[test]
18635 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
18636 assert_eq!(grouped_worker_prefetch_position(0, None), None);
18637 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
18638 .chain(
18639 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
18640 )
18641 .collect();
18642 assert_eq!(positions, vec![0, 1, 2, 3]);
18643 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
18644 }
18645
18646 #[test]
18647 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
18648 let queued: Vec<_> = (0..8)
18649 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
18650 .collect();
18651 assert_eq!(queued, (0..8).collect::<Vec<_>>());
18652
18653 let one_at_a_time: Vec<_> = (0..4)
18654 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
18655 .collect();
18656 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
18657 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
18658 }
18659}
18660
18661pub struct G4DcSlots {
18662 x: CudaSlice<f32>,
18663 xn: CudaSlice<f32>,
18664 cur: CudaSlice<f32>,
18665 hq: CudaSlice<i8>,
18666 hd_: CudaSlice<f32>,
18667 q0: CudaSlice<f32>,
18668 k0: CudaSlice<f32>,
18669 v0: CudaSlice<f32>,
18670 q: CudaSlice<f32>,
18671 k: CudaSlice<f32>,
18672 v: CudaSlice<f32>,
18673 attn: CudaSlice<f32>,
18674 o: CudaSlice<f32>,
18675 attn_out: CudaSlice<f32>,
18676 zsh: CudaSlice<f32>,
18677 zq: CudaSlice<i8>,
18678 zd: CudaSlice<f32>,
18679 gate: CudaSlice<f32>,
18680 up: CudaSlice<f32>,
18681 act: CudaSlice<f32>,
18682 actq: CudaSlice<i8>,
18683 actd: CudaSlice<f32>,
18684 f0: CudaSlice<f32>,
18685 sn: CudaSlice<f32>,
18686 hn: CudaSlice<f32>,
18687 logits: CudaSlice<f32>,
18688}
18689
18690pub struct Step35TokenGraphState {
18695 pub graphs: Vec<(usize, crate::tp::TokenGraph)>,
18697 pub token_d: cudarc::driver::CudaSlice<u32>,
18698 pub pos_d: cudarc::driver::CudaSlice<i32>,
18699 pub logits_stage: cudarc::driver::CudaSlice<f32>,
18700 pub x: cudarc::driver::CudaSlice<f32>,
18705 pub x1: cudarc::driver::CudaSlice<f32>,
18706 pub mixed_stage: cudarc::driver::CudaSlice<f32>,
18707 pub sh_stage: cudarc::driver::CudaSlice<f32>,
18708 pub k_shadow_stage: cudarc::driver::CudaSlice<f32>,
18709 pub v_shadow_stage: cudarc::driver::CudaSlice<f32>,
18710 pub router_logits: cudarc::driver::CudaSlice<f32>,
18713 pub shexp_gate: cudarc::driver::CudaSlice<f32>,
18714 pub shexp_up: cudarc::driver::CudaSlice<f32>,
18715 pub shexp_act: cudarc::driver::CudaSlice<f32>,
18716 pub gate_sig: cudarc::driver::CudaSlice<f32>,
18717 pub dense_z: cudarc::driver::CudaSlice<f32>,
18718 pub dense_gate: cudarc::driver::CudaSlice<f32>,
18719 pub dense_up: cudarc::driver::CudaSlice<f32>,
18720 pub dense_act: cudarc::driver::CudaSlice<f32>,
18721 pub hn: cudarc::driver::CudaSlice<f32>,
18722 pub probe_mixed: cudarc::driver::CudaSlice<f32>,
18725 pub probe_x: cudarc::driver::CudaSlice<f32>,
18726 pub token_hist: cudarc::driver::CudaSlice<u32>,
18729 pub hist_idx: cudarc::driver::CudaSlice<i32>,
18730}
18731
18732impl HybridModel {
18733 pub(crate) fn step35_token_graph_step(
18744 &self,
18745 e: &Engine,
18746 token: u32,
18747 cache: &mut Cache,
18748 ) -> Result<Option<(Vec<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
18749 if !self.uses_sliding_gated_moe_program()
18750 || !crate::tp::step_tp_graph_enabled()?
18751 || !crate::tp::step_tp_dcw_enabled()?
18752 || !crate::tp::step_tp_qkv_fused_enabled()?
18753 || !crate::tp::step_tp_dev_router_enabled()?
18754 || !crate::tp::step_nvfp4_dev_routes_enabled()?
18755 {
18756 return Ok(None);
18757 }
18758 let n_embd = self.cfg.n_embd as usize;
18759 let n_vocab = self.cfg.n_vocab as usize;
18760 let eps = self.cfg.rms_eps;
18761 let n_layers = self.layers.len();
18762 let pos = cache.pos;
18763 let staged_next = pos + 1;
18764 if staged_next < 96 {
18765 return Ok(None); }
18767
18768 for il in 0..n_layers {
18771 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
18772 return Ok(None); };
18774 if tp_kv.peek_append_ring(1)?.1 {
18775 return Ok(None);
18776 }
18777 }
18778
18779 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
18782 if !fa_vec {
18783 return Ok(None);
18784 }
18785 let sp = crate::fa_split_keys(staged_next, 8);
18786 let bucket_max = (n_splits * sp).max(staged_next);
18787
18788 let mut state_guard = self
18789 .step35_token_graph
18790 .lock()
18791 .map_err(|_| "step35 token graph lock is poisoned")?;
18792 if state_guard.is_none() {
18793 let _main = e.gpu.enter_main()?;
18794 let n_expert = self
18795 .cfg
18796 .moe
18797 .as_ref()
18798 .map(|m| m.expert_count as usize)
18799 .unwrap_or(0);
18800 let n_ff_sh = self
18801 .layers
18802 .iter()
18803 .find_map(|l| match &l.ffn {
18804 crate::hybrid::Ffn::Moe(m) => m.gate_shexp.as_ref().map(|g| g.out_features()),
18805 _ => None,
18806 })
18807 .unwrap_or(0);
18808 let n_ff_dense = self
18809 .layers
18810 .iter()
18811 .find_map(|l| match &l.ffn {
18812 crate::hybrid::Ffn::Dense { ffn_gate, .. } => Some(ffn_gate.out_features()),
18813 _ => None,
18814 })
18815 .unwrap_or(0);
18816 *state_guard = Some(Step35TokenGraphState {
18817 graphs: Vec::new(),
18818 token_d: e.stream().clone_htod(&[0u32])?,
18819 pos_d: e.htod_i32(&[pos as i32])?,
18820 logits_stage: e.htod(&vec![0.0f32; n_vocab])?,
18821 x: e.htod(&vec![0.0f32; n_embd])?,
18822 x1: e.htod(&vec![0.0f32; n_embd])?,
18823 mixed_stage: e.htod(&vec![0.0f32; n_embd])?,
18824 sh_stage: e.htod(&vec![0.0f32; n_embd])?,
18825 k_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
18826 v_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
18827 router_logits: e.htod(&vec![0.0f32; n_expert.max(1)])?,
18828 shexp_gate: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18829 shexp_up: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18830 shexp_act: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
18831 gate_sig: e.htod(&vec![1.0f32; 1])?,
18832 dense_z: e.htod(&vec![0.0f32; n_embd])?,
18833 dense_gate: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18834 dense_up: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18835 dense_act: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
18836 hn: e.htod(&vec![0.0f32; n_embd])?,
18837 probe_mixed: e.htod(&vec![0.0f32; n_embd])?,
18838 probe_x: e.htod(&vec![0.0f32; n_embd])?,
18839 token_hist: e.stream().clone_htod(&[0u32; 16])?,
18840 hist_idx: e.htod_i32(&[0])?,
18841 });
18842 }
18843 let state = state_guard.as_mut().expect("state armed above");
18844 {
18848 let _main = e.gpu.enter_main()?;
18849 let Step35TokenGraphState {
18850 logits_stage,
18851 token_d,
18852 ..
18853 } = &mut *state;
18854 e.argmax_token_device_into(logits_stage, token_d, n_vocab)?;
18855 }
18856
18857 if state.graphs.is_empty() {
18862 self.step35_token_graph_build(e, cache, state, bucket_max)?;
18865 }
18866 {
18867 let (b, g) = state.graphs.first_mut().expect("graph built above");
18868 if *b != bucket_max {
18869 g.retarget_bucket(bucket_max)?;
18870 *b = bucket_max;
18871 }
18872 }
18873 let graph = state
18874 .graphs
18875 .first()
18876 .map(|(_, g)| g)
18877 .expect("graph built above");
18878
18879 let tg_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
18880 let t_fence = tg_timing.then(std::time::Instant::now);
18881 {
18886 let fa0 = match &self.layers[0].mixer {
18887 Mixer::Full(fa) => fa,
18888 _ => return Err("step35 token graph expects full-attention layers".into()),
18889 };
18890 let tp0 = fa0
18891 .step_tp_qkv
18892 .as_ref()
18893 .ok_or("step35 token graph lost its TP state")?;
18894 for rank in 0..tp0.runtime.devices().len() {
18895 let engine = tp0
18896 .runtime
18897 .rank_engine(rank)
18898 .ok_or("step35 token graph lost a rank engine")?;
18899 let _main = engine.gpu.enter_main()?;
18900 engine.stream().synchronize()?;
18901 }
18902 }
18903
18904 {
18906 let _main = e.gpu.enter_main()?;
18907 e.set_u32_one(&mut state.token_d, token)?;
18908 e.set_i32_one(&mut state.pos_d, pos as i32)?;
18909 }
18910 let t_launch = tg_timing.then(std::time::Instant::now);
18911 graph.launch(e)?;
18912 let t_book = tg_timing.then(std::time::Instant::now);
18913 for il in 0..n_layers {
18918 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
18919 let transaction = tp_kv.begin_transaction()?;
18920 let fa = match &self.layers[il].mixer {
18921 Mixer::Full(fa) => fa,
18922 _ => return Err("step35 token graph expects full-attention layers".into()),
18923 };
18924 let tp = fa
18925 .step_tp_qkv
18926 .as_ref()
18927 .ok_or("step35 token graph lost its TP state")?;
18928 let empty: [CudaSlice<f32>; 0] = [];
18931 tp.runtime.append_tp_kv_transaction_inner(
18932 tp_kv,
18933 transaction,
18934 &empty,
18935 &empty,
18936 1,
18937 true,
18938 )?;
18939 tp.runtime
18940 .commit_tp_kv_transaction_external(tp_kv, transaction, 1)?;
18941 if let Some(local) = cache.kv[il].as_mut() {
18943 local.len = pos + 1;
18944 let _main = e.gpu.enter_main()?;
18945 e.set_i32_one(&mut local.len_d, (pos + 1) as i32)?;
18946 }
18947 }
18948 cache.pos = pos + 1;
18949 let t_sync = tg_timing.then(std::time::Instant::now);
18950 let (logits, h_seed) = {
18951 let _main = e.gpu.enter_main()?;
18952 e.stream().synchronize()?;
18953 (e.dtoh(&state.logits_stage)?, e.clone_dtod(&state.x)?)
18954 };
18955 if let (Some(f), Some(l), Some(b), Some(sy)) = (t_fence, t_launch, t_book, t_sync) {
18956 use std::sync::atomic::{AtomicU64, Ordering};
18957 static NS: [AtomicU64; 5] = [
18958 AtomicU64::new(0),
18959 AtomicU64::new(0),
18960 AtomicU64::new(0),
18961 AtomicU64::new(0),
18962 AtomicU64::new(0),
18963 ];
18964 static CALLS: AtomicU64 = AtomicU64::new(0);
18965 let now = std::time::Instant::now();
18966 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;
18972 if calls % 100 == 0 {
18973 let avg = |i: usize| NS[i].load(Ordering::Relaxed) as f64 / calls as f64 / 1e3;
18974 eprintln!(
18975 "[tg-timing] calls={calls} fence_us={:.0} launch_us={:.0} book_us={:.0} \
18976 syncdtoh_us={:.0} total_us={:.0}",
18977 avg(0),
18978 avg(1),
18979 avg(2),
18980 avg(3),
18981 avg(4)
18982 );
18983 }
18984 }
18985 if std::env::var("MEMRA_TG_PROBE_LAYER").is_ok() {
18987 use std::io::Write;
18988 let (pm, px) = {
18989 let _main = e.gpu.enter_main()?;
18990 (e.dtoh(&state.probe_mixed)?, e.dtoh(&state.probe_x)?)
18991 };
18992 for (path, data) in [
18993 ("/root/tg-probe-mixed.bin", &pm),
18994 ("/root/tg-probe-x.bin", &px),
18995 ] {
18996 let mut fo = std::fs::OpenOptions::new()
18997 .create(true)
18998 .append(true)
18999 .open(path)?;
19000 for v in data {
19001 fo.write_all(&v.to_le_bytes())?;
19002 }
19003 }
19004 }
19005 if let Ok(path) = std::env::var("MEMRA_DUMP_HN") {
19008 let hh = {
19009 let _main = e.gpu.enter_main()?;
19010 e.dtoh(&state.hn)?
19011 };
19012 use std::io::Write;
19013 let mut fo = std::fs::OpenOptions::new()
19014 .create(true)
19015 .append(true)
19016 .open(path)?;
19017 for v in &hh {
19018 fo.write_all(&v.to_le_bytes())?;
19019 }
19020 }
19021 if std::env::var("MEMRA_STEP_TP_GRAPH_DEBUG").as_deref() == Ok("1") {
19024 for il in [0usize, 1, 44] {
19025 let tp_kv = cache.tp_kv[il].as_ref().expect("eligibility checked above");
19026 let host_len = tp_kv.staged_len();
19027 let fa = match &self.layers[il].mixer {
19028 Mixer::Full(fa) => fa,
19029 _ => continue,
19030 };
19031 let tp = fa
19032 .step_tp_qkv
19033 .as_ref()
19034 .ok_or("step35 token graph lost its TP state")?;
19035 for rank in 0..tp.runtime.devices().len() {
19036 let engine = tp
19037 .runtime
19038 .rank_engine(rank)
19039 .ok_or("step35 token graph lost a rank engine")?;
19040 let rank_cache = tp_kv.rank(rank).ok_or("debug rank cache missing")?;
19041 let _main = engine.gpu.enter_main()?;
19042 engine.stream().synchronize()?;
19043 let len_d = engine.dtoh_i32_one(rank_cache.len_d())?;
19044 let base_d = match rank_cache.base_d() {
19045 Some(b) => engine.dtoh_i32_one(b)?,
19046 None => -1,
19047 };
19048 eprintln!(
19049 "[graph-debug] pos={pos} il={il} rank={rank} host_len={host_len} \
19050 len_d={len_d} base_d={base_d}"
19051 );
19052 }
19053 }
19054 }
19055 Ok(Some((logits, h_seed)))
19056 }
19057
19058 pub(crate) fn head_split_matvec(
19064 &self,
19065 e: &Engine,
19066 hn: &CudaSlice<f32>,
19067 ) -> Result<Option<Vec<f32>>, Box<dyn std::error::Error>> {
19068 if self.head_split_fill_device(e, hn)?.is_none() {
19069 return Ok(None);
19070 }
19071 let guard = HEAD_SPLIT_WS
19072 .lock()
19073 .map_err(|_| "head split lock is poisoned")?;
19074 let ws = guard.as_ref().expect("filled above");
19075 let _main = e.gpu.enter_main()?;
19076 Ok(Some(e.dtoh(&ws.logits_e)?))
19077 }
19078
19079 fn head_split_fill_device(
19083 &self,
19084 e: &Engine,
19085 hn: &CudaSlice<f32>,
19086 ) -> Result<Option<()>, Box<dyn std::error::Error>> {
19087 use cudarc::driver::DevicePtr;
19088 let crate::model::GpuTensor::FloatBf16 { data: head, .. } = &self.output else {
19089 return Ok(None);
19090 };
19091 let Some(rank1) = self.layers.first().and_then(|l| match &l.mixer {
19092 Mixer::Full(fa) => fa
19093 .step_tp_qkv
19094 .as_ref()
19095 .and_then(|tp| tp.runtime.rank_engine(1)),
19096 _ => None,
19097 }) else {
19098 return Ok(None);
19099 };
19100 let n_embd = self.cfg.n_embd as usize;
19101 let n_vocab = self.cfg.n_vocab as usize;
19102 let half = n_vocab / 2;
19103 let mut guard = HEAD_SPLIT_WS
19104 .lock()
19105 .map_err(|_| "head split lock is poisoned")?;
19106 let pin = {
19107 let _main = e.gpu.enter_main()?;
19108 let stream = e.stream();
19109 let (ptr, _g) = head.device_ptr(&stream);
19110 ptr as u64
19111 };
19112 if guard.as_ref().is_none_or(|ws| ws.pin != pin) {
19113 let hi_rows = n_vocab - half;
19115 let (w1, hn1, y1, ev_done) = {
19116 let _r1 = rank1.gpu.enter_main()?;
19117 (
19118 rank1.alloc_u8_uninit(hi_rows * n_embd * 2)?,
19119 rank1.htod(&vec![0.0f32; n_embd])?,
19120 rank1.htod(&vec![0.0f32; hi_rows])?,
19121 rank1.ctx().new_event(None)?,
19122 )
19123 };
19124 {
19125 use cudarc::driver::sys;
19126 let src = pin + (half * n_embd * 2) as u64;
19127 let dst = {
19128 let _r1 = rank1.gpu.enter_main()?;
19129 let rstream = rank1.stream();
19130 let (d, _g) = w1.device_ptr(&rstream);
19131 d as u64
19132 };
19133 let _r1 = rank1.gpu.enter_main()?;
19134 let r = unsafe {
19135 sys::cuMemcpyAsync(
19136 dst as sys::CUdeviceptr,
19137 src as sys::CUdeviceptr,
19138 hi_rows * n_embd * 2,
19139 rank1.stream().cu_stream() as sys::CUstream,
19140 )
19141 };
19142 if r != sys::CUresult::CUDA_SUCCESS {
19143 return Err(format!("head split replica upload: {r:?}").into());
19144 }
19145 rank1.stream().synchronize()?;
19146 }
19147 let (logits_e, ev_hn) = {
19148 let _main = e.gpu.enter_main()?;
19149 (e.htod(&vec![0.0f32; n_vocab])?, e.ctx().new_event(None)?)
19150 };
19151 let (raw_hn1, raw_y1) = {
19152 let _r1 = rank1.gpu.enter_main()?;
19153 let rstream = rank1.stream();
19154 let (a, _g0) = hn1.device_ptr(&rstream);
19155 let (b, _g1) = y1.device_ptr(&rstream);
19156 (a as u64, b as u64)
19157 };
19158 let raw_logits_hi = {
19159 let _main = e.gpu.enter_main()?;
19160 let stream = e.stream();
19161 let (l, _g) = logits_e.device_ptr(&stream);
19162 l as u64 + (half * 4) as u64
19163 };
19164 *guard = Some(HeadSplit {
19165 pin,
19166 w1,
19167 hn1,
19168 y1,
19169 logits_e,
19170 ev_hn,
19171 ev_done,
19172 raw_hn1,
19173 raw_y1,
19174 raw_logits_hi,
19175 });
19176 }
19177 let ws = guard.as_mut().expect("armed above");
19178 let hi_rows = n_vocab - half;
19179 let raw_hn = {
19181 let _main = e.gpu.enter_main()?;
19182 let stream = e.stream();
19183 let (h, _g) = hn.device_ptr(&stream);
19184 ws.ev_hn.record(&stream)?;
19185 h as u64
19186 };
19187 {
19188 let _r1 = rank1.gpu.enter_main()?;
19189 rank1.stream().wait(&ws.ev_hn)?;
19190 crate::tp::raw_copy_bytes(ws.raw_hn1, raw_hn, n_embd * 4, rank1)?;
19191 let HeadSplit { w1, hn1, y1, .. } = &mut *ws;
19192 rank1.matvec_bf16_into(w1, hn1, y1, n_embd, hi_rows)?;
19193 crate::tp::raw_copy_bytes(ws.raw_logits_hi, ws.raw_y1, hi_rows * 4, rank1)?;
19194 ws.ev_done.record(&rank1.stream())?;
19195 }
19196 {
19197 let _main = e.gpu.enter_main()?;
19198 let head_lo = head.slice(0..half * n_embd * 2);
19199 let HeadSplit { logits_e, .. } = &mut *ws;
19200 e.matvec_bf16_view_into(&head_lo, hn, logits_e, n_embd, half)?;
19202 e.stream().wait(&ws.ev_done)?;
19203 Ok(Some(()))
19204 }
19205 }
19206
19207 pub(crate) fn head_split_argmax_device(
19212 &self,
19213 e: &Engine,
19214 hn: &CudaSlice<f32>,
19215 token_d: &mut CudaSlice<u32>,
19216 ) -> Result<bool, Box<dyn std::error::Error>> {
19217 if self.head_split_fill_device(e, hn)?.is_none() {
19218 return Ok(false);
19219 }
19220 let n_vocab = self.cfg.n_vocab as usize;
19221 let guard = HEAD_SPLIT_WS
19222 .lock()
19223 .map_err(|_| "head split lock is poisoned")?;
19224 let ws = guard.as_ref().expect("filled above");
19225 let _main = e.gpu.enter_main()?;
19226 e.argmax_token_device_into(&ws.logits_e, token_d, n_vocab)?;
19227 Ok(true)
19228 }
19229
19230 pub(crate) fn head_split_logits_dtoh(
19233 &self,
19234 e: &Engine,
19235 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
19236 let guard = HEAD_SPLIT_WS
19237 .lock()
19238 .map_err(|_| "head split lock is poisoned")?;
19239 let ws = guard.as_ref().ok_or("head split logits not armed")?;
19240 let _main = e.gpu.enter_main()?;
19241 Ok(e.dtoh(&ws.logits_e)?)
19242 }
19243
19244 pub fn step35_token_graph_chunk(
19253 &self,
19254 e: &Engine,
19255 token: u32,
19256 k_target: usize,
19257 cache: &mut Cache,
19258 ) -> Result<Option<(Vec<u32>, Vec<f32>)>, Box<dyn std::error::Error>> {
19259 if !self.uses_sliding_gated_moe_program()
19260 || !crate::tp::step_tp_graph_enabled()?
19261 || !crate::tp::step_tp_dcw_enabled()?
19262 || !crate::tp::step_tp_qkv_fused_enabled()?
19263 || !crate::tp::step_tp_dev_router_enabled()?
19264 || !crate::tp::step_nvfp4_dev_routes_enabled()?
19265 {
19266 return Ok(None);
19267 }
19268 let n_layers = self.layers.len();
19269 let pos = cache.pos;
19270 let staged_next = pos + 1;
19271 if staged_next < 96 {
19272 return Ok(None);
19273 }
19274 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
19277 if !fa_vec {
19278 return Ok(None);
19279 }
19280 let sp = crate::fa_split_keys(staged_next, 8);
19281 let bucket_max = (n_splits * sp).max(staged_next);
19282 let to_boundary = bucket_max.saturating_sub(staged_next) + 1;
19283 let mut k = k_target.min(to_boundary).min(16);
19284 if k < 2 {
19285 return Ok(None);
19286 }
19287 for il in 0..n_layers {
19289 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
19290 return Ok(None);
19291 };
19292 while k >= 2 && tp_kv.peek_append_ring(k)?.1 {
19293 k -= 1;
19294 }
19295 if k < 2 {
19296 return Ok(None);
19297 }
19298 }
19299
19300 let mut state_guard = self
19301 .step35_token_graph
19302 .lock()
19303 .map_err(|_| "step35 token graph lock is poisoned")?;
19304 let Some(state) = state_guard.as_mut() else {
19305 return Ok(None); };
19307 if state.graphs.is_empty() {
19308 return Ok(None);
19309 }
19310 {
19311 let (b, g) = state.graphs.first_mut().expect("checked above");
19312 if *b != bucket_max {
19313 g.retarget_bucket(bucket_max)?;
19314 *b = bucket_max;
19315 }
19316 }
19317 let graph = state.graphs.first().map(|(_, g)| g).expect("checked above");
19318
19319 {
19321 let fa0 = match &self.layers[0].mixer {
19322 Mixer::Full(fa) => fa,
19323 _ => return Err("step35 token graph expects full-attention layers".into()),
19324 };
19325 let tp0 = fa0
19326 .step_tp_qkv
19327 .as_ref()
19328 .ok_or("step35 token graph lost its TP state")?;
19329 for rank in 0..tp0.runtime.devices().len() {
19330 let engine = tp0
19331 .runtime
19332 .rank_engine(rank)
19333 .ok_or("step35 token graph lost a rank engine")?;
19334 let _main = engine.gpu.enter_main()?;
19335 engine.stream().synchronize()?;
19336 }
19337 }
19338
19339 {
19342 let _main = e.gpu.enter_main()?;
19343 e.set_u32_one(&mut state.token_d, token)?;
19344 e.set_i32_one(&mut state.pos_d, pos as i32)?;
19345 e.set_i32_one(&mut state.hist_idx, 0)?;
19346 }
19347 for _ in 0..k {
19348 graph.launch(e)?;
19349 }
19350 for il in 0..n_layers {
19352 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
19353 let transaction = tp_kv.begin_transaction()?;
19354 let fa = match &self.layers[il].mixer {
19355 Mixer::Full(fa) => fa,
19356 _ => return Err("step35 token graph expects full-attention layers".into()),
19357 };
19358 let tp = fa
19359 .step_tp_qkv
19360 .as_ref()
19361 .ok_or("step35 token graph lost its TP state")?;
19362 let empty: [CudaSlice<f32>; 0] = [];
19363 tp.runtime.append_tp_kv_transaction_inner(
19364 tp_kv,
19365 transaction,
19366 &empty,
19367 &empty,
19368 k,
19369 true,
19370 )?;
19371 tp.runtime
19372 .commit_tp_kv_transaction_external(tp_kv, transaction, k)?;
19373 if let Some(local) = cache.kv[il].as_mut() {
19374 local.len = pos + k;
19375 let _main = e.gpu.enter_main()?;
19376 e.set_i32_one(&mut local.len_d, (pos + k) as i32)?;
19377 }
19378 }
19379 cache.pos = pos + k;
19380 let (hist, logits) = {
19381 let _main = e.gpu.enter_main()?;
19382 e.stream().synchronize()?;
19383 (e.dtoh_u32(&state.token_hist)?, e.dtoh(&state.logits_stage)?)
19384 };
19385 Ok(Some((hist[..k].to_vec(), logits)))
19386 }
19387}
19388
19389impl HybridModel {
19390 #[allow(clippy::too_many_arguments)]
19395 fn step35_token_graph_build(
19396 &self,
19397 e: &Engine,
19398 cache: &mut Cache,
19399 state: &mut Step35TokenGraphState,
19400 bucket_max: usize,
19401 ) -> Result<(), Box<dyn std::error::Error>> {
19402 use cudarc::driver::DevicePtr;
19403 let n_embd = self.cfg.n_embd as usize;
19404 let eps = self.cfg.rms_eps;
19405 let n_layers = self.layers.len();
19406 let started = std::time::Instant::now();
19407 if !crate::router_kernel_on() {
19408 return Err(
19409 "step35 token graph requires the router kernel (MEMRA_ROUTER_KERNEL=0)".into(),
19410 );
19411 }
19412 if !Engine::bf16_mmv_on() || n_embd % 8 != 0 {
19413 return Err("step35 token graph requires MEMRA_BF16_MMV bf16-resident matvecs".into());
19414 }
19415
19416 let embd_gpu = self
19418 .embd_gpu_try(e)
19419 .ok_or("step35 token graph could not upload the device embed table")?;
19420 let embd_qtype = match self.embd.ggml_type {
19421 memra_gguf::GgmlType::BF16 => crate::QT_BF16,
19422 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
19423 other => return Err(format!("token graph embed dtype {other:?} unhandled").into()),
19424 };
19425 let embd_row_bytes = self.embd.raw.len() / self.cfg.n_vocab as usize;
19426
19427 let (p_mixed, p_kshadow, p_vshadow) = {
19429 let _main = e.gpu.enter_main()?;
19430 let stream = e.stream();
19431 let (a, _g) = state.mixed_stage.device_ptr(&stream);
19432 let (b, _g) = state.k_shadow_stage.device_ptr(&stream);
19433 let (c, _g) = state.v_shadow_stage.device_ptr(&stream);
19434 (a as u64, b as u64, c as u64)
19435 };
19436
19437 crate::tp::token_graph_build_begin()?;
19438 let mut group_id: u32 = 0;
19439 for il in 0..n_layers {
19440 let layer = &self.layers[il];
19441 let fa = match &layer.mixer {
19442 Mixer::Full(fa) => fa,
19443 _ => return Err("step35 token graph expects full-attention layers".into()),
19444 };
19445 let tp = fa
19446 .step_tp_qkv
19447 .as_ref()
19448 .ok_or("step35 token graph lost its TP state")?;
19449 let attention = tp
19450 .attention
19451 .as_ref()
19452 .ok_or("step35 token graph lost its attention aux")?;
19453 let geometry = self.step35_geom(il);
19454 let window = geometry.window.map(|w| w as usize);
19455 let head_dim = geometry.head_dim_k as usize;
19456 let heads = geometry.n_head as usize;
19457 let kv_heads = geometry.n_head_kv as usize;
19458 let ranks = tp.runtime.devices().len();
19459 let local_heads = heads / ranks;
19460 let local_kv_heads = kv_heads / ranks;
19461 let layer_bucket = window.map(|w| bucket_max.min(w)).unwrap_or(bucket_max);
19462 let use_gate_shards =
19463 attention.gate_shards.is_some() || attention.gate_shards_bf16.is_some();
19464 if !use_gate_shards {
19465 return Err("step35 token graph requires the fused gate shards".into());
19466 }
19467
19468 let ws_index = tp
19469 .runtime
19470 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
19471 let ws_mutex = tp.runtime.decode_v2_workspace();
19472 let mut ws_guard = ws_mutex
19473 .lock()
19474 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
19475 let ws = ws_guard
19476 .get_mut(ws_index)
19477 .ok_or("step TP decode v2 workspace missing after ensure")?;
19478 tp.runtime
19479 .decode_v2_arm_token_mirrors(ws, p_mixed, (p_kshadow, p_vshadow))?;
19480 let mut rope_freqs = Vec::with_capacity(ranks);
19481 for rank in 0..ranks {
19482 let engine = tp
19483 .runtime
19484 .rank_engine(rank)
19485 .ok_or("step35 token graph lost a rank engine")?;
19486 rope_freqs.push(if geometry.rope_factors {
19487 self.step35_aux
19488 .as_ref()
19489 .and_then(|aux| aux.rope_freqs(engine))
19490 } else {
19491 None
19492 });
19493 }
19494 let gate_shards_arg = if let Some(shards) = attention.gate_shards.as_deref() {
19495 Some(crate::tp::StepTpGateShards::F32(shards))
19496 } else {
19497 attention
19498 .gate_shards_bf16
19499 .as_deref()
19500 .map(crate::tp::StepTpGateShards::Bf16)
19501 };
19502
19503 let decode_input = attention
19505 .decode_input
19506 .as_ref()
19507 .ok_or("step35 token graph requires the replicated decode input")?;
19508 let mut decode_input = decode_input
19509 .lock()
19510 .map_err(|_| "replicated decode input lock is poisoned")?;
19511 if ws.h_stage.is_none() {
19513 return Err(
19514 "step35 token graph requires the stage flow armed (run eager dcw first)".into(),
19515 );
19516 }
19517 {
19518 let state_x = &mut state.x;
19519 let token_d = &state.token_d;
19520 let pos_d = &state.pos_d;
19521 crate::tp::graph_section(e, None, || {
19522 let _main = e.gpu.enter_main()?;
19523 if il == 0 {
19524 e.embed_gather_device_into(
19525 embd_gpu,
19526 token_d,
19527 state_x,
19528 n_embd,
19529 embd_qtype,
19530 embd_row_bytes,
19531 )?;
19532 }
19533 {
19534 let h_stage = ws.h_stage.as_mut().expect("stage armed checked above");
19535 e.rms_norm(
19536 state_x,
19537 layer.attn_norm.float_data(),
19538 h_stage,
19539 n_embd,
19540 1,
19541 eps,
19542 )?;
19543 }
19544 {
19545 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
19546 let mut dst = pos_stage.slice_mut(0..1);
19547 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
19548 }
19549 Ok(())
19550 })?;
19551 }
19552
19553 group_id += 1;
19555 for rank in 0..ranks {
19556 let engine = tp
19557 .runtime
19558 .rank_engine(rank)
19559 .ok_or("step35 token graph lost a rank engine")?;
19560 {
19561 let ceiling = window
19566 .map(|w| cache.max_ctx.min(w))
19567 .unwrap_or(cache.max_ctx);
19568 let _main = engine.gpu.enter_main()?;
19569 engine.fa_dcw_pool_ensure(
19570 head_dim,
19571 local_heads,
19572 local_kv_heads,
19573 ceiling.min(2048),
19574 )?;
19575 engine.fa_dcw_pool_ensure(head_dim, local_heads, local_kv_heads, ceiling)?;
19576 engine.fa_dcw_pool_ensure(
19577 head_dim,
19578 local_heads,
19579 local_kv_heads,
19580 layer_bucket,
19581 )?;
19582 }
19583 let runtime = &tp.runtime;
19584 let q_norm = &attention.q_norm;
19585 let k_norm = &attention.k_norm;
19586 let gate_ref = gate_shards_arg.as_ref();
19587 crate::tp::graph_section(engine, Some(group_id), || {
19588 runtime.decode_v2_input_qkv_rank(
19589 ws,
19590 &state.pos_d,
19591 &mut decode_input,
19592 &tp.q,
19593 &tp.k,
19594 &tp.v,
19595 q_norm,
19596 k_norm,
19597 head_dim,
19598 geometry.n_rot as usize,
19599 geometry.rope_base,
19600 &rope_freqs,
19601 eps,
19602 gate_ref,
19603 true,
19604 false,
19605 rank,
19606 None,
19607 )?;
19608 let distributed = cache.tp_kv[il]
19611 .as_mut()
19612 .ok_or("step35 token graph lost a TP cache")?;
19613 let (kv_dim_k, kv_dim_v) = (distributed.kv_dim_k(), distributed.kv_dim_v());
19614 let (ktb, vtb) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
19615 let capacity = distributed.physical_capacity();
19616 {
19617 let rank_cache = distributed
19618 .rank_mut(rank)
19619 .ok_or("step35 token graph lost a rank cache")?;
19620 let (k_plane, v_plane, len_d, base_d) =
19621 rank_cache.planes_and_counters_mut();
19622 engine.append_kv_quantized_dcw(
19623 &ws.k[rank],
19624 &ws.v_raw[rank],
19625 k_plane,
19626 v_plane,
19627 len_d,
19628 base_d,
19629 kv_dim_k,
19630 kv_dim_v,
19631 ktb,
19632 vtb,
19633 )?;
19634 }
19635 {
19636 let rank_cache = distributed
19637 .rank_mut(rank)
19638 .ok_or("step35 token graph lost a rank cache")?;
19639 engine.inc_i32(rank_cache.len_d_mut())?;
19640 }
19641 let rank_cache = distributed
19642 .rank(rank)
19643 .ok_or("step35 token graph lost a rank cache")?;
19644 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * ktb);
19645 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * vtb);
19646 engine.fa_decode_dcw(
19651 &ws.q[rank],
19652 &k_ring,
19653 &v_ring,
19654 &mut ws.attn_out[rank],
19655 head_dim,
19656 local_heads,
19657 local_kv_heads,
19658 rank_cache.len_d(),
19659 rank_cache.base_d(),
19660 window.unwrap_or(0),
19661 layer_bucket,
19662 geometry.attention_scale(),
19663 ktb,
19664 vtb,
19665 None,
19666 )?;
19667 engine.attn_head_gate(
19668 &ws.attn_out[rank],
19669 &ws.gate[rank],
19670 &mut ws.gated[rank],
19671 None,
19672 head_dim,
19673 local_heads,
19674 1,
19675 )?;
19676 runtime.decode_v2_finish_rank_partial(ws, &tp.o, true, rank)?;
19677 Ok(())
19678 })?;
19679 }
19680
19681 {
19683 let root = tp
19684 .runtime
19685 .rank_engine(0)
19686 .ok_or("step35 token graph lost the root engine")?;
19687 let runtime = &tp.runtime;
19688 crate::tp::graph_section(root, None, || runtime.decode_v2_finish_root_fused(ws))?;
19689 }
19690 drop(ws_guard);
19691 drop(decode_input);
19692
19693 let probe_layer: Option<usize> = std::env::var("MEMRA_TG_PROBE_LAYER")
19694 .ok()
19695 .and_then(|v| v.parse().ok());
19696 if probe_layer == Some(il) {
19697 let Step35TokenGraphState {
19698 mixed_stage,
19699 probe_mixed,
19700 ..
19701 } = &mut *state;
19702 crate::tp::graph_section(e, None, || {
19703 let _main = e.gpu.enter_main()?;
19704 let mut dst = probe_mixed.slice_mut(0..n_embd);
19705 e.stream()
19706 .memcpy_dtod(&mixed_stage.slice(0..n_embd), &mut dst)?;
19707 Ok(())
19708 })?;
19709 }
19710
19711 match &layer.ffn {
19713 crate::hybrid::Ffn::Dense {
19714 ffn_gate,
19715 ffn_up,
19716 ffn_down,
19717 } => {
19718 let n_ff = ffn_gate.out_features();
19719 let lim = self.cfg.clamp_shexp_at(il as u32);
19720 if lim.is_some() {
19724 return Err("step35 token graph dense FFN with clamp unsupported".into());
19725 }
19726 let (wg_d, wu_d, wd_d) = match (ffn_gate, ffn_up, ffn_down) {
19727 (
19728 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
19729 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
19730 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
19731 ) => (wg, wu, wd),
19732 _ => {
19733 return Err(
19734 "step35 token graph dense FFN requires bf16-resident weights"
19735 .into(),
19736 );
19737 }
19738 };
19739 crate::tp::graph_section(e, None, || {
19740 let _main = e.gpu.enter_main()?;
19741 let Step35TokenGraphState {
19742 x,
19743 x1,
19744 mixed_stage,
19745 dense_z,
19746 dense_gate,
19747 dense_up,
19748 dense_act,
19749 sh_stage,
19750 ..
19751 } = &mut *state;
19752 e.add_rms_norm(
19753 x,
19754 mixed_stage,
19755 layer.post_attn_norm.float_data(),
19756 x1,
19757 dense_z,
19758 n_embd,
19759 1,
19760 eps,
19761 )?;
19762 e.matvec_bf16_into(wg_d, dense_z, dense_gate, n_embd, n_ff)?;
19766 e.matvec_bf16_into(wu_d, dense_z, dense_up, n_embd, n_ff)?;
19767 Self::ffn_act_lim(
19768 e, &self.cfg, dense_gate, dense_up, 1.0, 1.0, lim, dense_act, n_ff,
19769 )?;
19770 e.matvec_bf16_into(wd_d, dense_act, sh_stage, n_ff, n_embd)?;
19771 e.add(x1, sh_stage, x, n_embd)?;
19772 Ok(())
19773 })?;
19774 }
19775 crate::hybrid::Ffn::Moe(m) => {
19776 let moe = self
19777 .cfg
19778 .moe
19779 .as_ref()
19780 .ok_or("step35 token graph needs moe cfg")?;
19781 let n_expert = moe.expert_count as usize;
19782 let n_used = moe.expert_used_count as usize;
19783 let sigmoid = self
19784 .cfg
19785 .sigmoid_router()
19786 .ok_or("step35 token graph needs the sigmoid router")?;
19787 let step_tp = m
19788 .step_tp
19789 .as_ref()
19790 .ok_or("step35 token graph needs TP experts")?;
19791 let bank = match &step_tp.experts {
19792 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => bank,
19793 _ => return Err("step35 token graph needs the NVFP4 bank".into()),
19794 };
19795 let routes_ws_mutex = bank.device_workspace_handle();
19796 let mut routes_guard = routes_ws_mutex
19797 .lock()
19798 .map_err(|_| "routes workspace lock is poisoned")?;
19799 let routes_ws = routes_guard
19800 .as_mut()
19801 .ok_or("step35 token graph requires the routes workspace warmed")?;
19802 routes_ws.arm_stages(e, bank.input_width, n_used)?;
19803 step_tp.runtime.routes_arm_raw(bank, routes_ws)?;
19804 let p_z = {
19805 let root = step_tp
19806 .runtime
19807 .rank_engine(0)
19808 .ok_or("routes root engine missing")?;
19809 let _main = root.gpu.enter_main()?;
19810 let stream = root.stream();
19811 let in_stage = routes_ws
19812 .in_stage_handle()
19813 .ok_or("routes in stage not armed")?;
19814 let (a, _g) = in_stage.device_ptr(&stream);
19815 a as u64
19816 };
19817 let local_out = bank.expert_width / ranks;
19818
19819 crate::tp::graph_section(e, None, || {
19821 let _main = e.gpu.enter_main()?;
19822 {
19823 let in_stage = routes_ws
19824 .in_stage_mut()
19825 .ok_or("routes in stage not armed")?;
19826 let Step35TokenGraphState {
19827 x, x1, mixed_stage, ..
19828 } = &mut *state;
19829 e.add_rms_norm(
19830 x,
19831 mixed_stage,
19832 layer.post_attn_norm.float_data(),
19833 x1,
19834 in_stage,
19835 n_embd,
19836 1,
19837 eps,
19838 )?;
19839 }
19840 {
19841 let z_ref = routes_ws
19842 .in_stage_handle()
19843 .ok_or("routes in stage not armed")?;
19844 e.router_gemv_into(
19845 m.gate_inp.float_data(),
19846 z_ref,
19847 &mut state.router_logits,
19848 n_embd,
19849 n_expert,
19850 1,
19851 )?;
19852 }
19853 let (sel_e, w_e) = routes_ws
19854 .dev_route_e_mut()
19855 .ok_or("routes staging not armed")?;
19856 e.moe_router_sigmoid_topk_into(
19857 &state.router_logits,
19858 1,
19859 n_expert,
19860 n_used,
19861 m.active_count(),
19862 &m.exp_probs_b_dev,
19863 &m.active_experts_dev,
19864 sigmoid.0,
19865 sigmoid.1,
19866 sel_e,
19867 w_e,
19868 )?;
19869 Ok(())
19870 })?;
19871
19872 group_id += 1;
19874 for rank in 0..ranks {
19875 let engine = step_tp
19876 .runtime
19877 .rank_engine(rank)
19878 .ok_or("routes rank engine missing")?;
19879 let runtime = &step_tp.runtime;
19880 crate::tp::graph_section(engine, Some(group_id), || {
19881 runtime.routes_rank_section(
19882 bank,
19883 routes_ws,
19884 p_z,
19885 local_out,
19886 n_used,
19887 step_tp.activation_limit,
19888 rank,
19889 )
19890 })?;
19891 }
19892
19893 {
19895 let root = step_tp
19896 .runtime
19897 .rank_engine(0)
19898 .ok_or("routes root engine missing")?;
19899 let runtime = &step_tp.runtime;
19900 crate::tp::graph_section(root, None, || {
19901 runtime.routes_root_section(bank, routes_ws)
19902 })?;
19903 }
19904
19905 let lim_sh = self.cfg.clamp_shexp_at(il as u32);
19909 let (wg_sh, wu_sh, wd_sh) = match (&m.gate_shexp, &m.up_shexp, &m.down_shexp) {
19910 (
19911 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
19912 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
19913 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
19914 ) => (wg, wu, wd),
19915 _ => {
19916 return Err(
19917 "step35 token graph shexp requires bf16-resident weights".into()
19918 );
19919 }
19920 };
19921 let n_ff_sh = m
19922 .gate_shexp
19923 .as_ref()
19924 .expect("matched Some above")
19925 .out_features();
19926 let gate_inp_shexp = m.gate_inp_shexp.as_ref();
19929 crate::tp::graph_section(e, None, || {
19930 let _main = e.gpu.enter_main()?;
19931 let (z_ref, out_stage) = routes_ws
19932 .in_and_out_stages_mut()
19933 .ok_or("routes stages not armed")?;
19934 let Step35TokenGraphState {
19935 x,
19936 x1,
19937 sh_stage,
19938 shexp_gate,
19939 shexp_up,
19940 shexp_act,
19941 gate_sig,
19942 ..
19943 } = &mut *state;
19944 e.matvec_bf16_dual_into(
19945 wg_sh, wu_sh, z_ref, shexp_gate, shexp_up, n_embd, n_ff_sh,
19946 )?;
19947 Self::ffn_act_lim(
19948 e, &self.cfg, shexp_gate, shexp_up, 1.0, 1.0, lim_sh, shexp_act,
19949 n_ff_sh,
19950 )?;
19951 e.matvec_bf16_into(wd_sh, shexp_act, sh_stage, n_ff_sh, n_embd)?;
19952 if let Some(gate_w) = gate_inp_shexp {
19953 e.sigmoid_dot_rows_into(
19954 z_ref,
19955 gate_w.float_data(),
19956 gate_sig,
19957 n_embd,
19958 1,
19959 )?;
19960 }
19961 e.add_scaled_rows(sh_stage, gate_sig, out_stage, n_embd, 1)?;
19962 e.add(x1, out_stage, x, n_embd)?;
19963 Ok(())
19964 })?;
19965 }
19966 }
19967 if probe_layer == Some(il) {
19968 let Step35TokenGraphState { x, probe_x, .. } = &mut *state;
19969 crate::tp::graph_section(e, None, || {
19970 let _main = e.gpu.enter_main()?;
19971 let mut dst = probe_x.slice_mut(0..n_embd);
19972 e.stream().memcpy_dtod(&x.slice(0..n_embd), &mut dst)?;
19973 Ok(())
19974 })?;
19975 }
19976 }
19977
19978 let head = match &self.output {
19980 crate::model::GpuTensor::FloatBf16 { data, .. } => data,
19981 _ => return Err("step35 token graph head requires the bf16-resident output".into()),
19982 };
19983 crate::tp::graph_section(e, None, || {
19984 let _main = e.gpu.enter_main()?;
19985 let Step35TokenGraphState {
19986 x,
19987 hn,
19988 logits_stage,
19989 token_d,
19990 pos_d,
19991 token_hist,
19992 hist_idx,
19993 ..
19994 } = &mut *state;
19995 e.rms_norm(x, self.output_norm.float_data(), hn, n_embd, 1, eps)?;
19996 e.matvec_bf16_into(head, hn, logits_stage, n_embd, self.cfg.n_vocab as usize)?;
19997 e.argmax_token_device_into(logits_stage, token_d, self.cfg.n_vocab as usize)?;
20003 e.u32_hist_append(token_d, token_hist, hist_idx)?;
20004 e.inc_i32(pos_d)?;
20005 Ok(())
20006 })?;
20007
20008 let graph = crate::tp::token_graph_build_finish()?;
20009 state.graphs.push((bucket_max, graph));
20010 eprintln!(
20011 "[step35-token-graph] built bucket={bucket_max} layers={n_layers} \
20012 build_ms={:.0} performance_claim=false",
20013 started.elapsed().as_secs_f64() * 1e3
20014 );
20015 Ok(())
20016 }
20017}