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}
759static HEAD_SPLIT_WS: std::sync::Mutex<Option<HeadSplit>> = std::sync::Mutex::new(None);
761
762#[allow(clippy::type_complexity)]
766static DEV1_ROUTER_REPS: std::sync::Mutex<
767 Option<(
768 std::collections::HashMap<u16, (CudaSlice<f32>, CudaSlice<f32>, CudaSlice<u8>)>,
769 Option<CudaSlice<f32>>,
770 )>,
771> = std::sync::Mutex::new(None);
772
773#[allow(clippy::type_complexity)]
777static SHEXP_D1_REPS: std::sync::Mutex<
778 Option<std::collections::HashMap<u16, (CudaSlice<u8>, CudaSlice<u8>, CudaSlice<u8>)>>,
779> = std::sync::Mutex::new(None);
780#[allow(clippy::type_complexity)]
781static SHEXP_D1_WS: std::sync::Mutex<
782 Option<(
783 (usize, usize),
784 CudaSlice<f32>,
785 CudaSlice<f32>,
786 CudaSlice<f32>,
787 cudarc::driver::CudaEvent,
788 cudarc::driver::CudaEvent,
789 )>,
790> = std::sync::Mutex::new(None);
791
792static SHEXP_OV_WS: std::sync::Mutex<
794 Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>,
795> = std::sync::Mutex::new(None);
796
797impl HybridModel {
798 pub fn gdn_prime_grid_on(&self) -> bool {
804 Engine::gdn_chunked_enabled()
805 && self
806 .layers
807 .iter()
808 .any(|l| matches!(l.mixer, crate::hybrid::Mixer::Linear(_)))
809 }
810
811 fn step35_tp_device_resident(e: &Engine, tp: &crate::hybrid::StepTpQkv) -> bool {
816 tp.runtime.native_p2p() && tp.runtime.root_shares_ctx(e)
817 }
818
819 fn step35_tp_qkv(
820 &self,
821 e: &Engine,
822 fa: &FullAttnLayer,
823 h: &CudaSlice<f32>,
824 t: usize,
825 ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
826 let Some(tp) = fa.step_tp_qkv.as_ref() else {
827 return Ok(None);
828 };
829 let values = active_matrix_values(
830 h.len(),
831 t,
832 self.cfg.n_embd as usize,
833 "Step TP QKV activation",
834 )?;
835 if Self::step35_tp_device_resident(e, tp) {
844 e.stream().synchronize()?;
847 let q = tp
848 .runtime
849 .bf16_column_parallel_resident_native_device(&tp.q, h, t)?;
850 let k = tp
851 .runtime
852 .bf16_column_parallel_resident_native_device(&tp.k, h, t)?;
853 let v = tp
854 .runtime
855 .bf16_column_parallel_resident_native_device(&tp.v, h, t)?;
856 Self::step35_tp_log_once(tp, "qkv", "device-resident");
857 return Ok(Some(vec![q, k, v]));
858 }
859 let host = e.dtoh_view(&h.slice(0..values))?;
860 let q = if tp.runtime.native_p2p() {
861 tp.runtime
862 .bf16_column_parallel_resident_native(&tp.q, &host, t)?
863 } else {
864 tp.runtime
865 .bf16_column_parallel_resident(&tp.q, &host, t)?
866 .gathered
867 };
868 let k = if tp.runtime.native_p2p() {
869 tp.runtime
870 .bf16_column_parallel_resident_native(&tp.k, &host, t)?
871 } else {
872 tp.runtime
873 .bf16_column_parallel_resident(&tp.k, &host, t)?
874 .gathered
875 };
876 let v = if tp.runtime.native_p2p() {
877 tp.runtime
878 .bf16_column_parallel_resident_native(&tp.v, &host, t)?
879 } else {
880 tp.runtime
881 .bf16_column_parallel_resident(&tp.v, &host, t)?
882 .gathered
883 };
884 Self::step35_tp_log_once(tp, "qkv", "host-canonical");
885 Ok(Some(vec![e.htod(&q)?, e.htod(&k)?, e.htod(&v)?]))
886 }
887
888 fn step35_tp_log_once(tp: &crate::hybrid::StepTpQkv, proj: &str, activation: &'static str) {
892 use std::sync::atomic::{AtomicBool, Ordering};
893 static LOGGED: [AtomicBool; 4] = [
894 AtomicBool::new(false),
895 AtomicBool::new(false),
896 AtomicBool::new(false),
897 AtomicBool::new(false),
898 ];
899 let idx = 2 * usize::from(proj == "o") + usize::from(activation == "device-resident");
900 if LOGGED[idx].swap(true, Ordering::Relaxed) {
901 return;
902 }
903 eprintln!(
904 "[step-tp-{proj}] execute layer={} devices={:?} projections={proj} \
905 tensor_parallel=true attention_local=true kv_local=true transport={} \
906 native_p2p={} bulk_p2p={} activation={activation} \
907 output={} performance_claim=false (logged once per transport)",
908 tp.layer,
909 tp.devices,
910 tp.runtime.transport_label(),
911 tp.runtime.native_p2p(),
912 tp.runtime.bulk_p2p(),
913 if activation == "device-resident" {
914 "root-resident"
915 } else {
916 "root-readback"
917 },
918 );
919 }
920
921 fn step35_tp_o(
922 &self,
923 e: &Engine,
924 fa: &FullAttnLayer,
925 activation: &CudaSlice<f32>,
926 tokens: usize,
927 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
928 let Some(tp) = fa.step_tp_qkv.as_ref() else {
929 return Ok(None);
930 };
931 if Self::step35_tp_device_resident(e, tp) {
935 e.stream().synchronize()?; let output = tp
937 .runtime
938 .step_bf16_row_parallel_resident_native_device(&tp.o, activation, tokens)?;
939 Self::step35_tp_log_once(tp, "o", "device-resident");
940 return Ok(Some(output));
941 }
942 let host = e.dtoh(activation)?;
943 let output = if tp.runtime.native_p2p() {
944 tp.runtime
945 .step_bf16_row_parallel_resident_native(&tp.o, &host, tokens)?
946 } else {
947 tp.runtime
948 .step_bf16_row_parallel_resident(&tp.o, &host, tokens)?
949 };
950 Self::step35_tp_log_once(tp, "o", "host-canonical");
951 Ok(Some(e.htod(&output)?))
952 }
953
954 fn step35_o(
955 &self,
956 e: &Engine,
957 fa: &FullAttnLayer,
958 activation: &CudaSlice<f32>,
959 tokens: usize,
960 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
961 match self.step35_tp_o(e, fa, activation, tokens)? {
962 Some(output) => Ok(output),
963 None => e.matmul(&fa.wo, activation, tokens),
964 }
965 }
966
967 fn prime_trace_path() -> Option<&'static str> {
972 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
973 P.get_or_init(|| std::env::var("MEMRA_PRIME_TRACE").ok())
974 .as_deref()
975 }
976
977 fn prime_anatomy_on() -> bool {
983 static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
984 *E.get_or_init(|| std::env::var("MEMRA_PRIME_ANATOMY").as_deref() == Ok("1"))
985 }
986
987 fn prime_anatomy_slots() -> &'static [std::sync::atomic::AtomicU64; 5] {
988 static S: [std::sync::atomic::AtomicU64; 5] = [
989 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), ];
995 &S
996 }
997
998 pub fn forward(
1000 &self,
1001 e: &Engine,
1002 tokens: &[u32],
1003 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
1004 if self.is_gemma4_e4b() {
1005 return self.gemma4_e4b_forward(e, tokens, false);
1006 }
1007 if self.uses_gemma_program() {
1008 return self.gemma4_forward(e, tokens, false);
1009 }
1010 let cfg = &self.cfg;
1011 let n_embd = cfg.n_embd as usize;
1012 let t = tokens.len();
1013 let eps = cfg.rms_eps;
1014 let pos: Vec<i32> = (0..t as i32).collect();
1015 let pos_d = e.htod_i32(&pos)?;
1016
1017 let mut x = self.embed(e, tokens)?; for (il, layer) in self.layers.iter().enumerate() {
1020 let mut h = e.uninit(t * n_embd)?;
1022 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1023
1024 let mixed = match &layer.mixer {
1025 Mixer::Full(fa) => self.full_attn(e, fa, &h, &pos_d, t, il)?,
1026 Mixer::Linear(la) => self.linear_attn(e, la, &h, t)?,
1027 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1028 };
1029
1030 let mut x1 = e.uninit(t * n_embd)?;
1032 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1033
1034 let mut z = e.uninit(t * n_embd)?;
1036 e.rms_norm(
1037 &x1,
1038 layer.post_attn_norm.float_data(),
1039 &mut z,
1040 n_embd,
1041 t,
1042 eps,
1043 )?;
1044 let ffn_out = match &layer.ffn {
1045 crate::hybrid::Ffn::Dense {
1046 ffn_gate,
1047 ffn_up,
1048 ffn_down,
1049 } => {
1050 let n_ff = ffn_gate.out_features();
1051 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1052 let up = g2.pop().unwrap();
1053 let gate = g2.pop().unwrap();
1054 let mut act = e.uninit(t * n_ff)?;
1055 Self::ffn_act_lim(
1060 e,
1061 &self.cfg,
1062 &gate,
1063 &up,
1064 1.0,
1065 1.0,
1066 self.cfg.clamp_shexp_at(il as u32),
1067 &mut act,
1068 t * n_ff,
1069 )?;
1070 e.matmul(ffn_down, &act, t)?
1071 }
1072 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
1073 };
1074 let mut x2 = e.uninit(t * n_embd)?;
1075 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1076 x = x2;
1077 }
1078
1079 let mut hn = e.uninit(t * n_embd)?;
1080 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1081 let logits = e.matmul(&self.output, &hn, t)?;
1082 Ok(e.dtoh(&logits)?)
1083 }
1084
1085 pub fn forward_last(
1091 &self,
1092 e: &Engine,
1093 tokens: &[u32],
1094 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
1095 if self.uses_gemma_program() {
1096 return self.gemma4_forward(e, tokens, true);
1097 }
1098 let cfg = &self.cfg;
1099 let n_embd = cfg.n_embd as usize;
1100 let t = tokens.len();
1101 let eps = cfg.rms_eps;
1102 let pos: Vec<i32> = (0..t as i32).collect();
1103 let pos_d = e.htod_i32(&pos)?;
1104
1105 let mut x = self.embed(e, tokens)?; let probe = std::env::var("MEMRA_LAYER_PROBE").is_ok();
1109 let anat = Self::prime_anatomy_on();
1110 let mut anat_last = if anat {
1111 e.stream().synchronize()?;
1112 Some(std::time::Instant::now())
1113 } else {
1114 None
1115 };
1116 macro_rules! anat_mark {
1117 ($slot:expr) => {
1118 if let Some(ts) = anat_last.as_mut() {
1119 e.stream().synchronize()?;
1120 Self::prime_anatomy_slots()[$slot].fetch_add(
1121 ts.elapsed().as_nanos() as u64,
1122 std::sync::atomic::Ordering::Relaxed,
1123 );
1124 *ts = std::time::Instant::now();
1125 }
1126 };
1127 }
1128 for (il, layer) in self.layers.iter().enumerate() {
1129 let mut h = e.uninit(t * n_embd)?;
1130 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
1131 if probe {
1132 e.stream().synchronize()?;
1133 eprintln!("[probe] L{il} norm ok");
1134 }
1135 anat_mark!(4);
1136 let mixed = match &layer.mixer {
1137 Mixer::Full(fa) => {
1138 let y = self.full_attn(e, fa, &h, &pos_d, t, il)?;
1139 anat_mark!(0);
1140 y
1141 }
1142 Mixer::Linear(la) => {
1143 let y = self.linear_attn(e, la, &h, t)?;
1144 anat_mark!(1);
1145 y
1146 }
1147 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1148 };
1149 if probe {
1150 e.stream().synchronize()?;
1151 eprintln!("[probe] L{il} mixer ok");
1152 }
1153 let mut x1 = e.uninit(t * n_embd)?;
1154 e.add(&x, &mixed, &mut x1, t * n_embd)?;
1155 let mut z = e.uninit(t * n_embd)?;
1156 e.rms_norm(
1157 &x1,
1158 layer.post_attn_norm.float_data(),
1159 &mut z,
1160 n_embd,
1161 t,
1162 eps,
1163 )?;
1164 anat_mark!(4);
1165 let ffn_out = match &layer.ffn {
1166 crate::hybrid::Ffn::Dense {
1167 ffn_gate,
1168 ffn_up,
1169 ffn_down,
1170 } => {
1171 let n_ff = ffn_gate.out_features();
1172 let mut g2 = e.matmul_group(&[ffn_gate, ffn_up], &z, t)?;
1173 let up = g2.pop().unwrap();
1174 let gate = g2.pop().unwrap();
1175 let mut act = e.uninit(t * n_ff)?;
1176 Self::ffn_act_lim(
1178 e,
1179 &self.cfg,
1180 &gate,
1181 &up,
1182 1.0,
1183 1.0,
1184 self.cfg.clamp_shexp_at(il as u32),
1185 &mut act,
1186 t * n_ff,
1187 )?;
1188 let y = e.matmul(ffn_down, &act, t)?;
1189 anat_mark!(3);
1190 y
1191 }
1192 crate::hybrid::Ffn::Moe(m) => {
1193 let y = self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?;
1194 anat_mark!(2);
1195 y
1196 }
1197 };
1198 if probe {
1199 e.stream().synchronize()?;
1200 eprintln!("[probe] L{il} ffn ok");
1201 }
1202 let mut x2 = e.uninit(t * n_embd)?;
1203 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
1204 x = x2;
1205 }
1206 if anat {
1207 let s = Self::prime_anatomy_slots();
1208 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
1209 eprintln!(
1210 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
1211 dense={:.1} norms_adds={:.1} (t={t}, forward_last)",
1212 ms(0),
1213 ms(1),
1214 ms(2),
1215 ms(3),
1216 ms(4)
1217 );
1218 }
1219 let mut hn = e.uninit(t * n_embd)?;
1221 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
1222 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)?;
1225 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
1226 let logits = e.matmul(&self.output, &hlast, 1)?; Ok(e.dtoh(&logits)?)
1228 }
1229
1230 pub fn prime_cache(
1262 &self,
1263 e: &Engine,
1264 tokens: &[u32],
1265 cache: &mut Cache,
1266 queued_after: usize,
1267 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1268 self.prime_cache_overlaid(e, tokens, cache, queued_after, None)
1269 }
1270
1271 pub fn prime_cache_overlaid(
1277 &self,
1278 e: &Engine,
1279 tokens: &[u32],
1280 cache: &mut Cache,
1281 queued_after: usize,
1282 overlay: Option<&crate::vision::EmbedOverlay>,
1283 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1284 let n_embd = self.cfg.n_embd as usize;
1285 let t = tokens.len();
1286 if overlay.is_none() {
1290 if let Some(out) = self.step35_prime_trows(e, tokens, cache)? {
1291 return Ok(out);
1292 }
1293 }
1294 assert!(
1298 t >= PRIME_MIN_T,
1299 "prime_cache needs T >= {PRIME_MIN_T} (caller gates)"
1300 );
1301 assert!(
1302 cache.pos + t <= cache.max_ctx,
1303 "prime_cache: prompt exceeds cache max_ctx"
1304 );
1305
1306 if self.is_gemma4_e4b() || self.uses_gemma_program() {
1318 if self.is_gemma4_e4b() {
1319 if overlay.is_some() {
1320 return Err(
1321 "vision embedding overlay is unsupported on gemma4 E4B (PLE prime)".into(),
1322 );
1323 }
1324 return self.gemma4_e4b_prime(e, tokens, cache);
1325 }
1326 return self.gemma4_prime(e, tokens, cache, overlay);
1331 }
1332 let ranges = prime_chunk_ranges(t, self.layers.len(), self.gdn_prime_grid_on());
1333 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
1369 let seq_end = if legacy_calllocal {
1370 cache.pos + t
1371 } else {
1372 cache.pos + t + queued_after
1373 };
1374 if ranges.len() == 1 {
1375 return self.prime_chunk(e, tokens, cache, seq_end, 0, overlay);
1376 }
1377 if crate::pp::prime_pipe_on() && crate::pp::prime_pp_on() && !crate::pp::pp2_streams_off() {
1382 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()).filter(|f| f.len() == 3) {
1383 if overlay.is_some() {
1384 return Err(
1385 "vision embedding overlay + pipelined PP prime unsupported (v1); \
1386 run the serial prime (single device or MEMRA_PRIME_PIPE=0)"
1387 .into(),
1388 );
1389 }
1390 if crate::pp::pp_multi_stream_same_device() {
1391 return Err(
1392 "prime chunk pipeline refused with 2 stage streams on one device — \
1393 that concurrent-stream placement remains quarantined by the deferred \
1394 pp flake record. Use one device per stage or MEMRA_PRIME_PIPE=0 for \
1395 the serial split."
1396 .into(),
1397 );
1398 }
1399 return self.prime_cache_pp2_pipelined(e, tokens, cache, seq_end, &ranges, &fence);
1400 }
1401 }
1402 let mut hiddens = e.uninit(t * n_embd)?;
1403 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1404 for &(start, end) in &ranges {
1405 if let Some(taps) = cache.dflash_taps.as_mut() {
1407 taps.base = start;
1408 }
1409 let (l, hs, x) =
1410 self.prime_chunk(e, &tokens[start..end], cache, seq_end, start, overlay)?;
1411 e.copy_into(&mut hiddens, start * n_embd, &x, (end - start) * n_embd)?;
1412 last = Some((l, hs));
1413 }
1414 let (logits, h_seed) = last.unwrap();
1415 Ok((logits, h_seed, hiddens))
1416 }
1417
1418 fn prime_cache_pp2_pipelined(
1423 &self,
1424 e: &Engine,
1425 tokens: &[u32],
1426 cache: &mut Cache,
1427 seq_end: usize,
1428 ranges: &[(usize, usize)],
1429 fence: &[usize],
1430 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1431 debug_assert_eq!(fence.len(), 3);
1432 debug_assert!(ranges.len() >= 2);
1433 let rt = crate::pp::PpNRt::get(e)?;
1434 assert_eq!(
1435 rt.n_stages(),
1436 2,
1437 "prime pipeline requires exactly two PP stages"
1438 );
1439 let n_embd = self.cfg.n_embd as usize;
1440 let t = tokens.len();
1441 let initial_base = cache.pos;
1442 let caller_stream = e.stream();
1443
1444 rt.fence_stages_behind(&caller_stream)?;
1449 let max_payload = ranges.iter().map(|(s, e)| (e - s) * n_embd).max().unwrap();
1450 rt.prepare_overlap_slots(0, max_payload)?;
1451
1452 let mut hiddens = e.uninit(t * n_embd)?;
1453 let mut last: Option<(Vec<f32>, CudaSlice<f32>)> = None;
1454 let mut stage_caches = PrimeCacheStages::new(cache, fence[1]);
1455 let (cache0, cache1) = stage_caches.parts();
1456 let (first_start, first_end) = ranges[0];
1457 let mut slot = self.prime_pp2_stage0_enqueue(
1458 e,
1459 rt,
1460 &tokens[first_start..first_end],
1461 cache0,
1462 seq_end,
1463 fence,
1464 initial_base + first_start,
1465 true,
1466 )?;
1467 cache0.pos = initial_base + first_end;
1468
1469 for (i, &(start, end)) in ranges.iter().enumerate() {
1470 let base = initial_base + start;
1471 debug_assert_eq!(
1472 cache1.pos, base,
1473 "stage 1 must drain chunks in original position order"
1474 );
1475 let (out, next_slot) = if let Some(&(next_start, next_end)) = ranges.get(i + 1) {
1476 let next_base = initial_base + next_start;
1477 debug_assert_eq!(
1478 cache0.pos, next_base,
1479 "stage 0 must issue chunks in original position order"
1480 );
1481 let cache0_stage = &mut *cache0;
1482 std::thread::scope(|scope| -> Result<_, Box<dyn std::error::Error>> {
1487 let stage0 = scope.spawn(move || -> Result<usize, String> {
1488 let next = self
1489 .prime_pp2_stage0_enqueue(
1490 e,
1491 rt,
1492 &tokens[next_start..next_end],
1493 cache0_stage,
1494 seq_end,
1495 fence,
1496 next_base,
1497 true,
1498 )
1499 .map_err(|err| err.to_string())?;
1500 cache0_stage.pos = initial_base + next_end;
1501 Ok(next)
1502 });
1503 let x = self.prime_pp2_stage1_enqueue(
1504 e,
1505 rt,
1506 slot,
1507 end - start,
1508 cache1,
1509 seq_end,
1510 fence,
1511 base,
1512 true,
1513 )?;
1514 let out = {
1515 rt.bind_stage(1)?;
1516 let _st1 = rt.enter(1);
1517 let e1 = rt.engine(1, e);
1518 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1519 };
1520 let next = stage0
1521 .join()
1522 .map_err(|_| "pipeprime stage-0 host walker panicked")?
1523 .map_err(|err| -> Box<dyn std::error::Error> { err.into() })?;
1524 Ok((out, Some(next)))
1525 })?
1526 } else {
1527 let x = self.prime_pp2_stage1_enqueue(
1528 e,
1529 rt,
1530 slot,
1531 end - start,
1532 cache1,
1533 seq_end,
1534 fence,
1535 base,
1536 true,
1537 )?;
1538 let out = {
1539 rt.bind_stage(1)?;
1540 let _st1 = rt.enter(1);
1541 let e1 = rt.engine(1, e);
1542 self.prime_chunk_epilogue(e1, x, end - start, cache1)?
1543 };
1544 (out, None)
1545 };
1546
1547 rt.publish_to(1, &caller_stream)?;
1548 e.copy_into(&mut hiddens, start * n_embd, &out.2, (end - start) * n_embd)?;
1549 last = Some((out.0, out.1));
1550 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1551
1552 if let Some(next) = next_slot {
1553 rt.fence_stages_behind(&caller_stream)?;
1558 slot = next;
1559 }
1560 }
1561
1562 debug_assert_eq!(cache0.pos, initial_base + t);
1563 debug_assert_eq!(cache1.pos, initial_base + t);
1564 let (logits, h_seed) = last.unwrap();
1565 Ok((logits, h_seed, hiddens))
1566 }
1567
1568 fn gdn_hk(e: &Engine, t: usize, num_v: usize, num_k: usize) -> usize {
1575 if Engine::gdn_db_on()
1576 && Engine::gdn_chunked_enabled()
1577 && t >= 16
1578 && e.gdn_mma_enabled(Engine::gdn_chunk_size())
1579 && num_k * 2 == num_v
1580 {
1581 num_k
1582 } else {
1583 num_v
1584 }
1585 }
1586
1587 fn f16out_on(e: &Engine, t: usize) -> bool {
1592 crate::f16_ffi::pp_f16_enabled()
1593 && t >= 16
1594 && !e.verify_exact_on()
1595 && std::env::var("MEMRA_F16OUT").as_deref() != Ok("0")
1596 }
1597
1598 pub fn prime_slabs_get(
1606 &self,
1607 e: &Engine,
1608 t: usize,
1609 n_embd: usize,
1610 n_ff_max: usize,
1611 ) -> Result<std::sync::Arc<std::sync::Mutex<PrimeSlabs>>, Box<dyn std::error::Error>> {
1612 let mut slabs = self.prime_slabs.lock().unwrap();
1613 let dev = e.ctx().ordinal();
1614 let need_new = match slabs.get(&dev) {
1615 None => true,
1616 Some(sl) => sl.lock().unwrap().t_cap < t,
1617 };
1618 if need_new {
1619 slabs.insert(
1620 dev,
1621 std::sync::Arc::new(std::sync::Mutex::new(PrimeSlabs {
1622 t_cap: t,
1623 h: e.uninit(t * n_embd)?,
1624 x1: e.uninit(t * n_embd)?,
1625 z: e.uninit(t * n_embd)?,
1626 act: e.uninit(t * n_ff_max)?,
1627 xa: e.uninit(t * n_embd)?,
1628 xb: e.uninit(t * n_embd)?,
1629 h16: e.alloc_u8_uninit(t * n_embd * 2)?,
1630 z16: e.alloc_u8_uninit(t * n_embd * 2)?,
1631 gate: e.uninit(t * n_ff_max)?,
1632 up: e.uninit(t * n_ff_max)?,
1633 ffn_out: e.uninit(t * n_embd)?,
1634 seg_glue: Vec::new(),
1635 mixed: e.uninit(t * n_embd)?,
1636 seg_mid: Vec::new(),
1637 seg_t: 0,
1638 })),
1639 );
1640 }
1641 Ok(slabs.get(&dev).expect("prime slab inserted").clone())
1642 }
1643
1644 fn prime_chunk(
1648 &self,
1649 e: &Engine,
1650 tokens: &[u32],
1651 cache: &mut Cache,
1652 seq_end: usize,
1653 chunk_off: usize,
1654 overlay: Option<&crate::vision::EmbedOverlay>,
1655 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1656 if crate::pp::pp_host_bounce_active()
1657 && (self.uses_gemma_program() || !crate::pp::prime_pp_on())
1658 {
1659 return Err(
1660 "prime_chunk: refused with MEMRA_PP_HOST_BOUNCE=1 because this configuration \
1661 has no active prime stage split and would peer-read remote weights; keep \
1662 MEMRA_PRIME_PP enabled and use a PP-prime-supported model"
1663 .into(),
1664 );
1665 }
1666 if !self.uses_gemma_program() && !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
1675 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
1676 if overlay.is_some() {
1677 return Err("vision embedding overlay + PP prime unsupported (v1); \
1678 run single-device or MEMRA_PRIME_PP=0"
1679 .into());
1680 }
1681 return self.prime_chunk_ppn(e, tokens, cache, seq_end, &fence);
1682 }
1683 }
1684 if crate::pp::pp_host_bounce_active() {
1685 return Err(
1686 "prime_chunk: MEMRA_PP_HOST_BOUNCE=1 found no valid prime stage split; \
1687 refusing an unsplit remote-weight walk"
1688 .into(),
1689 );
1690 }
1691 let t = tokens.len();
1692 let base = cache.pos;
1693 debug_assert!(
1694 seq_end >= base + t,
1695 "prime_chunk: seq_end must cover this chunk"
1696 );
1697 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
1698 let pos_d = e.htod_i32(&pos)?;
1699
1700 let mut x_embed = self.embed(e, tokens)?; if let Some(ov) = overlay {
1702 let n_embd = self.cfg.n_embd as usize;
1706 for &(pos, row_off, n_rows) in &ov.spans {
1707 let lo = pos.max(chunk_off);
1708 let hi = (pos + n_rows).min(chunk_off + t);
1709 if lo < hi {
1710 let src_row = row_off + (lo - pos);
1711 let view = ov
1712 .rows
1713 .slice(src_row * n_embd..(src_row + (hi - lo)) * n_embd);
1714 e.copy_view_into(
1715 &mut x_embed,
1716 (lo - chunk_off) * n_embd,
1717 &view,
1718 (hi - lo) * n_embd,
1719 )?;
1720 }
1721 }
1722 }
1723 let x = self.prime_layers(
1724 e,
1725 x_embed,
1726 0,
1727 self.layers.len(),
1728 &pos_d,
1729 t,
1730 base,
1731 cache,
1732 seq_end,
1733 )?;
1734 self.prime_chunk_epilogue(e, x, t, cache)
1735 }
1736
1737 #[allow(clippy::too_many_arguments)]
1753 fn prime_layers(
1754 &self,
1755 e: &Engine,
1756 x_in: CudaSlice<f32>,
1757 lo: usize,
1758 hi: usize,
1759 pos_d: &CudaSlice<i32>,
1760 t: usize,
1761 base: usize,
1762 cache: &mut Cache,
1763 seq_end: usize,
1764 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1765 let cfg = &self.cfg;
1766 let n_embd = cfg.n_embd as usize;
1767 let eps = cfg.rms_eps;
1768 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
1772 let n_ff_max = self
1779 .layers
1780 .iter()
1781 .map(|l| match &l.ffn {
1782 crate::hybrid::Ffn::Dense { ffn_gate, .. } => ffn_gate.out_features(),
1783 _ => n_embd,
1784 })
1785 .max()
1786 .unwrap_or(n_embd)
1787 .max(n_embd);
1788 let use_slabs = std::env::var("MEMRA_PRIME_SLABS").as_deref() != Ok("0");
1789 let slab = if use_slabs {
1790 Some(self.prime_slabs_get(e, t, n_embd, n_ff_max)?)
1791 } else {
1792 None
1793 };
1794 let mut slab_guard = slab.as_ref().map(|sl| sl.lock().unwrap());
1795 let mut x_own; type SlabRefs<'a> = (
1797 &'a mut CudaSlice<f32>,
1798 &'a mut CudaSlice<f32>,
1799 &'a mut CudaSlice<f32>,
1800 &'a mut CudaSlice<f32>,
1801 &'a mut CudaSlice<u8>,
1802 &'a mut CudaSlice<u8>,
1803 &'a mut CudaSlice<f32>,
1804 &'a mut CudaSlice<f32>,
1805 &'a mut CudaSlice<f32>,
1806 );
1807 let (mut x_cur, mut x_nxt, sl): (
1808 &mut CudaSlice<f32>,
1809 &mut CudaSlice<f32>,
1810 Option<SlabRefs>,
1811 );
1812 let mut seg: Option<(
1813 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1814 &mut Vec<Option<cudarc::driver::CudaGraph>>,
1815 &mut CudaSlice<f32>,
1816 &mut usize,
1817 )> = None;
1818 let mut x_own2;
1819 match slab_guard.as_mut() {
1820 Some(g) => {
1821 let slabs = &mut **g;
1822 e.copy_into(&mut slabs.xa, 0, &x_in, t * n_embd)?;
1823 let PrimeSlabs {
1824 xa,
1825 xb,
1826 h,
1827 x1,
1828 z,
1829 act,
1830 h16,
1831 z16,
1832 gate,
1833 up,
1834 ffn_out,
1835 seg_glue,
1836 mixed,
1837 seg_mid,
1838 seg_t,
1839 ..
1840 } = slabs;
1841 x_cur = xa;
1842 x_nxt = xb;
1843 seg = Some((seg_glue, seg_mid, mixed, seg_t));
1844 sl = Some((h, x1, z, act, h16, z16, gate, up, ffn_out));
1845 }
1846 None => {
1847 x_own = x_in;
1848 x_own2 = e.uninit(t * n_embd)?;
1849 x_cur = &mut x_own;
1850 x_nxt = &mut x_own2;
1851 sl = None;
1852 }
1853 }
1854 let mut alloc_h;
1855 let mut alloc_x1;
1856 let mut alloc_z;
1857 let mut alloc_act;
1858 let mut alloc_h16;
1859 let mut alloc_z16;
1860 let mut alloc_gate;
1861 let mut alloc_up;
1862 let mut alloc_fo;
1863 let (h, x1, z, act): (
1864 &mut CudaSlice<f32>,
1865 &mut CudaSlice<f32>,
1866 &mut CudaSlice<f32>,
1867 &mut CudaSlice<f32>,
1868 );
1869 let (h16, z16): (&mut CudaSlice<u8>, &mut CudaSlice<u8>);
1870 let (sl_gate, sl_up, sl_fo): (
1871 &mut CudaSlice<f32>,
1872 &mut CudaSlice<f32>,
1873 &mut CudaSlice<f32>,
1874 );
1875 match sl {
1876 Some((a, b, c, d, e16, f16b, g, u, fo)) => {
1877 h = a;
1878 x1 = b;
1879 z = c;
1880 act = d;
1881 h16 = e16;
1882 z16 = f16b;
1883 sl_gate = g;
1884 sl_up = u;
1885 sl_fo = fo;
1886 }
1887 None => {
1888 alloc_h = e.uninit(t * n_embd)?;
1889 alloc_x1 = e.uninit(t * n_embd)?;
1890 alloc_z = e.uninit(t * n_embd)?;
1891 alloc_act = e.uninit(t * n_ff_max)?;
1892 alloc_h16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1893 alloc_z16 = e.alloc_u8_uninit(t * n_embd * 2)?;
1894 alloc_gate = e.uninit(t * n_ff_max)?;
1895 alloc_up = e.uninit(t * n_ff_max)?;
1896 alloc_fo = e.uninit(t * n_embd)?;
1897 h = &mut alloc_h;
1898 x1 = &mut alloc_x1;
1899 z = &mut alloc_z;
1900 act = &mut alloc_act;
1901 h16 = &mut alloc_h16;
1902 z16 = &mut alloc_z16;
1903 sl_gate = &mut alloc_gate;
1904 sl_up = &mut alloc_up;
1905 sl_fo = &mut alloc_fo;
1906 }
1907 }
1908 let n_layers = self.layers.len();
1913 let use_seg = f16fuse
1923 && seg.is_some()
1924 && !self.uses_sliding_gated_moe_program()
1925 && lo == 0
1926 && hi == n_layers
1927 && std::env::var("MEMRA_PRIME_SEG").as_deref() == Ok("1");
1928 if let Some((sg, sm, _, st)) = seg.as_mut() {
1929 if **st != t {
1930 sg.clear();
1931 sg.extend((0..n_layers).map(|_| None));
1932 sm.clear();
1933 sm.extend((0..n_layers).map(|_| None));
1934 **st = t;
1935 }
1936 }
1937 {
1938 let layer_lo = &self.layers[lo];
1939 if f16fuse {
1940 e.rms_norm_f16out(
1941 x_cur,
1942 layer_lo.attn_norm.float_data(),
1943 h,
1944 h16,
1945 n_embd,
1946 t,
1947 eps,
1948 )?;
1949 } else {
1950 e.rms_norm(x_cur, layer_lo.attn_norm.float_data(), h, n_embd, t, eps)?;
1951 }
1952 }
1953 let anat = Self::prime_anatomy_on();
1954 let mut anat_last = if anat {
1955 e.stream().synchronize()?;
1956 Some(std::time::Instant::now())
1957 } else {
1958 None
1959 };
1960 macro_rules! anat_mark {
1962 ($slot:expr) => {
1963 if let Some(ts) = anat_last.as_mut() {
1964 e.stream().synchronize()?;
1965 Self::prime_anatomy_slots()[$slot].fetch_add(
1966 ts.elapsed().as_nanos() as u64,
1967 std::sync::atomic::Ordering::Relaxed,
1968 );
1969 *ts = std::time::Instant::now();
1970 }
1971 };
1972 }
1973 for il in lo..hi {
1974 let layer = &self.layers[il];
1975 let hx16 = if f16fuse { Some(&*h16) } else { None };
1976 if use_seg {
1977 let (pre, pre16, w_out) = match &layer.mixer {
1980 Mixer::Full(fa) => {
1981 let g3 = match hx16 {
1982 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
1983 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
1984 };
1985 let (pre, pre16) =
1986 self.full_attn_prime_core_inner(e, fa, g3, &pos_d, t, cache, il)?;
1987 (pre, pre16, &fa.wo)
1988 }
1989 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
1990 Mixer::Linear(la) => {
1991 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
1992 let g4 = match hx16 {
1993 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
1994 None => e.matmul_group(&ws, h, t)?,
1995 };
1996 let (pre, pre16) =
1997 self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, None)?;
1998 (pre, pre16, &la.ssm_out)
1999 }
2000 };
2001 {
2002 let (_, sm, mslab, _) = seg.as_mut().unwrap();
2003 let pre_n = pre.len() / t;
2004 let xh_pre = match pre16 {
2005 Some(x) => x,
2006 None => e.f16_act(&pre, t * pre_n, pre_n)?,
2007 };
2008 if !e.try_f16_gemm_pre_into(w_out, &xh_pre, t, mslab)? {
2009 let y = e.matmul(w_out, &pre, t)?;
2010 e.copy_into(mslab, 0, &y, t * n_embd)?;
2011 }
2012 if sm[il].is_none() {
2013 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
2014 let w_post = layer.post_attn_norm.float_data();
2015 e.stream().synchronize()?;
2016 e.stream()
2017 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2018 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2019 e.add(x_cur, mslab, x1, t * n_embd)?;
2020 e.rms_norm_f16out(x1, w_post, z, z16, n_embd, t, eps)?;
2021 Ok(())
2022 })();
2023 let g = e.stream().end_capture(
2024 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH);
2025 r?;
2026 sm[il] = Some(g?.ok_or("S-mid capture produced no graph")?);
2027 }
2028 sm[il].as_ref().unwrap().launch()?;
2029 }
2030 } else {
2031 let mixed = match &layer.mixer {
2032 Mixer::Full(fa) => {
2033 let y =
2034 self.full_attn_prime(e, fa, h, hx16, &pos_d, t, cache, il, seq_end)?;
2035 anat_mark!(0);
2036 y
2037 }
2038 Mixer::Linear(la) => {
2039 let y = self.linear_attn_prime(e, la, h, hx16, t, cache, il)?;
2040 anat_mark!(1);
2041 y
2042 }
2043 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2044 };
2045 if f16fuse {
2046 e.add_rms_norm_f16out(
2049 x_cur,
2050 &mixed,
2051 layer.post_attn_norm.float_data(),
2052 x1,
2053 z,
2054 z16,
2055 n_embd,
2056 t,
2057 eps,
2058 )?;
2059 } else {
2060 e.add(x_cur, &mixed, x1, t * n_embd)?;
2061 e.rms_norm(x1, layer.post_attn_norm.float_data(), z, n_embd, t, eps)?;
2062 }
2063 anat_mark!(4);
2064 }
2065 let zx16 = if f16fuse { Some(&*z16) } else { None };
2066 match &layer.ffn {
2067 crate::hybrid::Ffn::Dense {
2068 ffn_gate,
2069 ffn_up,
2070 ffn_down,
2071 } => {
2072 let n_ff = ffn_gate.out_features();
2073 let mut into_ok = false;
2076 if let Some(xh) = zx16 {
2077 into_ok = e.try_f16_gemm_pre_into(ffn_gate, xh, t, sl_gate)?
2078 && e.try_f16_gemm_pre_into(ffn_up, xh, t, sl_up)?;
2079 }
2080 if !into_ok {
2081 let mut g2 = match zx16 {
2082 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], z, xh, t)?,
2083 None => e.matmul_group(&[ffn_gate, ffn_up], z, t)?,
2084 };
2085 let up_y = g2.pop().unwrap();
2086 let gate_y = g2.pop().unwrap();
2087 e.copy_into(sl_gate, 0, &gate_y, t * n_ff)?;
2088 e.copy_into(sl_up, 0, &up_y, t * n_ff)?;
2089 }
2090 let d_lim = self.cfg.clamp_shexp_at(il as u32);
2095 let act16 = if Self::f16out_on(e, t) && self.cfg.m3.is_none() && d_lim.is_none()
2096 {
2097 let mut a16 = e.alloc_u8_uninit(t * n_ff * 2)?;
2098 e.silu_mul_f16out(sl_gate, sl_up, act, &mut a16, t * n_ff)?;
2099 Some(a16)
2100 } else {
2101 Self::ffn_act_lim(
2102 e,
2103 &self.cfg,
2104 sl_gate,
2105 sl_up,
2106 1.0,
2107 1.0,
2108 d_lim,
2109 act,
2110 t * n_ff,
2111 )?;
2112 None
2113 };
2114 let xh_act = match act16 {
2116 Some(x) => x,
2117 None => e.f16_act(act, t * n_ff, n_ff)?,
2118 };
2119 if !e.try_f16_gemm_pre_into(ffn_down, &xh_act, t, sl_fo)? {
2120 let y = e.matmul(ffn_down, &*act, t)?;
2121 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2122 }
2123 }
2124 crate::hybrid::Ffn::Moe(m) => {
2125 let y = self.moe_ffn_il_prefill(e, m, z, t, il as u16)?;
2126 e.copy_into(sl_fo, 0, &y, t * n_embd)?;
2127 anat_mark!(2);
2128 }
2129 }
2130 if let (crate::hybrid::Ffn::Dense { .. }, true) = (&layer.ffn, anat) {
2131 anat_mark!(3);
2132 }
2133 if use_seg && il + 1 < hi {
2134 let w_next = self.layers[il + 1].attn_norm.float_data();
2136 let (sg, _, _, _) = seg.as_mut().unwrap();
2137 if sg[il].is_none() {
2138 use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
2139 e.stream().synchronize()?;
2140 e.stream()
2141 .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
2142 let r = (|| -> Result<(), Box<dyn std::error::Error>> {
2143 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2144 e.rms_norm_f16out(x_nxt, w_next, h, h16, n_embd, t, eps)?;
2145 Ok(())
2146 })();
2147 let g = e.stream().end_capture(
2148 CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
2149 );
2150 r?;
2151 sg[il] = Some(g?.ok_or("S-glue capture produced no graph")?);
2152 }
2153 sg[il].as_ref().unwrap().launch()?;
2154 } else {
2155 if il + 1 < hi {
2156 let w_next = self.layers[il + 1].attn_norm.float_data();
2157 if f16fuse {
2158 e.add_rms_norm_f16out(x1, sl_fo, w_next, x_nxt, h, h16, n_embd, t, eps)?;
2159 } else {
2160 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2161 e.rms_norm(x_nxt, w_next, h, n_embd, t, eps)?;
2162 }
2163 } else {
2164 e.add(x1, sl_fo, x_nxt, t * n_embd)?;
2165 }
2166 }
2167 anat_mark!(4);
2168 if let Some(path) = Self::prime_trace_path() {
2174 let row = (base + t - 1) as usize;
2175 let host = e.dtoh(x_nxt)?;
2176 let last = &host[(t - 1) * n_embd..t * n_embd];
2177 use std::io::Write as _;
2178 let mut f = std::fs::OpenOptions::new()
2179 .create(true)
2180 .append(true)
2181 .open(path)?;
2182 let mut h64: u64 = 0xcbf29ce484222325;
2183 for v in last {
2184 h64 ^= v.to_bits() as u64;
2185 h64 = h64.wrapping_mul(0x100000001b3);
2186 }
2187 writeln!(
2188 f,
2189 "{{\"pos\":{row},\"layer\":{il},\"t\":{t},\"base\":{base},\
2190 \"hash\":\"{h64:016x}\",\"v0\":{:.9e},\"v1\":{:.9e},\"v2\":{:.9e}}}",
2191 last[0], last[1], last[2]
2192 )?;
2193 }
2194 self.dflash_tap(e, cache, il, x_nxt, t)?;
2197 std::mem::swap(&mut x_cur, &mut x_nxt);
2198 }
2199 if anat {
2200 let s = Self::prime_anatomy_slots();
2201 let ms = |i: usize| s[i].load(std::sync::atomic::Ordering::Relaxed) as f64 / 1.0e6;
2202 eprintln!(
2203 "[prime-anatomy] cumulative ms: attn_full={:.1} gdn_linear={:.1} moe={:.1} \
2204 dense={:.1} norms_adds={:.1} (t={t}, layers {lo}..{hi})",
2205 ms(0),
2206 ms(1),
2207 ms(2),
2208 ms(3),
2209 ms(4)
2210 );
2211 }
2212 let mut x = e.uninit(t * n_embd)?;
2214 e.copy_into(&mut x, 0, x_cur, t * n_embd)?;
2215 drop(slab_guard);
2216 Ok(x)
2217 }
2218
2219 fn prime_chunk_epilogue(
2224 &self,
2225 e: &Engine,
2226 x: CudaSlice<f32>,
2227 t: usize,
2228 cache: &mut Cache,
2229 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2230 let n_embd = self.cfg.n_embd as usize;
2231 let eps = self.cfg.rms_eps;
2232 let mut h_seed = e.uninit(n_embd)?;
2236 if !crate::spec::spec_hpost() {
2237 e.copy_view_into(
2238 &mut h_seed,
2239 0,
2240 &x.slice((t - 1) * n_embd..t * n_embd),
2241 n_embd,
2242 )?;
2243 }
2244 let mut hn = e.uninit(t * n_embd)?;
2246 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2247 if crate::spec::spec_hpost() {
2248 e.copy_view_into(
2249 &mut h_seed,
2250 0,
2251 &hn.slice((t - 1) * n_embd..t * n_embd),
2252 n_embd,
2253 )?;
2254 }
2255 let last = e.view(&hn, t * n_embd);
2256 let last_row = last.slice((t - 1) * n_embd..t * n_embd);
2257 let mut hlast = e.uninit(n_embd)?;
2258 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
2259 let logits = e.matmul(&self.output, &hlast, 1)?;
2260 cache.pos += t;
2261 Ok((
2264 e.dtoh(&logits)?,
2265 h_seed,
2266 if crate::spec::spec_hpost() { hn } else { x },
2267 ))
2268 }
2269
2270 fn prime_chunk_ppn(
2294 &self,
2295 e: &Engine,
2296 tokens: &[u32],
2297 cache: &mut Cache,
2298 seq_end: usize,
2299 fence: &[usize],
2300 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2301 let rt = crate::pp::PpNRt::get(e)?;
2302 let n_st = fence.len() - 1;
2303 assert_eq!(
2304 rt.n_stages(),
2305 n_st,
2306 "PpNRt stage count {} != fence stages {n_st}",
2307 rt.n_stages()
2308 );
2309 let n_embd = self.cfg.n_embd as usize;
2310 let t = tokens.len();
2311 let base = cache.pos;
2312 debug_assert!(
2313 seq_end >= base + t,
2314 "prime_chunk_ppn: seq_end must cover this chunk"
2315 );
2316 let payload = t * n_embd;
2317 let caller_stream = e.stream();
2321 rt.fence_stages_behind(&caller_stream)?;
2322
2323 if n_st == 2 {
2324 let slot =
2325 self.prime_pp2_stage0_enqueue(e, rt, tokens, cache, seq_end, fence, base, false)?;
2326 let x =
2327 self.prime_pp2_stage1_enqueue(e, rt, slot, t, cache, seq_end, fence, base, false)?;
2328 let out = {
2329 rt.bind_stage(1)?;
2330 let _st1 = rt.enter(1);
2331 let e1 = rt.engine(1, e);
2332 self.prime_chunk_epilogue(e1, x, t, cache)?
2333 };
2334 rt.publish_to(1, &caller_stream)?;
2335 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2336 return Ok(out);
2337 }
2338
2339 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2340
2341 let mut slot = {
2343 let _st0 = rt.enter(0);
2344 let e0 = rt.engine(0, e);
2345 let pos_d = e0.htod_i32(&pos)?;
2346 let x = self.embed(e0, tokens)?;
2347 let x =
2348 self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2349 rt.tx(0, &x, payload)?
2350 };
2352
2353 for s in 1..n_st - 1 {
2355 let _st = rt.enter(s);
2356 let es = rt.engine(s, e);
2357 let pos_d = es.htod_i32(&pos)?;
2358 let x = rt.rx(s - 1, slot, payload)?;
2359 let x = self.prime_layers(
2360 es,
2361 x,
2362 fence[s],
2363 fence[s + 1],
2364 &pos_d,
2365 t,
2366 base,
2367 cache,
2368 seq_end,
2369 )?;
2370 slot = rt.tx(s, &x, payload)?;
2371 }
2372
2373 let _stl = rt.enter(n_st - 1);
2375 let el = rt.engine(n_st - 1, e);
2376 let pos_d = el.htod_i32(&pos)?;
2377 let x = rt.rx(n_st - 2, slot, payload)?;
2378 let x = self.prime_layers(
2379 el,
2380 x,
2381 fence[n_st - 1],
2382 fence[n_st],
2383 &pos_d,
2384 t,
2385 base,
2386 cache,
2387 seq_end,
2388 )?;
2389 let out = self.prime_chunk_epilogue(el, x, t, cache)?;
2390 rt.publish_to(n_st - 1, &caller_stream)?;
2396 crate::pp::PRIME_SPLIT_CHUNKS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2397 Ok(out)
2398 }
2399
2400 fn prime_pp2_stage0_enqueue(
2401 &self,
2402 e: &Engine,
2403 rt: &crate::pp::PpNRt,
2404 tokens: &[u32],
2405 cache: &mut Cache,
2406 seq_end: usize,
2407 fence: &[usize],
2408 base: usize,
2409 pipelined: bool,
2410 ) -> Result<usize, Box<dyn std::error::Error>> {
2411 let t = tokens.len();
2412 let n_embd = self.cfg.n_embd as usize;
2413 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2414 rt.bind_stage(0)?;
2415 let _st0 = rt.enter(0);
2416 let e0 = rt.engine(0, e);
2417 let pos_d = e0.htod_i32(&pos)?;
2418 let x = self.embed(e0, tokens)?;
2419 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2420 let x = self.prime_layers(e0, x, fence[0], fence[1], &pos_d, t, base, cache, seq_end)?;
2421 if pipelined {
2422 rt.tx_pipelined(0, &x, t * n_embd)
2423 } else {
2424 rt.tx(0, &x, t * n_embd)
2425 }
2426 }
2427
2428 fn prime_pp2_stage1_enqueue(
2429 &self,
2430 e: &Engine,
2431 rt: &crate::pp::PpNRt,
2432 slot: usize,
2433 t: usize,
2434 cache: &mut Cache,
2435 seq_end: usize,
2436 fence: &[usize],
2437 base: usize,
2438 pipelined: bool,
2439 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2440 let n_embd = self.cfg.n_embd as usize;
2441 let pos: Vec<i32> = (base as i32..(base + t) as i32).collect();
2442 rt.bind_stage(1)?;
2443 let _st1 = rt.enter(1);
2444 let e1 = rt.engine(1, e);
2445 let pos_d = e1.htod_i32(&pos)?;
2446 let x = rt.rx(0, slot, t * n_embd)?;
2447 let _overlap = pipelined.then(crate::pp::enter_prime_pipe_stage);
2448 self.prime_layers(e1, x, fence[1], fence[2], &pos_d, t, base, cache, seq_end)
2449 }
2450
2451 pub fn prime_chunk_captured(
2467 &self,
2468 e: &Engine,
2469 x_in: &CudaSlice<f32>,
2470 pos_d: &CudaSlice<i32>,
2471 t: usize,
2472 cache: &mut Cache,
2473 len_d: &CudaSlice<i32>,
2474 logits_out: &mut CudaSlice<f32>,
2475 h_seed_out: &mut CudaSlice<f32>,
2476 ) -> Result<(), Box<dyn std::error::Error>> {
2477 let cfg = &self.cfg;
2478 let n_embd = cfg.n_embd as usize;
2479 let eps = cfg.rms_eps;
2480 let f16fuse = crate::f16_ffi::pp_f16_enabled() && t >= 16;
2481 let mut x = e.uninit(t * n_embd)?;
2482 e.copy_into(&mut x, 0, x_in, t * n_embd)?;
2483 for (il, layer) in self.layers.iter().enumerate() {
2484 let mut h = e.uninit(t * n_embd)?;
2485 let mut hx16: Option<CudaSlice<u8>> = None;
2486 if f16fuse {
2487 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2488 e.rms_norm_f16out(
2489 &x,
2490 layer.attn_norm.float_data(),
2491 &mut h,
2492 &mut b16,
2493 n_embd,
2494 t,
2495 eps,
2496 )?;
2497 hx16 = Some(b16);
2498 } else {
2499 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
2500 }
2501 let mixed = match &layer.mixer {
2502 Mixer::Full(fa) => {
2506 self.full_attn_prime(e, fa, &h, hx16.as_ref(), pos_d, t, cache, il, t)?
2507 }
2508 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
2509 Mixer::Linear(la) => {
2510 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
2511 let g4 = match hx16.as_ref() {
2512 Some(xh) => e.matmul_group_xh(&ws, &h, xh, t)?,
2513 None => e.matmul_group(&ws, &h, t)?,
2514 };
2515 self.linear_attn_prime_core_pad(e, la, g4, t, cache, il, Some(len_d))?
2516 }
2517 };
2518 let mut x1 = e.uninit(t * n_embd)?;
2519 e.add(&x, &mixed, &mut x1, t * n_embd)?;
2520 let mut z = e.uninit(t * n_embd)?;
2521 let mut zx16: Option<CudaSlice<u8>> = None;
2522 if f16fuse {
2523 let mut b16 = e.alloc_u8_uninit(t * n_embd * 2)?;
2524 e.rms_norm_f16out(
2525 &x1,
2526 layer.post_attn_norm.float_data(),
2527 &mut z,
2528 &mut b16,
2529 n_embd,
2530 t,
2531 eps,
2532 )?;
2533 zx16 = Some(b16);
2534 } else {
2535 e.rms_norm(
2536 &x1,
2537 layer.post_attn_norm.float_data(),
2538 &mut z,
2539 n_embd,
2540 t,
2541 eps,
2542 )?;
2543 }
2544 let ffn_out = match &layer.ffn {
2545 crate::hybrid::Ffn::Dense {
2546 ffn_gate,
2547 ffn_up,
2548 ffn_down,
2549 } => {
2550 let n_ff = ffn_gate.out_features();
2551 let mut g2 = match &zx16 {
2552 Some(xh) => e.matmul_group_xh(&[ffn_gate, ffn_up], &z, xh, t)?,
2553 None => e.matmul_group(&[ffn_gate, ffn_up], &z, t)?,
2554 };
2555 let up = g2.pop().unwrap();
2556 let gate = g2.pop().unwrap();
2557 let mut act = e.uninit(t * n_ff)?;
2558 Self::ffn_act_lim(
2560 e,
2561 &self.cfg,
2562 &gate,
2563 &up,
2564 1.0,
2565 1.0,
2566 self.cfg.clamp_shexp_at(il as u32),
2567 &mut act,
2568 t * n_ff,
2569 )?;
2570 e.matmul(ffn_down, &act, t)?
2571 }
2572 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il_prefill(e, m, &z, t, il as u16)?,
2573 };
2574 let mut x2 = e.uninit(t * n_embd)?;
2575 e.add(&x1, &ffn_out, &mut x2, t * n_embd)?;
2576 x = x2;
2577 }
2578 if !crate::spec::spec_hpost() {
2580 e.row_gather_dev(&x, h_seed_out, len_d, n_embd)?;
2581 }
2582 let mut hn = e.uninit(t * n_embd)?;
2583 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
2584 if crate::spec::spec_hpost() {
2585 e.row_gather_dev(&hn, h_seed_out, len_d, n_embd)?;
2586 }
2587 let mut hlast = e.uninit(n_embd)?;
2588 e.row_gather_dev(&hn, &mut hlast, len_d, n_embd)?;
2589 let logits = e.matmul(&self.output, &hlast, 1)?;
2590 let nv = logits.len();
2591 e.copy_into(logits_out, 0, &logits, nv)?;
2592 Ok(())
2593 }
2594
2595 fn step35_prime_batch_on() -> bool {
2596 std::env::var("MEMRA_STEP35_PRIME_BATCH").as_deref() != Ok("0")
2597 }
2598
2599 #[allow(clippy::too_many_arguments)]
2602 fn step35_prime_batch_layers(
2603 &self,
2604 e: &Engine,
2605 mut x: CudaSlice<f32>,
2606 lo: usize,
2607 hi: usize,
2608 ts: &[usize],
2609 offs: &[usize],
2610 pos_ds: &[CudaSlice<i32>],
2611 caches: &mut [&mut Cache],
2612 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2613 let cfg = &self.cfg;
2614 let n_embd = cfg.n_embd as usize;
2615 let eps = cfg.rms_eps;
2616 let b = ts.len();
2617 let total: usize = ts.iter().sum();
2618 let f16fuse = crate::f16_ffi::pp_f16_enabled() && total >= 16;
2619
2620 let split = |e: &Engine,
2621 y: &CudaSlice<f32>,
2622 dim: usize|
2623 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2624 let mut out = Vec::with_capacity(b);
2625 for s in 0..b {
2626 let mut ys = e.uninit(ts[s] * dim)?;
2627 e.copy_view_into(
2628 &mut ys,
2629 0,
2630 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
2631 ts[s] * dim,
2632 )?;
2633 out.push(ys);
2634 }
2635 Ok(out)
2636 };
2637
2638 for il in lo..hi {
2639 let layer = &self.layers[il];
2640 let Mixer::Full(fa) = &layer.mixer else {
2641 return Err(format!("step35 layer {il} is not full-attn — corrupt config").into());
2642 };
2643
2644 let mut h = e.uninit(total * n_embd)?;
2645 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2646 if f16fuse {
2647 e.rms_norm_f16out(
2648 &x,
2649 layer.attn_norm.float_data(),
2650 &mut h,
2651 &mut hx16,
2652 n_embd,
2653 total,
2654 eps,
2655 )?;
2656 } else {
2657 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, total, eps)?;
2658 }
2659
2660 let gate_w = fa
2664 .attn_gate
2665 .as_ref()
2666 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
2667 let mut g4 = if f16fuse {
2668 e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, &hx16, total)?
2669 } else {
2670 e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv, gate_w], &h, total)?
2671 };
2672 let gate = g4.pop().unwrap();
2673 let mut parts: Vec<Vec<CudaSlice<f32>>> =
2674 (0..b).map(|_| Vec::with_capacity(3)).collect();
2675 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g4) {
2676 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
2677 parts[s].push(ys);
2678 }
2679 }
2680 let gates = split(e, &gate, gate_w.out_features())?;
2681 let geometry = self.step35_geom(il);
2682 let hd = geometry.head_dim_k as usize;
2683 let nh = geometry.n_head as usize;
2684 let mut ag_cat = e.uninit(total * nh * hd)?;
2685 for (s, (g3s, gate)) in parts.into_iter().zip(gates).enumerate() {
2686 let ag = self.step35_attn_pre_wo(
2687 e,
2688 fa,
2689 g3s,
2690 None,
2691 Some(&gate),
2692 &pos_ds[s],
2693 ts[s],
2694 Some(&mut *caches[s]),
2695 il,
2696 ts[s],
2697 )?;
2698 e.copy_into(&mut ag_cat, offs[s] * nh * hd, &ag, ts[s] * nh * hd)?;
2699 }
2700 let mixed = e.matmul(&fa.wo, &ag_cat, total)?;
2701
2702 let mut x1 = e.uninit(total * n_embd)?;
2703 let mut z = e.uninit(total * n_embd)?;
2704 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
2705 if f16fuse {
2706 e.add_rms_norm_f16out(
2707 &x,
2708 &mixed,
2709 layer.post_attn_norm.float_data(),
2710 &mut x1,
2711 &mut z,
2712 &mut zx16,
2713 n_embd,
2714 total,
2715 eps,
2716 )?;
2717 } else {
2718 e.add(&x, &mixed, &mut x1, total * n_embd)?;
2719 e.rms_norm(
2720 &x1,
2721 layer.post_attn_norm.float_data(),
2722 &mut z,
2723 n_embd,
2724 total,
2725 eps,
2726 )?;
2727 }
2728
2729 let ffn_out = match &layer.ffn {
2730 crate::hybrid::Ffn::Dense {
2731 ffn_gate,
2732 ffn_up,
2733 ffn_down,
2734 } => {
2735 let n_ff = ffn_gate.out_features();
2736 let mut g2 = if f16fuse {
2737 e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?
2738 } else {
2739 e.matmul_group(&[ffn_gate, ffn_up], &z, total)?
2740 };
2741 let up = g2.pop().unwrap();
2742 let gate = g2.pop().unwrap();
2743 let mut act = e.uninit(total * n_ff)?;
2744 let d_lim = cfg.clamp_shexp_at(il as u32);
2745 if Self::f16out_on(e, total) && cfg.m3.is_none() && d_lim.is_none() {
2746 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
2747 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
2748 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
2749 Some(y) => y,
2750 None => e.matmul(ffn_down, &act, total)?,
2751 }
2752 } else {
2753 Self::ffn_act_lim(
2754 e,
2755 cfg,
2756 &gate,
2757 &up,
2758 1.0,
2759 1.0,
2760 d_lim,
2761 &mut act,
2762 total * n_ff,
2763 )?;
2764 e.matmul(ffn_down, &act, total)?
2765 }
2766 }
2767 crate::hybrid::Ffn::Moe(m) => self.moe_ffn_il(e, m, &z, total, il as u16)?,
2768 };
2769 let mut x2 = e.uninit(total * n_embd)?;
2770 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
2771 x = x2;
2772 }
2773 Ok(x)
2774 }
2775
2776 fn step35_prime_batch_epilogue(
2777 &self,
2778 e: &Engine,
2779 x: CudaSlice<f32>,
2780 ts: &[usize],
2781 offs: &[usize],
2782 caches: &mut [&mut Cache],
2783 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2784 let n_embd = self.cfg.n_embd as usize;
2785 let total: usize = ts.iter().sum();
2786 let mut hn = e.uninit(total * n_embd)?;
2787 e.rms_norm(
2788 &x,
2789 self.output_norm.float_data(),
2790 &mut hn,
2791 n_embd,
2792 total,
2793 self.cfg.rms_eps,
2794 )?;
2795
2796 let hidden_src = if crate::spec::spec_hpost() { &hn } else { &x };
2797 let mut out = Vec::with_capacity(ts.len());
2798 for s in 0..ts.len() {
2799 let mut hidden = e.uninit(ts[s] * n_embd)?;
2800 e.copy_view_into(
2801 &mut hidden,
2802 0,
2803 &hidden_src.slice(offs[s] * n_embd..(offs[s] + ts[s]) * n_embd),
2804 ts[s] * n_embd,
2805 )?;
2806 let last0 = (offs[s] + ts[s] - 1) * n_embd;
2807 let mut h_seed = e.uninit(n_embd)?;
2808 e.copy_view_into(
2809 &mut h_seed,
2810 0,
2811 &hidden_src.slice(last0..last0 + n_embd),
2812 n_embd,
2813 )?;
2814 let mut hlast = e.uninit(n_embd)?;
2816 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
2817 let logits = e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?;
2818 caches[s].pos += ts[s];
2819 out.push((logits, h_seed, hidden));
2820 }
2821 Ok(out)
2822 }
2823
2824 fn step35_prime_cache_batch(
2825 &self,
2826 e: &Engine,
2827 prompts: &[&[u32]],
2828 caches: &mut [&mut Cache],
2829 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
2830 validate_step_prime_batch_modes(
2831 step_tp_prefill_enabled()?,
2832 step_ep_grouped_prefill_enabled()?,
2833 )?;
2834 if crate::pp::pp_host_bounce_active()
2835 && (!crate::pp::prime_pp_on() || crate::pp::pp_cuts(self.layers.len()).is_none())
2836 {
2837 return Err(
2838 "step35_prime_cache_batch: MEMRA_PP_HOST_BOUNCE=1 requires a valid prime \
2839 stage split; refusing an unsplit remote-weight walk"
2840 .into(),
2841 );
2842 }
2843 if !Self::step35_prime_batch_on() {
2844 return Err("step35 batched prime is disabled (MEMRA_STEP35_PRIME_BATCH=0)".into());
2845 }
2846 if caches.iter().any(|c| c.pos != 0) {
2847 return Err(
2848 "step35 batched prime currently supports complete fresh prompts only; \
2849 continuation/tick chunks require per-request queued_after"
2850 .into(),
2851 );
2852 }
2853
2854 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
2855 for &t in &ts {
2856 assert!(
2857 t >= PRIME_MIN_T,
2858 "step35 batched prime needs T >= {PRIME_MIN_T}"
2859 );
2860 }
2861 for (s, c) in caches.iter().enumerate() {
2862 assert!(
2863 ts[s] <= c.max_ctx,
2864 "step35 batched prime exceeds cache max_ctx"
2865 );
2866 }
2867 let offs: Vec<usize> = ts
2868 .iter()
2869 .scan(0usize, |a, &t| {
2870 let o = *a;
2871 *a += t;
2872 Some(o)
2873 })
2874 .collect();
2875 let total: usize = ts.iter().sum();
2876 let payload = total * self.cfg.n_embd as usize;
2877 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
2878 let positions: Vec<Vec<i32>> = ts.iter().map(|&t| (0..t as i32).collect()).collect();
2879 let upload_positions =
2880 |e: &Engine| -> Result<Vec<CudaSlice<i32>>, Box<dyn std::error::Error>> {
2881 positions
2882 .iter()
2883 .map(|p| e.htod_i32(p))
2884 .collect::<Result<_, _>>()
2885 };
2886
2887 static ONCE: std::sync::Once = std::sync::Once::new();
2888 ONCE.call_once(|| {
2889 eprintln!(
2890 "[step35-prime-batch] first concat prime: B={} tokens={total}",
2891 prompts.len()
2892 );
2893 });
2894
2895 let out = if !crate::pp::pp2_streams_off() && crate::pp::prime_pp_on() {
2896 if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
2897 let rt = crate::pp::PpNRt::get(e)?;
2898 let n_st = fence.len() - 1;
2899 assert_eq!(
2900 rt.n_stages(),
2901 n_st,
2902 "step35 prime batch stage count mismatch"
2903 );
2904 let caller_stream = e.stream();
2905 rt.fence_stages_behind(&caller_stream)?;
2906
2907 let mut slot = {
2908 let _st0 = rt.enter(0);
2909 let e0 = rt.engine(0, e);
2910 let pos_ds = upload_positions(e0)?;
2911 let x = self.embed(e0, &cat_tokens)?;
2912 let x = self.step35_prime_batch_layers(
2913 e0, x, fence[0], fence[1], &ts, &offs, &pos_ds, caches,
2914 )?;
2915 rt.tx(0, &x, payload)?
2916 };
2917 for s in 1..n_st - 1 {
2918 let _st = rt.enter(s);
2919 let es = rt.engine(s, e);
2920 let pos_ds = upload_positions(es)?;
2921 let x = rt.rx(s - 1, slot, payload)?;
2922 let x = self.step35_prime_batch_layers(
2923 es,
2924 x,
2925 fence[s],
2926 fence[s + 1],
2927 &ts,
2928 &offs,
2929 &pos_ds,
2930 caches,
2931 )?;
2932 slot = rt.tx(s, &x, payload)?;
2933 }
2934
2935 let _stl = rt.enter(n_st - 1);
2936 let el = rt.engine(n_st - 1, e);
2937 let pos_ds = upload_positions(el)?;
2938 let x = rt.rx(n_st - 2, slot, payload)?;
2939 let x = self.step35_prime_batch_layers(
2940 el,
2941 x,
2942 fence[n_st - 1],
2943 fence[n_st],
2944 &ts,
2945 &offs,
2946 &pos_ds,
2947 caches,
2948 )?;
2949 let out = self.step35_prime_batch_epilogue(el, x, &ts, &offs, caches)?;
2950 rt.publish_to(n_st - 1, &caller_stream)?;
2951 crate::pp::STEP35_PRIME_BATCH_SPLITS
2952 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2953 out
2954 } else {
2955 let pos_ds = upload_positions(e)?;
2956 let x = self.embed(e, &cat_tokens)?;
2957 let x = self.step35_prime_batch_layers(
2958 e,
2959 x,
2960 0,
2961 self.layers.len(),
2962 &ts,
2963 &offs,
2964 &pos_ds,
2965 caches,
2966 )?;
2967 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2968 }
2969 } else {
2970 let pos_ds = upload_positions(e)?;
2971 let x = self.embed(e, &cat_tokens)?;
2972 let x = self.step35_prime_batch_layers(
2973 e,
2974 x,
2975 0,
2976 self.layers.len(),
2977 &ts,
2978 &offs,
2979 &pos_ds,
2980 caches,
2981 )?;
2982 self.step35_prime_batch_epilogue(e, x, &ts, &offs, caches)?
2983 };
2984 crate::pp::STEP35_PRIME_BATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2985 Ok(out)
2986 }
2987
2988 pub fn prime_cache_batch(
3005 &self,
3006 e: &Engine,
3007 prompts: &[&[u32]],
3008 caches: &mut [&mut Cache],
3009 ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
3010 if crate::pp::pp_cuts(self.layers.len()).is_some()
3011 && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
3012 {
3013 return Err("pipeline rewrite is not qualified for batched prime".into());
3014 }
3015 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::CarriedPrime) {
3016 if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::DecodeEager) {
3017 return Err("neither batched-prime nor eager rewrite is qualified".into());
3018 }
3019 if prompts.len() != caches.len() {
3020 return Err("prime fallback prompt/cache shape mismatch".into());
3021 }
3022 static ONCE: std::sync::Once = std::sync::Once::new();
3023 ONCE.call_once(|| {
3024 eprintln!(
3025 "[rewrite] carried-prime.v1 unqualified; using individual native eager primes"
3026 );
3027 });
3028 return prompts
3029 .iter()
3030 .copied()
3031 .zip(caches.iter_mut())
3032 .map(|(prompt, cache)| self.prime_cache(e, prompt, cache, 0))
3033 .collect();
3034 }
3035 let cfg = &self.cfg;
3036 let n_embd = cfg.n_embd as usize;
3037 let eps = cfg.rms_eps;
3038 let b = prompts.len();
3039 assert!(b >= 1 && b == caches.len());
3040 let pos0s: Vec<usize> = caches.iter().map(|c| c.pos).collect();
3041 let carried = pos0s.iter().any(|&p| p > 0);
3042 if self.uses_gemma_program() {
3048 return Err(
3049 "prime_cache_batch: gemma4 has no batched prime core (per-layer \
3050 swa/global geometry, softcapped head) — use gemma4_prime per sequence"
3051 .into(),
3052 );
3053 }
3054 if self.uses_sliding_gated_moe_program() {
3057 return self.step35_prime_cache_batch(e, prompts, caches);
3058 }
3059 let ts: Vec<usize> = prompts.iter().map(|p| p.len()).collect();
3060 for &t in &ts {
3061 assert!(
3062 t >= PRIME_MIN_T,
3063 "prime_cache_batch needs T >= {PRIME_MIN_T}"
3064 );
3065 }
3066 for (s, c) in caches.iter().enumerate() {
3067 assert!(
3068 c.pos + ts[s] <= c.max_ctx,
3069 "prime_cache_batch: prompt exceeds cache max_ctx"
3070 );
3071 }
3072 let total: usize = ts.iter().sum();
3073 let offs: Vec<usize> = ts
3074 .iter()
3075 .scan(0usize, |a, &t| {
3076 let o = *a;
3077 *a += t;
3078 Some(o)
3079 })
3080 .collect();
3081 let pos_ds: Vec<CudaSlice<i32>> = ts
3083 .iter()
3084 .zip(&pos0s)
3085 .map(|(&t, &p0)| e.htod_i32(&(p0 as i32..(p0 + t) as i32).collect::<Vec<_>>()))
3086 .collect::<Result<_, _>>()?;
3087 let split = |e: &Engine,
3089 y: &CudaSlice<f32>,
3090 dim: usize|
3091 -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3092 let mut out = Vec::with_capacity(b);
3093 for s in 0..b {
3094 let mut ys = e.uninit(ts[s] * dim)?;
3095 e.copy_view_into(
3096 &mut ys,
3097 0,
3098 &y.slice(offs[s] * dim..(offs[s] + ts[s]) * dim),
3099 ts[s] * dim,
3100 )?;
3101 out.push(ys);
3102 }
3103 Ok(out)
3104 };
3105
3106 let cat_tokens: Vec<u32> = prompts.iter().flat_map(|p| p.iter().copied()).collect();
3107 let mut x = self.embed(e, &cat_tokens)?; for (il, layer) in self.layers.iter().enumerate() {
3109 let mut h = e.uninit(total * n_embd)?;
3110 let mut hx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3111 e.rms_norm_f16out(
3112 &x,
3113 layer.attn_norm.float_data(),
3114 &mut h,
3115 &mut hx16,
3116 n_embd,
3117 total,
3118 eps,
3119 )?;
3120 let mut mixed = e.uninit(total * n_embd)?;
3122 match &layer.mixer {
3123 Mixer::Full(fa) => {
3124 let g3 = e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], &h, &hx16, total)?;
3125 let geometry = self.cfg.full_attention_geometry_at(il as u32);
3131 let (n_head, n_head_kv, head_dim) = (
3132 geometry.n_head as usize,
3133 geometry.n_head_kv as usize,
3134 geometry.head_dim_k as usize,
3135 );
3136 let fa_scale = geometry.attention_scale();
3137 let use_favl = !carried
3138 && (2..=8).contains(&b)
3139 && (head_dim == 256 || head_dim == 128)
3140 && geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ
3141 && std::env::var("MEMRA_NOFA").is_err()
3142 && std::env::var("MEMRA_FA_FLOOR").is_err()
3143 && std::env::var("MEMRA_FA_PP_W2").as_deref() != Ok("1")
3144 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0")
3145 && std::env::var("MEMRA_FA_VL").as_deref() != Ok("0");
3146 if use_favl {
3147 let (qf_w, kf_w, vf_w) = (
3148 fa.wq.out_features(),
3149 fa.wk.out_features(),
3150 fa.wv.out_features(),
3151 );
3152 memra_gguf::config::check_fused_q_gate_extent(qf_w, head_dim, n_head, 1)?;
3157 struct APre {
3158 q: CudaSlice<f32>,
3159 gate: Option<CudaSlice<f32>>,
3160 qn: CudaSlice<f32>,
3161 kn: CudaSlice<f32>,
3162 }
3163 let mut aps = Vec::with_capacity(b);
3164 for &t in ts.iter().take(b) {
3165 aps.push(APre {
3166 q: e.uninit(t * n_head * head_dim)?,
3167 gate: Some(e.uninit(t * n_head * head_dim)?),
3168 qn: e.uninit(t * n_head * head_dim)?,
3169 kn: e.uninit(t * n_head_kv * head_dim)?,
3170 });
3171 }
3172 let (kv_dim_k, kv_dim_v, ktb, vtb) = {
3173 let kvl = caches[0].kv[il].as_ref().unwrap();
3174 (kvl.kv_dim_k, kvl.kv_dim_v, kvl.k_tok_bytes, kvl.v_tok_bytes)
3175 };
3176 let pargs: Vec<crate::AttnPreVl> = (0..b)
3177 .map(|s| {
3178 let (o, t) = (offs[s], ts[s]);
3179 let kvl = caches[s].kv[il].as_ref().unwrap();
3180 assert!(
3181 kvl.len == 0 && kvl.len + t <= caches[s].max_ctx,
3182 "prime_cache_batch attn vl: fresh + capacity"
3183 );
3184 crate::AttnPreVl {
3185 qf: e.addr_f32v(&g3[0].slice(o * qf_w..(o + t) * qf_w)),
3186 kf: e.addr_f32v(&g3[1].slice(o * kf_w..(o + t) * kf_w)),
3187 vf: e.addr_f32v(&g3[2].slice(o * vf_w..(o + t) * vf_w)),
3188 q: e.addr_f32(&aps[s].q),
3189 gate: e.addr_f32(aps[s].gate.as_ref().unwrap()),
3190 qn: e.addr_f32(&aps[s].qn),
3191 kn: e.addr_f32(&aps[s].kn),
3192 kc: e.addr_u8(&kvl.k),
3193 vc: e.addr_u8(&kvl.v),
3194 t: t as i32,
3195 pad: 0,
3196 }
3197 })
3198 .collect();
3199 e.attn_pre_vl8(
3200 &pargs,
3201 fa.q_norm.float_data(),
3202 fa.k_norm.float_data(),
3203 head_dim,
3204 geometry.n_rot as usize,
3205 n_head,
3206 n_head_kv,
3207 self.cfg.rms_eps,
3208 geometry.rope_base,
3209 1.0,
3210 kv_dim_k,
3211 kv_dim_v,
3212 ktb,
3213 vtb,
3214 )?;
3215 for s in 0..b {
3216 let kvl = caches[s].kv[il].as_mut().unwrap();
3217 kvl.len += ts[s];
3218 let new_len = kvl.len as i32;
3219 e.set_i32_one(&mut kvl.len_d, new_len)?;
3220 }
3221 let mut attns = Vec::with_capacity(b);
3222 let mut mirrors = Vec::with_capacity(b);
3223 for &t in ts.iter().take(b) {
3224 attns.push(e.uninit(t * n_head * head_dim)?);
3225 let n = t * n_head_kv * head_dim;
3226 mirrors.push((e.alloc_u8_uninit(n * 2)?, e.alloc_u8_uninit(n * 2)?));
3227 }
3228 let fa3_on = match std::env::var("MEMRA_FA3").as_deref() {
3231 Ok("0") => false,
3232 Ok("1") => {
3236 crate::refuse_portable_force(
3237 "MEMRA_FA3=1",
3238 "the sm_90a fa3/bf16 kernels",
3239 );
3240 true
3241 }
3242 _ => cfg!(memra_hopper_mma),
3243 };
3244 if fa3_on {
3245 let mut q16s = Vec::with_capacity(b);
3246 let mut v16s = Vec::with_capacity(b);
3247 for s in 0..b {
3248 let t = ts[s];
3249 let mut q16 = e.alloc_u8_uninit(t * n_head * head_dim * 2)?;
3250 e.f32_to_bf16_into(&aps[s].qn, &mut q16, t * n_head * head_dim)?;
3251 let mut k16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3252 e.f32_to_bf16_into(&aps[s].kn, &mut k16, t * n_head_kv * head_dim)?;
3253 let mut v16 = e.alloc_u8_uninit(t * n_head_kv * head_dim * 2)?;
3254 e.f32_to_bf16_v(
3255 &g3[2].slice(offs[s] * vf_w..(offs[s] + t) * vf_w),
3256 &mut v16,
3257 t * n_head_kv * head_dim,
3258 )?;
3259 q16s.push(q16);
3260 v16s.push((k16, v16));
3261 }
3262 let mut qp = [core::ptr::null::<core::ffi::c_void>(); 8];
3263 let mut kp = qp;
3264 let mut vp = qp;
3265 let mut op = [core::ptr::null_mut::<f32>(); 8];
3266 let mut tsv = [0i32; 8];
3267 for s in 0..b {
3268 qp[s] = e.addr_u8(&q16s[s]) as *const core::ffi::c_void;
3269 kp[s] = e.addr_u8(&v16s[s].0) as *const core::ffi::c_void;
3270 vp[s] = e.addr_u8(&v16s[s].1) as *const core::ffi::c_void;
3271 op[s] = e.addr_f32(&attns[s]) as *mut f32;
3272 tsv[s] = ts[s] as i32;
3273 }
3274 let rc = unsafe {
3275 crate::fa3_vl_raw(
3276 qp.as_ptr(),
3277 kp.as_ptr(),
3278 vp.as_ptr(),
3279 op.as_ptr(),
3280 tsv.as_ptr(),
3281 b as i32,
3282 n_head as i32,
3283 n_head_kv as i32,
3284 head_dim as i32,
3285 fa_scale,
3286 e.stream().cu_stream() as *mut core::ffi::c_void,
3287 )
3288 };
3289 if rc != 0 {
3290 return Err(format!("memra_fa3_vl rc={rc}").into());
3291 }
3292 } else {
3293 let fargs: Vec<crate::FaSeqVl> = (0..b)
3294 .map(|s| crate::FaSeqVl {
3295 q: e.addr_f32(&aps[s].qn),
3296 k16: e.addr_u8(&mirrors[s].0),
3297 v16: e.addr_u8(&mirrors[s].1),
3298 o: e.addr_f32(&attns[s]),
3299 kf: e.addr_f32(&aps[s].kn),
3300 vf: e.addr_f32v(
3301 &g3[2].slice(offs[s] * vf_w..(offs[s] + ts[s]) * vf_w),
3302 ),
3303 t: ts[s] as i32,
3304 pad: 0,
3305 })
3306 .collect();
3307 e.fa_prefill_vl8(&fargs, head_dim, n_head, n_head_kv, fa_scale)?;
3308 }
3309 for (s, attn) in attns.into_iter().enumerate() {
3310 let (attn_g, ag16) = self.full_attn_prime_post_fa(
3311 e,
3312 attn,
3313 &aps[s].gate,
3314 ts[s],
3315 n_head,
3316 head_dim,
3317 )?;
3318 let mut done = false;
3319 if let Some(xh) = &ag16 {
3320 done = e.try_f16_gemm_pre_into_off(
3321 &fa.wo,
3322 xh,
3323 ts[s],
3324 &mut mixed,
3325 offs[s] * n_embd,
3326 )?;
3327 }
3328 if !done {
3329 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3330 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3331 }
3332 }
3333 } else {
3334 let mut parts: Vec<Vec<CudaSlice<f32>>> =
3335 (0..b).map(|_| Vec::new()).collect();
3336 for (w, y) in [&fa.wq, &fa.wk, &fa.wv].iter().zip(g3) {
3337 for (s, ys) in split(e, &y, w.out_features())?.into_iter().enumerate() {
3338 parts[s].push(ys);
3339 }
3340 }
3341 for (s, g3s) in parts.into_iter().enumerate() {
3342 let (attn_g, ag16) = self.full_attn_prime_core_inner(
3344 e, fa, g3s, &pos_ds[s], ts[s], caches[s], il,
3345 )?;
3346 let mut done = false;
3347 if let Some(xh) = &ag16 {
3348 done = e.try_f16_gemm_pre_into_off(
3349 &fa.wo,
3350 xh,
3351 ts[s],
3352 &mut mixed,
3353 offs[s] * n_embd,
3354 )?;
3355 }
3356 if !done {
3357 let m = e.matmul(&fa.wo, &attn_g, ts[s])?;
3358 e.copy_into(&mut mixed, offs[s] * n_embd, &m, ts[s] * n_embd)?;
3359 }
3360 }
3361 }
3362 }
3363 Mixer::Mla(_) => crate::hybrid::mla_forward_unimplemented(),
3364 Mixer::Linear(la) => {
3365 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3370 let g4 = e.matmul_group_xh(&ws, &h, &hx16, total)?;
3371 let outs =
3372 self.linear_attn_prime_core_batch(e, la, &g4, &offs, &ts, caches, il)?;
3373 for (s, (gn, gn16)) in outs.into_iter().enumerate() {
3374 let (o, t) = (offs[s], ts[s]);
3375 let mut done = false;
3376 if let Some(xh) = &gn16 {
3377 done = e.try_f16_gemm_pre_into_off(
3378 &la.ssm_out,
3379 xh,
3380 t,
3381 &mut mixed,
3382 o * n_embd,
3383 )?;
3384 }
3385 if !done {
3386 let m = e.matmul(&la.ssm_out, &gn, t)?;
3387 e.copy_into(&mut mixed, o * n_embd, &m, t * n_embd)?;
3388 }
3389 }
3390 }
3391 }
3392 let mut x1 = e.uninit(total * n_embd)?;
3393 let mut z = e.uninit(total * n_embd)?;
3394 let mut zx16 = e.alloc_u8_uninit(total * n_embd * 2)?;
3395 e.add_rms_norm_f16out(
3396 &x,
3397 &mixed,
3398 layer.post_attn_norm.float_data(),
3399 &mut x1,
3400 &mut z,
3401 &mut zx16,
3402 n_embd,
3403 total,
3404 eps,
3405 )?;
3406 let ffn_out = match &layer.ffn {
3407 crate::hybrid::Ffn::Dense {
3408 ffn_gate,
3409 ffn_up,
3410 ffn_down,
3411 } => {
3412 let n_ff = ffn_gate.out_features();
3413 let mut g2 = e.matmul_group_xh(&[ffn_gate, ffn_up], &z, &zx16, total)?;
3414 let up = g2.pop().unwrap();
3415 let gate = g2.pop().unwrap();
3416 let mut act = e.uninit(total * n_ff)?;
3417 let d_lim = self.cfg.clamp_shexp_at(il as u32);
3421 if Self::f16out_on(e, total) && self.cfg.m3.is_none() && d_lim.is_none() {
3422 let mut a16 = e.alloc_u8_uninit(total * n_ff * 2)?;
3423 e.silu_mul_f16out(&gate, &up, &mut act, &mut a16, total * n_ff)?;
3424 match e.try_f16_gemm_pre(ffn_down, &a16, total)? {
3425 Some(y) => y,
3426 None => e.matmul(ffn_down, &act, total)?,
3427 }
3428 } else {
3429 Self::ffn_act_lim(
3430 e,
3431 &self.cfg,
3432 &gate,
3433 &up,
3434 1.0,
3435 1.0,
3436 d_lim,
3437 &mut act,
3438 total * n_ff,
3439 )?;
3440 e.matmul(ffn_down, &act, total)?
3441 }
3442 }
3443 crate::hybrid::Ffn::Moe(m) => {
3444 self.moe_ffn_il_prefill(e, m, &z, total, il as u16)?
3445 }
3446 };
3447 let mut x2 = e.uninit(total * n_embd)?;
3448 e.add(&x1, &ffn_out, &mut x2, total * n_embd)?;
3449 x = x2;
3450 }
3451 let mut hn = e.uninit(total * n_embd)?;
3453 e.rms_norm(
3454 &x,
3455 self.output_norm.float_data(),
3456 &mut hn,
3457 n_embd,
3458 total,
3459 eps,
3460 )?;
3461 let mut hcat = e.uninit(b * n_embd)?;
3467 for s in 0..b {
3468 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3469 e.copy_view_into(
3470 &mut hcat,
3471 s * n_embd,
3472 &hn.slice(last0..last0 + n_embd),
3473 n_embd,
3474 )?;
3475 }
3476 let logits_cat = if b >= 2 {
3477 e.try_f16_gemm(&self.output, &hcat, b)?
3478 } else {
3479 None
3480 };
3481 let logits_host: Option<Vec<f32>> = match &logits_cat {
3482 Some(lc) => Some(e.dtoh(lc)?),
3483 None => None,
3484 };
3485 let n_vocab = self.output.out_features();
3486 let mut hidden_all = if crate::spec::spec_hpost() {
3487 split(e, &hn, n_embd)?
3488 } else {
3489 split(e, &x, n_embd)?
3490 };
3491 let mut out = Vec::with_capacity(b);
3492 for s in 0..b {
3493 let last0 = (offs[s] + ts[s] - 1) * n_embd;
3494 let mut h_seed = e.uninit(n_embd)?;
3495 if !crate::spec::spec_hpost() {
3496 e.copy_view_into(&mut h_seed, 0, &x.slice(last0..last0 + n_embd), n_embd)?;
3497 } else {
3498 e.copy_view_into(&mut h_seed, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3499 }
3500 let logits = match &logits_host {
3501 Some(lh) => lh[s * n_vocab..(s + 1) * n_vocab].to_vec(),
3502 None => {
3503 let mut hlast = e.uninit(n_embd)?;
3504 e.copy_view_into(&mut hlast, 0, &hn.slice(last0..last0 + n_embd), n_embd)?;
3505 e.dtoh(&e.matmul(&self.output, &hlast, 1)?)?
3506 }
3507 };
3508 caches[s].pos += ts[s];
3509 out.push((logits, h_seed, hidden_all.remove(0)));
3510 }
3511 Ok(out)
3512 }
3513
3514 #[allow(clippy::too_many_arguments)]
3525 fn full_attn_prime(
3526 &self,
3527 e: &Engine,
3528 fa: &FullAttnLayer,
3529 h: &CudaSlice<f32>,
3530 hx: Option<&CudaSlice<u8>>,
3531 pos_d: &CudaSlice<i32>,
3532 t: usize,
3533 cache: &mut Cache,
3534 il: usize,
3535 seq_end: usize,
3536 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3537 if self.uses_sliding_gated_moe_program() {
3538 return self.step35_attn_prime(e, fa, h, hx, pos_d, t, cache, il, seq_end);
3539 }
3540 let g3 = match hx {
3545 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
3546 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
3547 };
3548 self.full_attn_prime_core(e, fa, g3, pos_d, t, cache, il)
3549 }
3550
3551 fn full_attn_prime_core(
3555 &self,
3556 e: &Engine,
3557 fa: &FullAttnLayer,
3558 g3: Vec<CudaSlice<f32>>,
3559 pos_d: &CudaSlice<i32>,
3560 t: usize,
3561 cache: &mut Cache,
3562 il: usize,
3563 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3564 let (attn_g, ag16) = self.full_attn_prime_core_inner(e, fa, g3, pos_d, t, cache, il)?;
3565 if let Some(xh) = &ag16 {
3566 if let Some(y) = e.try_f16_gemm_pre(&fa.wo, xh, t)? {
3567 return Ok(y);
3568 }
3569 }
3570 Ok(e.matmul(&fa.wo, &attn_g, t)?)
3571 }
3572
3573 fn full_attn_prime_core_inner(
3574 &self,
3575 e: &Engine,
3576 fa: &FullAttnLayer,
3577 g3: Vec<CudaSlice<f32>>,
3578 pos_d: &CudaSlice<i32>,
3579 t: usize,
3580 cache: &mut Cache,
3581 il: usize,
3582 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3583 let cfg = &self.cfg;
3584 let geometry = cfg.full_attention_geometry_at(il as u32);
3585 let n_head = geometry.n_head as usize;
3586 let n_head_kv = geometry.n_head_kv as usize;
3587 let head_dim = geometry.head_dim_k as usize;
3588 let scale = geometry.attention_scale();
3589 let (pre, base_len) = self.full_attn_prime_pre_fa(e, fa, g3, pos_d, t, cache, il)?;
3590 let AttnPre { q, k, v, gate } = pre;
3591 let mut attn = e.uninit(t * n_head * head_dim)?;
3592 self.full_attn_prime_fa_dispatch(
3593 e, &q, &k, &v, &mut attn, base_len, t, cache, il, head_dim, n_head, n_head_kv, scale,
3594 )?;
3595 self.full_attn_prime_post_fa(e, attn, &gate, t, n_head, head_dim)
3596 }
3597
3598 #[allow(clippy::type_complexity)]
3602 fn full_attn_prime_pre_fa(
3603 &self,
3604 e: &Engine,
3605 fa: &FullAttnLayer,
3606 mut g3: Vec<CudaSlice<f32>>,
3607 pos_d: &CudaSlice<i32>,
3608 t: usize,
3609 cache: &mut Cache,
3610 il: usize,
3611 ) -> Result<(AttnPre, usize), Box<dyn std::error::Error>> {
3612 let cfg = &self.cfg;
3613 let geometry = cfg.full_attention_geometry_at(il as u32);
3614 let n_head = geometry.n_head as usize;
3615 let n_head_kv = geometry.n_head_kv as usize;
3616 let head_dim = geometry.head_dim_k as usize;
3617 let eps = cfg.rms_eps;
3618
3619 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
3623 let v = g3.pop().unwrap();
3624 let mut k = g3.pop().unwrap();
3625 let qf = g3.pop().unwrap();
3626 let (mut q, gate) = if gated {
3627 let mut q = e.uninit(t * n_head * head_dim)?;
3628 let mut gate = e.uninit(t * n_head * head_dim)?;
3629 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
3630 (q, Some(gate))
3631 } else {
3632 (qf, None)
3633 };
3634
3635 let mut qn = e.uninit(t * n_head * head_dim)?;
3636 e.rms_norm(
3637 &q,
3638 fa.q_norm.float_data(),
3639 &mut qn,
3640 head_dim,
3641 n_head * t,
3642 eps,
3643 )?;
3644 q = qn;
3645 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
3646 e.rms_norm(
3647 &k,
3648 fa.k_norm.float_data(),
3649 &mut kn,
3650 head_dim,
3651 n_head_kv * t,
3652 eps,
3653 )?;
3654 k = kn;
3655 let rope_dims = geometry.n_rot as usize;
3656 e.rope_neox(
3657 &mut q,
3658 pos_d,
3659 head_dim,
3660 rope_dims,
3661 n_head,
3662 t,
3663 geometry.rope_base,
3664 1.0,
3665 )?;
3666 e.rope_neox(
3667 &mut k,
3668 pos_d,
3669 head_dim,
3670 rope_dims,
3671 n_head_kv,
3672 t,
3673 geometry.rope_base,
3674 1.0,
3675 )?;
3676
3677 {
3680 let kvl = cache.kv[il].as_mut().unwrap();
3681 assert!(kvl.len + t <= cache.max_ctx, "prime_cache: KV overflow");
3682 e.append_kv_quantized_rows(
3683 &k,
3684 &v,
3685 &mut kvl.k,
3686 &mut kvl.v,
3687 kvl.len,
3688 t,
3689 kvl.kv_dim_k,
3690 kvl.kv_dim_v,
3691 kvl.k_tok_bytes,
3692 kvl.v_tok_bytes,
3693 crate::Engine::kv_fp8_on(),
3694 )?;
3695 kvl.len += t;
3696 let new_len = kvl.len as i32;
3697 e.set_i32_one(&mut kvl.len_d, new_len)?;
3698 }
3699
3700 let base_len = {
3701 let kvl = cache.kv[il].as_ref().unwrap();
3702 kvl.len - t };
3704 Ok((AttnPre { q, k, v, gate }, base_len))
3705 }
3706
3707 #[allow(clippy::too_many_arguments)]
3714 fn full_attn_prime_fa_dispatch(
3715 &self,
3716 e: &Engine,
3717 q: &CudaSlice<f32>,
3718 k: &CudaSlice<f32>,
3719 v: &CudaSlice<f32>,
3720 attn: &mut CudaSlice<f32>,
3721 base_len: usize,
3722 t: usize,
3723 cache: &mut Cache,
3724 il: usize,
3725 head_dim: usize,
3726 n_head: usize,
3727 n_head_kv: usize,
3728 scale: f32,
3729 ) -> Result<(), Box<dyn std::error::Error>> {
3730 if base_len == 0 && std::env::var("MEMRA_PRIME_F32CHUNK0").as_deref() == Ok("1") {
3743 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3744 e.sdpa_naive(
3745 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3746 )?;
3747 } else {
3748 e.fa_prefill(
3749 q, k, v, attn, head_dim, n_head, n_head_kv, t, t, scale, true,
3750 )?;
3751 }
3752 return Ok(());
3753 }
3754 let kvl = cache.kv[il].as_ref().unwrap();
3755 let t_kv = base_len + t;
3756 let k_view = e.view_u8(&kvl.k, t_kv * kvl.k_tok_bytes);
3757 let v_view = e.view_u8(&kvl.v, t_kv * kvl.v_tok_bytes);
3758 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
3762 e.sdpa_naive_quantized_view(
3763 q,
3764 &k_view,
3765 &v_view,
3766 attn,
3767 head_dim,
3768 n_head,
3769 n_head_kv,
3770 t,
3771 t_kv,
3772 scale,
3773 true,
3774 kvl.k_tok_bytes,
3775 kvl.v_tok_bytes,
3776 )?;
3777 return Ok(());
3778 }
3779 let deqw = std::env::var("MEMRA_PRIME_DEQW")
3787 .map(|v| v != "0")
3788 .unwrap_or(true);
3789 if deqw {
3790 e.fa_prefill_view_ws(
3791 q,
3792 &k_view,
3793 &v_view,
3794 attn,
3795 head_dim,
3796 n_head,
3797 n_head_kv,
3798 t,
3799 t_kv,
3800 scale,
3801 true,
3802 kvl.k_tok_bytes,
3803 kvl.v_tok_bytes,
3804 crate::Engine::kv_fp8_on(),
3805 )?;
3806 } else {
3807 e.fa_prefill_view(
3808 q,
3809 &k_view,
3810 &v_view,
3811 attn,
3812 head_dim,
3813 n_head,
3814 n_head_kv,
3815 t,
3816 t_kv,
3817 scale,
3818 true,
3819 kvl.k_tok_bytes,
3820 kvl.v_tok_bytes,
3821 crate::Engine::kv_fp8_on(),
3822 )?;
3823 }
3824 Ok(())
3825 }
3826
3827 fn full_attn_prime_post_fa(
3830 &self,
3831 e: &Engine,
3832 attn: CudaSlice<f32>,
3833 gate: &Option<CudaSlice<f32>>,
3834 t: usize,
3835 n_head: usize,
3836 head_dim: usize,
3837 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3838 let (attn_g, ag16) = match gate {
3839 Some(gate) => {
3840 let n = t * n_head * head_dim;
3841 let mut ag = e.uninit(n)?;
3842 if Self::f16out_on(e, t) {
3843 let mut a16 = e.alloc_u8_uninit(n * 2)?;
3844 e.sig_mul_f16out(&attn, gate, &mut ag, &mut a16, n)?;
3845 (ag, Some(a16))
3846 } else {
3847 let mut gsig = e.uninit(n)?;
3848 e.sigmoid(gate, &mut gsig, n)?;
3849 e.mul(&attn, &gsig, &mut ag, n)?;
3850 (ag, None)
3851 }
3852 }
3853 None => (attn, None),
3854 };
3855 Ok((attn_g, ag16))
3856 }
3857
3858 fn linear_attn_prime(
3865 &self,
3866 e: &Engine,
3867 la: &LinearAttnLayer,
3868 h: &CudaSlice<f32>,
3869 hx: Option<&CudaSlice<u8>>,
3870 t: usize,
3871 cache: &mut Cache,
3872 il: usize,
3873 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3874 let ws = [&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha];
3876 let g4 = match hx {
3877 Some(xh) => e.matmul_group_xh(&ws, h, xh, t)?,
3878 None => e.matmul_group(&ws, h, t)?,
3879 };
3880 self.linear_attn_prime_core(e, la, g4, t, cache, il)
3881 }
3882
3883 fn linear_attn_prime_core(
3885 &self,
3886 e: &Engine,
3887 la: &LinearAttnLayer,
3888 mut g4: Vec<CudaSlice<f32>>,
3889 t: usize,
3890 cache: &mut Cache,
3891 il: usize,
3892 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3893 self.linear_attn_prime_core_pad(e, la, g4.drain(..).collect(), t, cache, il, None)
3894 }
3895
3896 #[allow(clippy::too_many_arguments)]
3900 fn linear_attn_prime_core_pad_inner(
3901 &self,
3902 e: &Engine,
3903 la: &LinearAttnLayer,
3904 mut g4: Vec<CudaSlice<f32>>,
3905 t: usize,
3906 cache: &mut Cache,
3907 il: usize,
3908 pad_len: Option<&CudaSlice<i32>>,
3909 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
3910 let geometry = la.geometry;
3912 let d_state = geometry.key_head_dim as usize;
3913 let num_k = geometry.key_heads as usize;
3914 let num_v = geometry.value_heads as usize;
3915 let key_dim = d_state * num_k;
3916 let value_dim = geometry.value_head_dim as usize * num_v;
3917 let conv_dim = key_dim * 2 + value_dim;
3918 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(
3923 e,
3924 la,
3925 &qkv_mixed.slice(0..t * conv_dim),
3926 &z.slice(0..t * value_dim),
3927 &beta_raw.slice(0..t * num_v),
3928 &alpha.slice(0..t * num_v),
3929 t,
3930 cache,
3931 il,
3932 pad_len,
3933 )
3934 }
3935
3936 #[allow(clippy::too_many_arguments)]
3939 fn linear_attn_gdn_prep(
3940 &self,
3941 e: &Engine,
3942 la: &LinearAttnLayer,
3943 qkv_mixed: &cudarc::driver::CudaView<f32>,
3944 beta_raw: &cudarc::driver::CudaView<f32>,
3945 alpha: &cudarc::driver::CudaView<f32>,
3946 t: usize,
3947 cache: &mut Cache,
3948 il: usize,
3949 pad_len: Option<&CudaSlice<i32>>,
3950 ) -> Result<GdnPrep, Box<dyn std::error::Error>> {
3951 let cfg = &self.cfg;
3952 let geometry = la.geometry;
3953 let d_state = geometry.key_head_dim as usize;
3954 let num_k = geometry.key_heads as usize;
3955 let num_v = geometry.value_heads as usize;
3956 let d_conv = geometry.conv_kernel as usize;
3957 let key_dim = d_state * num_k; let value_dim = geometry.value_head_dim as usize * num_v;
3959 let conv_dim = key_dim * 2 + value_dim; let eps = cfg.rms_eps;
3961 debug_assert!(
3962 t >= d_conv - 1,
3963 "stateful conv needs T >= pad (PRIME_MIN_T gates)"
3964 );
3965
3966 let rl = cache.recur[il].as_mut().unwrap();
3971 let hk = Self::gdn_hk(e, t, num_v, num_k);
3972 let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
3973 let hk = if conv_fuse { hk } else { num_v }; let mut q_g = e.uninit(d_state * hk * t)?;
3975 let mut k_g = e.uninit(d_state * hk * t)?;
3976 let mut v_g = e.uninit(d_state * num_v * t)?;
3977 if conv_fuse {
3978 e.ssm_conv1d_gdn_state_pad(
3979 qkv_mixed,
3980 &mut rl.conv_state,
3981 la.ssm_conv1d.float_data(),
3982 &mut q_g,
3983 &mut k_g,
3984 &mut v_g,
3985 conv_dim,
3986 t,
3987 d_conv,
3988 d_state,
3989 num_v,
3990 num_k,
3991 key_dim,
3992 hk,
3993 pad_len,
3994 )?;
3995 } else {
3996 let mut conv_out = e.uninit(conv_dim * t)?; e.ssm_conv1d_tm_state_pad_v(
3998 qkv_mixed,
3999 &mut rl.conv_state,
4000 la.ssm_conv1d.float_data(),
4001 &mut conv_out,
4002 conv_dim,
4003 t,
4004 d_conv,
4005 pad_len,
4006 )?;
4007 e.qkv_to_gdn_repack(
4008 &conv_out, &mut q_g, &mut k_g, &mut v_g, d_state, num_v, num_k, key_dim, t,
4009 )?;
4010 }
4011 let mut q_l2 = e.uninit(d_state * hk * t)?;
4012 let qb16 = if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(32) {
4016 let mut qb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4017 e.l2_norm_pp(&q_g, &mut q_l2, Some(&mut qb), d_state, hk * t, eps)?;
4018 Some(qb)
4019 } else {
4020 e.l2_norm_pp(&q_g, &mut q_l2, None, d_state, hk * t, eps)?;
4021 None
4022 };
4023 let mut k_l2 = e.uninit(d_state * hk * t)?;
4024 let kb16 = if Engine::l2_v2_on(d_state) {
4026 let mut kb = e.alloc_u8_uninit(d_state * hk * t * 2)?;
4027 e.l2_norm_pp(&k_g, &mut k_l2, Some(&mut kb), d_state, hk * t, eps)?;
4028 Some(kb)
4029 } else {
4030 e.l2_norm_pp(&k_g, &mut k_l2, None, d_state, hk * t, eps)?;
4031 None
4032 };
4033 let mut beta = e.uninit(t * num_v)?;
4034 e.sigmoid_v(beta_raw, &mut beta, t * num_v)?;
4035 let mut g_log = e.uninit(t * num_v)?;
4036 e.gdn_glog_v(
4037 alpha,
4038 la.ssm_dt.float_data(),
4039 la.ssm_a.float_data(),
4040 &mut g_log,
4041 num_v,
4042 t,
4043 )?;
4044 if let Some(len_d) = pad_len {
4045 e.gdn_pad_mask(&mut beta, &mut g_log, len_d, num_v, t)?;
4046 }
4047 Ok(GdnPrep {
4048 hk,
4049 q_l2,
4050 k_l2,
4051 v_g,
4052 beta,
4053 g_log,
4054 kb16,
4055 qb16,
4056 })
4057 }
4058
4059 #[allow(clippy::too_many_arguments)]
4064 fn linear_attn_prime_core_batch(
4065 &self,
4066 e: &Engine,
4067 la: &LinearAttnLayer,
4068 g4: &[CudaSlice<f32>],
4069 offs: &[usize],
4070 ts: &[usize],
4071 caches: &mut [&mut Cache],
4072 il: usize,
4073 ) -> Result<Vec<(CudaSlice<f32>, Option<CudaSlice<u8>>)>, Box<dyn std::error::Error>> {
4074 let geometry = la.geometry;
4075 let d_state = geometry.key_head_dim as usize;
4076 let num_k = geometry.key_heads as usize;
4077 let num_v = geometry.value_heads as usize;
4078 let d_conv = geometry.conv_kernel as usize;
4079 let key_dim = d_state * num_k;
4080 let value_dim = geometry.value_head_dim as usize * num_v;
4081 let conv_dim = key_dim * 2 + value_dim;
4082 let eps = self.cfg.rms_eps;
4083 let scale = 1.0 / (d_state as f32).sqrt();
4084 let b = ts.len();
4085 let c = Engine::gdn_chunk_size();
4086 let carried = caches.iter().any(|c| c.pos > 0);
4089 let use_vl = !carried
4090 && (2..=8).contains(&b)
4091 && Engine::gdn_chunked_enabled()
4092 && ts.iter().all(|&t| t >= 16)
4093 && e.gdn_mma_enabled(c)
4094 && std::env::var("MEMRA_GDN_VL").as_deref() != Ok("0");
4095 if !use_vl {
4096 return (0..b)
4097 .map(|s| {
4098 let (o, t) = (offs[s], ts[s]);
4099 self.linear_attn_prime_core_pad_view(
4100 e,
4101 la,
4102 &g4[0].slice(o * conv_dim..(o + t) * conv_dim),
4103 &g4[1].slice(o * value_dim..(o + t) * value_dim),
4104 &g4[2].slice(o * num_v..(o + t) * num_v),
4105 &g4[3].slice(o * num_v..(o + t) * num_v),
4106 t,
4107 caches[s],
4108 il,
4109 None,
4110 )
4111 })
4112 .collect();
4113 }
4114 struct SeqBufs {
4118 conv_out: CudaSlice<f32>,
4119 q_g: CudaSlice<f32>,
4120 k_g: CudaSlice<f32>,
4121 v_g: CudaSlice<f32>,
4122 q_l2: CudaSlice<f32>,
4123 k_l2: CudaSlice<f32>,
4124 beta: CudaSlice<f32>,
4125 g_log: CudaSlice<f32>,
4126 gn: CudaSlice<f32>,
4127 gn16: CudaSlice<u8>,
4128 }
4129 let f16o = Self::f16out_on(e, 16);
4130 let hk = Self::gdn_hk(e, 16, num_v, num_k); let mut sb = Vec::with_capacity(b);
4132 let mut pres = Vec::with_capacity(b);
4133 for &t in ts.iter().take(b) {
4134 sb.push(SeqBufs {
4135 conv_out: e.uninit(conv_dim * t)?,
4136 q_g: e.uninit(d_state * hk * t)?,
4137 k_g: e.uninit(d_state * hk * t)?,
4138 v_g: e.uninit(d_state * num_v * t)?,
4139 q_l2: e.uninit(d_state * hk * t)?,
4140 k_l2: e.uninit(d_state * hk * t)?,
4141 beta: e.uninit(t * num_v)?,
4142 g_log: e.uninit(t * num_v)?,
4143 gn: e.uninit(d_state * num_v * t)?,
4144 gn16: e.alloc_u8_uninit(d_state * num_v * t * 2)?,
4145 });
4146 pres.push(e.gdn_chunk_alloc(num_v, t, c, hk)?);
4147 }
4148 let prep_args: Vec<crate::GdnPrepVl> = (0..b)
4149 .map(|s| {
4150 let (o, t) = (offs[s], ts[s]);
4151 let rl = caches[s].recur[il].as_ref().unwrap();
4152 crate::GdnPrepVl {
4153 qkv: e.addr_f32v(&g4[0].slice(o * conv_dim..(o + t) * conv_dim)),
4154 conv_state: e.addr_f32(&rl.conv_state),
4155 conv_out: e.addr_f32(&sb[s].conv_out),
4156 q_g: e.addr_f32(&sb[s].q_g),
4157 k_g: e.addr_f32(&sb[s].k_g),
4158 v_g: e.addr_f32(&sb[s].v_g),
4159 q_l2: e.addr_f32(&sb[s].q_l2),
4160 k_l2: e.addr_f32(&sb[s].k_l2),
4161 beta_raw: e.addr_f32v(&g4[2].slice(o * num_v..(o + t) * num_v)),
4162 alpha: e.addr_f32v(&g4[3].slice(o * num_v..(o + t) * num_v)),
4163 beta: e.addr_f32(&sb[s].beta),
4164 g_log: e.addr_f32(&sb[s].g_log),
4165 o: e.addr_f32(&pres[s].o),
4166 z: e.addr_f32v(&g4[1].slice(o * value_dim..(o + t) * value_dim)),
4167 gn: e.addr_f32(&sb[s].gn),
4168 gn16: e.addr_u8(&sb[s].gn16),
4169 kb16: if Engine::l2_v2_on(d_state) {
4170 e.addr_u8(&pres[s].kb16)
4171 } else {
4172 0
4173 },
4174 qb16: if Engine::l2_v2_on(d_state) && e.gdn_wgmma_on(c) {
4175 e.addr_u8(&pres[s].qb16)
4176 } else {
4177 0
4178 },
4179 t: t as i32,
4180 pad: 0,
4181 }
4182 })
4183 .collect();
4184 let args: Vec<crate::GdnSeqVl> = (0..b)
4185 .map(|s| {
4186 let rl = caches[s].recur[il].as_ref().unwrap();
4187 crate::GdnSeqVl {
4188 kb16: e.addr_u8(&pres[s].kb16),
4189 gcum: e.addr_f32(&pres[s].gcum),
4190 beta: e.addr_f32(&sb[s].beta),
4191 u: e.addr_f32(&pres[s].u),
4192 wb16: e.addr_u8(&pres[s].wb16),
4193 y: e.addr_u8(&pres[s].y16),
4194 ssnap: e.addr_u8(&pres[s].ssnap16),
4195 state_in: e.addr_f32(&rl.ssm_state),
4196 state_out: e.addr_f32(&rl.ssm_state_alt),
4197 q: e.addr_f32(&sb[s].q_l2),
4198 p: e.addr_f32(&pres[s].p),
4199 o: e.addr_f32(&pres[s].o),
4200 k: e.addr_f32(&sb[s].k_l2),
4201 v: e.addr_f32(&sb[s].v_g),
4202 g: e.addr_f32(&sb[s].g_log),
4203 a: e.addr_f32(&pres[s].a),
4204 w: e.addr_f32(&pres[s].w),
4205 t: ts[s] as i32,
4206 nc: pres[s].nc as i32,
4207 }
4208 })
4209 .collect();
4210 e.gdn_prep_vl8(
4211 &prep_args,
4212 la.ssm_conv1d.float_data(),
4213 la.ssm_dt.float_data(),
4214 la.ssm_a.float_data(),
4215 conv_dim,
4216 d_conv,
4217 d_state,
4218 num_v,
4219 num_k,
4220 key_dim,
4221 hk,
4222 eps,
4223 )?;
4224 if !Engine::l2_v2_on(d_state) {
4227 e.gdn_mirror_vl8(&args, num_v, 0, hk)?;
4228 }
4229 let wq8: Option<crate::GdnWVl8> = if e.gdn_wgmma_on(c) {
4231 if !Engine::l2_v2_on(d_state) {
4233 for s in 0..b {
4234 e.f32_to_bf16_into(&sb[s].q_l2, &mut pres[s].qb16, d_state * hk * ts[s])?;
4235 }
4236 }
4237 let mut wa = [crate::GdnWVl::default(); 8];
4238 for s in 0..b {
4239 wa[s] = crate::GdnWVl {
4240 qb16: e.addr_u8(&pres[s].qb16),
4241 pb16: e.addr_u8(&pres[s].pb16),
4242 };
4243 }
4244 Some(crate::GdnWVl8(wa))
4245 } else {
4246 None
4247 };
4248 e.gdn_chunk_k123_vl8(&args, num_v, hk, wq8.as_ref())?;
4249 e.gdn_chunk_vl8(&args, num_v, scale, hk, wq8.as_ref())?;
4250 if f16o {
4251 e.gdn_tail_vl8(&prep_args, la.ssm_norm.float_data(), d_state, num_v, eps)?;
4252 }
4253 let mut out = Vec::with_capacity(b);
4255 for (s, bufs) in sb.into_iter().enumerate() {
4256 let rl = caches[s].recur[il].as_mut().unwrap();
4257 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4258 let (o, t) = (offs[s], ts[s]);
4259 let SeqBufs { mut gn, gn16, .. } = bufs;
4260 if f16o {
4261 out.push((gn, Some(gn16)));
4262 } else {
4263 let z_v = g4[1].slice(o * value_dim..(o + t) * value_dim);
4264 e.gated_rmsnorm_zv(
4265 &pres[s].o,
4266 la.ssm_norm.float_data(),
4267 &z_v,
4268 &mut gn,
4269 d_state,
4270 num_v * t,
4271 eps,
4272 )?;
4273 out.push((gn, None));
4274 }
4275 }
4276 Ok(out)
4277 }
4278
4279 #[allow(clippy::too_many_arguments)]
4283 fn linear_attn_prime_core_pad_view(
4284 &self,
4285 e: &Engine,
4286 la: &LinearAttnLayer,
4287 qkv_mixed: &cudarc::driver::CudaView<f32>,
4288 z: &cudarc::driver::CudaView<f32>,
4289 beta_raw: &cudarc::driver::CudaView<f32>,
4290 alpha: &cudarc::driver::CudaView<f32>,
4291 t: usize,
4292 cache: &mut Cache,
4293 il: usize,
4294 pad_len: Option<&CudaSlice<i32>>,
4295 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<u8>>), Box<dyn std::error::Error>> {
4296 let cfg = &self.cfg;
4297 let geometry = la.geometry;
4298 let d_state = geometry.key_head_dim as usize;
4299 let num_v = geometry.value_heads as usize;
4300 let eps = cfg.rms_eps;
4301 let scale = 1.0 / (d_state as f32).sqrt();
4302
4303 let prep =
4304 self.linear_attn_gdn_prep(e, la, qkv_mixed, beta_raw, alpha, t, cache, il, pad_len)?;
4305
4306 let mut o = e.uninit(d_state * num_v * t)?;
4312 let rl = cache.recur[il].as_mut().unwrap();
4313 {
4314 let crate::cache::RecurLayer {
4315 ssm_state,
4316 ssm_state_alt,
4317 ..
4318 } = rl;
4319 e.gdn_scan_prefill(
4320 &prep.q_l2,
4321 &prep.k_l2,
4322 &prep.v_g,
4323 &prep.g_log,
4324 &prep.beta,
4325 prep.kb16.as_ref(),
4326 prep.qb16.as_ref(),
4327 ssm_state,
4328 ssm_state_alt,
4329 &mut o,
4330 num_v,
4331 t,
4332 scale,
4333 prep.hk,
4334 )?;
4335 }
4336 std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
4337
4338 let mut gn = e.uninit(d_state * num_v * t)?;
4341 let gn16 = if Self::f16out_on(e, t) {
4342 let mut g16 = e.alloc_u8_uninit(d_state * num_v * t * 2)?;
4343 e.gated_rmsnorm_f16out_zv(
4344 &o,
4345 la.ssm_norm.float_data(),
4346 z,
4347 &mut gn,
4348 &mut g16,
4349 d_state,
4350 num_v * t,
4351 eps,
4352 )?;
4353 Some(g16)
4354 } else {
4355 e.gated_rmsnorm_zv(
4356 &o,
4357 la.ssm_norm.float_data(),
4358 z,
4359 &mut gn,
4360 d_state,
4361 num_v * t,
4362 eps,
4363 )?;
4364 None
4365 };
4366 Ok((gn, gn16))
4367 }
4368
4369 #[allow(clippy::too_many_arguments)]
4371 fn linear_attn_prime_core_pad(
4372 &self,
4373 e: &Engine,
4374 la: &LinearAttnLayer,
4375 g4: Vec<CudaSlice<f32>>,
4376 t: usize,
4377 cache: &mut Cache,
4378 il: usize,
4379 pad_len: Option<&CudaSlice<i32>>,
4380 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4381 let (gn, gn16) = self.linear_attn_prime_core_pad_inner(e, la, g4, t, cache, il, pad_len)?;
4382 if let Some(xh) = &gn16 {
4383 if let Some(y) = e.try_f16_gemm_pre(&la.ssm_out, xh, t)? {
4384 return Ok(y);
4385 }
4386 }
4387 Ok(e.matmul(&la.ssm_out, &gn, t)?)
4388 }
4389
4390 pub fn full_attn(
4395 &self,
4396 e: &Engine,
4397 fa: &FullAttnLayer,
4398 h: &CudaSlice<f32>,
4399 pos_d: &CudaSlice<i32>,
4400 t: usize,
4401 il: usize,
4402 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4403 if self.uses_sliding_gated_moe_program() {
4404 return self.step35_attn(e, fa, h, pos_d, t, il);
4405 }
4406 let cfg = &self.cfg;
4407 let _n_embd = cfg.n_embd as usize;
4408 let geometry = cfg.full_attention_geometry_at(il as u32);
4409 let n_head = geometry.n_head as usize;
4410 let n_head_kv = geometry.n_head_kv as usize;
4411 let head_dim = geometry.head_dim_k as usize;
4412 let eps = cfg.rms_eps;
4413 let scale = geometry.attention_scale();
4414
4415 let gated = geometry.attention_gate == memra_gguf::config::AttentionGateKind::FusedQ;
4418 let mut g3 = e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?;
4420 let v = g3.pop().unwrap();
4421 let mut k = g3.pop().unwrap();
4422 let qf = g3.pop().unwrap();
4423 let (mut q, gate) = if gated {
4424 let mut q = e.uninit(t * n_head * head_dim)?;
4425 let mut gate = e.uninit(t * n_head * head_dim)?;
4426 e.q_gate_split(&qf, &mut q, &mut gate, head_dim, n_head, t)?;
4427 (q, Some(gate))
4428 } else {
4429 (qf, None)
4430 };
4431
4432 let mut qn = e.uninit(t * n_head * head_dim)?;
4434 e.rms_norm(
4435 &q,
4436 fa.q_norm.float_data(),
4437 &mut qn,
4438 head_dim,
4439 n_head * t,
4440 eps,
4441 )?;
4442 q = qn;
4443 let mut kn = e.uninit(t * n_head_kv * head_dim)?;
4444 e.rms_norm(
4445 &k,
4446 fa.k_norm.float_data(),
4447 &mut kn,
4448 head_dim,
4449 n_head_kv * t,
4450 eps,
4451 )?;
4452 k = kn;
4453 let rope_dims = geometry.n_rot as usize;
4454 e.rope_neox(
4455 &mut q,
4456 pos_d,
4457 head_dim,
4458 rope_dims,
4459 n_head,
4460 t,
4461 geometry.rope_base,
4462 1.0,
4463 )?;
4464 e.rope_neox(
4465 &mut k,
4466 pos_d,
4467 head_dim,
4468 rope_dims,
4469 n_head_kv,
4470 t,
4471 geometry.rope_base,
4472 1.0,
4473 )?;
4474
4475 let mut attn = e.uninit(t * n_head * head_dim)?;
4477 if std::env::var("MEMRA_NOFA").is_ok() || !(head_dim == 256 || head_dim == 128) {
4480 e.sdpa_naive(
4482 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4483 )?;
4484 } else {
4485 e.fa_prefill(
4486 &q, &k, &v, &mut attn, head_dim, n_head, n_head_kv, t, t, scale, true,
4487 )?;
4488 }
4489
4490 let attn_g = match &gate {
4492 Some(gate) => {
4493 let mut gsig = e.uninit(t * n_head * head_dim)?;
4494 e.sigmoid(gate, &mut gsig, t * n_head * head_dim)?;
4495 let mut ag = e.uninit(t * n_head * head_dim)?;
4496 e.mul(&attn, &gsig, &mut ag, t * n_head * head_dim)?;
4497 ag
4498 }
4499 None => attn,
4500 };
4501
4502 let o = e.matmul(&fa.wo, &attn_g, t)?;
4504 Ok(o)
4505 }
4506
4507 pub fn linear_attn(
4509 &self,
4510 e: &Engine,
4511 la: &LinearAttnLayer,
4512 h: &CudaSlice<f32>,
4513 t: usize,
4514 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4515 let cfg = &self.cfg;
4516 let _n_embd = cfg.n_embd as usize;
4517 let geometry = la.geometry;
4518 let d_state = geometry.key_head_dim as usize;
4519 let num_k = geometry.key_heads as usize;
4520 let num_v = geometry.value_heads as usize;
4521 let d_conv = geometry.conv_kernel as usize;
4522 let head_k = d_state;
4523 let head_v = geometry.value_head_dim as usize;
4524 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;
4528 let scale = 1.0 / (d_state as f32).sqrt();
4529
4530 let mut g4 = e.matmul_group(
4533 &[&la.wqkv, &la.wqkv_gate, &la.ssm_beta, &la.ssm_alpha],
4534 h,
4535 t,
4536 )?;
4537 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);
4549 let mut q_g = e.uninit(d_state * num_v * t)?;
4550 let mut k_g = e.uninit(d_state * num_v * t)?;
4551 let mut v_g = e.uninit(d_state * num_v * t)?;
4552 e.ssm_conv1d_gdn(
4553 &qkv_mixed,
4554 la.ssm_conv1d.float_data(),
4555 &mut q_g,
4556 &mut k_g,
4557 &mut v_g,
4558 conv_dim,
4559 t,
4560 d_conv,
4561 d_state,
4562 num_v,
4563 num_k,
4564 key_dim,
4565 )?;
4566 let mut q_l2 = e.uninit(d_state * num_v * t)?;
4568 e.l2_norm(&q_g, &mut q_l2, d_state, num_v * t, eps)?;
4569 let mut k_l2 = e.uninit(d_state * num_v * t)?;
4570 e.l2_norm(&k_g, &mut k_l2, d_state, num_v * t, eps)?;
4571 let v_gd = v_g;
4572
4573 let mut beta = e.uninit(t * num_v)?;
4576 e.sigmoid(&beta_raw, &mut beta, t * num_v)?;
4577 let mut g_log = e.uninit(t * num_v)?;
4579 e.gdn_glog(
4580 &alpha,
4581 la.ssm_dt.float_data(),
4582 la.ssm_a.float_data(),
4583 &mut g_log,
4584 num_v,
4585 t,
4586 )?;
4587
4588 let state_in = e.zeros(d_state * d_state * num_v)?; let mut state_out = e.zeros(d_state * d_state * num_v)?;
4591 let mut o = e.uninit(d_state * num_v * t)?;
4592 e.gdn_scan_prefill(
4593 &q_l2,
4594 &k_l2,
4595 &v_gd,
4596 &g_log,
4597 &beta,
4598 None,
4599 None,
4600 &state_in,
4601 &mut state_out,
4602 &mut o,
4603 num_v,
4604 t,
4605 scale,
4606 num_v,
4607 )?;
4608
4609 let mut gn = e.uninit(d_state * num_v * t)?;
4614 e.gated_rmsnorm(
4615 &o,
4616 la.ssm_norm.float_data(),
4617 &z,
4618 &mut gn,
4619 d_state,
4620 num_v * t,
4621 eps,
4622 )?;
4623
4624 let out = e.matmul(&la.ssm_out, &gn, t)?;
4628 Ok(out)
4629 }
4630}
4631
4632impl HybridModel {
4633 pub fn moe_ffn_il(
4644 &self,
4645 e: &Engine,
4646 m: &MoeWeights,
4647 z: &CudaSlice<f32>,
4648 t: usize,
4649 il: u16,
4650 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4651 Self::moe_ffn_inner(
4652 e,
4653 m,
4654 z,
4655 None,
4656 t,
4657 &self.cfg,
4658 il,
4659 self.max_moe_block(),
4660 false,
4661 None,
4662 self.uses_sliding_gated_moe_program(),
4663 )
4664 }
4665
4666 pub fn moe_ffn_il_prefill(
4669 &self,
4670 e: &Engine,
4671 m: &MoeWeights,
4672 z: &CudaSlice<f32>,
4673 t: usize,
4674 il: u16,
4675 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4676 Self::moe_ffn_inner(
4677 e,
4678 m,
4679 z,
4680 None,
4681 t,
4682 &self.cfg,
4683 il,
4684 self.max_moe_block(),
4685 true,
4686 Some(&self.step_grouped_prefill),
4687 self.uses_sliding_gated_moe_program(),
4688 )
4689 }
4690
4691 pub fn moe_ffn_il_zq8(
4695 &self,
4696 e: &Engine,
4697 m: &MoeWeights,
4698 z: &CudaSlice<f32>,
4699 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4700 t: usize,
4701 il: u16,
4702 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4703 Self::moe_ffn_inner(
4704 e,
4705 m,
4706 z,
4707 zq8,
4708 t,
4709 &self.cfg,
4710 il,
4711 self.max_moe_block(),
4712 false,
4713 None,
4714 self.uses_sliding_gated_moe_program(),
4715 )
4716 }
4717
4718 pub(crate) fn moe_ffn(
4726 e: &Engine,
4727 m: &MoeWeights,
4728 z: &CudaSlice<f32>,
4729 t: usize,
4730 cfg: &ModelConfig,
4731 il: u16,
4732 max_block: usize,
4733 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4734 Self::moe_ffn_inner(e, m, z, None, t, cfg, il, max_block, false, None, false)
4735 }
4736
4737 #[allow(clippy::too_many_arguments)]
4738 pub(crate) fn moe_ffn_inner(
4739 e: &Engine,
4740 m: &MoeWeights,
4741 z: &CudaSlice<f32>,
4742 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
4743 t: usize,
4744 cfg: &ModelConfig,
4745 il: u16,
4746 max_block: usize,
4747 prefill: bool,
4748 grouped_prefill: Option<&std::sync::Mutex<crate::hybrid::StepEpGroupedPrefill>>,
4749 sliding_gated_moe: bool,
4750 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4751 let worker_io = crate::spill_pread::worker_enabled();
4752 let epoch_lfu = std::env::var_os("MEMRA_MOE_LFU_DECAY").is_some();
4753 if Engine::moe_cache_enabled() && (worker_io || epoch_lfu) {
4754 e.with_moe_cache(max_block, |cache, _| {
4755 cache.begin_forward_epoch(il, t);
4756 if worker_io {
4757 cache.begin_worker_scope();
4758 }
4759 Ok(())
4760 })?;
4761 }
4762 if m.step_ep.is_some() || m.step_tp.is_some() {
4763 let moe = cfg
4764 .moe
4765 .as_ref()
4766 .ok_or("Step distributed execution requires MoE model metadata")?;
4767 let n_embd = cfg.n_embd as usize;
4768 let n_expert = moe.expert_count as usize;
4769 let n_used = moe.expert_used_count as usize;
4770 let sigmoid = cfg
4771 .sigmoid_router()
4772 .ok_or("Step distributed execution requires the Step sigmoid router")?;
4773 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
4774 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
4775 let grouped_prefill_requested = prefill && step_ep_grouped_prefill_enabled()?;
4776 if grouped_prefill_requested && !step_tp_prefill_enabled()? {
4777 return Err(
4778 "MEMRA_STEP_EP_GROUPED_PREFILL=1 requires MEMRA_STEP_TP_PREFILL=1".into(),
4779 );
4780 }
4781 if grouped_prefill_requested && !step_grouped_prefill_shape(true, prefill, t) {
4782 return Err(format!(
4783 "Step grouped prefill tokens {t} are outside the qualified {}..={} range",
4784 PRIME_MIN_T,
4785 crate::cache::PRIME_CHUNK_MAX_TOKENS,
4786 )
4787 .into());
4788 }
4789 let grouped_decode_shape = step_grouped_decode_shape(prefill, t);
4790 let grouped_prefill_shape =
4791 step_grouped_prefill_shape(grouped_prefill_requested, prefill, t);
4792 if let Some(ep) = m.step_ep.as_ref().filter(|ep| {
4793 ep.grouped_decode.is_some() && (grouped_decode_shape || grouped_prefill_shape)
4794 }) {
4795 let (selected, route_weights) =
4796 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sigmoid)?;
4797 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
4798 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
4799 Self::trace_moe_input(e, il, t, n_embd, z)?;
4800 let selected = selected
4801 .iter()
4802 .map(|&expert| expert as usize)
4803 .collect::<Vec<_>>();
4804
4805 e.stream().synchronize()?;
4808 let execute = |state: &mut crate::hybrid::StepEpGroupedDecode| {
4809 state.projection.set_activation_limit(ep.activation_limit)?;
4810 ep.runtime
4811 .refresh_step_grouped_expert_parallel_gate_from_root_device(
4812 ep.experts.e4m3()?,
4813 &mut state.projection,
4814 z,
4815 t,
4816 &selected,
4817 )?;
4818 ep.runtime.refresh_step_grouped_expert_parallel_combine(
4819 &state.projection,
4820 &mut state.combine,
4821 &route_weights,
4822 )?;
4823 ep.runtime.execute_step_grouped_expert_parallel_gate(
4824 ep.experts.e4m3()?,
4825 &mut state.projection,
4826 )?;
4827 ep.runtime.execute_step_grouped_expert_parallel_combine(
4828 &state.projection,
4829 &mut state.combine,
4830 )?;
4831 let mut output = ep.runtime.copy_step_grouped_expert_parallel_combine_root(
4832 &state.projection,
4833 &state.combine,
4834 e,
4835 )?;
4836 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
4837 if prefill {
4838 e.stream().synchronize()?;
4841 }
4842 eprintln!(
4843 "[step-tp-ep-grouped] execute layer={il} tokens={t} devices={:?} \
4844 attention_layout=tensor-parallel expert_layout=expert-parallel \
4845 expert_transport={} native_p2p=true route_control=host-narrow \
4846 input=root-device projection_workspaces=persistent \
4847 combine=root-device output=owning-stage-device \
4848 prefill={prefill} batched_decode=false capacity={} \
4849 performance_claim=false",
4850 ep.devices,
4851 ep.runtime.transport_label(),
4852 state.projection.max_tokens(),
4853 );
4854 Ok::<_, Box<dyn std::error::Error>>(output)
4855 };
4856
4857 if grouped_prefill_shape {
4858 let grouped_prefill = grouped_prefill
4859 .ok_or("Step grouped prefill has no model-scoped executor")?;
4860 let mut shared = grouped_prefill
4861 .lock()
4862 .map_err(|_| "Step grouped prefill state lock is poisoned")?;
4863 let needs_prepare = shared.state.as_ref().is_none_or(|state| {
4864 state.devices != ep.devices
4865 || state.grouped.projection.max_tokens() < t
4866 || state.grouped.projection.input_width() != n_embd
4867 || state.grouped.projection.expert_width()
4868 != moe.expert_ff_length as usize
4869 });
4870 if needs_prepare {
4871 let seed_input = vec![0.0f32; n_embd];
4872 let seed_selected = &selected[..n_used];
4873 let seed_weights = &route_weights[..n_used];
4874 let projection = ep
4875 .runtime
4876 .prepare_step_grouped_expert_parallel_gate_with_capacity(
4877 ep.experts.e4m3()?,
4878 &seed_input,
4879 1,
4880 seed_selected,
4881 ep.activation_limit,
4882 t,
4883 )?;
4884 let combine = ep.runtime.prepare_step_grouped_expert_parallel_combine(
4885 &projection,
4886 seed_weights,
4887 )?;
4888 shared.state = Some(crate::hybrid::StepEpGroupedPrefillState {
4889 devices: ep.devices.clone(),
4890 grouped: crate::hybrid::StepEpGroupedDecode {
4891 projection,
4892 combine,
4893 },
4894 });
4895 eprintln!(
4896 "[step-tp-ep-grouped-prefill] prepare capacity={t} devices={:?} \
4897 shared_across_layers=true performance_claim=false",
4898 ep.devices,
4899 );
4900 }
4901 return execute(
4902 &mut shared
4903 .state
4904 .as_mut()
4905 .expect("Step grouped prefill state prepared above")
4906 .grouped,
4907 );
4908 }
4909
4910 let mut grouped = ep
4911 .grouped_decode
4912 .as_ref()
4913 .expect("grouped decode presence checked above")
4914 .lock()
4915 .map_err(|_| "Step grouped decode state lock is poisoned")?;
4916 return execute(&mut grouped);
4917 }
4918 if grouped_prefill_shape {
4919 return Err(
4920 "Step grouped prefill requires native-P2P expert-owner device arithmetic"
4921 .into(),
4922 );
4923 }
4924 if t == 1
4929 && crate::tp::step_nvfp4_dev_routes_enabled()?
4930 && crate::tp::step_tp_dev_router_enabled()?
4931 {
4932 if let Some(tp) = &m.step_tp {
4933 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
4934 let (sf, route_norm) = sigmoid;
4935 static D1_ROUTER: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4942 let d1_router = *D1_ROUTER.get_or_init(|| {
4943 std::env::var("MEMRA_DEV1_ROUTER").as_deref() == Ok("1")
4944 });
4945 if d1_router {
4946 let (sf_h, rn_h) = sigmoid;
4947 let n_ex = m.gate_exps.n_expert;
4948 let act_ct = m.active_count();
4949 let _ = tp.runtime.nvfp4_routes_prestage_with(
4950 bank,
4951 e,
4952 z,
4953 |rank1, in1, sel1, w1| {
4954 let mut guard = DEV1_ROUTER_REPS
4955 .lock()
4956 .map_err(|_| "dev1 router replica lock")?;
4957 let (reps, scratch) =
4958 guard.get_or_insert_with(|| (Default::default(), None));
4959 if !reps.contains_key(&il) {
4960 use cudarc::driver::DevicePtr;
4961 let (g1, p1, a1) = (
4962 rank1.htod(&vec![0.0f32; n_ex * n_embd])?,
4963 rank1.htod(&vec![0.0f32; n_ex])?,
4964 rank1.alloc_u8_uninit(n_ex)?,
4965 );
4966 for (src, dst_len, dst) in [
4967 (
4968 {
4969 let s = e.stream();
4970 let (p, _g) =
4971 m.gate_inp.float_data().device_ptr(&s);
4972 p as u64
4973 },
4974 n_ex * n_embd * 4,
4975 {
4976 let s = rank1.stream();
4977 let (p, _g) = g1.device_ptr(&s);
4978 p as u64
4979 },
4980 ),
4981 (
4982 {
4983 let s = e.stream();
4984 let (p, _g) = m.exp_probs_b_dev.device_ptr(&s);
4985 p as u64
4986 },
4987 n_ex * 4,
4988 {
4989 let s = rank1.stream();
4990 let (p, _g) = p1.device_ptr(&s);
4991 p as u64
4992 },
4993 ),
4994 (
4995 {
4996 let s = e.stream();
4997 let (p, _g) =
4998 m.active_experts_dev.device_ptr(&s);
4999 p as u64
5000 },
5001 n_ex,
5002 {
5003 let s = rank1.stream();
5004 let (p, _g) = a1.device_ptr(&s);
5005 p as u64
5006 },
5007 ),
5008 ] {
5009 crate::tp::raw_copy_bytes(dst, src, dst_len, rank1)?;
5010 }
5011 rank1.stream().synchronize()?;
5012 reps.insert(il, (g1, p1, a1));
5013 }
5014 if scratch.is_none() {
5015 *scratch = Some(rank1.htod(&vec![0.0f32; n_ex])?);
5016 }
5017 let (g1, p1, a1) = reps.get(&il).expect("armed above");
5018 let logits1 = scratch.as_mut().expect("armed above");
5019 rank1.router_gemv_into(g1, in1, logits1, n_embd, n_ex, 1)?;
5020 rank1.moe_router_sigmoid_topk_into(
5021 logits1, 1, n_ex, n_used, act_ct, p1, a1, sf_h, rn_h, sel1,
5022 w1,
5023 )?;
5024 Ok(true)
5025 },
5026 )?;
5027 } else {
5028 let _ = tp.runtime.nvfp4_routes_prestage(bank, e, z)?;
5029 }
5030 static SELW: std::sync::Mutex<
5034 Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>,
5035 > = std::sync::Mutex::new(None);
5036 let mut selw = SELW.lock().map_err(|_| "selw lock poisoned")?;
5037 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
5038 *selw = Some((
5039 e.ctx().ordinal(),
5040 e.htod_i32(&vec![0i32; n_used])?,
5041 e.htod(&vec![0.0f32; n_used])?,
5042 ));
5043 }
5044 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
5045 e.moe_router_sigmoid_topk_into(
5046 &logits,
5047 t,
5048 n_expert,
5049 n_used,
5050 m.active_count(),
5051 &m.exp_probs_b_dev,
5052 &m.active_experts_dev,
5053 sf,
5054 route_norm,
5055 sel_d,
5056 w_d,
5057 )?;
5058 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
5059 static SHEXP_OV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5063 let shexp_ov = *SHEXP_OV.get_or_init(|| {
5064 std::env::var("MEMRA_SHEXP_OVERLAP").as_deref() == Ok("1")
5065 });
5066 static SHEXP_D1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5070 let shexp_d1 = *SHEXP_D1.get_or_init(|| {
5071 std::env::var("MEMRA_SHEXP_DEV1").as_deref() == Ok("1")
5072 }) && tp.runtime.rank_engine(1).is_some();
5073 static TAIL3: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5077 let tail3 = *TAIL3
5078 .get_or_init(|| std::env::var("MEMRA_TAIL_ADD3").as_deref() != Ok("0"));
5079 let mut ov_issued = false;
5080 let mut d1_issued = false;
5081 let mut tail_folded = false;
5082 let mut output = if shexp_d1 {
5083 let rank1 = tp.runtime.rank_engine(1).expect("checked above");
5084 tp.runtime
5085 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
5086 bank,
5087 e,
5088 z,
5089 &sel_d,
5090 &w_d,
5091 n_used,
5092 tp.activation_limit,
5093 || {
5094 d1_issued = Self::shexp_dev1_issue(
5095 e, rank1, m, z, cfg, il, n_embd,
5096 )?;
5097 Ok(())
5098 },
5099 )?
5100 } else if shexp_ov {
5101 let post_add = if tail3 {
5106 Self::shexp_overlap_tail_ptrs(e, m, cfg, n_embd)?
5107 } else {
5108 None
5109 };
5110 let used_post = post_add.is_some();
5111 let out = tp
5112 .runtime
5113 .run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
5114 bank,
5115 e,
5116 z,
5117 &sel_d,
5118 &w_d,
5119 n_used,
5120 tp.activation_limit,
5121 || {
5122 ov_issued =
5123 Self::shexp_overlap_issue(e, m, z, cfg, il, n_embd)?;
5124 Ok(())
5125 },
5126 post_add,
5127 )?;
5128 if used_post && ov_issued {
5133 tail_folded = true; }
5135 out
5136 } else {
5137 tp.runtime.run_tensor_parallel_routes_nvfp4_device_routed(
5138 bank,
5139 e,
5140 z,
5141 &sel_d,
5142 &w_d,
5143 n_used,
5144 tp.activation_limit,
5145 )?
5146 };
5147 if output.len() != t * n_embd {
5148 return Err(format!(
5149 "Step tp routed output has {} values, expected {t}x{n_embd}",
5150 output.len()
5151 )
5152 .into());
5153 }
5154 if tail_folded {
5155 } else if d1_issued {
5157 Self::shexp_dev1_apply(e, &mut output, n_embd)?;
5158 } else if ov_issued {
5159 Self::shexp_overlap_apply(e, &mut output, n_embd)?;
5160 } else {
5161 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5162 }
5163 static DR_LOGGED: std::sync::atomic::AtomicU64 =
5164 std::sync::atomic::AtomicU64::new(0);
5165 let layer_bit = 1u64 << (il as u64 % 64);
5166 if DR_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5167 & layer_bit
5168 == 0
5169 {
5170 eprintln!(
5171 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5172 expert_transport={} native_p2p={} router=device \
5173 activation=host-canonical accumulation=host-canonical \
5174 output=e-device io=device performance_claim=false \
5175 (logged once per layer)",
5176 tp.devices,
5177 tp.runtime.transport_label(),
5178 tp.runtime.native_p2p(),
5179 );
5180 }
5181 return Ok(output);
5182 }
5183 }
5184 }
5185 static ROUTE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5189 static ROUTE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5190 let route_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
5191 let route_started = route_timing.then(std::time::Instant::now);
5192 let (selected, route_weights, input) = Self::moe_route_sigmoid_with_input(
5193 e,
5194 &logits,
5195 z,
5196 t,
5197 n_embd,
5198 n_expert,
5199 n_used,
5200 m.exp_probs_b.as_deref(),
5201 sigmoid,
5202 m.active_experts.as_deref(),
5203 )?;
5204 if let Some(started) = route_started {
5205 use std::sync::atomic::Ordering;
5206 let ns = ROUTE_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
5207 + started.elapsed().as_nanos() as u64;
5208 let calls = ROUTE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
5209 if calls % 430 == 0 {
5210 eprintln!(
5211 "[moe-route-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
5212 ns as f64 / 1.0e6,
5213 ns as f64 / calls as f64 / 1.0e3,
5214 );
5215 }
5216 }
5217 crate::moesd::record_host_routes(il, n_expert, n_used, &selected)?;
5218 Self::trace_moe_routes(il, t, &selected, &route_weights)?;
5219 Self::trace_moe_input(e, il, t, n_embd, z)?;
5220 let selected = selected
5221 .iter()
5222 .map(|&expert| expert as usize)
5223 .collect::<Vec<_>>();
5224 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5229 if let Some(tp) = &m.step_tp {
5230 if let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts {
5231 let mut output = tp.runtime.run_tensor_parallel_routes_nvfp4_device_io(
5232 bank,
5233 e,
5234 z,
5235 &selected,
5236 &route_weights,
5237 n_used,
5238 tp.activation_limit,
5239 )?;
5240 if output.len() != t * n_embd {
5241 return Err(format!(
5242 "Step tp routed output has {} values, expected {t}x{n_embd}",
5243 output.len()
5244 )
5245 .into());
5246 }
5247 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5248 static IO_LOGGED: std::sync::atomic::AtomicU64 =
5249 std::sync::atomic::AtomicU64::new(0);
5250 let layer_bit = 1u64 << (il as u64 % 64);
5251 if IO_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed)
5252 & layer_bit
5253 == 0
5254 {
5255 eprintln!(
5256 "[step-tp] execute layer={il} tokens={t} devices={:?} \
5257 expert_transport={} native_p2p={} activation=host-canonical \
5258 accumulation=host-canonical output=e-device io=device \
5259 performance_claim=false (logged once per layer)",
5260 tp.devices,
5261 tp.runtime.transport_label(),
5262 tp.runtime.native_p2p(),
5263 );
5264 }
5265 return Ok(output);
5266 }
5267 }
5268 }
5269 let (routed, mode, devices, transport, native_p2p) = if let Some(tp) = &m.step_tp {
5270 (
5271 match &tp.experts {
5272 crate::hybrid::StepTpExpertBank::E4m3(bank) => {
5273 tp.runtime.run_tensor_parallel_routes(
5274 bank,
5275 &input,
5276 t,
5277 &selected,
5278 &route_weights,
5279 n_used,
5280 )?
5281 }
5282 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => {
5283 if t == 1 && crate::tp::step_nvfp4_dev_routes_enabled()? {
5284 tp.runtime.run_tensor_parallel_routes_nvfp4_device(
5285 bank,
5286 &input,
5287 &selected,
5288 &route_weights,
5289 n_used,
5290 tp.activation_limit,
5291 )?
5292 } else {
5293 tp.runtime.run_tensor_parallel_routes_nvfp4(
5294 bank,
5295 &input,
5296 t,
5297 &selected,
5298 &route_weights,
5299 n_used,
5300 tp.activation_limit,
5301 )?
5302 }
5303 }
5304 },
5305 "tp",
5306 &tp.devices,
5307 tp.runtime.transport_label(),
5308 tp.runtime.native_p2p(),
5309 )
5310 } else {
5311 let ep = m
5312 .step_ep
5313 .as_ref()
5314 .ok_or("Step distributed runtime has no EP or TP state")?;
5315 (
5316 match &ep.experts {
5317 crate::hybrid::StepEpExpertBank::E4m3(bank) => {
5318 ep.runtime.run_routed_experts(
5319 bank,
5320 &input,
5321 t,
5322 &selected,
5323 &route_weights,
5324 n_used,
5325 ep.activation_limit,
5326 )?
5327 }
5328 crate::hybrid::StepEpExpertBank::Nvfp4(bank) => {
5329 ep.runtime.run_routed_experts_nvfp4(
5330 bank,
5331 &input,
5332 t,
5333 &selected,
5334 &route_weights,
5335 n_used,
5336 ep.activation_limit,
5337 )?
5338 }
5339 },
5340 if ep.configured_by_tp { "tp-ep" } else { "ep" },
5341 &ep.devices,
5342 ep.runtime.transport_label(),
5343 ep.runtime.native_p2p(),
5344 )
5345 };
5346 if routed.len() != t * n_embd {
5347 return Err(format!(
5348 "Step {mode} routed output has {} values, expected {t}x{n_embd}",
5349 routed.len()
5350 )
5351 .into());
5352 }
5353 let mut output = e.htod(&routed)?;
5354 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut output)?;
5355 static STEP_LOGGED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
5358 let layer_bit = 1u64 << (il as u64 % 64);
5359 if STEP_LOGGED.fetch_or(layer_bit, std::sync::atomic::Ordering::Relaxed) & layer_bit
5360 == 0
5361 {
5362 eprintln!(
5363 "[step-{mode}] execute layer={il} tokens={t} devices={devices:?} \
5364 expert_transport={transport} native_p2p={native_p2p} \
5365 activation={} accumulation={} output={} \
5366 performance_claim=false (logged once per layer)",
5367 if let Some(ep) = &m.step_ep {
5368 ep.runtime.expert_activation_label()
5369 } else {
5370 "host-canonical"
5371 },
5372 if let Some(ep) = &m.step_ep {
5373 ep.runtime.expert_accumulation_label()
5374 } else {
5375 "host-canonical"
5376 },
5377 if let Some(ep) = &m.step_ep {
5378 ep.runtime.expert_output_label()
5379 } else {
5380 "host-accumulated"
5381 },
5382 );
5383 if let Some(ep) = &m.step_ep {
5384 if let Some(limit) = ep.activation_limit {
5385 eprintln!(
5386 "[step-ep-clamp] execute layer={il} tokens={t} routed_clamp={limit} \
5387 formula=min-silu-times-clamped-up performance_claim=false"
5388 );
5389 }
5390 }
5391 }
5392 return Ok(output);
5393 }
5394 if Self::sigmoid_resident_dev_eligible(e, m, cfg, sliding_gated_moe) {
5395 let moe = cfg.moe.as_ref().unwrap();
5396 let n_expert = moe.expert_count as usize;
5397 let n_used = moe.expert_used_count as usize;
5398 let sigmoid = cfg.sigmoid_router().unwrap();
5399 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5400 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sigmoid)?;
5401 return Self::moe_ffn_sigmoid_dev(e, m, z, zq8, &logits, t, cfg, il, sigmoid);
5402 }
5403 if t > 1 && moe_grouped_enabled(cfg, prefill) {
5406 let grouped_out = Self::moe_ffn_grouped(e, m, z, t, cfg, il, max_block)?;
5407 if std::env::var("MEMRA_MOE_GATE").is_ok() {
5412 let seq_out = Self::moe_ffn_sequential(e, m, z, t, cfg, il, max_block)?;
5413 let g_host = e.dtoh(&grouped_out)?;
5414 let s_host = e.dtoh(&seq_out)?;
5415 let g_bytes: &[u8] = unsafe {
5416 std::slice::from_raw_parts(g_host.as_ptr() as *const u8, g_host.len() * 4)
5417 };
5418 let s_bytes: &[u8] = unsafe {
5419 std::slice::from_raw_parts(s_host.as_ptr() as *const u8, s_host.len() * 4)
5420 };
5421 if g_bytes == s_bytes {
5422 println!("moe-gate il={il} t={t} BYTE-IDENTICAL");
5423 } else {
5424 let diffs = g_host
5425 .iter()
5426 .zip(s_host.iter())
5427 .enumerate()
5428 .filter(|(_, (a, b))| a != b)
5429 .count();
5430 let maxdiff = g_host
5431 .iter()
5432 .zip(s_host.iter())
5433 .map(|(a, b)| (a - b).abs())
5434 .fold(0.0f32, f32::max);
5435 panic!(
5436 "moe-gate il={il} t={t} MISMATCH: {diffs}/{} elems differ, maxdiff={maxdiff:.6e}",
5437 g_host.len()
5438 );
5439 }
5440 }
5441 return Ok(grouped_out);
5442 }
5443 Self::moe_ffn_sequential_zq8(e, m, z, zq8, t, cfg, il, max_block)
5444 }
5445
5446 fn sigmoid_resident_dev_eligible(
5447 e: &Engine,
5448 m: &MoeWeights,
5449 cfg: &ModelConfig,
5450 sliding_gated_moe: bool,
5451 ) -> bool {
5452 let Some(moe) = cfg.moe.as_ref() else {
5453 return false;
5454 };
5455 static OBSERVATION_MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5458 let observation_mode = *OBSERVATION_MODE.get_or_init(|| {
5459 std::env::var("MEMRA_MOE_STATS").is_ok()
5460 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5461 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5462 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok()
5463 || std::env::var("MEMRA_MOE_GATE").is_ok()
5464 });
5465 let resident_layout_supported = m.dev_exps.as_ref().is_some_and(|dev| {
5466 if dev.dev != e.ctx().ordinal() {
5467 return false;
5468 }
5469 let q8 = moe_q8_enabled()
5470 && q8_expert_supported(m.gate_exps.qtype)
5471 && q8_expert_supported(m.up_exps.qtype)
5472 && q8_expert_supported(m.down_exps.qtype);
5473 let fp8 = dev.fp8_blk.is_some()
5474 && m.gate_exps.qtype == crate::QT_F8_E4M3_BLK
5475 && m.up_exps.qtype == crate::QT_F8_E4M3_BLK
5476 && m.down_exps.qtype == crate::QT_F8_E4M3_BLK;
5477 q8 || fp8
5478 });
5479 sliding_gated_moe
5480 && sigmoid_router_enabled()
5481 && moe_dev_enabled()
5482 && moe_slab_enabled()
5483 && !observation_mode
5484 && moe.expert_used_count <= 8
5485 && m.has_uniform_expert_layout()
5486 && m.gate_exps.macros.is_none()
5487 && m.up_exps.macros.is_none()
5488 && m.down_exps.macros.is_none()
5489 && !m.has_macros
5490 && resident_layout_supported
5491 }
5492
5493 pub(crate) fn moe_ffn_sequential(
5495 e: &Engine,
5496 m: &MoeWeights,
5497 z: &CudaSlice<f32>,
5498 t: usize,
5499 cfg: &ModelConfig,
5500 il: u16,
5501 max_block: usize,
5502 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5503 Self::moe_ffn_sequential_zq8(e, m, z, None, t, cfg, il, max_block)
5504 }
5505
5506 fn moe_router_logits(
5510 e: &Engine,
5511 m: &MoeWeights,
5512 z: &CudaSlice<f32>,
5513 t: usize,
5514 cfg: &ModelConfig,
5515 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5516 if t < PRIME_MIN_T {
5517 if crate::router_kernel_on() {
5519 e.router_gemv(
5520 m.gate_inp.float_data(),
5521 z,
5522 cfg.n_embd as usize,
5523 m.gate_exps.n_expert,
5524 t,
5525 )
5526 } else {
5527 e.matmul_decode_exact(&m.gate_inp, z, t)
5528 }
5529 } else if crate::router_prefill_exact_on() && crate::router_kernel_on() {
5530 e.router_gemv(
5531 m.gate_inp.float_data(),
5532 z,
5533 cfg.n_embd as usize,
5534 m.gate_exps.n_expert,
5535 t,
5536 )
5537 } else {
5538 e.matmul(&m.gate_inp, z, t)
5539 }
5540 }
5541
5542 fn trace_moe_routes(
5546 il: u16,
5547 t: usize,
5548 sel_all: &[u32],
5549 weights: &[f32],
5550 ) -> Result<(), Box<dyn std::error::Error>> {
5551 use std::io::Write as _;
5552 if let Ok(path) = std::env::var("MEMRA_MOE_TRACE") {
5553 let mut f = std::fs::OpenOptions::new()
5554 .create(true)
5555 .append(true)
5556 .open(path)?;
5557 let ids: Vec<String> = sel_all.iter().map(|s| s.to_string()).collect();
5558 writeln!(f, "{} {} {}", il, t, ids.join(","))?;
5559 }
5560 if let Ok(path) = std::env::var("MEMRA_MOE_WEIGHT_TRACE") {
5561 let mut f = std::fs::OpenOptions::new()
5562 .create(true)
5563 .append(true)
5564 .open(path)?;
5565 let pairs: Vec<String> = sel_all
5566 .iter()
5567 .zip(weights)
5568 .map(|(expert, weight)| format!("{expert}:{weight:.9}"))
5569 .collect();
5570 writeln!(f, "{} {} {}", il, t, pairs.join(","))?;
5571 }
5572 Ok(())
5573 }
5574
5575 #[allow(clippy::too_many_arguments)]
5576 fn trace_sigmoid_router_logits(
5577 e: &Engine,
5578 il: u16,
5579 t: usize,
5580 n_expert: usize,
5581 n_used: usize,
5582 logits: &CudaSlice<f32>,
5583 m: &MoeWeights,
5584 (scaling_factor, route_norm): (f32, bool),
5585 ) -> Result<(), Box<dyn std::error::Error>> {
5586 if !crate::sigrouter_contract::served_logit_trace_enabled() || t != 1 {
5587 return Ok(());
5588 }
5589 let logits = e.dtoh(logits)?;
5590 let active: Vec<u8> = m
5591 .active_experts
5592 .as_ref()
5593 .map(|mask| mask.iter().map(|&enabled| u8::from(enabled)).collect())
5594 .unwrap_or_else(|| vec![1; n_expert]);
5595 let bias = m.exp_probs_b.clone().unwrap_or_else(|| vec![0.0; n_expert]);
5596 crate::sigrouter_contract::capture_served_logits(
5597 il as u32,
5598 t,
5599 n_expert,
5600 n_used,
5601 scaling_factor,
5602 route_norm,
5603 &active,
5604 &bias,
5605 &logits,
5606 )?;
5607 Ok(())
5608 }
5609
5610 fn trace_moe_input(
5615 e: &Engine,
5616 il: u16,
5617 t: usize,
5618 n_embd: usize,
5619 z: &CudaSlice<f32>,
5620 ) -> Result<(), Box<dyn std::error::Error>> {
5621 use std::io::Write as _;
5622 let Ok(dir) = std::env::var("MEMRA_MOE_INPUT_TRACE_DIR") else {
5623 return Ok(());
5624 };
5625 let values = active_matrix_values(z.len(), t, n_embd, "MoE input trace activation")?;
5626 let host = e.dtoh_view(&z.slice(0..values))?;
5627 let bytes = unsafe {
5628 std::slice::from_raw_parts(
5629 host.as_ptr().cast::<u8>(),
5630 host.len() * std::mem::size_of::<f32>(),
5631 )
5632 };
5633 let state = MOE_INPUT_TRACE_WRITER.get_or_init(|| std::sync::Mutex::new(None));
5634 let mut state = state
5635 .lock()
5636 .map_err(|_| "MoE input trace writer lock is poisoned")?;
5637 if state.is_none() {
5638 let dir = std::path::PathBuf::from(&dir);
5639 std::fs::create_dir_all(&dir)?;
5640 let index = std::fs::OpenOptions::new()
5641 .create(true)
5642 .append(true)
5643 .open(dir.join("index.jsonl"))?;
5644 *state = Some(MoeInputTraceWriter {
5645 dir,
5646 index,
5647 payloads: std::collections::HashMap::new(),
5648 });
5649 }
5650 let writer = state.as_mut().unwrap();
5651 if writer.dir != std::path::Path::new(&dir) {
5652 return Err("MEMRA_MOE_INPUT_TRACE_DIR changed after capture started".into());
5653 }
5654 let file_name = format!("layer-{il:03}.f32");
5655 if !writer.payloads.contains_key(&il) {
5656 let payload = std::fs::OpenOptions::new()
5657 .create(true)
5658 .append(true)
5659 .open(writer.dir.join(&file_name))?;
5660 let offset = payload.metadata()?.len();
5661 writer.payloads.insert(il, (payload, offset));
5662 }
5663 let (payload, offset) = writer.payloads.get_mut(&il).unwrap();
5664 let row_offset = *offset;
5665 payload.write_all(bytes)?;
5666 *offset += bytes.len() as u64;
5667 writeln!(
5668 writer.index,
5669 "{{\"format\":\"memra-moe-input-trace-v1\",\"layer\":{il},\"tokens\":{t},\
5670 \"hidden_size\":{n_embd},\"file\":\"{file_name}\",\"offset\":{row_offset},\
5671 \"payload_bytes\":{}}}",
5672 bytes.len()
5673 )?;
5674 Ok(())
5675 }
5676
5677 #[allow(clippy::too_many_arguments)]
5678 pub(crate) fn moe_ffn_sequential_zq8(
5679 e: &Engine,
5680 m: &MoeWeights,
5681 z: &CudaSlice<f32>,
5682 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
5683 t: usize,
5684 cfg: &ModelConfig,
5685 il: u16,
5686 max_block: usize,
5687 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5688 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
5689 let moe = cfg.moe.as_ref().unwrap();
5690 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);
5697 debug_assert_eq!(m.gate_exps.out_f, n_ff_exp);
5698 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);
5701
5702 let lim_exp = cfg.clamp_exp_at(il as u32);
5705 let lim_shexp = cfg.clamp_shexp_at(il as u32);
5706 let use_cache = Engine::moe_cache_enabled();
5707 let uniform_experts = m.has_uniform_expert_layout();
5708 let moe_q8 = uniform_experts
5709 && moe_q8_enabled()
5710 && q8_expert_supported(m.gate_exps.qtype)
5711 && q8_expert_supported(m.up_exps.qtype)
5712 && q8_expert_supported(m.down_exps.qtype);
5713 let cpu_expert_requested = crate::cpu_experts::configured();
5720 if cpu_expert_requested && (cfg.hy3.is_none() || cfg.m3.is_some()) {
5721 return Err(std::io::Error::other(
5722 "MEMRA_CPU_EXPERT_LIB is experimental and currently gated to Hy3",
5723 )
5724 .into());
5725 }
5726 let cpu_hybrid = cpu_expert_requested && t < PRIME_MIN_T && m.dev_exps.is_none();
5727 let freeze_cpu_residency = cpu_expert_requested
5733 && std::env::var("MEMRA_CPU_EXPERT_FREEZE_CACHE").as_deref() == Ok("1");
5734 let caller_warms_before_freeze = std::env::var("MEMRA_CPU_EXPERT_FREEZE_WARMUP_TOKENS")
5735 .ok()
5736 .and_then(|value| value.parse::<usize>().ok())
5737 .is_some_and(|tokens| tokens > 0);
5738 if cpu_hybrid && freeze_cpu_residency && !caller_warms_before_freeze {
5739 e.freeze_moe_cache();
5740 }
5741 let cache_frozen = use_cache && e.moe_cache_frozen();
5742 let cache_dispatch = use_cache && (!cache_frozen || cpu_hybrid);
5743
5744 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
5747 if let Some(sig) = cfg.sigmoid_router() {
5748 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
5749 }
5750
5751 let no_exp_macros = m.gate_exps.macros.is_none()
5790 && m.up_exps.macros.is_none()
5791 && m.down_exps.macros.is_none();
5792 if cfg.sigmoid_router().is_none()
5796 && cfg.m3.is_none()
5797 && cfg.hy3.is_none()
5798 && !cfg.swiglu_clamped_at(il as u32)
5799 && no_exp_macros
5800 && t > MOE_DEV_MAX_T
5804 && m.dev_exps.is_some()
5805 && moe_q8_enabled()
5806 && q8_expert_supported(m.gate_exps.qtype)
5807 && q8_expert_supported(m.up_exps.qtype)
5808 && q8_expert_supported(m.down_exps.qtype)
5809 && std::env::var("MEMRA_MOE_PAIRS")
5810 .map(|v| v != "0")
5811 .unwrap_or(true)
5812 && std::env::var("MEMRA_MOE_STATS").is_err()
5813 {
5814 return Self::moe_ffn_pairs(e, m, z, &logits, t, cfg);
5815 }
5816
5817 let dev_ok = uniform_experts
5835 && cfg.sigmoid_router().is_none()
5836 && cfg.m3.is_none()
5837 && cfg.hy3.is_none()
5838 && !cfg.swiglu_clamped_at(il as u32);
5839 let observe_routes = std::env::var("MEMRA_MOE_STATS").is_ok()
5843 || std::env::var("MEMRA_MOE_TRACE").is_ok()
5844 || std::env::var("MEMRA_MOE_WEIGHT_TRACE").is_ok()
5845 || std::env::var("MEMRA_MOE_INPUT_TRACE_DIR").is_ok();
5846 if dev_ok
5847 && t <= MOE_DEV_MAX_T
5848 && m.dev_exps.is_some()
5849 && n_used <= 8
5850 && moe_dev_enabled()
5851 && !observe_routes
5852 {
5853 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5854 }
5855 if dev_ok && use_cache && n_used <= 8 && moe_dev_enabled() && !observe_routes {
5856 let row_ok = e.with_moe_cache(max_block, |c, eng| {
5857 if moe_prewarm_enabled() {
5858 c.prewarm_layer(il, m, eng)?;
5859 }
5860 Ok(c.layer_dev_row(il, n_expert, eng)?.is_some())
5861 })?;
5862 if row_ok {
5863 return Self::moe_ffn_dev(e, m, z, zq8, &logits, t, cfg, il, max_block);
5864 }
5865 }
5866
5867 let (sel_all, w_all, routed_cpu_input) = if let Some(sig) = cfg.sigmoid_router() {
5869 if cpu_hybrid {
5870 let (sel, w, input) = Self::moe_route_sigmoid_with_input(
5871 e,
5872 &logits,
5873 z,
5874 t,
5875 n_embd,
5876 n_expert,
5877 n_used,
5878 m.exp_probs_b.as_deref(),
5879 sig,
5880 m.active_experts.as_deref(),
5881 )?;
5882 (sel, w, Some(input))
5883 } else {
5884 let (sel, w) =
5885 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?;
5886 (sel, w, None)
5887 }
5888 } else {
5889 let (sel, w) =
5890 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?;
5891 (sel, w, None)
5892 };
5893 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
5894
5895 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
5899 Self::trace_moe_input(e, il, t, n_embd, z)?;
5900
5901 let worker_disk_prefetch =
5913 cache_dispatch && crate::spill_pread::worker_enabled() && !cpu_hybrid;
5914 let promote_worker_h2d =
5915 t == 1 && worker_disk_prefetch && crate::spill_pread::copy_h2d_enabled();
5916 if promote_worker_h2d {
5917 let mut selected_blocks = Vec::with_capacity(n_used * 3);
5918 for &ex in sel_all.iter().take(n_used) {
5919 let ex = ex as u16;
5920 selected_blocks.extend([
5921 BlockId::new(il, PROJ_GATE, ex),
5922 BlockId::new(il, PROJ_UP, ex),
5923 BlockId::new(il, PROJ_DOWN, ex),
5924 ]);
5925 }
5926 for &ex in sel_all.iter().take(n_used) {
5927 Self::moe_prefetch_disk_expert(e, il, ex as usize, m, max_block, &selected_blocks)?;
5928 }
5929 e.with_moe_cache(max_block, |cache, eng| {
5930 cache.promote_worker_reads_at_safe_boundary(
5931 &selected_blocks,
5932 &selected_blocks,
5933 eng,
5934 )?;
5935 Ok(())
5936 })?;
5937 }
5938
5939 if t > 1 && std::env::var("MEMRA_MOE_STATS").is_ok() {
5942 let mut cnt = vec![0u32; n_expert];
5943 for &s in sel_all.iter() {
5944 cnt[s as usize] += 1;
5945 }
5946 let total = sel_all.len() as f64;
5947 let mut h = 0.0f64;
5948 let mut active = 0usize;
5949 for &c in &cnt {
5950 if c > 0 {
5951 active += 1;
5952 let p = c as f64 / total;
5953 h -= p * p.log2();
5954 }
5955 }
5956 let maxc = cnt.iter().copied().max().unwrap_or(0);
5957 println!(
5958 "moe-stats il={} t={} assignments={} active={}/{} entropy={:.3}b (max {:.3}b) mean_tok_per_active={:.2} max_tok_per_expert={}",
5959 il,
5960 t,
5961 sel_all.len(),
5962 active,
5963 n_expert,
5964 h,
5965 (n_expert as f64).log2(),
5966 total / active.max(1) as f64,
5967 maxc
5968 );
5969 }
5970
5971 let gdec_may_fire = uniform_experts
5984 && use_cache
5985 && n_used <= 8
5986 && gdec_enabled()
5987 && !cfg.swiglu_clamped_at(il as u32);
5988 let slab_local = m
6004 .dev_exps
6005 .as_ref()
6006 .filter(|d| !d.gu_il && moe_slab_enabled() && d.dev == e.ctx().ordinal());
6007 let slab_bases = slab_local.map(|d| {
6008 use cudarc::driver::DevicePtr;
6009 let s = e.stream();
6010 let (pg, _g0) = d.gate.device_ptr(&s);
6011 let (pu, _g1) = d.up.device_ptr(&s);
6012 let (pd, _g2) = d.down.device_ptr(&s);
6013 (pg as u64, pu as u64, pd as u64)
6014 });
6015 let slab_fused_may_fire = slab_bases.is_some()
6025 && n_used <= 8
6026 && gdec_enabled()
6027 && !cfg.swiglu_clamped_at(il as u32)
6028 && cfg.m3.is_none()
6029 && no_exp_macros
6030 && moe_q8;
6031 let mut moe_out = if gdec_may_fire || slab_fused_may_fire {
6034 e.uninit(t * n_embd)?
6035 } else {
6036 e.zeros(t * n_embd)?
6037 };
6038 let cpu_input = if cpu_hybrid {
6041 Some(routed_cpu_input.ok_or("CPU expert routing did not return the MoE input")?)
6042 } else {
6043 None
6044 };
6045
6046 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;
6054 let mut scratch_u: Option<CudaSlice<u8>> = None;
6055 let mut scratch_d: Option<CudaSlice<u8>> = None;
6056 let page_window = moe_page_prefetch_window();
6064
6065 for tok in 0..t {
6068 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
6069 let w = &w_all[tok * n_used..(tok + 1) * n_used];
6070 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd); let mut tok_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
6072
6073 let no_macros = m.gate_exps.macros.is_none()
6087 && m.up_exps.macros.is_none()
6088 && m.down_exps.macros.is_none();
6089 if slab_fused_may_fire {
6099 let (pg, pu, pd) = slab_bases.unwrap();
6100 let mut gp = [0u64; 8];
6101 let mut up = [0u64; 8];
6102 let mut dp = [0u64; 8];
6103 for (j, &ex) in sel.iter().enumerate() {
6104 let ex = ex as usize;
6105 gp[j] = pg + (ex * m.gate_exps.expert_stride) as u64;
6106 up[j] = pu + (ex * m.up_exps.expert_stride) as u64;
6107 dp[j] = pd + (ex * m.down_exps.expert_stride) as u64;
6108 }
6109 let mut wv = [0f32; 8];
6110 wv[..n_used].copy_from_slice(w);
6111 if tok_q8.is_none() {
6112 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6113 }
6114 let (zq, zd) = tok_q8.as_ref().unwrap();
6115 let act = e.moe_gate_up_silu8_q8(
6116 crate::WPtr8(gp),
6117 crate::WPtr8(up),
6118 zq,
6119 zd,
6120 n_embd,
6121 n_ff_exp,
6122 n_used,
6123 m.gate_exps.qtype,
6124 m.up_exps.qtype,
6125 m.gate_exps.row_bytes,
6126 m.up_exps.row_bytes,
6127 )?;
6128 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
6129 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6130 e.moe_down8_fma_q8(
6131 crate::WPtr8(dp),
6132 crate::F32x8(wv),
6133 &aq2,
6134 &ad2,
6135 &mut dst,
6136 n_ff_exp,
6137 n_embd,
6138 n_used,
6139 m.down_exps.qtype,
6140 m.down_exps.row_bytes,
6141 )?;
6142 continue;
6143 }
6144 if gdec_may_fire && moe_q8 && cfg.m3.is_none() && no_macros {
6145 if tok_q8.is_none() {
6146 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6147 }
6148 let (zq, zd) = tok_q8.as_ref().unwrap();
6149 if Self::moe_gdec_token_q8(
6150 e,
6151 m,
6152 il,
6153 max_block,
6154 zq,
6155 zd,
6156 sel,
6157 w,
6158 &mut moe_out,
6159 tok,
6160 n_embd,
6161 n_ff_exp,
6162 n_used,
6163 )? {
6164 continue;
6165 }
6166 } else if gdec_may_fire
6167 && cfg.m3.is_none()
6168 && no_macros
6169 && Self::moe_gdec_token(
6170 e,
6171 m,
6172 il,
6173 max_block,
6174 &zt,
6175 sel,
6176 w,
6177 &mut moe_out,
6178 tok,
6179 n_embd,
6180 n_ff_exp,
6181 n_used,
6182 )?
6183 {
6184 continue;
6185 }
6186
6187 if gdec_may_fire || slab_fused_may_fire {
6193 let mut row = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6194 e.memset_zeros_view(&mut row)?;
6195 }
6196
6197 let mut cpu_mask = vec![false; sel.len()];
6203 let cpu_worker = if let Some(host_input) = cpu_input.as_ref() {
6204 let gpu_resident = if use_cache {
6205 e.with_moe_cache(max_block, |cache, _| {
6206 Ok(sel
6207 .iter()
6208 .map(|&expert| {
6209 let expert = expert as u16;
6210 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
6211 .into_iter()
6212 .filter(|&projection| {
6213 cache
6214 .resident(BlockId::new(il, projection, expert))
6215 .is_some()
6216 })
6217 .count()
6218 })
6219 .collect::<Vec<_>>())
6220 })?
6221 } else {
6222 vec![0; sel.len()]
6223 };
6224 let mut cpu_selected = Vec::new();
6225 for (index, (&expert, &route_weight)) in sel.iter().zip(w).enumerate() {
6226 if gpu_resident[index] != 3 {
6227 cpu_mask[index] = true;
6228 crate::cpu_experts::record_incomplete_gpu_residency(gpu_resident[index]);
6229 let expert = expert as usize;
6230 cpu_selected.push((expert, route_weight));
6231 }
6232 }
6233 if crate::cpu_experts::predictor_enabled() {
6234 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6238 crate::cpu_experts::predictor_submit(il, row);
6239 }
6240 if cpu_selected.is_empty() {
6241 None
6242 } else {
6243 let row = &host_input[tok * n_embd..(tok + 1) * n_embd];
6244 let job = crate::cpu_experts::prepare_job(m, il, &cpu_selected, row)
6245 .map_err(std::io::Error::other)?;
6246 Some(crate::cpu_experts::submit(job).map_err(std::io::Error::other)?)
6247 }
6248 } else {
6249 None
6250 };
6251
6252 let worker_window = worker_disk_prefetch
6253 .then(worker_prefetch_window)
6254 .unwrap_or(0);
6255 for (j, &ex) in sel.iter().enumerate() {
6256 if cpu_mask[j] {
6257 continue;
6258 }
6259 let ex = ex as usize;
6260 if let Some(d) = slab_local {
6267 let gl = m.gate_exps.expert_layout(ex);
6268 let ul = m.up_exps.expert_layout(ex);
6269 let dl = m.down_exps.expert_layout(ex);
6270 let (g0, u0, d0) = (
6271 ex * m.gate_exps.expert_stride,
6272 ex * m.up_exps.expert_stride,
6273 ex * m.down_exps.expert_stride,
6274 );
6275 let (gate, up) = if moe_q8 {
6276 if tok_q8.is_none() {
6277 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6278 }
6279 let (zq, zd) = tok_q8.as_ref().unwrap();
6280 (
6281 e.qmatvec_expert_q8(
6282 &d.gate,
6283 g0..g0 + gl.len,
6284 zq,
6285 zd,
6286 1,
6287 m.gate_exps.in_f,
6288 m.gate_exps.out_f,
6289 gl.qtype,
6290 gl.row_bytes,
6291 )?,
6292 e.qmatvec_expert_q8(
6293 &d.up,
6294 u0..u0 + ul.len,
6295 zq,
6296 zd,
6297 1,
6298 m.up_exps.in_f,
6299 m.up_exps.out_f,
6300 ul.qtype,
6301 ul.row_bytes,
6302 )?,
6303 )
6304 } else {
6305 (
6306 e.qmatvec_view(
6307 &d.gate,
6308 g0..g0 + gl.len,
6309 &zt,
6310 1,
6311 m.gate_exps.in_f,
6312 m.gate_exps.out_f,
6313 gl.qtype,
6314 gl.row_bytes,
6315 )?,
6316 e.qmatvec_view(
6317 &d.up,
6318 u0..u0 + ul.len,
6319 &zt,
6320 1,
6321 m.up_exps.in_f,
6322 m.up_exps.out_f,
6323 ul.qtype,
6324 ul.row_bytes,
6325 )?,
6326 )
6327 };
6328 let mut act = e.uninit(n_ff_exp)?;
6329 Self::ffn_act_lim(
6330 e,
6331 cfg,
6332 &gate,
6333 &up,
6334 m.gate_exps.macro_scale(ex),
6335 m.up_exps.macro_scale(ex),
6336 lim_exp,
6337 &mut act,
6338 n_ff_exp,
6339 )?;
6340 let y = if moe_q8 {
6341 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6342 e.qmatvec_expert_q8(
6343 &d.down,
6344 d0..d0 + dl.len,
6345 &aq2,
6346 &ad2,
6347 1,
6348 m.down_exps.in_f,
6349 m.down_exps.out_f,
6350 dl.qtype,
6351 dl.row_bytes,
6352 )?
6353 } else {
6354 let actv = act.slice(0..n_ff_exp);
6355 e.qmatvec_view(
6356 &d.down,
6357 d0..d0 + dl.len,
6358 &actv,
6359 1,
6360 m.down_exps.in_f,
6361 m.down_exps.out_f,
6362 dl.qtype,
6363 dl.row_bytes,
6364 )?
6365 };
6366 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6367 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6368 continue;
6369 }
6370 for next in page_prefetch_positions(j, sel.len(), page_window) {
6371 Self::moe_prefetch_host_expert(sel[next] as usize, m);
6372 }
6373 let keep = [
6374 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_GATE, ex as u16),
6375 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_UP, ex as u16),
6376 crate::moe_cache::BlockId::new(il, crate::moe_cache::PROJ_DOWN, ex as u16),
6377 ];
6378 if worker_disk_prefetch && worker_window > 0 {
6379 for next in worker_prefetch_positions(j, sel.len(), worker_window) {
6380 Self::moe_prefetch_disk_expert(
6381 e,
6382 il,
6383 sel[next] as usize,
6384 m,
6385 max_block,
6386 &keep,
6387 )?;
6388 }
6389 } else if cache_dispatch
6390 && !cpu_hybrid
6391 && moe_prefetch_enabled()
6392 && j + 1 < sel.len()
6393 {
6394 let next = sel[j + 1] as usize;
6395 Self::moe_prefetch_expert(e, il, next, m, max_block, &keep)?;
6396 }
6397 let [gate_q8, up_q8, down_q8] = [moe_q8; 3];
6398 if cache_dispatch && (gate_q8 || up_q8 || down_q8) {
6399 if (gate_q8 || up_q8) && tok_q8.is_none() {
6402 tok_q8 = Some(e.quantize_q8_1_view(&zt, 1, n_embd)?);
6403 }
6404 let gate = if gate_q8 {
6405 let (zq, zd) = tok_q8.as_ref().unwrap();
6406 Self::moe_cached_gemm_q8(e, il, PROJ_GATE, ex, m, max_block, zq, zd)?
6407 } else {
6408 Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?
6409 };
6410 let up = if up_q8 {
6411 let (zq, zd) = tok_q8.as_ref().unwrap();
6412 Self::moe_cached_gemm_q8(e, il, PROJ_UP, ex, m, max_block, zq, zd)?
6413 } else {
6414 Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?
6415 };
6416 let mut act = e.uninit(n_ff_exp)?;
6417 Self::ffn_act_lim(
6418 e,
6419 cfg,
6420 &gate,
6421 &up,
6422 m.gate_exps.macro_scale(ex),
6423 m.up_exps.macro_scale(ex),
6424 lim_exp,
6425 &mut act,
6426 n_ff_exp,
6427 )?;
6428 let y = if down_q8 {
6429 let (aq2, ad2) = e.quantize_q8_1(&act, 1, n_ff_exp)?;
6430 Self::moe_cached_gemm_q8(e, il, PROJ_DOWN, ex, m, max_block, &aq2, &ad2)?
6431 } else {
6432 let actv = act.slice(0..n_ff_exp);
6433 Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?
6434 };
6435 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6436 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6438 } else if cache_dispatch {
6439 let gate = Self::moe_cached_gemm(e, il, PROJ_GATE, ex, m, max_block, &zt)?;
6444 let up = Self::moe_cached_gemm(e, il, PROJ_UP, ex, m, max_block, &zt)?;
6445 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6447 e,
6448 cfg,
6449 &gate,
6450 &up,
6451 m.gate_exps.macro_scale(ex),
6452 m.up_exps.macro_scale(ex),
6453 lim_exp,
6454 &mut act,
6455 n_ff_exp,
6456 )?;
6457 let actv = act.slice(0..n_ff_exp);
6458 let y = Self::moe_cached_gemm(e, il, PROJ_DOWN, ex, m, max_block, &actv)?;
6459 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6460 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6462 } else if cache_frozen {
6463 let gate = Self::moe_frozen_gemm(
6468 e,
6469 il,
6470 PROJ_GATE,
6471 ex,
6472 m,
6473 max_block,
6474 &zt,
6475 &mut scratch_g,
6476 g_len,
6477 )?;
6478 let up = Self::moe_frozen_gemm(
6479 e,
6480 il,
6481 PROJ_UP,
6482 ex,
6483 m,
6484 max_block,
6485 &zt,
6486 &mut scratch_u,
6487 u_len,
6488 )?;
6489 let mut act = e.uninit(n_ff_exp)?;
6490 Self::ffn_act_lim(
6491 e,
6492 cfg,
6493 &gate,
6494 &up,
6495 m.gate_exps.macro_scale(ex),
6496 m.up_exps.macro_scale(ex),
6497 lim_exp,
6498 &mut act,
6499 n_ff_exp,
6500 )?;
6501 let actv = act.slice(0..n_ff_exp);
6502 let y = Self::moe_frozen_gemm(
6503 e,
6504 il,
6505 PROJ_DOWN,
6506 ex,
6507 m,
6508 max_block,
6509 &actv,
6510 &mut scratch_d,
6511 d_len,
6512 )?;
6513 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6514 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6515 } else {
6516 if scratch_g.is_none() {
6520 scratch_g = Some(e.alloc_u8_uninit(g_len)?);
6521 scratch_u = Some(e.alloc_u8_uninit(u_len)?);
6522 scratch_d = Some(e.alloc_u8_uninit(d_len)?);
6523 }
6524 let (sg, su, sd) = (
6525 scratch_g.as_mut().unwrap(),
6526 scratch_u.as_mut().unwrap(),
6527 scratch_d.as_mut().unwrap(),
6528 );
6529 let gl = m.gate_exps.expert_layout(ex);
6530 let ul = m.up_exps.expert_layout(ex);
6531 let dl = m.down_exps.expert_layout(ex);
6532 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
6533 let gate = e.qmatvec_view(
6534 sg,
6535 0..gl.len,
6536 &zt,
6537 1,
6538 m.gate_exps.in_f,
6539 m.gate_exps.out_f,
6540 gl.qtype,
6541 gl.row_bytes,
6542 )?;
6543
6544 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
6545 let up = e.qmatvec_view(
6546 su,
6547 0..ul.len,
6548 &zt,
6549 1,
6550 m.up_exps.in_f,
6551 m.up_exps.out_f,
6552 ul.qtype,
6553 ul.row_bytes,
6554 )?;
6555
6556 let mut act = e.uninit(n_ff_exp)?; Self::ffn_act_lim(
6558 e,
6559 cfg,
6560 &gate,
6561 &up,
6562 m.gate_exps.macro_scale(ex),
6563 m.up_exps.macro_scale(ex),
6564 lim_exp,
6565 &mut act,
6566 n_ff_exp,
6567 )?;
6568
6569 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
6570 let actv = act.slice(0..n_ff_exp);
6571 let y = e.qmatvec_view(
6572 sd,
6573 0..dl.len,
6574 &actv,
6575 1,
6576 m.down_exps.in_f,
6577 m.down_exps.out_f,
6578 dl.qtype,
6579 dl.row_bytes,
6580 )?;
6581
6582 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6583 e.axpy_into(&y, w[j] * m.down_exps.macro_scale(ex), &mut dst, n_embd)?;
6584 }
6585 }
6586 if let Some(worker) = cpu_worker {
6587 let cpu_output = worker.wait().map_err(std::io::Error::other)?;
6588 let cpu_output = e.htod(&cpu_output)?;
6589 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
6590 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
6591 }
6592 if cpu_hybrid && !cache_frozen && cpu_expert_profile_admit_enabled() {
6593 for (j, &ex) in sel.iter().enumerate() {
6594 if cpu_mask[j] {
6595 Self::moe_profile_admit_expert(e, il, ex as usize, m, max_block)?;
6596 }
6597 }
6598 }
6599 }
6600
6601 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
6606 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
6607 {
6608 let n_ff_sh = gate_shexp.out_features(); let verify_t = t > 1 && t < PRIME_MIN_T;
6617 let (sg_gate, sg_up) = if t == 1 {
6618 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
6619 } else if verify_t {
6620 (
6621 e.matmul_decode_exact(gate_shexp, z, t)?,
6622 e.matmul_decode_exact(up_shexp, z, t)?,
6623 )
6624 } else {
6625 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?) };
6627 let mut sa = e.uninit(t * n_ff_sh)?; Self::ffn_act_lim(
6629 e,
6630 cfg,
6631 &sg_gate,
6632 &sg_up,
6633 1.0,
6634 1.0,
6635 lim_shexp,
6636 &mut sa,
6637 t * n_ff_sh,
6638 )?;
6639 let sh = if verify_t {
6640 e.matmul_decode_exact(down_shexp, &sa, t)?
6641 } else {
6642 e.matmul(down_shexp, &sa, t)?
6643 }; let g = match &m.gate_inp_shexp {
6657 Some(gate_inp_shexp) => {
6658 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
6659 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
6660 } else {
6661 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
6662 let mut g = e.uninit(t)?; e.sigmoid(&gs, &mut g, t)?;
6664 g
6665 }
6666 }
6667 None => e.htod(&vec![1.0f32; t])?,
6668 };
6669 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
6671 }
6672
6673 Ok(moe_out)
6674 }
6675
6676 pub fn stage1_h2d_per_token(&self) -> u64 {
6679 use crate::hybrid::Ffn;
6680 let n_used = self
6681 .cfg
6682 .moe
6683 .as_ref()
6684 .map(|m| m.expert_used_count as u64)
6685 .unwrap_or(0);
6686 let mut bytes = 0u64;
6687 for l in self.layers.iter() {
6688 if let Ffn::Moe(m) = &l.ffn {
6689 bytes += n_used
6690 * (m.gate_exps.max_expert_bytes()
6691 + m.up_exps.max_expert_bytes()
6692 + m.down_exps.max_expert_bytes()) as u64;
6693 }
6694 }
6695 bytes
6696 }
6697
6698 pub(crate) fn max_moe_block(&self) -> usize {
6702 use crate::hybrid::Ffn;
6703 let mut mx = 0usize;
6704 let mut scan = |ffn: &Ffn| {
6705 if let Ffn::Moe(m) = ffn {
6706 mx = mx
6707 .max(m.gate_exps.max_expert_bytes())
6708 .max(m.up_exps.max_expert_bytes())
6709 .max(m.down_exps.max_expert_bytes());
6710 }
6711 };
6712 for l in self.layers.iter() {
6713 scan(&l.ffn);
6714 }
6715 if let Some(mtp) = self.mtp.as_ref() {
6716 scan(&mtp.ffn);
6717 }
6718 mx
6719 }
6720
6721 pub(crate) fn moe_cache_block_sizes(&self) -> Vec<usize> {
6724 use crate::hybrid::Ffn;
6725 let mut sizes = Vec::new();
6726 let mut scan = |ffn: &Ffn| {
6727 let Ffn::Moe(m) = ffn else { return };
6728 for ex in 0..m.gate_exps.n_expert {
6729 if m.active_experts.as_ref().is_some_and(|active| !active[ex]) {
6730 continue;
6731 }
6732 for exps in [&m.gate_exps, &m.up_exps, &m.down_exps] {
6733 let len = exps.expert_layout(ex).len;
6734 if len > 0 {
6735 sizes.push(len);
6736 }
6737 }
6738 }
6739 };
6740 for layer in &self.layers {
6741 scan(&layer.ffn);
6742 }
6743 if let Some(mtp) = &self.mtp {
6744 scan(&mtp.ffn);
6745 }
6746 sizes
6747 }
6748
6749 pub fn save_cpu_expert_residency_profile(
6755 &self,
6756 e: &Engine,
6757 path: &std::path::Path,
6758 ) -> Result<(), Box<dyn std::error::Error>> {
6759 let Some(ids) = e.export_moe_residency() else {
6760 return Err("no MoE residency cache to persist".into());
6761 };
6762 let mut body = format!(
6763 "memra-freeze-profile v1 max_block={} blocks={}\n",
6764 self.max_moe_block(),
6765 ids.len()
6766 );
6767 for (layer, proj, ex) in &ids {
6768 body.push_str(&format!("{layer} {proj} {ex}\n"));
6769 }
6770 let tmp = path.with_extension("tmp");
6771 std::fs::write(&tmp, body)?;
6772 std::fs::rename(&tmp, path)?;
6773 println!(
6774 "[moe-cache] freeze profile saved: {} blocks -> {}",
6775 ids.len(),
6776 path.display()
6777 );
6778 Ok(())
6779 }
6780
6781 pub fn restore_cpu_expert_residency_profile(
6785 &self,
6786 e: &Engine,
6787 path: &std::path::Path,
6788 ) -> Result<bool, Box<dyn std::error::Error>> {
6789 use crate::hybrid::Ffn;
6790 use crate::moe_cache::BlockId;
6791 let Ok(content) = std::fs::read_to_string(path) else {
6792 return Ok(false);
6793 };
6794 let mut lines = content.lines();
6795 let Some(header) = lines.next() else {
6796 return Ok(false);
6797 };
6798 let expected = format!("memra-freeze-profile v1 max_block={}", self.max_moe_block());
6799 if !header.starts_with(&expected) {
6800 println!(
6801 "[moe-cache] freeze profile ignored (geometry mismatch): {}",
6802 path.display()
6803 );
6804 return Ok(false);
6805 }
6806 let mut by_layer: std::collections::HashMap<u16, Vec<BlockId>> =
6807 std::collections::HashMap::new();
6808 for line in lines {
6809 let mut fields = line.split_whitespace();
6810 let (Some(layer), Some(proj), Some(ex)) = (fields.next(), fields.next(), fields.next())
6811 else {
6812 continue;
6813 };
6814 let (Ok(layer), Ok(proj), Ok(ex)) =
6815 (layer.parse::<u16>(), proj.parse::<u8>(), ex.parse::<u16>())
6816 else {
6817 continue;
6818 };
6819 by_layer
6820 .entry(layer)
6821 .or_default()
6822 .push(BlockId::new(layer, proj, ex));
6823 }
6824 let requested: usize = by_layer.values().map(Vec::len).sum();
6825 if requested == 0 {
6826 return Ok(false);
6827 }
6828 let max_block = self.max_moe_block();
6829 let mut restaged = 0usize;
6830 let mut stage_layer =
6831 |layer_index: u16, ffn: &Ffn| -> Result<(), Box<dyn std::error::Error>> {
6832 let Ffn::Moe(m) = ffn else { return Ok(()) };
6833 let Some(ids) = by_layer.get(&layer_index) else {
6834 return Ok(());
6835 };
6836 e.with_moe_cache(max_block, |cache, eng| {
6837 for id in ids {
6838 if cache.restage_block(*id, m, eng)? {
6839 restaged += 1;
6840 }
6841 }
6842 Ok(())
6843 })
6844 };
6845 for (index, layer) in self.layers.iter().enumerate() {
6846 stage_layer(index as u16, &layer.ffn)?;
6847 }
6848 if let Some(mtp) = self.mtp.as_ref() {
6849 stage_layer(u16::MAX, &mtp.ffn)?;
6850 }
6851 e.freeze_moe_cache();
6852 println!(
6853 "[moe-cache] freeze profile restored: {restaged}/{requested} blocks restaged from {}",
6854 path.display()
6855 );
6856 Ok(true)
6857 }
6858
6859 pub fn freeze_cpu_expert_residency(
6861 &self,
6862 e: &Engine,
6863 ) -> Result<(), Box<dyn std::error::Error>> {
6864 e.freeze_moe_cache();
6865 Ok(())
6866 }
6867
6868 pub fn ffn_act(
6876 e: &Engine,
6877 cfg: &ModelConfig,
6878 gate: &CudaSlice<f32>,
6879 up: &CudaSlice<f32>,
6880 act: &mut CudaSlice<f32>,
6881 n: usize,
6882 ) -> Result<(), Box<dyn std::error::Error>> {
6883 Self::ffn_act_scaled(e, cfg, gate, up, 1.0, 1.0, act, n)
6884 }
6885
6886 #[allow(clippy::too_many_arguments)]
6890 pub(crate) fn ffn_act_scaled(
6891 e: &Engine,
6892 cfg: &ModelConfig,
6893 gate: &CudaSlice<f32>,
6894 up: &CudaSlice<f32>,
6895 gs: f32,
6896 us: f32,
6897 act: &mut CudaSlice<f32>,
6898 n: usize,
6899 ) -> Result<(), Box<dyn std::error::Error>> {
6900 Self::ffn_act_lim(e, cfg, gate, up, gs, us, None, act, n)
6901 }
6902
6903 #[allow(clippy::too_many_arguments)]
6912 pub(crate) fn ffn_act_lim(
6913 e: &Engine,
6914 cfg: &ModelConfig,
6915 gate: &CudaSlice<f32>,
6916 up: &CudaSlice<f32>,
6917 gs: f32,
6918 us: f32,
6919 limit: Option<f32>,
6920 act: &mut CudaSlice<f32>,
6921 n: usize,
6922 ) -> Result<(), Box<dyn std::error::Error>> {
6923 if let Some(m3) = cfg.m3.as_ref() {
6924 debug_assert!(
6925 limit.is_none(),
6926 "m3 swigluoai and step35 clamp are different archs"
6927 );
6928 return e.swigluoai_mul_scaled(
6929 gate,
6930 up,
6931 gs,
6932 us,
6933 m3.swiglu_alpha,
6934 m3.swiglu_limit,
6935 act,
6936 n,
6937 );
6938 }
6939 if let Some(l) = limit {
6940 return e.swiglu_clamped_mul_scaled(gate, up, gs, us, l, act, n);
6941 }
6942 if gs == 1.0 && us == 1.0 {
6943 return e.silu_mul(gate, up, act, n);
6944 }
6945 e.silu_mul_scaled(gate, up, gs, us, act, n)
6946 }
6947
6948 fn moe_route(
6954 e: &Engine,
6955 logits: &CudaSlice<f32>,
6956 t: usize,
6957 n_expert: usize,
6958 n_used: usize,
6959 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6960 Self::moe_route_cfg(e, logits, t, n_expert, n_used, None)
6961 }
6962
6963 #[allow(clippy::too_many_arguments)]
6971 fn moe_route_sigmoid_cfg(
6972 e: &Engine,
6973 logits: &CudaSlice<f32>,
6974 t: usize,
6975 n_expert: usize,
6976 n_used: usize,
6977 m: &MoeWeights,
6978 (sf, route_norm): (f32, bool),
6979 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
6980 if sigmoid_router_enabled() {
6981 return e.moe_router_sigmoid_topk_host(
6982 logits,
6983 t,
6984 n_expert,
6985 n_used,
6986 m.active_count(),
6987 &m.exp_probs_b_dev,
6988 &m.active_experts_dev,
6989 sf,
6990 route_norm,
6991 );
6992 }
6993 let lg = e.dtoh(logits)?;
6994 Self::moe_route_sigmoid_host(
6995 &lg,
6996 t,
6997 n_expert,
6998 n_used,
6999 m.exp_probs_b.as_deref(),
7000 sf,
7001 route_norm,
7002 m.active_experts.as_deref(),
7003 )
7004 }
7005
7006 fn moe_route_cfg(
7009 e: &Engine,
7010 logits: &CudaSlice<f32>,
7011 t: usize,
7012 n_expert: usize,
7013 n_used: usize,
7014 active: Option<&[bool]>,
7015 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7016 if active.is_none() && !matches!(std::env::var("MEMRA_FUSED_ROUTER").as_deref(), Ok("0")) {
7019 return e.moe_router_topk_host(logits, t, n_expert, n_used);
7020 }
7021 let lg = e.dtoh(logits)?; let mut sel = vec![0u32; t * n_used];
7024 let mut w_out = vec![0f32; t * n_used];
7025 for tok in 0..t {
7026 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7027 let maxl = row
7029 .iter()
7030 .enumerate()
7031 .filter(|(i, _)| active.is_none_or(|mask| mask[*i]))
7032 .map(|(_, &x)| x)
7033 .fold(f32::NEG_INFINITY, f32::max);
7034 let mut probs = vec![0f32; n_expert];
7035 let mut den = 0f32;
7036 for i in 0..n_expert {
7037 if active.is_some_and(|mask| !mask[i]) {
7038 continue;
7039 }
7040 let x = (row[i] - maxl).exp();
7041 probs[i] = x;
7042 den += x;
7043 }
7044 for p in probs.iter_mut() {
7045 *p /= den;
7046 }
7047 let mut idx: Vec<usize> = (0..n_expert)
7049 .filter(|&i| active.is_none_or(|mask| mask[i]))
7050 .collect();
7051 idx.sort_by(|&a, &b| probs[b].total_cmp(&probs[a]).then(a.cmp(&b)));
7052 let sl = &idx[..n_used];
7053 let mut wv: Vec<f32> = sl.iter().map(|&i| probs[i]).collect();
7054 let mut ws: f32 = wv.iter().sum();
7055 ws = ws.max(6.103515625e-5_f32); for x in wv.iter_mut() {
7057 *x /= ws;
7058 }
7059 for j in 0..n_used {
7060 sel[tok * n_used + j] = sl[j] as u32;
7061 w_out[tok * n_used + j] = wv[j];
7062 }
7063 }
7064 Ok((sel, w_out))
7065 }
7066
7067 #[allow(clippy::too_many_arguments)]
7068 fn moe_route_sigmoid_with_input(
7069 e: &Engine,
7070 logits: &CudaSlice<f32>,
7071 input: &CudaSlice<f32>,
7072 t: usize,
7073 in_features: usize,
7074 n_expert: usize,
7075 n_used: usize,
7076 bias: Option<&[f32]>,
7077 (sf, route_norm): (f32, bool),
7078 active: Option<&[bool]>,
7079 ) -> Result<(Vec<u32>, Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
7080 let logit_values =
7081 active_matrix_values(logits.len(), t, n_expert, "sigmoid router logits")?;
7082 let input_values =
7083 active_matrix_values(input.len(), t, in_features, "sigmoid router input")?;
7084 let (lg, input) = e.dtoh_pair_views(
7085 &logits.slice(0..logit_values),
7086 &input.slice(0..input_values),
7087 )?;
7088 let (sel, w) =
7089 Self::moe_route_sigmoid_host(&lg, t, n_expert, n_used, bias, sf, route_norm, active)?;
7090 Ok((sel, w, input))
7091 }
7092
7093 pub fn start_moe_prefetch_predictor(
7098 &self,
7099 e: &Engine,
7100 cfg: &ModelConfig,
7101 ) -> Result<(), Box<dyn std::error::Error>> {
7102 use crate::hybrid::Ffn;
7103 let Some(sig) = cfg.sigmoid_router() else {
7104 return Err("prefetch predictor requires a sigmoid-router arch".into());
7105 };
7106 let resident: std::collections::HashSet<(u16, u8, u16)> = e
7107 .export_moe_residency()
7108 .ok_or("prefetch predictor needs the frozen MoE residency cache")?
7109 .into_iter()
7110 .collect();
7111 let mut layers = Vec::new();
7112 for (index, layer) in self.layers.iter().enumerate() {
7113 let Ffn::Moe(m) = &layer.ffn else { continue };
7114 let crate::model::GpuTensor::Float { data, .. } = &m.gate_inp else {
7115 continue;
7116 };
7117 let router = e.dtoh(data)?;
7118 let n_expert = m.gate_exps.n_expert;
7119 let n_embd = m.gate_exps.in_f;
7120 if router.len() != n_embd * n_expert {
7121 continue;
7122 }
7123 let build = |exps: &crate::model::HostExps| {
7124 (0..n_expert)
7125 .map(|expert| crate::cpu_experts::predictor_projection(exps, expert))
7126 .collect::<Vec<_>>()
7127 };
7128 layers.push((
7129 index as u16,
7130 crate::cpu_experts::PredictLayerInit {
7131 router,
7132 bias: m.exp_probs_b.clone(),
7133 active: m.active_experts.clone(),
7134 n_embd,
7135 n_used: cfg
7136 .moe
7137 .as_ref()
7138 .map(|moe| moe.expert_used_count as usize)
7139 .ok_or("prefetch predictor requires MoE config")?,
7140 sig,
7141 weights_n_expert: n_expert,
7142 gate: build(&m.gate_exps),
7143 up: build(&m.up_exps),
7144 down: build(&m.down_exps),
7145 },
7146 ));
7147 }
7148 crate::cpu_experts::start_prefetch_predictor(layers, resident).map_err(|error| error.into())
7149 }
7150
7151 #[allow(clippy::too_many_arguments)]
7154 pub fn moe_route_sigmoid_host_public(
7155 logits: &[f32],
7156 t: usize,
7157 n_expert: usize,
7158 n_used: usize,
7159 bias: Option<&[f32]>,
7160 sf: f32,
7161 route_norm: bool,
7162 active: Option<&[bool]>,
7163 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7164 Self::moe_route_sigmoid_host(logits, t, n_expert, n_used, bias, sf, route_norm, active)
7165 }
7166
7167 #[allow(clippy::too_many_arguments)]
7168 fn moe_route_sigmoid_host(
7169 lg: &[f32],
7170 t: usize,
7171 n_expert: usize,
7172 n_used: usize,
7173 bias: Option<&[f32]>,
7174 sf: f32,
7175 route_norm: bool,
7176 active: Option<&[bool]>,
7177 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
7178 let active_count = active
7179 .map(|mask| mask.iter().filter(|&&enabled| enabled).count())
7180 .unwrap_or(n_expert);
7181 crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
7182 if lg.len() != t * n_expert {
7183 return Err(format!(
7184 "sigmoid router logits length mismatch: got {}, expected {}",
7185 lg.len(),
7186 t * n_expert,
7187 )
7188 .into());
7189 }
7190 let mut sel = vec![0u32; t * n_used];
7191 let mut w_out = vec![0f32; t * n_used];
7192 for tok in 0..t {
7193 let row = &lg[tok * n_expert..(tok + 1) * n_expert];
7194 let scores: Vec<f32> = row.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect();
7195 let selsc: Vec<f32> = match bias {
7197 Some(b) => scores.iter().zip(b).map(|(s, bb)| s + bb).collect(),
7198 None => scores.clone(),
7199 };
7200 let mut idx: Vec<usize> = (0..n_expert)
7201 .filter(|&i| active.is_none_or(|mask| mask[i]))
7202 .collect();
7203 idx.sort_by(|&a, &b| selsc[b].total_cmp(&selsc[a]).then(a.cmp(&b)));
7204 let sl = &idx[..n_used];
7205 let mut wv: Vec<f32> = sl.iter().map(|&i| scores[i]).collect();
7206 if route_norm {
7207 let ws: f32 = wv.iter().sum::<f32>().max(1e-20);
7208 for x in wv.iter_mut() {
7209 *x = *x / ws * sf;
7210 }
7211 } else {
7212 for x in wv.iter_mut() {
7213 *x *= sf;
7214 }
7215 }
7216 for j in 0..n_used {
7217 sel[tok * n_used + j] = sl[j] as u32;
7218 w_out[tok * n_used + j] = wv[j];
7219 }
7220 }
7221 Ok((sel, w_out))
7222 }
7223
7224 #[allow(clippy::too_many_arguments)]
7228 fn moe_ffn_sigmoid_dev(
7229 e: &Engine,
7230 m: &MoeWeights,
7231 z: &CudaSlice<f32>,
7232 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7233 logits: &CudaSlice<f32>,
7234 t: usize,
7235 cfg: &ModelConfig,
7236 il: u16,
7237 (scaling_factor, route_norm): (f32, bool),
7238 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7239 let moe = cfg.moe.as_ref().unwrap();
7240 let n_embd = cfg.n_embd as usize;
7241 let n_expert = moe.expert_count as usize;
7242 let n_used = moe.expert_used_count as usize;
7243 let n_ff_exp = moe.expert_ff_length as usize;
7244 let dev = m.dev_exps.as_ref().unwrap();
7245 debug_assert_eq!(dev.dev, e.ctx().ordinal());
7246 debug_assert!(m.has_uniform_expert_layout());
7247 debug_assert!(!m.has_macros);
7248
7249 let (sel_d, w_d) = e.moe_router_sigmoid_topk(
7250 logits,
7251 t,
7252 n_expert,
7253 n_used,
7254 m.active_count(),
7255 &m.exp_probs_b_dev,
7256 &m.active_experts_dev,
7257 scaling_factor,
7258 route_norm,
7259 )?;
7260 crate::moesd::record_device_routes(e, il, n_expert, n_used, &sel_d)?;
7261 if let Some(fp8) = dev.fp8_blk.as_ref() {
7262 debug_assert_eq!(m.gate_exps.qtype, crate::QT_F8_E4M3_BLK);
7263 debug_assert_eq!(m.up_exps.qtype, crate::QT_F8_E4M3_BLK);
7264 debug_assert_eq!(m.down_exps.qtype, crate::QT_F8_E4M3_BLK);
7265 debug_assert_eq!(fp8.gate.rows, m.gate_exps.out_f.div_ceil(128));
7266 debug_assert_eq!(fp8.up.rows, m.up_exps.out_f.div_ceil(128));
7267 debug_assert_eq!(fp8.down.rows, m.down_exps.out_f.div_ceil(128));
7268
7269 let selected = e.dtoh_i32(&sel_d)?;
7276 let route_weights = e.dtoh(&w_d)?;
7277 let mut moe_out = e.zeros(t * n_embd)?;
7278 for tok in 0..t {
7279 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
7280 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
7281 for j in 0..n_used {
7282 let pair = tok * n_used + j;
7283 let expert = selected[pair] as usize;
7284 let gate = Self::moe_resident_fp8_e4m3(
7285 e,
7286 &m.gate_exps,
7287 &dev.gate,
7288 &fp8.gate,
7289 expert,
7290 &zt,
7291 1,
7292 )?;
7293 let up = Self::moe_resident_fp8_e4m3(
7294 e, &m.up_exps, &dev.up, &fp8.up, expert, &zt, 1,
7295 )?;
7296 let mut act = e.uninit(n_ff_exp)?;
7297 Self::ffn_act_lim(
7298 e,
7299 cfg,
7300 &gate,
7301 &up,
7302 1.0,
7303 1.0,
7304 cfg.clamp_exp_at(il as u32),
7305 &mut act,
7306 n_ff_exp,
7307 )?;
7308 let act = act.slice(0..n_ff_exp);
7309 let down = Self::moe_resident_fp8_e4m3(
7310 e,
7311 &m.down_exps,
7312 &dev.down,
7313 &fp8.down,
7314 expert,
7315 &act,
7316 1,
7317 )?;
7318 e.axpy_into(&down, route_weights[pair], &mut dst, n_embd)?;
7319 }
7320 }
7321 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7322 eprintln!(
7323 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} \
7324 native=fp8blk-w8a8-e4m3-reference clamp={}",
7325 cfg.clamp_exp_at(il as u32).is_some(),
7326 );
7327 }
7328 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7329 return Ok(moe_out);
7330 }
7331 let (gate_row_bytes, up_row_bytes) = if dev.gu_il {
7332 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7333 (combined, combined)
7334 } else {
7335 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7336 };
7337 let (zq, zd) = match (t, zq8) {
7338 (1, Some((q, d))) => (q.clone(), d.clone()),
7339 _ => e.quantize_q8_1(z, t, n_embd)?,
7340 };
7341 let n_pairs = t * n_used;
7342 let mut moe_out = if cfg.clamp_exp_at(il as u32).is_some() {
7343 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
7347 let pair_tok_d = e.htod_i32(&pair_tok)?;
7348 let gate = e.moe_pairs_matvec_q8(
7349 &dev.ptr_row,
7350 0,
7351 &pair_tok_d,
7352 &sel_d,
7353 &zq,
7354 &zd,
7355 n_embd,
7356 n_ff_exp,
7357 n_expert,
7358 n_pairs,
7359 m.gate_exps.qtype,
7360 gate_row_bytes,
7361 )?;
7362 let up = e.moe_pairs_matvec_q8(
7363 &dev.ptr_row,
7364 1,
7365 &pair_tok_d,
7366 &sel_d,
7367 &zq,
7368 &zd,
7369 n_embd,
7370 n_ff_exp,
7371 n_expert,
7372 n_pairs,
7373 m.up_exps.qtype,
7374 up_row_bytes,
7375 )?;
7376 let mut act = e.uninit(n_pairs * n_ff_exp)?;
7377 Self::ffn_act_lim(
7378 e,
7379 cfg,
7380 &gate,
7381 &up,
7382 1.0,
7383 1.0,
7384 cfg.clamp_exp_at(il as u32),
7385 &mut act,
7386 n_pairs * n_ff_exp,
7387 )?;
7388 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7389 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7390 let pair_self_d = e.htod_i32(&pair_self)?;
7391 let down = e.moe_pairs_matvec_q8(
7392 &dev.ptr_row,
7393 2,
7394 &pair_self_d,
7395 &sel_d,
7396 &aq2,
7397 &ad2,
7398 n_ff_exp,
7399 n_embd,
7400 n_expert,
7401 n_pairs,
7402 m.down_exps.qtype,
7403 m.down_exps.row_bytes,
7404 )?;
7405 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7406 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7407 let tok_off_d = e.htod_i32(&tok_off)?;
7408 let tok_ids_d = e.htod_i32(&tok_ids)?;
7409 let mut output = e.uninit(t * n_embd)?;
7410 e.moe_pairs_scatter(&down, &w_d, &tok_off_d, &tok_ids_d, &mut output, t, n_embd)?;
7411 output
7412 } else {
7413 let act = e.moe_gate_up_silu8_dev_q8_rows(
7414 &dev.ptr_row,
7415 &sel_d,
7416 &zq,
7417 &zd,
7418 t,
7419 n_embd,
7420 n_ff_exp,
7421 n_used,
7422 n_expert,
7423 m.gate_exps.qtype,
7424 m.up_exps.qtype,
7425 gate_row_bytes,
7426 up_row_bytes,
7427 &m.dev_macros,
7428 )?;
7429 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7430 let mut output = e.uninit(t * n_embd)?;
7431 e.moe_down8_fma_dev_q8_rows_g(
7432 &dev.ptr_row,
7433 &sel_d,
7434 &w_d,
7435 &aq2,
7436 &ad2,
7437 &mut output,
7438 t,
7439 n_ff_exp,
7440 n_embd,
7441 n_used,
7442 n_expert,
7443 m.down_exps.qtype,
7444 m.down_exps.row_bytes,
7445 )?;
7446 output
7447 };
7448
7449 if std::env::var("MEMRA_SIG_ROUTER_DISPATCH_TRACE").as_deref() == Ok("1") {
7450 eprintln!(
7451 "[sigrouter-dev] layer={il} tokens={t} experts={n_expert} used={n_used} clamp={} gu_il={}",
7452 cfg.clamp_exp_at(il as u32).is_some(),
7453 dev.gu_il,
7454 );
7455 }
7456 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
7457 Ok(moe_out)
7458 }
7459
7460 #[allow(clippy::too_many_arguments)]
7461 fn moe_resident_fp8_e4m3(
7462 e: &Engine,
7463 exps: &crate::model::HostExps,
7464 bytes: &CudaSlice<u8>,
7465 scales: &crate::hybrid::DevExpertFp8ProjectionScales,
7466 expert: usize,
7467 x: &cudarc::driver::CudaView<f32>,
7468 m: usize,
7469 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7470 let layout = exps.expert_layout(expert);
7471 debug_assert_eq!(layout.qtype, crate::QT_F8_E4M3_BLK);
7472 debug_assert_eq!(scales.rows * scales.cols, scales.expert_stride);
7473 let byte_start = expert * exps.expert_stride;
7474 let scale_start = expert * scales.expert_stride;
7475 let weight = bytes.slice(byte_start..byte_start + layout.len);
7476 let scale = scales
7477 .scales
7478 .slice(scale_start..scale_start + scales.expert_stride);
7479 e.qmatvec_mmq_fp8_blk_view(&weight, &scale, x, m, exps.in_f, exps.out_f)
7480 }
7481
7482 fn moe_ffn_pairs(
7491 e: &Engine,
7492 m: &MoeWeights,
7493 z: &CudaSlice<f32>,
7494 logits: &CudaSlice<f32>,
7495 t: usize,
7496 cfg: &ModelConfig,
7497 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7498 let moe = cfg.moe.as_ref().unwrap();
7499 let n_embd = cfg.n_embd as usize;
7500 let n_expert = moe.expert_count as usize;
7501 let n_used = moe.expert_used_count as usize;
7502 let n_ff_exp = moe.expert_ff_length as usize;
7503 debug_assert!(
7508 !cfg.swiglu_clamped_anywhere(),
7509 "moe_ffn_pairs has no per-layer clamp: fused epilogues are plain SiLU"
7510 );
7511 let dev = m.dev_exps.as_ref().unwrap();
7512 let (rbg_d, rbu_d) = if dev.gu_il {
7514 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
7515 (sxx, sxx)
7516 } else {
7517 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
7518 };
7519
7520 let (sel_all, w_all) = Self::moe_route(e, logits, t, n_expert, n_used)?;
7521 let n_pairs = t * n_used;
7522 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
7525 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
7526 let pair_w: Vec<f32> = w_all.clone();
7527 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
7528 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
7529 let pt = e.htod_i32(&pair_tok)?;
7530 let px = e.htod_i32(&pair_ex)?;
7531 let pw = e.htod(&pair_w)?;
7532 let toff = e.htod_i32(&tok_off)?;
7533 let tids = e.htod_i32(&tok_ids)?;
7534
7535 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
7539 for p in 0..n_pairs {
7540 by_ex[pair_ex[p] as usize].push(p as i32);
7541 }
7542 let mut ex_ids: Vec<i32> = Vec::new();
7543 let mut ex_off: Vec<i32> = vec![0];
7544 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
7545 for (ex, list) in by_ex.iter().enumerate() {
7546 if list.is_empty() {
7547 continue;
7548 }
7549 ex_ids.push(ex as i32);
7550 ex_pairs.extend_from_slice(list);
7551 ex_off.push(ex_pairs.len() as i32);
7552 }
7553 let n_active = ex_ids.len();
7554 let exi = e.htod_i32(&ex_ids)?;
7555 let exo = e.htod_i32(&ex_off)?;
7556 let exp_d = e.htod_i32(&ex_pairs)?;
7557 let _ = &px; static MMA_T: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7578 let mma_t = *MMA_T.get_or_init(|| {
7579 std::env::var("MEMRA_MOE_MMA_T")
7580 .ok()
7581 .and_then(|v| v.parse().ok())
7582 .unwrap_or(16)
7583 });
7584 let use_mma = std::env::var("MEMRA_MOE_MMA")
7585 .map(|v| v != "0")
7586 .unwrap_or(true)
7587 && t >= mma_t
7588 && q8_expert_dec_supported(m.gate_exps.qtype)
7589 && q8_expert_dec_supported(m.up_exps.qtype)
7590 && q8_expert_dec_supported(m.down_exps.qtype)
7591 && n_embd % 256 == 0
7592 && n_ff_exp % 256 == 0;
7593 let mma_capable = q8_expert_dec_supported(m.gate_exps.qtype)
7609 && q8_expert_dec_supported(m.up_exps.qtype)
7610 && q8_expert_dec_supported(m.down_exps.qtype)
7611 && n_embd % 256 == 0
7612 && n_ff_exp % 256 == 0;
7613 let f16g_mode = crate::moe_f16g_mode();
7614 let f16g = f16g_mode != 0
7615 && t >= mma_t
7616 && (f16g_mode != 3 || !mma_capable)
7617 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
7618 && f16g_proj_ok(m.up_exps.qtype, n_embd)
7619 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp);
7620 if use_mma || f16g {
7621 let y_down = if f16g {
7629 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
7633 let csr_tok_d = e.htod_i32(&csr_tok)?;
7634 let (z_f16, z_s) = e.moe_f16g_act(z, Some(&csr_tok_d), n_embd, n_pairs)?;
7635 let g_csr = e.moe_f16_grouped(
7636 &dev.ptr_row,
7637 0,
7638 n_expert,
7639 &exi,
7640 &ex_off,
7641 &exo,
7642 &z_f16,
7643 &z_s,
7644 n_embd,
7645 n_ff_exp,
7646 n_active,
7647 n_pairs,
7648 m.gate_exps.qtype,
7649 rbg_d,
7650 )?;
7651 let u_csr = e.moe_f16_grouped(
7652 &dev.ptr_row,
7653 1,
7654 n_expert,
7655 &exi,
7656 &ex_off,
7657 &exo,
7658 &z_f16,
7659 &z_s,
7660 n_embd,
7661 n_ff_exp,
7662 n_active,
7663 n_pairs,
7664 m.up_exps.qtype,
7665 rbu_d,
7666 )?;
7667 let act_csr = e.moe_pairs_silu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
7668 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
7669 let d_csr = e.moe_f16_grouped(
7670 &dev.ptr_row,
7671 2,
7672 n_expert,
7673 &exi,
7674 &ex_off,
7675 &exo,
7676 &a_f16,
7677 &a_s,
7678 n_ff_exp,
7679 n_embd,
7680 n_active,
7681 n_pairs,
7682 m.down_exps.qtype,
7683 m.down_exps.row_bytes,
7684 )?;
7685 e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?
7686 } else {
7687 let z_scr = e.mmq_iq_quantize_act(z, n_embd, t)?;
7689 let gate = e.mmq_iq_experts(
7690 &dev.ptr_row,
7691 0,
7692 n_expert,
7693 &exi,
7694 &exo,
7695 &exp_d,
7696 &pt,
7697 &z_scr,
7698 n_embd,
7699 n_ff_exp,
7700 n_active,
7701 n_pairs,
7702 t,
7703 m.gate_exps.qtype,
7704 rbg_d,
7705 )?;
7706 let up = e.mmq_iq_experts(
7707 &dev.ptr_row,
7708 1,
7709 n_expert,
7710 &exi,
7711 &exo,
7712 &exp_d,
7713 &pt,
7714 &z_scr,
7715 n_embd,
7716 n_ff_exp,
7717 n_active,
7718 n_pairs,
7719 t,
7720 m.up_exps.qtype,
7721 rbu_d,
7722 )?;
7723 let a_scr = if crate::moe_fuse_actq_on() {
7729 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 0)?
7730 } else {
7731 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7732 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
7733 };
7734 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7735 let pself = e.htod_i32(&pair_self)?;
7736 e.mmq_iq_experts(
7737 &dev.ptr_row,
7738 2,
7739 n_expert,
7740 &exi,
7741 &exo,
7742 &exp_d,
7743 &pself,
7744 &a_scr,
7745 n_ff_exp,
7746 n_embd,
7747 n_active,
7748 n_pairs,
7749 n_pairs,
7750 m.down_exps.qtype,
7751 m.down_exps.row_bytes,
7752 )?
7753 };
7754 let mut moe_out = e.uninit(t * n_embd)?;
7755 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7756 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7757 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7758 {
7759 let n_ff_sh = gate_shexp.out_features();
7760 let sg_gate = e.matmul(gate_shexp, z, t)?;
7761 let sg_up = e.matmul(up_shexp, z, t)?;
7762 let mut sa = e.uninit(t * n_ff_sh)?;
7763 Self::ffn_act(e, cfg, &sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7764 let sh = e.matmul(down_shexp, &sa, t)?;
7765 let g = match &m.gate_inp_shexp {
7771 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7772 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7773 }
7774 Some(gate_inp_shexp) => {
7775 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7776 let mut g = e.uninit(t)?;
7777 e.sigmoid(&gs, &mut g, t)?;
7778 g
7779 }
7780 None => e.htod(&vec![1.0f32; t])?,
7781 };
7782 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7783 }
7784 return Ok(moe_out);
7785 }
7786
7787 let dec = std::env::var("MEMRA_MOE_DEC")
7790 .map(|v| v != "0")
7791 .unwrap_or(true);
7792 let matvec = |proj,
7793 exi: &_,
7794 exo: &_,
7795 exp_d: &_,
7796 pt: &_,
7797 aq: &_,
7798 ad: &_,
7799 inf,
7800 outf,
7801 qtype,
7802 rb|
7803 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7804 let dec = dec && q8_expert_dec_supported(qtype);
7806 if dec {
7807 e.moe_pairs_matvec_q8_dec(
7808 &dev.ptr_row,
7809 proj,
7810 exi,
7811 exo,
7812 exp_d,
7813 pt,
7814 aq,
7815 ad,
7816 inf,
7817 outf,
7818 n_expert,
7819 n_active,
7820 n_pairs,
7821 qtype,
7822 rb,
7823 )
7824 } else {
7825 e.moe_pairs_matvec_q8_em(
7826 &dev.ptr_row,
7827 proj,
7828 exi,
7829 exo,
7830 exp_d,
7831 pt,
7832 aq,
7833 ad,
7834 inf,
7835 outf,
7836 n_expert,
7837 n_active,
7838 n_pairs,
7839 qtype,
7840 rb,
7841 )
7842 }
7843 };
7844 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7845 let gate = matvec(
7846 0,
7847 &exi,
7848 &exo,
7849 &exp_d,
7850 &pt,
7851 &zq,
7852 &zd,
7853 n_embd,
7854 n_ff_exp,
7855 m.gate_exps.qtype,
7856 rbg_d,
7857 )?;
7858 let up = matvec(
7859 1,
7860 &exi,
7861 &exo,
7862 &exp_d,
7863 &pt,
7864 &zq,
7865 &zd,
7866 n_embd,
7867 n_ff_exp,
7868 m.up_exps.qtype,
7869 rbu_d,
7870 )?;
7871 let act = e.moe_pairs_silu_mul(&gate, &up, n_pairs * n_ff_exp)?;
7872 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
7873 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
7875 let pself = e.htod_i32(&pair_self)?;
7876 let y_down = matvec(
7877 2,
7878 &exi,
7879 &exo,
7880 &exp_d,
7881 &pself,
7882 &aq2,
7883 &ad2,
7884 n_ff_exp,
7885 n_embd,
7886 m.down_exps.qtype,
7887 m.down_exps.row_bytes,
7888 )?;
7889 let mut moe_out = e.uninit(t * n_embd)?; e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
7891
7892 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
7896 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
7897 {
7898 let n_ff_sh = gate_shexp.out_features();
7899 let step_exact = true;
7903 let verify_t = step_exact && t > 1 && t < PRIME_MIN_T;
7904 let (sg_gate, sg_up) = if step_exact && t == 1 {
7905 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, None)?
7906 } else if verify_t {
7907 let mut fused = None;
7908 if crate::spec::spec_fused_t()
7909 && (2..=4).contains(&t)
7910 && e.uses_q8_1_fast(gate_shexp)
7911 && e.uses_q8_1_fast(up_shexp)
7912 {
7913 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
7914 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
7915 }
7916 match fused {
7917 Some(pair) => pair,
7918 None => (
7919 e.matmul_decode_exact(gate_shexp, z, t)?,
7920 e.matmul_decode_exact(up_shexp, z, t)?,
7921 ),
7922 }
7923 } else {
7924 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
7925 };
7926 let mut sa = e.uninit(t * n_ff_sh)?;
7927 e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
7928 let sh = if verify_t {
7929 e.matmul_decode_exact(down_shexp, &sa, t)?
7930 } else {
7931 e.matmul(down_shexp, &sa, t)?
7932 };
7933 let g = match &m.gate_inp_shexp {
7938 Some(gate_inp_shexp) if crate::router_prefill_exact_on() => {
7939 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
7940 }
7941 Some(gate_inp_shexp) => {
7942 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
7943 let mut g = e.uninit(t)?;
7944 e.sigmoid(&gs, &mut g, t)?;
7945 g
7946 }
7947 None => e.htod(&vec![1.0f32; t])?,
7948 };
7949 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
7950 }
7951 Ok(moe_out)
7952 }
7953
7954 #[allow(clippy::too_many_arguments)]
7956 #[allow(clippy::too_many_arguments)]
7957 fn moe_ffn_dev(
7958 e: &Engine,
7959 m: &MoeWeights,
7960 z: &CudaSlice<f32>,
7961 zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>,
7962 logits: &CudaSlice<f32>,
7963 t: usize,
7964 cfg: &ModelConfig,
7965 il: u16,
7966 max_block: usize,
7967 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7968 let moe = cfg.moe.as_ref().unwrap();
7969 let n_embd = cfg.n_embd as usize;
7970 let n_expert = moe.expert_count as usize;
7971 let n_used = moe.expert_used_count as usize;
7972 let n_ff_exp = moe.expert_ff_length as usize;
7973 debug_assert!(
7977 cfg.sigmoid_router().is_none(),
7978 "moe_ffn_dev routes SOFTMAX: a sigmoid-router arch would pick wrong experts"
7979 );
7980 debug_assert!(
7981 !cfg.swiglu_clamped_at(il as u32),
7982 "moe_ffn_dev's fused epilogue is plain SiLU: no clamped form"
7983 );
7984
7985 let (sel_d, mut w_d) = e.moe_router_topk(logits, t, n_expert, n_used)?;
7987 if m.has_macros {
7990 e.moe_w_scale_by_expert(&mut w_d, &sel_d, &m.dev_macros, n_expert, t * n_used)?;
7991 }
7992
7993 let mut moe_out = e.uninit(t * n_embd)?;
7995
7996 if let Some(dev) = m.dev_exps.as_ref() {
7999 let (rbg_d, rbu_d) = if dev.gu_il {
8002 let sxx = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8003 (sxx, sxx)
8004 } else {
8005 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8006 };
8007 let q8 = moe_q8_enabled()
8008 && q8_expert_supported(m.gate_exps.qtype)
8009 && q8_expert_supported(m.up_exps.qtype)
8010 && q8_expert_supported(m.down_exps.qtype);
8011 let rows_arm = q8
8020 && t > 1
8021 && crate::spec::spec_m2()
8022 && n_ff_exp == 512
8023 && n_used <= 8
8024 && std::env::var("MEMRA_MOE_DEVQ8_GU")
8025 .map(|v| v.is_empty() || v == "v")
8026 .unwrap_or(true)
8027 && std::env::var("MEMRA_MOE_DEVQ8_DOWN")
8028 .map(|v| v.is_empty() || v == "w8h2v")
8029 .unwrap_or(true);
8030 let csr_mode = std::env::var("MEMRA_MOE_CSR")
8039 .ok()
8040 .and_then(|v| v.parse::<i32>().ok())
8041 .unwrap_or(1);
8042 let csr_nvfp4_probe = std::env::var("MEMRA_MOE_CSR_NVFP4").as_deref() == Ok("1");
8059 let csr_qt = |qt: i32| {
8060 qt == crate::QT_IQ4_XS
8061 || qt == crate::QT_IQ3_S
8062 || (csr_nvfp4_probe && qt == crate::QT_NVFP4)
8063 };
8064 let csr_t_max = if csr_nvfp4_probe { MOE_DEV_MAX_T } else { 10 };
8065 let csr_uniform = m.gate_exps.qtype == m.up_exps.qtype;
8066 let csr_arm = rows_arm
8067 && csr_mode > 0
8068 && t <= csr_t_max
8069 && csr_uniform
8070 && csr_qt(m.gate_exps.qtype)
8071 && csr_qt(m.up_exps.qtype)
8072 && csr_qt(m.down_exps.qtype);
8073 if csr_arm {
8074 if csr_mode == 2 {
8075 static ENGAGED: std::sync::Once = std::sync::Once::new();
8076 ENGAGED.call_once(|| eprintln!("[memra] moe CSR byte-compare mode ON (t={t})"));
8077 }
8078 let n_pairs = t * n_used;
8079 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8080 let act = e.moe_gate_up_silu8_dev_q8_csr(
8081 &dev.ptr_row,
8082 &sel_d,
8083 &zq,
8084 &zd,
8085 n_pairs,
8086 n_embd,
8087 n_ff_exp,
8088 n_used,
8089 n_expert,
8090 m.gate_exps.qtype,
8091 m.up_exps.qtype,
8092 rbg_d,
8093 rbu_d,
8094 )?;
8095 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8096 e.moe_down8_fma_dev_q8_rows(
8100 &dev.ptr_row,
8101 &sel_d,
8102 &w_d,
8103 &aq2,
8104 &ad2,
8105 &mut moe_out,
8106 t,
8107 n_ff_exp,
8108 n_embd,
8109 n_used,
8110 n_expert,
8111 m.down_exps.qtype,
8112 m.down_exps.row_bytes,
8113 )?;
8114 if csr_mode == 2 {
8115 let act_r = e.moe_gate_up_silu8_dev_q8_rows(
8117 &dev.ptr_row,
8118 &sel_d,
8119 &zq,
8120 &zd,
8121 t,
8122 n_embd,
8123 n_ff_exp,
8124 n_used,
8125 n_expert,
8126 m.gate_exps.qtype,
8127 m.up_exps.qtype,
8128 rbg_d,
8129 rbu_d,
8130 &m.dev_macros,
8131 )?;
8132 let mut out_r = e.uninit(t * n_embd)?;
8133 let (aq2r, ad2r) = e.quantize_q8_1(&act_r, n_pairs, n_ff_exp)?;
8134 e.moe_down8_fma_dev_q8_rows(
8135 &dev.ptr_row,
8136 &sel_d,
8137 &w_d,
8138 &aq2r,
8139 &ad2r,
8140 &mut out_r,
8141 t,
8142 n_ff_exp,
8143 n_embd,
8144 n_used,
8145 n_expert,
8146 m.down_exps.qtype,
8147 m.down_exps.row_bytes,
8148 )?;
8149 let (a1, a2) = (e.dtoh(&act)?, e.dtoh(&act_r)?);
8150 let (o1, o2) = (e.dtoh(&moe_out)?, e.dtoh(&out_r)?);
8151 let ba = a1
8152 .iter()
8153 .zip(&a2)
8154 .filter(|(x, y)| x.to_bits() != y.to_bits())
8155 .count();
8156 let bo = o1
8157 .iter()
8158 .zip(&o2)
8159 .filter(|(x, y)| x.to_bits() != y.to_bits())
8160 .count();
8161 if ba + bo > 0 {
8162 eprintln!(
8163 "[csr-check] il={il} t={t} ACT diffs={ba}/{} OUT diffs={bo}/{}",
8164 a1.len(),
8165 o1.len()
8166 );
8167 let sel_h = e.dtoh_i32(&sel_d)?;
8169 let mut shown = 0;
8170 for (i, (x, y)) in a1.iter().zip(&a2).enumerate() {
8171 if x.to_bits() != y.to_bits() && shown < 4 {
8172 let (p, o) = (i / n_ff_exp, i % n_ff_exp);
8173 let ex = sel_h[p];
8174 let npx = sel_h.iter().filter(|&&v| v == ex).count();
8175 eprintln!(
8176 " ACT p={p} ex={ex} np={npx} o={o} csr={x:e} rows={y:e}"
8177 );
8178 shown += 1;
8179 }
8180 }
8181 std::process::exit(3);
8182 }
8183 }
8184 } else if rows_arm {
8185 if std::env::var("MEMRA_MOE_OVERLAP").as_deref() == Ok("1") {
8188 use std::sync::atomic::{AtomicU64, Ordering};
8189 static PAIRS: AtomicU64 = AtomicU64::new(0);
8190 static UNIQ: AtomicU64 = AtomicU64::new(0);
8191 static CALLS: AtomicU64 = AtomicU64::new(0);
8192 let sel_h = e.dtoh_i32(&sel_d)?;
8193 let mut u: Vec<i32> = sel_h.clone();
8194 u.sort_unstable();
8195 u.dedup();
8196 PAIRS.fetch_add(sel_h.len() as u64, Ordering::Relaxed);
8197 UNIQ.fetch_add(u.len() as u64, Ordering::Relaxed);
8198 let c = CALLS.fetch_add(1, Ordering::Relaxed) + 1;
8199 if c % 480 == 0 {
8200 let p = PAIRS.load(Ordering::Relaxed);
8201 let q = UNIQ.load(Ordering::Relaxed);
8202 eprintln!(
8203 "[overlap] calls={c} pairs={p} unique={q} ratio={:.3} (t={t})",
8204 q as f64 / p as f64
8205 );
8206 }
8207 }
8208 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8209 let act = e.moe_gate_up_silu8_dev_q8_rows(
8210 &dev.ptr_row,
8211 &sel_d,
8212 &zq,
8213 &zd,
8214 t,
8215 n_embd,
8216 n_ff_exp,
8217 n_used,
8218 n_expert,
8219 m.gate_exps.qtype,
8220 m.up_exps.qtype,
8221 rbg_d,
8222 rbu_d,
8223 &m.dev_macros,
8224 )?;
8225 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
8226 e.moe_down8_fma_dev_q8_rows(
8227 &dev.ptr_row,
8228 &sel_d,
8229 &w_d,
8230 &aq2,
8231 &ad2,
8232 &mut moe_out,
8233 t,
8234 n_ff_exp,
8235 n_embd,
8236 n_used,
8237 n_expert,
8238 m.down_exps.qtype,
8239 m.down_exps.row_bytes,
8240 )?;
8241 } else {
8242 for tok in 0..t {
8243 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8244 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8245 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8246 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8247 if q8 {
8248 let (zq, zd) = match (t, zq8) {
8249 (1, Some((q, d))) => (q.clone(), d.clone()),
8250 _ => e.quantize_q8_1_view(&zt, 1, n_embd)?,
8251 };
8252 let act = e.moe_gate_up_silu8_dev_q8(
8253 &dev.ptr_row,
8254 &selt,
8255 &zq,
8256 &zd,
8257 n_embd,
8258 n_ff_exp,
8259 n_used,
8260 n_expert,
8261 m.gate_exps.qtype,
8262 m.up_exps.qtype,
8263 rbg_d,
8264 rbu_d,
8265 &m.dev_macros,
8266 )?;
8267 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8268 e.moe_down8_fma_dev_q8(
8269 &dev.ptr_row,
8270 &selt,
8271 &wt,
8272 &aq2,
8273 &ad2,
8274 &mut dst,
8275 n_ff_exp,
8276 n_embd,
8277 n_used,
8278 n_expert,
8279 m.down_exps.qtype,
8280 m.down_exps.row_bytes,
8281 )?;
8282 } else {
8283 let act = e.moe_gate_up_silu8_dev(
8284 &dev.ptr_row,
8285 &selt,
8286 &zt,
8287 n_embd,
8288 n_ff_exp,
8289 n_used,
8290 n_expert,
8291 m.gate_exps.qtype,
8292 m.up_exps.qtype,
8293 rbg_d,
8294 rbu_d,
8295 &m.dev_macros,
8296 )?;
8297 e.moe_down8_fma_dev(
8298 &dev.ptr_row,
8299 &selt,
8300 &wt,
8301 &act,
8302 &mut dst,
8303 n_ff_exp,
8304 n_embd,
8305 n_used,
8306 n_expert,
8307 m.down_exps.qtype,
8308 m.down_exps.row_bytes,
8309 )?;
8310 }
8311 }
8312 }
8313 } else {
8314 let q8 = moe_q8_enabled()
8321 && q8_expert_supported(m.gate_exps.qtype)
8322 && q8_expert_supported(m.up_exps.qtype)
8323 && q8_expert_supported(m.down_exps.qtype);
8324 e.with_moe_cache(max_block, |c, eng| {
8325 let row = c
8326 .layer_dev_row(il, n_expert, eng)?
8327 .ok_or("moe_ffn_dev: layer row vanished under the lock")?;
8328 for tok in 0..t {
8329 let zt = z.slice(tok * n_embd..(tok + 1) * n_embd);
8330 let selt = sel_d.slice(tok * n_used..(tok + 1) * n_used);
8331 let wt = w_d.slice(tok * n_used..(tok + 1) * n_used);
8332 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8333 if q8 {
8334 let (zq, zd) = match (t, zq8) {
8335 (1, Some((q, d))) => (q.clone(), d.clone()),
8336 _ => eng.quantize_q8_1_view(&zt, 1, n_embd)?,
8337 };
8338 let act = eng.moe_gate_up_silu8_dev_q8(
8339 row,
8340 &selt,
8341 &zq,
8342 &zd,
8343 n_embd,
8344 n_ff_exp,
8345 n_used,
8346 n_expert,
8347 m.gate_exps.qtype,
8348 m.up_exps.qtype,
8349 m.gate_exps.row_bytes,
8350 m.up_exps.row_bytes,
8351 &m.dev_macros,
8352 )?;
8353 let (aq2, ad2) = eng.quantize_q8_1(&act, n_used, n_ff_exp)?;
8354 eng.moe_down8_fma_dev_q8(
8355 row,
8356 &selt,
8357 &wt,
8358 &aq2,
8359 &ad2,
8360 &mut dst,
8361 n_ff_exp,
8362 n_embd,
8363 n_used,
8364 n_expert,
8365 m.down_exps.qtype,
8366 m.down_exps.row_bytes,
8367 )?;
8368 } else {
8369 let act = eng.moe_gate_up_silu8_dev(
8370 row,
8371 &selt,
8372 &zt,
8373 n_embd,
8374 n_ff_exp,
8375 n_used,
8376 n_expert,
8377 m.gate_exps.qtype,
8378 m.up_exps.qtype,
8379 m.gate_exps.row_bytes,
8380 m.up_exps.row_bytes,
8381 &m.dev_macros,
8382 )?;
8383 eng.moe_down8_fma_dev(
8384 row,
8385 &selt,
8386 &wt,
8387 &act,
8388 &mut dst,
8389 n_ff_exp,
8390 n_embd,
8391 n_used,
8392 n_expert,
8393 m.down_exps.qtype,
8394 m.down_exps.row_bytes,
8395 )?;
8396 }
8397 }
8398 c.hits += (t * 3 * n_used) as u64;
8400 Ok(())
8401 })?;
8402 }
8403
8404 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
8409 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
8410 {
8411 let n_ff_sh = gate_shexp.out_features();
8412 let verify_t = t > 1 && t < PRIME_MIN_T;
8415 let (sg_gate, sg_up) = if t == 1 {
8416 shexp_gate_up_t1(e, gate_shexp, up_shexp, z, zq8)?
8417 } else if verify_t {
8418 let mut fused = None;
8422 if crate::spec::spec_fused_t()
8423 && (2..=4).contains(&t)
8424 && e.uses_q8_1_fast(gate_shexp)
8425 && e.uses_q8_1_fast(up_shexp)
8426 {
8427 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8428 fused = e.matmul_q8_fused2_t(gate_shexp, up_shexp, &zq, &zd, t)?;
8429 }
8430 match fused {
8431 Some(pair) => pair,
8432 None => (
8433 e.matmul_decode_exact(gate_shexp, z, t)?,
8434 e.matmul_decode_exact(up_shexp, z, t)?,
8435 ),
8436 }
8437 } else {
8438 (e.matmul(gate_shexp, z, t)?, e.matmul(up_shexp, z, t)?)
8439 };
8440 let mut sa = e.uninit(t * n_ff_sh)?; e.silu_mul(&sg_gate, &sg_up, &mut sa, t * n_ff_sh)?;
8442 let sh = if verify_t {
8443 e.matmul_decode_exact(down_shexp, &sa, t)?
8444 } else {
8445 e.matmul(down_shexp, &sa, t)?
8446 };
8447 let g = match &m.gate_inp_shexp {
8451 Some(gate_inp_shexp) => {
8452 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
8455 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
8456 } else {
8457 let gs = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
8458 let mut g = e.uninit(t)?;
8459 e.sigmoid(&gs, &mut g, t)?;
8460 g
8461 }
8462 }
8463 None => e.htod(&vec![1.0f32; t])?,
8464 };
8465 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, t)?;
8466 }
8467
8468 Ok(moe_out)
8469 }
8470
8471 #[allow(clippy::too_many_arguments)]
8481 #[allow(clippy::too_many_arguments)]
8484 fn moe_gdec_token_q8(
8485 e: &Engine,
8486 m: &MoeWeights,
8487 il: u16,
8488 max_block: usize,
8489 zq: &CudaSlice<i8>,
8490 zd: &CudaSlice<f32>,
8491 sel: &[u32],
8492 w: &[f32],
8493 moe_out: &mut CudaSlice<f32>,
8494 tok: usize,
8495 n_embd: usize,
8496 n_ff_exp: usize,
8497 n_used: usize,
8498 ) -> Result<bool, Box<dyn std::error::Error>> {
8499 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8500 use cudarc::driver::DevicePtr;
8501 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8502 let mut g = [0u64; 8];
8503 let mut u = [0u64; 8];
8504 let mut d = [0u64; 8];
8505 for (j, &ex) in sel.iter().enumerate() {
8506 let ex = ex as u16;
8507 let (Some(sg), Some(su), Some(sd)) = (
8508 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8509 c.resident(BlockId::new(il, PROJ_UP, ex)),
8510 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8511 ) else {
8512 return Ok(None);
8513 };
8514 let __s = eng.stream();
8515 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8516 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8517 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8518 g[j] = pg as u64;
8519 u[j] = pu as u64;
8520 d[j] = pd as u64;
8521 }
8522 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8523 for &ex in sel {
8524 let ex = ex as u16;
8525 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8526 c.note_profile_hit(BlockId::new(il, proj, ex));
8527 }
8528 }
8529 }
8530 c.hits += (3 * n_used) as u64;
8531 Ok(Some((g, u, d)))
8532 })?;
8533 let Some((g, u, d)) = ptrs else {
8534 return Ok(false);
8535 };
8536 let mut wv = [0f32; 8];
8537 wv[..n_used].copy_from_slice(w);
8538 let act = e.moe_gate_up_silu8_q8(
8539 crate::WPtr8(g),
8540 crate::WPtr8(u),
8541 zq,
8542 zd,
8543 n_embd,
8544 n_ff_exp,
8545 n_used,
8546 m.gate_exps.qtype,
8547 m.up_exps.qtype,
8548 m.gate_exps.row_bytes,
8549 m.up_exps.row_bytes,
8550 )?;
8551 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
8553 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8554 e.moe_down8_fma_q8(
8555 crate::WPtr8(d),
8556 crate::F32x8(wv),
8557 &aq2,
8558 &ad2,
8559 &mut dst,
8560 n_ff_exp,
8561 n_embd,
8562 n_used,
8563 m.down_exps.qtype,
8564 m.down_exps.row_bytes,
8565 )?;
8566 Ok(true)
8567 }
8568
8569 fn moe_gdec_token(
8570 e: &Engine,
8571 m: &MoeWeights,
8572 il: u16,
8573 max_block: usize,
8574 zt: &cudarc::driver::CudaView<f32>,
8575 sel: &[u32],
8576 w: &[f32],
8577 moe_out: &mut CudaSlice<f32>,
8578 tok: usize,
8579 n_embd: usize,
8580 n_ff_exp: usize,
8581 n_used: usize,
8582 ) -> Result<bool, Box<dyn std::error::Error>> {
8583 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8584 use cudarc::driver::DevicePtr;
8585 let ptrs = e.with_moe_cache(max_block, |c, eng| {
8587 let mut g = [0u64; 8];
8588 let mut u = [0u64; 8];
8589 let mut d = [0u64; 8];
8590 for (j, &ex) in sel.iter().enumerate() {
8591 let ex = ex as u16;
8592 let (Some(sg), Some(su), Some(sd)) = (
8593 c.resident(BlockId::new(il, PROJ_GATE, ex)),
8594 c.resident(BlockId::new(il, PROJ_UP, ex)),
8595 c.resident(BlockId::new(il, PROJ_DOWN, ex)),
8596 ) else {
8597 return Ok(None);
8598 };
8599 let __s = eng.stream();
8600 let (pg, _e0) = c.slot(sg).device_ptr(&__s);
8601 let (pu, _e1) = c.slot(su).device_ptr(&__s);
8602 let (pd, _e2) = c.slot(sd).device_ptr(&__s);
8603 g[j] = pg as u64;
8604 u[j] = pu as u64;
8605 d[j] = pd as u64;
8606 }
8607 if cpu_expert_profile_admit_enabled() && !c.is_frozen() {
8608 for &ex in sel {
8609 let ex = ex as u16;
8610 for proj in [PROJ_GATE, PROJ_UP, PROJ_DOWN] {
8611 c.note_profile_hit(BlockId::new(il, proj, ex));
8612 }
8613 }
8614 }
8615 c.hits += (3 * n_used) as u64; Ok(Some((g, u, d)))
8617 })?;
8618 let Some((g, u, d)) = ptrs else {
8619 return Ok(false);
8620 };
8621 let mut wv = [0f32; 8];
8622 wv[..n_used].copy_from_slice(w);
8623 let act = e.moe_gate_up_silu8(
8625 crate::WPtr8(g),
8626 crate::WPtr8(u),
8627 zt,
8628 n_embd,
8629 n_ff_exp,
8630 n_used,
8631 m.gate_exps.qtype,
8632 m.up_exps.qtype,
8633 m.gate_exps.row_bytes,
8634 m.up_exps.row_bytes,
8635 )?;
8636 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
8637 e.moe_down8_fma_into(
8638 crate::WPtr8(d),
8639 crate::F32x8(wv),
8640 &act,
8641 &mut dst,
8642 n_ff_exp,
8643 n_embd,
8644 n_used,
8645 m.down_exps.qtype,
8646 m.down_exps.row_bytes,
8647 )?;
8648 Ok(true)
8649 }
8650
8651 fn moe_cached_gemm_q8(
8656 e: &Engine,
8657 il: u16,
8658 proj: u8,
8659 ex: usize,
8660 m: &MoeWeights,
8661 max_block: usize,
8662 aq: &CudaSlice<i8>,
8663 ad: &CudaSlice<f32>,
8664 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8665 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8666 let exps = match proj {
8667 PROJ_GATE => &m.gate_exps,
8668 PROJ_UP => &m.up_exps,
8669 _ => &m.down_exps,
8670 };
8671 let layout = exps.expert_layout(ex);
8672 let id = BlockId::new(il, proj, ex as u16);
8673 let source = exps.expert_source(ex);
8674 e.with_moe_cache(max_block, |c, eng| {
8675 let slot = c.dispatch_source(id, source, eng)?;
8676 let DispatchSlot::Resident(sl) = slot;
8677 let buf = c.slot(sl);
8678 eng.qmatvec_expert_q8(
8679 buf,
8680 0..layout.len,
8681 aq,
8682 ad,
8683 1,
8684 exps.in_f,
8685 exps.out_f,
8686 layout.qtype,
8687 layout.row_bytes,
8688 )
8689 })
8690 }
8691
8692 fn moe_cached_gemm(
8693 e: &Engine,
8694 il: u16,
8695 proj: u8,
8696 ex: usize,
8697 m: &MoeWeights,
8698 max_block: usize,
8699 x: &cudarc::driver::CudaView<f32>,
8700 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8701 use crate::moe_cache::{BlockId, DispatchSlot, PROJ_GATE, PROJ_UP};
8702 let exps = match proj {
8703 PROJ_GATE => &m.gate_exps,
8704 PROJ_UP => &m.up_exps,
8705 _ => &m.down_exps,
8706 };
8707 let layout = exps.expert_layout(ex);
8708 let id = BlockId::new(il, proj, ex as u16);
8709 let source = exps.expert_source(ex);
8710 e.with_moe_cache(max_block, |c, eng| {
8712 let slot = c.dispatch_source(id, source, eng)?;
8713 let DispatchSlot::Resident(sl) = slot;
8716 let buf = c.slot(sl);
8717 eng.qmatvec_view(
8718 buf,
8719 0..layout.len,
8720 x,
8721 1,
8722 exps.in_f,
8723 exps.out_f,
8724 layout.qtype,
8725 layout.row_bytes,
8726 )
8727 })
8728 }
8729
8730 fn moe_profile_admit_expert(
8734 e: &Engine,
8735 il: u16,
8736 ex: usize,
8737 m: &MoeWeights,
8738 max_block: usize,
8739 ) -> Result<(), Box<dyn std::error::Error>> {
8740 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8741 e.with_moe_cache(max_block, |cache, eng| {
8742 for (proj, exps) in [
8743 (PROJ_GATE, &m.gate_exps),
8744 (PROJ_UP, &m.up_exps),
8745 (PROJ_DOWN, &m.down_exps),
8746 ] {
8747 let id = BlockId::new(il, proj, ex as u16);
8748 let _ = cache.dispatch_source(id, exps.expert_source(ex), eng)?;
8749 }
8750 Ok(())
8751 })
8752 }
8753
8754 #[allow(clippy::too_many_arguments)]
8757 fn moe_frozen_gemm(
8758 e: &Engine,
8759 il: u16,
8760 proj: u8,
8761 ex: usize,
8762 m: &MoeWeights,
8763 max_block: usize,
8764 x: &cudarc::driver::CudaView<f32>,
8765 scratch: &mut Option<CudaSlice<u8>>,
8766 scratch_len: usize,
8767 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8768 use crate::moe_cache::{BlockId, PROJ_GATE, PROJ_UP};
8769 let exps = match proj {
8770 PROJ_GATE => &m.gate_exps,
8771 PROJ_UP => &m.up_exps,
8772 _ => &m.down_exps,
8773 };
8774 let layout = exps.expert_layout(ex);
8775 let id = BlockId::new(il, proj, ex as u16);
8776 if let Some(output) = e.with_moe_cache(max_block, |cache, eng| {
8777 let Some(slot) = cache.resident(id) else {
8778 return Ok(None);
8779 };
8780 let buf = cache.slot(slot);
8781 Ok(Some(eng.qmatvec_view(
8782 buf,
8783 0..layout.len,
8784 x,
8785 1,
8786 exps.in_f,
8787 exps.out_f,
8788 layout.qtype,
8789 layout.row_bytes,
8790 )?))
8791 })? {
8792 return Ok(output);
8793 }
8794 if scratch.is_none() {
8795 *scratch = Some(e.alloc_u8_uninit(scratch_len)?);
8796 }
8797 let scratch = scratch.as_mut().unwrap();
8798 e.stage_expert(exps.expert_bytes(ex), scratch, 0)?;
8799 e.qmatvec_view(
8800 scratch,
8801 0..layout.len,
8802 x,
8803 1,
8804 exps.in_f,
8805 exps.out_f,
8806 layout.qtype,
8807 layout.row_bytes,
8808 )
8809 }
8810
8811 fn moe_prefetch_expert(
8812 e: &Engine,
8813 il: u16,
8814 ex: usize,
8815 m: &MoeWeights,
8816 max_block: usize,
8817 keep: &[crate::moe_cache::BlockId],
8818 ) -> Result<(), Box<dyn std::error::Error>> {
8819 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8820 e.with_moe_cache(max_block, |c, eng| {
8821 for (proj, exps) in [
8822 (PROJ_GATE, &m.gate_exps),
8823 (PROJ_UP, &m.up_exps),
8824 (PROJ_DOWN, &m.down_exps),
8825 ] {
8826 let id = BlockId::new(il, proj, ex as u16);
8827 let _ = c.prefetch_source(id, exps.expert_source(ex), keep, eng)?;
8828 }
8829 Ok(())
8830 })
8831 }
8832
8833 fn moe_prefetch_disk_expert(
8836 e: &Engine,
8837 il: u16,
8838 ex: usize,
8839 m: &MoeWeights,
8840 max_block: usize,
8841 keep: &[crate::moe_cache::BlockId],
8842 ) -> Result<(), Box<dyn std::error::Error>> {
8843 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
8844 e.with_moe_cache(max_block, |c, eng| {
8845 for (proj, exps) in [
8846 (PROJ_GATE, &m.gate_exps),
8847 (PROJ_UP, &m.up_exps),
8848 (PROJ_DOWN, &m.down_exps),
8849 ] {
8850 let source = exps.expert_source(ex);
8851 if let crate::model::ExpertSource::Disk { .. } = &source {
8852 let id = BlockId::new(il, proj, ex as u16);
8853 let _ = c.prefetch_source(id, source, keep, eng)?;
8854 }
8855 }
8856 Ok(())
8857 })
8858 }
8859
8860 #[inline]
8861 fn moe_prefetch_host_expert(ex: usize, m: &MoeWeights) {
8862 let _ = m.gate_exps.prefetch_expert_pages(ex);
8863 let _ = m.up_exps.prefetch_expert_pages(ex);
8864 let _ = m.down_exps.prefetch_expert_pages(ex);
8865 }
8866}
8867
8868impl HybridModel {
8885 #[allow(clippy::too_many_arguments)]
8889 fn moe_ffn_grouped_resident_q8(
8890 e: &Engine,
8891 m: &MoeWeights,
8892 z: &CudaSlice<f32>,
8893 t: usize,
8894 cfg: &ModelConfig,
8895 il: u16,
8896 sel_all: &[u32],
8897 w_all: &[f32],
8898 table: &CudaSlice<u64>,
8899 gu_il: bool,
8900 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8901 let moe = cfg.moe.as_ref().unwrap();
8902 let n_embd = cfg.n_embd as usize;
8903 let n_expert = moe.expert_count as usize;
8904 let n_used = moe.expert_used_count as usize;
8905 let n_ff_exp = moe.expert_ff_length as usize;
8906 let n_pairs = t * n_used;
8907 debug_assert_eq!(sel_all.len(), n_pairs);
8908 debug_assert_eq!(w_all.len(), n_pairs);
8909 debug_assert!(
8910 m.gate_exps.macros.is_none()
8911 && m.up_exps.macros.is_none()
8912 && m.down_exps.macros.is_none(),
8913 "resident grouped q8 does not fold per-expert macro scales",
8914 );
8915
8916 if !cfg.swiglu_clamped_at(il as u32) {
8922 let sel: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8923 let sel_d = e.htod_i32(&sel)?;
8924 let w_d = e.htod(w_all)?;
8925 let (gate_row_bytes, up_row_bytes) = if gu_il {
8926 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
8927 (combined, combined)
8928 } else {
8929 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
8930 };
8931 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
8932 let act = e.moe_gate_up_silu8_dev_q8_rows(
8933 table,
8934 &sel_d,
8935 &zq,
8936 &zd,
8937 t,
8938 n_embd,
8939 n_ff_exp,
8940 n_used,
8941 n_expert,
8942 m.gate_exps.qtype,
8943 m.up_exps.qtype,
8944 gate_row_bytes,
8945 up_row_bytes,
8946 &m.dev_macros,
8947 )?;
8948 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
8949 let mut moe_out = e.uninit(t * n_embd)?;
8950 e.moe_down8_fma_dev_q8_rows_g(
8951 table,
8952 &sel_d,
8953 &w_d,
8954 &aq2,
8955 &ad2,
8956 &mut moe_out,
8957 t,
8958 n_ff_exp,
8959 n_embd,
8960 n_used,
8961 n_expert,
8962 m.down_exps.qtype,
8963 m.down_exps.row_bytes,
8964 )?;
8965
8966 if std::env::var("MEMRA_MOE_STATS").is_ok() {
8967 let mut counts = vec![0usize; n_expert];
8968 for &expert in sel_all {
8969 counts[expert as usize] += 1;
8970 }
8971 let mut sizes: Vec<usize> =
8972 counts.into_iter().filter(|&count| count != 0).collect();
8973 sizes.sort_unstable();
8974 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
8975 println!(
8976 "moe-grouped il={il} t={t} dispatch=resident-q8-rows active={}/{} \
8977 m_e: min={} median={} mean={mean:.1} max={}",
8978 sizes.len(),
8979 n_expert,
8980 sizes.first().copied().unwrap_or(0),
8981 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
8982 sizes.last().copied().unwrap_or(0),
8983 );
8984 }
8985 return Ok(moe_out);
8986 }
8987
8988 let pair_tok: Vec<i32> = (0..n_pairs).map(|pair| (pair / n_used) as i32).collect();
8992 let pair_ex: Vec<i32> = sel_all.iter().map(|&expert| expert as i32).collect();
8993 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
8994 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
8995
8996 let mut by_expert: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
8997 for (pair, &expert) in pair_ex.iter().enumerate() {
8998 by_expert[expert as usize].push(pair as i32);
8999 }
9000
9001 let pair_tok_d = e.htod_i32(&pair_tok)?;
9002 let pair_ex_d = e.htod_i32(&pair_ex)?;
9003 let pair_w_d = e.htod(w_all)?;
9004 let tok_off_d = e.htod_i32(&tok_off)?;
9005 let tok_ids_d = e.htod_i32(&tok_ids)?;
9006
9007 let matvec = |proj: i32,
9008 pair_rows: &CudaSlice<i32>,
9009 aq: &CudaSlice<i8>,
9010 ad: &CudaSlice<f32>,
9011 in_f: usize,
9012 out_f: usize,
9013 qtype: i32,
9014 row_bytes: usize|
9015 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9016 e.moe_pairs_matvec_q8(
9017 table, proj, pair_rows, &pair_ex_d, aq, ad, in_f, out_f, n_expert, n_pairs, qtype,
9018 row_bytes,
9019 )
9020 };
9021
9022 let (gate_row_bytes, up_row_bytes) = if gu_il {
9023 let combined = m.gate_exps.row_bytes + m.up_exps.row_bytes;
9024 (combined, combined)
9025 } else {
9026 (m.gate_exps.row_bytes, m.up_exps.row_bytes)
9027 };
9028 let (zq, zd) = e.quantize_q8_1(z, t, n_embd)?;
9029 let gate = matvec(
9030 0,
9031 &pair_tok_d,
9032 &zq,
9033 &zd,
9034 n_embd,
9035 n_ff_exp,
9036 m.gate_exps.qtype,
9037 gate_row_bytes,
9038 )?;
9039 let up = matvec(
9040 1,
9041 &pair_tok_d,
9042 &zq,
9043 &zd,
9044 n_embd,
9045 n_ff_exp,
9046 m.up_exps.qtype,
9047 up_row_bytes,
9048 )?;
9049 let mut act = e.uninit(n_pairs * n_ff_exp)?;
9050 Self::ffn_act_lim(
9051 e,
9052 cfg,
9053 &gate,
9054 &up,
9055 1.0,
9056 1.0,
9057 cfg.clamp_exp_at(il as u32),
9058 &mut act,
9059 n_pairs * n_ff_exp,
9060 )?;
9061 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
9062 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
9063 let pair_self_d = e.htod_i32(&pair_self)?;
9064 let down = matvec(
9065 2,
9066 &pair_self_d,
9067 &aq2,
9068 &ad2,
9069 n_ff_exp,
9070 n_embd,
9071 m.down_exps.qtype,
9072 m.down_exps.row_bytes,
9073 )?;
9074 let mut moe_out = e.uninit(t * n_embd)?;
9075 e.moe_pairs_scatter(
9076 &down,
9077 &pair_w_d,
9078 &tok_off_d,
9079 &tok_ids_d,
9080 &mut moe_out,
9081 t,
9082 n_embd,
9083 )?;
9084
9085 if std::env::var("MEMRA_MOE_STATS").is_ok() {
9086 let mut sizes: Vec<usize> = by_expert
9087 .iter()
9088 .filter_map(|pairs| (!pairs.is_empty()).then_some(pairs.len()))
9089 .collect();
9090 sizes.sort_unstable();
9091 let mean = sizes.iter().sum::<usize>() as f64 / sizes.len().max(1) as f64;
9092 println!(
9093 "moe-grouped il={il} t={t} dispatch=resident-q8-clamped-pairs active={}/{} \
9094 m_e: min={} median={} mean={mean:.1} max={}",
9095 sizes.len(),
9096 n_expert,
9097 sizes.first().copied().unwrap_or(0),
9098 sizes.get(sizes.len() / 2).copied().unwrap_or(0),
9099 sizes.last().copied().unwrap_or(0),
9100 );
9101 }
9102 Ok(moe_out)
9103 }
9104
9105 #[allow(clippy::too_many_arguments)]
9111 fn shexp_split_matvec(
9112 e: &Engine,
9113 rank1: &Engine,
9114 wg: &CudaSlice<u8>,
9115 wu: &CudaSlice<u8>,
9116 wd: &CudaSlice<u8>,
9117 z: &CudaSlice<f32>,
9118 lim: Option<f32>,
9119 cfg: &ModelConfig,
9120 il: u16,
9121 n_embd: usize,
9122 n_ff_sh: usize,
9123 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
9124 use cudarc::driver::DevicePtr;
9125 if n_ff_sh % 2 != 0 || n_embd % 2 != 0 {
9126 return Ok(None);
9127 }
9128 let hf = n_ff_sh / 2;
9129 let nd = n_embd / 2;
9130 struct Rep {
9131 wg1: CudaSlice<u8>,
9132 wu1: CudaSlice<u8>,
9133 wd1: CudaSlice<u8>,
9134 }
9135 struct SplitWs {
9136 pin_dev: usize,
9137 gate0: CudaSlice<f32>,
9139 up0: CudaSlice<f32>,
9140 act: CudaSlice<f32>,
9141 sh_buf: CudaSlice<f32>,
9142 ev_z: cudarc::driver::CudaEvent,
9143 ev_act0: cudarc::driver::CudaEvent,
9144 z1: CudaSlice<f32>,
9146 g1: CudaSlice<f32>,
9147 u1: CudaSlice<f32>,
9148 a1h: CudaSlice<f32>,
9149 act1: CudaSlice<f32>,
9150 y1: CudaSlice<f32>,
9151 ev_act1: cudarc::driver::CudaEvent,
9152 ev_y1: cudarc::driver::CudaEvent,
9153 raw_act_e: u64,
9154 raw_sh_e: u64,
9155 raw_z1: u64,
9156 raw_a1h: u64,
9157 raw_act1: u64,
9158 raw_y1: u64,
9159 }
9160 static WS: std::sync::Mutex<Option<SplitWs>> = std::sync::Mutex::new(None);
9161 static REPS: std::sync::Mutex<Option<std::collections::HashMap<u64, Rep>>> =
9162 std::sync::Mutex::new(None);
9163 let mut guard = WS.lock().map_err(|_| "shexp split lock is poisoned")?;
9164 let mut reps_guard = REPS.lock().map_err(|_| "shexp reps lock is poisoned")?;
9165 let reps = reps_guard.get_or_insert_with(std::collections::HashMap::new);
9166 let pins = e.ctx().ordinal();
9167 if guard.as_ref().is_none_or(|w| w.pin_dev != pins) {
9168 let (gate0, up0, act, sh_buf, ev_z, ev_act0) = {
9169 let _m = e.gpu.enter_main()?;
9170 (
9171 e.htod(&vec![0.0f32; hf])?,
9172 e.htod(&vec![0.0f32; hf])?,
9173 e.htod(&vec![0.0f32; n_ff_sh])?,
9174 e.htod(&vec![0.0f32; n_embd])?,
9175 e.ctx().new_event(None)?,
9176 e.ctx().new_event(None)?,
9177 )
9178 };
9179 let (z1, g1, u1, a1h, act1, y1, ev_act1, ev_y1) = {
9180 let _r = rank1.gpu.enter_main()?;
9181 (
9182 rank1.htod(&vec![0.0f32; n_embd])?,
9183 rank1.htod(&vec![0.0f32; hf])?,
9184 rank1.htod(&vec![0.0f32; hf])?,
9185 rank1.htod(&vec![0.0f32; hf])?,
9186 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9187 rank1.htod(&vec![0.0f32; nd])?,
9188 rank1.ctx().new_event(None)?,
9189 rank1.ctx().new_event(None)?,
9190 )
9191 };
9192 let (raw_act_e, raw_sh_e) = {
9193 let _m = e.gpu.enter_main()?;
9194 let stream = e.stream();
9195 let (a, _g0) = act.device_ptr(&stream);
9196 let (b, _g1) = sh_buf.device_ptr(&stream);
9197 (a as u64, b as u64)
9198 };
9199 let (raw_z1, raw_a1h, raw_act1, raw_y1) = {
9200 let _r = rank1.gpu.enter_main()?;
9201 let rs = rank1.stream();
9202 let (a, _g0) = z1.device_ptr(&rs);
9203 let (b, _g1) = a1h.device_ptr(&rs);
9204 let (c, _g2) = act1.device_ptr(&rs);
9205 let (d, _g3) = y1.device_ptr(&rs);
9206 (a as u64, b as u64, c as u64, d as u64)
9207 };
9208 *guard = Some(SplitWs {
9209 pin_dev: pins,
9210 gate0,
9211 up0,
9212 act,
9213 sh_buf,
9214 ev_z,
9215 ev_act0,
9216 z1,
9217 g1,
9218 u1,
9219 a1h,
9220 act1,
9221 y1,
9222 ev_act1,
9223 ev_y1,
9224 raw_act_e,
9225 raw_sh_e,
9226 raw_z1,
9227 raw_a1h,
9228 raw_act1,
9229 raw_y1,
9230 });
9231 }
9232 let ws = guard.as_mut().expect("armed above");
9233 let wg_pin = {
9234 let _m = e.gpu.enter_main()?;
9235 let stream = e.stream();
9236 let (p, _g) = wg.device_ptr(&stream);
9237 p as u64
9238 };
9239 if !reps.contains_key(&wg_pin) {
9240 let mut up = |src: &CudaSlice<u8>,
9242 off_bytes: usize,
9243 len: usize|
9244 -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9245 use cudarc::driver::sys;
9246 let sptr = {
9247 let _m = e.gpu.enter_main()?;
9248 let stream = e.stream();
9249 let (p, _g) = src.device_ptr(&stream);
9250 p as u64 + off_bytes as u64
9251 };
9252 let dst = {
9253 let _r = rank1.gpu.enter_main()?;
9254 rank1.alloc_u8_uninit(len)?
9255 };
9256 let dptr = {
9257 let _r = rank1.gpu.enter_main()?;
9258 let rs = rank1.stream();
9259 let (p, _g) = dst.device_ptr(&rs);
9260 p as u64
9261 };
9262 let _r = rank1.gpu.enter_main()?;
9263 let r = unsafe {
9264 sys::cuMemcpyAsync(
9265 dptr as sys::CUdeviceptr,
9266 sptr as sys::CUdeviceptr,
9267 len,
9268 rank1.stream().cu_stream() as sys::CUstream,
9269 )
9270 };
9271 if r != sys::CUresult::CUDA_SUCCESS {
9272 return Err(format!("shexp split replica upload: {r:?}").into());
9273 }
9274 rank1.stream().synchronize()?;
9275 Ok(dst)
9276 };
9277 let wg1 = up(wg, hf * n_embd * 2, hf * n_embd * 2)?;
9278 let wu1 = up(wu, hf * n_embd * 2, hf * n_embd * 2)?;
9279 let wd1 = up(wd, nd * n_ff_sh * 2, nd * n_ff_sh * 2)?;
9280 reps.insert(wg_pin, Rep { wg1, wu1, wd1 });
9281 }
9282 let _ = il;
9283 let raw_z = {
9285 let _m = e.gpu.enter_main()?;
9286 let stream = e.stream();
9287 let (p, _g) = z.device_ptr(&stream);
9288 ws.ev_z.record(&stream)?;
9289 p as u64
9290 };
9291 {
9293 let rep = reps.get(&wg_pin).expect("uploaded above");
9294 let _r = rank1.gpu.enter_main()?;
9295 rank1.stream().wait(&ws.ev_z)?;
9296 crate::tp::raw_copy_bytes(ws.raw_z1, raw_z, n_embd * 4, rank1)?;
9297 let SplitWs {
9298 z1, g1, u1, a1h, ..
9299 } = &mut *ws;
9300 rank1.matvec_bf16_dual_into(&rep.wg1, &rep.wu1, z1, g1, u1, n_embd, hf)?;
9301 Self::ffn_act_lim(rank1, cfg, g1, u1, 1.0, 1.0, lim, a1h, hf)?;
9302 crate::tp::raw_copy_bytes(ws.raw_act1 + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9304 crate::tp::raw_copy_bytes(ws.raw_act_e + (hf * 4) as u64, ws.raw_a1h, hf * 4, rank1)?;
9305 ws.ev_act1.record(&rank1.stream())?;
9306 }
9307 {
9309 let _m = e.gpu.enter_main()?;
9310 let SplitWs {
9311 gate0, up0, act, ..
9312 } = &mut *ws;
9313 let wg_lo = wg.slice(0..hf * n_embd * 2);
9314 let wu_lo = wu.slice(0..hf * n_embd * 2);
9315 e.matvec_bf16_dual_view_into(&wg_lo, &wu_lo, z, gate0, up0, n_embd, hf)?;
9316 Self::ffn_act_lim(e, cfg, gate0, up0, 1.0, 1.0, lim, act, hf)?;
9317 ws.ev_act0.record(&e.stream())?;
9318 }
9319 {
9321 let rep = reps.get(&wg_pin).expect("uploaded above");
9322 let _r = rank1.gpu.enter_main()?;
9323 rank1.stream().wait(&ws.ev_act0)?;
9324 crate::tp::raw_copy_bytes(ws.raw_act1, ws.raw_act_e, hf * 4, rank1)?;
9325 let SplitWs { act1, y1, .. } = &mut *ws;
9326 rank1.matvec_bf16_into(&rep.wd1, act1, y1, n_ff_sh, nd)?;
9327 crate::tp::raw_copy_bytes(ws.raw_sh_e + (nd * 4) as u64, ws.raw_y1, nd * 4, rank1)?;
9328 ws.ev_y1.record(&rank1.stream())?;
9329 }
9330 {
9332 let _m = e.gpu.enter_main()?;
9333 e.stream().wait(&ws.ev_act1)?;
9334 let SplitWs { act, sh_buf, .. } = &mut *ws;
9335 let wd_lo = wd.slice(0..nd * n_ff_sh * 2);
9336 e.matvec_bf16_view_into(&wd_lo, act, sh_buf, n_ff_sh, nd)?;
9337 e.stream().wait(&ws.ev_y1)?;
9338 let mut sh = e.uninit(n_embd)?;
9339 {
9340 let mut dst = sh.slice_mut(0..n_embd);
9341 e.stream()
9342 .memcpy_dtod(&ws.sh_buf.slice(0..n_embd), &mut dst)?;
9343 }
9344 Ok(Some(sh))
9345 }
9346 }
9347
9348 fn shexp_overlap_issue(
9355 e: &Engine,
9356 m: &MoeWeights,
9357 z: &CudaSlice<f32>,
9358 cfg: &ModelConfig,
9359 il: u16,
9360 n_embd: usize,
9361 ) -> Result<bool, Box<dyn std::error::Error>> {
9362 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9363 return Ok(false);
9364 }
9365 let (
9366 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9367 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9368 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9369 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9370 else {
9371 return Ok(false);
9372 };
9373 let n_ff_sh = m
9374 .gate_shexp
9375 .as_ref()
9376 .expect("matched Some above")
9377 .out_features();
9378 let lim = cfg.clamp_shexp_at(il as u32);
9379 let mut guard = SHEXP_OV_WS
9380 .lock()
9381 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9382 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9383 if guard
9384 .as_ref()
9385 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9386 {
9387 *guard = Some((
9388 pins.0,
9389 pins.1,
9390 pins.2,
9391 e.uninit(n_ff_sh)?,
9392 e.uninit(n_embd)?,
9393 ));
9394 }
9395 let (_, _, _, act, sh) = guard.as_mut().expect("armed above");
9396 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9397 e.matvec_bf16_into(wd, act, sh, n_ff_sh, n_embd)?;
9398 drop(guard);
9399 Ok(true)
9400 }
9401
9402 #[allow(clippy::too_many_arguments)]
9408 fn shexp_dev1_issue(
9409 e: &Engine,
9410 rank1: &Engine,
9411 m: &MoeWeights,
9412 z: &CudaSlice<f32>,
9413 cfg: &ModelConfig,
9414 il: u16,
9415 n_embd: usize,
9416 ) -> Result<bool, Box<dyn std::error::Error>> {
9417 use cudarc::driver::DevicePtr;
9418 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9419 return Ok(false);
9420 }
9421 let (
9422 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
9423 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
9424 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
9425 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9426 else {
9427 return Ok(false);
9428 };
9429 let n_ff_sh = m
9430 .gate_shexp
9431 .as_ref()
9432 .expect("matched Some above")
9433 .out_features();
9434 let lim = cfg.clamp_shexp_at(il as u32);
9435 let mut ws_guard = SHEXP_D1_WS
9437 .lock()
9438 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9439 if ws_guard
9440 .as_ref()
9441 .is_none_or(|(k, ..)| *k != (n_embd, n_ff_sh))
9442 {
9443 let (act1, z1, ev_done) = {
9444 let _r1 = rank1.gpu.enter_main()?;
9445 (
9446 rank1.htod(&vec![0.0f32; n_ff_sh])?,
9447 rank1.htod(&vec![0.0f32; n_embd])?,
9448 rank1.ctx().new_event(None)?,
9449 )
9450 };
9451 let (sh_root, ev_z) = {
9452 let _main = e.gpu.enter_main()?;
9453 (e.htod(&vec![0.0f32; n_embd])?, e.ctx().new_event(None)?)
9454 };
9455 *ws_guard = Some(((n_embd, n_ff_sh), act1, z1, sh_root, ev_z, ev_done));
9456 }
9457 let mut reps_guard = SHEXP_D1_REPS
9459 .lock()
9460 .map_err(|_| "shexp dev1 replica lock is poisoned")?;
9461 let reps = reps_guard.get_or_insert_with(Default::default);
9462 if !reps.contains_key(&il) {
9463 let (wg1, wu1, wd1) = {
9464 let _r1 = rank1.gpu.enter_main()?;
9465 (
9466 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9467 rank1.alloc_u8_uninit(n_ff_sh * n_embd * 2)?,
9468 rank1.alloc_u8_uninit(n_embd * n_ff_sh * 2)?,
9469 )
9470 };
9471 for (src, dst) in [(wg, &wg1), (wu, &wu1), (wd, &wd1)] {
9472 let s_ptr = {
9473 let _main = e.gpu.enter_main()?;
9474 let stream = e.stream();
9475 let (p, _g) = src.device_ptr(&stream);
9476 p as u64
9477 };
9478 let d_ptr = {
9479 let _r1 = rank1.gpu.enter_main()?;
9480 let stream = rank1.stream();
9481 let (p, _g) = dst.device_ptr(&stream);
9482 p as u64
9483 };
9484 let _r1 = rank1.gpu.enter_main()?;
9485 crate::tp::raw_copy_bytes(d_ptr, s_ptr, src.len(), rank1)?;
9486 }
9487 {
9488 let _r1 = rank1.gpu.enter_main()?;
9489 rank1.stream().synchronize()?;
9490 }
9491 reps.insert(il, (wg1, wu1, wd1));
9492 }
9493 let (wg1, wu1, wd1) = reps.get(&il).expect("armed above");
9494 let (_, act1, z1, sh_root, ev_z, ev_done) = ws_guard.as_mut().expect("armed above");
9495 let (raw_z, raw_sh) = {
9498 let _main = e.gpu.enter_main()?;
9499 let stream = e.stream();
9500 let (a, _g0) = z.device_ptr(&stream);
9501 let (b, _g1) = sh_root.device_ptr(&stream);
9502 ev_z.record(&stream)?;
9503 (a as u64, b as u64)
9504 };
9505 {
9506 let _r1 = rank1.gpu.enter_main()?;
9507 rank1.stream().wait(ev_z)?;
9508 let raw_z1 = {
9509 let stream = rank1.stream();
9510 let (p, _g) = z1.device_ptr(&stream);
9511 p as u64
9512 };
9513 crate::tp::raw_copy_bytes(raw_z1, raw_z, n_embd * 4, rank1)?;
9514 rank1.matvec_bf16_dual_silu_into(wg1, wu1, z1, act1, n_embd, n_ff_sh, lim)?;
9515 rank1.matvec_bf16_raw_out(wd1, act1, raw_sh, n_ff_sh, n_embd)?;
9519 ev_done.record(&rank1.stream())?;
9520 }
9521 Ok(true)
9522 }
9523
9524 fn shexp_dev1_apply(
9526 e: &Engine,
9527 output: &mut CudaSlice<f32>,
9528 n_embd: usize,
9529 ) -> Result<(), Box<dyn std::error::Error>> {
9530 let guard = SHEXP_D1_WS
9531 .lock()
9532 .map_err(|_| "shexp dev1 workspace lock is poisoned")?;
9533 let (pin, _, _, sh_root, _, ev_done) =
9534 guard.as_ref().ok_or("shexp dev1 apply without issue")?;
9535 if pin.0 != n_embd {
9536 return Err("shexp dev1 width drifted".into());
9537 }
9538 let _main = e.gpu.enter_main()?;
9539 e.stream().wait(ev_done)?;
9540 static ONES_D1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9541 std::sync::Mutex::new(None);
9542 let mut og = ONES_D1.lock().map_err(|_| "ones lock is poisoned")?;
9543 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9544 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9545 }
9546 let ones = &og.as_ref().expect("armed above").1;
9547 e.add_scaled_rows(sh_root, ones, output, n_embd, 1)?;
9548 Ok(())
9549 }
9550
9551 fn shexp_overlap_tail_ptrs(
9555 e: &Engine,
9556 m: &MoeWeights,
9557 cfg: &ModelConfig,
9558 n_embd: usize,
9559 ) -> Result<Option<(u64, u64)>, Box<dyn std::error::Error>> {
9560 use cudarc::driver::DevicePtr;
9561 if cfg.m3.is_some() || m.gate_inp_shexp.is_some() {
9562 return Ok(None);
9563 }
9564 let (
9565 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9566 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9567 Some(crate::model::GpuTensor::FloatBf16 { .. }),
9568 ) = (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9569 else {
9570 return Ok(None);
9571 };
9572 let n_ff_sh = m
9573 .gate_shexp
9574 .as_ref()
9575 .expect("matched Some above")
9576 .out_features();
9577 let mut guard = SHEXP_OV_WS
9578 .lock()
9579 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9580 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9581 if guard
9582 .as_ref()
9583 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9584 {
9585 *guard = Some((
9586 pins.0,
9587 pins.1,
9588 pins.2,
9589 e.uninit(n_ff_sh)?,
9590 e.uninit(n_embd)?,
9591 ));
9592 }
9593 let sh_raw = {
9594 let (_, _, _, _, sh) = guard.as_ref().expect("armed above");
9595 let stream = e.stream();
9596 let (p, _g) = sh.device_ptr(&stream);
9597 p as u64
9598 };
9599 static ONES_T3: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9600 std::sync::Mutex::new(None);
9601 let mut og = ONES_T3.lock().map_err(|_| "ones lock is poisoned")?;
9602 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9603 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9604 }
9605 let ones_raw = {
9606 let stream = e.stream();
9607 let (p, _g) = og.as_ref().expect("armed above").1.device_ptr(&stream);
9608 p as u64
9609 };
9610 Ok(Some((sh_raw, ones_raw)))
9611 }
9612
9613 fn shexp_overlap_apply(
9616 e: &Engine,
9617 output: &mut CudaSlice<f32>,
9618 n_embd: usize,
9619 ) -> Result<(), Box<dyn std::error::Error>> {
9620 let guard = SHEXP_OV_WS
9621 .lock()
9622 .map_err(|_| "shexp overlap workspace lock is poisoned")?;
9623 let (_, ne, _, _, sh) = guard.as_ref().ok_or("shexp overlap apply without issue")?;
9624 if *ne != n_embd {
9625 return Err("shexp overlap width drifted".into());
9626 }
9627 static ONES_OV: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9628 std::sync::Mutex::new(None);
9629 let mut og = ONES_OV.lock().map_err(|_| "ones lock is poisoned")?;
9630 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9631 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9632 }
9633 let ones = &og.as_ref().expect("armed above").1;
9634 e.add_scaled_rows(sh, ones, output, n_embd, 1)?;
9635 Ok(())
9636 }
9637
9638 fn moe_ffn_grouped_add_shared(
9639 e: &Engine,
9640 m: &MoeWeights,
9641 z: &CudaSlice<f32>,
9642 t: usize,
9643 cfg: &ModelConfig,
9644 il: u16,
9645 moe_out: &mut CudaSlice<f32>,
9646 ) -> Result<(), Box<dyn std::error::Error>> {
9647 static SHEXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9650 static SHEXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9651 let shexp_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9652 let shexp_started = shexp_timing.then(std::time::Instant::now);
9653 let result = Self::moe_ffn_grouped_add_shared_inner(e, m, z, t, cfg, il, moe_out);
9654 if let Some(started) = shexp_started {
9655 use std::sync::atomic::Ordering;
9656 e.stream().synchronize()?;
9657 let ns = SHEXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9658 + started.elapsed().as_nanos() as u64;
9659 let calls = SHEXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9660 if calls % 430 == 0 {
9661 eprintln!(
9662 "[moe-shexp-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9663 ns as f64 / 1.0e6,
9664 ns as f64 / calls as f64 / 1.0e3,
9665 );
9666 }
9667 }
9668 result
9669 }
9670
9671 #[allow(clippy::too_many_arguments)]
9672 fn moe_ffn_grouped_add_shared_inner(
9673 e: &Engine,
9674 m: &MoeWeights,
9675 z: &CudaSlice<f32>,
9676 t: usize,
9677 cfg: &ModelConfig,
9678 il: u16,
9679 moe_out: &mut CudaSlice<f32>,
9680 ) -> Result<(), Box<dyn std::error::Error>> {
9681 let n_embd = cfg.n_embd as usize;
9682 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
9683 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
9684 {
9685 let n_ff_sh = gate_shexp.out_features();
9686 let lim = cfg.clamp_shexp_at(il as u32);
9687 let fused = t == 1
9694 && lim.is_none()
9695 && cfg.m3.is_none()
9696 && e.uses_q8_1_fast(gate_shexp)
9697 && e.uses_q8_1_fast(up_shexp);
9698 let bf16_dual = if t == 1 && crate::Engine::bf16_mmv_on() && n_embd % 8 == 0 {
9701 match (gate_shexp, up_shexp) {
9702 (
9703 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
9704 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
9705 ) => Some((wg, wu)),
9706 _ => None,
9707 }
9708 } else {
9709 None
9710 };
9711 let sh = if let Some((wg, wu)) = bf16_dual {
9712 static SHEXP_WS: std::sync::Mutex<
9716 Option<(
9717 usize,
9718 usize,
9719 usize,
9720 CudaSlice<f32>,
9721 CudaSlice<f32>,
9722 CudaSlice<f32>,
9723 CudaSlice<f32>,
9724 )>,
9725 > = std::sync::Mutex::new(None);
9726 let down_bf16 = match down_shexp {
9727 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9728 _ => None,
9729 };
9730 let mut guard = SHEXP_WS
9731 .lock()
9732 .map_err(|_| "shexp workspace lock is poisoned")?;
9733 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
9734 if guard
9735 .as_ref()
9736 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
9737 {
9738 *guard = Some((
9739 pins.0,
9740 pins.1,
9741 pins.2,
9742 e.uninit(n_ff_sh)?,
9743 e.uninit(n_ff_sh)?,
9744 e.uninit(n_ff_sh)?,
9745 e.uninit(n_embd)?,
9746 ));
9747 }
9748 {
9751 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9752 let split_on = *ON
9753 .get_or_init(|| std::env::var("MEMRA_SHEXP_SPLIT").as_deref() == Ok("1"));
9754 if split_on {
9755 if let (Some(wd), Some(rank1)) = (
9756 match down_shexp {
9757 crate::model::GpuTensor::FloatBf16 { data, .. } => Some(data),
9758 _ => None,
9759 },
9760 m.step_tp.as_ref().and_then(|st| st.runtime.rank_engine(1)),
9761 ) {
9762 if let Some(sh) = Self::shexp_split_matvec(
9763 e, rank1, wg, wu, wd, z, lim, cfg, il, n_embd, n_ff_sh,
9764 )? {
9765 drop(guard);
9766 let gate = match &m.gate_inp_shexp {
9767 Some(gate_inp_shexp) => e.sigmoid_dot_rows(
9768 z,
9769 gate_inp_shexp.float_data(),
9770 n_embd,
9771 t,
9772 )?,
9773 None => e.htod(&vec![1.0f32; t])?,
9774 };
9775 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9776 return Ok(());
9777 }
9778 }
9779 }
9780 }
9781 let (_, _, _, gate, up, act, sh_buf) =
9782 guard.as_mut().expect("shexp workspace initialized above");
9783 if cfg.m3.is_none() {
9784 e.matvec_bf16_dual_silu_into(wg, wu, z, act, n_embd, n_ff_sh, lim)?;
9787 let _ = (&gate, &up);
9788 } else {
9789 e.matvec_bf16_dual_into(wg, wu, z, gate, up, n_embd, n_ff_sh)?;
9790 Self::ffn_act_lim(e, cfg, gate, up, 1.0, 1.0, lim, act, n_ff_sh)?;
9791 }
9792 if let Some(down) = down_bf16 {
9793 static FUSE_DA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9800 let fuse_da = *FUSE_DA.get_or_init(|| {
9801 std::env::var("MEMRA_FUSE_DOWN_ADDSCALE").as_deref() != Ok("0")
9802 });
9803 if fuse_da && m.gate_inp_shexp.is_none() {
9804 static ONES1: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9805 std::sync::Mutex::new(None);
9806 let mut og = ONES1.lock().map_err(|_| "shexp ones lock is poisoned")?;
9807 if og.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9808 *og = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9809 }
9810 let ones = &og.as_ref().expect("armed above").1;
9811 e.matvec_bf16_down_addscale_into(
9812 down, act, ones, moe_out, n_ff_sh, n_embd,
9813 )?;
9814 return Ok(());
9815 }
9816 e.matvec_bf16_into(down, act, sh_buf, n_ff_sh, n_embd)?;
9817 let sh = e.uninit(n_embd)?;
9818 let mut sh = sh;
9820 {
9821 let mut dst = sh.slice_mut(0..n_embd);
9822 e.stream().memcpy_dtod(&sh_buf.slice(0..n_embd), &mut dst)?;
9823 }
9824 sh
9825 } else {
9826 e.matmul(down_shexp, act, 1)?
9827 }
9828 } else if fused {
9829 let (zq, zd) = e.quantize_q8_1(z, 1, n_embd)?;
9830 let pair = match e.matmul_pre_dual_noscale(gate_shexp, up_shexp, &zq, &zd, 1)? {
9831 Some((gate, up)) => Some((gate, up)),
9832 None => {
9833 match (
9834 e.matmul_pre_noscale(gate_shexp, &zq, &zd, 1)?,
9835 e.matmul_pre_noscale(up_shexp, &zq, &zd, 1)?,
9836 ) {
9837 (Some(gate), Some(up)) => Some((gate, up)),
9838 _ => None,
9839 }
9840 }
9841 };
9842 match pair {
9843 Some(((gate, gs), (up, us))) => {
9844 if e.uses_q8_1_fast(down_shexp) {
9845 let (aq, ad) = e.silu_mul_scaled_q8_1(&gate, &up, gs, us, n_ff_sh)?;
9846 e.matmul_pre(down_shexp, &aq, &ad, &gate, 1)?
9847 } else {
9848 let mut act = e.uninit(n_ff_sh)?;
9849 e.silu_mul_scaled(&gate, &up, gs, us, &mut act, n_ff_sh)?;
9850 e.matmul(down_shexp, &act, 1)?
9851 }
9852 }
9853 None => {
9854 let gate = e.matmul_pre(gate_shexp, &zq, &zd, z, 1)?;
9855 let up = e.matmul_pre(up_shexp, &zq, &zd, z, 1)?;
9856 let mut act = e.uninit(n_ff_sh)?;
9857 Self::ffn_act(e, cfg, &gate, &up, &mut act, n_ff_sh)?;
9858 e.matmul(down_shexp, &act, 1)?
9859 }
9860 }
9861 } else {
9862 let sg_gate = e.matmul(gate_shexp, z, t)?;
9863 let sg_up = e.matmul(up_shexp, z, t)?;
9864 let mut sa = e.uninit(t * n_ff_sh)?;
9865 Self::ffn_act_lim(
9866 e,
9867 cfg,
9868 &sg_gate,
9869 &sg_up,
9870 1.0,
9871 1.0,
9872 lim,
9873 &mut sa,
9874 t * n_ff_sh,
9875 )?;
9876 e.matmul(down_shexp, &sa, t)?
9877 };
9878 let gate = match &m.gate_inp_shexp {
9879 Some(gate_inp_shexp) => {
9880 if t < PRIME_MIN_T || crate::router_prefill_exact_on() {
9881 e.sigmoid_dot_rows(z, gate_inp_shexp.float_data(), n_embd, t)?
9882 } else {
9883 let raw = e.linear(z, gate_inp_shexp.float_data(), t, n_embd, 1)?;
9884 let mut gate = e.uninit(t)?;
9885 e.sigmoid(&raw, &mut gate, t)?;
9886 gate
9887 }
9888 }
9889 None if t == 1 => {
9894 static ONES: std::sync::Mutex<Option<(usize, CudaSlice<f32>)>> =
9895 std::sync::Mutex::new(None);
9896 let mut guard = ONES.lock().map_err(|_| "shexp ones lock is poisoned")?;
9897 if guard.as_ref().is_none_or(|(d, _)| *d != e.ctx().ordinal()) {
9898 *guard = Some((e.ctx().ordinal(), e.htod(&[1.0f32])?));
9899 }
9900 let ones = &guard.as_ref().expect("armed above").1;
9901 e.add_scaled_rows(&sh, ones, moe_out, n_embd, t)?;
9902 return Ok(());
9903 }
9904 None => e.htod(&vec![1.0f32; t])?,
9905 };
9906 e.add_scaled_rows(&sh, &gate, moe_out, n_embd, t)?;
9907 }
9908 Ok(())
9909 }
9910
9911 pub(crate) fn moe_ffn_grouped(
9914 e: &Engine,
9915 m: &MoeWeights,
9916 z: &CudaSlice<f32>,
9917 t: usize,
9918 cfg: &ModelConfig,
9919 il: u16,
9920 max_block: usize,
9921 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9922 let moe = cfg.moe.as_ref().unwrap();
9923 let n_embd = cfg.n_embd as usize;
9924 let n_expert = moe.expert_count as usize;
9925 let n_used = moe.expert_used_count as usize;
9926 let n_ff_exp = moe.expert_ff_length as usize;
9927 let lim_exp = cfg.clamp_exp_at(il as u32);
9929
9930 let logits = Self::moe_router_logits(e, m, z, t, cfg)?;
9934 if let Some(sig) = cfg.sigmoid_router() {
9935 Self::trace_sigmoid_router_logits(e, il, t, n_expert, n_used, &logits, m, sig)?;
9936 }
9937 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
9938 Self::moe_route_sigmoid_cfg(e, &logits, t, n_expert, n_used, m, sig)?
9939 } else {
9940 Self::moe_route_cfg(e, &logits, t, n_expert, n_used, m.active_experts.as_deref())?
9941 };
9942 crate::moesd::record_host_routes(il, n_expert, n_used, &sel_all)?;
9943 Self::trace_moe_routes(il, t, &sel_all, &w_all)?;
9944 Self::trace_moe_input(e, il, t, n_embd, z)?;
9945
9946 let no_exp_macros = m.gate_exps.macros.is_none()
9951 && m.up_exps.macros.is_none()
9952 && m.down_exps.macros.is_none();
9953 let resident_q8 = m.dev_exps.as_ref().filter(|dev| {
9954 m.has_uniform_expert_layout()
9955 && no_exp_macros
9956 && moe_q8_enabled()
9957 && q8_expert_supported(m.gate_exps.qtype)
9958 && q8_expert_supported(m.up_exps.qtype)
9959 && q8_expert_supported(m.down_exps.qtype)
9960 && moe_slab_enabled()
9961 && dev.dev == e.ctx().ordinal()
9962 });
9963 if let Some(dev) = resident_q8 {
9964 let mut moe_out = Self::moe_ffn_grouped_resident_q8(
9965 e,
9966 m,
9967 z,
9968 t,
9969 cfg,
9970 il,
9971 &sel_all,
9972 &w_all,
9973 &dev.ptr_row,
9974 dev.gu_il,
9975 )?;
9976 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
9977 return Ok(moe_out);
9978 }
9979
9980 struct ExpertGroup {
9984 tok_indices: Vec<i32>, slot_indices: Vec<i32>, weights: Vec<f32>, }
9988 let mut groups: Vec<ExpertGroup> = (0..n_expert)
9989 .map(|_| ExpertGroup {
9990 tok_indices: Vec::new(),
9991 slot_indices: Vec::new(),
9992 weights: Vec::new(),
9993 })
9994 .collect();
9995
9996 for tok in 0..t {
9997 for j in 0..n_used {
9998 let ex = sel_all[tok * n_used + j] as usize;
9999 let w = w_all[tok * n_used + j];
10000 groups[ex].tok_indices.push(tok as i32);
10001 groups[ex].slot_indices.push(j as i32);
10002 groups[ex].weights.push(w);
10003 }
10004 }
10005
10006 let mut slot_buf = e.zeros(t * n_used * n_embd)?;
10009 let mut wbuf = e.zeros(t * n_used)?; let g_len = m.gate_exps.max_expert_bytes();
10013 let u_len = m.up_exps.max_expert_bytes();
10014 let d_len = m.down_exps.max_expert_bytes();
10015 let moe_q8 = m.has_uniform_expert_layout()
10016 && moe_q8_enabled()
10017 && q8_expert_supported(m.gate_exps.qtype)
10018 && q8_expert_supported(m.up_exps.qtype)
10019 && q8_expert_supported(m.down_exps.qtype);
10020 let slab_local = m
10023 .dev_exps
10024 .as_ref()
10025 .filter(|dev| !dev.gu_il && moe_slab_enabled() && dev.dev == e.ctx().ordinal());
10026 let use_cache =
10027 slab_local.is_none() && Engine::moe_cache_enabled() && !e.moe_cache_frozen();
10028 let grouped_q8 = moe_q8 && (slab_local.is_some() || use_cache);
10031
10032 let (mut scratch_g, mut scratch_u, mut scratch_d) = if slab_local.is_none() && !use_cache {
10034 (
10035 Some(e.alloc_u8(g_len)?),
10036 Some(e.alloc_u8(u_len)?),
10037 Some(e.alloc_u8(d_len)?),
10038 )
10039 } else {
10040 (None, None, None)
10041 };
10042
10043 let mut order: Vec<usize> = (0..n_expert)
10054 .filter(|&ex| !groups[ex].tok_indices.is_empty())
10055 .collect();
10056 order.sort_by(|&a, &b| {
10057 groups[b]
10058 .tok_indices
10059 .len()
10060 .cmp(&groups[a].tok_indices.len())
10061 .then(a.cmp(&b))
10062 });
10063 let mut m_dist: Vec<usize> = Vec::new(); let page_window = moe_page_prefetch_window();
10065 let worker_disk_prefetch = use_cache && crate::spill_pread::worker_enabled();
10066 if worker_disk_prefetch {
10067 if let Some(first) = grouped_worker_prefetch_position(order.len(), None) {
10068 Self::moe_prefetch_disk_expert(e, il, order[first], m, max_block, &[])?;
10069 }
10070 }
10071 for (order_pos, &ex) in order.iter().enumerate() {
10072 for next in page_prefetch_positions(order_pos, order.len(), page_window) {
10073 Self::moe_prefetch_host_expert(order[next], m);
10074 }
10075 if worker_disk_prefetch {
10076 if let Some(next) = grouped_worker_prefetch_position(order.len(), Some(order_pos)) {
10077 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10078 let keep = [
10079 BlockId::new(il, PROJ_GATE, ex as u16),
10080 BlockId::new(il, PROJ_UP, ex as u16),
10081 BlockId::new(il, PROJ_DOWN, ex as u16),
10082 ];
10083 Self::moe_prefetch_disk_expert(e, il, order[next], m, max_block, &keep)?;
10084 }
10085 }
10086 let grp = &groups[ex];
10087 let m_e = grp.tok_indices.len();
10088 m_dist.push(m_e);
10089 let gl = m.gate_exps.expert_layout(ex);
10090 let ul = m.up_exps.expert_layout(ex);
10091 let dl = m.down_exps.expert_layout(ex);
10092
10093 let tok_idx_d = e.htod_i32(&grp.tok_indices)?;
10097 let slot_idx_d = e.htod_i32(&grp.slot_indices)?;
10098 let dmac = m.down_exps.macro_scale(ex);
10099 let weight_d = if dmac == 1.0 {
10100 e.htod(&grp.weights)?
10101 } else {
10102 let scaled: Vec<f32> = grp.weights.iter().map(|&w| w * dmac).collect();
10103 e.htod(&scaled)?
10104 };
10105
10106 let mut gathered = e.zeros(m_e * n_embd)?;
10108 e.gather_rows(z, &tok_idx_d, &mut gathered, n_embd, m_e)?;
10109 let gv = gathered.slice(0..m_e * n_embd);
10110
10111 let y = if let Some(dev) = slab_local {
10114 let gate_start = ex * m.gate_exps.expert_stride;
10115 let up_start = ex * m.up_exps.expert_stride;
10116 let down_start = ex * m.down_exps.expert_stride;
10117 if grouped_q8 {
10118 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10119 let gate = e.qmatvec_expert_q8(
10120 &dev.gate,
10121 gate_start..gate_start + gl.len,
10122 &zq,
10123 &zd,
10124 m_e,
10125 m.gate_exps.in_f,
10126 m.gate_exps.out_f,
10127 gl.qtype,
10128 gl.row_bytes,
10129 )?;
10130 let up = e.qmatvec_expert_q8(
10131 &dev.up,
10132 up_start..up_start + ul.len,
10133 &zq,
10134 &zd,
10135 m_e,
10136 m.up_exps.in_f,
10137 m.up_exps.out_f,
10138 ul.qtype,
10139 ul.row_bytes,
10140 )?;
10141 let mut act = e.uninit(m_e * n_ff_exp)?;
10142 Self::ffn_act_lim(
10143 e,
10144 cfg,
10145 &gate,
10146 &up,
10147 m.gate_exps.macro_scale(ex),
10148 m.up_exps.macro_scale(ex),
10149 lim_exp,
10150 &mut act,
10151 m_e * n_ff_exp,
10152 )?;
10153 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10154 e.qmatvec_expert_q8(
10155 &dev.down,
10156 down_start..down_start + dl.len,
10157 &aq2,
10158 &ad2,
10159 m_e,
10160 m.down_exps.in_f,
10161 m.down_exps.out_f,
10162 dl.qtype,
10163 dl.row_bytes,
10164 )?
10165 } else {
10166 let gate = e.qmatvec_view(
10167 &dev.gate,
10168 gate_start..gate_start + gl.len,
10169 &gv,
10170 m_e,
10171 m.gate_exps.in_f,
10172 m.gate_exps.out_f,
10173 gl.qtype,
10174 gl.row_bytes,
10175 )?;
10176 let up = e.qmatvec_view(
10177 &dev.up,
10178 up_start..up_start + ul.len,
10179 &gv,
10180 m_e,
10181 m.up_exps.in_f,
10182 m.up_exps.out_f,
10183 ul.qtype,
10184 ul.row_bytes,
10185 )?;
10186 let mut act = e.uninit(m_e * n_ff_exp)?;
10187 Self::ffn_act_lim(
10188 e,
10189 cfg,
10190 &gate,
10191 &up,
10192 m.gate_exps.macro_scale(ex),
10193 m.up_exps.macro_scale(ex),
10194 lim_exp,
10195 &mut act,
10196 m_e * n_ff_exp,
10197 )?;
10198 let actv = act.slice(0..m_e * n_ff_exp);
10199 e.qmatvec_view(
10200 &dev.down,
10201 down_start..down_start + dl.len,
10202 &actv,
10203 m_e,
10204 m.down_exps.in_f,
10205 m.down_exps.out_f,
10206 dl.qtype,
10207 dl.row_bytes,
10208 )?
10209 }
10210 } else if use_cache {
10211 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10212 if grouped_q8 {
10213 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10214 let gate = e.with_moe_cache(max_block, |cache, eng| {
10215 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10216 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10217 eng.qmatvec_expert_q8(
10218 cache.buf(slot),
10219 0..gl.len,
10220 &zq,
10221 &zd,
10222 m_e,
10223 m.gate_exps.in_f,
10224 m.gate_exps.out_f,
10225 gl.qtype,
10226 gl.row_bytes,
10227 )
10228 })?;
10229 let up = e.with_moe_cache(max_block, |cache, eng| {
10230 let id = BlockId::new(il, PROJ_UP, ex as u16);
10231 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10232 eng.qmatvec_expert_q8(
10233 cache.buf(slot),
10234 0..ul.len,
10235 &zq,
10236 &zd,
10237 m_e,
10238 m.up_exps.in_f,
10239 m.up_exps.out_f,
10240 ul.qtype,
10241 ul.row_bytes,
10242 )
10243 })?;
10244 let mut act = e.uninit(m_e * n_ff_exp)?;
10245 Self::ffn_act_lim(
10246 e,
10247 cfg,
10248 &gate,
10249 &up,
10250 m.gate_exps.macro_scale(ex),
10251 m.up_exps.macro_scale(ex),
10252 lim_exp,
10253 &mut act,
10254 m_e * n_ff_exp,
10255 )?;
10256 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10257 e.with_moe_cache(max_block, |cache, eng| {
10258 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10259 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10260 eng.qmatvec_expert_q8(
10261 cache.buf(slot),
10262 0..dl.len,
10263 &aq2,
10264 &ad2,
10265 m_e,
10266 m.down_exps.in_f,
10267 m.down_exps.out_f,
10268 dl.qtype,
10269 dl.row_bytes,
10270 )
10271 })?
10272 } else {
10273 let gate = e.with_moe_cache(max_block, |cache, eng| {
10274 let id = BlockId::new(il, PROJ_GATE, ex as u16);
10275 let slot = cache.dispatch_source(id, m.gate_exps.expert_source(ex), eng)?;
10276 eng.qmatvec_view(
10277 cache.buf(slot),
10278 0..gl.len,
10279 &gv,
10280 m_e,
10281 m.gate_exps.in_f,
10282 m.gate_exps.out_f,
10283 gl.qtype,
10284 gl.row_bytes,
10285 )
10286 })?;
10287 let up = e.with_moe_cache(max_block, |cache, eng| {
10288 let id = BlockId::new(il, PROJ_UP, ex as u16);
10289 let slot = cache.dispatch_source(id, m.up_exps.expert_source(ex), eng)?;
10290 eng.qmatvec_view(
10291 cache.buf(slot),
10292 0..ul.len,
10293 &gv,
10294 m_e,
10295 m.up_exps.in_f,
10296 m.up_exps.out_f,
10297 ul.qtype,
10298 ul.row_bytes,
10299 )
10300 })?;
10301 let mut act = e.uninit(m_e * n_ff_exp)?;
10302 Self::ffn_act_lim(
10303 e,
10304 cfg,
10305 &gate,
10306 &up,
10307 m.gate_exps.macro_scale(ex),
10308 m.up_exps.macro_scale(ex),
10309 lim_exp,
10310 &mut act,
10311 m_e * n_ff_exp,
10312 )?;
10313 let actv = act.slice(0..m_e * n_ff_exp);
10314 e.with_moe_cache(max_block, |cache, eng| {
10315 let id = BlockId::new(il, PROJ_DOWN, ex as u16);
10316 let slot = cache.dispatch_source(id, m.down_exps.expert_source(ex), eng)?;
10317 eng.qmatvec_view(
10318 cache.buf(slot),
10319 0..dl.len,
10320 &actv,
10321 m_e,
10322 m.down_exps.in_f,
10323 m.down_exps.out_f,
10324 dl.qtype,
10325 dl.row_bytes,
10326 )
10327 })?
10328 }
10329 } else {
10330 let sg = scratch_g.as_mut().unwrap();
10331 let su = scratch_u.as_mut().unwrap();
10332 let sd = scratch_d.as_mut().unwrap();
10333 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
10334 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
10335 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
10336 if grouped_q8 {
10337 let (zq, zd) = e.quantize_q8_1(&gathered, m_e, n_embd)?;
10338 let gate = e.qmatvec_expert_q8(
10339 sg,
10340 0..gl.len,
10341 &zq,
10342 &zd,
10343 m_e,
10344 m.gate_exps.in_f,
10345 m.gate_exps.out_f,
10346 gl.qtype,
10347 gl.row_bytes,
10348 )?;
10349 let up = e.qmatvec_expert_q8(
10350 su,
10351 0..ul.len,
10352 &zq,
10353 &zd,
10354 m_e,
10355 m.up_exps.in_f,
10356 m.up_exps.out_f,
10357 ul.qtype,
10358 ul.row_bytes,
10359 )?;
10360 let mut act = e.uninit(m_e * n_ff_exp)?;
10361 Self::ffn_act_lim(
10362 e,
10363 cfg,
10364 &gate,
10365 &up,
10366 m.gate_exps.macro_scale(ex),
10367 m.up_exps.macro_scale(ex),
10368 lim_exp,
10369 &mut act,
10370 m_e * n_ff_exp,
10371 )?;
10372 let (aq2, ad2) = e.quantize_q8_1(&act, m_e, n_ff_exp)?;
10373 e.qmatvec_expert_q8(
10374 sd,
10375 0..dl.len,
10376 &aq2,
10377 &ad2,
10378 m_e,
10379 m.down_exps.in_f,
10380 m.down_exps.out_f,
10381 dl.qtype,
10382 dl.row_bytes,
10383 )?
10384 } else {
10385 let gate = e.qmatvec_view(
10386 sg,
10387 0..gl.len,
10388 &gv,
10389 m_e,
10390 m.gate_exps.in_f,
10391 m.gate_exps.out_f,
10392 gl.qtype,
10393 gl.row_bytes,
10394 )?;
10395 let up = e.qmatvec_view(
10396 su,
10397 0..ul.len,
10398 &gv,
10399 m_e,
10400 m.up_exps.in_f,
10401 m.up_exps.out_f,
10402 ul.qtype,
10403 ul.row_bytes,
10404 )?;
10405 let mut act = e.uninit(m_e * n_ff_exp)?;
10406 Self::ffn_act_lim(
10407 e,
10408 cfg,
10409 &gate,
10410 &up,
10411 m.gate_exps.macro_scale(ex),
10412 m.up_exps.macro_scale(ex),
10413 lim_exp,
10414 &mut act,
10415 m_e * n_ff_exp,
10416 )?;
10417 let actv = act.slice(0..m_e * n_ff_exp);
10418 e.qmatvec_view(
10419 sd,
10420 0..dl.len,
10421 &actv,
10422 m_e,
10423 m.down_exps.in_f,
10424 m.down_exps.out_f,
10425 dl.qtype,
10426 dl.row_bytes,
10427 )?
10428 }
10429 };
10430
10431 e.scatter_slot(
10433 &y,
10434 &tok_idx_d,
10435 &slot_idx_d,
10436 &weight_d,
10437 &mut slot_buf,
10438 &mut wbuf,
10439 n_embd,
10440 n_used,
10441 m_e,
10442 )?;
10443 }
10444
10445 let mut moe_out = e.zeros(t * n_embd)?;
10447 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, t)?;
10448
10449 if std::env::var("MEMRA_MOE_STATS").is_ok() && !m_dist.is_empty() {
10451 m_dist.sort_unstable();
10452 let active = m_dist.len();
10453 let mean = m_dist.iter().sum::<usize>() as f64 / active as f64;
10454 let median = m_dist[active / 2];
10455 let max_m = *m_dist.last().unwrap();
10456 let min_m = m_dist[0];
10457 let above16 = m_dist.iter().filter(|&&x| x >= 16).count();
10458 println!(
10459 "moe-grouped il={il} t={t} active={active}/{n_expert} \
10460 m_e: min={min_m} median={median} mean={mean:.1} max={max_m} \
10461 above_gemm_threshold(>=16)={above16}/{active}"
10462 );
10463 }
10464
10465 Self::moe_ffn_grouped_add_shared(e, m, z, t, cfg, il, &mut moe_out)?;
10466 Ok(moe_out)
10467 }
10468
10469 pub(crate) fn moe_ffn_lockstep(
10476 &self,
10477 e: &Engine,
10478 m: &MoeWeights,
10479 zbatch: &CudaSlice<f32>,
10480 mrows: usize,
10481 il: u16,
10482 max_block: usize,
10483 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10484 use crate::moe_cache::{BlockId, PROJ_DOWN, PROJ_GATE, PROJ_UP};
10485 let cfg = &self.cfg;
10486 let moe = cfg.moe.as_ref().unwrap();
10487 let n_embd = cfg.n_embd as usize;
10488 let n_expert = moe.expert_count as usize;
10489 let n_used = moe.expert_used_count as usize;
10490 let n_ff_exp = moe.expert_ff_length as usize;
10491 let lim_exp = cfg.clamp_exp_at(il as u32);
10493 let lim_shexp = cfg.clamp_shexp_at(il as u32);
10494
10495 let logits = e.matmul(&m.gate_inp, zbatch, mrows)?;
10496 if let Some(sig) = cfg.sigmoid_router() {
10497 Self::trace_sigmoid_router_logits(e, il, mrows, n_expert, n_used, &logits, m, sig)?;
10498 }
10499 let (sel_all, w_all) = if let Some(sig) = cfg.sigmoid_router() {
10500 Self::moe_route_sigmoid_cfg(e, &logits, mrows, n_expert, n_used, m, sig)?
10501 } else {
10502 Self::moe_route_cfg(
10503 e,
10504 &logits,
10505 mrows,
10506 n_expert,
10507 n_used,
10508 m.active_experts.as_deref(),
10509 )?
10510 };
10511 Self::trace_moe_routes(il, mrows, &sel_all, &w_all)?;
10512
10513 let resident_expert: Vec<bool> = e.with_moe_cache(max_block, |c, _| {
10515 Ok((0..n_expert)
10516 .map(|ex| {
10517 [PROJ_GATE, PROJ_UP, PROJ_DOWN]
10518 .into_iter()
10519 .all(|p| c.resident(BlockId::new(il, p, ex as u16)).is_some())
10520 })
10521 .collect())
10522 })?;
10523
10524 struct Group {
10525 rows: Vec<i32>,
10526 slots: Vec<i32>,
10527 weights: Vec<f32>,
10528 }
10529 let mut groups: std::collections::HashMap<usize, Group> = Default::default();
10530 let mut cpu_rows: Vec<Vec<(usize, f32)>> = vec![Vec::new(); mrows];
10531 let mut cpu_by_expert: std::collections::HashMap<usize, Vec<(usize, f32)>> =
10532 Default::default();
10533 for row in 0..mrows {
10534 for j in 0..n_used {
10535 let ex = sel_all[row * n_used + j] as usize;
10536 let w = w_all[row * n_used + j];
10537 if resident_expert[ex] {
10538 let group = groups.entry(ex).or_insert_with(|| Group {
10539 rows: Vec::new(),
10540 slots: Vec::new(),
10541 weights: Vec::new(),
10542 });
10543 group.rows.push(row as i32);
10544 group.slots.push(j as i32);
10545 group.weights.push(w);
10546 } else {
10547 crate::cpu_experts::record_incomplete_gpu_residency(0);
10548 cpu_rows[row].push((ex, w));
10549 cpu_by_expert.entry(ex).or_default().push((row, w));
10550 }
10551 }
10552 }
10553
10554 let host_rows = e.dtoh(zbatch)?;
10560 let rows_ok = crate::cpu_experts::rows_supported();
10561 enum CpuPart {
10562 Single { row: usize },
10563 Rows { rows: Vec<usize> },
10564 }
10565 let mut tickets: Vec<(CpuPart, crate::cpu_experts::CpuExpertTicket)> = Vec::new();
10566 let mut rows_served: std::collections::HashSet<(usize, usize)> = Default::default();
10567 if rows_ok {
10568 let mut shared: Vec<(usize, Vec<(usize, f32)>)> = cpu_by_expert
10569 .into_iter()
10570 .filter(|(_, rows)| rows.len() >= 2)
10571 .collect();
10572 shared.sort_by_key(|(ex, _)| *ex);
10573 for (ex, mut row_weights) in shared {
10574 row_weights.sort_by_key(|(row, _)| *row);
10575 let inputs: Vec<(&[f32], f32)> = row_weights
10576 .iter()
10577 .map(|&(row, w)| (&host_rows[row * n_embd..(row + 1) * n_embd], w))
10578 .collect();
10579 let job = crate::cpu_experts::prepare_rows_job(m, ex, &inputs)
10580 .map_err(std::io::Error::other)?;
10581 for &(row, _) in &row_weights {
10582 rows_served.insert((row, ex));
10583 }
10584 tickets.push((
10585 CpuPart::Rows {
10586 rows: row_weights.iter().map(|&(row, _)| row).collect(),
10587 },
10588 crate::cpu_experts::submit_rows(job).map_err(std::io::Error::other)?,
10589 ));
10590 }
10591 }
10592 for (row, selected) in cpu_rows.iter().enumerate() {
10593 let leftover: Vec<(usize, f32)> = selected
10594 .iter()
10595 .copied()
10596 .filter(|&(ex, _)| !rows_served.contains(&(row, ex)))
10597 .collect();
10598 if leftover.is_empty() {
10599 continue;
10600 }
10601 let host_row = &host_rows[row * n_embd..(row + 1) * n_embd];
10602 let job = crate::cpu_experts::prepare_job(m, il, &leftover, host_row)
10603 .map_err(std::io::Error::other)?;
10604 tickets.push((
10605 CpuPart::Single { row },
10606 crate::cpu_experts::submit(job).map_err(std::io::Error::other)?,
10607 ));
10608 }
10609
10610 let mut slot_buf = e.zeros(mrows * n_used * n_embd)?;
10611 let mut wbuf = e.zeros(mrows * n_used)?;
10612 let mut order: Vec<usize> = groups.keys().copied().collect();
10613 order.sort_by(|&a, &b| {
10614 groups[&b]
10615 .rows
10616 .len()
10617 .cmp(&groups[&a].rows.len())
10618 .then(a.cmp(&b))
10619 });
10620 for &ex in &order {
10621 let group = &groups[&ex];
10622 let m_e = group.rows.len();
10623 let gl = m.gate_exps.expert_layout(ex);
10624 let ul = m.up_exps.expert_layout(ex);
10625 let dl = m.down_exps.expert_layout(ex);
10626 let row_idx_d = e.htod_i32(&group.rows)?;
10627 let slot_idx_d = e.htod_i32(&group.slots)?;
10628 let dmac = m.down_exps.macro_scale(ex);
10629 let weight_d = if dmac == 1.0 {
10630 e.htod(&group.weights)?
10631 } else {
10632 let scaled: Vec<f32> = group.weights.iter().map(|&w| w * dmac).collect();
10633 e.htod(&scaled)?
10634 };
10635 let mut gathered = e.zeros(m_e * n_embd)?;
10636 e.gather_rows(zbatch, &row_idx_d, &mut gathered, n_embd, m_e)?;
10637 let gv = gathered.slice(0..m_e * n_embd);
10638 let gate = e.with_moe_cache(max_block, |c, eng| {
10639 let slot = c
10640 .resident(BlockId::new(il, PROJ_GATE, ex as u16))
10641 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10642 eng.qmatvec_view(
10643 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10644 0..gl.len,
10645 &gv,
10646 m_e,
10647 m.gate_exps.in_f,
10648 m.gate_exps.out_f,
10649 gl.qtype,
10650 gl.row_bytes,
10651 )
10652 })?;
10653 let up = e.with_moe_cache(max_block, |c, eng| {
10654 let slot = c
10655 .resident(BlockId::new(il, PROJ_UP, ex as u16))
10656 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10657 eng.qmatvec_view(
10658 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10659 0..ul.len,
10660 &gv,
10661 m_e,
10662 m.up_exps.in_f,
10663 m.up_exps.out_f,
10664 ul.qtype,
10665 ul.row_bytes,
10666 )
10667 })?;
10668 let mut act = e.zeros(m_e * n_ff_exp)?;
10669 Self::ffn_act_lim(
10670 e,
10671 cfg,
10672 &gate,
10673 &up,
10674 m.gate_exps.macro_scale(ex),
10675 m.up_exps.macro_scale(ex),
10676 lim_exp,
10677 &mut act,
10678 m_e * n_ff_exp,
10679 )?;
10680 let actv = act.slice(0..m_e * n_ff_exp);
10681 let y = e.with_moe_cache(max_block, |c, eng| {
10682 let slot = c
10683 .resident(BlockId::new(il, PROJ_DOWN, ex as u16))
10684 .ok_or("lockstep resident expert vanished (cache not frozen?)")?;
10685 eng.qmatvec_view(
10686 c.buf(crate::moe_cache::DispatchSlot::Resident(slot)),
10687 0..dl.len,
10688 &actv,
10689 m_e,
10690 m.down_exps.in_f,
10691 m.down_exps.out_f,
10692 dl.qtype,
10693 dl.row_bytes,
10694 )
10695 })?;
10696 e.scatter_slot(
10697 &y,
10698 &row_idx_d,
10699 &slot_idx_d,
10700 &weight_d,
10701 &mut slot_buf,
10702 &mut wbuf,
10703 n_embd,
10704 n_used,
10705 m_e,
10706 )?;
10707 }
10708 let mut moe_out = e.zeros(mrows * n_embd)?;
10709 e.reduce_slots(&slot_buf, &wbuf, &mut moe_out, n_embd, n_used, mrows)?;
10710
10711 let mut row_sums: Vec<Option<Vec<f32>>> = vec![None; mrows];
10713 for (part, ticket) in tickets {
10714 let cpu_output = ticket.wait().map_err(std::io::Error::other)?;
10715 let mut add_row = |row: usize, chunk: &[f32]| {
10716 let sum = row_sums[row].get_or_insert_with(|| vec![0.0f32; n_embd]);
10717 for (accumulator, value) in sum.iter_mut().zip(chunk) {
10718 *accumulator += value;
10719 }
10720 };
10721 match part {
10722 CpuPart::Single { row } => add_row(row, &cpu_output),
10723 CpuPart::Rows { rows } => {
10724 for (slot, row) in rows.into_iter().enumerate() {
10725 add_row(row, &cpu_output[slot * n_embd..(slot + 1) * n_embd]);
10726 }
10727 }
10728 }
10729 }
10730 for (row, sum) in row_sums.into_iter().enumerate() {
10731 let Some(sum) = sum else { continue };
10732 let cpu_output = e.htod(&sum)?;
10733 let mut dst = moe_out.slice_mut(row * n_embd..(row + 1) * n_embd);
10734 e.axpy_into(&cpu_output, 1.0, &mut dst, n_embd)?;
10735 }
10736
10737 if let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
10738 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
10739 {
10740 let n_ff_sh = gate_shexp.out_features();
10741 let sg_gate = e.matmul(gate_shexp, zbatch, mrows)?;
10742 let sg_up = e.matmul(up_shexp, zbatch, mrows)?;
10743 let mut sa = e.zeros(mrows * n_ff_sh)?;
10744 Self::ffn_act_lim(
10745 e,
10746 cfg,
10747 &sg_gate,
10748 &sg_up,
10749 1.0,
10750 1.0,
10751 lim_shexp,
10752 &mut sa,
10753 mrows * n_ff_sh,
10754 )?;
10755 let sh = e.matmul(down_shexp, &sa, mrows)?;
10756 let g = match &m.gate_inp_shexp {
10759 Some(gate_inp_shexp) => {
10760 e.sigmoid_dot_rows(zbatch, gate_inp_shexp.float_data(), n_embd, mrows)?
10761 }
10762 None => e.htod(&vec![1.0f32; mrows])?,
10763 };
10764 e.add_scaled_rows(&sh, &g, &mut moe_out, n_embd, mrows)?;
10765 }
10766
10767 Ok(moe_out)
10768 }
10769}
10770
10771impl HybridModel {
10777 pub(crate) fn gemma4_rope_dims(&self, il: usize) -> usize {
10791 let g = self
10792 .cfg
10793 .gemma4
10794 .as_ref()
10795 .expect("gemma4_rope_dims on a non-gemma4 config");
10796 if g.swa_pattern[il] {
10797 g.rope_dims_swa as usize
10798 } else {
10799 g.rope_dims_global as usize
10800 }
10801 }
10802
10803 pub(crate) fn gemma4_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
10804 let g = self.cfg.gemma4.as_ref().unwrap();
10805 let swa = g.swa_pattern[il];
10806 let hd = if swa {
10807 g.key_length_swa
10808 } else {
10809 g.key_length_global
10810 } as usize;
10811 (
10815 hd,
10816 g.head_count_kv[il] as usize,
10817 self.cfg.n_head as usize,
10818 if swa {
10819 g.rope_base_swa
10820 } else {
10821 g.rope_base_global
10822 },
10823 1.0,
10824 swa,
10825 )
10826 }
10827
10828 pub(crate) fn gemma4_suppress(
10832 &self,
10833 e: &Engine,
10834 ld: &mut CudaSlice<f32>,
10835 t: usize,
10836 ) -> Result<(), Box<dyn std::error::Error>> {
10837 if let Some((ids, n)) = self.gemma4_aux.as_ref().and_then(|a| a.suppress_d.as_ref()) {
10838 #[cfg(debug_assertions)]
10843 crate::debug_assert_tensor_stream_device(
10844 ids,
10845 &e.stream(),
10846 "gemma4_suppress.suppress_d",
10847 );
10848 e.mask_ids_rows(ld, ids, *n, self.output.out_features(), t)?;
10849 }
10850 Ok(())
10851 }
10852
10853 #[allow(clippy::too_many_arguments)]
10858 fn gemma_fa_one_program() -> bool {
10867 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10868 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_FA_ONE_PROGRAM").as_deref() == Ok("1"))
10869 }
10870
10871 fn gemma4_attn_prime(
10872 &self,
10873 e: &Engine,
10874 fa: &crate::hybrid::FullAttnLayer,
10875 il: usize,
10876 h: &CudaSlice<f32>,
10877 pos_d: &CudaSlice<i32>,
10878 t: usize,
10879 cache: Option<&mut Cache>,
10880 island: Option<&CudaSlice<i32>>,
10881 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10882 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
10883 let eps = self.cfg.rms_eps;
10884 let aux = self.gemma4_aux.as_ref().unwrap();
10885 let ones = aux.ones(e);
10886 #[cfg(debug_assertions)]
10887 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_attn_prime.ones");
10888
10889 e.mmq_act_begin();
10892 let q0 = e.matmul(&fa.wq, h, t)?; if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10894 let v = e.dtoh(&q0)?;
10895 let nan = v.iter().filter(|x| x.is_nan()).count();
10896 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10897 eprintln!(
10898 "[g4-prime-trace] L0 q0: nan={nan}/{} amax={amax:.3}",
10899 v.len()
10900 );
10901 }
10902 let k0 = e.matmul(&fa.wk, h, t)?; let v0 = if swa {
10906 e.matmul(&fa.wv, h, t)?
10907 } else {
10908 e.clone_dtod(&k0)?
10909 };
10910 if il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
10911 for (tag, buf) in [("k0", &k0), ("v0", &v0)] {
10912 let v = e.dtoh(buf)?;
10913 let nan = v.iter().filter(|x| x.is_nan()).count();
10914 let amax = v.iter().fold(0f32, |a, x| a.max(x.abs()));
10915 eprintln!(
10916 "[g4-prime-trace] L0 {tag}: nan={nan}/{} amax={amax:.3}",
10917 v.len()
10918 );
10919 }
10920 }
10921
10922 let mut q = e.uninit(t * nh * hd)?;
10923 let mut k = e.uninit(t * nkv * hd)?;
10924 let mut v = e.uninit(t * nkv * hd)?;
10926 static EMIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10930 let emit = island.is_none()
10933 && t >= 16
10934 && crate::Engine::qkvnorm_w_on_prefill(nh * t + 2 * nkv * t, hd)
10935 && *EMIT.get_or_init(|| {
10936 std::env::var("MEMRA_FA_EMIT")
10937 .map(|s| s != "0")
10938 .unwrap_or(true)
10939 });
10940 let mut qb = e.alloc_uninit::<u8>(if emit { t * nh * hd * 2 } else { 1 })?;
10941 let mut kb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10942 let mut vb = e.alloc_uninit::<u8>(if emit { t * nkv * hd * 2 } else { 1 })?;
10943 let v_f16 = emit
10946 && crate::fa_f16pv_on()
10947 && match hd {
10948 512 => true,
10949 256 => swa && crate::faw_hp_on() && nh % 2 == 0 && (nh / nkv) % 2 == 0,
10950 _ => false,
10951 };
10952 if emit {
10953 e.rms_norm_qkv_w4b(
10954 &q0,
10955 &k0,
10956 &v0,
10957 fa.q_norm.float_data(),
10958 fa.k_norm.float_data(),
10959 ones,
10960 &mut q,
10961 &mut k,
10962 &mut v,
10963 &mut vb,
10964 hd,
10965 nh * t,
10966 nkv * t,
10967 eps,
10968 v_f16,
10969 )?;
10970 } else {
10971 e.rms_norm_qkv(
10972 &q0,
10973 &k0,
10974 &v0,
10975 fa.q_norm.float_data(),
10976 fa.k_norm.float_data(),
10977 ones,
10978 &mut q,
10979 &mut k,
10980 &mut v,
10981 hd,
10982 nh * t,
10983 nkv * t,
10984 eps,
10985 )?;
10986 }
10987
10988 let ff = if swa {
10989 None
10990 } else {
10991 Some(
10992 aux.rope_freqs(e)
10993 .expect("gemma4 global rope needs rope_freqs.weight"),
10994 )
10995 };
10996 #[cfg(debug_assertions)]
10997 if let Some(ff) = ff {
10998 crate::debug_assert_tensor_stream_device(
10999 ff,
11000 &e.stream(),
11001 "gemma4_attn_prime.rope_freqs",
11002 );
11003 }
11004 if emit {
11005 e.rope_neox2_bf16e(
11006 &mut q, &mut k, &mut qb, &mut kb, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff,
11007 )?;
11008 } else {
11009 e.rope_neox2(&mut q, &mut k, pos_d, hd, hd, nh, nkv, t, base, 1.0, ff)?;
11010 }
11011
11012 if let Some(cache) = cache {
11013 let kvl = cache.kv[il].as_mut().unwrap();
11014 assert_eq!(kvl.len, 0, "gemma4 prime is fresh-prompt only (v0)");
11015 e.append_kv_quantized_rows(
11016 &k,
11017 &v,
11018 &mut kvl.k,
11019 &mut kvl.v,
11020 kvl.len,
11021 t,
11022 kvl.kv_dim_k,
11023 kvl.kv_dim_v,
11024 kvl.k_tok_bytes,
11025 kvl.v_tok_bytes,
11026 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
11027 )?;
11028 kvl.len += t;
11029 }
11030 let mut attn = e.zeros(t * nh * hd)?;
11031 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
11035 if let Some(span) = island {
11036 let w = if swa && t > win { win } else { 0 };
11041 e.sdpa_naive_island(&q, &k, &v, &mut attn, span, hd, nh, nkv, t, t, scale, w)?;
11042 } else if swa && (t > win || Self::gemma_fa_one_program()) {
11043 if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11044 if emit {
11045 e.fa_prefill_w_pre(
11046 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, win, v_f16,
11047 )?;
11048 } else {
11049 e.fa_prefill_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11050 }
11051 } else {
11052 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
11053 }
11054 } else if hd == 256 && std::env::var("MEMRA_NOFA").is_err() {
11055 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11056 } else if hd == 512 && std::env::var("MEMRA_NOFA").is_err() {
11057 if emit {
11058 e.fa_prefill_hd512_pre(
11059 &qb, &kb, &vb, &mut attn, hd, nh, nkv, t, t, scale, true, v_f16,
11060 )?;
11061 } else {
11062 e.fa_prefill_hd512(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11063 }
11064 } else {
11065 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
11066 }
11067 Ok(e.matmul(&fa.wo, &attn, t)?)
11068 }
11069
11070 fn gemma4_attn(
11072 &self,
11073 e: &Engine,
11074 fa: &crate::hybrid::FullAttnLayer,
11075 il: usize,
11076 h: &CudaSlice<f32>,
11077 pos_d: &CudaSlice<i32>,
11078 t: usize,
11079 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11080 self.gemma4_attn_prime(e, fa, il, h, pos_d, t, None, None)
11081 }
11082
11083 fn gemma4_moe_q8(
11088 &self,
11089 e: &Engine,
11090 m: &crate::hybrid::MoeWeights,
11091 bits: &crate::hybrid::Gemma4MoeBits,
11092 mq: &(CudaSlice<i8>, CudaSlice<f32>),
11093 router_in: &CudaSlice<f32>,
11094 t: usize,
11095 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11096 let cfg = &self.cfg;
11097 let moe = cfg.moe.as_ref().unwrap();
11098 let n_embd = cfg.n_embd as usize;
11099 let n_expert = moe.expert_count as usize;
11100 let n_used = moe.expert_used_count as usize;
11101 let n_ff_exp = moe.expert_ff_length as usize;
11102 let logits = if crate::router_kernel_on() {
11106 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11107 } else {
11108 e.matmul(&m.gate_inp, router_in, t)?
11109 };
11110 let dev = m.dev_exps.as_ref().unwrap();
11111 let (sel_d, w_d) =
11112 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11113 let (zq, zd) = mq;
11114 if t == 1 {
11115 let selv = sel_d.slice(0..n_used);
11116 let wv = w_d.slice(0..n_used);
11117 let act = e.moe_gate_up_gelu8_dev_q8(
11118 &dev.ptr_row,
11119 &selv,
11120 zq,
11121 zd,
11122 n_embd,
11123 n_ff_exp,
11124 n_used,
11125 n_expert,
11126 m.gate_exps.qtype,
11127 m.up_exps.qtype,
11128 m.gate_exps.row_bytes,
11129 m.up_exps.row_bytes,
11130 )?;
11131 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11132 let mut moe_out = e.uninit(n_embd)?;
11133 e.moe_down8_fma_dev_q8(
11134 &dev.ptr_row,
11135 &selv,
11136 &wv,
11137 &aq2,
11138 &ad2,
11139 &mut moe_out.slice_mut(0..n_embd),
11140 n_ff_exp,
11141 n_embd,
11142 n_used,
11143 n_expert,
11144 m.down_exps.qtype,
11145 m.down_exps.row_bytes,
11146 )?;
11147 return Ok(moe_out);
11148 }
11149 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11150 let act = if csr {
11151 e.moe_gate_up_gelu8_dev_q8_csr(
11152 &dev.ptr_row,
11153 &sel_d,
11154 zq,
11155 zd,
11156 t * n_used,
11157 n_embd,
11158 n_ff_exp,
11159 n_used,
11160 n_expert,
11161 m.gate_exps.qtype,
11162 m.up_exps.qtype,
11163 m.gate_exps.row_bytes,
11164 m.up_exps.row_bytes,
11165 )?
11166 } else {
11167 e.moe_gate_up_gelu8_dev_q8_rows(
11168 &dev.ptr_row,
11169 &sel_d,
11170 zq,
11171 zd,
11172 t,
11173 n_embd,
11174 n_ff_exp,
11175 n_used,
11176 n_expert,
11177 m.gate_exps.qtype,
11178 m.up_exps.qtype,
11179 m.gate_exps.row_bytes,
11180 m.up_exps.row_bytes,
11181 )?
11182 };
11183 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11184 let mut moe_out = e.uninit(t * n_embd)?;
11185 e.moe_down8_fma_dev_q8_rows_g(
11188 &dev.ptr_row,
11189 &sel_d,
11190 &w_d,
11191 &aq2,
11192 &ad2,
11193 &mut moe_out,
11194 t,
11195 n_ff_exp,
11196 n_embd,
11197 n_used,
11198 n_expert,
11199 m.down_exps.qtype,
11200 m.down_exps.row_bytes,
11201 )?;
11202 Ok(moe_out)
11203 }
11204
11205 fn gemma4_moe(
11209 &self,
11210 e: &Engine,
11211 m: &crate::hybrid::MoeWeights,
11212 bits: &crate::hybrid::Gemma4MoeBits,
11213 moe_in: &CudaSlice<f32>,
11214 router_in: &CudaSlice<f32>,
11215 t: usize,
11216 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11217 let cfg = &self.cfg;
11218 let moe = cfg.moe.as_ref().unwrap();
11219 let n_embd = cfg.n_embd as usize;
11220 let n_expert = moe.expert_count as usize;
11221 let n_used = moe.expert_used_count as usize;
11222 let n_ff_exp = moe.expert_ff_length as usize;
11223
11224 let logits = if t < PRIME_MIN_T && crate::router_kernel_on() {
11228 e.router_gemv(m.gate_inp.float_data(), router_in, n_embd, n_expert, t)?
11229 } else {
11230 e.matmul(&m.gate_inp, router_in, t)?
11231 };
11232
11233 if t < PRIME_MIN_T
11238 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11239 && expert_dp4a_supported(m.gate_exps.qtype)
11240 && expert_dp4a_supported(m.up_exps.qtype)
11241 && expert_dp4a_supported(m.down_exps.qtype)
11242 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11243 {
11244 let dev = m.dev_exps.as_ref().unwrap();
11245 let (sel_d, w_d) =
11246 e.moe_router_topk_scaled(&logits, t, n_expert, n_used, &bits.per_expert_scale_d)?;
11247 if t == 1 {
11248 let (zq, zd) = e.quantize_q8_1(moe_in, 1, n_embd)?;
11249 let selv = sel_d.slice(0..n_used);
11250 let wv = w_d.slice(0..n_used);
11251 let act = e.moe_gate_up_gelu8_dev_q8(
11252 &dev.ptr_row,
11253 &selv,
11254 &zq,
11255 &zd,
11256 n_embd,
11257 n_ff_exp,
11258 n_used,
11259 n_expert,
11260 m.gate_exps.qtype,
11261 m.up_exps.qtype,
11262 m.gate_exps.row_bytes,
11263 m.up_exps.row_bytes,
11264 )?;
11265 let (aq2, ad2) = e.quantize_q8_1(&act, n_used, n_ff_exp)?;
11266 let mut moe_out = e.uninit(n_embd)?;
11267 e.moe_down8_fma_dev_q8(
11268 &dev.ptr_row,
11269 &selv,
11270 &wv,
11271 &aq2,
11272 &ad2,
11273 &mut moe_out.slice_mut(0..n_embd),
11274 n_ff_exp,
11275 n_embd,
11276 n_used,
11277 n_expert,
11278 m.down_exps.qtype,
11279 m.down_exps.row_bytes,
11280 )?;
11281 return Ok(moe_out);
11282 }
11283 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11288 let csr = t <= 10 && std::env::var("MEMRA_GEMMA_CSR").as_deref() != Ok("0");
11289 let act = if csr {
11290 e.moe_gate_up_gelu8_dev_q8_csr(
11291 &dev.ptr_row,
11292 &sel_d,
11293 &zq,
11294 &zd,
11295 t * n_used,
11296 n_embd,
11297 n_ff_exp,
11298 n_used,
11299 n_expert,
11300 m.gate_exps.qtype,
11301 m.up_exps.qtype,
11302 m.gate_exps.row_bytes,
11303 m.up_exps.row_bytes,
11304 )?
11305 } else {
11306 e.moe_gate_up_gelu8_dev_q8_rows(
11307 &dev.ptr_row,
11308 &sel_d,
11309 &zq,
11310 &zd,
11311 t,
11312 n_embd,
11313 n_ff_exp,
11314 n_used,
11315 n_expert,
11316 m.gate_exps.qtype,
11317 m.up_exps.qtype,
11318 m.gate_exps.row_bytes,
11319 m.up_exps.row_bytes,
11320 )?
11321 };
11322 let (aq2, ad2) = e.quantize_q8_1(&act, t * n_used, n_ff_exp)?;
11323 let mut moe_out = e.uninit(t * n_embd)?;
11324 e.moe_down8_fma_dev_q8_rows_g(
11325 &dev.ptr_row,
11326 &sel_d,
11327 &w_d,
11328 &aq2,
11329 &ad2,
11330 &mut moe_out,
11331 t,
11332 n_ff_exp,
11333 n_embd,
11334 n_used,
11335 n_expert,
11336 m.down_exps.qtype,
11337 m.down_exps.row_bytes,
11338 )?;
11339 return Ok(moe_out);
11340 }
11341
11342 let (sel_all, mut w_all) = Self::moe_route(e, &logits, t, n_expert, n_used)?;
11343 for (i, &sx) in sel_all.iter().enumerate() {
11344 w_all[i] *= bits.per_expert_scale[sx as usize];
11345 }
11346
11347 if t >= PRIME_MIN_T
11351 && m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11352 && expert_dp4a_supported(m.gate_exps.qtype)
11353 && expert_dp4a_supported(m.up_exps.qtype)
11354 && expert_dp4a_supported(m.down_exps.qtype)
11355 && std::env::var("MEMRA_GEMMA_MOE_PAIRS").as_deref() != Ok("0")
11356 {
11357 let dev = m.dev_exps.as_ref().unwrap();
11358 let n_pairs = t * n_used;
11359 let pair_ex: Vec<i32> = sel_all.iter().map(|&x| x as i32).collect();
11360 let pair_tok: Vec<i32> = (0..n_pairs).map(|p| (p / n_used) as i32).collect();
11361 let tok_off: Vec<i32> = (0..=t).map(|tok| (tok * n_used) as i32).collect();
11362 let tok_ids: Vec<i32> = (0..n_pairs as i32).collect();
11363 let pt = e.htod_i32(&pair_tok)?;
11364 let pw = e.htod(&w_all)?;
11365 let toff = e.htod_i32(&tok_off)?;
11366 let tids = e.htod_i32(&tok_ids)?;
11367 let mut by_ex: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11368 for p in 0..n_pairs {
11369 by_ex[pair_ex[p] as usize].push(p as i32);
11370 }
11371 let mut ex_ids: Vec<i32> = Vec::new();
11372 let mut ex_off: Vec<i32> = vec![0];
11373 let mut ex_pairs: Vec<i32> = Vec::with_capacity(n_pairs);
11374 for (ex, list) in by_ex.iter().enumerate() {
11375 if list.is_empty() {
11376 continue;
11377 }
11378 ex_ids.push(ex as i32);
11379 ex_pairs.extend_from_slice(list);
11380 ex_off.push(ex_pairs.len() as i32);
11381 }
11382 let n_active = ex_ids.len();
11383 let exi = e.htod_i32(&ex_ids)?;
11384 let exo = e.htod_i32(&ex_off)?;
11385 let exp_d = e.htod_i32(&ex_pairs)?;
11386 if crate::moe_f16g_gemma_on()
11394 && f16g_proj_ok(m.gate_exps.qtype, n_embd)
11395 && f16g_proj_ok(m.up_exps.qtype, n_embd)
11396 && f16g_proj_ok(m.down_exps.qtype, n_ff_exp)
11397 {
11398 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11399 let csr_tok_d = e.htod_i32(&csr_tok)?;
11400 let (z_f16, z_s) = e.moe_f16g_act(moe_in, Some(&csr_tok_d), n_embd, n_pairs)?;
11401 let g_csr = e.moe_f16_grouped(
11402 &dev.ptr_row,
11403 0,
11404 n_expert,
11405 &exi,
11406 &ex_off,
11407 &exo,
11408 &z_f16,
11409 &z_s,
11410 n_embd,
11411 n_ff_exp,
11412 n_active,
11413 n_pairs,
11414 m.gate_exps.qtype,
11415 m.gate_exps.row_bytes,
11416 )?;
11417 let u_csr = e.moe_f16_grouped(
11418 &dev.ptr_row,
11419 1,
11420 n_expert,
11421 &exi,
11422 &ex_off,
11423 &exo,
11424 &z_f16,
11425 &z_s,
11426 n_embd,
11427 n_ff_exp,
11428 n_active,
11429 n_pairs,
11430 m.up_exps.qtype,
11431 m.up_exps.row_bytes,
11432 )?;
11433 let act_csr = e.moe_pairs_gelu_mul(&g_csr, &u_csr, n_pairs * n_ff_exp)?;
11434 let (a_f16, a_s) = e.moe_f16g_act(&act_csr, None, n_ff_exp, n_pairs)?;
11435 let d_csr = e.moe_f16_grouped(
11436 &dev.ptr_row,
11437 2,
11438 n_expert,
11439 &exi,
11440 &ex_off,
11441 &exo,
11442 &a_f16,
11443 &a_s,
11444 n_ff_exp,
11445 n_embd,
11446 n_active,
11447 n_pairs,
11448 m.down_exps.qtype,
11449 m.down_exps.row_bytes,
11450 )?;
11451 let y_down = e.rows_permute(&d_csr, &exp_d, n_pairs, n_embd)?;
11452 let mut moe_out = e.uninit(t * n_embd)?;
11453 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11454 if std::env::var("MEMRA_F16G_DEBUG").is_ok() {
11455 let scan = |v: &[f32]| v.iter().filter(|x| !x.is_finite()).count();
11456 let (yd, mo) = (e.dtoh(&y_down)?, e.dtoh(&moe_out)?);
11457 eprintln!(
11458 "[f16g-debug] post-permute bad={} post-scatter bad={}",
11459 scan(&yd),
11460 scan(&mo)
11461 );
11462 }
11463 return Ok(moe_out);
11464 }
11465 let mma =
11468 n_embd % 256 == 0 && std::env::var("MEMRA_GEMMA_MOE_MMA").as_deref() != Ok("0");
11469 let (gate, up) = if mma {
11470 let z_scr = e.mmq_iq_quantize_act(moe_in, n_embd, t)?;
11471 (
11472 e.mmq_iq_experts(
11473 &dev.ptr_row,
11474 0,
11475 n_expert,
11476 &exi,
11477 &exo,
11478 &exp_d,
11479 &pt,
11480 &z_scr,
11481 n_embd,
11482 n_ff_exp,
11483 n_active,
11484 n_pairs,
11485 t,
11486 m.gate_exps.qtype,
11487 m.gate_exps.row_bytes,
11488 )?,
11489 e.mmq_iq_experts(
11490 &dev.ptr_row,
11491 1,
11492 n_expert,
11493 &exi,
11494 &exo,
11495 &exp_d,
11496 &pt,
11497 &z_scr,
11498 n_embd,
11499 n_ff_exp,
11500 n_active,
11501 n_pairs,
11502 t,
11503 m.up_exps.qtype,
11504 m.up_exps.row_bytes,
11505 )?,
11506 )
11507 } else {
11508 let (zq, zd) = e.quantize_q8_1(moe_in, t, n_embd)?;
11509 (
11510 e.moe_pairs_matvec_q8_dec(
11511 &dev.ptr_row,
11512 0,
11513 &exi,
11514 &exo,
11515 &exp_d,
11516 &pt,
11517 &zq,
11518 &zd,
11519 n_embd,
11520 n_ff_exp,
11521 n_expert,
11522 n_active,
11523 n_pairs,
11524 m.gate_exps.qtype,
11525 m.gate_exps.row_bytes,
11526 )?,
11527 e.moe_pairs_matvec_q8_dec(
11528 &dev.ptr_row,
11529 1,
11530 &exi,
11531 &exo,
11532 &exp_d,
11533 &pt,
11534 &zq,
11535 &zd,
11536 n_embd,
11537 n_ff_exp,
11538 n_expert,
11539 n_active,
11540 n_pairs,
11541 m.up_exps.qtype,
11542 m.up_exps.row_bytes,
11543 )?,
11544 )
11545 };
11546 let pair_self: Vec<i32> = (0..n_pairs as i32).collect();
11547 let pself = e.htod_i32(&pair_self)?;
11548 let y_down = if mma {
11560 let in_pad = n_ff_exp.div_ceil(256) * 256;
11561 let a_scr = if crate::moe_fuse_actq_on() {
11562 e.mmq_iq_fused_act_quant(&gate, &up, n_ff_exp, n_pairs, 1)?
11563 } else {
11564 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11565 e.mmq_iq_quantize_act(&act, n_ff_exp, n_pairs)?
11566 };
11567 e.mmq_iq_experts(
11568 &dev.ptr_row,
11569 2,
11570 n_expert,
11571 &exi,
11572 &exo,
11573 &exp_d,
11574 &pself,
11575 &a_scr,
11576 in_pad,
11577 n_embd,
11578 n_active,
11579 n_pairs,
11580 n_pairs,
11581 m.down_exps.qtype,
11582 m.down_exps.row_bytes,
11583 )?
11584 } else {
11585 let act = e.moe_pairs_gelu_mul(&gate, &up, n_pairs * n_ff_exp)?;
11586 let (aq2, ad2) = e.quantize_q8_1(&act, n_pairs, n_ff_exp)?;
11587 e.moe_pairs_matvec_q8_dec(
11588 &dev.ptr_row,
11589 2,
11590 &exi,
11591 &exo,
11592 &exp_d,
11593 &pself,
11594 &aq2,
11595 &ad2,
11596 n_ff_exp,
11597 n_embd,
11598 n_expert,
11599 n_active,
11600 n_pairs,
11601 m.down_exps.qtype,
11602 m.down_exps.row_bytes,
11603 )?
11604 };
11605 let mut moe_out = e.uninit(t * n_embd)?;
11606 e.moe_pairs_scatter(&y_down, &pw, &toff, &tids, &mut moe_out, t, n_embd)?;
11607 return Ok(moe_out);
11608 }
11609
11610 let g_len = m.gate_exps.expert_stride;
11611 let u_len = m.up_exps.expert_stride;
11612 let d_len = m.down_exps.expert_stride;
11613 let dev = m.dev_exps.as_ref().filter(|d| !d.gu_il);
11617 let (mut sg, mut su, mut sd) = if dev.is_some() {
11618 (None, None, None)
11619 } else {
11620 (
11621 Some(e.alloc_u8_uninit(g_len)?),
11622 Some(e.alloc_u8_uninit(u_len)?),
11623 Some(e.alloc_u8_uninit(d_len)?),
11624 )
11625 };
11626 let mut moe_out = e.zeros(t * n_embd)?;
11627 for tok in 0..t {
11628 let sel = &sel_all[tok * n_used..(tok + 1) * n_used];
11629 let w = &w_all[tok * n_used..(tok + 1) * n_used];
11630 let zt = moe_in.slice(tok * n_embd..(tok + 1) * n_embd);
11631 for (j, &ex) in sel.iter().enumerate() {
11632 let ex = ex as usize;
11633 let gate = match dev {
11634 Some(d) => e.qmatvec_view(
11635 &d.gate,
11636 ex * g_len..(ex + 1) * g_len,
11637 &zt,
11638 1,
11639 m.gate_exps.in_f,
11640 m.gate_exps.out_f,
11641 m.gate_exps.qtype,
11642 m.gate_exps.row_bytes,
11643 )?,
11644 None => {
11645 let sg = sg.as_mut().unwrap();
11646 e.stage_expert(m.gate_exps.expert_bytes(ex), sg, 0)?;
11647 e.qmatvec_view(
11648 sg,
11649 0..g_len,
11650 &zt,
11651 1,
11652 m.gate_exps.in_f,
11653 m.gate_exps.out_f,
11654 m.gate_exps.qtype,
11655 m.gate_exps.row_bytes,
11656 )?
11657 }
11658 };
11659 let up = match dev {
11660 Some(d) => e.qmatvec_view(
11661 &d.up,
11662 ex * u_len..(ex + 1) * u_len,
11663 &zt,
11664 1,
11665 m.up_exps.in_f,
11666 m.up_exps.out_f,
11667 m.up_exps.qtype,
11668 m.up_exps.row_bytes,
11669 )?,
11670 None => {
11671 let su = su.as_mut().unwrap();
11672 e.stage_expert(m.up_exps.expert_bytes(ex), su, 0)?;
11673 e.qmatvec_view(
11674 su,
11675 0..u_len,
11676 &zt,
11677 1,
11678 m.up_exps.in_f,
11679 m.up_exps.out_f,
11680 m.up_exps.qtype,
11681 m.up_exps.row_bytes,
11682 )?
11683 }
11684 };
11685 let mut act = e.uninit(n_ff_exp)?;
11686 e.gelu_tanh_mul(&gate, &up, &mut act, n_ff_exp)?;
11687 let actv = act.slice(0..n_ff_exp);
11688 let y = match dev {
11689 Some(d) => e.qmatvec_view(
11690 &d.down,
11691 ex * d_len..(ex + 1) * d_len,
11692 &actv,
11693 1,
11694 m.down_exps.in_f,
11695 m.down_exps.out_f,
11696 m.down_exps.qtype,
11697 m.down_exps.row_bytes,
11698 )?,
11699 None => {
11700 let sd = sd.as_mut().unwrap();
11701 e.stage_expert(m.down_exps.expert_bytes(ex), sd, 0)?;
11702 e.qmatvec_view(
11703 sd,
11704 0..d_len,
11705 &actv,
11706 1,
11707 m.down_exps.in_f,
11708 m.down_exps.out_f,
11709 m.down_exps.qtype,
11710 m.down_exps.row_bytes,
11711 )?
11712 }
11713 };
11714 let mut dst = moe_out.slice_mut(tok * n_embd..(tok + 1) * n_embd);
11715 e.axpy_into(&y, w[j], &mut dst, n_embd)?;
11716 }
11717 }
11718 Ok(moe_out)
11719 }
11720
11721 fn gemma4_layer(
11723 &self,
11724 e: &Engine,
11725 il: usize,
11726 layer: &crate::hybrid::HybridLayer,
11727 x: &CudaSlice<f32>,
11728 pos_d: &CudaSlice<i32>,
11729 t: usize,
11730 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11731 let n_embd = self.cfg.n_embd as usize;
11732 let eps = self.cfg.rms_eps;
11733
11734 let mut h = e.zeros(t * n_embd)?;
11735 e.rms_norm(x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
11736 let Mixer::Full(fa) = &layer.mixer else {
11737 panic!("gemma4 layer {il} not full-attn")
11738 };
11739 let o = self.gemma4_attn(e, fa, il, &h, pos_d, t)?;
11740 let mut cur = e.zeros(t * n_embd)?;
11742 e.rms_norm(
11743 &o,
11744 layer.post_attn_norm.float_data(),
11745 &mut cur,
11746 n_embd,
11747 t,
11748 eps,
11749 )?;
11750 self.gemma4_layer_tail_add(e, layer, &cur, x, t)
11751 }
11752
11753 fn gemma4_layer_tail_add(
11757 &self,
11758 e: &Engine,
11759 layer: &crate::hybrid::HybridLayer,
11760 cur: &CudaSlice<f32>,
11761 x: &CudaSlice<f32>,
11762 t: usize,
11763 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11764 Ok(self.gemma4_layer_tail_add_n(e, layer, cur, x, t, None)?.0)
11765 }
11766
11767 fn gemma4_layer_tail_add_n(
11770 &self,
11771 e: &Engine,
11772 layer: &crate::hybrid::HybridLayer,
11773 cur: &CudaSlice<f32>,
11774 x: &CudaSlice<f32>,
11775 t: usize,
11776 next_norm: Option<&CudaSlice<f32>>,
11777 ) -> Result<(CudaSlice<f32>, Option<CudaSlice<f32>>), Box<dyn std::error::Error>> {
11778 let n_embd = self.cfg.n_embd as usize;
11779 let bits = layer.gemma4.as_ref().unwrap();
11780 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
11781 let mut xn = e.uninit(t * n_embd)?;
11782 match next_norm {
11783 Some(w) => {
11784 let mut hn = e.uninit(t * n_embd)?;
11785 e.add_scale_rms_norm(
11786 &sn,
11787 &attn_out,
11788 bits.layer_scale,
11789 w,
11790 &mut xn,
11791 &mut hn,
11792 n_embd,
11793 t,
11794 self.cfg.rms_eps,
11795 )?;
11796 Ok((xn, Some(hn)))
11797 }
11798 None => {
11799 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
11800 Ok((xn, None))
11801 }
11802 }
11803 }
11804
11805 fn gemma4_layer_tail_core(
11808 &self,
11809 e: &Engine,
11810 layer: &crate::hybrid::HybridLayer,
11811 cur: &CudaSlice<f32>,
11812 x: &CudaSlice<f32>,
11813 t: usize,
11814 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11815 self.gemma4_layer_tail_core_pn(e, layer, cur, x, t, None, false)
11816 }
11817
11818 fn gemma4_layer_tail_core_pn(
11825 &self,
11826 e: &Engine,
11827 layer: &crate::hybrid::HybridLayer,
11828 cur: &CudaSlice<f32>,
11829 x: &CudaSlice<f32>,
11830 t: usize,
11831 pre_norm: Option<&CudaSlice<f32>>,
11832 defer_post_norm: bool,
11833 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11834 let n_embd = self.cfg.n_embd as usize;
11835 let eps = self.cfg.rms_eps;
11836 let bits = layer.gemma4.as_ref().unwrap();
11837
11838 let Some(mbits) = bits.moe_bits.as_ref() else {
11841 let crate::hybrid::Ffn::Dense {
11842 ffn_gate,
11843 ffn_up,
11844 ffn_down,
11845 } = &layer.ffn
11846 else {
11847 panic!("gemma4 dense layer without Dense ffn")
11848 };
11849 let mut attn_out = e.uninit(t * n_embd)?;
11850 let mut zsh = e.uninit(t * n_embd)?;
11851 let mut zpair: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
11854 match pre_norm {
11855 Some(wa) if t == 1 => {
11856 zpair = Some(e.rms_pre_add_rms_norm_q8z(
11857 cur,
11858 wa,
11859 x,
11860 bits.ffn_norm.float_data(),
11861 &mut attn_out,
11862 &mut zsh,
11863 n_embd,
11864 t,
11865 eps,
11866 )?);
11867 }
11868 Some(wa) => e.rms_pre_add_rms_norm(
11869 cur,
11870 wa,
11871 x,
11872 bits.ffn_norm.float_data(),
11873 &mut attn_out,
11874 &mut zsh,
11875 n_embd,
11876 t,
11877 eps,
11878 )?,
11879 None => e.add_rms_norm(
11880 cur,
11881 x,
11882 bits.ffn_norm.float_data(),
11883 &mut attn_out,
11884 &mut zsh,
11885 n_embd,
11886 t,
11887 eps,
11888 )?,
11889 }
11890 let n_ff = ffn_gate.out_features();
11891 let (gate, up) = if t == 1 {
11897 let (zq, zd) = match zpair {
11898 Some(p) => p,
11899 None => e.quantize_q8_1(&zsh, 1, n_embd)?,
11900 };
11901 match e.matmul_q4_fused2(ffn_gate, ffn_up, &zq, &zd)? {
11902 Some(p) => p,
11903 None => match e.matmul_nvfp4_fused2(ffn_gate, ffn_up, &zq, &zd, 1)? {
11905 Some(p) => p,
11906 None => (
11907 e.matmul_pre(ffn_gate, &zq, &zd, &zsh, 1)?,
11908 e.matmul_pre(ffn_up, &zq, &zd, &zsh, 1)?,
11909 ),
11910 },
11911 }
11912 } else {
11913 static F2B: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11918 let f2b = *F2B.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
11919 let fused = if f2b {
11920 let (zq, zd) = e.quantize_q8_1(&zsh, t, n_embd)?;
11921 e.matmul_q4_fused2_batched(ffn_gate, ffn_up, &zq, &zd, t)?
11922 } else {
11923 None
11924 };
11925 match fused {
11926 Some(p) => p,
11927 None => {
11928 e.mmq_act_begin();
11930 (e.matmul(ffn_gate, &zsh, t)?, e.matmul(ffn_up, &zsh, t)?)
11931 }
11932 }
11933 };
11934 let mut act = e.uninit(t * n_ff)?;
11935 let f0 = if e.uses_q8_1_fast(ffn_down) {
11938 let upv = e.view(&up, t * n_ff);
11939 let up_all = upv.slice(0..t * n_ff);
11940 let (aq, ad) = e.gelu_tanh_mul_q8_1(&gate, &up_all, &mut act, n_ff, t)?;
11941 e.matmul_pre(ffn_down, &aq, &ad, &act, t)?
11942 } else {
11943 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
11944 e.matmul(ffn_down, &act, t)?
11945 };
11946 if defer_post_norm {
11947 return Ok((f0, attn_out));
11948 }
11949 let mut sn = e.uninit(t * n_embd)?;
11950 e.rms_norm(
11951 &f0,
11952 bits.post_ffw_norm.float_data(),
11953 &mut sn,
11954 n_embd,
11955 t,
11956 eps,
11957 )?;
11958 return Ok((sn, attn_out));
11959 };
11960
11961 assert!(pre_norm.is_none(), "pre-norm fold is dense-entry only");
11962 let mut attn_out = e.uninit(t * n_embd)?;
11967 let mut router_in = e.uninit(t * n_embd)?;
11968 let fast_moe = match &layer.ffn {
11969 crate::hybrid::Ffn::Moe(m) => {
11970 m.dev_exps.as_ref().is_some_and(|d| !d.gu_il)
11971 && expert_dp4a_supported(m.gate_exps.qtype)
11972 && expert_dp4a_supported(m.up_exps.qtype)
11973 && expert_dp4a_supported(m.down_exps.qtype)
11974 && std::env::var("MEMRA_GEMMA_MOE_FAST").as_deref() != Ok("0")
11975 }
11976 _ => false,
11977 };
11978 let q8z = t < PRIME_MIN_T && fast_moe;
11979 let (zsh_f32, zsh_q8, moe_q8) = if q8z {
11980 let (z0, m2) = e.add_rms_norm3_q8z(
11981 cur,
11982 x,
11983 bits.ffn_norm.float_data(),
11984 &mbits.router_scale_pre,
11985 mbits.pre_ffw_norm_2.float_data(),
11986 &mut attn_out,
11987 &mut router_in,
11988 n_embd,
11989 t,
11990 eps,
11991 )?;
11992 (None, Some(z0), Some(m2))
11993 } else {
11994 let mut zsh = e.uninit(t * n_embd)?;
11995 let mut moe_in = e.uninit(t * n_embd)?;
11996 e.add_rms_norm3(
11997 cur,
11998 x,
11999 bits.ffn_norm.float_data(),
12000 &mbits.router_scale_pre,
12001 mbits.pre_ffw_norm_2.float_data(),
12002 &mut attn_out,
12003 &mut zsh,
12004 &mut router_in,
12005 &mut moe_in,
12006 n_embd,
12007 t,
12008 eps,
12009 )?;
12010 (Some((zsh, moe_in)), None, None)
12011 };
12012 let attn_out2 = attn_out;
12013 #[allow(unused_variables)]
12014 let attn_out = &attn_out2;
12015 let n_ff = mbits.shared_gate.out_features();
12016 let (gate, up) = if let Some((zq, zd)) = zsh_q8.as_ref() {
12017 if t == 1 {
12018 match e.matmul_q4_fused2(&mbits.shared_gate, &mbits.shared_up, zq, zd)? {
12019 Some(p) => p,
12020 None => match e.matmul_nvfp4_fused2(
12021 &mbits.shared_gate,
12022 &mbits.shared_up,
12023 zq,
12024 zd,
12025 1,
12026 )? {
12027 Some(p) => p,
12028 None => {
12029 let h0 = e.zeros(0)?;
12030 (
12031 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, 1)?,
12032 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, 1)?,
12033 )
12034 }
12035 },
12036 }
12037 } else {
12038 let h0 = e.zeros(0)?;
12040 (
12041 e.matmul_pre(&mbits.shared_gate, zq, zd, &h0, t)?,
12042 e.matmul_pre(&mbits.shared_up, zq, zd, &h0, t)?,
12043 )
12044 }
12045 } else {
12046 let (zsh, _) = zsh_f32.as_ref().unwrap();
12047 (
12048 e.matmul(&mbits.shared_gate, zsh, t)?,
12049 e.matmul(&mbits.shared_up, zsh, t)?,
12050 )
12051 };
12052 let mut act = e.uninit(t * n_ff)?;
12053 e.gelu_tanh_mul(&gate, &up, &mut act, t * n_ff)?;
12054 let mlp0 = e.matmul(&mbits.shared_down, &act, t)?;
12055 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
12056 panic!("gemma4 layer not MoE")
12057 };
12058 let moe0 = match (&moe_q8, &zsh_f32) {
12059 (Some(mq), _) => self.gemma4_moe_q8(e, m, mbits, mq, &router_in, t)?,
12060 (None, Some((_, moe_in))) => self.gemma4_moe(e, m, mbits, moe_in, &router_in, t)?,
12061 _ => unreachable!(),
12062 };
12063 let mut mlp = e.uninit(t * n_embd)?;
12065 let mut moe = e.uninit(t * n_embd)?;
12066 e.rms_norm2x(
12067 &mlp0,
12068 &moe0,
12069 mbits.post_ffw_norm_1.float_data(),
12070 mbits.post_ffw_norm_2.float_data(),
12071 &mut mlp,
12072 &mut moe,
12073 n_embd,
12074 t,
12075 eps,
12076 )?;
12077
12078 let mut sum = e.uninit(t * n_embd)?;
12081 let mut sn = e.uninit(t * n_embd)?;
12082 e.add_rms_norm(
12083 &mlp,
12084 &moe,
12085 bits.post_ffw_norm.float_data(),
12086 &mut sum,
12087 &mut sn,
12088 n_embd,
12089 t,
12090 eps,
12091 )?;
12092 Ok((sn, attn_out2))
12093 }
12094
12095 pub(crate) fn gemma4_layer_tail_add_nq_pn(
12105 &self,
12106 e: &Engine,
12107 layer: &crate::hybrid::HybridLayer,
12108 o: &CudaSlice<f32>,
12109 x: &CudaSlice<f32>,
12110 t: usize,
12111 next_norm: Option<&CudaSlice<f32>>,
12112 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12113 {
12114 let n_embd = self.cfg.n_embd as usize;
12115 let eps = self.cfg.rms_eps;
12116 let bits = layer.gemma4.as_ref().unwrap();
12117 if Engine::g4_pnfold_on() && matches!(layer.ffn, crate::hybrid::Ffn::Dense { .. }) {
12118 let (f0, attn_out) = self.gemma4_layer_tail_core_pn(
12119 e,
12120 layer,
12121 o,
12122 x,
12123 t,
12124 Some(layer.post_attn_norm.float_data()),
12125 true,
12126 )?;
12127 let mut xn = e.uninit(t * n_embd)?;
12128 return match next_norm {
12129 Some(w) => {
12130 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
12131 &f0,
12132 bits.post_ffw_norm.float_data(),
12133 &attn_out,
12134 bits.layer_scale,
12135 w,
12136 &mut xn,
12137 n_embd,
12138 t,
12139 eps,
12140 )?;
12141 Ok((xn, Some(pair)))
12142 }
12143 None => {
12144 let mut sn = e.uninit(t * n_embd)?;
12145 e.rms_norm(
12146 &f0,
12147 bits.post_ffw_norm.float_data(),
12148 &mut sn,
12149 n_embd,
12150 t,
12151 eps,
12152 )?;
12153 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12154 Ok((xn, None))
12155 }
12156 };
12157 }
12158 let mut cur = e.uninit(t * n_embd)?;
12159 e.rms_norm(
12160 o,
12161 layer.post_attn_norm.float_data(),
12162 &mut cur,
12163 n_embd,
12164 t,
12165 eps,
12166 )?;
12167 self.gemma4_layer_tail_add_nq(e, layer, &cur, x, t, next_norm)
12168 }
12169
12170 pub(crate) fn gemma4_layer_tail_add_nq(
12171 &self,
12172 e: &Engine,
12173 layer: &crate::hybrid::HybridLayer,
12174 cur: &CudaSlice<f32>,
12175 x: &CudaSlice<f32>,
12176 t: usize,
12177 next_norm: Option<&CudaSlice<f32>>,
12178 ) -> Result<(CudaSlice<f32>, Option<(CudaSlice<i8>, CudaSlice<f32>)>), Box<dyn std::error::Error>>
12179 {
12180 let n_embd = self.cfg.n_embd as usize;
12181 let bits = layer.gemma4.as_ref().unwrap();
12182 let (sn, attn_out) = self.gemma4_layer_tail_core(e, layer, cur, x, t)?;
12183 let mut xn = e.uninit(t * n_embd)?;
12184 match next_norm {
12185 Some(w) => {
12186 let pair = e.add_scale_rms_norm_q8_1(
12187 &sn,
12188 &attn_out,
12189 bits.layer_scale,
12190 w,
12191 &mut xn,
12192 n_embd,
12193 t,
12194 self.cfg.rms_eps,
12195 )?;
12196 Ok((xn, Some(pair)))
12197 }
12198 None => {
12199 e.add_scale(&sn, &attn_out, bits.layer_scale, &mut xn, t * n_embd)?;
12200 Ok((xn, None))
12201 }
12202 }
12203 }
12204
12205 fn gemma4_forward(
12208 &self,
12209 e: &Engine,
12210 tokens: &[u32],
12211 last_only: bool,
12212 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12213 if self.is_gemma4_e4b() {
12216 return self.gemma4_e4b_forward(e, tokens, last_only);
12217 }
12218 let n_embd = self.cfg.n_embd as usize;
12219 let t = tokens.len();
12220 let pos: Vec<i32> = (0..t as i32).collect();
12221 let pos_d = e.htod_i32(&pos)?;
12222
12223 let mut x = self.embed(e, tokens)?;
12224 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12225 let probe = std::env::var("MEMRA_GEMMA_PROBE").is_ok();
12228 let stat =
12229 |e: &Engine, x: &CudaSlice<f32>, tag: &str| -> Result<(), Box<dyn std::error::Error>> {
12230 let h = e.dtoh(x)?;
12231 let bad = h.iter().filter(|v| !v.is_finite()).count();
12232 let mx = h
12233 .iter()
12234 .filter(|v| v.is_finite())
12235 .fold(0.0f32, |m, v| m.max(v.abs()));
12236 eprintln!(
12237 "[gemma-probe] {tag}: tok0_first3={:?} bad={bad} max={mx:.3e}",
12238 &h[..3]
12239 );
12240 Ok(())
12241 };
12242 if probe {
12243 stat(e, &x, "embed")?;
12244 }
12245 for (il, layer) in self.layers.iter().enumerate() {
12246 x = self.gemma4_layer(e, il, layer, &x, &pos_d, t)?;
12247 if probe {
12248 stat(e, &x, &format!("L{il}"))?;
12249 }
12250 }
12251 let mut hn = e.zeros(t * n_embd)?;
12252 e.rms_norm(
12253 &x,
12254 self.output_norm.float_data(),
12255 &mut hn,
12256 n_embd,
12257 t,
12258 self.cfg.rms_eps,
12259 )?;
12260 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12261 let n_vocab = self.output.out_features();
12262 let logits = if last_only {
12263 let hv = e.view(&hn, t * n_embd);
12264 let last_row = hv.slice((t - 1) * n_embd..t * n_embd);
12265 let mut hlast = e.zeros(n_embd)?;
12266 e.copy_view_into(&mut hlast, 0, &last_row, n_embd)?;
12267 let mut ld = e.matmul(&self.output, &hlast, 1)?;
12268 e.softcap(&mut ld, cap, n_vocab)?;
12269 self.gemma4_suppress(e, &mut ld, 1)?;
12270 e.dtoh(&ld)?
12271 } else {
12272 let mut ld = e.matmul(&self.output, &hn, t)?;
12273 e.softcap(&mut ld, cap, t * n_vocab)?;
12274 self.gemma4_suppress(e, &mut ld, t)?;
12275 e.dtoh(&ld)?
12276 };
12277 Ok(logits)
12278 }
12279
12280 pub(crate) fn gemma4_prime(
12285 &self,
12286 e: &Engine,
12287 tokens: &[u32],
12288 cache: &mut Cache,
12289 overlay: Option<&crate::vision::EmbedOverlay>,
12290 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12291 if cache.pos != 0 {
12296 return Err(
12297 "gemma4 prime v0 is fresh-prompt only (no continuation/chunked prime) \
12298 — prime the full prompt in one call or decode tokenwise"
12299 .into(),
12300 );
12301 }
12302 let n_embd = self.cfg.n_embd as usize;
12303 let eps = self.cfg.rms_eps;
12304 let t = tokens.len();
12305 let pos: Vec<i32> = (0..t as i32).collect();
12306 let pos_d = e.htod_i32(&pos)?;
12307 let mut x = self.embed(e, tokens)?;
12308 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
12309 let island: Option<CudaSlice<i32>> = match overlay {
12316 Some(ov) => {
12317 let mut span_id = vec![-1i32; t];
12318 for (i, &(pos, row_off, n_rows)) in ov.spans.iter().enumerate() {
12319 if pos + n_rows > t {
12320 return Err(format!(
12321 "gemma4 overlay span {i} [{pos}, {}) exceeds the prompt ({t})",
12322 pos + n_rows
12323 )
12324 .into());
12325 }
12326 let view = ov.rows.slice(row_off * n_embd..(row_off + n_rows) * n_embd);
12327 e.copy_view_into(&mut x, pos * n_embd, &view, n_rows * n_embd)?;
12328 for s in span_id.iter_mut().skip(pos).take(n_rows) {
12329 *s = i as i32;
12330 }
12331 }
12332 if std::env::var("MEMRA_GV_FORCE_CAUSAL").as_deref() == Ok("1") {
12336 None
12337 } else {
12338 Some(e.htod_i32(&span_id)?)
12339 }
12340 }
12341 None => None,
12342 };
12343 for (il, layer) in self.layers.iter().enumerate() {
12344 let mut h = e.zeros(t * n_embd)?;
12345 e.rms_norm(&x, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
12346 let Mixer::Full(fa) = &layer.mixer else {
12347 panic!("gemma4 layer not full-attn")
12348 };
12349 let trace = il == 0 && std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1");
12350 if trace {
12351 let v = e.dtoh(&h)?;
12352 let nan = v.iter().filter(|x| x.is_nan()).count();
12353 eprintln!("[g4-prime-trace] L0 post-attn_norm: nan={nan}/{}", v.len());
12354 }
12355 let o =
12356 self.gemma4_attn_prime(e, fa, il, &h, &pos_d, t, Some(cache), island.as_ref())?;
12357 if trace {
12358 let v = e.dtoh(&o)?;
12359 let nan = v.iter().filter(|x| x.is_nan()).count();
12360 eprintln!("[g4-prime-trace] L0 post-attn: nan={nan}/{}", v.len());
12361 }
12362 let mut cur = e.zeros(t * n_embd)?;
12363 e.rms_norm(
12364 &o,
12365 layer.post_attn_norm.float_data(),
12366 &mut cur,
12367 n_embd,
12368 t,
12369 eps,
12370 )?;
12371 x = self.gemma4_layer_tail_add(e, layer, &cur, &x, t)?;
12372 self.dflash_tap(e, cache, il, &x, t)?;
12373 if std::env::var("MEMRA_G4_PRIME_TRACE").as_deref() == Ok("1") {
12375 let h = e.dtoh(&x)?;
12376 let nan = h.iter().filter(|v| v.is_nan()).count();
12377 let amax = h.iter().fold(0f32, |a, v| a.max(v.abs()));
12378 eprintln!(
12379 "[g4-prime-trace] layer {il}: nan={nan}/{} amax={amax:.3}",
12380 h.len()
12381 );
12382 if nan > 0 {
12383 return Err(format!("g4-prime-trace: first NaN at layer {il}").into());
12384 }
12385 }
12386 }
12387 cache.pos += t;
12388 let hiddens = e.clone_dtod(&x)?;
12389 let xv = e.view(&x, t * n_embd);
12390 let last_row = xv.slice((t - 1) * n_embd..t * n_embd);
12391 let mut h_seed = e.zeros(n_embd)?;
12392 e.copy_view_into(&mut h_seed, 0, &last_row, n_embd)?;
12393 let mut hn = e.uninit(n_embd)?;
12394 e.rms_norm(
12395 &h_seed,
12396 self.output_norm.float_data(),
12397 &mut hn,
12398 n_embd,
12399 1,
12400 eps,
12401 )?;
12402 let mut ld = e.matmul(&self.output, &hn, 1)?;
12403 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
12404 e.softcap(&mut ld, cap, self.output.out_features())?;
12405 self.gemma4_suppress(e, &mut ld, 1)?;
12406 let logits = e.dtoh(&ld)?;
12407 Ok((logits, h_seed, hiddens))
12408 }
12409
12410 fn gemma4_decode_attn(
12415 &self,
12416 e: &Engine,
12417 fa: &crate::hybrid::FullAttnLayer,
12418 il: usize,
12419 hq: &CudaSlice<i8>,
12420 hdq: &CudaSlice<f32>,
12421 pos_d: &CudaSlice<i32>,
12422 cache: &mut Cache,
12423 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12424 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12425 let eps = self.cfg.rms_eps;
12426 let aux = self.gemma4_aux.as_ref().unwrap();
12427 let ones = aux.ones(e);
12428 #[cfg(debug_assertions)]
12429 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn.ones");
12430 let (hq, hdq) = (hq, hdq);
12431 let h0 = e.zeros(0)?;
12432 let h = &h0;
12433 let (q0, k0, v0) = if swa {
12434 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
12435 Some(t3) => t3,
12436 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12439 Some((q0, k0)) => {
12440 let v0 = e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?;
12441 (q0, k0, v0)
12442 }
12443 None => (
12444 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12445 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12446 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
12447 ),
12448 },
12449 }
12450 } else {
12451 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, &hq, &hdq)? {
12452 Some(p) => p,
12453 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, &hq, &hdq, 1)? {
12454 Some(p) => p,
12455 None => (
12456 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
12457 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
12458 ),
12459 },
12460 };
12461 let v0 = e.clone_dtod(&k0)?;
12462 (q0, k0, v0)
12463 };
12464 let mut q = e.uninit(nh * hd)?;
12465 let mut k = e.uninit(nkv * hd)?;
12466 let mut v = e.uninit(nkv * hd)?;
12467 let ff = if swa {
12470 None
12471 } else {
12472 Some(
12473 aux.rope_freqs(e)
12474 .expect("gemma4 global rope needs rope_freqs.weight"),
12475 )
12476 };
12477 #[cfg(debug_assertions)]
12478 if let Some(ff) = ff {
12479 crate::debug_assert_tensor_stream_device(
12480 ff,
12481 &e.stream(),
12482 "gemma4_decode_attn.rope_freqs",
12483 );
12484 }
12485 let kvl = cache.kv[il].as_mut().unwrap();
12486 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12487 if crate::Engine::qkv_append_on() {
12488 e.rms_norm_qkv_rope_append(
12492 &q0,
12493 &k0,
12494 &v0,
12495 fa.q_norm.float_data(),
12496 fa.k_norm.float_data(),
12497 ones,
12498 &mut q,
12499 &mut k,
12500 &mut v,
12501 hd,
12502 self.gemma4_rope_dims(il),
12503 nh,
12504 nkv,
12505 pos_d,
12506 nh,
12507 nkv,
12508 base,
12509 1.0,
12510 ff,
12511 eps,
12512 &mut kvl.k,
12513 &mut kvl.v,
12514 kvl.len,
12515 kvl.k_tok_bytes,
12516 kvl.v_tok_bytes,
12517 kv_fp8,
12518 )?;
12519 } else {
12520 e.rms_norm_qkv_rope(
12521 &q0,
12522 &k0,
12523 &v0,
12524 fa.q_norm.float_data(),
12525 fa.k_norm.float_data(),
12526 ones,
12527 &mut q,
12528 &mut k,
12529 &mut v,
12530 hd,
12531 self.gemma4_rope_dims(il),
12532 nh,
12533 nkv,
12534 pos_d,
12535 nh,
12536 nkv,
12537 base,
12538 1.0,
12539 ff,
12540 eps,
12541 )?;
12542 e.append_kv_quantized(
12543 &k,
12544 &v,
12545 &mut kvl.k,
12546 &mut kvl.v,
12547 kvl.len,
12548 kvl.kv_dim_k,
12549 kvl.kv_dim_v,
12550 kvl.k_tok_bytes,
12551 kvl.v_tok_bytes,
12552 kv_fp8,
12553 )?;
12554 }
12555 kvl.len += 1;
12556 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
12560 let mut attn = e.uninit(nh * hd)?;
12561 if !swa
12563 && hd == 512
12564 && kvl.len >= crate::fa512_min_tkv()
12565 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12566 {
12567 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12568 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12569 let base = kvl.len as i32;
12571 e.i32_set_k(&mut kvl.len_d, base)?;
12572 e.fa_decode_rows(
12573 &q,
12574 &kp,
12575 &vp,
12576 &mut attn,
12577 hd,
12578 nh,
12579 nkv,
12580 kvl.len - 1,
12581 1,
12582 scale,
12583 kvl.k_tok_bytes,
12584 kvl.v_tok_bytes,
12585 Some((&kvl.len_d, -1)),
12586 false,
12587 false,
12588 None,
12589 )?;
12590 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12591 }
12592 if swa
12594 && kvl.len > win
12595 && hd == 256
12596 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
12597 {
12598 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
12599 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
12600 let base = kvl.len as i32;
12601 e.i32_set_k(&mut kvl.len_d, base)?;
12602 e.fa_decode_rows_w(
12603 &q,
12604 &kp,
12605 &vp,
12606 &mut attn,
12607 hd,
12608 nh,
12609 nkv,
12610 &kvl.len_d,
12611 -1,
12612 1,
12613 scale,
12614 win,
12615 kvl.k_tok_bytes,
12616 kvl.v_tok_bytes,
12617 None,
12618 )?;
12619 return Ok(e.matmul(&fa.wo, &attn, 1)?);
12620 }
12621 let (off_tok, t_kv) = if swa && kvl.len > win {
12622 (kvl.len - win, win)
12623 } else {
12624 (0, kvl.len)
12625 };
12626 let k_view = e.view_u8_range(
12627 &kvl.k,
12628 off_tok * kvl.k_tok_bytes,
12629 (off_tok + t_kv) * kvl.k_tok_bytes,
12630 );
12631 let v_view = e.view_u8_range(
12632 &kvl.v,
12633 off_tok * kvl.v_tok_bytes,
12634 (off_tok + t_kv) * kvl.v_tok_bytes,
12635 );
12636 e.fa_decode_kvmod(
12637 &q,
12638 &k_view,
12639 &v_view,
12640 &mut attn,
12641 hd,
12642 nh,
12643 nkv,
12644 t_kv,
12645 scale,
12646 kvl.k_tok_bytes,
12647 kvl.v_tok_bytes,
12648 swa && crate::Engine::wkv_on(),
12649 )?;
12650 Ok(e.matmul(&fa.wo, &attn, 1)?)
12651 }
12652
12653 #[allow(clippy::too_many_arguments)]
12660 pub fn gemma4_decode_step_dc(
12661 &self,
12662 e: &Engine,
12663 token_d: &CudaSlice<u32>,
12664 pos_d: &mut CudaSlice<i32>,
12665 embd_gpu: &CudaSlice<u8>,
12666 embd_qt: i32,
12667 embd_rb: usize,
12668 cache: &mut Cache,
12669 n_vocab: usize,
12670 cap_bucket_max: Option<(usize, usize)>,
12671 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
12672 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
12673 self.gemma4_decode_step_dc_into(
12674 e,
12675 token_d,
12676 pos_d,
12677 embd_gpu,
12678 embd_qt,
12679 embd_rb,
12680 cache,
12681 n_vocab,
12682 cap_bucket_max,
12683 &mut tok_out,
12684 )?;
12685 Ok(tok_out)
12686 }
12687
12688 #[allow(clippy::too_many_arguments)]
12691 pub fn gemma4_decode_step_dc_into(
12692 &self,
12693 e: &Engine,
12694 token_d: &CudaSlice<u32>,
12695 pos_d: &mut CudaSlice<i32>,
12696 embd_gpu: &CudaSlice<u8>,
12697 embd_qt: i32,
12698 embd_rb: usize,
12699 cache: &mut Cache,
12700 n_vocab: usize,
12701 cap_bucket_max: Option<(usize, usize)>,
12702 tok_out: &mut CudaSlice<u32>,
12703 ) -> Result<(), Box<dyn std::error::Error>> {
12704 let n_embd = self.cfg.n_embd as usize;
12705 let eps = self.cfg.rms_eps;
12706 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
12707 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
12708 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
12709 let n_layers = self.layers.len();
12710 for (il, layer) in self.layers.iter().enumerate() {
12711 let (hq, hdq) = match h_carry.take() {
12712 Some(p) => p,
12713 None => {
12714 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
12715 }
12716 };
12717 let Mixer::Full(fa) = &layer.mixer else {
12718 panic!("gemma4 layer {il} not full-attn")
12719 };
12720 let o =
12721 self.gemma4_decode_attn_dc(e, fa, il, &hq, &hdq, pos_d, cache, cap_bucket_max)?;
12722 let next_norm = if il + 1 < n_layers {
12723 Some(self.layers[il + 1].attn_norm.float_data())
12724 } else {
12725 None
12726 };
12727 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
12728 x = xn;
12729 h_carry = hn;
12730 }
12731 let mut hn = e.uninit(n_embd)?;
12732 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
12733 let mut logits = e.matmul(&self.output, &hn, 1)?;
12734 self.gemma4_suppress(e, &mut logits, 1)?; e.argmax_token_device_into(&logits, tok_out, n_vocab)?;
12736 e.inc_seqlen(pos_d)?;
12737 if cap_bucket_max.is_none() {
12738 cache.pos += 1;
12739 }
12740 Ok(())
12741 }
12742
12743 pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn std::error::Error>> {
12750 let n_embd = self.cfg.n_embd as usize;
12751 let n_vocab = self.output.out_features();
12752 let n_layers = self.layers.len();
12753 let (mut qmax, mut kvmax, mut ffmax) = (0usize, 0usize, 0usize);
12754 for il in 0..n_layers {
12755 let (hd, nkv, nh, _b, _s, _w) = self.gemma4_geom(il);
12756 qmax = qmax.max(nh * hd);
12757 kvmax = kvmax.max(nkv * hd);
12758 if let crate::hybrid::Ffn::Dense { ffn_gate, .. } = &self.layers[il].ffn {
12759 ffmax = ffmax.max(ffn_gate.out_features());
12760 }
12761 }
12762 Ok(G4DcSlots {
12763 x: e.uninit(n_embd)?,
12764 xn: e.uninit(n_embd)?,
12765 cur: e.uninit(n_embd)?,
12766 hq: e.alloc_i8_uninit(n_embd)?,
12767 hd_: e.uninit(n_embd / 32)?,
12768 q0: e.uninit(qmax)?,
12769 k0: e.uninit(kvmax)?,
12770 v0: e.uninit(kvmax)?,
12771 q: e.uninit(qmax)?,
12772 k: e.uninit(kvmax)?,
12773 v: e.uninit(kvmax)?,
12774 attn: e.uninit(qmax)?,
12775 o: e.uninit(n_embd)?,
12776 attn_out: e.uninit(n_embd)?,
12777 zsh: e.uninit(n_embd)?,
12778 zq: e.alloc_i8_uninit(n_embd.max(qmax))?,
12781 zd: e.uninit(n_embd.max(qmax) / 32)?,
12782 gate: e.uninit(ffmax)?,
12783 up: e.uninit(ffmax)?,
12784 act: e.uninit(ffmax)?,
12785 actq: e.alloc_i8_uninit(ffmax)?,
12786 actd: e.uninit(ffmax / 32)?,
12787 f0: e.uninit(n_embd)?,
12788 sn: e.uninit(n_embd)?,
12789 hn: e.uninit(n_embd)?,
12790 logits: e.uninit(n_vocab)?,
12791 })
12792 }
12793
12794 fn g4_matvec_m1_into(
12797 &self,
12798 e: &Engine,
12799 w: &crate::model::GpuTensor,
12800 aq: &CudaSlice<i8>,
12801 ad: &CudaSlice<f32>,
12802 y: &mut CudaSlice<f32>,
12803 ) -> Result<(), Box<dyn std::error::Error>> {
12804 use crate::model::GpuTensor;
12805 let (bytes, qtype, row_bytes, scale, rp) = match w {
12806 GpuTensor::Quant {
12807 bytes,
12808 qtype,
12809 row_bytes,
12810 scale,
12811 rp,
12812 ..
12813 } => (bytes, *qtype, *row_bytes, *scale, *rp),
12814 _ => return Err("g4_matvec_m1_into: non-quant tensor".into()),
12815 };
12816 let (mbytes, mrp) = match w {
12817 GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12818 _ => (bytes, rp),
12819 };
12820 e.qmatvec_mmvq_into(
12821 mbytes,
12822 aq,
12823 ad,
12824 1,
12825 w.in_features(),
12826 w.out_features(),
12827 qtype,
12828 row_bytes,
12829 scale,
12830 mrp,
12831 y,
12832 )
12833 }
12834
12835 #[allow(clippy::too_many_arguments)]
12839 pub fn gemma4_decode_step_dc_slotted(
12840 &self,
12841 e: &Engine,
12842 token_d: &CudaSlice<u32>,
12843 pos_d: &mut CudaSlice<i32>,
12844 embd_gpu: &CudaSlice<u8>,
12845 embd_qt: i32,
12846 embd_rb: usize,
12847 cache: &mut Cache,
12848 n_vocab: usize,
12849 cap_bucket_max: Option<(usize, usize)>,
12850 sl: &mut G4DcSlots,
12851 tok_out: &mut CudaSlice<u32>,
12852 ring: Option<(&mut CudaSlice<u32>, usize)>,
12853 ) -> Result<(), Box<dyn std::error::Error>> {
12854 let n_embd = self.cfg.n_embd as usize;
12855 let eps = self.cfg.rms_eps;
12856 e.embed_gather_device_into(embd_gpu, token_d, &mut sl.x, n_embd, embd_qt, embd_rb)?;
12857 e.scale_inplace(&mut sl.x, (n_embd as f32).sqrt(), n_embd)?;
12858 let n_layers = self.layers.len();
12859 let mut has_carry = false;
12860 for il in 0..n_layers {
12861 if !has_carry {
12862 e.rms_norm_q8_1_into(
12863 &sl.x,
12864 self.layers[il].attn_norm.float_data(),
12865 n_embd,
12866 1,
12867 eps,
12868 &mut sl.hq,
12869 &mut sl.hd_,
12870 )?;
12871 }
12872 has_carry = true;
12873 let layer = &self.layers[il];
12874 let Mixer::Full(fa) = &layer.mixer else {
12875 panic!("gemma4 layer {il} not full-attn")
12876 };
12877 self.gemma4_decode_attn_dc_slotted(e, fa, il, pos_d, cache, cap_bucket_max, sl)?;
12878 if !Engine::g4_pnfold_on() {
12881 e.rms_norm(
12882 &sl.o,
12883 layer.post_attn_norm.float_data(),
12884 &mut sl.cur,
12885 n_embd,
12886 1,
12887 eps,
12888 )?;
12889 }
12890 let next_norm = if il + 1 < n_layers {
12891 Some(self.layers[il + 1].attn_norm.float_data())
12892 } else {
12893 None
12894 };
12895 self.gemma4_layer_tail_slotted(e, layer, next_norm, sl)?;
12896 std::mem::swap(&mut sl.x, &mut sl.xn);
12897 }
12898 e.rms_norm(
12899 &sl.x,
12900 self.output_norm.float_data(),
12901 &mut sl.hn,
12902 n_embd,
12903 1,
12904 eps,
12905 )?;
12906 e.quantize_q8_1_into(&sl.hn, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
12907 {
12909 let (zq, zd) = (&sl.zq, &sl.zd);
12910 let zq = unsafe { &*(zq as *const CudaSlice<i8>) };
12911 let zd = unsafe { &*(zd as *const CudaSlice<f32>) };
12912 self.g4_matvec_m1_into(e, &self.output, zq, zd, &mut sl.logits)?;
12913 }
12914 self.gemma4_suppress(e, &mut sl.logits, 1)?;
12915 e.argmax_token_device_into(&sl.logits, tok_out, n_vocab)?;
12916 if let Some((ring, base)) = ring {
12917 e.plain_tok_ring(tok_out, pos_d, base, ring)?;
12921 }
12922 e.inc_seqlen(pos_d)?;
12923 if cap_bucket_max.is_none() {
12924 cache.pos += 1;
12925 }
12926 Ok(())
12927 }
12928
12929 #[allow(clippy::too_many_arguments)]
12931 fn gemma4_decode_attn_dc_slotted(
12932 &self,
12933 e: &Engine,
12934 fa: &crate::hybrid::FullAttnLayer,
12935 il: usize,
12936 pos_d: &CudaSlice<i32>,
12937 cache: &mut Cache,
12938 cap_bucket_max: Option<(usize, usize)>,
12939 sl: &mut G4DcSlots,
12940 ) -> Result<(), Box<dyn std::error::Error>> {
12941 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
12942 let eps = self.cfg.rms_eps;
12943 let aux = self.gemma4_aux.as_ref().unwrap();
12944 let ones = aux.ones(e);
12945 #[cfg(debug_assertions)]
12946 crate::debug_assert_tensor_stream_device(
12947 ones,
12948 &e.stream(),
12949 "gemma4_decode_attn_dc_slotted.ones",
12950 );
12951 {
12952 let hq = unsafe { &*(&sl.hq as *const CudaSlice<i8>) };
12953 let hdq = unsafe { &*(&sl.hd_ as *const CudaSlice<f32>) };
12954 if swa {
12955 if !e.matmul_q4_fused3_into(
12956 &fa.wq, &fa.wk, &fa.wv, hq, hdq, &mut sl.q0, &mut sl.k0, &mut sl.v0,
12957 )? {
12958 if e.matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12962 {
12963 self.g4_matvec_m1_into(e, &fa.wv, hq, hdq, &mut sl.v0)?;
12964 } else {
12965 return Err("slotted step: fused3 unavailable (non-uniform trunk)".into());
12966 }
12967 }
12968 } else {
12969 if !e.matmul_q4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12970 && !e
12971 .matmul_nvfp4_fused2_into(&fa.wq, &fa.wk, hq, hdq, &mut sl.q0, &mut sl.k0)?
12972 {
12973 return Err("slotted step: fused2 unavailable".into());
12974 }
12975 let k0r = unsafe { &*(&sl.k0 as *const CudaSlice<f32>) };
12976 e.copy_into(&mut sl.v0, 0, k0r, nkv * hd)?;
12977 }
12978 }
12979 let ff = if swa {
12982 None
12983 } else {
12984 Some(
12985 aux.rope_freqs(e)
12986 .expect("gemma4 global rope needs rope_freqs.weight"),
12987 )
12988 };
12989 #[cfg(debug_assertions)]
12990 if let Some(ff) = ff {
12991 crate::debug_assert_tensor_stream_device(
12992 ff,
12993 &e.stream(),
12994 "gemma4_decode_attn_dc_slotted.rope_freqs",
12995 );
12996 }
12997 let kvl = cache.kv[il].as_mut().unwrap();
12998 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
12999 if crate::Engine::qkv_append_on() {
13000 e.rms_norm_qkv_rope_append_dc(
13002 &sl.q0,
13003 &sl.k0,
13004 &sl.v0,
13005 fa.q_norm.float_data(),
13006 fa.k_norm.float_data(),
13007 ones,
13008 &mut sl.q,
13009 &mut sl.k,
13010 &mut sl.v,
13011 hd,
13012 self.gemma4_rope_dims(il),
13013 nh,
13014 nkv,
13015 pos_d,
13016 nh,
13017 nkv,
13018 base,
13019 1.0,
13020 ff,
13021 eps,
13022 &mut kvl.k,
13023 &mut kvl.v,
13024 &kvl.len_d,
13025 kvl.k_tok_bytes,
13026 kvl.v_tok_bytes,
13027 kv_fp8,
13028 )?;
13029 } else {
13030 e.rms_norm_qkv_rope(
13031 &sl.q0,
13032 &sl.k0,
13033 &sl.v0,
13034 fa.q_norm.float_data(),
13035 fa.k_norm.float_data(),
13036 ones,
13037 &mut sl.q,
13038 &mut sl.k,
13039 &mut sl.v,
13040 hd,
13041 self.gemma4_rope_dims(il),
13042 nh,
13043 nkv,
13044 pos_d,
13045 nh,
13046 nkv,
13047 base,
13048 1.0,
13049 ff,
13050 eps,
13051 )?;
13052 e.append_kv_quantized_dc(
13053 &sl.k,
13054 &sl.v,
13055 &mut kvl.k,
13056 &mut kvl.v,
13057 &kvl.len_d,
13058 kvl.kv_dim_k,
13059 kvl.kv_dim_v,
13060 kvl.k_tok_bytes,
13061 kvl.v_tok_bytes,
13062 kv_fp8,
13063 )?;
13064 }
13065 e.inc_seqlen(&mut kvl.len_d)?;
13066 let (b_swa, b_glob) = cap_bucket_max.expect("slotted step is capture-only");
13067 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13068 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13069 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13070 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13071 let mut fa_q8 = false;
13075 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13076 e.fa_decode_rows(
13077 &sl.q,
13078 &k_view,
13079 &v_view,
13080 &mut sl.attn,
13081 hd,
13082 nh,
13083 nkv,
13084 b_glob - 1,
13085 1,
13086 scale,
13087 kvl.k_tok_bytes,
13088 kvl.v_tok_bytes,
13089 Some((&kvl.len_d, -1)),
13090 false,
13091 false,
13092 Some((&mut sl.zq, &mut sl.zd)),
13093 )?;
13094 fa_q8 = true;
13095 } else if swa && b_swa > win && hd == 256 && rows_on {
13096 e.fa_decode_rows_w(
13097 &sl.q,
13098 &k_view,
13099 &v_view,
13100 &mut sl.attn,
13101 hd,
13102 nh,
13103 nkv,
13104 &kvl.len_d,
13105 -1,
13106 1,
13107 scale,
13108 win,
13109 kvl.k_tok_bytes,
13110 kvl.v_tok_bytes,
13111 Some((&mut sl.zq, &mut sl.zd)),
13112 )?;
13113 fa_q8 = true;
13114 } else {
13115 let b = if swa { b_swa } else { b_glob };
13116 e.fa_decode_dc(
13117 &sl.q,
13118 &k_view,
13119 &v_view,
13120 &mut sl.attn,
13121 hd,
13122 nh,
13123 nkv,
13124 &kvl.len_d,
13125 b,
13126 scale,
13127 kvl.k_tok_bytes,
13128 kvl.v_tok_bytes,
13129 swa && crate::Engine::wkv_on(),
13130 )?;
13131 }
13132 if !fa_q8 {
13133 let aq = unsafe { &*(&sl.attn as *const CudaSlice<f32>) };
13134 e.quantize_q8_1_into(aq, 1, nh * hd, &mut sl.zq, &mut sl.zd)?;
13135 }
13136 {
13137 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13138 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13139 self.g4_matvec_m1_into(e, &fa.wo, zq, zd, &mut sl.o)?;
13140 }
13141 Ok(())
13142 }
13143
13144 fn gemma4_layer_tail_slotted(
13147 &self,
13148 e: &Engine,
13149 layer: &crate::hybrid::HybridLayer,
13150 next_norm: Option<&CudaSlice<f32>>,
13151 sl: &mut G4DcSlots,
13152 ) -> Result<(), Box<dyn std::error::Error>> {
13153 let n_embd = self.cfg.n_embd as usize;
13154 let eps = self.cfg.rms_eps;
13155 let bits = layer.gemma4.as_ref().unwrap();
13156 let crate::hybrid::Ffn::Dense {
13157 ffn_gate,
13158 ffn_up,
13159 ffn_down,
13160 } = &layer.ffn
13161 else {
13162 return Err("slotted tail: dense ffn only".into());
13163 };
13164 let pnfold = Engine::g4_pnfold_on();
13165 if pnfold {
13166 let or = unsafe { &*(&sl.o as *const CudaSlice<f32>) };
13169 let xr = unsafe { &*(&sl.x as *const CudaSlice<f32>) };
13170 e.rms_pre_add_rms_norm_q8z_into(
13171 or,
13172 layer.post_attn_norm.float_data(),
13173 xr,
13174 bits.ffn_norm.float_data(),
13175 &mut sl.attn_out,
13176 &mut sl.zsh,
13177 n_embd,
13178 1,
13179 eps,
13180 &mut sl.zq,
13181 &mut sl.zd,
13182 )?;
13183 } else {
13184 e.add_rms_norm(
13185 &sl.cur,
13186 &sl.x,
13187 bits.ffn_norm.float_data(),
13188 &mut sl.attn_out,
13189 &mut sl.zsh,
13190 n_embd,
13191 1,
13192 eps,
13193 )?;
13194 }
13195 let n_ff = ffn_gate.out_features();
13196 if !pnfold {
13197 let zshr = unsafe { &*(&sl.zsh as *const CudaSlice<f32>) };
13198 e.quantize_q8_1_into(zshr, 1, n_embd, &mut sl.zq, &mut sl.zd)?;
13199 }
13200 {
13201 let zq = unsafe { &*(&sl.zq as *const CudaSlice<i8>) };
13202 let zd = unsafe { &*(&sl.zd as *const CudaSlice<f32>) };
13203 if !e.matmul_q4_fused2_into(ffn_gate, ffn_up, zq, zd, &mut sl.gate, &mut sl.up)?
13204 && !e.matmul_nvfp4_fused2_into(
13205 ffn_gate,
13206 ffn_up,
13207 zq,
13208 zd,
13209 &mut sl.gate,
13210 &mut sl.up,
13211 )?
13212 {
13213 return Err("slotted tail: ffn fused2 unavailable".into());
13214 }
13215 }
13216 debug_assert!(e.uses_q8_1_fast(ffn_down));
13217 {
13218 let upr = unsafe { &*(&sl.up as *const CudaSlice<f32>) };
13219 let upv = e.view(upr, n_ff);
13220 let up_all = upv.slice(0..n_ff);
13221 let gr = unsafe { &*(&sl.gate as *const CudaSlice<f32>) };
13222 e.gelu_tanh_mul_q8_1_into(
13223 gr,
13224 &up_all,
13225 &mut sl.act,
13226 n_ff,
13227 1,
13228 &mut sl.actq,
13229 &mut sl.actd,
13230 )?;
13231 }
13232 {
13233 let aq = unsafe { &*(&sl.actq as *const CudaSlice<i8>) };
13234 let ad = unsafe { &*(&sl.actd as *const CudaSlice<f32>) };
13235 self.g4_matvec_m1_into(e, ffn_down, aq, ad, &mut sl.f0)?;
13236 }
13237 if pnfold {
13238 if let Some(w) = next_norm {
13241 let f0r = unsafe { &*(&sl.f0 as *const CudaSlice<f32>) };
13242 let aor = unsafe { &*(&sl.attn_out as *const CudaSlice<f32>) };
13243 e.rms_pre_add_scale_rms_norm_q8_1_into(
13244 f0r,
13245 bits.post_ffw_norm.float_data(),
13246 aor,
13247 bits.layer_scale,
13248 w,
13249 &mut sl.xn,
13250 n_embd,
13251 1,
13252 eps,
13253 &mut sl.hq,
13254 &mut sl.hd_,
13255 )?;
13256 return Ok(());
13257 }
13258 }
13259 e.rms_norm(
13260 &sl.f0,
13261 bits.post_ffw_norm.float_data(),
13262 &mut sl.sn,
13263 n_embd,
13264 1,
13265 eps,
13266 )?;
13267 match next_norm {
13268 Some(w) => {
13269 e.add_scale_rms_norm_q8_1_into(
13270 &sl.sn,
13271 &sl.attn_out,
13272 bits.layer_scale,
13273 w,
13274 &mut sl.xn,
13275 n_embd,
13276 1,
13277 eps,
13278 &mut sl.hq,
13279 &mut sl.hd_,
13280 )?;
13281 }
13282 None => {
13283 e.add_scale(&sl.sn, &sl.attn_out, bits.layer_scale, &mut sl.xn, n_embd)?;
13284 }
13285 }
13286 Ok(())
13287 }
13288
13289 #[allow(clippy::too_many_arguments)]
13291 fn gemma4_decode_attn_dc(
13292 &self,
13293 e: &Engine,
13294 fa: &crate::hybrid::FullAttnLayer,
13295 il: usize,
13296 hq: &CudaSlice<i8>,
13297 hdq: &CudaSlice<f32>,
13298 pos_d: &CudaSlice<i32>,
13299 cache: &mut Cache,
13300 cap_bucket_max: Option<(usize, usize)>,
13301 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13302 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
13303 let eps = self.cfg.rms_eps;
13304 let aux = self.gemma4_aux.as_ref().unwrap();
13305 let ones = aux.ones(e);
13306 #[cfg(debug_assertions)]
13307 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_decode_attn_dc.ones");
13308 let (q0, k0, v0) = if swa {
13309 match e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
13310 Some(t3) => t3,
13311 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13313 Some((q0, k0)) => {
13314 let h0 = e.zeros(0)?;
13315 let v0 = e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?;
13316 (q0, k0, v0)
13317 }
13318 None => {
13319 let h0 = e.zeros(0)?;
13320 (
13321 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13322 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13323 e.matmul_pre(&fa.wv, hq, hdq, &h0, 1)?,
13324 )
13325 }
13326 },
13327 }
13328 } else {
13329 let (q0, k0) = match e.matmul_q4_fused2(&fa.wq, &fa.wk, hq, hdq)? {
13330 Some(p) => p,
13331 None => match e.matmul_nvfp4_fused2(&fa.wq, &fa.wk, hq, hdq, 1)? {
13332 Some(p) => p,
13333 None => {
13334 let h0 = e.zeros(0)?;
13335 (
13336 e.matmul_pre(&fa.wq, hq, hdq, &h0, 1)?,
13337 e.matmul_pre(&fa.wk, hq, hdq, &h0, 1)?,
13338 )
13339 }
13340 },
13341 };
13342 let v0 = e.clone_dtod(&k0)?;
13343 (q0, k0, v0)
13344 };
13345 let mut q = e.uninit(nh * hd)?;
13346 let mut k = e.uninit(nkv * hd)?;
13347 let mut v = e.uninit(nkv * hd)?;
13348 let ff = if swa {
13350 None
13351 } else {
13352 Some(
13353 aux.rope_freqs(e)
13354 .expect("gemma4 global rope needs rope_freqs.weight"),
13355 )
13356 };
13357 #[cfg(debug_assertions)]
13358 if let Some(ff) = ff {
13359 crate::debug_assert_tensor_stream_device(
13360 ff,
13361 &e.stream(),
13362 "gemma4_decode_attn_dc.rope_freqs",
13363 );
13364 }
13365 let kvl = cache.kv[il].as_mut().unwrap();
13366 let kv_fp8 = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
13367 if crate::Engine::qkv_append_on() {
13368 e.rms_norm_qkv_rope_append_dc(
13370 &q0,
13371 &k0,
13372 &v0,
13373 fa.q_norm.float_data(),
13374 fa.k_norm.float_data(),
13375 ones,
13376 &mut q,
13377 &mut k,
13378 &mut v,
13379 hd,
13380 self.gemma4_rope_dims(il),
13381 nh,
13382 nkv,
13383 pos_d,
13384 nh,
13385 nkv,
13386 base,
13387 1.0,
13388 ff,
13389 eps,
13390 &mut kvl.k,
13391 &mut kvl.v,
13392 &kvl.len_d,
13393 kvl.k_tok_bytes,
13394 kvl.v_tok_bytes,
13395 kv_fp8,
13396 )?;
13397 } else {
13398 e.rms_norm_qkv_rope(
13399 &q0,
13400 &k0,
13401 &v0,
13402 fa.q_norm.float_data(),
13403 fa.k_norm.float_data(),
13404 ones,
13405 &mut q,
13406 &mut k,
13407 &mut v,
13408 hd,
13409 self.gemma4_rope_dims(il),
13410 nh,
13411 nkv,
13412 pos_d,
13413 nh,
13414 nkv,
13415 base,
13416 1.0,
13417 ff,
13418 eps,
13419 )?;
13420 e.append_kv_quantized_dc(
13421 &k,
13422 &v,
13423 &mut kvl.k,
13424 &mut kvl.v,
13425 &kvl.len_d,
13426 kvl.kv_dim_k,
13427 kvl.kv_dim_v,
13428 kvl.k_tok_bytes,
13429 kvl.v_tok_bytes,
13430 kv_fp8,
13431 )?;
13432 }
13433 e.inc_seqlen(&mut kvl.len_d)?;
13434 let mut attn = e.uninit(nh * hd)?;
13435 let mut fa_q8: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13438 match cap_bucket_max {
13443 None => {
13444 kvl.len += 1;
13448 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13449 if !swa
13450 && hd == 512
13451 && kvl.len >= crate::fa512_min_tkv()
13452 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13453 {
13454 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13457 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13458 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13459 e.fa_decode_rows(
13460 &q,
13461 &kp,
13462 &vp,
13463 &mut attn,
13464 hd,
13465 nh,
13466 nkv,
13467 kvl.len - 1,
13468 1,
13469 scale,
13470 kvl.k_tok_bytes,
13471 kvl.v_tok_bytes,
13472 Some((&kvl.len_d, -1)),
13473 false,
13474 false,
13475 Some((&mut aq8, &mut ad8)),
13476 )?;
13477 fa_q8 = Some((aq8, ad8));
13478 } else if swa
13479 && kvl.len > win
13480 && hd == 256
13481 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
13482 {
13483 let kp = e.view_u8(&kvl.k, kvl.len * kvl.k_tok_bytes);
13485 let vp = e.view_u8(&kvl.v, kvl.len * kvl.v_tok_bytes);
13486 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13487 e.fa_decode_rows_w(
13488 &q,
13489 &kp,
13490 &vp,
13491 &mut attn,
13492 hd,
13493 nh,
13494 nkv,
13495 &kvl.len_d,
13496 -1,
13497 1,
13498 scale,
13499 win,
13500 kvl.k_tok_bytes,
13501 kvl.v_tok_bytes,
13502 Some((&mut aq8, &mut ad8)),
13503 )?;
13504 fa_q8 = Some((aq8, ad8));
13505 } else {
13506 let (off_tok, t_kv) = if swa && kvl.len > win {
13507 (kvl.len - win, win)
13508 } else {
13509 (0, kvl.len)
13510 };
13511 let k_view = e.view_u8_range(
13512 &kvl.k,
13513 off_tok * kvl.k_tok_bytes,
13514 (off_tok + t_kv) * kvl.k_tok_bytes,
13515 );
13516 let v_view = e.view_u8_range(
13517 &kvl.v,
13518 off_tok * kvl.v_tok_bytes,
13519 (off_tok + t_kv) * kvl.v_tok_bytes,
13520 );
13521 e.fa_decode_kvmod(
13522 &q,
13523 &k_view,
13524 &v_view,
13525 &mut attn,
13526 hd,
13527 nh,
13528 nkv,
13529 t_kv,
13530 scale,
13531 kvl.k_tok_bytes,
13532 kvl.v_tok_bytes,
13533 swa && crate::Engine::wkv_on(),
13534 )?;
13535 }
13536 }
13537 Some((b_swa, b_glob)) => {
13538 let k_view = e.view_u8(&kvl.k, kvl.k.len());
13544 let v_view = e.view_u8(&kvl.v, kvl.v.len());
13545 let rows_on = std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0");
13546 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13547 if !swa && hd == 512 && b_glob >= crate::fa512_min_tkv() && rows_on {
13548 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13549 e.fa_decode_rows(
13550 &q,
13551 &k_view,
13552 &v_view,
13553 &mut attn,
13554 hd,
13555 nh,
13556 nkv,
13557 b_glob - 1,
13558 1,
13559 scale,
13560 kvl.k_tok_bytes,
13561 kvl.v_tok_bytes,
13562 Some((&kvl.len_d, -1)),
13563 false,
13564 false,
13565 Some((&mut aq8, &mut ad8)),
13566 )?;
13567 fa_q8 = Some((aq8, ad8));
13568 } else if swa && b_swa > win && hd == 256 && rows_on {
13569 let (mut aq8, mut ad8) = e.uninit_q8_pair(nh * hd)?;
13570 e.fa_decode_rows_w(
13571 &q,
13572 &k_view,
13573 &v_view,
13574 &mut attn,
13575 hd,
13576 nh,
13577 nkv,
13578 &kvl.len_d,
13579 -1,
13580 1,
13581 scale,
13582 win,
13583 kvl.k_tok_bytes,
13584 kvl.v_tok_bytes,
13585 Some((&mut aq8, &mut ad8)),
13586 )?;
13587 fa_q8 = Some((aq8, ad8));
13588 } else {
13589 let b = if swa { b_swa } else { b_glob };
13590 e.fa_decode_dc(
13591 &q,
13592 &k_view,
13593 &v_view,
13594 &mut attn,
13595 hd,
13596 nh,
13597 nkv,
13598 &kvl.len_d,
13599 b,
13600 scale,
13601 kvl.k_tok_bytes,
13602 kvl.v_tok_bytes,
13603 swa && crate::Engine::wkv_on(),
13604 )?;
13605 }
13606 }
13607 }
13608 if let Some((aq8, ad8)) = fa_q8 {
13611 let mut y = e.uninit(fa.wo.out_features())?;
13612 self.g4_matvec_m1_into(e, &fa.wo, &aq8, &ad8, &mut y)?;
13613 return Ok(y);
13614 }
13615 Ok(e.matmul(&fa.wo, &attn, 1)?)
13616 }
13617
13618 pub fn gemma4_generate_graph(
13623 &self,
13624 e: &Engine,
13625 prompt_pos: usize,
13626 first_token: u32,
13627 cache: &mut Cache,
13628 max_new: usize,
13629 eos: &[u32],
13630 mut on_token: impl FnMut(u32) -> bool,
13631 ) -> Result<(Vec<u32>, crate::decode::StopReason), Box<dyn std::error::Error>> {
13632 if self.is_gemma4_e4b() {
13633 return Err(
13634 "E4B graph serving is unwired (HANDOVER-E4B.md) — dc-eager is the serving arm"
13635 .into(),
13636 );
13637 }
13638 use crate::decode::StopReason;
13639 let n_vocab = self.output.out_features();
13640 let n_embd = self.cfg.n_embd as usize;
13641 let embd_gpu = self
13642 .embd_gpu
13643 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13644 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13645 for kvl in cache.kv.iter_mut().flatten() {
13646 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
13647 }
13648 let mut token_d = e.stream().clone_htod(&[first_token])?;
13649 let mut pos_d = e.htod_i32(&[prompt_pos as i32])?;
13650 let g4 = self.cfg.gemma4.as_ref().unwrap();
13651 let (hd_s, hd_g) = (g4.key_length_swa as usize, g4.key_length_global as usize);
13652 let nkv_s = g4
13654 .head_count_kv
13655 .iter()
13656 .zip(g4.swa_pattern.iter())
13657 .find(|p| *p.1)
13658 .map(|p| *p.0 as usize)
13659 .unwrap_or(8);
13660 let nkv_g = g4
13661 .head_count_kv
13662 .iter()
13663 .zip(g4.swa_pattern.iter())
13664 .find(|p| !*p.1)
13665 .map(|p| *p.0 as usize)
13666 .unwrap_or(2);
13667 let mut graphs: std::collections::HashMap<
13668 ((bool, usize), (bool, usize), bool, bool),
13669 (
13670 cudarc::driver::CudaGraph,
13671 Vec<Box<dyn std::any::Any + Send>>,
13672 ),
13673 > = Default::default();
13674 let mut slots = self.g4_dc_slots(e)?;
13677 const RING: usize = 64;
13680 const DRAIN: usize = 1;
13686 let mut ring = e.stream().alloc_zeros::<u32>(RING)?;
13687 let ring_base = prompt_pos;
13688 let mut out = Vec::with_capacity(max_new);
13689 let mut reason = StopReason::MaxNew;
13690 let mut next = first_token;
13691 let mut captures = 0usize;
13692 for _ in 0..max_new {
13693 out.push(next);
13694 if eos.contains(&next) {
13695 reason = StopReason::Eos;
13696 break;
13697 }
13698 if !on_token(next) {
13699 reason = StopReason::Callback;
13700 break;
13701 }
13702 let t_kv = cache.pos + 1;
13703 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
13711 let f512 = crate::fa512_min_tkv();
13712 let key_s = if t_kv > win {
13713 (true, usize::MAX)
13714 } else {
13715 e.fa_bucket_key(t_kv, hd_s, nkv_s, crate::Engine::wkv_on())
13716 };
13717 let (key_g, rung_end) = if t_kv >= f512 {
13718 let end = (t_kv + 1).next_power_of_two().max(f512 * 2);
13721 ((true, end), end)
13722 } else {
13723 (e.fa_bucket_key(t_kv, hd_g, nkv_g, false), t_kv)
13724 };
13725 let key = (key_s, key_g, t_kv >= f512, t_kv > win);
13726 if !graphs.contains_key(&key) {
13727 let bucket_max = (t_kv, rung_end);
13728 let snap = cache.snapshot(e)?;
13730 let pos_save = e.dtoh_i32_one(&pos_d)?;
13731 let len_save: Vec<Option<i32>> = cache
13732 .kv
13733 .iter()
13734 .map(|k| k.as_ref().map(|kvl| e.dtoh_i32_one(&kvl.len_d).unwrap()))
13735 .collect();
13736 let tok_save = e.dtoh_u32_one(&token_d)?;
13737 let graph = {
13742 let tok_ref = &mut token_d;
13743 let pos_ref = &mut pos_d;
13744 let cache_ref = &mut *cache;
13745 let slots_ref = &mut slots;
13746 let ring_ref = &mut ring;
13747 e.capture_graph_retained_flags(
13748 cudarc::driver::sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
13749 |e| {
13750 let tok_in = unsafe { &*(tok_ref as *const CudaSlice<u32>) };
13752 let sl = unsafe { &mut *(slots_ref as *mut G4DcSlots) };
13753 let rg = unsafe { &mut *(ring_ref as *mut CudaSlice<u32>) };
13754 self.gemma4_decode_step_dc_slotted(e, tok_in, pos_ref, embd_gpu, qt, rb,
13755 cache_ref, n_vocab, Some(bucket_max),
13756 sl, tok_ref, Some((rg, ring_base)))
13757 })?
13758 };
13759 cache.rollback(e, &snap, 0)?;
13760 e.set_i32_one(&mut pos_d, pos_save)?;
13761 for (il, ls) in len_save.iter().enumerate() {
13762 if let (Some(kvl), Some(v)) = (cache.kv[il].as_mut(), ls) {
13763 e.set_i32_one(&mut kvl.len_d, *v)?;
13764 }
13765 }
13766 e.set_u32_one(&mut token_d, tok_save)?;
13767 if std::env::var("MEMRA_GRAPH_CENSUS").as_deref() == Ok("1") {
13768 if let Ok(c) = crate::graph_update::node_census(&graph.0) {
13769 eprintln!("[graph-census] {c:?}");
13770 }
13771 }
13772 graphs.insert(key, graph);
13773 captures += 1;
13774 }
13775 let mut chunk = 1usize;
13780 let drain_cap: usize = std::env::var("MEMRA_GRAPH_DRAIN")
13781 .ok()
13782 .and_then(|v| v.parse().ok())
13783 .unwrap_or(DRAIN);
13784 while chunk < drain_cap && out.len() + chunk < max_new {
13785 let t_next = cache.pos + 1 + chunk;
13786 let key_s2 = if t_next > win {
13787 (true, usize::MAX)
13788 } else {
13789 e.fa_bucket_key(t_next, hd_s, nkv_s, crate::Engine::wkv_on())
13790 };
13791 let key_g2 = if t_next >= f512 {
13792 (true, (t_next + 1).next_power_of_two().max(f512 * 2))
13793 } else {
13794 e.fa_bucket_key(t_next, hd_g, nkv_g, false)
13795 };
13796 if (key_s2, key_g2, t_next >= f512, t_next > win) != key {
13797 break;
13798 }
13799 chunk += 1;
13800 }
13801 let g = &graphs.get(&key).unwrap().0;
13802 for _ in 0..chunk {
13803 g.launch()?;
13804 }
13805 e.stream().synchronize()?;
13806 let ringh = e.dtoh_u32(&ring)?;
13807 for j in 0..chunk {
13808 let pos_j = cache.pos + j;
13809 let tok_j = ringh[(pos_j - ring_base) % RING];
13810 cache.pos += 0; if j + 1 == chunk {
13812 next = tok_j;
13813 } else {
13814 out.push(tok_j);
13815 if eos.contains(&tok_j) || !on_token(tok_j) {
13816 reason = if eos.contains(&tok_j) {
13817 StopReason::Eos
13818 } else {
13819 StopReason::Callback
13820 };
13821 let keep = cache.pos + j + 1;
13823 e.set_i32_one(&mut pos_d, keep as i32)?;
13824 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13825 e.set_i32_one(&mut kvl.len_d, keep as i32)?;
13826 kvl.len = keep;
13827 }
13828 cache.pos = keep;
13829 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13830 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13831 }
13832 return Ok((out, reason));
13833 }
13834 }
13835 }
13836 cache.pos += chunk;
13837 for kvl in cache.kv.iter_mut().filter_map(|k| k.as_mut()) {
13838 kvl.len += chunk;
13839 }
13840 }
13841 if std::env::var("MEMRA_GRAPH_STATS").is_ok() {
13842 eprintln!("[gemma-graph] captures={captures} buckets={}", graphs.len());
13843 }
13844 Ok((out, reason))
13845 }
13846
13847 pub(crate) fn gemma4_decode_step_t(
13853 &self,
13854 e: &Engine,
13855 tokens: &[u32],
13856 pos0: usize,
13857 cache: &mut Cache,
13858 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13859 Ok(self.gemma4_decode_step_t_h(e, tokens, pos0, cache)?.0)
13860 }
13861
13862 pub(crate) fn gemma4_decode_step_t_am(
13866 &self,
13867 e: &Engine,
13868 tokens: &[u32],
13869 pos0: usize,
13870 cache: &mut Cache,
13871 ) -> Result<(Vec<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13872 let (ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13873 let t = tokens.len();
13874 let n_vocab = self.output.out_features();
13875 let mut toks = e.stream().alloc_zeros::<u32>(t)?;
13876 for i in 0..t {
13877 e.argmax_token_device_col(&ld, i, n_vocab, &mut toks, i)?;
13878 }
13879 Ok((e.dtoh_u32(&toks)?, hn))
13880 }
13881
13882 pub(crate) fn gemma4_decode_step_t_am_dev(
13885 &self,
13886 e: &Engine,
13887 tok_d: &CudaSlice<u32>,
13888 t: usize,
13889 pos0: usize,
13890 cache: &mut Cache,
13891 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13892 let (ld, hn) = self.gemma4_verify_trunk(e, &vec![0u32; t], pos0, cache, Some(tok_d))?;
13893 let n_vocab = self.output.out_features();
13894 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13895 for i in 0..t {
13896 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13897 }
13898 Ok((vam, hn))
13899 }
13900
13901 pub(crate) fn gemma4_decode_step_t_h(
13904 &self,
13905 e: &Engine,
13906 tokens: &[u32],
13907 pos0: usize,
13908 cache: &mut Cache,
13909 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13910 let (mut ld, hn) = self.gemma4_verify_trunk(e, tokens, pos0, cache, None)?;
13911 let t = tokens.len();
13912 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
13913 e.softcap(&mut ld, cap, t * self.output.out_features())?;
13914 Ok((e.dtoh(&ld)?, hn))
13915 }
13916
13917 pub(crate) fn verify_stream_scratch(
13920 &self,
13921 e: &Engine,
13922 cap: usize,
13923 ) -> Result<VerifyStreamScratch, Box<dyn std::error::Error>> {
13924 Ok(VerifyStreamScratch {
13925 pos_d: e.htod_i32(&vec![0i32; cap])?,
13926 row_ctrs: (0..cap)
13927 .map(|_| e.htod_i32(&[0]))
13928 .collect::<Result<_, _>>()?,
13929 })
13930 }
13931
13932 pub(crate) fn gemma4_verify_t_am_stream(
13940 &self,
13941 e: &Engine,
13942 tok_d: &CudaSlice<u32>,
13943 t: usize,
13944 ctr: &CudaSlice<i32>,
13945 hint: usize,
13946 cache: &mut Cache,
13947 scr: &mut VerifyStreamScratch,
13948 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13949 let n_embd = self.cfg.n_embd as usize;
13950 let eps = self.cfg.rms_eps;
13951 assert!(t <= scr.row_ctrs.len() && t <= 64);
13952 e.i32_iota_from(ctr, &mut scr.pos_d, t)?;
13953 for i in 0..t {
13954 e.i32_copy_add(ctr, &mut scr.row_ctrs[i], (i + 1) as i32)?;
13955 }
13956 let (pos_d, row_ctrs) = (&scr.pos_d, &scr.row_ctrs);
13957 let embd_gpu = self
13958 .embd_gpu
13959 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
13960 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
13961 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
13962 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
13963 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
13964 let n_layers = self.layers.len();
13965 for (il, layer) in self.layers.iter().enumerate() {
13966 let (hq, hdq) = match h_carry.take() {
13967 Some(p) => p,
13968 None => {
13969 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
13970 }
13971 };
13972 let Mixer::Full(fa) = &layer.mixer else {
13973 panic!("gemma4 layer {il} not full-attn")
13974 };
13975 let o = self
13976 .gemma4_verify_attn_stream(e, fa, il, &hq, &hdq, pos_d, t, cache, hint, row_ctrs)?;
13977 let next_norm = if il + 1 < n_layers {
13978 Some(self.layers[il + 1].attn_norm.float_data())
13979 } else {
13980 None
13981 };
13982 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
13983 x = xn;
13984 h_carry = hn;
13985 self.dflash_tap(e, cache, il, &x, t)?;
13986 }
13987 let mut hn = e.uninit(t * n_embd)?;
13988 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
13989 let ld = e.matmul(&self.output, &hn, t)?;
13990 let n_vocab = self.output.out_features();
13991 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
13992 for i in 0..t {
13993 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
13994 }
13995 Ok((vam, hn))
13996 }
13997
13998 pub(crate) fn dflash_tap(
14005 &self,
14006 e: &Engine,
14007 cache: &mut Cache,
14008 il: usize,
14009 x: &CudaSlice<f32>,
14010 t: usize,
14011 ) -> Result<(), Box<dyn std::error::Error>> {
14012 let Some(taps) = cache.dflash_taps.as_mut() else {
14013 return Ok(());
14014 };
14015 let Some(slot) = taps.layer_ids.iter().position(|&l| l == il) else {
14016 return Ok(());
14017 };
14018 let h = taps.hidden;
14019 let n_taps = taps.layer_ids.len();
14020 let base = taps.base;
14021 debug_assert!(
14022 base + t <= taps.t,
14023 "tap window {base}+{t} exceeds sink {}",
14024 taps.t
14025 );
14026 let xv = e.view(x, t * h);
14027 for r in 0..t {
14028 let row = xv.slice(r * h..(r + 1) * h);
14029 e.copy_view_into(&mut taps.buf, (base + r) * n_taps * h + slot * h, &row, h)?;
14030 }
14031 Ok(())
14032 }
14033
14034 fn gemma4_verify_trunk(
14035 &self,
14036 e: &Engine,
14037 tokens: &[u32],
14038 pos0: usize,
14039 cache: &mut Cache,
14040 tok_dev: Option<&CudaSlice<u32>>,
14041 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14042 let n_embd = self.cfg.n_embd as usize;
14043 let eps = self.cfg.rms_eps;
14044 let t = tokens.len();
14045 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
14046 let pos_d = e.htod_i32(&pos)?;
14047 let mut x = match tok_dev {
14048 Some(td) => {
14049 let embd_gpu = self
14050 .embd_gpu
14051 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
14052 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
14053 e.embed_gather_device_td(embd_gpu, td, t, n_embd, qt, rb)?
14054 }
14055 None => e.htod(&self.embd.gather(n_embd, tokens))?,
14056 };
14057 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
14058 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14059 let n_layers = self.layers.len();
14060 for (il, layer) in self.layers.iter().enumerate() {
14061 let (hq, hdq) = match h_carry.take() {
14062 Some(p) => p,
14063 None => {
14064 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, t, eps)?
14065 }
14066 };
14067 let Mixer::Full(fa) = &layer.mixer else {
14068 panic!("gemma4 layer {il} not full-attn")
14069 };
14070 let o = self.gemma4_verify_attn(e, fa, il, &hq, &hdq, &pos_d, t, cache)?;
14071 let next_norm = if il + 1 < n_layers {
14072 Some(self.layers[il + 1].attn_norm.float_data())
14073 } else {
14074 None
14075 };
14076 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, t, next_norm)?;
14077 x = xn;
14078 h_carry = hn;
14079 self.dflash_tap(e, cache, il, &x, t)?;
14080 }
14081 let mut hn = e.uninit(t * n_embd)?;
14082 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
14083 let mut ld = e.matmul(&self.output, &hn, t)?;
14084 self.gemma4_suppress(e, &mut ld, t)?; cache.pos += t;
14086 Ok((ld, hn))
14087 }
14088
14089 #[allow(clippy::too_many_arguments)]
14097 fn gemma4_verify_attn_stream(
14098 &self,
14099 e: &Engine,
14100 fa: &crate::hybrid::FullAttnLayer,
14101 il: usize,
14102 hq: &CudaSlice<i8>,
14103 hdq: &CudaSlice<f32>,
14104 pos_d: &CudaSlice<i32>,
14105 t: usize,
14106 cache: &mut Cache,
14107 hint: usize,
14108 row_ctrs: &[CudaSlice<i32>],
14109 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14110 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14111 let eps = self.cfg.rms_eps;
14112 let aux = self.gemma4_aux.as_ref().unwrap();
14113 let ones = aux.ones(e);
14114 #[cfg(debug_assertions)]
14115 crate::debug_assert_tensor_stream_device(
14116 ones,
14117 &e.stream(),
14118 "gemma4_verify_attn_stream.ones",
14119 );
14120 let h0 = e.zeros(0)?;
14121 let h = &h0;
14122 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14125 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14126 let fused_qkv = if f2b {
14127 if swa {
14128 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14129 .map(|(a, b, c)| (a, b, Some(c)))
14130 } else {
14131 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14132 .map(|(a, b)| (a, b, None))
14133 }
14134 } else {
14135 None
14136 };
14137 let (q0, k0, v0) = match fused_qkv {
14138 Some((a, b, cv)) => {
14139 let v = match cv {
14140 Some(c) => c,
14141 None => e.clone_dtod(&b)?,
14142 };
14143 (a, b, v)
14144 }
14145 None => {
14146 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14147 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14148 let v0 = if swa {
14149 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14150 } else {
14151 e.clone_dtod(&k0)?
14152 };
14153 (q0, k0, v0)
14154 }
14155 };
14156 let mut q = e.uninit(t * nh * hd)?;
14157 let mut k = e.uninit(t * nkv * hd)?;
14158 let mut v = e.uninit(t * nkv * hd)?;
14159 let ff = if swa {
14162 None
14163 } else {
14164 Some(
14165 aux.rope_freqs(e)
14166 .expect("gemma4 global rope needs rope_freqs.weight"),
14167 )
14168 };
14169 #[cfg(debug_assertions)]
14170 if let Some(ff) = ff {
14171 crate::debug_assert_tensor_stream_device(
14172 ff,
14173 &e.stream(),
14174 "gemma4_verify_attn_stream.rope_freqs",
14175 );
14176 }
14177 e.rms_norm_qkv_rope(
14178 &q0,
14179 &k0,
14180 &v0,
14181 fa.q_norm.float_data(),
14182 fa.k_norm.float_data(),
14183 ones,
14184 &mut q,
14185 &mut k,
14186 &mut v,
14187 hd,
14188 self.gemma4_rope_dims(il),
14189 nh * t,
14190 nkv * t,
14191 pos_d,
14192 nh,
14193 nkv,
14194 base,
14195 1.0,
14196 ff,
14197 eps,
14198 )?;
14199 let kvl = cache.kv[il].as_mut().unwrap();
14200 e.append_kv_quantized_rows_dc(
14202 &k,
14203 &v,
14204 &mut kvl.k,
14205 &mut kvl.v,
14206 &kvl.len_d,
14207 t,
14208 kvl.kv_dim_k,
14209 kvl.kv_dim_v,
14210 kvl.k_tok_bytes,
14211 kvl.v_tok_bytes,
14212 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14213 )?;
14214 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14217 let mut attn = e.uninit(t * nh * hd)?;
14218 let k_view = e.view_u8(&kvl.k, kvl.k.len());
14219 let v_view = e.view_u8(&kvl.v, kvl.v.len());
14220 if swa && hint + 1 >= win {
14223 e.fa_decode_rows_w(
14226 &q,
14227 &k_view,
14228 &v_view,
14229 &mut attn,
14230 hd,
14231 nh,
14232 nkv,
14233 &kvl.len_d,
14234 0,
14235 t,
14236 scale,
14237 win,
14238 kvl.k_tok_bytes,
14239 kvl.v_tok_bytes,
14240 None,
14241 )?;
14242 } else if hd == 512 && hint + t < crate::fa512_min_tkv() {
14243 let bucket = (hint + t + 2)
14256 .next_power_of_two()
14257 .min(crate::fa512_min_tkv().saturating_sub(1));
14258 let qv = e.view(&q, t * nh * hd);
14259 for i in 0..t {
14260 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
14261 let mut q_one = e.uninit(nh * hd)?;
14262 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14263 let mut a_one = e.uninit(nh * hd)?;
14264 e.fa_decode_dc(
14265 &q_one,
14266 &k_view,
14267 &v_view,
14268 &mut a_one,
14269 hd,
14270 nh,
14271 nkv,
14272 &row_ctrs[i],
14273 bucket,
14274 scale,
14275 kvl.k_tok_bytes,
14276 kvl.v_tok_bytes,
14277 false,
14278 )?;
14279 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14280 }
14281 } else if hd == 512 {
14282 e.fa_decode_rows(
14285 &q,
14286 &k_view,
14287 &v_view,
14288 &mut attn,
14289 hd,
14290 nh,
14291 nkv,
14292 hint,
14293 t,
14294 scale,
14295 kvl.k_tok_bytes,
14296 kvl.v_tok_bytes,
14297 Some((&kvl.len_d, 0)),
14298 false,
14299 false,
14300 None,
14301 )?;
14302 } else {
14303 e.fa_decode_rows_dc(
14305 &q,
14306 &k_view,
14307 &v_view,
14308 &mut attn,
14309 hd,
14310 nh,
14311 nkv,
14312 &kvl.len_d,
14313 hint + t,
14314 t,
14315 scale,
14316 kvl.k_tok_bytes,
14317 kvl.v_tok_bytes,
14318 0,
14319 swa && crate::Engine::wkv_on(),
14320 )?;
14321 }
14322 Ok(e.matmul(&fa.wo, &attn, t)?)
14323 }
14324
14325 fn gemma4_verify_attn(
14326 &self,
14327 e: &Engine,
14328 fa: &crate::hybrid::FullAttnLayer,
14329 il: usize,
14330 hq: &CudaSlice<i8>,
14331 hdq: &CudaSlice<f32>,
14332 pos_d: &CudaSlice<i32>,
14333 t: usize,
14334 cache: &mut Cache,
14335 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14336 let (hd, nkv, nh, base, scale, swa) = self.gemma4_geom(il);
14337 let eps = self.cfg.rms_eps;
14338 let aux = self.gemma4_aux.as_ref().unwrap();
14339 let ones = aux.ones(e);
14340 #[cfg(debug_assertions)]
14341 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_verify_attn.ones");
14342 let n_embd = self.cfg.n_embd as usize;
14343 let _ = n_embd;
14344
14345 let h0 = e.zeros(0)?;
14346 let h = &h0;
14347 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14350 let f2b = *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0"));
14351 let fused_qkv = if f2b {
14352 if swa {
14353 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
14354 .map(|(a, b, c)| (a, b, Some(c)))
14355 } else {
14356 e.matmul_q4_fused2_batched(&fa.wq, &fa.wk, hq, hdq, t)?
14357 .map(|(a, b)| (a, b, None))
14358 }
14359 } else {
14360 None
14361 };
14362 let (q0, k0, v0) = match fused_qkv {
14363 Some((a, b, cv)) => {
14364 let v = match cv {
14365 Some(c) => c,
14366 None => e.clone_dtod(&b)?,
14367 };
14368 (a, b, v)
14369 }
14370 None => {
14371 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
14372 let k0 = e.matmul_pre(&fa.wk, hq, hdq, h, t)?;
14373 let v0 = if swa {
14374 e.matmul_pre(&fa.wv, hq, hdq, h, t)?
14375 } else {
14376 e.clone_dtod(&k0)?
14377 };
14378 (q0, k0, v0)
14379 }
14380 };
14381 let mut q = e.uninit(t * nh * hd)?;
14382 let mut k = e.uninit(t * nkv * hd)?;
14383 let mut v = e.uninit(t * nkv * hd)?;
14384 let ff = if swa {
14387 None
14388 } else {
14389 Some(
14390 aux.rope_freqs(e)
14391 .expect("gemma4 global rope needs rope_freqs.weight"),
14392 )
14393 };
14394 #[cfg(debug_assertions)]
14395 if let Some(ff) = ff {
14396 crate::debug_assert_tensor_stream_device(
14397 ff,
14398 &e.stream(),
14399 "gemma4_verify_attn.rope_freqs",
14400 );
14401 }
14402 e.rms_norm_qkv_rope(
14403 &q0,
14404 &k0,
14405 &v0,
14406 fa.q_norm.float_data(),
14407 fa.k_norm.float_data(),
14408 ones,
14409 &mut q,
14410 &mut k,
14411 &mut v,
14412 hd,
14413 self.gemma4_rope_dims(il),
14414 nh * t,
14415 nkv * t,
14416 pos_d,
14417 nh,
14418 nkv,
14419 base,
14420 1.0,
14421 ff,
14422 eps,
14423 )?;
14424 let kvl = cache.kv[il].as_mut().unwrap();
14425 let base_len = kvl.len;
14426 e.append_kv_quantized_rows(
14427 &k,
14428 &v,
14429 &mut kvl.k,
14430 &mut kvl.v,
14431 base_len,
14432 t,
14433 kvl.kv_dim_k,
14434 kvl.kv_dim_v,
14435 kvl.k_tok_bytes,
14436 kvl.v_tok_bytes,
14437 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
14438 )?;
14439 kvl.len += t;
14440 let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
14441 let mut attn = e.uninit(t * nh * hd)?;
14442 let rows_ok = (hd == 256 && base_len + 1 >= crate::fa_vec_min_tkv())
14445 || (hd == 512 && !swa && base_len + 1 >= crate::fa512_min_tkv());
14448 if rows_ok && (!swa || base_len + t <= win) {
14449 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14450 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14451 if hd == 512 {
14452 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14454 e.fa_decode_rows(
14455 &q,
14456 &k_view,
14457 &v_view,
14458 &mut attn,
14459 hd,
14460 nh,
14461 nkv,
14462 base_len,
14463 t,
14464 scale,
14465 kvl.k_tok_bytes,
14466 kvl.v_tok_bytes,
14467 Some((&kvl.len_d, 0)),
14468 false,
14469 swa && crate::Engine::wkv_on(),
14470 None,
14471 )?;
14472 } else {
14473 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14477 e.fa_decode_rows_dc(
14478 &q,
14479 &k_view,
14480 &v_view,
14481 &mut attn,
14482 hd,
14483 nh,
14484 nkv,
14485 &kvl.len_d,
14486 base_len + t,
14487 t,
14488 scale,
14489 kvl.k_tok_bytes,
14490 kvl.v_tok_bytes,
14491 0,
14492 swa && crate::Engine::wkv_on(),
14493 )?;
14494 }
14495 return Ok(e.matmul(&fa.wo, &attn, t)?);
14496 }
14497 if hd == 256
14505 && swa
14506 && base_len + 1 >= win
14507 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14508 {
14509 let k_view = e.view_u8(&kvl.k, (base_len + t) * kvl.k_tok_bytes);
14510 let v_view = e.view_u8(&kvl.v, (base_len + t) * kvl.v_tok_bytes);
14511 e.i32_set_k(&mut kvl.len_d, base_len as i32)?;
14512 e.fa_decode_rows_w(
14513 &q,
14514 &k_view,
14515 &v_view,
14516 &mut attn,
14517 hd,
14518 nh,
14519 nkv,
14520 &kvl.len_d,
14521 0,
14522 t,
14523 scale,
14524 win,
14525 kvl.k_tok_bytes,
14526 kvl.v_tok_bytes,
14527 None,
14528 )?;
14529 return Ok(e.matmul(&fa.wo, &attn, t)?);
14530 }
14531 for i in 0..t {
14532 let avail = base_len + i + 1;
14533 let (off_tok, t_kv) = if swa && avail > win {
14534 (avail - win, win)
14535 } else {
14536 (0, avail)
14537 };
14538 let k_view = e.view_u8_range(
14539 &kvl.k,
14540 off_tok * kvl.k_tok_bytes,
14541 (off_tok + t_kv) * kvl.k_tok_bytes,
14542 );
14543 let v_view = e.view_u8_range(
14544 &kvl.v,
14545 off_tok * kvl.v_tok_bytes,
14546 (off_tok + t_kv) * kvl.v_tok_bytes,
14547 );
14548 let qi = e.view(&q, t * nh * hd);
14549 let q_row = qi.slice(i * nh * hd..(i + 1) * nh * hd);
14550 let mut q_one = e.uninit(nh * hd)?;
14551 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
14552 let mut a_one = e.uninit(nh * hd)?;
14553 if swa
14557 && avail > win
14558 && hd == 256
14559 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14560 {
14561 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14562 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14563 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14564 e.fa_decode_rows_w(
14565 &q_one,
14566 &kp,
14567 &vp,
14568 &mut a_one,
14569 hd,
14570 nh,
14571 nkv,
14572 &kvl.len_d,
14573 0,
14574 1,
14575 scale,
14576 win,
14577 kvl.k_tok_bytes,
14578 kvl.v_tok_bytes,
14579 None,
14580 )?;
14581 } else if !swa
14582 && hd == 512
14583 && avail >= crate::fa512_min_tkv()
14584 && std::env::var("MEMRA_GEMMA_ROWS_W").as_deref() != Ok("0")
14585 {
14586 let kp = e.view_u8(&kvl.k, avail * kvl.k_tok_bytes);
14587 let vp = e.view_u8(&kvl.v, avail * kvl.v_tok_bytes);
14588 e.i32_set_k(&mut kvl.len_d, (avail - 1) as i32)?;
14589 e.fa_decode_rows(
14590 &q_one,
14591 &kp,
14592 &vp,
14593 &mut a_one,
14594 hd,
14595 nh,
14596 nkv,
14597 avail - 1,
14598 1,
14599 scale,
14600 kvl.k_tok_bytes,
14601 kvl.v_tok_bytes,
14602 Some((&kvl.len_d, 0)),
14603 false,
14604 false,
14605 None,
14606 )?;
14607 } else {
14608 e.fa_decode_kvmod(
14609 &q_one,
14610 &k_view,
14611 &v_view,
14612 &mut a_one,
14613 hd,
14614 nh,
14615 nkv,
14616 t_kv,
14617 scale,
14618 kvl.k_tok_bytes,
14619 kvl.v_tok_bytes,
14620 swa && crate::Engine::wkv_on(),
14621 )?;
14622 }
14623 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
14624 }
14625 Ok(e.matmul(&fa.wo, &attn, t)?)
14626 }
14627
14628 pub(crate) fn gemma4_decode_step_h(
14631 &self,
14632 e: &Engine,
14633 token: u32,
14634 cache: &mut Cache,
14635 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14636 if let Some(split) = crate::pp::pp2_split(self.layers.len()) {
14641 return self.gemma4_decode_step_h_pp2(e, token, cache, split);
14642 }
14643 if crate::pp::pp_cuts(self.layers.len()).is_some() {
14644 crate::pp::warn_unwired_once("gemma4 eager decode (N>2)");
14645 }
14646 let n_embd = self.cfg.n_embd as usize;
14647 let eps = self.cfg.rms_eps;
14648 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14649 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14650 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14651 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14654 let n_layers = self.layers.len();
14655 for (il, layer) in self.layers.iter().enumerate() {
14656 let (hq, hdq) = match h_carry.take() {
14657 Some(p) => p,
14658 None => {
14659 e.rms_norm_q8_1(&x, self.layers[0].attn_norm.float_data(), n_embd, 1, eps)?
14660 }
14661 };
14662 let Mixer::Full(fa) = &layer.mixer else {
14663 panic!("gemma4 layer {il} not full-attn")
14664 };
14665 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, &pos_d, cache)?;
14666 let next_norm = if il + 1 < n_layers {
14667 Some(self.layers[il + 1].attn_norm.float_data())
14668 } else {
14669 None
14670 };
14671 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14672 x = xn;
14673 h_carry = hn;
14674 }
14675 let mut hn = e.uninit(n_embd)?;
14676 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14677 let h_seed = e.clone_dtod(&x)?;
14678 let mut ld = e.matmul(&self.output, &hn, 1)?;
14679 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14680 e.softcap(&mut ld, cap, self.output.out_features())?; self.gemma4_suppress(e, &mut ld, 1)?;
14682 let logits = e.dtoh(&ld)?;
14683 cache.pos += 1;
14684 Ok((logits, h_seed))
14685 }
14686
14687 fn gemma4_decode_layers(
14695 &self,
14696 e: &Engine,
14697 mut x: CudaSlice<f32>,
14698 lo: usize,
14699 hi: usize,
14700 pos_d: &CudaSlice<i32>,
14701 cache: &mut Cache,
14702 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14703 let n_embd = self.cfg.n_embd as usize;
14704 let eps = self.cfg.rms_eps;
14705 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
14706 for il in lo..hi {
14707 let layer = &self.layers[il];
14708 let (hq, hdq) = match h_carry.take() {
14709 Some(p) => p,
14710 None => {
14712 e.rms_norm_q8_1(&x, self.layers[il].attn_norm.float_data(), n_embd, 1, eps)?
14713 }
14714 };
14715 let Mixer::Full(fa) = &layer.mixer else {
14716 panic!("gemma4 layer {il} not full-attn")
14717 };
14718 let o = self.gemma4_decode_attn(e, fa, il, &hq, &hdq, pos_d, cache)?;
14719 let next_norm = if il + 1 < hi {
14720 Some(self.layers[il + 1].attn_norm.float_data())
14721 } else {
14722 None
14723 };
14724 let (xn, hn) = self.gemma4_layer_tail_add_nq_pn(e, layer, &o, &x, 1, next_norm)?;
14725 x = xn;
14726 h_carry = hn;
14727 }
14728 Ok(x)
14729 }
14730
14731 fn gemma4_decode_step_h_pp2(
14739 &self,
14740 e: &Engine,
14741 token: u32,
14742 cache: &mut Cache,
14743 split: usize,
14744 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14745 if crate::pp::pp2_streams_off() {
14746 return self.gemma4_decode_step_h_pp2_samestream(e, token, cache, split);
14747 }
14748 let rt = crate::pp::Pp2Rt::get(e)?;
14749 let e0 = rt.engine(0, e);
14750 let e1 = rt.engine(1, e);
14751 let n_embd = self.cfg.n_embd as usize;
14752 let eps = self.cfg.rms_eps;
14753 let pos = cache.pos as i32;
14754
14755 let slot = {
14757 let _st0 = rt.enter(0);
14758 let pos_d = e0.htod_i32(&[pos])?;
14759 #[cfg(debug_assertions)]
14760 crate::debug_assert_tensor_stream_device(
14761 &pos_d,
14762 &e0.stream(),
14763 "gemma4_decode_step_h_pp2.stage0.pos_d",
14764 );
14765 let mut x = e0.htod(&self.embd.gather(n_embd, &[token]))?;
14766 e0.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14767 let x = self.gemma4_decode_layers(e0, x, 0, split, &pos_d, cache)?;
14768 rt.tx(0, &x, n_embd)?
14769 };
14770
14771 let _st1 = rt.enter(1);
14773 let pos_d = e1.htod_i32(&[pos])?;
14774 #[cfg(debug_assertions)]
14775 crate::debug_assert_tensor_stream_device(
14776 &pos_d,
14777 &e1.stream(),
14778 "gemma4_decode_step_h_pp2.stage1.pos_d",
14779 );
14780 let x = rt.rx(0, slot, n_embd)?;
14781 let x = self.gemma4_decode_layers(e1, x, split, self.layers.len(), &pos_d, cache)?;
14782
14783 let mut hn = e1.uninit(n_embd)?;
14784 e1.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14785 let h_seed = e1.clone_dtod(&x)?;
14786 let mut ld = e1.matmul(&self.output, &hn, 1)?;
14787 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14788 e1.softcap(&mut ld, cap, self.output.out_features())?;
14789 self.gemma4_suppress(e1, &mut ld, 1)?;
14790 let logits = e1.dtoh(&ld)?;
14791 cache.pos += 1;
14792 Ok((logits, h_seed))
14793 }
14794
14795 fn gemma4_decode_step_h_pp2_samestream(
14798 &self,
14799 e: &Engine,
14800 token: u32,
14801 cache: &mut Cache,
14802 split: usize,
14803 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14804 let n_embd = self.cfg.n_embd as usize;
14805 let eps = self.cfg.rms_eps;
14806 let pos_d = e.htod_i32(&[cache.pos as i32])?;
14807
14808 let mut x = e.htod(&self.embd.gather(n_embd, &[token]))?;
14810 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
14811 let x = self.gemma4_decode_layers(e, x, 0, split, &pos_d, cache)?;
14812
14813 let boundary_tx = e.clone_dtod(&x)?;
14815 let boundary_rx = e.clone_dtod(&boundary_tx)?;
14816
14817 let x =
14819 self.gemma4_decode_layers(e, boundary_rx, split, self.layers.len(), &pos_d, cache)?;
14820
14821 let mut hn = e.uninit(n_embd)?;
14822 e.rms_norm(&x, self.output_norm.float_data(), &mut hn, n_embd, 1, eps)?;
14823 let h_seed = e.clone_dtod(&x)?;
14824 let mut ld = e.matmul(&self.output, &hn, 1)?;
14825 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
14826 e.softcap(&mut ld, cap, self.output.out_features())?;
14827 self.gemma4_suppress(e, &mut ld, 1)?;
14828 let logits = e.dtoh(&ld)?;
14829 cache.pos += 1;
14830 Ok((logits, h_seed))
14831 }
14832}
14833
14834impl HybridModel {
14853 pub(crate) fn step35_geom(&self, il: usize) -> memra_gguf::config::LayerGeometry {
14856 let geometry = self
14857 .cfg
14858 .layer_geometry(il as u32)
14859 .unwrap_or_else(|| panic!("step35 layer {il} has no geometry-table row"));
14860 debug_assert_eq!(
14861 geometry.attention_gate,
14862 memra_gguf::config::AttentionGateKind::SeparateHead
14863 );
14864 geometry
14865 }
14866
14867 #[allow(clippy::too_many_arguments)]
14927 fn step35_attn_pre_wo(
14928 &self,
14929 e: &Engine,
14930 fa: &FullAttnLayer,
14931 mut g3: Vec<CudaSlice<f32>>,
14932 hg: Option<&CudaSlice<f32>>,
14933 gt_pre: Option<&CudaSlice<f32>>,
14934 pos_d: &CudaSlice<i32>,
14935 t: usize,
14936 cache: Option<&mut Cache>,
14937 il: usize,
14938 seq_end: usize,
14939 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14940 let geometry = self.step35_geom(il);
14941 let hd = geometry.head_dim_k as usize;
14942 let nkv = geometry.n_head_kv as usize;
14943 let nh = geometry.n_head as usize;
14944 let rbase = geometry.rope_base;
14945 let scale = geometry.attention_scale();
14946 let swa = geometry.window.is_some();
14947 let eps = self.cfg.rms_eps;
14948 let win = geometry.window.unwrap_or(0) as usize;
14949 let n_rot = geometry.n_rot as usize;
14950
14951 let v = g3.pop().unwrap();
14952 let k0 = g3.pop().unwrap();
14953 let q0 = g3.pop().unwrap();
14954
14955 let mut q = e.uninit(t * nh * hd)?;
14959 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh * t, eps)?;
14960 let mut k = e.uninit(t * nkv * hd)?;
14961 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv * t, eps)?;
14962 let ff = if geometry.rope_factors {
14963 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
14964 } else {
14965 None
14966 };
14967 #[cfg(debug_assertions)]
14968 if let Some(ff) = ff {
14969 crate::debug_assert_tensor_stream_device(
14970 ff,
14971 &e.stream(),
14972 "step35_attn_pre_wo.rope_freqs",
14973 );
14974 }
14975 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, t, rbase, 1.0, ff)?;
14976
14977 let mut attn = e.uninit(t * nh * hd)?;
14978 match cache {
14979 Some(cache) => {
14980 let base_len = cache.kv[il].as_ref().unwrap().len;
14981 let legacy_tkv = std::env::var("MEMRA_STEP35_SWA_TKV").as_deref() == Ok("1");
14983 let legacy_calllocal = std::env::var("MEMRA_PRIME_CALLLOCAL").as_deref() == Ok("1");
14984 let off = if swa {
14985 let raw = base_len.saturating_sub(win - 1);
14986 if legacy_tkv || legacy_calllocal {
14987 raw
14988 } else {
14989 raw & !31usize
14990 }
14991 } else {
14992 0
14993 };
14994 {
14995 let kvl = cache.kv[il].as_mut().unwrap();
14996 assert!(kvl.len + t <= cache.max_ctx, "step35 prime: KV overflow");
14997 let write_row = e.prepare_kv_append(kvl, off, t)?;
14998 e.append_kv_quantized_rows(
14999 &k,
15000 &v,
15001 &mut kvl.k,
15002 &mut kvl.v,
15003 write_row,
15004 t,
15005 kvl.kv_dim_k,
15006 kvl.kv_dim_v,
15007 kvl.k_tok_bytes,
15008 kvl.v_tok_bytes,
15009 crate::Engine::kv_fp8_on(),
15010 )?;
15011 kvl.len += t;
15012 let new_len = kvl.len as i32;
15013 e.set_i32_one(&mut kvl.len_d, new_len)?;
15014 }
15015 let kvl = cache.kv[il].as_ref().unwrap();
15016 let t_kv = base_len + t - off;
15039 let physical = kvl.physical_rows(off, off + t_kv)?;
15040 let k_view = e.view_u8_range(
15041 &kvl.k,
15042 physical.start * kvl.k_tok_bytes,
15043 physical.end * kvl.k_tok_bytes,
15044 );
15045 let v_view = e.view_u8_range(
15046 &kvl.v,
15047 physical.start * kvl.v_tok_bytes,
15048 physical.end * kvl.v_tok_bytes,
15049 );
15050 let swa_naive = if legacy_tkv {
15062 t_kv > win
15063 } else {
15064 seq_end > win
15065 };
15066 if swa && swa_naive {
15067 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15080 e.sdpa_naive_w_quantized_view(
15081 &q,
15082 &k_view,
15083 &v_view,
15084 &mut attn,
15085 hd,
15086 nh,
15087 nkv,
15088 t,
15089 t_kv,
15090 scale,
15091 true,
15092 win,
15093 kvl.k_tok_bytes,
15094 kvl.v_tok_bytes,
15095 )?;
15096 } else {
15097 e.fa_prefill_view_ws_w_hd128(
15098 &q,
15099 &k_view,
15100 &v_view,
15101 &mut attn,
15102 hd,
15103 nh,
15104 nkv,
15105 t,
15106 t_kv,
15107 scale,
15108 true,
15109 win,
15110 kvl.k_tok_bytes,
15111 kvl.v_tok_bytes,
15112 )?;
15113 }
15114 } else if std::env::var("MEMRA_NOFA").is_ok() {
15115 e.sdpa_naive_quantized_view(
15116 &q,
15117 &k_view,
15118 &v_view,
15119 &mut attn,
15120 hd,
15121 nh,
15122 nkv,
15123 t,
15124 t_kv,
15125 scale,
15126 true,
15127 kvl.k_tok_bytes,
15128 kvl.v_tok_bytes,
15129 )?;
15130 } else {
15131 e.fa_prefill_view_ws(
15136 &q,
15137 &k_view,
15138 &v_view,
15139 &mut attn,
15140 hd,
15141 nh,
15142 nkv,
15143 t,
15144 t_kv,
15145 scale,
15146 true,
15147 kvl.k_tok_bytes,
15148 kvl.v_tok_bytes,
15149 crate::Engine::kv_fp8_on(),
15150 )?;
15151 }
15152 }
15153 None => {
15154 debug_assert_eq!(
15159 seq_end, t,
15160 "step35 cacheless prefill is monolithic (seq_end == t)"
15161 );
15162 if swa && seq_end > win {
15163 e.sdpa_naive_w(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
15164 } else if std::env::var("MEMRA_NOFA").is_ok() {
15165 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15166 } else {
15167 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, t, t, scale, true)?;
15168 }
15169 }
15170 }
15171
15172 let gw = fa
15175 .attn_gate
15176 .as_ref()
15177 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
15178 let gt_owned = if gt_pre.is_none() {
15179 Some(e.matmul(
15180 gw,
15181 hg.ok_or("step35 attention needs hg when gt_pre is absent")?,
15182 t,
15183 )?)
15184 } else {
15185 None
15186 };
15187 let gt = gt_pre.or(gt_owned.as_ref()).unwrap();
15188 let mut ag = e.uninit(t * nh * hd)?;
15189 e.attn_head_gate(&attn, gt, &mut ag, None, hd, nh, t)?;
15190 Ok(ag)
15191 }
15192
15193 pub(crate) fn step35_attn(
15196 &self,
15197 e: &Engine,
15198 fa: &FullAttnLayer,
15199 h: &CudaSlice<f32>,
15200 pos_d: &CudaSlice<i32>,
15201 t: usize,
15202 il: usize,
15203 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15204 let g3 = match self.step35_tp_qkv(e, fa, h, t)? {
15205 Some(g3) => g3,
15206 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15207 };
15208 let ag = self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, None, il, t)?;
15210 self.step35_o(e, fa, &ag, t)
15211 }
15212
15213 #[allow(clippy::too_many_arguments)]
15220 pub(crate) fn step35_attn_prime(
15221 &self,
15222 e: &Engine,
15223 fa: &FullAttnLayer,
15224 h: &CudaSlice<f32>,
15225 hx: Option<&CudaSlice<u8>>,
15226 pos_d: &CudaSlice<i32>,
15227 t: usize,
15228 cache: &mut Cache,
15229 il: usize,
15230 seq_end: usize,
15231 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15232 if step_tp_prefill_enabled()? && fa.step_tp_qkv.is_some() {
15233 if hx.is_some() {
15234 return Err(
15235 "rank-local Step prefill preserves BF16 activations and refuses the q8_1 \
15236 pre-quantized prime path"
15237 .into(),
15238 );
15239 }
15240 return self.step35_tp_prefill_attn_resident(e, fa, il, h, pos_d, t, cache, seq_end);
15241 }
15242 let g3 = if fa.step_tp_qkv.is_some() {
15243 if hx.is_some() {
15244 return Err(
15245 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
15246 pre-quantized prime path"
15247 .into(),
15248 );
15249 }
15250 self.step35_tp_qkv(e, fa, h, t)?
15251 .expect("Step Q/K/V TP disappeared after the presence check")
15252 } else {
15253 match hx {
15254 Some(xh) => e.matmul_group_xh(&[&fa.wq, &fa.wk, &fa.wv], h, xh, t)?,
15255 None => e.matmul_group(&[&fa.wq, &fa.wk, &fa.wv], h, t)?,
15256 }
15257 };
15258 let ag =
15259 self.step35_attn_pre_wo(e, fa, g3, Some(h), None, pos_d, t, Some(cache), il, seq_end)?;
15260 self.step35_o(e, fa, &ag, t)
15261 }
15262
15263 fn ensure_step_tp_kv_cache(
15264 &self,
15265 e: &Engine,
15266 fa: &FullAttnLayer,
15267 il: usize,
15268 cache: &mut Cache,
15269 ) -> Result<bool, Box<dyn std::error::Error>> {
15270 let tp = fa
15271 .step_tp_qkv
15272 .as_ref()
15273 .ok_or("Step TP cache hydration lost its resident projections")?;
15274 let geometry = self.step35_geom(il);
15275 let window = geometry.window.map(|window| window as usize);
15276 let ranks = tp.runtime.devices().len();
15277 let head_dim = geometry.head_dim_k as usize;
15278 let kv_heads = geometry.n_head_kv as usize;
15279 let max_ctx = cache.max_ctx;
15280
15281 if cache.tp_kv[il].is_some() {
15282 return Ok(false);
15283 }
15284 let local = cache.kv[il]
15285 .as_ref()
15286 .ok_or_else(|| format!("Step TP layer {il} has no owning-stage KV cache"))?;
15287 if local.kv_dim_k != kv_heads * head_dim || local.kv_dim_v != kv_heads * head_dim {
15288 return Err(format!(
15289 "Step TP layer {il} local KV geometry k={} v={} != {}",
15290 local.kv_dim_k,
15291 local.kv_dim_v,
15292 kv_heads * head_dim
15293 )
15294 .into());
15295 }
15296 let resident_start = window
15297 .map(|window| local.len.saturating_sub(window.saturating_sub(1)) & !31usize)
15298 .unwrap_or(0);
15299 let resident_rows = local.len - resident_start;
15300 let physical = local.physical_rows(resident_start, local.len)?;
15301 let k_rows = if resident_rows == 0 {
15302 Vec::new()
15303 } else {
15304 e.dtoh_u8_view(&e.view_u8_range(
15305 &local.k,
15306 physical.start * local.k_tok_bytes,
15307 physical.end * local.k_tok_bytes,
15308 ))?
15309 };
15310 let v_rows = if resident_rows == 0 {
15311 Vec::new()
15312 } else {
15313 e.dtoh_u8_view(&e.view_u8_range(
15314 &local.v,
15315 physical.start * local.v_tok_bytes,
15316 physical.end * local.v_tok_bytes,
15317 ))?
15318 };
15319 let mut distributed = match window {
15320 Some(window) => tp.runtime.allocate_tp_swa_kv_cache(
15321 kv_heads * head_dim,
15322 kv_heads * head_dim,
15323 max_ctx,
15324 window,
15325 )?,
15326 None => tp.runtime.allocate_tp_kv_cache(
15327 kv_heads * head_dim,
15328 kv_heads * head_dim,
15329 max_ctx,
15330 )?,
15331 };
15332 if distributed.k_tok_bytes() * ranks != local.k_tok_bytes
15333 || distributed.v_tok_bytes() * ranks != local.v_tok_bytes
15334 {
15335 return Err(format!(
15336 "Step TP layer {il} distributed/local KV token bytes disagree: \
15337 k={}x{ranks}/{} v={}x{ranks}/{}",
15338 distributed.k_tok_bytes(),
15339 local.k_tok_bytes,
15340 distributed.v_tok_bytes(),
15341 local.v_tok_bytes,
15342 )
15343 .into());
15344 }
15345 tp.runtime.hydrate_tp_kv_cache_from(
15346 &mut distributed,
15347 local.len,
15348 resident_start,
15349 &k_rows,
15350 &v_rows,
15351 )?;
15352 cache.tp_kv[il] = Some(distributed);
15353 Ok(true)
15354 }
15355
15356 #[allow(clippy::too_many_arguments)]
15357 fn step35_tp_prefill_attn_resident(
15358 &self,
15359 e: &Engine,
15360 fa: &FullAttnLayer,
15361 il: usize,
15362 h: &CudaSlice<f32>,
15363 pos_d: &CudaSlice<i32>,
15364 tokens: usize,
15365 cache: &mut Cache,
15366 seq_end: usize,
15367 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15368 let tp = fa
15369 .step_tp_qkv
15370 .as_ref()
15371 .ok_or("Step TP prefill lost its resident projections")?;
15372 let attention = tp
15373 .attention
15374 .as_ref()
15375 .ok_or("Step TP prefill lost its resident attention auxiliaries")?;
15376 let ranks = tp.runtime.devices().len();
15377 if !step_tp_prefill_shape(
15378 true,
15379 tokens,
15380 ranks,
15381 tp.runtime.native_p2p(),
15382 true,
15383 crate::Engine::kv_fp8_on(),
15384 ) {
15385 return Err(format!(
15386 "rank-local Step prefill requires tokens>={PRIME_MIN_T}, TP2/TP4 native P2P, \
15387 rank-local attention, and q8_0/q5_1 KV; got tokens={tokens} ranks={ranks} \
15388 native_p2p={} fp8_kv={}",
15389 tp.runtime.native_p2p(),
15390 crate::Engine::kv_fp8_on(),
15391 )
15392 .into());
15393 }
15394 for seam in [
15395 "MEMRA_STEP35_SWA_TKV",
15396 "MEMRA_PRIME_CALLLOCAL",
15397 "MEMRA_PRIME_F32CHUNK0",
15398 ] {
15399 if std::env::var(seam).as_deref() == Ok("1") {
15400 return Err(format!(
15401 "rank-local Step prefill has not qualified the legacy seam {seam}=1"
15402 )
15403 .into());
15404 }
15405 }
15406
15407 let geometry = self.step35_geom(il);
15408 let window = geometry.window.map(|window| window as usize);
15409 let head_dim = geometry.head_dim_k as usize;
15410 let heads = geometry.n_head as usize;
15411 let kv_heads = geometry.n_head_kv as usize;
15412 if heads % ranks != 0 || kv_heads % ranks != 0 {
15413 return Err(format!(
15414 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15415 )
15416 .into());
15417 }
15418 let local_heads = heads / ranks;
15419 let local_kv_heads = kv_heads / ranks;
15420 let local_kv_dim = local_kv_heads * head_dim;
15421 let hidden = self.cfg.n_embd as usize;
15422 let expected_input = tokens
15423 .checked_mul(hidden)
15424 .ok_or("Step TP prefill input size overflow")?;
15425 if h.len() < expected_input {
15426 return Err(format!(
15427 "Step TP prefill input {} is shorter than {tokens}x{hidden}",
15428 h.len()
15429 )
15430 .into());
15431 }
15432 let positions = e.dtoh_i32(pos_d)?;
15433 if positions.len() != tokens {
15434 return Err(format!(
15435 "rank-local Step prefill positions {} != tokens {tokens}",
15436 positions.len()
15437 )
15438 .into());
15439 }
15440
15441 let mut active_input = e.uninit(expected_input)?;
15442 e.copy_view_into(
15443 &mut active_input,
15444 0,
15445 &h.slice(0..expected_input),
15446 expected_input,
15447 )?;
15448 let mut input = tp.runtime.allocate_replicated_device_rows(tokens, hidden)?;
15449 e.stream().synchronize()?;
15454 tp.runtime
15455 .refresh_replicated_device_rows_from_root(&mut input, &active_input)?;
15456 let q_raw = tp
15457 .runtime
15458 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &input)?;
15459 let k_raw = tp
15460 .runtime
15461 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &input)?;
15462 let v_raw = tp
15463 .runtime
15464 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &input)?;
15465 let mut q = Vec::with_capacity(ranks);
15466 let mut k = Vec::with_capacity(ranks);
15467 for rank in 0..ranks {
15468 let engine = tp
15469 .runtime
15470 .rank_engine(rank)
15471 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15472 let _main = engine.gpu.enter_main()?;
15473 let mut q_rank = engine.uninit(tokens * local_heads * head_dim)?;
15474 engine.rms_norm(
15475 &q_raw[rank],
15476 &attention.q_norm[rank],
15477 &mut q_rank,
15478 head_dim,
15479 tokens * local_heads,
15480 self.cfg.rms_eps,
15481 )?;
15482 let mut k_rank = engine.uninit(tokens * local_kv_dim)?;
15483 engine.rms_norm(
15484 &k_raw[rank],
15485 &attention.k_norm[rank],
15486 &mut k_rank,
15487 head_dim,
15488 tokens * local_kv_heads,
15489 self.cfg.rms_eps,
15490 )?;
15491 let position = engine.htod_i32(&positions)?;
15492 let rope_freqs = if geometry.rope_factors {
15493 self.step35_aux
15494 .as_ref()
15495 .and_then(|aux| aux.rope_freqs(engine))
15496 } else {
15497 None
15498 };
15499 engine.rope_neox2(
15500 &mut q_rank,
15501 &mut k_rank,
15502 &position,
15503 head_dim,
15504 geometry.n_rot as usize,
15505 local_heads,
15506 local_kv_heads,
15507 tokens,
15508 geometry.rope_base,
15509 1.0,
15510 rope_freqs,
15511 )?;
15512 q.push(q_rank);
15513 k.push(k_rank);
15514 }
15515
15516 let gate_weight = fa
15517 .attn_gate
15518 .as_ref()
15519 .ok_or("step35 layer is missing attn_gate.weight")?;
15520 let gate = e.dtoh(&e.matmul(gate_weight, h, tokens)?)?;
15521 if gate.len() != tokens * heads {
15522 return Err(format!(
15523 "Step TP layer {il} gate output {} != {tokens}x{heads}",
15524 gate.len()
15525 )
15526 .into());
15527 }
15528
15529 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15530 let base_len = cache.kv[il]
15531 .as_ref()
15532 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15533 .len;
15534 let distributed = cache.tp_kv[il]
15535 .as_ref()
15536 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15537 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15538 return Err(format!(
15539 "Step TP layer {il} cache lengths diverged before prefill: \
15540 local={base_len} distributed={}/{}",
15541 distributed.committed_len(),
15542 distributed.staged_len()
15543 )
15544 .into());
15545 }
15546 let target_len = base_len
15547 .checked_add(tokens)
15548 .ok_or("Step TP prefill cache length overflow")?;
15549 if target_len > cache.max_ctx {
15550 return Err(format!(
15551 "Step TP layer {il} prefill exceeds cache: {base_len}+{tokens}>{}",
15552 cache.max_ctx
15553 )
15554 .into());
15555 }
15556 if seq_end < target_len {
15557 return Err(format!(
15558 "Step TP layer {il} request end {seq_end} precedes chunk end {target_len}"
15559 )
15560 .into());
15561 }
15562
15563 let transaction = cache.tp_kv[il]
15564 .as_mut()
15565 .expect("distributed cache checked above")
15566 .begin_transaction()?;
15567 if let Err(error) = tp.runtime.append_tp_kv_transaction(
15568 cache.tp_kv[il]
15569 .as_mut()
15570 .expect("distributed cache checked above"),
15571 transaction,
15572 &k,
15573 &v_raw,
15574 tokens,
15575 ) {
15576 let _ = tp.runtime.rollback_tp_kv_transaction(
15577 cache.tp_kv[il]
15578 .as_mut()
15579 .expect("distributed cache checked above"),
15580 transaction,
15581 );
15582 return Err(error);
15583 }
15584
15585 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15586 let distributed = cache.tp_kv[il]
15587 .as_ref()
15588 .expect("distributed cache checked above");
15589 let staged_len = distributed.staged_len();
15590 let view_start = window
15591 .map(|window| base_len.saturating_sub(window.saturating_sub(1)) & !31usize)
15592 .unwrap_or(0);
15593 let physical = distributed.physical_range(view_start, staged_len)?;
15594 let t_kv = staged_len - view_start;
15595 let swa_naive = window.is_some_and(|window| seq_end > window);
15596 let mut gated = Vec::with_capacity(ranks);
15597 for rank in 0..ranks {
15598 let engine = tp
15599 .runtime
15600 .rank_engine(rank)
15601 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15602 let _main = engine.gpu.enter_main()?;
15603 let rank_cache = distributed
15604 .rank(rank)
15605 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
15606 let k_view = engine.view_u8_range(
15607 rank_cache.k(),
15608 physical.start * distributed.k_tok_bytes(),
15609 physical.end * distributed.k_tok_bytes(),
15610 );
15611 let v_view = engine.view_u8_range(
15612 rank_cache.v(),
15613 physical.start * distributed.v_tok_bytes(),
15614 physical.end * distributed.v_tok_bytes(),
15615 );
15616 let mut attention_out = engine.uninit(tokens * local_heads * head_dim)?;
15617 if swa_naive {
15618 let window = window.expect("SWA predicate requires a window");
15619 if std::env::var("MEMRA_STEP35_SWA_FA").as_deref() == Ok("0") {
15620 engine.sdpa_naive_w_quantized_view(
15621 &q[rank],
15622 &k_view,
15623 &v_view,
15624 &mut attention_out,
15625 head_dim,
15626 local_heads,
15627 local_kv_heads,
15628 tokens,
15629 t_kv,
15630 geometry.attention_scale(),
15631 true,
15632 window,
15633 distributed.k_tok_bytes(),
15634 distributed.v_tok_bytes(),
15635 )?;
15636 } else {
15637 engine.fa_prefill_view_ws_w_hd128(
15638 &q[rank],
15639 &k_view,
15640 &v_view,
15641 &mut attention_out,
15642 head_dim,
15643 local_heads,
15644 local_kv_heads,
15645 tokens,
15646 t_kv,
15647 geometry.attention_scale(),
15648 true,
15649 window,
15650 distributed.k_tok_bytes(),
15651 distributed.v_tok_bytes(),
15652 )?;
15653 }
15654 } else if std::env::var("MEMRA_NOFA").is_ok() {
15655 engine.sdpa_naive_quantized_view(
15656 &q[rank],
15657 &k_view,
15658 &v_view,
15659 &mut attention_out,
15660 head_dim,
15661 local_heads,
15662 local_kv_heads,
15663 tokens,
15664 t_kv,
15665 geometry.attention_scale(),
15666 true,
15667 distributed.k_tok_bytes(),
15668 distributed.v_tok_bytes(),
15669 )?;
15670 } else {
15671 engine.fa_prefill_view_ws(
15672 &q[rank],
15673 &k_view,
15674 &v_view,
15675 &mut attention_out,
15676 head_dim,
15677 local_heads,
15678 local_kv_heads,
15679 tokens,
15680 t_kv,
15681 geometry.attention_scale(),
15682 true,
15683 distributed.k_tok_bytes(),
15684 distributed.v_tok_bytes(),
15685 false,
15686 )?;
15687 }
15688
15689 let gate_start = rank * local_heads;
15690 let mut gate_rank = Vec::with_capacity(tokens * local_heads);
15691 for token in 0..tokens {
15692 let start = token * heads + gate_start;
15693 gate_rank.extend_from_slice(&gate[start..start + local_heads]);
15694 }
15695 let gate_rank = engine.htod(&gate_rank)?;
15696 let mut gated_rank = engine.uninit(tokens * local_heads * head_dim)?;
15697 engine.attn_head_gate(
15698 &attention_out,
15699 &gate_rank,
15700 &mut gated_rank,
15701 None,
15702 head_dim,
15703 local_heads,
15704 tokens,
15705 )?;
15706 gated.push(gated_rank);
15707 }
15708 for rank in 1..ranks {
15709 let engine = tp
15710 .runtime
15711 .rank_engine(rank)
15712 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
15713 let _main = engine.gpu.enter_main()?;
15714 engine.stream().synchronize()?;
15715 }
15716
15717 let (output, k_shadow, v_shadow) = if tp.runtime.bulk_p2p() {
15718 let output = tp
15719 .runtime
15720 .step_bf16_row_parallel_resident_root_device(&tp.o, &gated, tokens)?;
15721 let k_shadow =
15722 tp.runtime
15723 .gather_native_column_shards_device(&k, tokens, local_kv_dim)?;
15724 let v_shadow =
15725 tp.runtime
15726 .gather_native_column_shards_device(&v_raw, tokens, local_kv_dim)?;
15727 let root = tp
15728 .runtime
15729 .rank_engine(0)
15730 .ok_or("Step TP prefill lost its root engine")?;
15731 let _main = root.gpu.enter_main()?;
15732 root.stream().synchronize()?;
15733 (output, k_shadow, v_shadow)
15734 } else {
15735 let attention = tp.runtime.gather_native_column_shards(
15736 &gated,
15737 tokens,
15738 local_heads * head_dim,
15739 )?;
15740 let output = tp
15741 .runtime
15742 .step_bf16_row_parallel_resident_native(&tp.o, &attention, tokens)?;
15743 let k_shadow = tp
15744 .runtime
15745 .gather_native_column_shards(&k, tokens, local_kv_dim)?;
15746 let v_shadow =
15747 tp.runtime
15748 .gather_native_column_shards(&v_raw, tokens, local_kv_dim)?;
15749 (e.htod(&output)?, e.htod(&k_shadow)?, e.htod(&v_shadow)?)
15750 };
15751 let local = cache.kv[il]
15752 .as_mut()
15753 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
15754 if local.len != base_len {
15755 return Err(format!(
15756 "Step TP layer {il} local cache changed during prefill: \
15757 len={} base={base_len}",
15758 local.len
15759 )
15760 .into());
15761 }
15762 let retain_from = window
15763 .map(|window| {
15764 let staged_retain = staged_len.saturating_sub(window) & !31usize;
15765 let rollback_retain =
15766 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
15767 staged_retain.min(rollback_retain)
15768 })
15769 .unwrap_or(0);
15770 let write_row = e.prepare_kv_append(local, retain_from, tokens)?;
15771 e.append_kv_quantized_rows(
15772 &k_shadow,
15773 &v_shadow,
15774 &mut local.k,
15775 &mut local.v,
15776 write_row,
15777 tokens,
15778 local.kv_dim_k,
15779 local.kv_dim_v,
15780 local.k_tok_bytes,
15781 local.v_tok_bytes,
15782 false,
15783 )?;
15784 local.len = staged_len;
15785 e.set_i32_one(&mut local.len_d, staged_len as i32)?;
15786 Ok(output)
15787 })();
15788
15789 let output = match staged {
15790 Ok(output) => output,
15791 Err(error) => {
15792 let _ = tp.runtime.rollback_tp_kv_transaction(
15793 cache.tp_kv[il]
15794 .as_mut()
15795 .expect("distributed cache checked above"),
15796 transaction,
15797 );
15798 if let Some(local) = cache.kv[il].as_mut() {
15799 local.len = base_len;
15800 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
15801 }
15802 return Err(error);
15803 }
15804 };
15805 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
15806 cache.tp_kv[il]
15807 .as_mut()
15808 .expect("distributed cache checked above"),
15809 transaction,
15810 tokens,
15811 ) {
15812 let _ = tp.runtime.rollback_tp_kv_transaction(
15813 cache.tp_kv[il]
15814 .as_mut()
15815 .expect("distributed cache checked above"),
15816 transaction,
15817 );
15818 let local = cache.kv[il].as_mut().expect("local cache checked above");
15819 local.len = base_len;
15820 e.set_i32_one(&mut local.len_d, base_len as i32)?;
15821 return Err(error);
15822 }
15823
15824 let committed = cache.tp_kv[il]
15825 .as_ref()
15826 .expect("distributed cache checked above")
15827 .committed_len();
15828 let local_len = cache.kv[il]
15829 .as_ref()
15830 .expect("local cache checked above")
15831 .len;
15832 if committed != local_len {
15833 return Err(format!(
15834 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
15835 )
15836 .into());
15837 }
15838 eprintln!(
15839 "[step-tp-prefill-attn] execute layer={} devices={:?} tokens={tokens} \
15840 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
15841 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
15842 attention_scope={} input_path=root-device-replicated gate_tensor_parallel=false \
15843 gate_shards=host-canonical o_tensor_parallel=true local_cache_shadow=true \
15844 cache_commit=chunk transport={} native_p2p=true bulk_p2p={} \
15845 output={} performance_claim=false",
15846 tp.layer,
15847 tp.devices,
15848 hydrated,
15849 if window.is_some() {
15850 "rank-local-swa-ring"
15851 } else {
15852 "rank-local-global"
15853 },
15854 tp.runtime.transport_label(),
15855 tp.runtime.bulk_p2p(),
15856 if tp.runtime.bulk_p2p() {
15857 "root-device"
15858 } else {
15859 "root-readback"
15860 },
15861 );
15862 Ok(output)
15863 }
15864
15865 fn step35_tp_decode_attn_resident(
15866 &self,
15867 e: &Engine,
15868 fa: &FullAttnLayer,
15869 il: usize,
15870 h: &CudaSlice<f32>,
15871 pos_d: &CudaSlice<i32>,
15872 cache: &mut Cache,
15873 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15874 static ATTN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15878 static ATTN_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15879 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15880 let started = timing.then(std::time::Instant::now);
15881 let result = if crate::tp::step_tp_decode_v2_enabled()? {
15882 self.step35_tp_decode_attn_resident_v2(e, fa, il, h, pos_d, cache)
15883 } else {
15884 self.step35_tp_decode_attn_resident_inner(e, fa, il, h, pos_d, cache)
15885 };
15886 if let Some(started) = started {
15887 use std::sync::atomic::Ordering;
15888 let ns = ATTN_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
15889 + started.elapsed().as_nanos() as u64;
15890 let calls = ATTN_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
15891 if calls % 430 == 0 {
15892 eprintln!(
15893 "[step-tp-attn-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
15894 ns as f64 / 1.0e6,
15895 ns as f64 / calls as f64 / 1.0e3,
15896 );
15897 }
15898 }
15899 result
15900 }
15901
15902 #[allow(clippy::too_many_arguments)]
15903 fn step35_tp_decode_attn_resident_inner(
15904 &self,
15905 e: &Engine,
15906 fa: &FullAttnLayer,
15907 il: usize,
15908 h: &CudaSlice<f32>,
15909 pos_d: &CudaSlice<i32>,
15910 cache: &mut Cache,
15911 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15912 static T_POS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15917 static T_QKV: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15918 static T_NORMROPE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15919 static T_GATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15920 static T_APPEND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15921 static T_ATTN: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15922 static T_OPROJ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15923 static T_SHADOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15924 static T_PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
15925 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
15926 fn lap(
15927 runtime: &crate::tp::TpE4m3HostBounce,
15928 e: &Engine,
15929 timer: &std::sync::atomic::AtomicU64,
15930 started: &mut Option<std::time::Instant>,
15931 ) -> Result<(), Box<dyn std::error::Error>> {
15932 let Some(start) = started.as_mut() else {
15933 return Ok(());
15934 };
15935 for rank in 0..runtime.devices().len() {
15936 if let Some(engine) = runtime.rank_engine(rank) {
15937 let _main = engine.gpu.enter_main()?;
15938 engine.stream().synchronize()?;
15939 }
15940 }
15941 e.stream().synchronize()?;
15942 timer.fetch_add(
15943 start.elapsed().as_nanos() as u64,
15944 std::sync::atomic::Ordering::Relaxed,
15945 );
15946 *start = std::time::Instant::now();
15947 Ok(())
15948 }
15949 let tp = fa
15950 .step_tp_qkv
15951 .as_ref()
15952 .ok_or("Step TP decode lost its resident projections")?;
15953 let attention = tp
15954 .attention
15955 .as_ref()
15956 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
15957 if !tp.runtime.native_p2p() {
15958 return Err("rank-local Step attention requires native P2P".into());
15959 }
15960 if crate::Engine::kv_fp8_on() {
15961 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
15962 }
15963
15964 let geometry = self.step35_geom(il);
15965 let window = geometry.window.map(|window| window as usize);
15966 let ranks = tp.runtime.devices().len();
15967 let head_dim = geometry.head_dim_k as usize;
15968 let heads = geometry.n_head as usize;
15969 let kv_heads = geometry.n_head_kv as usize;
15970 if heads % ranks != 0 || kv_heads % ranks != 0 {
15971 return Err(format!(
15972 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
15973 )
15974 .into());
15975 }
15976 let local_heads = heads / ranks;
15977 let local_kv_heads = kv_heads / ranks;
15978 let local_kv_dim = local_kv_heads * head_dim;
15979 let max_ctx = cache.max_ctx;
15980
15981 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
15982
15983 let base_len = cache.kv[il]
15984 .as_ref()
15985 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
15986 .len;
15987 let distributed = cache.tp_kv[il]
15988 .as_ref()
15989 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
15990 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
15991 return Err(format!(
15992 "Step TP layer {il} cache lengths diverged before decode: \
15993 local={base_len} distributed={}/{}",
15994 distributed.committed_len(),
15995 distributed.staged_len()
15996 )
15997 .into());
15998 }
15999
16000 let mut lap_start = timing.then(std::time::Instant::now);
16001 let positions = e.dtoh_i32(pos_d)?;
16002 if positions.len() != 1 {
16003 return Err(format!(
16004 "rank-local Step decode requires one position, got {}",
16005 positions.len()
16006 )
16007 .into());
16008 }
16009 lap(&tp.runtime, e, &T_POS, &mut lap_start)?;
16010 let (q_raw, k_raw, v_raw, input_path) = if let Some(decode_input) =
16011 attention.decode_input.as_ref()
16012 {
16013 let mut decode_input = decode_input
16014 .lock()
16015 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
16016 e.stream().synchronize()?;
16020 tp.runtime
16021 .refresh_replicated_device_rows_from_root(&mut decode_input, h)?;
16022 let q_raw = tp
16023 .runtime
16024 .bf16_column_parallel_resident_replicated_device_shards(&tp.q, &decode_input)?;
16025 let k_raw = tp
16026 .runtime
16027 .bf16_column_parallel_resident_replicated_device_shards(&tp.k, &decode_input)?;
16028 let v_raw = tp
16029 .runtime
16030 .bf16_column_parallel_resident_replicated_device_shards(&tp.v, &decode_input)?;
16031 (q_raw, k_raw, v_raw, "root-device-replicated")
16032 } else {
16033 let activation = e.dtoh(h)?;
16034 let q_raw =
16035 tp.runtime
16036 .bf16_column_parallel_resident_device_shards(&tp.q, &activation, 1)?;
16037 let k_raw =
16038 tp.runtime
16039 .bf16_column_parallel_resident_device_shards(&tp.k, &activation, 1)?;
16040 let v_raw =
16041 tp.runtime
16042 .bf16_column_parallel_resident_device_shards(&tp.v, &activation, 1)?;
16043 (q_raw, k_raw, v_raw, "host-replicated")
16044 };
16045 lap(&tp.runtime, e, &T_QKV, &mut lap_start)?;
16046 let mut q = Vec::with_capacity(ranks);
16047 let mut k = Vec::with_capacity(ranks);
16048 for rank in 0..ranks {
16049 let engine = tp
16050 .runtime
16051 .rank_engine(rank)
16052 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16053 let _main = engine.gpu.enter_main()?;
16054 let mut q_rank = engine.uninit(local_heads * head_dim)?;
16055 engine.rms_norm(
16056 &q_raw[rank],
16057 &attention.q_norm[rank],
16058 &mut q_rank,
16059 head_dim,
16060 local_heads,
16061 self.cfg.rms_eps,
16062 )?;
16063 let mut k_rank = engine.uninit(local_kv_dim)?;
16064 engine.rms_norm(
16065 &k_raw[rank],
16066 &attention.k_norm[rank],
16067 &mut k_rank,
16068 head_dim,
16069 local_kv_heads,
16070 self.cfg.rms_eps,
16071 )?;
16072 let position = engine.htod_i32(&positions)?;
16073 let rope_freqs = if geometry.rope_factors {
16074 self.step35_aux
16075 .as_ref()
16076 .and_then(|aux| aux.rope_freqs(engine))
16077 } else {
16078 None
16079 };
16080 engine.rope_neox2(
16081 &mut q_rank,
16082 &mut k_rank,
16083 &position,
16084 head_dim,
16085 geometry.n_rot as usize,
16086 local_heads,
16087 local_kv_heads,
16088 1,
16089 geometry.rope_base,
16090 1.0,
16091 rope_freqs,
16092 )?;
16093 q.push(q_rank);
16094 k.push(k_rank);
16095 }
16096 lap(&tp.runtime, e, &T_NORMROPE, &mut lap_start)?;
16097
16098 let gate_weight = fa
16099 .attn_gate
16100 .as_ref()
16101 .ok_or("step35 layer is missing attn_gate.weight")?;
16102 let gate = e.matmul(gate_weight, h, 1)?;
16103 let gate = e.dtoh(&gate)?;
16104 if gate.len() != heads {
16105 return Err(format!("Step TP layer {il} gate output {} != {heads}", gate.len()).into());
16106 }
16107 lap(&tp.runtime, e, &T_GATE, &mut lap_start)?;
16108
16109 let transaction = cache.tp_kv[il]
16110 .as_mut()
16111 .expect("distributed cache checked above")
16112 .begin_transaction()?;
16113 if let Err(error) = tp.runtime.append_tp_kv_transaction(
16114 cache.tp_kv[il]
16115 .as_mut()
16116 .expect("distributed cache checked above"),
16117 transaction,
16118 &k,
16119 &v_raw,
16120 1,
16121 ) {
16122 let _ = tp.runtime.rollback_tp_kv_transaction(
16123 cache.tp_kv[il]
16124 .as_mut()
16125 .expect("distributed cache checked above"),
16126 transaction,
16127 );
16128 return Err(error);
16129 }
16130 lap(&tp.runtime, e, &T_APPEND, &mut lap_start)?;
16131
16132 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16133 let distributed = cache.tp_kv[il]
16134 .as_ref()
16135 .expect("distributed cache checked above");
16136 let staged_len = distributed.staged_len();
16137 let view_start = window
16138 .map(|window| staged_len.saturating_sub(window))
16139 .unwrap_or(0);
16140 let physical = distributed.physical_range(view_start, staged_len)?;
16141 let t_kv = staged_len - view_start;
16142 let mut gated = Vec::with_capacity(ranks);
16143 for rank in 0..ranks {
16144 let engine = tp
16145 .runtime
16146 .rank_engine(rank)
16147 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
16148 let _main = engine.gpu.enter_main()?;
16149 let rank_cache = distributed
16150 .rank(rank)
16151 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
16152 let k_view = engine.view_u8_range(
16153 rank_cache.k(),
16154 physical.start * distributed.k_tok_bytes(),
16155 physical.end * distributed.k_tok_bytes(),
16156 );
16157 let v_view = engine.view_u8_range(
16158 rank_cache.v(),
16159 physical.start * distributed.v_tok_bytes(),
16160 physical.end * distributed.v_tok_bytes(),
16161 );
16162 let mut attention_out = engine.uninit(local_heads * head_dim)?;
16163 engine.fa_decode_kvmod(
16164 &q[rank],
16165 &k_view,
16166 &v_view,
16167 &mut attention_out,
16168 head_dim,
16169 local_heads,
16170 local_kv_heads,
16171 t_kv,
16172 geometry.attention_scale(),
16173 distributed.k_tok_bytes(),
16174 distributed.v_tok_bytes(),
16175 false,
16176 )?;
16177 let gate_start = rank * local_heads;
16178 let gate_rank = engine.htod(&gate[gate_start..gate_start + local_heads])?;
16179 let mut gated_rank = engine.uninit(local_heads * head_dim)?;
16180 engine.attn_head_gate(
16181 &attention_out,
16182 &gate_rank,
16183 &mut gated_rank,
16184 None,
16185 head_dim,
16186 local_heads,
16187 1,
16188 )?;
16189 gated.push(gated_rank);
16190 }
16191 lap(&tp.runtime, e, &T_ATTN, &mut lap_start)?;
16192
16193 let gathered =
16194 tp.runtime
16195 .gather_native_column_shards(&gated, 1, local_heads * head_dim)?;
16196 let output = tp
16197 .runtime
16198 .step_bf16_row_parallel_resident_native(&tp.o, &gathered, 1)?;
16199 let output = e.htod(&output)?;
16200 lap(&tp.runtime, e, &T_OPROJ, &mut lap_start)?;
16201
16202 let k_shadow = tp
16203 .runtime
16204 .gather_native_column_shards(&k, 1, local_kv_dim)?;
16205 let v_shadow = tp
16206 .runtime
16207 .gather_native_column_shards(&v_raw, 1, local_kv_dim)?;
16208 let k_shadow = e.htod(&k_shadow)?;
16209 let v_shadow = e.htod(&v_shadow)?;
16210 let local = cache.kv[il]
16211 .as_mut()
16212 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
16213 if local.len != base_len || base_len + 1 > max_ctx {
16214 return Err(format!(
16215 "Step TP layer {il} local cache changed during decode: \
16216 len={} base={base_len} max={max_ctx}",
16217 local.len
16218 )
16219 .into());
16220 }
16221 let retain_from = window
16222 .map(|window| {
16223 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
16224 let rollback_retain =
16225 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
16226 staged_retain.min(rollback_retain)
16227 })
16228 .unwrap_or(0);
16229 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
16230 e.append_kv_quantized(
16231 &k_shadow,
16232 &v_shadow,
16233 &mut local.k,
16234 &mut local.v,
16235 write_row,
16236 local.kv_dim_k,
16237 local.kv_dim_v,
16238 local.k_tok_bytes,
16239 local.v_tok_bytes,
16240 false,
16241 )?;
16242 local.len = base_len + 1;
16243 e.set_i32_one(&mut local.len_d, local.len as i32)?;
16244 Ok(output)
16245 })();
16246
16247 let output = match staged {
16248 Ok(output) => output,
16249 Err(error) => {
16250 let _ = tp.runtime.rollback_tp_kv_transaction(
16251 cache.tp_kv[il]
16252 .as_mut()
16253 .expect("distributed cache checked above"),
16254 transaction,
16255 );
16256 if let Some(local) = cache.kv[il].as_mut() {
16257 local.len = base_len;
16258 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
16259 }
16260 return Err(error);
16261 }
16262 };
16263 if let Err(error) = tp.runtime.commit_tp_kv_transaction(
16264 cache.tp_kv[il]
16265 .as_mut()
16266 .expect("distributed cache checked above"),
16267 transaction,
16268 1,
16269 ) {
16270 let _ = tp.runtime.rollback_tp_kv_transaction(
16271 cache.tp_kv[il]
16272 .as_mut()
16273 .expect("distributed cache checked above"),
16274 transaction,
16275 );
16276 let local = cache.kv[il].as_mut().expect("local cache checked above");
16277 local.len = base_len;
16278 e.set_i32_one(&mut local.len_d, base_len as i32)?;
16279 return Err(error);
16280 }
16281
16282 let committed = cache.tp_kv[il]
16283 .as_ref()
16284 .expect("distributed cache checked above")
16285 .committed_len();
16286 let local_len = cache.kv[il]
16287 .as_ref()
16288 .expect("local cache checked above")
16289 .len;
16290 if committed != local_len {
16291 return Err(format!(
16292 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
16293 )
16294 .into());
16295 }
16296 lap(&tp.runtime, e, &T_SHADOW, &mut lap_start)?;
16297 if timing {
16298 use std::sync::atomic::Ordering;
16299 let calls = T_PHASE_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
16300 if calls % 430 == 0 {
16301 let avg = |t: &std::sync::atomic::AtomicU64| {
16302 t.load(Ordering::Relaxed) as f64 / calls as f64 / 1.0e3
16303 };
16304 eprintln!(
16305 "[step-tp-attn-phase] calls={calls} avg_us pos={:.1} qkv={:.1} \
16306 normrope={:.1} gate={:.1} append={:.1} attn={:.1} oproj={:.1} shadow={:.1}",
16307 avg(&T_POS),
16308 avg(&T_QKV),
16309 avg(&T_NORMROPE),
16310 avg(&T_GATE),
16311 avg(&T_APPEND),
16312 avg(&T_ATTN),
16313 avg(&T_OPROJ),
16314 avg(&T_SHADOW),
16315 );
16316 }
16317 }
16318 eprintln!(
16319 "[step-tp-attn] execute layer={} devices={:?} tokens=1 \
16320 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
16321 kv_cache_distributed=true kv_cache_hydrated={} attention_tensor_parallel=true \
16322 attention_scope={} input_path={} kv_physical_rows={} \
16323 gate_tensor_parallel=false gate_shards=host-canonical o_tensor_parallel=true \
16324 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
16325 bulk_p2p={} output=root-readback performance_claim=false",
16326 tp.layer,
16327 tp.devices,
16328 hydrated,
16329 if window.is_some() {
16330 "rank-local-swa-ring"
16331 } else {
16332 "rank-local-global"
16333 },
16334 input_path,
16335 cache.tp_kv[il]
16336 .as_ref()
16337 .expect("distributed cache checked above")
16338 .physical_capacity(),
16339 tp.runtime.transport_label(),
16340 tp.runtime.bulk_p2p(),
16341 );
16342 Ok(output)
16343 }
16344
16345 #[allow(clippy::too_many_arguments)]
16352 pub(crate) fn step35_verify_qkv_precompute(
16357 &self,
16358 e: &Engine,
16359 il: usize,
16360 h_t: &CudaSlice<f32>,
16361 t: usize,
16362 ) -> Result<bool, Box<dyn std::error::Error>> {
16363 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16364 return Ok(false);
16365 };
16366 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16367 return Ok(false);
16368 };
16369 let Some(attention) = tp.attention.as_ref() else {
16370 return Ok(false);
16371 };
16372 if !tp.runtime.native_p2p() || !crate::tp::step_tp_qkv_fused_enabled()? {
16373 return Ok(false);
16374 }
16375 let geometry = self.step35_geom(il);
16376 let heads = geometry.n_head as usize;
16377 let ws_index = tp
16378 .runtime
16379 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16380 let gate_shards = attention
16381 .gate_shards_bf16
16382 .as_deref()
16383 .map(crate::tp::StepTpGateShards::Bf16);
16384 tp.runtime.decode_v2_input_qkv_tcol(
16385 ws_index,
16386 e,
16387 h_t,
16388 t,
16389 &tp.q,
16390 &tp.k,
16391 &tp.v,
16392 gate_shards,
16393 )?;
16394 Ok(true)
16395 }
16396
16397 pub(crate) fn step35_verify_oproj_tcol(
16402 &self,
16403 e: &Engine,
16404 il: usize,
16405 t: usize,
16406 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16407 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16408 return Err("tcol o_proj join expects full attention".into());
16409 };
16410 let tp = fa
16411 .step_tp_qkv
16412 .as_ref()
16413 .ok_or("tcol o_proj join lost its resident projections")?;
16414 let heads = self.step35_geom(il).n_head as usize;
16415 let ws_index = tp
16416 .runtime
16417 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16418 tp.runtime.decode_v2_oproj_tcol(ws_index, e, &tp.o, t)
16419 }
16420
16421 pub(crate) fn step35_spec_fa2_precheck(
16428 &self,
16429 cache: &Cache,
16430 il: usize,
16431 pos0: usize,
16432 ) -> Result<bool, Box<dyn std::error::Error>> {
16433 fn nope(clause: &str, il: usize, pos0: usize) -> bool {
16436 static DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16437 static SEEN: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
16438 if *DBG.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_DEBUG").as_deref() == Ok("1")) {
16439 let mut seen = SEEN.lock().unwrap();
16440 if !seen.iter().any(|c| *c == clause) {
16441 seen.push(Box::leak(clause.to_string().into_boxed_str()));
16443 eprintln!("[spec-fa2] precheck FAIL clause={clause} il={il} pos0={pos0}");
16444 }
16445 }
16446 false
16447 }
16448 static ONLY: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
16450 if let Some(only) =
16451 ONLY.get_or_init(|| std::env::var("MEMRA_SPEC_FA2_LAYER").ok()?.parse().ok())
16452 {
16453 if *only != il {
16454 return Ok(false);
16455 }
16456 }
16457 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16458 return Ok(nope("mixer", il, pos0));
16459 };
16460 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16461 return Ok(nope("step_tp", il, pos0));
16462 };
16463 let Some(attention) = tp.attention.as_ref() else {
16464 return Ok(nope("attention", il, pos0));
16465 };
16466 if !tp.runtime.native_p2p()
16467 || crate::Engine::kv_fp8_on()
16468 || !crate::tp::step_tp_dcw_enabled()?
16469 || !crate::tp::step_tp_qkv_fused_enabled()?
16470 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16471 {
16472 return Ok(nope("runtime-doors", il, pos0));
16473 }
16474 let geometry = self.step35_geom(il);
16475 let head_dim = geometry.head_dim_k as usize;
16476 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16477 return Ok(nope("fa-class", il, pos0));
16478 }
16479 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16480 return Ok(nope("tp-kv", il, pos0));
16481 };
16482 if distributed.staged_len() != pos0 {
16483 return Ok(nope("staged-len", il, pos0));
16484 }
16485 let (_, would_rebase) = distributed.peek_append_ring(2)?;
16488 if would_rebase {
16489 return Ok(nope("rebase", il, pos0));
16490 }
16491 let window = geometry.window.map(|w| w as usize);
16492 if let Some(w) = window {
16500 if pos0 + 2 > w {
16501 return Ok(nope("swa-capped", il, pos0));
16502 }
16503 }
16504 let (t0, t1) = (pos0 + 1, pos0 + 2);
16508 if t0 < 96 {
16509 return Ok(nope("dcw-floor", il, pos0));
16510 }
16511 if std::env::var("MEMRA_NO_FA_VEC").is_ok() || t0 < crate::fa_vec_min_tkv() {
16512 return Ok(nope("vec-floor", il, pos0));
16513 }
16514 let ranks = tp.runtime.devices().len();
16520 let local_kv_heads = (geometry.n_head_kv as usize / ranks).max(1);
16521 let sp0 = crate::fa_split_keys_pub(t0, local_kv_heads);
16522 let sp1 = crate::fa_split_keys_pub(t1, local_kv_heads);
16523 if sp0 != sp1 {
16524 return Ok(nope("partition-sp", il, pos0));
16525 }
16526 let (ns0, ns1) = (t0.div_ceil(sp0), t1.div_ceil(sp1));
16527 if ns0 != ns1 {
16528 return Ok(nope("partition-ns", il, pos0));
16529 }
16530 if t0.div_ceil(ns0) != t1.div_ceil(ns1) {
16531 return Ok(nope("partition-per", il, pos0));
16532 }
16533 Ok(true)
16534 }
16535
16536 pub(crate) fn step35_fa_rows_precheck(
16541 &self,
16542 cache: &Cache,
16543 il: usize,
16544 pos0: usize,
16545 t: usize,
16546 ) -> Result<bool, Box<dyn std::error::Error>> {
16547 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16548 return Ok(false);
16549 };
16550 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16551 return Ok(false);
16552 };
16553 let Some(attention) = tp.attention.as_ref() else {
16554 return Ok(false);
16555 };
16556 if !tp.runtime.native_p2p()
16557 || crate::Engine::kv_fp8_on()
16558 || !crate::tp::step_tp_dcw_enabled()?
16559 || !crate::tp::step_tp_qkv_fused_enabled()?
16560 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16561 {
16562 return Ok(false);
16563 }
16564 let geometry = self.step35_geom(il);
16565 let head_dim = geometry.head_dim_k as usize;
16566 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16567 return Ok(false);
16568 }
16569 if crate::fa_sm_count() < 128
16570 || std::env::var("MEMRA_FA_SPLIT").is_ok()
16571 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
16572 || std::env::var("MEMRA_FA_SP16").is_ok()
16573 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
16574 {
16575 return Ok(false);
16576 }
16577 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16578 return Ok(false);
16579 };
16580 if distributed.staged_len() != pos0 {
16581 return Ok(false);
16582 }
16583 let (_, would_rebase) = distributed.peek_append_ring(t)?;
16584 if would_rebase {
16585 return Ok(false);
16586 }
16587 let window = geometry.window.map(|w| w as usize);
16590 let t0 = window.map(|w| (pos0 + 1).min(w)).unwrap_or(pos0 + 1);
16591 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
16592 return Ok(false);
16593 }
16594 Ok(true)
16595 }
16596
16597 pub(crate) fn step35_verify_fa_rows_join(
16601 &self,
16602 e: &Engine,
16603 il: usize,
16604 cache: &Cache,
16605 pos0: usize,
16606 t: usize,
16607 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16608 use cudarc::driver::DevicePtr;
16609 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16610 return Err("fa rows join expects full attention".into());
16611 };
16612 let tp = fa
16613 .step_tp_qkv
16614 .as_ref()
16615 .ok_or("fa rows join lost its resident projections")?;
16616 let geometry = self.step35_geom(il);
16617 let heads = geometry.n_head as usize;
16618 let head_dim = geometry.head_dim_k as usize;
16619 let window = geometry.window.map(|w| w as usize);
16620 let distributed = cache.tp_kv[il]
16621 .as_ref()
16622 .ok_or("fa rows join lost its distributed KV cache")?;
16623 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
16624 let ladder = |t_kv: usize| -> usize {
16626 if t_kv <= 2048 {
16627 16
16628 } else if t_kv <= 16384 {
16629 64
16630 } else {
16631 128
16632 }
16633 };
16634 let mut max_ns = 1usize;
16635 for r in 0..t {
16636 let t_kv = window
16637 .map(|w| (pos0 + r + 1).min(w))
16638 .unwrap_or(pos0 + r + 1);
16639 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
16640 }
16641 static TABS: std::sync::Mutex<
16644 Option<std::collections::HashMap<(usize, usize, u64, usize), CudaSlice<u64>>>,
16645 > = std::sync::Mutex::new(None);
16646 let ranks = tp.runtime.devices().len();
16647 let mut keys = Vec::with_capacity(ranks);
16648 let mut guard = TABS.lock().map_err(|_| "fa rows table lock is poisoned")?;
16649 {
16650 let map = guard.get_or_insert_with(Default::default);
16651 for rank in 0..ranks {
16652 let engine = tp
16653 .runtime
16654 .rank_engine(rank)
16655 .ok_or("fa rows join lost a rank engine")?;
16656 let rank_cache = distributed
16657 .rank(rank)
16658 .ok_or("fa rows join lost a KV cache rank")?;
16659 let _main = engine.gpu.enter_main()?;
16660 let s = engine.stream();
16661 let (kp, _g0) = rank_cache.k().device_ptr(&s);
16662 let (vp, _g1) = rank_cache.v().device_ptr(&s);
16663 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
16664 let bp = match rank_cache.base_d() {
16665 Some(b) => {
16666 let (p, _g) = b.device_ptr(&s);
16667 p as u64
16668 }
16669 None => 0u64,
16670 };
16671 let key = (il, rank, (kp as u64) ^ bp.rotate_left(32), t);
16674 if !map.contains_key(&key) {
16675 let mut host = Vec::with_capacity(t * 6);
16676 for r in 0..t {
16677 host.extend_from_slice(&[
16678 kp as u64,
16679 vp as u64,
16680 lp as u64,
16681 bp,
16682 0u64,
16683 (t - 1 - r) as u64,
16684 ]);
16685 }
16686 map.insert(key, engine.stream().clone_htod(&host)?);
16687 }
16688 keys.push(key);
16689 }
16690 }
16691 let map = guard.as_ref().expect("armed above");
16692 let tabs: Vec<&CudaSlice<u64>> = keys
16693 .iter()
16694 .map(|k| map.get(k).expect("inserted above"))
16695 .collect();
16696 let ws_index = tp
16697 .runtime
16698 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16699 tp.runtime.decode_v2_fa_rows_join(
16700 ws_index,
16701 e,
16702 &tp.o,
16703 &tabs,
16704 t,
16705 head_dim,
16706 window.unwrap_or(0),
16707 max_ns,
16708 geometry.attention_scale(),
16709 k_tok_bytes,
16710 v_tok_bytes,
16711 )
16712 }
16713
16714 pub(crate) fn step35_batch_fa_rows_precheck(
16718 &self,
16719 caches: &[&mut Cache],
16720 row_to_cache: impl Fn(usize) -> usize,
16721 positions: &[i32],
16722 il: usize,
16723 ) -> Result<bool, Box<dyn std::error::Error>> {
16724 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16725 return Ok(false);
16726 };
16727 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16728 return Ok(false);
16729 };
16730 let Some(attention) = tp.attention.as_ref() else {
16731 return Ok(false);
16732 };
16733 if !tp.runtime.native_p2p()
16734 || crate::Engine::kv_fp8_on()
16735 || !crate::tp::step_tp_dcw_enabled()?
16736 || !crate::tp::step_tp_qkv_fused_enabled()?
16737 || (attention.gate_shards.is_none() && attention.gate_shards_bf16.is_none())
16738 {
16739 return Ok(false);
16740 }
16741 let geometry = self.step35_geom(il);
16742 let head_dim = geometry.head_dim_k as usize;
16743 if head_dim > 256 || head_dim % 32 != 0 || !crate::fa_v3_on() {
16744 return Ok(false);
16745 }
16746 if crate::fa_sm_count() < 128
16747 || std::env::var("MEMRA_FA_SPLIT").is_ok()
16748 || std::env::var("MEMRA_FA_SP_SHORT").is_ok()
16749 || std::env::var("MEMRA_FA_SP16").is_ok()
16750 || std::env::var("MEMRA_NO_FA_VEC").is_ok()
16751 {
16752 return Ok(false);
16753 }
16754 let window = geometry.window.map(|w| w as usize);
16755 for (r, &pos) in positions.iter().enumerate() {
16756 let cache = &caches[row_to_cache(r)];
16757 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16758 return Ok(false);
16759 };
16760 if distributed.staged_len() != pos as usize {
16761 return Ok(false);
16762 }
16763 if distributed.peek_append_ring(1)?.1 {
16764 return Ok(false);
16765 }
16766 let t0 = window
16767 .map(|w| (pos as usize + 1).min(w))
16768 .unwrap_or(pos as usize + 1);
16769 if t0 < 96 || t0 < crate::fa_vec_min_tkv() {
16770 return Ok(false);
16771 }
16772 }
16773 Ok(true)
16774 }
16775
16776 pub(crate) fn step35_verify_rope_fa_pass(
16782 &self,
16783 e: &Engine,
16784 il: usize,
16785 cache: &Cache,
16786 pos0: usize,
16787 t: usize,
16788 stage_pos: bool,
16789 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16790 use cudarc::driver::DevicePtr;
16791 if !crate::tp::fuse_rope_append_on() {
16792 return Ok(None);
16793 }
16794 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16795 return Ok(None);
16796 };
16797 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16798 return Ok(None);
16799 };
16800 let Some(attention) = tp.attention.as_ref() else {
16801 return Ok(None);
16802 };
16803 let geometry = self.step35_geom(il);
16804 let head_dim = geometry.head_dim_k as usize;
16805 if head_dim != 128 {
16806 return Ok(None);
16807 }
16808 let heads = geometry.n_head as usize;
16809 let window = geometry.window.map(|w| w as usize);
16810 let ranks = tp.runtime.devices().len();
16811 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16812 return Ok(None);
16813 };
16814 if distributed.kv_dim_k() != distributed.kv_dim_v() {
16815 return Ok(None);
16816 }
16817 {
16818 let rank0 = distributed.rank(0).ok_or("verify rope pass lost rank 0")?;
16819 if rank0.base_d().is_none()
16820 && distributed.staged_len() + t > distributed.physical_capacity()
16821 {
16822 return Ok(None);
16823 }
16824 }
16825 let mut rope_freqs = Vec::with_capacity(ranks);
16826 for rank in 0..ranks {
16827 let engine = tp
16828 .runtime
16829 .rank_engine(rank)
16830 .ok_or("verify rope pass lost a rank engine")?;
16831 rope_freqs.push(if geometry.rope_factors {
16832 match self
16833 .step35_aux
16834 .as_ref()
16835 .and_then(|aux| aux.rope_freqs(engine))
16836 {
16837 Some(f) => Some(f),
16838 None => return Ok(None),
16839 }
16840 } else {
16841 None
16842 });
16843 }
16844 let ladder = |t_kv: usize| -> usize {
16845 if t_kv <= 2048 {
16846 16
16847 } else if t_kv <= 16384 {
16848 64
16849 } else {
16850 128
16851 }
16852 };
16853 let (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
16854 let mut max_ns = 1usize;
16855 let mut positions = Vec::with_capacity(t);
16856 for r in 0..t {
16857 positions.push((pos0 + r) as i32);
16858 let t_kv = window
16859 .map(|w| (pos0 + r + 1).min(w))
16860 .unwrap_or(pos0 + r + 1);
16861 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
16862 }
16863 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
16864 let mut tab_keys = vec![0u64; ranks];
16865 for rank in 0..ranks {
16866 let engine = tp
16867 .runtime
16868 .rank_engine(rank)
16869 .ok_or("verify rope pass lost a rank engine")?;
16870 let rank_cache = distributed
16871 .rank(rank)
16872 .ok_or("verify rope pass lost a KV cache rank")?;
16873 let _main = engine.gpu.enter_main()?;
16874 let s = engine.stream();
16875 let (kp, _g0) = rank_cache.k().device_ptr(&s);
16876 let (vp, _g1) = rank_cache.v().device_ptr(&s);
16877 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
16878 let bp = match rank_cache.base_d() {
16879 Some(b) => {
16880 let (p, _g) = b.device_ptr(&s);
16881 p as u64
16882 }
16883 None => 0u64,
16884 };
16885 tab_keys[rank] = (kp as u64)
16886 .rotate_left(17)
16887 .wrapping_add(bp)
16888 .wrapping_add((il as u64) << 32)
16889 .wrapping_add(t as u64)
16890 .wrapping_add(1 << 63);
16891 for _r in 0..t {
16892 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
16893 }
16894 }
16895 let ws_index = tp
16896 .runtime
16897 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
16898 tp.runtime
16899 .decode_v2_rope_fa_rows(
16900 ws_index,
16901 e,
16902 &tp.o,
16903 &session_parts,
16904 &tab_keys,
16905 &positions,
16906 stage_pos,
16907 true,
16908 &attention.q_norm,
16909 &attention.k_norm,
16910 &rope_freqs,
16911 t,
16912 head_dim,
16913 geometry.n_rot as usize,
16914 window.unwrap_or(0),
16915 max_ns,
16916 geometry.attention_scale(),
16917 k_tok_bytes,
16918 v_tok_bytes,
16919 self.cfg.rms_eps,
16920 geometry.rope_base,
16921 )
16922 .map(Some)
16923 }
16924
16925 #[allow(clippy::too_many_arguments)]
16930 pub(crate) fn step35_batch_rope_fa_pass(
16931 &self,
16932 e: &Engine,
16933 il: usize,
16934 caches: &[&mut Cache],
16935 row_to_cache: impl Fn(usize) -> usize,
16936 positions: &[i32],
16937 t: usize,
16938 stage_pos: bool,
16939 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16940 use cudarc::driver::DevicePtr;
16941 if !crate::tp::fuse_rope_append_on() {
16942 return Ok(None);
16943 }
16944 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
16945 return Ok(None);
16946 };
16947 let Some(tp) = fa.step_tp_qkv.as_ref() else {
16948 return Ok(None);
16949 };
16950 let Some(attention) = tp.attention.as_ref() else {
16951 return Ok(None);
16952 };
16953 let geometry = self.step35_geom(il);
16954 let head_dim = geometry.head_dim_k as usize;
16955 if head_dim != 128 {
16956 return Ok(None);
16957 }
16958 let heads = geometry.n_head as usize;
16959 let window = geometry.window.map(|w| w as usize);
16960 let ranks = tp.runtime.devices().len();
16961 for r in 0..t {
16964 let cache = &caches[row_to_cache(r)];
16965 let Some(distributed) = cache.tp_kv[il].as_ref() else {
16966 return Ok(None);
16967 };
16968 if distributed.kv_dim_k() != distributed.kv_dim_v() {
16969 return Ok(None);
16970 }
16971 let rank0 = distributed.rank(0).ok_or("rope fa pass lost rank 0")?;
16972 if rank0.base_d().is_none()
16973 && distributed.staged_len() + t > distributed.physical_capacity()
16974 {
16975 return Ok(None);
16976 }
16977 }
16978 let mut rope_freqs = Vec::with_capacity(ranks);
16979 for rank in 0..ranks {
16980 let engine = tp
16981 .runtime
16982 .rank_engine(rank)
16983 .ok_or("rope fa pass lost a rank engine")?;
16984 rope_freqs.push(if geometry.rope_factors {
16985 match self
16986 .step35_aux
16987 .as_ref()
16988 .and_then(|aux| aux.rope_freqs(engine))
16989 {
16990 Some(f) => Some(f),
16991 None => return Ok(None),
16992 }
16993 } else {
16994 None
16995 });
16996 }
16997 let ladder = |t_kv: usize| -> usize {
16998 if t_kv <= 2048 {
16999 16
17000 } else if t_kv <= 16384 {
17001 64
17002 } else {
17003 128
17004 }
17005 };
17006 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17007 let mut session_parts: Vec<Vec<[u64; 4]>> = vec![Vec::with_capacity(t); ranks];
17008 let mut tab_keys = vec![0u64; ranks];
17009 for (r, &pos) in positions.iter().enumerate().take(t) {
17010 let cache = &caches[row_to_cache(r)];
17011 let distributed = cache.tp_kv[il]
17012 .as_ref()
17013 .ok_or("rope fa pass lost a distributed KV cache")?;
17014 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17015 let t_kv = window
17016 .map(|w| (pos as usize + 1).min(w))
17017 .unwrap_or(pos as usize + 1);
17018 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17019 for rank in 0..ranks {
17020 let engine = tp
17021 .runtime
17022 .rank_engine(rank)
17023 .ok_or("rope fa pass lost a rank engine")?;
17024 let rank_cache = distributed
17025 .rank(rank)
17026 .ok_or("rope fa pass lost a KV cache rank")?;
17027 let _main = engine.gpu.enter_main()?;
17028 let s = engine.stream();
17029 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17030 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17031 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17032 let bp = match rank_cache.base_d() {
17033 Some(b) => {
17034 let (p, _g) = b.device_ptr(&s);
17035 p as u64
17036 }
17037 None => 0u64,
17038 };
17039 tab_keys[rank] = tab_keys[rank]
17040 .rotate_left(9)
17041 .wrapping_add(kp as u64)
17042 .wrapping_add(bp)
17043 .wrapping_add(il as u64);
17044 session_parts[rank].push([kp as u64, vp as u64, lp as u64, bp]);
17045 }
17046 }
17047 let ws_index = tp
17048 .runtime
17049 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17050 tp.runtime
17051 .decode_v2_rope_fa_rows(
17052 ws_index,
17053 e,
17054 &tp.o,
17055 &session_parts,
17056 &tab_keys,
17057 positions,
17058 stage_pos,
17059 false,
17060 &attention.q_norm,
17061 &attention.k_norm,
17062 &rope_freqs,
17063 t,
17064 head_dim,
17065 geometry.n_rot as usize,
17066 window.unwrap_or(0),
17067 max_ns,
17068 geometry.attention_scale(),
17069 k_tok_bytes,
17070 v_tok_bytes,
17071 self.cfg.rms_eps,
17072 geometry.rope_base,
17073 )
17074 .map(Some)
17075 }
17076
17077 #[allow(clippy::too_many_arguments)]
17081 pub(crate) fn step35_batch_fa_rows_join(
17082 &self,
17083 e: &Engine,
17084 il: usize,
17085 caches: &[&mut Cache],
17086 row_to_cache: impl Fn(usize) -> usize,
17087 positions: &[i32],
17088 t: usize,
17089 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17090 use cudarc::driver::DevicePtr;
17091 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17092 return Err("batch fa rows join expects full attention".into());
17093 };
17094 let tp = fa
17095 .step_tp_qkv
17096 .as_ref()
17097 .ok_or("batch fa rows join lost its resident projections")?;
17098 let geometry = self.step35_geom(il);
17099 let heads = geometry.n_head as usize;
17100 let head_dim = geometry.head_dim_k as usize;
17101 let window = geometry.window.map(|w| w as usize);
17102 let ladder = |t_kv: usize| -> usize {
17103 if t_kv <= 2048 {
17104 16
17105 } else if t_kv <= 16384 {
17106 64
17107 } else {
17108 128
17109 }
17110 };
17111 let (mut max_ns, mut k_tok_bytes, mut v_tok_bytes) = (1usize, 0usize, 0usize);
17112 for (r, &pos) in positions.iter().enumerate() {
17113 let cache = &caches[row_to_cache(r)];
17114 let distributed = cache.tp_kv[il]
17115 .as_ref()
17116 .ok_or("batch fa rows join lost a distributed KV cache")?;
17117 (k_tok_bytes, v_tok_bytes) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
17118 let t_kv = window
17119 .map(|w| (pos as usize + 1).min(w))
17120 .unwrap_or(pos as usize + 1);
17121 max_ns = max_ns.max(t_kv.div_ceil(ladder(t_kv)));
17122 }
17123 static TABS: std::sync::Mutex<
17124 Option<std::collections::HashMap<(usize, usize, u64, usize), CudaSlice<u64>>>,
17125 > = std::sync::Mutex::new(None);
17126 let ranks = tp.runtime.devices().len();
17127 let mut keys = Vec::with_capacity(ranks);
17128 let mut guard = TABS
17129 .lock()
17130 .map_err(|_| "batch fa rows table lock is poisoned")?;
17131 {
17132 let map = guard.get_or_insert_with(Default::default);
17133 for rank in 0..ranks {
17134 let engine = tp
17135 .runtime
17136 .rank_engine(rank)
17137 .ok_or("batch fa rows join lost a rank engine")?;
17138 let _main = engine.gpu.enter_main()?;
17139 let s = engine.stream();
17140 let mut host = Vec::with_capacity(t * 6);
17141 let mut sig = 0u64;
17142 for r in 0..t {
17143 let cache = &caches[row_to_cache(r)];
17144 let distributed = cache.tp_kv[il]
17145 .as_ref()
17146 .ok_or("batch fa rows join lost a distributed KV cache")?;
17147 let rank_cache = distributed
17148 .rank(rank)
17149 .ok_or("batch fa rows join lost a KV cache rank")?;
17150 let (kp, _g0) = rank_cache.k().device_ptr(&s);
17151 let (vp, _g1) = rank_cache.v().device_ptr(&s);
17152 let (lp, _g2) = rank_cache.len_d().device_ptr(&s);
17153 let bp = match rank_cache.base_d() {
17154 Some(b) => {
17155 let (p, _g) = b.device_ptr(&s);
17156 p as u64
17157 }
17158 None => 0u64,
17159 };
17160 sig = sig.rotate_left(7).wrapping_add(kp as u64).wrapping_add(bp);
17161 host.extend_from_slice(&[kp as u64, vp as u64, lp as u64, bp, 0u64, 0u64]);
17162 }
17163 let key = (il, rank, sig, t);
17164 if !map.contains_key(&key) {
17165 map.insert(key, engine.stream().clone_htod(&host)?);
17166 }
17167 keys.push(key);
17168 }
17169 }
17170 let map = guard.as_ref().expect("armed above");
17171 let tabs: Vec<&CudaSlice<u64>> = keys
17172 .iter()
17173 .map(|k| map.get(k).expect("inserted above"))
17174 .collect();
17175 let ws_index = tp
17176 .runtime
17177 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17178 tp.runtime.decode_v2_fa_rows_join(
17179 ws_index,
17180 e,
17181 &tp.o,
17182 &tabs,
17183 t,
17184 head_dim,
17185 window.unwrap_or(0),
17186 max_ns,
17187 geometry.attention_scale(),
17188 k_tok_bytes,
17189 v_tok_bytes,
17190 )
17191 }
17192
17193 pub(crate) fn step35_verify_spec_fa2_join(
17197 &self,
17198 e: &Engine,
17199 il: usize,
17200 cache: &Cache,
17201 pos0: usize,
17202 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17203 let crate::hybrid::Mixer::Full(fa) = &self.layers[il].mixer else {
17204 return Err("spec fa2 join expects full attention".into());
17205 };
17206 let tp = fa
17207 .step_tp_qkv
17208 .as_ref()
17209 .ok_or("spec fa2 join lost its resident projections")?;
17210 let geometry = self.step35_geom(il);
17211 let heads = geometry.n_head as usize;
17212 let head_dim = geometry.head_dim_k as usize;
17213 let window = geometry.window.map(|w| w as usize);
17214 let bucket = window.map(|w| (pos0 + 2).min(w)).unwrap_or(pos0 + 2);
17217 let distributed = cache.tp_kv[il]
17218 .as_ref()
17219 .ok_or("spec fa2 join lost its distributed KV cache")?;
17220 let ws_index = tp
17221 .runtime
17222 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17223 tp.runtime.decode_v2_spec_fa2_join(
17224 ws_index,
17225 e,
17226 &tp.o,
17227 distributed,
17228 head_dim,
17229 window.unwrap_or(0),
17230 bucket,
17231 geometry.attention_scale(),
17232 )
17233 }
17234
17235 pub(crate) fn step35_verify_moe_tn(
17241 &self,
17242 e: &Engine,
17243 il: usize,
17244 z_t: &CudaSlice<f32>,
17245 t: usize,
17246 ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
17247 let layer = &self.layers[il];
17248 let crate::hybrid::Ffn::Moe(m) = &layer.ffn else {
17249 return Ok(None);
17250 };
17251 let Some(tp) = m.step_tp.as_ref() else {
17252 return Ok(None);
17253 };
17254 let crate::hybrid::StepTpExpertBank::Nvfp4(bank) = &tp.experts else {
17255 return Ok(None);
17256 };
17257 if !crate::tp::step_nvfp4_dev_routes_enabled()?
17258 || !crate::tp::step_tp_dev_router_enabled()?
17259 || !crate::tp::nvfp4_bank_v2_on()
17260 || bank.ep2
17261 {
17262 return Ok(None);
17263 }
17264 let cfg = &self.cfg;
17265 let Some(moe) = cfg.moe.as_ref() else {
17266 return Ok(None);
17267 };
17268 let Some((sf, route_norm)) = cfg.sigmoid_router() else {
17269 return Ok(None);
17270 };
17271 let n_embd = cfg.n_embd as usize;
17272 let n_expert = moe.expert_count as usize;
17273 let n_used = moe.expert_used_count as usize;
17274 if t < 2 || t > 32 || z_t.len() < t * n_embd {
17275 return Err("verify moe t-row geometry".into());
17276 }
17277 let trace = std::env::var("MEMRA_TN_TRACE").as_deref() == Ok("1");
17278 if trace {
17279 eprintln!("[tn-trace] il={il} t={t} logits");
17280 }
17281 let logits = Self::moe_router_logits(e, m, z_t, t, cfg)?;
17282 static SELW2: std::sync::Mutex<Option<(usize, CudaSlice<i32>, CudaSlice<f32>)>> =
17285 std::sync::Mutex::new(None);
17286 let mut selw = SELW2.lock().map_err(|_| "selw2 lock poisoned")?;
17287 if selw.as_ref().is_none_or(|(d, ..)| *d != e.ctx().ordinal()) {
17288 *selw = Some((
17289 e.ctx().ordinal(),
17290 e.htod_i32(&vec![0i32; 32 * n_used])?,
17291 e.htod(&vec![0.0f32; 32 * n_used])?,
17292 ));
17293 }
17294 let (_, sel_d, w_d) = selw.as_mut().expect("armed above");
17295 if trace {
17296 eprintln!("[tn-trace] il={il} topk logits_len={}", logits.len());
17297 }
17298 e.moe_router_sigmoid_topk_into(
17299 &logits,
17300 t,
17301 n_expert,
17302 n_used,
17303 m.active_count(),
17304 &m.exp_probs_b_dev,
17305 &m.active_experts_dev,
17306 sf,
17307 route_norm,
17308 sel_d,
17309 w_d,
17310 )?;
17311 if trace {
17312 eprintln!("[tn-trace] il={il} driver");
17313 }
17314 let mut out_t = tp
17315 .runtime
17316 .run_tensor_parallel_routes_nvfp4_device_routed_tn(
17317 bank,
17318 e,
17319 z_t,
17320 sel_d,
17321 w_d,
17322 t,
17323 n_used,
17324 tp.activation_limit,
17325 )?;
17326 if trace {
17327 eprintln!("[tn-trace] il={il} shexp out_t={}", out_t.len());
17328 }
17329 if !Self::step35_shexp_rows(e, m, z_t, t, cfg, il as u16, &mut out_t)? {
17333 let mut z_row = e.uninit(n_embd)?;
17334 let mut out_row = e.uninit(n_embd)?;
17335 for c in 0..t {
17336 e.dtod_copy_view(&z_t.slice(c * n_embd..(c + 1) * n_embd), &mut z_row)?;
17337 e.dtod_copy_view(&out_t.slice(c * n_embd..(c + 1) * n_embd), &mut out_row)?;
17338 Self::moe_ffn_grouped_add_shared(e, m, &z_row, 1, cfg, il as u16, &mut out_row)?;
17339 e.dtod_copy_into(&out_row, &mut out_t, c * n_embd)?;
17340 }
17341 }
17342 Ok(Some(out_t))
17343 }
17344
17345 fn step35_shexp_rows(
17349 e: &Engine,
17350 m: &MoeWeights,
17351 z_t: &CudaSlice<f32>,
17352 t: usize,
17353 cfg: &ModelConfig,
17354 il: u16,
17355 out_t: &mut CudaSlice<f32>,
17356 ) -> Result<bool, Box<dyn std::error::Error>> {
17357 let n_embd = cfg.n_embd as usize;
17358 let (Some(gate_shexp), Some(up_shexp), Some(down_shexp)) =
17359 (&m.gate_shexp, &m.up_shexp, &m.down_shexp)
17360 else {
17361 return Ok(false);
17362 };
17363 if !crate::Engine::bf16_mmv_on() || n_embd % 8 != 0 || cfg.m3.is_some() {
17364 return Ok(false);
17365 }
17366 let (
17367 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
17368 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
17369 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
17370 ) = (gate_shexp, up_shexp, down_shexp)
17371 else {
17372 return Ok(false);
17373 };
17374 let n_ff_sh = gate_shexp.out_features();
17375 let lim = cfg.clamp_shexp_at(il as u32);
17376 static WS: std::sync::Mutex<Option<(usize, usize, usize, CudaSlice<f32>, CudaSlice<f32>)>> =
17378 std::sync::Mutex::new(None);
17379 let mut guard = WS.lock().map_err(|_| "shexp rows ws lock is poisoned")?;
17380 let pins = (e.ctx().ordinal(), n_embd, n_ff_sh);
17381 if guard
17382 .as_ref()
17383 .is_none_or(|(d, ne, nf, ..)| (*d, *ne, *nf) != pins)
17384 {
17385 *guard = Some((
17386 pins.0,
17387 pins.1,
17388 pins.2,
17389 e.uninit(32 * n_ff_sh)?,
17390 e.uninit(32 * n_embd)?,
17391 ));
17392 }
17393 let (_, _, _, act_t, sh_t) = guard.as_mut().expect("armed above");
17394 e.matvec_bf16_dual_silu_rows_into(wg, wu, z_t, act_t, n_embd, n_ff_sh, lim, t)?;
17395 e.matvec_bf16_rows_into(wd, act_t, sh_t, n_ff_sh, n_embd, t)?;
17396 let gate = match &m.gate_inp_shexp {
17399 Some(gate_inp_shexp) => {
17400 e.sigmoid_dot_rows(z_t, gate_inp_shexp.float_data(), n_embd, t)?
17401 }
17402 None => e.htod(&vec![1.0f32; t])?,
17403 };
17404 e.add_scaled_rows(sh_t, &gate, out_t, n_embd, t)?;
17405 Ok(true)
17406 }
17407
17408 fn step35_tp_decode_attn_resident_v2(
17409 &self,
17410 e: &Engine,
17411 fa: &FullAttnLayer,
17412 il: usize,
17413 h: &CudaSlice<f32>,
17414 pos_d: &CudaSlice<i32>,
17415 cache: &mut Cache,
17416 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17417 let tp = fa
17418 .step_tp_qkv
17419 .as_ref()
17420 .ok_or("Step TP decode lost its resident projections")?;
17421 let attention = tp
17422 .attention
17423 .as_ref()
17424 .ok_or("Step TP decode lost its resident attention auxiliaries")?;
17425 if !tp.runtime.native_p2p() {
17426 return Err("rank-local Step attention requires native P2P".into());
17427 }
17428 if crate::Engine::kv_fp8_on() {
17429 return Err("rank-local Step attention has not qualified the FP8 KV cache".into());
17430 }
17431
17432 let geometry = self.step35_geom(il);
17433 let window = geometry.window.map(|window| window as usize);
17434 let ranks = tp.runtime.devices().len();
17435 let head_dim = geometry.head_dim_k as usize;
17436 let heads = geometry.n_head as usize;
17437 let kv_heads = geometry.n_head_kv as usize;
17438 if heads % ranks != 0 || kv_heads % ranks != 0 {
17439 return Err(format!(
17440 "Step attention heads q={heads} kv={kv_heads} are not divisible by TP={ranks}"
17441 )
17442 .into());
17443 }
17444 let local_heads = heads / ranks;
17445 let local_kv_heads = kv_heads / ranks;
17446 let max_ctx = cache.max_ctx;
17447
17448 let hydrated = self.ensure_step_tp_kv_cache(e, fa, il, cache)?;
17449
17450 let base_len = cache.kv[il]
17451 .as_ref()
17452 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?
17453 .len;
17454 {
17455 let distributed = cache.tp_kv[il]
17456 .as_ref()
17457 .ok_or_else(|| format!("Step TP layer {il} lost its distributed KV cache"))?;
17458 if distributed.committed_len() != base_len || distributed.staged_len() != base_len {
17459 return Err(format!(
17460 "Step TP layer {il} cache lengths diverged before decode: \
17461 local={base_len} distributed={}/{}",
17462 distributed.committed_len(),
17463 distributed.staged_len()
17464 )
17465 .into());
17466 }
17467 }
17468 if pos_d.len() != 1 {
17469 return Err(format!(
17470 "rank-local Step decode requires one position, got {}",
17471 pos_d.len()
17472 )
17473 .into());
17474 }
17475
17476 let decode_input = attention
17477 .decode_input
17478 .as_ref()
17479 .ok_or("Step TP decode v2 requires the replicated decode input")?;
17480 let mut decode_input = decode_input
17481 .lock()
17482 .map_err(|_| "Step TP replicated decode input lock is poisoned")?;
17483
17484 let use_gate_shards = (attention.gate_shards.is_some()
17489 || attention.gate_shards_bf16.is_some())
17490 && crate::tp::step_tp_qkv_fused_enabled()?;
17491 let gate_raw = if use_gate_shards {
17492 None
17493 } else {
17494 let gate_weight = fa
17495 .attn_gate
17496 .as_ref()
17497 .ok_or("step35 layer is missing attn_gate.weight")?;
17498 let gate_raw = e.matmul(gate_weight, h, 1)?;
17499 if gate_raw.len() != heads {
17500 return Err(format!(
17501 "Step TP layer {il} gate output {} != {heads}",
17502 gate_raw.len()
17503 )
17504 .into());
17505 }
17506 Some(gate_raw)
17507 };
17508
17509 let ws_index = tp
17510 .runtime
17511 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
17512 let mut ws_guard = tp
17513 .runtime
17514 .decode_v2_workspace()
17515 .lock()
17516 .map_err(|_| "Step TP decode v2 workspace lock is poisoned")?;
17517 let ws = ws_guard
17518 .get_mut(ws_index)
17519 .ok_or("Step TP decode v2 workspace missing after ensure")?;
17520
17521 let mut rope_freqs = Vec::with_capacity(ranks);
17522 for rank in 0..ranks {
17523 let engine = tp
17524 .runtime
17525 .rank_engine(rank)
17526 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
17527 rope_freqs.push(if geometry.rope_factors {
17528 self.step35_aux
17529 .as_ref()
17530 .and_then(|aux| aux.rope_freqs(engine))
17531 } else {
17532 None
17533 });
17534 }
17535 let staged_next = base_len + 1;
17542 let t_kv_eff = window
17543 .map(|window| staged_next.min(window))
17544 .unwrap_or(staged_next);
17545 let dcw = crate::tp::step_tp_dcw_enabled()? && use_gate_shards && t_kv_eff >= 96 && {
17546 let (write_row, would_rebase) = cache.tp_kv[il]
17547 .as_ref()
17548 .expect("distributed cache checked above")
17549 .peek_append_ring(1)?;
17550 if !would_rebase {
17551 let base = (base_len - write_row) as i32;
17553 let distributed = cache.tp_kv[il]
17554 .as_mut()
17555 .expect("distributed cache checked above");
17556 for rank in 0..ranks {
17557 let engine = tp.runtime.rank_engine(rank).ok_or_else(|| {
17558 format!("Step TP layer {il} has no engine for rank {rank}")
17559 })?;
17560 let _main = engine.gpu.enter_main()?;
17561 let rank_cache = distributed
17562 .rank_mut(rank)
17563 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17564 if rank_cache.base_d().is_none() {
17565 rank_cache.arm_base_d(engine.htod_i32(&[base])?);
17566 }
17567 }
17568 }
17569 !would_rebase
17570 };
17571 let fuse_rope = dcw
17572 && crate::tp::fuse_rope_append_on()
17573 && head_dim == 128
17574 && cache.tp_kv[il]
17575 .as_ref()
17576 .map(|d| d.kv_dim_k() == d.kv_dim_v() && d.kv_dim_k() == local_kv_heads * head_dim)
17577 .unwrap_or(false);
17578
17579 let tcol_col = crate::tp::take_verify_tcol();
17580 let fa2_col = crate::tp::take_spec_fa2_defer();
17587 tp.runtime.decode_v2_input_qkv(
17588 ws,
17589 e,
17590 h,
17591 pos_d,
17592 gate_raw.as_ref(),
17593 if !use_gate_shards {
17594 None
17595 } else if let Some(shards) = attention.gate_shards.as_deref() {
17596 Some(crate::tp::StepTpGateShards::F32(shards))
17597 } else {
17598 attention
17599 .gate_shards_bf16
17600 .as_deref()
17601 .map(crate::tp::StepTpGateShards::Bf16)
17602 },
17603 &mut decode_input,
17604 &tp.q,
17605 &tp.k,
17606 &tp.v,
17607 &attention.q_norm,
17608 &attention.k_norm,
17609 head_dim,
17610 geometry.n_rot as usize,
17611 geometry.rope_base,
17612 &rope_freqs,
17613 self.cfg.rms_eps,
17614 fuse_rope,
17615 tcol_col,
17616 )?;
17617
17618 let transaction = cache.tp_kv[il]
17619 .as_mut()
17620 .expect("distributed cache checked above")
17621 .begin_transaction()?;
17622 let append_result = tp.runtime.append_tp_kv_transaction_inner(
17623 cache.tp_kv[il]
17624 .as_mut()
17625 .expect("distributed cache checked above"),
17626 transaction,
17627 &ws.k,
17628 &ws.v_raw,
17629 1,
17630 dcw,
17631 );
17632 if let Err(error) = append_result {
17633 let _ = tp.runtime.rollback_tp_kv_transaction(
17634 cache.tp_kv[il]
17635 .as_mut()
17636 .expect("distributed cache checked above"),
17637 transaction,
17638 );
17639 return Err(error);
17640 }
17641
17642 let staged = (|| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17643 let (staged_len, physical, k_tok_bytes_c, v_tok_bytes_c, capacity) = {
17646 let distributed = cache.tp_kv[il]
17647 .as_ref()
17648 .expect("distributed cache checked above");
17649 let staged_len = distributed.staged_len();
17650 let view_start = window
17651 .map(|window| staged_len.saturating_sub(window))
17652 .unwrap_or(0);
17653 (
17654 staged_len,
17655 distributed.physical_range(view_start, staged_len)?,
17656 distributed.k_tok_bytes(),
17657 distributed.v_tok_bytes(),
17658 distributed.physical_capacity(),
17659 )
17660 };
17661 let view_start = window
17662 .map(|window| staged_len.saturating_sub(window))
17663 .unwrap_or(0);
17664 let t_kv = staged_len - view_start;
17665 for rank in 0..ranks {
17666 let engine = tp
17667 .runtime
17668 .rank_engine(rank)
17669 .ok_or_else(|| format!("Step TP layer {il} has no engine for rank {rank}"))?;
17670 let _main = engine.gpu.enter_main()?;
17671 if dcw {
17672 {
17676 let distributed_mut = cache.tp_kv[il]
17677 .as_mut()
17678 .expect("distributed cache checked above");
17679 let (kv_dim_k, kv_dim_v) =
17680 (distributed_mut.kv_dim_k(), distributed_mut.kv_dim_v());
17681 let (k_tok_bytes, v_tok_bytes) =
17682 (distributed_mut.k_tok_bytes(), distributed_mut.v_tok_bytes());
17683 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
17684 format!("Step TP layer {il} has no KV cache rank {rank}")
17685 })?;
17686 let (k_plane, v_plane, len_d, base_d) =
17687 rank_cache.planes_and_counters_mut();
17688 if fuse_rope {
17689 let same_dev = engine.ctx().ordinal() == e.ctx().ordinal();
17692 let crate::tp::StepTpDecodeV2Ws {
17693 q_raw,
17694 k_raw,
17695 v_raw,
17696 q,
17697 k,
17698 pos,
17699 pos_stage,
17700 fuse_ctr,
17701 ..
17702 } = &mut *ws;
17703 let pos_ref: &CudaSlice<i32> = if same_dev {
17707 pos_stage
17708 .as_ref()
17709 .ok_or("step TP decode v2 pos stage not armed")?
17710 } else {
17711 &pos[rank]
17712 };
17713 engine.qk_norm_rope_append_inc_dcw(
17714 &q_raw[rank],
17715 &k_raw[rank],
17716 &v_raw[rank],
17717 &attention.q_norm[rank],
17718 &attention.k_norm[rank],
17719 &mut q[rank],
17720 &mut k[rank],
17721 pos_ref,
17722 k_plane,
17723 v_plane,
17724 len_d,
17725 base_d,
17726 &mut fuse_ctr[rank],
17727 kv_dim_k,
17728 kv_dim_v,
17729 k_tok_bytes,
17730 v_tok_bytes,
17731 head_dim,
17732 geometry.n_rot as usize,
17733 local_heads,
17734 local_kv_heads,
17735 self.cfg.rms_eps,
17736 geometry.rope_base,
17737 1.0,
17738 rope_freqs[rank],
17739 )?;
17740 } else {
17741 engine.append_kv_quantized_dcw(
17742 &ws.k[rank],
17743 &ws.v_raw[rank],
17744 k_plane,
17745 v_plane,
17746 len_d,
17747 base_d,
17748 kv_dim_k,
17749 kv_dim_v,
17750 k_tok_bytes,
17751 v_tok_bytes,
17752 )?;
17753 }
17754 if !fuse_rope {
17755 let rank_cache = distributed_mut.rank_mut(rank).ok_or_else(|| {
17756 format!("Step TP layer {il} has no KV cache rank {rank}")
17757 })?;
17758 engine.inc_i32(rank_cache.len_d_mut())?;
17759 }
17760 }
17761 let distributed = cache.tp_kv[il]
17762 .as_ref()
17763 .expect("distributed cache checked above");
17764 let rank_cache = distributed
17765 .rank(rank)
17766 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17767 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes_c);
17768 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes_c);
17769 if fa2_col.is_some() {
17770 continue;
17773 }
17774 {
17775 let crate::tp::StepTpDecodeV2Ws { q, gate, gated, .. } = &mut *ws;
17778 engine.fa_decode_dcw(
17779 &q[rank],
17780 &k_ring,
17781 &v_ring,
17782 &mut gated[rank],
17783 head_dim,
17784 local_heads,
17785 local_kv_heads,
17786 rank_cache.len_d(),
17787 rank_cache.base_d(),
17788 window.unwrap_or(0),
17789 t_kv,
17790 geometry.attention_scale(),
17791 k_tok_bytes_c,
17792 v_tok_bytes_c,
17793 Some(&gate[rank]),
17794 )?;
17795 }
17796 continue;
17797 }
17798 let distributed = cache.tp_kv[il]
17799 .as_ref()
17800 .expect("distributed cache checked above");
17801 let rank_cache = distributed
17802 .rank(rank)
17803 .ok_or_else(|| format!("Step TP layer {il} has no KV cache rank {rank}"))?;
17804 let k_view = engine.view_u8_range(
17805 rank_cache.k(),
17806 physical.start * k_tok_bytes_c,
17807 physical.end * k_tok_bytes_c,
17808 );
17809 let v_view = engine.view_u8_range(
17810 rank_cache.v(),
17811 physical.start * v_tok_bytes_c,
17812 physical.end * v_tok_bytes_c,
17813 );
17814 engine.fa_decode_kvmod(
17815 &ws.q[rank],
17816 &k_view,
17817 &v_view,
17818 &mut ws.attn_out[rank],
17819 head_dim,
17820 local_heads,
17821 local_kv_heads,
17822 t_kv,
17823 geometry.attention_scale(),
17824 k_tok_bytes_c,
17825 v_tok_bytes_c,
17826 false,
17827 )?;
17828 engine.attn_head_gate(
17829 &ws.attn_out[rank],
17830 &ws.gate[rank],
17831 &mut ws.gated[rank],
17832 None,
17833 head_dim,
17834 local_heads,
17835 1,
17836 )?;
17837 }
17838
17839 let output = if let Some(col) = fa2_col.filter(|_| dcw) {
17846 tp.runtime.decode_v2_stash_fa2(ws, e, col)?;
17850 crate::tp::set_spec_fa2_stashed();
17851 e.uninit(ws.o_out)?
17852 } else if let Some(col) = crate::tp::take_tcol_oproj_defer() {
17853 if tp.runtime.decode_v2_oproj_tcol_eligible(ws, &tp.o) {
17854 tp.runtime.decode_v2_stash_gated(ws, e, col)?;
17855 crate::tp::set_tcol_oproj_stashed();
17856 e.uninit(ws.o_out)?
17857 } else {
17858 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17859 }
17860 } else {
17861 tp.runtime.decode_v2_finish(ws, e, &tp.o)?
17862 };
17863
17864 let local = cache.kv[il]
17868 .as_mut()
17869 .ok_or_else(|| format!("Step TP layer {il} lost its local KV cache"))?;
17870 if local.len != base_len || base_len + 1 > max_ctx {
17871 return Err(format!(
17872 "Step TP layer {il} local cache changed during decode: \
17873 len={} base={base_len} max={max_ctx}",
17874 local.len
17875 )
17876 .into());
17877 }
17878 if crate::tp::no_local_shadow_on() {
17879 local.len = base_len + 1;
17882 if !crate::tp::len_mirror_lazy_on() {
17886 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17887 }
17888 } else {
17889 let retain_from = window
17890 .map(|window| {
17891 let staged_retain = (base_len + 1).saturating_sub(window) & !31usize;
17892 let rollback_retain =
17893 base_len.saturating_sub(window.saturating_sub(1)) & !31usize;
17894 staged_retain.min(rollback_retain)
17895 })
17896 .unwrap_or(0);
17897 let write_row = e.prepare_kv_append(local, retain_from, 1)?;
17898 e.append_kv_quantized(
17899 &ws.k_shadow,
17900 &ws.v_shadow,
17901 &mut local.k,
17902 &mut local.v,
17903 write_row,
17904 local.kv_dim_k,
17905 local.kv_dim_v,
17906 local.k_tok_bytes,
17907 local.v_tok_bytes,
17908 false,
17909 )?;
17910 local.len = base_len + 1;
17911 e.set_i32_one(&mut local.len_d, local.len as i32)?;
17912 }
17913 Ok(output)
17914 })();
17915
17916 let output = match staged {
17917 Ok(output) => output,
17918 Err(error) => {
17919 let _ = tp.runtime.rollback_tp_kv_transaction(
17920 cache.tp_kv[il]
17921 .as_mut()
17922 .expect("distributed cache checked above"),
17923 transaction,
17924 );
17925 if let Some(local) = cache.kv[il].as_mut() {
17926 local.len = base_len;
17927 let _ = e.set_i32_one(&mut local.len_d, base_len as i32);
17928 }
17929 return Err(error);
17930 }
17931 };
17932 let lazy_commit = fuse_rope && crate::tp::len_mirror_lazy_on();
17937 if lazy_commit {
17938 if let Err(error) = tp.runtime.commit_tp_kv_transaction_external(
17939 cache.tp_kv[il]
17940 .as_mut()
17941 .expect("distributed cache checked above"),
17942 transaction,
17943 1,
17944 ) {
17945 let _ = tp.runtime.rollback_tp_kv_transaction(
17946 cache.tp_kv[il]
17947 .as_mut()
17948 .expect("distributed cache checked above"),
17949 transaction,
17950 );
17951 let local = cache.kv[il].as_mut().expect("local cache checked above");
17952 local.len = base_len;
17953 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17954 return Err(error);
17955 }
17956 } else if let Err(error) = tp.runtime.commit_tp_kv_transaction(
17957 cache.tp_kv[il]
17958 .as_mut()
17959 .expect("distributed cache checked above"),
17960 transaction,
17961 1,
17962 ) {
17963 let _ = tp.runtime.rollback_tp_kv_transaction(
17964 cache.tp_kv[il]
17965 .as_mut()
17966 .expect("distributed cache checked above"),
17967 transaction,
17968 );
17969 let local = cache.kv[il].as_mut().expect("local cache checked above");
17970 local.len = base_len;
17971 e.set_i32_one(&mut local.len_d, base_len as i32)?;
17972 return Err(error);
17973 }
17974
17975 let committed = cache.tp_kv[il]
17976 .as_ref()
17977 .expect("distributed cache checked above")
17978 .committed_len();
17979 let local_len = cache.kv[il]
17980 .as_ref()
17981 .expect("local cache checked above")
17982 .len;
17983 if committed != local_len {
17984 return Err(format!(
17985 "Step TP layer {il} committed cache length {committed} != local shadow {local_len}"
17986 )
17987 .into());
17988 }
17989 static V2_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
17990 if !V2_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
17991 eprintln!(
17992 "[step-tp-attn-v2] execute layer={} devices={:?} tokens=1 driver=v2 \
17993 qkv_tensor_parallel=true qk_norm_rank_local=true rope_rank_local=true \
17994 kv_cache_distributed=true kv_cache_hydrated={hydrated} \
17995 attention_tensor_parallel=true attention_scope={} \
17996 input_path=root-device-replicated gate_tensor_parallel=false \
17997 gate_shards=device-staged o_tensor_parallel=true o_reduce=root-device \
17998 local_cache_shadow=true cache_commit=immediate transport={} native_p2p=true \
17999 bulk_p2p={} workspace=persistent ordering=evented output=e-device \
18000 performance_claim=false (logged once; every decode layer runs this driver)",
18001 tp.layer,
18002 tp.devices,
18003 if window.is_some() {
18004 "rank-local-swa-ring"
18005 } else {
18006 "rank-local-global"
18007 },
18008 tp.runtime.transport_label(),
18009 tp.runtime.bulk_p2p(),
18010 );
18011 }
18012 Ok(output)
18013 }
18014
18015 #[allow(clippy::too_many_arguments)]
18025 pub(crate) fn step35_decode_attn(
18026 &self,
18027 e: &Engine,
18028 fa: &FullAttnLayer,
18029 il: usize,
18030 h: &CudaSlice<f32>,
18031 pre_q: Option<(&CudaSlice<i8>, &CudaSlice<f32>)>,
18032 pos_d: &CudaSlice<i32>,
18033 cache: &mut Cache,
18034 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18035 if fa
18036 .step_tp_qkv
18037 .as_ref()
18038 .is_some_and(|tp| tp.attention.is_some())
18039 {
18040 if pre_q.is_some() {
18041 return Err(
18042 "rank-local Step attention preserves BF16 activations and refuses the q8_1 \
18043 pre-quantized decode path"
18044 .into(),
18045 );
18046 }
18047 return self.step35_tp_decode_attn_resident(e, fa, il, h, pos_d, cache);
18048 }
18049
18050 let geometry = self.step35_geom(il);
18051 let hd = geometry.head_dim_k as usize;
18052 let nkv = geometry.n_head_kv as usize;
18053 let nh = geometry.n_head as usize;
18054 let rbase = geometry.rope_base;
18055 let scale = geometry.attention_scale();
18056 let swa = geometry.window.is_some();
18057 let eps = self.cfg.rms_eps;
18058 let win = geometry.window.unwrap_or(0) as usize;
18059 let n_rot = geometry.n_rot as usize;
18060 let n_embd = self.cfg.n_embd as usize;
18061 let gw = fa
18062 .attn_gate
18063 .as_ref()
18064 .ok_or("step35 layer is missing attn_gate.weight (head-wise attention gate)")?;
18065
18066 let tp_qkv = if fa.step_tp_qkv.is_some() {
18067 if pre_q.is_some() {
18068 return Err(
18069 "Step Q/K/V TP preserves BF16 activations and refuses the q8_1 \
18070 pre-quantized decode path"
18071 .into(),
18072 );
18073 }
18074 self.step35_tp_qkv(e, fa, h, 1)?
18075 } else {
18076 None
18077 };
18078
18079 let (q0, k0, v0, gt) = match tp_qkv {
18080 Some(mut g3) => {
18081 let v = g3.pop().unwrap();
18082 let k = g3.pop().unwrap();
18083 let q = g3.pop().unwrap();
18084 let gt = e.matmul(gw, h, 1)?;
18085 (q, k, v, gt)
18086 }
18087 None => match pre_q {
18088 Some((hq, hdq)) => {
18089 debug_assert!(
18090 e.uses_q8_1_fast(gw),
18091 "step35 pre-quantized decode requires attn_gate on the q8_1 fast path \
18092 (h is a zero-length placeholder here) — see mixer_in_q8_1_fast"
18093 );
18094 let (a, b, c) = match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)? {
18095 Some(t3) => t3,
18096 None => (
18097 e.matmul_pre(&fa.wq, hq, hdq, h, 1)?,
18098 e.matmul_pre(&fa.wk, hq, hdq, h, 1)?,
18099 e.matmul_pre(&fa.wv, hq, hdq, h, 1)?,
18100 ),
18101 };
18102 let gt = e.matmul_pre(gw, hq, hdq, h, 1)?;
18103 (a, b, c, gt)
18104 }
18105 None => {
18106 if e.uses_q8_1_fast(&fa.wq)
18107 && e.uses_q8_1_fast(&fa.wk)
18108 && e.uses_q8_1_fast(&fa.wv)
18109 && e.uses_q8_1_fast(gw)
18110 {
18111 let (hq, hdq) = e.quantize_q8_1(h, 1, n_embd)?;
18112 let (a, b, c) =
18113 match e.matmul_q8_fused3(&fa.wq, &fa.wk, &fa.wv, &hq, &hdq)? {
18114 Some(t3) => t3,
18115 None => (
18116 e.matmul_pre(&fa.wq, &hq, &hdq, h, 1)?,
18117 e.matmul_pre(&fa.wk, &hq, &hdq, h, 1)?,
18118 e.matmul_pre(&fa.wv, &hq, &hdq, h, 1)?,
18119 ),
18120 };
18121 let gt = e.matmul_pre(gw, &hq, &hdq, h, 1)?;
18122 (a, b, c, gt)
18123 } else {
18124 (
18125 e.matmul(&fa.wq, h, 1)?,
18126 e.matmul(&fa.wk, h, 1)?,
18127 e.matmul(&fa.wv, h, 1)?,
18128 e.matmul(gw, h, 1)?,
18129 )
18130 }
18131 }
18132 },
18133 };
18134
18135 let mut q = e.uninit(nh * hd)?;
18136 e.rms_norm(&q0, fa.q_norm.float_data(), &mut q, hd, nh, eps)?;
18137 let mut k = e.uninit(nkv * hd)?;
18138 e.rms_norm(&k0, fa.k_norm.float_data(), &mut k, hd, nkv, eps)?;
18139 let ff = if swa {
18140 None
18141 } else {
18142 self.step35_aux.as_ref().and_then(|a| a.rope_freqs(e))
18143 };
18144 #[cfg(debug_assertions)]
18145 if let Some(ff) = ff {
18146 crate::debug_assert_tensor_stream_device(
18147 ff,
18148 &e.stream(),
18149 "step35_decode_attn.rope_freqs",
18150 );
18151 }
18152 e.rope_neox2(&mut q, &mut k, pos_d, hd, n_rot, nh, nkv, 1, rbase, 1.0, ff)?;
18153
18154 if std::env::var("MEMRA_NOFA").is_ok() {
18155 return Err(
18156 "MEMRA_NOFA (naive f32 SDPA) is incompatible with the quantized KV \
18157 cache; unset MEMRA_NOFA to use fa_decode"
18158 .into(),
18159 );
18160 }
18161 let kvl = cache.kv[il].as_mut().unwrap();
18162 let next_len = kvl.len + 1;
18163 let (off, t_kv) = if swa && next_len > win {
18164 (next_len - win, win)
18165 } else {
18166 (0, next_len)
18167 };
18168 let write_row = e.prepare_kv_append(kvl, off & !31usize, 1)?;
18169 e.append_kv_quantized(
18170 &k,
18171 &v0,
18172 &mut kvl.k,
18173 &mut kvl.v,
18174 write_row,
18175 kvl.kv_dim_k,
18176 kvl.kv_dim_v,
18177 kvl.k_tok_bytes,
18178 kvl.v_tok_bytes,
18179 crate::Engine::kv_fp8_on(),
18180 )?;
18181 kvl.len = next_len;
18182 let physical = kvl.physical_rows(off, off + t_kv)?;
18183 let k_view = e.view_u8_range(
18184 &kvl.k,
18185 physical.start * kvl.k_tok_bytes,
18186 physical.end * kvl.k_tok_bytes,
18187 );
18188 let v_view = e.view_u8_range(
18189 &kvl.v,
18190 physical.start * kvl.v_tok_bytes,
18191 physical.end * kvl.v_tok_bytes,
18192 );
18193 let mut attn = e.uninit(nh * hd)?;
18194 e.fa_decode_kvmod(
18195 &q,
18196 &k_view,
18197 &v_view,
18198 &mut attn,
18199 hd,
18200 nh,
18201 nkv,
18202 t_kv,
18203 scale,
18204 kvl.k_tok_bytes,
18205 kvl.v_tok_bytes,
18206 crate::Engine::kv_fp8_on(),
18207 )?;
18208
18209 let mut ag = e.uninit(nh * hd)?;
18210 e.attn_head_gate(&attn, >, &mut ag, None, hd, nh, 1)?;
18211 self.step35_o(e, fa, &ag, 1)
18212 }
18213}
18214
18215impl HybridModel {
18224 pub fn is_gemma4_e4b(&self) -> bool {
18225 self.gemma4_aux.as_ref().is_some_and(|a| a.e4b.is_some())
18226 }
18227
18228 fn gemma4_e4b_geom(&self, il: usize) -> (usize, usize, usize, f32, f32, bool) {
18232 let g = self.cfg.gemma4.as_ref().unwrap();
18233 let swa = g.swa_pattern[il];
18234 let hd = if swa {
18235 g.key_length_swa
18236 } else {
18237 g.key_length_global
18238 } as usize;
18239 let Mixer::Full(fa) = &self.layers[il].mixer else {
18240 panic!("e4b layer {il} not full-attn")
18241 };
18242 let nh = fa.wq.out_features() / hd;
18243 let nkv = fa.wk.out_features() / hd;
18244 (
18245 hd,
18246 nkv,
18247 nh,
18248 if swa {
18249 g.rope_base_swa
18250 } else {
18251 g.rope_base_global
18252 },
18253 1.0,
18254 swa,
18255 )
18256 }
18257
18258 fn gemma4_e4b_kv_target(&self, il: usize) -> Option<usize> {
18260 self.layers[il]
18261 .gemma4
18262 .as_ref()
18263 .and_then(|b| b.e4b.as_ref())
18264 .and_then(|e4| e4.kv_share.map(|t| t as usize))
18265 }
18266
18267 fn gemma4_e4b_inp_pl(
18272 &self,
18273 e: &Engine,
18274 tokens: &[u32],
18275 x_scaled: &CudaSlice<f32>,
18276 t: usize,
18277 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18278 let tok_d = e.stream().clone_htod(&tokens.to_vec())?;
18279 self.gemma4_e4b_inp_pl_dev(e, &tok_d, x_scaled, t)
18280 }
18281
18282 fn gemma4_e4b_inp_pl_dev(
18284 &self,
18285 e: &Engine,
18286 tok_d: &CudaSlice<u32>,
18287 x_scaled: &CudaSlice<f32>,
18288 t: usize,
18289 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18290 let aux = self.gemma4_aux.as_ref().unwrap();
18291 let m = aux.e4b.as_ref().unwrap();
18292 let n_embd = self.cfg.n_embd as usize;
18293 let n_layer = self.layers.len();
18294 let width = m.n_epl * n_layer;
18295 let tbl = m.tok_tbl_gpu.get_or_init(|| {
18296 e.upload_u8(&m.tok_embd_bytes)
18297 .expect("e4b per-layer token table upload")
18298 });
18299 let mut a =
18300 e.embed_gather_device_td(tbl, tok_d, t, width, m.tok_embd_qt, m.tok_embd_row_bytes)?;
18301 e.scale_inplace(&mut a, (m.n_epl as f32).sqrt(), t * width)?;
18302 let mut p = e.matmul(&m.model_proj, x_scaled, t)?;
18303 e.scale_inplace(&mut p, 1.0 / (n_embd as f32).sqrt(), t * width)?;
18304 let mut pn = e.uninit(t * width)?;
18305 e.rms_norm(
18306 &p,
18307 m.proj_norm.float_data(),
18308 &mut pn,
18309 m.n_epl,
18310 t * n_layer,
18311 self.cfg.rms_eps,
18312 )?;
18313 let mut out = e.uninit(t * width)?;
18314 e.add_scale(&a, &pn, 1.0 / 2f32.sqrt(), &mut out, t * width)?;
18315 Ok(out)
18316 }
18317
18318 #[allow(clippy::too_many_arguments)]
18323 fn gemma4_e4b_attn(
18324 &self,
18325 e: &Engine,
18326 il: usize,
18327 hq: &CudaSlice<i8>,
18328 hdq: &CudaSlice<f32>,
18329 pos_d: &CudaSlice<i32>,
18330 t: usize,
18331 cache: &mut Cache,
18332 dc_bucket: Option<usize>,
18333 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18334 let (hd, nkv, nh, base, scale, swa) = self.gemma4_e4b_geom(il);
18335 let eps = self.cfg.rms_eps;
18336 let aux = self.gemma4_aux.as_ref().unwrap();
18337 let ones = aux.ones(e);
18338 #[cfg(debug_assertions)]
18339 crate::debug_assert_tensor_stream_device(ones, &e.stream(), "gemma4_e4b_attn.ones");
18340 let Mixer::Full(fa) = &self.layers[il].mixer else {
18341 unreachable!()
18342 };
18343 let h0 = e.zeros(0)?;
18347 let h = &h0;
18348
18349 let ff = if swa {
18350 None
18351 } else {
18352 Some(
18353 aux.rope_freqs(e)
18354 .expect("e4b global rope needs rope_freqs.weight"),
18355 )
18356 };
18357 #[cfg(debug_assertions)]
18358 if let Some(ff) = ff {
18359 crate::debug_assert_tensor_stream_device(ff, &e.stream(), "gemma4_e4b_attn.rope_freqs");
18360 }
18361 let share = self.gemma4_e4b_kv_target(il);
18362 let mut kv_f32: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
18364 let mut q;
18365 if let Some(_tgt) = share {
18366 let q0 = e.matmul_pre(&fa.wq, hq, hdq, h, t)?;
18367 q = e.uninit(t * nh * hd)?;
18368 let mut kdummy = e.uninit(1)?;
18371 let mut vdummy = e.uninit(1)?;
18372 e.rms_norm_qkv_rope(
18373 &q0,
18374 &q0,
18375 &q0,
18376 fa.q_norm.float_data(),
18377 fa.q_norm.float_data(),
18378 ones,
18379 &mut q,
18380 &mut kdummy,
18381 &mut vdummy,
18382 hd,
18383 self.gemma4_rope_dims(il),
18384 nh * t,
18385 0,
18386 pos_d,
18387 nh,
18388 1,
18389 base,
18390 1.0,
18391 ff,
18392 eps,
18393 )?;
18394 } else {
18395 let e4bits = self.layers[il].gemma4.as_ref().and_then(|g| g.e4b.as_ref());
18399 let cat = e4bits.and_then(|e4| e4.qkv_cat.as_ref());
18400 q = e.uninit(t * nh * hd)?;
18401 let mut k = e.uninit(t * nkv * hd)?;
18402 let mut v = e.uninit(t * nkv * hd)?;
18403 if t == 1 && cat.is_some() {
18404 let qkv0 = e.matmul_pre(cat.unwrap(), hq, hdq, h, 1)?;
18405 e.rms_norm_qkv_rope_cat(
18406 &qkv0,
18407 fa.q_norm.float_data(),
18408 fa.k_norm.float_data(),
18409 ones,
18410 &mut q,
18411 &mut k,
18412 &mut v,
18413 hd,
18414 self.gemma4_rope_dims(il),
18415 nh,
18416 nkv,
18417 pos_d,
18418 nh,
18419 nkv,
18420 base,
18421 1.0,
18422 ff,
18423 eps,
18424 )?;
18425 } else {
18426 let (q0, k0, v0) = match if t == 1 {
18427 e.matmul_q4_fused3(&fa.wq, &fa.wk, &fa.wv, hq, hdq)?
18428 } else {
18429 static F2B_QKV: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18432 if *F2B_QKV.get_or_init(|| std::env::var("MEMRA_F2B").as_deref() != Ok("0")) {
18433 e.matmul_q4_fused3_batched(&fa.wq, &fa.wk, &fa.wv, hq, hdq, t)?
18434 } else {
18435 None
18436 }
18437 } {
18438 Some(triple) => triple,
18439 None => (
18440 e.matmul_pre(&fa.wq, hq, hdq, h, t)?,
18441 e.matmul_pre(&fa.wk, hq, hdq, h, t)?,
18442 e.matmul_pre(&fa.wv, hq, hdq, h, t)?,
18443 ), };
18445 e.rms_norm_qkv_rope(
18448 &q0,
18449 &k0,
18450 &v0,
18451 fa.q_norm.float_data(),
18452 fa.k_norm.float_data(),
18453 ones,
18454 &mut q,
18455 &mut k,
18456 &mut v,
18457 hd,
18458 self.gemma4_rope_dims(il),
18459 nh * t,
18460 nkv * t,
18461 pos_d,
18462 nh,
18463 nkv,
18464 base,
18465 1.0,
18466 ff,
18467 eps,
18468 )?;
18469 }
18470 let kvl = cache.kv[il].as_mut().unwrap();
18471 let cls = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18475 if dc_bucket.is_some() {
18476 debug_assert!(t == 1);
18481 e.append_kv_quantized_row_dc_inc(
18483 &k,
18484 &v,
18485 &mut kvl.k,
18486 &mut kvl.v,
18487 &mut kvl.len_d,
18488 kvl.kv_dim_k,
18489 kvl.kv_dim_v,
18490 kvl.k_tok_bytes,
18491 kvl.v_tok_bytes,
18492 cls,
18493 )?;
18494 } else {
18495 e.append_kv_quantized_rows(
18496 &k,
18497 &v,
18498 &mut kvl.k,
18499 &mut kvl.v,
18500 kvl.len,
18501 t,
18502 kvl.kv_dim_k,
18503 kvl.kv_dim_v,
18504 kvl.k_tok_bytes,
18505 kvl.v_tok_bytes,
18506 cls,
18507 )?;
18508 kvl.len += t;
18509 }
18510 kv_f32 = Some((k, v));
18511 }
18512 let kvl_idx = share.unwrap_or(il);
18515 let kvl = cache.kv[kvl_idx].as_ref().unwrap();
18516 let base_len = kvl.len - t; let win = self.cfg.gemma4.as_ref().unwrap().sliding_window as usize;
18518 let mut attn = e.uninit(t * nh * hd)?;
18519 if t > 1 && base_len == 0 && std::env::var("MEMRA_NOFA").is_err() {
18531 if let Some((kf, vf)) = &kv_f32 {
18532 if hd == 256 && t <= win {
18533 e.fa_prefill(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18534 return Ok(e.matmul(&fa.wo, &attn, t)?);
18535 }
18536 if hd == 256 && swa && t > win {
18537 e.fa_prefill_w(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
18538 return Ok(e.matmul(&fa.wo, &attn, t)?);
18539 }
18540 if hd == 512 && !swa {
18541 e.fa_prefill_hd512(&q, kf, vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18542 return Ok(e.matmul(&fa.wo, &attn, t)?);
18543 }
18544 } else if share.is_some() {
18545 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18546 let k_view = e.view_u8(&kvl.k, kvl.k.len());
18547 let v_view = e.view_u8(&kvl.v, kvl.v.len());
18548 if hd == 256 && (!swa || t <= win) {
18549 e.fa_prefill_view(
18551 &q,
18552 &k_view,
18553 &v_view,
18554 &mut attn,
18555 hd,
18556 nh,
18557 nkv,
18558 t,
18559 t,
18560 scale,
18561 true,
18562 kvl.k_tok_bytes,
18563 kvl.v_tok_bytes,
18564 g,
18565 )?;
18566 return Ok(e.matmul(&fa.wo, &attn, t)?);
18567 }
18568 let kv_dim = nkv * hd;
18571 let mut kf = e.uninit(t * kv_dim)?;
18572 let mut vf = e.uninit(t * kv_dim)?;
18573 e.fa_dequant_kv_view_f32(
18574 &k_view,
18575 &v_view,
18576 &mut kf,
18577 &mut vf,
18578 kv_dim,
18579 kv_dim,
18580 t,
18581 kvl.k_tok_bytes,
18582 kvl.v_tok_bytes,
18583 g,
18584 )?;
18585 if hd == 512 {
18586 e.fa_prefill_hd512(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true)?;
18587 } else {
18588 e.fa_prefill_w(&q, &kf, &vf, &mut attn, hd, nh, nkv, t, t, scale, true, win)?;
18589 }
18590 return Ok(e.matmul(&fa.wo, &attn, t)?);
18591 }
18592 }
18593 if let Some(bucket) = dc_bucket {
18594 assert!(t == 1);
18599 let bucket = if hd == 512 && win <= crate::fa512_min_tkv() {
18605 bucket.min(crate::fa512_min_tkv().saturating_sub(1))
18606 } else {
18607 bucket
18608 };
18609 let k_view = e.view_u8(&kvl.k, kvl.k.len());
18610 let v_view = e.view_u8(&kvl.v, kvl.v.len());
18611 let g = (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on());
18612 if crate::Engine::wpf_level() >= 1 {
18620 e.prefetch_weight_l2(&fa.wo)?;
18621 }
18622 if e.uses_q8_1_fast(&fa.wo) {
18625 let mut oq = e.alloc_i8_uninit(nh * hd)?;
18626 let mut od = e.zeros(nh * hd / 32)?;
18627 e.fa_decode_dc_q8(
18628 &q,
18629 &k_view,
18630 &v_view,
18631 &mut attn,
18632 hd,
18633 nh,
18634 nkv,
18635 &kvl.len_d,
18636 bucket,
18637 scale,
18638 kvl.k_tok_bytes,
18639 kvl.v_tok_bytes,
18640 g,
18641 Some((&mut oq, &mut od)),
18642 )?;
18643 return Ok(e.matmul_pre(&fa.wo, &oq, &od, &attn, t)?);
18644 }
18645 e.fa_decode_dc(
18646 &q,
18647 &k_view,
18648 &v_view,
18649 &mut attn,
18650 hd,
18651 nh,
18652 nkv,
18653 &kvl.len_d,
18654 bucket,
18655 scale,
18656 kvl.k_tok_bytes,
18657 kvl.v_tok_bytes,
18658 g,
18659 )?;
18660 return Ok(e.matmul(&fa.wo, &attn, t)?);
18661 }
18662 for i in 0..t {
18663 let avail = base_len + i + 1;
18664 let (off_tok, t_kv) = if swa && avail > win {
18665 (avail - win, win)
18666 } else {
18667 (0, avail)
18668 };
18669 let k_view = e.view_u8_range(
18670 &kvl.k,
18671 off_tok * kvl.k_tok_bytes,
18672 (off_tok + t_kv) * kvl.k_tok_bytes,
18673 );
18674 let v_view = e.view_u8_range(
18675 &kvl.v,
18676 off_tok * kvl.v_tok_bytes,
18677 (off_tok + t_kv) * kvl.v_tok_bytes,
18678 );
18679 let qv = e.view(&q, t * nh * hd);
18680 let q_row = qv.slice(i * nh * hd..(i + 1) * nh * hd);
18681 let mut q_one = e.uninit(nh * hd)?;
18682 e.copy_view_into(&mut q_one, 0, &q_row, nh * hd)?;
18683 let mut a_one = e.uninit(nh * hd)?;
18684 e.fa_decode_kvmod(
18688 &q_one,
18689 &k_view,
18690 &v_view,
18691 &mut a_one,
18692 hd,
18693 nh,
18694 nkv,
18695 t_kv,
18696 scale,
18697 kvl.k_tok_bytes,
18698 kvl.v_tok_bytes,
18699 (!swa && crate::Engine::gkv_on()) || (swa && crate::Engine::wkv_on()),
18700 )?;
18701 e.copy_into(&mut attn, i * nh * hd, &a_one, nh * hd)?;
18702 }
18703 Ok(e.matmul(&fa.wo, &attn, t)?)
18704 }
18705
18706 fn gemma4_e4b_trunk(
18711 &self,
18712 e: &Engine,
18713 tokens: &[u32],
18714 pos0: usize,
18715 cache: &mut Cache,
18716 head_last: bool,
18717 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18718 let n_embd = self.cfg.n_embd as usize;
18719 let t = tokens.len();
18720 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
18721 let pos_d = e.htod_i32(&pos)?;
18722 let mut x = e.htod(&self.embd.gather(n_embd, tokens))?;
18723 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
18724 let inp_pl = self.gemma4_e4b_inp_pl(e, tokens, &x, t)?;
18725 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, head_last)
18726 }
18727
18728 fn gemma4_e4b_trunk_core(
18732 &self,
18733 e: &Engine,
18734 x_in: CudaSlice<f32>,
18735 inp_pl: CudaSlice<f32>,
18736 pos_d: &CudaSlice<i32>,
18737 t: usize,
18738 cache: &mut Cache,
18739 dc_bucket: Option<usize>,
18740 cap_logits: bool,
18741 head_last: bool,
18742 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18743 let n_embd = self.cfg.n_embd as usize;
18744 let eps = self.cfg.rms_eps;
18745 let n_layer = self.layers.len();
18746 let mut x = x_in;
18747 let aux_e4b = self.gemma4_aux.as_ref().unwrap().e4b.as_ref().unwrap();
18748 let n_epl = aux_e4b.n_epl;
18749
18750 let mut h_carry: Option<(CudaSlice<i8>, CudaSlice<f32>)> = None;
18756 for il in 0..n_layer {
18757 let layer = &self.layers[il];
18758 let (hq, hdq) = match h_carry.take() {
18759 Some(p) => p,
18760 None => e.rms_norm_q8_1(&x, layer.attn_norm.float_data(), n_embd, t, eps)?,
18761 };
18762 let o = self.gemma4_e4b_attn(e, il, &hq, &hdq, pos_d, t, cache, dc_bucket)?;
18763 let bits = layer.gemma4.as_ref().unwrap();
18766 let e4b = bits.e4b.as_ref().expect("e4b layer bits");
18767 let fuse_exit = e.uses_q8_1_fast(&e4b.inp_gate);
18778 let (sn, attn_out) = self.gemma4_layer_tail_core_pn(
18779 e,
18780 layer,
18781 &o,
18782 &x,
18783 t,
18784 Some(layer.post_attn_norm.float_data()),
18785 fuse_exit,
18786 )?;
18787 let mut resid = e.uninit(t * n_embd)?;
18788 let g = if fuse_exit {
18794 let (rq, rd) = e.rms_pre_add_q8_1(
18796 &sn,
18797 bits.post_ffw_norm.float_data(),
18798 &attn_out,
18799 &mut resid,
18800 n_embd,
18801 t,
18802 self.cfg.rms_eps,
18803 )?;
18804 e.matmul_pre(&e4b.inp_gate, &rq, &rd, &resid, t)?
18805 } else {
18806 e.add(&sn, &attn_out, &mut resid, t * n_embd)?;
18807 e.matmul(&e4b.inp_gate, &resid, t)?
18808 };
18809 let mut act = e.uninit(t * n_epl)?;
18810 let y = if t == 1 && e.uses_q8_1_fast(&e4b.proj) {
18811 let ipv = e.view(&inp_pl, n_epl * n_layer);
18812 let row = ipv.slice(il * n_epl..(il + 1) * n_epl);
18813 let (aq, ad) = e.gelu_tanh_mul_q8_1(&g, &row, &mut act, n_epl, 1)?;
18814 e.matmul_pre(&e4b.proj, &aq, &ad, &act, t)?
18815 } else {
18816 let mut inp_this = e.uninit(t * n_epl)?;
18817 e.copy_rows_strided(
18818 &inp_pl,
18819 &mut inp_this,
18820 n_epl,
18821 t,
18822 n_epl * n_layer,
18823 il * n_epl,
18824 )?;
18825 e.gelu_tanh_mul(&g, &inp_this, &mut act, t * n_epl)?;
18826 e.matmul(&e4b.proj, &act, t)?
18827 };
18828 let next_norm = if il + 1 < n_layer {
18831 self.layers[il + 1].attn_norm.float_data()
18832 } else {
18833 self.output_norm.float_data()
18834 };
18835 let mut xn = e.uninit(t * n_embd)?;
18836 let pair = e.rms_pre_add_scale_rms_norm_q8_1(
18837 &y,
18838 e4b.post_norm.float_data(),
18839 &resid,
18840 bits.layer_scale,
18841 next_norm,
18842 &mut xn,
18843 n_embd,
18844 t,
18845 eps,
18846 )?;
18847 h_carry = Some(pair);
18848 x = xn;
18849 }
18850 let (oq, odq) = h_carry.take().unwrap();
18854 let h0 = e.zeros(0)?;
18855 let hm = if head_last { 1 } else { t };
18856 let (hq, hd) = if head_last && t > 1 {
18857 let mut q1 = e.uninit_i8(n_embd)?;
18858 e.dtod_copy_view_i8(&oq.slice((t - 1) * n_embd..t * n_embd), &mut q1)?;
18859 let nb = n_embd / 32;
18860 let mut d1 = e.uninit(nb)?;
18861 e.dtod_copy_view(&odq.slice((t - 1) * nb..t * nb), &mut d1)?;
18862 (q1, d1)
18863 } else {
18864 (oq, odq)
18865 };
18866 let mut ld = e.matmul_pre(&self.output, &hq, &hd, &h0, hm)?;
18867 if cap_logits {
18871 let cap = self.cfg.gemma4.as_ref().unwrap().final_logit_softcapping;
18872 e.softcap(&mut ld, cap, hm * self.output.out_features())?;
18873 }
18874 self.gemma4_suppress(e, &mut ld, hm)?; Ok((ld, x))
18876 }
18877
18878 pub fn gemma4_e4b_decode_step_t_am_dev(
18885 &self,
18886 e: &Engine,
18887 tok_d: &CudaSlice<u32>,
18888 t: usize,
18889 pos0: usize,
18890 cache: &mut Cache,
18891 ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18892 let n_embd = self.cfg.n_embd as usize;
18893 let eps = self.cfg.rms_eps;
18894 let pos: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
18895 let pos_d = e.htod_i32(&pos)?;
18896 let embd_gpu = self
18897 .embd_gpu
18898 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload"));
18899 let (qt, rb) = self.embd.qt_and_row_bytes(n_embd);
18900 let mut x = e.embed_gather_device_td(embd_gpu, tok_d, t, n_embd, qt, rb)?;
18901 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), t * n_embd)?;
18902 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, tok_d, &x, t)?;
18903 let (ld, xp) =
18904 self.gemma4_e4b_trunk_core(e, x, inp_pl, &pos_d, t, cache, None, true, false)?;
18905 let n_vocab = self.output.out_features();
18908 let mut vam = e.stream().alloc_zeros::<u32>(t)?;
18909 for i in 0..t {
18910 e.argmax_token_device_col(&ld, i, n_vocab, &mut vam, i)?;
18911 }
18912 let mut hn = e.uninit(t * n_embd)?;
18913 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18914 cache.pos += t;
18915 Ok((vam, hn))
18916 }
18917
18918 pub(crate) fn gemma4_e4b_decode_step_t_h(
18921 &self,
18922 e: &Engine,
18923 tokens: &[u32],
18924 pos0: usize,
18925 cache: &mut Cache,
18926 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18927 let n_embd = self.cfg.n_embd as usize;
18928 let eps = self.cfg.rms_eps;
18929 let t = tokens.len();
18930 let (ld, xp) = self.gemma4_e4b_trunk(e, tokens, pos0, cache, false)?;
18931 let mut hn = e.uninit(t * n_embd)?;
18932 e.rms_norm(&xp, self.output_norm.float_data(), &mut hn, n_embd, t, eps)?;
18933 cache.pos += t;
18934 Ok((e.dtoh(&ld)?, hn))
18935 }
18936
18937 pub fn gemma4_e4b_decode_step_dcg(
18943 &self,
18944 e: &Engine,
18945 token_d: &mut CudaSlice<u32>,
18946 pos_d: &mut CudaSlice<i32>,
18947 embd_gpu: &CudaSlice<u8>,
18948 embd_qt: i32,
18949 embd_rb: usize,
18950 cache: &mut Cache,
18951 n_vocab: usize,
18952 bucket: usize,
18953 ) -> Result<(), Box<dyn std::error::Error>> {
18954 let n_embd = self.cfg.n_embd as usize;
18955 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
18956 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
18957 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
18958 let (ld, _x) =
18959 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, Some(bucket), false, false)?;
18960 e.argmax_token_device_into(&ld, token_d, n_vocab)?;
18961 e.inc_seqlen(pos_d)?;
18962 Ok(())
18963 }
18964
18965 #[allow(clippy::too_many_arguments)]
18973 pub fn gemma4_e4b_decode_step_dc(
18974 &self,
18975 e: &Engine,
18976 token_d: &CudaSlice<u32>,
18977 pos_d: &mut CudaSlice<i32>,
18978 embd_gpu: &CudaSlice<u8>,
18979 embd_qt: i32,
18980 embd_rb: usize,
18981 cache: &mut Cache,
18982 n_vocab: usize,
18983 ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
18984 let n_embd = self.cfg.n_embd as usize;
18985 let eps = self.cfg.rms_eps;
18986 let mut x = e.embed_gather_device(embd_gpu, token_d, n_embd, embd_qt, embd_rb)?;
18987 e.scale_inplace(&mut x, (n_embd as f32).sqrt(), n_embd)?;
18988 let inp_pl = self.gemma4_e4b_inp_pl_dev(e, token_d, &x, 1)?;
18989 let (ld, _x) =
18990 self.gemma4_e4b_trunk_core(e, x, inp_pl, pos_d, 1, cache, None, false, false)?;
18991 let mut tok_out = e.stream().alloc_zeros::<u32>(1)?;
18992 e.argmax_token_device_into(&ld, &mut tok_out, n_vocab)?;
18993 e.inc_seqlen(pos_d)?;
18994 cache.pos += 1;
18995 let _ = eps;
18996 Ok(tok_out)
18997 }
18998
18999 pub(crate) fn gemma4_e4b_decode_step_h(
19002 &self,
19003 e: &Engine,
19004 token: u32,
19005 cache: &mut Cache,
19006 ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19007 let (ld, x) = self.gemma4_e4b_trunk(e, &[token], cache.pos, cache, false)?;
19008 let logits = e.dtoh(&ld)?;
19009 cache.pos += 1;
19010 Ok((logits, x))
19011 }
19012
19013 pub(crate) fn gemma4_e4b_prime(
19017 &self,
19018 e: &Engine,
19019 tokens: &[u32],
19020 cache: &mut Cache,
19021 ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
19022 if cache.pos != 0 {
19025 return Err(
19026 "e4b prime is fresh-prompt only (v0) — prime the full prompt in one \
19027 call or decode tokenwise"
19028 .into(),
19029 );
19030 }
19031 let n_embd = self.cfg.n_embd as usize;
19032 let t = tokens.len();
19033 let (ld, x) = self.gemma4_e4b_trunk(e, tokens, 0, cache, true)?;
19034 cache.pos += t;
19035 let last = e.dtoh(&ld)?; let xv = e.view(&x, t * n_embd);
19037 let row = xv.slice((t - 1) * n_embd..t * n_embd);
19038 let mut h_seed = e.uninit(n_embd)?;
19039 e.copy_view_into(&mut h_seed, 0, &row, n_embd)?;
19040 Ok((last, h_seed, x))
19041 }
19042
19043 pub(crate) fn gemma4_e4b_forward(
19045 &self,
19046 e: &Engine,
19047 tokens: &[u32],
19048 last_only: bool,
19049 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
19050 let mut cache = Cache::new(e, &self.cfg, tokens.len() + 8)?;
19051 let (ld, _x) = self.gemma4_e4b_trunk(e, tokens, 0, &mut cache, last_only)?;
19052 Ok(e.dtoh(&ld)?) }
19054}
19055
19056#[cfg(test)]
19057mod prime_chunk_schedule_tests {
19058 use super::{
19059 PRIME_MIN_T, PRIME_PIPE_MIN_CHUNK, active_matrix_values, align_prime_ranges_to_gdn,
19060 dynamic_prime_chunk_ranges, fixed_prime_chunk_ranges, fixed_prime_chunk_ranges_for_ring,
19061 parse_step_ep_grouped_prefill, parse_step_tp_prefill, step_grouped_decode_shape,
19062 step_grouped_prefill_shape, step_tp_prefill_shape, validate_step_prime_batch_modes,
19063 };
19064
19065 fn sizes(ranges: &[(usize, usize)]) -> Vec<usize> {
19066 ranges.iter().map(|(start, end)| end - start).collect()
19067 }
19068
19069 fn auto_chunk(t: usize) -> usize {
19070 t.div_ceil(8).max(PRIME_PIPE_MIN_CHUNK).min(4096)
19071 }
19072
19073 #[test]
19078 fn auto_prime_ranges_align_to_the_gdn_grid() {
19079 let c = 32usize; let assert_covers = |ranges: &[(usize, usize)], t: usize| {
19081 assert_eq!(ranges.first().map(|&(s, _)| s), Some(0));
19082 assert_eq!(ranges.last().map(|&(_, e)| e), Some(t));
19083 for w in ranges.windows(2) {
19084 assert_eq!(w[0].1, w[1].0, "ranges must stay contiguous");
19085 }
19086 assert!(ranges.iter().all(|&(s, e)| e > s), "no empty range");
19087 };
19088
19089 let t = 9510usize;
19092 let fill = auto_chunk(t);
19093 let fixed = fixed_prime_chunk_ranges(t, fill);
19094 assert!(
19095 fixed[..fixed.len() - 1].iter().any(|&(_, e)| e % c != 0),
19096 "broken arm vanished: fixed auto boundaries all landed on-grid"
19097 );
19098 let dynamic = dynamic_prime_chunk_ranges(t, fill, &fixed);
19099 assert!(
19100 dynamic[..dynamic.len() - 1]
19101 .iter()
19102 .any(|&(_, e)| e % c != 0),
19103 "broken arm vanished: dynamic auto boundaries all landed on-grid"
19104 );
19105
19106 for ranges in [&fixed, &dynamic] {
19107 let aligned = align_prime_ranges_to_gdn(ranges, t, c);
19108 assert_covers(&aligned, t);
19109 for &(_, e) in &aligned[..aligned.len() - 1] {
19110 assert_eq!(e % c, 0, "internal boundary {e} off the {c}-grid");
19111 }
19112 for (&(_, a), &(_, b)) in aligned.iter().zip(ranges.iter()) {
19114 assert!(a <= b && b - a < c);
19115 }
19116 }
19117
19118 let tight = vec![(0usize, 33usize), (33, 40), (40, 200)];
19121 let aligned = align_prime_ranges_to_gdn(&tight, 200, c);
19122 assert_covers(&aligned, 200);
19123 assert_eq!(aligned, vec![(0, 32), (32, 200)]);
19124
19125 assert_eq!(align_prime_ranges_to_gdn(&[(0, 200)], 200, c), [(0, 200)]);
19127 assert_eq!(align_prime_ranges_to_gdn(&tight, 200, 0), tight.as_slice());
19128 let on_grid = vec![(0usize, 128usize), (128, 256), (256, 300)];
19129 assert_eq!(
19130 align_prime_ranges_to_gdn(&on_grid, 300, c),
19131 on_grid.as_slice()
19132 );
19133 }
19134
19135 #[test]
19136 fn active_matrix_prefix_scopes_reused_prime_slabs() {
19137 assert_eq!(
19138 active_matrix_values(40 * 4096, 29, 4096, "activation").unwrap(),
19139 29 * 4096
19140 );
19141 assert_eq!(
19142 active_matrix_values(29 * 4096, 29, 4096, "activation").unwrap(),
19143 29 * 4096
19144 );
19145 assert_eq!(
19146 active_matrix_values(29 * 4096, 24, 4096, "activation").unwrap(),
19147 24 * 4096
19148 );
19149 assert!(active_matrix_values(28 * 4096, 29, 4096, "activation").is_err());
19150 assert!(active_matrix_values(usize::MAX, usize::MAX, 2, "activation").is_err());
19151 }
19152
19153 #[test]
19154 fn step_tp_prefill_batch_refuses_before_scheduler_fallback() {
19155 assert!(validate_step_prime_batch_modes(false, false).is_ok());
19156
19157 let grouped_without_tp = validate_step_prime_batch_modes(false, true).unwrap_err();
19158 assert!(grouped_without_tp.contains("requires MEMRA_STEP_TP_PREFILL=1"));
19159
19160 for grouped in [false, true] {
19161 let err = validate_step_prime_batch_modes(true, grouped).unwrap_err();
19162 assert!(err.contains("did not clear the live-server performance gate"));
19163 assert!(err.contains("per-session grouped prefill"));
19164 }
19165 }
19166
19167 #[test]
19168 fn step_grouped_path_is_eager_single_token_only() {
19169 assert!(step_grouped_decode_shape(false, 1));
19170 assert!(!step_grouped_decode_shape(true, 1));
19171 assert!(!step_grouped_decode_shape(false, 2));
19172 assert!(!step_grouped_decode_shape(true, 2));
19173 }
19174
19175 #[test]
19176 fn step_grouped_prefill_door_is_strict_and_capacity_bounded() {
19177 assert!(!parse_step_ep_grouped_prefill(None).unwrap());
19178 assert!(!parse_step_ep_grouped_prefill(Some("")).unwrap());
19179 assert!(!parse_step_ep_grouped_prefill(Some("0")).unwrap());
19180 assert!(parse_step_ep_grouped_prefill(Some("1")).unwrap());
19181 assert!(parse_step_ep_grouped_prefill(Some("true")).is_err());
19182 assert!(parse_step_ep_grouped_prefill(Some("2")).is_err());
19183
19184 assert!(step_grouped_prefill_shape(true, true, PRIME_MIN_T));
19185 assert!(step_grouped_prefill_shape(
19186 true,
19187 true,
19188 crate::cache::PRIME_CHUNK_MAX_TOKENS,
19189 ));
19190 assert!(!step_grouped_prefill_shape(true, true, PRIME_MIN_T - 1,));
19191 assert!(!step_grouped_prefill_shape(
19192 true,
19193 true,
19194 crate::cache::PRIME_CHUNK_MAX_TOKENS + 1,
19195 ));
19196 assert!(!step_grouped_prefill_shape(false, true, PRIME_MIN_T));
19197 assert!(!step_grouped_prefill_shape(true, false, PRIME_MIN_T));
19198 }
19199
19200 #[test]
19201 fn step_tp_prefill_door_is_strict_and_default_off() {
19202 assert!(!parse_step_tp_prefill(None).unwrap());
19203 assert!(!parse_step_tp_prefill(Some("")).unwrap());
19204 assert!(!parse_step_tp_prefill(Some("0")).unwrap());
19205 assert!(parse_step_tp_prefill(Some("1")).unwrap());
19206 assert!(parse_step_tp_prefill(Some("true")).is_err());
19207 assert!(parse_step_tp_prefill(Some("2")).is_err());
19208 }
19209
19210 #[test]
19211 fn step_tp_prefill_requires_a_qualified_even_rank_shape() {
19212 assert!(step_tp_prefill_shape(
19213 true,
19214 PRIME_MIN_T,
19215 4,
19216 true,
19217 true,
19218 false,
19219 ));
19220 assert!(!step_tp_prefill_shape(
19221 false,
19222 PRIME_MIN_T,
19223 4,
19224 true,
19225 true,
19226 false,
19227 ));
19228 assert!(!step_tp_prefill_shape(
19229 true,
19230 PRIME_MIN_T - 1,
19231 4,
19232 true,
19233 true,
19234 false,
19235 ));
19236 assert!(step_tp_prefill_shape(
19238 true,
19239 PRIME_MIN_T,
19240 2,
19241 true,
19242 true,
19243 false
19244 ));
19245 assert!(!step_tp_prefill_shape(
19246 true,
19247 PRIME_MIN_T,
19248 1,
19249 true,
19250 true,
19251 false
19252 ));
19253 assert!(!step_tp_prefill_shape(
19254 true,
19255 PRIME_MIN_T,
19256 3,
19257 true,
19258 true,
19259 false
19260 ));
19261 assert!(!step_tp_prefill_shape(
19262 true,
19263 PRIME_MIN_T,
19264 4,
19265 false,
19266 true,
19267 false,
19268 ));
19269 assert!(!step_tp_prefill_shape(
19270 true,
19271 PRIME_MIN_T,
19272 4,
19273 true,
19274 false,
19275 false,
19276 ));
19277 assert!(!step_tp_prefill_shape(
19278 true,
19279 PRIME_MIN_T,
19280 4,
19281 true,
19282 true,
19283 true,
19284 ));
19285 }
19286
19287 #[test]
19288 fn fixed_schedule_retains_measured_geometry() {
19289 assert_eq!(
19290 sizes(&fixed_prime_chunk_ranges(461, 128)),
19291 vec![128, 128, 128, 77]
19292 );
19293 assert_eq!(
19294 sizes(&fixed_prime_chunk_ranges(1833, 230)),
19295 vec![230, 230, 230, 230, 230, 230, 230, 223]
19296 );
19297 assert_eq!(sizes(&fixed_prime_chunk_ranges(4096, 512)), vec![512; 8]);
19298 let capped = sizes(&fixed_prime_chunk_ranges_for_ring(8200, 4096, true));
19299 assert_eq!(capped, vec![4096, 4088, 16]);
19300 assert!(capped.iter().all(|&rows| rows <= 4096));
19301 assert_eq!(
19302 sizes(&fixed_prime_chunk_ranges_for_ring(4100, 4096, false)),
19303 vec![4100],
19304 "flag-off schedule remains byte-for-byte the legacy monolithic tail",
19305 );
19306 }
19307
19308 #[test]
19309 fn dynamic_schedule_matches_registered_shapes() {
19310 let cases = [
19311 (461, vec![64, 141, 132, 124]),
19312 (1833, vec![115, 269, 260, 252, 244, 237, 231, 225]),
19313 (4096, vec![256, 602, 580, 563, 545, 531, 516, 503]),
19314 ];
19315 for (t, expected) in cases {
19316 let chunk = auto_chunk(t);
19317 let fixed = fixed_prime_chunk_ranges(t, chunk);
19318 assert_eq!(
19319 sizes(&dynamic_prime_chunk_ranges(t, chunk, &fixed)),
19320 expected
19321 );
19322 }
19323 }
19324
19325 #[test]
19326 fn dynamic_schedule_covers_exactly_and_shrinks_after_fill() {
19327 for t in 256..=8192 {
19328 let chunk = auto_chunk(t);
19329 let fixed = fixed_prime_chunk_ranges(t, chunk);
19330 let dynamic = dynamic_prime_chunk_ranges(t, chunk, &fixed);
19331 assert_eq!(dynamic.len(), fixed.len(), "T={t}");
19332 assert_eq!(dynamic.first().unwrap().0, 0, "T={t}");
19333 assert_eq!(dynamic.last().unwrap().1, t, "T={t}");
19334 for pair in dynamic.windows(2) {
19335 assert_eq!(pair[0].1, pair[1].0, "T={t}");
19336 }
19337 assert!(
19338 dynamic
19339 .iter()
19340 .all(|(start, end)| end - start >= PRIME_MIN_T),
19341 "T={t} sizes={:?}",
19342 sizes(&dynamic)
19343 );
19344 if dynamic.len() >= 3 {
19345 let chunk_sizes = sizes(&dynamic);
19346 assert!(
19347 chunk_sizes[0] < chunk_sizes[1],
19348 "T={t} sizes={chunk_sizes:?}"
19349 );
19350 assert!(
19351 chunk_sizes[1..].windows(2).all(|pair| pair[0] >= pair[1]),
19352 "T={t} sizes={chunk_sizes:?}"
19353 );
19354 }
19355 }
19356 }
19357}
19358
19359#[cfg(test)]
19360mod page_prefetch_tests {
19361 use super::{
19362 grouped_worker_prefetch_position, page_prefetch_positions,
19363 page_prefetch_window_from_values, worker_prefetch_positions,
19364 };
19365
19366 #[test]
19367 fn page_prefetch_window_keeps_existing_opt_in_default() {
19368 assert_eq!(page_prefetch_window_from_values(false, None), 0);
19369 assert_eq!(page_prefetch_window_from_values(false, Some("8")), 0);
19370 assert_eq!(page_prefetch_window_from_values(true, None), 1);
19371 assert_eq!(page_prefetch_window_from_values(true, Some("bad")), 1);
19372 assert_eq!(page_prefetch_window_from_values(true, Some("0")), 0);
19373 assert_eq!(page_prefetch_window_from_values(true, Some("8")), 8);
19374 }
19375
19376 #[test]
19377 fn rolling_page_prefetch_advises_each_future_expert_once() {
19378 let advised: Vec<_> = (0..7)
19379 .flat_map(|position| page_prefetch_positions(position, 7, 3))
19380 .collect();
19381 assert_eq!(advised, vec![1, 2, 3, 4, 5, 6]);
19382
19383 let one_ahead: Vec<_> = (0..4)
19384 .flat_map(|position| page_prefetch_positions(position, 4, 1))
19385 .collect();
19386 assert_eq!(one_ahead, vec![1, 2, 3]);
19387 assert!(page_prefetch_positions(0, 4, 0).is_empty());
19388 }
19389
19390 #[test]
19391 fn grouped_worker_prefetch_primes_first_then_each_known_next_once() {
19392 assert_eq!(grouped_worker_prefetch_position(0, None), None);
19393 let positions: Vec<_> = std::iter::once(grouped_worker_prefetch_position(4, None).unwrap())
19394 .chain(
19395 (0..4).filter_map(|position| grouped_worker_prefetch_position(4, Some(position))),
19396 )
19397 .collect();
19398 assert_eq!(positions, vec![0, 1, 2, 3]);
19399 assert_eq!(grouped_worker_prefetch_position(1, Some(0)), None);
19400 }
19401
19402 #[test]
19403 fn rolling_worker_prefetch_primes_current_and_each_future_expert_once() {
19404 let queued: Vec<_> = (0..8)
19405 .flat_map(|position| worker_prefetch_positions(position, 8, 5))
19406 .collect();
19407 assert_eq!(queued, (0..8).collect::<Vec<_>>());
19408
19409 let one_at_a_time: Vec<_> = (0..4)
19410 .flat_map(|position| worker_prefetch_positions(position, 4, 1))
19411 .collect();
19412 assert_eq!(one_at_a_time, vec![0, 1, 2, 3]);
19413 assert!(worker_prefetch_positions(0, 4, 0).is_empty());
19414 }
19415}
19416
19417pub struct G4DcSlots {
19418 x: CudaSlice<f32>,
19419 xn: CudaSlice<f32>,
19420 cur: CudaSlice<f32>,
19421 hq: CudaSlice<i8>,
19422 hd_: CudaSlice<f32>,
19423 q0: CudaSlice<f32>,
19424 k0: CudaSlice<f32>,
19425 v0: CudaSlice<f32>,
19426 q: CudaSlice<f32>,
19427 k: CudaSlice<f32>,
19428 v: CudaSlice<f32>,
19429 attn: CudaSlice<f32>,
19430 o: CudaSlice<f32>,
19431 attn_out: CudaSlice<f32>,
19432 zsh: CudaSlice<f32>,
19433 zq: CudaSlice<i8>,
19434 zd: CudaSlice<f32>,
19435 gate: CudaSlice<f32>,
19436 up: CudaSlice<f32>,
19437 act: CudaSlice<f32>,
19438 actq: CudaSlice<i8>,
19439 actd: CudaSlice<f32>,
19440 f0: CudaSlice<f32>,
19441 sn: CudaSlice<f32>,
19442 hn: CudaSlice<f32>,
19443 logits: CudaSlice<f32>,
19444}
19445
19446pub struct Step35TokenGraphState {
19451 pub graphs: Vec<(usize, crate::tp::TokenGraph)>,
19453 pub token_d: cudarc::driver::CudaSlice<u32>,
19454 pub pos_d: cudarc::driver::CudaSlice<i32>,
19455 pub logits_stage: cudarc::driver::CudaSlice<f32>,
19456 pub x: cudarc::driver::CudaSlice<f32>,
19461 pub x1: cudarc::driver::CudaSlice<f32>,
19462 pub mixed_stage: cudarc::driver::CudaSlice<f32>,
19463 pub sh_stage: cudarc::driver::CudaSlice<f32>,
19464 pub k_shadow_stage: cudarc::driver::CudaSlice<f32>,
19465 pub v_shadow_stage: cudarc::driver::CudaSlice<f32>,
19466 pub router_logits: cudarc::driver::CudaSlice<f32>,
19469 pub shexp_gate: cudarc::driver::CudaSlice<f32>,
19470 pub shexp_up: cudarc::driver::CudaSlice<f32>,
19471 pub shexp_act: cudarc::driver::CudaSlice<f32>,
19472 pub gate_sig: cudarc::driver::CudaSlice<f32>,
19473 pub dense_z: cudarc::driver::CudaSlice<f32>,
19474 pub dense_gate: cudarc::driver::CudaSlice<f32>,
19475 pub dense_up: cudarc::driver::CudaSlice<f32>,
19476 pub dense_act: cudarc::driver::CudaSlice<f32>,
19477 pub hn: cudarc::driver::CudaSlice<f32>,
19478 pub probe_mixed: cudarc::driver::CudaSlice<f32>,
19481 pub probe_x: cudarc::driver::CudaSlice<f32>,
19482 pub token_hist: cudarc::driver::CudaSlice<u32>,
19485 pub hist_idx: cudarc::driver::CudaSlice<i32>,
19486}
19487
19488impl HybridModel {
19489 pub(crate) fn step35_token_graph_step(
19500 &self,
19501 e: &Engine,
19502 token: u32,
19503 cache: &mut Cache,
19504 ) -> Result<Option<(Vec<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
19505 if !self.uses_sliding_gated_moe_program()
19506 || !crate::tp::step_tp_graph_enabled()?
19507 || !crate::tp::step_tp_dcw_enabled()?
19508 || !crate::tp::step_tp_qkv_fused_enabled()?
19509 || !crate::tp::step_tp_dev_router_enabled()?
19510 || !crate::tp::step_nvfp4_dev_routes_enabled()?
19511 {
19512 return Ok(None);
19513 }
19514 let n_embd = self.cfg.n_embd as usize;
19515 let n_vocab = self.cfg.n_vocab as usize;
19516 let eps = self.cfg.rms_eps;
19517 let n_layers = self.layers.len();
19518 let pos = cache.pos;
19519 let staged_next = pos + 1;
19520 if staged_next < 96 {
19521 return Ok(None); }
19523
19524 for il in 0..n_layers {
19527 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
19528 return Ok(None); };
19530 if tp_kv.peek_append_ring(1)?.1 {
19531 return Ok(None);
19532 }
19533 }
19534
19535 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
19538 if !fa_vec {
19539 return Ok(None);
19540 }
19541 let sp = crate::fa_split_keys(staged_next, 8);
19542 let bucket_max = (n_splits * sp).max(staged_next);
19543
19544 let mut state_guard = self
19545 .step35_token_graph
19546 .lock()
19547 .map_err(|_| "step35 token graph lock is poisoned")?;
19548 if state_guard.is_none() {
19549 let _main = e.gpu.enter_main()?;
19550 let n_expert = self
19551 .cfg
19552 .moe
19553 .as_ref()
19554 .map(|m| m.expert_count as usize)
19555 .unwrap_or(0);
19556 let n_ff_sh = self
19557 .layers
19558 .iter()
19559 .find_map(|l| match &l.ffn {
19560 crate::hybrid::Ffn::Moe(m) => m.gate_shexp.as_ref().map(|g| g.out_features()),
19561 _ => None,
19562 })
19563 .unwrap_or(0);
19564 let n_ff_dense = self
19565 .layers
19566 .iter()
19567 .find_map(|l| match &l.ffn {
19568 crate::hybrid::Ffn::Dense { ffn_gate, .. } => Some(ffn_gate.out_features()),
19569 _ => None,
19570 })
19571 .unwrap_or(0);
19572 *state_guard = Some(Step35TokenGraphState {
19573 graphs: Vec::new(),
19574 token_d: e.stream().clone_htod(&[0u32])?,
19575 pos_d: e.htod_i32(&[pos as i32])?,
19576 logits_stage: e.htod(&vec![0.0f32; n_vocab])?,
19577 x: e.htod(&vec![0.0f32; n_embd])?,
19578 x1: e.htod(&vec![0.0f32; n_embd])?,
19579 mixed_stage: e.htod(&vec![0.0f32; n_embd])?,
19580 sh_stage: e.htod(&vec![0.0f32; n_embd])?,
19581 k_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
19582 v_shadow_stage: e.htod(&vec![0.0f32; 2048])?,
19583 router_logits: e.htod(&vec![0.0f32; n_expert.max(1)])?,
19584 shexp_gate: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19585 shexp_up: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19586 shexp_act: e.htod(&vec![0.0f32; n_ff_sh.max(1)])?,
19587 gate_sig: e.htod(&vec![1.0f32; 1])?,
19588 dense_z: e.htod(&vec![0.0f32; n_embd])?,
19589 dense_gate: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19590 dense_up: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19591 dense_act: e.htod(&vec![0.0f32; n_ff_dense.max(1)])?,
19592 hn: e.htod(&vec![0.0f32; n_embd])?,
19593 probe_mixed: e.htod(&vec![0.0f32; n_embd])?,
19594 probe_x: e.htod(&vec![0.0f32; n_embd])?,
19595 token_hist: e.stream().clone_htod(&[0u32; 16])?,
19596 hist_idx: e.htod_i32(&[0])?,
19597 });
19598 }
19599 let state = state_guard.as_mut().expect("state armed above");
19600 {
19604 let _main = e.gpu.enter_main()?;
19605 let Step35TokenGraphState {
19606 logits_stage,
19607 token_d,
19608 ..
19609 } = &mut *state;
19610 e.argmax_token_device_into(logits_stage, token_d, n_vocab)?;
19611 }
19612
19613 if state.graphs.is_empty() {
19618 self.step35_token_graph_build(e, cache, state, bucket_max)?;
19621 }
19622 {
19623 let (b, g) = state.graphs.first_mut().expect("graph built above");
19624 if *b != bucket_max {
19625 g.retarget_bucket(bucket_max)?;
19626 *b = bucket_max;
19627 }
19628 }
19629 let graph = state
19630 .graphs
19631 .first()
19632 .map(|(_, g)| g)
19633 .expect("graph built above");
19634
19635 let tg_timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
19636 let t_fence = tg_timing.then(std::time::Instant::now);
19637 {
19642 let fa0 = match &self.layers[0].mixer {
19643 Mixer::Full(fa) => fa,
19644 _ => return Err("step35 token graph expects full-attention layers".into()),
19645 };
19646 let tp0 = fa0
19647 .step_tp_qkv
19648 .as_ref()
19649 .ok_or("step35 token graph lost its TP state")?;
19650 for rank in 0..tp0.runtime.devices().len() {
19651 let engine = tp0
19652 .runtime
19653 .rank_engine(rank)
19654 .ok_or("step35 token graph lost a rank engine")?;
19655 let _main = engine.gpu.enter_main()?;
19656 engine.stream().synchronize()?;
19657 }
19658 }
19659
19660 {
19662 let _main = e.gpu.enter_main()?;
19663 e.set_u32_one(&mut state.token_d, token)?;
19664 e.set_i32_one(&mut state.pos_d, pos as i32)?;
19665 }
19666 let t_launch = tg_timing.then(std::time::Instant::now);
19667 graph.launch(e)?;
19668 let t_book = tg_timing.then(std::time::Instant::now);
19669 for il in 0..n_layers {
19674 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
19675 let transaction = tp_kv.begin_transaction()?;
19676 let fa = match &self.layers[il].mixer {
19677 Mixer::Full(fa) => fa,
19678 _ => return Err("step35 token graph expects full-attention layers".into()),
19679 };
19680 let tp = fa
19681 .step_tp_qkv
19682 .as_ref()
19683 .ok_or("step35 token graph lost its TP state")?;
19684 let empty: [CudaSlice<f32>; 0] = [];
19687 tp.runtime.append_tp_kv_transaction_inner(
19688 tp_kv,
19689 transaction,
19690 &empty,
19691 &empty,
19692 1,
19693 true,
19694 )?;
19695 tp.runtime
19696 .commit_tp_kv_transaction_external(tp_kv, transaction, 1)?;
19697 if let Some(local) = cache.kv[il].as_mut() {
19699 local.len = pos + 1;
19700 let _main = e.gpu.enter_main()?;
19701 e.set_i32_one(&mut local.len_d, (pos + 1) as i32)?;
19702 }
19703 }
19704 cache.pos = pos + 1;
19705 let t_sync = tg_timing.then(std::time::Instant::now);
19706 let (logits, h_seed) = {
19707 let _main = e.gpu.enter_main()?;
19708 e.stream().synchronize()?;
19709 (e.dtoh(&state.logits_stage)?, e.clone_dtod(&state.x)?)
19710 };
19711 if let (Some(f), Some(l), Some(b), Some(sy)) = (t_fence, t_launch, t_book, t_sync) {
19712 use std::sync::atomic::{AtomicU64, Ordering};
19713 static NS: [AtomicU64; 5] = [
19714 AtomicU64::new(0),
19715 AtomicU64::new(0),
19716 AtomicU64::new(0),
19717 AtomicU64::new(0),
19718 AtomicU64::new(0),
19719 ];
19720 static CALLS: AtomicU64 = AtomicU64::new(0);
19721 let now = std::time::Instant::now();
19722 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;
19728 if calls % 100 == 0 {
19729 let avg = |i: usize| NS[i].load(Ordering::Relaxed) as f64 / calls as f64 / 1e3;
19730 eprintln!(
19731 "[tg-timing] calls={calls} fence_us={:.0} launch_us={:.0} book_us={:.0} \
19732 syncdtoh_us={:.0} total_us={:.0}",
19733 avg(0),
19734 avg(1),
19735 avg(2),
19736 avg(3),
19737 avg(4)
19738 );
19739 }
19740 }
19741 if std::env::var("MEMRA_TG_PROBE_LAYER").is_ok() {
19743 use std::io::Write;
19744 let (pm, px) = {
19745 let _main = e.gpu.enter_main()?;
19746 (e.dtoh(&state.probe_mixed)?, e.dtoh(&state.probe_x)?)
19747 };
19748 for (path, data) in [
19749 ("/root/tg-probe-mixed.bin", &pm),
19750 ("/root/tg-probe-x.bin", &px),
19751 ] {
19752 let mut fo = std::fs::OpenOptions::new()
19753 .create(true)
19754 .append(true)
19755 .open(path)?;
19756 for v in data {
19757 fo.write_all(&v.to_le_bytes())?;
19758 }
19759 }
19760 }
19761 if let Ok(path) = std::env::var("MEMRA_DUMP_HN") {
19764 let hh = {
19765 let _main = e.gpu.enter_main()?;
19766 e.dtoh(&state.hn)?
19767 };
19768 use std::io::Write;
19769 let mut fo = std::fs::OpenOptions::new()
19770 .create(true)
19771 .append(true)
19772 .open(path)?;
19773 for v in &hh {
19774 fo.write_all(&v.to_le_bytes())?;
19775 }
19776 }
19777 if std::env::var("MEMRA_STEP_TP_GRAPH_DEBUG").as_deref() == Ok("1") {
19780 for il in [0usize, 1, 44] {
19781 let tp_kv = cache.tp_kv[il].as_ref().expect("eligibility checked above");
19782 let host_len = tp_kv.staged_len();
19783 let fa = match &self.layers[il].mixer {
19784 Mixer::Full(fa) => fa,
19785 _ => continue,
19786 };
19787 let tp = fa
19788 .step_tp_qkv
19789 .as_ref()
19790 .ok_or("step35 token graph lost its TP state")?;
19791 for rank in 0..tp.runtime.devices().len() {
19792 let engine = tp
19793 .runtime
19794 .rank_engine(rank)
19795 .ok_or("step35 token graph lost a rank engine")?;
19796 let rank_cache = tp_kv.rank(rank).ok_or("debug rank cache missing")?;
19797 let _main = engine.gpu.enter_main()?;
19798 engine.stream().synchronize()?;
19799 let len_d = engine.dtoh_i32_one(rank_cache.len_d())?;
19800 let base_d = match rank_cache.base_d() {
19801 Some(b) => engine.dtoh_i32_one(b)?,
19802 None => -1,
19803 };
19804 eprintln!(
19805 "[graph-debug] pos={pos} il={il} rank={rank} host_len={host_len} \
19806 len_d={len_d} base_d={base_d}"
19807 );
19808 }
19809 }
19810 }
19811 Ok(Some((logits, h_seed)))
19812 }
19813
19814 pub(crate) fn head_split_matvec(
19820 &self,
19821 e: &Engine,
19822 hn: &CudaSlice<f32>,
19823 ) -> Result<Option<Vec<f32>>, Box<dyn std::error::Error>> {
19824 if self.head_split_fill_device(e, hn)?.is_none() {
19825 return Ok(None);
19826 }
19827 let guard = HEAD_SPLIT_WS
19828 .lock()
19829 .map_err(|_| "head split lock is poisoned")?;
19830 let ws = guard.as_ref().expect("filled above");
19831 let _main = e.gpu.enter_main()?;
19832 Ok(Some(e.dtoh(&ws.logits_e)?))
19833 }
19834
19835 fn head_split_fill_device(
19839 &self,
19840 e: &Engine,
19841 hn: &CudaSlice<f32>,
19842 ) -> Result<Option<()>, Box<dyn std::error::Error>> {
19843 use cudarc::driver::DevicePtr;
19844 let crate::model::GpuTensor::FloatBf16 { data: head, .. } = &self.output else {
19845 return Ok(None);
19846 };
19847 let Some(rank1) = self.layers.first().and_then(|l| match &l.mixer {
19848 Mixer::Full(fa) => fa
19849 .step_tp_qkv
19850 .as_ref()
19851 .and_then(|tp| tp.runtime.rank_engine(1)),
19852 _ => None,
19853 }) else {
19854 return Ok(None);
19855 };
19856 let n_embd = self.cfg.n_embd as usize;
19857 let n_vocab = self.cfg.n_vocab as usize;
19858 let half = n_vocab / 2;
19859 let mut guard = HEAD_SPLIT_WS
19860 .lock()
19861 .map_err(|_| "head split lock is poisoned")?;
19862 let pin = {
19863 let _main = e.gpu.enter_main()?;
19864 let stream = e.stream();
19865 let (ptr, _g) = head.device_ptr(&stream);
19866 ptr as u64
19867 };
19868 if guard.as_ref().is_none_or(|ws| ws.pin != pin) {
19869 let hi_rows = n_vocab - half;
19871 let (w1, hn1, y1, ev_done) = {
19872 let _r1 = rank1.gpu.enter_main()?;
19873 (
19874 rank1.alloc_u8_uninit(hi_rows * n_embd * 2)?,
19875 rank1.htod(&vec![0.0f32; n_embd])?,
19876 rank1.htod(&vec![0.0f32; hi_rows])?,
19877 rank1.ctx().new_event(None)?,
19878 )
19879 };
19880 {
19881 use cudarc::driver::sys;
19882 let src = pin + (half * n_embd * 2) as u64;
19883 let dst = {
19884 let _r1 = rank1.gpu.enter_main()?;
19885 let rstream = rank1.stream();
19886 let (d, _g) = w1.device_ptr(&rstream);
19887 d as u64
19888 };
19889 let _r1 = rank1.gpu.enter_main()?;
19890 let r = unsafe {
19891 sys::cuMemcpyAsync(
19892 dst as sys::CUdeviceptr,
19893 src as sys::CUdeviceptr,
19894 hi_rows * n_embd * 2,
19895 rank1.stream().cu_stream() as sys::CUstream,
19896 )
19897 };
19898 if r != sys::CUresult::CUDA_SUCCESS {
19899 return Err(format!("head split replica upload: {r:?}").into());
19900 }
19901 rank1.stream().synchronize()?;
19902 }
19903 let (logits_e, ev_hn) = {
19904 let _main = e.gpu.enter_main()?;
19905 (e.htod(&vec![0.0f32; n_vocab])?, e.ctx().new_event(None)?)
19906 };
19907 let (raw_hn1, raw_y1) = {
19908 let _r1 = rank1.gpu.enter_main()?;
19909 let rstream = rank1.stream();
19910 let (a, _g0) = hn1.device_ptr(&rstream);
19911 let (b, _g1) = y1.device_ptr(&rstream);
19912 (a as u64, b as u64)
19913 };
19914 let raw_logits_hi = {
19915 let _main = e.gpu.enter_main()?;
19916 let stream = e.stream();
19917 let (l, _g) = logits_e.device_ptr(&stream);
19918 l as u64 + (half * 4) as u64
19919 };
19920 *guard = Some(HeadSplit {
19921 pin,
19922 w1,
19923 hn1,
19924 y1,
19925 logits_e,
19926 ev_hn,
19927 ev_done,
19928 raw_hn1,
19929 raw_y1,
19930 raw_logits_hi,
19931 });
19932 }
19933 let ws = guard.as_mut().expect("armed above");
19934 let hi_rows = n_vocab - half;
19935 let raw_hn = {
19937 let _main = e.gpu.enter_main()?;
19938 let stream = e.stream();
19939 let (h, _g) = hn.device_ptr(&stream);
19940 ws.ev_hn.record(&stream)?;
19941 h as u64
19942 };
19943 {
19944 let _r1 = rank1.gpu.enter_main()?;
19945 rank1.stream().wait(&ws.ev_hn)?;
19946 crate::tp::raw_copy_bytes(ws.raw_hn1, raw_hn, n_embd * 4, rank1)?;
19947 let HeadSplit { w1, hn1, y1, .. } = &mut *ws;
19948 rank1.matvec_bf16_into(w1, hn1, y1, n_embd, hi_rows)?;
19949 crate::tp::raw_copy_bytes(ws.raw_logits_hi, ws.raw_y1, hi_rows * 4, rank1)?;
19950 ws.ev_done.record(&rank1.stream())?;
19951 }
19952 {
19953 let _main = e.gpu.enter_main()?;
19954 let head_lo = head.slice(0..half * n_embd * 2);
19955 let HeadSplit { logits_e, .. } = &mut *ws;
19956 e.matvec_bf16_view_into(&head_lo, hn, logits_e, n_embd, half)?;
19958 e.stream().wait(&ws.ev_done)?;
19959 Ok(Some(()))
19960 }
19961 }
19962
19963 pub(crate) fn head_split_argmax_device(
19968 &self,
19969 e: &Engine,
19970 hn: &CudaSlice<f32>,
19971 token_d: &mut CudaSlice<u32>,
19972 ) -> Result<bool, Box<dyn std::error::Error>> {
19973 if self.head_split_fill_device(e, hn)?.is_none() {
19974 return Ok(false);
19975 }
19976 let n_vocab = self.cfg.n_vocab as usize;
19977 let guard = HEAD_SPLIT_WS
19978 .lock()
19979 .map_err(|_| "head split lock is poisoned")?;
19980 let ws = guard.as_ref().expect("filled above");
19981 let _main = e.gpu.enter_main()?;
19982 e.argmax_token_device_into(&ws.logits_e, token_d, n_vocab)?;
19983 Ok(true)
19984 }
19985
19986 pub(crate) fn head_split_logits_dtoh(
19989 &self,
19990 e: &Engine,
19991 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
19992 let guard = HEAD_SPLIT_WS
19993 .lock()
19994 .map_err(|_| "head split lock is poisoned")?;
19995 let ws = guard.as_ref().ok_or("head split logits not armed")?;
19996 let _main = e.gpu.enter_main()?;
19997 Ok(e.dtoh(&ws.logits_e)?)
19998 }
19999
20000 pub fn step35_token_graph_chunk(
20009 &self,
20010 e: &Engine,
20011 token: u32,
20012 k_target: usize,
20013 cache: &mut Cache,
20014 ) -> Result<Option<(Vec<u32>, Vec<f32>)>, Box<dyn std::error::Error>> {
20015 if !self.uses_sliding_gated_moe_program()
20016 || !crate::tp::step_tp_graph_enabled()?
20017 || !crate::tp::step_tp_dcw_enabled()?
20018 || !crate::tp::step_tp_qkv_fused_enabled()?
20019 || !crate::tp::step_tp_dev_router_enabled()?
20020 || !crate::tp::step_nvfp4_dev_routes_enabled()?
20021 {
20022 return Ok(None);
20023 }
20024 let n_layers = self.layers.len();
20025 let pos = cache.pos;
20026 let staged_next = pos + 1;
20027 if staged_next < 96 {
20028 return Ok(None);
20029 }
20030 let (fa_vec, n_splits) = e.fa_geom_eager(staged_next, 128, 8, false);
20033 if !fa_vec {
20034 return Ok(None);
20035 }
20036 let sp = crate::fa_split_keys(staged_next, 8);
20037 let bucket_max = (n_splits * sp).max(staged_next);
20038 let to_boundary = bucket_max.saturating_sub(staged_next) + 1;
20039 let mut k = k_target.min(to_boundary).min(16);
20040 if k < 2 {
20041 return Ok(None);
20042 }
20043 for il in 0..n_layers {
20045 let Some(tp_kv) = cache.tp_kv[il].as_ref() else {
20046 return Ok(None);
20047 };
20048 while k >= 2 && tp_kv.peek_append_ring(k)?.1 {
20049 k -= 1;
20050 }
20051 if k < 2 {
20052 return Ok(None);
20053 }
20054 }
20055
20056 let mut state_guard = self
20057 .step35_token_graph
20058 .lock()
20059 .map_err(|_| "step35 token graph lock is poisoned")?;
20060 let Some(state) = state_guard.as_mut() else {
20061 return Ok(None); };
20063 if state.graphs.is_empty() {
20064 return Ok(None);
20065 }
20066 {
20067 let (b, g) = state.graphs.first_mut().expect("checked above");
20068 if *b != bucket_max {
20069 g.retarget_bucket(bucket_max)?;
20070 *b = bucket_max;
20071 }
20072 }
20073 let graph = state.graphs.first().map(|(_, g)| g).expect("checked above");
20074
20075 {
20077 let fa0 = match &self.layers[0].mixer {
20078 Mixer::Full(fa) => fa,
20079 _ => return Err("step35 token graph expects full-attention layers".into()),
20080 };
20081 let tp0 = fa0
20082 .step_tp_qkv
20083 .as_ref()
20084 .ok_or("step35 token graph lost its TP state")?;
20085 for rank in 0..tp0.runtime.devices().len() {
20086 let engine = tp0
20087 .runtime
20088 .rank_engine(rank)
20089 .ok_or("step35 token graph lost a rank engine")?;
20090 let _main = engine.gpu.enter_main()?;
20091 engine.stream().synchronize()?;
20092 }
20093 }
20094
20095 {
20098 let _main = e.gpu.enter_main()?;
20099 e.set_u32_one(&mut state.token_d, token)?;
20100 e.set_i32_one(&mut state.pos_d, pos as i32)?;
20101 e.set_i32_one(&mut state.hist_idx, 0)?;
20102 }
20103 for _ in 0..k {
20104 graph.launch(e)?;
20105 }
20106 for il in 0..n_layers {
20108 let tp_kv = cache.tp_kv[il].as_mut().expect("eligibility checked above");
20109 let transaction = tp_kv.begin_transaction()?;
20110 let fa = match &self.layers[il].mixer {
20111 Mixer::Full(fa) => fa,
20112 _ => return Err("step35 token graph expects full-attention layers".into()),
20113 };
20114 let tp = fa
20115 .step_tp_qkv
20116 .as_ref()
20117 .ok_or("step35 token graph lost its TP state")?;
20118 let empty: [CudaSlice<f32>; 0] = [];
20119 tp.runtime.append_tp_kv_transaction_inner(
20120 tp_kv,
20121 transaction,
20122 &empty,
20123 &empty,
20124 k,
20125 true,
20126 )?;
20127 tp.runtime
20128 .commit_tp_kv_transaction_external(tp_kv, transaction, k)?;
20129 if let Some(local) = cache.kv[il].as_mut() {
20130 local.len = pos + k;
20131 let _main = e.gpu.enter_main()?;
20132 e.set_i32_one(&mut local.len_d, (pos + k) as i32)?;
20133 }
20134 }
20135 cache.pos = pos + k;
20136 let (hist, logits) = {
20137 let _main = e.gpu.enter_main()?;
20138 e.stream().synchronize()?;
20139 (e.dtoh_u32(&state.token_hist)?, e.dtoh(&state.logits_stage)?)
20140 };
20141 Ok(Some((hist[..k].to_vec(), logits)))
20142 }
20143}
20144
20145impl HybridModel {
20146 #[allow(clippy::too_many_arguments)]
20151 fn step35_token_graph_build(
20152 &self,
20153 e: &Engine,
20154 cache: &mut Cache,
20155 state: &mut Step35TokenGraphState,
20156 bucket_max: usize,
20157 ) -> Result<(), Box<dyn std::error::Error>> {
20158 use cudarc::driver::DevicePtr;
20159 let n_embd = self.cfg.n_embd as usize;
20160 let eps = self.cfg.rms_eps;
20161 let n_layers = self.layers.len();
20162 let started = std::time::Instant::now();
20163 if !crate::router_kernel_on() {
20164 return Err(
20165 "step35 token graph requires the router kernel (MEMRA_ROUTER_KERNEL=0)".into(),
20166 );
20167 }
20168 if !Engine::bf16_mmv_on() || n_embd % 8 != 0 {
20169 return Err("step35 token graph requires MEMRA_BF16_MMV bf16-resident matvecs".into());
20170 }
20171
20172 let embd_gpu = self
20174 .embd_gpu_try(e)
20175 .ok_or("step35 token graph could not upload the device embed table")?;
20176 let embd_qtype = match self.embd.ggml_type {
20177 memra_gguf::GgmlType::BF16 => crate::QT_BF16,
20178 memra_gguf::GgmlType::Q8_0 => crate::QT_Q8_0,
20179 other => return Err(format!("token graph embed dtype {other:?} unhandled").into()),
20180 };
20181 let embd_row_bytes = self.embd.raw.len() / self.cfg.n_vocab as usize;
20182
20183 let (p_mixed, p_kshadow, p_vshadow) = {
20185 let _main = e.gpu.enter_main()?;
20186 let stream = e.stream();
20187 let (a, _g) = state.mixed_stage.device_ptr(&stream);
20188 let (b, _g) = state.k_shadow_stage.device_ptr(&stream);
20189 let (c, _g) = state.v_shadow_stage.device_ptr(&stream);
20190 (a as u64, b as u64, c as u64)
20191 };
20192
20193 crate::tp::token_graph_build_begin()?;
20194 let mut group_id: u32 = 0;
20195 for il in 0..n_layers {
20196 let layer = &self.layers[il];
20197 let fa = match &layer.mixer {
20198 Mixer::Full(fa) => fa,
20199 _ => return Err("step35 token graph expects full-attention layers".into()),
20200 };
20201 let tp = fa
20202 .step_tp_qkv
20203 .as_ref()
20204 .ok_or("step35 token graph lost its TP state")?;
20205 let attention = tp
20206 .attention
20207 .as_ref()
20208 .ok_or("step35 token graph lost its attention aux")?;
20209 let geometry = self.step35_geom(il);
20210 let window = geometry.window.map(|w| w as usize);
20211 let head_dim = geometry.head_dim_k as usize;
20212 let heads = geometry.n_head as usize;
20213 let kv_heads = geometry.n_head_kv as usize;
20214 let ranks = tp.runtime.devices().len();
20215 let local_heads = heads / ranks;
20216 let local_kv_heads = kv_heads / ranks;
20217 let layer_bucket = window.map(|w| bucket_max.min(w)).unwrap_or(bucket_max);
20218 let use_gate_shards =
20219 attention.gate_shards.is_some() || attention.gate_shards_bf16.is_some();
20220 if !use_gate_shards {
20221 return Err("step35 token graph requires the fused gate shards".into());
20222 }
20223
20224 let ws_index = tp
20225 .runtime
20226 .decode_v2_ensure(e, &tp.q, &tp.k, &tp.v, &tp.o, heads)?;
20227 let ws_mutex = tp.runtime.decode_v2_workspace();
20228 let mut ws_guard = ws_mutex
20229 .lock()
20230 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
20231 let ws = ws_guard
20232 .get_mut(ws_index)
20233 .ok_or("step TP decode v2 workspace missing after ensure")?;
20234 tp.runtime
20235 .decode_v2_arm_token_mirrors(ws, p_mixed, (p_kshadow, p_vshadow))?;
20236 let mut rope_freqs = Vec::with_capacity(ranks);
20237 for rank in 0..ranks {
20238 let engine = tp
20239 .runtime
20240 .rank_engine(rank)
20241 .ok_or("step35 token graph lost a rank engine")?;
20242 rope_freqs.push(if geometry.rope_factors {
20243 self.step35_aux
20244 .as_ref()
20245 .and_then(|aux| aux.rope_freqs(engine))
20246 } else {
20247 None
20248 });
20249 }
20250 let gate_shards_arg = if let Some(shards) = attention.gate_shards.as_deref() {
20251 Some(crate::tp::StepTpGateShards::F32(shards))
20252 } else {
20253 attention
20254 .gate_shards_bf16
20255 .as_deref()
20256 .map(crate::tp::StepTpGateShards::Bf16)
20257 };
20258
20259 let decode_input = attention
20261 .decode_input
20262 .as_ref()
20263 .ok_or("step35 token graph requires the replicated decode input")?;
20264 let mut decode_input = decode_input
20265 .lock()
20266 .map_err(|_| "replicated decode input lock is poisoned")?;
20267 if ws.h_stage.is_none() {
20269 return Err(
20270 "step35 token graph requires the stage flow armed (run eager dcw first)".into(),
20271 );
20272 }
20273 {
20274 let state_x = &mut state.x;
20275 let token_d = &state.token_d;
20276 let pos_d = &state.pos_d;
20277 crate::tp::graph_section(e, None, || {
20278 let _main = e.gpu.enter_main()?;
20279 if il == 0 {
20280 e.embed_gather_device_into(
20281 embd_gpu,
20282 token_d,
20283 state_x,
20284 n_embd,
20285 embd_qtype,
20286 embd_row_bytes,
20287 )?;
20288 }
20289 {
20290 let h_stage = ws.h_stage.as_mut().expect("stage armed checked above");
20291 e.rms_norm(
20292 state_x,
20293 layer.attn_norm.float_data(),
20294 h_stage,
20295 n_embd,
20296 1,
20297 eps,
20298 )?;
20299 }
20300 {
20301 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
20302 let mut dst = pos_stage.slice_mut(0..1);
20303 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
20304 }
20305 Ok(())
20306 })?;
20307 }
20308
20309 group_id += 1;
20311 for rank in 0..ranks {
20312 let engine = tp
20313 .runtime
20314 .rank_engine(rank)
20315 .ok_or("step35 token graph lost a rank engine")?;
20316 {
20317 let ceiling = window
20322 .map(|w| cache.max_ctx.min(w))
20323 .unwrap_or(cache.max_ctx);
20324 let _main = engine.gpu.enter_main()?;
20325 engine.fa_dcw_pool_ensure(
20326 head_dim,
20327 local_heads,
20328 local_kv_heads,
20329 ceiling.min(2048),
20330 )?;
20331 engine.fa_dcw_pool_ensure(head_dim, local_heads, local_kv_heads, ceiling)?;
20332 engine.fa_dcw_pool_ensure(
20333 head_dim,
20334 local_heads,
20335 local_kv_heads,
20336 layer_bucket,
20337 )?;
20338 }
20339 let runtime = &tp.runtime;
20340 let q_norm = &attention.q_norm;
20341 let k_norm = &attention.k_norm;
20342 let gate_ref = gate_shards_arg.as_ref();
20343 crate::tp::graph_section(engine, Some(group_id), || {
20344 runtime.decode_v2_input_qkv_rank(
20345 ws,
20346 &state.pos_d,
20347 &mut decode_input,
20348 &tp.q,
20349 &tp.k,
20350 &tp.v,
20351 q_norm,
20352 k_norm,
20353 head_dim,
20354 geometry.n_rot as usize,
20355 geometry.rope_base,
20356 &rope_freqs,
20357 eps,
20358 gate_ref,
20359 true,
20360 false,
20361 rank,
20362 None,
20363 )?;
20364 let distributed = cache.tp_kv[il]
20367 .as_mut()
20368 .ok_or("step35 token graph lost a TP cache")?;
20369 let (kv_dim_k, kv_dim_v) = (distributed.kv_dim_k(), distributed.kv_dim_v());
20370 let (ktb, vtb) = (distributed.k_tok_bytes(), distributed.v_tok_bytes());
20371 let capacity = distributed.physical_capacity();
20372 {
20373 let rank_cache = distributed
20374 .rank_mut(rank)
20375 .ok_or("step35 token graph lost a rank cache")?;
20376 let (k_plane, v_plane, len_d, base_d) =
20377 rank_cache.planes_and_counters_mut();
20378 engine.append_kv_quantized_dcw(
20379 &ws.k[rank],
20380 &ws.v_raw[rank],
20381 k_plane,
20382 v_plane,
20383 len_d,
20384 base_d,
20385 kv_dim_k,
20386 kv_dim_v,
20387 ktb,
20388 vtb,
20389 )?;
20390 }
20391 {
20392 let rank_cache = distributed
20393 .rank_mut(rank)
20394 .ok_or("step35 token graph lost a rank cache")?;
20395 engine.inc_i32(rank_cache.len_d_mut())?;
20396 }
20397 let rank_cache = distributed
20398 .rank(rank)
20399 .ok_or("step35 token graph lost a rank cache")?;
20400 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * ktb);
20401 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * vtb);
20402 engine.fa_decode_dcw(
20407 &ws.q[rank],
20408 &k_ring,
20409 &v_ring,
20410 &mut ws.attn_out[rank],
20411 head_dim,
20412 local_heads,
20413 local_kv_heads,
20414 rank_cache.len_d(),
20415 rank_cache.base_d(),
20416 window.unwrap_or(0),
20417 layer_bucket,
20418 geometry.attention_scale(),
20419 ktb,
20420 vtb,
20421 None,
20422 )?;
20423 engine.attn_head_gate(
20424 &ws.attn_out[rank],
20425 &ws.gate[rank],
20426 &mut ws.gated[rank],
20427 None,
20428 head_dim,
20429 local_heads,
20430 1,
20431 )?;
20432 runtime.decode_v2_finish_rank_partial(ws, &tp.o, true, rank)?;
20433 Ok(())
20434 })?;
20435 }
20436
20437 {
20439 let root = tp
20440 .runtime
20441 .rank_engine(0)
20442 .ok_or("step35 token graph lost the root engine")?;
20443 let runtime = &tp.runtime;
20444 crate::tp::graph_section(root, None, || runtime.decode_v2_finish_root_fused(ws))?;
20445 }
20446 drop(ws_guard);
20447 drop(decode_input);
20448
20449 let probe_layer: Option<usize> = std::env::var("MEMRA_TG_PROBE_LAYER")
20450 .ok()
20451 .and_then(|v| v.parse().ok());
20452 if probe_layer == Some(il) {
20453 let Step35TokenGraphState {
20454 mixed_stage,
20455 probe_mixed,
20456 ..
20457 } = &mut *state;
20458 crate::tp::graph_section(e, None, || {
20459 let _main = e.gpu.enter_main()?;
20460 let mut dst = probe_mixed.slice_mut(0..n_embd);
20461 e.stream()
20462 .memcpy_dtod(&mixed_stage.slice(0..n_embd), &mut dst)?;
20463 Ok(())
20464 })?;
20465 }
20466
20467 match &layer.ffn {
20469 crate::hybrid::Ffn::Dense {
20470 ffn_gate,
20471 ffn_up,
20472 ffn_down,
20473 } => {
20474 let n_ff = ffn_gate.out_features();
20475 let lim = self.cfg.clamp_shexp_at(il as u32);
20476 if lim.is_some() {
20480 return Err("step35 token graph dense FFN with clamp unsupported".into());
20481 }
20482 let (wg_d, wu_d, wd_d) = match (ffn_gate, ffn_up, ffn_down) {
20483 (
20484 crate::model::GpuTensor::FloatBf16 { data: wg, .. },
20485 crate::model::GpuTensor::FloatBf16 { data: wu, .. },
20486 crate::model::GpuTensor::FloatBf16 { data: wd, .. },
20487 ) => (wg, wu, wd),
20488 _ => {
20489 return Err(
20490 "step35 token graph dense FFN requires bf16-resident weights"
20491 .into(),
20492 );
20493 }
20494 };
20495 crate::tp::graph_section(e, None, || {
20496 let _main = e.gpu.enter_main()?;
20497 let Step35TokenGraphState {
20498 x,
20499 x1,
20500 mixed_stage,
20501 dense_z,
20502 dense_gate,
20503 dense_up,
20504 dense_act,
20505 sh_stage,
20506 ..
20507 } = &mut *state;
20508 e.add_rms_norm(
20509 x,
20510 mixed_stage,
20511 layer.post_attn_norm.float_data(),
20512 x1,
20513 dense_z,
20514 n_embd,
20515 1,
20516 eps,
20517 )?;
20518 e.matvec_bf16_into(wg_d, dense_z, dense_gate, n_embd, n_ff)?;
20522 e.matvec_bf16_into(wu_d, dense_z, dense_up, n_embd, n_ff)?;
20523 Self::ffn_act_lim(
20524 e, &self.cfg, dense_gate, dense_up, 1.0, 1.0, lim, dense_act, n_ff,
20525 )?;
20526 e.matvec_bf16_into(wd_d, dense_act, sh_stage, n_ff, n_embd)?;
20527 e.add(x1, sh_stage, x, n_embd)?;
20528 Ok(())
20529 })?;
20530 }
20531 crate::hybrid::Ffn::Moe(m) => {
20532 let moe = self
20533 .cfg
20534 .moe
20535 .as_ref()
20536 .ok_or("step35 token graph needs moe cfg")?;
20537 let n_expert = moe.expert_count as usize;
20538 let n_used = moe.expert_used_count as usize;
20539 let sigmoid = self
20540 .cfg
20541 .sigmoid_router()
20542 .ok_or("step35 token graph needs the sigmoid router")?;
20543 let step_tp = m
20544 .step_tp
20545 .as_ref()
20546 .ok_or("step35 token graph needs TP experts")?;
20547 let bank = match &step_tp.experts {
20548 crate::hybrid::StepTpExpertBank::Nvfp4(bank) => bank,
20549 _ => return Err("step35 token graph needs the NVFP4 bank".into()),
20550 };
20551 let routes_ws_mutex = bank.device_workspace_handle();
20552 let mut routes_guard = routes_ws_mutex
20553 .lock()
20554 .map_err(|_| "routes workspace lock is poisoned")?;
20555 let routes_ws = routes_guard
20556 .as_mut()
20557 .ok_or("step35 token graph requires the routes workspace warmed")?;
20558 routes_ws.arm_stages(e, bank.input_width, n_used)?;
20559 step_tp.runtime.routes_arm_raw(bank, routes_ws)?;
20560 let p_z = {
20561 let root = step_tp
20562 .runtime
20563 .rank_engine(0)
20564 .ok_or("routes root engine missing")?;
20565 let _main = root.gpu.enter_main()?;
20566 let stream = root.stream();
20567 let in_stage = routes_ws
20568 .in_stage_handle()
20569 .ok_or("routes in stage not armed")?;
20570 let (a, _g) = in_stage.device_ptr(&stream);
20571 a as u64
20572 };
20573 let local_out = bank.expert_width / ranks;
20574
20575 crate::tp::graph_section(e, None, || {
20577 let _main = e.gpu.enter_main()?;
20578 {
20579 let in_stage = routes_ws
20580 .in_stage_mut()
20581 .ok_or("routes in stage not armed")?;
20582 let Step35TokenGraphState {
20583 x, x1, mixed_stage, ..
20584 } = &mut *state;
20585 e.add_rms_norm(
20586 x,
20587 mixed_stage,
20588 layer.post_attn_norm.float_data(),
20589 x1,
20590 in_stage,
20591 n_embd,
20592 1,
20593 eps,
20594 )?;
20595 }
20596 {
20597 let z_ref = routes_ws
20598 .in_stage_handle()
20599 .ok_or("routes in stage not armed")?;
20600 e.router_gemv_into(
20601 m.gate_inp.float_data(),
20602 z_ref,
20603 &mut state.router_logits,
20604 n_embd,
20605 n_expert,
20606 1,
20607 )?;
20608 }
20609 let (sel_e, w_e) = routes_ws
20610 .dev_route_e_mut()
20611 .ok_or("routes staging not armed")?;
20612 e.moe_router_sigmoid_topk_into(
20613 &state.router_logits,
20614 1,
20615 n_expert,
20616 n_used,
20617 m.active_count(),
20618 &m.exp_probs_b_dev,
20619 &m.active_experts_dev,
20620 sigmoid.0,
20621 sigmoid.1,
20622 sel_e,
20623 w_e,
20624 )?;
20625 Ok(())
20626 })?;
20627
20628 group_id += 1;
20630 for rank in 0..ranks {
20631 let engine = step_tp
20632 .runtime
20633 .rank_engine(rank)
20634 .ok_or("routes rank engine missing")?;
20635 let runtime = &step_tp.runtime;
20636 crate::tp::graph_section(engine, Some(group_id), || {
20637 runtime.routes_rank_section(
20638 bank,
20639 routes_ws,
20640 p_z,
20641 local_out,
20642 n_used,
20643 step_tp.activation_limit,
20644 rank,
20645 )
20646 })?;
20647 }
20648
20649 {
20651 let root = step_tp
20652 .runtime
20653 .rank_engine(0)
20654 .ok_or("routes root engine missing")?;
20655 let runtime = &step_tp.runtime;
20656 crate::tp::graph_section(root, None, || {
20657 runtime.routes_root_section(bank, routes_ws)
20658 })?;
20659 }
20660
20661 let lim_sh = self.cfg.clamp_shexp_at(il as u32);
20665 let (wg_sh, wu_sh, wd_sh) = match (&m.gate_shexp, &m.up_shexp, &m.down_shexp) {
20666 (
20667 Some(crate::model::GpuTensor::FloatBf16 { data: wg, .. }),
20668 Some(crate::model::GpuTensor::FloatBf16 { data: wu, .. }),
20669 Some(crate::model::GpuTensor::FloatBf16 { data: wd, .. }),
20670 ) => (wg, wu, wd),
20671 _ => {
20672 return Err(
20673 "step35 token graph shexp requires bf16-resident weights".into()
20674 );
20675 }
20676 };
20677 let n_ff_sh = m
20678 .gate_shexp
20679 .as_ref()
20680 .expect("matched Some above")
20681 .out_features();
20682 let gate_inp_shexp = m.gate_inp_shexp.as_ref();
20685 crate::tp::graph_section(e, None, || {
20686 let _main = e.gpu.enter_main()?;
20687 let (z_ref, out_stage) = routes_ws
20688 .in_and_out_stages_mut()
20689 .ok_or("routes stages not armed")?;
20690 let Step35TokenGraphState {
20691 x,
20692 x1,
20693 sh_stage,
20694 shexp_gate,
20695 shexp_up,
20696 shexp_act,
20697 gate_sig,
20698 ..
20699 } = &mut *state;
20700 e.matvec_bf16_dual_into(
20701 wg_sh, wu_sh, z_ref, shexp_gate, shexp_up, n_embd, n_ff_sh,
20702 )?;
20703 Self::ffn_act_lim(
20704 e, &self.cfg, shexp_gate, shexp_up, 1.0, 1.0, lim_sh, shexp_act,
20705 n_ff_sh,
20706 )?;
20707 e.matvec_bf16_into(wd_sh, shexp_act, sh_stage, n_ff_sh, n_embd)?;
20708 if let Some(gate_w) = gate_inp_shexp {
20709 e.sigmoid_dot_rows_into(
20710 z_ref,
20711 gate_w.float_data(),
20712 gate_sig,
20713 n_embd,
20714 1,
20715 )?;
20716 }
20717 e.add_scaled_rows(sh_stage, gate_sig, out_stage, n_embd, 1)?;
20718 e.add(x1, out_stage, x, n_embd)?;
20719 Ok(())
20720 })?;
20721 }
20722 }
20723 if probe_layer == Some(il) {
20724 let Step35TokenGraphState { x, probe_x, .. } = &mut *state;
20725 crate::tp::graph_section(e, None, || {
20726 let _main = e.gpu.enter_main()?;
20727 let mut dst = probe_x.slice_mut(0..n_embd);
20728 e.stream().memcpy_dtod(&x.slice(0..n_embd), &mut dst)?;
20729 Ok(())
20730 })?;
20731 }
20732 }
20733
20734 let head = match &self.output {
20736 crate::model::GpuTensor::FloatBf16 { data, .. } => data,
20737 _ => return Err("step35 token graph head requires the bf16-resident output".into()),
20738 };
20739 crate::tp::graph_section(e, None, || {
20740 let _main = e.gpu.enter_main()?;
20741 let Step35TokenGraphState {
20742 x,
20743 hn,
20744 logits_stage,
20745 token_d,
20746 pos_d,
20747 token_hist,
20748 hist_idx,
20749 ..
20750 } = &mut *state;
20751 e.rms_norm(x, self.output_norm.float_data(), hn, n_embd, 1, eps)?;
20752 e.matvec_bf16_into(head, hn, logits_stage, n_embd, self.cfg.n_vocab as usize)?;
20753 e.argmax_token_device_into(logits_stage, token_d, self.cfg.n_vocab as usize)?;
20759 e.u32_hist_append(token_d, token_hist, hist_idx)?;
20760 e.inc_i32(pos_d)?;
20761 Ok(())
20762 })?;
20763
20764 let graph = crate::tp::token_graph_build_finish()?;
20765 state.graphs.push((bucket_max, graph));
20766 eprintln!(
20767 "[step35-token-graph] built bucket={bucket_max} layers={n_layers} \
20768 build_ms={:.0} performance_claim=false",
20769 started.elapsed().as_secs_f64() * 1e3
20770 );
20771 Ok(())
20772 }
20773}