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
546const MOE_DEV_MAX_T: usize = 16;
555const PRIME_PIPE_MICROBATCHES: usize = 8;
556const PRIME_PIPE_MIN_CHUNK: usize = 128;
557const PRIME_PIPE_EDGE_MIN_CHUNK: usize = 64;
558const PRIME_PIPE_LINEAR_WORK: usize = 8;
559
560fn prime_pp2_auto_geometry(n_layers: usize) -> bool {
561 crate::pp::prime_pp_on()
562 && !crate::pp::pp2_streams_off()
563 && crate::pp::pp_cuts(n_layers).is_some_and(|cuts| cuts.len() == 3)
564}
565
566pub fn prime_chunk_tokens(t: usize, n_layers: usize) -> usize {
570 if let Ok(value) = std::env::var("MEMRA_PRIME_CHUNK") {
571 let parsed = value
572 .parse::<usize>()
573 .unwrap_or(crate::cache::PRIME_CHUNK_MAX_TOKENS);
574 return if crate::cache::swa_ring_on() {
575 if parsed == 0 {
576 crate::cache::PRIME_CHUNK_MAX_TOKENS
577 } else {
578 parsed.min(crate::cache::PRIME_CHUNK_MAX_TOKENS)
579 }
580 } else {
581 parsed
582 };
583 }
584 let chunk = crate::cache::PRIME_CHUNK_MAX_TOKENS;
585 if prime_pp2_auto_geometry(n_layers) && t >= 2 * PRIME_PIPE_MIN_CHUNK {
586 chunk.min(
587 t.div_ceil(PRIME_PIPE_MICROBATCHES)
588 .max(PRIME_PIPE_MIN_CHUNK),
589 )
590 } else {
591 chunk
592 }
593}
594
595fn fixed_prime_chunk_ranges(t: usize, chunk: usize) -> Vec<(usize, usize)> {
596 fixed_prime_chunk_ranges_for_ring(t, chunk, crate::cache::swa_ring_on())
597}
598
599fn fixed_prime_chunk_ranges_for_ring(t: usize, chunk: usize, ring_on: bool) -> Vec<(usize, usize)> {
600 if chunk == 0 || t <= chunk {
601 return vec![(0, t)];
602 }
603 let mut ranges = Vec::with_capacity(t.div_ceil(chunk));
604 let mut start = 0usize;
605 while start < t {
606 let mut end = (start + chunk).min(t);
607 if t - end > 0 && t - end < PRIME_MIN_T {
608 if ring_on {
609 let shifted = t - PRIME_MIN_T;
610 end = if shifted > start { shifted } else { t };
611 } else {
612 end = t;
613 }
614 }
615 ranges.push((start, end));
616 start = end;
617 }
618 ranges
619}
620
621fn prime_chunk_work(prefix: usize, total: usize) -> u128 {
622 let prefix = prefix as u128;
623 prefix * (prefix + (PRIME_PIPE_LINEAR_WORK as u128) * (total as u128))
624}
625
626fn dynamic_prime_chunk_ranges(
627 t: usize,
628 fixed_chunk: usize,
629 fixed: &[(usize, usize)],
630) -> Vec<(usize, usize)> {
631 let n = fixed.len();
632 if n < 3 {
633 return fixed.to_vec();
634 }
635
636 let max_first = t - (n - 1) * PRIME_MIN_T;
637 let first = fixed_chunk
638 .div_ceil(2)
639 .max(PRIME_PIPE_EDGE_MIN_CHUNK)
640 .min(max_first);
641 let mut ranges = Vec::with_capacity(n);
642 ranges.push((0, first));
643
644 let first_work = prime_chunk_work(first, t);
645 let work_span = prime_chunk_work(t, t) - first_work;
646 let denominator = (n - 1) as u128;
647 let mut previous = first;
648 for boundary in 1..n - 1 {
649 let target = first_work * denominator + work_span * (boundary as u128);
650 let remaining = n - 1 - boundary;
651 let mut low = previous + PRIME_MIN_T;
652 let mut high = t - remaining * PRIME_MIN_T;
653 while low < high {
654 let mid = low + (high - low) / 2;
655 if prime_chunk_work(mid, t) * denominator >= target {
656 high = mid;
657 } else {
658 low = mid + 1;
659 }
660 }
661 ranges.push((previous, low));
662 previous = low;
663 }
664 ranges.push((previous, t));
665 ranges
666}
667
668pub fn prime_chunk_ranges(t: usize, n_layers: usize, gdn_grid: bool) -> Vec<(usize, usize)> {
678 let explicit_chunk = std::env::var_os("MEMRA_PRIME_CHUNK").is_some();
679 let chunk = prime_chunk_tokens(t, n_layers);
680 let fixed = fixed_prime_chunk_ranges(t, chunk);
681 let dynamic = match std::env::var("MEMRA_PRIME_CHUNK_SCHED") {
682 Ok(value) => value == "dynamic",
683 Err(_) => true,
684 };
685 if explicit_chunk {
686 return fixed;
687 }
688 let ranges = if !dynamic || !prime_pp2_auto_geometry(n_layers) {
689 fixed
690 } else {
691 dynamic_prime_chunk_ranges(t, chunk, &fixed)
692 };
693 if gdn_grid && std::env::var("MEMRA_PRIME_GRID_ALIGN").as_deref() != Ok("0") {
697 align_prime_ranges_to_gdn(&ranges, t, Engine::gdn_chunk_size())
698 } else {
699 ranges
700 }
701}
702
703pub fn align_prime_ranges_to_gdn(
723 ranges: &[(usize, usize)],
724 t: usize,
725 c: usize,
726) -> Vec<(usize, usize)> {
727 if c == 0 || ranges.len() < 2 {
728 return ranges.to_vec();
729 }
730 let mut out: Vec<(usize, usize)> = Vec::with_capacity(ranges.len());
731 let mut start = 0usize;
732 for (i, &(_, end)) in ranges.iter().enumerate() {
733 let e = if i + 1 == ranges.len() {
734 t
735 } else {
736 end / c * c
737 };
738 if e > start {
739 out.push((start, e));
740 start = e;
741 } }
743 debug_assert_eq!(out.last().map(|&(_, e)| e), Some(t));
744 out
745}
746
747struct HeadSplit {
748 pin: u64,
749 w1: CudaSlice<u8>,
750 hn1: CudaSlice<f32>,
751 y1: CudaSlice<f32>,
752 logits_e: CudaSlice<f32>,
753 ev_hn: cudarc::driver::CudaEvent,
754 ev_done: cudarc::driver::CudaEvent,
755 raw_hn1: u64,
756 raw_y1: u64,
757 raw_logits_hi: u64,
758 samp: Option<SampScratch>,
763}
764
765struct SampScratch {
766 pb: CudaSlice<f32>,
767 th: CudaSlice<f32>,
768 z: CudaSlice<f32>,
769 mx: CudaSlice<f32>,
770 rows: CudaSlice<i32>,
771}
772static HEAD_SPLIT_WS: std::sync::Mutex<Option<HeadSplit>> = std::sync::Mutex::new(None);
774
775#[allow(clippy::type_complexity)]
779static DEV1_ROUTER_REPS: std::sync::Mutex<
780 Option<(
781 std::collections::HashMap<u16, (CudaSlice<f32>, CudaSlice<f32>, CudaSlice<u8>)>,
782 Option<CudaSlice<f32>>,
783 )>,
784> = std::sync::Mutex::new(None);
785
786#[allow(clippy::type_complexity)]
790static SHEXP_D1_REPS: std::sync::Mutex<
791 Option<std::collections::HashMap<u16, (CudaSlice<u8>, CudaSlice<u8>, CudaSlice<u8>)>>,
792> = std::sync::Mutex::new(None);
793#[allow(clippy::type_complexity)]
794static SHEXP_D1_WS: std::sync::Mutex<
795 Option<(
796 (usize, usize),
797 CudaSlice<f32>,
798 CudaSlice<f32>,
799 CudaSlice<f32>,
800 cudarc::driver::CudaEvent,
801 cudarc::driver::CudaEvent,
802 )>,
803> = std::sync::Mutex::new(None);
804
805static SHEXP_OV_WS: std::sync::Mutex<
807 Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>,
808> = std::sync::Mutex::new(None);
809
810impl HybridModel {
811 pub fn gdn_prime_grid_on(&self) -> bool {
817 Engine::gdn_chunked_enabled()
818 && self
819 .layers
820 .iter()
821 .any(|l| matches!(l.mixer, crate::hybrid::Mixer::Linear(_)))
822 }
823
824 fn step35_tp_device_resident(e: &Engine, tp: &crate::hybrid::StepTpQkv) -> bool {
829 tp.runtime.native_p2p() && tp.runtime.root_shares_ctx(e)
830 }
831
832 fn step35_tp_qkv(
833 &self,
834 e: &Engine,
835 fa: &FullAttnLayer,
836 h: &CudaSlice<f32>,
837 t: usize,
838 ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
839 let Some(tp) = fa.step_tp_qkv.as_ref() else {
840 return Ok(None);
841 };
842 let values = active_matrix_values(
843 h.len(),
844 t,
845 self.cfg.n_embd as usize,
846 "Step TP QKV activation",
847 )?;
848 if Self::step35_tp_device_resident(e, tp) {
857 e.stream().synchronize()?;
860 let q = tp
861 .runtime
862 .bf16_column_parallel_resident_native_device(&tp.q, h, t)?;
863 let k = tp
864 .runtime
865 .bf16_column_parallel_resident_native_device(&tp.k, h, t)?;
866 let v = tp
867 .runtime
868 .bf16_column_parallel_resident_native_device(&tp.v, h, t)?;
869 Self::step35_tp_log_once(tp, "qkv", "device-resident");
870 return Ok(Some(vec![q, k, v]));
871 }
872 let host = e.dtoh_view(&h.slice(0..values))?;
873 let q = if tp.runtime.native_p2p() {
874 tp.runtime
875 .bf16_column_parallel_resident_native(&tp.q, &host, t)?
876 } else {
877 tp.runtime
878 .bf16_column_parallel_resident(&tp.q, &host, t)?
879 .gathered
880 };
881 let k = if tp.runtime.native_p2p() {
882 tp.runtime
883 .bf16_column_parallel_resident_native(&tp.k, &host, t)?
884 } else {
885 tp.runtime
886 .bf16_column_parallel_resident(&tp.k, &host, t)?
887 .gathered
888 };
889 let v = if tp.runtime.native_p2p() {
890 tp.runtime
891 .bf16_column_parallel_resident_native(&tp.v, &host, t)?
892 } else {
893 tp.runtime
894 .bf16_column_parallel_resident(&tp.v, &host, t)?
895 .gathered
896 };
897 Self::step35_tp_log_once(tp, "qkv", "host-canonical");
898 Ok(Some(vec![e.htod(&q)?, e.htod(&k)?, e.htod(&v)?]))
899 }
900
901 fn step35_tp_log_once(tp: &crate::hybrid::StepTpQkv, proj: &str, activation: &'static str) {
905 use std::sync::atomic::{AtomicBool, Ordering};
906 static LOGGED: [AtomicBool; 4] = [
907 AtomicBool::new(false),
908 AtomicBool::new(false),
909 AtomicBool::new(false),
910 AtomicBool::new(false),
911 ];
912 let idx = 2 * usize::from(proj == "o") + usize::from(activation == "device-resident");
913 if LOGGED[idx].swap(true, Ordering::Relaxed) {
914 return;
915 }
916 eprintln!(
917 "[step-tp-{proj}] execute layer={} devices={:?} projections={proj} \
918 tensor_parallel=true attention_local=true kv_local=true transport={} \
919 native_p2p={} bulk_p2p={} activation={activation} \
920 output={} performance_claim=false (logged once per transport)",
921 tp.layer,
922 tp.devices,
923 tp.runtime.transport_label(),
924 tp.runtime.native_p2p(),
925 tp.runtime.bulk_p2p(),
926 if activation == "device-resident" {
927 "root-resident"
928 } else {
929 "root-readback"
930 },
931 );
932 }
933
934 fn step35_tp_o(
935 &self,
936 e: &Engine,
937 fa: &FullAttnLayer,
938 activation: &CudaSlice<f32>,
939 tokens: usize,
940 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
941 let Some(tp) = fa.step_tp_qkv.as_ref() else {
942 return Ok(None);
943 };
944 if Self::step35_tp_device_resident(e, tp) {
948 e.stream().synchronize()?; let output = tp
950 .runtime
951 .step_bf16_row_parallel_resident_native_device(&tp.o, activation, tokens)?;
952 Self::step35_tp_log_once(tp, "o", "device-resident");
953 return Ok(Some(output));
954 }
955 let host = e.dtoh(activation)?;
956 let output = if tp.runtime.native_p2p() {
957 tp.runtime
958 .step_bf16_row_parallel_resident_native(&tp.o, &host, tokens)?
959 } else {
960 tp.runtime
961 .step_bf16_row_parallel_resident(&tp.o, &host, tokens)?
962 };
963 Self::step35_tp_log_once(tp, "o", "host-canonical");
964 Ok(Some(e.htod(&output)?))
965 }
966
967 fn step35_o(
968 &self,
969 e: &Engine,
970 fa: &FullAttnLayer,
971 activation: &CudaSlice<f32>,
972 tokens: usize,
973 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
974 match self.step35_tp_o(e, fa, activation, tokens)? {
975 Some(output) => Ok(output),
976 None => e.matmul(&fa.wo, activation, tokens),
977 }
978 }
979
980 fn prime_trace_path() -> Option<&'static str> {
985 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
986 P.get_or_init(|| std::env::var("MEMRA_PRIME_TRACE").ok())
987 .as_deref()
988 }
989
990 fn prime_anatomy_on() -> bool {
996 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
997 *E.get_or_init(|| std::env::var("MEMRA_PRIME_ANATOMY").as_deref() == Ok("1"))
998 }
999
1000 fn prime_anatomy_slots() -> &'static [std::sync::atomic::AtomicU64; 5] {
1001 static S: [std::sync::atomic::AtomicU64; 5] = [
1002 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), ];
1008 &S
1009 }
1010
1011 pub fn forward(
1013 &self,
1014 e: &Engine,
1015 tokens: &[u32],
1016 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
1017 if self.is_gemma4_e4b() {
1018 return self.gemma4_e4b_forward(e, tokens, false);
1019 }
1020 if self.uses_gemma_program() {
1021 return self.gemma4_forward(e, tokens, false);
1022 }
1023 let cfg = &self.cfg;
1024 let n_embd = cfg.n_embd as usize;
1025 let t = tokens.len();
1026 let eps = cfg.rms_eps;
1027 let pos: Vec<i32> = (0..t as i32).collect();
1028 let pos_d = e.htod_i32(&pos)?;
1029
1030 let mut x = self.embed(e, tokens)?; for (il, layer) in self.layers.iter().enumerate() {
1033 let mut h = e.uninit(t * n_embd)?;
1035 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1036
1037 let mixed = match &layer.mixer {
1038 Mixer::Full(fa) => self.full_attn(e, fa, &h, &pos_d, t, il)?,
1039 Mixer::Linear(la) => self.linear_attn(e, la, &h, t)?,
1040 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1041 };
1042
1043 let mut x1 = e.uninit(t * n_embd)?;
1045 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1046
1047 let mut z = e.uninit(t * n_embd)?;
1049 e.rms_norm(
1050 &x1,
1051 layer.post_attn_norm.float_data(),
1052 &mut z,
1053 n_embd,
1054 t,
1055 eps,
1056 )?;
1057 let ffn_out = match &layer.ffn {
1058 crate::hybrid::Ffn::Dense {
1059 ffn_gate,
1060 ffn_up,
1061 ffn_down,
1062 } => {
1063 let n_ff = ffn_gate.out_features();
1064 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1065 let up = g2.pop().unwrap();
1066 let gate = g2.pop().unwrap();
1067 let mut act = e.uninit(t * n_ff)?;
1068 Self::ffn_act_lim(
1073 e,
1074 &self.cfg,
1075 &gate,
1076 &up,
1077 1.0,
1078 1.0,
1079 self.cfg.clamp_shexp_at(il as u32),
1080 &mut act,
1081 t * n_ff,
1082 )?;
1083 e.matmul(ffn_down, &act, t)?
1084 }
1085 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
1086 };
1087 let mut x2 = e.uninit(t * n_embd)?;
1088 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1089 x = x2;
1090 }
1091
1092 let mut hn = e.uninit(t * n_embd)?;
1093 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1094 let logits = e.matmul(&self.output, &hn, t)?;
1095 Ok(e.dtoh(&logits)?)
1096 }
1097
1098 pub fn forward_last(
1104 &self,
1105 e: &Engine,
1106 tokens: &[u32],
1107 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
1108 if self.uses_gemma_program() {
1109 return self.gemma4_forward(e, tokens, true);
1110 }
1111 let cfg = &self.cfg;
1112 let n_embd = cfg.n_embd as usize;
1113 let t = tokens.len();
1114 let eps = cfg.rms_eps;
1115 let pos: Vec<i32> = (0..t as i32).collect();
1116 let pos_d = e.htod_i32(&pos)?;
1117
1118 let mut x = self.embed(e, tokens)?; let probe = std::env::var("MEMRA_LAYER_PROBE").is_ok();
1122 let anat = Self::prime_anatomy_on();
1123 let mut anat_last = if anat {
1124 e.stream().synchronize()?;
1125 Some(std::time::Instant::now())
1126 } else {
1127 None
1128 };
1129 macro_rules! anat_mark {
1130 ($slot:expr) => {
1131 if let Some(ts) = anat_last.as_mut() {
1132 e.stream().synchronize()?;
1133 Self::prime_anatomy_slots()[$slot].fetch_add(
1134 ts.elapsed().as_nanos() as u64,
1135 std::sync::atomic::Ordering::Relaxed,
1136 );
1137 *ts = std::time::Instant::now();
1138 }
1139 };
1140 }
1141 for (il, layer) in self.layers.iter().enumerate() {
1142 let mut h = e.uninit(t * n_embd)?;
1143 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1144 if probe {
1145 e.stream().synchronize()?;
1146 eprintln!("[probe] L{il} norm ok");
1147 }
1148 anat_mark!(4);
1149 let mixed = match &layer.mixer {
1150 Mixer::Full(fa) => {
1151 let y = self.full_attn(e, fa, &h, &pos_d, t, il)?;
1152 anat_mark!(0);
1153 y
1154 }
1155 Mixer::Linear(la) => {
1156 let y = self.linear_attn(e, la, &h, t)?;
1157 anat_mark!(1);
1158 y
1159 }
1160 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1161 };
1162 if probe {
1163 e.stream().synchronize()?;
1164 eprintln!("[probe] L{il} mixer ok");
1165 }
1166 let mut x1 = e.uninit(t * n_embd)?;
1167 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1168 let mut z = e.uninit(t * n_embd)?;
1169 e.rms_norm(
1170 &x1,
1171 layer.post_attn_norm.float_data(),
1172 &mut z,
1173 n_embd,
1174 t,
1175 eps,
1176 )?;
1177 anat_mark!(4);
1178 let ffn_out = match &layer.ffn {
1179 crate::hybrid::Ffn::Dense {
1180 ffn_gate,
1181 ffn_up,
1182 ffn_down,
1183 } => {
1184 let n_ff = ffn_gate.out_features();
1185 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1186 let up = g2.pop().unwrap();
1187 let gate = g2.pop().unwrap();
1188 let mut act = e.uninit(t * n_ff)?;
1189 Self::ffn_act_lim(
1191 e,
1192 &self.cfg,
1193 &gate,
1194 &up,
1195 1.0,
1196 1.0,
1197 self.cfg.clamp_shexp_at(il as u32),
1198 &mut act,
1199 t * n_ff,
1200 )?;
1201 let y = e.matmul(ffn_down, &act, t)?;
1202 anat_mark!(3);
1203 y
1204 }
1205 crate::hybrid::Ffn::Moe(m) => {
1206 let y = self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?;
1207 anat_mark!(2);
1208 y
1209 }
1210 };
1211 if probe {
1212 e.stream().synchronize()?;
1213 eprintln!("[probe] L{il} ffn ok");
1214 }
1215 let mut x2 = e.uninit(t * n_embd)?;
1216 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1217 x = x2;
1218 }
1219 if anat {
1220 let s = Self::prime_anatomy_slots();
1221 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
1222 eprintln!(
1223 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
1224 dense={:.1} norms_adds={:.1} (t={t}, forward_last)",
1225 ms(0),
1226 ms(1),
1227 ms(2),
1228 ms(3),
1229 ms(4)
1230 );
1231 }
1232 let mut hn = e.uninit(t * n_embd)?;
1234 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1235 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)?;
1238 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
1239 let logits = e.matmul(&self.output, &hlast, 1)?; Ok(e.dtoh(&logits)?)
1241 }
1242
1243 pub fn prime_cache(
1275 &self,
1276 e: &Engine,
1277 tokens: &[u32],
1278 cache: &mut Cache,
1279 queued_after: usize,
1280 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1281 self.prime_cache_overlaid(e, tokens, cache, queued_after, None)
1282 }
1283
1284 pub fn prime_cache_overlaid(
1290 &self,
1291 e: &Engine,
1292 tokens: &[u32],
1293 cache: &mut Cache,
1294 queued_after: usize,
1295 overlay: Option<&crate::vision::EmbedOverlay>,
1296 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1297 let n_embd = self.cfg.n_embd as usize;
1298 let t = tokens.len();
1299 if overlay.is_none() {
1303 if let Some(out) = self.step35_prime_trows(e, tokens, cache)? {
1304 return Ok(out);
1305 }
1306 }
1307 assert!(
1311 t >= PRIME_MIN_T,
1312 "prime_cache needs T >= {PRIME_MIN_T} (caller gates)"
1313 );
1314 assert!(
1315 cache.pos + t <= cache.max_ctx,
1316 "prime_cache: prompt exceeds cache max_ctx"
1317 );
1318
1319 if self.is_gemma4_e4b() || self.uses_gemma_program() {
1331 if self.is_gemma4_e4b() {
1332 if overlay.is_some() {
1333 return Err(
1334 "vision embedding overlay is unsupported on gemma4 E4B (PLE prime)".into(),
1335 );
1336 }
1337 return self.gemma4_e4b_prime(e, tokens, cache);
1338 }
1339 return self.gemma4_prime(e, tokens, cache, overlay);
1344 }
1345 let ranges = prime_chunk_ranges(t, self.layers.len(), self.gdn_prime_grid_on());
1346 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
1382 let seq_end = if legacy_calllocal {
1383 cache.pos + t
1384 } else {
1385 cache.pos + t + queued_after
1386 };
1387 if ranges.len() == 1 {
1388 return self.prime_chunk(e, tokens, cache, seq_end, 0, overlay);
1389 }
1390 if crate::pp::prime_pipe_on() && crate::pp::prime_pp_on() && !crate::pp::pp2_streams_off() {
1395 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()).filter(|f| f.len() == 3) {
1396 if overlay.is_some() {
1397 return Err(
1398 "vision embedding overlay + pipelined PP prime unsupported (v1); \
1399 run the serial prime (single device or MEMRA_PRIME_PIPE=0)"
1400 .into(),
1401 );
1402 }
1403 if crate::pp::pp_multi_stream_same_device() {
1404 return Err(
1405 "prime chunk pipeline refused with 2 stage streams on one device — \
1406 that concurrent-stream placement remains quarantined by the deferred \
1407 pp flake record. Use one device per stage or MEMRA_PRIME_PIPE=0 for \
1408 the serial split."
1409 .into(),
1410 );
1411 }
1412 return self.prime_cache_pp2_pipelined(e, tokens, cache, seq_end, &ranges, &fence);
1413 }
1414 }
1415 let mut hiddens = e.uninit(t * n_embd)?;
1416 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1417 for &(start, end) in &ranges {
1418 if let Some(taps) = cache.dflash_taps.as_mut() {
1420 taps.base = start;
1421 }
1422 let (l, hs, x) =
1423 self.prime_chunk(e, &tokens[start..end], cache, seq_end, start, overlay)?;
1424 e.copy_into(&mut hiddens, start * n_embd, &x, (end - start) * n_embd)?;
1425 last = Some((l, hs));
1426 }
1427 let (logits, h_seed) = last.unwrap();
1428 Ok((logits, h_seed, hiddens))
1429 }
1430
1431 fn prime_cache_pp2_pipelined(
1436 &self,
1437 e: &Engine,
1438 tokens: &[u32],
1439 cache: &mut Cache,
1440 seq_end: usize,
1441 ranges: &[(usize, usize)],
1442 fence: &[usize],
1443 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1444 debug_assert_eq!(fence.len(), 3);
1445 debug_assert!(ranges.len() >= 2);
1446 let rt = crate::pp::PpNRt::get(e)?;
1447 assert_eq!(
1448 rt.n_stages(),
1449 2,
1450 "prime pipeline requires exactly two PP stages"
1451 );
1452 let n_embd = self.cfg.n_embd as usize;
1453 let t = tokens.len();
1454 let initial_base = cache.pos;
1455 let caller_stream = e.stream();
1456
1457 rt.fence_stages_behind(&caller_stream)?;
1462 let max_payload = ranges.iter().map(|(s, e)| (e - s) * n_embd).max().unwrap();
1463 rt.prepare_overlap_slots(0, max_payload)?;
1464
1465 let mut hiddens = e.uninit(t * n_embd)?;
1466 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1467 let mut stage_caches = PrimeCacheStages::new(cache, fence[1]);
1468 let (cache0, cache1) = stage_caches.parts();
1469 let (first_start, first_end) = ranges[0];
1470 let mut slot = self.prime_pp2_stage0_enqueue(
1471 e,
1472 rt,
1473 &tokens[first_start..first_end],
1474 cache0,
1475 seq_end,
1476 fence,
1477 initial_base + first_start,
1478 true,
1479 )?;
1480 cache0.pos = initial_base + first_end;
1481
1482 for (i, &(start, end)) in ranges.iter().enumerate() {
1483 let base = initial_base + start;
1484 debug_assert_eq!(
1485 cache1.pos, base,
1486 "stage 1 must drain chunks in original position order"
1487 );
1488 let (out, next_slot) = if let Some(&(next_start, next_end)) = ranges.get(i + 1) {
1489 let next_base = initial_base + next_start;
1490 debug_assert_eq!(
1491 cache0.pos, next_base,
1492 "stage 0 must issue chunks in original position order"
1493 );
1494 let cache0_stage = &mut *cache0;
1495 std::thread::scope(|scope| -> Result<_, Box<dyn std::error::Error>> {
1500 let stage0 = scope.spawn(move || -> Result<usize, String> {
1501 let next = self
1502 .prime_pp2_stage0_enqueue(
1503 e,
1504 rt,
1505 &tokens[next_start..next_end],
1506 cache0_stage,
1507 seq_end,
1508 fence,
1509 next_base,
1510 true,
1511 )
1512 .map_err(|err| err.to_string())?;
1513 cache0_stage.pos = initial_base + next_end;
1514 Ok(next)
1515 });
1516 let x = self.prime_pp2_stage1_enqueue(
1517 e,
1518 rt,
1519 slot,
1520 end - start,
1521 cache1,
1522 seq_end,
1523 fence,
1524 base,
1525 true,
1526 )?;
1527 let out = {
1528 rt.bind_stage(1)?;
1529 let _st1 = rt.enter(1);
1530 let e1 = rt.engine(1, e);
1531 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1532 };
1533 let next = stage0
1534 .join()
1535 .map_err(|_| "pipeprime stage-0 host walker panicked")?
1536 .map_err(|err| -> Box<dyn std::error::Error> { err.into() })?;
1537 Ok((out, Some(next)))
1538 })?
1539 } else {
1540 let x = self.prime_pp2_stage1_enqueue(
1541 e,
1542 rt,
1543 slot,
1544 end - start,
1545 cache1,
1546 seq_end,
1547 fence,
1548 base,
1549 true,
1550 )?;
1551 let out = {
1552 rt.bind_stage(1)?;
1553 let _st1 = rt.enter(1);
1554 let e1 = rt.engine(1, e);
1555 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1556 };
1557 (out, None)
1558 };
1559
1560 rt.publish_to(1, &caller_stream)?;
1561 e.copy_into(&mut hiddens, start * n_embd, &out.2, (end - start) * n_embd)?;
1562 last = Some((out.0, out.1));
1563 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1564
1565 if let Some(next) = next_slot {
1566 rt.fence_stages_behind(&caller_stream)?;
1571 slot = next;
1572 }
1573 }
1574
1575 debug_assert_eq!(cache0.pos, initial_base + t);
1576 debug_assert_eq!(cache1.pos, initial_base + t);
1577 let (logits, h_seed) = last.unwrap();
1578 Ok((logits, h_seed, hiddens))
1579 }
1580
1581 fn gdn_hk(e: &Engine, t: usize, num_v: usize, num_k: usize) -> usize {
1588 if Engine::gdn_db_on()
1589 && Engine::gdn_chunked_enabled()
1590 && t >= 16
1591 && e.gdn_mma_enabled(Engine::gdn_chunk_size())
1592 && num_k * 2 == num_v
1593 {
1594 num_k
1595 } else {
1596 num_v
1597 }
1598 }
1599
1600 fn f16out_on(e: &Engine, t: usize) -> bool {
1605 crate::f16_ffi::pp_f16_enabled()
1606 && t >= 16
1607 && !e.verify_exact_on()
1608 && std::env::var("MEMRA_F16OUT").as_deref() != Ok("0")
1609 }
1610
1611 pub fn prime_slabs_get(
1619 &self,
1620 e: &Engine,
1621 t: usize,
1622 n_embd: usize,
1623 n_ff_max: usize,
1624 ) -> Result<std::sync::Arc<std::sync::Mutex<PrimeSlabs>>, Box<dyn std::error::Error>> {
1625 let mut slabs = self.prime_slabs.lock().unwrap();
1626 let dev = e.ctx().ordinal();
1627 let need_new = match slabs.get(&dev) {
1628 None => true,
1629 Some(sl) => sl.lock().unwrap().t_cap < t,
1630 };
1631 if need_new {
1632 slabs.insert(
1633 dev,
1634 std::sync::Arc::new(std::sync::Mutex::new(PrimeSlabs {
1635 t_cap: t,
1636 h: e.uninit(t * n_embd)?,
1637 x1: e.uninit(t * n_embd)?,
1638 z: e.uninit(t * n_embd)?,
1639 act: e.uninit(t * n_ff_max)?,
1640 xa: e.uninit(t * n_embd)?,
1641 xb: e.uninit(t * n_embd)?,
1642 h16: e.alloc_u8_uninit(t * n_embd * 2)?,
1643 z16: e.alloc_u8_uninit(t * n_embd * 2)?,
1644 gate: e.uninit(t * n_ff_max)?,
1645 up: e.uninit(t * n_ff_max)?,
1646 ffn_out: e.uninit(t * n_embd)?,
1647 seg_glue: Vec::new(),
1648 mixed: e.uninit(t * n_embd)?,
1649 seg_mid: Vec::new(),
1650 seg_t: 0,
1651 })),
1652 );
1653 }
1654 Ok(slabs.get(&dev).expect("prime slab inserted").clone())
1655 }
1656
1657 fn prime_chunk(
1661 &self,
1662 e: &Engine,
1663 tokens: &[u32],
1664 cache: &mut Cache,
1665 seq_end: usize,
1666 chunk_off: usize,
1667 overlay: Option<&crate::vision::EmbedOverlay>,
1668 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1669 if crate::pp::pp_host_bounce_active()
1670 && (self.uses_gemma_program() || !crate::pp::prime_pp_on())
1671 {
1672 return Err(
1673 "prime_chunk: refused with MEMRA_PP_HOST_BOUNCE=1 because this configuration \
1674 has no active prime stage split and would peer-read remote weights; keep \
1675 MEMRA_PRIME_PP enabled and use a PP-prime-supported model"
1676 .into(),
1677 );
1678 }
1679 if !self.uses_gemma_program() && !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
1688 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
1689 if overlay.is_some() {
1690 return Err("vision embedding overlay + PP prime unsupported (v1); \
1691 run single-device or MEMRA_PRIME_PP=0"
1692 .into());
1693 }
1694 return self.prime_chunk_ppn(e, tokens, cache, seq_end, &fence);
1695 }
1696 }
1697 if crate::pp::pp_host_bounce_active() {
1698 return Err(
1699 "prime_chunk: MEMRA_PP_HOST_BOUNCE=1 found no valid prime stage split; \
1700 refusing an unsplit remote-weight walk"
1701 .into(),
1702 );
1703 }
1704 let t = tokens.len();
1705 let base = cache.pos;
1706 debug_assert!(
1707 seq_end >= base + t,
1708 "prime_chunk: seq_end must cover this chunk"
1709 );
1710 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1711 let pos_d = e.htod_i32(&pos)?;
1712
1713 let mut x_embed = self.embed(e, tokens)?; if let Some(ov) = overlay {
1715 let n_embd = self.cfg.n_embd as usize;
1719 for &(pos, row_off, n_rows) in &ov.spans {
1720 let lo = pos.max(chunk_off);
1721 let hi = (pos + n_rows).min(chunk_off + t);
1722 if lo < hi {
1723 let src_row = row_off + (lo - pos);
1724 let view = ov
1725 .rows
1726 .slice(src_row * n_embd..(src_row + (hi - lo)) * n_embd);
1727 e.copy_view_into(
1728 &mut x_embed,
1729 (lo - chunk_off) * n_embd,
1730 &view,
1731 (hi - lo) * n_embd,
1732 )?;
1733 }
1734 }
1735 }
1736 let x = self.prime_layers(
1737 e,
1738 x_embed,
1739 0,
1740 self.layers.len(),
1741 &pos_d,
1742 t,
1743 base,
1744 cache,
1745 seq_end,
1746 )?;
1747 self.prime_chunk_epilogue(e, x, t, cache)
1748 }
1749
1750 #[allow(clippy::too_many_arguments)]
1766 fn prime_layers(
1767 &self,
1768 e: &Engine,
1769 x_in: CudaSlice<f32>,
1770 lo: usize,
1771 hi: usize,
1772 pos_d: &CudaSlice<i32>,
1773 t: usize,
1774 base: usize,
1775 cache: &mut Cache,
1776 seq_end: usize,
1777 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1778 let cfg = &self.cfg;
1779 let n_embd = cfg.n_embd as usize;
1780 let eps = cfg.rms_eps;
1781 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
1785 let n_ff_max = self
1792 .layers
1793 .iter()
1794 .map(|l| match &l.ffn {
1795 crate::hybrid::Ffn::Dense { ffn_gate, .. } => ffn_gate.out_features(),
1796 _ => n_embd,
1797 })
1798 .max()
1799 .unwrap_or(n_embd)
1800 .max(n_embd);
1801 let use_slabs = std::env::var("MEMRA_PRIME_SLABS").as_deref() != Ok("0");
1802 let slab = if use_slabs {
1803 Some(self.prime_slabs_get(e, t, n_embd, n_ff_max)?)
1804 } else {
1805 None
1806 };
1807 let mut slab_guard = slab.as_ref().map(|sl| sl.lock().unwrap());
1808 let mut x_own; type SlabRefs<'a> = (
1810 &'a mut CudaSlice<f32>,
1811 &'a mut CudaSlice<f32>,
1812 &'a mut CudaSlice<f32>,
1813 &'a mut CudaSlice<f32>,
1814 &'a mut CudaSlice<u8>,
1815 &'a mut CudaSlice<u8>,
1816 &'a mut CudaSlice<f32>,
1817 &'a mut CudaSlice<f32>,
1818 &'a mut CudaSlice<f32>,
1819 );
1820 let (mut x_cur, mut x_nxt, sl): (
1821 &mut CudaSlice<f32>,
1822 &mut CudaSlice<f32>,
1823 Option<SlabRefs>,
1824 );
1825 let mut seg: Option<(
1826 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1827 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1828 &mut CudaSlice<f32>,
1829 &mut usize,
1830 )> = None;
1831 let mut x_own2;
1832 match slab_guard.as_mut() {
1833 Some(g) => {
1834 let slabs = &mut **g;
1835 e.copy_into(&mut slabs.xa, 0, &x_in, t * n_embd)?;
1836 let PrimeSlabs {
1837 xa,
1838 xb,
1839 h,
1840 x1,
1841 z,
1842 act,
1843 h16,
1844 z16,
1845 gate,
1846 up,
1847 ffn_out,
1848 seg_glue,
1849 mixed,
1850 seg_mid,
1851 seg_t,
1852 ..
1853 } = slabs;
1854 x_cur = xa;
1855 x_nxt = xb;
1856 seg = Some((seg_glue, seg_mid, mixed, seg_t));
1857 sl = Some((h, x1, z, act, h16, z16, gate, up, ffn_out));
1858 }
1859 None => {
1860 x_own = x_in;
1861 x_own2 = e.uninit(t * n_embd)?;
1862 x_cur = &mut x_own;
1863 x_nxt = &mut x_own2;
1864 sl = None;
1865 }
1866 }
1867 let mut alloc_h;
1868 let mut alloc_x1;
1869 let mut alloc_z;
1870 let mut alloc_act;
1871 let mut alloc_h16;
1872 let mut alloc_z16;
1873 let mut alloc_gate;
1874 let mut alloc_up;
1875 let mut alloc_fo;
1876 let (h, x1, z, act): (
1877 &mut CudaSlice<f32>,
1878 &mut CudaSlice<f32>,
1879 &mut CudaSlice<f32>,
1880 &mut CudaSlice<f32>,
1881 );
1882 let (h16, z16): (&mut CudaSlice<u8>, &mut CudaSlice<u8>);
1883 let (sl_gate, sl_up, sl_fo): (
1884 &mut CudaSlice<f32>,
1885 &mut CudaSlice<f32>,
1886 &mut CudaSlice<f32>,
1887 );
1888 match sl {
1889 Some((a, b, c, d, e16, f16b, g, u, fo)) => {
1890 h = a;
1891 x1 = b;
1892 z = c;
1893 act = d;
1894 h16 = e16;
1895 z16 = f16b;
1896 sl_gate = g;
1897 sl_up = u;
1898 sl_fo = fo;
1899 }
1900 None => {
1901 alloc_h = e.uninit(t * n_embd)?;
1902 alloc_x1 = e.uninit(t * n_embd)?;
1903 alloc_z = e.uninit(t * n_embd)?;
1904 alloc_act = e.uninit(t * n_ff_max)?;
1905 alloc_h16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1906 alloc_z16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1907 alloc_gate = e.uninit(t * n_ff_max)?;
1908 alloc_up = e.uninit(t * n_ff_max)?;
1909 alloc_fo = e.uninit(t * n_embd)?;
1910 h = &mut alloc_h;
1911 x1 = &mut alloc_x1;
1912 z = &mut alloc_z;
1913 act = &mut alloc_act;
1914 h16 = &mut alloc_h16;
1915 z16 = &mut alloc_z16;
1916 sl_gate = &mut alloc_gate;
1917 sl_up = &mut alloc_up;
1918 sl_fo = &mut alloc_fo;
1919 }
1920 }
1921 let n_layers = self.layers.len();
1926 let use_seg = f16fuse
1936 && seg.is_some()
1937 && !self.uses_sliding_gated_moe_program()
1938 && lo == 0
1939 && hi == n_layers
1940 && std::env::var("MEMRA_PRIME_SEG").as_deref() == Ok("1");
1941 if let Some((sg, sm, _, st)) = seg.as_mut() {
1942 if **st != t {
1943 sg.clear();
1944 sg.extend((0..n_layers).map(|_| None));
1945 sm.clear();
1946 sm.extend((0..n_layers).map(|_| None));
1947 **st = t;
1948 }
1949 }
1950 {
1951 let layer_lo = &self.layers[lo];
1952 if f16fuse {
1953 e.rms_norm_f16out(
1954 x_cur,
1955 layer_lo.attn_norm.float_data(),
1956 h,
1957 h16,
1958 n_embd,
1959 t,
1960 eps,
1961 )?;
1962 } else {
1963 e.rms_norm(x_cur, layer_lo.attn_norm.float_data(), h, n_embd, t, eps)?;
1964 }
1965 }
1966 let anat = Self::prime_anatomy_on();
1967 let mut anat_last = if anat {
1968 e.stream().synchronize()?;
1969 Some(std::time::Instant::now())
1970 } else {
1971 None
1972 };
1973 macro_rules! anat_mark {
1975 ($slot:expr) => {
1976 if let Some(ts) = anat_last.as_mut() {
1977 e.stream().synchronize()?;
1978 Self::prime_anatomy_slots()[$slot].fetch_add(
1979 ts.elapsed().as_nanos() as u64,
1980 std::sync::atomic::Ordering::Relaxed,
1981 );
1982 *ts = std::time::Instant::now();
1983 }
1984 };
1985 }
1986 for il in lo..hi {
1987 let layer = &self.layers[il];
1988 let hx16 = if f16fuse { Some(&*h16) } else { None };
1989 if use_seg {
1990 let (pre, pre16, w_out) = match &layer.mixer {
1993 Mixer::Full(fa) => {
1994 let g3 = match hx16 {
1995 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
1996 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
1997 };
1998 let (pre, pre16) =
1999 self.full_attn_prime_core_inner(e, fa, g3, &pos_d, t, cache, il)?;
2000 (pre, pre16, &fa.wo)
2001 }
2002 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2003 Mixer::Linear(la) => {
2004 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2005 let g4 = match hx16 {
2006 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
2007 None => e.matmul_group(&ws, h, t)?,
2008 };
2009 let (pre, pre16) =
2010 self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, None)?;
2011 (pre, pre16, &la.ssm_out)
2012 }
2013 };
2014 {
2015 let (_, sm, mslab, _) = seg.as_mut().unwrap();
2016 let pre_n = pre.len() / t;
2017 let xh_pre = match pre16 {
2018 Some(x) => x,
2019 None => e.f16_act(&pre, t * pre_n, pre_n)?,
2020 };
2021 if !e.try_f16_gemm_pre_into(w_out, &xh_pre, t, mslab)? {
2022 let y = e.matmul(w_out, &pre, t)?;
2023 e.copy_into(mslab, 0, &y, t * n_embd)?;
2024 }
2025 if sm[il].is_none() {
2026 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
2027 let w_post = layer.post_attn_norm.float_data();
2028 e.stream().synchronize()?;
2029 e.stream()
2030 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2031 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2032 e.add(x_cur, mslab, x1, t * n_embd)?;
2033 e.rms_norm_f16out(x1, w_post, z, z16, n_embd, t, eps)?;
2034 Ok(())
2035 })();
2036 let g = e.stream().end_capture(
2037 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH);
2038 r?;
2039 sm[il] = Some(g?.ok_or("S-mid capture produced no graph")?);
2040 }
2041 sm[il].as_ref().unwrap().launch()?;
2042 }
2043 } else {
2044 let mixed = match &layer.mixer {
2045 Mixer::Full(fa) => {
2046 let y =
2047 self.full_attn_prime(e, fa, h, hx16, &pos_d, t, cache, il, seq_end)?;
2048 anat_mark!(0);
2049 y
2050 }
2051 Mixer::Linear(la) => {
2052 let y = self.linear_attn_prime(e, la, h, hx16, t, cache, il)?;
2053 anat_mark!(1);
2054 y
2055 }
2056 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2057 };
2058 if f16fuse {
2059 e.add_rms_norm_f16out(
2062 x_cur,
2063 &mixed,
2064 layer.post_attn_norm.float_data(),
2065 x1,
2066 z,
2067 z16,
2068 n_embd,
2069 t,
2070 eps,
2071 )?;
2072 } else {
2073 e.add(x_cur, &mixed, x1, t * n_embd)?;
2074 e.rms_norm(x1, layer.post_attn_norm.float_data(), z, n_embd, t, eps)?;
2075 }
2076 anat_mark!(4);
2077 }
2078 let zx16 = if f16fuse { Some(&*z16) } else { None };
2079 match &layer.ffn {
2080 crate::hybrid::Ffn::Dense {
2081 ffn_gate,
2082 ffn_up,
2083 ffn_down,
2084 } => {
2085 let n_ff = ffn_gate.out_features();
2086 let mut into_ok = false;
2089 if let Some(xh) = zx16 {
2090 into_ok = e.try_f16_gemm_pre_into(ffn_gate, xh, t, sl_gate)?
2091 && e.try_f16_gemm_pre_into(ffn_up, xh, t, sl_up)?;
2092 }
2093 if !into_ok {
2094 let mut g2 = match zx16 {
2095 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], z, xh, t)?,
2096 None => e.matmul_group(&[ffn_gate, ffn_up], z, t)?,
2097 };
2098 let up_y = g2.pop().unwrap();
2099 let gate_y = g2.pop().unwrap();
2100 e.copy_into(sl_gate, 0, &gate_y, t * n_ff)?;
2101 e.copy_into(sl_up, 0, &up_y, t * n_ff)?;
2102 }
2103 let d_lim = self.cfg.clamp_shexp_at(il as u32);
2108 let act16 = if Self::f16out_on(e, t) && self.cfg.m3.is_none() && d_lim.is_none()
2109 {
2110 let mut a16 = e.alloc_u8_uninit(t * n_ff * 2)?;
2111 e.silu_mul_f16out(sl_gate, sl_up, act, &mut a16, t * n_ff)?;
2112 Some(a16)
2113 } else {
2114 Self::ffn_act_lim(
2115 e,
2116 &self.cfg,
2117 sl_gate,
2118 sl_up,
2119 1.0,
2120 1.0,
2121 d_lim,
2122 act,
2123 t * n_ff,
2124 )?;
2125 None
2126 };
2127 let xh_act = match act16 {
2129 Some(x) => x,
2130 None => e.f16_act(act, t * n_ff, n_ff)?,
2131 };
2132 if !e.try_f16_gemm_pre_into(ffn_down, &xh_act, t, sl_fo)? {
2133 let y = e.matmul(ffn_down, &*act, t)?;
2134 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2135 }
2136 }
2137 crate::hybrid::Ffn::Moe(m) => {
2138 let y = self.moe_ffn_il_prefill(e, m, z, t, il as u16)?;
2139 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2140 anat_mark!(2);
2141 }
2142 }
2143 if let (crate::hybrid::Ffn::Dense { .. }, true) = (&layer.ffn, anat) {
2144 anat_mark!(3);
2145 }
2146 if use_seg && il + 1 < hi {
2147 let w_next = self.layers[il + 1].attn_norm.float_data();
2149 let (sg, _, _, _) = seg.as_mut().unwrap();
2150 if sg[il].is_none() {
2151 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
2152 e.stream().synchronize()?;
2153 e.stream()
2154 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2155 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2156 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2157 e.rms_norm_f16out(x_nxt, w_next, h, h16, n_embd, t, eps)?;
2158 Ok(())
2159 })();
2160 let g = e.stream().end_capture(
2161 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
2162 );
2163 r?;
2164 sg[il] = Some(g?.ok_or("S-glue capture produced no graph")?);
2165 }
2166 sg[il].as_ref().unwrap().launch()?;
2167 } else {
2168 if il + 1 < hi {
2169 let w_next = self.layers[il + 1].attn_norm.float_data();
2170 if f16fuse {
2171 e.add_rms_norm_f16out(x1, sl_fo, w_next, x_nxt, h, h16, n_embd, t, eps)?;
2172 } else {
2173 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2174 e.rms_norm(x_nxt, w_next, h, n_embd, t, eps)?;
2175 }
2176 } else {
2177 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2178 }
2179 }
2180 anat_mark!(4);
2181 if let Some(path) = Self::prime_trace_path() {
2187 let row = (base + t - 1) as usize;
2188 let host = e.dtoh(x_nxt)?;
2189 let last = &host[(t - 1) * n_embd..t * n_embd];
2190 use std::io::Write as _;
2191 let mut f = std::fs::OpenOptions::new()
2192 .create(true)
2193 .append(true)
2194 .open(path)?;
2195 let mut h64: u64 = 0xcbf29ce484222325;
2196 for v in last {
2197 h64 ^= v.to_bits() as u64;
2198 h64 = h64.wrapping_mul(0x100000001b3);
2199 }
2200 writeln!(
2201 f,
2202 "{{\"pos\":{row},\"layer\":{il},\"t\":{t},\"base\":{base},\
2203 \"hash\":\"{h64:016x}\",\"v0\":{:.9e},\"v1\":{:.9e},\"v2\":{:.9e}}}",
2204 last[0], last[1], last[2]
2205 )?;
2206 }
2207 self.dflash_tap(e, cache, il, x_nxt, t)?;
2210 std::mem::swap(&mut x_cur, &mut x_nxt);
2211 }
2212 if anat {
2213 let s = Self::prime_anatomy_slots();
2214 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
2215 eprintln!(
2216 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
2217 dense={:.1} norms_adds={:.1} (t={t}, layers {lo}..{hi})",
2218 ms(0),
2219 ms(1),
2220 ms(2),
2221 ms(3),
2222 ms(4)
2223 );
2224 }
2225 let mut x = e.uninit(t * n_embd)?;
2227 e.copy_into(&mut x, 0, x_cur, t * n_embd)?;
2228 drop(slab_guard);
2229 Ok(x)
2230 }
2231
2232 fn prime_chunk_epilogue(
2237 &self,
2238 e: &Engine,
2239 x: CudaSlice<f32>,
2240 t: usize,
2241 cache: &mut Cache,
2242 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2243 let n_embd = self.cfg.n_embd as usize;
2244 let eps = self.cfg.rms_eps;
2245 let mut h_seed = e.uninit(n_embd)?;
2249 if !crate::spec::spec_hpost() {
2250 e.copy_view_into(
2251 &mut h_seed,
2252 0,
2253 &x.slice((t - 1) * n_embd..t * n_embd),
2254 n_embd,
2255 )?;
2256 }
2257 let mut hn = e.uninit(t * n_embd)?;
2259 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2260 if crate::spec::spec_hpost() {
2261 e.copy_view_into(
2262 &mut h_seed,
2263 0,
2264 &hn.slice((t - 1) * n_embd..t * n_embd),
2265 n_embd,
2266 )?;
2267 }
2268 let last = e.view(&hn, t * n_embd);
2269 let last_row = last.slice((t - 1) * n_embd..t * n_embd);
2270 let mut hlast = e.uninit(n_embd)?;
2271 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
2272 let logits = e.matmul(&self.output, &hlast, 1)?;
2273 cache.pos += t;
2274 Ok((
2277 e.dtoh(&logits)?,
2278 h_seed,
2279 if crate::spec::spec_hpost() { hn } else { x },
2280 ))
2281 }
2282
2283 fn prime_chunk_ppn(
2307 &self,
2308 e: &Engine,
2309 tokens: &[u32],
2310 cache: &mut Cache,
2311 seq_end: usize,
2312 fence: &[usize],
2313 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2314 let rt = crate::pp::PpNRt::get(e)?;
2315 let n_st = fence.len() - 1;
2316 assert_eq!(
2317 rt.n_stages(),
2318 n_st,
2319 "PpNRt stage count {} != fence stages {n_st}",
2320 rt.n_stages()
2321 );
2322 let n_embd = self.cfg.n_embd as usize;
2323 let t = tokens.len();
2324 let base = cache.pos;
2325 debug_assert!(
2326 seq_end >= base + t,
2327 "prime_chunk_ppn: seq_end must cover this chunk"
2328 );
2329 let payload = t * n_embd;
2330 let caller_stream = e.stream();
2334 rt.fence_stages_behind(&caller_stream)?;
2335
2336 if n_st == 2 {
2337 let slot =
2338 self.prime_pp2_stage0_enqueue(e, rt, tokens, cache, seq_end, fence, base, false)?;
2339 let x =
2340 self.prime_pp2_stage1_enqueue(e, rt, slot, t, cache, seq_end, fence, base, false)?;
2341 let out = {
2342 rt.bind_stage(1)?;
2343 let _st1 = rt.enter(1);
2344 let e1 = rt.engine(1, e);
2345 self.prime_chunk_epilogue(e1, x, t, cache)?
2346 };
2347 rt.publish_to(1, &caller_stream)?;
2348 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2349 return Ok(out);
2350 }
2351
2352 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2353
2354 let mut slot = {
2356 let _st0 = rt.enter(0);
2357 let e0 = rt.engine(0, e);
2358 let pos_d = e0.htod_i32(&pos)?;
2359 let x = self.embed(e0, tokens)?;
2360 let x =
2361 self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2362 rt.tx(0, &x, payload)?
2363 };
2365
2366 for s in 1..n_st - 1 {
2368 let _st = rt.enter(s);
2369 let es = rt.engine(s, e);
2370 let pos_d = es.htod_i32(&pos)?;
2371 let x = rt.rx(s - 1, slot, payload)?;
2372 let x = self.prime_layers(
2373 es,
2374 x,
2375 fence[s],
2376 fence[s + 1],
2377 &pos_d,
2378 t,
2379 base,
2380 cache,
2381 seq_end,
2382 )?;
2383 slot = rt.tx(s, &x, payload)?;
2384 }
2385
2386 let _stl = rt.enter(n_st - 1);
2388 let el = rt.engine(n_st - 1, e);
2389 let pos_d = el.htod_i32(&pos)?;
2390 let x = rt.rx(n_st - 2, slot, payload)?;
2391 let x = self.prime_layers(
2392 el,
2393 x,
2394 fence[n_st - 1],
2395 fence[n_st],
2396 &pos_d,
2397 t,
2398 base,
2399 cache,
2400 seq_end,
2401 )?;
2402 let out = self.prime_chunk_epilogue(el, x, t, cache)?;
2403 rt.publish_to(n_st - 1, &caller_stream)?;
2409 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2410 Ok(out)
2411 }
2412
2413 fn prime_pp2_stage0_enqueue(
2414 &self,
2415 e: &Engine,
2416 rt: &crate::pp::PpNRt,
2417 tokens: &[u32],
2418 cache: &mut Cache,
2419 seq_end: usize,
2420 fence: &[usize],
2421 base: usize,
2422 pipelined: bool,
2423 ) -> Result<usize, Box<dyn std::error::Error>> {
2424 let t = tokens.len();
2425 let n_embd = self.cfg.n_embd as usize;
2426 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2427 rt.bind_stage(0)?;
2428 let _st0 = rt.enter(0);
2429 let e0 = rt.engine(0, e);
2430 let pos_d = e0.htod_i32(&pos)?;
2431 let x = self.embed(e0, tokens)?;
2432 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2433 let x = self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2434 if pipelined {
2435 rt.tx_pipelined(0, &x, t * n_embd)
2436 } else {
2437 rt.tx(0, &x, t * n_embd)
2438 }
2439 }
2440
2441 fn prime_pp2_stage1_enqueue(
2442 &self,
2443 e: &Engine,
2444 rt: &crate::pp::PpNRt,
2445 slot: usize,
2446 t: usize,
2447 cache: &mut Cache,
2448 seq_end: usize,
2449 fence: &[usize],
2450 base: usize,
2451 pipelined: bool,
2452 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2453 let n_embd = self.cfg.n_embd as usize;
2454 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2455 rt.bind_stage(1)?;
2456 let _st1 = rt.enter(1);
2457 let e1 = rt.engine(1, e);
2458 let pos_d = e1.htod_i32(&pos)?;
2459 let x = rt.rx(0, slot, t * n_embd)?;
2460 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2461 self.prime_layers(e1, x, fence[1], fence[2], &pos_d, t, base, cache, seq_end)
2462 }
2463
2464 pub fn prime_chunk_captured(
2480 &self,
2481 e: &Engine,
2482 x_in: &CudaSlice<f32>,
2483 pos_d: &CudaSlice<i32>,
2484 t: usize,
2485 cache: &mut Cache,
2486 len_d: &CudaSlice<i32>,
2487 logits_out: &mut CudaSlice<f32>,
2488 h_seed_out: &mut CudaSlice<f32>,
2489 ) -> Result<(), Box<dyn std::error::Error>> {
2490 let cfg = &self.cfg;
2491 let n_embd = cfg.n_embd as usize;
2492 let eps = cfg.rms_eps;
2493 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
2494 let mut x = e.uninit(t * n_embd)?;
2495 e.copy_into(&mut x, 0, x_in, t * n_embd)?;
2496 for (il, layer) in self.layers.iter().enumerate() {
2497 let mut h = e.uninit(t * n_embd)?;
2498 let mut hx16: Option<CudaSlice<u8>> = None;
2499 if f16fuse {
2500 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2501 e.rms_norm_f16out(
2502 &x,
2503 layer.attn_norm.float_data(),
2504 &mut h,
2505 &mut b16,
2506 n_embd,
2507 t,
2508 eps,
2509 )?;
2510 hx16 = Some(b16);
2511 } else {
2512 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
2513 }
2514 let mixed = match &layer.mixer {
2515 Mixer::Full(fa) => {
2519 self.full_attn_prime(e, fa, &h, hx16.as_ref(), pos_d, t, cache, il, t)?
2520 }
2521 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2522 Mixer::Linear(la) => {
2523 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2524 let g4 = match hx16.as_ref() {
2525 Some(xh) => e.matmul_group_xh(&ws, &h, xh, t)?,
2526 None => e.matmul_group(&ws, &h, t)?,
2527 };
2528 self.linear_attn_prime_core_pad(e, la, g4, t, cache, il, Some(len_d))?
2529 }
2530 };
2531 let mut x1 = e.uninit(t * n_embd)?;
2532 e.add(&x, &mixed, &mut x1, t * n_embd)?;
2533 let mut z = e.uninit(t * n_embd)?;
2534 let mut zx16: Option<CudaSlice<u8>> = None;
2535 if f16fuse {
2536 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2537 e.rms_norm_f16out(
2538 &x1,
2539 layer.post_attn_norm.float_data(),
2540 &mut z,
2541 &mut b16,
2542 n_embd,
2543 t,
2544 eps,
2545 )?;
2546 zx16 = Some(b16);
2547 } else {
2548 e.rms_norm(
2549 &x1,
2550 layer.post_attn_norm.float_data(),
2551 &mut z,
2552 n_embd,
2553 t,
2554 eps,
2555 )?;
2556 }
2557 let ffn_out = match &layer.ffn {
2558 crate::hybrid::Ffn::Dense {
2559 ffn_gate,
2560 ffn_up,
2561 ffn_down,
2562 } => {
2563 let n_ff = ffn_gate.out_features();
2564 let mut g2 = match &zx16 {
2565 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], &z, xh, t)?,
2566 None => e.matmul_group(&[ffn_gate, ffn_up], &z, t)?,
2567 };
2568 let up = g2.pop().unwrap();
2569 let gate = g2.pop().unwrap();
2570 let mut act = e.uninit(t * n_ff)?;
2571 Self::ffn_act_lim(
2573 e,
2574 &self.cfg,
2575 &gate,
2576 &up,
2577 1.0,
2578 1.0,
2579 self.cfg.clamp_shexp_at(il as u32),
2580 &mut act,
2581 t * n_ff,
2582 )?;
2583 e.matmul(ffn_down, &act, t)?
2584 }
2585 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
2586 };
2587 let mut x2 = e.uninit(t * n_embd)?;
2588 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
2589 x = x2;
2590 }
2591 if !crate::spec::spec_hpost() {
2593 e.row_gather_dev(&x, h_seed_out, len_d, n_embd)?;
2594 }
2595 let mut hn = e.uninit(t * n_embd)?;
2596 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2597 if crate::spec::spec_hpost() {
2598 e.row_gather_dev(&hn, h_seed_out, len_d, n_embd)?;
2599 }
2600 let mut hlast = e.uninit(n_embd)?;
2601 e.row_gather_dev(&hn, &mut hlast, len_d, n_embd)?;
2602 let logits = e.matmul(&self.output, &hlast, 1)?;
2603 let nv = logits.len();
2604 e.copy_into(logits_out, 0, &logits, nv)?;
2605 Ok(())
2606 }
2607
2608 fn step35_prime_batch_on() -> bool {
2609 std::env::var("MEMRA_STEP35_PRIME_BATCH").as_deref() != Ok("0")
2610 }
2611
2612 #[allow(clippy::too_many_arguments)]
2615 fn step35_prime_batch_layers(
2616 &self,
2617 e: &Engine,
2618 mut x: CudaSlice<f32>,
2619 lo: usize,
2620 hi: usize,
2621 ts: &[usize],
2622 offs: &[usize],
2623 pos_ds: &[CudaSlice<i32>],
2624 caches: &mut [&mut Cache],
2625 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2626 let cfg = &self.cfg;
2627 let n_embd = cfg.n_embd as usize;
2628 let eps = cfg.rms_eps;
2629 let b = ts.len();
2630 let total: usize = ts.iter().sum();
2631 let f16fuse = crate::f16_ffi::pp_f16_enabled() && total >= 16;
2632
2633 let split = |e: &Engine,
2634 y: &CudaSlice<f32>,
2635 dim: usize|
2636 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2637 let mut out = Vec::with_capacity(b);
2638 for s in 0..b {
2639 let mut ys = e.uninit(ts[s] * dim)?;
2640 e.copy_view_into(
2641 &mut ys,
2642 0,
2643 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2644 ts[s] * dim,
2645 )?;
2646 out.push(ys);
2647 }
2648 Ok(out)
2649 };
2650
2651 for il in lo..hi {
2652 let layer = &self.layers[il];
2653 let Mixer::Full(fa) = &layer.mixer else {
2654 return Err(format!("step35 layer {il} is not full-attn — corrupt config").into());
2655 };
2656
2657 let mut h = e.uninit(total * n_embd)?;
2658 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2659 if f16fuse {
2660 e.rms_norm_f16out(
2661 &x,
2662 layer.attn_norm.float_data(),
2663 &mut h,
2664 &mut hx16,
2665 n_embd,
2666 total,
2667 eps,
2668 )?;
2669 } else {
2670 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, total, eps)?;
2671 }
2672
2673 let gate_w = fa
2677 .attn_gate
2678 .as_ref()
2679 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
2680 let mut g4 = if f16fuse {
2681 e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, &hx16, total)?
2682 } else {
2683 e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, total)?
2684 };
2685 let gate = g4.pop().unwrap();
2686 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2687 (0..b).map(|_| Vec::with_capacity(3)).collect();
2688 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g4) {
2689 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2690 parts[s].push(ys);
2691 }
2692 }
2693 let gates = split(e, &gate, gate_w.out_features())?;
2694 let geometry = self.step35_geom(il);
2695 let hd = geometry.head_dim_k as usize;
2696 let nh = geometry.n_head as usize;
2697 let mut ag_cat = e.uninit(total * nh * hd)?;
2698 for (s, (g3s, gate)) in parts.into_iter().zip(gates).enumerate() {
2699 let ag = self.step35_attn_pre_wo(
2700 e,
2701 fa,
2702 g3s,
2703 None,
2704 Some(&gate),
2705 &pos_ds[s],
2706 ts[s],
2707 Some(&mut *caches[s]),
2708 il,
2709 ts[s],
2710 )?;
2711 e.copy_into(&mut ag_cat, offs[s] * nh * hd, &ag, ts[s] * nh * hd)?;
2712 }
2713 let mixed = e.matmul(&fa.wo, &ag_cat, total)?;
2714
2715 let mut x1 = e.uninit(total * n_embd)?;
2716 let mut z = e.uninit(total * n_embd)?;
2717 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2718 if f16fuse {
2719 e.add_rms_norm_f16out(
2720 &x,
2721 &mixed,
2722 layer.post_attn_norm.float_data(),
2723 &mut x1,
2724 &mut z,
2725 &mut zx16,
2726 n_embd,
2727 total,
2728 eps,
2729 )?;
2730 } else {
2731 e.add(&x, &mixed, &mut x1, total * n_embd)?;
2732 e.rms_norm(
2733 &x1,
2734 layer.post_attn_norm.float_data(),
2735 &mut z,
2736 n_embd,
2737 total,
2738 eps,
2739 )?;
2740 }
2741
2742 let ffn_out = match &layer.ffn {
2743 crate::hybrid::Ffn::Dense {
2744 ffn_gate,
2745 ffn_up,
2746 ffn_down,
2747 } => {
2748 let n_ff = ffn_gate.out_features();
2749 let mut g2 = if f16fuse {
2750 e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?
2751 } else {
2752 e.matmul_group(&[ffn_gate, ffn_up], &z, total)?
2753 };
2754 let up = g2.pop().unwrap();
2755 let gate = g2.pop().unwrap();
2756 let mut act = e.uninit(total * n_ff)?;
2757 let d_lim = cfg.clamp_shexp_at(il as u32);
2758 if Self::f16out_on(e, total) && cfg.m3.is_none() && d_lim.is_none() {
2759 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2760 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2761 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2762 Some(y) => y,
2763 None => e.matmul(ffn_down, &act, total)?,
2764 }
2765 } else {
2766 Self::ffn_act_lim(
2767 e,
2768 cfg,
2769 &gate,
2770 &up,
2771 1.0,
2772 1.0,
2773 d_lim,
2774 &mut act,
2775 total * n_ff,
2776 )?;
2777 e.matmul(ffn_down, &act, total)?
2778 }
2779 }
2780 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il(e, m, &z, total, il as u16)?,
2781 };
2782 let mut x2 = e.uninit(total * n_embd)?;
2783 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2784 x = x2;
2785 }
2786 Ok(x)
2787 }
2788
2789 fn step35_prime_batch_epilogue(
2790 &self,
2791 e: &Engine,
2792 x: CudaSlice<f32>,
2793 ts: &[usize],
2794 offs: &[usize],
2795 caches: &mut [&mut Cache],
2796 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2797 let n_embd = self.cfg.n_embd as usize;
2798 let total: usize = ts.iter().sum();
2799 let mut hn = e.uninit(total * n_embd)?;
2800 e.rms_norm(
2801 &x,
2802 self.output_norm.float_data(),
2803 &mut hn,
2804 n_embd,
2805 total,
2806 self.cfg.rms_eps,
2807 )?;
2808
2809 let hidden_src = if crate::spec::spec_hpost() { &hn } else { &x };
2810 let mut out = Vec::with_capacity(ts.len());
2811 for s in 0..ts.len() {
2812 let mut hidden = e.uninit(ts[s] * n_embd)?;
2813 e.copy_view_into(
2814 &mut hidden,
2815 0,
2816 &hidden_src.slice(offs[s] * n_embd..(offs[s] + ts[s]) * n_embd),
2817 ts[s] * n_embd,
2818 )?;
2819 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2820 let mut h_seed = e.uninit(n_embd)?;
2821 e.copy_view_into(
2822 &mut h_seed,
2823 0,
2824 &hidden_src.slice(last0..last0 + n_embd),
2825 n_embd,
2826 )?;
2827 let mut hlast = e.uninit(n_embd)?;
2829 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2830 let logits = e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?;
2831 caches[s].pos += ts[s];
2832 out.push((logits, h_seed, hidden));
2833 }
2834 Ok(out)
2835 }
2836
2837 fn step35_prime_cache_batch(
2838 &self,
2839 e: &Engine,
2840 prompts: &[&[u32]],
2841 caches: &mut [&mut Cache],
2842 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2843 validate_step_prime_batch_modes(
2844 step_tp_prefill_enabled()?,
2845 step_ep_grouped_prefill_enabled()?,
2846 )?;
2847 if crate::pp::pp_host_bounce_active()
2848 && (!crate::pp::prime_pp_on() || crate::pp::pp_cuts(self.layers.len()).is_none())
2849 {
2850 return Err(
2851 "step35_prime_cache_batch: MEMRA_PP_HOST_BOUNCE=1 requires a valid prime \
2852 stage split; refusing an unsplit remote-weight walk"
2853 .into(),
2854 );
2855 }
2856 if !Self::step35_prime_batch_on() {
2857 return Err("step35 batched prime is disabled (MEMRA_STEP35_PRIME_BATCH=0)".into());
2858 }
2859 if caches.iter().any(|c| c.pos != 0) {
2860 return Err(
2861 "step35 batched prime currently supports complete fresh prompts only; \
2862 continuation/tick chunks require per-request queued_after"
2863 .into(),
2864 );
2865 }
2866
2867 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2868 for &t in &ts {
2869 assert!(
2870 t >= PRIME_MIN_T,
2871 "step35 batched prime needs T >= {PRIME_MIN_T}"
2872 );
2873 }
2874 for (s, c) in caches.iter().enumerate() {
2875 assert!(
2876 ts[s] <= c.max_ctx,
2877 "step35 batched prime exceeds cache max_ctx"
2878 );
2879 }
2880 let offs: Vec<usize> = ts
2881 .iter()
2882 .scan(0usize, |a, &t| {
2883 let o = *a;
2884 *a += t;
2885 Some(o)
2886 })
2887 .collect();
2888 let total: usize = ts.iter().sum();
2889 let payload = total * self.cfg.n_embd as usize;
2890 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2891 let positions: Vec<Vec<i32>> = ts.iter().map(|&t| (0..t as i32).collect()).collect();
2892 let upload_positions =
2893 |e: &Engine| -> Result<Vec<CudaSlice<i32>>, Box<dyn std::error::Error>> {
2894 positions
2895 .iter()
2896 .map(|p| e.htod_i32(p))
2897 .collect::<Result<_, _>>()
2898 };
2899
2900 static ONCE: std::sync::Once = std::sync::Once::new();
2901 ONCE.call_once(|| {
2902 eprintln!(
2903 "[step35-prime-batch] first concat prime: B={} tokens={total}",
2904 prompts.len()
2905 );
2906 });
2907
2908 let out = if !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
2909 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
2910 let rt = crate::pp::PpNRt::get(e)?;
2911 let n_st = fence.len() - 1;
2912 assert_eq!(
2913 rt.n_stages(),
2914 n_st,
2915 "step35 prime batch stage count mismatch"
2916 );
2917 let caller_stream = e.stream();
2918 rt.fence_stages_behind(&caller_stream)?;
2919
2920 let mut slot = {
2921 let _st0 = rt.enter(0);
2922 let e0 = rt.engine(0, e);
2923 let pos_ds = upload_positions(e0)?;
2924 let x = self.embed(e0, &cat_tokens)?;
2925 let x = self.step35_prime_batch_layers(
2926 e0, x, fence[0], fence[1], &ts, &offs, &pos_ds, caches,
2927 )?;
2928 rt.tx(0, &x, payload)?
2929 };
2930 for s in 1..n_st - 1 {
2931 let _st = rt.enter(s);
2932 let es = rt.engine(s, e);
2933 let pos_ds = upload_positions(es)?;
2934 let x = rt.rx(s - 1, slot, payload)?;
2935 let x = self.step35_prime_batch_layers(
2936 es,
2937 x,
2938 fence[s],
2939 fence[s + 1],
2940 &ts,
2941 &offs,
2942 &pos_ds,
2943 caches,
2944 )?;
2945 slot = rt.tx(s, &x, payload)?;
2946 }
2947
2948 let _stl = rt.enter(n_st - 1);
2949 let el = rt.engine(n_st - 1, e);
2950 let pos_ds = upload_positions(el)?;
2951 let x = rt.rx(n_st - 2, slot, payload)?;
2952 let x = self.step35_prime_batch_layers(
2953 el,
2954 x,
2955 fence[n_st - 1],
2956 fence[n_st],
2957 &ts,
2958 &offs,
2959 &pos_ds,
2960 caches,
2961 )?;
2962 let out = self.step35_prime_batch_epilogue(el, x, &ts, &offs, caches)?;
2963 rt.publish_to(n_st - 1, &caller_stream)?;
2964 crate::pp::STEP35_PRIME_BATCH_SPLITS
2965 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2966 out
2967 } else {
2968 let pos_ds = upload_positions(e)?;
2969 let x = self.embed(e, &cat_tokens)?;
2970 let x = self.step35_prime_batch_layers(
2971 e,
2972 x,
2973 0,
2974 self.layers.len(),
2975 &ts,
2976 &offs,
2977 &pos_ds,
2978 caches,
2979 )?;
2980 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2981 }
2982 } else {
2983 let pos_ds = upload_positions(e)?;
2984 let x = self.embed(e, &cat_tokens)?;
2985 let x = self.step35_prime_batch_layers(
2986 e,
2987 x,
2988 0,
2989 self.layers.len(),
2990 &ts,
2991 &offs,
2992 &pos_ds,
2993 caches,
2994 )?;
2995 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2996 };
2997 crate::pp::STEP35_PRIME_BATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2998 Ok(out)
2999 }
3000
3001 pub fn prime_cache_batch(
3018 &self,
3019 e: &Engine,
3020 prompts: &[&[u32]],
3021 caches: &mut [&mut Cache],
3022 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
3023 if crate::pp::pp_cuts(self.layers.len()).is_some()
3024 && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
3025 {
3026 return Err("pipeline rewrite is not qualified for batched prime".into());
3027 }
3028 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::CarriedPrime) {
3029 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::DecodeEager) {
3030 return Err("neither batched-prime nor eager rewrite is qualified".into());
3031 }
3032 if prompts.len() != caches.len() {
3033 return Err("prime fallback prompt/cache shape mismatch".into());
3034 }
3035 static ONCE: std::sync::Once = std::sync::Once::new();
3036 ONCE.call_once(|| {
3037 eprintln!(
3038 "[rewrite] carried-prime.v1 unqualified; using individual native eager primes"
3039 );
3040 });
3041 return prompts
3042 .iter()
3043 .copied()
3044 .zip(caches.iter_mut())
3045 .map(|(prompt, cache)| self.prime_cache(e, prompt, cache, 0))
3046 .collect();
3047 }
3048 let cfg = &self.cfg;
3049 let n_embd = cfg.n_embd as usize;
3050 let eps = cfg.rms_eps;
3051 let b = prompts.len();
3052 assert!(b >= 1 && b == caches.len());
3053 let pos0s: Vec<usize> = caches.iter().map(|c| c.pos).collect();
3054 let carried = pos0s.iter().any(|&p| p > 0);
3055 if self.uses_gemma_program() {
3061 return Err(
3062 "prime_cache_batch: gemma4 has no batched prime core (per-layer \
3063 swa/global geometry, softcapped head) — use gemma4_prime per sequence"
3064 .into(),
3065 );
3066 }
3067 if self.uses_sliding_gated_moe_program() {
3070 return self.step35_prime_cache_batch(e, prompts, caches);
3071 }
3072 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
3073 for &t in &ts {
3074 assert!(
3075 t >= PRIME_MIN_T,
3076 "prime_cache_batch needs T >= {PRIME_MIN_T}"
3077 );
3078 }
3079 for (s, c) in caches.iter().enumerate() {
3080 assert!(
3081 c.pos + ts[s] <= c.max_ctx,
3082 "prime_cache_batch: prompt exceeds cache max_ctx"
3083 );
3084 }
3085 let total: usize = ts.iter().sum();
3086 let offs: Vec<usize> = ts
3087 .iter()
3088 .scan(0usize, |a, &t| {
3089 let o = *a;
3090 *a += t;
3091 Some(o)
3092 })
3093 .collect();
3094 let pos_ds: Vec<CudaSlice<i32>> = ts
3096 .iter()
3097 .zip(&pos0s)
3098 .map(|(&t, &p0)| e.htod_i32(&(p0 as i32..(p0 + t) as i32).collect::<Vec<_>>()))
3099 .collect::<Result<_, _>>()?;
3100 let split = |e: &Engine,
3102 y: &CudaSlice<f32>,
3103 dim: usize|
3104 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3105 let mut out = Vec::with_capacity(b);
3106 for s in 0..b {
3107 let mut ys = e.uninit(ts[s] * dim)?;
3108 e.copy_view_into(
3109 &mut ys,
3110 0,
3111 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
3112 ts[s] * dim,
3113 )?;
3114 out.push(ys);
3115 }
3116 Ok(out)
3117 };
3118
3119 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
3120 let mut x = self.embed(e, &cat_tokens)?; for (il, layer) in self.layers.iter().enumerate() {
3122 let mut h = e.uninit(total * n_embd)?;
3123 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3124 e.rms_norm_f16out(
3125 &x,
3126 layer.attn_norm.float_data(),
3127 &mut h,
3128 &mut hx16,
3129 n_embd,
3130 total,
3131 eps,
3132 )?;
3133 let mut mixed = e.uninit(total * n_embd)?;
3135 match &layer.mixer {
3136 Mixer::Full(fa) => {
3137 let g3 = e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], &h, &hx16, total)?;
3138 let geometry = self.cfg.full_attention_geometry_at(il as u32);
3144 let (n_head, n_head_kv, head_dim) = (
3145 geometry.n_head as usize,
3146 geometry.n_head_kv as usize,
3147 geometry.head_dim_k as usize,
3148 );
3149 let fa_scale = geometry.attention_scale();
3150 let use_favl = !carried
3151 && (2..=8).contains(&b)
3152 && (head_dim == 256 || head_dim == 128)
3153 && geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ
3154 && std::env::var("MEMRA_NOFA").is_err()
3155 && std::env::var("MEMRA_FA_FLOOR").is_err()
3156 && std::env::var("MEMRA_FA_PP_W2").as_deref() != Ok("1")
3157 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0")
3158 && std::env::var("MEMRA_FA_VL").as_deref() != Ok("0");
3159 if use_favl {
3160 let (qf_w, kf_w, vf_w) = (
3161 fa.wq.out_features(),
3162 fa.wk.out_features(),
3163 fa.wv.out_features(),
3164 );
3165 memra_gguf::config::check_fused_q_gate_extent(qf_w, head_dim, n_head, 1)?;
3170 struct APre {
3171 q: CudaSlice<f32>,
3172 gate: Option<CudaSlice<f32>>,
3173 qn: CudaSlice<f32>,
3174 kn: CudaSlice<f32>,
3175 }
3176 let mut aps = Vec::with_capacity(b);
3177 for &t in ts.iter().take(b) {
3178 aps.push(APre {
3179 q: e.uninit(t * n_head * head_dim)?,
3180 gate: Some(e.uninit(t * n_head * head_dim)?),
3181 qn: e.uninit(t * n_head * head_dim)?,
3182 kn: e.uninit(t * n_head_kv * head_dim)?,
3183 });
3184 }
3185 let (kv_dim_k, kv_dim_v, ktb, vtb) = {
3186 let kvl = caches[0].kv[il].as_ref().unwrap();
3187 (kvl.kv_dim_k, kvl.kv_dim_v, kvl.k_tok_bytes, kvl.v_tok_bytes)
3188 };
3189 let pargs: Vec<crate::AttnPreVl> = (0..b)
3190 .map(|s| {
3191 let (o, t) = (offs[s], ts[s]);
3192 let kvl = caches[s].kv[il].as_ref().unwrap();
3193 assert!(
3194 kvl.len == 0 && kvl.len + t <= caches[s].max_ctx,
3195 "prime_cache_batch attn vl: fresh + capacity"
3196 );
3197 crate::AttnPreVl {
3198 qf: e.addr_f32v(&g3[0].slice(o * qf_w..(o + t) * qf_w)),
3199 kf: e.addr_f32v(&g3[1].slice(o * kf_w..(o + t) * kf_w)),
3200 vf: e.addr_f32v(&g3[2].slice(o * vf_w..(o + t) * vf_w)),
3201 q: e.addr_f32(&aps[s].q),
3202 gate: e.addr_f32(aps[s].gate.as_ref().unwrap()),
3203 qn: e.addr_f32(&aps[s].qn),
3204 kn: e.addr_f32(&aps[s].kn),
3205 kc: e.addr_u8(&kvl.k),
3206 vc: e.addr_u8(&kvl.v),
3207 t: t as i32,
3208 pad: 0,
3209 }
3210 })
3211 .collect();
3212 e.attn_pre_vl8(
3213 &pargs,
3214 fa.q_norm.float_data(),
3215 fa.k_norm.float_data(),
3216 head_dim,
3217 geometry.n_rot as usize,
3218 n_head,
3219 n_head_kv,
3220 self.cfg.rms_eps,
3221 geometry.rope_base,
3222 1.0,
3223 kv_dim_k,
3224 kv_dim_v,
3225 ktb,
3226 vtb,
3227 )?;
3228 for s in 0..b {
3229 let kvl = caches[s].kv[il].as_mut().unwrap();
3230 kvl.len += ts[s];
3231 let new_len = kvl.len as i32;
3232 e.set_i32_one(&mut kvl.len_d, new_len)?;
3233 }
3234 let mut attns = Vec::with_capacity(b);
3235 let mut mirrors = Vec::with_capacity(b);
3236 for &t in ts.iter().take(b) {
3237 attns.push(e.uninit(t * n_head * head_dim)?);
3238 let n = t * n_head_kv * head_dim;
3239 mirrors.push((e.alloc_u8_uninit(n * 2)?, e.alloc_u8_uninit(n * 2)?));
3240 }
3241 let fa3_on = match std::env::var("MEMRA_FA3").as_deref() {
3244 Ok("0") => false,
3245 Ok("1") => {
3249 crate::refuse_portable_force(
3250 "MEMRA_FA3=1",
3251 "the sm_90a fa3/bf16 kernels",
3252 );
3253 true
3254 }
3255 _ => cfg!(memra_hopper_mma),
3256 };
3257 if fa3_on {
3258 let mut q16s = Vec::with_capacity(b);
3259 let mut v16s = Vec::with_capacity(b);
3260 for s in 0..b {
3261 let t = ts[s];
3262 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
3263 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
3264 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3265 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
3266 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3267 e.f32_to_bf16_v(
3268 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
3269 &mut v16,
3270 t * n_head_kv * head_dim,
3271 )?;
3272 q16s.push(q16);
3273 v16s.push((k16, v16));
3274 }
3275 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
3276 let mut kp = qp;
3277 let mut vp = qp;
3278 let mut op = [core::ptr::null_mut::<f32>(); 8];
3279 let mut tsv = [0i32; 8];
3280 for s in 0..b {
3281 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
3282 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
3283 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
3284 op[s] = e.addr_f32(&attns[s]) as *mut f32;
3285 tsv[s] = ts[s] as i32;
3286 }
3287 let rc = unsafe {
3288 crate::fa3_vl_raw(
3289 qp.as_ptr(),
3290 kp.as_ptr(),
3291 vp.as_ptr(),
3292 op.as_ptr(),
3293 tsv.as_ptr(),
3294 b as i32,
3295 n_head as i32,
3296 n_head_kv as i32,
3297 head_dim as i32,
3298 fa_scale,
3299 e.stream().cu_stream() as *mut core::ffi::c_void,
3300 )
3301 };
3302 if rc != 0 {
3303 return Err(format!("memra_fa3_vl rc={rc}").into());
3304 }
3305 } else {
3306 let fargs: Vec<crate::FaSeqVl> = (0..b)
3307 .map(|s| crate::FaSeqVl {
3308 q: e.addr_f32(&aps[s].qn),
3309 k16: e.addr_u8(&mirrors[s].0),
3310 v16: e.addr_u8(&mirrors[s].1),
3311 o: e.addr_f32(&attns[s]),
3312 kf: e.addr_f32(&aps[s].kn),
3313 vf: e.addr_f32v(
3314 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
3315 ),
3316 t: ts[s] as i32,
3317 pad: 0,
3318 })
3319 .collect();
3320 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
3321 }
3322 for (s, attn) in attns.into_iter().enumerate() {
3323 let (attn_g, ag16) = self.full_attn_prime_post_fa(
3324 e,
3325 attn,
3326 &aps[s].gate,
3327 ts[s],
3328 n_head,
3329 head_dim,
3330 )?;
3331 let mut done = false;
3332 if let Some(xh) = &ag16 {
3333 done = e.try_f16_gemm_pre_into_off(
3334 &fa.wo,
3335 xh,
3336 ts[s],
3337 &mut mixed,
3338 offs[s] * n_embd,
3339 )?;
3340 }
3341 if !done {
3342 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3343 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3344 }
3345 }
3346 } else {
3347 let mut parts: Vec<Vec<CudaSlice<f32>>> =
3348 (0..b).map(|_| Vec::new()).collect();
3349 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
3350 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
3351 parts[s].push(ys);
3352 }
3353 }
3354 for (s, g3s) in parts.into_iter().enumerate() {
3355 let (attn_g, ag16) = self.full_attn_prime_core_inner(
3357 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
3358 )?;
3359 let mut done = false;
3360 if let Some(xh) = &ag16 {
3361 done = e.try_f16_gemm_pre_into_off(
3362 &fa.wo,
3363 xh,
3364 ts[s],
3365 &mut mixed,
3366 offs[s] * n_embd,
3367 )?;
3368 }
3369 if !done {
3370 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3371 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3372 }
3373 }
3374 }
3375 }
3376 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
3377 Mixer::Linear(la) => {
3378 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3383 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
3384 let outs =
3385 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
3386 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
3387 let (o, t) = (offs[s], ts[s]);
3388 let mut done = false;
3389 if let Some(xh) = &gn16 {
3390 done = e.try_f16_gemm_pre_into_off(
3391 &la.ssm_out,
3392 xh,
3393 t,
3394 &mut mixed,
3395 o * n_embd,
3396 )?;
3397 }
3398 if !done {
3399 let m = e.matmul(&la.ssm_out, &gn, t)?;
3400 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
3401 }
3402 }
3403 }
3404 }
3405 let mut x1 = e.uninit(total * n_embd)?;
3406 let mut z = e.uninit(total * n_embd)?;
3407 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3408 e.add_rms_norm_f16out(
3409 &x,
3410 &mixed,
3411 layer.post_attn_norm.float_data(),
3412 &mut x1,
3413 &mut z,
3414 &mut zx16,
3415 n_embd,
3416 total,
3417 eps,
3418 )?;
3419 let ffn_out = match &layer.ffn {
3420 crate::hybrid::Ffn::Dense {
3421 ffn_gate,
3422 ffn_up,
3423 ffn_down,
3424 } => {
3425 let n_ff = ffn_gate.out_features();
3426 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
3427 let up = g2.pop().unwrap();
3428 let gate = g2.pop().unwrap();
3429 let mut act = e.uninit(total * n_ff)?;
3430 let d_lim = self.cfg.clamp_shexp_at(il as u32);
3434 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
3435 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
3436 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
3437 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
3438 Some(y) => y,
3439 None => e.matmul(ffn_down, &act, total)?,
3440 }
3441 } else {
3442 Self::ffn_act_lim(
3443 e,
3444 &self.cfg,
3445 &gate,
3446 &up,
3447 1.0,
3448 1.0,
3449 d_lim,
3450 &mut act,
3451 total * n_ff,
3452 )?;
3453 e.matmul(ffn_down, &act, total)?
3454 }
3455 }
3456 crate::hybrid::Ffn::Moe(m) => {
3457 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
3458 }
3459 };
3460 let mut x2 = e.uninit(total * n_embd)?;
3461 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
3462 x = x2;
3463 }
3464 let mut hn = e.uninit(total * n_embd)?;
3466 e.rms_norm(
3467 &x,
3468 self.output_norm.float_data(),
3469 &mut hn,
3470 n_embd,
3471 total,
3472 eps,
3473 )?;
3474 let mut hcat = e.uninit(b * n_embd)?;
3480 for s in 0..b {
3481 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3482 e.copy_view_into(
3483 &mut hcat,
3484 s * n_embd,
3485 &hn.slice(last0..last0 + n_embd),
3486 n_embd,
3487 )?;
3488 }
3489 let logits_cat = if b >= 2 {
3490 e.try_f16_gemm(&self.output, &hcat, b)?
3491 } else {
3492 None
3493 };
3494 let logits_host: Option<Vec<f32>> = match &logits_cat {
3495 Some(lc) => Some(e.dtoh(lc)?),
3496 None => None,
3497 };
3498 let n_vocab = self.output.out_features();
3499 let mut hidden_all = if crate::spec::spec_hpost() {
3500 split(e, &hn, n_embd)?
3501 } else {
3502 split(e, &x, n_embd)?
3503 };
3504 let mut out = Vec::with_capacity(b);
3505 for s in 0..b {
3506 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3507 let mut h_seed = e.uninit(n_embd)?;
3508 if !crate::spec::spec_hpost() {
3509 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
3510 } else {
3511 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3512 }
3513 let logits = match &logits_host {
3514 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
3515 None => {
3516 let mut hlast = e.uninit(n_embd)?;
3517 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3518 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
3519 }
3520 };
3521 caches[s].pos += ts[s];
3522 out.push((logits, h_seed, hidden_all.remove(0)));
3523 }
3524 Ok(out)
3525 }
3526
3527 #[allow(clippy::too_many_arguments)]
3538 fn full_attn_prime(
3539 &self,
3540 e: &Engine,
3541 fa: &FullAttnLayer,
3542 h: &CudaSlice<f32>,
3543 hx: Option<&CudaSlice<u8>>,
3544 pos_d: &CudaSlice<i32>,
3545 t: usize,
3546 cache: &mut Cache,
3547 il: usize,
3548 seq_end: usize,
3549 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3550 if self.uses_sliding_gated_moe_program() {
3551 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
3552 }
3553 let g3 = match hx {
3558 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
3559 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
3560 };
3561 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
3562 }
3563
3564 fn full_attn_prime_core(
3568 &self,
3569 e: &Engine,
3570 fa: &FullAttnLayer,
3571 g3: Vec<CudaSlice<f32>>,
3572 pos_d: &CudaSlice<i32>,
3573 t: usize,
3574 cache: &mut Cache,
3575 il: usize,
3576 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3577 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
3578 if let Some(xh) = &ag16 {
3579 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
3580 return Ok(y);
3581 }
3582 }
3583 Ok(e.matmul(&fa.wo, &attn_g, t)?)
3584 }
3585
3586 fn full_attn_prime_core_inner(
3587 &self,
3588 e: &Engine,
3589 fa: &FullAttnLayer,
3590 g3: Vec<CudaSlice<f32>>,
3591 pos_d: &CudaSlice<i32>,
3592 t: usize,
3593 cache: &mut Cache,
3594 il: usize,
3595 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3596 let cfg = &self.cfg;
3597 let geometry = cfg.full_attention_geometry_at(il as u32);
3598 let n_head = geometry.n_head as usize;
3599 let n_head_kv = geometry.n_head_kv as usize;
3600 let head_dim = geometry.head_dim_k as usize;
3601 let scale = geometry.attention_scale();
3602 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
3603 let AttnPre { q, k, v, gate } = pre;
3604 let mut attn = e.uninit(t * n_head * head_dim)?;
3605 self.full_attn_prime_fa_dispatch(
3606 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
3607 )?;
3608 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
3609 }
3610
3611 #[allow(clippy::type_complexity)]
3615 fn full_attn_prime_pre_fa(
3616 &self,
3617 e: &Engine,
3618 fa: &FullAttnLayer,
3619 mut g3: Vec<CudaSlice<f32>>,
3620 pos_d: &CudaSlice<i32>,
3621 t: usize,
3622 cache: &mut Cache,
3623 il: usize,
3624 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3625 let cfg = &self.cfg;
3626 let geometry = cfg.full_attention_geometry_at(il as u32);
3627 let n_head = geometry.n_head as usize;
3628 let n_head_kv = geometry.n_head_kv as usize;
3629 let head_dim = geometry.head_dim_k as usize;
3630 let eps = cfg.rms_eps;
3631
3632 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3636 let v = g3.pop().unwrap();
3637 let mut k = g3.pop().unwrap();
3638 let qf = g3.pop().unwrap();
3639 let (mut q, gate) = if gated {
3640 let mut q = e.uninit(t * n_head * head_dim)?;
3641 let mut gate = e.uninit(t * n_head * head_dim)?;
3642 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3643 (q, Some(gate))
3644 } else {
3645 (qf, None)
3646 };
3647
3648 let mut qn = e.uninit(t * n_head * head_dim)?;
3649 e.rms_norm(
3650 &q,
3651 fa.q_norm.float_data(),
3652 &mut qn,
3653 head_dim,
3654 n_head * t,
3655 eps,
3656 )?;
3657 q = qn;
3658 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3659 e.rms_norm(
3660 &k,
3661 fa.k_norm.float_data(),
3662 &mut kn,
3663 head_dim,
3664 n_head_kv * t,
3665 eps,
3666 )?;
3667 k = kn;
3668 let rope_dims = geometry.n_rot as usize;
3669 e.rope_neox(
3670 &mut q,
3671 pos_d,
3672 head_dim,
3673 rope_dims,
3674 n_head,
3675 t,
3676 geometry.rope_base,
3677 1.0,
3678 )?;
3679 e.rope_neox(
3680 &mut k,
3681 pos_d,
3682 head_dim,
3683 rope_dims,
3684 n_head_kv,
3685 t,
3686 geometry.rope_base,
3687 1.0,
3688 )?;
3689
3690 {
3693 let kvl = cache.kv[il].as_mut().unwrap();
3694 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3695 e.append_kv_quantized_rows(
3696 &k,
3697 &v,
3698 &mut kvl.k,
3699 &mut kvl.v,
3700 kvl.len,
3701 t,
3702 kvl.kv_dim_k,
3703 kvl.kv_dim_v,
3704 kvl.k_tok_bytes,
3705 kvl.v_tok_bytes,
3706 crate::Engine::kv_fp8_on(),
3707 )?;
3708 kvl.len += t;
3709 let new_len = kvl.len as i32;
3710 e.set_i32_one(&mut kvl.len_d, new_len)?;
3711 }
3712
3713 let base_len = {
3714 let kvl = cache.kv[il].as_ref().unwrap();
3715 kvl.len - t };
3717 Ok((AttnPre { q, k, v, gate }, base_len))
3718 }
3719
3720 #[allow(clippy::too_many_arguments)]
3727 fn full_attn_prime_fa_dispatch(
3728 &self,
3729 e: &Engine,
3730 q: &CudaSlice<f32>,
3731 k: &CudaSlice<f32>,
3732 v: &CudaSlice<f32>,
3733 attn: &mut CudaSlice<f32>,
3734 base_len: usize,
3735 t: usize,
3736 cache: &mut Cache,
3737 il: usize,
3738 head_dim: usize,
3739 n_head: usize,
3740 n_head_kv: usize,
3741 scale: f32,
3742 ) -> Result<(), Box<dyn std::error::Error>> {
3743 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
3756 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3757 e.sdpa_naive(
3758 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3759 )?;
3760 } else {
3761 e.fa_prefill(
3762 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3763 )?;
3764 }
3765 return Ok(());
3766 }
3767 let kvl = cache.kv[il].as_ref().unwrap();
3768 let t_kv = base_len + t;
3769 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
3770 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
3771 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3775 e.sdpa_naive_quantized_view(
3776 q,
3777 &k_view,
3778 &v_view,
3779 attn,
3780 head_dim,
3781 n_head,
3782 n_head_kv,
3783 t,
3784 t_kv,
3785 scale,
3786 true,
3787 kvl.k_tok_bytes,
3788 kvl.v_tok_bytes,
3789 )?;
3790 return Ok(());
3791 }
3792 let deqw = std::env::var("MEMRA_PRIME_DEQW")
3800 .map(|v| v != "0")
3801 .unwrap_or(true);
3802 if deqw {
3803 e.fa_prefill_view_ws(
3804 q,
3805 &k_view,
3806 &v_view,
3807 attn,
3808 head_dim,
3809 n_head,
3810 n_head_kv,
3811 t,
3812 t_kv,
3813 scale,
3814 true,
3815 kvl.k_tok_bytes,
3816 kvl.v_tok_bytes,
3817 crate::Engine::kv_fp8_on(),
3818 )?;
3819 } else {
3820 e.fa_prefill_view(
3821 q,
3822 &k_view,
3823 &v_view,
3824 attn,
3825 head_dim,
3826 n_head,
3827 n_head_kv,
3828 t,
3829 t_kv,
3830 scale,
3831 true,
3832 kvl.k_tok_bytes,
3833 kvl.v_tok_bytes,
3834 crate::Engine::kv_fp8_on(),
3835 )?;
3836 }
3837 Ok(())
3838 }
3839
3840 fn full_attn_prime_post_fa(
3843 &self,
3844 e: &Engine,
3845 attn: CudaSlice<f32>,
3846 gate: &Option<CudaSlice<f32>>,
3847 t: usize,
3848 n_head: usize,
3849 head_dim: usize,
3850 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3851 let (attn_g, ag16) = match gate {
3852 Some(gate) => {
3853 let n = t * n_head * head_dim;
3854 let mut ag = e.uninit(n)?;
3855 if Self::f16out_on(e, t) {
3856 let mut a16 = e.alloc_u8_uninit(n * 2)?;
3857 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
3858 (ag, Some(a16))
3859 } else {
3860 let mut gsig = e.uninit(n)?;
3861 e.sigmoid(gate, &mut gsig, n)?;
3862 e.mul(&attn, &gsig, &mut ag, n)?;
3863 (ag, None)
3864 }
3865 }
3866 None => (attn, None),
3867 };
3868 Ok((attn_g, ag16))
3869 }
3870
3871 fn linear_attn_prime(
3878 &self,
3879 e: &Engine,
3880 la: &LinearAttnLayer,
3881 h: &CudaSlice<f32>,
3882 hx: Option<&CudaSlice<u8>>,
3883 t: usize,
3884 cache: &mut Cache,
3885 il: usize,
3886 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3887 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3889 let g4 = match hx {
3890 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
3891 None => e.matmul_group(&ws, h, t)?,
3892 };
3893 self.linear_attn_prime_core(e, la, g4, t, cache, il)
3894 }
3895
3896 fn linear_attn_prime_core(
3898 &self,
3899 e: &Engine,
3900 la: &LinearAttnLayer,
3901 mut g4: Vec<CudaSlice<f32>>,
3902 t: usize,
3903 cache: &mut Cache,
3904 il: usize,
3905 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3906 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
3907 }
3908
3909 #[allow(clippy::too_many_arguments)]
3913 fn linear_attn_prime_core_pad_inner(
3914 &self,
3915 e: &Engine,
3916 la: &LinearAttnLayer,
3917 mut g4: Vec<CudaSlice<f32>>,
3918 t: usize,
3919 cache: &mut Cache,
3920 il: usize,
3921 pad_len: Option<&CudaSlice<i32>>,
3922 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3923 let geometry = la.geometry;
3925 let d_state = geometry.key_head_dim as usize;
3926 let num_k = geometry.key_heads as usize;
3927 let num_v = geometry.value_heads as usize;
3928 let key_dim = d_state * num_k;
3929 let value_dim = geometry.value_head_dim as usize * num_v;
3930 let conv_dim = key_dim * 2 + value_dim;
3931 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(
3936 e,
3937 la,
3938 &qkv_mixed.slice(0..t * conv_dim),
3939 &z.slice(0..t * value_dim),
3940 &beta_raw.slice(0..t * num_v),
3941 &alpha.slice(0..t * num_v),
3942 t,
3943 cache,
3944 il,
3945 pad_len,
3946 )
3947 }
3948
3949 #[allow(clippy::too_many_arguments)]
3952 fn linear_attn_gdn_prep(
3953 &self,
3954 e: &Engine,
3955 la: &LinearAttnLayer,
3956 qkv_mixed: &cudarc::driver::CudaView<f32>,
3957 beta_raw: &cudarc::driver::CudaView<f32>,
3958 alpha: &cudarc::driver::CudaView<f32>,
3959 t: usize,
3960 cache: &mut Cache,
3961 il: usize,
3962 pad_len: Option<&CudaSlice<i32>>,
3963 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
3964 let cfg = &self.cfg;
3965 let geometry = la.geometry;
3966 let d_state = geometry.key_head_dim as usize;
3967 let num_k = geometry.key_heads as usize;
3968 let num_v = geometry.value_heads as usize;
3969 let d_conv = geometry.conv_kernel as usize;
3970 let key_dim = d_state * num_k; let value_dim = geometry.value_head_dim as usize * num_v;
3972 let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
3974 debug_assert!(
3975 t >= d_conv - 1,
3976 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
3977 );
3978
3979 let rl = cache.recur[il].as_mut().unwrap();
3984 let hk = Self::gdn_hk(e, t, num_v, num_k);
3985 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
3986 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
3988 let mut k_g = e.uninit(d_state * hk * t)?;
3989 let mut v_g = e.uninit(d_state * num_v * t)?;
3990 if conv_fuse {
3991 e.ssm_conv1d_gdn_state_pad(
3992 qkv_mixed,
3993 &mut rl.conv_state,
3994 la.ssm_conv1d.float_data(),
3995 &mut q_g,
3996 &mut k_g,
3997 &mut v_g,
3998 conv_dim,
3999 t,
4000 d_conv,
4001 d_state,
4002 num_v,
4003 num_k,
4004 key_dim,
4005 hk,
4006 pad_len,
4007 )?;
4008 } else {
4009 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
4011 qkv_mixed,
4012 &mut rl.conv_state,
4013 la.ssm_conv1d.float_data(),
4014 &mut conv_out,
4015 conv_dim,
4016 t,
4017 d_conv,
4018 pad_len,
4019 )?;
4020 e.qkv_to_gdn_repack(
4021 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
4022 )?;
4023 }
4024 let mut q_l2 = e.uninit(d_state * hk * t)?;
4025 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
4029 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4030 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
4031 Some(qb)
4032 } else {
4033 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
4034 None
4035 };
4036 let mut k_l2 = e.uninit(d_state * hk * t)?;
4037 let kb16 = if Engine::l2_v2_on(d_state) {
4039 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4040 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
4041 Some(kb)
4042 } else {
4043 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
4044 None
4045 };
4046 let mut beta = e.uninit(t * num_v)?;
4047 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
4048 let mut g_log = e.uninit(t * num_v)?;
4049 e.gdn_glog_v(
4050 alpha,
4051 la.ssm_dt.float_data(),
4052 la.ssm_a.float_data(),
4053 &mut g_log,
4054 num_v,
4055 t,
4056 )?;
4057 if let Some(len_d) = pad_len {
4058 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
4059 }
4060 Ok(GdnPrep {
4061 hk,
4062 q_l2,
4063 k_l2,
4064 v_g,
4065 beta,
4066 g_log,
4067 kb16,
4068 qb16,
4069 })
4070 }
4071
4072 #[allow(clippy::too_many_arguments)]
4077 fn linear_attn_prime_core_batch(
4078 &self,
4079 e: &Engine,
4080 la: &LinearAttnLayer,
4081 g4: &[CudaSlice<f32>],
4082 offs: &[usize],
4083 ts: &[usize],
4084 caches: &mut [&mut Cache],
4085 il: usize,
4086 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
4087 let geometry = la.geometry;
4088 let d_state = geometry.key_head_dim as usize;
4089 let num_k = geometry.key_heads as usize;
4090 let num_v = geometry.value_heads as usize;
4091 let d_conv = geometry.conv_kernel as usize;
4092 let key_dim = d_state * num_k;
4093 let value_dim = geometry.value_head_dim as usize * num_v;
4094 let conv_dim = key_dim * 2 + value_dim;
4095 let eps = self.cfg.rms_eps;
4096 let scale = 1.0 / (d_state as f32).sqrt();
4097 let b = ts.len();
4098 let c = Engine::gdn_chunk_size();
4099 let carried = caches.iter().any(|c| c.pos > 0);
4102 let use_vl = !carried
4103 && (2..=8).contains(&b)
4104 && Engine::gdn_chunked_enabled()
4105 && ts.iter().all(|&t| t >= 16)
4106 && e.gdn_mma_enabled(c)
4107 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
4108 if !use_vl {
4109 return (0..b)
4110 .map(|s| {
4111 let (o, t) = (offs[s], ts[s]);
4112 self.linear_attn_prime_core_pad_view(
4113 e,
4114 la,
4115 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
4116 &g4[1].slice(o * value_dim..(o + t) * value_dim),
4117 &g4[2].slice(o * num_v..(o + t) * num_v),
4118 &g4[3].slice(o * num_v..(o + t) * num_v),
4119 t,
4120 caches[s],
4121 il,
4122 None,
4123 )
4124 })
4125 .collect();
4126 }
4127 struct SeqBufs {
4131 conv_out: CudaSlice<f32>,
4132 q_g: CudaSlice<f32>,
4133 k_g: CudaSlice<f32>,
4134 v_g: CudaSlice<f32>,
4135 q_l2: CudaSlice<f32>,
4136 k_l2: CudaSlice<f32>,
4137 beta: CudaSlice<f32>,
4138 g_log: CudaSlice<f32>,
4139 gn: CudaSlice<f32>,
4140 gn16: CudaSlice<u8>,
4141 }
4142 let f16o = Self::f16out_on(e, 16);
4143 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
4145 let mut pres = Vec::with_capacity(b);
4146 for &t in ts.iter().take(b) {
4147 sb.push(SeqBufs {
4148 conv_out: e.uninit(conv_dim * t)?,
4149 q_g: e.uninit(d_state * hk * t)?,
4150 k_g: e.uninit(d_state * hk * t)?,
4151 v_g: e.uninit(d_state * num_v * t)?,
4152 q_l2: e.uninit(d_state * hk * t)?,
4153 k_l2: e.uninit(d_state * hk * t)?,
4154 beta: e.uninit(t * num_v)?,
4155 g_log: e.uninit(t * num_v)?,
4156 gn: e.uninit(d_state * num_v * t)?,
4157 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
4158 });
4159 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
4160 }
4161 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
4162 .map(|s| {
4163 let (o, t) = (offs[s], ts[s]);
4164 let rl = caches[s].recur[il].as_ref().unwrap();
4165 crate::GdnPrepVl {
4166 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
4167 conv_state: e.addr_f32(&rl.conv_state),
4168 conv_out: e.addr_f32(&sb[s].conv_out),
4169 q_g: e.addr_f32(&sb[s].q_g),
4170 k_g: e.addr_f32(&sb[s].k_g),
4171 v_g: e.addr_f32(&sb[s].v_g),
4172 q_l2: e.addr_f32(&sb[s].q_l2),
4173 k_l2: e.addr_f32(&sb[s].k_l2),
4174 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
4175 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
4176 beta: e.addr_f32(&sb[s].beta),
4177 g_log: e.addr_f32(&sb[s].g_log),
4178 o: e.addr_f32(&pres[s].o),
4179 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
4180 gn: e.addr_f32(&sb[s].gn),
4181 gn16: e.addr_u8(&sb[s].gn16),
4182 kb16: if Engine::l2_v2_on(d_state) {
4183 e.addr_u8(&pres[s].kb16)
4184 } else {
4185 0
4186 },
4187 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
4188 e.addr_u8(&pres[s].qb16)
4189 } else {
4190 0
4191 },
4192 t: t as i32,
4193 pad: 0,
4194 }
4195 })
4196 .collect();
4197 let args: Vec<crate::GdnSeqVl> = (0..b)
4198 .map(|s| {
4199 let rl = caches[s].recur[il].as_ref().unwrap();
4200 crate::GdnSeqVl {
4201 kb16: e.addr_u8(&pres[s].kb16),
4202 gcum: e.addr_f32(&pres[s].gcum),
4203 beta: e.addr_f32(&sb[s].beta),
4204 u: e.addr_f32(&pres[s].u),
4205 wb16: e.addr_u8(&pres[s].wb16),
4206 y: e.addr_u8(&pres[s].y16),
4207 ssnap: e.addr_u8(&pres[s].ssnap16),
4208 state_in: e.addr_f32(&rl.ssm_state),
4209 state_out: e.addr_f32(&rl.ssm_state_alt),
4210 q: e.addr_f32(&sb[s].q_l2),
4211 p: e.addr_f32(&pres[s].p),
4212 o: e.addr_f32(&pres[s].o),
4213 k: e.addr_f32(&sb[s].k_l2),
4214 v: e.addr_f32(&sb[s].v_g),
4215 g: e.addr_f32(&sb[s].g_log),
4216 a: e.addr_f32(&pres[s].a),
4217 w: e.addr_f32(&pres[s].w),
4218 t: ts[s] as i32,
4219 nc: pres[s].nc as i32,
4220 }
4221 })
4222 .collect();
4223 e.gdn_prep_vl8(
4224 &prep_args,
4225 la.ssm_conv1d.float_data(),
4226 la.ssm_dt.float_data(),
4227 la.ssm_a.float_data(),
4228 conv_dim,
4229 d_conv,
4230 d_state,
4231 num_v,
4232 num_k,
4233 key_dim,
4234 hk,
4235 eps,
4236 )?;
4237 if !Engine::l2_v2_on(d_state) {
4240 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
4241 }
4242 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
4244 if !Engine::l2_v2_on(d_state) {
4246 for s in 0..b {
4247 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
4248 }
4249 }
4250 let mut wa = [crate::GdnWVl::default(); 8];
4251 for s in 0..b {
4252 wa[s] = crate::GdnWVl {
4253 qb16: e.addr_u8(&pres[s].qb16),
4254 pb16: e.addr_u8(&pres[s].pb16),
4255 };
4256 }
4257 Some(crate::GdnWVl8(wa))
4258 } else {
4259 None
4260 };
4261 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
4262 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
4263 if f16o {
4264 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
4265 }
4266 let mut out = Vec::with_capacity(b);
4268 for (s, bufs) in sb.into_iter().enumerate() {
4269 let rl = caches[s].recur[il].as_mut().unwrap();
4270 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4271 let (o, t) = (offs[s], ts[s]);
4272 let SeqBufs { mut gn, gn16, .. } = bufs;
4273 if f16o {
4274 out.push((gn, Some(gn16)));
4275 } else {
4276 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
4277 e.gated_rmsnorm_zv(
4278 &pres[s].o,
4279 la.ssm_norm.float_data(),
4280 &z_v,
4281 &mut gn,
4282 d_state,
4283 num_v * t,
4284 eps,
4285 )?;
4286 out.push((gn, None));
4287 }
4288 }
4289 Ok(out)
4290 }
4291
4292 #[allow(clippy::too_many_arguments)]
4296 fn linear_attn_prime_core_pad_view(
4297 &self,
4298 e: &Engine,
4299 la: &LinearAttnLayer,
4300 qkv_mixed: &cudarc::driver::CudaView<f32>,
4301 z: &cudarc::driver::CudaView<f32>,
4302 beta_raw: &cudarc::driver::CudaView<f32>,
4303 alpha: &cudarc::driver::CudaView<f32>,
4304 t: usize,
4305 cache: &mut Cache,
4306 il: usize,
4307 pad_len: Option<&CudaSlice<i32>>,
4308 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4309 let cfg = &self.cfg;
4310 let geometry = la.geometry;
4311 let d_state = geometry.key_head_dim as usize;
4312 let num_v = geometry.value_heads as usize;
4313 let eps = cfg.rms_eps;
4314 let scale = 1.0 / (d_state as f32).sqrt();
4315
4316 let prep =
4317 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
4318
4319 let mut o = e.uninit(d_state * num_v * t)?;
4325 let rl = cache.recur[il].as_mut().unwrap();
4326 {
4327 let crate::cache::RecurLayer {
4328 ssm_state,
4329 ssm_state_alt,
4330 ..
4331 } = rl;
4332 e.gdn_scan_prefill(
4333 &prep.q_l2,
4334 &prep.k_l2,
4335 &prep.v_g,
4336 &prep.g_log,
4337 &prep.beta,
4338 prep.kb16.as_ref(),
4339 prep.qb16.as_ref(),
4340 ssm_state,
4341 ssm_state_alt,
4342 &mut o,
4343 num_v,
4344 t,
4345 scale,
4346 prep.hk,
4347 )?;
4348 }
4349 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4350
4351 let mut gn = e.uninit(d_state * num_v * t)?;
4354 let gn16 = if Self::f16out_on(e, t) {
4355 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
4356 e.gated_rmsnorm_f16out_zv(
4357 &o,
4358 la.ssm_norm.float_data(),
4359 z,
4360 &mut gn,
4361 &mut g16,
4362 d_state,
4363 num_v * t,
4364 eps,
4365 )?;
4366 Some(g16)
4367 } else {
4368 e.gated_rmsnorm_zv(
4369 &o,
4370 la.ssm_norm.float_data(),
4371 z,
4372 &mut gn,
4373 d_state,
4374 num_v * t,
4375 eps,
4376 )?;
4377 None
4378 };
4379 Ok((gn, gn16))
4380 }
4381
4382 #[allow(clippy::too_many_arguments)]
4384 fn linear_attn_prime_core_pad(
4385 &self,
4386 e: &Engine,
4387 la: &LinearAttnLayer,
4388 g4: Vec<CudaSlice<f32>>,
4389 t: usize,
4390 cache: &mut Cache,
4391 il: usize,
4392 pad_len: Option<&CudaSlice<i32>>,
4393 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4394 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
4395 if let Some(xh) = &gn16 {
4396 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
4397 return Ok(y);
4398 }
4399 }
4400 Ok(e.matmul(&la.ssm_out, &gn, t)?)
4401 }
4402
4403 pub fn full_attn(
4408 &self,
4409 e: &Engine,
4410 fa: &FullAttnLayer,
4411 h: &CudaSlice<f32>,
4412 pos_d: &CudaSlice<i32>,
4413 t: usize,
4414 il: usize,
4415 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4416 if self.uses_sliding_gated_moe_program() {
4417 return self.step35_attn(e, fa, h, pos_d, t, il);
4418 }
4419 let cfg = &self.cfg;
4420 let _n_embd = cfg.n_embd as usize;
4421 let geometry = cfg.full_attention_geometry_at(il as u32);
4422 let n_head = geometry.n_head as usize;
4423 let n_head_kv = geometry.n_head_kv as usize;
4424 let head_dim = geometry.head_dim_k as usize;
4425 let eps = cfg.rms_eps;
4426 let scale = geometry.attention_scale();
4427
4428 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
4431 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
4433 let v = g3.pop().unwrap();
4434 let mut k = g3.pop().unwrap();
4435 let qf = g3.pop().unwrap();
4436 let (mut q, gate) = if gated {
4437 let mut q = e.uninit(t * n_head * head_dim)?;
4438 let mut gate = e.uninit(t * n_head * head_dim)?;
4439 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
4440 (q, Some(gate))
4441 } else {
4442 (qf, None)
4443 };
4444
4445 let mut qn = e.uninit(t * n_head * head_dim)?;
4447 e.rms_norm(
4448 &q,
4449 fa.q_norm.float_data(),
4450 &mut qn,
4451 head_dim,
4452 n_head * t,
4453 eps,
4454 )?;
4455 q = qn;
4456 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
4457 e.rms_norm(
4458 &k,
4459 fa.k_norm.float_data(),
4460 &mut kn,
4461 head_dim,
4462 n_head_kv * t,
4463 eps,
4464 )?;
4465 k = kn;
4466 let rope_dims = geometry.n_rot as usize;
4467 e.rope_neox(
4468 &mut q,
4469 pos_d,
4470 head_dim,
4471 rope_dims,
4472 n_head,
4473 t,
4474 geometry.rope_base,
4475 1.0,
4476 )?;
4477 e.rope_neox(
4478 &mut k,
4479 pos_d,
4480 head_dim,
4481 rope_dims,
4482 n_head_kv,
4483 t,
4484 geometry.rope_base,
4485 1.0,
4486 )?;
4487
4488 let mut attn = e.uninit(t * n_head * head_dim)?;
4490 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4493 e.sdpa_naive(
4495 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4496 )?;
4497 } else {
4498 e.fa_prefill(
4499 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4500 )?;
4501 }
4502
4503 let attn_g = match &gate {
4505 Some(gate) => {
4506 let mut gsig = e.uninit(t * n_head * head_dim)?;
4507 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
4508 let mut ag = e.uninit(t * n_head * head_dim)?;
4509 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
4510 ag
4511 }
4512 None => attn,
4513 };
4514
4515 let o = e.matmul(&fa.wo, &attn_g, t)?;
4517 Ok(o)
4518 }
4519
4520 pub fn linear_attn(
4522 &self,
4523 e: &Engine,
4524 la: &LinearAttnLayer,
4525 h: &CudaSlice<f32>,
4526 t: usize,
4527 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4528 let cfg = &self.cfg;
4529 let _n_embd = cfg.n_embd as usize;
4530 let geometry = la.geometry;
4531 let d_state = geometry.key_head_dim as usize;
4532 let num_k = geometry.key_heads as usize;
4533 let num_v = geometry.value_heads as usize;
4534 let d_conv = geometry.conv_kernel as usize;
4535 let head_k = d_state;
4536 let head_v = geometry.value_head_dim as usize;
4537 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;
4541 let scale = 1.0 / (d_state as f32).sqrt();
4542
4543 let mut g4 = e.matmul_group(
4546 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
4547 h,
4548 t,
4549 )?;
4550 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);
4562 let mut q_g = e.uninit(d_state * num_v * t)?;
4563 let mut k_g = e.uninit(d_state * num_v * t)?;
4564 let mut v_g = e.uninit(d_state * num_v * t)?;
4565 e.ssm_conv1d_gdn(
4566 &qkv_mixed,
4567 la.ssm_conv1d.float_data(),
4568 &mut q_g,
4569 &mut k_g,
4570 &mut v_g,
4571 conv_dim,
4572 t,
4573 d_conv,
4574 d_state,
4575 num_v,
4576 num_k,
4577 key_dim,
4578 )?;
4579 let mut q_l2 = e.uninit(d_state * num_v * t)?;
4581 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
4582 let mut k_l2 = e.uninit(d_state * num_v * t)?;
4583 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
4584 let v_gd = v_g;
4585
4586 let mut beta = e.uninit(t * num_v)?;
4589 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
4590 let mut g_log = e.uninit(t * num_v)?;
4592 e.gdn_glog(
4593 &alpha,
4594 la.ssm_dt.float_data(),
4595 la.ssm_a.float_data(),
4596 &mut g_log,
4597 num_v,
4598 t,
4599 )?;
4600
4601 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
4604 let mut o = e.uninit(d_state * num_v * t)?;
4605 e.gdn_scan_prefill(
4606 &q_l2,
4607 &k_l2,
4608 &v_gd,
4609 &g_log,
4610 &beta,
4611 None,
4612 None,
4613 &state_in,
4614 &mut state_out,
4615 &mut o,
4616 num_v,
4617 t,
4618 scale,
4619 num_v,
4620 )?;
4621
4622 let mut gn = e.uninit(d_state * num_v * t)?;
4627 e.gated_rmsnorm(
4628 &o,
4629 la.ssm_norm.float_data(),
4630 &z,
4631 &mut gn,
4632 d_state,
4633 num_v * t,
4634 eps,
4635 )?;
4636
4637 let out = e.matmul(&la.ssm_out, &gn, t)?;
4641 Ok(out)
4642 }
4643}
4644
4645impl HybridModel {
4646 pub fn moe_ffn_il(
4657 &self,
4658 e: &Engine,
4659 m: &MoeWeights,
4660 z: &CudaSlice<f32>,
4661 t: usize,
4662 il: u16,
4663 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4664 Self::moe_ffn_inner(
4665 e,
4666 m,
4667 z,
4668 None,
4669 t,
4670 &self.cfg,
4671 il,
4672 self.max_moe_block(),
4673 false,
4674 None,
4675 self.uses_sliding_gated_moe_program(),
4676 )
4677 }
4678
4679 pub fn moe_ffn_il_prefill(
4682 &self,
4683 e: &Engine,
4684 m: &MoeWeights,
4685 z: &CudaSlice<f32>,
4686 t: usize,
4687 il: u16,
4688 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4689 Self::moe_ffn_inner(
4690 e,
4691 m,
4692 z,
4693 None,
4694 t,
4695 &self.cfg,
4696 il,
4697 self.max_moe_block(),
4698 true,
4699 Some(&self.step_grouped_prefill),
4700 self.uses_sliding_gated_moe_program(),
4701 )
4702 }
4703
4704 pub fn moe_ffn_il_zq8(
4708 &self,
4709 e: &Engine,
4710 m: &MoeWeights,
4711 z: &CudaSlice<f32>,
4712 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4713 t: usize,
4714 il: u16,
4715 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4716 Self::moe_ffn_inner(
4717 e,
4718 m,
4719 z,
4720 zq8,
4721 t,
4722 &self.cfg,
4723 il,
4724 self.max_moe_block(),
4725 false,
4726 None,
4727 self.uses_sliding_gated_moe_program(),
4728 )
4729 }
4730
4731 pub(crate) fn moe_ffn(
4739 e: &Engine,
4740 m: &MoeWeights,
4741 z: &CudaSlice<f32>,
4742 t: usize,
4743 cfg: &ModelConfig,
4744 il: u16,
4745 max_block: usize,
4746 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4747 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false, None, false)
4748 }
4749
4750 #[allow(clippy::too_many_arguments)]
4751 pub(crate) fn moe_ffn_inner(
4752 e: &Engine,
4753 m: &MoeWeights,
4754 z: &CudaSlice<f32>,
4755 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4756 t: usize,
4757 cfg: &ModelConfig,
4758 il: u16,
4759 max_block: usize,
4760 prefill: bool,
4761 grouped_prefill: Option<&std::sync::Mutex<crate::hybrid::StepEpGroupedPrefill>>,
4762 sliding_gated_moe: bool,
4763 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4764 let worker_io = crate::spill_pread::worker_enabled();
4765 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
4766 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
4767 e.with_moe_cache(max_block, |cache, _| {
4768 cache.begin_forward_epoch(il, t);
4769 if worker_io {
4770 cache.begin_worker_scope();
4771 }
4772 Ok(())
4773 })?;
4774 }
4775 if m.step_ep.is_some() || m.step_tp.is_some() {
4776 let moe = cfg
4777 .moe
4778 .as_ref()
4779 .ok_or("Step distributed execution requires MoE model metadata")?;
4780 let n_embd = cfg.n_embd as usize;
4781 let n_expert = moe.expert_count as usize;
4782 let n_used = moe.expert_used_count as usize;
4783 let sigmoid = cfg
4784 .sigmoid_router()
4785 .ok_or("Step distributed execution requires the Step sigmoid router")?;
4786 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4787 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
4788 let grouped_prefill_requested = prefill && step_ep_grouped_prefill_enabled()?;
4789 if grouped_prefill_requested && !step_tp_prefill_enabled()? {
4790 return Err(
4791 "MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into(),
4792 );
4793 }
4794 if grouped_prefill_requested && !step_grouped_prefill_shape(true, prefill, t) {
4795 return Err(format!(
4796 "Step grouped prefill tokens {t} are outside the qualified {}..={} range",
4797 PRIME_MIN_T,
4798 crate::cache::PRIME_CHUNK_MAX_TOKENS,
4799 )
4800 .into());
4801 }
4802 let grouped_decode_shape = step_grouped_decode_shape(prefill, t);
4803 let grouped_prefill_shape =
4804 step_grouped_prefill_shape(grouped_prefill_requested, prefill, t);
4805 if let Some(ep) = m.step_ep.as_ref().filter(|ep| {
4806 ep.grouped_decode.is_some() && (grouped_decode_shape || grouped_prefill_shape)
4807 }) {
4808 let (selected, route_weights) =
4809 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sigmoid)?;
4810 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
4811 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
4812 Self::trace_moe_input(e, il, t, n_embd, z)?;
4813 let selected = selected
4814 .iter()
4815 .map(|&expert| expert as usize)
4816 .collect::<Vec<_>>();
4817
4818 e.stream().synchronize()?;
4821 let execute = |state: &mut crate::hybrid::StepEpGroupedDecode| {
4822 state.projection.set_activation_limit(ep.activation_limit)?;
4823 ep.runtime
4824 .refresh_step_grouped_expert_parallel_gate_from_root_device(
4825 ep.experts.e4m3()?,
4826 &mut state.projection,
4827 z,
4828 t,
4829 &selected,
4830 )?;
4831 ep.runtime.refresh_step_grouped_expert_parallel_combine(
4832 &state.projection,
4833 &mut state.combine,
4834 &route_weights,
4835 )?;
4836 ep.runtime.execute_step_grouped_expert_parallel_gate(
4837 ep.experts.e4m3()?,
4838 &mut state.projection,
4839 )?;
4840 ep.runtime.execute_step_grouped_expert_parallel_combine(
4841 &state.projection,
4842 &mut state.combine,
4843 )?;
4844 let mut output = ep.runtime.copy_step_grouped_expert_parallel_combine_root(
4845 &state.projection,
4846 &state.combine,
4847 e,
4848 )?;
4849 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
4850 if prefill {
4851 e.stream().synchronize()?;
4854 }
4855 eprintln!(
4856 "[step-tp-ep-grouped] execute layer={il} tokens={t} devices={:?} \
4857 attention_layout=tensor-parallel expert_layout=expert-parallel \
4858 expert_transport={} native_p2p=true route_control=host-narrow \
4859 input=root-device projection_workspaces=persistent \
4860 combine=root-device output=owning-stage-device \
4861 prefill={prefill} batched_decode=false capacity={} \
4862 performance_claim=false",
4863 ep.devices,
4864 ep.runtime.transport_label(),
4865 state.projection.max_tokens(),
4866 );
4867 Ok::<_, Box<dyn std::error::Error>>(output)
4868 };
4869
4870 if grouped_prefill_shape {
4871 let grouped_prefill = grouped_prefill
4872 .ok_or("Step grouped prefill has no model-scoped executor")?;
4873 let mut shared = grouped_prefill
4874 .lock()
4875 .map_err(|_| "Step grouped prefill state lock is poisoned")?;
4876 let needs_prepare = shared.state.as_ref().is_none_or(|state| {
4877 state.devices != ep.devices
4878 || state.grouped.projection.max_tokens() < t
4879 || state.grouped.projection.input_width() != n_embd
4880 || state.grouped.projection.expert_width()
4881 != moe.expert_ff_length as usize
4882 });
4883 if needs_prepare {
4884 let seed_input = vec![0.0f32; n_embd];
4885 let seed_selected = &selected[..n_used];
4886 let seed_weights = &route_weights[..n_used];
4887 let projection = ep
4888 .runtime
4889 .prepare_step_grouped_expert_parallel_gate_with_capacity(
4890 ep.experts.e4m3()?,
4891 &seed_input,
4892 1,
4893 seed_selected,
4894 ep.activation_limit,
4895 t,
4896 )?;
4897 let combine = ep.runtime.prepare_step_grouped_expert_parallel_combine(
4898 &projection,
4899 seed_weights,
4900 )?;
4901 shared.state = Some(crate::hybrid::StepEpGroupedPrefillState {
4902 devices: ep.devices.clone(),
4903 grouped: crate::hybrid::StepEpGroupedDecode {
4904 projection,
4905 combine,
4906 },
4907 });
4908 eprintln!(
4909 "[step-tp-ep-grouped-prefill] prepare capacity={t} devices={:?} \
4910 shared_across_layers=true performance_claim=false",
4911 ep.devices,
4912 );
4913 }
4914 return execute(
4915 &mut shared
4916 .state
4917 .as_mut()
4918 .expect("Step grouped prefill state prepared above")
4919 .grouped,
4920 );
4921 }
4922
4923 let mut grouped = ep
4924 .grouped_decode
4925 .as_ref()
4926 .expect("grouped decode presence checked above")
4927 .lock()
4928 .map_err(|_| "Step grouped decode state lock is poisoned")?;
4929 return execute(&mut grouped);
4930 }
4931 if grouped_prefill_shape {
4932 return Err(
4933 "Step grouped prefill requires native-P2P expert-owner device arithmetic"
4934 .into(),
4935 );
4936 }
4937 if t == 1
4942 && crate::tp::step_nvfp4_dev_routes_enabled()?
4943 && crate::tp::step_tp_dev_router_enabled()?
4944 {
4945 if let Some(tp) = &m.step_tp {
4946 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
4947 let (sf, route_norm) = sigmoid;
4948 static D1_ROUTER: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4955 let d1_router = *D1_ROUTER.get_or_init(|| {
4956 std::env::var("MEMRA_DEV1_ROUTER").as_deref() == Ok("1")
4957 });
4958 if d1_router {
4959 let (sf_h, rn_h) = sigmoid;
4960 let n_ex = m.gate_exps.n_expert;
4961 let act_ct = m.active_count();
4962 let _ = tp.runtime.nvfp4_routes_prestage_with(
4963 bank,
4964 e,
4965 z,
4966 |rank1, in1, sel1, w1| {
4967 let mut guard = DEV1_ROUTER_REPS
4968 .lock()
4969 .map_err(|_| "dev1 router replica lock")?;
4970 let (reps, scratch) =
4971 guard.get_or_insert_with(|| (Default::default(), None));
4972 if !reps.contains_key(&il) {
4973 use cudarc::driver::DevicePtr;
4974 let (g1, p1, a1) = (
4975 rank1.htod(&vec![0.0f32; n_ex * n_embd])?,
4976 rank1.htod(&vec![0.0f32; n_ex])?,
4977 rank1.alloc_u8_uninit(n_ex)?,
4978 );
4979 for (src, dst_len, dst) in [
4980 (
4981 {
4982 let s = e.stream();
4983 let (p, _g) =
4984 m.gate_inp.float_data().device_ptr(&s);
4985 p as u64
4986 },
4987 n_ex * n_embd * 4,
4988 {
4989 let s = rank1.stream();
4990 let (p, _g) = g1.device_ptr(&s);
4991 p as u64
4992 },
4993 ),
4994 (
4995 {
4996 let s = e.stream();
4997 let (p, _g) = m.exp_probs_b_dev.device_ptr(&s);
4998 p as u64
4999 },
5000 n_ex * 4,
5001 {
5002 let s = rank1.stream();
5003 let (p, _g) = p1.device_ptr(&s);
5004 p as u64
5005 },
5006 ),
5007 (
5008 {
5009 let s = e.stream();
5010 let (p, _g) =
5011 m.active_experts_dev.device_ptr(&s);
5012 p as u64
5013 },
5014 n_ex,
5015 {
5016 let s = rank1.stream();
5017 let (p, _g) = a1.device_ptr(&s);
5018 p as u64
5019 },
5020 ),
5021 ] {
5022 crate::tp::raw_copy_bytes(dst, src, dst_len, rank1)?;
5023 }
5024 rank1.stream().synchronize()?;
5025 reps.insert(il, (g1, p1, a1));
5026 }
5027 if scratch.is_none() {
5028 *scratch = Some(rank1.htod(&vec![0.0f32; n_ex])?);
5029 }
5030 let (g1, p1, a1) = reps.get(&il).expect("armed above");
5031 let logits1 = scratch.as_mut().expect("armed above");
5032 rank1.router_gemv_into(g1, in1, logits1, n_embd, n_ex, 1)?;
5033 rank1.moe_router_sigmoid_topk_into(
5034 logits1, 1, n_ex, n_used, act_ct, p1, a1, sf_h, rn_h, sel1,
5035 w1,
5036 )?;
5037 Ok(true)
5038 },
5039 )?;
5040 } else {
5041 let _ = tp.runtime.nvfp4_routes_prestage(bank, e, z)?;
5042 }
5043 static SELW: std::sync::Mutex<
5047 Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>,
5048 > = std::sync::Mutex::new(None);
5049 let mut selw = SELW.lock().map_err(|_| "selw lock poisoned")?;
5050 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
5051 *selw = Some((
5052 e.ctx().ordinal(),
5053 e.htod_i32(&vec![0i32; n_used])?,
5054 e.htod(&vec![0.0f32; n_used])?,
5055 ));
5056 }
5057 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
5058 e.moe_router_sigmoid_topk_into(
5059 &logits,
5060 t,
5061 n_expert,
5062 n_used,
5063 m.active_count(),
5064 &m.exp_probs_b_dev,
5065 &m.active_experts_dev,
5066 sf,
5067 route_norm,
5068 sel_d,
5069 w_d,
5070 )?;
5071 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
5072 static SHEXP_OV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5076 let shexp_ov = *SHEXP_OV.get_or_init(|| {
5077 std::env::var("MEMRA_SHEXP_OVERLAP").as_deref() == Ok("1")
5078 });
5079 static SHEXP_D1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5083 let shexp_d1 = *SHEXP_D1.get_or_init(|| {
5084 std::env::var("MEMRA_SHEXP_DEV1").as_deref() == Ok("1")
5085 }) && tp.runtime.rank_engine(1).is_some();
5086 static TAIL3: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5090 let tail3 = *TAIL3
5091 .get_or_init(|| std::env::var("MEMRA_TAIL_ADD3").as_deref() != Ok("0"));
5092 let mut ov_issued = false;
5093 let mut d1_issued = false;
5094 let mut tail_folded = false;
5095 let mut output = if shexp_d1 {
5096 let rank1 = tp.runtime.rank_engine(1).expect("checked above");
5097 tp.runtime
5098 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
5099 bank,
5100 e,
5101 z,
5102 &sel_d,
5103 &w_d,
5104 n_used,
5105 tp.activation_limit,
5106 || {
5107 d1_issued = Self::shexp_dev1_issue(
5108 e, rank1, m, z, cfg, il, n_embd,
5109 )?;
5110 Ok(())
5111 },
5112 )?
5113 } else if shexp_ov {
5114 let post_add = if tail3 {
5119 Self::shexp_overlap_tail_ptrs(e, m, cfg, n_embd)?
5120 } else {
5121 None
5122 };
5123 let used_post = post_add.is_some();
5124 let out = tp
5125 .runtime
5126 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
5127 bank,
5128 e,
5129 z,
5130 &sel_d,
5131 &w_d,
5132 n_used,
5133 tp.activation_limit,
5134 || {
5135 ov_issued =
5136 Self::shexp_overlap_issue(e, m, z, cfg, il, n_embd)?;
5137 Ok(())
5138 },
5139 post_add,
5140 )?;
5141 if used_post && ov_issued {
5146 tail_folded = true; }
5148 out
5149 } else {
5150 tp.runtime.run_tensor_parallel_routes_nvfp4_device_routed(
5151 bank,
5152 e,
5153 z,
5154 &sel_d,
5155 &w_d,
5156 n_used,
5157 tp.activation_limit,
5158 )?
5159 };
5160 if output.len() != t * n_embd {
5161 return Err(format!(
5162 "Step tp routed output has {} values, expected {t}x{n_embd}",
5163 output.len()
5164 )
5165 .into());
5166 }
5167 if tail_folded {
5168 } else if d1_issued {
5170 Self::shexp_dev1_apply(e, &mut output, n_embd)?;
5171 } else if ov_issued {
5172 Self::shexp_overlap_apply(e, &mut output, n_embd)?;
5173 } else {
5174 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5175 }
5176 static DR_LOGGED: std::sync::atomic::AtomicU64 =
5177 std::sync::atomic::AtomicU64::new(0);
5178 let layer_bit = 1u64 << (il as u64 % 64);
5179 if DR_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5180 & layer_bit
5181 == 0
5182 {
5183 eprintln!(
5184 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5185 expert_transport={} native_p2p={} router=device \
5186 activation=host-canonical accumulation=host-canonical \
5187 output=e-device io=device performance_claim=false \
5188 (logged once per layer)",
5189 tp.devices,
5190 tp.runtime.transport_label(),
5191 tp.runtime.native_p2p(),
5192 );
5193 }
5194 return Ok(output);
5195 }
5196 }
5197 }
5198 static ROUTE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5202 static ROUTE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5203 let route_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
5204 let route_started = route_timing.then(std::time::Instant::now);
5205 let (selected, route_weights, input) = Self::moe_route_sigmoid_with_input(
5206 e,
5207 &logits,
5208 z,
5209 t,
5210 n_embd,
5211 n_expert,
5212 n_used,
5213 m.exp_probs_b.as_deref(),
5214 sigmoid,
5215 m.active_experts.as_deref(),
5216 )?;
5217 if let Some(started) = route_started {
5218 use std::sync::atomic::Ordering;
5219 let ns = ROUTE_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
5220 + started.elapsed().as_nanos() as u64;
5221 let calls = ROUTE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
5222 if calls % 430 == 0 {
5223 eprintln!(
5224 "[moe-route-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
5225 ns as f64 / 1.0e6,
5226 ns as f64 / calls as f64 / 1.0e3,
5227 );
5228 }
5229 }
5230 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5231 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5232 Self::trace_moe_input(e, il, t, n_embd, z)?;
5233 let selected = selected
5234 .iter()
5235 .map(|&expert| expert as usize)
5236 .collect::<Vec<_>>();
5237 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5242 if let Some(tp) = &m.step_tp {
5243 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5244 let mut output = tp.runtime.run_tensor_parallel_routes_nvfp4_device_io(
5245 bank,
5246 e,
5247 z,
5248 &selected,
5249 &route_weights,
5250 n_used,
5251 tp.activation_limit,
5252 )?;
5253 if output.len() != t * n_embd {
5254 return Err(format!(
5255 "Step tp routed output has {} values, expected {t}x{n_embd}",
5256 output.len()
5257 )
5258 .into());
5259 }
5260 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5261 static IO_LOGGED: std::sync::atomic::AtomicU64 =
5262 std::sync::atomic::AtomicU64::new(0);
5263 let layer_bit = 1u64 << (il as u64 % 64);
5264 if IO_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5265 & layer_bit
5266 == 0
5267 {
5268 eprintln!(
5269 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5270 expert_transport={} native_p2p={} activation=host-canonical \
5271 accumulation=host-canonical output=e-device io=device \
5272 performance_claim=false (logged once per layer)",
5273 tp.devices,
5274 tp.runtime.transport_label(),
5275 tp.runtime.native_p2p(),
5276 );
5277 }
5278 return Ok(output);
5279 }
5280 }
5281 }
5282 let (routed, mode, devices, transport, native_p2p) = if let Some(tp) = &m.step_tp {
5283 (
5284 match &tp.experts {
5285 crate::hybrid::StepTpExpertBank::E4m3(bank) => {
5286 tp.runtime.run_tensor_parallel_routes(
5287 bank,
5288 &input,
5289 t,
5290 &selected,
5291 &route_weights,
5292 n_used,
5293 )?
5294 }
5295 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => {
5296 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5297 tp.runtime.run_tensor_parallel_routes_nvfp4_device(
5298 bank,
5299 &input,
5300 &selected,
5301 &route_weights,
5302 n_used,
5303 tp.activation_limit,
5304 )?
5305 } else {
5306 tp.runtime.run_tensor_parallel_routes_nvfp4(
5307 bank,
5308 &input,
5309 t,
5310 &selected,
5311 &route_weights,
5312 n_used,
5313 tp.activation_limit,
5314 )?
5315 }
5316 }
5317 },
5318 "tp",
5319 &tp.devices,
5320 tp.runtime.transport_label(),
5321 tp.runtime.native_p2p(),
5322 )
5323 } else {
5324 let ep = m
5325 .step_ep
5326 .as_ref()
5327 .ok_or("Step distributed runtime has no EP or TP state")?;
5328 (
5329 match &ep.experts {
5330 crate::hybrid::StepEpExpertBank::E4m3(bank) => {
5331 ep.runtime.run_routed_experts(
5332 bank,
5333 &input,
5334 t,
5335 &selected,
5336 &route_weights,
5337 n_used,
5338 ep.activation_limit,
5339 )?
5340 }
5341 crate::hybrid::StepEpExpertBank::Nvfp4(bank) => {
5342 ep.runtime.run_routed_experts_nvfp4(
5343 bank,
5344 &input,
5345 t,
5346 &selected,
5347 &route_weights,
5348 n_used,
5349 ep.activation_limit,
5350 )?
5351 }
5352 },
5353 if ep.configured_by_tp { "tp-ep" } else { "ep" },
5354 &ep.devices,
5355 ep.runtime.transport_label(),
5356 ep.runtime.native_p2p(),
5357 )
5358 };
5359 if routed.len() != t * n_embd {
5360 return Err(format!(
5361 "Step {mode} routed output has {} values, expected {t}x{n_embd}",
5362 routed.len()
5363 )
5364 .into());
5365 }
5366 let mut output = e.htod(&routed)?;
5367 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5368 static STEP_LOGGED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5371 let layer_bit = 1u64 << (il as u64 % 64);
5372 if STEP_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed) & layer_bit
5373 == 0
5374 {
5375 eprintln!(
5376 "[step-{mode}] execute layer={il} tokens={t} devices={devices:?} \
5377 expert_transport={transport} native_p2p={native_p2p} \
5378 activation={} accumulation={} output={} \
5379 performance_claim=false (logged once per layer)",
5380 if let Some(ep) = &m.step_ep {
5381 ep.runtime.expert_activation_label()
5382 } else {
5383 "host-canonical"
5384 },
5385 if let Some(ep) = &m.step_ep {
5386 ep.runtime.expert_accumulation_label()
5387 } else {
5388 "host-canonical"
5389 },
5390 if let Some(ep) = &m.step_ep {
5391 ep.runtime.expert_output_label()
5392 } else {
5393 "host-accumulated"
5394 },
5395 );
5396 if let Some(ep) = &m.step_ep {
5397 if let Some(limit) = ep.activation_limit {
5398 eprintln!(
5399 "[step-ep-clamp] execute layer={il} tokens={t} routed_clamp={limit} \
5400 formula=min-silu-times-clamped-up performance_claim=false"
5401 );
5402 }
5403 }
5404 }
5405 return Ok(output);
5406 }
5407 if Self::sigmoid_resident_dev_eligible(e, m, cfg, sliding_gated_moe) {
5408 let moe = cfg.moe.as_ref().unwrap();
5409 let n_expert = moe.expert_count as usize;
5410 let n_used = moe.expert_used_count as usize;
5411 let sigmoid = cfg.sigmoid_router().unwrap();
5412 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5413 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5414 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
5415 }
5416 if t > 1 && moe_grouped_enabled(cfg, prefill) {
5419 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
5420 if std::env::var("MEMRA_MOE_GATE").is_ok() {
5425 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
5426 let g_host = e.dtoh(&grouped_out)?;
5427 let s_host = e.dtoh(&seq_out)?;
5428 let g_bytes: &[u8] = unsafe {
5429 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
5430 };
5431 let s_bytes: &[u8] = unsafe {
5432 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
5433 };
5434 if g_bytes == s_bytes {
5435 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
5436 } else {
5437 let diffs = g_host
5438 .iter()
5439 .zip(s_host.iter())
5440 .enumerate()
5441 .filter(|(_, (a, b))| a != b)
5442 .count();
5443 let maxdiff = g_host
5444 .iter()
5445 .zip(s_host.iter())
5446 .map(|(a, b)| (a - b).abs())
5447 .fold(0.0f32, f32::max);
5448 panic!(
5449 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
5450 g_host.len()
5451 );
5452 }
5453 }
5454 return Ok(grouped_out);
5455 }
5456 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
5457 }
5458
5459 fn sigmoid_resident_dev_eligible(
5460 e: &Engine,
5461 m: &MoeWeights,
5462 cfg: &ModelConfig,
5463 sliding_gated_moe: bool,
5464 ) -> bool {
5465 let Some(moe) = cfg.moe.as_ref() else {
5466 return false;
5467 };
5468 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5471 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
5472 std::env::var("MEMRA_MOE_STATS").is_ok()
5473 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5474 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5475 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
5476 || std::env::var("MEMRA_MOE_GATE").is_ok()
5477 });
5478 let resident_layout_supported = m.dev_exps.as_ref().is_some_and(|dev| {
5479 if dev.dev != e.ctx().ordinal() {
5480 return false;
5481 }
5482 let q8 = moe_q8_enabled()
5483 && q8_expert_supported(m.gate_exps.qtype)
5484 && q8_expert_supported(m.up_exps.qtype)
5485 && q8_expert_supported(m.down_exps.qtype);
5486 let fp8 = dev.fp8_blk.is_some()
5487 && m.gate_exps.qtype == crate::QT_F8_E4M3_BLK
5488 && m.up_exps.qtype == crate::QT_F8_E4M3_BLK
5489 && m.down_exps.qtype == crate::QT_F8_E4M3_BLK;
5490 q8 || fp8
5491 });
5492 sliding_gated_moe
5493 && sigmoid_router_enabled()
5494 && moe_dev_enabled()
5495 && moe_slab_enabled()
5496 && !observation_mode
5497 && moe.expert_used_count <= 8
5498 && m.has_uniform_expert_layout()
5499 && m.gate_exps.macros.is_none()
5500 && m.up_exps.macros.is_none()
5501 && m.down_exps.macros.is_none()
5502 && !m.has_macros
5503 && resident_layout_supported
5504 }
5505
5506 pub(crate) fn moe_ffn_sequential(
5508 e: &Engine,
5509 m: &MoeWeights,
5510 z: &CudaSlice<f32>,
5511 t: usize,
5512 cfg: &ModelConfig,
5513 il: u16,
5514 max_block: usize,
5515 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5516 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
5517 }
5518
5519 fn moe_router_logits(
5523 e: &Engine,
5524 m: &MoeWeights,
5525 z: &CudaSlice<f32>,
5526 t: usize,
5527 cfg: &ModelConfig,
5528 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5529 if t < PRIME_MIN_T {
5530 if crate::router_kernel_on() {
5532 e.router_gemv(
5533 m.gate_inp.float_data(),
5534 z,
5535 cfg.n_embd as usize,
5536 m.gate_exps.n_expert,
5537 t,
5538 )
5539 } else {
5540 e.matmul_decode_exact(&m.gate_inp, z, t)
5541 }
5542 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
5543 e.router_gemv(
5544 m.gate_inp.float_data(),
5545 z,
5546 cfg.n_embd as usize,
5547 m.gate_exps.n_expert,
5548 t,
5549 )
5550 } else {
5551 e.matmul(&m.gate_inp, z, t)
5552 }
5553 }
5554
5555 fn trace_moe_routes(
5559 il: u16,
5560 t: usize,
5561 sel_all: &[u32],
5562 weights: &[f32],
5563 ) -> Result<(), Box<dyn std::error::Error>> {
5564 use std::io::Write as _;
5565 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
5566 let mut f = std::fs::OpenOptions::new()
5567 .create(true)
5568 .append(true)
5569 .open(path)?;
5570 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
5571 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
5572 }
5573 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
5574 let mut f = std::fs::OpenOptions::new()
5575 .create(true)
5576 .append(true)
5577 .open(path)?;
5578 let pairs: Vec<String> = sel_all
5579 .iter()
5580 .zip(weights)
5581 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
5582 .collect();
5583 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
5584 }
5585 Ok(())
5586 }
5587
5588 #[allow(clippy::too_many_arguments)]
5589 fn trace_sigmoid_router_logits(
5590 e: &Engine,
5591 il: u16,
5592 t: usize,
5593 n_expert: usize,
5594 n_used: usize,
5595 logits: &CudaSlice<f32>,
5596 m: &MoeWeights,
5597 (scaling_factor, route_norm): (f32, bool),
5598 ) -> Result<(), Box<dyn std::error::Error>> {
5599 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
5600 return Ok(());
5601 }
5602 let logits = e.dtoh(logits)?;
5603 let active: Vec<u8> = m
5604 .active_experts
5605 .as_ref()
5606 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
5607 .unwrap_or_else(|| vec![1; n_expert]);
5608 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
5609 crate::sigrouter_contract::capture_served_logits(
5610 il as u32,
5611 t,
5612 n_expert,
5613 n_used,
5614 scaling_factor,
5615 route_norm,
5616 &active,
5617 &bias,
5618 &logits,
5619 )?;
5620 Ok(())
5621 }
5622
5623 fn trace_moe_input(
5628 e: &Engine,
5629 il: u16,
5630 t: usize,
5631 n_embd: usize,
5632 z: &CudaSlice<f32>,
5633 ) -> Result<(), Box<dyn std::error::Error>> {
5634 use std::io::Write as _;
5635 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
5636 return Ok(());
5637 };
5638 let values = active_matrix_values(z.len(), t, n_embd, "MoE input trace activation")?;
5639 let host = e.dtoh_view(&z.slice(0..values))?;
5640 let bytes = unsafe {
5641 std::slice::from_raw_parts(
5642 host.as_ptr().cast::<u8>(),
5643 host.len() * std::mem::size_of::<f32>(),
5644 )
5645 };
5646 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
5647 let mut state = state
5648 .lock()
5649 .map_err(|_| "MoE input trace writer lock is poisoned")?;
5650 if state.is_none() {
5651 let dir = std::path::PathBuf::from(&dir);
5652 std::fs::create_dir_all(&dir)?;
5653 let index = std::fs::OpenOptions::new()
5654 .create(true)
5655 .append(true)
5656 .open(dir.join("index.jsonl"))?;
5657 *state = Some(MoeInputTraceWriter {
5658 dir,
5659 index,
5660 payloads: std::collections::HashMap::new(),
5661 });
5662 }
5663 let writer = state.as_mut().unwrap();
5664 if writer.dir != std::path::Path::new(&dir) {
5665 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
5666 }
5667 let file_name = format!("layer-{il:03}.f32");
5668 if !writer.payloads.contains_key(&il) {
5669 let payload = std::fs::OpenOptions::new()
5670 .create(true)
5671 .append(true)
5672 .open(writer.dir.join(&file_name))?;
5673 let offset = payload.metadata()?.len();
5674 writer.payloads.insert(il, (payload, offset));
5675 }
5676 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
5677 let row_offset = *offset;
5678 payload.write_all(bytes)?;
5679 *offset += bytes.len() as u64;
5680 writeln!(
5681 writer.index,
5682 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
5683 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
5684 \"payload_bytes\":{}}}",
5685 bytes.len()
5686 )?;
5687 Ok(())
5688 }
5689
5690 #[allow(clippy::too_many_arguments)]
5691 pub(crate) fn moe_ffn_sequential_zq8(
5692 e: &Engine,
5693 m: &MoeWeights,
5694 z: &CudaSlice<f32>,
5695 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5696 t: usize,
5697 cfg: &ModelConfig,
5698 il: u16,
5699 max_block: usize,
5700 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5701 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
5702 let moe = cfg.moe.as_ref().unwrap();
5703 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);
5710 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
5711 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);
5714
5715 let lim_exp = cfg.clamp_exp_at(il as u32);
5718 let lim_shexp = cfg.clamp_shexp_at(il as u32);
5719 let use_cache = Engine::moe_cache_enabled();
5720 let uniform_experts = m.has_uniform_expert_layout();
5721 let moe_q8 = uniform_experts
5722 && moe_q8_enabled()
5723 && q8_expert_supported(m.gate_exps.qtype)
5724 && q8_expert_supported(m.up_exps.qtype)
5725 && q8_expert_supported(m.down_exps.qtype);
5726 let cpu_expert_requested = crate::cpu_experts::configured();
5733 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
5734 return Err(std::io::Error::other(
5735 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
5736 )
5737 .into());
5738 }
5739 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
5740 let freeze_cpu_residency = cpu_expert_requested
5746 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
5747 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
5748 .ok()
5749 .and_then(|value| value.parse::<usize>().ok())
5750 .is_some_and(|tokens| tokens > 0);
5751 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
5752 e.freeze_moe_cache();
5753 }
5754 let cache_frozen = use_cache && e.moe_cache_frozen();
5755 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
5756
5757 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5760 if let Some(sig) = cfg.sigmoid_router() {
5761 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
5762 }
5763
5764 let no_exp_macros = m.gate_exps.macros.is_none()
5803 && m.up_exps.macros.is_none()
5804 && m.down_exps.macros.is_none();
5805 if cfg.sigmoid_router().is_none()
5809 && cfg.m3.is_none()
5810 && cfg.hy3.is_none()
5811 && !cfg.swiglu_clamped_at(il as u32)
5812 && no_exp_macros
5813 && t > MOE_DEV_MAX_T
5817 && m.dev_exps.is_some()
5818 && moe_q8_enabled()
5819 && q8_expert_supported(m.gate_exps.qtype)
5820 && q8_expert_supported(m.up_exps.qtype)
5821 && q8_expert_supported(m.down_exps.qtype)
5822 && std::env::var("MEMRA_MOE_PAIRS")
5823 .map(|v| v != "0")
5824 .unwrap_or(true)
5825 && std::env::var("MEMRA_MOE_STATS").is_err()
5826 {
5827 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
5828 }
5829
5830 let dev_ok = uniform_experts
5848 && cfg.sigmoid_router().is_none()
5849 && cfg.m3.is_none()
5850 && cfg.hy3.is_none()
5851 && !cfg.swiglu_clamped_at(il as u32);
5852 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
5856 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5857 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5858 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
5859 if dev_ok
5860 && t <= MOE_DEV_MAX_T
5861 && m.dev_exps.is_some()
5862 && n_used <= 8
5863 && moe_dev_enabled()
5864 && !observe_routes
5865 {
5866 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5867 }
5868 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
5869 let row_ok = e.with_moe_cache(max_block, |c, eng| {
5870 if moe_prewarm_enabled() {
5871 c.prewarm_layer(il, m, eng)?;
5872 }
5873 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
5874 })?;
5875 if row_ok {
5876 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5877 }
5878 }
5879
5880 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
5882 if cpu_hybrid {
5883 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
5884 e,
5885 &logits,
5886 z,
5887 t,
5888 n_embd,
5889 n_expert,
5890 n_used,
5891 m.exp_probs_b.as_deref(),
5892 sig,
5893 m.active_experts.as_deref(),
5894 )?;
5895 (sel, w, Some(input))
5896 } else {
5897 let (sel, w) =
5898 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
5899 (sel, w, None)
5900 }
5901 } else {
5902 let (sel, w) =
5903 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
5904 (sel, w, None)
5905 };
5906 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
5907
5908 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
5912 Self::trace_moe_input(e, il, t, n_embd, z)?;
5913
5914 let worker_disk_prefetch =
5926 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
5927 let promote_worker_h2d =
5928 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
5929 if promote_worker_h2d {
5930 let mut selected_blocks = Vec::with_capacity(n_used * 3);
5931 for &ex in sel_all.iter().take(n_used) {
5932 let ex = ex as u16;
5933 selected_blocks.extend([
5934 BlockId::new(il, PROJ_GATE, ex),
5935 BlockId::new(il, PROJ_UP, ex),
5936 BlockId::new(il, PROJ_DOWN, ex),
5937 ]);
5938 }
5939 for &ex in sel_all.iter().take(n_used) {
5940 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
5941 }
5942 e.with_moe_cache(max_block, |cache, eng| {
5943 cache.promote_worker_reads_at_safe_boundary(
5944 &selected_blocks,
5945 &selected_blocks,
5946 eng,
5947 )?;
5948 Ok(())
5949 })?;
5950 }
5951
5952 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
5955 let mut cnt = vec![0u32; n_expert];
5956 for &s in sel_all.iter() {
5957 cnt[s as usize] += 1;
5958 }
5959 let total = sel_all.len() as f64;
5960 let mut h = 0.0f64;
5961 let mut active = 0usize;
5962 for &c in &cnt {
5963 if c > 0 {
5964 active += 1;
5965 let p = c as f64 / total;
5966 h -= p * p.log2();
5967 }
5968 }
5969 let maxc = cnt.iter().copied().max().unwrap_or(0);
5970 println!(
5971 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
5972 il,
5973 t,
5974 sel_all.len(),
5975 active,
5976 n_expert,
5977 h,
5978 (n_expert as f64).log2(),
5979 total / active.max(1) as f64,
5980 maxc
5981 );
5982 }
5983
5984 let gdec_may_fire = uniform_experts
5997 && use_cache
5998 && n_used <= 8
5999 && gdec_enabled()
6000 && !cfg.swiglu_clamped_at(il as u32);
6001 let slab_local = m
6017 .dev_exps
6018 .as_ref()
6019 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
6020 let slab_bases = slab_local.map(|d| {
6021 use cudarc::driver::DevicePtr;
6022 let s = e.stream();
6023 let (pg, _g0) = d.gate.device_ptr(&s);
6024 let (pu, _g1) = d.up.device_ptr(&s);
6025 let (pd, _g2) = d.down.device_ptr(&s);
6026 (pg as u64, pu as u64, pd as u64)
6027 });
6028 let slab_fused_may_fire = slab_bases.is_some()
6038 && n_used <= 8
6039 && gdec_enabled()
6040 && !cfg.swiglu_clamped_at(il as u32)
6041 && cfg.m3.is_none()
6042 && no_exp_macros
6043 && moe_q8;
6044 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
6047 e.uninit(t * n_embd)?
6048 } else {
6049 e.zeros(t * n_embd)?
6050 };
6051 let cpu_input = if cpu_hybrid {
6054 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
6055 } else {
6056 None
6057 };
6058
6059 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;
6067 let mut scratch_u: Option<CudaSlice<u8>> = None;
6068 let mut scratch_d: Option<CudaSlice<u8>> = None;
6069 let page_window = moe_page_prefetch_window();
6077
6078 for tok in 0..t {
6081 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
6082 let w = &w_all[tok * n_used..(tok + 1) * n_used];
6083 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
6085
6086 let no_macros = m.gate_exps.macros.is_none()
6100 && m.up_exps.macros.is_none()
6101 && m.down_exps.macros.is_none();
6102 if slab_fused_may_fire {
6112 let (pg, pu, pd) = slab_bases.unwrap();
6113 let mut gp = [0u64; 8];
6114 let mut up = [0u64; 8];
6115 let mut dp = [0u64; 8];
6116 for (j, &ex) in sel.iter().enumerate() {
6117 let ex = ex as usize;
6118 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
6119 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
6120 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
6121 }
6122 let mut wv = [0f32; 8];
6123 wv[..n_used].copy_from_slice(w);
6124 if tok_q8.is_none() {
6125 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6126 }
6127 let (zq, zd) = tok_q8.as_ref().unwrap();
6128 let act = e.moe_gate_up_silu8_q8(
6129 crate::WPtr8(gp),
6130 crate::WPtr8(up),
6131 zq,
6132 zd,
6133 n_embd,
6134 n_ff_exp,
6135 n_used,
6136 m.gate_exps.qtype,
6137 m.up_exps.qtype,
6138 m.gate_exps.row_bytes,
6139 m.up_exps.row_bytes,
6140 )?;
6141 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
6142 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6143 e.moe_down8_fma_q8(
6144 crate::WPtr8(dp),
6145 crate::F32x8(wv),
6146 &aq2,
6147 &ad2,
6148 &mut dst,
6149 n_ff_exp,
6150 n_embd,
6151 n_used,
6152 m.down_exps.qtype,
6153 m.down_exps.row_bytes,
6154 )?;
6155 continue;
6156 }
6157 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
6158 if tok_q8.is_none() {
6159 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6160 }
6161 let (zq, zd) = tok_q8.as_ref().unwrap();
6162 if Self::moe_gdec_token_q8(
6163 e,
6164 m,
6165 il,
6166 max_block,
6167 zq,
6168 zd,
6169 sel,
6170 w,
6171 &mut moe_out,
6172 tok,
6173 n_embd,
6174 n_ff_exp,
6175 n_used,
6176 )? {
6177 continue;
6178 }
6179 } else if gdec_may_fire
6180 && cfg.m3.is_none()
6181 && no_macros
6182 && Self::moe_gdec_token(
6183 e,
6184 m,
6185 il,
6186 max_block,
6187 &zt,
6188 sel,
6189 w,
6190 &mut moe_out,
6191 tok,
6192 n_embd,
6193 n_ff_exp,
6194 n_used,
6195 )?
6196 {
6197 continue;
6198 }
6199
6200 if gdec_may_fire || slab_fused_may_fire {
6206 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6207 e.memset_zeros_view(&mut row)?;
6208 }
6209
6210 let mut cpu_mask = vec![false; sel.len()];
6216 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
6217 let gpu_resident = if use_cache {
6218 e.with_moe_cache(max_block, |cache, _| {
6219 Ok(sel
6220 .iter()
6221 .map(|&expert| {
6222 let expert = expert as u16;
6223 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
6224 .into_iter()
6225 .filter(|&projection| {
6226 cache
6227 .resident(BlockId::new(il, projection, expert))
6228 .is_some()
6229 })
6230 .count()
6231 })
6232 .collect::<Vec<_>>())
6233 })?
6234 } else {
6235 vec![0; sel.len()]
6236 };
6237 let mut cpu_selected = Vec::new();
6238 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
6239 if gpu_resident[index] != 3 {
6240 cpu_mask[index] = true;
6241 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
6242 let expert = expert as usize;
6243 cpu_selected.push((expert, route_weight));
6244 }
6245 }
6246 if crate::cpu_experts::predictor_enabled() {
6247 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6251 crate::cpu_experts::predictor_submit(il, row);
6252 }
6253 if cpu_selected.is_empty() {
6254 None
6255 } else {
6256 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6257 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
6258 .map_err(std::io::Error::other)?;
6259 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
6260 }
6261 } else {
6262 None
6263 };
6264
6265 let worker_window = worker_disk_prefetch
6266 .then(worker_prefetch_window)
6267 .unwrap_or(0);
6268 for (j, &ex) in sel.iter().enumerate() {
6269 if cpu_mask[j] {
6270 continue;
6271 }
6272 let ex = ex as usize;
6273 if let Some(d) = slab_local {
6280 let gl = m.gate_exps.expert_layout(ex);
6281 let ul = m.up_exps.expert_layout(ex);
6282 let dl = m.down_exps.expert_layout(ex);
6283 let (g0, u0, d0) = (
6284 ex * m.gate_exps.expert_stride,
6285 ex * m.up_exps.expert_stride,
6286 ex * m.down_exps.expert_stride,
6287 );
6288 let (gate, up) = if moe_q8 {
6289 if tok_q8.is_none() {
6290 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6291 }
6292 let (zq, zd) = tok_q8.as_ref().unwrap();
6293 (
6294 e.qmatvec_expert_q8(
6295 &d.gate,
6296 g0..g0 + gl.len,
6297 zq,
6298 zd,
6299 1,
6300 m.gate_exps.in_f,
6301 m.gate_exps.out_f,
6302 gl.qtype,
6303 gl.row_bytes,
6304 )?,
6305 e.qmatvec_expert_q8(
6306 &d.up,
6307 u0..u0 + ul.len,
6308 zq,
6309 zd,
6310 1,
6311 m.up_exps.in_f,
6312 m.up_exps.out_f,
6313 ul.qtype,
6314 ul.row_bytes,
6315 )?,
6316 )
6317 } else {
6318 (
6319 e.qmatvec_view(
6320 &d.gate,
6321 g0..g0 + gl.len,
6322 &zt,
6323 1,
6324 m.gate_exps.in_f,
6325 m.gate_exps.out_f,
6326 gl.qtype,
6327 gl.row_bytes,
6328 )?,
6329 e.qmatvec_view(
6330 &d.up,
6331 u0..u0 + ul.len,
6332 &zt,
6333 1,
6334 m.up_exps.in_f,
6335 m.up_exps.out_f,
6336 ul.qtype,
6337 ul.row_bytes,
6338 )?,
6339 )
6340 };
6341 let mut act = e.uninit(n_ff_exp)?;
6342 Self::ffn_act_lim(
6343 e,
6344 cfg,
6345 &gate,
6346 &up,
6347 m.gate_exps.macro_scale(ex),
6348 m.up_exps.macro_scale(ex),
6349 lim_exp,
6350 &mut act,
6351 n_ff_exp,
6352 )?;
6353 let y = if moe_q8 {
6354 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6355 e.qmatvec_expert_q8(
6356 &d.down,
6357 d0..d0 + dl.len,
6358 &aq2,
6359 &ad2,
6360 1,
6361 m.down_exps.in_f,
6362 m.down_exps.out_f,
6363 dl.qtype,
6364 dl.row_bytes,
6365 )?
6366 } else {
6367 let actv = act.slice(0..n_ff_exp);
6368 e.qmatvec_view(
6369 &d.down,
6370 d0..d0 + dl.len,
6371 &actv,
6372 1,
6373 m.down_exps.in_f,
6374 m.down_exps.out_f,
6375 dl.qtype,
6376 dl.row_bytes,
6377 )?
6378 };
6379 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6380 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6381 continue;
6382 }
6383 for next in page_prefetch_positions(j, sel.len(), page_window) {
6384 Self::moe_prefetch_host_expert(sel[next] as usize, m);
6385 }
6386 let keep = [
6387 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
6388 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
6389 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
6390 ];
6391 if worker_disk_prefetch && worker_window > 0 {
6392 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
6393 Self::moe_prefetch_disk_expert(
6394 e,
6395 il,
6396 sel[next] as usize,
6397 m,
6398 max_block,
6399 &keep,
6400 )?;
6401 }
6402 } else if cache_dispatch
6403 && !cpu_hybrid
6404 && moe_prefetch_enabled()
6405 && j + 1 < sel.len()
6406 {
6407 let next = sel[j + 1] as usize;
6408 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
6409 }
6410 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
6411 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
6412 if (gate_q8 || up_q8) && tok_q8.is_none() {
6415 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6416 }
6417 let gate = if gate_q8 {
6418 let (zq, zd) = tok_q8.as_ref().unwrap();
6419 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
6420 } else {
6421 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
6422 };
6423 let up = if up_q8 {
6424 let (zq, zd) = tok_q8.as_ref().unwrap();
6425 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
6426 } else {
6427 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
6428 };
6429 let mut act = e.uninit(n_ff_exp)?;
6430 Self::ffn_act_lim(
6431 e,
6432 cfg,
6433 &gate,
6434 &up,
6435 m.gate_exps.macro_scale(ex),
6436 m.up_exps.macro_scale(ex),
6437 lim_exp,
6438 &mut act,
6439 n_ff_exp,
6440 )?;
6441 let y = if down_q8 {
6442 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6443 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
6444 } else {
6445 let actv = act.slice(0..n_ff_exp);
6446 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
6447 };
6448 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6449 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6451 } else if cache_dispatch {
6452 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
6457 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
6458 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6460 e,
6461 cfg,
6462 &gate,
6463 &up,
6464 m.gate_exps.macro_scale(ex),
6465 m.up_exps.macro_scale(ex),
6466 lim_exp,
6467 &mut act,
6468 n_ff_exp,
6469 )?;
6470 let actv = act.slice(0..n_ff_exp);
6471 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
6472 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6473 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6475 } else if cache_frozen {
6476 let gate = Self::moe_frozen_gemm(
6481 e,
6482 il,
6483 PROJ_GATE,
6484 ex,
6485 m,
6486 max_block,
6487 &zt,
6488 &mut scratch_g,
6489 g_len,
6490 )?;
6491 let up = Self::moe_frozen_gemm(
6492 e,
6493 il,
6494 PROJ_UP,
6495 ex,
6496 m,
6497 max_block,
6498 &zt,
6499 &mut scratch_u,
6500 u_len,
6501 )?;
6502 let mut act = e.uninit(n_ff_exp)?;
6503 Self::ffn_act_lim(
6504 e,
6505 cfg,
6506 &gate,
6507 &up,
6508 m.gate_exps.macro_scale(ex),
6509 m.up_exps.macro_scale(ex),
6510 lim_exp,
6511 &mut act,
6512 n_ff_exp,
6513 )?;
6514 let actv = act.slice(0..n_ff_exp);
6515 let y = Self::moe_frozen_gemm(
6516 e,
6517 il,
6518 PROJ_DOWN,
6519 ex,
6520 m,
6521 max_block,
6522 &actv,
6523 &mut scratch_d,
6524 d_len,
6525 )?;
6526 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6527 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6528 } else {
6529 if scratch_g.is_none() {
6533 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
6534 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
6535 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
6536 }
6537 let (sg, su, sd) = (
6538 scratch_g.as_mut().unwrap(),
6539 scratch_u.as_mut().unwrap(),
6540 scratch_d.as_mut().unwrap(),
6541 );
6542 let gl = m.gate_exps.expert_layout(ex);
6543 let ul = m.up_exps.expert_layout(ex);
6544 let dl = m.down_exps.expert_layout(ex);
6545 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
6546 let gate = e.qmatvec_view(
6547 sg,
6548 0..gl.len,
6549 &zt,
6550 1,
6551 m.gate_exps.in_f,
6552 m.gate_exps.out_f,
6553 gl.qtype,
6554 gl.row_bytes,
6555 )?;
6556
6557 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
6558 let up = e.qmatvec_view(
6559 su,
6560 0..ul.len,
6561 &zt,
6562 1,
6563 m.up_exps.in_f,
6564 m.up_exps.out_f,
6565 ul.qtype,
6566 ul.row_bytes,
6567 )?;
6568
6569 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6571 e,
6572 cfg,
6573 &gate,
6574 &up,
6575 m.gate_exps.macro_scale(ex),
6576 m.up_exps.macro_scale(ex),
6577 lim_exp,
6578 &mut act,
6579 n_ff_exp,
6580 )?;
6581
6582 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
6583 let actv = act.slice(0..n_ff_exp);
6584 let y = e.qmatvec_view(
6585 sd,
6586 0..dl.len,
6587 &actv,
6588 1,
6589 m.down_exps.in_f,
6590 m.down_exps.out_f,
6591 dl.qtype,
6592 dl.row_bytes,
6593 )?;
6594
6595 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6596 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6597 }
6598 }
6599 if let Some(worker) = cpu_worker {
6600 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
6601 let cpu_output = e.htod(&cpu_output)?;
6602 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6603 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
6604 }
6605 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
6606 for (j, &ex) in sel.iter().enumerate() {
6607 if cpu_mask[j] {
6608 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
6609 }
6610 }
6611 }
6612 }
6613
6614 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6619 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6620 {
6621 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
6630 let (sg_gate, sg_up) = if t == 1 {
6631 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
6632 } else if verify_t {
6633 (
6634 e.matmul_decode_exact(gate_shexp, z, t)?,
6635 e.matmul_decode_exact(up_shexp, z, t)?,
6636 )
6637 } else {
6638 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
6640 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
6642 e,
6643 cfg,
6644 &sg_gate,
6645 &sg_up,
6646 1.0,
6647 1.0,
6648 lim_shexp,
6649 &mut sa,
6650 t * n_ff_sh,
6651 )?;
6652 let sh = if verify_t {
6653 e.matmul_decode_exact(down_shexp, &sa, t)?
6654 } else {
6655 e.matmul(down_shexp, &sa, t)?
6656 }; let g = match &m.gate_inp_shexp {
6670 Some(gate_inp_shexp) => {
6671 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
6672 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6673 } else {
6674 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6675 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
6677 g
6678 }
6679 }
6680 None => e.htod(&vec![1.0f32; t])?,
6681 };
6682 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6684 }
6685
6686 Ok(moe_out)
6687 }
6688
6689 pub fn stage1_h2d_per_token(&self) -> u64 {
6692 use crate::hybrid::Ffn;
6693 let n_used = self
6694 .cfg
6695 .moe
6696 .as_ref()
6697 .map(|m| m.expert_used_count as u64)
6698 .unwrap_or(0);
6699 let mut bytes = 0u64;
6700 for l in self.layers.iter() {
6701 if let Ffn::Moe(m) = &l.ffn {
6702 bytes += n_used
6703 * (m.gate_exps.max_expert_bytes()
6704 + m.up_exps.max_expert_bytes()
6705 + m.down_exps.max_expert_bytes()) as u64;
6706 }
6707 }
6708 bytes
6709 }
6710
6711 pub(crate) fn max_moe_block(&self) -> usize {
6715 use crate::hybrid::Ffn;
6716 let mut mx = 0usize;
6717 let mut scan = |ffn: &Ffn| {
6718 if let Ffn::Moe(m) = ffn {
6719 mx = mx
6720 .max(m.gate_exps.max_expert_bytes())
6721 .max(m.up_exps.max_expert_bytes())
6722 .max(m.down_exps.max_expert_bytes());
6723 }
6724 };
6725 for l in self.layers.iter() {
6726 scan(&l.ffn);
6727 }
6728 if let Some(mtp) = self.mtp.as_ref() {
6729 scan(&mtp.ffn);
6730 }
6731 mx
6732 }
6733
6734 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
6737 use crate::hybrid::Ffn;
6738 let mut sizes = Vec::new();
6739 let mut scan = |ffn: &Ffn| {
6740 let Ffn::Moe(m) = ffn else { return };
6741 for ex in 0..m.gate_exps.n_expert {
6742 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
6743 continue;
6744 }
6745 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
6746 let len = exps.expert_layout(ex).len;
6747 if len > 0 {
6748 sizes.push(len);
6749 }
6750 }
6751 }
6752 };
6753 for layer in &self.layers {
6754 scan(&layer.ffn);
6755 }
6756 if let Some(mtp) = &self.mtp {
6757 scan(&mtp.ffn);
6758 }
6759 sizes
6760 }
6761
6762 pub fn save_cpu_expert_residency_profile(
6768 &self,
6769 e: &Engine,
6770 path: &std::path::Path,
6771 ) -> Result<(), Box<dyn std::error::Error>> {
6772 let Some(ids) = e.export_moe_residency() else {
6773 return Err("no MoE residency cache to persist".into());
6774 };
6775 let mut body = format!(
6776 "memra-freeze-profile v1 max_block={} blocks={}\n",
6777 self.max_moe_block(),
6778 ids.len()
6779 );
6780 for (layer, proj, ex) in &ids {
6781 body.push_str(&format!("{layer} {proj} {ex}\n"));
6782 }
6783 let tmp = path.with_extension("tmp");
6784 std::fs::write(&tmp, body)?;
6785 std::fs::rename(&tmp, path)?;
6786 println!(
6787 "[moe-cache] freeze profile saved: {} blocks -> {}",
6788 ids.len(),
6789 path.display()
6790 );
6791 Ok(())
6792 }
6793
6794 pub fn restore_cpu_expert_residency_profile(
6798 &self,
6799 e: &Engine,
6800 path: &std::path::Path,
6801 ) -> Result<bool, Box<dyn std::error::Error>> {
6802 use crate::hybrid::Ffn;
6803 use crate::moe_cache::BlockId;
6804 let Ok(content) = std::fs::read_to_string(path) else {
6805 return Ok(false);
6806 };
6807 let mut lines = content.lines();
6808 let Some(header) = lines.next() else {
6809 return Ok(false);
6810 };
6811 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
6812 if !header.starts_with(&expected) {
6813 println!(
6814 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
6815 path.display()
6816 );
6817 return Ok(false);
6818 }
6819 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
6820 std::collections::HashMap::new();
6821 for line in lines {
6822 let mut fields = line.split_whitespace();
6823 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
6824 else {
6825 continue;
6826 };
6827 let (Ok(layer), Ok(proj), Ok(ex)) =
6828 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
6829 else {
6830 continue;
6831 };
6832 by_layer
6833 .entry(layer)
6834 .or_default()
6835 .push(BlockId::new(layer, proj, ex));
6836 }
6837 let requested: usize = by_layer.values().map(Vec::len).sum();
6838 if requested == 0 {
6839 return Ok(false);
6840 }
6841 let max_block = self.max_moe_block();
6842 let mut restaged = 0usize;
6843 let mut stage_layer =
6844 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
6845 let Ffn::Moe(m) = ffn else { return Ok(()) };
6846 let Some(ids) = by_layer.get(&layer_index) else {
6847 return Ok(());
6848 };
6849 e.with_moe_cache(max_block, |cache, eng| {
6850 for id in ids {
6851 if cache.restage_block(*id, m, eng)? {
6852 restaged += 1;
6853 }
6854 }
6855 Ok(())
6856 })
6857 };
6858 for (index, layer) in self.layers.iter().enumerate() {
6859 stage_layer(index as u16, &layer.ffn)?;
6860 }
6861 if let Some(mtp) = self.mtp.as_ref() {
6862 stage_layer(u16::MAX, &mtp.ffn)?;
6863 }
6864 e.freeze_moe_cache();
6865 println!(
6866 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
6867 path.display()
6868 );
6869 Ok(true)
6870 }
6871
6872 pub fn freeze_cpu_expert_residency(
6874 &self,
6875 e: &Engine,
6876 ) -> Result<(), Box<dyn std::error::Error>> {
6877 e.freeze_moe_cache();
6878 Ok(())
6879 }
6880
6881 pub fn ffn_act(
6889 e: &Engine,
6890 cfg: &ModelConfig,
6891 gate: &CudaSlice<f32>,
6892 up: &CudaSlice<f32>,
6893 act: &mut CudaSlice<f32>,
6894 n: usize,
6895 ) -> Result<(), Box<dyn std::error::Error>> {
6896 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
6897 }
6898
6899 #[allow(clippy::too_many_arguments)]
6903 pub(crate) fn ffn_act_scaled(
6904 e: &Engine,
6905 cfg: &ModelConfig,
6906 gate: &CudaSlice<f32>,
6907 up: &CudaSlice<f32>,
6908 gs: f32,
6909 us: f32,
6910 act: &mut CudaSlice<f32>,
6911 n: usize,
6912 ) -> Result<(), Box<dyn std::error::Error>> {
6913 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
6914 }
6915
6916 #[allow(clippy::too_many_arguments)]
6925 pub(crate) fn ffn_act_lim(
6926 e: &Engine,
6927 cfg: &ModelConfig,
6928 gate: &CudaSlice<f32>,
6929 up: &CudaSlice<f32>,
6930 gs: f32,
6931 us: f32,
6932 limit: Option<f32>,
6933 act: &mut CudaSlice<f32>,
6934 n: usize,
6935 ) -> Result<(), Box<dyn std::error::Error>> {
6936 if let Some(m3) = cfg.m3.as_ref() {
6937 debug_assert!(
6938 limit.is_none(),
6939 "m3 swigluoai and step35 clamp are different archs"
6940 );
6941 return e.swigluoai_mul_scaled(
6942 gate,
6943 up,
6944 gs,
6945 us,
6946 m3.swiglu_alpha,
6947 m3.swiglu_limit,
6948 act,
6949 n,
6950 );
6951 }
6952 if let Some(l) = limit {
6953 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
6954 }
6955 if gs == 1.0 && us == 1.0 {
6956 return e.silu_mul(gate, up, act, n);
6957 }
6958 e.silu_mul_scaled(gate, up, gs, us, act, n)
6959 }
6960
6961 fn moe_route(
6967 e: &Engine,
6968 logits: &CudaSlice<f32>,
6969 t: usize,
6970 n_expert: usize,
6971 n_used: usize,
6972 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6973 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
6974 }
6975
6976 #[allow(clippy::too_many_arguments)]
6984 fn moe_route_sigmoid_cfg(
6985 e: &Engine,
6986 logits: &CudaSlice<f32>,
6987 t: usize,
6988 n_expert: usize,
6989 n_used: usize,
6990 m: &MoeWeights,
6991 (sf, route_norm): (f32, bool),
6992 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6993 if sigmoid_router_enabled() {
6994 return e.moe_router_sigmoid_topk_host(
6995 logits,
6996 t,
6997 n_expert,
6998 n_used,
6999 m.active_count(),
7000 &m.exp_probs_b_dev,
7001 &m.active_experts_dev,
7002 sf,
7003 route_norm,
7004 );
7005 }
7006 let lg = e.dtoh(logits)?;
7007 Self::moe_route_sigmoid_host(
7008 &lg,
7009 t,
7010 n_expert,
7011 n_used,
7012 m.exp_probs_b.as_deref(),
7013 sf,
7014 route_norm,
7015 m.active_experts.as_deref(),
7016 )
7017 }
7018
7019 fn moe_route_cfg(
7022 e: &Engine,
7023 logits: &CudaSlice<f32>,
7024 t: usize,
7025 n_expert: usize,
7026 n_used: usize,
7027 active: Option<&[bool]>,
7028 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7029 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
7032 return e.moe_router_topk_host(logits, t, n_expert, n_used);
7033 }
7034 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
7037 let mut w_out = vec![0f32; t * n_used];
7038 for tok in 0..t {
7039 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7040 let maxl = row
7042 .iter()
7043 .enumerate()
7044 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
7045 .map(|(_, &x)| x)
7046 .fold(f32::NEG_INFINITY, f32::max);
7047 let mut probs = vec![0f32; n_expert];
7048 let mut den = 0f32;
7049 for i in 0..n_expert {
7050 if active.is_some_and(|mask| !mask[i]) {
7051 continue;
7052 }
7053 let x = (row[i] - maxl).exp();
7054 probs[i] = x;
7055 den += x;
7056 }
7057 for p in probs.iter_mut() {
7058 *p /= den;
7059 }
7060 let mut idx: Vec<usize> = (0..n_expert)
7062 .filter(|&i| active.is_none_or(|mask| mask[i]))
7063 .collect();
7064 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
7065 let sl = &idx[..n_used];
7066 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
7067 let mut ws: f32 = wv.iter().sum();
7068 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
7070 *x /= ws;
7071 }
7072 for j in 0..n_used {
7073 sel[tok * n_used + j] = sl[j] as u32;
7074 w_out[tok * n_used + j] = wv[j];
7075 }
7076 }
7077 Ok((sel, w_out))
7078 }
7079
7080 #[allow(clippy::too_many_arguments)]
7081 fn moe_route_sigmoid_with_input(
7082 e: &Engine,
7083 logits: &CudaSlice<f32>,
7084 input: &CudaSlice<f32>,
7085 t: usize,
7086 in_features: usize,
7087 n_expert: usize,
7088 n_used: usize,
7089 bias: Option<&[f32]>,
7090 (sf, route_norm): (f32, bool),
7091 active: Option<&[bool]>,
7092 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
7093 let logit_values =
7094 active_matrix_values(logits.len(), t, n_expert, "sigmoid router logits")?;
7095 let input_values =
7096 active_matrix_values(input.len(), t, in_features, "sigmoid router input")?;
7097 let (lg, input) = e.dtoh_pair_views(
7098 &logits.slice(0..logit_values),
7099 &input.slice(0..input_values),
7100 )?;
7101 let (sel, w) =
7102 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
7103 Ok((sel, w, input))
7104 }
7105
7106 pub fn start_moe_prefetch_predictor(
7111 &self,
7112 e: &Engine,
7113 cfg: &ModelConfig,
7114 ) -> Result<(), Box<dyn std::error::Error>> {
7115 use crate::hybrid::Ffn;
7116 let Some(sig) = cfg.sigmoid_router() else {
7117 return Err("prefetch predictor requires a sigmoid-router arch".into());
7118 };
7119 let resident: std::collections::HashSet<(u16, u8, u16)> = e
7120 .export_moe_residency()
7121 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
7122 .into_iter()
7123 .collect();
7124 let mut layers = Vec::new();
7125 for (index, layer) in self.layers.iter().enumerate() {
7126 let Ffn::Moe(m) = &layer.ffn else { continue };
7127 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
7128 continue;
7129 };
7130 let router = e.dtoh(data)?;
7131 let n_expert = m.gate_exps.n_expert;
7132 let n_embd = m.gate_exps.in_f;
7133 if router.len() != n_embd * n_expert {
7134 continue;
7135 }
7136 let build = |exps: &crate::model::HostExps| {
7137 (0..n_expert)
7138 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
7139 .collect::<Vec<_>>()
7140 };
7141 layers.push((
7142 index as u16,
7143 crate::cpu_experts::PredictLayerInit {
7144 router,
7145 bias: m.exp_probs_b.clone(),
7146 active: m.active_experts.clone(),
7147 n_embd,
7148 n_used: cfg
7149 .moe
7150 .as_ref()
7151 .map(|moe| moe.expert_used_count as usize)
7152 .ok_or("prefetch predictor requires MoE config")?,
7153 sig,
7154 weights_n_expert: n_expert,
7155 gate: build(&m.gate_exps),
7156 up: build(&m.up_exps),
7157 down: build(&m.down_exps),
7158 },
7159 ));
7160 }
7161 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
7162 }
7163
7164 #[allow(clippy::too_many_arguments)]
7167 pub fn moe_route_sigmoid_host_public(
7168 logits: &[f32],
7169 t: usize,
7170 n_expert: usize,
7171 n_used: usize,
7172 bias: Option<&[f32]>,
7173 sf: f32,
7174 route_norm: bool,
7175 active: Option<&[bool]>,
7176 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7177 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
7178 }
7179
7180 #[allow(clippy::too_many_arguments)]
7181 fn moe_route_sigmoid_host(
7182 lg: &[f32],
7183 t: usize,
7184 n_expert: usize,
7185 n_used: usize,
7186 bias: Option<&[f32]>,
7187 sf: f32,
7188 route_norm: bool,
7189 active: Option<&[bool]>,
7190 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7191 let active_count = active
7192 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
7193 .unwrap_or(n_expert);
7194 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
7195 if lg.len() != t * n_expert {
7196 return Err(format!(
7197 "sigmoid router logits length mismatch: got {}, expected {}",
7198 lg.len(),
7199 t * n_expert,
7200 )
7201 .into());
7202 }
7203 let mut sel = vec![0u32; t * n_used];
7204 let mut w_out = vec![0f32; t * n_used];
7205 for tok in 0..t {
7206 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7207 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
7208 let selsc: Vec<f32> = match bias {
7210 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
7211 None => scores.clone(),
7212 };
7213 let mut idx: Vec<usize> = (0..n_expert)
7214 .filter(|&i| active.is_none_or(|mask| mask[i]))
7215 .collect();
7216 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
7217 let sl = &idx[..n_used];
7218 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
7219 if route_norm {
7220 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
7221 for x in wv.iter_mut() {
7222 *x = *x / ws * sf;
7223 }
7224 } else {
7225 for x in wv.iter_mut() {
7226 *x *= sf;
7227 }
7228 }
7229 for j in 0..n_used {
7230 sel[tok * n_used + j] = sl[j] as u32;
7231 w_out[tok * n_used + j] = wv[j];
7232 }
7233 }
7234 Ok((sel, w_out))
7235 }
7236
7237 #[allow(clippy::too_many_arguments)]
7241 fn moe_ffn_sigmoid_dev(
7242 e: &Engine,
7243 m: &MoeWeights,
7244 z: &CudaSlice<f32>,
7245 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7246 logits: &CudaSlice<f32>,
7247 t: usize,
7248 cfg: &ModelConfig,
7249 il: u16,
7250 (scaling_factor, route_norm): (f32, bool),
7251 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7252 let moe = cfg.moe.as_ref().unwrap();
7253 let n_embd = cfg.n_embd as usize;
7254 let n_expert = moe.expert_count as usize;
7255 let n_used = moe.expert_used_count as usize;
7256 let n_ff_exp = moe.expert_ff_length as usize;
7257 let dev = m.dev_exps.as_ref().unwrap();
7258 debug_assert_eq!(dev.dev, e.ctx().ordinal());
7259 debug_assert!(m.has_uniform_expert_layout());
7260 debug_assert!(!m.has_macros);
7261
7262 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
7263 logits,
7264 t,
7265 n_expert,
7266 n_used,
7267 m.active_count(),
7268 &m.exp_probs_b_dev,
7269 &m.active_experts_dev,
7270 scaling_factor,
7271 route_norm,
7272 )?;
7273 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
7274 if let Some(fp8) = dev.fp8_blk.as_ref() {
7275 debug_assert_eq!(m.gate_exps.qtype, crate::QT_F8_E4M3_BLK);
7276 debug_assert_eq!(m.up_exps.qtype, crate::QT_F8_E4M3_BLK);
7277 debug_assert_eq!(m.down_exps.qtype, crate::QT_F8_E4M3_BLK);
7278 debug_assert_eq!(fp8.gate.rows, m.gate_exps.out_f.div_ceil(128));
7279 debug_assert_eq!(fp8.up.rows, m.up_exps.out_f.div_ceil(128));
7280 debug_assert_eq!(fp8.down.rows, m.down_exps.out_f.div_ceil(128));
7281
7282 let selected = e.dtoh_i32(&sel_d)?;
7289 let route_weights = e.dtoh(&w_d)?;
7290 let mut moe_out = e.zeros(t * n_embd)?;
7291 for tok in 0..t {
7292 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
7293 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7294 for j in 0..n_used {
7295 let pair = tok * n_used + j;
7296 let expert = selected[pair] as usize;
7297 let gate = Self::moe_resident_fp8_e4m3(
7298 e,
7299 &m.gate_exps,
7300 &dev.gate,
7301 &fp8.gate,
7302 expert,
7303 &zt,
7304 1,
7305 )?;
7306 let up = Self::moe_resident_fp8_e4m3(
7307 e, &m.up_exps, &dev.up, &fp8.up, expert, &zt, 1,
7308 )?;
7309 let mut act = e.uninit(n_ff_exp)?;
7310 Self::ffn_act_lim(
7311 e,
7312 cfg,
7313 &gate,
7314 &up,
7315 1.0,
7316 1.0,
7317 cfg.clamp_exp_at(il as u32),
7318 &mut act,
7319 n_ff_exp,
7320 )?;
7321 let act = act.slice(0..n_ff_exp);
7322 let down = Self::moe_resident_fp8_e4m3(
7323 e,
7324 &m.down_exps,
7325 &dev.down,
7326 &fp8.down,
7327 expert,
7328 &act,
7329 1,
7330 )?;
7331 e.axpy_into(&down, route_weights[pair], &mut dst, n_embd)?;
7332 }
7333 }
7334 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7335 eprintln!(
7336 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} \
7337 native=fp8blk-w8a8-e4m3-reference clamp={}",
7338 cfg.clamp_exp_at(il as u32).is_some(),
7339 );
7340 }
7341 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7342 return Ok(moe_out);
7343 }
7344 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
7345 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7346 (combined, combined)
7347 } else {
7348 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7349 };
7350 let (zq, zd) = match (t, zq8) {
7351 (1, Some((q, d))) => (q.clone(), d.clone()),
7352 _ => e.quantize_q8_1(z, t, n_embd)?,
7353 };
7354 let n_pairs = t * n_used;
7355 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
7356 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7360 let pair_tok_d = e.htod_i32(&pair_tok)?;
7361 let gate = e.moe_pairs_matvec_q8(
7362 &dev.ptr_row,
7363 0,
7364 &pair_tok_d,
7365 &sel_d,
7366 &zq,
7367 &zd,
7368 n_embd,
7369 n_ff_exp,
7370 n_expert,
7371 n_pairs,
7372 m.gate_exps.qtype,
7373 gate_row_bytes,
7374 )?;
7375 let up = e.moe_pairs_matvec_q8(
7376 &dev.ptr_row,
7377 1,
7378 &pair_tok_d,
7379 &sel_d,
7380 &zq,
7381 &zd,
7382 n_embd,
7383 n_ff_exp,
7384 n_expert,
7385 n_pairs,
7386 m.up_exps.qtype,
7387 up_row_bytes,
7388 )?;
7389 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7390 Self::ffn_act_lim(
7391 e,
7392 cfg,
7393 &gate,
7394 &up,
7395 1.0,
7396 1.0,
7397 cfg.clamp_exp_at(il as u32),
7398 &mut act,
7399 n_pairs * n_ff_exp,
7400 )?;
7401 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7402 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7403 let pair_self_d = e.htod_i32(&pair_self)?;
7404 let down = e.moe_pairs_matvec_q8(
7405 &dev.ptr_row,
7406 2,
7407 &pair_self_d,
7408 &sel_d,
7409 &aq2,
7410 &ad2,
7411 n_ff_exp,
7412 n_embd,
7413 n_expert,
7414 n_pairs,
7415 m.down_exps.qtype,
7416 m.down_exps.row_bytes,
7417 )?;
7418 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7419 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7420 let tok_off_d = e.htod_i32(&tok_off)?;
7421 let tok_ids_d = e.htod_i32(&tok_ids)?;
7422 let mut output = e.uninit(t * n_embd)?;
7423 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
7424 output
7425 } else {
7426 let act = e.moe_gate_up_silu8_dev_q8_rows(
7427 &dev.ptr_row,
7428 &sel_d,
7429 &zq,
7430 &zd,
7431 t,
7432 n_embd,
7433 n_ff_exp,
7434 n_used,
7435 n_expert,
7436 m.gate_exps.qtype,
7437 m.up_exps.qtype,
7438 gate_row_bytes,
7439 up_row_bytes,
7440 &m.dev_macros,
7441 )?;
7442 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7443 let mut output = e.uninit(t * n_embd)?;
7444 e.moe_down8_fma_dev_q8_rows_g(
7445 &dev.ptr_row,
7446 &sel_d,
7447 &w_d,
7448 &aq2,
7449 &ad2,
7450 &mut output,
7451 t,
7452 n_ff_exp,
7453 n_embd,
7454 n_used,
7455 n_expert,
7456 m.down_exps.qtype,
7457 m.down_exps.row_bytes,
7458 )?;
7459 output
7460 };
7461
7462 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7463 eprintln!(
7464 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
7465 cfg.clamp_exp_at(il as u32).is_some(),
7466 dev.gu_il,
7467 );
7468 }
7469 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7470 Ok(moe_out)
7471 }
7472
7473 #[allow(clippy::too_many_arguments)]
7474 fn moe_resident_fp8_e4m3(
7475 e: &Engine,
7476 exps: &crate::model::HostExps,
7477 bytes: &CudaSlice<u8>,
7478 scales: &crate::hybrid::DevExpertFp8ProjectionScales,
7479 expert: usize,
7480 x: &cudarc::driver::CudaView<f32>,
7481 m: usize,
7482 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7483 let layout = exps.expert_layout(expert);
7484 debug_assert_eq!(layout.qtype, crate::QT_F8_E4M3_BLK);
7485 debug_assert_eq!(scales.rows * scales.cols, scales.expert_stride);
7486 let byte_start = expert * exps.expert_stride;
7487 let scale_start = expert * scales.expert_stride;
7488 let weight = bytes.slice(byte_start..byte_start + layout.len);
7489 let scale = scales
7490 .scales
7491 .slice(scale_start..scale_start + scales.expert_stride);
7492 e.qmatvec_mmq_fp8_blk_view(&weight, &scale, x, m, exps.in_f, exps.out_f)
7493 }
7494
7495 fn moe_ffn_pairs(
7504 e: &Engine,
7505 m: &MoeWeights,
7506 z: &CudaSlice<f32>,
7507 logits: &CudaSlice<f32>,
7508 t: usize,
7509 cfg: &ModelConfig,
7510 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7511 let moe = cfg.moe.as_ref().unwrap();
7512 let n_embd = cfg.n_embd as usize;
7513 let n_expert = moe.expert_count as usize;
7514 let n_used = moe.expert_used_count as usize;
7515 let n_ff_exp = moe.expert_ff_length as usize;
7516 debug_assert!(
7521 !cfg.swiglu_clamped_anywhere(),
7522 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
7523 );
7524 let dev = m.dev_exps.as_ref().unwrap();
7525 let (rbg_d, rbu_d) = if dev.gu_il {
7527 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7528 (sxx, sxx)
7529 } else {
7530 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7531 };
7532
7533 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
7534 let n_pairs = t * n_used;
7535 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
7538 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
7539 let pair_w: Vec<f32> = w_all.clone();
7540 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7541 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7542 let pt = e.htod_i32(&pair_tok)?;
7543 let px = e.htod_i32(&pair_ex)?;
7544 let pw = e.htod(&pair_w)?;
7545 let toff = e.htod_i32(&tok_off)?;
7546 let tids = e.htod_i32(&tok_ids)?;
7547
7548 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7552 for p in 0..n_pairs {
7553 by_ex[pair_ex[p] as usize].push(p as i32);
7554 }
7555 let mut ex_ids: Vec<i32> = Vec::new();
7556 let mut ex_off: Vec<i32> = vec![0];
7557 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
7558 for (ex, list) in by_ex.iter().enumerate() {
7559 if list.is_empty() {
7560 continue;
7561 }
7562 ex_ids.push(ex as i32);
7563 ex_pairs.extend_from_slice(list);
7564 ex_off.push(ex_pairs.len() as i32);
7565 }
7566 let n_active = ex_ids.len();
7567 let exi = e.htod_i32(&ex_ids)?;
7568 let exo = e.htod_i32(&ex_off)?;
7569 let exp_d = e.htod_i32(&ex_pairs)?;
7570 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7591 let mma_t = *MMA_T.get_or_init(|| {
7592 std::env::var("MEMRA_MOE_MMA_T")
7593 .ok()
7594 .and_then(|v| v.parse().ok())
7595 .unwrap_or(16)
7596 });
7597 let use_mma = std::env::var("MEMRA_MOE_MMA")
7598 .map(|v| v != "0")
7599 .unwrap_or(true)
7600 && t >= mma_t
7601 && q8_expert_dec_supported(m.gate_exps.qtype)
7602 && q8_expert_dec_supported(m.up_exps.qtype)
7603 && q8_expert_dec_supported(m.down_exps.qtype)
7604 && n_embd % 256 == 0
7605 && n_ff_exp % 256 == 0;
7606 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
7622 && q8_expert_dec_supported(m.up_exps.qtype)
7623 && q8_expert_dec_supported(m.down_exps.qtype)
7624 && n_embd % 256 == 0
7625 && n_ff_exp % 256 == 0;
7626 let f16g_mode = crate::moe_f16g_mode();
7627 let f16g = f16g_mode != 0
7628 && t >= mma_t
7629 && (f16g_mode != 3 || !mma_capable)
7630 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
7631 && f16g_proj_ok(m.up_exps.qtype, n_embd)
7632 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
7633 if use_mma || f16g {
7634 let y_down = if f16g {
7642 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
7646 let csr_tok_d = e.htod_i32(&csr_tok)?;
7647 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
7648 let g_csr = e.moe_f16_grouped(
7649 &dev.ptr_row,
7650 0,
7651 n_expert,
7652 &exi,
7653 &ex_off,
7654 &exo,
7655 &z_f16,
7656 &z_s,
7657 n_embd,
7658 n_ff_exp,
7659 n_active,
7660 n_pairs,
7661 m.gate_exps.qtype,
7662 rbg_d,
7663 )?;
7664 let u_csr = e.moe_f16_grouped(
7665 &dev.ptr_row,
7666 1,
7667 n_expert,
7668 &exi,
7669 &ex_off,
7670 &exo,
7671 &z_f16,
7672 &z_s,
7673 n_embd,
7674 n_ff_exp,
7675 n_active,
7676 n_pairs,
7677 m.up_exps.qtype,
7678 rbu_d,
7679 )?;
7680 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
7681 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
7682 let d_csr = e.moe_f16_grouped(
7683 &dev.ptr_row,
7684 2,
7685 n_expert,
7686 &exi,
7687 &ex_off,
7688 &exo,
7689 &a_f16,
7690 &a_s,
7691 n_ff_exp,
7692 n_embd,
7693 n_active,
7694 n_pairs,
7695 m.down_exps.qtype,
7696 m.down_exps.row_bytes,
7697 )?;
7698 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
7699 } else {
7700 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
7702 let gate = e.mmq_iq_experts(
7703 &dev.ptr_row,
7704 0,
7705 n_expert,
7706 &exi,
7707 &exo,
7708 &exp_d,
7709 &pt,
7710 &z_scr,
7711 n_embd,
7712 n_ff_exp,
7713 n_active,
7714 n_pairs,
7715 t,
7716 m.gate_exps.qtype,
7717 rbg_d,
7718 )?;
7719 let up = e.mmq_iq_experts(
7720 &dev.ptr_row,
7721 1,
7722 n_expert,
7723 &exi,
7724 &exo,
7725 &exp_d,
7726 &pt,
7727 &z_scr,
7728 n_embd,
7729 n_ff_exp,
7730 n_active,
7731 n_pairs,
7732 t,
7733 m.up_exps.qtype,
7734 rbu_d,
7735 )?;
7736 let a_scr = if crate::moe_fuse_actq_on() {
7742 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
7743 } else {
7744 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7745 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
7746 };
7747 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7748 let pself = e.htod_i32(&pair_self)?;
7749 e.mmq_iq_experts(
7750 &dev.ptr_row,
7751 2,
7752 n_expert,
7753 &exi,
7754 &exo,
7755 &exp_d,
7756 &pself,
7757 &a_scr,
7758 n_ff_exp,
7759 n_embd,
7760 n_active,
7761 n_pairs,
7762 n_pairs,
7763 m.down_exps.qtype,
7764 m.down_exps.row_bytes,
7765 )?
7766 };
7767 let mut moe_out = e.uninit(t * n_embd)?;
7768 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7769 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7770 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7771 {
7772 let n_ff_sh = gate_shexp.out_features();
7773 let sg_gate = e.matmul(gate_shexp, z, t)?;
7774 let sg_up = e.matmul(up_shexp, z, t)?;
7775 let mut sa = e.uninit(t * n_ff_sh)?;
7776 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7777 let sh = e.matmul(down_shexp, &sa, t)?;
7778 let g = match &m.gate_inp_shexp {
7784 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7785 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7786 }
7787 Some(gate_inp_shexp) => {
7788 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7789 let mut g = e.uninit(t)?;
7790 e.sigmoid(&gs, &mut g, t)?;
7791 g
7792 }
7793 None => e.htod(&vec![1.0f32; t])?,
7794 };
7795 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7796 }
7797 return Ok(moe_out);
7798 }
7799
7800 let dec = std::env::var("MEMRA_MOE_DEC")
7803 .map(|v| v != "0")
7804 .unwrap_or(true);
7805 let matvec = |proj,
7806 exi: &_,
7807 exo: &_,
7808 exp_d: &_,
7809 pt: &_,
7810 aq: &_,
7811 ad: &_,
7812 inf,
7813 outf,
7814 qtype,
7815 rb|
7816 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7817 let dec = dec && q8_expert_dec_supported(qtype);
7819 if dec {
7820 e.moe_pairs_matvec_q8_dec(
7821 &dev.ptr_row,
7822 proj,
7823 exi,
7824 exo,
7825 exp_d,
7826 pt,
7827 aq,
7828 ad,
7829 inf,
7830 outf,
7831 n_expert,
7832 n_active,
7833 n_pairs,
7834 qtype,
7835 rb,
7836 )
7837 } else {
7838 e.moe_pairs_matvec_q8_em(
7839 &dev.ptr_row,
7840 proj,
7841 exi,
7842 exo,
7843 exp_d,
7844 pt,
7845 aq,
7846 ad,
7847 inf,
7848 outf,
7849 n_expert,
7850 n_active,
7851 n_pairs,
7852 qtype,
7853 rb,
7854 )
7855 }
7856 };
7857 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7858 let gate = matvec(
7859 0,
7860 &exi,
7861 &exo,
7862 &exp_d,
7863 &pt,
7864 &zq,
7865 &zd,
7866 n_embd,
7867 n_ff_exp,
7868 m.gate_exps.qtype,
7869 rbg_d,
7870 )?;
7871 let up = matvec(
7872 1,
7873 &exi,
7874 &exo,
7875 &exp_d,
7876 &pt,
7877 &zq,
7878 &zd,
7879 n_embd,
7880 n_ff_exp,
7881 m.up_exps.qtype,
7882 rbu_d,
7883 )?;
7884 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7885 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7886 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7888 let pself = e.htod_i32(&pair_self)?;
7889 let y_down = matvec(
7890 2,
7891 &exi,
7892 &exo,
7893 &exp_d,
7894 &pself,
7895 &aq2,
7896 &ad2,
7897 n_ff_exp,
7898 n_embd,
7899 m.down_exps.qtype,
7900 m.down_exps.row_bytes,
7901 )?;
7902 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7904
7905 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7909 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7910 {
7911 let n_ff_sh = gate_shexp.out_features();
7912 let step_exact = true;
7916 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
7917 let (sg_gate, sg_up) = if step_exact && t == 1 {
7918 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, None)?
7919 } else if verify_t {
7920 let mut fused = None;
7921 if crate::spec::spec_fused_t()
7922 && (2..=4).contains(&t)
7923 && e.uses_q8_1_fast(gate_shexp)
7924 && e.uses_q8_1_fast(up_shexp)
7925 {
7926 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7927 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
7928 }
7929 match fused {
7930 Some(pair) => pair,
7931 None => (
7932 e.matmul_decode_exact(gate_shexp, z, t)?,
7933 e.matmul_decode_exact(up_shexp, z, t)?,
7934 ),
7935 }
7936 } else {
7937 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
7938 };
7939 let mut sa = e.uninit(t * n_ff_sh)?;
7940 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7941 let sh = if verify_t {
7942 e.matmul_decode_exact(down_shexp, &sa, t)?
7943 } else {
7944 e.matmul(down_shexp, &sa, t)?
7945 };
7946 let g = match &m.gate_inp_shexp {
7951 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7952 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7953 }
7954 Some(gate_inp_shexp) => {
7955 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7956 let mut g = e.uninit(t)?;
7957 e.sigmoid(&gs, &mut g, t)?;
7958 g
7959 }
7960 None => e.htod(&vec![1.0f32; t])?,
7961 };
7962 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7963 }
7964 Ok(moe_out)
7965 }
7966
7967 #[allow(clippy::too_many_arguments)]
7969 #[allow(clippy::too_many_arguments)]
7970 fn moe_ffn_dev(
7971 e: &Engine,
7972 m: &MoeWeights,
7973 z: &CudaSlice<f32>,
7974 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7975 logits: &CudaSlice<f32>,
7976 t: usize,
7977 cfg: &ModelConfig,
7978 il: u16,
7979 max_block: usize,
7980 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7981 let moe = cfg.moe.as_ref().unwrap();
7982 let n_embd = cfg.n_embd as usize;
7983 let n_expert = moe.expert_count as usize;
7984 let n_used = moe.expert_used_count as usize;
7985 let n_ff_exp = moe.expert_ff_length as usize;
7986 debug_assert!(
7990 cfg.sigmoid_router().is_none(),
7991 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
7992 );
7993 debug_assert!(
7994 !cfg.swiglu_clamped_at(il as u32),
7995 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
7996 );
7997
7998 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
8000 if m.has_macros {
8003 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
8004 }
8005
8006 let mut moe_out = e.uninit(t * n_embd)?;
8008
8009 if let Some(dev) = m.dev_exps.as_ref() {
8012 let (rbg_d, rbu_d) = if dev.gu_il {
8015 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8016 (sxx, sxx)
8017 } else {
8018 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8019 };
8020 let q8 = moe_q8_enabled()
8021 && q8_expert_supported(m.gate_exps.qtype)
8022 && q8_expert_supported(m.up_exps.qtype)
8023 && q8_expert_supported(m.down_exps.qtype);
8024 let rows_arm = q8
8033 && t > 1
8034 && crate::spec::spec_m2()
8035 && n_ff_exp == 512
8036 && n_used <= 8
8037 && std::env::var("MEMRA_MOE_DEVQ8_GU")
8038 .map(|v| v.is_empty() || v == "v")
8039 .unwrap_or(true)
8040 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
8041 .map(|v| v.is_empty() || v == "w8h2v")
8042 .unwrap_or(true);
8043 let csr_mode = std::env::var("MEMRA_MOE_CSR")
8052 .ok()
8053 .and_then(|v| v.parse::<i32>().ok())
8054 .unwrap_or(1);
8055 let csr_nvfp4_probe = std::env::var("MEMRA_MOE_CSR_NVFP4").as_deref() == Ok("1");
8072 let csr_qt = |qt: i32| {
8073 qt == crate::QT_IQ4_XS
8074 || qt == crate::QT_IQ3_S
8075 || (csr_nvfp4_probe && qt == crate::QT_NVFP4)
8076 };
8077 let csr_t_max = if csr_nvfp4_probe { MOE_DEV_MAX_T } else { 10 };
8078 let csr_uniform = m.gate_exps.qtype == m.up_exps.qtype;
8079 let csr_arm = rows_arm
8080 && csr_mode > 0
8081 && t <= csr_t_max
8082 && csr_uniform
8083 && csr_qt(m.gate_exps.qtype)
8084 && csr_qt(m.up_exps.qtype)
8085 && csr_qt(m.down_exps.qtype);
8086 if csr_arm {
8087 if csr_mode == 2 {
8088 static ENGAGED: std::sync::Once = std::sync::Once::new();
8089 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
8090 }
8091 let n_pairs = t * n_used;
8092 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8093 let act = e.moe_gate_up_silu8_dev_q8_csr(
8094 &dev.ptr_row,
8095 &sel_d,
8096 &zq,
8097 &zd,
8098 n_pairs,
8099 n_embd,
8100 n_ff_exp,
8101 n_used,
8102 n_expert,
8103 m.gate_exps.qtype,
8104 m.up_exps.qtype,
8105 rbg_d,
8106 rbu_d,
8107 )?;
8108 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8109 e.moe_down8_fma_dev_q8_rows(
8113 &dev.ptr_row,
8114 &sel_d,
8115 &w_d,
8116 &aq2,
8117 &ad2,
8118 &mut moe_out,
8119 t,
8120 n_ff_exp,
8121 n_embd,
8122 n_used,
8123 n_expert,
8124 m.down_exps.qtype,
8125 m.down_exps.row_bytes,
8126 )?;
8127 if csr_mode == 2 {
8128 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
8130 &dev.ptr_row,
8131 &sel_d,
8132 &zq,
8133 &zd,
8134 t,
8135 n_embd,
8136 n_ff_exp,
8137 n_used,
8138 n_expert,
8139 m.gate_exps.qtype,
8140 m.up_exps.qtype,
8141 rbg_d,
8142 rbu_d,
8143 &m.dev_macros,
8144 )?;
8145 let mut out_r = e.uninit(t * n_embd)?;
8146 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
8147 e.moe_down8_fma_dev_q8_rows(
8148 &dev.ptr_row,
8149 &sel_d,
8150 &w_d,
8151 &aq2r,
8152 &ad2r,
8153 &mut out_r,
8154 t,
8155 n_ff_exp,
8156 n_embd,
8157 n_used,
8158 n_expert,
8159 m.down_exps.qtype,
8160 m.down_exps.row_bytes,
8161 )?;
8162 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
8163 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
8164 let ba = a1
8165 .iter()
8166 .zip(&a2)
8167 .filter(|(x, y)| x.to_bits() != y.to_bits())
8168 .count();
8169 let bo = o1
8170 .iter()
8171 .zip(&o2)
8172 .filter(|(x, y)| x.to_bits() != y.to_bits())
8173 .count();
8174 if ba + bo > 0 {
8175 eprintln!(
8176 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
8177 a1.len(),
8178 o1.len()
8179 );
8180 let sel_h = e.dtoh_i32(&sel_d)?;
8182 let mut shown = 0;
8183 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
8184 if x.to_bits() != y.to_bits() && shown < 4 {
8185 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
8186 let ex = sel_h[p];
8187 let npx = sel_h.iter().filter(|&&v| v == ex).count();
8188 eprintln!(
8189 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
8190 );
8191 shown += 1;
8192 }
8193 }
8194 std::process::exit(3);
8195 }
8196 }
8197 } else if rows_arm {
8198 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
8201 use std::sync::atomic::{AtomicU64, Ordering};
8202 static PAIRS: AtomicU64 = AtomicU64::new(0);
8203 static UNIQ: AtomicU64 = AtomicU64::new(0);
8204 static CALLS: AtomicU64 = AtomicU64::new(0);
8205 let sel_h = e.dtoh_i32(&sel_d)?;
8206 let mut u: Vec<i32> = sel_h.clone();
8207 u.sort_unstable();
8208 u.dedup();
8209 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
8210 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
8211 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
8212 if c % 480 == 0 {
8213 let p = PAIRS.load(Ordering::Relaxed);
8214 let q = UNIQ.load(Ordering::Relaxed);
8215 eprintln!(
8216 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
8217 q as f64 / p as f64
8218 );
8219 }
8220 }
8221 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8222 let act = e.moe_gate_up_silu8_dev_q8_rows(
8223 &dev.ptr_row,
8224 &sel_d,
8225 &zq,
8226 &zd,
8227 t,
8228 n_embd,
8229 n_ff_exp,
8230 n_used,
8231 n_expert,
8232 m.gate_exps.qtype,
8233 m.up_exps.qtype,
8234 rbg_d,
8235 rbu_d,
8236 &m.dev_macros,
8237 )?;
8238 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8239 e.moe_down8_fma_dev_q8_rows(
8240 &dev.ptr_row,
8241 &sel_d,
8242 &w_d,
8243 &aq2,
8244 &ad2,
8245 &mut moe_out,
8246 t,
8247 n_ff_exp,
8248 n_embd,
8249 n_used,
8250 n_expert,
8251 m.down_exps.qtype,
8252 m.down_exps.row_bytes,
8253 )?;
8254 } else {
8255 for tok in 0..t {
8256 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8257 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8258 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8259 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8260 if q8 {
8261 let (zq, zd) = match (t, zq8) {
8262 (1, Some((q, d))) => (q.clone(), d.clone()),
8263 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
8264 };
8265 let act = e.moe_gate_up_silu8_dev_q8(
8266 &dev.ptr_row,
8267 &selt,
8268 &zq,
8269 &zd,
8270 n_embd,
8271 n_ff_exp,
8272 n_used,
8273 n_expert,
8274 m.gate_exps.qtype,
8275 m.up_exps.qtype,
8276 rbg_d,
8277 rbu_d,
8278 &m.dev_macros,
8279 )?;
8280 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8281 e.moe_down8_fma_dev_q8(
8282 &dev.ptr_row,
8283 &selt,
8284 &wt,
8285 &aq2,
8286 &ad2,
8287 &mut dst,
8288 n_ff_exp,
8289 n_embd,
8290 n_used,
8291 n_expert,
8292 m.down_exps.qtype,
8293 m.down_exps.row_bytes,
8294 )?;
8295 } else {
8296 let act = e.moe_gate_up_silu8_dev(
8297 &dev.ptr_row,
8298 &selt,
8299 &zt,
8300 n_embd,
8301 n_ff_exp,
8302 n_used,
8303 n_expert,
8304 m.gate_exps.qtype,
8305 m.up_exps.qtype,
8306 rbg_d,
8307 rbu_d,
8308 &m.dev_macros,
8309 )?;
8310 e.moe_down8_fma_dev(
8311 &dev.ptr_row,
8312 &selt,
8313 &wt,
8314 &act,
8315 &mut dst,
8316 n_ff_exp,
8317 n_embd,
8318 n_used,
8319 n_expert,
8320 m.down_exps.qtype,
8321 m.down_exps.row_bytes,
8322 )?;
8323 }
8324 }
8325 }
8326 } else {
8327 let q8 = moe_q8_enabled()
8334 && q8_expert_supported(m.gate_exps.qtype)
8335 && q8_expert_supported(m.up_exps.qtype)
8336 && q8_expert_supported(m.down_exps.qtype);
8337 e.with_moe_cache(max_block, |c, eng| {
8338 let row = c
8339 .layer_dev_row(il, n_expert, eng)?
8340 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
8341 for tok in 0..t {
8342 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8343 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8344 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8345 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8346 if q8 {
8347 let (zq, zd) = match (t, zq8) {
8348 (1, Some((q, d))) => (q.clone(), d.clone()),
8349 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
8350 };
8351 let act = eng.moe_gate_up_silu8_dev_q8(
8352 row,
8353 &selt,
8354 &zq,
8355 &zd,
8356 n_embd,
8357 n_ff_exp,
8358 n_used,
8359 n_expert,
8360 m.gate_exps.qtype,
8361 m.up_exps.qtype,
8362 m.gate_exps.row_bytes,
8363 m.up_exps.row_bytes,
8364 &m.dev_macros,
8365 )?;
8366 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
8367 eng.moe_down8_fma_dev_q8(
8368 row,
8369 &selt,
8370 &wt,
8371 &aq2,
8372 &ad2,
8373 &mut dst,
8374 n_ff_exp,
8375 n_embd,
8376 n_used,
8377 n_expert,
8378 m.down_exps.qtype,
8379 m.down_exps.row_bytes,
8380 )?;
8381 } else {
8382 let act = eng.moe_gate_up_silu8_dev(
8383 row,
8384 &selt,
8385 &zt,
8386 n_embd,
8387 n_ff_exp,
8388 n_used,
8389 n_expert,
8390 m.gate_exps.qtype,
8391 m.up_exps.qtype,
8392 m.gate_exps.row_bytes,
8393 m.up_exps.row_bytes,
8394 &m.dev_macros,
8395 )?;
8396 eng.moe_down8_fma_dev(
8397 row,
8398 &selt,
8399 &wt,
8400 &act,
8401 &mut dst,
8402 n_ff_exp,
8403 n_embd,
8404 n_used,
8405 n_expert,
8406 m.down_exps.qtype,
8407 m.down_exps.row_bytes,
8408 )?;
8409 }
8410 }
8411 c.hits += (t * 3 * n_used) as u64;
8413 Ok(())
8414 })?;
8415 }
8416
8417 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8422 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8423 {
8424 let n_ff_sh = gate_shexp.out_features();
8425 let verify_t = t > 1 && t < PRIME_MIN_T;
8428 let (sg_gate, sg_up) = if t == 1 {
8429 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
8430 } else if verify_t {
8431 let mut fused = None;
8435 if crate::spec::spec_fused_t()
8436 && (2..=4).contains(&t)
8437 && e.uses_q8_1_fast(gate_shexp)
8438 && e.uses_q8_1_fast(up_shexp)
8439 {
8440 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8441 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8442 }
8443 match fused {
8444 Some(pair) => pair,
8445 None => (
8446 e.matmul_decode_exact(gate_shexp, z, t)?,
8447 e.matmul_decode_exact(up_shexp, z, t)?,
8448 ),
8449 }
8450 } else {
8451 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8452 };
8453 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8455 let sh = if verify_t {
8456 e.matmul_decode_exact(down_shexp, &sa, t)?
8457 } else {
8458 e.matmul(down_shexp, &sa, t)?
8459 };
8460 let g = match &m.gate_inp_shexp {
8464 Some(gate_inp_shexp) => {
8465 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
8468 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8469 } else {
8470 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8471 let mut g = e.uninit(t)?;
8472 e.sigmoid(&gs, &mut g, t)?;
8473 g
8474 }
8475 }
8476 None => e.htod(&vec![1.0f32; t])?,
8477 };
8478 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8479 }
8480
8481 Ok(moe_out)
8482 }
8483
8484 #[allow(clippy::too_many_arguments)]
8494 #[allow(clippy::too_many_arguments)]
8497 fn moe_gdec_token_q8(
8498 e: &Engine,
8499 m: &MoeWeights,
8500 il: u16,
8501 max_block: usize,
8502 zq: &CudaSlice<i8>,
8503 zd: &CudaSlice<f32>,
8504 sel: &[u32],
8505 w: &[f32],
8506 moe_out: &mut CudaSlice<f32>,
8507 tok: usize,
8508 n_embd: usize,
8509 n_ff_exp: usize,
8510 n_used: usize,
8511 ) -> Result<bool, Box<dyn std::error::Error>> {
8512 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8513 use cudarc::driver::DevicePtr;
8514 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8515 let mut g = [0u64; 8];
8516 let mut u = [0u64; 8];
8517 let mut d = [0u64; 8];
8518 for (j, &ex) in sel.iter().enumerate() {
8519 let ex = ex as u16;
8520 let (Some(sg), Some(su), Some(sd)) = (
8521 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8522 c.resident(BlockId::new(il, PROJ_UP, ex)),
8523 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8524 ) else {
8525 return Ok(None);
8526 };
8527 let __s = eng.stream();
8528 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8529 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8530 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8531 g[j] = pg as u64;
8532 u[j] = pu as u64;
8533 d[j] = pd as u64;
8534 }
8535 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8536 for &ex in sel {
8537 let ex = ex as u16;
8538 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8539 c.note_profile_hit(BlockId::new(il, proj, ex));
8540 }
8541 }
8542 }
8543 c.hits += (3 * n_used) as u64;
8544 Ok(Some((g, u, d)))
8545 })?;
8546 let Some((g, u, d)) = ptrs else {
8547 return Ok(false);
8548 };
8549 let mut wv = [0f32; 8];
8550 wv[..n_used].copy_from_slice(w);
8551 let act = e.moe_gate_up_silu8_q8(
8552 crate::WPtr8(g),
8553 crate::WPtr8(u),
8554 zq,
8555 zd,
8556 n_embd,
8557 n_ff_exp,
8558 n_used,
8559 m.gate_exps.qtype,
8560 m.up_exps.qtype,
8561 m.gate_exps.row_bytes,
8562 m.up_exps.row_bytes,
8563 )?;
8564 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8566 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8567 e.moe_down8_fma_q8(
8568 crate::WPtr8(d),
8569 crate::F32x8(wv),
8570 &aq2,
8571 &ad2,
8572 &mut dst,
8573 n_ff_exp,
8574 n_embd,
8575 n_used,
8576 m.down_exps.qtype,
8577 m.down_exps.row_bytes,
8578 )?;
8579 Ok(true)
8580 }
8581
8582 fn moe_gdec_token(
8583 e: &Engine,
8584 m: &MoeWeights,
8585 il: u16,
8586 max_block: usize,
8587 zt: &cudarc::driver::CudaView<f32>,
8588 sel: &[u32],
8589 w: &[f32],
8590 moe_out: &mut CudaSlice<f32>,
8591 tok: usize,
8592 n_embd: usize,
8593 n_ff_exp: usize,
8594 n_used: usize,
8595 ) -> Result<bool, Box<dyn std::error::Error>> {
8596 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8597 use cudarc::driver::DevicePtr;
8598 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8600 let mut g = [0u64; 8];
8601 let mut u = [0u64; 8];
8602 let mut d = [0u64; 8];
8603 for (j, &ex) in sel.iter().enumerate() {
8604 let ex = ex as u16;
8605 let (Some(sg), Some(su), Some(sd)) = (
8606 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8607 c.resident(BlockId::new(il, PROJ_UP, ex)),
8608 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8609 ) else {
8610 return Ok(None);
8611 };
8612 let __s = eng.stream();
8613 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8614 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8615 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8616 g[j] = pg as u64;
8617 u[j] = pu as u64;
8618 d[j] = pd as u64;
8619 }
8620 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8621 for &ex in sel {
8622 let ex = ex as u16;
8623 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8624 c.note_profile_hit(BlockId::new(il, proj, ex));
8625 }
8626 }
8627 }
8628 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
8630 })?;
8631 let Some((g, u, d)) = ptrs else {
8632 return Ok(false);
8633 };
8634 let mut wv = [0f32; 8];
8635 wv[..n_used].copy_from_slice(w);
8636 let act = e.moe_gate_up_silu8(
8638 crate::WPtr8(g),
8639 crate::WPtr8(u),
8640 zt,
8641 n_embd,
8642 n_ff_exp,
8643 n_used,
8644 m.gate_exps.qtype,
8645 m.up_exps.qtype,
8646 m.gate_exps.row_bytes,
8647 m.up_exps.row_bytes,
8648 )?;
8649 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8650 e.moe_down8_fma_into(
8651 crate::WPtr8(d),
8652 crate::F32x8(wv),
8653 &act,
8654 &mut dst,
8655 n_ff_exp,
8656 n_embd,
8657 n_used,
8658 m.down_exps.qtype,
8659 m.down_exps.row_bytes,
8660 )?;
8661 Ok(true)
8662 }
8663
8664 fn moe_cached_gemm_q8(
8669 e: &Engine,
8670 il: u16,
8671 proj: u8,
8672 ex: usize,
8673 m: &MoeWeights,
8674 max_block: usize,
8675 aq: &CudaSlice<i8>,
8676 ad: &CudaSlice<f32>,
8677 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8678 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8679 let exps = match proj {
8680 PROJ_GATE => &m.gate_exps,
8681 PROJ_UP => &m.up_exps,
8682 _ => &m.down_exps,
8683 };
8684 let layout = exps.expert_layout(ex);
8685 let id = BlockId::new(il, proj, ex as u16);
8686 let source = exps.expert_source(ex);
8687 e.with_moe_cache(max_block, |c, eng| {
8688 let slot = c.dispatch_source(id, source, eng)?;
8689 let DispatchSlot::Resident(sl) = slot;
8690 let buf = c.slot(sl);
8691 eng.qmatvec_expert_q8(
8692 buf,
8693 0..layout.len,
8694 aq,
8695 ad,
8696 1,
8697 exps.in_f,
8698 exps.out_f,
8699 layout.qtype,
8700 layout.row_bytes,
8701 )
8702 })
8703 }
8704
8705 fn moe_cached_gemm(
8706 e: &Engine,
8707 il: u16,
8708 proj: u8,
8709 ex: usize,
8710 m: &MoeWeights,
8711 max_block: usize,
8712 x: &cudarc::driver::CudaView<f32>,
8713 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8714 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8715 let exps = match proj {
8716 PROJ_GATE => &m.gate_exps,
8717 PROJ_UP => &m.up_exps,
8718 _ => &m.down_exps,
8719 };
8720 let layout = exps.expert_layout(ex);
8721 let id = BlockId::new(il, proj, ex as u16);
8722 let source = exps.expert_source(ex);
8723 e.with_moe_cache(max_block, |c, eng| {
8725 let slot = c.dispatch_source(id, source, eng)?;
8726 let DispatchSlot::Resident(sl) = slot;
8729 let buf = c.slot(sl);
8730 eng.qmatvec_view(
8731 buf,
8732 0..layout.len,
8733 x,
8734 1,
8735 exps.in_f,
8736 exps.out_f,
8737 layout.qtype,
8738 layout.row_bytes,
8739 )
8740 })
8741 }
8742
8743 fn moe_profile_admit_expert(
8747 e: &Engine,
8748 il: u16,
8749 ex: usize,
8750 m: &MoeWeights,
8751 max_block: usize,
8752 ) -> Result<(), Box<dyn std::error::Error>> {
8753 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8754 e.with_moe_cache(max_block, |cache, eng| {
8755 for (proj, exps) in [
8756 (PROJ_GATE, &m.gate_exps),
8757 (PROJ_UP, &m.up_exps),
8758 (PROJ_DOWN, &m.down_exps),
8759 ] {
8760 let id = BlockId::new(il, proj, ex as u16);
8761 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
8762 }
8763 Ok(())
8764 })
8765 }
8766
8767 #[allow(clippy::too_many_arguments)]
8770 fn moe_frozen_gemm(
8771 e: &Engine,
8772 il: u16,
8773 proj: u8,
8774 ex: usize,
8775 m: &MoeWeights,
8776 max_block: usize,
8777 x: &cudarc::driver::CudaView<f32>,
8778 scratch: &mut Option<CudaSlice<u8>>,
8779 scratch_len: usize,
8780 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8781 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
8782 let exps = match proj {
8783 PROJ_GATE => &m.gate_exps,
8784 PROJ_UP => &m.up_exps,
8785 _ => &m.down_exps,
8786 };
8787 let layout = exps.expert_layout(ex);
8788 let id = BlockId::new(il, proj, ex as u16);
8789 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
8790 let Some(slot) = cache.resident(id) else {
8791 return Ok(None);
8792 };
8793 let buf = cache.slot(slot);
8794 Ok(Some(eng.qmatvec_view(
8795 buf,
8796 0..layout.len,
8797 x,
8798 1,
8799 exps.in_f,
8800 exps.out_f,
8801 layout.qtype,
8802 layout.row_bytes,
8803 )?))
8804 })? {
8805 return Ok(output);
8806 }
8807 if scratch.is_none() {
8808 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
8809 }
8810 let scratch = scratch.as_mut().unwrap();
8811 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
8812 e.qmatvec_view(
8813 scratch,
8814 0..layout.len,
8815 x,
8816 1,
8817 exps.in_f,
8818 exps.out_f,
8819 layout.qtype,
8820 layout.row_bytes,
8821 )
8822 }
8823
8824 fn moe_prefetch_expert(
8825 e: &Engine,
8826 il: u16,
8827 ex: usize,
8828 m: &MoeWeights,
8829 max_block: usize,
8830 keep: &[crate::moe_cache::BlockId],
8831 ) -> Result<(), Box<dyn std::error::Error>> {
8832 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8833 e.with_moe_cache(max_block, |c, eng| {
8834 for (proj, exps) in [
8835 (PROJ_GATE, &m.gate_exps),
8836 (PROJ_UP, &m.up_exps),
8837 (PROJ_DOWN, &m.down_exps),
8838 ] {
8839 let id = BlockId::new(il, proj, ex as u16);
8840 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
8841 }
8842 Ok(())
8843 })
8844 }
8845
8846 fn moe_prefetch_disk_expert(
8849 e: &Engine,
8850 il: u16,
8851 ex: usize,
8852 m: &MoeWeights,
8853 max_block: usize,
8854 keep: &[crate::moe_cache::BlockId],
8855 ) -> Result<(), Box<dyn std::error::Error>> {
8856 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8857 e.with_moe_cache(max_block, |c, eng| {
8858 for (proj, exps) in [
8859 (PROJ_GATE, &m.gate_exps),
8860 (PROJ_UP, &m.up_exps),
8861 (PROJ_DOWN, &m.down_exps),
8862 ] {
8863 let source = exps.expert_source(ex);
8864 if let crate::model::ExpertSource::Disk { .. } = &source {
8865 let id = BlockId::new(il, proj, ex as u16);
8866 let _ = c.prefetch_source(id, source, keep, eng)?;
8867 }
8868 }
8869 Ok(())
8870 })
8871 }
8872
8873 #[inline]
8874 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
8875 let _ = m.gate_exps.prefetch_expert_pages(ex);
8876 let _ = m.up_exps.prefetch_expert_pages(ex);
8877 let _ = m.down_exps.prefetch_expert_pages(ex);
8878 }
8879}
8880
8881impl HybridModel {
8898 #[allow(clippy::too_many_arguments)]
8902 fn moe_ffn_grouped_resident_q8(
8903 e: &Engine,
8904 m: &MoeWeights,
8905 z: &CudaSlice<f32>,
8906 t: usize,
8907 cfg: &ModelConfig,
8908 il: u16,
8909 sel_all: &[u32],
8910 w_all: &[f32],
8911 table: &CudaSlice<u64>,
8912 gu_il: bool,
8913 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8914 let moe = cfg.moe.as_ref().unwrap();
8915 let n_embd = cfg.n_embd as usize;
8916 let n_expert = moe.expert_count as usize;
8917 let n_used = moe.expert_used_count as usize;
8918 let n_ff_exp = moe.expert_ff_length as usize;
8919 let n_pairs = t * n_used;
8920 debug_assert_eq!(sel_all.len(), n_pairs);
8921 debug_assert_eq!(w_all.len(), n_pairs);
8922 debug_assert!(
8923 m.gate_exps.macros.is_none()
8924 && m.up_exps.macros.is_none()
8925 && m.down_exps.macros.is_none(),
8926 "resident grouped q8 does not fold per-expert macro scales",
8927 );
8928
8929 if !cfg.swiglu_clamped_at(il as u32) {
8935 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8936 let sel_d = e.htod_i32(&sel)?;
8937 let w_d = e.htod(w_all)?;
8938 let (gate_row_bytes, up_row_bytes) = if gu_il {
8939 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8940 (combined, combined)
8941 } else {
8942 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8943 };
8944 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8945 let act = e.moe_gate_up_silu8_dev_q8_rows(
8946 table,
8947 &sel_d,
8948 &zq,
8949 &zd,
8950 t,
8951 n_embd,
8952 n_ff_exp,
8953 n_used,
8954 n_expert,
8955 m.gate_exps.qtype,
8956 m.up_exps.qtype,
8957 gate_row_bytes,
8958 up_row_bytes,
8959 &m.dev_macros,
8960 )?;
8961 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8962 let mut moe_out = e.uninit(t * n_embd)?;
8963 e.moe_down8_fma_dev_q8_rows_g(
8964 table,
8965 &sel_d,
8966 &w_d,
8967 &aq2,
8968 &ad2,
8969 &mut moe_out,
8970 t,
8971 n_ff_exp,
8972 n_embd,
8973 n_used,
8974 n_expert,
8975 m.down_exps.qtype,
8976 m.down_exps.row_bytes,
8977 )?;
8978
8979 if std::env::var("MEMRA_MOE_STATS").is_ok() {
8980 let mut counts = vec![0usize; n_expert];
8981 for &expert in sel_all {
8982 counts[expert as usize] += 1;
8983 }
8984 let mut sizes: Vec<usize> =
8985 counts.into_iter().filter(|&count| count != 0).collect();
8986 sizes.sort_unstable();
8987 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
8988 println!(
8989 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
8990 m_e: min={} median={} mean={mean:.1} max={}",
8991 sizes.len(),
8992 n_expert,
8993 sizes.first().copied().unwrap_or(0),
8994 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
8995 sizes.last().copied().unwrap_or(0),
8996 );
8997 }
8998 return Ok(moe_out);
8999 }
9000
9001 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
9005 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
9006 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
9007 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
9008
9009 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
9010 for (pair, &expert) in pair_ex.iter().enumerate() {
9011 by_expert[expert as usize].push(pair as i32);
9012 }
9013
9014 let pair_tok_d = e.htod_i32(&pair_tok)?;
9015 let pair_ex_d = e.htod_i32(&pair_ex)?;
9016 let pair_w_d = e.htod(w_all)?;
9017 let tok_off_d = e.htod_i32(&tok_off)?;
9018 let tok_ids_d = e.htod_i32(&tok_ids)?;
9019
9020 let matvec = |proj: i32,
9021 pair_rows: &CudaSlice<i32>,
9022 aq: &CudaSlice<i8>,
9023 ad: &CudaSlice<f32>,
9024 in_f: usize,
9025 out_f: usize,
9026 qtype: i32,
9027 row_bytes: usize|
9028 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9029 e.moe_pairs_matvec_q8(
9030 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
9031 row_bytes,
9032 )
9033 };
9034
9035 let (gate_row_bytes, up_row_bytes) = if gu_il {
9036 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
9037 (combined, combined)
9038 } else {
9039 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
9040 };
9041 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
9042 let gate = matvec(
9043 0,
9044 &pair_tok_d,
9045 &zq,
9046 &zd,
9047 n_embd,
9048 n_ff_exp,
9049 m.gate_exps.qtype,
9050 gate_row_bytes,
9051 )?;
9052 let up = matvec(
9053 1,
9054 &pair_tok_d,
9055 &zq,
9056 &zd,
9057 n_embd,
9058 n_ff_exp,
9059 m.up_exps.qtype,
9060 up_row_bytes,
9061 )?;
9062 let mut act = e.uninit(n_pairs * n_ff_exp)?;
9063 Self::ffn_act_lim(
9064 e,
9065 cfg,
9066 &gate,
9067 &up,
9068 1.0,
9069 1.0,
9070 cfg.clamp_exp_at(il as u32),
9071 &mut act,
9072 n_pairs * n_ff_exp,
9073 )?;
9074 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
9075 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
9076 let pair_self_d = e.htod_i32(&pair_self)?;
9077 let down = matvec(
9078 2,
9079 &pair_self_d,
9080 &aq2,
9081 &ad2,
9082 n_ff_exp,
9083 n_embd,
9084 m.down_exps.qtype,
9085 m.down_exps.row_bytes,
9086 )?;
9087 let mut moe_out = e.uninit(t * n_embd)?;
9088 e.moe_pairs_scatter(
9089 &down,
9090 &pair_w_d,
9091 &tok_off_d,
9092 &tok_ids_d,
9093 &mut moe_out,
9094 t,
9095 n_embd,
9096 )?;
9097
9098 if std::env::var("MEMRA_MOE_STATS").is_ok() {
9099 let mut sizes: Vec<usize> = by_expert
9100 .iter()
9101 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
9102 .collect();
9103 sizes.sort_unstable();
9104 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
9105 println!(
9106 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
9107 m_e: min={} median={} mean={mean:.1} max={}",
9108 sizes.len(),
9109 n_expert,
9110 sizes.first().copied().unwrap_or(0),
9111 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
9112 sizes.last().copied().unwrap_or(0),
9113 );
9114 }
9115 Ok(moe_out)
9116 }
9117
9118 #[allow(clippy::too_many_arguments)]
9124 fn shexp_split_matvec(
9125 e: &Engine,
9126 rank1: &Engine,
9127 wg: &CudaSlice<u8>,
9128 wu: &CudaSlice<u8>,
9129 wd: &CudaSlice<u8>,
9130 z: &CudaSlice<f32>,
9131 lim: Option<f32>,
9132 cfg: &ModelConfig,
9133 il: u16,
9134 n_embd: usize,
9135 n_ff_sh: usize,
9136 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
9137 use cudarc::driver::DevicePtr;
9138 if n_ff_sh % 2 != 0 || n_embd % 2 != 0 {
9139 return Ok(None);
9140 }
9141 let hf = n_ff_sh / 2;
9142 let nd = n_embd / 2;
9143 struct Rep {
9144 wg1: CudaSlice<u8>,
9145 wu1: CudaSlice<u8>,
9146 wd1: CudaSlice<u8>,
9147 }
9148 struct SplitWs {
9149 pin_dev: usize,
9150 gate0: CudaSlice<f32>,
9152 up0: CudaSlice<f32>,
9153 act: CudaSlice<f32>,
9154 sh_buf: CudaSlice<f32>,
9155 ev_z: cudarc::driver::CudaEvent,
9156 ev_act0: cudarc::driver::CudaEvent,
9157 z1: CudaSlice<f32>,
9159 g1: CudaSlice<f32>,
9160 u1: CudaSlice<f32>,
9161 a1h: CudaSlice<f32>,
9162 act1: CudaSlice<f32>,
9163 y1: CudaSlice<f32>,
9164 ev_act1: cudarc::driver::CudaEvent,
9165 ev_y1: cudarc::driver::CudaEvent,
9166 raw_act_e: u64,
9167 raw_sh_e: u64,
9168 raw_z1: u64,
9169 raw_a1h: u64,
9170 raw_act1: u64,
9171 raw_y1: u64,
9172 }
9173 static WS: std::sync::Mutex<Option<SplitWs>> = std::sync::Mutex::new(None);
9174 static REPS: std::sync::Mutex<Option<std::collections::HashMap<u64, Rep>>> =
9175 std::sync::Mutex::new(None);
9176 let mut guard = WS.lock().map_err(|_| "shexp split lock is poisoned")?;
9177 let mut reps_guard = REPS.lock().map_err(|_| "shexp reps lock is poisoned")?;
9178 let reps = reps_guard.get_or_insert_with(std::collections::HashMap::new);
9179 let pins = e.ctx().ordinal();
9180 if guard.as_ref().is_none_or(|w| w.pin_dev != pins) {
9181 let (gate0, up0, act, sh_buf, ev_z, ev_act0) = {
9182 let _m = e.gpu.enter_main()?;
9183 (
9184 e.htod(&vec![0.0f32; hf])?,
9185 e.htod(&vec![0.0f32; hf])?,
9186 e.htod(&vec![0.0f32; n_ff_sh])?,
9187 e.htod(&vec![0.0f32; n_embd])?,
9188 e.ctx().new_event(None)?,
9189 e.ctx().new_event(None)?,
9190 )
9191 };
9192 let (z1, g1, u1, a1h, act1, y1, ev_act1, ev_y1) = {
9193 let _r = rank1.gpu.enter_main()?;
9194 (
9195 rank1.htod(&vec![0.0f32; n_embd])?,
9196 rank1.htod(&vec![0.0f32; hf])?,
9197 rank1.htod(&vec![0.0f32; hf])?,
9198 rank1.htod(&vec![0.0f32; hf])?,
9199 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9200 rank1.htod(&vec![0.0f32; nd])?,
9201 rank1.ctx().new_event(None)?,
9202 rank1.ctx().new_event(None)?,
9203 )
9204 };
9205 let (raw_act_e, raw_sh_e) = {
9206 let _m = e.gpu.enter_main()?;
9207 let stream = e.stream();
9208 let (a, _g0) = act.device_ptr(&stream);
9209 let (b, _g1) = sh_buf.device_ptr(&stream);
9210 (a as u64, b as u64)
9211 };
9212 let (raw_z1, raw_a1h, raw_act1, raw_y1) = {
9213 let _r = rank1.gpu.enter_main()?;
9214 let rs = rank1.stream();
9215 let (a, _g0) = z1.device_ptr(&rs);
9216 let (b, _g1) = a1h.device_ptr(&rs);
9217 let (c, _g2) = act1.device_ptr(&rs);
9218 let (d, _g3) = y1.device_ptr(&rs);
9219 (a as u64, b as u64, c as u64, d as u64)
9220 };
9221 *guard = Some(SplitWs {
9222 pin_dev: pins,
9223 gate0,
9224 up0,
9225 act,
9226 sh_buf,
9227 ev_z,
9228 ev_act0,
9229 z1,
9230 g1,
9231 u1,
9232 a1h,
9233 act1,
9234 y1,
9235 ev_act1,
9236 ev_y1,
9237 raw_act_e,
9238 raw_sh_e,
9239 raw_z1,
9240 raw_a1h,
9241 raw_act1,
9242 raw_y1,
9243 });
9244 }
9245 let ws = guard.as_mut().expect("armed above");
9246 let wg_pin = {
9247 let _m = e.gpu.enter_main()?;
9248 let stream = e.stream();
9249 let (p, _g) = wg.device_ptr(&stream);
9250 p as u64
9251 };
9252 if !reps.contains_key(&wg_pin) {
9253 let mut up = |src: &CudaSlice<u8>,
9255 off_bytes: usize,
9256 len: usize|
9257 -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9258 use cudarc::driver::sys;
9259 let sptr = {
9260 let _m = e.gpu.enter_main()?;
9261 let stream = e.stream();
9262 let (p, _g) = src.device_ptr(&stream);
9263 p as u64 + off_bytes as u64
9264 };
9265 let dst = {
9266 let _r = rank1.gpu.enter_main()?;
9267 rank1.alloc_u8_uninit(len)?
9268 };
9269 let dptr = {
9270 let _r = rank1.gpu.enter_main()?;
9271 let rs = rank1.stream();
9272 let (p, _g) = dst.device_ptr(&rs);
9273 p as u64
9274 };
9275 let _r = rank1.gpu.enter_main()?;
9276 let r = unsafe {
9277 sys::cuMemcpyAsync(
9278 dptr as sys::CUdeviceptr,
9279 sptr as sys::CUdeviceptr,
9280 len,
9281 rank1.stream().cu_stream() as sys::CUstream,
9282 )
9283 };
9284 if r != sys::CUresult::CUDA_SUCCESS {
9285 return Err(format!("shexp split replica upload: {r:?}").into());
9286 }
9287 rank1.stream().synchronize()?;
9288 Ok(dst)
9289 };
9290 let wg1 = up(wg, hf * n_embd * 2, hf * n_embd * 2)?;
9291 let wu1 = up(wu, hf * n_embd * 2, hf * n_embd * 2)?;
9292 let wd1 = up(wd, nd * n_ff_sh * 2, nd * n_ff_sh * 2)?;
9293 reps.insert(wg_pin, Rep { wg1, wu1, wd1 });
9294 }
9295 let _ = il;
9296 let raw_z = {
9298 let _m = e.gpu.enter_main()?;
9299 let stream = e.stream();
9300 let (p, _g) = z.device_ptr(&stream);
9301 ws.ev_z.record(&stream)?;
9302 p as u64
9303 };
9304 {
9306 let rep = reps.get(&wg_pin).expect("uploaded above");
9307 let _r = rank1.gpu.enter_main()?;
9308 rank1.stream().wait(&ws.ev_z)?;
9309 crate::tp::raw_copy_bytes(ws.raw_z1, raw_z, n_embd * 4, rank1)?;
9310 let SplitWs {
9311 z1, g1, u1, a1h, ..
9312 } = &mut *ws;
9313 rank1.matvec_bf16_dual_into(&rep.wg1, &rep.wu1, z1, g1, u1, n_embd, hf)?;
9314 Self::ffn_act_lim(rank1, cfg, g1, u1, 1.0, 1.0, lim, a1h, hf)?;
9315 crate::tp::raw_copy_bytes(ws.raw_act1 + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9317 crate::tp::raw_copy_bytes(ws.raw_act_e + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9318 ws.ev_act1.record(&rank1.stream())?;
9319 }
9320 {
9322 let _m = e.gpu.enter_main()?;
9323 let SplitWs {
9324 gate0, up0, act, ..
9325 } = &mut *ws;
9326 let wg_lo = wg.slice(0..hf * n_embd * 2);
9327 let wu_lo = wu.slice(0..hf * n_embd * 2);
9328 e.matvec_bf16_dual_view_into(&wg_lo, &wu_lo, z, gate0, up0, n_embd, hf)?;
9329 Self::ffn_act_lim(e, cfg, gate0, up0, 1.0, 1.0, lim, act, hf)?;
9330 ws.ev_act0.record(&e.stream())?;
9331 }
9332 {
9334 let rep = reps.get(&wg_pin).expect("uploaded above");
9335 let _r = rank1.gpu.enter_main()?;
9336 rank1.stream().wait(&ws.ev_act0)?;
9337 crate::tp::raw_copy_bytes(ws.raw_act1, ws.raw_act_e, hf * 4, rank1)?;
9338 let SplitWs { act1, y1, .. } = &mut *ws;
9339 rank1.matvec_bf16_into(&rep.wd1, act1, y1, n_ff_sh, nd)?;
9340 crate::tp::raw_copy_bytes(ws.raw_sh_e + (nd * 4) as u64, ws.raw_y1, nd * 4, rank1)?;
9341 ws.ev_y1.record(&rank1.stream())?;
9342 }
9343 {
9345 let _m = e.gpu.enter_main()?;
9346 e.stream().wait(&ws.ev_act1)?;
9347 let SplitWs { act, sh_buf, .. } = &mut *ws;
9348 let wd_lo = wd.slice(0..nd * n_ff_sh * 2);
9349 e.matvec_bf16_view_into(&wd_lo, act, sh_buf, n_ff_sh, nd)?;
9350 e.stream().wait(&ws.ev_y1)?;
9351 let mut sh = e.uninit(n_embd)?;
9352 {
9353 let mut dst = sh.slice_mut(0..n_embd);
9354 e.stream()
9355 .memcpy_dtod(&ws.sh_buf.slice(0..n_embd), &mut dst)?;
9356 }
9357 Ok(Some(sh))
9358 }
9359 }
9360
9361 fn shexp_overlap_issue(
9368 e: &Engine,
9369 m: &MoeWeights,
9370 z: &CudaSlice<f32>,
9371 cfg: &ModelConfig,
9372 il: u16,
9373 n_embd: usize,
9374 ) -> Result<bool, Box<dyn std::error::Error>> {
9375 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9376 return Ok(false);
9377 }
9378 let (
9379 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9380 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9381 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9382 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9383 else {
9384 return Ok(false);
9385 };
9386 let n_ff_sh = m
9387 .gate_shexp
9388 .as_ref()
9389 .expect("matched Some above")
9390 .out_features();
9391 let lim = cfg.clamp_shexp_at(il as u32);
9392 let mut guard = SHEXP_OV_WS
9393 .lock()
9394 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9395 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9396 if guard
9397 .as_ref()
9398 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9399 {
9400 *guard = Some((
9401 pins.0,
9402 pins.1,
9403 pins.2,
9404 e.uninit(n_ff_sh)?,
9405 e.uninit(n_embd)?,
9406 ));
9407 }
9408 let (_, _, _, act, sh) = guard.as_mut().expect("armed above");
9409 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9410 e.matvec_bf16_into(wd, act, sh, n_ff_sh, n_embd)?;
9411 drop(guard);
9412 Ok(true)
9413 }
9414
9415 #[allow(clippy::too_many_arguments)]
9421 fn shexp_dev1_issue(
9422 e: &Engine,
9423 rank1: &Engine,
9424 m: &MoeWeights,
9425 z: &CudaSlice<f32>,
9426 cfg: &ModelConfig,
9427 il: u16,
9428 n_embd: usize,
9429 ) -> Result<bool, Box<dyn std::error::Error>> {
9430 use cudarc::driver::DevicePtr;
9431 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9432 return Ok(false);
9433 }
9434 let (
9435 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9436 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9437 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9438 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9439 else {
9440 return Ok(false);
9441 };
9442 let n_ff_sh = m
9443 .gate_shexp
9444 .as_ref()
9445 .expect("matched Some above")
9446 .out_features();
9447 let lim = cfg.clamp_shexp_at(il as u32);
9448 let mut ws_guard = SHEXP_D1_WS
9450 .lock()
9451 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9452 if ws_guard
9453 .as_ref()
9454 .is_none_or(|(k, ..)| *k != (n_embd, n_ff_sh))
9455 {
9456 let (act1, z1, ev_done) = {
9457 let _r1 = rank1.gpu.enter_main()?;
9458 (
9459 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9460 rank1.htod(&vec![0.0f32; n_embd])?,
9461 rank1.ctx().new_event(None)?,
9462 )
9463 };
9464 let (sh_root, ev_z) = {
9465 let _main = e.gpu.enter_main()?;
9466 (e.htod(&vec![0.0f32; n_embd])?, e.ctx().new_event(None)?)
9467 };
9468 *ws_guard = Some(((n_embd, n_ff_sh), act1, z1, sh_root, ev_z, ev_done));
9469 }
9470 let mut reps_guard = SHEXP_D1_REPS
9472 .lock()
9473 .map_err(|_| "shexp dev1 replica lock is poisoned")?;
9474 let reps = reps_guard.get_or_insert_with(Default::default);
9475 if !reps.contains_key(&il) {
9476 let (wg1, wu1, wd1) = {
9477 let _r1 = rank1.gpu.enter_main()?;
9478 (
9479 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9480 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9481 rank1.alloc_u8_uninit(n_embd * n_ff_sh * 2)?,
9482 )
9483 };
9484 for (src, dst) in [(wg, &wg1), (wu, &wu1), (wd, &wd1)] {
9485 let s_ptr = {
9486 let _main = e.gpu.enter_main()?;
9487 let stream = e.stream();
9488 let (p, _g) = src.device_ptr(&stream);
9489 p as u64
9490 };
9491 let d_ptr = {
9492 let _r1 = rank1.gpu.enter_main()?;
9493 let stream = rank1.stream();
9494 let (p, _g) = dst.device_ptr(&stream);
9495 p as u64
9496 };
9497 let _r1 = rank1.gpu.enter_main()?;
9498 crate::tp::raw_copy_bytes(d_ptr, s_ptr, src.len(), rank1)?;
9499 }
9500 {
9501 let _r1 = rank1.gpu.enter_main()?;
9502 rank1.stream().synchronize()?;
9503 }
9504 reps.insert(il, (wg1, wu1, wd1));
9505 }
9506 let (wg1, wu1, wd1) = reps.get(&il).expect("armed above");
9507 let (_, act1, z1, sh_root, ev_z, ev_done) = ws_guard.as_mut().expect("armed above");
9508 let (raw_z, raw_sh) = {
9511 let _main = e.gpu.enter_main()?;
9512 let stream = e.stream();
9513 let (a, _g0) = z.device_ptr(&stream);
9514 let (b, _g1) = sh_root.device_ptr(&stream);
9515 ev_z.record(&stream)?;
9516 (a as u64, b as u64)
9517 };
9518 {
9519 let _r1 = rank1.gpu.enter_main()?;
9520 rank1.stream().wait(ev_z)?;
9521 let raw_z1 = {
9522 let stream = rank1.stream();
9523 let (p, _g) = z1.device_ptr(&stream);
9524 p as u64
9525 };
9526 crate::tp::raw_copy_bytes(raw_z1, raw_z, n_embd * 4, rank1)?;
9527 rank1.matvec_bf16_dual_silu_into(wg1, wu1, z1, act1, n_embd, n_ff_sh, lim)?;
9528 rank1.matvec_bf16_raw_out(wd1, act1, raw_sh, n_ff_sh, n_embd)?;
9532 ev_done.record(&rank1.stream())?;
9533 }
9534 Ok(true)
9535 }
9536
9537 fn shexp_dev1_apply(
9539 e: &Engine,
9540 output: &mut CudaSlice<f32>,
9541 n_embd: usize,
9542 ) -> Result<(), Box<dyn std::error::Error>> {
9543 let guard = SHEXP_D1_WS
9544 .lock()
9545 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9546 let (pin, _, _, sh_root, _, ev_done) =
9547 guard.as_ref().ok_or("shexp dev1 apply without issue")?;
9548 if pin.0 != n_embd {
9549 return Err("shexp dev1 width drifted".into());
9550 }
9551 let _main = e.gpu.enter_main()?;
9552 e.stream().wait(ev_done)?;
9553 static ONES_D1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9554 std::sync::Mutex::new(None);
9555 let mut og = ONES_D1.lock().map_err(|_| "ones lock is poisoned")?;
9556 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9557 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9558 }
9559 let ones = &og.as_ref().expect("armed above").1;
9560 e.add_scaled_rows(sh_root, ones, output, n_embd, 1)?;
9561 Ok(())
9562 }
9563
9564 fn shexp_overlap_tail_ptrs(
9568 e: &Engine,
9569 m: &MoeWeights,
9570 cfg: &ModelConfig,
9571 n_embd: usize,
9572 ) -> Result<Option<(u64, u64)>, Box<dyn std::error::Error>> {
9573 use cudarc::driver::DevicePtr;
9574 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9575 return Ok(None);
9576 }
9577 let (
9578 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9579 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9580 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9581 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9582 else {
9583 return Ok(None);
9584 };
9585 let n_ff_sh = m
9586 .gate_shexp
9587 .as_ref()
9588 .expect("matched Some above")
9589 .out_features();
9590 let mut guard = SHEXP_OV_WS
9591 .lock()
9592 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9593 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9594 if guard
9595 .as_ref()
9596 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9597 {
9598 *guard = Some((
9599 pins.0,
9600 pins.1,
9601 pins.2,
9602 e.uninit(n_ff_sh)?,
9603 e.uninit(n_embd)?,
9604 ));
9605 }
9606 let sh_raw = {
9607 let (_, _, _, _, sh) = guard.as_ref().expect("armed above");
9608 let stream = e.stream();
9609 let (p, _g) = sh.device_ptr(&stream);
9610 p as u64
9611 };
9612 static ONES_T3: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9613 std::sync::Mutex::new(None);
9614 let mut og = ONES_T3.lock().map_err(|_| "ones lock is poisoned")?;
9615 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9616 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9617 }
9618 let ones_raw = {
9619 let stream = e.stream();
9620 let (p, _g) = og.as_ref().expect("armed above").1.device_ptr(&stream);
9621 p as u64
9622 };
9623 Ok(Some((sh_raw, ones_raw)))
9624 }
9625
9626 fn shexp_overlap_apply(
9629 e: &Engine,
9630 output: &mut CudaSlice<f32>,
9631 n_embd: usize,
9632 ) -> Result<(), Box<dyn std::error::Error>> {
9633 let guard = SHEXP_OV_WS
9634 .lock()
9635 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9636 let (_, ne, _, _, sh) = guard.as_ref().ok_or("shexp overlap apply without issue")?;
9637 if *ne != n_embd {
9638 return Err("shexp overlap width drifted".into());
9639 }
9640 static ONES_OV: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9641 std::sync::Mutex::new(None);
9642 let mut og = ONES_OV.lock().map_err(|_| "ones lock is poisoned")?;
9643 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9644 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9645 }
9646 let ones = &og.as_ref().expect("armed above").1;
9647 e.add_scaled_rows(sh, ones, output, n_embd, 1)?;
9648 Ok(())
9649 }
9650
9651 fn moe_ffn_grouped_add_shared(
9652 e: &Engine,
9653 m: &MoeWeights,
9654 z: &CudaSlice<f32>,
9655 t: usize,
9656 cfg: &ModelConfig,
9657 il: u16,
9658 moe_out: &mut CudaSlice<f32>,
9659 ) -> Result<(), Box<dyn std::error::Error>> {
9660 static SHEXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9663 static SHEXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9664 let shexp_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9665 let shexp_started = shexp_timing.then(std::time::Instant::now);
9666 let result = Self::moe_ffn_grouped_add_shared_inner(e, m, z, t, cfg, il, moe_out);
9667 if let Some(started) = shexp_started {
9668 use std::sync::atomic::Ordering;
9669 e.stream().synchronize()?;
9670 let ns = SHEXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9671 + started.elapsed().as_nanos() as u64;
9672 let calls = SHEXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9673 if calls % 430 == 0 {
9674 eprintln!(
9675 "[moe-shexp-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9676 ns as f64 / 1.0e6,
9677 ns as f64 / calls as f64 / 1.0e3,
9678 );
9679 }
9680 }
9681 result
9682 }
9683
9684 #[allow(clippy::too_many_arguments)]
9685 fn moe_ffn_grouped_add_shared_inner(
9686 e: &Engine,
9687 m: &MoeWeights,
9688 z: &CudaSlice<f32>,
9689 t: usize,
9690 cfg: &ModelConfig,
9691 il: u16,
9692 moe_out: &mut CudaSlice<f32>,
9693 ) -> Result<(), Box<dyn std::error::Error>> {
9694 let n_embd = cfg.n_embd as usize;
9695 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
9696 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9697 {
9698 let n_ff_sh = gate_shexp.out_features();
9699 let lim = cfg.clamp_shexp_at(il as u32);
9700 let fused = t == 1
9707 && lim.is_none()
9708 && cfg.m3.is_none()
9709 && e.uses_q8_1_fast(gate_shexp)
9710 && e.uses_q8_1_fast(up_shexp);
9711 let bf16_dual = if t == 1 && crate::Engine::bf16_mmv_on() && n_embd % 8 == 0 {
9714 match (gate_shexp, up_shexp) {
9715 (
9716 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
9717 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
9718 ) => Some((wg, wu)),
9719 _ => None,
9720 }
9721 } else {
9722 None
9723 };
9724 let sh = if let Some((wg, wu)) = bf16_dual {
9725 static SHEXP_WS: std::sync::Mutex<
9729 Option<(
9730 usize,
9731 usize,
9732 usize,
9733 CudaSlice<f32>,
9734 CudaSlice<f32>,
9735 CudaSlice<f32>,
9736 CudaSlice<f32>,
9737 )>,
9738 > = std::sync::Mutex::new(None);
9739 let down_bf16 = match down_shexp {
9740 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9741 _ => None,
9742 };
9743 let mut guard = SHEXP_WS
9744 .lock()
9745 .map_err(|_| "shexp workspace lock is poisoned")?;
9746 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9747 if guard
9748 .as_ref()
9749 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9750 {
9751 *guard = Some((
9752 pins.0,
9753 pins.1,
9754 pins.2,
9755 e.uninit(n_ff_sh)?,
9756 e.uninit(n_ff_sh)?,
9757 e.uninit(n_ff_sh)?,
9758 e.uninit(n_embd)?,
9759 ));
9760 }
9761 {
9764 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9765 let split_on = *ON
9766 .get_or_init(|| std::env::var("MEMRA_SHEXP_SPLIT").as_deref() == Ok("1"));
9767 if split_on {
9768 if let (Some(wd), Some(rank1)) = (
9769 match down_shexp {
9770 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9771 _ => None,
9772 },
9773 m.step_tp.as_ref().and_then(|st| st.runtime.rank_engine(1)),
9774 ) {
9775 if let Some(sh) = Self::shexp_split_matvec(
9776 e, rank1, wg, wu, wd, z, lim, cfg, il, n_embd, n_ff_sh,
9777 )? {
9778 drop(guard);
9779 let gate = match &m.gate_inp_shexp {
9780 Some(gate_inp_shexp) => e.sigmoid_dot_rows(
9781 z,
9782 gate_inp_shexp.float_data(),
9783 n_embd,
9784 t,
9785 )?,
9786 None => e.htod(&vec![1.0f32; t])?,
9787 };
9788 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9789 return Ok(());
9790 }
9791 }
9792 }
9793 }
9794 let (_, _, _, gate, up, act, sh_buf) =
9795 guard.as_mut().expect("shexp workspace initialized above");
9796 if cfg.m3.is_none() {
9797 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9800 let _ = (&gate, &up);
9801 } else {
9802 e.matvec_bf16_dual_into(wg, wu, z, gate, up, n_embd, n_ff_sh)?;
9803 Self::ffn_act_lim(e, cfg, gate, up, 1.0, 1.0, lim, act, n_ff_sh)?;
9804 }
9805 if let Some(down) = down_bf16 {
9806 static FUSE_DA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9813 let fuse_da = *FUSE_DA.get_or_init(|| {
9814 std::env::var("MEMRA_FUSE_DOWN_ADDSCALE").as_deref() != Ok("0")
9815 });
9816 if fuse_da && m.gate_inp_shexp.is_none() {
9817 static ONES1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9818 std::sync::Mutex::new(None);
9819 let mut og = ONES1.lock().map_err(|_| "shexp ones lock is poisoned")?;
9820 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9821 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9822 }
9823 let ones = &og.as_ref().expect("armed above").1;
9824 e.matvec_bf16_down_addscale_into(
9825 down, act, ones, moe_out, n_ff_sh, n_embd,
9826 )?;
9827 return Ok(());
9828 }
9829 e.matvec_bf16_into(down, act, sh_buf, n_ff_sh, n_embd)?;
9830 let sh = e.uninit(n_embd)?;
9831 let mut sh = sh;
9833 {
9834 let mut dst = sh.slice_mut(0..n_embd);
9835 e.stream().memcpy_dtod(&sh_buf.slice(0..n_embd), &mut dst)?;
9836 }
9837 sh
9838 } else {
9839 e.matmul(down_shexp, act, 1)?
9840 }
9841 } else if fused {
9842 let (zq, zd) = e.quantize_q8_1(z, 1, n_embd)?;
9843 let pair = match e.matmul_pre_dual_noscale(gate_shexp, up_shexp, &zq, &zd, 1)? {
9844 Some((gate, up)) => Some((gate, up)),
9845 None => {
9846 match (
9847 e.matmul_pre_noscale(gate_shexp, &zq, &zd, 1)?,
9848 e.matmul_pre_noscale(up_shexp, &zq, &zd, 1)?,
9849 ) {
9850 (Some(gate), Some(up)) => Some((gate, up)),
9851 _ => None,
9852 }
9853 }
9854 };
9855 match pair {
9856 Some(((gate, gs), (up, us))) => {
9857 if e.uses_q8_1_fast(down_shexp) {
9858 let (aq, ad) = e.silu_mul_scaled_q8_1(&gate, &up, gs, us, n_ff_sh)?;
9859 e.matmul_pre(down_shexp, &aq, &ad, &gate, 1)?
9860 } else {
9861 let mut act = e.uninit(n_ff_sh)?;
9862 e.silu_mul_scaled(&gate, &up, gs, us, &mut act, n_ff_sh)?;
9863 e.matmul(down_shexp, &act, 1)?
9864 }
9865 }
9866 None => {
9867 let gate = e.matmul_pre(gate_shexp, &zq, &zd, z, 1)?;
9868 let up = e.matmul_pre(up_shexp, &zq, &zd, z, 1)?;
9869 let mut act = e.uninit(n_ff_sh)?;
9870 Self::ffn_act(e, cfg, &gate, &up, &mut act, n_ff_sh)?;
9871 e.matmul(down_shexp, &act, 1)?
9872 }
9873 }
9874 } else {
9875 let sg_gate = e.matmul(gate_shexp, z, t)?;
9876 let sg_up = e.matmul(up_shexp, z, t)?;
9877 let mut sa = e.uninit(t * n_ff_sh)?;
9878 Self::ffn_act_lim(
9879 e,
9880 cfg,
9881 &sg_gate,
9882 &sg_up,
9883 1.0,
9884 1.0,
9885 lim,
9886 &mut sa,
9887 t * n_ff_sh,
9888 )?;
9889 e.matmul(down_shexp, &sa, t)?
9890 };
9891 let gate = match &m.gate_inp_shexp {
9892 Some(gate_inp_shexp) => {
9893 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
9894 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
9895 } else {
9896 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
9897 let mut gate = e.uninit(t)?;
9898 e.sigmoid(&raw, &mut gate, t)?;
9899 gate
9900 }
9901 }
9902 None if t == 1 => {
9907 static ONES: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9908 std::sync::Mutex::new(None);
9909 let mut guard = ONES.lock().map_err(|_| "shexp ones lock is poisoned")?;
9910 if guard.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9911 *guard = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9912 }
9913 let ones = &guard.as_ref().expect("armed above").1;
9914 e.add_scaled_rows(&sh, ones, moe_out, n_embd, t)?;
9915 return Ok(());
9916 }
9917 None => e.htod(&vec![1.0f32; t])?,
9918 };
9919 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9920 }
9921 Ok(())
9922 }
9923
9924 pub(crate) fn moe_ffn_grouped(
9927 e: &Engine,
9928 m: &MoeWeights,
9929 z: &CudaSlice<f32>,
9930 t: usize,
9931 cfg: &ModelConfig,
9932 il: u16,
9933 max_block: usize,
9934 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9935 let moe = cfg.moe.as_ref().unwrap();
9936 let n_embd = cfg.n_embd as usize;
9937 let n_expert = moe.expert_count as usize;
9938 let n_used = moe.expert_used_count as usize;
9939 let n_ff_exp = moe.expert_ff_length as usize;
9940 let lim_exp = cfg.clamp_exp_at(il as u32);
9942
9943 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
9947 if let Some(sig) = cfg.sigmoid_router() {
9948 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
9949 }
9950 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
9951 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
9952 } else {
9953 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
9954 };
9955 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
9956 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
9957 Self::trace_moe_input(e, il, t, n_embd, z)?;
9958
9959 let no_exp_macros = m.gate_exps.macros.is_none()
9964 && m.up_exps.macros.is_none()
9965 && m.down_exps.macros.is_none();
9966 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
9967 m.has_uniform_expert_layout()
9968 && no_exp_macros
9969 && moe_q8_enabled()
9970 && q8_expert_supported(m.gate_exps.qtype)
9971 && q8_expert_supported(m.up_exps.qtype)
9972 && q8_expert_supported(m.down_exps.qtype)
9973 && moe_slab_enabled()
9974 && dev.dev == e.ctx().ordinal()
9975 });
9976 if let Some(dev) = resident_q8 {
9977 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
9978 e,
9979 m,
9980 z,
9981 t,
9982 cfg,
9983 il,
9984 &sel_all,
9985 &w_all,
9986 &dev.ptr_row,
9987 dev.gu_il,
9988 )?;
9989 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
9990 return Ok(moe_out);
9991 }
9992
9993 struct ExpertGroup {
9997 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
10001 let mut groups: Vec<ExpertGroup> = (0..n_expert)
10002 .map(|_| ExpertGroup {
10003 tok_indices: Vec::new(),
10004 slot_indices: Vec::new(),
10005 weights: Vec::new(),
10006 })
10007 .collect();
10008
10009 for tok in 0..t {
10010 for j in 0..n_used {
10011 let ex = sel_all[tok * n_used + j] as usize;
10012 let w = w_all[tok * n_used + j];
10013 groups[ex].tok_indices.push(tok as i32);
10014 groups[ex].slot_indices.push(j as i32);
10015 groups[ex].weights.push(w);
10016 }
10017 }
10018
10019 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
10022 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
10026 let u_len = m.up_exps.max_expert_bytes();
10027 let d_len = m.down_exps.max_expert_bytes();
10028 let moe_q8 = m.has_uniform_expert_layout()
10029 && moe_q8_enabled()
10030 && q8_expert_supported(m.gate_exps.qtype)
10031 && q8_expert_supported(m.up_exps.qtype)
10032 && q8_expert_supported(m.down_exps.qtype);
10033 let slab_local = m
10036 .dev_exps
10037 .as_ref()
10038 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
10039 let use_cache =
10040 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
10041 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
10044
10045 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
10047 (
10048 Some(e.alloc_u8(g_len)?),
10049 Some(e.alloc_u8(u_len)?),
10050 Some(e.alloc_u8(d_len)?),
10051 )
10052 } else {
10053 (None, None, None)
10054 };
10055
10056 let mut order: Vec<usize> = (0..n_expert)
10067 .filter(|&ex| !groups[ex].tok_indices.is_empty())
10068 .collect();
10069 order.sort_by(|&a, &b| {
10070 groups[b]
10071 .tok_indices
10072 .len()
10073 .cmp(&groups[a].tok_indices.len())
10074 .then(a.cmp(&b))
10075 });
10076 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
10078 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
10079 if worker_disk_prefetch {
10080 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
10081 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
10082 }
10083 }
10084 for (order_pos, &ex) in order.iter().enumerate() {
10085 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
10086 Self::moe_prefetch_host_expert(order[next], m);
10087 }
10088 if worker_disk_prefetch {
10089 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
10090 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10091 let keep = [
10092 BlockId::new(il, PROJ_GATE, ex as u16),
10093 BlockId::new(il, PROJ_UP, ex as u16),
10094 BlockId::new(il, PROJ_DOWN, ex as u16),
10095 ];
10096 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
10097 }
10098 }
10099 let grp = &groups[ex];
10100 let m_e = grp.tok_indices.len();
10101 m_dist.push(m_e);
10102 let gl = m.gate_exps.expert_layout(ex);
10103 let ul = m.up_exps.expert_layout(ex);
10104 let dl = m.down_exps.expert_layout(ex);
10105
10106 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
10110 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
10111 let dmac = m.down_exps.macro_scale(ex);
10112 let weight_d = if dmac == 1.0 {
10113 e.htod(&grp.weights)?
10114 } else {
10115 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
10116 e.htod(&scaled)?
10117 };
10118
10119 let mut gathered = e.zeros(m_e * n_embd)?;
10121 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
10122 let gv = gathered.slice(0..m_e * n_embd);
10123
10124 let y = if let Some(dev) = slab_local {
10127 let gate_start = ex * m.gate_exps.expert_stride;
10128 let up_start = ex * m.up_exps.expert_stride;
10129 let down_start = ex * m.down_exps.expert_stride;
10130 if grouped_q8 {
10131 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10132 let gate = e.qmatvec_expert_q8(
10133 &dev.gate,
10134 gate_start..gate_start + gl.len,
10135 &zq,
10136 &zd,
10137 m_e,
10138 m.gate_exps.in_f,
10139 m.gate_exps.out_f,
10140 gl.qtype,
10141 gl.row_bytes,
10142 )?;
10143 let up = e.qmatvec_expert_q8(
10144 &dev.up,
10145 up_start..up_start + ul.len,
10146 &zq,
10147 &zd,
10148 m_e,
10149 m.up_exps.in_f,
10150 m.up_exps.out_f,
10151 ul.qtype,
10152 ul.row_bytes,
10153 )?;
10154 let mut act = e.uninit(m_e * n_ff_exp)?;
10155 Self::ffn_act_lim(
10156 e,
10157 cfg,
10158 &gate,
10159 &up,
10160 m.gate_exps.macro_scale(ex),
10161 m.up_exps.macro_scale(ex),
10162 lim_exp,
10163 &mut act,
10164 m_e * n_ff_exp,
10165 )?;
10166 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10167 e.qmatvec_expert_q8(
10168 &dev.down,
10169 down_start..down_start + dl.len,
10170 &aq2,
10171 &ad2,
10172 m_e,
10173 m.down_exps.in_f,
10174 m.down_exps.out_f,
10175 dl.qtype,
10176 dl.row_bytes,
10177 )?
10178 } else {
10179 let gate = e.qmatvec_view(
10180 &dev.gate,
10181 gate_start..gate_start + gl.len,
10182 &gv,
10183 m_e,
10184 m.gate_exps.in_f,
10185 m.gate_exps.out_f,
10186 gl.qtype,
10187 gl.row_bytes,
10188 )?;
10189 let up = e.qmatvec_view(
10190 &dev.up,
10191 up_start..up_start + ul.len,
10192 &gv,
10193 m_e,
10194 m.up_exps.in_f,
10195 m.up_exps.out_f,
10196 ul.qtype,
10197 ul.row_bytes,
10198 )?;
10199 let mut act = e.uninit(m_e * n_ff_exp)?;
10200 Self::ffn_act_lim(
10201 e,
10202 cfg,
10203 &gate,
10204 &up,
10205 m.gate_exps.macro_scale(ex),
10206 m.up_exps.macro_scale(ex),
10207 lim_exp,
10208 &mut act,
10209 m_e * n_ff_exp,
10210 )?;
10211 let actv = act.slice(0..m_e * n_ff_exp);
10212 e.qmatvec_view(
10213 &dev.down,
10214 down_start..down_start + dl.len,
10215 &actv,
10216 m_e,
10217 m.down_exps.in_f,
10218 m.down_exps.out_f,
10219 dl.qtype,
10220 dl.row_bytes,
10221 )?
10222 }
10223 } else if use_cache {
10224 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10225 if grouped_q8 {
10226 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10227 let gate = e.with_moe_cache(max_block, |cache, eng| {
10228 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10229 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10230 eng.qmatvec_expert_q8(
10231 cache.buf(slot),
10232 0..gl.len,
10233 &zq,
10234 &zd,
10235 m_e,
10236 m.gate_exps.in_f,
10237 m.gate_exps.out_f,
10238 gl.qtype,
10239 gl.row_bytes,
10240 )
10241 })?;
10242 let up = e.with_moe_cache(max_block, |cache, eng| {
10243 let id = BlockId::new(il, PROJ_UP, ex as u16);
10244 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10245 eng.qmatvec_expert_q8(
10246 cache.buf(slot),
10247 0..ul.len,
10248 &zq,
10249 &zd,
10250 m_e,
10251 m.up_exps.in_f,
10252 m.up_exps.out_f,
10253 ul.qtype,
10254 ul.row_bytes,
10255 )
10256 })?;
10257 let mut act = e.uninit(m_e * n_ff_exp)?;
10258 Self::ffn_act_lim(
10259 e,
10260 cfg,
10261 &gate,
10262 &up,
10263 m.gate_exps.macro_scale(ex),
10264 m.up_exps.macro_scale(ex),
10265 lim_exp,
10266 &mut act,
10267 m_e * n_ff_exp,
10268 )?;
10269 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10270 e.with_moe_cache(max_block, |cache, eng| {
10271 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10272 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10273 eng.qmatvec_expert_q8(
10274 cache.buf(slot),
10275 0..dl.len,
10276 &aq2,
10277 &ad2,
10278 m_e,
10279 m.down_exps.in_f,
10280 m.down_exps.out_f,
10281 dl.qtype,
10282 dl.row_bytes,
10283 )
10284 })?
10285 } else {
10286 let gate = e.with_moe_cache(max_block, |cache, eng| {
10287 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10288 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10289 eng.qmatvec_view(
10290 cache.buf(slot),
10291 0..gl.len,
10292 &gv,
10293 m_e,
10294 m.gate_exps.in_f,
10295 m.gate_exps.out_f,
10296 gl.qtype,
10297 gl.row_bytes,
10298 )
10299 })?;
10300 let up = e.with_moe_cache(max_block, |cache, eng| {
10301 let id = BlockId::new(il, PROJ_UP, ex as u16);
10302 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10303 eng.qmatvec_view(
10304 cache.buf(slot),
10305 0..ul.len,
10306 &gv,
10307 m_e,
10308 m.up_exps.in_f,
10309 m.up_exps.out_f,
10310 ul.qtype,
10311 ul.row_bytes,
10312 )
10313 })?;
10314 let mut act = e.uninit(m_e * n_ff_exp)?;
10315 Self::ffn_act_lim(
10316 e,
10317 cfg,
10318 &gate,
10319 &up,
10320 m.gate_exps.macro_scale(ex),
10321 m.up_exps.macro_scale(ex),
10322 lim_exp,
10323 &mut act,
10324 m_e * n_ff_exp,
10325 )?;
10326 let actv = act.slice(0..m_e * n_ff_exp);
10327 e.with_moe_cache(max_block, |cache, eng| {
10328 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10329 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10330 eng.qmatvec_view(
10331 cache.buf(slot),
10332 0..dl.len,
10333 &actv,
10334 m_e,
10335 m.down_exps.in_f,
10336 m.down_exps.out_f,
10337 dl.qtype,
10338 dl.row_bytes,
10339 )
10340 })?
10341 }
10342 } else {
10343 let sg = scratch_g.as_mut().unwrap();
10344 let su = scratch_u.as_mut().unwrap();
10345 let sd = scratch_d.as_mut().unwrap();
10346 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
10347 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
10348 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
10349 if grouped_q8 {
10350 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10351 let gate = e.qmatvec_expert_q8(
10352 sg,
10353 0..gl.len,
10354 &zq,
10355 &zd,
10356 m_e,
10357 m.gate_exps.in_f,
10358 m.gate_exps.out_f,
10359 gl.qtype,
10360 gl.row_bytes,
10361 )?;
10362 let up = e.qmatvec_expert_q8(
10363 su,
10364 0..ul.len,
10365 &zq,
10366 &zd,
10367 m_e,
10368 m.up_exps.in_f,
10369 m.up_exps.out_f,
10370 ul.qtype,
10371 ul.row_bytes,
10372 )?;
10373 let mut act = e.uninit(m_e * n_ff_exp)?;
10374 Self::ffn_act_lim(
10375 e,
10376 cfg,
10377 &gate,
10378 &up,
10379 m.gate_exps.macro_scale(ex),
10380 m.up_exps.macro_scale(ex),
10381 lim_exp,
10382 &mut act,
10383 m_e * n_ff_exp,
10384 )?;
10385 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10386 e.qmatvec_expert_q8(
10387 sd,
10388 0..dl.len,
10389 &aq2,
10390 &ad2,
10391 m_e,
10392 m.down_exps.in_f,
10393 m.down_exps.out_f,
10394 dl.qtype,
10395 dl.row_bytes,
10396 )?
10397 } else {
10398 let gate = e.qmatvec_view(
10399 sg,
10400 0..gl.len,
10401 &gv,
10402 m_e,
10403 m.gate_exps.in_f,
10404 m.gate_exps.out_f,
10405 gl.qtype,
10406 gl.row_bytes,
10407 )?;
10408 let up = e.qmatvec_view(
10409 su,
10410 0..ul.len,
10411 &gv,
10412 m_e,
10413 m.up_exps.in_f,
10414 m.up_exps.out_f,
10415 ul.qtype,
10416 ul.row_bytes,
10417 )?;
10418 let mut act = e.uninit(m_e * n_ff_exp)?;
10419 Self::ffn_act_lim(
10420 e,
10421 cfg,
10422 &gate,
10423 &up,
10424 m.gate_exps.macro_scale(ex),
10425 m.up_exps.macro_scale(ex),
10426 lim_exp,
10427 &mut act,
10428 m_e * n_ff_exp,
10429 )?;
10430 let actv = act.slice(0..m_e * n_ff_exp);
10431 e.qmatvec_view(
10432 sd,
10433 0..dl.len,
10434 &actv,
10435 m_e,
10436 m.down_exps.in_f,
10437 m.down_exps.out_f,
10438 dl.qtype,
10439 dl.row_bytes,
10440 )?
10441 }
10442 };
10443
10444 e.scatter_slot(
10446 &y,
10447 &tok_idx_d,
10448 &slot_idx_d,
10449 &weight_d,
10450 &mut slot_buf,
10451 &mut wbuf,
10452 n_embd,
10453 n_used,
10454 m_e,
10455 )?;
10456 }
10457
10458 let mut moe_out = e.zeros(t * n_embd)?;
10460 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
10461
10462 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
10464 m_dist.sort_unstable();
10465 let active = m_dist.len();
10466 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
10467 let median = m_dist[active / 2];
10468 let max_m = *m_dist.last().unwrap();
10469 let min_m = m_dist[0];
10470 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
10471 println!(
10472 "moe-grouped il={il} t={t} active={active}/{n_expert} \
10473 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
10474 above_gemm_threshold(>=16)={above16}/{active}"
10475 );
10476 }
10477
10478 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10479 Ok(moe_out)
10480 }
10481
10482 pub(crate) fn moe_ffn_lockstep(
10489 &self,
10490 e: &Engine,
10491 m: &MoeWeights,
10492 zbatch: &CudaSlice<f32>,
10493 mrows: usize,
10494 il: u16,
10495 max_block: usize,
10496 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10497 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10498 let cfg = &self.cfg;
10499 let moe = cfg.moe.as_ref().unwrap();
10500 let n_embd = cfg.n_embd as usize;
10501 let n_expert = moe.expert_count as usize;
10502 let n_used = moe.expert_used_count as usize;
10503 let n_ff_exp = moe.expert_ff_length as usize;
10504 let lim_exp = cfg.clamp_exp_at(il as u32);
10506 let lim_shexp = cfg.clamp_shexp_at(il as u32);
10507
10508 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
10509 if let Some(sig) = cfg.sigmoid_router() {
10510 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
10511 }
10512 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10513 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
10514 } else {
10515 Self::moe_route_cfg(
10516 e,
10517 &logits,
10518 mrows,
10519 n_expert,
10520 n_used,
10521 m.active_experts.as_deref(),
10522 )?
10523 };
10524 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
10525
10526 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
10528 Ok((0..n_expert)
10529 .map(|ex| {
10530 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
10531 .into_iter()
10532 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
10533 })
10534 .collect())
10535 })?;
10536
10537 struct Group {
10538 rows: Vec<i32>,
10539 slots: Vec<i32>,
10540 weights: Vec<f32>,
10541 }
10542 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
10543 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
10544 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
10545 Default::default();
10546 for row in 0..mrows {
10547 for j in 0..n_used {
10548 let ex = sel_all[row * n_used + j] as usize;
10549 let w = w_all[row * n_used + j];
10550 if resident_expert[ex] {
10551 let group = groups.entry(ex).or_insert_with(|| Group {
10552 rows: Vec::new(),
10553 slots: Vec::new(),
10554 weights: Vec::new(),
10555 });
10556 group.rows.push(row as i32);
10557 group.slots.push(j as i32);
10558 group.weights.push(w);
10559 } else {
10560 crate::cpu_experts::record_incomplete_gpu_residency(0);
10561 cpu_rows[row].push((ex, w));
10562 cpu_by_expert.entry(ex).or_default().push((row, w));
10563 }
10564 }
10565 }
10566
10567 let host_rows = e.dtoh(zbatch)?;
10573 let rows_ok = crate::cpu_experts::rows_supported();
10574 enum CpuPart {
10575 Single { row: usize },
10576 Rows { rows: Vec<usize> },
10577 }
10578 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
10579 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
10580 if rows_ok {
10581 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
10582 .into_iter()
10583 .filter(|(_, rows)| rows.len() >= 2)
10584 .collect();
10585 shared.sort_by_key(|(ex, _)| *ex);
10586 for (ex, mut row_weights) in shared {
10587 row_weights.sort_by_key(|(row, _)| *row);
10588 let inputs: Vec<(&[f32], f32)> = row_weights
10589 .iter()
10590 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
10591 .collect();
10592 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
10593 .map_err(std::io::Error::other)?;
10594 for &(row, _) in &row_weights {
10595 rows_served.insert((row, ex));
10596 }
10597 tickets.push((
10598 CpuPart::Rows {
10599 rows: row_weights.iter().map(|&(row, _)| row).collect(),
10600 },
10601 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
10602 ));
10603 }
10604 }
10605 for (row, selected) in cpu_rows.iter().enumerate() {
10606 let leftover: Vec<(usize, f32)> = selected
10607 .iter()
10608 .copied()
10609 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
10610 .collect();
10611 if leftover.is_empty() {
10612 continue;
10613 }
10614 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
10615 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
10616 .map_err(std::io::Error::other)?;
10617 tickets.push((
10618 CpuPart::Single { row },
10619 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
10620 ));
10621 }
10622
10623 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
10624 let mut wbuf = e.zeros(mrows * n_used)?;
10625 let mut order: Vec<usize> = groups.keys().copied().collect();
10626 order.sort_by(|&a, &b| {
10627 groups[&b]
10628 .rows
10629 .len()
10630 .cmp(&groups[&a].rows.len())
10631 .then(a.cmp(&b))
10632 });
10633 for &ex in &order {
10634 let group = &groups[&ex];
10635 let m_e = group.rows.len();
10636 let gl = m.gate_exps.expert_layout(ex);
10637 let ul = m.up_exps.expert_layout(ex);
10638 let dl = m.down_exps.expert_layout(ex);
10639 let row_idx_d = e.htod_i32(&group.rows)?;
10640 let slot_idx_d = e.htod_i32(&group.slots)?;
10641 let dmac = m.down_exps.macro_scale(ex);
10642 let weight_d = if dmac == 1.0 {
10643 e.htod(&group.weights)?
10644 } else {
10645 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
10646 e.htod(&scaled)?
10647 };
10648 let mut gathered = e.zeros(m_e * n_embd)?;
10649 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
10650 let gv = gathered.slice(0..m_e * n_embd);
10651 let gate = e.with_moe_cache(max_block, |c, eng| {
10652 let slot = c
10653 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
10654 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10655 eng.qmatvec_view(
10656 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10657 0..gl.len,
10658 &gv,
10659 m_e,
10660 m.gate_exps.in_f,
10661 m.gate_exps.out_f,
10662 gl.qtype,
10663 gl.row_bytes,
10664 )
10665 })?;
10666 let up = e.with_moe_cache(max_block, |c, eng| {
10667 let slot = c
10668 .resident(BlockId::new(il, PROJ_UP, ex as u16))
10669 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10670 eng.qmatvec_view(
10671 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10672 0..ul.len,
10673 &gv,
10674 m_e,
10675 m.up_exps.in_f,
10676 m.up_exps.out_f,
10677 ul.qtype,
10678 ul.row_bytes,
10679 )
10680 })?;
10681 let mut act = e.zeros(m_e * n_ff_exp)?;
10682 Self::ffn_act_lim(
10683 e,
10684 cfg,
10685 &gate,
10686 &up,
10687 m.gate_exps.macro_scale(ex),
10688 m.up_exps.macro_scale(ex),
10689 lim_exp,
10690 &mut act,
10691 m_e * n_ff_exp,
10692 )?;
10693 let actv = act.slice(0..m_e * n_ff_exp);
10694 let y = e.with_moe_cache(max_block, |c, eng| {
10695 let slot = c
10696 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
10697 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10698 eng.qmatvec_view(
10699 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10700 0..dl.len,
10701 &actv,
10702 m_e,
10703 m.down_exps.in_f,
10704 m.down_exps.out_f,
10705 dl.qtype,
10706 dl.row_bytes,
10707 )
10708 })?;
10709 e.scatter_slot(
10710 &y,
10711 &row_idx_d,
10712 &slot_idx_d,
10713 &weight_d,
10714 &mut slot_buf,
10715 &mut wbuf,
10716 n_embd,
10717 n_used,
10718 m_e,
10719 )?;
10720 }
10721 let mut moe_out = e.zeros(mrows * n_embd)?;
10722 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
10723
10724 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
10726 for (part, ticket) in tickets {
10727 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
10728 let mut add_row = |row: usize, chunk: &[f32]| {
10729 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
10730 for (accumulator, value) in sum.iter_mut().zip(chunk) {
10731 *accumulator += value;
10732 }
10733 };
10734 match part {
10735 CpuPart::Single { row } => add_row(row, &cpu_output),
10736 CpuPart::Rows { rows } => {
10737 for (slot, row) in rows.into_iter().enumerate() {
10738 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
10739 }
10740 }
10741 }
10742 }
10743 for (row, sum) in row_sums.into_iter().enumerate() {
10744 let Some(sum) = sum else { continue };
10745 let cpu_output = e.htod(&sum)?;
10746 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
10747 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
10748 }
10749
10750 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
10751 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
10752 {
10753 let n_ff_sh = gate_shexp.out_features();
10754 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
10755 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
10756 let mut sa = e.zeros(mrows * n_ff_sh)?;
10757 Self::ffn_act_lim(
10758 e,
10759 cfg,
10760 &sg_gate,
10761 &sg_up,
10762 1.0,
10763 1.0,
10764 lim_shexp,
10765 &mut sa,
10766 mrows * n_ff_sh,
10767 )?;
10768 let sh = e.matmul(down_shexp, &sa, mrows)?;
10769 let g = match &m.gate_inp_shexp {
10772 Some(gate_inp_shexp) => {
10773 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
10774 }
10775 None => e.htod(&vec![1.0f32; mrows])?,
10776 };
10777 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
10778 }
10779
10780 Ok(moe_out)
10781 }
10782}
10783
10784impl HybridModel {
10790 pub(crate) fn gemma4_rope_dims(&self, il: usize) -> usize {
10804 let g = self
10805 .cfg
10806 .gemma4
10807 .as_ref()
10808 .expect("gemma4_rope_dims on a non-gemma4 config");
10809 if g.swa_pattern[il] {
10810 g.rope_dims_swa as usize
10811 } else {
10812 g.rope_dims_global as usize
10813 }
10814 }
10815
10816 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
10817 let g = self.cfg.gemma4.as_ref().unwrap();
10818 let swa = g.swa_pattern[il];
10819 let hd = if swa {
10820 g.key_length_swa
10821 } else {
10822 g.key_length_global
10823 } as usize;
10824 (
10828 hd,
10829 g.head_count_kv[il] as usize,
10830 self.cfg.n_head as usize,
10831 if swa {
10832 g.rope_base_swa
10833 } else {
10834 g.rope_base_global
10835 },
10836 1.0,
10837 swa,
10838 )
10839 }
10840
10841 pub(crate) fn gemma4_suppress(
10845 &self,
10846 e: &Engine,
10847 ld: &mut CudaSlice<f32>,
10848 t: usize,
10849 ) -> Result<(), Box<dyn std::error::Error>> {
10850 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
10851 #[cfg(debug_assertions)]
10856 crate::debug_assert_tensor_stream_device(
10857 ids,
10858 &e.stream(),
10859 "gemma4_suppress.suppress_d",
10860 );
10861 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
10862 }
10863 Ok(())
10864 }
10865
10866 #[allow(clippy::too_many_arguments)]
10871 fn gemma_fa_one_program() -> bool {
10880 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10881 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_FA_ONE_PROGRAM").as_deref() == Ok("1"))
10882 }
10883
10884 fn gemma4_attn_prime(
10885 &self,
10886 e: &Engine,
10887 fa: &crate::hybrid::FullAttnLayer,
10888 il: usize,
10889 h: &CudaSlice<f32>,
10890 pos_d: &CudaSlice<i32>,
10891 t: usize,
10892 cache: Option<&mut Cache>,
10893 island: Option<&CudaSlice<i32>>,
10894 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10895 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10896 let eps = self.cfg.rms_eps;
10897 let aux = self.gemma4_aux.as_ref().unwrap();
10898 let ones = aux.ones(e);
10899 #[cfg(debug_assertions)]
10900 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
10901
10902 e.mmq_act_begin();
10905 let q0 = e.matmul(&fa.wq, h, t)?; if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10907 let v = e.dtoh(&q0)?;
10908 let nan = v.iter().filter(|x| x.is_nan()).count();
10909 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10910 eprintln!(
10911 "[g4-prime-trace] L0 q0: nan={nan}/{} amax={amax:.3}",
10912 v.len()
10913 );
10914 }
10915 let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
10919 e.matmul(&fa.wv, h, t)?
10920 } else {
10921 e.clone_dtod(&k0)?
10922 };
10923 if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10924 for (tag, buf) in [("k0", &k0), ("v0", &v0)] {
10925 let v = e.dtoh(buf)?;
10926 let nan = v.iter().filter(|x| x.is_nan()).count();
10927 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10928 eprintln!(
10929 "[g4-prime-trace] L0 {tag}: nan={nan}/{} amax={amax:.3}",
10930 v.len()
10931 );
10932 }
10933 }
10934
10935 let mut q = e.uninit(t * nh * hd)?;
10936 let mut k = e.uninit(t * nkv * hd)?;
10937 let mut v = e.uninit(t * nkv * hd)?;
10939 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10943 let emit = island.is_none()
10946 && t >= 16
10947 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
10948 && *EMIT.get_or_init(|| {
10949 std::env::var("MEMRA_FA_EMIT")
10950 .map(|s| s != "0")
10951 .unwrap_or(true)
10952 });
10953 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
10954 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10955 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10956 let v_f16 = emit
10959 && crate::fa_f16pv_on()
10960 && match hd {
10961 512 => true,
10962 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
10963 _ => false,
10964 };
10965 if emit {
10966 e.rms_norm_qkv_w4b(
10967 &q0,
10968 &k0,
10969 &v0,
10970 fa.q_norm.float_data(),
10971 fa.k_norm.float_data(),
10972 ones,
10973 &mut q,
10974 &mut k,
10975 &mut v,
10976 &mut vb,
10977 hd,
10978 nh * t,
10979 nkv * t,
10980 eps,
10981 v_f16,
10982 )?;
10983 } else {
10984 e.rms_norm_qkv(
10985 &q0,
10986 &k0,
10987 &v0,
10988 fa.q_norm.float_data(),
10989 fa.k_norm.float_data(),
10990 ones,
10991 &mut q,
10992 &mut k,
10993 &mut v,
10994 hd,
10995 nh * t,
10996 nkv * t,
10997 eps,
10998 )?;
10999 }
11000
11001 let ff = if swa {
11002 None
11003 } else {
11004 Some(
11005 aux.rope_freqs(e)
11006 .expect("gemma4 global rope needs rope_freqs.weight"),
11007 )
11008 };
11009 #[cfg(debug_assertions)]
11010 if let Some(ff) = ff {
11011 crate::debug_assert_tensor_stream_device(
11012 ff,
11013 &e.stream(),
11014 "gemma4_attn_prime.rope_freqs",
11015 );
11016 }
11017 if emit {
11018 e.rope_neox2_bf16e(
11019 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
11020 )?;
11021 } else {
11022 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
11023 }
11024
11025 if let Some(cache) = cache {
11026 let kvl = cache.kv[il].as_mut().unwrap();
11027 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
11028 e.append_kv_quantized_rows(
11029 &k,
11030 &v,
11031 &mut kvl.k,
11032 &mut kvl.v,
11033 kvl.len,
11034 t,
11035 kvl.kv_dim_k,
11036 kvl.kv_dim_v,
11037 kvl.k_tok_bytes,
11038 kvl.v_tok_bytes,
11039 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
11040 )?;
11041 kvl.len += t;
11042 }
11043 let mut attn = e.zeros(t * nh * hd)?;
11044 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11048 if let Some(span) = island {
11049 let w = if swa && t > win { win } else { 0 };
11054 e.sdpa_naive_island(&q, &k, &v, &mut attn, span, hd, nh, nkv, t, t, scale, w)?;
11055 } else if swa && (t > win || Self::gemma_fa_one_program()) {
11056 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11057 if emit {
11058 e.fa_prefill_w_pre(
11059 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
11060 )?;
11061 } else {
11062 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11063 }
11064 } else {
11065 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11066 }
11067 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11068 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11069 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
11070 if emit {
11071 e.fa_prefill_hd512_pre(
11072 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
11073 )?;
11074 } else {
11075 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11076 }
11077 } else {
11078 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11079 }
11080 Ok(e.matmul(&fa.wo, &attn, t)?)
11081 }
11082
11083 fn gemma4_attn(
11085 &self,
11086 e: &Engine,
11087 fa: &crate::hybrid::FullAttnLayer,
11088 il: usize,
11089 h: &CudaSlice<f32>,
11090 pos_d: &CudaSlice<i32>,
11091 t: usize,
11092 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11093 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None, None)
11094 }
11095
11096 fn gemma4_moe_q8(
11101 &self,
11102 e: &Engine,
11103 m: &crate::hybrid::MoeWeights,
11104 bits: &crate::hybrid::Gemma4MoeBits,
11105 mq: &(CudaSlice<i8>, CudaSlice<f32>),
11106 router_in: &CudaSlice<f32>,
11107 t: usize,
11108 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11109 let cfg = &self.cfg;
11110 let moe = cfg.moe.as_ref().unwrap();
11111 let n_embd = cfg.n_embd as usize;
11112 let n_expert = moe.expert_count as usize;
11113 let n_used = moe.expert_used_count as usize;
11114 let n_ff_exp = moe.expert_ff_length as usize;
11115 let logits = if crate::router_kernel_on() {
11119 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11120 } else {
11121 e.matmul(&m.gate_inp, router_in, t)?
11122 };
11123 let dev = m.dev_exps.as_ref().unwrap();
11124 let (sel_d, w_d) =
11125 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11126 let (zq, zd) = mq;
11127 if t == 1 {
11128 let selv = sel_d.slice(0..n_used);
11129 let wv = w_d.slice(0..n_used);
11130 let act = e.moe_gate_up_gelu8_dev_q8(
11131 &dev.ptr_row,
11132 &selv,
11133 zq,
11134 zd,
11135 n_embd,
11136 n_ff_exp,
11137 n_used,
11138 n_expert,
11139 m.gate_exps.qtype,
11140 m.up_exps.qtype,
11141 m.gate_exps.row_bytes,
11142 m.up_exps.row_bytes,
11143 )?;
11144 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11145 let mut moe_out = e.uninit(n_embd)?;
11146 e.moe_down8_fma_dev_q8(
11147 &dev.ptr_row,
11148 &selv,
11149 &wv,
11150 &aq2,
11151 &ad2,
11152 &mut moe_out.slice_mut(0..n_embd),
11153 n_ff_exp,
11154 n_embd,
11155 n_used,
11156 n_expert,
11157 m.down_exps.qtype,
11158 m.down_exps.row_bytes,
11159 )?;
11160 return Ok(moe_out);
11161 }
11162 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11163 let act = if csr {
11164 e.moe_gate_up_gelu8_dev_q8_csr(
11165 &dev.ptr_row,
11166 &sel_d,
11167 zq,
11168 zd,
11169 t * n_used,
11170 n_embd,
11171 n_ff_exp,
11172 n_used,
11173 n_expert,
11174 m.gate_exps.qtype,
11175 m.up_exps.qtype,
11176 m.gate_exps.row_bytes,
11177 m.up_exps.row_bytes,
11178 )?
11179 } else {
11180 e.moe_gate_up_gelu8_dev_q8_rows(
11181 &dev.ptr_row,
11182 &sel_d,
11183 zq,
11184 zd,
11185 t,
11186 n_embd,
11187 n_ff_exp,
11188 n_used,
11189 n_expert,
11190 m.gate_exps.qtype,
11191 m.up_exps.qtype,
11192 m.gate_exps.row_bytes,
11193 m.up_exps.row_bytes,
11194 )?
11195 };
11196 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11197 let mut moe_out = e.uninit(t * n_embd)?;
11198 e.moe_down8_fma_dev_q8_rows_g(
11201 &dev.ptr_row,
11202 &sel_d,
11203 &w_d,
11204 &aq2,
11205 &ad2,
11206 &mut moe_out,
11207 t,
11208 n_ff_exp,
11209 n_embd,
11210 n_used,
11211 n_expert,
11212 m.down_exps.qtype,
11213 m.down_exps.row_bytes,
11214 )?;
11215 Ok(moe_out)
11216 }
11217
11218 fn gemma4_moe(
11222 &self,
11223 e: &Engine,
11224 m: &crate::hybrid::MoeWeights,
11225 bits: &crate::hybrid::Gemma4MoeBits,
11226 moe_in: &CudaSlice<f32>,
11227 router_in: &CudaSlice<f32>,
11228 t: usize,
11229 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11230 let cfg = &self.cfg;
11231 let moe = cfg.moe.as_ref().unwrap();
11232 let n_embd = cfg.n_embd as usize;
11233 let n_expert = moe.expert_count as usize;
11234 let n_used = moe.expert_used_count as usize;
11235 let n_ff_exp = moe.expert_ff_length as usize;
11236
11237 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
11241 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11242 } else {
11243 e.matmul(&m.gate_inp, router_in, t)?
11244 };
11245
11246 if t < PRIME_MIN_T
11251 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11252 && expert_dp4a_supported(m.gate_exps.qtype)
11253 && expert_dp4a_supported(m.up_exps.qtype)
11254 && expert_dp4a_supported(m.down_exps.qtype)
11255 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11256 {
11257 let dev = m.dev_exps.as_ref().unwrap();
11258 let (sel_d, w_d) =
11259 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11260 if t == 1 {
11261 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
11262 let selv = sel_d.slice(0..n_used);
11263 let wv = w_d.slice(0..n_used);
11264 let act = e.moe_gate_up_gelu8_dev_q8(
11265 &dev.ptr_row,
11266 &selv,
11267 &zq,
11268 &zd,
11269 n_embd,
11270 n_ff_exp,
11271 n_used,
11272 n_expert,
11273 m.gate_exps.qtype,
11274 m.up_exps.qtype,
11275 m.gate_exps.row_bytes,
11276 m.up_exps.row_bytes,
11277 )?;
11278 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11279 let mut moe_out = e.uninit(n_embd)?;
11280 e.moe_down8_fma_dev_q8(
11281 &dev.ptr_row,
11282 &selv,
11283 &wv,
11284 &aq2,
11285 &ad2,
11286 &mut moe_out.slice_mut(0..n_embd),
11287 n_ff_exp,
11288 n_embd,
11289 n_used,
11290 n_expert,
11291 m.down_exps.qtype,
11292 m.down_exps.row_bytes,
11293 )?;
11294 return Ok(moe_out);
11295 }
11296 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11301 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11302 let act = if csr {
11303 e.moe_gate_up_gelu8_dev_q8_csr(
11304 &dev.ptr_row,
11305 &sel_d,
11306 &zq,
11307 &zd,
11308 t * n_used,
11309 n_embd,
11310 n_ff_exp,
11311 n_used,
11312 n_expert,
11313 m.gate_exps.qtype,
11314 m.up_exps.qtype,
11315 m.gate_exps.row_bytes,
11316 m.up_exps.row_bytes,
11317 )?
11318 } else {
11319 e.moe_gate_up_gelu8_dev_q8_rows(
11320 &dev.ptr_row,
11321 &sel_d,
11322 &zq,
11323 &zd,
11324 t,
11325 n_embd,
11326 n_ff_exp,
11327 n_used,
11328 n_expert,
11329 m.gate_exps.qtype,
11330 m.up_exps.qtype,
11331 m.gate_exps.row_bytes,
11332 m.up_exps.row_bytes,
11333 )?
11334 };
11335 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11336 let mut moe_out = e.uninit(t * n_embd)?;
11337 e.moe_down8_fma_dev_q8_rows_g(
11338 &dev.ptr_row,
11339 &sel_d,
11340 &w_d,
11341 &aq2,
11342 &ad2,
11343 &mut moe_out,
11344 t,
11345 n_ff_exp,
11346 n_embd,
11347 n_used,
11348 n_expert,
11349 m.down_exps.qtype,
11350 m.down_exps.row_bytes,
11351 )?;
11352 return Ok(moe_out);
11353 }
11354
11355 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
11356 for (i, &sx) in sel_all.iter().enumerate() {
11357 w_all[i] *= bits.per_expert_scale[sx as usize];
11358 }
11359
11360 if t >= PRIME_MIN_T
11364 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11365 && expert_dp4a_supported(m.gate_exps.qtype)
11366 && expert_dp4a_supported(m.up_exps.qtype)
11367 && expert_dp4a_supported(m.down_exps.qtype)
11368 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
11369 {
11370 let dev = m.dev_exps.as_ref().unwrap();
11371 let n_pairs = t * n_used;
11372 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
11373 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
11374 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
11375 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
11376 let pt = e.htod_i32(&pair_tok)?;
11377 let pw = e.htod(&w_all)?;
11378 let toff = e.htod_i32(&tok_off)?;
11379 let tids = e.htod_i32(&tok_ids)?;
11380 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11381 for p in 0..n_pairs {
11382 by_ex[pair_ex[p] as usize].push(p as i32);
11383 }
11384 let mut ex_ids: Vec<i32> = Vec::new();
11385 let mut ex_off: Vec<i32> = vec![0];
11386 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
11387 for (ex, list) in by_ex.iter().enumerate() {
11388 if list.is_empty() {
11389 continue;
11390 }
11391 ex_ids.push(ex as i32);
11392 ex_pairs.extend_from_slice(list);
11393 ex_off.push(ex_pairs.len() as i32);
11394 }
11395 let n_active = ex_ids.len();
11396 let exi = e.htod_i32(&ex_ids)?;
11397 let exo = e.htod_i32(&ex_off)?;
11398 let exp_d = e.htod_i32(&ex_pairs)?;
11399 if crate::moe_f16g_gemma_on()
11407 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
11408 && f16g_proj_ok(m.up_exps.qtype, n_embd)
11409 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
11410 {
11411 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11412 let csr_tok_d = e.htod_i32(&csr_tok)?;
11413 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
11414 let g_csr = e.moe_f16_grouped(
11415 &dev.ptr_row,
11416 0,
11417 n_expert,
11418 &exi,
11419 &ex_off,
11420 &exo,
11421 &z_f16,
11422 &z_s,
11423 n_embd,
11424 n_ff_exp,
11425 n_active,
11426 n_pairs,
11427 m.gate_exps.qtype,
11428 m.gate_exps.row_bytes,
11429 )?;
11430 let u_csr = e.moe_f16_grouped(
11431 &dev.ptr_row,
11432 1,
11433 n_expert,
11434 &exi,
11435 &ex_off,
11436 &exo,
11437 &z_f16,
11438 &z_s,
11439 n_embd,
11440 n_ff_exp,
11441 n_active,
11442 n_pairs,
11443 m.up_exps.qtype,
11444 m.up_exps.row_bytes,
11445 )?;
11446 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
11447 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
11448 let d_csr = e.moe_f16_grouped(
11449 &dev.ptr_row,
11450 2,
11451 n_expert,
11452 &exi,
11453 &ex_off,
11454 &exo,
11455 &a_f16,
11456 &a_s,
11457 n_ff_exp,
11458 n_embd,
11459 n_active,
11460 n_pairs,
11461 m.down_exps.qtype,
11462 m.down_exps.row_bytes,
11463 )?;
11464 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
11465 let mut moe_out = e.uninit(t * n_embd)?;
11466 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11467 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
11468 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
11469 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
11470 eprintln!(
11471 "[f16g-debug] post-permute bad={} post-scatter bad={}",
11472 scan(&yd),
11473 scan(&mo)
11474 );
11475 }
11476 return Ok(moe_out);
11477 }
11478 let mma =
11481 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
11482 let (gate, up) = if mma {
11483 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
11484 (
11485 e.mmq_iq_experts(
11486 &dev.ptr_row,
11487 0,
11488 n_expert,
11489 &exi,
11490 &exo,
11491 &exp_d,
11492 &pt,
11493 &z_scr,
11494 n_embd,
11495 n_ff_exp,
11496 n_active,
11497 n_pairs,
11498 t,
11499 m.gate_exps.qtype,
11500 m.gate_exps.row_bytes,
11501 )?,
11502 e.mmq_iq_experts(
11503 &dev.ptr_row,
11504 1,
11505 n_expert,
11506 &exi,
11507 &exo,
11508 &exp_d,
11509 &pt,
11510 &z_scr,
11511 n_embd,
11512 n_ff_exp,
11513 n_active,
11514 n_pairs,
11515 t,
11516 m.up_exps.qtype,
11517 m.up_exps.row_bytes,
11518 )?,
11519 )
11520 } else {
11521 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11522 (
11523 e.moe_pairs_matvec_q8_dec(
11524 &dev.ptr_row,
11525 0,
11526 &exi,
11527 &exo,
11528 &exp_d,
11529 &pt,
11530 &zq,
11531 &zd,
11532 n_embd,
11533 n_ff_exp,
11534 n_expert,
11535 n_active,
11536 n_pairs,
11537 m.gate_exps.qtype,
11538 m.gate_exps.row_bytes,
11539 )?,
11540 e.moe_pairs_matvec_q8_dec(
11541 &dev.ptr_row,
11542 1,
11543 &exi,
11544 &exo,
11545 &exp_d,
11546 &pt,
11547 &zq,
11548 &zd,
11549 n_embd,
11550 n_ff_exp,
11551 n_expert,
11552 n_active,
11553 n_pairs,
11554 m.up_exps.qtype,
11555 m.up_exps.row_bytes,
11556 )?,
11557 )
11558 };
11559 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
11560 let pself = e.htod_i32(&pair_self)?;
11561 let y_down = if mma {
11573 let in_pad = n_ff_exp.div_ceil(256) * 256;
11574 let a_scr = if crate::moe_fuse_actq_on() {
11575 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
11576 } else {
11577 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11578 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
11579 };
11580 e.mmq_iq_experts(
11581 &dev.ptr_row,
11582 2,
11583 n_expert,
11584 &exi,
11585 &exo,
11586 &exp_d,
11587 &pself,
11588 &a_scr,
11589 in_pad,
11590 n_embd,
11591 n_active,
11592 n_pairs,
11593 n_pairs,
11594 m.down_exps.qtype,
11595 m.down_exps.row_bytes,
11596 )?
11597 } else {
11598 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11599 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
11600 e.moe_pairs_matvec_q8_dec(
11601 &dev.ptr_row,
11602 2,
11603 &exi,
11604 &exo,
11605 &exp_d,
11606 &pself,
11607 &aq2,
11608 &ad2,
11609 n_ff_exp,
11610 n_embd,
11611 n_expert,
11612 n_active,
11613 n_pairs,
11614 m.down_exps.qtype,
11615 m.down_exps.row_bytes,
11616 )?
11617 };
11618 let mut moe_out = e.uninit(t * n_embd)?;
11619 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11620 return Ok(moe_out);
11621 }
11622
11623 let g_len = m.gate_exps.expert_stride;
11624 let u_len = m.up_exps.expert_stride;
11625 let d_len = m.down_exps.expert_stride;
11626 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
11630 let (mut sg, mut su, mut sd) = if dev.is_some() {
11631 (None, None, None)
11632 } else {
11633 (
11634 Some(e.alloc_u8_uninit(g_len)?),
11635 Some(e.alloc_u8_uninit(u_len)?),
11636 Some(e.alloc_u8_uninit(d_len)?),
11637 )
11638 };
11639 let mut moe_out = e.zeros(t * n_embd)?;
11640 for tok in 0..t {
11641 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
11642 let w = &w_all[tok * n_used..(tok + 1) * n_used];
11643 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
11644 for (j, &ex) in sel.iter().enumerate() {
11645 let ex = ex as usize;
11646 let gate = match dev {
11647 Some(d) => e.qmatvec_view(
11648 &d.gate,
11649 ex * g_len..(ex + 1) * g_len,
11650 &zt,
11651 1,
11652 m.gate_exps.in_f,
11653 m.gate_exps.out_f,
11654 m.gate_exps.qtype,
11655 m.gate_exps.row_bytes,
11656 )?,
11657 None => {
11658 let sg = sg.as_mut().unwrap();
11659 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
11660 e.qmatvec_view(
11661 sg,
11662 0..g_len,
11663 &zt,
11664 1,
11665 m.gate_exps.in_f,
11666 m.gate_exps.out_f,
11667 m.gate_exps.qtype,
11668 m.gate_exps.row_bytes,
11669 )?
11670 }
11671 };
11672 let up = match dev {
11673 Some(d) => e.qmatvec_view(
11674 &d.up,
11675 ex * u_len..(ex + 1) * u_len,
11676 &zt,
11677 1,
11678 m.up_exps.in_f,
11679 m.up_exps.out_f,
11680 m.up_exps.qtype,
11681 m.up_exps.row_bytes,
11682 )?,
11683 None => {
11684 let su = su.as_mut().unwrap();
11685 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
11686 e.qmatvec_view(
11687 su,
11688 0..u_len,
11689 &zt,
11690 1,
11691 m.up_exps.in_f,
11692 m.up_exps.out_f,
11693 m.up_exps.qtype,
11694 m.up_exps.row_bytes,
11695 )?
11696 }
11697 };
11698 let mut act = e.uninit(n_ff_exp)?;
11699 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
11700 let actv = act.slice(0..n_ff_exp);
11701 let y = match dev {
11702 Some(d) => e.qmatvec_view(
11703 &d.down,
11704 ex * d_len..(ex + 1) * d_len,
11705 &actv,
11706 1,
11707 m.down_exps.in_f,
11708 m.down_exps.out_f,
11709 m.down_exps.qtype,
11710 m.down_exps.row_bytes,
11711 )?,
11712 None => {
11713 let sd = sd.as_mut().unwrap();
11714 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
11715 e.qmatvec_view(
11716 sd,
11717 0..d_len,
11718 &actv,
11719 1,
11720 m.down_exps.in_f,
11721 m.down_exps.out_f,
11722 m.down_exps.qtype,
11723 m.down_exps.row_bytes,
11724 )?
11725 }
11726 };
11727 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
11728 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
11729 }
11730 }
11731 Ok(moe_out)
11732 }
11733
11734 fn gemma4_layer(
11736 &self,
11737 e: &Engine,
11738 il: usize,
11739 layer: &crate::hybrid::HybridLayer,
11740 x: &CudaSlice<f32>,
11741 pos_d: &CudaSlice<i32>,
11742 t: usize,
11743 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11744 let n_embd = self.cfg.n_embd as usize;
11745 let eps = self.cfg.rms_eps;
11746
11747 let mut h = e.zeros(t * n_embd)?;
11748 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
11749 let Mixer::Full(fa) = &layer.mixer else {
11750 panic!("gemma4 layer {il} not full-attn")
11751 };
11752 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
11753 let mut cur = e.zeros(t * n_embd)?;
11755 e.rms_norm(
11756 &o,
11757 layer.post_attn_norm.float_data(),
11758 &mut cur,
11759 n_embd,
11760 t,
11761 eps,
11762 )?;
11763 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
11764 }
11765
11766 fn gemma4_layer_tail_add(
11770 &self,
11771 e: &Engine,
11772 layer: &crate::hybrid::HybridLayer,
11773 cur: &CudaSlice<f32>,
11774 x: &CudaSlice<f32>,
11775 t: usize,
11776 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11777 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
11778 }
11779
11780 fn gemma4_layer_tail_add_n(
11783 &self,
11784 e: &Engine,
11785 layer: &crate::hybrid::HybridLayer,
11786 cur: &CudaSlice<f32>,
11787 x: &CudaSlice<f32>,
11788 t: usize,
11789 next_norm: Option<&CudaSlice<f32>>,
11790 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
11791 let n_embd = self.cfg.n_embd as usize;
11792 let bits = layer.gemma4.as_ref().unwrap();
11793 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
11794 let mut xn = e.uninit(t * n_embd)?;
11795 match next_norm {
11796 Some(w) => {
11797 let mut hn = e.uninit(t * n_embd)?;
11798 e.add_scale_rms_norm(
11799 &sn,
11800 &attn_out,
11801 bits.layer_scale,
11802 w,
11803 &mut xn,
11804 &mut hn,
11805 n_embd,
11806 t,
11807 self.cfg.rms_eps,
11808 )?;
11809 Ok((xn, Some(hn)))
11810 }
11811 None => {
11812 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
11813 Ok((xn, None))
11814 }
11815 }
11816 }
11817
11818 fn gemma4_layer_tail_core(
11821 &self,
11822 e: &Engine,
11823 layer: &crate::hybrid::HybridLayer,
11824 cur: &CudaSlice<f32>,
11825 x: &CudaSlice<f32>,
11826 t: usize,
11827 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11828 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
11829 }
11830
11831 fn gemma4_layer_tail_core_pn(
11838 &self,
11839 e: &Engine,
11840 layer: &crate::hybrid::HybridLayer,
11841 cur: &CudaSlice<f32>,
11842 x: &CudaSlice<f32>,
11843 t: usize,
11844 pre_norm: Option<&CudaSlice<f32>>,
11845 defer_post_norm: bool,
11846 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11847 let n_embd = self.cfg.n_embd as usize;
11848 let eps = self.cfg.rms_eps;
11849 let bits = layer.gemma4.as_ref().unwrap();
11850
11851 let Some(mbits) = bits.moe_bits.as_ref() else {
11854 let crate::hybrid::Ffn::Dense {
11855 ffn_gate,
11856 ffn_up,
11857 ffn_down,
11858 } = &layer.ffn
11859 else {
11860 panic!("gemma4 dense layer without Dense ffn")
11861 };
11862 let mut attn_out = e.uninit(t * n_embd)?;
11863 let mut zsh = e.uninit(t * n_embd)?;
11864 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11867 match pre_norm {
11868 Some(wa) if t == 1 => {
11869 zpair = Some(e.rms_pre_add_rms_norm_q8z(
11870 cur,
11871 wa,
11872 x,
11873 bits.ffn_norm.float_data(),
11874 &mut attn_out,
11875 &mut zsh,
11876 n_embd,
11877 t,
11878 eps,
11879 )?);
11880 }
11881 Some(wa) => e.rms_pre_add_rms_norm(
11882 cur,
11883 wa,
11884 x,
11885 bits.ffn_norm.float_data(),
11886 &mut attn_out,
11887 &mut zsh,
11888 n_embd,
11889 t,
11890 eps,
11891 )?,
11892 None => e.add_rms_norm(
11893 cur,
11894 x,
11895 bits.ffn_norm.float_data(),
11896 &mut attn_out,
11897 &mut zsh,
11898 n_embd,
11899 t,
11900 eps,
11901 )?,
11902 }
11903 let n_ff = ffn_gate.out_features();
11904 let (gate, up) = if t == 1 {
11910 let (zq, zd) = match zpair {
11911 Some(p) => p,
11912 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
11913 };
11914 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
11915 Some(p) => p,
11916 None => match e.matmul_nvfp4_fused2(ffn_gate, ffn_up, &zq, &zd, 1)? {
11918 Some(p) => p,
11919 None => (
11920 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
11921 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
11922 ),
11923 },
11924 }
11925 } else {
11926 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11931 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11932 let fused = if f2b {
11933 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
11934 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
11935 } else {
11936 None
11937 };
11938 match fused {
11939 Some(p) => p,
11940 None => {
11941 e.mmq_act_begin();
11943 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
11944 }
11945 }
11946 };
11947 let mut act = e.uninit(t * n_ff)?;
11948 let f0 = if e.uses_q8_1_fast(ffn_down) {
11951 let upv = e.view(&up, t * n_ff);
11952 let up_all = upv.slice(0..t * n_ff);
11953 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
11954 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
11955 } else {
11956 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11957 e.matmul(ffn_down, &act, t)?
11958 };
11959 if defer_post_norm {
11960 return Ok((f0, attn_out));
11961 }
11962 let mut sn = e.uninit(t * n_embd)?;
11963 e.rms_norm(
11964 &f0,
11965 bits.post_ffw_norm.float_data(),
11966 &mut sn,
11967 n_embd,
11968 t,
11969 eps,
11970 )?;
11971 return Ok((sn, attn_out));
11972 };
11973
11974 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
11975 let mut attn_out = e.uninit(t * n_embd)?;
11980 let mut router_in = e.uninit(t * n_embd)?;
11981 let fast_moe = match &layer.ffn {
11982 crate::hybrid::Ffn::Moe(m) => {
11983 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11984 && expert_dp4a_supported(m.gate_exps.qtype)
11985 && expert_dp4a_supported(m.up_exps.qtype)
11986 && expert_dp4a_supported(m.down_exps.qtype)
11987 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11988 }
11989 _ => false,
11990 };
11991 let q8z = t < PRIME_MIN_T && fast_moe;
11992 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
11993 let (z0, m2) = e.add_rms_norm3_q8z(
11994 cur,
11995 x,
11996 bits.ffn_norm.float_data(),
11997 &mbits.router_scale_pre,
11998 mbits.pre_ffw_norm_2.float_data(),
11999 &mut attn_out,
12000 &mut router_in,
12001 n_embd,
12002 t,
12003 eps,
12004 )?;
12005 (None, Some(z0), Some(m2))
12006 } else {
12007 let mut zsh = e.uninit(t * n_embd)?;
12008 let mut moe_in = e.uninit(t * n_embd)?;
12009 e.add_rms_norm3(
12010 cur,
12011 x,
12012 bits.ffn_norm.float_data(),
12013 &mbits.router_scale_pre,
12014 mbits.pre_ffw_norm_2.float_data(),
12015 &mut attn_out,
12016 &mut zsh,
12017 &mut router_in,
12018 &mut moe_in,
12019 n_embd,
12020 t,
12021 eps,
12022 )?;
12023 (Some((zsh, moe_in)), None, None)
12024 };
12025 let attn_out2 = attn_out;
12026 #[allow(unused_variables)]
12027 let attn_out = &attn_out2;
12028 let n_ff = mbits.shared_gate.out_features();
12029 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
12030 if t == 1 {
12031 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
12032 Some(p) => p,
12033 None => match e.matmul_nvfp4_fused2(
12034 &mbits.shared_gate,
12035 &mbits.shared_up,
12036 zq,
12037 zd,
12038 1,
12039 )? {
12040 Some(p) => p,
12041 None => {
12042 let h0 = e.zeros(0)?;
12043 (
12044 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
12045 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
12046 )
12047 }
12048 },
12049 }
12050 } else {
12051 let h0 = e.zeros(0)?;
12053 (
12054 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
12055 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
12056 )
12057 }
12058 } else {
12059 let (zsh, _) = zsh_f32.as_ref().unwrap();
12060 (
12061 e.matmul(&mbits.shared_gate, zsh, t)?,
12062 e.matmul(&mbits.shared_up, zsh, t)?,
12063 )
12064 };
12065 let mut act = e.uninit(t * n_ff)?;
12066 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
12067 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
12068 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
12069 panic!("gemma4 layer not MoE")
12070 };
12071 let moe0 = match (&moe_q8, &zsh_f32) {
12072 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
12073 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
12074 _ => unreachable!(),
12075 };
12076 let mut mlp = e.uninit(t * n_embd)?;
12078 let mut moe = e.uninit(t * n_embd)?;
12079 e.rms_norm2x(
12080 &mlp0,
12081 &moe0,
12082 mbits.post_ffw_norm_1.float_data(),
12083 mbits.post_ffw_norm_2.float_data(),
12084 &mut mlp,
12085 &mut moe,
12086 n_embd,
12087 t,
12088 eps,
12089 )?;
12090
12091 let mut sum = e.uninit(t * n_embd)?;
12094 let mut sn = e.uninit(t * n_embd)?;
12095 e.add_rms_norm(
12096 &mlp,
12097 &moe,
12098 bits.post_ffw_norm.float_data(),
12099 &mut sum,
12100 &mut sn,
12101 n_embd,
12102 t,
12103 eps,
12104 )?;
12105 Ok((sn, attn_out2))
12106 }
12107
12108 pub(crate) fn gemma4_layer_tail_add_nq_pn(
12118 &self,
12119 e: &Engine,
12120 layer: &crate::hybrid::HybridLayer,
12121 o: &CudaSlice<f32>,
12122 x: &CudaSlice<f32>,
12123 t: usize,
12124 next_norm: Option<&CudaSlice<f32>>,
12125 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12126 {
12127 let n_embd = self.cfg.n_embd as usize;
12128 let eps = self.cfg.rms_eps;
12129 let bits = layer.gemma4.as_ref().unwrap();
12130 if Engine::g4_pnfold_on() && matches!(layer.ffn, crate::hybrid::Ffn::Dense { .. }) {
12131 let (f0, attn_out) = self.gemma4_layer_tail_core_pn(
12132 e,
12133 layer,
12134 o,
12135 x,
12136 t,
12137 Some(layer.post_attn_norm.float_data()),
12138 true,
12139 )?;
12140 let mut xn = e.uninit(t * n_embd)?;
12141 return match next_norm {
12142 Some(w) => {
12143 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
12144 &f0,
12145 bits.post_ffw_norm.float_data(),
12146 &attn_out,
12147 bits.layer_scale,
12148 w,
12149 &mut xn,
12150 n_embd,
12151 t,
12152 eps,
12153 )?;
12154 Ok((xn, Some(pair)))
12155 }
12156 None => {
12157 let mut sn = e.uninit(t * n_embd)?;
12158 e.rms_norm(
12159 &f0,
12160 bits.post_ffw_norm.float_data(),
12161 &mut sn,
12162 n_embd,
12163 t,
12164 eps,
12165 )?;
12166 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12167 Ok((xn, None))
12168 }
12169 };
12170 }
12171 let mut cur = e.uninit(t * n_embd)?;
12172 e.rms_norm(
12173 o,
12174 layer.post_attn_norm.float_data(),
12175 &mut cur,
12176 n_embd,
12177 t,
12178 eps,
12179 )?;
12180 self.gemma4_layer_tail_add_nq(e, layer, &cur, x, t, next_norm)
12181 }
12182
12183 pub(crate) fn gemma4_layer_tail_add_nq(
12184 &self,
12185 e: &Engine,
12186 layer: &crate::hybrid::HybridLayer,
12187 cur: &CudaSlice<f32>,
12188 x: &CudaSlice<f32>,
12189 t: usize,
12190 next_norm: Option<&CudaSlice<f32>>,
12191 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12192 {
12193 let n_embd = self.cfg.n_embd as usize;
12194 let bits = layer.gemma4.as_ref().unwrap();
12195 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12196 let mut xn = e.uninit(t * n_embd)?;
12197 match next_norm {
12198 Some(w) => {
12199 let pair = e.add_scale_rms_norm_q8_1(
12200 &sn,
12201 &attn_out,
12202 bits.layer_scale,
12203 w,
12204 &mut xn,
12205 n_embd,
12206 t,
12207 self.cfg.rms_eps,
12208 )?;
12209 Ok((xn, Some(pair)))
12210 }
12211 None => {
12212 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12213 Ok((xn, None))
12214 }
12215 }
12216 }
12217
12218 fn gemma4_forward(
12221 &self,
12222 e: &Engine,
12223 tokens: &[u32],
12224 last_only: bool,
12225 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12226 if self.is_gemma4_e4b() {
12229 return self.gemma4_e4b_forward(e, tokens, last_only);
12230 }
12231 let n_embd = self.cfg.n_embd as usize;
12232 let t = tokens.len();
12233 let pos: Vec<i32> = (0..t as i32).collect();
12234 let pos_d = e.htod_i32(&pos)?;
12235
12236 let mut x = self.embed(e, tokens)?;
12237 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12238 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
12241 let stat =
12242 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
12243 let h = e.dtoh(x)?;
12244 let bad = h.iter().filter(|v| !v.is_finite()).count();
12245 let mx = h
12246 .iter()
12247 .filter(|v| v.is_finite())
12248 .fold(0.0f32, |m, v| m.max(v.abs()));
12249 eprintln!(
12250 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
12251 &h[..3]
12252 );
12253 Ok(())
12254 };
12255 if probe {
12256 stat(e, &x, "embed")?;
12257 }
12258 for (il, layer) in self.layers.iter().enumerate() {
12259 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
12260 if probe {
12261 stat(e, &x, &format!("L{il}"))?;
12262 }
12263 }
12264 let mut hn = e.zeros(t * n_embd)?;
12265 e.rms_norm(
12266 &x,
12267 self.output_norm.float_data(),
12268 &mut hn,
12269 n_embd,
12270 t,
12271 self.cfg.rms_eps,
12272 )?;
12273 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12274 let n_vocab = self.output.out_features();
12275 let logits = if last_only {
12276 let hv = e.view(&hn, t * n_embd);
12277 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
12278 let mut hlast = e.zeros(n_embd)?;
12279 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
12280 let mut ld = e.matmul(&self.output, &hlast, 1)?;
12281 e.softcap(&mut ld, cap, n_vocab)?;
12282 self.gemma4_suppress(e, &mut ld, 1)?;
12283 e.dtoh(&ld)?
12284 } else {
12285 let mut ld = e.matmul(&self.output, &hn, t)?;
12286 e.softcap(&mut ld, cap, t * n_vocab)?;
12287 self.gemma4_suppress(e, &mut ld, t)?;
12288 e.dtoh(&ld)?
12289 };
12290 Ok(logits)
12291 }
12292
12293 pub(crate) fn gemma4_prime(
12298 &self,
12299 e: &Engine,
12300 tokens: &[u32],
12301 cache: &mut Cache,
12302 overlay: Option<&crate::vision::EmbedOverlay>,
12303 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12304 if cache.pos != 0 {
12309 return Err(
12310 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
12311 — prime the full prompt in one call or decode tokenwise"
12312 .into(),
12313 );
12314 }
12315 let n_embd = self.cfg.n_embd as usize;
12316 let eps = self.cfg.rms_eps;
12317 let t = tokens.len();
12318 let pos: Vec<i32> = (0..t as i32).collect();
12319 let pos_d = e.htod_i32(&pos)?;
12320 let mut x = self.embed(e, tokens)?;
12321 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12322 let island: Option<CudaSlice<i32>> = match overlay {
12329 Some(ov) => {
12330 let mut span_id = vec![-1i32; t];
12331 for (i, &(pos, row_off, n_rows)) in ov.spans.iter().enumerate() {
12332 if pos + n_rows > t {
12333 return Err(format!(
12334 "gemma4 overlay span {i} [{pos}, {}) exceeds the prompt ({t})",
12335 pos + n_rows
12336 )
12337 .into());
12338 }
12339 let view = ov.rows.slice(row_off * n_embd..(row_off + n_rows) * n_embd);
12340 e.copy_view_into(&mut x, pos * n_embd, &view, n_rows * n_embd)?;
12341 for s in span_id.iter_mut().skip(pos).take(n_rows) {
12342 *s = i as i32;
12343 }
12344 }
12345 if std::env::var("MEMRA_GV_FORCE_CAUSAL").as_deref() == Ok("1") {
12349 None
12350 } else {
12351 Some(e.htod_i32(&span_id)?)
12352 }
12353 }
12354 None => None,
12355 };
12356 for (il, layer) in self.layers.iter().enumerate() {
12357 let mut h = e.zeros(t * n_embd)?;
12358 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12359 let Mixer::Full(fa) = &layer.mixer else {
12360 panic!("gemma4 layer not full-attn")
12361 };
12362 let trace = il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1");
12363 if trace {
12364 let v = e.dtoh(&h)?;
12365 let nan = v.iter().filter(|x| x.is_nan()).count();
12366 eprintln!("[g4-prime-trace] L0 post-attn_norm: nan={nan}/{}", v.len());
12367 }
12368 let o =
12369 self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache), island.as_ref())?;
12370 if trace {
12371 let v = e.dtoh(&o)?;
12372 let nan = v.iter().filter(|x| x.is_nan()).count();
12373 eprintln!("[g4-prime-trace] L0 post-attn: nan={nan}/{}", v.len());
12374 }
12375 let mut cur = e.zeros(t * n_embd)?;
12376 e.rms_norm(
12377 &o,
12378 layer.post_attn_norm.float_data(),
12379 &mut cur,
12380 n_embd,
12381 t,
12382 eps,
12383 )?;
12384 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
12385 self.dflash_tap(e, cache, il, &x, t)?;
12386 if std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
12388 let h = e.dtoh(&x)?;
12389 let nan = h.iter().filter(|v| v.is_nan()).count();
12390 let amax = h.iter().fold(0f32, |a, v| a.max(v.abs()));
12391 eprintln!(
12392 "[g4-prime-trace] layer {il}: nan={nan}/{} amax={amax:.3}",
12393 h.len()
12394 );
12395 if nan > 0 {
12396 return Err(format!("g4-prime-trace: first NaN at layer {il}").into());
12397 }
12398 }
12399 }
12400 cache.pos += t;
12401 let hiddens = e.clone_dtod(&x)?;
12402 let xv = e.view(&x, t * n_embd);
12403 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
12404 let mut h_seed = e.zeros(n_embd)?;
12405 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
12406 let mut hn = e.uninit(n_embd)?;
12407 e.rms_norm(
12408 &h_seed,
12409 self.output_norm.float_data(),
12410 &mut hn,
12411 n_embd,
12412 1,
12413 eps,
12414 )?;
12415 let mut ld = e.matmul(&self.output, &hn, 1)?;
12416 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12417 e.softcap(&mut ld, cap, self.output.out_features())?;
12418 self.gemma4_suppress(e, &mut ld, 1)?;
12419 let logits = e.dtoh(&ld)?;
12420 Ok((logits, h_seed, hiddens))
12421 }
12422
12423 fn gemma4_decode_attn(
12428 &self,
12429 e: &Engine,
12430 fa: &crate::hybrid::FullAttnLayer,
12431 il: usize,
12432 hq: &CudaSlice<i8>,
12433 hdq: &CudaSlice<f32>,
12434 pos_d: &CudaSlice<i32>,
12435 cache: &mut Cache,
12436 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12437 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12438 let eps = self.cfg.rms_eps;
12439 let aux = self.gemma4_aux.as_ref().unwrap();
12440 let ones = aux.ones(e);
12441 #[cfg(debug_assertions)]
12442 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
12443 let (hq, hdq) = (hq, hdq);
12444 let h0 = e.zeros(0)?;
12445 let h = &h0;
12446 let (q0, k0, v0) = if swa {
12447 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12448 Some(t3) => t3,
12449 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12452 Some((q0, k0)) => {
12453 let v0 = e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?;
12454 (q0, k0, v0)
12455 }
12456 None => (
12457 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12458 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12459 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12460 ),
12461 },
12462 }
12463 } else {
12464 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
12465 Some(p) => p,
12466 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12467 Some(p) => p,
12468 None => (
12469 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12470 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12471 ),
12472 },
12473 };
12474 let v0 = e.clone_dtod(&k0)?;
12475 (q0, k0, v0)
12476 };
12477 let mut q = e.uninit(nh * hd)?;
12478 let mut k = e.uninit(nkv * hd)?;
12479 let mut v = e.uninit(nkv * hd)?;
12480 let ff = if swa {
12483 None
12484 } else {
12485 Some(
12486 aux.rope_freqs(e)
12487 .expect("gemma4 global rope needs rope_freqs.weight"),
12488 )
12489 };
12490 #[cfg(debug_assertions)]
12491 if let Some(ff) = ff {
12492 crate::debug_assert_tensor_stream_device(
12493 ff,
12494 &e.stream(),
12495 "gemma4_decode_attn.rope_freqs",
12496 );
12497 }
12498 let kvl = cache.kv[il].as_mut().unwrap();
12499 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12500 if crate::Engine::qkv_append_on() {
12501 e.rms_norm_qkv_rope_append(
12505 &q0,
12506 &k0,
12507 &v0,
12508 fa.q_norm.float_data(),
12509 fa.k_norm.float_data(),
12510 ones,
12511 &mut q,
12512 &mut k,
12513 &mut v,
12514 hd,
12515 self.gemma4_rope_dims(il),
12516 nh,
12517 nkv,
12518 pos_d,
12519 nh,
12520 nkv,
12521 base,
12522 1.0,
12523 ff,
12524 eps,
12525 &mut kvl.k,
12526 &mut kvl.v,
12527 kvl.len,
12528 kvl.k_tok_bytes,
12529 kvl.v_tok_bytes,
12530 kv_fp8,
12531 )?;
12532 } else {
12533 e.rms_norm_qkv_rope(
12534 &q0,
12535 &k0,
12536 &v0,
12537 fa.q_norm.float_data(),
12538 fa.k_norm.float_data(),
12539 ones,
12540 &mut q,
12541 &mut k,
12542 &mut v,
12543 hd,
12544 self.gemma4_rope_dims(il),
12545 nh,
12546 nkv,
12547 pos_d,
12548 nh,
12549 nkv,
12550 base,
12551 1.0,
12552 ff,
12553 eps,
12554 )?;
12555 e.append_kv_quantized(
12556 &k,
12557 &v,
12558 &mut kvl.k,
12559 &mut kvl.v,
12560 kvl.len,
12561 kvl.kv_dim_k,
12562 kvl.kv_dim_v,
12563 kvl.k_tok_bytes,
12564 kvl.v_tok_bytes,
12565 kv_fp8,
12566 )?;
12567 }
12568 kvl.len += 1;
12569 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12573 let mut attn = e.uninit(nh * hd)?;
12574 if !swa
12576 && hd == 512
12577 && kvl.len >= crate::fa512_min_tkv()
12578 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12579 {
12580 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12581 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12582 let base = kvl.len as i32;
12584 e.i32_set_k(&mut kvl.len_d, base)?;
12585 e.fa_decode_rows(
12586 &q,
12587 &kp,
12588 &vp,
12589 &mut attn,
12590 hd,
12591 nh,
12592 nkv,
12593 kvl.len - 1,
12594 1,
12595 scale,
12596 kvl.k_tok_bytes,
12597 kvl.v_tok_bytes,
12598 Some((&kvl.len_d, -1)),
12599 false,
12600 false,
12601 None,
12602 )?;
12603 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12604 }
12605 if swa
12607 && kvl.len > win
12608 && hd == 256
12609 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12610 {
12611 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12612 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12613 let base = kvl.len as i32;
12614 e.i32_set_k(&mut kvl.len_d, base)?;
12615 e.fa_decode_rows_w(
12616 &q,
12617 &kp,
12618 &vp,
12619 &mut attn,
12620 hd,
12621 nh,
12622 nkv,
12623 &kvl.len_d,
12624 -1,
12625 1,
12626 scale,
12627 win,
12628 kvl.k_tok_bytes,
12629 kvl.v_tok_bytes,
12630 None,
12631 )?;
12632 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12633 }
12634 let (off_tok, t_kv) = if swa && kvl.len > win {
12635 (kvl.len - win, win)
12636 } else {
12637 (0, kvl.len)
12638 };
12639 let k_view = e.view_u8_range(
12640 &kvl.k,
12641 off_tok * kvl.k_tok_bytes,
12642 (off_tok + t_kv) * kvl.k_tok_bytes,
12643 );
12644 let v_view = e.view_u8_range(
12645 &kvl.v,
12646 off_tok * kvl.v_tok_bytes,
12647 (off_tok + t_kv) * kvl.v_tok_bytes,
12648 );
12649 e.fa_decode_kvmod(
12650 &q,
12651 &k_view,
12652 &v_view,
12653 &mut attn,
12654 hd,
12655 nh,
12656 nkv,
12657 t_kv,
12658 scale,
12659 kvl.k_tok_bytes,
12660 kvl.v_tok_bytes,
12661 swa && crate::Engine::wkv_on(),
12662 )?;
12663 Ok(e.matmul(&fa.wo, &attn, 1)?)
12664 }
12665
12666 #[allow(clippy::too_many_arguments)]
12673 pub fn gemma4_decode_step_dc(
12674 &self,
12675 e: &Engine,
12676 token_d: &CudaSlice<u32>,
12677 pos_d: &mut CudaSlice<i32>,
12678 embd_gpu: &CudaSlice<u8>,
12679 embd_qt: i32,
12680 embd_rb: usize,
12681 cache: &mut Cache,
12682 n_vocab: usize,
12683 cap_bucket_max: Option<(usize, usize)>,
12684 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
12685 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
12686 self.gemma4_decode_step_dc_into(
12687 e,
12688 token_d,
12689 pos_d,
12690 embd_gpu,
12691 embd_qt,
12692 embd_rb,
12693 cache,
12694 n_vocab,
12695 cap_bucket_max,
12696 &mut tok_out,
12697 )?;
12698 Ok(tok_out)
12699 }
12700
12701 #[allow(clippy::too_many_arguments)]
12704 pub fn gemma4_decode_step_dc_into(
12705 &self,
12706 e: &Engine,
12707 token_d: &CudaSlice<u32>,
12708 pos_d: &mut CudaSlice<i32>,
12709 embd_gpu: &CudaSlice<u8>,
12710 embd_qt: i32,
12711 embd_rb: usize,
12712 cache: &mut Cache,
12713 n_vocab: usize,
12714 cap_bucket_max: Option<(usize, usize)>,
12715 tok_out: &mut CudaSlice<u32>,
12716 ) -> Result<(), Box<dyn std::error::Error>> {
12717 let n_embd = self.cfg.n_embd as usize;
12718 let eps = self.cfg.rms_eps;
12719 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
12720 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12721 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12722 let n_layers = self.layers.len();
12723 for (il, layer) in self.layers.iter().enumerate() {
12724 let (hq, hdq) = match h_carry.take() {
12725 Some(p) => p,
12726 None => {
12727 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
12728 }
12729 };
12730 let Mixer::Full(fa) = &layer.mixer else {
12731 panic!("gemma4 layer {il} not full-attn")
12732 };
12733 let o =
12734 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
12735 let next_norm = if il + 1 < n_layers {
12736 Some(self.layers[il + 1].attn_norm.float_data())
12737 } else {
12738 None
12739 };
12740 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
12741 x = xn;
12742 h_carry = hn;
12743 }
12744 let mut hn = e.uninit(n_embd)?;
12745 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12746 let mut logits = e.matmul(&self.output, &hn, 1)?;
12747 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
12749 e.inc_seqlen(pos_d)?;
12750 if cap_bucket_max.is_none() {
12751 cache.pos += 1;
12752 }
12753 Ok(())
12754 }
12755
12756 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
12763 let n_embd = self.cfg.n_embd as usize;
12764 let n_vocab = self.output.out_features();
12765 let n_layers = self.layers.len();
12766 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
12767 for il in 0..n_layers {
12768 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
12769 qmax = qmax.max(nh * hd);
12770 kvmax = kvmax.max(nkv * hd);
12771 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
12772 ffmax = ffmax.max(ffn_gate.out_features());
12773 }
12774 }
12775 Ok(G4DcSlots {
12776 x: e.uninit(n_embd)?,
12777 xn: e.uninit(n_embd)?,
12778 cur: e.uninit(n_embd)?,
12779 hq: e.alloc_i8_uninit(n_embd)?,
12780 hd_: e.uninit(n_embd / 32)?,
12781 q0: e.uninit(qmax)?,
12782 k0: e.uninit(kvmax)?,
12783 v0: e.uninit(kvmax)?,
12784 q: e.uninit(qmax)?,
12785 k: e.uninit(kvmax)?,
12786 v: e.uninit(kvmax)?,
12787 attn: e.uninit(qmax)?,
12788 o: e.uninit(n_embd)?,
12789 attn_out: e.uninit(n_embd)?,
12790 zsh: e.uninit(n_embd)?,
12791 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
12794 zd: e.uninit(n_embd.max(qmax) / 32)?,
12795 gate: e.uninit(ffmax)?,
12796 up: e.uninit(ffmax)?,
12797 act: e.uninit(ffmax)?,
12798 actq: e.alloc_i8_uninit(ffmax)?,
12799 actd: e.uninit(ffmax / 32)?,
12800 f0: e.uninit(n_embd)?,
12801 sn: e.uninit(n_embd)?,
12802 hn: e.uninit(n_embd)?,
12803 logits: e.uninit(n_vocab)?,
12804 })
12805 }
12806
12807 fn g4_matvec_m1_into(
12810 &self,
12811 e: &Engine,
12812 w: &crate::model::GpuTensor,
12813 aq: &CudaSlice<i8>,
12814 ad: &CudaSlice<f32>,
12815 y: &mut CudaSlice<f32>,
12816 ) -> Result<(), Box<dyn std::error::Error>> {
12817 use crate::model::GpuTensor;
12818 let (bytes, qtype, row_bytes, scale, rp) = match w {
12819 GpuTensor::Quant {
12820 bytes,
12821 qtype,
12822 row_bytes,
12823 scale,
12824 rp,
12825 ..
12826 } => (bytes, *qtype, *row_bytes, *scale, *rp),
12827 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
12828 };
12829 let (mbytes, mrp) = match w {
12830 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12831 _ => (bytes, rp),
12832 };
12833 e.qmatvec_mmvq_into(
12834 mbytes,
12835 aq,
12836 ad,
12837 1,
12838 w.in_features(),
12839 w.out_features(),
12840 qtype,
12841 row_bytes,
12842 scale,
12843 mrp,
12844 y,
12845 )
12846 }
12847
12848 #[allow(clippy::too_many_arguments)]
12852 pub fn gemma4_decode_step_dc_slotted(
12853 &self,
12854 e: &Engine,
12855 token_d: &CudaSlice<u32>,
12856 pos_d: &mut CudaSlice<i32>,
12857 embd_gpu: &CudaSlice<u8>,
12858 embd_qt: i32,
12859 embd_rb: usize,
12860 cache: &mut Cache,
12861 n_vocab: usize,
12862 cap_bucket_max: Option<(usize, usize)>,
12863 sl: &mut G4DcSlots,
12864 tok_out: &mut CudaSlice<u32>,
12865 ring: Option<(&mut CudaSlice<u32>, usize)>,
12866 ) -> Result<(), Box<dyn std::error::Error>> {
12867 let n_embd = self.cfg.n_embd as usize;
12868 let eps = self.cfg.rms_eps;
12869 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
12870 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
12871 let n_layers = self.layers.len();
12872 let mut has_carry = false;
12873 for il in 0..n_layers {
12874 if !has_carry {
12875 e.rms_norm_q8_1_into(
12876 &sl.x,
12877 self.layers[il].attn_norm.float_data(),
12878 n_embd,
12879 1,
12880 eps,
12881 &mut sl.hq,
12882 &mut sl.hd_,
12883 )?;
12884 }
12885 has_carry = true;
12886 let layer = &self.layers[il];
12887 let Mixer::Full(fa) = &layer.mixer else {
12888 panic!("gemma4 layer {il} not full-attn")
12889 };
12890 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
12891 if !Engine::g4_pnfold_on() {
12894 e.rms_norm(
12895 &sl.o,
12896 layer.post_attn_norm.float_data(),
12897 &mut sl.cur,
12898 n_embd,
12899 1,
12900 eps,
12901 )?;
12902 }
12903 let next_norm = if il + 1 < n_layers {
12904 Some(self.layers[il + 1].attn_norm.float_data())
12905 } else {
12906 None
12907 };
12908 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
12909 std::mem::swap(&mut sl.x, &mut sl.xn);
12910 }
12911 e.rms_norm(
12912 &sl.x,
12913 self.output_norm.float_data(),
12914 &mut sl.hn,
12915 n_embd,
12916 1,
12917 eps,
12918 )?;
12919 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
12920 {
12922 let (zq, zd) = (&sl.zq, &sl.zd);
12923 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
12924 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
12925 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
12926 }
12927 self.gemma4_suppress(e, &mut sl.logits, 1)?;
12928 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
12929 if let Some((ring, base)) = ring {
12930 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
12934 }
12935 e.inc_seqlen(pos_d)?;
12936 if cap_bucket_max.is_none() {
12937 cache.pos += 1;
12938 }
12939 Ok(())
12940 }
12941
12942 #[allow(clippy::too_many_arguments)]
12944 fn gemma4_decode_attn_dc_slotted(
12945 &self,
12946 e: &Engine,
12947 fa: &crate::hybrid::FullAttnLayer,
12948 il: usize,
12949 pos_d: &CudaSlice<i32>,
12950 cache: &mut Cache,
12951 cap_bucket_max: Option<(usize, usize)>,
12952 sl: &mut G4DcSlots,
12953 ) -> Result<(), Box<dyn std::error::Error>> {
12954 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12955 let eps = self.cfg.rms_eps;
12956 let aux = self.gemma4_aux.as_ref().unwrap();
12957 let ones = aux.ones(e);
12958 #[cfg(debug_assertions)]
12959 crate::debug_assert_tensor_stream_device(
12960 ones,
12961 &e.stream(),
12962 "gemma4_decode_attn_dc_slotted.ones",
12963 );
12964 {
12965 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
12966 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
12967 if swa {
12968 if !e.matmul_q4_fused3_into(
12969 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
12970 )? {
12971 if e.matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12975 {
12976 self.g4_matvec_m1_into(e, &fa.wv, hq, hdq, &mut sl.v0)?;
12977 } else {
12978 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
12979 }
12980 }
12981 } else {
12982 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12983 && !e
12984 .matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12985 {
12986 return Err("slotted step: fused2 unavailable".into());
12987 }
12988 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
12989 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
12990 }
12991 }
12992 let ff = if swa {
12995 None
12996 } else {
12997 Some(
12998 aux.rope_freqs(e)
12999 .expect("gemma4 global rope needs rope_freqs.weight"),
13000 )
13001 };
13002 #[cfg(debug_assertions)]
13003 if let Some(ff) = ff {
13004 crate::debug_assert_tensor_stream_device(
13005 ff,
13006 &e.stream(),
13007 "gemma4_decode_attn_dc_slotted.rope_freqs",
13008 );
13009 }
13010 let kvl = cache.kv[il].as_mut().unwrap();
13011 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13012 if crate::Engine::qkv_append_on() {
13013 e.rms_norm_qkv_rope_append_dc(
13015 &sl.q0,
13016 &sl.k0,
13017 &sl.v0,
13018 fa.q_norm.float_data(),
13019 fa.k_norm.float_data(),
13020 ones,
13021 &mut sl.q,
13022 &mut sl.k,
13023 &mut sl.v,
13024 hd,
13025 self.gemma4_rope_dims(il),
13026 nh,
13027 nkv,
13028 pos_d,
13029 nh,
13030 nkv,
13031 base,
13032 1.0,
13033 ff,
13034 eps,
13035 &mut kvl.k,
13036 &mut kvl.v,
13037 &kvl.len_d,
13038 kvl.k_tok_bytes,
13039 kvl.v_tok_bytes,
13040 kv_fp8,
13041 )?;
13042 } else {
13043 e.rms_norm_qkv_rope(
13044 &sl.q0,
13045 &sl.k0,
13046 &sl.v0,
13047 fa.q_norm.float_data(),
13048 fa.k_norm.float_data(),
13049 ones,
13050 &mut sl.q,
13051 &mut sl.k,
13052 &mut sl.v,
13053 hd,
13054 self.gemma4_rope_dims(il),
13055 nh,
13056 nkv,
13057 pos_d,
13058 nh,
13059 nkv,
13060 base,
13061 1.0,
13062 ff,
13063 eps,
13064 )?;
13065 e.append_kv_quantized_dc(
13066 &sl.k,
13067 &sl.v,
13068 &mut kvl.k,
13069 &mut kvl.v,
13070 &kvl.len_d,
13071 kvl.kv_dim_k,
13072 kvl.kv_dim_v,
13073 kvl.k_tok_bytes,
13074 kvl.v_tok_bytes,
13075 kv_fp8,
13076 )?;
13077 }
13078 e.inc_seqlen(&mut kvl.len_d)?;
13079 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
13080 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13081 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13082 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13083 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13084 let mut fa_q8 = false;
13088 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13089 e.fa_decode_rows(
13090 &sl.q,
13091 &k_view,
13092 &v_view,
13093 &mut sl.attn,
13094 hd,
13095 nh,
13096 nkv,
13097 b_glob - 1,
13098 1,
13099 scale,
13100 kvl.k_tok_bytes,
13101 kvl.v_tok_bytes,
13102 Some((&kvl.len_d, -1)),
13103 false,
13104 false,
13105 Some((&mut sl.zq, &mut sl.zd)),
13106 )?;
13107 fa_q8 = true;
13108 } else if swa && b_swa > win && hd == 256 && rows_on {
13109 e.fa_decode_rows_w(
13110 &sl.q,
13111 &k_view,
13112 &v_view,
13113 &mut sl.attn,
13114 hd,
13115 nh,
13116 nkv,
13117 &kvl.len_d,
13118 -1,
13119 1,
13120 scale,
13121 win,
13122 kvl.k_tok_bytes,
13123 kvl.v_tok_bytes,
13124 Some((&mut sl.zq, &mut sl.zd)),
13125 )?;
13126 fa_q8 = true;
13127 } else {
13128 let b = if swa { b_swa } else { b_glob };
13129 e.fa_decode_dc(
13130 &sl.q,
13131 &k_view,
13132 &v_view,
13133 &mut sl.attn,
13134 hd,
13135 nh,
13136 nkv,
13137 &kvl.len_d,
13138 b,
13139 scale,
13140 kvl.k_tok_bytes,
13141 kvl.v_tok_bytes,
13142 swa && crate::Engine::wkv_on(),
13143 )?;
13144 }
13145 if !fa_q8 {
13146 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
13147 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
13148 }
13149 {
13150 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13151 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13152 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
13153 }
13154 Ok(())
13155 }
13156
13157 fn gemma4_layer_tail_slotted(
13160 &self,
13161 e: &Engine,
13162 layer: &crate::hybrid::HybridLayer,
13163 next_norm: Option<&CudaSlice<f32>>,
13164 sl: &mut G4DcSlots,
13165 ) -> Result<(), Box<dyn std::error::Error>> {
13166 let n_embd = self.cfg.n_embd as usize;
13167 let eps = self.cfg.rms_eps;
13168 let bits = layer.gemma4.as_ref().unwrap();
13169 let crate::hybrid::Ffn::Dense {
13170 ffn_gate,
13171 ffn_up,
13172 ffn_down,
13173 } = &layer.ffn
13174 else {
13175 return Err("slotted tail: dense ffn only".into());
13176 };
13177 let pnfold = Engine::g4_pnfold_on();
13178 if pnfold {
13179 let or = unsafe { &*(&sl.o as *const CudaSlice<f32>) };
13182 let xr = unsafe { &*(&sl.x as *const CudaSlice<f32>) };
13183 e.rms_pre_add_rms_norm_q8z_into(
13184 or,
13185 layer.post_attn_norm.float_data(),
13186 xr,
13187 bits.ffn_norm.float_data(),
13188 &mut sl.attn_out,
13189 &mut sl.zsh,
13190 n_embd,
13191 1,
13192 eps,
13193 &mut sl.zq,
13194 &mut sl.zd,
13195 )?;
13196 } else {
13197 e.add_rms_norm(
13198 &sl.cur,
13199 &sl.x,
13200 bits.ffn_norm.float_data(),
13201 &mut sl.attn_out,
13202 &mut sl.zsh,
13203 n_embd,
13204 1,
13205 eps,
13206 )?;
13207 }
13208 let n_ff = ffn_gate.out_features();
13209 if !pnfold {
13210 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
13211 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13212 }
13213 {
13214 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13215 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13216 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)?
13217 && !e.matmul_nvfp4_fused2_into(
13218 ffn_gate,
13219 ffn_up,
13220 zq,
13221 zd,
13222 &mut sl.gate,
13223 &mut sl.up,
13224 )?
13225 {
13226 return Err("slotted tail: ffn fused2 unavailable".into());
13227 }
13228 }
13229 debug_assert!(e.uses_q8_1_fast(ffn_down));
13230 {
13231 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
13232 let upv = e.view(upr, n_ff);
13233 let up_all = upv.slice(0..n_ff);
13234 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
13235 e.gelu_tanh_mul_q8_1_into(
13236 gr,
13237 &up_all,
13238 &mut sl.act,
13239 n_ff,
13240 1,
13241 &mut sl.actq,
13242 &mut sl.actd,
13243 )?;
13244 }
13245 {
13246 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
13247 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
13248 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
13249 }
13250 if pnfold {
13251 if let Some(w) = next_norm {
13254 let f0r = unsafe { &*(&sl.f0 as *const CudaSlice<f32>) };
13255 let aor = unsafe { &*(&sl.attn_out as *const CudaSlice<f32>) };
13256 e.rms_pre_add_scale_rms_norm_q8_1_into(
13257 f0r,
13258 bits.post_ffw_norm.float_data(),
13259 aor,
13260 bits.layer_scale,
13261 w,
13262 &mut sl.xn,
13263 n_embd,
13264 1,
13265 eps,
13266 &mut sl.hq,
13267 &mut sl.hd_,
13268 )?;
13269 return Ok(());
13270 }
13271 }
13272 e.rms_norm(
13273 &sl.f0,
13274 bits.post_ffw_norm.float_data(),
13275 &mut sl.sn,
13276 n_embd,
13277 1,
13278 eps,
13279 )?;
13280 match next_norm {
13281 Some(w) => {
13282 e.add_scale_rms_norm_q8_1_into(
13283 &sl.sn,
13284 &sl.attn_out,
13285 bits.layer_scale,
13286 w,
13287 &mut sl.xn,
13288 n_embd,
13289 1,
13290 eps,
13291 &mut sl.hq,
13292 &mut sl.hd_,
13293 )?;
13294 }
13295 None => {
13296 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
13297 }
13298 }
13299 Ok(())
13300 }
13301
13302 #[allow(clippy::too_many_arguments)]
13304 fn gemma4_decode_attn_dc(
13305 &self,
13306 e: &Engine,
13307 fa: &crate::hybrid::FullAttnLayer,
13308 il: usize,
13309 hq: &CudaSlice<i8>,
13310 hdq: &CudaSlice<f32>,
13311 pos_d: &CudaSlice<i32>,
13312 cache: &mut Cache,
13313 cap_bucket_max: Option<(usize, usize)>,
13314 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13315 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13316 let eps = self.cfg.rms_eps;
13317 let aux = self.gemma4_aux.as_ref().unwrap();
13318 let ones = aux.ones(e);
13319 #[cfg(debug_assertions)]
13320 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
13321 let (q0, k0, v0) = if swa {
13322 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
13323 Some(t3) => t3,
13324 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13326 Some((q0, k0)) => {
13327 let h0 = e.zeros(0)?;
13328 let v0 = e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?;
13329 (q0, k0, v0)
13330 }
13331 None => {
13332 let h0 = e.zeros(0)?;
13333 (
13334 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13335 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13336 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
13337 )
13338 }
13339 },
13340 }
13341 } else {
13342 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
13343 Some(p) => p,
13344 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13345 Some(p) => p,
13346 None => {
13347 let h0 = e.zeros(0)?;
13348 (
13349 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13350 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13351 )
13352 }
13353 },
13354 };
13355 let v0 = e.clone_dtod(&k0)?;
13356 (q0, k0, v0)
13357 };
13358 let mut q = e.uninit(nh * hd)?;
13359 let mut k = e.uninit(nkv * hd)?;
13360 let mut v = e.uninit(nkv * hd)?;
13361 let ff = if swa {
13363 None
13364 } else {
13365 Some(
13366 aux.rope_freqs(e)
13367 .expect("gemma4 global rope needs rope_freqs.weight"),
13368 )
13369 };
13370 #[cfg(debug_assertions)]
13371 if let Some(ff) = ff {
13372 crate::debug_assert_tensor_stream_device(
13373 ff,
13374 &e.stream(),
13375 "gemma4_decode_attn_dc.rope_freqs",
13376 );
13377 }
13378 let kvl = cache.kv[il].as_mut().unwrap();
13379 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13380 if crate::Engine::qkv_append_on() {
13381 e.rms_norm_qkv_rope_append_dc(
13383 &q0,
13384 &k0,
13385 &v0,
13386 fa.q_norm.float_data(),
13387 fa.k_norm.float_data(),
13388 ones,
13389 &mut q,
13390 &mut k,
13391 &mut v,
13392 hd,
13393 self.gemma4_rope_dims(il),
13394 nh,
13395 nkv,
13396 pos_d,
13397 nh,
13398 nkv,
13399 base,
13400 1.0,
13401 ff,
13402 eps,
13403 &mut kvl.k,
13404 &mut kvl.v,
13405 &kvl.len_d,
13406 kvl.k_tok_bytes,
13407 kvl.v_tok_bytes,
13408 kv_fp8,
13409 )?;
13410 } else {
13411 e.rms_norm_qkv_rope(
13412 &q0,
13413 &k0,
13414 &v0,
13415 fa.q_norm.float_data(),
13416 fa.k_norm.float_data(),
13417 ones,
13418 &mut q,
13419 &mut k,
13420 &mut v,
13421 hd,
13422 self.gemma4_rope_dims(il),
13423 nh,
13424 nkv,
13425 pos_d,
13426 nh,
13427 nkv,
13428 base,
13429 1.0,
13430 ff,
13431 eps,
13432 )?;
13433 e.append_kv_quantized_dc(
13434 &k,
13435 &v,
13436 &mut kvl.k,
13437 &mut kvl.v,
13438 &kvl.len_d,
13439 kvl.kv_dim_k,
13440 kvl.kv_dim_v,
13441 kvl.k_tok_bytes,
13442 kvl.v_tok_bytes,
13443 kv_fp8,
13444 )?;
13445 }
13446 e.inc_seqlen(&mut kvl.len_d)?;
13447 let mut attn = e.uninit(nh * hd)?;
13448 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13451 match cap_bucket_max {
13456 None => {
13457 kvl.len += 1;
13461 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13462 if !swa
13463 && hd == 512
13464 && kvl.len >= crate::fa512_min_tkv()
13465 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13466 {
13467 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13470 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13471 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13472 e.fa_decode_rows(
13473 &q,
13474 &kp,
13475 &vp,
13476 &mut attn,
13477 hd,
13478 nh,
13479 nkv,
13480 kvl.len - 1,
13481 1,
13482 scale,
13483 kvl.k_tok_bytes,
13484 kvl.v_tok_bytes,
13485 Some((&kvl.len_d, -1)),
13486 false,
13487 false,
13488 Some((&mut aq8, &mut ad8)),
13489 )?;
13490 fa_q8 = Some((aq8, ad8));
13491 } else if swa
13492 && kvl.len > win
13493 && hd == 256
13494 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13495 {
13496 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13498 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13499 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13500 e.fa_decode_rows_w(
13501 &q,
13502 &kp,
13503 &vp,
13504 &mut attn,
13505 hd,
13506 nh,
13507 nkv,
13508 &kvl.len_d,
13509 -1,
13510 1,
13511 scale,
13512 win,
13513 kvl.k_tok_bytes,
13514 kvl.v_tok_bytes,
13515 Some((&mut aq8, &mut ad8)),
13516 )?;
13517 fa_q8 = Some((aq8, ad8));
13518 } else {
13519 let (off_tok, t_kv) = if swa && kvl.len > win {
13520 (kvl.len - win, win)
13521 } else {
13522 (0, kvl.len)
13523 };
13524 let k_view = e.view_u8_range(
13525 &kvl.k,
13526 off_tok * kvl.k_tok_bytes,
13527 (off_tok + t_kv) * kvl.k_tok_bytes,
13528 );
13529 let v_view = e.view_u8_range(
13530 &kvl.v,
13531 off_tok * kvl.v_tok_bytes,
13532 (off_tok + t_kv) * kvl.v_tok_bytes,
13533 );
13534 e.fa_decode_kvmod(
13535 &q,
13536 &k_view,
13537 &v_view,
13538 &mut attn,
13539 hd,
13540 nh,
13541 nkv,
13542 t_kv,
13543 scale,
13544 kvl.k_tok_bytes,
13545 kvl.v_tok_bytes,
13546 swa && crate::Engine::wkv_on(),
13547 )?;
13548 }
13549 }
13550 Some((b_swa, b_glob)) => {
13551 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13557 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13558 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13559 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13560 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13561 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13562 e.fa_decode_rows(
13563 &q,
13564 &k_view,
13565 &v_view,
13566 &mut attn,
13567 hd,
13568 nh,
13569 nkv,
13570 b_glob - 1,
13571 1,
13572 scale,
13573 kvl.k_tok_bytes,
13574 kvl.v_tok_bytes,
13575 Some((&kvl.len_d, -1)),
13576 false,
13577 false,
13578 Some((&mut aq8, &mut ad8)),
13579 )?;
13580 fa_q8 = Some((aq8, ad8));
13581 } else if swa && b_swa > win && hd == 256 && rows_on {
13582 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13583 e.fa_decode_rows_w(
13584 &q,
13585 &k_view,
13586 &v_view,
13587 &mut attn,
13588 hd,
13589 nh,
13590 nkv,
13591 &kvl.len_d,
13592 -1,
13593 1,
13594 scale,
13595 win,
13596 kvl.k_tok_bytes,
13597 kvl.v_tok_bytes,
13598 Some((&mut aq8, &mut ad8)),
13599 )?;
13600 fa_q8 = Some((aq8, ad8));
13601 } else {
13602 let b = if swa { b_swa } else { b_glob };
13603 e.fa_decode_dc(
13604 &q,
13605 &k_view,
13606 &v_view,
13607 &mut attn,
13608 hd,
13609 nh,
13610 nkv,
13611 &kvl.len_d,
13612 b,
13613 scale,
13614 kvl.k_tok_bytes,
13615 kvl.v_tok_bytes,
13616 swa && crate::Engine::wkv_on(),
13617 )?;
13618 }
13619 }
13620 }
13621 if let Some((aq8, ad8)) = fa_q8 {
13624 let mut y = e.uninit(fa.wo.out_features())?;
13625 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
13626 return Ok(y);
13627 }
13628 Ok(e.matmul(&fa.wo, &attn, 1)?)
13629 }
13630
13631 pub fn gemma4_generate_graph(
13636 &self,
13637 e: &Engine,
13638 prompt_pos: usize,
13639 first_token: u32,
13640 cache: &mut Cache,
13641 max_new: usize,
13642 eos: &[u32],
13643 mut on_token: impl FnMut(u32) -> bool,
13644 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
13645 if self.is_gemma4_e4b() {
13646 return Err(
13647 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
13648 .into(),
13649 );
13650 }
13651 use crate::decode::StopReason;
13652 let n_vocab = self.output.out_features();
13653 let n_embd = self.cfg.n_embd as usize;
13654 let embd_gpu = self
13655 .embd_gpu
13656 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13657 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13658 for kvl in cache.kv.iter_mut().flatten() {
13659 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
13660 }
13661 let mut token_d = e.stream().clone_htod(&[first_token])?;
13662 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
13663 let g4 = self.cfg.gemma4.as_ref().unwrap();
13664 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
13665 let nkv_s = g4
13667 .head_count_kv
13668 .iter()
13669 .zip(g4.swa_pattern.iter())
13670 .find(|p| *p.1)
13671 .map(|p| *p.0 as usize)
13672 .unwrap_or(8);
13673 let nkv_g = g4
13674 .head_count_kv
13675 .iter()
13676 .zip(g4.swa_pattern.iter())
13677 .find(|p| !*p.1)
13678 .map(|p| *p.0 as usize)
13679 .unwrap_or(2);
13680 let mut graphs: std::collections::HashMap<
13681 ((bool, usize), (bool, usize), bool, bool),
13682 (
13683 cudarc::driver::CudaGraph,
13684 Vec<Box<dyn std::any::Any + Send>>,
13685 ),
13686 > = Default::default();
13687 let mut slots = self.g4_dc_slots(e)?;
13690 const RING: usize = 64;
13693 const DRAIN: usize = 1;
13699 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
13700 let ring_base = prompt_pos;
13701 let mut out = Vec::with_capacity(max_new);
13702 let mut reason = StopReason::MaxNew;
13703 let mut next = first_token;
13704 let mut captures = 0usize;
13705 for _ in 0..max_new {
13706 out.push(next);
13707 if eos.contains(&next) {
13708 reason = StopReason::Eos;
13709 break;
13710 }
13711 if !on_token(next) {
13712 reason = StopReason::Callback;
13713 break;
13714 }
13715 let t_kv = cache.pos + 1;
13716 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13724 let f512 = crate::fa512_min_tkv();
13725 let key_s = if t_kv > win {
13726 (true, usize::MAX)
13727 } else {
13728 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
13729 };
13730 let (key_g, rung_end) = if t_kv >= f512 {
13731 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
13734 ((true, end), end)
13735 } else {
13736 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
13737 };
13738 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
13739 if !graphs.contains_key(&key) {
13740 let bucket_max = (t_kv, rung_end);
13741 let snap = cache.snapshot(e)?;
13743 let pos_save = e.dtoh_i32_one(&pos_d)?;
13744 let len_save: Vec<Option<i32>> = cache
13745 .kv
13746 .iter()
13747 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
13748 .collect();
13749 let tok_save = e.dtoh_u32_one(&token_d)?;
13750 let graph = {
13755 let tok_ref = &mut token_d;
13756 let pos_ref = &mut pos_d;
13757 let cache_ref = &mut *cache;
13758 let slots_ref = &mut slots;
13759 let ring_ref = &mut ring;
13760 e.capture_graph_retained_flags(
13761 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
13762 |e| {
13763 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
13765 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
13766 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
13767 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
13768 cache_ref, n_vocab, Some(bucket_max),
13769 sl, tok_ref, Some((rg, ring_base)))
13770 })?
13771 };
13772 cache.rollback(e, &snap, 0)?;
13773 e.set_i32_one(&mut pos_d, pos_save)?;
13774 for (il, ls) in len_save.iter().enumerate() {
13775 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
13776 e.set_i32_one(&mut kvl.len_d, *v)?;
13777 }
13778 }
13779 e.set_u32_one(&mut token_d, tok_save)?;
13780 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
13781 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
13782 eprintln!("[graph-census] {c:?}");
13783 }
13784 }
13785 graphs.insert(key, graph);
13786 captures += 1;
13787 }
13788 let mut chunk = 1usize;
13793 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
13794 .ok()
13795 .and_then(|v| v.parse().ok())
13796 .unwrap_or(DRAIN);
13797 while chunk < drain_cap && out.len() + chunk < max_new {
13798 let t_next = cache.pos + 1 + chunk;
13799 let key_s2 = if t_next > win {
13800 (true, usize::MAX)
13801 } else {
13802 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
13803 };
13804 let key_g2 = if t_next >= f512 {
13805 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
13806 } else {
13807 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
13808 };
13809 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
13810 break;
13811 }
13812 chunk += 1;
13813 }
13814 let g = &graphs.get(&key).unwrap().0;
13815 for _ in 0..chunk {
13816 g.launch()?;
13817 }
13818 e.stream().synchronize()?;
13819 let ringh = e.dtoh_u32(&ring)?;
13820 for j in 0..chunk {
13821 let pos_j = cache.pos + j;
13822 let tok_j = ringh[(pos_j - ring_base) % RING];
13823 cache.pos += 0; if j + 1 == chunk {
13825 next = tok_j;
13826 } else {
13827 out.push(tok_j);
13828 if eos.contains(&tok_j) || !on_token(tok_j) {
13829 reason = if eos.contains(&tok_j) {
13830 StopReason::Eos
13831 } else {
13832 StopReason::Callback
13833 };
13834 let keep = cache.pos + j + 1;
13836 e.set_i32_one(&mut pos_d, keep as i32)?;
13837 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13838 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
13839 kvl.len = keep;
13840 }
13841 cache.pos = keep;
13842 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13843 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13844 }
13845 return Ok((out, reason));
13846 }
13847 }
13848 }
13849 cache.pos += chunk;
13850 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13851 kvl.len += chunk;
13852 }
13853 }
13854 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13855 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13856 }
13857 Ok((out, reason))
13858 }
13859
13860 pub(crate) fn gemma4_decode_step_t(
13866 &self,
13867 e: &Engine,
13868 tokens: &[u32],
13869 pos0: usize,
13870 cache: &mut Cache,
13871 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13872 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
13873 }
13874
13875 pub(crate) fn gemma4_decode_step_t_am(
13879 &self,
13880 e: &Engine,
13881 tokens: &[u32],
13882 pos0: usize,
13883 cache: &mut Cache,
13884 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13885 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13886 let t = tokens.len();
13887 let n_vocab = self.output.out_features();
13888 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
13889 for i in 0..t {
13890 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
13891 }
13892 Ok((e.dtoh_u32(&toks)?, hn))
13893 }
13894
13895 pub(crate) fn gemma4_decode_step_t_am_dev(
13898 &self,
13899 e: &Engine,
13900 tok_d: &CudaSlice<u32>,
13901 t: usize,
13902 pos0: usize,
13903 cache: &mut Cache,
13904 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13905 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
13906 let n_vocab = self.output.out_features();
13907 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13908 for i in 0..t {
13909 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13910 }
13911 Ok((vam, hn))
13912 }
13913
13914 pub(crate) fn gemma4_decode_step_t_h(
13917 &self,
13918 e: &Engine,
13919 tokens: &[u32],
13920 pos0: usize,
13921 cache: &mut Cache,
13922 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13923 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13924 let t = tokens.len();
13925 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
13926 e.softcap(&mut ld, cap, t * self.output.out_features())?;
13927 Ok((e.dtoh(&ld)?, hn))
13928 }
13929
13930 pub(crate) fn verify_stream_scratch(
13933 &self,
13934 e: &Engine,
13935 cap: usize,
13936 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
13937 Ok(VerifyStreamScratch {
13938 pos_d: e.htod_i32(&vec![0i32; cap])?,
13939 row_ctrs: (0..cap)
13940 .map(|_| e.htod_i32(&[0]))
13941 .collect::<Result<_, _>>()?,
13942 })
13943 }
13944
13945 pub(crate) fn gemma4_verify_t_am_stream(
13953 &self,
13954 e: &Engine,
13955 tok_d: &CudaSlice<u32>,
13956 t: usize,
13957 ctr: &CudaSlice<i32>,
13958 hint: usize,
13959 cache: &mut Cache,
13960 scr: &mut VerifyStreamScratch,
13961 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13962 let n_embd = self.cfg.n_embd as usize;
13963 let eps = self.cfg.rms_eps;
13964 assert!(t <= scr.row_ctrs.len() && t <= 64);
13965 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
13966 for i in 0..t {
13967 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
13968 }
13969 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
13970 let embd_gpu = self
13971 .embd_gpu
13972 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13973 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13974 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
13975 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13976 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13977 let n_layers = self.layers.len();
13978 for (il, layer) in self.layers.iter().enumerate() {
13979 let (hq, hdq) = match h_carry.take() {
13980 Some(p) => p,
13981 None => {
13982 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
13983 }
13984 };
13985 let Mixer::Full(fa) = &layer.mixer else {
13986 panic!("gemma4 layer {il} not full-attn")
13987 };
13988 let o = self
13989 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
13990 let next_norm = if il + 1 < n_layers {
13991 Some(self.layers[il + 1].attn_norm.float_data())
13992 } else {
13993 None
13994 };
13995 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
13996 x = xn;
13997 h_carry = hn;
13998 self.dflash_tap(e, cache, il, &x, t)?;
13999 }
14000 let mut hn = e.uninit(t * n_embd)?;
14001 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14002 let ld = e.matmul(&self.output, &hn, t)?;
14003 let n_vocab = self.output.out_features();
14004 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
14005 for i in 0..t {
14006 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
14007 }
14008 Ok((vam, hn))
14009 }
14010
14011 pub(crate) fn dflash_tap(
14018 &self,
14019 e: &Engine,
14020 cache: &mut Cache,
14021 il: usize,
14022 x: &CudaSlice<f32>,
14023 t: usize,
14024 ) -> Result<(), Box<dyn std::error::Error>> {
14025 let Some(taps) = cache.dflash_taps.as_mut() else {
14026 return Ok(());
14027 };
14028 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
14029 return Ok(());
14030 };
14031 let h = taps.hidden;
14032 let n_taps = taps.layer_ids.len();
14033 let base = taps.base;
14034 debug_assert!(
14035 base + t <= taps.t,
14036 "tap window {base}+{t} exceeds sink {}",
14037 taps.t
14038 );
14039 let xv = e.view(x, t * h);
14040 for r in 0..t {
14041 let row = xv.slice(r * h..(r + 1) * h);
14042 e.copy_view_into(&mut taps.buf, (base + r) * n_taps * h + slot * h, &row, h)?;
14043 }
14044 Ok(())
14045 }
14046
14047 fn gemma4_verify_trunk(
14048 &self,
14049 e: &Engine,
14050 tokens: &[u32],
14051 pos0: usize,
14052 cache: &mut Cache,
14053 tok_dev: Option<&CudaSlice<u32>>,
14054 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14055 let n_embd = self.cfg.n_embd as usize;
14056 let eps = self.cfg.rms_eps;
14057 let t = tokens.len();
14058 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
14059 let pos_d = e.htod_i32(&pos)?;
14060 let mut x = match tok_dev {
14061 Some(td) => {
14062 let embd_gpu = self
14063 .embd_gpu
14064 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
14065 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
14066 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
14067 }
14068 None => e.htod(&self.embd.gather(n_embd, tokens))?,
14069 };
14070 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
14071 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14072 let n_layers = self.layers.len();
14073 for (il, layer) in self.layers.iter().enumerate() {
14074 let (hq, hdq) = match h_carry.take() {
14075 Some(p) => p,
14076 None => {
14077 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
14078 }
14079 };
14080 let Mixer::Full(fa) = &layer.mixer else {
14081 panic!("gemma4 layer {il} not full-attn")
14082 };
14083 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
14084 let next_norm = if il + 1 < n_layers {
14085 Some(self.layers[il + 1].attn_norm.float_data())
14086 } else {
14087 None
14088 };
14089 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
14090 x = xn;
14091 h_carry = hn;
14092 self.dflash_tap(e, cache, il, &x, t)?;
14093 }
14094 let mut hn = e.uninit(t * n_embd)?;
14095 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14096 let mut ld = e.matmul(&self.output, &hn, t)?;
14097 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
14099 Ok((ld, hn))
14100 }
14101
14102 #[allow(clippy::too_many_arguments)]
14110 fn gemma4_verify_attn_stream(
14111 &self,
14112 e: &Engine,
14113 fa: &crate::hybrid::FullAttnLayer,
14114 il: usize,
14115 hq: &CudaSlice<i8>,
14116 hdq: &CudaSlice<f32>,
14117 pos_d: &CudaSlice<i32>,
14118 t: usize,
14119 cache: &mut Cache,
14120 hint: usize,
14121 row_ctrs: &[CudaSlice<i32>],
14122 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14123 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14124 let eps = self.cfg.rms_eps;
14125 let aux = self.gemma4_aux.as_ref().unwrap();
14126 let ones = aux.ones(e);
14127 #[cfg(debug_assertions)]
14128 crate::debug_assert_tensor_stream_device(
14129 ones,
14130 &e.stream(),
14131 "gemma4_verify_attn_stream.ones",
14132 );
14133 let h0 = e.zeros(0)?;
14134 let h = &h0;
14135 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14138 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14139 let fused_qkv = if f2b {
14140 if swa {
14141 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14142 .map(|(a, b, c)| (a, b, Some(c)))
14143 } else {
14144 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14145 .map(|(a, b)| (a, b, None))
14146 }
14147 } else {
14148 None
14149 };
14150 let (q0, k0, v0) = match fused_qkv {
14151 Some((a, b, cv)) => {
14152 let v = match cv {
14153 Some(c) => c,
14154 None => e.clone_dtod(&b)?,
14155 };
14156 (a, b, v)
14157 }
14158 None => {
14159 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14160 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14161 let v0 = if swa {
14162 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14163 } else {
14164 e.clone_dtod(&k0)?
14165 };
14166 (q0, k0, v0)
14167 }
14168 };
14169 let mut q = e.uninit(t * nh * hd)?;
14170 let mut k = e.uninit(t * nkv * hd)?;
14171 let mut v = e.uninit(t * nkv * hd)?;
14172 let ff = if swa {
14175 None
14176 } else {
14177 Some(
14178 aux.rope_freqs(e)
14179 .expect("gemma4 global rope needs rope_freqs.weight"),
14180 )
14181 };
14182 #[cfg(debug_assertions)]
14183 if let Some(ff) = ff {
14184 crate::debug_assert_tensor_stream_device(
14185 ff,
14186 &e.stream(),
14187 "gemma4_verify_attn_stream.rope_freqs",
14188 );
14189 }
14190 e.rms_norm_qkv_rope(
14191 &q0,
14192 &k0,
14193 &v0,
14194 fa.q_norm.float_data(),
14195 fa.k_norm.float_data(),
14196 ones,
14197 &mut q,
14198 &mut k,
14199 &mut v,
14200 hd,
14201 self.gemma4_rope_dims(il),
14202 nh * t,
14203 nkv * t,
14204 pos_d,
14205 nh,
14206 nkv,
14207 base,
14208 1.0,
14209 ff,
14210 eps,
14211 )?;
14212 let kvl = cache.kv[il].as_mut().unwrap();
14213 e.append_kv_quantized_rows_dc(
14215 &k,
14216 &v,
14217 &mut kvl.k,
14218 &mut kvl.v,
14219 &kvl.len_d,
14220 t,
14221 kvl.kv_dim_k,
14222 kvl.kv_dim_v,
14223 kvl.k_tok_bytes,
14224 kvl.v_tok_bytes,
14225 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14226 )?;
14227 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14230 let mut attn = e.uninit(t * nh * hd)?;
14231 let k_view = e.view_u8(&kvl.k, kvl.k.len());
14232 let v_view = e.view_u8(&kvl.v, kvl.v.len());
14233 if swa && hint + 1 >= win {
14236 e.fa_decode_rows_w(
14239 &q,
14240 &k_view,
14241 &v_view,
14242 &mut attn,
14243 hd,
14244 nh,
14245 nkv,
14246 &kvl.len_d,
14247 0,
14248 t,
14249 scale,
14250 win,
14251 kvl.k_tok_bytes,
14252 kvl.v_tok_bytes,
14253 None,
14254 )?;
14255 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
14256 let bucket = (hint + t + 2)
14269 .next_power_of_two()
14270 .min(crate::fa512_min_tkv().saturating_sub(1));
14271 let qv = e.view(&q, t * nh * hd);
14272 for i in 0..t {
14273 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
14274 let mut q_one = e.uninit(nh * hd)?;
14275 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14276 let mut a_one = e.uninit(nh * hd)?;
14277 e.fa_decode_dc(
14278 &q_one,
14279 &k_view,
14280 &v_view,
14281 &mut a_one,
14282 hd,
14283 nh,
14284 nkv,
14285 &row_ctrs[i],
14286 bucket,
14287 scale,
14288 kvl.k_tok_bytes,
14289 kvl.v_tok_bytes,
14290 false,
14291 )?;
14292 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14293 }
14294 } else if hd == 512 {
14295 e.fa_decode_rows(
14298 &q,
14299 &k_view,
14300 &v_view,
14301 &mut attn,
14302 hd,
14303 nh,
14304 nkv,
14305 hint,
14306 t,
14307 scale,
14308 kvl.k_tok_bytes,
14309 kvl.v_tok_bytes,
14310 Some((&kvl.len_d, 0)),
14311 false,
14312 false,
14313 None,
14314 )?;
14315 } else {
14316 e.fa_decode_rows_dc(
14318 &q,
14319 &k_view,
14320 &v_view,
14321 &mut attn,
14322 hd,
14323 nh,
14324 nkv,
14325 &kvl.len_d,
14326 hint + t,
14327 t,
14328 scale,
14329 kvl.k_tok_bytes,
14330 kvl.v_tok_bytes,
14331 0,
14332 swa && crate::Engine::wkv_on(),
14333 )?;
14334 }
14335 Ok(e.matmul(&fa.wo, &attn, t)?)
14336 }
14337
14338 fn gemma4_verify_attn(
14339 &self,
14340 e: &Engine,
14341 fa: &crate::hybrid::FullAttnLayer,
14342 il: usize,
14343 hq: &CudaSlice<i8>,
14344 hdq: &CudaSlice<f32>,
14345 pos_d: &CudaSlice<i32>,
14346 t: usize,
14347 cache: &mut Cache,
14348 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14349 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14350 let eps = self.cfg.rms_eps;
14351 let aux = self.gemma4_aux.as_ref().unwrap();
14352 let ones = aux.ones(e);
14353 #[cfg(debug_assertions)]
14354 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
14355 let n_embd = self.cfg.n_embd as usize;
14356 let _ = n_embd;
14357
14358 let h0 = e.zeros(0)?;
14359 let h = &h0;
14360 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14363 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14364 let fused_qkv = if f2b {
14365 if swa {
14366 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14367 .map(|(a, b, c)| (a, b, Some(c)))
14368 } else {
14369 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14370 .map(|(a, b)| (a, b, None))
14371 }
14372 } else {
14373 None
14374 };
14375 let (q0, k0, v0) = match fused_qkv {
14376 Some((a, b, cv)) => {
14377 let v = match cv {
14378 Some(c) => c,
14379 None => e.clone_dtod(&b)?,
14380 };
14381 (a, b, v)
14382 }
14383 None => {
14384 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14385 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14386 let v0 = if swa {
14387 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14388 } else {
14389 e.clone_dtod(&k0)?
14390 };
14391 (q0, k0, v0)
14392 }
14393 };
14394 let mut q = e.uninit(t * nh * hd)?;
14395 let mut k = e.uninit(t * nkv * hd)?;
14396 let mut v = e.uninit(t * nkv * hd)?;
14397 let ff = if swa {
14400 None
14401 } else {
14402 Some(
14403 aux.rope_freqs(e)
14404 .expect("gemma4 global rope needs rope_freqs.weight"),
14405 )
14406 };
14407 #[cfg(debug_assertions)]
14408 if let Some(ff) = ff {
14409 crate::debug_assert_tensor_stream_device(
14410 ff,
14411 &e.stream(),
14412 "gemma4_verify_attn.rope_freqs",
14413 );
14414 }
14415 e.rms_norm_qkv_rope(
14416 &q0,
14417 &k0,
14418 &v0,
14419 fa.q_norm.float_data(),
14420 fa.k_norm.float_data(),
14421 ones,
14422 &mut q,
14423 &mut k,
14424 &mut v,
14425 hd,
14426 self.gemma4_rope_dims(il),
14427 nh * t,
14428 nkv * t,
14429 pos_d,
14430 nh,
14431 nkv,
14432 base,
14433 1.0,
14434 ff,
14435 eps,
14436 )?;
14437 let kvl = cache.kv[il].as_mut().unwrap();
14438 let base_len = kvl.len;
14439 e.append_kv_quantized_rows(
14440 &k,
14441 &v,
14442 &mut kvl.k,
14443 &mut kvl.v,
14444 base_len,
14445 t,
14446 kvl.kv_dim_k,
14447 kvl.kv_dim_v,
14448 kvl.k_tok_bytes,
14449 kvl.v_tok_bytes,
14450 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14451 )?;
14452 kvl.len += t;
14453 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14454 let mut attn = e.uninit(t * nh * hd)?;
14455 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
14458 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
14461 if rows_ok && (!swa || base_len + t <= win) {
14462 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14463 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14464 if hd == 512 {
14465 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14467 e.fa_decode_rows(
14468 &q,
14469 &k_view,
14470 &v_view,
14471 &mut attn,
14472 hd,
14473 nh,
14474 nkv,
14475 base_len,
14476 t,
14477 scale,
14478 kvl.k_tok_bytes,
14479 kvl.v_tok_bytes,
14480 Some((&kvl.len_d, 0)),
14481 false,
14482 swa && crate::Engine::wkv_on(),
14483 None,
14484 )?;
14485 } else {
14486 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14490 e.fa_decode_rows_dc(
14491 &q,
14492 &k_view,
14493 &v_view,
14494 &mut attn,
14495 hd,
14496 nh,
14497 nkv,
14498 &kvl.len_d,
14499 base_len + t,
14500 t,
14501 scale,
14502 kvl.k_tok_bytes,
14503 kvl.v_tok_bytes,
14504 0,
14505 swa && crate::Engine::wkv_on(),
14506 )?;
14507 }
14508 return Ok(e.matmul(&fa.wo, &attn, t)?);
14509 }
14510 if hd == 256
14518 && swa
14519 && base_len + 1 >= win
14520 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14521 {
14522 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14523 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14524 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14525 e.fa_decode_rows_w(
14526 &q,
14527 &k_view,
14528 &v_view,
14529 &mut attn,
14530 hd,
14531 nh,
14532 nkv,
14533 &kvl.len_d,
14534 0,
14535 t,
14536 scale,
14537 win,
14538 kvl.k_tok_bytes,
14539 kvl.v_tok_bytes,
14540 None,
14541 )?;
14542 return Ok(e.matmul(&fa.wo, &attn, t)?);
14543 }
14544 for i in 0..t {
14545 let avail = base_len + i + 1;
14546 let (off_tok, t_kv) = if swa && avail > win {
14547 (avail - win, win)
14548 } else {
14549 (0, avail)
14550 };
14551 let k_view = e.view_u8_range(
14552 &kvl.k,
14553 off_tok * kvl.k_tok_bytes,
14554 (off_tok + t_kv) * kvl.k_tok_bytes,
14555 );
14556 let v_view = e.view_u8_range(
14557 &kvl.v,
14558 off_tok * kvl.v_tok_bytes,
14559 (off_tok + t_kv) * kvl.v_tok_bytes,
14560 );
14561 let qi = e.view(&q, t * nh * hd);
14562 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
14563 let mut q_one = e.uninit(nh * hd)?;
14564 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14565 let mut a_one = e.uninit(nh * hd)?;
14566 if swa
14570 && avail > win
14571 && hd == 256
14572 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14573 {
14574 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14575 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14576 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14577 e.fa_decode_rows_w(
14578 &q_one,
14579 &kp,
14580 &vp,
14581 &mut a_one,
14582 hd,
14583 nh,
14584 nkv,
14585 &kvl.len_d,
14586 0,
14587 1,
14588 scale,
14589 win,
14590 kvl.k_tok_bytes,
14591 kvl.v_tok_bytes,
14592 None,
14593 )?;
14594 } else if !swa
14595 && hd == 512
14596 && avail >= crate::fa512_min_tkv()
14597 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14598 {
14599 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14600 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14601 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14602 e.fa_decode_rows(
14603 &q_one,
14604 &kp,
14605 &vp,
14606 &mut a_one,
14607 hd,
14608 nh,
14609 nkv,
14610 avail - 1,
14611 1,
14612 scale,
14613 kvl.k_tok_bytes,
14614 kvl.v_tok_bytes,
14615 Some((&kvl.len_d, 0)),
14616 false,
14617 false,
14618 None,
14619 )?;
14620 } else {
14621 e.fa_decode_kvmod(
14622 &q_one,
14623 &k_view,
14624 &v_view,
14625 &mut a_one,
14626 hd,
14627 nh,
14628 nkv,
14629 t_kv,
14630 scale,
14631 kvl.k_tok_bytes,
14632 kvl.v_tok_bytes,
14633 swa && crate::Engine::wkv_on(),
14634 )?;
14635 }
14636 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14637 }
14638 Ok(e.matmul(&fa.wo, &attn, t)?)
14639 }
14640
14641 pub(crate) fn gemma4_decode_step_h(
14644 &self,
14645 e: &Engine,
14646 token: u32,
14647 cache: &mut Cache,
14648 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14649 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
14654 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
14655 }
14656 if crate::pp::pp_cuts(self.layers.len()).is_some() {
14657 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
14658 }
14659 let n_embd = self.cfg.n_embd as usize;
14660 let eps = self.cfg.rms_eps;
14661 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14662 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14663 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14664 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14667 let n_layers = self.layers.len();
14668 for (il, layer) in self.layers.iter().enumerate() {
14669 let (hq, hdq) = match h_carry.take() {
14670 Some(p) => p,
14671 None => {
14672 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
14673 }
14674 };
14675 let Mixer::Full(fa) = &layer.mixer else {
14676 panic!("gemma4 layer {il} not full-attn")
14677 };
14678 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
14679 let next_norm = if il + 1 < n_layers {
14680 Some(self.layers[il + 1].attn_norm.float_data())
14681 } else {
14682 None
14683 };
14684 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14685 x = xn;
14686 h_carry = hn;
14687 }
14688 let mut hn = e.uninit(n_embd)?;
14689 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14690 let h_seed = e.clone_dtod(&x)?;
14691 let mut ld = e.matmul(&self.output, &hn, 1)?;
14692 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14693 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
14695 let logits = e.dtoh(&ld)?;
14696 cache.pos += 1;
14697 Ok((logits, h_seed))
14698 }
14699
14700 fn gemma4_decode_layers(
14708 &self,
14709 e: &Engine,
14710 mut x: CudaSlice<f32>,
14711 lo: usize,
14712 hi: usize,
14713 pos_d: &CudaSlice<i32>,
14714 cache: &mut Cache,
14715 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14716 let n_embd = self.cfg.n_embd as usize;
14717 let eps = self.cfg.rms_eps;
14718 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14719 for il in lo..hi {
14720 let layer = &self.layers[il];
14721 let (hq, hdq) = match h_carry.take() {
14722 Some(p) => p,
14723 None => {
14725 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
14726 }
14727 };
14728 let Mixer::Full(fa) = &layer.mixer else {
14729 panic!("gemma4 layer {il} not full-attn")
14730 };
14731 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
14732 let next_norm = if il + 1 < hi {
14733 Some(self.layers[il + 1].attn_norm.float_data())
14734 } else {
14735 None
14736 };
14737 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14738 x = xn;
14739 h_carry = hn;
14740 }
14741 Ok(x)
14742 }
14743
14744 fn gemma4_decode_step_h_pp2(
14752 &self,
14753 e: &Engine,
14754 token: u32,
14755 cache: &mut Cache,
14756 split: usize,
14757 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14758 if crate::pp::pp2_streams_off() {
14759 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
14760 }
14761 let rt = crate::pp::Pp2Rt::get(e)?;
14762 let e0 = rt.engine(0, e);
14763 let e1 = rt.engine(1, e);
14764 let n_embd = self.cfg.n_embd as usize;
14765 let eps = self.cfg.rms_eps;
14766 let pos = cache.pos as i32;
14767
14768 let slot = {
14770 let _st0 = rt.enter(0);
14771 let pos_d = e0.htod_i32(&[pos])?;
14772 #[cfg(debug_assertions)]
14773 crate::debug_assert_tensor_stream_device(
14774 &pos_d,
14775 &e0.stream(),
14776 "gemma4_decode_step_h_pp2.stage0.pos_d",
14777 );
14778 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
14779 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14780 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
14781 rt.tx(0, &x, n_embd)?
14782 };
14783
14784 let _st1 = rt.enter(1);
14786 let pos_d = e1.htod_i32(&[pos])?;
14787 #[cfg(debug_assertions)]
14788 crate::debug_assert_tensor_stream_device(
14789 &pos_d,
14790 &e1.stream(),
14791 "gemma4_decode_step_h_pp2.stage1.pos_d",
14792 );
14793 let x = rt.rx(0, slot, n_embd)?;
14794 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
14795
14796 let mut hn = e1.uninit(n_embd)?;
14797 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14798 let h_seed = e1.clone_dtod(&x)?;
14799 let mut ld = e1.matmul(&self.output, &hn, 1)?;
14800 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14801 e1.softcap(&mut ld, cap, self.output.out_features())?;
14802 self.gemma4_suppress(e1, &mut ld, 1)?;
14803 let logits = e1.dtoh(&ld)?;
14804 cache.pos += 1;
14805 Ok((logits, h_seed))
14806 }
14807
14808 fn gemma4_decode_step_h_pp2_samestream(
14811 &self,
14812 e: &Engine,
14813 token: u32,
14814 cache: &mut Cache,
14815 split: usize,
14816 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14817 let n_embd = self.cfg.n_embd as usize;
14818 let eps = self.cfg.rms_eps;
14819 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14820
14821 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14823 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14824 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
14825
14826 let boundary_tx = e.clone_dtod(&x)?;
14828 let boundary_rx = e.clone_dtod(&boundary_tx)?;
14829
14830 let x =
14832 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
14833
14834 let mut hn = e.uninit(n_embd)?;
14835 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14836 let h_seed = e.clone_dtod(&x)?;
14837 let mut ld = e.matmul(&self.output, &hn, 1)?;
14838 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14839 e.softcap(&mut ld, cap, self.output.out_features())?;
14840 self.gemma4_suppress(e, &mut ld, 1)?;
14841 let logits = e.dtoh(&ld)?;
14842 cache.pos += 1;
14843 Ok((logits, h_seed))
14844 }
14845}
14846
14847impl HybridModel {
14866 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
14869 let geometry = self
14870 .cfg
14871 .layer_geometry(il as u32)
14872 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
14873 debug_assert_eq!(
14874 geometry.attention_gate,
14875 memra_gguf::config::AttentionGateKind::SeparateHead
14876 );
14877 geometry
14878 }
14879
14880 #[allow(clippy::too_many_arguments)]
14940 fn step35_attn_pre_wo(
14941 &self,
14942 e: &Engine,
14943 fa: &FullAttnLayer,
14944 mut g3: Vec<CudaSlice<f32>>,
14945 hg: Option<&CudaSlice<f32>>,
14946 gt_pre: Option<&CudaSlice<f32>>,
14947 pos_d: &CudaSlice<i32>,
14948 t: usize,
14949 cache: Option<&mut Cache>,
14950 il: usize,
14951 seq_end: usize,
14952 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14953 let geometry = self.step35_geom(il);
14954 let hd = geometry.head_dim_k as usize;
14955 let nkv = geometry.n_head_kv as usize;
14956 let nh = geometry.n_head as usize;
14957 let rbase = geometry.rope_base;
14958 let scale = geometry.attention_scale();
14959 let swa = geometry.window.is_some();
14960 let eps = self.cfg.rms_eps;
14961 let win = geometry.window.unwrap_or(0) as usize;
14962 let n_rot = geometry.n_rot as usize;
14963
14964 let v = g3.pop().unwrap();
14965 let k0 = g3.pop().unwrap();
14966 let q0 = g3.pop().unwrap();
14967
14968 let mut q = e.uninit(t * nh * hd)?;
14972 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
14973 let mut k = e.uninit(t * nkv * hd)?;
14974 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
14975 let ff = if geometry.rope_factors {
14976 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
14977 } else {
14978 None
14979 };
14980 #[cfg(debug_assertions)]
14981 if let Some(ff) = ff {
14982 crate::debug_assert_tensor_stream_device(
14983 ff,
14984 &e.stream(),
14985 "step35_attn_pre_wo.rope_freqs",
14986 );
14987 }
14988 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
14989
14990 let mut attn = e.uninit(t * nh * hd)?;
14991 match cache {
14992 Some(cache) => {
14993 let base_len = cache.kv[il].as_ref().unwrap().len;
14994 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
14996 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
14997 let off = if swa {
14998 let raw = base_len.saturating_sub(win - 1);
14999 if legacy_tkv || legacy_calllocal {
15000 raw
15001 } else {
15002 raw & !31usize
15003 }
15004 } else {
15005 0
15006 };
15007 {
15008 let kvl = cache.kv[il].as_mut().unwrap();
15009 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
15010 let write_row = e.prepare_kv_append(kvl, off, t)?;
15011 e.append_kv_quantized_rows(
15012 &k,
15013 &v,
15014 &mut kvl.k,
15015 &mut kvl.v,
15016 write_row,
15017 t,
15018 kvl.kv_dim_k,
15019 kvl.kv_dim_v,
15020 kvl.k_tok_bytes,
15021 kvl.v_tok_bytes,
15022 crate::Engine::kv_fp8_on(),
15023 )?;
15024 kvl.len += t;
15025 let new_len = kvl.len as i32;
15026 e.set_i32_one(&mut kvl.len_d, new_len)?;
15027 }
15028 let kvl = cache.kv[il].as_ref().unwrap();
15029 let t_kv = base_len + t - off;
15052 let physical = kvl.physical_rows(off, off + t_kv)?;
15053 let k_view = e.view_u8_range(
15054 &kvl.k,
15055 physical.start * kvl.k_tok_bytes,
15056 physical.end * kvl.k_tok_bytes,
15057 );
15058 let v_view = e.view_u8_range(
15059 &kvl.v,
15060 physical.start * kvl.v_tok_bytes,
15061 physical.end * kvl.v_tok_bytes,
15062 );
15063 let swa_naive = if legacy_tkv {
15075 t_kv > win
15076 } else {
15077 seq_end > win
15078 };
15079 if swa && swa_naive {
15080 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15093 e.sdpa_naive_w_quantized_view(
15094 &q,
15095 &k_view,
15096 &v_view,
15097 &mut attn,
15098 hd,
15099 nh,
15100 nkv,
15101 t,
15102 t_kv,
15103 scale,
15104 true,
15105 win,
15106 kvl.k_tok_bytes,
15107 kvl.v_tok_bytes,
15108 )?;
15109 } else {
15110 e.fa_prefill_view_ws_w_hd128(
15111 &q,
15112 &k_view,
15113 &v_view,
15114 &mut attn,
15115 hd,
15116 nh,
15117 nkv,
15118 t,
15119 t_kv,
15120 scale,
15121 true,
15122 win,
15123 kvl.k_tok_bytes,
15124 kvl.v_tok_bytes,
15125 )?;
15126 }
15127 } else if std::env::var("MEMRA_NOFA").is_ok() {
15128 e.sdpa_naive_quantized_view(
15129 &q,
15130 &k_view,
15131 &v_view,
15132 &mut attn,
15133 hd,
15134 nh,
15135 nkv,
15136 t,
15137 t_kv,
15138 scale,
15139 true,
15140 kvl.k_tok_bytes,
15141 kvl.v_tok_bytes,
15142 )?;
15143 } else {
15144 e.fa_prefill_view_ws(
15149 &q,
15150 &k_view,
15151 &v_view,
15152 &mut attn,
15153 hd,
15154 nh,
15155 nkv,
15156 t,
15157 t_kv,
15158 scale,
15159 true,
15160 kvl.k_tok_bytes,
15161 kvl.v_tok_bytes,
15162 crate::Engine::kv_fp8_on(),
15163 )?;
15164 }
15165 }
15166 None => {
15167 debug_assert_eq!(
15172 seq_end, t,
15173 "step35 cacheless prefill is monolithic (seq_end == t)"
15174 );
15175 if swa && seq_end > win {
15176 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
15177 } else if std::env::var("MEMRA_NOFA").is_ok() {
15178 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15179 } else {
15180 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15181 }
15182 }
15183 }
15184
15185 let gw = fa
15188 .attn_gate
15189 .as_ref()
15190 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
15191 let gt_owned = if gt_pre.is_none() {
15192 Some(e.matmul(
15193 gw,
15194 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
15195 t,
15196 )?)
15197 } else {
15198 None
15199 };
15200 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
15201 let mut ag = e.uninit(t * nh * hd)?;
15202 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
15203 Ok(ag)
15204 }
15205
15206 pub(crate) fn step35_attn(
15209 &self,
15210 e: &Engine,
15211 fa: &FullAttnLayer,
15212 h: &CudaSlice<f32>,
15213 pos_d: &CudaSlice<i32>,
15214 t: usize,
15215 il: usize,
15216 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15217 let g3 = match self.step35_tp_qkv(e, fa, h, t)? {
15218 Some(g3) => g3,
15219 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15220 };
15221 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
15223 self.step35_o(e, fa, &ag, t)
15224 }
15225
15226 #[allow(clippy::too_many_arguments)]
15233 pub(crate) fn step35_attn_prime(
15234 &self,
15235 e: &Engine,
15236 fa: &FullAttnLayer,
15237 h: &CudaSlice<f32>,
15238 hx: Option<&CudaSlice<u8>>,
15239 pos_d: &CudaSlice<i32>,
15240 t: usize,
15241 cache: &mut Cache,
15242 il: usize,
15243 seq_end: usize,
15244 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15245 if step_tp_prefill_enabled()? && fa.step_tp_qkv.is_some() {
15246 if hx.is_some() {
15247 return Err(
15248 "rank-local Step prefill preserves BF16 activations and refuses the q8_1 \
15249 pre-quantized prime path"
15250 .into(),
15251 );
15252 }
15253 return self.step35_tp_prefill_attn_resident(e, fa, il, h, pos_d, t, cache, seq_end);
15254 }
15255 let g3 = if fa.step_tp_qkv.is_some() {
15256 if hx.is_some() {
15257 return Err(
15258 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
15259 pre-quantized prime path"
15260 .into(),
15261 );
15262 }
15263 self.step35_tp_qkv(e, fa, h, t)?
15264 .expect("Step Q/K/V TP disappeared after the presence check")
15265 } else {
15266 match hx {
15267 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
15268 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15269 }
15270 };
15271 let ag =
15272 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
15273 self.step35_o(e, fa, &ag, t)
15274 }
15275
15276 fn ensure_step_tp_kv_cache(
15277 &self,
15278 e: &Engine,
15279 fa: &FullAttnLayer,
15280 il: usize,
15281 cache: &mut Cache,
15282 ) -> Result<bool, Box<dyn std::error::Error>> {
15283 let tp = fa
15284 .step_tp_qkv
15285 .as_ref()
15286 .ok_or("Step TP cache hydration lost its resident projections")?;
15287 let geometry = self.step35_geom(il);
15288 let window = geometry.window.map(|window| window as usize);
15289 let ranks = tp.runtime.devices().len();
15290 let head_dim = geometry.head_dim_k as usize;
15291 let kv_heads = geometry.n_head_kv as usize;
15292 let max_ctx = cache.max_ctx;
15293
15294 if cache.tp_kv[il].is_some() {
15295 return Ok(false);
15296 }
15297 let local = cache.kv[il]
15298 .as_ref()
15299 .ok_or_else(|| format!("Step TP layer {il} has no owning-stage KV cache"))?;
15300 if local.kv_dim_k != kv_heads * head_dim || local.kv_dim_v != kv_heads * head_dim {
15301 return Err(format!(
15302 "Step TP layer {il} local KV geometry k={} v={} != {}",
15303 local.kv_dim_k,
15304 local.kv_dim_v,
15305 kv_heads * head_dim
15306 )
15307 .into());
15308 }
15309 let resident_start = window
15310 .map(|window| local.len.saturating_sub(window.saturating_sub(1)) & !31usize)
15311 .unwrap_or(0);
15312 let resident_rows = local.len - resident_start;
15313 let physical = local.physical_rows(resident_start, local.len)?;
15314 let k_rows = if resident_rows == 0 {
15315 Vec::new()
15316 } else {
15317 e.dtoh_u8_view(&e.view_u8_range(
15318 &local.k,
15319 physical.start * local.k_tok_bytes,
15320 physical.end * local.k_tok_bytes,
15321 ))?
15322 };
15323 let v_rows = if resident_rows == 0 {
15324 Vec::new()
15325 } else {
15326 e.dtoh_u8_view(&e.view_u8_range(
15327 &local.v,
15328 physical.start * local.v_tok_bytes,
15329 physical.end * local.v_tok_bytes,
15330 ))?
15331 };
15332 let mut distributed = match window {
15333 Some(window) => tp.runtime.allocate_tp_swa_kv_cache(
15334 kv_heads * head_dim,
15335 kv_heads * head_dim,
15336 max_ctx,
15337 window,
15338 )?,
15339 None => tp.runtime.allocate_tp_kv_cache(
15340 kv_heads * head_dim,
15341 kv_heads * head_dim,
15342 max_ctx,
15343 )?,
15344 };
15345 if distributed.k_tok_bytes() * ranks != local.k_tok_bytes
15346 || distributed.v_tok_bytes() * ranks != local.v_tok_bytes
15347 {
15348 return Err(format!(
15349 "Step TP layer {il} distributed/local KV token bytes disagree: \
15350 k={}x{ranks}/{} v={}x{ranks}/{}",
15351 distributed.k_tok_bytes(),
15352 local.k_tok_bytes,
15353 distributed.v_tok_bytes(),
15354 local.v_tok_bytes,
15355 )
15356 .into());
15357 }
15358 tp.runtime.hydrate_tp_kv_cache_from(
15359 &mut distributed,
15360 local.len,
15361 resident_start,
15362 &k_rows,
15363 &v_rows,
15364 )?;
15365 cache.tp_kv[il] = Some(distributed);
15366 Ok(true)
15367 }
15368
15369 #[allow(clippy::too_many_arguments)]
15370 fn step35_tp_prefill_attn_resident(
15371 &self,
15372 e: &Engine,
15373 fa: &FullAttnLayer,
15374 il: usize,
15375 h: &CudaSlice<f32>,
15376 pos_d: &CudaSlice<i32>,
15377 tokens: usize,
15378 cache: &mut Cache,
15379 seq_end: usize,
15380 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15381 let tp = fa
15382 .step_tp_qkv
15383 .as_ref()
15384 .ok_or("Step TP prefill lost its resident projections")?;
15385 let attention = tp
15386 .attention
15387 .as_ref()
15388 .ok_or("Step TP prefill lost its resident attention auxiliaries")?;
15389 let ranks = tp.runtime.devices().len();
15390 if !step_tp_prefill_shape(
15391 true,
15392 tokens,
15393 ranks,
15394 tp.runtime.native_p2p(),
15395 true,
15396 crate::Engine::kv_fp8_on(),
15397 ) {
15398 return Err(format!(
15399 "rank-local Step prefill requires tokens>={PRIME_MIN_T}, TP2/TP4 native P2P, \
15400 rank-local attention, and q8_0/q5_1 KV; got tokens={tokens} ranks={ranks} \
15401 native_p2p={} fp8_kv={}",
15402 tp.runtime.native_p2p(),
15403 crate::Engine::kv_fp8_on(),
15404 )
15405 .into());
15406 }
15407 for seam in [
15408 "MEMRA_STEP35_SWA_TKV",
15409 "MEMRA_PRIME_CALLLOCAL",
15410 "MEMRA_PRIME_F32CHUNK0",
15411 ] {
15412 if std::env::var(seam).as_deref() == Ok("1") {
15413 return Err(format!(
15414 "rank-local Step prefill has not qualified the legacy seam {seam}=1"
15415 )
15416 .into());
15417 }
15418 }
15419
15420 let geometry = self.step35_geom(il);
15421 let window = geometry.window.map(|window| window as usize);
15422 let head_dim = geometry.head_dim_k as usize;
15423 let heads = geometry.n_head as usize;
15424 let kv_heads = geometry.n_head_kv as usize;
15425 if heads % ranks != 0 || kv_heads % ranks != 0 {
15426 return Err(format!(
15427 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15428 )
15429 .into());
15430 }
15431 let local_heads = heads / ranks;
15432 let local_kv_heads = kv_heads / ranks;
15433 let local_kv_dim = local_kv_heads * head_dim;
15434 let hidden = self.cfg.n_embd as usize;
15435 let expected_input = tokens
15436 .checked_mul(hidden)
15437 .ok_or("Step TP prefill input size overflow")?;
15438 if h.len() < expected_input {
15439 return Err(format!(
15440 "Step TP prefill input {} is shorter than {tokens}x{hidden}",
15441 h.len()
15442 )
15443 .into());
15444 }
15445 let positions = e.dtoh_i32(pos_d)?;
15446 if positions.len() != tokens {
15447 return Err(format!(
15448 "rank-local Step prefill positions {} != tokens {tokens}",
15449 positions.len()
15450 )
15451 .into());
15452 }
15453
15454 let mut active_input = e.uninit(expected_input)?;
15455 e.copy_view_into(
15456 &mut active_input,
15457 0,
15458 &h.slice(0..expected_input),
15459 expected_input,
15460 )?;
15461 let mut input = tp.runtime.allocate_replicated_device_rows(tokens, hidden)?;
15462 e.stream().synchronize()?;
15467 tp.runtime
15468 .refresh_replicated_device_rows_from_root(&mut input, &active_input)?;
15469 let q_raw = tp
15470 .runtime
15471 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &input)?;
15472 let k_raw = tp
15473 .runtime
15474 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &input)?;
15475 let v_raw = tp
15476 .runtime
15477 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &input)?;
15478 let mut q = Vec::with_capacity(ranks);
15479 let mut k = Vec::with_capacity(ranks);
15480 for rank in 0..ranks {
15481 let engine = tp
15482 .runtime
15483 .rank_engine(rank)
15484 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15485 let _main = engine.gpu.enter_main()?;
15486 let mut q_rank = engine.uninit(tokens * local_heads * head_dim)?;
15487 engine.rms_norm(
15488 &q_raw[rank],
15489 &attention.q_norm[rank],
15490 &mut q_rank,
15491 head_dim,
15492 tokens * local_heads,
15493 self.cfg.rms_eps,
15494 )?;
15495 let mut k_rank = engine.uninit(tokens * local_kv_dim)?;
15496 engine.rms_norm(
15497 &k_raw[rank],
15498 &attention.k_norm[rank],
15499 &mut k_rank,
15500 head_dim,
15501 tokens * local_kv_heads,
15502 self.cfg.rms_eps,
15503 )?;
15504 let position = engine.htod_i32(&positions)?;
15505 let rope_freqs = if geometry.rope_factors {
15506 self.step35_aux
15507 .as_ref()
15508 .and_then(|aux| aux.rope_freqs(engine))
15509 } else {
15510 None
15511 };
15512 engine.rope_neox2(
15513 &mut q_rank,
15514 &mut k_rank,
15515 &position,
15516 head_dim,
15517 geometry.n_rot as usize,
15518 local_heads,
15519 local_kv_heads,
15520 tokens,
15521 geometry.rope_base,
15522 1.0,
15523 rope_freqs,
15524 )?;
15525 q.push(q_rank);
15526 k.push(k_rank);
15527 }
15528
15529 let gate_weight = fa
15530 .attn_gate
15531 .as_ref()
15532 .ok_or("step35 layer is missing attn_gate.weight")?;
15533 let gate = e.dtoh(&e.matmul(gate_weight, h, tokens)?)?;
15534 if gate.len() != tokens * heads {
15535 return Err(format!(
15536 "Step TP layer {il} gate output {} != {tokens}x{heads}",
15537 gate.len()
15538 )
15539 .into());
15540 }
15541
15542 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15543 let base_len = cache.kv[il]
15544 .as_ref()
15545 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15546 .len;
15547 let distributed = cache.tp_kv[il]
15548 .as_ref()
15549 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15550 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15551 return Err(format!(
15552 "Step TP layer {il} cache lengths diverged before prefill: \
15553 local={base_len} distributed={}/{}",
15554 distributed.committed_len(),
15555 distributed.staged_len()
15556 )
15557 .into());
15558 }
15559 let target_len = base_len
15560 .checked_add(tokens)
15561 .ok_or("Step TP prefill cache length overflow")?;
15562 if target_len > cache.max_ctx {
15563 return Err(format!(
15564 "Step TP layer {il} prefill exceeds cache: {base_len}+{tokens}>{}",
15565 cache.max_ctx
15566 )
15567 .into());
15568 }
15569 if seq_end < target_len {
15570 return Err(format!(
15571 "Step TP layer {il} request end {seq_end} precedes chunk end {target_len}"
15572 )
15573 .into());
15574 }
15575
15576 let transaction = cache.tp_kv[il]
15577 .as_mut()
15578 .expect("distributed cache checked above")
15579 .begin_transaction()?;
15580 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15581 cache.tp_kv[il]
15582 .as_mut()
15583 .expect("distributed cache checked above"),
15584 transaction,
15585 &k,
15586 &v_raw,
15587 tokens,
15588 ) {
15589 let _ = tp.runtime.rollback_tp_kv_transaction(
15590 cache.tp_kv[il]
15591 .as_mut()
15592 .expect("distributed cache checked above"),
15593 transaction,
15594 );
15595 return Err(error);
15596 }
15597
15598 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15599 let distributed = cache.tp_kv[il]
15600 .as_ref()
15601 .expect("distributed cache checked above");
15602 let staged_len = distributed.staged_len();
15603 let view_start = window
15604 .map(|window| base_len.saturating_sub(window.saturating_sub(1)) & !31usize)
15605 .unwrap_or(0);
15606 let physical = distributed.physical_range(view_start, staged_len)?;
15607 let t_kv = staged_len - view_start;
15608 let swa_naive = window.is_some_and(|window| seq_end > window);
15609 let mut gated = Vec::with_capacity(ranks);
15610 for rank in 0..ranks {
15611 let engine = tp
15612 .runtime
15613 .rank_engine(rank)
15614 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15615 let _main = engine.gpu.enter_main()?;
15616 let rank_cache = distributed
15617 .rank(rank)
15618 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
15619 let k_view = engine.view_u8_range(
15620 rank_cache.k(),
15621 physical.start * distributed.k_tok_bytes(),
15622 physical.end * distributed.k_tok_bytes(),
15623 );
15624 let v_view = engine.view_u8_range(
15625 rank_cache.v(),
15626 physical.start * distributed.v_tok_bytes(),
15627 physical.end * distributed.v_tok_bytes(),
15628 );
15629 let mut attention_out = engine.uninit(tokens * local_heads * head_dim)?;
15630 if swa_naive {
15631 let window = window.expect("SWA predicate requires a window");
15632 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15633 engine.sdpa_naive_w_quantized_view(
15634 &q[rank],
15635 &k_view,
15636 &v_view,
15637 &mut attention_out,
15638 head_dim,
15639 local_heads,
15640 local_kv_heads,
15641 tokens,
15642 t_kv,
15643 geometry.attention_scale(),
15644 true,
15645 window,
15646 distributed.k_tok_bytes(),
15647 distributed.v_tok_bytes(),
15648 )?;
15649 } else {
15650 engine.fa_prefill_view_ws_w_hd128(
15651 &q[rank],
15652 &k_view,
15653 &v_view,
15654 &mut attention_out,
15655 head_dim,
15656 local_heads,
15657 local_kv_heads,
15658 tokens,
15659 t_kv,
15660 geometry.attention_scale(),
15661 true,
15662 window,
15663 distributed.k_tok_bytes(),
15664 distributed.v_tok_bytes(),
15665 )?;
15666 }
15667 } else if std::env::var("MEMRA_NOFA").is_ok() {
15668 engine.sdpa_naive_quantized_view(
15669 &q[rank],
15670 &k_view,
15671 &v_view,
15672 &mut attention_out,
15673 head_dim,
15674 local_heads,
15675 local_kv_heads,
15676 tokens,
15677 t_kv,
15678 geometry.attention_scale(),
15679 true,
15680 distributed.k_tok_bytes(),
15681 distributed.v_tok_bytes(),
15682 )?;
15683 } else {
15684 engine.fa_prefill_view_ws(
15685 &q[rank],
15686 &k_view,
15687 &v_view,
15688 &mut attention_out,
15689 head_dim,
15690 local_heads,
15691 local_kv_heads,
15692 tokens,
15693 t_kv,
15694 geometry.attention_scale(),
15695 true,
15696 distributed.k_tok_bytes(),
15697 distributed.v_tok_bytes(),
15698 false,
15699 )?;
15700 }
15701
15702 let gate_start = rank * local_heads;
15703 let mut gate_rank = Vec::with_capacity(tokens * local_heads);
15704 for token in 0..tokens {
15705 let start = token * heads + gate_start;
15706 gate_rank.extend_from_slice(&gate[start..start + local_heads]);
15707 }
15708 let gate_rank = engine.htod(&gate_rank)?;
15709 let mut gated_rank = engine.uninit(tokens * local_heads * head_dim)?;
15710 engine.attn_head_gate(
15711 &attention_out,
15712 &gate_rank,
15713 &mut gated_rank,
15714 None,
15715 head_dim,
15716 local_heads,
15717 tokens,
15718 )?;
15719 gated.push(gated_rank);
15720 }
15721 for rank in 1..ranks {
15722 let engine = tp
15723 .runtime
15724 .rank_engine(rank)
15725 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15726 let _main = engine.gpu.enter_main()?;
15727 engine.stream().synchronize()?;
15728 }
15729
15730 let (output, k_shadow, v_shadow) = if tp.runtime.bulk_p2p() {
15731 let output = tp
15732 .runtime
15733 .step_bf16_row_parallel_resident_root_device(&tp.o, &gated, tokens)?;
15734 let k_shadow =
15735 tp.runtime
15736 .gather_native_column_shards_device(&k, tokens, local_kv_dim)?;
15737 let v_shadow =
15738 tp.runtime
15739 .gather_native_column_shards_device(&v_raw, tokens, local_kv_dim)?;
15740 let root = tp
15741 .runtime
15742 .rank_engine(0)
15743 .ok_or("Step TP prefill lost its root engine")?;
15744 let _main = root.gpu.enter_main()?;
15745 root.stream().synchronize()?;
15746 (output, k_shadow, v_shadow)
15747 } else {
15748 let attention = tp.runtime.gather_native_column_shards(
15749 &gated,
15750 tokens,
15751 local_heads * head_dim,
15752 )?;
15753 let output = tp
15754 .runtime
15755 .step_bf16_row_parallel_resident_native(&tp.o, &attention, tokens)?;
15756 let k_shadow = tp
15757 .runtime
15758 .gather_native_column_shards(&k, tokens, local_kv_dim)?;
15759 let v_shadow =
15760 tp.runtime
15761 .gather_native_column_shards(&v_raw, tokens, local_kv_dim)?;
15762 (e.htod(&output)?, e.htod(&k_shadow)?, e.htod(&v_shadow)?)
15763 };
15764 let local = cache.kv[il]
15765 .as_mut()
15766 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
15767 if local.len != base_len {
15768 return Err(format!(
15769 "Step TP layer {il} local cache changed during prefill: \
15770 len={} base={base_len}",
15771 local.len
15772 )
15773 .into());
15774 }
15775 let retain_from = window
15776 .map(|window| {
15777 let staged_retain = staged_len.saturating_sub(window) & !31usize;
15778 let rollback_retain =
15779 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
15780 staged_retain.min(rollback_retain)
15781 })
15782 .unwrap_or(0);
15783 let write_row = e.prepare_kv_append(local, retain_from, tokens)?;
15784 e.append_kv_quantized_rows(
15785 &k_shadow,
15786 &v_shadow,
15787 &mut local.k,
15788 &mut local.v,
15789 write_row,
15790 tokens,
15791 local.kv_dim_k,
15792 local.kv_dim_v,
15793 local.k_tok_bytes,
15794 local.v_tok_bytes,
15795 false,
15796 )?;
15797 local.len = staged_len;
15798 e.set_i32_one(&mut local.len_d, staged_len as i32)?;
15799 Ok(output)
15800 })();
15801
15802 let output = match staged {
15803 Ok(output) => output,
15804 Err(error) => {
15805 let _ = tp.runtime.rollback_tp_kv_transaction(
15806 cache.tp_kv[il]
15807 .as_mut()
15808 .expect("distributed cache checked above"),
15809 transaction,
15810 );
15811 if let Some(local) = cache.kv[il].as_mut() {
15812 local.len = base_len;
15813 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
15814 }
15815 return Err(error);
15816 }
15817 };
15818 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
15819 cache.tp_kv[il]
15820 .as_mut()
15821 .expect("distributed cache checked above"),
15822 transaction,
15823 tokens,
15824 ) {
15825 let _ = tp.runtime.rollback_tp_kv_transaction(
15826 cache.tp_kv[il]
15827 .as_mut()
15828 .expect("distributed cache checked above"),
15829 transaction,
15830 );
15831 let local = cache.kv[il].as_mut().expect("local cache checked above");
15832 local.len = base_len;
15833 e.set_i32_one(&mut local.len_d, base_len as i32)?;
15834 return Err(error);
15835 }
15836
15837 let committed = cache.tp_kv[il]
15838 .as_ref()
15839 .expect("distributed cache checked above")
15840 .committed_len();
15841 let local_len = cache.kv[il]
15842 .as_ref()
15843 .expect("local cache checked above")
15844 .len;
15845 if committed != local_len {
15846 return Err(format!(
15847 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
15848 )
15849 .into());
15850 }
15851 eprintln!(
15852 "[step-tp-prefill-attn] execute layer={} devices={:?} tokens={tokens} \
15853 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
15854 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
15855 attention_scope={} input_path=root-device-replicated gate_tensor_parallel=false \
15856 gate_shards=host-canonical o_tensor_parallel=true local_cache_shadow=true \
15857 cache_commit=chunk transport={} native_p2p=true bulk_p2p={} \
15858 output={} performance_claim=false",
15859 tp.layer,
15860 tp.devices,
15861 hydrated,
15862 if window.is_some() {
15863 "rank-local-swa-ring"
15864 } else {
15865 "rank-local-global"
15866 },
15867 tp.runtime.transport_label(),
15868 tp.runtime.bulk_p2p(),
15869 if tp.runtime.bulk_p2p() {
15870 "root-device"
15871 } else {
15872 "root-readback"
15873 },
15874 );
15875 Ok(output)
15876 }
15877
15878 fn step35_tp_decode_attn_resident(
15879 &self,
15880 e: &Engine,
15881 fa: &FullAttnLayer,
15882 il: usize,
15883 h: &CudaSlice<f32>,
15884 pos_d: &CudaSlice<i32>,
15885 cache: &mut Cache,
15886 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15887 static ATTN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15891 static ATTN_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15892 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15893 let started = timing.then(std::time::Instant::now);
15894 let result = if crate::tp::step_tp_decode_v2_enabled()? {
15895 self.step35_tp_decode_attn_resident_v2(e, fa, il, h, pos_d, cache)
15896 } else {
15897 self.step35_tp_decode_attn_resident_inner(e, fa, il, h, pos_d, cache)
15898 };
15899 if let Some(started) = started {
15900 use std::sync::atomic::Ordering;
15901 let ns = ATTN_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
15902 + started.elapsed().as_nanos() as u64;
15903 let calls = ATTN_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
15904 if calls % 430 == 0 {
15905 eprintln!(
15906 "[step-tp-attn-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
15907 ns as f64 / 1.0e6,
15908 ns as f64 / calls as f64 / 1.0e3,
15909 );
15910 }
15911 }
15912 result
15913 }
15914
15915 #[allow(clippy::too_many_arguments)]
15916 fn step35_tp_decode_attn_resident_inner(
15917 &self,
15918 e: &Engine,
15919 fa: &FullAttnLayer,
15920 il: usize,
15921 h: &CudaSlice<f32>,
15922 pos_d: &CudaSlice<i32>,
15923 cache: &mut Cache,
15924 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15925 static T_POS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15930 static T_QKV: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15931 static T_NORMROPE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15932 static T_GATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15933 static T_APPEND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15934 static T_ATTN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15935 static T_OPROJ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15936 static T_SHADOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15937 static T_PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15938 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15939 fn lap(
15940 runtime: &crate::tp::TpE4m3HostBounce,
15941 e: &Engine,
15942 timer: &std::sync::atomic::AtomicU64,
15943 started: &mut Option<std::time::Instant>,
15944 ) -> Result<(), Box<dyn std::error::Error>> {
15945 let Some(start) = started.as_mut() else {
15946 return Ok(());
15947 };
15948 for rank in 0..runtime.devices().len() {
15949 if let Some(engine) = runtime.rank_engine(rank) {
15950 let _main = engine.gpu.enter_main()?;
15951 engine.stream().synchronize()?;
15952 }
15953 }
15954 e.stream().synchronize()?;
15955 timer.fetch_add(
15956 start.elapsed().as_nanos() as u64,
15957 std::sync::atomic::Ordering::Relaxed,
15958 );
15959 *start = std::time::Instant::now();
15960 Ok(())
15961 }
15962 let tp = fa
15963 .step_tp_qkv
15964 .as_ref()
15965 .ok_or("Step TP decode lost its resident projections")?;
15966 let attention = tp
15967 .attention
15968 .as_ref()
15969 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
15970 if !tp.runtime.native_p2p() {
15971 return Err("rank-local Step attention requires native P2P".into());
15972 }
15973 if crate::Engine::kv_fp8_on() {
15974 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
15975 }
15976
15977 let geometry = self.step35_geom(il);
15978 let window = geometry.window.map(|window| window as usize);
15979 let ranks = tp.runtime.devices().len();
15980 let head_dim = geometry.head_dim_k as usize;
15981 let heads = geometry.n_head as usize;
15982 let kv_heads = geometry.n_head_kv as usize;
15983 if heads % ranks != 0 || kv_heads % ranks != 0 {
15984 return Err(format!(
15985 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15986 )
15987 .into());
15988 }
15989 let local_heads = heads / ranks;
15990 let local_kv_heads = kv_heads / ranks;
15991 let local_kv_dim = local_kv_heads * head_dim;
15992 let max_ctx = cache.max_ctx;
15993
15994 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15995
15996 let base_len = cache.kv[il]
15997 .as_ref()
15998 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15999 .len;
16000 let distributed = cache.tp_kv[il]
16001 .as_ref()
16002 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
16003 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
16004 return Err(format!(
16005 "Step TP layer {il} cache lengths diverged before decode: \
16006 local={base_len} distributed={}/{}",
16007 distributed.committed_len(),
16008 distributed.staged_len()
16009 )
16010 .into());
16011 }
16012
16013 let mut lap_start = timing.then(std::time::Instant::now);
16014 let positions = e.dtoh_i32(pos_d)?;
16015 if positions.len() != 1 {
16016 return Err(format!(
16017 "rank-local Step decode requires one position, got {}",
16018 positions.len()
16019 )
16020 .into());
16021 }
16022 lap(&tp.runtime, e, &T_POS, &mut lap_start)?;
16023 let (q_raw, k_raw, v_raw, input_path) = if let Some(decode_input) =
16024 attention.decode_input.as_ref()
16025 {
16026 let mut decode_input = decode_input
16027 .lock()
16028 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16029 e.stream().synchronize()?;
16033 tp.runtime
16034 .refresh_replicated_device_rows_from_root(&mut decode_input, h)?;
16035 let q_raw = tp
16036 .runtime
16037 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &decode_input)?;
16038 let k_raw = tp
16039 .runtime
16040 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &decode_input)?;
16041 let v_raw = tp
16042 .runtime
16043 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &decode_input)?;
16044 (q_raw, k_raw, v_raw, "root-device-replicated")
16045 } else {
16046 let activation = e.dtoh(h)?;
16047 let q_raw =
16048 tp.runtime
16049 .bf16_column_parallel_resident_device_shards(&tp.q, &activation, 1)?;
16050 let k_raw =
16051 tp.runtime
16052 .bf16_column_parallel_resident_device_shards(&tp.k, &activation, 1)?;
16053 let v_raw =
16054 tp.runtime
16055 .bf16_column_parallel_resident_device_shards(&tp.v, &activation, 1)?;
16056 (q_raw, k_raw, v_raw, "host-replicated")
16057 };
16058 lap(&tp.runtime, e, &T_QKV, &mut lap_start)?;
16059 let mut q = Vec::with_capacity(ranks);
16060 let mut k = Vec::with_capacity(ranks);
16061 for rank in 0..ranks {
16062 let engine = tp
16063 .runtime
16064 .rank_engine(rank)
16065 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16066 let _main = engine.gpu.enter_main()?;
16067 let mut q_rank = engine.uninit(local_heads * head_dim)?;
16068 engine.rms_norm(
16069 &q_raw[rank],
16070 &attention.q_norm[rank],
16071 &mut q_rank,
16072 head_dim,
16073 local_heads,
16074 self.cfg.rms_eps,
16075 )?;
16076 let mut k_rank = engine.uninit(local_kv_dim)?;
16077 engine.rms_norm(
16078 &k_raw[rank],
16079 &attention.k_norm[rank],
16080 &mut k_rank,
16081 head_dim,
16082 local_kv_heads,
16083 self.cfg.rms_eps,
16084 )?;
16085 let position = engine.htod_i32(&positions)?;
16086 let rope_freqs = if geometry.rope_factors {
16087 self.step35_aux
16088 .as_ref()
16089 .and_then(|aux| aux.rope_freqs(engine))
16090 } else {
16091 None
16092 };
16093 engine.rope_neox2(
16094 &mut q_rank,
16095 &mut k_rank,
16096 &position,
16097 head_dim,
16098 geometry.n_rot as usize,
16099 local_heads,
16100 local_kv_heads,
16101 1,
16102 geometry.rope_base,
16103 1.0,
16104 rope_freqs,
16105 )?;
16106 q.push(q_rank);
16107 k.push(k_rank);
16108 }
16109 lap(&tp.runtime, e, &T_NORMROPE, &mut lap_start)?;
16110
16111 let gate_weight = fa
16112 .attn_gate
16113 .as_ref()
16114 .ok_or("step35 layer is missing attn_gate.weight")?;
16115 let gate = e.matmul(gate_weight, h, 1)?;
16116 let gate = e.dtoh(&gate)?;
16117 if gate.len() != heads {
16118 return Err(format!("Step TP layer {il} gate output {} != {heads}", gate.len()).into());
16119 }
16120 lap(&tp.runtime, e, &T_GATE, &mut lap_start)?;
16121
16122 let transaction = cache.tp_kv[il]
16123 .as_mut()
16124 .expect("distributed cache checked above")
16125 .begin_transaction()?;
16126 if let Err(error) = tp.runtime.append_tp_kv_transaction(
16127 cache.tp_kv[il]
16128 .as_mut()
16129 .expect("distributed cache checked above"),
16130 transaction,
16131 &k,
16132 &v_raw,
16133 1,
16134 ) {
16135 let _ = tp.runtime.rollback_tp_kv_transaction(
16136 cache.tp_kv[il]
16137 .as_mut()
16138 .expect("distributed cache checked above"),
16139 transaction,
16140 );
16141 return Err(error);
16142 }
16143 lap(&tp.runtime, e, &T_APPEND, &mut lap_start)?;
16144
16145 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16146 let distributed = cache.tp_kv[il]
16147 .as_ref()
16148 .expect("distributed cache checked above");
16149 let staged_len = distributed.staged_len();
16150 let view_start = window
16151 .map(|window| staged_len.saturating_sub(window))
16152 .unwrap_or(0);
16153 let physical = distributed.physical_range(view_start, staged_len)?;
16154 let t_kv = staged_len - view_start;
16155 let mut gated = Vec::with_capacity(ranks);
16156 for rank in 0..ranks {
16157 let engine = tp
16158 .runtime
16159 .rank_engine(rank)
16160 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16161 let _main = engine.gpu.enter_main()?;
16162 let rank_cache = distributed
16163 .rank(rank)
16164 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16165 let k_view = engine.view_u8_range(
16166 rank_cache.k(),
16167 physical.start * distributed.k_tok_bytes(),
16168 physical.end * distributed.k_tok_bytes(),
16169 );
16170 let v_view = engine.view_u8_range(
16171 rank_cache.v(),
16172 physical.start * distributed.v_tok_bytes(),
16173 physical.end * distributed.v_tok_bytes(),
16174 );
16175 let mut attention_out = engine.uninit(local_heads * head_dim)?;
16176 engine.fa_decode_kvmod(
16177 &q[rank],
16178 &k_view,
16179 &v_view,
16180 &mut attention_out,
16181 head_dim,
16182 local_heads,
16183 local_kv_heads,
16184 t_kv,
16185 geometry.attention_scale(),
16186 distributed.k_tok_bytes(),
16187 distributed.v_tok_bytes(),
16188 false,
16189 )?;
16190 let gate_start = rank * local_heads;
16191 let gate_rank = engine.htod(&gate[gate_start..gate_start + local_heads])?;
16192 let mut gated_rank = engine.uninit(local_heads * head_dim)?;
16193 engine.attn_head_gate(
16194 &attention_out,
16195 &gate_rank,
16196 &mut gated_rank,
16197 None,
16198 head_dim,
16199 local_heads,
16200 1,
16201 )?;
16202 gated.push(gated_rank);
16203 }
16204 lap(&tp.runtime, e, &T_ATTN, &mut lap_start)?;
16205
16206 let gathered =
16207 tp.runtime
16208 .gather_native_column_shards(&gated, 1, local_heads * head_dim)?;
16209 let output = tp
16210 .runtime
16211 .step_bf16_row_parallel_resident_native(&tp.o, &gathered, 1)?;
16212 let output = e.htod(&output)?;
16213 lap(&tp.runtime, e, &T_OPROJ, &mut lap_start)?;
16214
16215 let k_shadow = tp
16216 .runtime
16217 .gather_native_column_shards(&k, 1, local_kv_dim)?;
16218 let v_shadow = tp
16219 .runtime
16220 .gather_native_column_shards(&v_raw, 1, local_kv_dim)?;
16221 let k_shadow = e.htod(&k_shadow)?;
16222 let v_shadow = e.htod(&v_shadow)?;
16223 let local = cache.kv[il]
16224 .as_mut()
16225 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16226 if local.len != base_len || base_len + 1 > max_ctx {
16227 return Err(format!(
16228 "Step TP layer {il} local cache changed during decode: \
16229 len={} base={base_len} max={max_ctx}",
16230 local.len
16231 )
16232 .into());
16233 }
16234 let retain_from = window
16235 .map(|window| {
16236 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16237 let rollback_retain =
16238 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16239 staged_retain.min(rollback_retain)
16240 })
16241 .unwrap_or(0);
16242 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16243 e.append_kv_quantized(
16244 &k_shadow,
16245 &v_shadow,
16246 &mut local.k,
16247 &mut local.v,
16248 write_row,
16249 local.kv_dim_k,
16250 local.kv_dim_v,
16251 local.k_tok_bytes,
16252 local.v_tok_bytes,
16253 false,
16254 )?;
16255 local.len = base_len + 1;
16256 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16257 Ok(output)
16258 })();
16259
16260 let output = match staged {
16261 Ok(output) => output,
16262 Err(error) => {
16263 let _ = tp.runtime.rollback_tp_kv_transaction(
16264 cache.tp_kv[il]
16265 .as_mut()
16266 .expect("distributed cache checked above"),
16267 transaction,
16268 );
16269 if let Some(local) = cache.kv[il].as_mut() {
16270 local.len = base_len;
16271 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16272 }
16273 return Err(error);
16274 }
16275 };
16276 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16277 cache.tp_kv[il]
16278 .as_mut()
16279 .expect("distributed cache checked above"),
16280 transaction,
16281 1,
16282 ) {
16283 let _ = tp.runtime.rollback_tp_kv_transaction(
16284 cache.tp_kv[il]
16285 .as_mut()
16286 .expect("distributed cache checked above"),
16287 transaction,
16288 );
16289 let local = cache.kv[il].as_mut().expect("local cache checked above");
16290 local.len = base_len;
16291 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16292 return Err(error);
16293 }
16294
16295 let committed = cache.tp_kv[il]
16296 .as_ref()
16297 .expect("distributed cache checked above")
16298 .committed_len();
16299 let local_len = cache.kv[il]
16300 .as_ref()
16301 .expect("local cache checked above")
16302 .len;
16303 if committed != local_len {
16304 return Err(format!(
16305 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16306 )
16307 .into());
16308 }
16309 lap(&tp.runtime, e, &T_SHADOW, &mut lap_start)?;
16310 if timing {
16311 use std::sync::atomic::Ordering;
16312 let calls = T_PHASE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16313 if calls % 430 == 0 {
16314 let avg = |t: &std::sync::atomic::AtomicU64| {
16315 t.load(Ordering::Relaxed) as f64 / calls as f64 / 1.0e3
16316 };
16317 eprintln!(
16318 "[step-tp-attn-phase] calls={calls} avg_us pos={:.1} qkv={:.1} \
16319 normrope={:.1} gate={:.1} append={:.1} attn={:.1} oproj={:.1} shadow={:.1}",
16320 avg(&T_POS),
16321 avg(&T_QKV),
16322 avg(&T_NORMROPE),
16323 avg(&T_GATE),
16324 avg(&T_APPEND),
16325 avg(&T_ATTN),
16326 avg(&T_OPROJ),
16327 avg(&T_SHADOW),
16328 );
16329 }
16330 }
16331 eprintln!(
16332 "[step-tp-attn] execute layer={} devices={:?} tokens=1 \
16333 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16334 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16335 attention_scope={} input_path={} kv_physical_rows={} \
16336 gate_tensor_parallel=false gate_shards=host-canonical o_tensor_parallel=true \
16337 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16338 bulk_p2p={} output=root-readback performance_claim=false",
16339 tp.layer,
16340 tp.devices,
16341 hydrated,
16342 if window.is_some() {
16343 "rank-local-swa-ring"
16344 } else {
16345 "rank-local-global"
16346 },
16347 input_path,
16348 cache.tp_kv[il]
16349 .as_ref()
16350 .expect("distributed cache checked above")
16351 .physical_capacity(),
16352 tp.runtime.transport_label(),
16353 tp.runtime.bulk_p2p(),
16354 );
16355 Ok(output)
16356 }
16357
16358 #[allow(clippy::too_many_arguments)]
16365 pub(crate) fn step35_verify_qkv_precompute(
16370 &self,
16371 e: &Engine,
16372 il: usize,
16373 h_t: &CudaSlice<f32>,
16374 t: usize,
16375 ) -> Result<bool, Box<dyn std::error::Error>> {
16376 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16377 return Ok(false);
16378 };
16379 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16380 return Ok(false);
16381 };
16382 let Some(attention) = tp.attention.as_ref() else {
16383 return Ok(false);
16384 };
16385 if !tp.runtime.native_p2p() || !crate::tp::step_tp_qkv_fused_enabled()? {
16386 return Ok(false);
16387 }
16388 let geometry = self.step35_geom(il);
16389 let heads = geometry.n_head as usize;
16390 let ws_index = tp
16391 .runtime
16392 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16393 let gate_shards = attention
16394 .gate_shards_bf16
16395 .as_deref()
16396 .map(crate::tp::StepTpGateShards::Bf16);
16397 tp.runtime.decode_v2_input_qkv_tcol(
16398 ws_index,
16399 e,
16400 h_t,
16401 t,
16402 &tp.q,
16403 &tp.k,
16404 &tp.v,
16405 gate_shards,
16406 )?;
16407 Ok(true)
16408 }
16409
16410 pub(crate) fn step35_verify_oproj_tcol(
16415 &self,
16416 e: &Engine,
16417 il: usize,
16418 t: usize,
16419 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16420 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16421 return Err("tcol o_proj join expects full attention".into());
16422 };
16423 let tp = fa
16424 .step_tp_qkv
16425 .as_ref()
16426 .ok_or("tcol o_proj join lost its resident projections")?;
16427 let heads = self.step35_geom(il).n_head as usize;
16428 let ws_index = tp
16429 .runtime
16430 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16431 tp.runtime.decode_v2_oproj_tcol(ws_index, e, &tp.o, t)
16432 }
16433
16434 pub(crate) fn step35_spec_fa2_precheck(
16441 &self,
16442 cache: &Cache,
16443 il: usize,
16444 pos0: usize,
16445 ) -> Result<bool, Box<dyn std::error::Error>> {
16446 fn nope(clause: &str, il: usize, pos0: usize) -> bool {
16449 static DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16450 static SEEN: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
16451 if *DBG.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_DEBUG").as_deref() == Ok("1")) {
16452 let mut seen = SEEN.lock().unwrap();
16453 if !seen.iter().any(|c| *c == clause) {
16454 seen.push(Box::leak(clause.to_string().into_boxed_str()));
16456 eprintln!("[spec-fa2] precheck FAIL clause={clause} il={il} pos0={pos0}");
16457 }
16458 }
16459 false
16460 }
16461 static ONLY: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
16463 if let Some(only) =
16464 ONLY.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_LAYER").ok()?.parse().ok())
16465 {
16466 if *only != il {
16467 return Ok(false);
16468 }
16469 }
16470 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16471 return Ok(nope("mixer", il, pos0));
16472 };
16473 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16474 return Ok(nope("step_tp", il, pos0));
16475 };
16476 let Some(attention) = tp.attention.as_ref() else {
16477 return Ok(nope("attention", il, pos0));
16478 };
16479 if !tp.runtime.native_p2p()
16480 || crate::Engine::kv_fp8_on()
16481 || !crate::tp::step_tp_dcw_enabled()?
16482 || !crate::tp::step_tp_qkv_fused_enabled()?
16483 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16484 {
16485 return Ok(nope("runtime-doors", il, pos0));
16486 }
16487 let geometry = self.step35_geom(il);
16488 let head_dim = geometry.head_dim_k as usize;
16489 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16490 return Ok(nope("fa-class", il, pos0));
16491 }
16492 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16493 return Ok(nope("tp-kv", il, pos0));
16494 };
16495 if distributed.staged_len() != pos0 {
16496 return Ok(nope("staged-len", il, pos0));
16497 }
16498 let (_, would_rebase) = distributed.peek_append_ring(2)?;
16501 if would_rebase {
16502 return Ok(nope("rebase", il, pos0));
16503 }
16504 let window = geometry.window.map(|w| w as usize);
16505 if let Some(w) = window {
16513 if pos0 + 2 > w {
16514 return Ok(nope("swa-capped", il, pos0));
16515 }
16516 }
16517 let (t0, t1) = (pos0 + 1, pos0 + 2);
16521 if t0 < 96 {
16522 return Ok(nope("dcw-floor", il, pos0));
16523 }
16524 if std::env::var("MEMRA_NO_FA_VEC").is_ok() || t0 < crate::fa_vec_min_tkv() {
16525 return Ok(nope("vec-floor", il, pos0));
16526 }
16527 let ranks = tp.runtime.devices().len();
16533 let local_kv_heads = (geometry.n_head_kv as usize / ranks).max(1);
16534 let sp0 = crate::fa_split_keys_pub(t0, local_kv_heads);
16535 let sp1 = crate::fa_split_keys_pub(t1, local_kv_heads);
16536 if sp0 != sp1 {
16537 return Ok(nope("partition-sp", il, pos0));
16538 }
16539 let (ns0, ns1) = (t0.div_ceil(sp0), t1.div_ceil(sp1));
16540 if ns0 != ns1 {
16541 return Ok(nope("partition-ns", il, pos0));
16542 }
16543 if t0.div_ceil(ns0) != t1.div_ceil(ns1) {
16544 return Ok(nope("partition-per", il, pos0));
16545 }
16546 Ok(true)
16547 }
16548
16549 pub(crate) fn step35_fa_rows_precheck(
16554 &self,
16555 cache: &Cache,
16556 il: usize,
16557 pos0: usize,
16558 t: usize,
16559 ) -> Result<bool, Box<dyn std::error::Error>> {
16560 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16561 return Ok(false);
16562 };
16563 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16564 return Ok(false);
16565 };
16566 let Some(attention) = tp.attention.as_ref() else {
16567 return Ok(false);
16568 };
16569 if !tp.runtime.native_p2p()
16570 || crate::Engine::kv_fp8_on()
16571 || !crate::tp::step_tp_dcw_enabled()?
16572 || !crate::tp::step_tp_qkv_fused_enabled()?
16573 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16574 {
16575 return Ok(false);
16576 }
16577 let geometry = self.step35_geom(il);
16578 let head_dim = geometry.head_dim_k as usize;
16579 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16580 return Ok(false);
16581 }
16582 if crate::fa_sm_count() < 128
16583 || std::env::var("MEMRA_FA_SPLIT").is_ok()
16584 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
16585 || std::env::var("MEMRA_FA_SP16").is_ok()
16586 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
16587 {
16588 return Ok(false);
16589 }
16590 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16591 return Ok(false);
16592 };
16593 if distributed.staged_len() != pos0 {
16594 return Ok(false);
16595 }
16596 let (_, would_rebase) = distributed.peek_append_ring(t)?;
16597 if would_rebase {
16598 return Ok(false);
16599 }
16600 let window = geometry.window.map(|w| w as usize);
16603 let t0 = window.map(|w| (pos0 + 1).min(w)).unwrap_or(pos0 + 1);
16604 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
16605 return Ok(false);
16606 }
16607 Ok(true)
16608 }
16609
16610 pub(crate) fn step35_verify_fa_rows_join(
16614 &self,
16615 e: &Engine,
16616 il: usize,
16617 cache: &Cache,
16618 pos0: usize,
16619 t: usize,
16620 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16621 use cudarc::driver::DevicePtr;
16622 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16623 return Err("fa rows join expects full attention".into());
16624 };
16625 let tp = fa
16626 .step_tp_qkv
16627 .as_ref()
16628 .ok_or("fa rows join lost its resident projections")?;
16629 let geometry = self.step35_geom(il);
16630 let heads = geometry.n_head as usize;
16631 let head_dim = geometry.head_dim_k as usize;
16632 let window = geometry.window.map(|w| w as usize);
16633 let distributed = cache.tp_kv[il]
16634 .as_ref()
16635 .ok_or("fa rows join lost its distributed KV cache")?;
16636 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
16637 let ladder = |t_kv: usize| -> usize {
16639 if t_kv <= 2048 {
16640 16
16641 } else if t_kv <= 16384 {
16642 64
16643 } else {
16644 128
16645 }
16646 };
16647 let mut max_ns = 1usize;
16648 for r in 0..t {
16649 let t_kv = window
16650 .map(|w| (pos0 + r + 1).min(w))
16651 .unwrap_or(pos0 + r + 1);
16652 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
16653 }
16654 let ranks = tp.runtime.devices().len();
16659 let mut tables = Vec::with_capacity(ranks);
16660 for rank in 0..ranks {
16661 let engine = tp
16662 .runtime
16663 .rank_engine(rank)
16664 .ok_or("fa rows join lost a rank engine")?;
16665 let rank_cache = distributed
16666 .rank(rank)
16667 .ok_or("fa rows join lost a KV cache rank")?;
16668 let _main = engine.gpu.enter_main()?;
16669 let s = engine.stream();
16670 let (kp, _g0) = rank_cache.k().device_ptr(&s);
16671 let (vp, _g1) = rank_cache.v().device_ptr(&s);
16672 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
16673 let bp = match rank_cache.base_d() {
16674 Some(b) => {
16675 let (p, _g) = b.device_ptr(&s);
16676 p as u64
16677 }
16678 None => 0u64,
16679 };
16680 let mut host = Vec::with_capacity(t * 6);
16681 for r in 0..t {
16682 host.extend_from_slice(&[
16683 kp as u64,
16684 vp as u64,
16685 lp as u64,
16686 bp,
16687 0u64,
16688 (t - 1 - r) as u64,
16689 ]);
16690 }
16691 tables.push(engine.stream().clone_htod(&host)?);
16692 }
16693 let tabs: Vec<&CudaSlice<u64>> = tables.iter().collect();
16694 let ws_index = tp
16695 .runtime
16696 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16697 tp.runtime.decode_v2_fa_rows_join(
16698 ws_index,
16699 e,
16700 &tp.o,
16701 &tabs,
16702 t,
16703 head_dim,
16704 window.unwrap_or(0),
16705 max_ns,
16706 geometry.attention_scale(),
16707 k_tok_bytes,
16708 v_tok_bytes,
16709 )
16710 }
16711
16712 pub(crate) fn step35_batch_fa_rows_precheck(
16716 &self,
16717 caches: &[&mut Cache],
16718 row_to_cache: impl Fn(usize) -> usize,
16719 positions: &[i32],
16720 il: usize,
16721 ) -> Result<bool, Box<dyn std::error::Error>> {
16722 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16723 return Ok(false);
16724 };
16725 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16726 return Ok(false);
16727 };
16728 let Some(attention) = tp.attention.as_ref() else {
16729 return Ok(false);
16730 };
16731 if !tp.runtime.native_p2p()
16732 || crate::Engine::kv_fp8_on()
16733 || !crate::tp::step_tp_dcw_enabled()?
16734 || !crate::tp::step_tp_qkv_fused_enabled()?
16735 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16736 {
16737 return Ok(false);
16738 }
16739 let geometry = self.step35_geom(il);
16740 let head_dim = geometry.head_dim_k as usize;
16741 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16742 return Ok(false);
16743 }
16744 if crate::fa_sm_count() < 128
16745 || std::env::var("MEMRA_FA_SPLIT").is_ok()
16746 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
16747 || std::env::var("MEMRA_FA_SP16").is_ok()
16748 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
16749 {
16750 return Ok(false);
16751 }
16752 let window = geometry.window.map(|w| w as usize);
16753 for (r, &pos) in positions.iter().enumerate() {
16754 let cache = &caches[row_to_cache(r)];
16755 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16756 return Ok(false);
16757 };
16758 if distributed.staged_len() != pos as usize {
16759 return Ok(false);
16760 }
16761 if distributed.peek_append_ring(1)?.1 {
16762 return Ok(false);
16763 }
16764 let t0 = window
16765 .map(|w| (pos as usize + 1).min(w))
16766 .unwrap_or(pos as usize + 1);
16767 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
16768 return Ok(false);
16769 }
16770 }
16771 Ok(true)
16772 }
16773
16774 pub(crate) fn step35_verify_rope_fa_pass(
16780 &self,
16781 e: &Engine,
16782 il: usize,
16783 cache: &Cache,
16784 pos0: usize,
16785 t: usize,
16786 stage_pos: bool,
16787 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16788 use cudarc::driver::DevicePtr;
16789 if !crate::tp::fuse_rope_append_on() {
16790 return Ok(None);
16791 }
16792 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16793 return Ok(None);
16794 };
16795 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16796 return Ok(None);
16797 };
16798 let Some(attention) = tp.attention.as_ref() else {
16799 return Ok(None);
16800 };
16801 let geometry = self.step35_geom(il);
16802 let head_dim = geometry.head_dim_k as usize;
16803 if head_dim != 128 {
16804 return Ok(None);
16805 }
16806 let heads = geometry.n_head as usize;
16807 let window = geometry.window.map(|w| w as usize);
16808 let ranks = tp.runtime.devices().len();
16809 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16810 return Ok(None);
16811 };
16812 if distributed.kv_dim_k() != distributed.kv_dim_v() {
16813 return Ok(None);
16814 }
16815 {
16816 let rank0 = distributed.rank(0).ok_or("verify rope pass lost rank 0")?;
16817 if rank0.base_d().is_none()
16818 && distributed.staged_len() + t > distributed.physical_capacity()
16819 {
16820 return Ok(None);
16821 }
16822 }
16823 let mut rope_freqs = Vec::with_capacity(ranks);
16824 for rank in 0..ranks {
16825 let engine = tp
16826 .runtime
16827 .rank_engine(rank)
16828 .ok_or("verify rope pass lost a rank engine")?;
16829 rope_freqs.push(if geometry.rope_factors {
16830 match self
16831 .step35_aux
16832 .as_ref()
16833 .and_then(|aux| aux.rope_freqs(engine))
16834 {
16835 Some(f) => Some(f),
16836 None => return Ok(None),
16837 }
16838 } else {
16839 None
16840 });
16841 }
16842 let ladder = |t_kv: usize| -> usize {
16843 if t_kv <= 2048 {
16844 16
16845 } else if t_kv <= 16384 {
16846 64
16847 } else {
16848 128
16849 }
16850 };
16851 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
16852 let mut max_ns = 1usize;
16853 let mut positions = Vec::with_capacity(t);
16854 for r in 0..t {
16855 positions.push((pos0 + r) as i32);
16856 let t_kv = window
16857 .map(|w| (pos0 + r + 1).min(w))
16858 .unwrap_or(pos0 + r + 1);
16859 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
16860 }
16861 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
16862 let mut tab_keys = vec![0u64; ranks];
16863 for rank in 0..ranks {
16864 let engine = tp
16865 .runtime
16866 .rank_engine(rank)
16867 .ok_or("verify rope pass lost a rank engine")?;
16868 let rank_cache = distributed
16869 .rank(rank)
16870 .ok_or("verify rope pass lost a KV cache rank")?;
16871 let _main = engine.gpu.enter_main()?;
16872 let s = engine.stream();
16873 let (kp, _g0) = rank_cache.k().device_ptr(&s);
16874 let (vp, _g1) = rank_cache.v().device_ptr(&s);
16875 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
16876 let bp = match rank_cache.base_d() {
16877 Some(b) => {
16878 let (p, _g) = b.device_ptr(&s);
16879 p as u64
16880 }
16881 None => 0u64,
16882 };
16883 tab_keys[rank] = (kp as u64)
16884 .rotate_left(17)
16885 .wrapping_add(bp)
16886 .wrapping_add((il as u64) << 32)
16887 .wrapping_add(t as u64)
16888 .wrapping_add(1 << 63);
16889 for _r in 0..t {
16890 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
16891 }
16892 }
16893 let ws_index = tp
16894 .runtime
16895 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16896 tp.runtime
16897 .decode_v2_rope_fa_rows(
16898 ws_index,
16899 e,
16900 &tp.o,
16901 &session_parts,
16902 &tab_keys,
16903 &positions,
16904 stage_pos,
16905 true,
16906 &attention.q_norm,
16907 &attention.k_norm,
16908 &rope_freqs,
16909 t,
16910 head_dim,
16911 geometry.n_rot as usize,
16912 window.unwrap_or(0),
16913 max_ns,
16914 geometry.attention_scale(),
16915 k_tok_bytes,
16916 v_tok_bytes,
16917 self.cfg.rms_eps,
16918 geometry.rope_base,
16919 )
16920 .map(Some)
16921 }
16922
16923 #[allow(clippy::too_many_arguments)]
16928 pub(crate) fn step35_batch_rope_fa_pass(
16929 &self,
16930 e: &Engine,
16931 il: usize,
16932 caches: &[&mut Cache],
16933 row_to_cache: impl Fn(usize) -> usize,
16934 positions: &[i32],
16935 t: usize,
16936 stage_pos: bool,
16937 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16938 use cudarc::driver::DevicePtr;
16939 if !crate::tp::fuse_rope_append_on() {
16940 return Ok(None);
16941 }
16942 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16943 return Ok(None);
16944 };
16945 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16946 return Ok(None);
16947 };
16948 let Some(attention) = tp.attention.as_ref() else {
16949 return Ok(None);
16950 };
16951 let geometry = self.step35_geom(il);
16952 let head_dim = geometry.head_dim_k as usize;
16953 if head_dim != 128 {
16954 return Ok(None);
16955 }
16956 let heads = geometry.n_head as usize;
16957 let window = geometry.window.map(|w| w as usize);
16958 let ranks = tp.runtime.devices().len();
16959 for r in 0..t {
16962 let cache = &caches[row_to_cache(r)];
16963 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16964 return Ok(None);
16965 };
16966 if distributed.kv_dim_k() != distributed.kv_dim_v() {
16967 return Ok(None);
16968 }
16969 let rank0 = distributed.rank(0).ok_or("rope fa pass lost rank 0")?;
16970 if rank0.base_d().is_none()
16971 && distributed.staged_len() + t > distributed.physical_capacity()
16972 {
16973 return Ok(None);
16974 }
16975 }
16976 let mut rope_freqs = Vec::with_capacity(ranks);
16977 for rank in 0..ranks {
16978 let engine = tp
16979 .runtime
16980 .rank_engine(rank)
16981 .ok_or("rope fa pass lost a rank engine")?;
16982 rope_freqs.push(if geometry.rope_factors {
16983 match self
16984 .step35_aux
16985 .as_ref()
16986 .and_then(|aux| aux.rope_freqs(engine))
16987 {
16988 Some(f) => Some(f),
16989 None => return Ok(None),
16990 }
16991 } else {
16992 None
16993 });
16994 }
16995 let ladder = |t_kv: usize| -> usize {
16996 if t_kv <= 2048 {
16997 16
16998 } else if t_kv <= 16384 {
16999 64
17000 } else {
17001 128
17002 }
17003 };
17004 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17005 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
17006 let mut tab_keys = vec![0u64; ranks];
17007 for (r, &pos) in positions.iter().enumerate().take(t) {
17008 let cache = &caches[row_to_cache(r)];
17009 let distributed = cache.tp_kv[il]
17010 .as_ref()
17011 .ok_or("rope fa pass lost a distributed KV cache")?;
17012 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17013 let t_kv = window
17014 .map(|w| (pos as usize + 1).min(w))
17015 .unwrap_or(pos as usize + 1);
17016 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17017 for rank in 0..ranks {
17018 let engine = tp
17019 .runtime
17020 .rank_engine(rank)
17021 .ok_or("rope fa pass lost a rank engine")?;
17022 let rank_cache = distributed
17023 .rank(rank)
17024 .ok_or("rope fa pass lost a KV cache rank")?;
17025 let _main = engine.gpu.enter_main()?;
17026 let s = engine.stream();
17027 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17028 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17029 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17030 let bp = match rank_cache.base_d() {
17031 Some(b) => {
17032 let (p, _g) = b.device_ptr(&s);
17033 p as u64
17034 }
17035 None => 0u64,
17036 };
17037 tab_keys[rank] = tab_keys[rank]
17038 .rotate_left(9)
17039 .wrapping_add(kp as u64)
17040 .wrapping_add(bp)
17041 .wrapping_add(il as u64);
17042 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
17043 }
17044 }
17045 let ws_index = tp
17046 .runtime
17047 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17048 tp.runtime
17049 .decode_v2_rope_fa_rows(
17050 ws_index,
17051 e,
17052 &tp.o,
17053 &session_parts,
17054 &tab_keys,
17055 positions,
17056 stage_pos,
17057 false,
17058 &attention.q_norm,
17059 &attention.k_norm,
17060 &rope_freqs,
17061 t,
17062 head_dim,
17063 geometry.n_rot as usize,
17064 window.unwrap_or(0),
17065 max_ns,
17066 geometry.attention_scale(),
17067 k_tok_bytes,
17068 v_tok_bytes,
17069 self.cfg.rms_eps,
17070 geometry.rope_base,
17071 )
17072 .map(Some)
17073 }
17074
17075 #[allow(clippy::too_many_arguments)]
17079 pub(crate) fn step35_batch_fa_rows_join(
17080 &self,
17081 e: &Engine,
17082 il: usize,
17083 caches: &[&mut Cache],
17084 row_to_cache: impl Fn(usize) -> usize,
17085 positions: &[i32],
17086 t: usize,
17087 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17088 use cudarc::driver::DevicePtr;
17089 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17090 return Err("batch fa rows join expects full attention".into());
17091 };
17092 let tp = fa
17093 .step_tp_qkv
17094 .as_ref()
17095 .ok_or("batch fa rows join lost its resident projections")?;
17096 let geometry = self.step35_geom(il);
17097 let heads = geometry.n_head as usize;
17098 let head_dim = geometry.head_dim_k as usize;
17099 let window = geometry.window.map(|w| w as usize);
17100 let ladder = |t_kv: usize| -> usize {
17101 if t_kv <= 2048 {
17102 16
17103 } else if t_kv <= 16384 {
17104 64
17105 } else {
17106 128
17107 }
17108 };
17109 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17110 for (r, &pos) in positions.iter().enumerate() {
17111 let cache = &caches[row_to_cache(r)];
17112 let distributed = cache.tp_kv[il]
17113 .as_ref()
17114 .ok_or("batch fa rows join lost a distributed KV cache")?;
17115 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17116 let t_kv = window
17117 .map(|w| (pos as usize + 1).min(w))
17118 .unwrap_or(pos as usize + 1);
17119 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17120 }
17121 let ranks = tp.runtime.devices().len();
17125 let mut tables = Vec::with_capacity(ranks);
17126 for rank in 0..ranks {
17127 let engine = tp
17128 .runtime
17129 .rank_engine(rank)
17130 .ok_or("batch fa rows join lost a rank engine")?;
17131 let _main = engine.gpu.enter_main()?;
17132 let s = engine.stream();
17133 let mut host = Vec::with_capacity(t * 6);
17134 for r in 0..t {
17135 let cache = &caches[row_to_cache(r)];
17136 let distributed = cache.tp_kv[il]
17137 .as_ref()
17138 .ok_or("batch fa rows join lost a distributed KV cache")?;
17139 let rank_cache = distributed
17140 .rank(rank)
17141 .ok_or("batch fa rows join lost a KV cache rank")?;
17142 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17143 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17144 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17145 let bp = match rank_cache.base_d() {
17146 Some(b) => {
17147 let (p, _g) = b.device_ptr(&s);
17148 p as u64
17149 }
17150 None => 0u64,
17151 };
17152 host.extend_from_slice(&[kp as u64, vp as u64, lp as u64, bp, 0u64, 0u64]);
17153 }
17154 tables.push(engine.stream().clone_htod(&host)?);
17155 }
17156 let tabs: Vec<&CudaSlice<u64>> = tables.iter().collect();
17157 let ws_index = tp
17158 .runtime
17159 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17160 tp.runtime.decode_v2_fa_rows_join(
17161 ws_index,
17162 e,
17163 &tp.o,
17164 &tabs,
17165 t,
17166 head_dim,
17167 window.unwrap_or(0),
17168 max_ns,
17169 geometry.attention_scale(),
17170 k_tok_bytes,
17171 v_tok_bytes,
17172 )
17173 }
17174
17175 pub(crate) fn step35_verify_spec_fa2_join(
17179 &self,
17180 e: &Engine,
17181 il: usize,
17182 cache: &Cache,
17183 pos0: usize,
17184 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17185 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17186 return Err("spec fa2 join expects full attention".into());
17187 };
17188 let tp = fa
17189 .step_tp_qkv
17190 .as_ref()
17191 .ok_or("spec fa2 join lost its resident projections")?;
17192 let geometry = self.step35_geom(il);
17193 let heads = geometry.n_head as usize;
17194 let head_dim = geometry.head_dim_k as usize;
17195 let window = geometry.window.map(|w| w as usize);
17196 let bucket = window.map(|w| (pos0 + 2).min(w)).unwrap_or(pos0 + 2);
17199 let distributed = cache.tp_kv[il]
17200 .as_ref()
17201 .ok_or("spec fa2 join lost its distributed KV cache")?;
17202 let ws_index = tp
17203 .runtime
17204 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17205 tp.runtime.decode_v2_spec_fa2_join(
17206 ws_index,
17207 e,
17208 &tp.o,
17209 distributed,
17210 head_dim,
17211 window.unwrap_or(0),
17212 bucket,
17213 geometry.attention_scale(),
17214 )
17215 }
17216
17217 pub(crate) fn step35_verify_moe_tn(
17223 &self,
17224 e: &Engine,
17225 il: usize,
17226 z_t: &CudaSlice<f32>,
17227 t: usize,
17228 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
17229 let layer = &self.layers[il];
17230 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
17231 return Ok(None);
17232 };
17233 let Some(tp) = m.step_tp.as_ref() else {
17234 return Ok(None);
17235 };
17236 let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts else {
17237 return Ok(None);
17238 };
17239 if !crate::tp::step_nvfp4_dev_routes_enabled()?
17240 || !crate::tp::step_tp_dev_router_enabled()?
17241 || !crate::tp::nvfp4_bank_v2_on()
17242 || bank.ep2
17243 {
17244 return Ok(None);
17245 }
17246 let cfg = &self.cfg;
17247 let Some(moe) = cfg.moe.as_ref() else {
17248 return Ok(None);
17249 };
17250 let Some((sf, route_norm)) = cfg.sigmoid_router() else {
17251 return Ok(None);
17252 };
17253 let n_embd = cfg.n_embd as usize;
17254 let n_expert = moe.expert_count as usize;
17255 let n_used = moe.expert_used_count as usize;
17256 if t < 2 || t > 32 || z_t.len() < t * n_embd {
17257 return Err("verify moe t-row geometry".into());
17258 }
17259 let trace = std::env::var("MEMRA_TN_TRACE").as_deref() == Ok("1");
17260 if trace {
17261 eprintln!("[tn-trace] il={il} t={t} logits");
17262 }
17263 let logits = Self::moe_router_logits(e, m, z_t, t, cfg)?;
17264 static SELW2: std::sync::Mutex<Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>> =
17267 std::sync::Mutex::new(None);
17268 let mut selw = SELW2.lock().map_err(|_| "selw2 lock poisoned")?;
17269 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
17270 *selw = Some((
17271 e.ctx().ordinal(),
17272 e.htod_i32(&vec![0i32; 32 * n_used])?,
17273 e.htod(&vec![0.0f32; 32 * n_used])?,
17274 ));
17275 }
17276 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
17277 if trace {
17278 eprintln!("[tn-trace] il={il} topk logits_len={}", logits.len());
17279 }
17280 e.moe_router_sigmoid_topk_into(
17281 &logits,
17282 t,
17283 n_expert,
17284 n_used,
17285 m.active_count(),
17286 &m.exp_probs_b_dev,
17287 &m.active_experts_dev,
17288 sf,
17289 route_norm,
17290 sel_d,
17291 w_d,
17292 )?;
17293 if trace {
17294 eprintln!("[tn-trace] il={il} driver");
17295 }
17296 let mut out_t = tp
17297 .runtime
17298 .run_tensor_parallel_routes_nvfp4_device_routed_tn(
17299 bank,
17300 e,
17301 z_t,
17302 sel_d,
17303 w_d,
17304 t,
17305 n_used,
17306 tp.activation_limit,
17307 )?;
17308 if trace {
17309 eprintln!("[tn-trace] il={il} shexp out_t={}", out_t.len());
17310 }
17311 if !Self::step35_shexp_rows(e, m, z_t, t, cfg, il as u16, &mut out_t)? {
17315 let mut z_row = e.uninit(n_embd)?;
17316 let mut out_row = e.uninit(n_embd)?;
17317 for c in 0..t {
17318 e.dtod_copy_view(&z_t.slice(c * n_embd..(c + 1) * n_embd), &mut z_row)?;
17319 e.dtod_copy_view(&out_t.slice(c * n_embd..(c + 1) * n_embd), &mut out_row)?;
17320 Self::moe_ffn_grouped_add_shared(e, m, &z_row, 1, cfg, il as u16, &mut out_row)?;
17321 e.dtod_copy_into(&out_row, &mut out_t, c * n_embd)?;
17322 }
17323 }
17324 Ok(Some(out_t))
17325 }
17326
17327 fn step35_shexp_rows(
17331 e: &Engine,
17332 m: &MoeWeights,
17333 z_t: &CudaSlice<f32>,
17334 t: usize,
17335 cfg: &ModelConfig,
17336 il: u16,
17337 out_t: &mut CudaSlice<f32>,
17338 ) -> Result<bool, Box<dyn std::error::Error>> {
17339 let n_embd = cfg.n_embd as usize;
17340 let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
17341 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
17342 else {
17343 return Ok(false);
17344 };
17345 if !crate::Engine::bf16_mmv_on() || n_embd % 8 != 0 || cfg.m3.is_some() {
17346 return Ok(false);
17347 }
17348 let (
17349 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
17350 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
17351 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
17352 ) = (gate_shexp, up_shexp, down_shexp)
17353 else {
17354 return Ok(false);
17355 };
17356 let n_ff_sh = gate_shexp.out_features();
17357 let lim = cfg.clamp_shexp_at(il as u32);
17358 static WS: std::sync::Mutex<Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>> =
17360 std::sync::Mutex::new(None);
17361 let mut guard = WS.lock().map_err(|_| "shexp rows ws lock is poisoned")?;
17362 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
17363 if guard
17364 .as_ref()
17365 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
17366 {
17367 *guard = Some((
17368 pins.0,
17369 pins.1,
17370 pins.2,
17371 e.uninit(32 * n_ff_sh)?,
17372 e.uninit(32 * n_embd)?,
17373 ));
17374 }
17375 let (_, _, _, act_t, sh_t) = guard.as_mut().expect("armed above");
17376 e.matvec_bf16_dual_silu_rows_into(wg, wu, z_t, act_t, n_embd, n_ff_sh, lim, t)?;
17377 e.matvec_bf16_rows_into(wd, act_t, sh_t, n_ff_sh, n_embd, t)?;
17378 let gate = match &m.gate_inp_shexp {
17381 Some(gate_inp_shexp) => {
17382 e.sigmoid_dot_rows(z_t, gate_inp_shexp.float_data(), n_embd, t)?
17383 }
17384 None => e.htod(&vec![1.0f32; t])?,
17385 };
17386 e.add_scaled_rows(sh_t, &gate, out_t, n_embd, t)?;
17387 Ok(true)
17388 }
17389
17390 fn step35_tp_decode_attn_resident_v2(
17391 &self,
17392 e: &Engine,
17393 fa: &FullAttnLayer,
17394 il: usize,
17395 h: &CudaSlice<f32>,
17396 pos_d: &CudaSlice<i32>,
17397 cache: &mut Cache,
17398 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17399 let tp = fa
17400 .step_tp_qkv
17401 .as_ref()
17402 .ok_or("Step TP decode lost its resident projections")?;
17403 let attention = tp
17404 .attention
17405 .as_ref()
17406 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
17407 if !tp.runtime.native_p2p() {
17408 return Err("rank-local Step attention requires native P2P".into());
17409 }
17410 if crate::Engine::kv_fp8_on() {
17411 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
17412 }
17413
17414 let geometry = self.step35_geom(il);
17415 let window = geometry.window.map(|window| window as usize);
17416 let ranks = tp.runtime.devices().len();
17417 let head_dim = geometry.head_dim_k as usize;
17418 let heads = geometry.n_head as usize;
17419 let kv_heads = geometry.n_head_kv as usize;
17420 if heads % ranks != 0 || kv_heads % ranks != 0 {
17421 return Err(format!(
17422 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
17423 )
17424 .into());
17425 }
17426 let local_heads = heads / ranks;
17427 let local_kv_heads = kv_heads / ranks;
17428 let max_ctx = cache.max_ctx;
17429
17430 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
17431
17432 let base_len = cache.kv[il]
17433 .as_ref()
17434 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
17435 .len;
17436 {
17437 let distributed = cache.tp_kv[il]
17438 .as_ref()
17439 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
17440 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
17441 return Err(format!(
17442 "Step TP layer {il} cache lengths diverged before decode: \
17443 local={base_len} distributed={}/{}",
17444 distributed.committed_len(),
17445 distributed.staged_len()
17446 )
17447 .into());
17448 }
17449 }
17450 if pos_d.len() != 1 {
17451 return Err(format!(
17452 "rank-local Step decode requires one position, got {}",
17453 pos_d.len()
17454 )
17455 .into());
17456 }
17457
17458 let decode_input = attention
17459 .decode_input
17460 .as_ref()
17461 .ok_or("Step TP decode v2 requires the replicated decode input")?;
17462 let mut decode_input = decode_input
17463 .lock()
17464 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
17465
17466 let use_gate_shards = (attention.gate_shards.is_some()
17471 || attention.gate_shards_bf16.is_some())
17472 && crate::tp::step_tp_qkv_fused_enabled()?;
17473 let gate_raw = if use_gate_shards {
17474 None
17475 } else {
17476 let gate_weight = fa
17477 .attn_gate
17478 .as_ref()
17479 .ok_or("step35 layer is missing attn_gate.weight")?;
17480 let gate_raw = e.matmul(gate_weight, h, 1)?;
17481 if gate_raw.len() != heads {
17482 return Err(format!(
17483 "Step TP layer {il} gate output {} != {heads}",
17484 gate_raw.len()
17485 )
17486 .into());
17487 }
17488 Some(gate_raw)
17489 };
17490
17491 let ws_index = tp
17492 .runtime
17493 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17494 let mut ws_guard = tp
17495 .runtime
17496 .decode_v2_workspace()
17497 .lock()
17498 .map_err(|_| "Step TP decode v2 workspace lock is poisoned")?;
17499 let ws = ws_guard
17500 .get_mut(ws_index)
17501 .ok_or("Step TP decode v2 workspace missing after ensure")?;
17502
17503 let mut rope_freqs = Vec::with_capacity(ranks);
17504 for rank in 0..ranks {
17505 let engine = tp
17506 .runtime
17507 .rank_engine(rank)
17508 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
17509 rope_freqs.push(if geometry.rope_factors {
17510 self.step35_aux
17511 .as_ref()
17512 .and_then(|aux| aux.rope_freqs(engine))
17513 } else {
17514 None
17515 });
17516 }
17517 let staged_next = base_len + 1;
17524 let t_kv_eff = window
17525 .map(|window| staged_next.min(window))
17526 .unwrap_or(staged_next);
17527 let dcw = crate::tp::step_tp_dcw_enabled()? && use_gate_shards && t_kv_eff >= 96 && {
17528 let (write_row, would_rebase) = cache.tp_kv[il]
17529 .as_ref()
17530 .expect("distributed cache checked above")
17531 .peek_append_ring(1)?;
17532 if !would_rebase {
17533 let base = (base_len - write_row) as i32;
17535 let distributed = cache.tp_kv[il]
17536 .as_mut()
17537 .expect("distributed cache checked above");
17538 for rank in 0..ranks {
17539 let engine = tp.runtime.rank_engine(rank).ok_or_else(|| {
17540 format!("Step TP layer {il} has no engine for rank {rank}")
17541 })?;
17542 let _main = engine.gpu.enter_main()?;
17543 let rank_cache = distributed
17544 .rank_mut(rank)
17545 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17546 if rank_cache.base_d().is_none() {
17547 rank_cache.arm_base_d(engine.htod_i32(&[base])?);
17548 }
17549 }
17550 }
17551 !would_rebase
17552 };
17553 let fuse_rope = dcw
17554 && crate::tp::fuse_rope_append_on()
17555 && head_dim == 128
17556 && cache.tp_kv[il]
17557 .as_ref()
17558 .map(|d| d.kv_dim_k() == d.kv_dim_v() && d.kv_dim_k() == local_kv_heads * head_dim)
17559 .unwrap_or(false);
17560
17561 let tcol_col = crate::tp::take_verify_tcol();
17562 let fa2_col = crate::tp::take_spec_fa2_defer();
17569 tp.runtime.decode_v2_input_qkv(
17570 ws,
17571 e,
17572 h,
17573 pos_d,
17574 gate_raw.as_ref(),
17575 if !use_gate_shards {
17576 None
17577 } else if let Some(shards) = attention.gate_shards.as_deref() {
17578 Some(crate::tp::StepTpGateShards::F32(shards))
17579 } else {
17580 attention
17581 .gate_shards_bf16
17582 .as_deref()
17583 .map(crate::tp::StepTpGateShards::Bf16)
17584 },
17585 &mut decode_input,
17586 &tp.q,
17587 &tp.k,
17588 &tp.v,
17589 &attention.q_norm,
17590 &attention.k_norm,
17591 head_dim,
17592 geometry.n_rot as usize,
17593 geometry.rope_base,
17594 &rope_freqs,
17595 self.cfg.rms_eps,
17596 fuse_rope,
17597 tcol_col,
17598 )?;
17599
17600 let transaction = cache.tp_kv[il]
17601 .as_mut()
17602 .expect("distributed cache checked above")
17603 .begin_transaction()?;
17604 let append_result = tp.runtime.append_tp_kv_transaction_inner(
17605 cache.tp_kv[il]
17606 .as_mut()
17607 .expect("distributed cache checked above"),
17608 transaction,
17609 &ws.k,
17610 &ws.v_raw,
17611 1,
17612 dcw,
17613 );
17614 if let Err(error) = append_result {
17615 let _ = tp.runtime.rollback_tp_kv_transaction(
17616 cache.tp_kv[il]
17617 .as_mut()
17618 .expect("distributed cache checked above"),
17619 transaction,
17620 );
17621 return Err(error);
17622 }
17623
17624 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17625 let (staged_len, physical, k_tok_bytes_c, v_tok_bytes_c, capacity) = {
17628 let distributed = cache.tp_kv[il]
17629 .as_ref()
17630 .expect("distributed cache checked above");
17631 let staged_len = distributed.staged_len();
17632 let view_start = window
17633 .map(|window| staged_len.saturating_sub(window))
17634 .unwrap_or(0);
17635 (
17636 staged_len,
17637 distributed.physical_range(view_start, staged_len)?,
17638 distributed.k_tok_bytes(),
17639 distributed.v_tok_bytes(),
17640 distributed.physical_capacity(),
17641 )
17642 };
17643 let view_start = window
17644 .map(|window| staged_len.saturating_sub(window))
17645 .unwrap_or(0);
17646 let t_kv = staged_len - view_start;
17647 for rank in 0..ranks {
17648 let engine = tp
17649 .runtime
17650 .rank_engine(rank)
17651 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
17652 let _main = engine.gpu.enter_main()?;
17653 if dcw {
17654 {
17658 let distributed_mut = cache.tp_kv[il]
17659 .as_mut()
17660 .expect("distributed cache checked above");
17661 let (kv_dim_k, kv_dim_v) =
17662 (distributed_mut.kv_dim_k(), distributed_mut.kv_dim_v());
17663 let (k_tok_bytes, v_tok_bytes) =
17664 (distributed_mut.k_tok_bytes(), distributed_mut.v_tok_bytes());
17665 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
17666 format!("Step TP layer {il} has no KV cache rank {rank}")
17667 })?;
17668 let (k_plane, v_plane, len_d, base_d) =
17669 rank_cache.planes_and_counters_mut();
17670 if fuse_rope {
17671 let same_dev = engine.ctx().ordinal() == e.ctx().ordinal();
17674 let crate::tp::StepTpDecodeV2Ws {
17675 q_raw,
17676 k_raw,
17677 v_raw,
17678 q,
17679 k,
17680 pos,
17681 pos_stage,
17682 fuse_ctr,
17683 ..
17684 } = &mut *ws;
17685 let pos_ref: &CudaSlice<i32> = if same_dev {
17689 pos_stage
17690 .as_ref()
17691 .ok_or("step TP decode v2 pos stage not armed")?
17692 } else {
17693 &pos[rank]
17694 };
17695 engine.qk_norm_rope_append_inc_dcw(
17696 &q_raw[rank],
17697 &k_raw[rank],
17698 &v_raw[rank],
17699 &attention.q_norm[rank],
17700 &attention.k_norm[rank],
17701 &mut q[rank],
17702 &mut k[rank],
17703 pos_ref,
17704 k_plane,
17705 v_plane,
17706 len_d,
17707 base_d,
17708 &mut fuse_ctr[rank],
17709 kv_dim_k,
17710 kv_dim_v,
17711 k_tok_bytes,
17712 v_tok_bytes,
17713 head_dim,
17714 geometry.n_rot as usize,
17715 local_heads,
17716 local_kv_heads,
17717 self.cfg.rms_eps,
17718 geometry.rope_base,
17719 1.0,
17720 rope_freqs[rank],
17721 )?;
17722 } else {
17723 engine.append_kv_quantized_dcw(
17724 &ws.k[rank],
17725 &ws.v_raw[rank],
17726 k_plane,
17727 v_plane,
17728 len_d,
17729 base_d,
17730 kv_dim_k,
17731 kv_dim_v,
17732 k_tok_bytes,
17733 v_tok_bytes,
17734 )?;
17735 }
17736 if !fuse_rope {
17737 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
17738 format!("Step TP layer {il} has no KV cache rank {rank}")
17739 })?;
17740 engine.inc_i32(rank_cache.len_d_mut())?;
17741 }
17742 }
17743 let distributed = cache.tp_kv[il]
17744 .as_ref()
17745 .expect("distributed cache checked above");
17746 let rank_cache = distributed
17747 .rank(rank)
17748 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17749 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes_c);
17750 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes_c);
17751 if fa2_col.is_some() {
17752 continue;
17755 }
17756 {
17757 let crate::tp::StepTpDecodeV2Ws { q, gate, gated, .. } = &mut *ws;
17760 engine.fa_decode_dcw(
17761 &q[rank],
17762 &k_ring,
17763 &v_ring,
17764 &mut gated[rank],
17765 head_dim,
17766 local_heads,
17767 local_kv_heads,
17768 rank_cache.len_d(),
17769 rank_cache.base_d(),
17770 window.unwrap_or(0),
17771 t_kv,
17772 geometry.attention_scale(),
17773 k_tok_bytes_c,
17774 v_tok_bytes_c,
17775 Some(&gate[rank]),
17776 )?;
17777 }
17778 continue;
17779 }
17780 let distributed = cache.tp_kv[il]
17781 .as_ref()
17782 .expect("distributed cache checked above");
17783 let rank_cache = distributed
17784 .rank(rank)
17785 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17786 let k_view = engine.view_u8_range(
17787 rank_cache.k(),
17788 physical.start * k_tok_bytes_c,
17789 physical.end * k_tok_bytes_c,
17790 );
17791 let v_view = engine.view_u8_range(
17792 rank_cache.v(),
17793 physical.start * v_tok_bytes_c,
17794 physical.end * v_tok_bytes_c,
17795 );
17796 engine.fa_decode_kvmod(
17797 &ws.q[rank],
17798 &k_view,
17799 &v_view,
17800 &mut ws.attn_out[rank],
17801 head_dim,
17802 local_heads,
17803 local_kv_heads,
17804 t_kv,
17805 geometry.attention_scale(),
17806 k_tok_bytes_c,
17807 v_tok_bytes_c,
17808 false,
17809 )?;
17810 engine.attn_head_gate(
17811 &ws.attn_out[rank],
17812 &ws.gate[rank],
17813 &mut ws.gated[rank],
17814 None,
17815 head_dim,
17816 local_heads,
17817 1,
17818 )?;
17819 }
17820
17821 let output = if let Some(col) = fa2_col.filter(|_| dcw) {
17828 tp.runtime.decode_v2_stash_fa2(ws, e, col)?;
17832 crate::tp::set_spec_fa2_stashed();
17833 e.uninit(ws.o_out)?
17834 } else if let Some(col) = crate::tp::take_tcol_oproj_defer() {
17835 if tp.runtime.decode_v2_oproj_tcol_eligible(ws, &tp.o) {
17836 tp.runtime.decode_v2_stash_gated(ws, e, col)?;
17837 crate::tp::set_tcol_oproj_stashed();
17838 e.uninit(ws.o_out)?
17839 } else {
17840 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17841 }
17842 } else {
17843 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17844 };
17845
17846 let local = cache.kv[il]
17850 .as_mut()
17851 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
17852 if local.len != base_len || base_len + 1 > max_ctx {
17853 return Err(format!(
17854 "Step TP layer {il} local cache changed during decode: \
17855 len={} base={base_len} max={max_ctx}",
17856 local.len
17857 )
17858 .into());
17859 }
17860 if crate::tp::no_local_shadow_on() {
17861 local.len = base_len + 1;
17864 if !crate::tp::len_mirror_lazy_on() {
17868 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17869 }
17870 } else {
17871 let retain_from = window
17872 .map(|window| {
17873 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
17874 let rollback_retain =
17875 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
17876 staged_retain.min(rollback_retain)
17877 })
17878 .unwrap_or(0);
17879 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
17880 e.append_kv_quantized(
17881 &ws.k_shadow,
17882 &ws.v_shadow,
17883 &mut local.k,
17884 &mut local.v,
17885 write_row,
17886 local.kv_dim_k,
17887 local.kv_dim_v,
17888 local.k_tok_bytes,
17889 local.v_tok_bytes,
17890 false,
17891 )?;
17892 local.len = base_len + 1;
17893 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17894 }
17895 Ok(output)
17896 })();
17897
17898 let output = match staged {
17899 Ok(output) => output,
17900 Err(error) => {
17901 let _ = tp.runtime.rollback_tp_kv_transaction(
17902 cache.tp_kv[il]
17903 .as_mut()
17904 .expect("distributed cache checked above"),
17905 transaction,
17906 );
17907 if let Some(local) = cache.kv[il].as_mut() {
17908 local.len = base_len;
17909 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
17910 }
17911 return Err(error);
17912 }
17913 };
17914 let lazy_commit = fuse_rope && crate::tp::len_mirror_lazy_on();
17919 if lazy_commit {
17920 if let Err(error) = tp.runtime.commit_tp_kv_transaction_external(
17921 cache.tp_kv[il]
17922 .as_mut()
17923 .expect("distributed cache checked above"),
17924 transaction,
17925 1,
17926 ) {
17927 let _ = tp.runtime.rollback_tp_kv_transaction(
17928 cache.tp_kv[il]
17929 .as_mut()
17930 .expect("distributed cache checked above"),
17931 transaction,
17932 );
17933 let local = cache.kv[il].as_mut().expect("local cache checked above");
17934 local.len = base_len;
17935 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17936 return Err(error);
17937 }
17938 } else if let Err(error) = tp.runtime.commit_tp_kv_transaction(
17939 cache.tp_kv[il]
17940 .as_mut()
17941 .expect("distributed cache checked above"),
17942 transaction,
17943 1,
17944 ) {
17945 let _ = tp.runtime.rollback_tp_kv_transaction(
17946 cache.tp_kv[il]
17947 .as_mut()
17948 .expect("distributed cache checked above"),
17949 transaction,
17950 );
17951 let local = cache.kv[il].as_mut().expect("local cache checked above");
17952 local.len = base_len;
17953 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17954 return Err(error);
17955 }
17956
17957 let committed = cache.tp_kv[il]
17958 .as_ref()
17959 .expect("distributed cache checked above")
17960 .committed_len();
17961 let local_len = cache.kv[il]
17962 .as_ref()
17963 .expect("local cache checked above")
17964 .len;
17965 if committed != local_len {
17966 return Err(format!(
17967 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
17968 )
17969 .into());
17970 }
17971 static V2_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
17972 if !V2_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
17973 eprintln!(
17974 "[step-tp-attn-v2] execute layer={} devices={:?} tokens=1 driver=v2 \
17975 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
17976 kv_cache_distributed=true kv_cache_hydrated={hydrated} \
17977 attention_tensor_parallel=true attention_scope={} \
17978 input_path=root-device-replicated gate_tensor_parallel=false \
17979 gate_shards=device-staged o_tensor_parallel=true o_reduce=root-device \
17980 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
17981 bulk_p2p={} workspace=persistent ordering=evented output=e-device \
17982 performance_claim=false (logged once; every decode layer runs this driver)",
17983 tp.layer,
17984 tp.devices,
17985 if window.is_some() {
17986 "rank-local-swa-ring"
17987 } else {
17988 "rank-local-global"
17989 },
17990 tp.runtime.transport_label(),
17991 tp.runtime.bulk_p2p(),
17992 );
17993 }
17994 Ok(output)
17995 }
17996
17997 #[allow(clippy::too_many_arguments)]
18007 pub(crate) fn step35_decode_attn(
18008 &self,
18009 e: &Engine,
18010 fa: &FullAttnLayer,
18011 il: usize,
18012 h: &CudaSlice<f32>,
18013 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
18014 pos_d: &CudaSlice<i32>,
18015 cache: &mut Cache,
18016 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18017 if fa
18018 .step_tp_qkv
18019 .as_ref()
18020 .is_some_and(|tp| tp.attention.is_some())
18021 {
18022 if pre_q.is_some() {
18023 return Err(
18024 "rank-local Step attention preserves BF16 activations and refuses the q8_1 \
18025 pre-quantized decode path"
18026 .into(),
18027 );
18028 }
18029 return self.step35_tp_decode_attn_resident(e, fa, il, h, pos_d, cache);
18030 }
18031
18032 let geometry = self.step35_geom(il);
18033 let hd = geometry.head_dim_k as usize;
18034 let nkv = geometry.n_head_kv as usize;
18035 let nh = geometry.n_head as usize;
18036 let rbase = geometry.rope_base;
18037 let scale = geometry.attention_scale();
18038 let swa = geometry.window.is_some();
18039 let eps = self.cfg.rms_eps;
18040 let win = geometry.window.unwrap_or(0) as usize;
18041 let n_rot = geometry.n_rot as usize;
18042 let n_embd = self.cfg.n_embd as usize;
18043 let gw = fa
18044 .attn_gate
18045 .as_ref()
18046 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
18047
18048 let tp_qkv = if fa.step_tp_qkv.is_some() {
18049 if pre_q.is_some() {
18050 return Err(
18051 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
18052 pre-quantized decode path"
18053 .into(),
18054 );
18055 }
18056 self.step35_tp_qkv(e, fa, h, 1)?
18057 } else {
18058 None
18059 };
18060
18061 let (q0, k0, v0, gt) = match tp_qkv {
18062 Some(mut g3) => {
18063 let v = g3.pop().unwrap();
18064 let k = g3.pop().unwrap();
18065 let q = g3.pop().unwrap();
18066 let gt = e.matmul(gw, h, 1)?;
18067 (q, k, v, gt)
18068 }
18069 None => match pre_q {
18070 Some((hq, hdq)) => {
18071 debug_assert!(
18072 e.uses_q8_1_fast(gw),
18073 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
18074 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
18075 );
18076 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
18077 Some(t3) => t3,
18078 None => (
18079 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
18080 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
18081 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
18082 ),
18083 };
18084 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
18085 (a, b, c, gt)
18086 }
18087 None => {
18088 if e.uses_q8_1_fast(&fa.wq)
18089 && e.uses_q8_1_fast(&fa.wk)
18090 && e.uses_q8_1_fast(&fa.wv)
18091 && e.uses_q8_1_fast(gw)
18092 {
18093 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
18094 let (a, b, c) =
18095 match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
18096 Some(t3) => t3,
18097 None => (
18098 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
18099 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
18100 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
18101 ),
18102 };
18103 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
18104 (a, b, c, gt)
18105 } else {
18106 (
18107 e.matmul(&fa.wq, h, 1)?,
18108 e.matmul(&fa.wk, h, 1)?,
18109 e.matmul(&fa.wv, h, 1)?,
18110 e.matmul(gw, h, 1)?,
18111 )
18112 }
18113 }
18114 },
18115 };
18116
18117 let mut q = e.uninit(nh * hd)?;
18118 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
18119 let mut k = e.uninit(nkv * hd)?;
18120 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
18121 let ff = if swa {
18122 None
18123 } else {
18124 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
18125 };
18126 #[cfg(debug_assertions)]
18127 if let Some(ff) = ff {
18128 crate::debug_assert_tensor_stream_device(
18129 ff,
18130 &e.stream(),
18131 "step35_decode_attn.rope_freqs",
18132 );
18133 }
18134 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
18135
18136 if std::env::var("MEMRA_NOFA").is_ok() {
18137 return Err(
18138 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
18139 cache; unset MEMRA_NOFA to use fa_decode"
18140 .into(),
18141 );
18142 }
18143 let kvl = cache.kv[il].as_mut().unwrap();
18144 let next_len = kvl.len + 1;
18145 let (off, t_kv) = if swa && next_len > win {
18146 (next_len - win, win)
18147 } else {
18148 (0, next_len)
18149 };
18150 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
18151 e.append_kv_quantized(
18152 &k,
18153 &v0,
18154 &mut kvl.k,
18155 &mut kvl.v,
18156 write_row,
18157 kvl.kv_dim_k,
18158 kvl.kv_dim_v,
18159 kvl.k_tok_bytes,
18160 kvl.v_tok_bytes,
18161 crate::Engine::kv_fp8_on(),
18162 )?;
18163 kvl.len = next_len;
18164 let physical = kvl.physical_rows(off, off + t_kv)?;
18165 let k_view = e.view_u8_range(
18166 &kvl.k,
18167 physical.start * kvl.k_tok_bytes,
18168 physical.end * kvl.k_tok_bytes,
18169 );
18170 let v_view = e.view_u8_range(
18171 &kvl.v,
18172 physical.start * kvl.v_tok_bytes,
18173 physical.end * kvl.v_tok_bytes,
18174 );
18175 let mut attn = e.uninit(nh * hd)?;
18176 e.fa_decode_kvmod(
18177 &q,
18178 &k_view,
18179 &v_view,
18180 &mut attn,
18181 hd,
18182 nh,
18183 nkv,
18184 t_kv,
18185 scale,
18186 kvl.k_tok_bytes,
18187 kvl.v_tok_bytes,
18188 crate::Engine::kv_fp8_on(),
18189 )?;
18190
18191 let mut ag = e.uninit(nh * hd)?;
18192 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
18193 self.step35_o(e, fa, &ag, 1)
18194 }
18195}
18196
18197impl HybridModel {
18206 pub fn is_gemma4_e4b(&self) -> bool {
18207 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
18208 }
18209
18210 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
18214 let g = self.cfg.gemma4.as_ref().unwrap();
18215 let swa = g.swa_pattern[il];
18216 let hd = if swa {
18217 g.key_length_swa
18218 } else {
18219 g.key_length_global
18220 } as usize;
18221 let Mixer::Full(fa) = &self.layers[il].mixer else {
18222 panic!("e4b layer {il} not full-attn")
18223 };
18224 let nh = fa.wq.out_features() / hd;
18225 let nkv = fa.wk.out_features() / hd;
18226 (
18227 hd,
18228 nkv,
18229 nh,
18230 if swa {
18231 g.rope_base_swa
18232 } else {
18233 g.rope_base_global
18234 },
18235 1.0,
18236 swa,
18237 )
18238 }
18239
18240 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
18242 self.layers[il]
18243 .gemma4
18244 .as_ref()
18245 .and_then(|b| b.e4b.as_ref())
18246 .and_then(|e4| e4.kv_share.map(|t| t as usize))
18247 }
18248
18249 fn gemma4_e4b_inp_pl(
18254 &self,
18255 e: &Engine,
18256 tokens: &[u32],
18257 x_scaled: &CudaSlice<f32>,
18258 t: usize,
18259 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18260 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
18261 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
18262 }
18263
18264 fn gemma4_e4b_inp_pl_dev(
18266 &self,
18267 e: &Engine,
18268 tok_d: &CudaSlice<u32>,
18269 x_scaled: &CudaSlice<f32>,
18270 t: usize,
18271 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18272 let aux = self.gemma4_aux.as_ref().unwrap();
18273 let m = aux.e4b.as_ref().unwrap();
18274 let n_embd = self.cfg.n_embd as usize;
18275 let n_layer = self.layers.len();
18276 let width = m.n_epl * n_layer;
18277 let tbl = m.tok_tbl_gpu.get_or_init(|| {
18278 e.upload_u8(&m.tok_embd_bytes)
18279 .expect("e4b per-layer token table upload")
18280 });
18281 let mut a =
18282 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
18283 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
18284 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
18285 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
18286 let mut pn = e.uninit(t * width)?;
18287 e.rms_norm(
18288 &p,
18289 m.proj_norm.float_data(),
18290 &mut pn,
18291 m.n_epl,
18292 t * n_layer,
18293 self.cfg.rms_eps,
18294 )?;
18295 let mut out = e.uninit(t * width)?;
18296 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
18297 Ok(out)
18298 }
18299
18300 #[allow(clippy::too_many_arguments)]
18305 fn gemma4_e4b_attn(
18306 &self,
18307 e: &Engine,
18308 il: usize,
18309 hq: &CudaSlice<i8>,
18310 hdq: &CudaSlice<f32>,
18311 pos_d: &CudaSlice<i32>,
18312 t: usize,
18313 cache: &mut Cache,
18314 dc_bucket: Option<usize>,
18315 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18316 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
18317 let eps = self.cfg.rms_eps;
18318 let aux = self.gemma4_aux.as_ref().unwrap();
18319 let ones = aux.ones(e);
18320 #[cfg(debug_assertions)]
18321 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
18322 let Mixer::Full(fa) = &self.layers[il].mixer else {
18323 unreachable!()
18324 };
18325 let h0 = e.zeros(0)?;
18329 let h = &h0;
18330
18331 let ff = if swa {
18332 None
18333 } else {
18334 Some(
18335 aux.rope_freqs(e)
18336 .expect("e4b global rope needs rope_freqs.weight"),
18337 )
18338 };
18339 #[cfg(debug_assertions)]
18340 if let Some(ff) = ff {
18341 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
18342 }
18343 let share = self.gemma4_e4b_kv_target(il);
18344 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
18346 let mut q;
18347 if let Some(_tgt) = share {
18348 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
18349 q = e.uninit(t * nh * hd)?;
18350 let mut kdummy = e.uninit(1)?;
18353 let mut vdummy = e.uninit(1)?;
18354 e.rms_norm_qkv_rope(
18355 &q0,
18356 &q0,
18357 &q0,
18358 fa.q_norm.float_data(),
18359 fa.q_norm.float_data(),
18360 ones,
18361 &mut q,
18362 &mut kdummy,
18363 &mut vdummy,
18364 hd,
18365 self.gemma4_rope_dims(il),
18366 nh * t,
18367 0,
18368 pos_d,
18369 nh,
18370 1,
18371 base,
18372 1.0,
18373 ff,
18374 eps,
18375 )?;
18376 } else {
18377 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
18381 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
18382 q = e.uninit(t * nh * hd)?;
18383 let mut k = e.uninit(t * nkv * hd)?;
18384 let mut v = e.uninit(t * nkv * hd)?;
18385 if t == 1 && cat.is_some() {
18386 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
18387 e.rms_norm_qkv_rope_cat(
18388 &qkv0,
18389 fa.q_norm.float_data(),
18390 fa.k_norm.float_data(),
18391 ones,
18392 &mut q,
18393 &mut k,
18394 &mut v,
18395 hd,
18396 self.gemma4_rope_dims(il),
18397 nh,
18398 nkv,
18399 pos_d,
18400 nh,
18401 nkv,
18402 base,
18403 1.0,
18404 ff,
18405 eps,
18406 )?;
18407 } else {
18408 let (q0, k0, v0) = match if t == 1 {
18409 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
18410 } else {
18411 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18414 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
18415 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
18416 } else {
18417 None
18418 }
18419 } {
18420 Some(triple) => triple,
18421 None => (
18422 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
18423 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
18424 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
18425 ), };
18427 e.rms_norm_qkv_rope(
18430 &q0,
18431 &k0,
18432 &v0,
18433 fa.q_norm.float_data(),
18434 fa.k_norm.float_data(),
18435 ones,
18436 &mut q,
18437 &mut k,
18438 &mut v,
18439 hd,
18440 self.gemma4_rope_dims(il),
18441 nh * t,
18442 nkv * t,
18443 pos_d,
18444 nh,
18445 nkv,
18446 base,
18447 1.0,
18448 ff,
18449 eps,
18450 )?;
18451 }
18452 let kvl = cache.kv[il].as_mut().unwrap();
18453 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18457 if dc_bucket.is_some() {
18458 debug_assert!(t == 1);
18463 e.append_kv_quantized_row_dc_inc(
18465 &k,
18466 &v,
18467 &mut kvl.k,
18468 &mut kvl.v,
18469 &mut kvl.len_d,
18470 kvl.kv_dim_k,
18471 kvl.kv_dim_v,
18472 kvl.k_tok_bytes,
18473 kvl.v_tok_bytes,
18474 cls,
18475 )?;
18476 } else {
18477 e.append_kv_quantized_rows(
18478 &k,
18479 &v,
18480 &mut kvl.k,
18481 &mut kvl.v,
18482 kvl.len,
18483 t,
18484 kvl.kv_dim_k,
18485 kvl.kv_dim_v,
18486 kvl.k_tok_bytes,
18487 kvl.v_tok_bytes,
18488 cls,
18489 )?;
18490 kvl.len += t;
18491 }
18492 kv_f32 = Some((k, v));
18493 }
18494 let kvl_idx = share.unwrap_or(il);
18497 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
18498 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
18500 let mut attn = e.uninit(t * nh * hd)?;
18501 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
18513 if let Some((kf, vf)) = &kv_f32 {
18514 if hd == 256 && t <= win {
18515 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18516 return Ok(e.matmul(&fa.wo, &attn, t)?);
18517 }
18518 if hd == 256 && swa && t > win {
18519 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
18520 return Ok(e.matmul(&fa.wo, &attn, t)?);
18521 }
18522 if hd == 512 && !swa {
18523 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18524 return Ok(e.matmul(&fa.wo, &attn, t)?);
18525 }
18526 } else if share.is_some() {
18527 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18528 let k_view = e.view_u8(&kvl.k, kvl.k.len());
18529 let v_view = e.view_u8(&kvl.v, kvl.v.len());
18530 if hd == 256 && (!swa || t <= win) {
18531 e.fa_prefill_view(
18533 &q,
18534 &k_view,
18535 &v_view,
18536 &mut attn,
18537 hd,
18538 nh,
18539 nkv,
18540 t,
18541 t,
18542 scale,
18543 true,
18544 kvl.k_tok_bytes,
18545 kvl.v_tok_bytes,
18546 g,
18547 )?;
18548 return Ok(e.matmul(&fa.wo, &attn, t)?);
18549 }
18550 let kv_dim = nkv * hd;
18553 let mut kf = e.uninit(t * kv_dim)?;
18554 let mut vf = e.uninit(t * kv_dim)?;
18555 e.fa_dequant_kv_view_f32(
18556 &k_view,
18557 &v_view,
18558 &mut kf,
18559 &mut vf,
18560 kv_dim,
18561 kv_dim,
18562 t,
18563 kvl.k_tok_bytes,
18564 kvl.v_tok_bytes,
18565 g,
18566 )?;
18567 if hd == 512 {
18568 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18569 } else {
18570 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
18571 }
18572 return Ok(e.matmul(&fa.wo, &attn, t)?);
18573 }
18574 }
18575 if let Some(bucket) = dc_bucket {
18576 assert!(t == 1);
18581 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
18587 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
18588 } else {
18589 bucket
18590 };
18591 let k_view = e.view_u8(&kvl.k, kvl.k.len());
18592 let v_view = e.view_u8(&kvl.v, kvl.v.len());
18593 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18594 if crate::Engine::wpf_level() >= 1 {
18602 e.prefetch_weight_l2(&fa.wo)?;
18603 }
18604 if e.uses_q8_1_fast(&fa.wo) {
18607 let mut oq = e.alloc_i8_uninit(nh * hd)?;
18608 let mut od = e.zeros(nh * hd / 32)?;
18609 e.fa_decode_dc_q8(
18610 &q,
18611 &k_view,
18612 &v_view,
18613 &mut attn,
18614 hd,
18615 nh,
18616 nkv,
18617 &kvl.len_d,
18618 bucket,
18619 scale,
18620 kvl.k_tok_bytes,
18621 kvl.v_tok_bytes,
18622 g,
18623 Some((&mut oq, &mut od)),
18624 )?;
18625 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
18626 }
18627 e.fa_decode_dc(
18628 &q,
18629 &k_view,
18630 &v_view,
18631 &mut attn,
18632 hd,
18633 nh,
18634 nkv,
18635 &kvl.len_d,
18636 bucket,
18637 scale,
18638 kvl.k_tok_bytes,
18639 kvl.v_tok_bytes,
18640 g,
18641 )?;
18642 return Ok(e.matmul(&fa.wo, &attn, t)?);
18643 }
18644 for i in 0..t {
18645 let avail = base_len + i + 1;
18646 let (off_tok, t_kv) = if swa && avail > win {
18647 (avail - win, win)
18648 } else {
18649 (0, avail)
18650 };
18651 let k_view = e.view_u8_range(
18652 &kvl.k,
18653 off_tok * kvl.k_tok_bytes,
18654 (off_tok + t_kv) * kvl.k_tok_bytes,
18655 );
18656 let v_view = e.view_u8_range(
18657 &kvl.v,
18658 off_tok * kvl.v_tok_bytes,
18659 (off_tok + t_kv) * kvl.v_tok_bytes,
18660 );
18661 let qv = e.view(&q, t * nh * hd);
18662 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
18663 let mut q_one = e.uninit(nh * hd)?;
18664 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
18665 let mut a_one = e.uninit(nh * hd)?;
18666 e.fa_decode_kvmod(
18670 &q_one,
18671 &k_view,
18672 &v_view,
18673 &mut a_one,
18674 hd,
18675 nh,
18676 nkv,
18677 t_kv,
18678 scale,
18679 kvl.k_tok_bytes,
18680 kvl.v_tok_bytes,
18681 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
18682 )?;
18683 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
18684 }
18685 Ok(e.matmul(&fa.wo, &attn, t)?)
18686 }
18687
18688 fn gemma4_e4b_trunk(
18693 &self,
18694 e: &Engine,
18695 tokens: &[u32],
18696 pos0: usize,
18697 cache: &mut Cache,
18698 head_last: bool,
18699 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18700 let n_embd = self.cfg.n_embd as usize;
18701 let t = tokens.len();
18702 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
18703 let pos_d = e.htod_i32(&pos)?;
18704 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
18705 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
18706 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
18707 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
18708 }
18709
18710 fn gemma4_e4b_trunk_core(
18714 &self,
18715 e: &Engine,
18716 x_in: CudaSlice<f32>,
18717 inp_pl: CudaSlice<f32>,
18718 pos_d: &CudaSlice<i32>,
18719 t: usize,
18720 cache: &mut Cache,
18721 dc_bucket: Option<usize>,
18722 cap_logits: bool,
18723 head_last: bool,
18724 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18725 let n_embd = self.cfg.n_embd as usize;
18726 let eps = self.cfg.rms_eps;
18727 let n_layer = self.layers.len();
18728 let mut x = x_in;
18729 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
18730 let n_epl = aux_e4b.n_epl;
18731
18732 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
18738 for il in 0..n_layer {
18739 let layer = &self.layers[il];
18740 let (hq, hdq) = match h_carry.take() {
18741 Some(p) => p,
18742 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
18743 };
18744 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
18745 let bits = layer.gemma4.as_ref().unwrap();
18748 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
18749 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
18760 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
18761 e,
18762 layer,
18763 &o,
18764 &x,
18765 t,
18766 Some(layer.post_attn_norm.float_data()),
18767 fuse_exit,
18768 )?;
18769 let mut resid = e.uninit(t * n_embd)?;
18770 let g = if fuse_exit {
18776 let (rq, rd) = e.rms_pre_add_q8_1(
18778 &sn,
18779 bits.post_ffw_norm.float_data(),
18780 &attn_out,
18781 &mut resid,
18782 n_embd,
18783 t,
18784 self.cfg.rms_eps,
18785 )?;
18786 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
18787 } else {
18788 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
18789 e.matmul(&e4b.inp_gate, &resid, t)?
18790 };
18791 let mut act = e.uninit(t * n_epl)?;
18792 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
18793 let ipv = e.view(&inp_pl, n_epl * n_layer);
18794 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
18795 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
18796 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
18797 } else {
18798 let mut inp_this = e.uninit(t * n_epl)?;
18799 e.copy_rows_strided(
18800 &inp_pl,
18801 &mut inp_this,
18802 n_epl,
18803 t,
18804 n_epl * n_layer,
18805 il * n_epl,
18806 )?;
18807 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
18808 e.matmul(&e4b.proj, &act, t)?
18809 };
18810 let next_norm = if il + 1 < n_layer {
18813 self.layers[il + 1].attn_norm.float_data()
18814 } else {
18815 self.output_norm.float_data()
18816 };
18817 let mut xn = e.uninit(t * n_embd)?;
18818 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
18819 &y,
18820 e4b.post_norm.float_data(),
18821 &resid,
18822 bits.layer_scale,
18823 next_norm,
18824 &mut xn,
18825 n_embd,
18826 t,
18827 eps,
18828 )?;
18829 h_carry = Some(pair);
18830 x = xn;
18831 }
18832 let (oq, odq) = h_carry.take().unwrap();
18836 let h0 = e.zeros(0)?;
18837 let hm = if head_last { 1 } else { t };
18838 let (hq, hd) = if head_last && t > 1 {
18839 let mut q1 = e.uninit_i8(n_embd)?;
18840 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
18841 let nb = n_embd / 32;
18842 let mut d1 = e.uninit(nb)?;
18843 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
18844 (q1, d1)
18845 } else {
18846 (oq, odq)
18847 };
18848 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
18849 if cap_logits {
18853 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
18854 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
18855 }
18856 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
18858 }
18859
18860 pub fn gemma4_e4b_decode_step_t_am_dev(
18867 &self,
18868 e: &Engine,
18869 tok_d: &CudaSlice<u32>,
18870 t: usize,
18871 pos0: usize,
18872 cache: &mut Cache,
18873 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18874 let n_embd = self.cfg.n_embd as usize;
18875 let eps = self.cfg.rms_eps;
18876 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
18877 let pos_d = e.htod_i32(&pos)?;
18878 let embd_gpu = self
18879 .embd_gpu
18880 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
18881 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
18882 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
18883 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
18884 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
18885 let (ld, xp) =
18886 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
18887 let n_vocab = self.output.out_features();
18890 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
18891 for i in 0..t {
18892 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
18893 }
18894 let mut hn = e.uninit(t * n_embd)?;
18895 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18896 cache.pos += t;
18897 Ok((vam, hn))
18898 }
18899
18900 pub(crate) fn gemma4_e4b_decode_step_t_h(
18903 &self,
18904 e: &Engine,
18905 tokens: &[u32],
18906 pos0: usize,
18907 cache: &mut Cache,
18908 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18909 let n_embd = self.cfg.n_embd as usize;
18910 let eps = self.cfg.rms_eps;
18911 let t = tokens.len();
18912 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
18913 let mut hn = e.uninit(t * n_embd)?;
18914 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18915 cache.pos += t;
18916 Ok((e.dtoh(&ld)?, hn))
18917 }
18918
18919 pub fn gemma4_e4b_decode_step_dcg(
18925 &self,
18926 e: &Engine,
18927 token_d: &mut CudaSlice<u32>,
18928 pos_d: &mut CudaSlice<i32>,
18929 embd_gpu: &CudaSlice<u8>,
18930 embd_qt: i32,
18931 embd_rb: usize,
18932 cache: &mut Cache,
18933 n_vocab: usize,
18934 bucket: usize,
18935 ) -> Result<(), Box<dyn std::error::Error>> {
18936 let n_embd = self.cfg.n_embd as usize;
18937 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
18938 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
18939 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
18940 let (ld, _x) =
18941 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
18942 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
18943 e.inc_seqlen(pos_d)?;
18944 Ok(())
18945 }
18946
18947 #[allow(clippy::too_many_arguments)]
18955 pub fn gemma4_e4b_decode_step_dc(
18956 &self,
18957 e: &Engine,
18958 token_d: &CudaSlice<u32>,
18959 pos_d: &mut CudaSlice<i32>,
18960 embd_gpu: &CudaSlice<u8>,
18961 embd_qt: i32,
18962 embd_rb: usize,
18963 cache: &mut Cache,
18964 n_vocab: usize,
18965 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
18966 let n_embd = self.cfg.n_embd as usize;
18967 let eps = self.cfg.rms_eps;
18968 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
18969 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
18970 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
18971 let (ld, _x) =
18972 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
18973 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
18974 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
18975 e.inc_seqlen(pos_d)?;
18976 cache.pos += 1;
18977 let _ = eps;
18978 Ok(tok_out)
18979 }
18980
18981 pub(crate) fn gemma4_e4b_decode_step_h(
18984 &self,
18985 e: &Engine,
18986 token: u32,
18987 cache: &mut Cache,
18988 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18989 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
18990 let logits = e.dtoh(&ld)?;
18991 cache.pos += 1;
18992 Ok((logits, x))
18993 }
18994
18995 pub(crate) fn gemma4_e4b_prime(
18999 &self,
19000 e: &Engine,
19001 tokens: &[u32],
19002 cache: &mut Cache,
19003 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19004 if cache.pos != 0 {
19007 return Err(
19008 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
19009 call or decode tokenwise"
19010 .into(),
19011 );
19012 }
19013 let n_embd = self.cfg.n_embd as usize;
19014 let t = tokens.len();
19015 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
19016 cache.pos += t;
19017 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
19019 let row = xv.slice((t - 1) * n_embd..t * n_embd);
19020 let mut h_seed = e.uninit(n_embd)?;
19021 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
19022 Ok((last, h_seed, x))
19023 }
19024
19025 pub(crate) fn gemma4_e4b_forward(
19027 &self,
19028 e: &Engine,
19029 tokens: &[u32],
19030 last_only: bool,
19031 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
19032 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
19033 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
19034 Ok(e.dtoh(&ld)?) }
19036}
19037
19038#[cfg(test)]
19039mod prime_chunk_schedule_tests {
19040 use super::{
19041 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, active_matrix_values, align_prime_ranges_to_gdn,
19042 dynamic_prime_chunk_ranges, fixed_prime_chunk_ranges, fixed_prime_chunk_ranges_for_ring,
19043 parse_step_ep_grouped_prefill, parse_step_tp_prefill, step_grouped_decode_shape,
19044 step_grouped_prefill_shape, step_tp_prefill_shape, validate_step_prime_batch_modes,
19045 };
19046
19047 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
19048 ranges.iter().map(|(start, end)| end - start).collect()
19049 }
19050
19051 fn auto_chunk(t: usize) -> usize {
19052 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
19053 }
19054
19055 #[test]
19060 fn auto_prime_ranges_align_to_the_gdn_grid() {
19061 let c = 32usize; let assert_covers = |ranges: &[(usize, usize)], t: usize| {
19063 assert_eq!(ranges.first().map(|&(s, _)| s), Some(0));
19064 assert_eq!(ranges.last().map(|&(_, e)| e), Some(t));
19065 for w in ranges.windows(2) {
19066 assert_eq!(w[0].1, w[1].0, "ranges must stay contiguous");
19067 }
19068 assert!(ranges.iter().all(|&(s, e)| e > s), "no empty range");
19069 };
19070
19071 let t = 9510usize;
19074 let fill = auto_chunk(t);
19075 let fixed = fixed_prime_chunk_ranges(t, fill);
19076 assert!(
19077 fixed[..fixed.len() - 1].iter().any(|&(_, e)| e % c != 0),
19078 "broken arm vanished: fixed auto boundaries all landed on-grid"
19079 );
19080 let dynamic = dynamic_prime_chunk_ranges(t, fill, &fixed);
19081 assert!(
19082 dynamic[..dynamic.len() - 1]
19083 .iter()
19084 .any(|&(_, e)| e % c != 0),
19085 "broken arm vanished: dynamic auto boundaries all landed on-grid"
19086 );
19087
19088 for ranges in [&fixed, &dynamic] {
19089 let aligned = align_prime_ranges_to_gdn(ranges, t, c);
19090 assert_covers(&aligned, t);
19091 for &(_, e) in &aligned[..aligned.len() - 1] {
19092 assert_eq!(e % c, 0, "internal boundary {e} off the {c}-grid");
19093 }
19094 for (&(_, a), &(_, b)) in aligned.iter().zip(ranges.iter()) {
19096 assert!(a <= b && b - a < c);
19097 }
19098 }
19099
19100 let tight = vec![(0usize, 33usize), (33, 40), (40, 200)];
19103 let aligned = align_prime_ranges_to_gdn(&tight, 200, c);
19104 assert_covers(&aligned, 200);
19105 assert_eq!(aligned, vec![(0, 32), (32, 200)]);
19106
19107 assert_eq!(align_prime_ranges_to_gdn(&[(0, 200)], 200, c), [(0, 200)]);
19109 assert_eq!(align_prime_ranges_to_gdn(&tight, 200, 0), tight.as_slice());
19110 let on_grid = vec![(0usize, 128usize), (128, 256), (256, 300)];
19111 assert_eq!(
19112 align_prime_ranges_to_gdn(&on_grid, 300, c),
19113 on_grid.as_slice()
19114 );
19115 }
19116
19117 #[test]
19118 fn active_matrix_prefix_scopes_reused_prime_slabs() {
19119 assert_eq!(
19120 active_matrix_values(40 * 4096, 29, 4096, "activation").unwrap(),
19121 29 * 4096
19122 );
19123 assert_eq!(
19124 active_matrix_values(29 * 4096, 29, 4096, "activation").unwrap(),
19125 29 * 4096
19126 );
19127 assert_eq!(
19128 active_matrix_values(29 * 4096, 24, 4096, "activation").unwrap(),
19129 24 * 4096
19130 );
19131 assert!(active_matrix_values(28 * 4096, 29, 4096, "activation").is_err());
19132 assert!(active_matrix_values(usize::MAX, usize::MAX, 2, "activation").is_err());
19133 }
19134
19135 #[test]
19136 fn step_tp_prefill_batch_refuses_before_scheduler_fallback() {
19137 assert!(validate_step_prime_batch_modes(false, false).is_ok());
19138
19139 let grouped_without_tp = validate_step_prime_batch_modes(false, true).unwrap_err();
19140 assert!(grouped_without_tp.contains("requires MEMRA_STEP_TP_PREFILL=1"));
19141
19142 for grouped in [false, true] {
19143 let err = validate_step_prime_batch_modes(true, grouped).unwrap_err();
19144 assert!(err.contains("did not clear the live-server performance gate"));
19145 assert!(err.contains("per-session grouped prefill"));
19146 }
19147 }
19148
19149 #[test]
19150 fn step_grouped_path_is_eager_single_token_only() {
19151 assert!(step_grouped_decode_shape(false, 1));
19152 assert!(!step_grouped_decode_shape(true, 1));
19153 assert!(!step_grouped_decode_shape(false, 2));
19154 assert!(!step_grouped_decode_shape(true, 2));
19155 }
19156
19157 #[test]
19158 fn step_grouped_prefill_door_is_strict_and_capacity_bounded() {
19159 assert!(!parse_step_ep_grouped_prefill(None).unwrap());
19160 assert!(!parse_step_ep_grouped_prefill(Some("")).unwrap());
19161 assert!(!parse_step_ep_grouped_prefill(Some("0")).unwrap());
19162 assert!(parse_step_ep_grouped_prefill(Some("1")).unwrap());
19163 assert!(parse_step_ep_grouped_prefill(Some("true")).is_err());
19164 assert!(parse_step_ep_grouped_prefill(Some("2")).is_err());
19165
19166 assert!(step_grouped_prefill_shape(true, true, PRIME_MIN_T));
19167 assert!(step_grouped_prefill_shape(
19168 true,
19169 true,
19170 crate::cache::PRIME_CHUNK_MAX_TOKENS,
19171 ));
19172 assert!(!step_grouped_prefill_shape(true, true, PRIME_MIN_T - 1,));
19173 assert!(!step_grouped_prefill_shape(
19174 true,
19175 true,
19176 crate::cache::PRIME_CHUNK_MAX_TOKENS + 1,
19177 ));
19178 assert!(!step_grouped_prefill_shape(false, true, PRIME_MIN_T));
19179 assert!(!step_grouped_prefill_shape(true, false, PRIME_MIN_T));
19180 }
19181
19182 #[test]
19183 fn step_tp_prefill_door_is_strict_and_default_off() {
19184 assert!(!parse_step_tp_prefill(None).unwrap());
19185 assert!(!parse_step_tp_prefill(Some("")).unwrap());
19186 assert!(!parse_step_tp_prefill(Some("0")).unwrap());
19187 assert!(parse_step_tp_prefill(Some("1")).unwrap());
19188 assert!(parse_step_tp_prefill(Some("true")).is_err());
19189 assert!(parse_step_tp_prefill(Some("2")).is_err());
19190 }
19191
19192 #[test]
19193 fn step_tp_prefill_requires_a_qualified_even_rank_shape() {
19194 assert!(step_tp_prefill_shape(
19195 true,
19196 PRIME_MIN_T,
19197 4,
19198 true,
19199 true,
19200 false,
19201 ));
19202 assert!(!step_tp_prefill_shape(
19203 false,
19204 PRIME_MIN_T,
19205 4,
19206 true,
19207 true,
19208 false,
19209 ));
19210 assert!(!step_tp_prefill_shape(
19211 true,
19212 PRIME_MIN_T - 1,
19213 4,
19214 true,
19215 true,
19216 false,
19217 ));
19218 assert!(step_tp_prefill_shape(
19220 true,
19221 PRIME_MIN_T,
19222 2,
19223 true,
19224 true,
19225 false
19226 ));
19227 assert!(!step_tp_prefill_shape(
19228 true,
19229 PRIME_MIN_T,
19230 1,
19231 true,
19232 true,
19233 false
19234 ));
19235 assert!(!step_tp_prefill_shape(
19236 true,
19237 PRIME_MIN_T,
19238 3,
19239 true,
19240 true,
19241 false
19242 ));
19243 assert!(!step_tp_prefill_shape(
19244 true,
19245 PRIME_MIN_T,
19246 4,
19247 false,
19248 true,
19249 false,
19250 ));
19251 assert!(!step_tp_prefill_shape(
19252 true,
19253 PRIME_MIN_T,
19254 4,
19255 true,
19256 false,
19257 false,
19258 ));
19259 assert!(!step_tp_prefill_shape(
19260 true,
19261 PRIME_MIN_T,
19262 4,
19263 true,
19264 true,
19265 true,
19266 ));
19267 }
19268
19269 #[test]
19270 fn fixed_schedule_retains_measured_geometry() {
19271 assert_eq!(
19272 sizes(&fixed_prime_chunk_ranges(461, 128)),
19273 vec![128, 128, 128, 77]
19274 );
19275 assert_eq!(
19276 sizes(&fixed_prime_chunk_ranges(1833, 230)),
19277 vec![230, 230, 230, 230, 230, 230, 230, 223]
19278 );
19279 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
19280 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
19281 assert_eq!(capped, vec![4096, 4088, 16]);
19282 assert!(capped.iter().all(|&rows| rows <= 4096));
19283 assert_eq!(
19284 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
19285 vec![4100],
19286 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
19287 );
19288 }
19289
19290 #[test]
19291 fn dynamic_schedule_matches_registered_shapes() {
19292 let cases = [
19293 (461, vec![64, 141, 132, 124]),
19294 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
19295 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
19296 ];
19297 for (t, expected) in cases {
19298 let chunk = auto_chunk(t);
19299 let fixed = fixed_prime_chunk_ranges(t, chunk);
19300 assert_eq!(
19301 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
19302 expected
19303 );
19304 }
19305 }
19306
19307 #[test]
19308 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
19309 for t in 256..=8192 {
19310 let chunk = auto_chunk(t);
19311 let fixed = fixed_prime_chunk_ranges(t, chunk);
19312 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
19313 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
19314 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
19315 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
19316 for pair in dynamic.windows(2) {
19317 assert_eq!(pair[0].1, pair[1].0, "T={t}");
19318 }
19319 assert!(
19320 dynamic
19321 .iter()
19322 .all(|(start, end)| end - start >= PRIME_MIN_T),
19323 "T={t} sizes={:?}",
19324 sizes(&dynamic)
19325 );
19326 if dynamic.len() >= 3 {
19327 let chunk_sizes = sizes(&dynamic);
19328 assert!(
19329 chunk_sizes[0] < chunk_sizes[1],
19330 "T={t} sizes={chunk_sizes:?}"
19331 );
19332 assert!(
19333 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
19334 "T={t} sizes={chunk_sizes:?}"
19335 );
19336 }
19337 }
19338 }
19339}
19340
19341#[cfg(test)]
19342mod page_prefetch_tests {
19343 use super::{
19344 grouped_worker_prefetch_position, page_prefetch_positions,
19345 page_prefetch_window_from_values, worker_prefetch_positions,
19346 };
19347
19348 #[test]
19349 fn page_prefetch_window_keeps_existing_opt_in_default() {
19350 assert_eq!(page_prefetch_window_from_values(false, None), 0);
19351 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
19352 assert_eq!(page_prefetch_window_from_values(true, None), 1);
19353 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
19354 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
19355 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
19356 }
19357
19358 #[test]
19359 fn rolling_page_prefetch_advises_each_future_expert_once() {
19360 let advised: Vec<_> = (0..7)
19361 .flat_map(|position| page_prefetch_positions(position, 7, 3))
19362 .collect();
19363 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
19364
19365 let one_ahead: Vec<_> = (0..4)
19366 .flat_map(|position| page_prefetch_positions(position, 4, 1))
19367 .collect();
19368 assert_eq!(one_ahead, vec![1, 2, 3]);
19369 assert!(page_prefetch_positions(0, 4, 0).is_empty());
19370 }
19371
19372 #[test]
19373 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
19374 assert_eq!(grouped_worker_prefetch_position(0, None), None);
19375 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
19376 .chain(
19377 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
19378 )
19379 .collect();
19380 assert_eq!(positions, vec![0, 1, 2, 3]);
19381 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
19382 }
19383
19384 #[test]
19385 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
19386 let queued: Vec<_> = (0..8)
19387 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
19388 .collect();
19389 assert_eq!(queued, (0..8).collect::<Vec<_>>());
19390
19391 let one_at_a_time: Vec<_> = (0..4)
19392 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
19393 .collect();
19394 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
19395 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
19396 }
19397}
19398
19399pub struct G4DcSlots {
19400 x: CudaSlice<f32>,
19401 xn: CudaSlice<f32>,
19402 cur: CudaSlice<f32>,
19403 hq: CudaSlice<i8>,
19404 hd_: CudaSlice<f32>,
19405 q0: CudaSlice<f32>,
19406 k0: CudaSlice<f32>,
19407 v0: CudaSlice<f32>,
19408 q: CudaSlice<f32>,
19409 k: CudaSlice<f32>,
19410 v: CudaSlice<f32>,
19411 attn: CudaSlice<f32>,
19412 o: CudaSlice<f32>,
19413 attn_out: CudaSlice<f32>,
19414 zsh: CudaSlice<f32>,
19415 zq: CudaSlice<i8>,
19416 zd: CudaSlice<f32>,
19417 gate: CudaSlice<f32>,
19418 up: CudaSlice<f32>,
19419 act: CudaSlice<f32>,
19420 actq: CudaSlice<i8>,
19421 actd: CudaSlice<f32>,
19422 f0: CudaSlice<f32>,
19423 sn: CudaSlice<f32>,
19424 hn: CudaSlice<f32>,
19425 logits: CudaSlice<f32>,
19426}
19427
19428pub struct Step35TokenGraphState {
19433 pub graphs: Vec<(usize, crate::tp::TokenGraph)>,
19435 pub token_d: cudarc::driver::CudaSlice<u32>,
19436 pub pos_d: cudarc::driver::CudaSlice<i32>,
19437 pub logits_stage: cudarc::driver::CudaSlice<f32>,
19438 pub x: cudarc::driver::CudaSlice<f32>,
19443 pub x1: cudarc::driver::CudaSlice<f32>,
19444 pub mixed_stage: cudarc::driver::CudaSlice<f32>,
19445 pub sh_stage: cudarc::driver::CudaSlice<f32>,
19446 pub k_shadow_stage: cudarc::driver::CudaSlice<f32>,
19447 pub v_shadow_stage: cudarc::driver::CudaSlice<f32>,
19448 pub router_logits: cudarc::driver::CudaSlice<f32>,
19451 pub shexp_gate: cudarc::driver::CudaSlice<f32>,
19452 pub shexp_up: cudarc::driver::CudaSlice<f32>,
19453 pub shexp_act: cudarc::driver::CudaSlice<f32>,
19454 pub gate_sig: cudarc::driver::CudaSlice<f32>,
19455 pub dense_z: cudarc::driver::CudaSlice<f32>,
19456 pub dense_gate: cudarc::driver::CudaSlice<f32>,
19457 pub dense_up: cudarc::driver::CudaSlice<f32>,
19458 pub dense_act: cudarc::driver::CudaSlice<f32>,
19459 pub hn: cudarc::driver::CudaSlice<f32>,
19460 pub probe_mixed: cudarc::driver::CudaSlice<f32>,
19463 pub probe_x: cudarc::driver::CudaSlice<f32>,
19464 pub token_hist: cudarc::driver::CudaSlice<u32>,
19467 pub hist_idx: cudarc::driver::CudaSlice<i32>,
19468}
19469
19470impl HybridModel {
19471 pub(crate) fn step35_token_graph_step(
19482 &self,
19483 e: &Engine,
19484 token: u32,
19485 cache: &mut Cache,
19486 ) -> Result<Option<(Vec<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
19487 if !self.uses_sliding_gated_moe_program()
19488 || !crate::tp::step_tp_graph_enabled()?
19489 || !crate::tp::step_tp_dcw_enabled()?
19490 || !crate::tp::step_tp_qkv_fused_enabled()?
19491 || !crate::tp::step_tp_dev_router_enabled()?
19492 || !crate::tp::step_nvfp4_dev_routes_enabled()?
19493 {
19494 return Ok(None);
19495 }
19496 let n_embd = self.cfg.n_embd as usize;
19497 let n_vocab = self.cfg.n_vocab as usize;
19498 let eps = self.cfg.rms_eps;
19499 let n_layers = self.layers.len();
19500 let pos = cache.pos;
19501 let staged_next = pos + 1;
19502 if staged_next < 96 {
19503 return Ok(None); }
19505
19506 for il in 0..n_layers {
19509 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
19510 return Ok(None); };
19512 if tp_kv.peek_append_ring(1)?.1 {
19513 return Ok(None);
19514 }
19515 }
19516
19517 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
19520 if !fa_vec {
19521 return Ok(None);
19522 }
19523 let sp = crate::fa_split_keys(staged_next, 8);
19524 let bucket_max = (n_splits * sp).max(staged_next);
19525
19526 let mut state_guard = self
19527 .step35_token_graph
19528 .lock()
19529 .map_err(|_| "step35 token graph lock is poisoned")?;
19530 if state_guard.is_none() {
19531 let _main = e.gpu.enter_main()?;
19532 let n_expert = self
19533 .cfg
19534 .moe
19535 .as_ref()
19536 .map(|m| m.expert_count as usize)
19537 .unwrap_or(0);
19538 let n_ff_sh = self
19539 .layers
19540 .iter()
19541 .find_map(|l| match &l.ffn {
19542 crate::hybrid::Ffn::Moe(m) => m.gate_shexp.as_ref().map(|g| g.out_features()),
19543 _ => None,
19544 })
19545 .unwrap_or(0);
19546 let n_ff_dense = self
19547 .layers
19548 .iter()
19549 .find_map(|l| match &l.ffn {
19550 crate::hybrid::Ffn::Dense { ffn_gate, .. } => Some(ffn_gate.out_features()),
19551 _ => None,
19552 })
19553 .unwrap_or(0);
19554 *state_guard = Some(Step35TokenGraphState {
19555 graphs: Vec::new(),
19556 token_d: e.stream().clone_htod(&[0u32])?,
19557 pos_d: e.htod_i32(&[pos as i32])?,
19558 logits_stage: e.htod(&vec![0.0f32; n_vocab])?,
19559 x: e.htod(&vec![0.0f32; n_embd])?,
19560 x1: e.htod(&vec![0.0f32; n_embd])?,
19561 mixed_stage: e.htod(&vec![0.0f32; n_embd])?,
19562 sh_stage: e.htod(&vec![0.0f32; n_embd])?,
19563 k_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
19564 v_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
19565 router_logits: e.htod(&vec![0.0f32; n_expert.max(1)])?,
19566 shexp_gate: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19567 shexp_up: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19568 shexp_act: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19569 gate_sig: e.htod(&vec![1.0f32; 1])?,
19570 dense_z: e.htod(&vec![0.0f32; n_embd])?,
19571 dense_gate: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19572 dense_up: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19573 dense_act: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19574 hn: e.htod(&vec![0.0f32; n_embd])?,
19575 probe_mixed: e.htod(&vec![0.0f32; n_embd])?,
19576 probe_x: e.htod(&vec![0.0f32; n_embd])?,
19577 token_hist: e.stream().clone_htod(&[0u32; 16])?,
19578 hist_idx: e.htod_i32(&[0])?,
19579 });
19580 }
19581 let state = state_guard.as_mut().expect("state armed above");
19582 {
19586 let _main = e.gpu.enter_main()?;
19587 let Step35TokenGraphState {
19588 logits_stage,
19589 token_d,
19590 ..
19591 } = &mut *state;
19592 e.argmax_token_device_into(logits_stage, token_d, n_vocab)?;
19593 }
19594
19595 if state.graphs.is_empty() {
19600 self.step35_token_graph_build(e, cache, state, bucket_max)?;
19603 }
19604 {
19605 let (b, g) = state.graphs.first_mut().expect("graph built above");
19606 if *b != bucket_max {
19607 g.retarget_bucket(bucket_max)?;
19608 *b = bucket_max;
19609 }
19610 }
19611 let graph = state
19612 .graphs
19613 .first()
19614 .map(|(_, g)| g)
19615 .expect("graph built above");
19616
19617 let tg_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
19618 let t_fence = tg_timing.then(std::time::Instant::now);
19619 {
19624 let fa0 = match &self.layers[0].mixer {
19625 Mixer::Full(fa) => fa,
19626 _ => return Err("step35 token graph expects full-attention layers".into()),
19627 };
19628 let tp0 = fa0
19629 .step_tp_qkv
19630 .as_ref()
19631 .ok_or("step35 token graph lost its TP state")?;
19632 for rank in 0..tp0.runtime.devices().len() {
19633 let engine = tp0
19634 .runtime
19635 .rank_engine(rank)
19636 .ok_or("step35 token graph lost a rank engine")?;
19637 let _main = engine.gpu.enter_main()?;
19638 engine.stream().synchronize()?;
19639 }
19640 }
19641
19642 {
19644 let _main = e.gpu.enter_main()?;
19645 e.set_u32_one(&mut state.token_d, token)?;
19646 e.set_i32_one(&mut state.pos_d, pos as i32)?;
19647 }
19648 let t_launch = tg_timing.then(std::time::Instant::now);
19649 graph.launch(e)?;
19650 let t_book = tg_timing.then(std::time::Instant::now);
19651 for il in 0..n_layers {
19656 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
19657 let transaction = tp_kv.begin_transaction()?;
19658 let fa = match &self.layers[il].mixer {
19659 Mixer::Full(fa) => fa,
19660 _ => return Err("step35 token graph expects full-attention layers".into()),
19661 };
19662 let tp = fa
19663 .step_tp_qkv
19664 .as_ref()
19665 .ok_or("step35 token graph lost its TP state")?;
19666 let empty: [CudaSlice<f32>; 0] = [];
19669 tp.runtime.append_tp_kv_transaction_inner(
19670 tp_kv,
19671 transaction,
19672 &empty,
19673 &empty,
19674 1,
19675 true,
19676 )?;
19677 tp.runtime
19678 .commit_tp_kv_transaction_external(tp_kv, transaction, 1)?;
19679 if let Some(local) = cache.kv[il].as_mut() {
19681 local.len = pos + 1;
19682 let _main = e.gpu.enter_main()?;
19683 e.set_i32_one(&mut local.len_d, (pos + 1) as i32)?;
19684 }
19685 }
19686 cache.pos = pos + 1;
19687 let t_sync = tg_timing.then(std::time::Instant::now);
19688 let (logits, h_seed) = {
19689 let _main = e.gpu.enter_main()?;
19690 e.stream().synchronize()?;
19691 (e.dtoh(&state.logits_stage)?, e.clone_dtod(&state.x)?)
19692 };
19693 if let (Some(f), Some(l), Some(b), Some(sy)) = (t_fence, t_launch, t_book, t_sync) {
19694 use std::sync::atomic::{AtomicU64, Ordering};
19695 static NS: [AtomicU64; 5] = [
19696 AtomicU64::new(0),
19697 AtomicU64::new(0),
19698 AtomicU64::new(0),
19699 AtomicU64::new(0),
19700 AtomicU64::new(0),
19701 ];
19702 static CALLS: AtomicU64 = AtomicU64::new(0);
19703 let now = std::time::Instant::now();
19704 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;
19710 if calls % 100 == 0 {
19711 let avg = |i: usize| NS[i].load(Ordering::Relaxed) as f64 / calls as f64 / 1e3;
19712 eprintln!(
19713 "[tg-timing] calls={calls} fence_us={:.0} launch_us={:.0} book_us={:.0} \
19714 syncdtoh_us={:.0} total_us={:.0}",
19715 avg(0),
19716 avg(1),
19717 avg(2),
19718 avg(3),
19719 avg(4)
19720 );
19721 }
19722 }
19723 if std::env::var("MEMRA_TG_PROBE_LAYER").is_ok() {
19725 use std::io::Write;
19726 let (pm, px) = {
19727 let _main = e.gpu.enter_main()?;
19728 (e.dtoh(&state.probe_mixed)?, e.dtoh(&state.probe_x)?)
19729 };
19730 for (path, data) in [
19731 ("/root/tg-probe-mixed.bin", &pm),
19732 ("/root/tg-probe-x.bin", &px),
19733 ] {
19734 let mut fo = std::fs::OpenOptions::new()
19735 .create(true)
19736 .append(true)
19737 .open(path)?;
19738 for v in data {
19739 fo.write_all(&v.to_le_bytes())?;
19740 }
19741 }
19742 }
19743 if let Ok(path) = std::env::var("MEMRA_DUMP_HN") {
19746 let hh = {
19747 let _main = e.gpu.enter_main()?;
19748 e.dtoh(&state.hn)?
19749 };
19750 use std::io::Write;
19751 let mut fo = std::fs::OpenOptions::new()
19752 .create(true)
19753 .append(true)
19754 .open(path)?;
19755 for v in &hh {
19756 fo.write_all(&v.to_le_bytes())?;
19757 }
19758 }
19759 if std::env::var("MEMRA_STEP_TP_GRAPH_DEBUG").as_deref() == Ok("1") {
19762 for il in [0usize, 1, 44] {
19763 let tp_kv = cache.tp_kv[il].as_ref().expect("eligibility checked above");
19764 let host_len = tp_kv.staged_len();
19765 let fa = match &self.layers[il].mixer {
19766 Mixer::Full(fa) => fa,
19767 _ => continue,
19768 };
19769 let tp = fa
19770 .step_tp_qkv
19771 .as_ref()
19772 .ok_or("step35 token graph lost its TP state")?;
19773 for rank in 0..tp.runtime.devices().len() {
19774 let engine = tp
19775 .runtime
19776 .rank_engine(rank)
19777 .ok_or("step35 token graph lost a rank engine")?;
19778 let rank_cache = tp_kv.rank(rank).ok_or("debug rank cache missing")?;
19779 let _main = engine.gpu.enter_main()?;
19780 engine.stream().synchronize()?;
19781 let len_d = engine.dtoh_i32_one(rank_cache.len_d())?;
19782 let base_d = match rank_cache.base_d() {
19783 Some(b) => engine.dtoh_i32_one(b)?,
19784 None => -1,
19785 };
19786 eprintln!(
19787 "[graph-debug] pos={pos} il={il} rank={rank} host_len={host_len} \
19788 len_d={len_d} base_d={base_d}"
19789 );
19790 }
19791 }
19792 }
19793 Ok(Some((logits, h_seed)))
19794 }
19795
19796 pub(crate) fn head_split_matvec(
19802 &self,
19803 e: &Engine,
19804 hn: &CudaSlice<f32>,
19805 ) -> Result<Option<Vec<f32>>, Box<dyn std::error::Error>> {
19806 if self.head_split_fill_device(e, hn)?.is_none() {
19807 return Ok(None);
19808 }
19809 let guard = HEAD_SPLIT_WS
19810 .lock()
19811 .map_err(|_| "head split lock is poisoned")?;
19812 let ws = guard.as_ref().expect("filled above");
19813 let _main = e.gpu.enter_main()?;
19814 Ok(Some(e.dtoh(&ws.logits_e)?))
19815 }
19816
19817 fn head_split_fill_device(
19821 &self,
19822 e: &Engine,
19823 hn: &CudaSlice<f32>,
19824 ) -> Result<Option<()>, Box<dyn std::error::Error>> {
19825 use cudarc::driver::DevicePtr;
19826 let crate::model::GpuTensor::FloatBf16 { data: head, .. } = &self.output else {
19827 return Ok(None);
19828 };
19829 let Some(rank1) = self.layers.first().and_then(|l| match &l.mixer {
19830 Mixer::Full(fa) => fa
19831 .step_tp_qkv
19832 .as_ref()
19833 .and_then(|tp| tp.runtime.rank_engine(1)),
19834 _ => None,
19835 }) else {
19836 return Ok(None);
19837 };
19838 let n_embd = self.cfg.n_embd as usize;
19839 let n_vocab = self.cfg.n_vocab as usize;
19840 let half = n_vocab / 2;
19841 let mut guard = HEAD_SPLIT_WS
19842 .lock()
19843 .map_err(|_| "head split lock is poisoned")?;
19844 let pin = {
19845 let _main = e.gpu.enter_main()?;
19846 let stream = e.stream();
19847 let (ptr, _g) = head.device_ptr(&stream);
19848 ptr as u64
19849 };
19850 if guard.as_ref().is_none_or(|ws| ws.pin != pin) {
19851 let hi_rows = n_vocab - half;
19853 let (w1, hn1, y1, ev_done) = {
19854 let _r1 = rank1.gpu.enter_main()?;
19855 (
19856 rank1.alloc_u8_uninit(hi_rows * n_embd * 2)?,
19857 rank1.htod(&vec![0.0f32; n_embd])?,
19858 rank1.htod(&vec![0.0f32; hi_rows])?,
19859 rank1.ctx().new_event(None)?,
19860 )
19861 };
19862 {
19863 use cudarc::driver::sys;
19864 let src = pin + (half * n_embd * 2) as u64;
19865 let dst = {
19866 let _r1 = rank1.gpu.enter_main()?;
19867 let rstream = rank1.stream();
19868 let (d, _g) = w1.device_ptr(&rstream);
19869 d as u64
19870 };
19871 let _r1 = rank1.gpu.enter_main()?;
19872 let r = unsafe {
19873 sys::cuMemcpyAsync(
19874 dst as sys::CUdeviceptr,
19875 src as sys::CUdeviceptr,
19876 hi_rows * n_embd * 2,
19877 rank1.stream().cu_stream() as sys::CUstream,
19878 )
19879 };
19880 if r != sys::CUresult::CUDA_SUCCESS {
19881 return Err(format!("head split replica upload: {r:?}").into());
19882 }
19883 rank1.stream().synchronize()?;
19884 }
19885 let (logits_e, ev_hn) = {
19886 let _main = e.gpu.enter_main()?;
19887 (e.htod(&vec![0.0f32; n_vocab])?, e.ctx().new_event(None)?)
19888 };
19889 let (raw_hn1, raw_y1) = {
19890 let _r1 = rank1.gpu.enter_main()?;
19891 let rstream = rank1.stream();
19892 let (a, _g0) = hn1.device_ptr(&rstream);
19893 let (b, _g1) = y1.device_ptr(&rstream);
19894 (a as u64, b as u64)
19895 };
19896 let raw_logits_hi = {
19897 let _main = e.gpu.enter_main()?;
19898 let stream = e.stream();
19899 let (l, _g) = logits_e.device_ptr(&stream);
19900 l as u64 + (half * 4) as u64
19901 };
19902 *guard = Some(HeadSplit {
19903 pin,
19904 w1,
19905 hn1,
19906 y1,
19907 logits_e,
19908 ev_hn,
19909 ev_done,
19910 raw_hn1,
19911 raw_y1,
19912 raw_logits_hi,
19913 samp: None,
19914 });
19915 }
19916 let ws = guard.as_mut().expect("armed above");
19917 let hi_rows = n_vocab - half;
19918 let raw_hn = {
19920 let _main = e.gpu.enter_main()?;
19921 let stream = e.stream();
19922 let (h, _g) = hn.device_ptr(&stream);
19923 ws.ev_hn.record(&stream)?;
19924 h as u64
19925 };
19926 {
19927 let _r1 = rank1.gpu.enter_main()?;
19928 rank1.stream().wait(&ws.ev_hn)?;
19929 crate::tp::raw_copy_bytes(ws.raw_hn1, raw_hn, n_embd * 4, rank1)?;
19930 let HeadSplit { w1, hn1, y1, .. } = &mut *ws;
19931 rank1.matvec_bf16_into(w1, hn1, y1, n_embd, hi_rows)?;
19932 crate::tp::raw_copy_bytes(ws.raw_logits_hi, ws.raw_y1, hi_rows * 4, rank1)?;
19933 ws.ev_done.record(&rank1.stream())?;
19934 }
19935 {
19936 let _main = e.gpu.enter_main()?;
19937 let head_lo = head.slice(0..half * n_embd * 2);
19938 let HeadSplit { logits_e, .. } = &mut *ws;
19939 e.matvec_bf16_view_into(&head_lo, hn, logits_e, n_embd, half)?;
19941 e.stream().wait(&ws.ev_done)?;
19942 Ok(Some(()))
19943 }
19944 }
19945
19946 pub(crate) fn head_split_argmax_device(
19951 &self,
19952 e: &Engine,
19953 hn: &CudaSlice<f32>,
19954 token_d: &mut CudaSlice<u32>,
19955 ) -> Result<bool, Box<dyn std::error::Error>> {
19956 if self.head_split_fill_device(e, hn)?.is_none() {
19957 return Ok(false);
19958 }
19959 let n_vocab = self.cfg.n_vocab as usize;
19960 let guard = HEAD_SPLIT_WS
19961 .lock()
19962 .map_err(|_| "head split lock is poisoned")?;
19963 let ws = guard.as_ref().expect("filled above");
19964 let _main = e.gpu.enter_main()?;
19965 e.argmax_token_device_into(&ws.logits_e, token_d, n_vocab)?;
19966 Ok(true)
19967 }
19968
19969 pub(crate) fn head_split_sample_device(
19975 &self,
19976 e: &Engine,
19977 hn: &CudaSlice<f32>,
19978 token_d: &mut CudaSlice<u32>,
19979 samp: &crate::decode_batch::DevSamp,
19980 ctr: u32,
19981 ) -> Result<bool, Box<dyn std::error::Error>> {
19982 if self.head_split_fill_device(e, hn)?.is_none() {
19983 return Ok(false);
19984 }
19985 let n_vocab = self.cfg.n_vocab as usize;
19986 let guard = HEAD_SPLIT_WS
19987 .lock()
19988 .map_err(|_| "head split lock is poisoned")?;
19989 let mut guard = guard;
19990 let ws = guard.as_mut().expect("filled above");
19991 let _main = e.gpu.enter_main()?;
19992 if ws.samp.is_none() {
19993 ws.samp = Some(SampScratch {
19994 pb: e.zeros(n_vocab)?,
19995 th: e.zeros(1)?,
19996 z: e.zeros(1)?,
19997 mx: e.zeros(1)?,
19998 rows: e.htod_i32(&[0i32])?,
19999 });
20000 }
20001 let filtered = samp.top_k > 0 || samp.top_p < 1.0 || samp.min_p > 0.0;
20002 let HeadSplit {
20003 logits_e,
20004 samp: scratch,
20005 ..
20006 } = &mut *ws;
20007 let sc = scratch.as_mut().expect("armed above");
20008 if filtered {
20009 e.filter_stats(
20010 logits_e, n_vocab, &sc.rows, &mut sc.th, &mut sc.z, &mut sc.mx, n_vocab, 1,
20011 samp.temp, samp.top_k, samp.top_p, samp.min_p,
20012 )?;
20013 let SampScratch { pb, th, mx, .. } = sc;
20014 e.gumbel_perturb_filtered_col(
20015 logits_e, 0, pb, n_vocab, samp.seed, ctr, samp.temp, mx, th, 0,
20016 )?;
20017 } else {
20018 e.gumbel_perturb_col(logits_e, 0, &mut sc.pb, n_vocab, samp.seed, ctr, samp.temp)?;
20019 }
20020 e.argmax_token_device_col(&sc.pb, 0, n_vocab, token_d, 0)?;
20021 Ok(true)
20022 }
20023
20024 pub(crate) fn head_split_logits_dtoh(
20027 &self,
20028 e: &Engine,
20029 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
20030 let guard = HEAD_SPLIT_WS
20031 .lock()
20032 .map_err(|_| "head split lock is poisoned")?;
20033 let ws = guard.as_ref().ok_or("head split logits not armed")?;
20034 let _main = e.gpu.enter_main()?;
20035 Ok(e.dtoh(&ws.logits_e)?)
20036 }
20037
20038 pub fn step35_token_graph_chunk(
20047 &self,
20048 e: &Engine,
20049 token: u32,
20050 k_target: usize,
20051 cache: &mut Cache,
20052 ) -> Result<Option<(Vec<u32>, Vec<f32>)>, Box<dyn std::error::Error>> {
20053 if !self.uses_sliding_gated_moe_program()
20054 || !crate::tp::step_tp_graph_enabled()?
20055 || !crate::tp::step_tp_dcw_enabled()?
20056 || !crate::tp::step_tp_qkv_fused_enabled()?
20057 || !crate::tp::step_tp_dev_router_enabled()?
20058 || !crate::tp::step_nvfp4_dev_routes_enabled()?
20059 {
20060 return Ok(None);
20061 }
20062 let n_layers = self.layers.len();
20063 let pos = cache.pos;
20064 let staged_next = pos + 1;
20065 if staged_next < 96 {
20066 return Ok(None);
20067 }
20068 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
20071 if !fa_vec {
20072 return Ok(None);
20073 }
20074 let sp = crate::fa_split_keys(staged_next, 8);
20075 let bucket_max = (n_splits * sp).max(staged_next);
20076 let to_boundary = bucket_max.saturating_sub(staged_next) + 1;
20077 let mut k = k_target.min(to_boundary).min(16);
20078 if k < 2 {
20079 return Ok(None);
20080 }
20081 for il in 0..n_layers {
20083 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
20084 return Ok(None);
20085 };
20086 while k >= 2 && tp_kv.peek_append_ring(k)?.1 {
20087 k -= 1;
20088 }
20089 if k < 2 {
20090 return Ok(None);
20091 }
20092 }
20093
20094 let mut state_guard = self
20095 .step35_token_graph
20096 .lock()
20097 .map_err(|_| "step35 token graph lock is poisoned")?;
20098 let Some(state) = state_guard.as_mut() else {
20099 return Ok(None); };
20101 if state.graphs.is_empty() {
20102 return Ok(None);
20103 }
20104 {
20105 let (b, g) = state.graphs.first_mut().expect("checked above");
20106 if *b != bucket_max {
20107 g.retarget_bucket(bucket_max)?;
20108 *b = bucket_max;
20109 }
20110 }
20111 let graph = state.graphs.first().map(|(_, g)| g).expect("checked above");
20112
20113 {
20115 let fa0 = match &self.layers[0].mixer {
20116 Mixer::Full(fa) => fa,
20117 _ => return Err("step35 token graph expects full-attention layers".into()),
20118 };
20119 let tp0 = fa0
20120 .step_tp_qkv
20121 .as_ref()
20122 .ok_or("step35 token graph lost its TP state")?;
20123 for rank in 0..tp0.runtime.devices().len() {
20124 let engine = tp0
20125 .runtime
20126 .rank_engine(rank)
20127 .ok_or("step35 token graph lost a rank engine")?;
20128 let _main = engine.gpu.enter_main()?;
20129 engine.stream().synchronize()?;
20130 }
20131 }
20132
20133 {
20136 let _main = e.gpu.enter_main()?;
20137 e.set_u32_one(&mut state.token_d, token)?;
20138 e.set_i32_one(&mut state.pos_d, pos as i32)?;
20139 e.set_i32_one(&mut state.hist_idx, 0)?;
20140 }
20141 for _ in 0..k {
20142 graph.launch(e)?;
20143 }
20144 for il in 0..n_layers {
20146 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
20147 let transaction = tp_kv.begin_transaction()?;
20148 let fa = match &self.layers[il].mixer {
20149 Mixer::Full(fa) => fa,
20150 _ => return Err("step35 token graph expects full-attention layers".into()),
20151 };
20152 let tp = fa
20153 .step_tp_qkv
20154 .as_ref()
20155 .ok_or("step35 token graph lost its TP state")?;
20156 let empty: [CudaSlice<f32>; 0] = [];
20157 tp.runtime.append_tp_kv_transaction_inner(
20158 tp_kv,
20159 transaction,
20160 &empty,
20161 &empty,
20162 k,
20163 true,
20164 )?;
20165 tp.runtime
20166 .commit_tp_kv_transaction_external(tp_kv, transaction, k)?;
20167 if let Some(local) = cache.kv[il].as_mut() {
20168 local.len = pos + k;
20169 let _main = e.gpu.enter_main()?;
20170 e.set_i32_one(&mut local.len_d, (pos + k) as i32)?;
20171 }
20172 }
20173 cache.pos = pos + k;
20174 let (hist, logits) = {
20175 let _main = e.gpu.enter_main()?;
20176 e.stream().synchronize()?;
20177 (e.dtoh_u32(&state.token_hist)?, e.dtoh(&state.logits_stage)?)
20178 };
20179 Ok(Some((hist[..k].to_vec(), logits)))
20180 }
20181}
20182
20183impl HybridModel {
20184 #[allow(clippy::too_many_arguments)]
20189 fn step35_token_graph_build(
20190 &self,
20191 e: &Engine,
20192 cache: &mut Cache,
20193 state: &mut Step35TokenGraphState,
20194 bucket_max: usize,
20195 ) -> Result<(), Box<dyn std::error::Error>> {
20196 use cudarc::driver::DevicePtr;
20197 let n_embd = self.cfg.n_embd as usize;
20198 let eps = self.cfg.rms_eps;
20199 let n_layers = self.layers.len();
20200 let started = std::time::Instant::now();
20201 if !crate::router_kernel_on() {
20202 return Err(
20203 "step35 token graph requires the router kernel (MEMRA_ROUTER_KERNEL=0)".into(),
20204 );
20205 }
20206 if !Engine::bf16_mmv_on() || n_embd % 8 != 0 {
20207 return Err("step35 token graph requires MEMRA_BF16_MMV bf16-resident matvecs".into());
20208 }
20209
20210 let embd_gpu = self
20212 .embd_gpu_try(e)
20213 .ok_or("step35 token graph could not upload the device embed table")?;
20214 let embd_qtype = match self.embd.ggml_type {
20215 memra_gguf::GgmlType::BF16 => crate::QT_BF16,
20216 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
20217 other => return Err(format!("token graph embed dtype {other:?} unhandled").into()),
20218 };
20219 let embd_row_bytes = self.embd.raw.len() / self.cfg.n_vocab as usize;
20220
20221 let (p_mixed, p_kshadow, p_vshadow) = {
20223 let _main = e.gpu.enter_main()?;
20224 let stream = e.stream();
20225 let (a, _g) = state.mixed_stage.device_ptr(&stream);
20226 let (b, _g) = state.k_shadow_stage.device_ptr(&stream);
20227 let (c, _g) = state.v_shadow_stage.device_ptr(&stream);
20228 (a as u64, b as u64, c as u64)
20229 };
20230
20231 crate::tp::token_graph_build_begin()?;
20232 let mut group_id: u32 = 0;
20233 for il in 0..n_layers {
20234 let layer = &self.layers[il];
20235 let fa = match &layer.mixer {
20236 Mixer::Full(fa) => fa,
20237 _ => return Err("step35 token graph expects full-attention layers".into()),
20238 };
20239 let tp = fa
20240 .step_tp_qkv
20241 .as_ref()
20242 .ok_or("step35 token graph lost its TP state")?;
20243 let attention = tp
20244 .attention
20245 .as_ref()
20246 .ok_or("step35 token graph lost its attention aux")?;
20247 let geometry = self.step35_geom(il);
20248 let window = geometry.window.map(|w| w as usize);
20249 let head_dim = geometry.head_dim_k as usize;
20250 let heads = geometry.n_head as usize;
20251 let kv_heads = geometry.n_head_kv as usize;
20252 let ranks = tp.runtime.devices().len();
20253 let local_heads = heads / ranks;
20254 let local_kv_heads = kv_heads / ranks;
20255 let layer_bucket = window.map(|w| bucket_max.min(w)).unwrap_or(bucket_max);
20256 let use_gate_shards =
20257 attention.gate_shards.is_some() || attention.gate_shards_bf16.is_some();
20258 if !use_gate_shards {
20259 return Err("step35 token graph requires the fused gate shards".into());
20260 }
20261
20262 let ws_index = tp
20263 .runtime
20264 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
20265 let ws_mutex = tp.runtime.decode_v2_workspace();
20266 let mut ws_guard = ws_mutex
20267 .lock()
20268 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
20269 let ws = ws_guard
20270 .get_mut(ws_index)
20271 .ok_or("step TP decode v2 workspace missing after ensure")?;
20272 tp.runtime
20273 .decode_v2_arm_token_mirrors(ws, p_mixed, (p_kshadow, p_vshadow))?;
20274 let mut rope_freqs = Vec::with_capacity(ranks);
20275 for rank in 0..ranks {
20276 let engine = tp
20277 .runtime
20278 .rank_engine(rank)
20279 .ok_or("step35 token graph lost a rank engine")?;
20280 rope_freqs.push(if geometry.rope_factors {
20281 self.step35_aux
20282 .as_ref()
20283 .and_then(|aux| aux.rope_freqs(engine))
20284 } else {
20285 None
20286 });
20287 }
20288 let gate_shards_arg = if let Some(shards) = attention.gate_shards.as_deref() {
20289 Some(crate::tp::StepTpGateShards::F32(shards))
20290 } else {
20291 attention
20292 .gate_shards_bf16
20293 .as_deref()
20294 .map(crate::tp::StepTpGateShards::Bf16)
20295 };
20296
20297 let decode_input = attention
20299 .decode_input
20300 .as_ref()
20301 .ok_or("step35 token graph requires the replicated decode input")?;
20302 let mut decode_input = decode_input
20303 .lock()
20304 .map_err(|_| "replicated decode input lock is poisoned")?;
20305 if ws.h_stage.is_none() {
20307 return Err(
20308 "step35 token graph requires the stage flow armed (run eager dcw first)".into(),
20309 );
20310 }
20311 {
20312 let state_x = &mut state.x;
20313 let token_d = &state.token_d;
20314 let pos_d = &state.pos_d;
20315 crate::tp::graph_section(e, None, || {
20316 let _main = e.gpu.enter_main()?;
20317 if il == 0 {
20318 e.embed_gather_device_into(
20319 embd_gpu,
20320 token_d,
20321 state_x,
20322 n_embd,
20323 embd_qtype,
20324 embd_row_bytes,
20325 )?;
20326 }
20327 {
20328 let h_stage = ws.h_stage.as_mut().expect("stage armed checked above");
20329 e.rms_norm(
20330 state_x,
20331 layer.attn_norm.float_data(),
20332 h_stage,
20333 n_embd,
20334 1,
20335 eps,
20336 )?;
20337 }
20338 {
20339 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
20340 let mut dst = pos_stage.slice_mut(0..1);
20341 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
20342 }
20343 Ok(())
20344 })?;
20345 }
20346
20347 group_id += 1;
20349 for rank in 0..ranks {
20350 let engine = tp
20351 .runtime
20352 .rank_engine(rank)
20353 .ok_or("step35 token graph lost a rank engine")?;
20354 {
20355 let ceiling = window
20360 .map(|w| cache.max_ctx.min(w))
20361 .unwrap_or(cache.max_ctx);
20362 let _main = engine.gpu.enter_main()?;
20363 engine.fa_dcw_pool_ensure(
20364 head_dim,
20365 local_heads,
20366 local_kv_heads,
20367 ceiling.min(2048),
20368 )?;
20369 engine.fa_dcw_pool_ensure(head_dim, local_heads, local_kv_heads, ceiling)?;
20370 engine.fa_dcw_pool_ensure(
20371 head_dim,
20372 local_heads,
20373 local_kv_heads,
20374 layer_bucket,
20375 )?;
20376 }
20377 let runtime = &tp.runtime;
20378 let q_norm = &attention.q_norm;
20379 let k_norm = &attention.k_norm;
20380 let gate_ref = gate_shards_arg.as_ref();
20381 crate::tp::graph_section(engine, Some(group_id), || {
20382 runtime.decode_v2_input_qkv_rank(
20383 ws,
20384 &state.pos_d,
20385 &mut decode_input,
20386 &tp.q,
20387 &tp.k,
20388 &tp.v,
20389 q_norm,
20390 k_norm,
20391 head_dim,
20392 geometry.n_rot as usize,
20393 geometry.rope_base,
20394 &rope_freqs,
20395 eps,
20396 gate_ref,
20397 true,
20398 false,
20399 rank,
20400 None,
20401 )?;
20402 let distributed = cache.tp_kv[il]
20405 .as_mut()
20406 .ok_or("step35 token graph lost a TP cache")?;
20407 let (kv_dim_k, kv_dim_v) = (distributed.kv_dim_k(), distributed.kv_dim_v());
20408 let (ktb, vtb) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
20409 let capacity = distributed.physical_capacity();
20410 {
20411 let rank_cache = distributed
20412 .rank_mut(rank)
20413 .ok_or("step35 token graph lost a rank cache")?;
20414 let (k_plane, v_plane, len_d, base_d) =
20415 rank_cache.planes_and_counters_mut();
20416 engine.append_kv_quantized_dcw(
20417 &ws.k[rank],
20418 &ws.v_raw[rank],
20419 k_plane,
20420 v_plane,
20421 len_d,
20422 base_d,
20423 kv_dim_k,
20424 kv_dim_v,
20425 ktb,
20426 vtb,
20427 )?;
20428 }
20429 {
20430 let rank_cache = distributed
20431 .rank_mut(rank)
20432 .ok_or("step35 token graph lost a rank cache")?;
20433 engine.inc_i32(rank_cache.len_d_mut())?;
20434 }
20435 let rank_cache = distributed
20436 .rank(rank)
20437 .ok_or("step35 token graph lost a rank cache")?;
20438 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * ktb);
20439 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * vtb);
20440 engine.fa_decode_dcw(
20445 &ws.q[rank],
20446 &k_ring,
20447 &v_ring,
20448 &mut ws.attn_out[rank],
20449 head_dim,
20450 local_heads,
20451 local_kv_heads,
20452 rank_cache.len_d(),
20453 rank_cache.base_d(),
20454 window.unwrap_or(0),
20455 layer_bucket,
20456 geometry.attention_scale(),
20457 ktb,
20458 vtb,
20459 None,
20460 )?;
20461 engine.attn_head_gate(
20462 &ws.attn_out[rank],
20463 &ws.gate[rank],
20464 &mut ws.gated[rank],
20465 None,
20466 head_dim,
20467 local_heads,
20468 1,
20469 )?;
20470 runtime.decode_v2_finish_rank_partial(ws, &tp.o, true, rank)?;
20471 Ok(())
20472 })?;
20473 }
20474
20475 {
20477 let root = tp
20478 .runtime
20479 .rank_engine(0)
20480 .ok_or("step35 token graph lost the root engine")?;
20481 let runtime = &tp.runtime;
20482 crate::tp::graph_section(root, None, || runtime.decode_v2_finish_root_fused(ws))?;
20483 }
20484 drop(ws_guard);
20485 drop(decode_input);
20486
20487 let probe_layer: Option<usize> = std::env::var("MEMRA_TG_PROBE_LAYER")
20488 .ok()
20489 .and_then(|v| v.parse().ok());
20490 if probe_layer == Some(il) {
20491 let Step35TokenGraphState {
20492 mixed_stage,
20493 probe_mixed,
20494 ..
20495 } = &mut *state;
20496 crate::tp::graph_section(e, None, || {
20497 let _main = e.gpu.enter_main()?;
20498 let mut dst = probe_mixed.slice_mut(0..n_embd);
20499 e.stream()
20500 .memcpy_dtod(&mixed_stage.slice(0..n_embd), &mut dst)?;
20501 Ok(())
20502 })?;
20503 }
20504
20505 match &layer.ffn {
20507 crate::hybrid::Ffn::Dense {
20508 ffn_gate,
20509 ffn_up,
20510 ffn_down,
20511 } => {
20512 let n_ff = ffn_gate.out_features();
20513 let lim = self.cfg.clamp_shexp_at(il as u32);
20514 if lim.is_some() {
20518 return Err("step35 token graph dense FFN with clamp unsupported".into());
20519 }
20520 let (wg_d, wu_d, wd_d) = match (ffn_gate, ffn_up, ffn_down) {
20521 (
20522 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
20523 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
20524 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
20525 ) => (wg, wu, wd),
20526 _ => {
20527 return Err(
20528 "step35 token graph dense FFN requires bf16-resident weights"
20529 .into(),
20530 );
20531 }
20532 };
20533 crate::tp::graph_section(e, None, || {
20534 let _main = e.gpu.enter_main()?;
20535 let Step35TokenGraphState {
20536 x,
20537 x1,
20538 mixed_stage,
20539 dense_z,
20540 dense_gate,
20541 dense_up,
20542 dense_act,
20543 sh_stage,
20544 ..
20545 } = &mut *state;
20546 e.add_rms_norm(
20547 x,
20548 mixed_stage,
20549 layer.post_attn_norm.float_data(),
20550 x1,
20551 dense_z,
20552 n_embd,
20553 1,
20554 eps,
20555 )?;
20556 e.matvec_bf16_into(wg_d, dense_z, dense_gate, n_embd, n_ff)?;
20560 e.matvec_bf16_into(wu_d, dense_z, dense_up, n_embd, n_ff)?;
20561 Self::ffn_act_lim(
20562 e, &self.cfg, dense_gate, dense_up, 1.0, 1.0, lim, dense_act, n_ff,
20563 )?;
20564 e.matvec_bf16_into(wd_d, dense_act, sh_stage, n_ff, n_embd)?;
20565 e.add(x1, sh_stage, x, n_embd)?;
20566 Ok(())
20567 })?;
20568 }
20569 crate::hybrid::Ffn::Moe(m) => {
20570 let moe = self
20571 .cfg
20572 .moe
20573 .as_ref()
20574 .ok_or("step35 token graph needs moe cfg")?;
20575 let n_expert = moe.expert_count as usize;
20576 let n_used = moe.expert_used_count as usize;
20577 let sigmoid = self
20578 .cfg
20579 .sigmoid_router()
20580 .ok_or("step35 token graph needs the sigmoid router")?;
20581 let step_tp = m
20582 .step_tp
20583 .as_ref()
20584 .ok_or("step35 token graph needs TP experts")?;
20585 let bank = match &step_tp.experts {
20586 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => bank,
20587 _ => return Err("step35 token graph needs the NVFP4 bank".into()),
20588 };
20589 let routes_ws_mutex = bank.device_workspace_handle();
20590 let mut routes_guard = routes_ws_mutex
20591 .lock()
20592 .map_err(|_| "routes workspace lock is poisoned")?;
20593 let routes_ws = routes_guard
20594 .as_mut()
20595 .ok_or("step35 token graph requires the routes workspace warmed")?;
20596 routes_ws.arm_stages(e, bank.input_width, n_used)?;
20597 step_tp.runtime.routes_arm_raw(bank, routes_ws)?;
20598 let p_z = {
20599 let root = step_tp
20600 .runtime
20601 .rank_engine(0)
20602 .ok_or("routes root engine missing")?;
20603 let _main = root.gpu.enter_main()?;
20604 let stream = root.stream();
20605 let in_stage = routes_ws
20606 .in_stage_handle()
20607 .ok_or("routes in stage not armed")?;
20608 let (a, _g) = in_stage.device_ptr(&stream);
20609 a as u64
20610 };
20611 let local_out = bank.expert_width / ranks;
20612
20613 crate::tp::graph_section(e, None, || {
20615 let _main = e.gpu.enter_main()?;
20616 {
20617 let in_stage = routes_ws
20618 .in_stage_mut()
20619 .ok_or("routes in stage not armed")?;
20620 let Step35TokenGraphState {
20621 x, x1, mixed_stage, ..
20622 } = &mut *state;
20623 e.add_rms_norm(
20624 x,
20625 mixed_stage,
20626 layer.post_attn_norm.float_data(),
20627 x1,
20628 in_stage,
20629 n_embd,
20630 1,
20631 eps,
20632 )?;
20633 }
20634 {
20635 let z_ref = routes_ws
20636 .in_stage_handle()
20637 .ok_or("routes in stage not armed")?;
20638 e.router_gemv_into(
20639 m.gate_inp.float_data(),
20640 z_ref,
20641 &mut state.router_logits,
20642 n_embd,
20643 n_expert,
20644 1,
20645 )?;
20646 }
20647 let (sel_e, w_e) = routes_ws
20648 .dev_route_e_mut()
20649 .ok_or("routes staging not armed")?;
20650 e.moe_router_sigmoid_topk_into(
20651 &state.router_logits,
20652 1,
20653 n_expert,
20654 n_used,
20655 m.active_count(),
20656 &m.exp_probs_b_dev,
20657 &m.active_experts_dev,
20658 sigmoid.0,
20659 sigmoid.1,
20660 sel_e,
20661 w_e,
20662 )?;
20663 Ok(())
20664 })?;
20665
20666 group_id += 1;
20668 for rank in 0..ranks {
20669 let engine = step_tp
20670 .runtime
20671 .rank_engine(rank)
20672 .ok_or("routes rank engine missing")?;
20673 let runtime = &step_tp.runtime;
20674 crate::tp::graph_section(engine, Some(group_id), || {
20675 runtime.routes_rank_section(
20676 bank,
20677 routes_ws,
20678 p_z,
20679 local_out,
20680 n_used,
20681 step_tp.activation_limit,
20682 rank,
20683 )
20684 })?;
20685 }
20686
20687 {
20689 let root = step_tp
20690 .runtime
20691 .rank_engine(0)
20692 .ok_or("routes root engine missing")?;
20693 let runtime = &step_tp.runtime;
20694 crate::tp::graph_section(root, None, || {
20695 runtime.routes_root_section(bank, routes_ws)
20696 })?;
20697 }
20698
20699 let lim_sh = self.cfg.clamp_shexp_at(il as u32);
20703 let (wg_sh, wu_sh, wd_sh) = match (&m.gate_shexp, &m.up_shexp, &m.down_shexp) {
20704 (
20705 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
20706 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
20707 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
20708 ) => (wg, wu, wd),
20709 _ => {
20710 return Err(
20711 "step35 token graph shexp requires bf16-resident weights".into()
20712 );
20713 }
20714 };
20715 let n_ff_sh = m
20716 .gate_shexp
20717 .as_ref()
20718 .expect("matched Some above")
20719 .out_features();
20720 let gate_inp_shexp = m.gate_inp_shexp.as_ref();
20723 crate::tp::graph_section(e, None, || {
20724 let _main = e.gpu.enter_main()?;
20725 let (z_ref, out_stage) = routes_ws
20726 .in_and_out_stages_mut()
20727 .ok_or("routes stages not armed")?;
20728 let Step35TokenGraphState {
20729 x,
20730 x1,
20731 sh_stage,
20732 shexp_gate,
20733 shexp_up,
20734 shexp_act,
20735 gate_sig,
20736 ..
20737 } = &mut *state;
20738 e.matvec_bf16_dual_into(
20739 wg_sh, wu_sh, z_ref, shexp_gate, shexp_up, n_embd, n_ff_sh,
20740 )?;
20741 Self::ffn_act_lim(
20742 e, &self.cfg, shexp_gate, shexp_up, 1.0, 1.0, lim_sh, shexp_act,
20743 n_ff_sh,
20744 )?;
20745 e.matvec_bf16_into(wd_sh, shexp_act, sh_stage, n_ff_sh, n_embd)?;
20746 if let Some(gate_w) = gate_inp_shexp {
20747 e.sigmoid_dot_rows_into(
20748 z_ref,
20749 gate_w.float_data(),
20750 gate_sig,
20751 n_embd,
20752 1,
20753 )?;
20754 }
20755 e.add_scaled_rows(sh_stage, gate_sig, out_stage, n_embd, 1)?;
20756 e.add(x1, out_stage, x, n_embd)?;
20757 Ok(())
20758 })?;
20759 }
20760 }
20761 if probe_layer == Some(il) {
20762 let Step35TokenGraphState { x, probe_x, .. } = &mut *state;
20763 crate::tp::graph_section(e, None, || {
20764 let _main = e.gpu.enter_main()?;
20765 let mut dst = probe_x.slice_mut(0..n_embd);
20766 e.stream().memcpy_dtod(&x.slice(0..n_embd), &mut dst)?;
20767 Ok(())
20768 })?;
20769 }
20770 }
20771
20772 let head = match &self.output {
20774 crate::model::GpuTensor::FloatBf16 { data, .. } => data,
20775 _ => return Err("step35 token graph head requires the bf16-resident output".into()),
20776 };
20777 crate::tp::graph_section(e, None, || {
20778 let _main = e.gpu.enter_main()?;
20779 let Step35TokenGraphState {
20780 x,
20781 hn,
20782 logits_stage,
20783 token_d,
20784 pos_d,
20785 token_hist,
20786 hist_idx,
20787 ..
20788 } = &mut *state;
20789 e.rms_norm(x, self.output_norm.float_data(), hn, n_embd, 1, eps)?;
20790 e.matvec_bf16_into(head, hn, logits_stage, n_embd, self.cfg.n_vocab as usize)?;
20791 e.argmax_token_device_into(logits_stage, token_d, self.cfg.n_vocab as usize)?;
20797 e.u32_hist_append(token_d, token_hist, hist_idx)?;
20798 e.inc_i32(pos_d)?;
20799 Ok(())
20800 })?;
20801
20802 let graph = crate::tp::token_graph_build_finish()?;
20803 state.graphs.push((bucket_max, graph));
20804 eprintln!(
20805 "[step35-token-graph] built bucket={bucket_max} layers={n_layers} \
20806 build_ms={:.0} performance_claim=false",
20807 started.elapsed().as_secs_f64() * 1e3
20808 );
20809 Ok(())
20810 }
20811}