1use crate::Engine;
9use crate::mmq_ffi::{DeviceExpertCsr, ExpertCsr, Fp8GroupedWorkspace};
10use crate::parallel::{PRODUCT_MAX_CARDS, STEP37_TRUNK_LAYERS};
11use cudarc::driver::{CudaEvent, CudaSlice, DeviceSlice};
12use std::ops::Range;
13
14#[allow(clippy::type_complexity)] static DETERM_PREV: std::sync::OnceLock<
18 std::sync::Mutex<std::collections::HashMap<(usize, usize), Vec<f32>>>,
19> = std::sync::OnceLock::new();
20
21const FP8_BLOCK: usize = 128;
22const NATIVE_P2P_PROBE_WORDS: &[usize] = &[4096, 16_384, 262_144, 16_777_216];
23const STEP_GROUPED_FP8_EXPERTS: usize = 288;
24const STEP_GROUPED_FP8_TOP_K: usize = 8;
25const STEP_GROUPED_FP8_WIDTH: usize = 1280;
26
27fn validate_step_expert_activation_limit(limit: Option<f32>) -> Result<(), String> {
28 if let Some(limit) = limit
29 && (!limit.is_finite() || limit <= 0.0)
30 {
31 return Err(format!(
32 "Step routed-expert activation limit must be positive and finite, got {limit}"
33 ));
34 }
35 Ok(())
36}
37
38pub(crate) fn routes_prestage_on() -> bool {
61 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
62 *ON.get_or_init(|| std::env::var("MEMRA_ROUTES_PRESTAGE").as_deref() == Ok("1"))
63}
64
65pub(crate) fn oproj_tail_on() -> bool {
83 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
84 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_TAIL").as_deref() == Ok("1"))
85}
86thread_local! {
87 static OPROJ_TAIL_PENDING: std::cell::Cell<Option<(u64, u64)>> =
88 const { std::cell::Cell::new(None) };
89}
90thread_local! {
91 static OPROJ_TAIL_ELIGIBLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
96}
97pub(crate) struct OprojTailScope(());
99pub(crate) fn oproj_tail_scope() -> OprojTailScope {
100 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(true));
101 OprojTailScope(())
102}
103impl Drop for OprojTailScope {
104 fn drop(&mut self) {
105 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(false));
106 OPROJ_TAIL_PENDING.with(|c| c.set(None));
108 }
109}
110thread_local! {
111 static VERIFY_TCOL: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
114}
115pub(crate) fn set_verify_tcol(c: Option<usize>) {
116 VERIFY_TCOL.with(|x| x.set(c));
117}
118pub(crate) fn take_verify_tcol() -> Option<usize> {
119 VERIFY_TCOL.with(|x| x.take())
120}
121
122pub(crate) fn tcol_oproj_on() -> bool {
129 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
130 *ON.get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ").as_deref() == Ok("1"))
131}
132thread_local! {
133 static TCOL_OPROJ_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
137 static TCOL_OPROJ_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
138}
139pub(crate) fn set_tcol_oproj_defer(c: Option<usize>) {
140 TCOL_OPROJ_DEFER.with(|x| x.set(c));
141}
142pub(crate) fn take_tcol_oproj_defer() -> Option<usize> {
143 TCOL_OPROJ_DEFER.with(|x| x.take())
144}
145pub(crate) fn set_tcol_oproj_stashed() {
146 TCOL_OPROJ_STASHED.with(|x| x.set(true));
147}
148pub(crate) fn take_tcol_oproj_stashed() -> bool {
149 TCOL_OPROJ_STASHED.with(|x| x.replace(false))
150}
151
152pub(crate) fn oproj_tail_eligible() -> bool {
153 OPROJ_TAIL_ELIGIBLE.with(|c| c.get())
154}
155pub(crate) fn take_oproj_tail() -> Option<(u64, u64)> {
156 OPROJ_TAIL_PENDING.with(|c| c.take())
157}
158pub(crate) fn set_oproj_tail(v: (u64, u64)) {
159 OPROJ_TAIL_PENDING.with(|c| c.set(Some(v)));
160}
161
162pub(crate) fn rank0_merge_on() -> bool {
163 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
164 *ON.get_or_init(|| std::env::var("MEMRA_RANK0_MERGE").as_deref() == Ok("1"))
165}
166
167pub(crate) fn len_mirror_lazy_on() -> bool {
168 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
169 *ON.get_or_init(|| std::env::var("MEMRA_LEN_MIRROR_LAZY").as_deref() == Ok("1"))
170}
171
172pub(crate) fn fence_memops_on() -> bool {
173 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
174 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_MEMOPS").as_deref() == Ok("1"))
175}
176
177pub(crate) fn moe_direct_on() -> bool {
178 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
179 *ON.get_or_init(|| std::env::var("MEMRA_MOE_DIRECT").as_deref() == Ok("1"))
180}
181
182pub(crate) fn fence_rank1_on() -> bool {
194 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
195 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_RANK1").as_deref() == Ok("1"))
196}
197
198pub(crate) fn rows_tab_host(
222 parts_rank: &[[u64; 4]],
223 ctr_base: u64,
224 same_session: bool,
225 t: usize,
226) -> Vec<u64> {
227 let mut host = Vec::with_capacity(t * 6);
228 for (r, parts) in parts_rank.iter().enumerate().take(t) {
229 host.extend_from_slice(&[
230 parts[0],
231 parts[1],
232 parts[2],
233 parts[3],
234 if same_session {
235 ctr_base
236 } else {
237 ctr_base + (r as u64) * 4
238 },
239 if same_session {
240 (t - 1 - r) as u64
241 } else {
242 0u64
243 },
244 ]);
245 }
246 host
247}
248
249#[cfg(test)]
253pub(crate) fn retired_rows_tab_key(kp: u64, bp: u64, il: usize, t: usize) -> u64 {
254 kp.rotate_left(17)
255 .wrapping_add(bp)
256 .wrapping_add((il as u64) << 32)
257 .wrapping_add(t as u64)
258 .wrapping_add(1 << 63)
259}
260
261pub(crate) fn rows_tab_restage_on() -> bool {
262 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
263 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_RESTAGE").as_deref() != Ok("0"))
264}
265
266pub(crate) fn rows_tab_stale_scan() -> bool {
275 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
276 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_STALE_SCAN").as_deref() == Ok("1"))
277}
278
279pub(crate) static ROWS_TAB_ENGAGED: std::sync::atomic::AtomicU64 =
280 std::sync::atomic::AtomicU64::new(0);
281pub(crate) static ROWS_TAB_STALE: std::sync::atomic::AtomicU64 =
282 std::sync::atomic::AtomicU64::new(0);
283
284pub(crate) fn spec_fa2_on() -> bool {
285 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
286 crate::step37_door(&ENV, "MEMRA_SPEC_FA2")
287}
288thread_local! {
289 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
292 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
293}
294pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
295 SPEC_FA2_DEFER.with(|x| x.set(c));
296}
297pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
298 SPEC_FA2_DEFER.with(|x| x.take())
299}
300pub(crate) fn set_spec_fa2_stashed() {
301 SPEC_FA2_STASHED.with(|x| x.set(true));
302}
303pub(crate) fn take_spec_fa2_stashed() -> bool {
304 SPEC_FA2_STASHED.with(|x| x.replace(false))
305}
306
307pub(crate) fn sel_mirror_on() -> bool {
308 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
309 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
310}
311
312pub(crate) fn step_nvfp4_ep2_on() -> bool {
319 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
320 *ON.get_or_init(|| std::env::var("MEMRA_STEP_NVFP4_EP2").as_deref() == Ok("1"))
321}
322
323pub(crate) fn oproj_direct_on() -> bool {
324 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
325 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
326}
327
328fn door_default_on(name: &'static str) -> (bool, &'static str) {
367 let raw = std::env::var(name).ok();
368 door_default_on_value(name, raw.as_deref())
369}
370
371fn door_default_on_value(name: &str, value: Option<&str>) -> (bool, &'static str) {
376 match value {
377 Some("0") => (false, "env=0 (rollback seam)"),
378 Some("1") => (true, "env=1"),
379 None => (true, "default-on"),
380 Some(_) => {
381 eprintln!(
382 "[nvfp4-door] WARN {name} has an unrecognized value; only `0` and `1` are \
383 accepted and the DEFAULT-ON answer is kept. To roll back, set {name}=0."
384 );
385 (true, "default-on (unrecognized value ignored)")
386 }
387 }
388}
389
390pub(crate) fn bank_slot_major_on() -> bool {
410 bank_slot_major_source().0
411}
412
413pub(crate) fn bank_slot_major_source() -> (bool, &'static str) {
415 static ON: std::sync::OnceLock<(bool, &'static str)> = std::sync::OnceLock::new();
416 *ON.get_or_init(|| door_default_on("MEMRA_NVFP4_BANK_SM"))
417}
418
419pub(crate) fn sel_gu_fused_on() -> bool {
428 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
429 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_SEL_GU").as_deref() == Ok("1"))
430}
431
432pub(crate) fn sel_down8_on() -> bool {
456 sel_down8_source().0
457}
458
459pub(crate) fn sel_down8_source() -> (bool, &'static str) {
461 static ON: std::sync::OnceLock<(bool, &'static str)> = std::sync::OnceLock::new();
462 *ON.get_or_init(|| door_default_on("MEMRA_NVFP4_SEL_DOWN8"))
463}
464
465pub(crate) fn raw_copy_bytes(
466 dst: u64,
467 src: u64,
468 bytes: usize,
469 engine: &Engine,
470) -> Result<(), Box<dyn std::error::Error>> {
471 use cudarc::driver::sys;
472 let r = unsafe {
473 sys::cuMemcpyAsync(
474 dst as sys::CUdeviceptr,
475 src as sys::CUdeviceptr,
476 bytes,
477 engine.stream().cu_stream() as sys::CUstream,
478 )
479 };
480 if r == sys::CUresult::CUDA_SUCCESS {
481 Ok(())
482 } else {
483 if std::env::var("MEMRA_RAW_COPY_TRACE").as_deref() == Ok("1") {
486 eprintln!(
487 "[raw-copy-fail] dst={dst:#x} src={src:#x} bytes={bytes} {r:?}\n{}",
488 std::backtrace::Backtrace::force_capture()
489 );
490 }
491 Err(format!("raw_copy_bytes: {r:?} bytes={bytes} dst={dst:#x} src={src:#x}").into())
492 }
493}
494
495pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
496 let silu = gate / (1.0 + (-gate).exp());
497 match limit {
498 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
499 None => silu * up,
500 }
501}
502
503#[derive(Debug, Clone, PartialEq, Eq)]
504struct ExpertOwnerRoutes {
505 rank: usize,
506 selected: Vec<usize>,
507 token_rows: Vec<usize>,
508 global_pairs: Vec<usize>,
509}
510
511#[allow(clippy::manual_is_multiple_of)] fn partition_expert_owner_routes(
513 expert_count: usize,
514 ranks: usize,
515 tokens: usize,
516 experts_per_token: usize,
517 selected: &[usize],
518) -> Result<Vec<ExpertOwnerRoutes>, String> {
519 if expert_count == 0
520 || ranks == 0
521 || tokens == 0
522 || experts_per_token == 0
523 || expert_count % ranks != 0
524 {
525 return Err(format!(
526 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
527 tokens={tokens} experts_per_token={experts_per_token}"
528 ));
529 }
530 let pairs = tokens
531 .checked_mul(experts_per_token)
532 .ok_or("expert-owner route count overflow")?;
533 if selected.len() != pairs {
534 return Err(format!(
535 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
536 selected.len()
537 ));
538 }
539 let per_rank = expert_count / ranks;
540 let mut owners = (0..ranks)
541 .map(|rank| ExpertOwnerRoutes {
542 rank,
543 selected: Vec::new(),
544 token_rows: Vec::new(),
545 global_pairs: Vec::new(),
546 })
547 .collect::<Vec<_>>();
548 for (pair, &expert) in selected.iter().enumerate() {
549 if expert >= expert_count {
550 return Err(format!(
551 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
552 ));
553 }
554 let rank = expert / per_rank;
555 owners[rank].selected.push(expert - rank * per_rank);
556 owners[rank].token_rows.push(pair / experts_per_token);
557 owners[rank].global_pairs.push(pair);
558 }
559 Ok(owners)
560}
561
562fn validate_step_grouped_owner_routes(
563 expert_count: usize,
564 tokens: usize,
565 selected: &[usize],
566) -> Result<usize, String> {
567 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
568 return Err(format!(
569 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
570 experts={expert_count} tokens={tokens}",
571 STEP_GROUPED_FP8_EXPERTS
572 ));
573 }
574 let pairs = tokens
575 .checked_mul(STEP_GROUPED_FP8_TOP_K)
576 .ok_or("official Step owner-grouped FP8 route count overflow")?;
577 if selected.len() != pairs {
578 return Err(format!(
579 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
580 selected.len(),
581 STEP_GROUPED_FP8_TOP_K,
582 ));
583 }
584 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
585 let mut unique = routes.to_vec();
586 unique.sort_unstable();
587 unique.dedup();
588 if unique.len() != STEP_GROUPED_FP8_TOP_K {
589 return Err(format!(
590 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
591 {routes:?}"
592 ));
593 }
594 }
595 Ok(pairs)
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599struct WeightedRouteCombineShape {
600 pairs: usize,
601 max_pairs: usize,
602}
603
604fn validate_weighted_route_combine(
605 width: usize,
606 experts_per_token: usize,
607 max_tokens: usize,
608 tokens: usize,
609 owner_global_pairs: &[&[usize]],
610 route_weights: &[f32],
611) -> Result<WeightedRouteCombineShape, String> {
612 if width == 0
613 || experts_per_token == 0
614 || max_tokens == 0
615 || tokens == 0
616 || tokens > max_tokens
617 || width > i32::MAX as usize
618 || experts_per_token > i32::MAX as usize
619 || tokens > i32::MAX as usize
620 {
621 return Err(format!(
622 "invalid weighted route combine geometry width={width} experts_per_token=\
623 {experts_per_token} tokens={tokens}/{max_tokens}"
624 ));
625 }
626 let pairs = tokens
627 .checked_mul(experts_per_token)
628 .ok_or("weighted route combine pair count overflow")?;
629 let max_pairs = max_tokens
630 .checked_mul(experts_per_token)
631 .ok_or("weighted route combine capacity overflow")?;
632 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
633 return Err(format!(
634 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
635 route_weights.len()
636 ));
637 }
638 let mut seen = vec![false; pairs];
639 let mut observed = 0usize;
640 for pairs_for_owner in owner_global_pairs {
641 observed = observed
642 .checked_add(pairs_for_owner.len())
643 .ok_or("weighted route combine observed pair count overflow")?;
644 for &pair in *pairs_for_owner {
645 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
646 return Err(format!(
647 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
648 ));
649 }
650 }
651 }
652 if observed != pairs || seen.iter().any(|present| !present) {
653 return Err(format!(
654 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
655 ));
656 }
657 Ok(WeightedRouteCombineShape { pairs, max_pairs })
658}
659
660fn cache_rank_rows(
661 rows: &[u8],
662 tokens: usize,
663 local_token_bytes: usize,
664 ranks: usize,
665 rank: usize,
666) -> Result<Vec<u8>, String> {
667 if ranks == 0 || rank >= ranks {
668 return Err(format!(
669 "TP cache rank {rank} is outside a {ranks}-rank layout"
670 ));
671 }
672 let global_token_bytes = local_token_bytes
673 .checked_mul(ranks)
674 .ok_or("TP cache global token-byte overflow")?;
675 let expected = tokens
676 .checked_mul(global_token_bytes)
677 .ok_or("TP cache row-byte overflow")?;
678 if rows.len() != expected {
679 return Err(format!(
680 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
681 rows.len()
682 ));
683 }
684 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
685 for token in 0..tokens {
686 let start = token * global_token_bytes + rank * local_token_bytes;
687 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
688 }
689 Ok(shard)
690}
691
692fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
693 match value {
694 None | Some("") | Some("0") => Ok(false),
695 Some("1") => Ok(true),
696 Some(value) => Err(format!(
697 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
698 )),
699 }
700}
701
702pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
703 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
704}
705
706fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
707 match value {
708 None | Some("") | Some("0") => Ok(false),
709 Some("1") => Ok(true),
710 Some(value) => Err(format!(
711 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
712 )),
713 }
714}
715
716pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
717 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
718}
719
720fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
721 match value {
722 None | Some("") | Some("0") => Ok(false),
723 Some("1") => Ok(true),
724 Some(value) => Err(format!(
725 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
726 )),
727 }
728}
729
730fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
731 match value {
732 None | Some("") | Some("0") => Ok(false),
733 Some("1") => Ok(true),
734 Some(value) => Err(format!(
735 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
736 )),
737 }
738}
739
740pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
743 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
744}
745
746pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
747 parse_step_ep_device_arithmetic(
748 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
749 .ok()
750 .as_deref(),
751 )
752}
753
754fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
755 match value {
756 None | Some("") | Some("0") => Ok(false),
757 Some("1") => Ok(true),
758 Some(value) => Err(format!(
759 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
760 )),
761 }
762}
763
764pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
765 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
766}
767
768fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
769 match value {
770 None | Some("") | Some("0") => Ok(false),
771 Some("1") => Ok(true),
772 Some(value) => Err(format!(
773 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
774 )),
775 }
776}
777
778pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
783 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
784}
785
786fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
787 match value {
788 None | Some("") | Some("0") => Ok(false),
789 Some("1") => Ok(true),
790 Some(value) => Err(format!(
791 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
792 )),
793 }
794}
795
796fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
797 match value {
798 None | Some("") | Some("0") => Ok(false),
799 Some("1") => Ok(true),
800 Some(value) => Err(format!(
801 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
802 )),
803 }
804}
805
806pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
810 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
811}
812
813fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
814 match value {
815 None | Some("") | Some("0") => Ok(false),
816 Some("1") => Ok(true),
817 Some(value) => Err(format!(
818 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
819 )),
820 }
821}
822
823fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
824 match value {
825 None | Some("") | Some("0") => Ok(false),
826 Some("1") => Ok(true),
827 Some(value) => Err(format!(
828 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
829 )),
830 }
831}
832
833pub fn step_tp_dcw_enabled() -> Result<bool, String> {
838 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
839}
840
841pub fn step_tp_graph_enabled() -> Result<bool, String> {
846 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
847}
848
849fn step_tp_graph_headroom_ok(e: &Engine) -> bool {
854 let ok = crate::spec::graph_launch_headroom_ok(e);
855 if !ok {
856 static NOTED: std::sync::Once = std::sync::Once::new();
857 NOTED.call_once(|| crate::spec::graph_replay_suspended_note("step-tp-routes"));
858 }
859 ok
860}
861
862pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
866 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
867}
868
869#[derive(Debug, Clone, PartialEq, Eq)]
870pub struct StepEpLayerSpec {
871 pub layer: usize,
872 pub devices: Vec<usize>,
873}
874
875pub type StepTpLayerSpec = StepEpLayerSpec;
876
877fn parse_auto_parallel_devices(
881 mode: Option<&str>,
882 raw_devices: Option<&str>,
883) -> Result<Option<Vec<usize>>, String> {
884 let mode = match mode {
885 None | Some("") | Some("0") | Some("off") => return Ok(None),
886 Some("auto") => "auto",
887 Some(value) => {
888 return Err(format!(
889 "MEMRA_PARALLEL={value:?} is invalid; expected off or auto"
890 ));
891 }
892 };
893 let raw = raw_devices.ok_or_else(|| {
894 format!("{mode} parallel placement requires MEMRA_PARALLEL_DEVICES=DEVICE,DEVICE[...]")
895 })?;
896 let devices =
897 raw.split(',')
898 .map(|device| {
899 device.trim().parse::<usize>().map_err(|_| {
900 format!("MEMRA_PARALLEL_DEVICES entry {device:?} is not an integer")
901 })
902 })
903 .collect::<Result<Vec<_>, _>>()?;
904 if !(2..=crate::parallel::AUTO_PARALLEL_MAX_CARDS).contains(&devices.len()) {
905 return Err(format!(
906 "MEMRA_PARALLEL=auto requires 2..={} devices, got {}",
907 crate::parallel::AUTO_PARALLEL_MAX_CARDS,
908 devices.len()
909 ));
910 }
911 let mut unique = devices.clone();
912 unique.sort_unstable();
913 unique.dedup();
914 if unique.len() != devices.len() {
915 return Err(format!(
916 "MEMRA_PARALLEL_DEVICES must be distinct, got {devices:?}"
917 ));
918 }
919 Ok(Some(devices))
920}
921
922pub fn auto_parallel_devices() -> Result<Option<Vec<usize>>, String> {
923 parse_auto_parallel_devices(
924 std::env::var("MEMRA_PARALLEL").ok().as_deref(),
925 std::env::var("MEMRA_PARALLEL_DEVICES").ok().as_deref(),
926 )
927}
928
929fn parse_parallel_ep_device_router(value: Option<&str>) -> Result<bool, String> {
930 match value {
931 None | Some("") | Some("0") => Ok(false),
932 Some("1") => Ok(true),
933 Some(value) => Err(format!(
934 "MEMRA_PARALLEL_EP_DEVICE_ROUTER={value:?} is invalid; expected 0 or 1"
935 )),
936 }
937}
938
939pub fn parallel_ep_device_router_enabled() -> Result<bool, String> {
940 parse_parallel_ep_device_router(
941 std::env::var("MEMRA_PARALLEL_EP_DEVICE_ROUTER")
942 .ok()
943 .as_deref(),
944 )
945}
946
947fn parse_parallel_ep_graph(value: Option<&str>) -> Result<bool, String> {
948 match value {
949 None | Some("") | Some("0") => Ok(false),
950 Some("1") => Ok(true),
951 Some(value) => Err(format!(
952 "MEMRA_PARALLEL_EP_GRAPH={value:?} is invalid; expected 0 or 1"
953 )),
954 }
955}
956
957pub fn parallel_ep_graph_enabled() -> Result<bool, String> {
958 parse_parallel_ep_graph(std::env::var("MEMRA_PARALLEL_EP_GRAPH").ok().as_deref())
959}
960
961fn parse_parallel_ep_pair_down(value: Option<&str>) -> Result<bool, String> {
962 match value {
963 None | Some("") | Some("0") => Ok(false),
964 Some("1") => Ok(true),
965 Some(value) => Err(format!(
966 "MEMRA_PARALLEL_EP_PAIR_DOWN={value:?} is invalid; expected 0 or 1"
967 )),
968 }
969}
970
971pub fn parallel_ep_pair_down_enabled() -> Result<bool, String> {
972 parse_parallel_ep_pair_down(std::env::var("MEMRA_PARALLEL_EP_PAIR_DOWN").ok().as_deref())
973}
974
975fn parse_parallel_ep_q8_act(value: Option<&str>) -> Result<bool, String> {
976 match value {
977 None | Some("") | Some("0") => Ok(false),
978 Some("1") => Ok(true),
979 Some(value) => Err(format!(
980 "MEMRA_PARALLEL_EP_Q8_ACT={value:?} is invalid; expected 0 or 1"
981 )),
982 }
983}
984
985pub fn parallel_ep_q8_act_enabled() -> Result<bool, String> {
986 parse_parallel_ep_q8_act(std::env::var("MEMRA_PARALLEL_EP_Q8_ACT").ok().as_deref())
987}
988
989#[derive(Clone, Copy, Debug, PartialEq, Eq)]
990pub(crate) enum ParallelEpQ8Scope {
991 All,
992 GateUp,
993 Down,
994}
995
996impl ParallelEpQ8Scope {
997 fn label(self) -> &'static str {
998 match self {
999 Self::All => "all",
1000 Self::GateUp => "gate-up",
1001 Self::Down => "down",
1002 }
1003 }
1004}
1005
1006fn parse_parallel_ep_q8_scope(value: Option<&str>) -> Result<Option<ParallelEpQ8Scope>, String> {
1007 match value {
1008 None | Some("") => Ok(None),
1009 Some("all") => Ok(Some(ParallelEpQ8Scope::All)),
1010 Some("gate-up") => Ok(Some(ParallelEpQ8Scope::GateUp)),
1011 Some("down") => Ok(Some(ParallelEpQ8Scope::Down)),
1012 Some(value) => Err(format!(
1013 "MEMRA_PARALLEL_EP_Q8_SCOPE={value:?} is invalid; expected all, gate-up, or down"
1014 )),
1015 }
1016}
1017
1018pub(crate) fn parallel_ep_q8_scope() -> Result<Option<ParallelEpQ8Scope>, String> {
1019 parse_parallel_ep_q8_scope(std::env::var("MEMRA_PARALLEL_EP_Q8_SCOPE").ok().as_deref())
1020}
1021
1022fn parse_step_layer_specs(
1023 flag: &str,
1024 value: Option<&str>,
1025 allow_full_model: bool,
1026) -> Result<Vec<StepEpLayerSpec>, String> {
1027 let trunk = allow_full_model.then_some(STEP37_TRUNK_LAYERS);
1028 parse_layer_specs_for_trunk(flag, value, trunk)
1029}
1030
1031pub(crate) fn refuse_door_composition(
1039 primary: &str,
1040 table: &[(&str, &str)],
1041 armed: impl Fn(&str) -> bool,
1042) -> Result<(), String> {
1043 for (flag, why) in table {
1044 if armed(flag) {
1045 return Err(format!(
1046 "{primary} + {flag}: unproven composition, refused ({why})"
1047 ));
1048 }
1049 }
1050 Ok(())
1051}
1052
1053pub(crate) fn parse_layer_specs_for_trunk(
1058 flag: &str,
1059 value: Option<&str>,
1060 full_model_trunk: Option<usize>,
1061) -> Result<Vec<StepEpLayerSpec>, String> {
1062 let Some(value) = value else {
1063 return Ok(Vec::new());
1064 };
1065 if value.is_empty() || value == "0" {
1066 return Ok(Vec::new());
1067 }
1068
1069 let mut specs = Vec::new();
1070 for item in value.split(';') {
1071 let (layers, devices) = item.split_once('@').ok_or_else(|| {
1072 let layers = if full_model_trunk.is_some() {
1073 "LAYER[-LAYER] or all"
1074 } else {
1075 "LAYER[-LAYER]"
1076 };
1077 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
1078 })?;
1079 let (first, last) = if layers == "all" {
1080 let Some(trunk) = full_model_trunk else {
1081 return Err(format!(
1082 "{flag} does not support the full-model shorthand; assign routed layers \
1083 explicitly"
1084 ));
1085 };
1086 (0, trunk - 1)
1087 } else {
1088 match layers.split_once('-') {
1089 Some((first, last)) => {
1090 let first = first
1091 .parse::<usize>()
1092 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
1093 let last = last
1094 .parse::<usize>()
1095 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
1096 if first > last {
1097 return Err(format!("{flag} layer range {first}-{last} is reversed"));
1098 }
1099 if last - first + 1 > 128 {
1100 return Err(format!(
1101 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
1102 ));
1103 }
1104 (first, last)
1105 }
1106 None => {
1107 let layer = layers
1108 .parse::<usize>()
1109 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
1110 (layer, layer)
1111 }
1112 }
1113 };
1114 let devices = devices
1115 .split(',')
1116 .map(|device| {
1117 device
1118 .parse::<usize>()
1119 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
1120 })
1121 .collect::<Result<Vec<_>, _>>()?;
1122 if !(2..=8).contains(&devices.len()) {
1123 return Err(format!(
1124 "{flag} requires 2..=8 devices, got {}",
1125 devices.len()
1126 ));
1127 }
1128 let mut unique = devices.clone();
1129 unique.sort_unstable();
1130 unique.dedup();
1131 if unique.len() != devices.len() {
1132 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
1133 }
1134 for layer in first..=last {
1135 if specs
1136 .iter()
1137 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
1138 {
1139 return Err(format!("{flag} assigns layer {layer} more than once"));
1140 }
1141 specs.push(StepEpLayerSpec {
1142 layer,
1143 devices: devices.clone(),
1144 });
1145 }
1146 }
1147 Ok(specs)
1148}
1149
1150pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
1151 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
1152}
1153
1154pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
1155 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
1156}
1157
1158pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
1159 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
1160}
1161
1162pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
1163 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
1164}
1165
1166#[derive(Clone, Copy)]
1167pub struct E4m3BlockMatrix<'a> {
1168 pub codes: &'a [u8],
1169 pub scales: &'a [f32],
1170 pub out_features: usize,
1171 pub in_features: usize,
1172}
1173
1174impl E4m3BlockMatrix<'_> {
1175 fn validate(&self) -> Result<(), String> {
1176 let code_count = self
1177 .out_features
1178 .checked_mul(self.in_features)
1179 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
1180 if self.codes.len() != code_count {
1181 return Err(format!(
1182 "E4M3 code count {} != {}x{} ({code_count})",
1183 self.codes.len(),
1184 self.out_features,
1185 self.in_features,
1186 ));
1187 }
1188 let scale_count =
1189 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1190 if self.scales.len() != scale_count {
1191 return Err(format!(
1192 "E4M3 scale count {} != {scale_count} for {}x{}",
1193 self.scales.len(),
1194 self.out_features,
1195 self.in_features,
1196 ));
1197 }
1198 if !self
1199 .scales
1200 .iter()
1201 .all(|scale| scale.is_finite() && *scale > 0.0)
1202 {
1203 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
1204 }
1205 Ok(())
1206 }
1207}
1208
1209#[derive(Clone, Copy)]
1210pub struct E4m3ExpertBank<'a> {
1211 pub codes: &'a [u8],
1212 pub scales: &'a [f32],
1213 pub expert_count: usize,
1214 pub out_features: usize,
1215 pub in_features: usize,
1216}
1217
1218impl E4m3ExpertBank<'_> {
1219 fn validate(&self) -> Result<(), String> {
1220 if self.expert_count == 0 {
1221 return Err("E4M3 expert bank is empty".to_string());
1222 }
1223 let code_stride = self
1224 .out_features
1225 .checked_mul(self.in_features)
1226 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
1227 let code_count = self
1228 .expert_count
1229 .checked_mul(code_stride)
1230 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
1231 if self.codes.len() != code_count {
1232 return Err(format!(
1233 "E4M3 expert code count {} != {}x{} ({code_count})",
1234 self.codes.len(),
1235 self.expert_count,
1236 code_stride,
1237 ));
1238 }
1239 let scale_stride =
1240 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1241 let scale_count = self
1242 .expert_count
1243 .checked_mul(scale_stride)
1244 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
1245 if self.scales.len() != scale_count {
1246 return Err(format!(
1247 "E4M3 expert scale count {} != {}x{} ({scale_count})",
1248 self.scales.len(),
1249 self.expert_count,
1250 scale_stride,
1251 ));
1252 }
1253 if !self
1254 .scales
1255 .iter()
1256 .all(|scale| scale.is_finite() && *scale > 0.0)
1257 {
1258 return Err(
1259 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
1260 );
1261 }
1262 Ok(())
1263 }
1264
1265 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
1266 if expert >= self.expert_count {
1267 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
1268 }
1269 let code_stride = self.out_features * self.in_features;
1270 let scale_stride =
1271 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1272 Ok(E4m3BlockMatrix {
1273 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
1274 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
1275 out_features: self.out_features,
1276 in_features: self.in_features,
1277 })
1278 }
1279}
1280
1281pub struct ColumnParallelResult {
1282 pub gathered: Vec<f32>,
1283 pub rank_outputs: Vec<Vec<f32>>,
1284}
1285
1286pub struct RowParallelResult {
1287 pub reduced: Vec<f32>,
1288 pub rank_partials: Vec<Vec<f32>>,
1289}
1290
1291#[derive(Clone, Copy)]
1292pub struct Bf16Matrix<'a> {
1293 pub bytes: &'a [u8],
1294 pub out_features: usize,
1295 pub in_features: usize,
1296}
1297
1298impl Bf16Matrix<'_> {
1299 pub fn validate(&self) -> Result<(), String> {
1300 if self.out_features == 0 || self.in_features == 0 {
1301 return Err("BF16 matrix dimensions must be nonzero".into());
1302 }
1303 let expected = self
1304 .out_features
1305 .checked_mul(self.in_features)
1306 .and_then(|values| values.checked_mul(2))
1307 .ok_or("BF16 matrix byte count overflow")?;
1308 if self.bytes.len() != expected {
1309 return Err(format!(
1310 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
1311 self.bytes.len(),
1312 self.out_features,
1313 self.in_features,
1314 ));
1315 }
1316 Ok(())
1317 }
1318}
1319
1320struct ResidentE4m3Rank {
1321 codes: CudaSlice<u8>,
1322 scales: CudaSlice<f32>,
1323 out_features: usize,
1324 in_features: usize,
1325}
1326
1327enum ResidentBf16Weight {
1328 Bf16(CudaSlice<u8>),
1329 F32(CudaSlice<f32>),
1330}
1331
1332impl ResidentBf16Weight {
1333 fn ordinal(&self) -> usize {
1334 match self {
1335 Self::Bf16(bytes) => bytes.ordinal(),
1336 Self::F32(values) => values.ordinal(),
1337 }
1338 }
1339}
1340
1341struct ResidentBf16Rank {
1342 weight: ResidentBf16Weight,
1343 out_features: usize,
1344 in_features: usize,
1345 q8: Option<CudaSlice<u8>>,
1348}
1349
1350pub struct ResidentColumnParallel {
1351 ranks: Vec<ResidentE4m3Rank>,
1352 out_features: usize,
1353 in_features: usize,
1354}
1355
1356pub struct ResidentRowParallel {
1357 ranks: Vec<ResidentE4m3Rank>,
1358 out_features: usize,
1359 in_features: usize,
1360}
1361
1362pub struct ResidentBf16ColumnParallel {
1363 ranks: Vec<ResidentBf16Rank>,
1364 out_features: usize,
1365 in_features: usize,
1366 canonical_chunk_rows: Option<usize>,
1367}
1368
1369pub struct ResidentBf16RowParallel {
1370 ranks: Vec<ResidentBf16Rank>,
1371 out_features: usize,
1372 in_features: usize,
1373}
1374
1375pub struct ResidentStepBf16RowParallel {
1376 ranks: Vec<Vec<ResidentBf16Rank>>,
1377 out_features: usize,
1378 in_features: usize,
1379 canonical_chunk_cols: usize,
1380}
1381
1382pub struct ResidentSigmoidTopKRouter {
1384 weight: CudaSlice<f32>,
1385 correction_bias: CudaSlice<f32>,
1386 active: CudaSlice<u8>,
1387 root_device: usize,
1388 input_width: usize,
1389 expert_count: usize,
1390 experts_per_token: usize,
1391 active_count: usize,
1392 scaling_factor: f32,
1393 route_norm: bool,
1394}
1395
1396pub struct SigmoidTopKHostOutput {
1397 pub logits: Vec<f32>,
1398 pub selected: Vec<u32>,
1399 pub weights: Vec<f32>,
1400}
1401
1402pub struct ResidentReplicatedBf16SwiGlu {
1404 gate: Vec<ResidentBf16Rank>,
1405 up: Vec<ResidentBf16Rank>,
1406 down: Vec<ResidentBf16Rank>,
1407 input_width: usize,
1408 intermediate_width: usize,
1409}
1410
1411pub struct ResidentReplicatedDeviceRows {
1416 ranks: Vec<CudaSlice<f32>>,
1417 tokens: usize,
1418 width: usize,
1419}
1420
1421impl ResidentReplicatedDeviceRows {
1422 pub fn tokens(&self) -> usize {
1423 self.tokens
1424 }
1425
1426 pub fn width(&self) -> usize {
1427 self.width
1428 }
1429
1430 pub fn ranks(&self) -> usize {
1431 self.ranks.len()
1432 }
1433}
1434
1435pub fn moe_residual_host(
1437 residual: &[f32],
1438 routed: &[f32],
1439 shared: &[f32],
1440) -> Result<Vec<f32>, String> {
1441 if residual.len() != routed.len() || residual.len() != shared.len() {
1442 return Err(format!(
1443 "MoE residual lengths residual={} routed={} shared={}",
1444 residual.len(),
1445 routed.len(),
1446 shared.len()
1447 ));
1448 }
1449 let ffn = routed
1450 .iter()
1451 .zip(shared)
1452 .map(|(&routed, &shared)| routed + shared)
1453 .collect::<Vec<_>>();
1454 Ok(residual
1455 .iter()
1456 .zip(ffn)
1457 .map(|(&residual, ffn)| residual + ffn)
1458 .collect())
1459}
1460
1461pub use memra_kv::{
1462 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1463};
1464
1465pub struct ResidentTpExpert {
1471 gate: ResidentColumnParallel,
1472 up: ResidentColumnParallel,
1473 down: ResidentRowParallel,
1474 input_width: usize,
1475 expert_width: usize,
1476}
1477
1478struct ResidentE4m3ExpertBankRank {
1479 codes: CudaSlice<u8>,
1480 scales: CudaSlice<f32>,
1481 expert_range: Range<usize>,
1482 out_features: usize,
1483 in_features: usize,
1484 code_stride: usize,
1485 scale_stride: usize,
1486 k_blocks: Option<usize>,
1489}
1490
1491struct PackedE4m3ExpertBankRank {
1492 codes: Vec<u8>,
1493 scales: Vec<f32>,
1494 expert_range: Range<usize>,
1495 out_features: usize,
1496 in_features: usize,
1497 code_stride: usize,
1498 scale_stride: usize,
1499 k_blocks: Option<usize>,
1500}
1501
1502struct ResidentEpRank {
1503 gate: ResidentE4m3ExpertBankRank,
1504 up: ResidentE4m3ExpertBankRank,
1505 down: ResidentE4m3ExpertBankRank,
1506}
1507
1508pub struct ResidentExpertParallel {
1515 ranks: Vec<ResidentEpRank>,
1516 expert_count: usize,
1517 input_width: usize,
1518 expert_width: usize,
1519}
1520
1521pub struct StepGroupedFp8ProjectionOutput {
1526 pub gate: Vec<f32>,
1527 pub up: Vec<f32>,
1528 pub down: Vec<f32>,
1529}
1530
1531pub struct PreparedStepGroupedFp8Gate {
1536 device: usize,
1537 gate: ResidentE4m3ExpertBankRank,
1538 up: ResidentE4m3ExpertBankRank,
1539 down: ResidentE4m3ExpertBankRank,
1540 input: CudaSlice<f32>,
1541 route_csr: DeviceExpertCsr,
1542 down_csr: DeviceExpertCsr,
1543 gate_workspace: Fp8GroupedWorkspace,
1544 up_workspace: Fp8GroupedWorkspace,
1545 down_workspace: Fp8GroupedWorkspace,
1546 activation: CudaSlice<f32>,
1547 activation_limit: Option<f32>,
1548 tokens: usize,
1549 pairs: usize,
1550}
1551
1552impl PreparedStepGroupedFp8Gate {
1553 pub fn tokens(&self) -> usize {
1554 self.tokens
1555 }
1556
1557 pub fn pairs(&self) -> usize {
1558 self.pairs
1559 }
1560}
1561
1562struct PreparedStepGroupedExpertOwner {
1563 rank: usize,
1564 global_pairs: Vec<usize>,
1565 route_csr: DeviceExpertCsr,
1566 down_csr: DeviceExpertCsr,
1567 gate_workspace: Fp8GroupedWorkspace,
1568 up_workspace: Fp8GroupedWorkspace,
1569 down_workspace: Fp8GroupedWorkspace,
1570 activation: CudaSlice<f32>,
1571}
1572
1573struct StepGroupedExpertOwnerSchedule {
1574 global_pairs: Vec<usize>,
1575 route_csr: ExpertCsr,
1576 down_csr: ExpertCsr,
1577}
1578
1579pub struct PreparedStepGroupedExpertParallelGate {
1585 rank_inputs: Vec<CudaSlice<f32>>,
1586 owners: Vec<PreparedStepGroupedExpertOwner>,
1587 activation_limit: Option<f32>,
1588 tokens: usize,
1589 pairs: usize,
1590 max_tokens: usize,
1591 max_pairs: usize,
1592 input_width: usize,
1593 expert_width: usize,
1594 generation: u64,
1595 executed_generation: Option<u64>,
1596 ready: bool,
1597}
1598
1599impl PreparedStepGroupedExpertParallelGate {
1600 pub fn tokens(&self) -> usize {
1601 self.tokens
1602 }
1603
1604 pub fn pairs(&self) -> usize {
1605 self.pairs
1606 }
1607
1608 pub fn max_tokens(&self) -> usize {
1609 self.max_tokens
1610 }
1611
1612 pub fn input_width(&self) -> usize {
1613 self.input_width
1614 }
1615
1616 pub fn expert_width(&self) -> usize {
1617 self.expert_width
1618 }
1619
1620 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1621 validate_step_expert_activation_limit(limit)?;
1622 self.activation_limit = limit;
1623 self.executed_generation = None;
1624 Ok(())
1625 }
1626
1627 pub fn active_owners(&self) -> usize {
1628 self.owners
1629 .iter()
1630 .filter(|owner| !owner.global_pairs.is_empty())
1631 .count()
1632 }
1633
1634 pub fn owner_pair_counts(&self) -> Vec<usize> {
1635 self.owners
1636 .iter()
1637 .map(|owner| owner.global_pairs.len())
1638 .collect()
1639 }
1640
1641 pub fn generation(&self) -> u64 {
1642 self.generation
1643 }
1644}
1645
1646struct PreparedPeerWeightedRouteOwner {
1647 token_rows: CudaSlice<i32>,
1648 slots: CudaSlice<i32>,
1649 weights: CudaSlice<f32>,
1650 active_pairs: usize,
1651}
1652
1653pub struct PreparedPeerWeightedRouteCombine {
1659 root_device: usize,
1660 owners: Vec<PreparedPeerWeightedRouteOwner>,
1661 peer_staging: CudaSlice<f32>,
1662 slots: CudaSlice<f32>,
1663 weights: CudaSlice<f32>,
1664 output: CudaSlice<f32>,
1665 peer_devices: Vec<usize>,
1666 peer_outputs: Vec<CudaSlice<f32>>,
1667 width: usize,
1668 experts_per_token: usize,
1669 max_tokens: usize,
1670 max_pairs: usize,
1671 tokens: usize,
1672 pairs: usize,
1673 projection_generation: u64,
1674 output_generation: Option<u64>,
1675 broadcast_generation: Option<u64>,
1676 ready: bool,
1677}
1678
1679impl PreparedPeerWeightedRouteCombine {
1680 pub fn tokens(&self) -> usize {
1681 self.tokens
1682 }
1683
1684 pub fn pairs(&self) -> usize {
1685 self.pairs
1686 }
1687
1688 pub fn owner_pair_counts(&self) -> Vec<usize> {
1689 self.owners.iter().map(|owner| owner.active_pairs).collect()
1690 }
1691
1692 pub fn distributed_ranks(&self) -> usize {
1693 1 + self.peer_outputs.len()
1694 }
1695}
1696
1697struct ResidentTpExpertBank {
1698 gate: Vec<ResidentE4m3ExpertBankRank>,
1699 up: Vec<ResidentE4m3ExpertBankRank>,
1700 down: Vec<ResidentE4m3ExpertBankRank>,
1701 expert_count: usize,
1702 input_width: usize,
1703 expert_width: usize,
1704}
1705
1706pub struct ResidentTensorParallel {
1712 bank: ResidentTpExpertBank,
1713}
1714
1715pub struct TpE4m3HostBounce {
1721 devices: Vec<usize>,
1722 ranks: Vec<Engine>,
1723 native_p2p: bool,
1724 ep_device_arithmetic: bool,
1725 bulk_p2p: bool,
1726 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1729}
1730
1731pub enum StepTpGateShards<'a> {
1740 F32(&'a [crate::CudaSlice<f32>]),
1741 Bf16(&'a [crate::CudaSlice<u8>]),
1742}
1743
1744pub struct StepTpDecodeV2Ws {
1745 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1749 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1750 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1751 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1752 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1753 pub(crate) tcol_cap: usize,
1754 w8_aq: Vec<CudaSlice<i8>>,
1758 w8_ad: Vec<CudaSlice<f32>>,
1759 w8_in: usize,
1760 w8o_aq: Vec<CudaSlice<i8>>,
1763 w8o_ad: Vec<CudaSlice<f32>>,
1764 w8o_in: usize,
1765 w8t_aq: Vec<CudaSlice<i8>>,
1769 w8t_ad: Vec<CudaSlice<f32>>,
1770 w8t_in: usize,
1771 w8t_oaq: Vec<CudaSlice<i8>>,
1772 w8t_oad: Vec<CudaSlice<f32>>,
1773 w8t_oin: usize,
1774 w8t_cap: usize,
1775 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1782 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1783 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1784 pub(crate) fa2_cap: usize,
1785 rope_k_t: Vec<CudaSlice<f32>>,
1789 rope_ctr_t: Vec<CudaSlice<u32>>,
1790 rope_pos_t: Vec<CudaSlice<i32>>,
1791 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1795 rows_tab_t: Vec<CudaSlice<u64>>,
1805 rows_tab_shadow: Vec<std::collections::HashMap<u64, Vec<u64>>>,
1809 tcol_gated: Vec<CudaSlice<f32>>,
1810 tcol_opart: Vec<CudaSlice<f32>>,
1811 tcol_opeer: Option<CudaSlice<f32>>,
1812 tcol_omix: Option<CudaSlice<f32>>,
1813 tcol_ocap: usize,
1814 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1817 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1818 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1819 pub(crate) q: Vec<CudaSlice<f32>>,
1820 pub(crate) k: Vec<CudaSlice<f32>>,
1821 pub(crate) pos: Vec<CudaSlice<i32>>,
1822 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1824 pub(crate) gate: Vec<CudaSlice<f32>>,
1825 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1826 pub(crate) gated: Vec<CudaSlice<f32>>,
1827 o_partials: Vec<Vec<CudaSlice<f32>>>,
1829 raw_o_partials: Vec<Vec<u64>>,
1833 raw_k: Vec<u64>,
1834 raw_v_raw: Vec<u64>,
1835 ev_rank: Vec<CudaEvent>,
1837 peer_partial: CudaSlice<f32>,
1839 reduce_a: CudaSlice<f32>,
1840 reduce_b: CudaSlice<f32>,
1841 zeros: CudaSlice<f32>,
1843 pub(crate) k_shadow: CudaSlice<f32>,
1844 pub(crate) v_shadow: CudaSlice<f32>,
1845 ev_refresh: CudaEvent,
1846 ev_oproj: CudaEvent,
1847 gate_e: CudaSlice<f32>,
1849 pub(crate) h_stage: Option<CudaSlice<f32>>,
1852 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1853 attn_in: Vec<CudaSlice<f32>>,
1857 raw_h_stage: u64,
1859 raw_pos_stage: u64,
1860 raw_attn_in: Vec<u64>,
1861 raw_pos: Vec<u64>,
1862 raw_o_partial1: u64,
1863 raw_peer_partial: u64,
1864 raw_k1: u64,
1865 raw_v1: u64,
1866 raw_k_shadow: u64,
1867 raw_v_shadow: u64,
1868 raw_mixed_stage_e: u64,
1872 raw_reduce_a: u64,
1873 raw_shadow_stage_e: (u64, u64),
1874 ev_entry: CudaEvent,
1875 e_device: usize,
1876 local_q_dim: usize,
1878 local_kv_dim: usize,
1879 heads: usize,
1880 pub(crate) o_out: usize,
1881 o_block_cols: usize,
1882 blocks_per_rank: usize,
1883}
1884
1885impl TpE4m3HostBounce {
1886 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1887 Self::new_inner(devices, false, false, false, false)
1888 }
1889
1890 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1891 Self::new_inner(devices, false, true, false, false)
1892 }
1893
1894 pub fn new_native_p2p_device_arithmetic(
1895 devices: &[usize],
1896 ) -> Result<Self, Box<dyn std::error::Error>> {
1897 Self::new_inner(devices, false, true, true, false)
1898 }
1899
1900 pub(crate) fn new_configured(
1901 devices: &[usize],
1902 native_p2p: bool,
1903 ep_device_arithmetic: bool,
1904 bulk_p2p: bool,
1905 ) -> Result<Self, Box<dyn std::error::Error>> {
1906 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1907 }
1908
1909 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1914 Self::new_inner(&[device], true, false, false, false)
1915 }
1916
1917 fn new_inner(
1918 devices: &[usize],
1919 allow_single_rank: bool,
1920 native_p2p: bool,
1921 ep_device_arithmetic: bool,
1922 bulk_p2p: bool,
1923 ) -> Result<Self, Box<dyn std::error::Error>> {
1924 if ep_device_arithmetic && !native_p2p {
1925 return Err("device-resident EP arithmetic requires native P2P".into());
1926 }
1927 if bulk_p2p && !native_p2p {
1928 return Err("bulk TP transport requires native P2P".into());
1929 }
1930 let minimum = if allow_single_rank { 1 } else { 2 };
1931 if !(minimum..=8).contains(&devices.len()) {
1932 return Err(format!(
1933 "TP reference requires {minimum}..=8 devices, got {}",
1934 devices.len()
1935 )
1936 .into());
1937 }
1938 let mut unique = devices.to_vec();
1939 unique.sort_unstable();
1940 unique.dedup();
1941 if unique.len() != devices.len() {
1942 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1943 }
1944 let ranks = devices
1945 .iter()
1946 .map(|&device| Engine::new(device))
1947 .collect::<Result<Vec<_>, _>>()?;
1948 if native_p2p {
1949 configure_native_p2p(&ranks, devices)?;
1950 }
1951 if allow_single_rank {
1952 eprintln!(
1953 "[tp] canonical oracle transport=local device={} performance_claim=false",
1954 devices[0]
1955 );
1956 } else if native_p2p {
1957 if ep_device_arithmetic {
1958 eprintln!(
1959 "[tp] correctness transport=native-p2p devices={devices:?} \
1960 native_p2p=true activation=device-host-exact \
1961 accumulation=device-host-exact output=root-readback \
1962 bulk_p2p={bulk_p2p} performance_claim=false"
1963 );
1964 } else {
1965 eprintln!(
1966 "[tp] correctness transport=native-p2p devices={devices:?} \
1967 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1968 performance_claim=false"
1969 );
1970 }
1971 } else {
1972 eprintln!(
1973 "[tp] correctness transport=host-bounce devices={devices:?} \
1974 native_p2p=false performance_claim=false"
1975 );
1976 }
1977 Ok(Self {
1978 devices: devices.to_vec(),
1979 ranks,
1980 native_p2p,
1981 ep_device_arithmetic,
1982 bulk_p2p,
1983 decode_v2: std::sync::Mutex::new(Vec::new()),
1984 })
1985 }
1986
1987 pub fn devices(&self) -> &[usize] {
1988 &self.devices
1989 }
1990
1991 pub fn native_p2p(&self) -> bool {
1992 self.native_p2p
1993 }
1994
1995 pub fn bulk_p2p(&self) -> bool {
1996 self.bulk_p2p
1997 }
1998
1999 pub fn expert_activation_label(&self) -> &'static str {
2000 if self.ep_device_arithmetic {
2001 "device-host-exact"
2002 } else {
2003 "host-canonical"
2004 }
2005 }
2006
2007 pub fn expert_accumulation_label(&self) -> &'static str {
2008 self.expert_activation_label()
2009 }
2010
2011 pub fn expert_output_label(&self) -> &'static str {
2012 if self.ep_device_arithmetic {
2013 "root-readback"
2014 } else {
2015 "host-accumulated"
2016 }
2017 }
2018
2019 pub fn transport_label(&self) -> &'static str {
2020 if self.devices.len() == 1 {
2021 "local"
2022 } else if self.native_p2p {
2023 "native-p2p"
2024 } else {
2025 "host-bounce"
2026 }
2027 }
2028
2029 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
2030 self.ranks
2031 .iter()
2032 .map(|rank| rank.ctx().name().map_err(Into::into))
2033 .collect()
2034 }
2035
2036 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
2042 self.ranks.get(rank)
2043 }
2044
2045 pub fn allocate_tp_kv_cache(
2046 &self,
2047 kv_dim_k: usize,
2048 kv_dim_v: usize,
2049 capacity: usize,
2050 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2051 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
2052 }
2053
2054 pub fn allocate_tp_swa_kv_cache(
2055 &self,
2056 kv_dim_k: usize,
2057 kv_dim_v: usize,
2058 capacity: usize,
2059 window: usize,
2060 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2061 if window == 0 {
2062 return Err("TP SWA KV window must be nonzero".into());
2063 }
2064 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
2065 }
2066
2067 fn allocate_tp_kv_cache_inner(
2068 &self,
2069 kv_dim_k: usize,
2070 kv_dim_v: usize,
2071 capacity: usize,
2072 window: Option<usize>,
2073 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2074 if capacity == 0 || capacity > i32::MAX as usize {
2075 return Err(
2076 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
2077 );
2078 }
2079 let tp = self.ranks.len();
2080 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
2081 let physical_rows = window
2082 .map(|window| crate::cache::swa_ring_rows(window, capacity))
2083 .unwrap_or(capacity);
2084 let k_plane_bytes = physical_rows
2085 .checked_mul(shape.k_token_bytes)
2086 .and_then(|bytes| bytes.checked_add(8))
2087 .ok_or("TP KV K plane-byte overflow")?;
2088 let v_plane_bytes = physical_rows
2089 .checked_mul(shape.v_token_bytes)
2090 .and_then(|bytes| bytes.checked_add(8))
2091 .ok_or("TP KV V plane-byte overflow")?;
2092 let mut ranks = Vec::with_capacity(tp);
2093 for engine in &self.ranks {
2094 let _main = engine.gpu.enter_main()?;
2095 ranks.push(ResidentTpKvCacheRank::new(
2096 engine.alloc_u8(k_plane_bytes)?,
2097 engine.alloc_u8(v_plane_bytes)?,
2098 engine.htod_i32(&[0])?,
2099 ));
2100 }
2101 Ok(match window {
2102 Some(window) => ResidentTpKvCache::new_swa(
2103 ranks,
2104 shape.kv_dim_k,
2105 shape.kv_dim_v,
2106 shape.k_token_bytes,
2107 shape.v_token_bytes,
2108 capacity,
2109 window,
2110 ),
2111 None => ResidentTpKvCache::new(
2112 ranks,
2113 shape.kv_dim_k,
2114 shape.kv_dim_v,
2115 shape.k_token_bytes,
2116 shape.v_token_bytes,
2117 capacity,
2118 ),
2119 })
2120 }
2121
2122 pub fn grow_tp_kv_cache(
2123 &self,
2124 source: &ResidentTpKvCache,
2125 target_capacity: usize,
2126 rows: usize,
2127 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2128 self.validate_tp_kv_cache(source)?;
2129 let plan = source.prepare_grow(target_capacity, rows)?;
2130 let ranks = self.ranks.len();
2131 let global_k = source
2132 .kv_dim_k()
2133 .checked_mul(ranks)
2134 .ok_or("TP KV grow global K dimension overflow")?;
2135 let global_v = source
2136 .kv_dim_v()
2137 .checked_mul(ranks)
2138 .ok_or("TP KV grow global V dimension overflow")?;
2139 let mut target = match source.ring_window() {
2140 Some(window) => {
2141 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
2142 }
2143 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
2144 };
2145 self.validate_tp_kv_cache(&target)?;
2146
2147 for (rank, engine) in self.ranks.iter().enumerate() {
2148 let _main = engine.gpu.enter_main()?;
2149 let src = source
2150 .rank(rank)
2151 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
2152 let dst = target
2153 .rank_mut(rank)
2154 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
2155 if plan.k_bytes() > 0 {
2156 engine.copy_u8_range_into(
2157 dst.k_mut(),
2158 0,
2159 src.k(),
2160 plan.source_row() * source.k_tok_bytes(),
2161 plan.k_bytes(),
2162 )?;
2163 }
2164 if plan.v_bytes() > 0 {
2165 engine.copy_u8_range_into(
2166 dst.v_mut(),
2167 0,
2168 src.v(),
2169 plan.source_row() * source.v_tok_bytes(),
2170 plan.v_bytes(),
2171 )?;
2172 }
2173 }
2174 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
2175
2176 for engine in &self.ranks {
2179 let _main = engine.gpu.enter_main()?;
2180 engine.stream().synchronize()?;
2181 }
2182 let physical_copy_rows = plan.copy_rows();
2183 target.publish_grow(plan)?;
2184 eprintln!(
2185 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
2186 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
2187 rank_streams_synchronized=true generation_preserved=true",
2188 rows,
2189 source.capacity(),
2190 target_capacity,
2191 ranks,
2192 physical_copy_rows,
2193 source.ring_window(),
2194 );
2195 Ok(target)
2196 }
2197
2198 pub fn hydrate_tp_kv_cache(
2199 &self,
2200 cache: &mut ResidentTpKvCache,
2201 rows: usize,
2202 k_rows: &[u8],
2203 v_rows: &[u8],
2204 ) -> Result<(), Box<dyn std::error::Error>> {
2205 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
2206 }
2207
2208 pub fn hydrate_tp_kv_cache_from(
2209 &self,
2210 cache: &mut ResidentTpKvCache,
2211 logical_len: usize,
2212 resident_start: usize,
2213 k_rows: &[u8],
2214 v_rows: &[u8],
2215 ) -> Result<(), Box<dyn std::error::Error>> {
2216 self.validate_tp_kv_cache(cache)?;
2217 if cache.committed_len() != 0 || cache.staged_len() != 0 {
2218 return Err(format!(
2219 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
2220 cache.committed_len(),
2221 cache.staged_len()
2222 )
2223 .into());
2224 }
2225 if resident_start > logical_len || logical_len > cache.capacity() {
2226 return Err(format!(
2227 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
2228 cache.capacity(),
2229 )
2230 .into());
2231 }
2232 let rows = logical_len - resident_start;
2233 if rows > cache.physical_capacity() {
2234 return Err(format!(
2235 "TP KV hydration rows {rows} exceed physical capacity {}",
2236 cache.physical_capacity()
2237 )
2238 .into());
2239 }
2240 for rank in 0..self.ranks.len() {
2241 let k_rank =
2242 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
2243 let v_rank =
2244 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
2245 let engine = &self.ranks[rank];
2246 let _main = engine.gpu.enter_main()?;
2247 let rank_cache = cache
2248 .rank_mut(rank)
2249 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2250 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
2251 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
2252 }
2253 cache.publish_hydration(logical_len, resident_start)?;
2254 Ok(())
2255 }
2256
2257 pub fn append_tp_kv_transaction(
2258 &self,
2259 cache: &mut ResidentTpKvCache,
2260 transaction: TpKvTransaction,
2261 k_shards: &[CudaSlice<f32>],
2262 v_shards: &[CudaSlice<f32>],
2263 rows: usize,
2264 ) -> Result<(), Box<dyn std::error::Error>> {
2265 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
2266 }
2267
2268 #[allow(clippy::too_many_arguments)]
2273 pub fn append_tp_kv_transaction_inner(
2274 &self,
2275 cache: &mut ResidentTpKvCache,
2276 transaction: TpKvTransaction,
2277 k_shards: &[CudaSlice<f32>],
2278 v_shards: &[CudaSlice<f32>],
2279 rows: usize,
2280 external_rank_appends: bool,
2281 ) -> Result<(), Box<dyn std::error::Error>> {
2282 self.validate_tp_kv_cache(cache)?;
2283 let plan = cache.prepare_append(transaction, rows)?;
2284 let target = plan.target();
2285 let expected_k = rows
2286 .checked_mul(cache.kv_dim_k())
2287 .ok_or("TP KV K append size overflow")?;
2288 let expected_v = rows
2289 .checked_mul(cache.kv_dim_v())
2290 .ok_or("TP KV V append size overflow")?;
2291 if !external_rank_appends
2294 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
2295 {
2296 return Err(format!(
2297 "TP KV append shard counts k={} v={} != ranks {}",
2298 k_shards.len(),
2299 v_shards.len(),
2300 self.ranks.len()
2301 )
2302 .into());
2303 }
2304 let kv_dim_k = cache.kv_dim_k();
2305 let kv_dim_v = cache.kv_dim_v();
2306 let k_tok_bytes = cache.k_tok_bytes();
2307 let v_tok_bytes = cache.v_tok_bytes();
2308 if let Some(KvRingAppend::Rebase {
2309 src_row,
2310 keep_rows,
2311 new_base,
2312 ..
2313 }) = plan.ring_append()
2314 {
2315 for rank in 0..self.ranks.len() {
2316 let engine = &self.ranks[rank];
2317 let _main = engine.gpu.enter_main()?;
2318 let rank_cache = cache
2319 .rank_mut(rank)
2320 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2321 if keep_rows > 0 {
2322 let k_len = keep_rows
2323 .checked_mul(k_tok_bytes)
2324 .ok_or("TP KV K rebase-byte overflow")?;
2325 let v_len = keep_rows
2326 .checked_mul(v_tok_bytes)
2327 .ok_or("TP KV V rebase-byte overflow")?;
2328 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
2329 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
2330 engine.copy_u8_range_into(
2331 &mut k_tmp,
2332 0,
2333 rank_cache.k(),
2334 src_row * k_tok_bytes,
2335 k_len,
2336 )?;
2337 engine.copy_u8_range_into(
2338 &mut v_tmp,
2339 0,
2340 rank_cache.v(),
2341 src_row * v_tok_bytes,
2342 v_len,
2343 )?;
2344 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2345 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2346 }
2347 if rank_cache.base_d().is_some() {
2351 let value = new_base as i32;
2352 let rank_cache = cache
2353 .rank_mut(rank)
2354 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2355 if let Some(base_d) = rank_cache.base_d_mut() {
2356 engine.set_i32_one(base_d, value)?;
2357 }
2358 }
2359 }
2360 }
2361 cache.publish_append_rebase(plan)?;
2362 let write_row = plan.write_row();
2363 for rank in 0..self.ranks.len() {
2364 if external_rank_appends {
2365 break;
2366 }
2367 let engine = &self.ranks[rank];
2368 let _main = engine.gpu.enter_main()?;
2369 if k_shards[rank].len() != expected_k
2370 || v_shards[rank].len() != expected_v
2371 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2372 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2373 {
2374 return Err(format!(
2375 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2376 != expected {expected_k}/{expected_v} on device {}",
2377 k_shards[rank].len(),
2378 k_shards[rank].ordinal(),
2379 v_shards[rank].len(),
2380 v_shards[rank].ordinal(),
2381 engine.ctx().ordinal(),
2382 )
2383 .into());
2384 }
2385 let rank_cache = cache
2386 .rank_mut(rank)
2387 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2388 let (rank_k, rank_v) = rank_cache.planes_mut();
2389 engine.append_kv_quantized_rows(
2390 &k_shards[rank],
2391 &v_shards[rank],
2392 rank_k,
2393 rank_v,
2394 write_row,
2395 rows,
2396 kv_dim_k,
2397 kv_dim_v,
2398 k_tok_bytes,
2399 v_tok_bytes,
2400 Engine::kv_fp8_on(),
2401 )?;
2402 }
2403 if !external_rank_appends {
2404 self.set_tp_kv_len_mirrors(cache, target)?;
2407 }
2408 cache.publish_append_plan(plan)?;
2409 Ok(())
2410 }
2411
2412 pub fn commit_tp_kv_transaction(
2413 &self,
2414 cache: &mut ResidentTpKvCache,
2415 transaction: TpKvTransaction,
2416 accepted_rows: usize,
2417 ) -> Result<(), Box<dyn std::error::Error>> {
2418 self.validate_tp_kv_cache(cache)?;
2419 let target = cache.commit_target(transaction, accepted_rows)?;
2420 self.set_tp_kv_len_mirrors(cache, target)?;
2421 cache.publish_finalize(transaction, target)?;
2422 Ok(())
2423 }
2424
2425 pub fn commit_tp_kv_transaction_external(
2431 &self,
2432 cache: &mut ResidentTpKvCache,
2433 transaction: TpKvTransaction,
2434 accepted_rows: usize,
2435 ) -> Result<(), Box<dyn std::error::Error>> {
2436 self.validate_tp_kv_cache(cache)?;
2437 let target = cache.commit_target(transaction, accepted_rows)?;
2438 cache.publish_finalize(transaction, target)?;
2439 Ok(())
2440 }
2441
2442 pub fn rollback_tp_kv_transaction(
2443 &self,
2444 cache: &mut ResidentTpKvCache,
2445 transaction: TpKvTransaction,
2446 ) -> Result<(), Box<dyn std::error::Error>> {
2447 self.validate_tp_kv_cache(cache)?;
2448 cache.validate_transaction(transaction)?;
2449 let target = transaction.base_len();
2450 self.set_tp_kv_len_mirrors(cache, target)?;
2451 cache.publish_finalize(transaction, target)?;
2452 Ok(())
2453 }
2454
2455 pub fn tp_kv_device_lengths(
2456 &self,
2457 cache: &ResidentTpKvCache,
2458 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2459 self.validate_tp_kv_cache(cache)?;
2460 let mut lengths = Vec::with_capacity(self.ranks.len());
2461 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2462 let _main = engine.gpu.enter_main()?;
2463 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2464 }
2465 Ok(lengths)
2466 }
2467
2468 fn set_tp_kv_len_mirrors(
2469 &self,
2470 cache: &mut ResidentTpKvCache,
2471 len: usize,
2472 ) -> Result<(), Box<dyn std::error::Error>> {
2473 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2474 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2475 let _main = engine.gpu.enter_main()?;
2476 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2477 }
2478 Ok(())
2479 }
2480
2481 fn validate_tp_kv_cache(
2482 &self,
2483 cache: &ResidentTpKvCache,
2484 ) -> Result<(), Box<dyn std::error::Error>> {
2485 if cache.ranks_len() != self.ranks.len() {
2486 return Err(format!(
2487 "TP KV cache ranks {} != runtime ranks {}",
2488 cache.ranks_len(),
2489 self.ranks.len()
2490 )
2491 .into());
2492 }
2493 let expected_k = cache
2494 .physical_capacity()
2495 .checked_mul(cache.k_tok_bytes())
2496 .and_then(|bytes| bytes.checked_add(8))
2497 .ok_or("TP KV K plane validation overflow")?;
2498 let expected_v = cache
2499 .physical_capacity()
2500 .checked_mul(cache.v_tok_bytes())
2501 .and_then(|bytes| bytes.checked_add(8))
2502 .ok_or("TP KV V plane validation overflow")?;
2503 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2504 let device = engine.ctx().ordinal();
2505 if rank_cache.k().len() != expected_k
2506 || rank_cache.v().len() != expected_v
2507 || rank_cache.len_d().len() != 1
2508 || rank_cache.k().ordinal() != device
2509 || rank_cache.v().ordinal() != device
2510 || rank_cache.len_d().ordinal() != device
2511 {
2512 return Err(format!(
2513 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2514 )
2515 .into());
2516 }
2517 }
2518 Ok(())
2519 }
2520
2521 pub fn full(
2522 &self,
2523 matrix: E4m3BlockMatrix<'_>,
2524 activations: &[f32],
2525 tokens: usize,
2526 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2527 matrix.validate()?;
2528 validate_activations(activations, tokens, matrix.in_features)?;
2529 run_rank(&self.ranks[0], matrix, activations, tokens)
2530 }
2531
2532 #[allow(clippy::manual_is_multiple_of)] pub fn column_parallel(
2537 &self,
2538 matrix: E4m3BlockMatrix<'_>,
2539 activations: &[f32],
2540 tokens: usize,
2541 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2542 matrix.validate()?;
2543 validate_activations(activations, tokens, matrix.in_features)?;
2544 let tp = self.ranks.len();
2545 if matrix.out_features % tp != 0 {
2546 return Err(format!(
2547 "column-parallel out_features {} is not divisible by TP={tp}",
2548 matrix.out_features
2549 )
2550 .into());
2551 }
2552 let local_out = matrix.out_features / tp;
2553 if !local_out.is_multiple_of(FP8_BLOCK) {
2554 return Err(format!(
2555 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2556 E4M3 scale block"
2557 )
2558 .into());
2559 }
2560
2561 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2562 let mut rank_outputs = Vec::with_capacity(tp);
2563 for (rank_index, rank) in self.ranks.iter().enumerate() {
2564 let shard = column_shard(matrix, tp, rank_index)?;
2565 let output = run_rank(rank, shard, activations, tokens)?;
2566 let row_start = rank_index * local_out;
2567 for token in 0..tokens {
2568 gathered[token * matrix.out_features + row_start
2569 ..token * matrix.out_features + row_start + local_out]
2570 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2571 }
2572 rank_outputs.push(output);
2573 }
2574 Ok(ColumnParallelResult {
2575 gathered,
2576 rank_outputs,
2577 })
2578 }
2579
2580 pub fn upload_column_parallel(
2581 &self,
2582 matrix: E4m3BlockMatrix<'_>,
2583 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2584 matrix.validate()?;
2585 let tp = self.ranks.len();
2586 validate_column_shape(matrix, tp)?;
2587 let mut ranks = Vec::with_capacity(tp);
2588 for (rank_index, engine) in self.ranks.iter().enumerate() {
2589 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2590 }
2591 Ok(ResidentColumnParallel {
2592 ranks,
2593 out_features: matrix.out_features,
2594 in_features: matrix.in_features,
2595 })
2596 }
2597
2598 pub fn column_parallel_resident(
2599 &self,
2600 matrix: &ResidentColumnParallel,
2601 activations: &[f32],
2602 tokens: usize,
2603 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2604 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2605 validate_activations(activations, tokens, matrix.in_features)?;
2606 let local_out = matrix.out_features / self.ranks.len();
2607 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2608 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2609 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2610 let output = run_resident_rank(engine, shard, activations, tokens)?;
2611 let row_start = rank_index * local_out;
2612 for token in 0..tokens {
2613 gathered[token * matrix.out_features + row_start
2614 ..token * matrix.out_features + row_start + local_out]
2615 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2616 }
2617 rank_outputs.push(output);
2618 }
2619 Ok(ColumnParallelResult {
2620 gathered,
2621 rank_outputs,
2622 })
2623 }
2624
2625 #[allow(clippy::manual_is_multiple_of)] pub fn row_parallel(
2630 &self,
2631 matrix: E4m3BlockMatrix<'_>,
2632 activations: &[f32],
2633 tokens: usize,
2634 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2635 matrix.validate()?;
2636 validate_activations(activations, tokens, matrix.in_features)?;
2637 let tp = self.ranks.len();
2638 if matrix.in_features % tp != 0 {
2639 return Err(format!(
2640 "row-parallel in_features {} is not divisible by TP={tp}",
2641 matrix.in_features
2642 )
2643 .into());
2644 }
2645 let local_in = matrix.in_features / tp;
2646 if !local_in.is_multiple_of(FP8_BLOCK) {
2647 return Err(format!(
2648 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2649 E4M3 scale block"
2650 )
2651 .into());
2652 }
2653
2654 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2655 let mut rank_partials = Vec::with_capacity(tp);
2656 for (rank_index, rank) in self.ranks.iter().enumerate() {
2657 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2658 let local_activations =
2659 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2660 let shard = E4m3BlockMatrix {
2661 codes: &codes,
2662 scales: &scales,
2663 out_features: matrix.out_features,
2664 in_features: local_in,
2665 };
2666 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2667 for (sum, value) in reduced.iter_mut().zip(&partial) {
2668 *sum += *value;
2669 }
2670 rank_partials.push(partial);
2671 }
2672 Ok(RowParallelResult {
2673 reduced,
2674 rank_partials,
2675 })
2676 }
2677
2678 pub fn upload_row_parallel(
2679 &self,
2680 matrix: E4m3BlockMatrix<'_>,
2681 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2682 matrix.validate()?;
2683 let tp = self.ranks.len();
2684 validate_row_shape(matrix, tp)?;
2685 let local_in = matrix.in_features / tp;
2686 let mut ranks = Vec::with_capacity(tp);
2687 for (rank_index, engine) in self.ranks.iter().enumerate() {
2688 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2689 ranks.push(upload_rank(
2690 engine,
2691 E4m3BlockMatrix {
2692 codes: &codes,
2693 scales: &scales,
2694 out_features: matrix.out_features,
2695 in_features: local_in,
2696 },
2697 )?);
2698 }
2699 Ok(ResidentRowParallel {
2700 ranks,
2701 out_features: matrix.out_features,
2702 in_features: matrix.in_features,
2703 })
2704 }
2705
2706 pub fn row_parallel_resident(
2707 &self,
2708 matrix: &ResidentRowParallel,
2709 activations: &[f32],
2710 tokens: usize,
2711 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2712 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2713 validate_activations(activations, tokens, matrix.in_features)?;
2714 let tp = self.ranks.len();
2715 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2716 let mut rank_partials = Vec::with_capacity(tp);
2717 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2718 let local_activations =
2719 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2720 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2721 for (sum, value) in reduced.iter_mut().zip(&partial) {
2722 *sum += *value;
2723 }
2724 rank_partials.push(partial);
2725 }
2726 Ok(RowParallelResult {
2727 reduced,
2728 rank_partials,
2729 })
2730 }
2731
2732 pub fn upload_bf16_column_parallel(
2733 &self,
2734 matrix: Bf16Matrix<'_>,
2735 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2736 self.upload_bf16_column_parallel_inner(matrix, None, false)
2737 }
2738
2739 pub fn upload_step_bf16_column_parallel(
2741 &self,
2742 matrix: Bf16Matrix<'_>,
2743 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2744 self.upload_step_bf16_column_parallel_inner(matrix, false)
2745 }
2746
2747 pub fn upload_step_bf16_column_parallel_f32_mirror(
2752 &self,
2753 matrix: Bf16Matrix<'_>,
2754 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2755 self.upload_step_bf16_column_parallel_inner(matrix, true)
2756 }
2757
2758 fn upload_step_bf16_column_parallel_inner(
2759 &self,
2760 matrix: Bf16Matrix<'_>,
2761 f32_mirror: bool,
2762 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2763 let canonical_chunk_rows =
2764 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2765 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2766 }
2767
2768 #[allow(clippy::manual_is_multiple_of)] fn upload_bf16_column_parallel_inner(
2770 &self,
2771 matrix: Bf16Matrix<'_>,
2772 canonical_chunk_rows: Option<usize>,
2773 f32_mirror: bool,
2774 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2775 matrix.validate()?;
2776 let tp = self.ranks.len();
2777 if matrix.out_features % tp != 0 {
2778 return Err(format!(
2779 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2780 matrix.out_features
2781 )
2782 .into());
2783 }
2784 let mut ranks = Vec::with_capacity(tp);
2785 for (rank, engine) in self.ranks.iter().enumerate() {
2786 ranks.push(upload_bf16_rank(
2787 engine,
2788 bf16_column_shard(matrix, tp, rank)?,
2789 f32_mirror,
2790 )?);
2791 }
2792 Ok(ResidentBf16ColumnParallel {
2793 ranks,
2794 out_features: matrix.out_features,
2795 in_features: matrix.in_features,
2796 canonical_chunk_rows,
2797 })
2798 }
2799
2800 pub fn bf16_column_parallel_resident(
2801 &self,
2802 matrix: &ResidentBf16ColumnParallel,
2803 activations: &[f32],
2804 tokens: usize,
2805 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2806 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2807 validate_activations(activations, tokens, matrix.in_features)?;
2808 let local_out = matrix.out_features / self.ranks.len();
2809 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2810 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2811 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2812 let output = run_resident_bf16_rank(
2813 engine,
2814 shard,
2815 activations,
2816 tokens,
2817 matrix.canonical_chunk_rows,
2818 )?;
2819 for token in 0..tokens {
2820 let src = &output[token * local_out..(token + 1) * local_out];
2821 let dst_start = token * matrix.out_features + rank * local_out;
2822 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2823 }
2824 rank_outputs.push(output);
2825 }
2826 Ok(ColumnParallelResult {
2827 gathered,
2828 rank_outputs,
2829 })
2830 }
2831
2832 pub fn bf16_column_parallel_resident_native(
2839 &self,
2840 matrix: &ResidentBf16ColumnParallel,
2841 activations: &[f32],
2842 tokens: usize,
2843 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2844 let rank_outputs =
2845 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2846 let local_out = matrix.out_features / self.ranks.len();
2847 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2848 }
2849
2850 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2855 self.ranks
2856 .first()
2857 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2858 }
2859
2860 pub fn bf16_column_parallel_resident_native_device(
2873 &self,
2874 matrix: &ResidentBf16ColumnParallel,
2875 root_activation: &CudaSlice<f32>,
2876 tokens: usize,
2877 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2878 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2879 matrix,
2880 root_activation,
2881 tokens,
2882 )?;
2883 let local_out = matrix.out_features / self.ranks.len();
2884 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2885 let root = &self.ranks[0];
2886 let _main = root.gpu.enter_main()?;
2887 root.stream().synchronize()?;
2888 Ok(gathered)
2889 }
2890
2891 pub fn bf16_column_parallel_resident_device_shards_from_root(
2896 &self,
2897 matrix: &ResidentBf16ColumnParallel,
2898 root_activation: &CudaSlice<f32>,
2899 tokens: usize,
2900 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2901 if self.ranks.len() > 1 && !self.native_p2p {
2902 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2903 }
2904 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2905 let values = tokens
2906 .checked_mul(matrix.in_features)
2907 .ok_or("device BF16 column activation size overflow")?;
2908 let root = &self.ranks[0];
2909 if tokens == 0
2910 || root_activation.len() < values
2911 || root_activation.ordinal() != root.ctx().ordinal()
2912 {
2913 return Err("device BF16 column root activation geometry mismatch".into());
2914 }
2915
2916 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2917 let root_input = {
2918 let _main = root.gpu.enter_main()?;
2919 let mut root_input = root.uninit(values)?;
2920 root.stream()
2921 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2922 root_input
2923 };
2924 {
2928 let _main = root.gpu.enter_main()?;
2929 root.stream().synchronize()?;
2930 }
2931 rank_inputs.push(root_input);
2932 for engine in &self.ranks[1..] {
2933 let peer_input = {
2934 let _main = engine.gpu.enter_main()?;
2935 let mut peer_input = engine.uninit(values)?;
2936 engine
2937 .stream()
2938 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2939 peer_input
2940 };
2941 rank_inputs.push(peer_input);
2942 }
2943
2944 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2945 #[allow(clippy::needless_range_loop)]
2946 for rank in 0..self.ranks.len() {
2948 rank_outputs.push(run_resident_bf16_rank_device(
2949 &self.ranks[rank],
2950 &matrix.ranks[rank],
2951 &rank_inputs[rank],
2952 tokens,
2953 matrix.canonical_chunk_rows,
2954 self.bulk_p2p,
2955 )?);
2956 }
2957 Ok(rank_outputs)
2958 }
2959
2960 pub fn bf16_column_parallel_resident_device_shards(
2967 &self,
2968 matrix: &ResidentBf16ColumnParallel,
2969 activations: &[f32],
2970 tokens: usize,
2971 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2972 if self.ranks.len() > 1 && !self.native_p2p {
2973 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2974 }
2975 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2976 validate_activations(activations, tokens, matrix.in_features)?;
2977
2978 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2979 let root_input = {
2980 let root = &self.ranks[0];
2981 let _main = root.gpu.enter_main()?;
2982 root.htod(activations)?
2983 };
2984 {
2990 let root = &self.ranks[0];
2991 let _main = root.gpu.enter_main()?;
2992 root.stream().synchronize()?;
2993 }
2994 rank_inputs.push(root_input);
2995 for engine in &self.ranks[1..] {
2996 let peer_input = {
2997 let _main = engine.gpu.enter_main()?;
2998 let mut peer_input = engine.uninit(activations.len())?;
2999 engine
3000 .stream()
3001 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3002 peer_input
3003 };
3004 rank_inputs.push(peer_input);
3005 }
3006
3007 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3008 #[allow(clippy::needless_range_loop)]
3009 for rank in 0..self.ranks.len() {
3011 rank_outputs.push(run_resident_bf16_rank_device(
3012 &self.ranks[rank],
3013 &matrix.ranks[rank],
3014 &rank_inputs[rank],
3015 tokens,
3016 matrix.canonical_chunk_rows,
3017 self.bulk_p2p,
3018 )?);
3019 }
3020 Ok(rank_outputs)
3021 }
3022
3023 pub fn allocate_replicated_device_rows(
3027 &self,
3028 tokens: usize,
3029 width: usize,
3030 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3031 if self.ranks.len() > 1 && !self.native_p2p {
3032 return Err("replicated device rows require native P2P ranks".into());
3033 }
3034 let values = tokens
3035 .checked_mul(width)
3036 .ok_or("replicated device row size overflow")?;
3037 let rank_lengths = vec![values; self.ranks.len()];
3038 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
3039 let mut ranks = Vec::with_capacity(self.ranks.len());
3040 for engine in &self.ranks {
3041 let _main = engine.gpu.enter_main()?;
3042 ranks.push(engine.uninit(values)?);
3043 }
3044 Ok(ResidentReplicatedDeviceRows {
3045 ranks,
3046 tokens,
3047 width,
3048 })
3049 }
3050
3051 pub fn refresh_replicated_device_rows_from_root(
3053 &self,
3054 rows: &mut ResidentReplicatedDeviceRows,
3055 source: &CudaSlice<f32>,
3056 ) -> Result<(), Box<dyn std::error::Error>> {
3057 if self.ranks.len() > 1 && !self.native_p2p {
3058 return Err("replicated device rows require native P2P ranks".into());
3059 }
3060 validate_replicated_device_rows(&self.ranks, rows)?;
3061 let root = self
3062 .ranks
3063 .first()
3064 .ok_or("replicated rows have no root rank")?;
3065 let values = replicated_device_row_source_values(
3066 rows.tokens,
3067 rows.width,
3068 source.len(),
3069 source.ordinal(),
3070 root.ctx().ordinal(),
3071 )?;
3072 let (root_rows, peer_rows) = rows
3073 .ranks
3074 .split_first_mut()
3075 .ok_or("replicated rows have no root allocation")?;
3076 {
3077 let _main = root.gpu.enter_main()?;
3078 let mut destination = root_rows.slice_mut(0..values);
3079 root.stream()
3080 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
3081 root.stream().synchronize()?;
3082 }
3083 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
3084 let _main = engine.gpu.enter_main()?;
3085 let mut destination = peer_rows.slice_mut(0..values);
3086 engine
3087 .stream()
3088 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
3089 }
3090 Ok(())
3091 }
3092
3093 pub fn upload_replicated_device_rows(
3095 &self,
3096 rows: &[f32],
3097 tokens: usize,
3098 width: usize,
3099 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3100 if self.ranks.len() > 1 && !self.native_p2p {
3101 return Err("replicated device rows require native P2P ranks".into());
3102 }
3103 validate_activations(rows, tokens, width)?;
3104 let root = self
3105 .ranks
3106 .first()
3107 .ok_or("replicated rows have no root rank")?;
3108 let root_rows = {
3109 let _main = root.gpu.enter_main()?;
3110 root.htod(rows)?
3111 };
3112 {
3113 let _main = root.gpu.enter_main()?;
3114 root.stream().synchronize()?;
3115 }
3116 let mut ranks = Vec::with_capacity(self.ranks.len());
3117 ranks.push(root_rows);
3118 for engine in self.ranks.iter().skip(1) {
3119 let _main = engine.gpu.enter_main()?;
3120 let mut peer_rows = engine.uninit(rows.len())?;
3121 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
3122 ranks.push(peer_rows);
3123 }
3124 Ok(ResidentReplicatedDeviceRows {
3125 ranks,
3126 tokens,
3127 width,
3128 })
3129 }
3130
3131 pub fn bf16_column_parallel_resident_replicated_device_shards(
3133 &self,
3134 matrix: &ResidentBf16ColumnParallel,
3135 activations: &ResidentReplicatedDeviceRows,
3136 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3137 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3138 validate_replicated_device_rows(&self.ranks, activations)?;
3139 if activations.width != matrix.in_features {
3140 return Err(format!(
3141 "replicated BF16 column input width {} != matrix width {}",
3142 activations.width, matrix.in_features
3143 )
3144 .into());
3145 }
3146 let mut outputs = Vec::with_capacity(self.ranks.len());
3147 for rank in 0..self.ranks.len() {
3148 outputs.push(run_resident_bf16_rank_device(
3149 &self.ranks[rank],
3150 &matrix.ranks[rank],
3151 &activations.ranks[rank],
3152 activations.tokens,
3153 matrix.canonical_chunk_rows,
3154 self.bulk_p2p,
3155 )?);
3156 }
3157 Ok(outputs)
3158 }
3159
3160 #[allow(clippy::too_many_arguments)]
3162 pub fn upload_sigmoid_topk_router(
3163 &self,
3164 weight: Bf16Matrix<'_>,
3165 correction_bias: &[f32],
3166 active: Option<&[bool]>,
3167 experts_per_token: usize,
3168 scaling_factor: f32,
3169 route_norm: bool,
3170 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
3171 weight.validate()?;
3172 if correction_bias.len() != weight.out_features
3173 || experts_per_token == 0
3174 || experts_per_token > weight.out_features
3175 || !correction_bias.iter().all(|value| value.is_finite())
3176 || !scaling_factor.is_finite()
3177 || scaling_factor <= 0.0
3178 {
3179 return Err(format!(
3180 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
3181 weight.out_features,
3182 weight.in_features,
3183 correction_bias.len(),
3184 experts_per_token,
3185 )
3186 .into());
3187 }
3188 let active_row = active
3189 .map(|mask| {
3190 if mask.len() != weight.out_features {
3191 return Err(format!(
3192 "sigmoid router active mask {} != experts {}",
3193 mask.len(),
3194 weight.out_features
3195 ));
3196 }
3197 Ok(mask
3198 .iter()
3199 .map(|&enabled| u8::from(enabled))
3200 .collect::<Vec<_>>())
3201 })
3202 .transpose()?
3203 .unwrap_or_else(|| vec![1; weight.out_features]);
3204 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
3205 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
3206
3207 let root = self
3208 .ranks
3209 .first()
3210 .ok_or("sigmoid router runtime has no root rank")?;
3211 let _main = root.gpu.enter_main()?;
3212 let bf16 = root.htod_bytes(weight.bytes)?;
3213 let weight_f32 = root.bf16_to_f32(
3214 &bf16.slice(0..bf16.len()),
3215 weight.out_features * weight.in_features,
3216 )?;
3217 Ok(ResidentSigmoidTopKRouter {
3218 weight: weight_f32,
3219 correction_bias: root.htod(correction_bias)?,
3220 active: root.htod_bytes(&active_row)?,
3221 root_device: root.ctx().ordinal(),
3222 input_width: weight.in_features,
3223 expert_count: weight.out_features,
3224 experts_per_token,
3225 active_count,
3226 scaling_factor,
3227 route_norm,
3228 })
3229 }
3230
3231 pub fn sigmoid_topk_replicated_device_rows_host(
3236 &self,
3237 router: &ResidentSigmoidTopKRouter,
3238 input: &ResidentReplicatedDeviceRows,
3239 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
3240 validate_replicated_device_rows(&self.ranks, input)?;
3241 if input.width != router.input_width {
3242 return Err(format!(
3243 "sigmoid router input width {} != resident width {}",
3244 input.width, router.input_width
3245 )
3246 .into());
3247 }
3248 let root = self
3249 .ranks
3250 .first()
3251 .ok_or("sigmoid router runtime has no root rank")?;
3252 let _main = root.gpu.enter_main()?;
3253 if root.ctx().ordinal() != router.root_device
3254 || router.weight.ordinal() != router.root_device
3255 || router.correction_bias.ordinal() != router.root_device
3256 || router.active.ordinal() != router.root_device
3257 {
3258 return Err("sigmoid router root residency changed".into());
3259 }
3260 let logits = root.router_gemv(
3261 &router.weight,
3262 &input.ranks[0],
3263 router.input_width,
3264 router.expert_count,
3265 input.tokens,
3266 )?;
3267 let (selected, weights) = root.moe_router_sigmoid_topk_host(
3268 &logits,
3269 input.tokens,
3270 router.expert_count,
3271 router.experts_per_token,
3272 router.active_count,
3273 &router.correction_bias,
3274 &router.active,
3275 router.scaling_factor,
3276 router.route_norm,
3277 )?;
3278 Ok(SigmoidTopKHostOutput {
3279 logits: root.dtoh(&logits)?,
3280 selected,
3281 weights,
3282 })
3283 }
3284
3285 pub fn upload_replicated_bf16_swiglu(
3287 &self,
3288 gate: Bf16Matrix<'_>,
3289 up: Bf16Matrix<'_>,
3290 down: Bf16Matrix<'_>,
3291 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
3292 gate.validate()?;
3293 up.validate()?;
3294 down.validate()?;
3295 if gate.in_features != up.in_features
3296 || gate.out_features != up.out_features
3297 || down.in_features != gate.out_features
3298 || down.out_features != gate.in_features
3299 {
3300 return Err(format!(
3301 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
3302 gate.out_features,
3303 gate.in_features,
3304 up.out_features,
3305 up.in_features,
3306 down.out_features,
3307 down.in_features,
3308 )
3309 .into());
3310 }
3311 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
3312 let mut up_ranks = Vec::with_capacity(self.ranks.len());
3313 let mut down_ranks = Vec::with_capacity(self.ranks.len());
3314 for engine in &self.ranks {
3315 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
3316 up_ranks.push(upload_bf16_rank(engine, up, false)?);
3317 down_ranks.push(upload_bf16_rank(engine, down, false)?);
3318 }
3319 Ok(ResidentReplicatedBf16SwiGlu {
3320 gate: gate_ranks,
3321 up: up_ranks,
3322 down: down_ranks,
3323 input_width: gate.in_features,
3324 intermediate_width: gate.out_features,
3325 })
3326 }
3327
3328 pub fn replicated_bf16_swiglu_resident_device(
3330 &self,
3331 mlp: &ResidentReplicatedBf16SwiGlu,
3332 input: &ResidentReplicatedDeviceRows,
3333 activation_limit: Option<f32>,
3334 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3335 validate_step_expert_activation_limit(activation_limit)?;
3336 validate_replicated_device_rows(&self.ranks, input)?;
3337 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
3338 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
3339 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
3340 if input.width != mlp.input_width
3341 || mlp.gate.len() != self.ranks.len()
3342 || mlp.up.len() != self.ranks.len()
3343 || mlp.down.len() != self.ranks.len()
3344 {
3345 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3346 }
3347
3348 let mut outputs = Vec::with_capacity(self.ranks.len());
3349 for rank in 0..self.ranks.len() {
3350 let engine = &self.ranks[rank];
3351 let gate = run_resident_bf16_rank_device(
3352 engine,
3353 &mlp.gate[rank],
3354 &input.ranks[rank],
3355 input.tokens,
3356 None,
3357 self.bulk_p2p,
3358 )?;
3359 let up = run_resident_bf16_rank_device(
3360 engine,
3361 &mlp.up[rank],
3362 &input.ranks[rank],
3363 input.tokens,
3364 None,
3365 self.bulk_p2p,
3366 )?;
3367 let _main = engine.gpu.enter_main()?;
3368 let values = input
3369 .tokens
3370 .checked_mul(mlp.intermediate_width)
3371 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3372 let mut activation = engine.uninit(values)?;
3373 if let Some(limit) = activation_limit {
3374 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3375 } else {
3376 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3377 }
3378 outputs.push(run_resident_bf16_rank_device(
3379 engine,
3380 &mlp.down[rank],
3381 &activation,
3382 input.tokens,
3383 None,
3384 self.bulk_p2p,
3385 )?);
3386 }
3387 Ok(ResidentReplicatedDeviceRows {
3388 ranks: outputs,
3389 tokens: input.tokens,
3390 width: mlp.input_width,
3391 })
3392 }
3393
3394 pub fn rms_norm_replicated_device_rows(
3396 &self,
3397 input: &ResidentReplicatedDeviceRows,
3398 weight: &[f32],
3399 eps: f32,
3400 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3401 validate_replicated_device_rows(&self.ranks, input)?;
3402 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3403 return Err(format!(
3404 "replicated RMS norm weight/eps {}/{} != width {}",
3405 weight.len(),
3406 eps,
3407 input.width
3408 )
3409 .into());
3410 }
3411 let mut ranks = Vec::with_capacity(self.ranks.len());
3412 for (rank, engine) in self.ranks.iter().enumerate() {
3413 let _main = engine.gpu.enter_main()?;
3414 let weight = engine.htod(weight)?;
3415 let mut output = engine.uninit(input.tokens * input.width)?;
3416 engine.rms_norm(
3417 &input.ranks[rank],
3418 &weight,
3419 &mut output,
3420 input.width,
3421 input.tokens,
3422 eps,
3423 )?;
3424 ranks.push(output);
3425 }
3426 Ok(ResidentReplicatedDeviceRows {
3427 ranks,
3428 tokens: input.tokens,
3429 width: input.width,
3430 })
3431 }
3432
3433 pub fn add_rms_norm_replicated_device_rows(
3435 &self,
3436 input: &ResidentReplicatedDeviceRows,
3437 update: &ResidentReplicatedDeviceRows,
3438 weight: &[f32],
3439 eps: f32,
3440 ) -> Result<
3441 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3442 Box<dyn std::error::Error>,
3443 > {
3444 validate_replicated_device_rows(&self.ranks, input)?;
3445 validate_replicated_device_rows(&self.ranks, update)?;
3446 if input.tokens != update.tokens
3447 || input.width != update.width
3448 || weight.len() != input.width
3449 || !eps.is_finite()
3450 || eps <= 0.0
3451 {
3452 return Err(format!(
3453 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3454 input.tokens,
3455 input.width,
3456 update.tokens,
3457 update.width,
3458 weight.len(),
3459 )
3460 .into());
3461 }
3462 let values = input.tokens * input.width;
3463 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3464 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3465 for (rank, engine) in self.ranks.iter().enumerate() {
3466 let _main = engine.gpu.enter_main()?;
3467 let weight = engine.htod(weight)?;
3468 let mut residual = engine.uninit(values)?;
3469 let mut normalized = engine.uninit(values)?;
3470 engine.add_rms_norm(
3471 &input.ranks[rank],
3472 &update.ranks[rank],
3473 &weight,
3474 &mut residual,
3475 &mut normalized,
3476 input.width,
3477 input.tokens,
3478 eps,
3479 )?;
3480 residual_ranks.push(residual);
3481 normalized_ranks.push(normalized);
3482 }
3483 Ok((
3484 ResidentReplicatedDeviceRows {
3485 ranks: residual_ranks,
3486 tokens: input.tokens,
3487 width: input.width,
3488 },
3489 ResidentReplicatedDeviceRows {
3490 ranks: normalized_ranks,
3491 tokens: input.tokens,
3492 width: input.width,
3493 },
3494 ))
3495 }
3496
3497 pub fn collect_replicated_device_rows(
3498 &self,
3499 rows: &ResidentReplicatedDeviceRows,
3500 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3501 validate_replicated_device_rows(&self.ranks, rows)?;
3502 let mut outputs = Vec::with_capacity(self.ranks.len());
3503 for (rank, engine) in self.ranks.iter().enumerate() {
3504 let _main = engine.gpu.enter_main()?;
3505 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3506 }
3507 Ok(outputs)
3508 }
3509
3510 #[allow(clippy::manual_is_multiple_of)] pub fn upload_bf16_row_parallel(
3512 &self,
3513 matrix: Bf16Matrix<'_>,
3514 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3515 matrix.validate()?;
3516 let tp = self.ranks.len();
3517 if matrix.in_features % tp != 0 {
3518 return Err(format!(
3519 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3520 matrix.in_features
3521 )
3522 .into());
3523 }
3524 let mut ranks = Vec::with_capacity(tp);
3525 for (rank, engine) in self.ranks.iter().enumerate() {
3526 let shard = bf16_row_shard(matrix, tp, rank)?;
3527 ranks.push(upload_bf16_rank(
3528 engine,
3529 Bf16Matrix {
3530 bytes: &shard,
3531 out_features: matrix.out_features,
3532 in_features: matrix.in_features / tp,
3533 },
3534 false,
3535 )?);
3536 }
3537 Ok(ResidentBf16RowParallel {
3538 ranks,
3539 out_features: matrix.out_features,
3540 in_features: matrix.in_features,
3541 })
3542 }
3543
3544 pub fn bf16_row_parallel_resident(
3545 &self,
3546 matrix: &ResidentBf16RowParallel,
3547 activations: &[f32],
3548 tokens: usize,
3549 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3550 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3551 validate_activations(activations, tokens, matrix.in_features)?;
3552 let tp = self.ranks.len();
3553 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3554 let mut rank_partials = Vec::with_capacity(tp);
3555 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3556 let local_activations =
3557 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3558 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3559 for (sum, value) in reduced.iter_mut().zip(&partial) {
3560 *sum += value;
3561 }
3562 rank_partials.push(partial);
3563 }
3564 Ok(RowParallelResult {
3565 reduced,
3566 rank_partials,
3567 })
3568 }
3569
3570 pub fn upload_step_bf16_row_parallel(
3572 &self,
3573 matrix: Bf16Matrix<'_>,
3574 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3575 self.upload_step_bf16_row_parallel_inner(matrix, false)
3576 }
3577
3578 pub fn upload_step_bf16_row_parallel_f32_mirror(
3579 &self,
3580 matrix: Bf16Matrix<'_>,
3581 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3582 self.upload_step_bf16_row_parallel_inner(matrix, true)
3583 }
3584
3585 fn upload_step_bf16_row_parallel_inner(
3586 &self,
3587 matrix: Bf16Matrix<'_>,
3588 f32_mirror: bool,
3589 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3590 matrix.validate()?;
3591 let tp = self.ranks.len();
3592 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3593 let local_in = matrix.in_features / tp;
3594 let blocks_per_rank = local_in / canonical_chunk_cols;
3595 let mut ranks = Vec::with_capacity(tp);
3596 for (rank, engine) in self.ranks.iter().enumerate() {
3597 let mut blocks = Vec::with_capacity(blocks_per_rank);
3598 for block in 0..blocks_per_rank {
3599 let global_block = rank * blocks_per_rank + block;
3600 let col_start = global_block * canonical_chunk_cols;
3601 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3602 blocks.push(upload_bf16_rank(
3603 engine,
3604 Bf16Matrix {
3605 bytes: &bytes,
3606 out_features: matrix.out_features,
3607 in_features: canonical_chunk_cols,
3608 },
3609 f32_mirror,
3610 )?);
3611 }
3612 ranks.push(blocks);
3613 }
3614 Ok(ResidentStepBf16RowParallel {
3615 ranks,
3616 out_features: matrix.out_features,
3617 in_features: matrix.in_features,
3618 canonical_chunk_cols,
3619 })
3620 }
3621
3622 pub fn step_bf16_row_parallel_resident(
3627 &self,
3628 matrix: &ResidentStepBf16RowParallel,
3629 activations: &[f32],
3630 tokens: usize,
3631 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3632 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3633 validate_activations(activations, tokens, matrix.in_features)?;
3634 let root = &self.ranks[0];
3635 let output_len = tokens
3636 .checked_mul(matrix.out_features)
3637 .ok_or("Step BF16 row output size overflow")?;
3638 let mut reduced = {
3639 let _main = root.gpu.enter_main()?;
3640 root.htod(&vec![0.0f32; output_len])?
3641 };
3642 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3643 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3644 for (block, resident) in blocks.iter().enumerate() {
3645 let global_block = rank * blocks_per_rank + block;
3646 let input = activation_shard(
3647 activations,
3648 tokens,
3649 matrix.in_features,
3650 PRODUCT_MAX_CARDS,
3651 global_block,
3652 );
3653 let partial =
3654 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3655 let next = {
3656 let _main = root.gpu.enter_main()?;
3657 let partial = root.htod(&partial)?;
3658 let mut next = root.uninit(output_len)?;
3659 root.add(&reduced, &partial, &mut next, output_len)?;
3660 next
3661 };
3662 reduced = next;
3663 }
3664 }
3665 let _main = root.gpu.enter_main()?;
3666 root.dtoh(&reduced)
3667 }
3668
3669 pub fn step_bf16_row_parallel_resident_native(
3675 &self,
3676 matrix: &ResidentStepBf16RowParallel,
3677 activations: &[f32],
3678 tokens: usize,
3679 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3680 if self.ranks.len() > 1 && !self.native_p2p {
3681 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3682 }
3683 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3684 validate_activations(activations, tokens, matrix.in_features)?;
3685 let root = &self.ranks[0];
3686 let root_input = {
3687 let _main = root.gpu.enter_main()?;
3688 root.htod(activations)?
3689 };
3690 {
3693 let _main = root.gpu.enter_main()?;
3694 root.stream().synchronize()?;
3695 }
3696 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3697 let _main = root.gpu.enter_main()?;
3698 root.dtoh(&reduced)
3699 }
3700
3701 pub fn step_bf16_row_parallel_resident_native_device(
3709 &self,
3710 matrix: &ResidentStepBf16RowParallel,
3711 root_activation: &CudaSlice<f32>,
3712 tokens: usize,
3713 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3714 if self.ranks.len() > 1 && !self.native_p2p {
3715 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3716 }
3717 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3718 let values = tokens
3719 .checked_mul(matrix.in_features)
3720 .ok_or("device Step BF16 row activation size overflow")?;
3721 let root = &self.ranks[0];
3722 if tokens == 0
3723 || root_activation.len() < values
3724 || root_activation.ordinal() != root.ctx().ordinal()
3725 {
3726 return Err("device Step BF16 row root activation geometry mismatch".into());
3727 }
3728 let root_input = {
3729 let _main = root.gpu.enter_main()?;
3730 let mut root_input = root.uninit(values)?;
3731 root.stream()
3732 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3733 root.stream().synchronize()?; root_input
3735 };
3736 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3737 let _main = root.gpu.enter_main()?;
3738 root.stream().synchronize()?;
3739 Ok(reduced)
3740 }
3741
3742 fn step_bf16_row_native_reduce_from_root(
3747 &self,
3748 matrix: &ResidentStepBf16RowParallel,
3749 root_input: &CudaSlice<f32>,
3750 tokens: usize,
3751 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3752 let root = &self.ranks[0];
3753 let output_len = tokens
3754 .checked_mul(matrix.out_features)
3755 .ok_or("native Step BF16 row output size overflow")?;
3756 let mut reduced = {
3757 let _main = root.gpu.enter_main()?;
3758 root.htod(&vec![0.0f32; output_len])?
3759 };
3760 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3761 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3762 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3763 let mut remote_partial_keepalive = Vec::new();
3764 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3765 for (block, resident) in blocks.iter().enumerate() {
3766 let global_block = rank * blocks_per_rank + block;
3767 let col_start = global_block * matrix.canonical_chunk_cols;
3768 let block_len = tokens
3769 .checked_mul(matrix.canonical_chunk_cols)
3770 .ok_or("native Step BF16 row block size overflow")?;
3771 let block_input = if self.bulk_p2p {
3772 let root_packed = {
3773 let _main = root.gpu.enter_main()?;
3774 let mut root_packed = root.uninit(block_len)?;
3775 root.copy_rows_strided(
3776 root_input,
3777 &mut root_packed,
3778 matrix.canonical_chunk_cols,
3779 tokens,
3780 matrix.in_features,
3781 col_start,
3782 )?;
3783 root_packed
3784 };
3785 if rank == 0 {
3786 root_packed
3787 } else {
3788 {
3791 let _main = root.gpu.enter_main()?;
3792 root.stream().synchronize()?;
3793 }
3794 let engine = &self.ranks[rank];
3795 let _main = engine.gpu.enter_main()?;
3796 let mut block_input = engine.uninit(block_len)?;
3797 engine
3798 .stream()
3799 .memcpy_dtod(&root_packed, &mut block_input)?;
3800 root_packed_keepalive.push(root_packed);
3801 block_input
3802 }
3803 } else {
3804 let engine = &self.ranks[rank];
3805 let _main = engine.gpu.enter_main()?;
3806 let mut block_input = engine.uninit(block_len)?;
3807 for token in 0..tokens {
3808 let source_start = token * matrix.in_features + col_start;
3809 let source = root_input
3810 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3811 let destination_start = token * matrix.canonical_chunk_cols;
3812 let mut destination = block_input.slice_mut(
3813 destination_start..destination_start + matrix.canonical_chunk_cols,
3814 );
3815 engine.stream().memcpy_dtod(&source, &mut destination)?;
3816 }
3817 block_input
3818 };
3819 let partial = run_resident_bf16_rank_device(
3820 &self.ranks[rank],
3821 resident,
3822 &block_input,
3823 tokens,
3824 None,
3825 self.bulk_p2p,
3826 )?;
3827 block_input_keepalive.push(block_input);
3828 let root_partial = if rank == 0 {
3829 partial
3830 } else {
3831 {
3834 let engine = &self.ranks[rank];
3835 let _main = engine.gpu.enter_main()?;
3836 engine.stream().synchronize()?;
3837 }
3838 let _main = root.gpu.enter_main()?;
3839 let mut peer_partial = root.uninit(output_len)?;
3840 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3841 remote_partial_keepalive.push(partial);
3842 peer_partial
3843 };
3844 let next = {
3845 let _main = root.gpu.enter_main()?;
3846 let mut next = root.uninit(output_len)?;
3847 root.add(&reduced, &root_partial, &mut next, output_len)?;
3848 next
3849 };
3850 reduced = next;
3851 }
3852 }
3853 {
3854 let _main = root.gpu.enter_main()?;
3855 root.stream().synchronize()?;
3856 }
3857 drop(remote_partial_keepalive);
3858 drop(root_packed_keepalive);
3859 drop(block_input_keepalive);
3860 Ok(reduced)
3861 }
3862
3863 pub fn step_bf16_row_parallel_resident_root_device(
3866 &self,
3867 matrix: &ResidentStepBf16RowParallel,
3868 rank_activations: &[CudaSlice<f32>],
3869 tokens: usize,
3870 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3871 if self.ranks.len() > 1 && !self.native_p2p {
3872 return Err(
3873 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3874 );
3875 }
3876 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3877 let local_width = matrix.in_features / self.ranks.len();
3878 let shard_len = tokens
3879 .checked_mul(local_width)
3880 .ok_or("device Step BF16 row shard size overflow")?;
3881 if tokens == 0
3882 || rank_activations.len() != self.ranks.len()
3883 || rank_activations
3884 .iter()
3885 .zip(&self.ranks)
3886 .any(|(rows, engine)| {
3887 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3888 })
3889 {
3890 return Err("device Step BF16 row activation shard geometry changed".into());
3891 }
3892
3893 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3894 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3895 let mut partials = Vec::with_capacity(self.ranks.len());
3896 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3897 if blocks.len() != blocks_per_rank {
3898 return Err(format!(
3899 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3900 blocks.len()
3901 )
3902 .into());
3903 }
3904 let engine = &self.ranks[rank];
3905 let _main = engine.gpu.enter_main()?;
3906 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3907 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3908 for (block, resident) in blocks.iter().enumerate() {
3909 let block_len = tokens
3910 .checked_mul(matrix.canonical_chunk_cols)
3911 .ok_or("device Step BF16 row block size overflow")?;
3912 let mut block_input = engine.uninit(block_len)?;
3913 let local_col_start = block * matrix.canonical_chunk_cols;
3914 if self.bulk_p2p {
3915 engine.copy_rows_strided(
3916 &rank_activations[rank],
3917 &mut block_input,
3918 matrix.canonical_chunk_cols,
3919 tokens,
3920 local_width,
3921 local_col_start,
3922 )?;
3923 } else {
3924 for token in 0..tokens {
3925 let source_start = token * local_width + local_col_start;
3926 let source = rank_activations[rank]
3927 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3928 let destination_start = token * matrix.canonical_chunk_cols;
3929 let mut destination = block_input.slice_mut(
3930 destination_start..destination_start + matrix.canonical_chunk_cols,
3931 );
3932 engine.stream().memcpy_dtod(&source, &mut destination)?;
3933 }
3934 }
3935 let partial = run_resident_bf16_rank_device(
3936 engine,
3937 resident,
3938 &block_input,
3939 tokens,
3940 None,
3941 self.bulk_p2p,
3942 )?;
3943 rank_inputs.push(block_input);
3944 rank_partials.push(partial);
3945 }
3946 block_inputs.push(rank_inputs);
3947 partials.push(rank_partials);
3948 }
3949 for engine in self.ranks.iter().skip(1) {
3950 let _main = engine.gpu.enter_main()?;
3951 engine.stream().synchronize()?;
3952 }
3953
3954 let output_len = tokens
3955 .checked_mul(matrix.out_features)
3956 .ok_or("device Step BF16 row output size overflow")?;
3957 let root = &self.ranks[0];
3958 let _main = root.gpu.enter_main()?;
3959 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3960 let mut remote_partials = Vec::new();
3961 for (rank, rank_partials) in partials.into_iter().enumerate() {
3962 for partial in rank_partials {
3963 let root_partial = if rank == 0 {
3964 partial
3965 } else {
3966 let mut peer_partial = root.uninit(output_len)?;
3967 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3968 remote_partials.push(partial);
3969 peer_partial
3970 };
3971 let mut next = root.uninit(output_len)?;
3972 root.add(&reduced, &root_partial, &mut next, output_len)?;
3973 reduced = next;
3974 }
3975 }
3976 root.stream().synchronize()?;
3977 drop(remote_partials);
3978 drop(block_inputs);
3979 Ok(reduced)
3980 }
3981
3982 pub fn step_bf16_row_parallel_resident_replicated_device(
3984 &self,
3985 matrix: &ResidentStepBf16RowParallel,
3986 rank_activations: &[CudaSlice<f32>],
3987 tokens: usize,
3988 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3989 let reduced =
3990 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3991 let output_len = tokens
3992 .checked_mul(matrix.out_features)
3993 .ok_or("device Step BF16 row output size overflow")?;
3994 let mut ranks = Vec::with_capacity(self.ranks.len());
3995 ranks.push(reduced);
3996 for engine in self.ranks.iter().skip(1) {
3997 let _main = engine.gpu.enter_main()?;
3998 let mut peer_output = engine.uninit(output_len)?;
3999 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
4000 ranks.push(peer_output);
4001 }
4002 Ok(ResidentReplicatedDeviceRows {
4003 ranks,
4004 tokens,
4005 width: matrix.out_features,
4006 })
4007 }
4008
4009 pub fn upload_expert(
4010 &self,
4011 gate: E4m3BlockMatrix<'_>,
4012 up: E4m3BlockMatrix<'_>,
4013 down: E4m3BlockMatrix<'_>,
4014 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
4015 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4016 return Err("TP expert gate/up dimensions differ".into());
4017 }
4018 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4019 return Err(format!(
4020 "TP expert down {}x{} does not invert gate/up {}x{}",
4021 down.out_features, down.in_features, gate.out_features, gate.in_features
4022 )
4023 .into());
4024 }
4025 Ok(ResidentTpExpert {
4026 gate: self.upload_column_parallel(gate)?,
4027 up: self.upload_column_parallel(up)?,
4028 down: self.upload_row_parallel(down)?,
4029 input_width: gate.in_features,
4030 expert_width: gate.out_features,
4031 })
4032 }
4033
4034 pub fn run_expert(
4035 &self,
4036 expert: &ResidentTpExpert,
4037 input: &[f32],
4038 tokens: usize,
4039 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4040 validate_activations(input, tokens, expert.input_width)?;
4041 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
4042 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
4043 let activated: Vec<f32> = gate
4044 .gathered
4045 .iter()
4046 .zip(&up.gathered)
4047 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4048 .collect();
4049 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
4050 Ok(self
4051 .row_parallel_resident(&expert.down, &activated, tokens)?
4052 .reduced)
4053 }
4054
4055 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel(
4057 &self,
4058 gate: E4m3ExpertBank<'_>,
4059 up: E4m3ExpertBank<'_>,
4060 down: E4m3ExpertBank<'_>,
4061 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
4062 gate.validate()?;
4063 up.validate()?;
4064 down.validate()?;
4065 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4066 return Err("EP gate/up/down expert counts differ".into());
4067 }
4068 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4069 return Err("EP gate/up dimensions differ".into());
4070 }
4071 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4072 return Err(format!(
4073 "EP down {}x{} does not invert gate/up {}x{}",
4074 down.out_features, down.in_features, gate.out_features, gate.in_features
4075 )
4076 .into());
4077 }
4078 if gate.expert_count % self.ranks.len() != 0 {
4079 return Err(format!(
4080 "EP expert count {} is not divisible by {} ranks",
4081 gate.expert_count,
4082 self.ranks.len()
4083 )
4084 .into());
4085 }
4086
4087 let per_rank = gate.expert_count / self.ranks.len();
4088 let mut ranks = Vec::with_capacity(self.ranks.len());
4089 for (rank, engine) in self.ranks.iter().enumerate() {
4090 let expert_range = rank * per_rank..(rank + 1) * per_rank;
4091 ranks.push(ResidentEpRank {
4092 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
4093 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
4094 down: upload_expert_bank_rank(engine, down, expert_range)?,
4095 });
4096 }
4097 Ok(ResidentExpertParallel {
4098 ranks,
4099 expert_count: gate.expert_count,
4100 input_width: gate.in_features,
4101 expert_width: gate.out_features,
4102 })
4103 }
4104
4105 #[allow(clippy::too_many_arguments)]
4111 pub fn prepare_step_grouped_fp8_gate(
4112 &self,
4113 gate: E4m3ExpertBank<'_>,
4114 up: E4m3ExpertBank<'_>,
4115 down: E4m3ExpertBank<'_>,
4116 input: &[f32],
4117 tokens: usize,
4118 selected: &[usize],
4119 activation_limit: Option<f32>,
4120 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
4121 gate.validate()?;
4122 up.validate()?;
4123 down.validate()?;
4124 validate_step_expert_activation_limit(activation_limit)?;
4125 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
4126 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
4127 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
4128 {
4129 return Err(format!(
4130 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
4131 got gate/up/down={}/{}/{}",
4132 gate.expert_count, up.expert_count, down.expert_count,
4133 )
4134 .into());
4135 }
4136 if gate.in_features != up.in_features
4137 || gate.out_features != STEP_GROUPED_FP8_WIDTH
4138 || up.out_features != STEP_GROUPED_FP8_WIDTH
4139 || down.in_features != STEP_GROUPED_FP8_WIDTH
4140 || down.out_features != gate.in_features
4141 {
4142 return Err(format!(
4143 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
4144 gate.out_features,
4145 gate.in_features,
4146 up.out_features,
4147 up.in_features,
4148 down.out_features,
4149 down.in_features,
4150 )
4151 .into());
4152 }
4153 validate_activations(input, tokens, gate.in_features)?;
4154 let pairs = tokens
4155 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4156 .ok_or("official Step grouped FP8 route count overflow")?;
4157 if selected.len() != pairs {
4158 return Err(format!(
4159 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
4160 ({pairs})",
4161 selected.len()
4162 )
4163 .into());
4164 }
4165 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
4166 let mut unique = routes.to_vec();
4167 unique.sort_unstable();
4168 unique.dedup();
4169 if unique.len() != STEP_GROUPED_FP8_TOP_K {
4170 return Err(format!(
4171 "official Step grouped FP8 token {token} routes are not top-8 unique: \
4172 {routes:?}"
4173 )
4174 .into());
4175 }
4176 }
4177
4178 let engine = self
4179 .ranks
4180 .first()
4181 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4182 let _main = engine.gpu.enter_main()?;
4183 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
4184 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
4185 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
4186 let down = upload_expert_bank_rank(engine, down, expert_range)?;
4187 let input = engine.htod(input)?;
4188 let route_csr = ExpertCsr::from_token_routes(
4189 STEP_GROUPED_FP8_EXPERTS,
4190 tokens,
4191 STEP_GROUPED_FP8_TOP_K,
4192 selected,
4193 )?
4194 .upload(engine)?;
4195 let pair_rows = (0..pairs).collect::<Vec<_>>();
4196 let down_csr =
4197 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
4198 .upload(engine)?;
4199 let gate_workspace =
4200 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
4201 let up_workspace =
4202 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
4203 let down_workspace =
4204 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
4205 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
4206 Ok(PreparedStepGroupedFp8Gate {
4207 device: engine.ctx().ordinal(),
4208 gate,
4209 up,
4210 down,
4211 input,
4212 route_csr,
4213 down_csr,
4214 gate_workspace,
4215 up_workspace,
4216 down_workspace,
4217 activation,
4218 activation_limit,
4219 tokens,
4220 pairs,
4221 })
4222 }
4223
4224 pub fn run_step_grouped_fp8_gate(
4226 &self,
4227 plan: &mut PreparedStepGroupedFp8Gate,
4228 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4229 let engine = self
4230 .ranks
4231 .first()
4232 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4233 if engine.ctx().ordinal() != plan.device {
4234 return Err(format!(
4235 "official Step grouped FP8 plan device {} != rank-zero device {}",
4236 plan.device,
4237 engine.ctx().ordinal()
4238 )
4239 .into());
4240 }
4241 let _main = engine.gpu.enter_main()?;
4242
4243 plan.gate_workspace.quantize(engine, &plan.input)?;
4244 plan.gate_workspace.project(
4245 engine,
4246 &plan.gate.codes,
4247 &plan.gate.scales,
4248 &plan.route_csr,
4249 plan.gate.code_stride,
4250 plan.gate.scale_stride,
4251 1.0,
4252 )?;
4253 plan.up_workspace.quantize(engine, &plan.input)?;
4254 plan.up_workspace.project(
4255 engine,
4256 &plan.up.codes,
4257 &plan.up.scales,
4258 &plan.route_csr,
4259 plan.up.code_stride,
4260 plan.up.scale_stride,
4261 1.0,
4262 )?;
4263 if let Some(limit) = plan.activation_limit {
4264 engine.silu_clamped_mul_host_expf(
4265 plan.gate_workspace.output(),
4266 plan.up_workspace.output(),
4267 limit,
4268 &mut plan.activation,
4269 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4270 )?;
4271 } else {
4272 engine.silu_mul_host_expf(
4273 plan.gate_workspace.output(),
4274 plan.up_workspace.output(),
4275 &mut plan.activation,
4276 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4277 )?;
4278 }
4279 plan.down_workspace.quantize(engine, &plan.activation)?;
4280 plan.down_workspace.project(
4281 engine,
4282 &plan.down.codes,
4283 &plan.down.scales,
4284 &plan.down_csr,
4285 plan.down.code_stride,
4286 plan.down.scale_stride,
4287 1.0,
4288 )?;
4289
4290 Ok(StepGroupedFp8ProjectionOutput {
4291 gate: engine.dtoh(plan.gate_workspace.output())?,
4292 up: engine.dtoh(plan.up_workspace.output())?,
4293 down: engine.dtoh(plan.down_workspace.output())?,
4294 })
4295 }
4296
4297 pub fn prepare_step_grouped_expert_parallel_gate(
4298 &self,
4299 experts: &ResidentExpertParallel,
4300 input: &[f32],
4301 tokens: usize,
4302 selected: &[usize],
4303 activation_limit: Option<f32>,
4304 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4305 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
4306 experts,
4307 input,
4308 tokens,
4309 selected,
4310 activation_limit,
4311 tokens,
4312 )
4313 }
4314
4315 #[allow(clippy::too_many_arguments)]
4316 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
4317 &self,
4318 experts: &ResidentExpertParallel,
4319 input: &[f32],
4320 tokens: usize,
4321 selected: &[usize],
4322 activation_limit: Option<f32>,
4323 max_tokens: usize,
4324 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4325 if !self.native_p2p || !self.ep_device_arithmetic {
4326 return Err(
4327 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
4328 );
4329 }
4330 validate_step_expert_activation_limit(activation_limit)?;
4331 validate_ep_residency(&self.ranks, experts)?;
4332 validate_activations(input, tokens, experts.input_width)?;
4333 if max_tokens < tokens || max_tokens > i32::MAX as usize {
4334 return Err(format!(
4335 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
4336 )
4337 .into());
4338 }
4339 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
4340 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
4341 {
4342 return Err(format!(
4343 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
4344 STEP_GROUPED_FP8_EXPERTS,
4345 STEP_GROUPED_FP8_WIDTH,
4346 experts.expert_count,
4347 experts.expert_width,
4348 )
4349 .into());
4350 }
4351 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4352 let max_pairs = max_tokens
4353 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4354 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4355 let input_capacity = max_tokens
4356 .checked_mul(experts.input_width)
4357 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4358
4359 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4360 for engine in &self.ranks {
4361 let _main = engine.gpu.enter_main()?;
4362 rank_inputs.push(engine.uninit(input_capacity)?);
4363 }
4364
4365 let mut owners = Vec::with_capacity(self.ranks.len());
4366 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4367 if rank.gate.expert_range != rank.up.expert_range
4368 || rank.gate.expert_range != rank.down.expert_range
4369 {
4370 return Err(format!(
4371 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4372 owner_rank
4373 )
4374 .into());
4375 }
4376 let local_experts = rank.gate.expert_range.len();
4377 let engine = &self.ranks[owner_rank];
4378 let _main = engine.gpu.enter_main()?;
4379 let route_csr =
4380 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4381 let down_csr =
4382 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4383 let gate_workspace = Fp8GroupedWorkspace::new(
4384 engine,
4385 experts.input_width,
4386 experts.expert_width,
4387 max_tokens,
4388 max_pairs,
4389 )?;
4390 let up_workspace = Fp8GroupedWorkspace::new(
4391 engine,
4392 experts.input_width,
4393 experts.expert_width,
4394 max_tokens,
4395 max_pairs,
4396 )?;
4397 let down_workspace = Fp8GroupedWorkspace::new(
4398 engine,
4399 experts.expert_width,
4400 experts.input_width,
4401 max_pairs,
4402 max_pairs,
4403 )?;
4404 let activation = engine.uninit(
4405 max_pairs
4406 .checked_mul(experts.expert_width)
4407 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4408 )?;
4409 owners.push(PreparedStepGroupedExpertOwner {
4410 rank: owner_rank,
4411 global_pairs: Vec::new(),
4412 route_csr,
4413 down_csr,
4414 gate_workspace,
4415 up_workspace,
4416 down_workspace,
4417 activation,
4418 });
4419 }
4420
4421 let mut plan = PreparedStepGroupedExpertParallelGate {
4422 rank_inputs,
4423 owners,
4424 activation_limit,
4425 tokens: 0,
4426 pairs: 0,
4427 max_tokens,
4428 max_pairs,
4429 input_width: experts.input_width,
4430 expert_width: experts.expert_width,
4431 generation: 0,
4432 executed_generation: None,
4433 ready: false,
4434 };
4435 self.refresh_step_grouped_expert_parallel_gate(
4436 experts, &mut plan, input, tokens, selected,
4437 )?;
4438 Ok(plan)
4439 }
4440
4441 #[allow(clippy::type_complexity)] fn prepare_step_grouped_expert_parallel_refresh(
4443 &self,
4444 experts: &ResidentExpertParallel,
4445 plan: &PreparedStepGroupedExpertParallelGate,
4446 tokens: usize,
4447 selected: &[usize],
4448 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4449 {
4450 validate_ep_residency(&self.ranks, experts)?;
4451 if plan.rank_inputs.len() != self.ranks.len()
4452 || plan.owners.len() != self.ranks.len()
4453 || plan.input_width != experts.input_width
4454 || plan.expert_width != experts.expert_width
4455 || tokens > plan.max_tokens
4456 {
4457 return Err(format!(
4458 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4459 input={}/{} expert={}/{} tokens={}/{}",
4460 plan.rank_inputs.len(),
4461 self.ranks.len(),
4462 plan.owners.len(),
4463 self.ranks.len(),
4464 plan.input_width,
4465 experts.input_width,
4466 plan.expert_width,
4467 experts.expert_width,
4468 tokens,
4469 plan.max_tokens,
4470 )
4471 .into());
4472 }
4473 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4474 if pairs > plan.max_pairs {
4475 return Err(format!(
4476 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4477 plan.max_pairs
4478 )
4479 .into());
4480 }
4481 let next_generation = plan
4482 .generation
4483 .checked_add(1)
4484 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4485 let owner_routes = partition_expert_owner_routes(
4486 experts.expert_count,
4487 self.ranks.len(),
4488 tokens,
4489 STEP_GROUPED_FP8_TOP_K,
4490 selected,
4491 )?;
4492 let mut schedules = Vec::with_capacity(self.ranks.len());
4493 for routes in owner_routes {
4494 if routes.selected.is_empty() {
4495 schedules.push(None);
4496 continue;
4497 }
4498 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4499 let local_pairs = routes.selected.len();
4500 let route_csr = ExpertCsr::from_pair_rows(
4501 local_experts,
4502 tokens,
4503 &routes.selected,
4504 &routes.token_rows,
4505 )?;
4506 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4507 let down_csr = ExpertCsr::from_pair_rows(
4508 local_experts,
4509 local_pairs,
4510 &routes.selected,
4511 &down_rows,
4512 )?;
4513 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4514 global_pairs: routes.global_pairs,
4515 route_csr,
4516 down_csr,
4517 }));
4518 }
4519 Ok((pairs, next_generation, schedules))
4520 }
4521
4522 fn commit_step_grouped_expert_parallel_refresh(
4523 &self,
4524 plan: &mut PreparedStepGroupedExpertParallelGate,
4525 tokens: usize,
4526 pairs: usize,
4527 next_generation: u64,
4528 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4529 ) -> Result<(), Box<dyn std::error::Error>> {
4530 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4531 let engine = &self.ranks[owner.rank];
4532 let _main = engine.gpu.enter_main()?;
4533 if let Some(schedule) = schedule {
4534 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4535 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4536 owner.global_pairs = schedule.global_pairs;
4537 } else {
4538 owner.route_csr.clear();
4539 owner.down_csr.clear();
4540 owner.global_pairs.clear();
4541 }
4542 }
4543 plan.tokens = tokens;
4544 plan.pairs = pairs;
4545 plan.generation = next_generation;
4546 plan.ready = true;
4547 Ok(())
4548 }
4549
4550 pub fn refresh_step_grouped_expert_parallel_gate(
4551 &self,
4552 experts: &ResidentExpertParallel,
4553 plan: &mut PreparedStepGroupedExpertParallelGate,
4554 input: &[f32],
4555 tokens: usize,
4556 selected: &[usize],
4557 ) -> Result<(), Box<dyn std::error::Error>> {
4558 validate_activations(input, tokens, experts.input_width)?;
4559 let (pairs, next_generation, schedules) =
4560 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4561
4562 plan.ready = false;
4563 plan.executed_generation = None;
4564 {
4565 let root = &self.ranks[0];
4566 let _main = root.gpu.enter_main()?;
4567 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4568 root.stream().memcpy_htod(input, &mut destination)?;
4569 root.stream().synchronize()?;
4570 }
4571 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4572 let root_input = &root_inputs[0];
4573 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4574 let engine = &self.ranks[rank + 1];
4575 let _main = engine.gpu.enter_main()?;
4576 let mut destination = peer_input.slice_mut(0..input.len());
4577 engine
4578 .stream()
4579 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4580 }
4581 self.commit_step_grouped_expert_parallel_refresh(
4582 plan,
4583 tokens,
4584 pairs,
4585 next_generation,
4586 schedules,
4587 )
4588 }
4589
4590 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4595 &self,
4596 experts: &ResidentExpertParallel,
4597 plan: &mut PreparedStepGroupedExpertParallelGate,
4598 input: &CudaSlice<f32>,
4599 tokens: usize,
4600 selected: &[usize],
4601 ) -> Result<(), Box<dyn std::error::Error>> {
4602 let input_values = tokens
4603 .checked_mul(experts.input_width)
4604 .ok_or("Step owner-grouped FP8 input size overflow")?;
4605 let root = self
4606 .ranks
4607 .first()
4608 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4609 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4610 return Err(format!(
4611 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4612 device {}",
4613 input.len(),
4614 input.ordinal(),
4615 input_values,
4616 root.ctx().ordinal(),
4617 )
4618 .into());
4619 }
4620 let (pairs, next_generation, schedules) =
4621 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4622
4623 plan.ready = false;
4624 plan.executed_generation = None;
4625 {
4626 let _main = root.gpu.enter_main()?;
4627 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4628 root.stream()
4629 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4630 root.stream().synchronize()?;
4631 }
4632 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4633 let root_input = &root_inputs[0];
4634 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4635 let engine = &self.ranks[rank + 1];
4636 let _main = engine.gpu.enter_main()?;
4637 let mut destination = peer_input.slice_mut(0..input_values);
4638 engine
4639 .stream()
4640 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4641 }
4642 self.commit_step_grouped_expert_parallel_refresh(
4643 plan,
4644 tokens,
4645 pairs,
4646 next_generation,
4647 schedules,
4648 )
4649 }
4650
4651 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4656 &self,
4657 experts: &ResidentExpertParallel,
4658 plan: &mut PreparedStepGroupedExpertParallelGate,
4659 input: &ResidentReplicatedDeviceRows,
4660 ) -> Result<(), Box<dyn std::error::Error>> {
4661 validate_ep_residency(&self.ranks, experts)?;
4662 validate_replicated_device_rows(&self.ranks, input)?;
4663 if !plan.ready
4664 || input.tokens != plan.tokens
4665 || input.width != plan.input_width
4666 || input.tokens > plan.max_tokens
4667 || plan.rank_inputs.len() != self.ranks.len()
4668 || plan.owners.len() != self.ranks.len()
4669 || plan.input_width != experts.input_width
4670 || plan.expert_width != experts.expert_width
4671 {
4672 return Err("Step owner-grouped replicated input geometry changed".into());
4673 }
4674 let values = input
4675 .tokens
4676 .checked_mul(input.width)
4677 .ok_or("Step owner-grouped replicated input size overflow")?;
4678 let next_generation = plan
4679 .generation
4680 .checked_add(1)
4681 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4682 plan.ready = false;
4683 plan.executed_generation = None;
4684 for (rank, engine) in self.ranks.iter().enumerate() {
4685 let _main = engine.gpu.enter_main()?;
4686 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4687 engine
4688 .stream()
4689 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4690 }
4691 plan.generation = next_generation;
4692 plan.ready = true;
4693 Ok(())
4694 }
4695
4696 pub fn execute_step_grouped_expert_parallel_gate(
4697 &self,
4698 experts: &ResidentExpertParallel,
4699 plan: &mut PreparedStepGroupedExpertParallelGate,
4700 ) -> Result<(), Box<dyn std::error::Error>> {
4701 validate_ep_residency(&self.ranks, experts)?;
4702 if !plan.ready
4703 || plan.rank_inputs.len() != self.ranks.len()
4704 || plan.owners.len() != self.ranks.len()
4705 || plan.input_width != experts.input_width
4706 || plan.expert_width != experts.expert_width
4707 {
4708 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4709 }
4710 plan.executed_generation = None;
4711
4712 for owner in &mut plan.owners {
4713 if owner.global_pairs.is_empty() {
4714 continue;
4715 }
4716 let engine = &self.ranks[owner.rank];
4717 let bank = &experts.ranks[owner.rank];
4718 let _main = engine.gpu.enter_main()?;
4719 let local_pairs = owner.global_pairs.len();
4720 owner.gate_workspace.quantize_for_shape(
4721 engine,
4722 &plan.rank_inputs[owner.rank],
4723 plan.tokens,
4724 local_pairs,
4725 )?;
4726 owner.gate_workspace.project(
4727 engine,
4728 &bank.gate.codes,
4729 &bank.gate.scales,
4730 &owner.route_csr,
4731 bank.gate.code_stride,
4732 bank.gate.scale_stride,
4733 1.0,
4734 )?;
4735 owner.up_workspace.quantize_for_shape(
4736 engine,
4737 &plan.rank_inputs[owner.rank],
4738 plan.tokens,
4739 local_pairs,
4740 )?;
4741 owner.up_workspace.project(
4742 engine,
4743 &bank.up.codes,
4744 &bank.up.scales,
4745 &owner.route_csr,
4746 bank.up.code_stride,
4747 bank.up.scale_stride,
4748 1.0,
4749 )?;
4750 }
4751 for owner in &mut plan.owners {
4752 if owner.global_pairs.is_empty() {
4753 continue;
4754 }
4755 let engine = &self.ranks[owner.rank];
4756 let _main = engine.gpu.enter_main()?;
4757 let values = owner.global_pairs.len() * plan.expert_width;
4758 if let Some(limit) = plan.activation_limit {
4759 engine.silu_clamped_mul_host_expf(
4760 owner.gate_workspace.output(),
4761 owner.up_workspace.output(),
4762 limit,
4763 &mut owner.activation,
4764 values,
4765 )?;
4766 } else {
4767 engine.silu_mul_host_expf(
4768 owner.gate_workspace.output(),
4769 owner.up_workspace.output(),
4770 &mut owner.activation,
4771 values,
4772 )?;
4773 }
4774 }
4775 for owner in &mut plan.owners {
4776 if owner.global_pairs.is_empty() {
4777 continue;
4778 }
4779 let engine = &self.ranks[owner.rank];
4780 let bank = &experts.ranks[owner.rank];
4781 let _main = engine.gpu.enter_main()?;
4782 let local_pairs = owner.global_pairs.len();
4783 owner.down_workspace.quantize_for_shape(
4784 engine,
4785 &owner.activation,
4786 local_pairs,
4787 local_pairs,
4788 )?;
4789 owner.down_workspace.project(
4790 engine,
4791 &bank.down.codes,
4792 &bank.down.scales,
4793 &owner.down_csr,
4794 bank.down.code_stride,
4795 bank.down.scale_stride,
4796 1.0,
4797 )?;
4798 }
4799 plan.executed_generation = Some(plan.generation);
4800 Ok(())
4801 }
4802
4803 pub fn collect_step_grouped_expert_parallel_gate(
4804 &self,
4805 plan: &PreparedStepGroupedExpertParallelGate,
4806 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4807 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4808 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4809 }
4810 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4811 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4812 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4813 for owner in &plan.owners {
4814 if owner.global_pairs.is_empty() {
4815 continue;
4816 }
4817 let engine = &self.ranks[owner.rank];
4818 let _main = engine.gpu.enter_main()?;
4819 let owner_gate = engine.dtoh_view(
4820 &owner
4821 .gate_workspace
4822 .output()
4823 .slice(0..owner.gate_workspace.output_len()),
4824 )?;
4825 let owner_up = engine.dtoh_view(
4826 &owner
4827 .up_workspace
4828 .output()
4829 .slice(0..owner.up_workspace.output_len()),
4830 )?;
4831 let owner_down = engine.dtoh_view(
4832 &owner
4833 .down_workspace
4834 .output()
4835 .slice(0..owner.down_workspace.output_len()),
4836 )?;
4837 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4838 let local_expert = local_pair * plan.expert_width;
4839 let global_expert = global_pair * plan.expert_width;
4840 gate[global_expert..global_expert + plan.expert_width]
4841 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4842 up[global_expert..global_expert + plan.expert_width]
4843 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4844
4845 let local_hidden = local_pair * plan.input_width;
4846 let global_hidden = global_pair * plan.input_width;
4847 down[global_hidden..global_hidden + plan.input_width]
4848 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4849 }
4850 }
4851 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4852 }
4853
4854 pub fn run_step_grouped_expert_parallel_gate(
4855 &self,
4856 experts: &ResidentExpertParallel,
4857 plan: &mut PreparedStepGroupedExpertParallelGate,
4858 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4859 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4860 self.collect_step_grouped_expert_parallel_gate(plan)
4861 }
4862
4863 pub fn prepare_step_grouped_expert_parallel_combine(
4864 &self,
4865 plan: &PreparedStepGroupedExpertParallelGate,
4866 route_weights: &[f32],
4867 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4868 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4869 return Err(
4870 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4871 );
4872 }
4873 let owner_pairs = plan
4874 .owners
4875 .iter()
4876 .map(|owner| owner.global_pairs.as_slice())
4877 .collect::<Vec<_>>();
4878 let shape = validate_weighted_route_combine(
4879 plan.input_width,
4880 STEP_GROUPED_FP8_TOP_K,
4881 plan.max_tokens,
4882 plan.tokens,
4883 &owner_pairs,
4884 route_weights,
4885 )?;
4886 if shape.max_pairs != plan.max_pairs {
4887 return Err(format!(
4888 "Step owner-grouped combine capacity {} != projection capacity {}",
4889 shape.max_pairs, plan.max_pairs
4890 )
4891 .into());
4892 }
4893 let root = self
4894 .ranks
4895 .first()
4896 .ok_or("Step owner-grouped combine has no root rank")?;
4897 let slot_values = shape
4898 .max_pairs
4899 .checked_mul(plan.input_width)
4900 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4901 let output_values = plan
4902 .max_tokens
4903 .checked_mul(plan.input_width)
4904 .ok_or("Step owner-grouped combine output capacity overflow")?;
4905 let (root_device, owners, peer_staging, slots, weights, output) = {
4906 let _main = root.gpu.enter_main()?;
4907 let mut owners = Vec::with_capacity(plan.owners.len());
4908 for _ in &plan.owners {
4909 owners.push(PreparedPeerWeightedRouteOwner {
4910 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4911 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4912 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4913 active_pairs: 0,
4914 });
4915 }
4916 (
4917 root.ctx().ordinal(),
4918 owners,
4919 root.uninit(slot_values)?,
4920 root.uninit(slot_values)?,
4921 root.uninit(shape.max_pairs)?,
4922 root.uninit(output_values)?,
4923 )
4924 };
4925 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4926 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4927 for engine in self.ranks.iter().skip(1) {
4928 let _main = engine.gpu.enter_main()?;
4929 peer_devices.push(engine.ctx().ordinal());
4930 peer_outputs.push(engine.uninit(output_values)?);
4931 }
4932 let mut combine = PreparedPeerWeightedRouteCombine {
4933 root_device,
4934 owners,
4935 peer_staging,
4936 slots,
4937 weights,
4938 output,
4939 peer_devices,
4940 peer_outputs,
4941 width: plan.input_width,
4942 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4943 max_tokens: plan.max_tokens,
4944 max_pairs: shape.max_pairs,
4945 tokens: 0,
4946 pairs: 0,
4947 projection_generation: 0,
4948 output_generation: None,
4949 broadcast_generation: None,
4950 ready: false,
4951 };
4952 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4953 Ok(combine)
4954 }
4955
4956 pub fn refresh_step_grouped_expert_parallel_combine(
4957 &self,
4958 plan: &PreparedStepGroupedExpertParallelGate,
4959 combine: &mut PreparedPeerWeightedRouteCombine,
4960 route_weights: &[f32],
4961 ) -> Result<(), Box<dyn std::error::Error>> {
4962 let output_capacity = combine
4963 .max_tokens
4964 .checked_mul(combine.width)
4965 .ok_or("Step owner-grouped combine output capacity overflow")?;
4966 if !plan.ready
4967 || combine.owners.len() != plan.owners.len()
4968 || combine.peer_devices.len() + 1 != self.ranks.len()
4969 || combine.peer_outputs.len() + 1 != self.ranks.len()
4970 || combine.width != plan.input_width
4971 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4972 || combine.max_tokens != plan.max_tokens
4973 || combine.max_pairs != plan.max_pairs
4974 || combine.output.len() < output_capacity
4975 || combine
4976 .peer_outputs
4977 .iter()
4978 .any(|output| output.len() < output_capacity)
4979 {
4980 return Err("Step owner-grouped combine/projection geometry changed".into());
4981 }
4982 if self
4983 .ranks
4984 .iter()
4985 .skip(1)
4986 .zip(&combine.peer_devices)
4987 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4988 {
4989 return Err("Step owner-grouped combine peer devices changed".into());
4990 }
4991 let owner_pairs = plan
4992 .owners
4993 .iter()
4994 .map(|owner| owner.global_pairs.as_slice())
4995 .collect::<Vec<_>>();
4996 let shape = validate_weighted_route_combine(
4997 combine.width,
4998 combine.experts_per_token,
4999 combine.max_tokens,
5000 plan.tokens,
5001 &owner_pairs,
5002 route_weights,
5003 )?;
5004 if shape.max_pairs != combine.max_pairs {
5005 return Err("Step owner-grouped combine capacity changed during refresh".into());
5006 }
5007 let metadata = owner_pairs
5008 .iter()
5009 .map(|pairs| {
5010 let token_rows = pairs
5011 .iter()
5012 .map(|&pair| (pair / combine.experts_per_token) as i32)
5013 .collect::<Vec<_>>();
5014 let slots = pairs
5015 .iter()
5016 .map(|&pair| (pair % combine.experts_per_token) as i32)
5017 .collect::<Vec<_>>();
5018 let weights = pairs
5019 .iter()
5020 .map(|&pair| route_weights[pair])
5021 .collect::<Vec<_>>();
5022 (token_rows, slots, weights)
5023 })
5024 .collect::<Vec<_>>();
5025
5026 combine.ready = false;
5027 combine.output_generation = None;
5028 combine.broadcast_generation = None;
5029 let root = self
5030 .ranks
5031 .first()
5032 .ok_or("Step owner-grouped combine has no root rank")?;
5033 let _main = root.gpu.enter_main()?;
5034 if root.ctx().ordinal() != combine.root_device {
5035 return Err(format!(
5036 "Step owner-grouped combine root device changed {} != {}",
5037 root.ctx().ordinal(),
5038 combine.root_device
5039 )
5040 .into());
5041 }
5042 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
5043 if token_rows.is_empty() {
5044 owner.active_pairs = 0;
5045 continue;
5046 }
5047 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
5048 root.htod_i32_into(&mut owner.slots, &slots)?;
5049 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
5050 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
5051 owner.active_pairs = token_rows.len();
5052 }
5053 combine.tokens = plan.tokens;
5054 combine.pairs = shape.pairs;
5055 combine.projection_generation = plan.generation;
5056 combine.ready = true;
5057 Ok(())
5058 }
5059
5060 pub fn execute_step_grouped_expert_parallel_combine(
5061 &self,
5062 plan: &PreparedStepGroupedExpertParallelGate,
5063 combine: &mut PreparedPeerWeightedRouteCombine,
5064 ) -> Result<(), Box<dyn std::error::Error>> {
5065 if !plan.ready
5066 || plan.executed_generation != Some(plan.generation)
5067 || !combine.ready
5068 || combine.tokens != plan.tokens
5069 || combine.pairs != plan.pairs
5070 || combine.width != plan.input_width
5071 || combine.owners.len() != plan.owners.len()
5072 || combine.projection_generation != plan.generation
5073 {
5074 return Err("Step owner-grouped combine is stale or its geometry changed".into());
5075 }
5076 combine.output_generation = None;
5077 combine.broadcast_generation = None;
5078 for owner in &plan.owners {
5079 if owner.rank == 0 || owner.global_pairs.is_empty() {
5080 continue;
5081 }
5082 let engine = &self.ranks[owner.rank];
5083 let _main = engine.gpu.enter_main()?;
5084 engine.stream().synchronize()?;
5085 }
5086 let root = self
5087 .ranks
5088 .first()
5089 .ok_or("Step owner-grouped combine has no root rank")?;
5090 let _main = root.gpu.enter_main()?;
5091 if root.ctx().ordinal() != combine.root_device {
5092 return Err("Step owner-grouped combine is not resident on the root device".into());
5093 }
5094 for (index, owner) in plan.owners.iter().enumerate() {
5095 let metadata = &combine.owners[index];
5096 if owner.global_pairs.len() != metadata.active_pairs {
5097 return Err(format!(
5098 "Step owner-grouped combine owner {index} rows {} != metadata {}",
5099 owner.global_pairs.len(),
5100 metadata.active_pairs
5101 )
5102 .into());
5103 }
5104 if metadata.active_pairs == 0 {
5105 continue;
5106 }
5107 let values = metadata
5108 .active_pairs
5109 .checked_mul(combine.width)
5110 .ok_or("Step owner-grouped combine peer value count overflow")?;
5111 if owner.rank == 0 {
5112 root.scatter_slot(
5113 owner.down_workspace.output(),
5114 &metadata.token_rows,
5115 &metadata.slots,
5116 &metadata.weights,
5117 &mut combine.slots,
5118 &mut combine.weights,
5119 combine.width,
5120 combine.experts_per_token,
5121 metadata.active_pairs,
5122 )?;
5123 } else {
5124 let source = owner.down_workspace.output().slice(0..values);
5125 let mut destination = combine.peer_staging.slice_mut(0..values);
5126 root.stream().memcpy_dtod(&source, &mut destination)?;
5127 root.scatter_slot(
5128 &combine.peer_staging,
5129 &metadata.token_rows,
5130 &metadata.slots,
5131 &metadata.weights,
5132 &mut combine.slots,
5133 &mut combine.weights,
5134 combine.width,
5135 combine.experts_per_token,
5136 metadata.active_pairs,
5137 )?;
5138 }
5139 }
5140 root.reduce_slots_host(
5141 &combine.slots,
5142 &combine.weights,
5143 &mut combine.output,
5144 combine.width,
5145 combine.experts_per_token,
5146 combine.tokens,
5147 )?;
5148 combine.output_generation = Some(plan.generation);
5149 Ok(())
5150 }
5151
5152 pub fn collect_step_grouped_expert_parallel_combine(
5153 &self,
5154 plan: &PreparedStepGroupedExpertParallelGate,
5155 combine: &PreparedPeerWeightedRouteCombine,
5156 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5157 if !plan.ready
5158 || combine.output_generation != Some(plan.generation)
5159 || combine.projection_generation != plan.generation
5160 {
5161 return Err("Step owner-grouped combine output is stale or has not executed".into());
5162 }
5163 let root = self
5164 .ranks
5165 .first()
5166 .ok_or("Step owner-grouped combine has no root rank")?;
5167 let _main = root.gpu.enter_main()?;
5168 if root.ctx().ordinal() != combine.root_device {
5169 return Err("Step owner-grouped combine is not resident on the root device".into());
5170 }
5171 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
5172 }
5173
5174 pub fn copy_step_grouped_expert_parallel_combine_root(
5179 &self,
5180 plan: &PreparedStepGroupedExpertParallelGate,
5181 combine: &PreparedPeerWeightedRouteCombine,
5182 destination: &Engine,
5183 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5184 if !plan.ready
5185 || combine.output_generation != Some(plan.generation)
5186 || combine.projection_generation != plan.generation
5187 {
5188 return Err("Step owner-grouped combine output is stale or has not executed".into());
5189 }
5190 let root = self
5191 .ranks
5192 .first()
5193 .ok_or("Step owner-grouped combine has no root rank")?;
5194 if root.ctx().ordinal() != combine.root_device
5195 || destination.ctx().ordinal() != combine.root_device
5196 {
5197 return Err(format!(
5198 "Step owner-grouped combine root/destination devices {}/{} != {}",
5199 root.ctx().ordinal(),
5200 destination.ctx().ordinal(),
5201 combine.root_device,
5202 )
5203 .into());
5204 }
5205 let values = combine
5206 .tokens
5207 .checked_mul(combine.width)
5208 .ok_or("Step owner-grouped combine copy size overflow")?;
5209 {
5210 let _main = root.gpu.enter_main()?;
5211 root.stream().synchronize()?;
5212 }
5213 let _main = destination.gpu.enter_main()?;
5214 let mut output = destination.uninit(values)?;
5215 destination
5216 .stream()
5217 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
5218 Ok(output)
5219 }
5220
5221 pub fn broadcast_step_grouped_expert_parallel_combine(
5222 &self,
5223 plan: &PreparedStepGroupedExpertParallelGate,
5224 combine: &mut PreparedPeerWeightedRouteCombine,
5225 ) -> Result<(), Box<dyn std::error::Error>> {
5226 if !plan.ready
5227 || combine.output_generation != Some(plan.generation)
5228 || combine.projection_generation != plan.generation
5229 || combine.peer_devices.len() + 1 != self.ranks.len()
5230 || combine.peer_outputs.len() + 1 != self.ranks.len()
5231 {
5232 return Err("Step owner-grouped combine output cannot be broadcast".into());
5233 }
5234 combine.broadcast_generation = None;
5235 let values = combine
5236 .tokens
5237 .checked_mul(combine.width)
5238 .ok_or("Step owner-grouped combine broadcast size overflow")?;
5239 {
5240 let root = self
5241 .ranks
5242 .first()
5243 .ok_or("Step owner-grouped combine has no root rank")?;
5244 let _main = root.gpu.enter_main()?;
5245 if root.ctx().ordinal() != combine.root_device {
5246 return Err("Step owner-grouped combine root device changed".into());
5247 }
5248 root.stream().synchronize()?;
5249 }
5250 let source = &combine.output;
5251 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
5252 let engine = &self.ranks[index + 1];
5253 let _main = engine.gpu.enter_main()?;
5254 if engine.ctx().ordinal() != combine.peer_devices[index] {
5255 return Err(format!(
5256 "Step owner-grouped combine peer {} device changed",
5257 index + 1
5258 )
5259 .into());
5260 }
5261 let mut destination = destination_buffer.slice_mut(0..values);
5262 engine
5263 .stream()
5264 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
5265 }
5266 combine.broadcast_generation = Some(plan.generation);
5267 Ok(())
5268 }
5269
5270 pub fn collect_step_grouped_expert_parallel_broadcast(
5271 &self,
5272 plan: &PreparedStepGroupedExpertParallelGate,
5273 combine: &PreparedPeerWeightedRouteCombine,
5274 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
5275 if !plan.ready
5276 || combine.output_generation != Some(plan.generation)
5277 || combine.broadcast_generation != Some(plan.generation)
5278 || combine.peer_outputs.len() + 1 != self.ranks.len()
5279 {
5280 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
5281 }
5282 let values = combine
5283 .tokens
5284 .checked_mul(combine.width)
5285 .ok_or("Step owner-grouped combine collection size overflow")?;
5286 let mut outputs = Vec::with_capacity(self.ranks.len());
5287 {
5288 let root = &self.ranks[0];
5289 let _main = root.gpu.enter_main()?;
5290 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
5291 }
5292 for (index, output) in combine.peer_outputs.iter().enumerate() {
5293 let engine = &self.ranks[index + 1];
5294 let _main = engine.gpu.enter_main()?;
5295 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
5296 }
5297 Ok(outputs)
5298 }
5299
5300 pub fn finish_step_grouped_expert_parallel_layer(
5302 &self,
5303 plan: &PreparedStepGroupedExpertParallelGate,
5304 combine: &PreparedPeerWeightedRouteCombine,
5305 shared: &ResidentReplicatedDeviceRows,
5306 residual: &ResidentReplicatedDeviceRows,
5307 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
5308 validate_replicated_device_rows(&self.ranks, shared)?;
5309 validate_replicated_device_rows(&self.ranks, residual)?;
5310 if !plan.ready
5311 || plan.executed_generation != Some(plan.generation)
5312 || combine.output_generation != Some(plan.generation)
5313 || combine.broadcast_generation != Some(plan.generation)
5314 || combine.projection_generation != plan.generation
5315 || combine.peer_outputs.len() + 1 != self.ranks.len()
5316 || shared.tokens != combine.tokens
5317 || residual.tokens != combine.tokens
5318 || shared.width != combine.width
5319 || residual.width != combine.width
5320 {
5321 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
5322 }
5323 let values = combine
5324 .tokens
5325 .checked_mul(combine.width)
5326 .ok_or("Step full-layer output size overflow")?;
5327 let mut ranks = Vec::with_capacity(self.ranks.len());
5328 for rank in 0..self.ranks.len() {
5329 let engine = &self.ranks[rank];
5330 let _main = engine.gpu.enter_main()?;
5331 let routed = if rank == 0 {
5332 &combine.output
5333 } else {
5334 &combine.peer_outputs[rank - 1]
5335 };
5336 let mut ffn = engine.uninit(values)?;
5337 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
5338 let mut output = engine.uninit(values)?;
5339 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
5340 ranks.push(output);
5341 }
5342 Ok(ResidentReplicatedDeviceRows {
5343 ranks,
5344 tokens: combine.tokens,
5345 width: combine.width,
5346 })
5347 }
5348
5349 pub fn run_step_grouped_expert_parallel_combine(
5350 &self,
5351 plan: &PreparedStepGroupedExpertParallelGate,
5352 combine: &mut PreparedPeerWeightedRouteCombine,
5353 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5354 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5355 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5356 }
5357
5358 pub fn upload_tensor_parallel(
5359 &self,
5360 gate: E4m3ExpertBank<'_>,
5361 up: E4m3ExpertBank<'_>,
5362 down: E4m3ExpertBank<'_>,
5363 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5364 gate.validate()?;
5365 up.validate()?;
5366 down.validate()?;
5367 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5368 return Err("TP gate/up/down expert counts differ".into());
5369 }
5370 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5371 return Err("TP gate/up dimensions differ".into());
5372 }
5373 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5374 return Err(format!(
5375 "TP down {}x{} does not invert gate/up {}x{}",
5376 down.out_features, down.in_features, gate.out_features, gate.in_features
5377 )
5378 .into());
5379 }
5380 let tp = self.ranks.len();
5381 validate_column_bank_shape(gate, tp)?;
5382 validate_column_bank_shape(up, tp)?;
5383 validate_row_bank_shape(down, tp)?;
5384
5385 let mut gate_ranks = Vec::with_capacity(tp);
5386 let mut up_ranks = Vec::with_capacity(tp);
5387 let mut down_ranks = Vec::with_capacity(tp);
5388 for (rank, engine) in self.ranks.iter().enumerate() {
5389 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5390 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5391 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5392 }
5393 Ok(ResidentTensorParallel {
5394 bank: ResidentTpExpertBank {
5395 gate: gate_ranks,
5396 up: up_ranks,
5397 down: down_ranks,
5398 expert_count: gate.expert_count,
5399 input_width: gate.in_features,
5400 expert_width: gate.out_features,
5401 },
5402 })
5403 }
5404
5405 pub fn run_tensor_parallel_routes(
5406 &self,
5407 experts: &ResidentTensorParallel,
5408 input: &[f32],
5409 tokens: usize,
5410 selected: &[usize],
5411 route_weights: &[f32],
5412 experts_per_token: usize,
5413 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5414 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5415 validate_activations(input, tokens, experts.bank.input_width)?;
5416 let pairs = tokens
5417 .checked_mul(experts_per_token)
5418 .ok_or("TP route count overflow")?;
5419 if selected.len() != pairs || route_weights.len() != pairs {
5420 return Err(format!(
5421 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5422 {experts_per_token} ({pairs})",
5423 selected.len(),
5424 route_weights.len(),
5425 )
5426 .into());
5427 }
5428 if !route_weights.iter().all(|weight| weight.is_finite()) {
5429 return Err("TP route weights contain a non-finite value".into());
5430 }
5431
5432 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5433 for token in 0..tokens {
5434 let input_row =
5435 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5436 for slot in 0..experts_per_token {
5437 let pair = token * experts_per_token + slot;
5438 let expert = selected[pair];
5439 if expert >= experts.bank.expert_count {
5440 return Err(format!(
5441 "TP selected expert {expert} outside 0..{}",
5442 experts.bank.expert_count
5443 )
5444 .into());
5445 }
5446 let down = if self.native_p2p {
5447 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5448 } else {
5449 let gate =
5450 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5451 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5452 let activated: Vec<f32> = gate
5453 .iter()
5454 .zip(&up)
5455 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5456 .collect();
5457 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5458 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5459 };
5460 let weight = route_weights[pair];
5461 for (sum, value) in output
5462 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5463 .iter_mut()
5464 .zip(down)
5465 {
5466 *sum += weight * value;
5467 }
5468 }
5469 }
5470 Ok(output)
5471 }
5472
5473 fn run_column_bank_expert(
5474 &self,
5475 ranks: &[ResidentE4m3ExpertBankRank],
5476 expert: usize,
5477 input: &[f32],
5478 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5479 let local_out = ranks
5480 .first()
5481 .ok_or("TP column bank has no ranks")?
5482 .out_features;
5483 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5484 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5485 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5486 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5487 }
5488 Ok(gathered)
5489 }
5490
5491 fn run_row_bank_expert(
5492 &self,
5493 ranks: &[ResidentE4m3ExpertBankRank],
5494 expert: usize,
5495 input: &[f32],
5496 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5497 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5498 if input.len() != local_in * ranks.len() {
5499 return Err(format!(
5500 "TP row input {} != {} ranks x {local_in}",
5501 input.len(),
5502 ranks.len()
5503 )
5504 .into());
5505 }
5506 let out_features = ranks[0].out_features;
5507 let mut reduced = vec![0.0f32; out_features];
5508 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5509 let blocks = bank
5510 .k_blocks
5511 .ok_or("TP row bank is not packed in native K-block order")?;
5512 if blocks * FP8_BLOCK != local_in {
5513 return Err(format!(
5514 "TP row bank has {blocks} blocks but local input width is {local_in}"
5515 )
5516 .into());
5517 }
5518 for block in 0..blocks {
5519 let global_start = rank * local_in + block * FP8_BLOCK;
5520 let partial = run_resident_bank_expert_block(
5521 engine,
5522 bank,
5523 expert,
5524 block,
5525 &input[global_start..global_start + FP8_BLOCK],
5526 )?;
5527 for (sum, value) in reduced.iter_mut().zip(partial) {
5528 *sum += value;
5529 }
5530 }
5531 }
5532 Ok(reduced)
5533 }
5534
5535 fn run_tensor_parallel_expert_native(
5536 &self,
5537 bank: &ResidentTpExpertBank,
5538 expert: usize,
5539 input: &[f32],
5540 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5541 if !self.native_p2p || self.ranks.len() < 2 {
5542 return Err("native TP expert execution requires at least two P2P ranks".into());
5543 }
5544 let local_out = bank
5545 .gate
5546 .first()
5547 .ok_or("native TP gate bank has no ranks")?
5548 .out_features;
5549 if local_out * self.ranks.len() != bank.expert_width {
5550 return Err(format!(
5551 "native TP gate shards {}x{local_out} != expert width {}",
5552 self.ranks.len(),
5553 bank.expert_width
5554 )
5555 .into());
5556 }
5557
5558 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5561 let root_input = {
5562 let root = &self.ranks[0];
5563 let _main = root.gpu.enter_main()?;
5564 root.htod(input)?
5565 };
5566 rank_inputs.push(root_input);
5567 for engine in &self.ranks[1..] {
5568 let peer_input = {
5569 let _main = engine.gpu.enter_main()?;
5570 let mut peer_input = engine.uninit(input.len())?;
5571 engine
5572 .stream()
5573 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5574 peer_input
5575 };
5576 rank_inputs.push(peer_input);
5577 }
5578
5579 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5580 let mut up_shards = Vec::with_capacity(self.ranks.len());
5581 #[allow(clippy::needless_range_loop)]
5582 for rank in 0..self.ranks.len() {
5584 gate_shards.push(run_resident_bank_expert_device(
5585 &self.ranks[rank],
5586 &bank.gate[rank],
5587 expert,
5588 &rank_inputs[rank],
5589 1,
5590 )?);
5591 up_shards.push(run_resident_bank_expert_device(
5592 &self.ranks[rank],
5593 &bank.up[rank],
5594 expert,
5595 &rank_inputs[rank],
5596 1,
5597 )?);
5598 }
5599
5600 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5604 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5605 let activated = gate
5606 .iter()
5607 .zip(&up)
5608 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5609 .collect::<Vec<_>>();
5610 debug_assert_eq!(activated.len(), bank.expert_width);
5611
5612 let root_activated = {
5613 let root = &self.ranks[0];
5614 let _main = root.gpu.enter_main()?;
5615 root.htod(&activated)?
5616 };
5617 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5618 for (rank, engine) in self.ranks.iter().enumerate() {
5619 let start = rank * local_out;
5620 let source = root_activated.slice(start..start + local_out);
5621 let local = {
5622 let _main = engine.gpu.enter_main()?;
5623 let mut local = engine.uninit(local_out)?;
5624 engine.stream().memcpy_dtod(&source, &mut local)?;
5625 local
5626 };
5627 rank_activated.push(local);
5628 }
5629
5630 let out_features = bank
5631 .down
5632 .first()
5633 .ok_or("native TP down bank has no ranks")?
5634 .out_features;
5635 let mut reduced = {
5636 let root = &self.ranks[0];
5637 let _main = root.gpu.enter_main()?;
5638 root.htod(&vec![0.0f32; out_features])?
5639 };
5640 let mut remote_partial_keepalive = Vec::new();
5641 #[allow(clippy::needless_range_loop)]
5642 for rank in 0..self.ranks.len() {
5644 let down = &bank.down[rank];
5645 let blocks = down
5646 .k_blocks
5647 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5648 if blocks * FP8_BLOCK != local_out {
5649 return Err(format!(
5650 "native TP rank {rank} has {blocks} blocks but local activation width is \
5651 {local_out}"
5652 )
5653 .into());
5654 }
5655 for block in 0..blocks {
5656 let start = block * FP8_BLOCK;
5657 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5658 let partial = run_resident_bank_expert_block_device(
5659 &self.ranks[rank],
5660 down,
5661 expert,
5662 block,
5663 &input_block,
5664 )?;
5665 let root_partial = if rank == 0 {
5666 partial
5667 } else {
5668 let root = &self.ranks[0];
5669 let _main = root.gpu.enter_main()?;
5670 let mut peer_partial = root.uninit(out_features)?;
5671 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5672 remote_partial_keepalive.push(partial);
5673 peer_partial
5674 };
5675 let next = {
5676 let root = &self.ranks[0];
5677 let _main = root.gpu.enter_main()?;
5678 let mut next = root.uninit(out_features)?;
5679 root.add(&reduced, &root_partial, &mut next, out_features)?;
5680 next
5681 };
5682 reduced = next;
5683 }
5684 }
5685 let output = {
5686 let root = &self.ranks[0];
5687 let _main = root.gpu.enter_main()?;
5688 root.dtoh(&reduced)?
5689 };
5690 drop(remote_partial_keepalive);
5691 Ok(output)
5692 }
5693
5694 pub fn gather_native_column_shards_device(
5696 &self,
5697 shards: &[CudaSlice<f32>],
5698 tokens: usize,
5699 local_out: usize,
5700 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5701 let shard_len = tokens
5702 .checked_mul(local_out)
5703 .ok_or("native TP gather shard size overflow")?;
5704 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5705 return Err("native TP gather shard geometry mismatch".into());
5706 }
5707 for engine in &self.ranks[1..] {
5711 let _main = engine.gpu.enter_main()?;
5712 engine.stream().synchronize()?;
5713 }
5714 let root = &self.ranks[0];
5715 let _main = root.gpu.enter_main()?;
5716 let global_out = shards
5717 .len()
5718 .checked_mul(local_out)
5719 .ok_or("native TP gather output width overflow")?;
5720 let gathered_len = tokens
5721 .checked_mul(global_out)
5722 .ok_or("native TP gather output size overflow")?;
5723 let mut gathered = root.uninit(gathered_len)?;
5724 if self.bulk_p2p {
5725 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5726 if shards.len() > 1 {
5727 let mut staging = root.uninit(shard_len)?;
5728 for (rank, shard) in shards.iter().enumerate().skip(1) {
5729 root.stream().memcpy_dtod(shard, &mut staging)?;
5730 root.place_rows_strided(
5731 &staging,
5732 &mut gathered,
5733 local_out,
5734 tokens,
5735 global_out,
5736 rank * local_out,
5737 )?;
5738 }
5739 }
5740 } else {
5741 for token in 0..tokens {
5742 for (rank, shard) in shards.iter().enumerate() {
5743 let source = shard.slice(token * local_out..(token + 1) * local_out);
5744 let start = token * global_out + rank * local_out;
5745 let mut destination = gathered.slice_mut(start..start + local_out);
5746 root.stream().memcpy_dtod(&source, &mut destination)?;
5747 }
5748 }
5749 }
5750 Ok(gathered)
5751 }
5752
5753 pub fn gather_native_column_shards(
5754 &self,
5755 shards: &[CudaSlice<f32>],
5756 tokens: usize,
5757 local_out: usize,
5758 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5759 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5760 let root = &self.ranks[0];
5761 let _main = root.gpu.enter_main()?;
5762 root.dtoh(&gathered)
5763 }
5764
5765 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5766 &self.decode_v2
5767 }
5768
5769 #[allow(clippy::manual_is_multiple_of)] pub(crate) fn decode_v2_ensure(
5779 &self,
5780 e: &Engine,
5781 q_m: &ResidentBf16ColumnParallel,
5782 k_m: &ResidentBf16ColumnParallel,
5783 v_m: &ResidentBf16ColumnParallel,
5784 o_m: &ResidentStepBf16RowParallel,
5785 heads: usize,
5786 ) -> Result<usize, Box<dyn std::error::Error>> {
5787 if self.ranks.len() > 1 && !self.native_p2p {
5788 return Err("step TP decode v2 requires native P2P ranks".into());
5789 }
5790 let ranks = self.ranks.len();
5791 let fused_door = step_tp_qkv_fused_enabled()?;
5795 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5796 ResidentBf16Weight::F32(_) => true,
5797 ResidentBf16Weight::Bf16(_) => fused_door,
5798 };
5799 for matrix in [q_m, k_m, v_m] {
5800 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5801 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5802 return Err("step TP decode v2 QKV geometry mismatch".into());
5803 }
5804 for rank in &matrix.ranks {
5805 if !arm_ok(&rank.weight) {
5806 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5807 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5808 .into());
5809 }
5810 }
5811 }
5812 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5813 for blocks in &o_m.ranks {
5814 for block in blocks {
5815 if !arm_ok(&block.weight) {
5816 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5817 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5818 .into());
5819 }
5820 }
5821 }
5822 if v_m.out_features != k_m.out_features
5823 || o_m.in_features != q_m.out_features
5824 || heads == 0
5825 || heads % ranks != 0
5826 {
5827 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5828 }
5829 let local_q_dim = q_m.out_features / ranks;
5830 let local_kv_dim = k_m.out_features / ranks;
5831 let o_out = o_m.out_features;
5832 let o_block_cols = o_m.canonical_chunk_cols;
5833 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5834 if blocks_per_rank == 0
5835 || o_m
5836 .ranks
5837 .iter()
5838 .any(|blocks| blocks.len() != blocks_per_rank)
5839 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5840 {
5841 return Err("step TP decode v2 O canonical block grid mismatch".into());
5842 }
5843
5844 let mut guard = self
5845 .decode_v2
5846 .lock()
5847 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5848 if let Some(index) = guard.iter().position(|ws| {
5849 ws.local_q_dim == local_q_dim
5850 && ws.local_kv_dim == local_kv_dim
5851 && ws.heads == heads
5852 && ws.o_out == o_out
5853 && ws.o_block_cols == o_block_cols
5854 && ws.blocks_per_rank == blocks_per_rank
5855 && ws.e_device == e.ctx().ordinal()
5856 && ws.q.len() == ranks
5857 }) {
5858 return Ok(index);
5859 }
5860
5861 let mut q_raw = Vec::with_capacity(ranks);
5862 let mut k_raw = Vec::with_capacity(ranks);
5863 let mut v_raw = Vec::with_capacity(ranks);
5864 let mut q = Vec::with_capacity(ranks);
5865 let mut k = Vec::with_capacity(ranks);
5866 let mut pos = Vec::with_capacity(ranks);
5867 let mut gate = Vec::with_capacity(ranks);
5868 let mut attn_out = Vec::with_capacity(ranks);
5869 let mut gated = Vec::with_capacity(ranks);
5870 let mut fuse_ctr = Vec::with_capacity(ranks);
5871 let mut o_partials = Vec::with_capacity(ranks);
5872 let mut ev_rank = Vec::with_capacity(ranks);
5873 let direct_join = oproj_direct_on();
5874 for (rank, engine) in self.ranks.iter().enumerate() {
5875 let _main = engine.gpu.enter_main()?;
5876 q_raw.push(engine.uninit(local_q_dim)?);
5877 k_raw.push(engine.uninit(local_kv_dim)?);
5878 v_raw.push(engine.uninit(local_kv_dim)?);
5879 q.push(engine.uninit(local_q_dim)?);
5880 k.push(engine.uninit(local_kv_dim)?);
5881 pos.push(engine.htod_i32(&[0])?);
5882 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5883 gate.push(engine.uninit(heads / ranks)?);
5884 attn_out.push(engine.uninit(local_q_dim)?);
5885 gated.push(engine.uninit(local_q_dim)?);
5886 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5887 for _ in 0..blocks_per_rank {
5888 if direct_join && rank != 0 {
5891 let root = &self.ranks[0];
5892 let _root_main = root.gpu.enter_main()?;
5893 rank_partials.push(root.uninit(o_out)?);
5894 } else {
5895 rank_partials.push(engine.uninit(o_out)?);
5896 }
5897 }
5898 o_partials.push(rank_partials);
5899 ev_rank.push(engine.ctx().new_event(None)?);
5900 }
5901 use cudarc::driver::DevicePtr;
5902 let mut raw_o_partials = Vec::with_capacity(ranks);
5903 let mut raw_k = Vec::with_capacity(ranks);
5904 let mut raw_v_raw = Vec::with_capacity(ranks);
5905 for rank in 0..ranks {
5906 let engine = &self.ranks[rank];
5907 {
5908 let _main = engine.gpu.enter_main()?;
5909 let stream = engine.stream();
5910 let (k_ptr, _k_guard) = k[rank].device_ptr(&stream);
5911 let (v_ptr, _v_guard) = v_raw[rank].device_ptr(&stream);
5912 raw_k.push(k_ptr as u64);
5913 raw_v_raw.push(v_ptr as u64);
5914 }
5915 let partial_engine = if direct_join && rank != 0 {
5916 &self.ranks[0]
5917 } else {
5918 engine
5919 };
5920 let _main = partial_engine.gpu.enter_main()?;
5921 let stream = partial_engine.stream();
5922 let mut rank_raw = Vec::with_capacity(blocks_per_rank);
5923 for partial in &o_partials[rank] {
5924 let (ptr, _guard) = partial.device_ptr(&stream);
5925 rank_raw.push(ptr as u64);
5926 }
5927 raw_o_partials.push(rank_raw);
5928 }
5929 let root = &self.ranks[0];
5930 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5931 let _main = root.gpu.enter_main()?;
5932 (
5933 root.uninit(o_out)?,
5934 root.uninit(o_out)?,
5935 root.uninit(o_out)?,
5936 root.htod(&vec![0.0f32; o_out])?,
5937 root.uninit(ranks * local_kv_dim)?,
5938 root.uninit(ranks * local_kv_dim)?,
5939 root.ctx().new_event(None)?,
5940 root.ctx().new_event(None)?,
5941 )
5942 };
5943 let (raw_peer_partial, raw_k_shadow, raw_v_shadow) = {
5944 let _main = root.gpu.enter_main()?;
5945 let stream = root.stream();
5946 let (peer, _peer_guard) = peer_partial.device_ptr(&stream);
5947 let (k, _k_guard) = k_shadow.device_ptr(&stream);
5948 let (v, _v_guard) = v_shadow.device_ptr(&stream);
5949 (peer as u64, k as u64, v as u64)
5950 };
5951 let (gate_e, ev_entry) = {
5952 let _main = e.gpu.enter_main()?;
5953 (e.uninit(heads)?, e.ctx().new_event(None)?)
5954 };
5955 let raw_attn_in = Vec::new();
5956 let raw_pos = Vec::new();
5957 guard.push(StepTpDecodeV2Ws {
5958 tcol_q: Vec::new(),
5959 tcol_k: Vec::new(),
5960 tcol_v: Vec::new(),
5961 tcol_g: Vec::new(),
5962 tcol_in: Vec::new(),
5963 tcol_cap: 0,
5964 w8_aq: Vec::new(),
5965 w8_ad: Vec::new(),
5966 w8_in: 0,
5967 w8o_aq: Vec::new(),
5968 w8o_ad: Vec::new(),
5969 w8o_in: 0,
5970 w8t_aq: Vec::new(),
5971 w8t_ad: Vec::new(),
5972 w8t_in: 0,
5973 w8t_oaq: Vec::new(),
5974 w8t_oad: Vec::new(),
5975 w8t_oin: 0,
5976 w8t_cap: 0,
5977 fa2_q: Vec::new(),
5978 fa2_gate: Vec::new(),
5979 fa2_gated: Vec::new(),
5980 fa2_cap: 0,
5981 rope_k_t: Vec::new(),
5982 rope_ctr_t: Vec::new(),
5983 rope_pos_t: Vec::new(),
5984 rows_tabs: Vec::new(),
5985 rows_tab_t: Vec::new(),
5986 rows_tab_shadow: Vec::new(),
5987 tcol_gated: Vec::new(),
5988 tcol_opart: Vec::new(),
5989 tcol_opeer: None,
5990 tcol_omix: None,
5991 tcol_ocap: 0,
5992 q_raw,
5993 k_raw,
5994 v_raw,
5995 q,
5996 k,
5997 pos,
5998 fuse_ctr,
5999 gate,
6000 attn_out,
6001 gated,
6002 o_partials,
6003 raw_o_partials,
6004 raw_k,
6005 raw_v_raw,
6006 ev_rank,
6007 peer_partial,
6008 reduce_a,
6009 reduce_b,
6010 zeros,
6011 k_shadow,
6012 v_shadow,
6013 ev_refresh,
6014 ev_oproj,
6015 gate_e,
6016 attn_in: Vec::new(),
6017 h_stage: None,
6018 pos_stage: None,
6019 raw_h_stage: 0,
6020 raw_pos_stage: 0,
6021 raw_attn_in,
6022 raw_pos,
6023 raw_o_partial1: 0,
6024 raw_peer_partial,
6025 raw_k1: 0,
6026 raw_v1: 0,
6027 raw_k_shadow,
6028 raw_v_shadow,
6029 raw_mixed_stage_e: 0,
6030 raw_reduce_a: 0,
6031 raw_shadow_stage_e: (0, 0),
6032 ev_entry,
6033 e_device: e.ctx().ordinal(),
6034 local_q_dim,
6035 local_kv_dim,
6036 heads,
6037 o_out,
6038 o_block_cols,
6039 blocks_per_rank,
6040 });
6041 eprintln!(
6042 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
6043 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
6044 residency=persistent ordering=evented performance_claim=false"
6045 );
6046 Ok(guard.len() - 1)
6047 }
6048
6049 #[allow(clippy::too_many_arguments)]
6057 #[allow(clippy::too_many_arguments)]
6062 pub fn decode_v2_input_qkv_tcol(
6063 &self,
6064 ws_index: usize,
6065 e: &Engine,
6066 h_t: &CudaSlice<f32>,
6067 t: usize,
6068 q_m: &ResidentBf16ColumnParallel,
6069 k_m: &ResidentBf16ColumnParallel,
6070 v_m: &ResidentBf16ColumnParallel,
6071 gate_shards: Option<StepTpGateShards<'_>>,
6072 ) -> Result<(), Box<dyn std::error::Error>> {
6073 let ranks = self.ranks.len();
6074 let mut guard = self
6075 .decode_v2
6076 .lock()
6077 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6078 let ws = guard
6079 .get_mut(ws_index)
6080 .ok_or("step TP decode v2 workspace index out of range")?;
6081 let in_f = q_m.in_features;
6082 if h_t.len() < t * in_f || t == 0 || t > 32 {
6083 return Err("decode_v2_input_qkv_tcol geometry".into());
6084 }
6085 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6087 ws.tcol_q.clear();
6088 ws.tcol_k.clear();
6089 ws.tcol_v.clear();
6090 ws.tcol_g.clear();
6091 ws.tcol_in.clear();
6092 for engine in &self.ranks {
6093 let _m = engine.gpu.enter_main()?;
6094 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
6095 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
6096 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
6097 ws.tcol_g
6098 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
6099 ws.tcol_in.push(engine.uninit(32 * in_f)?);
6100 }
6101 ws.tcol_cap = 32;
6102 }
6103 use cudarc::driver::DevicePtr;
6105 let raw_src = {
6106 let _main = e.gpu.enter_main()?;
6107 let stream = e.stream();
6108 let (p, _g) = h_t.device_ptr(&stream);
6109 ws.ev_entry.record(&stream)?;
6110 p
6111 };
6112 for rank in 0..ranks {
6113 let engine = &self.ranks[rank];
6114 let _main = engine.gpu.enter_main()?;
6115 engine.stream().wait(&ws.ev_entry)?;
6116 let raw_dst = {
6117 let stream = engine.stream();
6118 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
6119 p
6120 };
6121 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
6122 let out_g = match &gate_shards {
6123 Some(_) => ws.heads / ranks,
6124 None => 0,
6125 };
6126 match (
6127 &q_m.ranks[rank].weight,
6128 &k_m.ranks[rank].weight,
6129 &v_m.ranks[rank].weight,
6130 ) {
6131 (
6132 ResidentBf16Weight::Bf16(wq),
6133 ResidentBf16Weight::Bf16(wk),
6134 ResidentBf16Weight::Bf16(wv),
6135 ) => {
6136 let wg = match &gate_shards {
6137 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
6138 Some(StepTpGateShards::F32(_)) => {
6139 return Err(
6140 "tcol verify: gate shard class does not match bf16 QKV".into()
6141 );
6142 }
6143 None => wq,
6144 };
6145 let StepTpDecodeV2Ws {
6146 tcol_q,
6147 tcol_k,
6148 tcol_v,
6149 tcol_g,
6150 tcol_in,
6151 local_q_dim,
6152 local_kv_dim,
6153 w8t_aq,
6154 w8t_ad,
6155 w8t_in,
6156 w8t_cap,
6157 ..
6158 } = &mut *ws;
6159 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6162 let refk = *REFK
6163 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
6164 if refk {
6165 let lq = *local_q_dim;
6166 let lkv = *local_kv_dim;
6167 let mut hrow = engine.uninit(in_f)?;
6168 let mut qr = engine.uninit(lq)?;
6169 let mut kr = engine.uninit(lkv)?;
6170 let mut vr = engine.uninit(lkv)?;
6171 let mut gr = engine.uninit(out_g.max(1))?;
6172 for c in 0..t {
6173 {
6174 let mut dst = hrow.slice_mut(0..in_f);
6175 engine.stream().memcpy_dtod(
6176 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
6177 &mut dst,
6178 )?;
6179 }
6180 engine.matvec_bf16_qkvg_into(
6181 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
6182 lq, lkv, out_g,
6183 )?;
6184 let stream = engine.stream();
6185 {
6186 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
6187 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
6188 }
6189 {
6190 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
6191 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
6192 }
6193 {
6194 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
6195 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
6196 }
6197 if out_g > 0 {
6198 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
6199 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
6200 }
6201 }
6202 } else if crate::step_tp_w8_on()
6203 && q_m.ranks[rank].q8.is_some()
6204 && k_m.ranks[rank].q8.is_some()
6205 && v_m.ranks[rank].q8.is_some()
6206 && in_f.is_multiple_of(32)
6207 {
6208 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
6214 w8t_aq.clear();
6215 w8t_ad.clear();
6216 for e_rank in &self.ranks {
6217 let _m = e_rank.gpu.enter_main()?;
6218 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6219 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6220 }
6221 *w8t_in = in_f;
6222 *w8t_cap = 32;
6223 }
6224 engine.quantize_q8_1_into(
6225 &tcol_in[rank],
6226 t,
6227 in_f,
6228 &mut w8t_aq[rank],
6229 &mut w8t_ad[rank],
6230 )?;
6231 engine.qmatvec_q8_0_qkv_rp_t_into(
6232 q_m.ranks[rank].q8.as_ref().unwrap(),
6233 k_m.ranks[rank].q8.as_ref().unwrap(),
6234 v_m.ranks[rank].q8.as_ref().unwrap(),
6235 &w8t_aq[rank],
6236 &w8t_ad[rank],
6237 &mut tcol_q[rank],
6238 &mut tcol_k[rank],
6239 &mut tcol_v[rank],
6240 in_f,
6241 *local_q_dim,
6242 *local_kv_dim,
6243 t,
6244 )?;
6245 if out_g > 0 {
6246 engine.matvec_bf16_rows_into(
6247 wg,
6248 &tcol_in[rank],
6249 &mut tcol_g[rank],
6250 in_f,
6251 out_g,
6252 t,
6253 )?;
6254 }
6255 } else {
6256 engine.matvec_bf16_qkvg_tcol_into(
6257 wq,
6258 wk,
6259 wv,
6260 wg,
6261 &tcol_in[rank],
6262 &mut tcol_q[rank],
6263 &mut tcol_k[rank],
6264 &mut tcol_v[rank],
6265 &mut tcol_g[rank],
6266 in_f,
6267 *local_q_dim,
6268 *local_kv_dim,
6269 out_g,
6270 t,
6271 )?;
6272 }
6273 }
6274 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
6275 }
6276 }
6277 Ok(())
6278 }
6279
6280 pub(crate) fn decode_v2_oproj_tcol_eligible(
6284 &self,
6285 ws: &StepTpDecodeV2Ws,
6286 o_m: &ResidentStepBf16RowParallel,
6287 ) -> bool {
6288 self.ranks.len() == 2
6289 && ws.blocks_per_rank == 4
6290 && step_tp_qkv_fused_enabled().unwrap_or(false)
6291 && no_local_shadow_on()
6292 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
6293 && o_m
6294 .ranks
6295 .iter()
6296 .flatten()
6297 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
6298 }
6299
6300 pub(crate) fn decode_v2_stash_fa2(
6305 &self,
6306 ws: &mut StepTpDecodeV2Ws,
6307 e: &Engine,
6308 col: usize,
6309 ) -> Result<(), Box<dyn std::error::Error>> {
6310 let ranks = self.ranks.len();
6311 if col >= 32 {
6312 return Err("decode_v2_stash_fa2 column out of range".into());
6313 }
6314 let lq = ws.local_q_dim;
6315 let lg = (ws.heads / ranks).max(1);
6316 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6317 ws.fa2_q.clear();
6318 ws.fa2_gate.clear();
6319 ws.fa2_gated.clear();
6320 ws.rope_k_t.clear();
6321 ws.rope_ctr_t.clear();
6322 ws.rope_pos_t.clear();
6323 ws.rows_tab_t.clear();
6324 for engine in &self.ranks {
6325 let _m = engine.gpu.enter_main()?;
6326 ws.fa2_q.push(engine.uninit(32 * lq)?);
6327 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6328 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6329 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
6330 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6331 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6332 ws.rows_tab_t
6333 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6334 }
6335 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6336 ws.fa2_cap = 32;
6337 }
6338 for rank in 0..ranks {
6339 let engine = &self.ranks[rank];
6340 let _main = engine.gpu.enter_main()?;
6341 {
6342 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
6343 engine
6344 .stream()
6345 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
6346 }
6347 {
6348 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
6349 engine
6350 .stream()
6351 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
6352 }
6353 ws.ev_rank[rank].record(&engine.stream())?;
6354 }
6355 {
6356 let _main = e.gpu.enter_main()?;
6357 for ev in ws.ev_rank.iter() {
6358 e.stream().wait(ev)?;
6359 }
6360 }
6361 Ok(())
6362 }
6363
6364 #[allow(clippy::too_many_arguments)]
6371 #[allow(dead_code)] pub(crate) fn decode_v2_spec_fa2_join(
6373 &self,
6374 ws_index: usize,
6375 e: &Engine,
6376 o_m: &ResidentStepBf16RowParallel,
6377 kv: &ResidentTpKvCache,
6378 head_dim: usize,
6379 window: usize,
6380 bucket_max: usize,
6381 scale: f32,
6382 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6383 let ranks = self.ranks.len();
6384 static ONCE: std::sync::Once = std::sync::Once::new();
6386 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
6387 {
6388 let mut guard = self
6389 .decode_v2
6390 .lock()
6391 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6392 let ws = guard
6393 .get_mut(ws_index)
6394 .ok_or("step TP decode v2 workspace index out of range")?;
6395 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
6396 return Err("spec fa2 join without stashed columns".into());
6397 }
6398 let lq = ws.local_q_dim;
6399 let local_heads = (ws.heads / ranks).max(1);
6400 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6401 let capacity = kv.physical_capacity();
6402 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
6403 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
6405 ws.tcol_gated.clear();
6406 ws.tcol_opart.clear();
6407 for engine in &self.ranks {
6408 let _m = engine.gpu.enter_main()?;
6409 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6410 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6411 }
6412 let root = &self.ranks[0];
6413 let _m = root.gpu.enter_main()?;
6414 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6415 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6416 ws.tcol_ocap = 32;
6417 }
6418 for rank in 0..ranks {
6419 let engine = &self.ranks[rank];
6420 let _main = engine.gpu.enter_main()?;
6421 let rank_cache = kv
6422 .rank(rank)
6423 .ok_or("spec fa2 join lost its KV cache rank")?;
6424 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
6425 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
6426 {
6427 let StepTpDecodeV2Ws {
6428 fa2_q,
6429 fa2_gate,
6430 fa2_gated,
6431 ..
6432 } = &mut *ws;
6433 engine.fa_decode_dcw2(
6434 &fa2_q[rank],
6435 &k_ring,
6436 &v_ring,
6437 &mut fa2_gated[rank],
6438 head_dim,
6439 local_heads,
6440 local_kv_heads,
6441 rank_cache.len_d(),
6442 rank_cache.base_d(),
6443 window,
6444 bucket_max,
6445 scale,
6446 k_tok_bytes,
6447 v_tok_bytes,
6448 &fa2_gate[rank],
6449 )?;
6450 }
6451 let StepTpDecodeV2Ws {
6454 fa2_gated,
6455 tcol_gated,
6456 ..
6457 } = &mut *ws;
6458 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
6459 engine
6460 .stream()
6461 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
6462 }
6463 }
6464 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
6465 }
6466
6467 #[allow(clippy::too_many_arguments)]
6477 pub(crate) fn decode_v2_rope_fa_rows(
6478 &self,
6479 ws_index: usize,
6480 e: &Engine,
6481 o_m: &ResidentStepBf16RowParallel,
6482 session_parts: &[Vec<[u64; 4]>],
6483 tab_keys: &[u64],
6484 positions: &[i32],
6485 stage_pos: bool,
6486 same_session: bool,
6487 q_norms: &[CudaSlice<f32>],
6488 k_norms: &[CudaSlice<f32>],
6489 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6490 t: usize,
6491 head_dim: usize,
6492 n_rot: usize,
6493 window: usize,
6494 max_ns: usize,
6495 scale: f32,
6496 k_tok_bytes: usize,
6497 v_tok_bytes: usize,
6498 eps: f32,
6499 rope_base: f32,
6500 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6501 use cudarc::driver::DevicePtr;
6502 let ranks = self.ranks.len();
6503 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6504 return Err("rope fa rows geometry".into());
6505 }
6506 {
6507 let mut guard = self
6508 .decode_v2
6509 .lock()
6510 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6511 let ws = guard
6512 .get_mut(ws_index)
6513 .ok_or("step TP decode v2 workspace index out of range")?;
6514 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6515 return Err("rope fa rows without tcol slabs".into());
6516 }
6517 let lq = ws.local_q_dim;
6518 let lkv = ws.local_kv_dim;
6519 let lg = (ws.heads / ranks).max(1);
6520 let local_heads = (ws.heads / ranks).max(1);
6521 let local_kv_heads = (lkv / head_dim).max(1);
6522 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6524 ws.fa2_q.clear();
6525 ws.fa2_gate.clear();
6526 ws.fa2_gated.clear();
6527 ws.rope_k_t.clear();
6528 ws.rope_ctr_t.clear();
6529 ws.rope_pos_t.clear();
6530 ws.rows_tab_t.clear();
6531 for engine in &self.ranks {
6532 let _m = engine.gpu.enter_main()?;
6533 ws.fa2_q.push(engine.uninit(32 * lq)?);
6534 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6535 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6536 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6537 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6538 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6539 ws.rows_tab_t
6540 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6541 }
6542 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6543 ws.fa2_cap = 32;
6544 }
6545 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6546 ws.tcol_gated.clear();
6547 ws.tcol_opart.clear();
6548 for engine in &self.ranks {
6549 let _m = engine.gpu.enter_main()?;
6550 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6551 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6552 }
6553 let root = &self.ranks[0];
6554 let _m = root.gpu.enter_main()?;
6555 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6556 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6557 ws.tcol_ocap = 32;
6558 }
6559 for rank in 0..ranks {
6560 let engine = &self.ranks[rank];
6561 let _main = engine.gpu.enter_main()?;
6562 if stage_pos {
6563 let host: Vec<i32> = positions[..t].to_vec();
6564 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6565 engine.stream().memcpy_htod(&host, &mut view)?;
6566 }
6567 let ctr_base = {
6586 let s = engine.stream();
6587 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6588 p
6589 };
6590 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6591 if rows_tab_stale_scan() {
6597 if ws.rows_tab_shadow.len() != ranks {
6598 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
6599 }
6600 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6601 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank])
6602 && prev != &host
6603 {
6604 let words = ["k", "v", "len", "base", "ctr", "back"];
6605 let moved: Vec<String> = (0..host.len())
6606 .filter(|&i| prev.get(i) != Some(&host[i]))
6607 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
6608 .collect();
6609 let stale =
6610 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6611 eprintln!(
6612 "[rows-tab] STALE #{stale} lookup #{n} rank={rank} t={t} key={:#018x} moved={}: the retired memo would have launched this row on another allocation's pointers",
6613 tab_keys[rank],
6614 moved.join(",")
6615 );
6616 }
6617 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
6618 }
6619 let legacy_memo = !rows_tab_restage_on();
6620 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6621 let tab = engine.stream().clone_htod(&host)?;
6622 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6623 }
6624 if !legacy_memo {
6625 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
6626 engine.stream().memcpy_htod(&host, &mut view)?;
6627 }
6628 let StepTpDecodeV2Ws {
6629 tcol_q,
6630 tcol_k,
6631 tcol_v,
6632 tcol_g,
6633 fa2_q,
6634 fa2_gated,
6635 rope_k_t,
6636 rope_pos_t,
6637 rows_tabs,
6638 rows_tab_t,
6639 ..
6640 } = &mut *ws;
6641 let tab = if legacy_memo {
6642 rows_tabs[rank]
6643 .get(&tab_keys[rank])
6644 .ok_or("rows tab memo lost its entry")?
6645 } else {
6646 &rows_tab_t[rank]
6647 };
6648 engine.qk_norm_rope_append_inc_dcw_rows(
6649 &tcol_q[rank],
6650 &tcol_k[rank],
6651 &tcol_v[rank],
6652 &q_norms[rank],
6653 &k_norms[rank],
6654 &mut fa2_q[rank],
6655 &mut rope_k_t[rank],
6656 tab,
6657 &rope_pos_t[rank],
6658 same_session,
6659 t,
6660 lkv,
6661 lkv,
6662 k_tok_bytes,
6663 v_tok_bytes,
6664 head_dim,
6665 n_rot,
6666 local_heads,
6667 local_kv_heads,
6668 eps,
6669 rope_base,
6670 1.0,
6671 rope_freqs[rank],
6672 )?;
6673 engine.fa_decode_dcw_rows(
6674 &fa2_q[rank],
6675 tab,
6676 &mut fa2_gated[rank],
6677 t,
6678 head_dim,
6679 local_heads,
6680 local_kv_heads,
6681 window,
6682 max_ns,
6683 scale,
6684 k_tok_bytes,
6685 v_tok_bytes,
6686 &tcol_g[rank],
6687 )?;
6688 let StepTpDecodeV2Ws {
6689 fa2_gated,
6690 tcol_gated,
6691 ..
6692 } = &mut *ws;
6693 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6694 engine
6695 .stream()
6696 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6697 }
6698 }
6699 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6700 }
6701
6702 #[allow(clippy::too_many_arguments)]
6709 pub(crate) fn decode_v2_fa_rows_join(
6710 &self,
6711 ws_index: usize,
6712 e: &Engine,
6713 o_m: &ResidentStepBf16RowParallel,
6714 tabs: &[&crate::CudaSlice<u64>],
6715 t: usize,
6716 head_dim: usize,
6717 window: usize,
6718 max_ns: usize,
6719 scale: f32,
6720 k_tok_bytes: usize,
6721 v_tok_bytes: usize,
6722 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6723 let ranks = self.ranks.len();
6724 if tabs.len() != ranks {
6725 return Err("fa rows join needs one table per rank".into());
6726 }
6727 {
6728 let mut guard = self
6729 .decode_v2
6730 .lock()
6731 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6732 let ws = guard
6733 .get_mut(ws_index)
6734 .ok_or("step TP decode v2 workspace index out of range")?;
6735 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6736 return Err("fa rows join without stashed rows".into());
6737 }
6738 let lq = ws.local_q_dim;
6739 let local_heads = (ws.heads / ranks).max(1);
6740 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6741 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6742 ws.tcol_gated.clear();
6743 ws.tcol_opart.clear();
6744 for engine in &self.ranks {
6745 let _m = engine.gpu.enter_main()?;
6746 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6747 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6748 }
6749 let root = &self.ranks[0];
6750 let _m = root.gpu.enter_main()?;
6751 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6752 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6753 ws.tcol_ocap = 32;
6754 }
6755 for rank in 0..ranks {
6756 let engine = &self.ranks[rank];
6757 let _main = engine.gpu.enter_main()?;
6758 {
6759 let StepTpDecodeV2Ws {
6760 fa2_q,
6761 fa2_gate,
6762 fa2_gated,
6763 ..
6764 } = &mut *ws;
6765 engine.fa_decode_dcw_rows(
6766 &fa2_q[rank],
6767 tabs[rank],
6768 &mut fa2_gated[rank],
6769 t,
6770 head_dim,
6771 local_heads,
6772 local_kv_heads,
6773 window,
6774 max_ns,
6775 scale,
6776 k_tok_bytes,
6777 v_tok_bytes,
6778 &fa2_gate[rank],
6779 )?;
6780 }
6781 let StepTpDecodeV2Ws {
6782 fa2_gated,
6783 tcol_gated,
6784 ..
6785 } = &mut *ws;
6786 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6787 engine
6788 .stream()
6789 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6790 }
6791 }
6792 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6793 }
6794
6795 pub(crate) fn decode_v2_stash_gated(
6800 &self,
6801 ws: &mut StepTpDecodeV2Ws,
6802 e: &Engine,
6803 col: usize,
6804 ) -> Result<(), Box<dyn std::error::Error>> {
6805 let ranks = self.ranks.len();
6806 if col >= 32 {
6810 return Err("decode_v2_stash_gated column out of range".into());
6811 }
6812 let lq = ws.local_q_dim;
6813 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6814 ws.tcol_gated.clear();
6815 ws.tcol_opart.clear();
6816 for engine in &self.ranks {
6817 let _m = engine.gpu.enter_main()?;
6818 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6819 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6820 }
6821 let root = &self.ranks[0];
6822 let _m = root.gpu.enter_main()?;
6823 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6824 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6825 ws.tcol_ocap = 32;
6826 }
6827 for rank in 0..ranks {
6828 let engine = &self.ranks[rank];
6829 let _main = engine.gpu.enter_main()?;
6830 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6831 engine
6832 .stream()
6833 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6834 ws.ev_rank[rank].record(&engine.stream())?;
6838 }
6839 {
6840 let _main = e.gpu.enter_main()?;
6841 for ev in ws.ev_rank.iter() {
6842 e.stream().wait(ev)?;
6843 }
6844 }
6845 Ok(())
6846 }
6847
6848 pub(crate) fn decode_v2_oproj_tcol(
6854 &self,
6855 ws_index: usize,
6856 e: &Engine,
6857 o_m: &ResidentStepBf16RowParallel,
6858 t: usize,
6859 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6860 let ranks = self.ranks.len();
6861 let mut guard = self
6862 .decode_v2
6863 .lock()
6864 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6865 let ws = guard
6866 .get_mut(ws_index)
6867 .ok_or("step TP decode v2 workspace index out of range")?;
6868 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6869 return Err("decode_v2_oproj_tcol geometry".into());
6870 }
6871 for rank in 0..ranks {
6872 let engine = &self.ranks[rank];
6873 let _main = engine.gpu.enter_main()?;
6874 let mut weights = Vec::with_capacity(4);
6875 for block in 0..4 {
6876 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6877 return Err("tcol o_proj requires bf16-resident O blocks".into());
6878 };
6879 weights.push(weight);
6880 }
6881 {
6882 let StepTpDecodeV2Ws {
6883 tcol_gated,
6884 tcol_opart,
6885 local_q_dim,
6886 o_block_cols,
6887 o_out,
6888 w8t_oaq,
6889 w8t_oad,
6890 w8t_oin,
6891 w8t_cap,
6892 ..
6893 } = &mut *ws;
6894 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6897 let refk = *REFK
6898 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6899 if refk {
6900 let lq = *local_q_dim;
6901 let mut xr = engine.uninit(lq)?;
6902 let mut yr = engine.uninit(*o_out)?;
6903 for c in 0..t {
6904 {
6905 let mut dst = xr.slice_mut(0..lq);
6906 engine.stream().memcpy_dtod(
6907 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6908 &mut dst,
6909 )?;
6910 }
6911 engine.matvec_bf16_b4_into(
6912 [weights[0], weights[1], weights[2], weights[3]],
6913 &xr,
6914 &mut yr,
6915 *o_block_cols,
6916 *o_out,
6917 )?;
6918 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6919 engine
6920 .stream()
6921 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6922 }
6923 } else if crate::step_tp_w8_on()
6924 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
6925 && (4 * *o_block_cols) % 32 == 0
6926 {
6927 let in_f = 4 * *o_block_cols;
6931 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
6932 w8t_oaq.clear();
6933 w8t_oad.clear();
6934 for e_rank in &self.ranks {
6935 let _m = e_rank.gpu.enter_main()?;
6936 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6937 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6938 }
6939 *w8t_oin = in_f;
6940 *w8t_cap = (*w8t_cap).max(32);
6941 }
6942 engine.quantize_q8_1_into(
6943 &tcol_gated[rank],
6944 t,
6945 in_f,
6946 &mut w8t_oaq[rank],
6947 &mut w8t_oad[rank],
6948 )?;
6949 engine.qmatvec_q8_0_b4_rp_t_into(
6950 [
6951 o_m.ranks[rank][0].q8.as_ref().unwrap(),
6952 o_m.ranks[rank][1].q8.as_ref().unwrap(),
6953 o_m.ranks[rank][2].q8.as_ref().unwrap(),
6954 o_m.ranks[rank][3].q8.as_ref().unwrap(),
6955 ],
6956 &w8t_oaq[rank],
6957 &w8t_oad[rank],
6958 &mut tcol_opart[rank],
6959 *o_block_cols,
6960 *o_out,
6961 t,
6962 )?;
6963 } else {
6964 engine.matvec_bf16_b4_tcol_into(
6965 [weights[0], weights[1], weights[2], weights[3]],
6966 &tcol_gated[rank],
6967 &mut tcol_opart[rank],
6968 *o_block_cols,
6969 *o_out,
6970 t,
6971 )?;
6972 }
6973 }
6974 if rank != 0 {
6975 ws.ev_rank[rank].record(&engine.stream())?;
6976 }
6977 }
6978 let root = &self.ranks[0];
6979 {
6980 let _main = root.gpu.enter_main()?;
6981 for ev in ws.ev_rank.iter().skip(1) {
6982 root.stream().wait(ev)?;
6983 }
6984 {
6985 let StepTpDecodeV2Ws {
6986 tcol_opart,
6987 tcol_opeer,
6988 tcol_omix,
6989 o_out,
6990 ..
6991 } = &mut *ws;
6992 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6993 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6994 {
6995 let mut dst = opeer.slice_mut(0..t * *o_out);
6996 root.stream()
6997 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6998 }
6999 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
7002 }
7003 ws.ev_oproj.record(&root.stream())?;
7004 }
7005 let _main = e.gpu.enter_main()?;
7006 e.stream().wait(&ws.ev_oproj)?;
7007 let mut out = e.uninit(t * ws.o_out)?;
7008 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
7009 e.stream().memcpy_dtod(
7010 &omix.slice(0..t * ws.o_out),
7011 &mut out.slice_mut(0..t * ws.o_out),
7012 )?;
7013 Ok(out)
7014 }
7015
7016 #[allow(clippy::too_many_arguments)] pub(crate) fn decode_v2_input_qkv(
7018 &self,
7019 ws: &mut StepTpDecodeV2Ws,
7020 e: &Engine,
7021 h: &CudaSlice<f32>,
7022 pos_d: &CudaSlice<i32>,
7023 gate_raw: Option<&CudaSlice<f32>>,
7024 gate_shards: Option<StepTpGateShards<'_>>,
7025 decode_input: &mut ResidentReplicatedDeviceRows,
7026 q_m: &ResidentBf16ColumnParallel,
7027 k_m: &ResidentBf16ColumnParallel,
7028 v_m: &ResidentBf16ColumnParallel,
7029 q_norm: &[CudaSlice<f32>],
7030 k_norm: &[CudaSlice<f32>],
7031 head_dim: usize,
7032 n_rot: usize,
7033 rope_base: f32,
7034 rope_freqs: &[Option<&CudaSlice<f32>>],
7035 rms_eps: f32,
7036 has_gate: bool,
7037 defer_norm_rope: bool,
7038 tcol_col: Option<usize>,
7039 ) -> Result<(), Box<dyn std::error::Error>> {
7040 let ranks = self.ranks.len();
7041 validate_replicated_device_rows(&self.ranks, decode_input)?;
7042 let gate_sources = usize::from(gate_raw.is_some()) + usize::from(gate_shards.is_some());
7043 if decode_input.tokens != 1
7044 || decode_input.width != q_m.in_features
7045 || pos_d.len() != 1
7046 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
7047 || (has_gate && gate_sources != 1)
7048 || (!has_gate && gate_sources != 0)
7049 || gate_shards.as_ref().is_some_and(|shards| match shards {
7050 StepTpGateShards::F32(shards) => shards.len() != ranks,
7051 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
7052 })
7053 || q_norm.len() != ranks
7054 || k_norm.len() != ranks
7055 || rope_freqs.len() != ranks
7056 || e.ctx().ordinal() != ws.e_device
7057 {
7058 return Err("step TP decode v2 input geometry mismatch".into());
7059 }
7060
7061 let qkv_fused = step_tp_qkv_fused_enabled()?;
7062 if gate_shards.is_some() && !qkv_fused {
7063 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
7064 }
7065 let values = decode_input.width;
7066 if h.len() != values {
7067 return Err(format!(
7068 "step TP decode v2 hidden width {} != replicated width {values}",
7069 h.len()
7070 )
7071 .into());
7072 }
7073
7074 if qkv_fused {
7075 if ws.h_stage.is_none() {
7079 use cudarc::driver::DevicePtr;
7080 let _main = e.gpu.enter_main()?;
7081 let h_stage = e.uninit(values)?;
7082 let pos_stage = e.htod_i32(&[0])?;
7083 {
7084 let stream = e.stream();
7085 let (hp, _g0) = h_stage.device_ptr(&stream);
7086 let (pp, _g1) = pos_stage.device_ptr(&stream);
7087 ws.raw_h_stage = hp;
7088 ws.raw_pos_stage = pp;
7089 }
7090 ws.h_stage = Some(h_stage);
7091 ws.pos_stage = Some(pos_stage);
7092 for rank in 0..ranks {
7093 use cudarc::driver::DevicePtr;
7094 let engine = &self.ranks[rank];
7095 let _rmain = engine.gpu.enter_main()?;
7096 let attn_in = engine.uninit(values)?;
7097 let (dp, pp) = {
7098 let stream = engine.stream();
7099 let (dp, _g2) = attn_in.device_ptr(&stream);
7100 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
7101 (dp, pp)
7102 };
7103 ws.raw_attn_in.push(dp);
7104 ws.raw_pos.push(pp);
7105 ws.attn_in.push(attn_in);
7106 }
7107 {
7108 use cudarc::driver::DevicePtr;
7109 let root = &self.ranks[0];
7110 let _rmain = root.gpu.enter_main()?;
7111 let stream = root.stream();
7112 let (a, _g) = ws.peer_partial.device_ptr(&stream);
7113 let (b, _g) = ws.k_shadow.device_ptr(&stream);
7114 let (c, _g) = ws.v_shadow.device_ptr(&stream);
7115 ws.raw_peer_partial = a;
7116 ws.raw_k_shadow = b;
7117 ws.raw_v_shadow = c;
7118 }
7119 {
7120 use cudarc::driver::DevicePtr;
7121 let rank1 = &self.ranks[1];
7122 let _rmain = rank1.gpu.enter_main()?;
7123 let stream = rank1.stream();
7124 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
7125 let (b, _g) = ws.k[1].device_ptr(&stream);
7126 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
7127 ws.raw_o_partial1 = a;
7128 ws.raw_k1 = b;
7129 ws.raw_v1 = c;
7130 }
7131 }
7132 {
7133 let _main = e.gpu.enter_main()?;
7134 {
7135 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
7138 let mut dst = h_stage.slice_mut(0..values);
7139 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
7140 }
7141 {
7142 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
7143 let mut dst = pos_stage.slice_mut(0..1);
7144 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
7145 }
7146 ws.ev_entry.record(&e.stream())?;
7147 }
7148 for rank in 0..ranks {
7149 let engine = &self.ranks[rank];
7150 let _main = engine.gpu.enter_main()?;
7151 engine.stream().wait(&ws.ev_entry)?;
7152 }
7153 } else {
7154 {
7156 let _main = e.gpu.enter_main()?;
7157 if let Some(gate_raw) = gate_raw {
7158 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
7159 e.stream()
7160 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
7161 }
7162 ws.ev_entry.record(&e.stream())?;
7163 }
7164 {
7165 let root = &self.ranks[0];
7166 let _main = root.gpu.enter_main()?;
7167 root.stream().wait(&ws.ev_entry)?;
7168 let mut destination = decode_input.ranks[0].slice_mut(0..values);
7169 root.stream()
7170 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
7171 ws.ev_refresh.record(&root.stream())?;
7172 }
7173 for rank in 1..ranks {
7174 let engine = &self.ranks[rank];
7175 let _main = engine.gpu.enter_main()?;
7176 engine.stream().wait(&ws.ev_refresh)?;
7177 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
7178 let mut destination = peer_rows[0].slice_mut(0..values);
7179 engine
7180 .stream()
7181 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
7182 }
7183 }
7184 for rank in 0..ranks {
7185 self.decode_v2_input_qkv_rank(
7186 ws,
7187 pos_d,
7188 decode_input,
7189 q_m,
7190 k_m,
7191 v_m,
7192 q_norm,
7193 k_norm,
7194 head_dim,
7195 n_rot,
7196 rope_base,
7197 rope_freqs,
7198 rms_eps,
7199 gate_shards.as_ref(),
7200 has_gate,
7201 qkv_fused,
7202 defer_norm_rope,
7203 rank,
7204 tcol_col,
7205 )?;
7206 }
7207 Ok(())
7208 }
7209
7210 #[allow(clippy::too_many_arguments)]
7213 pub(crate) fn decode_v2_input_qkv_rank(
7214 &self,
7215 ws: &mut StepTpDecodeV2Ws,
7216 pos_d: &CudaSlice<i32>,
7217 decode_input: &mut ResidentReplicatedDeviceRows,
7218 q_m: &ResidentBf16ColumnParallel,
7219 k_m: &ResidentBf16ColumnParallel,
7220 v_m: &ResidentBf16ColumnParallel,
7221 q_norm: &[CudaSlice<f32>],
7222 k_norm: &[CudaSlice<f32>],
7223 head_dim: usize,
7224 n_rot: usize,
7225 rope_base: f32,
7226 rope_freqs: &[Option<&CudaSlice<f32>>],
7227 rms_eps: f32,
7228 gate_shards: Option<&StepTpGateShards<'_>>,
7229 has_gate: bool,
7230 qkv_fused: bool,
7231 defer_norm_rope: bool,
7232 rank: usize,
7233 tcol_col: Option<usize>,
7234 ) -> Result<(), Box<dyn std::error::Error>> {
7235 let ranks = self.ranks.len();
7236 let local_heads = ws.local_q_dim / head_dim;
7237 let local_kv_heads = ws.local_kv_dim / head_dim;
7238 let engine = &self.ranks[rank];
7239 let _main = engine.gpu.enter_main()?;
7240 let ws_e_device = ws.e_device;
7241 if qkv_fused && tcol_col.is_some() {
7246 #[allow(clippy::unnecessary_unwrap)]
7247 let c = tcol_col.expect("checked");
7249 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
7250 return Err("tcol select without precompute".into());
7251 }
7252 if engine.ctx().ordinal() != ws_e_device {
7256 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7257 }
7258 let StepTpDecodeV2Ws {
7259 tcol_q,
7260 tcol_k,
7261 tcol_v,
7262 tcol_g,
7263 q_raw,
7264 k_raw,
7265 v_raw,
7266 gate,
7267 local_q_dim,
7268 local_kv_dim,
7269 heads,
7270 ..
7271 } = &mut *ws;
7272 let lg = *heads / ranks;
7273 let stream = engine.stream();
7274 {
7275 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
7276 stream.memcpy_dtod(
7277 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
7278 &mut dst,
7279 )?;
7280 }
7281 {
7282 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
7283 stream.memcpy_dtod(
7284 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7285 &mut dst,
7286 )?;
7287 }
7288 {
7289 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
7290 stream.memcpy_dtod(
7291 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7292 &mut dst,
7293 )?;
7294 }
7295 if has_gate && lg > 0 {
7296 let mut dst = gate[rank].slice_mut(0..lg);
7297 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
7298 }
7299 if !defer_norm_rope {
7300 } else {
7304 return Ok(());
7305 }
7306 }
7307 if qkv_fused {
7308 let same_dev = engine.ctx().ordinal() == ws.e_device;
7313 if !same_dev {
7314 raw_copy_bytes(
7315 ws.raw_attn_in[rank],
7316 ws.raw_h_stage,
7317 q_m.in_features * 4,
7318 engine,
7319 )?;
7320 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7321 }
7322 let StepTpDecodeV2Ws {
7323 q_raw,
7324 k_raw,
7325 v_raw,
7326 gate,
7327 gate_e,
7328 attn_in,
7329 h_stage,
7330 heads,
7331 local_q_dim,
7332 local_kv_dim,
7333 w8_aq,
7334 w8_ad,
7335 w8_in,
7336 ..
7337 } = &mut *ws;
7338 let input_ref: &CudaSlice<f32> = if same_dev {
7339 h_stage
7340 .as_ref()
7341 .ok_or("step TP decode v2 stage not armed")?
7342 } else {
7343 &attn_in[rank]
7344 };
7345 match (
7346 &q_m.ranks[rank].weight,
7347 &k_m.ranks[rank].weight,
7348 &v_m.ranks[rank].weight,
7349 ) {
7350 (
7351 ResidentBf16Weight::F32(wq),
7352 ResidentBf16Weight::F32(wk),
7353 ResidentBf16Weight::F32(wv),
7354 ) => {
7355 let (wg, out_g) = match &gate_shards {
7356 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
7357 Some(StepTpGateShards::Bf16(_)) => {
7358 return Err("step TP decode v2 gate shard class does not \
7359 match the F32 projections"
7360 .into());
7361 }
7362 None => (&*gate_e, 0),
7364 };
7365 engine.matvec_f32_qkv_into(
7366 wq,
7367 wk,
7368 wv,
7369 wg,
7370 input_ref,
7371 &mut q_raw[rank],
7372 &mut k_raw[rank],
7373 &mut v_raw[rank],
7374 &mut gate[rank],
7375 q_m.in_features,
7376 *local_q_dim,
7377 *local_kv_dim,
7378 out_g,
7379 )?;
7380 }
7381 (
7382 ResidentBf16Weight::Bf16(wq),
7383 ResidentBf16Weight::Bf16(wk),
7384 ResidentBf16Weight::Bf16(wv),
7385 ) => {
7386 let (wg, out_g) = match &gate_shards {
7387 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
7388 Some(StepTpGateShards::F32(_)) => {
7389 return Err("step TP decode v2 gate shard class does not \
7390 match the bf16 projections"
7391 .into());
7392 }
7393 None => (wq, 0),
7394 };
7395 let in_f = q_m.in_features;
7402 let q8_ready = crate::step_tp_w8_on()
7403 && q_m.ranks[rank].q8.is_some()
7404 && k_m.ranks[rank].q8.is_some()
7405 && v_m.ranks[rank].q8.is_some();
7406 if q8_ready {
7407 if *w8_in != in_f || w8_aq.len() != ranks {
7408 w8_aq.clear();
7409 w8_ad.clear();
7410 for e_rank in &self.ranks {
7411 let _m = e_rank.gpu.enter_main()?;
7412 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7413 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7414 }
7415 *w8_in = in_f;
7416 }
7417 engine.quantize_q8_1_into(
7418 input_ref,
7419 1,
7420 in_f,
7421 &mut w8_aq[rank],
7422 &mut w8_ad[rank],
7423 )?;
7424 engine.qmatvec_q8_0_qkv_rp_into(
7429 q_m.ranks[rank].q8.as_ref().unwrap(),
7430 k_m.ranks[rank].q8.as_ref().unwrap(),
7431 v_m.ranks[rank].q8.as_ref().unwrap(),
7432 &w8_aq[rank],
7433 &w8_ad[rank],
7434 &mut q_raw[rank],
7435 &mut k_raw[rank],
7436 &mut v_raw[rank],
7437 in_f,
7438 *local_q_dim,
7439 *local_kv_dim,
7440 )?;
7441 if out_g > 0 {
7442 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7443 }
7444 } else {
7445 engine.matvec_bf16_qkvg_into(
7446 wq,
7447 wk,
7448 wv,
7449 wg,
7450 input_ref,
7451 &mut q_raw[rank],
7452 &mut k_raw[rank],
7453 &mut v_raw[rank],
7454 &mut gate[rank],
7455 q_m.in_features,
7456 *local_q_dim,
7457 *local_kv_dim,
7458 out_g,
7459 )?;
7460 }
7461 }
7462 _ => {
7463 return Err("step TP decode v2 QKV projections mix residency classes".into());
7464 }
7465 }
7466 } else {
7467 for (matrix, local_out, raw) in [
7468 (q_m, ws.local_q_dim, &mut ws.q_raw),
7469 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7470 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7471 ] {
7472 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7473 return Err("step TP decode v2 lost its F32 projection residency".into());
7474 };
7475 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7476 engine.linear_f32_resident_canonical_rows_t1_into(
7477 &decode_input.ranks[rank],
7478 values_w,
7479 &mut raw[rank],
7480 matrix.in_features,
7481 local_out,
7482 chunk_rows,
7483 )?;
7484 }
7485 }
7486 if qkv_fused && defer_norm_rope {
7487 } else if qkv_fused {
7489 let StepTpDecodeV2Ws {
7492 q_raw,
7493 k_raw,
7494 q,
7495 k,
7496 pos,
7497 pos_stage,
7498 ..
7499 } = &mut *ws;
7500 let same_dev = engine.ctx().ordinal() == ws_e_device;
7501 let pos_ref: &CudaSlice<i32> = if same_dev {
7502 pos_stage
7503 .as_ref()
7504 .ok_or("step TP decode v2 pos stage not armed")?
7505 } else {
7506 &pos[rank]
7507 };
7508 engine.qk_norm_rope_into(
7509 &q_raw[rank],
7510 &k_raw[rank],
7511 &q_norm[rank],
7512 &k_norm[rank],
7513 &mut q[rank],
7514 &mut k[rank],
7515 pos_ref,
7516 head_dim,
7517 n_rot,
7518 local_heads,
7519 local_kv_heads,
7520 rms_eps,
7521 rope_base,
7522 1.0,
7523 rope_freqs[rank],
7524 )?;
7525 } else {
7526 engine.rms_norm(
7527 &ws.q_raw[rank],
7528 &q_norm[rank],
7529 &mut ws.q[rank],
7530 head_dim,
7531 local_heads,
7532 rms_eps,
7533 )?;
7534 engine.rms_norm(
7535 &ws.k_raw[rank],
7536 &k_norm[rank],
7537 &mut ws.k[rank],
7538 head_dim,
7539 local_kv_heads,
7540 rms_eps,
7541 )?;
7542 {
7543 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7544 engine
7545 .stream()
7546 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7547 }
7548 engine.rope_neox2(
7549 &mut ws.q[rank],
7550 &mut ws.k[rank],
7551 &ws.pos[rank],
7552 head_dim,
7553 n_rot,
7554 local_heads,
7555 local_kv_heads,
7556 1,
7557 rope_base,
7558 1.0,
7559 rope_freqs[rank],
7560 )?;
7561 }
7562 if has_gate && gate_shards.is_none() {
7563 let gate_start = rank * (ws.heads / ranks);
7564 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7565 engine.stream().memcpy_dtod(
7566 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7567 &mut gate_dst,
7568 )?;
7569 }
7570 Ok(())
7571 }
7572
7573 pub(crate) fn decode_v2_finish_rank_partial(
7577 &self,
7578 ws: &mut StepTpDecodeV2Ws,
7579 o_m: &ResidentStepBf16RowParallel,
7580 o_fused: bool,
7581 rank: usize,
7582 ) -> Result<(), Box<dyn std::error::Error>> {
7583 let engine = &self.ranks[rank];
7584 let _main = engine.gpu.enter_main()?;
7585 if o_fused {
7586 let StepTpDecodeV2Ws {
7587 gated,
7588 o_partials,
7589 o_block_cols,
7590 o_out,
7591 w8o_aq,
7592 w8o_ad,
7593 w8o_in,
7594 ..
7595 } = &mut *ws;
7596 let all_f32 = o_m.ranks[rank]
7597 .iter()
7598 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
7599 if all_f32 {
7600 let mut weights = Vec::with_capacity(4);
7601 for block in 0..4 {
7602 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7603 unreachable!("all_f32 checked above");
7604 };
7605 weights.push(weight);
7606 }
7607 engine.matvec_f32_b4_into(
7608 [weights[0], weights[1], weights[2], weights[3]],
7609 &gated[rank],
7610 &mut o_partials[rank][0],
7611 *o_block_cols,
7612 *o_out,
7613 )?;
7614 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
7615 let in_f = 4 * *o_block_cols;
7620 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
7621 w8o_aq.clear();
7622 w8o_ad.clear();
7623 for e_rank in &self.ranks {
7624 let _m = e_rank.gpu.enter_main()?;
7625 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7626 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7627 }
7628 *w8o_in = in_f;
7629 }
7630 engine.quantize_q8_1_into(
7631 &gated[rank],
7632 1,
7633 in_f,
7634 &mut w8o_aq[rank],
7635 &mut w8o_ad[rank],
7636 )?;
7637 engine.qmatvec_q8_0_b4_rp_into(
7638 [
7639 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7640 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7641 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7642 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7643 ],
7644 &w8o_aq[rank],
7645 &w8o_ad[rank],
7646 &mut o_partials[rank][0],
7647 *o_block_cols,
7648 *o_out,
7649 )?;
7650 } else {
7651 let mut weights = Vec::with_capacity(4);
7652 for block in 0..4 {
7653 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7654 return Err("step TP decode v2 O projections mix residency classes".into());
7655 };
7656 weights.push(weight);
7657 }
7658 engine.matvec_bf16_b4_into(
7659 [weights[0], weights[1], weights[2], weights[3]],
7660 &gated[rank],
7661 &mut o_partials[rank][0],
7662 *o_block_cols,
7663 *o_out,
7664 )?;
7665 }
7666 } else {
7667 for block in 0..ws.blocks_per_rank {
7668 let x =
7669 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7670 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7671 match &o_m.ranks[rank][block].weight {
7672 ResidentBf16Weight::F32(weight) => {
7673 let w = weight.slice(0..weight.len());
7674 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7675 }
7676 ResidentBf16Weight::Bf16(weight) => {
7677 engine.matvec_bf16_views_into(
7678 weight,
7679 &x,
7680 &mut y,
7681 ws.o_block_cols,
7682 ws.o_out,
7683 )?;
7684 }
7685 }
7686 }
7687 }
7688 Ok(())
7689 }
7690
7691 pub(crate) fn decode_v2_finish(
7699 &self,
7700 ws: &mut StepTpDecodeV2Ws,
7701 e: &Engine,
7702 o_m: &ResidentStepBf16RowParallel,
7703 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7704 let ranks = self.ranks.len();
7705 if e.ctx().ordinal() != ws.e_device {
7706 return Err("step TP decode v2 finish engine changed".into());
7707 }
7708 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7713
7714 for rank in 0..ranks {
7717 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7718 if rank == 0 {
7719 continue;
7722 }
7723 let engine = &self.ranks[rank];
7724 let _main = engine.gpu.enter_main()?;
7725 ws.ev_rank[rank].record(&engine.stream())?;
7726 }
7727
7728 let root = &self.ranks[0];
7730 #[allow(unused_assignments)]
7731 let mut final_in_a = false;
7732 {
7733 let _main = root.gpu.enter_main()?;
7734 for ev in ws.ev_rank.iter().skip(1) {
7735 root.stream().wait(ev)?;
7736 }
7737 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7738 ws.ev_oproj.record(&root.stream())?;
7744 let _main = e.gpu.enter_main()?;
7745 e.stream().wait(&ws.ev_oproj)?;
7746 let mut output = e.uninit(ws.o_out)?;
7747 if oproj_tail_on() && oproj_tail_eligible() {
7748 use cudarc::driver::DevicePtr;
7751 let stream = e.stream();
7752 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7753 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7754 set_oproj_tail((p0, p1));
7755 return Ok(output);
7756 }
7757 e.add(
7758 &ws.o_partials[0][0],
7759 &ws.o_partials[1][0],
7760 &mut output,
7761 ws.o_out,
7762 )?;
7763 return Ok(output);
7764 }
7765 if o_fused {
7766 self.decode_v2_finish_root_fused(ws)?;
7767 ws.ev_oproj.record(&root.stream())?;
7768 let _main = e.gpu.enter_main()?;
7769 e.stream().wait(&ws.ev_oproj)?;
7770 let mut output = e.uninit(ws.o_out)?;
7771 e.stream().memcpy_dtod(
7772 &ws.reduce_a.slice(0..ws.o_out),
7773 &mut output.slice_mut(0..ws.o_out),
7774 )?;
7775 return Ok(output);
7776 }
7777 let mut first = true;
7778 let mut current_is_a = false;
7779 for rank in 0..ranks {
7780 for block in 0..ws.blocks_per_rank {
7781 let use_peer = rank != 0;
7782 if use_peer {
7783 raw_copy_bytes(
7784 ws.raw_peer_partial,
7785 ws.raw_o_partials[rank][block],
7786 ws.o_out * std::mem::size_of::<f32>(),
7787 root,
7788 )?;
7789 }
7790 match (first, current_is_a, use_peer) {
7792 (true, _, true) => {
7793 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7794 }
7795 (true, _, false) => root.add(
7796 &ws.zeros,
7797 &ws.o_partials[0][block],
7798 &mut ws.reduce_a,
7799 ws.o_out,
7800 )?,
7801 (false, true, true) => {
7802 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7803 }
7804 (false, true, false) => root.add(
7805 &ws.reduce_a,
7806 &ws.o_partials[0][block],
7807 &mut ws.reduce_b,
7808 ws.o_out,
7809 )?,
7810 (false, false, true) => {
7811 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7812 }
7813 (false, false, false) => root.add(
7814 &ws.reduce_b,
7815 &ws.o_partials[0][block],
7816 &mut ws.reduce_a,
7817 ws.o_out,
7818 )?,
7819 }
7820 current_is_a = first || !current_is_a;
7821 first = false;
7822 }
7823 }
7824 final_in_a = current_is_a;
7825
7826 if !no_local_shadow_on() {
7827 let bytes = ws.local_kv_dim * std::mem::size_of::<f32>();
7828 for rank in 0..ranks {
7829 let offset = rank * bytes;
7830 raw_copy_bytes(ws.raw_k_shadow + offset as u64, ws.raw_k[rank], bytes, root)?;
7831 raw_copy_bytes(
7832 ws.raw_v_shadow + offset as u64,
7833 ws.raw_v_raw[rank],
7834 bytes,
7835 root,
7836 )?;
7837 }
7838 }
7839 ws.ev_oproj.record(&root.stream())?;
7840 }
7841
7842 let _main = e.gpu.enter_main()?;
7846 e.stream().wait(&ws.ev_oproj)?;
7847 let mut output = e.uninit(ws.o_out)?;
7848 let source = if final_in_a {
7849 &ws.reduce_a
7850 } else {
7851 &ws.reduce_b
7852 };
7853 e.stream().memcpy_dtod(
7854 &source.slice(0..ws.o_out),
7855 &mut output.slice_mut(0..ws.o_out),
7856 )?;
7857 Ok(output)
7858 }
7859
7860 #[allow(clippy::too_many_arguments)] pub fn run_routed_experts(
7862 &self,
7863 experts: &ResidentExpertParallel,
7864 input: &[f32],
7865 tokens: usize,
7866 selected: &[usize],
7867 route_weights: &[f32],
7868 experts_per_token: usize,
7869 activation_limit: Option<f32>,
7870 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7871 validate_step_expert_activation_limit(activation_limit)?;
7872 validate_ep_residency(&self.ranks, experts)?;
7873 validate_activations(input, tokens, experts.input_width)?;
7874 let pairs = tokens
7875 .checked_mul(experts_per_token)
7876 .ok_or("EP route count overflow")?;
7877 if selected.len() != pairs || route_weights.len() != pairs {
7878 return Err(format!(
7879 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7880 {experts_per_token} ({pairs})",
7881 selected.len(),
7882 route_weights.len(),
7883 )
7884 .into());
7885 }
7886 if !route_weights.iter().all(|weight| weight.is_finite()) {
7887 return Err("EP route weights contain a non-finite value".into());
7888 }
7889 if self.native_p2p {
7890 return self.run_routed_experts_native(
7891 experts,
7892 input,
7893 tokens,
7894 selected,
7895 route_weights,
7896 experts_per_token,
7897 activation_limit,
7898 );
7899 }
7900
7901 let mut output = vec![0.0f32; tokens * experts.input_width];
7902 let per_rank = experts.expert_count / experts.ranks.len();
7903 for token in 0..tokens {
7904 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7905 for slot in 0..experts_per_token {
7906 let pair = token * experts_per_token + slot;
7907 let expert = selected[pair];
7908 if expert >= experts.expert_count {
7909 return Err(format!(
7910 "EP selected expert {expert} outside 0..{}",
7911 experts.expert_count
7912 )
7913 .into());
7914 }
7915 let owner = expert / per_rank;
7916 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7917 let rank = &experts.ranks[owner];
7918 let engine = &self.ranks[owner];
7919 let gate =
7920 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7921 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7922 let activated: Vec<f32> = gate
7923 .iter()
7924 .zip(&up)
7925 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7926 .collect();
7927 debug_assert_eq!(activated.len(), experts.expert_width);
7928 let down =
7929 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7930 let weight = route_weights[pair];
7931 for (sum, value) in output
7932 [token * experts.input_width..(token + 1) * experts.input_width]
7933 .iter_mut()
7934 .zip(down)
7935 {
7936 *sum += weight * value;
7937 }
7938 }
7939 }
7940 Ok(output)
7941 }
7942
7943 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native(
7945 &self,
7946 experts: &ResidentExpertParallel,
7947 input: &[f32],
7948 tokens: usize,
7949 selected: &[usize],
7950 route_weights: &[f32],
7951 experts_per_token: usize,
7952 activation_limit: Option<f32>,
7953 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7954 if !self.native_p2p || self.ranks.len() < 2 {
7955 return Err("native EP execution requires at least two P2P ranks".into());
7956 }
7957 if self.ep_device_arithmetic {
7958 return self.run_routed_experts_native_device(
7959 experts,
7960 input,
7961 tokens,
7962 selected,
7963 route_weights,
7964 experts_per_token,
7965 activation_limit,
7966 );
7967 }
7968 let mut output = vec![0.0f32; tokens * experts.input_width];
7969 let per_rank = experts.expert_count / experts.ranks.len();
7970 for token in 0..tokens {
7971 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7972 let mut rank_inputs = (0..self.ranks.len())
7973 .map(|_| None)
7974 .collect::<Vec<Option<CudaSlice<f32>>>>();
7975 rank_inputs[0] = Some({
7976 let root = &self.ranks[0];
7977 let _main = root.gpu.enter_main()?;
7978 root.htod(input_row)?
7979 });
7980
7981 for slot in 0..experts_per_token {
7982 let pair = token * experts_per_token + slot;
7983 let expert = selected[pair];
7984 if expert >= experts.expert_count {
7985 return Err(format!(
7986 "EP selected expert {expert} outside 0..{}",
7987 experts.expert_count
7988 )
7989 .into());
7990 }
7991 let owner = expert / per_rank;
7992 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7993 if rank_inputs[owner].is_none() {
7994 let peer_input = {
7995 let root_input = rank_inputs[0]
7996 .as_ref()
7997 .ok_or("native EP lost its root input")?;
7998 let engine = &self.ranks[owner];
7999 let _main = engine.gpu.enter_main()?;
8000 let mut peer_input = engine.uninit(experts.input_width)?;
8001 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8002 peer_input
8003 };
8004 rank_inputs[owner] = Some(peer_input);
8005 }
8006
8007 let rank = &experts.ranks[owner];
8008 let engine = &self.ranks[owner];
8009 let owner_input = rank_inputs[owner]
8010 .as_ref()
8011 .ok_or("native EP owner input is absent after dispatch")?;
8012 let gate = run_resident_bank_expert_device(
8013 engine,
8014 &rank.gate,
8015 local_expert,
8016 owner_input,
8017 1,
8018 )?;
8019 let up = run_resident_bank_expert_device(
8020 engine,
8021 &rank.up,
8022 local_expert,
8023 owner_input,
8024 1,
8025 )?;
8026 let (gate, up) = {
8027 let _main = engine.gpu.enter_main()?;
8028 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
8029 };
8030 let activated = gate
8031 .iter()
8032 .zip(&up)
8033 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8034 .collect::<Vec<_>>();
8035 debug_assert_eq!(activated.len(), experts.expert_width);
8036 let activated = {
8037 let _main = engine.gpu.enter_main()?;
8038 engine.htod(&activated)?
8039 };
8040 let down = run_resident_bank_expert_device(
8041 engine,
8042 &rank.down,
8043 local_expert,
8044 &activated,
8045 1,
8046 )?;
8047 let down = if owner == 0 {
8048 let _main = engine.gpu.enter_main()?;
8049 engine.dtoh(&down)?
8050 } else {
8051 let root = &self.ranks[0];
8052 let _main = root.gpu.enter_main()?;
8053 let mut root_down = root.uninit(experts.input_width)?;
8054 root.stream().memcpy_dtod(&down, &mut root_down)?;
8055 root.dtoh(&root_down)?
8056 };
8057 let weight = route_weights[pair];
8058 for (sum, value) in output
8059 [token * experts.input_width..(token + 1) * experts.input_width]
8060 .iter_mut()
8061 .zip(down)
8062 {
8063 *sum += weight * value;
8064 }
8065 }
8066 }
8067 Ok(output)
8068 }
8069
8070 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native_device(
8072 &self,
8073 experts: &ResidentExpertParallel,
8074 input: &[f32],
8075 tokens: usize,
8076 selected: &[usize],
8077 route_weights: &[f32],
8078 experts_per_token: usize,
8079 activation_limit: Option<f32>,
8080 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8081 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
8082 return Err(
8083 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
8084 );
8085 }
8086 let mut output = Vec::with_capacity(tokens * experts.input_width);
8087 let per_rank = experts.expert_count / experts.ranks.len();
8088 let root = &self.ranks[0];
8089 for token in 0..tokens {
8090 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8091 let mut rank_inputs = (0..self.ranks.len())
8092 .map(|_| None)
8093 .collect::<Vec<Option<CudaSlice<f32>>>>();
8094 rank_inputs[0] = Some({
8095 let _main = root.gpu.enter_main()?;
8096 root.htod(input_row)?
8097 });
8098 let mut root_output = {
8099 let _main = root.gpu.enter_main()?;
8100 root.zeros(experts.input_width)?
8101 };
8102 let mut remote_down_keepalive = Vec::new();
8103
8104 for slot in 0..experts_per_token {
8105 let pair = token * experts_per_token + slot;
8106 let expert = selected[pair];
8107 if expert >= experts.expert_count {
8108 return Err(format!(
8109 "EP selected expert {expert} outside 0..{}",
8110 experts.expert_count
8111 )
8112 .into());
8113 }
8114 let owner = expert / per_rank;
8115 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8116 if rank_inputs[owner].is_none() {
8117 let peer_input = {
8118 let root_input = rank_inputs[0]
8119 .as_ref()
8120 .ok_or("native EP lost its root input")?;
8121 let engine = &self.ranks[owner];
8122 let _main = engine.gpu.enter_main()?;
8123 let mut peer_input = engine.uninit(experts.input_width)?;
8124 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8125 peer_input
8126 };
8127 rank_inputs[owner] = Some(peer_input);
8128 }
8129
8130 let rank = &experts.ranks[owner];
8131 let engine = &self.ranks[owner];
8132 let owner_input = rank_inputs[owner]
8133 .as_ref()
8134 .ok_or("native EP owner input is absent after dispatch")?;
8135 let gate = run_resident_bank_expert_device(
8136 engine,
8137 &rank.gate,
8138 local_expert,
8139 owner_input,
8140 1,
8141 )?;
8142 let up = run_resident_bank_expert_device(
8143 engine,
8144 &rank.up,
8145 local_expert,
8146 owner_input,
8147 1,
8148 )?;
8149 let activated = {
8150 let _main = engine.gpu.enter_main()?;
8151 let mut activated = engine.uninit(experts.expert_width)?;
8152 if let Some(limit) = activation_limit {
8153 engine.silu_clamped_mul_host_expf(
8154 &gate,
8155 &up,
8156 limit,
8157 &mut activated,
8158 experts.expert_width,
8159 )?;
8160 } else {
8161 engine.silu_mul_host_expf(
8162 &gate,
8163 &up,
8164 &mut activated,
8165 experts.expert_width,
8166 )?;
8167 }
8168 activated
8169 };
8170 let down = run_resident_bank_expert_device(
8171 engine,
8172 &rank.down,
8173 local_expert,
8174 &activated,
8175 1,
8176 )?;
8177 let root_down = if owner == 0 {
8178 down
8179 } else {
8180 let _main = root.gpu.enter_main()?;
8181 let mut root_down = root.uninit(experts.input_width)?;
8182 root.stream().memcpy_dtod(&down, &mut root_down)?;
8183 remote_down_keepalive.push(down);
8187 root_down
8188 };
8189 let _main = root.gpu.enter_main()?;
8190 let mut destination = root_output.slice_mut(0..experts.input_width);
8191 root.axpy_host_into(
8192 &root_down.slice(0..root_down.len()),
8193 route_weights[pair],
8194 &mut destination,
8195 experts.input_width,
8196 )?;
8197 }
8198
8199 let _main = root.gpu.enter_main()?;
8200 let root_output = root.dtoh(&root_output)?;
8201 drop(remote_down_keepalive);
8202 output.extend(root_output);
8203 }
8204 Ok(output)
8205 }
8206}
8207
8208#[allow(clippy::manual_is_multiple_of)] fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8210 if matrix.out_features % tp != 0 {
8211 return Err(format!(
8212 "column-parallel out_features {} is not divisible by TP={tp}",
8213 matrix.out_features
8214 ));
8215 }
8216 let local_out = matrix.out_features / tp;
8217 if !local_out.is_multiple_of(FP8_BLOCK) {
8218 return Err(format!(
8219 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
8220 E4M3 scale block"
8221 ));
8222 }
8223 Ok(())
8224}
8225
8226#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
8228 if !matches!(tp, 1 | 2 | 4 | 8) {
8229 return Err(format!(
8230 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8231 ));
8232 }
8233 if out_features == 0 || !out_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8234 return Err(format!(
8235 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
8236 ));
8237 }
8238 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
8239 let local_out = out_features / tp;
8240 if local_out % canonical_rows != 0 {
8241 return Err(format!(
8242 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
8243 {canonical_rows}-row chunks"
8244 ));
8245 }
8246 Ok(canonical_rows)
8247}
8248
8249#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
8251 if !matches!(tp, 1 | 2 | 4 | 8) {
8252 return Err(format!(
8253 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8254 ));
8255 }
8256 if in_features == 0 || !in_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8257 return Err(format!(
8258 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
8259 ));
8260 }
8261 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
8262 let local_in = in_features / tp;
8263 if local_in % canonical_cols != 0 {
8264 return Err(format!(
8265 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
8266 {canonical_cols}-column chunks"
8267 ));
8268 }
8269 Ok(canonical_cols)
8270}
8271
8272#[allow(clippy::manual_is_multiple_of)] fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8274 if matrix.in_features % tp != 0 {
8275 return Err(format!(
8276 "row-parallel in_features {} is not divisible by TP={tp}",
8277 matrix.in_features
8278 ));
8279 }
8280 let local_in = matrix.in_features / tp;
8281 if !local_in.is_multiple_of(FP8_BLOCK) {
8282 return Err(format!(
8283 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
8284 E4M3 scale block"
8285 ));
8286 }
8287 Ok(())
8288}
8289
8290fn upload_rank(
8291 engine: &Engine,
8292 matrix: E4m3BlockMatrix<'_>,
8293) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
8294 let _main = engine.gpu.enter_main()?;
8295 matrix.validate()?;
8296 Ok(ResidentE4m3Rank {
8297 codes: engine.htod_bytes(matrix.codes)?,
8298 scales: engine.htod(matrix.scales)?,
8299 out_features: matrix.out_features,
8300 in_features: matrix.in_features,
8301 })
8302}
8303
8304fn upload_bf16_rank(
8305 engine: &Engine,
8306 matrix: Bf16Matrix<'_>,
8307 f32_mirror: bool,
8308) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
8309 let _main = engine.gpu.enter_main()?;
8310 matrix.validate()?;
8311 let bytes = engine.htod_bytes(matrix.bytes)?;
8312 let weight = if f32_mirror {
8313 let values = matrix
8314 .out_features
8315 .checked_mul(matrix.in_features)
8316 .ok_or("resident BF16 mirror element count overflow")?;
8317 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
8318 } else {
8319 ResidentBf16Weight::Bf16(bytes)
8320 };
8321 let q8 = if crate::step_tp_w8_on() && matrix.in_features.is_multiple_of(32) {
8325 if let ResidentBf16Weight::Bf16(bytes) = &weight {
8326 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
8333 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
8334 engine.encode_q8_0_from_bf16(
8335 bytes,
8336 &mut interleaved,
8337 matrix.in_features,
8338 matrix.out_features,
8339 )?;
8340 let mirror =
8341 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
8342 Some(mirror)
8343 } else {
8344 None
8345 }
8346 } else {
8347 None
8348 };
8349 Ok(ResidentBf16Rank {
8350 weight,
8351 out_features: matrix.out_features,
8352 in_features: matrix.in_features,
8353 q8,
8354 })
8355}
8356
8357fn upload_expert_bank_rank(
8358 engine: &Engine,
8359 bank: E4m3ExpertBank<'_>,
8360 expert_range: Range<usize>,
8361) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8362 let _main = engine.gpu.enter_main()?;
8363 bank.validate()?;
8364 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
8365 return Err(format!(
8366 "invalid EP expert range {expert_range:?} for {} experts",
8367 bank.expert_count
8368 )
8369 .into());
8370 }
8371 let code_stride = bank.out_features * bank.in_features;
8372 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
8373 Ok(ResidentE4m3ExpertBankRank {
8374 codes: engine.htod_bytes(
8375 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
8376 )?,
8377 scales: engine.htod(
8378 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
8379 )?,
8380 expert_range,
8381 out_features: bank.out_features,
8382 in_features: bank.in_features,
8383 code_stride,
8384 scale_stride,
8385 k_blocks: None,
8386 })
8387}
8388
8389#[allow(clippy::manual_is_multiple_of)] fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8391 if bank.out_features % tp != 0 {
8392 return Err(format!(
8393 "TP expert output width {} is not divisible by TP={tp}",
8394 bank.out_features
8395 ));
8396 }
8397 let local_out = bank.out_features / tp;
8398 if !local_out.is_multiple_of(FP8_BLOCK) {
8399 return Err(format!(
8400 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
8401 ));
8402 }
8403 Ok(())
8404}
8405
8406#[allow(clippy::manual_is_multiple_of)] fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8408 if bank.in_features % tp != 0 {
8409 return Err(format!(
8410 "TP expert input width {} is not divisible by TP={tp}",
8411 bank.in_features
8412 ));
8413 }
8414 let local_in = bank.in_features / tp;
8415 if !local_in.is_multiple_of(FP8_BLOCK) {
8416 return Err(format!(
8417 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
8418 ));
8419 }
8420 Ok(())
8421}
8422
8423fn upload_column_bank_rank(
8424 engine: &Engine,
8425 bank: E4m3ExpertBank<'_>,
8426 tp: usize,
8427 rank: usize,
8428) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8429 let _main = engine.gpu.enter_main()?;
8430 let packed = pack_column_bank_rank(bank, tp, rank)?;
8431 Ok(ResidentE4m3ExpertBankRank {
8432 codes: engine.htod_bytes(&packed.codes)?,
8433 scales: engine.htod(&packed.scales)?,
8434 expert_range: packed.expert_range,
8435 out_features: packed.out_features,
8436 in_features: packed.in_features,
8437 code_stride: packed.code_stride,
8438 scale_stride: packed.scale_stride,
8439 k_blocks: packed.k_blocks,
8440 })
8441}
8442
8443fn pack_column_bank_rank(
8444 bank: E4m3ExpertBank<'_>,
8445 tp: usize,
8446 rank: usize,
8447) -> Result<PackedE4m3ExpertBankRank, String> {
8448 bank.validate()?;
8449 validate_column_bank_shape(bank, tp)?;
8450 if rank >= tp {
8451 return Err(format!("TP rank {rank} outside 0..{tp}"));
8452 }
8453 let local_out = bank.out_features / tp;
8454 let full_code_stride = bank.out_features * bank.in_features;
8455 let local_code_stride = local_out * bank.in_features;
8456 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8457 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8458 let local_scale_rows = local_out / FP8_BLOCK;
8459 let local_scale_stride = local_scale_rows * scale_cols;
8460 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8461 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8462 let row_start = rank * local_out;
8463 let scale_row_start = rank * local_scale_rows;
8464 for expert in 0..bank.expert_count {
8465 let code_start = expert * full_code_stride + row_start * bank.in_features;
8466 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8467 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8468 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8469 }
8470 Ok(PackedE4m3ExpertBankRank {
8471 codes,
8472 scales,
8473 expert_range: 0..bank.expert_count,
8474 out_features: local_out,
8475 in_features: bank.in_features,
8476 code_stride: local_code_stride,
8477 scale_stride: local_scale_stride,
8478 k_blocks: None,
8479 })
8480}
8481
8482fn upload_row_bank_rank(
8483 engine: &Engine,
8484 bank: E4m3ExpertBank<'_>,
8485 tp: usize,
8486 rank: usize,
8487) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8488 let _main = engine.gpu.enter_main()?;
8489 let packed = pack_row_bank_rank(bank, tp, rank)?;
8490 Ok(ResidentE4m3ExpertBankRank {
8491 codes: engine.htod_bytes(&packed.codes)?,
8492 scales: engine.htod(&packed.scales)?,
8493 expert_range: packed.expert_range,
8494 out_features: packed.out_features,
8495 in_features: packed.in_features,
8496 code_stride: packed.code_stride,
8497 scale_stride: packed.scale_stride,
8498 k_blocks: packed.k_blocks,
8499 })
8500}
8501
8502fn pack_row_bank_rank(
8503 bank: E4m3ExpertBank<'_>,
8504 tp: usize,
8505 rank: usize,
8506) -> Result<PackedE4m3ExpertBankRank, String> {
8507 bank.validate()?;
8508 validate_row_bank_shape(bank, tp)?;
8509 if rank >= tp {
8510 return Err(format!("TP rank {rank} outside 0..{tp}"));
8511 }
8512 let local_in = bank.in_features / tp;
8513 let full_code_stride = bank.out_features * bank.in_features;
8514 let local_code_stride = bank.out_features * local_in;
8515 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8516 let local_scale_cols = local_in / FP8_BLOCK;
8517 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8518 let full_scale_stride = scale_rows * full_scale_cols;
8519 let local_scale_stride = scale_rows * local_scale_cols;
8520 let global_block_start = rank * local_scale_cols;
8521 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8522 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8523 for expert in 0..bank.expert_count {
8524 let expert_code_start = expert * full_code_stride;
8525 let expert_scale_start = expert * full_scale_stride;
8526 for local_block in 0..local_scale_cols {
8527 let global_block = global_block_start + local_block;
8528 let column_start = global_block * FP8_BLOCK;
8529 for row in 0..bank.out_features {
8530 let start = expert_code_start + row * bank.in_features + column_start;
8531 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8532 }
8533 for row in 0..scale_rows {
8534 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8535 }
8536 }
8537 }
8538 Ok(PackedE4m3ExpertBankRank {
8539 codes,
8540 scales,
8541 expert_range: 0..bank.expert_count,
8542 out_features: bank.out_features,
8543 in_features: local_in,
8544 code_stride: local_code_stride,
8545 scale_stride: local_scale_stride,
8546 k_blocks: Some(local_scale_cols),
8547 })
8548}
8549
8550fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8551 if engines.len() != ranks.len() {
8552 return Err(format!(
8553 "resident TP rank count {} != runtime rank count {}",
8554 ranks.len(),
8555 engines.len()
8556 ));
8557 }
8558 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8559 let device = engine.ctx().ordinal();
8560 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8561 return Err(format!(
8562 "resident TP rank {rank} is not owned by runtime device {device}"
8563 ));
8564 }
8565 }
8566 Ok(())
8567}
8568
8569fn validate_tp_bank_residency(
8570 engines: &[Engine],
8571 experts: &ResidentTpExpertBank,
8572) -> Result<(), String> {
8573 if engines.len() != experts.gate.len()
8574 || engines.len() != experts.up.len()
8575 || engines.len() != experts.down.len()
8576 {
8577 return Err(format!(
8578 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8579 experts.gate.len(),
8580 experts.up.len(),
8581 experts.down.len(),
8582 engines.len()
8583 ));
8584 }
8585 for (rank, engine) in engines.iter().enumerate() {
8586 let device = engine.ctx().ordinal();
8587 for (projection, bank) in [
8588 ("gate", &experts.gate[rank]),
8589 ("up", &experts.up[rank]),
8590 ("down", &experts.down[rank]),
8591 ] {
8592 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8593 return Err(format!(
8594 "resident TP rank {rank} {projection} bank is not owned by runtime device \
8595 {device}"
8596 ));
8597 }
8598 }
8599 }
8600 Ok(())
8601}
8602
8603fn validate_ep_residency(
8604 engines: &[Engine],
8605 experts: &ResidentExpertParallel,
8606) -> Result<(), String> {
8607 if engines.len() != experts.ranks.len() {
8608 return Err(format!(
8609 "resident EP rank count {} != runtime rank count {}",
8610 experts.ranks.len(),
8611 engines.len()
8612 ));
8613 }
8614 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
8615 let device = engine.ctx().ordinal();
8616 for (projection, bank) in [
8617 ("gate", &resident.gate),
8618 ("up", &resident.up),
8619 ("down", &resident.down),
8620 ] {
8621 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8622 return Err(format!(
8623 "resident EP rank {rank} {projection} bank is not owned by runtime device \
8624 {device}"
8625 ));
8626 }
8627 }
8628 }
8629 Ok(())
8630}
8631
8632fn run_rank(
8633 engine: &Engine,
8634 matrix: E4m3BlockMatrix<'_>,
8635 activations: &[f32],
8636 tokens: usize,
8637) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8638 let _main = engine.gpu.enter_main()?;
8639 let codes = engine.htod_bytes(matrix.codes)?;
8640 let scales = engine.htod(matrix.scales)?;
8641 let activations = engine.htod(activations)?;
8642 let output = engine.qmatvec_mmq_fp8_blk(
8643 &codes,
8644 &scales,
8645 &activations,
8646 tokens,
8647 matrix.in_features,
8648 matrix.out_features,
8649 )?;
8650 engine.dtoh(&output)
8651}
8652
8653fn run_resident_rank(
8654 engine: &Engine,
8655 matrix: &ResidentE4m3Rank,
8656 activations: &[f32],
8657 tokens: usize,
8658) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8659 let _main = engine.gpu.enter_main()?;
8660 let activations = engine.htod(activations)?;
8661 let output = engine.qmatvec_mmq_fp8_blk(
8662 &matrix.codes,
8663 &matrix.scales,
8664 &activations,
8665 tokens,
8666 matrix.in_features,
8667 matrix.out_features,
8668 )?;
8669 engine.dtoh(&output)
8670}
8671
8672fn run_resident_bf16_rank(
8673 engine: &Engine,
8674 matrix: &ResidentBf16Rank,
8675 activations: &[f32],
8676 tokens: usize,
8677 canonical_chunk_rows: Option<usize>,
8678) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8679 let _main = engine.gpu.enter_main()?;
8680 let activations = engine.htod(activations)?;
8681 let output = run_resident_bf16_rank_device(
8682 engine,
8683 matrix,
8684 &activations,
8685 tokens,
8686 canonical_chunk_rows,
8687 false,
8688 )?;
8689 engine.dtoh(&output)
8690}
8691
8692fn run_resident_bf16_rank_device(
8693 engine: &Engine,
8694 matrix: &ResidentBf16Rank,
8695 activations: &CudaSlice<f32>,
8696 tokens: usize,
8697 canonical_chunk_rows: Option<usize>,
8698 strided_chunk_output: bool,
8699) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8700 let _main = engine.gpu.enter_main()?;
8701 if activations.ordinal() != engine.ctx().ordinal() {
8702 return Err(format!(
8703 "resident BF16 activation device {} != rank device {}",
8704 activations.ordinal(),
8705 engine.ctx().ordinal()
8706 )
8707 .into());
8708 }
8709 if activations.len() != tokens * matrix.in_features {
8710 return Err(format!(
8711 "resident BF16 activation count {} != {tokens}x{}",
8712 activations.len(),
8713 matrix.in_features
8714 )
8715 .into());
8716 }
8717 match (&matrix.weight, canonical_chunk_rows) {
8718 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8719 .linear_bf16_resident_canonical_rows(
8720 activations,
8721 bytes,
8722 tokens,
8723 matrix.in_features,
8724 matrix.out_features,
8725 rows,
8726 ),
8727 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8728 activations,
8729 bytes,
8730 tokens,
8731 matrix.in_features,
8732 matrix.out_features,
8733 ),
8734 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8735 .linear_f32_resident_canonical_rows_strided(
8736 activations,
8737 values,
8738 tokens,
8739 matrix.in_features,
8740 matrix.out_features,
8741 rows,
8742 ),
8743 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8744 activations,
8745 values,
8746 tokens,
8747 matrix.in_features,
8748 matrix.out_features,
8749 rows,
8750 ),
8751 (ResidentBf16Weight::F32(values), None) => engine.linear(
8752 activations,
8753 values,
8754 tokens,
8755 matrix.in_features,
8756 matrix.out_features,
8757 ),
8758 }
8759}
8760
8761fn validate_resident_bf16_ranks(
8762 engines: &[Engine],
8763 ranks: &[ResidentBf16Rank],
8764) -> Result<(), String> {
8765 if engines.len() != ranks.len() {
8766 return Err(format!(
8767 "resident BF16 TP rank count {} != runtime rank count {}",
8768 ranks.len(),
8769 engines.len(),
8770 ));
8771 }
8772 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8773 let device = engine.ctx().ordinal();
8774 if matrix.weight.ordinal() != device {
8775 return Err(format!(
8776 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8777 ));
8778 }
8779 }
8780 Ok(())
8781}
8782
8783fn validate_step_bf16_row_residency(
8784 engines: &[Engine],
8785 matrix: &ResidentStepBf16RowParallel,
8786) -> Result<(), String> {
8787 if engines.len() != matrix.ranks.len() {
8788 return Err(format!(
8789 "resident Step BF16 row rank count {} != runtime rank count {}",
8790 matrix.ranks.len(),
8791 engines.len(),
8792 ));
8793 }
8794 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
8795 if matrix.canonical_chunk_cols != canonical_cols {
8796 return Err(format!(
8797 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
8798 matrix.canonical_chunk_cols
8799 ));
8800 }
8801 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
8802 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
8803 if blocks.len() != blocks_per_rank {
8804 return Err(format!(
8805 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
8806 blocks.len()
8807 ));
8808 }
8809 let device = engine.ctx().ordinal();
8810 for (block, resident) in blocks.iter().enumerate() {
8811 if resident.weight.ordinal() != device
8812 || resident.in_features != canonical_cols
8813 || resident.out_features != matrix.out_features
8814 {
8815 return Err(format!(
8816 "resident Step BF16 row rank {rank} block {block} has inconsistent \
8817 device or geometry"
8818 ));
8819 }
8820 }
8821 }
8822 Ok(())
8823}
8824
8825fn validate_replicated_device_rows(
8826 engines: &[Engine],
8827 rows: &ResidentReplicatedDeviceRows,
8828) -> Result<(), String> {
8829 let rank_lengths = rows
8830 .ranks
8831 .iter()
8832 .map(|rank_rows| rank_rows.len())
8833 .collect::<Vec<_>>();
8834 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8835 if rows
8836 .ranks
8837 .iter()
8838 .zip(engines)
8839 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8840 {
8841 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8842 }
8843 Ok(())
8844}
8845
8846fn replicated_device_row_values(
8847 tokens: usize,
8848 width: usize,
8849 expected_ranks: usize,
8850 rank_lengths: &[usize],
8851) -> Result<usize, String> {
8852 let values = tokens
8853 .checked_mul(width)
8854 .ok_or("replicated device row size overflow")?;
8855 if tokens == 0
8856 || width == 0
8857 || expected_ranks == 0
8858 || rank_lengths.len() != expected_ranks
8859 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8860 {
8861 return Err(format!(
8862 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8863 tokens,
8864 width,
8865 rank_lengths.len(),
8866 expected_ranks
8867 ));
8868 }
8869 Ok(values)
8870}
8871
8872fn replicated_device_row_source_values(
8873 tokens: usize,
8874 width: usize,
8875 source_len: usize,
8876 source_device: usize,
8877 root_device: usize,
8878) -> Result<usize, String> {
8879 let values = tokens
8880 .checked_mul(width)
8881 .ok_or("replicated device row size overflow")?;
8882 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8883 return Err(format!(
8884 "replicated device row source has inconsistent geometry/device \
8885 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8886 ));
8887 }
8888 Ok(values)
8889}
8890
8891#[allow(clippy::manual_is_multiple_of)] fn bf16_column_shard(
8893 matrix: Bf16Matrix<'_>,
8894 tp: usize,
8895 rank: usize,
8896) -> Result<Bf16Matrix<'_>, String> {
8897 matrix.validate()?;
8898 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8899 return Err(format!(
8900 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8901 matrix.out_features
8902 ));
8903 }
8904 let local_out = matrix.out_features / tp;
8905 let row_bytes = matrix.in_features * 2;
8906 let start = rank * local_out * row_bytes;
8907 Ok(Bf16Matrix {
8908 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8909 out_features: local_out,
8910 in_features: matrix.in_features,
8911 })
8912}
8913
8914#[allow(clippy::manual_is_multiple_of)] fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8916 matrix.validate()?;
8917 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8918 return Err(format!(
8919 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8920 matrix.in_features
8921 ));
8922 }
8923 let local_in = matrix.in_features / tp;
8924 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8925 for row in 0..matrix.out_features {
8926 let start = (row * matrix.in_features + rank * local_in) * 2;
8927 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8928 }
8929 Ok(bytes)
8930}
8931
8932fn bf16_row_block(
8933 matrix: Bf16Matrix<'_>,
8934 col_start: usize,
8935 block_cols: usize,
8936) -> Result<Vec<u8>, String> {
8937 matrix.validate()?;
8938 let col_end = col_start
8939 .checked_add(block_cols)
8940 .ok_or("BF16 row block column overflow")?;
8941 if block_cols == 0 || col_end > matrix.in_features {
8942 return Err(format!(
8943 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8944 matrix.in_features
8945 ));
8946 }
8947 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8948 for row in 0..matrix.out_features {
8949 let start = (row * matrix.in_features + col_start) * 2;
8950 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8951 }
8952 Ok(bytes)
8953}
8954
8955fn run_resident_bank_expert(
8956 engine: &Engine,
8957 bank: &ResidentE4m3ExpertBankRank,
8958 local_expert: usize,
8959 activations: &[f32],
8960 tokens: usize,
8961) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8962 let _main = engine.gpu.enter_main()?;
8963 if bank.k_blocks.is_some() {
8964 return Err("block-major TP row bank requires canonical block execution".into());
8965 }
8966 let local_count = bank.expert_range.end - bank.expert_range.start;
8967 if local_expert >= local_count {
8968 return Err(format!(
8969 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
8970 bank.expert_range
8971 )
8972 .into());
8973 }
8974 validate_activations(activations, tokens, bank.in_features)?;
8975 let activations = engine.htod(activations)?;
8976 let weight = bank
8977 .codes
8978 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8979 let scales = bank
8980 .scales
8981 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8982 let input = activations.slice(0..activations.len());
8983 let output = engine.qmatvec_mmq_fp8_blk_view(
8984 &weight,
8985 &scales,
8986 &input,
8987 tokens,
8988 bank.in_features,
8989 bank.out_features,
8990 )?;
8991 engine.dtoh(&output)
8992}
8993
8994fn run_resident_bank_expert_block(
8995 engine: &Engine,
8996 bank: &ResidentE4m3ExpertBankRank,
8997 local_expert: usize,
8998 block: usize,
8999 activations: &[f32],
9000) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9001 let _main = engine.gpu.enter_main()?;
9002 let local_count = bank.expert_range.end - bank.expert_range.start;
9003 if local_expert >= local_count {
9004 return Err(format!(
9005 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9006 bank.expert_range
9007 )
9008 .into());
9009 }
9010 let blocks = bank
9011 .k_blocks
9012 .ok_or("TP row bank is not packed in native K-block order")?;
9013 if block >= blocks {
9014 return Err(format!("TP row block {block} outside 0..{blocks}").into());
9015 }
9016 validate_activations(activations, 1, FP8_BLOCK)?;
9017 let block_code_stride = bank.out_features * FP8_BLOCK;
9018 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9019 if bank.in_features != blocks * FP8_BLOCK
9020 || bank.code_stride != blocks * block_code_stride
9021 || bank.scale_stride != blocks * block_scale_stride
9022 {
9023 return Err("TP row bank block-major geometry is inconsistent".into());
9024 }
9025
9026 let expert_code_start = local_expert * bank.code_stride;
9027 let expert_scale_start = local_expert * bank.scale_stride;
9028 let weight = bank.codes.slice(
9029 expert_code_start + block * block_code_stride
9030 ..expert_code_start + (block + 1) * block_code_stride,
9031 );
9032 let scales = bank.scales.slice(
9033 expert_scale_start + block * block_scale_stride
9034 ..expert_scale_start + (block + 1) * block_scale_stride,
9035 );
9036 let activations = engine.htod(activations)?;
9037 let input = activations.slice(0..activations.len());
9038 let output = engine.qmatvec_mmq_fp8_blk_view(
9039 &weight,
9040 &scales,
9041 &input,
9042 1,
9043 FP8_BLOCK,
9044 bank.out_features,
9045 )?;
9046 engine.dtoh(&output)
9047}
9048
9049fn run_resident_bank_expert_device(
9050 engine: &Engine,
9051 bank: &ResidentE4m3ExpertBankRank,
9052 local_expert: usize,
9053 activations: &CudaSlice<f32>,
9054 tokens: usize,
9055) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9056 let _main = engine.gpu.enter_main()?;
9057 if bank.k_blocks.is_some() {
9058 return Err("block-major TP row bank requires canonical block execution".into());
9059 }
9060 let local_count = bank.expert_range.end - bank.expert_range.start;
9061 if local_expert >= local_count {
9062 return Err(format!(
9063 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9064 bank.expert_range
9065 )
9066 .into());
9067 }
9068 let expected = tokens
9069 .checked_mul(bank.in_features)
9070 .ok_or("native TP activation size overflow")?;
9071 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
9072 return Err(format!(
9073 "native TP activation len/device {}/{} != expected {expected}/{}",
9074 activations.len(),
9075 activations.ordinal(),
9076 engine.ctx().ordinal()
9077 )
9078 .into());
9079 }
9080 let weight = bank
9081 .codes
9082 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9083 let scales = bank
9084 .scales
9085 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9086 let input = activations.slice(0..activations.len());
9087 engine.qmatvec_mmq_fp8_blk_view(
9088 &weight,
9089 &scales,
9090 &input,
9091 tokens,
9092 bank.in_features,
9093 bank.out_features,
9094 )
9095}
9096
9097fn run_resident_bank_expert_block_device(
9098 engine: &Engine,
9099 bank: &ResidentE4m3ExpertBankRank,
9100 local_expert: usize,
9101 block: usize,
9102 activations: &cudarc::driver::CudaView<'_, f32>,
9103) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9104 let _main = engine.gpu.enter_main()?;
9105 let local_count = bank.expert_range.end - bank.expert_range.start;
9106 if local_expert >= local_count {
9107 return Err(format!(
9108 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9109 bank.expert_range
9110 )
9111 .into());
9112 }
9113 let blocks = bank
9114 .k_blocks
9115 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
9116 if block >= blocks {
9117 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
9118 }
9119 let activation_device = activations.stream().context().ordinal();
9120 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
9121 return Err(format!(
9122 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
9123 activations.len(),
9124 activation_device,
9125 engine.ctx().ordinal()
9126 )
9127 .into());
9128 }
9129 let block_code_stride = bank.out_features * FP8_BLOCK;
9130 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9131 if bank.in_features != blocks * FP8_BLOCK
9132 || bank.code_stride != blocks * block_code_stride
9133 || bank.scale_stride != blocks * block_scale_stride
9134 {
9135 return Err("native TP row bank block-major geometry is inconsistent".into());
9136 }
9137 let expert_code_start = local_expert * bank.code_stride;
9138 let expert_scale_start = local_expert * bank.scale_stride;
9139 let weight = bank.codes.slice(
9140 expert_code_start + block * block_code_stride
9141 ..expert_code_start + (block + 1) * block_code_stride,
9142 );
9143 let scales = bank.scales.slice(
9144 expert_scale_start + block * block_scale_stride
9145 ..expert_scale_start + (block + 1) * block_scale_stride,
9146 );
9147 engine.qmatvec_mmq_fp8_blk_view(
9148 &weight,
9149 &scales,
9150 activations,
9151 1,
9152 FP8_BLOCK,
9153 bank.out_features,
9154 )
9155}
9156
9157pub(crate) fn grant_peer_access(
9176 accessor: &Engine,
9177 owner: &Engine,
9178 label: &str,
9179) -> Result<(), Box<dyn std::error::Error>> {
9180 let (a_dev, o_dev) = (accessor.ctx().ordinal(), owner.ctx().ordinal());
9181 let mut can_access = 0;
9182 unsafe {
9183 cudarc::driver::sys::cuDeviceCanAccessPeer(
9184 &mut can_access,
9185 accessor.ctx().cu_device(),
9186 owner.ctx().cu_device(),
9187 )
9188 .result()?;
9189 }
9190 if can_access == 0 {
9191 return Err(
9192 format!("{label} requires P2P, but dev{a_dev} cannot access dev{o_dev}").into(),
9193 );
9194 }
9195 accessor.ctx().bind_to_thread()?;
9196 let rc = unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(owner.ctx().cu_ctx(), 0) };
9197 use cudarc::driver::sys::cudaError_enum as E;
9198 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
9199 return Err(format!(
9200 "{label} cuCtxEnablePeerAccess(dev{a_dev} -> dev{o_dev}) failed: {rc:?}"
9201 )
9202 .into());
9203 }
9204 let device = cudarc::driver::result::device::get(o_dev as i32)?;
9205 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
9206 unsafe {
9207 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
9208 }
9209 let desc = cudarc::driver::sys::CUmemAccessDesc {
9210 location: cudarc::driver::sys::CUmemLocation {
9211 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
9212 id: a_dev as i32,
9213 },
9214 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
9215 };
9216 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
9217 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
9218 return Err(format!(
9219 "{label} cuMemPoolSetAccess(dev{o_dev} pool -> dev{a_dev}) failed: {rc:?}"
9220 )
9221 .into());
9222 }
9223 Ok(())
9224}
9225
9226fn configure_native_p2p(
9227 ranks: &[Engine],
9228 devices: &[usize],
9229) -> Result<(), Box<dyn std::error::Error>> {
9230 if ranks.len() != devices.len() || ranks.len() < 2 {
9231 return Err("native TP P2P setup requires matching multi-rank devices".into());
9232 }
9233 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
9234 if engine.ctx().ordinal() != device {
9235 return Err(format!(
9236 "native TP rank {rank} context device {} != requested device {device}",
9237 engine.ctx().ordinal()
9238 )
9239 .into());
9240 }
9241 }
9242
9243 for src in 0..ranks.len() {
9244 for dst in 0..ranks.len() {
9245 if src == dst {
9246 continue;
9247 }
9248 grant_peer_access(&ranks[src], &ranks[dst], "native TP")?;
9249 }
9250 }
9251
9252 for src in 0..ranks.len() {
9253 for dst in 0..ranks.len() {
9254 if src == dst {
9255 continue;
9256 }
9257 for &words in NATIVE_P2P_PROBE_WORDS {
9258 let expected = (0..words)
9259 .map(|index| {
9260 (index as u32)
9261 .wrapping_mul(0x9e37_79b9)
9262 .wrapping_add(((src as u32) << 16) | dst as u32)
9263 })
9264 .collect::<Vec<_>>();
9265 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
9266 let source = ranks[src].htod_u32_v(&expected)?;
9267 let mut destination = ranks[dst].htod_u32_v(&poison)?;
9268 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
9269 let actual = ranks[dst].dtoh_u32(&destination)?;
9270 if actual != expected {
9271 let mismatches = actual
9272 .iter()
9273 .zip(&expected)
9274 .filter(|(actual, expected)| actual != expected)
9275 .count();
9276 return Err(format!(
9277 "native TP peer probe dev{}->dev{} failed at {} bytes: \
9278 {mismatches}/{} words differ",
9279 devices[src],
9280 devices[dst],
9281 words * std::mem::size_of::<u32>(),
9282 expected.len()
9283 )
9284 .into());
9285 }
9286 }
9287 }
9288 }
9289 ranks[0].ctx().bind_to_thread()?;
9290 eprintln!(
9291 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
9292 directions={} byte_ladder={:?} mismatches=0",
9293 ranks.len() * (ranks.len() - 1),
9294 NATIVE_P2P_PROBE_WORDS
9295 .iter()
9296 .map(|words| words * std::mem::size_of::<u32>())
9297 .collect::<Vec<_>>(),
9298 );
9299 Ok(())
9300}
9301
9302fn validate_activations(
9303 activations: &[f32],
9304 tokens: usize,
9305 in_features: usize,
9306) -> Result<(), String> {
9307 let expected = tokens
9308 .checked_mul(in_features)
9309 .ok_or_else(|| "activation size overflow".to_string())?;
9310 if activations.len() != expected {
9311 return Err(format!(
9312 "activation count {} != {tokens}x{in_features} ({expected})",
9313 activations.len()
9314 ));
9315 }
9316 if !activations.iter().all(|value| value.is_finite()) {
9317 return Err("activations contain a non-finite value".to_string());
9318 }
9319 Ok(())
9320}
9321
9322fn column_shard(
9323 matrix: E4m3BlockMatrix<'_>,
9324 tp: usize,
9325 rank: usize,
9326) -> Result<E4m3BlockMatrix<'_>, String> {
9327 let local_out = matrix.out_features / tp;
9328 let row_start = rank * local_out;
9329 let code_start = row_start * matrix.in_features;
9330 let code_end = code_start + local_out * matrix.in_features;
9331 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9332 let local_scale_rows = local_out / FP8_BLOCK;
9333 let scale_start = rank * local_scale_rows * scale_cols;
9334 let scale_end = scale_start + local_scale_rows * scale_cols;
9335 Ok(E4m3BlockMatrix {
9336 codes: &matrix.codes[code_start..code_end],
9337 scales: &matrix.scales[scale_start..scale_end],
9338 out_features: local_out,
9339 in_features: matrix.in_features,
9340 })
9341}
9342
9343fn row_shard(
9344 matrix: E4m3BlockMatrix<'_>,
9345 tp: usize,
9346 rank: usize,
9347) -> Result<(Vec<u8>, Vec<f32>), String> {
9348 let local_in = matrix.in_features / tp;
9349 let col_start = rank * local_in;
9350 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
9351 for row in 0..matrix.out_features {
9352 let start = row * matrix.in_features + col_start;
9353 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
9354 }
9355
9356 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
9357 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9358 let local_scale_cols = local_in / FP8_BLOCK;
9359 let scale_col_start = rank * local_scale_cols;
9360 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
9361 for row in 0..scale_rows {
9362 let start = row * scale_cols + scale_col_start;
9363 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
9364 }
9365 Ok((codes, scales))
9366}
9367
9368fn activation_shard(
9369 activations: &[f32],
9370 tokens: usize,
9371 in_features: usize,
9372 tp: usize,
9373 rank: usize,
9374) -> Vec<f32> {
9375 let local_in = in_features / tp;
9376 let col_start = rank * local_in;
9377 let mut shard = Vec::with_capacity(tokens * local_in);
9378 for token in 0..tokens {
9379 let start = token * in_features + col_start;
9380 shard.extend_from_slice(&activations[start..start + local_in]);
9381 }
9382 shard
9383}
9384
9385#[derive(Clone, Copy)]
9405pub struct Nvfp4BlockMatrix<'a> {
9406 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
9410 pub in_features: usize,
9411}
9412
9413impl Nvfp4BlockMatrix<'_> {
9414 pub fn validate(&self) -> Result<(), String> {
9415 if self.in_features == 0 || self.out_features == 0 {
9416 return Err("NVFP4 matrix has a zero dimension".to_string());
9417 }
9418 if !self.in_features.is_multiple_of(64) {
9419 return Err(format!(
9420 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
9421 self.in_features
9422 ));
9423 }
9424 if self.codes.len() != self.out_features * self.in_features / 2 {
9425 return Err(format!(
9426 "NVFP4 code bytes {} != {}x{}/2",
9427 self.codes.len(),
9428 self.out_features,
9429 self.in_features
9430 ));
9431 }
9432 if self.scales.len() != self.out_features * self.in_features / 16 {
9433 return Err(format!(
9434 "NVFP4 scale bytes {} != {}x{}/16",
9435 self.scales.len(),
9436 self.out_features,
9437 self.in_features
9438 ));
9439 }
9440 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
9441 return Err(format!(
9442 "NVFP4 macro scale {} is not finite-positive",
9443 self.macro_scale
9444 ));
9445 }
9446 Ok(())
9447 }
9448}
9449
9450#[derive(Clone, Copy)]
9452pub struct Nvfp4ExpertBank<'a> {
9453 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9457 pub out_features: usize,
9458 pub in_features: usize,
9459}
9460
9461impl Nvfp4ExpertBank<'_> {
9462 pub fn validate(&self) -> Result<(), String> {
9463 if self.expert_count == 0 {
9464 return Err("NVFP4 expert bank is empty".to_string());
9465 }
9466 if self.macros.len() != self.expert_count {
9467 return Err(format!(
9468 "NVFP4 bank macros {} != expert count {}",
9469 self.macros.len(),
9470 self.expert_count
9471 ));
9472 }
9473 self.expert(0).map(|_| ())
9474 }
9475
9476 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9477 if expert >= self.expert_count {
9478 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9479 }
9480 let code_stride = self.out_features * self.in_features / 2;
9481 let scale_stride = self.out_features * self.in_features / 16;
9482 if self.codes.len() != self.expert_count * code_stride
9483 || self.scales.len() != self.expert_count * scale_stride
9484 {
9485 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9486 }
9487 let matrix = Nvfp4BlockMatrix {
9488 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9489 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9490 macro_scale: self.macros[expert],
9491 out_features: self.out_features,
9492 in_features: self.in_features,
9493 };
9494 matrix.validate()?;
9495 Ok(matrix)
9496 }
9497}
9498
9499pub struct ResidentNvfp4Rank {
9501 blocks: crate::CudaSlice<u8>,
9502 macro_scale: f32,
9503 out_features: usize,
9504 in_features: usize,
9505 row_bytes: usize,
9506}
9507
9508pub struct ResidentNvfp4ColumnParallel {
9509 ranks: Vec<ResidentNvfp4Rank>,
9510 pub out_features: usize,
9511 pub in_features: usize,
9512}
9513
9514pub struct ResidentNvfp4RowParallel {
9515 ranks: Vec<ResidentNvfp4Rank>,
9516 pub out_features: usize,
9517 pub in_features: usize,
9518}
9519
9520pub struct ResidentTpNvfp4Expert {
9521 gate: ResidentNvfp4ColumnParallel,
9522 up: ResidentNvfp4ColumnParallel,
9523 down: ResidentNvfp4RowParallel,
9524 pub input_width: usize,
9525 pub expert_width: usize,
9526}
9527
9528pub struct ResidentNvfp4ColumnBankRank {
9532 bank: crate::CudaSlice<u8>,
9536 expert_bytes: usize,
9537 local_out: usize,
9538 in_features: usize,
9539 row_bytes: usize,
9540 slot_major: bool,
9547}
9548
9549impl ResidentNvfp4ColumnBankRank {
9550 fn host_canonical_expert(
9555 &self,
9556 engine: &Engine,
9557 expert: usize,
9558 activations: &crate::CudaSlice<f32>,
9559 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9560 let w = self.expert(expert);
9561 if self.slot_major {
9562 engine.qmatvec_nvfp4_fast_v2(
9563 &w,
9564 activations,
9565 1,
9566 self.in_features,
9567 self.local_out,
9568 self.row_bytes,
9569 )
9570 } else {
9571 engine.qmatvec_nvfp4_fast(
9572 &w,
9573 activations,
9574 1,
9575 self.in_features,
9576 self.local_out,
9577 self.row_bytes,
9578 )
9579 }
9580 }
9581
9582 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9583 self.bank
9584 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9585 }
9586}
9587
9588pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
9594
9595pub struct ResidentNvfp4RowBankRank {
9596 bank: crate::CudaSlice<u8>,
9598 expert_bytes: usize,
9599 device_rank: usize, out_features: usize,
9601 local_in: usize,
9602 row_bytes: usize,
9603 slot_major: bool,
9605}
9606
9607impl ResidentNvfp4RowBankRank {
9608 fn host_canonical_expert(
9611 &self,
9612 engine: &Engine,
9613 expert: usize,
9614 activations: &crate::CudaSlice<f32>,
9615 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9616 let w = self.expert(expert);
9617 if self.slot_major {
9618 engine.qmatvec_nvfp4_fast_v2(
9619 &w,
9620 activations,
9621 1,
9622 self.local_in,
9623 self.out_features,
9624 self.row_bytes,
9625 )
9626 } else {
9627 engine.qmatvec_nvfp4_fast(
9628 &w,
9629 activations,
9630 1,
9631 self.local_in,
9632 self.out_features,
9633 self.row_bytes,
9634 )
9635 }
9636 }
9637
9638 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9639 self.bank
9640 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9641 }
9642}
9643
9644impl ResidentNvfp4TensorParallel {
9645 pub(crate) fn device_workspace_handle(
9646 &self,
9647 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
9648 &self.device_workspace
9649 }
9650}
9651
9652pub struct ResidentNvfp4TensorParallel {
9653 gate: Vec<ResidentNvfp4ColumnBankRank>,
9654 up: Vec<ResidentNvfp4ColumnBankRank>,
9655 down: Vec<ResidentNvfp4RowBankRank>,
9656 macros_gate: Vec<f32>,
9657 macros_up: Vec<f32>,
9658 macros_down: Vec<f32>,
9659 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
9663 macros_up_dev: Vec<crate::CudaSlice<f32>>,
9664 macros_down_dev: Vec<crate::CudaSlice<f32>>,
9665 pub expert_count: usize,
9666 pub input_width: usize,
9667 pub expert_width: usize,
9668 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
9671 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
9675 pub(crate) ep2: bool,
9679}
9680
9681struct RoutesGraph {
9688 exec: cudarc::driver::sys::CUgraphExec,
9689 parent: cudarc::driver::sys::CUgraph,
9690 _children: Vec<cudarc::driver::CudaGraph>,
9691}
9692unsafe impl Send for RoutesGraph {}
9695
9696impl Drop for RoutesGraph {
9697 fn drop(&mut self) {
9698 unsafe {
9699 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
9700 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
9701 }
9702 }
9703}
9704
9705impl Nvfp4DeviceRoutesWorkspace {
9706 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
9707 self.in_stage_e.as_ref()
9708 }
9709 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9710 self.in_stage_e.as_mut()
9711 }
9712 #[allow(dead_code)] pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9714 self.out_stage_e.as_mut()
9715 }
9716 pub(crate) fn arm_stages(
9718 &mut self,
9719 e: &Engine,
9720 width: usize,
9721 n_sel: usize,
9722 ) -> Result<(), Box<dyn std::error::Error>> {
9723 let _main = e.gpu.enter_main()?;
9724 if self.in_stage_e.is_none() {
9725 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9726 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9727 }
9728 if self.dev_route_e.is_none() {
9729 self.dev_route_e = Some((
9730 e.htod_i32(&vec![0i32; n_sel])?,
9731 e.htod(&vec![0.0f32; n_sel])?,
9732 ));
9733 }
9734 Ok(())
9735 }
9736
9737 pub(crate) fn in_and_out_stages_mut(
9739 &mut self,
9740 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
9741 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
9742 (Some(input), Some(output)) => Some((input, output)),
9743 _ => None,
9744 }
9745 }
9746 pub(crate) fn dev_route_e_mut(
9747 &mut self,
9748 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
9749 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
9750 }
9751}
9752
9753pub struct Nvfp4DeviceRoutesWorkspace {
9754 gate_out: Vec<crate::CudaSlice<f32>>,
9757 up_out: Vec<crate::CudaSlice<f32>>,
9758 act_q: Vec<crate::CudaSlice<i8>>,
9759 act_d: Vec<crate::CudaSlice<f32>>,
9760 sel: Vec<crate::CudaSlice<i32>>,
9761 partial: Vec<crate::CudaSlice<f32>>,
9762 accumulator: Vec<crate::CudaSlice<f32>>,
9763 combine_w: Vec<crate::CudaSlice<f32>>,
9765 route_w: Vec<crate::CudaSlice<f32>>,
9768 in_q: Vec<crate::CudaSlice<i8>>,
9771 in_d: Vec<crate::CudaSlice<f32>>,
9772 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9776 prestaged: bool,
9779 rank1_routed: bool,
9782 fence_flags_raw: u64,
9786 fence_ticket: u32,
9787 ev_input: Option<(CudaEvent, usize)>,
9789 in_stage_e: Option<crate::CudaSlice<f32>>,
9792 out_stage_e: Option<crate::CudaSlice<f32>>,
9793 routes_graph: Option<RoutesGraph>,
9794 raw_dev_route_e: Option<(u64, u64)>,
9796 raw_combine: Option<(u64, u64, u64, u64)>,
9797 raw_input: Vec<u64>,
9798 raw_sel: Vec<u64>,
9799 raw_route_w: Vec<u64>,
9800 remote: crate::CudaSlice<f32>,
9801 combined: crate::CudaSlice<f32>,
9802 n_sel: usize,
9803 input: Vec<crate::CudaSlice<f32>>,
9807 ev_rank: Vec<CudaEvent>,
9808 ev_done: Option<CudaEvent>,
9809 ev_entry: Option<(CudaEvent, usize)>,
9810}
9811
9812struct ResidentNvfp4EpRank {
9814 gate: crate::CudaSlice<u8>,
9815 up: crate::CudaSlice<u8>,
9816 down: crate::CudaSlice<u8>,
9817 gate_expert_bytes: usize,
9818 down_expert_bytes: usize,
9819 macros_gate: crate::CudaSlice<f32>,
9820 macros_up: crate::CudaSlice<f32>,
9821 macros_down: crate::CudaSlice<f32>,
9822 expert_range: Range<usize>,
9823}
9824
9825struct Nvfp4EpDeviceWorkspace {
9826 input: Vec<crate::CudaSlice<f32>>,
9827 input_bf16: Vec<crate::CudaSlice<u8>>,
9828 input_q8: Vec<crate::CudaSlice<i8>>,
9829 input_q8_scales: Vec<crate::CudaSlice<f32>>,
9830 sel: Vec<crate::CudaSlice<i32>>,
9831 token_rows: Vec<crate::CudaSlice<i32>>,
9832 global_pairs: Vec<crate::CudaSlice<i32>>,
9833 route_w: Vec<crate::CudaSlice<f32>>,
9834 gate_out: Vec<crate::CudaSlice<f32>>,
9835 up_out: Vec<crate::CudaSlice<f32>>,
9836 activation_bf16: Vec<crate::CudaSlice<u8>>,
9837 activation_q8: Vec<crate::CudaSlice<i8>>,
9838 activation_q8_scales: Vec<crate::CudaSlice<f32>>,
9839 slot_rows: crate::CudaSlice<f32>,
9840 slot_rows_raw: u64,
9841 route_weights: crate::CudaSlice<f32>,
9842 graph_input: crate::CudaSlice<f32>,
9843 graph_output: crate::CudaSlice<f32>,
9844 graph_routes: Option<(u64, u64)>,
9845 graphs: Vec<Option<RoutesGraph>>,
9846 ev_entry: CudaEvent,
9847 ev_entry_device: usize,
9848 ev_rank: Vec<CudaEvent>,
9849 phase_events: Option<Nvfp4EpPhaseEvents>,
9850 capacity_tokens: usize,
9851 experts_per_token: usize,
9852}
9853
9854struct Nvfp4EpPhaseEvents {
9855 head: Vec<CudaEvent>,
9856 copy_done: Vec<CudaEvent>,
9857 gate_up_done: Vec<CudaEvent>,
9858 activation_done: Vec<CudaEvent>,
9859 down_done: Vec<CudaEvent>,
9860}
9861
9862pub(crate) const NVFP4_EP_DEVICE_BATCH_CAP: usize = 128;
9863pub(crate) const NVFP4_EP_DEVICE_ROUTER_BATCH_CAP: usize = 32;
9864pub(crate) const NVFP4_EP_Q8_BATCH_CAP: usize = 32;
9865const NVFP4_EP_GRAPH_BATCH_CAP: usize = 1;
9866
9867fn nvfp4_ep_active_input_values(
9868 input_values: usize,
9869 tokens: usize,
9870 input_width: usize,
9871) -> Result<usize, String> {
9872 if !(1..=NVFP4_EP_DEVICE_BATCH_CAP).contains(&tokens) {
9873 return Err(format!(
9874 "W4A16 NVFP4 device EP batch {tokens} is outside 1..={NVFP4_EP_DEVICE_BATCH_CAP}"
9875 ));
9876 }
9877 let active_values = tokens
9878 .checked_mul(input_width)
9879 .ok_or("W4A16 NVFP4 device EP active input size overflows usize")?;
9880 if input_values < active_values {
9881 return Err(format!(
9882 "W4A16 NVFP4 device EP input {input_values} is smaller than active \
9883 tokens {tokens} x width {input_width} ({active_values})"
9884 ));
9885 }
9886 Ok(active_values)
9887}
9888
9889pub struct ResidentNvfp4ExpertParallel {
9890 ranks: Vec<ResidentNvfp4EpRank>,
9891 macros_gate: Vec<f32>,
9892 macros_up: Vec<f32>,
9893 macros_down: Vec<f32>,
9894 pub expert_count: usize,
9895 pub input_width: usize,
9896 pub expert_width: usize,
9897 gate_row_bytes: usize,
9898 down_row_bytes: usize,
9899 device_workspace: std::sync::Mutex<Option<Nvfp4EpDeviceWorkspace>>,
9900}
9901
9902fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9903 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
9904 matrix.codes,
9905 matrix.scales,
9906 matrix.out_features,
9907 matrix.in_features,
9908 )
9909}
9910
9911fn nvfp4_row_bytes(in_features: usize) -> usize {
9912 in_features / 64 * 36 }
9914
9915pub(crate) fn fuse_rope_append_on() -> bool {
9921 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9922 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
9923}
9924
9925pub(crate) fn no_local_shadow_on() -> bool {
9926 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9927 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
9928}
9929
9930pub fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
9946 assert_eq!(
9952 in_features % 64,
9953 0,
9954 "v2 permute needs whole 64-element superblocks, got in_features={in_features}"
9955 );
9956 let row_bytes = nvfp4_row_bytes(in_features);
9957 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
9958 let n_slots = in_features / 32;
9959 let mut out = Vec::with_capacity(v1.len());
9960 for row in 0..out_features {
9961 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
9962 for g in 0..n_slots {
9963 let (sblk, h) = (g / 2, g % 2);
9964 let b = &r[sblk * 36..sblk * 36 + 36];
9965 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
9966 }
9967 for g in 0..n_slots {
9968 let (sblk, h) = (g / 2, g % 2);
9969 let b = &r[sblk * 36..sblk * 36 + 36];
9970 out.push(b[2 * h]);
9971 out.push(b[2 * h + 1]);
9972 }
9973 }
9974 out
9975}
9976
9977fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>, slot_major: bool) -> Vec<u8> {
9981 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
9982 let v1 = nvfp4_repack_matrix(matrix);
9983 if slot_major {
9984 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
9985 } else {
9986 v1
9987 }
9988}
9989
9990#[allow(clippy::manual_is_multiple_of)] fn nvfp4_column_shard<'a>(
9994 matrix: Nvfp4BlockMatrix<'a>,
9995 tp: usize,
9996 rank: usize,
9997) -> Result<Nvfp4BlockMatrix<'a>, String> {
9998 if matrix.out_features % tp != 0 {
9999 return Err(format!(
10000 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
10001 matrix.out_features
10002 ));
10003 }
10004 let local_out = matrix.out_features / tp;
10005 let code_row = matrix.in_features / 2;
10006 let scale_row = matrix.in_features / 16;
10007 Ok(Nvfp4BlockMatrix {
10008 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
10009 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
10010 macro_scale: matrix.macro_scale,
10011 out_features: local_out,
10012 in_features: matrix.in_features,
10013 })
10014}
10015
10016#[allow(clippy::manual_is_multiple_of)] fn nvfp4_row_shard(
10020 matrix: Nvfp4BlockMatrix<'_>,
10021 tp: usize,
10022 rank: usize,
10023) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
10024 if matrix.in_features % tp != 0 {
10025 return Err(format!(
10026 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
10027 matrix.in_features
10028 ));
10029 }
10030 let local_in = matrix.in_features / tp;
10031 if !local_in.is_multiple_of(64) {
10032 return Err(format!(
10033 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
10034 ));
10035 }
10036 let code_row = matrix.in_features / 2;
10037 let scale_row = matrix.in_features / 16;
10038 let local_code = local_in / 2;
10039 let local_scale = local_in / 16;
10040 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
10041 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
10042 for row in 0..matrix.out_features {
10043 let code_start = row * code_row + rank * local_code;
10044 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
10045 let scale_start = row * scale_row + rank * local_scale;
10046 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
10047 }
10048 Ok((codes, scales, local_in))
10049}
10050
10051fn run_rank_nvfp4(
10055 engine: &Engine,
10056 matrix: Nvfp4BlockMatrix<'_>,
10057 activations: &[f32],
10058 tokens: usize,
10059) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10060 matrix.validate()?;
10061 validate_activations(activations, tokens, matrix.in_features)?;
10062 let _main = engine.gpu.enter_main()?;
10063 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
10064 let activations = engine.htod(activations)?;
10065 let output = engine.qmatvec_nvfp4_fast(
10066 &blocks.slice(0..blocks.len()),
10067 &activations,
10068 tokens,
10069 matrix.in_features,
10070 matrix.out_features,
10071 nvfp4_row_bytes(matrix.in_features),
10072 )?;
10073 engine.dtoh(&output)
10074}
10075
10076fn upload_rank_nvfp4(
10077 engine: &Engine,
10078 matrix: Nvfp4BlockMatrix<'_>,
10079) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
10080 matrix.validate()?;
10081 let _main = engine.gpu.enter_main()?;
10082 Ok(ResidentNvfp4Rank {
10083 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
10084 macro_scale: matrix.macro_scale,
10085 out_features: matrix.out_features,
10086 in_features: matrix.in_features,
10087 row_bytes: nvfp4_row_bytes(matrix.in_features),
10088 })
10089}
10090
10091fn run_resident_rank_nvfp4(
10092 engine: &Engine,
10093 rank: &ResidentNvfp4Rank,
10094 activations: &[f32],
10095 tokens: usize,
10096) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10097 validate_activations(activations, tokens, rank.in_features)?;
10098 let _main = engine.gpu.enter_main()?;
10099 let activations = engine.htod(activations)?;
10100 let output = engine.qmatvec_nvfp4_fast(
10101 &rank.blocks.slice(0..rank.blocks.len()),
10102 &activations,
10103 tokens,
10104 rank.in_features,
10105 rank.out_features,
10106 rank.row_bytes,
10107 )?;
10108 engine.dtoh(&output)
10109}
10110
10111fn apply_macro(values: &mut [f32], macro_scale: f32) {
10112 for value in values.iter_mut() {
10113 *value *= macro_scale;
10114 }
10115}
10116
10117impl TpE4m3HostBounce {
10118 pub fn full_nvfp4(
10120 &self,
10121 matrix: Nvfp4BlockMatrix<'_>,
10122 activations: &[f32],
10123 tokens: usize,
10124 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10125 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
10126 apply_macro(&mut output, matrix.macro_scale);
10127 Ok(output)
10128 }
10129
10130 pub fn column_parallel_nvfp4(
10133 &self,
10134 matrix: Nvfp4BlockMatrix<'_>,
10135 activations: &[f32],
10136 tokens: usize,
10137 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
10138 matrix.validate()?;
10139 validate_activations(activations, tokens, matrix.in_features)?;
10140 let tp = self.ranks.len();
10141 let local_out = matrix.out_features / tp;
10142 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10143 let mut rank_outputs = Vec::with_capacity(tp);
10144 for (rank_index, rank) in self.ranks.iter().enumerate() {
10145 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
10146 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
10147 let row_start = rank_index * local_out;
10148 for token in 0..tokens {
10149 gathered[token * matrix.out_features + row_start
10150 ..token * matrix.out_features + row_start + local_out]
10151 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10152 }
10153 rank_outputs.push(output);
10154 }
10155 apply_macro(&mut gathered, matrix.macro_scale);
10156 Ok(ColumnParallelResult {
10157 gathered,
10158 rank_outputs,
10159 })
10160 }
10161
10162 pub fn row_parallel_nvfp4(
10165 &self,
10166 matrix: Nvfp4BlockMatrix<'_>,
10167 activations: &[f32],
10168 tokens: usize,
10169 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
10170 matrix.validate()?;
10171 validate_activations(activations, tokens, matrix.in_features)?;
10172 let tp = self.ranks.len();
10173 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10174 let mut rank_partials = Vec::with_capacity(tp);
10175 for (rank_index, rank) in self.ranks.iter().enumerate() {
10176 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
10177 let local_activations =
10178 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10179 let shard = Nvfp4BlockMatrix {
10180 codes: &codes,
10181 scales: &scales,
10182 macro_scale: matrix.macro_scale,
10183 out_features: matrix.out_features,
10184 in_features: local_in,
10185 };
10186 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
10187 for (sum, value) in reduced.iter_mut().zip(&partial) {
10188 *sum += *value;
10189 }
10190 rank_partials.push(partial);
10191 }
10192 apply_macro(&mut reduced, matrix.macro_scale);
10193 Ok(RowParallelResult {
10194 reduced,
10195 rank_partials,
10196 })
10197 }
10198
10199 pub fn upload_expert_nvfp4(
10200 &self,
10201 gate: Nvfp4BlockMatrix<'_>,
10202 up: Nvfp4BlockMatrix<'_>,
10203 down: Nvfp4BlockMatrix<'_>,
10204 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
10205 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10206 return Err("NVFP4 TP expert gate/up dimensions differ".into());
10207 }
10208 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10209 return Err(format!(
10210 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
10211 down.out_features, down.in_features, gate.out_features, gate.in_features
10212 )
10213 .into());
10214 }
10215 let tp = self.ranks.len();
10216 let mut gate_ranks = Vec::with_capacity(tp);
10217 let mut up_ranks = Vec::with_capacity(tp);
10218 let mut down_ranks = Vec::with_capacity(tp);
10219 for (rank_index, engine) in self.ranks.iter().enumerate() {
10220 gate_ranks.push(upload_rank_nvfp4(
10221 engine,
10222 nvfp4_column_shard(gate, tp, rank_index)?,
10223 )?);
10224 up_ranks.push(upload_rank_nvfp4(
10225 engine,
10226 nvfp4_column_shard(up, tp, rank_index)?,
10227 )?);
10228 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
10229 down_ranks.push(upload_rank_nvfp4(
10230 engine,
10231 Nvfp4BlockMatrix {
10232 codes: &codes,
10233 scales: &scales,
10234 macro_scale: down.macro_scale,
10235 out_features: down.out_features,
10236 in_features: local_in,
10237 },
10238 )?);
10239 }
10240 Ok(ResidentTpNvfp4Expert {
10241 gate: ResidentNvfp4ColumnParallel {
10242 ranks: gate_ranks,
10243 out_features: gate.out_features,
10244 in_features: gate.in_features,
10245 },
10246 up: ResidentNvfp4ColumnParallel {
10247 ranks: up_ranks,
10248 out_features: up.out_features,
10249 in_features: up.in_features,
10250 },
10251 down: ResidentNvfp4RowParallel {
10252 ranks: down_ranks,
10253 out_features: down.out_features,
10254 in_features: down.in_features,
10255 },
10256 input_width: gate.in_features,
10257 expert_width: gate.out_features,
10258 })
10259 }
10260
10261 fn column_parallel_resident_nvfp4(
10262 &self,
10263 matrix: &ResidentNvfp4ColumnParallel,
10264 activations: &[f32],
10265 tokens: usize,
10266 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10267 validate_activations(activations, tokens, matrix.in_features)?;
10268 let local_out = matrix.out_features / self.ranks.len();
10269 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10270 let mut macro_scale = None;
10271 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10272 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
10273 let row_start = rank_index * local_out;
10274 for token in 0..tokens {
10275 gathered[token * matrix.out_features + row_start
10276 ..token * matrix.out_features + row_start + local_out]
10277 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10278 }
10279 macro_scale = Some(shard.macro_scale);
10280 }
10281 apply_macro(
10282 &mut gathered,
10283 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
10284 );
10285 Ok(gathered)
10286 }
10287
10288 fn row_parallel_resident_nvfp4(
10289 &self,
10290 matrix: &ResidentNvfp4RowParallel,
10291 activations: &[f32],
10292 tokens: usize,
10293 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10294 validate_activations(activations, tokens, matrix.in_features)?;
10295 let tp = self.ranks.len();
10296 let local_in = matrix.in_features / tp;
10297 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10298 let mut macro_scale = None;
10299 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10300 if shard.in_features != local_in {
10301 return Err(format!(
10302 "NVFP4 resident row shard in_features {} != expected {local_in}",
10303 shard.in_features
10304 )
10305 .into());
10306 }
10307 let local_activations =
10308 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10309 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
10310 for (sum, value) in reduced.iter_mut().zip(&partial) {
10311 *sum += *value;
10312 }
10313 macro_scale = Some(shard.macro_scale);
10314 }
10315 apply_macro(
10316 &mut reduced,
10317 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
10318 );
10319 Ok(reduced)
10320 }
10321
10322 pub fn run_expert_nvfp4(
10323 &self,
10324 expert: &ResidentTpNvfp4Expert,
10325 input: &[f32],
10326 tokens: usize,
10327 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10328 validate_activations(input, tokens, expert.input_width)?;
10329 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
10330 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
10331 let activated: Vec<f32> = gate
10332 .iter()
10333 .zip(&up)
10334 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
10335 .collect();
10336 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
10337 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
10338 }
10339
10340 #[allow(clippy::manual_is_multiple_of)] pub fn upload_tensor_parallel_nvfp4(
10343 &self,
10344 gate: Nvfp4ExpertBank<'_>,
10345 up: Nvfp4ExpertBank<'_>,
10346 down: Nvfp4ExpertBank<'_>,
10347 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
10348 gate.validate()?;
10349 up.validate()?;
10350 down.validate()?;
10351 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10352 return Err("NVFP4 TP gate/up/down expert counts differ".into());
10353 }
10354 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10355 return Err("NVFP4 TP gate/up dimensions differ".into());
10356 }
10357 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10358 return Err(format!(
10359 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
10360 down.out_features, down.in_features, gate.out_features, gate.in_features
10361 )
10362 .into());
10363 }
10364 let tp = self.ranks.len();
10365 if gate.out_features % tp != 0 {
10366 return Err(format!(
10367 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
10368 gate.out_features
10369 )
10370 .into());
10371 }
10372 if !down.in_features.is_multiple_of(NVFP4_CANONICAL_ROW_SHARDS)
10373 || !(down.in_features / NVFP4_CANONICAL_ROW_SHARDS).is_multiple_of(64)
10374 {
10375 return Err(format!(
10376 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
10377 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
10378 down.in_features
10379 )
10380 .into());
10381 }
10382 if tp > NVFP4_CANONICAL_ROW_SHARDS {
10383 return Err(format!(
10384 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
10385 ({NVFP4_CANONICAL_ROW_SHARDS})"
10386 )
10387 .into());
10388 }
10389
10390 let ep2 = step_nvfp4_ep2_on() && tp == 2;
10391 let slot_major = ep2 || bank_slot_major_on();
10396 eprintln!(
10404 "[nvfp4-bank] layout={} source={} tp={tp} experts={} in_f={} out_f={}",
10405 if slot_major {
10406 "slot-major"
10407 } else {
10408 "block-nvfp4-v1"
10409 },
10410 if ep2 {
10415 "ep2-always"
10416 } else {
10417 bank_slot_major_source().1
10418 },
10419 gate.expert_count,
10420 gate.in_features,
10421 gate.out_features
10422 );
10423 let mut gate_ranks = Vec::with_capacity(tp);
10424 let mut up_ranks = Vec::with_capacity(tp);
10425 let mut macros_gate_dev = Vec::with_capacity(tp);
10426 let mut macros_up_dev = Vec::with_capacity(tp);
10427 let mut macros_down_dev = Vec::with_capacity(tp);
10428 for (rank_index, engine) in self.ranks.iter().enumerate() {
10429 let _main = engine.gpu.enter_main()?;
10430 let mut gate_host: Vec<u8> = Vec::new();
10436 let mut up_host: Vec<u8> = Vec::new();
10437 let mut owned = 0usize;
10438 for expert in 0..gate.expert_count {
10439 if ep2 {
10440 if expert % 2 != rank_index {
10441 continue;
10442 }
10443 owned += 1;
10444 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10445 gate.expert(expert)?,
10446 slot_major,
10447 ));
10448 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10449 up.expert(expert)?,
10450 slot_major,
10451 ));
10452 } else {
10453 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
10454 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard, slot_major));
10455 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
10456 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard, slot_major));
10457 }
10458 }
10459 let bank_experts = if ep2 { owned } else { gate.expert_count };
10460 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
10461 let up_expert_bytes = up_host.len() / bank_experts.max(1);
10462 let local_out = if ep2 {
10463 gate.out_features
10464 } else {
10465 gate.out_features / tp
10466 };
10467 gate_ranks.push(ResidentNvfp4ColumnBankRank {
10468 bank: engine.htod_bytes(&gate_host)?,
10469 expert_bytes: gate_expert_bytes,
10470 local_out,
10471 in_features: gate.in_features,
10472 row_bytes: nvfp4_row_bytes(gate.in_features),
10473 slot_major,
10474 });
10475 up_ranks.push(ResidentNvfp4ColumnBankRank {
10476 bank: engine.htod_bytes(&up_host)?,
10477 expert_bytes: up_expert_bytes,
10478 local_out,
10479 in_features: up.in_features,
10480 row_bytes: nvfp4_row_bytes(up.in_features),
10481 slot_major,
10482 });
10483 macros_gate_dev.push(engine.htod(gate.macros)?);
10484 macros_up_dev.push(engine.htod(up.macros)?);
10485 macros_down_dev.push(engine.htod(down.macros)?);
10486 }
10487 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
10491 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
10492 let device_rank = shard_index % tp;
10493 let engine = &self.ranks[device_rank];
10494 let _main = engine.gpu.enter_main()?;
10495 let mut down_host: Vec<u8> = Vec::new();
10496 let mut owned = 0usize;
10497 for expert in 0..down.expert_count {
10498 let down_matrix = down.expert(expert)?;
10499 if ep2 {
10500 if expert % 2 != device_rank {
10503 continue;
10504 }
10505 owned += 1;
10506 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix, slot_major));
10507 } else {
10508 let (codes, scales, local_in) =
10509 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
10510 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10511 Nvfp4BlockMatrix {
10512 codes: &codes,
10513 scales: &scales,
10514 macro_scale: down_matrix.macro_scale,
10515 out_features: down_matrix.out_features,
10516 in_features: local_in,
10517 },
10518 slot_major,
10519 ));
10520 }
10521 }
10522 let bank_experts = if ep2 { owned } else { down.expert_count };
10523 let down_expert_bytes = down_host.len() / bank_experts.max(1);
10524 let local_in = if ep2 {
10525 down.in_features
10526 } else {
10527 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
10528 };
10529 down_ranks.push(ResidentNvfp4RowBankRank {
10530 bank: engine.htod_bytes(&down_host)?,
10531 expert_bytes: down_expert_bytes,
10532 device_rank,
10533 out_features: down.out_features,
10534 local_in,
10535 row_bytes: nvfp4_row_bytes(local_in),
10536 slot_major,
10537 });
10538 }
10539 Ok(ResidentNvfp4TensorParallel {
10540 gate: gate_ranks,
10541 up: up_ranks,
10542 down: down_ranks,
10543 macros_gate: gate.macros.to_vec(),
10544 macros_up: up.macros.to_vec(),
10545 macros_down: down.macros.to_vec(),
10546 macros_gate_dev,
10547 macros_up_dev,
10548 macros_down_dev,
10549 expert_count: gate.expert_count,
10550 input_width: gate.in_features,
10551 expert_width: gate.out_features,
10552 device_workspace: std::sync::Mutex::new(None),
10553 prime_tables: std::sync::Mutex::new(Vec::new()),
10554 ep2,
10555 })
10556 }
10557
10558 fn run_full_bank_expert_nvfp4(
10562 &self,
10563 ranks: &[ResidentNvfp4ColumnBankRank],
10564 macros: &[f32],
10565 expert: usize,
10566 input: &[f32],
10567 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10568 let owner = expert & 1;
10569 let slot = expert >> 1;
10570 let bank = ranks
10571 .get(owner)
10572 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
10573 let engine = &self.ranks[owner];
10574 let _main = engine.gpu.enter_main()?;
10575 let activations = engine.htod(input)?;
10576 let output = bank.host_canonical_expert(engine, slot, &activations)?;
10577 let mut out = engine.dtoh(&output)?;
10578 apply_macro(&mut out, macros[expert]);
10579 Ok(out)
10580 }
10581
10582 fn run_full_down_expert_nvfp4(
10585 &self,
10586 shards: &[ResidentNvfp4RowBankRank],
10587 macros: &[f32],
10588 expert: usize,
10589 input: &[f32],
10590 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10591 let owner = expert & 1;
10592 let slot = expert >> 1;
10593 let shard = shards
10594 .get(owner)
10595 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
10596 let engine = &self.ranks[owner];
10597 let _main = engine.gpu.enter_main()?;
10598 let activations = engine.htod(input)?;
10599 let output = shard.host_canonical_expert(engine, slot, &activations)?;
10600 let mut out = engine.dtoh(&output)?;
10601 apply_macro(&mut out, macros[expert]);
10602 Ok(out)
10603 }
10604
10605 fn run_column_bank_expert_nvfp4(
10606 &self,
10607 ranks: &[ResidentNvfp4ColumnBankRank],
10608 macros: &[f32],
10609 expert: usize,
10610 input: &[f32],
10611 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10612 let local_out = ranks
10613 .first()
10614 .ok_or("NVFP4 TP column bank has no ranks")?
10615 .local_out;
10616 let mut gathered = vec![0.0f32; local_out * ranks.len()];
10617 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
10618 let _main = engine.gpu.enter_main()?;
10619 let activations = engine.htod(input)?;
10620 let output = bank.host_canonical_expert(engine, expert, &activations)?;
10621 let output = engine.dtoh(&output)?;
10622 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
10623 }
10624 apply_macro(&mut gathered, macros[expert]);
10625 Ok(gathered)
10626 }
10627
10628 fn run_row_bank_expert_nvfp4(
10632 &self,
10633 shards: &[ResidentNvfp4RowBankRank],
10634 macros: &[f32],
10635 expert: usize,
10636 input: &[f32],
10637 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10638 let out_features = shards
10639 .first()
10640 .ok_or("NVFP4 TP row bank has no canonical shards")?
10641 .out_features;
10642 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
10643 let mut reduced = vec![0.0f32; out_features];
10644 for (shard_index, shard) in shards.iter().enumerate() {
10645 let engine = self
10646 .ranks
10647 .get(shard.device_rank)
10648 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
10649 let _main = engine.gpu.enter_main()?;
10650 let local_activations =
10651 activation_shard(input, 1, in_features, shards.len(), shard_index);
10652 let activations = engine.htod(&local_activations)?;
10653 let output = shard.host_canonical_expert(engine, expert, &activations)?;
10654 let partial = engine.dtoh(&output)?;
10655 for (sum, value) in reduced.iter_mut().zip(&partial) {
10656 *sum += *value;
10657 }
10658 }
10659 apply_macro(&mut reduced, macros[expert]);
10660 Ok(reduced)
10661 }
10662
10663 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel_nvfp4(
10668 &self,
10669 gate: Nvfp4ExpertBank<'_>,
10670 up: Nvfp4ExpertBank<'_>,
10671 down: Nvfp4ExpertBank<'_>,
10672 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10673 gate.validate()?;
10674 up.validate()?;
10675 down.validate()?;
10676 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10677 return Err("NVFP4 EP gate/up/down expert counts differ".into());
10678 }
10679 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10680 return Err("NVFP4 EP gate/up dimensions differ".into());
10681 }
10682 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10683 return Err(format!(
10684 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
10685 down.out_features, down.in_features, gate.out_features, gate.in_features
10686 )
10687 .into());
10688 }
10689 let world = self.ranks.len();
10690 if gate.expert_count % world != 0 {
10691 return Err(format!(
10692 "NVFP4 EP expert count {} is not divisible by {world} ranks",
10693 gate.expert_count
10694 )
10695 .into());
10696 }
10697 let experts_per_rank = gate.expert_count / world;
10698 let mut ranks = Vec::with_capacity(world);
10699 for (rank_index, engine) in self.ranks.iter().enumerate() {
10700 let _main = engine.gpu.enter_main()?;
10701 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10702 let mut gate_host = Vec::new();
10703 let mut up_host = Vec::new();
10704 let mut down_host = Vec::new();
10705 for expert in expert_range.clone() {
10706 gate_host.extend_from_slice(&nvfp4_repack_matrix(gate.expert(expert)?));
10707 up_host.extend_from_slice(&nvfp4_repack_matrix(up.expert(expert)?));
10708 down_host.extend_from_slice(&nvfp4_repack_matrix(down.expert(expert)?));
10709 }
10710 let gate_expert_bytes = gate_host.len() / experts_per_rank;
10711 let up_expert_bytes = up_host.len() / experts_per_rank;
10712 if gate_expert_bytes != up_expert_bytes {
10713 return Err("NVFP4 EP gate/up packed expert bytes differ".into());
10714 }
10715 let down_expert_bytes = down_host.len() / experts_per_rank;
10716 ranks.push(ResidentNvfp4EpRank {
10717 gate: engine.htod_bytes(&gate_host)?,
10718 up: engine.htod_bytes(&up_host)?,
10719 down: engine.htod_bytes(&down_host)?,
10720 gate_expert_bytes,
10721 down_expert_bytes,
10722 macros_gate: engine.htod(&gate.macros[expert_range.clone()])?,
10723 macros_up: engine.htod(&up.macros[expert_range.clone()])?,
10724 macros_down: engine.htod(&down.macros[expert_range.clone()])?,
10725 expert_range,
10726 });
10727 }
10728 Ok(ResidentNvfp4ExpertParallel {
10729 ranks,
10730 macros_gate: gate.macros.to_vec(),
10731 macros_up: up.macros.to_vec(),
10732 macros_down: down.macros.to_vec(),
10733 expert_count: gate.expert_count,
10734 input_width: gate.in_features,
10735 expert_width: gate.out_features,
10736 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
10737 down_row_bytes: nvfp4_row_bytes(down.in_features),
10738 device_workspace: std::sync::Mutex::new(None),
10739 })
10740 }
10741
10742 pub fn upload_expert_parallel_nvfp4_normalized(
10748 &self,
10749 gate: &crate::model::HostExps,
10750 up: &crate::model::HostExps,
10751 down: &crate::model::HostExps,
10752 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10753 for (label, bank) in [("gate", gate), ("up", up), ("down", down)] {
10754 if bank.qtype != crate::QT_NVFP4 || !bank.is_uniform_layout() {
10755 return Err(format!(
10756 "NVFP4 EP normalized {label} bank requires one uniform NVFP4 layout, \
10757 got qtype={} uniform={}",
10758 bank.qtype,
10759 bank.is_uniform_layout()
10760 )
10761 .into());
10762 }
10763 if bank.n_expert == 0
10764 || bank.expert_stride != bank.out_f * bank.row_bytes
10765 || (0..bank.n_expert)
10766 .any(|expert| bank.expert_bytes(expert).len() != bank.expert_stride)
10767 {
10768 return Err(format!("NVFP4 EP normalized {label} bank geometry is invalid").into());
10769 }
10770 }
10771 if gate.n_expert != up.n_expert || gate.n_expert != down.n_expert {
10772 return Err("NVFP4 EP normalized gate/up/down expert counts differ".into());
10773 }
10774 if gate.in_f != up.in_f || gate.out_f != up.out_f {
10775 return Err("NVFP4 EP normalized gate/up dimensions differ".into());
10776 }
10777 if down.in_f != gate.out_f || down.out_f != gate.in_f {
10778 return Err(format!(
10779 "NVFP4 EP normalized down {}x{} does not invert gate/up {}x{}",
10780 down.out_f, down.in_f, gate.out_f, gate.in_f
10781 )
10782 .into());
10783 }
10784 let macros = |bank: &crate::model::HostExps| -> Result<Vec<f32>, String> {
10785 let values = bank
10786 .macros
10787 .clone()
10788 .unwrap_or_else(|| vec![1.0; bank.n_expert]);
10789 if values.len() != bank.n_expert
10790 || !values.iter().all(|value| value.is_finite() && *value > 0.0)
10791 {
10792 return Err("NVFP4 EP normalized macro row is not finite-positive".to_string());
10793 }
10794 Ok(values)
10795 };
10796 let macros_gate = macros(gate)?;
10797 let macros_up = macros(up)?;
10798 let macros_down = macros(down)?;
10799 let world = self.ranks.len();
10800 if !gate.n_expert.is_multiple_of(world) {
10801 return Err(format!(
10802 "NVFP4 EP normalized expert count {} is not divisible by {world} ranks",
10803 gate.n_expert
10804 )
10805 .into());
10806 }
10807 let experts_per_rank = gate.n_expert / world;
10808 let mut ranks = Vec::with_capacity(world);
10809 for (rank_index, engine) in self.ranks.iter().enumerate() {
10810 let _main = engine.gpu.enter_main()?;
10811 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10812 let mut gate_host = Vec::with_capacity(experts_per_rank * gate.expert_stride);
10813 let mut up_host = Vec::with_capacity(experts_per_rank * up.expert_stride);
10814 let mut down_host = Vec::with_capacity(experts_per_rank * down.expert_stride);
10815 for expert in expert_range.clone() {
10816 gate_host.extend_from_slice(gate.expert_bytes(expert));
10817 up_host.extend_from_slice(up.expert_bytes(expert));
10818 down_host.extend_from_slice(down.expert_bytes(expert));
10819 }
10820 ranks.push(ResidentNvfp4EpRank {
10821 gate: engine.htod_bytes(&gate_host)?,
10822 up: engine.htod_bytes(&up_host)?,
10823 down: engine.htod_bytes(&down_host)?,
10824 gate_expert_bytes: gate.expert_stride,
10825 down_expert_bytes: down.expert_stride,
10826 macros_gate: engine.htod(¯os_gate[expert_range.clone()])?,
10827 macros_up: engine.htod(¯os_up[expert_range.clone()])?,
10828 macros_down: engine.htod(¯os_down[expert_range.clone()])?,
10829 expert_range,
10830 });
10831 }
10832 Ok(ResidentNvfp4ExpertParallel {
10833 ranks,
10834 macros_gate,
10835 macros_up,
10836 macros_down,
10837 expert_count: gate.n_expert,
10838 input_width: gate.in_f,
10839 expert_width: gate.out_f,
10840 gate_row_bytes: gate.row_bytes,
10841 down_row_bytes: down.row_bytes,
10842 device_workspace: std::sync::Mutex::new(None),
10843 })
10844 }
10845
10846 #[allow(clippy::too_many_arguments)]
10852 pub fn run_routed_experts_nvfp4(
10853 &self,
10854 experts: &ResidentNvfp4ExpertParallel,
10855 input: &[f32],
10856 tokens: usize,
10857 selected: &[usize],
10858 route_weights: &[f32],
10859 experts_per_token: usize,
10860 activation_limit: Option<f32>,
10861 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10862 validate_activations(input, tokens, experts.input_width)?;
10863 let pairs = tokens
10864 .checked_mul(experts_per_token)
10865 .ok_or("NVFP4 EP route count overflow")?;
10866 if selected.len() != pairs || route_weights.len() != pairs {
10867 return Err(format!(
10868 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
10869 {experts_per_token} ({pairs})",
10870 selected.len(),
10871 route_weights.len(),
10872 )
10873 .into());
10874 }
10875 if !route_weights.iter().all(|weight| weight.is_finite()) {
10876 return Err("NVFP4 EP route weights contain a non-finite value".into());
10877 }
10878 let experts_per_rank = experts.expert_count / experts.ranks.len();
10879 let mut output = vec![0.0f32; tokens * experts.input_width];
10880 for token in 0..tokens {
10881 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
10882 for slot in 0..experts_per_token {
10883 let pair = token * experts_per_token + slot;
10884 let expert = selected[pair];
10885 if expert >= experts.expert_count {
10886 return Err(format!(
10887 "NVFP4 EP selected expert {expert} outside 0..{}",
10888 experts.expert_count
10889 )
10890 .into());
10891 }
10892 let owner = expert / experts_per_rank;
10893 let local = expert - owner * experts_per_rank;
10894 let rank = &experts.ranks[owner];
10895 let engine = &self.ranks[owner];
10896 let _main = engine.gpu.enter_main()?;
10897 let device_input = engine.htod(input_row)?;
10898 let gate_out = engine.qmatvec_nvfp4_fast(
10899 &rank.gate.slice(
10900 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
10901 ),
10902 &device_input,
10903 1,
10904 experts.input_width,
10905 experts.expert_width,
10906 experts.gate_row_bytes,
10907 )?;
10908 let up_out = engine.qmatvec_nvfp4_fast(
10909 &rank.up.slice(
10910 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
10911 ),
10912 &device_input,
10913 1,
10914 experts.input_width,
10915 experts.expert_width,
10916 experts.gate_row_bytes,
10917 )?;
10918 let mut gate_host = engine.dtoh(&gate_out)?;
10919 let mut up_host = engine.dtoh(&up_out)?;
10920 apply_macro(&mut gate_host, experts.macros_gate[expert]);
10921 apply_macro(&mut up_host, experts.macros_up[expert]);
10922 let activated: Vec<f32> = gate_host
10923 .iter()
10924 .zip(&up_host)
10925 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
10926 .collect();
10927 let device_activated = engine.htod(&activated)?;
10928 let down_out = engine.qmatvec_nvfp4_fast(
10929 &rank.down.slice(
10930 local * rank.down_expert_bytes..(local + 1) * rank.down_expert_bytes,
10931 ),
10932 &device_activated,
10933 1,
10934 experts.expert_width,
10935 experts.input_width,
10936 experts.down_row_bytes,
10937 )?;
10938 let mut down_host = engine.dtoh(&down_out)?;
10939 apply_macro(&mut down_host, experts.macros_down[expert]);
10940 let weight = route_weights[pair];
10941 for (sum, value) in output
10942 [token * experts.input_width..(token + 1) * experts.input_width]
10943 .iter_mut()
10944 .zip(down_host)
10945 {
10946 *sum += weight * value;
10947 }
10948 }
10949 }
10950 Ok(output)
10951 }
10952
10953 #[allow(clippy::too_many_arguments)]
10962 pub fn run_routed_experts_nvfp4_w4a16_device_io(
10963 &self,
10964 experts: &ResidentNvfp4ExpertParallel,
10965 e: &Engine,
10966 input_dev: &crate::CudaSlice<f32>,
10967 tokens: usize,
10968 selected: &[usize],
10969 route_weights: &[f32],
10970 experts_per_token: usize,
10971 activation_limit: Option<f32>,
10972 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10973 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10977 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10978 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10979 let started = timing.then(std::time::Instant::now);
10980 if !self.native_p2p {
10981 return Err("W4A16 NVFP4 device EP requires native P2P".into());
10982 }
10983 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
10984 return Err(format!(
10985 "W4A16 NVFP4 device EP root device {:?} != model engine device {}",
10986 self.devices.first(),
10987 e.ctx().ordinal()
10988 )
10989 .into());
10990 }
10991 let active_input_values =
10995 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
10996 let pairs = tokens
10997 .checked_mul(experts_per_token)
10998 .ok_or("W4A16 NVFP4 device EP route count overflow")?;
10999 if selected.len() != pairs || route_weights.len() != pairs {
11000 return Err(format!(
11001 "W4A16 NVFP4 device EP routes selected={} weights={} != tokens {tokens} x \
11002 experts/token {experts_per_token} ({pairs})",
11003 selected.len(),
11004 route_weights.len(),
11005 )
11006 .into());
11007 }
11008 if !route_weights.iter().all(|weight| weight.is_finite()) {
11009 return Err("W4A16 NVFP4 device EP route weights contain a non-finite value".into());
11010 }
11011 let world = self.ranks.len();
11012 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11013 return Err(format!(
11014 "W4A16 NVFP4 device EP runtime ranks {world} != bank ranks {}",
11015 experts.ranks.len()
11016 )
11017 .into());
11018 }
11019 let owner_routes = partition_expert_owner_routes(
11020 experts.expert_count,
11021 world,
11022 tokens,
11023 experts_per_token,
11024 selected,
11025 )?;
11026
11027 let mut workspace_guard = experts
11028 .device_workspace
11029 .lock()
11030 .map_err(|_| "W4A16 NVFP4 device EP workspace lock is poisoned")?;
11031 if workspace_guard.is_none() {
11032 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11033 let capacity_pairs = capacity_tokens * experts_per_token;
11034 let mut input = Vec::with_capacity(world);
11035 let mut input_bf16 = Vec::with_capacity(world);
11036 let mut input_q8 = Vec::with_capacity(world);
11037 let mut input_q8_scales = Vec::with_capacity(world);
11038 let mut sel = Vec::with_capacity(world);
11039 let mut token_rows = Vec::with_capacity(world);
11040 let mut global_pairs = Vec::with_capacity(world);
11041 let mut route_w = Vec::with_capacity(world);
11042 let mut gate_out = Vec::with_capacity(world);
11043 let mut up_out = Vec::with_capacity(world);
11044 let mut activation_bf16 = Vec::with_capacity(world);
11045 let mut activation_q8 = Vec::with_capacity(world);
11046 let mut activation_q8_scales = Vec::with_capacity(world);
11047 let mut ev_rank = Vec::with_capacity(world);
11048 for engine in &self.ranks {
11049 let _main = engine.gpu.enter_main()?;
11050 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11051 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11052 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11053 input_q8_scales
11054 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11055 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11056 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11057 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11058 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11059 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11060 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11061 activation_bf16
11062 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11063 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11064 activation_q8_scales
11065 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11066 ev_rank.push(engine.ctx().new_event(None)?);
11067 }
11068 let _main = e.gpu.enter_main()?;
11069 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11070 let slot_rows_raw = {
11071 use cudarc::driver::DevicePtr;
11072 let stream = e.stream();
11073 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11074 pointer
11075 };
11076 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11077 input,
11078 input_bf16,
11079 input_q8,
11080 input_q8_scales,
11081 sel,
11082 token_rows,
11083 global_pairs,
11084 route_w,
11085 gate_out,
11086 up_out,
11087 activation_bf16,
11088 activation_q8,
11089 activation_q8_scales,
11090 slot_rows,
11091 slot_rows_raw,
11092 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11093 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11094 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11095 graph_routes: None,
11096 graphs: std::iter::repeat_with(|| None)
11097 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11098 .collect(),
11099 ev_entry: e.ctx().new_event(None)?,
11100 ev_entry_device: e.ctx().ordinal(),
11101 ev_rank,
11102 phase_events: None,
11103 capacity_tokens,
11104 experts_per_token,
11105 });
11106 }
11107 let workspace = workspace_guard
11108 .as_mut()
11109 .expect("W4A16 NVFP4 device EP workspace initialized above");
11110 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11111 return Err(format!(
11112 "W4A16 NVFP4 device EP workspace tokens={} experts/token={} cannot serve \
11113 tokens={tokens} experts/token={experts_per_token}",
11114 workspace.capacity_tokens, workspace.experts_per_token,
11115 )
11116 .into());
11117 }
11118 if workspace.ev_entry_device != e.ctx().ordinal() {
11119 return Err("W4A16 NVFP4 device EP model engine changed".into());
11120 }
11121
11122 {
11123 let _main = e.gpu.enter_main()?;
11124 let mut destination = workspace.route_weights.slice_mut(0..pairs);
11125 e.stream()
11126 .memcpy_htod(&route_weights[..pairs], &mut destination)?;
11127 workspace.ev_entry.record(&e.stream())?;
11128 }
11129 for (rank_index, engine) in self.ranks.iter().enumerate() {
11130 let _main = engine.gpu.enter_main()?;
11131 engine.stream().wait(&workspace.ev_entry)?;
11132 {
11133 let mut destination = workspace.input[rank_index].slice_mut(0..active_input_values);
11134 engine
11135 .stream()
11136 .memcpy_dtod(&input_dev.slice(0..active_input_values), &mut destination)?;
11137 }
11138 engine.f32_to_bf16_into(
11139 &workspace.input[rank_index],
11140 &mut workspace.input_bf16[rank_index],
11141 tokens * experts.input_width,
11142 )?;
11143 let owner = &owner_routes[rank_index];
11144 debug_assert_eq!(owner.rank, rank_index);
11145 let local_count = owner.selected.len();
11146 if local_count > 0 {
11147 let local_selected = owner
11148 .selected
11149 .iter()
11150 .map(|&expert| expert as i32)
11151 .collect::<Vec<_>>();
11152 let local_token_rows = owner
11153 .token_rows
11154 .iter()
11155 .map(|&token| token as i32)
11156 .collect::<Vec<_>>();
11157 let local_global_pairs = owner
11158 .global_pairs
11159 .iter()
11160 .map(|&pair| pair as i32)
11161 .collect::<Vec<_>>();
11162 {
11163 let mut destination = workspace.sel[rank_index].slice_mut(0..local_count);
11164 engine
11165 .stream()
11166 .memcpy_htod(&local_selected, &mut destination)?;
11167 }
11168 {
11169 let mut destination =
11170 workspace.token_rows[rank_index].slice_mut(0..local_count);
11171 engine
11172 .stream()
11173 .memcpy_htod(&local_token_rows, &mut destination)?;
11174 }
11175 {
11176 let mut destination =
11177 workspace.global_pairs[rank_index].slice_mut(0..local_count);
11178 engine
11179 .stream()
11180 .memcpy_htod(&local_global_pairs, &mut destination)?;
11181 }
11182 let rank = &experts.ranks[rank_index];
11183 engine.qmatvec_nvfp4_bf16_sel_dual_rows_into(
11184 &rank.gate,
11185 &rank.up,
11186 &workspace.sel[rank_index],
11187 &workspace.token_rows[rank_index],
11188 &workspace.input_bf16[rank_index],
11189 &mut workspace.gate_out[rank_index],
11190 &mut workspace.up_out[rank_index],
11191 local_count,
11192 experts.input_width,
11193 experts.expert_width,
11194 experts.gate_row_bytes,
11195 rank.gate_expert_bytes,
11196 tokens,
11197 )?;
11198 engine.silu_mul_scaled_host_expf_bf16_sel_into(
11199 &workspace.gate_out[rank_index],
11200 &workspace.up_out[rank_index],
11201 &rank.macros_gate,
11202 &rank.macros_up,
11203 &workspace.sel[rank_index],
11204 activation_limit,
11205 &mut workspace.activation_bf16[rank_index],
11206 experts.expert_width,
11207 local_count,
11208 )?;
11209 engine.qmatvec_nvfp4_bf16_sel_down_rows_raw(
11210 &rank.down,
11211 &workspace.sel[rank_index],
11212 &workspace.global_pairs[rank_index],
11213 &workspace.activation_bf16[rank_index],
11214 &rank.macros_down,
11215 workspace.slot_rows_raw,
11216 local_count,
11217 experts.expert_width,
11218 experts.input_width,
11219 experts.down_row_bytes,
11220 rank.down_expert_bytes,
11221 pairs,
11222 )?;
11223 }
11224 workspace.ev_rank[rank_index].record(&engine.stream())?;
11225 }
11226
11227 let output = {
11228 let _main = e.gpu.enter_main()?;
11229 for event in &workspace.ev_rank {
11230 e.stream().wait(event)?;
11231 }
11232 let mut output = e.uninit(tokens * experts.input_width)?;
11233 e.axpy_rows_seq_tokens_into(
11234 &workspace.slot_rows,
11235 &workspace.route_weights,
11236 &mut output,
11237 experts.input_width,
11238 experts_per_token,
11239 tokens,
11240 )?;
11241 output
11242 };
11243 if let Some(started) = started {
11244 use std::sync::atomic::Ordering;
11245 e.stream().synchronize()?;
11246 let elapsed = started.elapsed().as_nanos() as u64;
11247 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11248 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11249 if calls.is_multiple_of(430) {
11250 eprintln!(
11251 "[nvfp4-ep-w4a16-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11252 ns as f64 / 1.0e6,
11253 ns as f64 / calls as f64 / 1.0e3,
11254 );
11255 }
11256 }
11257 Ok(output)
11258 }
11259
11260 #[allow(clippy::too_many_arguments)]
11266 pub fn run_routed_experts_nvfp4_w4a16_device_routed(
11267 &self,
11268 experts: &ResidentNvfp4ExpertParallel,
11269 e: &Engine,
11270 input_dev: &crate::CudaSlice<f32>,
11271 selected_dev: &crate::CudaSlice<i32>,
11272 route_weights_dev: &crate::CudaSlice<f32>,
11273 tokens: usize,
11274 experts_per_token: usize,
11275 activation_limit: Option<f32>,
11276 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11277 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11278 experts,
11279 e,
11280 input_dev,
11281 selected_dev,
11282 route_weights_dev,
11283 tokens,
11284 experts_per_token,
11285 activation_limit,
11286 None,
11287 )
11288 }
11289
11290 #[allow(clippy::too_many_arguments)]
11294 pub fn run_routed_experts_nvfp4_w4a16_device_routed_prejoin(
11295 &self,
11296 experts: &ResidentNvfp4ExpertParallel,
11297 e: &Engine,
11298 input_dev: &crate::CudaSlice<f32>,
11299 selected_dev: &crate::CudaSlice<i32>,
11300 route_weights_dev: &crate::CudaSlice<f32>,
11301 tokens: usize,
11302 experts_per_token: usize,
11303 activation_limit: Option<f32>,
11304 mut pre_join: impl FnMut() -> Result<(), Box<dyn std::error::Error>>,
11305 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11306 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11307 experts,
11308 e,
11309 input_dev,
11310 selected_dev,
11311 route_weights_dev,
11312 tokens,
11313 experts_per_token,
11314 activation_limit,
11315 Some(&mut pre_join),
11316 )
11317 }
11318
11319 #[allow(clippy::too_many_arguments)]
11320 fn run_routed_experts_nvfp4_w4a16_device_routed_inner(
11321 &self,
11322 experts: &ResidentNvfp4ExpertParallel,
11323 e: &Engine,
11324 input_dev: &crate::CudaSlice<f32>,
11325 selected_dev: &crate::CudaSlice<i32>,
11326 route_weights_dev: &crate::CudaSlice<f32>,
11327 tokens: usize,
11328 experts_per_token: usize,
11329 activation_limit: Option<f32>,
11330 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11331 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11332 if !self.native_p2p {
11333 return Err("W4A16 device-routed EP requires native P2P".into());
11334 }
11335 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11336 return Err(format!(
11337 "W4A16 device-routed EP root device {:?} != model engine device {}",
11338 self.devices.first(),
11339 e.ctx().ordinal()
11340 )
11341 .into());
11342 }
11343 let active_input_values =
11344 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11345 let pairs = tokens
11346 .checked_mul(experts_per_token)
11347 .ok_or("W4A16 device-routed EP route count overflow")?;
11348 if selected_dev.len() < pairs || route_weights_dev.len() < pairs {
11349 return Err(format!(
11350 "W4A16 device-routed EP metadata selected={} weights={} < pairs={pairs}",
11351 selected_dev.len(),
11352 route_weights_dev.len(),
11353 )
11354 .into());
11355 }
11356 let world = self.ranks.len();
11357 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11358 return Err(format!(
11359 "W4A16 device-routed EP runtime ranks {world} != bank ranks {}",
11360 experts.ranks.len()
11361 )
11362 .into());
11363 }
11364
11365 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11366 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11367 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11368 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11369 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11370 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11371 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11372 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11373 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11374 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11375 let started = timing.then(std::time::Instant::now);
11376 let graph_enabled = parallel_ep_graph_enabled()?;
11377 let pair_down_enabled = parallel_ep_pair_down_enabled()?;
11378
11379 let mut workspace_guard = experts
11380 .device_workspace
11381 .lock()
11382 .map_err(|_| "W4A16 device-routed EP workspace lock is poisoned")?;
11383 if workspace_guard.is_none() {
11384 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11385 let capacity_pairs = capacity_tokens * experts_per_token;
11386 let mut input = Vec::with_capacity(world);
11387 let mut input_bf16 = Vec::with_capacity(world);
11388 let mut input_q8 = Vec::with_capacity(world);
11389 let mut input_q8_scales = Vec::with_capacity(world);
11390 let mut sel = Vec::with_capacity(world);
11391 let mut token_rows = Vec::with_capacity(world);
11392 let mut global_pairs = Vec::with_capacity(world);
11393 let mut route_w = Vec::with_capacity(world);
11394 let mut gate_out = Vec::with_capacity(world);
11395 let mut up_out = Vec::with_capacity(world);
11396 let mut activation_bf16 = Vec::with_capacity(world);
11397 let mut activation_q8 = Vec::with_capacity(world);
11398 let mut activation_q8_scales = Vec::with_capacity(world);
11399 let mut ev_rank = Vec::with_capacity(world);
11400 let mut phase_head = Vec::with_capacity(world);
11401 let mut phase_copy_done = Vec::with_capacity(world);
11402 let mut phase_gate_up_done = Vec::with_capacity(world);
11403 let mut phase_activation_done = Vec::with_capacity(world);
11404 let mut phase_down_done = Vec::with_capacity(world);
11405 for engine in &self.ranks {
11406 let _main = engine.gpu.enter_main()?;
11407 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11408 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11409 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11410 input_q8_scales
11411 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11412 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11413 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11414 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11415 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11416 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11417 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11418 activation_bf16
11419 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11420 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11421 activation_q8_scales
11422 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11423 ev_rank.push(engine.ctx().new_event(None)?);
11424 if timing {
11425 phase_head.push(
11426 engine.ctx().new_event(Some(
11427 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11428 ))?,
11429 );
11430 phase_copy_done.push(
11431 engine.ctx().new_event(Some(
11432 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11433 ))?,
11434 );
11435 phase_gate_up_done.push(
11436 engine.ctx().new_event(Some(
11437 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11438 ))?,
11439 );
11440 phase_activation_done.push(
11441 engine.ctx().new_event(Some(
11442 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11443 ))?,
11444 );
11445 phase_down_done.push(
11446 engine.ctx().new_event(Some(
11447 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11448 ))?,
11449 );
11450 }
11451 }
11452 let _main = e.gpu.enter_main()?;
11453 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11454 let slot_rows_raw = {
11455 use cudarc::driver::DevicePtr;
11456 let stream = e.stream();
11457 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11458 pointer
11459 };
11460 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11461 input,
11462 input_bf16,
11463 input_q8,
11464 input_q8_scales,
11465 sel,
11466 token_rows,
11467 global_pairs,
11468 route_w,
11469 gate_out,
11470 up_out,
11471 activation_bf16,
11472 activation_q8,
11473 activation_q8_scales,
11474 slot_rows,
11475 slot_rows_raw,
11476 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11477 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11478 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11479 graph_routes: None,
11480 graphs: std::iter::repeat_with(|| None)
11481 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11482 .collect(),
11483 ev_entry: e.ctx().new_event(None)?,
11484 ev_entry_device: e.ctx().ordinal(),
11485 ev_rank,
11486 phase_events: timing.then_some(Nvfp4EpPhaseEvents {
11487 head: phase_head,
11488 copy_done: phase_copy_done,
11489 gate_up_done: phase_gate_up_done,
11490 activation_done: phase_activation_done,
11491 down_done: phase_down_done,
11492 }),
11493 capacity_tokens,
11494 experts_per_token,
11495 });
11496 }
11497 let workspace = workspace_guard
11498 .as_mut()
11499 .expect("W4A16 device-routed EP workspace initialized above");
11500 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11501 return Err(format!(
11502 "W4A16 device-routed EP workspace tokens={} experts/token={} cannot serve \
11503 tokens={tokens} experts/token={experts_per_token}",
11504 workspace.capacity_tokens, workspace.experts_per_token,
11505 )
11506 .into());
11507 }
11508
11509 if tokens <= NVFP4_EP_Q8_BATCH_CAP && parallel_ep_q8_act_enabled()? {
11510 if graph_enabled {
11511 return Err("MEMRA_PARALLEL_EP_GRAPH=1 is exact W4A16-only; disable \
11512 MEMRA_PARALLEL_EP_Q8_ACT or the graph door"
11513 .into());
11514 }
11515 return self.run_routed_experts_nvfp4_w4a8_device_routed(
11516 experts,
11517 e,
11518 input_dev,
11519 selected_dev,
11520 route_weights_dev,
11521 workspace,
11522 tokens,
11523 experts_per_token,
11524 activation_limit,
11525 pre_join,
11526 );
11527 }
11528
11529 if graph_enabled && !timing && pre_join.is_none() && tokens <= NVFP4_EP_GRAPH_BATCH_CAP {
11530 use cudarc::driver::DevicePtr;
11531 let route_ptrs = {
11532 let stream = e.stream();
11533 let (sel_ptr, _sel_guard) = selected_dev.device_ptr(&stream);
11534 let (weight_ptr, _weight_guard) = route_weights_dev.device_ptr(&stream);
11535 (sel_ptr, weight_ptr)
11536 };
11537 if let Some(graph_exec) = workspace.graphs[tokens].as_ref().map(|graph| graph.exec) {
11538 if workspace.graph_routes != Some(route_ptrs) {
11539 return Err(format!(
11540 "W4A16 EP graph route buffers moved: built={:?} current={route_ptrs:?}",
11541 workspace.graph_routes,
11542 )
11543 .into());
11544 }
11545 let _main = e.gpu.enter_main()?;
11546 e.stream().memcpy_dtod(
11547 &input_dev.slice(0..active_input_values),
11548 &mut workspace.graph_input.slice_mut(0..active_input_values),
11549 )?;
11550 e.memset_zeros_view(
11551 &mut workspace
11552 .slot_rows
11553 .slice_mut(0..pairs * experts.input_width),
11554 )?;
11555 unsafe {
11556 let result = cudarc::driver::sys::cuGraphLaunch(
11557 graph_exec,
11558 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11559 );
11560 if result != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11561 return Err(format!("W4A16 EP graph launch: {result:?}").into());
11562 }
11563 }
11564 let mut output = e.uninit(active_input_values)?;
11565 e.stream().memcpy_dtod(
11566 &workspace.graph_output.slice(0..active_input_values),
11567 &mut output.slice_mut(0..active_input_values),
11568 )?;
11569 return Ok(output);
11570 }
11571 }
11572
11573 {
11574 let _main = e.gpu.enter_main()?;
11575 e.memset_zeros_view(
11576 &mut workspace
11577 .slot_rows
11578 .slice_mut(0..pairs * experts.input_width),
11579 )?;
11580 workspace.ev_entry.record(&e.stream())?;
11581 }
11582
11583 for (rank_index, engine) in self.ranks.iter().enumerate() {
11584 let _main = engine.gpu.enter_main()?;
11585 if let Some(events) = workspace.phase_events.as_ref() {
11586 events.head[rank_index].record(&engine.stream())?;
11587 }
11588 engine.stream().wait(&workspace.ev_entry)?;
11589 let Nvfp4EpDeviceWorkspace {
11590 input_bf16,
11591 sel,
11592 route_w,
11593 ..
11594 } = &mut *workspace;
11595 engine.nvfp4_ep_stage_inputs(
11596 input_dev,
11597 selected_dev,
11598 route_weights_dev,
11599 &mut input_bf16[rank_index],
11600 &mut sel[rank_index],
11601 &mut route_w[rank_index],
11602 active_input_values,
11603 pairs,
11604 false,
11605 )?;
11606 if let Some(events) = workspace.phase_events.as_ref() {
11607 events.copy_done[rank_index].record(&engine.stream())?;
11608 }
11609 let rank = &experts.ranks[rank_index];
11610 let owner_start = rank.expert_range.start;
11611 let owner_end = rank.expert_range.end;
11612 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
11613 &rank.gate,
11614 &rank.up,
11615 &workspace.sel[rank_index],
11616 &workspace.input_bf16[rank_index],
11617 &mut workspace.gate_out[rank_index],
11618 &mut workspace.up_out[rank_index],
11619 pairs,
11620 experts_per_token,
11621 experts.input_width,
11622 experts.expert_width,
11623 owner_start,
11624 owner_end,
11625 experts.gate_row_bytes,
11626 rank.gate_expert_bytes,
11627 )?;
11628 if let Some(events) = workspace.phase_events.as_ref() {
11629 events.gate_up_done[rank_index].record(&engine.stream())?;
11630 }
11631 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
11632 &workspace.gate_out[rank_index],
11633 &workspace.up_out[rank_index],
11634 &rank.macros_gate,
11635 &rank.macros_up,
11636 &workspace.sel[rank_index],
11637 owner_start,
11638 owner_end,
11639 activation_limit,
11640 &mut workspace.activation_bf16[rank_index],
11641 experts.expert_width,
11642 pairs,
11643 )?;
11644 if let Some(events) = workspace.phase_events.as_ref() {
11645 events.activation_done[rank_index].record(&engine.stream())?;
11646 }
11647 if tokens > 1 && pair_down_enabled {
11648 engine.qmatvec_nvfp4_bf16_ep_down_pairs_raw(
11649 &rank.down,
11650 &workspace.sel[rank_index],
11651 &workspace.activation_bf16[rank_index],
11652 &rank.macros_down,
11653 workspace.slot_rows_raw,
11654 pairs,
11655 experts.expert_width,
11656 experts.input_width,
11657 owner_start,
11658 owner_end,
11659 experts.down_row_bytes,
11660 rank.down_expert_bytes,
11661 )?;
11662 } else {
11663 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
11664 &rank.down,
11665 &workspace.sel[rank_index],
11666 &workspace.activation_bf16[rank_index],
11667 &rank.macros_down,
11668 workspace.slot_rows_raw,
11669 pairs,
11670 experts.expert_width,
11671 experts.input_width,
11672 owner_start,
11673 owner_end,
11674 experts.down_row_bytes,
11675 rank.down_expert_bytes,
11676 )?;
11677 }
11678 if let Some(events) = workspace.phase_events.as_ref() {
11679 events.down_done[rank_index].record(&engine.stream())?;
11680 }
11681 workspace.ev_rank[rank_index].record(&engine.stream())?;
11682 }
11683
11684 if let Some(pre_join) = pre_join.as_mut() {
11685 pre_join()?;
11686 }
11687 let issue_ns_this = started
11688 .as_ref()
11689 .map(|started| started.elapsed().as_nanos() as u64);
11690 let join_started = timing.then(std::time::Instant::now);
11691 let output = {
11692 let _main = e.gpu.enter_main()?;
11693 for event in &workspace.ev_rank {
11694 e.stream().wait(event)?;
11695 }
11696 let mut output = e.uninit(tokens * experts.input_width)?;
11697 e.axpy_rows_seq_tokens_into(
11698 &workspace.slot_rows,
11699 route_weights_dev,
11700 &mut output,
11701 experts.input_width,
11702 experts_per_token,
11703 tokens,
11704 )?;
11705 output
11706 };
11707
11708 if let Some(started) = started {
11709 use std::sync::atomic::Ordering;
11710 e.stream().synchronize()?;
11711 let elapsed = started.elapsed().as_nanos() as u64;
11712 let join_ns_this = join_started
11713 .expect("timing join starts with total timing")
11714 .elapsed()
11715 .as_nanos() as u64;
11716 let mut phase_max_ms = [0.0f32; 5];
11717 if let Some(events) = workspace.phase_events.as_ref() {
11718 for rank_index in 0..world {
11719 let engine = &self.ranks[rank_index];
11720 let _main = engine.gpu.enter_main()?;
11721 phase_max_ms[0] = phase_max_ms[0]
11722 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
11723 phase_max_ms[1] = phase_max_ms[1].max(
11724 events.copy_done[rank_index]
11725 .elapsed_ms(&events.gate_up_done[rank_index])?,
11726 );
11727 phase_max_ms[2] = phase_max_ms[2].max(
11728 events.gate_up_done[rank_index]
11729 .elapsed_ms(&events.activation_done[rank_index])?,
11730 );
11731 phase_max_ms[3] = phase_max_ms[3].max(
11732 events.activation_done[rank_index]
11733 .elapsed_ms(&events.down_done[rank_index])?,
11734 );
11735 phase_max_ms[4] = phase_max_ms[4]
11736 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
11737 }
11738 }
11739 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
11740 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11741 let issue_ns = ISSUE_NS.fetch_add(
11742 issue_ns_this.expect("timing issue starts with total timing"),
11743 Ordering::Relaxed,
11744 ) + issue_ns_this.expect("timing issue starts with total timing");
11745 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
11746 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
11747 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
11748 let activation_ns =
11749 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
11750 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
11751 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
11752 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11753 if calls.is_multiple_of(430) {
11754 eprintln!(
11755 "[nvfp4-ep-device-router-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11756 ns as f64 / 1.0e6,
11757 ns as f64 / calls as f64 / 1.0e3,
11758 );
11759 eprintln!(
11760 "[nvfp4-ep-device-router-phases] calls={calls} issue_us={:.1} \
11761 join_us={:.1} rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
11762 activation_us={:.1} down_us={:.1}",
11763 issue_ns as f64 / calls as f64 / 1.0e3,
11764 join_ns as f64 / calls as f64 / 1.0e3,
11765 rank_span_ns as f64 / calls as f64 / 1.0e3,
11766 copy_ns as f64 / calls as f64 / 1.0e3,
11767 gate_up_ns as f64 / calls as f64 / 1.0e3,
11768 activation_ns as f64 / calls as f64 / 1.0e3,
11769 down_ns as f64 / calls as f64 / 1.0e3,
11770 );
11771 }
11772 }
11773 if graph_enabled
11774 && !timing
11775 && pre_join.is_none()
11776 && tokens <= NVFP4_EP_GRAPH_BATCH_CAP
11777 && workspace.graphs[tokens].is_none()
11778 {
11779 e.stream().synchronize()?;
11780 let graph = self.build_nvfp4_ep_routes_graph(
11781 experts,
11782 e,
11783 workspace,
11784 selected_dev,
11785 route_weights_dev,
11786 tokens,
11787 experts_per_token,
11788 activation_limit,
11789 )?;
11790 workspace.graphs[tokens] = Some(graph);
11791 eprintln!(
11792 "[parallel-ep-graph] captured devices={:?} tokens={tokens} \
11793 experts/token={experts_per_token} input=staged routes=fixed \
11794 device_arithmetic=unchanged performance_claim=false",
11795 self.devices,
11796 );
11797 }
11798 Ok(output)
11799 }
11800
11801 #[allow(clippy::too_many_arguments)]
11802 fn run_routed_experts_nvfp4_w4a8_device_routed(
11803 &self,
11804 experts: &ResidentNvfp4ExpertParallel,
11805 e: &Engine,
11806 input_dev: &crate::CudaSlice<f32>,
11807 selected_dev: &crate::CudaSlice<i32>,
11808 route_weights_dev: &crate::CudaSlice<f32>,
11809 workspace: &mut Nvfp4EpDeviceWorkspace,
11810 tokens: usize,
11811 experts_per_token: usize,
11812 activation_limit: Option<f32>,
11813 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11814 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11815 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11816 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11817 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11818 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11819 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11820 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11821 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11822 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11823 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11824 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11825 let started = timing.then(std::time::Instant::now);
11826 let pairs = tokens
11827 .checked_mul(experts_per_token)
11828 .ok_or("W4A8 device-routed EP route count overflow")?;
11829 let input_values = tokens
11830 .checked_mul(experts.input_width)
11831 .ok_or("W4A8 device-routed EP input size overflow")?;
11832 let scope = parallel_ep_q8_scope()?.unwrap_or(ParallelEpQ8Scope::All);
11833
11834 {
11835 let _main = e.gpu.enter_main()?;
11836 e.memset_zeros_view(
11837 &mut workspace
11838 .slot_rows
11839 .slice_mut(0..pairs * experts.input_width),
11840 )?;
11841 workspace.ev_entry.record(&e.stream())?;
11842 }
11843 for (rank_index, engine) in self.ranks.iter().enumerate() {
11844 let _main = engine.gpu.enter_main()?;
11845 if let Some(events) = workspace.phase_events.as_ref() {
11846 events.head[rank_index].record(&engine.stream())?;
11847 }
11848 engine.stream().wait(&workspace.ev_entry)?;
11849 let rank = &experts.ranks[rank_index];
11850 let owner_start = rank.expert_range.start;
11851 let owner_end = rank.expert_range.end;
11852 match scope {
11853 ParallelEpQ8Scope::All | ParallelEpQ8Scope::GateUp => {
11854 engine.quantize_q8_1_into(
11855 input_dev,
11856 tokens,
11857 experts.input_width,
11858 &mut workspace.input_q8[rank_index],
11859 &mut workspace.input_q8_scales[rank_index],
11860 )?;
11861 engine.moe_sel_w_mirror(
11862 selected_dev,
11863 route_weights_dev,
11864 &mut workspace.sel[rank_index],
11865 &mut workspace.route_w[rank_index],
11866 pairs,
11867 )?;
11868 if let Some(events) = workspace.phase_events.as_ref() {
11869 events.copy_done[rank_index].record(&engine.stream())?;
11870 }
11871 engine.qmatvec_nvfp4_q8_ep_dual_slots_into(
11872 &rank.gate,
11873 &rank.up,
11874 &workspace.sel[rank_index],
11875 &workspace.input_q8[rank_index],
11876 &workspace.input_q8_scales[rank_index],
11877 &mut workspace.gate_out[rank_index],
11878 &mut workspace.up_out[rank_index],
11879 pairs,
11880 experts_per_token,
11881 experts.input_width,
11882 experts.expert_width,
11883 owner_start,
11884 owner_end,
11885 experts.gate_row_bytes,
11886 rank.gate_expert_bytes,
11887 )?;
11888 }
11889 ParallelEpQ8Scope::Down => {
11890 engine.nvfp4_ep_stage_inputs(
11891 input_dev,
11892 selected_dev,
11893 route_weights_dev,
11894 &mut workspace.input_bf16[rank_index],
11895 &mut workspace.sel[rank_index],
11896 &mut workspace.route_w[rank_index],
11897 input_values,
11898 pairs,
11899 false,
11900 )?;
11901 if let Some(events) = workspace.phase_events.as_ref() {
11902 events.copy_done[rank_index].record(&engine.stream())?;
11903 }
11904 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
11905 &rank.gate,
11906 &rank.up,
11907 &workspace.sel[rank_index],
11908 &workspace.input_bf16[rank_index],
11909 &mut workspace.gate_out[rank_index],
11910 &mut workspace.up_out[rank_index],
11911 pairs,
11912 experts_per_token,
11913 experts.input_width,
11914 experts.expert_width,
11915 owner_start,
11916 owner_end,
11917 experts.gate_row_bytes,
11918 rank.gate_expert_bytes,
11919 )?;
11920 }
11921 }
11922 if let Some(events) = workspace.phase_events.as_ref() {
11923 events.gate_up_done[rank_index].record(&engine.stream())?;
11924 }
11925 match scope {
11926 ParallelEpQ8Scope::All | ParallelEpQ8Scope::Down => {
11927 engine.silu_mul_scaled_host_expf_q8_ep_slots_into(
11928 &workspace.gate_out[rank_index],
11929 &workspace.up_out[rank_index],
11930 &rank.macros_gate,
11931 &rank.macros_up,
11932 &workspace.sel[rank_index],
11933 owner_start,
11934 owner_end,
11935 activation_limit,
11936 &mut workspace.activation_q8[rank_index],
11937 &mut workspace.activation_q8_scales[rank_index],
11938 experts.expert_width,
11939 pairs,
11940 )?;
11941 if let Some(events) = workspace.phase_events.as_ref() {
11942 events.activation_done[rank_index].record(&engine.stream())?;
11943 }
11944 engine.qmatvec_nvfp4_q8_ep_down_slots_raw(
11945 &rank.down,
11946 &workspace.sel[rank_index],
11947 &workspace.activation_q8[rank_index],
11948 &workspace.activation_q8_scales[rank_index],
11949 &rank.macros_down,
11950 workspace.slot_rows_raw,
11951 pairs,
11952 experts.expert_width,
11953 experts.input_width,
11954 owner_start,
11955 owner_end,
11956 experts.down_row_bytes,
11957 rank.down_expert_bytes,
11958 )?;
11959 }
11960 ParallelEpQ8Scope::GateUp => {
11961 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
11962 &workspace.gate_out[rank_index],
11963 &workspace.up_out[rank_index],
11964 &rank.macros_gate,
11965 &rank.macros_up,
11966 &workspace.sel[rank_index],
11967 owner_start,
11968 owner_end,
11969 activation_limit,
11970 &mut workspace.activation_bf16[rank_index],
11971 experts.expert_width,
11972 pairs,
11973 )?;
11974 if let Some(events) = workspace.phase_events.as_ref() {
11975 events.activation_done[rank_index].record(&engine.stream())?;
11976 }
11977 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
11978 &rank.down,
11979 &workspace.sel[rank_index],
11980 &workspace.activation_bf16[rank_index],
11981 &rank.macros_down,
11982 workspace.slot_rows_raw,
11983 pairs,
11984 experts.expert_width,
11985 experts.input_width,
11986 owner_start,
11987 owner_end,
11988 experts.down_row_bytes,
11989 rank.down_expert_bytes,
11990 )?;
11991 }
11992 }
11993 if let Some(events) = workspace.phase_events.as_ref() {
11994 events.down_done[rank_index].record(&engine.stream())?;
11995 }
11996 workspace.ev_rank[rank_index].record(&engine.stream())?;
11997 }
11998
11999 if let Some(pre_join) = pre_join.as_mut() {
12000 pre_join()?;
12001 }
12002 let issue_ns_this = started
12003 .as_ref()
12004 .map(|started| started.elapsed().as_nanos() as u64);
12005 let join_started = timing.then(std::time::Instant::now);
12006 let output = {
12007 let _main = e.gpu.enter_main()?;
12008 for event in &workspace.ev_rank {
12009 e.stream().wait(event)?;
12010 }
12011 let mut output = e.uninit(input_values)?;
12012 e.axpy_rows_seq_tokens_into(
12013 &workspace.slot_rows,
12014 route_weights_dev,
12015 &mut output,
12016 experts.input_width,
12017 experts_per_token,
12018 tokens,
12019 )?;
12020 output
12021 };
12022 if let Some(started) = started {
12023 use std::sync::atomic::Ordering;
12024 e.stream().synchronize()?;
12025 let elapsed = started.elapsed().as_nanos() as u64;
12026 let join_ns_this = join_started
12027 .expect("timing join starts with total timing")
12028 .elapsed()
12029 .as_nanos() as u64;
12030 let mut phase_max_ms = [0.0f32; 5];
12031 if let Some(events) = workspace.phase_events.as_ref() {
12032 for rank_index in 0..self.ranks.len() {
12033 let engine = &self.ranks[rank_index];
12034 let _main = engine.gpu.enter_main()?;
12035 phase_max_ms[0] = phase_max_ms[0]
12036 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
12037 phase_max_ms[1] = phase_max_ms[1].max(
12038 events.copy_done[rank_index]
12039 .elapsed_ms(&events.gate_up_done[rank_index])?,
12040 );
12041 phase_max_ms[2] = phase_max_ms[2].max(
12042 events.gate_up_done[rank_index]
12043 .elapsed_ms(&events.activation_done[rank_index])?,
12044 );
12045 phase_max_ms[3] = phase_max_ms[3].max(
12046 events.activation_done[rank_index]
12047 .elapsed_ms(&events.down_done[rank_index])?,
12048 );
12049 phase_max_ms[4] = phase_max_ms[4]
12050 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
12051 }
12052 }
12053 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
12054 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
12055 let issue_ns = ISSUE_NS.fetch_add(
12056 issue_ns_this.expect("timing issue starts with total timing"),
12057 Ordering::Relaxed,
12058 ) + issue_ns_this.expect("timing issue starts with total timing");
12059 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
12060 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
12061 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
12062 let activation_ns =
12063 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
12064 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
12065 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
12066 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12067 if calls.is_multiple_of(430) {
12068 eprintln!(
12069 "[nvfp4-ep-q8-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12070 ns as f64 / 1.0e6,
12071 ns as f64 / calls as f64 / 1.0e3,
12072 );
12073 eprintln!(
12074 "[nvfp4-ep-q8-phases] calls={calls} issue_us={:.1} join_us={:.1} \
12075 rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
12076 activation_us={:.1} down_us={:.1}",
12077 issue_ns as f64 / calls as f64 / 1.0e3,
12078 join_ns as f64 / calls as f64 / 1.0e3,
12079 rank_span_ns as f64 / calls as f64 / 1.0e3,
12080 copy_ns as f64 / calls as f64 / 1.0e3,
12081 gate_up_ns as f64 / calls as f64 / 1.0e3,
12082 activation_ns as f64 / calls as f64 / 1.0e3,
12083 down_ns as f64 / calls as f64 / 1.0e3,
12084 );
12085 }
12086 }
12087 static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
12088 if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
12089 let (expert_input, post_activation, numeric_class) = match scope {
12090 ParallelEpQ8Scope::All => ("q8_1", "q8_1", "w4a8-internal"),
12091 ParallelEpQ8Scope::GateUp => ("q8_1", "bf16", "w4a8-gate-up-internal"),
12092 ParallelEpQ8Scope::Down => ("bf16", "q8_1", "w4a8-down-internal"),
12093 };
12094 eprintln!(
12095 "[parallel-ep-q8] devices={:?} tokens={tokens} scope={} \
12096 expert_input={expert_input} post_activation={post_activation} \
12097 external_boundary=bf16 numeric_class={numeric_class} \
12098 host_expf=true accumulation=token-slot-order performance_claim=false",
12099 self.devices,
12100 scope.label(),
12101 );
12102 }
12103 Ok(output)
12104 }
12105
12106 #[allow(clippy::too_many_arguments)]
12107 fn build_nvfp4_ep_routes_graph(
12108 &self,
12109 experts: &ResidentNvfp4ExpertParallel,
12110 e: &Engine,
12111 workspace: &mut Nvfp4EpDeviceWorkspace,
12112 selected_dev: &crate::CudaSlice<i32>,
12113 route_weights_dev: &crate::CudaSlice<f32>,
12114 tokens: usize,
12115 experts_per_token: usize,
12116 activation_limit: Option<f32>,
12117 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12118 use cudarc::driver::DevicePtr;
12119 use cudarc::driver::sys;
12120
12121 fn cu_try(result: sys::CUresult, context: &str) -> Result<(), Box<dyn std::error::Error>> {
12122 if result == sys::CUresult::CUDA_SUCCESS {
12123 Ok(())
12124 } else {
12125 Err(format!("{context}: {result:?}").into())
12126 }
12127 }
12128
12129 let world = self.ranks.len();
12130 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
12131 return Err(format!(
12132 "W4A16 EP graph world {world} != expert ranks {}",
12133 experts.ranks.len()
12134 )
12135 .into());
12136 }
12137 let width = experts.input_width;
12138 if !(1..=NVFP4_EP_GRAPH_BATCH_CAP).contains(&tokens) {
12139 return Err(format!(
12140 "W4A16 EP graph tokens {tokens} outside 1..={NVFP4_EP_GRAPH_BATCH_CAP}"
12141 )
12142 .into());
12143 }
12144 let pairs = tokens
12145 .checked_mul(experts_per_token)
12146 .ok_or("W4A16 EP graph pair count overflow")?;
12147 let input_values = tokens
12148 .checked_mul(width)
12149 .ok_or("W4A16 EP graph input size overflow")?;
12150 let root_stream = e.stream();
12151 let (input_ptr, _input_guard) = workspace.graph_input.device_ptr(&root_stream);
12152 let (selected_ptr, _selected_guard) = selected_dev.device_ptr(&root_stream);
12153 let (weights_ptr, _weights_guard) = route_weights_dev.device_ptr(&root_stream);
12154 let route_ptrs = (selected_ptr, weights_ptr);
12155
12156 let mut children = Vec::with_capacity(world + 1);
12157 for rank_index in 0..world {
12158 let engine = &self.ranks[rank_index];
12159 let rank = &experts.ranks[rank_index];
12160 let owner_start = rank.expert_range.start;
12161 let owner_end = rank.expert_range.end;
12162 let _main = engine.gpu.enter_main()?;
12163 let (child, _retained) = engine.capture_graph_retained(|_| {
12164 engine.nvfp4_ep_stage_inputs_raw(
12165 input_ptr,
12166 selected_ptr,
12167 weights_ptr,
12168 &mut workspace.input_bf16[rank_index],
12169 &mut workspace.sel[rank_index],
12170 &mut workspace.route_w[rank_index],
12171 input_values,
12172 pairs,
12173 false,
12174 )?;
12175 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12176 &rank.gate,
12177 &rank.up,
12178 &workspace.sel[rank_index],
12179 &workspace.input_bf16[rank_index],
12180 &mut workspace.gate_out[rank_index],
12181 &mut workspace.up_out[rank_index],
12182 pairs,
12183 experts_per_token,
12184 width,
12185 experts.expert_width,
12186 owner_start,
12187 owner_end,
12188 experts.gate_row_bytes,
12189 rank.gate_expert_bytes,
12190 )?;
12191 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12192 &workspace.gate_out[rank_index],
12193 &workspace.up_out[rank_index],
12194 &rank.macros_gate,
12195 &rank.macros_up,
12196 &workspace.sel[rank_index],
12197 owner_start,
12198 owner_end,
12199 activation_limit,
12200 &mut workspace.activation_bf16[rank_index],
12201 experts.expert_width,
12202 pairs,
12203 )?;
12204 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12205 &rank.down,
12206 &workspace.sel[rank_index],
12207 &workspace.activation_bf16[rank_index],
12208 &rank.macros_down,
12209 workspace.slot_rows_raw,
12210 pairs,
12211 experts.expert_width,
12212 width,
12213 owner_start,
12214 owner_end,
12215 experts.down_row_bytes,
12216 rank.down_expert_bytes,
12217 )?;
12218 Ok(())
12219 })?;
12220 children.push(child);
12221 }
12222
12223 {
12224 let _main = e.gpu.enter_main()?;
12225 let (child, _retained) = e.capture_graph_retained(|_| {
12226 e.axpy_rows_seq_tokens_into(
12227 &workspace.slot_rows,
12228 route_weights_dev,
12229 &mut workspace.graph_output,
12230 width,
12231 experts_per_token,
12232 tokens,
12233 )
12234 })?;
12235 children.push(child);
12236 }
12237
12238 let mut parent: sys::CUgraph = std::ptr::null_mut();
12239 unsafe {
12240 cu_try(sys::cuGraphCreate(&mut parent, 0), "W4A16 EP cuGraphCreate")?;
12241 }
12242 let mut rank_nodes = Vec::with_capacity(world);
12243 for (rank_index, child) in children.iter().take(world).enumerate() {
12244 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12245 unsafe {
12246 cu_try(
12247 sys::cuGraphAddChildGraphNode(
12248 &mut node,
12249 parent,
12250 std::ptr::null(),
12251 0,
12252 child.cu_graph(),
12253 ),
12254 &format!("W4A16 EP graph rank {rank_index}"),
12255 )?;
12256 }
12257 rank_nodes.push(node);
12258 }
12259 let mut combine_node: sys::CUgraphNode = std::ptr::null_mut();
12260 unsafe {
12261 cu_try(
12262 sys::cuGraphAddChildGraphNode(
12263 &mut combine_node,
12264 parent,
12265 rank_nodes.as_ptr(),
12266 rank_nodes.len(),
12267 children[world].cu_graph(),
12268 ),
12269 "W4A16 EP graph combine",
12270 )?;
12271 }
12272 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12273 unsafe {
12274 cu_try(
12275 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12276 "W4A16 EP graph instantiate",
12277 )?;
12278 }
12279 workspace.graph_routes = Some(route_ptrs);
12280 Ok(RoutesGraph {
12281 exec,
12282 parent,
12283 _children: children,
12284 })
12285 }
12286
12287 pub fn run_tensor_parallel_routes_nvfp4_device(
12301 &self,
12302 experts: &ResidentNvfp4TensorParallel,
12303 input: &[f32],
12304 selected: &[usize],
12305 route_weights: &[f32],
12306 experts_per_token: usize,
12307 activation_limit: Option<f32>,
12308 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12309 validate_activations(input, 1, experts.input_width)?;
12310 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
12311 return Err(format!(
12312 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
12313 selected.len(),
12314 route_weights.len(),
12315 )
12316 .into());
12317 }
12318 if !route_weights.iter().all(|weight| weight.is_finite()) {
12319 return Err("NVFP4 device route weights contain a non-finite value".into());
12320 }
12321 let world = self.ranks.len();
12322 if world != NVFP4_CANONICAL_ROW_SHARDS {
12323 return Err(format!(
12324 "NVFP4 device routes require world == canonical shard grid \
12325 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12326 )
12327 .into());
12328 }
12329 let local_out = if experts.ep2 {
12330 experts.expert_width
12331 } else {
12332 experts.expert_width / world
12333 };
12334
12335 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12339 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12340 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12341 let started = timing.then(std::time::Instant::now);
12342
12343 let n_sel = experts_per_token;
12344 let mut workspace_guard = experts
12345 .device_workspace
12346 .lock()
12347 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12348 if workspace_guard.is_none() {
12349 let mut gate_out = Vec::with_capacity(world);
12350 let mut up_out = Vec::with_capacity(world);
12351 let mut act_q = Vec::with_capacity(world);
12352 let mut act_d = Vec::with_capacity(world);
12353 let mut sel = Vec::with_capacity(world);
12354 let mut partial = Vec::with_capacity(world);
12355 let mut accumulator = Vec::with_capacity(world);
12356 let mut combine_w = Vec::with_capacity(world);
12357 let mut route_w = Vec::with_capacity(world);
12358 let mut in_q = Vec::with_capacity(world);
12359 let mut in_d = Vec::with_capacity(world);
12360 let mut input = Vec::with_capacity(world);
12361 let mut ev_rank = Vec::with_capacity(world);
12362 let moe_direct = moe_direct_on();
12363 for (rank, engine) in self.ranks.iter().enumerate() {
12364 let _main = engine.gpu.enter_main()?;
12365 gate_out.push(engine.uninit(n_sel * local_out)?);
12366 up_out.push(engine.uninit(n_sel * local_out)?);
12367 act_q.push(engine.uninit_i8(n_sel * local_out)?);
12368 act_d.push(engine.uninit(n_sel * local_out / 32)?);
12369 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
12370 partial.push(engine.uninit(n_sel * experts.input_width)?);
12371 if moe_direct && rank != 0 {
12373 let root = &self.ranks[0];
12374 let _root_main = root.gpu.enter_main()?;
12375 accumulator.push(root.zeros(experts.input_width)?);
12376 } else {
12377 accumulator.push(engine.zeros(experts.input_width)?);
12378 }
12379 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12380 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12381 in_q.push(engine.uninit_i8(experts.input_width)?);
12382 in_d.push(engine.uninit(experts.input_width / 32)?);
12383 input.push(engine.uninit(experts.input_width)?);
12384 ev_rank.push(engine.ctx().new_event(None)?);
12385 }
12386 let root = &self.ranks[0];
12387 let _main = root.gpu.enter_main()?;
12388 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
12389 prestaged: false,
12390 rank1_routed: false,
12391 ev_input: None,
12392 fence_flags_raw: 0,
12393 fence_ticket: 0,
12394 gate_out,
12395 up_out,
12396 act_q,
12397 act_d,
12398 sel,
12399 partial,
12400 accumulator,
12401 combine_w,
12402 route_w,
12403 in_q,
12404 in_d,
12405 dev_route_e: None,
12406 in_stage_e: None,
12407 out_stage_e: None,
12408 routes_graph: None,
12409 raw_dev_route_e: None,
12410 raw_combine: None,
12411 raw_input: Vec::new(),
12412 raw_sel: Vec::new(),
12413 raw_route_w: Vec::new(),
12414 remote: root.uninit(experts.input_width)?,
12415 combined: root.uninit(experts.input_width)?,
12416 n_sel,
12417 input,
12418 ev_rank,
12419 ev_done: Some(root.ctx().new_event(None)?),
12420 ev_entry: None,
12421 });
12422 }
12423 let workspace = workspace_guard
12424 .as_mut()
12425 .expect("NVFP4 device routes workspace initialized above");
12426 if experts.ep2 {
12429 return Ok(vec![0.0f32; experts.input_width]);
12430 }
12431 if workspace.n_sel != n_sel {
12432 return Err(format!(
12433 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
12434 workspace.n_sel
12435 )
12436 .into());
12437 }
12438 for &expert in selected {
12439 if expert >= experts.expert_count {
12440 return Err(format!(
12441 "NVFP4 device selected expert {expert} outside 0..{}",
12442 experts.expert_count
12443 )
12444 .into());
12445 }
12446 }
12447 let sel_i32 = selected
12448 .iter()
12449 .map(|&expert| expert as i32)
12450 .collect::<Vec<_>>();
12451
12452 for (rank_index, engine) in self.ranks.iter().enumerate() {
12459 let _main = engine.gpu.enter_main()?;
12460 let device_input = engine.htod(input)?;
12461 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
12462 engine.quantize_q8_1_into(
12463 &device_input,
12464 1,
12465 experts.input_width,
12466 &mut in_q[rank_index],
12467 &mut in_d[rank_index],
12468 )?;
12469 }
12471 self.nvfp4_routes_batched_sweeps(
12472 experts,
12473 workspace,
12474 selected,
12475 route_weights,
12476 &sel_i32,
12477 local_out,
12478 n_sel,
12479 activation_limit,
12480 false,
12481 )?;
12482
12483 let root = &self.ranks[0];
12486 for engine in &self.ranks[1..] {
12487 let _main = engine.gpu.enter_main()?;
12488 engine.stream().synchronize()?;
12489 }
12490 let _main = root.gpu.enter_main()?;
12491 root.stream()
12492 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12493 root.add(
12494 &workspace.accumulator[0],
12495 &workspace.remote,
12496 &mut workspace.combined,
12497 experts.input_width,
12498 )?;
12499 let output = root.dtoh(&workspace.combined)?;
12500 if let Some(started) = started {
12501 use std::sync::atomic::Ordering;
12502 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12503 + started.elapsed().as_nanos() as u64;
12504 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12505 if calls.is_multiple_of(430) {
12506 eprintln!(
12507 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12508 ns as f64 / 1.0e6,
12509 ns as f64 / calls as f64 / 1.0e3,
12510 );
12511 }
12512 }
12513 Ok(output)
12514 }
12515
12516 #[allow(clippy::too_many_arguments)]
12521 fn nvfp4_routes_batched_sweeps(
12522 &self,
12523 experts: &ResidentNvfp4TensorParallel,
12524 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12525 selected: &[usize],
12526 route_weights: &[f32],
12527 sel_i32: &[i32],
12528 local_out: usize,
12529 n_sel: usize,
12530 activation_limit: Option<f32>,
12531 device_routed: bool,
12532 ) -> Result<(), Box<dyn std::error::Error>> {
12533 for rank_index in 0..self.ranks.len() {
12534 self.nvfp4_routes_batched_sweeps_rank(
12535 experts,
12536 workspace,
12537 selected,
12538 route_weights,
12539 sel_i32,
12540 local_out,
12541 n_sel,
12542 activation_limit,
12543 device_routed,
12544 rank_index,
12545 )?;
12546 }
12547 Ok(())
12548 }
12549
12550 #[allow(clippy::too_many_arguments)]
12553 fn nvfp4_routes_batched_sweeps_rank(
12554 &self,
12555 experts: &ResidentNvfp4TensorParallel,
12556 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12557 selected: &[usize],
12558 route_weights: &[f32],
12559 sel_i32: &[i32],
12560 local_out: usize,
12561 n_sel: usize,
12562 activation_limit: Option<f32>,
12563 device_routed: bool,
12564 rank_index: usize,
12565 ) -> Result<(), Box<dyn std::error::Error>> {
12566 {
12567 let engine = &self.ranks[rank_index];
12568 let _main = engine.gpu.enter_main()?;
12569 if experts.ep2 {
12574 if !device_routed {
12575 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
12576 }
12577 let gate_bank = &experts.gate[rank_index];
12578 let up_bank = &experts.up[rank_index];
12579 if gate_bank.local_out != experts.expert_width
12580 || gate_bank.expert_bytes != up_bank.expert_bytes
12581 {
12582 return Err("NVFP4 EP2 bank geometry drifted".into());
12583 }
12584 {
12585 let Nvfp4DeviceRoutesWorkspace {
12586 sel,
12587 gate_out,
12588 up_out,
12589 in_q,
12590 in_d,
12591 ..
12592 } = &mut *workspace;
12593 engine.qmatvec_nvfp4_sel_gu_ep_into(
12594 &gate_bank.bank,
12595 &up_bank.bank,
12596 &sel[rank_index],
12597 &in_q[rank_index],
12598 &in_d[rank_index],
12599 &mut gate_out[rank_index],
12600 &mut up_out[rank_index],
12601 n_sel,
12602 gate_bank.in_features,
12603 gate_bank.local_out,
12604 gate_bank.row_bytes,
12605 gate_bank.expert_bytes,
12606 rank_index,
12607 )?;
12608 }
12609 {
12610 let Nvfp4DeviceRoutesWorkspace {
12611 gate_out,
12612 up_out,
12613 sel,
12614 act_q,
12615 act_d,
12616 ..
12617 } = &mut *workspace;
12618 engine.silu_mul_scaled_q8_1_sel_ep_into(
12619 &gate_out[rank_index],
12620 &up_out[rank_index],
12621 &experts.macros_gate_dev[rank_index],
12622 &experts.macros_up_dev[rank_index],
12623 &sel[rank_index],
12624 activation_limit,
12625 &mut act_q[rank_index],
12626 &mut act_d[rank_index],
12627 local_out,
12628 n_sel,
12629 rank_index,
12630 )?;
12631 }
12632 let shard = &experts.down[rank_index];
12633 if shard.device_rank != rank_index || shard.local_in != local_out {
12634 return Err("NVFP4 EP2 down bank placement drifted".into());
12635 }
12636 {
12637 let Nvfp4DeviceRoutesWorkspace {
12638 sel,
12639 act_q,
12640 act_d,
12641 route_w,
12642 accumulator,
12643 ..
12644 } = &mut *workspace;
12645 engine.qmatvec_nvfp4_sel_down8_ep_into(
12646 &shard.bank,
12647 &sel[rank_index],
12648 &act_q[rank_index],
12649 &act_d[rank_index],
12650 &route_w[rank_index],
12651 &experts.macros_down_dev[rank_index],
12652 &mut accumulator[rank_index],
12653 n_sel,
12654 shard.local_in,
12655 shard.out_features,
12656 shard.row_bytes,
12657 shard.expert_bytes,
12658 local_out,
12659 local_out / 32,
12660 rank_index,
12661 )?;
12662 }
12663 return Ok(());
12664 }
12665 if !device_routed {
12666 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
12667 let folded = (0..n_sel)
12670 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
12671 .collect::<Vec<_>>();
12672 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
12673 engine.stream().memcpy_htod(&folded, &mut view)?;
12674 }
12675 let gate_bank = &experts.gate[rank_index];
12676 let up_bank = &experts.up[rank_index];
12677 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
12678 let gu_fused = sel_gu_fused_on()
12686 && gate_bank.slot_major
12687 && up_bank.slot_major
12688 && gate_bank.in_features == up_bank.in_features
12689 && gate_bank.local_out == up_bank.local_out
12690 && gate_bank.row_bytes == up_bank.row_bytes
12691 && gate_bank.expert_bytes == up_bank.expert_bytes;
12692 {
12698 static SEEN_GU: std::sync::Mutex<Vec<(bool, bool, bool)>> =
12699 std::sync::Mutex::new(Vec::new());
12700 let combo = (gu_fused, sel_gu_fused_on(), gate_bank.slot_major);
12701 let mut seen = SEEN_GU.lock().unwrap();
12702 if !seen.contains(&combo) {
12703 seen.push(combo);
12704 eprintln!(
12705 "[nvfp4-sweep] gu_fused={} door={} slot_major={} geometry_match={} \
12706 in_f={} out_f={} n_sel={n_sel}",
12707 gu_fused,
12708 sel_gu_fused_on(),
12709 gate_bank.slot_major,
12710 gate_bank.in_features == up_bank.in_features
12711 && gate_bank.local_out == up_bank.local_out
12712 && gate_bank.row_bytes == up_bank.row_bytes
12713 && gate_bank.expert_bytes == up_bank.expert_bytes,
12714 gate_bank.in_features,
12715 gate_bank.local_out
12716 );
12717 }
12718 }
12719 if gu_fused {
12720 let Nvfp4DeviceRoutesWorkspace {
12721 sel,
12722 gate_out,
12723 up_out,
12724 in_q,
12725 in_d,
12726 ..
12727 } = &mut *workspace;
12728 engine.qmatvec_nvfp4_sel_gu_into(
12729 &gate_bank.bank,
12730 &up_bank.bank,
12731 &sel[rank_index],
12732 &in_q[rank_index],
12733 &in_d[rank_index],
12734 &mut gate_out[rank_index],
12735 &mut up_out[rank_index],
12736 n_sel,
12737 gate_bank.in_features,
12738 gate_bank.local_out,
12739 gate_bank.row_bytes,
12740 gate_bank.expert_bytes,
12741 gate_bank.slot_major,
12742 )?;
12743 } else {
12744 engine.qmatvec_nvfp4_sel_into(
12745 &gate_bank.bank,
12746 &workspace.sel[rank_index],
12747 aq,
12748 ad,
12749 &mut workspace.gate_out[rank_index],
12750 n_sel,
12751 gate_bank.in_features,
12752 gate_bank.local_out,
12753 gate_bank.row_bytes,
12754 gate_bank.expert_bytes,
12755 0,
12756 0,
12757 gate_bank.slot_major,
12758 )?;
12759 engine.qmatvec_nvfp4_sel_into(
12760 &up_bank.bank,
12761 &workspace.sel[rank_index],
12762 aq,
12763 ad,
12764 &mut workspace.up_out[rank_index],
12765 n_sel,
12766 up_bank.in_features,
12767 up_bank.local_out,
12768 up_bank.row_bytes,
12769 up_bank.expert_bytes,
12770 0,
12771 0,
12772 up_bank.slot_major,
12773 )?;
12774 }
12775 {
12779 let Nvfp4DeviceRoutesWorkspace {
12780 gate_out,
12781 up_out,
12782 sel,
12783 act_q,
12784 act_d,
12785 ..
12786 } = &mut *workspace;
12787 engine.silu_mul_scaled_q8_1_sel_into(
12788 &gate_out[rank_index],
12789 &up_out[rank_index],
12790 &experts.macros_gate_dev[rank_index],
12791 &experts.macros_up_dev[rank_index],
12792 &sel[rank_index],
12793 activation_limit,
12794 &mut act_q[rank_index],
12795 &mut act_d[rank_index],
12796 local_out,
12797 n_sel,
12798 )?;
12799 }
12800 let shard = &experts.down[rank_index];
12801 if shard.device_rank != rank_index || shard.local_in != local_out {
12802 return Err(
12803 "NVFP4 device routes: down canonical shard placement drifted from \
12804 the gate/up column split"
12805 .into(),
12806 );
12807 }
12808 let down8 =
12816 device_routed && sel_down8_on() && shard.slot_major && (shard.local_in >> 5) <= 32;
12817 {
12822 static SEEN_D8: std::sync::Mutex<Vec<(bool, bool, bool, bool)>> =
12823 std::sync::Mutex::new(Vec::new());
12824 let combo = (down8, sel_down8_on(), device_routed, shard.slot_major);
12825 let mut seen = SEEN_D8.lock().unwrap();
12826 if !seen.contains(&combo) {
12827 seen.push(combo);
12828 eprintln!(
12834 "[nvfp4-sweep] down8={} door={} door_source={} device_routed={} \
12835 slot_major={} nsb={} in_class={} n_sel={n_sel}",
12836 down8,
12837 sel_down8_on(),
12838 sel_down8_source().1,
12839 device_routed,
12840 shard.slot_major,
12841 shard.local_in >> 5,
12842 (shard.local_in >> 5) <= 32
12843 );
12844 }
12845 }
12846 if down8 {
12847 let Nvfp4DeviceRoutesWorkspace {
12848 sel,
12849 act_q,
12850 act_d,
12851 route_w,
12852 accumulator,
12853 ..
12854 } = &mut *workspace;
12855 engine.qmatvec_nvfp4_sel_down8_into(
12856 &shard.bank,
12857 &sel[rank_index],
12858 &act_q[rank_index],
12859 &act_d[rank_index],
12860 &route_w[rank_index],
12861 &experts.macros_down_dev[rank_index],
12862 &mut accumulator[rank_index],
12863 n_sel,
12864 shard.local_in,
12865 shard.out_features,
12866 shard.row_bytes,
12867 shard.expert_bytes,
12868 local_out,
12869 local_out / 32,
12870 shard.slot_major,
12871 )?;
12872 } else {
12873 let Nvfp4DeviceRoutesWorkspace {
12874 sel,
12875 act_q,
12876 act_d,
12877 partial,
12878 ..
12879 } = &mut *workspace;
12880 engine.qmatvec_nvfp4_sel_into(
12881 &shard.bank,
12882 &sel[rank_index],
12883 &act_q[rank_index],
12884 &act_d[rank_index],
12885 &mut partial[rank_index],
12886 n_sel,
12887 shard.local_in,
12888 shard.out_features,
12889 shard.row_bytes,
12890 shard.expert_bytes,
12891 local_out,
12892 local_out / 32,
12893 shard.slot_major,
12894 )?;
12895 }
12896 if !down8 {
12901 let Nvfp4DeviceRoutesWorkspace {
12902 partial,
12903 combine_w,
12904 route_w,
12905 sel,
12906 accumulator,
12907 ..
12908 } = &mut *workspace;
12909 if device_routed {
12910 engine.axpy_rows_seq_md_into(
12911 &partial[rank_index],
12912 &route_w[rank_index],
12913 &experts.macros_down_dev[rank_index],
12914 &sel[rank_index],
12915 &mut accumulator[rank_index],
12916 experts.input_width,
12917 n_sel,
12918 )?;
12919 } else {
12920 engine.axpy_rows_seq_into(
12921 &partial[rank_index],
12922 &combine_w[rank_index],
12923 &mut accumulator[rank_index],
12924 experts.input_width,
12925 n_sel,
12926 )?;
12927 }
12928 }
12929 }
12930 Ok(())
12931 }
12932
12933 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_io(
12942 &self,
12943 experts: &ResidentNvfp4TensorParallel,
12944 e: &Engine,
12945 input_dev: &crate::CudaSlice<f32>,
12946 selected: &[usize],
12947 route_weights: &[f32],
12948 experts_per_token: usize,
12949 activation_limit: Option<f32>,
12950 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
12951 if input_dev.len() != experts.input_width {
12952 return Err(format!(
12953 "NVFP4 device-io routes input {} != width {}",
12954 input_dev.len(),
12955 experts.input_width
12956 )
12957 .into());
12958 }
12959 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
12960 return Err(format!(
12961 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
12962 selected.len(),
12963 route_weights.len(),
12964 )
12965 .into());
12966 }
12967 if !route_weights.iter().all(|weight| weight.is_finite()) {
12968 return Err("NVFP4 device route weights contain a non-finite value".into());
12969 }
12970 let world = self.ranks.len();
12971 if world != NVFP4_CANONICAL_ROW_SHARDS {
12972 return Err(format!(
12973 "NVFP4 device routes require world == canonical shard grid \
12974 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12975 )
12976 .into());
12977 }
12978 let local_out = experts.expert_width / world;
12979 let n_sel = experts_per_token;
12980
12981 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12982 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12983 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12984 let started = timing.then(std::time::Instant::now);
12985
12986 let mut workspace_guard = experts
12987 .device_workspace
12988 .lock()
12989 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12990 if workspace_guard.is_none() {
12991 drop(workspace_guard);
12992 let zero = vec![0.0f32; experts.input_width];
12995 let zero_sel = vec![0usize; n_sel];
12996 let zero_w = vec![0.0f32; n_sel];
12997 let _ = self.run_tensor_parallel_routes_nvfp4_device(
12998 experts,
12999 &zero,
13000 &zero_sel,
13001 &zero_w,
13002 n_sel,
13003 activation_limit,
13004 )?;
13005 workspace_guard = experts
13006 .device_workspace
13007 .lock()
13008 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13009 }
13010 let workspace = workspace_guard
13011 .as_mut()
13012 .expect("NVFP4 device routes workspace initialized above");
13013 if workspace.n_sel != n_sel {
13014 return Err(format!(
13015 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
13016 workspace.n_sel
13017 )
13018 .into());
13019 }
13020 for &expert in selected {
13021 if expert >= experts.expert_count {
13022 return Err(format!(
13023 "NVFP4 device selected expert {expert} outside 0..{}",
13024 experts.expert_count
13025 )
13026 .into());
13027 }
13028 }
13029 let sel_i32 = selected
13030 .iter()
13031 .map(|&expert| expert as i32)
13032 .collect::<Vec<_>>();
13033
13034 if let Some((_, device)) = workspace.ev_entry.as_ref() {
13038 if *device != e.ctx().ordinal() {
13039 return Err("NVFP4 device-io routes engine changed".into());
13040 }
13041 } else {
13042 let _main = e.gpu.enter_main()?;
13043 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13044 }
13045 {
13046 let _main = e.gpu.enter_main()?;
13047 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13048 ev_entry.record(&e.stream())?;
13049 }
13050 for (rank_index, engine) in self.ranks.iter().enumerate() {
13051 let _main = engine.gpu.enter_main()?;
13052 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13053 engine.stream().wait(ev_entry)?;
13054 {
13055 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13056 engine
13057 .stream()
13058 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13059 }
13060 {
13061 let Nvfp4DeviceRoutesWorkspace {
13062 input, in_q, in_d, ..
13063 } = &mut *workspace;
13064 engine.quantize_q8_1_into(
13065 &input[rank_index],
13066 1,
13067 experts.input_width,
13068 &mut in_q[rank_index],
13069 &mut in_d[rank_index],
13070 )?;
13071 }
13072 }
13073 self.nvfp4_routes_batched_sweeps(
13074 experts,
13075 workspace,
13076 selected,
13077 route_weights,
13078 &sel_i32,
13079 local_out,
13080 n_sel,
13081 activation_limit,
13082 false,
13083 )?;
13084
13085 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
13091 let _main = engine.gpu.enter_main()?;
13092 workspace.ev_rank[rank_index].record(&engine.stream())?;
13093 }
13094 if moe_direct_on() && self.ranks.len() == 2 {
13095 {
13102 let root = &self.ranks[0];
13103 let _main = root.gpu.enter_main()?;
13104 workspace
13105 .ev_done
13106 .as_ref()
13107 .expect("device routes done event")
13108 .record(&root.stream())?;
13109 }
13110 let _main = e.gpu.enter_main()?;
13111 e.stream().wait(
13112 workspace
13113 .ev_done
13114 .as_ref()
13115 .expect("device routes done event"),
13116 )?;
13117 for ev in workspace.ev_rank.iter().skip(1) {
13118 e.stream().wait(ev)?;
13119 }
13120 let mut output = e.uninit(experts.input_width)?;
13121 e.add(
13122 &workspace.accumulator[0],
13123 &workspace.accumulator[1],
13124 &mut output,
13125 experts.input_width,
13126 )?;
13127 let output = output;
13128 if let Some(started) = started {
13129 use std::sync::atomic::Ordering;
13130 let ns = TIMING_NS
13131 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13132 + started.elapsed().as_nanos() as u64;
13133 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13134 if calls.is_multiple_of(430) {
13135 eprintln!(
13136 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13137 ns as f64 / 1.0e6,
13138 ns as f64 / calls as f64 / 1.0e3,
13139 );
13140 }
13141 }
13142 return Ok(output);
13143 }
13144 {
13145 let root = &self.ranks[0];
13146 let _main = root.gpu.enter_main()?;
13147 for ev in workspace.ev_rank.iter().skip(1) {
13148 root.stream().wait(ev)?;
13149 }
13150 root.stream()
13151 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
13152 {
13153 let Nvfp4DeviceRoutesWorkspace {
13154 accumulator,
13155 remote,
13156 combined,
13157 ..
13158 } = &mut *workspace;
13159 root.add(&accumulator[0], remote, combined, experts.input_width)?;
13160 }
13161 workspace
13162 .ev_done
13163 .as_ref()
13164 .expect("device routes done event")
13165 .record(&root.stream())?;
13166 }
13167 let output = {
13168 let _main = e.gpu.enter_main()?;
13169 e.stream().wait(
13170 workspace
13171 .ev_done
13172 .as_ref()
13173 .expect("device routes done event"),
13174 )?;
13175 let mut output = e.uninit(experts.input_width)?;
13178 e.stream().memcpy_dtod(
13179 &workspace.combined.slice(0..experts.input_width),
13180 &mut output.slice_mut(0..experts.input_width),
13181 )?;
13182 output
13183 };
13184 if let Some(started) = started {
13185 use std::sync::atomic::Ordering;
13186 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13187 + started.elapsed().as_nanos() as u64;
13188 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13189 if calls.is_multiple_of(430) {
13190 eprintln!(
13191 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13192 ns as f64 / 1.0e6,
13193 ns as f64 / calls as f64 / 1.0e3,
13194 );
13195 }
13196 }
13197 Ok(output)
13198 }
13199
13200 #[allow(clippy::too_many_arguments)]
13206 pub fn nvfp4_routes_prestage(
13211 &self,
13212 experts: &ResidentNvfp4TensorParallel,
13213 e: &Engine,
13214 input_dev: &crate::CudaSlice<f32>,
13215 ) -> Result<bool, Box<dyn std::error::Error>> {
13216 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
13217 }
13218
13219 pub fn nvfp4_routes_prestage_with(
13225 &self,
13226 experts: &ResidentNvfp4TensorParallel,
13227 e: &Engine,
13228 input_dev: &crate::CudaSlice<f32>,
13229 rank1_router: impl FnOnce(
13230 &Engine,
13231 &crate::CudaSlice<f32>,
13232 &mut crate::CudaSlice<i32>,
13233 &mut crate::CudaSlice<f32>,
13234 ) -> Result<bool, Box<dyn std::error::Error>>,
13235 ) -> Result<bool, Box<dyn std::error::Error>> {
13236 if !routes_prestage_on() || step_tp_graph_enabled()? {
13237 return Ok(false);
13238 }
13239 if input_dev.len() != experts.input_width {
13240 return Err("NVFP4 prestage input width mismatch".into());
13241 }
13242 let mut workspace_guard = experts
13243 .device_workspace
13244 .lock()
13245 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13246 let Some(workspace) = workspace_guard.as_mut() else {
13247 return Ok(false);
13248 };
13249 if workspace.ev_input.is_none() {
13250 let _main = e.gpu.enter_main()?;
13251 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13252 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
13253 return Err("NVFP4 prestage engine changed".into());
13254 }
13255 {
13256 let _main = e.gpu.enter_main()?;
13257 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13258 ev.record(&e.stream())?;
13259 }
13260 for (rank_index, engine) in self.ranks.iter().enumerate() {
13261 let _main = engine.gpu.enter_main()?;
13262 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13263 engine.stream().wait(ev)?;
13264 {
13265 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13266 engine
13267 .stream()
13268 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13269 }
13270 {
13271 let Nvfp4DeviceRoutesWorkspace {
13272 input, in_q, in_d, ..
13273 } = &mut *workspace;
13274 engine.quantize_q8_1_into(
13275 &input[rank_index],
13276 1,
13277 experts.input_width,
13278 &mut in_q[rank_index],
13279 &mut in_d[rank_index],
13280 )?;
13281 }
13282 }
13283 if self.ranks.len() == 2 {
13284 let rank1 = &self.ranks[1];
13285 let _r1 = rank1.gpu.enter_main()?;
13286 let Nvfp4DeviceRoutesWorkspace {
13287 input,
13288 sel,
13289 route_w,
13290 ..
13291 } = &mut *workspace;
13292 let (in1, rest_sel) = (&input[1], &mut sel[1]);
13293 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
13294 workspace.rank1_routed = true;
13295 }
13296 }
13297 workspace.prestaged = true;
13298 Ok(true)
13299 }
13300
13301 #[allow(clippy::too_many_arguments)]
13318 fn determ_stage_bytes(v: &[u8]) -> u64 {
13327 v.iter().fold(0u64, |a, b| {
13328 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
13329 })
13330 }
13331
13332 fn determ_stage_i32(v: &[i32]) -> u64 {
13337 v.iter().fold(0u64, |a, b| {
13338 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
13339 })
13340 }
13341
13342 fn determ_stage_sum(v: &[f32]) -> u64 {
13343 v.iter().fold(0u64, |a, x| {
13344 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
13345 })
13346 }
13347
13348 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
13350 &self,
13351 experts: &ResidentNvfp4TensorParallel,
13352 e: &Engine,
13353 z_t: &crate::CudaSlice<f32>,
13354 t: usize,
13355 sel: &[i32],
13356 w: &[f32],
13357 n_used: usize,
13358 activation_limit: Option<f32>,
13359 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13360 let world = self.ranks.len();
13361 if world != NVFP4_CANONICAL_ROW_SHARDS {
13362 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
13363 }
13364 let slot_major = experts.gate.iter().all(|b| b.slot_major)
13378 && experts.up.iter().all(|b| b.slot_major)
13379 && experts.down.iter().all(|b| b.slot_major);
13380 let any_slot_major = experts.gate.iter().any(|b| b.slot_major)
13381 || experts.up.iter().any(|b| b.slot_major)
13382 || experts.down.iter().any(|b| b.slot_major);
13383 if any_slot_major != slot_major {
13384 return Err(
13385 "NVFP4 grouped prime: gate/up/down banks disagree on the row layout — \
13386 one grouped GEMM cannot serve two byte maps"
13387 .into(),
13388 );
13389 }
13390 let bank_qt = if slot_major {
13391 crate::QT_NVFP4_V2
13392 } else {
13393 crate::QT_NVFP4
13394 };
13395 let width = experts.input_width;
13396 let n_expert = experts.expert_count;
13397 let n_pairs = t * n_used;
13398 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
13399 return Err("NVFP4 grouped prime geometry".into());
13400 }
13401 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
13410 let g_t0 = std::time::Instant::now();
13411 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
13414 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
13415 let s_id = s_id as usize;
13416 if s_id >= n_expert {
13417 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
13418 }
13419 buckets[s_id].push(p as i32);
13420 }
13421 let mut ex_ids: Vec<i32> = Vec::new();
13422 let mut ex_off: Vec<i32> = vec![0];
13423 let mut ex_pairs: Vec<i32> = Vec::new();
13424 for (e_id, b) in buckets.iter().enumerate() {
13425 if !b.is_empty() {
13426 ex_ids.push(e_id as i32);
13427 ex_pairs.extend_from_slice(b);
13428 ex_off.push(ex_pairs.len() as i32);
13429 }
13430 }
13431 let n_active = ex_ids.len();
13432 if n_active == 0 {
13433 return e.zeros(t * width);
13434 }
13435 if n_active > 512 {
13436 return Err("grouped prime n_active > 512 (direct lane cap)".into());
13437 }
13438 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
13439 let mut inv = vec![0i32; n_pairs];
13442 for (row, &pair) in ex_pairs.iter().enumerate() {
13443 inv[pair as usize] = row as i32;
13444 }
13445 let mg: Vec<f32> = ex_pairs
13447 .iter()
13448 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
13449 .collect();
13450 let mu: Vec<f32> = ex_pairs
13451 .iter()
13452 .map(|&p| experts.macros_up[sel[p as usize] as usize])
13453 .collect();
13454 let wd: Vec<f32> = (0..n_pairs)
13455 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
13456 .collect();
13457 {
13461 let mut tabs = experts
13462 .prime_tables
13463 .lock()
13464 .map_err(|_| "grouped prime table cache is poisoned")?;
13465 if tabs.len() != world {
13466 tabs.clear();
13467 for rank in 0..world {
13468 let engine = &self.ranks[rank];
13469 let _main = engine.gpu.enter_main()?;
13470 let (gb, ub, db) =
13471 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
13472 let mut tab = vec![0u64; 3 * n_expert];
13473 {
13474 use cudarc::driver::DevicePtr;
13475 let stream = engine.stream();
13476 let (pg, _g0) = gb.bank.device_ptr(&stream);
13477 let (pu, _g1) = ub.bank.device_ptr(&stream);
13478 let (pd, _g2) = db.bank.device_ptr(&stream);
13479 for ex in 0..n_expert {
13480 tab[ex] = pg + (ex * gb.expert_bytes) as u64;
13481 tab[n_expert + ex] = pu + (ex * ub.expert_bytes) as u64;
13482 tab[2 * n_expert + ex] = pd + (ex * db.expert_bytes) as u64;
13483 }
13484 }
13485 tabs.push(engine.htod_u64(&tab)?);
13486 }
13487 }
13488 }
13489 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
13490 let g_t1 = std::time::Instant::now();
13491 {
13498 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
13499 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13500 for rank in 0..world {
13501 let e_r = &self.ranks[rank];
13502 let _m = e_r.gpu.enter_main();
13503 eprintln!(
13504 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
13505 root_stream={:?}",
13506 e_r.ctx().ordinal(),
13507 std::sync::Arc::as_ptr(e_r.ctx()),
13508 e_r.stream().cu_stream(),
13509 e.ctx().ordinal(),
13510 e.stream().cu_stream(),
13511 );
13512 }
13513 }
13514 }
13515
13516 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
13517 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
13518 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
13519 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
13520 for rank in 0..world {
13521 let engine = &self.ranks[rank];
13522 let _main = engine.gpu.enter_main()?;
13523 if gprof {
13524 let h = engine
13529 .ctx()
13530 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13531 h.record(&engine.stream())?;
13532 ev_head.push(h);
13533 }
13534 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
13537 let gb = &experts.gate[rank];
13538 let ub = &experts.up[rank];
13539 let db = &experts.down[rank];
13540 if db.device_rank != rank {
13541 return Err("grouped prime: down shard placement drifted".into());
13542 }
13543 let local_ff = gb.local_out;
13544 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
13545 return Err("grouped prime: bank width mismatch".into());
13546 }
13547 let csr_tok_d = engine.htod_i32(&csr_tok)?;
13550 let exi_d = engine.htod_i32(&ex_ids)?;
13551 let exoff_d = engine.htod_i32(&ex_off)?;
13552 let mg_d = engine.htod(&mg)?;
13553 let mu_d = engine.htod(&mu)?;
13554 let tabs_guard = experts
13556 .prime_tables
13557 .lock()
13558 .map_err(|_| "grouped prime table cache is poisoned")?;
13559 let tab_d = &tabs_guard[rank];
13560 let mut z_r = engine.uninit(t * width)?;
13561 {
13562 let mut dst = z_r.slice_mut(0..t * width);
13563 engine
13564 .stream()
13565 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
13566 }
13567 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
13568 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
13569 if dstage {
13570 let zr = engine.dtoh(&z_r)?;
13574 let zsv = engine.dtoh(&zs)?;
13575 let z16v = engine.dtoh_u8(&z16)?;
13576 eprintln!(
13577 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
13578 Self::determ_stage_sum(&zr),
13579 Self::determ_stage_sum(&zsv),
13580 Self::determ_stage_bytes(&z16v)
13581 );
13582 }
13583 if dstage {
13584 engine.stream().synchronize()?;
13592 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
13593 let exi_v = engine.dtoh_i32(&exi_d)?;
13594 let exo_v = engine.dtoh_i32(&exoff_d)?;
13595 let mg_v = engine.dtoh(&mg_d)?;
13596 let mu_v = engine.dtoh(&mu_d)?;
13597 let tab_v = engine.dtoh_u64(tab_d)?;
13598 eprintln!(
13599 "[determ-closure] rank={rank} t={t} csr_tok={:016x} exi={:016x} exoff={:016x} ex_off_host={:016x} mg={:016x} mu={:016x} tab={:016x} | n_active={n_active} n_pairs={n_pairs} width={width} local_ff={local_ff} n_expert={n_expert} qt={bank_qt} rb={}",
13600 Self::determ_stage_i32(&csr_v),
13601 Self::determ_stage_i32(&exi_v),
13602 Self::determ_stage_i32(&exo_v),
13603 Self::determ_stage_i32(&ex_off),
13604 Self::determ_stage_sum(&mg_v),
13605 Self::determ_stage_sum(&mu_v),
13606 tab_v
13607 .iter()
13608 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
13609 gb.row_bytes
13610 );
13611 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
13614 let bank_v = engine.dtoh_u8(&gb.bank)?;
13615 eprintln!(
13616 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
13617 Self::determ_stage_bytes(&bank_v),
13618 bank_v.len()
13619 );
13620 }
13621 }
13622 let mut g = engine.moe_f16_grouped(
13623 tab_d,
13624 0,
13625 n_expert,
13626 &exi_d,
13627 &ex_off,
13628 &exoff_d,
13629 &z16,
13630 &zs,
13631 width,
13632 local_ff,
13633 n_active,
13634 n_pairs,
13635 bank_qt,
13636 gb.row_bytes,
13637 )?;
13638 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
13639 let mut u = engine.moe_f16_grouped(
13640 tab_d,
13641 1,
13642 n_expert,
13643 &exi_d,
13644 &ex_off,
13645 &exoff_d,
13646 &z16,
13647 &zs,
13648 width,
13649 local_ff,
13650 n_active,
13651 n_pairs,
13652 bank_qt,
13653 ub.row_bytes,
13654 )?;
13655 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
13656 let act = match activation_limit.filter(|l| *l > 1e-6) {
13660 Some(lim) => {
13661 let mut a = engine.uninit(n_pairs * local_ff)?;
13662 engine.swiglu_clamped_mul_scaled(
13663 &g,
13664 &u,
13665 1.0,
13666 1.0,
13667 lim,
13668 &mut a,
13669 n_pairs * local_ff,
13670 )?;
13671 a
13672 }
13673 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
13674 };
13675 if dstage {
13676 let gv = engine.dtoh(&g)?;
13677 let uv = engine.dtoh(&u)?;
13678 let av = engine.dtoh(&act)?;
13679 let key = (rank, t);
13684 let mut prev_map = DETERM_PREV
13685 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
13686 .lock()
13687 .map_err(|_| "determ prev map poisoned")?;
13688 let shape = match prev_map.get(&key) {
13689 Some(prev) if prev.len() == gv.len() => {
13690 let mut md = 0.0f32;
13691 let mut n_diff = 0usize;
13692 let mut n_big = 0usize;
13693 for (a, b) in prev.iter().zip(gv.iter()) {
13694 let d = (a - b).abs();
13695 if d > 0.0 {
13696 n_diff += 1;
13697 }
13698 if d > 1e-3 {
13699 n_big += 1;
13700 }
13701 if d > md {
13702 md = d;
13703 }
13704 }
13705 format!(
13706 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
13707 gv.len()
13708 )
13709 }
13710 _ => String::new(),
13711 };
13712 prev_map.insert(key, gv.clone());
13713 drop(prev_map);
13714 eprintln!(
13715 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
13716 Self::determ_stage_sum(&gv),
13717 Self::determ_stage_sum(&uv),
13718 Self::determ_stage_sum(&av)
13719 );
13720 }
13721 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
13722 let d_csr = engine.moe_f16_grouped(
13723 tab_d,
13724 2,
13725 n_expert,
13726 &exi_d,
13727 &ex_off,
13728 &exoff_d,
13729 &a16,
13730 &a_s,
13731 local_ff,
13732 width,
13733 n_active,
13734 n_pairs,
13735 bank_qt,
13736 db.row_bytes,
13737 )?;
13738
13739 if dstage {
13742 engine.stream().synchronize()?;
13743 let a16v = engine.dtoh_u8(&a16)?;
13744 let dv = engine.dtoh(&d_csr)?;
13745 eprintln!(
13746 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
13747 Self::determ_stage_bytes(&a16v),
13748 Self::determ_stage_sum(&dv)
13749 );
13750 }
13751 let ev = engine.ctx().new_event(None)?;
13752 ev.record(&engine.stream())?;
13753 if gprof {
13754 let tp = engine
13763 .ctx()
13764 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13765 tp.record(&engine.stream())?;
13766 ev_tail_prof.push(tp);
13767 }
13768 ev_rank.push(ev);
13769 partials.push(d_csr);
13770 }
13771 let _main = e.gpu.enter_main()?;
13772 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
13773 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
13775 let g_t2 = std::time::Instant::now();
13776 for ev in &ev_rank {
13777 e.stream().wait(ev)?;
13778 }
13779 let mut y0 = e.uninit(n_pairs * width)?;
13782 {
13783 let mut dst = y0.slice_mut(0..n_pairs * width);
13784 e.stream()
13785 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
13786 }
13787 let mut y1 = e.uninit(n_pairs * width)?;
13788 {
13789 let mut dst = y1.slice_mut(0..n_pairs * width);
13790 e.stream()
13791 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
13792 }
13793 let inv_d = e.htod_i32(&inv)?;
13794 let wd_d = e.htod(&wd)?;
13795 let mut out = e.uninit(t * width)?;
13796 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
13797 if gprof {
13798 let _ = e.stream().synchronize();
13799 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
13800 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
13808 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
13809 let guard = self.ranks[rank].gpu.enter_main();
13810 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
13811 Ok(v) => span_ms.push(v),
13812 Err(err) => {
13813 static SAID: std::sync::atomic::AtomicBool =
13814 std::sync::atomic::AtomicBool::new(false);
13815 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13816 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
13817 }
13818 span_ms.push(-1.0);
13819 }
13820 }
13821 }
13822 eprintln!(
13823 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
13824 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
13825 span_ms.iter().sum::<f32>(),
13826 span_ms.iter().cloned().fold(0.0f32, f32::max)
13827 );
13828 }
13829 Ok(out)
13830 }
13831
13832 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_routed(
13834 &self,
13835 experts: &ResidentNvfp4TensorParallel,
13836 e: &Engine,
13837 input_dev: &crate::CudaSlice<f32>,
13838 sel_d: &crate::CudaSlice<i32>,
13839 w_d: &crate::CudaSlice<f32>,
13840 experts_per_token: usize,
13841 activation_limit: Option<f32>,
13842 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13843 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
13844 experts,
13845 e,
13846 input_dev,
13847 sel_d,
13848 w_d,
13849 experts_per_token,
13850 activation_limit,
13851 || Ok(()),
13852 )
13853 }
13854
13855 #[allow(clippy::too_many_arguments)]
13861 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
13862 &self,
13863 experts: &ResidentNvfp4TensorParallel,
13864 e: &Engine,
13865 input_dev: &crate::CudaSlice<f32>,
13866 sel_d: &crate::CudaSlice<i32>,
13867 w_d: &crate::CudaSlice<f32>,
13868 experts_per_token: usize,
13869 activation_limit: Option<f32>,
13870 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
13871 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13872 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
13873 experts,
13874 e,
13875 input_dev,
13876 sel_d,
13877 w_d,
13878 experts_per_token,
13879 activation_limit,
13880 pre_join,
13881 None,
13882 )
13883 }
13884
13885 #[allow(clippy::too_many_arguments)]
13890 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
13891 &self,
13892 experts: &ResidentNvfp4TensorParallel,
13893 e: &Engine,
13894 input_dev: &crate::CudaSlice<f32>,
13895 sel_d: &crate::CudaSlice<i32>,
13896 w_d: &crate::CudaSlice<f32>,
13897 experts_per_token: usize,
13898 activation_limit: Option<f32>,
13899 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
13900 post_add: Option<(u64, u64)>,
13901 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13902 if input_dev.len() != experts.input_width {
13903 return Err(format!(
13904 "NVFP4 device-routed input {} != width {}",
13905 input_dev.len(),
13906 experts.input_width
13907 )
13908 .into());
13909 }
13910 let n_sel = experts_per_token;
13911 if sel_d.len() < n_sel || w_d.len() < n_sel {
13912 return Err(format!(
13913 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
13914 sel_d.len(),
13915 w_d.len()
13916 )
13917 .into());
13918 }
13919 let world = self.ranks.len();
13920 if world != NVFP4_CANONICAL_ROW_SHARDS {
13921 return Err(format!(
13922 "NVFP4 device routes require world == canonical shard grid \
13923 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
13924 )
13925 .into());
13926 }
13927 let local_out = if experts.ep2 {
13928 experts.expert_width
13929 } else {
13930 experts.expert_width / world
13931 };
13932
13933 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13934 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13935 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
13936 let started = timing.then(std::time::Instant::now);
13937
13938 let mut workspace_guard = experts
13939 .device_workspace
13940 .lock()
13941 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13942 if workspace_guard.is_none() {
13943 drop(workspace_guard);
13944 let zero = vec![0.0f32; experts.input_width];
13945 let zero_sel = vec![0usize; n_sel];
13946 let zero_w = vec![0.0f32; n_sel];
13947 let _ = self.run_tensor_parallel_routes_nvfp4_device(
13948 experts,
13949 &zero,
13950 &zero_sel,
13951 &zero_w,
13952 n_sel,
13953 activation_limit,
13954 )?;
13955 workspace_guard = experts
13956 .device_workspace
13957 .lock()
13958 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13959 }
13960 let workspace = workspace_guard
13961 .as_mut()
13962 .expect("NVFP4 device routes workspace initialized above");
13963 if workspace.n_sel != n_sel {
13964 return Err(format!(
13965 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
13966 workspace.n_sel
13967 )
13968 .into());
13969 }
13970
13971 if step_tp_graph_enabled()? && step_tp_graph_headroom_ok(e) {
13982 if experts.ep2 {
13983 return Err(
13984 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
13985 co-gated; unset one"
13986 .into(),
13987 );
13988 }
13989 if workspace.dev_route_e.is_none() {
13990 let _main = e.gpu.enter_main()?;
13991 workspace.dev_route_e = Some((
13992 e.htod_i32(&vec![0i32; n_sel])?,
13993 e.htod(&vec![0.0f32; n_sel])?,
13994 ));
13995 }
13996 if workspace.in_stage_e.is_none() {
13997 let _main = e.gpu.enter_main()?;
13998 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
13999 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
14000 }
14001 if workspace.routes_graph.is_none() {
14002 let graph = self.nvfp4_routes_build_graph(
14003 experts,
14004 workspace,
14005 local_out,
14006 n_sel,
14007 activation_limit,
14008 )?;
14009 workspace.routes_graph = Some(graph);
14010 eprintln!(
14011 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
14012 children=3 updates=none performance_claim=false"
14013 );
14014 }
14015 let output = {
14016 let _main = e.gpu.enter_main()?;
14017 {
14018 let (sel_e, w_e) = workspace
14019 .dev_route_e
14020 .as_mut()
14021 .expect("device route staging set above");
14022 {
14023 let mut dst = sel_e.slice_mut(0..n_sel);
14024 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14025 }
14026 {
14027 let mut dst = w_e.slice_mut(0..n_sel);
14028 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14029 }
14030 }
14031 {
14032 let in_stage = workspace
14033 .in_stage_e
14034 .as_mut()
14035 .expect("graph staging set above");
14036 let mut dst = in_stage.slice_mut(0..experts.input_width);
14037 e.stream()
14038 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
14039 }
14040 unsafe {
14041 let r = cudarc::driver::sys::cuGraphLaunch(
14042 workspace
14043 .routes_graph
14044 .as_ref()
14045 .expect("routes graph built above")
14046 .exec,
14047 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
14048 );
14049 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
14050 return Err(format!("routes graph launch: {r:?}").into());
14051 }
14052 }
14053 let mut output = e.uninit(experts.input_width)?;
14054 {
14055 let out_stage = workspace
14056 .out_stage_e
14057 .as_ref()
14058 .expect("graph staging set above");
14059 e.stream().memcpy_dtod(
14060 &out_stage.slice(0..experts.input_width),
14061 &mut output.slice_mut(0..experts.input_width),
14062 )?;
14063 }
14064 output
14065 };
14066 if let Some(started) = started {
14067 use std::sync::atomic::Ordering;
14068 let ns = TIMING_NS
14069 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14070 + started.elapsed().as_nanos() as u64;
14071 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14072 if calls.is_multiple_of(430) {
14073 eprintln!(
14074 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14075 ns as f64 / 1.0e6,
14076 ns as f64 / calls as f64 / 1.0e3,
14077 );
14078 }
14079 }
14080 return Ok(output);
14081 }
14082
14083 if let Some((_, device)) = workspace.ev_entry.as_ref() {
14087 if *device != e.ctx().ordinal() {
14088 return Err("NVFP4 device-routed routes engine changed".into());
14089 }
14090 } else {
14091 let _main = e.gpu.enter_main()?;
14092 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
14093 }
14094 if workspace.dev_route_e.is_none() {
14095 let _main = e.gpu.enter_main()?;
14096 workspace.dev_route_e = Some((
14097 e.htod_i32(&vec![0i32; n_sel])?,
14098 e.htod(&vec![0.0f32; n_sel])?,
14099 ));
14100 }
14101 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
14107 let e_device = e.ctx().ordinal();
14108 let rank1_routed_peek = workspace.rank1_routed;
14110 let stage_needed = !mirror
14111 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
14112 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
14113 });
14114 {
14115 let _main = e.gpu.enter_main()?;
14116 if stage_needed {
14117 let (sel_e, w_e) = workspace
14118 .dev_route_e
14119 .as_mut()
14120 .expect("device route staging set above");
14121 {
14122 let mut dst = sel_e.slice_mut(0..n_sel);
14123 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14124 }
14125 {
14126 let mut dst = w_e.slice_mut(0..n_sel);
14127 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14128 }
14129 }
14130 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14131 ev_entry.record(&e.stream())?;
14132 }
14133 let prestaged = std::mem::take(&mut workspace.prestaged);
14136 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
14137 for (rank_index, engine) in self.ranks.iter().enumerate() {
14138 let _main = engine.gpu.enter_main()?;
14139 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14140 engine.stream().wait(ev_entry)?;
14141 if !prestaged {
14142 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
14143 engine
14144 .stream()
14145 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
14146 }
14147 if !(rank1_routed && rank_index == 1) {
14148 let same_dev = engine.ctx().ordinal() == e_device;
14152 if mirror {
14153 let Nvfp4DeviceRoutesWorkspace {
14156 sel,
14157 route_w,
14158 dev_route_e,
14159 ..
14160 } = &mut *workspace;
14161 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
14162 if same_dev {
14163 (sel_d, w_d)
14164 } else {
14165 let (sel_e, w_e) = dev_route_e
14166 .as_ref()
14167 .expect("device route staging set above");
14168 (sel_e, w_e)
14169 };
14170 engine.moe_sel_w_mirror(
14171 src_sel,
14172 src_w,
14173 &mut sel[rank_index],
14174 &mut route_w[rank_index],
14175 n_sel,
14176 )?;
14177 } else {
14178 let (sel_e, w_e) = workspace
14179 .dev_route_e
14180 .as_ref()
14181 .expect("device route staging set above");
14182 {
14183 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
14184 engine
14185 .stream()
14186 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
14187 }
14188 {
14189 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
14190 engine
14191 .stream()
14192 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
14193 }
14194 }
14195 }
14196 if !prestaged {
14197 let Nvfp4DeviceRoutesWorkspace {
14198 input, in_q, in_d, ..
14199 } = &mut *workspace;
14200 engine.quantize_q8_1_into(
14201 &input[rank_index],
14202 1,
14203 experts.input_width,
14204 &mut in_q[rank_index],
14205 &mut in_d[rank_index],
14206 )?;
14207 }
14208 }
14209 self.nvfp4_routes_batched_sweeps(
14210 experts,
14211 workspace,
14212 &[],
14213 &[],
14214 &[],
14215 local_out,
14216 n_sel,
14217 activation_limit,
14218 true,
14219 )?;
14220
14221 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
14224 let _main = engine.gpu.enter_main()?;
14225 workspace.ev_rank[rank_index].record(&engine.stream())?;
14226 }
14227 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
14230 let mut ticket = 0u32;
14231 if memops {
14232 use cudarc::driver::sys;
14233 if workspace.fence_flags_raw == 0 {
14234 let root = &self.ranks[0];
14235 let _main = root.gpu.enter_main()?;
14236 let mut ptr: sys::CUdeviceptr = 0;
14237 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
14238 if r != sys::CUresult::CUDA_SUCCESS {
14239 return Err(format!("fence flag alloc: {r:?}").into());
14240 }
14241 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
14242 if r != sys::CUresult::CUDA_SUCCESS {
14243 return Err(format!("fence flag memset: {r:?}").into());
14244 }
14245 workspace.fence_flags_raw = ptr as u64;
14246 }
14247 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
14248 ticket = workspace.fence_ticket;
14249 let base = workspace.fence_flags_raw;
14250 if fence_rank1_on() {
14256 let peer = &self.ranks[1];
14257 let _pmain = peer.gpu.enter_main()?;
14258 peer.ring_flag_raw(base, ticket)?;
14259 }
14260 {
14261 let root = &self.ranks[0];
14262 let _main = root.gpu.enter_main()?;
14263 let r = unsafe {
14264 sys::cuStreamWriteValue32_v2(
14265 root.stream().cu_stream() as sys::CUstream,
14266 (base + 4) as sys::CUdeviceptr,
14267 ticket,
14268 0,
14269 )
14270 };
14271 if r != sys::CUresult::CUDA_SUCCESS {
14272 return Err(format!("fence write root: {r:?}").into());
14273 }
14274 }
14275 }
14276 pre_join()?;
14279
14280 if moe_direct_on() && self.ranks.len() == 2 {
14281 let _main = e.gpu.enter_main()?;
14288 if memops {
14289 use cudarc::driver::sys;
14290 let base = workspace.fence_flags_raw;
14291 let r = unsafe {
14292 sys::cuStreamWaitValue32_v2(
14293 e.stream().cu_stream() as sys::CUstream,
14294 (base + 4) as sys::CUdeviceptr,
14295 ticket,
14296 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14297 )
14298 };
14299 if r != sys::CUresult::CUDA_SUCCESS {
14300 return Err(format!("fence wait: {r:?}").into());
14301 }
14302 if fence_rank1_on() {
14303 let r = unsafe {
14305 sys::cuStreamWaitValue32_v2(
14306 e.stream().cu_stream() as sys::CUstream,
14307 base as sys::CUdeviceptr,
14308 ticket,
14309 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14310 )
14311 };
14312 if r != sys::CUresult::CUDA_SUCCESS {
14313 return Err(format!("fence wait rank1: {r:?}").into());
14314 }
14315 } else {
14316 for ev in workspace.ev_rank.iter().skip(1) {
14317 e.stream().wait(ev)?;
14318 }
14319 }
14320 } else {
14321 {
14322 let root = &self.ranks[0];
14323 let _rmain = root.gpu.enter_main()?;
14324 workspace
14325 .ev_done
14326 .as_ref()
14327 .expect("device routes done event")
14328 .record(&root.stream())?;
14329 }
14330 e.stream().wait(
14331 workspace
14332 .ev_done
14333 .as_ref()
14334 .expect("device routes done event"),
14335 )?;
14336 for ev in workspace.ev_rank.iter().skip(1) {
14337 e.stream().wait(ev)?;
14338 }
14339 }
14340 let mut output = e.uninit(experts.input_width)?;
14341 if let Some((sh_raw, scale_raw)) = post_add {
14342 e.add3_raw(
14345 &workspace.accumulator[0],
14346 &workspace.accumulator[1],
14347 sh_raw,
14348 scale_raw,
14349 &mut output,
14350 experts.input_width,
14351 )?;
14352 } else {
14353 e.add(
14354 &workspace.accumulator[0],
14355 &workspace.accumulator[1],
14356 &mut output,
14357 experts.input_width,
14358 )?;
14359 }
14360 let output = output;
14361 if let Some(started) = started {
14362 use std::sync::atomic::Ordering;
14363 let ns = TIMING_NS
14364 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14365 + started.elapsed().as_nanos() as u64;
14366 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14367 if calls.is_multiple_of(430) {
14368 eprintln!(
14369 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14370 ns as f64 / 1.0e6,
14371 ns as f64 / calls as f64 / 1.0e3,
14372 );
14373 }
14374 }
14375 return Ok(output);
14376 }
14377 {
14378 let root = &self.ranks[0];
14379 let _main = root.gpu.enter_main()?;
14380 for ev in workspace.ev_rank.iter().skip(1) {
14381 root.stream().wait(ev)?;
14382 }
14383 root.stream()
14384 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
14385 {
14386 let Nvfp4DeviceRoutesWorkspace {
14387 accumulator,
14388 remote,
14389 combined,
14390 ..
14391 } = &mut *workspace;
14392 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14393 }
14394 workspace
14395 .ev_done
14396 .as_ref()
14397 .expect("device routes done event")
14398 .record(&root.stream())?;
14399 }
14400 let output = {
14401 let _main = e.gpu.enter_main()?;
14402 e.stream().wait(
14403 workspace
14404 .ev_done
14405 .as_ref()
14406 .expect("device routes done event"),
14407 )?;
14408 let mut output = e.uninit(experts.input_width)?;
14411 e.stream().memcpy_dtod(
14412 &workspace.combined.slice(0..experts.input_width),
14413 &mut output.slice_mut(0..experts.input_width),
14414 )?;
14415 output
14416 };
14417 if let Some(started) = started {
14418 use std::sync::atomic::Ordering;
14419 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14420 + started.elapsed().as_nanos() as u64;
14421 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14422 if calls.is_multiple_of(430) {
14423 eprintln!(
14424 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14425 ns as f64 / 1.0e6,
14426 ns as f64 / calls as f64 / 1.0e3,
14427 );
14428 }
14429 }
14430 Ok(output)
14431 }
14432
14433 pub(crate) fn decode_v2_finish_root_fused(
14437 &self,
14438 ws: &mut StepTpDecodeV2Ws,
14439 ) -> Result<(), Box<dyn std::error::Error>> {
14440 let root = &self.ranks[0];
14441 let _main = root.gpu.enter_main()?;
14442 if ws.raw_peer_partial != 0 {
14443 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
14445 } else {
14446 root.stream()
14447 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
14448 }
14449 {
14450 let StepTpDecodeV2Ws {
14451 o_partials,
14452 peer_partial,
14453 reduce_a,
14454 o_out,
14455 ..
14456 } = &mut *ws;
14457 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
14458 }
14459 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
14460 if shadows {
14461 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
14464 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
14465 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
14466 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
14467 }
14468 if shadows && ws.raw_peer_partial != 0 {
14469 raw_copy_bytes(
14470 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
14471 ws.raw_k1,
14472 ws.local_kv_dim * 4,
14473 root,
14474 )?;
14475 raw_copy_bytes(
14476 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
14477 ws.raw_v1,
14478 ws.local_kv_dim * 4,
14479 root,
14480 )?;
14481 } else if shadows {
14482 let start = ws.local_kv_dim;
14483 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
14484 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
14485 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
14486 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
14487 }
14488 if ws.raw_mixed_stage_e != 0 {
14489 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
14492 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
14493 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
14494 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
14495 }
14496 Ok(())
14497 }
14498
14499 pub(crate) fn decode_v2_arm_token_mirrors(
14502 &self,
14503 ws: &mut StepTpDecodeV2Ws,
14504 mixed_stage_e: u64,
14505 shadow_stage_e: (u64, u64),
14506 ) -> Result<(), Box<dyn std::error::Error>> {
14507 use cudarc::driver::DevicePtr;
14508 let root = &self.ranks[0];
14509 let _main = root.gpu.enter_main()?;
14510 let stream = root.stream();
14511 let (a, _g) = ws.reduce_a.device_ptr(&stream);
14512 ws.raw_reduce_a = a;
14513 ws.raw_mixed_stage_e = mixed_stage_e;
14514 ws.raw_shadow_stage_e = shadow_stage_e;
14515 Ok(())
14516 }
14517
14518 fn nvfp4_routes_build_graph(
14524 &self,
14525 experts: &ResidentNvfp4TensorParallel,
14526 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14527 local_out: usize,
14528 n_sel: usize,
14529 activation_limit: Option<f32>,
14530 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
14531 use cudarc::driver::DevicePtr;
14532 use cudarc::driver::sys;
14533 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
14534 if r == sys::CUresult::CUDA_SUCCESS {
14535 Ok(())
14536 } else {
14537 Err(format!("{what}: {r:?}").into())
14538 }
14539 }
14540 let world = self.ranks.len();
14541 if world != 2 {
14542 return Err("routes graph door is built for the TP2 pair".into());
14543 }
14544 let width = experts.input_width;
14545
14546 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
14548 let stream = engine.stream();
14549 let (ptr, _g) = buf.device_ptr(&stream);
14550 ptr
14551 };
14552 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
14553 let stream = engine.stream();
14554 let (ptr, _g) = buf.device_ptr(&stream);
14555 ptr
14556 };
14557 let (sel_e, w_e) = workspace
14558 .dev_route_e
14559 .as_ref()
14560 .expect("device route staging set before graph build");
14561 let root_engine = &self.ranks[0];
14562 let p_in_stage = ptr_f32(
14563 workspace.in_stage_e.as_ref().expect("graph staging"),
14564 root_engine,
14565 );
14566 let p_out_stage = ptr_f32(
14567 workspace.out_stage_e.as_ref().expect("graph staging"),
14568 root_engine,
14569 );
14570 let p_sel_e = ptr_i32(sel_e, root_engine);
14571 let p_w_e = ptr_f32(w_e, root_engine);
14572 let p_input: Vec<u64> = (0..world)
14573 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
14574 .collect();
14575 let p_sel: Vec<u64> = (0..world)
14576 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
14577 .collect();
14578 let p_route_w: Vec<u64> = (0..world)
14579 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
14580 .collect();
14581 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
14582 let p_remote = ptr_f32(&workspace.remote, root_engine);
14583 let p_combined = ptr_f32(&workspace.combined, root_engine);
14584
14585 let raw_copy = |dst: u64,
14586 src: u64,
14587 bytes: usize,
14588 engine: &Engine|
14589 -> Result<(), Box<dyn std::error::Error>> {
14590 unsafe {
14591 cu_try(
14592 sys::cuMemcpyAsync(
14593 dst as sys::CUdeviceptr,
14594 src as sys::CUdeviceptr,
14595 bytes,
14596 engine.stream().cu_stream() as sys::CUstream,
14597 ),
14598 "routes graph cuMemcpyAsync",
14599 )
14600 }
14601 };
14602
14603 let mut children = Vec::with_capacity(3);
14604 for rank in 0..world {
14605 let engine = &self.ranks[rank];
14606 let _main = engine.gpu.enter_main()?;
14607 let (child, _retained) = engine.capture_graph_retained(|_| {
14608 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
14609 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
14610 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
14611 {
14612 let Nvfp4DeviceRoutesWorkspace {
14613 input, in_q, in_d, ..
14614 } = &mut *workspace;
14615 engine.quantize_q8_1_into(
14616 &input[rank],
14617 1,
14618 width,
14619 &mut in_q[rank],
14620 &mut in_d[rank],
14621 )?;
14622 }
14623 self.nvfp4_routes_batched_sweeps_rank(
14624 experts,
14625 workspace,
14626 &[],
14627 &[],
14628 &[],
14629 local_out,
14630 n_sel,
14631 activation_limit,
14632 true,
14633 rank,
14634 )?;
14635 Ok(())
14636 })?;
14637 children.push(child);
14638 }
14639 {
14640 let root = &self.ranks[0];
14641 let _main = root.gpu.enter_main()?;
14642 let (child, _retained) = root.capture_graph_retained(|_| {
14643 raw_copy(p_remote, p_acc1, width * 4, root)?;
14644 {
14645 let Nvfp4DeviceRoutesWorkspace {
14646 accumulator,
14647 remote,
14648 combined,
14649 ..
14650 } = &mut *workspace;
14651 root.add(&accumulator[0], remote, combined, width)?;
14652 }
14653 raw_copy(p_out_stage, p_combined, width * 4, root)?;
14654 Ok(())
14655 })?;
14656 children.push(child);
14657 }
14658
14659 let mut parent: sys::CUgraph = std::ptr::null_mut();
14660 unsafe {
14661 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
14662 }
14663 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
14664 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
14665 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
14666 unsafe {
14667 cu_try(
14668 sys::cuGraphAddChildGraphNode(
14669 &mut n0,
14670 parent,
14671 std::ptr::null(),
14672 0,
14673 children[0].cu_graph(),
14674 ),
14675 "routes child r0",
14676 )?;
14677 cu_try(
14678 sys::cuGraphAddChildGraphNode(
14679 &mut n1,
14680 parent,
14681 std::ptr::null(),
14682 0,
14683 children[1].cu_graph(),
14684 ),
14685 "routes child r1",
14686 )?;
14687 let deps = [n0, n1];
14688 cu_try(
14689 sys::cuGraphAddChildGraphNode(
14690 &mut n2,
14691 parent,
14692 deps.as_ptr(),
14693 2,
14694 children[2].cu_graph(),
14695 ),
14696 "routes child root",
14697 )?;
14698 }
14699 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
14700 unsafe {
14701 cu_try(
14702 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
14703 "routes instantiate",
14704 )?;
14705 }
14706 Ok(RoutesGraph {
14707 exec,
14708 parent,
14709 _children: children,
14710 })
14711 }
14712
14713 #[allow(clippy::too_many_arguments)]
14717 pub(crate) fn routes_rank_section(
14718 &self,
14719 experts: &ResidentNvfp4TensorParallel,
14720 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14721 raw_input_src: u64,
14722 local_out: usize,
14723 n_sel: usize,
14724 activation_limit: Option<f32>,
14725 rank_index: usize,
14726 ) -> Result<(), Box<dyn std::error::Error>> {
14727 let engine = &self.ranks[rank_index];
14728 {
14729 let _main = engine.gpu.enter_main()?;
14730 let (sel_e_ptr, w_e_ptr) = workspace
14732 .raw_dev_route_e
14733 .ok_or("routes rank section requires armed staging pointers")?;
14734 raw_copy_bytes(
14735 workspace.raw_input[rank_index],
14736 raw_input_src,
14737 experts.input_width * 4,
14738 engine,
14739 )?;
14740 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
14741 raw_copy_bytes(
14742 workspace.raw_route_w[rank_index],
14743 w_e_ptr,
14744 n_sel * 4,
14745 engine,
14746 )?;
14747 {
14748 let Nvfp4DeviceRoutesWorkspace {
14749 input, in_q, in_d, ..
14750 } = &mut *workspace;
14751 engine.quantize_q8_1_into(
14752 &input[rank_index],
14753 1,
14754 experts.input_width,
14755 &mut in_q[rank_index],
14756 &mut in_d[rank_index],
14757 )?;
14758 }
14759 }
14760 self.nvfp4_routes_batched_sweeps_rank(
14761 experts,
14762 workspace,
14763 &[],
14764 &[],
14765 &[],
14766 local_out,
14767 n_sel,
14768 activation_limit,
14769 true,
14770 rank_index,
14771 )
14772 }
14773
14774 pub(crate) fn routes_root_section(
14777 &self,
14778 experts: &ResidentNvfp4TensorParallel,
14779 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14780 ) -> Result<(), Box<dyn std::error::Error>> {
14781 let root = &self.ranks[0];
14782 let _main = root.gpu.enter_main()?;
14783 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
14784 .raw_combine
14785 .ok_or("routes root section requires armed combine pointers")?;
14786 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
14787 {
14788 let Nvfp4DeviceRoutesWorkspace {
14789 accumulator,
14790 remote,
14791 combined,
14792 ..
14793 } = &mut *workspace;
14794 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14795 }
14796 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
14797 Ok(())
14798 }
14799
14800 pub(crate) fn routes_arm_raw(
14803 &self,
14804 experts: &ResidentNvfp4TensorParallel,
14805 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14806 ) -> Result<(), Box<dyn std::error::Error>> {
14807 use cudarc::driver::DevicePtr;
14808 if workspace.raw_dev_route_e.is_some() {
14809 return Ok(());
14810 }
14811 let _ = experts;
14812 let (sel_e, w_e) = workspace
14813 .dev_route_e
14814 .as_ref()
14815 .ok_or("routes staging not armed")?;
14816 let root = &self.ranks[0];
14817 {
14818 let _main = root.gpu.enter_main()?;
14819 let stream = root.stream();
14820 let (a, _g) = sel_e.device_ptr(&stream);
14821 let (b, _g) = w_e.device_ptr(&stream);
14822 workspace.raw_dev_route_e = Some((a, b));
14823 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
14824 let (d, _g) = workspace.remote.device_ptr(&stream);
14825 let (f, _g) = workspace.combined.device_ptr(&stream);
14826 let out_stage = workspace
14827 .out_stage_e
14828 .as_ref()
14829 .ok_or("routes out stage not armed")?;
14830 let (g_, _g) = out_stage.device_ptr(&stream);
14831 workspace.raw_combine = Some((c, d, f, g_));
14832 }
14833 for rank in 0..self.ranks.len() {
14834 let engine = &self.ranks[rank];
14835 let _main = engine.gpu.enter_main()?;
14836 let stream = engine.stream();
14837 let (a, _g) = workspace.input[rank].device_ptr(&stream);
14838 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
14839 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
14840 workspace.raw_input.push(a);
14841 workspace.raw_sel.push(b);
14842 workspace.raw_route_w.push(c);
14843 }
14844 Ok(())
14845 }
14846
14847 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4(
14852 &self,
14853 experts: &ResidentNvfp4TensorParallel,
14854 input: &[f32],
14855 tokens: usize,
14856 selected: &[usize],
14857 route_weights: &[f32],
14858 experts_per_token: usize,
14859 activation_limit: Option<f32>,
14860 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
14861 validate_activations(input, tokens, experts.input_width)?;
14862 let pairs = tokens
14863 .checked_mul(experts_per_token)
14864 .ok_or("NVFP4 TP route count overflow")?;
14865 if selected.len() != pairs || route_weights.len() != pairs {
14866 return Err(format!(
14867 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
14868 {experts_per_token} ({pairs})",
14869 selected.len(),
14870 route_weights.len(),
14871 )
14872 .into());
14873 }
14874 if !route_weights.iter().all(|weight| weight.is_finite()) {
14875 return Err("NVFP4 TP route weights contain a non-finite value".into());
14876 }
14877
14878 let mut output = vec![0.0f32; tokens * experts.input_width];
14879 for token in 0..tokens {
14880 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
14881 for slot in 0..experts_per_token {
14882 let pair = token * experts_per_token + slot;
14883 let expert = selected[pair];
14884 if expert >= experts.expert_count {
14885 return Err(format!(
14886 "NVFP4 TP selected expert {expert} outside 0..{}",
14887 experts.expert_count
14888 )
14889 .into());
14890 }
14891 let gate = if experts.ep2 {
14897 self.run_full_bank_expert_nvfp4(
14898 &experts.gate,
14899 &experts.macros_gate,
14900 expert,
14901 input_row,
14902 )?
14903 } else {
14904 self.run_column_bank_expert_nvfp4(
14905 &experts.gate,
14906 &experts.macros_gate,
14907 expert,
14908 input_row,
14909 )?
14910 };
14911 let up = if experts.ep2 {
14912 self.run_full_bank_expert_nvfp4(
14913 &experts.up,
14914 &experts.macros_up,
14915 expert,
14916 input_row,
14917 )?
14918 } else {
14919 self.run_column_bank_expert_nvfp4(
14920 &experts.up,
14921 &experts.macros_up,
14922 expert,
14923 input_row,
14924 )?
14925 };
14926 let activated: Vec<f32> = gate
14927 .iter()
14928 .zip(&up)
14929 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
14930 .collect();
14931 debug_assert_eq!(activated.len(), experts.expert_width);
14932 let down = if experts.ep2 {
14933 self.run_full_down_expert_nvfp4(
14934 &experts.down,
14935 &experts.macros_down,
14936 expert,
14937 &activated,
14938 )?
14939 } else {
14940 self.run_row_bank_expert_nvfp4(
14941 &experts.down,
14942 &experts.macros_down,
14943 expert,
14944 &activated,
14945 )?
14946 };
14947 let weight = route_weights[pair];
14948 for (sum, value) in output
14949 [token * experts.input_width..(token + 1) * experts.input_width]
14950 .iter_mut()
14951 .zip(down)
14952 {
14953 *sum += weight * value;
14954 }
14955 }
14956 }
14957 Ok(output)
14958 }
14959}
14960
14961#[cfg(test)]
14962mod default_on_door_tests {
14963 use super::door_default_on_value;
14964
14965 #[test]
14976 fn the_default_on_door_parses_every_state_and_names_its_source() {
14977 assert_eq!(
14980 door_default_on_value("MEMRA_TEST_DOOR", None),
14981 (true, "default-on")
14982 );
14983 assert_eq!(
14985 door_default_on_value("MEMRA_TEST_DOOR", Some("1")),
14986 (true, "env=1")
14987 );
14988 assert_eq!(
14990 door_default_on_value("MEMRA_TEST_DOOR", Some("0")),
14991 (false, "env=0 (rollback seam)")
14992 );
14993 for bad in [
14998 "false", "off", "no", "", " 0", "0 ", "00", "true", "2", "-1",
14999 ] {
15000 let (on, source) = door_default_on_value("MEMRA_TEST_DOOR", Some(bad));
15001 assert!(on, "value {bad:?} must NOT disarm a default-ON door");
15002 assert!(
15003 source.contains("default-on") && source.contains("unrecognized"),
15004 "value {bad:?} gave source {source:?}, which does not announce itself as an \
15005 ignored value — a receipt reader would take it for a clean default"
15006 );
15007 }
15008 }
15009}
15010
15011#[cfg(test)]
15012mod bank_v2_layout_tests {
15013 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
15014
15015 #[test]
15025 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
15026 let (out_f, in_f) = (2usize, 128usize);
15028 let row_bytes = nvfp4_row_bytes(in_f);
15029 assert_eq!(row_bytes, 72);
15030 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
15031 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
15032 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
15033 let n_slots = in_f / 32;
15034 for row in 0..out_f {
15035 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
15036 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
15037 for g in 0..n_slots {
15038 let (sblk, h) = (g / 2, g % 2);
15039 let sb = &src[sblk * 36..sblk * 36 + 36];
15040 assert_eq!(
15041 &dst[g * 16..g * 16 + 16],
15042 &sb[4 + 16 * h..4 + 16 * h + 16],
15043 "row {row} slot {g} codes"
15044 );
15045 assert_eq!(
15046 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
15047 &sb[2 * h..2 * h + 2],
15048 "row {row} slot {g} scales"
15049 );
15050 }
15051 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
15053 a.sort_unstable();
15054 b.sort_unstable();
15055 assert_eq!(a, b, "row {row} is not a byte permutation");
15056 }
15057 }
15058}
15059
15060#[cfg(test)]
15061mod tests {
15062
15063 #[test]
15064 fn door_composition_refuses_first_armed_flag_by_name() {
15065 let table: [(&str, &str); 2] = [
15066 ("MEMRA_DOOR_A", "gated on the unsharded walk only"),
15067 ("MEMRA_DOOR_B", "no sharded branches"),
15068 ];
15069 super::refuse_door_composition("MEMRA_X_TP", &table, |_| false).expect("cold doors pass");
15071 let err = super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_B")
15073 .expect_err("armed door must refuse");
15074 assert_eq!(
15075 err,
15076 "MEMRA_X_TP + MEMRA_DOOR_B: unproven composition, refused (no sharded branches)"
15077 );
15078 super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_C")
15080 .expect("foreign flags are not the matrix");
15081 }
15082
15083 #[test]
15090 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
15091 let (kp, bp) = (0xdead_0000u64, 0u64);
15092 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
15093 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
15094 assert_eq!(
15095 super::retired_rows_tab_key(kp, bp, 20, 2),
15096 super::retired_rows_tab_key(kp, bp, 20, 2),
15097 "same layer and t must hash the same, or the test proves nothing"
15098 );
15099 let a = super::rows_tab_host(&live, 0x9000, true, 1);
15100 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
15101 assert_ne!(a, b, "the two generations write DIFFERENT tables");
15102 assert_eq!(
15104 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
15105 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
15106 "the retired key collides across allocation generations"
15107 );
15108 }
15109
15110 #[test]
15113 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
15114 let parts = [
15115 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
15116 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
15117 ];
15118 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
15119 assert_eq!(
15120 same,
15121 vec![
15122 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
15123 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
15126 "same-session rows share one counter cell and step back t-1-r"
15127 );
15128 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
15129 assert_eq!(
15130 cross,
15131 vec![
15132 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
15133 0x00c1u64, 0x00d1u64, 0x7004, 0,
15134 ],
15135 "cross-session rows get their own counter cell and no step back"
15136 );
15137 }
15138 use super::*;
15139
15140 #[test]
15141 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
15142 let limit = Some(7.0);
15143 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
15144 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
15145 assert!(
15146 step_expert_activation_host(-20.0, 9.0, limit).abs()
15147 < step_expert_activation_host(-20.0, 9.0, None).abs()
15148 );
15149 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
15150 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
15151 assert!(validate_step_expert_activation_limit(limit).is_ok());
15152 }
15153
15154 #[test]
15155 fn moe_residual_host_preserves_official_add_order() {
15156 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
15157 assert_eq!(output, [0.0]);
15158 assert_eq!(
15159 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
15160 "MoE residual lengths residual=1 routed=2 shared=1"
15161 );
15162 }
15163
15164 #[test]
15165 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
15166 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
15167 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
15168 assert_eq!(owners.len(), 4);
15169 for (rank, owner) in owners.iter().enumerate() {
15170 assert_eq!(owner.rank, rank);
15171 assert_eq!(owner.selected, vec![0, 36]);
15172 assert_eq!(owner.token_rows, vec![0, 0]);
15173 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
15174 }
15175 }
15176
15177 #[test]
15178 fn expert_owner_routes_validate_geometry_and_selected_experts() {
15179 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
15180 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
15181 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
15182 assert!(error.contains("outside 0..288"));
15183 }
15184
15185 #[test]
15186 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
15187 let selected = [
15188 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
15189 ];
15190 assert_eq!(
15191 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
15192 16
15193 );
15194 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
15195 assert_eq!(
15196 owners
15197 .iter()
15198 .map(|owner| owner.selected.len())
15199 .collect::<Vec<_>>(),
15200 vec![2, 4, 6, 4]
15201 );
15202 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
15203 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
15204 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
15205 }
15206
15207 #[test]
15208 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
15209 let owner0 = [0usize, 3];
15210 let owner1 = [1usize, 2];
15211 let owners = [owner0.as_slice(), owner1.as_slice()];
15212 assert_eq!(
15213 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
15214 .unwrap(),
15215 WeightedRouteCombineShape {
15216 pairs: 4,
15217 max_pairs: 12,
15218 }
15219 );
15220 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
15221 assert!(
15222 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
15223 .is_err()
15224 );
15225 assert!(
15226 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
15227 .is_err()
15228 );
15229 assert!(
15230 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
15231 .is_err()
15232 );
15233 }
15234
15235 #[test]
15236 fn native_p2p_door_is_strict_and_default_off() {
15237 assert!(!parse_step_tp_native_p2p(None).unwrap());
15238 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
15239 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
15240 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
15241 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
15242 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
15243 }
15244
15245 #[test]
15246 fn bulk_p2p_door_is_strict_and_default_off() {
15247 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
15248 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
15249 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
15250 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
15251 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
15252 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
15253 }
15254
15255 #[test]
15256 fn ep_device_arithmetic_door_is_strict_and_default_off() {
15257 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
15258 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
15259 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
15260 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
15261 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
15262 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
15263 }
15264
15265 #[test]
15266 fn f32_mirror_door_is_strict_and_default_off() {
15267 assert!(!parse_step_tp_f32_mirror(None).unwrap());
15268 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
15269 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
15270 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
15271 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
15272 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
15273 }
15274
15275 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
15276 let codes = (0..out_features * in_features)
15277 .map(|index| (index % 251) as u8)
15278 .collect();
15279 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
15280 .map(|index| index as f32 + 1.0)
15281 .collect();
15282 (codes, scales)
15283 }
15284
15285 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
15286 (0..out_features * in_features)
15287 .flat_map(|value| (value as u16).to_le_bytes())
15288 .collect()
15289 }
15290
15291 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
15292 bytes
15293 .chunks_exact(2)
15294 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
15295 .collect()
15296 }
15297
15298 #[test]
15299 fn bf16_matrix_rejects_wrong_byte_count() {
15300 let bytes = vec![0u8; 4 * 4 * 2 - 1];
15301 let matrix = Bf16Matrix {
15302 bytes: &bytes,
15303 out_features: 4,
15304 in_features: 4,
15305 };
15306 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
15307 }
15308
15309 #[test]
15310 fn replicated_device_rows_require_exact_rank_local_shapes() {
15311 assert_eq!(
15312 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
15313 12_288
15314 );
15315 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
15316 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
15317 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
15318 assert!(
15319 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
15320 );
15321 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
15322 }
15323
15324 #[test]
15325 fn replicated_device_row_refresh_requires_exact_root_source() {
15326 assert_eq!(
15327 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
15328 12_288
15329 );
15330 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
15331 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
15332 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
15333 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
15334 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
15335 }
15336
15337 #[test]
15338 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
15339 for tp in [1, 2, 4, 8] {
15340 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
15341 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
15342 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
15343 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
15344 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
15345 }
15346 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
15347 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
15348 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
15349 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
15350 }
15351
15352 #[test]
15353 fn cache_rows_split_by_token_then_rank() {
15354 let rows = (0u8..24).collect::<Vec<_>>();
15355 assert_eq!(
15356 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
15357 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
15358 );
15359 assert_eq!(
15360 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
15361 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
15362 );
15363 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
15364 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
15365 }
15366
15367 #[test]
15368 fn bf16_column_shard_preserves_contiguous_output_rows() {
15369 let bytes = bf16_matrix_bytes(4, 4);
15370 let matrix = Bf16Matrix {
15371 bytes: &bytes,
15372 out_features: 4,
15373 in_features: 4,
15374 };
15375 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
15376 assert_eq!(shard.out_features, 2);
15377 assert_eq!(shard.in_features, 4);
15378 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
15379 }
15380
15381 #[test]
15382 fn bf16_row_shard_preserves_each_input_column_window() {
15383 let bytes = bf16_matrix_bytes(3, 4);
15384 let matrix = Bf16Matrix {
15385 bytes: &bytes,
15386 out_features: 3,
15387 in_features: 4,
15388 };
15389 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
15390 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
15391 }
15392
15393 #[test]
15394 fn bf16_row_block_preserves_global_column_order() {
15395 let bytes = bf16_matrix_bytes(3, 8);
15396 let matrix = Bf16Matrix {
15397 bytes: &bytes,
15398 out_features: 3,
15399 in_features: 8,
15400 };
15401 let block = bf16_row_block(matrix, 2, 3).unwrap();
15402 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
15403 }
15404
15405 #[test]
15406 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
15407 let (codes, scales) = matrix(1280, 4096);
15408 let matrix = E4m3BlockMatrix {
15409 codes: &codes,
15410 scales: &scales,
15411 out_features: 1280,
15412 in_features: 4096,
15413 };
15414 let shard = column_shard(matrix, 2, 1).unwrap();
15415 assert_eq!(shard.out_features, 640);
15416 assert_eq!(shard.codes, &codes[640 * 4096..]);
15417 assert_eq!(shard.scales, &scales[5 * 32..]);
15418 }
15419
15420 #[test]
15421 fn row_shard_preserves_each_weight_and_scale_column_window() {
15422 let (codes, scales) = matrix(4096, 1280);
15423 let matrix = E4m3BlockMatrix {
15424 codes: &codes,
15425 scales: &scales,
15426 out_features: 4096,
15427 in_features: 1280,
15428 };
15429 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
15430 assert_eq!(shard_codes.len(), 4096 * 640);
15431 assert_eq!(&shard_codes[..640], &codes[640..1280]);
15432 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
15433 assert_eq!(shard_scales.len(), 32 * 5);
15434 assert_eq!(&shard_scales[..5], &scales[5..10]);
15435 assert_eq!(&shard_scales[5..10], &scales[15..20]);
15436 }
15437
15438 #[test]
15439 fn activation_shards_keep_token_rows_separate() {
15440 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
15441 assert_eq!(
15442 activation_shard(&activations, 2, 8, 2, 1),
15443 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
15444 );
15445 }
15446
15447 #[test]
15448 fn expert_bank_selects_expert_major_code_and_scale_planes() {
15449 let expert_count = 2;
15450 let out_features = 128;
15451 let in_features = 128;
15452 let code_stride = out_features * in_features;
15453 let codes: Vec<u8> = (0..expert_count * code_stride)
15454 .map(|index| (index % 251) as u8)
15455 .collect();
15456 let scales = vec![1.0f32, 2.0];
15457 let bank = E4m3ExpertBank {
15458 codes: &codes,
15459 scales: &scales,
15460 expert_count,
15461 out_features,
15462 in_features,
15463 };
15464 bank.validate().unwrap();
15465 let expert = bank.expert(1).unwrap();
15466 assert_eq!(expert.codes, &codes[code_stride..]);
15467 assert_eq!(expert.scales, &[2.0]);
15468 }
15469
15470 #[test]
15471 fn expert_bank_rejects_non_positive_scale() {
15472 let codes = vec![0u8; 128 * 128];
15473 let scales = vec![0.0f32];
15474 let bank = E4m3ExpertBank {
15475 codes: &codes,
15476 scales: &scales,
15477 expert_count: 1,
15478 out_features: 128,
15479 in_features: 128,
15480 };
15481 assert!(bank.validate().unwrap_err().contains("non-positive"));
15482 }
15483
15484 #[test]
15485 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
15486 let expert_count = 2;
15487 let out_features = 256;
15488 let in_features = 128;
15489 let code_stride = out_features * in_features;
15490 let scale_stride = 2;
15491 let codes = (0..expert_count * code_stride)
15492 .map(|index| (index % 251) as u8)
15493 .collect::<Vec<_>>();
15494 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15495 let bank = E4m3ExpertBank {
15496 codes: &codes,
15497 scales: &scales,
15498 expert_count,
15499 out_features,
15500 in_features,
15501 };
15502
15503 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
15504 assert_eq!(rank.out_features, 128);
15505 assert_eq!(rank.in_features, 128);
15506 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15507 assert_eq!(rank.scales, vec![11.0, 21.0]);
15508 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
15509 assert_eq!(
15510 &rank.codes[128 * 128..],
15511 &codes[code_stride + 128 * 128..2 * code_stride]
15512 );
15513 assert_eq!(scale_stride, scales.len() / expert_count);
15514 }
15515
15516 #[test]
15517 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
15518 let expert_count = 2;
15519 let out_features = 128;
15520 let in_features = 256;
15521 let code_stride = out_features * in_features;
15522 let codes = (0..expert_count * code_stride)
15523 .map(|index| (index % 251) as u8)
15524 .collect::<Vec<_>>();
15525 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15526 let bank = E4m3ExpertBank {
15527 codes: &codes,
15528 scales: &scales,
15529 expert_count,
15530 out_features,
15531 in_features,
15532 };
15533
15534 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15535 assert_eq!(rank.out_features, 128);
15536 assert_eq!(rank.in_features, 128);
15537 assert_eq!(rank.k_blocks, Some(1));
15538 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15539 assert_eq!(rank.scales, vec![11.0, 21.0]);
15540 assert_eq!(&rank.codes[..128], &codes[128..256]);
15541 assert_eq!(
15542 &rank.codes[128 * 128..128 * 128 + 128],
15543 &codes[code_stride + 128..code_stride + 256]
15544 );
15545 }
15546
15547 #[test]
15548 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
15549 let expert_count = 2;
15550 let out_features = 256;
15551 let in_features = 512;
15552 let code_stride = out_features * in_features;
15553 let mut codes = vec![0u8; expert_count * code_stride];
15554 for expert in 0..expert_count {
15555 for row in 0..out_features {
15556 for block in 0..4 {
15557 let value = (expert * 80 + block * 16 + row % 16) as u8;
15558 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
15559 codes[start..start + FP8_BLOCK].fill(value);
15560 }
15561 }
15562 }
15563 let scales = vec![
15564 1.0f32, 2.0, 3.0, 4.0, 11.0, 12.0, 13.0, 14.0, 101.0, 102.0, 103.0, 104.0, 111.0,
15565 112.0, 113.0, 114.0,
15566 ];
15567 let bank = E4m3ExpertBank {
15568 codes: &codes,
15569 scales: &scales,
15570 expert_count,
15571 out_features,
15572 in_features,
15573 };
15574
15575 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15576 assert_eq!(rank.out_features, out_features);
15577 assert_eq!(rank.in_features, 256);
15578 assert_eq!(rank.k_blocks, Some(2));
15579 assert_eq!(rank.code_stride, out_features * 256);
15580 assert_eq!(rank.scale_stride, 4);
15581 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
15582 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
15583
15584 let block_stride = out_features * FP8_BLOCK;
15585 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
15586 assert!(
15587 rank.codes[block_stride..block_stride + FP8_BLOCK]
15588 .iter()
15589 .all(|&code| code == 48)
15590 );
15591 assert!(
15592 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
15593 .iter()
15594 .all(|&code| code == 112)
15595 );
15596 assert!(
15597 rank.codes
15598 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
15599 .iter()
15600 .all(|&code| code == 128)
15601 );
15602 }
15603
15604 #[test]
15605 fn automatic_parallel_policy_needs_only_one_device_set_not_layer_recipes() {
15606 assert_eq!(parse_auto_parallel_devices(None, None).unwrap(), None);
15607 assert_eq!(
15608 parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3")).unwrap(),
15609 Some(vec![0, 1, 2, 3])
15610 );
15611 assert!(parse_auto_parallel_devices(Some("auto"), None).is_err());
15612 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,1")).is_err());
15613 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3,4")).is_err());
15614 assert!(parse_auto_parallel_devices(Some("ep"), Some("0,1")).is_err());
15615 }
15616
15617 #[test]
15618 fn automatic_ep_device_router_flag_is_strict() {
15619 assert!(!parse_parallel_ep_device_router(None).unwrap());
15620 assert!(!parse_parallel_ep_device_router(Some("0")).unwrap());
15621 assert!(parse_parallel_ep_device_router(Some("1")).unwrap());
15622 assert!(parse_parallel_ep_device_router(Some("true")).is_err());
15623 }
15624
15625 #[test]
15626 fn automatic_ep_graph_flag_is_strict_and_defaults_off() {
15627 assert!(!parse_parallel_ep_graph(None).unwrap());
15628 assert!(!parse_parallel_ep_graph(Some("0")).unwrap());
15629 assert!(parse_parallel_ep_graph(Some("1")).unwrap());
15630 assert!(parse_parallel_ep_graph(Some("true")).is_err());
15631 }
15632
15633 #[test]
15634 fn automatic_ep_pair_down_flag_is_strict_and_defaults_off() {
15635 assert!(!parse_parallel_ep_pair_down(None).unwrap());
15636 assert!(!parse_parallel_ep_pair_down(Some("0")).unwrap());
15637 assert!(parse_parallel_ep_pair_down(Some("1")).unwrap());
15638 assert!(parse_parallel_ep_pair_down(Some("true")).is_err());
15639 }
15640
15641 #[test]
15642 fn automatic_ep_q8_activation_flag_is_strict() {
15643 assert!(!parse_parallel_ep_q8_act(None).unwrap());
15644 assert!(!parse_parallel_ep_q8_act(Some("0")).unwrap());
15645 assert!(parse_parallel_ep_q8_act(Some("1")).unwrap());
15646 assert!(parse_parallel_ep_q8_act(Some("true")).is_err());
15647 }
15648
15649 #[test]
15650 fn automatic_ep_q8_scope_is_explicit_and_strict() {
15651 assert_eq!(parse_parallel_ep_q8_scope(None).unwrap(), None);
15652 assert_eq!(
15653 parse_parallel_ep_q8_scope(Some("all")).unwrap(),
15654 Some(ParallelEpQ8Scope::All)
15655 );
15656 assert_eq!(
15657 parse_parallel_ep_q8_scope(Some("gate-up")).unwrap(),
15658 Some(ParallelEpQ8Scope::GateUp)
15659 );
15660 assert_eq!(
15661 parse_parallel_ep_q8_scope(Some("down")).unwrap(),
15662 Some(ParallelEpQ8Scope::Down)
15663 );
15664 assert!(parse_parallel_ep_q8_scope(Some("input")).is_err());
15665 }
15666
15667 #[test]
15668 fn w4a16_device_ep_accepts_a_capacity_backed_active_prefix() {
15669 let width = 4096;
15670 assert_eq!(
15671 nvfp4_ep_active_input_values(160 * width, 44, width).unwrap(),
15672 44 * width
15673 );
15674 assert_eq!(
15675 nvfp4_ep_active_input_values(44 * width, 44, width).unwrap(),
15676 44 * width
15677 );
15678 assert!(nvfp4_ep_active_input_values(43 * width, 44, width).is_err());
15679 assert!(
15680 nvfp4_ep_active_input_values(160 * width, NVFP4_EP_DEVICE_BATCH_CAP + 1, width)
15681 .is_err()
15682 );
15683 }
15684
15685 #[test]
15686 fn step_ep_layer_specs_are_literal_and_fail_closed() {
15687 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
15688 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
15689 assert_eq!(
15690 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
15691 vec![StepEpLayerSpec {
15692 layer: 24,
15693 devices: vec![1, 2],
15694 }]
15695 );
15696 assert_eq!(
15697 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
15698 vec![
15699 StepEpLayerSpec {
15700 layer: 24,
15701 devices: vec![1, 2],
15702 },
15703 StepEpLayerSpec {
15704 layer: 25,
15705 devices: vec![1, 2],
15706 },
15707 StepEpLayerSpec {
15708 layer: 31,
15709 devices: vec![0, 2],
15710 },
15711 ]
15712 );
15713 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
15714 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
15715 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
15716 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
15717 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
15718 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
15719 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
15720 }
15721
15722 #[test]
15723 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
15724 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
15725 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
15726 assert_eq!(
15727 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
15728 vec![
15729 StepTpLayerSpec {
15730 layer: 24,
15731 devices: vec![1, 2],
15732 },
15733 StepTpLayerSpec {
15734 layer: 25,
15735 devices: vec![1, 2],
15736 },
15737 ]
15738 );
15739 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
15740 assert!(error.contains("MEMRA_STEP_TP"));
15741 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
15742 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
15743
15744 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
15745 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
15746 assert_eq!(all.first().unwrap().layer, 0);
15747 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
15748 let devices = (0..8).collect::<Vec<_>>();
15749 assert!(all.iter().all(|spec| spec.devices == devices));
15750 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
15751 }
15752}
15753
15754struct TokenGraphChild {
15766 #[allow(dead_code)]
15767 graph: cudarc::driver::CudaGraph,
15769 node: cudarc::driver::sys::CUgraphNode,
15770 ctx: cudarc::driver::sys::CUcontext,
15771}
15772
15773struct TokenGraphFaSite {
15777 ctx: cudarc::driver::sys::CUcontext,
15778 memset_o: cudarc::driver::sys::CUgraphNode,
15779 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
15780 fa: cudarc::driver::sys::CUgraphNode,
15781 combine: cudarc::driver::sys::CUgraphNode,
15782 window: usize,
15783 n_head: usize,
15784 n_head_kv: usize,
15785 head_dim: usize,
15786}
15787
15788pub struct TokenGraphBuilder {
15789 parent: cudarc::driver::sys::CUgraph,
15790 children: Vec<TokenGraphChild>,
15791 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
15794 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
15797 group: Option<(
15800 u32,
15801 Vec<cudarc::driver::sys::CUgraphNode>,
15802 Vec<cudarc::driver::sys::CUgraphNode>,
15803 )>,
15804}
15805
15806unsafe impl Send for TokenGraphBuilder {}
15808
15809impl TokenGraphBuilder {
15810 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
15811 use cudarc::driver::sys;
15812 let mut parent: sys::CUgraph = std::ptr::null_mut();
15813 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
15814 if r != sys::CUresult::CUDA_SUCCESS {
15815 return Err(format!("token graph create: {r:?}").into());
15816 }
15817 Ok(Self {
15818 parent,
15819 children: Vec::new(),
15820 frontier: Vec::new(),
15821 pending_detached: Vec::new(),
15822 group: None,
15823 })
15824 }
15825
15826 fn push_child(
15827 &mut self,
15828 graph: cudarc::driver::CudaGraph,
15829 parallel_group: Option<u32>,
15830 detached: bool,
15831 absorb: bool,
15832 ctx: cudarc::driver::sys::CUcontext,
15833 ) -> Result<(), Box<dyn std::error::Error>> {
15834 use cudarc::driver::sys;
15835 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
15839 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
15840 (state, Some(group)) => {
15841 if let Some((_, _, members)) = state.take() {
15843 self.frontier = members;
15844 }
15845 let base = self.frontier.clone();
15846 *state = Some((group, base.clone(), Vec::new()));
15847 base
15848 }
15849 (state, None) if detached => match state.as_ref() {
15850 Some((_, base, _)) => base.clone(),
15851 None => self.frontier.clone(),
15852 },
15853 (state, None) => {
15854 if let Some((_, _, members)) = state.take() {
15855 self.frontier = members;
15856 }
15857 let mut deps = self.frontier.clone();
15858 if absorb {
15859 deps.append(&mut self.pending_detached);
15860 }
15861 deps
15862 }
15863 };
15864 let mut node: sys::CUgraphNode = std::ptr::null_mut();
15865 let r = unsafe {
15866 sys::cuGraphAddChildGraphNode(
15867 &mut node,
15868 self.parent,
15869 if deps.is_empty() {
15870 std::ptr::null()
15871 } else {
15872 deps.as_ptr()
15873 },
15874 deps.len(),
15875 graph.cu_graph(),
15876 )
15877 };
15878 if r != sys::CUresult::CUDA_SUCCESS {
15879 return Err(format!("token graph child: {r:?}").into());
15880 }
15881 match (&mut self.group, parallel_group, detached) {
15882 (_, None, true) => self.pending_detached.push(node),
15883 (Some((_, _, members)), Some(_), _) => members.push(node),
15884 _ => self.frontier = vec![node],
15885 }
15886 self.children.push(TokenGraphChild { graph, node, ctx });
15887 Ok(())
15888 }
15889
15890 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
15891 use cudarc::driver::sys;
15892 if let Some((_, _, members)) = self.group.take() {
15893 self.frontier = members;
15894 }
15895 let mut fa_sites = Vec::new();
15898 for child in &self.children {
15899 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
15900 fa_sites.push(site);
15901 }
15902 }
15903 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
15904 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
15905 if r != sys::CUresult::CUDA_SUCCESS {
15906 return Err(format!("token graph instantiate: {r:?}").into());
15907 }
15908 Ok(TokenGraph {
15909 exec,
15910 parent: self.parent,
15911 _children: self.children,
15912 fa_sites,
15913 })
15914 }
15915}
15916
15917fn discover_fa_site(
15920 child_node: cudarc::driver::sys::CUgraphNode,
15921 ctx: cudarc::driver::sys::CUcontext,
15922) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
15923 use cudarc::driver::sys;
15924 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
15925 if r == sys::CUresult::CUDA_SUCCESS {
15926 Ok(())
15927 } else {
15928 Err(format!("{what}: {r:?}").into())
15929 }
15930 }
15931 let mut graph: sys::CUgraph = std::ptr::null_mut();
15932 unsafe {
15933 cu_try(
15934 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
15935 "fa-site child GetGraph",
15936 )?;
15937 }
15938 let mut count: usize = 0;
15939 unsafe {
15940 cu_try(
15941 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
15942 "fa-site GetNodes(count)",
15943 )?;
15944 }
15945 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
15946 unsafe {
15947 cu_try(
15948 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
15949 "fa-site GetNodes",
15950 )?;
15951 }
15952 nodes.truncate(count);
15953 let node_type =
15954 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
15955 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
15956 unsafe {
15957 cu_try(
15958 sys::cuGraphNodeGetType(node, &mut ty),
15959 "fa-site NodeGetType",
15960 )?;
15961 }
15962 Ok(ty)
15963 };
15964 let memsets: Vec<sys::CUgraphNode> = {
15965 let mut v = Vec::new();
15966 for &node in &nodes {
15967 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
15968 v.push(node);
15969 }
15970 }
15971 v
15972 };
15973 if memsets.len() != 3 {
15974 return Ok(None);
15975 }
15976 let dependents =
15978 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
15979 let mut n: usize = 0;
15980 unsafe {
15981 cu_try(
15982 sys::cuGraphNodeGetDependentNodes_v2(
15983 node,
15984 std::ptr::null_mut(),
15985 std::ptr::null_mut(),
15986 &mut n,
15987 ),
15988 "fa-site GetDependentNodes(count)",
15989 )?;
15990 }
15991 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
15992 unsafe {
15993 cu_try(
15994 sys::cuGraphNodeGetDependentNodes_v2(
15995 node,
15996 v.as_mut_ptr(),
15997 std::ptr::null_mut(),
15998 &mut n,
15999 ),
16000 "fa-site GetDependentNodes",
16001 )?;
16002 }
16003 v.truncate(n);
16004 Ok(v)
16005 };
16006 let mut fa: Option<sys::CUgraphNode> = None;
16009 let mut last_memset: Option<sys::CUgraphNode> = None;
16010 for &ms in &memsets {
16011 for dep in dependents(ms)? {
16012 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16013 fa = Some(dep);
16014 last_memset = Some(ms);
16015 }
16016 }
16017 }
16018 let (Some(fa), Some(_last)) = (fa, last_memset) else {
16019 return Ok(None);
16020 };
16021 let mut combine: Option<sys::CUgraphNode> = None;
16022 for dep in dependents(fa)? {
16023 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16024 combine = Some(dep);
16025 }
16026 }
16027 let Some(combine) = combine else {
16028 return Ok(None);
16029 };
16030 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16033 unsafe {
16034 cu_try(
16035 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
16036 "fa-site KernelNodeGetParams",
16037 )?;
16038 }
16039 let arg_i32 =
16040 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
16041 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
16042 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
16044 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16045 unsafe {
16046 cu_try(
16047 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16048 "fa-site MemsetNodeGetParams",
16049 )?;
16050 }
16051 Ok(mp.width)
16052 };
16053 let mut widest = memsets[0];
16054 for &ms in &memsets[1..] {
16055 if width_of(ms)? > width_of(widest)? {
16056 widest = ms;
16057 }
16058 }
16059 let memset_m: Vec<sys::CUgraphNode> =
16060 memsets.iter().copied().filter(|&m| m != widest).collect();
16061 Ok(Some(TokenGraphFaSite {
16062 ctx,
16063 memset_o: widest,
16064 memset_m: [memset_m[0], memset_m[1]],
16065 fa,
16066 combine,
16067 window: win as usize,
16068 n_head: nh as usize,
16069 n_head_kv: nhkv as usize,
16070 head_dim: hd as usize,
16071 }))
16072}
16073
16074pub struct TokenGraph {
16075 exec: cudarc::driver::sys::CUgraphExec,
16076 parent: cudarc::driver::sys::CUgraph,
16077 _children: Vec<TokenGraphChild>,
16078 fa_sites: Vec<TokenGraphFaSite>,
16079}
16080
16081unsafe impl Send for TokenGraph {}
16082
16083impl TokenGraph {
16084 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
16089 use cudarc::driver::sys;
16090 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
16091 if r == sys::CUresult::CUDA_SUCCESS {
16092 Ok(())
16093 } else {
16094 Err(format!("{what}: {r:?}").into())
16095 }
16096 }
16097 for site in &self.fa_sites {
16098 let layer_bucket = if site.window > 0 {
16099 bucket.min(site.window)
16100 } else {
16101 bucket
16102 };
16103 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
16104 let nsp = layer_bucket.div_ceil(sp).max(1);
16105 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16107 unsafe {
16108 cu_try(
16109 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
16110 "retarget fa GetParams",
16111 )?;
16112 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
16113 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
16114 params.gridDimY = nsp as u32;
16115 cu_try(
16116 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
16117 "retarget fa SetParams",
16118 )?;
16119 }
16120 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16122 unsafe {
16123 cu_try(
16124 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
16125 "retarget combine GetParams",
16126 )?;
16127 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
16128 cu_try(
16129 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
16130 "retarget combine SetParams",
16131 )?;
16132 }
16133 let set_width =
16135 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
16136 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16137 unsafe {
16138 cu_try(
16139 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16140 "retarget memset GetParams",
16141 )?;
16142 }
16143 mp.width = width;
16144 unsafe {
16145 cu_try(
16146 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
16147 "retarget memset SetParams",
16148 )?;
16149 }
16150 Ok(())
16151 };
16152 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
16153 set_width(site.memset_m[0], site.n_head * nsp)?;
16154 set_width(site.memset_m[1], site.n_head * nsp)?;
16155 }
16156 Ok(())
16157 }
16158
16159 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
16160 use cudarc::driver::sys;
16161 let _main = e.gpu.enter_main()?;
16162 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
16163 if r != sys::CUresult::CUDA_SUCCESS {
16164 return Err(format!("token graph launch: {r:?}").into());
16165 }
16166 Ok(())
16167 }
16168}
16169
16170impl Drop for TokenGraph {
16171 fn drop(&mut self) {
16172 unsafe {
16173 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
16174 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
16175 }
16176 }
16177}
16178
16179std::thread_local! {
16180 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
16181 const { std::cell::RefCell::new(None) };
16182}
16183
16184pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
16186 let builder = TokenGraphBuilder::new()?;
16187 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
16188 Ok(())
16189}
16190
16191pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
16193 let builder = TOKEN_GRAPH_BUILDER
16194 .with(|cell| cell.borrow_mut().take())
16195 .ok_or("token graph build was not begun")?;
16196 builder.finish()
16197}
16198
16199pub fn token_graph_building() -> bool {
16201 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
16202}
16203
16204pub fn graph_section<F>(
16209 engine: &Engine,
16210 parallel_group: Option<u32>,
16211 f: F,
16212) -> Result<(), Box<dyn std::error::Error>>
16213where
16214 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16215{
16216 graph_section_opts(engine, parallel_group, false, false, f)
16217}
16218
16219pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16221where
16222 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16223{
16224 graph_section_opts(engine, None, false, true, f)
16225}
16226
16227pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16230where
16231 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16232{
16233 graph_section_opts(engine, None, true, false, f)
16234}
16235
16236pub fn graph_section_opts<F>(
16237 engine: &Engine,
16238 parallel_group: Option<u32>,
16239 detached: bool,
16240 absorb: bool,
16241 f: F,
16242) -> Result<(), Box<dyn std::error::Error>>
16243where
16244 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16245{
16246 let building = token_graph_building();
16247 if !building {
16248 let mut f = f;
16249 return f();
16250 }
16251 let (child, ctx) = {
16252 let _main = engine.gpu.enter_main()?;
16253 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
16254 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
16255 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
16256 return Err(format!("graph section ctx query: {r:?}").into());
16257 }
16258 let mut f = f;
16259 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
16262 (child, ctx)
16263 };
16264 TOKEN_GRAPH_BUILDER.with(|cell| {
16265 cell.borrow_mut()
16266 .as_mut()
16267 .expect("builder checked above")
16268 .push_child(child, parallel_group, detached, absorb, ctx)
16269 })
16270}