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
169 && tokens >= PRIME_MIN_T
170 && matches!(ranks, 2 | 4)
171 && native_p2p
172 && has_rank_local_attention
173 && !fp8_kv
174}
175
176fn empty_cache_layers<T>(n: usize) -> Vec<Option<T>> {
177 std::iter::repeat_with(|| None).take(n).collect()
178}
179
180struct PrimeCacheStages<'a> {
185 parent: &'a mut Cache,
186 cut: usize,
187 stage0: Cache,
188 stage1: Cache,
189}
190
191impl<'a> PrimeCacheStages<'a> {
192 fn new(parent: &'a mut Cache, cut: usize) -> Self {
193 let n = parent.kv.len();
194 assert_eq!(parent.recur.len(), n, "cache layer vectors disagree");
195 assert!(cut <= n, "PP-2 cache cut {cut} exceeds {n} layers");
196 let mut kv0 = empty_cache_layers(n);
197 let mut kv1 = empty_cache_layers(n);
198 let mut tp_kv0 = empty_cache_layers(n);
199 let mut tp_kv1 = empty_cache_layers(n);
200 let mut recur0 = empty_cache_layers(n);
201 let mut recur1 = empty_cache_layers(n);
202 for i in 0..cut {
203 kv0[i] = parent.kv[i].take();
204 tp_kv0[i] = parent.tp_kv[i].take();
205 recur0[i] = parent.recur[i].take();
206 }
207 for i in cut..n {
208 kv1[i] = parent.kv[i].take();
209 tp_kv1[i] = parent.tp_kv[i].take();
210 recur1[i] = parent.recur[i].take();
211 }
212 let pos = parent.pos;
213 let max_ctx = parent.max_ctx;
214 Self {
215 parent,
216 cut,
217 stage0: Cache {
218 kv: kv0,
219 tp_kv: tp_kv0,
220 recur: recur0,
221 pos,
222 max_ctx,
223 last_logits_dev: None,
224 dflash_taps: None,
225 },
226 stage1: Cache {
227 kv: kv1,
228 tp_kv: tp_kv1,
229 recur: recur1,
230 pos,
231 max_ctx,
232 last_logits_dev: None,
233 dflash_taps: None,
234 },
235 }
236 }
237
238 fn parts(&mut self) -> (&mut Cache, &mut Cache) {
239 (&mut self.stage0, &mut self.stage1)
240 }
241}
242
243impl Drop for PrimeCacheStages<'_> {
244 fn drop(&mut self) {
245 let n = self.parent.kv.len();
246 for i in 0..n {
247 let source = if i < self.cut {
248 &mut self.stage0
249 } else {
250 &mut self.stage1
251 };
252 debug_assert!(self.parent.kv[i].is_none());
253 debug_assert!(self.parent.tp_kv[i].is_none());
254 debug_assert!(self.parent.recur[i].is_none());
255 self.parent.kv[i] = source.kv[i].take();
256 self.parent.tp_kv[i] = source.tp_kv[i].take();
257 self.parent.recur[i] = source.recur[i].take();
258 }
259 self.parent.pos = self.stage0.pos.min(self.stage1.pos);
260 }
261}
262
263pub(crate) struct AttnPre {
265 pub q: cudarc::driver::CudaSlice<f32>,
266 pub k: cudarc::driver::CudaSlice<f32>,
267 pub v: cudarc::driver::CudaSlice<f32>,
268 pub gate: Option<cudarc::driver::CudaSlice<f32>>,
269}
270
271pub(crate) struct GdnPrep {
273 pub hk: usize,
274 pub q_l2: cudarc::driver::CudaSlice<f32>,
275 pub k_l2: cudarc::driver::CudaSlice<f32>,
276 pub v_g: cudarc::driver::CudaSlice<f32>,
277 pub beta: cudarc::driver::CudaSlice<f32>,
278 pub g_log: cudarc::driver::CudaSlice<f32>,
279 pub kb16: Option<cudarc::driver::CudaSlice<u8>>,
280 pub qb16: Option<cudarc::driver::CudaSlice<u8>>,
281}
282
283pub(crate) struct VerifyStreamScratch {
285 pub pos_d: CudaSlice<i32>,
286 pub row_ctrs: Vec<CudaSlice<i32>>,
287}
288use crate::hybrid::{FullAttnLayer, HybridModel, LinearAttnLayer, Mixer, MoeWeights};
289
290struct MoeInputTraceWriter {
291 dir: std::path::PathBuf,
292 index: std::fs::File,
293 payloads: std::collections::HashMap<u16, (std::fs::File, u64)>,
294}
295
296static MOE_INPUT_TRACE_WRITER: std::sync::OnceLock<std::sync::Mutex<Option<MoeInputTraceWriter>>> =
297 std::sync::OnceLock::new();
298
299fn gdec_enabled() -> bool {
302 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
303 *E.get_or_init(|| {
304 std::env::var("MEMRA_MOE_GDEC")
305 .map(|v| v != "0")
306 .unwrap_or(true)
307 })
308}
309
310fn moe_slab_enabled() -> bool {
321 std::env::var("MEMRA_MOE_SLAB").as_deref() != Ok("0")
322}
323
324fn moe_grouped_enabled(_cfg: &ModelConfig, _prefill: bool) -> bool {
328 std::env::var("MEMRA_MOE_GROUPED")
329 .map(|value| value != "0")
330 .unwrap_or(false)
331}
332
333fn moe_prefetch_enabled() -> bool {
336 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
337 *E.get_or_init(|| {
338 std::env::var("MEMRA_MOE_PREFETCH").as_deref() == Ok("1")
339 || crate::spill_pread::worker_enabled()
340 })
341}
342
343fn moe_page_prefetch_window() -> usize {
348 static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
349 *W.get_or_init(|| {
350 page_prefetch_window_from_values(
351 std::env::var("MEMRA_MOE_PAGE_PREFETCH").as_deref() == Ok("1"),
352 std::env::var("MEMRA_MOE_PAGE_PREFETCH_WINDOW")
353 .ok()
354 .as_deref(),
355 )
356 })
357}
358
359fn page_prefetch_window_from_values(enabled: bool, raw_window: Option<&str>) -> usize {
360 if !enabled {
361 return 0;
362 }
363 raw_window.and_then(|value| value.parse().ok()).unwrap_or(1)
364}
365
366fn page_prefetch_positions(position: usize, len: usize, window: usize) -> std::ops::Range<usize> {
370 if window == 0 || position >= len {
371 return len..len;
372 }
373 let (start, count) = if position == 0 {
374 (1, window)
375 } else {
376 (position.saturating_add(window), 1)
377 };
378 let start = start.min(len);
379 start..start.saturating_add(count).min(len)
380}
381
382fn grouped_worker_prefetch_position(order_len: usize, current: Option<usize>) -> Option<usize> {
385 let position = current.map_or(0, |position| position.saturating_add(1));
386 (position < order_len).then_some(position)
387}
388
389fn worker_prefetch_window() -> usize {
394 static WINDOW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
395 *WINDOW.get_or_init(|| {
396 let automatic = crate::spill_pread::configured_depth().saturating_sub(1) / 3;
397 std::env::var("MEMRA_SPILL_WORKER_EXPERT_WINDOW")
398 .ok()
399 .and_then(|value| value.parse::<usize>().ok())
400 .unwrap_or(automatic.max(1))
401 })
402}
403
404fn worker_prefetch_positions(position: usize, len: usize, window: usize) -> std::ops::Range<usize> {
408 if window == 0 || position >= len {
409 return len..len;
410 }
411 let (start, count) = if position == 0 {
412 (0, window)
413 } else {
414 (position.saturating_add(window).saturating_sub(1), 1)
415 };
416 let start = start.min(len);
417 start..start.saturating_add(count).min(len)
418}
419
420fn moe_dev_enabled() -> bool {
425 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
426 *E.get_or_init(|| {
427 std::env::var("MEMRA_MOE_DEV")
428 .map(|v| v != "0")
429 .unwrap_or(true)
430 && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0"))
431 })
432}
433
434fn sigmoid_router_enabled() -> bool {
437 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
438 *E.get_or_init(|| {
439 std::env::var("MEMRA_SIG_ROUTER")
440 .map(|v| v != "0")
441 .unwrap_or(true)
442 })
443}
444
445fn moe_q8_enabled() -> bool {
450 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
451 *E.get_or_init(|| {
452 std::env::var("MEMRA_MOE_Q8")
453 .map(|v| v != "0")
454 .unwrap_or(true)
455 })
456}
457
458fn expert_dp4a_supported(qt: i32) -> bool {
461 qt == crate::QT_Q4_0
462 || qt == crate::QT_IQ3_S
463 || qt == crate::QT_IQ4_XS
464 || qt == crate::QT_Q3_K
465 || qt == crate::QT_Q4_K
466 || qt == crate::QT_Q6_K
467}
468
469fn q8_expert_supported(qt: i32) -> bool {
470 static KQ: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
476 let kq = *KQ.get_or_init(|| {
477 std::env::var("MEMRA_MOE_Q8_KQ")
478 .map(|v| v != "0")
479 .unwrap_or(true)
480 });
481 let nvfp4_q8 = std::env::var("MEMRA_MOE_Q8_NVFP4")
488 .map(|v| v != "0")
489 .unwrap_or(true);
490 qt == crate::QT_IQ3_S
491 || qt == crate::QT_IQ4_XS
492 || (nvfp4_q8 && qt == crate::QT_NVFP4)
493 || (kq && (qt == crate::QT_Q3_K || qt == crate::QT_Q4_K || qt == crate::QT_Q6_K))
494}
495
496fn q8_expert_dec_supported(qt: i32) -> bool {
499 qt == crate::QT_IQ3_S || qt == crate::QT_IQ4_XS || qt == crate::QT_Q4_0
500}
501
502fn f16g_proj_ok(qt: i32, in_f: usize) -> bool {
508 match qt {
509 crate::QT_Q4_0 => in_f % 32 == 0,
510 crate::QT_IQ4_XS | crate::QT_IQ3_S | crate::QT_Q3_K | crate::QT_Q4_K | crate::QT_Q6_K => {
511 in_f % 256 == 0
512 }
513 crate::QT_NVFP4 => in_f % 64 == 0,
518 _ => false,
519 }
520}
521
522fn moe_prewarm_enabled() -> bool {
525 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
526 *E.get_or_init(|| {
527 std::env::var("MEMRA_MOE_PREWARM")
528 .map(|v| v != "0")
529 .unwrap_or(true)
530 })
531}
532
533fn cpu_expert_profile_admit_enabled() -> bool {
537 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
538 *E.get_or_init(|| std::env::var("MEMRA_CPU_EXPERT_FREEZE_PROFILE_ADMIT").as_deref() == Ok("1"))
539}
540
541pub const PRIME_MIN_T: usize = 16;
545
546fn step_gemm_prime_suffix_on() -> bool {
575 std::env::var("MEMRA_STEP_GEMM_PRIME_SUFFIX").as_deref() != Ok("0")
576}
577
578const MOE_DEV_MAX_T: usize = 16;
587const PRIME_PIPE_MICROBATCHES: usize = 8;
588const PRIME_PIPE_MIN_CHUNK: usize = 128;
589const PRIME_PIPE_EDGE_MIN_CHUNK: usize = 64;
590const PRIME_PIPE_LINEAR_WORK: usize = 8;
591
592fn prime_pp2_auto_geometry(n_layers: usize) -> bool {
593 crate::pp::prime_pp_on()
594 && !crate::pp::pp2_streams_off()
595 && crate::pp::pp_cuts(n_layers).is_some_and(|cuts| cuts.len() == 3)
596}
597
598pub fn prime_chunk_tokens(t: usize, n_layers: usize) -> usize {
602 if let Ok(value) = std::env::var("MEMRA_PRIME_CHUNK") {
603 let parsed = value
604 .parse::<usize>()
605 .unwrap_or(crate::cache::PRIME_CHUNK_MAX_TOKENS);
606 return if crate::cache::swa_ring_on() {
607 if parsed == 0 {
608 crate::cache::PRIME_CHUNK_MAX_TOKENS
609 } else {
610 parsed.min(crate::cache::PRIME_CHUNK_MAX_TOKENS)
611 }
612 } else {
613 parsed
614 };
615 }
616 let chunk = crate::cache::PRIME_CHUNK_MAX_TOKENS;
617 if prime_pp2_auto_geometry(n_layers) && t >= 2 * PRIME_PIPE_MIN_CHUNK {
618 chunk.min(
619 t.div_ceil(PRIME_PIPE_MICROBATCHES)
620 .max(PRIME_PIPE_MIN_CHUNK),
621 )
622 } else {
623 chunk
624 }
625}
626
627fn fixed_prime_chunk_ranges(t: usize, chunk: usize) -> Vec<(usize, usize)> {
628 fixed_prime_chunk_ranges_for_ring(t, chunk, crate::cache::swa_ring_on())
629}
630
631fn fixed_prime_chunk_ranges_for_ring(t: usize, chunk: usize, ring_on: bool) -> Vec<(usize, usize)> {
632 if chunk == 0 || t <= chunk {
633 return vec![(0, t)];
634 }
635 let mut ranges = Vec::with_capacity(t.div_ceil(chunk));
636 let mut start = 0usize;
637 while start < t {
638 let mut end = (start + chunk).min(t);
639 if t - end > 0 && t - end < PRIME_MIN_T {
640 if ring_on {
641 let shifted = t - PRIME_MIN_T;
642 end = if shifted > start { shifted } else { t };
643 } else {
644 end = t;
645 }
646 }
647 ranges.push((start, end));
648 start = end;
649 }
650 ranges
651}
652
653fn prime_chunk_work(prefix: usize, total: usize) -> u128 {
654 let prefix = prefix as u128;
655 prefix * (prefix + (PRIME_PIPE_LINEAR_WORK as u128) * (total as u128))
656}
657
658fn dynamic_prime_chunk_ranges(
659 t: usize,
660 fixed_chunk: usize,
661 fixed: &[(usize, usize)],
662) -> Vec<(usize, usize)> {
663 let n = fixed.len();
664 if n < 3 {
665 return fixed.to_vec();
666 }
667
668 let max_first = t - (n - 1) * PRIME_MIN_T;
669 let first = fixed_chunk
670 .div_ceil(2)
671 .max(PRIME_PIPE_EDGE_MIN_CHUNK)
672 .min(max_first);
673 let mut ranges = Vec::with_capacity(n);
674 ranges.push((0, first));
675
676 let first_work = prime_chunk_work(first, t);
677 let work_span = prime_chunk_work(t, t) - first_work;
678 let denominator = (n - 1) as u128;
679 let mut previous = first;
680 for boundary in 1..n - 1 {
681 let target = first_work * denominator + work_span * (boundary as u128);
682 let remaining = n - 1 - boundary;
683 let mut low = previous + PRIME_MIN_T;
684 let mut high = t - remaining * PRIME_MIN_T;
685 while low < high {
686 let mid = low + (high - low) / 2;
687 if prime_chunk_work(mid, t) * denominator >= target {
688 high = mid;
689 } else {
690 low = mid + 1;
691 }
692 }
693 ranges.push((previous, low));
694 previous = low;
695 }
696 ranges.push((previous, t));
697 ranges
698}
699
700pub fn prime_chunk_ranges(t: usize, n_layers: usize, gdn_grid: bool) -> Vec<(usize, usize)> {
710 let explicit_chunk = std::env::var_os("MEMRA_PRIME_CHUNK").is_some();
711 let chunk = prime_chunk_tokens(t, n_layers);
712 let fixed = fixed_prime_chunk_ranges(t, chunk);
713 let dynamic = match std::env::var("MEMRA_PRIME_CHUNK_SCHED") {
714 Ok(value) => value == "dynamic",
715 Err(_) => true,
716 };
717 if explicit_chunk {
718 return fixed;
719 }
720 let ranges = if !dynamic || !prime_pp2_auto_geometry(n_layers) {
721 fixed
722 } else {
723 dynamic_prime_chunk_ranges(t, chunk, &fixed)
724 };
725 if gdn_grid && std::env::var("MEMRA_PRIME_GRID_ALIGN").as_deref() != Ok("0") {
729 align_prime_ranges_to_gdn(&ranges, t, Engine::gdn_chunk_size())
730 } else {
731 ranges
732 }
733}
734
735pub fn align_prime_ranges_to_gdn(
755 ranges: &[(usize, usize)],
756 t: usize,
757 c: usize,
758) -> Vec<(usize, usize)> {
759 if c == 0 || ranges.len() < 2 {
760 return ranges.to_vec();
761 }
762 let mut out: Vec<(usize, usize)> = Vec::with_capacity(ranges.len());
763 let mut start = 0usize;
764 for (i, &(_, end)) in ranges.iter().enumerate() {
765 let e = if i + 1 == ranges.len() {
766 t
767 } else {
768 end / c * c
769 };
770 if e > start {
771 out.push((start, e));
772 start = e;
773 } }
775 debug_assert_eq!(out.last().map(|&(_, e)| e), Some(t));
776 out
777}
778
779struct HeadSplit {
780 pin: u64,
781 w1: CudaSlice<u8>,
782 hn1: CudaSlice<f32>,
783 y1: CudaSlice<f32>,
784 logits_e: CudaSlice<f32>,
785 ev_hn: cudarc::driver::CudaEvent,
786 ev_done: cudarc::driver::CudaEvent,
787 raw_hn1: u64,
788 raw_y1: u64,
789 raw_logits_hi: u64,
790 samp: Option<SampScratch>,
795}
796
797struct SampScratch {
798 pb: CudaSlice<f32>,
799 th: CudaSlice<f32>,
800 z: CudaSlice<f32>,
801 mx: CudaSlice<f32>,
802 rows: CudaSlice<i32>,
803}
804static HEAD_SPLIT_WS: std::sync::Mutex<Option<HeadSplit>> = std::sync::Mutex::new(None);
806
807#[allow(clippy::type_complexity)]
811static DEV1_ROUTER_REPS: std::sync::Mutex<
812 Option<(
813 std::collections::HashMap<u16, (CudaSlice<f32>, CudaSlice<f32>, CudaSlice<u8>)>,
814 Option<CudaSlice<f32>>,
815 )>,
816> = std::sync::Mutex::new(None);
817
818#[allow(clippy::type_complexity)]
822static SHEXP_D1_REPS: std::sync::Mutex<
823 Option<std::collections::HashMap<u16, (CudaSlice<u8>, CudaSlice<u8>, CudaSlice<u8>)>>,
824> = std::sync::Mutex::new(None);
825#[allow(clippy::type_complexity)]
826static SHEXP_D1_WS: std::sync::Mutex<
827 Option<(
828 (usize, usize),
829 CudaSlice<f32>,
830 CudaSlice<f32>,
831 CudaSlice<f32>,
832 cudarc::driver::CudaEvent,
833 cudarc::driver::CudaEvent,
834 )>,
835> = std::sync::Mutex::new(None);
836
837static SHEXP_OV_WS: std::sync::Mutex<
839 Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>,
840> = std::sync::Mutex::new(None);
841
842impl HybridModel {
843 pub fn gdn_prime_grid_on(&self) -> bool {
849 Engine::gdn_chunked_enabled()
850 && self
851 .layers
852 .iter()
853 .any(|l| matches!(l.mixer, crate::hybrid::Mixer::Linear(_)))
854 }
855
856 fn step35_tp_device_resident(e: &Engine, tp: &crate::hybrid::StepTpQkv) -> bool {
861 tp.runtime.native_p2p() && tp.runtime.root_shares_ctx(e)
862 }
863
864 fn step35_tp_qkv(
865 &self,
866 e: &Engine,
867 fa: &FullAttnLayer,
868 h: &CudaSlice<f32>,
869 t: usize,
870 ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
871 let Some(tp) = fa.step_tp_qkv.as_ref() else {
872 return Ok(None);
873 };
874 let values = active_matrix_values(
875 h.len(),
876 t,
877 self.cfg.n_embd as usize,
878 "Step TP QKV activation",
879 )?;
880 if Self::step35_tp_device_resident(e, tp) {
889 e.stream().synchronize()?;
892 let q = tp
893 .runtime
894 .bf16_column_parallel_resident_native_device(&tp.q, h, t)?;
895 let k = tp
896 .runtime
897 .bf16_column_parallel_resident_native_device(&tp.k, h, t)?;
898 let v = tp
899 .runtime
900 .bf16_column_parallel_resident_native_device(&tp.v, h, t)?;
901 Self::step35_tp_log_once(tp, "qkv", "device-resident");
902 return Ok(Some(vec![q, k, v]));
903 }
904 let host = e.dtoh_view(&h.slice(0..values))?;
905 let q = if tp.runtime.native_p2p() {
906 tp.runtime
907 .bf16_column_parallel_resident_native(&tp.q, &host, t)?
908 } else {
909 tp.runtime
910 .bf16_column_parallel_resident(&tp.q, &host, t)?
911 .gathered
912 };
913 let k = if tp.runtime.native_p2p() {
914 tp.runtime
915 .bf16_column_parallel_resident_native(&tp.k, &host, t)?
916 } else {
917 tp.runtime
918 .bf16_column_parallel_resident(&tp.k, &host, t)?
919 .gathered
920 };
921 let v = if tp.runtime.native_p2p() {
922 tp.runtime
923 .bf16_column_parallel_resident_native(&tp.v, &host, t)?
924 } else {
925 tp.runtime
926 .bf16_column_parallel_resident(&tp.v, &host, t)?
927 .gathered
928 };
929 Self::step35_tp_log_once(tp, "qkv", "host-canonical");
930 Ok(Some(vec![e.htod(&q)?, e.htod(&k)?, e.htod(&v)?]))
931 }
932
933 fn step35_tp_log_once(tp: &crate::hybrid::StepTpQkv, proj: &str, activation: &'static str) {
937 use std::sync::atomic::{AtomicBool, Ordering};
938 static LOGGED: [AtomicBool; 4] = [
939 AtomicBool::new(false),
940 AtomicBool::new(false),
941 AtomicBool::new(false),
942 AtomicBool::new(false),
943 ];
944 let idx = 2 * usize::from(proj == "o") + usize::from(activation == "device-resident");
945 if LOGGED[idx].swap(true, Ordering::Relaxed) {
946 return;
947 }
948 eprintln!(
949 "[step-tp-{proj}] execute layer={} devices={:?} projections={proj} \
950 tensor_parallel=true attention_local=true kv_local=true transport={} \
951 native_p2p={} bulk_p2p={} activation={activation} \
952 output={} performance_claim=false (logged once per transport)",
953 tp.layer,
954 tp.devices,
955 tp.runtime.transport_label(),
956 tp.runtime.native_p2p(),
957 tp.runtime.bulk_p2p(),
958 if activation == "device-resident" {
959 "root-resident"
960 } else {
961 "root-readback"
962 },
963 );
964 }
965
966 fn step35_tp_o(
967 &self,
968 e: &Engine,
969 fa: &FullAttnLayer,
970 activation: &CudaSlice<f32>,
971 tokens: usize,
972 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
973 let Some(tp) = fa.step_tp_qkv.as_ref() else {
974 return Ok(None);
975 };
976 if Self::step35_tp_device_resident(e, tp) {
980 e.stream().synchronize()?; let output = tp
982 .runtime
983 .step_bf16_row_parallel_resident_native_device(&tp.o, activation, tokens)?;
984 Self::step35_tp_log_once(tp, "o", "device-resident");
985 return Ok(Some(output));
986 }
987 let host = e.dtoh(activation)?;
988 let output = if tp.runtime.native_p2p() {
989 tp.runtime
990 .step_bf16_row_parallel_resident_native(&tp.o, &host, tokens)?
991 } else {
992 tp.runtime
993 .step_bf16_row_parallel_resident(&tp.o, &host, tokens)?
994 };
995 Self::step35_tp_log_once(tp, "o", "host-canonical");
996 Ok(Some(e.htod(&output)?))
997 }
998
999 fn step35_o(
1000 &self,
1001 e: &Engine,
1002 fa: &FullAttnLayer,
1003 activation: &CudaSlice<f32>,
1004 tokens: usize,
1005 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1006 match self.step35_tp_o(e, fa, activation, tokens)? {
1007 Some(output) => Ok(output),
1008 None => e.matmul(&fa.wo, activation, tokens),
1009 }
1010 }
1011
1012 fn prime_trace_path() -> Option<&'static str> {
1017 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
1018 P.get_or_init(|| std::env::var("MEMRA_PRIME_TRACE").ok())
1019 .as_deref()
1020 }
1021
1022 fn prime_anatomy_on() -> bool {
1028 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1029 *E.get_or_init(|| std::env::var("MEMRA_PRIME_ANATOMY").as_deref() == Ok("1"))
1030 }
1031
1032 fn prime_anatomy_slots() -> &'static [std::sync::atomic::AtomicU64; 5] {
1033 static S: [std::sync::atomic::AtomicU64; 5] = [
1034 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), ];
1040 &S
1041 }
1042
1043 pub fn forward(
1045 &self,
1046 e: &Engine,
1047 tokens: &[u32],
1048 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
1049 if self.is_gemma4_e4b() {
1050 return self.gemma4_e4b_forward(e, tokens, false);
1051 }
1052 if self.uses_gemma_program() {
1053 return self.gemma4_forward(e, tokens, false);
1054 }
1055 let cfg = &self.cfg;
1056 let n_embd = cfg.n_embd as usize;
1057 let t = tokens.len();
1058 let eps = cfg.rms_eps;
1059 let pos: Vec<i32> = (0..t as i32).collect();
1060 let pos_d = e.htod_i32(&pos)?;
1061
1062 let mut x = self.embed(e, tokens)?; for (il, layer) in self.layers.iter().enumerate() {
1065 let mut h = e.uninit(t * n_embd)?;
1067 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1068
1069 let mixed = match &layer.mixer {
1070 Mixer::Full(fa) => self.full_attn(e, fa, &h, &pos_d, t, il)?,
1071 Mixer::Linear(la) => self.linear_attn(e, la, &h, t)?,
1072 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1073 };
1074
1075 let mut x1 = e.uninit(t * n_embd)?;
1077 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1078
1079 let mut z = e.uninit(t * n_embd)?;
1081 e.rms_norm(
1082 &x1,
1083 layer.post_attn_norm.float_data(),
1084 &mut z,
1085 n_embd,
1086 t,
1087 eps,
1088 )?;
1089 let ffn_out = match &layer.ffn {
1090 crate::hybrid::Ffn::Dense {
1091 ffn_gate,
1092 ffn_up,
1093 ffn_down,
1094 } => {
1095 let n_ff = ffn_gate.out_features();
1096 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1097 let up = g2.pop().unwrap();
1098 let gate = g2.pop().unwrap();
1099 let mut act = e.uninit(t * n_ff)?;
1100 Self::ffn_act_lim(
1105 e,
1106 &self.cfg,
1107 &gate,
1108 &up,
1109 1.0,
1110 1.0,
1111 self.cfg.clamp_shexp_at(il as u32),
1112 &mut act,
1113 t * n_ff,
1114 )?;
1115 e.matmul(ffn_down, &act, t)?
1116 }
1117 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
1118 };
1119 let mut x2 = e.uninit(t * n_embd)?;
1120 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1121 x = x2;
1122 }
1123
1124 let mut hn = e.uninit(t * n_embd)?;
1125 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1126 let logits = e.matmul(&self.output, &hn, t)?;
1127 Ok(e.dtoh(&logits)?)
1128 }
1129
1130 pub fn forward_last(
1136 &self,
1137 e: &Engine,
1138 tokens: &[u32],
1139 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
1140 if self.uses_gemma_program() {
1141 return self.gemma4_forward(e, tokens, true);
1142 }
1143 let cfg = &self.cfg;
1144 let n_embd = cfg.n_embd as usize;
1145 let t = tokens.len();
1146 let eps = cfg.rms_eps;
1147 let pos: Vec<i32> = (0..t as i32).collect();
1148 let pos_d = e.htod_i32(&pos)?;
1149
1150 let mut x = self.embed(e, tokens)?; let probe = std::env::var("MEMRA_LAYER_PROBE").is_ok();
1154 let anat = Self::prime_anatomy_on();
1155 let mut anat_last = if anat {
1156 e.stream().synchronize()?;
1157 Some(std::time::Instant::now())
1158 } else {
1159 None
1160 };
1161 macro_rules! anat_mark {
1162 ($slot:expr) => {
1163 if let Some(ts) = anat_last.as_mut() {
1164 e.stream().synchronize()?;
1165 Self::prime_anatomy_slots()[$slot].fetch_add(
1166 ts.elapsed().as_nanos() as u64,
1167 std::sync::atomic::Ordering::Relaxed,
1168 );
1169 *ts = std::time::Instant::now();
1170 }
1171 };
1172 }
1173 for (il, layer) in self.layers.iter().enumerate() {
1174 let mut h = e.uninit(t * n_embd)?;
1175 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1176 if probe {
1177 e.stream().synchronize()?;
1178 eprintln!("[probe] L{il} norm ok");
1179 }
1180 anat_mark!(4);
1181 let mixed = match &layer.mixer {
1182 Mixer::Full(fa) => {
1183 let y = self.full_attn(e, fa, &h, &pos_d, t, il)?;
1184 anat_mark!(0);
1185 y
1186 }
1187 Mixer::Linear(la) => {
1188 let y = self.linear_attn(e, la, &h, t)?;
1189 anat_mark!(1);
1190 y
1191 }
1192 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1193 };
1194 if probe {
1195 e.stream().synchronize()?;
1196 eprintln!("[probe] L{il} mixer ok");
1197 }
1198 let mut x1 = e.uninit(t * n_embd)?;
1199 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1200 let mut z = e.uninit(t * n_embd)?;
1201 e.rms_norm(
1202 &x1,
1203 layer.post_attn_norm.float_data(),
1204 &mut z,
1205 n_embd,
1206 t,
1207 eps,
1208 )?;
1209 anat_mark!(4);
1210 let ffn_out = match &layer.ffn {
1211 crate::hybrid::Ffn::Dense {
1212 ffn_gate,
1213 ffn_up,
1214 ffn_down,
1215 } => {
1216 let n_ff = ffn_gate.out_features();
1217 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1218 let up = g2.pop().unwrap();
1219 let gate = g2.pop().unwrap();
1220 let mut act = e.uninit(t * n_ff)?;
1221 Self::ffn_act_lim(
1223 e,
1224 &self.cfg,
1225 &gate,
1226 &up,
1227 1.0,
1228 1.0,
1229 self.cfg.clamp_shexp_at(il as u32),
1230 &mut act,
1231 t * n_ff,
1232 )?;
1233 let y = e.matmul(ffn_down, &act, t)?;
1234 anat_mark!(3);
1235 y
1236 }
1237 crate::hybrid::Ffn::Moe(m) => {
1238 let y = self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?;
1239 anat_mark!(2);
1240 y
1241 }
1242 };
1243 if probe {
1244 e.stream().synchronize()?;
1245 eprintln!("[probe] L{il} ffn ok");
1246 }
1247 let mut x2 = e.uninit(t * n_embd)?;
1248 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1249 x = x2;
1250 }
1251 if anat {
1252 let s = Self::prime_anatomy_slots();
1253 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
1254 eprintln!(
1255 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
1256 dense={:.1} norms_adds={:.1} (t={t}, forward_last)",
1257 ms(0),
1258 ms(1),
1259 ms(2),
1260 ms(3),
1261 ms(4)
1262 );
1263 }
1264 let mut hn = e.uninit(t * n_embd)?;
1266 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1267 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)?;
1270 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
1271 let logits = e.matmul(&self.output, &hlast, 1)?; Ok(e.dtoh(&logits)?)
1273 }
1274
1275 pub fn prime_cache(
1307 &self,
1308 e: &Engine,
1309 tokens: &[u32],
1310 cache: &mut Cache,
1311 queued_after: usize,
1312 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1313 self.prime_cache_overlaid(e, tokens, cache, queued_after, None)
1314 }
1315
1316 pub fn prime_cache_overlaid(
1322 &self,
1323 e: &Engine,
1324 tokens: &[u32],
1325 cache: &mut Cache,
1326 queued_after: usize,
1327 overlay: Option<&crate::vision::EmbedOverlay>,
1328 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1329 let n_embd = self.cfg.n_embd as usize;
1330 let t = tokens.len();
1331 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
1360 let seq_end = if legacy_calllocal {
1361 cache.pos + t
1362 } else {
1363 cache.pos + t + queued_after
1364 };
1365 if overlay.is_none()
1370 && (cache.pos == 0 || step_gemm_prime_suffix_on())
1371 && t >= PRIME_MIN_T
1372 && crate::step_gemm_prime_on()
1373 && self.uses_sliding_gated_moe_program()
1374 {
1375 let n_embd = self.cfg.n_embd as usize;
1376 let base = cache.pos;
1377 let width = crate::cache::PRIME_CHUNK_MAX_TOKENS;
1378 let mut hiddens = e.uninit(t * n_embd)?;
1379 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1380 let mut start = 0usize;
1381 while start < t {
1382 let mut end = (start + width).min(t);
1385 if t - end > 0 && t - end < PRIME_MIN_T {
1386 end = t;
1387 }
1388 let mut out = self.step35_prime_cache_batch(
1389 e,
1390 &[&tokens[start..end]],
1391 &mut [cache],
1392 &[seq_end],
1393 )?;
1394 if out.len() != 1 {
1395 return Err("B=1 batched prime returned a non-singleton".into());
1396 }
1397 let (logits, h_seed, hidden) = out.remove(0);
1398 e.copy_into(
1399 &mut hiddens,
1400 start * n_embd,
1401 &hidden,
1402 (end - start) * n_embd,
1403 )?;
1404 last = Some((logits, h_seed));
1405 start = end;
1406 }
1407 let (logits, h_seed) = last.expect("prime produced no chunk");
1408 eprintln!(
1413 "[gemm-prime] ENGAGED t={t} base={base} seq_end={seq_end} chunks<={width} (GEMM trunk + grouped MoE)"
1414 );
1415 return Ok((logits, h_seed, hiddens));
1416 }
1417 if self.uses_sliding_gated_moe_program() {
1418 eprintln!(
1419 "[gemm-prime] WALK t={t} base={} seq_end={seq_end} (batched prime declined)",
1420 cache.pos
1421 );
1422 }
1423 if overlay.is_none() {
1424 if let Some(out) = self.step35_prime_trows(e, tokens, cache)? {
1425 return Ok(out);
1426 }
1427 }
1428 assert!(
1432 t >= PRIME_MIN_T,
1433 "prime_cache needs T >= {PRIME_MIN_T} (caller gates)"
1434 );
1435 assert!(
1436 cache.pos + t <= cache.max_ctx,
1437 "prime_cache: prompt exceeds cache max_ctx"
1438 );
1439
1440 if self.is_gemma4_e4b() || self.uses_gemma_program() {
1452 if self.is_gemma4_e4b() {
1453 if overlay.is_some() {
1454 return Err(
1455 "vision embedding overlay is unsupported on gemma4 E4B (PLE prime)".into(),
1456 );
1457 }
1458 return self.gemma4_e4b_prime(e, tokens, cache);
1459 }
1460 return self.gemma4_prime(e, tokens, cache, overlay);
1465 }
1466 let ranges = prime_chunk_ranges(t, self.layers.len(), self.gdn_prime_grid_on());
1467 if ranges.len() == 1 {
1505 return self.prime_chunk(e, tokens, cache, seq_end, 0, overlay);
1506 }
1507 if crate::pp::prime_pipe_on() && crate::pp::prime_pp_on() && !crate::pp::pp2_streams_off() {
1512 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()).filter(|f| f.len() == 3) {
1513 if overlay.is_some() {
1514 return Err(
1515 "vision embedding overlay + pipelined PP prime unsupported (v1); \
1516 run the serial prime (single device or MEMRA_PRIME_PIPE=0)"
1517 .into(),
1518 );
1519 }
1520 if crate::pp::pp_multi_stream_same_device() {
1521 return Err(
1522 "prime chunk pipeline refused with 2 stage streams on one device — \
1523 that concurrent-stream placement remains quarantined by the deferred \
1524 pp flake record. Use one device per stage or MEMRA_PRIME_PIPE=0 for \
1525 the serial split."
1526 .into(),
1527 );
1528 }
1529 return self.prime_cache_pp2_pipelined(e, tokens, cache, seq_end, &ranges, &fence);
1530 }
1531 }
1532 let mut hiddens = e.uninit(t * n_embd)?;
1533 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1534 for &(start, end) in &ranges {
1535 if let Some(taps) = cache.dflash_taps.as_mut() {
1537 taps.base = start;
1538 }
1539 let (l, hs, x) =
1540 self.prime_chunk(e, &tokens[start..end], cache, seq_end, start, overlay)?;
1541 e.copy_into(&mut hiddens, start * n_embd, &x, (end - start) * n_embd)?;
1542 last = Some((l, hs));
1543 }
1544 let (logits, h_seed) = last.unwrap();
1545 Ok((logits, h_seed, hiddens))
1546 }
1547
1548 fn prime_cache_pp2_pipelined(
1553 &self,
1554 e: &Engine,
1555 tokens: &[u32],
1556 cache: &mut Cache,
1557 seq_end: usize,
1558 ranges: &[(usize, usize)],
1559 fence: &[usize],
1560 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1561 debug_assert_eq!(fence.len(), 3);
1562 debug_assert!(ranges.len() >= 2);
1563 let rt = crate::pp::PpNRt::get(e)?;
1564 assert_eq!(
1565 rt.n_stages(),
1566 2,
1567 "prime pipeline requires exactly two PP stages"
1568 );
1569 let n_embd = self.cfg.n_embd as usize;
1570 let t = tokens.len();
1571 let initial_base = cache.pos;
1572 let caller_stream = e.stream();
1573
1574 rt.fence_stages_behind(&caller_stream)?;
1579 let max_payload = ranges.iter().map(|(s, e)| (e - s) * n_embd).max().unwrap();
1580 rt.prepare_overlap_slots(0, max_payload)?;
1581
1582 let mut hiddens = e.uninit(t * n_embd)?;
1583 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1584 let mut stage_caches = PrimeCacheStages::new(cache, fence[1]);
1585 let (cache0, cache1) = stage_caches.parts();
1586 let (first_start, first_end) = ranges[0];
1587 let mut slot = self.prime_pp2_stage0_enqueue(
1588 e,
1589 rt,
1590 &tokens[first_start..first_end],
1591 cache0,
1592 seq_end,
1593 fence,
1594 initial_base + first_start,
1595 true,
1596 )?;
1597 cache0.pos = initial_base + first_end;
1598
1599 for (i, &(start, end)) in ranges.iter().enumerate() {
1600 let base = initial_base + start;
1601 debug_assert_eq!(
1602 cache1.pos, base,
1603 "stage 1 must drain chunks in original position order"
1604 );
1605 let (out, next_slot) = if let Some(&(next_start, next_end)) = ranges.get(i + 1) {
1606 let next_base = initial_base + next_start;
1607 debug_assert_eq!(
1608 cache0.pos, next_base,
1609 "stage 0 must issue chunks in original position order"
1610 );
1611 let cache0_stage = &mut *cache0;
1612 std::thread::scope(|scope| -> Result<_, Box<dyn std::error::Error>> {
1617 let stage0 = scope.spawn(move || -> Result<usize, String> {
1618 let next = self
1619 .prime_pp2_stage0_enqueue(
1620 e,
1621 rt,
1622 &tokens[next_start..next_end],
1623 cache0_stage,
1624 seq_end,
1625 fence,
1626 next_base,
1627 true,
1628 )
1629 .map_err(|err| err.to_string())?;
1630 cache0_stage.pos = initial_base + next_end;
1631 Ok(next)
1632 });
1633 let x = self.prime_pp2_stage1_enqueue(
1634 e,
1635 rt,
1636 slot,
1637 end - start,
1638 cache1,
1639 seq_end,
1640 fence,
1641 base,
1642 true,
1643 )?;
1644 let out = {
1645 rt.bind_stage(1)?;
1646 let _st1 = rt.enter(1);
1647 let e1 = rt.engine(1, e);
1648 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1649 };
1650 let next = stage0
1651 .join()
1652 .map_err(|_| "pipeprime stage-0 host walker panicked")?
1653 .map_err(|err| -> Box<dyn std::error::Error> { err.into() })?;
1654 Ok((out, Some(next)))
1655 })?
1656 } else {
1657 let x = self.prime_pp2_stage1_enqueue(
1658 e,
1659 rt,
1660 slot,
1661 end - start,
1662 cache1,
1663 seq_end,
1664 fence,
1665 base,
1666 true,
1667 )?;
1668 let out = {
1669 rt.bind_stage(1)?;
1670 let _st1 = rt.enter(1);
1671 let e1 = rt.engine(1, e);
1672 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1673 };
1674 (out, None)
1675 };
1676
1677 rt.publish_to(1, &caller_stream)?;
1678 e.copy_into(&mut hiddens, start * n_embd, &out.2, (end - start) * n_embd)?;
1679 last = Some((out.0, out.1));
1680 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1681
1682 if let Some(next) = next_slot {
1683 rt.fence_stages_behind(&caller_stream)?;
1688 slot = next;
1689 }
1690 }
1691
1692 debug_assert_eq!(cache0.pos, initial_base + t);
1693 debug_assert_eq!(cache1.pos, initial_base + t);
1694 let (logits, h_seed) = last.unwrap();
1695 Ok((logits, h_seed, hiddens))
1696 }
1697
1698 fn gdn_hk(e: &Engine, t: usize, num_v: usize, num_k: usize) -> usize {
1705 if Engine::gdn_db_on()
1706 && Engine::gdn_chunked_enabled()
1707 && t >= 16
1708 && e.gdn_mma_enabled(Engine::gdn_chunk_size())
1709 && num_k * 2 == num_v
1710 {
1711 num_k
1712 } else {
1713 num_v
1714 }
1715 }
1716
1717 fn f16out_on(e: &Engine, t: usize) -> bool {
1722 crate::f16_ffi::pp_f16_enabled()
1723 && t >= 16
1724 && !e.verify_exact_on()
1725 && std::env::var("MEMRA_F16OUT").as_deref() != Ok("0")
1726 }
1727
1728 pub fn prime_slabs_get(
1736 &self,
1737 e: &Engine,
1738 t: usize,
1739 n_embd: usize,
1740 n_ff_max: usize,
1741 ) -> Result<std::sync::Arc<std::sync::Mutex<PrimeSlabs>>, Box<dyn std::error::Error>> {
1742 let mut slabs = self.prime_slabs.lock().unwrap();
1743 let dev = e.ctx().ordinal();
1744 let need_new = match slabs.get(&dev) {
1745 None => true,
1746 Some(sl) => sl.lock().unwrap().t_cap < t,
1747 };
1748 if need_new {
1749 slabs.insert(
1750 dev,
1751 std::sync::Arc::new(std::sync::Mutex::new(PrimeSlabs {
1752 t_cap: t,
1753 h: e.uninit(t * n_embd)?,
1754 x1: e.uninit(t * n_embd)?,
1755 z: e.uninit(t * n_embd)?,
1756 act: e.uninit(t * n_ff_max)?,
1757 xa: e.uninit(t * n_embd)?,
1758 xb: e.uninit(t * n_embd)?,
1759 h16: e.alloc_u8_uninit(t * n_embd * 2)?,
1760 z16: e.alloc_u8_uninit(t * n_embd * 2)?,
1761 gate: e.uninit(t * n_ff_max)?,
1762 up: e.uninit(t * n_ff_max)?,
1763 ffn_out: e.uninit(t * n_embd)?,
1764 seg_glue: Vec::new(),
1765 mixed: e.uninit(t * n_embd)?,
1766 seg_mid: Vec::new(),
1767 seg_t: 0,
1768 })),
1769 );
1770 }
1771 Ok(slabs.get(&dev).expect("prime slab inserted").clone())
1772 }
1773
1774 fn prime_chunk(
1778 &self,
1779 e: &Engine,
1780 tokens: &[u32],
1781 cache: &mut Cache,
1782 seq_end: usize,
1783 chunk_off: usize,
1784 overlay: Option<&crate::vision::EmbedOverlay>,
1785 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1786 if crate::pp::pp_host_bounce_active()
1787 && (self.uses_gemma_program() || !crate::pp::prime_pp_on())
1788 {
1789 return Err(
1790 "prime_chunk: refused with MEMRA_PP_HOST_BOUNCE=1 because this configuration \
1791 has no active prime stage split and would peer-read remote weights; keep \
1792 MEMRA_PRIME_PP enabled and use a PP-prime-supported model"
1793 .into(),
1794 );
1795 }
1796 if !self.uses_gemma_program() && !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
1805 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
1806 if overlay.is_some() {
1807 return Err("vision embedding overlay + PP prime unsupported (v1); \
1808 run single-device or MEMRA_PRIME_PP=0"
1809 .into());
1810 }
1811 return self.prime_chunk_ppn(e, tokens, cache, seq_end, &fence);
1812 }
1813 }
1814 if crate::pp::pp_host_bounce_active() {
1815 return Err(
1816 "prime_chunk: MEMRA_PP_HOST_BOUNCE=1 found no valid prime stage split; \
1817 refusing an unsplit remote-weight walk"
1818 .into(),
1819 );
1820 }
1821 let t = tokens.len();
1822 let base = cache.pos;
1823 debug_assert!(
1824 seq_end >= base + t,
1825 "prime_chunk: seq_end must cover this chunk"
1826 );
1827 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1828 let pos_d = e.htod_i32(&pos)?;
1829
1830 let mut x_embed = self.embed(e, tokens)?; if let Some(ov) = overlay {
1832 let n_embd = self.cfg.n_embd as usize;
1836 for &(pos, row_off, n_rows) in &ov.spans {
1837 let lo = pos.max(chunk_off);
1838 let hi = (pos + n_rows).min(chunk_off + t);
1839 if lo < hi {
1840 let src_row = row_off + (lo - pos);
1841 let view = ov
1842 .rows
1843 .slice(src_row * n_embd..(src_row + (hi - lo)) * n_embd);
1844 e.copy_view_into(
1845 &mut x_embed,
1846 (lo - chunk_off) * n_embd,
1847 &view,
1848 (hi - lo) * n_embd,
1849 )?;
1850 }
1851 }
1852 }
1853 let x = self.prime_layers(
1854 e,
1855 x_embed,
1856 0,
1857 self.layers.len(),
1858 &pos_d,
1859 t,
1860 base,
1861 cache,
1862 seq_end,
1863 )?;
1864 self.prime_chunk_epilogue(e, x, t, cache)
1865 }
1866
1867 #[allow(clippy::too_many_arguments)]
1883 fn prime_layers(
1884 &self,
1885 e: &Engine,
1886 x_in: CudaSlice<f32>,
1887 lo: usize,
1888 hi: usize,
1889 pos_d: &CudaSlice<i32>,
1890 t: usize,
1891 base: usize,
1892 cache: &mut Cache,
1893 seq_end: usize,
1894 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1895 let cfg = &self.cfg;
1896 let n_embd = cfg.n_embd as usize;
1897 let eps = cfg.rms_eps;
1898 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
1902 let n_ff_max = self
1909 .layers
1910 .iter()
1911 .map(|l| match &l.ffn {
1912 crate::hybrid::Ffn::Dense { ffn_gate, .. } => ffn_gate.out_features(),
1913 _ => n_embd,
1914 })
1915 .max()
1916 .unwrap_or(n_embd)
1917 .max(n_embd);
1918 let use_slabs = std::env::var("MEMRA_PRIME_SLABS").as_deref() != Ok("0");
1919 let slab = if use_slabs {
1920 Some(self.prime_slabs_get(e, t, n_embd, n_ff_max)?)
1921 } else {
1922 None
1923 };
1924 let mut slab_guard = slab.as_ref().map(|sl| sl.lock().unwrap());
1925 let mut x_own; type SlabRefs<'a> = (
1927 &'a mut CudaSlice<f32>,
1928 &'a mut CudaSlice<f32>,
1929 &'a mut CudaSlice<f32>,
1930 &'a mut CudaSlice<f32>,
1931 &'a mut CudaSlice<u8>,
1932 &'a mut CudaSlice<u8>,
1933 &'a mut CudaSlice<f32>,
1934 &'a mut CudaSlice<f32>,
1935 &'a mut CudaSlice<f32>,
1936 );
1937 let (mut x_cur, mut x_nxt, sl): (
1938 &mut CudaSlice<f32>,
1939 &mut CudaSlice<f32>,
1940 Option<SlabRefs>,
1941 );
1942 let mut seg: Option<(
1943 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1944 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1945 &mut CudaSlice<f32>,
1946 &mut usize,
1947 )> = None;
1948 let mut x_own2;
1949 match slab_guard.as_mut() {
1950 Some(g) => {
1951 let slabs = &mut **g;
1952 e.copy_into(&mut slabs.xa, 0, &x_in, t * n_embd)?;
1953 let PrimeSlabs {
1954 xa,
1955 xb,
1956 h,
1957 x1,
1958 z,
1959 act,
1960 h16,
1961 z16,
1962 gate,
1963 up,
1964 ffn_out,
1965 seg_glue,
1966 mixed,
1967 seg_mid,
1968 seg_t,
1969 ..
1970 } = slabs;
1971 x_cur = xa;
1972 x_nxt = xb;
1973 seg = Some((seg_glue, seg_mid, mixed, seg_t));
1974 sl = Some((h, x1, z, act, h16, z16, gate, up, ffn_out));
1975 }
1976 None => {
1977 x_own = x_in;
1978 x_own2 = e.uninit(t * n_embd)?;
1979 x_cur = &mut x_own;
1980 x_nxt = &mut x_own2;
1981 sl = None;
1982 }
1983 }
1984 let mut alloc_h;
1985 let mut alloc_x1;
1986 let mut alloc_z;
1987 let mut alloc_act;
1988 let mut alloc_h16;
1989 let mut alloc_z16;
1990 let mut alloc_gate;
1991 let mut alloc_up;
1992 let mut alloc_fo;
1993 let (h, x1, z, act): (
1994 &mut CudaSlice<f32>,
1995 &mut CudaSlice<f32>,
1996 &mut CudaSlice<f32>,
1997 &mut CudaSlice<f32>,
1998 );
1999 let (h16, z16): (&mut CudaSlice<u8>, &mut CudaSlice<u8>);
2000 let (sl_gate, sl_up, sl_fo): (
2001 &mut CudaSlice<f32>,
2002 &mut CudaSlice<f32>,
2003 &mut CudaSlice<f32>,
2004 );
2005 match sl {
2006 Some((a, b, c, d, e16, f16b, g, u, fo)) => {
2007 h = a;
2008 x1 = b;
2009 z = c;
2010 act = d;
2011 h16 = e16;
2012 z16 = f16b;
2013 sl_gate = g;
2014 sl_up = u;
2015 sl_fo = fo;
2016 }
2017 None => {
2018 alloc_h = e.uninit(t * n_embd)?;
2019 alloc_x1 = e.uninit(t * n_embd)?;
2020 alloc_z = e.uninit(t * n_embd)?;
2021 alloc_act = e.uninit(t * n_ff_max)?;
2022 alloc_h16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2023 alloc_z16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2024 alloc_gate = e.uninit(t * n_ff_max)?;
2025 alloc_up = e.uninit(t * n_ff_max)?;
2026 alloc_fo = e.uninit(t * n_embd)?;
2027 h = &mut alloc_h;
2028 x1 = &mut alloc_x1;
2029 z = &mut alloc_z;
2030 act = &mut alloc_act;
2031 h16 = &mut alloc_h16;
2032 z16 = &mut alloc_z16;
2033 sl_gate = &mut alloc_gate;
2034 sl_up = &mut alloc_up;
2035 sl_fo = &mut alloc_fo;
2036 }
2037 }
2038 let n_layers = self.layers.len();
2043 let use_seg = f16fuse
2053 && seg.is_some()
2054 && !self.uses_sliding_gated_moe_program()
2055 && lo == 0
2056 && hi == n_layers
2057 && std::env::var("MEMRA_PRIME_SEG").as_deref() == Ok("1");
2058 if let Some((sg, sm, _, st)) = seg.as_mut() {
2059 if **st != t {
2060 sg.clear();
2061 sg.extend((0..n_layers).map(|_| None));
2062 sm.clear();
2063 sm.extend((0..n_layers).map(|_| None));
2064 **st = t;
2065 }
2066 }
2067 {
2068 let layer_lo = &self.layers[lo];
2069 if f16fuse {
2070 e.rms_norm_f16out(
2071 x_cur,
2072 layer_lo.attn_norm.float_data(),
2073 h,
2074 h16,
2075 n_embd,
2076 t,
2077 eps,
2078 )?;
2079 } else {
2080 e.rms_norm(x_cur, layer_lo.attn_norm.float_data(), h, n_embd, t, eps)?;
2081 }
2082 }
2083 let anat = Self::prime_anatomy_on();
2084 let mut anat_last = if anat {
2085 e.stream().synchronize()?;
2086 Some(std::time::Instant::now())
2087 } else {
2088 None
2089 };
2090 macro_rules! anat_mark {
2092 ($slot:expr) => {
2093 if let Some(ts) = anat_last.as_mut() {
2094 e.stream().synchronize()?;
2095 Self::prime_anatomy_slots()[$slot].fetch_add(
2096 ts.elapsed().as_nanos() as u64,
2097 std::sync::atomic::Ordering::Relaxed,
2098 );
2099 *ts = std::time::Instant::now();
2100 }
2101 };
2102 }
2103 for il in lo..hi {
2104 let layer = &self.layers[il];
2105 let hx16 = if f16fuse { Some(&*h16) } else { None };
2106 if use_seg {
2107 let (pre, pre16, w_out) = match &layer.mixer {
2110 Mixer::Full(fa) => {
2111 let g3 = match hx16 {
2112 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
2113 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
2114 };
2115 let (pre, pre16) =
2116 self.full_attn_prime_core_inner(e, fa, g3, &pos_d, t, cache, il)?;
2117 (pre, pre16, &fa.wo)
2118 }
2119 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2120 Mixer::Linear(la) => {
2121 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2122 let g4 = match hx16 {
2123 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
2124 None => e.matmul_group(&ws, h, t)?,
2125 };
2126 let (pre, pre16) =
2127 self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, None)?;
2128 (pre, pre16, &la.ssm_out)
2129 }
2130 };
2131 {
2132 let (_, sm, mslab, _) = seg.as_mut().unwrap();
2133 let pre_n = pre.len() / t;
2134 let xh_pre = match pre16 {
2135 Some(x) => x,
2136 None => e.f16_act(&pre, t * pre_n, pre_n)?,
2137 };
2138 if !e.try_f16_gemm_pre_into(w_out, &xh_pre, t, mslab)? {
2139 let y = e.matmul(w_out, &pre, t)?;
2140 e.copy_into(mslab, 0, &y, t * n_embd)?;
2141 }
2142 if sm[il].is_none() {
2143 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
2144 let w_post = layer.post_attn_norm.float_data();
2145 e.stream().synchronize()?;
2146 e.stream()
2147 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2148 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2149 e.add(x_cur, mslab, x1, t * n_embd)?;
2150 e.rms_norm_f16out(x1, w_post, z, z16, n_embd, t, eps)?;
2151 Ok(())
2152 })();
2153 let g = e.stream().end_capture(
2154 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH);
2155 r?;
2156 sm[il] = Some(g?.ok_or("S-mid capture produced no graph")?);
2157 }
2158 sm[il].as_ref().unwrap().launch()?;
2159 }
2160 } else {
2161 let mixed = match &layer.mixer {
2162 Mixer::Full(fa) => {
2163 let y =
2164 self.full_attn_prime(e, fa, h, hx16, &pos_d, t, cache, il, seq_end)?;
2165 anat_mark!(0);
2166 y
2167 }
2168 Mixer::Linear(la) => {
2169 let y = self.linear_attn_prime(e, la, h, hx16, t, cache, il)?;
2170 anat_mark!(1);
2171 y
2172 }
2173 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2174 };
2175 if f16fuse {
2176 e.add_rms_norm_f16out(
2179 x_cur,
2180 &mixed,
2181 layer.post_attn_norm.float_data(),
2182 x1,
2183 z,
2184 z16,
2185 n_embd,
2186 t,
2187 eps,
2188 )?;
2189 } else {
2190 e.add(x_cur, &mixed, x1, t * n_embd)?;
2191 e.rms_norm(x1, layer.post_attn_norm.float_data(), z, n_embd, t, eps)?;
2192 }
2193 anat_mark!(4);
2194 }
2195 let zx16 = if f16fuse { Some(&*z16) } else { None };
2196 match &layer.ffn {
2197 crate::hybrid::Ffn::Dense {
2198 ffn_gate,
2199 ffn_up,
2200 ffn_down,
2201 } => {
2202 let n_ff = ffn_gate.out_features();
2203 let mut into_ok = false;
2206 if let Some(xh) = zx16 {
2207 into_ok = e.try_f16_gemm_pre_into(ffn_gate, xh, t, sl_gate)?
2208 && e.try_f16_gemm_pre_into(ffn_up, xh, t, sl_up)?;
2209 }
2210 if !into_ok {
2211 let mut g2 = match zx16 {
2212 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], z, xh, t)?,
2213 None => e.matmul_group(&[ffn_gate, ffn_up], z, t)?,
2214 };
2215 let up_y = g2.pop().unwrap();
2216 let gate_y = g2.pop().unwrap();
2217 e.copy_into(sl_gate, 0, &gate_y, t * n_ff)?;
2218 e.copy_into(sl_up, 0, &up_y, t * n_ff)?;
2219 }
2220 let d_lim = self.cfg.clamp_shexp_at(il as u32);
2225 let act16 = if Self::f16out_on(e, t) && self.cfg.m3.is_none() && d_lim.is_none()
2226 {
2227 let mut a16 = e.alloc_u8_uninit(t * n_ff * 2)?;
2228 e.silu_mul_f16out(sl_gate, sl_up, act, &mut a16, t * n_ff)?;
2229 Some(a16)
2230 } else {
2231 Self::ffn_act_lim(
2232 e,
2233 &self.cfg,
2234 sl_gate,
2235 sl_up,
2236 1.0,
2237 1.0,
2238 d_lim,
2239 act,
2240 t * n_ff,
2241 )?;
2242 None
2243 };
2244 let xh_act = match act16 {
2246 Some(x) => x,
2247 None => e.f16_act(act, t * n_ff, n_ff)?,
2248 };
2249 if !e.try_f16_gemm_pre_into(ffn_down, &xh_act, t, sl_fo)? {
2250 let y = e.matmul(ffn_down, &*act, t)?;
2251 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2252 }
2253 }
2254 crate::hybrid::Ffn::Moe(m) => {
2255 let y = self.moe_ffn_il_prefill(e, m, z, t, il as u16)?;
2256 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2257 anat_mark!(2);
2258 }
2259 }
2260 if let (crate::hybrid::Ffn::Dense { .. }, true) = (&layer.ffn, anat) {
2261 anat_mark!(3);
2262 }
2263 if use_seg && il + 1 < hi {
2264 let w_next = self.layers[il + 1].attn_norm.float_data();
2266 let (sg, _, _, _) = seg.as_mut().unwrap();
2267 if sg[il].is_none() {
2268 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
2269 e.stream().synchronize()?;
2270 e.stream()
2271 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2272 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2273 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2274 e.rms_norm_f16out(x_nxt, w_next, h, h16, n_embd, t, eps)?;
2275 Ok(())
2276 })();
2277 let g = e.stream().end_capture(
2278 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
2279 );
2280 r?;
2281 sg[il] = Some(g?.ok_or("S-glue capture produced no graph")?);
2282 }
2283 sg[il].as_ref().unwrap().launch()?;
2284 } else {
2285 if il + 1 < hi {
2286 let w_next = self.layers[il + 1].attn_norm.float_data();
2287 if f16fuse {
2288 e.add_rms_norm_f16out(x1, sl_fo, w_next, x_nxt, h, h16, n_embd, t, eps)?;
2289 } else {
2290 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2291 e.rms_norm(x_nxt, w_next, h, n_embd, t, eps)?;
2292 }
2293 } else {
2294 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2295 }
2296 }
2297 anat_mark!(4);
2298 if let Some(path) = Self::prime_trace_path() {
2304 let row = (base + t - 1) as usize;
2305 let host = e.dtoh(x_nxt)?;
2306 let last = &host[(t - 1) * n_embd..t * n_embd];
2307 use std::io::Write as _;
2308 let mut f = std::fs::OpenOptions::new()
2309 .create(true)
2310 .append(true)
2311 .open(path)?;
2312 let mut h64: u64 = 0xcbf29ce484222325;
2313 for v in last {
2314 h64 ^= v.to_bits() as u64;
2315 h64 = h64.wrapping_mul(0x100000001b3);
2316 }
2317 writeln!(
2318 f,
2319 "{{\"pos\":{row},\"layer\":{il},\"t\":{t},\"base\":{base},\
2320 \"hash\":\"{h64:016x}\",\"v0\":{:.9e},\"v1\":{:.9e},\"v2\":{:.9e}}}",
2321 last[0], last[1], last[2]
2322 )?;
2323 }
2324 self.dflash_tap(e, cache, il, x_nxt, t)?;
2327 std::mem::swap(&mut x_cur, &mut x_nxt);
2328 }
2329 if anat {
2330 let s = Self::prime_anatomy_slots();
2331 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
2332 eprintln!(
2333 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
2334 dense={:.1} norms_adds={:.1} (t={t}, layers {lo}..{hi})",
2335 ms(0),
2336 ms(1),
2337 ms(2),
2338 ms(3),
2339 ms(4)
2340 );
2341 }
2342 let mut x = e.uninit(t * n_embd)?;
2344 e.copy_into(&mut x, 0, x_cur, t * n_embd)?;
2345 drop(slab_guard);
2346 Ok(x)
2347 }
2348
2349 fn prime_chunk_epilogue(
2354 &self,
2355 e: &Engine,
2356 x: CudaSlice<f32>,
2357 t: usize,
2358 cache: &mut Cache,
2359 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2360 let n_embd = self.cfg.n_embd as usize;
2361 let eps = self.cfg.rms_eps;
2362 let mut h_seed = e.uninit(n_embd)?;
2366 if !crate::spec::spec_hpost() {
2367 e.copy_view_into(
2368 &mut h_seed,
2369 0,
2370 &x.slice((t - 1) * n_embd..t * n_embd),
2371 n_embd,
2372 )?;
2373 }
2374 let mut hn = e.uninit(t * n_embd)?;
2376 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2377 if crate::spec::spec_hpost() {
2378 e.copy_view_into(
2379 &mut h_seed,
2380 0,
2381 &hn.slice((t - 1) * n_embd..t * n_embd),
2382 n_embd,
2383 )?;
2384 }
2385 let last = e.view(&hn, t * n_embd);
2386 let last_row = last.slice((t - 1) * n_embd..t * n_embd);
2387 let mut hlast = e.uninit(n_embd)?;
2388 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
2389 let logits = e.matmul(&self.output, &hlast, 1)?;
2390 cache.pos += t;
2391 Ok((
2394 e.dtoh(&logits)?,
2395 h_seed,
2396 if crate::spec::spec_hpost() { hn } else { x },
2397 ))
2398 }
2399
2400 pub fn hidden_postnorm_row(
2406 &self,
2407 e: &Engine,
2408 hiddens: &CudaSlice<f32>,
2409 row: usize,
2410 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2411 let n_embd = self.cfg.n_embd as usize;
2412 let mut x1 = e.uninit(n_embd)?;
2413 e.copy_view_into(
2414 &mut x1,
2415 0,
2416 &hiddens.slice(row * n_embd..(row + 1) * n_embd),
2417 n_embd,
2418 )?;
2419 if crate::spec::spec_hpost() {
2420 return Ok(e.dtoh(&x1)?);
2421 }
2422 let mut hn = e.uninit(n_embd)?;
2423 e.rms_norm(
2424 &x1,
2425 self.output_norm.float_data(),
2426 &mut hn,
2427 n_embd,
2428 1,
2429 self.cfg.rms_eps,
2430 )?;
2431 Ok(e.dtoh(&hn)?)
2432 }
2433
2434 fn prime_chunk_ppn(
2458 &self,
2459 e: &Engine,
2460 tokens: &[u32],
2461 cache: &mut Cache,
2462 seq_end: usize,
2463 fence: &[usize],
2464 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2465 let rt = crate::pp::PpNRt::get(e)?;
2466 let n_st = fence.len() - 1;
2467 assert_eq!(
2468 rt.n_stages(),
2469 n_st,
2470 "PpNRt stage count {} != fence stages {n_st}",
2471 rt.n_stages()
2472 );
2473 let n_embd = self.cfg.n_embd as usize;
2474 let t = tokens.len();
2475 let base = cache.pos;
2476 debug_assert!(
2477 seq_end >= base + t,
2478 "prime_chunk_ppn: seq_end must cover this chunk"
2479 );
2480 let payload = t * n_embd;
2481 let caller_stream = e.stream();
2485 rt.fence_stages_behind(&caller_stream)?;
2486
2487 if n_st == 2 {
2488 let slot =
2489 self.prime_pp2_stage0_enqueue(e, rt, tokens, cache, seq_end, fence, base, false)?;
2490 let x =
2491 self.prime_pp2_stage1_enqueue(e, rt, slot, t, cache, seq_end, fence, base, false)?;
2492 let out = {
2493 rt.bind_stage(1)?;
2494 let _st1 = rt.enter(1);
2495 let e1 = rt.engine(1, e);
2496 self.prime_chunk_epilogue(e1, x, t, cache)?
2497 };
2498 rt.publish_to(1, &caller_stream)?;
2499 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2500 return Ok(out);
2501 }
2502
2503 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2504
2505 let mut slot = {
2507 let _st0 = rt.enter(0);
2508 let e0 = rt.engine(0, e);
2509 let pos_d = e0.htod_i32(&pos)?;
2510 let x = self.embed(e0, tokens)?;
2511 let x =
2512 self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2513 rt.tx(0, &x, payload)?
2514 };
2516
2517 for s in 1..n_st - 1 {
2519 let _st = rt.enter(s);
2520 let es = rt.engine(s, e);
2521 let pos_d = es.htod_i32(&pos)?;
2522 let x = rt.rx(s - 1, slot, payload)?;
2523 let x = self.prime_layers(
2524 es,
2525 x,
2526 fence[s],
2527 fence[s + 1],
2528 &pos_d,
2529 t,
2530 base,
2531 cache,
2532 seq_end,
2533 )?;
2534 slot = rt.tx(s, &x, payload)?;
2535 }
2536
2537 let _stl = rt.enter(n_st - 1);
2539 let el = rt.engine(n_st - 1, e);
2540 let pos_d = el.htod_i32(&pos)?;
2541 let x = rt.rx(n_st - 2, slot, payload)?;
2542 let x = self.prime_layers(
2543 el,
2544 x,
2545 fence[n_st - 1],
2546 fence[n_st],
2547 &pos_d,
2548 t,
2549 base,
2550 cache,
2551 seq_end,
2552 )?;
2553 let out = self.prime_chunk_epilogue(el, x, t, cache)?;
2554 rt.publish_to(n_st - 1, &caller_stream)?;
2560 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2561 Ok(out)
2562 }
2563
2564 fn prime_pp2_stage0_enqueue(
2565 &self,
2566 e: &Engine,
2567 rt: &crate::pp::PpNRt,
2568 tokens: &[u32],
2569 cache: &mut Cache,
2570 seq_end: usize,
2571 fence: &[usize],
2572 base: usize,
2573 pipelined: bool,
2574 ) -> Result<usize, Box<dyn std::error::Error>> {
2575 let t = tokens.len();
2576 let n_embd = self.cfg.n_embd as usize;
2577 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2578 rt.bind_stage(0)?;
2579 let _st0 = rt.enter(0);
2580 let e0 = rt.engine(0, e);
2581 let pos_d = e0.htod_i32(&pos)?;
2582 let x = self.embed(e0, tokens)?;
2583 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2584 let x = self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2585 if pipelined {
2586 rt.tx_pipelined(0, &x, t * n_embd)
2587 } else {
2588 rt.tx(0, &x, t * n_embd)
2589 }
2590 }
2591
2592 fn prime_pp2_stage1_enqueue(
2593 &self,
2594 e: &Engine,
2595 rt: &crate::pp::PpNRt,
2596 slot: usize,
2597 t: usize,
2598 cache: &mut Cache,
2599 seq_end: usize,
2600 fence: &[usize],
2601 base: usize,
2602 pipelined: bool,
2603 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2604 let n_embd = self.cfg.n_embd as usize;
2605 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2606 rt.bind_stage(1)?;
2607 let _st1 = rt.enter(1);
2608 let e1 = rt.engine(1, e);
2609 let pos_d = e1.htod_i32(&pos)?;
2610 let x = rt.rx(0, slot, t * n_embd)?;
2611 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2612 self.prime_layers(e1, x, fence[1], fence[2], &pos_d, t, base, cache, seq_end)
2613 }
2614
2615 pub fn prime_chunk_captured(
2631 &self,
2632 e: &Engine,
2633 x_in: &CudaSlice<f32>,
2634 pos_d: &CudaSlice<i32>,
2635 t: usize,
2636 cache: &mut Cache,
2637 len_d: &CudaSlice<i32>,
2638 logits_out: &mut CudaSlice<f32>,
2639 h_seed_out: &mut CudaSlice<f32>,
2640 ) -> Result<(), Box<dyn std::error::Error>> {
2641 let cfg = &self.cfg;
2642 let n_embd = cfg.n_embd as usize;
2643 let eps = cfg.rms_eps;
2644 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
2645 let mut x = e.uninit(t * n_embd)?;
2646 e.copy_into(&mut x, 0, x_in, t * n_embd)?;
2647 for (il, layer) in self.layers.iter().enumerate() {
2648 let mut h = e.uninit(t * n_embd)?;
2649 let mut hx16: Option<CudaSlice<u8>> = None;
2650 if f16fuse {
2651 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2652 e.rms_norm_f16out(
2653 &x,
2654 layer.attn_norm.float_data(),
2655 &mut h,
2656 &mut b16,
2657 n_embd,
2658 t,
2659 eps,
2660 )?;
2661 hx16 = Some(b16);
2662 } else {
2663 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
2664 }
2665 let mixed = match &layer.mixer {
2666 Mixer::Full(fa) => {
2670 self.full_attn_prime(e, fa, &h, hx16.as_ref(), pos_d, t, cache, il, t)?
2671 }
2672 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2673 Mixer::Linear(la) => {
2674 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2675 let g4 = match hx16.as_ref() {
2676 Some(xh) => e.matmul_group_xh(&ws, &h, xh, t)?,
2677 None => e.matmul_group(&ws, &h, t)?,
2678 };
2679 self.linear_attn_prime_core_pad(e, la, g4, t, cache, il, Some(len_d))?
2680 }
2681 };
2682 let mut x1 = e.uninit(t * n_embd)?;
2683 e.add(&x, &mixed, &mut x1, t * n_embd)?;
2684 let mut z = e.uninit(t * n_embd)?;
2685 let mut zx16: Option<CudaSlice<u8>> = None;
2686 if f16fuse {
2687 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2688 e.rms_norm_f16out(
2689 &x1,
2690 layer.post_attn_norm.float_data(),
2691 &mut z,
2692 &mut b16,
2693 n_embd,
2694 t,
2695 eps,
2696 )?;
2697 zx16 = Some(b16);
2698 } else {
2699 e.rms_norm(
2700 &x1,
2701 layer.post_attn_norm.float_data(),
2702 &mut z,
2703 n_embd,
2704 t,
2705 eps,
2706 )?;
2707 }
2708 let ffn_out = match &layer.ffn {
2709 crate::hybrid::Ffn::Dense {
2710 ffn_gate,
2711 ffn_up,
2712 ffn_down,
2713 } => {
2714 let n_ff = ffn_gate.out_features();
2715 let mut g2 = match &zx16 {
2716 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], &z, xh, t)?,
2717 None => e.matmul_group(&[ffn_gate, ffn_up], &z, t)?,
2718 };
2719 let up = g2.pop().unwrap();
2720 let gate = g2.pop().unwrap();
2721 let mut act = e.uninit(t * n_ff)?;
2722 Self::ffn_act_lim(
2724 e,
2725 &self.cfg,
2726 &gate,
2727 &up,
2728 1.0,
2729 1.0,
2730 self.cfg.clamp_shexp_at(il as u32),
2731 &mut act,
2732 t * n_ff,
2733 )?;
2734 e.matmul(ffn_down, &act, t)?
2735 }
2736 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
2737 };
2738 let mut x2 = e.uninit(t * n_embd)?;
2739 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
2740 x = x2;
2741 }
2742 if !crate::spec::spec_hpost() {
2744 e.row_gather_dev(&x, h_seed_out, len_d, n_embd)?;
2745 }
2746 let mut hn = e.uninit(t * n_embd)?;
2747 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2748 if crate::spec::spec_hpost() {
2749 e.row_gather_dev(&hn, h_seed_out, len_d, n_embd)?;
2750 }
2751 let mut hlast = e.uninit(n_embd)?;
2752 e.row_gather_dev(&hn, &mut hlast, len_d, n_embd)?;
2753 let logits = e.matmul(&self.output, &hlast, 1)?;
2754 let nv = logits.len();
2755 e.copy_into(logits_out, 0, &logits, nv)?;
2756 Ok(())
2757 }
2758
2759 fn step35_prime_batch_on() -> bool {
2760 std::env::var("MEMRA_STEP35_PRIME_BATCH").as_deref() != Ok("0")
2761 }
2762
2763 #[allow(clippy::too_many_arguments)]
2766 #[allow(clippy::too_many_arguments)]
2771 fn step35_prime_batch_layers(
2772 &self,
2773 e: &Engine,
2774 mut x: CudaSlice<f32>,
2775 lo: usize,
2776 hi: usize,
2777 ts: &[usize],
2778 offs: &[usize],
2779 seq_ends: &[usize],
2780 pos_ds: &[CudaSlice<i32>],
2781 caches: &mut [&mut Cache],
2782 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2783 let cfg = &self.cfg;
2784 let n_embd = cfg.n_embd as usize;
2785 let eps = cfg.rms_eps;
2786 let b = ts.len();
2787 let total: usize = ts.iter().sum();
2788 let f16fuse = crate::f16_ffi::pp_f16_enabled() && total >= 16;
2789
2790 let split = |e: &Engine,
2791 y: &CudaSlice<f32>,
2792 dim: usize|
2793 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2794 let mut out = Vec::with_capacity(b);
2795 for s in 0..b {
2796 let mut ys = e.uninit(ts[s] * dim)?;
2797 e.copy_view_into(
2798 &mut ys,
2799 0,
2800 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2801 ts[s] * dim,
2802 )?;
2803 out.push(ys);
2804 }
2805 Ok(out)
2806 };
2807
2808 let prof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1");
2813 let mut ph = [0f64; 4]; let mut mark = |e: &Engine, acc: usize, t0: &mut std::time::Instant, ph: &mut [f64; 4]| {
2815 if prof {
2816 let _ = e.stream().synchronize();
2817 ph[acc] += t0.elapsed().as_secs_f64() * 1e3;
2818 *t0 = std::time::Instant::now();
2819 }
2820 };
2821 let mut pt = std::time::Instant::now();
2822 for il in lo..hi {
2823 let layer = &self.layers[il];
2824 let Mixer::Full(fa) = &layer.mixer else {
2825 return Err(format!("step35 layer {il} is not full-attn — corrupt config").into());
2826 };
2827
2828 let mut h = e.uninit(total * n_embd)?;
2829 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2830 if f16fuse {
2831 e.rms_norm_f16out(
2832 &x,
2833 layer.attn_norm.float_data(),
2834 &mut h,
2835 &mut hx16,
2836 n_embd,
2837 total,
2838 eps,
2839 )?;
2840 } else {
2841 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, total, eps)?;
2842 }
2843
2844 let gate_w = fa
2848 .attn_gate
2849 .as_ref()
2850 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
2851 let mut g4 = if f16fuse {
2852 e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, &hx16, total)?
2853 } else {
2854 e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, total)?
2855 };
2856 let gate = g4.pop().unwrap();
2857 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2858 (0..b).map(|_| Vec::with_capacity(3)).collect();
2859 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g4) {
2860 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2861 parts[s].push(ys);
2862 }
2863 }
2864 let gates = split(e, &gate, gate_w.out_features())?;
2865 let geometry = self.step35_geom(il);
2866 let hd = geometry.head_dim_k as usize;
2867 let nh = geometry.n_head as usize;
2868 let mut ag_cat = e.uninit(total * nh * hd)?;
2869 for (s, (g3s, gate)) in parts.into_iter().zip(gates).enumerate() {
2870 mark(e, 0, &mut pt, &mut ph);
2871 let ag = self.step35_attn_pre_wo(
2872 e,
2873 fa,
2874 g3s,
2875 None,
2876 Some(&gate),
2877 &pos_ds[s],
2878 ts[s],
2879 Some(&mut *caches[s]),
2880 il,
2881 seq_ends[s],
2882 )?;
2883 e.copy_into(&mut ag_cat, offs[s] * nh * hd, &ag, ts[s] * nh * hd)?;
2884 }
2885 mark(e, 1, &mut pt, &mut ph);
2886 let mixed = e.matmul(&fa.wo, &ag_cat, total)?;
2887
2888 let mut x1 = e.uninit(total * n_embd)?;
2889 let mut z = e.uninit(total * n_embd)?;
2890 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2891 if f16fuse {
2892 e.add_rms_norm_f16out(
2893 &x,
2894 &mixed,
2895 layer.post_attn_norm.float_data(),
2896 &mut x1,
2897 &mut z,
2898 &mut zx16,
2899 n_embd,
2900 total,
2901 eps,
2902 )?;
2903 } else {
2904 e.add(&x, &mixed, &mut x1, total * n_embd)?;
2905 e.rms_norm(
2906 &x1,
2907 layer.post_attn_norm.float_data(),
2908 &mut z,
2909 n_embd,
2910 total,
2911 eps,
2912 )?;
2913 }
2914
2915 mark(e, 2, &mut pt, &mut ph);
2916 let ffn_out = match &layer.ffn {
2917 crate::hybrid::Ffn::Dense {
2918 ffn_gate,
2919 ffn_up,
2920 ffn_down,
2921 } => {
2922 let n_ff = ffn_gate.out_features();
2923 let mut g2 = if f16fuse {
2924 e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?
2925 } else {
2926 e.matmul_group(&[ffn_gate, ffn_up], &z, total)?
2927 };
2928 let up = g2.pop().unwrap();
2929 let gate = g2.pop().unwrap();
2930 let mut act = e.uninit(total * n_ff)?;
2931 let d_lim = cfg.clamp_shexp_at(il as u32);
2932 if Self::f16out_on(e, total) && cfg.m3.is_none() && d_lim.is_none() {
2933 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2934 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2935 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2936 Some(y) => y,
2937 None => e.matmul(ffn_down, &act, total)?,
2938 }
2939 } else {
2940 Self::ffn_act_lim(
2941 e,
2942 cfg,
2943 &gate,
2944 &up,
2945 1.0,
2946 1.0,
2947 d_lim,
2948 &mut act,
2949 total * n_ff,
2950 )?;
2951 e.matmul(ffn_down, &act, total)?
2952 }
2953 }
2954 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il(e, m, &z, total, il as u16)?,
2955 };
2956 let mut x2 = e.uninit(total * n_embd)?;
2957 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2958 x = x2;
2959 mark(e, 3, &mut pt, &mut ph);
2960 }
2961 if prof {
2962 eprintln!(
2963 "[prime-prof] t={total} layers={} norm+qkv={:.0}ms attn={:.0}ms o_proj={:.0}ms moe={:.0}ms",
2964 hi - lo,
2965 ph[0],
2966 ph[1],
2967 ph[2],
2968 ph[3]
2969 );
2970 }
2971 Ok(x)
2972 }
2973
2974 fn step35_prime_batch_epilogue(
2975 &self,
2976 e: &Engine,
2977 x: CudaSlice<f32>,
2978 ts: &[usize],
2979 offs: &[usize],
2980 caches: &mut [&mut Cache],
2981 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2982 let n_embd = self.cfg.n_embd as usize;
2983 let total: usize = ts.iter().sum();
2984 let mut hn = e.uninit(total * n_embd)?;
2985 e.rms_norm(
2986 &x,
2987 self.output_norm.float_data(),
2988 &mut hn,
2989 n_embd,
2990 total,
2991 self.cfg.rms_eps,
2992 )?;
2993
2994 let hidden_src = if crate::spec::spec_hpost() { &hn } else { &x };
2995 let mut out = Vec::with_capacity(ts.len());
2996 for s in 0..ts.len() {
2997 let mut hidden = e.uninit(ts[s] * n_embd)?;
2998 e.copy_view_into(
2999 &mut hidden,
3000 0,
3001 &hidden_src.slice(offs[s] * n_embd..(offs[s] + ts[s]) * n_embd),
3002 ts[s] * n_embd,
3003 )?;
3004 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3005 let mut h_seed = e.uninit(n_embd)?;
3006 e.copy_view_into(
3007 &mut h_seed,
3008 0,
3009 &hidden_src.slice(last0..last0 + n_embd),
3010 n_embd,
3011 )?;
3012 let mut hlast = e.uninit(n_embd)?;
3014 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3015 let logits = e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?;
3016 caches[s].pos += ts[s];
3017 out.push((logits, h_seed, hidden));
3018 }
3019 Ok(out)
3020 }
3021
3022 fn step35_prime_cache_batch(
3027 &self,
3028 e: &Engine,
3029 prompts: &[&[u32]],
3030 caches: &mut [&mut Cache],
3031 seq_ends: &[usize],
3032 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
3033 assert_eq!(
3034 seq_ends.len(),
3035 prompts.len(),
3036 "step35 batched prime: one seq_end per sequence"
3037 );
3038 validate_step_prime_batch_modes(
3039 step_tp_prefill_enabled()?,
3040 step_ep_grouped_prefill_enabled()?,
3041 )?;
3042 if crate::pp::pp_host_bounce_active()
3043 && (!crate::pp::prime_pp_on() || crate::pp::pp_cuts(self.layers.len()).is_none())
3044 {
3045 return Err(
3046 "step35_prime_cache_batch: MEMRA_PP_HOST_BOUNCE=1 requires a valid prime \
3047 stage split; refusing an unsplit remote-weight walk"
3048 .into(),
3049 );
3050 }
3051 if !Self::step35_prime_batch_on() {
3052 return Err("step35 batched prime is disabled (MEMRA_STEP35_PRIME_BATCH=0)".into());
3053 }
3054 if prompts.len() > 1 && caches.iter().any(|c| c.pos != 0) {
3059 return Err(
3060 "step35 batched prime supports continuation only at B=1; a cross-request batch \
3061 at mixed positions requires per-request queued_after"
3062 .into(),
3063 );
3064 }
3065
3066 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
3067 for &t in &ts {
3068 assert!(
3069 t >= PRIME_MIN_T,
3070 "step35 batched prime needs T >= {PRIME_MIN_T}"
3071 );
3072 }
3073 for (s, c) in caches.iter().enumerate() {
3074 assert!(
3077 c.pos + ts[s] <= c.max_ctx,
3078 "step35 batched prime exceeds cache max_ctx"
3079 );
3080 assert!(
3081 seq_ends[s] >= c.pos + ts[s],
3082 "step35 batched prime: seq_end must cover this chunk"
3083 );
3084 }
3085 let legacy_tsend = std::env::var("MEMRA_STEP35_PRIME_BATCH_TSEND").as_deref() == Ok("1");
3092 let seq_ends_eff: Vec<usize> = if legacy_tsend {
3093 ts.clone()
3094 } else {
3095 seq_ends.to_vec()
3096 };
3097 let offs: Vec<usize> = ts
3098 .iter()
3099 .scan(0usize, |a, &t| {
3100 let o = *a;
3101 *a += t;
3102 Some(o)
3103 })
3104 .collect();
3105 let total: usize = ts.iter().sum();
3106 let payload = total * self.cfg.n_embd as usize;
3107 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
3108 let positions: Vec<Vec<i32>> = ts
3114 .iter()
3115 .zip(caches.iter())
3116 .map(|(&t, c)| {
3117 let base = c.pos as i32;
3118 (0..t as i32).map(|i| base + i).collect()
3119 })
3120 .collect();
3121 let upload_positions =
3122 |e: &Engine| -> Result<Vec<CudaSlice<i32>>, Box<dyn std::error::Error>> {
3123 positions
3124 .iter()
3125 .map(|p| e.htod_i32(p))
3126 .collect::<Result<_, _>>()
3127 };
3128
3129 static ONCE: std::sync::Once = std::sync::Once::new();
3130 ONCE.call_once(|| {
3131 eprintln!(
3132 "[step35-prime-batch] first concat prime: B={} tokens={total}",
3133 prompts.len()
3134 );
3135 });
3136
3137 let out = if !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
3138 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
3139 let rt = crate::pp::PpNRt::get(e)?;
3140 let n_st = fence.len() - 1;
3141 assert_eq!(
3142 rt.n_stages(),
3143 n_st,
3144 "step35 prime batch stage count mismatch"
3145 );
3146 let caller_stream = e.stream();
3147 rt.fence_stages_behind(&caller_stream)?;
3148
3149 let mut slot = {
3150 let _st0 = rt.enter(0);
3151 let e0 = rt.engine(0, e);
3152 let pos_ds = upload_positions(e0)?;
3153 let x = self.embed(e0, &cat_tokens)?;
3154 let x = self.step35_prime_batch_layers(
3155 e0,
3156 x,
3157 fence[0],
3158 fence[1],
3159 &ts,
3160 &offs,
3161 &seq_ends_eff,
3162 &pos_ds,
3163 caches,
3164 )?;
3165 rt.tx(0, &x, payload)?
3166 };
3167 for s in 1..n_st - 1 {
3168 let _st = rt.enter(s);
3169 let es = rt.engine(s, e);
3170 let pos_ds = upload_positions(es)?;
3171 let x = rt.rx(s - 1, slot, payload)?;
3172 let x = self.step35_prime_batch_layers(
3173 es,
3174 x,
3175 fence[s],
3176 fence[s + 1],
3177 &ts,
3178 &offs,
3179 &seq_ends_eff,
3180 &pos_ds,
3181 caches,
3182 )?;
3183 slot = rt.tx(s, &x, payload)?;
3184 }
3185
3186 let _stl = rt.enter(n_st - 1);
3187 let el = rt.engine(n_st - 1, e);
3188 let pos_ds = upload_positions(el)?;
3189 let x = rt.rx(n_st - 2, slot, payload)?;
3190 let x = self.step35_prime_batch_layers(
3191 el,
3192 x,
3193 fence[n_st - 1],
3194 fence[n_st],
3195 &ts,
3196 &offs,
3197 &seq_ends_eff,
3198 &pos_ds,
3199 caches,
3200 )?;
3201 let out = self.step35_prime_batch_epilogue(el, x, &ts, &offs, caches)?;
3202 rt.publish_to(n_st - 1, &caller_stream)?;
3203 crate::pp::STEP35_PRIME_BATCH_SPLITS
3204 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3205 out
3206 } else {
3207 let pos_ds = upload_positions(e)?;
3208 let x = self.embed(e, &cat_tokens)?;
3209 let x = self.step35_prime_batch_layers(
3210 e,
3211 x,
3212 0,
3213 self.layers.len(),
3214 &ts,
3215 &offs,
3216 &seq_ends_eff,
3217 &pos_ds,
3218 caches,
3219 )?;
3220 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
3221 }
3222 } else {
3223 let pos_ds = upload_positions(e)?;
3224 let x = self.embed(e, &cat_tokens)?;
3225 let x = self.step35_prime_batch_layers(
3226 e,
3227 x,
3228 0,
3229 self.layers.len(),
3230 &ts,
3231 &offs,
3232 &seq_ends_eff,
3233 &pos_ds,
3234 caches,
3235 )?;
3236 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
3237 };
3238 crate::pp::STEP35_PRIME_BATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3239 Ok(out)
3240 }
3241
3242 pub fn prime_cache_batch(
3259 &self,
3260 e: &Engine,
3261 prompts: &[&[u32]],
3262 caches: &mut [&mut Cache],
3263 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
3264 if crate::pp::pp_cuts(self.layers.len()).is_some()
3265 && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
3266 {
3267 return Err("pipeline rewrite is not qualified for batched prime".into());
3268 }
3269 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::CarriedPrime) {
3270 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::DecodeEager) {
3271 return Err("neither batched-prime nor eager rewrite is qualified".into());
3272 }
3273 if prompts.len() != caches.len() {
3274 return Err("prime fallback prompt/cache shape mismatch".into());
3275 }
3276 static ONCE: std::sync::Once = std::sync::Once::new();
3277 ONCE.call_once(|| {
3278 eprintln!(
3279 "[rewrite] carried-prime.v1 unqualified; using individual native eager primes"
3280 );
3281 });
3282 return prompts
3283 .iter()
3284 .copied()
3285 .zip(caches.iter_mut())
3286 .map(|(prompt, cache)| self.prime_cache(e, prompt, cache, 0))
3287 .collect();
3288 }
3289 let cfg = &self.cfg;
3290 let n_embd = cfg.n_embd as usize;
3291 let eps = cfg.rms_eps;
3292 let b = prompts.len();
3293 assert!(b >= 1 && b == caches.len());
3294 let pos0s: Vec<usize> = caches.iter().map(|c| c.pos).collect();
3295 let carried = pos0s.iter().any(|&p| p > 0);
3296 if self.uses_gemma_program() {
3302 return Err(
3303 "prime_cache_batch: gemma4 has no batched prime core (per-layer \
3304 swa/global geometry, softcapped head) — use gemma4_prime per sequence"
3305 .into(),
3306 );
3307 }
3308 if self.uses_sliding_gated_moe_program() {
3311 let seq_ends: Vec<usize> = caches
3316 .iter()
3317 .zip(prompts.iter())
3318 .map(|(c, p)| c.pos + p.len())
3319 .collect();
3320 return self.step35_prime_cache_batch(e, prompts, caches, &seq_ends);
3321 }
3322 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
3323 for &t in &ts {
3324 assert!(
3325 t >= PRIME_MIN_T,
3326 "prime_cache_batch needs T >= {PRIME_MIN_T}"
3327 );
3328 }
3329 for (s, c) in caches.iter().enumerate() {
3330 assert!(
3331 c.pos + ts[s] <= c.max_ctx,
3332 "prime_cache_batch: prompt exceeds cache max_ctx"
3333 );
3334 }
3335 let total: usize = ts.iter().sum();
3336 let offs: Vec<usize> = ts
3337 .iter()
3338 .scan(0usize, |a, &t| {
3339 let o = *a;
3340 *a += t;
3341 Some(o)
3342 })
3343 .collect();
3344 let pos_ds: Vec<CudaSlice<i32>> = ts
3346 .iter()
3347 .zip(&pos0s)
3348 .map(|(&t, &p0)| e.htod_i32(&(p0 as i32..(p0 + t) as i32).collect::<Vec<_>>()))
3349 .collect::<Result<_, _>>()?;
3350 let split = |e: &Engine,
3352 y: &CudaSlice<f32>,
3353 dim: usize|
3354 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3355 let mut out = Vec::with_capacity(b);
3356 for s in 0..b {
3357 let mut ys = e.uninit(ts[s] * dim)?;
3358 e.copy_view_into(
3359 &mut ys,
3360 0,
3361 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
3362 ts[s] * dim,
3363 )?;
3364 out.push(ys);
3365 }
3366 Ok(out)
3367 };
3368
3369 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
3370 let mut x = self.embed(e, &cat_tokens)?; for (il, layer) in self.layers.iter().enumerate() {
3372 let mut h = e.uninit(total * n_embd)?;
3373 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3374 e.rms_norm_f16out(
3375 &x,
3376 layer.attn_norm.float_data(),
3377 &mut h,
3378 &mut hx16,
3379 n_embd,
3380 total,
3381 eps,
3382 )?;
3383 let mut mixed = e.uninit(total * n_embd)?;
3385 match &layer.mixer {
3386 Mixer::Full(fa) => {
3387 let g3 = e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], &h, &hx16, total)?;
3388 let geometry = self.cfg.full_attention_geometry_at(il as u32);
3394 let (n_head, n_head_kv, head_dim) = (
3395 geometry.n_head as usize,
3396 geometry.n_head_kv as usize,
3397 geometry.head_dim_k as usize,
3398 );
3399 let fa_scale = geometry.attention_scale();
3400 let use_favl = !carried
3401 && (2..=8).contains(&b)
3402 && (head_dim == 256 || head_dim == 128)
3403 && geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ
3404 && std::env::var("MEMRA_NOFA").is_err()
3405 && std::env::var("MEMRA_FA_FLOOR").is_err()
3406 && std::env::var("MEMRA_FA_PP_W2").as_deref() != Ok("1")
3407 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0")
3408 && std::env::var("MEMRA_FA_VL").as_deref() != Ok("0");
3409 if use_favl {
3410 let (qf_w, kf_w, vf_w) = (
3411 fa.wq.out_features(),
3412 fa.wk.out_features(),
3413 fa.wv.out_features(),
3414 );
3415 memra_gguf::config::check_fused_q_gate_extent(qf_w, head_dim, n_head, 1)?;
3420 struct APre {
3421 q: CudaSlice<f32>,
3422 gate: Option<CudaSlice<f32>>,
3423 qn: CudaSlice<f32>,
3424 kn: CudaSlice<f32>,
3425 }
3426 let mut aps = Vec::with_capacity(b);
3427 for &t in ts.iter().take(b) {
3428 aps.push(APre {
3429 q: e.uninit(t * n_head * head_dim)?,
3430 gate: Some(e.uninit(t * n_head * head_dim)?),
3431 qn: e.uninit(t * n_head * head_dim)?,
3432 kn: e.uninit(t * n_head_kv * head_dim)?,
3433 });
3434 }
3435 let (kv_dim_k, kv_dim_v, ktb, vtb) = {
3436 let kvl = caches[0].kv[il].as_ref().unwrap();
3437 (kvl.kv_dim_k, kvl.kv_dim_v, kvl.k_tok_bytes, kvl.v_tok_bytes)
3438 };
3439 let pargs: Vec<crate::AttnPreVl> = (0..b)
3440 .map(|s| {
3441 let (o, t) = (offs[s], ts[s]);
3442 let kvl = caches[s].kv[il].as_ref().unwrap();
3443 assert!(
3444 kvl.len == 0 && kvl.len + t <= caches[s].max_ctx,
3445 "prime_cache_batch attn vl: fresh + capacity"
3446 );
3447 crate::AttnPreVl {
3448 qf: e.addr_f32v(&g3[0].slice(o * qf_w..(o + t) * qf_w)),
3449 kf: e.addr_f32v(&g3[1].slice(o * kf_w..(o + t) * kf_w)),
3450 vf: e.addr_f32v(&g3[2].slice(o * vf_w..(o + t) * vf_w)),
3451 q: e.addr_f32(&aps[s].q),
3452 gate: e.addr_f32(aps[s].gate.as_ref().unwrap()),
3453 qn: e.addr_f32(&aps[s].qn),
3454 kn: e.addr_f32(&aps[s].kn),
3455 kc: e.addr_u8(&kvl.k),
3456 vc: e.addr_u8(&kvl.v),
3457 t: t as i32,
3458 pad: 0,
3459 }
3460 })
3461 .collect();
3462 e.attn_pre_vl8(
3463 &pargs,
3464 fa.q_norm.float_data(),
3465 fa.k_norm.float_data(),
3466 head_dim,
3467 geometry.n_rot as usize,
3468 n_head,
3469 n_head_kv,
3470 self.cfg.rms_eps,
3471 geometry.rope_base,
3472 1.0,
3473 kv_dim_k,
3474 kv_dim_v,
3475 ktb,
3476 vtb,
3477 )?;
3478 for s in 0..b {
3479 let kvl = caches[s].kv[il].as_mut().unwrap();
3480 kvl.len += ts[s];
3481 let new_len = kvl.len as i32;
3482 e.set_i32_one(&mut kvl.len_d, new_len)?;
3483 }
3484 let mut attns = Vec::with_capacity(b);
3485 let mut mirrors = Vec::with_capacity(b);
3486 for &t in ts.iter().take(b) {
3487 attns.push(e.uninit(t * n_head * head_dim)?);
3488 let n = t * n_head_kv * head_dim;
3489 mirrors.push((e.alloc_u8_uninit(n * 2)?, e.alloc_u8_uninit(n * 2)?));
3490 }
3491 let fa3_on = match std::env::var("MEMRA_FA3").as_deref() {
3494 Ok("0") => false,
3495 Ok("1") => {
3499 crate::refuse_portable_force(
3500 "MEMRA_FA3=1",
3501 "the sm_90a fa3/bf16 kernels",
3502 );
3503 true
3504 }
3505 _ => cfg!(memra_hopper_mma),
3506 };
3507 if fa3_on {
3508 let mut q16s = Vec::with_capacity(b);
3509 let mut v16s = Vec::with_capacity(b);
3510 for s in 0..b {
3511 let t = ts[s];
3512 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
3513 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
3514 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3515 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
3516 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3517 e.f32_to_bf16_v(
3518 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
3519 &mut v16,
3520 t * n_head_kv * head_dim,
3521 )?;
3522 q16s.push(q16);
3523 v16s.push((k16, v16));
3524 }
3525 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
3526 let mut kp = qp;
3527 let mut vp = qp;
3528 let mut op = [core::ptr::null_mut::<f32>(); 8];
3529 let mut tsv = [0i32; 8];
3530 for s in 0..b {
3531 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
3532 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
3533 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
3534 op[s] = e.addr_f32(&attns[s]) as *mut f32;
3535 tsv[s] = ts[s] as i32;
3536 }
3537 let rc = unsafe {
3538 crate::fa3_vl_raw(
3539 qp.as_ptr(),
3540 kp.as_ptr(),
3541 vp.as_ptr(),
3542 op.as_ptr(),
3543 tsv.as_ptr(),
3544 b as i32,
3545 n_head as i32,
3546 n_head_kv as i32,
3547 head_dim as i32,
3548 fa_scale,
3549 e.stream().cu_stream() as *mut core::ffi::c_void,
3550 )
3551 };
3552 if rc != 0 {
3553 return Err(format!("memra_fa3_vl rc={rc}").into());
3554 }
3555 } else {
3556 let fargs: Vec<crate::FaSeqVl> = (0..b)
3557 .map(|s| crate::FaSeqVl {
3558 q: e.addr_f32(&aps[s].qn),
3559 k16: e.addr_u8(&mirrors[s].0),
3560 v16: e.addr_u8(&mirrors[s].1),
3561 o: e.addr_f32(&attns[s]),
3562 kf: e.addr_f32(&aps[s].kn),
3563 vf: e.addr_f32v(
3564 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
3565 ),
3566 t: ts[s] as i32,
3567 pad: 0,
3568 })
3569 .collect();
3570 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
3571 }
3572 for (s, attn) in attns.into_iter().enumerate() {
3573 let (attn_g, ag16) = self.full_attn_prime_post_fa(
3574 e,
3575 attn,
3576 &aps[s].gate,
3577 ts[s],
3578 n_head,
3579 head_dim,
3580 )?;
3581 let mut done = false;
3582 if let Some(xh) = &ag16 {
3583 done = e.try_f16_gemm_pre_into_off(
3584 &fa.wo,
3585 xh,
3586 ts[s],
3587 &mut mixed,
3588 offs[s] * n_embd,
3589 )?;
3590 }
3591 if !done {
3592 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3593 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3594 }
3595 }
3596 } else {
3597 let mut parts: Vec<Vec<CudaSlice<f32>>> =
3598 (0..b).map(|_| Vec::new()).collect();
3599 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
3600 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
3601 parts[s].push(ys);
3602 }
3603 }
3604 for (s, g3s) in parts.into_iter().enumerate() {
3605 let (attn_g, ag16) = self.full_attn_prime_core_inner(
3607 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
3608 )?;
3609 let mut done = false;
3610 if let Some(xh) = &ag16 {
3611 done = e.try_f16_gemm_pre_into_off(
3612 &fa.wo,
3613 xh,
3614 ts[s],
3615 &mut mixed,
3616 offs[s] * n_embd,
3617 )?;
3618 }
3619 if !done {
3620 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3621 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3622 }
3623 }
3624 }
3625 }
3626 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
3627 Mixer::Linear(la) => {
3628 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3633 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
3634 let outs =
3635 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
3636 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
3637 let (o, t) = (offs[s], ts[s]);
3638 let mut done = false;
3639 if let Some(xh) = &gn16 {
3640 done = e.try_f16_gemm_pre_into_off(
3641 &la.ssm_out,
3642 xh,
3643 t,
3644 &mut mixed,
3645 o * n_embd,
3646 )?;
3647 }
3648 if !done {
3649 let m = e.matmul(&la.ssm_out, &gn, t)?;
3650 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
3651 }
3652 }
3653 }
3654 }
3655 let mut x1 = e.uninit(total * n_embd)?;
3656 let mut z = e.uninit(total * n_embd)?;
3657 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3658 e.add_rms_norm_f16out(
3659 &x,
3660 &mixed,
3661 layer.post_attn_norm.float_data(),
3662 &mut x1,
3663 &mut z,
3664 &mut zx16,
3665 n_embd,
3666 total,
3667 eps,
3668 )?;
3669 let ffn_out = match &layer.ffn {
3670 crate::hybrid::Ffn::Dense {
3671 ffn_gate,
3672 ffn_up,
3673 ffn_down,
3674 } => {
3675 let n_ff = ffn_gate.out_features();
3676 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
3677 let up = g2.pop().unwrap();
3678 let gate = g2.pop().unwrap();
3679 let mut act = e.uninit(total * n_ff)?;
3680 let d_lim = self.cfg.clamp_shexp_at(il as u32);
3684 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
3685 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
3686 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
3687 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
3688 Some(y) => y,
3689 None => e.matmul(ffn_down, &act, total)?,
3690 }
3691 } else {
3692 Self::ffn_act_lim(
3693 e,
3694 &self.cfg,
3695 &gate,
3696 &up,
3697 1.0,
3698 1.0,
3699 d_lim,
3700 &mut act,
3701 total * n_ff,
3702 )?;
3703 e.matmul(ffn_down, &act, total)?
3704 }
3705 }
3706 crate::hybrid::Ffn::Moe(m) => {
3707 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
3708 }
3709 };
3710 let mut x2 = e.uninit(total * n_embd)?;
3711 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
3712 x = x2;
3713 }
3714 let mut hn = e.uninit(total * n_embd)?;
3716 e.rms_norm(
3717 &x,
3718 self.output_norm.float_data(),
3719 &mut hn,
3720 n_embd,
3721 total,
3722 eps,
3723 )?;
3724 let mut hcat = e.uninit(b * n_embd)?;
3730 for s in 0..b {
3731 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3732 e.copy_view_into(
3733 &mut hcat,
3734 s * n_embd,
3735 &hn.slice(last0..last0 + n_embd),
3736 n_embd,
3737 )?;
3738 }
3739 let logits_cat = if b >= 2 {
3740 e.try_f16_gemm(&self.output, &hcat, b)?
3741 } else {
3742 None
3743 };
3744 let logits_host: Option<Vec<f32>> = match &logits_cat {
3745 Some(lc) => Some(e.dtoh(lc)?),
3746 None => None,
3747 };
3748 let n_vocab = self.output.out_features();
3749 let mut hidden_all = if crate::spec::spec_hpost() {
3750 split(e, &hn, n_embd)?
3751 } else {
3752 split(e, &x, n_embd)?
3753 };
3754 let mut out = Vec::with_capacity(b);
3755 for s in 0..b {
3756 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3757 let mut h_seed = e.uninit(n_embd)?;
3758 if !crate::spec::spec_hpost() {
3759 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
3760 } else {
3761 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3762 }
3763 let logits = match &logits_host {
3764 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
3765 None => {
3766 let mut hlast = e.uninit(n_embd)?;
3767 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3768 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
3769 }
3770 };
3771 caches[s].pos += ts[s];
3772 out.push((logits, h_seed, hidden_all.remove(0)));
3773 }
3774 Ok(out)
3775 }
3776
3777 #[allow(clippy::too_many_arguments)]
3788 fn full_attn_prime(
3789 &self,
3790 e: &Engine,
3791 fa: &FullAttnLayer,
3792 h: &CudaSlice<f32>,
3793 hx: Option<&CudaSlice<u8>>,
3794 pos_d: &CudaSlice<i32>,
3795 t: usize,
3796 cache: &mut Cache,
3797 il: usize,
3798 seq_end: usize,
3799 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3800 if self.uses_sliding_gated_moe_program() {
3801 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
3802 }
3803 let g3 = match hx {
3808 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
3809 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
3810 };
3811 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
3812 }
3813
3814 fn full_attn_prime_core(
3818 &self,
3819 e: &Engine,
3820 fa: &FullAttnLayer,
3821 g3: Vec<CudaSlice<f32>>,
3822 pos_d: &CudaSlice<i32>,
3823 t: usize,
3824 cache: &mut Cache,
3825 il: usize,
3826 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3827 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
3828 if let Some(xh) = &ag16 {
3829 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
3830 return Ok(y);
3831 }
3832 }
3833 Ok(e.matmul(&fa.wo, &attn_g, t)?)
3834 }
3835
3836 fn full_attn_prime_core_inner(
3837 &self,
3838 e: &Engine,
3839 fa: &FullAttnLayer,
3840 g3: Vec<CudaSlice<f32>>,
3841 pos_d: &CudaSlice<i32>,
3842 t: usize,
3843 cache: &mut Cache,
3844 il: usize,
3845 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3846 let cfg = &self.cfg;
3847 let geometry = cfg.full_attention_geometry_at(il as u32);
3848 let n_head = geometry.n_head as usize;
3849 let n_head_kv = geometry.n_head_kv as usize;
3850 let head_dim = geometry.head_dim_k as usize;
3851 let scale = geometry.attention_scale();
3852 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
3853 let AttnPre { q, k, v, gate } = pre;
3854 let mut attn = e.uninit(t * n_head * head_dim)?;
3855 self.full_attn_prime_fa_dispatch(
3856 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
3857 )?;
3858 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
3859 }
3860
3861 #[allow(clippy::type_complexity)]
3865 fn full_attn_prime_pre_fa(
3866 &self,
3867 e: &Engine,
3868 fa: &FullAttnLayer,
3869 mut g3: Vec<CudaSlice<f32>>,
3870 pos_d: &CudaSlice<i32>,
3871 t: usize,
3872 cache: &mut Cache,
3873 il: usize,
3874 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3875 let cfg = &self.cfg;
3876 let geometry = cfg.full_attention_geometry_at(il as u32);
3877 let n_head = geometry.n_head as usize;
3878 let n_head_kv = geometry.n_head_kv as usize;
3879 let head_dim = geometry.head_dim_k as usize;
3880 let eps = cfg.rms_eps;
3881
3882 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3886 let v = g3.pop().unwrap();
3887 let mut k = g3.pop().unwrap();
3888 let qf = g3.pop().unwrap();
3889 let (mut q, gate) = if gated {
3890 let mut q = e.uninit(t * n_head * head_dim)?;
3891 let mut gate = e.uninit(t * n_head * head_dim)?;
3892 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3893 (q, Some(gate))
3894 } else {
3895 (qf, None)
3896 };
3897
3898 let mut qn = e.uninit(t * n_head * head_dim)?;
3899 e.rms_norm(
3900 &q,
3901 fa.q_norm.float_data(),
3902 &mut qn,
3903 head_dim,
3904 n_head * t,
3905 eps,
3906 )?;
3907 q = qn;
3908 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3909 e.rms_norm(
3910 &k,
3911 fa.k_norm.float_data(),
3912 &mut kn,
3913 head_dim,
3914 n_head_kv * t,
3915 eps,
3916 )?;
3917 k = kn;
3918 let rope_dims = geometry.n_rot as usize;
3919 e.rope_neox(
3920 &mut q,
3921 pos_d,
3922 head_dim,
3923 rope_dims,
3924 n_head,
3925 t,
3926 geometry.rope_base,
3927 1.0,
3928 )?;
3929 e.rope_neox(
3930 &mut k,
3931 pos_d,
3932 head_dim,
3933 rope_dims,
3934 n_head_kv,
3935 t,
3936 geometry.rope_base,
3937 1.0,
3938 )?;
3939
3940 {
3943 let kvl = cache.kv[il].as_mut().unwrap();
3944 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3945 e.append_kv_quantized_rows(
3946 &k,
3947 &v,
3948 &mut kvl.k,
3949 &mut kvl.v,
3950 kvl.len,
3951 t,
3952 kvl.kv_dim_k,
3953 kvl.kv_dim_v,
3954 kvl.k_tok_bytes,
3955 kvl.v_tok_bytes,
3956 crate::Engine::kv_fp8_on(),
3957 )?;
3958 kvl.len += t;
3959 let new_len = kvl.len as i32;
3960 e.set_i32_one(&mut kvl.len_d, new_len)?;
3961 }
3962
3963 let base_len = {
3964 let kvl = cache.kv[il].as_ref().unwrap();
3965 kvl.len - t };
3967 Ok((AttnPre { q, k, v, gate }, base_len))
3968 }
3969
3970 #[allow(clippy::too_many_arguments)]
3977 fn full_attn_prime_fa_dispatch(
3978 &self,
3979 e: &Engine,
3980 q: &CudaSlice<f32>,
3981 k: &CudaSlice<f32>,
3982 v: &CudaSlice<f32>,
3983 attn: &mut CudaSlice<f32>,
3984 base_len: usize,
3985 t: usize,
3986 cache: &mut Cache,
3987 il: usize,
3988 head_dim: usize,
3989 n_head: usize,
3990 n_head_kv: usize,
3991 scale: f32,
3992 ) -> Result<(), Box<dyn std::error::Error>> {
3993 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
4006 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4007 e.sdpa_naive(
4008 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4009 )?;
4010 } else {
4011 e.fa_prefill(
4012 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4013 )?;
4014 }
4015 return Ok(());
4016 }
4017 let kvl = cache.kv[il].as_ref().unwrap();
4018 let t_kv = base_len + t;
4019 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
4020 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
4021 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4025 e.sdpa_naive_quantized_view(
4026 q,
4027 &k_view,
4028 &v_view,
4029 attn,
4030 head_dim,
4031 n_head,
4032 n_head_kv,
4033 t,
4034 t_kv,
4035 scale,
4036 true,
4037 kvl.k_tok_bytes,
4038 kvl.v_tok_bytes,
4039 )?;
4040 return Ok(());
4041 }
4042 let deqw = std::env::var("MEMRA_PRIME_DEQW")
4050 .map(|v| v != "0")
4051 .unwrap_or(true);
4052 if deqw {
4053 e.fa_prefill_view_ws(
4054 q,
4055 &k_view,
4056 &v_view,
4057 attn,
4058 head_dim,
4059 n_head,
4060 n_head_kv,
4061 t,
4062 t_kv,
4063 scale,
4064 true,
4065 kvl.k_tok_bytes,
4066 kvl.v_tok_bytes,
4067 crate::Engine::kv_fp8_on(),
4068 )?;
4069 } else {
4070 e.fa_prefill_view(
4071 q,
4072 &k_view,
4073 &v_view,
4074 attn,
4075 head_dim,
4076 n_head,
4077 n_head_kv,
4078 t,
4079 t_kv,
4080 scale,
4081 true,
4082 kvl.k_tok_bytes,
4083 kvl.v_tok_bytes,
4084 crate::Engine::kv_fp8_on(),
4085 )?;
4086 }
4087 Ok(())
4088 }
4089
4090 fn full_attn_prime_post_fa(
4093 &self,
4094 e: &Engine,
4095 attn: CudaSlice<f32>,
4096 gate: &Option<CudaSlice<f32>>,
4097 t: usize,
4098 n_head: usize,
4099 head_dim: usize,
4100 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4101 let (attn_g, ag16) = match gate {
4102 Some(gate) => {
4103 let n = t * n_head * head_dim;
4104 let mut ag = e.uninit(n)?;
4105 if Self::f16out_on(e, t) {
4106 let mut a16 = e.alloc_u8_uninit(n * 2)?;
4107 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
4108 (ag, Some(a16))
4109 } else {
4110 let mut gsig = e.uninit(n)?;
4111 e.sigmoid(gate, &mut gsig, n)?;
4112 e.mul(&attn, &gsig, &mut ag, n)?;
4113 (ag, None)
4114 }
4115 }
4116 None => (attn, None),
4117 };
4118 Ok((attn_g, ag16))
4119 }
4120
4121 fn linear_attn_prime(
4128 &self,
4129 e: &Engine,
4130 la: &LinearAttnLayer,
4131 h: &CudaSlice<f32>,
4132 hx: Option<&CudaSlice<u8>>,
4133 t: usize,
4134 cache: &mut Cache,
4135 il: usize,
4136 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4137 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
4139 let g4 = match hx {
4140 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
4141 None => e.matmul_group(&ws, h, t)?,
4142 };
4143 self.linear_attn_prime_core(e, la, g4, t, cache, il)
4144 }
4145
4146 fn linear_attn_prime_core(
4148 &self,
4149 e: &Engine,
4150 la: &LinearAttnLayer,
4151 mut g4: Vec<CudaSlice<f32>>,
4152 t: usize,
4153 cache: &mut Cache,
4154 il: usize,
4155 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4156 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
4157 }
4158
4159 #[allow(clippy::too_many_arguments)]
4163 fn linear_attn_prime_core_pad_inner(
4164 &self,
4165 e: &Engine,
4166 la: &LinearAttnLayer,
4167 mut g4: Vec<CudaSlice<f32>>,
4168 t: usize,
4169 cache: &mut Cache,
4170 il: usize,
4171 pad_len: Option<&CudaSlice<i32>>,
4172 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4173 let geometry = la.geometry;
4175 let d_state = geometry.key_head_dim as usize;
4176 let num_k = geometry.key_heads as usize;
4177 let num_v = geometry.value_heads as usize;
4178 let key_dim = d_state * num_k;
4179 let value_dim = geometry.value_head_dim as usize * num_v;
4180 let conv_dim = key_dim * 2 + value_dim;
4181 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(
4186 e,
4187 la,
4188 &qkv_mixed.slice(0..t * conv_dim),
4189 &z.slice(0..t * value_dim),
4190 &beta_raw.slice(0..t * num_v),
4191 &alpha.slice(0..t * num_v),
4192 t,
4193 cache,
4194 il,
4195 pad_len,
4196 )
4197 }
4198
4199 #[allow(clippy::too_many_arguments)]
4202 fn linear_attn_gdn_prep(
4203 &self,
4204 e: &Engine,
4205 la: &LinearAttnLayer,
4206 qkv_mixed: &cudarc::driver::CudaView<f32>,
4207 beta_raw: &cudarc::driver::CudaView<f32>,
4208 alpha: &cudarc::driver::CudaView<f32>,
4209 t: usize,
4210 cache: &mut Cache,
4211 il: usize,
4212 pad_len: Option<&CudaSlice<i32>>,
4213 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
4214 let cfg = &self.cfg;
4215 let geometry = la.geometry;
4216 let d_state = geometry.key_head_dim as usize;
4217 let num_k = geometry.key_heads as usize;
4218 let num_v = geometry.value_heads as usize;
4219 let d_conv = geometry.conv_kernel as usize;
4220 let key_dim = d_state * num_k; let value_dim = geometry.value_head_dim as usize * num_v;
4222 let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
4224 debug_assert!(
4225 t >= d_conv - 1,
4226 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
4227 );
4228
4229 let rl = cache.recur[il].as_mut().unwrap();
4234 let hk = Self::gdn_hk(e, t, num_v, num_k);
4235 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
4236 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
4238 let mut k_g = e.uninit(d_state * hk * t)?;
4239 let mut v_g = e.uninit(d_state * num_v * t)?;
4240 if conv_fuse {
4241 e.ssm_conv1d_gdn_state_pad(
4242 qkv_mixed,
4243 &mut rl.conv_state,
4244 la.ssm_conv1d.float_data(),
4245 &mut q_g,
4246 &mut k_g,
4247 &mut v_g,
4248 conv_dim,
4249 t,
4250 d_conv,
4251 d_state,
4252 num_v,
4253 num_k,
4254 key_dim,
4255 hk,
4256 pad_len,
4257 )?;
4258 } else {
4259 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
4261 qkv_mixed,
4262 &mut rl.conv_state,
4263 la.ssm_conv1d.float_data(),
4264 &mut conv_out,
4265 conv_dim,
4266 t,
4267 d_conv,
4268 pad_len,
4269 )?;
4270 e.qkv_to_gdn_repack(
4271 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
4272 )?;
4273 }
4274 let mut q_l2 = e.uninit(d_state * hk * t)?;
4275 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
4279 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4280 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
4281 Some(qb)
4282 } else {
4283 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
4284 None
4285 };
4286 let mut k_l2 = e.uninit(d_state * hk * t)?;
4287 let kb16 = if Engine::l2_v2_on(d_state) {
4289 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4290 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
4291 Some(kb)
4292 } else {
4293 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
4294 None
4295 };
4296 let mut beta = e.uninit(t * num_v)?;
4297 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
4298 let mut g_log = e.uninit(t * num_v)?;
4299 e.gdn_glog_v(
4300 alpha,
4301 la.ssm_dt.float_data(),
4302 la.ssm_a.float_data(),
4303 &mut g_log,
4304 num_v,
4305 t,
4306 )?;
4307 if let Some(len_d) = pad_len {
4308 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
4309 }
4310 Ok(GdnPrep {
4311 hk,
4312 q_l2,
4313 k_l2,
4314 v_g,
4315 beta,
4316 g_log,
4317 kb16,
4318 qb16,
4319 })
4320 }
4321
4322 #[allow(clippy::too_many_arguments)]
4327 fn linear_attn_prime_core_batch(
4328 &self,
4329 e: &Engine,
4330 la: &LinearAttnLayer,
4331 g4: &[CudaSlice<f32>],
4332 offs: &[usize],
4333 ts: &[usize],
4334 caches: &mut [&mut Cache],
4335 il: usize,
4336 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
4337 let geometry = la.geometry;
4338 let d_state = geometry.key_head_dim as usize;
4339 let num_k = geometry.key_heads as usize;
4340 let num_v = geometry.value_heads as usize;
4341 let d_conv = geometry.conv_kernel as usize;
4342 let key_dim = d_state * num_k;
4343 let value_dim = geometry.value_head_dim as usize * num_v;
4344 let conv_dim = key_dim * 2 + value_dim;
4345 let eps = self.cfg.rms_eps;
4346 let scale = 1.0 / (d_state as f32).sqrt();
4347 let b = ts.len();
4348 let c = Engine::gdn_chunk_size();
4349 let carried = caches.iter().any(|c| c.pos > 0);
4352 let use_vl = !carried
4353 && (2..=8).contains(&b)
4354 && Engine::gdn_chunked_enabled()
4355 && ts.iter().all(|&t| t >= 16)
4356 && e.gdn_mma_enabled(c)
4357 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
4358 if !use_vl {
4359 return (0..b)
4360 .map(|s| {
4361 let (o, t) = (offs[s], ts[s]);
4362 self.linear_attn_prime_core_pad_view(
4363 e,
4364 la,
4365 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
4366 &g4[1].slice(o * value_dim..(o + t) * value_dim),
4367 &g4[2].slice(o * num_v..(o + t) * num_v),
4368 &g4[3].slice(o * num_v..(o + t) * num_v),
4369 t,
4370 caches[s],
4371 il,
4372 None,
4373 )
4374 })
4375 .collect();
4376 }
4377 struct SeqBufs {
4381 conv_out: CudaSlice<f32>,
4382 q_g: CudaSlice<f32>,
4383 k_g: CudaSlice<f32>,
4384 v_g: CudaSlice<f32>,
4385 q_l2: CudaSlice<f32>,
4386 k_l2: CudaSlice<f32>,
4387 beta: CudaSlice<f32>,
4388 g_log: CudaSlice<f32>,
4389 gn: CudaSlice<f32>,
4390 gn16: CudaSlice<u8>,
4391 }
4392 let f16o = Self::f16out_on(e, 16);
4393 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
4395 let mut pres = Vec::with_capacity(b);
4396 for &t in ts.iter().take(b) {
4397 sb.push(SeqBufs {
4398 conv_out: e.uninit(conv_dim * t)?,
4399 q_g: e.uninit(d_state * hk * t)?,
4400 k_g: e.uninit(d_state * hk * t)?,
4401 v_g: e.uninit(d_state * num_v * t)?,
4402 q_l2: e.uninit(d_state * hk * t)?,
4403 k_l2: e.uninit(d_state * hk * t)?,
4404 beta: e.uninit(t * num_v)?,
4405 g_log: e.uninit(t * num_v)?,
4406 gn: e.uninit(d_state * num_v * t)?,
4407 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
4408 });
4409 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
4410 }
4411 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
4412 .map(|s| {
4413 let (o, t) = (offs[s], ts[s]);
4414 let rl = caches[s].recur[il].as_ref().unwrap();
4415 crate::GdnPrepVl {
4416 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
4417 conv_state: e.addr_f32(&rl.conv_state),
4418 conv_out: e.addr_f32(&sb[s].conv_out),
4419 q_g: e.addr_f32(&sb[s].q_g),
4420 k_g: e.addr_f32(&sb[s].k_g),
4421 v_g: e.addr_f32(&sb[s].v_g),
4422 q_l2: e.addr_f32(&sb[s].q_l2),
4423 k_l2: e.addr_f32(&sb[s].k_l2),
4424 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
4425 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
4426 beta: e.addr_f32(&sb[s].beta),
4427 g_log: e.addr_f32(&sb[s].g_log),
4428 o: e.addr_f32(&pres[s].o),
4429 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
4430 gn: e.addr_f32(&sb[s].gn),
4431 gn16: e.addr_u8(&sb[s].gn16),
4432 kb16: if Engine::l2_v2_on(d_state) {
4433 e.addr_u8(&pres[s].kb16)
4434 } else {
4435 0
4436 },
4437 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
4438 e.addr_u8(&pres[s].qb16)
4439 } else {
4440 0
4441 },
4442 t: t as i32,
4443 pad: 0,
4444 }
4445 })
4446 .collect();
4447 let args: Vec<crate::GdnSeqVl> = (0..b)
4448 .map(|s| {
4449 let rl = caches[s].recur[il].as_ref().unwrap();
4450 crate::GdnSeqVl {
4451 kb16: e.addr_u8(&pres[s].kb16),
4452 gcum: e.addr_f32(&pres[s].gcum),
4453 beta: e.addr_f32(&sb[s].beta),
4454 u: e.addr_f32(&pres[s].u),
4455 wb16: e.addr_u8(&pres[s].wb16),
4456 y: e.addr_u8(&pres[s].y16),
4457 ssnap: e.addr_u8(&pres[s].ssnap16),
4458 state_in: e.addr_f32(&rl.ssm_state),
4459 state_out: e.addr_f32(&rl.ssm_state_alt),
4460 q: e.addr_f32(&sb[s].q_l2),
4461 p: e.addr_f32(&pres[s].p),
4462 o: e.addr_f32(&pres[s].o),
4463 k: e.addr_f32(&sb[s].k_l2),
4464 v: e.addr_f32(&sb[s].v_g),
4465 g: e.addr_f32(&sb[s].g_log),
4466 a: e.addr_f32(&pres[s].a),
4467 w: e.addr_f32(&pres[s].w),
4468 t: ts[s] as i32,
4469 nc: pres[s].nc as i32,
4470 }
4471 })
4472 .collect();
4473 e.gdn_prep_vl8(
4474 &prep_args,
4475 la.ssm_conv1d.float_data(),
4476 la.ssm_dt.float_data(),
4477 la.ssm_a.float_data(),
4478 conv_dim,
4479 d_conv,
4480 d_state,
4481 num_v,
4482 num_k,
4483 key_dim,
4484 hk,
4485 eps,
4486 )?;
4487 if !Engine::l2_v2_on(d_state) {
4490 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
4491 }
4492 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
4494 if !Engine::l2_v2_on(d_state) {
4496 for s in 0..b {
4497 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
4498 }
4499 }
4500 let mut wa = [crate::GdnWVl::default(); 8];
4501 for s in 0..b {
4502 wa[s] = crate::GdnWVl {
4503 qb16: e.addr_u8(&pres[s].qb16),
4504 pb16: e.addr_u8(&pres[s].pb16),
4505 };
4506 }
4507 Some(crate::GdnWVl8(wa))
4508 } else {
4509 None
4510 };
4511 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
4512 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
4513 if f16o {
4514 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
4515 }
4516 let mut out = Vec::with_capacity(b);
4518 for (s, bufs) in sb.into_iter().enumerate() {
4519 let rl = caches[s].recur[il].as_mut().unwrap();
4520 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4521 let (o, t) = (offs[s], ts[s]);
4522 let SeqBufs { mut gn, gn16, .. } = bufs;
4523 if f16o {
4524 out.push((gn, Some(gn16)));
4525 } else {
4526 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
4527 e.gated_rmsnorm_zv(
4528 &pres[s].o,
4529 la.ssm_norm.float_data(),
4530 &z_v,
4531 &mut gn,
4532 d_state,
4533 num_v * t,
4534 eps,
4535 )?;
4536 out.push((gn, None));
4537 }
4538 }
4539 Ok(out)
4540 }
4541
4542 #[allow(clippy::too_many_arguments)]
4546 fn linear_attn_prime_core_pad_view(
4547 &self,
4548 e: &Engine,
4549 la: &LinearAttnLayer,
4550 qkv_mixed: &cudarc::driver::CudaView<f32>,
4551 z: &cudarc::driver::CudaView<f32>,
4552 beta_raw: &cudarc::driver::CudaView<f32>,
4553 alpha: &cudarc::driver::CudaView<f32>,
4554 t: usize,
4555 cache: &mut Cache,
4556 il: usize,
4557 pad_len: Option<&CudaSlice<i32>>,
4558 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4559 let cfg = &self.cfg;
4560 let geometry = la.geometry;
4561 let d_state = geometry.key_head_dim as usize;
4562 let num_v = geometry.value_heads as usize;
4563 let eps = cfg.rms_eps;
4564 let scale = 1.0 / (d_state as f32).sqrt();
4565
4566 let prep =
4567 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
4568
4569 let mut o = e.uninit(d_state * num_v * t)?;
4575 let rl = cache.recur[il].as_mut().unwrap();
4576 {
4577 let crate::cache::RecurLayer {
4578 ssm_state,
4579 ssm_state_alt,
4580 ..
4581 } = rl;
4582 e.gdn_scan_prefill(
4583 &prep.q_l2,
4584 &prep.k_l2,
4585 &prep.v_g,
4586 &prep.g_log,
4587 &prep.beta,
4588 prep.kb16.as_ref(),
4589 prep.qb16.as_ref(),
4590 ssm_state,
4591 ssm_state_alt,
4592 &mut o,
4593 num_v,
4594 t,
4595 scale,
4596 prep.hk,
4597 )?;
4598 }
4599 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4600
4601 let mut gn = e.uninit(d_state * num_v * t)?;
4604 let gn16 = if Self::f16out_on(e, t) {
4605 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
4606 e.gated_rmsnorm_f16out_zv(
4607 &o,
4608 la.ssm_norm.float_data(),
4609 z,
4610 &mut gn,
4611 &mut g16,
4612 d_state,
4613 num_v * t,
4614 eps,
4615 )?;
4616 Some(g16)
4617 } else {
4618 e.gated_rmsnorm_zv(
4619 &o,
4620 la.ssm_norm.float_data(),
4621 z,
4622 &mut gn,
4623 d_state,
4624 num_v * t,
4625 eps,
4626 )?;
4627 None
4628 };
4629 Ok((gn, gn16))
4630 }
4631
4632 #[allow(clippy::too_many_arguments)]
4634 fn linear_attn_prime_core_pad(
4635 &self,
4636 e: &Engine,
4637 la: &LinearAttnLayer,
4638 g4: Vec<CudaSlice<f32>>,
4639 t: usize,
4640 cache: &mut Cache,
4641 il: usize,
4642 pad_len: Option<&CudaSlice<i32>>,
4643 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4644 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
4645 if let Some(xh) = &gn16 {
4646 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
4647 return Ok(y);
4648 }
4649 }
4650 Ok(e.matmul(&la.ssm_out, &gn, t)?)
4651 }
4652
4653 pub fn full_attn(
4658 &self,
4659 e: &Engine,
4660 fa: &FullAttnLayer,
4661 h: &CudaSlice<f32>,
4662 pos_d: &CudaSlice<i32>,
4663 t: usize,
4664 il: usize,
4665 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4666 if self.uses_sliding_gated_moe_program() {
4667 return self.step35_attn(e, fa, h, pos_d, t, il);
4668 }
4669 let cfg = &self.cfg;
4670 let _n_embd = cfg.n_embd as usize;
4671 let geometry = cfg.full_attention_geometry_at(il as u32);
4672 let n_head = geometry.n_head as usize;
4673 let n_head_kv = geometry.n_head_kv as usize;
4674 let head_dim = geometry.head_dim_k as usize;
4675 let eps = cfg.rms_eps;
4676 let scale = geometry.attention_scale();
4677
4678 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
4681 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
4683 let v = g3.pop().unwrap();
4684 let mut k = g3.pop().unwrap();
4685 let qf = g3.pop().unwrap();
4686 let (mut q, gate) = if gated {
4687 let mut q = e.uninit(t * n_head * head_dim)?;
4688 let mut gate = e.uninit(t * n_head * head_dim)?;
4689 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
4690 (q, Some(gate))
4691 } else {
4692 (qf, None)
4693 };
4694
4695 let mut qn = e.uninit(t * n_head * head_dim)?;
4697 e.rms_norm(
4698 &q,
4699 fa.q_norm.float_data(),
4700 &mut qn,
4701 head_dim,
4702 n_head * t,
4703 eps,
4704 )?;
4705 q = qn;
4706 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
4707 e.rms_norm(
4708 &k,
4709 fa.k_norm.float_data(),
4710 &mut kn,
4711 head_dim,
4712 n_head_kv * t,
4713 eps,
4714 )?;
4715 k = kn;
4716 let rope_dims = geometry.n_rot as usize;
4717 e.rope_neox(
4718 &mut q,
4719 pos_d,
4720 head_dim,
4721 rope_dims,
4722 n_head,
4723 t,
4724 geometry.rope_base,
4725 1.0,
4726 )?;
4727 e.rope_neox(
4728 &mut k,
4729 pos_d,
4730 head_dim,
4731 rope_dims,
4732 n_head_kv,
4733 t,
4734 geometry.rope_base,
4735 1.0,
4736 )?;
4737
4738 let mut attn = e.uninit(t * n_head * head_dim)?;
4740 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4743 e.sdpa_naive(
4745 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4746 )?;
4747 } else {
4748 e.fa_prefill(
4749 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4750 )?;
4751 }
4752
4753 let attn_g = match &gate {
4755 Some(gate) => {
4756 let mut gsig = e.uninit(t * n_head * head_dim)?;
4757 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
4758 let mut ag = e.uninit(t * n_head * head_dim)?;
4759 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
4760 ag
4761 }
4762 None => attn,
4763 };
4764
4765 let o = e.matmul(&fa.wo, &attn_g, t)?;
4767 Ok(o)
4768 }
4769
4770 pub fn linear_attn(
4772 &self,
4773 e: &Engine,
4774 la: &LinearAttnLayer,
4775 h: &CudaSlice<f32>,
4776 t: usize,
4777 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4778 let cfg = &self.cfg;
4779 let _n_embd = cfg.n_embd as usize;
4780 let geometry = la.geometry;
4781 let d_state = geometry.key_head_dim as usize;
4782 let num_k = geometry.key_heads as usize;
4783 let num_v = geometry.value_heads as usize;
4784 let d_conv = geometry.conv_kernel as usize;
4785 let head_k = d_state;
4786 let head_v = geometry.value_head_dim as usize;
4787 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;
4791 let scale = 1.0 / (d_state as f32).sqrt();
4792
4793 let mut g4 = e.matmul_group(
4796 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
4797 h,
4798 t,
4799 )?;
4800 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);
4812 let mut q_g = e.uninit(d_state * num_v * t)?;
4813 let mut k_g = e.uninit(d_state * num_v * t)?;
4814 let mut v_g = e.uninit(d_state * num_v * t)?;
4815 e.ssm_conv1d_gdn(
4816 &qkv_mixed,
4817 la.ssm_conv1d.float_data(),
4818 &mut q_g,
4819 &mut k_g,
4820 &mut v_g,
4821 conv_dim,
4822 t,
4823 d_conv,
4824 d_state,
4825 num_v,
4826 num_k,
4827 key_dim,
4828 )?;
4829 let mut q_l2 = e.uninit(d_state * num_v * t)?;
4831 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
4832 let mut k_l2 = e.uninit(d_state * num_v * t)?;
4833 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
4834 let v_gd = v_g;
4835
4836 let mut beta = e.uninit(t * num_v)?;
4839 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
4840 let mut g_log = e.uninit(t * num_v)?;
4842 e.gdn_glog(
4843 &alpha,
4844 la.ssm_dt.float_data(),
4845 la.ssm_a.float_data(),
4846 &mut g_log,
4847 num_v,
4848 t,
4849 )?;
4850
4851 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
4854 let mut o = e.uninit(d_state * num_v * t)?;
4855 e.gdn_scan_prefill(
4856 &q_l2,
4857 &k_l2,
4858 &v_gd,
4859 &g_log,
4860 &beta,
4861 None,
4862 None,
4863 &state_in,
4864 &mut state_out,
4865 &mut o,
4866 num_v,
4867 t,
4868 scale,
4869 num_v,
4870 )?;
4871
4872 let mut gn = e.uninit(d_state * num_v * t)?;
4877 e.gated_rmsnorm(
4878 &o,
4879 la.ssm_norm.float_data(),
4880 &z,
4881 &mut gn,
4882 d_state,
4883 num_v * t,
4884 eps,
4885 )?;
4886
4887 let out = e.matmul(&la.ssm_out, &gn, t)?;
4891 Ok(out)
4892 }
4893}
4894
4895impl HybridModel {
4896 pub fn moe_ffn_il(
4907 &self,
4908 e: &Engine,
4909 m: &MoeWeights,
4910 z: &CudaSlice<f32>,
4911 t: usize,
4912 il: u16,
4913 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4914 Self::moe_ffn_inner(
4915 e,
4916 m,
4917 z,
4918 None,
4919 t,
4920 &self.cfg,
4921 il,
4922 self.max_moe_block(),
4923 false,
4924 None,
4925 self.uses_sliding_gated_moe_program(),
4926 )
4927 }
4928
4929 pub fn moe_ffn_il_prefill(
4932 &self,
4933 e: &Engine,
4934 m: &MoeWeights,
4935 z: &CudaSlice<f32>,
4936 t: usize,
4937 il: u16,
4938 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4939 Self::moe_ffn_inner(
4940 e,
4941 m,
4942 z,
4943 None,
4944 t,
4945 &self.cfg,
4946 il,
4947 self.max_moe_block(),
4948 true,
4949 Some(&self.step_grouped_prefill),
4950 self.uses_sliding_gated_moe_program(),
4951 )
4952 }
4953
4954 pub fn moe_ffn_il_zq8(
4958 &self,
4959 e: &Engine,
4960 m: &MoeWeights,
4961 z: &CudaSlice<f32>,
4962 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4963 t: usize,
4964 il: u16,
4965 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4966 Self::moe_ffn_inner(
4967 e,
4968 m,
4969 z,
4970 zq8,
4971 t,
4972 &self.cfg,
4973 il,
4974 self.max_moe_block(),
4975 false,
4976 None,
4977 self.uses_sliding_gated_moe_program(),
4978 )
4979 }
4980
4981 pub(crate) fn moe_ffn(
4989 e: &Engine,
4990 m: &MoeWeights,
4991 z: &CudaSlice<f32>,
4992 t: usize,
4993 cfg: &ModelConfig,
4994 il: u16,
4995 max_block: usize,
4996 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4997 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false, None, false)
4998 }
4999
5000 #[allow(clippy::too_many_arguments)]
5001 pub(crate) fn moe_ffn_inner(
5002 e: &Engine,
5003 m: &MoeWeights,
5004 z: &CudaSlice<f32>,
5005 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5006 t: usize,
5007 cfg: &ModelConfig,
5008 il: u16,
5009 max_block: usize,
5010 prefill: bool,
5011 grouped_prefill: Option<&std::sync::Mutex<crate::hybrid::StepEpGroupedPrefill>>,
5012 sliding_gated_moe: bool,
5013 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5014 let worker_io = crate::spill_pread::worker_enabled();
5015 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
5016 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
5017 e.with_moe_cache(max_block, |cache, _| {
5018 cache.begin_forward_epoch(il, t);
5019 if worker_io {
5020 cache.begin_worker_scope();
5021 }
5022 Ok(())
5023 })?;
5024 }
5025 if m.step_ep.is_some() || m.step_tp.is_some() {
5026 let moe = cfg
5027 .moe
5028 .as_ref()
5029 .ok_or("Step distributed execution requires MoE model metadata")?;
5030 let n_embd = cfg.n_embd as usize;
5031 let n_expert = moe.expert_count as usize;
5032 let n_used = moe.expert_used_count as usize;
5033 let sigmoid = cfg
5034 .sigmoid_router()
5035 .ok_or("Step distributed execution requires the Step sigmoid router")?;
5036 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5037 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5038 let grouped_prefill_requested = prefill && step_ep_grouped_prefill_enabled()?;
5039 if grouped_prefill_requested && !step_tp_prefill_enabled()? {
5040 return Err(
5041 "MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into(),
5042 );
5043 }
5044 if grouped_prefill_requested && !step_grouped_prefill_shape(true, prefill, t) {
5045 return Err(format!(
5046 "Step grouped prefill tokens {t} are outside the qualified {}..={} range",
5047 PRIME_MIN_T,
5048 crate::cache::PRIME_CHUNK_MAX_TOKENS,
5049 )
5050 .into());
5051 }
5052 let grouped_decode_shape = step_grouped_decode_shape(prefill, t);
5053 let grouped_prefill_shape =
5054 step_grouped_prefill_shape(grouped_prefill_requested, prefill, t);
5055 if let Some(ep) = m.step_ep.as_ref().filter(|ep| {
5056 ep.grouped_decode.is_some() && (grouped_decode_shape || grouped_prefill_shape)
5057 }) {
5058 let (selected, route_weights) =
5059 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sigmoid)?;
5060 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5061 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5062 Self::trace_moe_input(e, il, t, n_embd, z)?;
5063 let selected = selected
5064 .iter()
5065 .map(|&expert| expert as usize)
5066 .collect::<Vec<_>>();
5067
5068 e.stream().synchronize()?;
5071 let execute = |state: &mut crate::hybrid::StepEpGroupedDecode| {
5072 state.projection.set_activation_limit(ep.activation_limit)?;
5073 ep.runtime
5074 .refresh_step_grouped_expert_parallel_gate_from_root_device(
5075 ep.experts.e4m3()?,
5076 &mut state.projection,
5077 z,
5078 t,
5079 &selected,
5080 )?;
5081 ep.runtime.refresh_step_grouped_expert_parallel_combine(
5082 &state.projection,
5083 &mut state.combine,
5084 &route_weights,
5085 )?;
5086 ep.runtime.execute_step_grouped_expert_parallel_gate(
5087 ep.experts.e4m3()?,
5088 &mut state.projection,
5089 )?;
5090 ep.runtime.execute_step_grouped_expert_parallel_combine(
5091 &state.projection,
5092 &mut state.combine,
5093 )?;
5094 let mut output = ep.runtime.copy_step_grouped_expert_parallel_combine_root(
5095 &state.projection,
5096 &state.combine,
5097 e,
5098 )?;
5099 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5100 if prefill {
5101 e.stream().synchronize()?;
5104 }
5105 eprintln!(
5106 "[step-tp-ep-grouped] execute layer={il} tokens={t} devices={:?} \
5107 attention_layout=tensor-parallel expert_layout=expert-parallel \
5108 expert_transport={} native_p2p=true route_control=host-narrow \
5109 input=root-device projection_workspaces=persistent \
5110 combine=root-device output=owning-stage-device \
5111 prefill={prefill} batched_decode=false capacity={} \
5112 performance_claim=false",
5113 ep.devices,
5114 ep.runtime.transport_label(),
5115 state.projection.max_tokens(),
5116 );
5117 Ok::<_, Box<dyn std::error::Error>>(output)
5118 };
5119
5120 if grouped_prefill_shape {
5121 let grouped_prefill = grouped_prefill
5122 .ok_or("Step grouped prefill has no model-scoped executor")?;
5123 let mut shared = grouped_prefill
5124 .lock()
5125 .map_err(|_| "Step grouped prefill state lock is poisoned")?;
5126 let needs_prepare = shared.state.as_ref().is_none_or(|state| {
5127 state.devices != ep.devices
5128 || state.grouped.projection.max_tokens() < t
5129 || state.grouped.projection.input_width() != n_embd
5130 || state.grouped.projection.expert_width()
5131 != moe.expert_ff_length as usize
5132 });
5133 if needs_prepare {
5134 let seed_input = vec![0.0f32; n_embd];
5135 let seed_selected = &selected[..n_used];
5136 let seed_weights = &route_weights[..n_used];
5137 let projection = ep
5138 .runtime
5139 .prepare_step_grouped_expert_parallel_gate_with_capacity(
5140 ep.experts.e4m3()?,
5141 &seed_input,
5142 1,
5143 seed_selected,
5144 ep.activation_limit,
5145 t,
5146 )?;
5147 let combine = ep.runtime.prepare_step_grouped_expert_parallel_combine(
5148 &projection,
5149 seed_weights,
5150 )?;
5151 shared.state = Some(crate::hybrid::StepEpGroupedPrefillState {
5152 devices: ep.devices.clone(),
5153 grouped: crate::hybrid::StepEpGroupedDecode {
5154 projection,
5155 combine,
5156 },
5157 });
5158 eprintln!(
5159 "[step-tp-ep-grouped-prefill] prepare capacity={t} devices={:?} \
5160 shared_across_layers=true performance_claim=false",
5161 ep.devices,
5162 );
5163 }
5164 return execute(
5165 &mut shared
5166 .state
5167 .as_mut()
5168 .expect("Step grouped prefill state prepared above")
5169 .grouped,
5170 );
5171 }
5172
5173 let mut grouped = ep
5174 .grouped_decode
5175 .as_ref()
5176 .expect("grouped decode presence checked above")
5177 .lock()
5178 .map_err(|_| "Step grouped decode state lock is poisoned")?;
5179 return execute(&mut grouped);
5180 }
5181 if grouped_prefill_shape {
5182 return Err(
5183 "Step grouped prefill requires native-P2P expert-owner device arithmetic"
5184 .into(),
5185 );
5186 }
5187 if t >= 16 && crate::step_gemm_prime_on() {
5202 if let Some(tp) = &m.step_tp {
5203 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5204 let mprof =
5212 std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
5213 let mut mt = std::time::Instant::now();
5214 let (selected, route_weights) = Self::moe_route_sigmoid_cfg(
5215 e, &logits, t, n_expert, n_used, m, sigmoid,
5216 )?;
5217 let sel_i32: Vec<i32> = selected.iter().map(|&x| x as i32).collect();
5218 let d_router = if mprof {
5219 let _ = e.stream().synchronize();
5220 let v = mt.elapsed().as_secs_f64() * 1e3;
5221 mt = std::time::Instant::now();
5222 v
5223 } else {
5224 0.0
5225 };
5226 let mdet = std::env::var("MEMRA_MOE_DETERM").as_deref() == Ok("1")
5240 && t >= 16
5241 && il < 4;
5242 if mdet {
5243 let a = tp.runtime.run_tensor_parallel_routes_nvfp4_prime_grouped(
5244 bank,
5245 e,
5246 z,
5247 t,
5248 &sel_i32,
5249 &route_weights,
5250 n_used,
5251 tp.activation_limit,
5252 )?;
5253 let b = tp.runtime.run_tensor_parallel_routes_nvfp4_prime_grouped(
5254 bank,
5255 e,
5256 z,
5257 t,
5258 &sel_i32,
5259 &route_weights,
5260 n_used,
5261 tp.activation_limit,
5262 )?;
5263 let (ha, hb) = (e.dtoh(&a)?, e.dtoh(&b)?);
5264 let mut md = 0.0f32;
5265 let mut ndiff = 0usize;
5266 for (x, y) in ha.iter().zip(hb.iter()) {
5267 let d = (x - y).abs();
5268 if d > 0.0 {
5269 ndiff += 1;
5270 }
5271 if d > md {
5272 md = d;
5273 }
5274 }
5275 eprintln!(
5276 "[moe-determ] il={il} t={t} maxdiff={md:.3e} \
5277 differing={ndiff}/{} -> {}",
5278 ha.len(),
5279 if ndiff == 0 {
5280 "IDENTICAL"
5281 } else {
5282 "NONDETERMINISTIC"
5283 }
5284 );
5285 }
5286 let mut output =
5287 tp.runtime.run_tensor_parallel_routes_nvfp4_prime_grouped(
5288 bank,
5289 e,
5290 z,
5291 t,
5292 &sel_i32,
5293 &route_weights,
5294 n_used,
5295 tp.activation_limit,
5296 )?;
5297 let d_gemm = if mprof {
5298 let _ = e.stream().synchronize();
5299 let v = mt.elapsed().as_secs_f64() * 1e3;
5300 mt = std::time::Instant::now();
5301 v
5302 } else {
5303 0.0
5304 };
5305 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5306 if mprof {
5307 let _ = e.stream().synchronize();
5308 let d_shared = mt.elapsed().as_secs_f64() * 1e3;
5309 eprintln!(
5313 "[moe-prof] il={il} t={t} router={d_router:.1}ms \
5314 gemm={d_gemm:.1}ms shared={d_shared:.1}ms"
5315 );
5316 }
5317 return Ok(output);
5318 }
5319 }
5320 }
5321 if t == 1
5322 && crate::tp::step_nvfp4_dev_routes_enabled()?
5323 && crate::tp::step_tp_dev_router_enabled()?
5324 {
5325 if let Some(tp) = &m.step_tp {
5326 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5327 let (sf, route_norm) = sigmoid;
5328 static D1_ROUTER: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5335 let d1_router = *D1_ROUTER.get_or_init(|| {
5336 std::env::var("MEMRA_DEV1_ROUTER").as_deref() == Ok("1")
5337 });
5338 if d1_router {
5339 let (sf_h, rn_h) = sigmoid;
5340 let n_ex = m.gate_exps.n_expert;
5341 let act_ct = m.active_count();
5342 let _ = tp.runtime.nvfp4_routes_prestage_with(
5343 bank,
5344 e,
5345 z,
5346 |rank1, in1, sel1, w1| {
5347 let mut guard = DEV1_ROUTER_REPS
5348 .lock()
5349 .map_err(|_| "dev1 router replica lock")?;
5350 let (reps, scratch) =
5351 guard.get_or_insert_with(|| (Default::default(), None));
5352 if !reps.contains_key(&il) {
5353 use cudarc::driver::DevicePtr;
5354 let (g1, p1, a1) = (
5355 rank1.htod(&vec![0.0f32; n_ex * n_embd])?,
5356 rank1.htod(&vec![0.0f32; n_ex])?,
5357 rank1.alloc_u8_uninit(n_ex)?,
5358 );
5359 for (src, dst_len, dst) in [
5360 (
5361 {
5362 let s = e.stream();
5363 let (p, _g) =
5364 m.gate_inp.float_data().device_ptr(&s);
5365 p as u64
5366 },
5367 n_ex * n_embd * 4,
5368 {
5369 let s = rank1.stream();
5370 let (p, _g) = g1.device_ptr(&s);
5371 p as u64
5372 },
5373 ),
5374 (
5375 {
5376 let s = e.stream();
5377 let (p, _g) = m.exp_probs_b_dev.device_ptr(&s);
5378 p as u64
5379 },
5380 n_ex * 4,
5381 {
5382 let s = rank1.stream();
5383 let (p, _g) = p1.device_ptr(&s);
5384 p as u64
5385 },
5386 ),
5387 (
5388 {
5389 let s = e.stream();
5390 let (p, _g) =
5391 m.active_experts_dev.device_ptr(&s);
5392 p as u64
5393 },
5394 n_ex,
5395 {
5396 let s = rank1.stream();
5397 let (p, _g) = a1.device_ptr(&s);
5398 p as u64
5399 },
5400 ),
5401 ] {
5402 crate::tp::raw_copy_bytes(dst, src, dst_len, rank1)?;
5403 }
5404 rank1.stream().synchronize()?;
5405 reps.insert(il, (g1, p1, a1));
5406 }
5407 if scratch.is_none() {
5408 *scratch = Some(rank1.htod(&vec![0.0f32; n_ex])?);
5409 }
5410 let (g1, p1, a1) = reps.get(&il).expect("armed above");
5411 let logits1 = scratch.as_mut().expect("armed above");
5412 rank1.router_gemv_into(g1, in1, logits1, n_embd, n_ex, 1)?;
5413 rank1.moe_router_sigmoid_topk_into(
5414 logits1, 1, n_ex, n_used, act_ct, p1, a1, sf_h, rn_h, sel1,
5415 w1,
5416 )?;
5417 Ok(true)
5418 },
5419 )?;
5420 } else {
5421 let _ = tp.runtime.nvfp4_routes_prestage(bank, e, z)?;
5422 }
5423 static SELW: std::sync::Mutex<
5427 Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>,
5428 > = std::sync::Mutex::new(None);
5429 let mut selw = SELW.lock().map_err(|_| "selw lock poisoned")?;
5430 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
5431 *selw = Some((
5432 e.ctx().ordinal(),
5433 e.htod_i32(&vec![0i32; n_used])?,
5434 e.htod(&vec![0.0f32; n_used])?,
5435 ));
5436 }
5437 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
5438 e.moe_router_sigmoid_topk_into(
5439 &logits,
5440 t,
5441 n_expert,
5442 n_used,
5443 m.active_count(),
5444 &m.exp_probs_b_dev,
5445 &m.active_experts_dev,
5446 sf,
5447 route_norm,
5448 sel_d,
5449 w_d,
5450 )?;
5451 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
5452 static SHEXP_OV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5456 let shexp_ov = *SHEXP_OV.get_or_init(|| {
5457 std::env::var("MEMRA_SHEXP_OVERLAP").as_deref() == Ok("1")
5458 });
5459 static SHEXP_D1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5463 let shexp_d1 = *SHEXP_D1.get_or_init(|| {
5464 std::env::var("MEMRA_SHEXP_DEV1").as_deref() == Ok("1")
5465 }) && tp.runtime.rank_engine(1).is_some();
5466 static TAIL3: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5470 let tail3 = *TAIL3
5471 .get_or_init(|| std::env::var("MEMRA_TAIL_ADD3").as_deref() != Ok("0"));
5472 let mut ov_issued = false;
5473 let mut d1_issued = false;
5474 let mut tail_folded = false;
5475 let mut output = if shexp_d1 {
5476 let rank1 = tp.runtime.rank_engine(1).expect("checked above");
5477 tp.runtime
5478 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
5479 bank,
5480 e,
5481 z,
5482 &sel_d,
5483 &w_d,
5484 n_used,
5485 tp.activation_limit,
5486 || {
5487 d1_issued = Self::shexp_dev1_issue(
5488 e, rank1, m, z, cfg, il, n_embd,
5489 )?;
5490 Ok(())
5491 },
5492 )?
5493 } else if shexp_ov {
5494 let post_add = if tail3 {
5499 Self::shexp_overlap_tail_ptrs(e, m, cfg, n_embd)?
5500 } else {
5501 None
5502 };
5503 let used_post = post_add.is_some();
5504 let out = tp
5505 .runtime
5506 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
5507 bank,
5508 e,
5509 z,
5510 &sel_d,
5511 &w_d,
5512 n_used,
5513 tp.activation_limit,
5514 || {
5515 ov_issued =
5516 Self::shexp_overlap_issue(e, m, z, cfg, il, n_embd)?;
5517 Ok(())
5518 },
5519 post_add,
5520 )?;
5521 if used_post && ov_issued {
5526 tail_folded = true; }
5528 out
5529 } else {
5530 tp.runtime.run_tensor_parallel_routes_nvfp4_device_routed(
5531 bank,
5532 e,
5533 z,
5534 &sel_d,
5535 &w_d,
5536 n_used,
5537 tp.activation_limit,
5538 )?
5539 };
5540 if output.len() != t * n_embd {
5541 return Err(format!(
5542 "Step tp routed output has {} values, expected {t}x{n_embd}",
5543 output.len()
5544 )
5545 .into());
5546 }
5547 if tail_folded {
5548 } else if d1_issued {
5550 Self::shexp_dev1_apply(e, &mut output, n_embd)?;
5551 } else if ov_issued {
5552 Self::shexp_overlap_apply(e, &mut output, n_embd)?;
5553 } else {
5554 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5555 }
5556 static DR_LOGGED: std::sync::atomic::AtomicU64 =
5557 std::sync::atomic::AtomicU64::new(0);
5558 let layer_bit = 1u64 << (il as u64 % 64);
5559 if DR_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5560 & layer_bit
5561 == 0
5562 {
5563 eprintln!(
5564 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5565 expert_transport={} native_p2p={} router=device \
5566 activation=host-canonical accumulation=host-canonical \
5567 output=e-device io=device performance_claim=false \
5568 (logged once per layer)",
5569 tp.devices,
5570 tp.runtime.transport_label(),
5571 tp.runtime.native_p2p(),
5572 );
5573 }
5574 return Ok(output);
5575 }
5576 }
5577 }
5578 static ROUTE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5582 static ROUTE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5583 let route_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
5584 let route_started = route_timing.then(std::time::Instant::now);
5585 let (selected, route_weights, input) = Self::moe_route_sigmoid_with_input(
5586 e,
5587 &logits,
5588 z,
5589 t,
5590 n_embd,
5591 n_expert,
5592 n_used,
5593 m.exp_probs_b.as_deref(),
5594 sigmoid,
5595 m.active_experts.as_deref(),
5596 )?;
5597 if let Some(started) = route_started {
5598 use std::sync::atomic::Ordering;
5599 let ns = ROUTE_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
5600 + started.elapsed().as_nanos() as u64;
5601 let calls = ROUTE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
5602 if calls % 430 == 0 {
5603 eprintln!(
5604 "[moe-route-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
5605 ns as f64 / 1.0e6,
5606 ns as f64 / calls as f64 / 1.0e3,
5607 );
5608 }
5609 }
5610 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5611 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5612 Self::trace_moe_input(e, il, t, n_embd, z)?;
5613 let selected = selected
5614 .iter()
5615 .map(|&expert| expert as usize)
5616 .collect::<Vec<_>>();
5617 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5622 if let Some(tp) = &m.step_tp {
5623 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5624 let mut output = tp.runtime.run_tensor_parallel_routes_nvfp4_device_io(
5625 bank,
5626 e,
5627 z,
5628 &selected,
5629 &route_weights,
5630 n_used,
5631 tp.activation_limit,
5632 )?;
5633 if output.len() != t * n_embd {
5634 return Err(format!(
5635 "Step tp routed output has {} values, expected {t}x{n_embd}",
5636 output.len()
5637 )
5638 .into());
5639 }
5640 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5641 static IO_LOGGED: std::sync::atomic::AtomicU64 =
5642 std::sync::atomic::AtomicU64::new(0);
5643 let layer_bit = 1u64 << (il as u64 % 64);
5644 if IO_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5645 & layer_bit
5646 == 0
5647 {
5648 eprintln!(
5649 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5650 expert_transport={} native_p2p={} activation=host-canonical \
5651 accumulation=host-canonical output=e-device io=device \
5652 performance_claim=false (logged once per layer)",
5653 tp.devices,
5654 tp.runtime.transport_label(),
5655 tp.runtime.native_p2p(),
5656 );
5657 }
5658 return Ok(output);
5659 }
5660 }
5661 }
5662 let (routed, mode, devices, transport, native_p2p) = if let Some(tp) = &m.step_tp {
5663 (
5664 match &tp.experts {
5665 crate::hybrid::StepTpExpertBank::E4m3(bank) => {
5666 tp.runtime.run_tensor_parallel_routes(
5667 bank,
5668 &input,
5669 t,
5670 &selected,
5671 &route_weights,
5672 n_used,
5673 )?
5674 }
5675 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => {
5676 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5677 tp.runtime.run_tensor_parallel_routes_nvfp4_device(
5678 bank,
5679 &input,
5680 &selected,
5681 &route_weights,
5682 n_used,
5683 tp.activation_limit,
5684 )?
5685 } else {
5686 tp.runtime.run_tensor_parallel_routes_nvfp4(
5687 bank,
5688 &input,
5689 t,
5690 &selected,
5691 &route_weights,
5692 n_used,
5693 tp.activation_limit,
5694 )?
5695 }
5696 }
5697 },
5698 "tp",
5699 &tp.devices,
5700 tp.runtime.transport_label(),
5701 tp.runtime.native_p2p(),
5702 )
5703 } else {
5704 let ep = m
5705 .step_ep
5706 .as_ref()
5707 .ok_or("Step distributed runtime has no EP or TP state")?;
5708 (
5709 match &ep.experts {
5710 crate::hybrid::StepEpExpertBank::E4m3(bank) => {
5711 ep.runtime.run_routed_experts(
5712 bank,
5713 &input,
5714 t,
5715 &selected,
5716 &route_weights,
5717 n_used,
5718 ep.activation_limit,
5719 )?
5720 }
5721 crate::hybrid::StepEpExpertBank::Nvfp4(bank) => {
5722 ep.runtime.run_routed_experts_nvfp4(
5723 bank,
5724 &input,
5725 t,
5726 &selected,
5727 &route_weights,
5728 n_used,
5729 ep.activation_limit,
5730 )?
5731 }
5732 },
5733 if ep.configured_by_tp { "tp-ep" } else { "ep" },
5734 &ep.devices,
5735 ep.runtime.transport_label(),
5736 ep.runtime.native_p2p(),
5737 )
5738 };
5739 if routed.len() != t * n_embd {
5740 return Err(format!(
5741 "Step {mode} routed output has {} values, expected {t}x{n_embd}",
5742 routed.len()
5743 )
5744 .into());
5745 }
5746 let mut output = e.htod(&routed)?;
5747 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5748 static STEP_LOGGED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5751 let layer_bit = 1u64 << (il as u64 % 64);
5752 if STEP_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed) & layer_bit
5753 == 0
5754 {
5755 eprintln!(
5756 "[step-{mode}] execute layer={il} tokens={t} devices={devices:?} \
5757 expert_transport={transport} native_p2p={native_p2p} \
5758 activation={} accumulation={} output={} \
5759 performance_claim=false (logged once per layer)",
5760 if let Some(ep) = &m.step_ep {
5761 ep.runtime.expert_activation_label()
5762 } else {
5763 "host-canonical"
5764 },
5765 if let Some(ep) = &m.step_ep {
5766 ep.runtime.expert_accumulation_label()
5767 } else {
5768 "host-canonical"
5769 },
5770 if let Some(ep) = &m.step_ep {
5771 ep.runtime.expert_output_label()
5772 } else {
5773 "host-accumulated"
5774 },
5775 );
5776 if let Some(ep) = &m.step_ep {
5777 if let Some(limit) = ep.activation_limit {
5778 eprintln!(
5779 "[step-ep-clamp] execute layer={il} tokens={t} routed_clamp={limit} \
5780 formula=min-silu-times-clamped-up performance_claim=false"
5781 );
5782 }
5783 }
5784 }
5785 return Ok(output);
5786 }
5787 if Self::sigmoid_resident_dev_eligible(e, m, cfg, sliding_gated_moe) {
5788 let moe = cfg.moe.as_ref().unwrap();
5789 let n_expert = moe.expert_count as usize;
5790 let n_used = moe.expert_used_count as usize;
5791 let sigmoid = cfg.sigmoid_router().unwrap();
5792 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5793 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5794 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
5795 }
5796 if t > 1 && moe_grouped_enabled(cfg, prefill) {
5799 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
5800 if std::env::var("MEMRA_MOE_GATE").is_ok() {
5805 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
5806 let g_host = e.dtoh(&grouped_out)?;
5807 let s_host = e.dtoh(&seq_out)?;
5808 let g_bytes: &[u8] = unsafe {
5809 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
5810 };
5811 let s_bytes: &[u8] = unsafe {
5812 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
5813 };
5814 if g_bytes == s_bytes {
5815 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
5816 } else {
5817 let diffs = g_host
5818 .iter()
5819 .zip(s_host.iter())
5820 .enumerate()
5821 .filter(|(_, (a, b))| a != b)
5822 .count();
5823 let maxdiff = g_host
5824 .iter()
5825 .zip(s_host.iter())
5826 .map(|(a, b)| (a - b).abs())
5827 .fold(0.0f32, f32::max);
5828 panic!(
5829 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
5830 g_host.len()
5831 );
5832 }
5833 }
5834 return Ok(grouped_out);
5835 }
5836 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
5837 }
5838
5839 fn sigmoid_resident_dev_eligible(
5840 e: &Engine,
5841 m: &MoeWeights,
5842 cfg: &ModelConfig,
5843 sliding_gated_moe: bool,
5844 ) -> bool {
5845 let Some(moe) = cfg.moe.as_ref() else {
5846 return false;
5847 };
5848 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5851 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
5852 std::env::var("MEMRA_MOE_STATS").is_ok()
5853 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5854 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5855 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
5856 || std::env::var("MEMRA_MOE_GATE").is_ok()
5857 });
5858 let resident_layout_supported = m.dev_exps.as_ref().is_some_and(|dev| {
5859 if dev.dev != e.ctx().ordinal() {
5860 return false;
5861 }
5862 let q8 = moe_q8_enabled()
5863 && q8_expert_supported(m.gate_exps.qtype)
5864 && q8_expert_supported(m.up_exps.qtype)
5865 && q8_expert_supported(m.down_exps.qtype);
5866 let fp8 = dev.fp8_blk.is_some()
5867 && m.gate_exps.qtype == crate::QT_F8_E4M3_BLK
5868 && m.up_exps.qtype == crate::QT_F8_E4M3_BLK
5869 && m.down_exps.qtype == crate::QT_F8_E4M3_BLK;
5870 q8 || fp8
5871 });
5872 sliding_gated_moe
5873 && sigmoid_router_enabled()
5874 && moe_dev_enabled()
5875 && moe_slab_enabled()
5876 && !observation_mode
5877 && moe.expert_used_count <= 8
5878 && m.has_uniform_expert_layout()
5879 && m.gate_exps.macros.is_none()
5880 && m.up_exps.macros.is_none()
5881 && m.down_exps.macros.is_none()
5882 && !m.has_macros
5883 && resident_layout_supported
5884 }
5885
5886 pub(crate) fn moe_ffn_sequential(
5888 e: &Engine,
5889 m: &MoeWeights,
5890 z: &CudaSlice<f32>,
5891 t: usize,
5892 cfg: &ModelConfig,
5893 il: u16,
5894 max_block: usize,
5895 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5896 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
5897 }
5898
5899 fn moe_router_logits(
5903 e: &Engine,
5904 m: &MoeWeights,
5905 z: &CudaSlice<f32>,
5906 t: usize,
5907 cfg: &ModelConfig,
5908 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5909 if t < PRIME_MIN_T {
5910 if crate::router_kernel_on() {
5912 e.router_gemv(
5913 m.gate_inp.float_data(),
5914 z,
5915 cfg.n_embd as usize,
5916 m.gate_exps.n_expert,
5917 t,
5918 )
5919 } else {
5920 e.matmul_decode_exact(&m.gate_inp, z, t)
5921 }
5922 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
5923 e.router_gemv(
5924 m.gate_inp.float_data(),
5925 z,
5926 cfg.n_embd as usize,
5927 m.gate_exps.n_expert,
5928 t,
5929 )
5930 } else {
5931 e.matmul(&m.gate_inp, z, t)
5932 }
5933 }
5934
5935 fn trace_moe_routes(
5939 il: u16,
5940 t: usize,
5941 sel_all: &[u32],
5942 weights: &[f32],
5943 ) -> Result<(), Box<dyn std::error::Error>> {
5944 use std::io::Write as _;
5945 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
5946 let mut f = std::fs::OpenOptions::new()
5947 .create(true)
5948 .append(true)
5949 .open(path)?;
5950 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
5951 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
5952 }
5953 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
5954 let mut f = std::fs::OpenOptions::new()
5955 .create(true)
5956 .append(true)
5957 .open(path)?;
5958 let pairs: Vec<String> = sel_all
5959 .iter()
5960 .zip(weights)
5961 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
5962 .collect();
5963 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
5964 }
5965 Ok(())
5966 }
5967
5968 #[allow(clippy::too_many_arguments)]
5969 fn trace_sigmoid_router_logits(
5970 e: &Engine,
5971 il: u16,
5972 t: usize,
5973 n_expert: usize,
5974 n_used: usize,
5975 logits: &CudaSlice<f32>,
5976 m: &MoeWeights,
5977 (scaling_factor, route_norm): (f32, bool),
5978 ) -> Result<(), Box<dyn std::error::Error>> {
5979 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
5980 return Ok(());
5981 }
5982 let logits = e.dtoh(logits)?;
5983 let active: Vec<u8> = m
5984 .active_experts
5985 .as_ref()
5986 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
5987 .unwrap_or_else(|| vec![1; n_expert]);
5988 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
5989 crate::sigrouter_contract::capture_served_logits(
5990 il as u32,
5991 t,
5992 n_expert,
5993 n_used,
5994 scaling_factor,
5995 route_norm,
5996 &active,
5997 &bias,
5998 &logits,
5999 )?;
6000 Ok(())
6001 }
6002
6003 fn trace_moe_input(
6008 e: &Engine,
6009 il: u16,
6010 t: usize,
6011 n_embd: usize,
6012 z: &CudaSlice<f32>,
6013 ) -> Result<(), Box<dyn std::error::Error>> {
6014 use std::io::Write as _;
6015 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
6016 return Ok(());
6017 };
6018 let values = active_matrix_values(z.len(), t, n_embd, "MoE input trace activation")?;
6019 let host = e.dtoh_view(&z.slice(0..values))?;
6020 let bytes = unsafe {
6021 std::slice::from_raw_parts(
6022 host.as_ptr().cast::<u8>(),
6023 host.len() * std::mem::size_of::<f32>(),
6024 )
6025 };
6026 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
6027 let mut state = state
6028 .lock()
6029 .map_err(|_| "MoE input trace writer lock is poisoned")?;
6030 if state.is_none() {
6031 let dir = std::path::PathBuf::from(&dir);
6032 std::fs::create_dir_all(&dir)?;
6033 let index = std::fs::OpenOptions::new()
6034 .create(true)
6035 .append(true)
6036 .open(dir.join("index.jsonl"))?;
6037 *state = Some(MoeInputTraceWriter {
6038 dir,
6039 index,
6040 payloads: std::collections::HashMap::new(),
6041 });
6042 }
6043 let writer = state.as_mut().unwrap();
6044 if writer.dir != std::path::Path::new(&dir) {
6045 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
6046 }
6047 let file_name = format!("layer-{il:03}.f32");
6048 if !writer.payloads.contains_key(&il) {
6049 let payload = std::fs::OpenOptions::new()
6050 .create(true)
6051 .append(true)
6052 .open(writer.dir.join(&file_name))?;
6053 let offset = payload.metadata()?.len();
6054 writer.payloads.insert(il, (payload, offset));
6055 }
6056 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
6057 let row_offset = *offset;
6058 payload.write_all(bytes)?;
6059 *offset += bytes.len() as u64;
6060 writeln!(
6061 writer.index,
6062 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
6063 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
6064 \"payload_bytes\":{}}}",
6065 bytes.len()
6066 )?;
6067 Ok(())
6068 }
6069
6070 #[allow(clippy::too_many_arguments)]
6071 pub(crate) fn moe_ffn_sequential_zq8(
6072 e: &Engine,
6073 m: &MoeWeights,
6074 z: &CudaSlice<f32>,
6075 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
6076 t: usize,
6077 cfg: &ModelConfig,
6078 il: u16,
6079 max_block: usize,
6080 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6081 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
6082 let moe = cfg.moe.as_ref().unwrap();
6083 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);
6090 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
6091 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);
6094
6095 let lim_exp = cfg.clamp_exp_at(il as u32);
6098 let lim_shexp = cfg.clamp_shexp_at(il as u32);
6099 let use_cache = Engine::moe_cache_enabled();
6100 let uniform_experts = m.has_uniform_expert_layout();
6101 let moe_q8 = uniform_experts
6102 && moe_q8_enabled()
6103 && q8_expert_supported(m.gate_exps.qtype)
6104 && q8_expert_supported(m.up_exps.qtype)
6105 && q8_expert_supported(m.down_exps.qtype);
6106 let cpu_expert_requested = crate::cpu_experts::configured();
6113 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
6114 return Err(std::io::Error::other(
6115 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
6116 )
6117 .into());
6118 }
6119 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
6120 let freeze_cpu_residency = cpu_expert_requested
6126 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
6127 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
6128 .ok()
6129 .and_then(|value| value.parse::<usize>().ok())
6130 .is_some_and(|tokens| tokens > 0);
6131 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
6132 e.freeze_moe_cache();
6133 }
6134 let cache_frozen = use_cache && e.moe_cache_frozen();
6135 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
6136
6137 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
6140 if let Some(sig) = cfg.sigmoid_router() {
6141 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
6142 }
6143
6144 let no_exp_macros = m.gate_exps.macros.is_none()
6183 && m.up_exps.macros.is_none()
6184 && m.down_exps.macros.is_none();
6185 if cfg.sigmoid_router().is_none()
6189 && cfg.m3.is_none()
6190 && cfg.hy3.is_none()
6191 && !cfg.swiglu_clamped_at(il as u32)
6192 && no_exp_macros
6193 && t > MOE_DEV_MAX_T
6197 && m.dev_exps.is_some()
6198 && moe_q8_enabled()
6199 && q8_expert_supported(m.gate_exps.qtype)
6200 && q8_expert_supported(m.up_exps.qtype)
6201 && q8_expert_supported(m.down_exps.qtype)
6202 && std::env::var("MEMRA_MOE_PAIRS")
6203 .map(|v| v != "0")
6204 .unwrap_or(true)
6205 && std::env::var("MEMRA_MOE_STATS").is_err()
6206 {
6207 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
6208 }
6209
6210 let dev_ok = uniform_experts
6228 && cfg.sigmoid_router().is_none()
6229 && cfg.m3.is_none()
6230 && cfg.hy3.is_none()
6231 && !cfg.swiglu_clamped_at(il as u32);
6232 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
6236 || std::env::var("MEMRA_MOE_TRACE").is_ok()
6237 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
6238 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
6239 if dev_ok
6240 && t <= MOE_DEV_MAX_T
6241 && m.dev_exps.is_some()
6242 && n_used <= 8
6243 && moe_dev_enabled()
6244 && !observe_routes
6245 {
6246 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
6247 }
6248 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
6249 let row_ok = e.with_moe_cache(max_block, |c, eng| {
6250 if moe_prewarm_enabled() {
6251 c.prewarm_layer(il, m, eng)?;
6252 }
6253 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
6254 })?;
6255 if row_ok {
6256 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
6257 }
6258 }
6259
6260 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
6262 if cpu_hybrid {
6263 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
6264 e,
6265 &logits,
6266 z,
6267 t,
6268 n_embd,
6269 n_expert,
6270 n_used,
6271 m.exp_probs_b.as_deref(),
6272 sig,
6273 m.active_experts.as_deref(),
6274 )?;
6275 (sel, w, Some(input))
6276 } else {
6277 let (sel, w) =
6278 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
6279 (sel, w, None)
6280 }
6281 } else {
6282 let (sel, w) =
6283 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
6284 (sel, w, None)
6285 };
6286 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
6287
6288 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
6292 Self::trace_moe_input(e, il, t, n_embd, z)?;
6293
6294 let worker_disk_prefetch =
6306 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
6307 let promote_worker_h2d =
6308 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
6309 if promote_worker_h2d {
6310 let mut selected_blocks = Vec::with_capacity(n_used * 3);
6311 for &ex in sel_all.iter().take(n_used) {
6312 let ex = ex as u16;
6313 selected_blocks.extend([
6314 BlockId::new(il, PROJ_GATE, ex),
6315 BlockId::new(il, PROJ_UP, ex),
6316 BlockId::new(il, PROJ_DOWN, ex),
6317 ]);
6318 }
6319 for &ex in sel_all.iter().take(n_used) {
6320 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
6321 }
6322 e.with_moe_cache(max_block, |cache, eng| {
6323 cache.promote_worker_reads_at_safe_boundary(
6324 &selected_blocks,
6325 &selected_blocks,
6326 eng,
6327 )?;
6328 Ok(())
6329 })?;
6330 }
6331
6332 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
6335 let mut cnt = vec![0u32; n_expert];
6336 for &s in sel_all.iter() {
6337 cnt[s as usize] += 1;
6338 }
6339 let total = sel_all.len() as f64;
6340 let mut h = 0.0f64;
6341 let mut active = 0usize;
6342 for &c in &cnt {
6343 if c > 0 {
6344 active += 1;
6345 let p = c as f64 / total;
6346 h -= p * p.log2();
6347 }
6348 }
6349 let maxc = cnt.iter().copied().max().unwrap_or(0);
6350 println!(
6351 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
6352 il,
6353 t,
6354 sel_all.len(),
6355 active,
6356 n_expert,
6357 h,
6358 (n_expert as f64).log2(),
6359 total / active.max(1) as f64,
6360 maxc
6361 );
6362 }
6363
6364 let gdec_may_fire = uniform_experts
6377 && use_cache
6378 && n_used <= 8
6379 && gdec_enabled()
6380 && !cfg.swiglu_clamped_at(il as u32);
6381 let slab_local = m
6397 .dev_exps
6398 .as_ref()
6399 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
6400 let slab_bases = slab_local.map(|d| {
6401 use cudarc::driver::DevicePtr;
6402 let s = e.stream();
6403 let (pg, _g0) = d.gate.device_ptr(&s);
6404 let (pu, _g1) = d.up.device_ptr(&s);
6405 let (pd, _g2) = d.down.device_ptr(&s);
6406 (pg as u64, pu as u64, pd as u64)
6407 });
6408 let slab_fused_may_fire = slab_bases.is_some()
6418 && n_used <= 8
6419 && gdec_enabled()
6420 && !cfg.swiglu_clamped_at(il as u32)
6421 && cfg.m3.is_none()
6422 && no_exp_macros
6423 && moe_q8;
6424 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
6427 e.uninit(t * n_embd)?
6428 } else {
6429 e.zeros(t * n_embd)?
6430 };
6431 let cpu_input = if cpu_hybrid {
6434 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
6435 } else {
6436 None
6437 };
6438
6439 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;
6447 let mut scratch_u: Option<CudaSlice<u8>> = None;
6448 let mut scratch_d: Option<CudaSlice<u8>> = None;
6449 let page_window = moe_page_prefetch_window();
6457
6458 for tok in 0..t {
6461 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
6462 let w = &w_all[tok * n_used..(tok + 1) * n_used];
6463 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
6465
6466 let no_macros = m.gate_exps.macros.is_none()
6480 && m.up_exps.macros.is_none()
6481 && m.down_exps.macros.is_none();
6482 if slab_fused_may_fire {
6492 let (pg, pu, pd) = slab_bases.unwrap();
6493 let mut gp = [0u64; 8];
6494 let mut up = [0u64; 8];
6495 let mut dp = [0u64; 8];
6496 for (j, &ex) in sel.iter().enumerate() {
6497 let ex = ex as usize;
6498 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
6499 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
6500 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
6501 }
6502 let mut wv = [0f32; 8];
6503 wv[..n_used].copy_from_slice(w);
6504 if tok_q8.is_none() {
6505 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6506 }
6507 let (zq, zd) = tok_q8.as_ref().unwrap();
6508 let act = e.moe_gate_up_silu8_q8(
6509 crate::WPtr8(gp),
6510 crate::WPtr8(up),
6511 zq,
6512 zd,
6513 n_embd,
6514 n_ff_exp,
6515 n_used,
6516 m.gate_exps.qtype,
6517 m.up_exps.qtype,
6518 m.gate_exps.row_bytes,
6519 m.up_exps.row_bytes,
6520 )?;
6521 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
6522 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6523 e.moe_down8_fma_q8(
6524 crate::WPtr8(dp),
6525 crate::F32x8(wv),
6526 &aq2,
6527 &ad2,
6528 &mut dst,
6529 n_ff_exp,
6530 n_embd,
6531 n_used,
6532 m.down_exps.qtype,
6533 m.down_exps.row_bytes,
6534 )?;
6535 continue;
6536 }
6537 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
6538 if tok_q8.is_none() {
6539 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6540 }
6541 let (zq, zd) = tok_q8.as_ref().unwrap();
6542 if Self::moe_gdec_token_q8(
6543 e,
6544 m,
6545 il,
6546 max_block,
6547 zq,
6548 zd,
6549 sel,
6550 w,
6551 &mut moe_out,
6552 tok,
6553 n_embd,
6554 n_ff_exp,
6555 n_used,
6556 )? {
6557 continue;
6558 }
6559 } else if gdec_may_fire
6560 && cfg.m3.is_none()
6561 && no_macros
6562 && Self::moe_gdec_token(
6563 e,
6564 m,
6565 il,
6566 max_block,
6567 &zt,
6568 sel,
6569 w,
6570 &mut moe_out,
6571 tok,
6572 n_embd,
6573 n_ff_exp,
6574 n_used,
6575 )?
6576 {
6577 continue;
6578 }
6579
6580 if gdec_may_fire || slab_fused_may_fire {
6586 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6587 e.memset_zeros_view(&mut row)?;
6588 }
6589
6590 let mut cpu_mask = vec![false; sel.len()];
6596 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
6597 let gpu_resident = if use_cache {
6598 e.with_moe_cache(max_block, |cache, _| {
6599 Ok(sel
6600 .iter()
6601 .map(|&expert| {
6602 let expert = expert as u16;
6603 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
6604 .into_iter()
6605 .filter(|&projection| {
6606 cache
6607 .resident(BlockId::new(il, projection, expert))
6608 .is_some()
6609 })
6610 .count()
6611 })
6612 .collect::<Vec<_>>())
6613 })?
6614 } else {
6615 vec![0; sel.len()]
6616 };
6617 let mut cpu_selected = Vec::new();
6618 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
6619 if gpu_resident[index] != 3 {
6620 cpu_mask[index] = true;
6621 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
6622 let expert = expert as usize;
6623 cpu_selected.push((expert, route_weight));
6624 }
6625 }
6626 if crate::cpu_experts::predictor_enabled() {
6627 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6631 crate::cpu_experts::predictor_submit(il, row);
6632 }
6633 if cpu_selected.is_empty() {
6634 None
6635 } else {
6636 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6637 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
6638 .map_err(std::io::Error::other)?;
6639 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
6640 }
6641 } else {
6642 None
6643 };
6644
6645 let worker_window = worker_disk_prefetch
6646 .then(worker_prefetch_window)
6647 .unwrap_or(0);
6648 for (j, &ex) in sel.iter().enumerate() {
6649 if cpu_mask[j] {
6650 continue;
6651 }
6652 let ex = ex as usize;
6653 if let Some(d) = slab_local {
6660 let gl = m.gate_exps.expert_layout(ex);
6661 let ul = m.up_exps.expert_layout(ex);
6662 let dl = m.down_exps.expert_layout(ex);
6663 let (g0, u0, d0) = (
6664 ex * m.gate_exps.expert_stride,
6665 ex * m.up_exps.expert_stride,
6666 ex * m.down_exps.expert_stride,
6667 );
6668 let (gate, up) = if moe_q8 {
6669 if tok_q8.is_none() {
6670 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6671 }
6672 let (zq, zd) = tok_q8.as_ref().unwrap();
6673 (
6674 e.qmatvec_expert_q8(
6675 &d.gate,
6676 g0..g0 + gl.len,
6677 zq,
6678 zd,
6679 1,
6680 m.gate_exps.in_f,
6681 m.gate_exps.out_f,
6682 gl.qtype,
6683 gl.row_bytes,
6684 )?,
6685 e.qmatvec_expert_q8(
6686 &d.up,
6687 u0..u0 + ul.len,
6688 zq,
6689 zd,
6690 1,
6691 m.up_exps.in_f,
6692 m.up_exps.out_f,
6693 ul.qtype,
6694 ul.row_bytes,
6695 )?,
6696 )
6697 } else {
6698 (
6699 e.qmatvec_view(
6700 &d.gate,
6701 g0..g0 + gl.len,
6702 &zt,
6703 1,
6704 m.gate_exps.in_f,
6705 m.gate_exps.out_f,
6706 gl.qtype,
6707 gl.row_bytes,
6708 )?,
6709 e.qmatvec_view(
6710 &d.up,
6711 u0..u0 + ul.len,
6712 &zt,
6713 1,
6714 m.up_exps.in_f,
6715 m.up_exps.out_f,
6716 ul.qtype,
6717 ul.row_bytes,
6718 )?,
6719 )
6720 };
6721 let mut act = e.uninit(n_ff_exp)?;
6722 Self::ffn_act_lim(
6723 e,
6724 cfg,
6725 &gate,
6726 &up,
6727 m.gate_exps.macro_scale(ex),
6728 m.up_exps.macro_scale(ex),
6729 lim_exp,
6730 &mut act,
6731 n_ff_exp,
6732 )?;
6733 let y = if moe_q8 {
6734 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6735 e.qmatvec_expert_q8(
6736 &d.down,
6737 d0..d0 + dl.len,
6738 &aq2,
6739 &ad2,
6740 1,
6741 m.down_exps.in_f,
6742 m.down_exps.out_f,
6743 dl.qtype,
6744 dl.row_bytes,
6745 )?
6746 } else {
6747 let actv = act.slice(0..n_ff_exp);
6748 e.qmatvec_view(
6749 &d.down,
6750 d0..d0 + dl.len,
6751 &actv,
6752 1,
6753 m.down_exps.in_f,
6754 m.down_exps.out_f,
6755 dl.qtype,
6756 dl.row_bytes,
6757 )?
6758 };
6759 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6760 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6761 continue;
6762 }
6763 for next in page_prefetch_positions(j, sel.len(), page_window) {
6764 Self::moe_prefetch_host_expert(sel[next] as usize, m);
6765 }
6766 let keep = [
6767 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
6768 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
6769 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
6770 ];
6771 if worker_disk_prefetch && worker_window > 0 {
6772 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
6773 Self::moe_prefetch_disk_expert(
6774 e,
6775 il,
6776 sel[next] as usize,
6777 m,
6778 max_block,
6779 &keep,
6780 )?;
6781 }
6782 } else if cache_dispatch
6783 && !cpu_hybrid
6784 && moe_prefetch_enabled()
6785 && j + 1 < sel.len()
6786 {
6787 let next = sel[j + 1] as usize;
6788 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
6789 }
6790 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
6791 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
6792 if (gate_q8 || up_q8) && tok_q8.is_none() {
6795 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6796 }
6797 let gate = if gate_q8 {
6798 let (zq, zd) = tok_q8.as_ref().unwrap();
6799 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
6800 } else {
6801 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
6802 };
6803 let up = if up_q8 {
6804 let (zq, zd) = tok_q8.as_ref().unwrap();
6805 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
6806 } else {
6807 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
6808 };
6809 let mut act = e.uninit(n_ff_exp)?;
6810 Self::ffn_act_lim(
6811 e,
6812 cfg,
6813 &gate,
6814 &up,
6815 m.gate_exps.macro_scale(ex),
6816 m.up_exps.macro_scale(ex),
6817 lim_exp,
6818 &mut act,
6819 n_ff_exp,
6820 )?;
6821 let y = if down_q8 {
6822 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6823 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
6824 } else {
6825 let actv = act.slice(0..n_ff_exp);
6826 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
6827 };
6828 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6829 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6831 } else if cache_dispatch {
6832 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
6837 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
6838 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6840 e,
6841 cfg,
6842 &gate,
6843 &up,
6844 m.gate_exps.macro_scale(ex),
6845 m.up_exps.macro_scale(ex),
6846 lim_exp,
6847 &mut act,
6848 n_ff_exp,
6849 )?;
6850 let actv = act.slice(0..n_ff_exp);
6851 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
6852 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6853 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6855 } else if cache_frozen {
6856 let gate = Self::moe_frozen_gemm(
6861 e,
6862 il,
6863 PROJ_GATE,
6864 ex,
6865 m,
6866 max_block,
6867 &zt,
6868 &mut scratch_g,
6869 g_len,
6870 )?;
6871 let up = Self::moe_frozen_gemm(
6872 e,
6873 il,
6874 PROJ_UP,
6875 ex,
6876 m,
6877 max_block,
6878 &zt,
6879 &mut scratch_u,
6880 u_len,
6881 )?;
6882 let mut act = e.uninit(n_ff_exp)?;
6883 Self::ffn_act_lim(
6884 e,
6885 cfg,
6886 &gate,
6887 &up,
6888 m.gate_exps.macro_scale(ex),
6889 m.up_exps.macro_scale(ex),
6890 lim_exp,
6891 &mut act,
6892 n_ff_exp,
6893 )?;
6894 let actv = act.slice(0..n_ff_exp);
6895 let y = Self::moe_frozen_gemm(
6896 e,
6897 il,
6898 PROJ_DOWN,
6899 ex,
6900 m,
6901 max_block,
6902 &actv,
6903 &mut scratch_d,
6904 d_len,
6905 )?;
6906 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6907 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6908 } else {
6909 if scratch_g.is_none() {
6913 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
6914 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
6915 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
6916 }
6917 let (sg, su, sd) = (
6918 scratch_g.as_mut().unwrap(),
6919 scratch_u.as_mut().unwrap(),
6920 scratch_d.as_mut().unwrap(),
6921 );
6922 let gl = m.gate_exps.expert_layout(ex);
6923 let ul = m.up_exps.expert_layout(ex);
6924 let dl = m.down_exps.expert_layout(ex);
6925 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
6926 let gate = e.qmatvec_view(
6927 sg,
6928 0..gl.len,
6929 &zt,
6930 1,
6931 m.gate_exps.in_f,
6932 m.gate_exps.out_f,
6933 gl.qtype,
6934 gl.row_bytes,
6935 )?;
6936
6937 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
6938 let up = e.qmatvec_view(
6939 su,
6940 0..ul.len,
6941 &zt,
6942 1,
6943 m.up_exps.in_f,
6944 m.up_exps.out_f,
6945 ul.qtype,
6946 ul.row_bytes,
6947 )?;
6948
6949 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6951 e,
6952 cfg,
6953 &gate,
6954 &up,
6955 m.gate_exps.macro_scale(ex),
6956 m.up_exps.macro_scale(ex),
6957 lim_exp,
6958 &mut act,
6959 n_ff_exp,
6960 )?;
6961
6962 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
6963 let actv = act.slice(0..n_ff_exp);
6964 let y = e.qmatvec_view(
6965 sd,
6966 0..dl.len,
6967 &actv,
6968 1,
6969 m.down_exps.in_f,
6970 m.down_exps.out_f,
6971 dl.qtype,
6972 dl.row_bytes,
6973 )?;
6974
6975 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6976 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6977 }
6978 }
6979 if let Some(worker) = cpu_worker {
6980 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
6981 let cpu_output = e.htod(&cpu_output)?;
6982 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6983 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
6984 }
6985 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
6986 for (j, &ex) in sel.iter().enumerate() {
6987 if cpu_mask[j] {
6988 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
6989 }
6990 }
6991 }
6992 }
6993
6994 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6999 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7000 {
7001 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
7010 let (sg_gate, sg_up) = if t == 1 {
7011 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
7012 } else if verify_t {
7013 (
7014 e.matmul_decode_exact(gate_shexp, z, t)?,
7015 e.matmul_decode_exact(up_shexp, z, t)?,
7016 )
7017 } else {
7018 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
7020 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
7022 e,
7023 cfg,
7024 &sg_gate,
7025 &sg_up,
7026 1.0,
7027 1.0,
7028 lim_shexp,
7029 &mut sa,
7030 t * n_ff_sh,
7031 )?;
7032 let sh = if verify_t {
7033 e.matmul_decode_exact(down_shexp, &sa, t)?
7034 } else {
7035 e.matmul(down_shexp, &sa, t)?
7036 }; let g = match &m.gate_inp_shexp {
7050 Some(gate_inp_shexp) => {
7051 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
7052 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7053 } else {
7054 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7055 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
7057 g
7058 }
7059 }
7060 None => e.htod(&vec![1.0f32; t])?,
7061 };
7062 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7064 }
7065
7066 Ok(moe_out)
7067 }
7068
7069 pub fn stage1_h2d_per_token(&self) -> u64 {
7072 use crate::hybrid::Ffn;
7073 let n_used = self
7074 .cfg
7075 .moe
7076 .as_ref()
7077 .map(|m| m.expert_used_count as u64)
7078 .unwrap_or(0);
7079 let mut bytes = 0u64;
7080 for l in self.layers.iter() {
7081 if let Ffn::Moe(m) = &l.ffn {
7082 bytes += n_used
7083 * (m.gate_exps.max_expert_bytes()
7084 + m.up_exps.max_expert_bytes()
7085 + m.down_exps.max_expert_bytes()) as u64;
7086 }
7087 }
7088 bytes
7089 }
7090
7091 pub(crate) fn max_moe_block(&self) -> usize {
7095 use crate::hybrid::Ffn;
7096 let mut mx = 0usize;
7097 let mut scan = |ffn: &Ffn| {
7098 if let Ffn::Moe(m) = ffn {
7099 mx = mx
7100 .max(m.gate_exps.max_expert_bytes())
7101 .max(m.up_exps.max_expert_bytes())
7102 .max(m.down_exps.max_expert_bytes());
7103 }
7104 };
7105 for l in self.layers.iter() {
7106 scan(&l.ffn);
7107 }
7108 if let Some(mtp) = self.mtp.as_ref() {
7109 scan(&mtp.ffn);
7110 }
7111 mx
7112 }
7113
7114 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
7117 use crate::hybrid::Ffn;
7118 let mut sizes = Vec::new();
7119 let mut scan = |ffn: &Ffn| {
7120 let Ffn::Moe(m) = ffn else { return };
7121 for ex in 0..m.gate_exps.n_expert {
7122 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
7123 continue;
7124 }
7125 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
7126 let len = exps.expert_layout(ex).len;
7127 if len > 0 {
7128 sizes.push(len);
7129 }
7130 }
7131 }
7132 };
7133 for layer in &self.layers {
7134 scan(&layer.ffn);
7135 }
7136 if let Some(mtp) = &self.mtp {
7137 scan(&mtp.ffn);
7138 }
7139 sizes
7140 }
7141
7142 pub fn save_cpu_expert_residency_profile(
7148 &self,
7149 e: &Engine,
7150 path: &std::path::Path,
7151 ) -> Result<(), Box<dyn std::error::Error>> {
7152 let Some(ids) = e.export_moe_residency() else {
7153 return Err("no MoE residency cache to persist".into());
7154 };
7155 let mut body = format!(
7156 "memra-freeze-profile v1 max_block={} blocks={}\n",
7157 self.max_moe_block(),
7158 ids.len()
7159 );
7160 for (layer, proj, ex) in &ids {
7161 body.push_str(&format!("{layer} {proj} {ex}\n"));
7162 }
7163 let tmp = path.with_extension("tmp");
7164 std::fs::write(&tmp, body)?;
7165 std::fs::rename(&tmp, path)?;
7166 println!(
7167 "[moe-cache] freeze profile saved: {} blocks -> {}",
7168 ids.len(),
7169 path.display()
7170 );
7171 Ok(())
7172 }
7173
7174 pub fn restore_cpu_expert_residency_profile(
7178 &self,
7179 e: &Engine,
7180 path: &std::path::Path,
7181 ) -> Result<bool, Box<dyn std::error::Error>> {
7182 use crate::hybrid::Ffn;
7183 use crate::moe_cache::BlockId;
7184 let Ok(content) = std::fs::read_to_string(path) else {
7185 return Ok(false);
7186 };
7187 let mut lines = content.lines();
7188 let Some(header) = lines.next() else {
7189 return Ok(false);
7190 };
7191 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
7192 if !header.starts_with(&expected) {
7193 println!(
7194 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
7195 path.display()
7196 );
7197 return Ok(false);
7198 }
7199 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
7200 std::collections::HashMap::new();
7201 for line in lines {
7202 let mut fields = line.split_whitespace();
7203 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
7204 else {
7205 continue;
7206 };
7207 let (Ok(layer), Ok(proj), Ok(ex)) =
7208 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
7209 else {
7210 continue;
7211 };
7212 by_layer
7213 .entry(layer)
7214 .or_default()
7215 .push(BlockId::new(layer, proj, ex));
7216 }
7217 let requested: usize = by_layer.values().map(Vec::len).sum();
7218 if requested == 0 {
7219 return Ok(false);
7220 }
7221 let max_block = self.max_moe_block();
7222 let mut restaged = 0usize;
7223 let mut stage_layer =
7224 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
7225 let Ffn::Moe(m) = ffn else { return Ok(()) };
7226 let Some(ids) = by_layer.get(&layer_index) else {
7227 return Ok(());
7228 };
7229 e.with_moe_cache(max_block, |cache, eng| {
7230 for id in ids {
7231 if cache.restage_block(*id, m, eng)? {
7232 restaged += 1;
7233 }
7234 }
7235 Ok(())
7236 })
7237 };
7238 for (index, layer) in self.layers.iter().enumerate() {
7239 stage_layer(index as u16, &layer.ffn)?;
7240 }
7241 if let Some(mtp) = self.mtp.as_ref() {
7242 stage_layer(u16::MAX, &mtp.ffn)?;
7243 }
7244 e.freeze_moe_cache();
7245 println!(
7246 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
7247 path.display()
7248 );
7249 Ok(true)
7250 }
7251
7252 pub fn freeze_cpu_expert_residency(
7254 &self,
7255 e: &Engine,
7256 ) -> Result<(), Box<dyn std::error::Error>> {
7257 e.freeze_moe_cache();
7258 Ok(())
7259 }
7260
7261 pub fn ffn_act(
7269 e: &Engine,
7270 cfg: &ModelConfig,
7271 gate: &CudaSlice<f32>,
7272 up: &CudaSlice<f32>,
7273 act: &mut CudaSlice<f32>,
7274 n: usize,
7275 ) -> Result<(), Box<dyn std::error::Error>> {
7276 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
7277 }
7278
7279 #[allow(clippy::too_many_arguments)]
7283 pub(crate) fn ffn_act_scaled(
7284 e: &Engine,
7285 cfg: &ModelConfig,
7286 gate: &CudaSlice<f32>,
7287 up: &CudaSlice<f32>,
7288 gs: f32,
7289 us: f32,
7290 act: &mut CudaSlice<f32>,
7291 n: usize,
7292 ) -> Result<(), Box<dyn std::error::Error>> {
7293 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
7294 }
7295
7296 #[allow(clippy::too_many_arguments)]
7305 pub(crate) fn ffn_act_lim(
7306 e: &Engine,
7307 cfg: &ModelConfig,
7308 gate: &CudaSlice<f32>,
7309 up: &CudaSlice<f32>,
7310 gs: f32,
7311 us: f32,
7312 limit: Option<f32>,
7313 act: &mut CudaSlice<f32>,
7314 n: usize,
7315 ) -> Result<(), Box<dyn std::error::Error>> {
7316 if let Some(m3) = cfg.m3.as_ref() {
7317 debug_assert!(
7318 limit.is_none(),
7319 "m3 swigluoai and step35 clamp are different archs"
7320 );
7321 return e.swigluoai_mul_scaled(
7322 gate,
7323 up,
7324 gs,
7325 us,
7326 m3.swiglu_alpha,
7327 m3.swiglu_limit,
7328 act,
7329 n,
7330 );
7331 }
7332 if let Some(l) = limit {
7333 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
7334 }
7335 if gs == 1.0 && us == 1.0 {
7336 return e.silu_mul(gate, up, act, n);
7337 }
7338 e.silu_mul_scaled(gate, up, gs, us, act, n)
7339 }
7340
7341 fn moe_route(
7347 e: &Engine,
7348 logits: &CudaSlice<f32>,
7349 t: usize,
7350 n_expert: usize,
7351 n_used: usize,
7352 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7353 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
7354 }
7355
7356 #[allow(clippy::too_many_arguments)]
7364 fn moe_route_sigmoid_cfg(
7365 e: &Engine,
7366 logits: &CudaSlice<f32>,
7367 t: usize,
7368 n_expert: usize,
7369 n_used: usize,
7370 m: &MoeWeights,
7371 (sf, route_norm): (f32, bool),
7372 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7373 if sigmoid_router_enabled() {
7374 return e.moe_router_sigmoid_topk_host(
7375 logits,
7376 t,
7377 n_expert,
7378 n_used,
7379 m.active_count(),
7380 &m.exp_probs_b_dev,
7381 &m.active_experts_dev,
7382 sf,
7383 route_norm,
7384 );
7385 }
7386 let lg = e.dtoh(logits)?;
7387 Self::moe_route_sigmoid_host(
7388 &lg,
7389 t,
7390 n_expert,
7391 n_used,
7392 m.exp_probs_b.as_deref(),
7393 sf,
7394 route_norm,
7395 m.active_experts.as_deref(),
7396 )
7397 }
7398
7399 fn moe_route_cfg(
7402 e: &Engine,
7403 logits: &CudaSlice<f32>,
7404 t: usize,
7405 n_expert: usize,
7406 n_used: usize,
7407 active: Option<&[bool]>,
7408 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7409 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
7412 return e.moe_router_topk_host(logits, t, n_expert, n_used);
7413 }
7414 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
7417 let mut w_out = vec![0f32; t * n_used];
7418 for tok in 0..t {
7419 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7420 let maxl = row
7422 .iter()
7423 .enumerate()
7424 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
7425 .map(|(_, &x)| x)
7426 .fold(f32::NEG_INFINITY, f32::max);
7427 let mut probs = vec![0f32; n_expert];
7428 let mut den = 0f32;
7429 for i in 0..n_expert {
7430 if active.is_some_and(|mask| !mask[i]) {
7431 continue;
7432 }
7433 let x = (row[i] - maxl).exp();
7434 probs[i] = x;
7435 den += x;
7436 }
7437 for p in probs.iter_mut() {
7438 *p /= den;
7439 }
7440 let mut idx: Vec<usize> = (0..n_expert)
7442 .filter(|&i| active.is_none_or(|mask| mask[i]))
7443 .collect();
7444 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
7445 let sl = &idx[..n_used];
7446 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
7447 let mut ws: f32 = wv.iter().sum();
7448 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
7450 *x /= ws;
7451 }
7452 for j in 0..n_used {
7453 sel[tok * n_used + j] = sl[j] as u32;
7454 w_out[tok * n_used + j] = wv[j];
7455 }
7456 }
7457 Ok((sel, w_out))
7458 }
7459
7460 #[allow(clippy::too_many_arguments)]
7461 fn moe_route_sigmoid_with_input(
7462 e: &Engine,
7463 logits: &CudaSlice<f32>,
7464 input: &CudaSlice<f32>,
7465 t: usize,
7466 in_features: usize,
7467 n_expert: usize,
7468 n_used: usize,
7469 bias: Option<&[f32]>,
7470 (sf, route_norm): (f32, bool),
7471 active: Option<&[bool]>,
7472 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
7473 let logit_values =
7474 active_matrix_values(logits.len(), t, n_expert, "sigmoid router logits")?;
7475 let input_values =
7476 active_matrix_values(input.len(), t, in_features, "sigmoid router input")?;
7477 let (lg, input) = e.dtoh_pair_views(
7478 &logits.slice(0..logit_values),
7479 &input.slice(0..input_values),
7480 )?;
7481 let (sel, w) =
7482 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
7483 Ok((sel, w, input))
7484 }
7485
7486 pub fn start_moe_prefetch_predictor(
7491 &self,
7492 e: &Engine,
7493 cfg: &ModelConfig,
7494 ) -> Result<(), Box<dyn std::error::Error>> {
7495 use crate::hybrid::Ffn;
7496 let Some(sig) = cfg.sigmoid_router() else {
7497 return Err("prefetch predictor requires a sigmoid-router arch".into());
7498 };
7499 let resident: std::collections::HashSet<(u16, u8, u16)> = e
7500 .export_moe_residency()
7501 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
7502 .into_iter()
7503 .collect();
7504 let mut layers = Vec::new();
7505 for (index, layer) in self.layers.iter().enumerate() {
7506 let Ffn::Moe(m) = &layer.ffn else { continue };
7507 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
7508 continue;
7509 };
7510 let router = e.dtoh(data)?;
7511 let n_expert = m.gate_exps.n_expert;
7512 let n_embd = m.gate_exps.in_f;
7513 if router.len() != n_embd * n_expert {
7514 continue;
7515 }
7516 let build = |exps: &crate::model::HostExps| {
7517 (0..n_expert)
7518 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
7519 .collect::<Vec<_>>()
7520 };
7521 layers.push((
7522 index as u16,
7523 crate::cpu_experts::PredictLayerInit {
7524 router,
7525 bias: m.exp_probs_b.clone(),
7526 active: m.active_experts.clone(),
7527 n_embd,
7528 n_used: cfg
7529 .moe
7530 .as_ref()
7531 .map(|moe| moe.expert_used_count as usize)
7532 .ok_or("prefetch predictor requires MoE config")?,
7533 sig,
7534 weights_n_expert: n_expert,
7535 gate: build(&m.gate_exps),
7536 up: build(&m.up_exps),
7537 down: build(&m.down_exps),
7538 },
7539 ));
7540 }
7541 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
7542 }
7543
7544 #[allow(clippy::too_many_arguments)]
7547 pub fn moe_route_sigmoid_host_public(
7548 logits: &[f32],
7549 t: usize,
7550 n_expert: usize,
7551 n_used: usize,
7552 bias: Option<&[f32]>,
7553 sf: f32,
7554 route_norm: bool,
7555 active: Option<&[bool]>,
7556 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7557 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
7558 }
7559
7560 #[allow(clippy::too_many_arguments)]
7561 fn moe_route_sigmoid_host(
7562 lg: &[f32],
7563 t: usize,
7564 n_expert: usize,
7565 n_used: usize,
7566 bias: Option<&[f32]>,
7567 sf: f32,
7568 route_norm: bool,
7569 active: Option<&[bool]>,
7570 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7571 let active_count = active
7572 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
7573 .unwrap_or(n_expert);
7574 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
7575 if lg.len() != t * n_expert {
7576 return Err(format!(
7577 "sigmoid router logits length mismatch: got {}, expected {}",
7578 lg.len(),
7579 t * n_expert,
7580 )
7581 .into());
7582 }
7583 let mut sel = vec![0u32; t * n_used];
7584 let mut w_out = vec![0f32; t * n_used];
7585 for tok in 0..t {
7586 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7587 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
7588 let selsc: Vec<f32> = match bias {
7590 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
7591 None => scores.clone(),
7592 };
7593 let mut idx: Vec<usize> = (0..n_expert)
7594 .filter(|&i| active.is_none_or(|mask| mask[i]))
7595 .collect();
7596 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
7597 let sl = &idx[..n_used];
7598 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
7599 if route_norm {
7600 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
7601 for x in wv.iter_mut() {
7602 *x = *x / ws * sf;
7603 }
7604 } else {
7605 for x in wv.iter_mut() {
7606 *x *= sf;
7607 }
7608 }
7609 for j in 0..n_used {
7610 sel[tok * n_used + j] = sl[j] as u32;
7611 w_out[tok * n_used + j] = wv[j];
7612 }
7613 }
7614 Ok((sel, w_out))
7615 }
7616
7617 #[allow(clippy::too_many_arguments)]
7621 fn moe_ffn_sigmoid_dev(
7622 e: &Engine,
7623 m: &MoeWeights,
7624 z: &CudaSlice<f32>,
7625 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7626 logits: &CudaSlice<f32>,
7627 t: usize,
7628 cfg: &ModelConfig,
7629 il: u16,
7630 (scaling_factor, route_norm): (f32, bool),
7631 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7632 let moe = cfg.moe.as_ref().unwrap();
7633 let n_embd = cfg.n_embd as usize;
7634 let n_expert = moe.expert_count as usize;
7635 let n_used = moe.expert_used_count as usize;
7636 let n_ff_exp = moe.expert_ff_length as usize;
7637 let dev = m.dev_exps.as_ref().unwrap();
7638 debug_assert_eq!(dev.dev, e.ctx().ordinal());
7639 debug_assert!(m.has_uniform_expert_layout());
7640 debug_assert!(!m.has_macros);
7641
7642 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
7643 logits,
7644 t,
7645 n_expert,
7646 n_used,
7647 m.active_count(),
7648 &m.exp_probs_b_dev,
7649 &m.active_experts_dev,
7650 scaling_factor,
7651 route_norm,
7652 )?;
7653 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
7654 if let Some(fp8) = dev.fp8_blk.as_ref() {
7655 debug_assert_eq!(m.gate_exps.qtype, crate::QT_F8_E4M3_BLK);
7656 debug_assert_eq!(m.up_exps.qtype, crate::QT_F8_E4M3_BLK);
7657 debug_assert_eq!(m.down_exps.qtype, crate::QT_F8_E4M3_BLK);
7658 debug_assert_eq!(fp8.gate.rows, m.gate_exps.out_f.div_ceil(128));
7659 debug_assert_eq!(fp8.up.rows, m.up_exps.out_f.div_ceil(128));
7660 debug_assert_eq!(fp8.down.rows, m.down_exps.out_f.div_ceil(128));
7661
7662 let selected = e.dtoh_i32(&sel_d)?;
7669 let route_weights = e.dtoh(&w_d)?;
7670 let mut moe_out = e.zeros(t * n_embd)?;
7671 for tok in 0..t {
7672 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
7673 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7674 for j in 0..n_used {
7675 let pair = tok * n_used + j;
7676 let expert = selected[pair] as usize;
7677 let gate = Self::moe_resident_fp8_e4m3(
7678 e,
7679 &m.gate_exps,
7680 &dev.gate,
7681 &fp8.gate,
7682 expert,
7683 &zt,
7684 1,
7685 )?;
7686 let up = Self::moe_resident_fp8_e4m3(
7687 e, &m.up_exps, &dev.up, &fp8.up, expert, &zt, 1,
7688 )?;
7689 let mut act = e.uninit(n_ff_exp)?;
7690 Self::ffn_act_lim(
7691 e,
7692 cfg,
7693 &gate,
7694 &up,
7695 1.0,
7696 1.0,
7697 cfg.clamp_exp_at(il as u32),
7698 &mut act,
7699 n_ff_exp,
7700 )?;
7701 let act = act.slice(0..n_ff_exp);
7702 let down = Self::moe_resident_fp8_e4m3(
7703 e,
7704 &m.down_exps,
7705 &dev.down,
7706 &fp8.down,
7707 expert,
7708 &act,
7709 1,
7710 )?;
7711 e.axpy_into(&down, route_weights[pair], &mut dst, n_embd)?;
7712 }
7713 }
7714 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7715 eprintln!(
7716 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} \
7717 native=fp8blk-w8a8-e4m3-reference clamp={}",
7718 cfg.clamp_exp_at(il as u32).is_some(),
7719 );
7720 }
7721 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7722 return Ok(moe_out);
7723 }
7724 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
7725 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7726 (combined, combined)
7727 } else {
7728 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7729 };
7730 let (zq, zd) = match (t, zq8) {
7731 (1, Some((q, d))) => (q.clone(), d.clone()),
7732 _ => e.quantize_q8_1(z, t, n_embd)?,
7733 };
7734 let n_pairs = t * n_used;
7735 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
7736 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7740 let pair_tok_d = e.htod_i32(&pair_tok)?;
7741 let gate = e.moe_pairs_matvec_q8(
7742 &dev.ptr_row,
7743 0,
7744 &pair_tok_d,
7745 &sel_d,
7746 &zq,
7747 &zd,
7748 n_embd,
7749 n_ff_exp,
7750 n_expert,
7751 n_pairs,
7752 m.gate_exps.qtype,
7753 gate_row_bytes,
7754 )?;
7755 let up = e.moe_pairs_matvec_q8(
7756 &dev.ptr_row,
7757 1,
7758 &pair_tok_d,
7759 &sel_d,
7760 &zq,
7761 &zd,
7762 n_embd,
7763 n_ff_exp,
7764 n_expert,
7765 n_pairs,
7766 m.up_exps.qtype,
7767 up_row_bytes,
7768 )?;
7769 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7770 Self::ffn_act_lim(
7771 e,
7772 cfg,
7773 &gate,
7774 &up,
7775 1.0,
7776 1.0,
7777 cfg.clamp_exp_at(il as u32),
7778 &mut act,
7779 n_pairs * n_ff_exp,
7780 )?;
7781 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7782 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7783 let pair_self_d = e.htod_i32(&pair_self)?;
7784 let down = e.moe_pairs_matvec_q8(
7785 &dev.ptr_row,
7786 2,
7787 &pair_self_d,
7788 &sel_d,
7789 &aq2,
7790 &ad2,
7791 n_ff_exp,
7792 n_embd,
7793 n_expert,
7794 n_pairs,
7795 m.down_exps.qtype,
7796 m.down_exps.row_bytes,
7797 )?;
7798 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7799 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7800 let tok_off_d = e.htod_i32(&tok_off)?;
7801 let tok_ids_d = e.htod_i32(&tok_ids)?;
7802 let mut output = e.uninit(t * n_embd)?;
7803 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
7804 output
7805 } else {
7806 let act = e.moe_gate_up_silu8_dev_q8_rows(
7807 &dev.ptr_row,
7808 &sel_d,
7809 &zq,
7810 &zd,
7811 t,
7812 n_embd,
7813 n_ff_exp,
7814 n_used,
7815 n_expert,
7816 m.gate_exps.qtype,
7817 m.up_exps.qtype,
7818 gate_row_bytes,
7819 up_row_bytes,
7820 &m.dev_macros,
7821 )?;
7822 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7823 let mut output = e.uninit(t * n_embd)?;
7824 e.moe_down8_fma_dev_q8_rows_g(
7825 &dev.ptr_row,
7826 &sel_d,
7827 &w_d,
7828 &aq2,
7829 &ad2,
7830 &mut output,
7831 t,
7832 n_ff_exp,
7833 n_embd,
7834 n_used,
7835 n_expert,
7836 m.down_exps.qtype,
7837 m.down_exps.row_bytes,
7838 )?;
7839 output
7840 };
7841
7842 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7843 eprintln!(
7844 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
7845 cfg.clamp_exp_at(il as u32).is_some(),
7846 dev.gu_il,
7847 );
7848 }
7849 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7850 Ok(moe_out)
7851 }
7852
7853 #[allow(clippy::too_many_arguments)]
7854 fn moe_resident_fp8_e4m3(
7855 e: &Engine,
7856 exps: &crate::model::HostExps,
7857 bytes: &CudaSlice<u8>,
7858 scales: &crate::hybrid::DevExpertFp8ProjectionScales,
7859 expert: usize,
7860 x: &cudarc::driver::CudaView<f32>,
7861 m: usize,
7862 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7863 let layout = exps.expert_layout(expert);
7864 debug_assert_eq!(layout.qtype, crate::QT_F8_E4M3_BLK);
7865 debug_assert_eq!(scales.rows * scales.cols, scales.expert_stride);
7866 let byte_start = expert * exps.expert_stride;
7867 let scale_start = expert * scales.expert_stride;
7868 let weight = bytes.slice(byte_start..byte_start + layout.len);
7869 let scale = scales
7870 .scales
7871 .slice(scale_start..scale_start + scales.expert_stride);
7872 e.qmatvec_mmq_fp8_blk_view(&weight, &scale, x, m, exps.in_f, exps.out_f)
7873 }
7874
7875 fn moe_ffn_pairs(
7884 e: &Engine,
7885 m: &MoeWeights,
7886 z: &CudaSlice<f32>,
7887 logits: &CudaSlice<f32>,
7888 t: usize,
7889 cfg: &ModelConfig,
7890 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7891 let moe = cfg.moe.as_ref().unwrap();
7892 let n_embd = cfg.n_embd as usize;
7893 let n_expert = moe.expert_count as usize;
7894 let n_used = moe.expert_used_count as usize;
7895 let n_ff_exp = moe.expert_ff_length as usize;
7896 debug_assert!(
7901 !cfg.swiglu_clamped_anywhere(),
7902 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
7903 );
7904 let dev = m.dev_exps.as_ref().unwrap();
7905 let (rbg_d, rbu_d) = if dev.gu_il {
7907 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7908 (sxx, sxx)
7909 } else {
7910 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7911 };
7912
7913 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
7914 let n_pairs = t * n_used;
7915 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
7918 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
7919 let pair_w: Vec<f32> = w_all.clone();
7920 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7921 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7922 let pt = e.htod_i32(&pair_tok)?;
7923 let px = e.htod_i32(&pair_ex)?;
7924 let pw = e.htod(&pair_w)?;
7925 let toff = e.htod_i32(&tok_off)?;
7926 let tids = e.htod_i32(&tok_ids)?;
7927
7928 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7932 for p in 0..n_pairs {
7933 by_ex[pair_ex[p] as usize].push(p as i32);
7934 }
7935 let mut ex_ids: Vec<i32> = Vec::new();
7936 let mut ex_off: Vec<i32> = vec![0];
7937 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
7938 for (ex, list) in by_ex.iter().enumerate() {
7939 if list.is_empty() {
7940 continue;
7941 }
7942 ex_ids.push(ex as i32);
7943 ex_pairs.extend_from_slice(list);
7944 ex_off.push(ex_pairs.len() as i32);
7945 }
7946 let n_active = ex_ids.len();
7947 let exi = e.htod_i32(&ex_ids)?;
7948 let exo = e.htod_i32(&ex_off)?;
7949 let exp_d = e.htod_i32(&ex_pairs)?;
7950 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7971 let mma_t = *MMA_T.get_or_init(|| {
7972 std::env::var("MEMRA_MOE_MMA_T")
7973 .ok()
7974 .and_then(|v| v.parse().ok())
7975 .unwrap_or(16)
7976 });
7977 let use_mma = std::env::var("MEMRA_MOE_MMA")
7978 .map(|v| v != "0")
7979 .unwrap_or(true)
7980 && t >= mma_t
7981 && q8_expert_dec_supported(m.gate_exps.qtype)
7982 && q8_expert_dec_supported(m.up_exps.qtype)
7983 && q8_expert_dec_supported(m.down_exps.qtype)
7984 && n_embd % 256 == 0
7985 && n_ff_exp % 256 == 0;
7986 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
8002 && q8_expert_dec_supported(m.up_exps.qtype)
8003 && q8_expert_dec_supported(m.down_exps.qtype)
8004 && n_embd % 256 == 0
8005 && n_ff_exp % 256 == 0;
8006 let f16g_mode = crate::moe_f16g_mode();
8007 let f16g = f16g_mode != 0
8008 && t >= mma_t
8009 && (f16g_mode != 3 || !mma_capable)
8010 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
8011 && f16g_proj_ok(m.up_exps.qtype, n_embd)
8012 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
8013 if use_mma || f16g {
8014 let y_down = if f16g {
8022 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
8026 let csr_tok_d = e.htod_i32(&csr_tok)?;
8027 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
8028 let g_csr = e.moe_f16_grouped(
8029 &dev.ptr_row,
8030 0,
8031 n_expert,
8032 &exi,
8033 &ex_off,
8034 &exo,
8035 &z_f16,
8036 &z_s,
8037 n_embd,
8038 n_ff_exp,
8039 n_active,
8040 n_pairs,
8041 m.gate_exps.qtype,
8042 rbg_d,
8043 )?;
8044 let u_csr = e.moe_f16_grouped(
8045 &dev.ptr_row,
8046 1,
8047 n_expert,
8048 &exi,
8049 &ex_off,
8050 &exo,
8051 &z_f16,
8052 &z_s,
8053 n_embd,
8054 n_ff_exp,
8055 n_active,
8056 n_pairs,
8057 m.up_exps.qtype,
8058 rbu_d,
8059 )?;
8060 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
8061 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
8062 let d_csr = e.moe_f16_grouped(
8063 &dev.ptr_row,
8064 2,
8065 n_expert,
8066 &exi,
8067 &ex_off,
8068 &exo,
8069 &a_f16,
8070 &a_s,
8071 n_ff_exp,
8072 n_embd,
8073 n_active,
8074 n_pairs,
8075 m.down_exps.qtype,
8076 m.down_exps.row_bytes,
8077 )?;
8078 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
8079 } else {
8080 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
8082 let gate = e.mmq_iq_experts(
8083 &dev.ptr_row,
8084 0,
8085 n_expert,
8086 &exi,
8087 &exo,
8088 &exp_d,
8089 &pt,
8090 &z_scr,
8091 n_embd,
8092 n_ff_exp,
8093 n_active,
8094 n_pairs,
8095 t,
8096 m.gate_exps.qtype,
8097 rbg_d,
8098 )?;
8099 let up = e.mmq_iq_experts(
8100 &dev.ptr_row,
8101 1,
8102 n_expert,
8103 &exi,
8104 &exo,
8105 &exp_d,
8106 &pt,
8107 &z_scr,
8108 n_embd,
8109 n_ff_exp,
8110 n_active,
8111 n_pairs,
8112 t,
8113 m.up_exps.qtype,
8114 rbu_d,
8115 )?;
8116 let a_scr = if crate::moe_fuse_actq_on() {
8122 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
8123 } else {
8124 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
8125 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
8126 };
8127 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
8128 let pself = e.htod_i32(&pair_self)?;
8129 e.mmq_iq_experts(
8130 &dev.ptr_row,
8131 2,
8132 n_expert,
8133 &exi,
8134 &exo,
8135 &exp_d,
8136 &pself,
8137 &a_scr,
8138 n_ff_exp,
8139 n_embd,
8140 n_active,
8141 n_pairs,
8142 n_pairs,
8143 m.down_exps.qtype,
8144 m.down_exps.row_bytes,
8145 )?
8146 };
8147 let mut moe_out = e.uninit(t * n_embd)?;
8148 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
8149 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8150 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8151 {
8152 let n_ff_sh = gate_shexp.out_features();
8153 let sg_gate = e.matmul(gate_shexp, z, t)?;
8154 let sg_up = e.matmul(up_shexp, z, t)?;
8155 let mut sa = e.uninit(t * n_ff_sh)?;
8156 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8157 let sh = e.matmul(down_shexp, &sa, t)?;
8158 let g = match &m.gate_inp_shexp {
8164 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
8165 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8166 }
8167 Some(gate_inp_shexp) => {
8168 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8169 let mut g = e.uninit(t)?;
8170 e.sigmoid(&gs, &mut g, t)?;
8171 g
8172 }
8173 None => e.htod(&vec![1.0f32; t])?,
8174 };
8175 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8176 }
8177 return Ok(moe_out);
8178 }
8179
8180 let dec = std::env::var("MEMRA_MOE_DEC")
8183 .map(|v| v != "0")
8184 .unwrap_or(true);
8185 let matvec = |proj,
8186 exi: &_,
8187 exo: &_,
8188 exp_d: &_,
8189 pt: &_,
8190 aq: &_,
8191 ad: &_,
8192 inf,
8193 outf,
8194 qtype,
8195 rb|
8196 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8197 let dec = dec && q8_expert_dec_supported(qtype);
8199 if dec {
8200 e.moe_pairs_matvec_q8_dec(
8201 &dev.ptr_row,
8202 proj,
8203 exi,
8204 exo,
8205 exp_d,
8206 pt,
8207 aq,
8208 ad,
8209 inf,
8210 outf,
8211 n_expert,
8212 n_active,
8213 n_pairs,
8214 qtype,
8215 rb,
8216 )
8217 } else {
8218 e.moe_pairs_matvec_q8_em(
8219 &dev.ptr_row,
8220 proj,
8221 exi,
8222 exo,
8223 exp_d,
8224 pt,
8225 aq,
8226 ad,
8227 inf,
8228 outf,
8229 n_expert,
8230 n_active,
8231 n_pairs,
8232 qtype,
8233 rb,
8234 )
8235 }
8236 };
8237 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8238 let gate = matvec(
8239 0,
8240 &exi,
8241 &exo,
8242 &exp_d,
8243 &pt,
8244 &zq,
8245 &zd,
8246 n_embd,
8247 n_ff_exp,
8248 m.gate_exps.qtype,
8249 rbg_d,
8250 )?;
8251 let up = matvec(
8252 1,
8253 &exi,
8254 &exo,
8255 &exp_d,
8256 &pt,
8257 &zq,
8258 &zd,
8259 n_embd,
8260 n_ff_exp,
8261 m.up_exps.qtype,
8262 rbu_d,
8263 )?;
8264 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
8265 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8266 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
8268 let pself = e.htod_i32(&pair_self)?;
8269 let y_down = matvec(
8270 2,
8271 &exi,
8272 &exo,
8273 &exp_d,
8274 &pself,
8275 &aq2,
8276 &ad2,
8277 n_ff_exp,
8278 n_embd,
8279 m.down_exps.qtype,
8280 m.down_exps.row_bytes,
8281 )?;
8282 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
8284
8285 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8289 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8290 {
8291 let n_ff_sh = gate_shexp.out_features();
8292 let step_exact = true;
8296 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
8297 let (sg_gate, sg_up) = if step_exact && t == 1 {
8298 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, None)?
8299 } else if verify_t {
8300 let mut fused = None;
8301 if crate::spec::spec_fused_t()
8302 && (2..=4).contains(&t)
8303 && e.uses_q8_1_fast(gate_shexp)
8304 && e.uses_q8_1_fast(up_shexp)
8305 {
8306 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8307 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8308 }
8309 match fused {
8310 Some(pair) => pair,
8311 None => (
8312 e.matmul_decode_exact(gate_shexp, z, t)?,
8313 e.matmul_decode_exact(up_shexp, z, t)?,
8314 ),
8315 }
8316 } else {
8317 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8318 };
8319 let mut sa = e.uninit(t * n_ff_sh)?;
8320 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8321 let sh = if verify_t {
8322 e.matmul_decode_exact(down_shexp, &sa, t)?
8323 } else {
8324 e.matmul(down_shexp, &sa, t)?
8325 };
8326 let g = match &m.gate_inp_shexp {
8331 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
8332 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8333 }
8334 Some(gate_inp_shexp) => {
8335 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8336 let mut g = e.uninit(t)?;
8337 e.sigmoid(&gs, &mut g, t)?;
8338 g
8339 }
8340 None => e.htod(&vec![1.0f32; t])?,
8341 };
8342 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8343 }
8344 Ok(moe_out)
8345 }
8346
8347 #[allow(clippy::too_many_arguments)]
8349 #[allow(clippy::too_many_arguments)]
8350 fn moe_ffn_dev(
8351 e: &Engine,
8352 m: &MoeWeights,
8353 z: &CudaSlice<f32>,
8354 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
8355 logits: &CudaSlice<f32>,
8356 t: usize,
8357 cfg: &ModelConfig,
8358 il: u16,
8359 max_block: usize,
8360 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8361 let moe = cfg.moe.as_ref().unwrap();
8362 let n_embd = cfg.n_embd as usize;
8363 let n_expert = moe.expert_count as usize;
8364 let n_used = moe.expert_used_count as usize;
8365 let n_ff_exp = moe.expert_ff_length as usize;
8366 debug_assert!(
8370 cfg.sigmoid_router().is_none(),
8371 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
8372 );
8373 debug_assert!(
8374 !cfg.swiglu_clamped_at(il as u32),
8375 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
8376 );
8377
8378 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
8380 if m.has_macros {
8383 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
8384 }
8385
8386 let mut moe_out = e.uninit(t * n_embd)?;
8388
8389 if let Some(dev) = m.dev_exps.as_ref() {
8392 let (rbg_d, rbu_d) = if dev.gu_il {
8395 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8396 (sxx, sxx)
8397 } else {
8398 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8399 };
8400 let q8 = moe_q8_enabled()
8401 && q8_expert_supported(m.gate_exps.qtype)
8402 && q8_expert_supported(m.up_exps.qtype)
8403 && q8_expert_supported(m.down_exps.qtype);
8404 let rows_arm = q8
8413 && t > 1
8414 && crate::spec::spec_m2()
8415 && n_ff_exp == 512
8416 && n_used <= 8
8417 && std::env::var("MEMRA_MOE_DEVQ8_GU")
8418 .map(|v| v.is_empty() || v == "v")
8419 .unwrap_or(true)
8420 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
8421 .map(|v| v.is_empty() || v == "w8h2v")
8422 .unwrap_or(true);
8423 let csr_mode = std::env::var("MEMRA_MOE_CSR")
8432 .ok()
8433 .and_then(|v| v.parse::<i32>().ok())
8434 .unwrap_or(1);
8435 let csr_nvfp4_probe = std::env::var("MEMRA_MOE_CSR_NVFP4").as_deref() == Ok("1");
8452 let csr_qt = |qt: i32| {
8453 qt == crate::QT_IQ4_XS
8454 || qt == crate::QT_IQ3_S
8455 || (csr_nvfp4_probe && qt == crate::QT_NVFP4)
8456 };
8457 let csr_t_max = if csr_nvfp4_probe { MOE_DEV_MAX_T } else { 10 };
8458 let csr_uniform = m.gate_exps.qtype == m.up_exps.qtype;
8459 let csr_arm = rows_arm
8460 && csr_mode > 0
8461 && t <= csr_t_max
8462 && csr_uniform
8463 && csr_qt(m.gate_exps.qtype)
8464 && csr_qt(m.up_exps.qtype)
8465 && csr_qt(m.down_exps.qtype);
8466 if csr_arm {
8467 if csr_mode == 2 {
8468 static ENGAGED: std::sync::Once = std::sync::Once::new();
8469 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
8470 }
8471 let n_pairs = t * n_used;
8472 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8473 let act = e.moe_gate_up_silu8_dev_q8_csr(
8474 &dev.ptr_row,
8475 &sel_d,
8476 &zq,
8477 &zd,
8478 n_pairs,
8479 n_embd,
8480 n_ff_exp,
8481 n_used,
8482 n_expert,
8483 m.gate_exps.qtype,
8484 m.up_exps.qtype,
8485 rbg_d,
8486 rbu_d,
8487 )?;
8488 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8489 e.moe_down8_fma_dev_q8_rows(
8493 &dev.ptr_row,
8494 &sel_d,
8495 &w_d,
8496 &aq2,
8497 &ad2,
8498 &mut moe_out,
8499 t,
8500 n_ff_exp,
8501 n_embd,
8502 n_used,
8503 n_expert,
8504 m.down_exps.qtype,
8505 m.down_exps.row_bytes,
8506 )?;
8507 if csr_mode == 2 {
8508 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
8510 &dev.ptr_row,
8511 &sel_d,
8512 &zq,
8513 &zd,
8514 t,
8515 n_embd,
8516 n_ff_exp,
8517 n_used,
8518 n_expert,
8519 m.gate_exps.qtype,
8520 m.up_exps.qtype,
8521 rbg_d,
8522 rbu_d,
8523 &m.dev_macros,
8524 )?;
8525 let mut out_r = e.uninit(t * n_embd)?;
8526 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
8527 e.moe_down8_fma_dev_q8_rows(
8528 &dev.ptr_row,
8529 &sel_d,
8530 &w_d,
8531 &aq2r,
8532 &ad2r,
8533 &mut out_r,
8534 t,
8535 n_ff_exp,
8536 n_embd,
8537 n_used,
8538 n_expert,
8539 m.down_exps.qtype,
8540 m.down_exps.row_bytes,
8541 )?;
8542 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
8543 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
8544 let ba = a1
8545 .iter()
8546 .zip(&a2)
8547 .filter(|(x, y)| x.to_bits() != y.to_bits())
8548 .count();
8549 let bo = o1
8550 .iter()
8551 .zip(&o2)
8552 .filter(|(x, y)| x.to_bits() != y.to_bits())
8553 .count();
8554 if ba + bo > 0 {
8555 eprintln!(
8556 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
8557 a1.len(),
8558 o1.len()
8559 );
8560 let sel_h = e.dtoh_i32(&sel_d)?;
8562 let mut shown = 0;
8563 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
8564 if x.to_bits() != y.to_bits() && shown < 4 {
8565 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
8566 let ex = sel_h[p];
8567 let npx = sel_h.iter().filter(|&&v| v == ex).count();
8568 eprintln!(
8569 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
8570 );
8571 shown += 1;
8572 }
8573 }
8574 std::process::exit(3);
8575 }
8576 }
8577 } else if rows_arm {
8578 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
8581 use std::sync::atomic::{AtomicU64, Ordering};
8582 static PAIRS: AtomicU64 = AtomicU64::new(0);
8583 static UNIQ: AtomicU64 = AtomicU64::new(0);
8584 static CALLS: AtomicU64 = AtomicU64::new(0);
8585 let sel_h = e.dtoh_i32(&sel_d)?;
8586 let mut u: Vec<i32> = sel_h.clone();
8587 u.sort_unstable();
8588 u.dedup();
8589 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
8590 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
8591 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
8592 if c % 480 == 0 {
8593 let p = PAIRS.load(Ordering::Relaxed);
8594 let q = UNIQ.load(Ordering::Relaxed);
8595 eprintln!(
8596 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
8597 q as f64 / p as f64
8598 );
8599 }
8600 }
8601 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8602 let act = e.moe_gate_up_silu8_dev_q8_rows(
8603 &dev.ptr_row,
8604 &sel_d,
8605 &zq,
8606 &zd,
8607 t,
8608 n_embd,
8609 n_ff_exp,
8610 n_used,
8611 n_expert,
8612 m.gate_exps.qtype,
8613 m.up_exps.qtype,
8614 rbg_d,
8615 rbu_d,
8616 &m.dev_macros,
8617 )?;
8618 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8619 e.moe_down8_fma_dev_q8_rows(
8620 &dev.ptr_row,
8621 &sel_d,
8622 &w_d,
8623 &aq2,
8624 &ad2,
8625 &mut moe_out,
8626 t,
8627 n_ff_exp,
8628 n_embd,
8629 n_used,
8630 n_expert,
8631 m.down_exps.qtype,
8632 m.down_exps.row_bytes,
8633 )?;
8634 } else {
8635 for tok in 0..t {
8636 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8637 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8638 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8639 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8640 if q8 {
8641 let (zq, zd) = match (t, zq8) {
8642 (1, Some((q, d))) => (q.clone(), d.clone()),
8643 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
8644 };
8645 let act = e.moe_gate_up_silu8_dev_q8(
8646 &dev.ptr_row,
8647 &selt,
8648 &zq,
8649 &zd,
8650 n_embd,
8651 n_ff_exp,
8652 n_used,
8653 n_expert,
8654 m.gate_exps.qtype,
8655 m.up_exps.qtype,
8656 rbg_d,
8657 rbu_d,
8658 &m.dev_macros,
8659 )?;
8660 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8661 e.moe_down8_fma_dev_q8(
8662 &dev.ptr_row,
8663 &selt,
8664 &wt,
8665 &aq2,
8666 &ad2,
8667 &mut dst,
8668 n_ff_exp,
8669 n_embd,
8670 n_used,
8671 n_expert,
8672 m.down_exps.qtype,
8673 m.down_exps.row_bytes,
8674 )?;
8675 } else {
8676 let act = e.moe_gate_up_silu8_dev(
8677 &dev.ptr_row,
8678 &selt,
8679 &zt,
8680 n_embd,
8681 n_ff_exp,
8682 n_used,
8683 n_expert,
8684 m.gate_exps.qtype,
8685 m.up_exps.qtype,
8686 rbg_d,
8687 rbu_d,
8688 &m.dev_macros,
8689 )?;
8690 e.moe_down8_fma_dev(
8691 &dev.ptr_row,
8692 &selt,
8693 &wt,
8694 &act,
8695 &mut dst,
8696 n_ff_exp,
8697 n_embd,
8698 n_used,
8699 n_expert,
8700 m.down_exps.qtype,
8701 m.down_exps.row_bytes,
8702 )?;
8703 }
8704 }
8705 }
8706 } else {
8707 let q8 = moe_q8_enabled()
8714 && q8_expert_supported(m.gate_exps.qtype)
8715 && q8_expert_supported(m.up_exps.qtype)
8716 && q8_expert_supported(m.down_exps.qtype);
8717 e.with_moe_cache(max_block, |c, eng| {
8718 let row = c
8719 .layer_dev_row(il, n_expert, eng)?
8720 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
8721 for tok in 0..t {
8722 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8723 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8724 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8725 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8726 if q8 {
8727 let (zq, zd) = match (t, zq8) {
8728 (1, Some((q, d))) => (q.clone(), d.clone()),
8729 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
8730 };
8731 let act = eng.moe_gate_up_silu8_dev_q8(
8732 row,
8733 &selt,
8734 &zq,
8735 &zd,
8736 n_embd,
8737 n_ff_exp,
8738 n_used,
8739 n_expert,
8740 m.gate_exps.qtype,
8741 m.up_exps.qtype,
8742 m.gate_exps.row_bytes,
8743 m.up_exps.row_bytes,
8744 &m.dev_macros,
8745 )?;
8746 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
8747 eng.moe_down8_fma_dev_q8(
8748 row,
8749 &selt,
8750 &wt,
8751 &aq2,
8752 &ad2,
8753 &mut dst,
8754 n_ff_exp,
8755 n_embd,
8756 n_used,
8757 n_expert,
8758 m.down_exps.qtype,
8759 m.down_exps.row_bytes,
8760 )?;
8761 } else {
8762 let act = eng.moe_gate_up_silu8_dev(
8763 row,
8764 &selt,
8765 &zt,
8766 n_embd,
8767 n_ff_exp,
8768 n_used,
8769 n_expert,
8770 m.gate_exps.qtype,
8771 m.up_exps.qtype,
8772 m.gate_exps.row_bytes,
8773 m.up_exps.row_bytes,
8774 &m.dev_macros,
8775 )?;
8776 eng.moe_down8_fma_dev(
8777 row,
8778 &selt,
8779 &wt,
8780 &act,
8781 &mut dst,
8782 n_ff_exp,
8783 n_embd,
8784 n_used,
8785 n_expert,
8786 m.down_exps.qtype,
8787 m.down_exps.row_bytes,
8788 )?;
8789 }
8790 }
8791 c.hits += (t * 3 * n_used) as u64;
8793 Ok(())
8794 })?;
8795 }
8796
8797 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8802 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8803 {
8804 let n_ff_sh = gate_shexp.out_features();
8805 let verify_t = t > 1 && t < PRIME_MIN_T;
8808 let (sg_gate, sg_up) = if t == 1 {
8809 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
8810 } else if verify_t {
8811 let mut fused = None;
8815 if crate::spec::spec_fused_t()
8816 && (2..=4).contains(&t)
8817 && e.uses_q8_1_fast(gate_shexp)
8818 && e.uses_q8_1_fast(up_shexp)
8819 {
8820 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8821 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8822 }
8823 match fused {
8824 Some(pair) => pair,
8825 None => (
8826 e.matmul_decode_exact(gate_shexp, z, t)?,
8827 e.matmul_decode_exact(up_shexp, z, t)?,
8828 ),
8829 }
8830 } else {
8831 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8832 };
8833 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8835 let sh = if verify_t {
8836 e.matmul_decode_exact(down_shexp, &sa, t)?
8837 } else {
8838 e.matmul(down_shexp, &sa, t)?
8839 };
8840 let g = match &m.gate_inp_shexp {
8844 Some(gate_inp_shexp) => {
8845 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
8848 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8849 } else {
8850 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8851 let mut g = e.uninit(t)?;
8852 e.sigmoid(&gs, &mut g, t)?;
8853 g
8854 }
8855 }
8856 None => e.htod(&vec![1.0f32; t])?,
8857 };
8858 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8859 }
8860
8861 Ok(moe_out)
8862 }
8863
8864 #[allow(clippy::too_many_arguments)]
8874 #[allow(clippy::too_many_arguments)]
8877 fn moe_gdec_token_q8(
8878 e: &Engine,
8879 m: &MoeWeights,
8880 il: u16,
8881 max_block: usize,
8882 zq: &CudaSlice<i8>,
8883 zd: &CudaSlice<f32>,
8884 sel: &[u32],
8885 w: &[f32],
8886 moe_out: &mut CudaSlice<f32>,
8887 tok: usize,
8888 n_embd: usize,
8889 n_ff_exp: usize,
8890 n_used: usize,
8891 ) -> Result<bool, Box<dyn std::error::Error>> {
8892 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8893 use cudarc::driver::DevicePtr;
8894 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8895 let mut g = [0u64; 8];
8896 let mut u = [0u64; 8];
8897 let mut d = [0u64; 8];
8898 for (j, &ex) in sel.iter().enumerate() {
8899 let ex = ex as u16;
8900 let (Some(sg), Some(su), Some(sd)) = (
8901 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8902 c.resident(BlockId::new(il, PROJ_UP, ex)),
8903 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8904 ) else {
8905 return Ok(None);
8906 };
8907 let __s = eng.stream();
8908 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8909 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8910 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8911 g[j] = pg as u64;
8912 u[j] = pu as u64;
8913 d[j] = pd as u64;
8914 }
8915 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8916 for &ex in sel {
8917 let ex = ex as u16;
8918 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8919 c.note_profile_hit(BlockId::new(il, proj, ex));
8920 }
8921 }
8922 }
8923 c.hits += (3 * n_used) as u64;
8924 Ok(Some((g, u, d)))
8925 })?;
8926 let Some((g, u, d)) = ptrs else {
8927 return Ok(false);
8928 };
8929 let mut wv = [0f32; 8];
8930 wv[..n_used].copy_from_slice(w);
8931 let act = e.moe_gate_up_silu8_q8(
8932 crate::WPtr8(g),
8933 crate::WPtr8(u),
8934 zq,
8935 zd,
8936 n_embd,
8937 n_ff_exp,
8938 n_used,
8939 m.gate_exps.qtype,
8940 m.up_exps.qtype,
8941 m.gate_exps.row_bytes,
8942 m.up_exps.row_bytes,
8943 )?;
8944 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8946 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8947 e.moe_down8_fma_q8(
8948 crate::WPtr8(d),
8949 crate::F32x8(wv),
8950 &aq2,
8951 &ad2,
8952 &mut dst,
8953 n_ff_exp,
8954 n_embd,
8955 n_used,
8956 m.down_exps.qtype,
8957 m.down_exps.row_bytes,
8958 )?;
8959 Ok(true)
8960 }
8961
8962 fn moe_gdec_token(
8963 e: &Engine,
8964 m: &MoeWeights,
8965 il: u16,
8966 max_block: usize,
8967 zt: &cudarc::driver::CudaView<f32>,
8968 sel: &[u32],
8969 w: &[f32],
8970 moe_out: &mut CudaSlice<f32>,
8971 tok: usize,
8972 n_embd: usize,
8973 n_ff_exp: usize,
8974 n_used: usize,
8975 ) -> Result<bool, Box<dyn std::error::Error>> {
8976 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8977 use cudarc::driver::DevicePtr;
8978 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8980 let mut g = [0u64; 8];
8981 let mut u = [0u64; 8];
8982 let mut d = [0u64; 8];
8983 for (j, &ex) in sel.iter().enumerate() {
8984 let ex = ex as u16;
8985 let (Some(sg), Some(su), Some(sd)) = (
8986 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8987 c.resident(BlockId::new(il, PROJ_UP, ex)),
8988 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8989 ) else {
8990 return Ok(None);
8991 };
8992 let __s = eng.stream();
8993 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8994 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8995 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8996 g[j] = pg as u64;
8997 u[j] = pu as u64;
8998 d[j] = pd as u64;
8999 }
9000 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
9001 for &ex in sel {
9002 let ex = ex as u16;
9003 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
9004 c.note_profile_hit(BlockId::new(il, proj, ex));
9005 }
9006 }
9007 }
9008 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
9010 })?;
9011 let Some((g, u, d)) = ptrs else {
9012 return Ok(false);
9013 };
9014 let mut wv = [0f32; 8];
9015 wv[..n_used].copy_from_slice(w);
9016 let act = e.moe_gate_up_silu8(
9018 crate::WPtr8(g),
9019 crate::WPtr8(u),
9020 zt,
9021 n_embd,
9022 n_ff_exp,
9023 n_used,
9024 m.gate_exps.qtype,
9025 m.up_exps.qtype,
9026 m.gate_exps.row_bytes,
9027 m.up_exps.row_bytes,
9028 )?;
9029 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
9030 e.moe_down8_fma_into(
9031 crate::WPtr8(d),
9032 crate::F32x8(wv),
9033 &act,
9034 &mut dst,
9035 n_ff_exp,
9036 n_embd,
9037 n_used,
9038 m.down_exps.qtype,
9039 m.down_exps.row_bytes,
9040 )?;
9041 Ok(true)
9042 }
9043
9044 fn moe_cached_gemm_q8(
9049 e: &Engine,
9050 il: u16,
9051 proj: u8,
9052 ex: usize,
9053 m: &MoeWeights,
9054 max_block: usize,
9055 aq: &CudaSlice<i8>,
9056 ad: &CudaSlice<f32>,
9057 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9058 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
9059 let exps = match proj {
9060 PROJ_GATE => &m.gate_exps,
9061 PROJ_UP => &m.up_exps,
9062 _ => &m.down_exps,
9063 };
9064 let layout = exps.expert_layout(ex);
9065 let id = BlockId::new(il, proj, ex as u16);
9066 let source = exps.expert_source(ex);
9067 e.with_moe_cache(max_block, |c, eng| {
9068 let slot = c.dispatch_source(id, source, eng)?;
9069 let DispatchSlot::Resident(sl) = slot;
9070 let buf = c.slot(sl);
9071 eng.qmatvec_expert_q8(
9072 buf,
9073 0..layout.len,
9074 aq,
9075 ad,
9076 1,
9077 exps.in_f,
9078 exps.out_f,
9079 layout.qtype,
9080 layout.row_bytes,
9081 )
9082 })
9083 }
9084
9085 fn moe_cached_gemm(
9086 e: &Engine,
9087 il: u16,
9088 proj: u8,
9089 ex: usize,
9090 m: &MoeWeights,
9091 max_block: usize,
9092 x: &cudarc::driver::CudaView<f32>,
9093 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9094 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
9095 let exps = match proj {
9096 PROJ_GATE => &m.gate_exps,
9097 PROJ_UP => &m.up_exps,
9098 _ => &m.down_exps,
9099 };
9100 let layout = exps.expert_layout(ex);
9101 let id = BlockId::new(il, proj, ex as u16);
9102 let source = exps.expert_source(ex);
9103 e.with_moe_cache(max_block, |c, eng| {
9105 let slot = c.dispatch_source(id, source, eng)?;
9106 let DispatchSlot::Resident(sl) = slot;
9109 let buf = c.slot(sl);
9110 eng.qmatvec_view(
9111 buf,
9112 0..layout.len,
9113 x,
9114 1,
9115 exps.in_f,
9116 exps.out_f,
9117 layout.qtype,
9118 layout.row_bytes,
9119 )
9120 })
9121 }
9122
9123 fn moe_profile_admit_expert(
9127 e: &Engine,
9128 il: u16,
9129 ex: usize,
9130 m: &MoeWeights,
9131 max_block: usize,
9132 ) -> Result<(), Box<dyn std::error::Error>> {
9133 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
9134 e.with_moe_cache(max_block, |cache, eng| {
9135 for (proj, exps) in [
9136 (PROJ_GATE, &m.gate_exps),
9137 (PROJ_UP, &m.up_exps),
9138 (PROJ_DOWN, &m.down_exps),
9139 ] {
9140 let id = BlockId::new(il, proj, ex as u16);
9141 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
9142 }
9143 Ok(())
9144 })
9145 }
9146
9147 #[allow(clippy::too_many_arguments)]
9150 fn moe_frozen_gemm(
9151 e: &Engine,
9152 il: u16,
9153 proj: u8,
9154 ex: usize,
9155 m: &MoeWeights,
9156 max_block: usize,
9157 x: &cudarc::driver::CudaView<f32>,
9158 scratch: &mut Option<CudaSlice<u8>>,
9159 scratch_len: usize,
9160 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9161 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
9162 let exps = match proj {
9163 PROJ_GATE => &m.gate_exps,
9164 PROJ_UP => &m.up_exps,
9165 _ => &m.down_exps,
9166 };
9167 let layout = exps.expert_layout(ex);
9168 let id = BlockId::new(il, proj, ex as u16);
9169 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
9170 let Some(slot) = cache.resident(id) else {
9171 return Ok(None);
9172 };
9173 let buf = cache.slot(slot);
9174 Ok(Some(eng.qmatvec_view(
9175 buf,
9176 0..layout.len,
9177 x,
9178 1,
9179 exps.in_f,
9180 exps.out_f,
9181 layout.qtype,
9182 layout.row_bytes,
9183 )?))
9184 })? {
9185 return Ok(output);
9186 }
9187 if scratch.is_none() {
9188 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
9189 }
9190 let scratch = scratch.as_mut().unwrap();
9191 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
9192 e.qmatvec_view(
9193 scratch,
9194 0..layout.len,
9195 x,
9196 1,
9197 exps.in_f,
9198 exps.out_f,
9199 layout.qtype,
9200 layout.row_bytes,
9201 )
9202 }
9203
9204 fn moe_prefetch_expert(
9205 e: &Engine,
9206 il: u16,
9207 ex: usize,
9208 m: &MoeWeights,
9209 max_block: usize,
9210 keep: &[crate::moe_cache::BlockId],
9211 ) -> Result<(), Box<dyn std::error::Error>> {
9212 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
9213 e.with_moe_cache(max_block, |c, eng| {
9214 for (proj, exps) in [
9215 (PROJ_GATE, &m.gate_exps),
9216 (PROJ_UP, &m.up_exps),
9217 (PROJ_DOWN, &m.down_exps),
9218 ] {
9219 let id = BlockId::new(il, proj, ex as u16);
9220 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
9221 }
9222 Ok(())
9223 })
9224 }
9225
9226 fn moe_prefetch_disk_expert(
9229 e: &Engine,
9230 il: u16,
9231 ex: usize,
9232 m: &MoeWeights,
9233 max_block: usize,
9234 keep: &[crate::moe_cache::BlockId],
9235 ) -> Result<(), Box<dyn std::error::Error>> {
9236 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
9237 e.with_moe_cache(max_block, |c, eng| {
9238 for (proj, exps) in [
9239 (PROJ_GATE, &m.gate_exps),
9240 (PROJ_UP, &m.up_exps),
9241 (PROJ_DOWN, &m.down_exps),
9242 ] {
9243 let source = exps.expert_source(ex);
9244 if let crate::model::ExpertSource::Disk { .. } = &source {
9245 let id = BlockId::new(il, proj, ex as u16);
9246 let _ = c.prefetch_source(id, source, keep, eng)?;
9247 }
9248 }
9249 Ok(())
9250 })
9251 }
9252
9253 #[inline]
9254 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
9255 let _ = m.gate_exps.prefetch_expert_pages(ex);
9256 let _ = m.up_exps.prefetch_expert_pages(ex);
9257 let _ = m.down_exps.prefetch_expert_pages(ex);
9258 }
9259}
9260
9261impl HybridModel {
9278 #[allow(clippy::too_many_arguments)]
9282 fn moe_ffn_grouped_resident_q8(
9283 e: &Engine,
9284 m: &MoeWeights,
9285 z: &CudaSlice<f32>,
9286 t: usize,
9287 cfg: &ModelConfig,
9288 il: u16,
9289 sel_all: &[u32],
9290 w_all: &[f32],
9291 table: &CudaSlice<u64>,
9292 gu_il: bool,
9293 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9294 let moe = cfg.moe.as_ref().unwrap();
9295 let n_embd = cfg.n_embd as usize;
9296 let n_expert = moe.expert_count as usize;
9297 let n_used = moe.expert_used_count as usize;
9298 let n_ff_exp = moe.expert_ff_length as usize;
9299 let n_pairs = t * n_used;
9300 debug_assert_eq!(sel_all.len(), n_pairs);
9301 debug_assert_eq!(w_all.len(), n_pairs);
9302 debug_assert!(
9303 m.gate_exps.macros.is_none()
9304 && m.up_exps.macros.is_none()
9305 && m.down_exps.macros.is_none(),
9306 "resident grouped q8 does not fold per-expert macro scales",
9307 );
9308
9309 if !cfg.swiglu_clamped_at(il as u32) {
9315 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
9316 let sel_d = e.htod_i32(&sel)?;
9317 let w_d = e.htod(w_all)?;
9318 let (gate_row_bytes, up_row_bytes) = if gu_il {
9319 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
9320 (combined, combined)
9321 } else {
9322 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
9323 };
9324 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
9325 let act = e.moe_gate_up_silu8_dev_q8_rows(
9326 table,
9327 &sel_d,
9328 &zq,
9329 &zd,
9330 t,
9331 n_embd,
9332 n_ff_exp,
9333 n_used,
9334 n_expert,
9335 m.gate_exps.qtype,
9336 m.up_exps.qtype,
9337 gate_row_bytes,
9338 up_row_bytes,
9339 &m.dev_macros,
9340 )?;
9341 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
9342 let mut moe_out = e.uninit(t * n_embd)?;
9343 e.moe_down8_fma_dev_q8_rows_g(
9344 table,
9345 &sel_d,
9346 &w_d,
9347 &aq2,
9348 &ad2,
9349 &mut moe_out,
9350 t,
9351 n_ff_exp,
9352 n_embd,
9353 n_used,
9354 n_expert,
9355 m.down_exps.qtype,
9356 m.down_exps.row_bytes,
9357 )?;
9358
9359 if std::env::var("MEMRA_MOE_STATS").is_ok() {
9360 let mut counts = vec![0usize; n_expert];
9361 for &expert in sel_all {
9362 counts[expert as usize] += 1;
9363 }
9364 let mut sizes: Vec<usize> =
9365 counts.into_iter().filter(|&count| count != 0).collect();
9366 sizes.sort_unstable();
9367 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
9368 println!(
9369 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
9370 m_e: min={} median={} mean={mean:.1} max={}",
9371 sizes.len(),
9372 n_expert,
9373 sizes.first().copied().unwrap_or(0),
9374 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
9375 sizes.last().copied().unwrap_or(0),
9376 );
9377 }
9378 return Ok(moe_out);
9379 }
9380
9381 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
9385 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
9386 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
9387 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
9388
9389 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
9390 for (pair, &expert) in pair_ex.iter().enumerate() {
9391 by_expert[expert as usize].push(pair as i32);
9392 }
9393
9394 let pair_tok_d = e.htod_i32(&pair_tok)?;
9395 let pair_ex_d = e.htod_i32(&pair_ex)?;
9396 let pair_w_d = e.htod(w_all)?;
9397 let tok_off_d = e.htod_i32(&tok_off)?;
9398 let tok_ids_d = e.htod_i32(&tok_ids)?;
9399
9400 let matvec = |proj: i32,
9401 pair_rows: &CudaSlice<i32>,
9402 aq: &CudaSlice<i8>,
9403 ad: &CudaSlice<f32>,
9404 in_f: usize,
9405 out_f: usize,
9406 qtype: i32,
9407 row_bytes: usize|
9408 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9409 e.moe_pairs_matvec_q8(
9410 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
9411 row_bytes,
9412 )
9413 };
9414
9415 let (gate_row_bytes, up_row_bytes) = if gu_il {
9416 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
9417 (combined, combined)
9418 } else {
9419 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
9420 };
9421 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
9422 let gate = matvec(
9423 0,
9424 &pair_tok_d,
9425 &zq,
9426 &zd,
9427 n_embd,
9428 n_ff_exp,
9429 m.gate_exps.qtype,
9430 gate_row_bytes,
9431 )?;
9432 let up = matvec(
9433 1,
9434 &pair_tok_d,
9435 &zq,
9436 &zd,
9437 n_embd,
9438 n_ff_exp,
9439 m.up_exps.qtype,
9440 up_row_bytes,
9441 )?;
9442 let mut act = e.uninit(n_pairs * n_ff_exp)?;
9443 Self::ffn_act_lim(
9444 e,
9445 cfg,
9446 &gate,
9447 &up,
9448 1.0,
9449 1.0,
9450 cfg.clamp_exp_at(il as u32),
9451 &mut act,
9452 n_pairs * n_ff_exp,
9453 )?;
9454 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
9455 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
9456 let pair_self_d = e.htod_i32(&pair_self)?;
9457 let down = matvec(
9458 2,
9459 &pair_self_d,
9460 &aq2,
9461 &ad2,
9462 n_ff_exp,
9463 n_embd,
9464 m.down_exps.qtype,
9465 m.down_exps.row_bytes,
9466 )?;
9467 let mut moe_out = e.uninit(t * n_embd)?;
9468 e.moe_pairs_scatter(
9469 &down,
9470 &pair_w_d,
9471 &tok_off_d,
9472 &tok_ids_d,
9473 &mut moe_out,
9474 t,
9475 n_embd,
9476 )?;
9477
9478 if std::env::var("MEMRA_MOE_STATS").is_ok() {
9479 let mut sizes: Vec<usize> = by_expert
9480 .iter()
9481 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
9482 .collect();
9483 sizes.sort_unstable();
9484 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
9485 println!(
9486 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
9487 m_e: min={} median={} mean={mean:.1} max={}",
9488 sizes.len(),
9489 n_expert,
9490 sizes.first().copied().unwrap_or(0),
9491 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
9492 sizes.last().copied().unwrap_or(0),
9493 );
9494 }
9495 Ok(moe_out)
9496 }
9497
9498 #[allow(clippy::too_many_arguments)]
9504 fn shexp_split_matvec(
9505 e: &Engine,
9506 rank1: &Engine,
9507 wg: &CudaSlice<u8>,
9508 wu: &CudaSlice<u8>,
9509 wd: &CudaSlice<u8>,
9510 z: &CudaSlice<f32>,
9511 lim: Option<f32>,
9512 cfg: &ModelConfig,
9513 il: u16,
9514 n_embd: usize,
9515 n_ff_sh: usize,
9516 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
9517 use cudarc::driver::DevicePtr;
9518 if n_ff_sh % 2 != 0 || n_embd % 2 != 0 {
9519 return Ok(None);
9520 }
9521 let hf = n_ff_sh / 2;
9522 let nd = n_embd / 2;
9523 struct Rep {
9524 wg1: CudaSlice<u8>,
9525 wu1: CudaSlice<u8>,
9526 wd1: CudaSlice<u8>,
9527 }
9528 struct SplitWs {
9529 pin_dev: usize,
9530 gate0: CudaSlice<f32>,
9532 up0: CudaSlice<f32>,
9533 act: CudaSlice<f32>,
9534 sh_buf: CudaSlice<f32>,
9535 ev_z: cudarc::driver::CudaEvent,
9536 ev_act0: cudarc::driver::CudaEvent,
9537 z1: CudaSlice<f32>,
9539 g1: CudaSlice<f32>,
9540 u1: CudaSlice<f32>,
9541 a1h: CudaSlice<f32>,
9542 act1: CudaSlice<f32>,
9543 y1: CudaSlice<f32>,
9544 ev_act1: cudarc::driver::CudaEvent,
9545 ev_y1: cudarc::driver::CudaEvent,
9546 raw_act_e: u64,
9547 raw_sh_e: u64,
9548 raw_z1: u64,
9549 raw_a1h: u64,
9550 raw_act1: u64,
9551 raw_y1: u64,
9552 }
9553 static WS: std::sync::Mutex<Option<SplitWs>> = std::sync::Mutex::new(None);
9554 static REPS: std::sync::Mutex<Option<std::collections::HashMap<u64, Rep>>> =
9555 std::sync::Mutex::new(None);
9556 let mut guard = WS.lock().map_err(|_| "shexp split lock is poisoned")?;
9557 let mut reps_guard = REPS.lock().map_err(|_| "shexp reps lock is poisoned")?;
9558 let reps = reps_guard.get_or_insert_with(std::collections::HashMap::new);
9559 let pins = e.ctx().ordinal();
9560 if guard.as_ref().is_none_or(|w| w.pin_dev != pins) {
9561 let (gate0, up0, act, sh_buf, ev_z, ev_act0) = {
9562 let _m = e.gpu.enter_main()?;
9563 (
9564 e.htod(&vec![0.0f32; hf])?,
9565 e.htod(&vec![0.0f32; hf])?,
9566 e.htod(&vec![0.0f32; n_ff_sh])?,
9567 e.htod(&vec![0.0f32; n_embd])?,
9568 e.ctx().new_event(None)?,
9569 e.ctx().new_event(None)?,
9570 )
9571 };
9572 let (z1, g1, u1, a1h, act1, y1, ev_act1, ev_y1) = {
9573 let _r = rank1.gpu.enter_main()?;
9574 (
9575 rank1.htod(&vec![0.0f32; n_embd])?,
9576 rank1.htod(&vec![0.0f32; hf])?,
9577 rank1.htod(&vec![0.0f32; hf])?,
9578 rank1.htod(&vec![0.0f32; hf])?,
9579 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9580 rank1.htod(&vec![0.0f32; nd])?,
9581 rank1.ctx().new_event(None)?,
9582 rank1.ctx().new_event(None)?,
9583 )
9584 };
9585 let (raw_act_e, raw_sh_e) = {
9586 let _m = e.gpu.enter_main()?;
9587 let stream = e.stream();
9588 let (a, _g0) = act.device_ptr(&stream);
9589 let (b, _g1) = sh_buf.device_ptr(&stream);
9590 (a as u64, b as u64)
9591 };
9592 let (raw_z1, raw_a1h, raw_act1, raw_y1) = {
9593 let _r = rank1.gpu.enter_main()?;
9594 let rs = rank1.stream();
9595 let (a, _g0) = z1.device_ptr(&rs);
9596 let (b, _g1) = a1h.device_ptr(&rs);
9597 let (c, _g2) = act1.device_ptr(&rs);
9598 let (d, _g3) = y1.device_ptr(&rs);
9599 (a as u64, b as u64, c as u64, d as u64)
9600 };
9601 *guard = Some(SplitWs {
9602 pin_dev: pins,
9603 gate0,
9604 up0,
9605 act,
9606 sh_buf,
9607 ev_z,
9608 ev_act0,
9609 z1,
9610 g1,
9611 u1,
9612 a1h,
9613 act1,
9614 y1,
9615 ev_act1,
9616 ev_y1,
9617 raw_act_e,
9618 raw_sh_e,
9619 raw_z1,
9620 raw_a1h,
9621 raw_act1,
9622 raw_y1,
9623 });
9624 }
9625 let ws = guard.as_mut().expect("armed above");
9626 let wg_pin = {
9627 let _m = e.gpu.enter_main()?;
9628 let stream = e.stream();
9629 let (p, _g) = wg.device_ptr(&stream);
9630 p as u64
9631 };
9632 if !reps.contains_key(&wg_pin) {
9633 let mut up = |src: &CudaSlice<u8>,
9635 off_bytes: usize,
9636 len: usize|
9637 -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9638 use cudarc::driver::sys;
9639 let sptr = {
9640 let _m = e.gpu.enter_main()?;
9641 let stream = e.stream();
9642 let (p, _g) = src.device_ptr(&stream);
9643 p as u64 + off_bytes as u64
9644 };
9645 let dst = {
9646 let _r = rank1.gpu.enter_main()?;
9647 rank1.alloc_u8_uninit(len)?
9648 };
9649 let dptr = {
9650 let _r = rank1.gpu.enter_main()?;
9651 let rs = rank1.stream();
9652 let (p, _g) = dst.device_ptr(&rs);
9653 p as u64
9654 };
9655 let _r = rank1.gpu.enter_main()?;
9656 let r = unsafe {
9657 sys::cuMemcpyAsync(
9658 dptr as sys::CUdeviceptr,
9659 sptr as sys::CUdeviceptr,
9660 len,
9661 rank1.stream().cu_stream() as sys::CUstream,
9662 )
9663 };
9664 if r != sys::CUresult::CUDA_SUCCESS {
9665 return Err(format!("shexp split replica upload: {r:?}").into());
9666 }
9667 rank1.stream().synchronize()?;
9668 Ok(dst)
9669 };
9670 let wg1 = up(wg, hf * n_embd * 2, hf * n_embd * 2)?;
9671 let wu1 = up(wu, hf * n_embd * 2, hf * n_embd * 2)?;
9672 let wd1 = up(wd, nd * n_ff_sh * 2, nd * n_ff_sh * 2)?;
9673 reps.insert(wg_pin, Rep { wg1, wu1, wd1 });
9674 }
9675 let _ = il;
9676 let raw_z = {
9678 let _m = e.gpu.enter_main()?;
9679 let stream = e.stream();
9680 let (p, _g) = z.device_ptr(&stream);
9681 ws.ev_z.record(&stream)?;
9682 p as u64
9683 };
9684 {
9686 let rep = reps.get(&wg_pin).expect("uploaded above");
9687 let _r = rank1.gpu.enter_main()?;
9688 rank1.stream().wait(&ws.ev_z)?;
9689 crate::tp::raw_copy_bytes(ws.raw_z1, raw_z, n_embd * 4, rank1)?;
9690 let SplitWs {
9691 z1, g1, u1, a1h, ..
9692 } = &mut *ws;
9693 rank1.matvec_bf16_dual_into(&rep.wg1, &rep.wu1, z1, g1, u1, n_embd, hf)?;
9694 Self::ffn_act_lim(rank1, cfg, g1, u1, 1.0, 1.0, lim, a1h, hf)?;
9695 crate::tp::raw_copy_bytes(ws.raw_act1 + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9697 crate::tp::raw_copy_bytes(ws.raw_act_e + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9698 ws.ev_act1.record(&rank1.stream())?;
9699 }
9700 {
9702 let _m = e.gpu.enter_main()?;
9703 let SplitWs {
9704 gate0, up0, act, ..
9705 } = &mut *ws;
9706 let wg_lo = wg.slice(0..hf * n_embd * 2);
9707 let wu_lo = wu.slice(0..hf * n_embd * 2);
9708 e.matvec_bf16_dual_view_into(&wg_lo, &wu_lo, z, gate0, up0, n_embd, hf)?;
9709 Self::ffn_act_lim(e, cfg, gate0, up0, 1.0, 1.0, lim, act, hf)?;
9710 ws.ev_act0.record(&e.stream())?;
9711 }
9712 {
9714 let rep = reps.get(&wg_pin).expect("uploaded above");
9715 let _r = rank1.gpu.enter_main()?;
9716 rank1.stream().wait(&ws.ev_act0)?;
9717 crate::tp::raw_copy_bytes(ws.raw_act1, ws.raw_act_e, hf * 4, rank1)?;
9718 let SplitWs { act1, y1, .. } = &mut *ws;
9719 rank1.matvec_bf16_into(&rep.wd1, act1, y1, n_ff_sh, nd)?;
9720 crate::tp::raw_copy_bytes(ws.raw_sh_e + (nd * 4) as u64, ws.raw_y1, nd * 4, rank1)?;
9721 ws.ev_y1.record(&rank1.stream())?;
9722 }
9723 {
9725 let _m = e.gpu.enter_main()?;
9726 e.stream().wait(&ws.ev_act1)?;
9727 let SplitWs { act, sh_buf, .. } = &mut *ws;
9728 let wd_lo = wd.slice(0..nd * n_ff_sh * 2);
9729 e.matvec_bf16_view_into(&wd_lo, act, sh_buf, n_ff_sh, nd)?;
9730 e.stream().wait(&ws.ev_y1)?;
9731 let mut sh = e.uninit(n_embd)?;
9732 {
9733 let mut dst = sh.slice_mut(0..n_embd);
9734 e.stream()
9735 .memcpy_dtod(&ws.sh_buf.slice(0..n_embd), &mut dst)?;
9736 }
9737 Ok(Some(sh))
9738 }
9739 }
9740
9741 fn shexp_overlap_issue(
9748 e: &Engine,
9749 m: &MoeWeights,
9750 z: &CudaSlice<f32>,
9751 cfg: &ModelConfig,
9752 il: u16,
9753 n_embd: usize,
9754 ) -> Result<bool, Box<dyn std::error::Error>> {
9755 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9756 return Ok(false);
9757 }
9758 let (
9759 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9760 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9761 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9762 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9763 else {
9764 return Ok(false);
9765 };
9766 let n_ff_sh = m
9767 .gate_shexp
9768 .as_ref()
9769 .expect("matched Some above")
9770 .out_features();
9771 let lim = cfg.clamp_shexp_at(il as u32);
9772 let mut guard = SHEXP_OV_WS
9773 .lock()
9774 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9775 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9776 if guard
9777 .as_ref()
9778 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9779 {
9780 *guard = Some((
9781 pins.0,
9782 pins.1,
9783 pins.2,
9784 e.uninit(n_ff_sh)?,
9785 e.uninit(n_embd)?,
9786 ));
9787 }
9788 let (_, _, _, act, sh) = guard.as_mut().expect("armed above");
9789 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9790 e.matvec_bf16_into(wd, act, sh, n_ff_sh, n_embd)?;
9791 drop(guard);
9792 Ok(true)
9793 }
9794
9795 #[allow(clippy::too_many_arguments)]
9801 fn shexp_dev1_issue(
9802 e: &Engine,
9803 rank1: &Engine,
9804 m: &MoeWeights,
9805 z: &CudaSlice<f32>,
9806 cfg: &ModelConfig,
9807 il: u16,
9808 n_embd: usize,
9809 ) -> Result<bool, Box<dyn std::error::Error>> {
9810 use cudarc::driver::DevicePtr;
9811 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9812 return Ok(false);
9813 }
9814 let (
9815 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9816 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9817 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9818 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9819 else {
9820 return Ok(false);
9821 };
9822 let n_ff_sh = m
9823 .gate_shexp
9824 .as_ref()
9825 .expect("matched Some above")
9826 .out_features();
9827 let lim = cfg.clamp_shexp_at(il as u32);
9828 let mut ws_guard = SHEXP_D1_WS
9830 .lock()
9831 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9832 if ws_guard
9833 .as_ref()
9834 .is_none_or(|(k, ..)| *k != (n_embd, n_ff_sh))
9835 {
9836 let (act1, z1, ev_done) = {
9837 let _r1 = rank1.gpu.enter_main()?;
9838 (
9839 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9840 rank1.htod(&vec![0.0f32; n_embd])?,
9841 rank1.ctx().new_event(None)?,
9842 )
9843 };
9844 let (sh_root, ev_z) = {
9845 let _main = e.gpu.enter_main()?;
9846 (e.htod(&vec![0.0f32; n_embd])?, e.ctx().new_event(None)?)
9847 };
9848 *ws_guard = Some(((n_embd, n_ff_sh), act1, z1, sh_root, ev_z, ev_done));
9849 }
9850 let mut reps_guard = SHEXP_D1_REPS
9852 .lock()
9853 .map_err(|_| "shexp dev1 replica lock is poisoned")?;
9854 let reps = reps_guard.get_or_insert_with(Default::default);
9855 if !reps.contains_key(&il) {
9856 let (wg1, wu1, wd1) = {
9857 let _r1 = rank1.gpu.enter_main()?;
9858 (
9859 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9860 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9861 rank1.alloc_u8_uninit(n_embd * n_ff_sh * 2)?,
9862 )
9863 };
9864 for (src, dst) in [(wg, &wg1), (wu, &wu1), (wd, &wd1)] {
9865 let s_ptr = {
9866 let _main = e.gpu.enter_main()?;
9867 let stream = e.stream();
9868 let (p, _g) = src.device_ptr(&stream);
9869 p as u64
9870 };
9871 let d_ptr = {
9872 let _r1 = rank1.gpu.enter_main()?;
9873 let stream = rank1.stream();
9874 let (p, _g) = dst.device_ptr(&stream);
9875 p as u64
9876 };
9877 let _r1 = rank1.gpu.enter_main()?;
9878 crate::tp::raw_copy_bytes(d_ptr, s_ptr, src.len(), rank1)?;
9879 }
9880 {
9881 let _r1 = rank1.gpu.enter_main()?;
9882 rank1.stream().synchronize()?;
9883 }
9884 reps.insert(il, (wg1, wu1, wd1));
9885 }
9886 let (wg1, wu1, wd1) = reps.get(&il).expect("armed above");
9887 let (_, act1, z1, sh_root, ev_z, ev_done) = ws_guard.as_mut().expect("armed above");
9888 let (raw_z, raw_sh) = {
9891 let _main = e.gpu.enter_main()?;
9892 let stream = e.stream();
9893 let (a, _g0) = z.device_ptr(&stream);
9894 let (b, _g1) = sh_root.device_ptr(&stream);
9895 ev_z.record(&stream)?;
9896 (a as u64, b as u64)
9897 };
9898 {
9899 let _r1 = rank1.gpu.enter_main()?;
9900 rank1.stream().wait(ev_z)?;
9901 let raw_z1 = {
9902 let stream = rank1.stream();
9903 let (p, _g) = z1.device_ptr(&stream);
9904 p as u64
9905 };
9906 crate::tp::raw_copy_bytes(raw_z1, raw_z, n_embd * 4, rank1)?;
9907 rank1.matvec_bf16_dual_silu_into(wg1, wu1, z1, act1, n_embd, n_ff_sh, lim)?;
9908 rank1.matvec_bf16_raw_out(wd1, act1, raw_sh, n_ff_sh, n_embd)?;
9912 ev_done.record(&rank1.stream())?;
9913 }
9914 Ok(true)
9915 }
9916
9917 fn shexp_dev1_apply(
9919 e: &Engine,
9920 output: &mut CudaSlice<f32>,
9921 n_embd: usize,
9922 ) -> Result<(), Box<dyn std::error::Error>> {
9923 let guard = SHEXP_D1_WS
9924 .lock()
9925 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9926 let (pin, _, _, sh_root, _, ev_done) =
9927 guard.as_ref().ok_or("shexp dev1 apply without issue")?;
9928 if pin.0 != n_embd {
9929 return Err("shexp dev1 width drifted".into());
9930 }
9931 let _main = e.gpu.enter_main()?;
9932 e.stream().wait(ev_done)?;
9933 static ONES_D1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9934 std::sync::Mutex::new(None);
9935 let mut og = ONES_D1.lock().map_err(|_| "ones lock is poisoned")?;
9936 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9937 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9938 }
9939 let ones = &og.as_ref().expect("armed above").1;
9940 e.add_scaled_rows(sh_root, ones, output, n_embd, 1)?;
9941 Ok(())
9942 }
9943
9944 fn shexp_overlap_tail_ptrs(
9948 e: &Engine,
9949 m: &MoeWeights,
9950 cfg: &ModelConfig,
9951 n_embd: usize,
9952 ) -> Result<Option<(u64, u64)>, Box<dyn std::error::Error>> {
9953 use cudarc::driver::DevicePtr;
9954 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9955 return Ok(None);
9956 }
9957 let (
9958 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9959 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9960 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9961 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9962 else {
9963 return Ok(None);
9964 };
9965 let n_ff_sh = m
9966 .gate_shexp
9967 .as_ref()
9968 .expect("matched Some above")
9969 .out_features();
9970 let mut guard = SHEXP_OV_WS
9971 .lock()
9972 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9973 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9974 if guard
9975 .as_ref()
9976 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9977 {
9978 *guard = Some((
9979 pins.0,
9980 pins.1,
9981 pins.2,
9982 e.uninit(n_ff_sh)?,
9983 e.uninit(n_embd)?,
9984 ));
9985 }
9986 let sh_raw = {
9987 let (_, _, _, _, sh) = guard.as_ref().expect("armed above");
9988 let stream = e.stream();
9989 let (p, _g) = sh.device_ptr(&stream);
9990 p as u64
9991 };
9992 static ONES_T3: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9993 std::sync::Mutex::new(None);
9994 let mut og = ONES_T3.lock().map_err(|_| "ones lock is poisoned")?;
9995 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9996 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9997 }
9998 let ones_raw = {
9999 let stream = e.stream();
10000 let (p, _g) = og.as_ref().expect("armed above").1.device_ptr(&stream);
10001 p as u64
10002 };
10003 Ok(Some((sh_raw, ones_raw)))
10004 }
10005
10006 fn shexp_overlap_apply(
10009 e: &Engine,
10010 output: &mut CudaSlice<f32>,
10011 n_embd: usize,
10012 ) -> Result<(), Box<dyn std::error::Error>> {
10013 let guard = SHEXP_OV_WS
10014 .lock()
10015 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
10016 let (_, ne, _, _, sh) = guard.as_ref().ok_or("shexp overlap apply without issue")?;
10017 if *ne != n_embd {
10018 return Err("shexp overlap width drifted".into());
10019 }
10020 static ONES_OV: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
10021 std::sync::Mutex::new(None);
10022 let mut og = ONES_OV.lock().map_err(|_| "ones lock is poisoned")?;
10023 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
10024 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
10025 }
10026 let ones = &og.as_ref().expect("armed above").1;
10027 e.add_scaled_rows(sh, ones, output, n_embd, 1)?;
10028 Ok(())
10029 }
10030
10031 fn moe_ffn_grouped_add_shared(
10032 e: &Engine,
10033 m: &MoeWeights,
10034 z: &CudaSlice<f32>,
10035 t: usize,
10036 cfg: &ModelConfig,
10037 il: u16,
10038 moe_out: &mut CudaSlice<f32>,
10039 ) -> Result<(), Box<dyn std::error::Error>> {
10040 static SHEXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10043 static SHEXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10044 let shexp_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10045 let shexp_started = shexp_timing.then(std::time::Instant::now);
10046 let result = Self::moe_ffn_grouped_add_shared_inner(e, m, z, t, cfg, il, moe_out);
10047 if let Some(started) = shexp_started {
10048 use std::sync::atomic::Ordering;
10049 e.stream().synchronize()?;
10050 let ns = SHEXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10051 + started.elapsed().as_nanos() as u64;
10052 let calls = SHEXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10053 if calls % 430 == 0 {
10054 eprintln!(
10055 "[moe-shexp-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10056 ns as f64 / 1.0e6,
10057 ns as f64 / calls as f64 / 1.0e3,
10058 );
10059 }
10060 }
10061 result
10062 }
10063
10064 #[allow(clippy::too_many_arguments)]
10065 fn moe_ffn_grouped_add_shared_inner(
10066 e: &Engine,
10067 m: &MoeWeights,
10068 z: &CudaSlice<f32>,
10069 t: usize,
10070 cfg: &ModelConfig,
10071 il: u16,
10072 moe_out: &mut CudaSlice<f32>,
10073 ) -> Result<(), Box<dyn std::error::Error>> {
10074 let n_embd = cfg.n_embd as usize;
10075 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
10076 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
10077 {
10078 let n_ff_sh = gate_shexp.out_features();
10079 let lim = cfg.clamp_shexp_at(il as u32);
10080 let fused = t == 1
10087 && lim.is_none()
10088 && cfg.m3.is_none()
10089 && e.uses_q8_1_fast(gate_shexp)
10090 && e.uses_q8_1_fast(up_shexp);
10091 let bf16_dual = if t == 1 && crate::Engine::bf16_mmv_on() && n_embd % 8 == 0 {
10094 match (gate_shexp, up_shexp) {
10095 (
10096 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
10097 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
10098 ) => Some((wg, wu)),
10099 _ => None,
10100 }
10101 } else {
10102 None
10103 };
10104 let sh = if let Some((wg, wu)) = bf16_dual {
10105 static SHEXP_WS: std::sync::Mutex<
10109 Option<(
10110 usize,
10111 usize,
10112 usize,
10113 CudaSlice<f32>,
10114 CudaSlice<f32>,
10115 CudaSlice<f32>,
10116 CudaSlice<f32>,
10117 )>,
10118 > = std::sync::Mutex::new(None);
10119 let down_bf16 = match down_shexp {
10120 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
10121 _ => None,
10122 };
10123 let mut guard = SHEXP_WS
10124 .lock()
10125 .map_err(|_| "shexp workspace lock is poisoned")?;
10126 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
10127 if guard
10128 .as_ref()
10129 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
10130 {
10131 *guard = Some((
10132 pins.0,
10133 pins.1,
10134 pins.2,
10135 e.uninit(n_ff_sh)?,
10136 e.uninit(n_ff_sh)?,
10137 e.uninit(n_ff_sh)?,
10138 e.uninit(n_embd)?,
10139 ));
10140 }
10141 {
10144 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10145 let split_on = *ON
10146 .get_or_init(|| std::env::var("MEMRA_SHEXP_SPLIT").as_deref() == Ok("1"));
10147 if split_on {
10148 if let (Some(wd), Some(rank1)) = (
10149 match down_shexp {
10150 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
10151 _ => None,
10152 },
10153 m.step_tp.as_ref().and_then(|st| st.runtime.rank_engine(1)),
10154 ) {
10155 if let Some(sh) = Self::shexp_split_matvec(
10156 e, rank1, wg, wu, wd, z, lim, cfg, il, n_embd, n_ff_sh,
10157 )? {
10158 drop(guard);
10159 let gate = match &m.gate_inp_shexp {
10160 Some(gate_inp_shexp) => e.sigmoid_dot_rows(
10161 z,
10162 gate_inp_shexp.float_data(),
10163 n_embd,
10164 t,
10165 )?,
10166 None => e.htod(&vec![1.0f32; t])?,
10167 };
10168 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
10169 return Ok(());
10170 }
10171 }
10172 }
10173 }
10174 let (_, _, _, gate, up, act, sh_buf) =
10175 guard.as_mut().expect("shexp workspace initialized above");
10176 if cfg.m3.is_none() {
10177 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
10180 let _ = (&gate, &up);
10181 } else {
10182 e.matvec_bf16_dual_into(wg, wu, z, gate, up, n_embd, n_ff_sh)?;
10183 Self::ffn_act_lim(e, cfg, gate, up, 1.0, 1.0, lim, act, n_ff_sh)?;
10184 }
10185 if let Some(down) = down_bf16 {
10186 static FUSE_DA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10193 let fuse_da = *FUSE_DA.get_or_init(|| {
10194 std::env::var("MEMRA_FUSE_DOWN_ADDSCALE").as_deref() != Ok("0")
10195 });
10196 if fuse_da && m.gate_inp_shexp.is_none() {
10197 static ONES1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
10198 std::sync::Mutex::new(None);
10199 let mut og = ONES1.lock().map_err(|_| "shexp ones lock is poisoned")?;
10200 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
10201 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
10202 }
10203 let ones = &og.as_ref().expect("armed above").1;
10204 e.matvec_bf16_down_addscale_into(
10205 down, act, ones, moe_out, n_ff_sh, n_embd,
10206 )?;
10207 return Ok(());
10208 }
10209 e.matvec_bf16_into(down, act, sh_buf, n_ff_sh, n_embd)?;
10210 let sh = e.uninit(n_embd)?;
10211 let mut sh = sh;
10213 {
10214 let mut dst = sh.slice_mut(0..n_embd);
10215 e.stream().memcpy_dtod(&sh_buf.slice(0..n_embd), &mut dst)?;
10216 }
10217 sh
10218 } else {
10219 e.matmul(down_shexp, act, 1)?
10220 }
10221 } else if fused {
10222 let (zq, zd) = e.quantize_q8_1(z, 1, n_embd)?;
10223 let pair = match e.matmul_pre_dual_noscale(gate_shexp, up_shexp, &zq, &zd, 1)? {
10224 Some((gate, up)) => Some((gate, up)),
10225 None => {
10226 match (
10227 e.matmul_pre_noscale(gate_shexp, &zq, &zd, 1)?,
10228 e.matmul_pre_noscale(up_shexp, &zq, &zd, 1)?,
10229 ) {
10230 (Some(gate), Some(up)) => Some((gate, up)),
10231 _ => None,
10232 }
10233 }
10234 };
10235 match pair {
10236 Some(((gate, gs), (up, us))) => {
10237 if e.uses_q8_1_fast(down_shexp) {
10238 let (aq, ad) = e.silu_mul_scaled_q8_1(&gate, &up, gs, us, n_ff_sh)?;
10239 e.matmul_pre(down_shexp, &aq, &ad, &gate, 1)?
10240 } else {
10241 let mut act = e.uninit(n_ff_sh)?;
10242 e.silu_mul_scaled(&gate, &up, gs, us, &mut act, n_ff_sh)?;
10243 e.matmul(down_shexp, &act, 1)?
10244 }
10245 }
10246 None => {
10247 let gate = e.matmul_pre(gate_shexp, &zq, &zd, z, 1)?;
10248 let up = e.matmul_pre(up_shexp, &zq, &zd, z, 1)?;
10249 let mut act = e.uninit(n_ff_sh)?;
10250 Self::ffn_act(e, cfg, &gate, &up, &mut act, n_ff_sh)?;
10251 e.matmul(down_shexp, &act, 1)?
10252 }
10253 }
10254 } else {
10255 let sg_gate = e.matmul(gate_shexp, z, t)?;
10256 let sg_up = e.matmul(up_shexp, z, t)?;
10257 let mut sa = e.uninit(t * n_ff_sh)?;
10258 Self::ffn_act_lim(
10259 e,
10260 cfg,
10261 &sg_gate,
10262 &sg_up,
10263 1.0,
10264 1.0,
10265 lim,
10266 &mut sa,
10267 t * n_ff_sh,
10268 )?;
10269 e.matmul(down_shexp, &sa, t)?
10270 };
10271 let gate = match &m.gate_inp_shexp {
10272 Some(gate_inp_shexp) => {
10273 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
10274 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
10275 } else {
10276 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
10277 let mut gate = e.uninit(t)?;
10278 e.sigmoid(&raw, &mut gate, t)?;
10279 gate
10280 }
10281 }
10282 None if t == 1 => {
10287 static ONES: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
10288 std::sync::Mutex::new(None);
10289 let mut guard = ONES.lock().map_err(|_| "shexp ones lock is poisoned")?;
10290 if guard.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
10291 *guard = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
10292 }
10293 let ones = &guard.as_ref().expect("armed above").1;
10294 e.add_scaled_rows(&sh, ones, moe_out, n_embd, t)?;
10295 return Ok(());
10296 }
10297 None => e.htod(&vec![1.0f32; t])?,
10298 };
10299 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
10300 }
10301 Ok(())
10302 }
10303
10304 pub(crate) fn moe_ffn_grouped(
10307 e: &Engine,
10308 m: &MoeWeights,
10309 z: &CudaSlice<f32>,
10310 t: usize,
10311 cfg: &ModelConfig,
10312 il: u16,
10313 max_block: usize,
10314 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10315 let moe = cfg.moe.as_ref().unwrap();
10316 let n_embd = cfg.n_embd as usize;
10317 let n_expert = moe.expert_count as usize;
10318 let n_used = moe.expert_used_count as usize;
10319 let n_ff_exp = moe.expert_ff_length as usize;
10320 let lim_exp = cfg.clamp_exp_at(il as u32);
10322
10323 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
10327 if let Some(sig) = cfg.sigmoid_router() {
10328 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
10329 }
10330 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10331 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
10332 } else {
10333 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
10334 };
10335 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
10336 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
10337 Self::trace_moe_input(e, il, t, n_embd, z)?;
10338
10339 let no_exp_macros = m.gate_exps.macros.is_none()
10344 && m.up_exps.macros.is_none()
10345 && m.down_exps.macros.is_none();
10346 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
10347 m.has_uniform_expert_layout()
10348 && no_exp_macros
10349 && moe_q8_enabled()
10350 && q8_expert_supported(m.gate_exps.qtype)
10351 && q8_expert_supported(m.up_exps.qtype)
10352 && q8_expert_supported(m.down_exps.qtype)
10353 && moe_slab_enabled()
10354 && dev.dev == e.ctx().ordinal()
10355 });
10356 if let Some(dev) = resident_q8 {
10357 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
10358 e,
10359 m,
10360 z,
10361 t,
10362 cfg,
10363 il,
10364 &sel_all,
10365 &w_all,
10366 &dev.ptr_row,
10367 dev.gu_il,
10368 )?;
10369 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10370 return Ok(moe_out);
10371 }
10372
10373 struct ExpertGroup {
10377 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
10381 let mut groups: Vec<ExpertGroup> = (0..n_expert)
10382 .map(|_| ExpertGroup {
10383 tok_indices: Vec::new(),
10384 slot_indices: Vec::new(),
10385 weights: Vec::new(),
10386 })
10387 .collect();
10388
10389 for tok in 0..t {
10390 for j in 0..n_used {
10391 let ex = sel_all[tok * n_used + j] as usize;
10392 let w = w_all[tok * n_used + j];
10393 groups[ex].tok_indices.push(tok as i32);
10394 groups[ex].slot_indices.push(j as i32);
10395 groups[ex].weights.push(w);
10396 }
10397 }
10398
10399 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
10402 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
10406 let u_len = m.up_exps.max_expert_bytes();
10407 let d_len = m.down_exps.max_expert_bytes();
10408 let moe_q8 = m.has_uniform_expert_layout()
10409 && moe_q8_enabled()
10410 && q8_expert_supported(m.gate_exps.qtype)
10411 && q8_expert_supported(m.up_exps.qtype)
10412 && q8_expert_supported(m.down_exps.qtype);
10413 let slab_local = m
10416 .dev_exps
10417 .as_ref()
10418 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
10419 let use_cache =
10420 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
10421 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
10424
10425 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
10427 (
10428 Some(e.alloc_u8(g_len)?),
10429 Some(e.alloc_u8(u_len)?),
10430 Some(e.alloc_u8(d_len)?),
10431 )
10432 } else {
10433 (None, None, None)
10434 };
10435
10436 let mut order: Vec<usize> = (0..n_expert)
10447 .filter(|&ex| !groups[ex].tok_indices.is_empty())
10448 .collect();
10449 order.sort_by(|&a, &b| {
10450 groups[b]
10451 .tok_indices
10452 .len()
10453 .cmp(&groups[a].tok_indices.len())
10454 .then(a.cmp(&b))
10455 });
10456 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
10458 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
10459 if worker_disk_prefetch {
10460 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
10461 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
10462 }
10463 }
10464 for (order_pos, &ex) in order.iter().enumerate() {
10465 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
10466 Self::moe_prefetch_host_expert(order[next], m);
10467 }
10468 if worker_disk_prefetch {
10469 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
10470 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10471 let keep = [
10472 BlockId::new(il, PROJ_GATE, ex as u16),
10473 BlockId::new(il, PROJ_UP, ex as u16),
10474 BlockId::new(il, PROJ_DOWN, ex as u16),
10475 ];
10476 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
10477 }
10478 }
10479 let grp = &groups[ex];
10480 let m_e = grp.tok_indices.len();
10481 m_dist.push(m_e);
10482 let gl = m.gate_exps.expert_layout(ex);
10483 let ul = m.up_exps.expert_layout(ex);
10484 let dl = m.down_exps.expert_layout(ex);
10485
10486 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
10490 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
10491 let dmac = m.down_exps.macro_scale(ex);
10492 let weight_d = if dmac == 1.0 {
10493 e.htod(&grp.weights)?
10494 } else {
10495 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
10496 e.htod(&scaled)?
10497 };
10498
10499 let mut gathered = e.zeros(m_e * n_embd)?;
10501 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
10502 let gv = gathered.slice(0..m_e * n_embd);
10503
10504 let y = if let Some(dev) = slab_local {
10507 let gate_start = ex * m.gate_exps.expert_stride;
10508 let up_start = ex * m.up_exps.expert_stride;
10509 let down_start = ex * m.down_exps.expert_stride;
10510 if grouped_q8 {
10511 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10512 let gate = e.qmatvec_expert_q8(
10513 &dev.gate,
10514 gate_start..gate_start + gl.len,
10515 &zq,
10516 &zd,
10517 m_e,
10518 m.gate_exps.in_f,
10519 m.gate_exps.out_f,
10520 gl.qtype,
10521 gl.row_bytes,
10522 )?;
10523 let up = e.qmatvec_expert_q8(
10524 &dev.up,
10525 up_start..up_start + ul.len,
10526 &zq,
10527 &zd,
10528 m_e,
10529 m.up_exps.in_f,
10530 m.up_exps.out_f,
10531 ul.qtype,
10532 ul.row_bytes,
10533 )?;
10534 let mut act = e.uninit(m_e * n_ff_exp)?;
10535 Self::ffn_act_lim(
10536 e,
10537 cfg,
10538 &gate,
10539 &up,
10540 m.gate_exps.macro_scale(ex),
10541 m.up_exps.macro_scale(ex),
10542 lim_exp,
10543 &mut act,
10544 m_e * n_ff_exp,
10545 )?;
10546 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10547 e.qmatvec_expert_q8(
10548 &dev.down,
10549 down_start..down_start + dl.len,
10550 &aq2,
10551 &ad2,
10552 m_e,
10553 m.down_exps.in_f,
10554 m.down_exps.out_f,
10555 dl.qtype,
10556 dl.row_bytes,
10557 )?
10558 } else {
10559 let gate = e.qmatvec_view(
10560 &dev.gate,
10561 gate_start..gate_start + gl.len,
10562 &gv,
10563 m_e,
10564 m.gate_exps.in_f,
10565 m.gate_exps.out_f,
10566 gl.qtype,
10567 gl.row_bytes,
10568 )?;
10569 let up = e.qmatvec_view(
10570 &dev.up,
10571 up_start..up_start + ul.len,
10572 &gv,
10573 m_e,
10574 m.up_exps.in_f,
10575 m.up_exps.out_f,
10576 ul.qtype,
10577 ul.row_bytes,
10578 )?;
10579 let mut act = e.uninit(m_e * n_ff_exp)?;
10580 Self::ffn_act_lim(
10581 e,
10582 cfg,
10583 &gate,
10584 &up,
10585 m.gate_exps.macro_scale(ex),
10586 m.up_exps.macro_scale(ex),
10587 lim_exp,
10588 &mut act,
10589 m_e * n_ff_exp,
10590 )?;
10591 let actv = act.slice(0..m_e * n_ff_exp);
10592 e.qmatvec_view(
10593 &dev.down,
10594 down_start..down_start + dl.len,
10595 &actv,
10596 m_e,
10597 m.down_exps.in_f,
10598 m.down_exps.out_f,
10599 dl.qtype,
10600 dl.row_bytes,
10601 )?
10602 }
10603 } else if use_cache {
10604 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10605 if grouped_q8 {
10606 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10607 let gate = e.with_moe_cache(max_block, |cache, eng| {
10608 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10609 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10610 eng.qmatvec_expert_q8(
10611 cache.buf(slot),
10612 0..gl.len,
10613 &zq,
10614 &zd,
10615 m_e,
10616 m.gate_exps.in_f,
10617 m.gate_exps.out_f,
10618 gl.qtype,
10619 gl.row_bytes,
10620 )
10621 })?;
10622 let up = e.with_moe_cache(max_block, |cache, eng| {
10623 let id = BlockId::new(il, PROJ_UP, ex as u16);
10624 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10625 eng.qmatvec_expert_q8(
10626 cache.buf(slot),
10627 0..ul.len,
10628 &zq,
10629 &zd,
10630 m_e,
10631 m.up_exps.in_f,
10632 m.up_exps.out_f,
10633 ul.qtype,
10634 ul.row_bytes,
10635 )
10636 })?;
10637 let mut act = e.uninit(m_e * n_ff_exp)?;
10638 Self::ffn_act_lim(
10639 e,
10640 cfg,
10641 &gate,
10642 &up,
10643 m.gate_exps.macro_scale(ex),
10644 m.up_exps.macro_scale(ex),
10645 lim_exp,
10646 &mut act,
10647 m_e * n_ff_exp,
10648 )?;
10649 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10650 e.with_moe_cache(max_block, |cache, eng| {
10651 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10652 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10653 eng.qmatvec_expert_q8(
10654 cache.buf(slot),
10655 0..dl.len,
10656 &aq2,
10657 &ad2,
10658 m_e,
10659 m.down_exps.in_f,
10660 m.down_exps.out_f,
10661 dl.qtype,
10662 dl.row_bytes,
10663 )
10664 })?
10665 } else {
10666 let gate = e.with_moe_cache(max_block, |cache, eng| {
10667 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10668 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10669 eng.qmatvec_view(
10670 cache.buf(slot),
10671 0..gl.len,
10672 &gv,
10673 m_e,
10674 m.gate_exps.in_f,
10675 m.gate_exps.out_f,
10676 gl.qtype,
10677 gl.row_bytes,
10678 )
10679 })?;
10680 let up = e.with_moe_cache(max_block, |cache, eng| {
10681 let id = BlockId::new(il, PROJ_UP, ex as u16);
10682 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10683 eng.qmatvec_view(
10684 cache.buf(slot),
10685 0..ul.len,
10686 &gv,
10687 m_e,
10688 m.up_exps.in_f,
10689 m.up_exps.out_f,
10690 ul.qtype,
10691 ul.row_bytes,
10692 )
10693 })?;
10694 let mut act = e.uninit(m_e * n_ff_exp)?;
10695 Self::ffn_act_lim(
10696 e,
10697 cfg,
10698 &gate,
10699 &up,
10700 m.gate_exps.macro_scale(ex),
10701 m.up_exps.macro_scale(ex),
10702 lim_exp,
10703 &mut act,
10704 m_e * n_ff_exp,
10705 )?;
10706 let actv = act.slice(0..m_e * n_ff_exp);
10707 e.with_moe_cache(max_block, |cache, eng| {
10708 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10709 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10710 eng.qmatvec_view(
10711 cache.buf(slot),
10712 0..dl.len,
10713 &actv,
10714 m_e,
10715 m.down_exps.in_f,
10716 m.down_exps.out_f,
10717 dl.qtype,
10718 dl.row_bytes,
10719 )
10720 })?
10721 }
10722 } else {
10723 let sg = scratch_g.as_mut().unwrap();
10724 let su = scratch_u.as_mut().unwrap();
10725 let sd = scratch_d.as_mut().unwrap();
10726 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
10727 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
10728 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
10729 if grouped_q8 {
10730 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10731 let gate = e.qmatvec_expert_q8(
10732 sg,
10733 0..gl.len,
10734 &zq,
10735 &zd,
10736 m_e,
10737 m.gate_exps.in_f,
10738 m.gate_exps.out_f,
10739 gl.qtype,
10740 gl.row_bytes,
10741 )?;
10742 let up = e.qmatvec_expert_q8(
10743 su,
10744 0..ul.len,
10745 &zq,
10746 &zd,
10747 m_e,
10748 m.up_exps.in_f,
10749 m.up_exps.out_f,
10750 ul.qtype,
10751 ul.row_bytes,
10752 )?;
10753 let mut act = e.uninit(m_e * n_ff_exp)?;
10754 Self::ffn_act_lim(
10755 e,
10756 cfg,
10757 &gate,
10758 &up,
10759 m.gate_exps.macro_scale(ex),
10760 m.up_exps.macro_scale(ex),
10761 lim_exp,
10762 &mut act,
10763 m_e * n_ff_exp,
10764 )?;
10765 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10766 e.qmatvec_expert_q8(
10767 sd,
10768 0..dl.len,
10769 &aq2,
10770 &ad2,
10771 m_e,
10772 m.down_exps.in_f,
10773 m.down_exps.out_f,
10774 dl.qtype,
10775 dl.row_bytes,
10776 )?
10777 } else {
10778 let gate = e.qmatvec_view(
10779 sg,
10780 0..gl.len,
10781 &gv,
10782 m_e,
10783 m.gate_exps.in_f,
10784 m.gate_exps.out_f,
10785 gl.qtype,
10786 gl.row_bytes,
10787 )?;
10788 let up = e.qmatvec_view(
10789 su,
10790 0..ul.len,
10791 &gv,
10792 m_e,
10793 m.up_exps.in_f,
10794 m.up_exps.out_f,
10795 ul.qtype,
10796 ul.row_bytes,
10797 )?;
10798 let mut act = e.uninit(m_e * n_ff_exp)?;
10799 Self::ffn_act_lim(
10800 e,
10801 cfg,
10802 &gate,
10803 &up,
10804 m.gate_exps.macro_scale(ex),
10805 m.up_exps.macro_scale(ex),
10806 lim_exp,
10807 &mut act,
10808 m_e * n_ff_exp,
10809 )?;
10810 let actv = act.slice(0..m_e * n_ff_exp);
10811 e.qmatvec_view(
10812 sd,
10813 0..dl.len,
10814 &actv,
10815 m_e,
10816 m.down_exps.in_f,
10817 m.down_exps.out_f,
10818 dl.qtype,
10819 dl.row_bytes,
10820 )?
10821 }
10822 };
10823
10824 e.scatter_slot(
10826 &y,
10827 &tok_idx_d,
10828 &slot_idx_d,
10829 &weight_d,
10830 &mut slot_buf,
10831 &mut wbuf,
10832 n_embd,
10833 n_used,
10834 m_e,
10835 )?;
10836 }
10837
10838 let mut moe_out = e.zeros(t * n_embd)?;
10840 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
10841
10842 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
10844 m_dist.sort_unstable();
10845 let active = m_dist.len();
10846 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
10847 let median = m_dist[active / 2];
10848 let max_m = *m_dist.last().unwrap();
10849 let min_m = m_dist[0];
10850 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
10851 println!(
10852 "moe-grouped il={il} t={t} active={active}/{n_expert} \
10853 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
10854 above_gemm_threshold(>=16)={above16}/{active}"
10855 );
10856 }
10857
10858 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10859 Ok(moe_out)
10860 }
10861
10862 pub(crate) fn moe_ffn_lockstep(
10869 &self,
10870 e: &Engine,
10871 m: &MoeWeights,
10872 zbatch: &CudaSlice<f32>,
10873 mrows: usize,
10874 il: u16,
10875 max_block: usize,
10876 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10877 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10878 let cfg = &self.cfg;
10879 let moe = cfg.moe.as_ref().unwrap();
10880 let n_embd = cfg.n_embd as usize;
10881 let n_expert = moe.expert_count as usize;
10882 let n_used = moe.expert_used_count as usize;
10883 let n_ff_exp = moe.expert_ff_length as usize;
10884 let lim_exp = cfg.clamp_exp_at(il as u32);
10886 let lim_shexp = cfg.clamp_shexp_at(il as u32);
10887
10888 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
10889 if let Some(sig) = cfg.sigmoid_router() {
10890 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
10891 }
10892 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10893 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
10894 } else {
10895 Self::moe_route_cfg(
10896 e,
10897 &logits,
10898 mrows,
10899 n_expert,
10900 n_used,
10901 m.active_experts.as_deref(),
10902 )?
10903 };
10904 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
10905
10906 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
10908 Ok((0..n_expert)
10909 .map(|ex| {
10910 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
10911 .into_iter()
10912 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
10913 })
10914 .collect())
10915 })?;
10916
10917 struct Group {
10918 rows: Vec<i32>,
10919 slots: Vec<i32>,
10920 weights: Vec<f32>,
10921 }
10922 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
10923 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
10924 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
10925 Default::default();
10926 for row in 0..mrows {
10927 for j in 0..n_used {
10928 let ex = sel_all[row * n_used + j] as usize;
10929 let w = w_all[row * n_used + j];
10930 if resident_expert[ex] {
10931 let group = groups.entry(ex).or_insert_with(|| Group {
10932 rows: Vec::new(),
10933 slots: Vec::new(),
10934 weights: Vec::new(),
10935 });
10936 group.rows.push(row as i32);
10937 group.slots.push(j as i32);
10938 group.weights.push(w);
10939 } else {
10940 crate::cpu_experts::record_incomplete_gpu_residency(0);
10941 cpu_rows[row].push((ex, w));
10942 cpu_by_expert.entry(ex).or_default().push((row, w));
10943 }
10944 }
10945 }
10946
10947 let host_rows = e.dtoh(zbatch)?;
10953 let rows_ok = crate::cpu_experts::rows_supported();
10954 enum CpuPart {
10955 Single { row: usize },
10956 Rows { rows: Vec<usize> },
10957 }
10958 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
10959 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
10960 if rows_ok {
10961 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
10962 .into_iter()
10963 .filter(|(_, rows)| rows.len() >= 2)
10964 .collect();
10965 shared.sort_by_key(|(ex, _)| *ex);
10966 for (ex, mut row_weights) in shared {
10967 row_weights.sort_by_key(|(row, _)| *row);
10968 let inputs: Vec<(&[f32], f32)> = row_weights
10969 .iter()
10970 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
10971 .collect();
10972 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
10973 .map_err(std::io::Error::other)?;
10974 for &(row, _) in &row_weights {
10975 rows_served.insert((row, ex));
10976 }
10977 tickets.push((
10978 CpuPart::Rows {
10979 rows: row_weights.iter().map(|&(row, _)| row).collect(),
10980 },
10981 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
10982 ));
10983 }
10984 }
10985 for (row, selected) in cpu_rows.iter().enumerate() {
10986 let leftover: Vec<(usize, f32)> = selected
10987 .iter()
10988 .copied()
10989 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
10990 .collect();
10991 if leftover.is_empty() {
10992 continue;
10993 }
10994 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
10995 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
10996 .map_err(std::io::Error::other)?;
10997 tickets.push((
10998 CpuPart::Single { row },
10999 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
11000 ));
11001 }
11002
11003 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
11004 let mut wbuf = e.zeros(mrows * n_used)?;
11005 let mut order: Vec<usize> = groups.keys().copied().collect();
11006 order.sort_by(|&a, &b| {
11007 groups[&b]
11008 .rows
11009 .len()
11010 .cmp(&groups[&a].rows.len())
11011 .then(a.cmp(&b))
11012 });
11013 for &ex in &order {
11014 let group = &groups[&ex];
11015 let m_e = group.rows.len();
11016 let gl = m.gate_exps.expert_layout(ex);
11017 let ul = m.up_exps.expert_layout(ex);
11018 let dl = m.down_exps.expert_layout(ex);
11019 let row_idx_d = e.htod_i32(&group.rows)?;
11020 let slot_idx_d = e.htod_i32(&group.slots)?;
11021 let dmac = m.down_exps.macro_scale(ex);
11022 let weight_d = if dmac == 1.0 {
11023 e.htod(&group.weights)?
11024 } else {
11025 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
11026 e.htod(&scaled)?
11027 };
11028 let mut gathered = e.zeros(m_e * n_embd)?;
11029 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
11030 let gv = gathered.slice(0..m_e * n_embd);
11031 let gate = e.with_moe_cache(max_block, |c, eng| {
11032 let slot = c
11033 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
11034 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
11035 eng.qmatvec_view(
11036 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
11037 0..gl.len,
11038 &gv,
11039 m_e,
11040 m.gate_exps.in_f,
11041 m.gate_exps.out_f,
11042 gl.qtype,
11043 gl.row_bytes,
11044 )
11045 })?;
11046 let up = e.with_moe_cache(max_block, |c, eng| {
11047 let slot = c
11048 .resident(BlockId::new(il, PROJ_UP, ex as u16))
11049 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
11050 eng.qmatvec_view(
11051 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
11052 0..ul.len,
11053 &gv,
11054 m_e,
11055 m.up_exps.in_f,
11056 m.up_exps.out_f,
11057 ul.qtype,
11058 ul.row_bytes,
11059 )
11060 })?;
11061 let mut act = e.zeros(m_e * n_ff_exp)?;
11062 Self::ffn_act_lim(
11063 e,
11064 cfg,
11065 &gate,
11066 &up,
11067 m.gate_exps.macro_scale(ex),
11068 m.up_exps.macro_scale(ex),
11069 lim_exp,
11070 &mut act,
11071 m_e * n_ff_exp,
11072 )?;
11073 let actv = act.slice(0..m_e * n_ff_exp);
11074 let y = e.with_moe_cache(max_block, |c, eng| {
11075 let slot = c
11076 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
11077 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
11078 eng.qmatvec_view(
11079 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
11080 0..dl.len,
11081 &actv,
11082 m_e,
11083 m.down_exps.in_f,
11084 m.down_exps.out_f,
11085 dl.qtype,
11086 dl.row_bytes,
11087 )
11088 })?;
11089 e.scatter_slot(
11090 &y,
11091 &row_idx_d,
11092 &slot_idx_d,
11093 &weight_d,
11094 &mut slot_buf,
11095 &mut wbuf,
11096 n_embd,
11097 n_used,
11098 m_e,
11099 )?;
11100 }
11101 let mut moe_out = e.zeros(mrows * n_embd)?;
11102 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
11103
11104 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
11106 for (part, ticket) in tickets {
11107 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
11108 let mut add_row = |row: usize, chunk: &[f32]| {
11109 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
11110 for (accumulator, value) in sum.iter_mut().zip(chunk) {
11111 *accumulator += value;
11112 }
11113 };
11114 match part {
11115 CpuPart::Single { row } => add_row(row, &cpu_output),
11116 CpuPart::Rows { rows } => {
11117 for (slot, row) in rows.into_iter().enumerate() {
11118 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
11119 }
11120 }
11121 }
11122 }
11123 for (row, sum) in row_sums.into_iter().enumerate() {
11124 let Some(sum) = sum else { continue };
11125 let cpu_output = e.htod(&sum)?;
11126 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
11127 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
11128 }
11129
11130 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
11131 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
11132 {
11133 let n_ff_sh = gate_shexp.out_features();
11134 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
11135 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
11136 let mut sa = e.zeros(mrows * n_ff_sh)?;
11137 Self::ffn_act_lim(
11138 e,
11139 cfg,
11140 &sg_gate,
11141 &sg_up,
11142 1.0,
11143 1.0,
11144 lim_shexp,
11145 &mut sa,
11146 mrows * n_ff_sh,
11147 )?;
11148 let sh = e.matmul(down_shexp, &sa, mrows)?;
11149 let g = match &m.gate_inp_shexp {
11152 Some(gate_inp_shexp) => {
11153 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
11154 }
11155 None => e.htod(&vec![1.0f32; mrows])?,
11156 };
11157 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
11158 }
11159
11160 Ok(moe_out)
11161 }
11162}
11163
11164impl HybridModel {
11170 pub(crate) fn gemma4_rope_dims(&self, il: usize) -> usize {
11184 let g = self
11185 .cfg
11186 .gemma4
11187 .as_ref()
11188 .expect("gemma4_rope_dims on a non-gemma4 config");
11189 if g.swa_pattern[il] {
11190 g.rope_dims_swa as usize
11191 } else {
11192 g.rope_dims_global as usize
11193 }
11194 }
11195
11196 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
11197 let g = self.cfg.gemma4.as_ref().unwrap();
11198 let swa = g.swa_pattern[il];
11199 let hd = if swa {
11200 g.key_length_swa
11201 } else {
11202 g.key_length_global
11203 } as usize;
11204 (
11208 hd,
11209 g.head_count_kv[il] as usize,
11210 self.cfg.n_head as usize,
11211 if swa {
11212 g.rope_base_swa
11213 } else {
11214 g.rope_base_global
11215 },
11216 1.0,
11217 swa,
11218 )
11219 }
11220
11221 pub(crate) fn gemma4_suppress(
11225 &self,
11226 e: &Engine,
11227 ld: &mut CudaSlice<f32>,
11228 t: usize,
11229 ) -> Result<(), Box<dyn std::error::Error>> {
11230 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
11231 #[cfg(debug_assertions)]
11236 crate::debug_assert_tensor_stream_device(
11237 ids,
11238 &e.stream(),
11239 "gemma4_suppress.suppress_d",
11240 );
11241 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
11242 }
11243 Ok(())
11244 }
11245
11246 #[allow(clippy::too_many_arguments)]
11251 fn gemma_fa_one_program() -> bool {
11260 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11261 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_FA_ONE_PROGRAM").as_deref() == Ok("1"))
11262 }
11263
11264 fn gemma4_attn_prime(
11265 &self,
11266 e: &Engine,
11267 fa: &crate::hybrid::FullAttnLayer,
11268 il: usize,
11269 h: &CudaSlice<f32>,
11270 pos_d: &CudaSlice<i32>,
11271 t: usize,
11272 cache: Option<&mut Cache>,
11273 island: Option<&CudaSlice<i32>>,
11274 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11275 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
11276 let eps = self.cfg.rms_eps;
11277 let aux = self.gemma4_aux.as_ref().unwrap();
11278 let ones = aux.ones(e);
11279 #[cfg(debug_assertions)]
11280 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
11281
11282 e.mmq_act_begin();
11285 let q0 = e.matmul(&fa.wq, h, t)?; if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
11287 let v = e.dtoh(&q0)?;
11288 let nan = v.iter().filter(|x| x.is_nan()).count();
11289 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
11290 eprintln!(
11291 "[g4-prime-trace] L0 q0: nan={nan}/{} amax={amax:.3}",
11292 v.len()
11293 );
11294 }
11295 let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
11299 e.matmul(&fa.wv, h, t)?
11300 } else {
11301 e.clone_dtod(&k0)?
11302 };
11303 if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
11304 for (tag, buf) in [("k0", &k0), ("v0", &v0)] {
11305 let v = e.dtoh(buf)?;
11306 let nan = v.iter().filter(|x| x.is_nan()).count();
11307 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
11308 eprintln!(
11309 "[g4-prime-trace] L0 {tag}: nan={nan}/{} amax={amax:.3}",
11310 v.len()
11311 );
11312 }
11313 }
11314
11315 let mut q = e.uninit(t * nh * hd)?;
11316 let mut k = e.uninit(t * nkv * hd)?;
11317 let mut v = e.uninit(t * nkv * hd)?;
11319 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11323 let emit = island.is_none()
11326 && t >= 16
11327 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
11328 && *EMIT.get_or_init(|| {
11329 std::env::var("MEMRA_FA_EMIT")
11330 .map(|s| s != "0")
11331 .unwrap_or(true)
11332 });
11333 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
11334 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
11335 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
11336 let v_f16 = emit
11339 && crate::fa_f16pv_on()
11340 && match hd {
11341 512 => true,
11342 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
11343 _ => false,
11344 };
11345 if emit {
11346 e.rms_norm_qkv_w4b(
11347 &q0,
11348 &k0,
11349 &v0,
11350 fa.q_norm.float_data(),
11351 fa.k_norm.float_data(),
11352 ones,
11353 &mut q,
11354 &mut k,
11355 &mut v,
11356 &mut vb,
11357 hd,
11358 nh * t,
11359 nkv * t,
11360 eps,
11361 v_f16,
11362 )?;
11363 } else {
11364 e.rms_norm_qkv(
11365 &q0,
11366 &k0,
11367 &v0,
11368 fa.q_norm.float_data(),
11369 fa.k_norm.float_data(),
11370 ones,
11371 &mut q,
11372 &mut k,
11373 &mut v,
11374 hd,
11375 nh * t,
11376 nkv * t,
11377 eps,
11378 )?;
11379 }
11380
11381 let ff = if swa {
11382 None
11383 } else {
11384 Some(
11385 aux.rope_freqs(e)
11386 .expect("gemma4 global rope needs rope_freqs.weight"),
11387 )
11388 };
11389 #[cfg(debug_assertions)]
11390 if let Some(ff) = ff {
11391 crate::debug_assert_tensor_stream_device(
11392 ff,
11393 &e.stream(),
11394 "gemma4_attn_prime.rope_freqs",
11395 );
11396 }
11397 if emit {
11398 e.rope_neox2_bf16e(
11399 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
11400 )?;
11401 } else {
11402 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
11403 }
11404
11405 if let Some(cache) = cache {
11406 let kvl = cache.kv[il].as_mut().unwrap();
11407 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
11408 e.append_kv_quantized_rows(
11409 &k,
11410 &v,
11411 &mut kvl.k,
11412 &mut kvl.v,
11413 kvl.len,
11414 t,
11415 kvl.kv_dim_k,
11416 kvl.kv_dim_v,
11417 kvl.k_tok_bytes,
11418 kvl.v_tok_bytes,
11419 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
11420 )?;
11421 kvl.len += t;
11422 }
11423 let mut attn = e.zeros(t * nh * hd)?;
11424 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11428 if let Some(span) = island {
11429 let w = if swa && t > win { win } else { 0 };
11434 e.sdpa_naive_island(&q, &k, &v, &mut attn, span, hd, nh, nkv, t, t, scale, w)?;
11435 } else if swa && (t > win || Self::gemma_fa_one_program()) {
11436 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11437 if emit {
11438 e.fa_prefill_w_pre(
11439 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
11440 )?;
11441 } else {
11442 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11443 }
11444 } else {
11445 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11446 }
11447 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11448 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11449 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
11450 if emit {
11451 e.fa_prefill_hd512_pre(
11452 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
11453 )?;
11454 } else {
11455 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11456 }
11457 } else {
11458 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11459 }
11460 Ok(e.matmul(&fa.wo, &attn, t)?)
11461 }
11462
11463 fn gemma4_attn(
11465 &self,
11466 e: &Engine,
11467 fa: &crate::hybrid::FullAttnLayer,
11468 il: usize,
11469 h: &CudaSlice<f32>,
11470 pos_d: &CudaSlice<i32>,
11471 t: usize,
11472 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11473 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None, None)
11474 }
11475
11476 fn gemma4_moe_q8(
11481 &self,
11482 e: &Engine,
11483 m: &crate::hybrid::MoeWeights,
11484 bits: &crate::hybrid::Gemma4MoeBits,
11485 mq: &(CudaSlice<i8>, CudaSlice<f32>),
11486 router_in: &CudaSlice<f32>,
11487 t: usize,
11488 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11489 let cfg = &self.cfg;
11490 let moe = cfg.moe.as_ref().unwrap();
11491 let n_embd = cfg.n_embd as usize;
11492 let n_expert = moe.expert_count as usize;
11493 let n_used = moe.expert_used_count as usize;
11494 let n_ff_exp = moe.expert_ff_length as usize;
11495 let logits = if crate::router_kernel_on() {
11499 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11500 } else {
11501 e.matmul(&m.gate_inp, router_in, t)?
11502 };
11503 let dev = m.dev_exps.as_ref().unwrap();
11504 let (sel_d, w_d) =
11505 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11506 let (zq, zd) = mq;
11507 if t == 1 {
11508 let selv = sel_d.slice(0..n_used);
11509 let wv = w_d.slice(0..n_used);
11510 let act = e.moe_gate_up_gelu8_dev_q8(
11511 &dev.ptr_row,
11512 &selv,
11513 zq,
11514 zd,
11515 n_embd,
11516 n_ff_exp,
11517 n_used,
11518 n_expert,
11519 m.gate_exps.qtype,
11520 m.up_exps.qtype,
11521 m.gate_exps.row_bytes,
11522 m.up_exps.row_bytes,
11523 )?;
11524 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11525 let mut moe_out = e.uninit(n_embd)?;
11526 e.moe_down8_fma_dev_q8(
11527 &dev.ptr_row,
11528 &selv,
11529 &wv,
11530 &aq2,
11531 &ad2,
11532 &mut moe_out.slice_mut(0..n_embd),
11533 n_ff_exp,
11534 n_embd,
11535 n_used,
11536 n_expert,
11537 m.down_exps.qtype,
11538 m.down_exps.row_bytes,
11539 )?;
11540 return Ok(moe_out);
11541 }
11542 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11543 let act = if csr {
11544 e.moe_gate_up_gelu8_dev_q8_csr(
11545 &dev.ptr_row,
11546 &sel_d,
11547 zq,
11548 zd,
11549 t * n_used,
11550 n_embd,
11551 n_ff_exp,
11552 n_used,
11553 n_expert,
11554 m.gate_exps.qtype,
11555 m.up_exps.qtype,
11556 m.gate_exps.row_bytes,
11557 m.up_exps.row_bytes,
11558 )?
11559 } else {
11560 e.moe_gate_up_gelu8_dev_q8_rows(
11561 &dev.ptr_row,
11562 &sel_d,
11563 zq,
11564 zd,
11565 t,
11566 n_embd,
11567 n_ff_exp,
11568 n_used,
11569 n_expert,
11570 m.gate_exps.qtype,
11571 m.up_exps.qtype,
11572 m.gate_exps.row_bytes,
11573 m.up_exps.row_bytes,
11574 )?
11575 };
11576 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11577 let mut moe_out = e.uninit(t * n_embd)?;
11578 e.moe_down8_fma_dev_q8_rows_g(
11581 &dev.ptr_row,
11582 &sel_d,
11583 &w_d,
11584 &aq2,
11585 &ad2,
11586 &mut moe_out,
11587 t,
11588 n_ff_exp,
11589 n_embd,
11590 n_used,
11591 n_expert,
11592 m.down_exps.qtype,
11593 m.down_exps.row_bytes,
11594 )?;
11595 Ok(moe_out)
11596 }
11597
11598 fn gemma4_moe(
11602 &self,
11603 e: &Engine,
11604 m: &crate::hybrid::MoeWeights,
11605 bits: &crate::hybrid::Gemma4MoeBits,
11606 moe_in: &CudaSlice<f32>,
11607 router_in: &CudaSlice<f32>,
11608 t: usize,
11609 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11610 let cfg = &self.cfg;
11611 let moe = cfg.moe.as_ref().unwrap();
11612 let n_embd = cfg.n_embd as usize;
11613 let n_expert = moe.expert_count as usize;
11614 let n_used = moe.expert_used_count as usize;
11615 let n_ff_exp = moe.expert_ff_length as usize;
11616
11617 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
11621 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11622 } else {
11623 e.matmul(&m.gate_inp, router_in, t)?
11624 };
11625
11626 if t < PRIME_MIN_T
11631 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11632 && expert_dp4a_supported(m.gate_exps.qtype)
11633 && expert_dp4a_supported(m.up_exps.qtype)
11634 && expert_dp4a_supported(m.down_exps.qtype)
11635 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11636 {
11637 let dev = m.dev_exps.as_ref().unwrap();
11638 let (sel_d, w_d) =
11639 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11640 if t == 1 {
11641 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
11642 let selv = sel_d.slice(0..n_used);
11643 let wv = w_d.slice(0..n_used);
11644 let act = e.moe_gate_up_gelu8_dev_q8(
11645 &dev.ptr_row,
11646 &selv,
11647 &zq,
11648 &zd,
11649 n_embd,
11650 n_ff_exp,
11651 n_used,
11652 n_expert,
11653 m.gate_exps.qtype,
11654 m.up_exps.qtype,
11655 m.gate_exps.row_bytes,
11656 m.up_exps.row_bytes,
11657 )?;
11658 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11659 let mut moe_out = e.uninit(n_embd)?;
11660 e.moe_down8_fma_dev_q8(
11661 &dev.ptr_row,
11662 &selv,
11663 &wv,
11664 &aq2,
11665 &ad2,
11666 &mut moe_out.slice_mut(0..n_embd),
11667 n_ff_exp,
11668 n_embd,
11669 n_used,
11670 n_expert,
11671 m.down_exps.qtype,
11672 m.down_exps.row_bytes,
11673 )?;
11674 return Ok(moe_out);
11675 }
11676 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11681 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11682 let act = if csr {
11683 e.moe_gate_up_gelu8_dev_q8_csr(
11684 &dev.ptr_row,
11685 &sel_d,
11686 &zq,
11687 &zd,
11688 t * n_used,
11689 n_embd,
11690 n_ff_exp,
11691 n_used,
11692 n_expert,
11693 m.gate_exps.qtype,
11694 m.up_exps.qtype,
11695 m.gate_exps.row_bytes,
11696 m.up_exps.row_bytes,
11697 )?
11698 } else {
11699 e.moe_gate_up_gelu8_dev_q8_rows(
11700 &dev.ptr_row,
11701 &sel_d,
11702 &zq,
11703 &zd,
11704 t,
11705 n_embd,
11706 n_ff_exp,
11707 n_used,
11708 n_expert,
11709 m.gate_exps.qtype,
11710 m.up_exps.qtype,
11711 m.gate_exps.row_bytes,
11712 m.up_exps.row_bytes,
11713 )?
11714 };
11715 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11716 let mut moe_out = e.uninit(t * n_embd)?;
11717 e.moe_down8_fma_dev_q8_rows_g(
11718 &dev.ptr_row,
11719 &sel_d,
11720 &w_d,
11721 &aq2,
11722 &ad2,
11723 &mut moe_out,
11724 t,
11725 n_ff_exp,
11726 n_embd,
11727 n_used,
11728 n_expert,
11729 m.down_exps.qtype,
11730 m.down_exps.row_bytes,
11731 )?;
11732 return Ok(moe_out);
11733 }
11734
11735 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
11736 for (i, &sx) in sel_all.iter().enumerate() {
11737 w_all[i] *= bits.per_expert_scale[sx as usize];
11738 }
11739
11740 if t >= PRIME_MIN_T
11744 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11745 && expert_dp4a_supported(m.gate_exps.qtype)
11746 && expert_dp4a_supported(m.up_exps.qtype)
11747 && expert_dp4a_supported(m.down_exps.qtype)
11748 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
11749 {
11750 let dev = m.dev_exps.as_ref().unwrap();
11751 let n_pairs = t * n_used;
11752 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
11753 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
11754 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
11755 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
11756 let pt = e.htod_i32(&pair_tok)?;
11757 let pw = e.htod(&w_all)?;
11758 let toff = e.htod_i32(&tok_off)?;
11759 let tids = e.htod_i32(&tok_ids)?;
11760 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11761 for p in 0..n_pairs {
11762 by_ex[pair_ex[p] as usize].push(p as i32);
11763 }
11764 let mut ex_ids: Vec<i32> = Vec::new();
11765 let mut ex_off: Vec<i32> = vec![0];
11766 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
11767 for (ex, list) in by_ex.iter().enumerate() {
11768 if list.is_empty() {
11769 continue;
11770 }
11771 ex_ids.push(ex as i32);
11772 ex_pairs.extend_from_slice(list);
11773 ex_off.push(ex_pairs.len() as i32);
11774 }
11775 let n_active = ex_ids.len();
11776 let exi = e.htod_i32(&ex_ids)?;
11777 let exo = e.htod_i32(&ex_off)?;
11778 let exp_d = e.htod_i32(&ex_pairs)?;
11779 if crate::moe_f16g_gemma_on()
11787 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
11788 && f16g_proj_ok(m.up_exps.qtype, n_embd)
11789 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
11790 {
11791 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11792 let csr_tok_d = e.htod_i32(&csr_tok)?;
11793 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
11794 let g_csr = e.moe_f16_grouped(
11795 &dev.ptr_row,
11796 0,
11797 n_expert,
11798 &exi,
11799 &ex_off,
11800 &exo,
11801 &z_f16,
11802 &z_s,
11803 n_embd,
11804 n_ff_exp,
11805 n_active,
11806 n_pairs,
11807 m.gate_exps.qtype,
11808 m.gate_exps.row_bytes,
11809 )?;
11810 let u_csr = e.moe_f16_grouped(
11811 &dev.ptr_row,
11812 1,
11813 n_expert,
11814 &exi,
11815 &ex_off,
11816 &exo,
11817 &z_f16,
11818 &z_s,
11819 n_embd,
11820 n_ff_exp,
11821 n_active,
11822 n_pairs,
11823 m.up_exps.qtype,
11824 m.up_exps.row_bytes,
11825 )?;
11826 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
11827 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
11828 let d_csr = e.moe_f16_grouped(
11829 &dev.ptr_row,
11830 2,
11831 n_expert,
11832 &exi,
11833 &ex_off,
11834 &exo,
11835 &a_f16,
11836 &a_s,
11837 n_ff_exp,
11838 n_embd,
11839 n_active,
11840 n_pairs,
11841 m.down_exps.qtype,
11842 m.down_exps.row_bytes,
11843 )?;
11844 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
11845 let mut moe_out = e.uninit(t * n_embd)?;
11846 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11847 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
11848 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
11849 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
11850 eprintln!(
11851 "[f16g-debug] post-permute bad={} post-scatter bad={}",
11852 scan(&yd),
11853 scan(&mo)
11854 );
11855 }
11856 return Ok(moe_out);
11857 }
11858 let mma =
11861 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
11862 let (gate, up) = if mma {
11863 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
11864 (
11865 e.mmq_iq_experts(
11866 &dev.ptr_row,
11867 0,
11868 n_expert,
11869 &exi,
11870 &exo,
11871 &exp_d,
11872 &pt,
11873 &z_scr,
11874 n_embd,
11875 n_ff_exp,
11876 n_active,
11877 n_pairs,
11878 t,
11879 m.gate_exps.qtype,
11880 m.gate_exps.row_bytes,
11881 )?,
11882 e.mmq_iq_experts(
11883 &dev.ptr_row,
11884 1,
11885 n_expert,
11886 &exi,
11887 &exo,
11888 &exp_d,
11889 &pt,
11890 &z_scr,
11891 n_embd,
11892 n_ff_exp,
11893 n_active,
11894 n_pairs,
11895 t,
11896 m.up_exps.qtype,
11897 m.up_exps.row_bytes,
11898 )?,
11899 )
11900 } else {
11901 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11902 (
11903 e.moe_pairs_matvec_q8_dec(
11904 &dev.ptr_row,
11905 0,
11906 &exi,
11907 &exo,
11908 &exp_d,
11909 &pt,
11910 &zq,
11911 &zd,
11912 n_embd,
11913 n_ff_exp,
11914 n_expert,
11915 n_active,
11916 n_pairs,
11917 m.gate_exps.qtype,
11918 m.gate_exps.row_bytes,
11919 )?,
11920 e.moe_pairs_matvec_q8_dec(
11921 &dev.ptr_row,
11922 1,
11923 &exi,
11924 &exo,
11925 &exp_d,
11926 &pt,
11927 &zq,
11928 &zd,
11929 n_embd,
11930 n_ff_exp,
11931 n_expert,
11932 n_active,
11933 n_pairs,
11934 m.up_exps.qtype,
11935 m.up_exps.row_bytes,
11936 )?,
11937 )
11938 };
11939 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
11940 let pself = e.htod_i32(&pair_self)?;
11941 let y_down = if mma {
11953 let in_pad = n_ff_exp.div_ceil(256) * 256;
11954 let a_scr = if crate::moe_fuse_actq_on() {
11955 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
11956 } else {
11957 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11958 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
11959 };
11960 e.mmq_iq_experts(
11961 &dev.ptr_row,
11962 2,
11963 n_expert,
11964 &exi,
11965 &exo,
11966 &exp_d,
11967 &pself,
11968 &a_scr,
11969 in_pad,
11970 n_embd,
11971 n_active,
11972 n_pairs,
11973 n_pairs,
11974 m.down_exps.qtype,
11975 m.down_exps.row_bytes,
11976 )?
11977 } else {
11978 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11979 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
11980 e.moe_pairs_matvec_q8_dec(
11981 &dev.ptr_row,
11982 2,
11983 &exi,
11984 &exo,
11985 &exp_d,
11986 &pself,
11987 &aq2,
11988 &ad2,
11989 n_ff_exp,
11990 n_embd,
11991 n_expert,
11992 n_active,
11993 n_pairs,
11994 m.down_exps.qtype,
11995 m.down_exps.row_bytes,
11996 )?
11997 };
11998 let mut moe_out = e.uninit(t * n_embd)?;
11999 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
12000 return Ok(moe_out);
12001 }
12002
12003 let g_len = m.gate_exps.expert_stride;
12004 let u_len = m.up_exps.expert_stride;
12005 let d_len = m.down_exps.expert_stride;
12006 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
12010 let (mut sg, mut su, mut sd) = if dev.is_some() {
12011 (None, None, None)
12012 } else {
12013 (
12014 Some(e.alloc_u8_uninit(g_len)?),
12015 Some(e.alloc_u8_uninit(u_len)?),
12016 Some(e.alloc_u8_uninit(d_len)?),
12017 )
12018 };
12019 let mut moe_out = e.zeros(t * n_embd)?;
12020 for tok in 0..t {
12021 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
12022 let w = &w_all[tok * n_used..(tok + 1) * n_used];
12023 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
12024 for (j, &ex) in sel.iter().enumerate() {
12025 let ex = ex as usize;
12026 let gate = match dev {
12027 Some(d) => e.qmatvec_view(
12028 &d.gate,
12029 ex * g_len..(ex + 1) * g_len,
12030 &zt,
12031 1,
12032 m.gate_exps.in_f,
12033 m.gate_exps.out_f,
12034 m.gate_exps.qtype,
12035 m.gate_exps.row_bytes,
12036 )?,
12037 None => {
12038 let sg = sg.as_mut().unwrap();
12039 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
12040 e.qmatvec_view(
12041 sg,
12042 0..g_len,
12043 &zt,
12044 1,
12045 m.gate_exps.in_f,
12046 m.gate_exps.out_f,
12047 m.gate_exps.qtype,
12048 m.gate_exps.row_bytes,
12049 )?
12050 }
12051 };
12052 let up = match dev {
12053 Some(d) => e.qmatvec_view(
12054 &d.up,
12055 ex * u_len..(ex + 1) * u_len,
12056 &zt,
12057 1,
12058 m.up_exps.in_f,
12059 m.up_exps.out_f,
12060 m.up_exps.qtype,
12061 m.up_exps.row_bytes,
12062 )?,
12063 None => {
12064 let su = su.as_mut().unwrap();
12065 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
12066 e.qmatvec_view(
12067 su,
12068 0..u_len,
12069 &zt,
12070 1,
12071 m.up_exps.in_f,
12072 m.up_exps.out_f,
12073 m.up_exps.qtype,
12074 m.up_exps.row_bytes,
12075 )?
12076 }
12077 };
12078 let mut act = e.uninit(n_ff_exp)?;
12079 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
12080 let actv = act.slice(0..n_ff_exp);
12081 let y = match dev {
12082 Some(d) => e.qmatvec_view(
12083 &d.down,
12084 ex * d_len..(ex + 1) * d_len,
12085 &actv,
12086 1,
12087 m.down_exps.in_f,
12088 m.down_exps.out_f,
12089 m.down_exps.qtype,
12090 m.down_exps.row_bytes,
12091 )?,
12092 None => {
12093 let sd = sd.as_mut().unwrap();
12094 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
12095 e.qmatvec_view(
12096 sd,
12097 0..d_len,
12098 &actv,
12099 1,
12100 m.down_exps.in_f,
12101 m.down_exps.out_f,
12102 m.down_exps.qtype,
12103 m.down_exps.row_bytes,
12104 )?
12105 }
12106 };
12107 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
12108 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
12109 }
12110 }
12111 Ok(moe_out)
12112 }
12113
12114 fn gemma4_layer(
12116 &self,
12117 e: &Engine,
12118 il: usize,
12119 layer: &crate::hybrid::HybridLayer,
12120 x: &CudaSlice<f32>,
12121 pos_d: &CudaSlice<i32>,
12122 t: usize,
12123 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12124 let n_embd = self.cfg.n_embd as usize;
12125 let eps = self.cfg.rms_eps;
12126
12127 let mut h = e.zeros(t * n_embd)?;
12128 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12129 let Mixer::Full(fa) = &layer.mixer else {
12130 panic!("gemma4 layer {il} not full-attn")
12131 };
12132 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
12133 let mut cur = e.zeros(t * n_embd)?;
12135 e.rms_norm(
12136 &o,
12137 layer.post_attn_norm.float_data(),
12138 &mut cur,
12139 n_embd,
12140 t,
12141 eps,
12142 )?;
12143 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
12144 }
12145
12146 fn gemma4_layer_tail_add(
12150 &self,
12151 e: &Engine,
12152 layer: &crate::hybrid::HybridLayer,
12153 cur: &CudaSlice<f32>,
12154 x: &CudaSlice<f32>,
12155 t: usize,
12156 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12157 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
12158 }
12159
12160 fn gemma4_layer_tail_add_n(
12163 &self,
12164 e: &Engine,
12165 layer: &crate::hybrid::HybridLayer,
12166 cur: &CudaSlice<f32>,
12167 x: &CudaSlice<f32>,
12168 t: usize,
12169 next_norm: Option<&CudaSlice<f32>>,
12170 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
12171 let n_embd = self.cfg.n_embd as usize;
12172 let bits = layer.gemma4.as_ref().unwrap();
12173 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12174 let mut xn = e.uninit(t * n_embd)?;
12175 match next_norm {
12176 Some(w) => {
12177 let mut hn = e.uninit(t * n_embd)?;
12178 e.add_scale_rms_norm(
12179 &sn,
12180 &attn_out,
12181 bits.layer_scale,
12182 w,
12183 &mut xn,
12184 &mut hn,
12185 n_embd,
12186 t,
12187 self.cfg.rms_eps,
12188 )?;
12189 Ok((xn, Some(hn)))
12190 }
12191 None => {
12192 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12193 Ok((xn, None))
12194 }
12195 }
12196 }
12197
12198 fn gemma4_layer_tail_core(
12201 &self,
12202 e: &Engine,
12203 layer: &crate::hybrid::HybridLayer,
12204 cur: &CudaSlice<f32>,
12205 x: &CudaSlice<f32>,
12206 t: usize,
12207 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12208 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
12209 }
12210
12211 fn gemma4_layer_tail_core_pn(
12218 &self,
12219 e: &Engine,
12220 layer: &crate::hybrid::HybridLayer,
12221 cur: &CudaSlice<f32>,
12222 x: &CudaSlice<f32>,
12223 t: usize,
12224 pre_norm: Option<&CudaSlice<f32>>,
12225 defer_post_norm: bool,
12226 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12227 let n_embd = self.cfg.n_embd as usize;
12228 let eps = self.cfg.rms_eps;
12229 let bits = layer.gemma4.as_ref().unwrap();
12230
12231 let Some(mbits) = bits.moe_bits.as_ref() else {
12234 let crate::hybrid::Ffn::Dense {
12235 ffn_gate,
12236 ffn_up,
12237 ffn_down,
12238 } = &layer.ffn
12239 else {
12240 panic!("gemma4 dense layer without Dense ffn")
12241 };
12242 let mut attn_out = e.uninit(t * n_embd)?;
12243 let mut zsh = e.uninit(t * n_embd)?;
12244 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12247 match pre_norm {
12248 Some(wa) if t == 1 => {
12249 zpair = Some(e.rms_pre_add_rms_norm_q8z(
12250 cur,
12251 wa,
12252 x,
12253 bits.ffn_norm.float_data(),
12254 &mut attn_out,
12255 &mut zsh,
12256 n_embd,
12257 t,
12258 eps,
12259 )?);
12260 }
12261 Some(wa) => e.rms_pre_add_rms_norm(
12262 cur,
12263 wa,
12264 x,
12265 bits.ffn_norm.float_data(),
12266 &mut attn_out,
12267 &mut zsh,
12268 n_embd,
12269 t,
12270 eps,
12271 )?,
12272 None => e.add_rms_norm(
12273 cur,
12274 x,
12275 bits.ffn_norm.float_data(),
12276 &mut attn_out,
12277 &mut zsh,
12278 n_embd,
12279 t,
12280 eps,
12281 )?,
12282 }
12283 let n_ff = ffn_gate.out_features();
12284 let (gate, up) = if t == 1 {
12290 let (zq, zd) = match zpair {
12291 Some(p) => p,
12292 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
12293 };
12294 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
12295 Some(p) => p,
12296 None => match e.matmul_nvfp4_fused2(ffn_gate, ffn_up, &zq, &zd, 1)? {
12298 Some(p) => p,
12299 None => (
12300 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
12301 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
12302 ),
12303 },
12304 }
12305 } else {
12306 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12311 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
12312 let fused = if f2b {
12313 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
12314 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
12315 } else {
12316 None
12317 };
12318 match fused {
12319 Some(p) => p,
12320 None => {
12321 e.mmq_act_begin();
12323 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
12324 }
12325 }
12326 };
12327 let mut act = e.uninit(t * n_ff)?;
12328 let f0 = if e.uses_q8_1_fast(ffn_down) {
12331 let upv = e.view(&up, t * n_ff);
12332 let up_all = upv.slice(0..t * n_ff);
12333 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
12334 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
12335 } else {
12336 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
12337 e.matmul(ffn_down, &act, t)?
12338 };
12339 if defer_post_norm {
12340 return Ok((f0, attn_out));
12341 }
12342 let mut sn = e.uninit(t * n_embd)?;
12343 e.rms_norm(
12344 &f0,
12345 bits.post_ffw_norm.float_data(),
12346 &mut sn,
12347 n_embd,
12348 t,
12349 eps,
12350 )?;
12351 return Ok((sn, attn_out));
12352 };
12353
12354 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
12355 let mut attn_out = e.uninit(t * n_embd)?;
12360 let mut router_in = e.uninit(t * n_embd)?;
12361 let fast_moe = match &layer.ffn {
12362 crate::hybrid::Ffn::Moe(m) => {
12363 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
12364 && expert_dp4a_supported(m.gate_exps.qtype)
12365 && expert_dp4a_supported(m.up_exps.qtype)
12366 && expert_dp4a_supported(m.down_exps.qtype)
12367 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
12368 }
12369 _ => false,
12370 };
12371 let q8z = t < PRIME_MIN_T && fast_moe;
12372 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
12373 let (z0, m2) = e.add_rms_norm3_q8z(
12374 cur,
12375 x,
12376 bits.ffn_norm.float_data(),
12377 &mbits.router_scale_pre,
12378 mbits.pre_ffw_norm_2.float_data(),
12379 &mut attn_out,
12380 &mut router_in,
12381 n_embd,
12382 t,
12383 eps,
12384 )?;
12385 (None, Some(z0), Some(m2))
12386 } else {
12387 let mut zsh = e.uninit(t * n_embd)?;
12388 let mut moe_in = e.uninit(t * n_embd)?;
12389 e.add_rms_norm3(
12390 cur,
12391 x,
12392 bits.ffn_norm.float_data(),
12393 &mbits.router_scale_pre,
12394 mbits.pre_ffw_norm_2.float_data(),
12395 &mut attn_out,
12396 &mut zsh,
12397 &mut router_in,
12398 &mut moe_in,
12399 n_embd,
12400 t,
12401 eps,
12402 )?;
12403 (Some((zsh, moe_in)), None, None)
12404 };
12405 let attn_out2 = attn_out;
12406 #[allow(unused_variables)]
12407 let attn_out = &attn_out2;
12408 let n_ff = mbits.shared_gate.out_features();
12409 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
12410 if t == 1 {
12411 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
12412 Some(p) => p,
12413 None => match e.matmul_nvfp4_fused2(
12414 &mbits.shared_gate,
12415 &mbits.shared_up,
12416 zq,
12417 zd,
12418 1,
12419 )? {
12420 Some(p) => p,
12421 None => {
12422 let h0 = e.zeros(0)?;
12423 (
12424 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
12425 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
12426 )
12427 }
12428 },
12429 }
12430 } else {
12431 let h0 = e.zeros(0)?;
12433 (
12434 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
12435 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
12436 )
12437 }
12438 } else {
12439 let (zsh, _) = zsh_f32.as_ref().unwrap();
12440 (
12441 e.matmul(&mbits.shared_gate, zsh, t)?,
12442 e.matmul(&mbits.shared_up, zsh, t)?,
12443 )
12444 };
12445 let mut act = e.uninit(t * n_ff)?;
12446 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
12447 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
12448 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
12449 panic!("gemma4 layer not MoE")
12450 };
12451 let moe0 = match (&moe_q8, &zsh_f32) {
12452 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
12453 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
12454 _ => unreachable!(),
12455 };
12456 let mut mlp = e.uninit(t * n_embd)?;
12458 let mut moe = e.uninit(t * n_embd)?;
12459 e.rms_norm2x(
12460 &mlp0,
12461 &moe0,
12462 mbits.post_ffw_norm_1.float_data(),
12463 mbits.post_ffw_norm_2.float_data(),
12464 &mut mlp,
12465 &mut moe,
12466 n_embd,
12467 t,
12468 eps,
12469 )?;
12470
12471 let mut sum = e.uninit(t * n_embd)?;
12474 let mut sn = e.uninit(t * n_embd)?;
12475 e.add_rms_norm(
12476 &mlp,
12477 &moe,
12478 bits.post_ffw_norm.float_data(),
12479 &mut sum,
12480 &mut sn,
12481 n_embd,
12482 t,
12483 eps,
12484 )?;
12485 Ok((sn, attn_out2))
12486 }
12487
12488 pub(crate) fn gemma4_layer_tail_add_nq_pn(
12498 &self,
12499 e: &Engine,
12500 layer: &crate::hybrid::HybridLayer,
12501 o: &CudaSlice<f32>,
12502 x: &CudaSlice<f32>,
12503 t: usize,
12504 next_norm: Option<&CudaSlice<f32>>,
12505 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12506 {
12507 let n_embd = self.cfg.n_embd as usize;
12508 let eps = self.cfg.rms_eps;
12509 let bits = layer.gemma4.as_ref().unwrap();
12510 if Engine::g4_pnfold_on() && matches!(layer.ffn, crate::hybrid::Ffn::Dense { .. }) {
12511 let (f0, attn_out) = self.gemma4_layer_tail_core_pn(
12512 e,
12513 layer,
12514 o,
12515 x,
12516 t,
12517 Some(layer.post_attn_norm.float_data()),
12518 true,
12519 )?;
12520 let mut xn = e.uninit(t * n_embd)?;
12521 return match next_norm {
12522 Some(w) => {
12523 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
12524 &f0,
12525 bits.post_ffw_norm.float_data(),
12526 &attn_out,
12527 bits.layer_scale,
12528 w,
12529 &mut xn,
12530 n_embd,
12531 t,
12532 eps,
12533 )?;
12534 Ok((xn, Some(pair)))
12535 }
12536 None => {
12537 let mut sn = e.uninit(t * n_embd)?;
12538 e.rms_norm(
12539 &f0,
12540 bits.post_ffw_norm.float_data(),
12541 &mut sn,
12542 n_embd,
12543 t,
12544 eps,
12545 )?;
12546 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12547 Ok((xn, None))
12548 }
12549 };
12550 }
12551 let mut cur = e.uninit(t * n_embd)?;
12552 e.rms_norm(
12553 o,
12554 layer.post_attn_norm.float_data(),
12555 &mut cur,
12556 n_embd,
12557 t,
12558 eps,
12559 )?;
12560 self.gemma4_layer_tail_add_nq(e, layer, &cur, x, t, next_norm)
12561 }
12562
12563 pub(crate) fn gemma4_layer_tail_add_nq(
12564 &self,
12565 e: &Engine,
12566 layer: &crate::hybrid::HybridLayer,
12567 cur: &CudaSlice<f32>,
12568 x: &CudaSlice<f32>,
12569 t: usize,
12570 next_norm: Option<&CudaSlice<f32>>,
12571 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12572 {
12573 let n_embd = self.cfg.n_embd as usize;
12574 let bits = layer.gemma4.as_ref().unwrap();
12575 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12576 let mut xn = e.uninit(t * n_embd)?;
12577 match next_norm {
12578 Some(w) => {
12579 let pair = e.add_scale_rms_norm_q8_1(
12580 &sn,
12581 &attn_out,
12582 bits.layer_scale,
12583 w,
12584 &mut xn,
12585 n_embd,
12586 t,
12587 self.cfg.rms_eps,
12588 )?;
12589 Ok((xn, Some(pair)))
12590 }
12591 None => {
12592 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12593 Ok((xn, None))
12594 }
12595 }
12596 }
12597
12598 fn gemma4_forward(
12601 &self,
12602 e: &Engine,
12603 tokens: &[u32],
12604 last_only: bool,
12605 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12606 if self.is_gemma4_e4b() {
12609 return self.gemma4_e4b_forward(e, tokens, last_only);
12610 }
12611 let n_embd = self.cfg.n_embd as usize;
12612 let t = tokens.len();
12613 let pos: Vec<i32> = (0..t as i32).collect();
12614 let pos_d = e.htod_i32(&pos)?;
12615
12616 let mut x = self.embed(e, tokens)?;
12617 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12618 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
12621 let stat =
12622 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
12623 let h = e.dtoh(x)?;
12624 let bad = h.iter().filter(|v| !v.is_finite()).count();
12625 let mx = h
12626 .iter()
12627 .filter(|v| v.is_finite())
12628 .fold(0.0f32, |m, v| m.max(v.abs()));
12629 eprintln!(
12630 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
12631 &h[..3]
12632 );
12633 Ok(())
12634 };
12635 if probe {
12636 stat(e, &x, "embed")?;
12637 }
12638 for (il, layer) in self.layers.iter().enumerate() {
12639 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
12640 if probe {
12641 stat(e, &x, &format!("L{il}"))?;
12642 }
12643 }
12644 let mut hn = e.zeros(t * n_embd)?;
12645 e.rms_norm(
12646 &x,
12647 self.output_norm.float_data(),
12648 &mut hn,
12649 n_embd,
12650 t,
12651 self.cfg.rms_eps,
12652 )?;
12653 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12654 let n_vocab = self.output.out_features();
12655 let logits = if last_only {
12656 let hv = e.view(&hn, t * n_embd);
12657 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
12658 let mut hlast = e.zeros(n_embd)?;
12659 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
12660 let mut ld = e.matmul(&self.output, &hlast, 1)?;
12661 e.softcap(&mut ld, cap, n_vocab)?;
12662 self.gemma4_suppress(e, &mut ld, 1)?;
12663 e.dtoh(&ld)?
12664 } else {
12665 let mut ld = e.matmul(&self.output, &hn, t)?;
12666 e.softcap(&mut ld, cap, t * n_vocab)?;
12667 self.gemma4_suppress(e, &mut ld, t)?;
12668 e.dtoh(&ld)?
12669 };
12670 Ok(logits)
12671 }
12672
12673 pub(crate) fn gemma4_prime(
12678 &self,
12679 e: &Engine,
12680 tokens: &[u32],
12681 cache: &mut Cache,
12682 overlay: Option<&crate::vision::EmbedOverlay>,
12683 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12684 if cache.pos != 0 {
12689 return Err(
12690 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
12691 — prime the full prompt in one call or decode tokenwise"
12692 .into(),
12693 );
12694 }
12695 let n_embd = self.cfg.n_embd as usize;
12696 let eps = self.cfg.rms_eps;
12697 let t = tokens.len();
12698 let pos: Vec<i32> = (0..t as i32).collect();
12699 let pos_d = e.htod_i32(&pos)?;
12700 let mut x = self.embed(e, tokens)?;
12701 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12702 let island: Option<CudaSlice<i32>> = match overlay {
12709 Some(ov) => {
12710 let mut span_id = vec![-1i32; t];
12711 for (i, &(pos, row_off, n_rows)) in ov.spans.iter().enumerate() {
12712 if pos + n_rows > t {
12713 return Err(format!(
12714 "gemma4 overlay span {i} [{pos}, {}) exceeds the prompt ({t})",
12715 pos + n_rows
12716 )
12717 .into());
12718 }
12719 let view = ov.rows.slice(row_off * n_embd..(row_off + n_rows) * n_embd);
12720 e.copy_view_into(&mut x, pos * n_embd, &view, n_rows * n_embd)?;
12721 for s in span_id.iter_mut().skip(pos).take(n_rows) {
12722 *s = i as i32;
12723 }
12724 }
12725 if std::env::var("MEMRA_GV_FORCE_CAUSAL").as_deref() == Ok("1") {
12729 None
12730 } else {
12731 Some(e.htod_i32(&span_id)?)
12732 }
12733 }
12734 None => None,
12735 };
12736 for (il, layer) in self.layers.iter().enumerate() {
12737 let mut h = e.zeros(t * n_embd)?;
12738 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12739 let Mixer::Full(fa) = &layer.mixer else {
12740 panic!("gemma4 layer not full-attn")
12741 };
12742 let trace = il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1");
12743 if trace {
12744 let v = e.dtoh(&h)?;
12745 let nan = v.iter().filter(|x| x.is_nan()).count();
12746 eprintln!("[g4-prime-trace] L0 post-attn_norm: nan={nan}/{}", v.len());
12747 }
12748 let o =
12749 self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache), island.as_ref())?;
12750 if trace {
12751 let v = e.dtoh(&o)?;
12752 let nan = v.iter().filter(|x| x.is_nan()).count();
12753 eprintln!("[g4-prime-trace] L0 post-attn: nan={nan}/{}", v.len());
12754 }
12755 let mut cur = e.zeros(t * n_embd)?;
12756 e.rms_norm(
12757 &o,
12758 layer.post_attn_norm.float_data(),
12759 &mut cur,
12760 n_embd,
12761 t,
12762 eps,
12763 )?;
12764 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
12765 self.dflash_tap(e, cache, il, &x, t)?;
12766 if std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
12768 let h = e.dtoh(&x)?;
12769 let nan = h.iter().filter(|v| v.is_nan()).count();
12770 let amax = h.iter().fold(0f32, |a, v| a.max(v.abs()));
12771 eprintln!(
12772 "[g4-prime-trace] layer {il}: nan={nan}/{} amax={amax:.3}",
12773 h.len()
12774 );
12775 if nan > 0 {
12776 return Err(format!("g4-prime-trace: first NaN at layer {il}").into());
12777 }
12778 }
12779 }
12780 cache.pos += t;
12781 let hiddens = e.clone_dtod(&x)?;
12782 let xv = e.view(&x, t * n_embd);
12783 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
12784 let mut h_seed = e.zeros(n_embd)?;
12785 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
12786 let mut hn = e.uninit(n_embd)?;
12787 e.rms_norm(
12788 &h_seed,
12789 self.output_norm.float_data(),
12790 &mut hn,
12791 n_embd,
12792 1,
12793 eps,
12794 )?;
12795 let mut ld = e.matmul(&self.output, &hn, 1)?;
12796 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12797 e.softcap(&mut ld, cap, self.output.out_features())?;
12798 self.gemma4_suppress(e, &mut ld, 1)?;
12799 let logits = e.dtoh(&ld)?;
12800 Ok((logits, h_seed, hiddens))
12801 }
12802
12803 fn gemma4_decode_attn(
12808 &self,
12809 e: &Engine,
12810 fa: &crate::hybrid::FullAttnLayer,
12811 il: usize,
12812 hq: &CudaSlice<i8>,
12813 hdq: &CudaSlice<f32>,
12814 pos_d: &CudaSlice<i32>,
12815 cache: &mut Cache,
12816 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12817 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12818 let eps = self.cfg.rms_eps;
12819 let aux = self.gemma4_aux.as_ref().unwrap();
12820 let ones = aux.ones(e);
12821 #[cfg(debug_assertions)]
12822 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
12823 let (hq, hdq) = (hq, hdq);
12824 let h0 = e.zeros(0)?;
12825 let h = &h0;
12826 let (q0, k0, v0) = if swa {
12827 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12828 Some(t3) => t3,
12829 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12832 Some((q0, k0)) => {
12833 let v0 = e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?;
12834 (q0, k0, v0)
12835 }
12836 None => (
12837 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12838 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12839 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12840 ),
12841 },
12842 }
12843 } else {
12844 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
12845 Some(p) => p,
12846 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12847 Some(p) => p,
12848 None => (
12849 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12850 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12851 ),
12852 },
12853 };
12854 let v0 = e.clone_dtod(&k0)?;
12855 (q0, k0, v0)
12856 };
12857 let mut q = e.uninit(nh * hd)?;
12858 let mut k = e.uninit(nkv * hd)?;
12859 let mut v = e.uninit(nkv * hd)?;
12860 let ff = if swa {
12863 None
12864 } else {
12865 Some(
12866 aux.rope_freqs(e)
12867 .expect("gemma4 global rope needs rope_freqs.weight"),
12868 )
12869 };
12870 #[cfg(debug_assertions)]
12871 if let Some(ff) = ff {
12872 crate::debug_assert_tensor_stream_device(
12873 ff,
12874 &e.stream(),
12875 "gemma4_decode_attn.rope_freqs",
12876 );
12877 }
12878 let kvl = cache.kv[il].as_mut().unwrap();
12879 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12880 if crate::Engine::qkv_append_on() {
12881 e.rms_norm_qkv_rope_append(
12885 &q0,
12886 &k0,
12887 &v0,
12888 fa.q_norm.float_data(),
12889 fa.k_norm.float_data(),
12890 ones,
12891 &mut q,
12892 &mut k,
12893 &mut v,
12894 hd,
12895 self.gemma4_rope_dims(il),
12896 nh,
12897 nkv,
12898 pos_d,
12899 nh,
12900 nkv,
12901 base,
12902 1.0,
12903 ff,
12904 eps,
12905 &mut kvl.k,
12906 &mut kvl.v,
12907 kvl.len,
12908 kvl.k_tok_bytes,
12909 kvl.v_tok_bytes,
12910 kv_fp8,
12911 )?;
12912 } else {
12913 e.rms_norm_qkv_rope(
12914 &q0,
12915 &k0,
12916 &v0,
12917 fa.q_norm.float_data(),
12918 fa.k_norm.float_data(),
12919 ones,
12920 &mut q,
12921 &mut k,
12922 &mut v,
12923 hd,
12924 self.gemma4_rope_dims(il),
12925 nh,
12926 nkv,
12927 pos_d,
12928 nh,
12929 nkv,
12930 base,
12931 1.0,
12932 ff,
12933 eps,
12934 )?;
12935 e.append_kv_quantized(
12936 &k,
12937 &v,
12938 &mut kvl.k,
12939 &mut kvl.v,
12940 kvl.len,
12941 kvl.kv_dim_k,
12942 kvl.kv_dim_v,
12943 kvl.k_tok_bytes,
12944 kvl.v_tok_bytes,
12945 kv_fp8,
12946 )?;
12947 }
12948 kvl.len += 1;
12949 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12953 let mut attn = e.uninit(nh * hd)?;
12954 if !swa
12956 && hd == 512
12957 && kvl.len >= crate::fa512_min_tkv()
12958 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12959 {
12960 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12961 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12962 let base = kvl.len as i32;
12964 e.i32_set_k(&mut kvl.len_d, base)?;
12965 e.fa_decode_rows(
12966 &q,
12967 &kp,
12968 &vp,
12969 &mut attn,
12970 hd,
12971 nh,
12972 nkv,
12973 kvl.len - 1,
12974 1,
12975 scale,
12976 kvl.k_tok_bytes,
12977 kvl.v_tok_bytes,
12978 Some((&kvl.len_d, -1)),
12979 false,
12980 false,
12981 None,
12982 )?;
12983 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12984 }
12985 if swa
12987 && kvl.len > win
12988 && hd == 256
12989 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12990 {
12991 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12992 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12993 let base = kvl.len as i32;
12994 e.i32_set_k(&mut kvl.len_d, base)?;
12995 e.fa_decode_rows_w(
12996 &q,
12997 &kp,
12998 &vp,
12999 &mut attn,
13000 hd,
13001 nh,
13002 nkv,
13003 &kvl.len_d,
13004 -1,
13005 1,
13006 scale,
13007 win,
13008 kvl.k_tok_bytes,
13009 kvl.v_tok_bytes,
13010 None,
13011 )?;
13012 return Ok(e.matmul(&fa.wo, &attn, 1)?);
13013 }
13014 let (off_tok, t_kv) = if swa && kvl.len > win {
13015 (kvl.len - win, win)
13016 } else {
13017 (0, kvl.len)
13018 };
13019 let k_view = e.view_u8_range(
13020 &kvl.k,
13021 off_tok * kvl.k_tok_bytes,
13022 (off_tok + t_kv) * kvl.k_tok_bytes,
13023 );
13024 let v_view = e.view_u8_range(
13025 &kvl.v,
13026 off_tok * kvl.v_tok_bytes,
13027 (off_tok + t_kv) * kvl.v_tok_bytes,
13028 );
13029 e.fa_decode_kvmod(
13030 &q,
13031 &k_view,
13032 &v_view,
13033 &mut attn,
13034 hd,
13035 nh,
13036 nkv,
13037 t_kv,
13038 scale,
13039 kvl.k_tok_bytes,
13040 kvl.v_tok_bytes,
13041 swa && crate::Engine::wkv_on(),
13042 )?;
13043 Ok(e.matmul(&fa.wo, &attn, 1)?)
13044 }
13045
13046 #[allow(clippy::too_many_arguments)]
13053 pub fn gemma4_decode_step_dc(
13054 &self,
13055 e: &Engine,
13056 token_d: &CudaSlice<u32>,
13057 pos_d: &mut CudaSlice<i32>,
13058 embd_gpu: &CudaSlice<u8>,
13059 embd_qt: i32,
13060 embd_rb: usize,
13061 cache: &mut Cache,
13062 n_vocab: usize,
13063 cap_bucket_max: Option<(usize, usize)>,
13064 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
13065 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
13066 self.gemma4_decode_step_dc_into(
13067 e,
13068 token_d,
13069 pos_d,
13070 embd_gpu,
13071 embd_qt,
13072 embd_rb,
13073 cache,
13074 n_vocab,
13075 cap_bucket_max,
13076 &mut tok_out,
13077 )?;
13078 Ok(tok_out)
13079 }
13080
13081 #[allow(clippy::too_many_arguments)]
13084 pub fn gemma4_decode_step_dc_into(
13085 &self,
13086 e: &Engine,
13087 token_d: &CudaSlice<u32>,
13088 pos_d: &mut CudaSlice<i32>,
13089 embd_gpu: &CudaSlice<u8>,
13090 embd_qt: i32,
13091 embd_rb: usize,
13092 cache: &mut Cache,
13093 n_vocab: usize,
13094 cap_bucket_max: Option<(usize, usize)>,
13095 tok_out: &mut CudaSlice<u32>,
13096 ) -> Result<(), Box<dyn std::error::Error>> {
13097 let n_embd = self.cfg.n_embd as usize;
13098 let eps = self.cfg.rms_eps;
13099 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
13100 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
13101 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13102 let n_layers = self.layers.len();
13103 for (il, layer) in self.layers.iter().enumerate() {
13104 let (hq, hdq) = match h_carry.take() {
13105 Some(p) => p,
13106 None => {
13107 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
13108 }
13109 };
13110 let Mixer::Full(fa) = &layer.mixer else {
13111 panic!("gemma4 layer {il} not full-attn")
13112 };
13113 let o =
13114 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
13115 let next_norm = if il + 1 < n_layers {
13116 Some(self.layers[il + 1].attn_norm.float_data())
13117 } else {
13118 None
13119 };
13120 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
13121 x = xn;
13122 h_carry = hn;
13123 }
13124 let mut hn = e.uninit(n_embd)?;
13125 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
13126 let mut logits = e.matmul(&self.output, &hn, 1)?;
13127 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
13129 e.inc_seqlen(pos_d)?;
13130 if cap_bucket_max.is_none() {
13131 cache.pos += 1;
13132 }
13133 Ok(())
13134 }
13135
13136 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
13143 let n_embd = self.cfg.n_embd as usize;
13144 let n_vocab = self.output.out_features();
13145 let n_layers = self.layers.len();
13146 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
13147 for il in 0..n_layers {
13148 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
13149 qmax = qmax.max(nh * hd);
13150 kvmax = kvmax.max(nkv * hd);
13151 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
13152 ffmax = ffmax.max(ffn_gate.out_features());
13153 }
13154 }
13155 Ok(G4DcSlots {
13156 x: e.uninit(n_embd)?,
13157 xn: e.uninit(n_embd)?,
13158 cur: e.uninit(n_embd)?,
13159 hq: e.alloc_i8_uninit(n_embd)?,
13160 hd_: e.uninit(n_embd / 32)?,
13161 q0: e.uninit(qmax)?,
13162 k0: e.uninit(kvmax)?,
13163 v0: e.uninit(kvmax)?,
13164 q: e.uninit(qmax)?,
13165 k: e.uninit(kvmax)?,
13166 v: e.uninit(kvmax)?,
13167 attn: e.uninit(qmax)?,
13168 o: e.uninit(n_embd)?,
13169 attn_out: e.uninit(n_embd)?,
13170 zsh: e.uninit(n_embd)?,
13171 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
13174 zd: e.uninit(n_embd.max(qmax) / 32)?,
13175 gate: e.uninit(ffmax)?,
13176 up: e.uninit(ffmax)?,
13177 act: e.uninit(ffmax)?,
13178 actq: e.alloc_i8_uninit(ffmax)?,
13179 actd: e.uninit(ffmax / 32)?,
13180 f0: e.uninit(n_embd)?,
13181 sn: e.uninit(n_embd)?,
13182 hn: e.uninit(n_embd)?,
13183 logits: e.uninit(n_vocab)?,
13184 })
13185 }
13186
13187 fn g4_matvec_m1_into(
13190 &self,
13191 e: &Engine,
13192 w: &crate::model::GpuTensor,
13193 aq: &CudaSlice<i8>,
13194 ad: &CudaSlice<f32>,
13195 y: &mut CudaSlice<f32>,
13196 ) -> Result<(), Box<dyn std::error::Error>> {
13197 use crate::model::GpuTensor;
13198 let (bytes, qtype, row_bytes, scale, rp) = match w {
13199 GpuTensor::Quant {
13200 bytes,
13201 qtype,
13202 row_bytes,
13203 scale,
13204 rp,
13205 ..
13206 } => (bytes, *qtype, *row_bytes, *scale, *rp),
13207 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
13208 };
13209 let (mbytes, mrp) = match w {
13210 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
13211 _ => (bytes, rp),
13212 };
13213 e.qmatvec_mmvq_into(
13214 mbytes,
13215 aq,
13216 ad,
13217 1,
13218 w.in_features(),
13219 w.out_features(),
13220 qtype,
13221 row_bytes,
13222 scale,
13223 mrp,
13224 y,
13225 )
13226 }
13227
13228 #[allow(clippy::too_many_arguments)]
13232 pub fn gemma4_decode_step_dc_slotted(
13233 &self,
13234 e: &Engine,
13235 token_d: &CudaSlice<u32>,
13236 pos_d: &mut CudaSlice<i32>,
13237 embd_gpu: &CudaSlice<u8>,
13238 embd_qt: i32,
13239 embd_rb: usize,
13240 cache: &mut Cache,
13241 n_vocab: usize,
13242 cap_bucket_max: Option<(usize, usize)>,
13243 sl: &mut G4DcSlots,
13244 tok_out: &mut CudaSlice<u32>,
13245 ring: Option<(&mut CudaSlice<u32>, usize)>,
13246 ) -> Result<(), Box<dyn std::error::Error>> {
13247 let n_embd = self.cfg.n_embd as usize;
13248 let eps = self.cfg.rms_eps;
13249 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
13250 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
13251 let n_layers = self.layers.len();
13252 let mut has_carry = false;
13253 for il in 0..n_layers {
13254 if !has_carry {
13255 e.rms_norm_q8_1_into(
13256 &sl.x,
13257 self.layers[il].attn_norm.float_data(),
13258 n_embd,
13259 1,
13260 eps,
13261 &mut sl.hq,
13262 &mut sl.hd_,
13263 )?;
13264 }
13265 has_carry = true;
13266 let layer = &self.layers[il];
13267 let Mixer::Full(fa) = &layer.mixer else {
13268 panic!("gemma4 layer {il} not full-attn")
13269 };
13270 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
13271 if !Engine::g4_pnfold_on() {
13274 e.rms_norm(
13275 &sl.o,
13276 layer.post_attn_norm.float_data(),
13277 &mut sl.cur,
13278 n_embd,
13279 1,
13280 eps,
13281 )?;
13282 }
13283 let next_norm = if il + 1 < n_layers {
13284 Some(self.layers[il + 1].attn_norm.float_data())
13285 } else {
13286 None
13287 };
13288 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
13289 std::mem::swap(&mut sl.x, &mut sl.xn);
13290 }
13291 e.rms_norm(
13292 &sl.x,
13293 self.output_norm.float_data(),
13294 &mut sl.hn,
13295 n_embd,
13296 1,
13297 eps,
13298 )?;
13299 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13300 {
13302 let (zq, zd) = (&sl.zq, &sl.zd);
13303 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
13304 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
13305 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
13306 }
13307 self.gemma4_suppress(e, &mut sl.logits, 1)?;
13308 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
13309 if let Some((ring, base)) = ring {
13310 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
13314 }
13315 e.inc_seqlen(pos_d)?;
13316 if cap_bucket_max.is_none() {
13317 cache.pos += 1;
13318 }
13319 Ok(())
13320 }
13321
13322 #[allow(clippy::too_many_arguments)]
13324 fn gemma4_decode_attn_dc_slotted(
13325 &self,
13326 e: &Engine,
13327 fa: &crate::hybrid::FullAttnLayer,
13328 il: usize,
13329 pos_d: &CudaSlice<i32>,
13330 cache: &mut Cache,
13331 cap_bucket_max: Option<(usize, usize)>,
13332 sl: &mut G4DcSlots,
13333 ) -> Result<(), Box<dyn std::error::Error>> {
13334 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13335 let eps = self.cfg.rms_eps;
13336 let aux = self.gemma4_aux.as_ref().unwrap();
13337 let ones = aux.ones(e);
13338 #[cfg(debug_assertions)]
13339 crate::debug_assert_tensor_stream_device(
13340 ones,
13341 &e.stream(),
13342 "gemma4_decode_attn_dc_slotted.ones",
13343 );
13344 {
13345 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
13346 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
13347 if swa {
13348 if !e.matmul_q4_fused3_into(
13349 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
13350 )? {
13351 if e.matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
13355 {
13356 self.g4_matvec_m1_into(e, &fa.wv, hq, hdq, &mut sl.v0)?;
13357 } else {
13358 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
13359 }
13360 }
13361 } else {
13362 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
13363 && !e
13364 .matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
13365 {
13366 return Err("slotted step: fused2 unavailable".into());
13367 }
13368 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
13369 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
13370 }
13371 }
13372 let ff = if swa {
13375 None
13376 } else {
13377 Some(
13378 aux.rope_freqs(e)
13379 .expect("gemma4 global rope needs rope_freqs.weight"),
13380 )
13381 };
13382 #[cfg(debug_assertions)]
13383 if let Some(ff) = ff {
13384 crate::debug_assert_tensor_stream_device(
13385 ff,
13386 &e.stream(),
13387 "gemma4_decode_attn_dc_slotted.rope_freqs",
13388 );
13389 }
13390 let kvl = cache.kv[il].as_mut().unwrap();
13391 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13392 if crate::Engine::qkv_append_on() {
13393 e.rms_norm_qkv_rope_append_dc(
13395 &sl.q0,
13396 &sl.k0,
13397 &sl.v0,
13398 fa.q_norm.float_data(),
13399 fa.k_norm.float_data(),
13400 ones,
13401 &mut sl.q,
13402 &mut sl.k,
13403 &mut sl.v,
13404 hd,
13405 self.gemma4_rope_dims(il),
13406 nh,
13407 nkv,
13408 pos_d,
13409 nh,
13410 nkv,
13411 base,
13412 1.0,
13413 ff,
13414 eps,
13415 &mut kvl.k,
13416 &mut kvl.v,
13417 &kvl.len_d,
13418 kvl.k_tok_bytes,
13419 kvl.v_tok_bytes,
13420 kv_fp8,
13421 )?;
13422 } else {
13423 e.rms_norm_qkv_rope(
13424 &sl.q0,
13425 &sl.k0,
13426 &sl.v0,
13427 fa.q_norm.float_data(),
13428 fa.k_norm.float_data(),
13429 ones,
13430 &mut sl.q,
13431 &mut sl.k,
13432 &mut sl.v,
13433 hd,
13434 self.gemma4_rope_dims(il),
13435 nh,
13436 nkv,
13437 pos_d,
13438 nh,
13439 nkv,
13440 base,
13441 1.0,
13442 ff,
13443 eps,
13444 )?;
13445 e.append_kv_quantized_dc(
13446 &sl.k,
13447 &sl.v,
13448 &mut kvl.k,
13449 &mut kvl.v,
13450 &kvl.len_d,
13451 kvl.kv_dim_k,
13452 kvl.kv_dim_v,
13453 kvl.k_tok_bytes,
13454 kvl.v_tok_bytes,
13455 kv_fp8,
13456 )?;
13457 }
13458 e.inc_seqlen(&mut kvl.len_d)?;
13459 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
13460 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13461 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13462 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13463 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13464 let mut fa_q8 = false;
13468 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13469 e.fa_decode_rows(
13470 &sl.q,
13471 &k_view,
13472 &v_view,
13473 &mut sl.attn,
13474 hd,
13475 nh,
13476 nkv,
13477 b_glob - 1,
13478 1,
13479 scale,
13480 kvl.k_tok_bytes,
13481 kvl.v_tok_bytes,
13482 Some((&kvl.len_d, -1)),
13483 false,
13484 false,
13485 Some((&mut sl.zq, &mut sl.zd)),
13486 )?;
13487 fa_q8 = true;
13488 } else if swa && b_swa > win && hd == 256 && rows_on {
13489 e.fa_decode_rows_w(
13490 &sl.q,
13491 &k_view,
13492 &v_view,
13493 &mut sl.attn,
13494 hd,
13495 nh,
13496 nkv,
13497 &kvl.len_d,
13498 -1,
13499 1,
13500 scale,
13501 win,
13502 kvl.k_tok_bytes,
13503 kvl.v_tok_bytes,
13504 Some((&mut sl.zq, &mut sl.zd)),
13505 )?;
13506 fa_q8 = true;
13507 } else {
13508 let b = if swa { b_swa } else { b_glob };
13509 e.fa_decode_dc(
13510 &sl.q,
13511 &k_view,
13512 &v_view,
13513 &mut sl.attn,
13514 hd,
13515 nh,
13516 nkv,
13517 &kvl.len_d,
13518 b,
13519 scale,
13520 kvl.k_tok_bytes,
13521 kvl.v_tok_bytes,
13522 swa && crate::Engine::wkv_on(),
13523 )?;
13524 }
13525 if !fa_q8 {
13526 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
13527 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
13528 }
13529 {
13530 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13531 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13532 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
13533 }
13534 Ok(())
13535 }
13536
13537 fn gemma4_layer_tail_slotted(
13540 &self,
13541 e: &Engine,
13542 layer: &crate::hybrid::HybridLayer,
13543 next_norm: Option<&CudaSlice<f32>>,
13544 sl: &mut G4DcSlots,
13545 ) -> Result<(), Box<dyn std::error::Error>> {
13546 let n_embd = self.cfg.n_embd as usize;
13547 let eps = self.cfg.rms_eps;
13548 let bits = layer.gemma4.as_ref().unwrap();
13549 let crate::hybrid::Ffn::Dense {
13550 ffn_gate,
13551 ffn_up,
13552 ffn_down,
13553 } = &layer.ffn
13554 else {
13555 return Err("slotted tail: dense ffn only".into());
13556 };
13557 let pnfold = Engine::g4_pnfold_on();
13558 if pnfold {
13559 let or = unsafe { &*(&sl.o as *const CudaSlice<f32>) };
13562 let xr = unsafe { &*(&sl.x as *const CudaSlice<f32>) };
13563 e.rms_pre_add_rms_norm_q8z_into(
13564 or,
13565 layer.post_attn_norm.float_data(),
13566 xr,
13567 bits.ffn_norm.float_data(),
13568 &mut sl.attn_out,
13569 &mut sl.zsh,
13570 n_embd,
13571 1,
13572 eps,
13573 &mut sl.zq,
13574 &mut sl.zd,
13575 )?;
13576 } else {
13577 e.add_rms_norm(
13578 &sl.cur,
13579 &sl.x,
13580 bits.ffn_norm.float_data(),
13581 &mut sl.attn_out,
13582 &mut sl.zsh,
13583 n_embd,
13584 1,
13585 eps,
13586 )?;
13587 }
13588 let n_ff = ffn_gate.out_features();
13589 if !pnfold {
13590 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
13591 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13592 }
13593 {
13594 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13595 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13596 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)?
13597 && !e.matmul_nvfp4_fused2_into(
13598 ffn_gate,
13599 ffn_up,
13600 zq,
13601 zd,
13602 &mut sl.gate,
13603 &mut sl.up,
13604 )?
13605 {
13606 return Err("slotted tail: ffn fused2 unavailable".into());
13607 }
13608 }
13609 debug_assert!(e.uses_q8_1_fast(ffn_down));
13610 {
13611 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
13612 let upv = e.view(upr, n_ff);
13613 let up_all = upv.slice(0..n_ff);
13614 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
13615 e.gelu_tanh_mul_q8_1_into(
13616 gr,
13617 &up_all,
13618 &mut sl.act,
13619 n_ff,
13620 1,
13621 &mut sl.actq,
13622 &mut sl.actd,
13623 )?;
13624 }
13625 {
13626 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
13627 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
13628 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
13629 }
13630 if pnfold {
13631 if let Some(w) = next_norm {
13634 let f0r = unsafe { &*(&sl.f0 as *const CudaSlice<f32>) };
13635 let aor = unsafe { &*(&sl.attn_out as *const CudaSlice<f32>) };
13636 e.rms_pre_add_scale_rms_norm_q8_1_into(
13637 f0r,
13638 bits.post_ffw_norm.float_data(),
13639 aor,
13640 bits.layer_scale,
13641 w,
13642 &mut sl.xn,
13643 n_embd,
13644 1,
13645 eps,
13646 &mut sl.hq,
13647 &mut sl.hd_,
13648 )?;
13649 return Ok(());
13650 }
13651 }
13652 e.rms_norm(
13653 &sl.f0,
13654 bits.post_ffw_norm.float_data(),
13655 &mut sl.sn,
13656 n_embd,
13657 1,
13658 eps,
13659 )?;
13660 match next_norm {
13661 Some(w) => {
13662 e.add_scale_rms_norm_q8_1_into(
13663 &sl.sn,
13664 &sl.attn_out,
13665 bits.layer_scale,
13666 w,
13667 &mut sl.xn,
13668 n_embd,
13669 1,
13670 eps,
13671 &mut sl.hq,
13672 &mut sl.hd_,
13673 )?;
13674 }
13675 None => {
13676 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
13677 }
13678 }
13679 Ok(())
13680 }
13681
13682 #[allow(clippy::too_many_arguments)]
13684 fn gemma4_decode_attn_dc(
13685 &self,
13686 e: &Engine,
13687 fa: &crate::hybrid::FullAttnLayer,
13688 il: usize,
13689 hq: &CudaSlice<i8>,
13690 hdq: &CudaSlice<f32>,
13691 pos_d: &CudaSlice<i32>,
13692 cache: &mut Cache,
13693 cap_bucket_max: Option<(usize, usize)>,
13694 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13695 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13696 let eps = self.cfg.rms_eps;
13697 let aux = self.gemma4_aux.as_ref().unwrap();
13698 let ones = aux.ones(e);
13699 #[cfg(debug_assertions)]
13700 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
13701 let (q0, k0, v0) = if swa {
13702 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
13703 Some(t3) => t3,
13704 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13706 Some((q0, k0)) => {
13707 let h0 = e.zeros(0)?;
13708 let v0 = e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?;
13709 (q0, k0, v0)
13710 }
13711 None => {
13712 let h0 = e.zeros(0)?;
13713 (
13714 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13715 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13716 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
13717 )
13718 }
13719 },
13720 }
13721 } else {
13722 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
13723 Some(p) => p,
13724 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13725 Some(p) => p,
13726 None => {
13727 let h0 = e.zeros(0)?;
13728 (
13729 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13730 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13731 )
13732 }
13733 },
13734 };
13735 let v0 = e.clone_dtod(&k0)?;
13736 (q0, k0, v0)
13737 };
13738 let mut q = e.uninit(nh * hd)?;
13739 let mut k = e.uninit(nkv * hd)?;
13740 let mut v = e.uninit(nkv * hd)?;
13741 let ff = if swa {
13743 None
13744 } else {
13745 Some(
13746 aux.rope_freqs(e)
13747 .expect("gemma4 global rope needs rope_freqs.weight"),
13748 )
13749 };
13750 #[cfg(debug_assertions)]
13751 if let Some(ff) = ff {
13752 crate::debug_assert_tensor_stream_device(
13753 ff,
13754 &e.stream(),
13755 "gemma4_decode_attn_dc.rope_freqs",
13756 );
13757 }
13758 let kvl = cache.kv[il].as_mut().unwrap();
13759 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13760 if crate::Engine::qkv_append_on() {
13761 e.rms_norm_qkv_rope_append_dc(
13763 &q0,
13764 &k0,
13765 &v0,
13766 fa.q_norm.float_data(),
13767 fa.k_norm.float_data(),
13768 ones,
13769 &mut q,
13770 &mut k,
13771 &mut v,
13772 hd,
13773 self.gemma4_rope_dims(il),
13774 nh,
13775 nkv,
13776 pos_d,
13777 nh,
13778 nkv,
13779 base,
13780 1.0,
13781 ff,
13782 eps,
13783 &mut kvl.k,
13784 &mut kvl.v,
13785 &kvl.len_d,
13786 kvl.k_tok_bytes,
13787 kvl.v_tok_bytes,
13788 kv_fp8,
13789 )?;
13790 } else {
13791 e.rms_norm_qkv_rope(
13792 &q0,
13793 &k0,
13794 &v0,
13795 fa.q_norm.float_data(),
13796 fa.k_norm.float_data(),
13797 ones,
13798 &mut q,
13799 &mut k,
13800 &mut v,
13801 hd,
13802 self.gemma4_rope_dims(il),
13803 nh,
13804 nkv,
13805 pos_d,
13806 nh,
13807 nkv,
13808 base,
13809 1.0,
13810 ff,
13811 eps,
13812 )?;
13813 e.append_kv_quantized_dc(
13814 &k,
13815 &v,
13816 &mut kvl.k,
13817 &mut kvl.v,
13818 &kvl.len_d,
13819 kvl.kv_dim_k,
13820 kvl.kv_dim_v,
13821 kvl.k_tok_bytes,
13822 kvl.v_tok_bytes,
13823 kv_fp8,
13824 )?;
13825 }
13826 e.inc_seqlen(&mut kvl.len_d)?;
13827 let mut attn = e.uninit(nh * hd)?;
13828 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13831 match cap_bucket_max {
13836 None => {
13837 kvl.len += 1;
13841 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13842 if !swa
13843 && hd == 512
13844 && kvl.len >= crate::fa512_min_tkv()
13845 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13846 {
13847 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13850 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13851 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13852 e.fa_decode_rows(
13853 &q,
13854 &kp,
13855 &vp,
13856 &mut attn,
13857 hd,
13858 nh,
13859 nkv,
13860 kvl.len - 1,
13861 1,
13862 scale,
13863 kvl.k_tok_bytes,
13864 kvl.v_tok_bytes,
13865 Some((&kvl.len_d, -1)),
13866 false,
13867 false,
13868 Some((&mut aq8, &mut ad8)),
13869 )?;
13870 fa_q8 = Some((aq8, ad8));
13871 } else if swa
13872 && kvl.len > win
13873 && hd == 256
13874 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13875 {
13876 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13878 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13879 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13880 e.fa_decode_rows_w(
13881 &q,
13882 &kp,
13883 &vp,
13884 &mut attn,
13885 hd,
13886 nh,
13887 nkv,
13888 &kvl.len_d,
13889 -1,
13890 1,
13891 scale,
13892 win,
13893 kvl.k_tok_bytes,
13894 kvl.v_tok_bytes,
13895 Some((&mut aq8, &mut ad8)),
13896 )?;
13897 fa_q8 = Some((aq8, ad8));
13898 } else {
13899 let (off_tok, t_kv) = if swa && kvl.len > win {
13900 (kvl.len - win, win)
13901 } else {
13902 (0, kvl.len)
13903 };
13904 let k_view = e.view_u8_range(
13905 &kvl.k,
13906 off_tok * kvl.k_tok_bytes,
13907 (off_tok + t_kv) * kvl.k_tok_bytes,
13908 );
13909 let v_view = e.view_u8_range(
13910 &kvl.v,
13911 off_tok * kvl.v_tok_bytes,
13912 (off_tok + t_kv) * kvl.v_tok_bytes,
13913 );
13914 e.fa_decode_kvmod(
13915 &q,
13916 &k_view,
13917 &v_view,
13918 &mut attn,
13919 hd,
13920 nh,
13921 nkv,
13922 t_kv,
13923 scale,
13924 kvl.k_tok_bytes,
13925 kvl.v_tok_bytes,
13926 swa && crate::Engine::wkv_on(),
13927 )?;
13928 }
13929 }
13930 Some((b_swa, b_glob)) => {
13931 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13937 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13938 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13939 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13940 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13941 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13942 e.fa_decode_rows(
13943 &q,
13944 &k_view,
13945 &v_view,
13946 &mut attn,
13947 hd,
13948 nh,
13949 nkv,
13950 b_glob - 1,
13951 1,
13952 scale,
13953 kvl.k_tok_bytes,
13954 kvl.v_tok_bytes,
13955 Some((&kvl.len_d, -1)),
13956 false,
13957 false,
13958 Some((&mut aq8, &mut ad8)),
13959 )?;
13960 fa_q8 = Some((aq8, ad8));
13961 } else if swa && b_swa > win && hd == 256 && rows_on {
13962 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13963 e.fa_decode_rows_w(
13964 &q,
13965 &k_view,
13966 &v_view,
13967 &mut attn,
13968 hd,
13969 nh,
13970 nkv,
13971 &kvl.len_d,
13972 -1,
13973 1,
13974 scale,
13975 win,
13976 kvl.k_tok_bytes,
13977 kvl.v_tok_bytes,
13978 Some((&mut aq8, &mut ad8)),
13979 )?;
13980 fa_q8 = Some((aq8, ad8));
13981 } else {
13982 let b = if swa { b_swa } else { b_glob };
13983 e.fa_decode_dc(
13984 &q,
13985 &k_view,
13986 &v_view,
13987 &mut attn,
13988 hd,
13989 nh,
13990 nkv,
13991 &kvl.len_d,
13992 b,
13993 scale,
13994 kvl.k_tok_bytes,
13995 kvl.v_tok_bytes,
13996 swa && crate::Engine::wkv_on(),
13997 )?;
13998 }
13999 }
14000 }
14001 if let Some((aq8, ad8)) = fa_q8 {
14004 let mut y = e.uninit(fa.wo.out_features())?;
14005 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
14006 return Ok(y);
14007 }
14008 Ok(e.matmul(&fa.wo, &attn, 1)?)
14009 }
14010
14011 pub fn gemma4_generate_graph(
14016 &self,
14017 e: &Engine,
14018 prompt_pos: usize,
14019 first_token: u32,
14020 cache: &mut Cache,
14021 max_new: usize,
14022 eos: &[u32],
14023 mut on_token: impl FnMut(u32) -> bool,
14024 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
14025 if self.is_gemma4_e4b() {
14026 return Err(
14027 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
14028 .into(),
14029 );
14030 }
14031 use crate::decode::StopReason;
14032 let n_vocab = self.output.out_features();
14033 let n_embd = self.cfg.n_embd as usize;
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 for kvl in cache.kv.iter_mut().flatten() {
14039 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
14040 }
14041 let mut token_d = e.stream().clone_htod(&[first_token])?;
14042 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
14043 let g4 = self.cfg.gemma4.as_ref().unwrap();
14044 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
14045 let nkv_s = g4
14047 .head_count_kv
14048 .iter()
14049 .zip(g4.swa_pattern.iter())
14050 .find(|p| *p.1)
14051 .map(|p| *p.0 as usize)
14052 .unwrap_or(8);
14053 let nkv_g = g4
14054 .head_count_kv
14055 .iter()
14056 .zip(g4.swa_pattern.iter())
14057 .find(|p| !*p.1)
14058 .map(|p| *p.0 as usize)
14059 .unwrap_or(2);
14060 let mut graphs: std::collections::HashMap<
14061 ((bool, usize), (bool, usize), bool, bool),
14062 (
14063 cudarc::driver::CudaGraph,
14064 Vec<Box<dyn std::any::Any + Send>>,
14065 ),
14066 > = Default::default();
14067 let mut slots = self.g4_dc_slots(e)?;
14070 const RING: usize = 64;
14073 const DRAIN: usize = 1;
14079 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
14080 let ring_base = prompt_pos;
14081 let mut out = Vec::with_capacity(max_new);
14082 let mut reason = StopReason::MaxNew;
14083 let mut next = first_token;
14084 let mut captures = 0usize;
14085 for _ in 0..max_new {
14086 out.push(next);
14087 if eos.contains(&next) {
14088 reason = StopReason::Eos;
14089 break;
14090 }
14091 if !on_token(next) {
14092 reason = StopReason::Callback;
14093 break;
14094 }
14095 let t_kv = cache.pos + 1;
14096 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14104 let f512 = crate::fa512_min_tkv();
14105 let key_s = if t_kv > win {
14106 (true, usize::MAX)
14107 } else {
14108 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
14109 };
14110 let (key_g, rung_end) = if t_kv >= f512 {
14111 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
14114 ((true, end), end)
14115 } else {
14116 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
14117 };
14118 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
14119 if !graphs.contains_key(&key) {
14120 let bucket_max = (t_kv, rung_end);
14121 let snap = cache.snapshot(e)?;
14123 let pos_save = e.dtoh_i32_one(&pos_d)?;
14124 let len_save: Vec<Option<i32>> = cache
14125 .kv
14126 .iter()
14127 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
14128 .collect();
14129 let tok_save = e.dtoh_u32_one(&token_d)?;
14130 let graph = {
14135 let tok_ref = &mut token_d;
14136 let pos_ref = &mut pos_d;
14137 let cache_ref = &mut *cache;
14138 let slots_ref = &mut slots;
14139 let ring_ref = &mut ring;
14140 e.capture_graph_retained_flags(
14141 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
14142 |e| {
14143 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
14145 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
14146 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
14147 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
14148 cache_ref, n_vocab, Some(bucket_max),
14149 sl, tok_ref, Some((rg, ring_base)))
14150 })?
14151 };
14152 cache.rollback(e, &snap, 0)?;
14153 e.set_i32_one(&mut pos_d, pos_save)?;
14154 for (il, ls) in len_save.iter().enumerate() {
14155 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
14156 e.set_i32_one(&mut kvl.len_d, *v)?;
14157 }
14158 }
14159 e.set_u32_one(&mut token_d, tok_save)?;
14160 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
14161 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
14162 eprintln!("[graph-census] {c:?}");
14163 }
14164 }
14165 graphs.insert(key, graph);
14166 captures += 1;
14167 }
14168 let mut chunk = 1usize;
14173 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
14174 .ok()
14175 .and_then(|v| v.parse().ok())
14176 .unwrap_or(DRAIN);
14177 while chunk < drain_cap && out.len() + chunk < max_new {
14178 let t_next = cache.pos + 1 + chunk;
14179 let key_s2 = if t_next > win {
14180 (true, usize::MAX)
14181 } else {
14182 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
14183 };
14184 let key_g2 = if t_next >= f512 {
14185 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
14186 } else {
14187 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
14188 };
14189 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
14190 break;
14191 }
14192 chunk += 1;
14193 }
14194 let g = &graphs.get(&key).unwrap().0;
14195 for _ in 0..chunk {
14196 g.launch()?;
14197 }
14198 e.stream().synchronize()?;
14199 let ringh = e.dtoh_u32(&ring)?;
14200 for j in 0..chunk {
14201 let pos_j = cache.pos + j;
14202 let tok_j = ringh[(pos_j - ring_base) % RING];
14203 cache.pos += 0; if j + 1 == chunk {
14205 next = tok_j;
14206 } else {
14207 out.push(tok_j);
14208 if eos.contains(&tok_j) || !on_token(tok_j) {
14209 reason = if eos.contains(&tok_j) {
14210 StopReason::Eos
14211 } else {
14212 StopReason::Callback
14213 };
14214 let keep = cache.pos + j + 1;
14216 e.set_i32_one(&mut pos_d, keep as i32)?;
14217 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
14218 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
14219 kvl.len = keep;
14220 }
14221 cache.pos = keep;
14222 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
14223 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
14224 }
14225 return Ok((out, reason));
14226 }
14227 }
14228 }
14229 cache.pos += chunk;
14230 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
14231 kvl.len += chunk;
14232 }
14233 }
14234 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
14235 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
14236 }
14237 Ok((out, reason))
14238 }
14239
14240 pub(crate) fn gemma4_decode_step_t(
14246 &self,
14247 e: &Engine,
14248 tokens: &[u32],
14249 pos0: usize,
14250 cache: &mut Cache,
14251 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
14252 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
14253 }
14254
14255 pub(crate) fn gemma4_decode_step_t_am(
14259 &self,
14260 e: &Engine,
14261 tokens: &[u32],
14262 pos0: usize,
14263 cache: &mut Cache,
14264 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14265 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
14266 let t = tokens.len();
14267 let n_vocab = self.output.out_features();
14268 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
14269 for i in 0..t {
14270 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
14271 }
14272 Ok((e.dtoh_u32(&toks)?, hn))
14273 }
14274
14275 pub(crate) fn gemma4_decode_step_t_am_dev(
14278 &self,
14279 e: &Engine,
14280 tok_d: &CudaSlice<u32>,
14281 t: usize,
14282 pos0: usize,
14283 cache: &mut Cache,
14284 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14285 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
14286 let n_vocab = self.output.out_features();
14287 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
14288 for i in 0..t {
14289 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
14290 }
14291 Ok((vam, hn))
14292 }
14293
14294 pub(crate) fn gemma4_decode_step_t_h(
14297 &self,
14298 e: &Engine,
14299 tokens: &[u32],
14300 pos0: usize,
14301 cache: &mut Cache,
14302 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14303 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
14304 let t = tokens.len();
14305 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14306 e.softcap(&mut ld, cap, t * self.output.out_features())?;
14307 Ok((e.dtoh(&ld)?, hn))
14308 }
14309
14310 pub(crate) fn verify_stream_scratch(
14313 &self,
14314 e: &Engine,
14315 cap: usize,
14316 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
14317 Ok(VerifyStreamScratch {
14318 pos_d: e.htod_i32(&vec![0i32; cap])?,
14319 row_ctrs: (0..cap)
14320 .map(|_| e.htod_i32(&[0]))
14321 .collect::<Result<_, _>>()?,
14322 })
14323 }
14324
14325 pub(crate) fn gemma4_verify_t_am_stream(
14333 &self,
14334 e: &Engine,
14335 tok_d: &CudaSlice<u32>,
14336 t: usize,
14337 ctr: &CudaSlice<i32>,
14338 hint: usize,
14339 cache: &mut Cache,
14340 scr: &mut VerifyStreamScratch,
14341 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14342 let n_embd = self.cfg.n_embd as usize;
14343 let eps = self.cfg.rms_eps;
14344 assert!(t <= scr.row_ctrs.len() && t <= 64);
14345 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
14346 for i in 0..t {
14347 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
14348 }
14349 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
14350 let embd_gpu = self
14351 .embd_gpu
14352 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
14353 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
14354 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
14355 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
14356 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14357 let n_layers = self.layers.len();
14358 for (il, layer) in self.layers.iter().enumerate() {
14359 let (hq, hdq) = match h_carry.take() {
14360 Some(p) => p,
14361 None => {
14362 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
14363 }
14364 };
14365 let Mixer::Full(fa) = &layer.mixer else {
14366 panic!("gemma4 layer {il} not full-attn")
14367 };
14368 let o = self
14369 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
14370 let next_norm = if il + 1 < n_layers {
14371 Some(self.layers[il + 1].attn_norm.float_data())
14372 } else {
14373 None
14374 };
14375 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
14376 x = xn;
14377 h_carry = hn;
14378 self.dflash_tap(e, cache, il, &x, t)?;
14379 }
14380 let mut hn = e.uninit(t * n_embd)?;
14381 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14382 let ld = e.matmul(&self.output, &hn, t)?;
14383 let n_vocab = self.output.out_features();
14384 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
14385 for i in 0..t {
14386 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
14387 }
14388 Ok((vam, hn))
14389 }
14390
14391 pub(crate) fn dflash_tap(
14398 &self,
14399 e: &Engine,
14400 cache: &mut Cache,
14401 il: usize,
14402 x: &CudaSlice<f32>,
14403 t: usize,
14404 ) -> Result<(), Box<dyn std::error::Error>> {
14405 let Some(taps) = cache.dflash_taps.as_mut() else {
14406 return Ok(());
14407 };
14408 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
14409 return Ok(());
14410 };
14411 let h = taps.hidden;
14412 let n_taps = taps.layer_ids.len();
14413 let base = taps.base;
14414 debug_assert!(
14415 base + t <= taps.t,
14416 "tap window {base}+{t} exceeds sink {}",
14417 taps.t
14418 );
14419 let xv = e.view(x, t * h);
14420 for r in 0..t {
14421 let row = xv.slice(r * h..(r + 1) * h);
14422 e.copy_view_into(&mut taps.buf, (base + r) * n_taps * h + slot * h, &row, h)?;
14423 }
14424 Ok(())
14425 }
14426
14427 fn gemma4_verify_trunk(
14428 &self,
14429 e: &Engine,
14430 tokens: &[u32],
14431 pos0: usize,
14432 cache: &mut Cache,
14433 tok_dev: Option<&CudaSlice<u32>>,
14434 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14435 let n_embd = self.cfg.n_embd as usize;
14436 let eps = self.cfg.rms_eps;
14437 let t = tokens.len();
14438 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
14439 let pos_d = e.htod_i32(&pos)?;
14440 let mut x = match tok_dev {
14441 Some(td) => {
14442 let embd_gpu = self
14443 .embd_gpu
14444 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
14445 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
14446 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
14447 }
14448 None => e.htod(&self.embd.gather(n_embd, tokens))?,
14449 };
14450 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
14451 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14452 let n_layers = self.layers.len();
14453 for (il, layer) in self.layers.iter().enumerate() {
14454 let (hq, hdq) = match h_carry.take() {
14455 Some(p) => p,
14456 None => {
14457 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
14458 }
14459 };
14460 let Mixer::Full(fa) = &layer.mixer else {
14461 panic!("gemma4 layer {il} not full-attn")
14462 };
14463 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
14464 let next_norm = if il + 1 < n_layers {
14465 Some(self.layers[il + 1].attn_norm.float_data())
14466 } else {
14467 None
14468 };
14469 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
14470 x = xn;
14471 h_carry = hn;
14472 self.dflash_tap(e, cache, il, &x, t)?;
14473 }
14474 let mut hn = e.uninit(t * n_embd)?;
14475 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14476 let mut ld = e.matmul(&self.output, &hn, t)?;
14477 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
14479 Ok((ld, hn))
14480 }
14481
14482 #[allow(clippy::too_many_arguments)]
14490 fn gemma4_verify_attn_stream(
14491 &self,
14492 e: &Engine,
14493 fa: &crate::hybrid::FullAttnLayer,
14494 il: usize,
14495 hq: &CudaSlice<i8>,
14496 hdq: &CudaSlice<f32>,
14497 pos_d: &CudaSlice<i32>,
14498 t: usize,
14499 cache: &mut Cache,
14500 hint: usize,
14501 row_ctrs: &[CudaSlice<i32>],
14502 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14503 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14504 let eps = self.cfg.rms_eps;
14505 let aux = self.gemma4_aux.as_ref().unwrap();
14506 let ones = aux.ones(e);
14507 #[cfg(debug_assertions)]
14508 crate::debug_assert_tensor_stream_device(
14509 ones,
14510 &e.stream(),
14511 "gemma4_verify_attn_stream.ones",
14512 );
14513 let h0 = e.zeros(0)?;
14514 let h = &h0;
14515 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14518 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14519 let fused_qkv = if f2b {
14520 if swa {
14521 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14522 .map(|(a, b, c)| (a, b, Some(c)))
14523 } else {
14524 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14525 .map(|(a, b)| (a, b, None))
14526 }
14527 } else {
14528 None
14529 };
14530 let (q0, k0, v0) = match fused_qkv {
14531 Some((a, b, cv)) => {
14532 let v = match cv {
14533 Some(c) => c,
14534 None => e.clone_dtod(&b)?,
14535 };
14536 (a, b, v)
14537 }
14538 None => {
14539 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14540 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14541 let v0 = if swa {
14542 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14543 } else {
14544 e.clone_dtod(&k0)?
14545 };
14546 (q0, k0, v0)
14547 }
14548 };
14549 let mut q = e.uninit(t * nh * hd)?;
14550 let mut k = e.uninit(t * nkv * hd)?;
14551 let mut v = e.uninit(t * nkv * hd)?;
14552 let ff = if swa {
14555 None
14556 } else {
14557 Some(
14558 aux.rope_freqs(e)
14559 .expect("gemma4 global rope needs rope_freqs.weight"),
14560 )
14561 };
14562 #[cfg(debug_assertions)]
14563 if let Some(ff) = ff {
14564 crate::debug_assert_tensor_stream_device(
14565 ff,
14566 &e.stream(),
14567 "gemma4_verify_attn_stream.rope_freqs",
14568 );
14569 }
14570 e.rms_norm_qkv_rope(
14571 &q0,
14572 &k0,
14573 &v0,
14574 fa.q_norm.float_data(),
14575 fa.k_norm.float_data(),
14576 ones,
14577 &mut q,
14578 &mut k,
14579 &mut v,
14580 hd,
14581 self.gemma4_rope_dims(il),
14582 nh * t,
14583 nkv * t,
14584 pos_d,
14585 nh,
14586 nkv,
14587 base,
14588 1.0,
14589 ff,
14590 eps,
14591 )?;
14592 let kvl = cache.kv[il].as_mut().unwrap();
14593 e.append_kv_quantized_rows_dc(
14595 &k,
14596 &v,
14597 &mut kvl.k,
14598 &mut kvl.v,
14599 &kvl.len_d,
14600 t,
14601 kvl.kv_dim_k,
14602 kvl.kv_dim_v,
14603 kvl.k_tok_bytes,
14604 kvl.v_tok_bytes,
14605 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14606 )?;
14607 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14610 let mut attn = e.uninit(t * nh * hd)?;
14611 let k_view = e.view_u8(&kvl.k, kvl.k.len());
14612 let v_view = e.view_u8(&kvl.v, kvl.v.len());
14613 if swa && hint + 1 >= win {
14616 e.fa_decode_rows_w(
14619 &q,
14620 &k_view,
14621 &v_view,
14622 &mut attn,
14623 hd,
14624 nh,
14625 nkv,
14626 &kvl.len_d,
14627 0,
14628 t,
14629 scale,
14630 win,
14631 kvl.k_tok_bytes,
14632 kvl.v_tok_bytes,
14633 None,
14634 )?;
14635 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
14636 let bucket = (hint + t + 2)
14649 .next_power_of_two()
14650 .min(crate::fa512_min_tkv().saturating_sub(1));
14651 let qv = e.view(&q, t * nh * hd);
14652 for i in 0..t {
14653 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
14654 let mut q_one = e.uninit(nh * hd)?;
14655 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14656 let mut a_one = e.uninit(nh * hd)?;
14657 e.fa_decode_dc(
14658 &q_one,
14659 &k_view,
14660 &v_view,
14661 &mut a_one,
14662 hd,
14663 nh,
14664 nkv,
14665 &row_ctrs[i],
14666 bucket,
14667 scale,
14668 kvl.k_tok_bytes,
14669 kvl.v_tok_bytes,
14670 false,
14671 )?;
14672 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14673 }
14674 } else if hd == 512 {
14675 e.fa_decode_rows(
14678 &q,
14679 &k_view,
14680 &v_view,
14681 &mut attn,
14682 hd,
14683 nh,
14684 nkv,
14685 hint,
14686 t,
14687 scale,
14688 kvl.k_tok_bytes,
14689 kvl.v_tok_bytes,
14690 Some((&kvl.len_d, 0)),
14691 false,
14692 false,
14693 None,
14694 )?;
14695 } else {
14696 e.fa_decode_rows_dc(
14698 &q,
14699 &k_view,
14700 &v_view,
14701 &mut attn,
14702 hd,
14703 nh,
14704 nkv,
14705 &kvl.len_d,
14706 hint + t,
14707 t,
14708 scale,
14709 kvl.k_tok_bytes,
14710 kvl.v_tok_bytes,
14711 0,
14712 swa && crate::Engine::wkv_on(),
14713 )?;
14714 }
14715 Ok(e.matmul(&fa.wo, &attn, t)?)
14716 }
14717
14718 fn gemma4_verify_attn(
14719 &self,
14720 e: &Engine,
14721 fa: &crate::hybrid::FullAttnLayer,
14722 il: usize,
14723 hq: &CudaSlice<i8>,
14724 hdq: &CudaSlice<f32>,
14725 pos_d: &CudaSlice<i32>,
14726 t: usize,
14727 cache: &mut Cache,
14728 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14729 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14730 let eps = self.cfg.rms_eps;
14731 let aux = self.gemma4_aux.as_ref().unwrap();
14732 let ones = aux.ones(e);
14733 #[cfg(debug_assertions)]
14734 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
14735 let n_embd = self.cfg.n_embd as usize;
14736 let _ = n_embd;
14737
14738 let h0 = e.zeros(0)?;
14739 let h = &h0;
14740 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14743 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14744 let fused_qkv = if f2b {
14745 if swa {
14746 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14747 .map(|(a, b, c)| (a, b, Some(c)))
14748 } else {
14749 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14750 .map(|(a, b)| (a, b, None))
14751 }
14752 } else {
14753 None
14754 };
14755 let (q0, k0, v0) = match fused_qkv {
14756 Some((a, b, cv)) => {
14757 let v = match cv {
14758 Some(c) => c,
14759 None => e.clone_dtod(&b)?,
14760 };
14761 (a, b, v)
14762 }
14763 None => {
14764 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14765 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14766 let v0 = if swa {
14767 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14768 } else {
14769 e.clone_dtod(&k0)?
14770 };
14771 (q0, k0, v0)
14772 }
14773 };
14774 let mut q = e.uninit(t * nh * hd)?;
14775 let mut k = e.uninit(t * nkv * hd)?;
14776 let mut v = e.uninit(t * nkv * hd)?;
14777 let ff = if swa {
14780 None
14781 } else {
14782 Some(
14783 aux.rope_freqs(e)
14784 .expect("gemma4 global rope needs rope_freqs.weight"),
14785 )
14786 };
14787 #[cfg(debug_assertions)]
14788 if let Some(ff) = ff {
14789 crate::debug_assert_tensor_stream_device(
14790 ff,
14791 &e.stream(),
14792 "gemma4_verify_attn.rope_freqs",
14793 );
14794 }
14795 e.rms_norm_qkv_rope(
14796 &q0,
14797 &k0,
14798 &v0,
14799 fa.q_norm.float_data(),
14800 fa.k_norm.float_data(),
14801 ones,
14802 &mut q,
14803 &mut k,
14804 &mut v,
14805 hd,
14806 self.gemma4_rope_dims(il),
14807 nh * t,
14808 nkv * t,
14809 pos_d,
14810 nh,
14811 nkv,
14812 base,
14813 1.0,
14814 ff,
14815 eps,
14816 )?;
14817 let kvl = cache.kv[il].as_mut().unwrap();
14818 let base_len = kvl.len;
14819 e.append_kv_quantized_rows(
14820 &k,
14821 &v,
14822 &mut kvl.k,
14823 &mut kvl.v,
14824 base_len,
14825 t,
14826 kvl.kv_dim_k,
14827 kvl.kv_dim_v,
14828 kvl.k_tok_bytes,
14829 kvl.v_tok_bytes,
14830 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14831 )?;
14832 kvl.len += t;
14833 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14834 let mut attn = e.uninit(t * nh * hd)?;
14835 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
14838 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
14841 if rows_ok && (!swa || base_len + t <= win) {
14842 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14843 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14844 if hd == 512 {
14845 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14847 e.fa_decode_rows(
14848 &q,
14849 &k_view,
14850 &v_view,
14851 &mut attn,
14852 hd,
14853 nh,
14854 nkv,
14855 base_len,
14856 t,
14857 scale,
14858 kvl.k_tok_bytes,
14859 kvl.v_tok_bytes,
14860 Some((&kvl.len_d, 0)),
14861 false,
14862 swa && crate::Engine::wkv_on(),
14863 None,
14864 )?;
14865 } else {
14866 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14870 e.fa_decode_rows_dc(
14871 &q,
14872 &k_view,
14873 &v_view,
14874 &mut attn,
14875 hd,
14876 nh,
14877 nkv,
14878 &kvl.len_d,
14879 base_len + t,
14880 t,
14881 scale,
14882 kvl.k_tok_bytes,
14883 kvl.v_tok_bytes,
14884 0,
14885 swa && crate::Engine::wkv_on(),
14886 )?;
14887 }
14888 return Ok(e.matmul(&fa.wo, &attn, t)?);
14889 }
14890 if hd == 256
14898 && swa
14899 && base_len + 1 >= win
14900 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14901 {
14902 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14903 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14904 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14905 e.fa_decode_rows_w(
14906 &q,
14907 &k_view,
14908 &v_view,
14909 &mut attn,
14910 hd,
14911 nh,
14912 nkv,
14913 &kvl.len_d,
14914 0,
14915 t,
14916 scale,
14917 win,
14918 kvl.k_tok_bytes,
14919 kvl.v_tok_bytes,
14920 None,
14921 )?;
14922 return Ok(e.matmul(&fa.wo, &attn, t)?);
14923 }
14924 for i in 0..t {
14925 let avail = base_len + i + 1;
14926 let (off_tok, t_kv) = if swa && avail > win {
14927 (avail - win, win)
14928 } else {
14929 (0, avail)
14930 };
14931 let k_view = e.view_u8_range(
14932 &kvl.k,
14933 off_tok * kvl.k_tok_bytes,
14934 (off_tok + t_kv) * kvl.k_tok_bytes,
14935 );
14936 let v_view = e.view_u8_range(
14937 &kvl.v,
14938 off_tok * kvl.v_tok_bytes,
14939 (off_tok + t_kv) * kvl.v_tok_bytes,
14940 );
14941 let qi = e.view(&q, t * nh * hd);
14942 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
14943 let mut q_one = e.uninit(nh * hd)?;
14944 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14945 let mut a_one = e.uninit(nh * hd)?;
14946 if swa
14950 && avail > win
14951 && hd == 256
14952 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14953 {
14954 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14955 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14956 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14957 e.fa_decode_rows_w(
14958 &q_one,
14959 &kp,
14960 &vp,
14961 &mut a_one,
14962 hd,
14963 nh,
14964 nkv,
14965 &kvl.len_d,
14966 0,
14967 1,
14968 scale,
14969 win,
14970 kvl.k_tok_bytes,
14971 kvl.v_tok_bytes,
14972 None,
14973 )?;
14974 } else if !swa
14975 && hd == 512
14976 && avail >= crate::fa512_min_tkv()
14977 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14978 {
14979 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14980 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14981 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14982 e.fa_decode_rows(
14983 &q_one,
14984 &kp,
14985 &vp,
14986 &mut a_one,
14987 hd,
14988 nh,
14989 nkv,
14990 avail - 1,
14991 1,
14992 scale,
14993 kvl.k_tok_bytes,
14994 kvl.v_tok_bytes,
14995 Some((&kvl.len_d, 0)),
14996 false,
14997 false,
14998 None,
14999 )?;
15000 } else {
15001 e.fa_decode_kvmod(
15002 &q_one,
15003 &k_view,
15004 &v_view,
15005 &mut a_one,
15006 hd,
15007 nh,
15008 nkv,
15009 t_kv,
15010 scale,
15011 kvl.k_tok_bytes,
15012 kvl.v_tok_bytes,
15013 swa && crate::Engine::wkv_on(),
15014 )?;
15015 }
15016 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
15017 }
15018 Ok(e.matmul(&fa.wo, &attn, t)?)
15019 }
15020
15021 pub(crate) fn gemma4_decode_step_h(
15024 &self,
15025 e: &Engine,
15026 token: u32,
15027 cache: &mut Cache,
15028 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
15029 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
15034 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
15035 }
15036 if crate::pp::pp_cuts(self.layers.len()).is_some() {
15037 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
15038 }
15039 let n_embd = self.cfg.n_embd as usize;
15040 let eps = self.cfg.rms_eps;
15041 let pos_d = e.htod_i32(&[cache.pos as i32])?;
15042 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
15043 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
15044 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
15047 let n_layers = self.layers.len();
15048 for (il, layer) in self.layers.iter().enumerate() {
15049 let (hq, hdq) = match h_carry.take() {
15050 Some(p) => p,
15051 None => {
15052 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
15053 }
15054 };
15055 let Mixer::Full(fa) = &layer.mixer else {
15056 panic!("gemma4 layer {il} not full-attn")
15057 };
15058 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
15059 let next_norm = if il + 1 < n_layers {
15060 Some(self.layers[il + 1].attn_norm.float_data())
15061 } else {
15062 None
15063 };
15064 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
15065 x = xn;
15066 h_carry = hn;
15067 }
15068 let mut hn = e.uninit(n_embd)?;
15069 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
15070 let h_seed = e.clone_dtod(&x)?;
15071 let mut ld = e.matmul(&self.output, &hn, 1)?;
15072 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
15073 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
15075 let logits = e.dtoh(&ld)?;
15076 cache.pos += 1;
15077 Ok((logits, h_seed))
15078 }
15079
15080 fn gemma4_decode_layers(
15088 &self,
15089 e: &Engine,
15090 mut x: CudaSlice<f32>,
15091 lo: usize,
15092 hi: usize,
15093 pos_d: &CudaSlice<i32>,
15094 cache: &mut Cache,
15095 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15096 let n_embd = self.cfg.n_embd as usize;
15097 let eps = self.cfg.rms_eps;
15098 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
15099 for il in lo..hi {
15100 let layer = &self.layers[il];
15101 let (hq, hdq) = match h_carry.take() {
15102 Some(p) => p,
15103 None => {
15105 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
15106 }
15107 };
15108 let Mixer::Full(fa) = &layer.mixer else {
15109 panic!("gemma4 layer {il} not full-attn")
15110 };
15111 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
15112 let next_norm = if il + 1 < hi {
15113 Some(self.layers[il + 1].attn_norm.float_data())
15114 } else {
15115 None
15116 };
15117 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
15118 x = xn;
15119 h_carry = hn;
15120 }
15121 Ok(x)
15122 }
15123
15124 fn gemma4_decode_step_h_pp2(
15132 &self,
15133 e: &Engine,
15134 token: u32,
15135 cache: &mut Cache,
15136 split: usize,
15137 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
15138 if crate::pp::pp2_streams_off() {
15139 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
15140 }
15141 let rt = crate::pp::Pp2Rt::get(e)?;
15142 let e0 = rt.engine(0, e);
15143 let e1 = rt.engine(1, e);
15144 let n_embd = self.cfg.n_embd as usize;
15145 let eps = self.cfg.rms_eps;
15146 let pos = cache.pos as i32;
15147
15148 let slot = {
15150 let _st0 = rt.enter(0);
15151 let pos_d = e0.htod_i32(&[pos])?;
15152 #[cfg(debug_assertions)]
15153 crate::debug_assert_tensor_stream_device(
15154 &pos_d,
15155 &e0.stream(),
15156 "gemma4_decode_step_h_pp2.stage0.pos_d",
15157 );
15158 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
15159 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
15160 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
15161 rt.tx(0, &x, n_embd)?
15162 };
15163
15164 let _st1 = rt.enter(1);
15166 let pos_d = e1.htod_i32(&[pos])?;
15167 #[cfg(debug_assertions)]
15168 crate::debug_assert_tensor_stream_device(
15169 &pos_d,
15170 &e1.stream(),
15171 "gemma4_decode_step_h_pp2.stage1.pos_d",
15172 );
15173 let x = rt.rx(0, slot, n_embd)?;
15174 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
15175
15176 let mut hn = e1.uninit(n_embd)?;
15177 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
15178 let h_seed = e1.clone_dtod(&x)?;
15179 let mut ld = e1.matmul(&self.output, &hn, 1)?;
15180 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
15181 e1.softcap(&mut ld, cap, self.output.out_features())?;
15182 self.gemma4_suppress(e1, &mut ld, 1)?;
15183 let logits = e1.dtoh(&ld)?;
15184 cache.pos += 1;
15185 Ok((logits, h_seed))
15186 }
15187
15188 fn gemma4_decode_step_h_pp2_samestream(
15191 &self,
15192 e: &Engine,
15193 token: u32,
15194 cache: &mut Cache,
15195 split: usize,
15196 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
15197 let n_embd = self.cfg.n_embd as usize;
15198 let eps = self.cfg.rms_eps;
15199 let pos_d = e.htod_i32(&[cache.pos as i32])?;
15200
15201 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
15203 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
15204 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
15205
15206 let boundary_tx = e.clone_dtod(&x)?;
15208 let boundary_rx = e.clone_dtod(&boundary_tx)?;
15209
15210 let x =
15212 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
15213
15214 let mut hn = e.uninit(n_embd)?;
15215 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
15216 let h_seed = e.clone_dtod(&x)?;
15217 let mut ld = e.matmul(&self.output, &hn, 1)?;
15218 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
15219 e.softcap(&mut ld, cap, self.output.out_features())?;
15220 self.gemma4_suppress(e, &mut ld, 1)?;
15221 let logits = e.dtoh(&ld)?;
15222 cache.pos += 1;
15223 Ok((logits, h_seed))
15224 }
15225}
15226
15227impl HybridModel {
15246 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
15249 let geometry = self
15250 .cfg
15251 .layer_geometry(il as u32)
15252 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
15253 debug_assert_eq!(
15254 geometry.attention_gate,
15255 memra_gguf::config::AttentionGateKind::SeparateHead
15256 );
15257 geometry
15258 }
15259
15260 #[allow(clippy::too_many_arguments)]
15320 fn step35_attn_pre_wo(
15321 &self,
15322 e: &Engine,
15323 fa: &FullAttnLayer,
15324 mut g3: Vec<CudaSlice<f32>>,
15325 hg: Option<&CudaSlice<f32>>,
15326 gt_pre: Option<&CudaSlice<f32>>,
15327 pos_d: &CudaSlice<i32>,
15328 t: usize,
15329 cache: Option<&mut Cache>,
15330 il: usize,
15331 seq_end: usize,
15332 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15333 let geometry = self.step35_geom(il);
15334 let hd = geometry.head_dim_k as usize;
15335 let nkv = geometry.n_head_kv as usize;
15336 let nh = geometry.n_head as usize;
15337 let rbase = geometry.rope_base;
15338 let scale = geometry.attention_scale();
15339 let swa = geometry.window.is_some();
15340 let eps = self.cfg.rms_eps;
15341 let win = geometry.window.unwrap_or(0) as usize;
15342 let n_rot = geometry.n_rot as usize;
15343
15344 let v = g3.pop().unwrap();
15345 let k0 = g3.pop().unwrap();
15346 let q0 = g3.pop().unwrap();
15347
15348 let mut q = e.uninit(t * nh * hd)?;
15352 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
15353 let mut k = e.uninit(t * nkv * hd)?;
15354 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
15355 let ff = if geometry.rope_factors {
15356 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
15357 } else {
15358 None
15359 };
15360 #[cfg(debug_assertions)]
15361 if let Some(ff) = ff {
15362 crate::debug_assert_tensor_stream_device(
15363 ff,
15364 &e.stream(),
15365 "step35_attn_pre_wo.rope_freqs",
15366 );
15367 }
15368 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
15369
15370 let mut attn = e.uninit(t * nh * hd)?;
15371 match cache {
15372 Some(cache) => {
15373 let base_len = cache.kv[il].as_ref().unwrap().len;
15374 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
15376 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
15377 let off = if swa {
15378 let raw = base_len.saturating_sub(win - 1);
15379 if legacy_tkv || legacy_calllocal {
15380 raw
15381 } else {
15382 raw & !31usize
15383 }
15384 } else {
15385 0
15386 };
15387 {
15388 let kvl = cache.kv[il].as_mut().unwrap();
15389 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
15390 let write_row = e.prepare_kv_append(kvl, off, t)?;
15391 e.append_kv_quantized_rows(
15392 &k,
15393 &v,
15394 &mut kvl.k,
15395 &mut kvl.v,
15396 write_row,
15397 t,
15398 kvl.kv_dim_k,
15399 kvl.kv_dim_v,
15400 kvl.k_tok_bytes,
15401 kvl.v_tok_bytes,
15402 crate::Engine::kv_fp8_on(),
15403 )?;
15404 kvl.len += t;
15405 let new_len = kvl.len as i32;
15406 e.set_i32_one(&mut kvl.len_d, new_len)?;
15407 }
15408 let kvl = cache.kv[il].as_ref().unwrap();
15409 let t_kv = base_len + t - off;
15432 let physical = kvl.physical_rows(off, off + t_kv)?;
15433 let k_view = e.view_u8_range(
15434 &kvl.k,
15435 physical.start * kvl.k_tok_bytes,
15436 physical.end * kvl.k_tok_bytes,
15437 );
15438 let v_view = e.view_u8_range(
15439 &kvl.v,
15440 physical.start * kvl.v_tok_bytes,
15441 physical.end * kvl.v_tok_bytes,
15442 );
15443 let swa_naive = if legacy_tkv {
15455 t_kv > win
15456 } else {
15457 seq_end > win
15458 };
15459 if swa && swa_naive {
15460 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15473 e.sdpa_naive_w_quantized_view(
15474 &q,
15475 &k_view,
15476 &v_view,
15477 &mut attn,
15478 hd,
15479 nh,
15480 nkv,
15481 t,
15482 t_kv,
15483 scale,
15484 true,
15485 win,
15486 kvl.k_tok_bytes,
15487 kvl.v_tok_bytes,
15488 )?;
15489 } else {
15490 e.fa_prefill_view_ws_w_hd128(
15491 &q,
15492 &k_view,
15493 &v_view,
15494 &mut attn,
15495 hd,
15496 nh,
15497 nkv,
15498 t,
15499 t_kv,
15500 scale,
15501 true,
15502 win,
15503 kvl.k_tok_bytes,
15504 kvl.v_tok_bytes,
15505 )?;
15506 }
15507 } else if std::env::var("MEMRA_NOFA").is_ok() {
15508 e.sdpa_naive_quantized_view(
15509 &q,
15510 &k_view,
15511 &v_view,
15512 &mut attn,
15513 hd,
15514 nh,
15515 nkv,
15516 t,
15517 t_kv,
15518 scale,
15519 true,
15520 kvl.k_tok_bytes,
15521 kvl.v_tok_bytes,
15522 )?;
15523 } else {
15524 e.fa_prefill_view_ws(
15529 &q,
15530 &k_view,
15531 &v_view,
15532 &mut attn,
15533 hd,
15534 nh,
15535 nkv,
15536 t,
15537 t_kv,
15538 scale,
15539 true,
15540 kvl.k_tok_bytes,
15541 kvl.v_tok_bytes,
15542 crate::Engine::kv_fp8_on(),
15543 )?;
15544 }
15545 }
15546 None => {
15547 debug_assert_eq!(
15552 seq_end, t,
15553 "step35 cacheless prefill is monolithic (seq_end == t)"
15554 );
15555 if swa && seq_end > win {
15556 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
15557 } else if std::env::var("MEMRA_NOFA").is_ok() {
15558 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15559 } else {
15560 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15561 }
15562 }
15563 }
15564
15565 let gw = fa
15568 .attn_gate
15569 .as_ref()
15570 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
15571 let gt_owned = if gt_pre.is_none() {
15572 Some(e.matmul(
15573 gw,
15574 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
15575 t,
15576 )?)
15577 } else {
15578 None
15579 };
15580 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
15581 let mut ag = e.uninit(t * nh * hd)?;
15582 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
15583 Ok(ag)
15584 }
15585
15586 pub(crate) fn step35_attn(
15589 &self,
15590 e: &Engine,
15591 fa: &FullAttnLayer,
15592 h: &CudaSlice<f32>,
15593 pos_d: &CudaSlice<i32>,
15594 t: usize,
15595 il: usize,
15596 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15597 let g3 = match self.step35_tp_qkv(e, fa, h, t)? {
15598 Some(g3) => g3,
15599 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15600 };
15601 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
15603 self.step35_o(e, fa, &ag, t)
15604 }
15605
15606 #[allow(clippy::too_many_arguments)]
15613 pub(crate) fn step35_attn_prime(
15614 &self,
15615 e: &Engine,
15616 fa: &FullAttnLayer,
15617 h: &CudaSlice<f32>,
15618 hx: Option<&CudaSlice<u8>>,
15619 pos_d: &CudaSlice<i32>,
15620 t: usize,
15621 cache: &mut Cache,
15622 il: usize,
15623 seq_end: usize,
15624 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15625 if step_tp_prefill_enabled()? && fa.step_tp_qkv.is_some() {
15626 if hx.is_some() {
15627 return Err(
15628 "rank-local Step prefill preserves BF16 activations and refuses the q8_1 \
15629 pre-quantized prime path"
15630 .into(),
15631 );
15632 }
15633 return self.step35_tp_prefill_attn_resident(e, fa, il, h, pos_d, t, cache, seq_end);
15634 }
15635 let g3 = if fa.step_tp_qkv.is_some() {
15636 if hx.is_some() {
15637 return Err(
15638 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
15639 pre-quantized prime path"
15640 .into(),
15641 );
15642 }
15643 self.step35_tp_qkv(e, fa, h, t)?
15644 .expect("Step Q/K/V TP disappeared after the presence check")
15645 } else {
15646 match hx {
15647 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
15648 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15649 }
15650 };
15651 let ag =
15652 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
15653 self.step35_o(e, fa, &ag, t)
15654 }
15655
15656 fn ensure_step_tp_kv_cache(
15657 &self,
15658 e: &Engine,
15659 fa: &FullAttnLayer,
15660 il: usize,
15661 cache: &mut Cache,
15662 ) -> Result<bool, Box<dyn std::error::Error>> {
15663 let tp = fa
15664 .step_tp_qkv
15665 .as_ref()
15666 .ok_or("Step TP cache hydration lost its resident projections")?;
15667 let geometry = self.step35_geom(il);
15668 let window = geometry.window.map(|window| window as usize);
15669 let ranks = tp.runtime.devices().len();
15670 let head_dim = geometry.head_dim_k as usize;
15671 let kv_heads = geometry.n_head_kv as usize;
15672 let max_ctx = cache.max_ctx;
15673
15674 if cache.tp_kv[il].is_some() {
15675 return Ok(false);
15676 }
15677 let local = cache.kv[il]
15678 .as_ref()
15679 .ok_or_else(|| format!("Step TP layer {il} has no owning-stage KV cache"))?;
15680 if local.kv_dim_k != kv_heads * head_dim || local.kv_dim_v != kv_heads * head_dim {
15681 return Err(format!(
15682 "Step TP layer {il} local KV geometry k={} v={} != {}",
15683 local.kv_dim_k,
15684 local.kv_dim_v,
15685 kv_heads * head_dim
15686 )
15687 .into());
15688 }
15689 let resident_start = window
15690 .map(|window| local.len.saturating_sub(window.saturating_sub(1)) & !31usize)
15691 .unwrap_or(0);
15692 let resident_rows = local.len - resident_start;
15693 let physical = local.physical_rows(resident_start, local.len)?;
15694 let k_rows = if resident_rows == 0 {
15695 Vec::new()
15696 } else {
15697 e.dtoh_u8_view(&e.view_u8_range(
15698 &local.k,
15699 physical.start * local.k_tok_bytes,
15700 physical.end * local.k_tok_bytes,
15701 ))?
15702 };
15703 let v_rows = if resident_rows == 0 {
15704 Vec::new()
15705 } else {
15706 e.dtoh_u8_view(&e.view_u8_range(
15707 &local.v,
15708 physical.start * local.v_tok_bytes,
15709 physical.end * local.v_tok_bytes,
15710 ))?
15711 };
15712 let mut distributed = match window {
15713 Some(window) => tp.runtime.allocate_tp_swa_kv_cache(
15714 kv_heads * head_dim,
15715 kv_heads * head_dim,
15716 max_ctx,
15717 window,
15718 )?,
15719 None => tp.runtime.allocate_tp_kv_cache(
15720 kv_heads * head_dim,
15721 kv_heads * head_dim,
15722 max_ctx,
15723 )?,
15724 };
15725 if distributed.k_tok_bytes() * ranks != local.k_tok_bytes
15726 || distributed.v_tok_bytes() * ranks != local.v_tok_bytes
15727 {
15728 return Err(format!(
15729 "Step TP layer {il} distributed/local KV token bytes disagree: \
15730 k={}x{ranks}/{} v={}x{ranks}/{}",
15731 distributed.k_tok_bytes(),
15732 local.k_tok_bytes,
15733 distributed.v_tok_bytes(),
15734 local.v_tok_bytes,
15735 )
15736 .into());
15737 }
15738 tp.runtime.hydrate_tp_kv_cache_from(
15739 &mut distributed,
15740 local.len,
15741 resident_start,
15742 &k_rows,
15743 &v_rows,
15744 )?;
15745 cache.tp_kv[il] = Some(distributed);
15746 Ok(true)
15747 }
15748
15749 #[allow(clippy::too_many_arguments)]
15750 fn step35_tp_prefill_attn_resident(
15751 &self,
15752 e: &Engine,
15753 fa: &FullAttnLayer,
15754 il: usize,
15755 h: &CudaSlice<f32>,
15756 pos_d: &CudaSlice<i32>,
15757 tokens: usize,
15758 cache: &mut Cache,
15759 seq_end: usize,
15760 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15761 let tp = fa
15762 .step_tp_qkv
15763 .as_ref()
15764 .ok_or("Step TP prefill lost its resident projections")?;
15765 let attention = tp
15766 .attention
15767 .as_ref()
15768 .ok_or("Step TP prefill lost its resident attention auxiliaries")?;
15769 let ranks = tp.runtime.devices().len();
15770 if !step_tp_prefill_shape(
15771 true,
15772 tokens,
15773 ranks,
15774 tp.runtime.native_p2p(),
15775 true,
15776 crate::Engine::kv_fp8_on(),
15777 ) {
15778 return Err(format!(
15779 "rank-local Step prefill requires tokens>={PRIME_MIN_T}, TP2/TP4 native P2P, \
15780 rank-local attention, and q8_0/q5_1 KV; got tokens={tokens} ranks={ranks} \
15781 native_p2p={} fp8_kv={}",
15782 tp.runtime.native_p2p(),
15783 crate::Engine::kv_fp8_on(),
15784 )
15785 .into());
15786 }
15787 for seam in [
15788 "MEMRA_STEP35_SWA_TKV",
15789 "MEMRA_PRIME_CALLLOCAL",
15790 "MEMRA_PRIME_F32CHUNK0",
15791 ] {
15792 if std::env::var(seam).as_deref() == Ok("1") {
15793 return Err(format!(
15794 "rank-local Step prefill has not qualified the legacy seam {seam}=1"
15795 )
15796 .into());
15797 }
15798 }
15799
15800 let geometry = self.step35_geom(il);
15801 let window = geometry.window.map(|window| window as usize);
15802 let head_dim = geometry.head_dim_k as usize;
15803 let heads = geometry.n_head as usize;
15804 let kv_heads = geometry.n_head_kv as usize;
15805 if heads % ranks != 0 || kv_heads % ranks != 0 {
15806 return Err(format!(
15807 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15808 )
15809 .into());
15810 }
15811 let local_heads = heads / ranks;
15812 let local_kv_heads = kv_heads / ranks;
15813 let local_kv_dim = local_kv_heads * head_dim;
15814 let hidden = self.cfg.n_embd as usize;
15815 let expected_input = tokens
15816 .checked_mul(hidden)
15817 .ok_or("Step TP prefill input size overflow")?;
15818 if h.len() < expected_input {
15819 return Err(format!(
15820 "Step TP prefill input {} is shorter than {tokens}x{hidden}",
15821 h.len()
15822 )
15823 .into());
15824 }
15825 let positions = e.dtoh_i32(pos_d)?;
15826 if positions.len() != tokens {
15827 return Err(format!(
15828 "rank-local Step prefill positions {} != tokens {tokens}",
15829 positions.len()
15830 )
15831 .into());
15832 }
15833
15834 let mut active_input = e.uninit(expected_input)?;
15835 e.copy_view_into(
15836 &mut active_input,
15837 0,
15838 &h.slice(0..expected_input),
15839 expected_input,
15840 )?;
15841 let mut input = tp.runtime.allocate_replicated_device_rows(tokens, hidden)?;
15842 e.stream().synchronize()?;
15847 tp.runtime
15848 .refresh_replicated_device_rows_from_root(&mut input, &active_input)?;
15849 let q_raw = tp
15850 .runtime
15851 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &input)?;
15852 let k_raw = tp
15853 .runtime
15854 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &input)?;
15855 let v_raw = tp
15856 .runtime
15857 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &input)?;
15858 let mut q = Vec::with_capacity(ranks);
15859 let mut k = Vec::with_capacity(ranks);
15860 for rank in 0..ranks {
15861 let engine = tp
15862 .runtime
15863 .rank_engine(rank)
15864 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15865 let _main = engine.gpu.enter_main()?;
15866 let mut q_rank = engine.uninit(tokens * local_heads * head_dim)?;
15867 engine.rms_norm(
15868 &q_raw[rank],
15869 &attention.q_norm[rank],
15870 &mut q_rank,
15871 head_dim,
15872 tokens * local_heads,
15873 self.cfg.rms_eps,
15874 )?;
15875 let mut k_rank = engine.uninit(tokens * local_kv_dim)?;
15876 engine.rms_norm(
15877 &k_raw[rank],
15878 &attention.k_norm[rank],
15879 &mut k_rank,
15880 head_dim,
15881 tokens * local_kv_heads,
15882 self.cfg.rms_eps,
15883 )?;
15884 let position = engine.htod_i32(&positions)?;
15885 let rope_freqs = if geometry.rope_factors {
15886 self.step35_aux
15887 .as_ref()
15888 .and_then(|aux| aux.rope_freqs(engine))
15889 } else {
15890 None
15891 };
15892 engine.rope_neox2(
15893 &mut q_rank,
15894 &mut k_rank,
15895 &position,
15896 head_dim,
15897 geometry.n_rot as usize,
15898 local_heads,
15899 local_kv_heads,
15900 tokens,
15901 geometry.rope_base,
15902 1.0,
15903 rope_freqs,
15904 )?;
15905 q.push(q_rank);
15906 k.push(k_rank);
15907 }
15908
15909 let gate_weight = fa
15910 .attn_gate
15911 .as_ref()
15912 .ok_or("step35 layer is missing attn_gate.weight")?;
15913 let gate = e.dtoh(&e.matmul(gate_weight, h, tokens)?)?;
15914 if gate.len() != tokens * heads {
15915 return Err(format!(
15916 "Step TP layer {il} gate output {} != {tokens}x{heads}",
15917 gate.len()
15918 )
15919 .into());
15920 }
15921
15922 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15923 let base_len = cache.kv[il]
15924 .as_ref()
15925 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15926 .len;
15927 let distributed = cache.tp_kv[il]
15928 .as_ref()
15929 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15930 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15931 return Err(format!(
15932 "Step TP layer {il} cache lengths diverged before prefill: \
15933 local={base_len} distributed={}/{}",
15934 distributed.committed_len(),
15935 distributed.staged_len()
15936 )
15937 .into());
15938 }
15939 let target_len = base_len
15940 .checked_add(tokens)
15941 .ok_or("Step TP prefill cache length overflow")?;
15942 if target_len > cache.max_ctx {
15943 return Err(format!(
15944 "Step TP layer {il} prefill exceeds cache: {base_len}+{tokens}>{}",
15945 cache.max_ctx
15946 )
15947 .into());
15948 }
15949 if seq_end < target_len {
15950 return Err(format!(
15951 "Step TP layer {il} request end {seq_end} precedes chunk end {target_len}"
15952 )
15953 .into());
15954 }
15955
15956 let transaction = cache.tp_kv[il]
15957 .as_mut()
15958 .expect("distributed cache checked above")
15959 .begin_transaction()?;
15960 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15961 cache.tp_kv[il]
15962 .as_mut()
15963 .expect("distributed cache checked above"),
15964 transaction,
15965 &k,
15966 &v_raw,
15967 tokens,
15968 ) {
15969 let _ = tp.runtime.rollback_tp_kv_transaction(
15970 cache.tp_kv[il]
15971 .as_mut()
15972 .expect("distributed cache checked above"),
15973 transaction,
15974 );
15975 return Err(error);
15976 }
15977
15978 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15979 let distributed = cache.tp_kv[il]
15980 .as_ref()
15981 .expect("distributed cache checked above");
15982 let staged_len = distributed.staged_len();
15983 let view_start = window
15984 .map(|window| base_len.saturating_sub(window.saturating_sub(1)) & !31usize)
15985 .unwrap_or(0);
15986 let physical = distributed.physical_range(view_start, staged_len)?;
15987 let t_kv = staged_len - view_start;
15988 let swa_naive = window.is_some_and(|window| seq_end > window);
15989 let mut gated = Vec::with_capacity(ranks);
15990 for rank in 0..ranks {
15991 let engine = tp
15992 .runtime
15993 .rank_engine(rank)
15994 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15995 let _main = engine.gpu.enter_main()?;
15996 let rank_cache = distributed
15997 .rank(rank)
15998 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
15999 let k_view = engine.view_u8_range(
16000 rank_cache.k(),
16001 physical.start * distributed.k_tok_bytes(),
16002 physical.end * distributed.k_tok_bytes(),
16003 );
16004 let v_view = engine.view_u8_range(
16005 rank_cache.v(),
16006 physical.start * distributed.v_tok_bytes(),
16007 physical.end * distributed.v_tok_bytes(),
16008 );
16009 let mut attention_out = engine.uninit(tokens * local_heads * head_dim)?;
16010 if swa_naive {
16011 let window = window.expect("SWA predicate requires a window");
16012 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
16013 engine.sdpa_naive_w_quantized_view(
16014 &q[rank],
16015 &k_view,
16016 &v_view,
16017 &mut attention_out,
16018 head_dim,
16019 local_heads,
16020 local_kv_heads,
16021 tokens,
16022 t_kv,
16023 geometry.attention_scale(),
16024 true,
16025 window,
16026 distributed.k_tok_bytes(),
16027 distributed.v_tok_bytes(),
16028 )?;
16029 } else {
16030 engine.fa_prefill_view_ws_w_hd128(
16031 &q[rank],
16032 &k_view,
16033 &v_view,
16034 &mut attention_out,
16035 head_dim,
16036 local_heads,
16037 local_kv_heads,
16038 tokens,
16039 t_kv,
16040 geometry.attention_scale(),
16041 true,
16042 window,
16043 distributed.k_tok_bytes(),
16044 distributed.v_tok_bytes(),
16045 )?;
16046 }
16047 } else if std::env::var("MEMRA_NOFA").is_ok() {
16048 engine.sdpa_naive_quantized_view(
16049 &q[rank],
16050 &k_view,
16051 &v_view,
16052 &mut attention_out,
16053 head_dim,
16054 local_heads,
16055 local_kv_heads,
16056 tokens,
16057 t_kv,
16058 geometry.attention_scale(),
16059 true,
16060 distributed.k_tok_bytes(),
16061 distributed.v_tok_bytes(),
16062 )?;
16063 } else {
16064 engine.fa_prefill_view_ws(
16065 &q[rank],
16066 &k_view,
16067 &v_view,
16068 &mut attention_out,
16069 head_dim,
16070 local_heads,
16071 local_kv_heads,
16072 tokens,
16073 t_kv,
16074 geometry.attention_scale(),
16075 true,
16076 distributed.k_tok_bytes(),
16077 distributed.v_tok_bytes(),
16078 false,
16079 )?;
16080 }
16081
16082 let gate_start = rank * local_heads;
16083 let mut gate_rank = Vec::with_capacity(tokens * local_heads);
16084 for token in 0..tokens {
16085 let start = token * heads + gate_start;
16086 gate_rank.extend_from_slice(&gate[start..start + local_heads]);
16087 }
16088 let gate_rank = engine.htod(&gate_rank)?;
16089 let mut gated_rank = engine.uninit(tokens * local_heads * head_dim)?;
16090 engine.attn_head_gate(
16091 &attention_out,
16092 &gate_rank,
16093 &mut gated_rank,
16094 None,
16095 head_dim,
16096 local_heads,
16097 tokens,
16098 )?;
16099 gated.push(gated_rank);
16100 }
16101 for rank in 1..ranks {
16102 let engine = tp
16103 .runtime
16104 .rank_engine(rank)
16105 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16106 let _main = engine.gpu.enter_main()?;
16107 engine.stream().synchronize()?;
16108 }
16109
16110 let (output, k_shadow, v_shadow) = if tp.runtime.bulk_p2p() {
16111 let output = tp
16112 .runtime
16113 .step_bf16_row_parallel_resident_root_device(&tp.o, &gated, tokens)?;
16114 let k_shadow =
16115 tp.runtime
16116 .gather_native_column_shards_device(&k, tokens, local_kv_dim)?;
16117 let v_shadow =
16118 tp.runtime
16119 .gather_native_column_shards_device(&v_raw, tokens, local_kv_dim)?;
16120 let root = tp
16121 .runtime
16122 .rank_engine(0)
16123 .ok_or("Step TP prefill lost its root engine")?;
16124 let _main = root.gpu.enter_main()?;
16125 root.stream().synchronize()?;
16126 (output, k_shadow, v_shadow)
16127 } else {
16128 let attention = tp.runtime.gather_native_column_shards(
16129 &gated,
16130 tokens,
16131 local_heads * head_dim,
16132 )?;
16133 let output = tp
16134 .runtime
16135 .step_bf16_row_parallel_resident_native(&tp.o, &attention, tokens)?;
16136 let k_shadow = tp
16137 .runtime
16138 .gather_native_column_shards(&k, tokens, local_kv_dim)?;
16139 let v_shadow =
16140 tp.runtime
16141 .gather_native_column_shards(&v_raw, tokens, local_kv_dim)?;
16142 (e.htod(&output)?, e.htod(&k_shadow)?, e.htod(&v_shadow)?)
16143 };
16144 let local = cache.kv[il]
16145 .as_mut()
16146 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16147 if local.len != base_len {
16148 return Err(format!(
16149 "Step TP layer {il} local cache changed during prefill: \
16150 len={} base={base_len}",
16151 local.len
16152 )
16153 .into());
16154 }
16155 let retain_from = window
16156 .map(|window| {
16157 let staged_retain = staged_len.saturating_sub(window) & !31usize;
16158 let rollback_retain =
16159 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16160 staged_retain.min(rollback_retain)
16161 })
16162 .unwrap_or(0);
16163 let write_row = e.prepare_kv_append(local, retain_from, tokens)?;
16164 e.append_kv_quantized_rows(
16165 &k_shadow,
16166 &v_shadow,
16167 &mut local.k,
16168 &mut local.v,
16169 write_row,
16170 tokens,
16171 local.kv_dim_k,
16172 local.kv_dim_v,
16173 local.k_tok_bytes,
16174 local.v_tok_bytes,
16175 false,
16176 )?;
16177 local.len = staged_len;
16178 e.set_i32_one(&mut local.len_d, staged_len as i32)?;
16179 Ok(output)
16180 })();
16181
16182 let output = match staged {
16183 Ok(output) => output,
16184 Err(error) => {
16185 let _ = tp.runtime.rollback_tp_kv_transaction(
16186 cache.tp_kv[il]
16187 .as_mut()
16188 .expect("distributed cache checked above"),
16189 transaction,
16190 );
16191 if let Some(local) = cache.kv[il].as_mut() {
16192 local.len = base_len;
16193 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16194 }
16195 return Err(error);
16196 }
16197 };
16198 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16199 cache.tp_kv[il]
16200 .as_mut()
16201 .expect("distributed cache checked above"),
16202 transaction,
16203 tokens,
16204 ) {
16205 let _ = tp.runtime.rollback_tp_kv_transaction(
16206 cache.tp_kv[il]
16207 .as_mut()
16208 .expect("distributed cache checked above"),
16209 transaction,
16210 );
16211 let local = cache.kv[il].as_mut().expect("local cache checked above");
16212 local.len = base_len;
16213 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16214 return Err(error);
16215 }
16216
16217 let committed = cache.tp_kv[il]
16218 .as_ref()
16219 .expect("distributed cache checked above")
16220 .committed_len();
16221 let local_len = cache.kv[il]
16222 .as_ref()
16223 .expect("local cache checked above")
16224 .len;
16225 if committed != local_len {
16226 return Err(format!(
16227 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16228 )
16229 .into());
16230 }
16231 eprintln!(
16232 "[step-tp-prefill-attn] execute layer={} devices={:?} tokens={tokens} \
16233 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16234 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16235 attention_scope={} input_path=root-device-replicated gate_tensor_parallel=false \
16236 gate_shards=host-canonical o_tensor_parallel=true local_cache_shadow=true \
16237 cache_commit=chunk transport={} native_p2p=true bulk_p2p={} \
16238 output={} performance_claim=false",
16239 tp.layer,
16240 tp.devices,
16241 hydrated,
16242 if window.is_some() {
16243 "rank-local-swa-ring"
16244 } else {
16245 "rank-local-global"
16246 },
16247 tp.runtime.transport_label(),
16248 tp.runtime.bulk_p2p(),
16249 if tp.runtime.bulk_p2p() {
16250 "root-device"
16251 } else {
16252 "root-readback"
16253 },
16254 );
16255 Ok(output)
16256 }
16257
16258 fn step35_tp_decode_attn_resident(
16259 &self,
16260 e: &Engine,
16261 fa: &FullAttnLayer,
16262 il: usize,
16263 h: &CudaSlice<f32>,
16264 pos_d: &CudaSlice<i32>,
16265 cache: &mut Cache,
16266 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16267 static ATTN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16271 static ATTN_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16272 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
16273 let started = timing.then(std::time::Instant::now);
16274 let result = if crate::tp::step_tp_decode_v2_enabled()? {
16275 self.step35_tp_decode_attn_resident_v2(e, fa, il, h, pos_d, cache)
16276 } else {
16277 self.step35_tp_decode_attn_resident_inner(e, fa, il, h, pos_d, cache)
16278 };
16279 if let Some(started) = started {
16280 use std::sync::atomic::Ordering;
16281 let ns = ATTN_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
16282 + started.elapsed().as_nanos() as u64;
16283 let calls = ATTN_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16284 if calls % 430 == 0 {
16285 eprintln!(
16286 "[step-tp-attn-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
16287 ns as f64 / 1.0e6,
16288 ns as f64 / calls as f64 / 1.0e3,
16289 );
16290 }
16291 }
16292 result
16293 }
16294
16295 #[allow(clippy::too_many_arguments)]
16296 fn step35_tp_decode_attn_resident_inner(
16297 &self,
16298 e: &Engine,
16299 fa: &FullAttnLayer,
16300 il: usize,
16301 h: &CudaSlice<f32>,
16302 pos_d: &CudaSlice<i32>,
16303 cache: &mut Cache,
16304 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16305 static T_POS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16310 static T_QKV: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16311 static T_NORMROPE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16312 static T_GATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16313 static T_APPEND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16314 static T_ATTN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16315 static T_OPROJ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16316 static T_SHADOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16317 static T_PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
16318 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
16319 fn lap(
16320 runtime: &crate::tp::TpE4m3HostBounce,
16321 e: &Engine,
16322 timer: &std::sync::atomic::AtomicU64,
16323 started: &mut Option<std::time::Instant>,
16324 ) -> Result<(), Box<dyn std::error::Error>> {
16325 let Some(start) = started.as_mut() else {
16326 return Ok(());
16327 };
16328 for rank in 0..runtime.devices().len() {
16329 if let Some(engine) = runtime.rank_engine(rank) {
16330 let _main = engine.gpu.enter_main()?;
16331 engine.stream().synchronize()?;
16332 }
16333 }
16334 e.stream().synchronize()?;
16335 timer.fetch_add(
16336 start.elapsed().as_nanos() as u64,
16337 std::sync::atomic::Ordering::Relaxed,
16338 );
16339 *start = std::time::Instant::now();
16340 Ok(())
16341 }
16342 let tp = fa
16343 .step_tp_qkv
16344 .as_ref()
16345 .ok_or("Step TP decode lost its resident projections")?;
16346 let attention = tp
16347 .attention
16348 .as_ref()
16349 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
16350 if !tp.runtime.native_p2p() {
16351 return Err("rank-local Step attention requires native P2P".into());
16352 }
16353 if crate::Engine::kv_fp8_on() {
16354 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
16355 }
16356
16357 let geometry = self.step35_geom(il);
16358 let window = geometry.window.map(|window| window as usize);
16359 let ranks = tp.runtime.devices().len();
16360 let head_dim = geometry.head_dim_k as usize;
16361 let heads = geometry.n_head as usize;
16362 let kv_heads = geometry.n_head_kv as usize;
16363 if heads % ranks != 0 || kv_heads % ranks != 0 {
16364 return Err(format!(
16365 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
16366 )
16367 .into());
16368 }
16369 let local_heads = heads / ranks;
16370 let local_kv_heads = kv_heads / ranks;
16371 let local_kv_dim = local_kv_heads * head_dim;
16372 let max_ctx = cache.max_ctx;
16373
16374 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
16375
16376 let base_len = cache.kv[il]
16377 .as_ref()
16378 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
16379 .len;
16380 let distributed = cache.tp_kv[il]
16381 .as_ref()
16382 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
16383 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
16384 return Err(format!(
16385 "Step TP layer {il} cache lengths diverged before decode: \
16386 local={base_len} distributed={}/{}",
16387 distributed.committed_len(),
16388 distributed.staged_len()
16389 )
16390 .into());
16391 }
16392
16393 let mut lap_start = timing.then(std::time::Instant::now);
16394 let positions = e.dtoh_i32(pos_d)?;
16395 if positions.len() != 1 {
16396 return Err(format!(
16397 "rank-local Step decode requires one position, got {}",
16398 positions.len()
16399 )
16400 .into());
16401 }
16402 lap(&tp.runtime, e, &T_POS, &mut lap_start)?;
16403 let (q_raw, k_raw, v_raw, input_path) = if let Some(decode_input) =
16404 attention.decode_input.as_ref()
16405 {
16406 let mut decode_input = decode_input
16407 .lock()
16408 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16409 e.stream().synchronize()?;
16413 tp.runtime
16414 .refresh_replicated_device_rows_from_root(&mut decode_input, h)?;
16415 let q_raw = tp
16416 .runtime
16417 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &decode_input)?;
16418 let k_raw = tp
16419 .runtime
16420 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &decode_input)?;
16421 let v_raw = tp
16422 .runtime
16423 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &decode_input)?;
16424 (q_raw, k_raw, v_raw, "root-device-replicated")
16425 } else {
16426 let activation = e.dtoh(h)?;
16427 let q_raw =
16428 tp.runtime
16429 .bf16_column_parallel_resident_device_shards(&tp.q, &activation, 1)?;
16430 let k_raw =
16431 tp.runtime
16432 .bf16_column_parallel_resident_device_shards(&tp.k, &activation, 1)?;
16433 let v_raw =
16434 tp.runtime
16435 .bf16_column_parallel_resident_device_shards(&tp.v, &activation, 1)?;
16436 (q_raw, k_raw, v_raw, "host-replicated")
16437 };
16438 lap(&tp.runtime, e, &T_QKV, &mut lap_start)?;
16439 let mut q = Vec::with_capacity(ranks);
16440 let mut k = Vec::with_capacity(ranks);
16441 for rank in 0..ranks {
16442 let engine = tp
16443 .runtime
16444 .rank_engine(rank)
16445 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16446 let _main = engine.gpu.enter_main()?;
16447 let mut q_rank = engine.uninit(local_heads * head_dim)?;
16448 engine.rms_norm(
16449 &q_raw[rank],
16450 &attention.q_norm[rank],
16451 &mut q_rank,
16452 head_dim,
16453 local_heads,
16454 self.cfg.rms_eps,
16455 )?;
16456 let mut k_rank = engine.uninit(local_kv_dim)?;
16457 engine.rms_norm(
16458 &k_raw[rank],
16459 &attention.k_norm[rank],
16460 &mut k_rank,
16461 head_dim,
16462 local_kv_heads,
16463 self.cfg.rms_eps,
16464 )?;
16465 let position = engine.htod_i32(&positions)?;
16466 let rope_freqs = if geometry.rope_factors {
16467 self.step35_aux
16468 .as_ref()
16469 .and_then(|aux| aux.rope_freqs(engine))
16470 } else {
16471 None
16472 };
16473 engine.rope_neox2(
16474 &mut q_rank,
16475 &mut k_rank,
16476 &position,
16477 head_dim,
16478 geometry.n_rot as usize,
16479 local_heads,
16480 local_kv_heads,
16481 1,
16482 geometry.rope_base,
16483 1.0,
16484 rope_freqs,
16485 )?;
16486 q.push(q_rank);
16487 k.push(k_rank);
16488 }
16489 lap(&tp.runtime, e, &T_NORMROPE, &mut lap_start)?;
16490
16491 let gate_weight = fa
16492 .attn_gate
16493 .as_ref()
16494 .ok_or("step35 layer is missing attn_gate.weight")?;
16495 let gate = e.matmul(gate_weight, h, 1)?;
16496 let gate = e.dtoh(&gate)?;
16497 if gate.len() != heads {
16498 return Err(format!("Step TP layer {il} gate output {} != {heads}", gate.len()).into());
16499 }
16500 lap(&tp.runtime, e, &T_GATE, &mut lap_start)?;
16501
16502 let transaction = cache.tp_kv[il]
16503 .as_mut()
16504 .expect("distributed cache checked above")
16505 .begin_transaction()?;
16506 if let Err(error) = tp.runtime.append_tp_kv_transaction(
16507 cache.tp_kv[il]
16508 .as_mut()
16509 .expect("distributed cache checked above"),
16510 transaction,
16511 &k,
16512 &v_raw,
16513 1,
16514 ) {
16515 let _ = tp.runtime.rollback_tp_kv_transaction(
16516 cache.tp_kv[il]
16517 .as_mut()
16518 .expect("distributed cache checked above"),
16519 transaction,
16520 );
16521 return Err(error);
16522 }
16523 lap(&tp.runtime, e, &T_APPEND, &mut lap_start)?;
16524
16525 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16526 let distributed = cache.tp_kv[il]
16527 .as_ref()
16528 .expect("distributed cache checked above");
16529 let staged_len = distributed.staged_len();
16530 let view_start = window
16531 .map(|window| staged_len.saturating_sub(window))
16532 .unwrap_or(0);
16533 let physical = distributed.physical_range(view_start, staged_len)?;
16534 let t_kv = staged_len - view_start;
16535 let mut gated = Vec::with_capacity(ranks);
16536 for rank in 0..ranks {
16537 let engine = tp
16538 .runtime
16539 .rank_engine(rank)
16540 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16541 let _main = engine.gpu.enter_main()?;
16542 let rank_cache = distributed
16543 .rank(rank)
16544 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16545 let k_view = engine.view_u8_range(
16546 rank_cache.k(),
16547 physical.start * distributed.k_tok_bytes(),
16548 physical.end * distributed.k_tok_bytes(),
16549 );
16550 let v_view = engine.view_u8_range(
16551 rank_cache.v(),
16552 physical.start * distributed.v_tok_bytes(),
16553 physical.end * distributed.v_tok_bytes(),
16554 );
16555 let mut attention_out = engine.uninit(local_heads * head_dim)?;
16556 engine.fa_decode_kvmod(
16557 &q[rank],
16558 &k_view,
16559 &v_view,
16560 &mut attention_out,
16561 head_dim,
16562 local_heads,
16563 local_kv_heads,
16564 t_kv,
16565 geometry.attention_scale(),
16566 distributed.k_tok_bytes(),
16567 distributed.v_tok_bytes(),
16568 false,
16569 )?;
16570 let gate_start = rank * local_heads;
16571 let gate_rank = engine.htod(&gate[gate_start..gate_start + local_heads])?;
16572 let mut gated_rank = engine.uninit(local_heads * head_dim)?;
16573 engine.attn_head_gate(
16574 &attention_out,
16575 &gate_rank,
16576 &mut gated_rank,
16577 None,
16578 head_dim,
16579 local_heads,
16580 1,
16581 )?;
16582 gated.push(gated_rank);
16583 }
16584 lap(&tp.runtime, e, &T_ATTN, &mut lap_start)?;
16585
16586 let gathered =
16587 tp.runtime
16588 .gather_native_column_shards(&gated, 1, local_heads * head_dim)?;
16589 let output = tp
16590 .runtime
16591 .step_bf16_row_parallel_resident_native(&tp.o, &gathered, 1)?;
16592 let output = e.htod(&output)?;
16593 lap(&tp.runtime, e, &T_OPROJ, &mut lap_start)?;
16594
16595 let k_shadow = tp
16596 .runtime
16597 .gather_native_column_shards(&k, 1, local_kv_dim)?;
16598 let v_shadow = tp
16599 .runtime
16600 .gather_native_column_shards(&v_raw, 1, local_kv_dim)?;
16601 let k_shadow = e.htod(&k_shadow)?;
16602 let v_shadow = e.htod(&v_shadow)?;
16603 let local = cache.kv[il]
16604 .as_mut()
16605 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16606 if local.len != base_len || base_len + 1 > max_ctx {
16607 return Err(format!(
16608 "Step TP layer {il} local cache changed during decode: \
16609 len={} base={base_len} max={max_ctx}",
16610 local.len
16611 )
16612 .into());
16613 }
16614 let retain_from = window
16615 .map(|window| {
16616 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16617 let rollback_retain =
16618 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16619 staged_retain.min(rollback_retain)
16620 })
16621 .unwrap_or(0);
16622 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16623 e.append_kv_quantized(
16624 &k_shadow,
16625 &v_shadow,
16626 &mut local.k,
16627 &mut local.v,
16628 write_row,
16629 local.kv_dim_k,
16630 local.kv_dim_v,
16631 local.k_tok_bytes,
16632 local.v_tok_bytes,
16633 false,
16634 )?;
16635 local.len = base_len + 1;
16636 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16637 Ok(output)
16638 })();
16639
16640 let output = match staged {
16641 Ok(output) => output,
16642 Err(error) => {
16643 let _ = tp.runtime.rollback_tp_kv_transaction(
16644 cache.tp_kv[il]
16645 .as_mut()
16646 .expect("distributed cache checked above"),
16647 transaction,
16648 );
16649 if let Some(local) = cache.kv[il].as_mut() {
16650 local.len = base_len;
16651 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16652 }
16653 return Err(error);
16654 }
16655 };
16656 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16657 cache.tp_kv[il]
16658 .as_mut()
16659 .expect("distributed cache checked above"),
16660 transaction,
16661 1,
16662 ) {
16663 let _ = tp.runtime.rollback_tp_kv_transaction(
16664 cache.tp_kv[il]
16665 .as_mut()
16666 .expect("distributed cache checked above"),
16667 transaction,
16668 );
16669 let local = cache.kv[il].as_mut().expect("local cache checked above");
16670 local.len = base_len;
16671 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16672 return Err(error);
16673 }
16674
16675 let committed = cache.tp_kv[il]
16676 .as_ref()
16677 .expect("distributed cache checked above")
16678 .committed_len();
16679 let local_len = cache.kv[il]
16680 .as_ref()
16681 .expect("local cache checked above")
16682 .len;
16683 if committed != local_len {
16684 return Err(format!(
16685 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16686 )
16687 .into());
16688 }
16689 lap(&tp.runtime, e, &T_SHADOW, &mut lap_start)?;
16690 if timing {
16691 use std::sync::atomic::Ordering;
16692 let calls = T_PHASE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16693 if calls % 430 == 0 {
16694 let avg = |t: &std::sync::atomic::AtomicU64| {
16695 t.load(Ordering::Relaxed) as f64 / calls as f64 / 1.0e3
16696 };
16697 eprintln!(
16698 "[step-tp-attn-phase] calls={calls} avg_us pos={:.1} qkv={:.1} \
16699 normrope={:.1} gate={:.1} append={:.1} attn={:.1} oproj={:.1} shadow={:.1}",
16700 avg(&T_POS),
16701 avg(&T_QKV),
16702 avg(&T_NORMROPE),
16703 avg(&T_GATE),
16704 avg(&T_APPEND),
16705 avg(&T_ATTN),
16706 avg(&T_OPROJ),
16707 avg(&T_SHADOW),
16708 );
16709 }
16710 }
16711 eprintln!(
16712 "[step-tp-attn] execute layer={} devices={:?} tokens=1 \
16713 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16714 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16715 attention_scope={} input_path={} kv_physical_rows={} \
16716 gate_tensor_parallel=false gate_shards=host-canonical o_tensor_parallel=true \
16717 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16718 bulk_p2p={} output=root-readback performance_claim=false",
16719 tp.layer,
16720 tp.devices,
16721 hydrated,
16722 if window.is_some() {
16723 "rank-local-swa-ring"
16724 } else {
16725 "rank-local-global"
16726 },
16727 input_path,
16728 cache.tp_kv[il]
16729 .as_ref()
16730 .expect("distributed cache checked above")
16731 .physical_capacity(),
16732 tp.runtime.transport_label(),
16733 tp.runtime.bulk_p2p(),
16734 );
16735 Ok(output)
16736 }
16737
16738 #[allow(clippy::too_many_arguments)]
16745 pub(crate) fn step35_verify_qkv_precompute(
16750 &self,
16751 e: &Engine,
16752 il: usize,
16753 h_t: &CudaSlice<f32>,
16754 t: usize,
16755 ) -> Result<bool, Box<dyn std::error::Error>> {
16756 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16757 return Ok(false);
16758 };
16759 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16760 return Ok(false);
16761 };
16762 let Some(attention) = tp.attention.as_ref() else {
16763 return Ok(false);
16764 };
16765 if !tp.runtime.native_p2p() || !crate::tp::step_tp_qkv_fused_enabled()? {
16766 return Ok(false);
16767 }
16768 let geometry = self.step35_geom(il);
16769 let heads = geometry.n_head as usize;
16770 let ws_index = tp
16771 .runtime
16772 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16773 let gate_shards = attention
16774 .gate_shards_bf16
16775 .as_deref()
16776 .map(crate::tp::StepTpGateShards::Bf16);
16777 tp.runtime.decode_v2_input_qkv_tcol(
16778 ws_index,
16779 e,
16780 h_t,
16781 t,
16782 &tp.q,
16783 &tp.k,
16784 &tp.v,
16785 gate_shards,
16786 )?;
16787 Ok(true)
16788 }
16789
16790 pub(crate) fn step35_verify_oproj_tcol(
16795 &self,
16796 e: &Engine,
16797 il: usize,
16798 t: usize,
16799 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16800 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16801 return Err("tcol o_proj join expects full attention".into());
16802 };
16803 let tp = fa
16804 .step_tp_qkv
16805 .as_ref()
16806 .ok_or("tcol o_proj join lost its resident projections")?;
16807 let heads = self.step35_geom(il).n_head as usize;
16808 let ws_index = tp
16809 .runtime
16810 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16811 tp.runtime.decode_v2_oproj_tcol(ws_index, e, &tp.o, t)
16812 }
16813
16814 pub(crate) fn step35_spec_fa2_precheck(
16821 &self,
16822 cache: &Cache,
16823 il: usize,
16824 pos0: usize,
16825 ) -> Result<bool, Box<dyn std::error::Error>> {
16826 fn nope(clause: &str, il: usize, pos0: usize) -> bool {
16829 static DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16830 static SEEN: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
16831 if *DBG.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_DEBUG").as_deref() == Ok("1")) {
16832 let mut seen = SEEN.lock().unwrap();
16833 if !seen.iter().any(|c| *c == clause) {
16834 seen.push(Box::leak(clause.to_string().into_boxed_str()));
16836 eprintln!("[spec-fa2] precheck FAIL clause={clause} il={il} pos0={pos0}");
16837 }
16838 }
16839 false
16840 }
16841 static ONLY: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
16843 if let Some(only) =
16844 ONLY.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_LAYER").ok()?.parse().ok())
16845 {
16846 if *only != il {
16847 return Ok(false);
16848 }
16849 }
16850 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16851 return Ok(nope("mixer", il, pos0));
16852 };
16853 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16854 return Ok(nope("step_tp", il, pos0));
16855 };
16856 let Some(attention) = tp.attention.as_ref() else {
16857 return Ok(nope("attention", il, pos0));
16858 };
16859 if !tp.runtime.native_p2p()
16860 || crate::Engine::kv_fp8_on()
16861 || !crate::tp::step_tp_dcw_enabled()?
16862 || !crate::tp::step_tp_qkv_fused_enabled()?
16863 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16864 {
16865 return Ok(nope("runtime-doors", il, pos0));
16866 }
16867 let geometry = self.step35_geom(il);
16868 let head_dim = geometry.head_dim_k as usize;
16869 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16870 return Ok(nope("fa-class", il, pos0));
16871 }
16872 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16873 return Ok(nope("tp-kv", il, pos0));
16874 };
16875 if distributed.staged_len() != pos0 {
16876 return Ok(nope("staged-len", il, pos0));
16877 }
16878 let (_, would_rebase) = distributed.peek_append_ring(2)?;
16881 if would_rebase {
16882 return Ok(nope("rebase", il, pos0));
16883 }
16884 let window = geometry.window.map(|w| w as usize);
16885 if let Some(w) = window {
16893 if pos0 + 2 > w {
16894 return Ok(nope("swa-capped", il, pos0));
16895 }
16896 }
16897 let (t0, t1) = (pos0 + 1, pos0 + 2);
16901 if t0 < 96 {
16902 return Ok(nope("dcw-floor", il, pos0));
16903 }
16904 if std::env::var("MEMRA_NO_FA_VEC").is_ok() || t0 < crate::fa_vec_min_tkv() {
16905 return Ok(nope("vec-floor", il, pos0));
16906 }
16907 let ranks = tp.runtime.devices().len();
16913 let local_kv_heads = (geometry.n_head_kv as usize / ranks).max(1);
16914 let sp0 = crate::fa_split_keys_pub(t0, local_kv_heads);
16915 let sp1 = crate::fa_split_keys_pub(t1, local_kv_heads);
16916 if sp0 != sp1 {
16917 return Ok(nope("partition-sp", il, pos0));
16918 }
16919 let (ns0, ns1) = (t0.div_ceil(sp0), t1.div_ceil(sp1));
16920 if ns0 != ns1 {
16921 return Ok(nope("partition-ns", il, pos0));
16922 }
16923 if t0.div_ceil(ns0) != t1.div_ceil(ns1) {
16924 return Ok(nope("partition-per", il, pos0));
16925 }
16926 Ok(true)
16927 }
16928
16929 pub(crate) fn step35_fa_rows_precheck(
16934 &self,
16935 cache: &Cache,
16936 il: usize,
16937 pos0: usize,
16938 t: usize,
16939 ) -> Result<bool, Box<dyn std::error::Error>> {
16940 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16941 return Ok(false);
16942 };
16943 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16944 return Ok(false);
16945 };
16946 let Some(attention) = tp.attention.as_ref() else {
16947 return Ok(false);
16948 };
16949 if !tp.runtime.native_p2p()
16950 || crate::Engine::kv_fp8_on()
16951 || !crate::tp::step_tp_dcw_enabled()?
16952 || !crate::tp::step_tp_qkv_fused_enabled()?
16953 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16954 {
16955 return Ok(false);
16956 }
16957 let geometry = self.step35_geom(il);
16958 let head_dim = geometry.head_dim_k as usize;
16959 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16960 return Ok(false);
16961 }
16962 if crate::fa_sm_count() < 128
16963 || std::env::var("MEMRA_FA_SPLIT").is_ok()
16964 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
16965 || std::env::var("MEMRA_FA_SP16").is_ok()
16966 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
16967 {
16968 return Ok(false);
16969 }
16970 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16971 return Ok(false);
16972 };
16973 if distributed.staged_len() != pos0 {
16974 return Ok(false);
16975 }
16976 let (_, would_rebase) = distributed.peek_append_ring(t)?;
16977 if would_rebase {
16978 return Ok(false);
16979 }
16980 let window = geometry.window.map(|w| w as usize);
16983 let t0 = window.map(|w| (pos0 + 1).min(w)).unwrap_or(pos0 + 1);
16984 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
16985 return Ok(false);
16986 }
16987 Ok(true)
16988 }
16989
16990 pub(crate) fn step35_verify_fa_rows_join(
16994 &self,
16995 e: &Engine,
16996 il: usize,
16997 cache: &Cache,
16998 pos0: usize,
16999 t: usize,
17000 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17001 use cudarc::driver::DevicePtr;
17002 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17003 return Err("fa rows join expects full attention".into());
17004 };
17005 let tp = fa
17006 .step_tp_qkv
17007 .as_ref()
17008 .ok_or("fa rows join lost its resident projections")?;
17009 let geometry = self.step35_geom(il);
17010 let heads = geometry.n_head as usize;
17011 let head_dim = geometry.head_dim_k as usize;
17012 let window = geometry.window.map(|w| w as usize);
17013 let distributed = cache.tp_kv[il]
17014 .as_ref()
17015 .ok_or("fa rows join lost its distributed KV cache")?;
17016 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17017 let ladder = |t_kv: usize| -> usize {
17019 if t_kv <= 2048 {
17020 16
17021 } else if t_kv <= 16384 {
17022 64
17023 } else {
17024 128
17025 }
17026 };
17027 let mut max_ns = 1usize;
17028 for r in 0..t {
17029 let t_kv = window
17030 .map(|w| (pos0 + r + 1).min(w))
17031 .unwrap_or(pos0 + r + 1);
17032 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17033 }
17034 let ranks = tp.runtime.devices().len();
17039 let mut tables = Vec::with_capacity(ranks);
17040 for rank in 0..ranks {
17041 let engine = tp
17042 .runtime
17043 .rank_engine(rank)
17044 .ok_or("fa rows join lost a rank engine")?;
17045 let rank_cache = distributed
17046 .rank(rank)
17047 .ok_or("fa rows join lost a KV cache rank")?;
17048 let _main = engine.gpu.enter_main()?;
17049 let s = engine.stream();
17050 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17051 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17052 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17053 let bp = match rank_cache.base_d() {
17054 Some(b) => {
17055 let (p, _g) = b.device_ptr(&s);
17056 p as u64
17057 }
17058 None => 0u64,
17059 };
17060 let mut host = Vec::with_capacity(t * 6);
17061 for r in 0..t {
17062 host.extend_from_slice(&[
17063 kp as u64,
17064 vp as u64,
17065 lp as u64,
17066 bp,
17067 0u64,
17068 (t - 1 - r) as u64,
17069 ]);
17070 }
17071 tables.push(engine.stream().clone_htod(&host)?);
17072 }
17073 let tabs: Vec<&CudaSlice<u64>> = tables.iter().collect();
17074 let ws_index = tp
17075 .runtime
17076 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17077 tp.runtime.decode_v2_fa_rows_join(
17078 ws_index,
17079 e,
17080 &tp.o,
17081 &tabs,
17082 t,
17083 head_dim,
17084 window.unwrap_or(0),
17085 max_ns,
17086 geometry.attention_scale(),
17087 k_tok_bytes,
17088 v_tok_bytes,
17089 )
17090 }
17091
17092 pub(crate) fn step35_batch_fa_rows_precheck(
17096 &self,
17097 caches: &[&mut Cache],
17098 row_to_cache: impl Fn(usize) -> usize,
17099 positions: &[i32],
17100 il: usize,
17101 ) -> Result<bool, Box<dyn std::error::Error>> {
17102 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17103 return Ok(false);
17104 };
17105 let Some(tp) = fa.step_tp_qkv.as_ref() else {
17106 return Ok(false);
17107 };
17108 let Some(attention) = tp.attention.as_ref() else {
17109 return Ok(false);
17110 };
17111 if !tp.runtime.native_p2p()
17112 || crate::Engine::kv_fp8_on()
17113 || !crate::tp::step_tp_dcw_enabled()?
17114 || !crate::tp::step_tp_qkv_fused_enabled()?
17115 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
17116 {
17117 return Ok(false);
17118 }
17119 let geometry = self.step35_geom(il);
17120 let head_dim = geometry.head_dim_k as usize;
17121 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
17122 return Ok(false);
17123 }
17124 if crate::fa_sm_count() < 128
17125 || std::env::var("MEMRA_FA_SPLIT").is_ok()
17126 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
17127 || std::env::var("MEMRA_FA_SP16").is_ok()
17128 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
17129 {
17130 return Ok(false);
17131 }
17132 let window = geometry.window.map(|w| w as usize);
17133 for (r, &pos) in positions.iter().enumerate() {
17134 let cache = &caches[row_to_cache(r)];
17135 let Some(distributed) = cache.tp_kv[il].as_ref() else {
17136 return Ok(false);
17137 };
17138 if distributed.staged_len() != pos as usize {
17139 return Ok(false);
17140 }
17141 if distributed.peek_append_ring(1)?.1 {
17142 return Ok(false);
17143 }
17144 let t0 = window
17145 .map(|w| (pos as usize + 1).min(w))
17146 .unwrap_or(pos as usize + 1);
17147 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
17148 return Ok(false);
17149 }
17150 }
17151 Ok(true)
17152 }
17153
17154 pub(crate) fn step35_verify_rope_fa_pass(
17160 &self,
17161 e: &Engine,
17162 il: usize,
17163 cache: &Cache,
17164 pos0: usize,
17165 t: usize,
17166 stage_pos: bool,
17167 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
17168 use cudarc::driver::DevicePtr;
17169 if !crate::tp::fuse_rope_append_on() {
17170 return Ok(None);
17171 }
17172 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17173 return Ok(None);
17174 };
17175 let Some(tp) = fa.step_tp_qkv.as_ref() else {
17176 return Ok(None);
17177 };
17178 let Some(attention) = tp.attention.as_ref() else {
17179 return Ok(None);
17180 };
17181 let geometry = self.step35_geom(il);
17182 let head_dim = geometry.head_dim_k as usize;
17183 if head_dim != 128 {
17184 return Ok(None);
17185 }
17186 let heads = geometry.n_head as usize;
17187 let window = geometry.window.map(|w| w as usize);
17188 let ranks = tp.runtime.devices().len();
17189 let Some(distributed) = cache.tp_kv[il].as_ref() else {
17190 return Ok(None);
17191 };
17192 if distributed.kv_dim_k() != distributed.kv_dim_v() {
17193 return Ok(None);
17194 }
17195 {
17196 let rank0 = distributed.rank(0).ok_or("verify rope pass lost rank 0")?;
17197 if rank0.base_d().is_none()
17198 && distributed.staged_len() + t > distributed.physical_capacity()
17199 {
17200 return Ok(None);
17201 }
17202 }
17203 let mut rope_freqs = Vec::with_capacity(ranks);
17204 for rank in 0..ranks {
17205 let engine = tp
17206 .runtime
17207 .rank_engine(rank)
17208 .ok_or("verify rope pass lost a rank engine")?;
17209 rope_freqs.push(if geometry.rope_factors {
17210 match self
17211 .step35_aux
17212 .as_ref()
17213 .and_then(|aux| aux.rope_freqs(engine))
17214 {
17215 Some(f) => Some(f),
17216 None => return Ok(None),
17217 }
17218 } else {
17219 None
17220 });
17221 }
17222 let ladder = |t_kv: usize| -> usize {
17223 if t_kv <= 2048 {
17224 16
17225 } else if t_kv <= 16384 {
17226 64
17227 } else {
17228 128
17229 }
17230 };
17231 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17232 let mut max_ns = 1usize;
17233 let mut positions = Vec::with_capacity(t);
17234 for r in 0..t {
17235 positions.push((pos0 + r) as i32);
17236 let t_kv = window
17237 .map(|w| (pos0 + r + 1).min(w))
17238 .unwrap_or(pos0 + r + 1);
17239 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17240 }
17241 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
17242 let mut tab_keys = vec![0u64; ranks];
17243 for rank in 0..ranks {
17244 let engine = tp
17245 .runtime
17246 .rank_engine(rank)
17247 .ok_or("verify rope pass lost a rank engine")?;
17248 let rank_cache = distributed
17249 .rank(rank)
17250 .ok_or("verify rope pass lost a KV cache rank")?;
17251 let _main = engine.gpu.enter_main()?;
17252 let s = engine.stream();
17253 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17254 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17255 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17256 let bp = match rank_cache.base_d() {
17257 Some(b) => {
17258 let (p, _g) = b.device_ptr(&s);
17259 p as u64
17260 }
17261 None => 0u64,
17262 };
17263 tab_keys[rank] = (kp as u64)
17264 .rotate_left(17)
17265 .wrapping_add(bp)
17266 .wrapping_add((il as u64) << 32)
17267 .wrapping_add(t as u64)
17268 .wrapping_add(1 << 63);
17269 for _r in 0..t {
17270 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
17271 }
17272 }
17273 let ws_index = tp
17274 .runtime
17275 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17276 tp.runtime
17277 .decode_v2_rope_fa_rows(
17278 ws_index,
17279 e,
17280 &tp.o,
17281 &session_parts,
17282 &tab_keys,
17283 &positions,
17284 stage_pos,
17285 true,
17286 &attention.q_norm,
17287 &attention.k_norm,
17288 &rope_freqs,
17289 t,
17290 head_dim,
17291 geometry.n_rot as usize,
17292 window.unwrap_or(0),
17293 max_ns,
17294 geometry.attention_scale(),
17295 k_tok_bytes,
17296 v_tok_bytes,
17297 self.cfg.rms_eps,
17298 geometry.rope_base,
17299 )
17300 .map(Some)
17301 }
17302
17303 #[allow(clippy::too_many_arguments)]
17308 pub(crate) fn step35_batch_rope_fa_pass(
17309 &self,
17310 e: &Engine,
17311 il: usize,
17312 caches: &[&mut Cache],
17313 row_to_cache: impl Fn(usize) -> usize,
17314 positions: &[i32],
17315 t: usize,
17316 stage_pos: bool,
17317 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
17318 use cudarc::driver::DevicePtr;
17319 if !crate::tp::fuse_rope_append_on() {
17320 return Ok(None);
17321 }
17322 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17323 return Ok(None);
17324 };
17325 let Some(tp) = fa.step_tp_qkv.as_ref() else {
17326 return Ok(None);
17327 };
17328 let Some(attention) = tp.attention.as_ref() else {
17329 return Ok(None);
17330 };
17331 let geometry = self.step35_geom(il);
17332 let head_dim = geometry.head_dim_k as usize;
17333 if head_dim != 128 {
17334 return Ok(None);
17335 }
17336 let heads = geometry.n_head as usize;
17337 let window = geometry.window.map(|w| w as usize);
17338 let ranks = tp.runtime.devices().len();
17339 for r in 0..t {
17342 let cache = &caches[row_to_cache(r)];
17343 let Some(distributed) = cache.tp_kv[il].as_ref() else {
17344 return Ok(None);
17345 };
17346 if distributed.kv_dim_k() != distributed.kv_dim_v() {
17347 return Ok(None);
17348 }
17349 let rank0 = distributed.rank(0).ok_or("rope fa pass lost rank 0")?;
17350 if rank0.base_d().is_none()
17351 && distributed.staged_len() + t > distributed.physical_capacity()
17352 {
17353 return Ok(None);
17354 }
17355 }
17356 let mut rope_freqs = Vec::with_capacity(ranks);
17357 for rank in 0..ranks {
17358 let engine = tp
17359 .runtime
17360 .rank_engine(rank)
17361 .ok_or("rope fa pass lost a rank engine")?;
17362 rope_freqs.push(if geometry.rope_factors {
17363 match self
17364 .step35_aux
17365 .as_ref()
17366 .and_then(|aux| aux.rope_freqs(engine))
17367 {
17368 Some(f) => Some(f),
17369 None => return Ok(None),
17370 }
17371 } else {
17372 None
17373 });
17374 }
17375 let ladder = |t_kv: usize| -> usize {
17376 if t_kv <= 2048 {
17377 16
17378 } else if t_kv <= 16384 {
17379 64
17380 } else {
17381 128
17382 }
17383 };
17384 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17385 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
17386 let mut tab_keys = vec![0u64; ranks];
17387 for (r, &pos) in positions.iter().enumerate().take(t) {
17388 let cache = &caches[row_to_cache(r)];
17389 let distributed = cache.tp_kv[il]
17390 .as_ref()
17391 .ok_or("rope fa pass lost a distributed KV cache")?;
17392 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17393 let t_kv = window
17394 .map(|w| (pos as usize + 1).min(w))
17395 .unwrap_or(pos as usize + 1);
17396 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17397 for rank in 0..ranks {
17398 let engine = tp
17399 .runtime
17400 .rank_engine(rank)
17401 .ok_or("rope fa pass lost a rank engine")?;
17402 let rank_cache = distributed
17403 .rank(rank)
17404 .ok_or("rope fa pass lost a KV cache rank")?;
17405 let _main = engine.gpu.enter_main()?;
17406 let s = engine.stream();
17407 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17408 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17409 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17410 let bp = match rank_cache.base_d() {
17411 Some(b) => {
17412 let (p, _g) = b.device_ptr(&s);
17413 p as u64
17414 }
17415 None => 0u64,
17416 };
17417 tab_keys[rank] = tab_keys[rank]
17418 .rotate_left(9)
17419 .wrapping_add(kp as u64)
17420 .wrapping_add(bp)
17421 .wrapping_add(il as u64);
17422 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
17423 }
17424 }
17425 let ws_index = tp
17426 .runtime
17427 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17428 tp.runtime
17429 .decode_v2_rope_fa_rows(
17430 ws_index,
17431 e,
17432 &tp.o,
17433 &session_parts,
17434 &tab_keys,
17435 positions,
17436 stage_pos,
17437 false,
17438 &attention.q_norm,
17439 &attention.k_norm,
17440 &rope_freqs,
17441 t,
17442 head_dim,
17443 geometry.n_rot as usize,
17444 window.unwrap_or(0),
17445 max_ns,
17446 geometry.attention_scale(),
17447 k_tok_bytes,
17448 v_tok_bytes,
17449 self.cfg.rms_eps,
17450 geometry.rope_base,
17451 )
17452 .map(Some)
17453 }
17454
17455 #[allow(clippy::too_many_arguments)]
17459 pub(crate) fn step35_batch_fa_rows_join(
17460 &self,
17461 e: &Engine,
17462 il: usize,
17463 caches: &[&mut Cache],
17464 row_to_cache: impl Fn(usize) -> usize,
17465 positions: &[i32],
17466 t: usize,
17467 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17468 use cudarc::driver::DevicePtr;
17469 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17470 return Err("batch fa rows join expects full attention".into());
17471 };
17472 let tp = fa
17473 .step_tp_qkv
17474 .as_ref()
17475 .ok_or("batch fa rows join lost its resident projections")?;
17476 let geometry = self.step35_geom(il);
17477 let heads = geometry.n_head as usize;
17478 let head_dim = geometry.head_dim_k as usize;
17479 let window = geometry.window.map(|w| w as usize);
17480 let ladder = |t_kv: usize| -> usize {
17481 if t_kv <= 2048 {
17482 16
17483 } else if t_kv <= 16384 {
17484 64
17485 } else {
17486 128
17487 }
17488 };
17489 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17490 for (r, &pos) in positions.iter().enumerate() {
17491 let cache = &caches[row_to_cache(r)];
17492 let distributed = cache.tp_kv[il]
17493 .as_ref()
17494 .ok_or("batch fa rows join lost a distributed KV cache")?;
17495 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17496 let t_kv = window
17497 .map(|w| (pos as usize + 1).min(w))
17498 .unwrap_or(pos as usize + 1);
17499 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17500 }
17501 let ranks = tp.runtime.devices().len();
17505 let mut tables = Vec::with_capacity(ranks);
17506 for rank in 0..ranks {
17507 let engine = tp
17508 .runtime
17509 .rank_engine(rank)
17510 .ok_or("batch fa rows join lost a rank engine")?;
17511 let _main = engine.gpu.enter_main()?;
17512 let s = engine.stream();
17513 let mut host = Vec::with_capacity(t * 6);
17514 for r in 0..t {
17515 let cache = &caches[row_to_cache(r)];
17516 let distributed = cache.tp_kv[il]
17517 .as_ref()
17518 .ok_or("batch fa rows join lost a distributed KV cache")?;
17519 let rank_cache = distributed
17520 .rank(rank)
17521 .ok_or("batch fa rows join lost a KV cache rank")?;
17522 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17523 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17524 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17525 let bp = match rank_cache.base_d() {
17526 Some(b) => {
17527 let (p, _g) = b.device_ptr(&s);
17528 p as u64
17529 }
17530 None => 0u64,
17531 };
17532 host.extend_from_slice(&[kp as u64, vp as u64, lp as u64, bp, 0u64, 0u64]);
17533 }
17534 tables.push(engine.stream().clone_htod(&host)?);
17535 }
17536 let tabs: Vec<&CudaSlice<u64>> = tables.iter().collect();
17537 let ws_index = tp
17538 .runtime
17539 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17540 tp.runtime.decode_v2_fa_rows_join(
17541 ws_index,
17542 e,
17543 &tp.o,
17544 &tabs,
17545 t,
17546 head_dim,
17547 window.unwrap_or(0),
17548 max_ns,
17549 geometry.attention_scale(),
17550 k_tok_bytes,
17551 v_tok_bytes,
17552 )
17553 }
17554
17555 pub(crate) fn step35_verify_spec_fa2_join(
17559 &self,
17560 e: &Engine,
17561 il: usize,
17562 cache: &Cache,
17563 pos0: usize,
17564 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17565 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17566 return Err("spec fa2 join expects full attention".into());
17567 };
17568 let tp = fa
17569 .step_tp_qkv
17570 .as_ref()
17571 .ok_or("spec fa2 join lost its resident projections")?;
17572 let geometry = self.step35_geom(il);
17573 let heads = geometry.n_head as usize;
17574 let head_dim = geometry.head_dim_k as usize;
17575 let window = geometry.window.map(|w| w as usize);
17576 let bucket = window.map(|w| (pos0 + 2).min(w)).unwrap_or(pos0 + 2);
17579 let distributed = cache.tp_kv[il]
17580 .as_ref()
17581 .ok_or("spec fa2 join lost its distributed KV cache")?;
17582 let ws_index = tp
17583 .runtime
17584 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17585 tp.runtime.decode_v2_spec_fa2_join(
17586 ws_index,
17587 e,
17588 &tp.o,
17589 distributed,
17590 head_dim,
17591 window.unwrap_or(0),
17592 bucket,
17593 geometry.attention_scale(),
17594 )
17595 }
17596
17597 pub(crate) fn step35_verify_moe_tn(
17603 &self,
17604 e: &Engine,
17605 il: usize,
17606 z_t: &CudaSlice<f32>,
17607 t: usize,
17608 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
17609 let layer = &self.layers[il];
17610 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
17611 return Ok(None);
17612 };
17613 let Some(tp) = m.step_tp.as_ref() else {
17614 return Ok(None);
17615 };
17616 let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts else {
17617 return Ok(None);
17618 };
17619 if !crate::tp::step_nvfp4_dev_routes_enabled()?
17620 || !crate::tp::step_tp_dev_router_enabled()?
17621 || !crate::tp::nvfp4_bank_v2_on()
17622 || bank.ep2
17623 {
17624 return Ok(None);
17625 }
17626 let cfg = &self.cfg;
17627 let Some(moe) = cfg.moe.as_ref() else {
17628 return Ok(None);
17629 };
17630 let Some((sf, route_norm)) = cfg.sigmoid_router() else {
17631 return Ok(None);
17632 };
17633 let n_embd = cfg.n_embd as usize;
17634 let n_expert = moe.expert_count as usize;
17635 let n_used = moe.expert_used_count as usize;
17636 if t < 2 || t > 32 || z_t.len() < t * n_embd {
17637 return Err("verify moe t-row geometry".into());
17638 }
17639 let trace = std::env::var("MEMRA_TN_TRACE").as_deref() == Ok("1");
17640 if trace {
17641 eprintln!("[tn-trace] il={il} t={t} logits");
17642 }
17643 let logits = Self::moe_router_logits(e, m, z_t, t, cfg)?;
17644 static SELW2: std::sync::Mutex<Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>> =
17647 std::sync::Mutex::new(None);
17648 let mut selw = SELW2.lock().map_err(|_| "selw2 lock poisoned")?;
17649 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
17650 *selw = Some((
17651 e.ctx().ordinal(),
17652 e.htod_i32(&vec![0i32; 32 * n_used])?,
17653 e.htod(&vec![0.0f32; 32 * n_used])?,
17654 ));
17655 }
17656 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
17657 if trace {
17658 eprintln!("[tn-trace] il={il} topk logits_len={}", logits.len());
17659 }
17660 e.moe_router_sigmoid_topk_into(
17661 &logits,
17662 t,
17663 n_expert,
17664 n_used,
17665 m.active_count(),
17666 &m.exp_probs_b_dev,
17667 &m.active_experts_dev,
17668 sf,
17669 route_norm,
17670 sel_d,
17671 w_d,
17672 )?;
17673 if trace {
17674 eprintln!("[tn-trace] il={il} driver");
17675 }
17676 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
17683 let sel_host = e.dtoh_i32(sel_d)?;
17684 let slots = t * n_used;
17685 if sel_host.len() >= slots {
17686 let mut uniq = std::collections::HashSet::new();
17687 for &x in &sel_host[..slots] {
17688 uniq.insert(x);
17689 }
17690 type OverlapMap = std::collections::BTreeMap<usize, (u64, u64, u64)>;
17695 static BUCKETS: std::sync::Mutex<Option<OverlapMap>> = std::sync::Mutex::new(None);
17696 if let Ok(mut g) = BUCKETS.lock() {
17697 let map = g.get_or_insert_with(OverlapMap::new);
17698 let ent = map.entry(t).or_insert((0, 0, 0));
17699 ent.0 += slots as u64;
17700 ent.1 += uniq.len() as u64;
17701 ent.2 += 1;
17702 let total: u64 = map.values().map(|v| v.2).sum();
17703 if total % 2000 == 0 {
17704 let line = map
17705 .iter()
17706 .map(|(tb, (sl, uq, n))| {
17707 format!(
17708 "t={tb}: slots {:.1} distinct {:.1} dup {:.1}% (n={n})",
17709 *sl as f64 / *n as f64,
17710 *uq as f64 / *n as f64,
17711 100.0 * (1.0 - *uq as f64 / *sl as f64)
17712 )
17713 })
17714 .collect::<Vec<_>>()
17715 .join(" | ");
17716 eprintln!("[moe-overlap] {line}");
17717 }
17718 }
17719 }
17720 }
17721 let prejoin_shexp = {
17729 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
17730 crate::step37_door(&ENV, "MEMRA_TN_PREJOIN")
17731 };
17732 let mut staged_gate: Option<CudaSlice<f32>> = None;
17733 let mut out_t = if prejoin_shexp {
17734 let mut staged: Option<CudaSlice<f32>> = None;
17735 let out = tp
17736 .runtime
17737 .run_tensor_parallel_routes_nvfp4_device_routed_tn_prejoin(
17738 bank,
17739 e,
17740 z_t,
17741 sel_d,
17742 w_d,
17743 t,
17744 n_used,
17745 tp.activation_limit,
17746 || {
17747 staged = Self::step35_shexp_rows_compute(e, m, z_t, t, cfg, il as u16)?;
17748 Ok(())
17749 },
17750 )?;
17751 staged_gate = staged;
17752 out
17753 } else {
17754 tp.runtime
17755 .run_tensor_parallel_routes_nvfp4_device_routed_tn(
17756 bank,
17757 e,
17758 z_t,
17759 sel_d,
17760 w_d,
17761 t,
17762 n_used,
17763 tp.activation_limit,
17764 )?
17765 };
17766 if trace {
17767 eprintln!("[tn-trace] il={il} shexp out_t={}", out_t.len());
17768 }
17769 if let Some(gate) = staged_gate.take() {
17771 Self::step35_shexp_rows_apply(e, m, t, cfg, &gate, &mut out_t)?;
17772 return Ok(Some(out_t));
17773 }
17774 if !Self::step35_shexp_rows(e, m, z_t, t, cfg, il as u16, &mut out_t)? {
17778 let mut z_row = e.uninit(n_embd)?;
17779 let mut out_row = e.uninit(n_embd)?;
17780 for c in 0..t {
17781 e.dtod_copy_view(&z_t.slice(c * n_embd..(c + 1) * n_embd), &mut z_row)?;
17782 e.dtod_copy_view(&out_t.slice(c * n_embd..(c + 1) * n_embd), &mut out_row)?;
17783 Self::moe_ffn_grouped_add_shared(e, m, &z_row, 1, cfg, il as u16, &mut out_row)?;
17784 e.dtod_copy_into(&out_row, &mut out_t, c * n_embd)?;
17785 }
17786 }
17787 Ok(Some(out_t))
17788 }
17789
17790 fn step35_shexp_rows(
17799 e: &Engine,
17800 m: &MoeWeights,
17801 z_t: &CudaSlice<f32>,
17802 t: usize,
17803 cfg: &ModelConfig,
17804 il: u16,
17805 out_t: &mut CudaSlice<f32>,
17806 ) -> Result<bool, Box<dyn std::error::Error>> {
17807 match Self::step35_shexp_rows_compute(e, m, z_t, t, cfg, il)? {
17808 Some(gate) => {
17809 Self::step35_shexp_rows_apply(e, m, t, cfg, &gate, out_t)?;
17810 Ok(true)
17811 }
17812 None => Ok(false),
17813 }
17814 }
17815
17816 fn step35_shexp_rows_compute(
17820 e: &Engine,
17821 m: &MoeWeights,
17822 z_t: &CudaSlice<f32>,
17823 t: usize,
17824 cfg: &ModelConfig,
17825 il: u16,
17826 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
17827 let n_embd = cfg.n_embd as usize;
17828 let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
17829 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
17830 else {
17831 return Ok(None);
17832 };
17833 if !crate::Engine::bf16_mmv_on() || n_embd % 8 != 0 || cfg.m3.is_some() {
17834 return Ok(None);
17835 }
17836 let (
17837 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
17838 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
17839 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
17840 ) = (gate_shexp, up_shexp, down_shexp)
17841 else {
17842 return Ok(None);
17843 };
17844 let n_ff_sh = gate_shexp.out_features();
17845 let lim = cfg.clamp_shexp_at(il as u32);
17846 let mut guard = Self::step35_shexp_rows_ws()
17847 .lock()
17848 .map_err(|_| "shexp rows ws lock is poisoned")?;
17849 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
17850 if guard
17851 .as_ref()
17852 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
17853 {
17854 *guard = Some((
17855 pins.0,
17856 pins.1,
17857 pins.2,
17858 e.uninit(32 * n_ff_sh)?,
17859 e.uninit(32 * n_embd)?,
17860 ));
17861 }
17862 let (_, _, _, act_t, sh_t) = guard.as_mut().expect("armed above");
17863 e.matvec_bf16_dual_silu_rows_into(wg, wu, z_t, act_t, n_embd, n_ff_sh, lim, t)?;
17864 e.matvec_bf16_rows_into(wd, act_t, sh_t, n_ff_sh, n_embd, t)?;
17865 let gate = match &m.gate_inp_shexp {
17868 Some(gate_inp_shexp) => {
17869 e.sigmoid_dot_rows(z_t, gate_inp_shexp.float_data(), n_embd, t)?
17870 }
17871 None => e.htod(&vec![1.0f32; t])?,
17872 };
17873 Ok(Some(gate))
17874 }
17875
17876 #[allow(clippy::type_complexity)]
17881 fn step35_shexp_rows_ws()
17882 -> &'static std::sync::Mutex<Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>>
17883 {
17884 static WS: std::sync::Mutex<Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>> =
17885 std::sync::Mutex::new(None);
17886 &WS
17887 }
17888
17889 fn step35_shexp_rows_apply(
17893 e: &Engine,
17894 m: &MoeWeights,
17895 t: usize,
17896 cfg: &ModelConfig,
17897 gate: &CudaSlice<f32>,
17898 out_t: &mut CudaSlice<f32>,
17899 ) -> Result<(), Box<dyn std::error::Error>> {
17900 let n_embd = cfg.n_embd as usize;
17901 let n_ff_sh = m
17902 .gate_shexp
17903 .as_ref()
17904 .ok_or("shexp apply lost its gate projection")?
17905 .out_features();
17906 let mut guard = Self::step35_shexp_rows_ws()
17907 .lock()
17908 .map_err(|_| "shexp rows ws lock is poisoned")?;
17909 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
17910 let (_, _, _, _, sh_t) = guard
17911 .as_mut()
17912 .filter(|(d, ne, nf, ..)| (*d, *ne, *nf) == pins)
17913 .ok_or("shexp apply found no compute-side workspace for this shape")?;
17914 e.add_scaled_rows(sh_t, gate, out_t, n_embd, t)?;
17915 Ok(())
17916 }
17917
17918 fn step35_tp_decode_attn_resident_v2(
17919 &self,
17920 e: &Engine,
17921 fa: &FullAttnLayer,
17922 il: usize,
17923 h: &CudaSlice<f32>,
17924 pos_d: &CudaSlice<i32>,
17925 cache: &mut Cache,
17926 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17927 let tp = fa
17928 .step_tp_qkv
17929 .as_ref()
17930 .ok_or("Step TP decode lost its resident projections")?;
17931 let attention = tp
17932 .attention
17933 .as_ref()
17934 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
17935 if !tp.runtime.native_p2p() {
17936 return Err("rank-local Step attention requires native P2P".into());
17937 }
17938 if crate::Engine::kv_fp8_on() {
17939 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
17940 }
17941
17942 let geometry = self.step35_geom(il);
17943 let window = geometry.window.map(|window| window as usize);
17944 let ranks = tp.runtime.devices().len();
17945 let head_dim = geometry.head_dim_k as usize;
17946 let heads = geometry.n_head as usize;
17947 let kv_heads = geometry.n_head_kv as usize;
17948 if heads % ranks != 0 || kv_heads % ranks != 0 {
17949 return Err(format!(
17950 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
17951 )
17952 .into());
17953 }
17954 let local_heads = heads / ranks;
17955 let local_kv_heads = kv_heads / ranks;
17956 let max_ctx = cache.max_ctx;
17957
17958 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
17959
17960 let base_len = cache.kv[il]
17961 .as_ref()
17962 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
17963 .len;
17964 {
17965 let distributed = cache.tp_kv[il]
17966 .as_ref()
17967 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
17968 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
17969 return Err(format!(
17970 "Step TP layer {il} cache lengths diverged before decode: \
17971 local={base_len} distributed={}/{}",
17972 distributed.committed_len(),
17973 distributed.staged_len()
17974 )
17975 .into());
17976 }
17977 }
17978 if pos_d.len() != 1 {
17979 return Err(format!(
17980 "rank-local Step decode requires one position, got {}",
17981 pos_d.len()
17982 )
17983 .into());
17984 }
17985
17986 let decode_input = attention
17987 .decode_input
17988 .as_ref()
17989 .ok_or("Step TP decode v2 requires the replicated decode input")?;
17990 let mut decode_input = decode_input
17991 .lock()
17992 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
17993
17994 let use_gate_shards = (attention.gate_shards.is_some()
17999 || attention.gate_shards_bf16.is_some())
18000 && crate::tp::step_tp_qkv_fused_enabled()?;
18001 let gate_raw = if use_gate_shards {
18002 None
18003 } else {
18004 let gate_weight = fa
18005 .attn_gate
18006 .as_ref()
18007 .ok_or("step35 layer is missing attn_gate.weight")?;
18008 let gate_raw = e.matmul(gate_weight, h, 1)?;
18009 if gate_raw.len() != heads {
18010 return Err(format!(
18011 "Step TP layer {il} gate output {} != {heads}",
18012 gate_raw.len()
18013 )
18014 .into());
18015 }
18016 Some(gate_raw)
18017 };
18018
18019 let ws_index = tp
18020 .runtime
18021 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
18022 let mut ws_guard = tp
18023 .runtime
18024 .decode_v2_workspace()
18025 .lock()
18026 .map_err(|_| "Step TP decode v2 workspace lock is poisoned")?;
18027 let ws = ws_guard
18028 .get_mut(ws_index)
18029 .ok_or("Step TP decode v2 workspace missing after ensure")?;
18030
18031 let mut rope_freqs = Vec::with_capacity(ranks);
18032 for rank in 0..ranks {
18033 let engine = tp
18034 .runtime
18035 .rank_engine(rank)
18036 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
18037 rope_freqs.push(if geometry.rope_factors {
18038 self.step35_aux
18039 .as_ref()
18040 .and_then(|aux| aux.rope_freqs(engine))
18041 } else {
18042 None
18043 });
18044 }
18045 let staged_next = base_len + 1;
18052 let t_kv_eff = window
18053 .map(|window| staged_next.min(window))
18054 .unwrap_or(staged_next);
18055 let dcw = crate::tp::step_tp_dcw_enabled()? && use_gate_shards && t_kv_eff >= 96 && {
18056 let (write_row, would_rebase) = cache.tp_kv[il]
18057 .as_ref()
18058 .expect("distributed cache checked above")
18059 .peek_append_ring(1)?;
18060 if !would_rebase {
18061 let base = (base_len - write_row) as i32;
18063 let distributed = cache.tp_kv[il]
18064 .as_mut()
18065 .expect("distributed cache checked above");
18066 for rank in 0..ranks {
18067 let engine = tp.runtime.rank_engine(rank).ok_or_else(|| {
18068 format!("Step TP layer {il} has no engine for rank {rank}")
18069 })?;
18070 let _main = engine.gpu.enter_main()?;
18071 let rank_cache = distributed
18072 .rank_mut(rank)
18073 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
18074 if rank_cache.base_d().is_none() {
18075 rank_cache.arm_base_d(engine.htod_i32(&[base])?);
18076 }
18077 }
18078 }
18079 !would_rebase
18080 };
18081 let fuse_rope = dcw
18082 && crate::tp::fuse_rope_append_on()
18083 && head_dim == 128
18084 && cache.tp_kv[il]
18085 .as_ref()
18086 .map(|d| d.kv_dim_k() == d.kv_dim_v() && d.kv_dim_k() == local_kv_heads * head_dim)
18087 .unwrap_or(false);
18088
18089 let tcol_col = crate::tp::take_verify_tcol();
18090 let fa2_col = crate::tp::take_spec_fa2_defer();
18097 tp.runtime.decode_v2_input_qkv(
18098 ws,
18099 e,
18100 h,
18101 pos_d,
18102 gate_raw.as_ref(),
18103 if !use_gate_shards {
18104 None
18105 } else if let Some(shards) = attention.gate_shards.as_deref() {
18106 Some(crate::tp::StepTpGateShards::F32(shards))
18107 } else {
18108 attention
18109 .gate_shards_bf16
18110 .as_deref()
18111 .map(crate::tp::StepTpGateShards::Bf16)
18112 },
18113 &mut decode_input,
18114 &tp.q,
18115 &tp.k,
18116 &tp.v,
18117 &attention.q_norm,
18118 &attention.k_norm,
18119 head_dim,
18120 geometry.n_rot as usize,
18121 geometry.rope_base,
18122 &rope_freqs,
18123 self.cfg.rms_eps,
18124 fuse_rope,
18125 tcol_col,
18126 )?;
18127
18128 let transaction = cache.tp_kv[il]
18129 .as_mut()
18130 .expect("distributed cache checked above")
18131 .begin_transaction()?;
18132 let append_result = tp.runtime.append_tp_kv_transaction_inner(
18133 cache.tp_kv[il]
18134 .as_mut()
18135 .expect("distributed cache checked above"),
18136 transaction,
18137 &ws.k,
18138 &ws.v_raw,
18139 1,
18140 dcw,
18141 );
18142 if let Err(error) = append_result {
18143 let _ = tp.runtime.rollback_tp_kv_transaction(
18144 cache.tp_kv[il]
18145 .as_mut()
18146 .expect("distributed cache checked above"),
18147 transaction,
18148 );
18149 return Err(error);
18150 }
18151
18152 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18153 let (staged_len, physical, k_tok_bytes_c, v_tok_bytes_c, capacity) = {
18156 let distributed = cache.tp_kv[il]
18157 .as_ref()
18158 .expect("distributed cache checked above");
18159 let staged_len = distributed.staged_len();
18160 let view_start = window
18161 .map(|window| staged_len.saturating_sub(window))
18162 .unwrap_or(0);
18163 (
18164 staged_len,
18165 distributed.physical_range(view_start, staged_len)?,
18166 distributed.k_tok_bytes(),
18167 distributed.v_tok_bytes(),
18168 distributed.physical_capacity(),
18169 )
18170 };
18171 let view_start = window
18172 .map(|window| staged_len.saturating_sub(window))
18173 .unwrap_or(0);
18174 let t_kv = staged_len - view_start;
18175 for rank in 0..ranks {
18176 let engine = tp
18177 .runtime
18178 .rank_engine(rank)
18179 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
18180 let _main = engine.gpu.enter_main()?;
18181 if dcw {
18182 {
18186 let distributed_mut = cache.tp_kv[il]
18187 .as_mut()
18188 .expect("distributed cache checked above");
18189 let (kv_dim_k, kv_dim_v) =
18190 (distributed_mut.kv_dim_k(), distributed_mut.kv_dim_v());
18191 let (k_tok_bytes, v_tok_bytes) =
18192 (distributed_mut.k_tok_bytes(), distributed_mut.v_tok_bytes());
18193 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
18194 format!("Step TP layer {il} has no KV cache rank {rank}")
18195 })?;
18196 let (k_plane, v_plane, len_d, base_d) =
18197 rank_cache.planes_and_counters_mut();
18198 if fuse_rope {
18199 let same_dev = engine.ctx().ordinal() == e.ctx().ordinal();
18202 let crate::tp::StepTpDecodeV2Ws {
18203 q_raw,
18204 k_raw,
18205 v_raw,
18206 q,
18207 k,
18208 pos,
18209 pos_stage,
18210 fuse_ctr,
18211 ..
18212 } = &mut *ws;
18213 let pos_ref: &CudaSlice<i32> = if same_dev {
18217 pos_stage
18218 .as_ref()
18219 .ok_or("step TP decode v2 pos stage not armed")?
18220 } else {
18221 &pos[rank]
18222 };
18223 engine.qk_norm_rope_append_inc_dcw(
18224 &q_raw[rank],
18225 &k_raw[rank],
18226 &v_raw[rank],
18227 &attention.q_norm[rank],
18228 &attention.k_norm[rank],
18229 &mut q[rank],
18230 &mut k[rank],
18231 pos_ref,
18232 k_plane,
18233 v_plane,
18234 len_d,
18235 base_d,
18236 &mut fuse_ctr[rank],
18237 kv_dim_k,
18238 kv_dim_v,
18239 k_tok_bytes,
18240 v_tok_bytes,
18241 head_dim,
18242 geometry.n_rot as usize,
18243 local_heads,
18244 local_kv_heads,
18245 self.cfg.rms_eps,
18246 geometry.rope_base,
18247 1.0,
18248 rope_freqs[rank],
18249 )?;
18250 } else {
18251 engine.append_kv_quantized_dcw(
18252 &ws.k[rank],
18253 &ws.v_raw[rank],
18254 k_plane,
18255 v_plane,
18256 len_d,
18257 base_d,
18258 kv_dim_k,
18259 kv_dim_v,
18260 k_tok_bytes,
18261 v_tok_bytes,
18262 )?;
18263 }
18264 if !fuse_rope {
18265 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
18266 format!("Step TP layer {il} has no KV cache rank {rank}")
18267 })?;
18268 engine.inc_i32(rank_cache.len_d_mut())?;
18269 }
18270 }
18271 let distributed = cache.tp_kv[il]
18272 .as_ref()
18273 .expect("distributed cache checked above");
18274 let rank_cache = distributed
18275 .rank(rank)
18276 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
18277 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes_c);
18278 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes_c);
18279 if fa2_col.is_some() {
18280 continue;
18283 }
18284 {
18285 let crate::tp::StepTpDecodeV2Ws { q, gate, gated, .. } = &mut *ws;
18288 engine.fa_decode_dcw(
18289 &q[rank],
18290 &k_ring,
18291 &v_ring,
18292 &mut gated[rank],
18293 head_dim,
18294 local_heads,
18295 local_kv_heads,
18296 rank_cache.len_d(),
18297 rank_cache.base_d(),
18298 window.unwrap_or(0),
18299 t_kv,
18300 geometry.attention_scale(),
18301 k_tok_bytes_c,
18302 v_tok_bytes_c,
18303 Some(&gate[rank]),
18304 )?;
18305 }
18306 continue;
18307 }
18308 let distributed = cache.tp_kv[il]
18309 .as_ref()
18310 .expect("distributed cache checked above");
18311 let rank_cache = distributed
18312 .rank(rank)
18313 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
18314 let k_view = engine.view_u8_range(
18315 rank_cache.k(),
18316 physical.start * k_tok_bytes_c,
18317 physical.end * k_tok_bytes_c,
18318 );
18319 let v_view = engine.view_u8_range(
18320 rank_cache.v(),
18321 physical.start * v_tok_bytes_c,
18322 physical.end * v_tok_bytes_c,
18323 );
18324 engine.fa_decode_kvmod(
18325 &ws.q[rank],
18326 &k_view,
18327 &v_view,
18328 &mut ws.attn_out[rank],
18329 head_dim,
18330 local_heads,
18331 local_kv_heads,
18332 t_kv,
18333 geometry.attention_scale(),
18334 k_tok_bytes_c,
18335 v_tok_bytes_c,
18336 false,
18337 )?;
18338 engine.attn_head_gate(
18339 &ws.attn_out[rank],
18340 &ws.gate[rank],
18341 &mut ws.gated[rank],
18342 None,
18343 head_dim,
18344 local_heads,
18345 1,
18346 )?;
18347 }
18348
18349 let output = if let Some(col) = fa2_col.filter(|_| dcw) {
18356 tp.runtime.decode_v2_stash_fa2(ws, e, col)?;
18360 crate::tp::set_spec_fa2_stashed();
18361 e.uninit(ws.o_out)?
18362 } else if let Some(col) = crate::tp::take_tcol_oproj_defer() {
18363 if tp.runtime.decode_v2_oproj_tcol_eligible(ws, &tp.o) {
18364 tp.runtime.decode_v2_stash_gated(ws, e, col)?;
18365 crate::tp::set_tcol_oproj_stashed();
18366 e.uninit(ws.o_out)?
18367 } else {
18368 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
18369 }
18370 } else {
18371 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
18372 };
18373
18374 let local = cache.kv[il]
18378 .as_mut()
18379 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
18380 if local.len != base_len || base_len + 1 > max_ctx {
18381 return Err(format!(
18382 "Step TP layer {il} local cache changed during decode: \
18383 len={} base={base_len} max={max_ctx}",
18384 local.len
18385 )
18386 .into());
18387 }
18388 if crate::tp::no_local_shadow_on() {
18389 local.len = base_len + 1;
18392 if !crate::tp::len_mirror_lazy_on() {
18396 e.set_i32_one(&mut local.len_d, local.len as i32)?;
18397 }
18398 } else {
18399 let retain_from = window
18400 .map(|window| {
18401 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
18402 let rollback_retain =
18403 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
18404 staged_retain.min(rollback_retain)
18405 })
18406 .unwrap_or(0);
18407 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
18408 e.append_kv_quantized(
18409 &ws.k_shadow,
18410 &ws.v_shadow,
18411 &mut local.k,
18412 &mut local.v,
18413 write_row,
18414 local.kv_dim_k,
18415 local.kv_dim_v,
18416 local.k_tok_bytes,
18417 local.v_tok_bytes,
18418 false,
18419 )?;
18420 local.len = base_len + 1;
18421 e.set_i32_one(&mut local.len_d, local.len as i32)?;
18422 }
18423 Ok(output)
18424 })();
18425
18426 let output = match staged {
18427 Ok(output) => output,
18428 Err(error) => {
18429 let _ = tp.runtime.rollback_tp_kv_transaction(
18430 cache.tp_kv[il]
18431 .as_mut()
18432 .expect("distributed cache checked above"),
18433 transaction,
18434 );
18435 if let Some(local) = cache.kv[il].as_mut() {
18436 local.len = base_len;
18437 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
18438 }
18439 return Err(error);
18440 }
18441 };
18442 let lazy_commit = fuse_rope && crate::tp::len_mirror_lazy_on();
18447 if lazy_commit {
18448 if let Err(error) = tp.runtime.commit_tp_kv_transaction_external(
18449 cache.tp_kv[il]
18450 .as_mut()
18451 .expect("distributed cache checked above"),
18452 transaction,
18453 1,
18454 ) {
18455 let _ = tp.runtime.rollback_tp_kv_transaction(
18456 cache.tp_kv[il]
18457 .as_mut()
18458 .expect("distributed cache checked above"),
18459 transaction,
18460 );
18461 let local = cache.kv[il].as_mut().expect("local cache checked above");
18462 local.len = base_len;
18463 e.set_i32_one(&mut local.len_d, base_len as i32)?;
18464 return Err(error);
18465 }
18466 } else if let Err(error) = tp.runtime.commit_tp_kv_transaction(
18467 cache.tp_kv[il]
18468 .as_mut()
18469 .expect("distributed cache checked above"),
18470 transaction,
18471 1,
18472 ) {
18473 let _ = tp.runtime.rollback_tp_kv_transaction(
18474 cache.tp_kv[il]
18475 .as_mut()
18476 .expect("distributed cache checked above"),
18477 transaction,
18478 );
18479 let local = cache.kv[il].as_mut().expect("local cache checked above");
18480 local.len = base_len;
18481 e.set_i32_one(&mut local.len_d, base_len as i32)?;
18482 return Err(error);
18483 }
18484
18485 let committed = cache.tp_kv[il]
18486 .as_ref()
18487 .expect("distributed cache checked above")
18488 .committed_len();
18489 let local_len = cache.kv[il]
18490 .as_ref()
18491 .expect("local cache checked above")
18492 .len;
18493 if committed != local_len {
18494 return Err(format!(
18495 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
18496 )
18497 .into());
18498 }
18499 static V2_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
18500 if !V2_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
18501 eprintln!(
18502 "[step-tp-attn-v2] execute layer={} devices={:?} tokens=1 driver=v2 \
18503 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
18504 kv_cache_distributed=true kv_cache_hydrated={hydrated} \
18505 attention_tensor_parallel=true attention_scope={} \
18506 input_path=root-device-replicated gate_tensor_parallel=false \
18507 gate_shards=device-staged o_tensor_parallel=true o_reduce=root-device \
18508 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
18509 bulk_p2p={} workspace=persistent ordering=evented output=e-device \
18510 performance_claim=false (logged once; every decode layer runs this driver)",
18511 tp.layer,
18512 tp.devices,
18513 if window.is_some() {
18514 "rank-local-swa-ring"
18515 } else {
18516 "rank-local-global"
18517 },
18518 tp.runtime.transport_label(),
18519 tp.runtime.bulk_p2p(),
18520 );
18521 }
18522 Ok(output)
18523 }
18524
18525 #[allow(clippy::too_many_arguments)]
18535 pub(crate) fn step35_decode_attn(
18536 &self,
18537 e: &Engine,
18538 fa: &FullAttnLayer,
18539 il: usize,
18540 h: &CudaSlice<f32>,
18541 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
18542 pos_d: &CudaSlice<i32>,
18543 cache: &mut Cache,
18544 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18545 if fa
18546 .step_tp_qkv
18547 .as_ref()
18548 .is_some_and(|tp| tp.attention.is_some())
18549 {
18550 if pre_q.is_some() {
18551 return Err(
18552 "rank-local Step attention preserves BF16 activations and refuses the q8_1 \
18553 pre-quantized decode path"
18554 .into(),
18555 );
18556 }
18557 return self.step35_tp_decode_attn_resident(e, fa, il, h, pos_d, cache);
18558 }
18559
18560 let geometry = self.step35_geom(il);
18561 let hd = geometry.head_dim_k as usize;
18562 let nkv = geometry.n_head_kv as usize;
18563 let nh = geometry.n_head as usize;
18564 let rbase = geometry.rope_base;
18565 let scale = geometry.attention_scale();
18566 let swa = geometry.window.is_some();
18567 let eps = self.cfg.rms_eps;
18568 let win = geometry.window.unwrap_or(0) as usize;
18569 let n_rot = geometry.n_rot as usize;
18570 let n_embd = self.cfg.n_embd as usize;
18571 let gw = fa
18572 .attn_gate
18573 .as_ref()
18574 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
18575
18576 let tp_qkv = if fa.step_tp_qkv.is_some() {
18577 if pre_q.is_some() {
18578 return Err(
18579 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
18580 pre-quantized decode path"
18581 .into(),
18582 );
18583 }
18584 self.step35_tp_qkv(e, fa, h, 1)?
18585 } else {
18586 None
18587 };
18588
18589 let (q0, k0, v0, gt) = match tp_qkv {
18590 Some(mut g3) => {
18591 let v = g3.pop().unwrap();
18592 let k = g3.pop().unwrap();
18593 let q = g3.pop().unwrap();
18594 let gt = e.matmul(gw, h, 1)?;
18595 (q, k, v, gt)
18596 }
18597 None => match pre_q {
18598 Some((hq, hdq)) => {
18599 debug_assert!(
18600 e.uses_q8_1_fast(gw),
18601 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
18602 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
18603 );
18604 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
18605 Some(t3) => t3,
18606 None => (
18607 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
18608 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
18609 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
18610 ),
18611 };
18612 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
18613 (a, b, c, gt)
18614 }
18615 None => {
18616 if e.uses_q8_1_fast(&fa.wq)
18617 && e.uses_q8_1_fast(&fa.wk)
18618 && e.uses_q8_1_fast(&fa.wv)
18619 && e.uses_q8_1_fast(gw)
18620 {
18621 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
18622 let (a, b, c) =
18623 match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
18624 Some(t3) => t3,
18625 None => (
18626 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
18627 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
18628 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
18629 ),
18630 };
18631 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
18632 (a, b, c, gt)
18633 } else {
18634 (
18635 e.matmul(&fa.wq, h, 1)?,
18636 e.matmul(&fa.wk, h, 1)?,
18637 e.matmul(&fa.wv, h, 1)?,
18638 e.matmul(gw, h, 1)?,
18639 )
18640 }
18641 }
18642 },
18643 };
18644
18645 let mut q = e.uninit(nh * hd)?;
18646 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
18647 let mut k = e.uninit(nkv * hd)?;
18648 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
18649 let ff = if swa {
18650 None
18651 } else {
18652 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
18653 };
18654 #[cfg(debug_assertions)]
18655 if let Some(ff) = ff {
18656 crate::debug_assert_tensor_stream_device(
18657 ff,
18658 &e.stream(),
18659 "step35_decode_attn.rope_freqs",
18660 );
18661 }
18662 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
18663
18664 if std::env::var("MEMRA_NOFA").is_ok() {
18665 return Err(
18666 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
18667 cache; unset MEMRA_NOFA to use fa_decode"
18668 .into(),
18669 );
18670 }
18671 let kvl = cache.kv[il].as_mut().unwrap();
18672 let next_len = kvl.len + 1;
18673 let (off, t_kv) = if swa && next_len > win {
18674 (next_len - win, win)
18675 } else {
18676 (0, next_len)
18677 };
18678 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
18679 e.append_kv_quantized(
18680 &k,
18681 &v0,
18682 &mut kvl.k,
18683 &mut kvl.v,
18684 write_row,
18685 kvl.kv_dim_k,
18686 kvl.kv_dim_v,
18687 kvl.k_tok_bytes,
18688 kvl.v_tok_bytes,
18689 crate::Engine::kv_fp8_on(),
18690 )?;
18691 kvl.len = next_len;
18692 let physical = kvl.physical_rows(off, off + t_kv)?;
18693 let k_view = e.view_u8_range(
18694 &kvl.k,
18695 physical.start * kvl.k_tok_bytes,
18696 physical.end * kvl.k_tok_bytes,
18697 );
18698 let v_view = e.view_u8_range(
18699 &kvl.v,
18700 physical.start * kvl.v_tok_bytes,
18701 physical.end * kvl.v_tok_bytes,
18702 );
18703 let mut attn = e.uninit(nh * hd)?;
18704 e.fa_decode_kvmod(
18705 &q,
18706 &k_view,
18707 &v_view,
18708 &mut attn,
18709 hd,
18710 nh,
18711 nkv,
18712 t_kv,
18713 scale,
18714 kvl.k_tok_bytes,
18715 kvl.v_tok_bytes,
18716 crate::Engine::kv_fp8_on(),
18717 )?;
18718
18719 let mut ag = e.uninit(nh * hd)?;
18720 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
18721 self.step35_o(e, fa, &ag, 1)
18722 }
18723}
18724
18725impl HybridModel {
18734 pub fn is_gemma4_e4b(&self) -> bool {
18735 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
18736 }
18737
18738 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
18742 let g = self.cfg.gemma4.as_ref().unwrap();
18743 let swa = g.swa_pattern[il];
18744 let hd = if swa {
18745 g.key_length_swa
18746 } else {
18747 g.key_length_global
18748 } as usize;
18749 let Mixer::Full(fa) = &self.layers[il].mixer else {
18750 panic!("e4b layer {il} not full-attn")
18751 };
18752 let nh = fa.wq.out_features() / hd;
18753 let nkv = fa.wk.out_features() / hd;
18754 (
18755 hd,
18756 nkv,
18757 nh,
18758 if swa {
18759 g.rope_base_swa
18760 } else {
18761 g.rope_base_global
18762 },
18763 1.0,
18764 swa,
18765 )
18766 }
18767
18768 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
18770 self.layers[il]
18771 .gemma4
18772 .as_ref()
18773 .and_then(|b| b.e4b.as_ref())
18774 .and_then(|e4| e4.kv_share.map(|t| t as usize))
18775 }
18776
18777 fn gemma4_e4b_inp_pl(
18782 &self,
18783 e: &Engine,
18784 tokens: &[u32],
18785 x_scaled: &CudaSlice<f32>,
18786 t: usize,
18787 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18788 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
18789 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
18790 }
18791
18792 fn gemma4_e4b_inp_pl_dev(
18794 &self,
18795 e: &Engine,
18796 tok_d: &CudaSlice<u32>,
18797 x_scaled: &CudaSlice<f32>,
18798 t: usize,
18799 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18800 let aux = self.gemma4_aux.as_ref().unwrap();
18801 let m = aux.e4b.as_ref().unwrap();
18802 let n_embd = self.cfg.n_embd as usize;
18803 let n_layer = self.layers.len();
18804 let width = m.n_epl * n_layer;
18805 let tbl = m.tok_tbl_gpu.get_or_init(|| {
18806 e.upload_u8(&m.tok_embd_bytes)
18807 .expect("e4b per-layer token table upload")
18808 });
18809 let mut a =
18810 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
18811 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
18812 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
18813 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
18814 let mut pn = e.uninit(t * width)?;
18815 e.rms_norm(
18816 &p,
18817 m.proj_norm.float_data(),
18818 &mut pn,
18819 m.n_epl,
18820 t * n_layer,
18821 self.cfg.rms_eps,
18822 )?;
18823 let mut out = e.uninit(t * width)?;
18824 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
18825 Ok(out)
18826 }
18827
18828 #[allow(clippy::too_many_arguments)]
18833 fn gemma4_e4b_attn(
18834 &self,
18835 e: &Engine,
18836 il: usize,
18837 hq: &CudaSlice<i8>,
18838 hdq: &CudaSlice<f32>,
18839 pos_d: &CudaSlice<i32>,
18840 t: usize,
18841 cache: &mut Cache,
18842 dc_bucket: Option<usize>,
18843 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18844 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
18845 let eps = self.cfg.rms_eps;
18846 let aux = self.gemma4_aux.as_ref().unwrap();
18847 let ones = aux.ones(e);
18848 #[cfg(debug_assertions)]
18849 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
18850 let Mixer::Full(fa) = &self.layers[il].mixer else {
18851 unreachable!()
18852 };
18853 let h0 = e.zeros(0)?;
18857 let h = &h0;
18858
18859 let ff = if swa {
18860 None
18861 } else {
18862 Some(
18863 aux.rope_freqs(e)
18864 .expect("e4b global rope needs rope_freqs.weight"),
18865 )
18866 };
18867 #[cfg(debug_assertions)]
18868 if let Some(ff) = ff {
18869 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
18870 }
18871 let share = self.gemma4_e4b_kv_target(il);
18872 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
18874 let mut q;
18875 if let Some(_tgt) = share {
18876 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
18877 q = e.uninit(t * nh * hd)?;
18878 let mut kdummy = e.uninit(1)?;
18881 let mut vdummy = e.uninit(1)?;
18882 e.rms_norm_qkv_rope(
18883 &q0,
18884 &q0,
18885 &q0,
18886 fa.q_norm.float_data(),
18887 fa.q_norm.float_data(),
18888 ones,
18889 &mut q,
18890 &mut kdummy,
18891 &mut vdummy,
18892 hd,
18893 self.gemma4_rope_dims(il),
18894 nh * t,
18895 0,
18896 pos_d,
18897 nh,
18898 1,
18899 base,
18900 1.0,
18901 ff,
18902 eps,
18903 )?;
18904 } else {
18905 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
18909 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
18910 q = e.uninit(t * nh * hd)?;
18911 let mut k = e.uninit(t * nkv * hd)?;
18912 let mut v = e.uninit(t * nkv * hd)?;
18913 if t == 1 && cat.is_some() {
18914 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
18915 e.rms_norm_qkv_rope_cat(
18916 &qkv0,
18917 fa.q_norm.float_data(),
18918 fa.k_norm.float_data(),
18919 ones,
18920 &mut q,
18921 &mut k,
18922 &mut v,
18923 hd,
18924 self.gemma4_rope_dims(il),
18925 nh,
18926 nkv,
18927 pos_d,
18928 nh,
18929 nkv,
18930 base,
18931 1.0,
18932 ff,
18933 eps,
18934 )?;
18935 } else {
18936 let (q0, k0, v0) = match if t == 1 {
18937 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
18938 } else {
18939 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18942 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
18943 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
18944 } else {
18945 None
18946 }
18947 } {
18948 Some(triple) => triple,
18949 None => (
18950 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
18951 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
18952 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
18953 ), };
18955 e.rms_norm_qkv_rope(
18958 &q0,
18959 &k0,
18960 &v0,
18961 fa.q_norm.float_data(),
18962 fa.k_norm.float_data(),
18963 ones,
18964 &mut q,
18965 &mut k,
18966 &mut v,
18967 hd,
18968 self.gemma4_rope_dims(il),
18969 nh * t,
18970 nkv * t,
18971 pos_d,
18972 nh,
18973 nkv,
18974 base,
18975 1.0,
18976 ff,
18977 eps,
18978 )?;
18979 }
18980 let kvl = cache.kv[il].as_mut().unwrap();
18981 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18985 if dc_bucket.is_some() {
18986 debug_assert!(t == 1);
18991 e.append_kv_quantized_row_dc_inc(
18993 &k,
18994 &v,
18995 &mut kvl.k,
18996 &mut kvl.v,
18997 &mut kvl.len_d,
18998 kvl.kv_dim_k,
18999 kvl.kv_dim_v,
19000 kvl.k_tok_bytes,
19001 kvl.v_tok_bytes,
19002 cls,
19003 )?;
19004 } else {
19005 e.append_kv_quantized_rows(
19006 &k,
19007 &v,
19008 &mut kvl.k,
19009 &mut kvl.v,
19010 kvl.len,
19011 t,
19012 kvl.kv_dim_k,
19013 kvl.kv_dim_v,
19014 kvl.k_tok_bytes,
19015 kvl.v_tok_bytes,
19016 cls,
19017 )?;
19018 kvl.len += t;
19019 }
19020 kv_f32 = Some((k, v));
19021 }
19022 let kvl_idx = share.unwrap_or(il);
19025 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
19026 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
19028 let mut attn = e.uninit(t * nh * hd)?;
19029 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
19041 if let Some((kf, vf)) = &kv_f32 {
19042 if hd == 256 && t <= win {
19043 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
19044 return Ok(e.matmul(&fa.wo, &attn, t)?);
19045 }
19046 if hd == 256 && swa && t > win {
19047 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
19048 return Ok(e.matmul(&fa.wo, &attn, t)?);
19049 }
19050 if hd == 512 && !swa {
19051 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
19052 return Ok(e.matmul(&fa.wo, &attn, t)?);
19053 }
19054 } else if share.is_some() {
19055 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
19056 let k_view = e.view_u8(&kvl.k, kvl.k.len());
19057 let v_view = e.view_u8(&kvl.v, kvl.v.len());
19058 if hd == 256 && (!swa || t <= win) {
19059 e.fa_prefill_view(
19061 &q,
19062 &k_view,
19063 &v_view,
19064 &mut attn,
19065 hd,
19066 nh,
19067 nkv,
19068 t,
19069 t,
19070 scale,
19071 true,
19072 kvl.k_tok_bytes,
19073 kvl.v_tok_bytes,
19074 g,
19075 )?;
19076 return Ok(e.matmul(&fa.wo, &attn, t)?);
19077 }
19078 let kv_dim = nkv * hd;
19081 let mut kf = e.uninit(t * kv_dim)?;
19082 let mut vf = e.uninit(t * kv_dim)?;
19083 e.fa_dequant_kv_view_f32(
19084 &k_view,
19085 &v_view,
19086 &mut kf,
19087 &mut vf,
19088 kv_dim,
19089 kv_dim,
19090 t,
19091 kvl.k_tok_bytes,
19092 kvl.v_tok_bytes,
19093 g,
19094 )?;
19095 if hd == 512 {
19096 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
19097 } else {
19098 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
19099 }
19100 return Ok(e.matmul(&fa.wo, &attn, t)?);
19101 }
19102 }
19103 if let Some(bucket) = dc_bucket {
19104 assert!(t == 1);
19109 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
19115 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
19116 } else {
19117 bucket
19118 };
19119 let k_view = e.view_u8(&kvl.k, kvl.k.len());
19120 let v_view = e.view_u8(&kvl.v, kvl.v.len());
19121 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
19122 if crate::Engine::wpf_level() >= 1 {
19130 e.prefetch_weight_l2(&fa.wo)?;
19131 }
19132 if e.uses_q8_1_fast(&fa.wo) {
19135 let mut oq = e.alloc_i8_uninit(nh * hd)?;
19136 let mut od = e.zeros(nh * hd / 32)?;
19137 e.fa_decode_dc_q8(
19138 &q,
19139 &k_view,
19140 &v_view,
19141 &mut attn,
19142 hd,
19143 nh,
19144 nkv,
19145 &kvl.len_d,
19146 bucket,
19147 scale,
19148 kvl.k_tok_bytes,
19149 kvl.v_tok_bytes,
19150 g,
19151 Some((&mut oq, &mut od)),
19152 )?;
19153 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
19154 }
19155 e.fa_decode_dc(
19156 &q,
19157 &k_view,
19158 &v_view,
19159 &mut attn,
19160 hd,
19161 nh,
19162 nkv,
19163 &kvl.len_d,
19164 bucket,
19165 scale,
19166 kvl.k_tok_bytes,
19167 kvl.v_tok_bytes,
19168 g,
19169 )?;
19170 return Ok(e.matmul(&fa.wo, &attn, t)?);
19171 }
19172 for i in 0..t {
19173 let avail = base_len + i + 1;
19174 let (off_tok, t_kv) = if swa && avail > win {
19175 (avail - win, win)
19176 } else {
19177 (0, avail)
19178 };
19179 let k_view = e.view_u8_range(
19180 &kvl.k,
19181 off_tok * kvl.k_tok_bytes,
19182 (off_tok + t_kv) * kvl.k_tok_bytes,
19183 );
19184 let v_view = e.view_u8_range(
19185 &kvl.v,
19186 off_tok * kvl.v_tok_bytes,
19187 (off_tok + t_kv) * kvl.v_tok_bytes,
19188 );
19189 let qv = e.view(&q, t * nh * hd);
19190 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
19191 let mut q_one = e.uninit(nh * hd)?;
19192 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
19193 let mut a_one = e.uninit(nh * hd)?;
19194 e.fa_decode_kvmod(
19198 &q_one,
19199 &k_view,
19200 &v_view,
19201 &mut a_one,
19202 hd,
19203 nh,
19204 nkv,
19205 t_kv,
19206 scale,
19207 kvl.k_tok_bytes,
19208 kvl.v_tok_bytes,
19209 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
19210 )?;
19211 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
19212 }
19213 Ok(e.matmul(&fa.wo, &attn, t)?)
19214 }
19215
19216 fn gemma4_e4b_trunk(
19221 &self,
19222 e: &Engine,
19223 tokens: &[u32],
19224 pos0: usize,
19225 cache: &mut Cache,
19226 head_last: bool,
19227 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19228 let n_embd = self.cfg.n_embd as usize;
19229 let t = tokens.len();
19230 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
19231 let pos_d = e.htod_i32(&pos)?;
19232 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
19233 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
19234 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
19235 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
19236 }
19237
19238 fn gemma4_e4b_trunk_core(
19242 &self,
19243 e: &Engine,
19244 x_in: CudaSlice<f32>,
19245 inp_pl: CudaSlice<f32>,
19246 pos_d: &CudaSlice<i32>,
19247 t: usize,
19248 cache: &mut Cache,
19249 dc_bucket: Option<usize>,
19250 cap_logits: bool,
19251 head_last: bool,
19252 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19253 let n_embd = self.cfg.n_embd as usize;
19254 let eps = self.cfg.rms_eps;
19255 let n_layer = self.layers.len();
19256 let mut x = x_in;
19257 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
19258 let n_epl = aux_e4b.n_epl;
19259
19260 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
19266 for il in 0..n_layer {
19267 let layer = &self.layers[il];
19268 let (hq, hdq) = match h_carry.take() {
19269 Some(p) => p,
19270 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
19271 };
19272 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
19273 let bits = layer.gemma4.as_ref().unwrap();
19276 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
19277 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
19288 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
19289 e,
19290 layer,
19291 &o,
19292 &x,
19293 t,
19294 Some(layer.post_attn_norm.float_data()),
19295 fuse_exit,
19296 )?;
19297 let mut resid = e.uninit(t * n_embd)?;
19298 let g = if fuse_exit {
19304 let (rq, rd) = e.rms_pre_add_q8_1(
19306 &sn,
19307 bits.post_ffw_norm.float_data(),
19308 &attn_out,
19309 &mut resid,
19310 n_embd,
19311 t,
19312 self.cfg.rms_eps,
19313 )?;
19314 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
19315 } else {
19316 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
19317 e.matmul(&e4b.inp_gate, &resid, t)?
19318 };
19319 let mut act = e.uninit(t * n_epl)?;
19320 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
19321 let ipv = e.view(&inp_pl, n_epl * n_layer);
19322 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
19323 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
19324 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
19325 } else {
19326 let mut inp_this = e.uninit(t * n_epl)?;
19327 e.copy_rows_strided(
19328 &inp_pl,
19329 &mut inp_this,
19330 n_epl,
19331 t,
19332 n_epl * n_layer,
19333 il * n_epl,
19334 )?;
19335 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
19336 e.matmul(&e4b.proj, &act, t)?
19337 };
19338 let next_norm = if il + 1 < n_layer {
19341 self.layers[il + 1].attn_norm.float_data()
19342 } else {
19343 self.output_norm.float_data()
19344 };
19345 let mut xn = e.uninit(t * n_embd)?;
19346 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
19347 &y,
19348 e4b.post_norm.float_data(),
19349 &resid,
19350 bits.layer_scale,
19351 next_norm,
19352 &mut xn,
19353 n_embd,
19354 t,
19355 eps,
19356 )?;
19357 h_carry = Some(pair);
19358 x = xn;
19359 }
19360 let (oq, odq) = h_carry.take().unwrap();
19364 let h0 = e.zeros(0)?;
19365 let hm = if head_last { 1 } else { t };
19366 let (hq, hd) = if head_last && t > 1 {
19367 let mut q1 = e.uninit_i8(n_embd)?;
19368 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
19369 let nb = n_embd / 32;
19370 let mut d1 = e.uninit(nb)?;
19371 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
19372 (q1, d1)
19373 } else {
19374 (oq, odq)
19375 };
19376 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
19377 if cap_logits {
19381 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
19382 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
19383 }
19384 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
19386 }
19387
19388 pub fn gemma4_e4b_decode_step_t_am_dev(
19395 &self,
19396 e: &Engine,
19397 tok_d: &CudaSlice<u32>,
19398 t: usize,
19399 pos0: usize,
19400 cache: &mut Cache,
19401 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19402 let n_embd = self.cfg.n_embd as usize;
19403 let eps = self.cfg.rms_eps;
19404 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
19405 let pos_d = e.htod_i32(&pos)?;
19406 let embd_gpu = self
19407 .embd_gpu
19408 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
19409 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
19410 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
19411 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
19412 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
19413 let (ld, xp) =
19414 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
19415 let n_vocab = self.output.out_features();
19418 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
19419 for i in 0..t {
19420 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
19421 }
19422 let mut hn = e.uninit(t * n_embd)?;
19423 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
19424 cache.pos += t;
19425 Ok((vam, hn))
19426 }
19427
19428 pub(crate) fn gemma4_e4b_decode_step_t_h(
19431 &self,
19432 e: &Engine,
19433 tokens: &[u32],
19434 pos0: usize,
19435 cache: &mut Cache,
19436 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19437 let n_embd = self.cfg.n_embd as usize;
19438 let eps = self.cfg.rms_eps;
19439 let t = tokens.len();
19440 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
19441 let mut hn = e.uninit(t * n_embd)?;
19442 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
19443 cache.pos += t;
19444 Ok((e.dtoh(&ld)?, hn))
19445 }
19446
19447 pub fn gemma4_e4b_decode_step_dcg(
19453 &self,
19454 e: &Engine,
19455 token_d: &mut CudaSlice<u32>,
19456 pos_d: &mut CudaSlice<i32>,
19457 embd_gpu: &CudaSlice<u8>,
19458 embd_qt: i32,
19459 embd_rb: usize,
19460 cache: &mut Cache,
19461 n_vocab: usize,
19462 bucket: usize,
19463 ) -> Result<(), Box<dyn std::error::Error>> {
19464 let n_embd = self.cfg.n_embd as usize;
19465 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
19466 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
19467 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
19468 let (ld, _x) =
19469 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
19470 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
19471 e.inc_seqlen(pos_d)?;
19472 Ok(())
19473 }
19474
19475 #[allow(clippy::too_many_arguments)]
19483 pub fn gemma4_e4b_decode_step_dc(
19484 &self,
19485 e: &Engine,
19486 token_d: &CudaSlice<u32>,
19487 pos_d: &mut CudaSlice<i32>,
19488 embd_gpu: &CudaSlice<u8>,
19489 embd_qt: i32,
19490 embd_rb: usize,
19491 cache: &mut Cache,
19492 n_vocab: usize,
19493 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
19494 let n_embd = self.cfg.n_embd as usize;
19495 let eps = self.cfg.rms_eps;
19496 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
19497 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
19498 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
19499 let (ld, _x) =
19500 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
19501 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
19502 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
19503 e.inc_seqlen(pos_d)?;
19504 cache.pos += 1;
19505 let _ = eps;
19506 Ok(tok_out)
19507 }
19508
19509 pub(crate) fn gemma4_e4b_decode_step_h(
19512 &self,
19513 e: &Engine,
19514 token: u32,
19515 cache: &mut Cache,
19516 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19517 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
19518 let logits = e.dtoh(&ld)?;
19519 cache.pos += 1;
19520 Ok((logits, x))
19521 }
19522
19523 pub(crate) fn gemma4_e4b_prime(
19527 &self,
19528 e: &Engine,
19529 tokens: &[u32],
19530 cache: &mut Cache,
19531 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19532 if cache.pos != 0 {
19535 return Err(
19536 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
19537 call or decode tokenwise"
19538 .into(),
19539 );
19540 }
19541 let n_embd = self.cfg.n_embd as usize;
19542 let t = tokens.len();
19543 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
19544 cache.pos += t;
19545 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
19547 let row = xv.slice((t - 1) * n_embd..t * n_embd);
19548 let mut h_seed = e.uninit(n_embd)?;
19549 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
19550 Ok((last, h_seed, x))
19551 }
19552
19553 pub(crate) fn gemma4_e4b_forward(
19555 &self,
19556 e: &Engine,
19557 tokens: &[u32],
19558 last_only: bool,
19559 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
19560 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
19561 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
19562 Ok(e.dtoh(&ld)?) }
19564}
19565
19566#[cfg(test)]
19567mod prime_chunk_schedule_tests {
19568 use super::{
19569 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, active_matrix_values, align_prime_ranges_to_gdn,
19570 dynamic_prime_chunk_ranges, fixed_prime_chunk_ranges, fixed_prime_chunk_ranges_for_ring,
19571 parse_step_ep_grouped_prefill, parse_step_tp_prefill, step_grouped_decode_shape,
19572 step_grouped_prefill_shape, step_tp_prefill_shape, validate_step_prime_batch_modes,
19573 };
19574
19575 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
19576 ranges.iter().map(|(start, end)| end - start).collect()
19577 }
19578
19579 fn auto_chunk(t: usize) -> usize {
19580 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
19581 }
19582
19583 #[test]
19588 fn auto_prime_ranges_align_to_the_gdn_grid() {
19589 let c = 32usize; let assert_covers = |ranges: &[(usize, usize)], t: usize| {
19591 assert_eq!(ranges.first().map(|&(s, _)| s), Some(0));
19592 assert_eq!(ranges.last().map(|&(_, e)| e), Some(t));
19593 for w in ranges.windows(2) {
19594 assert_eq!(w[0].1, w[1].0, "ranges must stay contiguous");
19595 }
19596 assert!(ranges.iter().all(|&(s, e)| e > s), "no empty range");
19597 };
19598
19599 let t = 9510usize;
19602 let fill = auto_chunk(t);
19603 let fixed = fixed_prime_chunk_ranges(t, fill);
19604 assert!(
19605 fixed[..fixed.len() - 1].iter().any(|&(_, e)| e % c != 0),
19606 "broken arm vanished: fixed auto boundaries all landed on-grid"
19607 );
19608 let dynamic = dynamic_prime_chunk_ranges(t, fill, &fixed);
19609 assert!(
19610 dynamic[..dynamic.len() - 1]
19611 .iter()
19612 .any(|&(_, e)| e % c != 0),
19613 "broken arm vanished: dynamic auto boundaries all landed on-grid"
19614 );
19615
19616 for ranges in [&fixed, &dynamic] {
19617 let aligned = align_prime_ranges_to_gdn(ranges, t, c);
19618 assert_covers(&aligned, t);
19619 for &(_, e) in &aligned[..aligned.len() - 1] {
19620 assert_eq!(e % c, 0, "internal boundary {e} off the {c}-grid");
19621 }
19622 for (&(_, a), &(_, b)) in aligned.iter().zip(ranges.iter()) {
19624 assert!(a <= b && b - a < c);
19625 }
19626 }
19627
19628 let tight = vec![(0usize, 33usize), (33, 40), (40, 200)];
19631 let aligned = align_prime_ranges_to_gdn(&tight, 200, c);
19632 assert_covers(&aligned, 200);
19633 assert_eq!(aligned, vec![(0, 32), (32, 200)]);
19634
19635 assert_eq!(align_prime_ranges_to_gdn(&[(0, 200)], 200, c), [(0, 200)]);
19637 assert_eq!(align_prime_ranges_to_gdn(&tight, 200, 0), tight.as_slice());
19638 let on_grid = vec![(0usize, 128usize), (128, 256), (256, 300)];
19639 assert_eq!(
19640 align_prime_ranges_to_gdn(&on_grid, 300, c),
19641 on_grid.as_slice()
19642 );
19643 }
19644
19645 #[test]
19646 fn active_matrix_prefix_scopes_reused_prime_slabs() {
19647 assert_eq!(
19648 active_matrix_values(40 * 4096, 29, 4096, "activation").unwrap(),
19649 29 * 4096
19650 );
19651 assert_eq!(
19652 active_matrix_values(29 * 4096, 29, 4096, "activation").unwrap(),
19653 29 * 4096
19654 );
19655 assert_eq!(
19656 active_matrix_values(29 * 4096, 24, 4096, "activation").unwrap(),
19657 24 * 4096
19658 );
19659 assert!(active_matrix_values(28 * 4096, 29, 4096, "activation").is_err());
19660 assert!(active_matrix_values(usize::MAX, usize::MAX, 2, "activation").is_err());
19661 }
19662
19663 #[test]
19664 fn step_tp_prefill_batch_refuses_before_scheduler_fallback() {
19665 assert!(validate_step_prime_batch_modes(false, false).is_ok());
19666
19667 let grouped_without_tp = validate_step_prime_batch_modes(false, true).unwrap_err();
19668 assert!(grouped_without_tp.contains("requires MEMRA_STEP_TP_PREFILL=1"));
19669
19670 for grouped in [false, true] {
19671 let err = validate_step_prime_batch_modes(true, grouped).unwrap_err();
19672 assert!(err.contains("did not clear the live-server performance gate"));
19673 assert!(err.contains("per-session grouped prefill"));
19674 }
19675 }
19676
19677 #[test]
19678 fn step_grouped_path_is_eager_single_token_only() {
19679 assert!(step_grouped_decode_shape(false, 1));
19680 assert!(!step_grouped_decode_shape(true, 1));
19681 assert!(!step_grouped_decode_shape(false, 2));
19682 assert!(!step_grouped_decode_shape(true, 2));
19683 }
19684
19685 #[test]
19686 fn step_grouped_prefill_door_is_strict_and_capacity_bounded() {
19687 assert!(!parse_step_ep_grouped_prefill(None).unwrap());
19688 assert!(!parse_step_ep_grouped_prefill(Some("")).unwrap());
19689 assert!(!parse_step_ep_grouped_prefill(Some("0")).unwrap());
19690 assert!(parse_step_ep_grouped_prefill(Some("1")).unwrap());
19691 assert!(parse_step_ep_grouped_prefill(Some("true")).is_err());
19692 assert!(parse_step_ep_grouped_prefill(Some("2")).is_err());
19693
19694 assert!(step_grouped_prefill_shape(true, true, PRIME_MIN_T));
19695 assert!(step_grouped_prefill_shape(
19696 true,
19697 true,
19698 crate::cache::PRIME_CHUNK_MAX_TOKENS,
19699 ));
19700 assert!(!step_grouped_prefill_shape(true, true, PRIME_MIN_T - 1,));
19701 assert!(!step_grouped_prefill_shape(
19702 true,
19703 true,
19704 crate::cache::PRIME_CHUNK_MAX_TOKENS + 1,
19705 ));
19706 assert!(!step_grouped_prefill_shape(false, true, PRIME_MIN_T));
19707 assert!(!step_grouped_prefill_shape(true, false, PRIME_MIN_T));
19708 }
19709
19710 #[test]
19711 fn step_tp_prefill_door_is_strict_and_default_off() {
19712 assert!(!parse_step_tp_prefill(None).unwrap());
19713 assert!(!parse_step_tp_prefill(Some("")).unwrap());
19714 assert!(!parse_step_tp_prefill(Some("0")).unwrap());
19715 assert!(parse_step_tp_prefill(Some("1")).unwrap());
19716 assert!(parse_step_tp_prefill(Some("true")).is_err());
19717 assert!(parse_step_tp_prefill(Some("2")).is_err());
19718 }
19719
19720 #[test]
19721 fn step_tp_prefill_requires_a_qualified_even_rank_shape() {
19722 assert!(step_tp_prefill_shape(
19723 true,
19724 PRIME_MIN_T,
19725 4,
19726 true,
19727 true,
19728 false,
19729 ));
19730 assert!(!step_tp_prefill_shape(
19731 false,
19732 PRIME_MIN_T,
19733 4,
19734 true,
19735 true,
19736 false,
19737 ));
19738 assert!(!step_tp_prefill_shape(
19739 true,
19740 PRIME_MIN_T - 1,
19741 4,
19742 true,
19743 true,
19744 false,
19745 ));
19746 assert!(step_tp_prefill_shape(
19748 true,
19749 PRIME_MIN_T,
19750 2,
19751 true,
19752 true,
19753 false
19754 ));
19755 assert!(!step_tp_prefill_shape(
19756 true,
19757 PRIME_MIN_T,
19758 1,
19759 true,
19760 true,
19761 false
19762 ));
19763 assert!(!step_tp_prefill_shape(
19764 true,
19765 PRIME_MIN_T,
19766 3,
19767 true,
19768 true,
19769 false
19770 ));
19771 assert!(!step_tp_prefill_shape(
19772 true,
19773 PRIME_MIN_T,
19774 4,
19775 false,
19776 true,
19777 false,
19778 ));
19779 assert!(!step_tp_prefill_shape(
19780 true,
19781 PRIME_MIN_T,
19782 4,
19783 true,
19784 false,
19785 false,
19786 ));
19787 assert!(!step_tp_prefill_shape(
19788 true,
19789 PRIME_MIN_T,
19790 4,
19791 true,
19792 true,
19793 true,
19794 ));
19795 }
19796
19797 #[test]
19798 fn fixed_schedule_retains_measured_geometry() {
19799 assert_eq!(
19800 sizes(&fixed_prime_chunk_ranges(461, 128)),
19801 vec![128, 128, 128, 77]
19802 );
19803 assert_eq!(
19804 sizes(&fixed_prime_chunk_ranges(1833, 230)),
19805 vec![230, 230, 230, 230, 230, 230, 230, 223]
19806 );
19807 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
19808 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
19809 assert_eq!(capped, vec![4096, 4088, 16]);
19810 assert!(capped.iter().all(|&rows| rows <= 4096));
19811 assert_eq!(
19812 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
19813 vec![4100],
19814 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
19815 );
19816 }
19817
19818 #[test]
19819 fn dynamic_schedule_matches_registered_shapes() {
19820 let cases = [
19821 (461, vec![64, 141, 132, 124]),
19822 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
19823 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
19824 ];
19825 for (t, expected) in cases {
19826 let chunk = auto_chunk(t);
19827 let fixed = fixed_prime_chunk_ranges(t, chunk);
19828 assert_eq!(
19829 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
19830 expected
19831 );
19832 }
19833 }
19834
19835 #[test]
19836 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
19837 for t in 256..=8192 {
19838 let chunk = auto_chunk(t);
19839 let fixed = fixed_prime_chunk_ranges(t, chunk);
19840 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
19841 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
19842 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
19843 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
19844 for pair in dynamic.windows(2) {
19845 assert_eq!(pair[0].1, pair[1].0, "T={t}");
19846 }
19847 assert!(
19848 dynamic
19849 .iter()
19850 .all(|(start, end)| end - start >= PRIME_MIN_T),
19851 "T={t} sizes={:?}",
19852 sizes(&dynamic)
19853 );
19854 if dynamic.len() >= 3 {
19855 let chunk_sizes = sizes(&dynamic);
19856 assert!(
19857 chunk_sizes[0] < chunk_sizes[1],
19858 "T={t} sizes={chunk_sizes:?}"
19859 );
19860 assert!(
19861 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
19862 "T={t} sizes={chunk_sizes:?}"
19863 );
19864 }
19865 }
19866 }
19867}
19868
19869#[cfg(test)]
19870mod page_prefetch_tests {
19871 use super::{
19872 grouped_worker_prefetch_position, page_prefetch_positions,
19873 page_prefetch_window_from_values, worker_prefetch_positions,
19874 };
19875
19876 #[test]
19877 fn page_prefetch_window_keeps_existing_opt_in_default() {
19878 assert_eq!(page_prefetch_window_from_values(false, None), 0);
19879 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
19880 assert_eq!(page_prefetch_window_from_values(true, None), 1);
19881 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
19882 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
19883 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
19884 }
19885
19886 #[test]
19887 fn rolling_page_prefetch_advises_each_future_expert_once() {
19888 let advised: Vec<_> = (0..7)
19889 .flat_map(|position| page_prefetch_positions(position, 7, 3))
19890 .collect();
19891 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
19892
19893 let one_ahead: Vec<_> = (0..4)
19894 .flat_map(|position| page_prefetch_positions(position, 4, 1))
19895 .collect();
19896 assert_eq!(one_ahead, vec![1, 2, 3]);
19897 assert!(page_prefetch_positions(0, 4, 0).is_empty());
19898 }
19899
19900 #[test]
19901 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
19902 assert_eq!(grouped_worker_prefetch_position(0, None), None);
19903 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
19904 .chain(
19905 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
19906 )
19907 .collect();
19908 assert_eq!(positions, vec![0, 1, 2, 3]);
19909 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
19910 }
19911
19912 #[test]
19913 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
19914 let queued: Vec<_> = (0..8)
19915 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
19916 .collect();
19917 assert_eq!(queued, (0..8).collect::<Vec<_>>());
19918
19919 let one_at_a_time: Vec<_> = (0..4)
19920 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
19921 .collect();
19922 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
19923 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
19924 }
19925}
19926
19927pub struct G4DcSlots {
19928 x: CudaSlice<f32>,
19929 xn: CudaSlice<f32>,
19930 cur: CudaSlice<f32>,
19931 hq: CudaSlice<i8>,
19932 hd_: CudaSlice<f32>,
19933 q0: CudaSlice<f32>,
19934 k0: CudaSlice<f32>,
19935 v0: CudaSlice<f32>,
19936 q: CudaSlice<f32>,
19937 k: CudaSlice<f32>,
19938 v: CudaSlice<f32>,
19939 attn: CudaSlice<f32>,
19940 o: CudaSlice<f32>,
19941 attn_out: CudaSlice<f32>,
19942 zsh: CudaSlice<f32>,
19943 zq: CudaSlice<i8>,
19944 zd: CudaSlice<f32>,
19945 gate: CudaSlice<f32>,
19946 up: CudaSlice<f32>,
19947 act: CudaSlice<f32>,
19948 actq: CudaSlice<i8>,
19949 actd: CudaSlice<f32>,
19950 f0: CudaSlice<f32>,
19951 sn: CudaSlice<f32>,
19952 hn: CudaSlice<f32>,
19953 logits: CudaSlice<f32>,
19954}
19955
19956pub struct Step35TokenGraphState {
19961 pub graphs: Vec<(usize, crate::tp::TokenGraph)>,
19963 pub token_d: cudarc::driver::CudaSlice<u32>,
19964 pub pos_d: cudarc::driver::CudaSlice<i32>,
19965 pub logits_stage: cudarc::driver::CudaSlice<f32>,
19966 pub x: cudarc::driver::CudaSlice<f32>,
19971 pub x1: cudarc::driver::CudaSlice<f32>,
19972 pub mixed_stage: cudarc::driver::CudaSlice<f32>,
19973 pub sh_stage: cudarc::driver::CudaSlice<f32>,
19974 pub k_shadow_stage: cudarc::driver::CudaSlice<f32>,
19975 pub v_shadow_stage: cudarc::driver::CudaSlice<f32>,
19976 pub router_logits: cudarc::driver::CudaSlice<f32>,
19979 pub shexp_gate: cudarc::driver::CudaSlice<f32>,
19980 pub shexp_up: cudarc::driver::CudaSlice<f32>,
19981 pub shexp_act: cudarc::driver::CudaSlice<f32>,
19982 pub gate_sig: cudarc::driver::CudaSlice<f32>,
19983 pub dense_z: cudarc::driver::CudaSlice<f32>,
19984 pub dense_gate: cudarc::driver::CudaSlice<f32>,
19985 pub dense_up: cudarc::driver::CudaSlice<f32>,
19986 pub dense_act: cudarc::driver::CudaSlice<f32>,
19987 pub hn: cudarc::driver::CudaSlice<f32>,
19988 pub probe_mixed: cudarc::driver::CudaSlice<f32>,
19991 pub probe_x: cudarc::driver::CudaSlice<f32>,
19992 pub token_hist: cudarc::driver::CudaSlice<u32>,
19995 pub hist_idx: cudarc::driver::CudaSlice<i32>,
19996}
19997
19998impl HybridModel {
19999 pub(crate) fn step35_token_graph_step(
20010 &self,
20011 e: &Engine,
20012 token: u32,
20013 cache: &mut Cache,
20014 ) -> Result<Option<(Vec<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
20015 if !self.uses_sliding_gated_moe_program()
20016 || !crate::tp::step_tp_graph_enabled()?
20017 || !crate::tp::step_tp_dcw_enabled()?
20018 || !crate::tp::step_tp_qkv_fused_enabled()?
20019 || !crate::tp::step_tp_dev_router_enabled()?
20020 || !crate::tp::step_nvfp4_dev_routes_enabled()?
20021 {
20022 return Ok(None);
20023 }
20024 let n_embd = self.cfg.n_embd as usize;
20025 let n_vocab = self.cfg.n_vocab as usize;
20026 let eps = self.cfg.rms_eps;
20027 let n_layers = self.layers.len();
20028 let pos = cache.pos;
20029 let staged_next = pos + 1;
20030 if staged_next < 96 {
20031 return Ok(None); }
20033
20034 for il in 0..n_layers {
20037 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
20038 return Ok(None); };
20040 if tp_kv.peek_append_ring(1)?.1 {
20041 return Ok(None);
20042 }
20043 }
20044
20045 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
20048 if !fa_vec {
20049 return Ok(None);
20050 }
20051 let sp = crate::fa_split_keys(staged_next, 8);
20052 let bucket_max = (n_splits * sp).max(staged_next);
20053
20054 let mut state_guard = self
20055 .step35_token_graph
20056 .lock()
20057 .map_err(|_| "step35 token graph lock is poisoned")?;
20058 if state_guard.is_none() {
20059 let _main = e.gpu.enter_main()?;
20060 let n_expert = self
20061 .cfg
20062 .moe
20063 .as_ref()
20064 .map(|m| m.expert_count as usize)
20065 .unwrap_or(0);
20066 let n_ff_sh = self
20067 .layers
20068 .iter()
20069 .find_map(|l| match &l.ffn {
20070 crate::hybrid::Ffn::Moe(m) => m.gate_shexp.as_ref().map(|g| g.out_features()),
20071 _ => None,
20072 })
20073 .unwrap_or(0);
20074 let n_ff_dense = self
20075 .layers
20076 .iter()
20077 .find_map(|l| match &l.ffn {
20078 crate::hybrid::Ffn::Dense { ffn_gate, .. } => Some(ffn_gate.out_features()),
20079 _ => None,
20080 })
20081 .unwrap_or(0);
20082 *state_guard = Some(Step35TokenGraphState {
20083 graphs: Vec::new(),
20084 token_d: e.stream().clone_htod(&[0u32])?,
20085 pos_d: e.htod_i32(&[pos as i32])?,
20086 logits_stage: e.htod(&vec![0.0f32; n_vocab])?,
20087 x: e.htod(&vec![0.0f32; n_embd])?,
20088 x1: e.htod(&vec![0.0f32; n_embd])?,
20089 mixed_stage: e.htod(&vec![0.0f32; n_embd])?,
20090 sh_stage: e.htod(&vec![0.0f32; n_embd])?,
20091 k_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
20092 v_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
20093 router_logits: e.htod(&vec![0.0f32; n_expert.max(1)])?,
20094 shexp_gate: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
20095 shexp_up: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
20096 shexp_act: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
20097 gate_sig: e.htod(&vec![1.0f32; 1])?,
20098 dense_z: e.htod(&vec![0.0f32; n_embd])?,
20099 dense_gate: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
20100 dense_up: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
20101 dense_act: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
20102 hn: e.htod(&vec![0.0f32; n_embd])?,
20103 probe_mixed: e.htod(&vec![0.0f32; n_embd])?,
20104 probe_x: e.htod(&vec![0.0f32; n_embd])?,
20105 token_hist: e.stream().clone_htod(&[0u32; 16])?,
20106 hist_idx: e.htod_i32(&[0])?,
20107 });
20108 }
20109 let state = state_guard.as_mut().expect("state armed above");
20110 {
20114 let _main = e.gpu.enter_main()?;
20115 let Step35TokenGraphState {
20116 logits_stage,
20117 token_d,
20118 ..
20119 } = &mut *state;
20120 e.argmax_token_device_into(logits_stage, token_d, n_vocab)?;
20121 }
20122
20123 if state.graphs.is_empty() {
20128 self.step35_token_graph_build(e, cache, state, bucket_max)?;
20131 }
20132 {
20133 let (b, g) = state.graphs.first_mut().expect("graph built above");
20134 if *b != bucket_max {
20135 g.retarget_bucket(bucket_max)?;
20136 *b = bucket_max;
20137 }
20138 }
20139 let graph = state
20140 .graphs
20141 .first()
20142 .map(|(_, g)| g)
20143 .expect("graph built above");
20144
20145 let tg_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
20146 let t_fence = tg_timing.then(std::time::Instant::now);
20147 {
20152 let fa0 = match &self.layers[0].mixer {
20153 Mixer::Full(fa) => fa,
20154 _ => return Err("step35 token graph expects full-attention layers".into()),
20155 };
20156 let tp0 = fa0
20157 .step_tp_qkv
20158 .as_ref()
20159 .ok_or("step35 token graph lost its TP state")?;
20160 for rank in 0..tp0.runtime.devices().len() {
20161 let engine = tp0
20162 .runtime
20163 .rank_engine(rank)
20164 .ok_or("step35 token graph lost a rank engine")?;
20165 let _main = engine.gpu.enter_main()?;
20166 engine.stream().synchronize()?;
20167 }
20168 }
20169
20170 {
20172 let _main = e.gpu.enter_main()?;
20173 e.set_u32_one(&mut state.token_d, token)?;
20174 e.set_i32_one(&mut state.pos_d, pos as i32)?;
20175 }
20176 let t_launch = tg_timing.then(std::time::Instant::now);
20177 graph.launch(e)?;
20178 let t_book = tg_timing.then(std::time::Instant::now);
20179 for il in 0..n_layers {
20184 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
20185 let transaction = tp_kv.begin_transaction()?;
20186 let fa = match &self.layers[il].mixer {
20187 Mixer::Full(fa) => fa,
20188 _ => return Err("step35 token graph expects full-attention layers".into()),
20189 };
20190 let tp = fa
20191 .step_tp_qkv
20192 .as_ref()
20193 .ok_or("step35 token graph lost its TP state")?;
20194 let empty: [CudaSlice<f32>; 0] = [];
20197 tp.runtime.append_tp_kv_transaction_inner(
20198 tp_kv,
20199 transaction,
20200 &empty,
20201 &empty,
20202 1,
20203 true,
20204 )?;
20205 tp.runtime
20206 .commit_tp_kv_transaction_external(tp_kv, transaction, 1)?;
20207 if let Some(local) = cache.kv[il].as_mut() {
20209 local.len = pos + 1;
20210 let _main = e.gpu.enter_main()?;
20211 e.set_i32_one(&mut local.len_d, (pos + 1) as i32)?;
20212 }
20213 }
20214 cache.pos = pos + 1;
20215 let t_sync = tg_timing.then(std::time::Instant::now);
20216 let (logits, h_seed) = {
20217 let _main = e.gpu.enter_main()?;
20218 e.stream().synchronize()?;
20219 (e.dtoh(&state.logits_stage)?, e.clone_dtod(&state.x)?)
20220 };
20221 if let (Some(f), Some(l), Some(b), Some(sy)) = (t_fence, t_launch, t_book, t_sync) {
20222 use std::sync::atomic::{AtomicU64, Ordering};
20223 static NS: [AtomicU64; 5] = [
20224 AtomicU64::new(0),
20225 AtomicU64::new(0),
20226 AtomicU64::new(0),
20227 AtomicU64::new(0),
20228 AtomicU64::new(0),
20229 ];
20230 static CALLS: AtomicU64 = AtomicU64::new(0);
20231 let now = std::time::Instant::now();
20232 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;
20238 if calls % 100 == 0 {
20239 let avg = |i: usize| NS[i].load(Ordering::Relaxed) as f64 / calls as f64 / 1e3;
20240 eprintln!(
20241 "[tg-timing] calls={calls} fence_us={:.0} launch_us={:.0} book_us={:.0} \
20242 syncdtoh_us={:.0} total_us={:.0}",
20243 avg(0),
20244 avg(1),
20245 avg(2),
20246 avg(3),
20247 avg(4)
20248 );
20249 }
20250 }
20251 if std::env::var("MEMRA_TG_PROBE_LAYER").is_ok() {
20253 use std::io::Write;
20254 let (pm, px) = {
20255 let _main = e.gpu.enter_main()?;
20256 (e.dtoh(&state.probe_mixed)?, e.dtoh(&state.probe_x)?)
20257 };
20258 for (path, data) in [
20259 ("/root/tg-probe-mixed.bin", &pm),
20260 ("/root/tg-probe-x.bin", &px),
20261 ] {
20262 let mut fo = std::fs::OpenOptions::new()
20263 .create(true)
20264 .append(true)
20265 .open(path)?;
20266 for v in data {
20267 fo.write_all(&v.to_le_bytes())?;
20268 }
20269 }
20270 }
20271 if let Ok(path) = std::env::var("MEMRA_DUMP_HN") {
20274 let hh = {
20275 let _main = e.gpu.enter_main()?;
20276 e.dtoh(&state.hn)?
20277 };
20278 use std::io::Write;
20279 let mut fo = std::fs::OpenOptions::new()
20280 .create(true)
20281 .append(true)
20282 .open(path)?;
20283 for v in &hh {
20284 fo.write_all(&v.to_le_bytes())?;
20285 }
20286 }
20287 if std::env::var("MEMRA_STEP_TP_GRAPH_DEBUG").as_deref() == Ok("1") {
20290 for il in [0usize, 1, 44] {
20291 let tp_kv = cache.tp_kv[il].as_ref().expect("eligibility checked above");
20292 let host_len = tp_kv.staged_len();
20293 let fa = match &self.layers[il].mixer {
20294 Mixer::Full(fa) => fa,
20295 _ => continue,
20296 };
20297 let tp = fa
20298 .step_tp_qkv
20299 .as_ref()
20300 .ok_or("step35 token graph lost its TP state")?;
20301 for rank in 0..tp.runtime.devices().len() {
20302 let engine = tp
20303 .runtime
20304 .rank_engine(rank)
20305 .ok_or("step35 token graph lost a rank engine")?;
20306 let rank_cache = tp_kv.rank(rank).ok_or("debug rank cache missing")?;
20307 let _main = engine.gpu.enter_main()?;
20308 engine.stream().synchronize()?;
20309 let len_d = engine.dtoh_i32_one(rank_cache.len_d())?;
20310 let base_d = match rank_cache.base_d() {
20311 Some(b) => engine.dtoh_i32_one(b)?,
20312 None => -1,
20313 };
20314 eprintln!(
20315 "[graph-debug] pos={pos} il={il} rank={rank} host_len={host_len} \
20316 len_d={len_d} base_d={base_d}"
20317 );
20318 }
20319 }
20320 }
20321 Ok(Some((logits, h_seed)))
20322 }
20323
20324 pub(crate) fn head_split_matvec(
20330 &self,
20331 e: &Engine,
20332 hn: &CudaSlice<f32>,
20333 ) -> Result<Option<Vec<f32>>, Box<dyn std::error::Error>> {
20334 if self.head_split_fill_device(e, hn)?.is_none() {
20335 return Ok(None);
20336 }
20337 let guard = HEAD_SPLIT_WS
20338 .lock()
20339 .map_err(|_| "head split lock is poisoned")?;
20340 let ws = guard.as_ref().expect("filled above");
20341 let _main = e.gpu.enter_main()?;
20342 Ok(Some(e.dtoh(&ws.logits_e)?))
20343 }
20344
20345 fn head_split_fill_device(
20349 &self,
20350 e: &Engine,
20351 hn: &CudaSlice<f32>,
20352 ) -> Result<Option<()>, Box<dyn std::error::Error>> {
20353 use cudarc::driver::DevicePtr;
20354 let crate::model::GpuTensor::FloatBf16 { data: head, .. } = &self.output else {
20355 return Ok(None);
20356 };
20357 let Some(rank1) = self.layers.first().and_then(|l| match &l.mixer {
20358 Mixer::Full(fa) => fa
20359 .step_tp_qkv
20360 .as_ref()
20361 .and_then(|tp| tp.runtime.rank_engine(1)),
20362 _ => None,
20363 }) else {
20364 return Ok(None);
20365 };
20366 let n_embd = self.cfg.n_embd as usize;
20367 let n_vocab = self.cfg.n_vocab as usize;
20368 let half = n_vocab / 2;
20369 let mut guard = HEAD_SPLIT_WS
20370 .lock()
20371 .map_err(|_| "head split lock is poisoned")?;
20372 let pin = {
20373 let _main = e.gpu.enter_main()?;
20374 let stream = e.stream();
20375 let (ptr, _g) = head.device_ptr(&stream);
20376 ptr as u64
20377 };
20378 if guard.as_ref().is_none_or(|ws| ws.pin != pin) {
20379 let hi_rows = n_vocab - half;
20381 let (w1, hn1, y1, ev_done) = {
20382 let _r1 = rank1.gpu.enter_main()?;
20383 (
20384 rank1.alloc_u8_uninit(hi_rows * n_embd * 2)?,
20385 rank1.htod(&vec![0.0f32; n_embd])?,
20386 rank1.htod(&vec![0.0f32; hi_rows])?,
20387 rank1.ctx().new_event(None)?,
20388 )
20389 };
20390 {
20391 use cudarc::driver::sys;
20392 let src = pin + (half * n_embd * 2) as u64;
20393 let dst = {
20394 let _r1 = rank1.gpu.enter_main()?;
20395 let rstream = rank1.stream();
20396 let (d, _g) = w1.device_ptr(&rstream);
20397 d as u64
20398 };
20399 let _r1 = rank1.gpu.enter_main()?;
20400 let r = unsafe {
20401 sys::cuMemcpyAsync(
20402 dst as sys::CUdeviceptr,
20403 src as sys::CUdeviceptr,
20404 hi_rows * n_embd * 2,
20405 rank1.stream().cu_stream() as sys::CUstream,
20406 )
20407 };
20408 if r != sys::CUresult::CUDA_SUCCESS {
20409 return Err(format!("head split replica upload: {r:?}").into());
20410 }
20411 rank1.stream().synchronize()?;
20412 }
20413 let (logits_e, ev_hn) = {
20414 let _main = e.gpu.enter_main()?;
20415 (e.htod(&vec![0.0f32; n_vocab])?, e.ctx().new_event(None)?)
20416 };
20417 let (raw_hn1, raw_y1) = {
20418 let _r1 = rank1.gpu.enter_main()?;
20419 let rstream = rank1.stream();
20420 let (a, _g0) = hn1.device_ptr(&rstream);
20421 let (b, _g1) = y1.device_ptr(&rstream);
20422 (a as u64, b as u64)
20423 };
20424 let raw_logits_hi = {
20425 let _main = e.gpu.enter_main()?;
20426 let stream = e.stream();
20427 let (l, _g) = logits_e.device_ptr(&stream);
20428 l as u64 + (half * 4) as u64
20429 };
20430 *guard = Some(HeadSplit {
20431 pin,
20432 w1,
20433 hn1,
20434 y1,
20435 logits_e,
20436 ev_hn,
20437 ev_done,
20438 raw_hn1,
20439 raw_y1,
20440 raw_logits_hi,
20441 samp: None,
20442 });
20443 }
20444 let ws = guard.as_mut().expect("armed above");
20445 let hi_rows = n_vocab - half;
20446 let raw_hn = {
20448 let _main = e.gpu.enter_main()?;
20449 let stream = e.stream();
20450 let (h, _g) = hn.device_ptr(&stream);
20451 ws.ev_hn.record(&stream)?;
20452 h as u64
20453 };
20454 {
20455 let _r1 = rank1.gpu.enter_main()?;
20456 rank1.stream().wait(&ws.ev_hn)?;
20457 crate::tp::raw_copy_bytes(ws.raw_hn1, raw_hn, n_embd * 4, rank1)?;
20458 let HeadSplit { w1, hn1, y1, .. } = &mut *ws;
20459 rank1.matvec_bf16_into(w1, hn1, y1, n_embd, hi_rows)?;
20460 crate::tp::raw_copy_bytes(ws.raw_logits_hi, ws.raw_y1, hi_rows * 4, rank1)?;
20461 ws.ev_done.record(&rank1.stream())?;
20462 }
20463 {
20464 let _main = e.gpu.enter_main()?;
20465 let head_lo = head.slice(0..half * n_embd * 2);
20466 let HeadSplit { logits_e, .. } = &mut *ws;
20467 e.matvec_bf16_view_into(&head_lo, hn, logits_e, n_embd, half)?;
20469 e.stream().wait(&ws.ev_done)?;
20470 Ok(Some(()))
20471 }
20472 }
20473
20474 pub(crate) fn head_split_argmax_device(
20479 &self,
20480 e: &Engine,
20481 hn: &CudaSlice<f32>,
20482 token_d: &mut CudaSlice<u32>,
20483 ) -> Result<bool, Box<dyn std::error::Error>> {
20484 if self.head_split_fill_device(e, hn)?.is_none() {
20485 return Ok(false);
20486 }
20487 let n_vocab = self.cfg.n_vocab as usize;
20488 let guard = HEAD_SPLIT_WS
20489 .lock()
20490 .map_err(|_| "head split lock is poisoned")?;
20491 let ws = guard.as_ref().expect("filled above");
20492 let _main = e.gpu.enter_main()?;
20493 e.argmax_token_device_into(&ws.logits_e, token_d, n_vocab)?;
20494 Ok(true)
20495 }
20496
20497 pub(crate) fn head_split_sample_device(
20503 &self,
20504 e: &Engine,
20505 hn: &CudaSlice<f32>,
20506 token_d: &mut CudaSlice<u32>,
20507 samp: &crate::decode_batch::DevSamp,
20508 ctr: u32,
20509 ) -> Result<bool, Box<dyn std::error::Error>> {
20510 if self.head_split_fill_device(e, hn)?.is_none() {
20511 return Ok(false);
20512 }
20513 let n_vocab = self.cfg.n_vocab as usize;
20514 let guard = HEAD_SPLIT_WS
20515 .lock()
20516 .map_err(|_| "head split lock is poisoned")?;
20517 let mut guard = guard;
20518 let ws = guard.as_mut().expect("filled above");
20519 let _main = e.gpu.enter_main()?;
20520 if ws.samp.is_none() {
20521 ws.samp = Some(SampScratch {
20522 pb: e.zeros(n_vocab)?,
20523 th: e.zeros(1)?,
20524 z: e.zeros(1)?,
20525 mx: e.zeros(1)?,
20526 rows: e.htod_i32(&[0i32])?,
20527 });
20528 }
20529 let filtered = samp.top_k > 0 || samp.top_p < 1.0 || samp.min_p > 0.0;
20530 let HeadSplit {
20531 logits_e,
20532 samp: scratch,
20533 ..
20534 } = &mut *ws;
20535 let sc = scratch.as_mut().expect("armed above");
20536 if filtered {
20537 e.filter_stats(
20538 logits_e, n_vocab, &sc.rows, &mut sc.th, &mut sc.z, &mut sc.mx, n_vocab, 1,
20539 samp.temp, samp.top_k, samp.top_p, samp.min_p,
20540 )?;
20541 let SampScratch { pb, th, mx, .. } = sc;
20542 e.gumbel_perturb_filtered_col(
20543 logits_e, 0, pb, n_vocab, samp.seed, ctr, samp.temp, mx, th, 0,
20544 )?;
20545 } else {
20546 e.gumbel_perturb_col(logits_e, 0, &mut sc.pb, n_vocab, samp.seed, ctr, samp.temp)?;
20547 }
20548 e.argmax_token_device_col(&sc.pb, 0, n_vocab, token_d, 0)?;
20549 Ok(true)
20550 }
20551
20552 pub(crate) fn head_split_logits_dtoh(
20555 &self,
20556 e: &Engine,
20557 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
20558 let guard = HEAD_SPLIT_WS
20559 .lock()
20560 .map_err(|_| "head split lock is poisoned")?;
20561 let ws = guard.as_ref().ok_or("head split logits not armed")?;
20562 let _main = e.gpu.enter_main()?;
20563 Ok(e.dtoh(&ws.logits_e)?)
20564 }
20565
20566 pub fn step35_token_graph_chunk(
20575 &self,
20576 e: &Engine,
20577 token: u32,
20578 k_target: usize,
20579 cache: &mut Cache,
20580 ) -> Result<Option<(Vec<u32>, Vec<f32>)>, Box<dyn std::error::Error>> {
20581 if !self.uses_sliding_gated_moe_program()
20582 || !crate::tp::step_tp_graph_enabled()?
20583 || !crate::tp::step_tp_dcw_enabled()?
20584 || !crate::tp::step_tp_qkv_fused_enabled()?
20585 || !crate::tp::step_tp_dev_router_enabled()?
20586 || !crate::tp::step_nvfp4_dev_routes_enabled()?
20587 {
20588 return Ok(None);
20589 }
20590 let n_layers = self.layers.len();
20591 let pos = cache.pos;
20592 let staged_next = pos + 1;
20593 if staged_next < 96 {
20594 return Ok(None);
20595 }
20596 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
20599 if !fa_vec {
20600 return Ok(None);
20601 }
20602 let sp = crate::fa_split_keys(staged_next, 8);
20603 let bucket_max = (n_splits * sp).max(staged_next);
20604 let to_boundary = bucket_max.saturating_sub(staged_next) + 1;
20605 let mut k = k_target.min(to_boundary).min(16);
20606 if k < 2 {
20607 return Ok(None);
20608 }
20609 for il in 0..n_layers {
20611 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
20612 return Ok(None);
20613 };
20614 while k >= 2 && tp_kv.peek_append_ring(k)?.1 {
20615 k -= 1;
20616 }
20617 if k < 2 {
20618 return Ok(None);
20619 }
20620 }
20621
20622 let mut state_guard = self
20623 .step35_token_graph
20624 .lock()
20625 .map_err(|_| "step35 token graph lock is poisoned")?;
20626 let Some(state) = state_guard.as_mut() else {
20627 return Ok(None); };
20629 if state.graphs.is_empty() {
20630 return Ok(None);
20631 }
20632 {
20633 let (b, g) = state.graphs.first_mut().expect("checked above");
20634 if *b != bucket_max {
20635 g.retarget_bucket(bucket_max)?;
20636 *b = bucket_max;
20637 }
20638 }
20639 let graph = state.graphs.first().map(|(_, g)| g).expect("checked above");
20640
20641 {
20643 let fa0 = match &self.layers[0].mixer {
20644 Mixer::Full(fa) => fa,
20645 _ => return Err("step35 token graph expects full-attention layers".into()),
20646 };
20647 let tp0 = fa0
20648 .step_tp_qkv
20649 .as_ref()
20650 .ok_or("step35 token graph lost its TP state")?;
20651 for rank in 0..tp0.runtime.devices().len() {
20652 let engine = tp0
20653 .runtime
20654 .rank_engine(rank)
20655 .ok_or("step35 token graph lost a rank engine")?;
20656 let _main = engine.gpu.enter_main()?;
20657 engine.stream().synchronize()?;
20658 }
20659 }
20660
20661 {
20664 let _main = e.gpu.enter_main()?;
20665 e.set_u32_one(&mut state.token_d, token)?;
20666 e.set_i32_one(&mut state.pos_d, pos as i32)?;
20667 e.set_i32_one(&mut state.hist_idx, 0)?;
20668 }
20669 for _ in 0..k {
20670 graph.launch(e)?;
20671 }
20672 for il in 0..n_layers {
20674 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
20675 let transaction = tp_kv.begin_transaction()?;
20676 let fa = match &self.layers[il].mixer {
20677 Mixer::Full(fa) => fa,
20678 _ => return Err("step35 token graph expects full-attention layers".into()),
20679 };
20680 let tp = fa
20681 .step_tp_qkv
20682 .as_ref()
20683 .ok_or("step35 token graph lost its TP state")?;
20684 let empty: [CudaSlice<f32>; 0] = [];
20685 tp.runtime.append_tp_kv_transaction_inner(
20686 tp_kv,
20687 transaction,
20688 &empty,
20689 &empty,
20690 k,
20691 true,
20692 )?;
20693 tp.runtime
20694 .commit_tp_kv_transaction_external(tp_kv, transaction, k)?;
20695 if let Some(local) = cache.kv[il].as_mut() {
20696 local.len = pos + k;
20697 let _main = e.gpu.enter_main()?;
20698 e.set_i32_one(&mut local.len_d, (pos + k) as i32)?;
20699 }
20700 }
20701 cache.pos = pos + k;
20702 let (hist, logits) = {
20703 let _main = e.gpu.enter_main()?;
20704 e.stream().synchronize()?;
20705 (e.dtoh_u32(&state.token_hist)?, e.dtoh(&state.logits_stage)?)
20706 };
20707 Ok(Some((hist[..k].to_vec(), logits)))
20708 }
20709}
20710
20711impl HybridModel {
20712 #[allow(clippy::too_many_arguments)]
20717 fn step35_token_graph_build(
20718 &self,
20719 e: &Engine,
20720 cache: &mut Cache,
20721 state: &mut Step35TokenGraphState,
20722 bucket_max: usize,
20723 ) -> Result<(), Box<dyn std::error::Error>> {
20724 use cudarc::driver::DevicePtr;
20725 let n_embd = self.cfg.n_embd as usize;
20726 let eps = self.cfg.rms_eps;
20727 let n_layers = self.layers.len();
20728 let started = std::time::Instant::now();
20729 if !crate::router_kernel_on() {
20730 return Err(
20731 "step35 token graph requires the router kernel (MEMRA_ROUTER_KERNEL=0)".into(),
20732 );
20733 }
20734 if !Engine::bf16_mmv_on() || n_embd % 8 != 0 {
20735 return Err("step35 token graph requires MEMRA_BF16_MMV bf16-resident matvecs".into());
20736 }
20737
20738 let embd_gpu = self
20740 .embd_gpu_try(e)
20741 .ok_or("step35 token graph could not upload the device embed table")?;
20742 let embd_qtype = match self.embd.ggml_type {
20743 memra_gguf::GgmlType::BF16 => crate::QT_BF16,
20744 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
20745 other => return Err(format!("token graph embed dtype {other:?} unhandled").into()),
20746 };
20747 let embd_row_bytes = self.embd.raw.len() / self.cfg.n_vocab as usize;
20748
20749 let (p_mixed, p_kshadow, p_vshadow) = {
20751 let _main = e.gpu.enter_main()?;
20752 let stream = e.stream();
20753 let (a, _g) = state.mixed_stage.device_ptr(&stream);
20754 let (b, _g) = state.k_shadow_stage.device_ptr(&stream);
20755 let (c, _g) = state.v_shadow_stage.device_ptr(&stream);
20756 (a as u64, b as u64, c as u64)
20757 };
20758
20759 crate::tp::token_graph_build_begin()?;
20760 let mut group_id: u32 = 0;
20761 for il in 0..n_layers {
20762 let layer = &self.layers[il];
20763 let fa = match &layer.mixer {
20764 Mixer::Full(fa) => fa,
20765 _ => return Err("step35 token graph expects full-attention layers".into()),
20766 };
20767 let tp = fa
20768 .step_tp_qkv
20769 .as_ref()
20770 .ok_or("step35 token graph lost its TP state")?;
20771 let attention = tp
20772 .attention
20773 .as_ref()
20774 .ok_or("step35 token graph lost its attention aux")?;
20775 let geometry = self.step35_geom(il);
20776 let window = geometry.window.map(|w| w as usize);
20777 let head_dim = geometry.head_dim_k as usize;
20778 let heads = geometry.n_head as usize;
20779 let kv_heads = geometry.n_head_kv as usize;
20780 let ranks = tp.runtime.devices().len();
20781 let local_heads = heads / ranks;
20782 let local_kv_heads = kv_heads / ranks;
20783 let layer_bucket = window.map(|w| bucket_max.min(w)).unwrap_or(bucket_max);
20784 let use_gate_shards =
20785 attention.gate_shards.is_some() || attention.gate_shards_bf16.is_some();
20786 if !use_gate_shards {
20787 return Err("step35 token graph requires the fused gate shards".into());
20788 }
20789
20790 let ws_index = tp
20791 .runtime
20792 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
20793 let ws_mutex = tp.runtime.decode_v2_workspace();
20794 let mut ws_guard = ws_mutex
20795 .lock()
20796 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
20797 let ws = ws_guard
20798 .get_mut(ws_index)
20799 .ok_or("step TP decode v2 workspace missing after ensure")?;
20800 tp.runtime
20801 .decode_v2_arm_token_mirrors(ws, p_mixed, (p_kshadow, p_vshadow))?;
20802 let mut rope_freqs = Vec::with_capacity(ranks);
20803 for rank in 0..ranks {
20804 let engine = tp
20805 .runtime
20806 .rank_engine(rank)
20807 .ok_or("step35 token graph lost a rank engine")?;
20808 rope_freqs.push(if geometry.rope_factors {
20809 self.step35_aux
20810 .as_ref()
20811 .and_then(|aux| aux.rope_freqs(engine))
20812 } else {
20813 None
20814 });
20815 }
20816 let gate_shards_arg = if let Some(shards) = attention.gate_shards.as_deref() {
20817 Some(crate::tp::StepTpGateShards::F32(shards))
20818 } else {
20819 attention
20820 .gate_shards_bf16
20821 .as_deref()
20822 .map(crate::tp::StepTpGateShards::Bf16)
20823 };
20824
20825 let decode_input = attention
20827 .decode_input
20828 .as_ref()
20829 .ok_or("step35 token graph requires the replicated decode input")?;
20830 let mut decode_input = decode_input
20831 .lock()
20832 .map_err(|_| "replicated decode input lock is poisoned")?;
20833 if ws.h_stage.is_none() {
20835 return Err(
20836 "step35 token graph requires the stage flow armed (run eager dcw first)".into(),
20837 );
20838 }
20839 {
20840 let state_x = &mut state.x;
20841 let token_d = &state.token_d;
20842 let pos_d = &state.pos_d;
20843 crate::tp::graph_section(e, None, || {
20844 let _main = e.gpu.enter_main()?;
20845 if il == 0 {
20846 e.embed_gather_device_into(
20847 embd_gpu,
20848 token_d,
20849 state_x,
20850 n_embd,
20851 embd_qtype,
20852 embd_row_bytes,
20853 )?;
20854 }
20855 {
20856 let h_stage = ws.h_stage.as_mut().expect("stage armed checked above");
20857 e.rms_norm(
20858 state_x,
20859 layer.attn_norm.float_data(),
20860 h_stage,
20861 n_embd,
20862 1,
20863 eps,
20864 )?;
20865 }
20866 {
20867 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
20868 let mut dst = pos_stage.slice_mut(0..1);
20869 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
20870 }
20871 Ok(())
20872 })?;
20873 }
20874
20875 group_id += 1;
20877 for rank in 0..ranks {
20878 let engine = tp
20879 .runtime
20880 .rank_engine(rank)
20881 .ok_or("step35 token graph lost a rank engine")?;
20882 {
20883 let ceiling = window
20888 .map(|w| cache.max_ctx.min(w))
20889 .unwrap_or(cache.max_ctx);
20890 let _main = engine.gpu.enter_main()?;
20891 engine.fa_dcw_pool_ensure(
20892 head_dim,
20893 local_heads,
20894 local_kv_heads,
20895 ceiling.min(2048),
20896 )?;
20897 engine.fa_dcw_pool_ensure(head_dim, local_heads, local_kv_heads, ceiling)?;
20898 engine.fa_dcw_pool_ensure(
20899 head_dim,
20900 local_heads,
20901 local_kv_heads,
20902 layer_bucket,
20903 )?;
20904 }
20905 let runtime = &tp.runtime;
20906 let q_norm = &attention.q_norm;
20907 let k_norm = &attention.k_norm;
20908 let gate_ref = gate_shards_arg.as_ref();
20909 crate::tp::graph_section(engine, Some(group_id), || {
20910 runtime.decode_v2_input_qkv_rank(
20911 ws,
20912 &state.pos_d,
20913 &mut decode_input,
20914 &tp.q,
20915 &tp.k,
20916 &tp.v,
20917 q_norm,
20918 k_norm,
20919 head_dim,
20920 geometry.n_rot as usize,
20921 geometry.rope_base,
20922 &rope_freqs,
20923 eps,
20924 gate_ref,
20925 true,
20926 false,
20927 rank,
20928 None,
20929 )?;
20930 let distributed = cache.tp_kv[il]
20933 .as_mut()
20934 .ok_or("step35 token graph lost a TP cache")?;
20935 let (kv_dim_k, kv_dim_v) = (distributed.kv_dim_k(), distributed.kv_dim_v());
20936 let (ktb, vtb) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
20937 let capacity = distributed.physical_capacity();
20938 {
20939 let rank_cache = distributed
20940 .rank_mut(rank)
20941 .ok_or("step35 token graph lost a rank cache")?;
20942 let (k_plane, v_plane, len_d, base_d) =
20943 rank_cache.planes_and_counters_mut();
20944 engine.append_kv_quantized_dcw(
20945 &ws.k[rank],
20946 &ws.v_raw[rank],
20947 k_plane,
20948 v_plane,
20949 len_d,
20950 base_d,
20951 kv_dim_k,
20952 kv_dim_v,
20953 ktb,
20954 vtb,
20955 )?;
20956 }
20957 {
20958 let rank_cache = distributed
20959 .rank_mut(rank)
20960 .ok_or("step35 token graph lost a rank cache")?;
20961 engine.inc_i32(rank_cache.len_d_mut())?;
20962 }
20963 let rank_cache = distributed
20964 .rank(rank)
20965 .ok_or("step35 token graph lost a rank cache")?;
20966 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * ktb);
20967 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * vtb);
20968 engine.fa_decode_dcw(
20973 &ws.q[rank],
20974 &k_ring,
20975 &v_ring,
20976 &mut ws.attn_out[rank],
20977 head_dim,
20978 local_heads,
20979 local_kv_heads,
20980 rank_cache.len_d(),
20981 rank_cache.base_d(),
20982 window.unwrap_or(0),
20983 layer_bucket,
20984 geometry.attention_scale(),
20985 ktb,
20986 vtb,
20987 None,
20988 )?;
20989 engine.attn_head_gate(
20990 &ws.attn_out[rank],
20991 &ws.gate[rank],
20992 &mut ws.gated[rank],
20993 None,
20994 head_dim,
20995 local_heads,
20996 1,
20997 )?;
20998 runtime.decode_v2_finish_rank_partial(ws, &tp.o, true, rank)?;
20999 Ok(())
21000 })?;
21001 }
21002
21003 {
21005 let root = tp
21006 .runtime
21007 .rank_engine(0)
21008 .ok_or("step35 token graph lost the root engine")?;
21009 let runtime = &tp.runtime;
21010 crate::tp::graph_section(root, None, || runtime.decode_v2_finish_root_fused(ws))?;
21011 }
21012 drop(ws_guard);
21013 drop(decode_input);
21014
21015 let probe_layer: Option<usize> = std::env::var("MEMRA_TG_PROBE_LAYER")
21016 .ok()
21017 .and_then(|v| v.parse().ok());
21018 if probe_layer == Some(il) {
21019 let Step35TokenGraphState {
21020 mixed_stage,
21021 probe_mixed,
21022 ..
21023 } = &mut *state;
21024 crate::tp::graph_section(e, None, || {
21025 let _main = e.gpu.enter_main()?;
21026 let mut dst = probe_mixed.slice_mut(0..n_embd);
21027 e.stream()
21028 .memcpy_dtod(&mixed_stage.slice(0..n_embd), &mut dst)?;
21029 Ok(())
21030 })?;
21031 }
21032
21033 match &layer.ffn {
21035 crate::hybrid::Ffn::Dense {
21036 ffn_gate,
21037 ffn_up,
21038 ffn_down,
21039 } => {
21040 let n_ff = ffn_gate.out_features();
21041 let lim = self.cfg.clamp_shexp_at(il as u32);
21042 if lim.is_some() {
21046 return Err("step35 token graph dense FFN with clamp unsupported".into());
21047 }
21048 let (wg_d, wu_d, wd_d) = match (ffn_gate, ffn_up, ffn_down) {
21049 (
21050 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
21051 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
21052 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
21053 ) => (wg, wu, wd),
21054 _ => {
21055 return Err(
21056 "step35 token graph dense FFN requires bf16-resident weights"
21057 .into(),
21058 );
21059 }
21060 };
21061 crate::tp::graph_section(e, None, || {
21062 let _main = e.gpu.enter_main()?;
21063 let Step35TokenGraphState {
21064 x,
21065 x1,
21066 mixed_stage,
21067 dense_z,
21068 dense_gate,
21069 dense_up,
21070 dense_act,
21071 sh_stage,
21072 ..
21073 } = &mut *state;
21074 e.add_rms_norm(
21075 x,
21076 mixed_stage,
21077 layer.post_attn_norm.float_data(),
21078 x1,
21079 dense_z,
21080 n_embd,
21081 1,
21082 eps,
21083 )?;
21084 e.matvec_bf16_into(wg_d, dense_z, dense_gate, n_embd, n_ff)?;
21088 e.matvec_bf16_into(wu_d, dense_z, dense_up, n_embd, n_ff)?;
21089 Self::ffn_act_lim(
21090 e, &self.cfg, dense_gate, dense_up, 1.0, 1.0, lim, dense_act, n_ff,
21091 )?;
21092 e.matvec_bf16_into(wd_d, dense_act, sh_stage, n_ff, n_embd)?;
21093 e.add(x1, sh_stage, x, n_embd)?;
21094 Ok(())
21095 })?;
21096 }
21097 crate::hybrid::Ffn::Moe(m) => {
21098 let moe = self
21099 .cfg
21100 .moe
21101 .as_ref()
21102 .ok_or("step35 token graph needs moe cfg")?;
21103 let n_expert = moe.expert_count as usize;
21104 let n_used = moe.expert_used_count as usize;
21105 let sigmoid = self
21106 .cfg
21107 .sigmoid_router()
21108 .ok_or("step35 token graph needs the sigmoid router")?;
21109 let step_tp = m
21110 .step_tp
21111 .as_ref()
21112 .ok_or("step35 token graph needs TP experts")?;
21113 let bank = match &step_tp.experts {
21114 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => bank,
21115 _ => return Err("step35 token graph needs the NVFP4 bank".into()),
21116 };
21117 let routes_ws_mutex = bank.device_workspace_handle();
21118 let mut routes_guard = routes_ws_mutex
21119 .lock()
21120 .map_err(|_| "routes workspace lock is poisoned")?;
21121 let routes_ws = routes_guard
21122 .as_mut()
21123 .ok_or("step35 token graph requires the routes workspace warmed")?;
21124 routes_ws.arm_stages(e, bank.input_width, n_used)?;
21125 step_tp.runtime.routes_arm_raw(bank, routes_ws)?;
21126 let p_z = {
21127 let root = step_tp
21128 .runtime
21129 .rank_engine(0)
21130 .ok_or("routes root engine missing")?;
21131 let _main = root.gpu.enter_main()?;
21132 let stream = root.stream();
21133 let in_stage = routes_ws
21134 .in_stage_handle()
21135 .ok_or("routes in stage not armed")?;
21136 let (a, _g) = in_stage.device_ptr(&stream);
21137 a as u64
21138 };
21139 let local_out = bank.expert_width / ranks;
21140
21141 crate::tp::graph_section(e, None, || {
21143 let _main = e.gpu.enter_main()?;
21144 {
21145 let in_stage = routes_ws
21146 .in_stage_mut()
21147 .ok_or("routes in stage not armed")?;
21148 let Step35TokenGraphState {
21149 x, x1, mixed_stage, ..
21150 } = &mut *state;
21151 e.add_rms_norm(
21152 x,
21153 mixed_stage,
21154 layer.post_attn_norm.float_data(),
21155 x1,
21156 in_stage,
21157 n_embd,
21158 1,
21159 eps,
21160 )?;
21161 }
21162 {
21163 let z_ref = routes_ws
21164 .in_stage_handle()
21165 .ok_or("routes in stage not armed")?;
21166 e.router_gemv_into(
21167 m.gate_inp.float_data(),
21168 z_ref,
21169 &mut state.router_logits,
21170 n_embd,
21171 n_expert,
21172 1,
21173 )?;
21174 }
21175 let (sel_e, w_e) = routes_ws
21176 .dev_route_e_mut()
21177 .ok_or("routes staging not armed")?;
21178 e.moe_router_sigmoid_topk_into(
21179 &state.router_logits,
21180 1,
21181 n_expert,
21182 n_used,
21183 m.active_count(),
21184 &m.exp_probs_b_dev,
21185 &m.active_experts_dev,
21186 sigmoid.0,
21187 sigmoid.1,
21188 sel_e,
21189 w_e,
21190 )?;
21191 Ok(())
21192 })?;
21193
21194 group_id += 1;
21196 for rank in 0..ranks {
21197 let engine = step_tp
21198 .runtime
21199 .rank_engine(rank)
21200 .ok_or("routes rank engine missing")?;
21201 let runtime = &step_tp.runtime;
21202 crate::tp::graph_section(engine, Some(group_id), || {
21203 runtime.routes_rank_section(
21204 bank,
21205 routes_ws,
21206 p_z,
21207 local_out,
21208 n_used,
21209 step_tp.activation_limit,
21210 rank,
21211 )
21212 })?;
21213 }
21214
21215 {
21217 let root = step_tp
21218 .runtime
21219 .rank_engine(0)
21220 .ok_or("routes root engine missing")?;
21221 let runtime = &step_tp.runtime;
21222 crate::tp::graph_section(root, None, || {
21223 runtime.routes_root_section(bank, routes_ws)
21224 })?;
21225 }
21226
21227 let lim_sh = self.cfg.clamp_shexp_at(il as u32);
21231 let (wg_sh, wu_sh, wd_sh) = match (&m.gate_shexp, &m.up_shexp, &m.down_shexp) {
21232 (
21233 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
21234 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
21235 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
21236 ) => (wg, wu, wd),
21237 _ => {
21238 return Err(
21239 "step35 token graph shexp requires bf16-resident weights".into()
21240 );
21241 }
21242 };
21243 let n_ff_sh = m
21244 .gate_shexp
21245 .as_ref()
21246 .expect("matched Some above")
21247 .out_features();
21248 let gate_inp_shexp = m.gate_inp_shexp.as_ref();
21251 crate::tp::graph_section(e, None, || {
21252 let _main = e.gpu.enter_main()?;
21253 let (z_ref, out_stage) = routes_ws
21254 .in_and_out_stages_mut()
21255 .ok_or("routes stages not armed")?;
21256 let Step35TokenGraphState {
21257 x,
21258 x1,
21259 sh_stage,
21260 shexp_gate,
21261 shexp_up,
21262 shexp_act,
21263 gate_sig,
21264 ..
21265 } = &mut *state;
21266 e.matvec_bf16_dual_into(
21267 wg_sh, wu_sh, z_ref, shexp_gate, shexp_up, n_embd, n_ff_sh,
21268 )?;
21269 Self::ffn_act_lim(
21270 e, &self.cfg, shexp_gate, shexp_up, 1.0, 1.0, lim_sh, shexp_act,
21271 n_ff_sh,
21272 )?;
21273 e.matvec_bf16_into(wd_sh, shexp_act, sh_stage, n_ff_sh, n_embd)?;
21274 if let Some(gate_w) = gate_inp_shexp {
21275 e.sigmoid_dot_rows_into(
21276 z_ref,
21277 gate_w.float_data(),
21278 gate_sig,
21279 n_embd,
21280 1,
21281 )?;
21282 }
21283 e.add_scaled_rows(sh_stage, gate_sig, out_stage, n_embd, 1)?;
21284 e.add(x1, out_stage, x, n_embd)?;
21285 Ok(())
21286 })?;
21287 }
21288 }
21289 if probe_layer == Some(il) {
21290 let Step35TokenGraphState { x, probe_x, .. } = &mut *state;
21291 crate::tp::graph_section(e, None, || {
21292 let _main = e.gpu.enter_main()?;
21293 let mut dst = probe_x.slice_mut(0..n_embd);
21294 e.stream().memcpy_dtod(&x.slice(0..n_embd), &mut dst)?;
21295 Ok(())
21296 })?;
21297 }
21298 }
21299
21300 let head = match &self.output {
21302 crate::model::GpuTensor::FloatBf16 { data, .. } => data,
21303 _ => return Err("step35 token graph head requires the bf16-resident output".into()),
21304 };
21305 crate::tp::graph_section(e, None, || {
21306 let _main = e.gpu.enter_main()?;
21307 let Step35TokenGraphState {
21308 x,
21309 hn,
21310 logits_stage,
21311 token_d,
21312 pos_d,
21313 token_hist,
21314 hist_idx,
21315 ..
21316 } = &mut *state;
21317 e.rms_norm(x, self.output_norm.float_data(), hn, n_embd, 1, eps)?;
21318 e.matvec_bf16_into(head, hn, logits_stage, n_embd, self.cfg.n_vocab as usize)?;
21319 e.argmax_token_device_into(logits_stage, token_d, self.cfg.n_vocab as usize)?;
21325 e.u32_hist_append(token_d, token_hist, hist_idx)?;
21326 e.inc_i32(pos_d)?;
21327 Ok(())
21328 })?;
21329
21330 let graph = crate::tp::token_graph_build_finish()?;
21331 state.graphs.push((bucket_max, graph));
21332 eprintln!(
21333 "[step35-token-graph] built bucket={bucket_max} layers={n_layers} \
21334 build_ms={:.0} performance_claim=false",
21335 started.elapsed().as_secs_f64() * 1e3
21336 );
21337 Ok(())
21338 }
21339}