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 pub fn hidden_postnorm_row(
2289 &self,
2290 e: &Engine,
2291 hiddens: &CudaSlice<f32>,
2292 row: usize,
2293 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2294 let n_embd = self.cfg.n_embd as usize;
2295 let mut x1 = e.uninit(n_embd)?;
2296 e.copy_view_into(
2297 &mut x1,
2298 0,
2299 &hiddens.slice(row * n_embd..(row + 1) * n_embd),
2300 n_embd,
2301 )?;
2302 if crate::spec::spec_hpost() {
2303 return Ok(e.dtoh(&x1)?);
2304 }
2305 let mut hn = e.uninit(n_embd)?;
2306 e.rms_norm(
2307 &x1,
2308 self.output_norm.float_data(),
2309 &mut hn,
2310 n_embd,
2311 1,
2312 self.cfg.rms_eps,
2313 )?;
2314 Ok(e.dtoh(&hn)?)
2315 }
2316
2317 fn prime_chunk_ppn(
2341 &self,
2342 e: &Engine,
2343 tokens: &[u32],
2344 cache: &mut Cache,
2345 seq_end: usize,
2346 fence: &[usize],
2347 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2348 let rt = crate::pp::PpNRt::get(e)?;
2349 let n_st = fence.len() - 1;
2350 assert_eq!(
2351 rt.n_stages(),
2352 n_st,
2353 "PpNRt stage count {} != fence stages {n_st}",
2354 rt.n_stages()
2355 );
2356 let n_embd = self.cfg.n_embd as usize;
2357 let t = tokens.len();
2358 let base = cache.pos;
2359 debug_assert!(
2360 seq_end >= base + t,
2361 "prime_chunk_ppn: seq_end must cover this chunk"
2362 );
2363 let payload = t * n_embd;
2364 let caller_stream = e.stream();
2368 rt.fence_stages_behind(&caller_stream)?;
2369
2370 if n_st == 2 {
2371 let slot =
2372 self.prime_pp2_stage0_enqueue(e, rt, tokens, cache, seq_end, fence, base, false)?;
2373 let x =
2374 self.prime_pp2_stage1_enqueue(e, rt, slot, t, cache, seq_end, fence, base, false)?;
2375 let out = {
2376 rt.bind_stage(1)?;
2377 let _st1 = rt.enter(1);
2378 let e1 = rt.engine(1, e);
2379 self.prime_chunk_epilogue(e1, x, t, cache)?
2380 };
2381 rt.publish_to(1, &caller_stream)?;
2382 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2383 return Ok(out);
2384 }
2385
2386 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2387
2388 let mut slot = {
2390 let _st0 = rt.enter(0);
2391 let e0 = rt.engine(0, e);
2392 let pos_d = e0.htod_i32(&pos)?;
2393 let x = self.embed(e0, tokens)?;
2394 let x =
2395 self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2396 rt.tx(0, &x, payload)?
2397 };
2399
2400 for s in 1..n_st - 1 {
2402 let _st = rt.enter(s);
2403 let es = rt.engine(s, e);
2404 let pos_d = es.htod_i32(&pos)?;
2405 let x = rt.rx(s - 1, slot, payload)?;
2406 let x = self.prime_layers(
2407 es,
2408 x,
2409 fence[s],
2410 fence[s + 1],
2411 &pos_d,
2412 t,
2413 base,
2414 cache,
2415 seq_end,
2416 )?;
2417 slot = rt.tx(s, &x, payload)?;
2418 }
2419
2420 let _stl = rt.enter(n_st - 1);
2422 let el = rt.engine(n_st - 1, e);
2423 let pos_d = el.htod_i32(&pos)?;
2424 let x = rt.rx(n_st - 2, slot, payload)?;
2425 let x = self.prime_layers(
2426 el,
2427 x,
2428 fence[n_st - 1],
2429 fence[n_st],
2430 &pos_d,
2431 t,
2432 base,
2433 cache,
2434 seq_end,
2435 )?;
2436 let out = self.prime_chunk_epilogue(el, x, t, cache)?;
2437 rt.publish_to(n_st - 1, &caller_stream)?;
2443 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2444 Ok(out)
2445 }
2446
2447 fn prime_pp2_stage0_enqueue(
2448 &self,
2449 e: &Engine,
2450 rt: &crate::pp::PpNRt,
2451 tokens: &[u32],
2452 cache: &mut Cache,
2453 seq_end: usize,
2454 fence: &[usize],
2455 base: usize,
2456 pipelined: bool,
2457 ) -> Result<usize, Box<dyn std::error::Error>> {
2458 let t = tokens.len();
2459 let n_embd = self.cfg.n_embd as usize;
2460 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2461 rt.bind_stage(0)?;
2462 let _st0 = rt.enter(0);
2463 let e0 = rt.engine(0, e);
2464 let pos_d = e0.htod_i32(&pos)?;
2465 let x = self.embed(e0, tokens)?;
2466 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2467 let x = self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2468 if pipelined {
2469 rt.tx_pipelined(0, &x, t * n_embd)
2470 } else {
2471 rt.tx(0, &x, t * n_embd)
2472 }
2473 }
2474
2475 fn prime_pp2_stage1_enqueue(
2476 &self,
2477 e: &Engine,
2478 rt: &crate::pp::PpNRt,
2479 slot: usize,
2480 t: usize,
2481 cache: &mut Cache,
2482 seq_end: usize,
2483 fence: &[usize],
2484 base: usize,
2485 pipelined: bool,
2486 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2487 let n_embd = self.cfg.n_embd as usize;
2488 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2489 rt.bind_stage(1)?;
2490 let _st1 = rt.enter(1);
2491 let e1 = rt.engine(1, e);
2492 let pos_d = e1.htod_i32(&pos)?;
2493 let x = rt.rx(0, slot, t * n_embd)?;
2494 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2495 self.prime_layers(e1, x, fence[1], fence[2], &pos_d, t, base, cache, seq_end)
2496 }
2497
2498 pub fn prime_chunk_captured(
2514 &self,
2515 e: &Engine,
2516 x_in: &CudaSlice<f32>,
2517 pos_d: &CudaSlice<i32>,
2518 t: usize,
2519 cache: &mut Cache,
2520 len_d: &CudaSlice<i32>,
2521 logits_out: &mut CudaSlice<f32>,
2522 h_seed_out: &mut CudaSlice<f32>,
2523 ) -> Result<(), Box<dyn std::error::Error>> {
2524 let cfg = &self.cfg;
2525 let n_embd = cfg.n_embd as usize;
2526 let eps = cfg.rms_eps;
2527 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
2528 let mut x = e.uninit(t * n_embd)?;
2529 e.copy_into(&mut x, 0, x_in, t * n_embd)?;
2530 for (il, layer) in self.layers.iter().enumerate() {
2531 let mut h = e.uninit(t * n_embd)?;
2532 let mut hx16: Option<CudaSlice<u8>> = None;
2533 if f16fuse {
2534 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2535 e.rms_norm_f16out(
2536 &x,
2537 layer.attn_norm.float_data(),
2538 &mut h,
2539 &mut b16,
2540 n_embd,
2541 t,
2542 eps,
2543 )?;
2544 hx16 = Some(b16);
2545 } else {
2546 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
2547 }
2548 let mixed = match &layer.mixer {
2549 Mixer::Full(fa) => {
2553 self.full_attn_prime(e, fa, &h, hx16.as_ref(), pos_d, t, cache, il, t)?
2554 }
2555 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2556 Mixer::Linear(la) => {
2557 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2558 let g4 = match hx16.as_ref() {
2559 Some(xh) => e.matmul_group_xh(&ws, &h, xh, t)?,
2560 None => e.matmul_group(&ws, &h, t)?,
2561 };
2562 self.linear_attn_prime_core_pad(e, la, g4, t, cache, il, Some(len_d))?
2563 }
2564 };
2565 let mut x1 = e.uninit(t * n_embd)?;
2566 e.add(&x, &mixed, &mut x1, t * n_embd)?;
2567 let mut z = e.uninit(t * n_embd)?;
2568 let mut zx16: Option<CudaSlice<u8>> = None;
2569 if f16fuse {
2570 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2571 e.rms_norm_f16out(
2572 &x1,
2573 layer.post_attn_norm.float_data(),
2574 &mut z,
2575 &mut b16,
2576 n_embd,
2577 t,
2578 eps,
2579 )?;
2580 zx16 = Some(b16);
2581 } else {
2582 e.rms_norm(
2583 &x1,
2584 layer.post_attn_norm.float_data(),
2585 &mut z,
2586 n_embd,
2587 t,
2588 eps,
2589 )?;
2590 }
2591 let ffn_out = match &layer.ffn {
2592 crate::hybrid::Ffn::Dense {
2593 ffn_gate,
2594 ffn_up,
2595 ffn_down,
2596 } => {
2597 let n_ff = ffn_gate.out_features();
2598 let mut g2 = match &zx16 {
2599 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], &z, xh, t)?,
2600 None => e.matmul_group(&[ffn_gate, ffn_up], &z, t)?,
2601 };
2602 let up = g2.pop().unwrap();
2603 let gate = g2.pop().unwrap();
2604 let mut act = e.uninit(t * n_ff)?;
2605 Self::ffn_act_lim(
2607 e,
2608 &self.cfg,
2609 &gate,
2610 &up,
2611 1.0,
2612 1.0,
2613 self.cfg.clamp_shexp_at(il as u32),
2614 &mut act,
2615 t * n_ff,
2616 )?;
2617 e.matmul(ffn_down, &act, t)?
2618 }
2619 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
2620 };
2621 let mut x2 = e.uninit(t * n_embd)?;
2622 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
2623 x = x2;
2624 }
2625 if !crate::spec::spec_hpost() {
2627 e.row_gather_dev(&x, h_seed_out, len_d, n_embd)?;
2628 }
2629 let mut hn = e.uninit(t * n_embd)?;
2630 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2631 if crate::spec::spec_hpost() {
2632 e.row_gather_dev(&hn, h_seed_out, len_d, n_embd)?;
2633 }
2634 let mut hlast = e.uninit(n_embd)?;
2635 e.row_gather_dev(&hn, &mut hlast, len_d, n_embd)?;
2636 let logits = e.matmul(&self.output, &hlast, 1)?;
2637 let nv = logits.len();
2638 e.copy_into(logits_out, 0, &logits, nv)?;
2639 Ok(())
2640 }
2641
2642 fn step35_prime_batch_on() -> bool {
2643 std::env::var("MEMRA_STEP35_PRIME_BATCH").as_deref() != Ok("0")
2644 }
2645
2646 #[allow(clippy::too_many_arguments)]
2649 fn step35_prime_batch_layers(
2650 &self,
2651 e: &Engine,
2652 mut x: CudaSlice<f32>,
2653 lo: usize,
2654 hi: usize,
2655 ts: &[usize],
2656 offs: &[usize],
2657 pos_ds: &[CudaSlice<i32>],
2658 caches: &mut [&mut Cache],
2659 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2660 let cfg = &self.cfg;
2661 let n_embd = cfg.n_embd as usize;
2662 let eps = cfg.rms_eps;
2663 let b = ts.len();
2664 let total: usize = ts.iter().sum();
2665 let f16fuse = crate::f16_ffi::pp_f16_enabled() && total >= 16;
2666
2667 let split = |e: &Engine,
2668 y: &CudaSlice<f32>,
2669 dim: usize|
2670 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2671 let mut out = Vec::with_capacity(b);
2672 for s in 0..b {
2673 let mut ys = e.uninit(ts[s] * dim)?;
2674 e.copy_view_into(
2675 &mut ys,
2676 0,
2677 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2678 ts[s] * dim,
2679 )?;
2680 out.push(ys);
2681 }
2682 Ok(out)
2683 };
2684
2685 for il in lo..hi {
2686 let layer = &self.layers[il];
2687 let Mixer::Full(fa) = &layer.mixer else {
2688 return Err(format!("step35 layer {il} is not full-attn — corrupt config").into());
2689 };
2690
2691 let mut h = e.uninit(total * n_embd)?;
2692 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2693 if f16fuse {
2694 e.rms_norm_f16out(
2695 &x,
2696 layer.attn_norm.float_data(),
2697 &mut h,
2698 &mut hx16,
2699 n_embd,
2700 total,
2701 eps,
2702 )?;
2703 } else {
2704 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, total, eps)?;
2705 }
2706
2707 let gate_w = fa
2711 .attn_gate
2712 .as_ref()
2713 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
2714 let mut g4 = if f16fuse {
2715 e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, &hx16, total)?
2716 } else {
2717 e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, total)?
2718 };
2719 let gate = g4.pop().unwrap();
2720 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2721 (0..b).map(|_| Vec::with_capacity(3)).collect();
2722 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g4) {
2723 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2724 parts[s].push(ys);
2725 }
2726 }
2727 let gates = split(e, &gate, gate_w.out_features())?;
2728 let geometry = self.step35_geom(il);
2729 let hd = geometry.head_dim_k as usize;
2730 let nh = geometry.n_head as usize;
2731 let mut ag_cat = e.uninit(total * nh * hd)?;
2732 for (s, (g3s, gate)) in parts.into_iter().zip(gates).enumerate() {
2733 let ag = self.step35_attn_pre_wo(
2734 e,
2735 fa,
2736 g3s,
2737 None,
2738 Some(&gate),
2739 &pos_ds[s],
2740 ts[s],
2741 Some(&mut *caches[s]),
2742 il,
2743 ts[s],
2744 )?;
2745 e.copy_into(&mut ag_cat, offs[s] * nh * hd, &ag, ts[s] * nh * hd)?;
2746 }
2747 let mixed = e.matmul(&fa.wo, &ag_cat, total)?;
2748
2749 let mut x1 = e.uninit(total * n_embd)?;
2750 let mut z = e.uninit(total * n_embd)?;
2751 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2752 if f16fuse {
2753 e.add_rms_norm_f16out(
2754 &x,
2755 &mixed,
2756 layer.post_attn_norm.float_data(),
2757 &mut x1,
2758 &mut z,
2759 &mut zx16,
2760 n_embd,
2761 total,
2762 eps,
2763 )?;
2764 } else {
2765 e.add(&x, &mixed, &mut x1, total * n_embd)?;
2766 e.rms_norm(
2767 &x1,
2768 layer.post_attn_norm.float_data(),
2769 &mut z,
2770 n_embd,
2771 total,
2772 eps,
2773 )?;
2774 }
2775
2776 let ffn_out = match &layer.ffn {
2777 crate::hybrid::Ffn::Dense {
2778 ffn_gate,
2779 ffn_up,
2780 ffn_down,
2781 } => {
2782 let n_ff = ffn_gate.out_features();
2783 let mut g2 = if f16fuse {
2784 e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?
2785 } else {
2786 e.matmul_group(&[ffn_gate, ffn_up], &z, total)?
2787 };
2788 let up = g2.pop().unwrap();
2789 let gate = g2.pop().unwrap();
2790 let mut act = e.uninit(total * n_ff)?;
2791 let d_lim = cfg.clamp_shexp_at(il as u32);
2792 if Self::f16out_on(e, total) && cfg.m3.is_none() && d_lim.is_none() {
2793 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2794 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2795 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2796 Some(y) => y,
2797 None => e.matmul(ffn_down, &act, total)?,
2798 }
2799 } else {
2800 Self::ffn_act_lim(
2801 e,
2802 cfg,
2803 &gate,
2804 &up,
2805 1.0,
2806 1.0,
2807 d_lim,
2808 &mut act,
2809 total * n_ff,
2810 )?;
2811 e.matmul(ffn_down, &act, total)?
2812 }
2813 }
2814 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il(e, m, &z, total, il as u16)?,
2815 };
2816 let mut x2 = e.uninit(total * n_embd)?;
2817 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2818 x = x2;
2819 }
2820 Ok(x)
2821 }
2822
2823 fn step35_prime_batch_epilogue(
2824 &self,
2825 e: &Engine,
2826 x: CudaSlice<f32>,
2827 ts: &[usize],
2828 offs: &[usize],
2829 caches: &mut [&mut Cache],
2830 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2831 let n_embd = self.cfg.n_embd as usize;
2832 let total: usize = ts.iter().sum();
2833 let mut hn = e.uninit(total * n_embd)?;
2834 e.rms_norm(
2835 &x,
2836 self.output_norm.float_data(),
2837 &mut hn,
2838 n_embd,
2839 total,
2840 self.cfg.rms_eps,
2841 )?;
2842
2843 let hidden_src = if crate::spec::spec_hpost() { &hn } else { &x };
2844 let mut out = Vec::with_capacity(ts.len());
2845 for s in 0..ts.len() {
2846 let mut hidden = e.uninit(ts[s] * n_embd)?;
2847 e.copy_view_into(
2848 &mut hidden,
2849 0,
2850 &hidden_src.slice(offs[s] * n_embd..(offs[s] + ts[s]) * n_embd),
2851 ts[s] * n_embd,
2852 )?;
2853 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2854 let mut h_seed = e.uninit(n_embd)?;
2855 e.copy_view_into(
2856 &mut h_seed,
2857 0,
2858 &hidden_src.slice(last0..last0 + n_embd),
2859 n_embd,
2860 )?;
2861 let mut hlast = e.uninit(n_embd)?;
2863 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2864 let logits = e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?;
2865 caches[s].pos += ts[s];
2866 out.push((logits, h_seed, hidden));
2867 }
2868 Ok(out)
2869 }
2870
2871 fn step35_prime_cache_batch(
2872 &self,
2873 e: &Engine,
2874 prompts: &[&[u32]],
2875 caches: &mut [&mut Cache],
2876 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2877 validate_step_prime_batch_modes(
2878 step_tp_prefill_enabled()?,
2879 step_ep_grouped_prefill_enabled()?,
2880 )?;
2881 if crate::pp::pp_host_bounce_active()
2882 && (!crate::pp::prime_pp_on() || crate::pp::pp_cuts(self.layers.len()).is_none())
2883 {
2884 return Err(
2885 "step35_prime_cache_batch: MEMRA_PP_HOST_BOUNCE=1 requires a valid prime \
2886 stage split; refusing an unsplit remote-weight walk"
2887 .into(),
2888 );
2889 }
2890 if !Self::step35_prime_batch_on() {
2891 return Err("step35 batched prime is disabled (MEMRA_STEP35_PRIME_BATCH=0)".into());
2892 }
2893 if caches.iter().any(|c| c.pos != 0) {
2894 return Err(
2895 "step35 batched prime currently supports complete fresh prompts only; \
2896 continuation/tick chunks require per-request queued_after"
2897 .into(),
2898 );
2899 }
2900
2901 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2902 for &t in &ts {
2903 assert!(
2904 t >= PRIME_MIN_T,
2905 "step35 batched prime needs T >= {PRIME_MIN_T}"
2906 );
2907 }
2908 for (s, c) in caches.iter().enumerate() {
2909 assert!(
2910 ts[s] <= c.max_ctx,
2911 "step35 batched prime exceeds cache max_ctx"
2912 );
2913 }
2914 let offs: Vec<usize> = ts
2915 .iter()
2916 .scan(0usize, |a, &t| {
2917 let o = *a;
2918 *a += t;
2919 Some(o)
2920 })
2921 .collect();
2922 let total: usize = ts.iter().sum();
2923 let payload = total * self.cfg.n_embd as usize;
2924 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2925 let positions: Vec<Vec<i32>> = ts.iter().map(|&t| (0..t as i32).collect()).collect();
2926 let upload_positions =
2927 |e: &Engine| -> Result<Vec<CudaSlice<i32>>, Box<dyn std::error::Error>> {
2928 positions
2929 .iter()
2930 .map(|p| e.htod_i32(p))
2931 .collect::<Result<_, _>>()
2932 };
2933
2934 static ONCE: std::sync::Once = std::sync::Once::new();
2935 ONCE.call_once(|| {
2936 eprintln!(
2937 "[step35-prime-batch] first concat prime: B={} tokens={total}",
2938 prompts.len()
2939 );
2940 });
2941
2942 let out = if !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
2943 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
2944 let rt = crate::pp::PpNRt::get(e)?;
2945 let n_st = fence.len() - 1;
2946 assert_eq!(
2947 rt.n_stages(),
2948 n_st,
2949 "step35 prime batch stage count mismatch"
2950 );
2951 let caller_stream = e.stream();
2952 rt.fence_stages_behind(&caller_stream)?;
2953
2954 let mut slot = {
2955 let _st0 = rt.enter(0);
2956 let e0 = rt.engine(0, e);
2957 let pos_ds = upload_positions(e0)?;
2958 let x = self.embed(e0, &cat_tokens)?;
2959 let x = self.step35_prime_batch_layers(
2960 e0, x, fence[0], fence[1], &ts, &offs, &pos_ds, caches,
2961 )?;
2962 rt.tx(0, &x, payload)?
2963 };
2964 for s in 1..n_st - 1 {
2965 let _st = rt.enter(s);
2966 let es = rt.engine(s, e);
2967 let pos_ds = upload_positions(es)?;
2968 let x = rt.rx(s - 1, slot, payload)?;
2969 let x = self.step35_prime_batch_layers(
2970 es,
2971 x,
2972 fence[s],
2973 fence[s + 1],
2974 &ts,
2975 &offs,
2976 &pos_ds,
2977 caches,
2978 )?;
2979 slot = rt.tx(s, &x, payload)?;
2980 }
2981
2982 let _stl = rt.enter(n_st - 1);
2983 let el = rt.engine(n_st - 1, e);
2984 let pos_ds = upload_positions(el)?;
2985 let x = rt.rx(n_st - 2, slot, payload)?;
2986 let x = self.step35_prime_batch_layers(
2987 el,
2988 x,
2989 fence[n_st - 1],
2990 fence[n_st],
2991 &ts,
2992 &offs,
2993 &pos_ds,
2994 caches,
2995 )?;
2996 let out = self.step35_prime_batch_epilogue(el, x, &ts, &offs, caches)?;
2997 rt.publish_to(n_st - 1, &caller_stream)?;
2998 crate::pp::STEP35_PRIME_BATCH_SPLITS
2999 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3000 out
3001 } else {
3002 let pos_ds = upload_positions(e)?;
3003 let x = self.embed(e, &cat_tokens)?;
3004 let x = self.step35_prime_batch_layers(
3005 e,
3006 x,
3007 0,
3008 self.layers.len(),
3009 &ts,
3010 &offs,
3011 &pos_ds,
3012 caches,
3013 )?;
3014 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
3015 }
3016 } else {
3017 let pos_ds = upload_positions(e)?;
3018 let x = self.embed(e, &cat_tokens)?;
3019 let x = self.step35_prime_batch_layers(
3020 e,
3021 x,
3022 0,
3023 self.layers.len(),
3024 &ts,
3025 &offs,
3026 &pos_ds,
3027 caches,
3028 )?;
3029 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
3030 };
3031 crate::pp::STEP35_PRIME_BATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3032 Ok(out)
3033 }
3034
3035 pub fn prime_cache_batch(
3052 &self,
3053 e: &Engine,
3054 prompts: &[&[u32]],
3055 caches: &mut [&mut Cache],
3056 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
3057 if crate::pp::pp_cuts(self.layers.len()).is_some()
3058 && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
3059 {
3060 return Err("pipeline rewrite is not qualified for batched prime".into());
3061 }
3062 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::CarriedPrime) {
3063 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::DecodeEager) {
3064 return Err("neither batched-prime nor eager rewrite is qualified".into());
3065 }
3066 if prompts.len() != caches.len() {
3067 return Err("prime fallback prompt/cache shape mismatch".into());
3068 }
3069 static ONCE: std::sync::Once = std::sync::Once::new();
3070 ONCE.call_once(|| {
3071 eprintln!(
3072 "[rewrite] carried-prime.v1 unqualified; using individual native eager primes"
3073 );
3074 });
3075 return prompts
3076 .iter()
3077 .copied()
3078 .zip(caches.iter_mut())
3079 .map(|(prompt, cache)| self.prime_cache(e, prompt, cache, 0))
3080 .collect();
3081 }
3082 let cfg = &self.cfg;
3083 let n_embd = cfg.n_embd as usize;
3084 let eps = cfg.rms_eps;
3085 let b = prompts.len();
3086 assert!(b >= 1 && b == caches.len());
3087 let pos0s: Vec<usize> = caches.iter().map(|c| c.pos).collect();
3088 let carried = pos0s.iter().any(|&p| p > 0);
3089 if self.uses_gemma_program() {
3095 return Err(
3096 "prime_cache_batch: gemma4 has no batched prime core (per-layer \
3097 swa/global geometry, softcapped head) — use gemma4_prime per sequence"
3098 .into(),
3099 );
3100 }
3101 if self.uses_sliding_gated_moe_program() {
3104 return self.step35_prime_cache_batch(e, prompts, caches);
3105 }
3106 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
3107 for &t in &ts {
3108 assert!(
3109 t >= PRIME_MIN_T,
3110 "prime_cache_batch needs T >= {PRIME_MIN_T}"
3111 );
3112 }
3113 for (s, c) in caches.iter().enumerate() {
3114 assert!(
3115 c.pos + ts[s] <= c.max_ctx,
3116 "prime_cache_batch: prompt exceeds cache max_ctx"
3117 );
3118 }
3119 let total: usize = ts.iter().sum();
3120 let offs: Vec<usize> = ts
3121 .iter()
3122 .scan(0usize, |a, &t| {
3123 let o = *a;
3124 *a += t;
3125 Some(o)
3126 })
3127 .collect();
3128 let pos_ds: Vec<CudaSlice<i32>> = ts
3130 .iter()
3131 .zip(&pos0s)
3132 .map(|(&t, &p0)| e.htod_i32(&(p0 as i32..(p0 + t) as i32).collect::<Vec<_>>()))
3133 .collect::<Result<_, _>>()?;
3134 let split = |e: &Engine,
3136 y: &CudaSlice<f32>,
3137 dim: usize|
3138 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3139 let mut out = Vec::with_capacity(b);
3140 for s in 0..b {
3141 let mut ys = e.uninit(ts[s] * dim)?;
3142 e.copy_view_into(
3143 &mut ys,
3144 0,
3145 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
3146 ts[s] * dim,
3147 )?;
3148 out.push(ys);
3149 }
3150 Ok(out)
3151 };
3152
3153 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
3154 let mut x = self.embed(e, &cat_tokens)?; for (il, layer) in self.layers.iter().enumerate() {
3156 let mut h = e.uninit(total * n_embd)?;
3157 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3158 e.rms_norm_f16out(
3159 &x,
3160 layer.attn_norm.float_data(),
3161 &mut h,
3162 &mut hx16,
3163 n_embd,
3164 total,
3165 eps,
3166 )?;
3167 let mut mixed = e.uninit(total * n_embd)?;
3169 match &layer.mixer {
3170 Mixer::Full(fa) => {
3171 let g3 = e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], &h, &hx16, total)?;
3172 let geometry = self.cfg.full_attention_geometry_at(il as u32);
3178 let (n_head, n_head_kv, head_dim) = (
3179 geometry.n_head as usize,
3180 geometry.n_head_kv as usize,
3181 geometry.head_dim_k as usize,
3182 );
3183 let fa_scale = geometry.attention_scale();
3184 let use_favl = !carried
3185 && (2..=8).contains(&b)
3186 && (head_dim == 256 || head_dim == 128)
3187 && geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ
3188 && std::env::var("MEMRA_NOFA").is_err()
3189 && std::env::var("MEMRA_FA_FLOOR").is_err()
3190 && std::env::var("MEMRA_FA_PP_W2").as_deref() != Ok("1")
3191 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0")
3192 && std::env::var("MEMRA_FA_VL").as_deref() != Ok("0");
3193 if use_favl {
3194 let (qf_w, kf_w, vf_w) = (
3195 fa.wq.out_features(),
3196 fa.wk.out_features(),
3197 fa.wv.out_features(),
3198 );
3199 memra_gguf::config::check_fused_q_gate_extent(qf_w, head_dim, n_head, 1)?;
3204 struct APre {
3205 q: CudaSlice<f32>,
3206 gate: Option<CudaSlice<f32>>,
3207 qn: CudaSlice<f32>,
3208 kn: CudaSlice<f32>,
3209 }
3210 let mut aps = Vec::with_capacity(b);
3211 for &t in ts.iter().take(b) {
3212 aps.push(APre {
3213 q: e.uninit(t * n_head * head_dim)?,
3214 gate: Some(e.uninit(t * n_head * head_dim)?),
3215 qn: e.uninit(t * n_head * head_dim)?,
3216 kn: e.uninit(t * n_head_kv * head_dim)?,
3217 });
3218 }
3219 let (kv_dim_k, kv_dim_v, ktb, vtb) = {
3220 let kvl = caches[0].kv[il].as_ref().unwrap();
3221 (kvl.kv_dim_k, kvl.kv_dim_v, kvl.k_tok_bytes, kvl.v_tok_bytes)
3222 };
3223 let pargs: Vec<crate::AttnPreVl> = (0..b)
3224 .map(|s| {
3225 let (o, t) = (offs[s], ts[s]);
3226 let kvl = caches[s].kv[il].as_ref().unwrap();
3227 assert!(
3228 kvl.len == 0 && kvl.len + t <= caches[s].max_ctx,
3229 "prime_cache_batch attn vl: fresh + capacity"
3230 );
3231 crate::AttnPreVl {
3232 qf: e.addr_f32v(&g3[0].slice(o * qf_w..(o + t) * qf_w)),
3233 kf: e.addr_f32v(&g3[1].slice(o * kf_w..(o + t) * kf_w)),
3234 vf: e.addr_f32v(&g3[2].slice(o * vf_w..(o + t) * vf_w)),
3235 q: e.addr_f32(&aps[s].q),
3236 gate: e.addr_f32(aps[s].gate.as_ref().unwrap()),
3237 qn: e.addr_f32(&aps[s].qn),
3238 kn: e.addr_f32(&aps[s].kn),
3239 kc: e.addr_u8(&kvl.k),
3240 vc: e.addr_u8(&kvl.v),
3241 t: t as i32,
3242 pad: 0,
3243 }
3244 })
3245 .collect();
3246 e.attn_pre_vl8(
3247 &pargs,
3248 fa.q_norm.float_data(),
3249 fa.k_norm.float_data(),
3250 head_dim,
3251 geometry.n_rot as usize,
3252 n_head,
3253 n_head_kv,
3254 self.cfg.rms_eps,
3255 geometry.rope_base,
3256 1.0,
3257 kv_dim_k,
3258 kv_dim_v,
3259 ktb,
3260 vtb,
3261 )?;
3262 for s in 0..b {
3263 let kvl = caches[s].kv[il].as_mut().unwrap();
3264 kvl.len += ts[s];
3265 let new_len = kvl.len as i32;
3266 e.set_i32_one(&mut kvl.len_d, new_len)?;
3267 }
3268 let mut attns = Vec::with_capacity(b);
3269 let mut mirrors = Vec::with_capacity(b);
3270 for &t in ts.iter().take(b) {
3271 attns.push(e.uninit(t * n_head * head_dim)?);
3272 let n = t * n_head_kv * head_dim;
3273 mirrors.push((e.alloc_u8_uninit(n * 2)?, e.alloc_u8_uninit(n * 2)?));
3274 }
3275 let fa3_on = match std::env::var("MEMRA_FA3").as_deref() {
3278 Ok("0") => false,
3279 Ok("1") => {
3283 crate::refuse_portable_force(
3284 "MEMRA_FA3=1",
3285 "the sm_90a fa3/bf16 kernels",
3286 );
3287 true
3288 }
3289 _ => cfg!(memra_hopper_mma),
3290 };
3291 if fa3_on {
3292 let mut q16s = Vec::with_capacity(b);
3293 let mut v16s = Vec::with_capacity(b);
3294 for s in 0..b {
3295 let t = ts[s];
3296 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
3297 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
3298 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3299 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
3300 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3301 e.f32_to_bf16_v(
3302 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
3303 &mut v16,
3304 t * n_head_kv * head_dim,
3305 )?;
3306 q16s.push(q16);
3307 v16s.push((k16, v16));
3308 }
3309 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
3310 let mut kp = qp;
3311 let mut vp = qp;
3312 let mut op = [core::ptr::null_mut::<f32>(); 8];
3313 let mut tsv = [0i32; 8];
3314 for s in 0..b {
3315 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
3316 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
3317 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
3318 op[s] = e.addr_f32(&attns[s]) as *mut f32;
3319 tsv[s] = ts[s] as i32;
3320 }
3321 let rc = unsafe {
3322 crate::fa3_vl_raw(
3323 qp.as_ptr(),
3324 kp.as_ptr(),
3325 vp.as_ptr(),
3326 op.as_ptr(),
3327 tsv.as_ptr(),
3328 b as i32,
3329 n_head as i32,
3330 n_head_kv as i32,
3331 head_dim as i32,
3332 fa_scale,
3333 e.stream().cu_stream() as *mut core::ffi::c_void,
3334 )
3335 };
3336 if rc != 0 {
3337 return Err(format!("memra_fa3_vl rc={rc}").into());
3338 }
3339 } else {
3340 let fargs: Vec<crate::FaSeqVl> = (0..b)
3341 .map(|s| crate::FaSeqVl {
3342 q: e.addr_f32(&aps[s].qn),
3343 k16: e.addr_u8(&mirrors[s].0),
3344 v16: e.addr_u8(&mirrors[s].1),
3345 o: e.addr_f32(&attns[s]),
3346 kf: e.addr_f32(&aps[s].kn),
3347 vf: e.addr_f32v(
3348 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
3349 ),
3350 t: ts[s] as i32,
3351 pad: 0,
3352 })
3353 .collect();
3354 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
3355 }
3356 for (s, attn) in attns.into_iter().enumerate() {
3357 let (attn_g, ag16) = self.full_attn_prime_post_fa(
3358 e,
3359 attn,
3360 &aps[s].gate,
3361 ts[s],
3362 n_head,
3363 head_dim,
3364 )?;
3365 let mut done = false;
3366 if let Some(xh) = &ag16 {
3367 done = e.try_f16_gemm_pre_into_off(
3368 &fa.wo,
3369 xh,
3370 ts[s],
3371 &mut mixed,
3372 offs[s] * n_embd,
3373 )?;
3374 }
3375 if !done {
3376 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3377 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3378 }
3379 }
3380 } else {
3381 let mut parts: Vec<Vec<CudaSlice<f32>>> =
3382 (0..b).map(|_| Vec::new()).collect();
3383 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
3384 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
3385 parts[s].push(ys);
3386 }
3387 }
3388 for (s, g3s) in parts.into_iter().enumerate() {
3389 let (attn_g, ag16) = self.full_attn_prime_core_inner(
3391 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
3392 )?;
3393 let mut done = false;
3394 if let Some(xh) = &ag16 {
3395 done = e.try_f16_gemm_pre_into_off(
3396 &fa.wo,
3397 xh,
3398 ts[s],
3399 &mut mixed,
3400 offs[s] * n_embd,
3401 )?;
3402 }
3403 if !done {
3404 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3405 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3406 }
3407 }
3408 }
3409 }
3410 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
3411 Mixer::Linear(la) => {
3412 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3417 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
3418 let outs =
3419 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
3420 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
3421 let (o, t) = (offs[s], ts[s]);
3422 let mut done = false;
3423 if let Some(xh) = &gn16 {
3424 done = e.try_f16_gemm_pre_into_off(
3425 &la.ssm_out,
3426 xh,
3427 t,
3428 &mut mixed,
3429 o * n_embd,
3430 )?;
3431 }
3432 if !done {
3433 let m = e.matmul(&la.ssm_out, &gn, t)?;
3434 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
3435 }
3436 }
3437 }
3438 }
3439 let mut x1 = e.uninit(total * n_embd)?;
3440 let mut z = e.uninit(total * n_embd)?;
3441 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3442 e.add_rms_norm_f16out(
3443 &x,
3444 &mixed,
3445 layer.post_attn_norm.float_data(),
3446 &mut x1,
3447 &mut z,
3448 &mut zx16,
3449 n_embd,
3450 total,
3451 eps,
3452 )?;
3453 let ffn_out = match &layer.ffn {
3454 crate::hybrid::Ffn::Dense {
3455 ffn_gate,
3456 ffn_up,
3457 ffn_down,
3458 } => {
3459 let n_ff = ffn_gate.out_features();
3460 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
3461 let up = g2.pop().unwrap();
3462 let gate = g2.pop().unwrap();
3463 let mut act = e.uninit(total * n_ff)?;
3464 let d_lim = self.cfg.clamp_shexp_at(il as u32);
3468 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
3469 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
3470 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
3471 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
3472 Some(y) => y,
3473 None => e.matmul(ffn_down, &act, total)?,
3474 }
3475 } else {
3476 Self::ffn_act_lim(
3477 e,
3478 &self.cfg,
3479 &gate,
3480 &up,
3481 1.0,
3482 1.0,
3483 d_lim,
3484 &mut act,
3485 total * n_ff,
3486 )?;
3487 e.matmul(ffn_down, &act, total)?
3488 }
3489 }
3490 crate::hybrid::Ffn::Moe(m) => {
3491 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
3492 }
3493 };
3494 let mut x2 = e.uninit(total * n_embd)?;
3495 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
3496 x = x2;
3497 }
3498 let mut hn = e.uninit(total * n_embd)?;
3500 e.rms_norm(
3501 &x,
3502 self.output_norm.float_data(),
3503 &mut hn,
3504 n_embd,
3505 total,
3506 eps,
3507 )?;
3508 let mut hcat = e.uninit(b * n_embd)?;
3514 for s in 0..b {
3515 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3516 e.copy_view_into(
3517 &mut hcat,
3518 s * n_embd,
3519 &hn.slice(last0..last0 + n_embd),
3520 n_embd,
3521 )?;
3522 }
3523 let logits_cat = if b >= 2 {
3524 e.try_f16_gemm(&self.output, &hcat, b)?
3525 } else {
3526 None
3527 };
3528 let logits_host: Option<Vec<f32>> = match &logits_cat {
3529 Some(lc) => Some(e.dtoh(lc)?),
3530 None => None,
3531 };
3532 let n_vocab = self.output.out_features();
3533 let mut hidden_all = if crate::spec::spec_hpost() {
3534 split(e, &hn, n_embd)?
3535 } else {
3536 split(e, &x, n_embd)?
3537 };
3538 let mut out = Vec::with_capacity(b);
3539 for s in 0..b {
3540 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3541 let mut h_seed = e.uninit(n_embd)?;
3542 if !crate::spec::spec_hpost() {
3543 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
3544 } else {
3545 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3546 }
3547 let logits = match &logits_host {
3548 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
3549 None => {
3550 let mut hlast = e.uninit(n_embd)?;
3551 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3552 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
3553 }
3554 };
3555 caches[s].pos += ts[s];
3556 out.push((logits, h_seed, hidden_all.remove(0)));
3557 }
3558 Ok(out)
3559 }
3560
3561 #[allow(clippy::too_many_arguments)]
3572 fn full_attn_prime(
3573 &self,
3574 e: &Engine,
3575 fa: &FullAttnLayer,
3576 h: &CudaSlice<f32>,
3577 hx: Option<&CudaSlice<u8>>,
3578 pos_d: &CudaSlice<i32>,
3579 t: usize,
3580 cache: &mut Cache,
3581 il: usize,
3582 seq_end: usize,
3583 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3584 if self.uses_sliding_gated_moe_program() {
3585 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
3586 }
3587 let g3 = match hx {
3592 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
3593 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
3594 };
3595 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
3596 }
3597
3598 fn full_attn_prime_core(
3602 &self,
3603 e: &Engine,
3604 fa: &FullAttnLayer,
3605 g3: Vec<CudaSlice<f32>>,
3606 pos_d: &CudaSlice<i32>,
3607 t: usize,
3608 cache: &mut Cache,
3609 il: usize,
3610 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3611 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
3612 if let Some(xh) = &ag16 {
3613 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
3614 return Ok(y);
3615 }
3616 }
3617 Ok(e.matmul(&fa.wo, &attn_g, t)?)
3618 }
3619
3620 fn full_attn_prime_core_inner(
3621 &self,
3622 e: &Engine,
3623 fa: &FullAttnLayer,
3624 g3: Vec<CudaSlice<f32>>,
3625 pos_d: &CudaSlice<i32>,
3626 t: usize,
3627 cache: &mut Cache,
3628 il: usize,
3629 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3630 let cfg = &self.cfg;
3631 let geometry = cfg.full_attention_geometry_at(il as u32);
3632 let n_head = geometry.n_head as usize;
3633 let n_head_kv = geometry.n_head_kv as usize;
3634 let head_dim = geometry.head_dim_k as usize;
3635 let scale = geometry.attention_scale();
3636 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
3637 let AttnPre { q, k, v, gate } = pre;
3638 let mut attn = e.uninit(t * n_head * head_dim)?;
3639 self.full_attn_prime_fa_dispatch(
3640 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
3641 )?;
3642 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
3643 }
3644
3645 #[allow(clippy::type_complexity)]
3649 fn full_attn_prime_pre_fa(
3650 &self,
3651 e: &Engine,
3652 fa: &FullAttnLayer,
3653 mut g3: Vec<CudaSlice<f32>>,
3654 pos_d: &CudaSlice<i32>,
3655 t: usize,
3656 cache: &mut Cache,
3657 il: usize,
3658 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3659 let cfg = &self.cfg;
3660 let geometry = cfg.full_attention_geometry_at(il as u32);
3661 let n_head = geometry.n_head as usize;
3662 let n_head_kv = geometry.n_head_kv as usize;
3663 let head_dim = geometry.head_dim_k as usize;
3664 let eps = cfg.rms_eps;
3665
3666 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3670 let v = g3.pop().unwrap();
3671 let mut k = g3.pop().unwrap();
3672 let qf = g3.pop().unwrap();
3673 let (mut q, gate) = if gated {
3674 let mut q = e.uninit(t * n_head * head_dim)?;
3675 let mut gate = e.uninit(t * n_head * head_dim)?;
3676 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3677 (q, Some(gate))
3678 } else {
3679 (qf, None)
3680 };
3681
3682 let mut qn = e.uninit(t * n_head * head_dim)?;
3683 e.rms_norm(
3684 &q,
3685 fa.q_norm.float_data(),
3686 &mut qn,
3687 head_dim,
3688 n_head * t,
3689 eps,
3690 )?;
3691 q = qn;
3692 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3693 e.rms_norm(
3694 &k,
3695 fa.k_norm.float_data(),
3696 &mut kn,
3697 head_dim,
3698 n_head_kv * t,
3699 eps,
3700 )?;
3701 k = kn;
3702 let rope_dims = geometry.n_rot as usize;
3703 e.rope_neox(
3704 &mut q,
3705 pos_d,
3706 head_dim,
3707 rope_dims,
3708 n_head,
3709 t,
3710 geometry.rope_base,
3711 1.0,
3712 )?;
3713 e.rope_neox(
3714 &mut k,
3715 pos_d,
3716 head_dim,
3717 rope_dims,
3718 n_head_kv,
3719 t,
3720 geometry.rope_base,
3721 1.0,
3722 )?;
3723
3724 {
3727 let kvl = cache.kv[il].as_mut().unwrap();
3728 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3729 e.append_kv_quantized_rows(
3730 &k,
3731 &v,
3732 &mut kvl.k,
3733 &mut kvl.v,
3734 kvl.len,
3735 t,
3736 kvl.kv_dim_k,
3737 kvl.kv_dim_v,
3738 kvl.k_tok_bytes,
3739 kvl.v_tok_bytes,
3740 crate::Engine::kv_fp8_on(),
3741 )?;
3742 kvl.len += t;
3743 let new_len = kvl.len as i32;
3744 e.set_i32_one(&mut kvl.len_d, new_len)?;
3745 }
3746
3747 let base_len = {
3748 let kvl = cache.kv[il].as_ref().unwrap();
3749 kvl.len - t };
3751 Ok((AttnPre { q, k, v, gate }, base_len))
3752 }
3753
3754 #[allow(clippy::too_many_arguments)]
3761 fn full_attn_prime_fa_dispatch(
3762 &self,
3763 e: &Engine,
3764 q: &CudaSlice<f32>,
3765 k: &CudaSlice<f32>,
3766 v: &CudaSlice<f32>,
3767 attn: &mut CudaSlice<f32>,
3768 base_len: usize,
3769 t: usize,
3770 cache: &mut Cache,
3771 il: usize,
3772 head_dim: usize,
3773 n_head: usize,
3774 n_head_kv: usize,
3775 scale: f32,
3776 ) -> Result<(), Box<dyn std::error::Error>> {
3777 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
3790 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3791 e.sdpa_naive(
3792 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3793 )?;
3794 } else {
3795 e.fa_prefill(
3796 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3797 )?;
3798 }
3799 return Ok(());
3800 }
3801 let kvl = cache.kv[il].as_ref().unwrap();
3802 let t_kv = base_len + t;
3803 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
3804 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
3805 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3809 e.sdpa_naive_quantized_view(
3810 q,
3811 &k_view,
3812 &v_view,
3813 attn,
3814 head_dim,
3815 n_head,
3816 n_head_kv,
3817 t,
3818 t_kv,
3819 scale,
3820 true,
3821 kvl.k_tok_bytes,
3822 kvl.v_tok_bytes,
3823 )?;
3824 return Ok(());
3825 }
3826 let deqw = std::env::var("MEMRA_PRIME_DEQW")
3834 .map(|v| v != "0")
3835 .unwrap_or(true);
3836 if deqw {
3837 e.fa_prefill_view_ws(
3838 q,
3839 &k_view,
3840 &v_view,
3841 attn,
3842 head_dim,
3843 n_head,
3844 n_head_kv,
3845 t,
3846 t_kv,
3847 scale,
3848 true,
3849 kvl.k_tok_bytes,
3850 kvl.v_tok_bytes,
3851 crate::Engine::kv_fp8_on(),
3852 )?;
3853 } else {
3854 e.fa_prefill_view(
3855 q,
3856 &k_view,
3857 &v_view,
3858 attn,
3859 head_dim,
3860 n_head,
3861 n_head_kv,
3862 t,
3863 t_kv,
3864 scale,
3865 true,
3866 kvl.k_tok_bytes,
3867 kvl.v_tok_bytes,
3868 crate::Engine::kv_fp8_on(),
3869 )?;
3870 }
3871 Ok(())
3872 }
3873
3874 fn full_attn_prime_post_fa(
3877 &self,
3878 e: &Engine,
3879 attn: CudaSlice<f32>,
3880 gate: &Option<CudaSlice<f32>>,
3881 t: usize,
3882 n_head: usize,
3883 head_dim: usize,
3884 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3885 let (attn_g, ag16) = match gate {
3886 Some(gate) => {
3887 let n = t * n_head * head_dim;
3888 let mut ag = e.uninit(n)?;
3889 if Self::f16out_on(e, t) {
3890 let mut a16 = e.alloc_u8_uninit(n * 2)?;
3891 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
3892 (ag, Some(a16))
3893 } else {
3894 let mut gsig = e.uninit(n)?;
3895 e.sigmoid(gate, &mut gsig, n)?;
3896 e.mul(&attn, &gsig, &mut ag, n)?;
3897 (ag, None)
3898 }
3899 }
3900 None => (attn, None),
3901 };
3902 Ok((attn_g, ag16))
3903 }
3904
3905 fn linear_attn_prime(
3912 &self,
3913 e: &Engine,
3914 la: &LinearAttnLayer,
3915 h: &CudaSlice<f32>,
3916 hx: Option<&CudaSlice<u8>>,
3917 t: usize,
3918 cache: &mut Cache,
3919 il: usize,
3920 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3921 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3923 let g4 = match hx {
3924 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
3925 None => e.matmul_group(&ws, h, t)?,
3926 };
3927 self.linear_attn_prime_core(e, la, g4, t, cache, il)
3928 }
3929
3930 fn linear_attn_prime_core(
3932 &self,
3933 e: &Engine,
3934 la: &LinearAttnLayer,
3935 mut g4: Vec<CudaSlice<f32>>,
3936 t: usize,
3937 cache: &mut Cache,
3938 il: usize,
3939 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3940 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
3941 }
3942
3943 #[allow(clippy::too_many_arguments)]
3947 fn linear_attn_prime_core_pad_inner(
3948 &self,
3949 e: &Engine,
3950 la: &LinearAttnLayer,
3951 mut g4: Vec<CudaSlice<f32>>,
3952 t: usize,
3953 cache: &mut Cache,
3954 il: usize,
3955 pad_len: Option<&CudaSlice<i32>>,
3956 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3957 let geometry = la.geometry;
3959 let d_state = geometry.key_head_dim as usize;
3960 let num_k = geometry.key_heads as usize;
3961 let num_v = geometry.value_heads as usize;
3962 let key_dim = d_state * num_k;
3963 let value_dim = geometry.value_head_dim as usize * num_v;
3964 let conv_dim = key_dim * 2 + value_dim;
3965 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(
3970 e,
3971 la,
3972 &qkv_mixed.slice(0..t * conv_dim),
3973 &z.slice(0..t * value_dim),
3974 &beta_raw.slice(0..t * num_v),
3975 &alpha.slice(0..t * num_v),
3976 t,
3977 cache,
3978 il,
3979 pad_len,
3980 )
3981 }
3982
3983 #[allow(clippy::too_many_arguments)]
3986 fn linear_attn_gdn_prep(
3987 &self,
3988 e: &Engine,
3989 la: &LinearAttnLayer,
3990 qkv_mixed: &cudarc::driver::CudaView<f32>,
3991 beta_raw: &cudarc::driver::CudaView<f32>,
3992 alpha: &cudarc::driver::CudaView<f32>,
3993 t: usize,
3994 cache: &mut Cache,
3995 il: usize,
3996 pad_len: Option<&CudaSlice<i32>>,
3997 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
3998 let cfg = &self.cfg;
3999 let geometry = la.geometry;
4000 let d_state = geometry.key_head_dim as usize;
4001 let num_k = geometry.key_heads as usize;
4002 let num_v = geometry.value_heads as usize;
4003 let d_conv = geometry.conv_kernel as usize;
4004 let key_dim = d_state * num_k; let value_dim = geometry.value_head_dim as usize * num_v;
4006 let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
4008 debug_assert!(
4009 t >= d_conv - 1,
4010 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
4011 );
4012
4013 let rl = cache.recur[il].as_mut().unwrap();
4018 let hk = Self::gdn_hk(e, t, num_v, num_k);
4019 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
4020 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
4022 let mut k_g = e.uninit(d_state * hk * t)?;
4023 let mut v_g = e.uninit(d_state * num_v * t)?;
4024 if conv_fuse {
4025 e.ssm_conv1d_gdn_state_pad(
4026 qkv_mixed,
4027 &mut rl.conv_state,
4028 la.ssm_conv1d.float_data(),
4029 &mut q_g,
4030 &mut k_g,
4031 &mut v_g,
4032 conv_dim,
4033 t,
4034 d_conv,
4035 d_state,
4036 num_v,
4037 num_k,
4038 key_dim,
4039 hk,
4040 pad_len,
4041 )?;
4042 } else {
4043 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
4045 qkv_mixed,
4046 &mut rl.conv_state,
4047 la.ssm_conv1d.float_data(),
4048 &mut conv_out,
4049 conv_dim,
4050 t,
4051 d_conv,
4052 pad_len,
4053 )?;
4054 e.qkv_to_gdn_repack(
4055 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
4056 )?;
4057 }
4058 let mut q_l2 = e.uninit(d_state * hk * t)?;
4059 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
4063 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4064 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
4065 Some(qb)
4066 } else {
4067 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
4068 None
4069 };
4070 let mut k_l2 = e.uninit(d_state * hk * t)?;
4071 let kb16 = if Engine::l2_v2_on(d_state) {
4073 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4074 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
4075 Some(kb)
4076 } else {
4077 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
4078 None
4079 };
4080 let mut beta = e.uninit(t * num_v)?;
4081 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
4082 let mut g_log = e.uninit(t * num_v)?;
4083 e.gdn_glog_v(
4084 alpha,
4085 la.ssm_dt.float_data(),
4086 la.ssm_a.float_data(),
4087 &mut g_log,
4088 num_v,
4089 t,
4090 )?;
4091 if let Some(len_d) = pad_len {
4092 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
4093 }
4094 Ok(GdnPrep {
4095 hk,
4096 q_l2,
4097 k_l2,
4098 v_g,
4099 beta,
4100 g_log,
4101 kb16,
4102 qb16,
4103 })
4104 }
4105
4106 #[allow(clippy::too_many_arguments)]
4111 fn linear_attn_prime_core_batch(
4112 &self,
4113 e: &Engine,
4114 la: &LinearAttnLayer,
4115 g4: &[CudaSlice<f32>],
4116 offs: &[usize],
4117 ts: &[usize],
4118 caches: &mut [&mut Cache],
4119 il: usize,
4120 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
4121 let geometry = la.geometry;
4122 let d_state = geometry.key_head_dim as usize;
4123 let num_k = geometry.key_heads as usize;
4124 let num_v = geometry.value_heads as usize;
4125 let d_conv = geometry.conv_kernel as usize;
4126 let key_dim = d_state * num_k;
4127 let value_dim = geometry.value_head_dim as usize * num_v;
4128 let conv_dim = key_dim * 2 + value_dim;
4129 let eps = self.cfg.rms_eps;
4130 let scale = 1.0 / (d_state as f32).sqrt();
4131 let b = ts.len();
4132 let c = Engine::gdn_chunk_size();
4133 let carried = caches.iter().any(|c| c.pos > 0);
4136 let use_vl = !carried
4137 && (2..=8).contains(&b)
4138 && Engine::gdn_chunked_enabled()
4139 && ts.iter().all(|&t| t >= 16)
4140 && e.gdn_mma_enabled(c)
4141 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
4142 if !use_vl {
4143 return (0..b)
4144 .map(|s| {
4145 let (o, t) = (offs[s], ts[s]);
4146 self.linear_attn_prime_core_pad_view(
4147 e,
4148 la,
4149 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
4150 &g4[1].slice(o * value_dim..(o + t) * value_dim),
4151 &g4[2].slice(o * num_v..(o + t) * num_v),
4152 &g4[3].slice(o * num_v..(o + t) * num_v),
4153 t,
4154 caches[s],
4155 il,
4156 None,
4157 )
4158 })
4159 .collect();
4160 }
4161 struct SeqBufs {
4165 conv_out: CudaSlice<f32>,
4166 q_g: CudaSlice<f32>,
4167 k_g: CudaSlice<f32>,
4168 v_g: CudaSlice<f32>,
4169 q_l2: CudaSlice<f32>,
4170 k_l2: CudaSlice<f32>,
4171 beta: CudaSlice<f32>,
4172 g_log: CudaSlice<f32>,
4173 gn: CudaSlice<f32>,
4174 gn16: CudaSlice<u8>,
4175 }
4176 let f16o = Self::f16out_on(e, 16);
4177 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
4179 let mut pres = Vec::with_capacity(b);
4180 for &t in ts.iter().take(b) {
4181 sb.push(SeqBufs {
4182 conv_out: e.uninit(conv_dim * t)?,
4183 q_g: e.uninit(d_state * hk * t)?,
4184 k_g: e.uninit(d_state * hk * t)?,
4185 v_g: e.uninit(d_state * num_v * t)?,
4186 q_l2: e.uninit(d_state * hk * t)?,
4187 k_l2: e.uninit(d_state * hk * t)?,
4188 beta: e.uninit(t * num_v)?,
4189 g_log: e.uninit(t * num_v)?,
4190 gn: e.uninit(d_state * num_v * t)?,
4191 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
4192 });
4193 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
4194 }
4195 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
4196 .map(|s| {
4197 let (o, t) = (offs[s], ts[s]);
4198 let rl = caches[s].recur[il].as_ref().unwrap();
4199 crate::GdnPrepVl {
4200 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
4201 conv_state: e.addr_f32(&rl.conv_state),
4202 conv_out: e.addr_f32(&sb[s].conv_out),
4203 q_g: e.addr_f32(&sb[s].q_g),
4204 k_g: e.addr_f32(&sb[s].k_g),
4205 v_g: e.addr_f32(&sb[s].v_g),
4206 q_l2: e.addr_f32(&sb[s].q_l2),
4207 k_l2: e.addr_f32(&sb[s].k_l2),
4208 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
4209 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
4210 beta: e.addr_f32(&sb[s].beta),
4211 g_log: e.addr_f32(&sb[s].g_log),
4212 o: e.addr_f32(&pres[s].o),
4213 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
4214 gn: e.addr_f32(&sb[s].gn),
4215 gn16: e.addr_u8(&sb[s].gn16),
4216 kb16: if Engine::l2_v2_on(d_state) {
4217 e.addr_u8(&pres[s].kb16)
4218 } else {
4219 0
4220 },
4221 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
4222 e.addr_u8(&pres[s].qb16)
4223 } else {
4224 0
4225 },
4226 t: t as i32,
4227 pad: 0,
4228 }
4229 })
4230 .collect();
4231 let args: Vec<crate::GdnSeqVl> = (0..b)
4232 .map(|s| {
4233 let rl = caches[s].recur[il].as_ref().unwrap();
4234 crate::GdnSeqVl {
4235 kb16: e.addr_u8(&pres[s].kb16),
4236 gcum: e.addr_f32(&pres[s].gcum),
4237 beta: e.addr_f32(&sb[s].beta),
4238 u: e.addr_f32(&pres[s].u),
4239 wb16: e.addr_u8(&pres[s].wb16),
4240 y: e.addr_u8(&pres[s].y16),
4241 ssnap: e.addr_u8(&pres[s].ssnap16),
4242 state_in: e.addr_f32(&rl.ssm_state),
4243 state_out: e.addr_f32(&rl.ssm_state_alt),
4244 q: e.addr_f32(&sb[s].q_l2),
4245 p: e.addr_f32(&pres[s].p),
4246 o: e.addr_f32(&pres[s].o),
4247 k: e.addr_f32(&sb[s].k_l2),
4248 v: e.addr_f32(&sb[s].v_g),
4249 g: e.addr_f32(&sb[s].g_log),
4250 a: e.addr_f32(&pres[s].a),
4251 w: e.addr_f32(&pres[s].w),
4252 t: ts[s] as i32,
4253 nc: pres[s].nc as i32,
4254 }
4255 })
4256 .collect();
4257 e.gdn_prep_vl8(
4258 &prep_args,
4259 la.ssm_conv1d.float_data(),
4260 la.ssm_dt.float_data(),
4261 la.ssm_a.float_data(),
4262 conv_dim,
4263 d_conv,
4264 d_state,
4265 num_v,
4266 num_k,
4267 key_dim,
4268 hk,
4269 eps,
4270 )?;
4271 if !Engine::l2_v2_on(d_state) {
4274 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
4275 }
4276 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
4278 if !Engine::l2_v2_on(d_state) {
4280 for s in 0..b {
4281 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
4282 }
4283 }
4284 let mut wa = [crate::GdnWVl::default(); 8];
4285 for s in 0..b {
4286 wa[s] = crate::GdnWVl {
4287 qb16: e.addr_u8(&pres[s].qb16),
4288 pb16: e.addr_u8(&pres[s].pb16),
4289 };
4290 }
4291 Some(crate::GdnWVl8(wa))
4292 } else {
4293 None
4294 };
4295 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
4296 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
4297 if f16o {
4298 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
4299 }
4300 let mut out = Vec::with_capacity(b);
4302 for (s, bufs) in sb.into_iter().enumerate() {
4303 let rl = caches[s].recur[il].as_mut().unwrap();
4304 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4305 let (o, t) = (offs[s], ts[s]);
4306 let SeqBufs { mut gn, gn16, .. } = bufs;
4307 if f16o {
4308 out.push((gn, Some(gn16)));
4309 } else {
4310 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
4311 e.gated_rmsnorm_zv(
4312 &pres[s].o,
4313 la.ssm_norm.float_data(),
4314 &z_v,
4315 &mut gn,
4316 d_state,
4317 num_v * t,
4318 eps,
4319 )?;
4320 out.push((gn, None));
4321 }
4322 }
4323 Ok(out)
4324 }
4325
4326 #[allow(clippy::too_many_arguments)]
4330 fn linear_attn_prime_core_pad_view(
4331 &self,
4332 e: &Engine,
4333 la: &LinearAttnLayer,
4334 qkv_mixed: &cudarc::driver::CudaView<f32>,
4335 z: &cudarc::driver::CudaView<f32>,
4336 beta_raw: &cudarc::driver::CudaView<f32>,
4337 alpha: &cudarc::driver::CudaView<f32>,
4338 t: usize,
4339 cache: &mut Cache,
4340 il: usize,
4341 pad_len: Option<&CudaSlice<i32>>,
4342 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4343 let cfg = &self.cfg;
4344 let geometry = la.geometry;
4345 let d_state = geometry.key_head_dim as usize;
4346 let num_v = geometry.value_heads as usize;
4347 let eps = cfg.rms_eps;
4348 let scale = 1.0 / (d_state as f32).sqrt();
4349
4350 let prep =
4351 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
4352
4353 let mut o = e.uninit(d_state * num_v * t)?;
4359 let rl = cache.recur[il].as_mut().unwrap();
4360 {
4361 let crate::cache::RecurLayer {
4362 ssm_state,
4363 ssm_state_alt,
4364 ..
4365 } = rl;
4366 e.gdn_scan_prefill(
4367 &prep.q_l2,
4368 &prep.k_l2,
4369 &prep.v_g,
4370 &prep.g_log,
4371 &prep.beta,
4372 prep.kb16.as_ref(),
4373 prep.qb16.as_ref(),
4374 ssm_state,
4375 ssm_state_alt,
4376 &mut o,
4377 num_v,
4378 t,
4379 scale,
4380 prep.hk,
4381 )?;
4382 }
4383 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4384
4385 let mut gn = e.uninit(d_state * num_v * t)?;
4388 let gn16 = if Self::f16out_on(e, t) {
4389 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
4390 e.gated_rmsnorm_f16out_zv(
4391 &o,
4392 la.ssm_norm.float_data(),
4393 z,
4394 &mut gn,
4395 &mut g16,
4396 d_state,
4397 num_v * t,
4398 eps,
4399 )?;
4400 Some(g16)
4401 } else {
4402 e.gated_rmsnorm_zv(
4403 &o,
4404 la.ssm_norm.float_data(),
4405 z,
4406 &mut gn,
4407 d_state,
4408 num_v * t,
4409 eps,
4410 )?;
4411 None
4412 };
4413 Ok((gn, gn16))
4414 }
4415
4416 #[allow(clippy::too_many_arguments)]
4418 fn linear_attn_prime_core_pad(
4419 &self,
4420 e: &Engine,
4421 la: &LinearAttnLayer,
4422 g4: Vec<CudaSlice<f32>>,
4423 t: usize,
4424 cache: &mut Cache,
4425 il: usize,
4426 pad_len: Option<&CudaSlice<i32>>,
4427 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4428 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
4429 if let Some(xh) = &gn16 {
4430 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
4431 return Ok(y);
4432 }
4433 }
4434 Ok(e.matmul(&la.ssm_out, &gn, t)?)
4435 }
4436
4437 pub fn full_attn(
4442 &self,
4443 e: &Engine,
4444 fa: &FullAttnLayer,
4445 h: &CudaSlice<f32>,
4446 pos_d: &CudaSlice<i32>,
4447 t: usize,
4448 il: usize,
4449 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4450 if self.uses_sliding_gated_moe_program() {
4451 return self.step35_attn(e, fa, h, pos_d, t, il);
4452 }
4453 let cfg = &self.cfg;
4454 let _n_embd = cfg.n_embd as usize;
4455 let geometry = cfg.full_attention_geometry_at(il as u32);
4456 let n_head = geometry.n_head as usize;
4457 let n_head_kv = geometry.n_head_kv as usize;
4458 let head_dim = geometry.head_dim_k as usize;
4459 let eps = cfg.rms_eps;
4460 let scale = geometry.attention_scale();
4461
4462 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
4465 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
4467 let v = g3.pop().unwrap();
4468 let mut k = g3.pop().unwrap();
4469 let qf = g3.pop().unwrap();
4470 let (mut q, gate) = if gated {
4471 let mut q = e.uninit(t * n_head * head_dim)?;
4472 let mut gate = e.uninit(t * n_head * head_dim)?;
4473 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
4474 (q, Some(gate))
4475 } else {
4476 (qf, None)
4477 };
4478
4479 let mut qn = e.uninit(t * n_head * head_dim)?;
4481 e.rms_norm(
4482 &q,
4483 fa.q_norm.float_data(),
4484 &mut qn,
4485 head_dim,
4486 n_head * t,
4487 eps,
4488 )?;
4489 q = qn;
4490 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
4491 e.rms_norm(
4492 &k,
4493 fa.k_norm.float_data(),
4494 &mut kn,
4495 head_dim,
4496 n_head_kv * t,
4497 eps,
4498 )?;
4499 k = kn;
4500 let rope_dims = geometry.n_rot as usize;
4501 e.rope_neox(
4502 &mut q,
4503 pos_d,
4504 head_dim,
4505 rope_dims,
4506 n_head,
4507 t,
4508 geometry.rope_base,
4509 1.0,
4510 )?;
4511 e.rope_neox(
4512 &mut k,
4513 pos_d,
4514 head_dim,
4515 rope_dims,
4516 n_head_kv,
4517 t,
4518 geometry.rope_base,
4519 1.0,
4520 )?;
4521
4522 let mut attn = e.uninit(t * n_head * head_dim)?;
4524 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4527 e.sdpa_naive(
4529 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4530 )?;
4531 } else {
4532 e.fa_prefill(
4533 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4534 )?;
4535 }
4536
4537 let attn_g = match &gate {
4539 Some(gate) => {
4540 let mut gsig = e.uninit(t * n_head * head_dim)?;
4541 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
4542 let mut ag = e.uninit(t * n_head * head_dim)?;
4543 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
4544 ag
4545 }
4546 None => attn,
4547 };
4548
4549 let o = e.matmul(&fa.wo, &attn_g, t)?;
4551 Ok(o)
4552 }
4553
4554 pub fn linear_attn(
4556 &self,
4557 e: &Engine,
4558 la: &LinearAttnLayer,
4559 h: &CudaSlice<f32>,
4560 t: usize,
4561 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4562 let cfg = &self.cfg;
4563 let _n_embd = cfg.n_embd as usize;
4564 let geometry = la.geometry;
4565 let d_state = geometry.key_head_dim as usize;
4566 let num_k = geometry.key_heads as usize;
4567 let num_v = geometry.value_heads as usize;
4568 let d_conv = geometry.conv_kernel as usize;
4569 let head_k = d_state;
4570 let head_v = geometry.value_head_dim as usize;
4571 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;
4575 let scale = 1.0 / (d_state as f32).sqrt();
4576
4577 let mut g4 = e.matmul_group(
4580 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
4581 h,
4582 t,
4583 )?;
4584 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);
4596 let mut q_g = e.uninit(d_state * num_v * t)?;
4597 let mut k_g = e.uninit(d_state * num_v * t)?;
4598 let mut v_g = e.uninit(d_state * num_v * t)?;
4599 e.ssm_conv1d_gdn(
4600 &qkv_mixed,
4601 la.ssm_conv1d.float_data(),
4602 &mut q_g,
4603 &mut k_g,
4604 &mut v_g,
4605 conv_dim,
4606 t,
4607 d_conv,
4608 d_state,
4609 num_v,
4610 num_k,
4611 key_dim,
4612 )?;
4613 let mut q_l2 = e.uninit(d_state * num_v * t)?;
4615 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
4616 let mut k_l2 = e.uninit(d_state * num_v * t)?;
4617 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
4618 let v_gd = v_g;
4619
4620 let mut beta = e.uninit(t * num_v)?;
4623 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
4624 let mut g_log = e.uninit(t * num_v)?;
4626 e.gdn_glog(
4627 &alpha,
4628 la.ssm_dt.float_data(),
4629 la.ssm_a.float_data(),
4630 &mut g_log,
4631 num_v,
4632 t,
4633 )?;
4634
4635 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
4638 let mut o = e.uninit(d_state * num_v * t)?;
4639 e.gdn_scan_prefill(
4640 &q_l2,
4641 &k_l2,
4642 &v_gd,
4643 &g_log,
4644 &beta,
4645 None,
4646 None,
4647 &state_in,
4648 &mut state_out,
4649 &mut o,
4650 num_v,
4651 t,
4652 scale,
4653 num_v,
4654 )?;
4655
4656 let mut gn = e.uninit(d_state * num_v * t)?;
4661 e.gated_rmsnorm(
4662 &o,
4663 la.ssm_norm.float_data(),
4664 &z,
4665 &mut gn,
4666 d_state,
4667 num_v * t,
4668 eps,
4669 )?;
4670
4671 let out = e.matmul(&la.ssm_out, &gn, t)?;
4675 Ok(out)
4676 }
4677}
4678
4679impl HybridModel {
4680 pub fn moe_ffn_il(
4691 &self,
4692 e: &Engine,
4693 m: &MoeWeights,
4694 z: &CudaSlice<f32>,
4695 t: usize,
4696 il: u16,
4697 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4698 Self::moe_ffn_inner(
4699 e,
4700 m,
4701 z,
4702 None,
4703 t,
4704 &self.cfg,
4705 il,
4706 self.max_moe_block(),
4707 false,
4708 None,
4709 self.uses_sliding_gated_moe_program(),
4710 )
4711 }
4712
4713 pub fn moe_ffn_il_prefill(
4716 &self,
4717 e: &Engine,
4718 m: &MoeWeights,
4719 z: &CudaSlice<f32>,
4720 t: usize,
4721 il: u16,
4722 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4723 Self::moe_ffn_inner(
4724 e,
4725 m,
4726 z,
4727 None,
4728 t,
4729 &self.cfg,
4730 il,
4731 self.max_moe_block(),
4732 true,
4733 Some(&self.step_grouped_prefill),
4734 self.uses_sliding_gated_moe_program(),
4735 )
4736 }
4737
4738 pub fn moe_ffn_il_zq8(
4742 &self,
4743 e: &Engine,
4744 m: &MoeWeights,
4745 z: &CudaSlice<f32>,
4746 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4747 t: usize,
4748 il: u16,
4749 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4750 Self::moe_ffn_inner(
4751 e,
4752 m,
4753 z,
4754 zq8,
4755 t,
4756 &self.cfg,
4757 il,
4758 self.max_moe_block(),
4759 false,
4760 None,
4761 self.uses_sliding_gated_moe_program(),
4762 )
4763 }
4764
4765 pub(crate) fn moe_ffn(
4773 e: &Engine,
4774 m: &MoeWeights,
4775 z: &CudaSlice<f32>,
4776 t: usize,
4777 cfg: &ModelConfig,
4778 il: u16,
4779 max_block: usize,
4780 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4781 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false, None, false)
4782 }
4783
4784 #[allow(clippy::too_many_arguments)]
4785 pub(crate) fn moe_ffn_inner(
4786 e: &Engine,
4787 m: &MoeWeights,
4788 z: &CudaSlice<f32>,
4789 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4790 t: usize,
4791 cfg: &ModelConfig,
4792 il: u16,
4793 max_block: usize,
4794 prefill: bool,
4795 grouped_prefill: Option<&std::sync::Mutex<crate::hybrid::StepEpGroupedPrefill>>,
4796 sliding_gated_moe: bool,
4797 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4798 let worker_io = crate::spill_pread::worker_enabled();
4799 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
4800 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
4801 e.with_moe_cache(max_block, |cache, _| {
4802 cache.begin_forward_epoch(il, t);
4803 if worker_io {
4804 cache.begin_worker_scope();
4805 }
4806 Ok(())
4807 })?;
4808 }
4809 if m.step_ep.is_some() || m.step_tp.is_some() {
4810 let moe = cfg
4811 .moe
4812 .as_ref()
4813 .ok_or("Step distributed execution requires MoE model metadata")?;
4814 let n_embd = cfg.n_embd as usize;
4815 let n_expert = moe.expert_count as usize;
4816 let n_used = moe.expert_used_count as usize;
4817 let sigmoid = cfg
4818 .sigmoid_router()
4819 .ok_or("Step distributed execution requires the Step sigmoid router")?;
4820 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4821 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
4822 let grouped_prefill_requested = prefill && step_ep_grouped_prefill_enabled()?;
4823 if grouped_prefill_requested && !step_tp_prefill_enabled()? {
4824 return Err(
4825 "MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into(),
4826 );
4827 }
4828 if grouped_prefill_requested && !step_grouped_prefill_shape(true, prefill, t) {
4829 return Err(format!(
4830 "Step grouped prefill tokens {t} are outside the qualified {}..={} range",
4831 PRIME_MIN_T,
4832 crate::cache::PRIME_CHUNK_MAX_TOKENS,
4833 )
4834 .into());
4835 }
4836 let grouped_decode_shape = step_grouped_decode_shape(prefill, t);
4837 let grouped_prefill_shape =
4838 step_grouped_prefill_shape(grouped_prefill_requested, prefill, t);
4839 if let Some(ep) = m.step_ep.as_ref().filter(|ep| {
4840 ep.grouped_decode.is_some() && (grouped_decode_shape || grouped_prefill_shape)
4841 }) {
4842 let (selected, route_weights) =
4843 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sigmoid)?;
4844 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
4845 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
4846 Self::trace_moe_input(e, il, t, n_embd, z)?;
4847 let selected = selected
4848 .iter()
4849 .map(|&expert| expert as usize)
4850 .collect::<Vec<_>>();
4851
4852 e.stream().synchronize()?;
4855 let execute = |state: &mut crate::hybrid::StepEpGroupedDecode| {
4856 state.projection.set_activation_limit(ep.activation_limit)?;
4857 ep.runtime
4858 .refresh_step_grouped_expert_parallel_gate_from_root_device(
4859 ep.experts.e4m3()?,
4860 &mut state.projection,
4861 z,
4862 t,
4863 &selected,
4864 )?;
4865 ep.runtime.refresh_step_grouped_expert_parallel_combine(
4866 &state.projection,
4867 &mut state.combine,
4868 &route_weights,
4869 )?;
4870 ep.runtime.execute_step_grouped_expert_parallel_gate(
4871 ep.experts.e4m3()?,
4872 &mut state.projection,
4873 )?;
4874 ep.runtime.execute_step_grouped_expert_parallel_combine(
4875 &state.projection,
4876 &mut state.combine,
4877 )?;
4878 let mut output = ep.runtime.copy_step_grouped_expert_parallel_combine_root(
4879 &state.projection,
4880 &state.combine,
4881 e,
4882 )?;
4883 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
4884 if prefill {
4885 e.stream().synchronize()?;
4888 }
4889 eprintln!(
4890 "[step-tp-ep-grouped] execute layer={il} tokens={t} devices={:?} \
4891 attention_layout=tensor-parallel expert_layout=expert-parallel \
4892 expert_transport={} native_p2p=true route_control=host-narrow \
4893 input=root-device projection_workspaces=persistent \
4894 combine=root-device output=owning-stage-device \
4895 prefill={prefill} batched_decode=false capacity={} \
4896 performance_claim=false",
4897 ep.devices,
4898 ep.runtime.transport_label(),
4899 state.projection.max_tokens(),
4900 );
4901 Ok::<_, Box<dyn std::error::Error>>(output)
4902 };
4903
4904 if grouped_prefill_shape {
4905 let grouped_prefill = grouped_prefill
4906 .ok_or("Step grouped prefill has no model-scoped executor")?;
4907 let mut shared = grouped_prefill
4908 .lock()
4909 .map_err(|_| "Step grouped prefill state lock is poisoned")?;
4910 let needs_prepare = shared.state.as_ref().is_none_or(|state| {
4911 state.devices != ep.devices
4912 || state.grouped.projection.max_tokens() < t
4913 || state.grouped.projection.input_width() != n_embd
4914 || state.grouped.projection.expert_width()
4915 != moe.expert_ff_length as usize
4916 });
4917 if needs_prepare {
4918 let seed_input = vec![0.0f32; n_embd];
4919 let seed_selected = &selected[..n_used];
4920 let seed_weights = &route_weights[..n_used];
4921 let projection = ep
4922 .runtime
4923 .prepare_step_grouped_expert_parallel_gate_with_capacity(
4924 ep.experts.e4m3()?,
4925 &seed_input,
4926 1,
4927 seed_selected,
4928 ep.activation_limit,
4929 t,
4930 )?;
4931 let combine = ep.runtime.prepare_step_grouped_expert_parallel_combine(
4932 &projection,
4933 seed_weights,
4934 )?;
4935 shared.state = Some(crate::hybrid::StepEpGroupedPrefillState {
4936 devices: ep.devices.clone(),
4937 grouped: crate::hybrid::StepEpGroupedDecode {
4938 projection,
4939 combine,
4940 },
4941 });
4942 eprintln!(
4943 "[step-tp-ep-grouped-prefill] prepare capacity={t} devices={:?} \
4944 shared_across_layers=true performance_claim=false",
4945 ep.devices,
4946 );
4947 }
4948 return execute(
4949 &mut shared
4950 .state
4951 .as_mut()
4952 .expect("Step grouped prefill state prepared above")
4953 .grouped,
4954 );
4955 }
4956
4957 let mut grouped = ep
4958 .grouped_decode
4959 .as_ref()
4960 .expect("grouped decode presence checked above")
4961 .lock()
4962 .map_err(|_| "Step grouped decode state lock is poisoned")?;
4963 return execute(&mut grouped);
4964 }
4965 if grouped_prefill_shape {
4966 return Err(
4967 "Step grouped prefill requires native-P2P expert-owner device arithmetic"
4968 .into(),
4969 );
4970 }
4971 if t == 1
4976 && crate::tp::step_nvfp4_dev_routes_enabled()?
4977 && crate::tp::step_tp_dev_router_enabled()?
4978 {
4979 if let Some(tp) = &m.step_tp {
4980 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
4981 let (sf, route_norm) = sigmoid;
4982 static D1_ROUTER: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4989 let d1_router = *D1_ROUTER.get_or_init(|| {
4990 std::env::var("MEMRA_DEV1_ROUTER").as_deref() == Ok("1")
4991 });
4992 if d1_router {
4993 let (sf_h, rn_h) = sigmoid;
4994 let n_ex = m.gate_exps.n_expert;
4995 let act_ct = m.active_count();
4996 let _ = tp.runtime.nvfp4_routes_prestage_with(
4997 bank,
4998 e,
4999 z,
5000 |rank1, in1, sel1, w1| {
5001 let mut guard = DEV1_ROUTER_REPS
5002 .lock()
5003 .map_err(|_| "dev1 router replica lock")?;
5004 let (reps, scratch) =
5005 guard.get_or_insert_with(|| (Default::default(), None));
5006 if !reps.contains_key(&il) {
5007 use cudarc::driver::DevicePtr;
5008 let (g1, p1, a1) = (
5009 rank1.htod(&vec![0.0f32; n_ex * n_embd])?,
5010 rank1.htod(&vec![0.0f32; n_ex])?,
5011 rank1.alloc_u8_uninit(n_ex)?,
5012 );
5013 for (src, dst_len, dst) in [
5014 (
5015 {
5016 let s = e.stream();
5017 let (p, _g) =
5018 m.gate_inp.float_data().device_ptr(&s);
5019 p as u64
5020 },
5021 n_ex * n_embd * 4,
5022 {
5023 let s = rank1.stream();
5024 let (p, _g) = g1.device_ptr(&s);
5025 p as u64
5026 },
5027 ),
5028 (
5029 {
5030 let s = e.stream();
5031 let (p, _g) = m.exp_probs_b_dev.device_ptr(&s);
5032 p as u64
5033 },
5034 n_ex * 4,
5035 {
5036 let s = rank1.stream();
5037 let (p, _g) = p1.device_ptr(&s);
5038 p as u64
5039 },
5040 ),
5041 (
5042 {
5043 let s = e.stream();
5044 let (p, _g) =
5045 m.active_experts_dev.device_ptr(&s);
5046 p as u64
5047 },
5048 n_ex,
5049 {
5050 let s = rank1.stream();
5051 let (p, _g) = a1.device_ptr(&s);
5052 p as u64
5053 },
5054 ),
5055 ] {
5056 crate::tp::raw_copy_bytes(dst, src, dst_len, rank1)?;
5057 }
5058 rank1.stream().synchronize()?;
5059 reps.insert(il, (g1, p1, a1));
5060 }
5061 if scratch.is_none() {
5062 *scratch = Some(rank1.htod(&vec![0.0f32; n_ex])?);
5063 }
5064 let (g1, p1, a1) = reps.get(&il).expect("armed above");
5065 let logits1 = scratch.as_mut().expect("armed above");
5066 rank1.router_gemv_into(g1, in1, logits1, n_embd, n_ex, 1)?;
5067 rank1.moe_router_sigmoid_topk_into(
5068 logits1, 1, n_ex, n_used, act_ct, p1, a1, sf_h, rn_h, sel1,
5069 w1,
5070 )?;
5071 Ok(true)
5072 },
5073 )?;
5074 } else {
5075 let _ = tp.runtime.nvfp4_routes_prestage(bank, e, z)?;
5076 }
5077 static SELW: std::sync::Mutex<
5081 Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>,
5082 > = std::sync::Mutex::new(None);
5083 let mut selw = SELW.lock().map_err(|_| "selw lock poisoned")?;
5084 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
5085 *selw = Some((
5086 e.ctx().ordinal(),
5087 e.htod_i32(&vec![0i32; n_used])?,
5088 e.htod(&vec![0.0f32; n_used])?,
5089 ));
5090 }
5091 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
5092 e.moe_router_sigmoid_topk_into(
5093 &logits,
5094 t,
5095 n_expert,
5096 n_used,
5097 m.active_count(),
5098 &m.exp_probs_b_dev,
5099 &m.active_experts_dev,
5100 sf,
5101 route_norm,
5102 sel_d,
5103 w_d,
5104 )?;
5105 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
5106 static SHEXP_OV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5110 let shexp_ov = *SHEXP_OV.get_or_init(|| {
5111 std::env::var("MEMRA_SHEXP_OVERLAP").as_deref() == Ok("1")
5112 });
5113 static SHEXP_D1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5117 let shexp_d1 = *SHEXP_D1.get_or_init(|| {
5118 std::env::var("MEMRA_SHEXP_DEV1").as_deref() == Ok("1")
5119 }) && tp.runtime.rank_engine(1).is_some();
5120 static TAIL3: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5124 let tail3 = *TAIL3
5125 .get_or_init(|| std::env::var("MEMRA_TAIL_ADD3").as_deref() != Ok("0"));
5126 let mut ov_issued = false;
5127 let mut d1_issued = false;
5128 let mut tail_folded = false;
5129 let mut output = if shexp_d1 {
5130 let rank1 = tp.runtime.rank_engine(1).expect("checked above");
5131 tp.runtime
5132 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
5133 bank,
5134 e,
5135 z,
5136 &sel_d,
5137 &w_d,
5138 n_used,
5139 tp.activation_limit,
5140 || {
5141 d1_issued = Self::shexp_dev1_issue(
5142 e, rank1, m, z, cfg, il, n_embd,
5143 )?;
5144 Ok(())
5145 },
5146 )?
5147 } else if shexp_ov {
5148 let post_add = if tail3 {
5153 Self::shexp_overlap_tail_ptrs(e, m, cfg, n_embd)?
5154 } else {
5155 None
5156 };
5157 let used_post = post_add.is_some();
5158 let out = tp
5159 .runtime
5160 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
5161 bank,
5162 e,
5163 z,
5164 &sel_d,
5165 &w_d,
5166 n_used,
5167 tp.activation_limit,
5168 || {
5169 ov_issued =
5170 Self::shexp_overlap_issue(e, m, z, cfg, il, n_embd)?;
5171 Ok(())
5172 },
5173 post_add,
5174 )?;
5175 if used_post && ov_issued {
5180 tail_folded = true; }
5182 out
5183 } else {
5184 tp.runtime.run_tensor_parallel_routes_nvfp4_device_routed(
5185 bank,
5186 e,
5187 z,
5188 &sel_d,
5189 &w_d,
5190 n_used,
5191 tp.activation_limit,
5192 )?
5193 };
5194 if output.len() != t * n_embd {
5195 return Err(format!(
5196 "Step tp routed output has {} values, expected {t}x{n_embd}",
5197 output.len()
5198 )
5199 .into());
5200 }
5201 if tail_folded {
5202 } else if d1_issued {
5204 Self::shexp_dev1_apply(e, &mut output, n_embd)?;
5205 } else if ov_issued {
5206 Self::shexp_overlap_apply(e, &mut output, n_embd)?;
5207 } else {
5208 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5209 }
5210 static DR_LOGGED: std::sync::atomic::AtomicU64 =
5211 std::sync::atomic::AtomicU64::new(0);
5212 let layer_bit = 1u64 << (il as u64 % 64);
5213 if DR_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5214 & layer_bit
5215 == 0
5216 {
5217 eprintln!(
5218 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5219 expert_transport={} native_p2p={} router=device \
5220 activation=host-canonical accumulation=host-canonical \
5221 output=e-device io=device performance_claim=false \
5222 (logged once per layer)",
5223 tp.devices,
5224 tp.runtime.transport_label(),
5225 tp.runtime.native_p2p(),
5226 );
5227 }
5228 return Ok(output);
5229 }
5230 }
5231 }
5232 static ROUTE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5236 static ROUTE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5237 let route_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
5238 let route_started = route_timing.then(std::time::Instant::now);
5239 let (selected, route_weights, input) = Self::moe_route_sigmoid_with_input(
5240 e,
5241 &logits,
5242 z,
5243 t,
5244 n_embd,
5245 n_expert,
5246 n_used,
5247 m.exp_probs_b.as_deref(),
5248 sigmoid,
5249 m.active_experts.as_deref(),
5250 )?;
5251 if let Some(started) = route_started {
5252 use std::sync::atomic::Ordering;
5253 let ns = ROUTE_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
5254 + started.elapsed().as_nanos() as u64;
5255 let calls = ROUTE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
5256 if calls % 430 == 0 {
5257 eprintln!(
5258 "[moe-route-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
5259 ns as f64 / 1.0e6,
5260 ns as f64 / calls as f64 / 1.0e3,
5261 );
5262 }
5263 }
5264 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5265 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5266 Self::trace_moe_input(e, il, t, n_embd, z)?;
5267 let selected = selected
5268 .iter()
5269 .map(|&expert| expert as usize)
5270 .collect::<Vec<_>>();
5271 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5276 if let Some(tp) = &m.step_tp {
5277 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5278 let mut output = tp.runtime.run_tensor_parallel_routes_nvfp4_device_io(
5279 bank,
5280 e,
5281 z,
5282 &selected,
5283 &route_weights,
5284 n_used,
5285 tp.activation_limit,
5286 )?;
5287 if output.len() != t * n_embd {
5288 return Err(format!(
5289 "Step tp routed output has {} values, expected {t}x{n_embd}",
5290 output.len()
5291 )
5292 .into());
5293 }
5294 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5295 static IO_LOGGED: std::sync::atomic::AtomicU64 =
5296 std::sync::atomic::AtomicU64::new(0);
5297 let layer_bit = 1u64 << (il as u64 % 64);
5298 if IO_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5299 & layer_bit
5300 == 0
5301 {
5302 eprintln!(
5303 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5304 expert_transport={} native_p2p={} activation=host-canonical \
5305 accumulation=host-canonical output=e-device io=device \
5306 performance_claim=false (logged once per layer)",
5307 tp.devices,
5308 tp.runtime.transport_label(),
5309 tp.runtime.native_p2p(),
5310 );
5311 }
5312 return Ok(output);
5313 }
5314 }
5315 }
5316 let (routed, mode, devices, transport, native_p2p) = if let Some(tp) = &m.step_tp {
5317 (
5318 match &tp.experts {
5319 crate::hybrid::StepTpExpertBank::E4m3(bank) => {
5320 tp.runtime.run_tensor_parallel_routes(
5321 bank,
5322 &input,
5323 t,
5324 &selected,
5325 &route_weights,
5326 n_used,
5327 )?
5328 }
5329 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => {
5330 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5331 tp.runtime.run_tensor_parallel_routes_nvfp4_device(
5332 bank,
5333 &input,
5334 &selected,
5335 &route_weights,
5336 n_used,
5337 tp.activation_limit,
5338 )?
5339 } else {
5340 tp.runtime.run_tensor_parallel_routes_nvfp4(
5341 bank,
5342 &input,
5343 t,
5344 &selected,
5345 &route_weights,
5346 n_used,
5347 tp.activation_limit,
5348 )?
5349 }
5350 }
5351 },
5352 "tp",
5353 &tp.devices,
5354 tp.runtime.transport_label(),
5355 tp.runtime.native_p2p(),
5356 )
5357 } else {
5358 let ep = m
5359 .step_ep
5360 .as_ref()
5361 .ok_or("Step distributed runtime has no EP or TP state")?;
5362 (
5363 match &ep.experts {
5364 crate::hybrid::StepEpExpertBank::E4m3(bank) => {
5365 ep.runtime.run_routed_experts(
5366 bank,
5367 &input,
5368 t,
5369 &selected,
5370 &route_weights,
5371 n_used,
5372 ep.activation_limit,
5373 )?
5374 }
5375 crate::hybrid::StepEpExpertBank::Nvfp4(bank) => {
5376 ep.runtime.run_routed_experts_nvfp4(
5377 bank,
5378 &input,
5379 t,
5380 &selected,
5381 &route_weights,
5382 n_used,
5383 ep.activation_limit,
5384 )?
5385 }
5386 },
5387 if ep.configured_by_tp { "tp-ep" } else { "ep" },
5388 &ep.devices,
5389 ep.runtime.transport_label(),
5390 ep.runtime.native_p2p(),
5391 )
5392 };
5393 if routed.len() != t * n_embd {
5394 return Err(format!(
5395 "Step {mode} routed output has {} values, expected {t}x{n_embd}",
5396 routed.len()
5397 )
5398 .into());
5399 }
5400 let mut output = e.htod(&routed)?;
5401 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5402 static STEP_LOGGED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5405 let layer_bit = 1u64 << (il as u64 % 64);
5406 if STEP_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed) & layer_bit
5407 == 0
5408 {
5409 eprintln!(
5410 "[step-{mode}] execute layer={il} tokens={t} devices={devices:?} \
5411 expert_transport={transport} native_p2p={native_p2p} \
5412 activation={} accumulation={} output={} \
5413 performance_claim=false (logged once per layer)",
5414 if let Some(ep) = &m.step_ep {
5415 ep.runtime.expert_activation_label()
5416 } else {
5417 "host-canonical"
5418 },
5419 if let Some(ep) = &m.step_ep {
5420 ep.runtime.expert_accumulation_label()
5421 } else {
5422 "host-canonical"
5423 },
5424 if let Some(ep) = &m.step_ep {
5425 ep.runtime.expert_output_label()
5426 } else {
5427 "host-accumulated"
5428 },
5429 );
5430 if let Some(ep) = &m.step_ep {
5431 if let Some(limit) = ep.activation_limit {
5432 eprintln!(
5433 "[step-ep-clamp] execute layer={il} tokens={t} routed_clamp={limit} \
5434 formula=min-silu-times-clamped-up performance_claim=false"
5435 );
5436 }
5437 }
5438 }
5439 return Ok(output);
5440 }
5441 if Self::sigmoid_resident_dev_eligible(e, m, cfg, sliding_gated_moe) {
5442 let moe = cfg.moe.as_ref().unwrap();
5443 let n_expert = moe.expert_count as usize;
5444 let n_used = moe.expert_used_count as usize;
5445 let sigmoid = cfg.sigmoid_router().unwrap();
5446 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5447 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5448 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
5449 }
5450 if t > 1 && moe_grouped_enabled(cfg, prefill) {
5453 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
5454 if std::env::var("MEMRA_MOE_GATE").is_ok() {
5459 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
5460 let g_host = e.dtoh(&grouped_out)?;
5461 let s_host = e.dtoh(&seq_out)?;
5462 let g_bytes: &[u8] = unsafe {
5463 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
5464 };
5465 let s_bytes: &[u8] = unsafe {
5466 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
5467 };
5468 if g_bytes == s_bytes {
5469 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
5470 } else {
5471 let diffs = g_host
5472 .iter()
5473 .zip(s_host.iter())
5474 .enumerate()
5475 .filter(|(_, (a, b))| a != b)
5476 .count();
5477 let maxdiff = g_host
5478 .iter()
5479 .zip(s_host.iter())
5480 .map(|(a, b)| (a - b).abs())
5481 .fold(0.0f32, f32::max);
5482 panic!(
5483 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
5484 g_host.len()
5485 );
5486 }
5487 }
5488 return Ok(grouped_out);
5489 }
5490 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
5491 }
5492
5493 fn sigmoid_resident_dev_eligible(
5494 e: &Engine,
5495 m: &MoeWeights,
5496 cfg: &ModelConfig,
5497 sliding_gated_moe: bool,
5498 ) -> bool {
5499 let Some(moe) = cfg.moe.as_ref() else {
5500 return false;
5501 };
5502 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5505 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
5506 std::env::var("MEMRA_MOE_STATS").is_ok()
5507 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5508 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5509 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
5510 || std::env::var("MEMRA_MOE_GATE").is_ok()
5511 });
5512 let resident_layout_supported = m.dev_exps.as_ref().is_some_and(|dev| {
5513 if dev.dev != e.ctx().ordinal() {
5514 return false;
5515 }
5516 let q8 = moe_q8_enabled()
5517 && q8_expert_supported(m.gate_exps.qtype)
5518 && q8_expert_supported(m.up_exps.qtype)
5519 && q8_expert_supported(m.down_exps.qtype);
5520 let fp8 = dev.fp8_blk.is_some()
5521 && m.gate_exps.qtype == crate::QT_F8_E4M3_BLK
5522 && m.up_exps.qtype == crate::QT_F8_E4M3_BLK
5523 && m.down_exps.qtype == crate::QT_F8_E4M3_BLK;
5524 q8 || fp8
5525 });
5526 sliding_gated_moe
5527 && sigmoid_router_enabled()
5528 && moe_dev_enabled()
5529 && moe_slab_enabled()
5530 && !observation_mode
5531 && moe.expert_used_count <= 8
5532 && m.has_uniform_expert_layout()
5533 && m.gate_exps.macros.is_none()
5534 && m.up_exps.macros.is_none()
5535 && m.down_exps.macros.is_none()
5536 && !m.has_macros
5537 && resident_layout_supported
5538 }
5539
5540 pub(crate) fn moe_ffn_sequential(
5542 e: &Engine,
5543 m: &MoeWeights,
5544 z: &CudaSlice<f32>,
5545 t: usize,
5546 cfg: &ModelConfig,
5547 il: u16,
5548 max_block: usize,
5549 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5550 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
5551 }
5552
5553 fn moe_router_logits(
5557 e: &Engine,
5558 m: &MoeWeights,
5559 z: &CudaSlice<f32>,
5560 t: usize,
5561 cfg: &ModelConfig,
5562 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5563 if t < PRIME_MIN_T {
5564 if crate::router_kernel_on() {
5566 e.router_gemv(
5567 m.gate_inp.float_data(),
5568 z,
5569 cfg.n_embd as usize,
5570 m.gate_exps.n_expert,
5571 t,
5572 )
5573 } else {
5574 e.matmul_decode_exact(&m.gate_inp, z, t)
5575 }
5576 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
5577 e.router_gemv(
5578 m.gate_inp.float_data(),
5579 z,
5580 cfg.n_embd as usize,
5581 m.gate_exps.n_expert,
5582 t,
5583 )
5584 } else {
5585 e.matmul(&m.gate_inp, z, t)
5586 }
5587 }
5588
5589 fn trace_moe_routes(
5593 il: u16,
5594 t: usize,
5595 sel_all: &[u32],
5596 weights: &[f32],
5597 ) -> Result<(), Box<dyn std::error::Error>> {
5598 use std::io::Write as _;
5599 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
5600 let mut f = std::fs::OpenOptions::new()
5601 .create(true)
5602 .append(true)
5603 .open(path)?;
5604 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
5605 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
5606 }
5607 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
5608 let mut f = std::fs::OpenOptions::new()
5609 .create(true)
5610 .append(true)
5611 .open(path)?;
5612 let pairs: Vec<String> = sel_all
5613 .iter()
5614 .zip(weights)
5615 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
5616 .collect();
5617 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
5618 }
5619 Ok(())
5620 }
5621
5622 #[allow(clippy::too_many_arguments)]
5623 fn trace_sigmoid_router_logits(
5624 e: &Engine,
5625 il: u16,
5626 t: usize,
5627 n_expert: usize,
5628 n_used: usize,
5629 logits: &CudaSlice<f32>,
5630 m: &MoeWeights,
5631 (scaling_factor, route_norm): (f32, bool),
5632 ) -> Result<(), Box<dyn std::error::Error>> {
5633 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
5634 return Ok(());
5635 }
5636 let logits = e.dtoh(logits)?;
5637 let active: Vec<u8> = m
5638 .active_experts
5639 .as_ref()
5640 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
5641 .unwrap_or_else(|| vec![1; n_expert]);
5642 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
5643 crate::sigrouter_contract::capture_served_logits(
5644 il as u32,
5645 t,
5646 n_expert,
5647 n_used,
5648 scaling_factor,
5649 route_norm,
5650 &active,
5651 &bias,
5652 &logits,
5653 )?;
5654 Ok(())
5655 }
5656
5657 fn trace_moe_input(
5662 e: &Engine,
5663 il: u16,
5664 t: usize,
5665 n_embd: usize,
5666 z: &CudaSlice<f32>,
5667 ) -> Result<(), Box<dyn std::error::Error>> {
5668 use std::io::Write as _;
5669 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
5670 return Ok(());
5671 };
5672 let values = active_matrix_values(z.len(), t, n_embd, "MoE input trace activation")?;
5673 let host = e.dtoh_view(&z.slice(0..values))?;
5674 let bytes = unsafe {
5675 std::slice::from_raw_parts(
5676 host.as_ptr().cast::<u8>(),
5677 host.len() * std::mem::size_of::<f32>(),
5678 )
5679 };
5680 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
5681 let mut state = state
5682 .lock()
5683 .map_err(|_| "MoE input trace writer lock is poisoned")?;
5684 if state.is_none() {
5685 let dir = std::path::PathBuf::from(&dir);
5686 std::fs::create_dir_all(&dir)?;
5687 let index = std::fs::OpenOptions::new()
5688 .create(true)
5689 .append(true)
5690 .open(dir.join("index.jsonl"))?;
5691 *state = Some(MoeInputTraceWriter {
5692 dir,
5693 index,
5694 payloads: std::collections::HashMap::new(),
5695 });
5696 }
5697 let writer = state.as_mut().unwrap();
5698 if writer.dir != std::path::Path::new(&dir) {
5699 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
5700 }
5701 let file_name = format!("layer-{il:03}.f32");
5702 if !writer.payloads.contains_key(&il) {
5703 let payload = std::fs::OpenOptions::new()
5704 .create(true)
5705 .append(true)
5706 .open(writer.dir.join(&file_name))?;
5707 let offset = payload.metadata()?.len();
5708 writer.payloads.insert(il, (payload, offset));
5709 }
5710 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
5711 let row_offset = *offset;
5712 payload.write_all(bytes)?;
5713 *offset += bytes.len() as u64;
5714 writeln!(
5715 writer.index,
5716 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
5717 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
5718 \"payload_bytes\":{}}}",
5719 bytes.len()
5720 )?;
5721 Ok(())
5722 }
5723
5724 #[allow(clippy::too_many_arguments)]
5725 pub(crate) fn moe_ffn_sequential_zq8(
5726 e: &Engine,
5727 m: &MoeWeights,
5728 z: &CudaSlice<f32>,
5729 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5730 t: usize,
5731 cfg: &ModelConfig,
5732 il: u16,
5733 max_block: usize,
5734 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5735 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
5736 let moe = cfg.moe.as_ref().unwrap();
5737 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);
5744 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
5745 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);
5748
5749 let lim_exp = cfg.clamp_exp_at(il as u32);
5752 let lim_shexp = cfg.clamp_shexp_at(il as u32);
5753 let use_cache = Engine::moe_cache_enabled();
5754 let uniform_experts = m.has_uniform_expert_layout();
5755 let moe_q8 = uniform_experts
5756 && moe_q8_enabled()
5757 && q8_expert_supported(m.gate_exps.qtype)
5758 && q8_expert_supported(m.up_exps.qtype)
5759 && q8_expert_supported(m.down_exps.qtype);
5760 let cpu_expert_requested = crate::cpu_experts::configured();
5767 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
5768 return Err(std::io::Error::other(
5769 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
5770 )
5771 .into());
5772 }
5773 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
5774 let freeze_cpu_residency = cpu_expert_requested
5780 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
5781 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
5782 .ok()
5783 .and_then(|value| value.parse::<usize>().ok())
5784 .is_some_and(|tokens| tokens > 0);
5785 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
5786 e.freeze_moe_cache();
5787 }
5788 let cache_frozen = use_cache && e.moe_cache_frozen();
5789 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
5790
5791 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5794 if let Some(sig) = cfg.sigmoid_router() {
5795 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
5796 }
5797
5798 let no_exp_macros = m.gate_exps.macros.is_none()
5837 && m.up_exps.macros.is_none()
5838 && m.down_exps.macros.is_none();
5839 if cfg.sigmoid_router().is_none()
5843 && cfg.m3.is_none()
5844 && cfg.hy3.is_none()
5845 && !cfg.swiglu_clamped_at(il as u32)
5846 && no_exp_macros
5847 && t > MOE_DEV_MAX_T
5851 && m.dev_exps.is_some()
5852 && moe_q8_enabled()
5853 && q8_expert_supported(m.gate_exps.qtype)
5854 && q8_expert_supported(m.up_exps.qtype)
5855 && q8_expert_supported(m.down_exps.qtype)
5856 && std::env::var("MEMRA_MOE_PAIRS")
5857 .map(|v| v != "0")
5858 .unwrap_or(true)
5859 && std::env::var("MEMRA_MOE_STATS").is_err()
5860 {
5861 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
5862 }
5863
5864 let dev_ok = uniform_experts
5882 && cfg.sigmoid_router().is_none()
5883 && cfg.m3.is_none()
5884 && cfg.hy3.is_none()
5885 && !cfg.swiglu_clamped_at(il as u32);
5886 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
5890 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5891 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5892 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
5893 if dev_ok
5894 && t <= MOE_DEV_MAX_T
5895 && m.dev_exps.is_some()
5896 && n_used <= 8
5897 && moe_dev_enabled()
5898 && !observe_routes
5899 {
5900 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5901 }
5902 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
5903 let row_ok = e.with_moe_cache(max_block, |c, eng| {
5904 if moe_prewarm_enabled() {
5905 c.prewarm_layer(il, m, eng)?;
5906 }
5907 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
5908 })?;
5909 if row_ok {
5910 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5911 }
5912 }
5913
5914 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
5916 if cpu_hybrid {
5917 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
5918 e,
5919 &logits,
5920 z,
5921 t,
5922 n_embd,
5923 n_expert,
5924 n_used,
5925 m.exp_probs_b.as_deref(),
5926 sig,
5927 m.active_experts.as_deref(),
5928 )?;
5929 (sel, w, Some(input))
5930 } else {
5931 let (sel, w) =
5932 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
5933 (sel, w, None)
5934 }
5935 } else {
5936 let (sel, w) =
5937 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
5938 (sel, w, None)
5939 };
5940 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
5941
5942 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
5946 Self::trace_moe_input(e, il, t, n_embd, z)?;
5947
5948 let worker_disk_prefetch =
5960 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
5961 let promote_worker_h2d =
5962 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
5963 if promote_worker_h2d {
5964 let mut selected_blocks = Vec::with_capacity(n_used * 3);
5965 for &ex in sel_all.iter().take(n_used) {
5966 let ex = ex as u16;
5967 selected_blocks.extend([
5968 BlockId::new(il, PROJ_GATE, ex),
5969 BlockId::new(il, PROJ_UP, ex),
5970 BlockId::new(il, PROJ_DOWN, ex),
5971 ]);
5972 }
5973 for &ex in sel_all.iter().take(n_used) {
5974 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
5975 }
5976 e.with_moe_cache(max_block, |cache, eng| {
5977 cache.promote_worker_reads_at_safe_boundary(
5978 &selected_blocks,
5979 &selected_blocks,
5980 eng,
5981 )?;
5982 Ok(())
5983 })?;
5984 }
5985
5986 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
5989 let mut cnt = vec![0u32; n_expert];
5990 for &s in sel_all.iter() {
5991 cnt[s as usize] += 1;
5992 }
5993 let total = sel_all.len() as f64;
5994 let mut h = 0.0f64;
5995 let mut active = 0usize;
5996 for &c in &cnt {
5997 if c > 0 {
5998 active += 1;
5999 let p = c as f64 / total;
6000 h -= p * p.log2();
6001 }
6002 }
6003 let maxc = cnt.iter().copied().max().unwrap_or(0);
6004 println!(
6005 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
6006 il,
6007 t,
6008 sel_all.len(),
6009 active,
6010 n_expert,
6011 h,
6012 (n_expert as f64).log2(),
6013 total / active.max(1) as f64,
6014 maxc
6015 );
6016 }
6017
6018 let gdec_may_fire = uniform_experts
6031 && use_cache
6032 && n_used <= 8
6033 && gdec_enabled()
6034 && !cfg.swiglu_clamped_at(il as u32);
6035 let slab_local = m
6051 .dev_exps
6052 .as_ref()
6053 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
6054 let slab_bases = slab_local.map(|d| {
6055 use cudarc::driver::DevicePtr;
6056 let s = e.stream();
6057 let (pg, _g0) = d.gate.device_ptr(&s);
6058 let (pu, _g1) = d.up.device_ptr(&s);
6059 let (pd, _g2) = d.down.device_ptr(&s);
6060 (pg as u64, pu as u64, pd as u64)
6061 });
6062 let slab_fused_may_fire = slab_bases.is_some()
6072 && n_used <= 8
6073 && gdec_enabled()
6074 && !cfg.swiglu_clamped_at(il as u32)
6075 && cfg.m3.is_none()
6076 && no_exp_macros
6077 && moe_q8;
6078 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
6081 e.uninit(t * n_embd)?
6082 } else {
6083 e.zeros(t * n_embd)?
6084 };
6085 let cpu_input = if cpu_hybrid {
6088 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
6089 } else {
6090 None
6091 };
6092
6093 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;
6101 let mut scratch_u: Option<CudaSlice<u8>> = None;
6102 let mut scratch_d: Option<CudaSlice<u8>> = None;
6103 let page_window = moe_page_prefetch_window();
6111
6112 for tok in 0..t {
6115 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
6116 let w = &w_all[tok * n_used..(tok + 1) * n_used];
6117 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
6119
6120 let no_macros = m.gate_exps.macros.is_none()
6134 && m.up_exps.macros.is_none()
6135 && m.down_exps.macros.is_none();
6136 if slab_fused_may_fire {
6146 let (pg, pu, pd) = slab_bases.unwrap();
6147 let mut gp = [0u64; 8];
6148 let mut up = [0u64; 8];
6149 let mut dp = [0u64; 8];
6150 for (j, &ex) in sel.iter().enumerate() {
6151 let ex = ex as usize;
6152 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
6153 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
6154 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
6155 }
6156 let mut wv = [0f32; 8];
6157 wv[..n_used].copy_from_slice(w);
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 let act = e.moe_gate_up_silu8_q8(
6163 crate::WPtr8(gp),
6164 crate::WPtr8(up),
6165 zq,
6166 zd,
6167 n_embd,
6168 n_ff_exp,
6169 n_used,
6170 m.gate_exps.qtype,
6171 m.up_exps.qtype,
6172 m.gate_exps.row_bytes,
6173 m.up_exps.row_bytes,
6174 )?;
6175 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
6176 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6177 e.moe_down8_fma_q8(
6178 crate::WPtr8(dp),
6179 crate::F32x8(wv),
6180 &aq2,
6181 &ad2,
6182 &mut dst,
6183 n_ff_exp,
6184 n_embd,
6185 n_used,
6186 m.down_exps.qtype,
6187 m.down_exps.row_bytes,
6188 )?;
6189 continue;
6190 }
6191 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
6192 if tok_q8.is_none() {
6193 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6194 }
6195 let (zq, zd) = tok_q8.as_ref().unwrap();
6196 if Self::moe_gdec_token_q8(
6197 e,
6198 m,
6199 il,
6200 max_block,
6201 zq,
6202 zd,
6203 sel,
6204 w,
6205 &mut moe_out,
6206 tok,
6207 n_embd,
6208 n_ff_exp,
6209 n_used,
6210 )? {
6211 continue;
6212 }
6213 } else if gdec_may_fire
6214 && cfg.m3.is_none()
6215 && no_macros
6216 && Self::moe_gdec_token(
6217 e,
6218 m,
6219 il,
6220 max_block,
6221 &zt,
6222 sel,
6223 w,
6224 &mut moe_out,
6225 tok,
6226 n_embd,
6227 n_ff_exp,
6228 n_used,
6229 )?
6230 {
6231 continue;
6232 }
6233
6234 if gdec_may_fire || slab_fused_may_fire {
6240 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6241 e.memset_zeros_view(&mut row)?;
6242 }
6243
6244 let mut cpu_mask = vec![false; sel.len()];
6250 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
6251 let gpu_resident = if use_cache {
6252 e.with_moe_cache(max_block, |cache, _| {
6253 Ok(sel
6254 .iter()
6255 .map(|&expert| {
6256 let expert = expert as u16;
6257 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
6258 .into_iter()
6259 .filter(|&projection| {
6260 cache
6261 .resident(BlockId::new(il, projection, expert))
6262 .is_some()
6263 })
6264 .count()
6265 })
6266 .collect::<Vec<_>>())
6267 })?
6268 } else {
6269 vec![0; sel.len()]
6270 };
6271 let mut cpu_selected = Vec::new();
6272 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
6273 if gpu_resident[index] != 3 {
6274 cpu_mask[index] = true;
6275 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
6276 let expert = expert as usize;
6277 cpu_selected.push((expert, route_weight));
6278 }
6279 }
6280 if crate::cpu_experts::predictor_enabled() {
6281 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6285 crate::cpu_experts::predictor_submit(il, row);
6286 }
6287 if cpu_selected.is_empty() {
6288 None
6289 } else {
6290 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6291 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
6292 .map_err(std::io::Error::other)?;
6293 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
6294 }
6295 } else {
6296 None
6297 };
6298
6299 let worker_window = worker_disk_prefetch
6300 .then(worker_prefetch_window)
6301 .unwrap_or(0);
6302 for (j, &ex) in sel.iter().enumerate() {
6303 if cpu_mask[j] {
6304 continue;
6305 }
6306 let ex = ex as usize;
6307 if let Some(d) = slab_local {
6314 let gl = m.gate_exps.expert_layout(ex);
6315 let ul = m.up_exps.expert_layout(ex);
6316 let dl = m.down_exps.expert_layout(ex);
6317 let (g0, u0, d0) = (
6318 ex * m.gate_exps.expert_stride,
6319 ex * m.up_exps.expert_stride,
6320 ex * m.down_exps.expert_stride,
6321 );
6322 let (gate, up) = if moe_q8 {
6323 if tok_q8.is_none() {
6324 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6325 }
6326 let (zq, zd) = tok_q8.as_ref().unwrap();
6327 (
6328 e.qmatvec_expert_q8(
6329 &d.gate,
6330 g0..g0 + gl.len,
6331 zq,
6332 zd,
6333 1,
6334 m.gate_exps.in_f,
6335 m.gate_exps.out_f,
6336 gl.qtype,
6337 gl.row_bytes,
6338 )?,
6339 e.qmatvec_expert_q8(
6340 &d.up,
6341 u0..u0 + ul.len,
6342 zq,
6343 zd,
6344 1,
6345 m.up_exps.in_f,
6346 m.up_exps.out_f,
6347 ul.qtype,
6348 ul.row_bytes,
6349 )?,
6350 )
6351 } else {
6352 (
6353 e.qmatvec_view(
6354 &d.gate,
6355 g0..g0 + gl.len,
6356 &zt,
6357 1,
6358 m.gate_exps.in_f,
6359 m.gate_exps.out_f,
6360 gl.qtype,
6361 gl.row_bytes,
6362 )?,
6363 e.qmatvec_view(
6364 &d.up,
6365 u0..u0 + ul.len,
6366 &zt,
6367 1,
6368 m.up_exps.in_f,
6369 m.up_exps.out_f,
6370 ul.qtype,
6371 ul.row_bytes,
6372 )?,
6373 )
6374 };
6375 let mut act = e.uninit(n_ff_exp)?;
6376 Self::ffn_act_lim(
6377 e,
6378 cfg,
6379 &gate,
6380 &up,
6381 m.gate_exps.macro_scale(ex),
6382 m.up_exps.macro_scale(ex),
6383 lim_exp,
6384 &mut act,
6385 n_ff_exp,
6386 )?;
6387 let y = if moe_q8 {
6388 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6389 e.qmatvec_expert_q8(
6390 &d.down,
6391 d0..d0 + dl.len,
6392 &aq2,
6393 &ad2,
6394 1,
6395 m.down_exps.in_f,
6396 m.down_exps.out_f,
6397 dl.qtype,
6398 dl.row_bytes,
6399 )?
6400 } else {
6401 let actv = act.slice(0..n_ff_exp);
6402 e.qmatvec_view(
6403 &d.down,
6404 d0..d0 + dl.len,
6405 &actv,
6406 1,
6407 m.down_exps.in_f,
6408 m.down_exps.out_f,
6409 dl.qtype,
6410 dl.row_bytes,
6411 )?
6412 };
6413 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6414 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6415 continue;
6416 }
6417 for next in page_prefetch_positions(j, sel.len(), page_window) {
6418 Self::moe_prefetch_host_expert(sel[next] as usize, m);
6419 }
6420 let keep = [
6421 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
6422 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
6423 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
6424 ];
6425 if worker_disk_prefetch && worker_window > 0 {
6426 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
6427 Self::moe_prefetch_disk_expert(
6428 e,
6429 il,
6430 sel[next] as usize,
6431 m,
6432 max_block,
6433 &keep,
6434 )?;
6435 }
6436 } else if cache_dispatch
6437 && !cpu_hybrid
6438 && moe_prefetch_enabled()
6439 && j + 1 < sel.len()
6440 {
6441 let next = sel[j + 1] as usize;
6442 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
6443 }
6444 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
6445 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
6446 if (gate_q8 || up_q8) && tok_q8.is_none() {
6449 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6450 }
6451 let gate = if gate_q8 {
6452 let (zq, zd) = tok_q8.as_ref().unwrap();
6453 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
6454 } else {
6455 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
6456 };
6457 let up = if up_q8 {
6458 let (zq, zd) = tok_q8.as_ref().unwrap();
6459 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
6460 } else {
6461 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
6462 };
6463 let mut act = e.uninit(n_ff_exp)?;
6464 Self::ffn_act_lim(
6465 e,
6466 cfg,
6467 &gate,
6468 &up,
6469 m.gate_exps.macro_scale(ex),
6470 m.up_exps.macro_scale(ex),
6471 lim_exp,
6472 &mut act,
6473 n_ff_exp,
6474 )?;
6475 let y = if down_q8 {
6476 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6477 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
6478 } else {
6479 let actv = act.slice(0..n_ff_exp);
6480 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
6481 };
6482 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6483 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6485 } else if cache_dispatch {
6486 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
6491 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
6492 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6494 e,
6495 cfg,
6496 &gate,
6497 &up,
6498 m.gate_exps.macro_scale(ex),
6499 m.up_exps.macro_scale(ex),
6500 lim_exp,
6501 &mut act,
6502 n_ff_exp,
6503 )?;
6504 let actv = act.slice(0..n_ff_exp);
6505 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
6506 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6507 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6509 } else if cache_frozen {
6510 let gate = Self::moe_frozen_gemm(
6515 e,
6516 il,
6517 PROJ_GATE,
6518 ex,
6519 m,
6520 max_block,
6521 &zt,
6522 &mut scratch_g,
6523 g_len,
6524 )?;
6525 let up = Self::moe_frozen_gemm(
6526 e,
6527 il,
6528 PROJ_UP,
6529 ex,
6530 m,
6531 max_block,
6532 &zt,
6533 &mut scratch_u,
6534 u_len,
6535 )?;
6536 let mut act = e.uninit(n_ff_exp)?;
6537 Self::ffn_act_lim(
6538 e,
6539 cfg,
6540 &gate,
6541 &up,
6542 m.gate_exps.macro_scale(ex),
6543 m.up_exps.macro_scale(ex),
6544 lim_exp,
6545 &mut act,
6546 n_ff_exp,
6547 )?;
6548 let actv = act.slice(0..n_ff_exp);
6549 let y = Self::moe_frozen_gemm(
6550 e,
6551 il,
6552 PROJ_DOWN,
6553 ex,
6554 m,
6555 max_block,
6556 &actv,
6557 &mut scratch_d,
6558 d_len,
6559 )?;
6560 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6561 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6562 } else {
6563 if scratch_g.is_none() {
6567 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
6568 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
6569 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
6570 }
6571 let (sg, su, sd) = (
6572 scratch_g.as_mut().unwrap(),
6573 scratch_u.as_mut().unwrap(),
6574 scratch_d.as_mut().unwrap(),
6575 );
6576 let gl = m.gate_exps.expert_layout(ex);
6577 let ul = m.up_exps.expert_layout(ex);
6578 let dl = m.down_exps.expert_layout(ex);
6579 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
6580 let gate = e.qmatvec_view(
6581 sg,
6582 0..gl.len,
6583 &zt,
6584 1,
6585 m.gate_exps.in_f,
6586 m.gate_exps.out_f,
6587 gl.qtype,
6588 gl.row_bytes,
6589 )?;
6590
6591 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
6592 let up = e.qmatvec_view(
6593 su,
6594 0..ul.len,
6595 &zt,
6596 1,
6597 m.up_exps.in_f,
6598 m.up_exps.out_f,
6599 ul.qtype,
6600 ul.row_bytes,
6601 )?;
6602
6603 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6605 e,
6606 cfg,
6607 &gate,
6608 &up,
6609 m.gate_exps.macro_scale(ex),
6610 m.up_exps.macro_scale(ex),
6611 lim_exp,
6612 &mut act,
6613 n_ff_exp,
6614 )?;
6615
6616 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
6617 let actv = act.slice(0..n_ff_exp);
6618 let y = e.qmatvec_view(
6619 sd,
6620 0..dl.len,
6621 &actv,
6622 1,
6623 m.down_exps.in_f,
6624 m.down_exps.out_f,
6625 dl.qtype,
6626 dl.row_bytes,
6627 )?;
6628
6629 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6630 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6631 }
6632 }
6633 if let Some(worker) = cpu_worker {
6634 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
6635 let cpu_output = e.htod(&cpu_output)?;
6636 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6637 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
6638 }
6639 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
6640 for (j, &ex) in sel.iter().enumerate() {
6641 if cpu_mask[j] {
6642 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
6643 }
6644 }
6645 }
6646 }
6647
6648 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6653 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6654 {
6655 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
6664 let (sg_gate, sg_up) = if t == 1 {
6665 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
6666 } else if verify_t {
6667 (
6668 e.matmul_decode_exact(gate_shexp, z, t)?,
6669 e.matmul_decode_exact(up_shexp, z, t)?,
6670 )
6671 } else {
6672 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
6674 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
6676 e,
6677 cfg,
6678 &sg_gate,
6679 &sg_up,
6680 1.0,
6681 1.0,
6682 lim_shexp,
6683 &mut sa,
6684 t * n_ff_sh,
6685 )?;
6686 let sh = if verify_t {
6687 e.matmul_decode_exact(down_shexp, &sa, t)?
6688 } else {
6689 e.matmul(down_shexp, &sa, t)?
6690 }; let g = match &m.gate_inp_shexp {
6704 Some(gate_inp_shexp) => {
6705 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
6706 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6707 } else {
6708 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6709 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
6711 g
6712 }
6713 }
6714 None => e.htod(&vec![1.0f32; t])?,
6715 };
6716 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6718 }
6719
6720 Ok(moe_out)
6721 }
6722
6723 pub fn stage1_h2d_per_token(&self) -> u64 {
6726 use crate::hybrid::Ffn;
6727 let n_used = self
6728 .cfg
6729 .moe
6730 .as_ref()
6731 .map(|m| m.expert_used_count as u64)
6732 .unwrap_or(0);
6733 let mut bytes = 0u64;
6734 for l in self.layers.iter() {
6735 if let Ffn::Moe(m) = &l.ffn {
6736 bytes += n_used
6737 * (m.gate_exps.max_expert_bytes()
6738 + m.up_exps.max_expert_bytes()
6739 + m.down_exps.max_expert_bytes()) as u64;
6740 }
6741 }
6742 bytes
6743 }
6744
6745 pub(crate) fn max_moe_block(&self) -> usize {
6749 use crate::hybrid::Ffn;
6750 let mut mx = 0usize;
6751 let mut scan = |ffn: &Ffn| {
6752 if let Ffn::Moe(m) = ffn {
6753 mx = mx
6754 .max(m.gate_exps.max_expert_bytes())
6755 .max(m.up_exps.max_expert_bytes())
6756 .max(m.down_exps.max_expert_bytes());
6757 }
6758 };
6759 for l in self.layers.iter() {
6760 scan(&l.ffn);
6761 }
6762 if let Some(mtp) = self.mtp.as_ref() {
6763 scan(&mtp.ffn);
6764 }
6765 mx
6766 }
6767
6768 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
6771 use crate::hybrid::Ffn;
6772 let mut sizes = Vec::new();
6773 let mut scan = |ffn: &Ffn| {
6774 let Ffn::Moe(m) = ffn else { return };
6775 for ex in 0..m.gate_exps.n_expert {
6776 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
6777 continue;
6778 }
6779 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
6780 let len = exps.expert_layout(ex).len;
6781 if len > 0 {
6782 sizes.push(len);
6783 }
6784 }
6785 }
6786 };
6787 for layer in &self.layers {
6788 scan(&layer.ffn);
6789 }
6790 if let Some(mtp) = &self.mtp {
6791 scan(&mtp.ffn);
6792 }
6793 sizes
6794 }
6795
6796 pub fn save_cpu_expert_residency_profile(
6802 &self,
6803 e: &Engine,
6804 path: &std::path::Path,
6805 ) -> Result<(), Box<dyn std::error::Error>> {
6806 let Some(ids) = e.export_moe_residency() else {
6807 return Err("no MoE residency cache to persist".into());
6808 };
6809 let mut body = format!(
6810 "memra-freeze-profile v1 max_block={} blocks={}\n",
6811 self.max_moe_block(),
6812 ids.len()
6813 );
6814 for (layer, proj, ex) in &ids {
6815 body.push_str(&format!("{layer} {proj} {ex}\n"));
6816 }
6817 let tmp = path.with_extension("tmp");
6818 std::fs::write(&tmp, body)?;
6819 std::fs::rename(&tmp, path)?;
6820 println!(
6821 "[moe-cache] freeze profile saved: {} blocks -> {}",
6822 ids.len(),
6823 path.display()
6824 );
6825 Ok(())
6826 }
6827
6828 pub fn restore_cpu_expert_residency_profile(
6832 &self,
6833 e: &Engine,
6834 path: &std::path::Path,
6835 ) -> Result<bool, Box<dyn std::error::Error>> {
6836 use crate::hybrid::Ffn;
6837 use crate::moe_cache::BlockId;
6838 let Ok(content) = std::fs::read_to_string(path) else {
6839 return Ok(false);
6840 };
6841 let mut lines = content.lines();
6842 let Some(header) = lines.next() else {
6843 return Ok(false);
6844 };
6845 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
6846 if !header.starts_with(&expected) {
6847 println!(
6848 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
6849 path.display()
6850 );
6851 return Ok(false);
6852 }
6853 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
6854 std::collections::HashMap::new();
6855 for line in lines {
6856 let mut fields = line.split_whitespace();
6857 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
6858 else {
6859 continue;
6860 };
6861 let (Ok(layer), Ok(proj), Ok(ex)) =
6862 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
6863 else {
6864 continue;
6865 };
6866 by_layer
6867 .entry(layer)
6868 .or_default()
6869 .push(BlockId::new(layer, proj, ex));
6870 }
6871 let requested: usize = by_layer.values().map(Vec::len).sum();
6872 if requested == 0 {
6873 return Ok(false);
6874 }
6875 let max_block = self.max_moe_block();
6876 let mut restaged = 0usize;
6877 let mut stage_layer =
6878 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
6879 let Ffn::Moe(m) = ffn else { return Ok(()) };
6880 let Some(ids) = by_layer.get(&layer_index) else {
6881 return Ok(());
6882 };
6883 e.with_moe_cache(max_block, |cache, eng| {
6884 for id in ids {
6885 if cache.restage_block(*id, m, eng)? {
6886 restaged += 1;
6887 }
6888 }
6889 Ok(())
6890 })
6891 };
6892 for (index, layer) in self.layers.iter().enumerate() {
6893 stage_layer(index as u16, &layer.ffn)?;
6894 }
6895 if let Some(mtp) = self.mtp.as_ref() {
6896 stage_layer(u16::MAX, &mtp.ffn)?;
6897 }
6898 e.freeze_moe_cache();
6899 println!(
6900 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
6901 path.display()
6902 );
6903 Ok(true)
6904 }
6905
6906 pub fn freeze_cpu_expert_residency(
6908 &self,
6909 e: &Engine,
6910 ) -> Result<(), Box<dyn std::error::Error>> {
6911 e.freeze_moe_cache();
6912 Ok(())
6913 }
6914
6915 pub fn ffn_act(
6923 e: &Engine,
6924 cfg: &ModelConfig,
6925 gate: &CudaSlice<f32>,
6926 up: &CudaSlice<f32>,
6927 act: &mut CudaSlice<f32>,
6928 n: usize,
6929 ) -> Result<(), Box<dyn std::error::Error>> {
6930 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
6931 }
6932
6933 #[allow(clippy::too_many_arguments)]
6937 pub(crate) fn ffn_act_scaled(
6938 e: &Engine,
6939 cfg: &ModelConfig,
6940 gate: &CudaSlice<f32>,
6941 up: &CudaSlice<f32>,
6942 gs: f32,
6943 us: f32,
6944 act: &mut CudaSlice<f32>,
6945 n: usize,
6946 ) -> Result<(), Box<dyn std::error::Error>> {
6947 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
6948 }
6949
6950 #[allow(clippy::too_many_arguments)]
6959 pub(crate) fn ffn_act_lim(
6960 e: &Engine,
6961 cfg: &ModelConfig,
6962 gate: &CudaSlice<f32>,
6963 up: &CudaSlice<f32>,
6964 gs: f32,
6965 us: f32,
6966 limit: Option<f32>,
6967 act: &mut CudaSlice<f32>,
6968 n: usize,
6969 ) -> Result<(), Box<dyn std::error::Error>> {
6970 if let Some(m3) = cfg.m3.as_ref() {
6971 debug_assert!(
6972 limit.is_none(),
6973 "m3 swigluoai and step35 clamp are different archs"
6974 );
6975 return e.swigluoai_mul_scaled(
6976 gate,
6977 up,
6978 gs,
6979 us,
6980 m3.swiglu_alpha,
6981 m3.swiglu_limit,
6982 act,
6983 n,
6984 );
6985 }
6986 if let Some(l) = limit {
6987 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
6988 }
6989 if gs == 1.0 && us == 1.0 {
6990 return e.silu_mul(gate, up, act, n);
6991 }
6992 e.silu_mul_scaled(gate, up, gs, us, act, n)
6993 }
6994
6995 fn moe_route(
7001 e: &Engine,
7002 logits: &CudaSlice<f32>,
7003 t: usize,
7004 n_expert: usize,
7005 n_used: usize,
7006 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7007 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
7008 }
7009
7010 #[allow(clippy::too_many_arguments)]
7018 fn moe_route_sigmoid_cfg(
7019 e: &Engine,
7020 logits: &CudaSlice<f32>,
7021 t: usize,
7022 n_expert: usize,
7023 n_used: usize,
7024 m: &MoeWeights,
7025 (sf, route_norm): (f32, bool),
7026 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7027 if sigmoid_router_enabled() {
7028 return e.moe_router_sigmoid_topk_host(
7029 logits,
7030 t,
7031 n_expert,
7032 n_used,
7033 m.active_count(),
7034 &m.exp_probs_b_dev,
7035 &m.active_experts_dev,
7036 sf,
7037 route_norm,
7038 );
7039 }
7040 let lg = e.dtoh(logits)?;
7041 Self::moe_route_sigmoid_host(
7042 &lg,
7043 t,
7044 n_expert,
7045 n_used,
7046 m.exp_probs_b.as_deref(),
7047 sf,
7048 route_norm,
7049 m.active_experts.as_deref(),
7050 )
7051 }
7052
7053 fn moe_route_cfg(
7056 e: &Engine,
7057 logits: &CudaSlice<f32>,
7058 t: usize,
7059 n_expert: usize,
7060 n_used: usize,
7061 active: Option<&[bool]>,
7062 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7063 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
7066 return e.moe_router_topk_host(logits, t, n_expert, n_used);
7067 }
7068 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
7071 let mut w_out = vec![0f32; t * n_used];
7072 for tok in 0..t {
7073 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7074 let maxl = row
7076 .iter()
7077 .enumerate()
7078 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
7079 .map(|(_, &x)| x)
7080 .fold(f32::NEG_INFINITY, f32::max);
7081 let mut probs = vec![0f32; n_expert];
7082 let mut den = 0f32;
7083 for i in 0..n_expert {
7084 if active.is_some_and(|mask| !mask[i]) {
7085 continue;
7086 }
7087 let x = (row[i] - maxl).exp();
7088 probs[i] = x;
7089 den += x;
7090 }
7091 for p in probs.iter_mut() {
7092 *p /= den;
7093 }
7094 let mut idx: Vec<usize> = (0..n_expert)
7096 .filter(|&i| active.is_none_or(|mask| mask[i]))
7097 .collect();
7098 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
7099 let sl = &idx[..n_used];
7100 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
7101 let mut ws: f32 = wv.iter().sum();
7102 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
7104 *x /= ws;
7105 }
7106 for j in 0..n_used {
7107 sel[tok * n_used + j] = sl[j] as u32;
7108 w_out[tok * n_used + j] = wv[j];
7109 }
7110 }
7111 Ok((sel, w_out))
7112 }
7113
7114 #[allow(clippy::too_many_arguments)]
7115 fn moe_route_sigmoid_with_input(
7116 e: &Engine,
7117 logits: &CudaSlice<f32>,
7118 input: &CudaSlice<f32>,
7119 t: usize,
7120 in_features: usize,
7121 n_expert: usize,
7122 n_used: usize,
7123 bias: Option<&[f32]>,
7124 (sf, route_norm): (f32, bool),
7125 active: Option<&[bool]>,
7126 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
7127 let logit_values =
7128 active_matrix_values(logits.len(), t, n_expert, "sigmoid router logits")?;
7129 let input_values =
7130 active_matrix_values(input.len(), t, in_features, "sigmoid router input")?;
7131 let (lg, input) = e.dtoh_pair_views(
7132 &logits.slice(0..logit_values),
7133 &input.slice(0..input_values),
7134 )?;
7135 let (sel, w) =
7136 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
7137 Ok((sel, w, input))
7138 }
7139
7140 pub fn start_moe_prefetch_predictor(
7145 &self,
7146 e: &Engine,
7147 cfg: &ModelConfig,
7148 ) -> Result<(), Box<dyn std::error::Error>> {
7149 use crate::hybrid::Ffn;
7150 let Some(sig) = cfg.sigmoid_router() else {
7151 return Err("prefetch predictor requires a sigmoid-router arch".into());
7152 };
7153 let resident: std::collections::HashSet<(u16, u8, u16)> = e
7154 .export_moe_residency()
7155 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
7156 .into_iter()
7157 .collect();
7158 let mut layers = Vec::new();
7159 for (index, layer) in self.layers.iter().enumerate() {
7160 let Ffn::Moe(m) = &layer.ffn else { continue };
7161 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
7162 continue;
7163 };
7164 let router = e.dtoh(data)?;
7165 let n_expert = m.gate_exps.n_expert;
7166 let n_embd = m.gate_exps.in_f;
7167 if router.len() != n_embd * n_expert {
7168 continue;
7169 }
7170 let build = |exps: &crate::model::HostExps| {
7171 (0..n_expert)
7172 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
7173 .collect::<Vec<_>>()
7174 };
7175 layers.push((
7176 index as u16,
7177 crate::cpu_experts::PredictLayerInit {
7178 router,
7179 bias: m.exp_probs_b.clone(),
7180 active: m.active_experts.clone(),
7181 n_embd,
7182 n_used: cfg
7183 .moe
7184 .as_ref()
7185 .map(|moe| moe.expert_used_count as usize)
7186 .ok_or("prefetch predictor requires MoE config")?,
7187 sig,
7188 weights_n_expert: n_expert,
7189 gate: build(&m.gate_exps),
7190 up: build(&m.up_exps),
7191 down: build(&m.down_exps),
7192 },
7193 ));
7194 }
7195 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
7196 }
7197
7198 #[allow(clippy::too_many_arguments)]
7201 pub fn moe_route_sigmoid_host_public(
7202 logits: &[f32],
7203 t: usize,
7204 n_expert: usize,
7205 n_used: usize,
7206 bias: Option<&[f32]>,
7207 sf: f32,
7208 route_norm: bool,
7209 active: Option<&[bool]>,
7210 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7211 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
7212 }
7213
7214 #[allow(clippy::too_many_arguments)]
7215 fn moe_route_sigmoid_host(
7216 lg: &[f32],
7217 t: usize,
7218 n_expert: usize,
7219 n_used: usize,
7220 bias: Option<&[f32]>,
7221 sf: f32,
7222 route_norm: bool,
7223 active: Option<&[bool]>,
7224 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7225 let active_count = active
7226 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
7227 .unwrap_or(n_expert);
7228 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
7229 if lg.len() != t * n_expert {
7230 return Err(format!(
7231 "sigmoid router logits length mismatch: got {}, expected {}",
7232 lg.len(),
7233 t * n_expert,
7234 )
7235 .into());
7236 }
7237 let mut sel = vec![0u32; t * n_used];
7238 let mut w_out = vec![0f32; t * n_used];
7239 for tok in 0..t {
7240 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7241 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
7242 let selsc: Vec<f32> = match bias {
7244 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
7245 None => scores.clone(),
7246 };
7247 let mut idx: Vec<usize> = (0..n_expert)
7248 .filter(|&i| active.is_none_or(|mask| mask[i]))
7249 .collect();
7250 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
7251 let sl = &idx[..n_used];
7252 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
7253 if route_norm {
7254 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
7255 for x in wv.iter_mut() {
7256 *x = *x / ws * sf;
7257 }
7258 } else {
7259 for x in wv.iter_mut() {
7260 *x *= sf;
7261 }
7262 }
7263 for j in 0..n_used {
7264 sel[tok * n_used + j] = sl[j] as u32;
7265 w_out[tok * n_used + j] = wv[j];
7266 }
7267 }
7268 Ok((sel, w_out))
7269 }
7270
7271 #[allow(clippy::too_many_arguments)]
7275 fn moe_ffn_sigmoid_dev(
7276 e: &Engine,
7277 m: &MoeWeights,
7278 z: &CudaSlice<f32>,
7279 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7280 logits: &CudaSlice<f32>,
7281 t: usize,
7282 cfg: &ModelConfig,
7283 il: u16,
7284 (scaling_factor, route_norm): (f32, bool),
7285 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7286 let moe = cfg.moe.as_ref().unwrap();
7287 let n_embd = cfg.n_embd as usize;
7288 let n_expert = moe.expert_count as usize;
7289 let n_used = moe.expert_used_count as usize;
7290 let n_ff_exp = moe.expert_ff_length as usize;
7291 let dev = m.dev_exps.as_ref().unwrap();
7292 debug_assert_eq!(dev.dev, e.ctx().ordinal());
7293 debug_assert!(m.has_uniform_expert_layout());
7294 debug_assert!(!m.has_macros);
7295
7296 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
7297 logits,
7298 t,
7299 n_expert,
7300 n_used,
7301 m.active_count(),
7302 &m.exp_probs_b_dev,
7303 &m.active_experts_dev,
7304 scaling_factor,
7305 route_norm,
7306 )?;
7307 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
7308 if let Some(fp8) = dev.fp8_blk.as_ref() {
7309 debug_assert_eq!(m.gate_exps.qtype, crate::QT_F8_E4M3_BLK);
7310 debug_assert_eq!(m.up_exps.qtype, crate::QT_F8_E4M3_BLK);
7311 debug_assert_eq!(m.down_exps.qtype, crate::QT_F8_E4M3_BLK);
7312 debug_assert_eq!(fp8.gate.rows, m.gate_exps.out_f.div_ceil(128));
7313 debug_assert_eq!(fp8.up.rows, m.up_exps.out_f.div_ceil(128));
7314 debug_assert_eq!(fp8.down.rows, m.down_exps.out_f.div_ceil(128));
7315
7316 let selected = e.dtoh_i32(&sel_d)?;
7323 let route_weights = e.dtoh(&w_d)?;
7324 let mut moe_out = e.zeros(t * n_embd)?;
7325 for tok in 0..t {
7326 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
7327 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7328 for j in 0..n_used {
7329 let pair = tok * n_used + j;
7330 let expert = selected[pair] as usize;
7331 let gate = Self::moe_resident_fp8_e4m3(
7332 e,
7333 &m.gate_exps,
7334 &dev.gate,
7335 &fp8.gate,
7336 expert,
7337 &zt,
7338 1,
7339 )?;
7340 let up = Self::moe_resident_fp8_e4m3(
7341 e, &m.up_exps, &dev.up, &fp8.up, expert, &zt, 1,
7342 )?;
7343 let mut act = e.uninit(n_ff_exp)?;
7344 Self::ffn_act_lim(
7345 e,
7346 cfg,
7347 &gate,
7348 &up,
7349 1.0,
7350 1.0,
7351 cfg.clamp_exp_at(il as u32),
7352 &mut act,
7353 n_ff_exp,
7354 )?;
7355 let act = act.slice(0..n_ff_exp);
7356 let down = Self::moe_resident_fp8_e4m3(
7357 e,
7358 &m.down_exps,
7359 &dev.down,
7360 &fp8.down,
7361 expert,
7362 &act,
7363 1,
7364 )?;
7365 e.axpy_into(&down, route_weights[pair], &mut dst, n_embd)?;
7366 }
7367 }
7368 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7369 eprintln!(
7370 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} \
7371 native=fp8blk-w8a8-e4m3-reference clamp={}",
7372 cfg.clamp_exp_at(il as u32).is_some(),
7373 );
7374 }
7375 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7376 return Ok(moe_out);
7377 }
7378 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
7379 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7380 (combined, combined)
7381 } else {
7382 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7383 };
7384 let (zq, zd) = match (t, zq8) {
7385 (1, Some((q, d))) => (q.clone(), d.clone()),
7386 _ => e.quantize_q8_1(z, t, n_embd)?,
7387 };
7388 let n_pairs = t * n_used;
7389 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
7390 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7394 let pair_tok_d = e.htod_i32(&pair_tok)?;
7395 let gate = e.moe_pairs_matvec_q8(
7396 &dev.ptr_row,
7397 0,
7398 &pair_tok_d,
7399 &sel_d,
7400 &zq,
7401 &zd,
7402 n_embd,
7403 n_ff_exp,
7404 n_expert,
7405 n_pairs,
7406 m.gate_exps.qtype,
7407 gate_row_bytes,
7408 )?;
7409 let up = e.moe_pairs_matvec_q8(
7410 &dev.ptr_row,
7411 1,
7412 &pair_tok_d,
7413 &sel_d,
7414 &zq,
7415 &zd,
7416 n_embd,
7417 n_ff_exp,
7418 n_expert,
7419 n_pairs,
7420 m.up_exps.qtype,
7421 up_row_bytes,
7422 )?;
7423 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7424 Self::ffn_act_lim(
7425 e,
7426 cfg,
7427 &gate,
7428 &up,
7429 1.0,
7430 1.0,
7431 cfg.clamp_exp_at(il as u32),
7432 &mut act,
7433 n_pairs * n_ff_exp,
7434 )?;
7435 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7436 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7437 let pair_self_d = e.htod_i32(&pair_self)?;
7438 let down = e.moe_pairs_matvec_q8(
7439 &dev.ptr_row,
7440 2,
7441 &pair_self_d,
7442 &sel_d,
7443 &aq2,
7444 &ad2,
7445 n_ff_exp,
7446 n_embd,
7447 n_expert,
7448 n_pairs,
7449 m.down_exps.qtype,
7450 m.down_exps.row_bytes,
7451 )?;
7452 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7453 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7454 let tok_off_d = e.htod_i32(&tok_off)?;
7455 let tok_ids_d = e.htod_i32(&tok_ids)?;
7456 let mut output = e.uninit(t * n_embd)?;
7457 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
7458 output
7459 } else {
7460 let act = e.moe_gate_up_silu8_dev_q8_rows(
7461 &dev.ptr_row,
7462 &sel_d,
7463 &zq,
7464 &zd,
7465 t,
7466 n_embd,
7467 n_ff_exp,
7468 n_used,
7469 n_expert,
7470 m.gate_exps.qtype,
7471 m.up_exps.qtype,
7472 gate_row_bytes,
7473 up_row_bytes,
7474 &m.dev_macros,
7475 )?;
7476 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7477 let mut output = e.uninit(t * n_embd)?;
7478 e.moe_down8_fma_dev_q8_rows_g(
7479 &dev.ptr_row,
7480 &sel_d,
7481 &w_d,
7482 &aq2,
7483 &ad2,
7484 &mut output,
7485 t,
7486 n_ff_exp,
7487 n_embd,
7488 n_used,
7489 n_expert,
7490 m.down_exps.qtype,
7491 m.down_exps.row_bytes,
7492 )?;
7493 output
7494 };
7495
7496 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7497 eprintln!(
7498 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
7499 cfg.clamp_exp_at(il as u32).is_some(),
7500 dev.gu_il,
7501 );
7502 }
7503 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7504 Ok(moe_out)
7505 }
7506
7507 #[allow(clippy::too_many_arguments)]
7508 fn moe_resident_fp8_e4m3(
7509 e: &Engine,
7510 exps: &crate::model::HostExps,
7511 bytes: &CudaSlice<u8>,
7512 scales: &crate::hybrid::DevExpertFp8ProjectionScales,
7513 expert: usize,
7514 x: &cudarc::driver::CudaView<f32>,
7515 m: usize,
7516 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7517 let layout = exps.expert_layout(expert);
7518 debug_assert_eq!(layout.qtype, crate::QT_F8_E4M3_BLK);
7519 debug_assert_eq!(scales.rows * scales.cols, scales.expert_stride);
7520 let byte_start = expert * exps.expert_stride;
7521 let scale_start = expert * scales.expert_stride;
7522 let weight = bytes.slice(byte_start..byte_start + layout.len);
7523 let scale = scales
7524 .scales
7525 .slice(scale_start..scale_start + scales.expert_stride);
7526 e.qmatvec_mmq_fp8_blk_view(&weight, &scale, x, m, exps.in_f, exps.out_f)
7527 }
7528
7529 fn moe_ffn_pairs(
7538 e: &Engine,
7539 m: &MoeWeights,
7540 z: &CudaSlice<f32>,
7541 logits: &CudaSlice<f32>,
7542 t: usize,
7543 cfg: &ModelConfig,
7544 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7545 let moe = cfg.moe.as_ref().unwrap();
7546 let n_embd = cfg.n_embd as usize;
7547 let n_expert = moe.expert_count as usize;
7548 let n_used = moe.expert_used_count as usize;
7549 let n_ff_exp = moe.expert_ff_length as usize;
7550 debug_assert!(
7555 !cfg.swiglu_clamped_anywhere(),
7556 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
7557 );
7558 let dev = m.dev_exps.as_ref().unwrap();
7559 let (rbg_d, rbu_d) = if dev.gu_il {
7561 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7562 (sxx, sxx)
7563 } else {
7564 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7565 };
7566
7567 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
7568 let n_pairs = t * n_used;
7569 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
7572 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
7573 let pair_w: Vec<f32> = w_all.clone();
7574 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7575 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7576 let pt = e.htod_i32(&pair_tok)?;
7577 let px = e.htod_i32(&pair_ex)?;
7578 let pw = e.htod(&pair_w)?;
7579 let toff = e.htod_i32(&tok_off)?;
7580 let tids = e.htod_i32(&tok_ids)?;
7581
7582 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7586 for p in 0..n_pairs {
7587 by_ex[pair_ex[p] as usize].push(p as i32);
7588 }
7589 let mut ex_ids: Vec<i32> = Vec::new();
7590 let mut ex_off: Vec<i32> = vec![0];
7591 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
7592 for (ex, list) in by_ex.iter().enumerate() {
7593 if list.is_empty() {
7594 continue;
7595 }
7596 ex_ids.push(ex as i32);
7597 ex_pairs.extend_from_slice(list);
7598 ex_off.push(ex_pairs.len() as i32);
7599 }
7600 let n_active = ex_ids.len();
7601 let exi = e.htod_i32(&ex_ids)?;
7602 let exo = e.htod_i32(&ex_off)?;
7603 let exp_d = e.htod_i32(&ex_pairs)?;
7604 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7625 let mma_t = *MMA_T.get_or_init(|| {
7626 std::env::var("MEMRA_MOE_MMA_T")
7627 .ok()
7628 .and_then(|v| v.parse().ok())
7629 .unwrap_or(16)
7630 });
7631 let use_mma = std::env::var("MEMRA_MOE_MMA")
7632 .map(|v| v != "0")
7633 .unwrap_or(true)
7634 && t >= mma_t
7635 && q8_expert_dec_supported(m.gate_exps.qtype)
7636 && q8_expert_dec_supported(m.up_exps.qtype)
7637 && q8_expert_dec_supported(m.down_exps.qtype)
7638 && n_embd % 256 == 0
7639 && n_ff_exp % 256 == 0;
7640 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
7656 && q8_expert_dec_supported(m.up_exps.qtype)
7657 && q8_expert_dec_supported(m.down_exps.qtype)
7658 && n_embd % 256 == 0
7659 && n_ff_exp % 256 == 0;
7660 let f16g_mode = crate::moe_f16g_mode();
7661 let f16g = f16g_mode != 0
7662 && t >= mma_t
7663 && (f16g_mode != 3 || !mma_capable)
7664 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
7665 && f16g_proj_ok(m.up_exps.qtype, n_embd)
7666 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
7667 if use_mma || f16g {
7668 let y_down = if f16g {
7676 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
7680 let csr_tok_d = e.htod_i32(&csr_tok)?;
7681 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
7682 let g_csr = e.moe_f16_grouped(
7683 &dev.ptr_row,
7684 0,
7685 n_expert,
7686 &exi,
7687 &ex_off,
7688 &exo,
7689 &z_f16,
7690 &z_s,
7691 n_embd,
7692 n_ff_exp,
7693 n_active,
7694 n_pairs,
7695 m.gate_exps.qtype,
7696 rbg_d,
7697 )?;
7698 let u_csr = e.moe_f16_grouped(
7699 &dev.ptr_row,
7700 1,
7701 n_expert,
7702 &exi,
7703 &ex_off,
7704 &exo,
7705 &z_f16,
7706 &z_s,
7707 n_embd,
7708 n_ff_exp,
7709 n_active,
7710 n_pairs,
7711 m.up_exps.qtype,
7712 rbu_d,
7713 )?;
7714 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
7715 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
7716 let d_csr = e.moe_f16_grouped(
7717 &dev.ptr_row,
7718 2,
7719 n_expert,
7720 &exi,
7721 &ex_off,
7722 &exo,
7723 &a_f16,
7724 &a_s,
7725 n_ff_exp,
7726 n_embd,
7727 n_active,
7728 n_pairs,
7729 m.down_exps.qtype,
7730 m.down_exps.row_bytes,
7731 )?;
7732 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
7733 } else {
7734 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
7736 let gate = e.mmq_iq_experts(
7737 &dev.ptr_row,
7738 0,
7739 n_expert,
7740 &exi,
7741 &exo,
7742 &exp_d,
7743 &pt,
7744 &z_scr,
7745 n_embd,
7746 n_ff_exp,
7747 n_active,
7748 n_pairs,
7749 t,
7750 m.gate_exps.qtype,
7751 rbg_d,
7752 )?;
7753 let up = e.mmq_iq_experts(
7754 &dev.ptr_row,
7755 1,
7756 n_expert,
7757 &exi,
7758 &exo,
7759 &exp_d,
7760 &pt,
7761 &z_scr,
7762 n_embd,
7763 n_ff_exp,
7764 n_active,
7765 n_pairs,
7766 t,
7767 m.up_exps.qtype,
7768 rbu_d,
7769 )?;
7770 let a_scr = if crate::moe_fuse_actq_on() {
7776 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
7777 } else {
7778 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7779 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
7780 };
7781 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7782 let pself = e.htod_i32(&pair_self)?;
7783 e.mmq_iq_experts(
7784 &dev.ptr_row,
7785 2,
7786 n_expert,
7787 &exi,
7788 &exo,
7789 &exp_d,
7790 &pself,
7791 &a_scr,
7792 n_ff_exp,
7793 n_embd,
7794 n_active,
7795 n_pairs,
7796 n_pairs,
7797 m.down_exps.qtype,
7798 m.down_exps.row_bytes,
7799 )?
7800 };
7801 let mut moe_out = e.uninit(t * n_embd)?;
7802 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7803 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7804 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7805 {
7806 let n_ff_sh = gate_shexp.out_features();
7807 let sg_gate = e.matmul(gate_shexp, z, t)?;
7808 let sg_up = e.matmul(up_shexp, z, t)?;
7809 let mut sa = e.uninit(t * n_ff_sh)?;
7810 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7811 let sh = e.matmul(down_shexp, &sa, t)?;
7812 let g = match &m.gate_inp_shexp {
7818 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7819 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7820 }
7821 Some(gate_inp_shexp) => {
7822 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7823 let mut g = e.uninit(t)?;
7824 e.sigmoid(&gs, &mut g, t)?;
7825 g
7826 }
7827 None => e.htod(&vec![1.0f32; t])?,
7828 };
7829 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7830 }
7831 return Ok(moe_out);
7832 }
7833
7834 let dec = std::env::var("MEMRA_MOE_DEC")
7837 .map(|v| v != "0")
7838 .unwrap_or(true);
7839 let matvec = |proj,
7840 exi: &_,
7841 exo: &_,
7842 exp_d: &_,
7843 pt: &_,
7844 aq: &_,
7845 ad: &_,
7846 inf,
7847 outf,
7848 qtype,
7849 rb|
7850 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7851 let dec = dec && q8_expert_dec_supported(qtype);
7853 if dec {
7854 e.moe_pairs_matvec_q8_dec(
7855 &dev.ptr_row,
7856 proj,
7857 exi,
7858 exo,
7859 exp_d,
7860 pt,
7861 aq,
7862 ad,
7863 inf,
7864 outf,
7865 n_expert,
7866 n_active,
7867 n_pairs,
7868 qtype,
7869 rb,
7870 )
7871 } else {
7872 e.moe_pairs_matvec_q8_em(
7873 &dev.ptr_row,
7874 proj,
7875 exi,
7876 exo,
7877 exp_d,
7878 pt,
7879 aq,
7880 ad,
7881 inf,
7882 outf,
7883 n_expert,
7884 n_active,
7885 n_pairs,
7886 qtype,
7887 rb,
7888 )
7889 }
7890 };
7891 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7892 let gate = matvec(
7893 0,
7894 &exi,
7895 &exo,
7896 &exp_d,
7897 &pt,
7898 &zq,
7899 &zd,
7900 n_embd,
7901 n_ff_exp,
7902 m.gate_exps.qtype,
7903 rbg_d,
7904 )?;
7905 let up = matvec(
7906 1,
7907 &exi,
7908 &exo,
7909 &exp_d,
7910 &pt,
7911 &zq,
7912 &zd,
7913 n_embd,
7914 n_ff_exp,
7915 m.up_exps.qtype,
7916 rbu_d,
7917 )?;
7918 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7919 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7920 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7922 let pself = e.htod_i32(&pair_self)?;
7923 let y_down = matvec(
7924 2,
7925 &exi,
7926 &exo,
7927 &exp_d,
7928 &pself,
7929 &aq2,
7930 &ad2,
7931 n_ff_exp,
7932 n_embd,
7933 m.down_exps.qtype,
7934 m.down_exps.row_bytes,
7935 )?;
7936 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7938
7939 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7943 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7944 {
7945 let n_ff_sh = gate_shexp.out_features();
7946 let step_exact = true;
7950 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
7951 let (sg_gate, sg_up) = if step_exact && t == 1 {
7952 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, None)?
7953 } else if verify_t {
7954 let mut fused = None;
7955 if crate::spec::spec_fused_t()
7956 && (2..=4).contains(&t)
7957 && e.uses_q8_1_fast(gate_shexp)
7958 && e.uses_q8_1_fast(up_shexp)
7959 {
7960 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7961 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
7962 }
7963 match fused {
7964 Some(pair) => pair,
7965 None => (
7966 e.matmul_decode_exact(gate_shexp, z, t)?,
7967 e.matmul_decode_exact(up_shexp, z, t)?,
7968 ),
7969 }
7970 } else {
7971 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
7972 };
7973 let mut sa = e.uninit(t * n_ff_sh)?;
7974 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7975 let sh = if verify_t {
7976 e.matmul_decode_exact(down_shexp, &sa, t)?
7977 } else {
7978 e.matmul(down_shexp, &sa, t)?
7979 };
7980 let g = match &m.gate_inp_shexp {
7985 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7986 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7987 }
7988 Some(gate_inp_shexp) => {
7989 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7990 let mut g = e.uninit(t)?;
7991 e.sigmoid(&gs, &mut g, t)?;
7992 g
7993 }
7994 None => e.htod(&vec![1.0f32; t])?,
7995 };
7996 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7997 }
7998 Ok(moe_out)
7999 }
8000
8001 #[allow(clippy::too_many_arguments)]
8003 #[allow(clippy::too_many_arguments)]
8004 fn moe_ffn_dev(
8005 e: &Engine,
8006 m: &MoeWeights,
8007 z: &CudaSlice<f32>,
8008 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
8009 logits: &CudaSlice<f32>,
8010 t: usize,
8011 cfg: &ModelConfig,
8012 il: u16,
8013 max_block: usize,
8014 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8015 let moe = cfg.moe.as_ref().unwrap();
8016 let n_embd = cfg.n_embd as usize;
8017 let n_expert = moe.expert_count as usize;
8018 let n_used = moe.expert_used_count as usize;
8019 let n_ff_exp = moe.expert_ff_length as usize;
8020 debug_assert!(
8024 cfg.sigmoid_router().is_none(),
8025 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
8026 );
8027 debug_assert!(
8028 !cfg.swiglu_clamped_at(il as u32),
8029 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
8030 );
8031
8032 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
8034 if m.has_macros {
8037 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
8038 }
8039
8040 let mut moe_out = e.uninit(t * n_embd)?;
8042
8043 if let Some(dev) = m.dev_exps.as_ref() {
8046 let (rbg_d, rbu_d) = if dev.gu_il {
8049 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8050 (sxx, sxx)
8051 } else {
8052 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8053 };
8054 let q8 = moe_q8_enabled()
8055 && q8_expert_supported(m.gate_exps.qtype)
8056 && q8_expert_supported(m.up_exps.qtype)
8057 && q8_expert_supported(m.down_exps.qtype);
8058 let rows_arm = q8
8067 && t > 1
8068 && crate::spec::spec_m2()
8069 && n_ff_exp == 512
8070 && n_used <= 8
8071 && std::env::var("MEMRA_MOE_DEVQ8_GU")
8072 .map(|v| v.is_empty() || v == "v")
8073 .unwrap_or(true)
8074 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
8075 .map(|v| v.is_empty() || v == "w8h2v")
8076 .unwrap_or(true);
8077 let csr_mode = std::env::var("MEMRA_MOE_CSR")
8086 .ok()
8087 .and_then(|v| v.parse::<i32>().ok())
8088 .unwrap_or(1);
8089 let csr_nvfp4_probe = std::env::var("MEMRA_MOE_CSR_NVFP4").as_deref() == Ok("1");
8106 let csr_qt = |qt: i32| {
8107 qt == crate::QT_IQ4_XS
8108 || qt == crate::QT_IQ3_S
8109 || (csr_nvfp4_probe && qt == crate::QT_NVFP4)
8110 };
8111 let csr_t_max = if csr_nvfp4_probe { MOE_DEV_MAX_T } else { 10 };
8112 let csr_uniform = m.gate_exps.qtype == m.up_exps.qtype;
8113 let csr_arm = rows_arm
8114 && csr_mode > 0
8115 && t <= csr_t_max
8116 && csr_uniform
8117 && csr_qt(m.gate_exps.qtype)
8118 && csr_qt(m.up_exps.qtype)
8119 && csr_qt(m.down_exps.qtype);
8120 if csr_arm {
8121 if csr_mode == 2 {
8122 static ENGAGED: std::sync::Once = std::sync::Once::new();
8123 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
8124 }
8125 let n_pairs = t * n_used;
8126 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8127 let act = e.moe_gate_up_silu8_dev_q8_csr(
8128 &dev.ptr_row,
8129 &sel_d,
8130 &zq,
8131 &zd,
8132 n_pairs,
8133 n_embd,
8134 n_ff_exp,
8135 n_used,
8136 n_expert,
8137 m.gate_exps.qtype,
8138 m.up_exps.qtype,
8139 rbg_d,
8140 rbu_d,
8141 )?;
8142 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8143 e.moe_down8_fma_dev_q8_rows(
8147 &dev.ptr_row,
8148 &sel_d,
8149 &w_d,
8150 &aq2,
8151 &ad2,
8152 &mut moe_out,
8153 t,
8154 n_ff_exp,
8155 n_embd,
8156 n_used,
8157 n_expert,
8158 m.down_exps.qtype,
8159 m.down_exps.row_bytes,
8160 )?;
8161 if csr_mode == 2 {
8162 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
8164 &dev.ptr_row,
8165 &sel_d,
8166 &zq,
8167 &zd,
8168 t,
8169 n_embd,
8170 n_ff_exp,
8171 n_used,
8172 n_expert,
8173 m.gate_exps.qtype,
8174 m.up_exps.qtype,
8175 rbg_d,
8176 rbu_d,
8177 &m.dev_macros,
8178 )?;
8179 let mut out_r = e.uninit(t * n_embd)?;
8180 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
8181 e.moe_down8_fma_dev_q8_rows(
8182 &dev.ptr_row,
8183 &sel_d,
8184 &w_d,
8185 &aq2r,
8186 &ad2r,
8187 &mut out_r,
8188 t,
8189 n_ff_exp,
8190 n_embd,
8191 n_used,
8192 n_expert,
8193 m.down_exps.qtype,
8194 m.down_exps.row_bytes,
8195 )?;
8196 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
8197 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
8198 let ba = a1
8199 .iter()
8200 .zip(&a2)
8201 .filter(|(x, y)| x.to_bits() != y.to_bits())
8202 .count();
8203 let bo = o1
8204 .iter()
8205 .zip(&o2)
8206 .filter(|(x, y)| x.to_bits() != y.to_bits())
8207 .count();
8208 if ba + bo > 0 {
8209 eprintln!(
8210 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
8211 a1.len(),
8212 o1.len()
8213 );
8214 let sel_h = e.dtoh_i32(&sel_d)?;
8216 let mut shown = 0;
8217 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
8218 if x.to_bits() != y.to_bits() && shown < 4 {
8219 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
8220 let ex = sel_h[p];
8221 let npx = sel_h.iter().filter(|&&v| v == ex).count();
8222 eprintln!(
8223 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
8224 );
8225 shown += 1;
8226 }
8227 }
8228 std::process::exit(3);
8229 }
8230 }
8231 } else if rows_arm {
8232 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
8235 use std::sync::atomic::{AtomicU64, Ordering};
8236 static PAIRS: AtomicU64 = AtomicU64::new(0);
8237 static UNIQ: AtomicU64 = AtomicU64::new(0);
8238 static CALLS: AtomicU64 = AtomicU64::new(0);
8239 let sel_h = e.dtoh_i32(&sel_d)?;
8240 let mut u: Vec<i32> = sel_h.clone();
8241 u.sort_unstable();
8242 u.dedup();
8243 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
8244 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
8245 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
8246 if c % 480 == 0 {
8247 let p = PAIRS.load(Ordering::Relaxed);
8248 let q = UNIQ.load(Ordering::Relaxed);
8249 eprintln!(
8250 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
8251 q as f64 / p as f64
8252 );
8253 }
8254 }
8255 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8256 let act = e.moe_gate_up_silu8_dev_q8_rows(
8257 &dev.ptr_row,
8258 &sel_d,
8259 &zq,
8260 &zd,
8261 t,
8262 n_embd,
8263 n_ff_exp,
8264 n_used,
8265 n_expert,
8266 m.gate_exps.qtype,
8267 m.up_exps.qtype,
8268 rbg_d,
8269 rbu_d,
8270 &m.dev_macros,
8271 )?;
8272 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8273 e.moe_down8_fma_dev_q8_rows(
8274 &dev.ptr_row,
8275 &sel_d,
8276 &w_d,
8277 &aq2,
8278 &ad2,
8279 &mut moe_out,
8280 t,
8281 n_ff_exp,
8282 n_embd,
8283 n_used,
8284 n_expert,
8285 m.down_exps.qtype,
8286 m.down_exps.row_bytes,
8287 )?;
8288 } else {
8289 for tok in 0..t {
8290 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8291 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8292 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8293 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8294 if q8 {
8295 let (zq, zd) = match (t, zq8) {
8296 (1, Some((q, d))) => (q.clone(), d.clone()),
8297 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
8298 };
8299 let act = e.moe_gate_up_silu8_dev_q8(
8300 &dev.ptr_row,
8301 &selt,
8302 &zq,
8303 &zd,
8304 n_embd,
8305 n_ff_exp,
8306 n_used,
8307 n_expert,
8308 m.gate_exps.qtype,
8309 m.up_exps.qtype,
8310 rbg_d,
8311 rbu_d,
8312 &m.dev_macros,
8313 )?;
8314 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8315 e.moe_down8_fma_dev_q8(
8316 &dev.ptr_row,
8317 &selt,
8318 &wt,
8319 &aq2,
8320 &ad2,
8321 &mut dst,
8322 n_ff_exp,
8323 n_embd,
8324 n_used,
8325 n_expert,
8326 m.down_exps.qtype,
8327 m.down_exps.row_bytes,
8328 )?;
8329 } else {
8330 let act = e.moe_gate_up_silu8_dev(
8331 &dev.ptr_row,
8332 &selt,
8333 &zt,
8334 n_embd,
8335 n_ff_exp,
8336 n_used,
8337 n_expert,
8338 m.gate_exps.qtype,
8339 m.up_exps.qtype,
8340 rbg_d,
8341 rbu_d,
8342 &m.dev_macros,
8343 )?;
8344 e.moe_down8_fma_dev(
8345 &dev.ptr_row,
8346 &selt,
8347 &wt,
8348 &act,
8349 &mut dst,
8350 n_ff_exp,
8351 n_embd,
8352 n_used,
8353 n_expert,
8354 m.down_exps.qtype,
8355 m.down_exps.row_bytes,
8356 )?;
8357 }
8358 }
8359 }
8360 } else {
8361 let q8 = moe_q8_enabled()
8368 && q8_expert_supported(m.gate_exps.qtype)
8369 && q8_expert_supported(m.up_exps.qtype)
8370 && q8_expert_supported(m.down_exps.qtype);
8371 e.with_moe_cache(max_block, |c, eng| {
8372 let row = c
8373 .layer_dev_row(il, n_expert, eng)?
8374 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
8375 for tok in 0..t {
8376 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8377 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8378 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8379 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8380 if q8 {
8381 let (zq, zd) = match (t, zq8) {
8382 (1, Some((q, d))) => (q.clone(), d.clone()),
8383 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
8384 };
8385 let act = eng.moe_gate_up_silu8_dev_q8(
8386 row,
8387 &selt,
8388 &zq,
8389 &zd,
8390 n_embd,
8391 n_ff_exp,
8392 n_used,
8393 n_expert,
8394 m.gate_exps.qtype,
8395 m.up_exps.qtype,
8396 m.gate_exps.row_bytes,
8397 m.up_exps.row_bytes,
8398 &m.dev_macros,
8399 )?;
8400 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
8401 eng.moe_down8_fma_dev_q8(
8402 row,
8403 &selt,
8404 &wt,
8405 &aq2,
8406 &ad2,
8407 &mut dst,
8408 n_ff_exp,
8409 n_embd,
8410 n_used,
8411 n_expert,
8412 m.down_exps.qtype,
8413 m.down_exps.row_bytes,
8414 )?;
8415 } else {
8416 let act = eng.moe_gate_up_silu8_dev(
8417 row,
8418 &selt,
8419 &zt,
8420 n_embd,
8421 n_ff_exp,
8422 n_used,
8423 n_expert,
8424 m.gate_exps.qtype,
8425 m.up_exps.qtype,
8426 m.gate_exps.row_bytes,
8427 m.up_exps.row_bytes,
8428 &m.dev_macros,
8429 )?;
8430 eng.moe_down8_fma_dev(
8431 row,
8432 &selt,
8433 &wt,
8434 &act,
8435 &mut dst,
8436 n_ff_exp,
8437 n_embd,
8438 n_used,
8439 n_expert,
8440 m.down_exps.qtype,
8441 m.down_exps.row_bytes,
8442 )?;
8443 }
8444 }
8445 c.hits += (t * 3 * n_used) as u64;
8447 Ok(())
8448 })?;
8449 }
8450
8451 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8456 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8457 {
8458 let n_ff_sh = gate_shexp.out_features();
8459 let verify_t = t > 1 && t < PRIME_MIN_T;
8462 let (sg_gate, sg_up) = if t == 1 {
8463 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
8464 } else if verify_t {
8465 let mut fused = None;
8469 if crate::spec::spec_fused_t()
8470 && (2..=4).contains(&t)
8471 && e.uses_q8_1_fast(gate_shexp)
8472 && e.uses_q8_1_fast(up_shexp)
8473 {
8474 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8475 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8476 }
8477 match fused {
8478 Some(pair) => pair,
8479 None => (
8480 e.matmul_decode_exact(gate_shexp, z, t)?,
8481 e.matmul_decode_exact(up_shexp, z, t)?,
8482 ),
8483 }
8484 } else {
8485 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8486 };
8487 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8489 let sh = if verify_t {
8490 e.matmul_decode_exact(down_shexp, &sa, t)?
8491 } else {
8492 e.matmul(down_shexp, &sa, t)?
8493 };
8494 let g = match &m.gate_inp_shexp {
8498 Some(gate_inp_shexp) => {
8499 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
8502 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8503 } else {
8504 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8505 let mut g = e.uninit(t)?;
8506 e.sigmoid(&gs, &mut g, t)?;
8507 g
8508 }
8509 }
8510 None => e.htod(&vec![1.0f32; t])?,
8511 };
8512 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8513 }
8514
8515 Ok(moe_out)
8516 }
8517
8518 #[allow(clippy::too_many_arguments)]
8528 #[allow(clippy::too_many_arguments)]
8531 fn moe_gdec_token_q8(
8532 e: &Engine,
8533 m: &MoeWeights,
8534 il: u16,
8535 max_block: usize,
8536 zq: &CudaSlice<i8>,
8537 zd: &CudaSlice<f32>,
8538 sel: &[u32],
8539 w: &[f32],
8540 moe_out: &mut CudaSlice<f32>,
8541 tok: usize,
8542 n_embd: usize,
8543 n_ff_exp: usize,
8544 n_used: usize,
8545 ) -> Result<bool, Box<dyn std::error::Error>> {
8546 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8547 use cudarc::driver::DevicePtr;
8548 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8549 let mut g = [0u64; 8];
8550 let mut u = [0u64; 8];
8551 let mut d = [0u64; 8];
8552 for (j, &ex) in sel.iter().enumerate() {
8553 let ex = ex as u16;
8554 let (Some(sg), Some(su), Some(sd)) = (
8555 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8556 c.resident(BlockId::new(il, PROJ_UP, ex)),
8557 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8558 ) else {
8559 return Ok(None);
8560 };
8561 let __s = eng.stream();
8562 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8563 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8564 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8565 g[j] = pg as u64;
8566 u[j] = pu as u64;
8567 d[j] = pd as u64;
8568 }
8569 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8570 for &ex in sel {
8571 let ex = ex as u16;
8572 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8573 c.note_profile_hit(BlockId::new(il, proj, ex));
8574 }
8575 }
8576 }
8577 c.hits += (3 * n_used) as u64;
8578 Ok(Some((g, u, d)))
8579 })?;
8580 let Some((g, u, d)) = ptrs else {
8581 return Ok(false);
8582 };
8583 let mut wv = [0f32; 8];
8584 wv[..n_used].copy_from_slice(w);
8585 let act = e.moe_gate_up_silu8_q8(
8586 crate::WPtr8(g),
8587 crate::WPtr8(u),
8588 zq,
8589 zd,
8590 n_embd,
8591 n_ff_exp,
8592 n_used,
8593 m.gate_exps.qtype,
8594 m.up_exps.qtype,
8595 m.gate_exps.row_bytes,
8596 m.up_exps.row_bytes,
8597 )?;
8598 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8600 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8601 e.moe_down8_fma_q8(
8602 crate::WPtr8(d),
8603 crate::F32x8(wv),
8604 &aq2,
8605 &ad2,
8606 &mut dst,
8607 n_ff_exp,
8608 n_embd,
8609 n_used,
8610 m.down_exps.qtype,
8611 m.down_exps.row_bytes,
8612 )?;
8613 Ok(true)
8614 }
8615
8616 fn moe_gdec_token(
8617 e: &Engine,
8618 m: &MoeWeights,
8619 il: u16,
8620 max_block: usize,
8621 zt: &cudarc::driver::CudaView<f32>,
8622 sel: &[u32],
8623 w: &[f32],
8624 moe_out: &mut CudaSlice<f32>,
8625 tok: usize,
8626 n_embd: usize,
8627 n_ff_exp: usize,
8628 n_used: usize,
8629 ) -> Result<bool, Box<dyn std::error::Error>> {
8630 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8631 use cudarc::driver::DevicePtr;
8632 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8634 let mut g = [0u64; 8];
8635 let mut u = [0u64; 8];
8636 let mut d = [0u64; 8];
8637 for (j, &ex) in sel.iter().enumerate() {
8638 let ex = ex as u16;
8639 let (Some(sg), Some(su), Some(sd)) = (
8640 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8641 c.resident(BlockId::new(il, PROJ_UP, ex)),
8642 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8643 ) else {
8644 return Ok(None);
8645 };
8646 let __s = eng.stream();
8647 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8648 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8649 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8650 g[j] = pg as u64;
8651 u[j] = pu as u64;
8652 d[j] = pd as u64;
8653 }
8654 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8655 for &ex in sel {
8656 let ex = ex as u16;
8657 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8658 c.note_profile_hit(BlockId::new(il, proj, ex));
8659 }
8660 }
8661 }
8662 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
8664 })?;
8665 let Some((g, u, d)) = ptrs else {
8666 return Ok(false);
8667 };
8668 let mut wv = [0f32; 8];
8669 wv[..n_used].copy_from_slice(w);
8670 let act = e.moe_gate_up_silu8(
8672 crate::WPtr8(g),
8673 crate::WPtr8(u),
8674 zt,
8675 n_embd,
8676 n_ff_exp,
8677 n_used,
8678 m.gate_exps.qtype,
8679 m.up_exps.qtype,
8680 m.gate_exps.row_bytes,
8681 m.up_exps.row_bytes,
8682 )?;
8683 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8684 e.moe_down8_fma_into(
8685 crate::WPtr8(d),
8686 crate::F32x8(wv),
8687 &act,
8688 &mut dst,
8689 n_ff_exp,
8690 n_embd,
8691 n_used,
8692 m.down_exps.qtype,
8693 m.down_exps.row_bytes,
8694 )?;
8695 Ok(true)
8696 }
8697
8698 fn moe_cached_gemm_q8(
8703 e: &Engine,
8704 il: u16,
8705 proj: u8,
8706 ex: usize,
8707 m: &MoeWeights,
8708 max_block: usize,
8709 aq: &CudaSlice<i8>,
8710 ad: &CudaSlice<f32>,
8711 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8712 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8713 let exps = match proj {
8714 PROJ_GATE => &m.gate_exps,
8715 PROJ_UP => &m.up_exps,
8716 _ => &m.down_exps,
8717 };
8718 let layout = exps.expert_layout(ex);
8719 let id = BlockId::new(il, proj, ex as u16);
8720 let source = exps.expert_source(ex);
8721 e.with_moe_cache(max_block, |c, eng| {
8722 let slot = c.dispatch_source(id, source, eng)?;
8723 let DispatchSlot::Resident(sl) = slot;
8724 let buf = c.slot(sl);
8725 eng.qmatvec_expert_q8(
8726 buf,
8727 0..layout.len,
8728 aq,
8729 ad,
8730 1,
8731 exps.in_f,
8732 exps.out_f,
8733 layout.qtype,
8734 layout.row_bytes,
8735 )
8736 })
8737 }
8738
8739 fn moe_cached_gemm(
8740 e: &Engine,
8741 il: u16,
8742 proj: u8,
8743 ex: usize,
8744 m: &MoeWeights,
8745 max_block: usize,
8746 x: &cudarc::driver::CudaView<f32>,
8747 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8748 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8749 let exps = match proj {
8750 PROJ_GATE => &m.gate_exps,
8751 PROJ_UP => &m.up_exps,
8752 _ => &m.down_exps,
8753 };
8754 let layout = exps.expert_layout(ex);
8755 let id = BlockId::new(il, proj, ex as u16);
8756 let source = exps.expert_source(ex);
8757 e.with_moe_cache(max_block, |c, eng| {
8759 let slot = c.dispatch_source(id, source, eng)?;
8760 let DispatchSlot::Resident(sl) = slot;
8763 let buf = c.slot(sl);
8764 eng.qmatvec_view(
8765 buf,
8766 0..layout.len,
8767 x,
8768 1,
8769 exps.in_f,
8770 exps.out_f,
8771 layout.qtype,
8772 layout.row_bytes,
8773 )
8774 })
8775 }
8776
8777 fn moe_profile_admit_expert(
8781 e: &Engine,
8782 il: u16,
8783 ex: usize,
8784 m: &MoeWeights,
8785 max_block: usize,
8786 ) -> Result<(), Box<dyn std::error::Error>> {
8787 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8788 e.with_moe_cache(max_block, |cache, eng| {
8789 for (proj, exps) in [
8790 (PROJ_GATE, &m.gate_exps),
8791 (PROJ_UP, &m.up_exps),
8792 (PROJ_DOWN, &m.down_exps),
8793 ] {
8794 let id = BlockId::new(il, proj, ex as u16);
8795 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
8796 }
8797 Ok(())
8798 })
8799 }
8800
8801 #[allow(clippy::too_many_arguments)]
8804 fn moe_frozen_gemm(
8805 e: &Engine,
8806 il: u16,
8807 proj: u8,
8808 ex: usize,
8809 m: &MoeWeights,
8810 max_block: usize,
8811 x: &cudarc::driver::CudaView<f32>,
8812 scratch: &mut Option<CudaSlice<u8>>,
8813 scratch_len: usize,
8814 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8815 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
8816 let exps = match proj {
8817 PROJ_GATE => &m.gate_exps,
8818 PROJ_UP => &m.up_exps,
8819 _ => &m.down_exps,
8820 };
8821 let layout = exps.expert_layout(ex);
8822 let id = BlockId::new(il, proj, ex as u16);
8823 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
8824 let Some(slot) = cache.resident(id) else {
8825 return Ok(None);
8826 };
8827 let buf = cache.slot(slot);
8828 Ok(Some(eng.qmatvec_view(
8829 buf,
8830 0..layout.len,
8831 x,
8832 1,
8833 exps.in_f,
8834 exps.out_f,
8835 layout.qtype,
8836 layout.row_bytes,
8837 )?))
8838 })? {
8839 return Ok(output);
8840 }
8841 if scratch.is_none() {
8842 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
8843 }
8844 let scratch = scratch.as_mut().unwrap();
8845 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
8846 e.qmatvec_view(
8847 scratch,
8848 0..layout.len,
8849 x,
8850 1,
8851 exps.in_f,
8852 exps.out_f,
8853 layout.qtype,
8854 layout.row_bytes,
8855 )
8856 }
8857
8858 fn moe_prefetch_expert(
8859 e: &Engine,
8860 il: u16,
8861 ex: usize,
8862 m: &MoeWeights,
8863 max_block: usize,
8864 keep: &[crate::moe_cache::BlockId],
8865 ) -> Result<(), Box<dyn std::error::Error>> {
8866 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8867 e.with_moe_cache(max_block, |c, eng| {
8868 for (proj, exps) in [
8869 (PROJ_GATE, &m.gate_exps),
8870 (PROJ_UP, &m.up_exps),
8871 (PROJ_DOWN, &m.down_exps),
8872 ] {
8873 let id = BlockId::new(il, proj, ex as u16);
8874 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
8875 }
8876 Ok(())
8877 })
8878 }
8879
8880 fn moe_prefetch_disk_expert(
8883 e: &Engine,
8884 il: u16,
8885 ex: usize,
8886 m: &MoeWeights,
8887 max_block: usize,
8888 keep: &[crate::moe_cache::BlockId],
8889 ) -> Result<(), Box<dyn std::error::Error>> {
8890 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8891 e.with_moe_cache(max_block, |c, eng| {
8892 for (proj, exps) in [
8893 (PROJ_GATE, &m.gate_exps),
8894 (PROJ_UP, &m.up_exps),
8895 (PROJ_DOWN, &m.down_exps),
8896 ] {
8897 let source = exps.expert_source(ex);
8898 if let crate::model::ExpertSource::Disk { .. } = &source {
8899 let id = BlockId::new(il, proj, ex as u16);
8900 let _ = c.prefetch_source(id, source, keep, eng)?;
8901 }
8902 }
8903 Ok(())
8904 })
8905 }
8906
8907 #[inline]
8908 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
8909 let _ = m.gate_exps.prefetch_expert_pages(ex);
8910 let _ = m.up_exps.prefetch_expert_pages(ex);
8911 let _ = m.down_exps.prefetch_expert_pages(ex);
8912 }
8913}
8914
8915impl HybridModel {
8932 #[allow(clippy::too_many_arguments)]
8936 fn moe_ffn_grouped_resident_q8(
8937 e: &Engine,
8938 m: &MoeWeights,
8939 z: &CudaSlice<f32>,
8940 t: usize,
8941 cfg: &ModelConfig,
8942 il: u16,
8943 sel_all: &[u32],
8944 w_all: &[f32],
8945 table: &CudaSlice<u64>,
8946 gu_il: bool,
8947 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8948 let moe = cfg.moe.as_ref().unwrap();
8949 let n_embd = cfg.n_embd as usize;
8950 let n_expert = moe.expert_count as usize;
8951 let n_used = moe.expert_used_count as usize;
8952 let n_ff_exp = moe.expert_ff_length as usize;
8953 let n_pairs = t * n_used;
8954 debug_assert_eq!(sel_all.len(), n_pairs);
8955 debug_assert_eq!(w_all.len(), n_pairs);
8956 debug_assert!(
8957 m.gate_exps.macros.is_none()
8958 && m.up_exps.macros.is_none()
8959 && m.down_exps.macros.is_none(),
8960 "resident grouped q8 does not fold per-expert macro scales",
8961 );
8962
8963 if !cfg.swiglu_clamped_at(il as u32) {
8969 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8970 let sel_d = e.htod_i32(&sel)?;
8971 let w_d = e.htod(w_all)?;
8972 let (gate_row_bytes, up_row_bytes) = if gu_il {
8973 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8974 (combined, combined)
8975 } else {
8976 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8977 };
8978 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8979 let act = e.moe_gate_up_silu8_dev_q8_rows(
8980 table,
8981 &sel_d,
8982 &zq,
8983 &zd,
8984 t,
8985 n_embd,
8986 n_ff_exp,
8987 n_used,
8988 n_expert,
8989 m.gate_exps.qtype,
8990 m.up_exps.qtype,
8991 gate_row_bytes,
8992 up_row_bytes,
8993 &m.dev_macros,
8994 )?;
8995 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8996 let mut moe_out = e.uninit(t * n_embd)?;
8997 e.moe_down8_fma_dev_q8_rows_g(
8998 table,
8999 &sel_d,
9000 &w_d,
9001 &aq2,
9002 &ad2,
9003 &mut moe_out,
9004 t,
9005 n_ff_exp,
9006 n_embd,
9007 n_used,
9008 n_expert,
9009 m.down_exps.qtype,
9010 m.down_exps.row_bytes,
9011 )?;
9012
9013 if std::env::var("MEMRA_MOE_STATS").is_ok() {
9014 let mut counts = vec![0usize; n_expert];
9015 for &expert in sel_all {
9016 counts[expert as usize] += 1;
9017 }
9018 let mut sizes: Vec<usize> =
9019 counts.into_iter().filter(|&count| count != 0).collect();
9020 sizes.sort_unstable();
9021 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
9022 println!(
9023 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
9024 m_e: min={} median={} mean={mean:.1} max={}",
9025 sizes.len(),
9026 n_expert,
9027 sizes.first().copied().unwrap_or(0),
9028 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
9029 sizes.last().copied().unwrap_or(0),
9030 );
9031 }
9032 return Ok(moe_out);
9033 }
9034
9035 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
9039 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
9040 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
9041 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
9042
9043 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
9044 for (pair, &expert) in pair_ex.iter().enumerate() {
9045 by_expert[expert as usize].push(pair as i32);
9046 }
9047
9048 let pair_tok_d = e.htod_i32(&pair_tok)?;
9049 let pair_ex_d = e.htod_i32(&pair_ex)?;
9050 let pair_w_d = e.htod(w_all)?;
9051 let tok_off_d = e.htod_i32(&tok_off)?;
9052 let tok_ids_d = e.htod_i32(&tok_ids)?;
9053
9054 let matvec = |proj: i32,
9055 pair_rows: &CudaSlice<i32>,
9056 aq: &CudaSlice<i8>,
9057 ad: &CudaSlice<f32>,
9058 in_f: usize,
9059 out_f: usize,
9060 qtype: i32,
9061 row_bytes: usize|
9062 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9063 e.moe_pairs_matvec_q8(
9064 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
9065 row_bytes,
9066 )
9067 };
9068
9069 let (gate_row_bytes, up_row_bytes) = if gu_il {
9070 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
9071 (combined, combined)
9072 } else {
9073 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
9074 };
9075 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
9076 let gate = matvec(
9077 0,
9078 &pair_tok_d,
9079 &zq,
9080 &zd,
9081 n_embd,
9082 n_ff_exp,
9083 m.gate_exps.qtype,
9084 gate_row_bytes,
9085 )?;
9086 let up = matvec(
9087 1,
9088 &pair_tok_d,
9089 &zq,
9090 &zd,
9091 n_embd,
9092 n_ff_exp,
9093 m.up_exps.qtype,
9094 up_row_bytes,
9095 )?;
9096 let mut act = e.uninit(n_pairs * n_ff_exp)?;
9097 Self::ffn_act_lim(
9098 e,
9099 cfg,
9100 &gate,
9101 &up,
9102 1.0,
9103 1.0,
9104 cfg.clamp_exp_at(il as u32),
9105 &mut act,
9106 n_pairs * n_ff_exp,
9107 )?;
9108 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
9109 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
9110 let pair_self_d = e.htod_i32(&pair_self)?;
9111 let down = matvec(
9112 2,
9113 &pair_self_d,
9114 &aq2,
9115 &ad2,
9116 n_ff_exp,
9117 n_embd,
9118 m.down_exps.qtype,
9119 m.down_exps.row_bytes,
9120 )?;
9121 let mut moe_out = e.uninit(t * n_embd)?;
9122 e.moe_pairs_scatter(
9123 &down,
9124 &pair_w_d,
9125 &tok_off_d,
9126 &tok_ids_d,
9127 &mut moe_out,
9128 t,
9129 n_embd,
9130 )?;
9131
9132 if std::env::var("MEMRA_MOE_STATS").is_ok() {
9133 let mut sizes: Vec<usize> = by_expert
9134 .iter()
9135 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
9136 .collect();
9137 sizes.sort_unstable();
9138 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
9139 println!(
9140 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
9141 m_e: min={} median={} mean={mean:.1} max={}",
9142 sizes.len(),
9143 n_expert,
9144 sizes.first().copied().unwrap_or(0),
9145 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
9146 sizes.last().copied().unwrap_or(0),
9147 );
9148 }
9149 Ok(moe_out)
9150 }
9151
9152 #[allow(clippy::too_many_arguments)]
9158 fn shexp_split_matvec(
9159 e: &Engine,
9160 rank1: &Engine,
9161 wg: &CudaSlice<u8>,
9162 wu: &CudaSlice<u8>,
9163 wd: &CudaSlice<u8>,
9164 z: &CudaSlice<f32>,
9165 lim: Option<f32>,
9166 cfg: &ModelConfig,
9167 il: u16,
9168 n_embd: usize,
9169 n_ff_sh: usize,
9170 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
9171 use cudarc::driver::DevicePtr;
9172 if n_ff_sh % 2 != 0 || n_embd % 2 != 0 {
9173 return Ok(None);
9174 }
9175 let hf = n_ff_sh / 2;
9176 let nd = n_embd / 2;
9177 struct Rep {
9178 wg1: CudaSlice<u8>,
9179 wu1: CudaSlice<u8>,
9180 wd1: CudaSlice<u8>,
9181 }
9182 struct SplitWs {
9183 pin_dev: usize,
9184 gate0: CudaSlice<f32>,
9186 up0: CudaSlice<f32>,
9187 act: CudaSlice<f32>,
9188 sh_buf: CudaSlice<f32>,
9189 ev_z: cudarc::driver::CudaEvent,
9190 ev_act0: cudarc::driver::CudaEvent,
9191 z1: CudaSlice<f32>,
9193 g1: CudaSlice<f32>,
9194 u1: CudaSlice<f32>,
9195 a1h: CudaSlice<f32>,
9196 act1: CudaSlice<f32>,
9197 y1: CudaSlice<f32>,
9198 ev_act1: cudarc::driver::CudaEvent,
9199 ev_y1: cudarc::driver::CudaEvent,
9200 raw_act_e: u64,
9201 raw_sh_e: u64,
9202 raw_z1: u64,
9203 raw_a1h: u64,
9204 raw_act1: u64,
9205 raw_y1: u64,
9206 }
9207 static WS: std::sync::Mutex<Option<SplitWs>> = std::sync::Mutex::new(None);
9208 static REPS: std::sync::Mutex<Option<std::collections::HashMap<u64, Rep>>> =
9209 std::sync::Mutex::new(None);
9210 let mut guard = WS.lock().map_err(|_| "shexp split lock is poisoned")?;
9211 let mut reps_guard = REPS.lock().map_err(|_| "shexp reps lock is poisoned")?;
9212 let reps = reps_guard.get_or_insert_with(std::collections::HashMap::new);
9213 let pins = e.ctx().ordinal();
9214 if guard.as_ref().is_none_or(|w| w.pin_dev != pins) {
9215 let (gate0, up0, act, sh_buf, ev_z, ev_act0) = {
9216 let _m = e.gpu.enter_main()?;
9217 (
9218 e.htod(&vec![0.0f32; hf])?,
9219 e.htod(&vec![0.0f32; hf])?,
9220 e.htod(&vec![0.0f32; n_ff_sh])?,
9221 e.htod(&vec![0.0f32; n_embd])?,
9222 e.ctx().new_event(None)?,
9223 e.ctx().new_event(None)?,
9224 )
9225 };
9226 let (z1, g1, u1, a1h, act1, y1, ev_act1, ev_y1) = {
9227 let _r = rank1.gpu.enter_main()?;
9228 (
9229 rank1.htod(&vec![0.0f32; n_embd])?,
9230 rank1.htod(&vec![0.0f32; hf])?,
9231 rank1.htod(&vec![0.0f32; hf])?,
9232 rank1.htod(&vec![0.0f32; hf])?,
9233 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9234 rank1.htod(&vec![0.0f32; nd])?,
9235 rank1.ctx().new_event(None)?,
9236 rank1.ctx().new_event(None)?,
9237 )
9238 };
9239 let (raw_act_e, raw_sh_e) = {
9240 let _m = e.gpu.enter_main()?;
9241 let stream = e.stream();
9242 let (a, _g0) = act.device_ptr(&stream);
9243 let (b, _g1) = sh_buf.device_ptr(&stream);
9244 (a as u64, b as u64)
9245 };
9246 let (raw_z1, raw_a1h, raw_act1, raw_y1) = {
9247 let _r = rank1.gpu.enter_main()?;
9248 let rs = rank1.stream();
9249 let (a, _g0) = z1.device_ptr(&rs);
9250 let (b, _g1) = a1h.device_ptr(&rs);
9251 let (c, _g2) = act1.device_ptr(&rs);
9252 let (d, _g3) = y1.device_ptr(&rs);
9253 (a as u64, b as u64, c as u64, d as u64)
9254 };
9255 *guard = Some(SplitWs {
9256 pin_dev: pins,
9257 gate0,
9258 up0,
9259 act,
9260 sh_buf,
9261 ev_z,
9262 ev_act0,
9263 z1,
9264 g1,
9265 u1,
9266 a1h,
9267 act1,
9268 y1,
9269 ev_act1,
9270 ev_y1,
9271 raw_act_e,
9272 raw_sh_e,
9273 raw_z1,
9274 raw_a1h,
9275 raw_act1,
9276 raw_y1,
9277 });
9278 }
9279 let ws = guard.as_mut().expect("armed above");
9280 let wg_pin = {
9281 let _m = e.gpu.enter_main()?;
9282 let stream = e.stream();
9283 let (p, _g) = wg.device_ptr(&stream);
9284 p as u64
9285 };
9286 if !reps.contains_key(&wg_pin) {
9287 let mut up = |src: &CudaSlice<u8>,
9289 off_bytes: usize,
9290 len: usize|
9291 -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9292 use cudarc::driver::sys;
9293 let sptr = {
9294 let _m = e.gpu.enter_main()?;
9295 let stream = e.stream();
9296 let (p, _g) = src.device_ptr(&stream);
9297 p as u64 + off_bytes as u64
9298 };
9299 let dst = {
9300 let _r = rank1.gpu.enter_main()?;
9301 rank1.alloc_u8_uninit(len)?
9302 };
9303 let dptr = {
9304 let _r = rank1.gpu.enter_main()?;
9305 let rs = rank1.stream();
9306 let (p, _g) = dst.device_ptr(&rs);
9307 p as u64
9308 };
9309 let _r = rank1.gpu.enter_main()?;
9310 let r = unsafe {
9311 sys::cuMemcpyAsync(
9312 dptr as sys::CUdeviceptr,
9313 sptr as sys::CUdeviceptr,
9314 len,
9315 rank1.stream().cu_stream() as sys::CUstream,
9316 )
9317 };
9318 if r != sys::CUresult::CUDA_SUCCESS {
9319 return Err(format!("shexp split replica upload: {r:?}").into());
9320 }
9321 rank1.stream().synchronize()?;
9322 Ok(dst)
9323 };
9324 let wg1 = up(wg, hf * n_embd * 2, hf * n_embd * 2)?;
9325 let wu1 = up(wu, hf * n_embd * 2, hf * n_embd * 2)?;
9326 let wd1 = up(wd, nd * n_ff_sh * 2, nd * n_ff_sh * 2)?;
9327 reps.insert(wg_pin, Rep { wg1, wu1, wd1 });
9328 }
9329 let _ = il;
9330 let raw_z = {
9332 let _m = e.gpu.enter_main()?;
9333 let stream = e.stream();
9334 let (p, _g) = z.device_ptr(&stream);
9335 ws.ev_z.record(&stream)?;
9336 p as u64
9337 };
9338 {
9340 let rep = reps.get(&wg_pin).expect("uploaded above");
9341 let _r = rank1.gpu.enter_main()?;
9342 rank1.stream().wait(&ws.ev_z)?;
9343 crate::tp::raw_copy_bytes(ws.raw_z1, raw_z, n_embd * 4, rank1)?;
9344 let SplitWs {
9345 z1, g1, u1, a1h, ..
9346 } = &mut *ws;
9347 rank1.matvec_bf16_dual_into(&rep.wg1, &rep.wu1, z1, g1, u1, n_embd, hf)?;
9348 Self::ffn_act_lim(rank1, cfg, g1, u1, 1.0, 1.0, lim, a1h, hf)?;
9349 crate::tp::raw_copy_bytes(ws.raw_act1 + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9351 crate::tp::raw_copy_bytes(ws.raw_act_e + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9352 ws.ev_act1.record(&rank1.stream())?;
9353 }
9354 {
9356 let _m = e.gpu.enter_main()?;
9357 let SplitWs {
9358 gate0, up0, act, ..
9359 } = &mut *ws;
9360 let wg_lo = wg.slice(0..hf * n_embd * 2);
9361 let wu_lo = wu.slice(0..hf * n_embd * 2);
9362 e.matvec_bf16_dual_view_into(&wg_lo, &wu_lo, z, gate0, up0, n_embd, hf)?;
9363 Self::ffn_act_lim(e, cfg, gate0, up0, 1.0, 1.0, lim, act, hf)?;
9364 ws.ev_act0.record(&e.stream())?;
9365 }
9366 {
9368 let rep = reps.get(&wg_pin).expect("uploaded above");
9369 let _r = rank1.gpu.enter_main()?;
9370 rank1.stream().wait(&ws.ev_act0)?;
9371 crate::tp::raw_copy_bytes(ws.raw_act1, ws.raw_act_e, hf * 4, rank1)?;
9372 let SplitWs { act1, y1, .. } = &mut *ws;
9373 rank1.matvec_bf16_into(&rep.wd1, act1, y1, n_ff_sh, nd)?;
9374 crate::tp::raw_copy_bytes(ws.raw_sh_e + (nd * 4) as u64, ws.raw_y1, nd * 4, rank1)?;
9375 ws.ev_y1.record(&rank1.stream())?;
9376 }
9377 {
9379 let _m = e.gpu.enter_main()?;
9380 e.stream().wait(&ws.ev_act1)?;
9381 let SplitWs { act, sh_buf, .. } = &mut *ws;
9382 let wd_lo = wd.slice(0..nd * n_ff_sh * 2);
9383 e.matvec_bf16_view_into(&wd_lo, act, sh_buf, n_ff_sh, nd)?;
9384 e.stream().wait(&ws.ev_y1)?;
9385 let mut sh = e.uninit(n_embd)?;
9386 {
9387 let mut dst = sh.slice_mut(0..n_embd);
9388 e.stream()
9389 .memcpy_dtod(&ws.sh_buf.slice(0..n_embd), &mut dst)?;
9390 }
9391 Ok(Some(sh))
9392 }
9393 }
9394
9395 fn shexp_overlap_issue(
9402 e: &Engine,
9403 m: &MoeWeights,
9404 z: &CudaSlice<f32>,
9405 cfg: &ModelConfig,
9406 il: u16,
9407 n_embd: usize,
9408 ) -> Result<bool, Box<dyn std::error::Error>> {
9409 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9410 return Ok(false);
9411 }
9412 let (
9413 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9414 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9415 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9416 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9417 else {
9418 return Ok(false);
9419 };
9420 let n_ff_sh = m
9421 .gate_shexp
9422 .as_ref()
9423 .expect("matched Some above")
9424 .out_features();
9425 let lim = cfg.clamp_shexp_at(il as u32);
9426 let mut guard = SHEXP_OV_WS
9427 .lock()
9428 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9429 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9430 if guard
9431 .as_ref()
9432 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9433 {
9434 *guard = Some((
9435 pins.0,
9436 pins.1,
9437 pins.2,
9438 e.uninit(n_ff_sh)?,
9439 e.uninit(n_embd)?,
9440 ));
9441 }
9442 let (_, _, _, act, sh) = guard.as_mut().expect("armed above");
9443 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9444 e.matvec_bf16_into(wd, act, sh, n_ff_sh, n_embd)?;
9445 drop(guard);
9446 Ok(true)
9447 }
9448
9449 #[allow(clippy::too_many_arguments)]
9455 fn shexp_dev1_issue(
9456 e: &Engine,
9457 rank1: &Engine,
9458 m: &MoeWeights,
9459 z: &CudaSlice<f32>,
9460 cfg: &ModelConfig,
9461 il: u16,
9462 n_embd: usize,
9463 ) -> Result<bool, Box<dyn std::error::Error>> {
9464 use cudarc::driver::DevicePtr;
9465 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9466 return Ok(false);
9467 }
9468 let (
9469 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9470 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9471 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9472 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9473 else {
9474 return Ok(false);
9475 };
9476 let n_ff_sh = m
9477 .gate_shexp
9478 .as_ref()
9479 .expect("matched Some above")
9480 .out_features();
9481 let lim = cfg.clamp_shexp_at(il as u32);
9482 let mut ws_guard = SHEXP_D1_WS
9484 .lock()
9485 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9486 if ws_guard
9487 .as_ref()
9488 .is_none_or(|(k, ..)| *k != (n_embd, n_ff_sh))
9489 {
9490 let (act1, z1, ev_done) = {
9491 let _r1 = rank1.gpu.enter_main()?;
9492 (
9493 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9494 rank1.htod(&vec![0.0f32; n_embd])?,
9495 rank1.ctx().new_event(None)?,
9496 )
9497 };
9498 let (sh_root, ev_z) = {
9499 let _main = e.gpu.enter_main()?;
9500 (e.htod(&vec![0.0f32; n_embd])?, e.ctx().new_event(None)?)
9501 };
9502 *ws_guard = Some(((n_embd, n_ff_sh), act1, z1, sh_root, ev_z, ev_done));
9503 }
9504 let mut reps_guard = SHEXP_D1_REPS
9506 .lock()
9507 .map_err(|_| "shexp dev1 replica lock is poisoned")?;
9508 let reps = reps_guard.get_or_insert_with(Default::default);
9509 if !reps.contains_key(&il) {
9510 let (wg1, wu1, wd1) = {
9511 let _r1 = rank1.gpu.enter_main()?;
9512 (
9513 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9514 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9515 rank1.alloc_u8_uninit(n_embd * n_ff_sh * 2)?,
9516 )
9517 };
9518 for (src, dst) in [(wg, &wg1), (wu, &wu1), (wd, &wd1)] {
9519 let s_ptr = {
9520 let _main = e.gpu.enter_main()?;
9521 let stream = e.stream();
9522 let (p, _g) = src.device_ptr(&stream);
9523 p as u64
9524 };
9525 let d_ptr = {
9526 let _r1 = rank1.gpu.enter_main()?;
9527 let stream = rank1.stream();
9528 let (p, _g) = dst.device_ptr(&stream);
9529 p as u64
9530 };
9531 let _r1 = rank1.gpu.enter_main()?;
9532 crate::tp::raw_copy_bytes(d_ptr, s_ptr, src.len(), rank1)?;
9533 }
9534 {
9535 let _r1 = rank1.gpu.enter_main()?;
9536 rank1.stream().synchronize()?;
9537 }
9538 reps.insert(il, (wg1, wu1, wd1));
9539 }
9540 let (wg1, wu1, wd1) = reps.get(&il).expect("armed above");
9541 let (_, act1, z1, sh_root, ev_z, ev_done) = ws_guard.as_mut().expect("armed above");
9542 let (raw_z, raw_sh) = {
9545 let _main = e.gpu.enter_main()?;
9546 let stream = e.stream();
9547 let (a, _g0) = z.device_ptr(&stream);
9548 let (b, _g1) = sh_root.device_ptr(&stream);
9549 ev_z.record(&stream)?;
9550 (a as u64, b as u64)
9551 };
9552 {
9553 let _r1 = rank1.gpu.enter_main()?;
9554 rank1.stream().wait(ev_z)?;
9555 let raw_z1 = {
9556 let stream = rank1.stream();
9557 let (p, _g) = z1.device_ptr(&stream);
9558 p as u64
9559 };
9560 crate::tp::raw_copy_bytes(raw_z1, raw_z, n_embd * 4, rank1)?;
9561 rank1.matvec_bf16_dual_silu_into(wg1, wu1, z1, act1, n_embd, n_ff_sh, lim)?;
9562 rank1.matvec_bf16_raw_out(wd1, act1, raw_sh, n_ff_sh, n_embd)?;
9566 ev_done.record(&rank1.stream())?;
9567 }
9568 Ok(true)
9569 }
9570
9571 fn shexp_dev1_apply(
9573 e: &Engine,
9574 output: &mut CudaSlice<f32>,
9575 n_embd: usize,
9576 ) -> Result<(), Box<dyn std::error::Error>> {
9577 let guard = SHEXP_D1_WS
9578 .lock()
9579 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9580 let (pin, _, _, sh_root, _, ev_done) =
9581 guard.as_ref().ok_or("shexp dev1 apply without issue")?;
9582 if pin.0 != n_embd {
9583 return Err("shexp dev1 width drifted".into());
9584 }
9585 let _main = e.gpu.enter_main()?;
9586 e.stream().wait(ev_done)?;
9587 static ONES_D1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9588 std::sync::Mutex::new(None);
9589 let mut og = ONES_D1.lock().map_err(|_| "ones lock is poisoned")?;
9590 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9591 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9592 }
9593 let ones = &og.as_ref().expect("armed above").1;
9594 e.add_scaled_rows(sh_root, ones, output, n_embd, 1)?;
9595 Ok(())
9596 }
9597
9598 fn shexp_overlap_tail_ptrs(
9602 e: &Engine,
9603 m: &MoeWeights,
9604 cfg: &ModelConfig,
9605 n_embd: usize,
9606 ) -> Result<Option<(u64, u64)>, Box<dyn std::error::Error>> {
9607 use cudarc::driver::DevicePtr;
9608 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9609 return Ok(None);
9610 }
9611 let (
9612 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9613 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9614 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9615 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9616 else {
9617 return Ok(None);
9618 };
9619 let n_ff_sh = m
9620 .gate_shexp
9621 .as_ref()
9622 .expect("matched Some above")
9623 .out_features();
9624 let mut guard = SHEXP_OV_WS
9625 .lock()
9626 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9627 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9628 if guard
9629 .as_ref()
9630 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9631 {
9632 *guard = Some((
9633 pins.0,
9634 pins.1,
9635 pins.2,
9636 e.uninit(n_ff_sh)?,
9637 e.uninit(n_embd)?,
9638 ));
9639 }
9640 let sh_raw = {
9641 let (_, _, _, _, sh) = guard.as_ref().expect("armed above");
9642 let stream = e.stream();
9643 let (p, _g) = sh.device_ptr(&stream);
9644 p as u64
9645 };
9646 static ONES_T3: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9647 std::sync::Mutex::new(None);
9648 let mut og = ONES_T3.lock().map_err(|_| "ones lock is poisoned")?;
9649 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9650 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9651 }
9652 let ones_raw = {
9653 let stream = e.stream();
9654 let (p, _g) = og.as_ref().expect("armed above").1.device_ptr(&stream);
9655 p as u64
9656 };
9657 Ok(Some((sh_raw, ones_raw)))
9658 }
9659
9660 fn shexp_overlap_apply(
9663 e: &Engine,
9664 output: &mut CudaSlice<f32>,
9665 n_embd: usize,
9666 ) -> Result<(), Box<dyn std::error::Error>> {
9667 let guard = SHEXP_OV_WS
9668 .lock()
9669 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9670 let (_, ne, _, _, sh) = guard.as_ref().ok_or("shexp overlap apply without issue")?;
9671 if *ne != n_embd {
9672 return Err("shexp overlap width drifted".into());
9673 }
9674 static ONES_OV: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9675 std::sync::Mutex::new(None);
9676 let mut og = ONES_OV.lock().map_err(|_| "ones lock is poisoned")?;
9677 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9678 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9679 }
9680 let ones = &og.as_ref().expect("armed above").1;
9681 e.add_scaled_rows(sh, ones, output, n_embd, 1)?;
9682 Ok(())
9683 }
9684
9685 fn moe_ffn_grouped_add_shared(
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 static SHEXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9697 static SHEXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9698 let shexp_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9699 let shexp_started = shexp_timing.then(std::time::Instant::now);
9700 let result = Self::moe_ffn_grouped_add_shared_inner(e, m, z, t, cfg, il, moe_out);
9701 if let Some(started) = shexp_started {
9702 use std::sync::atomic::Ordering;
9703 e.stream().synchronize()?;
9704 let ns = SHEXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9705 + started.elapsed().as_nanos() as u64;
9706 let calls = SHEXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9707 if calls % 430 == 0 {
9708 eprintln!(
9709 "[moe-shexp-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9710 ns as f64 / 1.0e6,
9711 ns as f64 / calls as f64 / 1.0e3,
9712 );
9713 }
9714 }
9715 result
9716 }
9717
9718 #[allow(clippy::too_many_arguments)]
9719 fn moe_ffn_grouped_add_shared_inner(
9720 e: &Engine,
9721 m: &MoeWeights,
9722 z: &CudaSlice<f32>,
9723 t: usize,
9724 cfg: &ModelConfig,
9725 il: u16,
9726 moe_out: &mut CudaSlice<f32>,
9727 ) -> Result<(), Box<dyn std::error::Error>> {
9728 let n_embd = cfg.n_embd as usize;
9729 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
9730 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9731 {
9732 let n_ff_sh = gate_shexp.out_features();
9733 let lim = cfg.clamp_shexp_at(il as u32);
9734 let fused = t == 1
9741 && lim.is_none()
9742 && cfg.m3.is_none()
9743 && e.uses_q8_1_fast(gate_shexp)
9744 && e.uses_q8_1_fast(up_shexp);
9745 let bf16_dual = if t == 1 && crate::Engine::bf16_mmv_on() && n_embd % 8 == 0 {
9748 match (gate_shexp, up_shexp) {
9749 (
9750 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
9751 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
9752 ) => Some((wg, wu)),
9753 _ => None,
9754 }
9755 } else {
9756 None
9757 };
9758 let sh = if let Some((wg, wu)) = bf16_dual {
9759 static SHEXP_WS: std::sync::Mutex<
9763 Option<(
9764 usize,
9765 usize,
9766 usize,
9767 CudaSlice<f32>,
9768 CudaSlice<f32>,
9769 CudaSlice<f32>,
9770 CudaSlice<f32>,
9771 )>,
9772 > = std::sync::Mutex::new(None);
9773 let down_bf16 = match down_shexp {
9774 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9775 _ => None,
9776 };
9777 let mut guard = SHEXP_WS
9778 .lock()
9779 .map_err(|_| "shexp workspace lock is poisoned")?;
9780 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9781 if guard
9782 .as_ref()
9783 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9784 {
9785 *guard = Some((
9786 pins.0,
9787 pins.1,
9788 pins.2,
9789 e.uninit(n_ff_sh)?,
9790 e.uninit(n_ff_sh)?,
9791 e.uninit(n_ff_sh)?,
9792 e.uninit(n_embd)?,
9793 ));
9794 }
9795 {
9798 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9799 let split_on = *ON
9800 .get_or_init(|| std::env::var("MEMRA_SHEXP_SPLIT").as_deref() == Ok("1"));
9801 if split_on {
9802 if let (Some(wd), Some(rank1)) = (
9803 match down_shexp {
9804 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9805 _ => None,
9806 },
9807 m.step_tp.as_ref().and_then(|st| st.runtime.rank_engine(1)),
9808 ) {
9809 if let Some(sh) = Self::shexp_split_matvec(
9810 e, rank1, wg, wu, wd, z, lim, cfg, il, n_embd, n_ff_sh,
9811 )? {
9812 drop(guard);
9813 let gate = match &m.gate_inp_shexp {
9814 Some(gate_inp_shexp) => e.sigmoid_dot_rows(
9815 z,
9816 gate_inp_shexp.float_data(),
9817 n_embd,
9818 t,
9819 )?,
9820 None => e.htod(&vec![1.0f32; t])?,
9821 };
9822 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9823 return Ok(());
9824 }
9825 }
9826 }
9827 }
9828 let (_, _, _, gate, up, act, sh_buf) =
9829 guard.as_mut().expect("shexp workspace initialized above");
9830 if cfg.m3.is_none() {
9831 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9834 let _ = (&gate, &up);
9835 } else {
9836 e.matvec_bf16_dual_into(wg, wu, z, gate, up, n_embd, n_ff_sh)?;
9837 Self::ffn_act_lim(e, cfg, gate, up, 1.0, 1.0, lim, act, n_ff_sh)?;
9838 }
9839 if let Some(down) = down_bf16 {
9840 static FUSE_DA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9847 let fuse_da = *FUSE_DA.get_or_init(|| {
9848 std::env::var("MEMRA_FUSE_DOWN_ADDSCALE").as_deref() != Ok("0")
9849 });
9850 if fuse_da && m.gate_inp_shexp.is_none() {
9851 static ONES1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9852 std::sync::Mutex::new(None);
9853 let mut og = ONES1.lock().map_err(|_| "shexp ones lock is poisoned")?;
9854 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9855 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9856 }
9857 let ones = &og.as_ref().expect("armed above").1;
9858 e.matvec_bf16_down_addscale_into(
9859 down, act, ones, moe_out, n_ff_sh, n_embd,
9860 )?;
9861 return Ok(());
9862 }
9863 e.matvec_bf16_into(down, act, sh_buf, n_ff_sh, n_embd)?;
9864 let sh = e.uninit(n_embd)?;
9865 let mut sh = sh;
9867 {
9868 let mut dst = sh.slice_mut(0..n_embd);
9869 e.stream().memcpy_dtod(&sh_buf.slice(0..n_embd), &mut dst)?;
9870 }
9871 sh
9872 } else {
9873 e.matmul(down_shexp, act, 1)?
9874 }
9875 } else if fused {
9876 let (zq, zd) = e.quantize_q8_1(z, 1, n_embd)?;
9877 let pair = match e.matmul_pre_dual_noscale(gate_shexp, up_shexp, &zq, &zd, 1)? {
9878 Some((gate, up)) => Some((gate, up)),
9879 None => {
9880 match (
9881 e.matmul_pre_noscale(gate_shexp, &zq, &zd, 1)?,
9882 e.matmul_pre_noscale(up_shexp, &zq, &zd, 1)?,
9883 ) {
9884 (Some(gate), Some(up)) => Some((gate, up)),
9885 _ => None,
9886 }
9887 }
9888 };
9889 match pair {
9890 Some(((gate, gs), (up, us))) => {
9891 if e.uses_q8_1_fast(down_shexp) {
9892 let (aq, ad) = e.silu_mul_scaled_q8_1(&gate, &up, gs, us, n_ff_sh)?;
9893 e.matmul_pre(down_shexp, &aq, &ad, &gate, 1)?
9894 } else {
9895 let mut act = e.uninit(n_ff_sh)?;
9896 e.silu_mul_scaled(&gate, &up, gs, us, &mut act, n_ff_sh)?;
9897 e.matmul(down_shexp, &act, 1)?
9898 }
9899 }
9900 None => {
9901 let gate = e.matmul_pre(gate_shexp, &zq, &zd, z, 1)?;
9902 let up = e.matmul_pre(up_shexp, &zq, &zd, z, 1)?;
9903 let mut act = e.uninit(n_ff_sh)?;
9904 Self::ffn_act(e, cfg, &gate, &up, &mut act, n_ff_sh)?;
9905 e.matmul(down_shexp, &act, 1)?
9906 }
9907 }
9908 } else {
9909 let sg_gate = e.matmul(gate_shexp, z, t)?;
9910 let sg_up = e.matmul(up_shexp, z, t)?;
9911 let mut sa = e.uninit(t * n_ff_sh)?;
9912 Self::ffn_act_lim(
9913 e,
9914 cfg,
9915 &sg_gate,
9916 &sg_up,
9917 1.0,
9918 1.0,
9919 lim,
9920 &mut sa,
9921 t * n_ff_sh,
9922 )?;
9923 e.matmul(down_shexp, &sa, t)?
9924 };
9925 let gate = match &m.gate_inp_shexp {
9926 Some(gate_inp_shexp) => {
9927 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
9928 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
9929 } else {
9930 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
9931 let mut gate = e.uninit(t)?;
9932 e.sigmoid(&raw, &mut gate, t)?;
9933 gate
9934 }
9935 }
9936 None if t == 1 => {
9941 static ONES: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9942 std::sync::Mutex::new(None);
9943 let mut guard = ONES.lock().map_err(|_| "shexp ones lock is poisoned")?;
9944 if guard.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9945 *guard = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9946 }
9947 let ones = &guard.as_ref().expect("armed above").1;
9948 e.add_scaled_rows(&sh, ones, moe_out, n_embd, t)?;
9949 return Ok(());
9950 }
9951 None => e.htod(&vec![1.0f32; t])?,
9952 };
9953 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9954 }
9955 Ok(())
9956 }
9957
9958 pub(crate) fn moe_ffn_grouped(
9961 e: &Engine,
9962 m: &MoeWeights,
9963 z: &CudaSlice<f32>,
9964 t: usize,
9965 cfg: &ModelConfig,
9966 il: u16,
9967 max_block: usize,
9968 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9969 let moe = cfg.moe.as_ref().unwrap();
9970 let n_embd = cfg.n_embd as usize;
9971 let n_expert = moe.expert_count as usize;
9972 let n_used = moe.expert_used_count as usize;
9973 let n_ff_exp = moe.expert_ff_length as usize;
9974 let lim_exp = cfg.clamp_exp_at(il as u32);
9976
9977 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
9981 if let Some(sig) = cfg.sigmoid_router() {
9982 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
9983 }
9984 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
9985 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
9986 } else {
9987 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
9988 };
9989 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
9990 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
9991 Self::trace_moe_input(e, il, t, n_embd, z)?;
9992
9993 let no_exp_macros = m.gate_exps.macros.is_none()
9998 && m.up_exps.macros.is_none()
9999 && m.down_exps.macros.is_none();
10000 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
10001 m.has_uniform_expert_layout()
10002 && no_exp_macros
10003 && moe_q8_enabled()
10004 && q8_expert_supported(m.gate_exps.qtype)
10005 && q8_expert_supported(m.up_exps.qtype)
10006 && q8_expert_supported(m.down_exps.qtype)
10007 && moe_slab_enabled()
10008 && dev.dev == e.ctx().ordinal()
10009 });
10010 if let Some(dev) = resident_q8 {
10011 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
10012 e,
10013 m,
10014 z,
10015 t,
10016 cfg,
10017 il,
10018 &sel_all,
10019 &w_all,
10020 &dev.ptr_row,
10021 dev.gu_il,
10022 )?;
10023 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10024 return Ok(moe_out);
10025 }
10026
10027 struct ExpertGroup {
10031 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
10035 let mut groups: Vec<ExpertGroup> = (0..n_expert)
10036 .map(|_| ExpertGroup {
10037 tok_indices: Vec::new(),
10038 slot_indices: Vec::new(),
10039 weights: Vec::new(),
10040 })
10041 .collect();
10042
10043 for tok in 0..t {
10044 for j in 0..n_used {
10045 let ex = sel_all[tok * n_used + j] as usize;
10046 let w = w_all[tok * n_used + j];
10047 groups[ex].tok_indices.push(tok as i32);
10048 groups[ex].slot_indices.push(j as i32);
10049 groups[ex].weights.push(w);
10050 }
10051 }
10052
10053 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
10056 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
10060 let u_len = m.up_exps.max_expert_bytes();
10061 let d_len = m.down_exps.max_expert_bytes();
10062 let moe_q8 = m.has_uniform_expert_layout()
10063 && moe_q8_enabled()
10064 && q8_expert_supported(m.gate_exps.qtype)
10065 && q8_expert_supported(m.up_exps.qtype)
10066 && q8_expert_supported(m.down_exps.qtype);
10067 let slab_local = m
10070 .dev_exps
10071 .as_ref()
10072 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
10073 let use_cache =
10074 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
10075 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
10078
10079 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
10081 (
10082 Some(e.alloc_u8(g_len)?),
10083 Some(e.alloc_u8(u_len)?),
10084 Some(e.alloc_u8(d_len)?),
10085 )
10086 } else {
10087 (None, None, None)
10088 };
10089
10090 let mut order: Vec<usize> = (0..n_expert)
10101 .filter(|&ex| !groups[ex].tok_indices.is_empty())
10102 .collect();
10103 order.sort_by(|&a, &b| {
10104 groups[b]
10105 .tok_indices
10106 .len()
10107 .cmp(&groups[a].tok_indices.len())
10108 .then(a.cmp(&b))
10109 });
10110 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
10112 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
10113 if worker_disk_prefetch {
10114 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
10115 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
10116 }
10117 }
10118 for (order_pos, &ex) in order.iter().enumerate() {
10119 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
10120 Self::moe_prefetch_host_expert(order[next], m);
10121 }
10122 if worker_disk_prefetch {
10123 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
10124 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10125 let keep = [
10126 BlockId::new(il, PROJ_GATE, ex as u16),
10127 BlockId::new(il, PROJ_UP, ex as u16),
10128 BlockId::new(il, PROJ_DOWN, ex as u16),
10129 ];
10130 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
10131 }
10132 }
10133 let grp = &groups[ex];
10134 let m_e = grp.tok_indices.len();
10135 m_dist.push(m_e);
10136 let gl = m.gate_exps.expert_layout(ex);
10137 let ul = m.up_exps.expert_layout(ex);
10138 let dl = m.down_exps.expert_layout(ex);
10139
10140 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
10144 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
10145 let dmac = m.down_exps.macro_scale(ex);
10146 let weight_d = if dmac == 1.0 {
10147 e.htod(&grp.weights)?
10148 } else {
10149 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
10150 e.htod(&scaled)?
10151 };
10152
10153 let mut gathered = e.zeros(m_e * n_embd)?;
10155 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
10156 let gv = gathered.slice(0..m_e * n_embd);
10157
10158 let y = if let Some(dev) = slab_local {
10161 let gate_start = ex * m.gate_exps.expert_stride;
10162 let up_start = ex * m.up_exps.expert_stride;
10163 let down_start = ex * m.down_exps.expert_stride;
10164 if grouped_q8 {
10165 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10166 let gate = e.qmatvec_expert_q8(
10167 &dev.gate,
10168 gate_start..gate_start + gl.len,
10169 &zq,
10170 &zd,
10171 m_e,
10172 m.gate_exps.in_f,
10173 m.gate_exps.out_f,
10174 gl.qtype,
10175 gl.row_bytes,
10176 )?;
10177 let up = e.qmatvec_expert_q8(
10178 &dev.up,
10179 up_start..up_start + ul.len,
10180 &zq,
10181 &zd,
10182 m_e,
10183 m.up_exps.in_f,
10184 m.up_exps.out_f,
10185 ul.qtype,
10186 ul.row_bytes,
10187 )?;
10188 let mut act = e.uninit(m_e * n_ff_exp)?;
10189 Self::ffn_act_lim(
10190 e,
10191 cfg,
10192 &gate,
10193 &up,
10194 m.gate_exps.macro_scale(ex),
10195 m.up_exps.macro_scale(ex),
10196 lim_exp,
10197 &mut act,
10198 m_e * n_ff_exp,
10199 )?;
10200 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10201 e.qmatvec_expert_q8(
10202 &dev.down,
10203 down_start..down_start + dl.len,
10204 &aq2,
10205 &ad2,
10206 m_e,
10207 m.down_exps.in_f,
10208 m.down_exps.out_f,
10209 dl.qtype,
10210 dl.row_bytes,
10211 )?
10212 } else {
10213 let gate = e.qmatvec_view(
10214 &dev.gate,
10215 gate_start..gate_start + gl.len,
10216 &gv,
10217 m_e,
10218 m.gate_exps.in_f,
10219 m.gate_exps.out_f,
10220 gl.qtype,
10221 gl.row_bytes,
10222 )?;
10223 let up = e.qmatvec_view(
10224 &dev.up,
10225 up_start..up_start + ul.len,
10226 &gv,
10227 m_e,
10228 m.up_exps.in_f,
10229 m.up_exps.out_f,
10230 ul.qtype,
10231 ul.row_bytes,
10232 )?;
10233 let mut act = e.uninit(m_e * n_ff_exp)?;
10234 Self::ffn_act_lim(
10235 e,
10236 cfg,
10237 &gate,
10238 &up,
10239 m.gate_exps.macro_scale(ex),
10240 m.up_exps.macro_scale(ex),
10241 lim_exp,
10242 &mut act,
10243 m_e * n_ff_exp,
10244 )?;
10245 let actv = act.slice(0..m_e * n_ff_exp);
10246 e.qmatvec_view(
10247 &dev.down,
10248 down_start..down_start + dl.len,
10249 &actv,
10250 m_e,
10251 m.down_exps.in_f,
10252 m.down_exps.out_f,
10253 dl.qtype,
10254 dl.row_bytes,
10255 )?
10256 }
10257 } else if use_cache {
10258 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10259 if grouped_q8 {
10260 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10261 let gate = e.with_moe_cache(max_block, |cache, eng| {
10262 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10263 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10264 eng.qmatvec_expert_q8(
10265 cache.buf(slot),
10266 0..gl.len,
10267 &zq,
10268 &zd,
10269 m_e,
10270 m.gate_exps.in_f,
10271 m.gate_exps.out_f,
10272 gl.qtype,
10273 gl.row_bytes,
10274 )
10275 })?;
10276 let up = e.with_moe_cache(max_block, |cache, eng| {
10277 let id = BlockId::new(il, PROJ_UP, ex as u16);
10278 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10279 eng.qmatvec_expert_q8(
10280 cache.buf(slot),
10281 0..ul.len,
10282 &zq,
10283 &zd,
10284 m_e,
10285 m.up_exps.in_f,
10286 m.up_exps.out_f,
10287 ul.qtype,
10288 ul.row_bytes,
10289 )
10290 })?;
10291 let mut act = e.uninit(m_e * n_ff_exp)?;
10292 Self::ffn_act_lim(
10293 e,
10294 cfg,
10295 &gate,
10296 &up,
10297 m.gate_exps.macro_scale(ex),
10298 m.up_exps.macro_scale(ex),
10299 lim_exp,
10300 &mut act,
10301 m_e * n_ff_exp,
10302 )?;
10303 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10304 e.with_moe_cache(max_block, |cache, eng| {
10305 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10306 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10307 eng.qmatvec_expert_q8(
10308 cache.buf(slot),
10309 0..dl.len,
10310 &aq2,
10311 &ad2,
10312 m_e,
10313 m.down_exps.in_f,
10314 m.down_exps.out_f,
10315 dl.qtype,
10316 dl.row_bytes,
10317 )
10318 })?
10319 } else {
10320 let gate = e.with_moe_cache(max_block, |cache, eng| {
10321 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10322 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10323 eng.qmatvec_view(
10324 cache.buf(slot),
10325 0..gl.len,
10326 &gv,
10327 m_e,
10328 m.gate_exps.in_f,
10329 m.gate_exps.out_f,
10330 gl.qtype,
10331 gl.row_bytes,
10332 )
10333 })?;
10334 let up = e.with_moe_cache(max_block, |cache, eng| {
10335 let id = BlockId::new(il, PROJ_UP, ex as u16);
10336 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10337 eng.qmatvec_view(
10338 cache.buf(slot),
10339 0..ul.len,
10340 &gv,
10341 m_e,
10342 m.up_exps.in_f,
10343 m.up_exps.out_f,
10344 ul.qtype,
10345 ul.row_bytes,
10346 )
10347 })?;
10348 let mut act = e.uninit(m_e * n_ff_exp)?;
10349 Self::ffn_act_lim(
10350 e,
10351 cfg,
10352 &gate,
10353 &up,
10354 m.gate_exps.macro_scale(ex),
10355 m.up_exps.macro_scale(ex),
10356 lim_exp,
10357 &mut act,
10358 m_e * n_ff_exp,
10359 )?;
10360 let actv = act.slice(0..m_e * n_ff_exp);
10361 e.with_moe_cache(max_block, |cache, eng| {
10362 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10363 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10364 eng.qmatvec_view(
10365 cache.buf(slot),
10366 0..dl.len,
10367 &actv,
10368 m_e,
10369 m.down_exps.in_f,
10370 m.down_exps.out_f,
10371 dl.qtype,
10372 dl.row_bytes,
10373 )
10374 })?
10375 }
10376 } else {
10377 let sg = scratch_g.as_mut().unwrap();
10378 let su = scratch_u.as_mut().unwrap();
10379 let sd = scratch_d.as_mut().unwrap();
10380 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
10381 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
10382 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
10383 if grouped_q8 {
10384 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10385 let gate = e.qmatvec_expert_q8(
10386 sg,
10387 0..gl.len,
10388 &zq,
10389 &zd,
10390 m_e,
10391 m.gate_exps.in_f,
10392 m.gate_exps.out_f,
10393 gl.qtype,
10394 gl.row_bytes,
10395 )?;
10396 let up = e.qmatvec_expert_q8(
10397 su,
10398 0..ul.len,
10399 &zq,
10400 &zd,
10401 m_e,
10402 m.up_exps.in_f,
10403 m.up_exps.out_f,
10404 ul.qtype,
10405 ul.row_bytes,
10406 )?;
10407 let mut act = e.uninit(m_e * n_ff_exp)?;
10408 Self::ffn_act_lim(
10409 e,
10410 cfg,
10411 &gate,
10412 &up,
10413 m.gate_exps.macro_scale(ex),
10414 m.up_exps.macro_scale(ex),
10415 lim_exp,
10416 &mut act,
10417 m_e * n_ff_exp,
10418 )?;
10419 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10420 e.qmatvec_expert_q8(
10421 sd,
10422 0..dl.len,
10423 &aq2,
10424 &ad2,
10425 m_e,
10426 m.down_exps.in_f,
10427 m.down_exps.out_f,
10428 dl.qtype,
10429 dl.row_bytes,
10430 )?
10431 } else {
10432 let gate = e.qmatvec_view(
10433 sg,
10434 0..gl.len,
10435 &gv,
10436 m_e,
10437 m.gate_exps.in_f,
10438 m.gate_exps.out_f,
10439 gl.qtype,
10440 gl.row_bytes,
10441 )?;
10442 let up = e.qmatvec_view(
10443 su,
10444 0..ul.len,
10445 &gv,
10446 m_e,
10447 m.up_exps.in_f,
10448 m.up_exps.out_f,
10449 ul.qtype,
10450 ul.row_bytes,
10451 )?;
10452 let mut act = e.uninit(m_e * n_ff_exp)?;
10453 Self::ffn_act_lim(
10454 e,
10455 cfg,
10456 &gate,
10457 &up,
10458 m.gate_exps.macro_scale(ex),
10459 m.up_exps.macro_scale(ex),
10460 lim_exp,
10461 &mut act,
10462 m_e * n_ff_exp,
10463 )?;
10464 let actv = act.slice(0..m_e * n_ff_exp);
10465 e.qmatvec_view(
10466 sd,
10467 0..dl.len,
10468 &actv,
10469 m_e,
10470 m.down_exps.in_f,
10471 m.down_exps.out_f,
10472 dl.qtype,
10473 dl.row_bytes,
10474 )?
10475 }
10476 };
10477
10478 e.scatter_slot(
10480 &y,
10481 &tok_idx_d,
10482 &slot_idx_d,
10483 &weight_d,
10484 &mut slot_buf,
10485 &mut wbuf,
10486 n_embd,
10487 n_used,
10488 m_e,
10489 )?;
10490 }
10491
10492 let mut moe_out = e.zeros(t * n_embd)?;
10494 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
10495
10496 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
10498 m_dist.sort_unstable();
10499 let active = m_dist.len();
10500 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
10501 let median = m_dist[active / 2];
10502 let max_m = *m_dist.last().unwrap();
10503 let min_m = m_dist[0];
10504 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
10505 println!(
10506 "moe-grouped il={il} t={t} active={active}/{n_expert} \
10507 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
10508 above_gemm_threshold(>=16)={above16}/{active}"
10509 );
10510 }
10511
10512 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10513 Ok(moe_out)
10514 }
10515
10516 pub(crate) fn moe_ffn_lockstep(
10523 &self,
10524 e: &Engine,
10525 m: &MoeWeights,
10526 zbatch: &CudaSlice<f32>,
10527 mrows: usize,
10528 il: u16,
10529 max_block: usize,
10530 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10531 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10532 let cfg = &self.cfg;
10533 let moe = cfg.moe.as_ref().unwrap();
10534 let n_embd = cfg.n_embd as usize;
10535 let n_expert = moe.expert_count as usize;
10536 let n_used = moe.expert_used_count as usize;
10537 let n_ff_exp = moe.expert_ff_length as usize;
10538 let lim_exp = cfg.clamp_exp_at(il as u32);
10540 let lim_shexp = cfg.clamp_shexp_at(il as u32);
10541
10542 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
10543 if let Some(sig) = cfg.sigmoid_router() {
10544 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
10545 }
10546 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10547 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
10548 } else {
10549 Self::moe_route_cfg(
10550 e,
10551 &logits,
10552 mrows,
10553 n_expert,
10554 n_used,
10555 m.active_experts.as_deref(),
10556 )?
10557 };
10558 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
10559
10560 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
10562 Ok((0..n_expert)
10563 .map(|ex| {
10564 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
10565 .into_iter()
10566 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
10567 })
10568 .collect())
10569 })?;
10570
10571 struct Group {
10572 rows: Vec<i32>,
10573 slots: Vec<i32>,
10574 weights: Vec<f32>,
10575 }
10576 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
10577 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
10578 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
10579 Default::default();
10580 for row in 0..mrows {
10581 for j in 0..n_used {
10582 let ex = sel_all[row * n_used + j] as usize;
10583 let w = w_all[row * n_used + j];
10584 if resident_expert[ex] {
10585 let group = groups.entry(ex).or_insert_with(|| Group {
10586 rows: Vec::new(),
10587 slots: Vec::new(),
10588 weights: Vec::new(),
10589 });
10590 group.rows.push(row as i32);
10591 group.slots.push(j as i32);
10592 group.weights.push(w);
10593 } else {
10594 crate::cpu_experts::record_incomplete_gpu_residency(0);
10595 cpu_rows[row].push((ex, w));
10596 cpu_by_expert.entry(ex).or_default().push((row, w));
10597 }
10598 }
10599 }
10600
10601 let host_rows = e.dtoh(zbatch)?;
10607 let rows_ok = crate::cpu_experts::rows_supported();
10608 enum CpuPart {
10609 Single { row: usize },
10610 Rows { rows: Vec<usize> },
10611 }
10612 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
10613 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
10614 if rows_ok {
10615 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
10616 .into_iter()
10617 .filter(|(_, rows)| rows.len() >= 2)
10618 .collect();
10619 shared.sort_by_key(|(ex, _)| *ex);
10620 for (ex, mut row_weights) in shared {
10621 row_weights.sort_by_key(|(row, _)| *row);
10622 let inputs: Vec<(&[f32], f32)> = row_weights
10623 .iter()
10624 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
10625 .collect();
10626 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
10627 .map_err(std::io::Error::other)?;
10628 for &(row, _) in &row_weights {
10629 rows_served.insert((row, ex));
10630 }
10631 tickets.push((
10632 CpuPart::Rows {
10633 rows: row_weights.iter().map(|&(row, _)| row).collect(),
10634 },
10635 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
10636 ));
10637 }
10638 }
10639 for (row, selected) in cpu_rows.iter().enumerate() {
10640 let leftover: Vec<(usize, f32)> = selected
10641 .iter()
10642 .copied()
10643 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
10644 .collect();
10645 if leftover.is_empty() {
10646 continue;
10647 }
10648 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
10649 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
10650 .map_err(std::io::Error::other)?;
10651 tickets.push((
10652 CpuPart::Single { row },
10653 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
10654 ));
10655 }
10656
10657 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
10658 let mut wbuf = e.zeros(mrows * n_used)?;
10659 let mut order: Vec<usize> = groups.keys().copied().collect();
10660 order.sort_by(|&a, &b| {
10661 groups[&b]
10662 .rows
10663 .len()
10664 .cmp(&groups[&a].rows.len())
10665 .then(a.cmp(&b))
10666 });
10667 for &ex in &order {
10668 let group = &groups[&ex];
10669 let m_e = group.rows.len();
10670 let gl = m.gate_exps.expert_layout(ex);
10671 let ul = m.up_exps.expert_layout(ex);
10672 let dl = m.down_exps.expert_layout(ex);
10673 let row_idx_d = e.htod_i32(&group.rows)?;
10674 let slot_idx_d = e.htod_i32(&group.slots)?;
10675 let dmac = m.down_exps.macro_scale(ex);
10676 let weight_d = if dmac == 1.0 {
10677 e.htod(&group.weights)?
10678 } else {
10679 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
10680 e.htod(&scaled)?
10681 };
10682 let mut gathered = e.zeros(m_e * n_embd)?;
10683 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
10684 let gv = gathered.slice(0..m_e * n_embd);
10685 let gate = e.with_moe_cache(max_block, |c, eng| {
10686 let slot = c
10687 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
10688 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10689 eng.qmatvec_view(
10690 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10691 0..gl.len,
10692 &gv,
10693 m_e,
10694 m.gate_exps.in_f,
10695 m.gate_exps.out_f,
10696 gl.qtype,
10697 gl.row_bytes,
10698 )
10699 })?;
10700 let up = e.with_moe_cache(max_block, |c, eng| {
10701 let slot = c
10702 .resident(BlockId::new(il, PROJ_UP, ex as u16))
10703 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10704 eng.qmatvec_view(
10705 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10706 0..ul.len,
10707 &gv,
10708 m_e,
10709 m.up_exps.in_f,
10710 m.up_exps.out_f,
10711 ul.qtype,
10712 ul.row_bytes,
10713 )
10714 })?;
10715 let mut act = e.zeros(m_e * n_ff_exp)?;
10716 Self::ffn_act_lim(
10717 e,
10718 cfg,
10719 &gate,
10720 &up,
10721 m.gate_exps.macro_scale(ex),
10722 m.up_exps.macro_scale(ex),
10723 lim_exp,
10724 &mut act,
10725 m_e * n_ff_exp,
10726 )?;
10727 let actv = act.slice(0..m_e * n_ff_exp);
10728 let y = e.with_moe_cache(max_block, |c, eng| {
10729 let slot = c
10730 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
10731 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10732 eng.qmatvec_view(
10733 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10734 0..dl.len,
10735 &actv,
10736 m_e,
10737 m.down_exps.in_f,
10738 m.down_exps.out_f,
10739 dl.qtype,
10740 dl.row_bytes,
10741 )
10742 })?;
10743 e.scatter_slot(
10744 &y,
10745 &row_idx_d,
10746 &slot_idx_d,
10747 &weight_d,
10748 &mut slot_buf,
10749 &mut wbuf,
10750 n_embd,
10751 n_used,
10752 m_e,
10753 )?;
10754 }
10755 let mut moe_out = e.zeros(mrows * n_embd)?;
10756 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
10757
10758 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
10760 for (part, ticket) in tickets {
10761 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
10762 let mut add_row = |row: usize, chunk: &[f32]| {
10763 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
10764 for (accumulator, value) in sum.iter_mut().zip(chunk) {
10765 *accumulator += value;
10766 }
10767 };
10768 match part {
10769 CpuPart::Single { row } => add_row(row, &cpu_output),
10770 CpuPart::Rows { rows } => {
10771 for (slot, row) in rows.into_iter().enumerate() {
10772 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
10773 }
10774 }
10775 }
10776 }
10777 for (row, sum) in row_sums.into_iter().enumerate() {
10778 let Some(sum) = sum else { continue };
10779 let cpu_output = e.htod(&sum)?;
10780 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
10781 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
10782 }
10783
10784 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
10785 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
10786 {
10787 let n_ff_sh = gate_shexp.out_features();
10788 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
10789 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
10790 let mut sa = e.zeros(mrows * n_ff_sh)?;
10791 Self::ffn_act_lim(
10792 e,
10793 cfg,
10794 &sg_gate,
10795 &sg_up,
10796 1.0,
10797 1.0,
10798 lim_shexp,
10799 &mut sa,
10800 mrows * n_ff_sh,
10801 )?;
10802 let sh = e.matmul(down_shexp, &sa, mrows)?;
10803 let g = match &m.gate_inp_shexp {
10806 Some(gate_inp_shexp) => {
10807 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
10808 }
10809 None => e.htod(&vec![1.0f32; mrows])?,
10810 };
10811 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
10812 }
10813
10814 Ok(moe_out)
10815 }
10816}
10817
10818impl HybridModel {
10824 pub(crate) fn gemma4_rope_dims(&self, il: usize) -> usize {
10838 let g = self
10839 .cfg
10840 .gemma4
10841 .as_ref()
10842 .expect("gemma4_rope_dims on a non-gemma4 config");
10843 if g.swa_pattern[il] {
10844 g.rope_dims_swa as usize
10845 } else {
10846 g.rope_dims_global as usize
10847 }
10848 }
10849
10850 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
10851 let g = self.cfg.gemma4.as_ref().unwrap();
10852 let swa = g.swa_pattern[il];
10853 let hd = if swa {
10854 g.key_length_swa
10855 } else {
10856 g.key_length_global
10857 } as usize;
10858 (
10862 hd,
10863 g.head_count_kv[il] as usize,
10864 self.cfg.n_head as usize,
10865 if swa {
10866 g.rope_base_swa
10867 } else {
10868 g.rope_base_global
10869 },
10870 1.0,
10871 swa,
10872 )
10873 }
10874
10875 pub(crate) fn gemma4_suppress(
10879 &self,
10880 e: &Engine,
10881 ld: &mut CudaSlice<f32>,
10882 t: usize,
10883 ) -> Result<(), Box<dyn std::error::Error>> {
10884 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
10885 #[cfg(debug_assertions)]
10890 crate::debug_assert_tensor_stream_device(
10891 ids,
10892 &e.stream(),
10893 "gemma4_suppress.suppress_d",
10894 );
10895 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
10896 }
10897 Ok(())
10898 }
10899
10900 #[allow(clippy::too_many_arguments)]
10905 fn gemma_fa_one_program() -> bool {
10914 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10915 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_FA_ONE_PROGRAM").as_deref() == Ok("1"))
10916 }
10917
10918 fn gemma4_attn_prime(
10919 &self,
10920 e: &Engine,
10921 fa: &crate::hybrid::FullAttnLayer,
10922 il: usize,
10923 h: &CudaSlice<f32>,
10924 pos_d: &CudaSlice<i32>,
10925 t: usize,
10926 cache: Option<&mut Cache>,
10927 island: Option<&CudaSlice<i32>>,
10928 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10929 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10930 let eps = self.cfg.rms_eps;
10931 let aux = self.gemma4_aux.as_ref().unwrap();
10932 let ones = aux.ones(e);
10933 #[cfg(debug_assertions)]
10934 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
10935
10936 e.mmq_act_begin();
10939 let q0 = e.matmul(&fa.wq, h, t)?; if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10941 let v = e.dtoh(&q0)?;
10942 let nan = v.iter().filter(|x| x.is_nan()).count();
10943 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10944 eprintln!(
10945 "[g4-prime-trace] L0 q0: nan={nan}/{} amax={amax:.3}",
10946 v.len()
10947 );
10948 }
10949 let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
10953 e.matmul(&fa.wv, h, t)?
10954 } else {
10955 e.clone_dtod(&k0)?
10956 };
10957 if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10958 for (tag, buf) in [("k0", &k0), ("v0", &v0)] {
10959 let v = e.dtoh(buf)?;
10960 let nan = v.iter().filter(|x| x.is_nan()).count();
10961 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10962 eprintln!(
10963 "[g4-prime-trace] L0 {tag}: nan={nan}/{} amax={amax:.3}",
10964 v.len()
10965 );
10966 }
10967 }
10968
10969 let mut q = e.uninit(t * nh * hd)?;
10970 let mut k = e.uninit(t * nkv * hd)?;
10971 let mut v = e.uninit(t * nkv * hd)?;
10973 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10977 let emit = island.is_none()
10980 && t >= 16
10981 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
10982 && *EMIT.get_or_init(|| {
10983 std::env::var("MEMRA_FA_EMIT")
10984 .map(|s| s != "0")
10985 .unwrap_or(true)
10986 });
10987 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
10988 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10989 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10990 let v_f16 = emit
10993 && crate::fa_f16pv_on()
10994 && match hd {
10995 512 => true,
10996 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
10997 _ => false,
10998 };
10999 if emit {
11000 e.rms_norm_qkv_w4b(
11001 &q0,
11002 &k0,
11003 &v0,
11004 fa.q_norm.float_data(),
11005 fa.k_norm.float_data(),
11006 ones,
11007 &mut q,
11008 &mut k,
11009 &mut v,
11010 &mut vb,
11011 hd,
11012 nh * t,
11013 nkv * t,
11014 eps,
11015 v_f16,
11016 )?;
11017 } else {
11018 e.rms_norm_qkv(
11019 &q0,
11020 &k0,
11021 &v0,
11022 fa.q_norm.float_data(),
11023 fa.k_norm.float_data(),
11024 ones,
11025 &mut q,
11026 &mut k,
11027 &mut v,
11028 hd,
11029 nh * t,
11030 nkv * t,
11031 eps,
11032 )?;
11033 }
11034
11035 let ff = if swa {
11036 None
11037 } else {
11038 Some(
11039 aux.rope_freqs(e)
11040 .expect("gemma4 global rope needs rope_freqs.weight"),
11041 )
11042 };
11043 #[cfg(debug_assertions)]
11044 if let Some(ff) = ff {
11045 crate::debug_assert_tensor_stream_device(
11046 ff,
11047 &e.stream(),
11048 "gemma4_attn_prime.rope_freqs",
11049 );
11050 }
11051 if emit {
11052 e.rope_neox2_bf16e(
11053 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
11054 )?;
11055 } else {
11056 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
11057 }
11058
11059 if let Some(cache) = cache {
11060 let kvl = cache.kv[il].as_mut().unwrap();
11061 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
11062 e.append_kv_quantized_rows(
11063 &k,
11064 &v,
11065 &mut kvl.k,
11066 &mut kvl.v,
11067 kvl.len,
11068 t,
11069 kvl.kv_dim_k,
11070 kvl.kv_dim_v,
11071 kvl.k_tok_bytes,
11072 kvl.v_tok_bytes,
11073 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
11074 )?;
11075 kvl.len += t;
11076 }
11077 let mut attn = e.zeros(t * nh * hd)?;
11078 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11082 if let Some(span) = island {
11083 let w = if swa && t > win { win } else { 0 };
11088 e.sdpa_naive_island(&q, &k, &v, &mut attn, span, hd, nh, nkv, t, t, scale, w)?;
11089 } else if swa && (t > win || Self::gemma_fa_one_program()) {
11090 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11091 if emit {
11092 e.fa_prefill_w_pre(
11093 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
11094 )?;
11095 } else {
11096 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11097 }
11098 } else {
11099 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11100 }
11101 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11102 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11103 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
11104 if emit {
11105 e.fa_prefill_hd512_pre(
11106 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
11107 )?;
11108 } else {
11109 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11110 }
11111 } else {
11112 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11113 }
11114 Ok(e.matmul(&fa.wo, &attn, t)?)
11115 }
11116
11117 fn gemma4_attn(
11119 &self,
11120 e: &Engine,
11121 fa: &crate::hybrid::FullAttnLayer,
11122 il: usize,
11123 h: &CudaSlice<f32>,
11124 pos_d: &CudaSlice<i32>,
11125 t: usize,
11126 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11127 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None, None)
11128 }
11129
11130 fn gemma4_moe_q8(
11135 &self,
11136 e: &Engine,
11137 m: &crate::hybrid::MoeWeights,
11138 bits: &crate::hybrid::Gemma4MoeBits,
11139 mq: &(CudaSlice<i8>, CudaSlice<f32>),
11140 router_in: &CudaSlice<f32>,
11141 t: usize,
11142 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11143 let cfg = &self.cfg;
11144 let moe = cfg.moe.as_ref().unwrap();
11145 let n_embd = cfg.n_embd as usize;
11146 let n_expert = moe.expert_count as usize;
11147 let n_used = moe.expert_used_count as usize;
11148 let n_ff_exp = moe.expert_ff_length as usize;
11149 let logits = if crate::router_kernel_on() {
11153 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11154 } else {
11155 e.matmul(&m.gate_inp, router_in, t)?
11156 };
11157 let dev = m.dev_exps.as_ref().unwrap();
11158 let (sel_d, w_d) =
11159 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11160 let (zq, zd) = mq;
11161 if t == 1 {
11162 let selv = sel_d.slice(0..n_used);
11163 let wv = w_d.slice(0..n_used);
11164 let act = e.moe_gate_up_gelu8_dev_q8(
11165 &dev.ptr_row,
11166 &selv,
11167 zq,
11168 zd,
11169 n_embd,
11170 n_ff_exp,
11171 n_used,
11172 n_expert,
11173 m.gate_exps.qtype,
11174 m.up_exps.qtype,
11175 m.gate_exps.row_bytes,
11176 m.up_exps.row_bytes,
11177 )?;
11178 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11179 let mut moe_out = e.uninit(n_embd)?;
11180 e.moe_down8_fma_dev_q8(
11181 &dev.ptr_row,
11182 &selv,
11183 &wv,
11184 &aq2,
11185 &ad2,
11186 &mut moe_out.slice_mut(0..n_embd),
11187 n_ff_exp,
11188 n_embd,
11189 n_used,
11190 n_expert,
11191 m.down_exps.qtype,
11192 m.down_exps.row_bytes,
11193 )?;
11194 return Ok(moe_out);
11195 }
11196 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11197 let act = if csr {
11198 e.moe_gate_up_gelu8_dev_q8_csr(
11199 &dev.ptr_row,
11200 &sel_d,
11201 zq,
11202 zd,
11203 t * n_used,
11204 n_embd,
11205 n_ff_exp,
11206 n_used,
11207 n_expert,
11208 m.gate_exps.qtype,
11209 m.up_exps.qtype,
11210 m.gate_exps.row_bytes,
11211 m.up_exps.row_bytes,
11212 )?
11213 } else {
11214 e.moe_gate_up_gelu8_dev_q8_rows(
11215 &dev.ptr_row,
11216 &sel_d,
11217 zq,
11218 zd,
11219 t,
11220 n_embd,
11221 n_ff_exp,
11222 n_used,
11223 n_expert,
11224 m.gate_exps.qtype,
11225 m.up_exps.qtype,
11226 m.gate_exps.row_bytes,
11227 m.up_exps.row_bytes,
11228 )?
11229 };
11230 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11231 let mut moe_out = e.uninit(t * n_embd)?;
11232 e.moe_down8_fma_dev_q8_rows_g(
11235 &dev.ptr_row,
11236 &sel_d,
11237 &w_d,
11238 &aq2,
11239 &ad2,
11240 &mut moe_out,
11241 t,
11242 n_ff_exp,
11243 n_embd,
11244 n_used,
11245 n_expert,
11246 m.down_exps.qtype,
11247 m.down_exps.row_bytes,
11248 )?;
11249 Ok(moe_out)
11250 }
11251
11252 fn gemma4_moe(
11256 &self,
11257 e: &Engine,
11258 m: &crate::hybrid::MoeWeights,
11259 bits: &crate::hybrid::Gemma4MoeBits,
11260 moe_in: &CudaSlice<f32>,
11261 router_in: &CudaSlice<f32>,
11262 t: usize,
11263 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11264 let cfg = &self.cfg;
11265 let moe = cfg.moe.as_ref().unwrap();
11266 let n_embd = cfg.n_embd as usize;
11267 let n_expert = moe.expert_count as usize;
11268 let n_used = moe.expert_used_count as usize;
11269 let n_ff_exp = moe.expert_ff_length as usize;
11270
11271 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
11275 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11276 } else {
11277 e.matmul(&m.gate_inp, router_in, t)?
11278 };
11279
11280 if t < PRIME_MIN_T
11285 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11286 && expert_dp4a_supported(m.gate_exps.qtype)
11287 && expert_dp4a_supported(m.up_exps.qtype)
11288 && expert_dp4a_supported(m.down_exps.qtype)
11289 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11290 {
11291 let dev = m.dev_exps.as_ref().unwrap();
11292 let (sel_d, w_d) =
11293 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11294 if t == 1 {
11295 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
11296 let selv = sel_d.slice(0..n_used);
11297 let wv = w_d.slice(0..n_used);
11298 let act = e.moe_gate_up_gelu8_dev_q8(
11299 &dev.ptr_row,
11300 &selv,
11301 &zq,
11302 &zd,
11303 n_embd,
11304 n_ff_exp,
11305 n_used,
11306 n_expert,
11307 m.gate_exps.qtype,
11308 m.up_exps.qtype,
11309 m.gate_exps.row_bytes,
11310 m.up_exps.row_bytes,
11311 )?;
11312 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11313 let mut moe_out = e.uninit(n_embd)?;
11314 e.moe_down8_fma_dev_q8(
11315 &dev.ptr_row,
11316 &selv,
11317 &wv,
11318 &aq2,
11319 &ad2,
11320 &mut moe_out.slice_mut(0..n_embd),
11321 n_ff_exp,
11322 n_embd,
11323 n_used,
11324 n_expert,
11325 m.down_exps.qtype,
11326 m.down_exps.row_bytes,
11327 )?;
11328 return Ok(moe_out);
11329 }
11330 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11335 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11336 let act = if csr {
11337 e.moe_gate_up_gelu8_dev_q8_csr(
11338 &dev.ptr_row,
11339 &sel_d,
11340 &zq,
11341 &zd,
11342 t * n_used,
11343 n_embd,
11344 n_ff_exp,
11345 n_used,
11346 n_expert,
11347 m.gate_exps.qtype,
11348 m.up_exps.qtype,
11349 m.gate_exps.row_bytes,
11350 m.up_exps.row_bytes,
11351 )?
11352 } else {
11353 e.moe_gate_up_gelu8_dev_q8_rows(
11354 &dev.ptr_row,
11355 &sel_d,
11356 &zq,
11357 &zd,
11358 t,
11359 n_embd,
11360 n_ff_exp,
11361 n_used,
11362 n_expert,
11363 m.gate_exps.qtype,
11364 m.up_exps.qtype,
11365 m.gate_exps.row_bytes,
11366 m.up_exps.row_bytes,
11367 )?
11368 };
11369 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11370 let mut moe_out = e.uninit(t * n_embd)?;
11371 e.moe_down8_fma_dev_q8_rows_g(
11372 &dev.ptr_row,
11373 &sel_d,
11374 &w_d,
11375 &aq2,
11376 &ad2,
11377 &mut moe_out,
11378 t,
11379 n_ff_exp,
11380 n_embd,
11381 n_used,
11382 n_expert,
11383 m.down_exps.qtype,
11384 m.down_exps.row_bytes,
11385 )?;
11386 return Ok(moe_out);
11387 }
11388
11389 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
11390 for (i, &sx) in sel_all.iter().enumerate() {
11391 w_all[i] *= bits.per_expert_scale[sx as usize];
11392 }
11393
11394 if t >= PRIME_MIN_T
11398 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11399 && expert_dp4a_supported(m.gate_exps.qtype)
11400 && expert_dp4a_supported(m.up_exps.qtype)
11401 && expert_dp4a_supported(m.down_exps.qtype)
11402 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
11403 {
11404 let dev = m.dev_exps.as_ref().unwrap();
11405 let n_pairs = t * n_used;
11406 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
11407 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
11408 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
11409 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
11410 let pt = e.htod_i32(&pair_tok)?;
11411 let pw = e.htod(&w_all)?;
11412 let toff = e.htod_i32(&tok_off)?;
11413 let tids = e.htod_i32(&tok_ids)?;
11414 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11415 for p in 0..n_pairs {
11416 by_ex[pair_ex[p] as usize].push(p as i32);
11417 }
11418 let mut ex_ids: Vec<i32> = Vec::new();
11419 let mut ex_off: Vec<i32> = vec![0];
11420 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
11421 for (ex, list) in by_ex.iter().enumerate() {
11422 if list.is_empty() {
11423 continue;
11424 }
11425 ex_ids.push(ex as i32);
11426 ex_pairs.extend_from_slice(list);
11427 ex_off.push(ex_pairs.len() as i32);
11428 }
11429 let n_active = ex_ids.len();
11430 let exi = e.htod_i32(&ex_ids)?;
11431 let exo = e.htod_i32(&ex_off)?;
11432 let exp_d = e.htod_i32(&ex_pairs)?;
11433 if crate::moe_f16g_gemma_on()
11441 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
11442 && f16g_proj_ok(m.up_exps.qtype, n_embd)
11443 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
11444 {
11445 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11446 let csr_tok_d = e.htod_i32(&csr_tok)?;
11447 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
11448 let g_csr = e.moe_f16_grouped(
11449 &dev.ptr_row,
11450 0,
11451 n_expert,
11452 &exi,
11453 &ex_off,
11454 &exo,
11455 &z_f16,
11456 &z_s,
11457 n_embd,
11458 n_ff_exp,
11459 n_active,
11460 n_pairs,
11461 m.gate_exps.qtype,
11462 m.gate_exps.row_bytes,
11463 )?;
11464 let u_csr = e.moe_f16_grouped(
11465 &dev.ptr_row,
11466 1,
11467 n_expert,
11468 &exi,
11469 &ex_off,
11470 &exo,
11471 &z_f16,
11472 &z_s,
11473 n_embd,
11474 n_ff_exp,
11475 n_active,
11476 n_pairs,
11477 m.up_exps.qtype,
11478 m.up_exps.row_bytes,
11479 )?;
11480 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
11481 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
11482 let d_csr = e.moe_f16_grouped(
11483 &dev.ptr_row,
11484 2,
11485 n_expert,
11486 &exi,
11487 &ex_off,
11488 &exo,
11489 &a_f16,
11490 &a_s,
11491 n_ff_exp,
11492 n_embd,
11493 n_active,
11494 n_pairs,
11495 m.down_exps.qtype,
11496 m.down_exps.row_bytes,
11497 )?;
11498 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
11499 let mut moe_out = e.uninit(t * n_embd)?;
11500 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11501 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
11502 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
11503 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
11504 eprintln!(
11505 "[f16g-debug] post-permute bad={} post-scatter bad={}",
11506 scan(&yd),
11507 scan(&mo)
11508 );
11509 }
11510 return Ok(moe_out);
11511 }
11512 let mma =
11515 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
11516 let (gate, up) = if mma {
11517 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
11518 (
11519 e.mmq_iq_experts(
11520 &dev.ptr_row,
11521 0,
11522 n_expert,
11523 &exi,
11524 &exo,
11525 &exp_d,
11526 &pt,
11527 &z_scr,
11528 n_embd,
11529 n_ff_exp,
11530 n_active,
11531 n_pairs,
11532 t,
11533 m.gate_exps.qtype,
11534 m.gate_exps.row_bytes,
11535 )?,
11536 e.mmq_iq_experts(
11537 &dev.ptr_row,
11538 1,
11539 n_expert,
11540 &exi,
11541 &exo,
11542 &exp_d,
11543 &pt,
11544 &z_scr,
11545 n_embd,
11546 n_ff_exp,
11547 n_active,
11548 n_pairs,
11549 t,
11550 m.up_exps.qtype,
11551 m.up_exps.row_bytes,
11552 )?,
11553 )
11554 } else {
11555 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11556 (
11557 e.moe_pairs_matvec_q8_dec(
11558 &dev.ptr_row,
11559 0,
11560 &exi,
11561 &exo,
11562 &exp_d,
11563 &pt,
11564 &zq,
11565 &zd,
11566 n_embd,
11567 n_ff_exp,
11568 n_expert,
11569 n_active,
11570 n_pairs,
11571 m.gate_exps.qtype,
11572 m.gate_exps.row_bytes,
11573 )?,
11574 e.moe_pairs_matvec_q8_dec(
11575 &dev.ptr_row,
11576 1,
11577 &exi,
11578 &exo,
11579 &exp_d,
11580 &pt,
11581 &zq,
11582 &zd,
11583 n_embd,
11584 n_ff_exp,
11585 n_expert,
11586 n_active,
11587 n_pairs,
11588 m.up_exps.qtype,
11589 m.up_exps.row_bytes,
11590 )?,
11591 )
11592 };
11593 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
11594 let pself = e.htod_i32(&pair_self)?;
11595 let y_down = if mma {
11607 let in_pad = n_ff_exp.div_ceil(256) * 256;
11608 let a_scr = if crate::moe_fuse_actq_on() {
11609 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
11610 } else {
11611 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11612 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
11613 };
11614 e.mmq_iq_experts(
11615 &dev.ptr_row,
11616 2,
11617 n_expert,
11618 &exi,
11619 &exo,
11620 &exp_d,
11621 &pself,
11622 &a_scr,
11623 in_pad,
11624 n_embd,
11625 n_active,
11626 n_pairs,
11627 n_pairs,
11628 m.down_exps.qtype,
11629 m.down_exps.row_bytes,
11630 )?
11631 } else {
11632 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11633 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
11634 e.moe_pairs_matvec_q8_dec(
11635 &dev.ptr_row,
11636 2,
11637 &exi,
11638 &exo,
11639 &exp_d,
11640 &pself,
11641 &aq2,
11642 &ad2,
11643 n_ff_exp,
11644 n_embd,
11645 n_expert,
11646 n_active,
11647 n_pairs,
11648 m.down_exps.qtype,
11649 m.down_exps.row_bytes,
11650 )?
11651 };
11652 let mut moe_out = e.uninit(t * n_embd)?;
11653 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11654 return Ok(moe_out);
11655 }
11656
11657 let g_len = m.gate_exps.expert_stride;
11658 let u_len = m.up_exps.expert_stride;
11659 let d_len = m.down_exps.expert_stride;
11660 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
11664 let (mut sg, mut su, mut sd) = if dev.is_some() {
11665 (None, None, None)
11666 } else {
11667 (
11668 Some(e.alloc_u8_uninit(g_len)?),
11669 Some(e.alloc_u8_uninit(u_len)?),
11670 Some(e.alloc_u8_uninit(d_len)?),
11671 )
11672 };
11673 let mut moe_out = e.zeros(t * n_embd)?;
11674 for tok in 0..t {
11675 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
11676 let w = &w_all[tok * n_used..(tok + 1) * n_used];
11677 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
11678 for (j, &ex) in sel.iter().enumerate() {
11679 let ex = ex as usize;
11680 let gate = match dev {
11681 Some(d) => e.qmatvec_view(
11682 &d.gate,
11683 ex * g_len..(ex + 1) * g_len,
11684 &zt,
11685 1,
11686 m.gate_exps.in_f,
11687 m.gate_exps.out_f,
11688 m.gate_exps.qtype,
11689 m.gate_exps.row_bytes,
11690 )?,
11691 None => {
11692 let sg = sg.as_mut().unwrap();
11693 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
11694 e.qmatvec_view(
11695 sg,
11696 0..g_len,
11697 &zt,
11698 1,
11699 m.gate_exps.in_f,
11700 m.gate_exps.out_f,
11701 m.gate_exps.qtype,
11702 m.gate_exps.row_bytes,
11703 )?
11704 }
11705 };
11706 let up = match dev {
11707 Some(d) => e.qmatvec_view(
11708 &d.up,
11709 ex * u_len..(ex + 1) * u_len,
11710 &zt,
11711 1,
11712 m.up_exps.in_f,
11713 m.up_exps.out_f,
11714 m.up_exps.qtype,
11715 m.up_exps.row_bytes,
11716 )?,
11717 None => {
11718 let su = su.as_mut().unwrap();
11719 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
11720 e.qmatvec_view(
11721 su,
11722 0..u_len,
11723 &zt,
11724 1,
11725 m.up_exps.in_f,
11726 m.up_exps.out_f,
11727 m.up_exps.qtype,
11728 m.up_exps.row_bytes,
11729 )?
11730 }
11731 };
11732 let mut act = e.uninit(n_ff_exp)?;
11733 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
11734 let actv = act.slice(0..n_ff_exp);
11735 let y = match dev {
11736 Some(d) => e.qmatvec_view(
11737 &d.down,
11738 ex * d_len..(ex + 1) * d_len,
11739 &actv,
11740 1,
11741 m.down_exps.in_f,
11742 m.down_exps.out_f,
11743 m.down_exps.qtype,
11744 m.down_exps.row_bytes,
11745 )?,
11746 None => {
11747 let sd = sd.as_mut().unwrap();
11748 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
11749 e.qmatvec_view(
11750 sd,
11751 0..d_len,
11752 &actv,
11753 1,
11754 m.down_exps.in_f,
11755 m.down_exps.out_f,
11756 m.down_exps.qtype,
11757 m.down_exps.row_bytes,
11758 )?
11759 }
11760 };
11761 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
11762 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
11763 }
11764 }
11765 Ok(moe_out)
11766 }
11767
11768 fn gemma4_layer(
11770 &self,
11771 e: &Engine,
11772 il: usize,
11773 layer: &crate::hybrid::HybridLayer,
11774 x: &CudaSlice<f32>,
11775 pos_d: &CudaSlice<i32>,
11776 t: usize,
11777 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11778 let n_embd = self.cfg.n_embd as usize;
11779 let eps = self.cfg.rms_eps;
11780
11781 let mut h = e.zeros(t * n_embd)?;
11782 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
11783 let Mixer::Full(fa) = &layer.mixer else {
11784 panic!("gemma4 layer {il} not full-attn")
11785 };
11786 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
11787 let mut cur = e.zeros(t * n_embd)?;
11789 e.rms_norm(
11790 &o,
11791 layer.post_attn_norm.float_data(),
11792 &mut cur,
11793 n_embd,
11794 t,
11795 eps,
11796 )?;
11797 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
11798 }
11799
11800 fn gemma4_layer_tail_add(
11804 &self,
11805 e: &Engine,
11806 layer: &crate::hybrid::HybridLayer,
11807 cur: &CudaSlice<f32>,
11808 x: &CudaSlice<f32>,
11809 t: usize,
11810 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11811 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
11812 }
11813
11814 fn gemma4_layer_tail_add_n(
11817 &self,
11818 e: &Engine,
11819 layer: &crate::hybrid::HybridLayer,
11820 cur: &CudaSlice<f32>,
11821 x: &CudaSlice<f32>,
11822 t: usize,
11823 next_norm: Option<&CudaSlice<f32>>,
11824 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
11825 let n_embd = self.cfg.n_embd as usize;
11826 let bits = layer.gemma4.as_ref().unwrap();
11827 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
11828 let mut xn = e.uninit(t * n_embd)?;
11829 match next_norm {
11830 Some(w) => {
11831 let mut hn = e.uninit(t * n_embd)?;
11832 e.add_scale_rms_norm(
11833 &sn,
11834 &attn_out,
11835 bits.layer_scale,
11836 w,
11837 &mut xn,
11838 &mut hn,
11839 n_embd,
11840 t,
11841 self.cfg.rms_eps,
11842 )?;
11843 Ok((xn, Some(hn)))
11844 }
11845 None => {
11846 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
11847 Ok((xn, None))
11848 }
11849 }
11850 }
11851
11852 fn gemma4_layer_tail_core(
11855 &self,
11856 e: &Engine,
11857 layer: &crate::hybrid::HybridLayer,
11858 cur: &CudaSlice<f32>,
11859 x: &CudaSlice<f32>,
11860 t: usize,
11861 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11862 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
11863 }
11864
11865 fn gemma4_layer_tail_core_pn(
11872 &self,
11873 e: &Engine,
11874 layer: &crate::hybrid::HybridLayer,
11875 cur: &CudaSlice<f32>,
11876 x: &CudaSlice<f32>,
11877 t: usize,
11878 pre_norm: Option<&CudaSlice<f32>>,
11879 defer_post_norm: bool,
11880 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11881 let n_embd = self.cfg.n_embd as usize;
11882 let eps = self.cfg.rms_eps;
11883 let bits = layer.gemma4.as_ref().unwrap();
11884
11885 let Some(mbits) = bits.moe_bits.as_ref() else {
11888 let crate::hybrid::Ffn::Dense {
11889 ffn_gate,
11890 ffn_up,
11891 ffn_down,
11892 } = &layer.ffn
11893 else {
11894 panic!("gemma4 dense layer without Dense ffn")
11895 };
11896 let mut attn_out = e.uninit(t * n_embd)?;
11897 let mut zsh = e.uninit(t * n_embd)?;
11898 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11901 match pre_norm {
11902 Some(wa) if t == 1 => {
11903 zpair = Some(e.rms_pre_add_rms_norm_q8z(
11904 cur,
11905 wa,
11906 x,
11907 bits.ffn_norm.float_data(),
11908 &mut attn_out,
11909 &mut zsh,
11910 n_embd,
11911 t,
11912 eps,
11913 )?);
11914 }
11915 Some(wa) => e.rms_pre_add_rms_norm(
11916 cur,
11917 wa,
11918 x,
11919 bits.ffn_norm.float_data(),
11920 &mut attn_out,
11921 &mut zsh,
11922 n_embd,
11923 t,
11924 eps,
11925 )?,
11926 None => e.add_rms_norm(
11927 cur,
11928 x,
11929 bits.ffn_norm.float_data(),
11930 &mut attn_out,
11931 &mut zsh,
11932 n_embd,
11933 t,
11934 eps,
11935 )?,
11936 }
11937 let n_ff = ffn_gate.out_features();
11938 let (gate, up) = if t == 1 {
11944 let (zq, zd) = match zpair {
11945 Some(p) => p,
11946 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
11947 };
11948 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
11949 Some(p) => p,
11950 None => match e.matmul_nvfp4_fused2(ffn_gate, ffn_up, &zq, &zd, 1)? {
11952 Some(p) => p,
11953 None => (
11954 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
11955 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
11956 ),
11957 },
11958 }
11959 } else {
11960 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11965 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11966 let fused = if f2b {
11967 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
11968 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
11969 } else {
11970 None
11971 };
11972 match fused {
11973 Some(p) => p,
11974 None => {
11975 e.mmq_act_begin();
11977 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
11978 }
11979 }
11980 };
11981 let mut act = e.uninit(t * n_ff)?;
11982 let f0 = if e.uses_q8_1_fast(ffn_down) {
11985 let upv = e.view(&up, t * n_ff);
11986 let up_all = upv.slice(0..t * n_ff);
11987 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
11988 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
11989 } else {
11990 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11991 e.matmul(ffn_down, &act, t)?
11992 };
11993 if defer_post_norm {
11994 return Ok((f0, attn_out));
11995 }
11996 let mut sn = e.uninit(t * n_embd)?;
11997 e.rms_norm(
11998 &f0,
11999 bits.post_ffw_norm.float_data(),
12000 &mut sn,
12001 n_embd,
12002 t,
12003 eps,
12004 )?;
12005 return Ok((sn, attn_out));
12006 };
12007
12008 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
12009 let mut attn_out = e.uninit(t * n_embd)?;
12014 let mut router_in = e.uninit(t * n_embd)?;
12015 let fast_moe = match &layer.ffn {
12016 crate::hybrid::Ffn::Moe(m) => {
12017 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
12018 && expert_dp4a_supported(m.gate_exps.qtype)
12019 && expert_dp4a_supported(m.up_exps.qtype)
12020 && expert_dp4a_supported(m.down_exps.qtype)
12021 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
12022 }
12023 _ => false,
12024 };
12025 let q8z = t < PRIME_MIN_T && fast_moe;
12026 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
12027 let (z0, m2) = e.add_rms_norm3_q8z(
12028 cur,
12029 x,
12030 bits.ffn_norm.float_data(),
12031 &mbits.router_scale_pre,
12032 mbits.pre_ffw_norm_2.float_data(),
12033 &mut attn_out,
12034 &mut router_in,
12035 n_embd,
12036 t,
12037 eps,
12038 )?;
12039 (None, Some(z0), Some(m2))
12040 } else {
12041 let mut zsh = e.uninit(t * n_embd)?;
12042 let mut moe_in = e.uninit(t * n_embd)?;
12043 e.add_rms_norm3(
12044 cur,
12045 x,
12046 bits.ffn_norm.float_data(),
12047 &mbits.router_scale_pre,
12048 mbits.pre_ffw_norm_2.float_data(),
12049 &mut attn_out,
12050 &mut zsh,
12051 &mut router_in,
12052 &mut moe_in,
12053 n_embd,
12054 t,
12055 eps,
12056 )?;
12057 (Some((zsh, moe_in)), None, None)
12058 };
12059 let attn_out2 = attn_out;
12060 #[allow(unused_variables)]
12061 let attn_out = &attn_out2;
12062 let n_ff = mbits.shared_gate.out_features();
12063 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
12064 if t == 1 {
12065 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
12066 Some(p) => p,
12067 None => match e.matmul_nvfp4_fused2(
12068 &mbits.shared_gate,
12069 &mbits.shared_up,
12070 zq,
12071 zd,
12072 1,
12073 )? {
12074 Some(p) => p,
12075 None => {
12076 let h0 = e.zeros(0)?;
12077 (
12078 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
12079 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
12080 )
12081 }
12082 },
12083 }
12084 } else {
12085 let h0 = e.zeros(0)?;
12087 (
12088 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
12089 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
12090 )
12091 }
12092 } else {
12093 let (zsh, _) = zsh_f32.as_ref().unwrap();
12094 (
12095 e.matmul(&mbits.shared_gate, zsh, t)?,
12096 e.matmul(&mbits.shared_up, zsh, t)?,
12097 )
12098 };
12099 let mut act = e.uninit(t * n_ff)?;
12100 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
12101 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
12102 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
12103 panic!("gemma4 layer not MoE")
12104 };
12105 let moe0 = match (&moe_q8, &zsh_f32) {
12106 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
12107 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
12108 _ => unreachable!(),
12109 };
12110 let mut mlp = e.uninit(t * n_embd)?;
12112 let mut moe = e.uninit(t * n_embd)?;
12113 e.rms_norm2x(
12114 &mlp0,
12115 &moe0,
12116 mbits.post_ffw_norm_1.float_data(),
12117 mbits.post_ffw_norm_2.float_data(),
12118 &mut mlp,
12119 &mut moe,
12120 n_embd,
12121 t,
12122 eps,
12123 )?;
12124
12125 let mut sum = e.uninit(t * n_embd)?;
12128 let mut sn = e.uninit(t * n_embd)?;
12129 e.add_rms_norm(
12130 &mlp,
12131 &moe,
12132 bits.post_ffw_norm.float_data(),
12133 &mut sum,
12134 &mut sn,
12135 n_embd,
12136 t,
12137 eps,
12138 )?;
12139 Ok((sn, attn_out2))
12140 }
12141
12142 pub(crate) fn gemma4_layer_tail_add_nq_pn(
12152 &self,
12153 e: &Engine,
12154 layer: &crate::hybrid::HybridLayer,
12155 o: &CudaSlice<f32>,
12156 x: &CudaSlice<f32>,
12157 t: usize,
12158 next_norm: Option<&CudaSlice<f32>>,
12159 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12160 {
12161 let n_embd = self.cfg.n_embd as usize;
12162 let eps = self.cfg.rms_eps;
12163 let bits = layer.gemma4.as_ref().unwrap();
12164 if Engine::g4_pnfold_on() && matches!(layer.ffn, crate::hybrid::Ffn::Dense { .. }) {
12165 let (f0, attn_out) = self.gemma4_layer_tail_core_pn(
12166 e,
12167 layer,
12168 o,
12169 x,
12170 t,
12171 Some(layer.post_attn_norm.float_data()),
12172 true,
12173 )?;
12174 let mut xn = e.uninit(t * n_embd)?;
12175 return match next_norm {
12176 Some(w) => {
12177 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
12178 &f0,
12179 bits.post_ffw_norm.float_data(),
12180 &attn_out,
12181 bits.layer_scale,
12182 w,
12183 &mut xn,
12184 n_embd,
12185 t,
12186 eps,
12187 )?;
12188 Ok((xn, Some(pair)))
12189 }
12190 None => {
12191 let mut sn = e.uninit(t * n_embd)?;
12192 e.rms_norm(
12193 &f0,
12194 bits.post_ffw_norm.float_data(),
12195 &mut sn,
12196 n_embd,
12197 t,
12198 eps,
12199 )?;
12200 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12201 Ok((xn, None))
12202 }
12203 };
12204 }
12205 let mut cur = e.uninit(t * n_embd)?;
12206 e.rms_norm(
12207 o,
12208 layer.post_attn_norm.float_data(),
12209 &mut cur,
12210 n_embd,
12211 t,
12212 eps,
12213 )?;
12214 self.gemma4_layer_tail_add_nq(e, layer, &cur, x, t, next_norm)
12215 }
12216
12217 pub(crate) fn gemma4_layer_tail_add_nq(
12218 &self,
12219 e: &Engine,
12220 layer: &crate::hybrid::HybridLayer,
12221 cur: &CudaSlice<f32>,
12222 x: &CudaSlice<f32>,
12223 t: usize,
12224 next_norm: Option<&CudaSlice<f32>>,
12225 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12226 {
12227 let n_embd = self.cfg.n_embd as usize;
12228 let bits = layer.gemma4.as_ref().unwrap();
12229 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12230 let mut xn = e.uninit(t * n_embd)?;
12231 match next_norm {
12232 Some(w) => {
12233 let pair = e.add_scale_rms_norm_q8_1(
12234 &sn,
12235 &attn_out,
12236 bits.layer_scale,
12237 w,
12238 &mut xn,
12239 n_embd,
12240 t,
12241 self.cfg.rms_eps,
12242 )?;
12243 Ok((xn, Some(pair)))
12244 }
12245 None => {
12246 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12247 Ok((xn, None))
12248 }
12249 }
12250 }
12251
12252 fn gemma4_forward(
12255 &self,
12256 e: &Engine,
12257 tokens: &[u32],
12258 last_only: bool,
12259 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12260 if self.is_gemma4_e4b() {
12263 return self.gemma4_e4b_forward(e, tokens, last_only);
12264 }
12265 let n_embd = self.cfg.n_embd as usize;
12266 let t = tokens.len();
12267 let pos: Vec<i32> = (0..t as i32).collect();
12268 let pos_d = e.htod_i32(&pos)?;
12269
12270 let mut x = self.embed(e, tokens)?;
12271 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12272 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
12275 let stat =
12276 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
12277 let h = e.dtoh(x)?;
12278 let bad = h.iter().filter(|v| !v.is_finite()).count();
12279 let mx = h
12280 .iter()
12281 .filter(|v| v.is_finite())
12282 .fold(0.0f32, |m, v| m.max(v.abs()));
12283 eprintln!(
12284 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
12285 &h[..3]
12286 );
12287 Ok(())
12288 };
12289 if probe {
12290 stat(e, &x, "embed")?;
12291 }
12292 for (il, layer) in self.layers.iter().enumerate() {
12293 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
12294 if probe {
12295 stat(e, &x, &format!("L{il}"))?;
12296 }
12297 }
12298 let mut hn = e.zeros(t * n_embd)?;
12299 e.rms_norm(
12300 &x,
12301 self.output_norm.float_data(),
12302 &mut hn,
12303 n_embd,
12304 t,
12305 self.cfg.rms_eps,
12306 )?;
12307 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12308 let n_vocab = self.output.out_features();
12309 let logits = if last_only {
12310 let hv = e.view(&hn, t * n_embd);
12311 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
12312 let mut hlast = e.zeros(n_embd)?;
12313 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
12314 let mut ld = e.matmul(&self.output, &hlast, 1)?;
12315 e.softcap(&mut ld, cap, n_vocab)?;
12316 self.gemma4_suppress(e, &mut ld, 1)?;
12317 e.dtoh(&ld)?
12318 } else {
12319 let mut ld = e.matmul(&self.output, &hn, t)?;
12320 e.softcap(&mut ld, cap, t * n_vocab)?;
12321 self.gemma4_suppress(e, &mut ld, t)?;
12322 e.dtoh(&ld)?
12323 };
12324 Ok(logits)
12325 }
12326
12327 pub(crate) fn gemma4_prime(
12332 &self,
12333 e: &Engine,
12334 tokens: &[u32],
12335 cache: &mut Cache,
12336 overlay: Option<&crate::vision::EmbedOverlay>,
12337 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12338 if cache.pos != 0 {
12343 return Err(
12344 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
12345 — prime the full prompt in one call or decode tokenwise"
12346 .into(),
12347 );
12348 }
12349 let n_embd = self.cfg.n_embd as usize;
12350 let eps = self.cfg.rms_eps;
12351 let t = tokens.len();
12352 let pos: Vec<i32> = (0..t as i32).collect();
12353 let pos_d = e.htod_i32(&pos)?;
12354 let mut x = self.embed(e, tokens)?;
12355 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12356 let island: Option<CudaSlice<i32>> = match overlay {
12363 Some(ov) => {
12364 let mut span_id = vec![-1i32; t];
12365 for (i, &(pos, row_off, n_rows)) in ov.spans.iter().enumerate() {
12366 if pos + n_rows > t {
12367 return Err(format!(
12368 "gemma4 overlay span {i} [{pos}, {}) exceeds the prompt ({t})",
12369 pos + n_rows
12370 )
12371 .into());
12372 }
12373 let view = ov.rows.slice(row_off * n_embd..(row_off + n_rows) * n_embd);
12374 e.copy_view_into(&mut x, pos * n_embd, &view, n_rows * n_embd)?;
12375 for s in span_id.iter_mut().skip(pos).take(n_rows) {
12376 *s = i as i32;
12377 }
12378 }
12379 if std::env::var("MEMRA_GV_FORCE_CAUSAL").as_deref() == Ok("1") {
12383 None
12384 } else {
12385 Some(e.htod_i32(&span_id)?)
12386 }
12387 }
12388 None => None,
12389 };
12390 for (il, layer) in self.layers.iter().enumerate() {
12391 let mut h = e.zeros(t * n_embd)?;
12392 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12393 let Mixer::Full(fa) = &layer.mixer else {
12394 panic!("gemma4 layer not full-attn")
12395 };
12396 let trace = il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1");
12397 if trace {
12398 let v = e.dtoh(&h)?;
12399 let nan = v.iter().filter(|x| x.is_nan()).count();
12400 eprintln!("[g4-prime-trace] L0 post-attn_norm: nan={nan}/{}", v.len());
12401 }
12402 let o =
12403 self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache), island.as_ref())?;
12404 if trace {
12405 let v = e.dtoh(&o)?;
12406 let nan = v.iter().filter(|x| x.is_nan()).count();
12407 eprintln!("[g4-prime-trace] L0 post-attn: nan={nan}/{}", v.len());
12408 }
12409 let mut cur = e.zeros(t * n_embd)?;
12410 e.rms_norm(
12411 &o,
12412 layer.post_attn_norm.float_data(),
12413 &mut cur,
12414 n_embd,
12415 t,
12416 eps,
12417 )?;
12418 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
12419 self.dflash_tap(e, cache, il, &x, t)?;
12420 if std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
12422 let h = e.dtoh(&x)?;
12423 let nan = h.iter().filter(|v| v.is_nan()).count();
12424 let amax = h.iter().fold(0f32, |a, v| a.max(v.abs()));
12425 eprintln!(
12426 "[g4-prime-trace] layer {il}: nan={nan}/{} amax={amax:.3}",
12427 h.len()
12428 );
12429 if nan > 0 {
12430 return Err(format!("g4-prime-trace: first NaN at layer {il}").into());
12431 }
12432 }
12433 }
12434 cache.pos += t;
12435 let hiddens = e.clone_dtod(&x)?;
12436 let xv = e.view(&x, t * n_embd);
12437 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
12438 let mut h_seed = e.zeros(n_embd)?;
12439 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
12440 let mut hn = e.uninit(n_embd)?;
12441 e.rms_norm(
12442 &h_seed,
12443 self.output_norm.float_data(),
12444 &mut hn,
12445 n_embd,
12446 1,
12447 eps,
12448 )?;
12449 let mut ld = e.matmul(&self.output, &hn, 1)?;
12450 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12451 e.softcap(&mut ld, cap, self.output.out_features())?;
12452 self.gemma4_suppress(e, &mut ld, 1)?;
12453 let logits = e.dtoh(&ld)?;
12454 Ok((logits, h_seed, hiddens))
12455 }
12456
12457 fn gemma4_decode_attn(
12462 &self,
12463 e: &Engine,
12464 fa: &crate::hybrid::FullAttnLayer,
12465 il: usize,
12466 hq: &CudaSlice<i8>,
12467 hdq: &CudaSlice<f32>,
12468 pos_d: &CudaSlice<i32>,
12469 cache: &mut Cache,
12470 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12471 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12472 let eps = self.cfg.rms_eps;
12473 let aux = self.gemma4_aux.as_ref().unwrap();
12474 let ones = aux.ones(e);
12475 #[cfg(debug_assertions)]
12476 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
12477 let (hq, hdq) = (hq, hdq);
12478 let h0 = e.zeros(0)?;
12479 let h = &h0;
12480 let (q0, k0, v0) = if swa {
12481 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12482 Some(t3) => t3,
12483 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12486 Some((q0, k0)) => {
12487 let v0 = e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?;
12488 (q0, k0, v0)
12489 }
12490 None => (
12491 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12492 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12493 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12494 ),
12495 },
12496 }
12497 } else {
12498 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
12499 Some(p) => p,
12500 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12501 Some(p) => p,
12502 None => (
12503 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12504 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12505 ),
12506 },
12507 };
12508 let v0 = e.clone_dtod(&k0)?;
12509 (q0, k0, v0)
12510 };
12511 let mut q = e.uninit(nh * hd)?;
12512 let mut k = e.uninit(nkv * hd)?;
12513 let mut v = e.uninit(nkv * hd)?;
12514 let ff = if swa {
12517 None
12518 } else {
12519 Some(
12520 aux.rope_freqs(e)
12521 .expect("gemma4 global rope needs rope_freqs.weight"),
12522 )
12523 };
12524 #[cfg(debug_assertions)]
12525 if let Some(ff) = ff {
12526 crate::debug_assert_tensor_stream_device(
12527 ff,
12528 &e.stream(),
12529 "gemma4_decode_attn.rope_freqs",
12530 );
12531 }
12532 let kvl = cache.kv[il].as_mut().unwrap();
12533 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12534 if crate::Engine::qkv_append_on() {
12535 e.rms_norm_qkv_rope_append(
12539 &q0,
12540 &k0,
12541 &v0,
12542 fa.q_norm.float_data(),
12543 fa.k_norm.float_data(),
12544 ones,
12545 &mut q,
12546 &mut k,
12547 &mut v,
12548 hd,
12549 self.gemma4_rope_dims(il),
12550 nh,
12551 nkv,
12552 pos_d,
12553 nh,
12554 nkv,
12555 base,
12556 1.0,
12557 ff,
12558 eps,
12559 &mut kvl.k,
12560 &mut kvl.v,
12561 kvl.len,
12562 kvl.k_tok_bytes,
12563 kvl.v_tok_bytes,
12564 kv_fp8,
12565 )?;
12566 } else {
12567 e.rms_norm_qkv_rope(
12568 &q0,
12569 &k0,
12570 &v0,
12571 fa.q_norm.float_data(),
12572 fa.k_norm.float_data(),
12573 ones,
12574 &mut q,
12575 &mut k,
12576 &mut v,
12577 hd,
12578 self.gemma4_rope_dims(il),
12579 nh,
12580 nkv,
12581 pos_d,
12582 nh,
12583 nkv,
12584 base,
12585 1.0,
12586 ff,
12587 eps,
12588 )?;
12589 e.append_kv_quantized(
12590 &k,
12591 &v,
12592 &mut kvl.k,
12593 &mut kvl.v,
12594 kvl.len,
12595 kvl.kv_dim_k,
12596 kvl.kv_dim_v,
12597 kvl.k_tok_bytes,
12598 kvl.v_tok_bytes,
12599 kv_fp8,
12600 )?;
12601 }
12602 kvl.len += 1;
12603 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12607 let mut attn = e.uninit(nh * hd)?;
12608 if !swa
12610 && hd == 512
12611 && kvl.len >= crate::fa512_min_tkv()
12612 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12613 {
12614 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12615 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12616 let base = kvl.len as i32;
12618 e.i32_set_k(&mut kvl.len_d, base)?;
12619 e.fa_decode_rows(
12620 &q,
12621 &kp,
12622 &vp,
12623 &mut attn,
12624 hd,
12625 nh,
12626 nkv,
12627 kvl.len - 1,
12628 1,
12629 scale,
12630 kvl.k_tok_bytes,
12631 kvl.v_tok_bytes,
12632 Some((&kvl.len_d, -1)),
12633 false,
12634 false,
12635 None,
12636 )?;
12637 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12638 }
12639 if swa
12641 && kvl.len > win
12642 && hd == 256
12643 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12644 {
12645 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12646 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12647 let base = kvl.len as i32;
12648 e.i32_set_k(&mut kvl.len_d, base)?;
12649 e.fa_decode_rows_w(
12650 &q,
12651 &kp,
12652 &vp,
12653 &mut attn,
12654 hd,
12655 nh,
12656 nkv,
12657 &kvl.len_d,
12658 -1,
12659 1,
12660 scale,
12661 win,
12662 kvl.k_tok_bytes,
12663 kvl.v_tok_bytes,
12664 None,
12665 )?;
12666 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12667 }
12668 let (off_tok, t_kv) = if swa && kvl.len > win {
12669 (kvl.len - win, win)
12670 } else {
12671 (0, kvl.len)
12672 };
12673 let k_view = e.view_u8_range(
12674 &kvl.k,
12675 off_tok * kvl.k_tok_bytes,
12676 (off_tok + t_kv) * kvl.k_tok_bytes,
12677 );
12678 let v_view = e.view_u8_range(
12679 &kvl.v,
12680 off_tok * kvl.v_tok_bytes,
12681 (off_tok + t_kv) * kvl.v_tok_bytes,
12682 );
12683 e.fa_decode_kvmod(
12684 &q,
12685 &k_view,
12686 &v_view,
12687 &mut attn,
12688 hd,
12689 nh,
12690 nkv,
12691 t_kv,
12692 scale,
12693 kvl.k_tok_bytes,
12694 kvl.v_tok_bytes,
12695 swa && crate::Engine::wkv_on(),
12696 )?;
12697 Ok(e.matmul(&fa.wo, &attn, 1)?)
12698 }
12699
12700 #[allow(clippy::too_many_arguments)]
12707 pub fn gemma4_decode_step_dc(
12708 &self,
12709 e: &Engine,
12710 token_d: &CudaSlice<u32>,
12711 pos_d: &mut CudaSlice<i32>,
12712 embd_gpu: &CudaSlice<u8>,
12713 embd_qt: i32,
12714 embd_rb: usize,
12715 cache: &mut Cache,
12716 n_vocab: usize,
12717 cap_bucket_max: Option<(usize, usize)>,
12718 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
12719 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
12720 self.gemma4_decode_step_dc_into(
12721 e,
12722 token_d,
12723 pos_d,
12724 embd_gpu,
12725 embd_qt,
12726 embd_rb,
12727 cache,
12728 n_vocab,
12729 cap_bucket_max,
12730 &mut tok_out,
12731 )?;
12732 Ok(tok_out)
12733 }
12734
12735 #[allow(clippy::too_many_arguments)]
12738 pub fn gemma4_decode_step_dc_into(
12739 &self,
12740 e: &Engine,
12741 token_d: &CudaSlice<u32>,
12742 pos_d: &mut CudaSlice<i32>,
12743 embd_gpu: &CudaSlice<u8>,
12744 embd_qt: i32,
12745 embd_rb: usize,
12746 cache: &mut Cache,
12747 n_vocab: usize,
12748 cap_bucket_max: Option<(usize, usize)>,
12749 tok_out: &mut CudaSlice<u32>,
12750 ) -> Result<(), Box<dyn std::error::Error>> {
12751 let n_embd = self.cfg.n_embd as usize;
12752 let eps = self.cfg.rms_eps;
12753 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
12754 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12755 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12756 let n_layers = self.layers.len();
12757 for (il, layer) in self.layers.iter().enumerate() {
12758 let (hq, hdq) = match h_carry.take() {
12759 Some(p) => p,
12760 None => {
12761 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
12762 }
12763 };
12764 let Mixer::Full(fa) = &layer.mixer else {
12765 panic!("gemma4 layer {il} not full-attn")
12766 };
12767 let o =
12768 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
12769 let next_norm = if il + 1 < n_layers {
12770 Some(self.layers[il + 1].attn_norm.float_data())
12771 } else {
12772 None
12773 };
12774 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
12775 x = xn;
12776 h_carry = hn;
12777 }
12778 let mut hn = e.uninit(n_embd)?;
12779 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12780 let mut logits = e.matmul(&self.output, &hn, 1)?;
12781 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
12783 e.inc_seqlen(pos_d)?;
12784 if cap_bucket_max.is_none() {
12785 cache.pos += 1;
12786 }
12787 Ok(())
12788 }
12789
12790 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
12797 let n_embd = self.cfg.n_embd as usize;
12798 let n_vocab = self.output.out_features();
12799 let n_layers = self.layers.len();
12800 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
12801 for il in 0..n_layers {
12802 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
12803 qmax = qmax.max(nh * hd);
12804 kvmax = kvmax.max(nkv * hd);
12805 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
12806 ffmax = ffmax.max(ffn_gate.out_features());
12807 }
12808 }
12809 Ok(G4DcSlots {
12810 x: e.uninit(n_embd)?,
12811 xn: e.uninit(n_embd)?,
12812 cur: e.uninit(n_embd)?,
12813 hq: e.alloc_i8_uninit(n_embd)?,
12814 hd_: e.uninit(n_embd / 32)?,
12815 q0: e.uninit(qmax)?,
12816 k0: e.uninit(kvmax)?,
12817 v0: e.uninit(kvmax)?,
12818 q: e.uninit(qmax)?,
12819 k: e.uninit(kvmax)?,
12820 v: e.uninit(kvmax)?,
12821 attn: e.uninit(qmax)?,
12822 o: e.uninit(n_embd)?,
12823 attn_out: e.uninit(n_embd)?,
12824 zsh: e.uninit(n_embd)?,
12825 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
12828 zd: e.uninit(n_embd.max(qmax) / 32)?,
12829 gate: e.uninit(ffmax)?,
12830 up: e.uninit(ffmax)?,
12831 act: e.uninit(ffmax)?,
12832 actq: e.alloc_i8_uninit(ffmax)?,
12833 actd: e.uninit(ffmax / 32)?,
12834 f0: e.uninit(n_embd)?,
12835 sn: e.uninit(n_embd)?,
12836 hn: e.uninit(n_embd)?,
12837 logits: e.uninit(n_vocab)?,
12838 })
12839 }
12840
12841 fn g4_matvec_m1_into(
12844 &self,
12845 e: &Engine,
12846 w: &crate::model::GpuTensor,
12847 aq: &CudaSlice<i8>,
12848 ad: &CudaSlice<f32>,
12849 y: &mut CudaSlice<f32>,
12850 ) -> Result<(), Box<dyn std::error::Error>> {
12851 use crate::model::GpuTensor;
12852 let (bytes, qtype, row_bytes, scale, rp) = match w {
12853 GpuTensor::Quant {
12854 bytes,
12855 qtype,
12856 row_bytes,
12857 scale,
12858 rp,
12859 ..
12860 } => (bytes, *qtype, *row_bytes, *scale, *rp),
12861 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
12862 };
12863 let (mbytes, mrp) = match w {
12864 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12865 _ => (bytes, rp),
12866 };
12867 e.qmatvec_mmvq_into(
12868 mbytes,
12869 aq,
12870 ad,
12871 1,
12872 w.in_features(),
12873 w.out_features(),
12874 qtype,
12875 row_bytes,
12876 scale,
12877 mrp,
12878 y,
12879 )
12880 }
12881
12882 #[allow(clippy::too_many_arguments)]
12886 pub fn gemma4_decode_step_dc_slotted(
12887 &self,
12888 e: &Engine,
12889 token_d: &CudaSlice<u32>,
12890 pos_d: &mut CudaSlice<i32>,
12891 embd_gpu: &CudaSlice<u8>,
12892 embd_qt: i32,
12893 embd_rb: usize,
12894 cache: &mut Cache,
12895 n_vocab: usize,
12896 cap_bucket_max: Option<(usize, usize)>,
12897 sl: &mut G4DcSlots,
12898 tok_out: &mut CudaSlice<u32>,
12899 ring: Option<(&mut CudaSlice<u32>, usize)>,
12900 ) -> Result<(), Box<dyn std::error::Error>> {
12901 let n_embd = self.cfg.n_embd as usize;
12902 let eps = self.cfg.rms_eps;
12903 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
12904 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
12905 let n_layers = self.layers.len();
12906 let mut has_carry = false;
12907 for il in 0..n_layers {
12908 if !has_carry {
12909 e.rms_norm_q8_1_into(
12910 &sl.x,
12911 self.layers[il].attn_norm.float_data(),
12912 n_embd,
12913 1,
12914 eps,
12915 &mut sl.hq,
12916 &mut sl.hd_,
12917 )?;
12918 }
12919 has_carry = true;
12920 let layer = &self.layers[il];
12921 let Mixer::Full(fa) = &layer.mixer else {
12922 panic!("gemma4 layer {il} not full-attn")
12923 };
12924 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
12925 if !Engine::g4_pnfold_on() {
12928 e.rms_norm(
12929 &sl.o,
12930 layer.post_attn_norm.float_data(),
12931 &mut sl.cur,
12932 n_embd,
12933 1,
12934 eps,
12935 )?;
12936 }
12937 let next_norm = if il + 1 < n_layers {
12938 Some(self.layers[il + 1].attn_norm.float_data())
12939 } else {
12940 None
12941 };
12942 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
12943 std::mem::swap(&mut sl.x, &mut sl.xn);
12944 }
12945 e.rms_norm(
12946 &sl.x,
12947 self.output_norm.float_data(),
12948 &mut sl.hn,
12949 n_embd,
12950 1,
12951 eps,
12952 )?;
12953 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
12954 {
12956 let (zq, zd) = (&sl.zq, &sl.zd);
12957 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
12958 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
12959 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
12960 }
12961 self.gemma4_suppress(e, &mut sl.logits, 1)?;
12962 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
12963 if let Some((ring, base)) = ring {
12964 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
12968 }
12969 e.inc_seqlen(pos_d)?;
12970 if cap_bucket_max.is_none() {
12971 cache.pos += 1;
12972 }
12973 Ok(())
12974 }
12975
12976 #[allow(clippy::too_many_arguments)]
12978 fn gemma4_decode_attn_dc_slotted(
12979 &self,
12980 e: &Engine,
12981 fa: &crate::hybrid::FullAttnLayer,
12982 il: usize,
12983 pos_d: &CudaSlice<i32>,
12984 cache: &mut Cache,
12985 cap_bucket_max: Option<(usize, usize)>,
12986 sl: &mut G4DcSlots,
12987 ) -> Result<(), Box<dyn std::error::Error>> {
12988 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12989 let eps = self.cfg.rms_eps;
12990 let aux = self.gemma4_aux.as_ref().unwrap();
12991 let ones = aux.ones(e);
12992 #[cfg(debug_assertions)]
12993 crate::debug_assert_tensor_stream_device(
12994 ones,
12995 &e.stream(),
12996 "gemma4_decode_attn_dc_slotted.ones",
12997 );
12998 {
12999 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
13000 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
13001 if swa {
13002 if !e.matmul_q4_fused3_into(
13003 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
13004 )? {
13005 if e.matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
13009 {
13010 self.g4_matvec_m1_into(e, &fa.wv, hq, hdq, &mut sl.v0)?;
13011 } else {
13012 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
13013 }
13014 }
13015 } else {
13016 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
13017 && !e
13018 .matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
13019 {
13020 return Err("slotted step: fused2 unavailable".into());
13021 }
13022 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
13023 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
13024 }
13025 }
13026 let ff = if swa {
13029 None
13030 } else {
13031 Some(
13032 aux.rope_freqs(e)
13033 .expect("gemma4 global rope needs rope_freqs.weight"),
13034 )
13035 };
13036 #[cfg(debug_assertions)]
13037 if let Some(ff) = ff {
13038 crate::debug_assert_tensor_stream_device(
13039 ff,
13040 &e.stream(),
13041 "gemma4_decode_attn_dc_slotted.rope_freqs",
13042 );
13043 }
13044 let kvl = cache.kv[il].as_mut().unwrap();
13045 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13046 if crate::Engine::qkv_append_on() {
13047 e.rms_norm_qkv_rope_append_dc(
13049 &sl.q0,
13050 &sl.k0,
13051 &sl.v0,
13052 fa.q_norm.float_data(),
13053 fa.k_norm.float_data(),
13054 ones,
13055 &mut sl.q,
13056 &mut sl.k,
13057 &mut sl.v,
13058 hd,
13059 self.gemma4_rope_dims(il),
13060 nh,
13061 nkv,
13062 pos_d,
13063 nh,
13064 nkv,
13065 base,
13066 1.0,
13067 ff,
13068 eps,
13069 &mut kvl.k,
13070 &mut kvl.v,
13071 &kvl.len_d,
13072 kvl.k_tok_bytes,
13073 kvl.v_tok_bytes,
13074 kv_fp8,
13075 )?;
13076 } else {
13077 e.rms_norm_qkv_rope(
13078 &sl.q0,
13079 &sl.k0,
13080 &sl.v0,
13081 fa.q_norm.float_data(),
13082 fa.k_norm.float_data(),
13083 ones,
13084 &mut sl.q,
13085 &mut sl.k,
13086 &mut sl.v,
13087 hd,
13088 self.gemma4_rope_dims(il),
13089 nh,
13090 nkv,
13091 pos_d,
13092 nh,
13093 nkv,
13094 base,
13095 1.0,
13096 ff,
13097 eps,
13098 )?;
13099 e.append_kv_quantized_dc(
13100 &sl.k,
13101 &sl.v,
13102 &mut kvl.k,
13103 &mut kvl.v,
13104 &kvl.len_d,
13105 kvl.kv_dim_k,
13106 kvl.kv_dim_v,
13107 kvl.k_tok_bytes,
13108 kvl.v_tok_bytes,
13109 kv_fp8,
13110 )?;
13111 }
13112 e.inc_seqlen(&mut kvl.len_d)?;
13113 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
13114 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13115 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13116 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13117 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13118 let mut fa_q8 = false;
13122 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13123 e.fa_decode_rows(
13124 &sl.q,
13125 &k_view,
13126 &v_view,
13127 &mut sl.attn,
13128 hd,
13129 nh,
13130 nkv,
13131 b_glob - 1,
13132 1,
13133 scale,
13134 kvl.k_tok_bytes,
13135 kvl.v_tok_bytes,
13136 Some((&kvl.len_d, -1)),
13137 false,
13138 false,
13139 Some((&mut sl.zq, &mut sl.zd)),
13140 )?;
13141 fa_q8 = true;
13142 } else if swa && b_swa > win && hd == 256 && rows_on {
13143 e.fa_decode_rows_w(
13144 &sl.q,
13145 &k_view,
13146 &v_view,
13147 &mut sl.attn,
13148 hd,
13149 nh,
13150 nkv,
13151 &kvl.len_d,
13152 -1,
13153 1,
13154 scale,
13155 win,
13156 kvl.k_tok_bytes,
13157 kvl.v_tok_bytes,
13158 Some((&mut sl.zq, &mut sl.zd)),
13159 )?;
13160 fa_q8 = true;
13161 } else {
13162 let b = if swa { b_swa } else { b_glob };
13163 e.fa_decode_dc(
13164 &sl.q,
13165 &k_view,
13166 &v_view,
13167 &mut sl.attn,
13168 hd,
13169 nh,
13170 nkv,
13171 &kvl.len_d,
13172 b,
13173 scale,
13174 kvl.k_tok_bytes,
13175 kvl.v_tok_bytes,
13176 swa && crate::Engine::wkv_on(),
13177 )?;
13178 }
13179 if !fa_q8 {
13180 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
13181 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
13182 }
13183 {
13184 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13185 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13186 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
13187 }
13188 Ok(())
13189 }
13190
13191 fn gemma4_layer_tail_slotted(
13194 &self,
13195 e: &Engine,
13196 layer: &crate::hybrid::HybridLayer,
13197 next_norm: Option<&CudaSlice<f32>>,
13198 sl: &mut G4DcSlots,
13199 ) -> Result<(), Box<dyn std::error::Error>> {
13200 let n_embd = self.cfg.n_embd as usize;
13201 let eps = self.cfg.rms_eps;
13202 let bits = layer.gemma4.as_ref().unwrap();
13203 let crate::hybrid::Ffn::Dense {
13204 ffn_gate,
13205 ffn_up,
13206 ffn_down,
13207 } = &layer.ffn
13208 else {
13209 return Err("slotted tail: dense ffn only".into());
13210 };
13211 let pnfold = Engine::g4_pnfold_on();
13212 if pnfold {
13213 let or = unsafe { &*(&sl.o as *const CudaSlice<f32>) };
13216 let xr = unsafe { &*(&sl.x as *const CudaSlice<f32>) };
13217 e.rms_pre_add_rms_norm_q8z_into(
13218 or,
13219 layer.post_attn_norm.float_data(),
13220 xr,
13221 bits.ffn_norm.float_data(),
13222 &mut sl.attn_out,
13223 &mut sl.zsh,
13224 n_embd,
13225 1,
13226 eps,
13227 &mut sl.zq,
13228 &mut sl.zd,
13229 )?;
13230 } else {
13231 e.add_rms_norm(
13232 &sl.cur,
13233 &sl.x,
13234 bits.ffn_norm.float_data(),
13235 &mut sl.attn_out,
13236 &mut sl.zsh,
13237 n_embd,
13238 1,
13239 eps,
13240 )?;
13241 }
13242 let n_ff = ffn_gate.out_features();
13243 if !pnfold {
13244 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
13245 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13246 }
13247 {
13248 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13249 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13250 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)?
13251 && !e.matmul_nvfp4_fused2_into(
13252 ffn_gate,
13253 ffn_up,
13254 zq,
13255 zd,
13256 &mut sl.gate,
13257 &mut sl.up,
13258 )?
13259 {
13260 return Err("slotted tail: ffn fused2 unavailable".into());
13261 }
13262 }
13263 debug_assert!(e.uses_q8_1_fast(ffn_down));
13264 {
13265 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
13266 let upv = e.view(upr, n_ff);
13267 let up_all = upv.slice(0..n_ff);
13268 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
13269 e.gelu_tanh_mul_q8_1_into(
13270 gr,
13271 &up_all,
13272 &mut sl.act,
13273 n_ff,
13274 1,
13275 &mut sl.actq,
13276 &mut sl.actd,
13277 )?;
13278 }
13279 {
13280 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
13281 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
13282 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
13283 }
13284 if pnfold {
13285 if let Some(w) = next_norm {
13288 let f0r = unsafe { &*(&sl.f0 as *const CudaSlice<f32>) };
13289 let aor = unsafe { &*(&sl.attn_out as *const CudaSlice<f32>) };
13290 e.rms_pre_add_scale_rms_norm_q8_1_into(
13291 f0r,
13292 bits.post_ffw_norm.float_data(),
13293 aor,
13294 bits.layer_scale,
13295 w,
13296 &mut sl.xn,
13297 n_embd,
13298 1,
13299 eps,
13300 &mut sl.hq,
13301 &mut sl.hd_,
13302 )?;
13303 return Ok(());
13304 }
13305 }
13306 e.rms_norm(
13307 &sl.f0,
13308 bits.post_ffw_norm.float_data(),
13309 &mut sl.sn,
13310 n_embd,
13311 1,
13312 eps,
13313 )?;
13314 match next_norm {
13315 Some(w) => {
13316 e.add_scale_rms_norm_q8_1_into(
13317 &sl.sn,
13318 &sl.attn_out,
13319 bits.layer_scale,
13320 w,
13321 &mut sl.xn,
13322 n_embd,
13323 1,
13324 eps,
13325 &mut sl.hq,
13326 &mut sl.hd_,
13327 )?;
13328 }
13329 None => {
13330 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
13331 }
13332 }
13333 Ok(())
13334 }
13335
13336 #[allow(clippy::too_many_arguments)]
13338 fn gemma4_decode_attn_dc(
13339 &self,
13340 e: &Engine,
13341 fa: &crate::hybrid::FullAttnLayer,
13342 il: usize,
13343 hq: &CudaSlice<i8>,
13344 hdq: &CudaSlice<f32>,
13345 pos_d: &CudaSlice<i32>,
13346 cache: &mut Cache,
13347 cap_bucket_max: Option<(usize, usize)>,
13348 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13349 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13350 let eps = self.cfg.rms_eps;
13351 let aux = self.gemma4_aux.as_ref().unwrap();
13352 let ones = aux.ones(e);
13353 #[cfg(debug_assertions)]
13354 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
13355 let (q0, k0, v0) = if swa {
13356 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
13357 Some(t3) => t3,
13358 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13360 Some((q0, k0)) => {
13361 let h0 = e.zeros(0)?;
13362 let v0 = e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?;
13363 (q0, k0, v0)
13364 }
13365 None => {
13366 let h0 = e.zeros(0)?;
13367 (
13368 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13369 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13370 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
13371 )
13372 }
13373 },
13374 }
13375 } else {
13376 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
13377 Some(p) => p,
13378 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13379 Some(p) => p,
13380 None => {
13381 let h0 = e.zeros(0)?;
13382 (
13383 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13384 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13385 )
13386 }
13387 },
13388 };
13389 let v0 = e.clone_dtod(&k0)?;
13390 (q0, k0, v0)
13391 };
13392 let mut q = e.uninit(nh * hd)?;
13393 let mut k = e.uninit(nkv * hd)?;
13394 let mut v = e.uninit(nkv * hd)?;
13395 let ff = if swa {
13397 None
13398 } else {
13399 Some(
13400 aux.rope_freqs(e)
13401 .expect("gemma4 global rope needs rope_freqs.weight"),
13402 )
13403 };
13404 #[cfg(debug_assertions)]
13405 if let Some(ff) = ff {
13406 crate::debug_assert_tensor_stream_device(
13407 ff,
13408 &e.stream(),
13409 "gemma4_decode_attn_dc.rope_freqs",
13410 );
13411 }
13412 let kvl = cache.kv[il].as_mut().unwrap();
13413 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13414 if crate::Engine::qkv_append_on() {
13415 e.rms_norm_qkv_rope_append_dc(
13417 &q0,
13418 &k0,
13419 &v0,
13420 fa.q_norm.float_data(),
13421 fa.k_norm.float_data(),
13422 ones,
13423 &mut q,
13424 &mut k,
13425 &mut v,
13426 hd,
13427 self.gemma4_rope_dims(il),
13428 nh,
13429 nkv,
13430 pos_d,
13431 nh,
13432 nkv,
13433 base,
13434 1.0,
13435 ff,
13436 eps,
13437 &mut kvl.k,
13438 &mut kvl.v,
13439 &kvl.len_d,
13440 kvl.k_tok_bytes,
13441 kvl.v_tok_bytes,
13442 kv_fp8,
13443 )?;
13444 } else {
13445 e.rms_norm_qkv_rope(
13446 &q0,
13447 &k0,
13448 &v0,
13449 fa.q_norm.float_data(),
13450 fa.k_norm.float_data(),
13451 ones,
13452 &mut q,
13453 &mut k,
13454 &mut v,
13455 hd,
13456 self.gemma4_rope_dims(il),
13457 nh,
13458 nkv,
13459 pos_d,
13460 nh,
13461 nkv,
13462 base,
13463 1.0,
13464 ff,
13465 eps,
13466 )?;
13467 e.append_kv_quantized_dc(
13468 &k,
13469 &v,
13470 &mut kvl.k,
13471 &mut kvl.v,
13472 &kvl.len_d,
13473 kvl.kv_dim_k,
13474 kvl.kv_dim_v,
13475 kvl.k_tok_bytes,
13476 kvl.v_tok_bytes,
13477 kv_fp8,
13478 )?;
13479 }
13480 e.inc_seqlen(&mut kvl.len_d)?;
13481 let mut attn = e.uninit(nh * hd)?;
13482 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13485 match cap_bucket_max {
13490 None => {
13491 kvl.len += 1;
13495 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13496 if !swa
13497 && hd == 512
13498 && kvl.len >= crate::fa512_min_tkv()
13499 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13500 {
13501 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13504 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13505 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13506 e.fa_decode_rows(
13507 &q,
13508 &kp,
13509 &vp,
13510 &mut attn,
13511 hd,
13512 nh,
13513 nkv,
13514 kvl.len - 1,
13515 1,
13516 scale,
13517 kvl.k_tok_bytes,
13518 kvl.v_tok_bytes,
13519 Some((&kvl.len_d, -1)),
13520 false,
13521 false,
13522 Some((&mut aq8, &mut ad8)),
13523 )?;
13524 fa_q8 = Some((aq8, ad8));
13525 } else if swa
13526 && kvl.len > win
13527 && hd == 256
13528 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13529 {
13530 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13532 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13533 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13534 e.fa_decode_rows_w(
13535 &q,
13536 &kp,
13537 &vp,
13538 &mut attn,
13539 hd,
13540 nh,
13541 nkv,
13542 &kvl.len_d,
13543 -1,
13544 1,
13545 scale,
13546 win,
13547 kvl.k_tok_bytes,
13548 kvl.v_tok_bytes,
13549 Some((&mut aq8, &mut ad8)),
13550 )?;
13551 fa_q8 = Some((aq8, ad8));
13552 } else {
13553 let (off_tok, t_kv) = if swa && kvl.len > win {
13554 (kvl.len - win, win)
13555 } else {
13556 (0, kvl.len)
13557 };
13558 let k_view = e.view_u8_range(
13559 &kvl.k,
13560 off_tok * kvl.k_tok_bytes,
13561 (off_tok + t_kv) * kvl.k_tok_bytes,
13562 );
13563 let v_view = e.view_u8_range(
13564 &kvl.v,
13565 off_tok * kvl.v_tok_bytes,
13566 (off_tok + t_kv) * kvl.v_tok_bytes,
13567 );
13568 e.fa_decode_kvmod(
13569 &q,
13570 &k_view,
13571 &v_view,
13572 &mut attn,
13573 hd,
13574 nh,
13575 nkv,
13576 t_kv,
13577 scale,
13578 kvl.k_tok_bytes,
13579 kvl.v_tok_bytes,
13580 swa && crate::Engine::wkv_on(),
13581 )?;
13582 }
13583 }
13584 Some((b_swa, b_glob)) => {
13585 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13591 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13592 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13593 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13594 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13595 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13596 e.fa_decode_rows(
13597 &q,
13598 &k_view,
13599 &v_view,
13600 &mut attn,
13601 hd,
13602 nh,
13603 nkv,
13604 b_glob - 1,
13605 1,
13606 scale,
13607 kvl.k_tok_bytes,
13608 kvl.v_tok_bytes,
13609 Some((&kvl.len_d, -1)),
13610 false,
13611 false,
13612 Some((&mut aq8, &mut ad8)),
13613 )?;
13614 fa_q8 = Some((aq8, ad8));
13615 } else if swa && b_swa > win && hd == 256 && rows_on {
13616 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13617 e.fa_decode_rows_w(
13618 &q,
13619 &k_view,
13620 &v_view,
13621 &mut attn,
13622 hd,
13623 nh,
13624 nkv,
13625 &kvl.len_d,
13626 -1,
13627 1,
13628 scale,
13629 win,
13630 kvl.k_tok_bytes,
13631 kvl.v_tok_bytes,
13632 Some((&mut aq8, &mut ad8)),
13633 )?;
13634 fa_q8 = Some((aq8, ad8));
13635 } else {
13636 let b = if swa { b_swa } else { b_glob };
13637 e.fa_decode_dc(
13638 &q,
13639 &k_view,
13640 &v_view,
13641 &mut attn,
13642 hd,
13643 nh,
13644 nkv,
13645 &kvl.len_d,
13646 b,
13647 scale,
13648 kvl.k_tok_bytes,
13649 kvl.v_tok_bytes,
13650 swa && crate::Engine::wkv_on(),
13651 )?;
13652 }
13653 }
13654 }
13655 if let Some((aq8, ad8)) = fa_q8 {
13658 let mut y = e.uninit(fa.wo.out_features())?;
13659 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
13660 return Ok(y);
13661 }
13662 Ok(e.matmul(&fa.wo, &attn, 1)?)
13663 }
13664
13665 pub fn gemma4_generate_graph(
13670 &self,
13671 e: &Engine,
13672 prompt_pos: usize,
13673 first_token: u32,
13674 cache: &mut Cache,
13675 max_new: usize,
13676 eos: &[u32],
13677 mut on_token: impl FnMut(u32) -> bool,
13678 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
13679 if self.is_gemma4_e4b() {
13680 return Err(
13681 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
13682 .into(),
13683 );
13684 }
13685 use crate::decode::StopReason;
13686 let n_vocab = self.output.out_features();
13687 let n_embd = self.cfg.n_embd as usize;
13688 let embd_gpu = self
13689 .embd_gpu
13690 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13691 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13692 for kvl in cache.kv.iter_mut().flatten() {
13693 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
13694 }
13695 let mut token_d = e.stream().clone_htod(&[first_token])?;
13696 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
13697 let g4 = self.cfg.gemma4.as_ref().unwrap();
13698 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
13699 let nkv_s = g4
13701 .head_count_kv
13702 .iter()
13703 .zip(g4.swa_pattern.iter())
13704 .find(|p| *p.1)
13705 .map(|p| *p.0 as usize)
13706 .unwrap_or(8);
13707 let nkv_g = g4
13708 .head_count_kv
13709 .iter()
13710 .zip(g4.swa_pattern.iter())
13711 .find(|p| !*p.1)
13712 .map(|p| *p.0 as usize)
13713 .unwrap_or(2);
13714 let mut graphs: std::collections::HashMap<
13715 ((bool, usize), (bool, usize), bool, bool),
13716 (
13717 cudarc::driver::CudaGraph,
13718 Vec<Box<dyn std::any::Any + Send>>,
13719 ),
13720 > = Default::default();
13721 let mut slots = self.g4_dc_slots(e)?;
13724 const RING: usize = 64;
13727 const DRAIN: usize = 1;
13733 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
13734 let ring_base = prompt_pos;
13735 let mut out = Vec::with_capacity(max_new);
13736 let mut reason = StopReason::MaxNew;
13737 let mut next = first_token;
13738 let mut captures = 0usize;
13739 for _ in 0..max_new {
13740 out.push(next);
13741 if eos.contains(&next) {
13742 reason = StopReason::Eos;
13743 break;
13744 }
13745 if !on_token(next) {
13746 reason = StopReason::Callback;
13747 break;
13748 }
13749 let t_kv = cache.pos + 1;
13750 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13758 let f512 = crate::fa512_min_tkv();
13759 let key_s = if t_kv > win {
13760 (true, usize::MAX)
13761 } else {
13762 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
13763 };
13764 let (key_g, rung_end) = if t_kv >= f512 {
13765 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
13768 ((true, end), end)
13769 } else {
13770 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
13771 };
13772 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
13773 if !graphs.contains_key(&key) {
13774 let bucket_max = (t_kv, rung_end);
13775 let snap = cache.snapshot(e)?;
13777 let pos_save = e.dtoh_i32_one(&pos_d)?;
13778 let len_save: Vec<Option<i32>> = cache
13779 .kv
13780 .iter()
13781 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
13782 .collect();
13783 let tok_save = e.dtoh_u32_one(&token_d)?;
13784 let graph = {
13789 let tok_ref = &mut token_d;
13790 let pos_ref = &mut pos_d;
13791 let cache_ref = &mut *cache;
13792 let slots_ref = &mut slots;
13793 let ring_ref = &mut ring;
13794 e.capture_graph_retained_flags(
13795 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
13796 |e| {
13797 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
13799 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
13800 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
13801 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
13802 cache_ref, n_vocab, Some(bucket_max),
13803 sl, tok_ref, Some((rg, ring_base)))
13804 })?
13805 };
13806 cache.rollback(e, &snap, 0)?;
13807 e.set_i32_one(&mut pos_d, pos_save)?;
13808 for (il, ls) in len_save.iter().enumerate() {
13809 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
13810 e.set_i32_one(&mut kvl.len_d, *v)?;
13811 }
13812 }
13813 e.set_u32_one(&mut token_d, tok_save)?;
13814 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
13815 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
13816 eprintln!("[graph-census] {c:?}");
13817 }
13818 }
13819 graphs.insert(key, graph);
13820 captures += 1;
13821 }
13822 let mut chunk = 1usize;
13827 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
13828 .ok()
13829 .and_then(|v| v.parse().ok())
13830 .unwrap_or(DRAIN);
13831 while chunk < drain_cap && out.len() + chunk < max_new {
13832 let t_next = cache.pos + 1 + chunk;
13833 let key_s2 = if t_next > win {
13834 (true, usize::MAX)
13835 } else {
13836 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
13837 };
13838 let key_g2 = if t_next >= f512 {
13839 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
13840 } else {
13841 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
13842 };
13843 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
13844 break;
13845 }
13846 chunk += 1;
13847 }
13848 let g = &graphs.get(&key).unwrap().0;
13849 for _ in 0..chunk {
13850 g.launch()?;
13851 }
13852 e.stream().synchronize()?;
13853 let ringh = e.dtoh_u32(&ring)?;
13854 for j in 0..chunk {
13855 let pos_j = cache.pos + j;
13856 let tok_j = ringh[(pos_j - ring_base) % RING];
13857 cache.pos += 0; if j + 1 == chunk {
13859 next = tok_j;
13860 } else {
13861 out.push(tok_j);
13862 if eos.contains(&tok_j) || !on_token(tok_j) {
13863 reason = if eos.contains(&tok_j) {
13864 StopReason::Eos
13865 } else {
13866 StopReason::Callback
13867 };
13868 let keep = cache.pos + j + 1;
13870 e.set_i32_one(&mut pos_d, keep as i32)?;
13871 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13872 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
13873 kvl.len = keep;
13874 }
13875 cache.pos = keep;
13876 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13877 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13878 }
13879 return Ok((out, reason));
13880 }
13881 }
13882 }
13883 cache.pos += chunk;
13884 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13885 kvl.len += chunk;
13886 }
13887 }
13888 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13889 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13890 }
13891 Ok((out, reason))
13892 }
13893
13894 pub(crate) fn gemma4_decode_step_t(
13900 &self,
13901 e: &Engine,
13902 tokens: &[u32],
13903 pos0: usize,
13904 cache: &mut Cache,
13905 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13906 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
13907 }
13908
13909 pub(crate) fn gemma4_decode_step_t_am(
13913 &self,
13914 e: &Engine,
13915 tokens: &[u32],
13916 pos0: usize,
13917 cache: &mut Cache,
13918 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13919 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13920 let t = tokens.len();
13921 let n_vocab = self.output.out_features();
13922 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
13923 for i in 0..t {
13924 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
13925 }
13926 Ok((e.dtoh_u32(&toks)?, hn))
13927 }
13928
13929 pub(crate) fn gemma4_decode_step_t_am_dev(
13932 &self,
13933 e: &Engine,
13934 tok_d: &CudaSlice<u32>,
13935 t: usize,
13936 pos0: usize,
13937 cache: &mut Cache,
13938 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13939 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
13940 let n_vocab = self.output.out_features();
13941 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13942 for i in 0..t {
13943 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13944 }
13945 Ok((vam, hn))
13946 }
13947
13948 pub(crate) fn gemma4_decode_step_t_h(
13951 &self,
13952 e: &Engine,
13953 tokens: &[u32],
13954 pos0: usize,
13955 cache: &mut Cache,
13956 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13957 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13958 let t = tokens.len();
13959 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
13960 e.softcap(&mut ld, cap, t * self.output.out_features())?;
13961 Ok((e.dtoh(&ld)?, hn))
13962 }
13963
13964 pub(crate) fn verify_stream_scratch(
13967 &self,
13968 e: &Engine,
13969 cap: usize,
13970 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
13971 Ok(VerifyStreamScratch {
13972 pos_d: e.htod_i32(&vec![0i32; cap])?,
13973 row_ctrs: (0..cap)
13974 .map(|_| e.htod_i32(&[0]))
13975 .collect::<Result<_, _>>()?,
13976 })
13977 }
13978
13979 pub(crate) fn gemma4_verify_t_am_stream(
13987 &self,
13988 e: &Engine,
13989 tok_d: &CudaSlice<u32>,
13990 t: usize,
13991 ctr: &CudaSlice<i32>,
13992 hint: usize,
13993 cache: &mut Cache,
13994 scr: &mut VerifyStreamScratch,
13995 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13996 let n_embd = self.cfg.n_embd as usize;
13997 let eps = self.cfg.rms_eps;
13998 assert!(t <= scr.row_ctrs.len() && t <= 64);
13999 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
14000 for i in 0..t {
14001 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
14002 }
14003 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
14004 let embd_gpu = self
14005 .embd_gpu
14006 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
14007 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
14008 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
14009 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
14010 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14011 let n_layers = self.layers.len();
14012 for (il, layer) in self.layers.iter().enumerate() {
14013 let (hq, hdq) = match h_carry.take() {
14014 Some(p) => p,
14015 None => {
14016 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
14017 }
14018 };
14019 let Mixer::Full(fa) = &layer.mixer else {
14020 panic!("gemma4 layer {il} not full-attn")
14021 };
14022 let o = self
14023 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
14024 let next_norm = if il + 1 < n_layers {
14025 Some(self.layers[il + 1].attn_norm.float_data())
14026 } else {
14027 None
14028 };
14029 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
14030 x = xn;
14031 h_carry = hn;
14032 self.dflash_tap(e, cache, il, &x, t)?;
14033 }
14034 let mut hn = e.uninit(t * n_embd)?;
14035 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14036 let ld = e.matmul(&self.output, &hn, t)?;
14037 let n_vocab = self.output.out_features();
14038 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
14039 for i in 0..t {
14040 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
14041 }
14042 Ok((vam, hn))
14043 }
14044
14045 pub(crate) fn dflash_tap(
14052 &self,
14053 e: &Engine,
14054 cache: &mut Cache,
14055 il: usize,
14056 x: &CudaSlice<f32>,
14057 t: usize,
14058 ) -> Result<(), Box<dyn std::error::Error>> {
14059 let Some(taps) = cache.dflash_taps.as_mut() else {
14060 return Ok(());
14061 };
14062 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
14063 return Ok(());
14064 };
14065 let h = taps.hidden;
14066 let n_taps = taps.layer_ids.len();
14067 let base = taps.base;
14068 debug_assert!(
14069 base + t <= taps.t,
14070 "tap window {base}+{t} exceeds sink {}",
14071 taps.t
14072 );
14073 let xv = e.view(x, t * h);
14074 for r in 0..t {
14075 let row = xv.slice(r * h..(r + 1) * h);
14076 e.copy_view_into(&mut taps.buf, (base + r) * n_taps * h + slot * h, &row, h)?;
14077 }
14078 Ok(())
14079 }
14080
14081 fn gemma4_verify_trunk(
14082 &self,
14083 e: &Engine,
14084 tokens: &[u32],
14085 pos0: usize,
14086 cache: &mut Cache,
14087 tok_dev: Option<&CudaSlice<u32>>,
14088 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14089 let n_embd = self.cfg.n_embd as usize;
14090 let eps = self.cfg.rms_eps;
14091 let t = tokens.len();
14092 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
14093 let pos_d = e.htod_i32(&pos)?;
14094 let mut x = match tok_dev {
14095 Some(td) => {
14096 let embd_gpu = self
14097 .embd_gpu
14098 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
14099 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
14100 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
14101 }
14102 None => e.htod(&self.embd.gather(n_embd, tokens))?,
14103 };
14104 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
14105 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14106 let n_layers = self.layers.len();
14107 for (il, layer) in self.layers.iter().enumerate() {
14108 let (hq, hdq) = match h_carry.take() {
14109 Some(p) => p,
14110 None => {
14111 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
14112 }
14113 };
14114 let Mixer::Full(fa) = &layer.mixer else {
14115 panic!("gemma4 layer {il} not full-attn")
14116 };
14117 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
14118 let next_norm = if il + 1 < n_layers {
14119 Some(self.layers[il + 1].attn_norm.float_data())
14120 } else {
14121 None
14122 };
14123 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
14124 x = xn;
14125 h_carry = hn;
14126 self.dflash_tap(e, cache, il, &x, t)?;
14127 }
14128 let mut hn = e.uninit(t * n_embd)?;
14129 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14130 let mut ld = e.matmul(&self.output, &hn, t)?;
14131 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
14133 Ok((ld, hn))
14134 }
14135
14136 #[allow(clippy::too_many_arguments)]
14144 fn gemma4_verify_attn_stream(
14145 &self,
14146 e: &Engine,
14147 fa: &crate::hybrid::FullAttnLayer,
14148 il: usize,
14149 hq: &CudaSlice<i8>,
14150 hdq: &CudaSlice<f32>,
14151 pos_d: &CudaSlice<i32>,
14152 t: usize,
14153 cache: &mut Cache,
14154 hint: usize,
14155 row_ctrs: &[CudaSlice<i32>],
14156 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14157 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14158 let eps = self.cfg.rms_eps;
14159 let aux = self.gemma4_aux.as_ref().unwrap();
14160 let ones = aux.ones(e);
14161 #[cfg(debug_assertions)]
14162 crate::debug_assert_tensor_stream_device(
14163 ones,
14164 &e.stream(),
14165 "gemma4_verify_attn_stream.ones",
14166 );
14167 let h0 = e.zeros(0)?;
14168 let h = &h0;
14169 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14172 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14173 let fused_qkv = if f2b {
14174 if swa {
14175 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14176 .map(|(a, b, c)| (a, b, Some(c)))
14177 } else {
14178 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14179 .map(|(a, b)| (a, b, None))
14180 }
14181 } else {
14182 None
14183 };
14184 let (q0, k0, v0) = match fused_qkv {
14185 Some((a, b, cv)) => {
14186 let v = match cv {
14187 Some(c) => c,
14188 None => e.clone_dtod(&b)?,
14189 };
14190 (a, b, v)
14191 }
14192 None => {
14193 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14194 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14195 let v0 = if swa {
14196 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14197 } else {
14198 e.clone_dtod(&k0)?
14199 };
14200 (q0, k0, v0)
14201 }
14202 };
14203 let mut q = e.uninit(t * nh * hd)?;
14204 let mut k = e.uninit(t * nkv * hd)?;
14205 let mut v = e.uninit(t * nkv * hd)?;
14206 let ff = if swa {
14209 None
14210 } else {
14211 Some(
14212 aux.rope_freqs(e)
14213 .expect("gemma4 global rope needs rope_freqs.weight"),
14214 )
14215 };
14216 #[cfg(debug_assertions)]
14217 if let Some(ff) = ff {
14218 crate::debug_assert_tensor_stream_device(
14219 ff,
14220 &e.stream(),
14221 "gemma4_verify_attn_stream.rope_freqs",
14222 );
14223 }
14224 e.rms_norm_qkv_rope(
14225 &q0,
14226 &k0,
14227 &v0,
14228 fa.q_norm.float_data(),
14229 fa.k_norm.float_data(),
14230 ones,
14231 &mut q,
14232 &mut k,
14233 &mut v,
14234 hd,
14235 self.gemma4_rope_dims(il),
14236 nh * t,
14237 nkv * t,
14238 pos_d,
14239 nh,
14240 nkv,
14241 base,
14242 1.0,
14243 ff,
14244 eps,
14245 )?;
14246 let kvl = cache.kv[il].as_mut().unwrap();
14247 e.append_kv_quantized_rows_dc(
14249 &k,
14250 &v,
14251 &mut kvl.k,
14252 &mut kvl.v,
14253 &kvl.len_d,
14254 t,
14255 kvl.kv_dim_k,
14256 kvl.kv_dim_v,
14257 kvl.k_tok_bytes,
14258 kvl.v_tok_bytes,
14259 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14260 )?;
14261 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14264 let mut attn = e.uninit(t * nh * hd)?;
14265 let k_view = e.view_u8(&kvl.k, kvl.k.len());
14266 let v_view = e.view_u8(&kvl.v, kvl.v.len());
14267 if swa && hint + 1 >= win {
14270 e.fa_decode_rows_w(
14273 &q,
14274 &k_view,
14275 &v_view,
14276 &mut attn,
14277 hd,
14278 nh,
14279 nkv,
14280 &kvl.len_d,
14281 0,
14282 t,
14283 scale,
14284 win,
14285 kvl.k_tok_bytes,
14286 kvl.v_tok_bytes,
14287 None,
14288 )?;
14289 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
14290 let bucket = (hint + t + 2)
14303 .next_power_of_two()
14304 .min(crate::fa512_min_tkv().saturating_sub(1));
14305 let qv = e.view(&q, t * nh * hd);
14306 for i in 0..t {
14307 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
14308 let mut q_one = e.uninit(nh * hd)?;
14309 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14310 let mut a_one = e.uninit(nh * hd)?;
14311 e.fa_decode_dc(
14312 &q_one,
14313 &k_view,
14314 &v_view,
14315 &mut a_one,
14316 hd,
14317 nh,
14318 nkv,
14319 &row_ctrs[i],
14320 bucket,
14321 scale,
14322 kvl.k_tok_bytes,
14323 kvl.v_tok_bytes,
14324 false,
14325 )?;
14326 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14327 }
14328 } else if hd == 512 {
14329 e.fa_decode_rows(
14332 &q,
14333 &k_view,
14334 &v_view,
14335 &mut attn,
14336 hd,
14337 nh,
14338 nkv,
14339 hint,
14340 t,
14341 scale,
14342 kvl.k_tok_bytes,
14343 kvl.v_tok_bytes,
14344 Some((&kvl.len_d, 0)),
14345 false,
14346 false,
14347 None,
14348 )?;
14349 } else {
14350 e.fa_decode_rows_dc(
14352 &q,
14353 &k_view,
14354 &v_view,
14355 &mut attn,
14356 hd,
14357 nh,
14358 nkv,
14359 &kvl.len_d,
14360 hint + t,
14361 t,
14362 scale,
14363 kvl.k_tok_bytes,
14364 kvl.v_tok_bytes,
14365 0,
14366 swa && crate::Engine::wkv_on(),
14367 )?;
14368 }
14369 Ok(e.matmul(&fa.wo, &attn, t)?)
14370 }
14371
14372 fn gemma4_verify_attn(
14373 &self,
14374 e: &Engine,
14375 fa: &crate::hybrid::FullAttnLayer,
14376 il: usize,
14377 hq: &CudaSlice<i8>,
14378 hdq: &CudaSlice<f32>,
14379 pos_d: &CudaSlice<i32>,
14380 t: usize,
14381 cache: &mut Cache,
14382 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14383 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14384 let eps = self.cfg.rms_eps;
14385 let aux = self.gemma4_aux.as_ref().unwrap();
14386 let ones = aux.ones(e);
14387 #[cfg(debug_assertions)]
14388 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
14389 let n_embd = self.cfg.n_embd as usize;
14390 let _ = n_embd;
14391
14392 let h0 = e.zeros(0)?;
14393 let h = &h0;
14394 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14397 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14398 let fused_qkv = if f2b {
14399 if swa {
14400 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14401 .map(|(a, b, c)| (a, b, Some(c)))
14402 } else {
14403 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14404 .map(|(a, b)| (a, b, None))
14405 }
14406 } else {
14407 None
14408 };
14409 let (q0, k0, v0) = match fused_qkv {
14410 Some((a, b, cv)) => {
14411 let v = match cv {
14412 Some(c) => c,
14413 None => e.clone_dtod(&b)?,
14414 };
14415 (a, b, v)
14416 }
14417 None => {
14418 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14419 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14420 let v0 = if swa {
14421 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14422 } else {
14423 e.clone_dtod(&k0)?
14424 };
14425 (q0, k0, v0)
14426 }
14427 };
14428 let mut q = e.uninit(t * nh * hd)?;
14429 let mut k = e.uninit(t * nkv * hd)?;
14430 let mut v = e.uninit(t * nkv * hd)?;
14431 let ff = if swa {
14434 None
14435 } else {
14436 Some(
14437 aux.rope_freqs(e)
14438 .expect("gemma4 global rope needs rope_freqs.weight"),
14439 )
14440 };
14441 #[cfg(debug_assertions)]
14442 if let Some(ff) = ff {
14443 crate::debug_assert_tensor_stream_device(
14444 ff,
14445 &e.stream(),
14446 "gemma4_verify_attn.rope_freqs",
14447 );
14448 }
14449 e.rms_norm_qkv_rope(
14450 &q0,
14451 &k0,
14452 &v0,
14453 fa.q_norm.float_data(),
14454 fa.k_norm.float_data(),
14455 ones,
14456 &mut q,
14457 &mut k,
14458 &mut v,
14459 hd,
14460 self.gemma4_rope_dims(il),
14461 nh * t,
14462 nkv * t,
14463 pos_d,
14464 nh,
14465 nkv,
14466 base,
14467 1.0,
14468 ff,
14469 eps,
14470 )?;
14471 let kvl = cache.kv[il].as_mut().unwrap();
14472 let base_len = kvl.len;
14473 e.append_kv_quantized_rows(
14474 &k,
14475 &v,
14476 &mut kvl.k,
14477 &mut kvl.v,
14478 base_len,
14479 t,
14480 kvl.kv_dim_k,
14481 kvl.kv_dim_v,
14482 kvl.k_tok_bytes,
14483 kvl.v_tok_bytes,
14484 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14485 )?;
14486 kvl.len += t;
14487 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14488 let mut attn = e.uninit(t * nh * hd)?;
14489 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
14492 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
14495 if rows_ok && (!swa || base_len + t <= win) {
14496 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14497 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14498 if hd == 512 {
14499 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14501 e.fa_decode_rows(
14502 &q,
14503 &k_view,
14504 &v_view,
14505 &mut attn,
14506 hd,
14507 nh,
14508 nkv,
14509 base_len,
14510 t,
14511 scale,
14512 kvl.k_tok_bytes,
14513 kvl.v_tok_bytes,
14514 Some((&kvl.len_d, 0)),
14515 false,
14516 swa && crate::Engine::wkv_on(),
14517 None,
14518 )?;
14519 } else {
14520 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14524 e.fa_decode_rows_dc(
14525 &q,
14526 &k_view,
14527 &v_view,
14528 &mut attn,
14529 hd,
14530 nh,
14531 nkv,
14532 &kvl.len_d,
14533 base_len + t,
14534 t,
14535 scale,
14536 kvl.k_tok_bytes,
14537 kvl.v_tok_bytes,
14538 0,
14539 swa && crate::Engine::wkv_on(),
14540 )?;
14541 }
14542 return Ok(e.matmul(&fa.wo, &attn, t)?);
14543 }
14544 if hd == 256
14552 && swa
14553 && base_len + 1 >= win
14554 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14555 {
14556 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14557 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14558 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14559 e.fa_decode_rows_w(
14560 &q,
14561 &k_view,
14562 &v_view,
14563 &mut attn,
14564 hd,
14565 nh,
14566 nkv,
14567 &kvl.len_d,
14568 0,
14569 t,
14570 scale,
14571 win,
14572 kvl.k_tok_bytes,
14573 kvl.v_tok_bytes,
14574 None,
14575 )?;
14576 return Ok(e.matmul(&fa.wo, &attn, t)?);
14577 }
14578 for i in 0..t {
14579 let avail = base_len + i + 1;
14580 let (off_tok, t_kv) = if swa && avail > win {
14581 (avail - win, win)
14582 } else {
14583 (0, avail)
14584 };
14585 let k_view = e.view_u8_range(
14586 &kvl.k,
14587 off_tok * kvl.k_tok_bytes,
14588 (off_tok + t_kv) * kvl.k_tok_bytes,
14589 );
14590 let v_view = e.view_u8_range(
14591 &kvl.v,
14592 off_tok * kvl.v_tok_bytes,
14593 (off_tok + t_kv) * kvl.v_tok_bytes,
14594 );
14595 let qi = e.view(&q, t * nh * hd);
14596 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
14597 let mut q_one = e.uninit(nh * hd)?;
14598 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14599 let mut a_one = e.uninit(nh * hd)?;
14600 if swa
14604 && avail > win
14605 && hd == 256
14606 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14607 {
14608 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14609 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14610 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14611 e.fa_decode_rows_w(
14612 &q_one,
14613 &kp,
14614 &vp,
14615 &mut a_one,
14616 hd,
14617 nh,
14618 nkv,
14619 &kvl.len_d,
14620 0,
14621 1,
14622 scale,
14623 win,
14624 kvl.k_tok_bytes,
14625 kvl.v_tok_bytes,
14626 None,
14627 )?;
14628 } else if !swa
14629 && hd == 512
14630 && avail >= crate::fa512_min_tkv()
14631 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14632 {
14633 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14634 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14635 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14636 e.fa_decode_rows(
14637 &q_one,
14638 &kp,
14639 &vp,
14640 &mut a_one,
14641 hd,
14642 nh,
14643 nkv,
14644 avail - 1,
14645 1,
14646 scale,
14647 kvl.k_tok_bytes,
14648 kvl.v_tok_bytes,
14649 Some((&kvl.len_d, 0)),
14650 false,
14651 false,
14652 None,
14653 )?;
14654 } else {
14655 e.fa_decode_kvmod(
14656 &q_one,
14657 &k_view,
14658 &v_view,
14659 &mut a_one,
14660 hd,
14661 nh,
14662 nkv,
14663 t_kv,
14664 scale,
14665 kvl.k_tok_bytes,
14666 kvl.v_tok_bytes,
14667 swa && crate::Engine::wkv_on(),
14668 )?;
14669 }
14670 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14671 }
14672 Ok(e.matmul(&fa.wo, &attn, t)?)
14673 }
14674
14675 pub(crate) fn gemma4_decode_step_h(
14678 &self,
14679 e: &Engine,
14680 token: u32,
14681 cache: &mut Cache,
14682 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14683 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
14688 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
14689 }
14690 if crate::pp::pp_cuts(self.layers.len()).is_some() {
14691 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
14692 }
14693 let n_embd = self.cfg.n_embd as usize;
14694 let eps = self.cfg.rms_eps;
14695 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14696 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14697 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14698 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14701 let n_layers = self.layers.len();
14702 for (il, layer) in self.layers.iter().enumerate() {
14703 let (hq, hdq) = match h_carry.take() {
14704 Some(p) => p,
14705 None => {
14706 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
14707 }
14708 };
14709 let Mixer::Full(fa) = &layer.mixer else {
14710 panic!("gemma4 layer {il} not full-attn")
14711 };
14712 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
14713 let next_norm = if il + 1 < n_layers {
14714 Some(self.layers[il + 1].attn_norm.float_data())
14715 } else {
14716 None
14717 };
14718 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14719 x = xn;
14720 h_carry = hn;
14721 }
14722 let mut hn = e.uninit(n_embd)?;
14723 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14724 let h_seed = e.clone_dtod(&x)?;
14725 let mut ld = e.matmul(&self.output, &hn, 1)?;
14726 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14727 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
14729 let logits = e.dtoh(&ld)?;
14730 cache.pos += 1;
14731 Ok((logits, h_seed))
14732 }
14733
14734 fn gemma4_decode_layers(
14742 &self,
14743 e: &Engine,
14744 mut x: CudaSlice<f32>,
14745 lo: usize,
14746 hi: usize,
14747 pos_d: &CudaSlice<i32>,
14748 cache: &mut Cache,
14749 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14750 let n_embd = self.cfg.n_embd as usize;
14751 let eps = self.cfg.rms_eps;
14752 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14753 for il in lo..hi {
14754 let layer = &self.layers[il];
14755 let (hq, hdq) = match h_carry.take() {
14756 Some(p) => p,
14757 None => {
14759 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
14760 }
14761 };
14762 let Mixer::Full(fa) = &layer.mixer else {
14763 panic!("gemma4 layer {il} not full-attn")
14764 };
14765 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
14766 let next_norm = if il + 1 < hi {
14767 Some(self.layers[il + 1].attn_norm.float_data())
14768 } else {
14769 None
14770 };
14771 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14772 x = xn;
14773 h_carry = hn;
14774 }
14775 Ok(x)
14776 }
14777
14778 fn gemma4_decode_step_h_pp2(
14786 &self,
14787 e: &Engine,
14788 token: u32,
14789 cache: &mut Cache,
14790 split: usize,
14791 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14792 if crate::pp::pp2_streams_off() {
14793 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
14794 }
14795 let rt = crate::pp::Pp2Rt::get(e)?;
14796 let e0 = rt.engine(0, e);
14797 let e1 = rt.engine(1, e);
14798 let n_embd = self.cfg.n_embd as usize;
14799 let eps = self.cfg.rms_eps;
14800 let pos = cache.pos as i32;
14801
14802 let slot = {
14804 let _st0 = rt.enter(0);
14805 let pos_d = e0.htod_i32(&[pos])?;
14806 #[cfg(debug_assertions)]
14807 crate::debug_assert_tensor_stream_device(
14808 &pos_d,
14809 &e0.stream(),
14810 "gemma4_decode_step_h_pp2.stage0.pos_d",
14811 );
14812 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
14813 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14814 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
14815 rt.tx(0, &x, n_embd)?
14816 };
14817
14818 let _st1 = rt.enter(1);
14820 let pos_d = e1.htod_i32(&[pos])?;
14821 #[cfg(debug_assertions)]
14822 crate::debug_assert_tensor_stream_device(
14823 &pos_d,
14824 &e1.stream(),
14825 "gemma4_decode_step_h_pp2.stage1.pos_d",
14826 );
14827 let x = rt.rx(0, slot, n_embd)?;
14828 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
14829
14830 let mut hn = e1.uninit(n_embd)?;
14831 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14832 let h_seed = e1.clone_dtod(&x)?;
14833 let mut ld = e1.matmul(&self.output, &hn, 1)?;
14834 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14835 e1.softcap(&mut ld, cap, self.output.out_features())?;
14836 self.gemma4_suppress(e1, &mut ld, 1)?;
14837 let logits = e1.dtoh(&ld)?;
14838 cache.pos += 1;
14839 Ok((logits, h_seed))
14840 }
14841
14842 fn gemma4_decode_step_h_pp2_samestream(
14845 &self,
14846 e: &Engine,
14847 token: u32,
14848 cache: &mut Cache,
14849 split: usize,
14850 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14851 let n_embd = self.cfg.n_embd as usize;
14852 let eps = self.cfg.rms_eps;
14853 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14854
14855 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14857 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14858 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
14859
14860 let boundary_tx = e.clone_dtod(&x)?;
14862 let boundary_rx = e.clone_dtod(&boundary_tx)?;
14863
14864 let x =
14866 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
14867
14868 let mut hn = e.uninit(n_embd)?;
14869 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14870 let h_seed = e.clone_dtod(&x)?;
14871 let mut ld = e.matmul(&self.output, &hn, 1)?;
14872 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14873 e.softcap(&mut ld, cap, self.output.out_features())?;
14874 self.gemma4_suppress(e, &mut ld, 1)?;
14875 let logits = e.dtoh(&ld)?;
14876 cache.pos += 1;
14877 Ok((logits, h_seed))
14878 }
14879}
14880
14881impl HybridModel {
14900 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
14903 let geometry = self
14904 .cfg
14905 .layer_geometry(il as u32)
14906 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
14907 debug_assert_eq!(
14908 geometry.attention_gate,
14909 memra_gguf::config::AttentionGateKind::SeparateHead
14910 );
14911 geometry
14912 }
14913
14914 #[allow(clippy::too_many_arguments)]
14974 fn step35_attn_pre_wo(
14975 &self,
14976 e: &Engine,
14977 fa: &FullAttnLayer,
14978 mut g3: Vec<CudaSlice<f32>>,
14979 hg: Option<&CudaSlice<f32>>,
14980 gt_pre: Option<&CudaSlice<f32>>,
14981 pos_d: &CudaSlice<i32>,
14982 t: usize,
14983 cache: Option<&mut Cache>,
14984 il: usize,
14985 seq_end: usize,
14986 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14987 let geometry = self.step35_geom(il);
14988 let hd = geometry.head_dim_k as usize;
14989 let nkv = geometry.n_head_kv as usize;
14990 let nh = geometry.n_head as usize;
14991 let rbase = geometry.rope_base;
14992 let scale = geometry.attention_scale();
14993 let swa = geometry.window.is_some();
14994 let eps = self.cfg.rms_eps;
14995 let win = geometry.window.unwrap_or(0) as usize;
14996 let n_rot = geometry.n_rot as usize;
14997
14998 let v = g3.pop().unwrap();
14999 let k0 = g3.pop().unwrap();
15000 let q0 = g3.pop().unwrap();
15001
15002 let mut q = e.uninit(t * nh * hd)?;
15006 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
15007 let mut k = e.uninit(t * nkv * hd)?;
15008 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
15009 let ff = if geometry.rope_factors {
15010 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
15011 } else {
15012 None
15013 };
15014 #[cfg(debug_assertions)]
15015 if let Some(ff) = ff {
15016 crate::debug_assert_tensor_stream_device(
15017 ff,
15018 &e.stream(),
15019 "step35_attn_pre_wo.rope_freqs",
15020 );
15021 }
15022 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
15023
15024 let mut attn = e.uninit(t * nh * hd)?;
15025 match cache {
15026 Some(cache) => {
15027 let base_len = cache.kv[il].as_ref().unwrap().len;
15028 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
15030 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
15031 let off = if swa {
15032 let raw = base_len.saturating_sub(win - 1);
15033 if legacy_tkv || legacy_calllocal {
15034 raw
15035 } else {
15036 raw & !31usize
15037 }
15038 } else {
15039 0
15040 };
15041 {
15042 let kvl = cache.kv[il].as_mut().unwrap();
15043 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
15044 let write_row = e.prepare_kv_append(kvl, off, t)?;
15045 e.append_kv_quantized_rows(
15046 &k,
15047 &v,
15048 &mut kvl.k,
15049 &mut kvl.v,
15050 write_row,
15051 t,
15052 kvl.kv_dim_k,
15053 kvl.kv_dim_v,
15054 kvl.k_tok_bytes,
15055 kvl.v_tok_bytes,
15056 crate::Engine::kv_fp8_on(),
15057 )?;
15058 kvl.len += t;
15059 let new_len = kvl.len as i32;
15060 e.set_i32_one(&mut kvl.len_d, new_len)?;
15061 }
15062 let kvl = cache.kv[il].as_ref().unwrap();
15063 let t_kv = base_len + t - off;
15086 let physical = kvl.physical_rows(off, off + t_kv)?;
15087 let k_view = e.view_u8_range(
15088 &kvl.k,
15089 physical.start * kvl.k_tok_bytes,
15090 physical.end * kvl.k_tok_bytes,
15091 );
15092 let v_view = e.view_u8_range(
15093 &kvl.v,
15094 physical.start * kvl.v_tok_bytes,
15095 physical.end * kvl.v_tok_bytes,
15096 );
15097 let swa_naive = if legacy_tkv {
15109 t_kv > win
15110 } else {
15111 seq_end > win
15112 };
15113 if swa && swa_naive {
15114 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15127 e.sdpa_naive_w_quantized_view(
15128 &q,
15129 &k_view,
15130 &v_view,
15131 &mut attn,
15132 hd,
15133 nh,
15134 nkv,
15135 t,
15136 t_kv,
15137 scale,
15138 true,
15139 win,
15140 kvl.k_tok_bytes,
15141 kvl.v_tok_bytes,
15142 )?;
15143 } else {
15144 e.fa_prefill_view_ws_w_hd128(
15145 &q,
15146 &k_view,
15147 &v_view,
15148 &mut attn,
15149 hd,
15150 nh,
15151 nkv,
15152 t,
15153 t_kv,
15154 scale,
15155 true,
15156 win,
15157 kvl.k_tok_bytes,
15158 kvl.v_tok_bytes,
15159 )?;
15160 }
15161 } else if std::env::var("MEMRA_NOFA").is_ok() {
15162 e.sdpa_naive_quantized_view(
15163 &q,
15164 &k_view,
15165 &v_view,
15166 &mut attn,
15167 hd,
15168 nh,
15169 nkv,
15170 t,
15171 t_kv,
15172 scale,
15173 true,
15174 kvl.k_tok_bytes,
15175 kvl.v_tok_bytes,
15176 )?;
15177 } else {
15178 e.fa_prefill_view_ws(
15183 &q,
15184 &k_view,
15185 &v_view,
15186 &mut attn,
15187 hd,
15188 nh,
15189 nkv,
15190 t,
15191 t_kv,
15192 scale,
15193 true,
15194 kvl.k_tok_bytes,
15195 kvl.v_tok_bytes,
15196 crate::Engine::kv_fp8_on(),
15197 )?;
15198 }
15199 }
15200 None => {
15201 debug_assert_eq!(
15206 seq_end, t,
15207 "step35 cacheless prefill is monolithic (seq_end == t)"
15208 );
15209 if swa && seq_end > win {
15210 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
15211 } else if std::env::var("MEMRA_NOFA").is_ok() {
15212 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15213 } else {
15214 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15215 }
15216 }
15217 }
15218
15219 let gw = fa
15222 .attn_gate
15223 .as_ref()
15224 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
15225 let gt_owned = if gt_pre.is_none() {
15226 Some(e.matmul(
15227 gw,
15228 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
15229 t,
15230 )?)
15231 } else {
15232 None
15233 };
15234 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
15235 let mut ag = e.uninit(t * nh * hd)?;
15236 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
15237 Ok(ag)
15238 }
15239
15240 pub(crate) fn step35_attn(
15243 &self,
15244 e: &Engine,
15245 fa: &FullAttnLayer,
15246 h: &CudaSlice<f32>,
15247 pos_d: &CudaSlice<i32>,
15248 t: usize,
15249 il: usize,
15250 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15251 let g3 = match self.step35_tp_qkv(e, fa, h, t)? {
15252 Some(g3) => g3,
15253 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15254 };
15255 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
15257 self.step35_o(e, fa, &ag, t)
15258 }
15259
15260 #[allow(clippy::too_many_arguments)]
15267 pub(crate) fn step35_attn_prime(
15268 &self,
15269 e: &Engine,
15270 fa: &FullAttnLayer,
15271 h: &CudaSlice<f32>,
15272 hx: Option<&CudaSlice<u8>>,
15273 pos_d: &CudaSlice<i32>,
15274 t: usize,
15275 cache: &mut Cache,
15276 il: usize,
15277 seq_end: usize,
15278 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15279 if step_tp_prefill_enabled()? && fa.step_tp_qkv.is_some() {
15280 if hx.is_some() {
15281 return Err(
15282 "rank-local Step prefill preserves BF16 activations and refuses the q8_1 \
15283 pre-quantized prime path"
15284 .into(),
15285 );
15286 }
15287 return self.step35_tp_prefill_attn_resident(e, fa, il, h, pos_d, t, cache, seq_end);
15288 }
15289 let g3 = if fa.step_tp_qkv.is_some() {
15290 if hx.is_some() {
15291 return Err(
15292 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
15293 pre-quantized prime path"
15294 .into(),
15295 );
15296 }
15297 self.step35_tp_qkv(e, fa, h, t)?
15298 .expect("Step Q/K/V TP disappeared after the presence check")
15299 } else {
15300 match hx {
15301 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
15302 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15303 }
15304 };
15305 let ag =
15306 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
15307 self.step35_o(e, fa, &ag, t)
15308 }
15309
15310 fn ensure_step_tp_kv_cache(
15311 &self,
15312 e: &Engine,
15313 fa: &FullAttnLayer,
15314 il: usize,
15315 cache: &mut Cache,
15316 ) -> Result<bool, Box<dyn std::error::Error>> {
15317 let tp = fa
15318 .step_tp_qkv
15319 .as_ref()
15320 .ok_or("Step TP cache hydration lost its resident projections")?;
15321 let geometry = self.step35_geom(il);
15322 let window = geometry.window.map(|window| window as usize);
15323 let ranks = tp.runtime.devices().len();
15324 let head_dim = geometry.head_dim_k as usize;
15325 let kv_heads = geometry.n_head_kv as usize;
15326 let max_ctx = cache.max_ctx;
15327
15328 if cache.tp_kv[il].is_some() {
15329 return Ok(false);
15330 }
15331 let local = cache.kv[il]
15332 .as_ref()
15333 .ok_or_else(|| format!("Step TP layer {il} has no owning-stage KV cache"))?;
15334 if local.kv_dim_k != kv_heads * head_dim || local.kv_dim_v != kv_heads * head_dim {
15335 return Err(format!(
15336 "Step TP layer {il} local KV geometry k={} v={} != {}",
15337 local.kv_dim_k,
15338 local.kv_dim_v,
15339 kv_heads * head_dim
15340 )
15341 .into());
15342 }
15343 let resident_start = window
15344 .map(|window| local.len.saturating_sub(window.saturating_sub(1)) & !31usize)
15345 .unwrap_or(0);
15346 let resident_rows = local.len - resident_start;
15347 let physical = local.physical_rows(resident_start, local.len)?;
15348 let k_rows = if resident_rows == 0 {
15349 Vec::new()
15350 } else {
15351 e.dtoh_u8_view(&e.view_u8_range(
15352 &local.k,
15353 physical.start * local.k_tok_bytes,
15354 physical.end * local.k_tok_bytes,
15355 ))?
15356 };
15357 let v_rows = if resident_rows == 0 {
15358 Vec::new()
15359 } else {
15360 e.dtoh_u8_view(&e.view_u8_range(
15361 &local.v,
15362 physical.start * local.v_tok_bytes,
15363 physical.end * local.v_tok_bytes,
15364 ))?
15365 };
15366 let mut distributed = match window {
15367 Some(window) => tp.runtime.allocate_tp_swa_kv_cache(
15368 kv_heads * head_dim,
15369 kv_heads * head_dim,
15370 max_ctx,
15371 window,
15372 )?,
15373 None => tp.runtime.allocate_tp_kv_cache(
15374 kv_heads * head_dim,
15375 kv_heads * head_dim,
15376 max_ctx,
15377 )?,
15378 };
15379 if distributed.k_tok_bytes() * ranks != local.k_tok_bytes
15380 || distributed.v_tok_bytes() * ranks != local.v_tok_bytes
15381 {
15382 return Err(format!(
15383 "Step TP layer {il} distributed/local KV token bytes disagree: \
15384 k={}x{ranks}/{} v={}x{ranks}/{}",
15385 distributed.k_tok_bytes(),
15386 local.k_tok_bytes,
15387 distributed.v_tok_bytes(),
15388 local.v_tok_bytes,
15389 )
15390 .into());
15391 }
15392 tp.runtime.hydrate_tp_kv_cache_from(
15393 &mut distributed,
15394 local.len,
15395 resident_start,
15396 &k_rows,
15397 &v_rows,
15398 )?;
15399 cache.tp_kv[il] = Some(distributed);
15400 Ok(true)
15401 }
15402
15403 #[allow(clippy::too_many_arguments)]
15404 fn step35_tp_prefill_attn_resident(
15405 &self,
15406 e: &Engine,
15407 fa: &FullAttnLayer,
15408 il: usize,
15409 h: &CudaSlice<f32>,
15410 pos_d: &CudaSlice<i32>,
15411 tokens: usize,
15412 cache: &mut Cache,
15413 seq_end: usize,
15414 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15415 let tp = fa
15416 .step_tp_qkv
15417 .as_ref()
15418 .ok_or("Step TP prefill lost its resident projections")?;
15419 let attention = tp
15420 .attention
15421 .as_ref()
15422 .ok_or("Step TP prefill lost its resident attention auxiliaries")?;
15423 let ranks = tp.runtime.devices().len();
15424 if !step_tp_prefill_shape(
15425 true,
15426 tokens,
15427 ranks,
15428 tp.runtime.native_p2p(),
15429 true,
15430 crate::Engine::kv_fp8_on(),
15431 ) {
15432 return Err(format!(
15433 "rank-local Step prefill requires tokens>={PRIME_MIN_T}, TP2/TP4 native P2P, \
15434 rank-local attention, and q8_0/q5_1 KV; got tokens={tokens} ranks={ranks} \
15435 native_p2p={} fp8_kv={}",
15436 tp.runtime.native_p2p(),
15437 crate::Engine::kv_fp8_on(),
15438 )
15439 .into());
15440 }
15441 for seam in [
15442 "MEMRA_STEP35_SWA_TKV",
15443 "MEMRA_PRIME_CALLLOCAL",
15444 "MEMRA_PRIME_F32CHUNK0",
15445 ] {
15446 if std::env::var(seam).as_deref() == Ok("1") {
15447 return Err(format!(
15448 "rank-local Step prefill has not qualified the legacy seam {seam}=1"
15449 )
15450 .into());
15451 }
15452 }
15453
15454 let geometry = self.step35_geom(il);
15455 let window = geometry.window.map(|window| window as usize);
15456 let head_dim = geometry.head_dim_k as usize;
15457 let heads = geometry.n_head as usize;
15458 let kv_heads = geometry.n_head_kv as usize;
15459 if heads % ranks != 0 || kv_heads % ranks != 0 {
15460 return Err(format!(
15461 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15462 )
15463 .into());
15464 }
15465 let local_heads = heads / ranks;
15466 let local_kv_heads = kv_heads / ranks;
15467 let local_kv_dim = local_kv_heads * head_dim;
15468 let hidden = self.cfg.n_embd as usize;
15469 let expected_input = tokens
15470 .checked_mul(hidden)
15471 .ok_or("Step TP prefill input size overflow")?;
15472 if h.len() < expected_input {
15473 return Err(format!(
15474 "Step TP prefill input {} is shorter than {tokens}x{hidden}",
15475 h.len()
15476 )
15477 .into());
15478 }
15479 let positions = e.dtoh_i32(pos_d)?;
15480 if positions.len() != tokens {
15481 return Err(format!(
15482 "rank-local Step prefill positions {} != tokens {tokens}",
15483 positions.len()
15484 )
15485 .into());
15486 }
15487
15488 let mut active_input = e.uninit(expected_input)?;
15489 e.copy_view_into(
15490 &mut active_input,
15491 0,
15492 &h.slice(0..expected_input),
15493 expected_input,
15494 )?;
15495 let mut input = tp.runtime.allocate_replicated_device_rows(tokens, hidden)?;
15496 e.stream().synchronize()?;
15501 tp.runtime
15502 .refresh_replicated_device_rows_from_root(&mut input, &active_input)?;
15503 let q_raw = tp
15504 .runtime
15505 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &input)?;
15506 let k_raw = tp
15507 .runtime
15508 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &input)?;
15509 let v_raw = tp
15510 .runtime
15511 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &input)?;
15512 let mut q = Vec::with_capacity(ranks);
15513 let mut k = Vec::with_capacity(ranks);
15514 for rank in 0..ranks {
15515 let engine = tp
15516 .runtime
15517 .rank_engine(rank)
15518 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15519 let _main = engine.gpu.enter_main()?;
15520 let mut q_rank = engine.uninit(tokens * local_heads * head_dim)?;
15521 engine.rms_norm(
15522 &q_raw[rank],
15523 &attention.q_norm[rank],
15524 &mut q_rank,
15525 head_dim,
15526 tokens * local_heads,
15527 self.cfg.rms_eps,
15528 )?;
15529 let mut k_rank = engine.uninit(tokens * local_kv_dim)?;
15530 engine.rms_norm(
15531 &k_raw[rank],
15532 &attention.k_norm[rank],
15533 &mut k_rank,
15534 head_dim,
15535 tokens * local_kv_heads,
15536 self.cfg.rms_eps,
15537 )?;
15538 let position = engine.htod_i32(&positions)?;
15539 let rope_freqs = if geometry.rope_factors {
15540 self.step35_aux
15541 .as_ref()
15542 .and_then(|aux| aux.rope_freqs(engine))
15543 } else {
15544 None
15545 };
15546 engine.rope_neox2(
15547 &mut q_rank,
15548 &mut k_rank,
15549 &position,
15550 head_dim,
15551 geometry.n_rot as usize,
15552 local_heads,
15553 local_kv_heads,
15554 tokens,
15555 geometry.rope_base,
15556 1.0,
15557 rope_freqs,
15558 )?;
15559 q.push(q_rank);
15560 k.push(k_rank);
15561 }
15562
15563 let gate_weight = fa
15564 .attn_gate
15565 .as_ref()
15566 .ok_or("step35 layer is missing attn_gate.weight")?;
15567 let gate = e.dtoh(&e.matmul(gate_weight, h, tokens)?)?;
15568 if gate.len() != tokens * heads {
15569 return Err(format!(
15570 "Step TP layer {il} gate output {} != {tokens}x{heads}",
15571 gate.len()
15572 )
15573 .into());
15574 }
15575
15576 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15577 let base_len = cache.kv[il]
15578 .as_ref()
15579 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15580 .len;
15581 let distributed = cache.tp_kv[il]
15582 .as_ref()
15583 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15584 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15585 return Err(format!(
15586 "Step TP layer {il} cache lengths diverged before prefill: \
15587 local={base_len} distributed={}/{}",
15588 distributed.committed_len(),
15589 distributed.staged_len()
15590 )
15591 .into());
15592 }
15593 let target_len = base_len
15594 .checked_add(tokens)
15595 .ok_or("Step TP prefill cache length overflow")?;
15596 if target_len > cache.max_ctx {
15597 return Err(format!(
15598 "Step TP layer {il} prefill exceeds cache: {base_len}+{tokens}>{}",
15599 cache.max_ctx
15600 )
15601 .into());
15602 }
15603 if seq_end < target_len {
15604 return Err(format!(
15605 "Step TP layer {il} request end {seq_end} precedes chunk end {target_len}"
15606 )
15607 .into());
15608 }
15609
15610 let transaction = cache.tp_kv[il]
15611 .as_mut()
15612 .expect("distributed cache checked above")
15613 .begin_transaction()?;
15614 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15615 cache.tp_kv[il]
15616 .as_mut()
15617 .expect("distributed cache checked above"),
15618 transaction,
15619 &k,
15620 &v_raw,
15621 tokens,
15622 ) {
15623 let _ = tp.runtime.rollback_tp_kv_transaction(
15624 cache.tp_kv[il]
15625 .as_mut()
15626 .expect("distributed cache checked above"),
15627 transaction,
15628 );
15629 return Err(error);
15630 }
15631
15632 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15633 let distributed = cache.tp_kv[il]
15634 .as_ref()
15635 .expect("distributed cache checked above");
15636 let staged_len = distributed.staged_len();
15637 let view_start = window
15638 .map(|window| base_len.saturating_sub(window.saturating_sub(1)) & !31usize)
15639 .unwrap_or(0);
15640 let physical = distributed.physical_range(view_start, staged_len)?;
15641 let t_kv = staged_len - view_start;
15642 let swa_naive = window.is_some_and(|window| seq_end > window);
15643 let mut gated = Vec::with_capacity(ranks);
15644 for rank in 0..ranks {
15645 let engine = tp
15646 .runtime
15647 .rank_engine(rank)
15648 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15649 let _main = engine.gpu.enter_main()?;
15650 let rank_cache = distributed
15651 .rank(rank)
15652 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
15653 let k_view = engine.view_u8_range(
15654 rank_cache.k(),
15655 physical.start * distributed.k_tok_bytes(),
15656 physical.end * distributed.k_tok_bytes(),
15657 );
15658 let v_view = engine.view_u8_range(
15659 rank_cache.v(),
15660 physical.start * distributed.v_tok_bytes(),
15661 physical.end * distributed.v_tok_bytes(),
15662 );
15663 let mut attention_out = engine.uninit(tokens * local_heads * head_dim)?;
15664 if swa_naive {
15665 let window = window.expect("SWA predicate requires a window");
15666 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15667 engine.sdpa_naive_w_quantized_view(
15668 &q[rank],
15669 &k_view,
15670 &v_view,
15671 &mut attention_out,
15672 head_dim,
15673 local_heads,
15674 local_kv_heads,
15675 tokens,
15676 t_kv,
15677 geometry.attention_scale(),
15678 true,
15679 window,
15680 distributed.k_tok_bytes(),
15681 distributed.v_tok_bytes(),
15682 )?;
15683 } else {
15684 engine.fa_prefill_view_ws_w_hd128(
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 window,
15697 distributed.k_tok_bytes(),
15698 distributed.v_tok_bytes(),
15699 )?;
15700 }
15701 } else if std::env::var("MEMRA_NOFA").is_ok() {
15702 engine.sdpa_naive_quantized_view(
15703 &q[rank],
15704 &k_view,
15705 &v_view,
15706 &mut attention_out,
15707 head_dim,
15708 local_heads,
15709 local_kv_heads,
15710 tokens,
15711 t_kv,
15712 geometry.attention_scale(),
15713 true,
15714 distributed.k_tok_bytes(),
15715 distributed.v_tok_bytes(),
15716 )?;
15717 } else {
15718 engine.fa_prefill_view_ws(
15719 &q[rank],
15720 &k_view,
15721 &v_view,
15722 &mut attention_out,
15723 head_dim,
15724 local_heads,
15725 local_kv_heads,
15726 tokens,
15727 t_kv,
15728 geometry.attention_scale(),
15729 true,
15730 distributed.k_tok_bytes(),
15731 distributed.v_tok_bytes(),
15732 false,
15733 )?;
15734 }
15735
15736 let gate_start = rank * local_heads;
15737 let mut gate_rank = Vec::with_capacity(tokens * local_heads);
15738 for token in 0..tokens {
15739 let start = token * heads + gate_start;
15740 gate_rank.extend_from_slice(&gate[start..start + local_heads]);
15741 }
15742 let gate_rank = engine.htod(&gate_rank)?;
15743 let mut gated_rank = engine.uninit(tokens * local_heads * head_dim)?;
15744 engine.attn_head_gate(
15745 &attention_out,
15746 &gate_rank,
15747 &mut gated_rank,
15748 None,
15749 head_dim,
15750 local_heads,
15751 tokens,
15752 )?;
15753 gated.push(gated_rank);
15754 }
15755 for rank in 1..ranks {
15756 let engine = tp
15757 .runtime
15758 .rank_engine(rank)
15759 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15760 let _main = engine.gpu.enter_main()?;
15761 engine.stream().synchronize()?;
15762 }
15763
15764 let (output, k_shadow, v_shadow) = if tp.runtime.bulk_p2p() {
15765 let output = tp
15766 .runtime
15767 .step_bf16_row_parallel_resident_root_device(&tp.o, &gated, tokens)?;
15768 let k_shadow =
15769 tp.runtime
15770 .gather_native_column_shards_device(&k, tokens, local_kv_dim)?;
15771 let v_shadow =
15772 tp.runtime
15773 .gather_native_column_shards_device(&v_raw, tokens, local_kv_dim)?;
15774 let root = tp
15775 .runtime
15776 .rank_engine(0)
15777 .ok_or("Step TP prefill lost its root engine")?;
15778 let _main = root.gpu.enter_main()?;
15779 root.stream().synchronize()?;
15780 (output, k_shadow, v_shadow)
15781 } else {
15782 let attention = tp.runtime.gather_native_column_shards(
15783 &gated,
15784 tokens,
15785 local_heads * head_dim,
15786 )?;
15787 let output = tp
15788 .runtime
15789 .step_bf16_row_parallel_resident_native(&tp.o, &attention, tokens)?;
15790 let k_shadow = tp
15791 .runtime
15792 .gather_native_column_shards(&k, tokens, local_kv_dim)?;
15793 let v_shadow =
15794 tp.runtime
15795 .gather_native_column_shards(&v_raw, tokens, local_kv_dim)?;
15796 (e.htod(&output)?, e.htod(&k_shadow)?, e.htod(&v_shadow)?)
15797 };
15798 let local = cache.kv[il]
15799 .as_mut()
15800 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
15801 if local.len != base_len {
15802 return Err(format!(
15803 "Step TP layer {il} local cache changed during prefill: \
15804 len={} base={base_len}",
15805 local.len
15806 )
15807 .into());
15808 }
15809 let retain_from = window
15810 .map(|window| {
15811 let staged_retain = staged_len.saturating_sub(window) & !31usize;
15812 let rollback_retain =
15813 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
15814 staged_retain.min(rollback_retain)
15815 })
15816 .unwrap_or(0);
15817 let write_row = e.prepare_kv_append(local, retain_from, tokens)?;
15818 e.append_kv_quantized_rows(
15819 &k_shadow,
15820 &v_shadow,
15821 &mut local.k,
15822 &mut local.v,
15823 write_row,
15824 tokens,
15825 local.kv_dim_k,
15826 local.kv_dim_v,
15827 local.k_tok_bytes,
15828 local.v_tok_bytes,
15829 false,
15830 )?;
15831 local.len = staged_len;
15832 e.set_i32_one(&mut local.len_d, staged_len as i32)?;
15833 Ok(output)
15834 })();
15835
15836 let output = match staged {
15837 Ok(output) => output,
15838 Err(error) => {
15839 let _ = tp.runtime.rollback_tp_kv_transaction(
15840 cache.tp_kv[il]
15841 .as_mut()
15842 .expect("distributed cache checked above"),
15843 transaction,
15844 );
15845 if let Some(local) = cache.kv[il].as_mut() {
15846 local.len = base_len;
15847 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
15848 }
15849 return Err(error);
15850 }
15851 };
15852 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
15853 cache.tp_kv[il]
15854 .as_mut()
15855 .expect("distributed cache checked above"),
15856 transaction,
15857 tokens,
15858 ) {
15859 let _ = tp.runtime.rollback_tp_kv_transaction(
15860 cache.tp_kv[il]
15861 .as_mut()
15862 .expect("distributed cache checked above"),
15863 transaction,
15864 );
15865 let local = cache.kv[il].as_mut().expect("local cache checked above");
15866 local.len = base_len;
15867 e.set_i32_one(&mut local.len_d, base_len as i32)?;
15868 return Err(error);
15869 }
15870
15871 let committed = cache.tp_kv[il]
15872 .as_ref()
15873 .expect("distributed cache checked above")
15874 .committed_len();
15875 let local_len = cache.kv[il]
15876 .as_ref()
15877 .expect("local cache checked above")
15878 .len;
15879 if committed != local_len {
15880 return Err(format!(
15881 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
15882 )
15883 .into());
15884 }
15885 eprintln!(
15886 "[step-tp-prefill-attn] execute layer={} devices={:?} tokens={tokens} \
15887 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
15888 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
15889 attention_scope={} input_path=root-device-replicated gate_tensor_parallel=false \
15890 gate_shards=host-canonical o_tensor_parallel=true local_cache_shadow=true \
15891 cache_commit=chunk transport={} native_p2p=true bulk_p2p={} \
15892 output={} performance_claim=false",
15893 tp.layer,
15894 tp.devices,
15895 hydrated,
15896 if window.is_some() {
15897 "rank-local-swa-ring"
15898 } else {
15899 "rank-local-global"
15900 },
15901 tp.runtime.transport_label(),
15902 tp.runtime.bulk_p2p(),
15903 if tp.runtime.bulk_p2p() {
15904 "root-device"
15905 } else {
15906 "root-readback"
15907 },
15908 );
15909 Ok(output)
15910 }
15911
15912 fn step35_tp_decode_attn_resident(
15913 &self,
15914 e: &Engine,
15915 fa: &FullAttnLayer,
15916 il: usize,
15917 h: &CudaSlice<f32>,
15918 pos_d: &CudaSlice<i32>,
15919 cache: &mut Cache,
15920 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15921 static ATTN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15925 static ATTN_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15926 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15927 let started = timing.then(std::time::Instant::now);
15928 let result = if crate::tp::step_tp_decode_v2_enabled()? {
15929 self.step35_tp_decode_attn_resident_v2(e, fa, il, h, pos_d, cache)
15930 } else {
15931 self.step35_tp_decode_attn_resident_inner(e, fa, il, h, pos_d, cache)
15932 };
15933 if let Some(started) = started {
15934 use std::sync::atomic::Ordering;
15935 let ns = ATTN_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
15936 + started.elapsed().as_nanos() as u64;
15937 let calls = ATTN_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
15938 if calls % 430 == 0 {
15939 eprintln!(
15940 "[step-tp-attn-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
15941 ns as f64 / 1.0e6,
15942 ns as f64 / calls as f64 / 1.0e3,
15943 );
15944 }
15945 }
15946 result
15947 }
15948
15949 #[allow(clippy::too_many_arguments)]
15950 fn step35_tp_decode_attn_resident_inner(
15951 &self,
15952 e: &Engine,
15953 fa: &FullAttnLayer,
15954 il: usize,
15955 h: &CudaSlice<f32>,
15956 pos_d: &CudaSlice<i32>,
15957 cache: &mut Cache,
15958 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15959 static T_POS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15964 static T_QKV: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15965 static T_NORMROPE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15966 static T_GATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15967 static T_APPEND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15968 static T_ATTN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15969 static T_OPROJ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15970 static T_SHADOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15971 static T_PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15972 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15973 fn lap(
15974 runtime: &crate::tp::TpE4m3HostBounce,
15975 e: &Engine,
15976 timer: &std::sync::atomic::AtomicU64,
15977 started: &mut Option<std::time::Instant>,
15978 ) -> Result<(), Box<dyn std::error::Error>> {
15979 let Some(start) = started.as_mut() else {
15980 return Ok(());
15981 };
15982 for rank in 0..runtime.devices().len() {
15983 if let Some(engine) = runtime.rank_engine(rank) {
15984 let _main = engine.gpu.enter_main()?;
15985 engine.stream().synchronize()?;
15986 }
15987 }
15988 e.stream().synchronize()?;
15989 timer.fetch_add(
15990 start.elapsed().as_nanos() as u64,
15991 std::sync::atomic::Ordering::Relaxed,
15992 );
15993 *start = std::time::Instant::now();
15994 Ok(())
15995 }
15996 let tp = fa
15997 .step_tp_qkv
15998 .as_ref()
15999 .ok_or("Step TP decode lost its resident projections")?;
16000 let attention = tp
16001 .attention
16002 .as_ref()
16003 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
16004 if !tp.runtime.native_p2p() {
16005 return Err("rank-local Step attention requires native P2P".into());
16006 }
16007 if crate::Engine::kv_fp8_on() {
16008 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
16009 }
16010
16011 let geometry = self.step35_geom(il);
16012 let window = geometry.window.map(|window| window as usize);
16013 let ranks = tp.runtime.devices().len();
16014 let head_dim = geometry.head_dim_k as usize;
16015 let heads = geometry.n_head as usize;
16016 let kv_heads = geometry.n_head_kv as usize;
16017 if heads % ranks != 0 || kv_heads % ranks != 0 {
16018 return Err(format!(
16019 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
16020 )
16021 .into());
16022 }
16023 let local_heads = heads / ranks;
16024 let local_kv_heads = kv_heads / ranks;
16025 let local_kv_dim = local_kv_heads * head_dim;
16026 let max_ctx = cache.max_ctx;
16027
16028 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
16029
16030 let base_len = cache.kv[il]
16031 .as_ref()
16032 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
16033 .len;
16034 let distributed = cache.tp_kv[il]
16035 .as_ref()
16036 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
16037 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
16038 return Err(format!(
16039 "Step TP layer {il} cache lengths diverged before decode: \
16040 local={base_len} distributed={}/{}",
16041 distributed.committed_len(),
16042 distributed.staged_len()
16043 )
16044 .into());
16045 }
16046
16047 let mut lap_start = timing.then(std::time::Instant::now);
16048 let positions = e.dtoh_i32(pos_d)?;
16049 if positions.len() != 1 {
16050 return Err(format!(
16051 "rank-local Step decode requires one position, got {}",
16052 positions.len()
16053 )
16054 .into());
16055 }
16056 lap(&tp.runtime, e, &T_POS, &mut lap_start)?;
16057 let (q_raw, k_raw, v_raw, input_path) = if let Some(decode_input) =
16058 attention.decode_input.as_ref()
16059 {
16060 let mut decode_input = decode_input
16061 .lock()
16062 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16063 e.stream().synchronize()?;
16067 tp.runtime
16068 .refresh_replicated_device_rows_from_root(&mut decode_input, h)?;
16069 let q_raw = tp
16070 .runtime
16071 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &decode_input)?;
16072 let k_raw = tp
16073 .runtime
16074 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &decode_input)?;
16075 let v_raw = tp
16076 .runtime
16077 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &decode_input)?;
16078 (q_raw, k_raw, v_raw, "root-device-replicated")
16079 } else {
16080 let activation = e.dtoh(h)?;
16081 let q_raw =
16082 tp.runtime
16083 .bf16_column_parallel_resident_device_shards(&tp.q, &activation, 1)?;
16084 let k_raw =
16085 tp.runtime
16086 .bf16_column_parallel_resident_device_shards(&tp.k, &activation, 1)?;
16087 let v_raw =
16088 tp.runtime
16089 .bf16_column_parallel_resident_device_shards(&tp.v, &activation, 1)?;
16090 (q_raw, k_raw, v_raw, "host-replicated")
16091 };
16092 lap(&tp.runtime, e, &T_QKV, &mut lap_start)?;
16093 let mut q = Vec::with_capacity(ranks);
16094 let mut k = Vec::with_capacity(ranks);
16095 for rank in 0..ranks {
16096 let engine = tp
16097 .runtime
16098 .rank_engine(rank)
16099 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16100 let _main = engine.gpu.enter_main()?;
16101 let mut q_rank = engine.uninit(local_heads * head_dim)?;
16102 engine.rms_norm(
16103 &q_raw[rank],
16104 &attention.q_norm[rank],
16105 &mut q_rank,
16106 head_dim,
16107 local_heads,
16108 self.cfg.rms_eps,
16109 )?;
16110 let mut k_rank = engine.uninit(local_kv_dim)?;
16111 engine.rms_norm(
16112 &k_raw[rank],
16113 &attention.k_norm[rank],
16114 &mut k_rank,
16115 head_dim,
16116 local_kv_heads,
16117 self.cfg.rms_eps,
16118 )?;
16119 let position = engine.htod_i32(&positions)?;
16120 let rope_freqs = if geometry.rope_factors {
16121 self.step35_aux
16122 .as_ref()
16123 .and_then(|aux| aux.rope_freqs(engine))
16124 } else {
16125 None
16126 };
16127 engine.rope_neox2(
16128 &mut q_rank,
16129 &mut k_rank,
16130 &position,
16131 head_dim,
16132 geometry.n_rot as usize,
16133 local_heads,
16134 local_kv_heads,
16135 1,
16136 geometry.rope_base,
16137 1.0,
16138 rope_freqs,
16139 )?;
16140 q.push(q_rank);
16141 k.push(k_rank);
16142 }
16143 lap(&tp.runtime, e, &T_NORMROPE, &mut lap_start)?;
16144
16145 let gate_weight = fa
16146 .attn_gate
16147 .as_ref()
16148 .ok_or("step35 layer is missing attn_gate.weight")?;
16149 let gate = e.matmul(gate_weight, h, 1)?;
16150 let gate = e.dtoh(&gate)?;
16151 if gate.len() != heads {
16152 return Err(format!("Step TP layer {il} gate output {} != {heads}", gate.len()).into());
16153 }
16154 lap(&tp.runtime, e, &T_GATE, &mut lap_start)?;
16155
16156 let transaction = cache.tp_kv[il]
16157 .as_mut()
16158 .expect("distributed cache checked above")
16159 .begin_transaction()?;
16160 if let Err(error) = tp.runtime.append_tp_kv_transaction(
16161 cache.tp_kv[il]
16162 .as_mut()
16163 .expect("distributed cache checked above"),
16164 transaction,
16165 &k,
16166 &v_raw,
16167 1,
16168 ) {
16169 let _ = tp.runtime.rollback_tp_kv_transaction(
16170 cache.tp_kv[il]
16171 .as_mut()
16172 .expect("distributed cache checked above"),
16173 transaction,
16174 );
16175 return Err(error);
16176 }
16177 lap(&tp.runtime, e, &T_APPEND, &mut lap_start)?;
16178
16179 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16180 let distributed = cache.tp_kv[il]
16181 .as_ref()
16182 .expect("distributed cache checked above");
16183 let staged_len = distributed.staged_len();
16184 let view_start = window
16185 .map(|window| staged_len.saturating_sub(window))
16186 .unwrap_or(0);
16187 let physical = distributed.physical_range(view_start, staged_len)?;
16188 let t_kv = staged_len - view_start;
16189 let mut gated = Vec::with_capacity(ranks);
16190 for rank in 0..ranks {
16191 let engine = tp
16192 .runtime
16193 .rank_engine(rank)
16194 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16195 let _main = engine.gpu.enter_main()?;
16196 let rank_cache = distributed
16197 .rank(rank)
16198 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16199 let k_view = engine.view_u8_range(
16200 rank_cache.k(),
16201 physical.start * distributed.k_tok_bytes(),
16202 physical.end * distributed.k_tok_bytes(),
16203 );
16204 let v_view = engine.view_u8_range(
16205 rank_cache.v(),
16206 physical.start * distributed.v_tok_bytes(),
16207 physical.end * distributed.v_tok_bytes(),
16208 );
16209 let mut attention_out = engine.uninit(local_heads * head_dim)?;
16210 engine.fa_decode_kvmod(
16211 &q[rank],
16212 &k_view,
16213 &v_view,
16214 &mut attention_out,
16215 head_dim,
16216 local_heads,
16217 local_kv_heads,
16218 t_kv,
16219 geometry.attention_scale(),
16220 distributed.k_tok_bytes(),
16221 distributed.v_tok_bytes(),
16222 false,
16223 )?;
16224 let gate_start = rank * local_heads;
16225 let gate_rank = engine.htod(&gate[gate_start..gate_start + local_heads])?;
16226 let mut gated_rank = engine.uninit(local_heads * head_dim)?;
16227 engine.attn_head_gate(
16228 &attention_out,
16229 &gate_rank,
16230 &mut gated_rank,
16231 None,
16232 head_dim,
16233 local_heads,
16234 1,
16235 )?;
16236 gated.push(gated_rank);
16237 }
16238 lap(&tp.runtime, e, &T_ATTN, &mut lap_start)?;
16239
16240 let gathered =
16241 tp.runtime
16242 .gather_native_column_shards(&gated, 1, local_heads * head_dim)?;
16243 let output = tp
16244 .runtime
16245 .step_bf16_row_parallel_resident_native(&tp.o, &gathered, 1)?;
16246 let output = e.htod(&output)?;
16247 lap(&tp.runtime, e, &T_OPROJ, &mut lap_start)?;
16248
16249 let k_shadow = tp
16250 .runtime
16251 .gather_native_column_shards(&k, 1, local_kv_dim)?;
16252 let v_shadow = tp
16253 .runtime
16254 .gather_native_column_shards(&v_raw, 1, local_kv_dim)?;
16255 let k_shadow = e.htod(&k_shadow)?;
16256 let v_shadow = e.htod(&v_shadow)?;
16257 let local = cache.kv[il]
16258 .as_mut()
16259 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16260 if local.len != base_len || base_len + 1 > max_ctx {
16261 return Err(format!(
16262 "Step TP layer {il} local cache changed during decode: \
16263 len={} base={base_len} max={max_ctx}",
16264 local.len
16265 )
16266 .into());
16267 }
16268 let retain_from = window
16269 .map(|window| {
16270 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16271 let rollback_retain =
16272 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16273 staged_retain.min(rollback_retain)
16274 })
16275 .unwrap_or(0);
16276 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16277 e.append_kv_quantized(
16278 &k_shadow,
16279 &v_shadow,
16280 &mut local.k,
16281 &mut local.v,
16282 write_row,
16283 local.kv_dim_k,
16284 local.kv_dim_v,
16285 local.k_tok_bytes,
16286 local.v_tok_bytes,
16287 false,
16288 )?;
16289 local.len = base_len + 1;
16290 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16291 Ok(output)
16292 })();
16293
16294 let output = match staged {
16295 Ok(output) => output,
16296 Err(error) => {
16297 let _ = tp.runtime.rollback_tp_kv_transaction(
16298 cache.tp_kv[il]
16299 .as_mut()
16300 .expect("distributed cache checked above"),
16301 transaction,
16302 );
16303 if let Some(local) = cache.kv[il].as_mut() {
16304 local.len = base_len;
16305 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16306 }
16307 return Err(error);
16308 }
16309 };
16310 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16311 cache.tp_kv[il]
16312 .as_mut()
16313 .expect("distributed cache checked above"),
16314 transaction,
16315 1,
16316 ) {
16317 let _ = tp.runtime.rollback_tp_kv_transaction(
16318 cache.tp_kv[il]
16319 .as_mut()
16320 .expect("distributed cache checked above"),
16321 transaction,
16322 );
16323 let local = cache.kv[il].as_mut().expect("local cache checked above");
16324 local.len = base_len;
16325 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16326 return Err(error);
16327 }
16328
16329 let committed = cache.tp_kv[il]
16330 .as_ref()
16331 .expect("distributed cache checked above")
16332 .committed_len();
16333 let local_len = cache.kv[il]
16334 .as_ref()
16335 .expect("local cache checked above")
16336 .len;
16337 if committed != local_len {
16338 return Err(format!(
16339 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16340 )
16341 .into());
16342 }
16343 lap(&tp.runtime, e, &T_SHADOW, &mut lap_start)?;
16344 if timing {
16345 use std::sync::atomic::Ordering;
16346 let calls = T_PHASE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16347 if calls % 430 == 0 {
16348 let avg = |t: &std::sync::atomic::AtomicU64| {
16349 t.load(Ordering::Relaxed) as f64 / calls as f64 / 1.0e3
16350 };
16351 eprintln!(
16352 "[step-tp-attn-phase] calls={calls} avg_us pos={:.1} qkv={:.1} \
16353 normrope={:.1} gate={:.1} append={:.1} attn={:.1} oproj={:.1} shadow={:.1}",
16354 avg(&T_POS),
16355 avg(&T_QKV),
16356 avg(&T_NORMROPE),
16357 avg(&T_GATE),
16358 avg(&T_APPEND),
16359 avg(&T_ATTN),
16360 avg(&T_OPROJ),
16361 avg(&T_SHADOW),
16362 );
16363 }
16364 }
16365 eprintln!(
16366 "[step-tp-attn] execute layer={} devices={:?} tokens=1 \
16367 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16368 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16369 attention_scope={} input_path={} kv_physical_rows={} \
16370 gate_tensor_parallel=false gate_shards=host-canonical o_tensor_parallel=true \
16371 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16372 bulk_p2p={} output=root-readback performance_claim=false",
16373 tp.layer,
16374 tp.devices,
16375 hydrated,
16376 if window.is_some() {
16377 "rank-local-swa-ring"
16378 } else {
16379 "rank-local-global"
16380 },
16381 input_path,
16382 cache.tp_kv[il]
16383 .as_ref()
16384 .expect("distributed cache checked above")
16385 .physical_capacity(),
16386 tp.runtime.transport_label(),
16387 tp.runtime.bulk_p2p(),
16388 );
16389 Ok(output)
16390 }
16391
16392 #[allow(clippy::too_many_arguments)]
16399 pub(crate) fn step35_verify_qkv_precompute(
16404 &self,
16405 e: &Engine,
16406 il: usize,
16407 h_t: &CudaSlice<f32>,
16408 t: usize,
16409 ) -> Result<bool, Box<dyn std::error::Error>> {
16410 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16411 return Ok(false);
16412 };
16413 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16414 return Ok(false);
16415 };
16416 let Some(attention) = tp.attention.as_ref() else {
16417 return Ok(false);
16418 };
16419 if !tp.runtime.native_p2p() || !crate::tp::step_tp_qkv_fused_enabled()? {
16420 return Ok(false);
16421 }
16422 let geometry = self.step35_geom(il);
16423 let heads = geometry.n_head as usize;
16424 let ws_index = tp
16425 .runtime
16426 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16427 let gate_shards = attention
16428 .gate_shards_bf16
16429 .as_deref()
16430 .map(crate::tp::StepTpGateShards::Bf16);
16431 tp.runtime.decode_v2_input_qkv_tcol(
16432 ws_index,
16433 e,
16434 h_t,
16435 t,
16436 &tp.q,
16437 &tp.k,
16438 &tp.v,
16439 gate_shards,
16440 )?;
16441 Ok(true)
16442 }
16443
16444 pub(crate) fn step35_verify_oproj_tcol(
16449 &self,
16450 e: &Engine,
16451 il: usize,
16452 t: usize,
16453 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16454 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16455 return Err("tcol o_proj join expects full attention".into());
16456 };
16457 let tp = fa
16458 .step_tp_qkv
16459 .as_ref()
16460 .ok_or("tcol o_proj join lost its resident projections")?;
16461 let heads = self.step35_geom(il).n_head as usize;
16462 let ws_index = tp
16463 .runtime
16464 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16465 tp.runtime.decode_v2_oproj_tcol(ws_index, e, &tp.o, t)
16466 }
16467
16468 pub(crate) fn step35_spec_fa2_precheck(
16475 &self,
16476 cache: &Cache,
16477 il: usize,
16478 pos0: usize,
16479 ) -> Result<bool, Box<dyn std::error::Error>> {
16480 fn nope(clause: &str, il: usize, pos0: usize) -> bool {
16483 static DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16484 static SEEN: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
16485 if *DBG.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_DEBUG").as_deref() == Ok("1")) {
16486 let mut seen = SEEN.lock().unwrap();
16487 if !seen.iter().any(|c| *c == clause) {
16488 seen.push(Box::leak(clause.to_string().into_boxed_str()));
16490 eprintln!("[spec-fa2] precheck FAIL clause={clause} il={il} pos0={pos0}");
16491 }
16492 }
16493 false
16494 }
16495 static ONLY: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
16497 if let Some(only) =
16498 ONLY.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_LAYER").ok()?.parse().ok())
16499 {
16500 if *only != il {
16501 return Ok(false);
16502 }
16503 }
16504 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16505 return Ok(nope("mixer", il, pos0));
16506 };
16507 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16508 return Ok(nope("step_tp", il, pos0));
16509 };
16510 let Some(attention) = tp.attention.as_ref() else {
16511 return Ok(nope("attention", il, pos0));
16512 };
16513 if !tp.runtime.native_p2p()
16514 || crate::Engine::kv_fp8_on()
16515 || !crate::tp::step_tp_dcw_enabled()?
16516 || !crate::tp::step_tp_qkv_fused_enabled()?
16517 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16518 {
16519 return Ok(nope("runtime-doors", il, pos0));
16520 }
16521 let geometry = self.step35_geom(il);
16522 let head_dim = geometry.head_dim_k as usize;
16523 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16524 return Ok(nope("fa-class", il, pos0));
16525 }
16526 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16527 return Ok(nope("tp-kv", il, pos0));
16528 };
16529 if distributed.staged_len() != pos0 {
16530 return Ok(nope("staged-len", il, pos0));
16531 }
16532 let (_, would_rebase) = distributed.peek_append_ring(2)?;
16535 if would_rebase {
16536 return Ok(nope("rebase", il, pos0));
16537 }
16538 let window = geometry.window.map(|w| w as usize);
16539 if let Some(w) = window {
16547 if pos0 + 2 > w {
16548 return Ok(nope("swa-capped", il, pos0));
16549 }
16550 }
16551 let (t0, t1) = (pos0 + 1, pos0 + 2);
16555 if t0 < 96 {
16556 return Ok(nope("dcw-floor", il, pos0));
16557 }
16558 if std::env::var("MEMRA_NO_FA_VEC").is_ok() || t0 < crate::fa_vec_min_tkv() {
16559 return Ok(nope("vec-floor", il, pos0));
16560 }
16561 let ranks = tp.runtime.devices().len();
16567 let local_kv_heads = (geometry.n_head_kv as usize / ranks).max(1);
16568 let sp0 = crate::fa_split_keys_pub(t0, local_kv_heads);
16569 let sp1 = crate::fa_split_keys_pub(t1, local_kv_heads);
16570 if sp0 != sp1 {
16571 return Ok(nope("partition-sp", il, pos0));
16572 }
16573 let (ns0, ns1) = (t0.div_ceil(sp0), t1.div_ceil(sp1));
16574 if ns0 != ns1 {
16575 return Ok(nope("partition-ns", il, pos0));
16576 }
16577 if t0.div_ceil(ns0) != t1.div_ceil(ns1) {
16578 return Ok(nope("partition-per", il, pos0));
16579 }
16580 Ok(true)
16581 }
16582
16583 pub(crate) fn step35_fa_rows_precheck(
16588 &self,
16589 cache: &Cache,
16590 il: usize,
16591 pos0: usize,
16592 t: usize,
16593 ) -> Result<bool, Box<dyn std::error::Error>> {
16594 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16595 return Ok(false);
16596 };
16597 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16598 return Ok(false);
16599 };
16600 let Some(attention) = tp.attention.as_ref() else {
16601 return Ok(false);
16602 };
16603 if !tp.runtime.native_p2p()
16604 || crate::Engine::kv_fp8_on()
16605 || !crate::tp::step_tp_dcw_enabled()?
16606 || !crate::tp::step_tp_qkv_fused_enabled()?
16607 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16608 {
16609 return Ok(false);
16610 }
16611 let geometry = self.step35_geom(il);
16612 let head_dim = geometry.head_dim_k as usize;
16613 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16614 return Ok(false);
16615 }
16616 if crate::fa_sm_count() < 128
16617 || std::env::var("MEMRA_FA_SPLIT").is_ok()
16618 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
16619 || std::env::var("MEMRA_FA_SP16").is_ok()
16620 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
16621 {
16622 return Ok(false);
16623 }
16624 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16625 return Ok(false);
16626 };
16627 if distributed.staged_len() != pos0 {
16628 return Ok(false);
16629 }
16630 let (_, would_rebase) = distributed.peek_append_ring(t)?;
16631 if would_rebase {
16632 return Ok(false);
16633 }
16634 let window = geometry.window.map(|w| w as usize);
16637 let t0 = window.map(|w| (pos0 + 1).min(w)).unwrap_or(pos0 + 1);
16638 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
16639 return Ok(false);
16640 }
16641 Ok(true)
16642 }
16643
16644 pub(crate) fn step35_verify_fa_rows_join(
16648 &self,
16649 e: &Engine,
16650 il: usize,
16651 cache: &Cache,
16652 pos0: usize,
16653 t: usize,
16654 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16655 use cudarc::driver::DevicePtr;
16656 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16657 return Err("fa rows join expects full attention".into());
16658 };
16659 let tp = fa
16660 .step_tp_qkv
16661 .as_ref()
16662 .ok_or("fa rows join lost its resident projections")?;
16663 let geometry = self.step35_geom(il);
16664 let heads = geometry.n_head as usize;
16665 let head_dim = geometry.head_dim_k as usize;
16666 let window = geometry.window.map(|w| w as usize);
16667 let distributed = cache.tp_kv[il]
16668 .as_ref()
16669 .ok_or("fa rows join lost its distributed KV cache")?;
16670 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
16671 let ladder = |t_kv: usize| -> usize {
16673 if t_kv <= 2048 {
16674 16
16675 } else if t_kv <= 16384 {
16676 64
16677 } else {
16678 128
16679 }
16680 };
16681 let mut max_ns = 1usize;
16682 for r in 0..t {
16683 let t_kv = window
16684 .map(|w| (pos0 + r + 1).min(w))
16685 .unwrap_or(pos0 + r + 1);
16686 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
16687 }
16688 let ranks = tp.runtime.devices().len();
16693 let mut tables = Vec::with_capacity(ranks);
16694 for rank in 0..ranks {
16695 let engine = tp
16696 .runtime
16697 .rank_engine(rank)
16698 .ok_or("fa rows join lost a rank engine")?;
16699 let rank_cache = distributed
16700 .rank(rank)
16701 .ok_or("fa rows join lost a KV cache rank")?;
16702 let _main = engine.gpu.enter_main()?;
16703 let s = engine.stream();
16704 let (kp, _g0) = rank_cache.k().device_ptr(&s);
16705 let (vp, _g1) = rank_cache.v().device_ptr(&s);
16706 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
16707 let bp = match rank_cache.base_d() {
16708 Some(b) => {
16709 let (p, _g) = b.device_ptr(&s);
16710 p as u64
16711 }
16712 None => 0u64,
16713 };
16714 let mut host = Vec::with_capacity(t * 6);
16715 for r in 0..t {
16716 host.extend_from_slice(&[
16717 kp as u64,
16718 vp as u64,
16719 lp as u64,
16720 bp,
16721 0u64,
16722 (t - 1 - r) as u64,
16723 ]);
16724 }
16725 tables.push(engine.stream().clone_htod(&host)?);
16726 }
16727 let tabs: Vec<&CudaSlice<u64>> = tables.iter().collect();
16728 let ws_index = tp
16729 .runtime
16730 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16731 tp.runtime.decode_v2_fa_rows_join(
16732 ws_index,
16733 e,
16734 &tp.o,
16735 &tabs,
16736 t,
16737 head_dim,
16738 window.unwrap_or(0),
16739 max_ns,
16740 geometry.attention_scale(),
16741 k_tok_bytes,
16742 v_tok_bytes,
16743 )
16744 }
16745
16746 pub(crate) fn step35_batch_fa_rows_precheck(
16750 &self,
16751 caches: &[&mut Cache],
16752 row_to_cache: impl Fn(usize) -> usize,
16753 positions: &[i32],
16754 il: usize,
16755 ) -> Result<bool, Box<dyn std::error::Error>> {
16756 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16757 return Ok(false);
16758 };
16759 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16760 return Ok(false);
16761 };
16762 let Some(attention) = tp.attention.as_ref() else {
16763 return Ok(false);
16764 };
16765 if !tp.runtime.native_p2p()
16766 || crate::Engine::kv_fp8_on()
16767 || !crate::tp::step_tp_dcw_enabled()?
16768 || !crate::tp::step_tp_qkv_fused_enabled()?
16769 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16770 {
16771 return Ok(false);
16772 }
16773 let geometry = self.step35_geom(il);
16774 let head_dim = geometry.head_dim_k as usize;
16775 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16776 return Ok(false);
16777 }
16778 if crate::fa_sm_count() < 128
16779 || std::env::var("MEMRA_FA_SPLIT").is_ok()
16780 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
16781 || std::env::var("MEMRA_FA_SP16").is_ok()
16782 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
16783 {
16784 return Ok(false);
16785 }
16786 let window = geometry.window.map(|w| w as usize);
16787 for (r, &pos) in positions.iter().enumerate() {
16788 let cache = &caches[row_to_cache(r)];
16789 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16790 return Ok(false);
16791 };
16792 if distributed.staged_len() != pos as usize {
16793 return Ok(false);
16794 }
16795 if distributed.peek_append_ring(1)?.1 {
16796 return Ok(false);
16797 }
16798 let t0 = window
16799 .map(|w| (pos as usize + 1).min(w))
16800 .unwrap_or(pos as usize + 1);
16801 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
16802 return Ok(false);
16803 }
16804 }
16805 Ok(true)
16806 }
16807
16808 pub(crate) fn step35_verify_rope_fa_pass(
16814 &self,
16815 e: &Engine,
16816 il: usize,
16817 cache: &Cache,
16818 pos0: usize,
16819 t: usize,
16820 stage_pos: bool,
16821 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16822 use cudarc::driver::DevicePtr;
16823 if !crate::tp::fuse_rope_append_on() {
16824 return Ok(None);
16825 }
16826 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16827 return Ok(None);
16828 };
16829 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16830 return Ok(None);
16831 };
16832 let Some(attention) = tp.attention.as_ref() else {
16833 return Ok(None);
16834 };
16835 let geometry = self.step35_geom(il);
16836 let head_dim = geometry.head_dim_k as usize;
16837 if head_dim != 128 {
16838 return Ok(None);
16839 }
16840 let heads = geometry.n_head as usize;
16841 let window = geometry.window.map(|w| w as usize);
16842 let ranks = tp.runtime.devices().len();
16843 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16844 return Ok(None);
16845 };
16846 if distributed.kv_dim_k() != distributed.kv_dim_v() {
16847 return Ok(None);
16848 }
16849 {
16850 let rank0 = distributed.rank(0).ok_or("verify rope pass lost rank 0")?;
16851 if rank0.base_d().is_none()
16852 && distributed.staged_len() + t > distributed.physical_capacity()
16853 {
16854 return Ok(None);
16855 }
16856 }
16857 let mut rope_freqs = Vec::with_capacity(ranks);
16858 for rank in 0..ranks {
16859 let engine = tp
16860 .runtime
16861 .rank_engine(rank)
16862 .ok_or("verify rope pass lost a rank engine")?;
16863 rope_freqs.push(if geometry.rope_factors {
16864 match self
16865 .step35_aux
16866 .as_ref()
16867 .and_then(|aux| aux.rope_freqs(engine))
16868 {
16869 Some(f) => Some(f),
16870 None => return Ok(None),
16871 }
16872 } else {
16873 None
16874 });
16875 }
16876 let ladder = |t_kv: usize| -> usize {
16877 if t_kv <= 2048 {
16878 16
16879 } else if t_kv <= 16384 {
16880 64
16881 } else {
16882 128
16883 }
16884 };
16885 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
16886 let mut max_ns = 1usize;
16887 let mut positions = Vec::with_capacity(t);
16888 for r in 0..t {
16889 positions.push((pos0 + r) as i32);
16890 let t_kv = window
16891 .map(|w| (pos0 + r + 1).min(w))
16892 .unwrap_or(pos0 + r + 1);
16893 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
16894 }
16895 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
16896 let mut tab_keys = vec![0u64; ranks];
16897 for rank in 0..ranks {
16898 let engine = tp
16899 .runtime
16900 .rank_engine(rank)
16901 .ok_or("verify rope pass lost a rank engine")?;
16902 let rank_cache = distributed
16903 .rank(rank)
16904 .ok_or("verify rope pass lost a KV cache rank")?;
16905 let _main = engine.gpu.enter_main()?;
16906 let s = engine.stream();
16907 let (kp, _g0) = rank_cache.k().device_ptr(&s);
16908 let (vp, _g1) = rank_cache.v().device_ptr(&s);
16909 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
16910 let bp = match rank_cache.base_d() {
16911 Some(b) => {
16912 let (p, _g) = b.device_ptr(&s);
16913 p as u64
16914 }
16915 None => 0u64,
16916 };
16917 tab_keys[rank] = (kp as u64)
16918 .rotate_left(17)
16919 .wrapping_add(bp)
16920 .wrapping_add((il as u64) << 32)
16921 .wrapping_add(t as u64)
16922 .wrapping_add(1 << 63);
16923 for _r in 0..t {
16924 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
16925 }
16926 }
16927 let ws_index = tp
16928 .runtime
16929 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16930 tp.runtime
16931 .decode_v2_rope_fa_rows(
16932 ws_index,
16933 e,
16934 &tp.o,
16935 &session_parts,
16936 &tab_keys,
16937 &positions,
16938 stage_pos,
16939 true,
16940 &attention.q_norm,
16941 &attention.k_norm,
16942 &rope_freqs,
16943 t,
16944 head_dim,
16945 geometry.n_rot as usize,
16946 window.unwrap_or(0),
16947 max_ns,
16948 geometry.attention_scale(),
16949 k_tok_bytes,
16950 v_tok_bytes,
16951 self.cfg.rms_eps,
16952 geometry.rope_base,
16953 )
16954 .map(Some)
16955 }
16956
16957 #[allow(clippy::too_many_arguments)]
16962 pub(crate) fn step35_batch_rope_fa_pass(
16963 &self,
16964 e: &Engine,
16965 il: usize,
16966 caches: &[&mut Cache],
16967 row_to_cache: impl Fn(usize) -> usize,
16968 positions: &[i32],
16969 t: usize,
16970 stage_pos: bool,
16971 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16972 use cudarc::driver::DevicePtr;
16973 if !crate::tp::fuse_rope_append_on() {
16974 return Ok(None);
16975 }
16976 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16977 return Ok(None);
16978 };
16979 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16980 return Ok(None);
16981 };
16982 let Some(attention) = tp.attention.as_ref() else {
16983 return Ok(None);
16984 };
16985 let geometry = self.step35_geom(il);
16986 let head_dim = geometry.head_dim_k as usize;
16987 if head_dim != 128 {
16988 return Ok(None);
16989 }
16990 let heads = geometry.n_head as usize;
16991 let window = geometry.window.map(|w| w as usize);
16992 let ranks = tp.runtime.devices().len();
16993 for r in 0..t {
16996 let cache = &caches[row_to_cache(r)];
16997 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16998 return Ok(None);
16999 };
17000 if distributed.kv_dim_k() != distributed.kv_dim_v() {
17001 return Ok(None);
17002 }
17003 let rank0 = distributed.rank(0).ok_or("rope fa pass lost rank 0")?;
17004 if rank0.base_d().is_none()
17005 && distributed.staged_len() + t > distributed.physical_capacity()
17006 {
17007 return Ok(None);
17008 }
17009 }
17010 let mut rope_freqs = Vec::with_capacity(ranks);
17011 for rank in 0..ranks {
17012 let engine = tp
17013 .runtime
17014 .rank_engine(rank)
17015 .ok_or("rope fa pass lost a rank engine")?;
17016 rope_freqs.push(if geometry.rope_factors {
17017 match self
17018 .step35_aux
17019 .as_ref()
17020 .and_then(|aux| aux.rope_freqs(engine))
17021 {
17022 Some(f) => Some(f),
17023 None => return Ok(None),
17024 }
17025 } else {
17026 None
17027 });
17028 }
17029 let ladder = |t_kv: usize| -> usize {
17030 if t_kv <= 2048 {
17031 16
17032 } else if t_kv <= 16384 {
17033 64
17034 } else {
17035 128
17036 }
17037 };
17038 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17039 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
17040 let mut tab_keys = vec![0u64; ranks];
17041 for (r, &pos) in positions.iter().enumerate().take(t) {
17042 let cache = &caches[row_to_cache(r)];
17043 let distributed = cache.tp_kv[il]
17044 .as_ref()
17045 .ok_or("rope fa pass lost a distributed KV cache")?;
17046 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17047 let t_kv = window
17048 .map(|w| (pos as usize + 1).min(w))
17049 .unwrap_or(pos as usize + 1);
17050 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17051 for rank in 0..ranks {
17052 let engine = tp
17053 .runtime
17054 .rank_engine(rank)
17055 .ok_or("rope fa pass lost a rank engine")?;
17056 let rank_cache = distributed
17057 .rank(rank)
17058 .ok_or("rope fa pass lost a KV cache rank")?;
17059 let _main = engine.gpu.enter_main()?;
17060 let s = engine.stream();
17061 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17062 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17063 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17064 let bp = match rank_cache.base_d() {
17065 Some(b) => {
17066 let (p, _g) = b.device_ptr(&s);
17067 p as u64
17068 }
17069 None => 0u64,
17070 };
17071 tab_keys[rank] = tab_keys[rank]
17072 .rotate_left(9)
17073 .wrapping_add(kp as u64)
17074 .wrapping_add(bp)
17075 .wrapping_add(il as u64);
17076 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
17077 }
17078 }
17079 let ws_index = tp
17080 .runtime
17081 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17082 tp.runtime
17083 .decode_v2_rope_fa_rows(
17084 ws_index,
17085 e,
17086 &tp.o,
17087 &session_parts,
17088 &tab_keys,
17089 positions,
17090 stage_pos,
17091 false,
17092 &attention.q_norm,
17093 &attention.k_norm,
17094 &rope_freqs,
17095 t,
17096 head_dim,
17097 geometry.n_rot as usize,
17098 window.unwrap_or(0),
17099 max_ns,
17100 geometry.attention_scale(),
17101 k_tok_bytes,
17102 v_tok_bytes,
17103 self.cfg.rms_eps,
17104 geometry.rope_base,
17105 )
17106 .map(Some)
17107 }
17108
17109 #[allow(clippy::too_many_arguments)]
17113 pub(crate) fn step35_batch_fa_rows_join(
17114 &self,
17115 e: &Engine,
17116 il: usize,
17117 caches: &[&mut Cache],
17118 row_to_cache: impl Fn(usize) -> usize,
17119 positions: &[i32],
17120 t: usize,
17121 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17122 use cudarc::driver::DevicePtr;
17123 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17124 return Err("batch fa rows join expects full attention".into());
17125 };
17126 let tp = fa
17127 .step_tp_qkv
17128 .as_ref()
17129 .ok_or("batch fa rows join lost its resident projections")?;
17130 let geometry = self.step35_geom(il);
17131 let heads = geometry.n_head as usize;
17132 let head_dim = geometry.head_dim_k as usize;
17133 let window = geometry.window.map(|w| w as usize);
17134 let ladder = |t_kv: usize| -> usize {
17135 if t_kv <= 2048 {
17136 16
17137 } else if t_kv <= 16384 {
17138 64
17139 } else {
17140 128
17141 }
17142 };
17143 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17144 for (r, &pos) in positions.iter().enumerate() {
17145 let cache = &caches[row_to_cache(r)];
17146 let distributed = cache.tp_kv[il]
17147 .as_ref()
17148 .ok_or("batch fa rows join lost a distributed KV cache")?;
17149 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17150 let t_kv = window
17151 .map(|w| (pos as usize + 1).min(w))
17152 .unwrap_or(pos as usize + 1);
17153 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17154 }
17155 let ranks = tp.runtime.devices().len();
17159 let mut tables = Vec::with_capacity(ranks);
17160 for rank in 0..ranks {
17161 let engine = tp
17162 .runtime
17163 .rank_engine(rank)
17164 .ok_or("batch fa rows join lost a rank engine")?;
17165 let _main = engine.gpu.enter_main()?;
17166 let s = engine.stream();
17167 let mut host = Vec::with_capacity(t * 6);
17168 for r in 0..t {
17169 let cache = &caches[row_to_cache(r)];
17170 let distributed = cache.tp_kv[il]
17171 .as_ref()
17172 .ok_or("batch fa rows join lost a distributed KV cache")?;
17173 let rank_cache = distributed
17174 .rank(rank)
17175 .ok_or("batch fa rows join lost a KV cache rank")?;
17176 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17177 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17178 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17179 let bp = match rank_cache.base_d() {
17180 Some(b) => {
17181 let (p, _g) = b.device_ptr(&s);
17182 p as u64
17183 }
17184 None => 0u64,
17185 };
17186 host.extend_from_slice(&[kp as u64, vp as u64, lp as u64, bp, 0u64, 0u64]);
17187 }
17188 tables.push(engine.stream().clone_htod(&host)?);
17189 }
17190 let tabs: Vec<&CudaSlice<u64>> = tables.iter().collect();
17191 let ws_index = tp
17192 .runtime
17193 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17194 tp.runtime.decode_v2_fa_rows_join(
17195 ws_index,
17196 e,
17197 &tp.o,
17198 &tabs,
17199 t,
17200 head_dim,
17201 window.unwrap_or(0),
17202 max_ns,
17203 geometry.attention_scale(),
17204 k_tok_bytes,
17205 v_tok_bytes,
17206 )
17207 }
17208
17209 pub(crate) fn step35_verify_spec_fa2_join(
17213 &self,
17214 e: &Engine,
17215 il: usize,
17216 cache: &Cache,
17217 pos0: usize,
17218 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17219 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17220 return Err("spec fa2 join expects full attention".into());
17221 };
17222 let tp = fa
17223 .step_tp_qkv
17224 .as_ref()
17225 .ok_or("spec fa2 join lost its resident projections")?;
17226 let geometry = self.step35_geom(il);
17227 let heads = geometry.n_head as usize;
17228 let head_dim = geometry.head_dim_k as usize;
17229 let window = geometry.window.map(|w| w as usize);
17230 let bucket = window.map(|w| (pos0 + 2).min(w)).unwrap_or(pos0 + 2);
17233 let distributed = cache.tp_kv[il]
17234 .as_ref()
17235 .ok_or("spec fa2 join lost its distributed KV cache")?;
17236 let ws_index = tp
17237 .runtime
17238 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17239 tp.runtime.decode_v2_spec_fa2_join(
17240 ws_index,
17241 e,
17242 &tp.o,
17243 distributed,
17244 head_dim,
17245 window.unwrap_or(0),
17246 bucket,
17247 geometry.attention_scale(),
17248 )
17249 }
17250
17251 pub(crate) fn step35_verify_moe_tn(
17257 &self,
17258 e: &Engine,
17259 il: usize,
17260 z_t: &CudaSlice<f32>,
17261 t: usize,
17262 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
17263 let layer = &self.layers[il];
17264 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
17265 return Ok(None);
17266 };
17267 let Some(tp) = m.step_tp.as_ref() else {
17268 return Ok(None);
17269 };
17270 let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts else {
17271 return Ok(None);
17272 };
17273 if !crate::tp::step_nvfp4_dev_routes_enabled()?
17274 || !crate::tp::step_tp_dev_router_enabled()?
17275 || !crate::tp::nvfp4_bank_v2_on()
17276 || bank.ep2
17277 {
17278 return Ok(None);
17279 }
17280 let cfg = &self.cfg;
17281 let Some(moe) = cfg.moe.as_ref() else {
17282 return Ok(None);
17283 };
17284 let Some((sf, route_norm)) = cfg.sigmoid_router() else {
17285 return Ok(None);
17286 };
17287 let n_embd = cfg.n_embd as usize;
17288 let n_expert = moe.expert_count as usize;
17289 let n_used = moe.expert_used_count as usize;
17290 if t < 2 || t > 32 || z_t.len() < t * n_embd {
17291 return Err("verify moe t-row geometry".into());
17292 }
17293 let trace = std::env::var("MEMRA_TN_TRACE").as_deref() == Ok("1");
17294 if trace {
17295 eprintln!("[tn-trace] il={il} t={t} logits");
17296 }
17297 let logits = Self::moe_router_logits(e, m, z_t, t, cfg)?;
17298 static SELW2: std::sync::Mutex<Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>> =
17301 std::sync::Mutex::new(None);
17302 let mut selw = SELW2.lock().map_err(|_| "selw2 lock poisoned")?;
17303 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
17304 *selw = Some((
17305 e.ctx().ordinal(),
17306 e.htod_i32(&vec![0i32; 32 * n_used])?,
17307 e.htod(&vec![0.0f32; 32 * n_used])?,
17308 ));
17309 }
17310 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
17311 if trace {
17312 eprintln!("[tn-trace] il={il} topk logits_len={}", logits.len());
17313 }
17314 e.moe_router_sigmoid_topk_into(
17315 &logits,
17316 t,
17317 n_expert,
17318 n_used,
17319 m.active_count(),
17320 &m.exp_probs_b_dev,
17321 &m.active_experts_dev,
17322 sf,
17323 route_norm,
17324 sel_d,
17325 w_d,
17326 )?;
17327 if trace {
17328 eprintln!("[tn-trace] il={il} driver");
17329 }
17330 let mut out_t = tp
17331 .runtime
17332 .run_tensor_parallel_routes_nvfp4_device_routed_tn(
17333 bank,
17334 e,
17335 z_t,
17336 sel_d,
17337 w_d,
17338 t,
17339 n_used,
17340 tp.activation_limit,
17341 )?;
17342 if trace {
17343 eprintln!("[tn-trace] il={il} shexp out_t={}", out_t.len());
17344 }
17345 if !Self::step35_shexp_rows(e, m, z_t, t, cfg, il as u16, &mut out_t)? {
17349 let mut z_row = e.uninit(n_embd)?;
17350 let mut out_row = e.uninit(n_embd)?;
17351 for c in 0..t {
17352 e.dtod_copy_view(&z_t.slice(c * n_embd..(c + 1) * n_embd), &mut z_row)?;
17353 e.dtod_copy_view(&out_t.slice(c * n_embd..(c + 1) * n_embd), &mut out_row)?;
17354 Self::moe_ffn_grouped_add_shared(e, m, &z_row, 1, cfg, il as u16, &mut out_row)?;
17355 e.dtod_copy_into(&out_row, &mut out_t, c * n_embd)?;
17356 }
17357 }
17358 Ok(Some(out_t))
17359 }
17360
17361 fn step35_shexp_rows(
17365 e: &Engine,
17366 m: &MoeWeights,
17367 z_t: &CudaSlice<f32>,
17368 t: usize,
17369 cfg: &ModelConfig,
17370 il: u16,
17371 out_t: &mut CudaSlice<f32>,
17372 ) -> Result<bool, Box<dyn std::error::Error>> {
17373 let n_embd = cfg.n_embd as usize;
17374 let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
17375 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
17376 else {
17377 return Ok(false);
17378 };
17379 if !crate::Engine::bf16_mmv_on() || n_embd % 8 != 0 || cfg.m3.is_some() {
17380 return Ok(false);
17381 }
17382 let (
17383 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
17384 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
17385 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
17386 ) = (gate_shexp, up_shexp, down_shexp)
17387 else {
17388 return Ok(false);
17389 };
17390 let n_ff_sh = gate_shexp.out_features();
17391 let lim = cfg.clamp_shexp_at(il as u32);
17392 static WS: std::sync::Mutex<Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>> =
17394 std::sync::Mutex::new(None);
17395 let mut guard = WS.lock().map_err(|_| "shexp rows ws lock is poisoned")?;
17396 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
17397 if guard
17398 .as_ref()
17399 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
17400 {
17401 *guard = Some((
17402 pins.0,
17403 pins.1,
17404 pins.2,
17405 e.uninit(32 * n_ff_sh)?,
17406 e.uninit(32 * n_embd)?,
17407 ));
17408 }
17409 let (_, _, _, act_t, sh_t) = guard.as_mut().expect("armed above");
17410 e.matvec_bf16_dual_silu_rows_into(wg, wu, z_t, act_t, n_embd, n_ff_sh, lim, t)?;
17411 e.matvec_bf16_rows_into(wd, act_t, sh_t, n_ff_sh, n_embd, t)?;
17412 let gate = match &m.gate_inp_shexp {
17415 Some(gate_inp_shexp) => {
17416 e.sigmoid_dot_rows(z_t, gate_inp_shexp.float_data(), n_embd, t)?
17417 }
17418 None => e.htod(&vec![1.0f32; t])?,
17419 };
17420 e.add_scaled_rows(sh_t, &gate, out_t, n_embd, t)?;
17421 Ok(true)
17422 }
17423
17424 fn step35_tp_decode_attn_resident_v2(
17425 &self,
17426 e: &Engine,
17427 fa: &FullAttnLayer,
17428 il: usize,
17429 h: &CudaSlice<f32>,
17430 pos_d: &CudaSlice<i32>,
17431 cache: &mut Cache,
17432 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17433 let tp = fa
17434 .step_tp_qkv
17435 .as_ref()
17436 .ok_or("Step TP decode lost its resident projections")?;
17437 let attention = tp
17438 .attention
17439 .as_ref()
17440 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
17441 if !tp.runtime.native_p2p() {
17442 return Err("rank-local Step attention requires native P2P".into());
17443 }
17444 if crate::Engine::kv_fp8_on() {
17445 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
17446 }
17447
17448 let geometry = self.step35_geom(il);
17449 let window = geometry.window.map(|window| window as usize);
17450 let ranks = tp.runtime.devices().len();
17451 let head_dim = geometry.head_dim_k as usize;
17452 let heads = geometry.n_head as usize;
17453 let kv_heads = geometry.n_head_kv as usize;
17454 if heads % ranks != 0 || kv_heads % ranks != 0 {
17455 return Err(format!(
17456 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
17457 )
17458 .into());
17459 }
17460 let local_heads = heads / ranks;
17461 let local_kv_heads = kv_heads / ranks;
17462 let max_ctx = cache.max_ctx;
17463
17464 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
17465
17466 let base_len = cache.kv[il]
17467 .as_ref()
17468 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
17469 .len;
17470 {
17471 let distributed = cache.tp_kv[il]
17472 .as_ref()
17473 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
17474 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
17475 return Err(format!(
17476 "Step TP layer {il} cache lengths diverged before decode: \
17477 local={base_len} distributed={}/{}",
17478 distributed.committed_len(),
17479 distributed.staged_len()
17480 )
17481 .into());
17482 }
17483 }
17484 if pos_d.len() != 1 {
17485 return Err(format!(
17486 "rank-local Step decode requires one position, got {}",
17487 pos_d.len()
17488 )
17489 .into());
17490 }
17491
17492 let decode_input = attention
17493 .decode_input
17494 .as_ref()
17495 .ok_or("Step TP decode v2 requires the replicated decode input")?;
17496 let mut decode_input = decode_input
17497 .lock()
17498 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
17499
17500 let use_gate_shards = (attention.gate_shards.is_some()
17505 || attention.gate_shards_bf16.is_some())
17506 && crate::tp::step_tp_qkv_fused_enabled()?;
17507 let gate_raw = if use_gate_shards {
17508 None
17509 } else {
17510 let gate_weight = fa
17511 .attn_gate
17512 .as_ref()
17513 .ok_or("step35 layer is missing attn_gate.weight")?;
17514 let gate_raw = e.matmul(gate_weight, h, 1)?;
17515 if gate_raw.len() != heads {
17516 return Err(format!(
17517 "Step TP layer {il} gate output {} != {heads}",
17518 gate_raw.len()
17519 )
17520 .into());
17521 }
17522 Some(gate_raw)
17523 };
17524
17525 let ws_index = tp
17526 .runtime
17527 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17528 let mut ws_guard = tp
17529 .runtime
17530 .decode_v2_workspace()
17531 .lock()
17532 .map_err(|_| "Step TP decode v2 workspace lock is poisoned")?;
17533 let ws = ws_guard
17534 .get_mut(ws_index)
17535 .ok_or("Step TP decode v2 workspace missing after ensure")?;
17536
17537 let mut rope_freqs = Vec::with_capacity(ranks);
17538 for rank in 0..ranks {
17539 let engine = tp
17540 .runtime
17541 .rank_engine(rank)
17542 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
17543 rope_freqs.push(if geometry.rope_factors {
17544 self.step35_aux
17545 .as_ref()
17546 .and_then(|aux| aux.rope_freqs(engine))
17547 } else {
17548 None
17549 });
17550 }
17551 let staged_next = base_len + 1;
17558 let t_kv_eff = window
17559 .map(|window| staged_next.min(window))
17560 .unwrap_or(staged_next);
17561 let dcw = crate::tp::step_tp_dcw_enabled()? && use_gate_shards && t_kv_eff >= 96 && {
17562 let (write_row, would_rebase) = cache.tp_kv[il]
17563 .as_ref()
17564 .expect("distributed cache checked above")
17565 .peek_append_ring(1)?;
17566 if !would_rebase {
17567 let base = (base_len - write_row) as i32;
17569 let distributed = cache.tp_kv[il]
17570 .as_mut()
17571 .expect("distributed cache checked above");
17572 for rank in 0..ranks {
17573 let engine = tp.runtime.rank_engine(rank).ok_or_else(|| {
17574 format!("Step TP layer {il} has no engine for rank {rank}")
17575 })?;
17576 let _main = engine.gpu.enter_main()?;
17577 let rank_cache = distributed
17578 .rank_mut(rank)
17579 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17580 if rank_cache.base_d().is_none() {
17581 rank_cache.arm_base_d(engine.htod_i32(&[base])?);
17582 }
17583 }
17584 }
17585 !would_rebase
17586 };
17587 let fuse_rope = dcw
17588 && crate::tp::fuse_rope_append_on()
17589 && head_dim == 128
17590 && cache.tp_kv[il]
17591 .as_ref()
17592 .map(|d| d.kv_dim_k() == d.kv_dim_v() && d.kv_dim_k() == local_kv_heads * head_dim)
17593 .unwrap_or(false);
17594
17595 let tcol_col = crate::tp::take_verify_tcol();
17596 let fa2_col = crate::tp::take_spec_fa2_defer();
17603 tp.runtime.decode_v2_input_qkv(
17604 ws,
17605 e,
17606 h,
17607 pos_d,
17608 gate_raw.as_ref(),
17609 if !use_gate_shards {
17610 None
17611 } else if let Some(shards) = attention.gate_shards.as_deref() {
17612 Some(crate::tp::StepTpGateShards::F32(shards))
17613 } else {
17614 attention
17615 .gate_shards_bf16
17616 .as_deref()
17617 .map(crate::tp::StepTpGateShards::Bf16)
17618 },
17619 &mut decode_input,
17620 &tp.q,
17621 &tp.k,
17622 &tp.v,
17623 &attention.q_norm,
17624 &attention.k_norm,
17625 head_dim,
17626 geometry.n_rot as usize,
17627 geometry.rope_base,
17628 &rope_freqs,
17629 self.cfg.rms_eps,
17630 fuse_rope,
17631 tcol_col,
17632 )?;
17633
17634 let transaction = cache.tp_kv[il]
17635 .as_mut()
17636 .expect("distributed cache checked above")
17637 .begin_transaction()?;
17638 let append_result = tp.runtime.append_tp_kv_transaction_inner(
17639 cache.tp_kv[il]
17640 .as_mut()
17641 .expect("distributed cache checked above"),
17642 transaction,
17643 &ws.k,
17644 &ws.v_raw,
17645 1,
17646 dcw,
17647 );
17648 if let Err(error) = append_result {
17649 let _ = tp.runtime.rollback_tp_kv_transaction(
17650 cache.tp_kv[il]
17651 .as_mut()
17652 .expect("distributed cache checked above"),
17653 transaction,
17654 );
17655 return Err(error);
17656 }
17657
17658 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17659 let (staged_len, physical, k_tok_bytes_c, v_tok_bytes_c, capacity) = {
17662 let distributed = cache.tp_kv[il]
17663 .as_ref()
17664 .expect("distributed cache checked above");
17665 let staged_len = distributed.staged_len();
17666 let view_start = window
17667 .map(|window| staged_len.saturating_sub(window))
17668 .unwrap_or(0);
17669 (
17670 staged_len,
17671 distributed.physical_range(view_start, staged_len)?,
17672 distributed.k_tok_bytes(),
17673 distributed.v_tok_bytes(),
17674 distributed.physical_capacity(),
17675 )
17676 };
17677 let view_start = window
17678 .map(|window| staged_len.saturating_sub(window))
17679 .unwrap_or(0);
17680 let t_kv = staged_len - view_start;
17681 for rank in 0..ranks {
17682 let engine = tp
17683 .runtime
17684 .rank_engine(rank)
17685 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
17686 let _main = engine.gpu.enter_main()?;
17687 if dcw {
17688 {
17692 let distributed_mut = cache.tp_kv[il]
17693 .as_mut()
17694 .expect("distributed cache checked above");
17695 let (kv_dim_k, kv_dim_v) =
17696 (distributed_mut.kv_dim_k(), distributed_mut.kv_dim_v());
17697 let (k_tok_bytes, v_tok_bytes) =
17698 (distributed_mut.k_tok_bytes(), distributed_mut.v_tok_bytes());
17699 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
17700 format!("Step TP layer {il} has no KV cache rank {rank}")
17701 })?;
17702 let (k_plane, v_plane, len_d, base_d) =
17703 rank_cache.planes_and_counters_mut();
17704 if fuse_rope {
17705 let same_dev = engine.ctx().ordinal() == e.ctx().ordinal();
17708 let crate::tp::StepTpDecodeV2Ws {
17709 q_raw,
17710 k_raw,
17711 v_raw,
17712 q,
17713 k,
17714 pos,
17715 pos_stage,
17716 fuse_ctr,
17717 ..
17718 } = &mut *ws;
17719 let pos_ref: &CudaSlice<i32> = if same_dev {
17723 pos_stage
17724 .as_ref()
17725 .ok_or("step TP decode v2 pos stage not armed")?
17726 } else {
17727 &pos[rank]
17728 };
17729 engine.qk_norm_rope_append_inc_dcw(
17730 &q_raw[rank],
17731 &k_raw[rank],
17732 &v_raw[rank],
17733 &attention.q_norm[rank],
17734 &attention.k_norm[rank],
17735 &mut q[rank],
17736 &mut k[rank],
17737 pos_ref,
17738 k_plane,
17739 v_plane,
17740 len_d,
17741 base_d,
17742 &mut fuse_ctr[rank],
17743 kv_dim_k,
17744 kv_dim_v,
17745 k_tok_bytes,
17746 v_tok_bytes,
17747 head_dim,
17748 geometry.n_rot as usize,
17749 local_heads,
17750 local_kv_heads,
17751 self.cfg.rms_eps,
17752 geometry.rope_base,
17753 1.0,
17754 rope_freqs[rank],
17755 )?;
17756 } else {
17757 engine.append_kv_quantized_dcw(
17758 &ws.k[rank],
17759 &ws.v_raw[rank],
17760 k_plane,
17761 v_plane,
17762 len_d,
17763 base_d,
17764 kv_dim_k,
17765 kv_dim_v,
17766 k_tok_bytes,
17767 v_tok_bytes,
17768 )?;
17769 }
17770 if !fuse_rope {
17771 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
17772 format!("Step TP layer {il} has no KV cache rank {rank}")
17773 })?;
17774 engine.inc_i32(rank_cache.len_d_mut())?;
17775 }
17776 }
17777 let distributed = cache.tp_kv[il]
17778 .as_ref()
17779 .expect("distributed cache checked above");
17780 let rank_cache = distributed
17781 .rank(rank)
17782 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17783 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes_c);
17784 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes_c);
17785 if fa2_col.is_some() {
17786 continue;
17789 }
17790 {
17791 let crate::tp::StepTpDecodeV2Ws { q, gate, gated, .. } = &mut *ws;
17794 engine.fa_decode_dcw(
17795 &q[rank],
17796 &k_ring,
17797 &v_ring,
17798 &mut gated[rank],
17799 head_dim,
17800 local_heads,
17801 local_kv_heads,
17802 rank_cache.len_d(),
17803 rank_cache.base_d(),
17804 window.unwrap_or(0),
17805 t_kv,
17806 geometry.attention_scale(),
17807 k_tok_bytes_c,
17808 v_tok_bytes_c,
17809 Some(&gate[rank]),
17810 )?;
17811 }
17812 continue;
17813 }
17814 let distributed = cache.tp_kv[il]
17815 .as_ref()
17816 .expect("distributed cache checked above");
17817 let rank_cache = distributed
17818 .rank(rank)
17819 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17820 let k_view = engine.view_u8_range(
17821 rank_cache.k(),
17822 physical.start * k_tok_bytes_c,
17823 physical.end * k_tok_bytes_c,
17824 );
17825 let v_view = engine.view_u8_range(
17826 rank_cache.v(),
17827 physical.start * v_tok_bytes_c,
17828 physical.end * v_tok_bytes_c,
17829 );
17830 engine.fa_decode_kvmod(
17831 &ws.q[rank],
17832 &k_view,
17833 &v_view,
17834 &mut ws.attn_out[rank],
17835 head_dim,
17836 local_heads,
17837 local_kv_heads,
17838 t_kv,
17839 geometry.attention_scale(),
17840 k_tok_bytes_c,
17841 v_tok_bytes_c,
17842 false,
17843 )?;
17844 engine.attn_head_gate(
17845 &ws.attn_out[rank],
17846 &ws.gate[rank],
17847 &mut ws.gated[rank],
17848 None,
17849 head_dim,
17850 local_heads,
17851 1,
17852 )?;
17853 }
17854
17855 let output = if let Some(col) = fa2_col.filter(|_| dcw) {
17862 tp.runtime.decode_v2_stash_fa2(ws, e, col)?;
17866 crate::tp::set_spec_fa2_stashed();
17867 e.uninit(ws.o_out)?
17868 } else if let Some(col) = crate::tp::take_tcol_oproj_defer() {
17869 if tp.runtime.decode_v2_oproj_tcol_eligible(ws, &tp.o) {
17870 tp.runtime.decode_v2_stash_gated(ws, e, col)?;
17871 crate::tp::set_tcol_oproj_stashed();
17872 e.uninit(ws.o_out)?
17873 } else {
17874 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17875 }
17876 } else {
17877 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17878 };
17879
17880 let local = cache.kv[il]
17884 .as_mut()
17885 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
17886 if local.len != base_len || base_len + 1 > max_ctx {
17887 return Err(format!(
17888 "Step TP layer {il} local cache changed during decode: \
17889 len={} base={base_len} max={max_ctx}",
17890 local.len
17891 )
17892 .into());
17893 }
17894 if crate::tp::no_local_shadow_on() {
17895 local.len = base_len + 1;
17898 if !crate::tp::len_mirror_lazy_on() {
17902 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17903 }
17904 } else {
17905 let retain_from = window
17906 .map(|window| {
17907 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
17908 let rollback_retain =
17909 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
17910 staged_retain.min(rollback_retain)
17911 })
17912 .unwrap_or(0);
17913 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
17914 e.append_kv_quantized(
17915 &ws.k_shadow,
17916 &ws.v_shadow,
17917 &mut local.k,
17918 &mut local.v,
17919 write_row,
17920 local.kv_dim_k,
17921 local.kv_dim_v,
17922 local.k_tok_bytes,
17923 local.v_tok_bytes,
17924 false,
17925 )?;
17926 local.len = base_len + 1;
17927 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17928 }
17929 Ok(output)
17930 })();
17931
17932 let output = match staged {
17933 Ok(output) => output,
17934 Err(error) => {
17935 let _ = tp.runtime.rollback_tp_kv_transaction(
17936 cache.tp_kv[il]
17937 .as_mut()
17938 .expect("distributed cache checked above"),
17939 transaction,
17940 );
17941 if let Some(local) = cache.kv[il].as_mut() {
17942 local.len = base_len;
17943 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
17944 }
17945 return Err(error);
17946 }
17947 };
17948 let lazy_commit = fuse_rope && crate::tp::len_mirror_lazy_on();
17953 if lazy_commit {
17954 if let Err(error) = tp.runtime.commit_tp_kv_transaction_external(
17955 cache.tp_kv[il]
17956 .as_mut()
17957 .expect("distributed cache checked above"),
17958 transaction,
17959 1,
17960 ) {
17961 let _ = tp.runtime.rollback_tp_kv_transaction(
17962 cache.tp_kv[il]
17963 .as_mut()
17964 .expect("distributed cache checked above"),
17965 transaction,
17966 );
17967 let local = cache.kv[il].as_mut().expect("local cache checked above");
17968 local.len = base_len;
17969 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17970 return Err(error);
17971 }
17972 } else if let Err(error) = tp.runtime.commit_tp_kv_transaction(
17973 cache.tp_kv[il]
17974 .as_mut()
17975 .expect("distributed cache checked above"),
17976 transaction,
17977 1,
17978 ) {
17979 let _ = tp.runtime.rollback_tp_kv_transaction(
17980 cache.tp_kv[il]
17981 .as_mut()
17982 .expect("distributed cache checked above"),
17983 transaction,
17984 );
17985 let local = cache.kv[il].as_mut().expect("local cache checked above");
17986 local.len = base_len;
17987 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17988 return Err(error);
17989 }
17990
17991 let committed = cache.tp_kv[il]
17992 .as_ref()
17993 .expect("distributed cache checked above")
17994 .committed_len();
17995 let local_len = cache.kv[il]
17996 .as_ref()
17997 .expect("local cache checked above")
17998 .len;
17999 if committed != local_len {
18000 return Err(format!(
18001 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
18002 )
18003 .into());
18004 }
18005 static V2_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
18006 if !V2_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
18007 eprintln!(
18008 "[step-tp-attn-v2] execute layer={} devices={:?} tokens=1 driver=v2 \
18009 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
18010 kv_cache_distributed=true kv_cache_hydrated={hydrated} \
18011 attention_tensor_parallel=true attention_scope={} \
18012 input_path=root-device-replicated gate_tensor_parallel=false \
18013 gate_shards=device-staged o_tensor_parallel=true o_reduce=root-device \
18014 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
18015 bulk_p2p={} workspace=persistent ordering=evented output=e-device \
18016 performance_claim=false (logged once; every decode layer runs this driver)",
18017 tp.layer,
18018 tp.devices,
18019 if window.is_some() {
18020 "rank-local-swa-ring"
18021 } else {
18022 "rank-local-global"
18023 },
18024 tp.runtime.transport_label(),
18025 tp.runtime.bulk_p2p(),
18026 );
18027 }
18028 Ok(output)
18029 }
18030
18031 #[allow(clippy::too_many_arguments)]
18041 pub(crate) fn step35_decode_attn(
18042 &self,
18043 e: &Engine,
18044 fa: &FullAttnLayer,
18045 il: usize,
18046 h: &CudaSlice<f32>,
18047 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
18048 pos_d: &CudaSlice<i32>,
18049 cache: &mut Cache,
18050 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18051 if fa
18052 .step_tp_qkv
18053 .as_ref()
18054 .is_some_and(|tp| tp.attention.is_some())
18055 {
18056 if pre_q.is_some() {
18057 return Err(
18058 "rank-local Step attention preserves BF16 activations and refuses the q8_1 \
18059 pre-quantized decode path"
18060 .into(),
18061 );
18062 }
18063 return self.step35_tp_decode_attn_resident(e, fa, il, h, pos_d, cache);
18064 }
18065
18066 let geometry = self.step35_geom(il);
18067 let hd = geometry.head_dim_k as usize;
18068 let nkv = geometry.n_head_kv as usize;
18069 let nh = geometry.n_head as usize;
18070 let rbase = geometry.rope_base;
18071 let scale = geometry.attention_scale();
18072 let swa = geometry.window.is_some();
18073 let eps = self.cfg.rms_eps;
18074 let win = geometry.window.unwrap_or(0) as usize;
18075 let n_rot = geometry.n_rot as usize;
18076 let n_embd = self.cfg.n_embd as usize;
18077 let gw = fa
18078 .attn_gate
18079 .as_ref()
18080 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
18081
18082 let tp_qkv = if fa.step_tp_qkv.is_some() {
18083 if pre_q.is_some() {
18084 return Err(
18085 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
18086 pre-quantized decode path"
18087 .into(),
18088 );
18089 }
18090 self.step35_tp_qkv(e, fa, h, 1)?
18091 } else {
18092 None
18093 };
18094
18095 let (q0, k0, v0, gt) = match tp_qkv {
18096 Some(mut g3) => {
18097 let v = g3.pop().unwrap();
18098 let k = g3.pop().unwrap();
18099 let q = g3.pop().unwrap();
18100 let gt = e.matmul(gw, h, 1)?;
18101 (q, k, v, gt)
18102 }
18103 None => match pre_q {
18104 Some((hq, hdq)) => {
18105 debug_assert!(
18106 e.uses_q8_1_fast(gw),
18107 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
18108 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
18109 );
18110 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
18111 Some(t3) => t3,
18112 None => (
18113 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
18114 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
18115 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
18116 ),
18117 };
18118 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
18119 (a, b, c, gt)
18120 }
18121 None => {
18122 if e.uses_q8_1_fast(&fa.wq)
18123 && e.uses_q8_1_fast(&fa.wk)
18124 && e.uses_q8_1_fast(&fa.wv)
18125 && e.uses_q8_1_fast(gw)
18126 {
18127 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
18128 let (a, b, c) =
18129 match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
18130 Some(t3) => t3,
18131 None => (
18132 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
18133 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
18134 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
18135 ),
18136 };
18137 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
18138 (a, b, c, gt)
18139 } else {
18140 (
18141 e.matmul(&fa.wq, h, 1)?,
18142 e.matmul(&fa.wk, h, 1)?,
18143 e.matmul(&fa.wv, h, 1)?,
18144 e.matmul(gw, h, 1)?,
18145 )
18146 }
18147 }
18148 },
18149 };
18150
18151 let mut q = e.uninit(nh * hd)?;
18152 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
18153 let mut k = e.uninit(nkv * hd)?;
18154 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
18155 let ff = if swa {
18156 None
18157 } else {
18158 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
18159 };
18160 #[cfg(debug_assertions)]
18161 if let Some(ff) = ff {
18162 crate::debug_assert_tensor_stream_device(
18163 ff,
18164 &e.stream(),
18165 "step35_decode_attn.rope_freqs",
18166 );
18167 }
18168 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
18169
18170 if std::env::var("MEMRA_NOFA").is_ok() {
18171 return Err(
18172 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
18173 cache; unset MEMRA_NOFA to use fa_decode"
18174 .into(),
18175 );
18176 }
18177 let kvl = cache.kv[il].as_mut().unwrap();
18178 let next_len = kvl.len + 1;
18179 let (off, t_kv) = if swa && next_len > win {
18180 (next_len - win, win)
18181 } else {
18182 (0, next_len)
18183 };
18184 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
18185 e.append_kv_quantized(
18186 &k,
18187 &v0,
18188 &mut kvl.k,
18189 &mut kvl.v,
18190 write_row,
18191 kvl.kv_dim_k,
18192 kvl.kv_dim_v,
18193 kvl.k_tok_bytes,
18194 kvl.v_tok_bytes,
18195 crate::Engine::kv_fp8_on(),
18196 )?;
18197 kvl.len = next_len;
18198 let physical = kvl.physical_rows(off, off + t_kv)?;
18199 let k_view = e.view_u8_range(
18200 &kvl.k,
18201 physical.start * kvl.k_tok_bytes,
18202 physical.end * kvl.k_tok_bytes,
18203 );
18204 let v_view = e.view_u8_range(
18205 &kvl.v,
18206 physical.start * kvl.v_tok_bytes,
18207 physical.end * kvl.v_tok_bytes,
18208 );
18209 let mut attn = e.uninit(nh * hd)?;
18210 e.fa_decode_kvmod(
18211 &q,
18212 &k_view,
18213 &v_view,
18214 &mut attn,
18215 hd,
18216 nh,
18217 nkv,
18218 t_kv,
18219 scale,
18220 kvl.k_tok_bytes,
18221 kvl.v_tok_bytes,
18222 crate::Engine::kv_fp8_on(),
18223 )?;
18224
18225 let mut ag = e.uninit(nh * hd)?;
18226 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
18227 self.step35_o(e, fa, &ag, 1)
18228 }
18229}
18230
18231impl HybridModel {
18240 pub fn is_gemma4_e4b(&self) -> bool {
18241 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
18242 }
18243
18244 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
18248 let g = self.cfg.gemma4.as_ref().unwrap();
18249 let swa = g.swa_pattern[il];
18250 let hd = if swa {
18251 g.key_length_swa
18252 } else {
18253 g.key_length_global
18254 } as usize;
18255 let Mixer::Full(fa) = &self.layers[il].mixer else {
18256 panic!("e4b layer {il} not full-attn")
18257 };
18258 let nh = fa.wq.out_features() / hd;
18259 let nkv = fa.wk.out_features() / hd;
18260 (
18261 hd,
18262 nkv,
18263 nh,
18264 if swa {
18265 g.rope_base_swa
18266 } else {
18267 g.rope_base_global
18268 },
18269 1.0,
18270 swa,
18271 )
18272 }
18273
18274 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
18276 self.layers[il]
18277 .gemma4
18278 .as_ref()
18279 .and_then(|b| b.e4b.as_ref())
18280 .and_then(|e4| e4.kv_share.map(|t| t as usize))
18281 }
18282
18283 fn gemma4_e4b_inp_pl(
18288 &self,
18289 e: &Engine,
18290 tokens: &[u32],
18291 x_scaled: &CudaSlice<f32>,
18292 t: usize,
18293 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18294 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
18295 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
18296 }
18297
18298 fn gemma4_e4b_inp_pl_dev(
18300 &self,
18301 e: &Engine,
18302 tok_d: &CudaSlice<u32>,
18303 x_scaled: &CudaSlice<f32>,
18304 t: usize,
18305 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18306 let aux = self.gemma4_aux.as_ref().unwrap();
18307 let m = aux.e4b.as_ref().unwrap();
18308 let n_embd = self.cfg.n_embd as usize;
18309 let n_layer = self.layers.len();
18310 let width = m.n_epl * n_layer;
18311 let tbl = m.tok_tbl_gpu.get_or_init(|| {
18312 e.upload_u8(&m.tok_embd_bytes)
18313 .expect("e4b per-layer token table upload")
18314 });
18315 let mut a =
18316 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
18317 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
18318 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
18319 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
18320 let mut pn = e.uninit(t * width)?;
18321 e.rms_norm(
18322 &p,
18323 m.proj_norm.float_data(),
18324 &mut pn,
18325 m.n_epl,
18326 t * n_layer,
18327 self.cfg.rms_eps,
18328 )?;
18329 let mut out = e.uninit(t * width)?;
18330 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
18331 Ok(out)
18332 }
18333
18334 #[allow(clippy::too_many_arguments)]
18339 fn gemma4_e4b_attn(
18340 &self,
18341 e: &Engine,
18342 il: usize,
18343 hq: &CudaSlice<i8>,
18344 hdq: &CudaSlice<f32>,
18345 pos_d: &CudaSlice<i32>,
18346 t: usize,
18347 cache: &mut Cache,
18348 dc_bucket: Option<usize>,
18349 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18350 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
18351 let eps = self.cfg.rms_eps;
18352 let aux = self.gemma4_aux.as_ref().unwrap();
18353 let ones = aux.ones(e);
18354 #[cfg(debug_assertions)]
18355 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
18356 let Mixer::Full(fa) = &self.layers[il].mixer else {
18357 unreachable!()
18358 };
18359 let h0 = e.zeros(0)?;
18363 let h = &h0;
18364
18365 let ff = if swa {
18366 None
18367 } else {
18368 Some(
18369 aux.rope_freqs(e)
18370 .expect("e4b global rope needs rope_freqs.weight"),
18371 )
18372 };
18373 #[cfg(debug_assertions)]
18374 if let Some(ff) = ff {
18375 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
18376 }
18377 let share = self.gemma4_e4b_kv_target(il);
18378 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
18380 let mut q;
18381 if let Some(_tgt) = share {
18382 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
18383 q = e.uninit(t * nh * hd)?;
18384 let mut kdummy = e.uninit(1)?;
18387 let mut vdummy = e.uninit(1)?;
18388 e.rms_norm_qkv_rope(
18389 &q0,
18390 &q0,
18391 &q0,
18392 fa.q_norm.float_data(),
18393 fa.q_norm.float_data(),
18394 ones,
18395 &mut q,
18396 &mut kdummy,
18397 &mut vdummy,
18398 hd,
18399 self.gemma4_rope_dims(il),
18400 nh * t,
18401 0,
18402 pos_d,
18403 nh,
18404 1,
18405 base,
18406 1.0,
18407 ff,
18408 eps,
18409 )?;
18410 } else {
18411 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
18415 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
18416 q = e.uninit(t * nh * hd)?;
18417 let mut k = e.uninit(t * nkv * hd)?;
18418 let mut v = e.uninit(t * nkv * hd)?;
18419 if t == 1 && cat.is_some() {
18420 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
18421 e.rms_norm_qkv_rope_cat(
18422 &qkv0,
18423 fa.q_norm.float_data(),
18424 fa.k_norm.float_data(),
18425 ones,
18426 &mut q,
18427 &mut k,
18428 &mut v,
18429 hd,
18430 self.gemma4_rope_dims(il),
18431 nh,
18432 nkv,
18433 pos_d,
18434 nh,
18435 nkv,
18436 base,
18437 1.0,
18438 ff,
18439 eps,
18440 )?;
18441 } else {
18442 let (q0, k0, v0) = match if t == 1 {
18443 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
18444 } else {
18445 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18448 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
18449 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
18450 } else {
18451 None
18452 }
18453 } {
18454 Some(triple) => triple,
18455 None => (
18456 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
18457 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
18458 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
18459 ), };
18461 e.rms_norm_qkv_rope(
18464 &q0,
18465 &k0,
18466 &v0,
18467 fa.q_norm.float_data(),
18468 fa.k_norm.float_data(),
18469 ones,
18470 &mut q,
18471 &mut k,
18472 &mut v,
18473 hd,
18474 self.gemma4_rope_dims(il),
18475 nh * t,
18476 nkv * t,
18477 pos_d,
18478 nh,
18479 nkv,
18480 base,
18481 1.0,
18482 ff,
18483 eps,
18484 )?;
18485 }
18486 let kvl = cache.kv[il].as_mut().unwrap();
18487 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18491 if dc_bucket.is_some() {
18492 debug_assert!(t == 1);
18497 e.append_kv_quantized_row_dc_inc(
18499 &k,
18500 &v,
18501 &mut kvl.k,
18502 &mut kvl.v,
18503 &mut kvl.len_d,
18504 kvl.kv_dim_k,
18505 kvl.kv_dim_v,
18506 kvl.k_tok_bytes,
18507 kvl.v_tok_bytes,
18508 cls,
18509 )?;
18510 } else {
18511 e.append_kv_quantized_rows(
18512 &k,
18513 &v,
18514 &mut kvl.k,
18515 &mut kvl.v,
18516 kvl.len,
18517 t,
18518 kvl.kv_dim_k,
18519 kvl.kv_dim_v,
18520 kvl.k_tok_bytes,
18521 kvl.v_tok_bytes,
18522 cls,
18523 )?;
18524 kvl.len += t;
18525 }
18526 kv_f32 = Some((k, v));
18527 }
18528 let kvl_idx = share.unwrap_or(il);
18531 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
18532 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
18534 let mut attn = e.uninit(t * nh * hd)?;
18535 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
18547 if let Some((kf, vf)) = &kv_f32 {
18548 if hd == 256 && t <= win {
18549 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18550 return Ok(e.matmul(&fa.wo, &attn, t)?);
18551 }
18552 if hd == 256 && swa && t > win {
18553 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
18554 return Ok(e.matmul(&fa.wo, &attn, t)?);
18555 }
18556 if hd == 512 && !swa {
18557 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18558 return Ok(e.matmul(&fa.wo, &attn, t)?);
18559 }
18560 } else if share.is_some() {
18561 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18562 let k_view = e.view_u8(&kvl.k, kvl.k.len());
18563 let v_view = e.view_u8(&kvl.v, kvl.v.len());
18564 if hd == 256 && (!swa || t <= win) {
18565 e.fa_prefill_view(
18567 &q,
18568 &k_view,
18569 &v_view,
18570 &mut attn,
18571 hd,
18572 nh,
18573 nkv,
18574 t,
18575 t,
18576 scale,
18577 true,
18578 kvl.k_tok_bytes,
18579 kvl.v_tok_bytes,
18580 g,
18581 )?;
18582 return Ok(e.matmul(&fa.wo, &attn, t)?);
18583 }
18584 let kv_dim = nkv * hd;
18587 let mut kf = e.uninit(t * kv_dim)?;
18588 let mut vf = e.uninit(t * kv_dim)?;
18589 e.fa_dequant_kv_view_f32(
18590 &k_view,
18591 &v_view,
18592 &mut kf,
18593 &mut vf,
18594 kv_dim,
18595 kv_dim,
18596 t,
18597 kvl.k_tok_bytes,
18598 kvl.v_tok_bytes,
18599 g,
18600 )?;
18601 if hd == 512 {
18602 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18603 } else {
18604 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
18605 }
18606 return Ok(e.matmul(&fa.wo, &attn, t)?);
18607 }
18608 }
18609 if let Some(bucket) = dc_bucket {
18610 assert!(t == 1);
18615 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
18621 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
18622 } else {
18623 bucket
18624 };
18625 let k_view = e.view_u8(&kvl.k, kvl.k.len());
18626 let v_view = e.view_u8(&kvl.v, kvl.v.len());
18627 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18628 if crate::Engine::wpf_level() >= 1 {
18636 e.prefetch_weight_l2(&fa.wo)?;
18637 }
18638 if e.uses_q8_1_fast(&fa.wo) {
18641 let mut oq = e.alloc_i8_uninit(nh * hd)?;
18642 let mut od = e.zeros(nh * hd / 32)?;
18643 e.fa_decode_dc_q8(
18644 &q,
18645 &k_view,
18646 &v_view,
18647 &mut attn,
18648 hd,
18649 nh,
18650 nkv,
18651 &kvl.len_d,
18652 bucket,
18653 scale,
18654 kvl.k_tok_bytes,
18655 kvl.v_tok_bytes,
18656 g,
18657 Some((&mut oq, &mut od)),
18658 )?;
18659 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
18660 }
18661 e.fa_decode_dc(
18662 &q,
18663 &k_view,
18664 &v_view,
18665 &mut attn,
18666 hd,
18667 nh,
18668 nkv,
18669 &kvl.len_d,
18670 bucket,
18671 scale,
18672 kvl.k_tok_bytes,
18673 kvl.v_tok_bytes,
18674 g,
18675 )?;
18676 return Ok(e.matmul(&fa.wo, &attn, t)?);
18677 }
18678 for i in 0..t {
18679 let avail = base_len + i + 1;
18680 let (off_tok, t_kv) = if swa && avail > win {
18681 (avail - win, win)
18682 } else {
18683 (0, avail)
18684 };
18685 let k_view = e.view_u8_range(
18686 &kvl.k,
18687 off_tok * kvl.k_tok_bytes,
18688 (off_tok + t_kv) * kvl.k_tok_bytes,
18689 );
18690 let v_view = e.view_u8_range(
18691 &kvl.v,
18692 off_tok * kvl.v_tok_bytes,
18693 (off_tok + t_kv) * kvl.v_tok_bytes,
18694 );
18695 let qv = e.view(&q, t * nh * hd);
18696 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
18697 let mut q_one = e.uninit(nh * hd)?;
18698 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
18699 let mut a_one = e.uninit(nh * hd)?;
18700 e.fa_decode_kvmod(
18704 &q_one,
18705 &k_view,
18706 &v_view,
18707 &mut a_one,
18708 hd,
18709 nh,
18710 nkv,
18711 t_kv,
18712 scale,
18713 kvl.k_tok_bytes,
18714 kvl.v_tok_bytes,
18715 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
18716 )?;
18717 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
18718 }
18719 Ok(e.matmul(&fa.wo, &attn, t)?)
18720 }
18721
18722 fn gemma4_e4b_trunk(
18727 &self,
18728 e: &Engine,
18729 tokens: &[u32],
18730 pos0: usize,
18731 cache: &mut Cache,
18732 head_last: bool,
18733 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18734 let n_embd = self.cfg.n_embd as usize;
18735 let t = tokens.len();
18736 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
18737 let pos_d = e.htod_i32(&pos)?;
18738 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
18739 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
18740 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
18741 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
18742 }
18743
18744 fn gemma4_e4b_trunk_core(
18748 &self,
18749 e: &Engine,
18750 x_in: CudaSlice<f32>,
18751 inp_pl: CudaSlice<f32>,
18752 pos_d: &CudaSlice<i32>,
18753 t: usize,
18754 cache: &mut Cache,
18755 dc_bucket: Option<usize>,
18756 cap_logits: bool,
18757 head_last: bool,
18758 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18759 let n_embd = self.cfg.n_embd as usize;
18760 let eps = self.cfg.rms_eps;
18761 let n_layer = self.layers.len();
18762 let mut x = x_in;
18763 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
18764 let n_epl = aux_e4b.n_epl;
18765
18766 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
18772 for il in 0..n_layer {
18773 let layer = &self.layers[il];
18774 let (hq, hdq) = match h_carry.take() {
18775 Some(p) => p,
18776 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
18777 };
18778 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
18779 let bits = layer.gemma4.as_ref().unwrap();
18782 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
18783 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
18794 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
18795 e,
18796 layer,
18797 &o,
18798 &x,
18799 t,
18800 Some(layer.post_attn_norm.float_data()),
18801 fuse_exit,
18802 )?;
18803 let mut resid = e.uninit(t * n_embd)?;
18804 let g = if fuse_exit {
18810 let (rq, rd) = e.rms_pre_add_q8_1(
18812 &sn,
18813 bits.post_ffw_norm.float_data(),
18814 &attn_out,
18815 &mut resid,
18816 n_embd,
18817 t,
18818 self.cfg.rms_eps,
18819 )?;
18820 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
18821 } else {
18822 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
18823 e.matmul(&e4b.inp_gate, &resid, t)?
18824 };
18825 let mut act = e.uninit(t * n_epl)?;
18826 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
18827 let ipv = e.view(&inp_pl, n_epl * n_layer);
18828 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
18829 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
18830 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
18831 } else {
18832 let mut inp_this = e.uninit(t * n_epl)?;
18833 e.copy_rows_strided(
18834 &inp_pl,
18835 &mut inp_this,
18836 n_epl,
18837 t,
18838 n_epl * n_layer,
18839 il * n_epl,
18840 )?;
18841 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
18842 e.matmul(&e4b.proj, &act, t)?
18843 };
18844 let next_norm = if il + 1 < n_layer {
18847 self.layers[il + 1].attn_norm.float_data()
18848 } else {
18849 self.output_norm.float_data()
18850 };
18851 let mut xn = e.uninit(t * n_embd)?;
18852 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
18853 &y,
18854 e4b.post_norm.float_data(),
18855 &resid,
18856 bits.layer_scale,
18857 next_norm,
18858 &mut xn,
18859 n_embd,
18860 t,
18861 eps,
18862 )?;
18863 h_carry = Some(pair);
18864 x = xn;
18865 }
18866 let (oq, odq) = h_carry.take().unwrap();
18870 let h0 = e.zeros(0)?;
18871 let hm = if head_last { 1 } else { t };
18872 let (hq, hd) = if head_last && t > 1 {
18873 let mut q1 = e.uninit_i8(n_embd)?;
18874 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
18875 let nb = n_embd / 32;
18876 let mut d1 = e.uninit(nb)?;
18877 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
18878 (q1, d1)
18879 } else {
18880 (oq, odq)
18881 };
18882 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
18883 if cap_logits {
18887 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
18888 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
18889 }
18890 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
18892 }
18893
18894 pub fn gemma4_e4b_decode_step_t_am_dev(
18901 &self,
18902 e: &Engine,
18903 tok_d: &CudaSlice<u32>,
18904 t: usize,
18905 pos0: usize,
18906 cache: &mut Cache,
18907 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18908 let n_embd = self.cfg.n_embd as usize;
18909 let eps = self.cfg.rms_eps;
18910 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
18911 let pos_d = e.htod_i32(&pos)?;
18912 let embd_gpu = self
18913 .embd_gpu
18914 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
18915 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
18916 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
18917 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
18918 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
18919 let (ld, xp) =
18920 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
18921 let n_vocab = self.output.out_features();
18924 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
18925 for i in 0..t {
18926 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
18927 }
18928 let mut hn = e.uninit(t * n_embd)?;
18929 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18930 cache.pos += t;
18931 Ok((vam, hn))
18932 }
18933
18934 pub(crate) fn gemma4_e4b_decode_step_t_h(
18937 &self,
18938 e: &Engine,
18939 tokens: &[u32],
18940 pos0: usize,
18941 cache: &mut Cache,
18942 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18943 let n_embd = self.cfg.n_embd as usize;
18944 let eps = self.cfg.rms_eps;
18945 let t = tokens.len();
18946 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
18947 let mut hn = e.uninit(t * n_embd)?;
18948 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18949 cache.pos += t;
18950 Ok((e.dtoh(&ld)?, hn))
18951 }
18952
18953 pub fn gemma4_e4b_decode_step_dcg(
18959 &self,
18960 e: &Engine,
18961 token_d: &mut CudaSlice<u32>,
18962 pos_d: &mut CudaSlice<i32>,
18963 embd_gpu: &CudaSlice<u8>,
18964 embd_qt: i32,
18965 embd_rb: usize,
18966 cache: &mut Cache,
18967 n_vocab: usize,
18968 bucket: usize,
18969 ) -> Result<(), Box<dyn std::error::Error>> {
18970 let n_embd = self.cfg.n_embd as usize;
18971 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
18972 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
18973 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
18974 let (ld, _x) =
18975 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
18976 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
18977 e.inc_seqlen(pos_d)?;
18978 Ok(())
18979 }
18980
18981 #[allow(clippy::too_many_arguments)]
18989 pub fn gemma4_e4b_decode_step_dc(
18990 &self,
18991 e: &Engine,
18992 token_d: &CudaSlice<u32>,
18993 pos_d: &mut CudaSlice<i32>,
18994 embd_gpu: &CudaSlice<u8>,
18995 embd_qt: i32,
18996 embd_rb: usize,
18997 cache: &mut Cache,
18998 n_vocab: usize,
18999 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
19000 let n_embd = self.cfg.n_embd as usize;
19001 let eps = self.cfg.rms_eps;
19002 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
19003 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
19004 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
19005 let (ld, _x) =
19006 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
19007 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
19008 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
19009 e.inc_seqlen(pos_d)?;
19010 cache.pos += 1;
19011 let _ = eps;
19012 Ok(tok_out)
19013 }
19014
19015 pub(crate) fn gemma4_e4b_decode_step_h(
19018 &self,
19019 e: &Engine,
19020 token: u32,
19021 cache: &mut Cache,
19022 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19023 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
19024 let logits = e.dtoh(&ld)?;
19025 cache.pos += 1;
19026 Ok((logits, x))
19027 }
19028
19029 pub(crate) fn gemma4_e4b_prime(
19033 &self,
19034 e: &Engine,
19035 tokens: &[u32],
19036 cache: &mut Cache,
19037 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19038 if cache.pos != 0 {
19041 return Err(
19042 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
19043 call or decode tokenwise"
19044 .into(),
19045 );
19046 }
19047 let n_embd = self.cfg.n_embd as usize;
19048 let t = tokens.len();
19049 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
19050 cache.pos += t;
19051 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
19053 let row = xv.slice((t - 1) * n_embd..t * n_embd);
19054 let mut h_seed = e.uninit(n_embd)?;
19055 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
19056 Ok((last, h_seed, x))
19057 }
19058
19059 pub(crate) fn gemma4_e4b_forward(
19061 &self,
19062 e: &Engine,
19063 tokens: &[u32],
19064 last_only: bool,
19065 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
19066 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
19067 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
19068 Ok(e.dtoh(&ld)?) }
19070}
19071
19072#[cfg(test)]
19073mod prime_chunk_schedule_tests {
19074 use super::{
19075 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, active_matrix_values, align_prime_ranges_to_gdn,
19076 dynamic_prime_chunk_ranges, fixed_prime_chunk_ranges, fixed_prime_chunk_ranges_for_ring,
19077 parse_step_ep_grouped_prefill, parse_step_tp_prefill, step_grouped_decode_shape,
19078 step_grouped_prefill_shape, step_tp_prefill_shape, validate_step_prime_batch_modes,
19079 };
19080
19081 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
19082 ranges.iter().map(|(start, end)| end - start).collect()
19083 }
19084
19085 fn auto_chunk(t: usize) -> usize {
19086 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
19087 }
19088
19089 #[test]
19094 fn auto_prime_ranges_align_to_the_gdn_grid() {
19095 let c = 32usize; let assert_covers = |ranges: &[(usize, usize)], t: usize| {
19097 assert_eq!(ranges.first().map(|&(s, _)| s), Some(0));
19098 assert_eq!(ranges.last().map(|&(_, e)| e), Some(t));
19099 for w in ranges.windows(2) {
19100 assert_eq!(w[0].1, w[1].0, "ranges must stay contiguous");
19101 }
19102 assert!(ranges.iter().all(|&(s, e)| e > s), "no empty range");
19103 };
19104
19105 let t = 9510usize;
19108 let fill = auto_chunk(t);
19109 let fixed = fixed_prime_chunk_ranges(t, fill);
19110 assert!(
19111 fixed[..fixed.len() - 1].iter().any(|&(_, e)| e % c != 0),
19112 "broken arm vanished: fixed auto boundaries all landed on-grid"
19113 );
19114 let dynamic = dynamic_prime_chunk_ranges(t, fill, &fixed);
19115 assert!(
19116 dynamic[..dynamic.len() - 1]
19117 .iter()
19118 .any(|&(_, e)| e % c != 0),
19119 "broken arm vanished: dynamic auto boundaries all landed on-grid"
19120 );
19121
19122 for ranges in [&fixed, &dynamic] {
19123 let aligned = align_prime_ranges_to_gdn(ranges, t, c);
19124 assert_covers(&aligned, t);
19125 for &(_, e) in &aligned[..aligned.len() - 1] {
19126 assert_eq!(e % c, 0, "internal boundary {e} off the {c}-grid");
19127 }
19128 for (&(_, a), &(_, b)) in aligned.iter().zip(ranges.iter()) {
19130 assert!(a <= b && b - a < c);
19131 }
19132 }
19133
19134 let tight = vec![(0usize, 33usize), (33, 40), (40, 200)];
19137 let aligned = align_prime_ranges_to_gdn(&tight, 200, c);
19138 assert_covers(&aligned, 200);
19139 assert_eq!(aligned, vec![(0, 32), (32, 200)]);
19140
19141 assert_eq!(align_prime_ranges_to_gdn(&[(0, 200)], 200, c), [(0, 200)]);
19143 assert_eq!(align_prime_ranges_to_gdn(&tight, 200, 0), tight.as_slice());
19144 let on_grid = vec![(0usize, 128usize), (128, 256), (256, 300)];
19145 assert_eq!(
19146 align_prime_ranges_to_gdn(&on_grid, 300, c),
19147 on_grid.as_slice()
19148 );
19149 }
19150
19151 #[test]
19152 fn active_matrix_prefix_scopes_reused_prime_slabs() {
19153 assert_eq!(
19154 active_matrix_values(40 * 4096, 29, 4096, "activation").unwrap(),
19155 29 * 4096
19156 );
19157 assert_eq!(
19158 active_matrix_values(29 * 4096, 29, 4096, "activation").unwrap(),
19159 29 * 4096
19160 );
19161 assert_eq!(
19162 active_matrix_values(29 * 4096, 24, 4096, "activation").unwrap(),
19163 24 * 4096
19164 );
19165 assert!(active_matrix_values(28 * 4096, 29, 4096, "activation").is_err());
19166 assert!(active_matrix_values(usize::MAX, usize::MAX, 2, "activation").is_err());
19167 }
19168
19169 #[test]
19170 fn step_tp_prefill_batch_refuses_before_scheduler_fallback() {
19171 assert!(validate_step_prime_batch_modes(false, false).is_ok());
19172
19173 let grouped_without_tp = validate_step_prime_batch_modes(false, true).unwrap_err();
19174 assert!(grouped_without_tp.contains("requires MEMRA_STEP_TP_PREFILL=1"));
19175
19176 for grouped in [false, true] {
19177 let err = validate_step_prime_batch_modes(true, grouped).unwrap_err();
19178 assert!(err.contains("did not clear the live-server performance gate"));
19179 assert!(err.contains("per-session grouped prefill"));
19180 }
19181 }
19182
19183 #[test]
19184 fn step_grouped_path_is_eager_single_token_only() {
19185 assert!(step_grouped_decode_shape(false, 1));
19186 assert!(!step_grouped_decode_shape(true, 1));
19187 assert!(!step_grouped_decode_shape(false, 2));
19188 assert!(!step_grouped_decode_shape(true, 2));
19189 }
19190
19191 #[test]
19192 fn step_grouped_prefill_door_is_strict_and_capacity_bounded() {
19193 assert!(!parse_step_ep_grouped_prefill(None).unwrap());
19194 assert!(!parse_step_ep_grouped_prefill(Some("")).unwrap());
19195 assert!(!parse_step_ep_grouped_prefill(Some("0")).unwrap());
19196 assert!(parse_step_ep_grouped_prefill(Some("1")).unwrap());
19197 assert!(parse_step_ep_grouped_prefill(Some("true")).is_err());
19198 assert!(parse_step_ep_grouped_prefill(Some("2")).is_err());
19199
19200 assert!(step_grouped_prefill_shape(true, true, PRIME_MIN_T));
19201 assert!(step_grouped_prefill_shape(
19202 true,
19203 true,
19204 crate::cache::PRIME_CHUNK_MAX_TOKENS,
19205 ));
19206 assert!(!step_grouped_prefill_shape(true, true, PRIME_MIN_T - 1,));
19207 assert!(!step_grouped_prefill_shape(
19208 true,
19209 true,
19210 crate::cache::PRIME_CHUNK_MAX_TOKENS + 1,
19211 ));
19212 assert!(!step_grouped_prefill_shape(false, true, PRIME_MIN_T));
19213 assert!(!step_grouped_prefill_shape(true, false, PRIME_MIN_T));
19214 }
19215
19216 #[test]
19217 fn step_tp_prefill_door_is_strict_and_default_off() {
19218 assert!(!parse_step_tp_prefill(None).unwrap());
19219 assert!(!parse_step_tp_prefill(Some("")).unwrap());
19220 assert!(!parse_step_tp_prefill(Some("0")).unwrap());
19221 assert!(parse_step_tp_prefill(Some("1")).unwrap());
19222 assert!(parse_step_tp_prefill(Some("true")).is_err());
19223 assert!(parse_step_tp_prefill(Some("2")).is_err());
19224 }
19225
19226 #[test]
19227 fn step_tp_prefill_requires_a_qualified_even_rank_shape() {
19228 assert!(step_tp_prefill_shape(
19229 true,
19230 PRIME_MIN_T,
19231 4,
19232 true,
19233 true,
19234 false,
19235 ));
19236 assert!(!step_tp_prefill_shape(
19237 false,
19238 PRIME_MIN_T,
19239 4,
19240 true,
19241 true,
19242 false,
19243 ));
19244 assert!(!step_tp_prefill_shape(
19245 true,
19246 PRIME_MIN_T - 1,
19247 4,
19248 true,
19249 true,
19250 false,
19251 ));
19252 assert!(step_tp_prefill_shape(
19254 true,
19255 PRIME_MIN_T,
19256 2,
19257 true,
19258 true,
19259 false
19260 ));
19261 assert!(!step_tp_prefill_shape(
19262 true,
19263 PRIME_MIN_T,
19264 1,
19265 true,
19266 true,
19267 false
19268 ));
19269 assert!(!step_tp_prefill_shape(
19270 true,
19271 PRIME_MIN_T,
19272 3,
19273 true,
19274 true,
19275 false
19276 ));
19277 assert!(!step_tp_prefill_shape(
19278 true,
19279 PRIME_MIN_T,
19280 4,
19281 false,
19282 true,
19283 false,
19284 ));
19285 assert!(!step_tp_prefill_shape(
19286 true,
19287 PRIME_MIN_T,
19288 4,
19289 true,
19290 false,
19291 false,
19292 ));
19293 assert!(!step_tp_prefill_shape(
19294 true,
19295 PRIME_MIN_T,
19296 4,
19297 true,
19298 true,
19299 true,
19300 ));
19301 }
19302
19303 #[test]
19304 fn fixed_schedule_retains_measured_geometry() {
19305 assert_eq!(
19306 sizes(&fixed_prime_chunk_ranges(461, 128)),
19307 vec![128, 128, 128, 77]
19308 );
19309 assert_eq!(
19310 sizes(&fixed_prime_chunk_ranges(1833, 230)),
19311 vec![230, 230, 230, 230, 230, 230, 230, 223]
19312 );
19313 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
19314 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
19315 assert_eq!(capped, vec![4096, 4088, 16]);
19316 assert!(capped.iter().all(|&rows| rows <= 4096));
19317 assert_eq!(
19318 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
19319 vec![4100],
19320 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
19321 );
19322 }
19323
19324 #[test]
19325 fn dynamic_schedule_matches_registered_shapes() {
19326 let cases = [
19327 (461, vec![64, 141, 132, 124]),
19328 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
19329 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
19330 ];
19331 for (t, expected) in cases {
19332 let chunk = auto_chunk(t);
19333 let fixed = fixed_prime_chunk_ranges(t, chunk);
19334 assert_eq!(
19335 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
19336 expected
19337 );
19338 }
19339 }
19340
19341 #[test]
19342 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
19343 for t in 256..=8192 {
19344 let chunk = auto_chunk(t);
19345 let fixed = fixed_prime_chunk_ranges(t, chunk);
19346 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
19347 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
19348 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
19349 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
19350 for pair in dynamic.windows(2) {
19351 assert_eq!(pair[0].1, pair[1].0, "T={t}");
19352 }
19353 assert!(
19354 dynamic
19355 .iter()
19356 .all(|(start, end)| end - start >= PRIME_MIN_T),
19357 "T={t} sizes={:?}",
19358 sizes(&dynamic)
19359 );
19360 if dynamic.len() >= 3 {
19361 let chunk_sizes = sizes(&dynamic);
19362 assert!(
19363 chunk_sizes[0] < chunk_sizes[1],
19364 "T={t} sizes={chunk_sizes:?}"
19365 );
19366 assert!(
19367 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
19368 "T={t} sizes={chunk_sizes:?}"
19369 );
19370 }
19371 }
19372 }
19373}
19374
19375#[cfg(test)]
19376mod page_prefetch_tests {
19377 use super::{
19378 grouped_worker_prefetch_position, page_prefetch_positions,
19379 page_prefetch_window_from_values, worker_prefetch_positions,
19380 };
19381
19382 #[test]
19383 fn page_prefetch_window_keeps_existing_opt_in_default() {
19384 assert_eq!(page_prefetch_window_from_values(false, None), 0);
19385 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
19386 assert_eq!(page_prefetch_window_from_values(true, None), 1);
19387 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
19388 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
19389 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
19390 }
19391
19392 #[test]
19393 fn rolling_page_prefetch_advises_each_future_expert_once() {
19394 let advised: Vec<_> = (0..7)
19395 .flat_map(|position| page_prefetch_positions(position, 7, 3))
19396 .collect();
19397 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
19398
19399 let one_ahead: Vec<_> = (0..4)
19400 .flat_map(|position| page_prefetch_positions(position, 4, 1))
19401 .collect();
19402 assert_eq!(one_ahead, vec![1, 2, 3]);
19403 assert!(page_prefetch_positions(0, 4, 0).is_empty());
19404 }
19405
19406 #[test]
19407 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
19408 assert_eq!(grouped_worker_prefetch_position(0, None), None);
19409 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
19410 .chain(
19411 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
19412 )
19413 .collect();
19414 assert_eq!(positions, vec![0, 1, 2, 3]);
19415 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
19416 }
19417
19418 #[test]
19419 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
19420 let queued: Vec<_> = (0..8)
19421 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
19422 .collect();
19423 assert_eq!(queued, (0..8).collect::<Vec<_>>());
19424
19425 let one_at_a_time: Vec<_> = (0..4)
19426 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
19427 .collect();
19428 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
19429 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
19430 }
19431}
19432
19433pub struct G4DcSlots {
19434 x: CudaSlice<f32>,
19435 xn: CudaSlice<f32>,
19436 cur: CudaSlice<f32>,
19437 hq: CudaSlice<i8>,
19438 hd_: CudaSlice<f32>,
19439 q0: CudaSlice<f32>,
19440 k0: CudaSlice<f32>,
19441 v0: CudaSlice<f32>,
19442 q: CudaSlice<f32>,
19443 k: CudaSlice<f32>,
19444 v: CudaSlice<f32>,
19445 attn: CudaSlice<f32>,
19446 o: CudaSlice<f32>,
19447 attn_out: CudaSlice<f32>,
19448 zsh: CudaSlice<f32>,
19449 zq: CudaSlice<i8>,
19450 zd: CudaSlice<f32>,
19451 gate: CudaSlice<f32>,
19452 up: CudaSlice<f32>,
19453 act: CudaSlice<f32>,
19454 actq: CudaSlice<i8>,
19455 actd: CudaSlice<f32>,
19456 f0: CudaSlice<f32>,
19457 sn: CudaSlice<f32>,
19458 hn: CudaSlice<f32>,
19459 logits: CudaSlice<f32>,
19460}
19461
19462pub struct Step35TokenGraphState {
19467 pub graphs: Vec<(usize, crate::tp::TokenGraph)>,
19469 pub token_d: cudarc::driver::CudaSlice<u32>,
19470 pub pos_d: cudarc::driver::CudaSlice<i32>,
19471 pub logits_stage: cudarc::driver::CudaSlice<f32>,
19472 pub x: cudarc::driver::CudaSlice<f32>,
19477 pub x1: cudarc::driver::CudaSlice<f32>,
19478 pub mixed_stage: cudarc::driver::CudaSlice<f32>,
19479 pub sh_stage: cudarc::driver::CudaSlice<f32>,
19480 pub k_shadow_stage: cudarc::driver::CudaSlice<f32>,
19481 pub v_shadow_stage: cudarc::driver::CudaSlice<f32>,
19482 pub router_logits: cudarc::driver::CudaSlice<f32>,
19485 pub shexp_gate: cudarc::driver::CudaSlice<f32>,
19486 pub shexp_up: cudarc::driver::CudaSlice<f32>,
19487 pub shexp_act: cudarc::driver::CudaSlice<f32>,
19488 pub gate_sig: cudarc::driver::CudaSlice<f32>,
19489 pub dense_z: cudarc::driver::CudaSlice<f32>,
19490 pub dense_gate: cudarc::driver::CudaSlice<f32>,
19491 pub dense_up: cudarc::driver::CudaSlice<f32>,
19492 pub dense_act: cudarc::driver::CudaSlice<f32>,
19493 pub hn: cudarc::driver::CudaSlice<f32>,
19494 pub probe_mixed: cudarc::driver::CudaSlice<f32>,
19497 pub probe_x: cudarc::driver::CudaSlice<f32>,
19498 pub token_hist: cudarc::driver::CudaSlice<u32>,
19501 pub hist_idx: cudarc::driver::CudaSlice<i32>,
19502}
19503
19504impl HybridModel {
19505 pub(crate) fn step35_token_graph_step(
19516 &self,
19517 e: &Engine,
19518 token: u32,
19519 cache: &mut Cache,
19520 ) -> Result<Option<(Vec<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
19521 if !self.uses_sliding_gated_moe_program()
19522 || !crate::tp::step_tp_graph_enabled()?
19523 || !crate::tp::step_tp_dcw_enabled()?
19524 || !crate::tp::step_tp_qkv_fused_enabled()?
19525 || !crate::tp::step_tp_dev_router_enabled()?
19526 || !crate::tp::step_nvfp4_dev_routes_enabled()?
19527 {
19528 return Ok(None);
19529 }
19530 let n_embd = self.cfg.n_embd as usize;
19531 let n_vocab = self.cfg.n_vocab as usize;
19532 let eps = self.cfg.rms_eps;
19533 let n_layers = self.layers.len();
19534 let pos = cache.pos;
19535 let staged_next = pos + 1;
19536 if staged_next < 96 {
19537 return Ok(None); }
19539
19540 for il in 0..n_layers {
19543 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
19544 return Ok(None); };
19546 if tp_kv.peek_append_ring(1)?.1 {
19547 return Ok(None);
19548 }
19549 }
19550
19551 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
19554 if !fa_vec {
19555 return Ok(None);
19556 }
19557 let sp = crate::fa_split_keys(staged_next, 8);
19558 let bucket_max = (n_splits * sp).max(staged_next);
19559
19560 let mut state_guard = self
19561 .step35_token_graph
19562 .lock()
19563 .map_err(|_| "step35 token graph lock is poisoned")?;
19564 if state_guard.is_none() {
19565 let _main = e.gpu.enter_main()?;
19566 let n_expert = self
19567 .cfg
19568 .moe
19569 .as_ref()
19570 .map(|m| m.expert_count as usize)
19571 .unwrap_or(0);
19572 let n_ff_sh = self
19573 .layers
19574 .iter()
19575 .find_map(|l| match &l.ffn {
19576 crate::hybrid::Ffn::Moe(m) => m.gate_shexp.as_ref().map(|g| g.out_features()),
19577 _ => None,
19578 })
19579 .unwrap_or(0);
19580 let n_ff_dense = self
19581 .layers
19582 .iter()
19583 .find_map(|l| match &l.ffn {
19584 crate::hybrid::Ffn::Dense { ffn_gate, .. } => Some(ffn_gate.out_features()),
19585 _ => None,
19586 })
19587 .unwrap_or(0);
19588 *state_guard = Some(Step35TokenGraphState {
19589 graphs: Vec::new(),
19590 token_d: e.stream().clone_htod(&[0u32])?,
19591 pos_d: e.htod_i32(&[pos as i32])?,
19592 logits_stage: e.htod(&vec![0.0f32; n_vocab])?,
19593 x: e.htod(&vec![0.0f32; n_embd])?,
19594 x1: e.htod(&vec![0.0f32; n_embd])?,
19595 mixed_stage: e.htod(&vec![0.0f32; n_embd])?,
19596 sh_stage: e.htod(&vec![0.0f32; n_embd])?,
19597 k_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
19598 v_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
19599 router_logits: e.htod(&vec![0.0f32; n_expert.max(1)])?,
19600 shexp_gate: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19601 shexp_up: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19602 shexp_act: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19603 gate_sig: e.htod(&vec![1.0f32; 1])?,
19604 dense_z: e.htod(&vec![0.0f32; n_embd])?,
19605 dense_gate: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19606 dense_up: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19607 dense_act: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19608 hn: e.htod(&vec![0.0f32; n_embd])?,
19609 probe_mixed: e.htod(&vec![0.0f32; n_embd])?,
19610 probe_x: e.htod(&vec![0.0f32; n_embd])?,
19611 token_hist: e.stream().clone_htod(&[0u32; 16])?,
19612 hist_idx: e.htod_i32(&[0])?,
19613 });
19614 }
19615 let state = state_guard.as_mut().expect("state armed above");
19616 {
19620 let _main = e.gpu.enter_main()?;
19621 let Step35TokenGraphState {
19622 logits_stage,
19623 token_d,
19624 ..
19625 } = &mut *state;
19626 e.argmax_token_device_into(logits_stage, token_d, n_vocab)?;
19627 }
19628
19629 if state.graphs.is_empty() {
19634 self.step35_token_graph_build(e, cache, state, bucket_max)?;
19637 }
19638 {
19639 let (b, g) = state.graphs.first_mut().expect("graph built above");
19640 if *b != bucket_max {
19641 g.retarget_bucket(bucket_max)?;
19642 *b = bucket_max;
19643 }
19644 }
19645 let graph = state
19646 .graphs
19647 .first()
19648 .map(|(_, g)| g)
19649 .expect("graph built above");
19650
19651 let tg_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
19652 let t_fence = tg_timing.then(std::time::Instant::now);
19653 {
19658 let fa0 = match &self.layers[0].mixer {
19659 Mixer::Full(fa) => fa,
19660 _ => return Err("step35 token graph expects full-attention layers".into()),
19661 };
19662 let tp0 = fa0
19663 .step_tp_qkv
19664 .as_ref()
19665 .ok_or("step35 token graph lost its TP state")?;
19666 for rank in 0..tp0.runtime.devices().len() {
19667 let engine = tp0
19668 .runtime
19669 .rank_engine(rank)
19670 .ok_or("step35 token graph lost a rank engine")?;
19671 let _main = engine.gpu.enter_main()?;
19672 engine.stream().synchronize()?;
19673 }
19674 }
19675
19676 {
19678 let _main = e.gpu.enter_main()?;
19679 e.set_u32_one(&mut state.token_d, token)?;
19680 e.set_i32_one(&mut state.pos_d, pos as i32)?;
19681 }
19682 let t_launch = tg_timing.then(std::time::Instant::now);
19683 graph.launch(e)?;
19684 let t_book = tg_timing.then(std::time::Instant::now);
19685 for il in 0..n_layers {
19690 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
19691 let transaction = tp_kv.begin_transaction()?;
19692 let fa = match &self.layers[il].mixer {
19693 Mixer::Full(fa) => fa,
19694 _ => return Err("step35 token graph expects full-attention layers".into()),
19695 };
19696 let tp = fa
19697 .step_tp_qkv
19698 .as_ref()
19699 .ok_or("step35 token graph lost its TP state")?;
19700 let empty: [CudaSlice<f32>; 0] = [];
19703 tp.runtime.append_tp_kv_transaction_inner(
19704 tp_kv,
19705 transaction,
19706 &empty,
19707 &empty,
19708 1,
19709 true,
19710 )?;
19711 tp.runtime
19712 .commit_tp_kv_transaction_external(tp_kv, transaction, 1)?;
19713 if let Some(local) = cache.kv[il].as_mut() {
19715 local.len = pos + 1;
19716 let _main = e.gpu.enter_main()?;
19717 e.set_i32_one(&mut local.len_d, (pos + 1) as i32)?;
19718 }
19719 }
19720 cache.pos = pos + 1;
19721 let t_sync = tg_timing.then(std::time::Instant::now);
19722 let (logits, h_seed) = {
19723 let _main = e.gpu.enter_main()?;
19724 e.stream().synchronize()?;
19725 (e.dtoh(&state.logits_stage)?, e.clone_dtod(&state.x)?)
19726 };
19727 if let (Some(f), Some(l), Some(b), Some(sy)) = (t_fence, t_launch, t_book, t_sync) {
19728 use std::sync::atomic::{AtomicU64, Ordering};
19729 static NS: [AtomicU64; 5] = [
19730 AtomicU64::new(0),
19731 AtomicU64::new(0),
19732 AtomicU64::new(0),
19733 AtomicU64::new(0),
19734 AtomicU64::new(0),
19735 ];
19736 static CALLS: AtomicU64 = AtomicU64::new(0);
19737 let now = std::time::Instant::now();
19738 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;
19744 if calls % 100 == 0 {
19745 let avg = |i: usize| NS[i].load(Ordering::Relaxed) as f64 / calls as f64 / 1e3;
19746 eprintln!(
19747 "[tg-timing] calls={calls} fence_us={:.0} launch_us={:.0} book_us={:.0} \
19748 syncdtoh_us={:.0} total_us={:.0}",
19749 avg(0),
19750 avg(1),
19751 avg(2),
19752 avg(3),
19753 avg(4)
19754 );
19755 }
19756 }
19757 if std::env::var("MEMRA_TG_PROBE_LAYER").is_ok() {
19759 use std::io::Write;
19760 let (pm, px) = {
19761 let _main = e.gpu.enter_main()?;
19762 (e.dtoh(&state.probe_mixed)?, e.dtoh(&state.probe_x)?)
19763 };
19764 for (path, data) in [
19765 ("/root/tg-probe-mixed.bin", &pm),
19766 ("/root/tg-probe-x.bin", &px),
19767 ] {
19768 let mut fo = std::fs::OpenOptions::new()
19769 .create(true)
19770 .append(true)
19771 .open(path)?;
19772 for v in data {
19773 fo.write_all(&v.to_le_bytes())?;
19774 }
19775 }
19776 }
19777 if let Ok(path) = std::env::var("MEMRA_DUMP_HN") {
19780 let hh = {
19781 let _main = e.gpu.enter_main()?;
19782 e.dtoh(&state.hn)?
19783 };
19784 use std::io::Write;
19785 let mut fo = std::fs::OpenOptions::new()
19786 .create(true)
19787 .append(true)
19788 .open(path)?;
19789 for v in &hh {
19790 fo.write_all(&v.to_le_bytes())?;
19791 }
19792 }
19793 if std::env::var("MEMRA_STEP_TP_GRAPH_DEBUG").as_deref() == Ok("1") {
19796 for il in [0usize, 1, 44] {
19797 let tp_kv = cache.tp_kv[il].as_ref().expect("eligibility checked above");
19798 let host_len = tp_kv.staged_len();
19799 let fa = match &self.layers[il].mixer {
19800 Mixer::Full(fa) => fa,
19801 _ => continue,
19802 };
19803 let tp = fa
19804 .step_tp_qkv
19805 .as_ref()
19806 .ok_or("step35 token graph lost its TP state")?;
19807 for rank in 0..tp.runtime.devices().len() {
19808 let engine = tp
19809 .runtime
19810 .rank_engine(rank)
19811 .ok_or("step35 token graph lost a rank engine")?;
19812 let rank_cache = tp_kv.rank(rank).ok_or("debug rank cache missing")?;
19813 let _main = engine.gpu.enter_main()?;
19814 engine.stream().synchronize()?;
19815 let len_d = engine.dtoh_i32_one(rank_cache.len_d())?;
19816 let base_d = match rank_cache.base_d() {
19817 Some(b) => engine.dtoh_i32_one(b)?,
19818 None => -1,
19819 };
19820 eprintln!(
19821 "[graph-debug] pos={pos} il={il} rank={rank} host_len={host_len} \
19822 len_d={len_d} base_d={base_d}"
19823 );
19824 }
19825 }
19826 }
19827 Ok(Some((logits, h_seed)))
19828 }
19829
19830 pub(crate) fn head_split_matvec(
19836 &self,
19837 e: &Engine,
19838 hn: &CudaSlice<f32>,
19839 ) -> Result<Option<Vec<f32>>, Box<dyn std::error::Error>> {
19840 if self.head_split_fill_device(e, hn)?.is_none() {
19841 return Ok(None);
19842 }
19843 let guard = HEAD_SPLIT_WS
19844 .lock()
19845 .map_err(|_| "head split lock is poisoned")?;
19846 let ws = guard.as_ref().expect("filled above");
19847 let _main = e.gpu.enter_main()?;
19848 Ok(Some(e.dtoh(&ws.logits_e)?))
19849 }
19850
19851 fn head_split_fill_device(
19855 &self,
19856 e: &Engine,
19857 hn: &CudaSlice<f32>,
19858 ) -> Result<Option<()>, Box<dyn std::error::Error>> {
19859 use cudarc::driver::DevicePtr;
19860 let crate::model::GpuTensor::FloatBf16 { data: head, .. } = &self.output else {
19861 return Ok(None);
19862 };
19863 let Some(rank1) = self.layers.first().and_then(|l| match &l.mixer {
19864 Mixer::Full(fa) => fa
19865 .step_tp_qkv
19866 .as_ref()
19867 .and_then(|tp| tp.runtime.rank_engine(1)),
19868 _ => None,
19869 }) else {
19870 return Ok(None);
19871 };
19872 let n_embd = self.cfg.n_embd as usize;
19873 let n_vocab = self.cfg.n_vocab as usize;
19874 let half = n_vocab / 2;
19875 let mut guard = HEAD_SPLIT_WS
19876 .lock()
19877 .map_err(|_| "head split lock is poisoned")?;
19878 let pin = {
19879 let _main = e.gpu.enter_main()?;
19880 let stream = e.stream();
19881 let (ptr, _g) = head.device_ptr(&stream);
19882 ptr as u64
19883 };
19884 if guard.as_ref().is_none_or(|ws| ws.pin != pin) {
19885 let hi_rows = n_vocab - half;
19887 let (w1, hn1, y1, ev_done) = {
19888 let _r1 = rank1.gpu.enter_main()?;
19889 (
19890 rank1.alloc_u8_uninit(hi_rows * n_embd * 2)?,
19891 rank1.htod(&vec![0.0f32; n_embd])?,
19892 rank1.htod(&vec![0.0f32; hi_rows])?,
19893 rank1.ctx().new_event(None)?,
19894 )
19895 };
19896 {
19897 use cudarc::driver::sys;
19898 let src = pin + (half * n_embd * 2) as u64;
19899 let dst = {
19900 let _r1 = rank1.gpu.enter_main()?;
19901 let rstream = rank1.stream();
19902 let (d, _g) = w1.device_ptr(&rstream);
19903 d as u64
19904 };
19905 let _r1 = rank1.gpu.enter_main()?;
19906 let r = unsafe {
19907 sys::cuMemcpyAsync(
19908 dst as sys::CUdeviceptr,
19909 src as sys::CUdeviceptr,
19910 hi_rows * n_embd * 2,
19911 rank1.stream().cu_stream() as sys::CUstream,
19912 )
19913 };
19914 if r != sys::CUresult::CUDA_SUCCESS {
19915 return Err(format!("head split replica upload: {r:?}").into());
19916 }
19917 rank1.stream().synchronize()?;
19918 }
19919 let (logits_e, ev_hn) = {
19920 let _main = e.gpu.enter_main()?;
19921 (e.htod(&vec![0.0f32; n_vocab])?, e.ctx().new_event(None)?)
19922 };
19923 let (raw_hn1, raw_y1) = {
19924 let _r1 = rank1.gpu.enter_main()?;
19925 let rstream = rank1.stream();
19926 let (a, _g0) = hn1.device_ptr(&rstream);
19927 let (b, _g1) = y1.device_ptr(&rstream);
19928 (a as u64, b as u64)
19929 };
19930 let raw_logits_hi = {
19931 let _main = e.gpu.enter_main()?;
19932 let stream = e.stream();
19933 let (l, _g) = logits_e.device_ptr(&stream);
19934 l as u64 + (half * 4) as u64
19935 };
19936 *guard = Some(HeadSplit {
19937 pin,
19938 w1,
19939 hn1,
19940 y1,
19941 logits_e,
19942 ev_hn,
19943 ev_done,
19944 raw_hn1,
19945 raw_y1,
19946 raw_logits_hi,
19947 samp: None,
19948 });
19949 }
19950 let ws = guard.as_mut().expect("armed above");
19951 let hi_rows = n_vocab - half;
19952 let raw_hn = {
19954 let _main = e.gpu.enter_main()?;
19955 let stream = e.stream();
19956 let (h, _g) = hn.device_ptr(&stream);
19957 ws.ev_hn.record(&stream)?;
19958 h as u64
19959 };
19960 {
19961 let _r1 = rank1.gpu.enter_main()?;
19962 rank1.stream().wait(&ws.ev_hn)?;
19963 crate::tp::raw_copy_bytes(ws.raw_hn1, raw_hn, n_embd * 4, rank1)?;
19964 let HeadSplit { w1, hn1, y1, .. } = &mut *ws;
19965 rank1.matvec_bf16_into(w1, hn1, y1, n_embd, hi_rows)?;
19966 crate::tp::raw_copy_bytes(ws.raw_logits_hi, ws.raw_y1, hi_rows * 4, rank1)?;
19967 ws.ev_done.record(&rank1.stream())?;
19968 }
19969 {
19970 let _main = e.gpu.enter_main()?;
19971 let head_lo = head.slice(0..half * n_embd * 2);
19972 let HeadSplit { logits_e, .. } = &mut *ws;
19973 e.matvec_bf16_view_into(&head_lo, hn, logits_e, n_embd, half)?;
19975 e.stream().wait(&ws.ev_done)?;
19976 Ok(Some(()))
19977 }
19978 }
19979
19980 pub(crate) fn head_split_argmax_device(
19985 &self,
19986 e: &Engine,
19987 hn: &CudaSlice<f32>,
19988 token_d: &mut CudaSlice<u32>,
19989 ) -> Result<bool, Box<dyn std::error::Error>> {
19990 if self.head_split_fill_device(e, hn)?.is_none() {
19991 return Ok(false);
19992 }
19993 let n_vocab = self.cfg.n_vocab as usize;
19994 let guard = HEAD_SPLIT_WS
19995 .lock()
19996 .map_err(|_| "head split lock is poisoned")?;
19997 let ws = guard.as_ref().expect("filled above");
19998 let _main = e.gpu.enter_main()?;
19999 e.argmax_token_device_into(&ws.logits_e, token_d, n_vocab)?;
20000 Ok(true)
20001 }
20002
20003 pub(crate) fn head_split_sample_device(
20009 &self,
20010 e: &Engine,
20011 hn: &CudaSlice<f32>,
20012 token_d: &mut CudaSlice<u32>,
20013 samp: &crate::decode_batch::DevSamp,
20014 ctr: u32,
20015 ) -> Result<bool, Box<dyn std::error::Error>> {
20016 if self.head_split_fill_device(e, hn)?.is_none() {
20017 return Ok(false);
20018 }
20019 let n_vocab = self.cfg.n_vocab as usize;
20020 let guard = HEAD_SPLIT_WS
20021 .lock()
20022 .map_err(|_| "head split lock is poisoned")?;
20023 let mut guard = guard;
20024 let ws = guard.as_mut().expect("filled above");
20025 let _main = e.gpu.enter_main()?;
20026 if ws.samp.is_none() {
20027 ws.samp = Some(SampScratch {
20028 pb: e.zeros(n_vocab)?,
20029 th: e.zeros(1)?,
20030 z: e.zeros(1)?,
20031 mx: e.zeros(1)?,
20032 rows: e.htod_i32(&[0i32])?,
20033 });
20034 }
20035 let filtered = samp.top_k > 0 || samp.top_p < 1.0 || samp.min_p > 0.0;
20036 let HeadSplit {
20037 logits_e,
20038 samp: scratch,
20039 ..
20040 } = &mut *ws;
20041 let sc = scratch.as_mut().expect("armed above");
20042 if filtered {
20043 e.filter_stats(
20044 logits_e, n_vocab, &sc.rows, &mut sc.th, &mut sc.z, &mut sc.mx, n_vocab, 1,
20045 samp.temp, samp.top_k, samp.top_p, samp.min_p,
20046 )?;
20047 let SampScratch { pb, th, mx, .. } = sc;
20048 e.gumbel_perturb_filtered_col(
20049 logits_e, 0, pb, n_vocab, samp.seed, ctr, samp.temp, mx, th, 0,
20050 )?;
20051 } else {
20052 e.gumbel_perturb_col(logits_e, 0, &mut sc.pb, n_vocab, samp.seed, ctr, samp.temp)?;
20053 }
20054 e.argmax_token_device_col(&sc.pb, 0, n_vocab, token_d, 0)?;
20055 Ok(true)
20056 }
20057
20058 pub(crate) fn head_split_logits_dtoh(
20061 &self,
20062 e: &Engine,
20063 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
20064 let guard = HEAD_SPLIT_WS
20065 .lock()
20066 .map_err(|_| "head split lock is poisoned")?;
20067 let ws = guard.as_ref().ok_or("head split logits not armed")?;
20068 let _main = e.gpu.enter_main()?;
20069 Ok(e.dtoh(&ws.logits_e)?)
20070 }
20071
20072 pub fn step35_token_graph_chunk(
20081 &self,
20082 e: &Engine,
20083 token: u32,
20084 k_target: usize,
20085 cache: &mut Cache,
20086 ) -> Result<Option<(Vec<u32>, Vec<f32>)>, Box<dyn std::error::Error>> {
20087 if !self.uses_sliding_gated_moe_program()
20088 || !crate::tp::step_tp_graph_enabled()?
20089 || !crate::tp::step_tp_dcw_enabled()?
20090 || !crate::tp::step_tp_qkv_fused_enabled()?
20091 || !crate::tp::step_tp_dev_router_enabled()?
20092 || !crate::tp::step_nvfp4_dev_routes_enabled()?
20093 {
20094 return Ok(None);
20095 }
20096 let n_layers = self.layers.len();
20097 let pos = cache.pos;
20098 let staged_next = pos + 1;
20099 if staged_next < 96 {
20100 return Ok(None);
20101 }
20102 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
20105 if !fa_vec {
20106 return Ok(None);
20107 }
20108 let sp = crate::fa_split_keys(staged_next, 8);
20109 let bucket_max = (n_splits * sp).max(staged_next);
20110 let to_boundary = bucket_max.saturating_sub(staged_next) + 1;
20111 let mut k = k_target.min(to_boundary).min(16);
20112 if k < 2 {
20113 return Ok(None);
20114 }
20115 for il in 0..n_layers {
20117 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
20118 return Ok(None);
20119 };
20120 while k >= 2 && tp_kv.peek_append_ring(k)?.1 {
20121 k -= 1;
20122 }
20123 if k < 2 {
20124 return Ok(None);
20125 }
20126 }
20127
20128 let mut state_guard = self
20129 .step35_token_graph
20130 .lock()
20131 .map_err(|_| "step35 token graph lock is poisoned")?;
20132 let Some(state) = state_guard.as_mut() else {
20133 return Ok(None); };
20135 if state.graphs.is_empty() {
20136 return Ok(None);
20137 }
20138 {
20139 let (b, g) = state.graphs.first_mut().expect("checked above");
20140 if *b != bucket_max {
20141 g.retarget_bucket(bucket_max)?;
20142 *b = bucket_max;
20143 }
20144 }
20145 let graph = state.graphs.first().map(|(_, g)| g).expect("checked above");
20146
20147 {
20149 let fa0 = match &self.layers[0].mixer {
20150 Mixer::Full(fa) => fa,
20151 _ => return Err("step35 token graph expects full-attention layers".into()),
20152 };
20153 let tp0 = fa0
20154 .step_tp_qkv
20155 .as_ref()
20156 .ok_or("step35 token graph lost its TP state")?;
20157 for rank in 0..tp0.runtime.devices().len() {
20158 let engine = tp0
20159 .runtime
20160 .rank_engine(rank)
20161 .ok_or("step35 token graph lost a rank engine")?;
20162 let _main = engine.gpu.enter_main()?;
20163 engine.stream().synchronize()?;
20164 }
20165 }
20166
20167 {
20170 let _main = e.gpu.enter_main()?;
20171 e.set_u32_one(&mut state.token_d, token)?;
20172 e.set_i32_one(&mut state.pos_d, pos as i32)?;
20173 e.set_i32_one(&mut state.hist_idx, 0)?;
20174 }
20175 for _ in 0..k {
20176 graph.launch(e)?;
20177 }
20178 for il in 0..n_layers {
20180 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
20181 let transaction = tp_kv.begin_transaction()?;
20182 let fa = match &self.layers[il].mixer {
20183 Mixer::Full(fa) => fa,
20184 _ => return Err("step35 token graph expects full-attention layers".into()),
20185 };
20186 let tp = fa
20187 .step_tp_qkv
20188 .as_ref()
20189 .ok_or("step35 token graph lost its TP state")?;
20190 let empty: [CudaSlice<f32>; 0] = [];
20191 tp.runtime.append_tp_kv_transaction_inner(
20192 tp_kv,
20193 transaction,
20194 &empty,
20195 &empty,
20196 k,
20197 true,
20198 )?;
20199 tp.runtime
20200 .commit_tp_kv_transaction_external(tp_kv, transaction, k)?;
20201 if let Some(local) = cache.kv[il].as_mut() {
20202 local.len = pos + k;
20203 let _main = e.gpu.enter_main()?;
20204 e.set_i32_one(&mut local.len_d, (pos + k) as i32)?;
20205 }
20206 }
20207 cache.pos = pos + k;
20208 let (hist, logits) = {
20209 let _main = e.gpu.enter_main()?;
20210 e.stream().synchronize()?;
20211 (e.dtoh_u32(&state.token_hist)?, e.dtoh(&state.logits_stage)?)
20212 };
20213 Ok(Some((hist[..k].to_vec(), logits)))
20214 }
20215}
20216
20217impl HybridModel {
20218 #[allow(clippy::too_many_arguments)]
20223 fn step35_token_graph_build(
20224 &self,
20225 e: &Engine,
20226 cache: &mut Cache,
20227 state: &mut Step35TokenGraphState,
20228 bucket_max: usize,
20229 ) -> Result<(), Box<dyn std::error::Error>> {
20230 use cudarc::driver::DevicePtr;
20231 let n_embd = self.cfg.n_embd as usize;
20232 let eps = self.cfg.rms_eps;
20233 let n_layers = self.layers.len();
20234 let started = std::time::Instant::now();
20235 if !crate::router_kernel_on() {
20236 return Err(
20237 "step35 token graph requires the router kernel (MEMRA_ROUTER_KERNEL=0)".into(),
20238 );
20239 }
20240 if !Engine::bf16_mmv_on() || n_embd % 8 != 0 {
20241 return Err("step35 token graph requires MEMRA_BF16_MMV bf16-resident matvecs".into());
20242 }
20243
20244 let embd_gpu = self
20246 .embd_gpu_try(e)
20247 .ok_or("step35 token graph could not upload the device embed table")?;
20248 let embd_qtype = match self.embd.ggml_type {
20249 memra_gguf::GgmlType::BF16 => crate::QT_BF16,
20250 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
20251 other => return Err(format!("token graph embed dtype {other:?} unhandled").into()),
20252 };
20253 let embd_row_bytes = self.embd.raw.len() / self.cfg.n_vocab as usize;
20254
20255 let (p_mixed, p_kshadow, p_vshadow) = {
20257 let _main = e.gpu.enter_main()?;
20258 let stream = e.stream();
20259 let (a, _g) = state.mixed_stage.device_ptr(&stream);
20260 let (b, _g) = state.k_shadow_stage.device_ptr(&stream);
20261 let (c, _g) = state.v_shadow_stage.device_ptr(&stream);
20262 (a as u64, b as u64, c as u64)
20263 };
20264
20265 crate::tp::token_graph_build_begin()?;
20266 let mut group_id: u32 = 0;
20267 for il in 0..n_layers {
20268 let layer = &self.layers[il];
20269 let fa = match &layer.mixer {
20270 Mixer::Full(fa) => fa,
20271 _ => return Err("step35 token graph expects full-attention layers".into()),
20272 };
20273 let tp = fa
20274 .step_tp_qkv
20275 .as_ref()
20276 .ok_or("step35 token graph lost its TP state")?;
20277 let attention = tp
20278 .attention
20279 .as_ref()
20280 .ok_or("step35 token graph lost its attention aux")?;
20281 let geometry = self.step35_geom(il);
20282 let window = geometry.window.map(|w| w as usize);
20283 let head_dim = geometry.head_dim_k as usize;
20284 let heads = geometry.n_head as usize;
20285 let kv_heads = geometry.n_head_kv as usize;
20286 let ranks = tp.runtime.devices().len();
20287 let local_heads = heads / ranks;
20288 let local_kv_heads = kv_heads / ranks;
20289 let layer_bucket = window.map(|w| bucket_max.min(w)).unwrap_or(bucket_max);
20290 let use_gate_shards =
20291 attention.gate_shards.is_some() || attention.gate_shards_bf16.is_some();
20292 if !use_gate_shards {
20293 return Err("step35 token graph requires the fused gate shards".into());
20294 }
20295
20296 let ws_index = tp
20297 .runtime
20298 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
20299 let ws_mutex = tp.runtime.decode_v2_workspace();
20300 let mut ws_guard = ws_mutex
20301 .lock()
20302 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
20303 let ws = ws_guard
20304 .get_mut(ws_index)
20305 .ok_or("step TP decode v2 workspace missing after ensure")?;
20306 tp.runtime
20307 .decode_v2_arm_token_mirrors(ws, p_mixed, (p_kshadow, p_vshadow))?;
20308 let mut rope_freqs = Vec::with_capacity(ranks);
20309 for rank in 0..ranks {
20310 let engine = tp
20311 .runtime
20312 .rank_engine(rank)
20313 .ok_or("step35 token graph lost a rank engine")?;
20314 rope_freqs.push(if geometry.rope_factors {
20315 self.step35_aux
20316 .as_ref()
20317 .and_then(|aux| aux.rope_freqs(engine))
20318 } else {
20319 None
20320 });
20321 }
20322 let gate_shards_arg = if let Some(shards) = attention.gate_shards.as_deref() {
20323 Some(crate::tp::StepTpGateShards::F32(shards))
20324 } else {
20325 attention
20326 .gate_shards_bf16
20327 .as_deref()
20328 .map(crate::tp::StepTpGateShards::Bf16)
20329 };
20330
20331 let decode_input = attention
20333 .decode_input
20334 .as_ref()
20335 .ok_or("step35 token graph requires the replicated decode input")?;
20336 let mut decode_input = decode_input
20337 .lock()
20338 .map_err(|_| "replicated decode input lock is poisoned")?;
20339 if ws.h_stage.is_none() {
20341 return Err(
20342 "step35 token graph requires the stage flow armed (run eager dcw first)".into(),
20343 );
20344 }
20345 {
20346 let state_x = &mut state.x;
20347 let token_d = &state.token_d;
20348 let pos_d = &state.pos_d;
20349 crate::tp::graph_section(e, None, || {
20350 let _main = e.gpu.enter_main()?;
20351 if il == 0 {
20352 e.embed_gather_device_into(
20353 embd_gpu,
20354 token_d,
20355 state_x,
20356 n_embd,
20357 embd_qtype,
20358 embd_row_bytes,
20359 )?;
20360 }
20361 {
20362 let h_stage = ws.h_stage.as_mut().expect("stage armed checked above");
20363 e.rms_norm(
20364 state_x,
20365 layer.attn_norm.float_data(),
20366 h_stage,
20367 n_embd,
20368 1,
20369 eps,
20370 )?;
20371 }
20372 {
20373 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
20374 let mut dst = pos_stage.slice_mut(0..1);
20375 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
20376 }
20377 Ok(())
20378 })?;
20379 }
20380
20381 group_id += 1;
20383 for rank in 0..ranks {
20384 let engine = tp
20385 .runtime
20386 .rank_engine(rank)
20387 .ok_or("step35 token graph lost a rank engine")?;
20388 {
20389 let ceiling = window
20394 .map(|w| cache.max_ctx.min(w))
20395 .unwrap_or(cache.max_ctx);
20396 let _main = engine.gpu.enter_main()?;
20397 engine.fa_dcw_pool_ensure(
20398 head_dim,
20399 local_heads,
20400 local_kv_heads,
20401 ceiling.min(2048),
20402 )?;
20403 engine.fa_dcw_pool_ensure(head_dim, local_heads, local_kv_heads, ceiling)?;
20404 engine.fa_dcw_pool_ensure(
20405 head_dim,
20406 local_heads,
20407 local_kv_heads,
20408 layer_bucket,
20409 )?;
20410 }
20411 let runtime = &tp.runtime;
20412 let q_norm = &attention.q_norm;
20413 let k_norm = &attention.k_norm;
20414 let gate_ref = gate_shards_arg.as_ref();
20415 crate::tp::graph_section(engine, Some(group_id), || {
20416 runtime.decode_v2_input_qkv_rank(
20417 ws,
20418 &state.pos_d,
20419 &mut decode_input,
20420 &tp.q,
20421 &tp.k,
20422 &tp.v,
20423 q_norm,
20424 k_norm,
20425 head_dim,
20426 geometry.n_rot as usize,
20427 geometry.rope_base,
20428 &rope_freqs,
20429 eps,
20430 gate_ref,
20431 true,
20432 false,
20433 rank,
20434 None,
20435 )?;
20436 let distributed = cache.tp_kv[il]
20439 .as_mut()
20440 .ok_or("step35 token graph lost a TP cache")?;
20441 let (kv_dim_k, kv_dim_v) = (distributed.kv_dim_k(), distributed.kv_dim_v());
20442 let (ktb, vtb) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
20443 let capacity = distributed.physical_capacity();
20444 {
20445 let rank_cache = distributed
20446 .rank_mut(rank)
20447 .ok_or("step35 token graph lost a rank cache")?;
20448 let (k_plane, v_plane, len_d, base_d) =
20449 rank_cache.planes_and_counters_mut();
20450 engine.append_kv_quantized_dcw(
20451 &ws.k[rank],
20452 &ws.v_raw[rank],
20453 k_plane,
20454 v_plane,
20455 len_d,
20456 base_d,
20457 kv_dim_k,
20458 kv_dim_v,
20459 ktb,
20460 vtb,
20461 )?;
20462 }
20463 {
20464 let rank_cache = distributed
20465 .rank_mut(rank)
20466 .ok_or("step35 token graph lost a rank cache")?;
20467 engine.inc_i32(rank_cache.len_d_mut())?;
20468 }
20469 let rank_cache = distributed
20470 .rank(rank)
20471 .ok_or("step35 token graph lost a rank cache")?;
20472 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * ktb);
20473 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * vtb);
20474 engine.fa_decode_dcw(
20479 &ws.q[rank],
20480 &k_ring,
20481 &v_ring,
20482 &mut ws.attn_out[rank],
20483 head_dim,
20484 local_heads,
20485 local_kv_heads,
20486 rank_cache.len_d(),
20487 rank_cache.base_d(),
20488 window.unwrap_or(0),
20489 layer_bucket,
20490 geometry.attention_scale(),
20491 ktb,
20492 vtb,
20493 None,
20494 )?;
20495 engine.attn_head_gate(
20496 &ws.attn_out[rank],
20497 &ws.gate[rank],
20498 &mut ws.gated[rank],
20499 None,
20500 head_dim,
20501 local_heads,
20502 1,
20503 )?;
20504 runtime.decode_v2_finish_rank_partial(ws, &tp.o, true, rank)?;
20505 Ok(())
20506 })?;
20507 }
20508
20509 {
20511 let root = tp
20512 .runtime
20513 .rank_engine(0)
20514 .ok_or("step35 token graph lost the root engine")?;
20515 let runtime = &tp.runtime;
20516 crate::tp::graph_section(root, None, || runtime.decode_v2_finish_root_fused(ws))?;
20517 }
20518 drop(ws_guard);
20519 drop(decode_input);
20520
20521 let probe_layer: Option<usize> = std::env::var("MEMRA_TG_PROBE_LAYER")
20522 .ok()
20523 .and_then(|v| v.parse().ok());
20524 if probe_layer == Some(il) {
20525 let Step35TokenGraphState {
20526 mixed_stage,
20527 probe_mixed,
20528 ..
20529 } = &mut *state;
20530 crate::tp::graph_section(e, None, || {
20531 let _main = e.gpu.enter_main()?;
20532 let mut dst = probe_mixed.slice_mut(0..n_embd);
20533 e.stream()
20534 .memcpy_dtod(&mixed_stage.slice(0..n_embd), &mut dst)?;
20535 Ok(())
20536 })?;
20537 }
20538
20539 match &layer.ffn {
20541 crate::hybrid::Ffn::Dense {
20542 ffn_gate,
20543 ffn_up,
20544 ffn_down,
20545 } => {
20546 let n_ff = ffn_gate.out_features();
20547 let lim = self.cfg.clamp_shexp_at(il as u32);
20548 if lim.is_some() {
20552 return Err("step35 token graph dense FFN with clamp unsupported".into());
20553 }
20554 let (wg_d, wu_d, wd_d) = match (ffn_gate, ffn_up, ffn_down) {
20555 (
20556 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
20557 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
20558 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
20559 ) => (wg, wu, wd),
20560 _ => {
20561 return Err(
20562 "step35 token graph dense FFN requires bf16-resident weights"
20563 .into(),
20564 );
20565 }
20566 };
20567 crate::tp::graph_section(e, None, || {
20568 let _main = e.gpu.enter_main()?;
20569 let Step35TokenGraphState {
20570 x,
20571 x1,
20572 mixed_stage,
20573 dense_z,
20574 dense_gate,
20575 dense_up,
20576 dense_act,
20577 sh_stage,
20578 ..
20579 } = &mut *state;
20580 e.add_rms_norm(
20581 x,
20582 mixed_stage,
20583 layer.post_attn_norm.float_data(),
20584 x1,
20585 dense_z,
20586 n_embd,
20587 1,
20588 eps,
20589 )?;
20590 e.matvec_bf16_into(wg_d, dense_z, dense_gate, n_embd, n_ff)?;
20594 e.matvec_bf16_into(wu_d, dense_z, dense_up, n_embd, n_ff)?;
20595 Self::ffn_act_lim(
20596 e, &self.cfg, dense_gate, dense_up, 1.0, 1.0, lim, dense_act, n_ff,
20597 )?;
20598 e.matvec_bf16_into(wd_d, dense_act, sh_stage, n_ff, n_embd)?;
20599 e.add(x1, sh_stage, x, n_embd)?;
20600 Ok(())
20601 })?;
20602 }
20603 crate::hybrid::Ffn::Moe(m) => {
20604 let moe = self
20605 .cfg
20606 .moe
20607 .as_ref()
20608 .ok_or("step35 token graph needs moe cfg")?;
20609 let n_expert = moe.expert_count as usize;
20610 let n_used = moe.expert_used_count as usize;
20611 let sigmoid = self
20612 .cfg
20613 .sigmoid_router()
20614 .ok_or("step35 token graph needs the sigmoid router")?;
20615 let step_tp = m
20616 .step_tp
20617 .as_ref()
20618 .ok_or("step35 token graph needs TP experts")?;
20619 let bank = match &step_tp.experts {
20620 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => bank,
20621 _ => return Err("step35 token graph needs the NVFP4 bank".into()),
20622 };
20623 let routes_ws_mutex = bank.device_workspace_handle();
20624 let mut routes_guard = routes_ws_mutex
20625 .lock()
20626 .map_err(|_| "routes workspace lock is poisoned")?;
20627 let routes_ws = routes_guard
20628 .as_mut()
20629 .ok_or("step35 token graph requires the routes workspace warmed")?;
20630 routes_ws.arm_stages(e, bank.input_width, n_used)?;
20631 step_tp.runtime.routes_arm_raw(bank, routes_ws)?;
20632 let p_z = {
20633 let root = step_tp
20634 .runtime
20635 .rank_engine(0)
20636 .ok_or("routes root engine missing")?;
20637 let _main = root.gpu.enter_main()?;
20638 let stream = root.stream();
20639 let in_stage = routes_ws
20640 .in_stage_handle()
20641 .ok_or("routes in stage not armed")?;
20642 let (a, _g) = in_stage.device_ptr(&stream);
20643 a as u64
20644 };
20645 let local_out = bank.expert_width / ranks;
20646
20647 crate::tp::graph_section(e, None, || {
20649 let _main = e.gpu.enter_main()?;
20650 {
20651 let in_stage = routes_ws
20652 .in_stage_mut()
20653 .ok_or("routes in stage not armed")?;
20654 let Step35TokenGraphState {
20655 x, x1, mixed_stage, ..
20656 } = &mut *state;
20657 e.add_rms_norm(
20658 x,
20659 mixed_stage,
20660 layer.post_attn_norm.float_data(),
20661 x1,
20662 in_stage,
20663 n_embd,
20664 1,
20665 eps,
20666 )?;
20667 }
20668 {
20669 let z_ref = routes_ws
20670 .in_stage_handle()
20671 .ok_or("routes in stage not armed")?;
20672 e.router_gemv_into(
20673 m.gate_inp.float_data(),
20674 z_ref,
20675 &mut state.router_logits,
20676 n_embd,
20677 n_expert,
20678 1,
20679 )?;
20680 }
20681 let (sel_e, w_e) = routes_ws
20682 .dev_route_e_mut()
20683 .ok_or("routes staging not armed")?;
20684 e.moe_router_sigmoid_topk_into(
20685 &state.router_logits,
20686 1,
20687 n_expert,
20688 n_used,
20689 m.active_count(),
20690 &m.exp_probs_b_dev,
20691 &m.active_experts_dev,
20692 sigmoid.0,
20693 sigmoid.1,
20694 sel_e,
20695 w_e,
20696 )?;
20697 Ok(())
20698 })?;
20699
20700 group_id += 1;
20702 for rank in 0..ranks {
20703 let engine = step_tp
20704 .runtime
20705 .rank_engine(rank)
20706 .ok_or("routes rank engine missing")?;
20707 let runtime = &step_tp.runtime;
20708 crate::tp::graph_section(engine, Some(group_id), || {
20709 runtime.routes_rank_section(
20710 bank,
20711 routes_ws,
20712 p_z,
20713 local_out,
20714 n_used,
20715 step_tp.activation_limit,
20716 rank,
20717 )
20718 })?;
20719 }
20720
20721 {
20723 let root = step_tp
20724 .runtime
20725 .rank_engine(0)
20726 .ok_or("routes root engine missing")?;
20727 let runtime = &step_tp.runtime;
20728 crate::tp::graph_section(root, None, || {
20729 runtime.routes_root_section(bank, routes_ws)
20730 })?;
20731 }
20732
20733 let lim_sh = self.cfg.clamp_shexp_at(il as u32);
20737 let (wg_sh, wu_sh, wd_sh) = match (&m.gate_shexp, &m.up_shexp, &m.down_shexp) {
20738 (
20739 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
20740 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
20741 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
20742 ) => (wg, wu, wd),
20743 _ => {
20744 return Err(
20745 "step35 token graph shexp requires bf16-resident weights".into()
20746 );
20747 }
20748 };
20749 let n_ff_sh = m
20750 .gate_shexp
20751 .as_ref()
20752 .expect("matched Some above")
20753 .out_features();
20754 let gate_inp_shexp = m.gate_inp_shexp.as_ref();
20757 crate::tp::graph_section(e, None, || {
20758 let _main = e.gpu.enter_main()?;
20759 let (z_ref, out_stage) = routes_ws
20760 .in_and_out_stages_mut()
20761 .ok_or("routes stages not armed")?;
20762 let Step35TokenGraphState {
20763 x,
20764 x1,
20765 sh_stage,
20766 shexp_gate,
20767 shexp_up,
20768 shexp_act,
20769 gate_sig,
20770 ..
20771 } = &mut *state;
20772 e.matvec_bf16_dual_into(
20773 wg_sh, wu_sh, z_ref, shexp_gate, shexp_up, n_embd, n_ff_sh,
20774 )?;
20775 Self::ffn_act_lim(
20776 e, &self.cfg, shexp_gate, shexp_up, 1.0, 1.0, lim_sh, shexp_act,
20777 n_ff_sh,
20778 )?;
20779 e.matvec_bf16_into(wd_sh, shexp_act, sh_stage, n_ff_sh, n_embd)?;
20780 if let Some(gate_w) = gate_inp_shexp {
20781 e.sigmoid_dot_rows_into(
20782 z_ref,
20783 gate_w.float_data(),
20784 gate_sig,
20785 n_embd,
20786 1,
20787 )?;
20788 }
20789 e.add_scaled_rows(sh_stage, gate_sig, out_stage, n_embd, 1)?;
20790 e.add(x1, out_stage, x, n_embd)?;
20791 Ok(())
20792 })?;
20793 }
20794 }
20795 if probe_layer == Some(il) {
20796 let Step35TokenGraphState { x, probe_x, .. } = &mut *state;
20797 crate::tp::graph_section(e, None, || {
20798 let _main = e.gpu.enter_main()?;
20799 let mut dst = probe_x.slice_mut(0..n_embd);
20800 e.stream().memcpy_dtod(&x.slice(0..n_embd), &mut dst)?;
20801 Ok(())
20802 })?;
20803 }
20804 }
20805
20806 let head = match &self.output {
20808 crate::model::GpuTensor::FloatBf16 { data, .. } => data,
20809 _ => return Err("step35 token graph head requires the bf16-resident output".into()),
20810 };
20811 crate::tp::graph_section(e, None, || {
20812 let _main = e.gpu.enter_main()?;
20813 let Step35TokenGraphState {
20814 x,
20815 hn,
20816 logits_stage,
20817 token_d,
20818 pos_d,
20819 token_hist,
20820 hist_idx,
20821 ..
20822 } = &mut *state;
20823 e.rms_norm(x, self.output_norm.float_data(), hn, n_embd, 1, eps)?;
20824 e.matvec_bf16_into(head, hn, logits_stage, n_embd, self.cfg.n_vocab as usize)?;
20825 e.argmax_token_device_into(logits_stage, token_d, self.cfg.n_vocab as usize)?;
20831 e.u32_hist_append(token_d, token_hist, hist_idx)?;
20832 e.inc_i32(pos_d)?;
20833 Ok(())
20834 })?;
20835
20836 let graph = crate::tp::token_graph_build_finish()?;
20837 state.graphs.push((bucket_max, graph));
20838 eprintln!(
20839 "[step35-token-graph] built bucket={bucket_max} layers={n_layers} \
20840 build_ms={:.0} performance_claim=false",
20841 started.elapsed().as_secs_f64() * 1e3
20842 );
20843 Ok(())
20844 }
20845}